commit f52cf32e563aa7f5d60c25b25d35cff24835753e Author: wehub-resource-sync Date: Mon Jul 13 13:29:29 2026 +0800 chore: import upstream snapshot with attribution diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..5da60c1 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,62 @@ + + +# Repository Guidelines + +## Project Structure & Module Organization + +Core C++ code lives in `src/`: `src/brpc`, `src/bthread`, +`src/butil`, `src/bvar`, `src/json2pb`, and `src/mcpack2pb`. Tests are in +`test/` and mirror module names. Samples are in `example/`; utilities +are in `tools/`. Documentation is under `docs/en` and `docs/cn`; packaging and +bindings live in `package/`, `homebrew-formula/`, `python/`, and `java/`. + +## Build, Test, and Development Commands + +- `sh config_brpc.sh --headers=/usr/include --libs=/usr/lib && make`: configure and build with Make. +- `cmake -B build && cmake --build build -j6`: configure and build with CMake. +- `cmake -B build -DBUILD_UNIT_TESTS=ON && cmake --build build -j6 && cd build && ctest`: run CMake tests. +- `cd test && make && sh run_tests.sh`: run the Make-based test suite. +- `bazel build //:brpc` and `bazel test //test/...`: build or test with Bazel. +- `cd example/echo_c++ && make && ./echo_server & ./echo_client`: smoke-test an example. + +## Coding Style & Naming Conventions + +Follow Google C++ style with 4-space indentation. Keep feature-specific code in +the relevant protocol or module, not broad files such as `server.cpp` or +`channel.cpp`, unless the behavior is general. Use existing names: +`*_unittest.cpp` or `*_unittest.cc`, module prefixes such as `brpc_`, +`bthread_`, and `bvar_`, and `.proto` files beside related code. + +## Testing Guidelines + +New behavior should include unit tests. Run the smallest relevant test first, +then the broader affected suite. Tests use Google Test and live in `test/`. +Some integration tests require Redis or MySQL and may skip when absent. + +## Commit & Pull Request Guidelines + +Recent history uses short imperative subjects such as `Fix bazel compile error +on macOS`. Keep commits focused, explain behavioral impact when needed, and +link issues. Pull requests should describe the change, list tests run, note +platform or dependency assumptions, and pass GitHub Actions. + +## Security & Configuration Tips + +Read `SECURITY.md` before reporting vulnerabilities. Avoid committing +build directories, local paths, credentials, or machine-specific configuration. +Prefer flags such as `--with-glog`, `--with-thrift`, `--with-asan`, +or the matching CMake/Bazel options. diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 0000000..b51ee0f --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,582 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "objc_library") +load("//bazel/tools:brpc_proto_library.bzl", "brpc_proto_library") + +licenses(["notice"]) # Apache v2 + +exports_files(["LICENSE"]) + +COPTS = [ + "-fno-omit-frame-pointer", +] + select({ + "//bazel/config:brpc_with_asan": ["-fsanitize=address"], + "//conditions:default": [], +}) + +DEFINES = [ + "BTHREAD_USE_FAST_PTHREAD_MUTEX", + "__const__=__unused__", + "_GNU_SOURCE", + "USE_SYMBOLIZE", + "NO_TCMALLOC", + "__STDC_FORMAT_MACROS", + "__STDC_LIMIT_MACROS", + "__STDC_CONSTANT_MACROS", +] + select({ + "//bazel/config:brpc_with_glog": ["BRPC_WITH_GLOG=1"], + "//conditions:default": ["BRPC_WITH_GLOG=0"], + }) + select({ + "//bazel/config:brpc_with_mesalink": ["USE_MESALINK"], + "//conditions:default": [], + }) + select({ + "//bazel/config:brpc_with_thrift": ["ENABLE_THRIFT_FRAMED_PROTOCOL=1"], + "//conditions:default": [], + }) + select({ + "//bazel/config:brpc_with_thrift_legacy_version": [], + "//conditions:default": ["THRIFT_STDCXX=std"], + }) + select({ + "//bazel/config:brpc_with_rdma": ["BRPC_WITH_RDMA=1"], + "//conditions:default": [], + }) + select({ + "//bazel/config:brpc_with_debug_bthread_sche_safety": ["BRPC_DEBUG_BTHREAD_SCHE_SAFETY=1"], + "//conditions:default": ["BRPC_DEBUG_BTHREAD_SCHE_SAFETY=0"], + }) + select({ + "//bazel/config:brpc_with_debug_lock": ["BRPC_DEBUG_LOCK=1"], + "//conditions:default": ["BRPC_DEBUG_LOCK=0"], + }) + select({ + "//bazel/config:brpc_with_no_pthread_mutex_hook": ["NO_PTHREAD_MUTEX_HOOK"], + "//conditions:default": [], + }) + +LINKOPTS = [ + "-pthread", + "-ldl", +] + select({ + "@bazel_tools//src/conditions:darwin": [ + "-framework CoreFoundation", + "-framework CoreGraphics", + "-framework CoreData", + "-framework CoreText", + "-framework Security", + "-framework Foundation", + "-Wl,-U,_MallocExtension_ReleaseFreeMemory", + "-Wl,-U,_ProfilerStart", + "-Wl,-U,_ProfilerStop", + "-Wl,-U,__Z13GetStackTracePPvii", + "-Wl,-U,_RegisterThriftProtocol", + "-Wl,-U,_mallctl", + "-Wl,-U,_malloc_stats_print", + ], + "//conditions:default": [ + "-lrt", + ], +}) + select({ + "//bazel/config:brpc_with_mesalink": [ + "-lmesalink", + ], + "//conditions:default": [], +}) + select({ + "//bazel/config:brpc_with_rdma": [ + "-libverbs", + ], + "//conditions:default": [], +}) + select({ + "//bazel/config:brpc_with_asan": ["-fsanitize=address"], + "//conditions:default": [], + }) + +genrule( + name = "config_h", + outs = [ + "src/butil/config.h", + ], + cmd = """cat << EOF >$@""" + """ +// This file is auto-generated. +#ifndef BUTIL_CONFIG_H +#define BUTIL_CONFIG_H +#ifdef BRPC_WITH_GLOG +#undef BRPC_WITH_GLOG +#endif +#define BRPC_WITH_GLOG """ + select({ + "//bazel/config:brpc_with_glog": "1", + "//conditions:default": "0", + }) + + """ +#endif // BUTIL_CONFIG_H +EOF + """, +) + +BUTIL_SRCS = [ + "src/butil/third_party/dmg_fp/g_fmt.cc", + "src/butil/third_party/dmg_fp/dtoa_wrapper.cc", + "src/butil/third_party/dynamic_annotations/dynamic_annotations.c", + "src/butil/third_party/icu/icu_utf.cc", + "src/butil/third_party/superfasthash/superfasthash.c", + "src/butil/third_party/modp_b64/modp_b64.cc", + "src/butil/third_party/symbolize/demangle.cc", + "src/butil/third_party/symbolize/symbolize.cc", + "src/butil/third_party/snappy/snappy-sinksource.cc", + "src/butil/third_party/snappy/snappy-stubs-internal.cc", + "src/butil/third_party/snappy/snappy.cc", + "src/butil/third_party/murmurhash3/murmurhash3.cpp", + "src/butil/arena.cpp", + "src/butil/at_exit.cc", + "src/butil/atomicops_internals_x86_gcc.cc", + "src/butil/base64.cc", + "src/butil/base64url.cc", + "src/butil/big_endian.cc", + "src/butil/cpu.cc", + "src/butil/debug/alias.cc", + "src/butil/debug/asan_invalid_access.cc", + "src/butil/debug/crash_logging.cc", + "src/butil/debug/debugger.cc", + "src/butil/debug/debugger_posix.cc", + "src/butil/debug/dump_without_crashing.cc", + "src/butil/debug/proc_maps_linux.cc", + "src/butil/debug/stack_trace.cc", + "src/butil/debug/stack_trace_posix.cc", + "src/butil/environment.cc", + "src/butil/files/file.cc", + "src/butil/files/file_posix.cc", + "src/butil/files/file_enumerator.cc", + "src/butil/files/file_enumerator_posix.cc", + "src/butil/files/file_path.cc", + "src/butil/files/file_path_constants.cc", + "src/butil/files/memory_mapped_file.cc", + "src/butil/files/memory_mapped_file_posix.cc", + "src/butil/files/scoped_file.cc", + "src/butil/files/scoped_temp_dir.cc", + "src/butil/file_util.cc", + "src/butil/file_util_posix.cc", + "src/butil/guid.cc", + "src/butil/guid_posix.cc", + "src/butil/hash.cc", + "src/butil/lazy_instance.cc", + "src/butil/location.cc", + "src/butil/memory/aligned_memory.cc", + "src/butil/memory/ref_counted.cc", + "src/butil/memory/ref_counted_memory.cc", + "src/butil/memory/singleton.cc", + "src/butil/memory/weak_ptr.cc", + "src/butil/posix/file_descriptor_shuffle.cc", + "src/butil/posix/global_descriptors.cc", + "src/butil/process_util.cc", + "src/butil/rand_util.cc", + "src/butil/rand_util_posix.cc", + "src/butil/fast_rand.cpp", + "src/butil/safe_strerror_posix.cc", + "src/butil/sha1_portable.cc", + "src/butil/strings/latin1_string_conversions.cc", + "src/butil/strings/nullable_string16.cc", + "src/butil/strings/safe_sprintf.cc", + "src/butil/strings/string16.cc", + "src/butil/strings/string_number_conversions.cc", + "src/butil/strings/string_split.cc", + "src/butil/strings/string_piece.cc", + "src/butil/strings/string_util.cc", + "src/butil/strings/string_util_constants.cc", + "src/butil/strings/stringprintf.cc", + "src/butil/strings/utf_offset_string_conversions.cc", + "src/butil/strings/utf_string_conversion_utils.cc", + "src/butil/strings/utf_string_conversions.cc", + "src/butil/synchronization/cancellation_flag.cc", + "src/butil/synchronization/condition_variable_posix.cc", + "src/butil/synchronization/waitable_event_posix.cc", + "src/butil/threading/non_thread_safe_impl.cc", + "src/butil/threading/platform_thread_posix.cc", + "src/butil/threading/simple_thread.cc", + "src/butil/threading/thread_checker_impl.cc", + "src/butil/threading/thread_collision_warner.cc", + "src/butil/threading/thread_id_name_manager.cc", + "src/butil/threading/thread_local_posix.cc", + "src/butil/threading/thread_local_storage.cc", + "src/butil/threading/thread_local_storage_posix.cc", + "src/butil/threading/thread_restrictions.cc", + "src/butil/threading/watchdog.cc", + "src/butil/time/clock.cc", + "src/butil/time/default_clock.cc", + "src/butil/time/default_tick_clock.cc", + "src/butil/time/tick_clock.cc", + "src/butil/time/time.cc", + "src/butil/time/time_posix.cc", + "src/butil/version.cc", + "src/butil/logging.cc", + "src/butil/class_name.cpp", + "src/butil/errno.cpp", + "src/butil/find_cstr.cpp", + "src/butil/status.cpp", + "src/butil/string_printf.cpp", + "src/butil/thread_local.cpp", + "src/butil/thread_key.cpp", + "src/butil/unix_socket.cpp", + "src/butil/endpoint.cpp", + "src/butil/fd_utility.cpp", + "src/butil/files/temp_file.cpp", + "src/butil/files/file_watcher.cpp", + "src/butil/time.cpp", + "src/butil/zero_copy_stream_as_streambuf.cpp", + "src/butil/crc32c.cc", + "src/butil/containers/case_ignored_flat_map.cpp", + "src/butil/iobuf.cpp", + "src/butil/single_iobuf.cpp", + "src/butil/iobuf_profiler.cpp", + "src/butil/binary_printer.cpp", + "src/butil/recordio.cc", + "src/butil/popen.cpp", +] + select({ + "@bazel_tools//src/conditions:darwin": [ + "src/butil/time/time_mac.cc", + "src/butil/mac/scoped_mach_port.cc", + ], + "//conditions:default": [ + "src/butil/file_util_linux.cc", + "src/butil/threading/platform_thread_linux.cc", + "src/butil/strings/sys_string_conversions_posix.cc", + ], +}) + +objc_library( + name = "macos_lib", + hdrs = [ + "src/butil/atomicops.h", + "src/butil/atomicops_internals_atomicword_compat.h", + "src/butil/atomicops_internals_mac.h", + "src/butil/base_export.h", + "src/butil/basictypes.h", + "src/butil/build_config.h", + "src/butil/compat.h", + "src/butil/compiler_specific.h", + "src/butil/containers/hash_tables.h", + "src/butil/debug/debugger.h", + "src/butil/debug/leak_annotations.h", + "src/butil/file_descriptor_posix.h", + "src/butil/file_util.h", + "src/butil/files/file.h", + "src/butil/files/file_path.h", + "src/butil/files/scoped_file.h", + "src/butil/lazy_instance.h", + "src/butil/logging.h", + "src/butil/mac/bundle_locations.h", + "src/butil/mac/foundation_util.h", + "src/butil/mac/scoped_cftyperef.h", + "src/butil/mac/scoped_typeref.h", + "src/butil/macros.h", + "src/butil/string_printf.h", + "src/butil/memory/aligned_memory.h", + "src/butil/memory/scoped_policy.h", + "src/butil/memory/scoped_ptr.h", + "src/butil/move.h", + "src/butil/port.h", + "src/butil/posix/eintr_wrapper.h", + "src/butil/scoped_generic.h", + "src/butil/strings/string16.h", + "src/butil/strings/string_piece.h", + "src/butil/strings/string_util.h", + "src/butil/strings/string_util_posix.h", + "src/butil/strings/sys_string_conversions.h", + "src/butil/synchronization/lock.h", + "src/butil/third_party/dynamic_annotations/dynamic_annotations.h", + "src/butil/third_party/murmurhash3/murmurhash3.h", + "src/butil/threading/platform_thread.h", + "src/butil/threading/thread_id_name_manager.h", + "src/butil/threading/thread_restrictions.h", + "src/butil/time.h", + "src/butil/time/time.h", + "src/butil/type_traits.h", + ":config_h", + ], + enable_modules = True, + includes = ["src/"], + non_arc_srcs = [ + "src/butil/mac/bundle_locations.mm", + "src/butil/mac/foundation_util.mm", + "src/butil/file_util_mac.mm", + "src/butil/threading/platform_thread_mac.mm", + "src/butil/strings/sys_string_conversions_mac.mm", + ], + tags = ["manual"], + deps = [ + "@com_github_gflags_gflags//:gflags", + ] + select({ + "//bazel/config:brpc_with_glog": ["@com_github_google_glog//:glog"], + "//conditions:default": [], + }), +) + +cc_library( + name = "butil", + srcs = BUTIL_SRCS, + hdrs = glob([ + "src/butil/*.h", + "src/butil/*.hpp", + "src/butil/**/*.h", + "src/butil/**/*.hpp", + "src/butil/**/**/*.h", + "src/butil/**/**/*.hpp", + "src/butil/**/**/**/*.h", + "src/butil/**/**/**/*.hpp", + ]) + [ + "src/butil/third_party/dmg_fp/dtoa.cc", + ":config_h", + ], + defines = DEFINES + select({ + "//bazel/config:brpc_build_for_unittest": [ + "UNIT_TEST", + ], + "//conditions:default": [], + }), + copts = COPTS, + includes = [ + "src/", + ], + linkopts = LINKOPTS, + visibility = ["//visibility:public"], + deps = [ + "@com_github_gflags_gflags//:gflags", + "@com_github_madler_zlib//:zlib", + "@com_google_protobuf//:protobuf", + ] + select({ + "//bazel/config:brpc_with_glog": ["@com_github_google_glog//:glog"], + "//conditions:default": [], + }) + select({ + "@bazel_tools//src/conditions:darwin": [":macos_lib"], + "//conditions:default": [], + }) + select({ + "//bazel/config:brpc_with_boringssl": [ + "@boringssl//:ssl", + "@boringssl//:crypto" + ], + "//conditions:default": [ + "@openssl//:ssl", + "@openssl//:crypto" + ], + }), +) + +cc_library( + name = "bvar", + srcs = glob( + [ + "src/bvar/*.cpp", + "src/bvar/detail/*.cpp", + ], + exclude = [ + "src/bvar/default_variables.cpp", + ], + ) + select({ + "//bazel/config:brpc_build_for_unittest": [], + "//conditions:default": ["src/bvar/default_variables.cpp"], + }), + hdrs = glob([ + "src/bvar/*.h", + "src/bvar/utils/*.h", + "src/bvar/detail/*.h", + ]), + defines = [] + select({ + "//bazel/config:with_babylon_counter": ["WITH_BABYLON_COUNTER=1"], + "//conditions:default": [], + }) + select({ + "//bazel/config:brpc_build_for_unittest": [ + "BVAR_NOT_LINK_DEFAULT_VARIABLES", + ], + "//conditions:default": [], + }), + copts = COPTS, + includes = [ + "src/", + ], + linkopts = LINKOPTS, + visibility = ["//visibility:public"], + deps = [ + ":butil", + ] + select({ + "//bazel/config:with_babylon_counter": ["@babylon//:concurrent_counter"], + "//conditions:default": [], + }), +) + +cc_library( + name = "bthread", + srcs = glob([ + "src/bthread/*.cpp", + ]), + hdrs = glob([ + "src/bthread/*.h", + "src/bthread/*.list", + ]), + defines = [] + select({ + "//bazel/config:brpc_with_bthread_tracer": [ + "BRPC_BTHREAD_TRACER", + ], + "//conditions:default": [], + }), + copts = COPTS, + includes = [ + "src/", + ], + linkopts = LINKOPTS, + visibility = ["//visibility:public"], + deps = [ + ":butil", + ":bvar", + ] + select({ + "//bazel/config:brpc_with_bthread_tracer": [ + "@com_github_libunwind_libunwind//:libunwind", + "@com_google_absl//absl/debugging:stacktrace", + "@com_google_absl//absl/debugging:symbolize", + ], + "//conditions:default": [], + }), +) + +cc_library( + name = "json2pb", + srcs = glob([ + "src/json2pb/*.cpp", + ]), + hdrs = glob([ + "src/json2pb/*.h", + ]), + copts = COPTS, + includes = [ + "src/", + ], + linkopts = LINKOPTS, + visibility = ["//visibility:public"], + deps = [ + ":butil", + ], +) + +cc_library( + name = "mcpack2pb", + srcs = [ + "src/mcpack2pb/field_type.cpp", + "src/mcpack2pb/mcpack2pb.cpp", + "src/mcpack2pb/parser.cpp", + "src/mcpack2pb/serializer.cpp", + ], + hdrs = glob([ + "src/mcpack2pb/*.h", + ]), + copts = COPTS, + includes = [ + "src/", + ], + linkopts = LINKOPTS, + visibility = ["//visibility:public"], + deps = [ + ":brpc_idl_options_cc_proto", + ":butil", + ], +) + +filegroup( + name = "brpc_idl_options_proto_srcs", + srcs = [ + "src/idl_options.proto", + ], + visibility = ["//visibility:public"], +) + +brpc_proto_library( + name = "brpc_idl_options_cc_proto", + srcs = [":brpc_idl_options_proto_srcs"], + include = "src", + visibility = ["//visibility:public"], +) + +filegroup( + name = "brpc_internal_proto_srcs", + srcs = glob([ + "src/brpc/*.proto", + "src/brpc/policy/*.proto", + "src/brpc/rdma/*.proto", + ]), + visibility = ["//visibility:public"], +) + +brpc_proto_library( + name = "brpc_internal_cc_proto", + srcs = [":brpc_internal_proto_srcs"], + include = "src", + deps = [":brpc_idl_options_cc_proto"], + visibility = ["//visibility:public"], +) + +cc_library( + name = "brpc", + srcs = glob([ + "src/brpc/*.cpp", + "src/brpc/**/*.cpp", + ], + exclude = [ + "src/brpc/thrift_service.cpp", + "src/brpc/thrift_message.cpp", + "src/brpc/policy/thrift_protocol.cpp", + "src/brpc/event_dispatcher_epoll.cpp", + "src/brpc/event_dispatcher_kqueue.cpp", + ]) + select({ + "//bazel/config:brpc_with_thrift": glob([ + "src/brpc/thrift*.cpp", + "src/brpc/**/thrift*.cpp", + ]), + "//conditions:default": [], + }), + hdrs = glob([ + "src/brpc/*.h", + "src/brpc/**/*.h", + "src/brpc/event_dispatcher_epoll.cpp", + "src/brpc/event_dispatcher_kqueue.cpp", + ]), + copts = COPTS, + includes = [ + "src/", + ], + linkopts = LINKOPTS, + visibility = ["//visibility:public"], + deps = [ + ":brpc_internal_cc_proto", + ":bthread", + ":butil", + ":bvar", + ":json2pb", + ":mcpack2pb", + "@com_github_google_leveldb//:leveldb", + ] + select({ + "//bazel/config:brpc_with_thrift": [ + "@org_apache_thrift//:thrift", + ], + "//conditions:default": [], + }), +) + +cc_binary( + name = "protoc-gen-mcpack", + srcs = [ + "src/mcpack2pb/generator.cpp", + ], + copts = COPTS, + linkopts = LINKOPTS, + visibility = ["//visibility:public"], + deps = [ + ":brpc", + ":brpc_idl_options_cc_proto", + "@com_google_protobuf//:protoc_lib", + ], +) diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..41acb2e --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,61 @@ + + +# Table of Contents + +- [0.9.7](#0.9.7) +- [0.9.6](#0.9.6) +- [0.9.5](#0.9.5) +- [0.9.0](#0.9.0) + +## 0.9.7 +* Add DISCLAIMER-WIP as license issues are not all resolved +* Fix many license related issues +* Ignore flow control in h2 when sending first request +* Add flame graph view for profiling builtin service +* Fix bug that _avg_latency maybe zero in lalb +* Fix bug that logging namespace conflicts with others +* Add gdb_bthread_stack.py to read bthread stack +* Adapt to Arm64 +* Support redis server protocol +* Enable circuit breaker for backup request +* Support zone for bilibili discovery naming service when fetching server nodes +* Add brpc revision in release version + +## 0.9.6 +* Health (of a connection) can be checked at rpc-level +* Fix SSL-related compilation issues on Mac +* Support SSL-replacement lib MesaLink +* Support consistent hashing with ketama algo. +* bvar variables can be exported for prometheus services +* String[Multi]Splitter supports '\0' as separator +* Support for bilibili discovery service +* Improved CircuitBreaker +* grpc impl. supports timeout + +## 0.9.5 +* h2c/grpc are supported now, h2(encrypted) is not included. +* thrift support. +* Mac build support +* Extend ConcurrencyLimiter to control max-concurrency dynamically and an "auto" CL is supported by default +* CircuitBreaker support to isolate abnormal servers more effectively + +## 0.9.0 +* Contains major features of brpc, OK for production usages. +* No h2/h2c/rdma support, Mac/Windows ports are not ready yet. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..5617963 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,123 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Apache bRPC is an industrial-grade C++ RPC framework supporting multiple protocols (baidu_std, HTTP/H2, gRPC, thrift, redis, memcached, RTMP, RDMA) on the same port. Used in high-performance systems: search, storage, ML, ads, recommendations. Current version: 1.17.0. + +## Build Commands + +### Make (primary) + +```bash +# Generate config (required before first build) +./config_brpc.sh --headers=/usr/include --libs=/usr/lib + +# Build library (produces libbrpc.a and libbrpc.so/dylib) +make -j$(nproc) + +# Build debug version (with UNIT_TEST flag, no NDEBUG) +make debug +``` + +Config options: `--with-glog`, `--with-thrift`, `--with-rdma`, `--with-asan`, `--werror` + +### CMake + +```bash +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Release +make -j$(nproc) +``` + +Key CMake options: `-DWITH_GLOG=ON`, `-DWITH_THRIFT=ON`, `-DWITH_RDMA=ON`, `-DWITH_ASAN=ON`, `-DBUILD_UNIT_TESTS=ON`, `-DDOWNLOAD_GTEST=ON` + +### Bazel + +```bash +bazel build -- //... -//example/... +``` + +## Running Tests + +Tests use Google Test. Build and run with Make: + +```bash +cd test +make -j$(nproc) +./run_tests.sh # runs all: test_butil, test_bvar, bthread_*unittest, brpc_*unittest +``` + +Run a single test binary: + +```bash +cd test +./ # e.g. ./test_butil +./ --gtest_filter='TestSuite.TestName' # single test case +``` + +With CMake: + +```bash +cmake .. -DBUILD_UNIT_TESTS=ON -DDOWNLOAD_GTEST=ON +make -j$(nproc) && ctest +``` + +ASAN is used in CI: `ASAN_OPTIONS="detect_leaks=0:detect_stack_use_after_return=1" ./` + +## Architecture + +### Core Libraries (under `src/`) + +- **brpc/** — The RPC framework. Three core abstractions: + - `Server` — listens on a port, dispatches requests to registered services. Supports multiple protocols on the same port via protocol detection. + - `Channel` — client-side stub for sending RPCs. Configured with naming service, load balancer, timeout, retry policies via `ChannelOptions`. + - `Controller` — per-RPC context carrying request metadata, error state, timeout, attachments. Extends `google::protobuf::RpcController`. + - `Socket` (`socket.h`) — low-level connection abstraction managing fd lifecycle, SSL, and write buffering. Uses `VersionedRefWithId` for safe concurrent access. + +- **bthread/** — M:N user-level threading (the concurrency foundation of brpc) + - `TaskControl` manages a pool of worker pthreads, each running a `TaskGroup` + - `TaskGroup` owns a local run queue + work-stealing queue (`work_stealing_queue.h`, lock-free CAS-based) + - `bthread_start_urgent()` runs task immediately on current worker; `bthread_start_background()` enqueues for later scheduling + - Most brpc callbacks execute in bthreads, not pthreads + +- **butil/** — Base utility library (originally forked from Chromium) + - `IOBuf` (`iobuf.h`) — zero-copy buffer using reference-counted blocks with SmallView (2 inline BlockRefs) / BigView (heap) optimization. Core data structure for network I/O. + - Also: FlatMap, logging, string utils, time, files, containers + - `third_party/` — Bundled snappy, murmurhash3, symbolize, etc. + +- **bvar/** — Multi-dimensional statistics variables + - Thread-local aggregation via `AgentCombiner` for lock-free counters + - Types: `Adder`, `Recorder`, `LatencyRecorder`, `PassiveStatus` + - Composable windows: `PerSecond>`, `Window<>` + - Auto-exposed via builtin `/vars` endpoint + +- **json2pb/** — Bidirectional JSON <-> Protobuf conversion + +- **mcpack2pb/** — MCPack format <-> Protobuf conversion (Baidu legacy) + +### Key Design Patterns + +- **Protocol plugins** (`src/brpc/policy/`): Each protocol implements the `Protocol` struct (function pointers for Parse/Serialize/Pack/Process/Verify in `protocol.h`), registered via `RegisterProtocol()`. 18 protocols implemented. Adding a new protocol does not touch core files — see `docs/en/new_protocol.md`. +- **Naming services / Load balancers**: Pluggable via `NamingService` and `LoadBalancer` interfaces (DNS, ZK, etcd, round-robin, consistent hashing, locality-aware, etc.) +- **Builtin services** (`src/brpc/builtin/`): Every brpc server auto-exposes debug endpoints — `/status`, `/vars`, `/flags`, `/rpcz`, `/hotspots` (cpu/heap/contention profilers). +- **Error model**: Functions return 0/-1 with errno; `Controller::SetFailed()` for RPC-level errors; custom error codes defined in `errno.proto`. +- **Memory patterns**: `ResourcePool` / `ObjectPool` for socket and bthread recycling; `butil::intrusive_ptr` for hot-path reference counting; IOBuf for zero-copy I/O. + +## Code Style + +- Google C++ Style Guide with **4-space indentation** +- Protocol-specific code goes in `src/brpc/policy/`, not in core files like `server.cpp` or `channel.cpp` +- General modifications should not be hidden inside protocol-specific files +- All changes require unit tests +- CI runs on GitHub Actions (Linux gcc/clang, macOS) — must pass before merge +- Uses C++11 minimum; some C++17 features with compiler guards. Legacy patterns (`DISALLOW_COPY_AND_ASSIGN` macro) still prevalent. + +## Dependencies + +Required: protobuf (3.x–21.x), gflags, leveldb, openssl. Optional: glog, thrift, gperftools (tcmalloc/profiler), gtest (for tests), libunwind, abseil-cpp, BoringSSL (alternative to openssl). + +## Examples + +30+ examples in `example/` covering echo, HTTP, gRPC, streaming, redis, memcache, thrift, RDMA, coroutine, etc. Each has its own Makefile/CMakeLists.txt/BUILD. diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..130860a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,654 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(brpc C CXX) + +option(WITH_GLOG "With glog" OFF) +option(WITH_MESALINK "With MesaLink" OFF) +option(WITH_BORINGSSL "With BoringSSL" OFF) +option(DEBUG "Print debug logs" OFF) +option(WITH_DEBUG_SYMBOLS "With debug symbols" ON) +option(WITH_THRIFT "With thrift framed protocol supported" OFF) +option(WITH_BTHREAD_TRACER "With bthread tracer supported" OFF) +option(WITH_SNAPPY "With snappy" OFF) +option(WITH_RDMA "With RDMA" OFF) +option(WITH_DEBUG_BTHREAD_SCHE_SAFETY "With debugging bthread sche safety" OFF) +option(WITH_DEBUG_LOCK "With debugging lock" OFF) +option(WITH_ASAN "With AddressSanitizer" OFF) +option(BUILD_UNIT_TESTS "Whether to build unit tests" OFF) +option(BUILD_FUZZ_TESTS "Whether to build fuzz tests" OFF) +option(BUILD_BRPC_TOOLS "Whether to build brpc tools" ON) +option(DOWNLOAD_GTEST "Download and build a fresh copy of googletest. Requires Internet access." ON) + +# Enable MACOSX_RPATH. Run "cmake --help-policy CMP0042" for policy details. +if(POLICY CMP0042) + cmake_policy(SET CMP0042 NEW) +endif() + +set(BRPC_VERSION 1.17.0) + +SET(CPACK_GENERATOR "DEB") +SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "brpc authors") +INCLUDE(CPack) + +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # require at least gcc 4.8 + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.8) + message(FATAL_ERROR "GCC is too old, please install a newer version supporting C++11") + endif() +elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # require at least clang 3.3 + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.3) + message(FATAL_ERROR "Clang is too old, please install a newer version supporting C++11") + endif() +else() + message(WARNING "You are using an unsupported compiler! Compilation has only been tested with Clang and GCC.") +endif() + +set(BRPC_COMMON_COMPILE_OPTIONS) +set(BRPC_COMMON_DEFINITIONS) +set(BRPC_COMMON_INCLUDE_DIRS + ${PROJECT_SOURCE_DIR}/src + ${CMAKE_CURRENT_BINARY_DIR} +) +set(BRPC_COMMON_LINK_OPTIONS) +set(BRPC_CXX_STANDARD 11) + +set(WITH_GLOG_VAL "0") +if(WITH_GLOG) + set(WITH_GLOG_VAL "1") + set(BRPC_WITH_GLOG 1) +endif() + +if(WITH_DEBUG_SYMBOLS) + list(APPEND BRPC_COMMON_COMPILE_OPTIONS -g) +endif() + +set(WITH_DEBUG_LOCK_VAL "0") +if(WITH_DEBUG_LOCK) + set(WITH_DEBUG_LOCK_VAL "1") +endif() + +if(WITH_THRIFT) + list(APPEND BRPC_COMMON_DEFINITIONS ENABLE_THRIFT_FRAMED_PROTOCOL) + find_library(THRIFT_LIB NAMES thrift) + if (NOT THRIFT_LIB) + message(FATAL_ERROR "Fail to find Thrift") + endif() +endif() + +if (WITH_BTHREAD_TRACER) + if (NOT (CMAKE_SYSTEM_NAME STREQUAL "Linux") OR NOT (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")) + message(FATAL_ERROR "bthread tracer is only supported on Linux x86_64 platform") + endif() + find_path(LIBUNWIND_INCLUDE_PATH NAMES libunwind.h) + find_library(LIBUNWIND_LIB NAMES unwind) + find_library(LIBUNWIND_X86_64_LIB NAMES unwind-x86_64) + if (NOT LIBUNWIND_INCLUDE_PATH OR NOT LIBUNWIND_LIB) + message(FATAL_ERROR "Fail to find libunwind, which is needed by bthread tracer") + endif() + find_package(absl REQUIRED CONFIG) + set(bthread_tracer_ABSL_USED_TARGETS absl::base absl::stacktrace absl::symbolize) + list(APPEND BRPC_COMMON_DEFINITIONS BRPC_BTHREAD_TRACER) + list(APPEND BRPC_COMMON_INCLUDE_DIRS ${LIBUNWIND_INCLUDE_PATH}) +endif () + +set(WITH_RDMA_VAL "0") +if(WITH_RDMA) + set(WITH_RDMA_VAL "1") +endif() + +set(WITH_DEBUG_BTHREAD_SCHE_SAFETY_VAL "0") +if(WITH_DEBUG_BTHREAD_SCHE_SAFETY) + set(WITH_DEBUG_BTHREAD_SCHE_SAFETY_VAL "1") +endif() + +include(GNUInstallDirs) + +configure_file(${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_SOURCE_DIR}/src/butil/config.h @ONLY) + +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") + +find_package(GFLAGS REQUIRED) + +execute_process( + COMMAND bash -c "${PROJECT_SOURCE_DIR}/tools/get_brpc_revision.sh ${PROJECT_SOURCE_DIR} | tr -d '\n'" + OUTPUT_VARIABLE BRPC_REVISION +) +string(REPLACE "\\|" "|" BRPC_REVISION "${BRPC_REVISION}") + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + include(CheckFunctionExists) + CHECK_FUNCTION_EXISTS(clock_gettime HAVE_CLOCK_GETTIME) + if(NOT HAVE_CLOCK_GETTIME) + list(APPEND BRPC_COMMON_DEFINITIONS NO_CLOCK_GETTIME_IN_MAC) + endif() + list(APPEND BRPC_COMMON_COMPILE_OPTIONS + -Wno-deprecated-declarations + -Wno-inconsistent-missing-override + ) +endif() + +list(APPEND BRPC_COMMON_DEFINITIONS + BRPC_WITH_GLOG=${WITH_GLOG_VAL} + BRPC_WITH_RDMA=${WITH_RDMA_VAL} + BRPC_DEBUG_BTHREAD_SCHE_SAFETY=${WITH_DEBUG_BTHREAD_SCHE_SAFETY_VAL} + BRPC_DEBUG_LOCK=${WITH_DEBUG_LOCK_VAL} + BTHREAD_USE_FAST_PTHREAD_MUTEX + __const__=__unused__ + _GNU_SOURCE + USE_SYMBOLIZE + NO_TCMALLOC + __STDC_FORMAT_MACROS + __STDC_LIMIT_MACROS + __STDC_CONSTANT_MACROS + __STRICT_ANSI__ +) +if(NOT DEBUG) + list(APPEND BRPC_COMMON_DEFINITIONS NDEBUG) +endif() +if(WITH_ASAN) + list(APPEND BRPC_COMMON_COMPILE_OPTIONS -fsanitize=address) + list(APPEND BRPC_COMMON_LINK_OPTIONS -fsanitize=address) +endif() +if(WITH_MESALINK) + list(APPEND BRPC_COMMON_DEFINITIONS USE_MESALINK) +endif() + +list(APPEND BRPC_COMMON_COMPILE_OPTIONS + -O2 + -pipe + -Wall + -W + -fPIC + -fstrict-aliasing + -Wno-unused-parameter + -fno-omit-frame-pointer + "-DBRPC_REVISION=\"${BRPC_REVISION}\"" + $<$:-Wno-invalid-offsetof> +) + +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + #required by butil/crc32.cc to boost performance for 10x + if((CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)") AND NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.4)) + list(APPEND BRPC_COMMON_COMPILE_OPTIONS + $<$:-msse4> + $<$:-msse4.2> + ) + elseif((CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")) + # segmentation fault in libcontext + list(APPEND BRPC_COMMON_COMPILE_OPTIONS $<$:-fno-gcse>) + elseif((CMAKE_SYSTEM_PROCESSOR MATCHES "riscv64")) + # RISC-V specific optimizations + option(WITH_RISCV_ZBC "Enable RISC-V Zbc carry-less multiplication for CRC32C acceleration" OFF) + option(WITH_RISCV_ZVBC "Enable RISC-V Zvbc vector carry-less multiplication for CRC32C acceleration" OFF) + if(WITH_RISCV_ZVBC) + list(APPEND BRPC_COMMON_COMPILE_OPTIONS $<$:-march=rv64gcv_zbc_zvbc>) + elseif(WITH_RISCV_ZBC) + list(APPEND BRPC_COMMON_COMPILE_OPTIONS $<$:-march=rv64gc_zbc>) + else() + list(APPEND BRPC_COMMON_COMPILE_OPTIONS $<$:-march=rv64gc>) + endif() + endif() + if(NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0)) + list(APPEND BRPC_COMMON_COMPILE_OPTIONS $<$:-Wno-aligned-new>) + endif() +endif() + +find_package(Protobuf REQUIRED) +if(Protobuf_VERSION VERSION_GREATER 4.21) + # required by absl + set(BRPC_CXX_STANDARD 17) + + find_package(absl REQUIRED CONFIG) + set(protobuf_ABSL_USED_TARGETS + absl::absl_check + absl::absl_log + absl::algorithm + absl::base + absl::bind_front + absl::bits + absl::btree + absl::cleanup + absl::cord + absl::core_headers + absl::debugging + absl::die_if_null + absl::dynamic_annotations + absl::flags + absl::flat_hash_map + absl::flat_hash_set + absl::function_ref + absl::hash + absl::layout + absl::log_initialize + absl::log_globals + absl::log_severity + absl::memory + absl::node_hash_map + absl::node_hash_set + absl::random_distributions + absl::random_random + absl::span + absl::status + absl::statusor + absl::strings + absl::synchronization + absl::time + absl::type_traits + absl::utility + absl::variant + ) +endif() +find_package(Threads REQUIRED) + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(WITH_SNAPPY) + find_path(SNAPPY_INCLUDE_PATH NAMES snappy.h) + find_library(SNAPPY_LIB NAMES snappy) + if ((NOT SNAPPY_INCLUDE_PATH) OR (NOT SNAPPY_LIB)) + message(FATAL_ERROR "Fail to find snappy") + endif() + list(APPEND BRPC_COMMON_INCLUDE_DIRS ${SNAPPY_INCLUDE_PATH}) +endif() + +if(WITH_GLOG) + find_path(GLOG_INCLUDE_PATH NAMES glog/logging.h) + find_library(GLOG_LIB NAMES glog) + if((NOT GLOG_INCLUDE_PATH) OR (NOT GLOG_LIB)) + message(FATAL_ERROR "Fail to find glog") + endif() + list(APPEND BRPC_COMMON_INCLUDE_DIRS ${GLOG_INCLUDE_PATH}) +endif() + +if(WITH_MESALINK) + find_path(MESALINK_INCLUDE_PATH NAMES mesalink/openssl/ssl.h) + find_library(MESALINK_LIB NAMES mesalink) + if((NOT MESALINK_INCLUDE_PATH) OR (NOT MESALINK_LIB)) + message(FATAL_ERROR "Fail to find MesaLink") + else() + message(STATUS "Found MesaLink: ${MESALINK_LIB}") + endif() + list(APPEND BRPC_COMMON_INCLUDE_DIRS ${MESALINK_INCLUDE_PATH}) +endif() + +if(WITH_RDMA) + message("brpc compile with rdma") + find_path(RDMA_INCLUDE_PATH NAMES infiniband/verbs.h) + find_library(RDMA_LIB NAMES ibverbs) + if((NOT RDMA_INCLUDE_PATH) OR (NOT RDMA_LIB)) + message(FATAL_ERROR "Fail to find ibverbs") + endif() + list(APPEND BRPC_COMMON_INCLUDE_DIRS ${RDMA_INCLUDE_PATH}) +endif() + +find_library(PROTOC_LIB NAMES protoc) +if(NOT PROTOC_LIB) + message(FATAL_ERROR "Fail to find protoc lib") +endif() + +if(WITH_BORINGSSL) + find_package(BoringSSL) + list(APPEND BRPC_COMMON_INCLUDE_DIRS ${BORINGSSL_INCLUDE_DIR}) +else() + if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) + endif() + + find_package(OpenSSL REQUIRED) + list(APPEND BRPC_COMMON_INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR}) +endif() + +list(APPEND BRPC_COMMON_INCLUDE_DIRS + ${GFLAGS_INCLUDE_PATH} + ${PROTOBUF_INCLUDE_DIRS} + ${LEVELDB_INCLUDE_PATH} +) + +set(DYNAMIC_LIB + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} ${protobuf_ABSL_USED_TARGETS} + ${LEVELDB_LIB} + ${PROTOC_LIB} + ${CMAKE_THREAD_LIBS_INIT} + ${THRIFT_LIB} + dl + z) + +if(WITH_BORINGSSL) + list(APPEND DYNAMIC_LIB ${BORINGSSL_SSL_LIBRARY}) + list(APPEND DYNAMIC_LIB ${BORINGSSL_CRYPTO_LIBRARY}) +else() + if(WITH_MESALINK) + list(APPEND DYNAMIC_LIB ${OPENSSL_CRYPTO_LIBRARY}) + list(APPEND DYNAMIC_LIB ${MESALINK_LIB}) + else() + list(APPEND DYNAMIC_LIB ${OPENSSL_SSL_LIBRARY}) + list(APPEND DYNAMIC_LIB ${OPENSSL_CRYPTO_LIBRARY}) + endif() +endif() + +if(WITH_RDMA) + list(APPEND DYNAMIC_LIB ${RDMA_LIB}) +endif() + +set(BRPC_PRIVATE_LIBS "-lgflags -lprotobuf -lleveldb -lprotoc -lssl -lcrypto -ldl -lz") + +if(WITH_GLOG) + set(DYNAMIC_LIB ${GLOG_LIB} ${DYNAMIC_LIB}) + set(BRPC_PRIVATE_LIBS "-lglog ${BRPC_PRIVATE_LIBS}") +endif() + +if(WITH_SNAPPY) + set(DYNAMIC_LIB ${DYNAMIC_LIB} ${SNAPPY_LIB}) + set(BRPC_PRIVATE_LIBS "${BRPC_PRIVATE_LIBS} -lsnappy") +endif() + +if (WITH_BTHREAD_TRACER) + set(DYNAMIC_LIB ${DYNAMIC_LIB} ${LIBUNWIND_LIB} ${LIBUNWIND_X86_64_LIB} ${bthread_tracer_ABSL_USED_TARGETS}) + set(BRPC_PRIVATE_LIBS "${BRPC_PRIVATE_LIBS} -lunwind -lunwind-x86_64 -labsl_stacktrace -labsl_symbolize -labsl_debugging_internal -labsl_demangle_internal -labsl_malloc_internal -labsl_raw_logging_internal -labsl_spinlock_wait -labsl_base") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(DYNAMIC_LIB ${DYNAMIC_LIB} rt) + set(BRPC_PRIVATE_LIBS "${BRPC_PRIVATE_LIBS} -lrt") +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +list(REMOVE_DUPLICATES BRPC_COMMON_INCLUDE_DIRS) + +add_library(brpc_common_config INTERFACE) +target_include_directories(brpc_common_config INTERFACE ${BRPC_COMMON_INCLUDE_DIRS}) +target_compile_definitions(brpc_common_config INTERFACE ${BRPC_COMMON_DEFINITIONS}) +target_compile_options(brpc_common_config INTERFACE ${BRPC_COMMON_COMPILE_OPTIONS}) +target_compile_features(brpc_common_config INTERFACE cxx_std_${BRPC_CXX_STANDARD}) +if(BRPC_COMMON_LINK_OPTIONS) + target_link_options(brpc_common_config INTERFACE ${BRPC_COMMON_LINK_OPTIONS}) +endif() + +# for *.so +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/output/lib) +# for *.a +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/output/lib) + +# the reason why not using file(GLOB_RECURSE...) is that we want to +# include different files on different platforms. +set(BUTIL_SOURCES + ${PROJECT_SOURCE_DIR}/src/butil/third_party/dmg_fp/g_fmt.cc + ${PROJECT_SOURCE_DIR}/src/butil/third_party/dmg_fp/dtoa_wrapper.cc + ${PROJECT_SOURCE_DIR}/src/butil/third_party/dynamic_annotations/dynamic_annotations.c + ${PROJECT_SOURCE_DIR}/src/butil/third_party/icu/icu_utf.cc + ${PROJECT_SOURCE_DIR}/src/butil/third_party/superfasthash/superfasthash.c + ${PROJECT_SOURCE_DIR}/src/butil/third_party/modp_b64/modp_b64.cc + ${PROJECT_SOURCE_DIR}/src/butil/third_party/symbolize/demangle.cc + ${PROJECT_SOURCE_DIR}/src/butil/third_party/symbolize/symbolize.cc + ${PROJECT_SOURCE_DIR}/src/butil/third_party/snappy/snappy-sinksource.cc + ${PROJECT_SOURCE_DIR}/src/butil/third_party/snappy/snappy-stubs-internal.cc + ${PROJECT_SOURCE_DIR}/src/butil/third_party/snappy/snappy.cc + ${PROJECT_SOURCE_DIR}/src/butil/third_party/murmurhash3/murmurhash3.cpp + ${PROJECT_SOURCE_DIR}/src/butil/arena.cpp + ${PROJECT_SOURCE_DIR}/src/butil/at_exit.cc + ${PROJECT_SOURCE_DIR}/src/butil/atomicops_internals_x86_gcc.cc + ${PROJECT_SOURCE_DIR}/src/butil/base64.cc + ${PROJECT_SOURCE_DIR}/src/butil/base64url.cc + ${PROJECT_SOURCE_DIR}/src/butil/big_endian.cc + ${PROJECT_SOURCE_DIR}/src/butil/cpu.cc + ${PROJECT_SOURCE_DIR}/src/butil/debug/alias.cc + ${PROJECT_SOURCE_DIR}/src/butil/debug/asan_invalid_access.cc + ${PROJECT_SOURCE_DIR}/src/butil/debug/crash_logging.cc + ${PROJECT_SOURCE_DIR}/src/butil/debug/debugger.cc + ${PROJECT_SOURCE_DIR}/src/butil/debug/debugger_posix.cc + ${PROJECT_SOURCE_DIR}/src/butil/debug/dump_without_crashing.cc + ${PROJECT_SOURCE_DIR}/src/butil/debug/proc_maps_linux.cc + ${PROJECT_SOURCE_DIR}/src/butil/debug/stack_trace.cc + ${PROJECT_SOURCE_DIR}/src/butil/debug/stack_trace_posix.cc + ${PROJECT_SOURCE_DIR}/src/butil/environment.cc + ${PROJECT_SOURCE_DIR}/src/butil/files/file.cc + ${PROJECT_SOURCE_DIR}/src/butil/files/file_posix.cc + ${PROJECT_SOURCE_DIR}/src/butil/files/file_enumerator.cc + ${PROJECT_SOURCE_DIR}/src/butil/files/file_enumerator_posix.cc + ${PROJECT_SOURCE_DIR}/src/butil/files/file_path.cc + ${PROJECT_SOURCE_DIR}/src/butil/files/file_path_constants.cc + ${PROJECT_SOURCE_DIR}/src/butil/files/memory_mapped_file.cc + ${PROJECT_SOURCE_DIR}/src/butil/files/memory_mapped_file_posix.cc + ${PROJECT_SOURCE_DIR}/src/butil/files/scoped_file.cc + ${PROJECT_SOURCE_DIR}/src/butil/files/scoped_temp_dir.cc + ${PROJECT_SOURCE_DIR}/src/butil/file_util.cc + ${PROJECT_SOURCE_DIR}/src/butil/file_util_posix.cc + ${PROJECT_SOURCE_DIR}/src/butil/guid.cc + ${PROJECT_SOURCE_DIR}/src/butil/guid_posix.cc + ${PROJECT_SOURCE_DIR}/src/butil/hash.cc + ${PROJECT_SOURCE_DIR}/src/butil/lazy_instance.cc + ${PROJECT_SOURCE_DIR}/src/butil/location.cc + ${PROJECT_SOURCE_DIR}/src/butil/memory/aligned_memory.cc + ${PROJECT_SOURCE_DIR}/src/butil/memory/ref_counted.cc + ${PROJECT_SOURCE_DIR}/src/butil/memory/ref_counted_memory.cc + ${PROJECT_SOURCE_DIR}/src/butil/memory/singleton.cc + ${PROJECT_SOURCE_DIR}/src/butil/memory/weak_ptr.cc + ${PROJECT_SOURCE_DIR}/src/butil/posix/file_descriptor_shuffle.cc + ${PROJECT_SOURCE_DIR}/src/butil/posix/global_descriptors.cc + ${PROJECT_SOURCE_DIR}/src/butil/process_util.cc + ${PROJECT_SOURCE_DIR}/src/butil/rand_util.cc + ${PROJECT_SOURCE_DIR}/src/butil/rand_util_posix.cc + ${PROJECT_SOURCE_DIR}/src/butil/fast_rand.cpp + ${PROJECT_SOURCE_DIR}/src/butil/safe_strerror_posix.cc + ${PROJECT_SOURCE_DIR}/src/butil/sha1_portable.cc + ${PROJECT_SOURCE_DIR}/src/butil/strings/latin1_string_conversions.cc + ${PROJECT_SOURCE_DIR}/src/butil/strings/nullable_string16.cc + ${PROJECT_SOURCE_DIR}/src/butil/strings/safe_sprintf.cc + ${PROJECT_SOURCE_DIR}/src/butil/strings/string16.cc + ${PROJECT_SOURCE_DIR}/src/butil/strings/string_number_conversions.cc + ${PROJECT_SOURCE_DIR}/src/butil/strings/string_split.cc + ${PROJECT_SOURCE_DIR}/src/butil/strings/string_piece.cc + ${PROJECT_SOURCE_DIR}/src/butil/strings/string_util.cc + ${PROJECT_SOURCE_DIR}/src/butil/strings/string_util_constants.cc + ${PROJECT_SOURCE_DIR}/src/butil/strings/stringprintf.cc + ${PROJECT_SOURCE_DIR}/src/butil/strings/utf_offset_string_conversions.cc + ${PROJECT_SOURCE_DIR}/src/butil/strings/utf_string_conversion_utils.cc + ${PROJECT_SOURCE_DIR}/src/butil/strings/utf_string_conversions.cc + ${PROJECT_SOURCE_DIR}/src/butil/synchronization/cancellation_flag.cc + ${PROJECT_SOURCE_DIR}/src/butil/synchronization/condition_variable_posix.cc + ${PROJECT_SOURCE_DIR}/src/butil/synchronization/waitable_event_posix.cc + ${PROJECT_SOURCE_DIR}/src/butil/threading/non_thread_safe_impl.cc + ${PROJECT_SOURCE_DIR}/src/butil/threading/platform_thread_posix.cc + ${PROJECT_SOURCE_DIR}/src/butil/threading/simple_thread.cc + ${PROJECT_SOURCE_DIR}/src/butil/threading/thread_checker_impl.cc + ${PROJECT_SOURCE_DIR}/src/butil/threading/thread_collision_warner.cc + ${PROJECT_SOURCE_DIR}/src/butil/threading/thread_id_name_manager.cc + ${PROJECT_SOURCE_DIR}/src/butil/threading/thread_local_posix.cc + ${PROJECT_SOURCE_DIR}/src/butil/threading/thread_local_storage.cc + ${PROJECT_SOURCE_DIR}/src/butil/threading/thread_local_storage_posix.cc + ${PROJECT_SOURCE_DIR}/src/butil/threading/thread_restrictions.cc + ${PROJECT_SOURCE_DIR}/src/butil/threading/watchdog.cc + ${PROJECT_SOURCE_DIR}/src/butil/time/clock.cc + ${PROJECT_SOURCE_DIR}/src/butil/time/default_clock.cc + ${PROJECT_SOURCE_DIR}/src/butil/time/default_tick_clock.cc + ${PROJECT_SOURCE_DIR}/src/butil/time/tick_clock.cc + ${PROJECT_SOURCE_DIR}/src/butil/time/time.cc + ${PROJECT_SOURCE_DIR}/src/butil/time/time_posix.cc + ${PROJECT_SOURCE_DIR}/src/butil/version.cc + ${PROJECT_SOURCE_DIR}/src/butil/logging.cc + ${PROJECT_SOURCE_DIR}/src/butil/class_name.cpp + ${PROJECT_SOURCE_DIR}/src/butil/errno.cpp + ${PROJECT_SOURCE_DIR}/src/butil/find_cstr.cpp + ${PROJECT_SOURCE_DIR}/src/butil/status.cpp + ${PROJECT_SOURCE_DIR}/src/butil/string_printf.cpp + ${PROJECT_SOURCE_DIR}/src/butil/thread_local.cpp + ${PROJECT_SOURCE_DIR}/src/butil/thread_key.cpp + ${PROJECT_SOURCE_DIR}/src/butil/unix_socket.cpp + ${PROJECT_SOURCE_DIR}/src/butil/endpoint.cpp + ${PROJECT_SOURCE_DIR}/src/butil/fd_utility.cpp + ${PROJECT_SOURCE_DIR}/src/butil/files/temp_file.cpp + ${PROJECT_SOURCE_DIR}/src/butil/files/file_watcher.cpp + ${PROJECT_SOURCE_DIR}/src/butil/time.cpp + ${PROJECT_SOURCE_DIR}/src/butil/zero_copy_stream_as_streambuf.cpp + ${PROJECT_SOURCE_DIR}/src/butil/crc32c.cc + ${PROJECT_SOURCE_DIR}/src/butil/containers/case_ignored_flat_map.cpp + ${PROJECT_SOURCE_DIR}/src/butil/iobuf.cpp + ${PROJECT_SOURCE_DIR}/src/butil/single_iobuf.cpp + ${PROJECT_SOURCE_DIR}/src/butil/iobuf_profiler.cpp + ${PROJECT_SOURCE_DIR}/src/butil/binary_printer.cpp + ${PROJECT_SOURCE_DIR}/src/butil/recordio.cc + ${PROJECT_SOURCE_DIR}/src/butil/popen.cpp + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(BUTIL_SOURCES ${BUTIL_SOURCES} + ${PROJECT_SOURCE_DIR}/src/butil/file_util_linux.cc + ${PROJECT_SOURCE_DIR}/src/butil/threading/platform_thread_linux.cc + ${PROJECT_SOURCE_DIR}/src/butil/strings/sys_string_conversions_posix.cc) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(BUTIL_SOURCES ${BUTIL_SOURCES} + ${PROJECT_SOURCE_DIR}/src/butil/mac/bundle_locations.mm + ${PROJECT_SOURCE_DIR}/src/butil/mac/foundation_util.mm + ${PROJECT_SOURCE_DIR}/src/butil/file_util_mac.mm + ${PROJECT_SOURCE_DIR}/src/butil/threading/platform_thread_mac.mm + ${PROJECT_SOURCE_DIR}/src/butil/strings/sys_string_conversions_mac.mm + ${PROJECT_SOURCE_DIR}/src/butil/time/time_mac.cc + ${PROJECT_SOURCE_DIR}/src/butil/mac/scoped_mach_port.cc) +endif() + +file(GLOB_RECURSE BVAR_SOURCES CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/src/bvar/*.cpp") +file(GLOB_RECURSE BTHREAD_SOURCES CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/src/bthread/*.cpp") +file(GLOB_RECURSE JSON2PB_SOURCES CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/src/json2pb/*.cpp") +file(GLOB_RECURSE BRPC_SOURCES CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/src/brpc/*.cpp") +file(GLOB_RECURSE THRIFT_SOURCES CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/src/brpc/thrift*.cpp") +file(GLOB_RECURSE EXCLUDE_SOURCES CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/src/brpc/event_dispatcher_*.cpp") + +if(WITH_THRIFT) + message("brpc compile with thrift protocol") +else() + # Remove thrift sources + foreach(v ${THRIFT_SOURCES}) + list(REMOVE_ITEM BRPC_SOURCES ${v}) + endforeach() + set(THRIFT_SOURCES "") +endif() + +foreach(v ${EXCLUDE_SOURCES}) + list(REMOVE_ITEM BRPC_SOURCES ${v}) +endforeach() + +set(MCPACK2PB_SOURCES + ${PROJECT_SOURCE_DIR}/src/mcpack2pb/field_type.cpp + ${PROJECT_SOURCE_DIR}/src/mcpack2pb/mcpack2pb.cpp + ${PROJECT_SOURCE_DIR}/src/mcpack2pb/parser.cpp + ${PROJECT_SOURCE_DIR}/src/mcpack2pb/serializer.cpp) + +include(CompileProto) +set(PROTO_FILES idl_options.proto + brpc/rtmp.proto + brpc/rpc_dump.proto + brpc/get_favicon.proto + brpc/span.proto + brpc/builtin_service.proto + brpc/grpc_health_check.proto + brpc/get_js.proto + brpc/errno.proto + brpc/nshead_meta.proto + brpc/options.proto + brpc/policy/baidu_rpc_meta.proto + brpc/policy/hulu_pbrpc_meta.proto + brpc/policy/public_pbrpc_meta.proto + brpc/policy/sofa_pbrpc_meta.proto + brpc/policy/mongo.proto + brpc/trackme.proto + brpc/streaming_rpc_meta.proto + brpc/proto_base.proto + brpc/rdma/rdma_handshake.proto) +file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/output/include/brpc) +set(PROTOC_FLAGS ${PROTOC_FLAGS} -I${PROTOBUF_INCLUDE_DIR}) +compile_proto(PROTO_HDRS PROTO_SRCS ${PROJECT_BINARY_DIR} + ${PROJECT_BINARY_DIR}/output/include + ${PROJECT_SOURCE_DIR}/src + "${PROTO_FILES}") +add_library(PROTO_LIB OBJECT ${PROTO_SRCS} ${PROTO_HDRS}) +target_link_libraries(PROTO_LIB PRIVATE brpc_common_config) + +set(SOURCES + ${BVAR_SOURCES} + ${BTHREAD_SOURCES} + ${JSON2PB_SOURCES} + ${MCPACK2PB_SOURCES} + ${BRPC_SOURCES} + ${THRIFT_SOURCES} + ) + +add_subdirectory(src) +if(BUILD_UNIT_TESTS) + enable_testing() + add_subdirectory(test) +endif() + +if(BUILD_FUZZ_TESTS) + if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") + message(FATAL_ERROR "Fuzzing is only supported with clang") + endif() + if(NOT BUILD_UNIT_TESTS) + message(FATAL_ERROR "BUILD_UNIT_TESTS must be enabled to build fuzz tests") + endif() +endif() + +if(BUILD_BRPC_TOOLS) + add_subdirectory(tools) +endif() + +file(COPY ${CMAKE_CURRENT_BINARY_DIR}/brpc/ + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/output/include/brpc/ + FILES_MATCHING + PATTERN "*.h" + PATTERN "*.hpp" + ) +file(COPY ${PROJECT_SOURCE_DIR}/src/ + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/output/include/ + FILES_MATCHING + PATTERN "*.h" + PATTERN "*.hpp" + ) +install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/output/include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + FILES_MATCHING + PATTERN "*.h" + PATTERN "*.hpp" + ) + +# Install pkgconfig +configure_file(cmake/brpc.pc.in ${PROJECT_BINARY_DIR}/brpc.pc @ONLY) +install(FILES ${PROJECT_BINARY_DIR}/brpc.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..b7afd17 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at dev@brpc.apache.org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a458f38 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,29 @@ +If you meet any problem or request a new feature, you're welcome to [create an issue](https://github.com/apache/brpc/issues/new/choose). + +If you can solve any of [the issues](https://github.com/apache/brpc/issues), you're welcome to send the PR to us. + +Before the PR: + +* Make sure your code style conforms to [google C++ coding style](https://google.github.io/styleguide/cppguide.html). Indentation is preferred to be 4 spaces. +* The code appears where it should be. For example the code to support an extra protocol should not be put in general classes like server.cpp, channel.cpp, while a general modification would better not be hidden inside a very specific protocol. +* Has unittests. + +After the PR: + +* Make sure the [GitHub Actions](https://github.com/apache/brpc/actions) passed. + +# Chinese version + +如果你遇到问题或需要新功能,欢迎[创建issue](https://github.com/apache/brpc/issues/new/choose)。 + +如果你可以解决某个[issue](https://github.com/apache/brpc/issues), 欢迎发送PR。 + +发送PR前请确认: + +* 你的代码符合[google C++代码规范](https://google.github.io/styleguide/cppguide.html)。缩进最好为4个空格。 +* 代码出现的位置和其定位相符。比如对于某特定协议的扩展代码不该出现在server.cpp, channel.cpp这些较为通用的类中,而一些非常通用的改动也不该深藏在某个特定协议的cpp中。 +* 有对应的单测代码。 + +提交PR后请确认: + +* [GitHub Actions](https://github.com/apache/brpc/actions)成功通过。 diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 0000000..6f7b01a --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,54 @@ +module( + name = 'brpc', + version = '1.17.0', + compatibility_level = 1, +) + +# --registry=https://bcr.bazel.build +bazel_dep(name = 'abseil-cpp', version = '20210324.2', repo_name = 'com_google_absl') +bazel_dep(name = 'bazel_skylib', version = '1.0.3') +bazel_dep(name = 'boringssl', version = '0.0.0-20211025-d4f1ab9') +bazel_dep(name = 'protobuf', version = '27.3', repo_name = 'com_google_protobuf') +bazel_dep(name = 'gflags', version = '2.2.2', repo_name = 'com_github_gflags_gflags') +bazel_dep(name = 'glog', version = '0.5.0', repo_name = 'com_github_google_glog') +bazel_dep(name = 'platforms', version = '0.0.4') +bazel_dep(name = "apple_support", version = "1.22.1") +bazel_dep(name = 'rules_cc', version = '0.0.1') +bazel_dep(name = 'rules_proto', version = '4.0.0') +bazel_dep(name = 'zlib', version = '1.3.1.bcr.5', repo_name = 'com_github_madler_zlib') +bazel_dep(name = 'babylon', version = '1.4.4') +# --registry=https://raw.githubusercontent.com/apache/brpc/master/registry +# `registry/modules/libunwind/` (see the `--registry=https://raw.githubusercontent.com/apache/brpc/master/registry` +# entry at the top of `.bazelrc`). The version suffix `.brpc-no-unwind` marks +# this as brpc's self-maintained variant whose `src/unwind/*.c` (the GCC +# `_Unwind_*` ABI compatibility layer) is gated by the +# `--define=libunwind_hide_unwind_symbols=true` switch. Hiding those +# `_Unwind_*` symbols is required for `--define=with_bthread_tracer=true` +# to work without crashing in pthread_exit / exception unwinding. +# See docs/cn/bthread_tracer.md for background. +bazel_dep(name = 'libunwind', version = '1.8.1.brpc-no-unwind', repo_name = 'com_github_libunwind_libunwind') + +# --registry=https://baidu.github.io/babylon/registry +bazel_dep(name = 'leveldb', version = '1.23', repo_name = 'com_github_google_leveldb') +single_version_override( + module_name = "leveldb", + registry = "https://raw.githubusercontent.com/secretflow/bazel-registry/main", +) +bazel_dep(name = 'openssl', version = '3.3.2') +single_version_override( + module_name = "openssl", + version = "3.3.2.bcr.1", + registry = "https://raw.githubusercontent.com/secretflow/bazel-registry/main", +) +bazel_dep(name = 'thrift', version = '0.21.0', repo_name = 'org_apache_thrift') + +# test only +bazel_dep(name = "gperftools", version = "2.18.1", dev_dependency = True) +bazel_dep(name = "nlohmann_json", version = "3.12.0", dev_dependency = True) +bazel_dep(name = 'googletest', version = '1.14.0.bcr.1', repo_name = 'com_google_googletest', dev_dependency = True) +bazel_dep(name = 'hedron_compile_commands', dev_dependency = True) +git_override( + module_name = 'hedron_compile_commands', + remote = 'https://github.com/hedronvision/bazel-compile-commands-extractor.git', + commit = '1e08f8e0507b6b6b1f4416a9a22cf5c28beaba93', # Jun 28, 2024 +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..1c4f785 --- /dev/null +++ b/README.md @@ -0,0 +1,117 @@ +[中文版](README_cn.md) + +[![Linux Build Status](https://github.com/apache/brpc/actions/workflows/ci-linux.yml/badge.svg)](https://github.com/apache/brpc/actions/workflows/ci-linux.yml) +[![MacOs Build Status](https://github.com/apache/brpc/actions/workflows/ci-macos.yml/badge.svg)](https://github.com/apache/brpc/actions/workflows/ci-macos.yml) + +![brpc logo (light)](docs/images/logo.png#gh-light-mode-only) +![brpc logo (dark)](docs/images/logo-white.png#gh-dark-mode-only) + +[bRPC](https://brpc.apache.org/) is an Industrial-grade RPC framework using C++ Language, which is often used in high performance system such as Search, Storage, Machine learning, Advertisement, Recommendation etc. + +### "bRPC" means "better RPC". + +You can use it to: +* Build a server that can talk in multiple protocols (**on same port**), or access all sorts of services + * restful http/https, [h2](https://httpwg.org/specs/rfc9113.html)/[gRPC](https://grpc.io). using http/h2 in bRPC is much more friendly than [libcurl](https://curl.haxx.se/libcurl/). Access protobuf-based protocols with HTTP/h2+json, probably from another language. + * [redis](docs/en/redis_client.md) and [memcached](docs/en/memcache_client.md), thread-safe, more friendly and performant than the official clients. + * [rtmp](https://github.com/apache/brpc/blob/master/src/brpc/rtmp.h)/[flv](https://en.wikipedia.org/wiki/Flash_Video)/[hls](https://en.wikipedia.org/wiki/HTTP_Live_Streaming), for building [streaming services](https://github.com/brpc/media-server). + * hadoop_rpc (may be opensourced) + * [rdma](https://en.wikipedia.org/wiki/Remote_direct_memory_access) support + * [thrift](docs/en/thrift.md) support, thread-safe, more friendly and performant than the official clients. + * all sorts of protocols used in Baidu: [baidu_std](docs/en/baidu_std.md), [streaming_rpc](docs/en/streaming_rpc.md), hulu_pbrpc, [sofa_pbrpc](https://github.com/baidu/sofa-pbrpc), nova_pbrpc, public_pbrpc, ubrpc and nshead-based ones. + * Build [HA](https://en.wikipedia.org/wiki/High_availability) distributed services using an industrial-grade implementation of [RAFT consensus algorithm](https://raft.github.io) which is opensourced at [braft](https://github.com/brpc/braft) +* Servers can handle requests [synchronously](docs/en/server.md) or [asynchronously](docs/en/server.md#asynchronous-service). +* Clients can access servers [synchronously](docs/en/client.md#synchronus-call), [asynchronously](docs/en/client.md#asynchronous-call), [semi-synchronously](docs/en/client.md#semi-synchronous-call), or use [combo channels](docs/en/combo_channel.md) to simplify sharded or parallel accesses declaratively. +* Debug services [via http](docs/en/builtin_service.md), and run [cpu](docs/en/cpu_profiler.md), [heap](docs/en/heap_profiler.md) and [contention](docs/en/contention_profiler.md) profilers. +* Get [better latency and throughput](docs/en/overview.md#better-latency-and-throughput). +* [Extend bRPC](docs/en/new_protocol.md) with the protocols used in your organization quickly, or customize components, including [naming services](docs/en/load_balancing.md) (dns, zk, etcd), [load balancers](docs/en/load_balancing.md) (rr, random, consistent hashing) + +# Try it! + +* Read [overview](docs/en/overview.md) to know where bRPC can be used and its advantages. +* Read [getting started](docs/en/getting_started.md) for building steps and play with [examples](https://github.com/apache/brpc/tree/master/example/). +* Docs: + * [Performance benchmark](docs/en/benchmark.md) + * [bvar](docs/en/bvar.md) + * [bvar_c++](docs/en/bvar_c++.md) + * [bthread](docs/en/bthread.md) + * [bthread or not](docs/en/bthread_or_not.md) + * [thread-local](docs/en/thread_local.md) + * [Execution Queue](docs/en/execution_queue.md) + * [bthread tracer](docs/en/bthread_tracer.md) + * [bthread tagged task group](docs/en/bthread_tagged_task_group.md) + * Client + * [Basics](docs/en/client.md) + * [Error code](docs/en/error_code.md) + * [Combo channels](docs/en/combo_channel.md) + * [Access http/h2](docs/en/http_client.md) + * [Access gRPC](docs/en/http_derivatives.md#h2grpc) + * [Access thrift](docs/en/thrift.md#client-accesses-thrift-server) + * [Access UB](docs/en/ub_client.md) + * [Streaming RPC](docs/en/streaming_rpc.md) + * [Access redis](docs/en/redis_client.md) + * [Access memcached](docs/en/memcache_client.md) + * [Backup request](docs/en/backup_request.md) + * [Dummy server](docs/en/dummy_server.md) + * Server + * [Basics](docs/en/server.md) + * [Serve http/h2](docs/en/http_service.md) + * [Serve gRPC](docs/en/http_derivatives.md#h2grpc) + * [Serve thrift](docs/en/thrift.md#server-processes-thrift-requests) + * [Serve Nshead](docs/en/nshead_service.md) + * [Debug server issues](docs/en/server_debugging.md) + * [Server push](docs/en/server_push.md) + * [Avalanche](docs/en/avalanche.md) + * [Auto ConcurrencyLimiter](docs/en/auto_concurrency_limiter.md) + * [Media Server](https://github.com/brpc/media-server) + * [json2pb](docs/en/json2pb.md) + * [Builtin Services](docs/en/builtin_service.md) + * [status](docs/en/status.md) + * [vars](docs/en/vars.md) + * [connections](docs/en/connections.md) + * [flags](docs/en/flags.md) + * [rpcz](docs/en/rpcz.md) + * [cpu_profiler](docs/en/cpu_profiler.md) + * [heap_profiler](docs/en/heap_profiler.md) + * [contention_profiler](docs/en/contention_profiler.md) + * Tools + * [rpc_press](docs/en/rpc_press.md) + * [rpc_replay](docs/en/rpc_replay.md) + * [rpc_view](docs/en/rpc_view.md) + * [benchmark_http](docs/en/benchmark_http.md) + * [parallel_http](docs/en/parallel_http.md) + * Others + * [IOBuf](docs/en/iobuf.md) + * [Streaming Log](docs/en/streaming_log.md) + * [FlatMap](docs/en/flatmap.md) + * [Coroutine](docs/en/coroutine.md) + * [Circuit Breaker](docs/en/circuit_breaker.md) + * [RDMA](docs/en/rdma.md) + * [Bazel Support](docs/en/bazel_support.md) + * [Wireshark baidu_std dissector plugin](docs/en/wireshark_baidu_std.md) + * [bRPC introduction](docs/cn/brpc_intro.pptx)(training material) + * [A tutorial on building large-scale services](docs/en/tutorial_on_building_services.pptx)(training material) + * [bRPC internal](docs/en/brpc_internal.pptx)(training material) + * RPC in depth + * [New Protocol](docs/en/new_protocol.md) + * [Atomic instructions](docs/en/atomic_instructions.md) + * [IO](docs/en/io.md) + * [Threading Overview](docs/en/threading_overview.md) + * [Load Balancing](docs/en/load_balancing.md) + * [Locality-aware](docs/en/lalb.md) + * [Consistent Hashing](docs/en/consistent_hashing.md) + * [Memory Management](docs/en/memory_management.md) + * [Timer keeping](docs/en/timer_keeping.md) + * [bthread_id](docs/en/bthread_id.md) + * Use cases + * [User cases](community/cases.md) + +# Contribute code +Please refer to [here](CONTRIBUTING.md). + +# Feedback and Getting involved +* Report bugs, ask questions or give suggestions by [Github Issues](https://github.com/apache/brpc/issues) +* Subscribe to the mailing list(dev-subscribe@brpc.apache.org) to get updated with the project + +# Code of Conduct +We follow the code of conduct from Apache Software Foundation, please refer it here [Link](https://www.apache.org/foundation/policies/conduct) diff --git a/README.wehub.md b/README.wehub.md new file mode 100644 index 0000000..bad5b24 --- /dev/null +++ b/README.wehub.md @@ -0,0 +1,7 @@ +# WeHub 来源说明 + +- 原始项目:`apache/brpc` +- 原始仓库:https://github.com/apache/brpc +- 导入方式:上游默认分支的最新快照 +- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准 +- 本文件仅用于记录来源,不代表 WeHub 是原项目作者 diff --git a/README_cn.md b/README_cn.md new file mode 100644 index 0000000..6413f83 --- /dev/null +++ b/README_cn.md @@ -0,0 +1,124 @@ +[English version](README.md) + +[![Linux Build Status](https://github.com/apache/brpc/actions/workflows/ci-linux.yml/badge.svg)](https://github.com/apache/brpc/actions/workflows/ci-linux.yml) +[![MacOs Build Status](https://github.com/apache/brpc/actions/workflows/ci-macos.yml/badge.svg)](https://github.com/apache/brpc/actions/workflows/ci-macos.yml) + +![brpc logo (light)](docs/images/logo.png#gh-light-mode-only) +![brpc logo (dark)](docs/images/logo-white.png#gh-dark-mode-only) + +[bRPC](https://brpc.apache.org/)是用C++语言编写的工业级RPC框架,常用于搜索、存储、机器学习、广告、推荐等高性能系统。 + +### "bRPC"的含义是"better RPC" + +你可以使用它: + +* 搭建能在**一个端口**支持多协议的服务, 或访问各种服务 + * restful http/https, [h2](https://httpwg.org/specs/rfc9113.html)/[gRPC](https://grpc.io)。使用bRPC的http实现比[libcurl](https://curl.haxx.se/libcurl/)方便多了。从其他语言通过HTTP/h2+json访问基于protobuf的协议. + * [redis](docs/cn/redis_client.md)和[memcached](docs/cn/memcache_client.md), 线程安全,比官方client更方便。 + * [rtmp](https://github.com/apache/brpc/blob/master/src/brpc/rtmp.h)/[flv](https://en.wikipedia.org/wiki/Flash_Video)/[hls](https://en.wikipedia.org/wiki/HTTP_Live_Streaming), 可用于搭建[流媒体服务](https://github.com/brpc/media-server). + * hadoop_rpc(可能开源) + * 支持[rdma](https://en.wikipedia.org/wiki/Remote_direct_memory_access) + * 支持[thrift](docs/cn/thrift.md) , 线程安全,比官方client更方便 + * 各种百度内使用的协议: [baidu_std](docs/cn/baidu_std.md), [streaming_rpc](docs/cn/streaming_rpc.md), hulu_pbrpc, [sofa_pbrpc](https://github.com/baidu/sofa-pbrpc), nova_pbrpc, public_pbrpc, ubrpc和使用nshead的各种协议. + * 基于工业级的[RAFT算法](https://raft.github.io)实现搭建[高可用](https://en.wikipedia.org/wiki/High_availability)分布式系统,已在[braft](https://github.com/brpc/braft)开源。 +* Server能[同步](docs/cn/server.md)或[异步](docs/cn/server.md#异步service)处理请求。 +* Client支持[同步](docs/cn/client.md#同步访问)、[异步](docs/cn/client.md#异步访问)、[半同步](docs/cn/client.md#半同步),或使用[组合channels](docs/cn/combo_channel.md)简化复杂的分库或并发访问。 +* [通过http界面](docs/cn/builtin_service.md)调试服务, 使用[cpu](docs/cn/cpu_profiler.md), [heap](docs/cn/heap_profiler.md), [contention](docs/cn/contention_profiler.md) profilers. +* 获得[更好的延时和吞吐](docs/cn/overview.md#更好的延时和吞吐). +* 把你组织中使用的协议快速地[加入bRPC](docs/cn/new_protocol.md),或定制各类组件, 包括[命名服务](docs/cn/load_balancing.md#命名服务) (dns, zk, etcd), [负载均衡](docs/cn/load_balancing.md#负载均衡) (rr, random, consistent hashing) + +# 试一下! + +* 通过[概述](docs/cn/overview.md)了解哪里可以用bRPC及其优势。 +* 阅读[编译步骤](docs/cn/getting_started.md)了解如何开始使用, 之后可以运行一下[示例程序](https://github.com/apache/brpc/tree/master/example/). +* 文档: + * [性能测试](docs/cn/benchmark.md) + * [bvar](docs/cn/bvar.md) + * [bvar_c++](docs/cn/bvar_c++.md) + * [bthread](docs/cn/bthread.md) + * [bthread or not](docs/cn/bthread_or_not.md) + * [thread-local](docs/cn/thread_local.md) + * [Execution Queue](docs/cn/execution_queue.md) + * [bthread tracer](docs/cn/bthread_tracer.md) + * [bthread tagged task group](docs/cn/bthread_tagged_task_group.md) + * Client + * [基础功能](docs/cn/client.md) + * [错误码](docs/cn/error_code.md) + * [组合channels](docs/cn/combo_channel.md) + * [访问http/h2](docs/cn/http_client.md) + * [访问gRPC](docs/cn/http_derivatives.md#h2grpc) + * [访问thrift](docs/cn/thrift.md#client端访问thrift-server) + * [访问UB](docs/cn/ub_client.md) + * [Streaming RPC](docs/cn/streaming_rpc.md) + * [访问redis](docs/cn/redis_client.md) + * [访问memcached](docs/cn/memcache_client.md) + * [Backup request](docs/cn/backup_request.md) + * [Dummy server](docs/cn/dummy_server.md) + * Server + * [基础功能](docs/cn/server.md) + * [搭建http/h2服务](docs/cn/http_service.md) + * [搭建gRPC服务](docs/cn/http_derivatives.md#h2grpc) + * [搭建thrift服务](docs/cn/thrift.md#server端处理thrift请求) + * [搭建Nshead服务](docs/cn/nshead_service.md) + * [高效率排查server卡顿](docs/cn/server_debugging.md) + * [推送](docs/cn/server_push.md) + * [雪崩](docs/cn/avalanche.md) + * [自适应限流](docs/cn/auto_concurrency_limiter.md) + * [流媒体服务](https://github.com/brpc/media-server) + * [json2pb](docs/cn/json2pb.md) + * [内置服务](docs/cn/builtin_service.md) + * [status](docs/cn/status.md) + * [vars](docs/cn/vars.md) + * [connections](docs/cn/connections.md) + * [flags](docs/cn/flags.md) + * [rpcz](docs/cn/rpcz.md) + * [cpu_profiler](docs/cn/cpu_profiler.md) + * [heap_profiler](docs/cn/heap_profiler.md) + * [contention_profiler](docs/cn/contention_profiler.md) + * 工具 + * [rpc_press](docs/cn/rpc_press.md) + * [rpc_replay](docs/cn/rpc_replay.md) + * [rpc_view](docs/cn/rpc_view.md) + * [benchmark_http](docs/cn/benchmark_http.md) + * [parallel_http](docs/cn/parallel_http.md) + * 其他 + * [IOBuf](docs/cn/iobuf.md) + * [Streaming Log](docs/cn/streaming_log.md) + * [FlatMap](docs/cn/flatmap.md) + * [协程](docs/cn/coroutine.md) + * [熔断](docs/cn/circuit_breaker.md) + * [RDMA](docs/cn/rdma.md) + * [Bazel构建支持](docs/cn/bazel_support.md) + * [Wireshark baidu_std协议解析插件](docs/cn/wireshark_baidu_std.md) + * [bRPC外功修炼宝典](docs/cn/brpc_intro.pptx)(培训材料) + * [搭建大型服务入门](docs/en/tutorial_on_building_services.pptx)(培训材料) + * [bRPC内功修炼宝典](docs/en/brpc_internal.pptx)(培训材料) + * 深入RPC + * [New Protocol](docs/cn/new_protocol.md) + * [Atomic instructions](docs/cn/atomic_instructions.md) + * [IO](docs/cn/io.md) + * [Threading Overview](docs/cn/threading_overview.md) + * [Load Balancing](docs/cn/load_balancing.md) + * [Locality-aware](docs/cn/lalb.md) + * [Consistent Hashing](docs/cn/consistent_hashing.md) + * [Memory Management](docs/cn/memory_management.md) + * [Timer keeping](docs/cn/timer_keeping.md) + * [bthread_id](docs/cn/bthread_id.md) + * Use cases inside Baidu + * [百度地图api入口](docs/cn/case_apicontrol.md) + * [联盟DSP](docs/cn/case_baidu_dsp.md) + * [ELF学习框架](docs/cn/case_elf.md) + * [云平台代理服务](docs/cn/case_ubrpc.md) + +# 贡献代码 + +请参考[这里](CONTRIBUTING.md#chinese-version)。 + +# 反馈和参与 + +* bug、疑惑、修改建议都欢迎提在[Github Issues](https://github.com/apache/brpc/issues)中 +* 订阅邮件列表(dev-subscribe@brpc.apache.org)获得项目最新信息 + + +# 行为准则 +我们遵守Apache软件基金会的行为准则, 请参考如下 [链接](https://www.apache.org/foundation/policies/conduct) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..ac8d16b --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Reporting a Vulnerability + +`apache/brpc` follows the [Apache Software Foundation security process](https://www.apache.org/security/). Please report suspected +vulnerabilities privately to `security@apache.org`; do not open public +GitHub issues or pull requests for security reports. + +## Threat Model + +What the project treats as in scope and out of scope, the security +properties it provides and disclaims, the adversary model, and how +findings are triaged are documented in [THREAT_MODEL.md](./THREAT_MODEL.md). diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md new file mode 100644 index 0000000..e318507 --- /dev/null +++ b/THREAT_MODEL.md @@ -0,0 +1,627 @@ +# Apache bRPC — Threat Model (v1 draft) + +## §1 Header + +- **Project:** Apache bRPC (`apache/brpc`). +- **Scope:** the `apache/brpc` repository only. The PMC confirmed on 2026-05-20 that `apache/brpc-website` is out of scope for this engagement. This draft is first authored by the ASF Security Team and then reviewed by the PMC. +- **Version binding:** based on `master` around 2026-05-21. Vulnerability reports should be triaged against the model for the corresponding version, not against `HEAD`; re-bind on each release. +- **Authors and status:** drafted by the ASF Security Team (Glasswing pre-scan), revised by PMC member Weibing Wang. **DRAFT v1** +- **Reporting entry point:** at drafting time, the repository had no `SECURITY.md`, and the Apache security project index listed only the generic `security@apache.org` address. Until project-level disclosure documentation is published, report vulnerabilities to `security@apache.org` per ASF policy. +- **Provenance legend:** + - *(documented — source)*: from repository documentation, headers, gflags, source code, or Apache governance artifacts. + - *(inferred — Qn)*: inferred from code structure, general RPC security experience, or absence of a defense, with a corresponding question in §14. + - *(maintainer)*: not yet used in this draft; to be substituted after PMC confirmation. +- **Draft confidence:** most factual descriptions in §1-§13 come from documentation and source code. Intent, default security posture, resource boundaries, the built-in service trust model, and vulnerability triage criteria still require confirmation in §14. +- **bRPC overview:** bRPC is an embeddable C++ RPC framework that supports dispatching multiple protocols on the same port via content sniffing, including `baidu_std`, HTTP/1.x, HTTP/2/gRPC, Thrift, Redis, memcached, RTMP, Mongo, the nshead family, and others. It provides naming services, load balancers, optional TLS, bthread scheduling, and HTTP built-in admin services. bRPC is not a standalone daemon; downstream applications link `libbrpc` and start `brpc::Server` in-process. + +--- + +## §2 Scope and intended use + +bRPC's primary uses are: + +1. Expose one or more `google::protobuf::Service` implementations on a TCP port, with multiple protocols sharing the same port. +2. Act as a client `brpc::Channel` to access a single server or a cluster described by a naming service plus load balancer. +3. Be embedded by a host application; deployment, TLS, network exposure, authentication, routing, and process lifecycle are the responsibility of the application or operator. +4. Use built-in services such as `/status`, `/vars`, `/flags`, `/connections`, `/rpcz`, and `/health` for debugging and monitoring. +5. Use bRPC as an HTTP/h2 server or client, including Restful URL mapping and JSON-to-Protobuf / Protobuf-to-JSON conversion. + +bRPC is not a secure-by-default managed service. Threats land in the runtime code linked by the application and in the built-in admin services the application chooses to expose. + +### Caller / actor roles + +- **Application developer:** defines `.proto` files, implements services, chooses protocols and ports, and calls clients. Trusted within the compile-time and configuration boundary. +- **Operator:** runs the binary and configures ports, `internal_port`, TLS, gflags, naming services, rate limiting, and built-in services. Trusted for that instance. +- **Client peer (RPC client):** sends bytes to a bRPC server. Untrusted by default; protocol sniffers must handle arbitrary input. +- **Server peer:** the remote server that a bRPC client connects to. Response bytes are also untrusted. +- **Naming-service backend:** `bns`, `file`, `consul`, `nacos`, DNS, and similar backends. Configured by the operator and treated by bRPC as trusted infrastructure. +- **Built-in admin-service consumer:** a person or program accessing `/status`, `/flags`, `/rpcz`, and similar endpoints. Its trust level depends entirely on whether the operator places these endpoints on an internal network or disables them. + +### Component-family table + +| Family | Representative entry point | Touches outside the process? | In model? | +| --- | --- | --- | --- | +| **C++ runtime core** | `Server::Start`, `Channel::CallMethod`, `Socket`, `InputMessenger` | sockets, files, threads, TLS, optional RDMA | **In model** | +| **Wire-protocol parsers** | `Parse*Message` | reads untrusted bytes from sockets | **In model**, highest priority | +| **HTTP / h2 (+ gRPC) stack** | `HttpServerHandler`, `H2StreamContext`, `URI` | sockets, TLS, json2pb | **In model** | +| **Built-in admin services** | `/status`, `/vars`, `/flags`, `/rpcz`, `/dir`, `/threads`, profiler, metrics | HTTP, `/proc`, filesystem, profiler | **In model**, depending on exposure | +| **TLS / SSL layer** | `ServerSSLOptions`, `ChannelSSLOptions` | OpenSSL, certificate files | **In model** | +| **Authentication hooks** | `Authenticator::VerifyCredential` | wire token | **In model**, policy is implemented by the application | +| **Naming services** | `file://`, `http(s)://`, `consul://`, `nacos://`, `dns://` | files and network | **In model**, backend treated as trusted | +| **Load balancers** | rr, wrr, random, la, consistent hashing | pure computation | **In model**, not a direct attack surface | +| **Compression layer** | gzip/snappy/zlib/lz4 | CPU/memory | **In model** | +| **RDMA transport** | `rdma://` | verbs API, pinned memory | **In model** only when enabled at build time and runtime | +| **Coroutine bridge** | `usercode_in_coroutine` | scheduling | **In model** in non-default mode | +| **bthread / butil / bvar** | scheduler, IOBuf, counters | threads, futex, files, `/proc` | **In model** | +| **mcpack2pb / json2pb** | mcpack/JSON <-> pb | pure computation | **In model** on the corresponding protocol paths | +| **Java / Python bindings** | JNI / pyBRPC | language bridge | currently unsupported; **Out of model** | +| **tools / test / example / build / community / docs** | CLI, tests, examples, build, governance, docs | non-production runtime surface | **Out of model** | + +Wire-protocol parsers are the most important surface: they directly receive untrusted bytes, are implemented in C++, cover many protocols with different maturity levels, and multi-protocol sniffing can make every registered parser reachable from the same listening port. `enabled_protocols` can restrict the sniffing set, but its implementation details need maintainer confirmation. + +--- + +## §3 Out of scope (explicit non-goals) + +The following scenarios or threats are not protected by the bRPC threat model. Matching reports are closed according to §13. + +### Use cases out of scope + +1. **A secure-by-default RPC endpoint.** bRPC provides components, not a `brpcd` with a strong default security posture. +2. **A sandbox or process-isolation boundary.** Deserialization, business handlers, and the host application run in the same process. +3. **Cryptographic primitives.** TLS security comes from OpenSSL; bRPC only wraps and configures it. +4. **An application field-validation framework.** bRPC validates wire framing, not semantic fields such as email addresses, IDs, or business ranges. +5. **A complete replacement for authenticated transport.** Bare `baidu_std`, HTTP, Thrift, Redis, and similar protocols have no default authentication; `Authenticator` and mTLS require explicit configuration. +6. **A general HTML/JSON rendering security surface.** Escaping in application HTTP handlers is the application's responsibility. +7. **A general file server.** `/dir` is disabled by default and is not modeled as a production file-serving feature. +8. **Exposing built-in services to the public internet.** bRPC built-in services should only be exposed to trusted internal consumers. + +### Threats explicitly out of scope + +1. Attackers who control the calling process, can read/write process memory, or can call arbitrary bRPC APIs. +2. Attackers who control the compiler, dependencies, build environment, or supply chain. +3. Side channels such as timing, cache, power, and RowHammer. +4. Raw socket-layer DoS: SYN flood, slowloris, half-open connection exhaustion, and kernel table exhaustion. +5. Resource exhaustion in `bvar`/`bthread`/`butil` caused purely by in-process call patterns. +6. Performance loss caused by an operator intentionally enabling expensive tracing/profiling. +7. Compromise of the naming-service backend; bRPC trusts the backend configured by the operator. +8. Vulnerabilities in application handlers, such as SQL injection or missing business authorization. + +### Code shipped in the repo but out of scope + +`test/`, `example/`, `tools/`, build and packaging files, `community/`, `docs/`, and `apache/brpc-website` are not modeled as production runtime surfaces. The core boundary is the runtime, parsers, transports, built-in services, and support libraries under `src/`. + +--- + +## §4 Trust boundaries and data flow + +bRPC has four main trust boundaries: three on the network surface and one on the operator configuration and naming-service surface. + +### Boundary 1 — the wire (server side, business RPC) + +Server-side socket bytes are untrusted by default: + +```text +socket bytes + -> Acceptor / Socket + -> InputMessenger::CutInputMessage + -> try registered protocol parsers one by one + -> check whether declared body size exceeds -max_body_size + -> protocol message structure + -> protobuf / json2pb / mcpack2pb / other decoder + -> Service::Method(controller, request, response, done) +``` + +The trust transition occurs when the framework calls the user's `Service::Method`. Crashes or memory corruption reachable from the wire in parsers, decoders, compression, and TLS handshake are in model. How the business handler processes `Request` is the application's responsibility. + +All protocol parsers are enabled by default. `ServerOptions.enabled_protocols` can restrict the protocol sniffing set. + +### Boundary 2 — the wire (server side, built-in admin services) + +HTTP/h2 requests on the same listening port may be routed to built-in services. `internal_port` can move built-in services to a separate port, and `has_builtin_services=false` can disable them entirely. `/dir` and `/threads` are disabled by default; other built-in services are enabled by default when `has_builtin_services=true`. + +Model posture: built-in services are treated as an operator-trusted surface when placed on an internal port or protected by firewall. If exposed to the public internet, they become a major risk surface. Specific triage depends on Q46/Q50 in §14. + +Even when built-in services are only served internally, they still need to avoid severe attacks such as command injection or denial of service. However, modifying gflags, enabling rpcz, profiling, and similar operations through built-in services can affect business logic or program performance; those effects are normal use and are not security vulnerabilities. + +### Boundary 3 — the wire (client side) + +A bRPC client deserializes server responses through paths symmetric to the server side: untrusted bytes enter parsers and decoders before being handed to the application. Parser/decoder vulnerabilities triggered by malicious server responses are in model. + +### Boundary 4 — the operator config + naming-service backend + +`ServerOptions`, `ChannelOptions`, gflags, flagfiles, TLS file paths, and naming-service URIs are supplied by the operator and are trusted. Server lists returned by naming services are also treated as trusted infrastructure. Backend compromise is out of scope, but crashes in reply parsers on malformed input remain in model. + +### Reachability preconditions per family + +- Parser issues are in model only when reachable from Boundary 1/3; opt-in protocols require operator enablement. +- Built-in services are triaged according to the Boundary 2 exposure rules. +- TLS issues are inside the network surface; OpenSSL CVEs are handled as external dependency issues. +- Compression, json2pb, and mcpack2pb are in model when reachable from attacker input. +- RDMA and coroutine paths are modeled only when explicitly enabled or loaded. + +### Data not crossing a boundary + +Purely internal scheduler queues, in-process `bvar` counters, internal `IOBuf` lifetimes, `socket_map` lifetimes, Wireshark dissectors under `tools/`, and similar paths are not trust transitions. + +--- + +## §5 Assumptions about the environment + +bRPC is a C++ library that normally runs on Linux/macOS with POSIX sockets, OpenSSL, and a C++ toolchain. + +### OS / runtime / hardware + +- Linux and macOS are supported; Windows is not included in the security support scope for now. +- A C++11 or newer toolchain is required. +- POSIX sockets and Linux epoll or macOS kqueue are required. +- TLS depends on OpenSSL; risks from old OpenSSL versions are managed by the operator. +- gflags, protobuf, and leveldb are regular dependencies; tcmalloc/gperftools is optional. + +### Concurrency + +- bthread is an M:N scheduler; the number of worker pthreads depends on CPU count or configuration. +- `ServerOptions.num_threads` is a global worker-count hint, not hard isolation for one server. +- bthread-local state may be reused; user destructors must be reentrant. +- `-usercode_in_pthread` moves user code to pthreads and changes concurrency and queuing semantics. + +### Memory model + +bRPC is memory-unsafe C++. All `Parse*Message`, `Read*`, `Decompress*`, JSON/mcpack/protobuf decoder paths are candidate surfaces for OOB access, UAF, double-free, integer overflow, and stack overflow. `-max_body_size` is the key outer cap; the model depends on parsers correctly checking lengths and avoiding signed/unsigned overflow. + +### Time / clock + +bRPC uses `gettimeofday`, `clock_gettime`, `cpuwide_time_us`, and similar clocks. Resistance to timing side channels is not a goal. + +### Filesystem / network / peripherals + +bRPC opens TCP/Unix sockets, pid files, TLS cert/key files, naming-service files, profile output, rpc dumps, and reads `/proc/*` for bvar. It also sets normal socket options such as keepalive, `SO_REUSEPORT`, and `TCP_USER_TIMEOUT`. + +### What bRPC does *not* do to its host (negative-claim inventory) + +By default it does not install signal handlers. Normal operation does not fork child processes unless a profiler endpoint is triggered. It reads limited environment variables and gflags. First SSL use initializes OpenSSL global state. Logs go to stdout/stderr or glog. It does not modify locale or FPU state. `fork without exec` must happen before bRPC initialization. + +--- + +## §5a Build-time and configuration variants + +bRPC's security boundary is affected by build options, gflags, `ServerOptions`, `ChannelOptions`, and SSL options. + +### Build-time options that change the security envelope + +| Knob / flag | Default | Effect on model | +| --- | --- | --- | +| `WITH_THRIFT` | off | makes compiling and enabling the Thrift parser possible | +| `WITH_GLOG` | off | changes the logging backend; not critical | +| `WITH_RDMA` | off | introduces RDMA transport and pinned memory | +| `WITH_MESALINK` | off | OpenSSL replacement implementation | +| `BUILD_UNIT_TESTS` | off | builds tests, out of model | + +*Note: the table above is only an example, not a complete list. Analyze the latest code for details.* + +### Runtime gflags that change the security envelope + +| Flag / option | Default | Effect | Maintainer stance | +| --- | --- | --- | --- | +| `-max_body_size` | 64 MiB | global body cap for all protocols; protobuf's internal cap is lifted to `INT_MAX` | critical | +| `-enable_dir_service` | false | `/dir` reads files | dangerous when enabled | +| `-enable_threads_service` | false | `/threads` dumps stacks | dangerous when enabled | +| `-immutable_flags` | false | `/flags` can modify reloadable gflags | safer when enabled | +| `-usercode_in_pthread` | false | changes the thread model for user code | changes concurrency semantics and may cause deadlocks | +| `ServerOptions.has_builtin_services` | true | global switch for built-in services | critical | +| `ServerOptions.internal_port` | -1 | built-in services are on the main port by default | critical; safer when set | + +*Note: the table above is only an example, not a complete list. Analyze the latest code for details.* + +### Insecure-default summary (the §13 routing question) + +Defaults triaged by the PMC: + +1. `-max_body_size=64 MiB` is the production default. +2. `has_builtin_services=false` or `internal_port!=-1` is operator responsibility. +3. The combination of `-immutable_flags=false` and reachable `/flags` is acceptable. +4. `ChannelSSLOptions.verify.verify_mode=NOT_SET`, causing client TLS not to verify the server certificate, is acceptable. +5. `ChannelSSLOptions.protocols` including TLS 1.0/1.1 is acceptable. +6. `force_ssl=false`, causing a TLS-configured port to still accept plaintext, is acceptable. +7. `/dir` and `/threads` are disabled by default. If an operator enables them and exposes them publicly, treat that as a dangerous non-default configuration. + +*Note: the table above is only an example, not a complete list. Analyze the latest code for details.* + +--- + +## §6 Assumptions about inputs + +bRPC receives three classes of input: socket bytes, in-process API parameters, and operator configuration. Boundary 1/3 wire bytes are the highest risk. + +### Per-parameter trust table + +| Component / function | Parameter | Attacker-controllable? | Caller / operator must enforce | +| --- | --- | --- | --- | +| `InputMessenger::CutInputMessage` | initial connection bytes | yes | sniffer must defend against arbitrary bytes | +| `ParseRpcMessage` | PRPC header, `RpcMeta`, payload | yes | `-max_body_size`, `meta_size <= body_size` | +| `ParseHttpMessage` | method, URI, headers, body | yes | HTTP grammar, body cap | +| `ParseH2Message` | frame, HPACK headers, SETTINGS | yes | frame/header/HPACK/SETTINGS caps | +| Redis / memcache / Thrift / Mongo / nshead parsers, etc. | declared length, frame, body | yes | must be uniformly constrained by `-max_body_size` or protocol caps | +| RTMP / AMF | chunk stream, AMF object | yes | chunk and nesting caps | +| `json2pb::JsonToProtoMessage` | HTTP JSON body | yes | UTF-8, depth, repeated-field expansion | +| `mcpack2pb` | mcpack body | yes | depth and size caps | +| `Decompress` | compressed payload | yes | bRPC currently does not guarantee a decompressed-size cap; decompression may fail | +| TLS handshake | cert, SNI, ALPN | yes | operator configures verify, SNI, ALPN | +| `Authenticator::VerifyCredential` | credential, client_addr | yes; more obvious when header IP is enabled | application implements authentication; operator protects proxy boundary | +| Built-in `/flags` | gflag value | yes if reachable | hide built-ins or set `-immutable_flags=true` | +| Built-in `/dir` | path | yes if enabled | do not enable on public internet | +| Built-in profiler | `seconds` and similar parameters | yes if reachable | hide built-ins and limit profiling cost | +| `ServerOptions` / `ChannelOptions` / gflags | configuration values | no | operator trusted | +| Naming service reply | backend response | trusted by transitivity | parser should still tolerate malformed replies | + +### Size, shape, rate + +- `-max_body_size=64 MiB` is the global message cap; there is no unified per-method or per-protocol cap. +- The cap for unconsumed streaming RPC bytes is `-socket_max_streams_unconsumed_bytes`; default 0 means unlimited. +- Pending write buffers per socket are limited by `-socket_max_unwritten_bytes`. +- `max_concurrency` limits in-flight requests, not connection count; built-in services are not constrained by this option. +- `idle_timeout_sec` is disabled by default; connection count and slowloris are mainly handled by operators / load balancers. +- Protobuf has a recursion limit; JSON, mcpack, and AMF depth limits need confirmation. +- The framework has no general rate limiting. + +--- + +## §7 Adversary model + +### Primary adversary — the wire peer + +The primary attacker is the socket peer, which can send arbitrary bytes, confuse inputs across protocols, declare large lengths, construct compression bombs, trigger parser bugs, or make a client parse malicious server responses. Goals include crashes, memory/CPU exhaustion, OOB read/write, framing bypass, arbitrary command execution, authentication bypass, abuse of reachable built-in services, modification of reloadable gflags, and reading information through `/dir`. + +### Secondary adversary — the authenticated peer + +A peer that passes `Authenticator` is still untrusted at the parser layer. Authentication only narrows the attacker set; it does not change the peer's ability to send bytes. + +### Tertiary adversary — the HTTP-built-in-services consumer + +When `internal_port < 0` and `has_builtin_services=true`, any HTTP client that can reach the listening port may access `/status`, `/vars`, `/flags`, profiler, metrics, and similar endpoints. Final triage depends on Q46/Q50. + +### Out-of-scope adversaries + +In-process attackers, local side-channel attackers, attackers controlling `ServerOptions`/`ChannelOptions`, attackers controlling naming-service backends, build-chain attackers, and attacks that depend on the operator failing to harden network boundaries are not objects that bRPC itself promises to defend against. + +### Adversary capabilities by transport + +- **Plain TCP:** full wire control, no default authentication or encryption. +- **TLS:** provides encryption, but client/server certificate verification and TLS-only behavior require operator configuration. +- **RDMA:** modeled only when enabled; risks come from the verbs API and memory regions. +- **Unix domain socket:** if supported, the trust model is similar to TCP, with socket file permissions managed by the operator. + +--- + +## §8 Security properties the project provides + +### Memory and process-safety properties + +**P1. Under configured caps, valid wire input should not cause memory corruption.** +Any OOB access, UAF, double-free, or heap corruption in a parser, decoder, decompressor, TLS path, or built-in handler reachable from Boundary 1/3 is a high/critical issue. + +**P2. Bounded recursive structures should not cause stack overflow.** +Protobuf, JSON, AMF, mcpack, and similar decoders should reject excessively deep input before exhausting the stack. + +### Wire-format properties + +**P3. Multi-protocol sniffing should be conservative.** +When a parser returns `TRY_OTHERS`, it should not consume bytes; the same frame should not be taken over by the wrong parser. + +**P4. `-max_body_size` should be enforced.** +Parsers with declared lengths should reject and close the connection when the cap is exceeded; they should not allocate oversized memory first. + +### TLS / transport-security properties (when enabled) + +**P5. With SSL correctly configured and OpenSSL secure, TLS provides confidentiality and integrity.** + +**P6. The server rejects SSLv3 by default.** + +**P7. SNI can be used for certificate selection; fallback behavior on no match is controlled by `strict_sni`.** + +**P8. ALPN selection should be limited to `ServerSSLOptions.alpns`.** + +### Resource-bound properties + +**P9. Single-message memory use should be bounded by `-max_body_size`.** +Linear constant-factor overhead is acceptable; superlinear expansion or allocations that bypass the cap should be treated as bugs. See §9 D5 for decompression expansion. + +**P10. Per-socket unwritten buffers should be limited by `-socket_max_unwritten_bytes`.** + +**P11. Server/method concurrency caps should take effect when configured by the operator.** + +### Concurrency properties + +**P12. The framework's own cross-connection state should be thread-safe.** Synchronization of shared state in user handlers is the user's responsibility. + +**P13. `Channel::CallMethod` may be called across threads; `Channel::Init()` is not guaranteed to be thread-safe.** + +### Built-in admin-service properties (conditional on operator exposure) + +**P14. Built-in services should be protected by `has_builtin_services`, `internal_port`, or network boundaries.** + +**P15. `/dir` and `/threads` are disabled by default.** + +--- + +## §9 Security properties the project does *not* provide + +### Disclaimed properties (downstream's job, not bRPC's) + +**D1.** Bare protocols do not have default peer authentication; `Authenticator` authenticates per connection, not per request. +**D2.** No application-layer authorization is provided; RBAC, ACLs, scopes, and rate limits are implemented by the application or `Interceptor`. +**D3.** When `-http_header_of_user_ip` is enabled, bRPC does not defend against attacker-forged headers; trusted proxies and firewalls must enforce the boundary. +**D4.** Without a configured cap, streaming does not defend against unlimited growth of unconsumed stream data. +**D5.** bRPC does not guarantee a decompressed-output size cap. Decompression failure is allowed, but crashes or memory boundary violations are not. +**D6.** bRPC does not guarantee a cap on decoded JSON/repeated-field size. Decode failure is allowed, but crashes or memory boundary violations are not. +**D7.** h2/HPACK bomb, CONTINUATION flood, and PING flood defenses are currently missing and should preferably be added. +**D8.** No constant-time guarantee is provided. +**D9.** OpenSSL weaknesses and default TLS version choices are managed by the operator. +**D10.** No anti-replay is provided; request/sequence IDs are only used for request/response matching. +**D11.** There is no confidentiality without TLS. +**D12.** bRPC does not defend against socket-layer connection floods or slowloris. +**D13.** When built-ins are exposed and `-immutable_flags=false`, bRPC does not defend against `/flags` modifying reloadable gflags. +**D14.** When built-ins are reachable, bRPC does not defend against information disclosure from `/status`, `/vars`, `/connections`, `/rpcz`, and similar endpoints. +**D15.** When built-ins are reachable, bRPC does not defend against CPU/IO cost from profiler endpoints. +**D16.** When `internal_port=-1`, user services and built-in services on the same port are not isolated. +**D17.** Cross-protocol confusion from the multi-protocol sniffer is a parser issue the framework must defend against, but the risk of not restricting the protocol set is the operator's responsibility. +**D18.** Pathological protobuf input under protobuf `TotalBytesLimit=INT_MAX` has no additional framework cap. +**D19.** When `force_ssl=false`, a TLS-configured port still accepts plaintext. + +### False-friend properties + +**F1.** `has_builtin_services=true` is an administrative surface, not just harmless debug pages. +**F2.** `Authenticator::VerifyCredential` runs once per connection, not once per request. +**F3.** `force_ssl=false` means the same port accepts both SSL and non-SSL. +**F4.** `Controller::remote_side()` may be affected by `-http_header_of_user_ip`. +**F5.** `verify_depth=0` means verification is disabled, not strict verification. +**F6.** `Authenticator` does not cover all protocols by default. +**F7.** `strict_sni=false` means an unknown SNI falls back to the default certificate. +**F8.** `-max_body_size` does not limit decompressed size, decoded JSON size, or protobuf's internal cap. +**F9.** The LZ4 enum and documented support status are not fully aligned and need confirmation. + +### Well-known attack classes left to the caller + +Compression bombs, JSON/repeated-field expansion, HPACK bombs, TLS compression oracles, TLS downgrade, authentication brute force, `/flags` weaponization, slow decoder DoS, HTTP request smuggling, cross-protocol confusion, and naming-service spoofing. Except for parser memory safety and explicit claims, most of these are mitigated by the operator or application. + +--- + +## §10 Downstream responsibilities + +### Application developer responsibilities + +1. Pin the bRPC version and evaluate against that version's model. +2. Treat all deserialized fields as untrusted application input and perform business-semantic validation. +3. Implement `Authenticator` when identity is required, remembering that it authenticates per connection. +4. Implement `Interceptor` or handler checks when per-request authorization is required. +5. Do not call `Channel::Init()` concurrently across threads; `CallMethod()` is the thread-safe path. +6. Do not cache gflag values long-term when running with reloadable flags. +7. Do not use `assert` for security checks. + +### Operator responsibilities + +8. Hide built-in services on public ports: set `internal_port`, set `has_builtin_services=false`, or use a firewall. +9. Keep `-enable_dir_service=false` and `-enable_threads_service=false` on public ports. +10. Unless truly needed, set `-immutable_flags=true` or ensure `/flags` is reachable only by trusted operators. +11. Configure TLS: server certificate, client verification, removal of TLS 1.0/1.1, and `force_ssl=true` when needed. +12. Set `max_concurrency`, per-method concurrency, `idle_timeout_sec`, and streaming caps. +13. When enabling `-http_header_of_user_ip`, allow only trusted proxies to access the backend port. +14. Do not run bRPC processes as root. +15. Use escaping when application HTTP handlers output HTML. +16. Put public endpoints behind a load balancer / reverse proxy for connection-rate control, slowloris handling, and L7 hardening. +17. Track OpenSSL patches and rotate TLS material separately. +18. If compression is enabled, limit decompressed size at the application layer or sidecar. +19. Use `enabled_protocols` to expose only required protocols. +20. Choose trusted naming-service backends and protect permissions on flagfiles, cert/key files, and pid files. + +### Both + +21. Re-read and revise this model on each major bRPC upgrade or whenever a §12 condition occurs. + +--- + +## §11 Known misuse patterns + +**M1.** Exposing a server with default `has_builtin_services=true` and `internal_port=-1` to the public internet. +**M2.** Treating `Authenticator` as per-request authentication. +**M3.** Enabling `-http_header_of_user_ip` while allowing clients to bypass the trusted proxy and connect directly. +**M4.** Calling `Channel::Init()` from multiple threads. +**M5.** Using `assert` for security checks. +**M6.** Running bRPC as root. +**M7.** Configuring SSL but forgetting `force_ssl=true`, leaving plaintext available. +**M8.** Using plaintext protocols across trust boundaries. +**M9.** Setting `-max_body_size` very large and assuming it will not affect memory. +**M10.** Assuming compressed output size is limited by the framework. +**M11.** Enabling `/dir` or `/threads` on the public internet. +**M12.** Enabling nshead/nova/public/nshead_mcpack and forgetting the sniffer will try these parsers. +**M13.** Treating sequence/correlation IDs as nonces. +**M14.** Exposing `/flags` while keeping `-immutable_flags=false`. +**M15.** Not revalidating `auth_context()` or downstream identity information. +**M16.** Combining `-usercode_in_pthread` with high concurrency without setting `max_concurrency`. +**M17.** Using an untrusted naming service while client TLS does not verify the server certificate. +**M18.** Exposing profiler endpoints to the public internet. +**M19.** Enabling `/rpcz` on traffic containing sensitive request fields. +**M20.** Enabling RDMA and exposing it to untrusted peers. + +--- + +## §11a Known non-findings (recurring false positives) + +**N1.** A parser reading length and checking it with `FLAGS_max_body_size` is by design; it is an issue only if a parser truly skips the check. +**N2.** ~~Decompression has no decompressed-size cap, per §9 D5.~~ +**N3.** Built-in services expose internal information; operators should hide them. +**N4.** `/flags` modifying reloadable gflags without authentication is pending Q46. +**N5.** `/dir` reads files: disabled by default; enabling it is a dangerous non-default configuration. +**N6.** `/threads` dumps stacks: disabled by default. +**N7.** bvar reading `/proc/loadavg` and `/proc/stat` is by design. +**N8.** Client TLS includes TLS 1.0/1.1 by default; triage pending Q56. +**N9.** Client TLS does not verify server certificates by default; triage pending Q57. +**N10.** `force_ssl=false` allowing plaintext is documented behavior. +**N11.** `Authenticator` running once per connection is documented behavior. +**N12.** Issues in test/example/tools/build/community are out of model. +**N13.** Hard-coded certificates under `test/` are out of model. +**N14.** HTTP Basic credentials in plaintext are a consequence of the operator choosing plaintext transport. +**N15.** MD5/SHA-1 used for load-balancer hash distribution is not cryptographic use. +**N16.** Wire-reachable integer overflow/OOB is a valid vulnerability class and should not be mechanically closed. +**N17.** `session_local_data_factory` lifetime is guaranteed by the user as required by documentation. +**N18.** The bthread keytable pool retaining objects is a reuse design. +**N19.** `Channel.Init()` being non-thread-safe is documented behavior. +**N20.** Runtime certificate add/delete/modify concurrency safety is judged case by case and needs maintainer confirmation. +**N21.** Built-ins being handled on the main port is default behavior; triage pending Q50. +**N22.** The sniffer trying multiple parsers on unknown bytes is by design. +**N23.** A fuzz crash in an opt-in parser can still be `VALID` when the path is enabled. +**N24.** Protobuf `TotalBytesLimit=INT_MAX` is documented design; the outer layer relies on `-max_body_size`. +**N25.** Naming-service backend replies are trusted. +**N26.** Triage for crashes on malformed DNS/consul/nacos replies must distinguish backend compromise from parser hardening. +**N27.** Brute-force authentication using many short connections is rate-limited by the operator. +**N28.** `/rpcz` may expose sampled request information; this is a design risk after enabling it. +**N29.** OpenSSL CVEs are not bRPC CVEs, but they affect deployments. +**N30.** `fork without exec` after bRPC initialization is unsupported. + +--- + +## §12 Conditions that would change this model + +The following changes should trigger model revision: + +1. A new wire protocol parser or sniffer registration. +2. A new built-in admin service, especially one with write capability. +3. Changes to security-relevant defaults in §5a: `-max_body_size`, streaming caps, `-immutable_flags`, built-in defaults, `force_ssl`, TLS verification, TLS protocols, and so on. +4. An opt-in protocol becoming enabled by default. +5. Adding a decompressed-size cap. +6. Adding h2/HPACK bomb defenses. +7. Adding a per-request authentication mode. +8. Adding `SECURITY.md` or a project security page to the repository. +9. The PMC publishing a security policy. +10. A new CVE class that cannot be triaged under §13. + +Vulnerabilities should be triaged against the model in effect for the affected version, not against the latest `HEAD`. + +--- + +## §13 Triage dispositions + +| Disposition | Meaning | Licensed by | +| --- | --- | --- | +| `VALID` | violates a property claimed in §8, with attacker, input, and component all in model | §8, §6, §7 | +| `VALID-HARDENING` | does not violate an existing claim, but the API easily leads to §11 misuse and the project may choose to harden it | §11 | +| `OUT-OF-MODEL: trusted-input` | requires controlling configuration or objects marked trusted by the model | §6 | +| `OUT-OF-MODEL: adversary-not-in-scope` | requires attacker capabilities excluded by the model | §7, §3 | +| `OUT-OF-MODEL: unsupported-component` | located in out-of-scope components such as test/tools/example/build/community/docs | §3 | +| `OUT-OF-MODEL: non-default-build` | appears only under dangerous or non-default configuration | §5a | +| `BY-DESIGN: property-disclaimed` | belongs to a property explicitly disclaimed in §9 | §9 | +| `KNOWN-NON-FINDING` | matches a recurring false-positive pattern in §11a | §11a | +| `MODEL-GAP` | cannot be classified and requires model revision | §12 | + +### Worked routing examples + +- `Parse*Message` OOB write: `VALID`. +- Private key leak under `test/`: `OUT-OF-MODEL: unsupported-component`. +- Client TLS not verifying server certificates by default: `BY-DESIGN`. +- Remote access to `/flags/max_body_size?setvalue=0`: operator responsibility. +- `/dir?path=/etc/passwd`: if it requires `-enable_dir_service=true`, `OUT-OF-MODEL: non-default-build`. +- A small gzip payload decompressing to huge output and causing OOM: `VALID`. +- h2 HPACK bomb: if defenses are missing and reachable, potentially `VALID`. +- Hostile consul backend redirecting a client: `OUT-OF-MODEL: adversary-not-in-scope`; malformed reply crashes can be judged separately. +- OpenSSL CVE: `BY-DESIGN: property-disclaimed`. +- `Channel.Init()` data race: `BY-DESIGN`, because documentation says it is not thread-safe. + +--- + +## §14 Open questions for the maintainers + +Each question includes the draft recommendation and awaits PMC confirmation, correction, or deletion. After confirmation, the corresponding *(inferred)* tags in the body should be updated to *(maintainer)*. + +--- + +### Wave 1 — scope, adversary, and the insecure-default rulings (must answer first) + +**Q1.** Is the server-side wire peer untrusted by default, so the runtime cannot assume input matches any registered protocol? Yes. +**Q2.** Is client-side deserialization of server responses in the same model scope as server-side handling of client requests? Yes. +**Q40.** Is `-max_body_size=64 MiB` a supported production default, or a baseline that operators must lower per deployment? Production default. +**Q50.** `has_builtin_services=true` + `internal_port=-1` puts built-ins on the main port by default; how should public exposure of `/flags`/`/version` be triaged? Operator responsibility. +**Q46.** `-immutable_flags=false` allows reachable `/flags` to modify reloadable gflags; should the default be changed to true? No, operator responsibility. +**Q57.** Client TLS does not verify server certificates by default; should successful MITM be `BY-DESIGN` or `VALID`/`VALID-HARDENING`? `BY-DESIGN`. +**Q56.** Is it acceptable that `ChannelSSLOptions.protocols` includes TLS 1.0/1.1 by default? `BY-DESIGN`. +**Q51.** Is `force_ssl=false`, which makes a TLS port still accept plaintext, only operator responsibility? Operator responsibility. + +### Wave 2 — what the runtime does (and does not do) to its host + +**Q9.** Is bRPC explicitly an embedded library rather than a secure-by-default daemon? Yes. +**Q10.** Does the user handler run in the same process as the deserializer, with no sandbox provided by bRPC? Yes. +**Q11.** Are TLS/SSL cryptographic guarantees entirely inherited from OpenSSL? Yes. +**Q12.** Does bRPC validate only wire framing, not application field semantics? Yes. +**Q24.** Is Windows outside the security support environment? Yes. +**Q28.** Do maintainers agree that parser/read/decompress paths are memory-unsafe C++ attack surfaces? Yes. + +### Wave 3 — wire-protocol parser surface (per-protocol hardening parity) + +**Q8.** Is the list of default-on, opt-in, and client-only protocols accurate? Yes. +**Q37.** Should `enabled_protocols` ensure parsers not listed are never tried? Yes. +**Q22.** Should vulnerabilities in opt-in parsers be triaged as `OUT-OF-MODEL: non-default-build` when not enabled? Parsers are all enabled by default, so triage as `VALID`. +**Q80.** Do all parsers with length framing uniformly check `FLAGS_max_body_size`? Yes. +**Q59.** Does h2/HPACK have defenses against bomb, CONTINUATION flood, PING flood, and SETTINGS flood? Preferably yes. +**Q68.** Does `mcpack2pb` have recursion/depth caps? Preferably yes. +**Q69.** Does `json2pb` have a JSON depth cap? Preferably yes. + +### Wave 4 — built-in admin services trust model + +**Q4.** Is the trust status of built-in admin consumers determined entirely by operator exposure? Yes. +**Q19.** Should public access to default built-ins such as `/version` be triaged as operator responsibility? Yes. +**Q45.** Because `/dir` and `/threads` are disabled by default, should issues after public enablement be triaged as non-default-build? Yes. +**Q71.** Do profiler endpoint `seconds` parameters have reasonable upper bounds? How should DoS after exposure be triaged? Preferably yes. +**Q87.** Is there no isolation between built-in services, so a bug in one handler can affect other services in the same process? This is an issue. + +### Wave 5 — TLS / SSL / authentication + +**Q52.** Is `strict_sni=false` fallback to the default certificate a supported posture? Yes. +**Q54.** Is server-side mTLS being disabled by default a supported posture? Yes. +**Q90.** Which protocols does `Authenticator` actually cover? Does it not cover Thrift/Redis/memcache/Mongo/RTMP/nshead by default? Yes. +**Q77.** Is an authenticated peer still an untrusted adversary at the parser layer? Yes. +**Q47.** Does `-http_header_of_user_ip` have no built-in trusted-proxy verification? Yes; this is not an issue. + +### Wave 6 — resource bounds, compression, streaming + +**Q42.** Should streaming unconsumed bytes being unlimited by default change default behavior or require explicit operator configuration? Yes. +**Q70.** Is it confirmed that bRPC has no decompressed-output size cap? Should `-max_decompressed_size` be added? Preferably yes. +**Q73.** Is it true that there is no per-protocol/per-method body cap and only global `-max_body_size`? There is indeed none, and this is acceptable. +**Q74.** Is there no framework-level connection-count cap? None. +**Q82.** Can the resource policy be stated as: superlinear memory growth over the wire-declared size is a bug when constrained by `-max_body_size`? Yes. +**Q93.** Has the HTTP parser been audited for request smuggling defenses? Yes. + +### Wave 7 — concurrency, properties, and remaining open items + +**Q5.** Does RDMA require explicit build-time and runtime enablement, with related vulnerabilities out of model when not enabled? Yes. +**Q7.** Are `test/`, `example/`, `tools/`, and `community/` out of model? Yes. +**Q18.** Do wire bytes remain untrusted until the user's `Service::Method` is called? Yes. +**Q20.** Is client response parsing modeled equivalently to server request parsing? Yes. +**Q23.** Can crashes on malformed naming-service replies be `VALID`, while malicious backend redirection is out of model? Yes. +**Q72.** Should consul/nacos and similar naming-service parsers defend against malformed input? Yes. +**Q79.** Does bRPC have continuous fuzzing/OSS-Fuzz; if not, does P1 mainly rely on code review and CI? No. +**Q83.** Under the per-connection bthread model, is synchronization of application shared state entirely the user's responsibility? Yes. +**Q84.** Is per-request authorization implemented by `Interceptor` or the handler, not `Authenticator`? Yes. +**Q88.** Should cross-protocol sniffer confusion be handled as a framework `VALID` issue? Yes. + +--- + +## §15 Optional: machine-readable companion + +v1 does not generate a machine-readable sidecar. After the defaults and triage decisions in §14 wave 1 stabilize, generate a derived index for tooling: entry points and parameter trust levels, in-scope/out-of-scope components, security-relevant gflag defaults, §8 properties, §9 disclaimed properties, §11a non-findings, and §13 disposition labels. This document remains the normative specification. + +--- + +## Appendix: SECURITY.md statement -> threat-model § back-map + +At drafting time, the repository had no `SECURITY.md`, and the Apache security project index provided only the generic `security@apache.org` address, with no bRPC project-level security page. + +| Source | Statement | Threat-model section | +| --- | --- | --- | +| `security.apache.org/projects/` | no project-level security content | N/A | +| future `SECURITY.md` | TBD by PMC | to be backfilled in a future version | + +Once the PMC publishes `SECURITY.md` or an official security policy, that artifact should become a higher-authority source. This document should then map back to or link to the official document. + +--- + +*End of v1 draft.* + diff --git a/WORKSPACE.bzlmod b/WORKSPACE.bzlmod new file mode 100644 index 0000000..e69de29 diff --git a/bazel/BUILD.bazel b/bazel/BUILD.bazel new file mode 100644 index 0000000..c75172c --- /dev/null +++ b/bazel/BUILD.bazel @@ -0,0 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +load("@hedron_compile_commands//:refresh_compile_commands.bzl", "refresh_compile_commands") + +refresh_compile_commands( + name = "brpc_compdb", + # Specify the targets of interest. + # For example, specify a dict of targets and their arguments: + targets = { + "//:brpc": "", + }, + # For more details, feel free to look into refresh_compile_commands.bzl if you want. +) diff --git a/bazel/config/BUILD.bazel b/bazel/config/BUILD.bazel new file mode 100644 index 0000000..eec551d --- /dev/null +++ b/bazel/config/BUILD.bazel @@ -0,0 +1,152 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +load("@bazel_skylib//lib:selects.bzl", "selects") + +licenses(["notice"]) # Apache v2 + +selects.config_setting_group( + name = "brpc_with_glog", + match_any = [ + ":brpc_with_glog_deprecated_flag", + ":brpc_with_glog_new_flag", + ], + visibility = ["//visibility:public"], +) + +config_setting( + name = "brpc_with_glog_deprecated_flag", + define_values = {"with_glog": "true"}, +) + +config_setting( + name = "brpc_with_glog_new_flag", + define_values = {"BRPC_WITH_GLOG": "true"}, +) + +selects.config_setting_group( + name = "brpc_with_mesalink", + match_any = [ + ":brpc_with_mesalink_deprecated_flag", + ":brpc_with_mesalink_new_flag", + ], + visibility = ["//visibility:public"], +) + +config_setting( + name = "brpc_with_mesalink_deprecated_flag", + define_values = {"with_mesalink": "true"}, +) + +config_setting( + name = "brpc_with_mesalink_new_flag", + define_values = {"BRPC_WITH_MESALINK": "true"}, +) + +selects.config_setting_group( + name = "brpc_with_thrift", + match_any = [ + ":brpc_with_thrift_deprecated_flag", + ":brpc_with_thrift_new_flag", + ], + visibility = ["//visibility:public"], +) + +config_setting( + name = "brpc_with_thrift_legacy_version", + define_values = {"BRPC_WITH_THRIFT_LEGACY_VERSION": "true"}, + visibility = ["//visibility:public"], +) + +config_setting( + name = "brpc_with_thrift_deprecated_flag", + define_values = {"with_thrift": "true"}, +) + +config_setting( + name = "brpc_with_thrift_new_flag", + define_values = {"BRPC_WITH_THRIFT": "true"}, +) + +config_setting( + name = "brpc_build_for_unittest", + define_values = {"BRPC_BUILD_FOR_UNITTEST": "true"}, + visibility = ["//visibility:public"], +) + +config_setting( + name = "brpc_with_sse42", + define_values = {"BRPC_WITH_SSE42": "true"}, + visibility = ["//visibility:public"], +) + +config_setting( + name = "darwin", + values = {"cpu": "darwin"}, + visibility = ["//:__subpkgs__"], +) + +config_setting( + name = "brpc_with_rdma", + define_values = {"BRPC_WITH_RDMA": "true"}, + visibility = ["//visibility:public"], +) + +config_setting( + name = "brpc_with_boringssl", + define_values = {"BRPC_WITH_BORINGSSL": "true"}, + visibility = ["//visibility:public"], +) + +config_setting( + name = "brpc_with_debug_bthread_sche_safety", + define_values = {"with_debug_bthread_sche_safety": "true"}, + visibility = ["//visibility:public"], +) + +config_setting( + name = "brpc_with_debug_lock", + define_values = {"with_debug_lock": "true"}, + visibility = ["//visibility:public"], +) + +config_setting( + name = "brpc_with_asan", + define_values = {"with_asan": "true"}, + visibility = ["//visibility:public"], +) + +config_setting( + name = "brpc_with_bthread_tracer", + constraint_values = [ + "@platforms//cpu:x86_64", + ], + define_values = { + "with_bthread_tracer": "true", + }, + visibility = ["//visibility:public"], +) + +config_setting( + name = "brpc_with_no_pthread_mutex_hook", + define_values = {"BRPC_WITH_NO_PTHREAD_MUTEX_HOOK": "true"}, + visibility = ["//visibility:public"], +) + +config_setting( + name = "with_babylon_counter", + define_values = {"with_babylon_counter": "true"}, + visibility = ["//visibility:public"], +) \ No newline at end of file diff --git a/bazel/third_party/BUILD.bazel b/bazel/third_party/BUILD.bazel new file mode 100644 index 0000000..fefa6c3 --- /dev/null +++ b/bazel/third_party/BUILD.bazel @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# Thie empty BUILD.bazel file is required to make Bazel treat +# this directory as a package. diff --git a/bazel/third_party/crc32c/BUILD.bazel b/bazel/third_party/crc32c/BUILD.bazel new file mode 100644 index 0000000..fefa6c3 --- /dev/null +++ b/bazel/third_party/crc32c/BUILD.bazel @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# Thie empty BUILD.bazel file is required to make Bazel treat +# this directory as a package. diff --git a/bazel/third_party/crc32c/crc32c.BUILD b/bazel/third_party/crc32c/crc32c.BUILD new file mode 100644 index 0000000..72715d4 --- /dev/null +++ b/bazel/third_party/crc32c/crc32c.BUILD @@ -0,0 +1,93 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") + +genrule( + name = "crc32c_config_h", + srcs = ["src/crc32c_config.h.in"], + outs = ["crc32c/crc32c_config.h"], + cmd = """ +sed -e 's/#cmakedefine01/#define/' \ +""" + select({ + "@//bazel/config:brpc_with_sse42": """-e 's/ HAVE_SSE42/ HAVE_SSE42 1/' \ +""", + "//conditions:default": """-e 's/ HAVE_SSE42/ HAVE_SSE42 0/' \ +""", + }) + select({ + "@//bazel/config:brpc_with_glog": """-e 's/ CRC32C_TESTS_BUILT_WITH_GLOG/ CRC32C_TESTS_BUILT_WITH_GLOG 1/' \ +""", + "//conditions:default": """-e 's/ CRC32C_TESTS_BUILT_WITH_GLOG/ CRC32C_TESTS_BUILT_WITH_GLOG 0/' \ +""", + }) + """-e 's/ BYTE_ORDER_BIG_ENDIAN/ BYTE_ORDER_BIG_ENDIAN 0/' \ + -e 's/ HAVE_BUILTIN_PREFETCH/ HAVE_BUILTIN_PREFETCH 0/' \ + -e 's/ HAVE_MM_PREFETCH/ HAVE_MM_PREFETCH 0/' \ + -e 's/ HAVE_ARM64_CRC32C/ HAVE_ARM64_CRC32C 0/' \ + -e 's/ HAVE_STRONG_GETAUXVAL/ HAVE_STRONG_GETAUXVAL 0/' \ + -e 's/ HAVE_WEAK_GETAUXVAL/ HAVE_WEAK_GETAUXVAL 0/' \ + < $< > $@ +""", +) + +cc_library( + name = "crc32c", + srcs = [ + "src/crc32c.cc", + "src/crc32c_arm64.cc", + "src/crc32c_arm64.h", + "src/crc32c_arm64_check.h", + "src/crc32c_internal.h", + "src/crc32c_portable.cc", + "src/crc32c_prefetch.h", + "src/crc32c_read_le.h", + "src/crc32c_round_up.h", + "src/crc32c_sse42.cc", + "src/crc32c_sse42.h", + "src/crc32c_sse42_check.h", + ":crc32c_config_h", + ], + hdrs = [ + "include/crc32c/crc32c.h", + ], + copts = select({ + "@//bazel/config:brpc_with_sse42": ["-msse4.2"], + "//conditions:default": [], + }), + strip_include_prefix = "include", + visibility = ["//visibility:public"], +) + +cc_test( + name = "crc32c_test", + srcs = [ + "src/crc32c_arm64_unittest.cc", + "src/crc32c_extend_unittests.h", + "src/crc32c_portable_unittest.cc", + "src/crc32c_prefetch_unittest.cc", + "src/crc32c_read_le_unittest.cc", + "src/crc32c_round_up_unittest.cc", + "src/crc32c_sse42_unittest.cc", + "src/crc32c_test_main.cc", + "src/crc32c_unittest.cc", + ], + deps = [ + ":crc32c", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ] + select({ + "@//bazel/config:brpc_with_glog": ["@com_github_google_glog//:glog"], + "//conditions:default": [], + }), +) diff --git a/bazel/third_party/event/BUILD.bazel b/bazel/third_party/event/BUILD.bazel new file mode 100644 index 0000000..fefa6c3 --- /dev/null +++ b/bazel/third_party/event/BUILD.bazel @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# Thie empty BUILD.bazel file is required to make Bazel treat +# this directory as a package. diff --git a/bazel/third_party/event/event.BUILD b/bazel/third_party/event/event.BUILD new file mode 100644 index 0000000..6fd6759 --- /dev/null +++ b/bazel/third_party/event/event.BUILD @@ -0,0 +1,59 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +load("@rules_foreign_cc//foreign_cc:defs.bzl", "cmake") + +filegroup( + name = "all_srcs", + srcs = glob(["**"]), +) + +cmake( + name = "event", + cache_entries = { + "EVENT__DISABLE_BENCHMARK": "ON", + "EVENT__DISABLE_TESTS": "ON", + "EVENT__DISABLE_SAMPLES": "ON", + "EVENT__LIBRARY_TYPE": "STATIC", + "OPENSSL_ROOT_DIR": "$$EXT_BUILD_DEPS$$/openssl", + }, + generate_args = ["-GNinja"], + lib_source = ":all_srcs", + linkopts = [ + "-pthread", + ], + out_static_libs = select({ + "@platforms//os:windows": [ + "event.lib", + "event_core.lib", + "event_extra.lib", + "event_openssl.lib", + "event_pthreads.lib", + ], + "//conditions:default": [ + "libevent.a", + "libevent_core.a", + "libevent_extra.a", + "libevent_openssl.a", + "libevent_pthreads.a", + ], + }), + visibility = ["//visibility:public"], + deps = [ + # Zlib is only used for testing. + "@openssl//:crypto", + "@openssl//:ssl", + ], +) diff --git a/bazel/third_party/glog/0001-mark-override-resolve-warning.patch b/bazel/third_party/glog/0001-mark-override-resolve-warning.patch new file mode 100644 index 0000000..7a9bbb8 --- /dev/null +++ b/bazel/third_party/glog/0001-mark-override-resolve-warning.patch @@ -0,0 +1,36 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +diff --git a/src/glog/logging.h.in b/src/glog/logging.h.in +index 421f1e0..a363141 100755 +--- a/src/glog/logging.h.in ++++ b/src/glog/logging.h.in +@@ -1334,7 +1334,7 @@ class GOOGLE_GLOG_DLL_DECL LogStreamBuf : public std::streambuf { + } + + // This effectively ignores overflow. +- int_type overflow(int_type ch) { ++ int_type overflow(int_type ch) override { + return ch; + } + +@@ -1862,7 +1862,7 @@ class GOOGLE_GLOG_DLL_DECL NullStreamFatal : public NullStream { + NullStreamFatal() { } + NullStreamFatal(const char* file, int line, const CheckOpString& result) : + NullStream(file, line, result) { } +- @ac_cv___attribute___noreturn@ ~NullStreamFatal() throw () { _exit(1); } ++ @ac_cv___attribute___noreturn@ ~NullStreamFatal() throw () override { _exit(1); } + }; + + // Install a signal handler that will dump signal information and a stack diff --git a/bazel/third_party/glog/BUILD.bazel b/bazel/third_party/glog/BUILD.bazel new file mode 100644 index 0000000..fefa6c3 --- /dev/null +++ b/bazel/third_party/glog/BUILD.bazel @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# Thie empty BUILD.bazel file is required to make Bazel treat +# this directory as a package. diff --git a/bazel/third_party/leveldb/BUILD.bazel b/bazel/third_party/leveldb/BUILD.bazel new file mode 100644 index 0000000..ea0c109 --- /dev/null +++ b/bazel/third_party/leveldb/BUILD.bazel @@ -0,0 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +exports_files( + [ + "port_config.h", + "port.h", + ], +) diff --git a/bazel/third_party/leveldb/leveldb.BUILD b/bazel/third_party/leveldb/leveldb.BUILD new file mode 100644 index 0000000..787f77f --- /dev/null +++ b/bazel/third_party/leveldb/leveldb.BUILD @@ -0,0 +1,72 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +load("@rules_cc//cc:defs.bzl", "cc_library") +load("@bazel_skylib//rules:copy_file.bzl", "copy_file") + +copy_file( + name = "port_config_h", + src = "@//bazel/third_party/leveldb:port_config.h", + out = "port/port_config.h", + allow_symlink = True, +) + +copy_file( + name = "port_h", + src = "@//bazel/third_party/leveldb:port.h", + out = "port/port.h", + allow_symlink = True, +) + +cc_library( + name = "leveldb", + srcs = glob( + [ + "db/**/*.cc", + "db/**/*.h", + "helpers/**/*.cc", + "helpers/**/*.h", + "port/**/*.cc", + "port/**/*.h", + "table/**/*.cc", + "table/**/*.h", + "util/**/*.cc", + "util/**/*.h", + ], + exclude = [ + "**/*_test.cc", + "**/testutil.*", + "**/*_bench.cc", + "**/*_windows*", + "db/leveldbutil.cc", + ], + ), + hdrs = glob( + ["include/**/*.h"], + exclude = ["doc/**"], + ) + [ + ":port_h", + ":port_config_h", + ], + includes = [ + ".", + "include", + ], + visibility = ["//visibility:public"], + deps = [ + "@com_github_google_crc32c//:crc32c", + "@com_github_google_snappy//:snappy", + ], +) diff --git a/bazel/third_party/leveldb/port.h b/bazel/third_party/leveldb/port.h new file mode 100644 index 0000000..8c9a4ea --- /dev/null +++ b/bazel/third_party/leveldb/port.h @@ -0,0 +1,34 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +// Copyright (c) 2011 The LevelDB Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. See the AUTHORS file for names of contributors. + +#ifndef STORAGE_LEVELDB_PORT_PORT_H_ +#define STORAGE_LEVELDB_PORT_PORT_H_ + +#include + +#define LEVELDB_HAS_PORT_CONFIG_H 1 + +// Include the appropriate platform specific file below. If you are +// porting to a new platform, see "port_example.h" for documentation +// of what the new port_.h file must provide. +#include "port/port_stdcxx.h" + +#endif // STORAGE_LEVELDB_PORT_PORT_H_ diff --git a/bazel/third_party/leveldb/port_config.h b/bazel/third_party/leveldb/port_config.h new file mode 100644 index 0000000..4ccdebf --- /dev/null +++ b/bazel/third_party/leveldb/port_config.h @@ -0,0 +1,38 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +// Copyright 2017 The LevelDB Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. See the AUTHORS file for names of contributors. + +#ifndef STORAGE_LEVELDB_PORT_PORT_CONFIG_H_ +#define STORAGE_LEVELDB_PORT_PORT_CONFIG_H_ + +// Define to 1 if you have a definition for fdatasync() in . +#define HAVE_FUNC_FDATASYNC 1 + +// Define to 1 if you have Google CRC32C. +#define HAVE_CRC32C 1 + +// Define to 1 if you have Google Snappy. +#define HAVE_SNAPPY 1 + +// Define to 1 if your processor stores words with the most significant byte +// first (like Motorola and SPARC, unlike Intel and VAX). +#define LEVELDB_IS_BIG_ENDIAN 0 + +#endif // STORAGE_LEVELDB_PORT_PORT_CONFIG_H_ diff --git a/bazel/third_party/openssl/BUILD.bazel b/bazel/third_party/openssl/BUILD.bazel new file mode 100644 index 0000000..fefa6c3 --- /dev/null +++ b/bazel/third_party/openssl/BUILD.bazel @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# Thie empty BUILD.bazel file is required to make Bazel treat +# this directory as a package. diff --git a/bazel/third_party/openssl/openssl.BUILD b/bazel/third_party/openssl/openssl.BUILD new file mode 100644 index 0000000..c02cb6f --- /dev/null +++ b/bazel/third_party/openssl/openssl.BUILD @@ -0,0 +1,165 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright 2016 The Bazel Authors. All rights reserved. +# +# 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. +# +# Copied from https://github.com/bazelbuild/rules_foreign_cc/blob/0.7.0/examples/third_party/openssl/BUILD.openssl.bazel +# +# Modifications: +# 1. Create alias `ssl` & `crypto` to align with boringssl. +# 2. Build with `@com_github_madler_zlib//:zlib`. +# 3. Add more configure options coming from debian openssl package configurations. + +load("@rules_foreign_cc//foreign_cc:defs.bzl", "configure_make", "configure_make_variant") + +filegroup( + name = "all_srcs", + srcs = glob(["**"]), +) + +CONFIGURE_OPTIONS = [ + "no-idea", + "no-mdc2", + "no-rc5", + "no-ssl3", + "no-ssl3-method", + "enable-rfc3779", + "enable-cms", + "no-capieng", + "enable-ec_nistp_64_gcc_128", + "--with-zlib-include=$$EXT_BUILD_DEPS$$", + "--with-zlib-lib=$$EXT_BUILD_DEPS$$", + # https://stackoverflow.com/questions/36220341/struct-in6-addr-has-no-member-named-s6-addr32-with-ansi + "-D_DEFAULT_SOURCE=1", + "-DPEDANTIC", +] + +LIB_NAME = "openssl" + +MAKE_TARGETS = [ + "build_libs", + "install_dev", +] + +config_setting( + name = "msvc_compiler", + flag_values = { + "@bazel_tools//tools/cpp:compiler": "msvc-cl", + }, + visibility = ["//visibility:public"], +) + +alias( + name = "ssl", + actual = "openssl", + visibility = ["//visibility:public"], +) + +alias( + name = "crypto", + actual = "openssl", + visibility = ["//visibility:public"], +) + +alias( + name = "openssl", + actual = select({ + ":msvc_compiler": "openssl_msvc", + "//conditions:default": "openssl_default", + }), + visibility = ["//visibility:public"], +) + +configure_make_variant( + name = "openssl_msvc", + build_data = [ + "@nasm//:nasm", + "@perl//:perl", + ], + configure_command = "Configure", + configure_in_place = True, + configure_options = CONFIGURE_OPTIONS + [ + "VC-WIN64A", + # Unset Microsoft Assembler (MASM) flags set by built-in MSVC toolchain, + # as NASM is unsed to build OpenSSL rather than MASM + "ASFLAGS=\" \"", + ], + configure_prefix = "$PERL", + env = { + # The Zi flag must be set otherwise OpenSSL fails to build due to missing .pdb files + "CFLAGS": "-Zi", + "PATH": "$$(dirname $(execpath @nasm//:nasm)):$$PATH", + "PERL": "$(execpath @perl//:perl)", + }, + lib_name = LIB_NAME, + lib_source = ":all_srcs", + out_static_libs = [ + "libssl.lib", + "libcrypto.lib", + ], + targets = MAKE_TARGETS, + toolchain = "@rules_foreign_cc//toolchains:preinstalled_nmake_toolchain", + deps = [ + "@com_github_madler_zlib//:zlib", + ], +) + +# https://wiki.openssl.org/index.php/Compilation_and_Installation +configure_make( + name = "openssl_default", + configure_command = "config", + configure_in_place = True, + configure_options = CONFIGURE_OPTIONS, + env = select({ + "@platforms//os:macos": { + "AR": "", + "PERL": "$$EXT_BUILD_ROOT$$/$(PERL)", + }, + "//conditions:default": { + "PERL": "$$EXT_BUILD_ROOT$$/$(PERL)", + }, + }), + lib_name = LIB_NAME, + lib_source = ":all_srcs", + # Note that for Linux builds, libssl must come before libcrypto on the linker command-line. + # As such, libssl must be listed before libcrypto + out_static_libs = [ + "libssl.a", + "libcrypto.a", + ], + targets = MAKE_TARGETS, + toolchains = ["@rules_perl//:current_toolchain"], + deps = [ + "@com_github_madler_zlib//:zlib", + ], +) + +filegroup( + name = "gen_dir", + srcs = [":openssl"], + output_group = "gen_dir", +) diff --git a/bazel/third_party/protobuf/BUILD.bazel b/bazel/third_party/protobuf/BUILD.bazel new file mode 100644 index 0000000..fefa6c3 --- /dev/null +++ b/bazel/third_party/protobuf/BUILD.bazel @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# Thie empty BUILD.bazel file is required to make Bazel treat +# this directory as a package. diff --git a/bazel/third_party/protobuf/protobuf.BUILD b/bazel/third_party/protobuf/protobuf.BUILD new file mode 100644 index 0000000..0d5188e --- /dev/null +++ b/bazel/third_party/protobuf/protobuf.BUILD @@ -0,0 +1,498 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright 2008 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Copied from https://github.com/protocolbuffers/protobuf/blob/v3.19.1/BUILD +# +# Modifications: +# 1. Remove all non-cxx rules. +# 2. Remove android support. +# 3. zlib use @com_github_madler_zlib//:zlib + +# Bazel (https://bazel.build/) BUILD file for Protobuf. + +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", native_cc_proto_library = "cc_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_lang_toolchain", "proto_library") +load(":compiler_config_setting.bzl", "create_compiler_config_setting") +load( + ":protobuf.bzl", + "adapt_proto_library", +) + +licenses(["notice"]) + +exports_files(["LICENSE"]) + +################################################################################ +# build configuration +################################################################################ + +################################################################################ +# ZLIB configuration +################################################################################ + +ZLIB_DEPS = ["@com_github_madler_zlib//:zlib"] + +################################################################################ +# Protobuf Runtime Library +################################################################################ + +MSVC_COPTS = [ + "/wd4018", # -Wno-sign-compare + "/wd4065", # switch statement contains 'default' but no 'case' labels + "/wd4146", # unary minus operator applied to unsigned type, result still unsigned + "/wd4244", # 'conversion' conversion from 'type1' to 'type2', possible loss of data + "/wd4251", # 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2' + "/wd4267", # 'var' : conversion from 'size_t' to 'type', possible loss of data + "/wd4305", # 'identifier' : truncation from 'type1' to 'type2' + "/wd4307", # 'operator' : integral constant overflow + "/wd4309", # 'conversion' : truncation of constant value + "/wd4334", # 'operator' : result of 32-bit shift implicitly converted to 64 bits (was 64-bit shift intended?) + "/wd4355", # 'this' : used in base member initializer list + "/wd4506", # no definition for inline function 'function' + "/wd4800", # 'type' : forcing value to bool 'true' or 'false' (performance warning) + "/wd4996", # The compiler encountered a deprecated declaration. +] + +COPTS = select({ + ":msvc": MSVC_COPTS, + "//conditions:default": [ + "-DHAVE_ZLIB", + "-Wmissing-field-initializers", + "-Woverloaded-virtual", + "-Wno-sign-compare", + ], +}) + +create_compiler_config_setting( + name = "msvc", + value = "msvc-cl", + visibility = [ + # Public, but Protobuf only visibility. + "//:__subpackages__", + ], +) + +# Android and MSVC builds do not need to link in a separate pthread library. +LINK_OPTS = select({ + ":msvc": [ + # Suppress linker warnings about files with no symbols defined. + "-ignore:4221", + ], + "//conditions:default": [ + "-lpthread", + "-lm", + ], +}) + +cc_library( + name = "protobuf_lite", + srcs = [ + # AUTOGEN(protobuf_lite_srcs) + "src/google/protobuf/any_lite.cc", + "src/google/protobuf/arena.cc", + "src/google/protobuf/arenastring.cc", + "src/google/protobuf/extension_set.cc", + "src/google/protobuf/generated_enum_util.cc", + "src/google/protobuf/generated_message_table_driven_lite.cc", + "src/google/protobuf/generated_message_tctable_lite.cc", + "src/google/protobuf/generated_message_util.cc", + "src/google/protobuf/implicit_weak_message.cc", + "src/google/protobuf/inlined_string_field.cc", + "src/google/protobuf/io/coded_stream.cc", + "src/google/protobuf/io/io_win32.cc", + "src/google/protobuf/io/strtod.cc", + "src/google/protobuf/io/zero_copy_stream.cc", + "src/google/protobuf/io/zero_copy_stream_impl.cc", + "src/google/protobuf/io/zero_copy_stream_impl_lite.cc", + "src/google/protobuf/map.cc", + "src/google/protobuf/message_lite.cc", + "src/google/protobuf/parse_context.cc", + "src/google/protobuf/repeated_field.cc", + "src/google/protobuf/repeated_ptr_field.cc", + "src/google/protobuf/stubs/bytestream.cc", + "src/google/protobuf/stubs/common.cc", + "src/google/protobuf/stubs/int128.cc", + "src/google/protobuf/stubs/status.cc", + "src/google/protobuf/stubs/statusor.cc", + "src/google/protobuf/stubs/stringpiece.cc", + "src/google/protobuf/stubs/stringprintf.cc", + "src/google/protobuf/stubs/structurally_valid.cc", + "src/google/protobuf/stubs/strutil.cc", + "src/google/protobuf/stubs/time.cc", + "src/google/protobuf/wire_format_lite.cc", + ], + hdrs = glob([ + "src/google/protobuf/**/*.h", + "src/google/protobuf/**/*.inc", + ]), + copts = COPTS, + includes = ["src/"], + linkopts = LINK_OPTS, + visibility = ["//visibility:public"], +) + +PROTOBUF_DEPS = select({ + ":msvc": [], + "//conditions:default": ZLIB_DEPS, +}) + +cc_library( + name = "protobuf", + srcs = [ + # AUTOGEN(protobuf_srcs) + "src/google/protobuf/any.cc", + "src/google/protobuf/any.pb.cc", + "src/google/protobuf/api.pb.cc", + "src/google/protobuf/compiler/importer.cc", + "src/google/protobuf/compiler/parser.cc", + "src/google/protobuf/descriptor.cc", + "src/google/protobuf/descriptor.pb.cc", + "src/google/protobuf/descriptor_database.cc", + "src/google/protobuf/duration.pb.cc", + "src/google/protobuf/dynamic_message.cc", + "src/google/protobuf/empty.pb.cc", + "src/google/protobuf/extension_set_heavy.cc", + "src/google/protobuf/field_mask.pb.cc", + "src/google/protobuf/generated_message_bases.cc", + "src/google/protobuf/generated_message_reflection.cc", + "src/google/protobuf/generated_message_table_driven.cc", + "src/google/protobuf/generated_message_tctable_full.cc", + "src/google/protobuf/io/gzip_stream.cc", + "src/google/protobuf/io/printer.cc", + "src/google/protobuf/io/tokenizer.cc", + "src/google/protobuf/map_field.cc", + "src/google/protobuf/message.cc", + "src/google/protobuf/reflection_ops.cc", + "src/google/protobuf/service.cc", + "src/google/protobuf/source_context.pb.cc", + "src/google/protobuf/struct.pb.cc", + "src/google/protobuf/stubs/substitute.cc", + "src/google/protobuf/text_format.cc", + "src/google/protobuf/timestamp.pb.cc", + "src/google/protobuf/type.pb.cc", + "src/google/protobuf/unknown_field_set.cc", + "src/google/protobuf/util/delimited_message_util.cc", + "src/google/protobuf/util/field_comparator.cc", + "src/google/protobuf/util/field_mask_util.cc", + "src/google/protobuf/util/internal/datapiece.cc", + "src/google/protobuf/util/internal/default_value_objectwriter.cc", + "src/google/protobuf/util/internal/error_listener.cc", + "src/google/protobuf/util/internal/field_mask_utility.cc", + "src/google/protobuf/util/internal/json_escaping.cc", + "src/google/protobuf/util/internal/json_objectwriter.cc", + "src/google/protobuf/util/internal/json_stream_parser.cc", + "src/google/protobuf/util/internal/object_writer.cc", + "src/google/protobuf/util/internal/proto_writer.cc", + "src/google/protobuf/util/internal/protostream_objectsource.cc", + "src/google/protobuf/util/internal/protostream_objectwriter.cc", + "src/google/protobuf/util/internal/type_info.cc", + "src/google/protobuf/util/internal/utility.cc", + "src/google/protobuf/util/json_util.cc", + "src/google/protobuf/util/message_differencer.cc", + "src/google/protobuf/util/time_util.cc", + "src/google/protobuf/util/type_resolver_util.cc", + "src/google/protobuf/wire_format.cc", + "src/google/protobuf/wrappers.pb.cc", + ], + hdrs = glob([ + "src/**/*.h", + "src/**/*.inc", + ]), + copts = COPTS, + includes = ["src/"], + linkopts = LINK_OPTS, + visibility = ["//visibility:public"], + deps = [":protobuf_lite"] + PROTOBUF_DEPS, +) + +# This provides just the header files for use in projects that need to build +# shared libraries for dynamic loading. This target is available until Bazel +# adds native support for such use cases. +# TODO(keveman): Remove this target once the support gets added to Bazel. +cc_library( + name = "protobuf_headers", + hdrs = glob([ + "src/**/*.h", + "src/**/*.inc", + ]), + includes = ["src/"], + visibility = ["//visibility:public"], +) + +# Map of all well known protos. +# name => (include path, imports) +WELL_KNOWN_PROTO_MAP = { + "any": ("src/google/protobuf/any.proto", []), + "api": ( + "src/google/protobuf/api.proto", + [ + "source_context", + "type", + ], + ), + "compiler_plugin": ( + "src/google/protobuf/compiler/plugin.proto", + ["descriptor"], + ), + "descriptor": ("src/google/protobuf/descriptor.proto", []), + "duration": ("src/google/protobuf/duration.proto", []), + "empty": ("src/google/protobuf/empty.proto", []), + "field_mask": ("src/google/protobuf/field_mask.proto", []), + "source_context": ("src/google/protobuf/source_context.proto", []), + "struct": ("src/google/protobuf/struct.proto", []), + "timestamp": ("src/google/protobuf/timestamp.proto", []), + "type": ( + "src/google/protobuf/type.proto", + [ + "any", + "source_context", + ], + ), + "wrappers": ("src/google/protobuf/wrappers.proto", []), +} + +WELL_KNOWN_PROTOS = [value[0] for value in WELL_KNOWN_PROTO_MAP.values()] + +LITE_WELL_KNOWN_PROTO_MAP = { + "any": ("src/google/protobuf/any.proto", []), + "api": ( + "src/google/protobuf/api.proto", + [ + "source_context", + "type", + ], + ), + "duration": ("src/google/protobuf/duration.proto", []), + "empty": ("src/google/protobuf/empty.proto", []), + "field_mask": ("src/google/protobuf/field_mask.proto", []), + "source_context": ("src/google/protobuf/source_context.proto", []), + "struct": ("src/google/protobuf/struct.proto", []), + "timestamp": ("src/google/protobuf/timestamp.proto", []), + "type": ( + "src/google/protobuf/type.proto", + [ + "any", + "source_context", + ], + ), + "wrappers": ("src/google/protobuf/wrappers.proto", []), +} + +LITE_WELL_KNOWN_PROTOS = [value[0] for value in LITE_WELL_KNOWN_PROTO_MAP.values()] + +filegroup( + name = "well_known_protos", + srcs = WELL_KNOWN_PROTOS, + visibility = ["//visibility:public"], +) + +filegroup( + name = "lite_well_known_protos", + srcs = LITE_WELL_KNOWN_PROTOS, + visibility = ["//visibility:public"], +) + +adapt_proto_library( + name = "cc_wkt_protos_genproto", + visibility = ["//visibility:public"], + deps = [proto + "_proto" for proto in WELL_KNOWN_PROTO_MAP.keys()], +) + +cc_library( + name = "cc_wkt_protos", + deprecation = "Only for backward compatibility. Do not use.", + visibility = ["//visibility:public"], +) + +################################################################################ +# Well Known Types Proto Library Rules +# +# These proto_library rules can be used with one of the language specific proto +# library rules i.e. java_proto_library: +# +# java_proto_library( +# name = "any_java_proto", +# deps = ["@com_google_protobuf//:any_proto], +# ) +################################################################################ + +[proto_library( + name = proto[0] + "_proto", + srcs = [proto[1][0]], + strip_import_prefix = "src", + visibility = ["//visibility:public"], + deps = [dep + "_proto" for dep in proto[1][1]], +) for proto in WELL_KNOWN_PROTO_MAP.items()] + +[native_cc_proto_library( + name = proto + "_cc_proto", + visibility = ["//visibility:private"], + deps = [proto + "_proto"], +) for proto in WELL_KNOWN_PROTO_MAP.keys()] + +################################################################################ +# Protocol Buffers Compiler +################################################################################ + +cc_library( + name = "protoc_lib", + srcs = [ + # AUTOGEN(protoc_lib_srcs) + "src/google/protobuf/compiler/code_generator.cc", + "src/google/protobuf/compiler/command_line_interface.cc", + "src/google/protobuf/compiler/cpp/cpp_enum.cc", + "src/google/protobuf/compiler/cpp/cpp_enum_field.cc", + "src/google/protobuf/compiler/cpp/cpp_extension.cc", + "src/google/protobuf/compiler/cpp/cpp_field.cc", + "src/google/protobuf/compiler/cpp/cpp_file.cc", + "src/google/protobuf/compiler/cpp/cpp_generator.cc", + "src/google/protobuf/compiler/cpp/cpp_helpers.cc", + "src/google/protobuf/compiler/cpp/cpp_map_field.cc", + "src/google/protobuf/compiler/cpp/cpp_message.cc", + "src/google/protobuf/compiler/cpp/cpp_message_field.cc", + "src/google/protobuf/compiler/cpp/cpp_padding_optimizer.cc", + "src/google/protobuf/compiler/cpp/cpp_parse_function_generator.cc", + "src/google/protobuf/compiler/cpp/cpp_primitive_field.cc", + "src/google/protobuf/compiler/cpp/cpp_service.cc", + "src/google/protobuf/compiler/cpp/cpp_string_field.cc", + "src/google/protobuf/compiler/csharp/csharp_doc_comment.cc", + "src/google/protobuf/compiler/csharp/csharp_enum.cc", + "src/google/protobuf/compiler/csharp/csharp_enum_field.cc", + "src/google/protobuf/compiler/csharp/csharp_field_base.cc", + "src/google/protobuf/compiler/csharp/csharp_generator.cc", + "src/google/protobuf/compiler/csharp/csharp_helpers.cc", + "src/google/protobuf/compiler/csharp/csharp_map_field.cc", + "src/google/protobuf/compiler/csharp/csharp_message.cc", + "src/google/protobuf/compiler/csharp/csharp_message_field.cc", + "src/google/protobuf/compiler/csharp/csharp_primitive_field.cc", + "src/google/protobuf/compiler/csharp/csharp_reflection_class.cc", + "src/google/protobuf/compiler/csharp/csharp_repeated_enum_field.cc", + "src/google/protobuf/compiler/csharp/csharp_repeated_message_field.cc", + "src/google/protobuf/compiler/csharp/csharp_repeated_primitive_field.cc", + "src/google/protobuf/compiler/csharp/csharp_source_generator_base.cc", + "src/google/protobuf/compiler/csharp/csharp_wrapper_field.cc", + "src/google/protobuf/compiler/java/java_context.cc", + "src/google/protobuf/compiler/java/java_doc_comment.cc", + "src/google/protobuf/compiler/java/java_enum.cc", + "src/google/protobuf/compiler/java/java_enum_field.cc", + "src/google/protobuf/compiler/java/java_enum_field_lite.cc", + "src/google/protobuf/compiler/java/java_enum_lite.cc", + "src/google/protobuf/compiler/java/java_extension.cc", + "src/google/protobuf/compiler/java/java_extension_lite.cc", + "src/google/protobuf/compiler/java/java_field.cc", + "src/google/protobuf/compiler/java/java_file.cc", + "src/google/protobuf/compiler/java/java_generator.cc", + "src/google/protobuf/compiler/java/java_generator_factory.cc", + "src/google/protobuf/compiler/java/java_helpers.cc", + "src/google/protobuf/compiler/java/java_kotlin_generator.cc", + "src/google/protobuf/compiler/java/java_map_field.cc", + "src/google/protobuf/compiler/java/java_map_field_lite.cc", + "src/google/protobuf/compiler/java/java_message.cc", + "src/google/protobuf/compiler/java/java_message_builder.cc", + "src/google/protobuf/compiler/java/java_message_builder_lite.cc", + "src/google/protobuf/compiler/java/java_message_field.cc", + "src/google/protobuf/compiler/java/java_message_field_lite.cc", + "src/google/protobuf/compiler/java/java_message_lite.cc", + "src/google/protobuf/compiler/java/java_name_resolver.cc", + "src/google/protobuf/compiler/java/java_primitive_field.cc", + "src/google/protobuf/compiler/java/java_primitive_field_lite.cc", + "src/google/protobuf/compiler/java/java_service.cc", + "src/google/protobuf/compiler/java/java_shared_code_generator.cc", + "src/google/protobuf/compiler/java/java_string_field.cc", + "src/google/protobuf/compiler/java/java_string_field_lite.cc", + "src/google/protobuf/compiler/js/js_generator.cc", + "src/google/protobuf/compiler/js/well_known_types_embed.cc", + "src/google/protobuf/compiler/objectivec/objectivec_enum.cc", + "src/google/protobuf/compiler/objectivec/objectivec_enum_field.cc", + "src/google/protobuf/compiler/objectivec/objectivec_extension.cc", + "src/google/protobuf/compiler/objectivec/objectivec_field.cc", + "src/google/protobuf/compiler/objectivec/objectivec_file.cc", + "src/google/protobuf/compiler/objectivec/objectivec_generator.cc", + "src/google/protobuf/compiler/objectivec/objectivec_helpers.cc", + "src/google/protobuf/compiler/objectivec/objectivec_map_field.cc", + "src/google/protobuf/compiler/objectivec/objectivec_message.cc", + "src/google/protobuf/compiler/objectivec/objectivec_message_field.cc", + "src/google/protobuf/compiler/objectivec/objectivec_oneof.cc", + "src/google/protobuf/compiler/objectivec/objectivec_primitive_field.cc", + "src/google/protobuf/compiler/php/php_generator.cc", + "src/google/protobuf/compiler/plugin.cc", + "src/google/protobuf/compiler/plugin.pb.cc", + "src/google/protobuf/compiler/python/python_generator.cc", + "src/google/protobuf/compiler/ruby/ruby_generator.cc", + "src/google/protobuf/compiler/subprocess.cc", + "src/google/protobuf/compiler/zip_writer.cc", + ], + copts = COPTS, + includes = ["src/"], + linkopts = LINK_OPTS, + visibility = ["//visibility:public"], + deps = [":protobuf"], +) + +cc_binary( + name = "protoc", + srcs = ["src/google/protobuf/compiler/main.cc"], + linkopts = LINK_OPTS, + visibility = ["//visibility:public"], + deps = [":protoc_lib"], +) + +proto_lang_toolchain( + name = "cc_toolchain", + blacklisted_protos = [proto + "_proto" for proto in WELL_KNOWN_PROTO_MAP.keys()], + command_line = "--cpp_out=$(OUT)", + runtime = ":protobuf", + visibility = ["//visibility:public"], +) + +alias( + name = "objectivec", + actual = "//objectivec", + visibility = ["//visibility:public"], +) + +alias( + name = "protobuf_objc", + actual = "//objectivec", + visibility = ["//visibility:public"], +) diff --git a/bazel/third_party/snappy/BUILD.bazel b/bazel/third_party/snappy/BUILD.bazel new file mode 100644 index 0000000..fefa6c3 --- /dev/null +++ b/bazel/third_party/snappy/BUILD.bazel @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# Thie empty BUILD.bazel file is required to make Bazel treat +# this directory as a package. diff --git a/bazel/third_party/snappy/snappy.BUILD b/bazel/third_party/snappy/snappy.BUILD new file mode 100644 index 0000000..9cfb1ad --- /dev/null +++ b/bazel/third_party/snappy/snappy.BUILD @@ -0,0 +1,122 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright 2015 The TensorFlow Authors. All Rights Reserved. +# 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. + +# Copied from https://github.com/tensorflow/tensorflow/blob/bdd8bf316e4ab7d699127d192d30eb614a158462/third_party/snappy.BUILD + +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "snappy", + srcs = [ + "config.h", + "snappy.cc", + "snappy.h", + "snappy-internal.h", + "snappy-sinksource.cc", + "snappy-stubs-internal.cc", + "snappy-stubs-internal.h", + "snappy-stubs-public.h", + ], + hdrs = [ + "snappy.h", + "snappy-sinksource.h", + ], + copts = [ + "-DHAVE_CONFIG_H", + "-fno-exceptions", + "-Wno-sign-compare", + "-Wno-shift-negative-value", + ], + includes = ["."], + visibility = ["//visibility:public"], +) + +genrule( + name = "config_h", + outs = ["config.h"], + cmd = "\n".join([ + "cat <<'EOF' >$@", + "#define HAVE_STDDEF_H 1", + "#define HAVE_STDINT_H 1", + "", + "#ifdef __has_builtin", + "# if !defined(HAVE_BUILTIN_EXPECT) && __has_builtin(__builtin_expect)", + "# define HAVE_BUILTIN_EXPECT 1", + "# endif", + "# if !defined(HAVE_BUILTIN_CTZ) && __has_builtin(__builtin_ctzll)", + "# define HAVE_BUILTIN_CTZ 1", + "# endif", + "#elif defined(__GNUC__) && (__GNUC__ > 3 || __GNUC__ == 3 && __GNUC_MINOR__ >= 4)", + "# ifndef HAVE_BUILTIN_EXPECT", + "# define HAVE_BUILTIN_EXPECT 1", + "# endif", + "# ifndef HAVE_BUILTIN_CTZ", + "# define HAVE_BUILTIN_CTZ 1", + "# endif", + "#endif", + "", + "#ifdef __has_include", + "# if !defined(HAVE_BYTESWAP_H) && __has_include()", + "# define HAVE_BYTESWAP_H 1", + "# endif", + "# if !defined(HAVE_UNISTD_H) && __has_include()", + "# define HAVE_UNISTD_H 1", + "# endif", + "# if !defined(HAVE_SYS_ENDIAN_H) && __has_include()", + "# define HAVE_SYS_ENDIAN_H 1", + "# endif", + "# if !defined(HAVE_SYS_MMAN_H) && __has_include()", + "# define HAVE_SYS_MMAN_H 1", + "# endif", + "# if !defined(HAVE_SYS_UIO_H) && __has_include()", + "# define HAVE_SYS_UIO_H 1", + "# endif", + "#endif", + "", + "#ifndef SNAPPY_IS_BIG_ENDIAN", + "# ifdef __s390x__", + "# define SNAPPY_IS_BIG_ENDIAN 1", + "# elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__", + "# define SNAPPY_IS_BIG_ENDIAN 1", + "# endif", + "#endif", + "EOF", + ]), +) + +genrule( + name = "snappy_stubs_public_h", + srcs = ["snappy-stubs-public.h.in"], + outs = ["snappy-stubs-public.h"], + cmd = ("sed " + + "-e 's/$${\\(.*\\)_01}/\\1/g' " + + "-e 's/$${SNAPPY_MAJOR}/1/g' " + + "-e 's/$${SNAPPY_MINOR}/1/g' " + + "-e 's/$${SNAPPY_PATCHLEVEL}/7/g' " + + "$< >$@"), +) diff --git a/bazel/third_party/thrift/BUILD.bazel b/bazel/third_party/thrift/BUILD.bazel new file mode 100644 index 0000000..fefa6c3 --- /dev/null +++ b/bazel/third_party/thrift/BUILD.bazel @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# Thie empty BUILD.bazel file is required to make Bazel treat +# this directory as a package. diff --git a/bazel/third_party/thrift/thrift.BUILD b/bazel/third_party/thrift/thrift.BUILD new file mode 100644 index 0000000..079606a --- /dev/null +++ b/bazel/third_party/thrift/thrift.BUILD @@ -0,0 +1,75 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +load("@rules_foreign_cc//foreign_cc:defs.bzl", "cmake") + +filegroup( + name = "all_srcs", + srcs = glob(["**"]), +) + +cmake( + name = "thrift", + cache_entries = { + "BUILD_COMPILER": "OFF", + "BUILD_TESTING": "OFF", + "BUILD_TUTORIALS": "OFF", + "WITH_AS3": "OFF", + "WITH_CPP": "ON", + "WITH_C_GLIB": "OFF", + "WITH_JAVA": "OFF", + "WITH_JAVASCRIPT": "OFF", + "WITH_NODEJS": "OFF", + "WITH_PYTHON": "OFF", + "BUILD_SHARED_LIBS": "OFF", + "Boost_USE_STATIC_LIBS": "ON", + "BOOST_ROOT": "$$EXT_BUILD_DEPS$$", + "LIBEVENT_INCLUDE_DIRS": "$$EXT_BUILD_DEPS$$/event/include", + "LIBEVENT_LIBRARIES": "$$EXT_BUILD_DEPS$$/event/lib/libevent.a", + "OPENSSL_ROOT_DIR": "$$EXT_BUILD_DEPS$$/openssl", + "ZLIB_ROOT": "$$EXT_BUILD_DEPS$$/zlib", + }, + generate_args = ["-GNinja"], + lib_source = ":all_srcs", + linkopts = [ + "-pthread", + ], + out_static_libs = select({ + "@platforms//os:windows": [ + "thrift.lib", + "thriftnb.lib", + "thriftz.lib", + ], + "//conditions:default": [ + "libthrift.a", + "libthriftnb.a", + "libthriftz.a", + ], + }), + visibility = ["//visibility:public"], + deps = [ + "@boost//:algorithm", + "@boost//:locale", + "@boost//:noncopyable", + "@boost//:numeric_conversion", + "@boost//:scoped_array", + "@boost//:smart_ptr", + "@boost//:tokenizer", + "@com_github_libevent_libevent//:event", + "@com_github_madler_zlib//:zlib", + "@openssl//:crypto", + "@openssl//:ssl", + ], +) diff --git a/bazel/third_party/zlib/BUILD.bazel b/bazel/third_party/zlib/BUILD.bazel new file mode 100644 index 0000000..fefa6c3 --- /dev/null +++ b/bazel/third_party/zlib/BUILD.bazel @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# Thie empty BUILD.bazel file is required to make Bazel treat +# this directory as a package. diff --git a/bazel/third_party/zlib/zlib.BUILD b/bazel/third_party/zlib/zlib.BUILD new file mode 100644 index 0000000..d8139b6 --- /dev/null +++ b/bazel/third_party/zlib/zlib.BUILD @@ -0,0 +1,111 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright 2008 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Copied from https://github.com/protocolbuffers/protobuf/blob/v3.9.1/third_party/zlib.BUILD + +load("@rules_cc//cc:defs.bzl", "cc_library") + +_ZLIB_HEADERS = [ + "crc32.h", + "deflate.h", + "gzguts.h", + "inffast.h", + "inffixed.h", + "inflate.h", + "inftrees.h", + "trees.h", + "zconf.h", + "zlib.h", + "zutil.h", +] + +_ZLIB_PREFIXED_HEADERS = ["zlib/include/" + hdr for hdr in _ZLIB_HEADERS] + +# In order to limit the damage from the `includes` propagation +# via `:zlib`, copy the public headers to a subdirectory and +# expose those. +genrule( + name = "copy_public_headers", + srcs = _ZLIB_HEADERS, + outs = _ZLIB_PREFIXED_HEADERS, + cmd = "cp $(SRCS) $(@D)/zlib/include/", + visibility = ["//visibility:private"], +) + +cc_library( + name = "zlib", + srcs = [ + "adler32.c", + "compress.c", + "crc32.c", + "deflate.c", + "gzclose.c", + "gzlib.c", + "gzread.c", + "gzwrite.c", + "infback.c", + "inffast.c", + "inflate.c", + "inftrees.c", + "trees.c", + "uncompr.c", + "zutil.c", + ], + hdrs = _ZLIB_PREFIXED_HEADERS, + copts = select({ + ":windows": [], + "//conditions:default": [ + "-Wno-unused-variable", + "-Wno-implicit-function-declaration", + ], + }), + includes = ["zlib/include/"], + visibility = ["//visibility:public"], +) + +config_setting( + name = "windows", + constraint_values = [ + "@platforms//os:windows", + ], +) diff --git a/bazel/tools/BUILD.bazel b/bazel/tools/BUILD.bazel new file mode 100644 index 0000000..3b44830 --- /dev/null +++ b/bazel/tools/BUILD.bazel @@ -0,0 +1,23 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +# This BUILD file marks bazel/tools/ as a Bazel package so that loads +# of the form `//bazel/tools:xxx.bzl` are valid. The directory only +# hosts .bzl files (brpc_proto_library.bzl / proto_gen.bzl) and does +# not define any build targets. + +package(default_visibility = ["//visibility:public"]) + +exports_files(glob(["*.bzl"])) diff --git a/bazel/tools/brpc_proto_library.bzl b/bazel/tools/brpc_proto_library.bzl new file mode 100644 index 0000000..22a3c00 --- /dev/null +++ b/bazel/tools/brpc_proto_library.bzl @@ -0,0 +1,138 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# brpc_proto_library: a proto -> cc_library macro that is robust against +# protobuf and Bazel version churn. +# +# Background: +# - Old protobuf (< 3.21) used to expose a `cc_proto_library` macro +# via `@com_google_protobuf//:protobuf.bzl`; newer protobuf removed +# that macro. +# - Newer protobuf expects callers to use Bazel's native +# `proto_library` / `cc_proto_library` (or the `cc_proto_library` +# re-exported from `@com_google_protobuf//bazel:cc_proto_library.bzl`), +# but older protobuf does not surface a complete `ProtoInfo`, so +# the native rules cannot build it. +# - Bazel 8 additionally removed `cc_proto_library` from +# `@rules_cc//cc:defs.bzl` (the load now resolves to a stub rule +# that fails analysis), forcing every caller to either load it +# from `@com_google_protobuf` or replace it. +# - Bazel's `load()` is static and cannot be conditional on the +# protobuf version, so a single brpc_proto_library.bzl cannot +# directly satisfy both old and new protobuf by switching imports. +# +# Solution (inspired by https://github.com/trpc-group/trpc-cpp/blob/a69d2950f4a9c16b3cb40a2750c69d8940c96820/trpc/trpc.bzl): +# We implement the proto compilation rule from scratch (see +# proto_gen.bzl in this directory). Dependency information is +# propagated through Bazel's native `ProtoInfo` provider, and the +# default well-known-proto source is +# `@com_google_protobuf//:descriptor_proto` -- a `proto_library` +# whose name has been stable across protobuf 3.x / 27.x / 34.x. +load("//bazel/tools:proto_gen.bzl", "brpc_proto_gen") + +# `:descriptor_proto` is the protobuf repo's `proto_library` target; +# its label has been stable since protobuf 3.x. We consume it through +# Bazel's native `ProtoInfo` provider, which is uniform across PB +# versions. If a caller's .proto needs additional well-known protos +# (any.proto, timestamp.proto, ...), they can pass extra entries via +# the `proto_deps` argument. +_DEFAULT_PROTO_DEPS = ["@com_google_protobuf//:descriptor_proto"] + +def brpc_proto_library( + name, + srcs, + deps = [], + include = None, + proto_deps = None, + visibility = None, + testonly = 0): + """Generate a cc_library from a set of .proto files. + + Args: + name: target name. + srcs: list of .proto files, paths relative to the current package. + deps: list of other `brpc_proto_library` targets this target + depends on. The macro internally translates these labels + to the underlying `_genproto` rule so that .proto + include paths and sources propagate. + include: protoc `-I` root AND the resulting cc_library `includes` + root, relative to the current package. + When omitted, "" or None, the include root is the + current package itself (suitable for .proto files + sitting directly under the package root, as in `test/` + and `example/...`). The root `BUILD.bazel` of brpc must + pass `"src"` so that code can reference the protos as + `import "brpc/foo.proto"`. + proto_deps: list of native `proto_library` dependencies + (well-known protos or external .proto libraries). + Defaults to + `["@com_google_protobuf//:descriptor_proto"]`. + Pass `[]` explicitly to disable the default; pass + None (the default) to use it. + visibility: same semantics as cc_library. + testonly: same semantics as cc_library. + """ + + real_include = include if include != None else "" + real_proto_deps = proto_deps if proto_deps != None else _DEFAULT_PROTO_DEPS + + gen_name = name + "_genproto" + brpc_proto_gen( + name = gen_name, + srcs = srcs, + # Rewrite each `brpc_proto_library` dep to its underlying + # `_genproto` rule, which exports BrpcProtoInfo. + deps = [d + "_genproto" for d in deps], + include = real_include, + proto_deps = real_proto_deps, + visibility = visibility, + testonly = testonly, + ) + + # Split the generated files into .pb.h / .pb.cc via OutputGroupInfo + # and feed them to cc_library's hdrs / srcs separately. Bazel + # rejects declaring the same File in both hdrs and srcs, so a + # plain DefaultInfo wouldn't work here. + native.filegroup( + name = gen_name + "_hdrs", + srcs = [":" + gen_name], + output_group = "hdrs", + visibility = visibility, + testonly = testonly, + ) + native.filegroup( + name = gen_name + "_srcs", + srcs = [":" + gen_name], + output_group = "srcs", + visibility = visibility, + testonly = testonly, + ) + + native.cc_library( + name = name, + srcs = [":" + gen_name + "_srcs"], + hdrs = [":" + gen_name + "_hdrs"], + # cc_library `includes` is required, otherwise the .pb.cc + # files inside this cc_library cannot find the .pb.h headers + # they just generated (the headers live under + # bazel-bin///...). When include="" we pass + # "." to mean "the current package itself"; Bazel then exposes + # both `-I ` and `-I bazel-bin/` + # automatically to dependents. + includes = [real_include if real_include else "."], + deps = deps + ["@com_google_protobuf//:protobuf"], + visibility = visibility, + testonly = testonly, + ) diff --git a/bazel/tools/proto_gen.bzl b/bazel/tools/proto_gen.bzl new file mode 100644 index 0000000..554d24d --- /dev/null +++ b/bazel/tools/proto_gen.bzl @@ -0,0 +1,240 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# brpc's self-implemented proto -> .pb.{h,cc} compilation rule. +# +# Cross-protobuf-version compatibility strategy (inspired by trpc.bzl): +# 1) Do NOT load `@com_google_protobuf//:protobuf.bzl` and do NOT +# call `native.cc_proto_library` -- this side-steps the double +# incompatibility of newer protobuf removing the macro and older +# protobuf missing a complete `ProtoInfo` provider. +# 2) Do NOT hardcode any protobuf-repo-internal filegroup names +# (such as `well_known_protos` / `descriptor_proto_srcs`) -- the +# names of those filegroups have shifted between protobuf 3.x +# and 27.x+. +# 3) Propagate dependencies via Bazel's native `ProtoInfo` provider: +# callers pass `proto_library` targets through `proto_deps` +# (defaulting to `@com_google_protobuf//:descriptor_proto`, a +# `proto_library` whose label has been stable across PB versions), +# and this rule walks `transitive_sources` / `transitive_proto_path` +# to collect .proto source files and protoc `-I` paths -- fully +# decoupled from the PB version. +# 4) Among `brpc_proto_gen` targets themselves, dependencies are +# propagated via the custom `BrpcProtoInfo` provider declared +# below. +# +# Note on minimum Bazel version: this rule itself does not call any +# Bazel-version-specific API, but it still consumes `ProtoInfo` from +# `proto_library` targets (e.g. `descriptor_proto`) defined inside +# the protobuf repo. Protobuf >= 34 calls +# `ProtoInfo(option_deps = ..., extension_declarations = ...)` in its +# own `proto_library` implementation, and those fields are only +# accepted by Bazel >= 8. In other words, the minimum Bazel version +# brpc requires is whatever the bundled protobuf version requires -- +# we just don't add any extra requirement on top of that. + +BrpcProtoInfo = provider( + doc = "Carries transitive .proto sources and protoc -I flags between brpc_proto_gen targets.", + fields = { + "transitive_srcs": "depset of all transitive .proto Files.", + "transitive_imports": "depset of strings, all -I flags needed by protoc.", + }, +) + +def _resolve_include_dir(ctx): + """Compute this target's protoc include root (workspace-relative). + + Examples: + ctx.label.package = "" + include = "src" -> "src" + ctx.label.package = "test" + include = "" -> "test" + ctx.label.package = "" + include = "" -> "." + """ + pkg = ctx.label.package + inc = ctx.attr.include.rstrip("/") + if pkg and inc: + return pkg + "/" + inc + if pkg: + return pkg + if inc: + return inc + return "." + +def _proto_gen_impl(ctx): + srcs = ctx.files.srcs + include_dir = _resolve_include_dir(ctx) + bin_root = ctx.bin_dir.path + + # `-I` flags for this target itself: the source-tree root plus + # the corresponding bin-dir root. The bin-dir entry is needed + # when a transitive dep generates .proto files into bazel-bin + # (e.g. via a custom code generator). + own_imports = ["-I" + include_dir] + if include_dir == ".": + own_imports.append("-I" + bin_root) + else: + own_imports.append("-I" + bin_root + "/" + include_dir) + + # Collect transitive info from other `brpc_proto_gen` deps. + dep_srcs_list = [d[BrpcProtoInfo].transitive_srcs for d in ctx.attr.deps] + dep_imports_list = [d[BrpcProtoInfo].transitive_imports for d in ctx.attr.deps] + + # Collect native `proto_library` deps (well-known protos or + # external .proto libraries). Key design: .proto sources and + # `-I` paths are obtained via Bazel's native `ProtoInfo`, + # avoiding any reliance on protobuf-repo-internal filegroup + # names (descriptor_proto_srcs / well_known_protos / ...). + # `transitive_proto_path` already accounts for the + # `_virtual_imports` paths created by `strip_import_prefix`, so + # protoc can consume the paths directly. + # + # Important: `strip_import_prefix` causes .proto files to live at + # virtual paths under bazel-out//bin// + # (see trpc.bzl for prior art). For every `proto_dep` repo we + # therefore expose FOUR candidate `-I` paths to be safe: + # 1) -- source root (plain proto_library) + # 2) / -- virtual root after strip_import_prefix + # 3) /src -- fallback for older PB descriptor_proto without strip + # 4) //src -- same as #3, under bin-dir + # protoc only warns on non-existent `-I` paths, so this is harmless. + proto_dep_src_depsets = [] + proto_dep_imports = [] + extra_pb_root_imports = [] + # Belt-and-suspenders: also feed each `proto_dep`'s default outputs + # (i.e. the .proto files themselves) into `inputs`, in case the + # ProtoInfo's transitive_sources does not cover everything we need. + proto_dep_src_depsets.append(depset(direct = ctx.files.proto_deps)) + for pd in ctx.attr.proto_deps: + pi = pd[ProtoInfo] + proto_dep_src_depsets.append(pi.transitive_sources) + for path in pi.transitive_proto_path.to_list(): + proto_dep_imports.append("-I" + path) + wsroot = pd.label.workspace_root + if wsroot: + extra_pb_root_imports.append("-I" + wsroot) + extra_pb_root_imports.append("-I" + bin_root + "/" + wsroot) + extra_pb_root_imports.append("-I" + wsroot + "/src") + extra_pb_root_imports.append("-I" + bin_root + "/" + wsroot + "/src") + # Deduplicate the workspace-level `-I` entries so the same repo + # is not listed multiple times when several proto_deps share it. + proto_dep_imports.extend(depset(extra_pb_root_imports).to_list()) + + all_imports = depset( + direct = own_imports + proto_dep_imports, + transitive = dep_imports_list, + ) + + # Output files: every .proto produces a .pb.h and a .pb.cc. + # NB: the `path` argument of `ctx.actions.declare_file(path)` is + # *relative to the current package*, while `src.short_path` is + # *relative to the workspace root*. The two differ by the package + # prefix; we must strip that prefix before calling declare_file, + # otherwise the outputs would land at + # bazel-bin///... (one level too deep). + pkg = ctx.label.package + pkg_prefix = pkg + "/" if pkg else "" + outs = [] + for src in srcs: + if not src.short_path.endswith(".proto"): + fail("brpc_proto_gen srcs must be .proto, got: %s" % src.short_path) + + rel = src.short_path + if pkg_prefix and rel.startswith(pkg_prefix): + rel = rel[len(pkg_prefix):] + base = rel[:-len(".proto")] + outs.append(ctx.actions.declare_file(base + ".pb.h")) + outs.append(ctx.actions.declare_file(base + ".pb.cc")) + + # protoc's --cpp_out points at the include root under bin_root. + # After protoc organizes outputs by their import-relative path, + # the .pb.{h,cc} files land exactly where declare_file declared + # them above. + if include_dir == ".": + cpp_out_dir = bin_root + else: + cpp_out_dir = bin_root + "/" + include_dir + + args = ctx.actions.args() + args.add_all(all_imports.to_list()) + args.add("--cpp_out=" + cpp_out_dir) + args.add_all([s.path for s in srcs]) + + ctx.actions.run( + executable = ctx.executable.protoc, + arguments = [args], + inputs = depset( + direct = srcs, + transitive = dep_srcs_list + proto_dep_src_depsets, + ), + outputs = outs, + mnemonic = "BrpcProtoCompile", + progress_message = "BrpcProtoCompile %s" % ctx.label, + ) + + hdr_files = [f for f in outs if f.path.endswith(".pb.h")] + src_files = [f for f in outs if f.path.endswith(".pb.cc")] + + return [ + DefaultInfo(files = depset(outs)), + # Split outputs so the brpc_proto_library macro can route + # them to cc_library.hdrs vs cc_library.srcs separately. + OutputGroupInfo( + hdrs = depset(hdr_files), + srcs = depset(src_files), + ), + BrpcProtoInfo( + transitive_srcs = depset(direct = srcs, transitive = dep_srcs_list), + transitive_imports = all_imports, + ), + ] + +brpc_proto_gen = rule( + implementation = _proto_gen_impl, + attrs = { + "srcs": attr.label_list( + allow_files = [".proto"], + mandatory = True, + ), + # Other `brpc_proto_gen` targets; deps are propagated via the + # custom `BrpcProtoInfo` provider. + "deps": attr.label_list( + providers = [BrpcProtoInfo], + default = [], + ), + # The include root, relative to the current BUILD package + # (e.g. "src" for the brpc root BUILD). + "include": attr.string(default = ""), + "protoc": attr.label( + default = Label("@com_google_protobuf//:protoc"), + executable = True, + # `"host"` is the legacy spelling of `"exec"`. Modern + # Bazel (>= 6) prefers `"exec"`, but still accepts + # `"host"` as an alias through at least Bazel 8.x. We + # keep `"host"` here for the broad range of Bazel + # versions used to build brpc; switch to `"exec"` once + # `"host"` is finally removed (likely a future major + # Bazel release). + cfg = "host", + allow_files = True, + ), + # Native `proto_library` deps (well-known protos or external + # .proto libraries). .proto sources and -I paths are obtained + # automatically through Bazel's native `ProtoInfo`, fully + # decoupled from the protobuf version. + "proto_deps": attr.label_list( + providers = [ProtoInfo], + default = [], + ), + }, +) diff --git a/cmake/CMakeLists.download_gtest.in b/cmake/CMakeLists.download_gtest.in new file mode 100644 index 0000000..03ad5c8 --- /dev/null +++ b/cmake/CMakeLists.download_gtest.in @@ -0,0 +1,30 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +cmake_minimum_required(VERSION 3.16...3.28) + +project(googletest-download NONE) + +include(ExternalProject) +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.12.0 + SOURCE_DIR "${PROJECT_BINARY_DIR}/googletest-src" + BINARY_DIR "${PROJECT_BINARY_DIR}/googletest-build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/cmake/CompileProto.cmake b/cmake/CompileProto.cmake new file mode 100644 index 0000000..d4e2440 --- /dev/null +++ b/cmake/CompileProto.cmake @@ -0,0 +1,34 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +function(compile_proto OUT_HDRS OUT_SRCS DESTDIR HDR_OUTPUT_DIR PROTO_DIR PROTO_FILES) + foreach(P ${PROTO_FILES}) + string(REPLACE .proto .pb.h HDR ${P}) + set(HDR_RELATIVE ${HDR}) + set(HDR ${DESTDIR}/${HDR}) + string(REPLACE .proto .pb.cc SRC ${P}) + set(SRC ${DESTDIR}/${SRC}) + list(APPEND HDRS ${HDR}) + list(APPEND SRCS ${SRC}) + add_custom_command( + OUTPUT ${HDR} ${SRC} + COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} ${PROTOC_FLAGS} -I${PROTO_DIR} --cpp_out=${DESTDIR} ${PROTO_DIR}/${P} + COMMAND ${CMAKE_COMMAND} -E copy ${HDR} ${HDR_OUTPUT_DIR}/${HDR_RELATIVE} + DEPENDS ${PROTO_DIR}/${P} + ) + endforeach() + set(${OUT_HDRS} ${HDRS} PARENT_SCOPE) + set(${OUT_SRCS} ${SRCS} PARENT_SCOPE) +endfunction() diff --git a/cmake/FindBoringSSL.cmake b/cmake/FindBoringSSL.cmake new file mode 100644 index 0000000..b475f0a --- /dev/null +++ b/cmake/FindBoringSSL.cmake @@ -0,0 +1,77 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +# Usage of this module as follows: +# +# find_package(BORINGSSL) +# +# Variables used by this module, they can change the default behaviour and need +# to be set before calling find_package: +# +# BORINGSSL_ROOT_DIR Set this variable to the root installation of +# boringssl if the module has problems finding the +# proper installation path. +# +# Variables defined by this module: +# +# BORINGSSL_FOUND System has boringssl, include and library dirs found +# BORINGSSL_INCLUDE_DIR The boringssl include directories. +# BORINGSSL_LIBRARIES The boringssl libraries. +# BORINGSSL_CRYPTO_LIBRARY The boringssl crypto library. +# BORINGSSL_SSL_LIBRARY The boringssl ssl library. +# BORING_USE_STATIC_LIBS Whether use static library. + +if(BORING_USE_STATIC_LIBS) + set(_boringssl_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) + if(MSVC) + set(CMAKE_FIND_LIBRARY_SUFFIXES .lib ${CMAKE_FIND_LIBRARY_SUFFIXES}) + else() + set(CMAKE_FIND_LIBRARY_SUFFIXES .a ${CMAKE_FIND_LIBRARY_SUFFIXES}) + endif() +endif() + +find_path(BORINGSSL_ROOT_DIR + NAMES include/openssl/ssl.h include/openssl/base.h include/openssl/hkdf.h + HINTS ${BORINGSSL_ROOT_DIR}) + +find_path(BORINGSSL_INCLUDE_DIR + NAMES openssl/ssl.h openssl/base.h openssl/hkdf.h + HINTS ${BORINGSSL_ROOT_DIR}/include) + +find_library(BORINGSSL_SSL_LIBRARY + NAMES ssl + HINTS ${BORINGSSL_ROOT_DIR}/lib) + +find_library(BORINGSSL_CRYPTO_LIBRARY + NAMES crypto + HINTS ${BORINGSSL_ROOT_DIR}/lib) + +set(BORINGSSL_LIBRARIES ${BORINGSSL_SSL_LIBRARY} ${BORINGSSL_CRYPTO_LIBRARY} + CACHE STRING "BoringSSL SSL and crypto libraries" FORCE) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(BoringSSL DEFAULT_MSG + BORINGSSL_LIBRARIES + BORINGSSL_INCLUDE_DIR) + +mark_as_advanced( + BORINGSSL_ROOT_DIR + BORINGSSL_INCLUDE_DIR + BORINGSSL_LIBRARIES + BORINGSSL_CRYPTO_LIBRARY + BORINGSSL_SSL_LIBRARY +) + +set(CMAKE_FIND_LIBRARY_SUFFIXES ${_boringssl_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES}) diff --git a/cmake/FindGFLAGS.cmake b/cmake/FindGFLAGS.cmake new file mode 100644 index 0000000..dfad5fd --- /dev/null +++ b/cmake/FindGFLAGS.cmake @@ -0,0 +1,41 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +set(_gflags_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) + +if (GFLAGS_STATIC) + if (WIN32) + set(CMAKE_FIND_LIBRARY_SUFFIXES .lib ${CMAKE_FIND_LIBRARY_SUFFIXES}) + else (WIN32) + set(CMAKE_FIND_LIBRARY_SUFFIXES .a ${CMAKE_FIND_LIBRARY_SUFFIXES}) + endif (WIN32) +endif (GFLAGS_STATIC) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if(GFLAGS_INCLUDE_PATH AND GFLAGS_LIBRARY) + set(GFLAGS_FOUND TRUE) +endif(GFLAGS_INCLUDE_PATH AND GFLAGS_LIBRARY) +if(GFLAGS_FOUND) + if(NOT GFLAGS_FIND_QUIETLY) + message(STATUS "Found gflags: ${GFLAGS_LIBRARY}") + endif(NOT GFLAGS_FIND_QUIETLY) +else(GFLAGS_FOUND) + if(GFLAGS_FIND_REQUIRED) + message(FATAL_ERROR "Could not find gflags library.") + endif(GFLAGS_FIND_REQUIRED) +endif(GFLAGS_FOUND) + +set(CMAKE_FIND_LIBRARY_SUFFIXES ${_gflags_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES}) diff --git a/cmake/FindGperftools.cmake b/cmake/FindGperftools.cmake new file mode 100644 index 0000000..26f6de4 --- /dev/null +++ b/cmake/FindGperftools.cmake @@ -0,0 +1,66 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +# Tries to find Gperftools. +# +# Usage of this module as follows: +# +# find_package(Gperftools) +# +# Variables used by this module, they can change the default behaviour and need +# to be set before calling find_package: +# +# Gperftools_ROOT_DIR Set this variable to the root installation of +# Gperftools if the module has problems finding +# the proper installation path. +# +# Variables defined by this module: +# +# GPERFTOOLS_FOUND System has Gperftools libs/headers +# GPERFTOOLS_LIBRARIES The Gperftools libraries (tcmalloc & profiler) +# GPERFTOOLS_INCLUDE_DIR The location of Gperftools headers + +find_library(GPERFTOOLS_TCMALLOC + NAMES tcmalloc + HINTS ${Gperftools_ROOT_DIR}/lib) + +find_library(GPERFTOOLS_PROFILER + NAMES profiler + HINTS ${Gperftools_ROOT_DIR}/lib) + +find_library(GPERFTOOLS_TCMALLOC_AND_PROFILER + NAMES tcmalloc_and_profiler + HINTS ${Gperftools_ROOT_DIR}/lib) + +find_path(GPERFTOOLS_INCLUDE_DIR + NAMES gperftools/heap-profiler.h + HINTS ${Gperftools_ROOT_DIR}/include) + +set(GPERFTOOLS_LIBRARIES ${GPERFTOOLS_TCMALLOC_AND_PROFILER}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + Gperftools + DEFAULT_MSG + GPERFTOOLS_LIBRARIES + GPERFTOOLS_INCLUDE_DIR) + +mark_as_advanced( + Gperftools_ROOT_DIR + GPERFTOOLS_TCMALLOC + GPERFTOOLS_PROFILER + GPERFTOOLS_TCMALLOC_AND_PROFILER + GPERFTOOLS_LIBRARIES + GPERFTOOLS_INCLUDE_DIR) diff --git a/cmake/SetupGtest.cmake b/cmake/SetupGtest.cmake new file mode 100644 index 0000000..33e701c --- /dev/null +++ b/cmake/SetupGtest.cmake @@ -0,0 +1,39 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +# Setup googletest +configure_file("${PROJECT_SOURCE_DIR}/cmake/CMakeLists.download_gtest.in" ${PROJECT_BINARY_DIR}/googletest-download/CMakeLists.txt) + +execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . + RESULT_VARIABLE result + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/googletest-download +) +if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") +endif() + +execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/googletest-download +) +if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") +endif() + +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + +add_subdirectory(${PROJECT_BINARY_DIR}/googletest-src + ${PROJECT_BINARY_DIR}/googletest-build + EXCLUDE_FROM_ALL) diff --git a/cmake/brpc.pc.in b/cmake/brpc.pc.in new file mode 100644 index 0000000..723dab4 --- /dev/null +++ b/cmake/brpc.pc.in @@ -0,0 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ +libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ + +Name: brpc +Description: Apache bRPC is an Industrial-grade RPC framework using C++ Language, which is often used in high performance system such as Search, Storage, Machine learning, Advertisement, Recommendation etc. "bRPC" means "better RPC". +Version: @BRPC_VERSION@ +Cflags: -I${includedir} +Libs: -L${libdir}/ -lbrpc +Libs.private: @BRPC_PRIVATE_LIBS@ diff --git a/community/apache-package-validator.sh b/community/apache-package-validator.sh new file mode 100755 index 0000000..289b106 --- /dev/null +++ b/community/apache-package-validator.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +set -e + +g_package_link=${1} +g_package_keys=${2:-https://downloads.apache.org/brpc/KEYS} + +g_valid_package_link=' ' +g_valid_package_name=' ' +g_valid_package_checksum=' ' +g_valid_package_sig=' ' +g_valid_package_content=' ' +g_valid_package_license=' ' +g_valid_package_binary=' ' + +summary() { + cat < /dev/null 2>&1 + [[ -f RELEASE_VERSION ]] \ + && [[ $(cat RELEASE_VERSION) = "${ver}" ]] \ + && grep BRPC_VERSION CMakeLists.txt | grep -q ${ver} + ) && g_valid_package_content='x' + + ( + pushd ${package_name%.tar.gz} > /dev/null 2>&1 + [[ -f LICENSE && -f NOTICE ]] + ) && g_valid_package_license='x' + + local has_unexpected_binary= + for i in $(find ${package_name%.tar.gz} -type f); do + file ${i} | grep -v 'GIF\|JPEG\|PNG\|SVG\|PowerPoint\|Git\|JSON\|PEM\|empty\|text' && has_unexpected_binary=1 + done + [[ -z "${has_unexpected_binary}" ]] && g_valid_package_binary='x' +} + +trap on_exit EXIT + +validate_package diff --git a/community/cases.md b/community/cases.md new file mode 100644 index 0000000..70568be --- /dev/null +++ b/community/cases.md @@ -0,0 +1,130 @@ +# brpc的应用案例集合 + +## what is this +这里列出brpc在各个企业中的落地场景,包括企业名称,应用项目作用,集群规模和QPS统计,使用的版本信息等 + +## Why this +列出各个案例,一来方便用户进行参考,了解brpc可以用在哪些场景下; +二来方便社区开发者统计brpc的版本,规模等情况 + +# Case List +## 落地case的sample (如果有多个场景,建议分开) +* 公司名称: xxx公司 +* 落地项目: 例如app的个性化推荐系统的预测服务 +* 集群规模: 例如100台 +* QPS: 例如峰值1000万, 均值100万 +* 使用版本: 例如社区版本0.9.7 +* 信息提供者:某某 + +## brpc在 百度的落地情况 +* 公司名称: 百度 +* 落地项目: 基础架构(分布式计算、存储、数据库等),业务系统(Feed、凤巢、地图等) +* 集群规模: 4000多个活跃模块,600w以上实例 +* 使用版本: baidu内部版本 +* 信息提供者:wwbmmm + +## brpc在维沃的落地情况 +* 公司名称: 维沃(vivo) +* 落地项目: 在线推荐系统 +* 使用版本: 社区版本0.9.7 +* 信息提供者:guodongxiaren + +## brpc在爱奇艺的落地情况 +* 公司名称: 爱奇艺(iqiyi) +* 落地项目: 广告、推荐、搜索 +* 使用版本: 基于社区版本定制 +* 集群规模: 3000+台机器(广告) +* 信息提供者:cdjingit + +## brpc在第四范式的落地情况 +* 公司名称: 第四范式(4paradigm) +* 落地项目: 风控、推荐、智能运维等 +* 使用版本: 基于社区版本定制 +* 信息提供者:dl239 + +## brpc在 小红书的落地情况 +* 公司名称: 小红书 +* 落地项目: 推荐系统,基础架构(存储等) +* 集群规模: 1w以上实例(推荐) +* 使用版本: 基于社区版本定制 +* 信息提供者:lzfhust + +## brpc在作业帮的落地情况 +* 公司名称: 作业帮 +* 落地项目: 长连接IM,消息分发系统 +* 集群规模: 2000+核 +* 使用版本: 基于0.9.7定制 +* 信息提供者:xdh0817 + +## brpc在欢聚时代的落地情况 +* 公司名称: 欢聚时代 +* 落地项目: 推荐、直播 +* 使用版本: 基于社区版本定制 +* 信息提供者:chenBright + +## brpc 在 Apache Doris 中的应用 +* 落地项目:Apache Doris +* 使用版本:1.2.0 +* 使用情况:Apache Doris 作为一款 MPP 分析型数据库,其内部节点间使用 Apache Brpc 作为主要 RPC 框架。Brpc 为 Doris 提供了稳定易用的高性能通信机制。并且 BRPC 提供的 bthread,bvar 等基础库,以及各种性能调试工具,也极大的方便了 Doris 的开发和调试工作。 +* 信息提供者:morningman + +## brpc在BaikalDB中的应用 +* 落地项目:BaikalDB +* 使用版本:社区版0.9.7&百度内部stable +* 使用情况:BaikalDB是一款面向OLTP场景为主的NewSQL数据库,其内部节点均采用brpc框架通信,实现数据处理和复制以及集群管理,brpc的高性能对OLTP场景的低延迟至关重要,同时brpc集成的性能调优工具对SQL性能优化提效显著。 +* 信息提供者:tullyliu + +## brpc在数美科技的落地情况 +* 公司名称: 数美科技(nextdata) +* 落地项目: 智能风控业务(模型特征服务、规则引擎) +* 使用版本: 1.0.0 +* 信息提供者:day253 + +## brpc在信网信息的落地情况 +* 公司名称:信网(eyou.com) +* 落地项目:分布式元数据存储引擎服务 +* 使用版本:1.3.0 +* 集群规模:10台+ +* 信息提供者:haihuju + +## brpc在滴滴的落地情况 +* 公司名称:滴滴(www.didiglobal.com) +* 落地项目:地图等业务 +* 使用版本:0.9.3 +* 信息提供者:NIGHTFIGHTING + +## brpc在网易的实现 +* 公司名称:网易 +* 实施项目:高性能分布式存储系统Curve。 +* 集群规模:单个集群30,000个节点 +* 使用版本:1.3.0 +* 信息提供者:Divyansh200102 + +## brpc在搜狗的实现 +* 公司名称:搜狗 +* 实现项目:SRPC是搜狗几乎所有在线服务都使用的企业级RPC系统之一 + 主要功能包括支持 bRPC 等 RPC 协议。 +* IDL:PB +* 通讯方式:TCP +* 网络数据:二进制 +* 信息提供者:Divyansh20010 + +## brpc在B站的实现 +* 公司名称:哔哩哔哩 +* 实现项目:支持bilibili在获取服务器节点时发现命名服务的zone,支持bilibili发现服务 +* 使用版本:0.9.7,0.9.6 +* 信息提供者:Divyansh200102 + +## brpc在腾讯的实现 +* 公司名称:腾讯 +* 已实现的项目:Doris、StarRocks是后端的BRPC端口,用于后端实例之间的通信。 +* 端口号:8060 +* 信息提供者:Divyansh200102 + +## brpc在阿里巴巴的实现 +* 公司名称:阿里巴巴 +* 已实现项目:阿里云,bRPC端口用于与另一个BE通信。 +* 端口名称:brpc_port +* 默认端口号:8060 +* 通讯方向:FE <--> BE,BE <--> BE +* 信息提供者:Divyansh200102 \ No newline at end of file diff --git a/community/logo-bRPC.svg b/community/logo-bRPC.svg new file mode 100644 index 0000000..bd7c97d --- /dev/null +++ b/community/logo-bRPC.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/community/newcommitter_cn.md b/community/newcommitter_cn.md new file mode 100644 index 0000000..34bbf3e --- /dev/null +++ b/community/newcommitter_cn.md @@ -0,0 +1,68 @@ +# 这里记录发展Committer 和 PPMC成员的流程和参考网站信息 + +## 1. 如何发展committer + +### 前置条件 +1. 贡献者commit数量达到10个以上 +2. 贡献者个人有意愿接受邀请成为committer +3. 贡献者订阅dev@brpc.apache.org,并发邮件介绍自己 + +### 成为committer的路程 +1. 提名者在private@brpc中发起讨论和投票,投票通过即OK (最少3+1, +1 > -1),[邮件模版](https://community.apache.org/newcommitter.html#committer-vote-template) +2. 提名者发送close vote邮件给private@brpc, 标题可以为subject [RESULT][VOTE],[邮件模版](https://community.apache.org/newcommitter.html#close-vote) +3. 提名者给被提名者发invite letter([邮件模板](https://community.apache.org/newcommitter.html#committer-invite-template)),并得到回复后再提示他提交ICLA([邮件模板](https://community.apache.org/newcommitter.html#committer-accept-template)) +4. 被提名者填写[CLA](https://www.apache.org/licenses/contributor-agreements.html), 个人贡献者需要下载[ICLA](https://www.apache.org/licenses/icla.pdf)填写个人信息并签名,发送电子版给 secretary@apache.org。(注意:1. ICLA需要填写信息完全,包括邮寄地址和签名,否则会被ASF的秘书打回, 2. ICLA中含有个人信息,所以应该单发给secretary@apache.org,不应该同时抄送PMC或其他邮件组 3. ICLA中的preffered Apache id和notify project虽然是optional,但最好填上,这样秘书会帮新commiter申请好apache id,否则就需要PMC chair或者ASF member去申请)个人信息填写项(除签名外)可以使用 PDF 阅读器或浏览器填写,填写后保存进行签名。签名方式支持: + - 打印 该pdf 文件,手工填写表单(姓名、邮箱、邮寄地址),然后手写签名,最后扫描为电子版; + - 使用支持手写的设备进行电子签名; + - 使用 `gpg` 进行电子签名,即对填写好个人基本信息的 pdf 文件进行操作(需要提前生成与登记邮箱匹配的公钥/密钥对):`gpg --armor --detach-sign icla.pdf`; + - 使用 `DocuSign` 进行签名; + +5. 提名者发送announce邮件到dev@brpc.apache.org + + +### 如何赋予committer在github上的权限 + +1. 加为committer +https://whimsy.apache.org/roster/ppmc/brpc + +2. 让他设置github id +https://id.apache.org/ + +3. 让他访问该网址,获得github的权限 +https://gitbox.apache.org/setup/ + + +### Apache 官网new committer相关的文档 + +* https://community.apache.org/newcommitter.html + +* https://infra.apache.org/new-committers-guide.html + +* https://juejin.cn/post/6844903788982042632 + +### Suggested steps from secretary@apache.org +Please do these things: + +1. Hold the discussion and vote on your private@ list. This avoids any issues related to personnel, which should remain private. +2. If the vote is successful, announce the result to the private@ list with a new email thread with subject [RESULT][VOTE]. This makes it easier for secretary to find the result of the vote in order to request the account at the time of the filing of the ICLA. +3. Only if the candidate accepts committership, announce the new committer on your dev@ list. + +Doing these things will make everyone's job easier. + + + +## 2. 如何把committer变成为PPMC + +### 流程参考:Apache官网文档 +* https://incubator.apache.org/guides/ppmc.html#voting_in_a_new_ppmc_member +* https://community.apache.org/newcommitter.html +* https://incubator.apache.org/guides/ppmc.html#podling_project_management_committee_ppmc + +### 实际流程 +1. 在private@brpc中发起讨论,如果没有反对,则继续 +2. 在private@brpc中发起投票 +3. 在private@brpc中发邮件,结束投票,并通知private@incubator.apache.org +4. 在private@brpc中和dev中announce new PPMC +5. 设定他的权限,通过访问https://whimsy.apache.org/roster/ppmc/brpc +6. 帮他订阅private邮件组,参见https://whimsy.apache.org/committers/moderationhelper.cgi + diff --git a/community/newcommitter_en.md b/community/newcommitter_en.md new file mode 100644 index 0000000..2e99334 --- /dev/null +++ b/community/newcommitter_en.md @@ -0,0 +1,60 @@ +# Here is the doc for the process and reference website information of developing committee and PPMC members. + + +## 1. How to develop committers + +### Preconditions + +1. More than 10 commits from the contributor +2. Contributor is willing to accept the invitation to become a committer +3. Contributor subscribe to dev@brpc.apache.org and introduce himself by sending an email to dev@apache.org + +### The journey to become a committer + +1. The nominator sends an email to private@brpc to initiate discussion and begins to voting. If the voting is passed, it is OK (at least 3 +1, +1 >- 1). (See the [Vote email template](https://community.apache.org/newcommitter.html#committer-vote-template)) +2. The nominator sends a close vote email to private@brpc, the title can be subject [RESULT] [VOTE]. (See the [close email template](https://community.apache.org/newcommitter.html#close-vote)) +3. The nominator sends an invitation letter to the nominee ([email template](https://community.apache.org/newcommitter.html#committer-invite-template)) and prompts him to submit ICLA after receiving a reply ([email template](https://community.apache.org/newcommitter.html#committer-accept-template)) +4. The nominee fills in [ICLA](https://www.apache.org/licenses/contributor-agreements.html), individual contributors need to download [ICLA](https://www.apache.org/licenses/icla.pdf) Fill in personal information and sign, and send the electronic version to secretary@apache.org 。 (Note: 1. ICLA needs to fill in complete information, including mailing address and signature, otherwise it will be returned by ASF's secretary. 2. ICLA contains personal information, it should be send to secretary@apache.org only, not to the PMC or other mail list) The personal information entry (except for the signature) can be filled in with a PDF reader or browser, and then saved for signature. Signature method support: + * Print pdf documents and scan them into electronic version after handwritten signature; + * Electronic signature using devices that support handwriting; + * Use 'gpg' for electronic signature, that is, to operate the pdf file filled with personal basic information (a public key/key pair matching the registered mailbox needs to be generated in advance): 'gpg -- armor -- detach sign icla. pdf'; + * Use 'DocuSign' to sign; +5. The nominator sends an announcement email to dev@brpc.apache.org + +### How to grant a committer permission on github + +1. Add as committer ([https://whimsy.apache.org/roster/ppmc/brpc](https://whimsy.apache.org/roster/ppmc/brpc)) +2. Let him set github id ([https://id.apache.org/](https://id.apache.org/)) +3. Let him visit the website and get github permission([https://gitbox.apache.org/setup/](https://id.apache.org/)) + +### Documents related to the new committer on the official Apache website + +* [https://community.apache.org/newcommitter.html](https://community.apache.org/newcommitter.html) +* [https://infra.apache.org/new-committers-guide.html](https://infra.apache.org/new-committers-guide.html) +* [https://juejin.cn/post/6844903788982042632](https://juejin.cn/post/6844903788982042632) + +### Suggested steps from secretary@apache.org + +Please do these things: +1. Hold the discussion and vote on your private@ list. This avoids any issues related to personnel, which should remain private. +2. If the vote is successful, announce the result to the private@ list with a new email thread with subject [RESULT][VOTE]. This makes it easier for secretary to find the result of the vote in order to request the account at the time of the filing of the ICLA. +3. Only if the candidate accepts committership, announce the new committer on your dev@ list. + +Doing these things will make everyone's job easier. + +## 2. How to upgrade a committer into a PPMC + +### Process reference: Apache official website document + +* https://incubator.apache.org/guides/ppmc.html#voting_in_a_new_ppmc_member +* https://community.apache.org/newcommitter.html +* https://incubator.apache.org/guides/ppmc.html#podling_project_management_committee_ppmc + +### Actual process + +1. Initiate discussion in the private@brpc . If there is no objection, continue +2. Open a Vote in private@brpc +3. Send an email to close the voting and notify private@incubator.apache.org +4. Announce new PPMC On private@brpc +5. Set his authority by visiting https://whimsy.apache.org/roster/ppmc/brpc +6. Help him subscribe to a private email group. See https://whimsy.apache.org/committers/moderationhelper.cgi diff --git a/community/oncall.md b/community/oncall.md new file mode 100644 index 0000000..85abb02 --- /dev/null +++ b/community/oncall.md @@ -0,0 +1,71 @@ +# 值周工程师的职责如下 + +## 1. 每天查看github上brpc项目待处理的Pull Request和Issue列表,负责问题的处理 + * 包括标记issue,回复issue,关闭issue等; + * 判断issue是否是长期Issue,如果是则标记为Pending + * 判断Issue的类型,例如bug,enhancement, discussion等 + * 把issue分配到熟悉该模块的贡献者(可在微信群里询问谁来负责) + +## 2. 轮值时间为一周 + * 即从周日早上到下周六晚上 + +## 3. 轮值结束需要 + * 编写值周report,并发送到dev@brpc.apache.org邮件群中 + * 提醒下一位轮值同学 + +## 4. 值周顺序如下 + * 朱佳顺 @zyearn + * 李磊 @lorinlee + * 王伟冰 @wwbmmm + * 蔡道进 @cdjingit + * 何磊 @TousakaRin + * 刘帅 @serverglen + * 胡希国 @Huixxi + * 杨立明 @yanglimingcn + +## 5. 值周记录如下 + +| 时间(月/日/年) | 值周人 | 值周report| +| ---- | ---- | --- | +| 06/21/2021 to 06/27/2021 | 李磊 | https://lists.apache.org/thread/czolsqo80jzsytqc7dp37knqwxnkymx1 +| 06/28/2021 to 07/04/2021 | 蔡道进 | https://lists.apache.org/thread/c05fcjdjh7473ho1ylyl98mxscmnkbv0 +| 07/05/2021 - 07/12/2021 | 何磊 | https://lists.apache.org/thread/hz1vn7358v5fslglg2cl4g8x0wtxy4lv +| 08/16/2021 - 08/22/2021 | 朱佳顺 | https://lists.apache.org/thread/g36jtjc3v75lfoc7m3ynzplgbqlsjjrd +| 08/23/2021 - 08/29/2021 | 李磊 | https://lists.apache.org/thread/2098ndgdy6fs2b1s8tywpmz47n5swh29 +| 08/30/2021 - 09/05/2021 | 蔡道进 | https://lists.apache.org/thread/wjxdomg7fp75dc0n0rmh37bkwd8w7myv +| 09/06/2021 - 09/12/2021 | 何磊 | https://lists.apache.org/thread/cwjtocpjbqjgndog53rqw8gs6f9c0rmo +| 09/20/2021 - 09/26/2021 | 李磊 | https://lists.apache.org/thread/p779y43hogv7ftckc4cqx006gv9j65r8 +| 09/27/2021 - 10/03/2021 | 蔡道进 | https://lists.apache.org/thread/ync12fx2dwjb2l3p4yck0kodmkgzz7wd +| 12/13/2021 - 12/19/2021 | 蔡道进 | https://lists.apache.org/thread/mvclsy79859mrbdso1xzm6y7yz3lg6w0 +| 01/24/2022 - 02/06/2022 | 王伟冰 | https://lists.apache.org/thread/ttgqnw4hfw0qnb7swvnn2kxb5b9hkdbo +| 04/10/2022 - 04/16/2022 | 刘帅 | https://lists.apache.org/thread/0l67lqnw0l6rgfwkvhrc168prwr7fp60 +| 04/17/2022 - 04/24/2022 | 朱佳顺| https://lists.apache.org/thread/xwkpyonndgtp8tq4c9v4yfowqx7fg9gl +| 04/25/2022 - 05/01/2022 | 李磊 | https://lists.apache.org/thread/cx7t1glptr7x7posxstsb1691h4m4mbl +| 05/02/2022 - 05/15/2022 | 王伟冰 | https://lists.apache.org/thread/tgyqkhh6fqgyzcn5d56kt46hk978wogx +| 06/06/2022 - 06/12/2022 | 何磊 | https://lists.apache.org/thread/s19p7ygnsj0bknfjvrh0wlf1mbgstxbk +| 06/13/2022 - 06/19/2022 | 刘帅 | https://lists.apache.org/thread/tvqjyz6rh7jb1zcclx0lh6zrcsf9ykxr +| 07/25/2022 - 07/31/2022 | 王伟冰 | https://lists.apache.org/thread/83scwkkfxrp6kkkoltbrn1fthfy3w0qz +| 08/08/2022 - 08/14/2022 | 何磊 | https://lists.apache.org/thread/jj16rzfh34yrt6o0xqfdz9wtdtzxzswq +| 08/15/2022 - 08/21/2022 | 刘帅 | https://lists.apache.org/thread/jp69sm7c8fs3dkdd828qk0fsojqwwz6h +| 09/05/2022 - 09/12/2022 | 王伟冰 | https://lists.apache.org/thread/4jjk2hxw9s2wskccclqb8fvpqxqffnlb +| 09/12/2022 - 09/18/2022 | 蔡道进| https://lists.apache.org/thread/8mo7zl0l2yrd8tp4v3kx86rnlyfk6wz4 +| 09/19/2022 - 09/25/2022 | 何磊 | https://lists.apache.org/thread/qlkr7cmwow3ob47dt80tpx0zrgzg7bdm +| 09/26/2022 - 10/09/2022 | 刘帅 | https://lists.apache.org/thread/b0lwr8wyflmhqlnf0kkh1j30tgt5qw54 +| 10/10/2022 - 10/16/2022 | 朱佳顺 | https://lists.apache.org/thread/y8sgbprxt21j6r0812dlftosfov6pbgk +| 10/17/2022 - 10/23/2022 | 李磊 | https://lists.apache.org/thread/qn2270p9qsrglkh7oy013ts1zk5rlhwx +| 10/24/2022 - 10/30/2022 | 王伟冰 | https://lists.apache.org/thread/k74155opmwmopgtsqo6p79z9q0f0sv8j +| 10/31/2022 - 11/06/2022 | 蔡道进 | https://lists.apache.org/thread/t4z49yt7ymp4vr5sms0m4cpoo94db4oz +| 11/14/2022 - 11/20/2022 | 刘帅 | https://lists.apache.org/thread/nq50fp79ox3follc7gxp814cvcqnmqzz +| 11/21/2022 - 11/27/2022 | 朱佳顺 | https://lists.apache.org/thread/57kzov5g3j4vv6l2zcyw0msm36qglc8k +| 11/28/2022 - 12/04/2022 | 李磊 | https://lists.apache.org/thread/92hbzcd662slj75v3ndjf69o1dgsnd6o +| 12/05/2022 - 12/11/2022 | 王伟冰 | https://lists.apache.org/thread/99o15h9hk5dd73jv8wyp49l8mbw0j611 +| 12/19/2022 - 12/25/2022 | 何磊 | https://lists.apache.org/thread/g1stjjo1mc9ds47do6gosrw5k6wwb9mj +| 12/26/2022 - 01/01/2023 | 刘帅 | https://lists.apache.org/thread/3xw1gkobqmvr6oo375x3gsfcqvg80n23 +| 01/02/2023 - 01/08/2023 | 朱佳顺 | https://lists.apache.org/thread/sm4f209c8ltols04gpmzo386b02dyc9s +| 01/09/2023 - 01/15/2023 | 李磊 | https://lists.apache.org/thread/fydjz4h88omsrb7fzw65wl64kh5r520s +| 01/16/2023 - 01/29/2023 | 王伟冰 | https://lists.apache.org/thread/k5bb4pn2v23dksmwpwqphzlc4bd92ndr +| 01/30/2023 - 02/05/2023 | 蔡道进 | https://lists.apache.org/thread/4scwpdh163l92czm5pvc7ox78n44mrn4 +| 02/13/2023 - 02/19/2023 | 刘帅 | https://lists.apache.org/thread/jwynp880hdhcfkqwq7thsm05y037wzy5 +| 02/27/2023 - 03/05/2023 | 李磊 | https://lists.apache.org/thread/94ndftsvooydfnn8hdddv294pp0tfvdm +| 03/06/2023 - 03/12/2023 | 王伟冰 | https://lists.apache.org/thread/bv3qw5w8gj9xs576fplxqhktopkbk7md +| 03/27/2023 - 04/03/2023 | 刘帅 | https://lists.apache.org/thread/fk37cn8r5pd1y10vjvzvkl211b67vn4q diff --git a/community/release_cn.md b/community/release_cn.md new file mode 100644 index 0000000..ee24c1b --- /dev/null +++ b/community/release_cn.md @@ -0,0 +1,592 @@ +# brpc 发布 apache release 版本流程 step by step + +## 准备工作 + +### 1. 确认 Release Notes + +该阶段会持续一周左右,主要工作包含: +- 确认 Release Notes 中是否有遗漏项以及各项的描述是否准确、恰当; +- 合入计划发布但未及时处理的 PR。 + +Release Notes 需要提供中英两个版本,用于不同的对外发布渠道。 + +模板如下: +``` +[Release Notes] + +Feature: +- new feature 1; +- new feature 2; +- ... + +Bugfix: +- fix bug 1; +- fix bug 2; +- ... + +Enhancement: +- improve blabla + +Other: +- ... +``` + +### 2. 设置 GPG(非首次发版的同学请直接跳过此步骤) + +#### 1. 安装 GPG + +通常 Linux 发行版中会集成 `GnuPG` 工具,OSX 可以使用 [`brew`](https://brew.sh/) 安装。 +```bash +brew install gnupg +``` + +也可以直接在[GnuPG 官网](https://www.gnupg.org/download/index.html)下载相应的安装包。`GnuPG` 的 1.x 版本和 2.x 版本的命令有细微差别,下面以 `GnuPG-2.3.1` 版本(OSX)为例进行说明。 + +安装完成后,执行以下命令查看版本号。 +```bash +gpg --version +``` + +#### 2. 创建 key + +安装完成后,执行以下命令创建 key。 + +```bash +gpg --full-gen-key +``` + +根据提示完成创建 key,注意邮箱要使用 Apache 邮件地址,`Real Name` 使用姓名 Pinyin、Apache ID 或 GitHub ID 等均可: +``` +gpg (GnuPG) 2.3.1; Copyright (C) 2021 Free Software Foundation, Inc. +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. + +Please select what kind of key you want: + (1) RSA and RSA + (2) DSA and Elgamal + (3) DSA (sign only) + (4) RSA (sign only) + (9) ECC (sign and encrypt) *default* + (10) ECC (sign only) + (14) Existing key from card +Your selection? 1 +RSA keys may be between 1024 and 4096 bits long. +What keysize do you want? (3072) 4096 +Requested keysize is 4096 bits +Please specify how long the key should be valid. + 0 = key does not expire + = key expires in n days + w = key expires in n weeks + m = key expires in n months + y = key expires in n years +Key is valid for? (0) 0 +Key does not expire at all +Is this correct? (y/N) y + +GnuPG needs to construct a user ID to identify your key. + +Real name: LorinLee +Email address: lorinlee@apache.org +Comment: lorinlee's key +You selected this USER-ID: + "LorinLee (lorinlee's key) " + +Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O +You need a Passphrase to protect your secret key. # 输入密码 + +We need to generate a lot of random bytes. It is a good idea to perform +some other action (type on the keyboard, move the mouse, utilize the +disks) during the prime generation; this gives the random number +generator a better chance to gain enough entropy. +gpg: key 92E18A11B6585834 marked as ultimately trusted +gpg: revocation certificate stored as '/Users/lilei/.gnupg/openpgp-revocs.d/C30F211F071894258497F46392E18A11B6585834.rev' +public and secret key created and signed. + +pub rsa4096 2021-10-17 [SC] + C30F211F071894258497F46392E18A11B6585834 +uid LorinLee (lorinlee's key) +sub rsa4096 2021-10-17 [E] +``` + +#### 3. 查看生成的 key + +```bash +gpg --list-keys +``` + +执行结果: + +``` +gpg: checking the trustdb +gpg: marginals needed: 3 completes needed: 1 trust model: pgp +gpg: depth: 0 valid: 2 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 2u +/Users/lilei/.gnupg/pubring.kbx +---------------------------------- +pub rsa4096 2021-10-17 [SC] + C30F211F071894258497F46392E18A11B6585834 +uid [ultimate] LorinLee (lorinlee's key) +sub rsa4096 2021-10-17 [E] +``` + +其中 `C30F211F071894258497F46392E18A11B6585834` 为公钥ID。 + +#### 4. 将公钥公布到服务器 + +命令如下: + +```bash +gpg --keyserver hkps://pgp.mit.edu --send-key C30F211F071894258497F46392E18A11B6585834 +``` +keyserver 也可以用 `hkps://keys.openpgp.org` 或 `hkps://keyserver.ubuntu.com`,Web 上都提供了比较方便的查询接口。 + +#### 5. 生成 fingerprint 并上传到 Apache 用户信息中 + +由于公钥服务器没有检查机制,任何人都可以用你的名义上传公钥,所以没有办法保证服务器上的公钥的可靠性。通常,你可以在⽹站上公布一个公钥指纹,让其他⼈核对下载到的公钥是否为真。fingerprint 参数生成公钥指纹。 + +执行如下命令查看 fingerprint: +``` +gpg --fingerprint lorinlee(用户ID) +``` + +输出如下: +``` +/Users/lilei/.gnupg/pubring.kbx +---------------------------------- +pub rsa4096 2021-10-17 [SC] + C30F 211F 0718 9425 8497 F463 92E1 8A11 B658 5834 +uid [ultimate] LorinLee (lorinlee's key) +sub rsa4096 2021-10-17 [E] +``` + +将上面的 fingerprint `C30F 211F 0718 9425 8497 F463 92E1 8A11 B658 5834` 粘贴到⾃己 Apache ⽤户信息 https://id.apache.org 的 `OpenPGP Public Key Primary Fingerprint:` 字段中。 + +## 制作发布包 + +### 1. 拉出发版分支 + +如果是发布新的 2 位版本,如 `1.0.0`,则需要从 master 拉出新的分支 `release-1.0`。 + +如果是在已有的 2 位版本上发布新的 3 位版本,如 `1.0.1` 版本,则只需要在已有的 `release-1.0` 分支上修改加上要发布的内容。 + +发版过程中的操作都在 release 分支(如:`release-1.0`)上操作,如果发版过程发现代码有问题需要修改,也在该分支上进行修改。发版完成后,将该分支合回 master 分支。 + +### 2. 更新 `NOTICE` 文件 + +检查 `NOTICE` 文件中的年份是否需要更新,通常在年初发版时需重点关注。 + +### 3. 编辑版本号相关文件 + +#### 更新 `RELEASE_VERSION` 文件 + +编辑项目根目录下 `RELEASE_VERSION` 文件,更新版本号,并提交至代码仓库,本文以 `1.0.0` 版本为例,文件内容为: + +``` +1.0.0 +``` + +#### 更新 `CMakeLists.txt` 文件 +编辑项目根目录下 `CMakeLists.txt` 文件,更新版本号,并提交至代码仓库,本文以 `1.0.0` 版本为例,修改 `BRPC_VERSION` 为: + +``` +set(BRPC_VERSION 1.0.0) +``` + +#### 更新 `package/rpm/brpc.spec` 文件 + +编辑项目根目录下 `package/rpm/brpc.spec` 文件,更新版本号,并提交至代码仓库,本文以 `1.0.0` 版本为例,修改 `Version` 为: + +``` +Version: 1.0.0 +``` + +#### 更新 `MODULE.bazel` 文件 + +编辑项目根目录下 `MODULE.bazel` 文件,更新版本号,并提交至代码仓库,本文以 `1.0.0` 版本为例,修改 `version` 为: + +``` +# in MODULE.bazel +module( + ... + version = '1.0.0', + ... +) +``` + +### 4. 创建发布 tag +```bash +export BRPCVERSION=1.0.0 +export BRPCBRANCH=1.0 +export BRPCUSERNAME=lorinlee +``` +拉取发布分支,创建并推送 tag +```bash +git clone -b release-$BRPCBRANCH git@github.com:apache/brpc.git ~/brpc + +cd ~/brpc + +git tag -a $BRPCVERSION -m "release $BRPCVERSION" + +git push origin --tags +``` + +### 5. 打包发布包 + +```bash +git archive --format=tar $BRPCVERSION --prefix=apache-brpc-$BRPCVERSION-src/ | gzip > apache-brpc-$BRPCVERSION-src.tar.gz +``` +或 +```bash +git archive --format=tar.gz $BRPCVERSION --prefix=apache-brpc-$BRPCVERSION-src/ --output=apache-brpc-$BRPCVERSION-src.tar.gz +``` + +### 6. 生成签名文件 + +```bash +gpg -u $BRPCUSERNAME@apache.org --armor --output apache-brpc-$BRPCVERSION-src.tar.gz.asc --detach-sign apache-brpc-$BRPCVERSION-src.tar.gz + +gpg --verify apache-brpc-$BRPCVERSION-src.tar.gz.asc apache-brpc-$BRPCVERSION-src.tar.gz +``` + +### 7. 生成哈希文件 + +```bash +sha512sum apache-brpc-$BRPCVERSION-src.tar.gz > apache-brpc-$BRPCVERSION-src.tar.gz.sha512 + +sha512sum --check apache-brpc-$BRPCVERSION-src.tar.gz.sha512 +``` + +## 发布至 Apache SVN 仓库 + +### 1. 检出 dist/dev 下的 brpc 仓库目录 + +如无本地工作目录,则先创建本地工作目录。将 Apache SVN 仓库克隆下来,username 需要使用自己的 Apache LDAP 用户名。 + +```bash +mkdir -p ~/brpc_svn/dev/ + +cd ~/brpc_svn/dev/ + +svn --username=$BRPCUSERNAME co https://dist.apache.org/repos/dist/dev/brpc/ + +cd ~/brpc_svn/dev/brpc +``` + +### 2. 添加 GPG 公钥 + +仅第一次部署的账号需要添加,只要 KEYS 中包含已经部署过的账户的公钥即可。 + +```bash +(gpg --list-sigs $BRPCUSERNAME && gpg -a --export $BRPCUSERNAME) >> KEYS +``` + +注意:当有多个名相同的 key 时,可以指定完整邮件地址或者公钥来导出指定的公钥信息。如: +```bash +(gpg --list-sigs $BRPCUSERNAME@apache.org && gpg -a --export $BRPCUSERNAME@apache.org) >> KEYS +``` +或: +```bash +(gpg --list-sigs C30F211F071894258497F46392E18A11B6585834 && gpg -a --export C30F211F071894258497F46392E18A11B6585834) >> KEYS +``` + +### 3. 将待发布的代码包添加至 SVN 目录 + +```bash +mkdir -p ~/brpc_svn/dev/brpc/$BRPCVERSION + +cd ~/brpc_svn/dev/brpc/$BRPCVERSION + +cp ~/brpc/apache-brpc-$BRPCVERSION-src.tar.gz ~/brpc_svn/dev/brpc/$BRPCVERSION + +cp ~/brpc/apache-brpc-$BRPCVERSION-src.tar.gz.asc ~/brpc_svn/dev/brpc/$BRPCVERSION + +cp ~/brpc/apache-brpc-$BRPCVERSION-src.tar.gz.sha512 ~/brpc_svn/dev/brpc/$BRPCVERSION +``` + +### 4. 提交 SVN + +退回到上级目录,使用 Apache LDAP 账号提交 SVN。 + +```bash +cd ~/brpc_svn/dev/brpc + +svn add * + +svn --username=$BRPCUSERNAME commit -m "release $BRPCVERSION" +``` + +## 检查发布结果 +```bash +cd ~/brpc_svn/dev/brpc/$BRPCVERSION +``` +### 1. 检查 sha512 哈希 + +```bash +sha512sum --check apache-brpc-$BRPCVERSION-src.tar.gz.sha512 +``` + +### 2. 检查 GPG 签名 + +首先导入发布人公钥。从 svn 仓库导入 KEYS 到本地环境。(发布版本的人不需要再导入,帮助做验证的人需要导入,用户名填发版人的即可) + +```bash +curl https://dist.apache.org/repos/dist/dev/brpc/KEYS >> KEYS + +gpg --import KEYS +``` + +设置信任该用户的签名,执行以下命令,填写发布人的用户名 +```bash +gpg --edit-key $BRPCUSERNAME +``` + +输出为 +``` +gpg (GnuPG) 2.3.1; Copyright (C) 2021 Free Software Foundation, Inc. +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. + +Secret key is available. + +gpg> trust + +Please decide how far you trust this user to correctly verify other users' keys +(by looking at passports, checking fingerprints from different sources, etc.) + + 1 = I don't know or won't say + 2 = I do NOT trust + 3 = I trust marginally + 4 = I trust fully + 5 = I trust ultimately + m = back to the main menu + +Your decision? 5 +Do you really want to set this key to ultimate trust? (y/N) y + +gpg> save +``` + +然后进行 gpg 签名检查。 +```bash +gpg --verify apache-brpc-$BRPCVERSION-src.tar.gz.asc apache-brpc-$BRPCVERSION-src.tar.gz +``` + +### 3. 检查发布内容 + +#### 1. 对比源码包与 github 上的 tag 内容差异 + +```bash +curl -Lo tag-$BRPCVERSION.tar.gz https://github.com/apache/brpc/archive/refs/tags/$BRPCVERSION.tar.gz + +tar xvzf tag-$BRPCVERSION.tar.gz + +tar xvzf apache-brpc-$BRPCVERSION-src.tar.gz + +diff -r brpc-$BRPCVERSION apache-brpc-$BRPCVERSION-src +``` + +#### 2. 检查源码包的文件内容 + +- 检查源码包是否包含由于包含不必要文件,致使 tarball 过于庞大 +- 存在 LICENSE 和 NOTICE 文件,并且 NOTICE 文件中的年份正确 +- 只存在文本文件,不存在二进制文件 +- 所有文件的开头都有 ASF 许可证 +- 能够正确编译,单元测试可以通过 +- 检查是否有多余文件或文件夹,例如空文件夹等 +- 检查第三方依赖许可证: + - 第三方依赖的许可证兼容 + - 所有第三方依赖的许可证都在 LICENSE 文件中声名 + - 依赖许可证的完整版全部在 license 目录 + - 如果依赖的是 Apache 许可证并且存在 NOTICE 文件,那么这些 NOTICE 文件也需要加入到版本的 NOTICE 文件中 + +## 在 Apache bRPC 社区发起投票 + +该阶段会持续至少 3 天。 + +### 1. 投票阶段 + +1. 发起投票邮件到 dev@brpc.apache.org。PMC 需要先按文档检查版本的正确性,然后再进行投票。经过至少 72 小时并统计到 3 个 +1 PMC member 票后,方可进入下一阶段。 +2. 宣布投票结果,发起投票结果邮件到 dev@brpc.apache.org。 + +### 2. 投票邮件模板 + +#### Apache bRPC 社区投票邮件模板 + +标题: +``` +[VOTE] Release Apache bRPC 1.0.0 +``` + +正文: +``` +Hi Apache bRPC Community, + +This is a call for vote to release Apache bRPC version 1.0.0 + +[Release Note] +- xxx + +The release candidates: +https://dist.apache.org/repos/dist/dev/brpc/1.0.0/ + +Git tag for the release: +https://github.com/apache/brpc/releases/tag/1.0.0 + +Release Commit ID: +https://github.com/apache/brpc/commit/xxx + +Keys to verify the Release Candidate: +https://dist.apache.org/repos/dist/dev/brpc/KEYS + +The vote will be open for at least 72 hours or until the necessary number of +votes are reached. + +Please vote accordingly: +[ ] +1 approve +[ ] +0 no opinion +[ ] -1 disapprove with the reason + +PMC vote is +1 binding, all others are +1 non-binding. + +Checklist for reference: +[ ] Download links are valid. +[ ] Checksums and PGP signatures are valid. +[ ] Source code distributions have correct names matching the current +release. +[ ] LICENSE and NOTICE files are correct for each brpc repo. +[ ] All files have license headers if necessary. +[ ] No compiled archives bundled in source archive. + +Regards, +LorinLee +``` + +注:`Release Commit ID` 填写当前 tag(1.0.0) 的 commit id。 + +#### Apache bRPC 社区宣布结果邮件模板 + +标题: +``` +[RESULT] [VOTE] Release Apache bRPC 1.0.0 +``` + +正文: +``` +Hi all, + +The vote to release Apache bRPC 1.0.0 has passed. + +The vote PASSED with 3 binding +1, 3 non binding +1 and no -1 votes: + +Binding votes: +- xxx +- yyy +- zzz + +Non-binding votes: +- aaa +- bbb +- ccc + +Vote thread: xxx (vote email link in https://lists.apache.org/) + +Thank you to all the above members to help us to verify and vote for +the 1.0.0 release. I will process to publish the release and send ANNOUNCE. + +Regards, +LorinLee +``` + +### 3. 投票未通过 + +若社区投票未通过,则在 release 分支对代码仓库进行修改,重新打包,发起投票。 + +## 完成发布 + +### 1. 将软件包从 Apache SVN 仓库的 dist/dev 目录移至 dist/release 目录 + +注意:该过程需要 PMC 成员进行,投票通过后可以联系 PMC 成员进行相关操作,首次发版的成员还需要更新公钥信息 KEYS。 + +```bash +svn mv https://dist.apache.org/repos/dist/dev/brpc/$BRPCVERSION https://dist.apache.org/repos/dist/release/brpc/$BRPCVERSION -m "release brpc $BRPCVERSION" +``` + +### 2. 创建 GitHub 版本发布页 + +在 [GitHub Releases 页面](https://github.com/apache/brpc/tags)的对应 tag 上点击,创建新的 Release,编辑版本号及版本说明。注意Release title统一根据本次版本号写为Apache bRPC 1.x.0,并点击 Publish release。 + +### 3. 更新网站下载页 + +等待并确认新的发布版本同步至 Apache 镜像后,更新如下页面:, 更新方式在 仓库中,注意中英文都要更新。 + +GPG 签名文件和哈希校验文件的下载链接应该使用这个前缀:https://downloads.apache.org/brpc/ + +代码包的下载链接应该使用这个前缀:https://dlcdn.apache.org/brpc/ + +### 4. 发送邮件通知新版发布 + +发送邮件到 dev@brpc.apache.org 和 announce@apache.org 通知完成版本发布。 + +注意:发邮件账号必须使用**个人 apache 邮箱**,且邮件内容必须是**纯文本格式**(可在 gmail 选择"纯文本模式")。announce@apache.org 邮件组需要经过人工审核才能送达,发出邮件后请耐心等待,一般会在一天之内通过。 + +个人 apache 邮箱配置方式可以参考:https://shenyu.apache.org/zh/community/use-apache-email +注意:SMTP 服务器需要填 `mail-relay.apache.org`。 + +通知邮件模板如下: + +标题: +``` +[ANNOUNCE] Apache bRPC 1.0.0 released +``` + +正文: +注:`Brief notes of this release` 仅需列出本次发版的主要变更,且无需指出对应贡献人和 PR 编号,建议参考下之前的 Announce 邮件。 +``` +Hi all, + +The Apache bRPC community is glad to announce the new release +of Apache bRPC 1.0.0. + +Apache bRPC is an Industrial-grade RPC framework using C++ Language, +which is often used in high performance systems such as Search, Storage, +Machine learning, Advertisement, Recommendation etc. + +Brief notes of this release: +- xxx +- yyy +- zzz + +More details regarding Apache brpc can be found at: +https://brpc.apache.org/ + +The release is available for download at: +https://brpc.apache.org/download/ + +The release notes can be found here: +https://github.com/apache/brpc/releases/tag/1.0.0 + +Website: https://brpc.apache.org/ + +Apache bRPC Resources: +- Issue: https://github.com/apache/brpc/issues/ +- Mailing list: dev@brpc.apache.org +- Documents: https://brpc.apache.org/docs/ + +We would like to thank all contributors of the Apache bRPC community +who made this release possible! + + +Best Regards, +Apache bRPC Community +``` + +### 5. 其他对外公共账号 + +#### 微信公众号 + +参考 。 +建议先在腾讯文档中编辑后粘贴至公众号,统一字体大小和格式,参考 [腾讯文档:bRPC 1.11.0](https://docs.qq.com/doc/DYmZ2Tnpub1lySWZO?_t=1730208105245&u=31460cd039dd4461877a61ab9f56be1f) + + +### 6. 更新 master 分支 + +发版完成后,将 release 分支合并到 master 分支。 diff --git a/community/release_en.md b/community/release_en.md new file mode 100644 index 0000000..8a31482 --- /dev/null +++ b/community/release_en.md @@ -0,0 +1,595 @@ +# brpc apache release guide step by step + +## Preparation + +### 1. Confirm the release notes + +It will cost about 1 week or so to finish this phase, during this phase: +* The content of the `Release Notes` will be confirmed, + - no missing changes; + - and the description is clear; +* The PRs those are scheduled for the release can be merged. + +The `Release Notes` template: +``` +[Release Notes] + +Feature: +- ... + +Bugfix: +- ... + +Enhancement: +- ... + +Other: +- ... +``` + +### 2. Setup GPG(Please skip this step if you are not a first time distributor) + +#### 1. Install GPG + +The popular Linux distributions should have the GnuPG pre-installed. With OSX, you can install GnuPG using [`brew`](https://brew.sh/): +```bash +brew install gnupg +``` + +You can also download the installation package from [the GnuPG official website](https://www.gnupg.org/download/index.html). The commands of GnuPG version 1.x and version 2.x are slightly different. The following instructions take the `GnuPG-2.3.1` version (OSX) as an example. + +After the installation is complete, execute the following command to check the version number. +```bash +gpg --version +``` + +#### 2. Create key + +After the installation is complete, execute the following command to create a key. + +```bash +gpg --full-gen-key +``` + +Complete the key creation according to the prompts. Note that the mailbox should use the Apache email address, and the `Real Name` can use Apache ID or GitHub ID: + +``` +gpg (GnuPG) 2.3.1; Copyright (C) 2021 Free Software Foundation, Inc. +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. + +Please select what kind of key you want: + (1) RSA and RSA + (2) DSA and Elgamal + (3) DSA (sign only) + (4) RSA (sign only) + (9) ECC (sign and encrypt) *default* + (10) ECC (sign only) + (14) Existing key from card +Your selection? 1 +RSA keys may be between 1024 and 4096 bits long. +What keysize do you want? (3072) 4096 +Requested keysize is 4096 bits +Please specify how long the key should be valid. + 0 = key does not expire + = key expires in n days + w = key expires in n weeks + m = key expires in n months + y = key expires in n years +Key is valid for? (0) 0 +Key does not expire at all +Is this correct? (y/N) y + +GnuPG needs to construct a user ID to identify your key. + +Real name: LorinLee +Email address: lorinlee@apache.org +Comment: lorinlee's key +You selected this USER-ID: + "LorinLee (lorinlee's key) " + +Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O +You need a Passphrase to protect your secret key. # Input password + +We need to generate a lot of random bytes. It is a good idea to perform +some other action (type on the keyboard, move the mouse, utilize the +disks) during the prime generation; this gives the random number +generator a better chance to gain enough entropy. +gpg: key 92E18A11B6585834 marked as ultimately trusted +gpg: revocation certificate stored as '/Users/lilei/.gnupg/openpgp-revocs.d/C30F211F071894258497F46392E18A11B6585834.rev' +public and secret key created and signed. + +pub rsa4096 2021-10-17 [SC] + C30F211F071894258497F46392E18A11B6585834 +uid LorinLee (lorinlee's key) +sub rsa4096 2021-10-17 [E] +``` + +#### 3. Check the generated key + +```bash +gpg --list-keys +``` + +output: + +``` +gpg: checking the trustdb +gpg: marginals needed: 3 completes needed: 1 trust model: pgp +gpg: depth: 0 valid: 2 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 2u +/Users/lilei/.gnupg/pubring.kbx +---------------------------------- +pub rsa4096 2021-10-17 [SC] + C30F211F071894258497F46392E18A11B6585834 +uid [ultimate] LorinLee (lorinlee's key) +sub rsa4096 2021-10-17 [E] +``` + +Note that `C30F211F071894258497F46392E18A11B6585834` is the public key. + +#### 4. Publish the public key to server + +Execute the following command: + +```bash +gpg --keyserver hkps://pgp.mit.edu --send-key C30F211F071894258497F46392E18A11B6585834 +``` + +The keyserver can also use hkps://keys.openpgp.org or hkps://keyserver.ubuntu.com,which provide web page to view the keys. + +#### 5. Generate fingerprint and upload to Apache user profile + +Since the public key server has no verifying mechanism, anyone can upload the public key in your name, so there is no way to guarantee the reliability of the public key on the server. Usually, you can publish a public key fingerprint on your website and let others check the downloaded public key. + +Execute the following command to view the fingerprint. +``` +gpg --fingerprint lorinlee # user id +``` + +output: +``` +/Users/lilei/.gnupg/pubring.kbx +---------------------------------- +pub rsa4096 2021-10-17 [SC] + C30F 211F 0718 9425 8497 F463 92E1 8A11 B658 5834 +uid [ultimate] LorinLee (lorinlee's key) +sub rsa4096 2021-10-17 [E] +``` + +Paste the above fingerprint `C30F 211F 0718 9425 8497 F463 92E1 8A11 B658 5834` into the `OpenPGP Public Key Primary Fingerprint:` field of your Apache user information on https://id.apache.org. + +## Make the release + +### 1. Create release branch + +If you are releasing a new MAJOR/MINOR version, like `1.0.0`, you need to create a new branch `release-1.0` from master. + +If you are releasing a new PATCH version from existing MINOR version, like `1.0.1`, you only need to modify the existing `release-1.0` branch and add the content to be released. + +The code modification during the release process are performed on the release branch (such as `release-1.0`). After the release is complete, please merge the release branch back into the master branch. + +### 2. Update the `NOTICE` file + +Check and update the YEAR field of the `NOTIEC` file. + +### 3. Update version in source code + +#### Update `RELEASE_VERSION` file + +Edit the `RELEASE_VERSION` file in the project root directory, update the version number, and submit it to the code repository. For example, the `1.0.0` version of the file is: + +``` +1.0.0 +``` + +#### Update the `CMakeLists.txt` file + +Edit the `CMakeLists.txt` file in the project root directory, update the version number, and submit it to the code repository. For example: + +``` +set(BRPC_VERSION 1.0.0) +``` + +#### Update the `package/rpm/brpc.spec` file + +Edit the `/package/rpm/brpc.spec` file in the project root directory, update the version number, and submit it to the code repository. For example: + +``` +Version: 1.0.0 +``` + +#### Update the `MODULE.bazel` file + +Edit the `MODULE.bazel` file in the project root directory, update the version number, and submit it to the code repository. For example: + +``` +# in MODULE.bazel +module( + ... + version = '1.0.0', + ... +) +``` + +### 4. Create releasing tag + +Pull the release branch to tag, for example: + +```bash +git clone -b release-1.0 git@github.com:apache/brpc.git ~/brpc + +cd ~/brpc + +git tag -a 1.0.0 -m "release 1.0.0" + +git push origin --tags +``` + +### 5. Create releasing package + +```bash +git archive --format=tar 1.0.0 --prefix=apache-brpc-1.0.0-src/ | gzip > apache-brpc-1.0.0-src.tar.gz +``` +or +```bash +git archive --format=tar.gz 1.0.0 --prefix=apache-brpc-1.0.0-src/ --output=apache-brpc-1.0.0-src.tar.gz +``` + +### 6. Generate GPG signature + +```bash +gpg -u lorinlee@apache.org --armor --output apache-brpc-1.0.0-src.tar.gz.asc --detach-sign apache-brpc-1.0.0-src.tar.gz + +gpg --verify apache-brpc-1.0.0-src.tar.gz.asc apache-brpc-1.0.0-src.tar.gz +``` + +### 7. Generate SHA512 checksum + +```bash +sha512sum apache-brpc-1.0.0-src.tar.gz > apache-brpc-1.0.0-src.tar.gz.sha512 + +sha512sum --check apache-brpc-1.0.0-src.tar.gz.sha512 +``` + +## Publish to Apache SVN repository + +### 1. Checkout dist/dev/brpc directory + +If there is no local working directory, create a local working directory first. Checkout the Apache SVN repository, username needs to use your own Apache LDAP username: + +```bash +mkdir -p ~/brpc_svn/dev/ + +cd ~/brpc_svn/dev/ + +svn --username=lorinlee co https://dist.apache.org/repos/dist/dev/brpc/ + +cd ~/brpc_svn/dev/brpc +``` + +### 2. Add GPG public key + +A new release manager must add the key into KEYS file for the first time. + +``` +(gpg --list-sigs lorinlee && gpg -a --export lorinlee) >> KEYS +``` + +Note: if you have more than one keys that use the name, you should explicitly specify the key by email or fingerprint to export. + +By email +```bash +(gpg --list-sigs lorinlee@apache.org && gpg -a --export lorinlee@apache.org) >> KEYS +``` +By fingerprint: +```bash +(gpg --list-sigs C30F211F071894258497F46392E18A11B6585834 && gpg -a --export C30F211F071894258497F46392E18A11B6585834) >> KEYS +``` + +### 3. Add the releasing package to SVN directory + +```bash +mkdir -p ~/brpc_svn/dev/brpc/1.0.0 + +cd ~/brpc_svn/dev/brpc/1.0.0 + +cp ~/brpc/apache-brpc-1.0.0-src.tar.gz ~/brpc_svn/dev/brpc/1.0.0 + +cp ~/brpc/apache-brpc-1.0.0-src.tar.gz.asc ~/brpc_svn/dev/brpc/1.0.0 + +cp ~/brpc/apache-brpc-1.0.0-src.tar.gz.sha512 ~/brpc_svn/dev/brpc/1.0.0 +``` + +### 4. Submit SVN + +Return to the parent directory and use the Apache LDAP account to submit SVN + +```bash +cd ~/brpc_svn/dev/brpc + +svn add * + +svn --username=lorinlee commit -m "release 1.0.0" +``` + +## Verify release +```bash +cd ~/brpc_svn/dev/brpc/1.0.0 +``` +### 1. Verify SHA512 checksum + +```bash +sha512sum --check apache-brpc-1.0.0-src.tar.gz.sha512 +``` + +### 2. Verify GPG signature + +First import the publisher's public key. Import KEYS from the svn repository to the local. (The person who releases the version does not need to import it again. The person who verify needs to import it.) + +```bash +curl https://dist.apache.org/repos/dist/dev/brpc/KEYS >> KEYS + +gpg --import KEYS +``` + +Trust the signature of publisher, execute the following command using the publisher's user name + +```bash +gpg --edit-key lorinlee +``` + +output: +``` +gpg (GnuPG) 2.3.1; Copyright (C) 2021 Free Software Foundation, Inc. +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. + +Secret key is available. + +gpg> trust + +Please decide how far you trust this user to correctly verify other users' keys +(by looking at passports, checking fingerprints from different sources, etc.) + + 1 = I don't know or won't say + 2 = I do NOT trust + 3 = I trust marginally + 4 = I trust fully + 5 = I trust ultimately + m = back to the main menu + +Your decision? 5 +Do you really want to set this key to ultimate trust? (y/N) y + +gpg> save +``` + +Then verify the GPG signature: +``` +gpg --verify apache-brpc-1.0.0-src.tar.gz.asc apache-brpc-1.0.0-src.tar.gz +``` + +### 3. Check release content + +#### 1. Compare the difference between the candidate source package and the package download through github tag page + +Should use the `tar.gz` format tarball. + +```bash +curl -Lo tag-1.0.0.tar.gz https://github.com/apache/brpc/archive/refs/tags/1.0.0.tar.gz + +tar xvzf tag-1.0.0.tar.gz + +tar xvzf apache-brpc-1.0.0-src.tar.gz + +diff -r brpc-1.0.0 apache-brpc-1.0.0-src +``` + +#### 2. Check file content + +- Check whether the source code package contains unnecessary files, which makes the tarball too large +- LICENSE and NOTICE files exist +- The year in the NOTICE file is correct +- Only text files exist, no binary files exist +- All files have an ASF license at the beginning +- Source code can be compiled correctly, and the unit test can pass +- Check whether there are redundant files or folders, such as empty folders +- Check for third-party dependency licenses: + - Third party dependency license compatibility + - All licenses of third-party dependency are declared in the LICENSE file + - The complete version of the dependency license is in the license directory + - If third-party dependency have the Apache license and have NOTICE files, these NOTICE files also need to be added to the releasing NOTICE file + +## Vote in the Apache bRPC community + +This stage will cost 3+ days. + +### 1. Vote stage + +1. Send a voting email to `dev@brpc.apache.org`. PMC needs to check the correctness of the version according to the document before voting. After at least 72 hours and 3 +1 PMC member votes, you can move to the next stage. +2. Announce the voting result and send the voting result to dev@brpc.apache.org. + +### 2. Vote email template + +1. Apache bRPC community vote email template + +Title: +``` +[VOTE] Release Apache bRPC 1.0.0 +``` + +Content: + +Note: `Release Commit ID` fills in the commit ID of the last commit of the current release branch. + +``` +Hi Apache bRPC Community, + +This is a call for vote to release Apache bRPC version +1.0.0 + +[Release Note] +- xxx + +The release candidates: +https://dist.apache.org/repos/dist/dev/brpc/1.0.0/ + +Git tag for the release: +https://github.com/apache/brpc/releases/tag/1.0.0 + +Release Commit ID: +https://github.com/apache/brpc/commit/xxx + +Keys to verify the Release Candidate: +https://dist.apache.org/repos/dist/dev/brpc/KEYS + +The vote will be open for at least 72 hours or until the necessary number of +votes are reached. + +Please vote accordingly: +[ ] +1 approve +[ ] +0 no opinion +[ ] -1 disapprove with the reason + +PMC vote is +1 binding, all others are +1 non-binding. + +Checklist for reference: +[ ] Download links are valid. +[ ] Checksums and PGP signatures are valid. +[ ] Source code distributions have correct names matching the current +release. +[ ] LICENSE and NOTICE files are correct for each brpc repo. +[ ] All files have license headers if necessary. +[ ] No compiled archives bundled in source archive. + +Regards, +LorinLee +``` + +2. Apache bRPC community announcement of vote result template + +Title: +``` +[Result] [VOTE] Release Apache bRPC 1.0.0 +``` + +Content: +``` +Hi all, + +The vote to release Apache bRPC 1.0.0 has passed. + +The vote PASSED with 3 binding +1, 3 non binding +1 and no -1 votes: + +Binding votes: +- xxx +- yyy +- zzz + +Non-binding votes: +- aaa +- bbb +- ccc + +Vote thread: xxx (vote email link in https://lists.apache.org/) + +Thank you to all the above members to help us to verify and vote for +the 1.0.0 release. I will process to publish the release and send ANNOUNCE. + +Regards, +LorinLee +``` + +### 3. Vote not passed + +If the community vote is not passed, please modify the code of the release branch, package and vote again. + + +## Finish the release + +### 1. Move the release package from Apache SVN directory dist/dev to dist/release + +Note: PMC members only, and it will also update the `KEYS` if changed. + +``` +svn mv https://dist.apache.org/repos/dist/dev/brpc/1.0.0 https://dist.apache.org/repos/dist/release/brpc/1.0.0 -m "release brpc 1.0.0" +``` + +### 2. Create github release + +1. On the [GitHub Releases page](https://github.com/apache/brpc/tags) Click the corresponding version of to create a new Release +2. Edit the version number and version description, and click `Publish release` + +### 3. Update download page + +After waiting and confirming that the new release is synchronized to the Apache image, update the following page: by change the code in . Please update both Chinese and English. + +The download links of GPG signature files and hash check files should use this prefix: `https://downloads.apache.org/brpc/` + +The download link of the code package should use this prefix: `https://dlcdn.apache.org/brpc/` + +### 4. Send email to announce release finished + +Send mail to `dev@brpc.apache.org` and `announce@apache.org` to announce the completion of release. + +Note: The email account must use **personal apache email**, and the email content must be **plain text format** ("plain text mode" can be selected in gmail). And email to `announce@apache.org` mail group will be delivered after manual review. Please wait patiently after sending the email, and it will be passed within one day. + +See https://shenyu.apache.org/community/use-apache-email for more details to setup a personal apache account email, the `SMTP` server should be `mail-relay.apache.org`. + +The announcement email template: + +Title: +``` +[ANNOUNCE] Apache bRPC 1.0.0 released +``` + +Content: + +Note: `Brief notes of this release` It is only necessary to list the main changes of this release, without corresponding contributors and PR numbers. It is recommended to refer to the previous announcement email. + +``` +Hi all, + +The Apache bRPC community is glad to announce the new release +of Apache bRPC 1.0.0. + +Apache bRPC is an Industrial-grade RPC framework using C++ Language, +which is often used in high performance systems such as Search, Storage, +Machine learning, Advertisement, Recommendation etc. + +Brief notes of this release: +- xxx +- yyy +- zzz + +More details regarding Apache brpc can be found at: +https://brpc.apache.org/ + +The release is available for download at: +https://brpc.apache.org/download/ + +The release notes can be found here: +https://github.com/apache/brpc/releases/tag/1.0.0 + +Website: https://brpc.apache.org/ + +Apache bRPC Resources: +- Issue: https://github.com/apache/brpc/issues/ +- Mailing list: dev@brpc.apache.org +- Documents: https://brpc.apache.org/docs/ + +We would like to thank all contributors of the Apache bRPC community +who made this release possible! + + +Best Regards, +Apache bRPC Community +``` + +### 5. Publish WeChat official account announcement + +Reference . + +### 6. Update master branch + +After the release is completed, merge the release branch into the `master` branch. diff --git a/community/release_schedule.md b/community/release_schedule.md new file mode 100644 index 0000000..1cf4ee2 --- /dev/null +++ b/community/release_schedule.md @@ -0,0 +1,27 @@ +# bRPC 发版日程 + +|版本号|发版时间|发版人| +|-|-|-| +|1.0.0|2021-10-31|李磊| +|1.1.0|2022-03-29|王伟冰| +|1.2.0|2022-07-11|刘帅| +|1.3.0|2022-10-25|胡希国| +|1.4.0|2023-02-07|王晓峰| +|1.5.0|2023-04-xx|胡希国| +|1.6.0|2023-07-xx|陈光明| +|1.7.0|2023-10-xx|李磊| +|1.8.0|2024-01-xx|王伟冰| +|1.9.0|2024-04-xx|刘帅| +|1.10.0|2024-07-xx|王晓峰| +|1.11.0|2024-10-xx|胡希国| +|1.12.0|2025-01-xx|陈光明| +|1.13.0|2025-04-xx|李磊| +|1.14.0|2025-07-xx|王伟冰| +|1.15.0|2025-10-xx|刘帅| +|1.16.0|2026-01-xx|王晓峰| +|1.17.0|2026-05-xx|胡希国| +|1.18.0|2026-09-xx|陈光明| +|1.19.0|2027-01-xx|李磊| +|1.20.0|2027-05-xx|王伟冰| +|1.21.0|2027-09-xx|刘帅| +|1.22.0|2028-01-xx|王晓峰| diff --git a/community/releasecheck.md b/community/releasecheck.md new file mode 100644 index 0000000..c780b66 --- /dev/null +++ b/community/releasecheck.md @@ -0,0 +1,54 @@ +# brpc 发版时候的 Check List + +## 文档背景: + +当 Release Manager 在 dev 邮件群中发起发布投票的时候,需要 PMC 成员对发版相关信息进行检查,如果检查通过则在邮件群中回复通过并附上检查结果。本文档就是各个检查项。 + +## Checklist 资料来源 + +根据 Incubator PMC Chair Justin 在 ApacheCon North America 2019 的分享 +https://training.apache.org/topics/ApacheWay/NavigatingASFIncubator/index.html + +![image](./releasecheck.png) + + +## 常见的问题导致 -1 + +![image](./releasefail.png) + +## Vote 时候的检查项 + +毕业后检查时不再需要 `DISCLAIMER` 以及 `incubating` + +1. ~~Incubating in name 即下载包的文件名是否带上了incubating~~ +2. LICENSE and NOTICE are good + - LICENSE 是否符合 Apache 的要求 + - ASF 允许的 LICENSE:Category A(Apache V2, BSD 3, MIT etc) + - ASF 建议不放到源码中的 LICENSE:Category B(EPL, MPL, CDDL, etc) + - ASF 不允许的 LICENSE:Category X(GPL, LGPL, CC Non commercial, etc) + - NOTICE 是否符合 Apache 的要求 +3. Signatures and hashes correct +4. All ASF files have ASF headers +5. No unexpected binary files +6. Must have an incubating disclaimer + - ~~Repo 根目录下应该有 DISCLAIMER 文件~~ + - 内容是 Apache 统一的内容 + + +## Vote 时候的常用回复 + +> +> +1 (binding) +> +> I checked: +> - ~~incubating in name~~ +> - LICENSE and NOTICE are good +> - signatures and hashes correct +> - All ASF files have ASF headers +> - no unexpected binary files +> + +## 注意: + +1. 不要回复简单的 +1,需要加上执行的几个检查项和检查结果 +2. 给出 -1 的时候,同样需要给出明确的理由 diff --git a/community/releasecheck.png b/community/releasecheck.png new file mode 100644 index 0000000..9bc1141 Binary files /dev/null and b/community/releasecheck.png differ diff --git a/community/releasefail.png b/community/releasefail.png new file mode 100644 index 0000000..580f7d8 Binary files /dev/null and b/community/releasefail.png differ diff --git a/config.h.in b/config.h.in new file mode 100644 index 0000000..4f26e57 --- /dev/null +++ b/config.h.in @@ -0,0 +1,24 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BUTIL_CONFIG_H +#define BUTIL_CONFIG_H + +#ifdef BRPC_WITH_GLOG +#undef BRPC_WITH_GLOG +#endif +#cmakedefine BRPC_WITH_GLOG @WITH_GLOG_VAL@ + +#endif // BUTIL_CONFIG_H diff --git a/config_brpc.sh b/config_brpc.sh new file mode 100755 index 0000000..1c05942 --- /dev/null +++ b/config_brpc.sh @@ -0,0 +1,673 @@ +#!/usr/bin/env sh + +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +BOLD='\033[1m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +print_info() { + printf "${CYAN}[INFO]${NC} %s\n" "$1" +} +print_success() { + printf "${GREEN}[OK]${NC} %s\n" "$1" +} +print_step() { + printf "${BOLD}==> %s${NC}\n" "$1" +} + +SYSTEM=$(uname -s) +if [ "$SYSTEM" = "Darwin" ]; then + if [ -z "$BASH" ] || [ "$BASH" = "/bin/sh" ] ; then + ECHO=echo + else + ECHO='echo -e' + fi + SO=dylib + LDD="otool -L" + if [ "$(getopt -V)" = " --" ]; then + >&2 $ECHO "gnu-getopt must be installed and used" + exit 1 + fi +else + if [ -z "$BASH" ]; then + ECHO=echo + else + ECHO='echo -e' + fi + SO=so + LDD=ldd +fi + +TEMP=`getopt -o v: --long headers:,libs:,cc:,cxx:,with-glog,with-thrift,with-rdma,with-mesalink,with-bthread-tracer,with-debug-bthread-sche-safety,with-debug-lock,with-asan,with-riscv-zvbc,with-riscv-zbc,nodebugsymbols,werror -n 'config_brpc' -- "$@"` +WITH_GLOG=0 +WITH_THRIFT=0 +WITH_RDMA=0 +WITH_MESALINK=0 +WITH_BTHREAD_TRACER=0 +WITH_ASAN=0 +WITH_RISCV_ZVBC=0 +WITH_RISCV_ZBC=0 +BRPC_DEBUG_BTHREAD_SCHE_SAFETY=0 +DEBUGSYMBOLS=-g +WERROR= +BRPC_DEBUG_LOCK=0 + +if [ $? != 0 ] ; then >&2 $ECHO "Terminating..."; exit 1 ; fi + +# Note the quotes around `$TEMP': they are essential! +eval set -- "$TEMP" + +if [ "$SYSTEM" = "Darwin" ]; then + REALPATH=realpath +else + REALPATH="readlink -f" +fi + +# Convert to abspath always so that generated mk is include-able from everywhere +while true; do + case "$1" in + --headers ) HDRS_IN="$(${REALPATH} $2)"; shift 2 ;; + --libs ) LIBS_IN="$(${REALPATH} $2)"; shift 2 ;; + --cc ) CC=$2; shift 2 ;; + --cxx ) CXX=$2; shift 2 ;; + --with-glog ) WITH_GLOG=1; shift 1 ;; + --with-thrift) WITH_THRIFT=1; shift 1 ;; + --with-rdma) WITH_RDMA=1; shift 1 ;; + --with-mesalink) WITH_MESALINK=1; shift 1 ;; + --with-bthread-tracer) WITH_BTHREAD_TRACER=1; shift 1 ;; + --with-debug-bthread-sche-safety ) BRPC_DEBUG_BTHREAD_SCHE_SAFETY=1; shift 1 ;; + --with-debug-lock ) BRPC_DEBUG_LOCK=1; shift 1 ;; + --with-asan) WITH_ASAN=1; shift 1 ;; + --with-riscv-zvbc) WITH_RISCV_ZVBC=1; shift 1 ;; + --with-riscv-zbc) WITH_RISCV_ZBC=1; shift 1 ;; + --nodebugsymbols ) DEBUGSYMBOLS=; shift 1 ;; + --werror ) WERROR=-Werror; shift 1 ;; + -- ) shift; break ;; + * ) break ;; + esac +done + +print_step "Configuring brpc (${SYSTEM})" +print_info "Headers path: ${HDRS_IN}" +print_info "Libs path: ${LIBS_IN}" + +if [ -z "$CC" ]; then + if [ ! -z "$CXX" ]; then + >&2 $ECHO "--cc and --cxx must be both set or unset" + exit 1 + fi + CC=gcc + CXX=g++ + if [ "$SYSTEM" = "Darwin" ]; then + CC=clang + CXX=clang++ + fi +elif [ -z "$CXX" ]; then + >&2 $ECHO "--cc and --cxx must be both set or unset" + exit 1 +fi + +print_info "CC=$CC, CXX=$CXX" + +GCC_VERSION=$(CXX=$CXX tools/print_gcc_version.sh) +if [ $GCC_VERSION -gt 0 ] && [ $GCC_VERSION -lt 40800 ]; then + >&2 $ECHO "GCC is too old, please install a newer version supporting C++11" + exit 1 +fi + +if [ -z "$HDRS_IN" ] || [ -z "$LIBS_IN" ]; then + >&2 $ECHO "config_brpc: --headers=HDRPATHS --libs=LIBPATHS must be specified" + exit 1 +fi + +find_dir_of_lib() { + local lib=$(find ${LIBS_IN} -name "lib${1}.a" -o -name "lib${1}.$SO" 2>/dev/null | head -n1) + if [ ! -z "$lib" ]; then + dirname $lib + fi +} +find_dir_of_lib_or_die() { + local dir=$(find_dir_of_lib $1) + if [ -z "$dir" ]; then + >&2 $ECHO "Fail to find $1 from --libs" + exit 1 + else + $ECHO $dir + fi +} + +find_bin() { + TARGET_BIN=$(find -L ${LIBS_IN} -type f -name "$1" 2>/dev/null | head -n1) + if [ ! -z "$TARGET_BIN" ]; then + $ECHO $TARGET_BIN + else + which "$1" 2>/dev/null + fi +} +find_bin_or_die() { + TARGET_BIN=$(find_bin "$1") + if [ ! -z "$TARGET_BIN" ]; then + $ECHO $TARGET_BIN + else + >&2 $ECHO "Fail to find $1" + exit 1 + fi +} + +find_dir_of_header() { + find -L ${HDRS_IN} -path "*/$1" | head -n1 | sed "s|$1||g" +} + +find_dir_of_header_excluding() { + find -L ${HDRS_IN} -path "*/$1" | grep -v "$2\$" | head -n1 | sed "s|$1||g" +} + +find_dir_of_header_or_die() { + if [ -z "$2" ]; then + local dir=$(find_dir_of_header $1) + else + local dir=$(find_dir_of_header_excluding $1 $2) + fi + if [ -z "$dir" ]; then + >&2 $ECHO "Fail to find $1 from --headers" + exit 1 + fi + $ECHO $dir +} + +if [ "$SYSTEM" = "Darwin" ]; then + if [ -d "/usr/local/opt/openssl" ]; then + LIBS_IN="/usr/local/opt/openssl/lib $LIBS_IN" + HDRS_IN="/usr/local/opt/openssl/include $HDRS_IN" + elif [ -d "/opt/homebrew/Cellar" ]; then + LIBS_IN="/opt/homebrew/Cellar $LIBS_IN" + HDRS_IN="/opt/homebrew/Cellar $HDRS_IN" + fi +fi + +print_step "Checking dependencies" + +# User specified path of openssl, if not given it's empty +OPENSSL_LIB=$(find_dir_of_lib ssl) +# Inconvenient to check these headers in baidu-internal +#PTHREAD_HDR=$(find_dir_of_header_or_die pthread.h) +OPENSSL_HDR=$(find_dir_of_header_or_die openssl/ssl.h mesalink/openssl/ssl.h) +print_success "Found openssl: lib=${OPENSSL_LIB:-system}, hdr=${OPENSSL_HDR}" + +if [ $WITH_MESALINK != 0 ]; then + MESALINK_HDR=$(find_dir_of_header_or_die mesalink/openssl/ssl.h) + OPENSSL_HDR="$OPENSSL_HDR\n$MESALINK_HDR" +fi + +STATIC_LINKINGS= +DYNAMIC_LINKINGS="-lpthread -lssl -lcrypto -ldl -lz" + +if [ $WITH_MESALINK != 0 ]; then + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -lmesalink" +fi + +if [ "$SYSTEM" = "Linux" ]; then + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -lrt" +fi +if [ "$SYSTEM" = "Darwin" ]; then + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -framework CoreFoundation" + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -framework CoreGraphics" + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -framework CoreData" + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -framework CoreText" + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -framework Security" + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -framework Foundation" + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -Wl,-U,_MallocExtension_ReleaseFreeMemory" + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -Wl,-U,_ProfilerStart" + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -Wl,-U,_ProfilerStop" + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -Wl,-U,__Z13GetStackTracePPvii" + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -Wl,-U,_RegisterThriftProtocol" + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -Wl,-U,_mallctl" + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -Wl,-U,_malloc_stats_print" +fi +append_linking() { + if [ -f $1/lib${2}.a ]; then + if [ "$SYSTEM" = "Darwin" ]; then + # *.a must be explicitly specified in clang + STATIC_LINKINGS="$STATIC_LINKINGS $1/lib${2}.a" + else + STATIC_LINKINGS="$STATIC_LINKINGS -l$2" + fi + export STATICALLY_LINKED_$2=1 + else + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -l$2" + export STATICALLY_LINKED_$2=0 + fi +} + +GFLAGS_LIB=$(find_dir_of_lib_or_die gflags) +append_linking $GFLAGS_LIB gflags +print_success "Found gflags: $GFLAGS_LIB" + +PROTOBUF_LIB=$(find_dir_of_lib_or_die protobuf) +append_linking $PROTOBUF_LIB protobuf +print_success "Found protobuf: $PROTOBUF_LIB" + +LEVELDB_LIB=$(find_dir_of_lib_or_die leveldb) +print_success "Found leveldb: $LEVELDB_LIB" +# required by leveldb +if [ -f $LEVELDB_LIB/libleveldb.a ]; then + if [ -f $LEVELDB_LIB/libleveldb.$SO ]; then + if $LDD $LEVELDB_LIB/libleveldb.$SO | grep -q libsnappy; then + SNAPPY_LIB=$(find_dir_of_lib snappy) + REQUIRE_SNAPPY="yes" + fi + fi + if [ -z "$REQUIRE_SNAPPY" ]; then + if [ "$SYSTEM" = "Darwin" ]; then + STATIC_LINKINGS="$STATIC_LINKINGS $LEVELDB_LIB/libleveldb.a" + else + STATIC_LINKINGS="$STATIC_LINKINGS -lleveldb" + fi + elif [ -f $SNAPPY_LIB/libsnappy.a ]; then + if [ "$SYSTEM" = "Darwin" ]; then + STATIC_LINKINGS="$STATIC_LINKINGS $LEVELDB_LIB/libleveldb.a $SNAPPY_LIB/libsnappy.a" + else + STATIC_LINKINGS="$STATIC_LINKINGS -lleveldb -lsnappy" + fi + else + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -lleveldb" + fi +else + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -lleveldb" +fi + +PROTOC=$(find_bin_or_die protoc) +print_success "Found protoc: $PROTOC" + +GFLAGS_HDR=$(find_dir_of_header_or_die gflags/gflags.h) + +PROTOBUF_HDR=$(find_dir_of_header_or_die google/protobuf/message.h) +PROTOBUF_VERSION=$(grep '#define GOOGLE_PROTOBUF_VERSION [0-9]\+' $PROTOBUF_HDR/google/protobuf/stubs/common.h | awk '{print $3}') +if [ "$PROTOBUF_VERSION" -ge 4022000 ]; then + # from v22, utf8_validity should be explicitly linked + # https://github.com/protocolbuffers/protobuf/blob/a847a8dc4ba1d99e7ba917146c84438b4de7d085/cmake/libprotobuf.cmake#L47 + UTF8_VALIDITY_LIB=$(find_dir_of_lib utf8_validity) + append_linking "$UTF8_VALIDITY_LIB" utf8_validity + + ABSL_HDR=$(find_dir_of_header_or_die absl/base/config.h) + ABSL_LIB=$(find_dir_of_lib_or_die absl_strings) + ABSL_TARGET_NAMES=" + absl_bad_optional_access + absl_bad_variant_access + absl_base + absl_city + absl_civil_time + absl_cord + absl_cord_internal + absl_cordz_functions + absl_cordz_handle + absl_cordz_info + absl_crc32c + absl_crc_cord_state + absl_crc_cpu_detect + absl_crc_internal + absl_debugging_internal + absl_demangle_internal + absl_die_if_null + absl_examine_stack + absl_exponential_biased + absl_flags + absl_flags_commandlineflag + absl_flags_commandlineflag_internal + absl_flags_config + absl_flags_internal + absl_flags_marshalling + absl_flags_private_handle_accessor + absl_flags_program_name + absl_flags_reflection + absl_graphcycles_internal + absl_hash + absl_hashtablez_sampler + absl_int128 + absl_kernel_timeout_internal + absl_leak_check + absl_log_entry + absl_log_globals + absl_log_initialize + absl_log_internal_check_op + absl_log_internal_conditions + absl_log_internal_format + absl_log_internal_globals + absl_log_internal_log_sink_set + absl_log_internal_message + absl_log_internal_nullguard + absl_log_internal_proto + absl_log_severity + absl_log_sink + absl_low_level_hash + absl_malloc_internal + absl_raw_hash_set + absl_raw_logging_internal + absl_spinlock_wait + absl_stacktrace + absl_status + absl_statusor + absl_str_format_internal + absl_strerror + absl_string_view + absl_strings + absl_strings_internal + absl_symbolize + absl_synchronization + absl_throw_delegate + absl_time + absl_time_zone + " + for i in $ABSL_TARGET_NAMES; do + # ignore interface targets + if [ -n "$(find_dir_of_lib $i)" ]; then + append_linking "$ABSL_LIB" "$i" + fi + done + CXXFLAGS="-std=c++17" + print_success "Found protobuf version $PROTOBUF_VERSION (>= v22, using C++17 with abseil)" +else + CXXFLAGS="-std=c++0x" + print_success "Found protobuf version $PROTOBUF_VERSION" +fi + +CPPFLAGS= + +if [ $WITH_ASAN != 0 ]; then + CPPFLAGS="${CPPFLAGS} -fsanitize=address" + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -fsanitize=address" +fi + +LEVELDB_HDR=$(find_dir_of_header_or_die leveldb/db.h) + +if [ $WITH_BTHREAD_TRACER != 0 ]; then + if [ "$SYSTEM" != "Linux" ] || [ "$(uname -m)" != "x86_64" ]; then + >&2 $ECHO "bthread tracer is only supported on Linux x86_64 platform" + exit 1 + fi + LIBUNWIND_HDR=$(find_dir_of_header_or_die libunwind.h) + LIBUNWIND_LIB=$(find_dir_of_lib_or_die unwind) + ABSL_HDR=$(find_dir_of_header_or_die absl/base/config.h) + ABSL_LIB=$(find_dir_of_lib_or_die absl_symbolize) + + CPPFLAGS="${CPPFLAGS} -DBRPC_BTHREAD_TRACER" + + if [ -f "$LIBUNWIND_LIB/libunwind.$SO" ]; then + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -lunwind -lunwind-x86_64" + else + STATIC_LINKINGS="$STATIC_LINKINGS -lunwind -lunwind-x86_64" + fi + if [ -f "$ABSL_LIB/libabsl_base.$SO" ]; then + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -labsl_stacktrace -labsl_symbolize -labsl_debugging_internal -labsl_demangle_internal -labsl_malloc_internal -labsl_raw_logging_internal -labsl_spinlock_wait -labsl_base" + else + STATIC_LINKINGS="$STATIC_LINKINGS -labsl_stacktrace -labsl_symbolize -labsl_debugging_internal -labsl_demangle_internal -labsl_malloc_internal -labsl_raw_logging_internal -labsl_spinlock_wait -labsl_base" + fi +fi + +HDRS=$($ECHO "$LIBUNWIND_HDR\n$GFLAGS_HDR\n$PROTOBUF_HDR\n$ABSL_HDR\n$LEVELDB_HDR\n$OPENSSL_HDR" | sort | uniq) +LIBS=$($ECHO "$LIBUNWIND_LIB\n$GFLAGS_LIB\n$PROTOBUF_LIB\n$ABSL_LIB\n$LEVELDB_LIB\n$OPENSSL_LIB\n$SNAPPY_LIB" | sort | uniq) + +absent_in_the_list() { + TMP=`$ECHO "$1\n$2" | sort | uniq` + if [ "${TMP}" = "$2" ]; then + return 1 + fi + return 0 +} + +OUTPUT_CONTENT="# Generated by config_brpc.sh, don't modify manually" +append_to_output() { + OUTPUT_CONTENT="${OUTPUT_CONTENT}\n$*" +} +# $1: libname, $2: indentation +append_to_output_headers() { + if absent_in_the_list "$1" "$HDRS"; then + append_to_output "${2}HDRS+=$1" + HDRS=`$ECHO "${HDRS}\n$1" | sort | uniq` + fi +} +# $1: libname, $2: indentation +append_to_output_libs() { + if absent_in_the_list "$1" "$LIBS"; then + append_to_output "${2}LIBS+=$1" + LIBS=`$ECHO "${LIBS}\n$1" | sort | uniq` + fi +} +# $1: libdir, $2: libname, $3: indentation +append_to_output_linkings() { + if [ -f $1/lib$2.a ]; then + append_to_output_libs $1 $3 + if [ "$SYSTEM" = "Darwin" ]; then + append_to_output "${3}STATIC_LINKINGS+=$1/lib$2.a" + else + append_to_output "${3}STATIC_LINKINGS+=-l$2" + fi + export STATICALLY_LINKED_$2=1 + else + append_to_output_libs $1 $3 + append_to_output "${3}DYNAMIC_LINKINGS+=-l$2" + export STATICALLY_LINKED_$2=0 + fi +} + +#can't use \n in texts because sh does not support -e +append_to_output "SYSTEM=$SYSTEM" +append_to_output "HDRS=$($ECHO $HDRS)" +append_to_output "LIBS=$($ECHO $LIBS)" +append_to_output "PROTOC=$PROTOC" +append_to_output "PROTOBUF_HDR=$PROTOBUF_HDR" +append_to_output "CC=$CC" +append_to_output "CXX=$CXX" +append_to_output "GCC_VERSION=$GCC_VERSION" +append_to_output "STATIC_LINKINGS=$STATIC_LINKINGS" +append_to_output "DYNAMIC_LINKINGS=$DYNAMIC_LINKINGS" + +# CPP means C PreProcessing, not C PlusPlus +CPPFLAGS="${CPPFLAGS} -DBRPC_WITH_GLOG=$WITH_GLOG -DBRPC_DEBUG_BTHREAD_SCHE_SAFETY=$BRPC_DEBUG_BTHREAD_SCHE_SAFETY -DBRPC_DEBUG_LOCK=$BRPC_DEBUG_LOCK" + +# Avoid over-optimizations of TLS variables by GCC>=4.8 +# See: https://github.com/apache/brpc/issues/1693 +CPPFLAGS="${CPPFLAGS} -D__const__=__unused__" + +if [ ! -z "$DEBUGSYMBOLS" ]; then + CPPFLAGS="${CPPFLAGS} $DEBUGSYMBOLS" +fi +if [ ! -z "$WERROR" ]; then + CPPFLAGS="${CPPFLAGS} $WERROR" +fi +if [ "$SYSTEM" = "Darwin" ]; then + CPPFLAGS="${CPPFLAGS} -Wno-deprecated-declarations -Wno-inconsistent-missing-override" + version=`sw_vers -productVersion | awk -F '.' '{print $1 "." $2}'` + if [[ `echo "$version<10.12" | bc -l` == 1 ]]; then + CPPFLAGS="${CPPFLAGS} -DNO_CLOCK_GETTIME_IN_MAC" + fi +fi + +if [ $WITH_THRIFT != 0 ]; then + THRIFT_LIB=$(find_dir_of_lib_or_die thriftnb) + THRIFT_HDR=$(find_dir_of_header_or_die thrift/Thrift.h) + append_to_output_libs "$THRIFT_LIB" + append_to_output_headers "$THRIFT_HDR" + + CPPFLAGS="${CPPFLAGS} -DENABLE_THRIFT_FRAMED_PROTOCOL" + + if [ -f "$THRIFT_LIB/libthriftnb.$SO" ]; then + append_to_output "DYNAMIC_LINKINGS+=-lthriftnb -levent -lthrift" + else + append_to_output "STATIC_LINKINGS+=-lthriftnb" + fi + # get thrift version + thrift_version=$(thrift --version | awk '{print $3}') + major=$(echo "$thrift_version" | awk -F '.' '{print $1}') + minor=$(echo "$thrift_version" | awk -F '.' '{print $2}') + if [ $((major)) -eq 0 -a $((minor)) -lt 11 ]; then + CPPFLAGS="${CPPFLAGS} -D_THRIFT_VERSION_LOWER_THAN_0_11_0_" + echo "less" + else + echo "greater" + fi +fi + +if [ $WITH_RDMA != 0 ]; then + RDMA_LIB=$(find_dir_of_lib_or_die ibverbs) + RDMA_HDR=$(find_dir_of_header_or_die infiniband/verbs.h) + append_to_output_libs "$RDMA_LIB" + append_to_output_headers "$RDMA_HDR" + + CPPFLAGS="${CPPFLAGS} -DBRPC_WITH_RDMA" + + append_to_output "DYNAMIC_LINKINGS+=-libverbs" + append_to_output "WITH_RDMA=1" +fi + +if [ $WITH_MESALINK != 0 ]; then + CPPFLAGS="${CPPFLAGS} -DUSE_MESALINK" +fi + +append_to_output "CPPFLAGS=${CPPFLAGS}" +append_to_output "# without the flag, linux+arm64 may crash due to folding on TLS. +ifeq (\$(CC),gcc) + ifeq (\$(shell uname -p),aarch64) + CPPFLAGS+=-fno-gcse + endif +endif +" + +# RISC-V Zvbc/Zbc support +if [ "$(uname -m)" = "riscv64" ]; then + if [ $WITH_RISCV_ZVBC != 0 ]; then + CXXFLAGS="${CXXFLAGS} -march=rv64gcv_zbc_zvbc" + print_success "RISC-V Zvbc enabled: -march=rv64gcv_zbc_zvbc" + elif [ $WITH_RISCV_ZBC != 0 ]; then + CXXFLAGS="${CXXFLAGS} -march=rv64gc_zbc" + print_success "RISC-V Zbc enabled: -march=rv64gc_zbc" + fi +fi + +append_to_output "CXXFLAGS=${CXXFLAGS}" + +append_to_output "ifeq (\$(NEED_LIBPROTOC), 1)" +PROTOC_LIB=$(find $PROTOBUF_LIB -name "libprotoc.*" | head -n1) +if [ -z "$PROTOC_LIB" ]; then + append_to_output " \$(error \"Fail to find libprotoc\")" +else + # libprotobuf and libprotoc must be linked same statically or dynamically + # otherwise the bin will crash. + if [ $STATICALLY_LINKED_protobuf -gt 0 ]; then + if [ "$SYSTEM" = "Darwin" ]; then + append_to_output " STATIC_LINKINGS+=$(find $PROTOBUF_LIB -name "libprotoc.a" | head -n1)" + else + append_to_output " STATIC_LINKINGS+=-lprotoc" + fi + else + append_to_output " DYNAMIC_LINKINGS+=-lprotoc" + fi +fi +append_to_output "endif" + +OLD_HDRS=$HDRS +OLD_LIBS=$LIBS +append_to_output "ifeq (\$(NEED_GPERFTOOLS), 1)" +# required by cpu/heap profiler +TCMALLOC_LIB=$(find_dir_of_lib tcmalloc_and_profiler) +if [ -z "$TCMALLOC_LIB" ]; then + append_to_output " \$(error \"Fail to find gperftools\")" +elif [ $WITH_ASAN != 0 ]; then + append_to_output " \$(error \"gperftools is not compatible with ASAN\")" +else + append_to_output " CPPFLAGS+=-DBRPC_ENABLE_CPU_PROFILER" + append_to_output_libs "$TCMALLOC_LIB" " " + if [ -f $TCMALLOC_LIB/libtcmalloc.$SO ]; then + append_to_output " DYNAMIC_LINKINGS+=-ltcmalloc_and_profiler" + else + if [ "$SYSTEM" = "Darwin" ]; then + append_to_output " STATIC_LINKINGS+=$TCMALLOC_LIB/libtcmalloc.a" + else + append_to_output " STATIC_LINKINGS+=-ltcmalloc_and_profiler" + fi + fi +fi +append_to_output "endif" + +if [ $WITH_GLOG != 0 ]; then + GLOG_LIB=$(find_dir_of_lib_or_die glog) + GLOG_HDR=$(find_dir_of_header_or_die glog/logging.h windows/glog/logging.h) + append_to_output_libs "$GLOG_LIB" + append_to_output_headers "$GLOG_HDR" + if [ -f "$GLOG_LIB/libglog.$SO" ]; then + append_to_output "DYNAMIC_LINKINGS+=-lglog" + else + if [ "$SYSTEM" = "Darwin" ]; then + append_to_output "STATIC_LINKINGS+=$GLOG_LIB/libglog.a" + else + append_to_output "STATIC_LINKINGS+=-lglog" + fi + fi +fi + +# required by UT +#gtest +GTEST_LIB=$(find_dir_of_lib gtest) +HDRS=$OLD_HDRS +LIBS=$OLD_LIBS +append_to_output "ifeq (\$(NEED_GTEST), 1)" +if [ -z "$GTEST_LIB" ]; then + append_to_output " \$(error \"Fail to find gtest\")" +else + GTEST_HDR=$(find_dir_of_header_or_die gtest/gtest.h) + append_to_output_libs $GTEST_LIB " " + append_to_output_headers $GTEST_HDR " " + append_to_output_linkings $GTEST_LIB gtest " " + append_to_output_linkings $GTEST_LIB gtest_main " " +fi +append_to_output "endif" + +# generate src/butil/config.h +cat << EOF > src/butil/config.h +// This file is auto-generated by $(basename "$0"). DON'T edit it! +#ifndef BUTIL_CONFIG_H +#define BUTIL_CONFIG_H + +#ifdef BRPC_WITH_GLOG +#undef BRPC_WITH_GLOG +#endif +#define BRPC_WITH_GLOG $WITH_GLOG + +#endif // BUTIL_CONFIG_H +EOF + +print_step "Generating output files" + +# write to config.mk +$ECHO "$OUTPUT_CONTENT" > config.mk +print_success "Generated config.mk" +print_success "Generated src/butil/config.h" + +printf "\n" +print_step "Configuration complete" +print_info "Compiler: $CC / $CXX" +print_info "C++ std: $CXXFLAGS" +print_info "System: $SYSTEM" +if [ $WITH_GLOG -ne 0 ]; then print_info "With glog: yes"; fi +if [ $WITH_THRIFT -ne 0 ]; then print_info "With thrift: yes"; fi +if [ $WITH_RDMA -ne 0 ]; then print_info "With RDMA: yes"; fi +if [ $WITH_MESALINK -ne 0 ]; then print_info "With MesaLink: yes"; fi +if [ $WITH_BTHREAD_TRACER -ne 0 ]; then print_info "With bthread tracer: yes"; fi +if [ $WITH_ASAN -ne 0 ]; then print_info "With ASAN: yes"; fi +printf "\n${GREEN}brpc is now configured. You can build it with 'make'.${NC}\n" diff --git a/docs/cn/atomic_instructions.md b/docs/cn/atomic_instructions.md new file mode 100644 index 0000000..e5a14cb --- /dev/null +++ b/docs/cn/atomic_instructions.md @@ -0,0 +1,107 @@ +[English version](../en/atomic_instructions.md) + +我们都知道多核编程常用锁避免多个线程在修改同一个数据时产生[race condition](http://en.wikipedia.org/wiki/Race_condition)。当锁成为性能瓶颈时,我们又总想试着绕开它,而不可避免地接触了原子指令。但在实践中,用原子指令写出正确的代码是一件非常困难的事,琢磨不透的race condition、[ABA problem](https://en.wikipedia.org/wiki/ABA_problem)、[memory fence](https://en.wikipedia.org/wiki/Memory_barrier)很烧脑,这篇文章试图通过介绍[SMP](http://en.wikipedia.org/wiki/Symmetric_multiprocessing)架构下的原子指令帮助大家入门。C++11正式引入了[原子指令](http://en.cppreference.com/w/cpp/atomic/atomic),我们就以其语法描述。 + +顾名思义,原子指令是**对软件**不可再分的指令,比如x.fetch_add(n)指原子地给x加上n,这个指令**对软件**要么没做,要么完成,不会观察到中间状态。常见的原子指令有: + +| 原子指令 (x均为std::atomic) | 作用 | +| ---------------------------------------- | ---------------------------------------- | +| x.load() | 返回x的值。 | +| x.store(n) | 把x设为n,什么都不返回。 | +| x.exchange(n) | 把x设为n,返回设定之前的值。 | +| x.compare_exchange_strong(expected_ref, desired) | 若x等于expected_ref,则设为desired,返回成功;否则把最新值写入expected_ref,返回失败。 | +| x.compare_exchange_weak(expected_ref, desired) | 相比compare_exchange_strong可能有[spurious wakeup](http://en.wikipedia.org/wiki/Spurious_wakeup)。 | +| x.fetch_add(n), x.fetch_sub(n) | 原子地做x += n, x-= n,返回修改之前的值。 | + +你已经可以用这些指令做原子计数,比如多个线程同时累加一个原子变量,以统计这些线程对一些资源的操作次数。但是,这可能会有两个问题: + +- 这个操作没有你想象地快。 +- 如果你尝试通过看似简单的原子操作控制对一些资源的访问,你的程序有很大几率会crash。 + +# Cacheline + +没有任何竞争或只被一个线程访问的原子操作是比较快的,“竞争”指的是多个线程同时访问同一个[cacheline](https://en.wikipedia.org/wiki/CPU_cache#Cache_entries)。现代CPU为了以低价格获得高性能,大量使用了cache,并把cache分了多级。百度内常见的Intel E5-2620拥有32K的L1 dcache和icache,256K的L2 cache和15M的L3 cache。其中L1和L2 cache为每个核心独有,L3则所有核心共享。一个核心写入自己的L1 cache是极快的(4 cycles, ~2ns),但当另一个核心读或写同一处内存时,它得确认看到其他核心中对应的cacheline。对于软件来说,这个过程是原子的,不能在中间穿插其他代码,只能等待CPU完成[一致性同步](https://en.wikipedia.org/wiki/Cache_coherence),这个复杂的硬件算法使得原子操作会变得很慢,在E5-2620上竞争激烈时fetch_add会耗费700纳秒左右。访问被多个线程频繁共享的内存往往是比较慢的。比如像一些场景临界区看着很小,但保护它的spinlock性能不佳,因为spinlock使用的exchange, fetch_add等指令必须等待最新的cacheline,看上去只有几条指令,花费若干微秒并不奇怪。 + +要提高性能,就要避免让CPU频繁同步cacheline。这不单和原子指令本身的性能有关,还会影响到程序的整体性能。最有效的解决方法很直白:**尽量避免共享**。 + +- 一个依赖全局多生产者多消费者队列(MPMC)的程序难有很好的多核扩展性,因为这个队列的极限吞吐取决于同步cache的延时,而不是核心的个数。最好是用多个SPMC或多个MPSC队列,甚至多个SPSC队列代替,在源头就规避掉竞争。 +- 另一个例子是计数器,如果所有线程都频繁修改一个计数器,性能就会很差,原因同样在于不同的核心在不停地同步同一个cacheline。如果这个计数器只是用作打打日志之类的,那我们完全可以让每个线程修改thread-local变量,在需要时再合并所有线程中的值,性能可能有[几十倍的差别](bvar.md)。 + +一个相关的编程陷阱是false sharing:对那些不怎么被修改甚至只读变量的访问,由于同一个cacheline中的其他变量被频繁修改,而不得不经常等待cacheline同步而显著变慢了。多线程中的变量尽量按访问规律排列,频繁被其他线程修改的变量要放在独立的cacheline中。要让一个变量或结构体按cacheline对齐,可以include \后使用BAIDU_CACHELINE_ALIGNMENT宏,请自行grep brpc的代码了解用法。 + +# Memory fence + +仅靠原子技术实现不了对资源的访问控制,即使简单如[spinlock](https://en.wikipedia.org/wiki/Spinlock)或[引用计数](https://en.wikipedia.org/wiki/Reference_counting),看上去正确的代码也可能会crash。这里的关键在于**重排指令**导致了读写顺序的变化。只要没有依赖,代码中在后面的指令就可能跑到前面去,[编译器](http://preshing.com/20120625/memory-ordering-at-compile-time/)和[CPU](https://en.wikipedia.org/wiki/Out-of-order_execution)都会这么做。 + +这么做的动机非常自然,CPU要尽量塞满每个cycle,在单位时间内运行尽量多的指令。如上节中提到的,访存指令在等待cacheline同步时要花费数百纳秒,最高效地自然是同时同步多个cacheline,而不是一个个做。一个线程在代码中对多个变量的依次修改,可能会以不同的次序同步到另一个线程所在的核心上。不同线程对数据的需求不同,按需同步也会导致cacheline的读序和写序不同。 + +如果其中第一个变量扮演了开关的作用,控制对后续变量的访问。那么当这些变量被一起同步到其他核心时,更新顺序可能变了,第一个变量未必是第一个更新的,然而其他线程还认为它代表着其他变量有效,去访问了实际已被删除的变量,从而导致未定义的行为。比如下面的代码片段: + +```c++ +// Thread 1 +// bool ready was initialized to false +p.init(); +ready = true; +``` + +```c++ +// Thread2 +if (ready) { + p.bar(); +} +``` +从人的角度,这是对的,因为线程2在ready为true时才会访问p,按线程1的逻辑,此时p应该初始化好了。但对多核机器而言,这段代码可能难以正常运行: + +- 线程1中的ready = true可能会被编译器或cpu重排到p.init()之前,从而使线程2看到ready为true时,p仍然未初始化。这种情况同样也会在线程2中发生,p.bar()中的一些代码可能被重排到检查ready之前。 +- 即使没有重排,ready和p的值也会独立地同步到线程2所在核心的cache,线程2仍然可能在看到ready为true时看到未初始化的p。 + +注:x86/x64的load带acquire语意,store带release语意,上面的代码刨除编译器和CPU因素可以正确运行。 + +通过这个简单例子,你可以窥见原子指令编程的复杂性了吧。为了解决这个问题,CPU和编译器提供了[memory fence](http://en.wikipedia.org/wiki/Memory_barrier),让用户可以声明访存指令间的可见性(visibility)关系,boost和C++11对memory fence做了抽象,总结为如下几种[memory order](http://en.cppreference.com/w/cpp/atomic/memory_order). + +| memory order | 作用 | +| -------------------- | ---------------------------------------- | +| memory_order_relaxed | 没有fencing作用 | +| memory_order_consume | 后面依赖此原子变量的访存指令勿重排至此条指令之前 | +| memory_order_acquire | 后面访存指令勿重排至此条指令之前 | +| memory_order_release | 前面访存指令勿重排至此条指令之后。当此条指令的结果对其他线程可见后,之前的所有指令都可见 | +| memory_order_acq_rel | acquire + release语意 | +| memory_order_seq_cst | acq_rel语意外加所有使用seq_cst的指令有严格地全序关系 | + +有了memory order,上面的例子可以这么更正: + +```c++ +// Thread1 +// std::atomic ready was initialized to false +p.init(); +ready.store(true, std::memory_order_release); +``` + +```c++ +// Thread2 +if (ready.load(std::memory_order_acquire)) { + p.bar(); +} +``` + +线程2中的acquire和线程1的release配对,确保线程2在看到ready==true时能看到线程1 release之前所有的访存操作。 + +注意,memory fence不等于可见性,即使线程2恰好在线程1在把ready设置为true后读取了ready也不意味着它能看到true,因为同步cache是有延时的。memory fence保证的是可见性的顺序:“假如我看到了a的最新值,那么我一定也得看到b的最新值”。 + +一个相关问题是:如何知道看到的值是新还是旧?一般分两种情况: + +- 值是特殊的。比如在上面的例子中,ready=true是个特殊值,只要线程2看到ready为true就意味着更新了。只要设定了特殊值,读到或没有读到特殊值都代表了一种含义。 +- 总是累加。一些场景下没有特殊值,那我们就用fetch_add之类的指令累加一个变量,只要变量的值域足够大,在很长一段时间内,新值和之前所有的旧值都会不相同,我们就能区分彼此了。 + +原子指令的例子可以看boost.atomic的[Example](http://www.boost.org/doc/libs/1_56_0/doc/html/atomic/usage_examples.html),atomic的官方描述可以看[这里](http://en.cppreference.com/w/cpp/atomic/atomic)。 + +# wait-free & lock-free + +原子指令能为我们的服务赋予两个重要属性:[wait-free](http://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom)和[lock-free](http://en.wikipedia.org/wiki/Non-blocking_algorithm#Lock-freedom)。前者指不管OS如何调度线程,每个线程都始终在做有用的事;后者比前者弱一些,指不管OS如何调度线程,至少有一个线程在做有用的事。如果我们的服务中使用了锁,那么OS可能把一个刚获得锁的线程切换出去,这时候所有依赖这个锁的线程都在等待,而没有做有用的事,所以用了锁就不是lock-free,更不会是wait-free。为了确保一件事情总在确定时间内完成,实时系统的关键代码至少是lock-free的。在百度广泛又多样的在线服务中,对时效性也有着严苛的要求,如果RPC中最关键的部分满足wait-free或lock-free,就可以提供更稳定的服务质量。事实上,brpc中的读写都是wait-free的,具体见[IO](io.md)。 + +值得提醒的是,常见想法是lock-free或wait-free的算法会更快,但事实可能相反,因为: + +- lock-free和wait-free必须处理更多更复杂的race condition和ABA problem,完成相同目的的代码比用锁更复杂。代码越多,耗时就越长。 +- 使用mutex的算法变相带“后退”效果。后退(backoff)指出现竞争时尝试另一个途径以临时避免竞争,mutex出现竞争时会使调用者睡眠,使拿到锁的那个线程可以很快地独占完成一系列流程,总体吞吐可能反而高了。 + +mutex导致低性能往往是因为临界区过大(限制了并发度),或竞争过于激烈(上下文切换开销变得突出)。lock-free/wait-free算法的价值在于其保证了一个或所有线程始终在做有用的事,而不是绝对的高性能。但在一种情况下lock-free和wait-free算法的性能多半更高:就是算法本身可以用少量原子指令实现。实现锁也是要用原子指令的,当算法本身用一两条指令就能完成的时候,相比额外用锁肯定是更快了。 diff --git a/docs/cn/auto_concurrency_limiter.md b/docs/cn/auto_concurrency_limiter.md new file mode 100644 index 0000000..342e9ba --- /dev/null +++ b/docs/cn/auto_concurrency_limiter.md @@ -0,0 +1,174 @@ +# 自适应限流 + +服务的处理能力是有客观上限的。当请求速度超过服务的处理速度时,服务就会过载。 + +如果服务持续过载,会导致越来越多的请求积压,最终所有的请求都必须等待较长时间才能被处理,从而使整个服务处于瘫痪状态。 + +与之相对的,如果直接拒绝掉一部分请求,反而能够让服务能够"及时"处理更多的请求。对应的方法就是[设置最大并发](https://github.com/apache/brpc/blob/master/docs/cn/server.md#%E9%99%90%E5%88%B6%E6%9C%80%E5%A4%A7%E5%B9%B6%E5%8F%91)。 + +自适应限流能动态调整服务的最大并发,在保证服务不过载的前提下,让服务尽可能多的处理请求。 + +## 使用场景 +通常情况下要让服务不过载,只需在上线前进行压力测试,并通过little's law计算出best_max_concurrency就可以了。但在服务数量多,拓扑复杂,且处理能力会逐渐变化的局面下,使用固定的最大并发会带来巨大的测试工作量,很不方便。自适应限流就是为了解决这个问题。 + +使用自适应限流前建议做到: +1. 客户端开启了重试功能。 + +2. 服务端有多个节点。 + +这样当一个节点返回过载时,客户端可以向其他的节点发起重试,从而尽量不丢失流量。 + +## 开启方法 +目前只有method级别支持自适应限流。如果要为某个method开启自适应限流,只需要将它的最大并发设置为"auto"即可。 + +```c++ +// Set auto concurrency limiter for all methods +brpc::ServerOptions options; +options.method_max_concurrency = "auto"; + +// Set auto concurrency limiter for specific method +server.MaxConcurrencyOf("example.EchoService.Echo") = "auto"; +``` + +## 原理 + +### 名词 +**concurrency**: 同时处理的请求数,又被称为“并发度”。 + +**max_concurrency**: 设置的最大并发度。超过并发的请求会被拒绝(返回ELIMIT错误),在集群层面,client应重试到另一台server上去。 + +**best_max_concurrency**: 并发的物理含义是任务处理槽位,天然存在上限,这个上限就是best_max_concurrency。若max_concurrency设置的过大,则concurrency可能大于best_max_concurrency,任务将无法被及时处理而暂存在各种队列中排队,系统也会进入拥塞状态。若max_concurrency设置的过小,则concurrency总是会小于best_max_concurrency,限制系统达到本可以达到的更高吞吐。 + +**noload_latency**: 单纯处理任务的延时,不包括排队时间。另一种解释是低负载的延时。由于正确处理任务得经历必要的环节,其中会耗费cpu或等待下游返回,noload_latency是一个服务固有的属性,但可能随时间逐渐改变(由于内存碎片,压力变化,业务数据变化等因素)。 + +**min_latency**: 实际测定的latency中的较小值的ema,当concurrency不大于best_max_concurrency时,min_latency和noload_latency接近(可能轻微上升)。 + +**peak_qps**: qps的上限。注意是处理或回复的qps而不是接收的qps。值取决于best_max_concurrency / noload_latency,这两个量都是服务的固有属性,故peak_qps也是服务的固有属性,和拥塞状况无关,但可能随时间逐渐改变。 + +**max_qps**: 实际测定的qps中的较大值。由于qps具有上限,max_qps总是会小于peak_qps,不论拥塞与否。 + +### Little's Law +在服务处于稳定状态时: concurrency = latency * qps。 这是自适应限流的理论基础。 + +当服务没有超载时,随着流量的上升,latency基本稳定(接近noload_latency),qps和concurrency呈线性关系一起上升。 + +当流量超过服务的peak_qps时,则concurrency和latency会一起上升,而qps会稳定在peak_qps。 + +假如一个服务的peak_qps和noload_latency都比较稳定,那么它的best_max_concurrency = noload_latency * peak_qps。 + +自适应限流就是要找到服务的noload_latency和peak_qps, 并将最大并发设置为靠近两者乘积的一个值。 + +### 计算公式 + +自适应限流会不断的对请求进行采样,当采样窗口的样本数量足够时,会根据样本的平均延迟和服务当前的qps计算出下一个采样窗口的max_concurrency: + +> max_concurrency = max_qps * ((2+alpha) * min_latency - latency) + +alpha为可接受的延时上升幅度,默认0.3。 + +latency是当前采样窗口内所有请求的平均latency。 + +max_qps是最近一段时间测量到的qps的极大值。 + +min_latency是最近一段时间测量到的latency较小值的ema,是noload_latency的估算值。 + +注意:当计算出来的 max_concurrency 和当前的 max_concurrency 的值不同时,每次对 max_concurrency 的调整的比例有一个上限,让 max_concurrency +的[变化更为平滑](https://github.com/apache/brpc/blob/master/src/brpc/policy/auto_concurrency_limiter.cpp#L249)。 + +当服务处于低负载时,min_latency约等于noload_latency,此时计算出来的max_concurrency会高于concurrency,但低于best_max_concurrency,给流量上涨留探索空间。而当服务过载时,服务的qps约等于max_qps,同时latency开始明显超过min_latency,此时max_concurrency则会接近concurrency,并通过定期衰减避免远离best_max_concurrency,保证服务不会过载。 + + +### 估算noload_latency +服务的noload_latency并非是一成不变的,自适应限流必须能够正确的探测noload_latency的变化。当noload_latency下降时,是很容感知到的,因为这个时候latency也会下降。难点在于当latency上涨时,需要能够正确的辨别到底是服务过载了,还是noload_latency上涨了。 + +可能的方案有: +1. 取最近一段时间的最小latency来近似noload_latency +2. 取最近一段时间的latency的各种平均值来预测noload_latency +3. 收集请求的平均排队等待时间,使用latency - queue_time作为noload_latency +4. 每隔一段时间缩小max_concurrency,过一小段时间后以此时的latency作为noload_latency + +方案1和方案2的问题在于:假如服务持续处于高负载,那么最近的所有latency都会高出noload_latency,从而使得算法估计的noload_latency不断升高。 + +方案3的问题在于,假如服务的性能瓶颈在下游服务,那么请求在服务本身的排队等待时间无法反应整体的负载情况。 + +方案4是最通用的,也经过了大量实验的考验。缩小max_concurrency和公式中的alpha存在关联。让我们做个假想实验,若latency极为稳定并都等于min_latency,那么公式简化为max_concurrency = max_qps * latency * (1 + alpha)。根据little's law,qps最多为max_qps * (1 + alpha). alpha是qps的"探索空间",若alpha为0,则qps被锁定为max_qps,算法可能无法探索到peak_qps。但在qps已经达到peak_qps时,alpha会使延时上升(已拥塞),此时测定的min_latency会大于noload_latency,一轮轮下去最终会导致min_latency不收敛。定期降低max_concurrency就是阻止这个过程,并给min_latency下降提供"探索空间"。 + +#### 减少重测时的流量损失 + +每隔一段时间,自适应限流算法都会缩小max_concurrency,并持续一段时间,然后将此时的latency作为服务的noload_latency,以处理noload_latency上涨了的情况。测量noload_latency时,必须让先服务处于低负载的状态,因此对max_concurrency的缩小是难以避免的。 + +由于max_concurrency < concurrency时,服务会拒绝掉所有的请求,限流算法将"排空所有的经历过排队等待的请求的时间" 设置为 latency * 2 ,以确保用于计算min_latency的样本绝大部分都是没有经过排队等待的。 + +由于服务的latency通常都不会太长,这种做法所带来的流量损失也很小。 + +#### 应对抖动 +即使服务自身没有过载,latency也会发生波动,根据Little's Law,latency的波动会导致server的concurrency发生波动。 + +我们在设计自适应限流的计算公式时,考虑到了latency发生抖动的情况: +当latency与min_latency很接近时,根据计算公式会得到一个较高max_concurrency来适应concurrency的波动,从而尽可能的减少“误杀”。同时,随着latency的升高,max_concurrency会逐渐降低,以保护服务不会过载。 + +从另一个角度来说,当latency也开始升高时,通常意味着某处(不一定是服务本身,也有可能是下游服务)消耗了大量CPU资源,这个时候缩小max_concurrency也是合理的。 + +#### 平滑处理 +为了减少个别窗口的抖动对限流算法的影响,同时尽量降低计算开销,计算min_latency时会通过使用[EMA](https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average)来进行平滑处理: + +``` +if latency < min_latency: + min_latency = latency * ema_alpha + (1 - ema_alpha) * min_latency +else: + do_nothing +``` + +### 估算peak_qps + +#### 提高qps增长的速度 +当服务启动时,由于服务本身需要进行一系列的初始化,tcp本身也有慢启动等一系列原因。服务在刚启动时的qps一定会很低。这就导致了服务启动时的max_concurrency也很低。而按照上面的计算公式,当max_concurrency很低的时候,预留给qps增长的冗余concurrency也很低(即:alpha * max_qps * min_latency)。从而会影响当流量增加时,服务max_concurrency的增加速度。 + +假如从启动到打满qps的时间过长,这期间会损失大量流量。在这里我们采取的措施有两个, + +1. 采样方面,一旦采到的请求数量足够多,直接提交当前采样窗口,而不是等待采样窗口的到时间了才提交 +2. 计算公式方面,当current_qps > 保存的max_qps时,直接进行更新,不进行平滑处理。 + +在进行了这两个处理之后,绝大部分情况下都能够在2秒左右将qps打满。 + +#### 平滑处理 +为了减少个别窗口的抖动对限流算法的影响,同时尽量降低计算开销,在计算max_qps时,会通过使用[EMA](https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average)来进行平滑处理: + +``` +if current_qps > max_qps: + max_qps = current_qps +else: + max_qps = current_qps * ema_alpha / 10 + (1 - ema_alpha / 10) * max_qps +``` +将max_qps的ema参数置为min_latency的ema参数的十分之一的原因是: max_qps 下降了通常并不意味着极限qps也下降了。而min_latency下降了,通常意味着noload_latency确实下降了。 + +### 与netflix gradient算法的对比 + +netflix中的gradient算法公式为:max_concurrency = min_latency / latency * max_concurrency + queue_size。 + +其中latency是采样窗口的最小latency,min_latency是最近多个采样窗口的最小latency。min_latency / latency就是算法中的"梯度",当latency大于min_latency时,max_concurrency会逐渐减少;反之,max_concurrency会逐渐上升,从而让max_concurrency围绕在best_max_concurrency附近。 + +这个公式可以和本文的算法进行类比: + +* gradient算法中的latency和本算法的不同,前者的latency是最小值,后者是平均值。netflix的原意是最小值能更好地代表noload_latency,但实际上只要不对max_concurrency做定期衰减,不管最小值还是平均值都有可能不断上升使算法不收敛。最小值并不能带来额外的好处,反而会使算法更不稳定。 +* gradient算法中的max_concurrency / latency从概念上和qps有关联(根据little's law),但可能严重脱节。比如在重测 +min_latency前,若所有latency都小于min_latency,那么max_concurrency会不断下降甚至到0;但按照本算法,max_qps和min_latency仍然是稳定的,它们计算出的max_concurrency也不会剧烈变动。究其本质,gradient算法在迭代max_concurrency时,latency并不能代表实际并发为max_concurrency时的延时,两者是脱节的,所以max_concurrency / latency的实际物理含义不明,与qps可能差异甚大,最后导致了很大的偏差。 +* gradient算法的queue_size推荐为sqrt(max_concurrency),这是不合理的。netflix对queue_size的理解大概是代表各种不可控环节的缓存,比如socket里的,和max_concurrency存在一定的正向关系情有可原。但在我们的理解中,这部分queue_size作用微乎其微,没有或用常量即可。我们关注的queue_size是给concurrency上升留出的探索空间: max_concurrency的更新是有延迟的,在并发从低到高的增长过程中,queue_size的作用就是在max_concurrency更新前不限制qps上升。而当concurrency高时,服务可能已经过载了,queue_size就应该小一点,防止进一步恶化延时。这里的queue_size和并发是反向关系。 + +## 错误率惩罚阈值 + +`auto_cl_error_rate_punish_threshold`用于设置错误率"死区",低于该阈值的错误率不会产生惩罚,避免少量错误请求对max_concurrency的过度影响。 + +| GFlag | 默认值 | 有效范围 | 说明 | +|-------|--------|----------|------| +| auto_cl_error_rate_punish_threshold | 0 | [0, 1) | 错误率惩罚阈值,0表示禁用 | + +- **默认值为0**:禁用该功能,保持原有行为 +- **设置为有效值(如0.1)**:错误率 ≤ 阈值时惩罚为0;错误率 > 阈值时惩罚线性增长 +- **无效值处理**:≥1 的值会被忽略,等同于0 + +**示例**: +``` +# 错误率低于10%时不惩罚,高于10%时线性增加惩罚 +--auto_cl_error_rate_punish_threshold=0.1 +``` diff --git a/docs/cn/avalanche.md b/docs/cn/avalanche.md new file mode 100644 index 0000000..568feba --- /dev/null +++ b/docs/cn/avalanche.md @@ -0,0 +1,21 @@ +“雪崩”指的是访问服务集群时绝大部分请求都超时,且在流量减少时仍无法恢复的现象。下面解释这个现象的来源。 + +当流量超出服务的最大qps时,服务将无法正常服务;当流量恢复正常时(小于服务的处理能力),积压的请求会被处理,虽然其中很大一部分可能会因为处理的不及时而超时,但服务本身一般还是会恢复正常的。这就相当于一个水池有一个入水口和一个出水口,如果入水量大于出水量,水池子终将盛满,多出的水会溢出来。但如果入水量降到出水量之下,一段时间后水池总会排空。雪崩并不是单一服务能产生的。 + +如果一个请求经过两个服务,情况就有所不同了。比如请求访问A服务,A服务又访问了B服务。当B被打满时,A处的client会大量超时,如果A处的client在等待B返回时也阻塞了A的服务线程(常见),且使用了固定个数的线程池(常见),那么A处的最大qps就从**线程数 / 平均延时**,降到了**线程数 / 超时**。由于超时往往是平均延时的3至4倍,A处的最大qps会相应地下降3至4倍,从而产生比B处更激烈的拥塞。如果A还有类似的上游,拥塞会继续传递上去。但这个过程还是可恢复的。B处的流量终究由最前端的流量触发,只要最前端的流量回归正常,B处的流量总会慢慢降下来直到能正常回复大多数请求,从而让A恢复正常。 + +但有两个例外: + +1. A可能对B发起了过于频繁的基于超时的重试。这不仅会让A的最大qps降到**线程数 / 超时**,还会让B处的qps翻**重试次数**倍。这就可能陷入恶性循环了:只要**线程数 / 超时 \* 重试次数**大于B的最大qps,B就无法恢复 -> A处的client会继续超时 -> A继续重试 -> B继续无法恢复。 +2. A或B没有限制某个缓冲或队列的长度,或限制过于宽松。拥塞请求会大量地积压在那里,要恢复就得全部处理完,时间可能长得无法接受。由于有限长的缓冲或队列需要在填满时解决等待、唤醒等问题,有时为了简单,代码可能会假定缓冲或队列不会满,这就埋下了种子。即使队列是有限长的,恢复时间也可能很长,因为清空队列的过程是个追赶问题,排空的时间取决于**积压的请求数 / (最大qps - 当前qps)**,如果当前qps和最大qps差的不多,积压的请求又比较多,那排空时间就遥遥无期了。 + +了解这些因素后可以更好的理解brpc中相关的设计。 + +1. 拥塞时A服务最大qps的跳变是因为线程个数是**硬限**,单个请求的处理时间很大程度上决定了最大qps。而brpc server端默认在bthread中处理请求,个数是软限,单个请求超时只是阻塞所在的bthread,并不会影响为新请求建立新的bthread。brpc也提供了完整的异步接口,让用户可以进一步提高io-bound服务的并发度,降低服务被打满的可能性。 +2. brpc中[重试](client.md#重试)默认只在连接出错时发起,避免了流量放大,这是比较有效率的重试方式。如果需要基于超时重试,可以设置[backup request](client.md#重试),这类重试最多只有一次,放大程度降到了最低。brpc中的RPC超时是deadline,超过后RPC一定会结束,这让用户对服务的行为有更好的预判。在之前的一些实现中,RPC超时是单次超时*重试次数,在实践中容易误判。 +3. brpc server端的[max_concurrency选项](server.md#限制最大并发)控制了server的最大并发:当同时处理的请求数超过max_concurrency时,server会回复client错误,而不是继续积压。这一方面在服务开始的源头控制住了积压的请求数,尽量避免延生到用户缓冲或队列中,另一方面也让client尽快地去重试其他server,对集群来说是个更好的策略。 + +对于brpc的用户来说,要防止雪崩,主要注意两点: + +1. 评估server的最大并发,设置合理的max_concurrency值。这个默认是不设的,也就是不限制。无论程序是同步还是异步,用户都可以通过 **最大qps \* 非拥塞时的延时**(秒)来评估最大并发,原理见[little's law](https://en.wikipedia.org/wiki/Little%27s_law),这两个量都可以在brpc中的内置服务中看到。max_concurrency与最大并发相等或大一些就行了。 +2. 注意考察重试发生时的行为,特别是在定制RetryPolicy时。如果你只是用默认的brpc重试,一般是安全的。但用户程序也常会自己做重试,比如通过一个Channel访问失败后,去访问另外一个Channel,这种情况下要想清楚重试发生时最差情况下请求量会放大几倍,服务是否可承受。 diff --git a/docs/cn/backup_request.md b/docs/cn/backup_request.md new file mode 100644 index 0000000..b2e0bb6 --- /dev/null +++ b/docs/cn/backup_request.md @@ -0,0 +1,120 @@ +有时为了保证可用性,需要同时访问两路服务,哪个先返回就取哪个。在brpc中,这有多种做法: + +# 当后端server可以挂在一个命名服务内时 + +Channel开启backup request。这个Channel会先向其中一个server发送请求,如果在ChannelOptions.backup_request_ms后还没回来,再向另一个server发送。之后哪个先回来就取哪个。在设置了合理的backup_request_ms后,大部分时候只会发一个请求,对后端服务只有一倍压力。 + +示例代码见[example/backup_request_c++](https://github.com/apache/brpc/blob/master/example/backup_request_c++)。这个例子中,client设定了在2ms后发送backup request,server在碰到偶数位的请求后会故意睡眠20ms以触发backup request。 + +运行后,client端和server端的日志分别如下,"index"是请求的编号。可以看到server端在收到第一个请求后会故意sleep 20ms,client端之后发送另一个同样index的请求,最终的延时并没有受到故意sleep的影响。 + +![img](../images/backup_request_1.png) + +![img](../images/backup_request_2.png) + +/rpcz也显示client在2ms后触发了backup超时并发出了第二个请求。 + +![img](../images/backup_request_3.png) + +## 选择合理的backup_request_ms + +可以观察brpc默认提供的latency_cdf图,或自行添加。cdf图的y轴是延时(默认微秒),x轴是小于y轴延时的请求的比例。在下图中,选择backup_request_ms=2ms可以大约覆盖95.5%的请求,选择backup_request_ms=10ms则可以覆盖99.99%的请求。 + +![img](../images/backup_request_4.png) + +自行添加的方法: + +```c++ +#include +#include +... +bvar::LatencyRecorder my_func_latency("my_func"); +... +butil::Timer tm; +tm.start(); +my_func(); +tm.stop(); +my_func_latency << tm.u_elapsed(); // u代表微秒,还有s_elapsed(), m_elapsed(), n_elapsed()分别对应秒,毫秒,纳秒。 + +// 好了,在/vars中会显示my_func_qps, my_func_latency, my_func_latency_cdf等很多计数器。 +``` + +## Backup Request 限流 + +如需限制 backup request 的发送比例,可使用内置工厂函数创建限流策略,也可自行实现 `BackupRequestPolicy` 接口。 + +优先级顺序:`backup_request_policy` > `backup_request_ms`。 + +### 使用内置限流策略 + +调用 `CreateRateLimitedBackupPolicy` 创建限流策略,并将其设置到 `ChannelOptions.backup_request_policy`: + +```c++ +#include "brpc/backup_request_policy.h" +#include + +brpc::RateLimitedBackupPolicyOptions opts; +opts.backup_request_ms = 10; // 超过10ms未返回时发送backup请求 +opts.max_backup_ratio = 0.3; // backup请求比例上限30% +opts.window_size_seconds = 10; // 滑动窗口宽度(秒) +opts.update_interval_seconds = 5; // 缓存比例的刷新间隔(秒) + +// CreateRateLimitedBackupPolicy返回的指针由调用方负责释放。 +// policy的生命周期必须长于channel——先销毁channel,再销毁policy。 +std::unique_ptr policy( + brpc::CreateRateLimitedBackupPolicy(opts)); + +brpc::ChannelOptions options; +options.backup_request_policy = policy.get(); // Channel不拥有该对象 +channel.Init(..., &options); +// channel必须在policy析构之前销毁。 +``` + +参数说明(`RateLimitedBackupPolicyOptions`): + +| 字段 | 默认值 | 说明 | +|------|--------|------| +| `backup_request_ms` | -1 | 超时阈值(毫秒)。-1 表示继承 `ChannelOptions.backup_request_ms`(仅在通过 `ChannelOptions.backup_request_policy` 设置策略时有效;通过 Controller 注入时没有 channel 级的回退值,应显式指定 >= 0 的值)。必须 >= -1。 | +| `max_backup_ratio` | 0.1 | backup比例上限,取值范围 (0, 1] | +| `window_size_seconds` | 10 | 滑动窗口宽度(秒),取值范围 [1, 3600] | +| `update_interval_seconds` | 5 | 缓存刷新间隔(秒),必须 >= 1 | + +参数不合法时 `CreateRateLimitedBackupPolicy` 返回 `NULL`。 + +### 使用自定义 BackupRequestPolicy + +如需完全控制,可实现 `BackupRequestPolicy` 接口并设置到 `ChannelOptions.backup_request_policy`: + +```c++ +#include "brpc/backup_request_policy.h" + +class MyBackupPolicy : public brpc::BackupRequestPolicy { +public: + int32_t GetBackupRequestMs(const brpc::Controller*) const override { + return 10; // 10ms后发送backup + } + bool DoBackup(const brpc::Controller*) const override { + return should_allow_backup(); // 自定义逻辑 + } + void OnRPCEnd(const brpc::Controller*) override { + // 每次RPC结束时调用,可在此更新统计 + } +}; + +MyBackupPolicy my_policy; +brpc::ChannelOptions options; +options.backup_request_policy = &my_policy; // Channel不拥有该对象,需保证其生命周期长于Channel +channel.Init(..., &options); +``` + +### 实现说明 + +- 比例通过bvar计数器在滑动时间窗口内统计。缓存值通过无锁CAS选举最多每 `update_interval_seconds` 刷新一次,因此每次RPC的开销极低(公共路径仅有两次原子读)。 +- Backup决策在做出时立即计数(RPC完成前),以便在延迟抖动期间更快地反馈。总RPC数在完成时统计。这意味着比例在抖动期间可能短暂滞后,这是设计有意为之——限流器的目标是近似的尽力而为的节流,而非精确执行。 +- 每个使用限流的Channel会维护两个 `bvar::Window` 采样任务,在Channel数量极多的部署中请留意此开销。 + +# 当后端server不能挂在一个命名服务内时 + +【推荐】建立一个开启backup request的SelectiveChannel,其中包含两个sub channel。访问这个SelectiveChannel和上面的情况类似,会先访问一个sub channel,如果在ChannelOptions.backup_request_ms后没返回,再访问另一个sub channel。如果一个sub channel对应一个集群,这个方法就是在两个集群间做互备。SelectiveChannel的例子见[example/selective_echo_c++](https://github.com/apache/brpc/tree/master/example/selective_echo_c++),具体做法请参考上面的过程。 + +【不推荐】发起两个异步RPC后Join它们,它们的done内是相互取消的逻辑。示例代码见[example/cancel_c++](https://github.com/apache/brpc/tree/master/example/cancel_c++)。这种方法的问题是总会发两个请求,对后端服务有两倍压力,这个方法怎么算都是不经济的,你应该尽量避免用这个方法。 diff --git a/docs/cn/baidu_std.md b/docs/cn/baidu_std.md new file mode 100644 index 0000000..0f901e5 --- /dev/null +++ b/docs/cn/baidu_std.md @@ -0,0 +1,159 @@ +# 介绍 + +baidu_std是一种基于TCP协议的二进制RPC通信协议。它以Protobuf作为基本的数据交换格式,并基于Protobuf内置的RPC Service形式,规定了通信双方之间的数据交换协议,以实现完整的RPC调用。 + +baidu_std不考虑跨TCP连接的情况。 + +# 基本协议 + +## 服务 + +所有RPC服务都在某个IP地址上通过某个端口发布。 + +一个端口可以同时发布多个服务。服务以名字标识。服务名必须是UpperCamelCase,由大小写字母和数字组成,长度不超过64个字符。 + +一个服务可以包含一个或多个方法。每个方法以名字标识,由大小写字母、数字和下划线组成,长度不超过64个字符。考虑到不同语言风格差异较大,这里对方法命名格式不做强制规定。 + +四元组唯一地标识了一个RPC方法。 + +调用方法所需参数应放在一个Protobuf消息内。如果方法有返回结果,也同样应放在一个Protobuf消息内。具体定义由通信双方自行约定。特别地,可以使用空的Protobuf消息来表示请求/响应为空的情况。 + +## 包 + +包是baidu_std的基本数据交换单位。每个包由包头和包体组成,其中包体又分为元数据、数据、附件三部分。具体的参数和返回结果放在数据部分。 + +包分为请求包和响应包两种。它们的包头格式一致,但元数据部分的定义不同。 + +## 包头 + +包头长度固定为12字节。前四字节为协议标识PRPC,中间四字节是一个32位整数,表示包体长度(不包括包头的12字节),最后四字节是一个32位整数,表示包体中的元数据包长度。整数均采用网络字节序表示。 + +## 元数据 + +元数据用于描述请求/响应。 + +``` +message RpcMeta { + optional RpcRequestMeta request = 1; + optional RpcResponseMeta response = 2; + optional int32 compress_type = 3; + optional int64 correlation_id = 4; + optional int32 attachment_size = 5; + optional ChunkInfo chuck_info = 6; + optional bytes authentication_data = 7; +}; +``` + +请求包中只有request,响应包中只有response。实现可以根据域的存在性来区分请求包和响应包。 + +| 参数 | 说明 | +| ------------------- | ---------------------------------------- | +| request | 请求包元数据 | +| response | 响应包元数据 | +| compress_type | 详见附录[压缩算法](#compress_algorithm) | +| correlation_id | 请求包中的该域由请求方设置,用于唯一标识一个RPC请求。请求方有义务保证其唯一性,协议本身对此不做任何检查。响应方需要在对应的响应包里面将correlation_id设为同样的值。 | +| attachment_size | 附件大小,详见[附件](#attachment) | +| chuck_info | 详见[Chunk模式](#chunk-mode) | +| authentication_data | 用于存放身份认证相关信息 | + +### 请求包元数据 + +请求包的元数据主要描述了需要调用的RPC方法信息,Protobuf如下 + +``` +message RpcRequestMeta { + required string service_name = 1; + required string method_name = 2; + optional int64 log_id = 3; +}; +``` + +| 参数 | 说明 | +| ------------ | ---------------------------- | +| service_name | 服务名,约束见上文 | +| method_name | 方法名,约束见上文 | +| log_id | 用于打印日志。可用于存放BFE_LOGID。该参数可选。 | + +### 响应包元数据 + +响应包的元数据是对返回结果的描述。如果出现任何异常,错误也会放在元数据中。其Protobuf描述如下 + +``` +message RpcResponseMeta { + optional int32 error_code = 1; + optional string error_text = 2; +}; +``` + +| 参数 | 说明 | +| ---------- | ------------------------------------ | +| error_code | 发生错误时的错误号,0表示正常,非0表示错误。具体含义由应用方自行定义。 | +| error_text | 错误的文本描述 | + +### 对元数据的扩展 + +某些实现需要在元数据中增加自己专有的字段。为了避免冲突,并保证不同实现之间相互调用的兼容性,所有实现都需要向接口规范委员会申请一个专用的序号用于存放自己的扩展字段。 + +以Hulu为例,被分配的序号为100。因此Hulu可以使用这样的proto定义: + +``` +message RpcMeta { + optional RpcRequestMeta request = 1; + optional RpcResponseMeta response = 2; + optional int32 compress_type = 3; + optional int64 correlation_id = 4; + optional int32 attachment_size = 5; + optional ChunkInfo chuck_info = 6; + optional HuluMeta hulu_meta = 100; +}; +message RpcRequestMeta { + required string service_name = 1; + required string method_name = 2; + optional int64 log_id = 3; + optional HuluRequestMeta hulu_request_meta = 100; +}; +message RpcResponseMeta { + optional int32 error_code = 1; + optional string error_text = 2; + optional HuluResponseMeta hulu_response_meta = 100; +}; +``` + +因为只是将100这个序号保留给Hulu使用,因此Hulu可以自由决定是否添加这些字段,以及使用什么样的名字。其余实现使用的proto中不存在这些定义,会直接作为Unknown字段忽略。 + +当前分配的序号如下 + +| 序号 | 实现 | +| ---- | ---- | +| 100 | Hulu | +| 101 | Sofa | + +## 数据 + +自定义的Protobuf Message。用于存放参数或返回结果。 + +## Attachment + +某些场景下需要通过RPC来传递二进制数据,例如文件上传下载,多媒体转码等等。将这些二进制数据打包在Protobuf内会增加不必要的内存拷贝。因此协议允许使用附件的方式直接传送二进制数据。 + +附件总是放在包体的最后,紧跟数据部分。消息包需要携带附件时,应将RpcMeta中的attachment_size设为附件的实际字节数。 + +## 压缩算法 + +可以使用指定的压缩算法来压缩消息包中的数据部分。 + +| 值 | 含义 | +| ---- | -------- | +| 0 | 不压缩 | +| 1 | 使用Snappy | +| 2 | 使用gzip | + +# HTTP接口 + +服务应以标准的HTTP协议对外发布接口。 + +数据交换格式默认应使用JSON。Content-Type使用application/json。有特殊需求的接口不受此限制。例如上传文件可以使用multipart/form-data;下载文件可以根据实际内容选用合适的Content-Type。 + +URL和JSON中的字符编码一律使用UTF-8。 + +建议使用RESTful形式的Web Service接口。由于RESTful并非一个严格的规范,本规范对此不做强制规定。 \ No newline at end of file diff --git a/docs/cn/bazel_support.md b/docs/cn/bazel_support.md new file mode 100644 index 0000000..0ff8611 --- /dev/null +++ b/docs/cn/bazel_support.md @@ -0,0 +1,20 @@ +## bRPC 作为Bazel第三方依赖 +1. bRPC 依赖于一些开源库, 但这些库并没有提供bazel支持, 所以需要你手动将一部分依赖加入到你的构建项目中. +2. 将 /example/build_with_bazel/*.BUILD 和 brpc_workspace.bzl 该文件移动到你的项目根目录下, 将 +```c++ + load("@//:brpc_workspace.bzl", "brpc_workspace") + brpc_workspace(); +``` +内容添加到你的WORKSPACE中. + +3. 链接请使用 + ```c++ + ... + deps = [ + "@apache_brpc//:bthread", + "@apache_brpc//:brpc", + "@apache_brpc//:butil", + "@apache_brpc//:bvar", + ] + ... + ``` diff --git a/docs/cn/benchmark.md b/docs/cn/benchmark.md new file mode 100644 index 0000000..f02a5af --- /dev/null +++ b/docs/cn/benchmark.md @@ -0,0 +1,221 @@ +NOTE: following tests were done in 2015, which may not reflect latest status of the package. + +# 序言 + +在多核的前提下,性能和线程是紧密联系在一起的。线程间的跳转对高频IO操作的性能有决定性作用: 一次跳转意味着至少3-20微秒的延时,由于每个核心的L1 cache独立(我们的cpu L2 cache也是独立的),随之而来是大量的cache miss,一些变量的读取、写入延时会从纳秒级上升几百倍至微秒级: 等待cpu把对应的cacheline同步过来。有时这带来了一个出乎意料的结果,当每次的处理都很简短时,一个多线程程序未必比一个单线程程序更快。因为前者可能在每次付出了大的切换代价后只做了一点点“正事”,而后者在不停地做“正事”。不过单线程也是有代价的,它工作良好的前提是“正事”都很快,否则一旦某次变慢就使后续的所有“正事”都被延迟了。在一些处理时间普遍较短的程序中,使用(多个不相交的)单线程能最大程度地”做正事“,由于每个请求的处理时间确定,延时表现也很稳定,各种http server正是这样。但我们的检索服务要做的事情可就复杂多了,有大量的后端服务需要访问,广泛存在的长尾请求使每次处理的时间无法确定,排序策略也越来越复杂。如果还是使用(多个不相交的)单线程的话,一次难以预计的性能抖动,或是一个大请求可能导致后续一堆请求被延迟。 + +为了避免请求之间相互影响,请求级的线程跳转是brpc必须付出的代价,我们能做的是使[线程跳转最优化](io.md#the-full-picture)。不过,对服务的性能测试还不能很好地体现这点。测试中的处理往往极为简单,使得线程切换的影响空前巨大,通过控制多线程和单线程处理的比例,我们可以把一个测试服务的qps从100万到500万操纵自如(同机),这损伤了性能测试结果的可信度。要知道,真实的服务并不是在累加一个数字,或者echo一个字符串,一个qps几百万的echo程序没有指导意义。鉴于此,在发起性能测试一年后(15年底),在brpc又经历了1200多次改动后,我们需要review所有的测试,加强其中的线程因素,以获得对真实场景有明确意义的结果。具体来说: + +- 请求不应等长,要有长尾。这能考察RPC能否让请求并发,否则一个慢请求会影响大量后续请求。 +- 要有多级server的场景。server内用client访问下游server,这能考察server和client的综合表现。 +- 要有一个client访问多个server的场景。这能考察负载均衡是否足够并发,真实场景中很少一个client只访问一个server。 + +我们希望这套测试场景对其他服务的性能测试有借鉴意义。 + +# 测试目标 + +## UB + +百度在08年开发的RPC框架,在百度产品线广泛使用,已被brpc代替。UB的每个请求独占一个连接(连接池),在大规模服务中每台机器都需要保持大量的连接,限制了其使用场景,像百度的分布式系统没有用UB。UB只支持nshead+mcpack协议,也没怎么考虑扩展性,所以增加新协议和新功能往往要调整大段代码,在实践中大部分人“知难而退”了。UB缺乏调试和运维接口,服务的运行状态对用户基本是黑盒,只能靠低效地打日志来追踪问题,服务出现问题时常要拉上维护者一起排查,效率很低。UB有多个变种: + +* ubrpc: 百度在10年基于UB开发的RPC框架,用.idl文件(类似.proto)描述数据的schema,而不是手动打包。这个RPC有被使用,但不广泛。 + +- nova_pbrpc: 百度网盟团队在12年基于UB开发的RPC框架,用protobuf代替mcpack作为序列化方法,协议是nshead + user's protobuf。 +- public_pbrpc: 百度在13年初基于UB开发的RPC框架,用protobuf代替mcpack作为序列化方法,但协议与nova_pbrpc不同,大致是nshead + meta protobuf。meta protobuf中有个string字段包含user's protobuf。由于用户数据要序列化两次,这个RPC的性能很差,没有被推广开来。 + +我们以在百度网盟团队广泛使用的nova_pbrpc为UB的代表。测试时其代码为r10500。早期的UB支持CPOOL和XPOOL,分别使用[select](http://linux.die.net/man/2/select)和[leader-follower模型](http://kircher-schwanninger.de/michael/publications/lf.pdf),后来提供了EPOLL,使用[epoll](http://man7.org/linux/man-pages/man7/epoll.7.html)处理多路连接。鉴于产品线大都是用EPOLL模型,我们的UB配置也使用EPOLL。UB只支持[连接池](client.md#连接方式),结果用“**ubrpc_mc**"指代(mc代表"multiple +connection")。虽然这个名称不太准确(见上文对ubrpc的介绍),但在本文的语境下,请默认ubrpc = UB。 + +## hulu-pbrpc + +百度在13年基于saber(kylin变种)和protobuf实现的RPC框架,hulu在多线程实现上有较多问题,已被brpc代替,测试时其代码为`pbrpc_2-0-15-27959_PD_BL`。hulu-pbrpc只支持单连接,结果用“**hulu-pbrpc**"指代。 + +## brpc + +INF在2014年底开发至今的rpc产品,支持百度内所有协议(不限于protobuf),并第一次统一了百度主要分布式系统和业务线的RPC框架。测试时代码为r31906。brpc既支持单连接也支持连接池,前者的结果用"**baidu-rpc**"指代,后者用“**baidu-rpc_mc**"指代。 + +## sofa-pbrpc + +百度大搜团队在13年基于boost::asio和protobuf实现的RPC框架,有多个版本,咨询相关同学后,确认ps/opensource下的和github上的较新,且会定期同步。故测试使用使用ps/opensource下的版本。测试时其代码为`sofa-pbrpc_1-0-2_BRANCH`。sofa-pbrpc只支持单连接,结果用“**sofa-pbrpc**”指代。 + +## apache thrift + +thrift是由facebook最早在07年开发的序列化方法和rpc框架,包含独特的序列化格式和IDL,支持很多编程语言。开源后改名[apache thrift](https://thrift.apache.org/),fb自己有一个[fbthrift分支](https://github.com/facebook/fbthrift),我们使用的是apache thrift。测试时其代码为`thrift_0-9-1-400_PD_BL`。thrift的缺点是: 代码看似分层清晰,client和server选择很多,但没有一个足够通用,每个server实现都只能解决很小一块场景,每个client都线程不安全,实际使用很麻烦。由于thrift没有线程安全的client,所以每个线程中都得建立一个client,使用独立的连接。在测试中thrift其实是占了其他实现的便宜: 它的client不需要处理多线程问题。thrift的结果用"**thrift_mc**"指代。 + +## gRPC + +由google开发的rpc框架,使用http/2和protobuf 3.0,测试时其代码为。gRPC并不是stubby,定位更像是为了推广http/2和protobuf 3.0,但鉴于很多人对它的表现很感兴趣,我们也(很麻烦地)把它加了进来。gRPC的结果用"**grpc**"指代。 + +# 测试方法 + +如序言中解释的那样,性能数字有巨大的调整空间。这里的关键在于,我们对RPC的底线要求是什么,脱离了这个底线,测试中的表现就严重偏离真实环境中的了。 + +这个底线我们认为是**RPC必须能处理长尾**。 + +在百度的环境中,这是句大白话,哪个产品线,哪个系统没有长尾呢?作为承载大部分服务的RPC框架自然得处理好长尾,减少长尾对正常请求的影响。但在实现层面,这个问题对设计的影响太大了。如果测试中没有长尾,那么RPC实现就可以假设每个请求都差不多快,这时候最优的方法是用多个线程独立地处理请求。由于没有上下文切换和cache一致性同步,程序的性能会显著高于多个线程协作时的表现。 + +比如简单的echo程序,处理一个请求只需要200-300纳秒,单个线程可以达到300-500万的吞吐。但如果多个线程协作,即使在极其流畅的系统中,也要付出3-5微秒的上下文切换代价和1微秒的cache同步代价,这还没有考虑多个线程间的其他互斥逻辑,一般来说单个线程的吞吐很难超过10万,即使24核全部用满,吞吐也只有240万,不及一个线程。这正是以http server为典型的服务选用[单线程模型](threading_overview.md#单线程reactor)的原因(多个线程独立运行eventloop): 大部分http请求的处理时间是可预测的,对下游的访问也不会有任何阻塞代码。这个模型可以最大化cpu利用率,同时提供可接受的延时。 + +多线程付出这么大的代价是为了**隔离请求间的影响**。一个计算复杂或索性阻塞的过程不会影响到其他请求,1%的长尾最终只会影响到1%的性能。而多个独立的线程是保证不了这点的,一个请求进入了一个线程就等于“定了终生”,如果前面的请求慢了一下,那也只能跟着慢了。1%的长尾会影响远超1%的请求,最终表现不佳。换句话说,乍看上去多线程模型“慢”了,但在真实应用中反而会获得更好的综合性能。 + +延时能精确地体现出长尾的干扰作用,如果普通请求的延时没有被长尾请求干扰,就说明RPC成功地隔离了请求。而QPS无法体现这点,只要CPU都在忙,即使一个正常请求进入了挤满长尾的队列而被严重延迟,最终的QPS也变化不大。为了测量长尾的干扰作用,我们在涉及到延时的测试中都增加了1%的长尾请求。 + +# 开始测试 + +## 环境 + +性能测试使用的机器配置为: + +- 单机1: CPU开超线程24核,E5-2620 @ 2.00GHz;64GB内存;OS linux 2.6.32_1-15-0-0 +- 多机1(15台+8台): CPU均未开超线程12核,其中15台的CPU为E5-2420 @ 1.90GHz.,64GB内存,千兆网卡,无法开启多队列。其余8台为E5-2620 2.0GHz,千兆网卡,绑定多队列到前8个核。这些长期测试机器比较杂,跨了多个机房,测试中延时在1ms以上的就是这批机器。 +- 多机2(30台): CPU未开超线程12核,E5-2620 v3 @ 2.40GHz.;96GB内存;OS linux 2.6.32_1-17-0-0;万兆网卡,绑定多队列到前8个核。这是临时借用的新机器,配置非常好,都在广州机房,延时非常短,测试中延时在几百微秒的就是这批机器。 + +下面所有的曲线图是使用brpc开发的dashboard程序绘制的,去掉路径后可以看到和所有brpc +server一样的[内置服务](builtin_service.md)。 + +## 配置 + +如无特殊说明,所有测试中的配置只是数量差异(线程数,请求大小,client个数etc),而不是模型差异。我们确保用户看到的qps和延时是同一个场景的不同维度,而不是无法统一的两个场景。 + +所有RPC server都配置了24个工作线程,这些线程一般运行用户的处理逻辑。关于每种RPC的特殊说明: + +- UB: 配置了12个reactor线程,使用EPOOL模型。连接池限制数配置为线程个数(24) +- hulu-pbrpc: 额外配置了12个IO线程。这些线程会处理fd读取,请求解析等任务。hulu有个“共享队列“的配置项,默认不打开,作用是把fd静态散列到多个线程中,由于线程间不再争抢,hulu的qps会显著提高,但会明显地被长尾影响(原因见[测试方法](#测试方法))。考虑到大部分使用者并不会去改配置,我们也选择不打开。 +- thrift: 额外配置了12个IO线程。这些线程会处理fd读取,请求解析等任务。thrift的client不支持多线程,每个线程得使用独立的client,连接也都是分开的。 +- sofa-pbrpc: 按照sofa同学的要求,把io_service_pool_size配置为24,work_thread_num配置为1。大概含义是使用独立的24组线程池,每组1个worker thread。和hulu不打开“共享队列”时类似,这个配置会显著提高sofa-pbrpc的QPS,但同时使它失去了处理长尾的能力。如果你在真实产品中使用,我们不建议这个配置。(而应该用io_service_pool_size=1, work_thread_num=24) +- brpc: 尽管brpc的client运行在bthread中时会获得10%~20%的QPS提升和更低的延时,但测试中的client都运行统一的pthread中。 + +所有的RPC client都以多个线程同步方式发送,这种方法最接近于真实系统中的情况,在考察QPS时也兼顾了延时因素。 + +一种流行的方案是client不停地往连接中写入数据看server表现,这个方法的弊端在于: server一下子能读出大量请求,不同RPC的比拼变成了“for循环执行用户代码”的比拼,而不是分发请求的效率。在真实系统中server很少能同时读到超过4个请求。这个方法也完全放弃了延时,client其实是让server陷入了雪崩时才会进入的状态,所有请求都因大量排队而超时了。 + +## 同机单client→单server在不同请求下的QPS(越高越好) + +本测试运行在[单机1](#环境)上。图中的数值均为用户数据的字节数,实际的请求尺寸还要包括协议头,一般会增加40字节左右。 + +(X轴是用户数据的字节数,Y轴是对应的QPS) + +![img](../images/qps_vs_reqsize.png) + +以_mc结尾的曲线代表client和server保持多个连接(线程数个),在本测试中会有更好的表现。 + +**分析** + + * brpc: 当请求包小于16KB时,单连接下的吞吐超过了多连接的ubrpc_mc和thrift_mc,随着请求包变大,内核对单个连接的写入速度成为瓶颈。而多连接下的brpc则达到了测试中最高的2.3GB/s。注意: 虽然使用连接池的brpc在发送大包时吞吐更高,但也会耗费更多的CPU(UB和thrift也是这样)。下图中的单连接brpc已经可以提供800多兆的吞吐,足以打满万兆网卡,而使用的CPU可能只有多链接下的1/2(写出过程是[wait-free的](io.md#发消息)),真实系统中请优先使用单链接。 +* thrift: 初期明显低于brpc,随着包变大超过了单连接的brpc。 +* UB:和thrift类似的曲线,但平均要低4-5万QPS,在32K包时超过了单连接的brpc。整个过程中QPS几乎没变过。 +* gRPC: 初期几乎与UB平行,但低1万左右,超过8K开始下降。 +* hulu-pbrpc和sofa-pbrpc: 512字节前高于UB和gRPC,但之后就急转直下,相继垫底。这个趋势是写不够并发的迹象。 + +## 同机单client→单server在不同线程数下的QPS(越高越好) + +本测试运行在[单机1](#环境)上。 + +(X轴是线程数,Y轴是对应的QPS) + +![img](../images/qps_vs_threadnum.png) + +**分析** + +brpc: 随着发送线程增加,QPS在快速增加,有很好的多线程扩展性。 + +UB和thrift: 8个线程下高于brpc,但超过8个线程后被brpc迅速超过,thrift继续“平移”,UB出现了明显下降。 + +gRPC,hulu-pbrpc,sofa-pbrpc: 几乎重合,256个线程时相比1个线程时只有1倍的提升,多线程扩展性不佳。 + +## 同机单client→单server在固定QPS下的延时[CDF](vars.md#统计和查看分位值)(越左越好,越直越好) +本测试运行在[单机1](#环境)上。考虑到不同RPC的处理能力,我们选择了一个较低、在不少系统中会达到的的QPS: 1万。 + +本测试中有1%的长尾请求耗时5毫秒,长尾请求的延时不计入结果,因为我们考察的是普通请求是否被及时处理了。 + +(X轴是延时(微秒),Y轴是小于X轴延时的请求比例) + +![img](../images/latency_cdf.png) + +**分析** +- brpc: 平均延时短,几乎没有被长尾影响。 +- UB和thrift: 平均延时比brpc高1毫秒,受长尾影响不大。 +- hulu-pbrpc: 走向和UB和thrift类似,但平均延时进一步增加了1毫秒。 +- gRPC : 初期不错,到长尾区域后表现糟糕,直接有一部分请求超时了。(反复测试都是这样,像是有bug) +- sofa-pbrpc: 30%的普通请求(上图未显示)被长尾严重干扰。 + +## 跨机多client→单server的QPS(越高越好) + +本测试运行在[多机1](#环境)上。 + +(X轴是client数,Y轴是对应的QPS) + +![img](../images/qps_vs_multi_client.png) + +**分析** +* brpc: 随着cilent增加,server的QPS在快速增加,有不错的client扩展性。 +* sofa-pbrpc: 随着client增加,server的QPS也在快速增加,但幅度不如brpc,client扩展性也不错。从16个client到32个client时的提升较小。 +* hulu-pbrpc: 随着client增加,server的QPS在增加,但幅度进一步小于sofa-pbrpc。 +* UB: 增加client几乎不能增加server的QPS。 +* thrift: 平均QPS低于UB,增加client几乎不能增加server的QPS。 +* gRPC: 垫底、增加client几乎不能增加server的QPS。 + +## 跨机多client→单server在固定QPS下的延时[CDF](vars.md#统计和查看分位值)(越左越好,越直越好) + +本测试运行在[多机1](#环境)上。负载均衡算法为round-robin或RPC默认提供的。由于有32个client且一些RPC的单client能力不佳,我们为每个client仅设定了2500QPS,这是一个真实业务系统能达到的数字。 + +本测试中有1%的长尾请求耗时15毫秒,长尾请求的延时不计入结果,因为我们考察的是普通请求是否被及时处理了。 + +(X轴是延时(微秒),Y轴是小于X轴延时的请求比例) + +![img](../images/multi_client_latency_cdf.png) + +**分析** +- brpc: 平均延时短,几乎没有被长尾影响。 +- UB和thrift: 平均延时短,受长尾影响小,平均延时高于brpc +- sofa-pbrpc: 14%的普通请求被长尾严重干扰。 +- hulu-pbrpc: 15%的普通请求被长尾严重干扰。 +- gRPC: 已经完全失控,非常糟糕。 + +## 跨机多client→多server在固定QPS下的延时[CDF](vars.md#统计和查看分位值)(越左越好,越直越好) + +本测试运行在[多机2](#环境)上。20台每台运行4个client,多线程同步访问10台server。负载均衡算法为round-robin或RPC默认提供的。由于gRPC访问多server较麻烦且有很大概率仍表现不佳,这个测试不包含gRPC。 + +本测试中有1%的长尾请求耗时10毫秒,长尾请求的延时不计入结果,因为我们考察的是普通请求是否被及时处理了。 + +(X轴是延时(微秒),Y轴是小于X轴延时的请求比例) + +![img](../images/multi_server_latency_cdf.png) + +**分析** +- brpc和UB: 平均延时短,几乎没有被长尾影响。 +- thrift: 平均延时显著高于brpc和UB。 +- sofa-pbrpc: 2.5%的普通请求被长尾严重干扰。 +- hulu-pbrpc: 22%的普通请求被长尾严重干扰。 + +## 跨机多client→多server→多server在固定QPS下的延时[CDF](vars.md#统计和查看分位值)(越左越好,越直越好) + +本测试运行在[多机2](#环境)上。14台每台运行4个client,多线程同步访问8台server,这些server还会同步访问另外8台server。负载均衡算法为round-robin或RPC默认提供的。由于gRPC访问多server较麻烦且有很大概率仍表现不佳,这个测试不包含gRPC。 + +本测试中有1%的长尾请求耗时10毫秒,长尾请求的延时不计入结果,因为我们考察的是普通请求是否被及时处理了。 + +(X轴是延时(微秒),Y轴是小于X轴延时的请求比例) + +![img](../images/twolevel_server_latency_cdf.png) + +**分析** +- brpc: 平均延时短,几乎没有被长尾影响。 +- UB: 平均延时短,长尾区域略差于brpc。 +- thrift: 平均延时显著高于brpc和UB。 +- sofa-pbrpc: 17%的普通请求被长尾严重干扰,其中2%的请求延时极长。 +- hulu-pbrpc: 基本消失在视野中,已无法正常工作。 + +# 结论 + +brpc: 在吞吐,平均延时,长尾处理上都表现优秀。 + +UB: 平均延时和长尾处理的表现都不错,吞吐的扩展性较差,提高线程数和client数几乎不能提升吞吐。 + +thrift: 单机的平均延时和吞吐尚可,多机的平均延时明显高于brpc和UB。吞吐的扩展性较差,提高线程数和client数几乎不能提升吞吐。 + +sofa-pbrpc: 处理小包的吞吐尚可,大包的吞吐显著低于其他RPC,延时受长尾影响很大。 + +hulu-pbrpc: 单机表现和sofa-pbrpc类似,但多机的延时表现极差。 + +gRPC: 几乎在所有参与的测试中垫底,可能它的定位是给google cloud platform的用户提供一个多语言,对网络友好的实现,性能还不是要务。 + diff --git a/docs/cn/benchmark_http.md b/docs/cn/benchmark_http.md new file mode 100644 index 0000000..36086fc --- /dev/null +++ b/docs/cn/benchmark_http.md @@ -0,0 +1,5 @@ +可代替[ab](https://httpd.apache.org/docs/2.2/programs/ab.html)测试http server极限性能。ab功能较多但年代久远,有时本身可能会成为瓶颈。benchmark_http基本上就是一个brpc http client,性能很高,功能较少,一般压测够用了。 + +使用方法: + +首先你得[下载和编译](getting_started.md)了brpc源码,然后去example/http_c++目录编译,成功后应该能看到benchmark_http。 diff --git a/docs/cn/brpc_intro.pptx b/docs/cn/brpc_intro.pptx new file mode 100644 index 0000000..b3bd4b2 Binary files /dev/null and b/docs/cn/brpc_intro.pptx differ diff --git a/docs/cn/bthread.md b/docs/cn/bthread.md new file mode 100644 index 0000000..f0de58b --- /dev/null +++ b/docs/cn/bthread.md @@ -0,0 +1,66 @@ +[bthread](https://github.com/apache/brpc/tree/master/src/bthread)是brpc使用的M:N线程库,目的是在提高程序的并发度的同时,降低编码难度,并在核数日益增多的CPU上提供更好的scalability和cache locality。”M:N“是指M个bthread会映射至N个pthread,一般M远大于N。由于linux当下的pthread实现([NPTL](http://en.wikipedia.org/wiki/Native_POSIX_Thread_Library))是1:1的,M个bthread也相当于映射至N个[LWP](http://en.wikipedia.org/wiki/Light-weight_process)。bthread的前身是Distributed Process(DP)中的fiber,一个N:1的合作式线程库,等价于event-loop库,但写的是同步代码。 + +# Goals + +- 用户可以延续同步的编程模式,能在数百纳秒内建立bthread,可以用多种原语同步。 +- bthread所有接口可在pthread中被调用并有合理的行为,使用bthread的代码可以在pthread中正常执行。 +- 能充分利用多核。 +- better cache locality, supporting NUMA is a plus. + +# NonGoals + +- 提供pthread的兼容接口,只需链接即可使用。**拒绝理由**: bthread没有优先级,不适用于所有的场景,链接的方式容易使用户在不知情的情况下误用bthread,造成bug。 +- 覆盖各类可能阻塞的glibc函数和系统调用,让原本阻塞系统线程的函数改为阻塞bthread。**拒绝理由**: + - bthread阻塞可能切换系统线程,依赖系统TLS的函数的行为未定义。 + - 和阻塞pthread的函数混用时可能死锁。 + - 这类hook函数本身的效率一般更差,因为往往还需要额外的系统调用,如epoll。但这类覆盖对N:1合作式线程库(fiber)有一定意义:虽然函数本身慢了,但若不覆盖会更慢(系统线程阻塞会导致所有fiber阻塞)。 +- 修改内核让pthread支持同核快速切换。**拒绝理由**: 拥有大量pthread后,每个线程对资源的需求被稀释了,基于thread-local cache的代码效果会很差,比如tcmalloc。而独立的bthread不会有这个问题,因为它最终还是被映射到了少量的pthread。bthread相比pthread的性能提升很大一部分来自更集中的线程资源。另一个考量是可移植性,bthread更倾向于纯用户态代码。 + +# FAQ + +##### Q:bthread是协程(coroutine)吗? + +不是。我们常说的协程特指N:1线程库,即所有的协程运行于一个系统线程中,计算能力和各类eventloop库等价。由于不跨线程,协程之间的切换不需要系统调用,可以非常快(100ns-200ns),受cache一致性的影响也小。但代价是协程无法高效地利用多核,代码必须非阻塞,否则所有的协程都被卡住,对开发者要求苛刻。协程的这个特点使其适合写运行时间确定的IO服务器,典型如http server,在一些精心调试的场景中,可以达到非常高的吞吐。但百度内大部分在线服务的运行时间并不确定,且很多检索由几十人合作完成,一个缓慢的函数会卡住所有的协程。在这点上eventloop是类似的,一个回调卡住整个loop就卡住了,比如ub**a**server(注意那个a,不是ubserver)是百度对异步框架的尝试,由多个并行的eventloop组成,真实表现糟糕:回调里打日志慢一些,访问redis卡顿,计算重一点,等待中的其他请求就会大量超时。所以这个框架从未流行起来。 + +bthread是一个M:N线程库,一个bthread被卡住不会影响其他bthread。关键技术两点:work stealing调度和butex,前者让bthread更快地被调度到更多的核心上,后者让bthread和pthread可以相互等待和唤醒。这两点协程都不需要。更多线程的知识查看[这里](threading_overview.md)。 + +##### Q: 我应该在程序中多使用bthread吗? + +不应该。除非你需要在一次RPC过程中[让一些代码并发运行](bthread_or_not.md),你不应该直接调用bthread函数,把这些留给brpc做更好。 + +##### Q:bthread和pthread worker如何对应? + +pthread worker在任何时间只会运行一个bthread,当前bthread挂起时,pthread worker先尝试从本地runqueue弹出一个待运行的bthread,若没有,则随机偷另一个worker的待运行bthread,仍然没有才睡眠并会在有新的待运行bthread时被唤醒。 + +##### Q:bthread中能调用阻塞的pthread或系统函数吗? + +可以,只阻塞当前pthread worker。其他pthread worker不受影响。 + +##### Q:一个bthread阻塞会影响其他bthread吗? + +不影响。若bthread因bthread API而阻塞,它会把当前pthread worker让给其他bthread。若bthread因pthread API或系统函数而阻塞,当前pthread worker上待运行的bthread会被其他空闲的pthread worker偷过去运行。 + +##### Q:pthread中可以调用bthread API吗? + +可以。bthread API在bthread中被调用时影响的是当前bthread,在pthread中被调用时影响的是当前pthread。使用bthread API的代码可以直接运行在pthread中。 + +##### Q:若有大量的bthread调用了阻塞的pthread或系统函数,会影响RPC运行么? + +会。比如有8个pthread worker,当有8个bthread都调用了系统usleep()后,处理网络收发的RPC代码就暂时无法运行了。只要阻塞时间不太长, 这一般**没什么影响**, 毕竟worker都用完了, 除了排队也没有什么好方法. +在brpc中用户可以选择调大worker数来缓解问题, 在server端可设置[ServerOptions.num_threads](server.md#worker线程数)或[-bthread_concurrency](http://brpc.baidu.com:8765/flags/bthread_concurrency), 在client端可设置[-bthread_concurrency](http://brpc.baidu.com:8765/flags/bthread_concurrency). + +那有没有完全规避的方法呢? + +- 一个容易想到的方法是动态增加worker数. 但实际未必如意, 当大量的worker同时被阻塞时, + 它们很可能在等待同一个资源(比如同一把锁), 增加worker可能只是增加了更多的等待者. +- 那区分io线程和worker线程? io线程专门处理收发, worker线程调用用户逻辑, 即使worker线程全部阻塞也不会影响io线程. 但增加一层处理环节(io线程)并不能缓解拥塞, 如果worker线程全部卡住, 程序仍然会卡住, + 只是卡的地方从socket缓冲转移到了io线程和worker线程之间的消息队列. 换句话说, 在worker卡住时, + 还在运行的io线程做的可能是无用功. 事实上, 这正是上面提到的**没什么影响**真正的含义. 另一个问题是每个请求都要从io线程跳转至worker线程, 增加了一次上下文切换, 在机器繁忙时, 切换都有一定概率无法被及时调度, 会导致更多的延时长尾. +- 一个实际的解决方法是[限制最大并发](server.md#限制最大并发), 只要同时被处理的请求数低于worker数, 自然可以规避掉"所有worker被阻塞"的情况. +- 另一个解决方法当被阻塞的worker超过阈值时(比如8个中的6个), 就不在原地调用用户代码了, 而是扔到一个独立的线程池中运行. 这样即使用户代码全部阻塞, 也总能保留几个worker处理rpc的收发. 不过目前bthread模式并没有这个机制, 但类似的机制在[打开pthread模式](server.md#pthread模式)时已经被实现了. 那像上面提到的, 这个机制是不是在用户代码都阻塞时也在做"无用功"呢? 可能是的. 但这个机制更多是为了规避在一些极端情况下的死锁, 比如所有的用户代码都lock在一个pthread mutex上, 并且这个mutex需要在某个RPC回调中unlock, 如果所有的worker都被阻塞, 那么就没有线程来处理RPC回调了, 整个程序就死锁了. 虽然绝大部分的RPC实现都有这个潜在问题, 但实际出现频率似乎很低, 只要养成不在锁内做RPC的好习惯, 这是完全可以规避的. + +##### Q:bthread会有[Channel](https://gobyexample.com/channels)吗? + +不会。channel代表的是两点间的关系,而很多现实问题是多点的,这个时候使用channel最自然的解决方案就是:有一个角色负责操作某件事情或某个资源,其他线程都通过channel向这个角色发号施令。如果我们在程序中设置N个角色,让它们各司其职,那么程序就能分类有序地运转下去。所以使用channel的潜台词就是把程序划分为不同的角色。channel固然直观,但是有代价:额外的上下文切换。做成任何事情都得等到被调用处被调度,处理,回复,调用处才能继续。这个再怎么优化,再怎么尊重cache locality,也是有明显开销的。另外一个现实是:用channel的代码也不好写。由于业务一致性的限制,一些资源往往被绑定在一起,所以一个角色很可能身兼数职,但它做一件事情时便无法做另一件事情,而事情又有优先级。各种打断、跳出、继续形成的最终代码异常复杂。 + +我们需要的往往是buffered channel,扮演的是队列和有序执行的作用,bthread提供了[ExecutionQueue](execution_queue.md),可以完成这个目的。 diff --git a/docs/cn/bthread_id.md b/docs/cn/bthread_id.md new file mode 100644 index 0000000..d6ba8a2 --- /dev/null +++ b/docs/cn/bthread_id.md @@ -0,0 +1,36 @@ +bthread_id是一个特殊的同步结构,它可以互斥RPC过程中的不同环节,也可以O(1)时间内找到RPC上下文(即Controller)。注意,这里我们谈论的是bthread_id_t,不是bthread_t(bthread的tid),这个名字起的确实不太好,容易混淆。 + +具体来说,bthread_id解决的问题有: + +- 在发送RPC过程中response回来了,处理response的代码和发送代码产生竞争。 +- 设置timer后很快触发了,超时处理代码和发送代码产生竞争。 +- 重试产生的多个response同时回来产生的竞争。 +- 通过correlation_id在O(1)时间内找到对应的RPC上下文,而无需建立从correlation_id到RPC上下文的全局哈希表。 +- 取消RPC。 + +上文提到的bug在其他rpc框架中广泛存在,下面我们来看下brpc是如何通过bthread_id解决这些问题的。 + +bthread_id包括两部分,一个是用户可见的64位id,另一个是对应的不可见的bthread::Id结构体。用户接口都是操作id的。从id映射到结构体的方式和brpc中的[其他结构](memory_management.md)类似:32位是内存池的位移,32位是version。前者O(1)时间定位,后者防止ABA问题。 + +bthread_id的接口不太简洁,有不少API: + +- create +- lock +- unlock +- unlock_and_destroy +- join +- error + +这么多接口是为了满足不同的使用流程。 + +- 发送request的流程:bthread_id_create -> bthread_id_lock -> ... register timer and send RPC ... -> bthread_id_unlock +- 接收response的流程:bthread_id_lock -> ..process response -> bthread_id_unlock_and_destroy +- 异常处理流程:timeout/socket fail -> bthread_id_error -> 执行on_error回调(这里会加锁),分两种情况 + - 请求重试/backup request: 重新register timer and send RPC -> bthread_id_unlock + - 无法重试,最终失败:bthread_id_unlock_and_destroy +- 同步等待RPC结束:bthread_id_join + +为了减少等待,bthread_id做了一些优化的机制: + +- error发生的时候,如果bthread_id已经被锁住,会把error信息放到一个pending queue中,bthread_id_error函数立即返回。当bthread_id_unlock的时候,如果pending queue里面有任务就取出来执行。 +- RPC结束的时候,如果存在用户回调,先执行一个bthread_id_about_to_destroy,让正在等待的bthread_id_lock操作立即失败,再执行用户回调(这个可能耗时较长,不可控),最后再执行bthread_id_unlock_and_destroy diff --git a/docs/cn/bthread_or_not.md b/docs/cn/bthread_or_not.md new file mode 100644 index 0000000..27432ca --- /dev/null +++ b/docs/cn/bthread_or_not.md @@ -0,0 +1,59 @@ +brpc提供了[异步接口](client.md#异步访问),所以一个常见的问题是:我应该用异步接口还是bthread? + +短回答:延时不高时你应该先用简单易懂的同步接口,不行的话用异步接口,只有在需要多核并行计算时才用bthread。 + +# 同步或异步 + +异步即用回调代替阻塞,有阻塞的地方就有回调。虽然在javascript这种语言中回调工作的很好,接受度也非常高,但只要用过,就会发现这和我们需要的回调是两码事,这个区别不是[lambda](https://en.wikipedia.org/wiki/Anonymous_function),也不是[future](https://en.wikipedia.org/wiki/Futures_and_promises),而是javascript是单线程的。javascript的回调放到多线程下可能没有一个能跑过,竞争太多,单线程的同步方法和多线程的同步方法是完全不同的。那是不是服务能搞成类似的形式呢?多个线程,每个都是独立的eventloop。可以,ub**a**server就是(注意带a),但实际效果糟糕,因为阻塞改回调可不简单,当阻塞发生在循环,条件分支,深层子函数中时,改造特别困难,况且很多老代码、第三方代码根本不可能去改造。结果是代码中会出现不可避免的阻塞,导致那个线程中其他回调都被延迟,流量超时,server性能不符合预期。如果你说,”我想把现在的同步代码改造为大量的回调,除了我其他人都看不太懂,并且性能可能更差了”,我猜大部分人不会同意。别被那些鼓吹异步的人迷惑了,他们写的是从头到尾从下到上全异步且不考虑多线程的代码,和你要写的完全是两码事。 + +brpc中的异步和单线程的异步是完全不同的,异步回调会运行在与调用处不同的线程中,你会获得多核扩展性,但代价是你得意识到多线程问题。你可以在回调中阻塞,只要线程够用,对server整体的性能并不会有什么影响。不过异步代码还是很难写的,所以我们提供了[组合访问](combo_channel.md)来简化问题,通过组合不同的channel,你可以声明式地执行复杂的访问,而不用太关心其中的细节。 + +当然,延时不长,qps不高时,我们更建议使用同步接口,这也是创建bthread的动机:维持同步代码也能提升交互性能。 + +**判断使用同步或异步**:计算qps * latency(in seconds),如果和cpu核数是同一数量级,就用同步,否则用异步。 + +比如: + +- qps = 2000,latency = 10ms,计算结果 = 2000 * 0.01s = 20。和常见的32核在同一个数量级,用同步。 +- qps = 100, latency = 5s, 计算结果 = 100 * 5s = 500。和核数不在同一个数量级,用异步。 +- qps = 500, latency = 100ms,计算结果 = 500 * 0.1s = 50。基本在同一个数量级,可用同步。如果未来延时继续增长,考虑异步。 + +这个公式计算的是同时进行的平均请求数(你可以尝试证明一下),和线程数,cpu核数是可比的。当这个值远大于cpu核数时,说明大部分操作并不耗费cpu,而是让大量线程阻塞着,使用异步可以明显节省线程资源(栈占用的内存)。当这个值小于或和cpu核数差不多时,异步能节省的线程资源就很有限了,这时候简单易懂的同步代码更重要。 + +# 异步或bthread + +有了bthread这个工具,用户甚至可以自己实现异步。以“半同步”为例,在brpc中用户有多种选择: + +- 发起多个异步RPC后挨个Join,这个函数会阻塞直到RPC结束。(这儿是为了和bthread对比,实现中我们建议你使用[ParallelChannel](combo_channel.md#parallelchannel),而不是自己Join) +- 启动多个bthread各自执行同步RPC后挨个join bthreads。 + +哪种效率更高呢?显然是前者。后者不仅要付出创建bthread的代价,在RPC过程中bthread还被阻塞着,不能用于其他用途。 + +**如果仅仅是为了并发RPC,别用bthread。** + +不过当你需要并行计算时,问题就不同了。使用bthread可以简单地构建树形的并行计算,充分利用多核资源。比如检索过程中有三个环节可以并行处理,你可以建立两个bthread运行两个环节,在原地运行剩下的环节,最后join那两个bthread。过程大致如下: +```c++ +bool search() { + ... + bthread th1, th2; + if (bthread_start_background(&th1, NULL, part1, part1_args) != 0) { + LOG(ERROR) << "Fail to create bthread for part1"; + return false; + } + if (bthread_start_background(&th2, NULL, part2, part2_args) != 0) { + LOG(ERROR) << "Fail to create bthread for part2"; + return false; + } + part3(part3_args); + bthread_join(th1); + bthread_join(th2); + return true; +} +``` +这么实现的point: +- 你当然可以建立三个bthread分别执行三个部分,最后join它们,但相比这个方法要多耗费一个线程资源。 +- bthread从建立到执行是有延时的(调度延时),在不是很忙的机器上,这个延时的中位数在3微秒左右,90%在10微秒内,99.99%在30微秒内。这说明两点: + - 计算时间超过1ms时收益比较明显。如果计算非常简单,几微秒就结束了,用bthread是没有意义的。 + - 尽量让原地运行的部分最慢,那样bthread中的部分即使被延迟了几微秒,最后可能还是会先结束,而消除掉延迟的影响。并且join一个已结束的bthread时会立刻返回,不会有上下文切换开销。 + +另外当你有类似线程池的需求时,像执行一类job的线程池时,也可以用bthread代替。如果对job的执行顺序有要求,你可以使用基于bthread的[ExecutionQueue](execution_queue.md)。 diff --git a/docs/cn/bthread_tagged_task_group.md b/docs/cn/bthread_tagged_task_group.md new file mode 100644 index 0000000..027bd4e --- /dev/null +++ b/docs/cn/bthread_tagged_task_group.md @@ -0,0 +1,53 @@ + +# Bthread tagged task group + +在很多应用开发过程中都会有线程资源隔离的需求,比如服务分为控制层和数据层,数据层的请求压力大,不希望控制层受到影响;再比如,服务有多个磁盘,希望服务不同磁盘的线程之间没有什么影响;bthread的任务组打标签就是实现bthread的worker线程池按照tag分组,让不同分组之间达到没有互相影响的目的。服务是按照server级别做tag分组的,用户需要将不同分组的service安排到不同server上,不同server将使用不同端口。还有些场景需要有一些后台任务或者定时任务在单独的线程池中调度,这些任务没有service,这种情况也可以使用tag分组专门划分一个线程池,让这些任务在这个tag分组上执行,这个线程池的并发度任务数等都由用户自己控制。用户可以在这个基础上实现多种策略,比如,将tag组限制在NUMA的某个组,设置一些线程本地变量等。 +实现的逻辑就是在bthread层面创建了多个worker分组,每个分组的处理逻辑和原来一样;bthread接口层面在bthread_attr_t里面增加了tag字段,让用户去设置;rpc层面在brpc::ServerOptions里面增加了bthread_tag字段,用于指定这个server在哪个worker分组上执行。 + + +# 使用方式 + +在example/bthread_tag_echo_c++里面有一个实例代码,分别启动服务端和客户端,服务端将worker划分成3个tag(分组),例子里面可以设置FLAGS_tag1,FLAGS_tag2,给不同server打标签。剩下的一个tag(分组)给服务的后台任务使用。 + +```c++ +服务端启动 +./echo_server -task_group_ntags 3 -tag1 0 -tag2 1 -bthread_concurrency 20 -bthread_min_concurrency 8 -event_dispatcher_num 1 + +客户端启动 +./echo_client -dummy_port 8888 -server "0.0.0.0:8002" -use_bthread true +./echo_client -dummy_port 8889 -server "0.0.0.0:8003" -use_bthread true +``` + +FLAGS_bthread_concurrency为所有线程的数,FLAGS_bthread_min_concurrency为所有分组的线程数的下限,FLAGS_event_dispatcher_num为单个分组中事件驱动器的数量。FLAGS_bthread_current_tag为将要修改的分组的tag值,FLAGS_bthread_concurrency_by_tag设置这个分组的线程数。 +一般情况应用创建的bthread不需要设置bthread_attr_t的tag字段,创建的bthread会在当前tag上下文中执行;如果希望创建的bthread不在当前tag上下文中执行,可以设置bthread_attr_t的tag字段为希望的值,这么做会对性能有些损失,关键路径上应该避免这么做。 + +Q:如何动态改变分组线程的数量? + +A:你可以根据你的服务更自由的设计你的每个分组的线程数,启动的时候会根据你设置的 bthread_concurrency 来初始化线程池,如果你设置了 bthread_min_concurrency,那么会根据 bthread_min_concurrency 来设置线程池,对于 server 来说,num_threads 就是该 tag 对应的 worker 数量。可以通过设置 FLAGS_bthread_current_tag 和 FLAGS_bthread_concurrency_by_tag 来改变某个分组的线程数。如果没有设置(相当于没有启用分组,默认值为BTHREAD_TAG_INVALID),num_threads的含义是所有分组的 worker 总数。 + +Q:不同分组之间有什么关系吗? + +A:不同分组是独立的线程池和事件驱动器,完全没有关系。 + +Q:可以在分组之间做bthread的同步操作吗? + +A:可以的,每个bthread都有自己的tag标签,挂起后重新投入运行将继续在这个tag的线程池上执行。 + +Q:客户端发送和接收RPC消息是在哪个分组上执行的? + +A:这取决于客户端的上下文,如果客户端不在任何tag分组上,那将使用tag0分组收发消息;否则将在当前所在的tag分组收发消息。 + +Q:如何将一个分组的线程绑定到指定的一些cpu上面。 + +A:int bthread_set_tagged_worker_startfn(void (*start_fn)(bthread_tag_t))这个函数用于在某个分组上做一些初始化的工作,比如:可以实现绑核的代码,根据tag入参来确定不同分组绑定不同的cpu。 + +# 监控 + +目前监控按照tag划分的指标有,线程的数量、线程的使用量、bthread_count、连接信息 + +线程使用量:![img](../images/bthread_tagged_worker_usage.png) + +动态调整tag1分组的线程数 + +设置tag1:![img](../images/bthread_tagged_increment_tag1.png) +设置所有tag:![img](../images/bthread_tagged_increment_all.png) diff --git a/docs/cn/bthread_tracer.md b/docs/cn/bthread_tracer.md new file mode 100644 index 0000000..bab09ce --- /dev/null +++ b/docs/cn/bthread_tracer.md @@ -0,0 +1,217 @@ +gdb(ptrace)+ gdb_bthread_stack.py主要的缺点是要慢和阻塞进程,需要一种高效的追踪bthread调用栈的方法。 + +bRPC框架的协作式用户态协程无法像Golang内建的抢占式协程一样实现高效的STW(Stop the World),框架也无法干预用户逻辑的执行,所以要追踪bthread调用栈是比较困难的。 + +在线追踪bthread调用栈需要解决以下问题: +1. 追踪挂起bthread的调用栈。 +2. 追踪运行中bthread的调用栈。 + +# bthread状态模型 + +以下是目前的bthread状态模型。 + +![bthread状态模型](../images/bthread_status_model.svg) + +# 设计方案 + +## 核心思路 + +为了解决上述两个问题,该方案实现了STB(Stop The Bthread),核心思路可以简单总结为,在追踪bthread调用栈的过程中,状态不能流转到当前追踪方法不支持的状态。STB包含了两种追踪模式:上下文(context)追踪模式和信号追踪模式。 + +### 上下文(context)追踪模式 +上下文追踪模式可以追踪挂起bthread的调用栈。挂起的bthread栈是稳定的,利用TaskMeta.stack中保存的上下文信息(x86_64下关键的寄存器主要是RIP、RSP、RBP),通过一些可以回溯指定上下文调用栈的库来追踪bthread调用栈。但是挂起的bthread随时可能会被唤醒,执行逻辑(包括jump_stack),则bthread栈会一直变化。不稳定的上下文是不能用来追踪调用栈的,需要在jump_stack前拦截bthread的调度,等到调用栈追踪完成后才继续运行bthread。所以,上下文追踪模式支持就绪、挂起这两个状态。 + +### 信号追踪模式 + +信号追踪模式可以追踪运行中bthread的调用栈。运行中bthread是不稳定的,不能使用TaskMeta.stack来追踪bthread调用栈。只能另辟蹊径,使用信号中断bthread运行逻辑,在信号处理函数中回溯bthread调用栈。使用信号有两个问题: + +1. 异步信号安全问题。 +2. 信号追踪模式不支持jump_stack。调用栈回溯需要寄存器信息,但jump_stack会操作寄存器,这个过程是不安全的,所以jump_stack不能被信号中断,需要在jump_stack前拦截bthread的调度,等到bthread调用栈追踪完成后才继续挂起bthread。 + +所以,追踪模式只支持运行状态。 + +### 小结 + +jump_stack是bthread挂起或者运行的必经之路,也是STB的拦截点。STB将状态分成三类: +1. 上下文追踪模式的状态:就绪、挂起。 +2. 支持信号追踪模式的状态:运行。 +3. 不支持追踪的状态。jump_stack的过程是不允许使用以上两种调用栈追踪方法,需要在jump_stack前拦截bthread的调度,等到调用栈追踪完成后才继续调度bthread。 + +### 详细流程 + +以下是引入STB后的bthread状态模型,在原来bthread状态模型的基础上,加入两个状态(拦截点):将运行、挂起中。 + +![bthread STB状态模型](../images/bthread_stb_model.svg) + +经过上述分析,总结出STB的流程: + +1. TaskTracer(实现STB的一个模块)收到追踪bthread调用栈的请求时,标识正在追踪。追踪完成后,标识追踪完成,并TaskTracer发信号通知可能处于将运行或者挂起中状态的bthread。根据bthread状态,TaskTracer执行不同的逻辑: +- 创建、就绪但还没分配栈、销毁:直接结束追踪。 +- 挂起、就绪:使用上下文追踪模式追踪bthread的调用栈。 +- 运行:使用信号追踪模式追踪bthread的调用栈。 +- 将运行、挂起中:TaskTracer自旋等到bthread状态流转到下一个状态(挂起或者运行)后继续追踪。 + +2. TaskTracer追踪时,bthread根据状态也会执行不同的逻辑: +- 创建、就绪但还没分配栈、就绪:不需要额外处理。 +- 挂起、运行:通知TaskTracer继续追踪。 +- 将运行、挂起中、销毁:bthread通过条件变量等到TaskTracer追踪完成。TaskTracer追踪完成后会通过条件变量通知bthread继续执行jump_stack。 + +# 使用方法 + +1. 下载安装libunwind和abseil-cpp。**注意:libunwind 必须从源码编译,不要使用系统包管理器安装的 `libunwind-dev` / `libunwind-devel`**,否则会触发本文末尾「[已知问题:libunwind 与 libgcc_s 的 `_Unwind_*` 符号冲突](#已知问题libunwind-与-libgcc_s-的-_unwind_-符号冲突)」中描述的崩溃。bazel 构建可以跳过此步,直接使用 brpc 仓库自维护的 libunwind 版本。 +2. 给config_brpc.sh增加`--with-bthread-tracer`选项或者给cmake增加`-DWITH_BTHREAD_TRACER=ON`选项或者给bazel(Bzlmod模式)增加`--define with_bthread_tracer=true`选项。 +3. 访问服务的内置服务:`http://ip:port/bthreads/?st=1`或者代码里调用`bthread::stack_trace()`函数。 +4. 如果希望追踪pthread的调用栈,在对应pthread上调用`bthread::init_for_pthread_stack_trace()`函数获取一个伪bthread_t,然后使用步骤3即可获取pthread调用栈。 + +下面是追踪bthread调用栈的输出示例: +```shell +#0 0x00007fdbbed500b5 __clock_gettime_2 +#1 0x000000000041f2b6 butil::cpuwide_time_ns() +#2 0x000000000041f289 butil::cpuwide_time_us() +#3 0x000000000041f1b9 butil::EveryManyUS::operator bool() +#4 0x0000000000413289 (anonymous namespace)::spin_and_log() +#5 0x00007fdbbfa58dc0 bthread::TaskGroup::task_runner() +``` + +# 已知问题 + +## libunwind 与 libgcc_s 的 `_Unwind_*` 符号冲突 + +### 现象 + +启用 bthread tracer 后,可能在 `bthread_exit` / `pthread_exit` 或者 C++ 异常处理路径上偶发段错误,类似如下调用栈: + +```text +#0 0x0000000000000000 in ?? () +#1 0x00007fa2b5d6458a in _ULx86_64_dwarf_find_proc_info () + from /root/.cache/bazel/_bazel_root/743b333b2429a1dbd390ef66b59c771d/execroot/_main/bazel-out/k8-fastbuild/bin/test/../_solib_k8/libexternal_Slibunwind~_Slibunwind.so +#2 0x00007fa2b5d6668d in fetch_proc_info () + from /root/.cache/bazel/_bazel_root/743b333b2429a1dbd390ef66b59c771d/execroot/_main/bazel-out/k8-fastbuild/bin/test/../_solib_k8/libexternal_Slibunwind~_Slibunwind.so +#3 0x00007fa2b5d681a1 in _ULx86_64_dwarf_make_proc_info () + from /root/.cache/bazel/_bazel_root/743b333b2429a1dbd390ef66b59c771d/execroot/_main/bazel-out/k8-fastbuild/bin/test/../_solib_k8/libexternal_Slibunwind~_Slibunwind.so +#4 0x00007fa2b5d70cfd in _ULx86_64_get_proc_info () + from /root/.cache/bazel/_bazel_root/743b333b2429a1dbd390ef66b59c771d/execroot/_main/bazel-out/k8-fastbuild/bin/test/../_solib_k8/libexternal_Slibunwind~_Slibunwind.so +#5 0x00007fa2b5d6c775 in __libunwind_Unwind_GetLanguageSpecificData () + from /root/.cache/bazel/_bazel_root/743b333b2429a1dbd390ef66b59c771d/execroot/_main/bazel-out/k8-fastbuild/bin/test/../_solib_k8/libexternal_Slibunwind~_Slibunwind.so +#6 0x00007fa2b503c6df in __gxx_personality_v0 () from /lib/x86_64-linux-gnu/libstdc++.so.6 +#7 0x00007fa2b5452ce5 in ?? () from /lib/x86_64-linux-gnu/libgcc_s.so.1 +#8 0x00007fa2b54533c0 in _Unwind_ForcedUnwind () from /lib/x86_64-linux-gnu/libgcc_s.so.1 +#9 0x00007fa2b4ca57a4 in __GI___pthread_unwind (buf=) at ./nptl/unwind.c:130 +#10 0x00007fa2b4c9dd22 in __do_cancel () at ../sysdeps/nptl/pthreadP.h:271 +#11 __GI___pthread_exit (value=0x0) at ./nptl/pthread_exit.c:36 +#12 0x0000000000000000 in ?? () +``` + +### 根因 + +libunwind 的 `src/unwind/*.c` 实现了 GCC 的 `_Unwind_*` ABI 兼容层(`_Unwind_GetLanguageSpecificData`、`_Unwind_ForcedUnwind`、`_Unwind_Resume` 等),与 `libgcc_s.so.1` 提供同名的全局符号。当 libunwind 以**动态库**形式被链接,并且在最终二进制的 `DT_NEEDED` 列表中位置比 `libgcc_s.so.1` 靠前时,运行时动态链接器 `ld.so` 会把 `pthread_exit` / 异常处理触发的 `_Unwind_*` 调用解析到 libunwind 中的 DWARF 实现。该实现需要的内部上下文在 `pthread_exit` 路径上未被 bRPC 初始化好,从而触发空指针访问。 + +这是一个 ELF **运行时符号解析顺序**问题,与编译器(GCC / Clang)无关 —— Clang 默认运行时同样使用 `libstdc++ + libgcc_s`,会复现完全一致的崩溃。 + +### 解决方案 + +> **重要:不要使用系统包管理器安装的 libunwind**(例如 `apt install libunwind-dev`、`yum install libunwind-devel`)。多数发行版打包的 `libunwind.so` 仍把 `_Unwind_*` 暴露在动态符号表中,会触发本节描述的崩溃。 +> +> 必须使用**从源码编译的 libunwind**。上游 `./configure` + `make` 默认会通过 `-Wl,--version-script` 把 `_Unwind_*` 标为 local,不导出到动态符号表,从而避免冲突。 + +下表汇总了三种构建方式的推荐方案: + +| 构建方式 | 推荐方案 | +|---|---| +| `config_brpc.sh` + `make` | 从源码编译并安装 libunwind,把头文件与库目录显式传给 `config_brpc.sh` | +| `cmake` | 从源码编译并安装 libunwind,把头文件与库目录显式传给 `cmake` | +| `bazel`(Bzlmod) | 直接使用 brpc 仓库自维护的 libunwind 版本 | + +### make (config_brpc.sh) + +源码编译并安装 libunwind 到一个独立目录(避免污染系统目录),然后让 `config_brpc.sh` 显式从该目录查找 libunwind。 + +```bash +# 1) 源码编译 libunwind(推荐 v1.8.1 或以上版本) +git clone https://github.com/libunwind/libunwind.git +cd libunwind && git checkout tags/v1.8.1 +mkdir -p /opt/libunwind +autoreconf -i +./configure --prefix=/opt/libunwind +make -j$(nproc) && make install +cd .. + +# 2) 让 config_brpc.sh 使用 /libunwind 下的头文件与库(不要让它自动找到系统的 libunwind-dev) +cd brpc +sh config_brpc.sh \ + --with-bthread-tracer \ + --headers="/opt/libunwind/include /usr/include" \ + --libs="/opt/libunwind/lib /usr/lib /usr/lib64" +make -j$(nproc) +``` + +构建完成后可用以下命令确认 libunwind.so 没有导出 `_Unwind_*`: + +```bash +nm -D /libunwind/lib/libunwind.so | grep ' T _Unwind_' \ + && echo "WARN: _Unwind_* exported" \ + || echo "OK: _Unwind_* hidden" +``` + +### cmake + +[`CMakeLists.txt`](../../CMakeLists.txt:90-100) 通过 `find_library(... NAMES unwind unwind-x86_64)` 查找 libunwind。同样需要先源码编译 libunwind 到独立前缀,再用 `CMAKE_PREFIX_PATH` 让 cmake 优先在该前缀下查找: + +```bash +# 1) 源码编译 libunwind(同 make 章节) + +# 2) 让 cmake 在 /libunwind 下优先查找头文件和库 +cd brpc +mkdir build && cd build +cmake -DWITH_BTHREAD_TRACER=ON \ + -DCMAKE_PREFIX_PATH=/opt/libunwind \ + .. +make -j$(nproc) +``` + +> 提示:如果系统已经装了 `libunwind-dev`,`find_library` 仍可能优先匹配到 `/usr/lib`。可在 cmake 命令上额外指定 +> `-DLIBUNWIND_LIB=/libunwind/lib/libunwind.so -DLIBUNWIND_X86_64_LIB=/libunwind/lib/libunwind-x86_64.so -DLIBUNWIND_INCLUDE_PATH=/libunwind/include` +> 强制走自编译版本,避免系统包混入。 + +### bazel (Bzlmod) + +bRPC 仓库已经在 [`registry/modules/libunwind/`](../../registry/modules/libunwind/) 维护了一份 libunwind 的 Bzlmod overlay,并通过 [`.bazelrc`](../../.bazelrc) 中的 `--registry=https://github.com/apache/brpc/registry` 使用 bRPC 维护的 overlay。版本号采用 `.brpc-no-unwind` 后缀(例如 `1.8.3.brpc-no-unwind`),用以区别于 BCR 上的同基版本。该 overlay 增加了一个开关: + +``` +--define libunwind_hide_unwind_symbols=true +``` + +开启后,libunwind 的 `src/unwind/*.c`(即 GCC `_Unwind_*` 兼容层)整体不参与编译,等效于上游 autoconf 默认效果。bRPC 只使用 libunwind 的 `unw_*` 原生 API(`unw_getcontext`、`unw_init_local`、`unw_step` 等),不依赖 `_Unwind_*` 兼容层,因此该开关安全无副作用。 + +`.bazelrc` 已默认在 `build:test` / `test` 配置下打开此开关: + +``` +build:test --define libunwind_hide_unwind_symbols=true +test --define libunwind_hide_unwind_symbols=true +``` + +用户按文档使用方法第 2 步加上 `--define=with_bthread_tracer=true` 即可: + +```bash +# 测试场景,.bazelrc 中 test 配置已自动带上 hide 开关 +bazel test //test:bthread_unittest + +# 非测试构建(生产部署),需要显式同时带上两个 define +bazel build --define=with_bthread_tracer=true \ + --define=libunwind_hide_unwind_symbols=true \ + //... +``` + +> **特别注意**:如果在生产构建中只启用 `--define=with_bthread_tracer=true` 而漏掉 `--define=libunwind_hide_unwind_symbols=true`,binary 在 `pthread_exit` / 异常路径上会概率性崩溃。 + +构建后可用以下命令验证 libunwind 共享库没有导出 `_Unwind_*`: + +```bash +nm -D bazel-bin/external/_solib_*/libexternal*libunwind*.so 2>/dev/null \ + | grep ' T _Unwind_' || echo "OK: no _Unwind_* exported by libunwind.so" +``` + + +# 相关flag + +- `signal_trace_timeout_ms`:信号追踪模式的超时时间,默认为50ms。 \ No newline at end of file diff --git a/docs/cn/builtin_service.md b/docs/cn/builtin_service.md new file mode 100644 index 0000000..1cec53e --- /dev/null +++ b/docs/cn/builtin_service.md @@ -0,0 +1,57 @@ +[English version](../en/builtin_service.md) + +# 什么是内置服务? + +内置服务以多种形式展现服务器内部状态,提高你开发和调试服务的效率。brpc通过HTTP协议提供内置服务,可通过浏览器或curl访问,服务器会根据User-Agent返回纯文本或html,你也可以添加?console=1要求返回纯文本。我们在自己的开发机上启动了[一个长期运行的例子](http://brpc.baidu.com:8765/)(只能百度内访问),你可以点击后随便看看。如果服务端口被限(比如百度内不是所有的端口都能被笔记本访问到),可以使用[rpc_view](rpc_view.md)转发。 + +下面是分别从浏览器和终端访问的截图,注意其中的logo是百度内部的名称,在开源版本中是brpc。 + +**从浏览器访问** + +![img](../images/builtin_service_more.png) + +**从命令行访问** ![img](../images/builtin_service_from_console.png) + +# 安全模式 + +出于安全考虑,直接对外服务需要隐藏内置服务(包括经过nginx或其他http server转发流量的),具体方法请阅读[这里](server.md#安全模式)。 + +# 主要服务 + +[/status](status.md): 显示所有服务的主要状态。 + +[/vars](vars.md): 用户可定制的,描绘各种指标的计数器。 + +[/connections](connections.md): 所有连接的统计信息。 + +[/flags](flags.md): 所有gflags的状态,可动态修改。 + +[/rpcz](rpcz.md): 查看所有的RPC的细节。 + +[cpu profiler](cpu_profiler.md): 分析cpu热点。 + +[heap profiler](heap_profiler.md): 分析内存占用。 + +[contention profiler](contention_profiler.md): 分析锁竞争。 + +# 其他服务 + +[/version](http://brpc.baidu.com:8765/version): 查看服务器的版本。用户可通过Server::set_version()设置Server的版本,如果用户没有设置,框架会自动为用户生成,规则:`brpc_server__ ...` + +![img](../images/version_service.png) + +[/health](http://brpc.baidu.com:8765/health): 探测服务的存活情况。 + +![img](../images/health_service.png) + +[/protobufs](http://brpc.baidu.com:8765/protobufs): 查看程序中所有的protobuf结构体。 + +![img](../images/protobufs_service.png) + +[/vlog](http://brpc.baidu.com:8765/vlog): 查看程序中当前可开启的[VLOG](streaming_log.md#VLOG) (对glog无效)。 + +![img](../images/vlog_service.png) + +/dir: 浏览服务器上的所有文件,方便但非常危险,默认关闭。 + +/threads: 查看进程内所有线程的运行状况,调用时对程序性能影响较大,默认关闭。 \ No newline at end of file diff --git a/docs/cn/bvar.md b/docs/cn/bvar.md new file mode 100644 index 0000000..84ec443 --- /dev/null +++ b/docs/cn/bvar.md @@ -0,0 +1,80 @@ +[English version](../en/bvar.md) + +# 什么是bvar? + +[bvar](https://github.com/apache/brpc/tree/master/src/bvar/)是多线程环境下的计数器类库,支持[单维度bvar](bvar_c++.md)和[多维度mbvar](mbvar_c++.md),方便记录和查看用户程序中的各类数值,它利用了thread local存储减少了cache bouncing,相比UbMonitor(百度内的老计数器库)几乎不会给程序增加性能开销,也快于竞争频繁的原子操作。brpc集成了bvar,[/vars](vars.md)可查看所有曝光的bvar,[/vars/VARNAME](vars.md)可查阅某个bvar,在brpc中的使用方法请查看[vars](vars.md)。brpc大量使用了bvar提供统计数值,当你需要在多线程环境中计数并展现时,应该第一时间想到bvar。但bvar不能代替所有的计数器,它的本质是把写时的竞争转移到了读:读得合并所有写过的线程中的数据,而不可避免地变慢了。当你读写都很频繁或得基于最新值做一些逻辑判断时,你不应该用bvar。 + +为了理解bvar的原理,你得先阅读[Cacheline这节](atomic_instructions.md#cacheline),其中提到的计数器例子便是bvar。当很多线程都在累加一个计数器时,每个线程只累加私有的变量而不参与全局竞争,在读取时累加所有线程的私有变量。虽然读比之前慢多了,但由于这类计数器的读多为低频的记录和展现,慢点无所谓。而写就快多了,极小的开销使得用户可以无顾虑地使用bvar监控系统,这便是我们设计bvar的目的。 + +下图是bvar和原子变量,静态UbMonitor,动态UbMonitor在被多个线程同时使用时的开销。可以看到bvar的耗时基本和线程数无关,一直保持在极低的水平(~20纳秒)。而动态UbMonitor在24核时每次累加的耗时达7微秒,这意味着使用300次bvar的开销才抵得上使用一次动态UbMonitor变量。 + +![img](../images/bvar_perf.png) + +# 新增bvar + +增加C++ bvar的方法请看[快速介绍](bvar_c++.md#quick-introduction). bvar默认统计了进程、系统的一些变量,以process\_, system\_等开头,比如: + +``` +process_context_switches_involuntary_second : 14 +process_context_switches_voluntary_second : 15760 +process_cpu_usage : 0.428 +process_cpu_usage_system : 0.142 +process_cpu_usage_user : 0.286 +process_disk_read_bytes_second : 0 +process_disk_write_bytes_second : 260902 +process_faults_major : 256 +process_faults_minor_second : 14 +process_memory_resident : 392744960 +system_core_count : 12 +system_loadavg_15m : 0.040 +system_loadavg_1m : 0.000 +system_loadavg_5m : 0.020 +``` + +还有像brpc内部的各类计数器: + +``` +bthread_switch_second : 20422 +bthread_timer_scheduled_second : 4 +bthread_timer_triggered_second : 4 +bthread_timer_usage : 2.64987e-05 +bthread_worker_count : 13 +bthread_worker_usage : 1.33733 +bvar_collector_dump_second : 0 +bvar_collector_dump_thread_usage : 0.000307385 +bvar_collector_grab_second : 0 +bvar_collector_grab_thread_usage : 1.9699e-05 +bvar_collector_pending_samples : 0 +bvar_dump_interval : 10 +bvar_revision : "34975" +bvar_sampler_collector_usage : 0.00106495 +iobuf_block_count : 89 +iobuf_block_count_hit_tls_threshold : 0 +iobuf_block_memory : 729088 +iobuf_newbigview_second : 10 +``` + +# 监控bvar +打开bvar的[dump功能](bvar_c++.md#export-all-variables)以导出所有的bvar到文件,格式就如上文一样,每行是一对"名字:值"。打开dump功能后应检查monitor/目录下是否有数据,比如: + +``` +$ ls monitor/ +bvar.echo_client.data bvar.echo_server.data + +$ tail -5 monitor/bvar.echo_client.data +process_swaps : 0 +process_time_real : 2580.157680 +process_time_system : 0.380942 +process_time_user : 0.741887 +process_username : "gejun" +``` +每次导出都会覆盖之前的文件,这与普通log添加在后面是不同的。 + +监控系统应定期搜集每台单机导出的数据,并把它们汇总到一起。这里以百度内的noah为例,bvar定义的变量会出现在noah的指标项中,用户可勾选并查看历史曲线。 + +![img](../images/bvar_noah2.png) +![img](../images/bvar_noah3.png) + +# 导出到Prometheus + +将[Prometheus](https://prometheus.io)的抓取url地址的路径设置为`/brpc_metrics`即可,例如brpc server跑在本机的8080端口,则抓取url配置为`127.0.0.1:8080/brpc_metrics`。 diff --git a/docs/cn/bvar_c++.md b/docs/cn/bvar_c++.md new file mode 100644 index 0000000..ddc0de1 --- /dev/null +++ b/docs/cn/bvar_c++.md @@ -0,0 +1,658 @@ +- [bvar Introduction](#bvar-introduction) +- [bvar::Variable](#bvarvariable) +- [Export all variables](#export-all-variables) +- [bvar::Reducer](#bvarreducer) + - [bvar::Adder](#bvaradder) + - [bvar::Maxer](#bvarmaxer) + - [bvar::Miner](#bvarminer) +- [bvar::IntRecorder](#bvarintrecorder) +- [bvar::LatencyRecorder](#bvarlatencyrecorder) +- [bvar::Window](#bvarwindow) + - [How to use bvar::Window](#how-to-use-bvarwindow) +- [bvar::PerSecond](#bvarpersecond) + - [Difference with Window](#difference-with-window) +- [bvar::WindowEx](#bvarwindowex) + - [How to use bvar::WindowEx](#how-to-use-bvarwindowex) + - [Difference between bvar::WindowEx and bvar::Window](#difference-between-bvarwindowex-and-bvarwindow) +- [bvar::PerSecondEx](#bvarpersecondex) + - [How to use bvar::PerSecondEx](#how-to-use-bvarpersecondex) + - [Difference between bvar::PerSecondEx and bvar::WindowEx](#difference-between-bvarpersecondex-and-bvarwindowex) + - [Difference between bvar::PerSecondEx and bvar::PerSecond](#difference-between-bvarpersecondex-and-bvarpersecond) +- [bvar::Status](#bvarstatus) +- [bvar::PassiveStatus](#bvarpassivestatus) +- [bvar::GFlag](#bvargflag) + +# bvar Introduction + +单维度bvar使用文档,多维度mbvar请[移步](mbvar_c++.md)。 + +bvar分为多个具体的类,常用的有: + +| 类型 | 说明 | +|-----------------|-----------------------------------------------------------------------------------------| +| bvar::Adder\| 计数器,默认0,varname << N相当于varname += N | +| bvar::Maxer\ | 求最大值,默认std::numeric_limits::min(),varname << N相当于varname = max(varname, N) | +| bvar::Miner\| 求最小值,默认std::numeric_limits::max(),varname << N相当于varname = min(varname, N) | +| bvar::IntRecorder| 求自使用以来的平均值。注意这里的定语不是“一段时间内”。一般要通过Window衍生出时间窗口内的平均值 | +| bvar::Window\| 获得某个bvar在一段时间内的累加值。Window衍生于已存在的bvar,会自动更新 | +| bvar::PerSecond\| 获得某个bvar在一段时间内平均每秒的累加值。PerSecond也是会自动更新的衍生变量 | +| bvar::WindowEx\ | 获得某个bvar在一段时间内的累加值。不依赖其他的bvar,需要给它发送数据 | +| bvar::PerSecondEx\| 获得某个bvar在一段时间内平均每秒的累加值。不依赖其他的bvar,需要给它发送数据 | +| bvar::LatencyRecorder| 专用于记录延时和qps的变量。输入延时,平均延时/最大延时/qps/总次数 都有了 | +| bvar::Status\ | 记录和显示一个值,拥有额外的set_value函数 | +| bvar::PassiveStatus | 按需显示值。在一些场合中,我们无法set_value或不知道以何种频率set_value,更适合的方式也许是当需要显示时才打印。用户传入打印回调函数实现这个目的 | +| bvar::GFlag | 将重要的gflags公开为bvar,以便监控它们 | + +例子: +```c++ +#include + +namespace foo { +namespace bar { + +// bvar::Adder用于累加,下面定义了一个统计read error总数的Adder。 +bvar::Adder g_read_error; +// 把bvar::Window套在其他bvar上就可以获得时间窗口内的值。 +bvar::Window > g_read_error_minute("foo_bar", "read_error", &g_read_error, 60); +// ^ ^ ^ +// 前缀 监控项名称 60秒,忽略则为10秒 + +// bvar::LatencyRecorder是一个复合变量,可以统计:总量、qps、平均延时,延时分位值,最大延时。 +bvar::LatencyRecorder g_write_latency("foo_bar", "write"); +// ^ ^ +// 前缀 监控项,别加latency!LatencyRecorder包含多个bvar,它们会加上各自的后缀,比如write_qps, write_latency等等。 + +// 定义一个统计“已推入task”个数的变量。 +bvar::Adder g_task_pushed("foo_bar", "task_pushed"); +// 把bvar::PerSecond套在其他bvar上可以获得时间窗口内*平均每秒*的值,这里是每秒内推入task的个数。 +bvar::PerSecond > g_task_pushed_second("foo_bar", "task_pushed_second", &g_task_pushed); +// ^ ^ +// 和Window不同,PerSecond会除以时间窗口的大小. 时间窗口是最后一个参数,这里没填,就是默认10秒。 + +} // bar +} // foo +``` + +在应用的地方: + +```c++ +// 碰到read error +foo::bar::g_read_error << 1; + +// write_latency是23ms +foo::bar::g_write_latency << 23; + +// 推入了1个task +foo::bar::g_task_pushed << 1; +``` + +注意Window<>和PerSecond<>都是衍生变量,会自动更新,你不用给它们推值。你当然也可以把bvar作为成员变量或局部变量。 + +**确认变量名是全局唯一的!** 否则会曝光失败,如果-bvar_abort_on_same_name为true,程序会直接abort。 + +程序中有来自各种模块不同的bvar,为避免重名,建议如此命名:**模块_类名_指标** + +- **模块**一般是程序名,可以加上产品线的缩写,比如inf_ds,ecom_retrbs等等。 +- **类名**一般是类名或函数名,比如storage_manager, file_transfer, rank_stage1等等。 +- **指标**一般是count,qps,latency这类。 + +一些正确的命名如下: + +``` +iobuf_block_count : 29 # 模块=iobuf 类名=block 指标=count +iobuf_block_memory : 237568 # 模块=iobuf 类名=block 指标=memory +process_memory_resident : 34709504 # 模块=process 类名=memory 指标=resident +process_memory_shared : 6844416 # 模块=process 类名=memory 指标=shared +rpc_channel_connection_count : 0 # 模块=rpc 类名=channel_connection 指标=count +rpc_controller_count : 1 # 模块=rpc 类名=controller 指标=count +rpc_socket_count : 6 # 模块=rpc 类名=socket 指标=count +``` + +目前bvar会做名字归一化,不管你打入的是foo::BarNum, foo.bar.num, foo bar num , foo-bar-num,最后都是foo_bar_num。 + +关于指标: + +- 个数以_count为后缀,比如request_count, error_count。 +- 每秒的个数以_second为后缀,比如request_second, process_inblocks_second,已经足够明确,不用写成_count_second或_per_second。 +- 每分钟的个数以_minute为后缀,比如request_minute, process_inblocks_minute + +如果需要使用定义在另一个文件中的计数器,需要在头文件中声明对应的变量。 + +```c++ +namespace foo { +namespace bar { +// 注意g_read_error_minute和g_task_pushed_second都是衍生的bvar,会自动更新,不要声明。 +extern bvar::Adder g_read_error; +extern bvar::LatencyRecorder g_write_latency; +extern bvar::Adder g_task_pushed; +} // bar +} // foo +``` + +**不要跨文件定义全局Window或PerSecond**。不同编译单元中全局变量的初始化顺序是[未定义的](https://isocpp.org/wiki/faq/ctors#static-init-order)。在foo.cpp中定义`Adder foo_count`,在foo_qps.cpp中定义`PerSecond > foo_qps(&foo_count);`是**错误**的做法。 + +About thread-safety: + +- bvar是线程兼容的。你可以在不同的线程里操作不同的bvar。比如你可以在多个线程中同时expose或hide**不同的**bvar,它们会合理地操作需要共享的全局数据,是安全的。 +- **除了读写接口**,bvar的其他函数都是线程不安全的:比如说你不能在多个线程中同时expose或hide**同一个**bvar,这很可能会导致程序crash。一般来说,读写之外的其他接口也没有必要在多个线程中同时操作。 + +计时可以使用butil::Timer,接口如下: + +```c++ +#include +namespace butil { +class Timer { +public: + enum TimerType { STARTED }; + + Timer(); + + // butil::Timer tm(butil::Timer::STARTED); // tm is already started after creation. + explicit Timer(TimerType); + + // Start this timer + void start(); + + // Stop this timer + void stop(); + + // Get the elapse from start() to stop(). + int64_t n_elapsed() const; // in nanoseconds + int64_t u_elapsed() const; // in microseconds + int64_t m_elapsed() const; // in milliseconds + int64_t s_elapsed() const; // in seconds +}; +} // namespace butil +``` + +# bvar::Variable + +Variable是所有bvar的基类,主要提供全局注册,列举,查询等功能。 + +用户以默认参数建立一个bvar时,这个bvar并未注册到任何全局结构中,在这种情况下,bvar纯粹是一个更快的计数器。我们称把一个bvar注册到全局表中的行为为“曝光”,可通过`expose`函数曝光: +```c++ +// Expose this variable globally so that it's counted in following functions: +// list_exposed +// count_exposed +// describe_exposed +// find_exposed +// Return 0 on success, -1 otherwise. +int expose(const butil::StringPiece& name); +int expose_as(const butil::StringPiece& prefix, const butil::StringPiece& name); +``` +全局曝光后的bvar名字便为name或prefix + name,可通过以_exposed为后缀的static函数查询。比如Variable::describe_exposed(name)会返回名为name的bvar的描述。 + +当相同名字的bvar已存在时,expose会打印FATAL日志并返回-1。如果选项 **-bvar_abort_on_same_name**设为true (默认是false),程序会直接abort。 + +下面是一些曝光bvar的例子: +```c++ +bvar::Adder count1; + +count1 << 10 << 20 << 30; // values add up to 60. +count1.expose("count1"); // expose the variable globally +CHECK_EQ("60", bvar::Variable::describe_exposed("count1")); +count1.expose("another_name_for_count1"); // expose the variable with another name +CHECK_EQ("", bvar::Variable::describe_exposed("count1")); +CHECK_EQ("60", bvar::Variable::describe_exposed("another_name_for_count1")); + +bvar::Adder count2("count2"); // exposed in constructor directly +CHECK_EQ("0", bvar::Variable::describe_exposed("count2")); // default value of Adder is 0 + +bvar::Status status1("count2", "hello"); // the name conflicts. if -bvar_abort_on_same_name is true, + // program aborts, otherwise a fatal log is printed. +``` + +为避免重名,bvar的名字应加上前缀,建议为`__`。为了方便使用,我们提供了**expose_as**函数,接收一个前缀。 +```c++ +// Expose this variable with a prefix. +// Example: +// namespace foo { +// namespace bar { +// class ApplePie { +// ApplePie() { +// // foo_bar_apple_pie_error +// _error.expose_as("foo_bar_apple_pie", "error"); +// } +// private: +// bvar::Adder _error; +// }; +// } // foo +// } // bar +int expose_as(const butil::StringPiece& prefix, const butil::StringPiece& name); +``` + +# Export all variables + +最常见的导出需求是通过HTTP接口查询和写入本地文件。前者在brpc中通过[/vars](vars.md)服务提供,后者则已实现在bvar中,默认不打开。有几种方法打开这个功能: + +- 用[gflags](flags.md)解析输入参数,在程序启动时加入-bvar_dump,或在brpc中也可通过[/flags](flags.md)服务在启动后动态修改。gflags的解析方法如下,在main函数处添加如下代码: + +```c++ + #include + ... + int main(int argc, char* argv[]) { + google::ParseCommandLineFlags(&argc, &argv, true/*表示把识别的参数从argc/argv中删除*/); + ... + } +``` + +- 不想用gflags解析参数,希望直接在程序中默认打开,在main函数处添加如下代码: + +```c++ +#include +... +int main(int argc, char* argv[]) { + if (google::SetCommandLineOption("bvar_dump", "true").empty()) { + LOG(FATAL) << "Fail to enable bvar dump"; + } + ... +} +``` + +dump功能由如下gflags控制: + +| 名称 | 默认值 | 作用 | +| ------------------ | ----------------------- | ---------------------------------------- | +| bvar_dump | false | Create a background thread dumping all bvar periodically, all bvar_dump_* flags are not effective when this flag is off | +| bvar_dump_exclude | "" | Dump bvar excluded from these wildcards(separated by comma), empty means no exclusion | +| bvar_dump_file | monitor/bvar.\.data | Dump bvar into this file | +| bvar_dump_include | "" | Dump bvar matching these wildcards(separated by comma), empty means including all | +| bvar_dump_interval | 10 | Seconds between consecutive dump | +| bvar_dump_prefix | \ | Every dumped name starts with this prefix | +| bvar_dump_tabs | \ | Dump bvar into different tabs according to the filters (separated by semicolon), format: *(tab_name=wildcards) | + +当bvar_dump_file不为空时,程序会启动一个后台导出线程以bvar_dump_interval指定的间隔更新bvar_dump_file,其中包含了被bvar_dump_include匹配且不被bvar_dump_exclude匹配的所有bvar。 + +比如我们把所有的gflags修改为下图: + +![img](../images/bvar_dump_flags_2.png) + +导出文件为: + +``` +$ cat bvar.echo_server.data +rpc_server_8002_builtin_service_count : 20 +rpc_server_8002_connection_count : 1 +rpc_server_8002_nshead_service_adaptor : brpc::policy::NovaServiceAdaptor +rpc_server_8002_service_count : 1 +rpc_server_8002_start_time : 2015/07/24-21:08:03 +rpc_server_8002_uptime_ms : 14740954 +``` + +像”`iobuf_block_count : 8`”被bvar_dump_include过滤了,“`rpc_server_8002_error : 0`”则被bvar_dump_exclude排除了。 + +如果你的程序没有使用brpc,仍需要动态修改gflag(一般不需要),可以调用google::SetCommandLineOption(),如下所示: +```c++ +#include +... +if (google::SetCommandLineOption("bvar_dump_include", "*service*").empty()) { + LOG(ERROR) << "Fail to set bvar_dump_include"; + return -1; +} +LOG(INFO) << "Successfully set bvar_dump_include to *service*"; +``` + +请勿直接设置FLAGS_bvar_dump_file / FLAGS_bvar_dump_include / FLAGS_bvar_dump_exclude。 +一方面这些gflag类型都是std::string,直接覆盖是线程不安全的;另一方面不会触发validator(检查正确性的回调),所以也不会启动后台导出线程。 + +用户也可以使用dump_exposed函数自定义如何导出进程中的所有已曝光的bvar: +```c++ +// Implement this class to write variables into different places. +// If dump() returns false, Variable::dump_exposed() stops and returns -1. +class Dumper { +public: + virtual bool dump(const std::string& name, const butil::StringPiece& description) = 0; +}; + +// Options for Variable::dump_exposed(). +struct DumpOptions { + // Contructed with default options. + DumpOptions(); + // If this is true, string-type values will be quoted. + bool quote_string; + // The ? in wildcards. Wildcards in URL need to use another character + // because ? is reserved. + char question_mark; + // Separator for white_wildcards and black_wildcards. + char wildcard_separator; + // Name matched by these wildcards (or exact names) are kept. + std::string white_wildcards; + // Name matched by these wildcards (or exact names) are skipped. + std::string black_wildcards; +}; + +class Variable { + ... + ... + // Find all exposed variables matching `white_wildcards' but + // `black_wildcards' and send them to `dumper'. + // Use default options when `options' is NULL. + // Return number of dumped variables, -1 on error. + static int dump_exposed(Dumper* dumper, const DumpOptions* options); +}; +``` + +# bvar::Reducer + +Reducer用二元运算符把多个值合并为一个值,运算符需满足结合律,交换律,没有副作用。只有满足这三点,我们才能确保合并的结果不受线程私有数据如何分布的影响。像减法就不满足结合律和交换律,它无法作为此处的运算符。 +```c++ +// Reduce multiple values into one with `Op': e1 Op e2 Op e3 ... +// `Op' shall satisfy: +// - associative: a Op (b Op c) == (a Op b) Op c +// - commutative: a Op b == b Op a; +// - no side effects: a Op b never changes if a and b are fixed. +// otherwise the result is undefined. +template +class Reducer : public Variable; +``` +reducer << e1 << e2 << e3的作用等价于reducer = e1 op e2 op e3。 + +常见的Redcuer子类有bvar::Adder, bvar::Maxer, bvar::Miner。 + +## bvar::Adder + +顾名思义,用于累加,Op为+。 +```c++ +bvar::Adder value; +value << 1 << 2 << 3 << -4; +CHECK_EQ(2, value.get_value()); + +bvar::Adder fp_value; // 可能有warning +fp_value << 1.0 << 2.0 << 3.0 << -4.0; +CHECK_DOUBLE_EQ(2.0, fp_value.get_value()); +``` +Adder<>可用于非基本类型,对应的类型至少要重载`T operator+(T, T)`。一个已经存在的例子是std::string,下面的代码会把string拼接起来: +```c++ +// This is just proof-of-concept, don't use it for production code because it makes a +// bunch of temporary strings which is not efficient, use std::ostringstream instead. +bvar::Adder concater; +std::string str1 = "world"; +concater << "hello " << str1; +CHECK_EQ("hello world", concater.get_value()); +``` + +## bvar::Maxer +用于取最大值,运算符为std::max。 +```c++ +bvar::Maxer value; +value << 1 << 2 << 3 << -4; +CHECK_EQ(3, value.get_value()); +``` +Since Maxer<> use std::numeric_limits::min() as the identity, it cannot be applied to generic types unless you specialized std::numeric_limits<> (and overloaded operator<, yes, not operator>). + +## bvar::Miner + +用于取最小值,运算符为std::min。 +```c++ +bvar::Maxer value; +value << 1 << 2 << 3 << -4; +CHECK_EQ(-4, value.get_value()); +``` +Since Miner<> use std::numeric_limits::max() as the identity, it cannot be applied to generic types unless you specialized std::numeric_limits<> (and overloaded operator<). + +# bvar::IntRecorder + +用于计算平均值。 +```c++ +// For calculating average of numbers. +// Example: +// IntRecorder latency; +// latency << 1 << 3 << 5; +// CHECK_EQ(3, latency.average()); +class IntRecorder : public Variable; +``` + +# bvar::LatencyRecorder + +专用于计算latency和qps的计数器。只需填入latency数据,就能获得latency / max_latency / qps / count。统计窗口是最后一个参数,不填为bvar_dump_interval(这里没填)。 + +注意:LatencyRecorder没有继承Variable,而是多个bvar的组合。 +```c++ +LatencyRecorder write_latency("table2_my_table_write"); // produces 4 variables: + // table2_my_table_write_latency + // table2_my_table_write_max_latency + // table2_my_table_write_qps + // table2_my_table_write_count +// In your write function +write_latency << the_latency_of_write; +``` + +# bvar::Window + +获得之前一段时间内的统计值。Window不能独立存在,必须依赖于一个已有的计数器。Window会自动更新,不用给它发送数据。出于性能考虑,Window的数据来自于每秒一次对原计数器的采样,在最差情况下,Window的返回值有1秒的延时。 +```c++ +// Get data within a time window. +// The time unit is 1 second fixed. +// Window relies on other bvar which should be constructed before this window and destructs after this window. +// R must: +// - have get_sampler() (not require thread-safe) +// - defined value_type and sampler_type +template +class Window : public Variable; +``` + +## How to use bvar::Window +```c++ +bvar::Adder sum; +bvar::Maxer max_value; +bvar::IntRecorder avg_value; + +// sum_minute.get_value()是sum在之前60秒内的累加值。 +bvar::Window > sum_minute(&sum, 60); + +// max_value_minute.get_value()是max_value在之前60秒内的最大值。 +bvar::Window > max_value_minute(&max_value, 60); + +// avg_value_minute.get_value()是avg_value在之前60秒内的平均值。 +bvar::Window avg_value_minute(&avg_value, 60); +``` + +# bvar::PerSecond + +获得之前一段时间内平均每秒的统计值。它和Window基本相同,除了返回值会除以时间窗口之外。 +```c++ +bvar::Adder sum; + +// sum_per_second.get_value()是sum在之前60秒内*平均每秒*的累加值,省略最后一个时间窗口的话默认为bvar_dump_interval。 +bvar::PerSecond > sum_per_second(&sum, 60); +``` +**PerSecond并不总是有意义** + +上面的代码中没有Maxer,因为一段时间内的最大值除以时间窗口是没有意义的。 +```c++ +bvar::Maxer max_value; + +// 错误!最大值除以时间是没有意义的 +bvar::PerSecond > max_value_per_second_wrong(&max_value); + +// 正确,把Window的时间窗口设为1秒才是正确的做法 +bvar::Window > max_value_per_second(&max_value, 1); +``` + +## Difference with Window + +比如要统计内存在上一分钟内的变化,用Window<>的话,返回值的含义是”上一分钟内存增加了18M”,用PerSecond<>的话,返回值的含义是“上一分钟平均每秒增加了0.3M”。 + +Window的优点是精确值,适合一些比较小的量,比如“上一分钟的错误数“,如果这用PerSecond的话,得到可能是”上一分钟平均每秒产生了0.0167个错误",这相比于”上一分钟有1个错误“显然不够清晰。另外一些和时间无关的量也要用Window,比如统计上一分钟cpu占用率的方法是用一个Adder同时累加cpu时间和真实时间,然后用Window获得上一分钟的cpu时间和真实时间,两者相除就得到了上一分钟的cpu占用率,这和时间无关,用PerSecond会产生错误的结果。 + +# bvar::WindowEx + +获得之前一段时间内的统计值。WindowEx是独立存在的,不依赖其他的计数器,需要给它发送数据。出于性能考虑,WindowEx每秒对数据做一次统计,在最差情况下,WindowEx的返回值有1秒的延时。 +```c++ +// Get data within a time window. +// The time unit is 1 second fixed. +// Window not relies on other bvar. + +// R must: +// - window_size must be a constant +template +class WindowEx : public adapter::WindowExAdapter > { +public: + typedef adapter::WindowExAdapter > Base; + + WindowEx() : Base(window_size) {} + + WindowEx(const base::StringPiece& name) : Base(window_size) { + this->expose(name); + } + + WindowEx(const base::StringPiece& prefix, + const base::StringPiece& name) + : Base(window_size) { + this->expose_as(prefix, name); + } +}; +``` + +## How to use bvar::WindowEx +```c++ +const int window_size = 60; + +// sum_minute.get_value()是60秒内的累加值,省略最后一个window_size(时间窗口)的话默认为bvar_dump_interval。 +bvar::WindowEx, window_size> sum_minute("sum_minute"); +sum_minute << 1 << 2 << 3; + +// max_minute.get_value()是60秒内的最大值,省略最后一个window_size(时间窗口)的话默认为bvar_dump_interval。 +bvar::WindowEx, window_size> max_minute("max_minute"); +max_minute << 1 << 2 << 3; + +// min_minute.get_value()是60秒内的最小值,省略最后一个window_size(时间窗口)的话默认为bvar_dump_interval。 +bvar::WindowEx, window_size> min_minute("min_minute"); +min_minute << 1 << 2 << 3; + +// avg_minute.get_value是60秒内的平均值(返回值是bvar::Stat),省略最后一个window_size(时间窗口)的话默认为bvar_dump_interval。 +bvar::WindowEx avg_minute("avg_minute"); +avg_minute << 1 << 2 << 3; +// 获得avg_minuter 60秒内的平均值stat +bvar::Stat avg_stat = avg_minute.get_value(); +// 获得整型平均值 +int64_t avg_int = avg_stat.get_average_int(); +// 获得double类型平均值 +double avg_double = avg_stat.get_average_double(); +``` + +## Difference between bvar::WindowEx and bvar::Window + +- bvar::Window 不能独立存在,必须依赖于一个已有的计数器。Window会自动更新,不用给它发送数据;window_size是通过构造函数参数传递的。 + +- bvar::WindowEx 是独立存在的,不依赖其他的计数器,需要给它发送数据。使用起来比较方便;window_size是通过模板参数传递的,省略最后一个window_size(时间窗口)的话默认为bvar_dump_interval。 + +# bvar::PerSecondEx +获得之前一段时间内平均每秒的统计值。它和WindowEx基本相同,除了返回值会除以时间窗口之外。 +```c++ +// Get data per second within a time window. +// The only difference between PerSecondEx and WindowEx is that PerSecondEx divides +// the data by time duration. + +// R must: +// - window_size must be a constant +template +class PerSecondEx : public adapter::WindowExAdapter > { +public: + typedef adapter::WindowExAdapter > Base; + + PerSecondEx() : Base(window_size) {} + + PerSecondEx(const base::StringPiece& name) : Base(window_size) { + this->expose(name); + } + + PerSecondEx(const base::StringPiece& prefix, + const base::StringPiece& name) + : Base(window_size) { + this->expose_as(prefix, name); + } +}; +``` + +## How to use bvar::PerSecondEx + +```c++ +const int window_size = 60; + +// sum_per_second.get_value()是60秒内*平均每秒*的累加值,省略最后一个window_size(时间窗口)的话默认为bvar_dump_interval。 +bvar::PerSecondEx, window_size> sum_per_second("sum_per_second"); +sum_per_second << 1 << 2 << 3; +``` + +## Difference between bvar::PerSecondEx and bvar::WindowEx + +- bvar::PerSecondEx 获得之前一段时间内平均每秒的统计值。它和WindowEx基本相同,除了返回值会除以时间窗口之外。 + +## Difference between bvar::PerSecondEx and bvar::PerSecond +- bvar::PerSecond 不能独立存在,必须依赖于一个已有的计数器。PerSecond会自动更新,不用给它发送数据;window_size是通过构造函数参数传递的。 +- bvar::PerSecondEx 是独立存在的,不依赖其他的计数器,需要给它发送数据。使用起来比较方便;window_size是通过模板参数传递的,省略最后一个window_size(时间窗口)的话默认为bvar_dump_interval。 + +# bvar::Status + +记录和显示一个值,拥有额外的set_value函数。 + +```c++ +// Display a rarely or periodically updated value. +// Usage: +// bvar::Status foo_count1(17); +// foo_count1.expose("my_value"); +// +// bvar::Status foo_count2; +// foo_count2.set_value(17); +// +// bvar::Status foo_count3("my_value", 17); +// +// Notice that Tp needs to be std::string or acceptable by boost::atomic. +template +class Status : public Variable; +``` + +# bvar::PassiveStatus + +按需显示值。在一些场合中,我们无法set_value或不知道以何种频率set_value,更适合的方式也许是当需要显示时才打印。用户传入打印回调函数实现这个目的。 +```c++ +// Display a updated-by-need value. This is done by passing in an user callback +// which is called to produce the value. +// Example: +// int print_number(void* arg) { +// ... +// return 5; +// } +// +// // number1 : 5 +// bvar::PassiveStatus status1("number1", print_number, arg); +// +// // foo_number2 : 5 +// bvar::PassiveStatus status2(typeid(Foo), "number2", print_number, arg); +template +class PassiveStatus : public Variable; +``` +虽然很简单,但PassiveStatus是最有用的bvar之一,因为很多统计量已经存在,我们不需要再次存储它们,而只要按需获取。比如下面的代码声明了一个在linux下显示进程用户名的bvar: +```c++ +static void get_username(std::ostream& os, void*) { + char buf[32]; + if (getlogin_r(buf, sizeof(buf)) == 0) { + buf[sizeof(buf)-1] = '\0'; + os << buf; + } else { + os << "unknown"; + } +} +PassiveStatus g_username("process_username", get_username, NULL); +``` + +# bvar::GFlag +Expose important gflags as bvar so that they're monitored. +```c++ +DEFINE_int32(my_flag_that_matters, 8, "..."); + +// Expose the gflag as *same-named* bvar so that it's monitored. +static bvar::GFlag s_gflag_my_flag_that_matters("my_flag_that_matters"); +// ^ +// the gflag name + +// Expose the gflag as a bvar named "foo_bar_my_flag_that_matters". +static bvar::GFlag s_gflag_my_flag_that_matters_with_prefix("foo_bar", "my_flag_that_matters"); +``` +# babylon counter bvar + +原理和性能见[babylon介绍](https://github.com/baidu/babylon/tree/main/example/use-counter-with-bvar)。 + +目前只支持bazel编译方式:`--define with_babylon_counter=true`,babylon版本要求:>= 1.4.4。打开开关后,即可使用基于babylon counter实现的更高性能的bvar,无需修改代码。 + diff --git a/docs/cn/case_apicontrol.md b/docs/cn/case_apicontrol.md new file mode 100644 index 0000000..f116b8d --- /dev/null +++ b/docs/cn/case_apicontrol.md @@ -0,0 +1,50 @@ +# 进展 + +| 时间 | 内容 | 说明 | +| ----------- | ------------------------------------- | -------------- | +| 8.11 - 8.28 | 调研 + 研发 + 自测 | 自测性能报告见附件 | +| 9.8 - 9.22 | QA测试 | QA测试报告见附件 | +| 10.8 | 北京机房1台机器上线 | | +| 10.14 | 北京机房1台机器上线 | 修复URL编码问题 | +| 10.19 | 北京机房7/35机器上线杭州和南京各2台机器上线 | 开始小流量上线 | +| 10.22 | 北京机房10/35机器上线杭州机房5/26机器上线南京机房5/19机器上线 | 修复http响应数据压缩问题 | +| 11.3 | 北京机房10/35机器上线 | 修复RPC内存泄露问题 | +| 11.6 | 杭州机房5/26机器上线南京机房5/19机器上线 | 同北京机房版本 | +| 11.9 | 北京机房全流量上线 | | + +截止目前,线上服务表现稳定。 + +# QA测试结论 + +1. 【性能测试】单机支持最大QPS:**9000+**。可以有效解决原来hulu_pbrpc中一个慢服务拖垮所有服务的问题。性能很好。 +2. 【稳定性测试】长时间压测没问题。 + +QA测试结论:通过 + +# 性能提升实时统计 + +统计时间2015.11.3 15:00 – 2015.11.9 14:30,共**143.5**小时(近6天)不间断运行。北京机房升级前和升级后同机房各6台机器,共**12**台线上机器的Noah监控数据。 + +| 指标 | 升级**前**均值hulu_pbrpc | 升级**后**均值brpc | 收益对比 | 说明 | +| -------- | ------------------- | ------------- | ------------ | ------------------------ | +| CPU占用率 | 67.35% | 29.28% | 降低**56.53**% | | +| 内存占用 | 327.81MB | 336.91MB | 基本持平 | | +| 鉴权平响(ms) | 0.605 | 0.208 | 降低**65.62**% | | +| 转发平响(ms) | 22.49 | 23.18 | 基本持平 | 依赖后端各个服务的性能 | +| 总线程数 | 193 | 132 | 降低**31.61**% | Baidu RPC版本线程数使用率较低,还可降低 | +| 极限QPS | 3000 | 9000 | 提升**3**倍 | 线下使用Geoconv和Geocoder服务测试 | + +**CPU使用率(%)**(红色为升级前,蓝色为升级后) +![img](../images/apicontrol_compare_1.png) + +**内存使用量(KB)**(红色为升级前,蓝色为升级后) +![img](../images/apicontrol_compare_2.png) + +**鉴权平响(ms)**(红色为升级前,蓝色为升级后) +![img](../images/apicontrol_compare_3.png) + +**转发平响(ms)**(红色为升级前,蓝色为升级后) +![img](../images/apicontrol_compare_4.png) + +**总线程数(个)**(红色为升级前,蓝色为升级后) +![img](../images/apicontrol_compare_5.png) diff --git a/docs/cn/case_baidu_dsp.md b/docs/cn/case_baidu_dsp.md new file mode 100644 index 0000000..6d2c0b8 --- /dev/null +++ b/docs/cn/case_baidu_dsp.md @@ -0,0 +1,23 @@ +# 背景 + +baidu-dsp是联盟基于Ad Exchange和RTB模式的需求方平台,服务大客户、代理的投放产品体系。我们改造了多个模块,均取得了显著的效果。本文只介绍其中关于super-nova-as的改动。super-nova-as是的baidu-dsp的AS,之前使用ub-aserver编写,为了尽量减少改动,我们没有改造整个as,而只是把super-nova-as连接下游(ctr-server、cvr-server、super-nova-bs)的client从ubrpc升级为brpc。 + +# 结论 + +1. as的吞吐量有显著提升(不到1500 -> 2500+) +2. cpu优化:从1500qps 50%cpu_idle提高到2000qps 50% cpu_idle; +3. 超时率改善明显。 + +# 测试过程 + +1. 环境:1个as,1个bs,1个ctr,1个cvr;部署情况为:bs单机部署,as+ctr+cvr混布;ctr和cvr为brpc版本 +2. 分别采用1000,1500压力对ubrpc版本的as进行压测,发现1500压力下,as对bs有大量的超时,as到达瓶颈; +3. 分别采用2000,2500压力对brpc版本的as进行压测,发现2500压力下,as机器的cpu_idle低于30%,as到达瓶颈。brpc对资源利用充分。 + +| | ubrpc | brpc | +| -------- | ---------------------------------------- | ---------------------------------------- | +| 流量 | ![img](../images/baidu_dsp_compare_1.png) | ![img](../images/baidu_dsp_compare_2.png) | +| bs成功率 | ![img](../images/baidu_dsp_compare_3.png) | ![img](../images/baidu_dsp_compare_4.png) | +| cpu_idle | ![img](../images/baidu_dsp_compare_5.png) | ![img](../images/baidu_dsp_compare_6.png) | +| ctr成功率 | ![img](../images/baidu_dsp_compare_7.png) | ![img](../images/baidu_dsp_compare_8.png) | +| cvr成功率 | ![img](../images/baidu_dsp_compare_9.png) | ![img](../images/baidu_dsp_compare_10.png) | diff --git a/docs/cn/case_elf.md b/docs/cn/case_elf.md new file mode 100644 index 0000000..2a91aeb --- /dev/null +++ b/docs/cn/case_elf.md @@ -0,0 +1,87 @@ +# 背景 + +ELF(Essential/Extreme/Excellent Learning Framework) 框架为公司内外的大数据应用提供学习/挖掘算法开发支持。 平台主要包括数据迭代处理的框架支持,并行计算过程中的通信支持和用于存储大规模参数的分布式、快速、高可用参数服务器。应用于fcr-model,公有云bml,大数据实验室,语音技术部门等等。之前是基于[zeromq](http://zeromq.org/)封装的rpc,这次改用brpc。 + +# 结论 + +单个rpc-call以及单次请求所有rpc-call的提升非常显著,延时都缩短了**50%**以上,总训练的耗时除了ftrl-sync-no-shuffle提升不明显外,其余三个算法训练总体性能都有**15%**左右的提升。现在瓶颈在于计算逻辑,所以相对单个的rpc的提升没有那么多。该成果后续会推动凤巢模型训练的上线替换。详细耗时见下节。 + +# 性能对比报告 + +## 算法总耗时 + +ftrl算法: 替换前总耗时2:4:39, 替换后总耗时1:42:48, 提升18% + +ftrl-sync-no-shuffle算法: 替换前总耗时3:20:47, 替换后总耗时3:15:28, 提升2.5% + +ftrl-sync算法: 替换前总耗时4:28:47, 替换后总耗时3:45:57, 提升16% + +fm-sync算法: 替换前总耗时6:16:37, 替换后总耗时5:21:00, 提升14.6% + +## 子任务耗时 + +单个rpc-call(针对ftrl算法) + +| | Average | Max | Min | +| ---- | --------- | --------- | ---------- | +| 替换前 | 164.946ms | 7938.76ms | 0.249756ms | +| 替换后 | 10.4198ms | 2614.38ms | 0.076416ms | +| 缩短 | 93% | 67% | 70% | + +单次请求所有rpc-call(针对ftrl算法) + +| | Average | Max | Min | +| ---- | -------- | -------- | --------- | +| 替换前 | 658.08ms | 7123.5ms | 1.88159ms | +| 替换后 | 304.878 | 2571.34 | 0 | +| 缩短 | 53.7% | 63.9% | | + +## 结论 + +单个rpc-call以及单次请求所有rpc-call的提升非常显著,提升都在50%以上,总任务的耗时除了ftrl-sync-no-shuffle提升不明显外,其余三个算法都有15%左右的提升,现在算法的瓶颈在于对计算逻辑,所以相对单个的rpc的提升没有那么多。 + +附cpu profiling结果, top 40没有rpc相关函数。 +``` +Total: 8664 samples + 755 8.7% 8.7% 757 8.7% baidu::elf::Partitioner + 709 8.2% 16.9% 724 8.4% baidu::elf::GlobalShardWriterClient::local_aggregator::{lambda#1}::operator [clone .part.1341] + 655 7.6% 24.5% 655 7.6% boost::detail::lcast_ret_unsigned + 582 6.7% 31.2% 642 7.4% baidu::elf::RobinHoodLinkedHashMap + 530 6.1% 37.3% 2023 23.4% std::vector + 529 6.1% 43.4% 529 6.1% std::binary_search + 406 4.7% 48.1% 458 5.3% tc_delete + 405 4.7% 52.8% 2454 28.3% baidu::elf::GlobalShardWriterClient + 297 3.4% 56.2% 297 3.4% __memcpy_sse2_unaligned + 256 3.0% 59.2% 297 3.4% tc_new + 198 2.3% 61.5% 853 9.8% std::__introsort_loop + 157 1.8% 63.3% 157 1.8% baidu::elf::GrowableArray + 152 1.8% 65.0% 177 2.0% calculate_grad + 142 1.6% 66.7% 699 8.1% baidu::elf::BlockTableReaderManager + 137 1.6% 68.2% 656 7.6% baidu::elf::DefaultWriterServer + 127 1.5% 69.7% 127 1.5% _init + 122 1.4% 71.1% 582 6.7% __gnu_cxx::__normal_iterator + 117 1.4% 72.5% 123 1.4% baidu::elf::GrowableArray::emplace_back + 116 1.3% 73.8% 116 1.3% baidu::elf::RobinHoodHashMap::insert + 101 1.2% 75.0% 451 5.2% baidu::elf::NoCacheReaderClient + 99 1.1% 76.1% 3614 41.7% parse_ins + 97 1.1% 77.2% 97 1.1% std::basic_string::_Rep::_M_dispose [clone .part.12] + 96 1.1% 78.3% 154 1.8% std::basic_string + 91 1.1% 79.4% 246 2.8% boost::algorithm::split_iterator + 87 1.0% 80.4% 321 3.7% boost::function2 + 76 0.9% 81.3% 385 4.4% boost::detail::function::functor_manager + 69 0.8% 82.1% 69 0.8% std::locale::~locale + 63 0.7% 82.8% 319 3.7% std::__unguarded_linear_insert + 58 0.7% 83.5% 2178 25.2% boost::algorithm::split [clone .constprop.2471] + 54 0.6% 84.1% 100 1.2% std::vector::_M_emplace_back_aux + 49 0.6% 84.7% 49 0.6% boost::algorithm::detail::is_any_ofF + 47 0.5% 85.2% 79 0.9% baidu::elf::DefaultReaderServer + 41 0.5% 85.7% 41 0.5% std::locale::_S_initialize + 39 0.5% 86.1% 677 7.8% boost::detail::function::function_obj_invoker2 + 39 0.5% 86.6% 39 0.5% memset + 39 0.5% 87.0% 39 0.5% std::locale::locale + 38 0.4% 87.5% 50 0.6% FTRLAggregator::serialize + 36 0.4% 87.9% 67 0.8% tcmalloc::CentralFreeList::ReleaseToSpans + 34 0.4% 88.3% 34 0.4% madvise + 34 0.4% 88.7% 38 0.4% tcmalloc::CentralFreeList::FetchFromOneSpans + 32 0.4% 89.0% 32 0.4% std::__insertion_sort +``` diff --git a/docs/cn/case_ubrpc.md b/docs/cn/case_ubrpc.md new file mode 100644 index 0000000..03ba829 --- /dev/null +++ b/docs/cn/case_ubrpc.md @@ -0,0 +1,45 @@ +# 背景 + +云平台部把使用ubrpc的模块改造为使用brpc。由于使用了mcpack2pb的转换功能,这个模块既能被老的ubrpc client访问,也可以通过protobuf类的协议访问(baidu_std,sofa_pbrpc等)。 + +原有使用43台机器(对ubrpc也有富余),brpc使用3台机器即可(此时访问redis的io达到瓶颈)。当前流量4w qps,支持流量增长,考虑跨机房冗余,避免redis和vip瓶颈,brpc实际使用8台机器提供服务。 + +brpc改造后的connecter收益明显,可以用较少的机器提供更优质的服务。收益分3个方面: + +# 相同配置的机器qps和latency的比较 + +通过逐渐缩容,不断增加connecter的压力,获得单机qps和latency的对应数据如下: +![img](../images/ubrpc_compare_1.png) + +机器配置:cpu: 24 Intel(R) Xeon(R) CPU E5645 @ 2.40GHz || mem: 64G + +混布情况:同机部署了逻辑层2.0/3.0和C逻辑层,均有流量 + +图中可以看到随着压力的增大: +* brpc的延时,增加微乎其微,提供了较为一致的延时体验 +* ubrpc的延时,快速增大,到了6000~8000qps的时候,出现*queue full*,服务不可用。 + +# 不同配置机器qps和延时的比较 +qps固定为6500,观察延时。 + +| 机器名称 | 略 | 略 | +| ----- | ---------------------------------------- | ---------------------------------------- | +| cpu | 24 Intel(R) Xeon(R) CPU E5645 @ 2.40GHz | 24 Intel(R) Xeon(R) CPU E5-2620 0 @ 2.00GHz | +| ubrpc | 8363.46(us) | 12649.5(us) | +| brpc | 3364.66(us) | 3382.15(us) | + +有此可见: + +* ubrpc在不同配置下性能表现差异大,在配置较低的机器下表现较差。 +* brpc表现的比ubrpc好,在较低配置的机器上也能有好的表现,因机器不同带来的差异不大。 + +# 相同配置机器idle分布的比较 + +机器配置:cpu: 24 Intel(R) Xeon(R) CPU E5645 @ 2.40GHz || mem:64G + +![img](../images/ubrpc_compare_2.png) + +在线上缩容 不断增大压力过程中: + +* ubrpc cpu idle分布在35%~60%,在55%最集中,最低30%; +* brpc cpu idle分布在60%~85%,在75%最集中,最低50%; brpc比ubrpc对cpu的消耗低。 diff --git a/docs/cn/circuit_breaker.md b/docs/cn/circuit_breaker.md new file mode 100644 index 0000000..bf7b480 --- /dev/null +++ b/docs/cn/circuit_breaker.md @@ -0,0 +1,65 @@ +# 熔断功能 +当我们发起一个rpc之后,brpc首先会从命名服务(naming service)拿到一个可用节点列表,之后根据负载均衡策略挑选出一个节点作为实际访问的节点。当某个节点出现故障时,brpc能够自动将它从可用节点列表中剔除,并周期性的对故障节点进行健康检查。 + +# 默认的熔断策略 +brpc 默认会提供一个简单的熔断策略,在的默认的熔断策略下,brpc若检测到某个节点无法建立连接,则会将该节点熔断。当某一次rpc返回以下错误时,brpc会认为目标节点无法建立连接:ECONNREFUSED 、ENETUNREACH、EHOSTUNREACH、EINVAL。 + +这里需要指出的是,假如brpc发现某个节点出现连续三次连接超时(而不是rpc超时),那么也会把第三次超时当做ENETUNREACH来处理。所以假如rpc的超时时间设置的比连接超时更短,那么当节点无法建立连接时,rpc超时会比连接超时更早触发,最终导致永远触发不了熔断。所以在自定义超时时间时,需要保证rpc的超时时间大于连接超时时间。即ChannelOptions.timeout_ms > ChannelOptions.connect_timeout_ms。 + +默认的熔断策略是一直开启的,并不需要做任何配置,也无法关闭。 + +# 可选的熔断策略 +仅仅依赖上述默认的熔断策略有时候并不能完全满足需求,举个极端的例子: 假如某个下游节点逻辑线程全部卡死,但是io线程能够正常工作,那么所有的请求都会超时,但是tcp连接却能够正常建立。对于这类情况,brpc在默认的熔断策略的基础上,提供了更加激进的熔断策略。开启之后brpc会根据出错率来判断节点是否处于故障状态。 + +## 开启方法 +可选的熔断策略默认是关闭的,用户可以根据实际需要在ChannelOptions中开启: +``` +brpc::ChannelOptions option; +option.enable_circuit_breaker = true; +``` + +## 工作原理 +可选的熔断由CircuitBreaker实现,在开启了熔断之后,CircuitBreaker会记录每一个请求的处理结果,并维护一个累计出错时长,记为acc_error_cost,当acc_error_cost > max_error_cost时,熔断该节点。 + +**每次请求返回成功之后,更新max_error_cost:** +1. 首先需要更新latency的[EMA](https://en.wikipedia.org/wiki/Moving_average)值,记为ema_latency: ema_latency = ema_latency * alpha + (1 - alpha) * latency。 +2. 之后根据ema_latency更新max_error_cost: max_error_cost = window_size * max_error_rate * ema_latency。 + + +上面的window_size和max_error_rate均为gflag所指定的常量, alpha则是一个略小于1的常量,其值由window_size和下面提到的circuit_breaker_epsilon_value决定。latency则指该次请求的耗时。 + +**每次请求返回之后,都会更新acc_error_cost:** +1. 如果请求处理成功,则令 acc_error_cost = alpha * acc_error_cost +2. 如果请求处理失败,则令 acc_error_cost = acc_error_cost + min(latency, ema_latency * 2) + + +上面的alpha与计算max_error_cost所用到的alpha为同一个值。考虑到出现超时等错误时,latency往往会远远大于ema_latency。所以在计算acc_error_cost时对失败请求的latency进行了修正,使其值不超过ema_latency的两倍。这个倍率同样可以通过gflag配置。 + + +**根据实际需要配置熔断参数:** + +为了允许某个节点在短时间内抖动,同时又能够剔除长期错误率较高的节点,CircuitBreaker同时维护了长短两个窗口,长窗口阈值较低,短窗口阈值较高。长窗口的主要作用是剔除那些长期错误率较高的服务。我们可以根据实际的qps及对于错误的容忍程度来调整circuit_breaker_long_window_size及circuit_breaker_long_window_error_percent。 + +短窗口则允许我们更加精细的控制熔断的灵敏度,在一些对抖动很敏感的场景,可以通过调整circuit_breaker_short_window_size和circuit_breaker_short_window_error_percent来缩短短窗口的长度、降低短窗口对于错误的容忍程度,使得出现抖动时能够快速对故障节点进行熔断。 + +此外,circuit_breaker_epsilon_value可以调整窗口对于**连续抖动的容忍程度**,circuit_breaker_epsilon_value的值越低,计算公式中的alpha越小,acc_error_cost下降的速度就越快,当circuit_breaker_epsilon_value的值达到0.001时,若一整个窗口的请求都没有出错,那么正好可以把acc_error_cost降低到0。 + +由于计算EMA需要积累一定量的数据,在熔断的初始阶段(即目前已经收集到的请求 < 窗口大小),会直接使用错误数量来判定是否该熔断,即:若 acc_error_count > window_size * max_error_rate 为真,则进行熔断。 + +## 熔断的范围 +brpc在决定熔断某个节点时,会熔断掉整个连接,即: +1. 假如我们使用pooled模式,那么会熔断掉所有的连接。 +2. brpc的tcp连接是会被channel所共享的,当某个连接被熔断之后,所有的channel都不能再使用这个故障的连接。 +3. 假如想要避免2中所述的情况,可以通过设置ChannelOptions.connection_group将channel放进不同的ConnectionGroup,不同ConnectionGroup的channel并不会共享连接。 + +## 熔断数据的收集 +只有通过开启了enable_circuit_breaker的channel发送的请求,才会将请求的处理结果提交到CircuitBreaker。所以假如我们决定对下游某个服务开启可选的熔断策略,最好是在所有的连接到该服务的channel里都开启enable_circuit_breaker。 + +## 熔断的恢复 +目前brpc使用通用的健康检查来判定某个节点是否已经恢复,即只要能够建立tcp连接则认为该节点已经恢复。为了能够正确的摘除那些能够建立tcp连接的故障节点,每次熔断之后会先对故障节点进行一段时间的隔离,隔离期间故障节点即不会被lb选中,也不会进行健康检查。若节点在短时间内被连续熔断,则隔离时间翻倍。初始的隔离时间为100ms,最大的隔离时间和判断两次熔断是否为连续熔断的时间间隔都使用circuit_breaker_max_isolation_duration_ms控制,默认为30秒。 + +## 数据体现 +节点的熔断次数、最近一次从熔断中恢复之后的累积错误数都可以在监控页面的/connections里找到,即便我们没有开启可选的熔断策略,brpc也会对这些数据进行统计。nBreak表示进程启动之后该节点的总熔断次数,RecentErr则表示该节点最近一次从熔断中恢复之后,累计的出错请求数。 + +由于brpc默认熔断策略是一直开启的,即便我们没有开启可选的熔断策略,nBreak还是可能会大于0,这时nBreak通常是因为tcp连接建立失败而产生的。 + diff --git a/docs/cn/client.md b/docs/cn/client.md new file mode 100755 index 0000000..bb16eef --- /dev/null +++ b/docs/cn/client.md @@ -0,0 +1,1028 @@ +[English version](../en/client.md) + +# 示例程序 + +Echo的[client端代码](https://github.com/apache/brpc/blob/master/example/echo_c++/client.cpp)。 + +# 事实速查 + +- Channel.Init()是线程不安全的。 +- Channel.CallMethod()是线程安全的,一个Channel可以被所有线程同时使用。 +- Channel可以分配在栈上。 +- Channel在发送异步请求后可以析构。 +- 没有brpc::Client这个类。 + +# Channel +Client指发起请求的一端,在brpc中没有对应的实体,取而代之的是[brpc::Channel](https://github.com/apache/brpc/blob/master/src/brpc/channel.h),它代表和一台或一组服务器的交互通道,Client和Channel在角色上的差别在实践中并不重要,你可以把Channel视作Client。 + +Channel可以**被所有线程共用**,你不需要为每个线程创建独立的Channel,也不需要用锁互斥。不过Channel的创建和Init并不是线程安全的,请确保在Init成功后再被多线程访问,在没有线程访问后再析构。 + +一些RPC实现中有ClientManager的概念,包含了Client端的配置信息和资源管理。brpc不需要这些,以往在ClientManager中配置的线程数、长短连接等等要么被加入了brpc::ChannelOptions,要么可以通过gflags全局配置,这么做的好处: + +1. 方便。你不需要在创建Channel时传入ClientManager,也不需要存储ClientManager。否则不少代码需要一层层地传递ClientManager,很麻烦。gflags使一些全局行为的配置更加简单。 +2. 共用资源。比如server和channel可以共用后台线程。(bthread的工作线程) +3. 生命周期。析构ClientManager的过程很容易出错,现在由框架负责则不会有问题。 + +就像大部分类那样,Channel必须在**Init**之后才能使用,options为NULL时所有参数取默认值,如果你要使用非默认值,这么做就行了: +```c++ +brpc::ChannelOptions options; // 包含了默认值 +options.xxx = yyy; +... +channel.Init(..., &options); +``` +注意Channel不会修改options,Init结束后不会再访问options。所以options一般就像上面代码中那样放栈上。Channel.options()可以获得channel在使用的所有选项。 + +Init函数分为连接一台服务器和连接服务集群。 + +# 连接一台服务器 + +```c++ +// options为NULL时取默认值 +int Init(EndPoint server_addr_and_port, const ChannelOptions* options); +int Init(const char* server_addr_and_port, const ChannelOptions* options); +int Init(const char* server_addr, int port, const ChannelOptions* options); +``` +这类Init连接的服务器往往有固定的ip地址,不需要命名服务和负载均衡,创建起来相对轻量。但是**请勿频繁创建使用域名的Channel**。这需要查询dns,可能最多耗时10秒(查询DNS的默认超时)。重用它们。 + +合法的“server_addr_and_port”: +- 127.0.0.1:80 +- www.foo.com:8765 +- localhost:9000 +- [::1]:8080 # IPV6 +- unix:path.sock # Unix domain socket + +不合法的"server_addr_and_port": +- 127.0.0.1:90000 # 端口过大 +- 10.39.2.300:8000 # 非法的ip + +关于IPV6和Unix domain socket的使用,详见 [EndPoint](endpoint.md)。 + +# 连接服务集群 + +```c++ +int Init(const char* naming_service_url, + const char* load_balancer_name, + const ChannelOptions* options); +``` +这类Channel需要定期从`naming_service_url`指定的命名服务中获得服务器列表,并通过`load_balancer_name`指定的负载均衡算法选择出一台机器发送请求。 + +你**不应该**在每次请求前动态地创建此类(连接服务集群的)Channel。因为创建和析构此类Channel牵涉到较多的资源,比如在创建时得访问一次命名服务,否则便不知道有哪些服务器可选。由于Channel可被多个线程共用,一般也没有必要动态创建。 + +当`load_balancer_name`为NULL或空时,此Init等同于连接单台server的Init,`naming_service_url`应该是"ip:port"或"域名:port"。你可以通过这个Init函数统一Channel的初始化方式。比如你可以把`naming_service_url`和`load_balancer_name`放在配置文件中,要连接单台server时把`load_balancer_name`置空,要连接服务集群时则设置一个有效的算法名称。 + +## 命名服务 + +命名服务把一个名字映射为可修改的机器列表,在client端的位置如下: + +![img](../images/ns.png) + +有了命名服务后client记录的是一个名字,而不是每一台下游机器。而当下游机器变化时,就只需要修改命名服务中的列表,而不需要逐台修改每个上游。这个过程也常被称为“解耦上下游”。当然在具体实现上,上游会记录每一台下游机器,并定期向命名服务请求或被推送最新的列表,以避免在RPC请求时才去访问命名服务。使用命名服务一般不会对访问性能造成影响,对命名服务的压力也很小。 + +`naming_service_url`的一般形式是"**protocol://service_name**" + +### bns://\ + +BNS是百度内常用的命名服务,比如bns://rdev.matrix.all,其中"bns"是protocol,"rdev.matrix.all"是service-name。相关一个gflag是-ns_access_interval: ![img](../images/ns_access_interval.png) + +如果BNS中显示不为空,但Channel却说找不到服务器,那么有可能BNS列表中的机器状态位(status)为非0,含义为机器不可用,所以不会被加入到server候选集中.状态位可通过命令行查看: + +`get_instance_by_service [bns_node_name] -s` + +### file://\ + +服务器列表放在`path`所在的文件里,比如"file://conf/machine_list"中的“conf/machine_list”对应一个文件: + * 每行是一台服务器的地址。 + * \#之后的是注释会被忽略 + * 地址后出现的非注释内容被认为是tag,由一个或多个空格与前面的地址分隔,相同的地址+不同的tag被认为是不同的实例。 + * 当文件更新时, brpc会重新加载。 +``` +# 此行会被忽略 +10.24.234.17:8080 tag1 # 这是注释,会被忽略 +10.24.234.17:8090 tag2 # 此行和上一行被认为是不同的实例 +10.24.234.18:8080 +10.24.234.19:8080 +``` + +优点: 易于修改,方便单测。 + +缺点: 更新时需要修改每个上游的列表文件,不适合线上部署。 + +### list://\,\... + +服务器列表直接跟在list://之后,以逗号分隔,比如"list://db-bce-81-3-186.db01:7000,m1-bce-44-67-72.m1:7000,cp01-rd-cos-006.cp01:7000"中有三个地址。 + +地址后可以声明tag,用一个或多个空格分隔,相同的地址+不同的tag被认为是不同的实例。 + +优点: 可在命令行中直接配置,方便单测。 + +缺点: 无法在运行时修改,完全不能用于线上部署。 + +### http://\ + +连接一个域名下所有的机器, 例如http://www.baidu.com:80 ,注意连接单点的Init(两个参数)虽然也可传入域名,但只会连接域名下的一台机器。 + +优点: DNS的通用性,公网内网均可使用。 + +缺点: 受限于DNS的格式限制无法传递复杂的meta数据,也无法实现通知机制。 + +### https://\ + +和http前缀类似,只是会自动开启SSL。 + +### consul://\ + +通过consul获取服务名称为service-name的服务列表。consul的默认地址是localhost:8500,可通过gflags设置-consul\_agent\_addr来修改。consul的连接超时时间默认是200ms,可通过-consul\_connect\_timeout\_ms来修改。 + +默认在consul请求参数中添加[stale](https://www.consul.io/api/index.html#consistency-modes)和passing(仅返回状态为passing的服务列表),可通过gflags中-consul\_url\_parameter改变[consul请求参数](https://www.consul.io/api/health.html#parameters-2)。 + +除了对consul的首次请求,后续对consul的请求都采用[long polling](https://www.consul.io/api/index.html#blocking-queries)的方式,即仅当服务列表更新或请求超时后consul才返回结果,这里超时时间默认为60s,可通过-consul\_blocking\_query\_wait\_secs来设置。 + +若consul返回的服务列表[响应格式](https://www.consul.io/api/health.html#sample-response-2)有错误,或者列表中所有服务都因为地址、端口等关键字段缺失或无法解析而被过滤,consul naming server会拒绝更新服务列表,并在一段时间后(默认500ms,可通过-consul\_retry\_interval\_ms设置)重新访问consul。 + +如果consul不可访问,服务可自动降级到file naming service获取服务列表。此功能默认关闭,可通过设置-consul\_enable\_degrade\_to\_file\_naming\_service来打开。服务列表文件目录通过-consul \_file\_naming\_service\_dir来设置,使用service-name作为文件名。该文件可通过consul-template生成,里面会保存consul不可用之前最新的下游服务节点。当consul恢复时可自动恢复到consul naming service。 + + +### nacos://\ + +NacosNamingService使用[Open-Api](https://nacos.io/zh-cn/docs/open-api.html)定时从nacos获取服务列表。 +NacosNamingService支持[简单鉴权](https://nacos.io/zh-cn/docs/auth.html)。 + +``是一个http uri query,具体参数参见`/nacos/v1/ns/instance/list`文档。 +注意:``需要urlencode。 +``` +nacos://serviceName=test&groupName=g&namespaceId=n&clusters=c&healthyOnly=true +``` + +NacosNamingService拉取列表的时间间隔为`/nacos/v1/ns/instance/list`api返回的`cacheMillis`。 +NacosNamingService只支持整形的权重值。 + +| GFlags | 描述 | 默认值 | +| ---------------------------------- | -------------------------- | ---------------------------- | +| nacos_address | nacos http url | "" | +| nacos_service_discovery_path | nacos服务发现路径 | "/nacos/v1/ns/instance/list" | +| nacos_service_auth_path | nacos登陆路径 | "/nacos/v1/auth/login" | +| nacos_service_timeout_ms | 连接nacos超时时间(毫秒) | 200 | +| nacos_username | 用户名(urlencode编码) | "" | +| nacos_password | 密码(urlencode编码) | "" | +| nacos_load_balancer | nacos集群的负载均衡 | "rr" | + + +### 更多命名服务 +用户可以通过实现brpc::NamingService来对接更多命名服务,具体见[这里](https://github.com/apache/brpc/blob/master/docs/cn/load_balancing.md#%E5%91%BD%E5%90%8D%E6%9C%8D%E5%8A%A1) + +### 命名服务中的tag +每个地址可以附带一个tag,在常见的命名服务中,如果地址后有空格,则空格之后的内容均为tag。 +相同的地址配合不同的tag被认为是不同的实例,brpc会建立不同的连接。用户可利用这个特性更灵活地控制与单个地址的连接方式。 +如果你需要"带权重的轮询",你应当优先考虑使用[wrr算法](#wrr),而不是用tag来模拟。 + +### VIP相关的问题 +VIP一般是4层负载均衡器的公网ip,背后有多个RS。当客户端连接至VIP时,VIP会选择一个RS建立连接,当客户端连接断开时,VIP也会断开与对应RS的连接。 + +如果客户端只与VIP建立一个连接(brpc中的单连接),那么来自这个客户端的所有流量都会落到一台RS上。如果客户端的数量非常多,至少在集群的角度,所有的RS还是会分到足够多的连接,从而基本均衡。但如果客户端的数量不多,或客户端的负载差异很大,那么可能在个别RS上出现热点。另一个问题是当有多个VIP可选时,客户端分给它们的流量与各自后面的RS数量可能不一致。 + +解决这个问题的一种方法是使用连接池模式(pooled),这样客户端对一个VIP就可能建立多个连接(约为一段时间内的最大并发度),从而让负载落到多个RS上。如果有多个VIP,可以用[wrr负载均衡](#wrr)给不同的VIP声明不同的权重从而分到对应比例的流量,或给相同的VIP后加上多个不同的tag而被认为是多个不同的实例。 + +如果对性能有更高的要求,或要限制大集群中连接的数量,可以使用单连接并给相同的VIP加上不同的tag以建立多个连接。相比连接池一般连接数量更小,系统调用开销更低,但如果tag不够多,仍可能出现RS热点。 + +### 命名服务过滤器 + +当命名服务获得机器列表后,可以自定义一个过滤器进行筛选,最后把结果传递给负载均衡: + +![img](../images/ns_filter.jpg) + +过滤器的接口如下: +```c++ +// naming_service_filter.h +class NamingServiceFilter { +public: + // Return true to take this `server' as a candidate to issue RPC + // Return false to filter it out + virtual bool Accept(const ServerNode& server) const = 0; +}; + +// naming_service.h +struct ServerNode { + butil::EndPoint addr; + std::string tag; +}; +``` +常见的业务策略如根据server的tag进行过滤。 + +自定义的过滤器配置在ChannelOptions中,默认为NULL(不过滤)。 + +```c++ +class MyNamingServiceFilter : public brpc::NamingServiceFilter { +public: + bool Accept(const brpc::ServerNode& server) const { + return server.tag == "main"; + } +}; + +int main() { + ... + MyNamingServiceFilter my_filter; + ... + brpc::ChannelOptions options; + options.ns_filter = &my_filter; + ... +} +``` + +## 负载均衡 + +当下游机器超过一台时,我们需要分割流量,此过程一般称为负载均衡,在client端的位置如下图所示: + +![img](../images/lb.png) + +理想的算法是每个请求都得到及时的处理,且任意机器crash对全局影响较小。但由于client端无法及时获得server端的延迟或拥塞,而且负载均衡算法不能耗费太多的cpu,一般来说用户得根据具体的场景选择合适的算法,目前rpc提供的算法有(通过load_balancer_name指定): + +### rr + +即round robin,总是选择列表中的下一台服务器,结尾的下一台是开头,无需其他设置。比如有3台机器a,b,c,那么brpc会依次向a, b, c, a, b, c, ...发送请求。注意这个算法的前提是服务器的配置,网络条件,负载都是类似的。 + +### wrr + +即weighted round robin, 根据服务器列表配置的权重值来选择服务器。服务器被选到的机会正比于其权重值,并且该算法能保证同一服务器被选到的结果较均衡的散开。 + +实例的tag需要是表示权值的int32数字,如tag="50"。 + +### random + +随机从列表中选择一台服务器,无需其他设置。和round robin类似,这个算法的前提也是服务器都是类似的。 + +### wr + +即weighted random, 根据服务器列表配置的权重值来选择服务器,服务器被选到的机会正比于其权重值。 + +实例tag的要求同wrr。 + +### la + +locality-aware,优先选择延时低的下游,直到其延时高于其他机器,无需其他设置。实现原理请查看[Locality-aware load balancing](lalb.md)。 + +### p2c + +即power-of-two-choices加peak-EWMA延时评分。每次选择随机采样两台服务器,把请求发给`延时 * (inflight + 1) / 权重`得分较低的那台。其中延时是对尖峰敏感的滑动平均:延时上升立即生效,恢复则按`tau_ms`(默认10秒)衰减。变慢或出错的服务器在一次观察内即被避开,且选择开销与集群规模无关,为O(1)。权重取自实例tag(同wrr,默认为1)。可选参数:`p2c:choices=4`(每次比较4台采样服务器,适合多台机器同时劣化的场景)、`p2c:tau_ms=5000`。 + +### c_murmurhash or c_md5 + +一致性哈希,与简单hash的不同之处在于增加或删除机器时不会使分桶结果剧烈变化,特别适合cache类服务。 + +发起RPC前需要设置Controller.set_request_code(),否则RPC会失败。request_code一般是请求中主键部分的32位哈希值,**不需要和负载均衡使用的哈希算法一致**。比如用c_murmurhash算法也可以用md5计算哈希值。 + +[src/brpc/policy/hasher.h](https://github.com/apache/brpc/blob/master/src/brpc/policy/hasher.h)中包含了常用的hash函数。如果用std::string key代表请求的主键,controller.set_request_code(brpc::policy::MurmurHash32(key.data(), key.size()))就正确地设置了request_code。 + +注意甄别请求中的“主键”部分和“属性”部分,不要为了偷懒或通用,就把请求的所有内容一股脑儿计算出哈希值,属性的变化会使请求的目的地发生剧烈的变化。另外也要注意padding问题,比如struct Foo { int32_t a; int64_t b; }在64位机器上a和b之间有4个字节的空隙,内容未定义,如果像hash(&foo, sizeof(foo))这样计算哈希值,结果就是未定义的,得把内容紧密排列或序列化后再算。 + +实现原理请查看[Consistent Hashing](consistent_hashing.md)。 + +其他lb不需要设置Controller.set_request_code(),如果调用了request_code也不会被lb使用,例如:lb=rr调用了Controller.set_request_code(),即使所有RPC的request_code都相同,也依然是rr。 + +### 从集群宕机后恢复时的客户端限流 + +集群宕机指的是集群中所有server都处于不可用的状态。由于健康检查机制,当集群恢复正常后,server会间隔性地上线。当某一个server上线后,所有的流量会发送过去,可能导致服务再次过载。若熔断开启,则可能导致其它server上线前该server再次熔断,集群永远无法恢复。作为解决方案,brpc提供了在集群宕机后恢复时的限流机制:当集群中没有可用server时,集群进入恢复状态,假设正好能服务所有请求的server数量为min_working_instances,当前集群可用的server数量为q,则在恢复状态时,client接受请求的概率为q/min_working_instances,否则丢弃;若一段时间hold_seconds内q保持不变,则把流量重新发送全部可用的server上,并离开恢复状态。在恢复阶段时,可以通过判断controller.ErrorCode()是否等于brpc::ERJECT来判断该次请求是否被拒绝,被拒绝的请求不会被框架重试。 + +此恢复机制要求下游server的能力是类似的,所以目前只针对rr和random有效,开启方式是在*load_balancer_name*后面加上min_working_instances和hold_seconds参数的值,例如: +```c++ +channel.Init("http://...", "random:min_working_instances=6 hold_seconds=10", &options); +``` + +## 健康检查 + +连接断开的server会被暂时隔离而不会被负载均衡算法选中,brpc会定期连接被隔离的server,以检查他们是否恢复正常,间隔由参数-health_check_interval控制: + +| Name | Value | Description | Defined At | +| ------------------------- | ----- | ---------------------------------------- | ----------------------- | +| health_check_interval (R) | 3 | seconds between consecutive health-checkings | src/brpc/socket_map.cpp | + +在默认的配置下,一旦server被连接上,它会恢复为可用状态,可通过-health\_check\_timeout\_ms设置超时(默认500ms);brpc还提供了应用层健康检查的机制,框架会发送一个HTTP GET请求到该server,只有当server返回200时,它才会恢复,在这种机制下,既可通过-health\_check\_path(默认为空)和-health\_check\_timeout\_ms(默认500ms)分别设置全局的健康检查请求路径和超时,也可通过ChannelOptions中的hc_option成员变量来对不同的channel设置不同的请求路径和超时,ChannelOptions设置的健康检查参数优先级要高于gflag参数。如果在隔离过程中,server从命名服务中删除了,brpc也会停止连接尝试。 + +# 发起访问 + +一般来说,我们不直接调用Channel.CallMethod,而是通过protobuf生成的桩XXX_Stub,过程更像是“调用函数”。stub内没什么成员变量,建议在栈上创建和使用,而不必new,当然你也可以把stub存下来复用。Channel::CallMethod和stub访问都是**线程安全**的,可以被所有线程同时访问。比如: +```c++ +XXX_Stub stub(&channel); +stub.some_method(controller, request, response, done); +``` +甚至 +```c++ +XXX_Stub(&channel).some_method(controller, request, response, done); +``` +一个例外是http/h2 client。访问http服务和protobuf没什么关系,直接调用CallMethod即可,除了Controller和done均为NULL,详见[访问http/h2服务](http_client.md)。 + +## 同步访问 + +指的是:CallMethod会阻塞到收到server端返回response或发生错误(包括超时)。 + +同步访问中的response/controller不会在CallMethod后被框架使用,它们都可以分配在栈上。注意,如果request/response字段特别多字节数特别大的话,还是更适合分配在堆上。 +```c++ +MyRequest request; +MyResponse response; +brpc::Controller cntl; +XXX_Stub stub(&channel); + +request.set_foo(...); +cntl.set_timeout_ms(...); +stub.some_method(&cntl, &request, &response, NULL); +if (cntl->Failed()) { + // RPC失败了. response里的值是未定义的,勿用。 +} else { + // RPC成功了,response里有我们想要的回复数据。 +} +``` + +> 警告: 请勿在持有pthread锁的情况下,调用brpc的同步CallMethod!否则很容易导致死锁。 +> +> 解决方案(二选一): +> 1. 将pthread锁换成bthread锁(bthread_mutex_t) +> 1. 在CallMethod之前将锁释放 + +## 异步访问 + +指的是:给CallMethod传递一个额外的回调对象done,CallMethod在发出request后就结束了,而不是在RPC结束后。当server端返回response或发生错误(包括超时)时,done->Run()会被调用。对RPC的后续处理应该写在done->Run()里,而不是CallMethod后。 + +由于CallMethod结束不意味着RPC结束,response/controller仍可能被框架及done->Run()使用,它们一般得创建在堆上,并在done->Run()中删除。如果提前删除了它们,那当done->Run()被调用时,将访问到无效内存。 + +你可以独立地创建这些对象,并使用[NewCallback](#使用NewCallback)生成done,也可以把Response和Controller作为done的成员变量,[一起new出来](#继承google::protobuf::Closure),一般使用前一种方法。 + +发起异步请求后Request可以立刻析构。(SelectiveChannel是个例外,SelectiveChannel情况下必须在请求处理完成后再释放request对象) + +发起异步请求后Channel可以立刻析构。 + +注意:这是说Request/Channel的析构可以立刻发生在CallMethod**之后**,并不是说析构可以和CallMethod同时发生,删除正被另一个线程使用的Channel是未定义行为(很可能crash)。 + +### 使用NewCallback +```c++ +static void OnRPCDone(MyResponse* response, brpc::Controller* cntl) { + // unique_ptr会帮助我们在return时自动删掉response/cntl,防止忘记。gcc 3.4下的unique_ptr是模拟版本。 + std::unique_ptr response_guard(response); + std::unique_ptr cntl_guard(cntl); + if (cntl->Failed()) { + // RPC失败了. response里的值是未定义的,勿用。 + } else { + // RPC成功了,response里有我们想要的数据。开始RPC的后续处理。 + } + // NewCallback产生的Closure会在Run结束后删除自己,不用我们做。 +} + +MyResponse* response = new MyResponse; +brpc::Controller* cntl = new brpc::Controller; +MyService_Stub stub(&channel); + +MyRequest request; // 你不用new request,即使在异步访问中. +request.set_foo(...); +cntl->set_timeout_ms(...); +stub.some_method(cntl, &request, response, brpc::NewCallback(OnRPCDone, response, cntl)); +``` +由于protobuf 3把NewCallback设置为私有,r32035后brpc把NewCallback独立于[src/brpc/callback.h](https://github.com/apache/brpc/blob/master/src/brpc/callback.h)(并增加了一些重载)。如果你的程序出现NewCallback相关的编译错误,把google::protobuf::NewCallback替换为brpc::NewCallback就行了。 + +### 继承google::protobuf::Closure + +使用NewCallback的缺点是要分配三次内存:response, controller, done。如果profiler证明这儿的内存分配有瓶颈,可以考虑自己继承Closure,把response/controller作为成员变量,这样可以把三次new合并为一次。但缺点就是代码不够美观,如果内存分配不是瓶颈,别用这种方法。 +```c++ +class OnRPCDone: public google::protobuf::Closure { +public: + void Run() { + // unique_ptr会帮助我们在return时自动delete this,防止忘记。gcc 3.4下的unique_ptr是模拟版本。 + std::unique_ptr self_guard(this); + + if (cntl->Failed()) { + // RPC失败了. response里的值是未定义的,勿用。 + } else { + // RPC成功了,response里有我们想要的数据。开始RPC的后续处理。 + } + } + + MyResponse response; + brpc::Controller cntl; +} + +OnRPCDone* done = new OnRPCDone; +MyService_Stub stub(&channel); + +MyRequest request; // 你不用new request,即使在异步访问中. +request.set_foo(...); +done->cntl.set_timeout_ms(...); +stub.some_method(&done->cntl, &request, &done->response, done); +``` + +### 如果异步访问中的回调函数特别复杂会有什么影响吗? + +没有特别的影响,回调会运行在独立的bthread中,不会阻塞其他的逻辑。你可以在回调中做各种阻塞操作。 + +### rpc发送处的代码和回调函数是在同一个线程里执行吗? + +一定不在同一个线程里运行,即使该次rpc调用刚进去就失败了,回调也会在另一个bthread中运行。这可以在加锁进行rpc(不推荐)的代码中避免死锁。 + +## 等待RPC完成 +注意:当你需要发起多个并发操作时,可能[ParallelChannel](combo_channel.md#parallelchannel)更方便。 + +如下代码发起两个异步RPC后等待它们完成。 +```c++ +const brpc::CallId cid1 = controller1->call_id(); +const brpc::CallId cid2 = controller2->call_id(); +... +stub.method1(controller1, request1, response1, done1); +stub.method2(controller2, request2, response2, done2); +... +brpc::Join(cid1); +brpc::Join(cid2); +``` +**在发起RPC前**调用Controller.call_id()获得一个id,发起RPC调用后Join那个id。 + +Join()的行为是等到RPC结束**且done->Run()运行后**,一些Join的性质如下: + +- 如果对应的RPC已经结束,Join将立刻返回。 +- 多个线程可以Join同一个id,它们都会醒来。 +- 同步RPC也可以在另一个线程中被Join,但一般不会这么做。 + +Join()在之前的版本叫做JoinResponse(),如果你在编译时被提示deprecated之类的,修改为Join()。 + +在RPC调用后`Join(controller->call_id())`是**错误**的行为,一定要先把call_id保存下来。因为RPC调用后controller可能被随时开始运行的done删除。下面代码的Join方式是**错误**的。 + +```c++ +static void on_rpc_done(Controller* controller, MyResponse* response) { + ... Handle response ... + delete controller; + delete response; +} + +Controller* controller1 = new Controller; +Controller* controller2 = new Controller; +MyResponse* response1 = new MyResponse; +MyResponse* response2 = new MyResponse; +... +stub.method1(controller1, &request1, response1, google::protobuf::NewCallback(on_rpc_done, controller1, response1)); +stub.method2(controller2, &request2, response2, google::protobuf::NewCallback(on_rpc_done, controller2, response2)); +... +brpc::Join(controller1->call_id()); // 错误,controller1可能被on_rpc_done删除了 +brpc::Join(controller2->call_id()); // 错误,controller2可能被on_rpc_done删除了 +``` + +## 半同步 + +Join可用来实现“半同步”访问:即等待多个异步访问完成。由于调用处的代码会等到所有RPC都结束后再醒来,所以controller和response都可以放栈上。 +```c++ +brpc::Controller cntl1; +brpc::Controller cntl2; +MyResponse response1; +MyResponse response2; +... +stub1.method1(&cntl1, &request1, &response1, brpc::DoNothing()); +stub2.method2(&cntl2, &request2, &response2, brpc::DoNothing()); +... +brpc::Join(cntl1.call_id()); +brpc::Join(cntl2.call_id()); +``` +brpc::DoNothing()可获得一个什么都不干的done,专门用于半同步访问。它的生命周期由框架管理,用户不用关心。 + +注意在上面的代码中,我们在RPC结束后又访问了controller.call_id(),这是没有问题的,因为DoNothing中并不会像上节中的on_rpc_done中那样删除Controller。 + +## 取消RPC + +brpc::StartCancel(call_id)可取消对应的RPC,call_id必须**在发起RPC前**通过Controller.call_id()获得,其他时刻都可能有race condition。 + +注意:是brpc::StartCancel(call_id),不是controller->StartCancel(),后者被禁用,没有效果。后者是protobuf默认提供的接口,但是在controller对象的生命周期上有严重的竞争问题。 + +顾名思义,StartCancel调用完成后RPC并未立刻结束,你不应该碰触Controller的任何字段或删除任何资源,它们自然会在RPC结束时被done中对应逻辑处理。如果你一定要在原地等到RPC结束(一般不需要),则可通过Join(call_id)。 + +关于StartCancel的一些事实: + +- call_id在发起RPC前就可以被取消,RPC会直接结束(done仍会被调用)。 +- call_id可以在另一个线程中被取消。 +- 取消一个已经取消的call_id不会有任何效果。推论:同一个call_id可以被多个线程同时取消,但最多一次有效果。 +- 这里的取消是纯client端的功能,**server端未必会取消对应的操作**,server cancelation是另一个功能。 + +## 获取Server的地址和端口 + +remote_side()方法可知道request被送向了哪个server,返回值类型是[butil::EndPoint](https://github.com/apache/brpc/blob/master/src/butil/endpoint.h),包含一个ip4地址和端口。在RPC结束前调用这个方法都是没有意义的。 + +打印方式: +```c++ +LOG(INFO) << "remote_side=" << cntl->remote_side(); +printf("remote_side=%s\n", butil::endpoint2str(cntl->remote_side()).c_str()); +``` +## 获取Client的地址和端口 + +r31384后通过local_side()方法可**在RPC结束后**获得发起RPC的地址和端口。 + +打印方式: +```c++ +LOG(INFO) << "local_side=" << cntl->local_side(); +printf("local_side=%s\n", butil::endpoint2str(cntl->local_side()).c_str()); +``` +## 应该重用brpc::Controller吗? + +不用刻意地重用,但Controller是个大杂烩,可能会包含一些缓存,Reset()可以避免反复地创建这些缓存。 + +在大部分场景下,构造Controller(snippet1)和重置Controller(snippet2)的性能差异不大。 +```c++ +// snippet1 +for (int i = 0; i < n; ++i) { + brpc::Controller controller; + ... + stub.CallSomething(..., &controller); +} + +// snippet2 +brpc::Controller controller; +for (int i = 0; i < n; ++i) { + controller.Reset(); + ... + stub.CallSomething(..., &controller); +} +``` +但如果snippet1中的Controller是new出来的,那么snippet1就会多出“内存分配”的开销,在一些情况下可能会慢一些。 + +# 设置 + +Client端的设置主要由三部分组成: + +- brpc::ChannelOptions: 定义在[src/brpc/channel.h](https://github.com/apache/brpc/blob/master/src/brpc/channel.h)中,用于初始化Channel,一旦初始化成功无法修改。 +- brpc::Controller: 定义在[src/brpc/controller.h](https://github.com/apache/brpc/blob/master/src/brpc/controller.h)中,用于在某次RPC中覆盖ChannelOptions中的选项,可根据上下文每次均不同。 +- 全局gflags:常用于调节一些底层代码的行为,一般不用修改。请自行阅读服务[/flags页面](flags.md)中的说明。 + +Controller包含了request中没有的数据和选项。server端和client端的Controller结构体是一样的,但使用的字段可能是不同的,你需要仔细阅读Controller中的注释,明确哪些字段可以在server端使用,哪些可以在client端使用。 + +一个Controller对应一次RPC。一个Controller可以在Reset()后被另一个RPC复用,但一个Controller不能被多个RPC同时使用(不论是否在同一个线程发起)。 + +Controller的特点: +1. 一个Controller只能有一个使用者,没有特殊说明的话,Controller中的方法默认线程不安全。 +2. 因为不能被共享,所以一般不会用共享指针管理Controller,如果你用共享指针了,很可能意味着出错了。 +3. Controller创建于开始RPC前,析构于RPC结束后,常见几种模式: + - 同步RPC前Controller放栈上,出作用域后自行析构。注意异步RPC的Controller绝对不能放栈上,否则其析构时异步调用很可能还在进行中,从而引发未定义行为。 + - 异步RPC前new Controller,done中删除。 + +## 线程数 + +和大部分的RPC框架不同,brpc中并没有独立的Client线程池。所有Channel和Server通过[bthread](bthread.md)共享相同的线程池. 如果你的程序同样使用了brpc的server, 仅仅需要设置Server的线程数。 或者可以通过[gflags](flags.md)设置[-bthread_concurrency](http://brpc.baidu.com:8765/flags/bthread_concurrency)来设置全局的线程数. + +## 超时 + +**ChannelOptions.timeout_ms**是对应Channel上所有RPC的总超时,Controller.set_timeout_ms()可修改某次RPC的值。单位毫秒,默认值1秒,最大值2^31(约24天),-1表示一直等到回复或错误。 + +**ChannelOptions.connect_timeout_ms**是对应Channel上所有RPC的连接超时(单位毫秒)。-1表示等到连接建立或出错,此值被限制为不能超过timeout_ms。注意此超时独立于TCP的连接超时,一般来说前者小于后者,反之则可能在connect_timeout_ms未达到前由于TCP连接超时而出错。 + +注意1:brpc中的超时是deadline,超过就意味着RPC结束,超时后没有重试。其他实现可能既有单次访问的超时,也有代表deadline的超时。迁移到brpc时请仔细区分。 + +注意2:RPC超时的错误码为**ERPCTIMEDOUT (1008)**,ETIMEDOUT的意思是连接超时,且可重试。 + +## 重试 + +ChannelOptions.max_retry是该Channel上所有RPC的默认最大重试次数,默认值3,0表示不重试。Controller.set_max_retry()可修改某次RPC的值。 + +r32111后Controller.retried_count()返回重试次数。 + +r34717后Controller.has_backup_request()获知是否发送过backup_request。 + +**重试时框架会尽量避开之前尝试过的server。** + +重试的触发条件有(条件之间是AND关系): + +* 连接出错 +* 没到超时 +* 有剩余重试次数 +* 错误值得重试 + +### 连接出错 + +如果server一直没有返回,但连接没有问题,这种情况下不会重试。如果你需要在一定时间后发送另一个请求,使用backup request。 + +### 没到超时 + +超时后RPC会尽快结束。 + +### 有剩余重试次数 + +Controller.set_max_retry(0)或ChannelOptions.max_retry=0关闭重试。 + +### 错误值得重试 + +一些错误重试是没有意义的,就不会重试,比如请求有错时(EREQUEST)不会重试,因为server总不会接受,没有意义。 + +用户可以通过继承[brpc::RetryPolicy](https://github.com/apache/brpc/blob/master/src/brpc/retry_policy.h)自定义重试条件。比如brpc默认不重试http/h2相关的错误,而你的程序中希望在碰到HTTP_STATUS_FORBIDDEN (403)时重试,可以这么做: + +```c++ +#include + +class MyRetryPolicy : public brpc::RetryPolicy { +public: + bool DoRetry(const brpc::Controller* cntl) const { + if (cntl->ErrorCode() == brpc::EHTTP && // http/h2错误 + cntl->http_response().status_code() == brpc::HTTP_STATUS_FORBIDDEN) { + return true; + } + // 把其他情况丢给框架。 + return brpc::DefaultRetryPolicy()->DoRetry(cntl); + } +}; +... + +// 给ChannelOptions.retry_policy赋值就行了。 +// 注意:retry_policy必须在Channel使用期间保持有效,Channel也不会删除retry_policy,所以大部分情况下RetryPolicy都应以单例模式创建。 +brpc::ChannelOptions options; +static MyRetryPolicy g_my_retry_policy; +options.retry_policy = &g_my_retry_policy; +... +``` + +一些提示: + +* 通过cntl->response()可获得对应RPC的response。 +* 对ERPCTIMEDOUT代表的RPC超时总是不重试,即使你继承的RetryPolicy中允许。 + + +### 重试退避 + +对于一些暂时性的错误,如网络抖动等,等待一小会儿再重试的成功率比立即重试的成功率高,同时可以打散上游重试的时机,减轻服务端压力,避免重试风暴导致服务端出现瞬间流量洪峰。 + +框架支持两种重试退避策略:固定时间间隔退避策略和随机时间间隔退避策略。 + +固定时间间隔退避策略需要设置固定时间间隔(毫秒)、无需重试退避的剩余rpc时间阈值(毫秒,当剩余rpc时间小于阈值,则不进行重试退避)、是否允许在pthread进行重试退避。使用方法如下: + +```c++ +// 给ChannelOptions.retry_policy赋值就行了。 +// 注意:retry_policy必须在Channel使用期间保持有效,Channel也不会删除retry_policy,所以大部分情况下RetryPolicy都应以单例模式创建。 +brpc::ChannelOptions options; +int32_t fixed_backoff_time_ms = 100; // 固定时间间隔(毫秒) +int32_t no_backoff_remaining_rpc_time_ms = 150; // 无需重试退避的剩余rpc时间阈值(毫秒) +bool retry_backoff_in_pthread = false; +static brpc::RpcRetryPolicyWithFixedBackoff g_retry_policy_with_fixed_backoff( + fixed_backoff_time_ms, no_backoff_remaining_rpc_time_ms, retry_backoff_in_pthread); +options.retry_policy = &g_retry_policy_with_fixed_backoff; +... +``` + +随机时间间隔退避策略需要设置最小时间间隔(毫秒)、最大时间间隔(毫秒)、无需重试退避的剩余rpc时间阈值(毫秒,当剩余rpc时间小于阈值,则不进行重试退避)、是否允许在pthread做重试退避。每次策略会随机生成一个在最小时间间隔和最大时间间隔之间的重试退避间隔。使用方法如下: + +```c++ +// 给ChannelOptions.retry_policy赋值就行了。 +// 注意:retry_policy必须在Channel使用期间保持有效,Channel也不会删除retry_policy,所以大部分情况下RetryPolicy都应以单例模式创建。 +brpc::ChannelOptions options; +int32_t min_backoff_time_ms = 100; // 最小时间间隔(毫秒) +int32_t max_backoff_time_ms = 200; // 最大时间间隔(毫秒) +int32_t no_backoff_remaining_rpc_time_ms = 150; // 无需重试退避的剩余rpc时间阈值(毫秒) +bool retry_backoff_in_pthread = false; // 是否允许在pthread做重试退避 +static brpc::RpcRetryPolicyWithJitteredBackoff g_retry_policy_with_jitter_backoff( + min_backoff_time_ms, max_backoff_time_ms, + no_backoff_remaining_rpc_time_ms, retry_backoff_in_pthread); +options.retry_policy = &g_retry_policy_with_jitter_backoff; +... +``` + +用户可以通过继承[brpc::RetryPolicy](https://github.com/apache/brpc/blob/master/src/brpc/retry_policy.h)自定义重试退避策略。比如只需要针对服务端并发数超限的情况进行重试退避,可以这么做: + +```c++ +class MyRetryPolicy : public brpc::RetryPolicy { +public: + bool DoRetry(const brpc::Controller* cntl) const { + // 同《错误值得重试》一节 + } + + int32_t GetBackoffTimeMs(const brpc::Controller* cntl) const { + if (controller->ErrorCode() == brpc::ELIMIT) { + return 100; // 退避100毫秒 + } + return 0; // 返回0表示不进行重试退避。 + } + + bool CanRetryBackoffInPthread() const { + return true; + } +}; +... + +// 给ChannelOptions.retry_policy赋值就行了。 +// 注意:retry_policy必须在Channel使用期间保持有效,Channel也不会删除retry_policy,所以大部分情况下RetryPolicy都应以单例模式创建。 +brpc::ChannelOptions options; +static MyRetryPolicy g_my_retry_policy; +options.retry_policy = &g_my_retry_policy; +... +``` + +如果用户希望使用框架默认的DoRetry,只实现自定义的重试退避策略,则可以继承[brpc::RpcRetryPolicy](https://github.com/apache/brpc/blob/master/src/brpc/retry_policy.h)。 + +一些提示: + +- 当策略返回的重试退避时间大于等于剩余的rpc时间或者等于0,框架不会进行重试退避,而是立即进行重试。 +- [brpc::RpcRetryPolicyWithFixedBackoff](https://github.com/apache/brpc/blob/master/src/brpc/retry_policy.h)(固定时间间隔退策略)和[brpc::RpcRetryPolicyWithJitteredBackoff](https://github.com/apache/brpc/blob/master/src/brpc/retry_policy.h)(随机时间间隔退策略)继承了[brpc::RpcRetryPolicy](https://github.com/apache/brpc/blob/master/src/brpc/retry_policy.h),使用框架默认的DoRetry。 +- 在pthread中进行重试退避(实际上通过bthread_usleep实现)会阻塞pthread,所以默认不会在pthread上进行重试退避。 + +### backup request + +工作机制如下:如果response没有在backup_request_ms内返回,则发送另外一个请求,哪个先回来就取哪个。新请求会被尽量送到不同的server。注意如果backup_request_ms大于超时,则backup request总不会被发送。backup request会消耗一次重试次数。backup request不意味着server端cancel。 + +ChannelOptions.backup_request_ms影响该Channel上所有RPC,单位毫秒,默认值-1(表示不开启)。Controller.set_backup_request_ms()可修改某次RPC的值。 + +用户可以通过继承[brpc::BackupRequestPolicy](https://github.com/apache/brpc/blob/master/src/brpc/backup_request_policy.h)自定义策略(backup_request_ms和熔断backup request)。 比如根据延时调节backup_request_ms或者根据错误率熔断部分backup request。 + +ChannelOptions.backup_request_policy同样影响该Channel上所有RPC。Controller.set_backup_request_policy()可修改某次RPC的策略。backup_request_policy优先级高于backup_request_ms。 + +brpc::BackupRequestPolicy接口如下: + +```c++ +class BackupRequestPolicy { +public: + virtual ~BackupRequestPolicy() = default; + + // Return the time in milliseconds in which another request + // will be sent if RPC does not finish. + virtual int32_t GetBackupRequestMs(const Controller* controller) const = 0; + + // Return true if the backup request should be sent. + virtual bool DoBackup(const Controller* controller) const = 0; + + // Called when a rpc is end, user can collect call information to adjust policy. + virtual void OnRPCEnd(const Controller* controller) = 0; +}; +``` + +### 重试应当保守 + +由于成本的限制,大部分线上server的冗余度是有限的,主要是满足多机房互备的需求。而激进的重试逻辑很容易导致众多client对server集群造成2-3倍的压力,最终使集群雪崩:由于server来不及处理导致队列越积越长,使所有的请求得经过很长的排队才被处理而最终超时,相当于服务停摆。默认的重试是比较安全的: 只要连接不断RPC就不会重试,一般不会产生大量的重试请求。用户可以通过RetryPolicy定制重试策略,但也可能使重试变成一场“风暴”。当你定制RetryPolicy时,你需要仔细考虑client和server的协作关系,并设计对应的异常测试,以确保行为符合预期。 + +## 熔断 + +具体方法见[这里](circuit_breaker.md)。 + +## 协议 + +Channel的默认协议是baidu_std,可通过设置ChannelOptions.protocol换为其他协议,这个字段既接受enum也接受字符串。 + +目前支持的有: + +- PROTOCOL_BAIDU_STD 或 “baidu_std",即[百度标准协议](baidu_std.md),默认为单连接。 +- PROTOCOL_HTTP 或 ”http", http/1.0或http/1.1协议,默认为连接池(Keep-Alive)。 + - 访问普通http服务的方法见[访问http/h2服务](http_client.md) + - 通过http:json或http:proto访问pb服务的方法见[http/h2衍生协议](http_derivatives.md) +- PROTOCOL_H2 或 ”h2", http/2协议,默认是单连接。 + - 访问普通h2服务的方法见[访问http/h2服务](http_client.md)。 + - 通过h2:json或h2:proto访问pb服务的方法见[http/h2衍生协议](http_derivatives.md) +- "h2:grpc", [gRPC](https://grpc.io)的协议,也是h2的衍生协议,默认为单连接,具体见[h2:grpc](http_derivatives.md#h2grpc)。 +- PROTOCOL_THRIFT 或 "thrift",[apache thrift](https://thrift.apache.org)的协议,默认为连接池, 具体方法见[访问thrift](thrift.md)。 +- PROTOCOL_MEMCACHE 或 "memcache",memcached的二进制协议,默认为单连接。具体方法见[访问memcached](memcache_client.md)。 +- PROTOCOL_REDIS 或 "redis",redis 1.2后的协议(也是hiredis支持的协议),默认为单连接。具体方法见[访问Redis](redis_client.md)。 +- PROTOCOL_HULU_PBRPC 或 "hulu_pbrpc",hulu的协议,默认为单连接。 +- PROTOCOL_NOVA_PBRPC 或 ”nova_pbrpc“,网盟的协议,默认为连接池。 +- PROTOCOL_SOFA_PBRPC 或 "sofa_pbrpc",sofa-pbrpc的协议,默认为单连接。 +- PROTOCOL_PUBLIC_PBRPC 或 "public_pbrpc",public_pbrpc的协议,默认为连接池。 +- PROTOCOL_UBRPC_COMPACK 或 "ubrpc_compack",public/ubrpc的协议,使用compack打包,默认为连接池。具体方法见[ubrpc (by protobuf)](ub_client.md)。相关的还有PROTOCOL_UBRPC_MCPACK2或ubrpc_mcpack2,使用mcpack2打包。 +- PROTOCOL_NSHEAD_CLIENT 或 "nshead_client",这是发送baidu-rpc-ub中所有UBXXXRequest需要的协议,默认为连接池。具体方法见[访问UB](ub_client.md)。 +- PROTOCOL_NSHEAD 或 "nshead",这是发送NsheadMessage需要的协议,默认为连接池。具体方法见[nshead+blob](ub_client.md#nshead-blob) 。 +- PROTOCOL_NSHEAD_MCPACK 或 "nshead_mcpack", 顾名思义,格式为nshead + mcpack,使用mcpack2pb适配,默认为连接池。 +- PROTOCOL_ESP 或 "esp",访问使用esp协议的服务,默认为连接池。 + +## 连接方式 + +brpc支持以下连接方式: + +- 短连接:每次RPC前建立连接,结束后关闭连接。由于每次调用得有建立连接的开销,这种方式一般用于偶尔发起的操作,而不是持续发起请求的场景。没有协议默认使用这种连接方式,http/1.0对连接的处理效果类似短链接。 +- 连接池:每次RPC前取用空闲连接,结束后归还,一个连接上最多只有一个请求,一个client对一台server可能有多条连接。http/1.1和各类使用nshead的协议都是这个方式。 +- 单连接:进程内所有client与一台server最多只有一个连接,一个连接上可能同时有多个请求,回复返回顺序和请求顺序不需要一致,这是baidu_std,hulu_pbrpc,sofa_pbrpc协议的默认选项。 + +| | 短连接 | 连接池 | 单连接 | +| ------------------- | ---------------------------------------- | --------------------- | ------------------- | +| 长连接 | 否 | 是 | 是 | +| server端连接数(单client) | qps*latency (原理见[little's law](https://en.wikipedia.org/wiki/Little%27s_law)) | qps*latency | 1 | +| 极限qps | 差,且受限于单机端口数 | 中等 | 高 | +| latency | 1.5RTT(connect) + 1RTT + 处理时间 | 1RTT + 处理时间 | 1RTT + 处理时间 | +| cpu占用 | 高, 每次都要tcp connect | 中等, 每个请求都要一次sys write | 低, 合并写出在大流量时减少cpu占用 | + +框架会为协议选择默认的连接方式,用户**一般不用修改**。若需要,把ChannelOptions.connection_type设为: + +- CONNECTION_TYPE_SINGLE 或 "single" 为单连接 + +- CONNECTION_TYPE_POOLED 或 "pooled" 为连接池, 单个远端对应的连接池最多能容纳的连接数由-max_connection_pool_size控制。注意,此选项不等价于“最大连接数”。需要连接时只要没有闲置的,就会新建;归还时,若池中已有max_connection_pool_size个连接的话,会直接关闭。max_connection_pool_size的取值要符合并发,否则超出的部分会被频繁建立和关闭,效果类似短连接。若max_connection_pool_size为0,就近似于完全的短连接。 + + | Name | Value | Description | Defined At | + | ---------------------------- | ----- | ---------------------------------------- | ------------------- | + | max_connection_pool_size (R) | 100 | Max number of pooled connections to a single endpoint | src/brpc/socket.cpp | + +- CONNECTION_TYPE_SHORT 或 "short" 为短连接 + +- 设置为“”(空字符串)则让框架选择协议对应的默认连接方式。 + +brpc支持[Streaming RPC](streaming_rpc.md),这是一种应用层的连接,用于传递流式数据。 + +## 关闭连接池中的闲置连接 + +当连接池中的某个连接在-idle_timeout_second时间内没有读写,则被视作“闲置”,会被自动关闭。默认值为10秒。此功能只对连接池(pooled)有效。打开-log_idle_connection_close在关闭前会打印一条日志。 + +| Name | Value | Description | Defined At | +| ------------------------- | ----- | ---------------------------------------- | ----------------------- | +| idle_timeout_second | 10 | Pooled connections without data transmission for so many seconds will be closed. No effect for non-positive values | src/brpc/socket_map.cpp | +| log_idle_connection_close | false | Print log when an idle connection is closed | src/brpc/socket.cpp | + +## 延迟关闭连接 + +多个channel可能通过引用计数引用同一个连接,当引用某个连接的最后一个channel析构时,该连接将被关闭。但在一些场景中,channel在使用前才被创建,用完立刻析构,这时其中一些连接就会被无谓地关闭再被打开,效果类似短连接。 + +一个解决办法是用户把所有或常用的channel缓存下来,这样自然能避免channel频繁产生和析构,但目前brpc没有提供这样一个utility,用户自己(正确)实现有一些工作量。 + +另一个解决办法是设置全局选项-defer_close_second + +| Name | Value | Description | Defined At | +| ------------------ | ----- | ---------------------------------------- | ----------------------- | +| defer_close_second | 0 | Defer close of connections for so many seconds even if the connection is not used by anyone. Close immediately for non-positive values | src/brpc/socket_map.cpp | +| defer_close_respect_idle | false | 当 defer_close_second > 0 时,如果连接在最后一个引用释放时已经闲置超过 defer_close_second,则立刻关闭连接(默认关闭以保持兼容) | src/brpc/socket_map.cpp | + +设置后引用计数清0时连接并不会立刻被关闭,而是会等待这么多秒再关闭,如果在这段时间内又有channel引用了这个连接,它会恢复正常被使用的状态。不管channel创建析构有多频率,这个选项使得关闭连接的频率有上限。这个选项的副作用是一些fd不会被及时关闭,如果延时被误设为一个大数值,程序占据的fd个数可能会很大。开启 -defer_close_respect_idle 后,如果连接在最后一个引用释放时已经闲置超过 defer_close_second,则可能会被关闭。 + +## 连接的缓冲区大小 + +-socket_recv_buffer_size设置所有连接的接收缓冲区大小,默认-1(不修改) + +-socket_send_buffer_size设置所有连接的发送缓冲区大小,默认-1(不修改) + +| Name | Value | Description | Defined At | +| ----------------------- | ----- | ---------------------------------------- | ------------------- | +| socket_recv_buffer_size | -1 | Set the recv buffer size of socket if this value is positive | src/brpc/socket.cpp | +| socket_send_buffer_size | -1 | Set send buffer size of sockets if this value is positive | src/brpc/socket.cpp | + +## log_id + +通过set_log_id()可设置64位整型log_id。这个id会和请求一起被送到服务器端,一般会被打在日志里,从而把一次检索经过的所有服务串联起来。字符串格式的需要转化为64位整形才能设入log_id。 + +## 附件 + +baidu_std和hulu_pbrpc协议支持附件,这段数据由用户自定义,不经过protobuf的序列化。站在client的角度,设置在Controller::request_attachment()的附件会被server端收到,response_attachment()则包含了server端送回的附件。附件不受压缩选项影响。 + +在http/h2协议中,附件对应[message body](http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html),比如要POST的数据就设置在request_attachment()中。 + +## 开启SSL + +要开启SSL,首先确保代码依赖了最新的openssl库。如果openssl版本很旧,会有严重的安全漏洞,支持的加密算法也少,违背了开启SSL的初衷。 +然后设置`ChannelOptions.mutable_ssl_options()`,具体选项见[ssl_options.h](https://github.com/apache/brpc/blob/master/src/brpc/ssl_options.h)。ChannelOptions.has_ssl_options()可查询是否设置过ssl_options, ChannelOptions.ssl_options()可访问到设置过的只读ssl_options。 + +```c++ +// 开启客户端SSL并使用默认值。 +options.mutable_ssl_options(); + +// 开启客户端SSL并定制选项。 +options.mutable_ssl_options()->ciphers_name = "..."; +options.mutable_ssl_options()->sni_name = "..."; + +// 设置 ALPN 的协议优先级(默认不启用 ALPN)。 +options.mutable_ssl_options()->alpn_protocols = {"h2", "http/1.1"}; +``` +- 连接单点和集群的Channel均可以开启SSL访问(初始实现曾不支持集群)。 +- 开启后,该Channel上任何协议的请求,都会被SSL加密后发送。如果希望某些请求不加密,需要额外再创建一个Channel。 +- 针对HTTPS做了些易用性优化:Channel.Init能自动识别`https://`前缀并自动开启SSL;开启-http_verbose也会输出证书信息。 + +## 认证 + +client端的认证一般分为2种: + +1. 基于请求的认证:每次请求都会带上认证信息。这种方式比较灵活,认证信息中可以含有本次请求中的字段,但是缺点是每次请求都会需要认证,性能上有所损失 +2. 基于连接的认证:当TCP连接建立后,client发送认证包,认证成功后,后续该连接上的请求不再需要认证。相比前者,这种方式灵活度不高(一般认证包里只能携带本机一些静态信息),但性能较好,一般用于单连接/连接池场景 + +针对第一种认证场景,在实现上非常简单,将认证的格式定义加到请求结构体中,每次当做正常RPC发送出去即可;针对第二种场景,brpc提供了一种机制,只要用户继承实现: + +```c++ +class Authenticator { +public: + virtual ~Authenticator() {} + + // Implement this method to generate credential information + // into `auth_str' which will be sent to `VerifyCredential' + // at server side. This method will be called on client side. + // Returns 0 on success, error code otherwise + virtual int GenerateCredential(std::string* auth_str) const = 0; +}; +``` + +那么当用户并发调用RPC接口用单连接往同一个server发请求时,框架会自动保证:建立TCP连接后,连接上的第一个请求中会带有上述`GenerateCredential`产生的认证包,其余剩下的并发请求不会带有认证信息,依次排在第一个请求之后。整个发送过程依旧是并发的,并不会等第一个请求先返回。若server端认证成功,那么所有请求都能成功返回;若认证失败,一般server端则会关闭连接,这些请求则会收到相应错误。 + +目前自带协议中支持客户端认证的有:[baidu_std](baidu_std.md)(默认协议), HTTP, hulu_pbrpc, ESP。对于自定义协议,一般可以在组装请求阶段,调用Authenticator接口生成认证串,来支持客户端认证。 + +## 重置 + +调用Reset方法可让Controller回到刚创建时的状态。 + +别在RPC结束前重置Controller,行为是未定义的。 + +## 压缩 + +set_request_compress_type()设置request的压缩方式,默认不压缩。 + +注意:附件不会被压缩。 + +http/h2 body的压缩方法见[client压缩request body](http_client.md#压缩request-body)。 + +支持的压缩方法有: + +- brpc::CompressTypeSnappy : [snappy压缩](http://google.github.io/snappy/),压缩和解压显著快于其他压缩方法,但压缩率最低。 +- brpc::CompressTypeGzip : [gzip压缩](http://en.wikipedia.org/wiki/Gzip),显著慢于snappy,但压缩率高 +- brpc::CompressTypeZlib : [zlib压缩](http://en.wikipedia.org/wiki/Zlib),比gzip快10%~20%,压缩率略好于gzip,但速度仍明显慢于snappy。 + +下表是多种压缩算法应对重复率很高的数据时的性能,仅供参考。 + +| Compress method | Compress size(B) | Compress time(us) | Decompress time(us) | Compress throughput(MB/s) | Decompress throughput(MB/s) | Compress ratio | +| --------------- | ---------------- | ----------------- | ------------------- | ------------------------- | --------------------------- | -------------- | +| Snappy | 128 | 0.753114 | 0.890815 | 162.0875 | 137.0322 | 37.50% | +| Gzip | 10.85185 | 1.849199 | 11.2488 | 66.01252 | 47.66% | | +| Zlib | 10.71955 | 1.66522 | 11.38763 | 73.30581 | 38.28% | | +| Snappy | 1024 | 1.404812 | 1.374915 | 695.1555 | 710.2713 | 8.79% | +| Gzip | 16.97748 | 3.950946 | 57.52106 | 247.1718 | 6.64% | | +| Zlib | 15.98913 | 3.06195 | 61.07665 | 318.9348 | 5.47% | | +| Snappy | 16384 | 8.822967 | 9.865008 | 1770.946 | 1583.881 | 4.96% | +| Gzip | 160.8642 | 43.85911 | 97.13162 | 356.2544 | 0.78% | | +| Zlib | 147.6828 | 29.06039 | 105.8011 | 537.6734 | 0.71% | | +| Snappy | 32768 | 16.16362 | 19.43596 | 1933.354 | 1607.844 | 4.82% | +| Gzip | 229.7803 | 82.71903 | 135.9995 | 377.7849 | 0.54% | | +| Zlib | 240.7464 | 54.44099 | 129.8046 | 574.0161 | 0.50% | | + +下表是多种压缩算法应对重复率很低的数据时的性能,仅供参考。 + +| Compress method | Compress size(B) | Compress time(us) | Decompress time(us) | Compress throughput(MB/s) | Decompress throughput(MB/s) | Compress ratio | +| --------------- | ---------------- | ----------------- | ------------------- | ------------------------- | --------------------------- | -------------- | +| Snappy | 128 | 0.866002 | 0.718052 | 140.9584 | 170.0021 | 105.47% | +| Gzip | 15.89855 | 4.936242 | 7.678077 | 24.7294 | 116.41% | | +| Zlib | 15.88757 | 4.793953 | 7.683384 | 25.46339 | 107.03% | | +| Snappy | 1024 | 2.087972 | 1.06572 | 467.7087 | 916.3403 | 100.78% | +| Gzip | 32.54279 | 12.27744 | 30.00857 | 79.5412 | 79.79% | | +| Zlib | 31.51397 | 11.2374 | 30.98824 | 86.90288 | 78.61% | | +| Snappy | 16384 | 12.598 | 6.306592 | 1240.276 | 2477.566 | 100.06% | +| Gzip | 537.1803 | 129.7558 | 29.08707 | 120.4185 | 75.32% | | +| Zlib | 519.5705 | 115.1463 | 30.07291 | 135.697 | 75.24% | | +| Snappy | 32768 | 22.68531 | 12.39793 | 1377.543 | 2520.582 | 100.03% | +| Gzip | 1403.974 | 258.9239 | 22.25825 | 120.6919 | 75.25% | | +| Zlib | 1370.201 | 230.3683 | 22.80687 | 135.6524 | 75.21% | | + +# FAQ + +### Q: brpc能用unix domain socket吗 + +支持,参考 [EndPoint](endpoint.md). + +### Q: Fail to connect to xx.xx.xx.xx:xxxx, Connection refused + +一般是对端server没打开端口(很可能挂了)。 + +### Q: 经常遇到至另一个机房的Connection timedout + +![img](../images/connection_timedout.png) + +这个就是连接超时了,调大连接和RPC超时: + +```c++ +struct ChannelOptions { + ... + // Issue error when a connection is not established after so many + // milliseconds. -1 means wait indefinitely. + // Default: 200 (milliseconds) + // Maximum: 0x7fffffff (roughly 30 days) + int32_t connect_timeout_ms; + + // Max duration of RPC over this Channel. -1 means wait indefinitely. + // Overridable by Controller.set_timeout_ms(). + // Default: 500 (milliseconds) + // Maximum: 0x7fffffff (roughly 30 days) + int32_t timeout_ms; + ... +}; +``` + +注意: 连接超时不是RPC超时,RPC超时打印的日志是"Reached timeout=..."。 + +### Q: 为什么同步方式是好的,异步就crash了 + +重点检查Controller,Response和done的生命周期。在异步访问中,RPC调用结束并不意味着RPC整个过程结束,而是在进入done->Run()时才会结束。所以这些对象不应在调用RPC后就释放,而是要在done->Run()里释放。你一般不能把这些对象分配在栈上,而应该分配在堆上。详见[异步访问](client.md#异步访问)。 + +### Q: 怎么确保请求只被处理一次 + +这不是RPC层面的事情。当response返回且成功时,我们确认这个过程一定成功了。当response返回且失败时,我们确认这个过程一定失败了。但当response没有返回时,它可能失败,也可能成功。如果我们选择重试,那一个成功的过程也可能会被再执行一次。一般来说带副作用的RPC服务都应当考虑[幂等](http://en.wikipedia.org/wiki/Idempotence)问题,否则重试可能会导致多次叠加副作用而产生意向不到的结果。只有读的检索服务大都没有副作用而天然幂等,无需特殊处理。而带写的存储服务则要在设计时就加入版本号或序列号之类的机制以拒绝已经发生的过程,保证幂等。 + +### Q: Invalid address=`bns://group.user-persona.dumi.nj03' +``` +FATAL 04-07 20:00:03 7778 src/brpc/channel.cpp:123] Invalid address=`bns://group.user-persona.dumi.nj03'. You should use Init(naming_service_name, load_balancer_name, options) to access multiple servers. +``` +访问命名服务要使用三个参数的Init,其中第二个参数是load_balancer_name,而这里用的是两个参数的Init,框架认为是访问单点,就会报这个错。 + +### Q: 两端都用protobuf,为什么不能互相访问 + +**协议 !=protobuf**。protobuf负责一个包的序列化,协议中的一个消息可能会包含多个protobuf包,以及额外的长度、校验码、magic number等等。打包格式相同不意味着协议可以互通。在brpc中写一份代码就能服务多协议的能力是通过把不同协议的数据转化为统一的编程接口完成的,而不是在protobuf层面。 + +### Q: 为什么C++ client/server 能够互相通信, 和其他语言的client/server 通信会报序列化失败的错误 + +检查一下C++ 版本是否开启了压缩 (Controller::set_compress_type), 目前其他语言的rpc框架还没有实现压缩,互相返回会出现问题。 + +# 附:Client端基本流程 + +![img](../images/client_side.png) + +主要步骤: + +1. 创建一个[bthread_id](https://github.com/apache/brpc/blob/master/src/bthread/id.h)作为本次RPC的correlation_id。 +2. 根据Channel的创建方式,从进程级的[SocketMap](https://github.com/apache/brpc/blob/master/src/brpc/socket_map.h)中或从[LoadBalancer](https://github.com/apache/brpc/blob/master/src/brpc/load_balancer.h)中选择一台下游server作为本次RPC发送的目的地。 +3. 根据连接方式(单连接、连接池、短连接),选择一个[Socket](https://github.com/apache/brpc/blob/master/src/brpc/socket.h)。 +4. 如果开启验证且当前Socket没有被验证过时,第一个请求进入验证分支,其余请求会阻塞直到第一个包含认证信息的请求写入Socket。server端只对第一个请求进行验证。 +5. 根据Channel的协议,选择对应的序列化函数把request序列化至[IOBuf](https://github.com/apache/brpc/blob/master/src/butil/iobuf.h)。 +6. 如果配置了超时,设置定时器。从这个点开始要避免使用Controller对象,因为在设定定时器后随时可能触发超时->调用到用户的超时回调->用户在回调中析构Controller。 +7. 发送准备阶段结束,若上述任何步骤出错,会调用Channel::HandleSendFailed。 +8. 将之前序列化好的IOBuf写出到Socket上,同时传入回调Channel::HandleSocketFailed,当连接断开、写失败等错误发生时会调用此回调。 +9. 如果是同步发送,Join correlation_id;否则至此CallMethod结束。 +10. 网络上发消息+收消息。 +11. 收到response后,提取出其中的correlation_id,在O(1)时间内找到对应的Controller。这个过程中不需要查找全局哈希表,有良好的多核扩展性。 +12. 根据协议格式反序列化response。 +13. 调用Controller::OnRPCReturned,可能会根据错误码判断是否需要重试,或让RPC结束。如果是异步发送,调用用户回调。最后摧毁correlation_id唤醒Join着的线程。 diff --git a/docs/cn/combo_channel.md b/docs/cn/combo_channel.md new file mode 100644 index 0000000..fba4f6b --- /dev/null +++ b/docs/cn/combo_channel.md @@ -0,0 +1,509 @@ +[English version](../en/combo_channel.md) + +随着服务规模的增大,对下游的访问流程会越来越复杂,其中往往包含多个同时发起的RPC或有复杂的层次结构。但这类代码的多线程陷阱很多,用户可能写出了bug也不自知,复现和调试也比较困难。而且实现要么只能支持同步的情况,要么得为异步重写一套。以"在多个异步RPC完成后运行一些代码"为例,它的同步实现一般是异步地发起多个RPC,然后逐个等待各自完成;它的异步实现一般是用一个带计数器的回调,每当一个RPC完成时计数器减一,直到0时调用回调。可以看到它的缺点: + +- 同步和异步代码不一致。用户无法轻易地从一个模式转为另一种模式。从设计的角度,不一致暗示了没有抓住本质。 +- 往往不能被取消。正确及时地取消一个操作不是一件易事,何况是组合访问。但取消对于终结无意义的等待是很必要的。 +- 不能继续组合。比如你很难把一个上述实现变成“更大"的访问模式的一部分。换个场景还得重写一套。 + +我们需要更好的抽象。如果我们能以不同的方式把一些Channel组合为更大的Channel,并把不同的访问模式置入其中,那么用户可以便用统一接口完成同步、异步、取消等操作。这种channel在brpc中被称为组合channel。 + +# ParallelChannel + +ParallelChannel (有时被称为“pchan”)同时访问其包含的sub channel,并合并它们的结果。用户可通过CallMapper修改请求,通过ResponseMerger合并结果。ParallelChannel看起来就像是一个Channel: + +- 支持同步和异步访问。 +- 发起异步操作后可以立刻删除。 +- 可以取消。 +- 支持超时。 + +示例代码见[example/parallel_echo_c++](https://github.com/apache/brpc/tree/master/example/parallel_echo_c++/)。 + +任何brpc::ChannelBase的子类都可以加入ParallelChannel,包括ParallelChannel和其他组合Channel。 + +用户可以设置ParallelChannelOptions.fail_limit来控制访问的最大失败次数,当失败的访问达到这个数目时,RPC会立刻结束而不等待超时。 + +用户可以设置ParallelChannelOptions.success_limit来控制访问的最大成功次数,当成功的访问达到这个数目时,RPC会立刻结束。ParallelChannelOptions.fail_limit的优先级高于ParallelChannelOptions.success_limit,只有未设置fail_limit时,success_limit才会生效。 + +一个sub channel可多次加入同一个ParallelChannel。当你需要对同一个服务发起多次异步访问并等待它们完成的话,这很有用。 + +ParallelChannel的内部结构大致如下: + +![img](../images/pchan.png) + +## 插入sub channel + +可通过如下接口把sub channel插入ParallelChannel: + +```c++ +int AddChannel(brpc::ChannelBase* sub_channel, + ChannelOwnership ownership, + CallMapper* call_mapper, + ResponseMerger* response_merger); +``` + +当ownership为brpc::OWNS_CHANNEL时,sub_channel会在ParallelChannel析构时被删除。一个sub channel可能会多次加入一个ParallelChannel,如果其中一个指明了ownership为brpc::OWNS_CHANNEL,那个sub channel会在ParallelChannel析构时被最多删除一次。 + +访问ParallelChannel时调用AddChannel是**线程不安全**的。 + +## CallMapper + +用于把对ParallelChannel的调用转化为对sub channel的调用。如果call_mapper是NULL,sub channel的请求就是ParallelChannel的请求,而response则New()自ParallelChannel的response。如果call_mapper不为NULL,则会在ParallelChannel析构时被删除。call_mapper内含引用计数,一个call_mapper可与多个sub channel关联。 + +```c++ +class CallMapper { +public: + virtual ~CallMapper(); + + virtual SubCall Map(int channel_index/*starting from 0*/, + int channel_count, + const google::protobuf::MethodDescriptor* method, + const google::protobuf::Message* request, + google::protobuf::Message* response) = 0; + + virtual void MapController(int channel_index/*starting from 0*/, int channel_count, + const Controller* main_cntl, Controller* sub_cntl); +}; +``` +### Map + +channel_index:该sub channel在ParallelChannel中的位置,从0开始计数。 + +channel_count:ParallelChannel中sub channel的数量。 + +method/request/response:ParallelChannel.CallMethod()的参数。 + +返回的SubCall被用于访问对应sub channel,SubCall有两个特殊值: + +- 返回SubCall::Bad()则对ParallelChannel的该次访问立刻失败,Controller.ErrorCode()为EREQUEST。 +- 返回SubCall::Skip()则跳过对该sub channel的访问,如果所有的sub channel都被跳过了,该次访问立刻失败,Controller.ErrorCode()为ECANCELED。 + +常见的Map()实现有: + +- 广播request。这也是call_mapper为NULL时的行为: +```c++ + class Broadcaster : public CallMapper { + public: + SubCall Map(int channel_index/*starting from 0*/, + const google::protobuf::MethodDescriptor* method, + const google::protobuf::Message* request, + google::protobuf::Message* response) { + // method/request和pchan保持一致. + // response是new出来的,最后的flag告诉pchan在RPC结束后删除Response。 + return SubCall(method, request, response->New(), DELETE_RESPONSE); + } + }; +``` +- 修改request中的字段后再发。 +```c++ + class ModifyRequest : public CallMapper { + public: + SubCall Map(int channel_index/*starting from 0*/, + const google::protobuf::MethodDescriptor* method, + const google::protobuf::Message* request, + google::protobuf::Message* response) { + FooRequest* copied_req = brpc::Clone(request); + copied_req->set_xxx(...); + // 拷贝并修改request,最后的flag告诉pchan在RPC结束后删除Request和Response。 + return SubCall(method, copied_req, response->New(), DELETE_REQUEST | DELETE_RESPONSE); + } + }; +``` +- request和response已经包含了sub request/response,直接取出来。 +```c++ + class UseFieldAsSubRequest : public CallMapper { + public: + SubCall Map(int channel_index/*starting from 0*/, + const google::protobuf::MethodDescriptor* method, + const google::protobuf::Message* request, + google::protobuf::Message* response) { + if (channel_index >= request->sub_request_size()) { + // sub_request不够,说明外面准备数据的地方和pchan中sub channel的个数不符. + // 返回Bad()让该次访问立刻失败 + return SubCall::Bad(); + } + // 取出对应的sub request,增加一个sub response,最后的flag为0告诉pchan什么都不用删 + return SubCall(sub_method, request->sub_request(channel_index), response->add_sub_response(), 0); + } + }; +``` + +### MapController + +channel_index:该sub channel在ParallelChannel中的位置,从0开始计数。 + +channel_count:ParallelChannel中sub channel的数量。 + +main_cntl:ParallelChannel.CallMethod()的参数。 + +sub_cntl:sub channel的请求对应的controller。默认实现:拷贝main_cntl的http_request和request_attachment到sub_cntl中。 + +注意:修改ClientSettings相关配置(如超时、重试等)是无效的,因为所有sub_cntl都是使用main_cntl的ClientSettings配置。 + +## ResponseMerger + +response_merger把sub channel的response合并入总的response,其为NULL时,则使用response->MergeFrom(*sub_response),MergeFrom的行为可概括为“除了合并repeated字段,其余都是覆盖”。如果你需要更复杂的行为,则需实现ResponseMerger。response_merger是一个个执行的,所以你并不需要考虑多个Merge同时运行的情况。response_merger在ParallelChannel析构时被删除。response_merger内含引用计数,一个response_merger可与多个sub channel关联。 + +Result的取值有: +- MERGED: 成功合并。 +- FAIL: sub_response没有合并成功,会被记作一次失败。比如有10个sub channels且fail_limit为4,只要有4个合并结果返回了FAIL,这次RPC就会达到fail_limit并立刻结束。 +- FAIL_ALL: 使本次RPC直接结束。 + + +## 获得访问sub channel时的controller + +有时访问者需要了解访问sub channel时的细节,通过Controller.sub(i)可获得访问sub channel的controller. + +```c++ +// Get the controllers for accessing sub channels in combo channels. +// Ordinary channel: +// sub_count() is 0 and sub() is always NULL. +// ParallelChannel/PartitionChannel: +// sub_count() is #sub-channels and sub(i) is the controller for +// accessing i-th sub channel inside ParallelChannel, if i is outside +// [0, sub_count() - 1], sub(i) is NULL. +// NOTE: You must test sub() against NULL, ALWAYS. Even if i is inside +// range, sub(i) can still be NULL: +// * the rpc call may fail and terminate before accessing the sub channel +// * the sub channel was skipped +// SelectiveChannel/DynamicPartitionChannel: +// sub_count() is always 1 and sub(0) is the controller of successful +// or last call to sub channels. +int sub_count() const; +const Controller* sub(int index) const; +``` + +# SelectiveChannel + +[SelectiveChannel](https://github.com/apache/brpc/blob/master/src/brpc/selective_channel.h) (有时被称为“schan”)按负载均衡算法访问其包含的Channel,相比普通Channel它更加高层:把流量分给sub channel,而不是具体的Server。SelectiveChannel主要用来支持机器组之间的负载均衡,它具备Channel的主要属性: + +- 支持同步和异步访问。 +- 发起异步操作后可以立刻删除。 +- 可以取消。 +- 支持超时。 + +示例代码见[example/selective_echo_c++](https://github.com/apache/brpc/tree/master/example/selective_echo_c++/)。 + +任何brpc::ChannelBase的子类都可加入SelectiveChannel,包括SelectiveChannel和其他组合Channel。 + +SelectiveChannel的重试独立于其中的sub channel,当SelectiveChannel访问某个sub channel失败后(本身可能重试),它会重试另外一个sub channel。 + +目前SelectiveChannel要求**request必须在RPC结束前有效**,其他channel没有这个要求。如果你使用SelectiveChannel发起异步操作,确保request在done中才被删除。 + +## 使用SelectiveChannel + +SelectiveChannel的初始化和普通Channel基本一样,但Init不需要指定命名服务,因为SelectiveChannel通过AddChannel动态添加sub channel,而普通Channel通过命名服务动态管理server。 + +```c++ +#include +... +brpc::SelectiveChannel schan; +brpc::ChannelOptions schan_options; +schan_options.timeout_ms = ...; +schan_options.backup_request_ms = ...; +schan_options.max_retry = ...; +if (schan.Init(load_balancer, &schan_options) != 0) { + LOG(ERROR) << "Fail to init SelectiveChannel"; + return -1; +} +``` + +初始化完毕后通过AddChannel加入sub channel。 + +```c++ +if (schan.AddChannel(sub_channel, NULL/*ChannelHandle*/) != 0) { // 第二个参数ChannelHandle用于删除sub channel,不用删除可填NULL + LOG(ERROR) << "Fail to add sub_channel"; + return -1; +} +``` + +注意: + +- 和ParallelChannel不同,SelectiveChannel的AddChannel可在任意时刻调用,即使该SelectiveChannel正在被访问(下一次访问时生效) +- SelectiveChannel总是own sub channel,这和ParallelChannel可选择ownership是不同的。 +- 如果AddChannel第二个参数不为空,会填入一个类型为brpc::SelectiveChannel::ChannelHandle的值,这个handle可作为RemoveAndDestroyChannel的参数来动态删除一个channel。 +- SelectiveChannel会用自身的超时覆盖sub channel初始化时指定的超时。比如某个sub channel的超时为100ms,SelectiveChannel的超时为500ms,实际访问时的超时是500ms。 + +访问SelectiveChannel的方式和普通Channel是一样的。 + +## 例子: 往多个命名服务分流 + +一些场景中我们需要向多个命名服务下的机器分流,原因可能有: + +- 完成同一个检索功能的机器被挂载到了不同的命名服务下。 +- 机器被拆成了多个组,流量先分流给一个组,再分流到组内机器。组间的分流方式和组内有所不同。 + +这都可以通过SelectiveChannel完成。 + +下面的代码创建了一个SelectiveChannel,并插入三个访问不同bns的普通Channel。 + +```c++ +brpc::SelectiveChannel channel; +brpc::ChannelOptions schan_options; +schan_options.timeout_ms = FLAGS_timeout_ms; +schan_options.max_retry = FLAGS_max_retry; +if (channel.Init("c_murmurhash", &schan_options) != 0) { + LOG(ERROR) << "Fail to init SelectiveChannel"; + return -1; +} + +for (int i = 0; i < 3; ++i) { + brpc::Channel* sub_channel = new brpc::Channel; + if (sub_channel->Init(ns_node_name[i], "rr", NULL) != 0) { + LOG(ERROR) << "Fail to init sub channel " << i; + return -1; + } + if (channel.AddChannel(sub_channel, NULL/*handle for removal*/) != 0) { + LOG(ERROR) << "Fail to add sub_channel to channel"; + return -1; + } +} +... +XXXService_Stub stub(&channel); +stub.FooMethod(&cntl, &request, &response, NULL); +... +``` + +# PartitionChannel + +[PartitionChannel](https://github.com/apache/brpc/blob/master/src/brpc/partition_channel.h)是特殊的ParallelChannel,它会根据命名服务中的tag自动建立对应分库的sub channel。这样用户就可以把所有的分库机器挂在一个命名服务内,通过tag来指定哪台机器对应哪个分库。示例代码见[example/partition_echo_c++](https://github.com/apache/brpc/tree/master/example/partition_echo_c++/)。 + +ParititonChannel只能处理一种分库方法,当用户需要多种分库方法共存,或从一个分库方法平滑地切换为另一种分库方法时,可以使用DynamicPartitionChannel,它会根据不同的分库方式动态地建立对应的sub PartitionChannel,并根据容量把请求分配给不同的分库。示例代码见[example/dynamic_partition_echo_c++](https://github.com/apache/brpc/tree/master/example/dynamic_partition_echo_c++/)。 + +如果分库在不同的命名服务内,那么用户得自行用ParallelChannel组装,即每个sub channel对应一个分库(使用不同的命名服务)。ParellelChannel的使用方法见[上面](#ParallelChannel)。 + +## 使用PartitionChannel + +首先定制PartitionParser。这个例子中tag的形式是N/M,N代表分库的index,M是分库的个数。比如0/3代表一共3个分库,这是第一个。 + +```c++ +#include +... +class MyPartitionParser : public brpc::PartitionParser { +public: + bool ParseFromTag(const std::string& tag, brpc::Partition* out) { + // "N/M" : #N partition of M partitions. + size_t pos = tag.find_first_of('/'); + if (pos == std::string::npos) { + LOG(ERROR) << "Invalid tag=" << tag; + return false; + } + char* endptr = NULL; + out->index = strtol(tag.c_str(), &endptr, 10); + if (endptr != tag.data() + pos) { + LOG(ERROR) << "Invalid index=" << butil::StringPiece(tag.data(), pos); + return false; + } + out->num_partition_kinds = strtol(tag.c_str() + pos + 1, &endptr, 10); + if (endptr != tag.c_str() + tag.size()) { + LOG(ERROR) << "Invalid num=" << tag.data() + pos + 1; + return false; + } + return true; + } +}; +``` + +然后初始化PartitionChannel。 + +```c++ +#include +... +brpc::PartitionChannel channel; + +brpc::PartitionChannelOptions options; +options.protocol = ...; // PartitionChannelOptions继承了ChannelOptions,后者有的前者也有 +options.timeout_ms = ...; // 同上 +options.fail_limit = 1; // PartitionChannel自己的选项,意思同ParalellChannel中的fail_limit。这里为1的意思是只要有1个分库访问失败,这次RPC就失败了。 + +if (channel.Init(num_partition_kinds, new MyPartitionParser(), + server_address, load_balancer, &options) != 0) { + LOG(ERROR) << "Fail to init PartitionChannel"; + return -1; +} +// 访问方法和普通Channel是一样的 +``` + +## 使用DynamicPartitionChannel + +DynamicPartitionChannel的使用方法和PartitionChannel基本上是一样的,先定制PartitionParser再初始化,但Init时不需要num_partition_kinds,因为DynamicPartitionChannel会为不同的分库方法动态建立不同的sub PartitionChannel。 + +下面演示一下使用DynamicPartitionChannel平滑地从3库变成4库。 + +首先分别在8004, 8005, 8006端口启动三个server。 + +``` +$ ./echo_server -server_num 3 +TRACE: 09-06 10:40:39: * 0 server.cpp:159] EchoServer is serving on port=8004 +TRACE: 09-06 10:40:39: * 0 server.cpp:159] EchoServer is serving on port=8005 +TRACE: 09-06 10:40:39: * 0 server.cpp:159] EchoServer is serving on port=8006 +TRACE: 09-06 10:40:40: * 0 server.cpp:192] S[0]=0 S[1]=0 S[2]=0 [total=0] +TRACE: 09-06 10:40:41: * 0 server.cpp:192] S[0]=0 S[1]=0 S[2]=0 [total=0] +TRACE: 09-06 10:40:42: * 0 server.cpp:192] S[0]=0 S[1]=0 S[2]=0 [total=0] +``` + +启动后每个Server每秒会打印上一秒收到的流量,目前都是0。 + +在本地启动使用DynamicPartitionChannel的Client,初始化代码如下: + +```c++ + ... + brpc::DynamicPartitionChannel channel; + brpc::PartitionChannelOptions options; + // 访问任何分库失败都认为RPC失败。调大这个数值可以使访问更宽松,比如等于2的话表示至少两个分库失败才算失败。 + options.fail_limit = 1; + if (channel.Init(new MyPartitionParser(), "file://server_list", "rr", &options) != 0) { + LOG(ERROR) << "Fail to init channel"; + return -1; + } + ... +``` + +命名服务"file://server_list"的内容是: +``` +0.0.0.0:8004 0/3 # 表示3分库中的第一个分库,其他依次类推 +0.0.0.0:8004 1/3 +0.0.0.0:8004 2/3 +``` + +3分库方案的3个库都在8004端口对应的server上。启动Client后Client发现了8004,并向其发送流量。 + +``` +$ ./echo_client +TRACE: 09-06 10:51:10: * 0 src/brpc/policy/file_naming_service.cpp:83] Got 3 unique addresses from `server_list' +TRACE: 09-06 10:51:10: * 0 src/brpc/socket.cpp:779] Connected to 0.0.0.0:8004 via fd=3 SocketId=0 self_port=46544 +TRACE: 09-06 10:51:11: * 0 client.cpp:226] Sending EchoRequest at qps=132472 latency=371 +TRACE: 09-06 10:51:12: * 0 client.cpp:226] Sending EchoRequest at qps=132658 latency=370 +TRACE: 09-06 10:51:13: * 0 client.cpp:226] Sending EchoRequest at qps=133208 latency=369 +``` + +同时Server端收到了3倍的流量:因为访问一次Client端要访问三次8004,分别对应每个分库。 + +``` +TRACE: 09-06 10:51:11: * 0 server.cpp:192] S[0]=398866 S[1]=0 S[2]=0 [total=398866] +TRACE: 09-06 10:51:12: * 0 server.cpp:192] S[0]=398117 S[1]=0 S[2]=0 [total=398117] +TRACE: 09-06 10:51:13: * 0 server.cpp:192] S[0]=398873 S[1]=0 S[2]=0 [total=398873] +``` + +开始修改分库,在server_list中加入4分库的8005: + +``` +0.0.0.0:8004 0/3 +0.0.0.0:8004 1/3 +0.0.0.0:8004 2/3 + +0.0.0.0:8005 0/4 +0.0.0.0:8005 1/4 +0.0.0.0:8005 2/4 +0.0.0.0:8005 3/4 +``` + +观察Client和Server的输出变化。Client端发现了server_list的变化并重新载入,但qps并没有什么变化。 + +``` +TRACE: 09-06 10:57:10: * 0 src/brpc/policy/file_naming_service.cpp:83] Got 7 unique addresses from `server_list' +TRACE: 09-06 10:57:10: * 0 src/brpc/socket.cpp:779] Connected to 0.0.0.0:8005 via fd=7 SocketId=768 self_port=39171 +TRACE: 09-06 10:57:11: * 0 client.cpp:226] Sending EchoRequest at qps=135346 latency=363 +TRACE: 09-06 10:57:12: * 0 client.cpp:226] Sending EchoRequest at qps=134201 latency=366 +TRACE: 09-06 10:57:13: * 0 client.cpp:226] Sending EchoRequest at qps=137627 latency=356 +TRACE: 09-06 10:57:14: * 0 client.cpp:226] Sending EchoRequest at qps=136775 latency=359 +TRACE: 09-06 10:57:15: * 0 client.cpp:226] Sending EchoRequest at qps=139043 latency=353 +``` + +server端的变化比较大。8005收到了流量,并且和8004的流量比例关系约为4:3。 + +``` +TRACE: 09-06 10:57:09: * 0 server.cpp:192] S[0]=398597 S[1]=0 S[2]=0 [total=398597] +TRACE: 09-06 10:57:10: * 0 server.cpp:192] S[0]=392839 S[1]=0 S[2]=0 [total=392839] +TRACE: 09-06 10:57:11: * 0 server.cpp:192] S[0]=334704 S[1]=83219 S[2]=0 [total=417923] +TRACE: 09-06 10:57:12: * 0 server.cpp:192] S[0]=206215 S[1]=273873 S[2]=0 [total=480088] +TRACE: 09-06 10:57:13: * 0 server.cpp:192] S[0]=204520 S[1]=270483 S[2]=0 [total=475003] +TRACE: 09-06 10:57:14: * 0 server.cpp:192] S[0]=207055 S[1]=273725 S[2]=0 [total=480780] +TRACE: 09-06 10:57:15: * 0 server.cpp:192] S[0]=208453 S[1]=276803 S[2]=0 [total=485256] +``` + +一次RPC要访问三次8004或四次8005,8004和8005流量比是3:4,说明Client以1:1的比例访问了3分库和4分库。这个比例关系取决于其容量。容量的计算是递归的: + +- 普通Channel的容量等于它其中所有server的容量之和。如果命名服务没有配置权值,单个server的容量为1。 +- ParallelChannel或PartitionChannel的容量等于它其中Sub Channel容量的最小值。 +- SelectiveChannel的容量等于它其中Sub Channel的容量之和。 +- DynamicPartitionChannel的容量等于它其中Sub PartitionChannel的容量之和。 + +在这儿的场景中,3分库和4分库的容量都是1,因为所有的3库都在8004一台server上,所有的4库都在8005一台server上。 + +在4分库方案加入加入8006端口的server: + +``` +0.0.0.0:8004 0/3 +0.0.0.0:8004 1/3 +0.0.0.0:8004 2/3 + +0.0.0.0:8005 0/4 +0.0.0.0:8005 1/4 +0.0.0.0:8005 2/4 +0.0.0.0:8005 3/4 + +0.0.0.0:8006 0/4 +0.0.0.0:8006 1/4 +0.0.0.0:8006 2/4 +0.0.0.0:8006 3/4 +``` + +Client的变化仍旧不大: + +``` +TRACE: 09-06 11:11:51: * 0 src/brpc/policy/file_naming_service.cpp:83] Got 11 unique addresses from `server_list' +TRACE: 09-06 11:11:51: * 0 src/brpc/socket.cpp:779] Connected to 0.0.0.0:8006 via fd=8 SocketId=1280 self_port=40759 +TRACE: 09-06 11:11:51: * 0 client.cpp:226] Sending EchoRequest at qps=131799 latency=372 +TRACE: 09-06 11:11:52: * 0 client.cpp:226] Sending EchoRequest at qps=136217 latency=361 +TRACE: 09-06 11:11:53: * 0 client.cpp:226] Sending EchoRequest at qps=133531 latency=368 +TRACE: 09-06 11:11:54: * 0 client.cpp:226] Sending EchoRequest at qps=136072 latency=361 +``` + +Server端可以看到8006收到了流量。三台server的流量比例约为3:4:4。这是因为3分库的容量仍为1,而4分库由于8006的加入变成了2。3分库和4分库的流量比例是3:8。4分库中的每个分库在8005和8006上都有实例,同一个分库的不同实例使用round robin分流,所以8005和8006平摊了流量。最后的效果就是3:4:4。 + +``` +TRACE: 09-06 11:11:51: * 0 server.cpp:192] S[0]=199625 S[1]=263226 S[2]=0 [total=462851] +TRACE: 09-06 11:11:52: * 0 server.cpp:192] S[0]=143248 S[1]=190717 S[2]=159756 [total=493721] +TRACE: 09-06 11:11:53: * 0 server.cpp:192] S[0]=133003 S[1]=178328 S[2]=178325 [total=489656] +TRACE: 09-06 11:11:54: * 0 server.cpp:192] S[0]=135534 S[1]=180386 S[2]=180333 [total=496253] +``` + +尝试去掉3分库中的一个分库: (你可以在file://server_list中使用#注释一行) + +``` + 0.0.0.0:8004 0/3 + 0.0.0.0:8004 1/3 +#0.0.0.0:8004 2/3 + + 0.0.0.0:8005 0/4 + 0.0.0.0:8005 1/4 + 0.0.0.0:8005 2/4 + 0.0.0.0:8005 3/4 + + 0.0.0.0:8006 0/4 + 0.0.0.0:8006 1/4 + 0.0.0.0:8006 2/4 + 0.0.0.0:8006 3/4 +``` + +Client端发现了这点。 + +``` +TRACE: 09-06 11:17:47: * 0 src/brpc/policy/file_naming_service.cpp:83] Got 10 unique addresses from `server_list' +TRACE: 09-06 11:17:47: * 0 client.cpp:226] Sending EchoRequest at qps=131653 latency=373 +TRACE: 09-06 11:17:48: * 0 client.cpp:226] Sending EchoRequest at qps=120560 latency=407 +TRACE: 09-06 11:17:49: * 0 client.cpp:226] Sending EchoRequest at qps=124100 latency=395 +TRACE: 09-06 11:17:50: * 0 client.cpp:226] Sending EchoRequest at qps=123743 latency=397 +``` + +Server端更明显,8004很快没有了流量。这是因为去掉的分库已经是3分库中最后的2/3分库,去掉后3分库的容量变为了0,导致8004分不到任何流量了。 + +``` +TRACE: 09-06 11:17:47: * 0 server.cpp:192] S[0]=130864 S[1]=174499 S[2]=174548 [total=479911] +TRACE: 09-06 11:17:48: * 0 server.cpp:192] S[0]=20063 S[1]=230027 S[2]=230098 [total=480188] +TRACE: 09-06 11:17:49: * 0 server.cpp:192] S[0]=0 S[1]=245961 S[2]=245888 [total=491849] +TRACE: 09-06 11:17:50: * 0 server.cpp:192] S[0]=0 S[1]=250198 S[2]=250150 [total=500348] +``` + +在真实的线上环境中,我们会逐渐地增加4分库的server,同时下掉3分库中的server。DynamicParititonChannel会按照每种分库方式的容量动态切分流量。当某个时刻3分库的容量变为0时,我们便平滑地把Server从3分库变为了4分库,同时并没有修改Client的代码。 diff --git a/docs/cn/connections.md b/docs/cn/connections.md new file mode 100644 index 0000000..8ad1072 --- /dev/null +++ b/docs/cn/connections.md @@ -0,0 +1,52 @@ +[connections服务](http://brpc.baidu.com:8765/connections)可以查看所有的连接。一个典型的页面如下: + +server_socket_count: 5 + +| CreatedTime | RemoteSide | SSL | Protocol | fd | BytesIn/s | In/s | BytesOut/s | Out/s | BytesIn/m | In/m | BytesOut/m | Out/m | SocketId | +| -------------------------- | ------------------- | ---- | --------- | ---- | --------- | ---- | ---------- | ----- | --------- | ---- | ---------- | ----- | -------- | +| 2015/09/21-21:32:09.630840 | 172.22.38.217:51379 | No | http | 19 | 1300 | 1 | 269 | 1 | 68844 | 53 | 115860 | 53 | 257 | +| 2015/09/21-21:32:09.630857 | 172.22.38.217:51380 | No | http | 20 | 1308 | 1 | 5766 | 1 | 68884 | 53 | 129978 | 53 | 258 | +| 2015/09/21-21:32:09.630880 | 172.22.38.217:51381 | No | http | 21 | 1292 | 1 | 1447 | 1 | 67672 | 52 | 143414 | 52 | 259 | +| 2015/09/21-21:32:01.324587 | 127.0.0.1:55385 | No | baidu_std | 15 | 1480 | 20 | 880 | 20 | 88020 | 1192 | 52260 | 1192 | 512 | +| 2015/09/21-21:32:01.325969 | 127.0.0.1:55387 | No | baidu_std | 17 | 4016 | 40 | 1554 | 40 | 238879 | 2384 | 92660 | 2384 | 1024 | + +channel_socket_count: 1 + +| CreatedTime | RemoteSide | SSL | Protocol | fd | BytesIn/s | In/s | BytesOut/s | Out/s | BytesIn/m | In/m | BytesOut/m | Out/m | SocketId | +| -------------------------- | -------------- | ---- | --------- | ---- | --------- | ---- | ---------- | ----- | --------- | ---- | ---------- | ----- | -------- | +| 2015/09/21-21:32:01.325870 | 127.0.0.1:8765 | No | baidu_std | 16 | 1554 | 40 | 4016 | 40 | 92660 | 2384 | 238879 | 2384 | 0 | + +channel_short_socket_count: 0 + +上述信息分为三段: + +- 第一段是server接受(accept)的连接。 +- 第二段是server与下游的单连接(使用brpc::Channel建立),fd为-1的是虚拟连接,对应第三段中所有相同RemoteSide的连接。 +- 第三段是server与下游的短连接或连接池(pooled connections),这些连接从属于第二段中的相同RemoteSide的虚拟连接。 + +表格标题的含义: + +- RemoteSide : 远端的ip和端口。 +- SSL:是否使用SSL加密,若为Yes的话,一般是HTTPS连接。 +- Protocol : 使用的协议,可能为baidu_std hulu_pbrpc sofa_pbrpc memcache http public_pbrpc nova_pbrpc nshead_server等。 +- fd : file descriptor(文件描述符),可能为-1。 +- BytesIn/s : 上一秒读入的字节数 +- In/s : 上一秒读入的消息数(消息是对request和response的统称) +- BytesOut/s : 上一秒写出的字节数 +- Out/s : 上一秒写出的消息数 +- BytesIn/m: 上一分钟读入的字节数 +- In/m: 上一分钟读入的消息数 +- BytesOut/m: 上一分钟写出的字节数 +- Out/m: 上一分钟写出的消息数 +- SocketId :内部id,用于debug,用户不用关心。 + + + +典型截图分别如下所示: + +单连接:![img](../images/single_conn.png) + +连接池:![img](../images/pooled_conn.png) + +短连接:![img](../images/short_conn.png) + diff --git a/docs/cn/consistent_hashing.md b/docs/cn/consistent_hashing.md new file mode 100644 index 0000000..3e7f01b --- /dev/null +++ b/docs/cn/consistent_hashing.md @@ -0,0 +1,41 @@ +# 概述 + +一些场景希望同样的请求尽量落到一台机器上,比如访问缓存集群时,我们往往希望同一种请求能落到同一个后端上,以充分利用其上已有的缓存,不同的机器承载不同的稳定working set。而不是随机地散落到所有机器上,那样的话会迫使所有机器缓存所有的内容,最终由于存不下形成颠簸而表现糟糕。 我们都知道hash能满足这个要求,比如当有n台服务器时,输入x总是会发送到第hash(x) % n台服务器上。但当服务器变为m台时,hash(x) % n和hash(x) % m很可能都不相等,这会使得几乎所有请求的发送目的地都发生变化,如果目的地是缓存服务,所有缓存将失效,继而对原本被缓存遮挡的数据库或计算服务造成请求风暴,触发雪崩。一致性哈希是一种特殊的哈希算法,在增加服务器时,发向每个老节点的请求中只会有一部分转向新节点,从而实现平滑的迁移。[这篇论文](http://blog.phpdr.net/wp-content/uploads/2012/08/Consistent-Hashing-and-Random-Trees.pdf)中提出了一致性hash的概念。 + +一致性hash满足以下四个性质: + +- 平衡性 (Balance) : 每个节点被选到的概率是O(1/n)。 +- 单调性 (Monotonicity) : 当新节点加入时, 不会有请求在老节点间移动, 只会从老节点移动到新节点。当有节点被删除时,也不会影响落在别的节点上的请求。 +- 分散性 (Spread) : 当上游的机器看到不同的下游列表时(在上线时及不稳定的网络中比较常见), 同一个请求尽量映射到少量的节点中。 +- 负载 (Load) : 当上游的机器看到不同的下游列表的时候, 保证每台下游分到的请求数量尽量一致。 + +# 实现方式 + +所有server的32位hash值在32位整数值域上构成一个环(Hash Ring),环上的每个区间和一个server唯一对应,如果一个key落在某个区间内, 它就被分流到对应的server上。 + +![img](../images/chash.png) + +当删除一个server的,它对应的区间会归属于相邻的server,所有的请求都会跑过去。当增加一个server时,它会分割某个server的区间并承载落在这个区间上的所有请求。单纯使用Hash Ring很难满足我们上节提到的属性,主要两个问题: + +- 在机器数量较少的时候, 区间大小会不平衡。 +- 当一台机器故障的时候, 它的压力会完全转移到另外一台机器, 可能无法承载。 + +为了解决这个问题,我们为每个server计算m个hash值,从而把32位整数值域划分为n*m个区间,当key落到某个区间时,分流到对应的server上。那些额外的hash值使得区间划分更加均匀,被称为虚拟节点(Virtual Node)。当删除一个server时,它对应的m个区间会分别合入相邻的区间中,那个server上的请求会较为平均地转移到其他server上。当增加server时,它会分割m个现有区间,从对应server上分别转移一些请求过来。 + +由于节点故障和变化不常发生,我们选择了修改复杂度为O(n)的有序数组来存储hash ring,每次分流使用二分查找来选择对应的机器,由于存储是连续的,查找效率比基于平衡二叉树的实现高。线程安全性请参照[Double Buffered Data](lalb.md#doublybuffereddata)章节. + +# 使用方式 + +我们内置了分别基于murmurhash3和md5两种hash算法的实现,使用要做两件事: + +- 在Channel.Init 时指定*load_balancer_name*为 "c_murmurhash" 或 "c_md5"。 +- 发起rpc时通过Controller::set_request_code(uint64_t)填入请求的hash code。 + +> request的hash算法并不需要和lb的hash算法保持一致,只需要hash的值域是32位无符号整数。由于memcache默认使用md5,访问memcached集群时请选择c_md5保证兼容性,其他场景可以选择c_murmurhash以获得更高的性能和更均匀的分布。 + +# 虚拟节点个数 + +通过-chash\_num\_replicas可设置默认的虚拟节点个数,默认值为100。对于某些特殊场合,对虚拟节点个数有自定义的需求,可以通过将*load_balancer_name*加上参数replicas=配置,如: +```c++ +channel.Init("http://...", "c_murmurhash:replicas=150", &options); +``` diff --git a/docs/cn/contention_profiler.md b/docs/cn/contention_profiler.md new file mode 100644 index 0000000..bc1f4c0 --- /dev/null +++ b/docs/cn/contention_profiler.md @@ -0,0 +1,31 @@ +brpc可以分析花在等待锁上的时间及发生等待的函数。 + +# 开启方法 + +按需开启。无需配置,不依赖tcmalloc,不需要链接frame pointer或libunwind。如果只是brpc client或没有使用brpc,看[这里](dummy_server.md)。 + +# 图示 + +当很多线程争抢同一把锁时,一些线程无法立刻获得锁,而必须睡眠直到某个线程退出临界区。这个争抢过程我们称之为**contention**。在多核机器上,当多个线程需要操作同一个资源却被一把锁挡住时,便无法充分发挥多个核心的并发能力。现代OS通过提供比锁更底层的同步原语,使得无竞争锁完全不需要系统调用,只是一两条wait-free,耗时10-20ns的原子操作,非常快。而锁一旦发生竞争,一些线程就要陷入睡眠,再次醒来触发了OS的调度代码,代价至少为3-5us。所以让锁尽量无竞争,让所有线程“一起飞”是需要高性能的server的永恒话题。 + +r31906后brpc支持contention profiler,可以分析在等待锁上花费了多少时间。等待过程中线程是睡着的不会占用CPU,所以contention profiler中的时间并不是cpu时间,也不会出现在[cpu profiler](cpu_profiler.md)中。cpu profiler可以抓到特别频繁的锁(以至于花费了很多cpu),但耗时真正巨大的临界区往往不是那么频繁,而无法被cpu profiler发现。**contention profiler和cpu profiler好似互补关系,前者分析等待时间(被动),后者分析忙碌时间。**还有一类由用户基于condition或sleep发起的主动等待时间,无需分析。 + +目前contention profiler支持pthread_mutex_t(非递归)和bthread_mutex_t,开启后每秒最多采集1000个竞争锁,这个数字由参数-bvar_collector_expected_per_second控制(同时影响rpc_dump)。 + +| Name | Value | Description | Defined At | +| ---------------------------------- | ----- | ---------------------------------------- | ------------------ | +| bvar_collector_expected_per_second | 1000 | Expected number of samples to be collected per second | bvar/collector.cpp | + +如果一秒内竞争锁的次数N超过了1000,那么每把锁会有1000/N的概率被采集。在我们的各类测试场景中(qps在10万-60万不等)没有观察到被采集程序的性能有明显变化。 + +我们通过实际例子来看下如何使用contention profiler,点击“contention”按钮(more左侧)后就会开启默认10秒的分析过程。下图是libraft中的一个示例程序的锁状况,这个程序是3个节点复制组的leader,qps在10-12万左右。左上角的**Total seconds: 2.449**是采集时间内(10秒)在锁上花费的所有等待时间。注意是“等待”,无竞争的锁不会被采集也不会出现在下图中。顺着箭头往下走能看到每份时间来自哪些函数。 + +![img](../images/raft_contention_1.png) + + 上图有点大,让我们放大一个局部看看。下图红框中的0.768是这个局部中最大的数字,它代表raft::LogManager::get_entry在等待涉及到bvar::detail::UniqueLockBase的函数上共等待了0.768秒(10秒内)。我们如果觉得这个时间不符合预期,就可以去排查代码。 + +![img](../images/raft_contention_2.png) + +点击上方的count选择框,可以查看锁的竞争次数。选择后左上角变为了**Total samples: 439026**,代表采集时间内总共的锁竞争次数(估算)。图中箭头上的数字也相应地变为了次数,而不是时间。对比同一份结果的时间和次数,可以更深入地理解竞争状况。 + +![img](../images/raft_contention_3.png) diff --git a/docs/cn/coroutine.md b/docs/cn/coroutine.md new file mode 100644 index 0000000..cdce5b9 --- /dev/null +++ b/docs/cn/coroutine.md @@ -0,0 +1,193 @@ +# C++20 协程支持 + +bRPC 支持 C++20 协程说明文档。 + +> 注:该功能是实验性的,请勿在生产环境下使用。 + +## 使用说明 + +### 适用场景 + +C++协程适用于极高并发的场景。由于bthread使用了mmap,存在系统限制,一个进程bthread数量一般最多到万级别,如果采用同步方式用一个bthread来处理一个请求,那么请求的并发度也只能到万级别。如果采用异步方式来写代码,可以达到更高的并发,但又会导致代码难以维护。这时我们就可以使用C++协程,以类似同步的方式来写代码,而达到异步的性能效果。 + +### 使用前提 + +1. 需要使用支持c++20的编译器,如gcc 11 +2. 需要编译选项中加上 `-std=c++20` + +### 简单示例 + +以下例子显示了如何在bRPC中启动一个C++20协程,在协程中发起 RPC调用,并等待返回结果。 + +```cpp +#include +#include + +// 协程函数的返回类型,需要是brpc::experimental::Awaitable +// T是函数返回的实际数据类型 +brpc::experimental::Awaitable RpcCall(brpc::Channel& channel) { + EchoRequest request; + EchoResponse response; + EchoService_Stub stub(&_channel); + brpc::Controller cntl; + brpc::experimental::AwaitableDone done; + stub.Echo(&cntl, &request, &response, &done); + // 等待RPC返回结果 + co_await done.awaitable(); + // 返回数据,注意这里用co_return而不是return + // 因为函数返回值类型是brpc::experimental::Awaitable而不是int + co_return cntl.ErrorCode(); +} + +brpc::experimental::Awaitable CoroutineMain(const char* server) { + brpc::Channel channel; + channel.Init(server, NULL); + // co_await会从Awaitable得到int类型的返回值 + int code = co_await RpcCall(channel); + printf("Rpc result:%d\n", code); +} + +int main() { + // 启动协程 + brpc::experimental::Coroutine coro(CoroutineMain("127.0.0.1:8080")); + // 等待协程执行完成 + coro.join(); + return 0; +} +``` + +更完整的例子可以查看源码中的`example/coroutine/coroutine_server.cpp`文件。 + +### 更多用法 + +1. 在非协程环境下等待一个协程执行完成: + +```cpp +brpc::experimental::Coroutine coro(func(args)); +coro.join(); +``` + +2. 在非协程环境下等待协程完成并获取返回值: + +```cpp +brpc::experimental::Coroutine coro(func(args)); // func的返回值类型为Awaitable +int result = coro.join(); +``` + +3. 在协程环境下等待协程执行完成: + +```cpp +brpc::experimental::Coroutine coro(func(args)); +... // 做一些其它事情 +co_await coro.awaitable(); +``` + +4. 在协程环境下等待协程执行完成并获取返回值: + +```cpp +brpc::experimental::Coroutine coro(func(args)); // func的返回值类型为Awaitable +... // 做一些其它事情 +int ret = co_await coro.awaitable(); +``` + +5. 在协程环境下sleep: +```cpp +co_await brpc::experimental::Coroutine::usleep(1000); +``` + +### 注意事项 + +1. 协程不保证一个函数的上下文都在同一个pthread或同一个bthread下执行。在co_await之后,代码所在的pthread或bthread可能发生变化,因此依赖于pthread或bthread的线程局部变量的代码(比如rpcz功能)将无法正确工作。 +2. 不应在协程中使用阻塞bthread(如bthread_join、同步RPC)或阻塞pthread的函数,否则可能导致死锁或者长耗时。 +3. 不要在不必要的地方使用协程,如下面的代码,虽然也能正常工作,但没有意义: + +```cpp +brpc::experimental::Awaitable inplace_func() { + co_return 123; +} +``` + +### 实现极致性能 + +如果确保服务的处理代码都运行在协程之中,并且没有任何阻塞bthread或阻塞pthread操作,则可以开启`usercode_in_coroutine`这个flag。开启这个flag之后,bRPC会简化服务端处理逻辑,减少不必要的bthread开销。在这种情况下,实际的工作线程数量将由event_dispatcher_num控制,而不再是由bthread worker数量控制。 + +## 实现原理 + +### C++20协程实现原理 + +为了方便理解,我们把上面的CoroutineMain函数稍微改写一下,把co_await前后的逻辑分成两部分: + +```cpp +brpc::experimental::Awaitable CoroutineMain(const char* server) { + brpc::Channel channel; + channel.Init(server, NULL); + brpc::experimental::Awaitable awaitable = RpcCall(channel); + + int code = co_await awaitable; + printf("Rpc result:%d\n", code); +} +``` + +上面的代码实际上是怎么执行的呢?当你使用co_await关键字的时候,编译器会把co_await后面的步骤转换成一个callback函数,把这个callback传给实际co_await的那个`Awaitable`对象,比如上面的CoroutineMain函数,经过编译器转换后会变成大概如下的逻辑(简化版,实际要比这个复杂得多): + +```cpp + +brpc::experimental::Awaitable CoroutineMain(const char* server) { + // 根据函数返回类型,找到Awaitable的名为promise_type的子类 + // 在函数的入口,创建一个promise_type类型的对象 + auto promise = new brpc::experimental::Awaitable::promise_type(); + // 从promise对象中创建返回Awaitable对象 + Awaitable ret = promise->get_return_object(); + + // co_await之前的逻辑,保持不变 + brpc::Channel channel; + channel.Init(server, NULL); + brpc::experimental::Awaitable awaitable = RpcCall(channel); + + // co_await的逻辑,转成一个await_suspend的函数调用,传入一个callback函数 + awaitable.await_suspend([promise, &awaitable]() { + // co_await之后的逻辑,转移到callback函数中 + int code = awaitable.await_resume(); + printf("Rpc result:%d\n", code); + // 在final_suspend里面,会做一些唤醒调用者、资源释放的工作 + promise->final_suspend(); + delete promise; + }) + // 返回Awaitable对象,以便上层函数进行处理 + return ret; +} +``` + +也就是说,co_await就是一个语法转换器,把看似同步的代码转化成异步调用的代码,仅此而已。至于Awaitable类和promise类的具体实现,编译器就不关心了,这是基础库需要做的。比如在brpc中封装了brpc::experimental::Awaitable类和promise子类,实现了await_suspend/await_resume等逻辑,使协程可以正确的工作起来。 + +### 原子等待操作 + +上面我们看到的是一个中间函数,它co_await一个子函数返回的Awaitable对象,然后自己也返回一个Awaitable对象。这样层层调用一定有一个尽头,即原子等待操作,它会返回Awaitable对象,但是它内部不再有co_await/co_return这样的语句了。目前实现了3种原子等待操作,未来可以扩展更多。 + +1. 等待RPC返回结果: `AwaitableDone::awaitable()` +2. 等待sleep: `Coroutine::usleep()` +3. 等待另一个协程完成: `Coroutine::awaitable()` + +下面是一个原子等待操作的示例实现,我们需要手动创建一个promise对象,设置set_needs_suspend(),然后发起一个异步调用(如bthread_timer_add),在回调函数里设置好返回值、调用promise->on_done(),最后根据promise返回Awaitable对象即可。 + +```cpp +inline Awaitable Coroutine::usleep(int sleep_us) { + auto promise = new detail::AwaitablePromise(); + promise->set_needs_suspend(); + bthread_timer_t timer; + auto abstime = butil::microseconds_from_now(sleep_us); + auto cb = [](void* p) { + auto promise = static_cast*>(p); + promise->set_value(0); + promise->on_done(); + }; + bthread_timer_add(&timer, abstime, cb, promise); + return Awaitable(promise); +} +``` + +### 协程与多线程 + +上面我们可以看到,协程本质上就是一种callback,和线程没有直接关系。它可以是单线程的,也可以是多线程的,这完全取决于它的原子等待操作里是怎么调用callback的。在bRPC的环境里,callback有可能从另一个pthread或bthread发起,所以协程也是需要考虑多线程问题。比如,有可能在调用co_await语句之前,要等待的事情就已经结束了,对于这种情况co_await应该立即返回。 + +协程和线程可以一起使用,比如我们可以使用bthread将任务scale到多核,然后在任务内部的子任务用协程来实现异步化。 \ No newline at end of file diff --git a/docs/cn/couchbase_example.md b/docs/cn/couchbase_example.md new file mode 100644 index 0000000..f7ed6d7 --- /dev/null +++ b/docs/cn/couchbase_example.md @@ -0,0 +1,5 @@ +# Couchbase 示例 + +本文档尚未翻译为中文。 + +请参阅[英文版](../en/couchbase_example.md)获取完整内容。 diff --git a/docs/cn/cpu_profiler.md b/docs/cn/cpu_profiler.md new file mode 100644 index 0000000..15d4e01 --- /dev/null +++ b/docs/cn/cpu_profiler.md @@ -0,0 +1,119 @@ +brpc可以分析程序中的热点函数。 + +# 开启方法 + +1. 链接`libtcmalloc_and_profiler.a` + 1. 这么写也开启了tcmalloc,不建议单独链接cpu profiler而不链接tcmalloc,可能越界访问导致[crash](https://github.com/gperftools/gperftools/blob/master/README#L226).可能由于tcmalloc不及时归还内存,越界访问不会crash。 + 2. 如果tcmalloc使用frame pointer而不是libunwind回溯栈,请确保在CXXFLAGS或CFLAGS中加上`-fno-omit-frame-pointer`,否则函数间的调用关系会丢失,最后产生的图片中都是彼此独立的函数方框。 +2. 定义宏BRPC_ENABLE_CPU_PROFILER, 一般加入编译参数-DBRPC_ENABLE_CPU_PROFILER。注意:BRPC_ENABLE_CPU_PROFILER宏需要定义在引用到brpc头文件(channel.h或server.h)的代码里。比如A模块引用B模块,B模块在实现中引用brpc头文件,必须在B模块的编译参数加上BRPC_ENABLE_CPU_PROFILER宏,在A模块加是没用的。 +3. 如果只是brpc client或没有使用brpc,看[这里](dummy_server.md)。 + + 注意要关闭Server端的认证,否则可能会看到这个: + +``` +$ tools/pprof --text localhost:9002/pprof/profile +Use of uninitialized value in substitution (s///) at tools/pprof line 2703. +http://localhost:9002/profile/symbol doesn't exist +``` + +server端可能会有这样的日志: + +``` +FATAL: 12-26 10:01:25: * 0 [src/brpc/policy/giano_authenticator.cpp:65][4294969345] Giano fails to verify credentical, 70003 +WARNING: 12-26 10:01:25: * 0 [src/brpc/input_messenger.cpp:132][4294969345] Authentication failed, remote side(127.0.0.1:22989) of sockfd=5, close it +``` + +# 查看方法 + +1. 通过builtin service的 /hotspots/cpu 页面查看 +1. 通过pprof 工具查看,如 tools/pprof --text localhost:9002/pprof/profile + +# 控制采样频率 + +启动前设置环境变量:export CPUPROFILE_FREQUENCY=xxx + +默认值为: 100 + +# 控制采样时间 + +url加上?seconds=秒数,如/hotspots/cpu?seconds=5 + +# 图示 + +下图是一次运行cpu profiler后的结果: + +- 左上角是总体信息,包括时间,程序名,总采样数等等。 +- View框中可以选择查看之前运行过的profile结果,Diff框中可选择查看和之前的结果的变化量,重启后清空。 +- 代表函数调用的方框中的字段从上到下依次为:函数名,这个函数本身(除去所有子函数)占的采样数和比例,这个函数及调用的所有子函数累计的采样数和比例。采样数越大框越大。 +- 方框之间连线上的数字表示被采样到的上层函数对下层函数的调用数,数字越大线越粗。 + +热点分析一般开始于找到最大的框最粗的线考察其来源及去向。 + +cpu profiler的原理是在定期被调用的SIGPROF handler中采样所在线程的栈,由于handler(在linux 2.6后)会被随机地摆放于活跃线程的栈上运行,cpu profiler在运行一段时间后能以很大的概率采集到所有活跃线程中的活跃函数,最后根据栈代表的函数调用关系汇总为调用图,并把地址转换成符号,这就是我们看到的结果图了。采集频率由环境变量CPUPROFILE_FREQUENCY控制,默认100,即每秒钟100次或每10ms一次。在实践中cpu profiler对原程序的影响不明显。 + +![img](../images/echo_cpu_profiling.png) + +在Linux下,你也可以使用[pprof](https://github.com/apache/brpc/blob/master/tools/pprof)或gperftools中的pprof进行profiling。 + +比如`pprof --text localhost:9002 --seconds=5`的意思是统计运行在本机9002端口的server的cpu情况,时长5秒。一次运行的例子如下: + +``` +$ tools/pprof --text 0.0.0.0:9002 --seconds=5 +Gathering CPU profile from http://0.0.0.0:9002/pprof/profile?seconds=5 for 5 seconds to + /home/gejun/pprof/echo_server.1419501210.0.0.0.0 +Be patient... +Wrote profile to /home/gejun/pprof/echo_server.1419501210.0.0.0.0 +Removing funlockfile from all stack traces. +Total: 2946 samples + 1161 39.4% 39.4% 1161 39.4% syscall + 248 8.4% 47.8% 248 8.4% bthread::TaskControl::steal_task + 227 7.7% 55.5% 227 7.7% writev + 87 3.0% 58.5% 88 3.0% ::cpp_alloc + 74 2.5% 61.0% 74 2.5% __read_nocancel + 46 1.6% 62.6% 48 1.6% tc_delete + 42 1.4% 64.0% 42 1.4% brpc::Socket::Address + 41 1.4% 65.4% 41 1.4% epoll_wait + 35 1.2% 66.6% 35 1.2% memcpy + 33 1.1% 67.7% 33 1.1% __pthread_getspecific + 33 1.1% 68.8% 33 1.1% brpc::Socket::Write + 33 1.1% 69.9% 33 1.1% epoll_ctl + 28 1.0% 70.9% 42 1.4% brpc::policy::ProcessRpcRequest + 27 0.9% 71.8% 27 0.9% butil::IOBuf::_push_back_ref + 27 0.9% 72.7% 27 0.9% bthread::TaskGroup::ending_sched +``` + +省略–text进入交互模式,如下图所示: + +``` +$ tools/pprof localhost:9002 --seconds=5 +Gathering CPU profile from http://0.0.0.0:9002/pprof/profile?seconds=5 for 5 seconds to + /home/gejun/pprof/echo_server.1419501236.0.0.0.0 +Be patient... +Wrote profile to /home/gejun/pprof/echo_server.1419501236.0.0.0.0 +Removing funlockfile from all stack traces. +Welcome to pprof! For help, type 'help'. +(pprof) top +Total: 2954 samples + 1099 37.2% 37.2% 1099 37.2% syscall + 253 8.6% 45.8% 253 8.6% bthread::TaskControl::steal_task + 240 8.1% 53.9% 240 8.1% writev + 90 3.0% 56.9% 90 3.0% ::cpp_alloc + 67 2.3% 59.2% 67 2.3% __read_nocancel + 47 1.6% 60.8% 47 1.6% butil::IOBuf::_push_back_ref + 42 1.4% 62.2% 56 1.9% brpc::policy::ProcessRpcRequest + 41 1.4% 63.6% 41 1.4% epoll_wait + 38 1.3% 64.9% 38 1.3% epoll_ctl + 37 1.3% 66.1% 37 1.3% memcpy + 35 1.2% 67.3% 35 1.2% brpc::Socket::Address +``` + +# MacOS的额外配置 + +在MacOS下,gperftools中的perl pprof脚本无法将函数地址转变成函数名,解决办法是: + +1. 安装[standalone pprof](https://github.com/google/pprof),并把下载的pprof二进制文件路径写入环境变量GOOGLE_PPROF_BINARY_PATH中 +2. 安装llvm-symbolizer(将函数符号转化为函数名),直接用brew安装即可:`brew install llvm` + +# 火焰图 + +若需要结果以火焰图的方式展示,请下载并安装[FlameGraph](https://github.com/brendangregg/FlameGraph)工具,将环境变量FLAMEGRAPH_PL_PATH正确设置到本地的/path/to/flamegraph.pl后启动server即可。 diff --git a/docs/cn/dummy_server.md b/docs/cn/dummy_server.md new file mode 100644 index 0000000..d8392db --- /dev/null +++ b/docs/cn/dummy_server.md @@ -0,0 +1,24 @@ +如果你的程序只使用了brpc的client或根本没有使用brpc,但你也想使用brpc的内置服务,只要在程序中启动一个空的server就行了,这种server我们称为**dummy server**。 + +# 使用了brpc的client + +只要在程序运行目录建立dummy_server.port文件,填入一个端口号(比如8888),程序会马上在这个端口上启动一个dummy server。在浏览器中访问它的内置服务,便可看到同进程内的所有bvar。 +![img](../images/dummy_server_1.png) ![img](../images/dummy_server_2.png) + +![img](../images/dummy_server_3.png) + +# 没有使用brpc + +你必须手动加入dummy server。你得先查看[Getting Started](getting_started.md)如何下载和编译brpc,然后在程序入口处加入如下代码片段: + +```c++ +#include + +... + +int main() { + ... + brpc::StartDummyServerAt(8888/*port*/); + ... +} +``` diff --git a/docs/cn/endpoint.md b/docs/cn/endpoint.md new file mode 100644 index 0000000..10a0ea3 --- /dev/null +++ b/docs/cn/endpoint.md @@ -0,0 +1,61 @@ +# UDS及IPV6支持 + +butil::EndPoint已经支持UDS(Unix Domain Socket)及IPV6。 + +## 基本用法 +代码用法: + +```cpp +EndPoint ep; +str2endpoint("unix:path.sock", &ep); // 初始化一个UDS的EndPoint +str2endpoint("[::1]:8086", &ep); // 初始化一个IPV6的EndPoint +str2endpoint("[::1]", 8086, &ep); // 初始化一个IPV6的EndPoint + +// 获取EndPoint的类型 +sa_family_t type = get_endpoint_type(ep); // 可能为AF_INET、AF_INET6或AF_UNIX + +// 使用EndPoint,和原来的方式一样 +LOG(DEBUG) << ep; // 打印EndPoint +std::string ep_str = endpoint2str(ep).c_str(); // EndPoint转str +tcp_listen(ep); // 用监听EndPoint表示的tcp端口 +tcp_connect(ep, NULL); // 用连接EndPoint表示的tcp端口 + +sockaddr_storage ss; +socklen_t socklen = 0; +endpoint2sockaddr(ep, &ss, &socklen); // 将EndPoint转为sockaddr结构,以便调用系统函数 +``` + +## 在brpc中使用UDS或IPV6 + +只需要在原来输入IPV4字符串的时候,填写UDS路径或IPV6地址即可,如: + +```cpp +server.Start("unix:path.sock", options); // 启动server监听UDS地址 +server.Start("[::0]:8086", options); // 启动server监听IPV6地址 + +channel.Init("unix:path.sock", options); // 初始化single server的Channel,访问UDS地址 +channel.Init("list://[::1]:8086,[::1]:8087", "rr", options); // 初始化带LB的Channel,访问IPV6地址 +``` + +通过 example/echo_c++ ,展示了如何使用UDS或IPV6: + +```bash +./echo_server -listen_addr='unix:path.sock' & # 启动Server监听UDS地址 +./echo_server -listen_addr='[::0]:8080' & # 启动Server监听IPV6端口 + +./echo_client -server='unix:path.sock' # 启动Client访问UDS地址 +./echo_client -server='[::1]:8080' # 启动Client访问IPV6端口 +``` + +## 限制 + +由于EndPoint结构被广泛地使用,为了保证对存量代码的兼容性(包括ABI兼容性),目前采用的实现方式是不修改EndPoint的ABI定义,使用原来的ip字段作为id,port字段来做为扩展标记,把真正的信息存在一个外部的数据结构中。 + +这种实现方式对于现存的仅使用IPV4的代码是完全兼容的,但对于使用UDS或IPV6的用户,有些代码是不兼容的,比如直接访问EndPoint的ip和port成员的代码。 + +关于UDS和IPV6,目前已知的一些限制: + +- 不兼容rpcz +- 不支持使用PortRange方式启动server +- 不支持在ServerOption中指定internal_port +- IPV6不支持link local地址(fe80::开头的地址) \ No newline at end of file diff --git a/docs/cn/error_code.md b/docs/cn/error_code.md new file mode 100644 index 0000000..7cd36b7 --- /dev/null +++ b/docs/cn/error_code.md @@ -0,0 +1,81 @@ +[English version](../en/error_code.md) + +brpc使用[brpc::Controller](https://github.com/apache/brpc/blob/master/src/brpc/controller.h)设置和获取一次RPC的参数,`Controller::ErrorCode()`和`Controller::ErrorText()`则分别是该次RPC的错误码和错误描述,RPC结束后才能访问,否则结果未定义。ErrorText()由Controller的基类google::protobuf::RpcController定义,ErrorCode()则是brpc::Controller定义的。Controller还有个Failed()方法告知该次RPC是否失败,这三者的关系是: + +- 当Failed()为true时,ErrorCode()一定为非0,ErrorText()则为非空。 +- 当Failed()为false时,ErrorCode()一定为0,ErrorText()未定义(目前在brpc中会为空,但你最好不要依赖这个事实) + +# 标记RPC为错误 + +brpc的client端和server端都有Controller,都可以通过SetFailed()修改其中的ErrorCode和ErrorText。当多次调用一个Controller的SetFailed时,ErrorCode会被覆盖,ErrorText则是**添加**而不是覆盖。在client端,框架会额外加上第几次重试,在server端,框架会额外加上server的地址信息。 + +client端Controller的SetFailed()常由框架调用,比如发送request失败,接收到的response不符合要求等等。只有在进行较复杂的访问操作时用户才可能需要设置client端的错误,比如在访问后端前做额外的请求检查,发现有错误时把RPC设置为失败。 + +server端Controller的SetFailed()常由用户在服务回调中调用。当处理过程发生错误时,一般调用SetFailed()并释放资源后就return了。框架会把错误码和错误信息按交互协议填入response,client端的框架收到后会填入它那边的Controller中,从而让用户在RPC结束后取到。需要注意的是,**server端在SetFailed()时默认不打印送往client的错误**。打日志是比较慢的,在繁忙的线上磁盘上,很容易出现巨大的lag。一个错误频发的client容易减慢整个server的速度而影响到其他的client,理论上来说这甚至能成为一种攻击手段。对于希望在server端看到错误信息的场景,可以打开gflag **-log_error_text**(可动态开关),server会在每次失败的RPC后把对应Controller的ErrorText()打印出来。 + +# brpc的错误码 + +brpc使用的所有ErrorCode都定义在[errno.proto](https://github.com/apache/brpc/blob/master/src/brpc/errno.proto)中,*SYS_*开头的来自linux系统,与/usr/include/errno.h中定义的精确一致,定义在proto中是为了跨语言。其余的是brpc自有的。 + +[berror(error_code)](https://github.com/apache/brpc/blob/master/src/butil/errno.h)可获得error_code的描述,berror()可获得当前[system errno](http://www.cplusplus.com/reference/cerrno/errno/)的描述。**ErrorText() != berror(ErrorCode())**,ErrorText()会包含更具体的错误信息。brpc默认包含berror,你可以直接使用。 + +brpc中常见错误的打印内容列表如下: + + + +| 错误码 | 数值 | 重试 | 说明 | 日志 | +| -------------- | ---- | ---- | ---------------------------------------- | ---------------------------------------- | +| EAGAIN | 11 | 是 | 同时发送的请求过多。软限,很少出现。 | Resource temporarily unavailable | +| ENODATA | 61 | 是 | 1. Naming Service返回的server列表为空 2. Naming Service某次变更时,所有实例都发生了修改,Naming Service更新LB的逻辑是先Remove再Add,会存在很短时间内LB实例列表为空的情况 | Fail to select server from xxx | +| ETIMEDOUT | 110 | 是 | 连接超时。 | Connection timed out | +| EHOSTDOWN | 112 | 是 | 可能原因:一、Naming Server返回的列表不为空,但LB选不出可用的server,LB返回了EHOSTDOWN错误。具体可能原因:a.Server正在退出中(返回了ELOGOFF) b. Server因为之前的某种失败而被封禁,封禁的具体逻辑:1. 对于单连接,唯一的连接socket被SetFail即封禁,SetFail在代码里出现非常多,有很多种可能性触发 2. 对于连接池/短连接,只有错误号满足does_error_affect_main_socket时(ECONNREFUSED,ENETUNREACH,EHOSTUNREACH或EINVAL)才会封禁 3. 封禁之后,有CheckHealth线程健康检查,就是尝试去连接一下,检查间隔由SocketOptions的health_check_interval_s控制,检查正常会解封。二、使用SingleServer方式初始化Channel(没有LB),唯一的一个连接为LOGOFF或者封禁状态(同上) | "Fail to select server from …" "Not connected to … yet" | +| ENOSERVICE | 1001 | 否 | 找不到服务,不太出现,一般会返回ENOMETHOD。 | | +| ENOMETHOD | 1002 | 否 | 找不到方法。 | 形式广泛,常见如"Fail to find method=..." | +| EREQUEST | 1003 | 否 | request序列化错误,client端和server端都可能设置 | 形式广泛:"Missing required fields in request: …" "Fail to parse request message, …" "Bad request" | +| EAUTH | 1004 | 否 | 认证失败 | "Authentication failed" | +| ETOOMANYFAILS | 1005 | 否 | ParallelChannel中太多子channel失败 | "%d/%d channels failed, fail_limit=%d" | +| EBACKUPREQUEST | 1007 | 是 | 触发backup request时设置,不会出现在ErrorCode中,但可在/rpcz里看到 | “reached backup timeout=%dms" | +| ERPCTIMEDOUT | 1008 | 否 | RPC超时 | "reached timeout=%dms" | +| EFAILEDSOCKET | 1009 | 是 | RPC进行过程中TCP连接出现问题 | "The socket was SetFailed" | +| EHTTP | 1010 | 否 | 非2xx状态码的HTTP访问结果均认为失败并被设置为这个错误码。默认不重试,可通过RetryPolicy定制 | Bad http call | +| EOVERCROWDED | 1011 | 是 | 连接上有过多的未发送数据,常由同时发起了过多的异步访问导致。可通过参数-socket_max_unwritten_bytes控制,默认64MB。 | The server is overcrowded | +| EINTERNAL | 2001 | 否 | Server端Controller.SetFailed没有指定错误码时使用的默认错误码。 | "Internal Server Error" | +| ERESPONSE | 2002 | 否 | response解析错误,client端和server端都可能设置 | 形式广泛"Missing required fields in response: ...""Fail to parse response message, ""Bad response" | +| ELOGOFF | 2003 | 是 | Server已经被Stop了 | "Server is going to quit" | +| ELIMIT | 2004 | 是 | 同时处理的请求数超过ServerOptions.max_concurrency了 | "Reached server's limit=%d on concurrent requests" | + +# 自定义错误码 + +在C++/C中你可以通过宏、常量、enum等方式定义ErrorCode: +```c++ +#define ESTOP -114 // C/C++ +static const int EMYERROR = 30; // C/C++ +const int EMYERROR2 = -31; // C++ only +``` +如果你需要用berror返回这些新错误码的描述,你可以在.cpp或.c文件的全局域中调用BAIDU_REGISTER_ERRNO(error_code, description)进行注册,比如: +```c++ +BAIDU_REGISTER_ERRNO(ESTOP, "the thread is stopping") +BAIDU_REGISTER_ERRNO(EMYERROR, "my error") +``` +strerror和strerror_r不认识使用BAIDU_REGISTER_ERRNO定义的错误码,自然地,printf类的函数中的%m也不能转化为对应的描述,你必须使用%s并配以berror()。 +```c++ +errno = ESTOP; +printf("Describe errno: %m\n"); // [Wrong] Describe errno: Unknown error -114 +printf("Describe errno: %s\n", strerror_r(errno, NULL, 0)); // [Wrong] Describe errno: Unknown error -114 +printf("Describe errno: %s\n", berror()); // [Correct] Describe errno: the thread is stopping +printf("Describe errno: %s\n", berror(errno)); // [Correct] Describe errno: the thread is stopping +``` +当同一个error code被重复注册时,那么会出现链接错误: + +``` +redefinition of `class BaiduErrnoHelper<30>' +``` +或者在程序启动时会abort: +``` +Fail to define EMYERROR(30) which is already defined as `Read-only file system', abort +``` + +你得确保不同的模块对ErrorCode的理解是相同的,否则当两个模块把一个错误码理解为不同的错误时,它们之间的交互将出现无法预计的行为。为了防止这种情况出现,你最好这么做: +- 优先使用系统错误码,它们的值和含义一般是固定不变的。 +- 多个交互的模块使用同一份错误码定义,防止后续修改时产生不一致。 +- 使用BAIDU_REGISTER_ERRNO描述新错误码,以确保同一个进程内错误码是互斥的。 diff --git a/docs/cn/execution_queue.md b/docs/cn/execution_queue.md new file mode 100644 index 0000000..8640a4a --- /dev/null +++ b/docs/cn/execution_queue.md @@ -0,0 +1,219 @@ +# 概述 + +类似于kylin的ExecMan, [ExecutionQueue](https://github.com/apache/brpc/blob/master/src/bthread/execution_queue.h)提供了异步串行执行的功能。ExecutionQueue的相关技术最早使用在RPC中实现[多线程向同一个fd写数据](io.md#发消息). 在r31345之后加入到bthread。 ExecutionQueue 提供了如下基本功能: + +- 异步有序执行: 任务在另外一个单独的线程中执行, 并且执行顺序严格和提交顺序一致. +- Multi Producer: 多个线程可以同时向一个ExecutionQueue提交任务 +- 支持cancel一个已经提交的任务 +- 支持stop +- 支持高优任务插队 + +和ExecMan的主要区别: +- ExecutionQueue的任务提交接口是[wait-free](https://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom)的, ExecMan依赖了lock, 这意味着当机器整体比较繁忙的时候,使用ExecutionQueue不会因为某个进程被系统强制切换导致所有线程都被阻塞。 +- ExecutionQueue支持批量处理: 执行线程可以批量处理提交的任务, 获得更好的locality. ExecMan的某个线程处理完某个AsyncClient的AsyncContext之后下一个任务很可能是属于另外一个AsyncClient的AsyncContex, 这时候cpu cache会在不同AsyncClient依赖的资源间进行不停的切换。 +- ExecutionQueue的处理函数不会被绑定到固定的线程中执行, ExecMan中是根据AsyncClient hash到固定的执行线程,不同的ExecutionQueue之间的任务处理完全独立,当线程数足够多的情况下,所有非空闲的ExecutionQueue都能同时得到调度。同时也意味着当线程数不足的时候,ExecutionQueue无法保证公平性, 当发生这种情况的时候需要动态增加bthread的worker线程来增加整体的处理能力. +- ExecutionQueue运行线程为bthread, 可以随意的使用一些bthread同步原语而不用担心阻塞pthread的执行. 而在ExecMan里面得尽量避免使用较高概率会导致阻塞的同步原语. + +# 背景 + +在多核并发编程领域, [Message passing](https://en.wikipedia.org/wiki/Message_passing)作为一种解决竞争的手段得到了比较广泛的应用,它按照业务依赖的资源将逻辑拆分成若干个独立actor,每个actor负责对应资源的维护工作,当一个流程需要修改某个资源的时候, +就转化为一个消息发送给对应actor,这个actor(通常在另外的上下文中)根据命令内容对这个资源进行相应的修改,之后可以选择唤醒调用者(同步)或者提交到下一个actor(异步)的方式进行后续处理。 + +![img](http://web.mit.edu/6.005/www/fa14/classes/20-queues-locks/figures/producer-consumer.png) + +# ExecutionQueue Vs Mutex + +ExecutionQueue和mutex都可以用来在多线程场景中消除竞争. 相比较使用mutex, +使用ExecutionQueue有着如下几个优点: + +- 角色划分比较清晰, 概念理解上比较简单, 实现中无需考虑锁带来的问题(比如死锁) +- 能保证任务的执行顺序,mutex的唤醒顺序不能得到严格保证. +- 所有线程各司其职,都能在做有用的事情,不存在等待. +- 在繁忙、卡顿的情况下能更好的批量执行,整体上获得较高的吞吐. + +但是缺点也同样明显: + +- 一个流程的代码往往散落在多个地方,代码理解和维护成本高。 +- 为了提高并发度, 一件事情往往会被拆分到多个ExecutionQueue进行流水线处理,这样会导致在多核之间不停的进行切换,会付出额外的调度以及同步cache的开销, 尤其是竞争的临界区非常小的情况下, 这些开销不能忽略. +- 同时原子的操作多个资源实现会变得复杂, 使用mutex可以同时锁住多个mutex, 用了ExeuctionQueue就需要依赖额外的dispatch queue了。 +- 由于所有操作都是单线程的,某个任务运行慢了就会阻塞同一个ExecutionQueue的其他操作。 +- 并发控制变得复杂,ExecutionQueue可能会由于缓存的任务过多占用过多的内存。 + +不考虑性能和复杂度,理论上任何系统都可以只使用mutex或者ExecutionQueue来消除竞争. +但是复杂系统的设计上,建议根据不同的场景灵活决定如何使用这两个工具: + +- 如果临界区非常小,竞争又不是很激烈,优先选择使用mutex, 之后可以结合[contention profiler](contention_profiler.md)来判断mutex是否成为瓶颈。 +- 需要有序执行,或者无法消除的激烈竞争但是可以通过批量执行来提高吞吐, 可以选择使用ExecutionQueue。 + +总之,多线程编程没有万能的模型,需要根据具体的场景,结合丰富的profliling工具,最终在复杂度和性能之间找到合适的平衡。 + +**特别指出一点**,Linux中mutex无竞争的lock/unlock只有需要几条原子指令,在绝大多数场景下的开销都可以忽略不计. + +# 使用方式 + +### 实现执行函数 + +``` +// Iterate over the given tasks +// +// Example: +// +// #include +// +// int demo_execute(void* meta, TaskIterator& iter) { +// if (iter.is_queue_stopped()) { +// // destroy meta and related resources +// return 0; +// } +// for (; iter; ++iter) { +// // do_something(meta, *iter) +// // or do_something(meta, iter->a_member_of_T) +// } +// return 0; +// } +template +class TaskIterator; +``` + +### 启动一个ExecutionQueue: + +``` +struct ExecutionQueueOptions { + ExecutionQueueOptions(); + + // Execute in resident pthread instead of bthread. default: false. + bool use_pthread; + + // Attribute of the bthread which execute runs on. default: BTHREAD_ATTR_NORMAL + // Bthread will be used when executor = NULL and use_pthread == false. + bthread_attr_t bthread_attr; + + // Executor that tasks run on. default: NULL + // Note that TaskOptions.in_place_if_possible = false will not work, if implementation of + // Executor is in-place(synchronous). + Executor * executor; +}; + +// Start a ExecutionQueue. If |options| is NULL, the queue will be created with +// default options. +// Returns 0 on success, errno otherwise +// NOTE: type |T| can be non-POD but must be copy-constructible +template +int execution_queue_start( + ExecutionQueueId* id, + const ExecutionQueueOptions* options, + int (*execute)(void* meta, TaskIterator& iter), + void* meta); +``` + +创建的返回值是一个64位的id, 相当于ExecutionQueue实例的一个[弱引用](https://en.wikipedia.org/wiki/Weak_reference), 可以wait-free的在O(1)时间内定位一个ExecutionQueue, 你可以到处拷贝这个id, 甚至可以放在RPC中,作为远端资源的定位工具。 +你必须保证meta的生命周期,在对应的ExecutionQueue真正停止前不会释放. + +### 停止一个ExecutionQueue: + +``` +// Stop the ExecutionQueue. +// After this function is called: +// - All the following calls to execution_queue_execute would fail immediately. +// - The executor will call |execute| with TaskIterator::is_queue_stopped() being +// true exactly once when all the pending tasks have been executed, and after +// this point it's ok to release the resource referenced by |meta|. +// Returns 0 on success, errno othrwise +template +int execution_queue_stop(ExecutionQueueId id); + +// Wait until the the stop task (Iterator::is_queue_stopped() returns true) has +// been executed +template +int execution_queue_join(ExecutionQueueId id); +``` + +stop和join都可以多次调用, 都会有合理的行为。stop可以随时调用而不用当心线程安全性问题。 + +和fd的close类似,如果stop不被调用, 相应的资源会永久泄露。 + +安全释放meta的时机: 可以在execute函数中收到iter.is_queue_stopped()==true的任务的时候释放,也可以等到join返回之后释放. 注意不要double-free + +### 提交任务 + +``` +struct TaskOptions { + TaskOptions(); + TaskOptions(bool high_priority, bool in_place_if_possible); + + // Executor would execute high-priority tasks in the FIFO order but before + // all pending normal-priority tasks. + // NOTE: We don't guarantee any kind of real-time as there might be tasks still + // in process which are uninterruptible. + // + // Default: false + bool high_priority; + + // If |in_place_if_possible| is true, execution_queue_execute would call + // execute immediately instead of starting a bthread if possible + // + // Note: Running callbacks in place might cause the dead lock issue, you + // should be very careful turning this flag on. + // + // Default: false + bool in_place_if_possible; +}; + +const static TaskOptions TASK_OPTIONS_NORMAL = TaskOptions(/*high_priority=*/ false, /*in_place_if_possible=*/ false); +const static TaskOptions TASK_OPTIONS_URGENT = TaskOptions(/*high_priority=*/ true, /*in_place_if_possible=*/ false); +const static TaskOptions TASK_OPTIONS_INPLACE = TaskOptions(/*high_priority=*/ false, /*in_place_if_possible=*/ true); + +// Thread-safe and Wait-free. +// Execute a task with defaut TaskOptions (normal task); +template +int execution_queue_execute(ExecutionQueueId id, + typename butil::add_const_reference::type task); + +// Thread-safe and Wait-free. +// Execute a task with options. e.g +// bthread::execution_queue_execute(queue, task, &bthread::TASK_OPTIONS_URGENT) +// If |options| is NULL, we will use default options (normal task) +// If |handle| is not NULL, we will assign it with the handler of this task. +template +int execution_queue_execute(ExecutionQueueId id, + typename butil::add_const_reference::type task, + const TaskOptions* options); +template +int execution_queue_execute(ExecutionQueueId id, + typename butil::add_const_reference::type task, + const TaskOptions* options, + TaskHandle* handle); + +template +int execution_queue_execute(ExecutionQueueId id, + T&& task); + +template +int execution_queue_execute(ExecutionQueueId id, + T&& task, + const TaskOptions* options); + +template +int execution_queue_execute(ExecutionQueueId id, + T&& task, + const TaskOptions* options, + TaskHandle* handle); + +``` + +high_priority的task之间的执行顺序也会**严格按照提交顺序**, 这点和ExecMan不同, ExecMan的QueueExecEmergent的AsyncContex执行顺序是undefined. 但是这也意味着你没有办法将任何任务插队到一个high priority的任务之前执行. + +开启inplace_if_possible, 在无竞争的场景中可以省去一次线程调度和cache同步的开销. 但是可能会造成死锁或者递归层数过多(比如不停的ping-pong)的问题,开启前请确定你的代码中不存在这些问题。 + +### 取消一个已提交任务 + +``` +/// [Thread safe and ABA free] Cancel the corresponding task. +// Returns: +// -1: The task was executed or h is an invalid handle +// 0: Success +// 1: The task is executing +int execution_queue_cancel(const TaskHandle& h); +``` + +返回非0仅仅意味着ExecutionQueue已经将对应的task递给过execute, 真实的逻辑中可能将这个task缓存在另外的容器中,所以这并不意味着逻辑上的task已经结束,你需要在自己的业务上保证这一点. diff --git a/docs/cn/flags.md b/docs/cn/flags.md new file mode 100644 index 0000000..fa8eac8 --- /dev/null +++ b/docs/cn/flags.md @@ -0,0 +1,137 @@ +brpc使用gflags管理配置。如果你的程序也使用gflags,那么你应该已经可以修改和brpc相关的flags,你可以浏览[flags服务](http://brpc.baidu.com:8765/flags)了解每个flag的具体功能。如果你的程序还没有使用gflags,我们建议你使用,原因如下: + +- 命令行和文件均可传入,前者方便做测试,后者适合线上运维。放在文件中的gflags可以reload。而configure只支持从文件读取配置。 +- 你可以在浏览器中查看brpc服务器中所有gflags,并对其动态修改(如果允许的话)。configure不可能做到这点。 +- gflags分散在和其作用紧密关联的文件中,更好管理。而使用configure需要聚集到一个庞大的读取函数中。 + +# Usage of gflags + +gflags一般定义在需要它的源文件中。#include 后在全局scope加入DEFINE_*\*(*\*, *\*, *\*); 比如: + +```c++ +#include +... +DEFINE_bool(hex_log_id, false, "Show log_id in hexadecimal"); +DEFINE_int32(health_check_interval, 3, "seconds between consecutive health-checkings"); +``` + +一般在main函数开头用ParseCommandLineFlags处理程序参数: + +```c++ +#include +... +int main(int argc, char* argv[]) { + google::ParseCommandLineFlags(&argc, &argv, true/*表示把识别的参数从argc/argv中删除*/); + ... +} +``` + +如果要从conf/gflags.conf中加载gflags,则可以加上参数-flagfile=conf/gflags.conf。如果希望默认(什么参数都不加)就从文件中读取,则可以在程序中直接给flagfile赋值,一般这么写 + +```c++ +google::SetCommandLineOption("flagfile", "conf/gflags.conf"); +``` + +程序启动时会检查conf/gflags.conf是否存在,如果不存在则会报错: + +``` +$ ./my_program +conf/gflags.conf: No such file or directory +``` + +更具体的使用指南请阅读[官方文档](http://gflags.github.io/gflags/)。 + +# flagfile + +在命令行中参数和值之间可不加等号,而在flagfile中一定要加。比如`./myapp -param 7`是ok的,但在`./myapp -flagfile=./gflags.conf`对应的gflags.conf中一定要写成 **-param=7** 或 **--param=7**,否则就不正确且不会报错。 + +在命令行中字符串可用单引号或双引号包围,而在flagfile中不能加。比如`./myapp -name="tom"`或`./myapp -name='tom'`都是ok的,但在`./myapp -flagfile=./gflags.conf`对应的gflags.conf中一定要写成 **-name=tom** 或 **--name=tom**,如果写成-name="tom"的话,引号也会作为值的一部分。配置文件中的值可以有空格,比如gflags.conf中写成-name=value with spaces是ok的,参数name的值就是value with spaces,而在命令行中要用引号括起来。 + +flagfile中参数可由单横线(如-foo)或双横线(如--foo)打头,但不能以三横线或更多横线打头,否则的话是无效参数且不会报错! + +flagfile中以`#开头的行被认为是注释。开头的空格和空白行都会被忽略。` + +flagfile中可以使用`--flagfile包含另一个flagfile。` + +# Change gflag on-the-fly + +[flags服务](http://brpc.baidu.com:8765/flags)可以查看服务器进程中所有的gflags。修改过的flags会以红色高亮。“修改过”指的是修改这一行为,即使再改回默认值,仍然会显示为红色。 + +/flags:列出所有的gflags + +/flags/NAME:查询名字为NAME的gflag + +/flags/NAME1,NAME2,NAME3:查询名字为NAME1或NAME2或NAME3的gflag + +/flags/foo*,b$r:查询名字与某一统配符匹配的gflag,注意用$代替?匹配单个字符,因为?在url中有特殊含义。 + +访问/flags/NAME?setvalue=VALUE即可动态修改一个gflag的值,validator会被调用。 + +为了防止误修改,需要动态修改的gflag必须有validator,显示此类gflag名字时有(R)后缀。 + +![img](../images/reloadable_flags.png) + +*修改成功后会显示如下信息*: + +![img](../images/flag_setvalue.png) + +*尝试修改不允许修改的gflag会显示如下错误信息*: + +![img](../images/set_flag_reject.png) + +*设置一个不允许的值会显示如下错误(flag值不会变化)*: + +![img](../images/set_flag_invalid_value.png) + + + +r31658之后支持可视化地修改,在浏览器上访问时将看到(R)下多了下划线: + +![img](../images/the_r_after_flag.png) + +点击后在一个独立页面可视化地修改对应的flag: + +![img](../images/set_flag_with_form.png) + +填入true后确定: + +![img](../images/set_flag_with_form_2.png) + +返回/flags可以看到对应的flag已经被修改了: + +![img](../images/set_flag_with_form_3.png) + + + +关于重载gflags,重点关注: + +- 避免在一段代码中多次调用同一个gflag,应把该gflag的值保存下来并调用该值。因为gflag的值随时可能变化,而产生意想不到的结果。 +- 使用google::GetCommandLineOption()访问string类型的gflag,直接访问是线程不安全的。 +- 处理逻辑和副作用应放到validator里去。比如修改FLAGS_foo后得更新另一处的值,如果只是写在程序初始化的地方,而不是validator里,那么重载时这段逻辑就运行不到了。 + +如果你确认某个gflag不需要额外的线程同步和处理逻辑就可以重载,那么可以用如下方式为其注册一个总是返回true的validator: + +```c++ +DEFINE_bool(hex_log_id, false, "Show log_id in hexadecimal"); +BRPC_VALIDATE_GFLAG(hex_log_id, brpc::PassValidate/*always true*/); +``` + +这个flag是单纯的开关,修改后不需要更新其他数据(没有处理逻辑),代码中前面看到true后面看到false也不会产生什么后果(不需要线程同步),所以我们让其默认可重载。 + +对于int32和int64类型,有一个判断是否为正数的常用validator: + +```c++ +DEFINE_int32(health_check_interval, 3, "seconds between consecutive health-checkings"); +BRPC_VALIDATE_GFLAG(health_check_interval, brpc::PositiveInteger); +``` + +以上操作都可以在命令行中进行: + +```shell +$ curl brpc.baidu.com:8765/flags/health_check_interval +Name | Value | Description | Defined At +--------------------------------------- +health_check_interval (R) | 3 | seconds between consecutive health-checkings | src/brpc/socket_map.cpp +``` + +1.0.251.32399后增加了-immutable_flags,打开后所有的gflags将不能被动态修改。当一个服务对某个gflag值比较敏感且不希望在线上被误改,可打开这个开关。打开这个开关的同时也意味着你无法动态修改线上的配置,每次修改都要重启程序,对于还在调试阶段或待收敛阶段的程序不建议打开。 diff --git a/docs/cn/flatmap.md b/docs/cn/flatmap.md new file mode 100644 index 0000000..814558a --- /dev/null +++ b/docs/cn/flatmap.md @@ -0,0 +1,179 @@ +# NAME + +FlatMap - Maybe the fastest hashmap, with tradeoff of space. + +# EXAMPLE + +```c++ +#include +#include +#include + +void flatmap_example() { + butil::FlatMap map; + // bucket_count: initial count of buckets, big enough to avoid resize. + // load_factor: element_count * 100 / bucket_count, 80 as default. + int bucket_count = 1000; + int load_factor = 80; + map.init(bucket_count, load_factor); + map.insert(10, "hello"); + map[20] = "world"; + std::string* value = map.seek(20); + CHECK(value != NULL); + + CHECK_EQ(2UL, map.size()); + CHECK_EQ(0UL, map.erase(30)); + CHECK_EQ(1UL, map.erase(10)); + + LOG(INFO) << "All elements of the map:"; + for (butil::FlatMap::const_iterator it = map.begin(); it != map.end(); ++it) { + LOG(INFO) << it->first << " : " << it->second; + } + map.clear(); + CHECK_EQ(0UL, map.size()); +} + +void flatmap_erase_hinted_during_iteration_example() { + typedef butil::FlatMap Map; + Map map; + // bucket_count: initial count of buckets, big enough to avoid resize. + // load_factor: element_count * 100 / bucket_count, 80 as default. + int bucket_count = 1000; + int load_factor = 80; + map.init(bucket_count, load_factor); + const int N = 10; + for (int i = 0; i < N; ++i) { + map[i] = i; + } + + for (Map::const_iterator it = map.begin(); it != map.end(); ++it) { + // After `erase()', ++iterator may fail. + // We need to save iterator before `erase()' + // and restore iterator after `erase()'. + typename Map::PositionHint hint{}; + map.save_iterator(it, &hint); + if (it->first % 2 == 0) { + CHECK_EQ(1UL, map.erase(it->first)); + } + it = map.restore_iterator(hint); + if (it == map.end()) { + break; + } + } + CHECK_EQ((size_t)(N / 2), map.size()); + + LOG(INFO) << "All remaining elements of the map:"; + for (Map::const_iterator it = map.begin(); it != map.end(); ++it) { + CHECK_EQ(1, it->first % 2); + LOG(INFO) << it->first << " : " << it->second; + } + map.clear(); + CHECK_EQ(0UL, map.size()); +} +``` + +# DESCRIPTION + +[FlatMap](https://github.com/apache/brpc/blob/master/src/butil/containers/flat_map.h)可能是最快的哈希表,但当value较大时它需要更多的内存,它最适合作为检索过程中需要极快查找的小字典。 + +原理:把开链桶中第一个节点的内容直接放桶内。由于在实践中,大部分桶没有冲突或冲突较少,所以大部分操作只需要一次内存跳转:通过哈希值访问对应的桶。桶内两个及以上元素仍存放在链表中,由于桶之间彼此独立,一个桶的冲突不会影响其他桶,性能很稳定。在很多时候,FlatMap的查找性能和原生数组接近。 + +# BENCHMARK + +下面是FlatMap和其他key/value容器的比较: + +- [AlignHashMap](https://svn.baidu.com/app/ecom/nova/trunk/public/util/container/alignhash.h):闭链中较快的实现。 +- [CowHashMap](https://svn.baidu.com/app/ecom/nova/trunk/afs/smalltable/cow_hash_map.hpp):smalltable中的开链哈希表,和普通开链不同的是带Copy-on-write逻辑。 +- [std::map](http://www.cplusplus.com/reference/map/map/):非哈希表,一般是红黑树。 + +``` +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:474] [ value = 8 bytes ] +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:521] Sequentially inserting 100 into FlatMap/AlignHashMap/CowHashMap/std::map takes 15/19/30/102ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:558] Sequentially erasing 100 from FlatMap/AlighHashMap/CowHashMap/std::map takes 7/11/33/146ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:521] Sequentially inserting 1000 into FlatMap/AlignHashMap/CowHashMap/std::map takes 10/28/26/93ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:558] Sequentially erasing 1000 from FlatMap/AlighHashMap/CowHashMap/std::map takes 6/9/29/100ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:521] Sequentially inserting 10000 into FlatMap/AlignHashMap/CowHashMap/std::map takes 10/21/26/130ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:558] Sequentially erasing 10000 from FlatMap/AlighHashMap/CowHashMap/std::map takes 5/10/30/104ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:474] [ value = 32 bytes ] +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:521] Sequentially inserting 100 into FlatMap/AlignHashMap/CowHashMap/std::map takes 23/31/31/130ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:558] Sequentially erasing 100 from FlatMap/AlighHashMap/CowHashMap/std::map takes 9/11/72/104ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:521] Sequentially inserting 1000 into FlatMap/AlignHashMap/CowHashMap/std::map takes 20/53/28/112ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:558] Sequentially erasing 1000 from FlatMap/AlighHashMap/CowHashMap/std::map takes 7/10/29/101ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:521] Sequentially inserting 10000 into FlatMap/AlignHashMap/CowHashMap/std::map takes 20/46/28/137ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:558] Sequentially erasing 10000 from FlatMap/AlighHashMap/CowHashMap/std::map takes 7/10/29/112ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:474] [ value = 128 bytes ] +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:521] Sequentially inserting 100 into FlatMap/AlignHashMap/CowHashMap/std::map takes 34/109/91/179ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:558] Sequentially erasing 100 from FlatMap/AlighHashMap/CowHashMap/std::map takes 8/11/33/112ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:521] Sequentially inserting 1000 into FlatMap/AlignHashMap/CowHashMap/std::map takes 28/76/86/169ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:558] Sequentially erasing 1000 from FlatMap/AlighHashMap/CowHashMap/std::map takes 8/9/30/110ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:521] Sequentially inserting 10000 into FlatMap/AlignHashMap/CowHashMap/std::map takes 28/68/87/201ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:558] Sequentially erasing 10000 from FlatMap/AlighHashMap/CowHashMap/std::map takes 9/9/30/125ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:474] [ value = 8 bytes ] +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:521] Randomly inserting 100 into FlatMap/AlignHashMap/CowHashMap/std::map takes 14/56/29/157ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:558] Randomly erasing 100 from FlatMap/AlighHashMap/CowHashMap/std::map takes 9/11/31/181ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:521] Randomly inserting 1000 into FlatMap/AlignHashMap/CowHashMap/std::map takes 11/17/27/156ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:558] Randomly erasing 1000 from FlatMap/AlighHashMap/CowHashMap/std::map takes 6/10/30/204ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:521] Randomly inserting 10000 into FlatMap/AlignHashMap/CowHashMap/std::map takes 13/26/27/212ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:558] Randomly erasing 10000 from FlatMap/AlighHashMap/CowHashMap/std::map takes 7/11/38/309ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:474] [ value = 32 bytes ] +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:521] Randomly inserting 100 into FlatMap/AlignHashMap/CowHashMap/std::map takes 24/32/32/181ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:558] Randomly erasing 100 from FlatMap/AlighHashMap/CowHashMap/std::map takes 10/12/32/182ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:521] Randomly inserting 1000 into FlatMap/AlignHashMap/CowHashMap/std::map takes 21/46/35/168ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:558] Randomly erasing 1000 from FlatMap/AlighHashMap/CowHashMap/std::map takes 7/10/36/209ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:521] Randomly inserting 10000 into FlatMap/AlignHashMap/CowHashMap/std::map takes 24/46/31/240ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:558] Randomly erasing 10000 from FlatMap/AlighHashMap/CowHashMap/std::map takes 8/11/40/314ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:474] [ value = 128 bytes ] +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:521] Randomly inserting 100 into FlatMap/AlignHashMap/CowHashMap/std::map takes 36/114/93/231ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:558] Randomly erasing 100 from FlatMap/AlighHashMap/CowHashMap/std::map takes 9/12/35/190ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:521] Randomly inserting 1000 into FlatMap/AlignHashMap/CowHashMap/std::map takes 44/94/88/224ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:558] Randomly erasing 1000 from FlatMap/AlighHashMap/CowHashMap/std::map takes 8/10/34/236ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:521] Randomly inserting 10000 into FlatMap/AlignHashMap/CowHashMap/std::map takes 46/92/93/314ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:558] Randomly erasing 10000 from FlatMap/AlighHashMap/CowHashMap/std::map takes 12/11/42/362ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:576] [ value = 8 bytes ] +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:637] Seeking 100 from FlatMap/AlignHashMap/CowHashMap/std::map takes 4/7/12/54ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:637] Seeking 1000 from FlatMap/AlignHashMap/CowHashMap/std::map takes 3/7/11/78ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:637] Seeking 10000 from FlatMap/AlignHashMap/CowHashMap/std::map takes 4/8/13/172ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:576] [ value = 32 bytes ] +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:637] Seeking 100 from FlatMap/AlignHashMap/CowHashMap/std::map takes 5/8/12/55ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:637] Seeking 1000 from FlatMap/AlignHashMap/CowHashMap/std::map takes 4/8/11/82ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:637] Seeking 10000 from FlatMap/AlignHashMap/CowHashMap/std::map takes 6/10/14/164ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:576] [ value = 128 bytes ] +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:637] Seeking 100 from FlatMap/AlignHashMap/CowHashMap/std::map takes 7/9/13/56ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:637] Seeking 1000 from FlatMap/AlignHashMap/CowHashMap/std::map takes 6/10/12/93ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:637] Seeking 10000 from FlatMap/AlignHashMap/CowHashMap/std::map takes 9/12/21/166ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:576] [ value = 8 bytes ] +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:637] Seeking 100 from FlatMap/AlignHashMap/CowHashMap/std::map takes 4/7/11/56ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:637] Seeking 1000 from FlatMap/AlignHashMap/CowHashMap/std::map takes 3/7/11/79ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:637] Seeking 10000 from FlatMap/AlignHashMap/CowHashMap/std::map takes 4/9/13/173ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:576] [ value = 32 bytes ] +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:637] Seeking 100 from FlatMap/AlignHashMap/CowHashMap/std::map takes 5/8/12/54ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:637] Seeking 1000 from FlatMap/AlignHashMap/CowHashMap/std::map takes 4/8/11/100ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:637] Seeking 10000 from FlatMap/AlignHashMap/CowHashMap/std::map takes 6/10/14/165ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:576] [ value = 128 bytes ] +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:637] Seeking 100 from FlatMap/AlignHashMap/CowHashMap/std::map takes 7/9/12/56ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:637] Seeking 1000 from FlatMap/AlignHashMap/CowHashMap/std::map takes 6/10/12/88ns +TRACE: 12-30 13:19:53: * 0 [test_flat_map.cpp:637] Seeking 10000 from FlatMap/AlignHashMap/CowHashMap/std::map takes 9/14/20/169ns +``` +# Overview of hashmaps + +哈希表是最常用的数据结构,它的基本原理是通过[计算哈希值](http://en.wikipedia.org/wiki/Hash_function)把不同的key分散到不同的区间,在查找时通过key的哈希值能快速地缩小查找区间。在使用恰当参数的前提下,哈希表在大部分时候能在O(1)时间内把一个key映射为value。像其他算法一样,这个“O(1)”在不同的实现中差异很大。哈希表的实现一般有两部分: + +## 计算哈希值(非加密型) + +即把key散列开的方法,最常见的莫过于线性同余,但一个好的哈希算法(非加密型)要考虑很多因素: + +- 结果是确定的。 +- [雪崩效应](http://en.wikipedia.org/wiki/Avalanche_effect):输入中一个bit的变化应该尽量影响输出所有bit的变化。 +- 输出应尽量在值域中均匀分布。 +- 充分利用现代cpu特性:成块计算,减少分支,循环展开等等。 + +大部分哈希算法针对的只是一个key,不会耗用太多的cpu。影响主要来自哈希表的整体数据分布,对于工程师来说,选用何种算法得看实践效果,一些最简单的方法也许就有很好的效果。通用算法可选择Murmurhash。 + +## 解决冲突 + +哈希值可能重合,解决冲突是哈希表性能的另一关键因素。常见的冲突解决方法有: + +- 开链哈希(open hashing, closed addressing): 开链哈希表是链表的数组,其中链表一般称为桶。当若干个key落到同一个桶时,做链表插入。这是最通用的结构,有很多优点:占用内存为O(NumElement * (KeySize + ValueSize + SomePointers)),resize时候不会使之前的存放key/value的内存失效。桶之间是独立的,一个桶的冲突不会影响到其他桶,平均查找时间较为稳定,独立的桶也易于高并发。缺点是至少要两次内存跳转:先跳到桶入口,再跳到桶中的第一个节点。对于一些很小的表这个问题不明显,因为当表很小时,节点内存是接近的,但当表变大时,访存就愈发随机。如果一次访存在50ns左右(2G左右主频),开链哈希的查找时间往往就在100ns以上。在检索端的层层ranking过程中,对一些热点字典的查找1秒内可能有几百万次以上,开链哈希有时会成为热点。一些产品线可能对开链哈希的内存也有诟病,因为每对key/value都需要额外的指针。 +- [闭链哈希](http://en.wikipedia.org/wiki/Open_addressing)(closed hashing or open addressing): 闭链的初衷是减少内存跳转,桶不再是链表入口,而只需要记录一对key/value与一些标记,当桶被占时,按照不同的探查方法直到找到空桶为止。比如线性探查就是查找下一个桶,二次探查是按1,2,4,9...平方数位移查找。优点是:当表很空时或冲突较少时,查找只需要一次访存,也不需要管理节点内存池。但仅此而已,这个方法带来了更多缺点:桶个数必须大于元素个数,resize后之前的内存全部失效,难以并发. 更关键的是聚集效应:当区域内元素较多时(超过70%,其实不算多),大量元素的实际桶和它们应在的桶有较大位移。这使哈希表的主要操作都要扫过一大片内存才能找到元素,性能不稳定难以预测。闭链哈希表在很多人的印象中“很快”,但在复杂的应用中往往不如开链哈希表,并且可能是数量级的慢。闭链有一些衍生版本试图解决这个问题,比如[Hopscotch hashing](http://en.wikipedia.org/wiki/Hopscotch_hashing)。 +- 混合开链和闭链:一般是把桶数组中的一部分拿出来作为容纳冲突元素的空间,典型如[Coalesced hashing](http://en.wikipedia.org/wiki/Coalesced_hashing),但这种结构没有解决开链的内存跳转问题,结构又比闭链复杂很多,工程效果并不好。 +- 多次哈希:一般用多个哈希表代替一个哈希表,当发生冲突时(用另一个哈希值)尝试另一个哈希表。典型如[Cuckoo hashing](http://en.wikipedia.org/wiki/Cuckoo_hashing),这个结构也没有解决内存跳转。 diff --git a/docs/cn/getting_started.md b/docs/cn/getting_started.md new file mode 100644 index 0000000..6dee100 --- /dev/null +++ b/docs/cn/getting_started.md @@ -0,0 +1,401 @@ +[English version](../en/getting_started.md) + +# 构建 + +brpc鼓励静态链接依赖,以便于每个运行brpc服务的机器不必再安装依赖。 + +brpc有如下依赖: + +* [gflags](https://github.com/gflags/gflags): Extensively used to define global options. +* [protobuf](https://github.com/google/protobuf): Serializations of messages, interfaces of services. +* [leveldb](https://github.com/google/leveldb): Required by [rpcz](rpcz.md) to record RPCs for tracing. + +# 支持的环境 + +* [Ubuntu/LinuxMint/WSL](#ubuntulinuxmintwsl) +* [Fedora/CentOS](#fedoracentos) +* [自己构建依赖的Linux](#自己构建依赖的Linux) +* [MacOS](#macos) +* [docker](#docker) + +## Ubuntu/LinuxMint/WSL +### 依赖准备 + +安装依赖: +```shell +sudo apt-get install -y git g++ make libssl-dev libgflags-dev libprotobuf-dev libprotoc-dev protobuf-compiler libleveldb-dev +``` + +如果你需要静态链接leveldb: +```shell +sudo apt-get install -y libsnappy-dev +``` + +如果你需要通过源码编译生成 leveldb 静态库: + +```shell +git clone --recurse-submodules https://github.com/google/leveldb.git +mkdir -p build && cd build +cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_POSITION_INDEPENDENT_CODE=ON .. && cmake --build . +sudo cp -r ../include/leveldb /usr/include/ && sudo cp libleveldb.a /usr/lib/ +``` + +如果你要在样例中启用cpu/heap的profiler: + +```shell +sudo apt-get install -y libgoogle-perftools-dev +``` + +如果你要运行测试,那么要安装并编译libgtest-dev(它没有被默认编译): +```shell +sudo apt-get install -y cmake libgtest-dev && cd /usr/src/gtest && sudo cmake . && sudo make && sudo mv lib/libgtest* /usr/lib/ && cd - +``` +gtest源码目录可能变动,如果`/usr/src/gtest`不存在,请尝试`/usr/src/googletest/googletest`。 + +### 使用config_brpc.sh编译brpc +git克隆brpc,进入到项目目录,然后运行 +```shell +$ sh config_brpc.sh --headers=/usr/include --libs=/usr/lib +$ make +``` +修改编译器为clang,添加选项`--cxx=clang++ --cc=clang`。 + +不想链接调试符号,添加选项`--nodebugsymbols`,然后编译将会得到更轻量的二进制文件。 + +使用glog版的brpc,添加选项`--with-glog`。 + +要启用 [thrift 支持](../en/thrift.md),首先安装thrift并且添加选项`--with-thrift`。 + +**运行样例** + +```shell +$ cd example/echo_c++ +$ make +$ ./echo_server & +$ ./echo_client +``` + +上述操作会链接brpc的静态库到样例中,如果你想链接brpc的共享库,请依次执行:`make clean`和`LINK_SO=1 make` + +**运行测试** +```shell +$ cd test +$ make +$ sh run_tests.sh +``` + +### 使用cmake编译brpc + +```shell +mkdir build && cd build && cmake .. && cmake --build . -j6 +``` +对于 cmake 3.13+ 也可以使用如下命令进行编译: +```shell +cmake -B build && cmake --build build -j6 +``` +要帮助VSCode或Emacs(LSP)去正确地理解代码,添加`-DCMAKE_EXPORT_COMPILE_COMMANDS=ON`选项去生成`compile_commands.json`。 + +要修改编译器为clang,请修改环境变量`CC`和`CXX`为`clang`和`clang++`。 + +不想链接调试符号,请移除`build/CMakeCache.txt`,然后用`-DWITH_DEBUG_SYMBOLS=OFF`选项执行cmake。 + +想要让brpc使用glog,用`-DWITH_GLOG=ON`选项执行cmake。 + +要启用 [thrift 支持](../en/thrift.md),先安装thrift,然后用`-DWITH_THRIFT=ON`选项执行cmake。 + +**用cmake运行样例** + +```shell +$ cd example/echo_c++ +$ cmake -B build && cmake --build build -j4 +$ cd build +$ ./echo_server & +$ ./echo_client +``` + +上述操作会链接brpc的静态库到样例中,如果你想链接brpc的共享库,请先移除`CMakeCache.txt`,然后用`-DLINK_SO=ON`选项重新执行cmake。 + +**运行测试** + +```shell +$ mkdir build && cd build && cmake -DBUILD_UNIT_TESTS=ON .. && make && make test +``` + +## Fedora/CentOS + +### 依赖准备 + +CentOS一般需要安装EPEL,否则很多包都默认不可用。 +```shell +sudo yum install epel-release +``` + +安装依赖: +```shell +sudo yum install git gcc-c++ make openssl-devel gflags-devel protobuf-devel protobuf-compiler leveldb-devel +``` + +如果你要在样例中启用cpu/heap的profiler: +```shell +sudo yum install gperftools-devel +``` + +如果你要运行测试,那么要安装ligtest-dev: +```shell +sudo yum install gtest-devel +``` + +### 使用config_brpc.sh编译brpc + +git克隆brpc,进入项目目录然后执行: + +```shell +$ sh config_brpc.sh --headers="/usr/include" --libs="/usr/lib64 /usr/bin" +$ make +``` +修改编译器为clang,添加选项`--cxx=clang++ --cc=clang`。 + +不想链接调试符号,添加选项`--nodebugsymbols` 然后编译将会得到更轻量的二进制文件。 + +想要让brpc使用glog,添加选项:`--with-glog`。 + +要启用 [thrift 支持](../en/thrift.md),先安装thrift,然后添加选项:`--with-thrift`。 + +**运行样例** + +```shell +$ cd example/echo_c++ +$ make +$ ./echo_server & +$ ./echo_client +``` + +上述操作会链接brpc的静态库到样例中,如果你想链接brpc的共享库,请依次执行:`make clean`和`LINK_SO=1 make` + +**运行测试** +```shell +$ cd test +$ make +$ sh run_tests.sh +``` + +### 使用cmake编译brpc +参考[这里](#使用cmake编译brpc) + +### 使用vcpkg编译brpc + +[vcpkg](https://github.com/microsoft/vcpkg) 是一个全平台支持的包管理器,你可以使用以下步骤vcpkg轻松编译brpc: + +```shell +$ git clone https://github.com/microsoft/vcpkg.git +$ ./bootstrap-vcpkg.bat # 使用 powershell +$ ./bootstrap-vcpkg.sh # 使用 bash +$ ./vcpkg install brpc +``` + +## 自己构建依赖的Linux + +### 依赖准备 + +brpc默认会构建出静态库和共享库,因此它也需要依赖有静态库和共享库两个版本。 + +以[gflags](https://github.com/gflags/gflags)为例,它默认不构建共享库,你需要给`cmake`指定选项去改变这一行为: +```shell +$ cmake . -DBUILD_SHARED_LIBS=1 -DBUILD_STATIC_LIBS=1 +$ make +``` + +### 编译brpc + +还以gflags为例,`../gflags_dev`表示gflags被克隆的位置。 + +git克隆brpc。进入到项目目录然后运行: + +```shell +$ sh config_brpc.sh --headers="../gflags_dev /usr/include" --libs="../gflags_dev /usr/lib64" +$ make +``` + +这里我们给`--headers`和`--libs`传递多个路径使得脚本能够在多个地方进行检索。你也可以打包所有依赖和brpc一起放到一个目录中,然后把目录传递给 --headers/--libs选项,它会递归搜索所有子目录直到找到必须的文件。 + +修改编译器为clang,添加选项`--cxx=clang++ --cc=clang`。 + +不想链接调试符号,添加选项`--nodebugsymbols`,然后编译将会得到更轻量的二进制文件。 + +使用glog版的brpc,添加选项`--with-glog`。 + +要启用[thrift 支持](../en/thrift.md),首先安装thrift并且添加选项`--with-thrift`。 + +```shell +$ ls my_dev +gflags_dev protobuf_dev leveldb_dev brpc_dev +$ cd brpc_dev +$ sh config_brpc.sh --headers=.. --libs=.. +$ make +``` + +### 使用cmake编译brpc +参考[这里](#使用cmake编译brpc) + +## MacOS + +注意:在相同硬件条件下,MacOS版brpc的性能可能明显差于Linux版。如果你的服务是性能敏感的,请不要使用MacOS作为你的生产环境。 + +### Apple Silicon + +master HEAD已支持M1系列芯片,M2未测试过。欢迎通过issues向我们报告遗留的warning/error。 + +### 依赖准备 + +安装依赖: +```shell +brew install ./homebrew-formula/protobuf.rb +brew install openssl git gnu-getopt coreutils gflags leveldb +``` + +如果你要在样例中启用cpu/heap的profiler: +```shell +brew install gperftools +``` + +如果你要运行测试,需安装gtest。先运行`brew install googletest`看看homebrew是否支持(老版本没有),没有的话请下载和编译googletest: +```shell +git clone https://github.com/google/googletest -b release-1.10.0 && cd googletest/googletest && mkdir build && cd build && cmake -DCMAKE_CXX_FLAGS="-std=c++11" .. && make +``` +在编译完成后,复制`include/`和`lib/`目录到`/usr/local/include`和`/usr/local/lib`目录中,以便于让所有应用都能使用gtest。 + +### OpenSSL +Monterey中openssl的安装位置可能不再位于`/usr/local/opt/openssl`,很可能会在`/opt/homebrew/Cellar`目录下,如果编译时报告找不到openssl: + +* 先运行`brew link openssl --force`看看`/usr/local/opt/openssl`是否出现了 +* 没有的话可以自行设置软链:`sudo ln -s /opt/homebrew/Cellar/openssl@3/3.0.3 /usr/local/opt/openssl`。请注意此命令中openssl的目录可能随环境变化而变化,可通过`brew info openssl`查看。 + +### 使用config_brpc.sh编译brpc +git克隆brpc,进入到项目目录然后运行: +```shell +$ sh config_brpc.sh --headers=/usr/local/include --libs=/usr/local/lib --cc=clang --cxx=clang++ +$ make +``` +MacOS Monterey下的brew安装路径可能改变,如有路径相关的错误,可考虑设置如下: + +```shell +$ sh config_brpc.sh --headers=/opt/homebrew/include --libs=/opt/homebrew/lib --cc=clang --cxx=clang++ +$ make +``` + +不想链接调试符号,添加选项`--nodebugsymbols`,然后编译将会得到更轻量的二进制文件。 + +使用glog版的brpc,添加选项`--with-glog`。 + +要启用[thrift 支持](../en/thrift.md),首先安装thrift并且添加选项`--with-thrift`。 + +**运行样例** + +```shell +$ cd example/echo_c++ +$ make +$ ./echo_server & +$ ./echo_client +``` +上述操作会链接brpc的静态库到样例中,如果你想链接brpc的共享库,请依次执行:`make clean`和`LINK_SO=1 make` + +**运行测试** +```shell +$ cd test +$ make +$ sh run_tests.sh +``` + +### 使用cmake编译brpc +参考[这里](#使用cmake编译brpc) + +## Docker +使用docker 编译brpc: + +```shell +$ mkdir -p ~/brpc +$ cd ~/brpc +$ git clone https://github.com/apache/brpc.git +$ cd brpc +$ docker build -t brpc:master . +$ docker images +$ docker run -it brpc:master /bin/bash +``` + +# 支持的依赖 + +## GCC: 4.8-11.2 + +**推荐 8.2 及以上版本。** + +默认启用 c++11,以去除对 boost 的依赖(比如 atomic)。 + +理论支持 c++11 的编译器都应可以,但部分编译器版本对 c++11 的支持存在问题。目前 GCC 4.8 可支持编译的最高版本为 1.5.0。 + +GCC7中over-aligned的问题暂时被禁止。 + +使用其他版本的gcc可能会产生编译警告,请联系我们予以修复。 + +请在makefile中给cxxflags增加`-D__const__=__unused__`选项以避免[gcc4+中的errno问题](thread_local.md). + +## Clang: 3.5-4.0 + +无已知问题。 + +## glibc: 2.12-2.25 + +无已知问题。 + +## protobuf: 3.0-5.29 + +bRPC 中使用了 protobuf 内部 API,上游不保证相关 API 的兼容性,目前测试可以支持到 v29(5.29),如有问题欢迎[反馈](https://github.com/apache/brpc/issues)。 + +[1.8.0](https://github.com/apache/brpc/releases/tag/1.8.0) 中 [#2406](https://github.com/apache/brpc/pull/2406) 和 [#2493](https://github.com/apache/brpc/pull/2493)引入了部分 proto3 语法,所以目前 bRPC 不再兼容 protobuf 2.x 版本。如果你希望使用 2.x 版本,可以使用 1.8.0 之前的 bRPC 版本。 + +## gflags: 2.1-2.2.2 + +2.1.1 中存在一处已知问题,需要[补丁](https://github.com/gflags/gflags/commit/408061b46974cc8377a8a794a048ecae359ad887)。 + +## openssl: 0.97-1.1 + +被https功能需要。 + +## tcmalloc: 1.7-2.5 + +brpc默认**不**链接 [tcmalloc](http://goog-perftools.sourceforge.net/doc/tcmalloc.html)。用户按需要链接tcmalloc。 + +和glibc内置的ptmalloc相比,tcmalloc通常能提升性能。然而不同版本的tcmalloc可能表现迥异。例如:tcmalloc 2.1与 tcmalloc 1.7和2.5相比,可能会让brpc的多线程样例性能显著恶化(tcmalloc中的一个自旋锁导致的)。甚至不同的小版本号之间变现也可能不同。当你的程序表现不符合预期的时候,移除tcmalloc然后尝试其他版本。 + +用gcc4.8.2编译然后链接更早版本GCC编译的tcmalloc,可能会让程序中main()函数之前挂掉或者死锁,例如: + +![img](../images/tcmalloc_stuck.png) + +当你遇到这个问题的时候,请用同一个GCC重新编译tcmalloc。 + +另外一个使用tcmalloc的常见问题是,它不会像 ptmalloc一样及时地归还内存给系统。因此当有一个无效的内存访问的时候,程序可能不会直接挂掉,取而代之的是它可能在一个不相关的地方挂掉,或者甚至一直不挂掉。当你的程序出现怪异的内存问题的时候,尝试移除tcmalloc。 + +如果你要使用[cpu profiler](cpu_profiler.md)或[heap profiler](heap_profiler.md),要链接`libtcmalloc_and_profiler.a`。这两个 profiler都是基于tcmalloc的。而[contention profiler](contention_profiler.md)不需要tcmalloc。 + +当你移除tcmalloc的时候,不仅要移除tcmalloc的链接,也要移除宏`-DBRPC_ENABLE_CPU_PROFILER`。 + +## glog: 3.3+ + +brpc实现了一个默认的[日志功能](../../src/butil/logging.h)它和glog冲突。要替换成glog,可以给config_brpc.sh增加`--with-glog`选项或者给cmake增加`-DWITH_GLOG=ON`选项。 + +## valgrind: 3.8+ + +brpc会自动检测valgrind(然后注册bthread的栈)。不支持老版本的valgrind(比如3.2)。 + +## thrift: 0.9.3-0.11.0 + +无已知问题。 + +## libunwind: 1.3-1.8.1 + +bRPC默认**不**链接 [libunwind](https://github.com/libunwind/libunwind)。用户需要追踪bthread功能则链接libunwind,可以给config_brpc.sh增加`--with-bthread-tracer`选项或者给cmake增加`-DWITH_BTHREAD_TRACER=ON`选项,如果是用 bazel 构建,请添加 `--define with_bthread_tracer=true` 选项。 + +建议使用最新版本的libunwind。 + +# 实例追踪 + +我们提供了一个程序去帮助你追踪和监控所有brpc实例。 只需要在某处运行 [trackme_server](https://github.com/apache/brpc/tree/master/tools/trackme_server/) 然后再带着 -trackme_server=SERVER参数启动需要被追踪的实例。trackme_server将从实例周期性地收到ping消息然后打印日志。您可以从日志中聚合实例地址,并调用实例的内置服务以获取更多信息。 diff --git a/docs/cn/heap_profiler.md b/docs/cn/heap_profiler.md new file mode 100644 index 0000000..2243b50 --- /dev/null +++ b/docs/cn/heap_profiler.md @@ -0,0 +1,169 @@ +brpc可以分析内存是被哪些函数占据的。heap profiler的原理是每分配满一些内存就采样调用处的栈,“一些”由环境变量TCMALLOC_SAMPLE_PARAMETER控制,默认524288,即512K字节。根据栈表现出的函数调用关系汇总为我们看到的结果图。在实践中heap profiler对原程序的影响不明显。 + +# 开启方法 + +1. 链接`libtcmalloc_and_profiler.a` + + 1. 如果tcmalloc使用frame pointer而不是libunwind回溯栈,请确保在CXXFLAGS或CFLAGS中加上`-fno-omit-frame-pointer`,否则函数间的调用关系会丢失,最后产生的图片中都是彼此独立的函数方框。 + +2. 在shell中`export TCMALLOC_SAMPLE_PARAMETER=524288`。该变量指每分配这么多字节内存时做一次统计,默认为0,代表不开启内存统计。[官方文档](http://goog-perftools.sourceforge.net/doc/tcmalloc.html)建议设置为524288。这个变量也可在运行前临时设置,如`TCMALLOC_SAMPLE_PARAMETER=524288 ./server`。如果没有这个环境变量,可能会看到这样的结果: + + ``` + $ tools/pprof --text localhost:9002/pprof/heap + Fetching /pprof/heap profile from http://localhost:9002/pprof/heap to + /home/gejun/pprof/echo_server.1419559063.localhost.pprof.heap + Wrote profile to /home/gejun/pprof/echo_server.1419559063.localhost.pprof.heap + /home/gejun/pprof/echo_server.1419559063.localhost.pprof.heap: header size >= 2**16 + ``` + +3. 如果只是brpc client或没有使用brpc,看[这里](dummy_server.md)。 + +注意要关闭Server端的认证,否则可能会看到这个: + +``` +$ tools/pprof --text localhost:9002/pprof/heap +Use of uninitialized value in substitution (s///) at tools/pprof line 2703. +http://localhost:9002/pprof/symbol doesn't exist +``` + +server端可能会有这样的日志: + +``` +FATAL: 12-26 10:01:25: * 0 [src/brpc/policy/giano_authenticator.cpp:65][4294969345] Giano fails to verify credentical, 70003 +WARNING: 12-26 10:01:25: * 0 [src/brpc/input_messenger.cpp:132][4294969345] Authentication failed, remote side(127.0.0.1:22989) of sockfd=5, close it +``` + +# 图示 + +![img](../images/heap_profiler_1.png) + +左上角是当前程序通过malloc分配的内存总量,顺着箭头上的数字可以看到内存来自哪些函数。方框内第一个百分比是本函数的内存占用比例, 第二个百分比是本函数及其子函数的内存占用比例 + +点击左上角的text选择框可以查看文本格式的结果,有时候这种按分配量排序的形式更方便。 + +![img](../images/heap_profiler_2.png) + +左上角的两个选择框作用分别是: + +- View:当前正在看的profile。选择\表示新建一个。新建完毕后,View选择框中会出现新profile,URL也会被修改为对应的地址。这意味着你可以通过粘贴URL分享结果,点击链接的人将看到和你一模一样的结果,而不是重做profiling的结果。你可以在框中选择之前的profile查看。历史profiie保留最近的32个,可通过[--max_profiles_kept](http://brpc.baidu.com:8765/flags/max_profiles_kept)调整。 +- Diff:和选择的profile做对比。表示什么都不选。如果你选择了之前的某个profile,那么将看到View框中的profile相比Diff框中profile的变化量。 + +下图演示了勾选Diff和Text的效果。 + +![img](../images/heap_profiler_3.gif) + +在Linux下,你也可以使用pprof脚本(tools/pprof)在命令行中查看文本格式结果: + +``` +$ tools/pprof --text db-rpc-dev00.db01:8765/pprof/heap +Fetching /pprof/heap profile from http://db-rpc-dev00.db01:8765/pprof/heap to + /home/gejun/pprof/play_server.1453216025.db-rpc-dev00.db01.pprof.heap +Wrote profile to /home/gejun/pprof/play_server.1453216025.db-rpc-dev00.db01.pprof.heap +Adjusting heap profiles for 1-in-524288 sampling rate +Heap version 2 +Total: 38.9 MB + 35.8 92.0% 92.0% 35.8 92.0% ::cpp_alloc + 2.1 5.4% 97.4% 2.1 5.4% butil::FlatMap + 0.5 1.3% 98.7% 0.5 1.3% butil::IOBuf::append + 0.5 1.3% 100.0% 0.5 1.3% butil::IOBufAsZeroCopyOutputStream::Next + 0.0 0.0% 100.0% 0.6 1.5% MallocExtension::GetHeapSample + 0.0 0.0% 100.0% 0.5 1.3% ProfileHandler::Init + 0.0 0.0% 100.0% 0.5 1.3% ProfileHandlerRegisterCallback + 0.0 0.0% 100.0% 0.5 1.3% __do_global_ctors_aux + 0.0 0.0% 100.0% 1.6 4.2% _end + 0.0 0.0% 100.0% 0.5 1.3% _init + 0.0 0.0% 100.0% 0.6 1.5% brpc::CloseIdleConnections + 0.0 0.0% 100.0% 1.1 2.9% brpc::GlobalUpdate + 0.0 0.0% 100.0% 0.6 1.5% brpc::PProfService::heap + 0.0 0.0% 100.0% 1.9 4.9% brpc::Socket::Create + 0.0 0.0% 100.0% 2.9 7.4% brpc::Socket::Write + 0.0 0.0% 100.0% 3.8 9.7% brpc::Span::CreateServerSpan + 0.0 0.0% 100.0% 1.4 3.5% brpc::SpanQueue::Push + 0.0 0.0% 100.0% 1.9 4.8% butil::ObjectPool + 0.0 0.0% 100.0% 0.8 2.0% butil::ResourcePool + 0.0 0.0% 100.0% 1.0 2.6% butil::iobuf::tls_block + 0.0 0.0% 100.0% 1.0 2.6% bthread::TimerThread::Bucket::schedule + 0.0 0.0% 100.0% 1.6 4.1% bthread::get_stack + 0.0 0.0% 100.0% 4.2 10.8% bthread_id_create + 0.0 0.0% 100.0% 1.1 2.9% bvar::Variable::describe_series_exposed + 0.0 0.0% 100.0% 1.0 2.6% bvar::detail::AgentGroup + 0.0 0.0% 100.0% 0.5 1.3% bvar::detail::Percentile::operator + 0.0 0.0% 100.0% 0.5 1.3% bvar::detail::PercentileSamples + 0.0 0.0% 100.0% 0.5 1.3% bvar::detail::Sampler::schedule + 0.0 0.0% 100.0% 6.5 16.8% leveldb::Arena::AllocateNewBlock + 0.0 0.0% 100.0% 0.5 1.3% leveldb::VersionSet::LogAndApply + 0.0 0.0% 100.0% 4.2 10.8% pthread_mutex_unlock + 0.0 0.0% 100.0% 0.5 1.3% pthread_once + 0.0 0.0% 100.0% 0.5 1.3% std::_Rb_tree + 0.0 0.0% 100.0% 1.5 3.9% std::basic_string + 0.0 0.0% 100.0% 3.5 9.0% std::string::_Rep::_S_create +``` + +brpc还提供一个类似的growth profiler分析内存的分配去向(不考虑释放)。 + +![img](../images/growth_profiler.png) + +# MacOS的额外配置 + +1. 安装[standalone pprof](https://github.com/google/pprof),并把下载的pprof二进制文件路径写入环境变量GOOGLE_PPROF_BINARY_PATH中 +2. 安装llvm-symbolizer(将函数符号转化为函数名),直接用brew安装即可:`brew install llvm` + +# Jemalloc Heap Profiler + +## 开启方法 + +1. 编译[jemalloc](https://github.com/jemalloc/jemalloc)时需--enable-prof以支持profiler, 安装完成后bin目录下会有jeprof文件。 +2. 启动进程前最好配置env `JEPROF_FILE=/xxx/jeprof`,否则进程默认用$PATH里的jeprof解析。 +3. 进程开启profiler: + - 启动进程并开启profiler功能:`MALLOC_CONF="prof:true" LD_PRELOAD=/xxx/lib/libjemalloc.so ./bin/test_server`,MALLOC_CONF是env项,prof:true只做一些初始化动作,并不会采样,但[prof_active](https://jemalloc.net/jemalloc.3.html#opt.prof_active)默认是true,所以进程启动就会采样。 + - 若静态链接jemalloc:`MALLOC_CONF="prof:true" ./bin/test_server`。 + - 或通过下面的gflags控制,gflags不会反应MALLOC_CONF值。 +4. 相关gflags说明: + - FLAGS_je_prof_active:true:开启采样,false:关闭采样。 + - FLAGS_je_prof_dump:修改值会生成heap文件,用于手动操作jeprof分析。 + - FLAGS_je_prof_reset:清理已采样数据,并且动态设置采样率,[默认](https://jemalloc.net/jemalloc.3.html#opt.lg_prof_sample)2^19B(512K),对性能影响可忽略。 +5. 若要做memory leak: + - `MALLOC_CONF="prof:true,prof_leak:true,prof_final:true" LD_PRELOAD=/xxx/lib/libjemalloc.so ./bin/test_server` ,进程退出时生成heap文件。 + - 注:可`kill pid`优雅退出,不可`kill -9 pid`;可用`FLAGS_graceful_quit_on_sigterm=true FLAGS_graceful_quit_on_sighup=true`来支持优雅退出。 + +注: + - 每次dump的都是从采样至今的所有数据,若触发了reset,接来下dump的是从reset至今的所有数据,方便做diff。 + - 更多jemalloc profiler选项请参考[官网](https://jemalloc.net/jemalloc.3.html),如`prof_leak_error:true`则检测到内存泄漏,进程立即退出。 + +## 样例 + +- jeprof命令`jeprof ip:port/pprof/heap`。 + +![img](../images/cmd_jeprof_text.png) + +- curl生成text格式`curl ip:port/pprof/heap?display=text`。 + +![img](../images/curl_jeprof_text.png) + +- curl生成svg图片格式`curl ip:port/pprof/heap?display=svg`。 + +![img](../images/curl_jeprof_svg.png) + +- 分配对象个数`curl ip:port/pprof/heap?display=svg&extra_options=inuse_objects`。 + +- curl生成火焰图`curl ip:port/pprof/heap?display=flamegraph`。需配置env FLAMEGRAPH_PL_PATH=/xxx/flamegraph.pl,[flamegraph](https://github.com/brendangregg/FlameGraph) + +![img](../images/curl_jeprof_flamegraph.png) + +- curl获取内存统计信息`curl ip:port/pprof/heap?display=stats&opts=Ja`或`curl ip:port/memory?opts=Ja`,更多opts请参考[opts](https://github.com/jemalloc/jemalloc/blob/dev/include/jemalloc/internal/stats.h#L9)。 + +![img](../images/je_stats_print.png) + + - 内存使用量可关注: + 1. jemalloc.stats下的 + - [resident](https://jemalloc.net/jemalloc.3.html#stats.resident) + - [metadata](https://jemalloc.net/jemalloc.3.html#stats.metadata) + - [allocated](https://jemalloc.net/jemalloc.3.html#stats.allocated):jeprof分析的就是这部分内存。 + - [active](https://jemalloc.net/jemalloc.3.html#stats.active):active - allocated ≈ unuse。 + 2. stats.arenas下的: + - [resident](https://jemalloc.net/jemalloc.3.html#stats.arenas.i.resident) + - [pactive](https://jemalloc.net/jemalloc.3.html#stats.arenas.i.pactive) + - [base](https://jemalloc.net/jemalloc.3.html#stats.arenas.i.base):含义近似metadata + - [small.allocated](https://jemalloc.net/jemalloc.3.html#stats.arenas.i.small.allocated) + - [large.allocated](https://jemalloc.net/jemalloc.3.html#stats.arenas.i.large.allocated):arena allocated ≈ small.allocated + large.allocated + diff --git a/docs/cn/http_client.md b/docs/cn/http_client.md new file mode 100644 index 0000000..f5fea9f --- /dev/null +++ b/docs/cn/http_client.md @@ -0,0 +1,259 @@ +[English version](../en/http_client.md) + +# 示例 + +[example/http_c++](https://github.com/apache/brpc/blob/master/example/http_c++/http_client.cpp) + +# 关于h2 + +brpc把HTTP/2协议统称为"h2",不论是否加密。然而未开启ssl的HTTP/2连接在/connections中会按官方名称h2c显示,而开启ssl的会显示为h2。 + +brpc中http和h2的编程接口基本没有区别。除非特殊说明,所有提到的http特性都同时对h2有效。 + +# 创建Channel + +brpc::Channel可访问http/h2服务,ChannelOptions.protocol须指定为PROTOCOL_HTTP或PROTOCOL_H2。 + +设定好协议后,`Channel::Init`的第一个参数可为任意合法的URL。注意:允许任意URL是为了省去用户取出host和port的麻烦,`Channel::Init`只用其中的host及port,其他部分都会丢弃。 + +```c++ +brpc::ChannelOptions options; +options.protocol = brpc::PROTOCOL_HTTP; // or brpc::PROTOCOL_H2 +if (channel.Init("www.baidu.com" /*any url*/, &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; +} +``` + +http/h2 channel也支持bns地址或其他NamingService。 + +# GET + +```c++ +brpc::Controller cntl; +cntl.http_request().uri() = "www.baidu.com/index.html"; // 设置为待访问的URL +channel.CallMethod(NULL, &cntl, NULL, NULL, NULL/*done*/); +``` + +HTTP/h2和protobuf关系不大,所以除了Controller和done,CallMethod的其他参数均为NULL。如果要异步操作,最后一个参数传入done。 + +`cntl.response_attachment()`是回复的body,类型也是butil::IOBuf。IOBuf可通过to_string()转化为std::string,但是需要分配内存并拷贝所有内容,如果关注性能,处理过程应直接支持IOBuf,而不要求连续内存。 + +# POST + +默认的HTTP Method为GET,可设置为POST或[更多http method](https://github.com/apache/brpc/blob/master/src/brpc/http_method.h)。待POST的数据应置入request_attachment(),它([butil::IOBuf](https://github.com/apache/brpc/blob/master/src/butil/iobuf.h))可以直接append std::string或char*。 + +```c++ +brpc::Controller cntl; +cntl.http_request().uri() = "..."; // 设置为待访问的URL +cntl.http_request().set_method(brpc::HTTP_METHOD_POST); +cntl.request_attachment().append("{\"message\":\"hello world!\"}"); +channel.CallMethod(NULL, &cntl, NULL, NULL, NULL/*done*/); +``` + +需要大量打印过程的body建议使用butil::IOBufBuilder,它的用法和std::ostringstream是一样的。对于有大量对象要打印的场景,IOBufBuilder简化了代码,效率也可能比c-style printf更高。 + +```c++ +brpc::Controller cntl; +cntl.http_request().uri() = "..."; // 设置为待访问的URL +cntl.http_request().set_method(brpc::HTTP_METHOD_POST); +butil::IOBufBuilder os; +os << "A lot of printing" << printable_objects << ...; +os.move_to(cntl.request_attachment()); +channel.CallMethod(NULL, &cntl, NULL, NULL, NULL/*done*/); +``` +# 控制HTTP版本 + +brpc的http行为默认是http/1.1。 + +http/1.0相比http/1.1缺少长连接功能,brpc client与一些古老的http server通信时可能需要按如下方法设置为1.0。 +```c++ +cntl.http_request().set_version(1, 0); +``` + +设置http版本对h2无效,但是client收到的h2 response和server收到的h2 request中的version会被设置为(2, 0)。 + +brpc server会自动识别HTTP版本,并相应回复,无需用户设置。 + +# URL + +URL的一般形式如下图: + +``` +// URI scheme : http://en.wikipedia.org/wiki/URI_scheme +// +// foo://username:password@example.com:8042/over/there/index.dtb?type=animal&name=narwhal#nose +// \_/ \_______________/ \_________/ \__/ \___/ \_/ \______________________/ \__/ +// | | | | | | | | +// | userinfo host port | | query fragment +// | \________________________________/\_____________|____|/ \__/ \__/ +// scheme | | | | | | +// authority | | | | | +// path | | interpretable as keys +// | | +// \_______________________________________________|____|/ \____/ \_____/ +// | | | | | +// hierarchical part | | interpretable as values +// | | +// interpretable as filename | +// | +// | +// interpretable as extension +``` + +在上面例子中可以看到,Channel.Init()和cntl.http_request().uri()被设置了相同的URL。为什么Channel不直接利用Init时传入的URL,而需要给uri()再设置一次? + +确实,在简单使用场景下,这两者有所重复,但在复杂场景中,两者差别很大,比如: + +- 访问命名服务(如BNS)下的多个http/h2 server。此时Channel.Init传入的是对该命名服务有意义的名称(如BNS中的节点名称),对uri()的赋值则是包含Host的完整URL(比如"www.foo.com/index.html?name=value")。 +- 通过http/h2 proxy访问目标server。此时Channel.Init传入的是proxy server的地址,但uri()填入的是目标server的URL。 + +## Host字段 + +若用户自己填写了"host"字段(大小写不敏感),框架不会修改。 + +若用户没有填且URL中包含host,比如http://www.foo.com/path,则http request中会包含"Host: www.foo.com"。 + +若用户没有填且URL不包含host,比如"/index.html?name=value",但如果Channel初始化的地址scheme为http(s)且包含域名,则框架会以域名作为Host,比如"http://www.foo.com",该http server将会看到"Host: www.foo.com"。如果地址是"http://www.foo.com:8989",则该http server将会看到"Host: www.foo.com:8989"。 + +若用户没有填且URL不包含host,比如"/index.html?name=value",如果Channel初始化的地址也不包含域名,则框架会以目标server的ip和port为Host,地址为10.46.188.39:8989的http server将会看到"Host: 10.46.188.39:8989"。 + +对应的字段在h2中叫":authority"。 + +# 常见设置 + +以http request为例 (对response的操作自行替换), 常见操作方式如下所示: + +访问名为Foo的header +```c++ +const std::string* value = cntl->http_request().GetHeader("Foo"); //不存在为NULL +``` +设置名为Foo的header +```c++ +cntl->http_request().SetHeader("Foo", "value"); +``` +访问名为Foo的query +```c++ +const std::string* value = cntl->http_request().uri().GetQuery("Foo"); // 不存在为NULL +``` +设置名为Foo的query +```c++ +cntl->http_request().uri().SetQuery("Foo", "value"); +``` +设置HTTP Method +```c++ +cntl->http_request().set_method(brpc::HTTP_METHOD_POST); +``` +设置url +```c++ +cntl->http_request().uri() = "http://www.baidu.com"; +``` +设置content-type +```c++ +cntl->http_request().set_content_type("text/plain"); +``` +访问body +```c++ +butil::IOBuf& buf = cntl->request_attachment(); +std::string str = cntl->request_attachment().to_string(); // 有拷贝 +``` +设置body +```c++ +cntl->request_attachment().append("...."); +butil::IOBufBuilder os; +os << "...."; +os.move_to(cntl->request_attachment()); +``` + +Notes on http header: + +- 根据[rfc2616](http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2),http header的field_name不区分大小写。brpc支持大小写不敏感,同时会在打印时保持用户传入的大小写。 +- 若http header中出现了相同的field_name, 根据[rfc2616](http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2),value应合并到一起,用逗号(,)分隔,用户自行处理. +- query之间用"&"分隔, key和value之间用"="分隔, value可以省略,比如key1=value1&key2&key3=value3中key2是合理的query,值为空字符串。 + +# 查看HTTP消息 + +打开[-http_verbose](http://brpc.baidu.com:8765/flags/http_verbose)即可看到所有的http/h2 request和response,注意这应该只用于线下调试,而不是线上程序。 + +# HTTP错误 + +当Server返回的http status code不是2xx时,该次http/h2访问被视为失败,client端会把`cntl->ErrorCode()`设置为EHTTP,用户可通过`cntl->http_response().status_code()`获得具体的http错误。同时server端可以把代表错误的html或json置入`cntl->response_attachment()`作为http body传递回来。 + +如果Server也是brpc框架实现的服务,client端希望在http/h2失败时获取brpc Server返回的真实`ErrorCode`,而不是统一设置的`EHTTP`,则需要设置GFlag`-use_http_error_code=true`。 + +# 压缩request body + +调用Controller::set_request_compress_type(brpc::COMPRESS_TYPE_GZIP)将尝试用gzip压缩http body。 + +“尝试”指的是压缩有可能不发生,条件有: + +- body尺寸小于-http_body_compress_threshold指定的字节数,默认是512。这是因为gzip并不是一个很快的压缩算法,当body较小时,压缩增加的延时可能比网络传输省下的还多。 + +# 解压response body + +出于通用性考虑brpc不会自动解压response body,解压代码并不复杂,用户可以自己做,方法如下: + +```c++ +#include +... +const std::string* encoding = cntl->http_response().GetHeader("Content-Encoding"); +if (encoding != NULL && *encoding == "gzip") { + butil::IOBuf uncompressed; + if (!brpc::policy::GzipDecompress(cntl->response_attachment(), &uncompressed)) { + LOG(ERROR) << "Fail to un-gzip response body"; + return; + } + cntl->response_attachment().swap(uncompressed); +} +// cntl->response_attachment()中已经是解压后的数据了 +``` + +# 持续下载 + +http client往往需要等待到body下载完整才结束RPC,这个过程中body都会存在内存中,如果body超长或无限长(比如直播用的flv文件),那么内存会持续增长,直到超时。这样的http client不适合下载大文件。 + +brpc client支持在读取完body前就结束RPC,让用户在RPC结束后再读取持续增长的body。注意这个功能不等同于“支持http chunked mode”,brpc的http实现一直支持解析chunked mode,这里的问题是如何让用户处理超长或无限长的body,和body是否以chunked mode传输无关。 + +使用方法如下: + +1. 首先实现ProgressiveReader,接口如下: + + ```c++ + #include + ... + class ProgressiveReader { + public: + // Called when one part was read. + // Error returned is treated as *permanent* and the socket where the + // data was read will be closed. + // A temporary error may be handled by blocking this function, which + // may block the HTTP parsing on the socket. + virtual butil::Status OnReadOnePart(const void* data, size_t length) = 0; + + // Called when there's nothing to read anymore. The `status' is a hint for + // why this method is called. + // - status.ok(): the message is complete and successfully consumed. + // - otherwise: socket was broken or OnReadOnePart() failed. + // This method will be called once and only once. No other methods will + // be called after. User can release the memory of this object inside. + virtual void OnEndOfMessage(const butil::Status& status) = 0; + }; + ``` + OnReadOnePart在每读到一段数据时被调用,OnEndOfMessage在数据结束或连接断开时调用,实现前仔细阅读注释。 + +2. 发起RPC前设置`cntl.response_will_be_read_progressively();` + 这告诉brpc在读取http response时只要读完header部分RPC就可以结束了。 + +3. RPC结束后调用`cntl.ReadProgressiveAttachmentBy(new MyProgressiveReader);` + MyProgressiveReader就是用户实现ProgressiveReader的实例。用户可以在这个实例的OnEndOfMessage接口中删除这个实例。 + +# 持续上传 + +目前Post的数据必须是完整生成好的,不适合POST超长的body。 + +# 访问带认证的Server + +根据Server的认证方式生成对应的auth_data,并设置为http header "Authorization"的值。比如用的是curl,那就加上选项`-H "Authorization : "。` + +# 发送https请求 +https是http over SSL的简称,SSL并不是http特有的,而是对所有协议都有效。开启客户端SSL的一般性方法见[这里](client.md#开启ssl)。为方便使用,brpc会对https开头的uri自动开启SSL。 diff --git a/docs/cn/http_derivatives.md b/docs/cn/http_derivatives.md new file mode 100644 index 0000000..ed166c3 --- /dev/null +++ b/docs/cn/http_derivatives.md @@ -0,0 +1,37 @@ +[English version](../en/http_derivatives.md) + +http/h2协议的基本用法见[http_client](http_client.md)和[http_service](http_service.md) + +下文中的小节名均为可填入ChannelOptions.protocol中的协议名。冒号后的内容是协议参数,用于动态选择衍生行为,但基本协议仍然是http/1.x或http/2,故这些协议在服务端只会被显示为http或h2/h2c。 + +# http:json, h2:json + +如果pb request不为空,则会被转为json并作为http/h2请求的body,此时Controller.request_attachment()必须为空否则报错。 + +如果pb response不为空,则会以json解析http/h2回复的body,并转填至pb response的对应字段。 + +http/1.x默认是这个行为,所以"http"和"http:json"等价。 + +# http:proto, h2:proto + +如果pb request不为空,则会把pb序列化结果作为http/h2请求的body,此时Controller.request_attachment()必须为空否则报错。 + +如果pb response不为空,则会以pb序列化格式解析http/h2回复的body,并填至pb response。 + +http/2默认是这个行为,所以"h2"和"h2:proto"等价。 + +# h2:grpc + +[gRPC](https://github.com/grpc)的默认协议,具体格式可阅读[gRPC over HTTP2](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md) + +使用brpc的客户端把ChannelOptions.protocol设置为"h2:grpc"一般就能连通gRPC。 + +使用brpc的服务端一般无需修改代码即可自动被gRPC客户端访问。 + +gRPC默认序列化是pb二进制格式,所以"h2:grpc"和"h2:grpc+proto"等价。 + +TODO: gRPC其他配置 + +# h2:grpc+json + +这个协议相比h2:grpc就是用json序列化结果代替pb序列化结果。gRPC未必直接支持这个格式,如grpc-go用户可参考[这里](https://github.com/johanbrandhorst/grpc-json-example/blob/master/codec/json.go)注册相应的codec后才支持。 diff --git a/docs/cn/http_service.md b/docs/cn/http_service.md new file mode 100644 index 0000000..88bb46f --- /dev/null +++ b/docs/cn/http_service.md @@ -0,0 +1,385 @@ +[English version](../en/http_service.md) + +这里指我们通常说的http/h2服务,而不是可通过http/h2访问的pb服务。 + +虽然用不到pb消息,但brpc中的http/h2服务接口也得定义在proto文件中,只是request和response都是空的结构体。这确保了所有的服务声明集中在proto文件中,而不是散列在proto文件、程序、配置等多个地方。 + +#示例 +[http_server.cpp](https://github.com/apache/brpc/blob/master/example/http_c++/http_server.cpp)。 + +# 关于h2 + +brpc把HTTP/2协议统称为"h2",不论是否加密。然而未开启ssl的HTTP/2连接在/connections中会按官方名称h2c显示,而开启ssl的会显示为h2。 + +brpc中http和h2的编程接口基本没有区别。除非特殊说明,所有提到的http特性都同时对h2有效。 + +# URL类型 + +## 前缀为/ServiceName/MethodName + +定义一个service名为ServiceName(不包含package名), method名为MethodName的pb服务,且让request和response定义为空,则该服务默认在/ServiceName/MethodName上提供http/h2服务。 + +request和response可为空是因为数据都在Controller中: + +* http/h2 request的header在Controller.http_request()中,body在Controller.request_attachment()中。 +* http/h2 response的header在Controller.http_response()中,body在Controller.response_attachment()中。 + +实现步骤如下: + +1. 填写proto文件。 + +```protobuf +option cc_generic_services = true; + +message HttpRequest { }; +message HttpResponse { }; + +service HttpService { + rpc Echo(HttpRequest) returns (HttpResponse); +}; +``` + +2. 实现Service接口。和pb服务一样,也是继承定义在.pb.h中的service基类。 + +```c++ +class HttpServiceImpl : public HttpService { +public: + ... + virtual void Echo(google::protobuf::RpcController* cntl_base, + const HttpRequest* /*request*/, + HttpResponse* /*response*/, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = static_cast(cntl_base); + + // body是纯文本 + cntl->http_response().set_content_type("text/plain"); + + // 把请求的query-string和body打印结果作为回复内容。 + butil::IOBufBuilder os; + os << "queries:"; + for (brpc::URI::QueryIterator it = cntl->http_request().uri().QueryBegin(); + it != cntl->http_request().uri().QueryEnd(); ++it) { + os << ' ' << it->first << '=' << it->second; + } + os << "\nbody: " << cntl->request_attachment() << '\n'; + os.move_to(cntl->response_attachment()); + } +}; +``` + +3. 把实现好的服务插入Server后可通过如下URL访问,/HttpService/Echo后的部分在 cntl->http_request().unresolved_path()中。 + +| URL | 访问方法 | cntl->http_request().uri().path() | cntl->http_request().unresolved_path() | +| -------------------------- | ---------------- | --------------------------------- | -------------------------------------- | +| /HttpService/Echo | HttpService.Echo | "/HttpService/Echo" | "" | +| /HttpService/Echo/Foo | HttpService.Echo | "/HttpService/Echo/Foo" | "Foo" | +| /HttpService/Echo/Foo/Bar | HttpService.Echo | "/HttpService/Echo/Foo/Bar" | "Foo/Bar" | +| /HttpService//Echo///Foo// | HttpService.Echo | "/HttpService//Echo///Foo//" | "Foo" | +| /HttpService | 访问错误 | | | + +## 前缀为/ServiceName + +资源类的http/h2服务可能需要这样的URL,ServiceName后均为动态内容。比如/FileService/foobar.txt代表./foobar.txt,/FileService/app/data/boot.cfg代表./app/data/boot.cfg。 + +实现方法: + +1. proto文件中应以FileService为服务名,以default_method为方法名。 + +```protobuf +option cc_generic_services = true; + +message HttpRequest { }; +message HttpResponse { }; + +service FileService { + rpc default_method(HttpRequest) returns (HttpResponse); +} +``` + +2. 实现Service。 + +```c++ +class FileServiceImpl: public FileService { +public: + ... + virtual void default_method(google::protobuf::RpcController* cntl_base, + const HttpRequest* /*request*/, + HttpResponse* /*response*/, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = static_cast(cntl_base); + cntl->response_attachment().append("Getting file: "); + cntl->response_attachment().append(cntl->http_request().unresolved_path()); + } +}; +``` + +3. 实现完毕插入Server后可通过如下URL访问,/FileService之后的路径在cntl->http_request().unresolved_path()中i。 + +| URL | 访问方法 | cntl->http_request().uri().path() | cntl->http_request().unresolved_path() | +| ------------------------------- | -------------------------- | --------------------------------- | -------------------------------------- | +| /FileService | FileService.default_method | "/FileService" | "" | +| /FileService/123.txt | FileService.default_method | "/FileService/123.txt" | "123.txt" | +| /FileService/mydir/123.txt | FileService.default_method | "/FileService/mydir/123.txt" | "mydir/123.txt" | +| /FileService//mydir///123.txt// | FileService.default_method | "/FileService//mydir///123.txt//" | "mydir/123.txt" | + +## Restful URL + +brpc支持为service中的每个方法指定一个URL。API如下: + +```c++ +// 如果restful_mappings不为空, service中的方法可通过指定的URL被http/h2协议访问,而不是/ServiceName/MethodName. +// 映射格式:"PATH1 => NAME1, PATH2 => NAME2 ..." +// PATHs是有效的路径, NAMEs是service中的方法名. +int AddService(google::protobuf::Service* service, + ServiceOwnership ownership, + butil::StringPiece restful_mappings); +``` + +下面的QueueService包含多个方法。如果我们像之前那样把它插入server,那么只能通过`/QueueService/start, /QueueService/stop`等url来访问。 + +```protobuf +service QueueService { + rpc start(HttpRequest) returns (HttpResponse); + rpc stop(HttpRequest) returns (HttpResponse); + rpc get_stats(HttpRequest) returns (HttpResponse); + rpc download_data(HttpRequest) returns (HttpResponse); +}; +``` + +而在调用AddService时指定第三个参数(restful_mappings)就能定制URL了,如下所示: + +```c++ +if (server.AddService(&queue_svc, + brpc::SERVER_DOESNT_OWN_SERVICE, + "/v1/queue/start => start," + "/v1/queue/stop => stop," + "/v1/queue/stats/* => get_stats") != 0) { + LOG(ERROR) << "Fail to add queue_svc"; + return -1; +} + +// 星号可出现在中间 +if (server.AddService(&queue_svc, + brpc::SERVER_DOESNT_OWN_SERVICE, + "/v1/*/start => start," + "/v1/*/stop => stop," + "*.data => download_data") != 0) { + LOG(ERROR) << "Fail to add queue_svc"; + return -1; +} +``` + +上面代码中AddService的第三个参数分了三行,但实际上是一个字符串。这个字符串包含以逗号(,)分隔的三个映射关系,每个映射告诉brpc:在遇到箭头左侧的URL时调用右侧的方法。"/v1/queue/stats/*"中的星号可匹配任意字串。 + +关于映射规则: + +- 多个路径可映射至同一个方法。 +- service不要求是纯http/h2,pb service也支持。 +- 没有出现在映射中的方法仍旧通过/ServiceName/MethodName访问。出现在映射中的方法不再能通过/ServiceName/MethodName访问。 +- ==> ===> ...都是可以的。开头结尾的空格,额外的斜杠(/),最后多余的逗号,都不要紧。 +- PATH和PATH/*两者可以共存。 +- 支持后缀匹配: 星号后可以有更多字符。 +- 一个路径中只能出现一个星号。 + +`cntl.http_request().unresolved_path()` 对应星号(*)匹配的部分,保证normalized:开头结尾都不包含斜杠(/),中间斜杠不重复。比如: + +![img](../images/restful_1.png) + +或 + +![img](../images/restful_2.png) + +unresolved_path都是`"foo/bar"`,左右、中间多余的斜杠被移除了。 + + 注意:`cntl.http_request().uri().path()`不保证normalized,这两个例子中分别为`"//v1//queue//stats//foo///bar//////"`和`"//vars///foo////bar/////"` + +/status页面上的方法名后会加上所有相关的URL,形式是:@URL1 @URL2 ... + +![img](../images/restful_3.png) + +# HTTP参数 + +## HTTP headers + +http header是一系列key/value对,有些由HTTP协议规定有特殊含义,其余则由用户自由设定。 + +query string也是key/value对,http headers与query string的区别: + +* 虽然http headers由协议准确定义操作方式,但由于也不易在地址栏中被修改,常用于传递框架或协议层面的参数。 +* query string是URL的一部分,**常见**形式是key1=value1&key2=value2&…,易于阅读和修改,常用于传递应用层参数。但query string的具体格式并不是HTTP规范的一部分,只是约定成俗。 + +```c++ +// 获得header中"User-Agent"的值,大小写不敏感。 +const std::string* user_agent_str = cntl->http_request().GetHeader("User-Agent"); +if (user_agent_str != NULL) { // has the header + LOG(TRACE) << "User-Agent is " << *user_agent_str; +} +... + +// 在header中增加"Accept-encoding: gzip",大小写不敏感。 +cntl->http_response().SetHeader("Accept-encoding", "gzip"); +// 覆盖为"Accept-encoding: deflate" +cntl->http_response().SetHeader("Accept-encoding", "deflate"); +// 增加一个value,逗号分隔,变为"Accept-encoding: deflate,gzip" +cntl->http_response().AppendHeader("Accept-encoding", "gzip"); +``` + +## Content-Type + +Content-type记录body的类型,是一个使用频率较高的header。它在brpc中被特殊处理,需要通过cntl->http_request().content_type()来访问,cntl->GetHeader("Content-Type")是获取不到的。 + +```c++ +// Get Content-Type +if (cntl->http_request().content_type() == "application/json") { + ... +} +... +// Set Content-Type +cntl->http_response().set_content_type("text/html"); +``` + +如果RPC失败(Controller被SetFailed), Content-Type会框架强制设为text/plain,而response body设为Controller::ErrorText()。 + +## Status Code + +status code是http response特有的字段,标记http请求的完成情况。可能的值定义在[http_status_code.h](https://github.com/apache/brpc/blob/master/src/brpc/http_status_code.h)中。 + +```c++ +// Get Status Code +if (cntl->http_response().status_code() == brpc::HTTP_STATUS_NOT_FOUND) { + LOG(FATAL) << "FAILED: " << controller.http_response().reason_phrase(); +} +... +// Set Status code +cntl->http_response().set_status_code(brpc::HTTP_STATUS_INTERNAL_SERVER_ERROR); +cntl->http_response().set_status_code(brpc::HTTP_STATUS_INTERNAL_SERVER_ERROR, "My explanation of the error..."); +``` + +比如,以下代码以302错误实现重定向: + +```c++ +cntl->http_response().set_status_code(brpc::HTTP_STATUS_FOUND); +cntl->http_response().SetHeader("Location", "http://bj.bs.bae.baidu.com/family/image001(4979).jpg"); +``` + +![img](../images/302.png) + +## Query String + +如上面的[HTTP headers](#http-headers)中提到的那样,我们按约定成俗的方式来理解query string,即key1=value1&key2=value2&...。只有key而没有value也是可以的,仍然会被GetQuery查询到,只是值为空字符串,这常被用做bool型的开关。接口定义在[uri.h](https://github.com/apache/brpc/blob/master/src/brpc/uri.h)。 + +```c++ +const std::string* time_value = cntl->http_request().uri().GetQuery("time"); +if (time_value != NULL) { // the query string is present + LOG(TRACE) << "time = " << *time_value; +} + +... +cntl->http_request().uri().SetQuery("time", "2015/1/2"); +``` + +# 调试 + +打开[-http_verbose](http://brpc.baidu.com:8765/flags/http_verbose)即可看到所有的http/h2 request和response,注意这应该只用于线下调试,而不是线上程序。 + +# 压缩response body + +http服务常对http body进行压缩,可以有效减少网页的传输时间,加快页面的展现速度。 + +设置Controller::set_response_compress_type(brpc::COMPRESS_TYPE_GZIP)后将**尝试**用gzip压缩http body。“尝试“指的是压缩有可能不发生,条件有: + +- 请求中没有设置Accept-encoding或不包含gzip。比如curl不加--compressed时是不支持压缩的,这时server总是会返回不压缩的结果。 + +- body尺寸小于-http_body_compress_threshold指定的字节数,默认是512。gzip并不是一个很快的压缩算法,当body较小时,压缩增加的延时可能比网络传输省下的还多。当包较小时不做压缩可能是个更好的选项。 + + | Name | Value | Description | Defined At | + | ---------------------------- | ----- | ---------------------------------------- | ------------------------------------- | + | http_body_compress_threshold | 512 | Not compress http body when it's less than so many bytes. | src/brpc/policy/http_rpc_protocol.cpp | + +# 解压request body + +出于通用性考虑且解压代码不复杂,brpc不会自动解压request body,用户可以自己做,方法如下: + +```c++ +#include +... +const std::string* encoding = cntl->http_request().GetHeader("Content-Encoding"); +if (encoding != NULL && *encoding == "gzip") { + butil::IOBuf uncompressed; + if (!brpc::policy::GzipDecompress(cntl->request_attachment(), &uncompressed)) { + LOG(ERROR) << "Fail to un-gzip request body"; + return; + } + cntl->request_attachment().swap(uncompressed); +} +// cntl->request_attachment()中已经是解压后的数据了 +``` + +# 处理https请求 +https是http over SSL的简称,SSL并不是http特有的,而是对所有协议都有效。开启服务端SSL的一般性方法见[这里](server.md#开启ssl)。 + +# 性能 + +没有极端性能要求的产品都有使用HTTP协议的倾向,特别是移动产品,所以我们很重视HTTP的实现质量,具体来说: + +- 使用了node.js的[http parser](https://github.com/apache/brpc/blob/master/src/brpc/details/http_parser.h)解析http消息,这是一个轻量、优秀、被广泛使用的实现。 +- 使用[rapidjson](https://github.com/miloyip/rapidjson)解析json,这是一个主打性能的json库。 +- 在最差情况下解析http请求的时间复杂度也是O(N),其中N是请求的字节数。反过来说,如果解析代码要求http请求是完整的,那么它可能会花费O(N^2)的时间。HTTP请求普遍较大,这一点意义还是比较大的。 +- 来自不同client的http消息是高度并发的,即使相当复杂的http消息也不会影响对其他客户端的响应。其他rpc和[基于单线程reactor](threading_overview.md#单线程reactor)的各类http server往往难以做到这一点。 + +# 持续发送 + +brpc server支持发送超大或无限长的body。方法如下: + +1. 调用Controller::CreateProgressiveAttachment()创建可持续发送的body。返回的ProgressiveAttachment对象需要用intrusive_ptr管理。 + ```c++ + #include + ... + butil::intrusive_ptr pa = cntl->CreateProgressiveAttachment(); + ``` + +2. 调用ProgressiveAttachment::Write()发送数据。 + + * 如果写入发生在server-side done调用前,发送的数据将会被缓存直到回调结束后才会开始发送。 + * 如果写入发生在server-side done调用后,发送的数据将立刻以chunked mode写出。 + +3. 发送完毕后确保所有的`butil::intrusive_ptr`都析构以释放资源。 + +另外,利用该特性可以轻松实现Server-Sent Events(SSE)服务,从而使客户端能够通过 HTTP 连接从服务器自动接收更新。非常适合构建诸如chatGPT这类实时应用程序,应用例子详见[http_server.cpp](https://github.com/apache/brpc/blob/master/example/http_c++/http_server.cpp)中的HttpSSEServiceImpl。 + +# 持续接收 + +目前brpc server不支持在收齐http请求的header部分后就调用服务回调,即brpc server不适合接收超长或无限长的body。 + +# FAQ + +### Q: brpc前的nginx报了final fail + +这个错误在于brpc server直接关闭了http连接而没有发送任何回复。 + +brpc server一个端口支持多种协议,当它无法解析某个http请求时无法说这个请求一定是HTTP。server会对一些基本可确认是HTTP的请求返回HTTP 400错误并关闭连接,但如果是HTTP method错误(在http包开头)或严重的格式错误(可能由HTTP client的bug导致),server仍会直接断开连接,导致nginx的final fail。 + +解决方案: 在使用Nginx转发流量时,通过指定$HTTP_method只放行允许的方法或者干脆设置proxy_method为指定方法。 + +### Q: brpc支持http chunked方式传输吗 + +支持。 + +### Q: HTTP请求的query string中含有BASE64编码过的value,为什么有时候无法正常解析 + +根据[HTTP协议](http://tools.ietf.org/html/rfc3986#section-2.2)中的要求,以下字符应该使用%编码 + +``` + reserved = gen-delims / sub-delims + + gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" + + sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + / "*" / "+" / "," / ";" / "=" +``` + +但Base64 编码后的字符串中,会以"="或者"=="作为结尾,比如: ?wi=NDgwMDB8dGVzdA==&anothorkey=anothervalue。这个字段可能会被正确解析,也可能不会,取决于具体实现,原则上不应做任何假设. + +一个解决方法是删除末尾的"=", 不影响Base64的[正常解码](http://en.wikipedia.org/wiki/Base64#Padding); 第二个方法是在对这个URI做[percent encoding](https://en.wikipedia.org/wiki/Percent-encoding),解码时先做percent decoding再用Base64. diff --git a/docs/cn/io.md b/docs/cn/io.md new file mode 100644 index 0000000..b39c5ec --- /dev/null +++ b/docs/cn/io.md @@ -0,0 +1,51 @@ +[English version](../en/io.md) + +一般有三种操作IO的方式: + +- blocking IO: 发起IO操作后阻塞当前线程直到IO结束,标准的同步IO,如默认行为的posix [read](http://linux.die.net/man/2/read)和[write](http://linux.die.net/man/2/write)。 +- non-blocking IO: 发起IO操作后不阻塞,用户可阻塞等待多个IO操作同时结束。non-blocking也是一种同步IO:“批量的同步”。如linux下的[poll](http://linux.die.net/man/2/poll),[select](http://linux.die.net/man/2/select), [epoll](http://linux.die.net/man/4/epoll),BSD下的[kqueue](https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2)。 +- asynchronous IO: 发起IO操作后不阻塞,用户得递一个回调待IO结束后被调用。如windows下的[OVERLAPPED](https://msdn.microsoft.com/en-us/library/windows/desktop/ms684342(v=vs.85).aspx) + [IOCP](https://msdn.microsoft.com/en-us/library/windows/desktop/aa365198(v=vs.85).aspx)。linux的native AIO只对文件有效。 + +linux一般使用non-blocking IO提高IO并发度。当IO并发度很低时,non-blocking IO不一定比blocking IO更高效,因为后者完全由内核负责,而read/write这类系统调用已高度优化,效率显然高于一般得多个线程协作的non-blocking IO。但当IO并发度愈发提高时,blocking IO阻塞一个线程的弊端便显露出来:内核得不停地在线程间切换才能完成有效的工作,一个cpu core上可能只做了一点点事情,就马上又换成了另一个线程,cpu cache没得到充分利用,另外大量的线程会使得依赖thread-local加速的代码性能明显下降,如tcmalloc,一旦malloc变慢,程序整体性能往往也会随之下降。而non-blocking IO一般由少量event dispatching线程和一些运行用户逻辑的worker线程组成,这些线程往往会被复用(换句话说调度工作转移到了用户态),event dispatching和worker可以同时在不同的核运行(流水线化),内核不用频繁的切换就能完成有效的工作。线程总量也不用很多,所以对thread-local的使用也比较充分。这时候non-blocking IO就往往比blocking IO快了。不过non-blocking IO也有自己的问题,它需要调用更多系统调用,比如[epoll_ctl](http://man7.org/linux/man-pages/man2/epoll_ctl.2.html),由于epoll实现为一棵红黑树,epoll_ctl并不是一个很快的操作,特别在多核环境下,依赖epoll_ctl的实现往往会面临棘手的扩展性问题。non-blocking需要更大的缓冲,否则就会触发更多的事件而影响效率。non-blocking还得解决不少多线程问题,代码比blocking复杂很多。 + +# 收消息 + +“消息”指从连接读入的有边界的二进制串,可能是来自上游client的request或来自下游server的response。brpc使用一个或多个[EventDispatcher](https://github.com/apache/brpc/blob/master/src/brpc/event_dispatcher.h)(简称为EDISP)等待任一fd发生事件。和常见的“IO线程”不同,EDISP不负责读取。IO线程的问题在于一个线程同时只能读一个fd,当多个繁忙的fd聚集在一个IO线程中时,一些读取就被延迟了。多租户、复杂分流算法,[Streaming RPC](streaming_rpc.md)等功能会加重这个问题。高负载下常见的某次读取卡顿会拖慢一个IO线程中所有fd的读取,对可用性的影响幅度较大。 + +由于epoll的[一个bug](https://web.archive.org/web/20150423184820/https://patchwork.kernel.org/patch/1970231/)(开发brpc时仍有)及epoll_ctl较大的开销,EDISP使用Edge triggered模式。当收到事件时,EDISP给一个原子变量加1,只有当加1前的值是0时启动一个bthread处理对应fd上的数据。在背后,EDISP把所在的pthread让给了新建的bthread,使其有更好的cache locality,可以尽快地读取fd上的数据。而EDISP所在的bthread会被偷到另外一个pthread继续执行,这个过程即是bthread的work stealing调度。要准确理解那个原子变量的工作方式可以先阅读[atomic instructions](atomic_instructions.md),再看[Socket::StartInputEvent](https://github.com/apache/brpc/blob/master/src/brpc/socket.cpp)。这些方法使得brpc读取同一个fd时产生的竞争是[wait-free](http://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom)的。 + +在当前实现里,`Transport::ProcessEvent` 会按 `EventDispatcherUnsched()` 选择启动方式:返回 `false` 时走 `bthread_start_urgent`,返回 `true` 时走 `bthread_start_background`。此外,RDMA 在轮询模式与事件模式对 `last_msg` 的处理不同:`rdma_use_polling=false` 时不会在 `RdmaTransport::QueueMessage` 里处理 `last_msg`,轮询模式下会继续处理。并且在 `EventDispatcherUnsched()` 返回 `true` 时,`last_msg` 不会在当前执行流里直接处理,而是在新的 bthread 中执行。用户可以通过 `event_dispatcher_edisp_unsched` 来控制这一行为。 + +[InputMessenger](https://github.com/apache/brpc/blob/master/src/brpc/input_messenger.h)负责从fd上切割和处理消息,它通过用户回调函数理解不同的格式。Parse一般是把消息从二进制流上切割下来,运行时间较固定;Process则是进一步解析消息(比如反序列化为protobuf)后调用用户回调,时间不确定。若一次从某个fd读取出n个消息(n > 1),InputMessenger会启动n-1个bthread分别处理前n-1个消息,最后一个消息则会在原地被Process。InputMessenger会逐一尝试多种协议,由于一个连接上往往只有一种消息格式,InputMessenger会记录下上次的选择,而避免每次都重复尝试。 + +可以看到,fd间和fd内的消息都会在brpc中获得并发,这使brpc非常擅长大消息的读取,在高负载时仍能及时处理不同来源的消息,减少长尾的存在。 + +# 发消息 + +"消息”指向连接写出的有边界的二进制串,可能是发向上游client的response或下游server的request。多个线程可能会同时向一个fd发送消息,而写fd又是非原子的,所以如何高效率地排队不同线程写出的数据包是这里的关键。brpc使用一种wait-free MPSC链表来实现这个功能。所有待写出的数据都放在一个单链表节点中,next指针初始化为一个特殊值(Socket::WriteRequest::UNCONNECTED)。当一个线程想写出数据前,它先尝试和对应的链表头(Socket::_write_head)做原子交换,返回值是交换前的链表头。如果返回值为空,说明它获得了写出的权利,它会在原地写一次数据。否则说明有另一个线程在写,它把next指针指向返回的头以让链表连通。正在写的线程之后会看到新的头并写出这块数据。 + +这套方法可以让写竞争是wait-free的,而获得写权利的线程虽然在原理上不是wait-free也不是lock-free,可能会被一个值仍为UNCONNECTED的节点锁定(这需要发起写的线程正好在原子交换后,在设置next指针前,仅仅一条指令的时间内被OS换出),但在实践中很少出现。在当前的实现中,如果获得写权利的线程一下子无法写出所有的数据,会启动一个KeepWrite线程继续写,直到所有的数据都被写出。这套逻辑非常复杂,大致原理如下图,细节请阅读[socket.cpp](https://github.com/apache/brpc/blob/master/src/brpc/socket.cpp)。 + +![img](../images/write.png) + +由于brpc的写出总能很快地返回,调用线程可以更快地处理新任务,后台KeepWrite写线程也能每次拿到一批任务批量写出,在大吞吐时容易形成流水线效应而提高IO效率。 + +# Socket + +和fd相关的数据均在[Socket](https://github.com/apache/brpc/blob/master/src/brpc/socket.h)中,是rpc最复杂的结构之一,这个结构的独特之处在于用64位的SocketId指代Socket对象以方便在多线程环境下使用fd。常用的三个方法: + +- Create:创建Socket,并返回其SocketId。 +- Address:取得id对应的Socket,包装在一个会自动释放的unique_ptr中(SocketUniquePtr),当Socket被SetFailed后,返回指针为空。只要Address返回了非空指针,其内容保证不会变化,直到指针自动析构。这个函数是wait-free的。 +- SetFailed:标记一个Socket为失败,之后所有对那个SocketId的Address会返回空指针(直到健康检查成功)。当Socket对象没人使用后会被回收。这个函数是lock-free的。 + +可以看到Socket类似[shared_ptr](http://en.cppreference.com/w/cpp/memory/shared_ptr),SocketId类似[weak_ptr](http://en.cppreference.com/w/cpp/memory/weak_ptr),但Socket独有的SetFailed可以在需要时确保Socket不能被继续Address而最终引用计数归0,单纯使用shared_ptr/weak_ptr则无法保证这点,当一个server需要退出时,如果请求仍频繁地到来,对应Socket的引用计数可能迟迟无法清0而导致server无法退出。另外weak_ptr无法直接作为epoll的data,而SocketId可以。这些因素使我们设计了Socket,这个类的核心部分自14年完成后很少改动,非常稳定。 + +存储SocketUniquePtr还是SocketId取决于是否需要强引用。像Controller贯穿了RPC的整个流程,和Socket中的数据有大量交互,它存放的是SocketUniquePtr。epoll主要是提醒对应fd上发生了事件,如果Socket回收了,那这个事件是可有可无的,所以它存放了SocketId。 + +由于SocketUniquePtr只要有效,其中的数据就不会变,这个机制使用户不用关心麻烦的race condition和ABA problem,可以放心地对共享的fd进行操作。这种方法也规避了隐式的引用计数,内存的ownership明确,程序的质量有很好的保证。brpc中有大量的SocketUniquePtr和SocketId,它们确实简化了我们的开发。 + +事实上,Socket不仅仅用于管理原生的fd,它也被用来管理其他资源。比如SelectiveChannel中的每个Sub Channel都被置入了一个Socket中,这样SelectiveChannel可以像普通channel选择下游server那样选择一个Sub Channel进行发送。这个假Socket甚至还实现了健康检查。Streaming RPC也使用了Socket以复用wait-free的写出过程。 + +# The full picture + +![img](../images/rpc_flow.png) diff --git a/docs/cn/iobuf.md b/docs/cn/iobuf.md new file mode 100644 index 0000000..75c7ffc --- /dev/null +++ b/docs/cn/iobuf.md @@ -0,0 +1,103 @@ +[English version](../en/iobuf.md) + +brpc使用[butil::IOBuf](https://github.com/apache/brpc/blob/master/src/butil/iobuf.h)作为一些协议中的附件或http body的数据结构,它是一种非连续零拷贝缓冲,在其他项目中得到了验证并有出色的性能。IOBuf的接口和std::string类似,但不相同。 + +如果你之前使用Kylin中的BufHandle,你将更能感受到IOBuf的便利性:前者几乎没有实现完整,直接暴露了内部结构,用户得小心翼翼地处理引用计数,极易出错。 + +# IOBuf能做的: + +- 默认构造不分配内存。 +- 可以拷贝,修改拷贝不影响原IOBuf。拷贝的是IOBuf的管理结构而不是数据。 +- 可以append另一个IOBuf,不拷贝数据。 +- 可以append字符串,拷贝数据。 +- 可以从fd读取,可以写入fd。 +- 可以解析或序列化为protobuf messages. +- IOBufBuilder可以把IOBuf当std::ostream用。 + +# IOBuf不能做的: + +- 程序内的通用存储结构。IOBuf应保持较短的生命周期,以避免一个IOBuf锁定了多个block (8K each)。 + +# 切割 + +从source_buf头部切下16字节放入dest_buf: + +```c++ +source_buf.cut(&dest_buf, 16); // 当source_buf不足16字节时,切掉所有字节。 +``` + +从source_buf头部弹掉16字节: + +```c++ +source_buf.pop_front(16); // 当source_buf不足16字节时,清空它 +``` + +# 拼接 + +在尾部加入另一个IOBuf: + +```c++ +buf.append(another_buf); // no data copy +``` + +在尾部加入std::string + +```c++ +buf.append(str); // copy data of str into buf +``` + +# 解析 + +解析IOBuf为protobuf message + +```c++ +IOBufAsZeroCopyInputStream wrapper(&iobuf); +pb_message.ParseFromZeroCopyStream(&wrapper); +``` + +解析IOBuf为自定义结构 + +```c++ +IOBufAsZeroCopyInputStream wrapper(&iobuf); +CodedInputStream coded_stream(&wrapper); +coded_stream.ReadLittleEndian32(&value); +... +``` + +# 序列化 + +protobuf序列化为IOBuf + +```c++ +IOBufAsZeroCopyOutputStream wrapper(&iobuf); +pb_message.SerializeToZeroCopyStream(&wrapper); +``` + +用可打印数据创建IOBuf + +```c++ +IOBufBuilder os; +os << "anything can be sent to std::ostream"; +os.buf(); // IOBuf +``` + +# 打印 + +可直接打印至std::ostream. 注意这个例子中的iobuf必需只包含可打印字符。 + +```c++ +std::cout << iobuf << std::endl; +// or +std::string str = iobuf.to_string(); // 注意: 会分配内存 +printf("%s\n", str.c_str()); +``` + +# 性能 + +IOBuf有不错的综合性能: + +| 动作 | 吞吐 | QPS | +| ---------------------------------------- | ----------- | ------- | +| 文件读入->切割12+16字节->拷贝->合并到另一个缓冲->写出到/dev/null | 240.423MB/s | 8586535 | +| 文件读入->切割12+128字节->拷贝->合并到另一个缓冲->写出到/dev/null | 790.022MB/s | 5643014 | +| 文件读入->切割12+1024字节->拷贝->合并到另一个缓冲->写出到/dev/null | 1519.99MB/s | 1467171 | diff --git a/docs/cn/json2pb.md b/docs/cn/json2pb.md new file mode 100644 index 0000000..4442b12 --- /dev/null +++ b/docs/cn/json2pb.md @@ -0,0 +1,141 @@ +brpc支持json和protobuf间的**双向**转化,实现于[json2pb](https://github.com/apache/brpc/tree/master/src/json2pb/),json解析使用[rapidjson](https://github.com/miloyip/rapidjson)。此功能对pb2.x和3.x均有效。pb3内置了[转换json](https://developers.google.com/protocol-buffers/docs/proto3#json)的功能。 + +by design, 通过HTTP + json访问protobuf服务是对外服务的常见方式,故转化必须精准,转化规则列举如下。 + +## message + + 对应rapidjson Object, 以花括号包围,其中的元素会被递归地解析。 + +```protobuf +// protobuf +message Foo { + required string field1 = 1; + required int32 field2 = 2; +} +message Bar { + required Foo foo = 1; + optional bool flag = 2; + required string name = 3; +} + +// rapidjson +{"foo":{"field1":"hello", "field2":3},"name":"Tom" } +``` + +## repeated field + +对应rapidjson Array, 以方括号包围,其中的元素会被递归地解析,和message不同,每个元素的类型相同。 + +```protobuf +// protobuf +repeated int32 numbers = 1; + +// rapidjson +{"numbers" : [12, 17, 1, 24] } +``` + +特别的,针对仅有一个 `repeated` 类型成员的 `message`,序列化为 `json` 时支持直接序列化为数组,以简化包体。 + +```protobuf +// protobuf +message Foo { + repeated int32 numbers = 1; +} + +// rapidjson +[12, 17, 1, 24] +``` + +该特性默认为关闭状态,客户端在发送请求时,或服务端在发送回复时,可手动开启: +```c++ +brpc::Controller cntl; +cntl.set_pb_single_repeated_to_array(true); +``` + +## map + +满足如下条件的repeated MSG被视作json map : + +- MSG包含一个名为key的字段,类型为string,tag为1。 +- MSG包含一个名为value的字段,tag为2。 +- 不包含其他字段。 + +这种"map"的属性有: + +- 自然不能确保key有序或不重复,用户视需求自行检查。 +- 与protobuf 3.x中的map二进制兼容,故3.x中的map使用pb2json也会正确地转化为json map。 + +如果符合所有条件的repeated MSG并不需要被认为是json map,打破上面任一条件就行了: 在MSG中加入optional int32 this_message_is_not_map_entry = 3; 这个办法破坏了“不包含其他字段”这项,且不影响二进制兼容。也可以调换key和value的tag值,让前者为2后者为1,也使条件不再满足。 + +## integers + +rapidjson会根据值打上对应的类型标记,比如: + +* 对于3,rapidjson中的IsUInt, IsInt, IsUint64, IsInt64等函数均会返回true。 +* 对于-1,则IsUInt和IsUint64会返回false。 +* 对于5000000000,IsUInt和IsInt是false。 + +这使得我们不用特殊处理,转化代码就可以自动地把json中的UInt填入protobuf中的int64,而不是机械地认为这两个类型不匹配。相应地,转化代码自然能识别overflow和underflow,当出现时会转化失败。 + +```protobuf +// protobuf +int32 uint32 int64 uint64 + +// rapidjson +Int UInt Int64 UInt64 +``` + +## floating point + +json的整数类型也可以转至pb的浮点数类型。浮点数(IEEE754)除了普通数字外还接受"NaN", "Infinity", "-Infinity"三个字符串,分别对应Not A Number,正无穷,负无穷。 + +```protobuf +// protobuf +float double + +// rapidjson +Float Double Int Uint Int64 Uint64 +``` + +## enum + +enum可转化为整数或其名字对应的字符串,可由Pb2JsonOptions.enum_options控制。默认后者。 + +## string + +默认同名转化。但当json中出现非法C++变量名(pb的变量名规则)时,允许转化,规则是: + +`illegal-char <-> **_Z****_**` + +## bytes + +和string不同,可能包含\0的bytes默认以base64编码。 + +```protobuf +// protobuf +"Hello, World!" + +// json +"SGVsbG8sIFdvcmxkIQo=" +``` + +## bool + +对应json的true false + +## unknown fields + +unknown_fields → json目前不支持,未来可能支持。json → unknown_fields目前也未支持,即protobuf无法透传json中不认识的字段。原因在于protobuf真正的key是proto文件中每个字段后的数字: + +```protobuf +... +required int32 foo = 3; <-- the real key +... +``` + +这也是unknown_fields的key。当一个protobuf不认识某个字段时,其proto中必然不会有那个数字,所以没办法插入unknown_fields。 + +可行的方案有几种: + +- 确保被json访问的服务的proto文件最新。这样就不需要透传了,但越前端的服务越类似proxy,可能并不现实。 +- protobuf中定义特殊透传字段。比如名为unknown_json_fields,在解析对应的protobuf时特殊处理。此方案修改面广且对性能有一定影响,有明确需求时再议。 diff --git a/docs/cn/lalb.md b/docs/cn/lalb.md new file mode 100644 index 0000000..d295139 --- /dev/null +++ b/docs/cn/lalb.md @@ -0,0 +1,151 @@ +# 概述 + +LALB全称Locality-aware load balancing,是一个能把请求及时、自动地送到延时最低的下游的负载均衡算法,特别适合混合部署环境。该算法产生自DP系统,现已加入brpc! + +LALB可以解决的问题: + +- 下游的机器配置不同,访问延时不同,round-robin和随机分流效果不佳。 +- 下游服务和离线服务或其他服务混部,性能难以预测。 +- 自动地把大部分流量送给同机部署的模块,当同机模块出问题时,再跨机器。 +- 优先访问本机房服务,出问题时再跨机房。 + +**…** + +# 背景 + +最常见的分流算法是round robin和随机。这两个方法的前提是下游的机器和网络都是类似的,但在目前的线上环境下,特别是混部的产品线中,已经很难成立,因为: + +- 每台机器运行着不同的程序组合,并伴随着一些离线任务,机器的可用资源在持续动态地变化着。 +- 机器配置不同。 +- 网络延时不同。 + +这些问题其实一直有,但往往被OP辛勤的机器监控和替换给隐藏了。框架层面也有过一些努力,比如UB中的[WeightedStrategy](https://svn.baidu.com/public/trunk/ub/ub_client/ubclient_weightstrategy.h)是根据下游的cpu占用率来进行分流,但明显地它解决不了延时相关的问题,甚至cpu的问题也解决不了:因为它被实现为定期reload一个权值列表,可想而知更新频率高不了,等到负载均衡反应过来,一大堆请求可能都超时了。并且这儿有个数学问题:怎么把cpu占用率转为权值。假设下游差异仅仅由同机运行的其他程序导致,机器配置和网络完全相同,两台机器权值之比是cpu idle之比吗?假如是的,当我们以这个比例给两台机器分流之后,它们的cpu idle应该会更接近对吧?而这会导致我们的分流比例也变得接近,从而使两台机器的cpu idle又出现差距。你注意到这个悖论了吗?这些因素使得这类算法的实际效果和那两个基本算法没什么差距,甚至更差,用者甚少。 + +我们需要一个能自适应下游负载、规避慢节点的通用分流算法。 + +# Locality-aware + +在DP 2.0中我们使用了一种新的算法: Locality-aware load balancing,能根据下游节点的负载分配流量,还能快速规避失效的节点,在很大程度上,这种算法的延时也是全局最优的。基本原理非常简单: + +> 以下游节点的吞吐除以延时作为分流权值。 + +比如只有两台下游节点,W代表权值,QPS代表吞吐,L代表延时,那么W1 = QPS1 / L1和W2 = QPS2 / L2分别是这两个节点的分流权值,分流时随机数落入的权值区间就是流量的目的地了。 + +一种分析方法如下: + +- 稳定状态时的QPS显然和其分流权值W成正比,即W1 / W2 ≈ QPS1 / QPS2。 +- 根据分流公式又有:W1 / W2 = QPS1 / QPS2 * (L2 / L1)。 + +故稳定状态时L1和L2应当是趋同的。当L1小于L2时,节点1会更获得相比其QPS1更大的W1,从而在未来获得更多的流量,直到**其延时高于平均值或没有更多的流量。** + +注意这个算法并不是按照延时的比例来分流,不是说一个下游30ms,另一个60ms,它们的流量比例就是60 / 30。而是30ms的节点会一直获得流量直到它的延时高于60ms,或者没有更多流量了。以下图为例,曲线1和曲线2分别是节点1和节点2的延时与吞吐关系图,随着吞吐增大延时会逐渐升高,接近极限吞吐时,延时会飙升。左下的虚线标记了QPS=400时的延时,此时虽然节点1的延时有所上升,但还未高于节点2的基本延时(QPS=0时的延时),所以所有流量都会分给节点1,而不是按它们基本延时的比例(图中大约2:1)。当QPS继续上升达到1600时,分流比例会在两个节点延时相等时平衡,图中为9 : 7。很明显这个比例是高度非线性的,取决于不同曲线的组合,和单一指标的比例关系没有直接关联。在真实系统中,延时和吞吐的曲线也在动态变化着,分流比例更加动态。 + +![img](../images/lalb_1.png) + +我们用一个例子来看一下具体的分流过程。启动3台server,逻辑分别是sleep 1ms,2ms,3ms,对于client来说这些值就是延时。启动client(50个同步访问线程)后每秒打印的分流结果如下: + +![img](../images/lalb_2.png) + +S[n]代表第n台server。由于S[1]和S[2]的平均延时大于1ms,LALB会发现这点并降低它们的权值。它们的权值会继续下降,直到被算法设定的最低值拦住。这时停掉server,反转延时并重新启动,即逻辑分别为sleep 3ms,2ms,1ms,运行一段时候后分流效果如下: + +![img](../images/lalb_3.png) + +刚重连上server时,client还是按之前的权值把大部分流量都分给了S[0],但由于S[0]的延时从1ms上升到了3ms,client的qps也降到了原来的1/3。随着数据积累,LALB逐渐发现S[2]才是最快的,而把大部分流量切换了过去。同样的服务如果用rr或random访问,则qps会显著下降: + +"rr" or "random": ![img](../images/lalb_4.png) + +"la" : ![img](../images/lalb_5.png) + +真实的场景中不会有这么显著的差异,但你应该能看到差别了。 + +这有很多应用场景: + +- 如果本机有下游服务,LALB会优先访问这些最近的节点。比如CTR应用中有一个计算在1ms左右的receiver模块,被model模块访问,很多model和receiver是同机部署的,以前的分流算法必须走网络,使得receiver的延时开销较大(3-5ms),特别是在晚上由于离线任务起来,很不稳定,失败率偏高,而LALB会优先访问本机或最近的receiver模块,很多流量都不走网络了,成功率一下子提升了很多。 +- 如果同rack有下游服务,LALB也会优先访问,减少机房核心路由器的压力。甚至不同机房的服务可能不再需要隔离,LALB会优先走本机房的下游,当本机房下游出问题时再自动访问另一些机房。 + +但我们也不能仅看到“基本原理”,这个算法有它复杂的一面: + +- 传统的经验告诉我们,不能把所有鸡蛋放一个篮子里,而按延时优化不可避免地会把很多流量送到同一个节点,如果这个节点出问题了,我们如何尽快知道并绕开它。 +- 对吞吐和延时的统计都需要统计窗口,窗口越大数据越可信,噪声越少,但反应也慢了,一个异常的请求可能对统计值造不成什么影响,等我们看到统计值有显著变化时可能已经太晚了。 +- 我们也不能只统计已经回来的,还得盯着路上的请求,否则我们可能会向一个已经出问题(总是不回)的节点傻傻地浪费请求。 +- ”按权值分流”听上去好简单,但你能写出多线程和可能修改节点的前提下,在O(logN)时间内尽量不互斥的查找算法吗? + +这些问题可以归纳为以下几个方面。 + +## DoublyBufferedData + +LoadBalancer是一个读远多于写的数据结构:大部分时候,所有线程从一个不变的server列表中选取一台server。如果server列表真是“不变的”,那么选取server的过程就不用加锁,我们可以写更复杂的分流算法。一个方法是用读写锁,但当读临界区不是特别大时(毫秒级),读写锁并不比mutex快,而实用的分流算法不可能到毫秒级,否则开销也太大了。另一个方法是双缓冲,很多检索端用类似的方法实现无锁的查找过程,它大概这么工作: + +- 数据分前台和后台。 +- 检索线程只读前台,不用加锁。 +- 只有一个写线程:修改后台数据,切换前后台,睡眠一段时间,以确保老前台(新后台)不再被检索线程访问。 + +这个方法的问题在于它假定睡眠一段时间后就能避免和前台读线程发生竞争,这个时间一般是若干秒。由于多次写之间有间隔,这儿的写往往是批量写入,睡眠时正好用于积累数据增量。 + +但这套机制对“server列表”不太好用:总不能插入一个server就得等几秒钟才能插入下一个吧,即使我们用批量插入,这个"冷却"间隔多少会让用户觉得疑惑:短了担心安全性,长了觉得没有必要。我们能尽量降低这个时间并使其安全么? + +我们需要**写以某种形式和读同步,但读之间相互没竞争**。一种解法是,读拿一把thread-local锁,写需要拿到所有的thread-local锁。具体过程如下: + +- 数据分前台和后台。 +- 读拿到自己所在线程的thread-local锁,执行查询逻辑后释放锁。 +- 同时只有一个写:修改后台数据,切换前后台,**挨个**获得所有thread-local锁并立刻释放,结束后再改一遍新后台(老前台)。 + +我们来分析下这个方法的基本原理: + +- 当一个读正在发生时,它会拿着所在线程的thread-local锁,这把锁会挡住同时进行的写,从而保证前台数据不会被修改。 +- 在大部分时候thread-local锁都没有竞争,对性能影响很小。 +- 逐个获取thread-local锁并立刻释放是为了**确保对应的读线程看到了切换后的新前台**。如果所有的读线程都看到了新前台,写线程便可以安全地修改老前台(新后台)了。 + +其他特点: + +- 不同的读之间没有竞争,高度并发。 +- 如果没有写,读总是能无竞争地获取和释放thread-local锁,一般小于25ns,对延时基本无影响。如果有写,由于其临界区极小(拿到立刻释放),读在大部分时候仍能快速地获得锁,少数时候释放锁时可能有唤醒写线程的代价。由于写本身就是少数情况,读整体上几乎不会碰到竞争锁。 + +完成这些功能的数据结构是[DoublyBufferedData<>](https://github.com/apache/brpc/blob/master/src/butil/containers/doubly_buffered_data.h),我们常简称为DBD。brpc中的所有load balancer都使用了这个数据结构,使不同线程在分流时几乎不会互斥。而其他rpc实现往往使用了全局锁,这使得它们无法写出复杂的分流算法:否则分流代码将会成为竞争热点。 + +这个结构有广泛的应用场景: + +- reload词典。大部分时候词典都是只读的,不同线程同时查询时不应互斥。 +- 可替换的全局callback。像butil/logging.cpp支持配置全局LogSink以重定向日志,这个LogSink就是一个带状态的callback。如果只是简单的全局变量,在替换后我们无法直接删除LogSink,因为可能还有都写线程在用。用DBD可以解决这个问题。 + +## weight tree + +LALB的查找过程是按权值分流,O(N)方法如下:获得所有权值的和total,产生一个间于[0, total-1]的随机数R,逐个遍历权值,直到当前权值之和不大于R,而下一个权值之和大于R。 + +这个方法可以工作,也好理解,但当N达到几百时性能已经很差,这儿的主要因素是cache一致性:LALB是一个基于反馈的算法,RPC结束时信息会被反馈入LALB,被遍历的数据结构也一直在被修改。这意味着前台的O(N)读必须刷新每一行cacheline。当N达到数百时,一次查找过程可能会耗时百微秒,更别提更大的N了,LALB(将)作为brpc的默认分流算法,这个性能开销是无法接受的。 + +另一个办法是用完全二叉树。每个节点记录了左子树的权值之和,这样我们就能在O(logN)时间内完成查找。当N为1024时,我们最多跳转10次内存,总耗时可控制在1微秒内,这个性能是可接受的。这个方法的难点是如何和DoublyBufferedData结合。 + +- 我们不考虑不使用DoublyBufferedData,那样要么绕不开锁,要么写不出正确的算法。 +- 前台后必须共享权值数据,否则切换前后台时,前台积累的权值数据没法同步到后台。 +- “左子树权值之和”也被前后台共享,但和权值数据不同,它和位置绑定。比如权值结构的指针可能从位置10移动到位置5,但“左子树权值之和”的指针不会移动,算法需要从原位置减掉差值,而向新位置加上差值。 +- 我们不追求一致性,只要最终一致即可,这能让我们少加锁。这也意味着“权值之和”,“左子树权值之和”,“节点权值”未必能精确吻合,查找算法要能适应这一点。 + +最困难的部分是增加和删除节点,它们需要在整体上对前台查找不造成什么影响,详细过程请参考代码。 + +## base_weight + +QPS和latency使用一个循环队列统计,默认容量128。我们可以使用这么小的统计窗口,是因为inflight delay能及时纠正过度反应,而128也具备了一定的统计可信度。不过,这么计算latency的缺点是:如果server的性能出现很大的变化,那么我们需要积累一段时间才能看到平均延时的变化。就像上节例子中那样,server反馈延时后client需要积累很多秒的数据才能看到的平均延时的变化。目前我们并么有处理这个问题,因为真实生产环境中的server不太会像例子中那样跳变延时,大都是缓缓变慢。当集群有几百台机器时,即使我们反应慢点给个别机器少分流点也不会导致什么问题。如果在产品线中确实出现了性能跳变,并且集群规模不大,我们再处理这个问题。 + +权值的计算方法是base_weight = QPS * WEIGHT_SCALE / latency ^ p。其中WEIGHT_SCALE是一个放大系数,为了能用整数存储权值,又能让权值有足够的精度,类似定点数。p默认为2,延时的收敛速度大约为p=1时的p倍,选项quadratic_latency=false可使p=1。 + +权值计算在各个环节都有最小值限制,为了防止某个节点的权值过低而使其完全没有访问机会。即使一些延时远大于平均延时的节点,也应该有足够的权值,以确保它们可以被定期访问,否则即使它们变快了,我们也不会知道。 + +> 除了待删除节点,所有节点的权值绝对不会为0。 + +这也制造了一个问题:即使一个server非常缓慢(但没有断开连接),它的权值也不会为0,所以总会有一些请求被定期送过去而铁定超时。当qps不高时,为了降低影响面,探测间隔必须拉长。比如为了把对qps=1000的影响控制在1%%内,故障server的权值必须低至使其探测间隔为10秒以上,这降低了我们发现server变快的速度。这个问题的解决方法有: + +- 什么都不干。这个问题也许没有想象中那么严重,由于持续的资源监控,线上服务很少出现“非常缓慢”的情况,一般性的变慢并不会导致请求超时。 +- 保存一些曾经发向缓慢server的请求,用这些请求探测。这个办法的好处是不浪费请求。但实现起来耦合很多,比较麻烦。 +- 强制backup request。 +- 再选一次。 + +## inflight delay + +我们必须追踪还未结束的RPC,否则我们就必须等待到超时或其他错误发生,而这可能会很慢(超时一般会是正常延时的若干倍),在这段时间内我们可能做出了很多错误的分流。最简单的方法是统计未结束RPC的耗时: + +- 选择server时累加发出时间和未结束次数。 +- 反馈时扣除发出时间和未结束次数。 +- 框架保证每个选择总对应一次反馈。 + +这样“当前时间 - 发出时间之和 / 未结束次数”便是未结束RPC的平均耗时,我们称之为inflight delay。当inflight delay大于平均延时时,我们就线性地惩罚节点权值,即weight = base_weight * avg_latency / inflight_delay。当发向一个节点的请求没有在平均延时内回来时,它的权值就会很快下降,从而纠正我们的行为,这比等待超时快多了。不过这没有考虑延时的正常抖动,我们还得有方差,方差可以来自统计,也可简单线性于平均延时。不管怎样,有了方差bound后,当inflight delay > avg_latency + max(bound * 3, MIN_BOUND)时才会惩罚权值。3是正态分布中的经验数值。 diff --git a/docs/cn/load_balancing.md b/docs/cn/load_balancing.md new file mode 100644 index 0000000..689c1e8 --- /dev/null +++ b/docs/cn/load_balancing.md @@ -0,0 +1,47 @@ +上游一般通过命名服务发现所有的下游节点,并通过多种负载均衡方法把流量分配给下游节点。当下游节点出现问题时,它可能会被隔离以提高负载均衡的效率。被隔离的节点定期被健康检查,成功后重新加入正常节点。 + +# 命名服务 + +在brpc中,[NamingService](https://github.com/apache/brpc/blob/master/src/brpc/naming_service.h)用于获得服务名对应的所有节点。一个直观的做法是定期调用一个函数以获取最新的节点列表。但这会带来一定的延时(定期调用的周期一般在若干秒左右),作为通用接口不太合适。特别当命名服务提供事件通知时(比如zk),这个特性没有被利用。所以我们反转了控制权:不是我们调用用户函数,而是用户在获得列表后调用我们的接口,对应[NamingServiceActions](https://github.com/apache/brpc/blob/master/src/brpc/naming_service.h)。当然我们还是得启动进行这一过程的函数,对应NamingService::RunNamingService。下面以三个实现解释这套方式: + +- bns:没有事件通知,所以我们只能定期去获得最新列表,默认间隔是[5秒](http://brpc.baidu.com:8765/flags/ns_access_interval)。为了简化这类定期获取的逻辑,brpc提供了[PeriodicNamingService](https://github.com/apache/brpc/blob/master/src/brpc/periodic_naming_service.h) 供用户继承,用户只需要实现单次如何获取(GetServers)。获取后调用NamingServiceActions::ResetServers告诉框架。框架会对列表去重,和之前的列表比较,通知对列表有兴趣的观察者(NamingServiceWatcher)。这套逻辑会运行在独立的bthread中,即NamingServiceThread。一个NamingServiceThread可能被多个Channel共享,通过intrusive_ptr管理ownership。 +- file:列表即文件。合理的方式是在文件更新后重新读取。[该实现](https://github.com/apache/brpc/blob/master/src/brpc/policy/file_naming_service.cpp)使用[FileWatcher](https://github.com/apache/brpc/blob/master/src/butil/files/file_watcher.h)关注文件的修改时间,当文件修改后,读取并调用NamingServiceActions::ResetServers告诉框架。 +- list:列表就在服务名里(逗号分隔)。在读取完一次并调用NamingServiceActions::ResetServers后就退出了,因为列表再不会改变了。 + +如果用户需要建立这些对象仍然是不够方便的,因为总是需要一些工厂代码根据配置项建立不同的对象,鉴于此,我们把工厂类做进了框架,并且是非常方便的形式: + +``` +"protocol://service-name" + +e.g. +bns:// # baidu naming service +file:// # load addresses from the file +list://addr1,addr2,... # use the addresses separated by comma +http:// # Domain Naming Service, aka DNS. +``` + +这套方式是可扩展的,实现了新的NamingService后在[global.cpp](https://github.com/apache/brpc/blob/master/src/brpc/global.cpp)中依葫芦画瓢注册下就行了,如下图所示: + +![img](../images/register_ns.png) + +看到这些熟悉的字符串格式,容易联想到ftp:// zk:// galileo://等等都是可以支持的。用户在新建Channel时传入这类NamingService描述,并能把这些描述写在各类配置文件中。 + +# 负载均衡 + +brpc中[LoadBalancer](https://github.com/apache/brpc/blob/master/src/brpc/load_balancer.h)从多个服务节点中选择一个节点,目前的实现见[负载均衡](client.md#负载均衡)。 + +Load balancer最重要的是如何让不同线程中的负载均衡不互斥,解决这个问题的技术是[DoublyBufferedData](lalb.md#doublybuffereddata)。 + +和NamingService类似,我们使用字符串来指代一个load balancer,在global.cpp中注册: + +![img](../images/register_lb.png) + +# 健康检查 + +对于那些无法连接却仍在NamingService的节点,brpc会定期连接它们,成功后对应的Socket将被”复活“,并可能被LoadBalancer选择上,这个过程就是健康检查。注意:被健康检查或在LoadBalancer中的节点一定在NamingService中。换句话说,只要一个节点不从NamingService删除,它要么是正常的(会被LoadBalancer选上),要么在做健康检查。 + +传统的做法是使用一个线程做所有连接的健康检查,brpc简化了这个过程:为需要的连接动态创建一个bthread专门做健康检查(Socket::StartHealthCheck)。这个线程的生命周期被对应连接管理。具体来说,当Socket被SetFailed后,健康检查线程就可能启动(如果SocketOptions.health_check_interval为正数的话): + +- 健康检查线程先在确保没有其他人在使用Socket了后关闭连接。目前是通过对Socket的引用计数判断的。这个方法之所以有效在于Socket被SetFailed后就不能被Address了,所以引用计数只减不增。 +- 定期连接直到远端机器被连接上,在这个过程中,如果Socket析构了,那么该线程也就随之退出了。 +- 连上后复活Socket(Socket::Revive),这样Socket就又能被其他地方,包括LoadBalancer访问到了(通过Socket::Address)。 diff --git a/docs/cn/mbvar_c++.md b/docs/cn/mbvar_c++.md new file mode 100644 index 0000000..1ce1364 --- /dev/null +++ b/docs/cn/mbvar_c++.md @@ -0,0 +1,922 @@ +- [mbvar Introduction](#mbvar-introduction) +- [bvar::MVariables](#bvarmvariables) + - [expose](#expose) + - [expose](#expose-1) + - [expose_as](#expose_as) + - [Export all MVariable](#export-all-mvariable) + - [count](#count) + - [count_exposed](#count_exposed) + - [list](#list) + - [list_exposed](#list_exposed) +- [bvar::MultiDimension](#bvarmultidimension) + - [constructor](#constructor) + - [stats](#stats) + - [get_stats](#get_stats) + - [count](#count-1) + - [count_labels](#count_labels) + - [count_stats](#count_stats) + - [list](#list-1) + - [list_stats](#list_stats) + - [template](#template) + - [bvar::Adder](#bvaradder) + - [bvar::Maxer](#bvarmaxer) + - [bvar::Miner](#bvarminer) + - [bvar::IntRecorder](#bvarintrecorder) + - [bvar::LatencyRecorder](#bvarlatencyrecorder) + - [bvar::Status](#bvarstatus) + - [bvar::WindowEx](#bvarwindowex) + - [bvar::PerSecondEx](#bvarpersecondex) + +# mbvar Introduction + +多维度mbvar使用文档 + +mbvar中有两个类,分别是MVariable和MultiDimension,MVariable是多维度统计的基类,MultiDimension是派生模板类,目前支持如下几种类型: + +| bvar类型 | 说明 | +| ------ | ------ | +| bvar::Adder | 计数器,默认0,varname << N相当于varname += N。 | +| bvar::Maxer | 求最大值,默认std::numeric_limits::min(),varname << N相当于varname = max(varname, N)。 | +| bvar::Miner | 求最小值,默认std::numeric_limits::max(),varname << N相当于varname = min(varname, N)。 | +| bvar::IntRecorder | 求自使用以来的平均值。注意这里的定语不是“一段时间内”。一般要通过Window衍生出时间窗口内的平均值。 | +| bvar::LatencyRecorder | 专用于记录延时和qps的变量。输入延时,平均延时/最大延时/qps/总次数 都有了。 | +| bvar::Status | 记录和显示一个值,拥有额外的set_value函数。 | +| bvar::WindowEx | 获得之前一段时间内的统计值。WindowEx是独立存在的,不依赖其他的计数器,需要给它发送数据。 | +| bvar::PerSecondEx | 获得之前一段时间内平均每秒的统计值。PerSecondEx是独立存在的,不依赖其他的计数器,需要给它发送数据。 | + +例子: +```c++ +#include +#include + +namespace foo { +namespace bar { +// 定义一个全局多维度mbvar变量 +bvar::MultiDimension > g_request_count("request_count", {"idc", "method", "status"}); +// bvar::MultiDimension > g_request_count("foo_bar", "request_count", {"idc", "method", "status"}); + +int process_request(const std::list& request_label) { + // 获取request_label对应的单维度bvar指针,比如:request_label = {"tc", "get", "200"} + bvar::Adder* adder = g_request_count.get_stats(request_label); + // 判断指针非空 + if (!adder) { + return -1; + } + // adder只能在g_request_count的生命周期内访问,否则行为未定义,可能会出core + // 给adder输入一些值 + *adder << 1 << 2 <<3; + LOG(INFO) << "adder=" << *adder; // adder add up to 6 + ... + return 0; +} + +} // namespace bar +} // namespace foo +``` + +# bvar::MVariables +MVariale是MultiDimension(多维度统计)的基类,主要提供全局注册、列举、查询和dump等功能。 + +## expose + +### expose + +用户在创建mbvar变量(bvar::MultiDimension)的时候,如果使用一个参数的构造函数(共有三个构造函数),这个mbvar并未注册到任何全局结构中(当然也不会dump到本地文件),在这种情况下,mbvar纯粹是一个更快的多维度计数器。我们称把一个mbvar注册到全局表中的行为为“曝光”,可以通过expose函数曝光: + +```c++ +class MVariable { +public + ... + // Expose this mvariable globally so that it's counted in following + // functions: + // list_exposed + // count_exposed + // Return 0 on success, -1 otherwise. + int expose(const base::StringPiece& name); + int expose_as(const base::StringPiece& prefix, const base::StringPiece& name); +}; +``` + +全局曝光后的mbvar名字便为name或者prefix+name,可通过以_exposed为后缀的static函数查询。比如MVariable::describe_exposed(name)会返回名为name的mbvar的描述。 + +当相同名字的mbvar已存在时,expose会打印FATAL日志并返回-1。如果选项-bvar_abort_on_same_name设为true (默认是false),程序会直接abort。 + +下面是一些曝光mbvar的例子: +```c++ +#include +#include + +namespace foo { +namespace bar { +// 定义一个全局的多维度mbvar变量 +bvar::MultiDimension > g_request_count("request_count", {"idc", "method", "status"}); + +// 换一个名字 +g_request_count->expose("request_count_another"); + +int process_request(const std::list& request_label) { + // 获取request_label对应的单维度bvar指针,比如:request_label = {"tc", "get", "200"} + bvar::Adder* adder = g_request_count.get_stats(labels_value); + // 判断指针非空 + if (!adder) { + return -1; + } + + //adder只能在g_request_count的生命周期内访问,否则行为未定义,可能会出core + *adder << 10 << 20 << 30; // adder add up to 60 +} + +} // namespace bar +} // namespace foo +``` + +### expose_as + +为了避免重名,mbvar的名字应加上前缀,建议为\\_\\_\。为了方便使用,我们提供了expose_as函数,接收一个前缀。 +```c++ +class MVariable { +public + ... + // Expose this mvariable with a prefix. + // Example: + // namespace foo { + // namespace bar { + // + // bvar::MultiDimension > g_request_count("request_count", {"idc", "method", "status"}); + // g_request_count.expose_as("foo_bar", "request_count"); + // ... + // + // } // foo + // } // bar + int expose_as(const base::StringPiece& prefix, const base::StringPiece& name); +}; +``` + +## Export all MVariable +提供dump_exposed函数导出进程中所有已曝光的mbvar: + +```c++ +// Implement this class to write mvariables into different places. +// If dump() returns false, MVariable::dump_exposed() skip and continue dump. +class Dumper { +public: + virtual bool dump(const std::string& name, const base::StringPiece& description) = 0; +}; + +// Options for MVariable::dump_exposed(). +struct DumpOptions { + // Contructed with default options. + DumpOptions(); + // If this is true, string-type values will be quoted. + bool quote_string; + // The ? in wildcards. Wildcards in URL need to use another character + // because ? is reserved. + char question_mark; // 目前不支持 + // Separator for white_wildcards and black_wildcards. + char wildcard_separator; // 目前不支持 + // Name matched by these wildcards (or exact names) are kept. + std::string white_wildcards; // 目前不支持 + // Name matched by these wildcards (or exact names) are skipped. + std::string black_wildcards; // 目前不支持 +}; + +class MVariable { + ... + ... + // Find all exposed mvariables and send them to `dumper'. + // Use default options when `options' is NULL. + // Return number of dumped mvariables, -1 on error. + static size_t dump_exposed(Dumper* dumper, const DumpOptions* options); +}; +``` + +最常见的导出需求是通过HTTP接口查询和写入本地文件,多维度统计支持通过内置服务`/brpc_metrics`接口方式查询。也支持写入本地文件,该功能由下面5个gflags控制,你的程序需要使用gflags。 + +| 名称 | 默认值 | 描述 | +| ------ | ------ | ------ | +| mbvar_dump |false | Create a background thread dumping(shares the same thread as bvar_dump) all mbvar periodically, all bvar_dump_* flags are not effective when this flag is off | +| mbvar_dump_file | monitor/mbvar.\.data | Dump mbvar into this file | +| mbvar_dump_format | common | Dump mbvar write format
common:文本格式,Key和Value用冒号分割(和目前的单维度dump文件格式一致)

prometheus:文本格式,Key和Value用空格分开protobuf:二进制格式,暂时不支持| +| bvar_dump_interval | 10 |Seconds between consecutive dump | +| mbvar_dump_prefix | \ | Every dumped name starts with this prefix | +| bvar_max_dump_multi_dimension_metric_number | 0 | 最多导出的mbvar的bvar个数,默认是0,即不导出任何mbvar | + +用户可在程序启动前加上对应的gflags。 + +>当mbvar_dump=true并且mbvar_dump_file不为空时,程序会启动一个后台导出线程以bvar_dump_interval指定的间隔更新mbvar_dump_file,其中包含所有的多维统计项mbvar。 + +比如我们把所有的gflags修改为下表: +| 名称 | 默认值 | 描述 | +| ------ | ------ | ------ | +| mbvar_dump |true | Create a background thread dumping(shares the same thread as bvar_dump) all mbvar periodically, all bvar_dump_* flags are not effective when this flag is off | +| mbvar_dump_file | monitor/mbvar.\.data | Dump mbvar into this file | +| mbvar_dump_format | common | Dump mbvar write format
common:文本格式,Key和Value用冒号分割(和目前的单维度dump文件格式一致)

prometheus:文本格式,Key和Value用空格分开protobuf:二进制格式,暂时不支持| +| bvar_dump_interval | 10 |Seconds between consecutive dump | +| mbvar_dump_prefix | mbvar | Every dumped name starts with this prefix | +| bvar_max_dump_multi_dimension_metric_number | 2000 | 最多导出的mbvar的bvar个数,默认是0,即不导出任何mbvar | + +导出的本地文件为monitor/mbvar.\.data: +``` +mbvar_test_concurrent_write_and_read{idc=idc3,method=method6,status=status40} : 76896 +mbvar_test_concurrent_write_and_read{idc=idc5,method=method6,status=status36} : 147910 +mbvar_test_concurrent_write_and_read{idc=idc5,method=method12,status=status23} : 209270 +mbvar_test_concurrent_write_and_read{idc=idc3,method=method1,status=status6} : 116325 +mbvar_test_concurrent_write_and_read{idc=idc5,method=method10,status=status29} : 193536 +mbvar_test_concurrent_write_and_read{idc=idc5,method=method15,status=status6} : 294726 +mbvar_test_concurrent_write_and_read{idc=idc3,method=method2,status=status13} : 136676 +mbvar_test_concurrent_write_and_read{idc=idc2,method=method5,status=status49} : 43203 +mbvar_test_concurrent_write_and_read{idc=idc5,method=method10,status=status1} : 312499 +mbvar_test_concurrent_write_and_read{idc=idc1,method=method10,status=status35} : 35698 +``` + +如果你的程序没有使用brpc,仍需要动态修改gflags(一般不需要),可以调用google::SetCommandLineOption(),如下所示: + +```c++ +#include +... +if (google::SetCommandLineOption("mbvar_dump_format", "prometheus").empty()) { + LOG(ERROR) << "Fail to set mbvar_dump_format"; + return -1; +} +LOG(INFO) << "Successfully set mbvar_dump_format to prometheus"; +``` + +>请勿直接设置 FLAGS_mbvar_dump_file / FLAGS_mbvar_dump_format / FLAGS_bvar_dump_prefix,如果有需求可以调用google::SetCommandLineOption()方法进行修改。 + +一方面这些gflag类型都是std::string,直接覆盖是线程不安全的;另一方面不会触发validator(检查正确性的回调),所以也不会启动后台导出线程。 + + +## count +统计相关函数 +```c++ +class MVariable { +public: + ... + // Get number of exposed mvariables + static size_t count_exposed(); +}; +``` + +### count_exposed + +获取目前已经曝光的多维度统计项mbvar的个数,注意:这里是多维度统计项(多维度mbvar变量),而不是维度数。 +```c++ +#include +#include + +namespace foo { +namespace bar { +// 定义一个全局的多维度mbvar变量 +bvar::MultiDimension > g_request_count("request_count", {"idc", "method", "status"}); + +// 定义另一个全局多维度mbvar变量 +bvar::MultiDimension > g_psmi_count("psmi_count", {"product", "system", "module", "interface"}); + +size_t count_exposed() { + size_t mbvar_count_exposed = bvar::MVariable::count_exposed(); + CHECK_EQ(2, mbvar_count_exposed); + return mbvar_count_exposed; +} + +} // namespace bar +} // namespace foo +``` + +使用说明 +> 一般情况下用不到count_系列统计函数,如果有特殊需求,也不建议频繁调用。 + + +## list +```c++ +class MVariable { +public: + ... + // Put names of all exposed mbvariable into `names' + static size_t list_exposed(std::vector* names); +}; +``` + +### list_exposed +获取所有曝光的多维度统计项mbvar名称 +```c++ +#include +#include + +namespace foo { +namespace bar { +// 定义一个全局的多维度mbvar变量 +bvar::MultiDimension > g_request_count("request_count", {"idc", "method", "status"}); + +// 定义另一个全局多维度mbvar变量 +bvar::MultiDimension > g_psmi_count("psmi_count", {"product", "system", "module", "interface"}); + +size_t mbvar_list_exposed(std::vector* names) { + if (!names) { + return -1; + } + + // clear + names.clear(); + bvar::MVariable::list_exposed(names); + // names:[ "request_count", "psmi_count" ] + CHECK_EQ(2, names->size()); + return names->size(); +} + +} // namespace bar +} // namespace foo +``` + +# bvar::MultiDimension + +多维度统计的实现,主要提供bvar的获取、列举等功能。 + +为了兼容旧的用法,KeyType默认类型是std::list。KeyType必须是(STL或者自定义)容器,value_type必须是std::string。 + +## constructor + +有三个构造函数: +```c++ +template > +class MultiDimension : public MVariable { +public: + // 不建议使用 + explicit MultiDimension(const key_type& labels); + + // 推荐使用 + MultiDimension(const base::StringPiece& name, + const key_type& labels); + // 推荐使用 + MultiDimension(const base::StringPiece& prefix, + const base::StringPiece& name, + const key_type& labels); + ... +}; +``` + +**explicit MultiDimension(const key_type& labels)** + +* ~~不建议使用~~ +* 不会“曝光”多维度统计变量(mbvar),即没有注册到任何全局结构* 中 +* 不会dump到本地文件,即使-bvar_dump=true、-mbvar_dump_file不为空 +* mbvar纯粹是一个更快的多维度计数器 + +**MultiDimension(const base::StringPiece& name, const key_type& labels)** + +* **推荐使用** +* 会曝光(调用MVariable::expose(name)),也会注册到全局结构中 +* -bvar_dump=true时,会启动一个后台导出线程以bvar_dump_interval指定的时间间隔更新mbvar_dump_file文件 + +```c++ +#include +#include + +namespace foo { +namespace bar { +// 定义一个全局的多维度mbvar变量 +bvar::MultiDimension > g_request_count("request_count", {"idc", "method", "status"}); + +} // namespace bar +} // namespace foo +``` + +**MultiDimension(const base::StringPiece& prefix, const base::StringPiece& name, const key_type& labels)** +* **推荐使用** +* 会曝光(调用MVariable::expose_as(prefix, name)),也会注册到全局结构中 +* -bvar_dump=true时,会启动一个后台导出线程以bvar_dump_interval指定的时间间隔更新mbvar_dump_file文件 + +```c++ +#include +#include + +namespace foo { +namespace bar { +// 定义一个全局的多维度mbvar变量 +bvar::MultiDimension > g_request_count("foo_bar", "request_count", {"idc", "method", "status"}); + +} // namespace bar +} // namespace foo +``` + +## stats +```c++ +template > +class MultiDimension : public MVariable { +public: + ... + + // Get real bvar pointer object + // Return real bvar pointer(Not NULL) on success, NULL otherwise. + T* get_stats(const std::list& labels_value); +}; +``` + +### get_stats + +根据指定label获取对应的单维度统计项bvar。 + +get_stats除了支持(默认)std::list参数类型,也支持自定义参数类型,满足以下条件: +1. (STL或者自定义)容器。 +2. K::value_type支持通过operator std::string()转换为std::string和通过operator butil::StringPiece()转换为butil::StringPiece。 +3. K::value_type支持和std::string进行比较。 + +推荐使用不需要分配内存的容器(例如,std::array、absl::InlinedVector)和不需要拷贝字符串的数据结构(例如,const char*、std::string_view、butil::StringPieces),可以提高性能。 + +bvar::MultiDimension的模板参数Shared,默认为false: +1. 如果Shared等于false,get_stats返回模板参数T的指针,例如bvar::Adder*。 +2. 如果Shared等于true,get_stats返回模板参数T的shared_ptr,例如std::shared_ptr\\>。 + +**注意**:因为shared_ptr的开销,Shared等于true的性能会比Shared等于false的性能差一些。 + +```c++ +#include +#include + +namespace foo { +namespace bar { +// 定义一个全局的多维度mbvar变量 +bvar::MultiDimension > g_request_count("request_count", {"idc", "method", "status"}); + +template +int get_request_count(const K& request_label) { + // 获取request_label对应的单维度bvar指针,比如:request_label = {"tc", "get", "200"} + bvar::Adder *request_adder = g_request_count.get_stats(request_label); + // 判断指针非空 + if (!request_adder) { + return -1; + } + + // request_adder只能在g_request_count的生命周期内访问,否则行为未定义,可能会出core + *request_adder << 1; + return request_adder->get_value(); +} + +std::list request_label_list = {"tc", "get", "200"}; +int request_count = get_request_count(request_label_list); + +std::vector request_label_list = {"tc", "get", "200"}; +int request_count = get_request_count(request_label_list); + +std::vector request_label_list = {"tc", "get", "200"}; +int request_count = get_request_count(request_label_list); + +class MyStringView { +public: + MyStringView() : _ptr(NULL), _len(0) {} + MyStringView(const char* str) + : _ptr(str), + _len(str == NULL ? 0 : strlen(str)) {} +#if __cplusplus >= 201703L + MyStringView(const std::string_view& str) + : _ptr(str.data()), _len(str.size()) {} +#endif // __cplusplus >= 201703L + MyStringView(const std::string& str) + : _ptr(str.data()), _len(str.size()) {} + MyStringView(const char* offset, size_t len) + : _ptr(offset), _len(len) {} + + const char* data() const { return _ptr; } + size_t size() const { return _len; } + + // Converts to `std::basic_string`. + explicit operator std::string() const { + if (NULL == _ptr) { + return {}; + } + return {_ptr, size()}; + } + + // Converts to butil::StringPiece. + explicit operator butil::StringPiece() const { + if (NULL == _ptr) { + return {}; + } + return {_ptr, size()}; + } + +private: + const char* _ptr; + size_t _len; +}; + +bool operator==(const MyStringView& x, const std::string& y) { + if (x.size() != y.size()) { + return false; + } + return butil::StringPiece::wordmemcmp(x.data(), y.data(), x.size()) == 0; +} +bool operator==(const std::string& x, const MyStringView& y) { + if (x.size() != y.size()) { + return false; + } + return butil::StringPiece::wordmemcmp(x.data(), y.data(), x.size()) == 0; +} + +std::vector request_label_list = {"tc", "get", "200"}; +int request_count = get_request_count(request_label_list); + +} // namespace bar +} // namespace foo +``` + +**内部实现逻辑** + +判断该label是否已经存在对应的单维度统计项bvar: + +* 存在 + * return bvar +* 不存在 + * new bvar() + * store(bvar) + * return bvar + +### delete_stats + +根据指定label删除对应的单维度统计项bvar。 + +bvar::MultiDimension的模板参数Shared,默认为false: +1. 如果Shared等于false,get_stats返回的是模板参数T的指针,delete_stats无法保证没有使用者,所以无法安全删除bvar。 +2. 如果Shared等于true,get_stats返回的是模板参数T的shared_ptr,delete_stats可以安全删除bvar。 + +### clear_stats + +清理所有单维度统计项bvar。 + +安全性同样受bvar::MultiDimension的模板参数Shared的影响,见delete_stats一节说明。 + +**bvar的生命周期** + +label对应的单维度统计项bvar存储在多维度统计项(mbvar)中,当mbvar析构的时候会释放自身所有bvar,所以用户必须保证在mbvar的生命周期之内操作bvar,在mbvar生命周期外访问bvar的行为未定义,极有可能出core。 + +## count +```c++ +class MVariable { +public: + ... + + // Get number of mvariable labels + size_t count_labels() const; +}; + +template > +class MultiDimension : public MVariable { +public: + ... + + // Get number of stats + size_t count_stats(); +}; +``` + +### count_labels + +获取多维度统计项的labels个数,用户在创建多维度(bvar::MultiDimension)统计项的时候,需要提供类型为std::list\的labels变量,我们提供了count_labels函数,返回labels的长度。 + +```c++ +#include +#include + +namespace foo { +namespace bar { +// 定义一个全局的多维度mbvar变量 +bvar::MultiDimension > g_request_count("request_count", {"idc", "method", "status"}); + +size_t count_labels() { + size_t mbvar_count_labels = g_request_count.count_labels(); + CHECK_EQ(3, mbvar_count_labels); + return mbvar_count_labels; +} +} // namespace bar +} // namespace foo +``` + +### count_stats + +获取多维度(bvar::MultiDimension)统计项的维度(stats)数 +```c++ +#include +#include + +namespace foo { +namespace bar { +// 定义一个全局的多维度mbvar变量 +bvar::MultiDimension > g_request_count("request_count", {"idc", "method", "status"}); + +size_t count_stats() { + // 获取request1对应的单维度mbvar指针,假设request1_labels = {"tc", "get", "200"} + bvar::Adder *request1_adder = g_request_count.get_stats(request1_labels); + // 判断指针非空 + if (!request1_adder) { + return -1; + } + // request1_adder只能在g_request_count生命周期内访问,否则行为未定义,可能会出core + *request1_adder << 1; + + // 获取request2对应的单维度mbvar指针,假设request2_labels = {"nj", "get", "200"} + bvar::Adder *request2_adder = g_request_count.get_stats(request2_labels); + // 判断指针非空 + if (!request2_adder) { + return -1; + } + // request2_adder只能在g_request_count生命周期内访问,否则行为未定义,可能会出core + *request2_adder << 1; + + + size_t mbvar_count_stats = g_request_count.count_stats(); + CHECK_EQ(2, mbvar_count_stats); + return mbvar_count_stats; +} +} // namespace bar +} // namespace foo +``` + +## list +```c++ +template > +class MultiDimension : public MVariable { +public: + ... + // Put all stats labels into `names' + void list_stats(std::vector >* names); +}; +``` + +### list_stats +获取一个多维度统计项下所有labels组合列表 + +```c++ +#include +#include + +namespace foo { +namespace bar { +// 定义一个全局的多维度mbvar变量 +bvar::MultiDimension > g_request_count("request_count", {"idc", "method", "status"}); + +size_t list_stats(std::vector > *stats_names) { + if (!stats_names) { + return -1; + } + + // clear + stats_names.clear(); + + // 获取request1对应的单维度mbvar指针,假设request1_labels = {"tc", "get", "200"} + bvar::Adder *request1_adder = g_request_count.get_stats(request1_labels); + // 判断指针非空 + if (!request1_adder) { + return -1; + } + + // 获取request2对应的单维度mbvar指针,假设request2_labels = {"nj", "get", "200"} + bvar::Adder *request2_adder = g_request_count.get_stats(request2_labels); + // 判断指针非空 + if (!request2_adder) { + return -1; + } + + g_request_count.list_stats(stats_names); + // labels_names: + // [ + // {"tc", "get", "200"}, + // {"nj", "get", "200"} + // ] + + CHECK_EQ(2, stats_names.size()); + return stats_names.size(); +} + +} // namespace bar +} // namespace foo +``` + +**使用说明** + +一般情况下用户不需要获取labels组合列表,如果有特殊需求,也不建议频繁调用,否则可能影响get_stats的写入性能。 + +## template + +### bvar::Adder +顾名思义,用于累加 +```c++ +#include +#include + +namespace foo { +namespace bar { +// 定义一个全局的多维度mbvar变量 +bvar::MultiDimension > g_request_cost("request_count", {"idc", "method", "status"}); + +int request_cost_total(const std::list& request_labels) { + // 获取request对应的单维度mbvar指针,假设request_labels = {"tc", "get", "200"} + bvar::Adder* cost_add = g_request_cost.get_stats(request_labels); + // 判断指针非空 + if (!cost_add) { + return -1; + } + // cost_add只能在g_request_cost生命周期内访问,否则行为未定义,可能会出core + *cost_add << 1 << 2 << 3 << 4; + CHECK_EQ(10, cost_add->get_value()); + return cost_add->get_value(); +} + +} // namespace bar +} // namespace foo +``` + +### bvar::Maxer +用于取最大值,运算符为std::max +```c++ +#include +#include + +namespace foo { +namespace bar { +// 定义一个全局的多维度mbvar变量 +bvar::MultiDimension > g_request_cost("request_cost", {"idc", "method", "status"}); + +int request_cost_max(const std::list& request_labels) { + // 获取request对应的单维度mbvar指针,假设request_labels = {"tc", "get", "200"} + bvar::Maxer* cost_max = g_request_cost.get_stats(request_labels); + // 判断指针非空 + if (!cost_max) { + return -1; + } + + // cost_max只能在g_request_cost生命周期内访问,否则行为未定义,可能会出core + *cost_max << 1 << 2 << 3 << 4; + CHECK_EQ(4, cost_max->get_value()); + return cost_max->get_value(); +} + +} // namespace bar +} // namespace foo +``` + +### bvar::Miner +用于取最小值,运算符为std::min +```c++ +#include +#include + +namespace foo { +namespace bar { +// 定义一个全局的多维度mbvar变量 +bvar::MultiDimension > g_request_cost("request_cost", {"idc", "method", "status"}); + +int request_cost_min(const std::list& request_labels) { + // 获取request对应的单维度mbvar指针,假设request_labels = {"tc", "get", "200"} + bvar::Miner* cost_min = g_request_cost.get_stats(request_labels); + // 判断指针非空 + if (!cost_min) { + return -1; + } + + // cost_min只能在g_request_cost生命周期内访问,否则行为未定义,可能会出core + *cost_min << 1 << 2 << 3 << 4; + CHECK_EQ(1, cost_min->get_value()); + return cost_min->get_value(); +} + +} // namespace bar +} // namespace foo +``` + +### bvar::IntRecorder +用于计算平均值。 +```c++ +#include +#include + +namespace foo { +namespace bar { +// 定义一个全局的多维度mbvar变量 +bvar::MultiDimension g_request_cost("request_cost", {"idc", "method", "status"}); + +int request_cost_avg(const std::list& request_labels) { + // 获取request对应的单维度mbvar指针,假设request_labels = {"tc", "get", "200"} + bvar::IntRecorder* cost_avg = g_request_cost.get_stats(request_labels); + // 判断指针非空 + if (!cost_avg) { + return -1; + } + + // cost_avg只能在g_request_cost生命周期内访问,否则行为未定义,可能会出core + *cost_avg << 1 << 3 << 5; + CHECK_EQ(3, cost_avg->get_value()); + return cost_avg->get_value(); +} + +} // namespace bar +} // namespace foo +``` + +### bvar::LatencyRecorder +专用于计算latency和qps的计数器。只需填入latency数据,就能获取latency / max_latency / qps / count,统计窗口是bvar_dump_interval。 +```c++ +#include +#include + +namespace foo { +namespace bar { +// 定义一个全局的多维度mbvar变量 +bvar::MultiDimension g_request_cost("request_cost", {"idc", "method", "status"}); + +void request_cost_latency(const std::list& request_labels) { + // 获取request对应的单维度mbvar指针,假设request_labels = {"tc", "get", "200"} + bvar::LatencyRecorder* cost_latency = g_request_cost.get_stats(request_labels); + // 判断指针非空 + if (!cost_latency) { + return -1; + } + + // cost_latency只能在g_request_cost生命周期内访问,否则行为未定义,可能会出core + *cost_latency << 1 << 2 << 3 << 4 << 5 << 6 << 7; + + // 获取latency + int64_t request_cost_latency = cost_latency->latency(); + // 获取max_latency + int64_t request_cost_max_latency = cost_latency->max_latency(); + // 获取qps + int64_t request_cost_qps = cost_latency->qps(); + // 获取count + int64_t request_cost_count = cost_latency->count(); +} + +} // namespace bar +} // namespace foo +``` + +### bvar::Status +记录和显示一个值,拥有额外的set_value函数。 +```c++ +#include +#include + +namespace foo { +namespace bar { +// 定义一个全局的多维度mbvar变量 +bvar::MultiDimension > g_request_cost("request_cost", {"idc", "method", "status"}); + +void request_cost(const std::list& request_labels) { + // 获取request对应的单维度mbvar指针,假设request_labels = {"tc", "get", "200"} + bvar::Status* cost_status = g_request_cost.get_stats(request_labels); + // 判断指针非空 + if (!cost_status) { + return -1; + } + + // cost_status只能在g_request_cost生命周期内访问,否则行为未定义,可能会出core + cost_status->set_value(5); + CHECK_EQ(5, cost_status->get_value()); +} + +} // namespace bar +} // namespace foo +``` + +### bvar::WindowEx +获得之前一段时间内的统计值。 +```c++ +#include +#include +#include + +namespace foo { +namespace bar { +// 定义一个全局的多维度mbvar变量 +bvar::MultiDimension, 60>> sum_minute("sum_minute", {"idc", "method", "status"}); + +void Record(const std::list& request_labels, int num) { + // 获取request对应的单维度mbvar指针,假设request_labels = {"tc", "get", "200"} + bvar::WindowEx, 60>* status = sum_minute.get_stats(request_labels); + // status只能在sum_minute生命周期内访问,否则行为未定义,可能会出core + *status << num; +} + +} // namespace bar +} // namespace foo +``` + +### bvar::PerSecondEx +获得之前一段时间内平均每秒的统计值。 +```c++ +#include +#include +#include + +namespace foo { +namespace bar { +// 定义一个全局的多维度mbvar变量 +bvar::MultiDimension>> sum_per_second("sum_per_second", {"idc", "method", "status"}); + +void Record(const std::list& request_labels, int num) { + // 获取request对应的单维度mbvar指针,假设request_labels = {"tc", "get", "200"} + bvar::PerSecondEx>* status = sum_per_second.get_stats(request_labels); + // status只能在sum_per_second生命周期内访问,否则行为未定义,可能会出core + *status << num; +} + +} // namespace bar +} // namespace foo +``` diff --git a/docs/cn/memcache_client.md b/docs/cn/memcache_client.md new file mode 100644 index 0000000..2e7a8f6 --- /dev/null +++ b/docs/cn/memcache_client.md @@ -0,0 +1,102 @@ +[English version](../en/memcache_client.md) + +[memcached](http://memcached.org/)是常用的缓存服务,为了使用户更快捷地访问memcached并充分利用bthread的并发能力,brpc直接支持memcache协议。示例程序:[example/memcache_c++](https://github.com/apache/brpc/tree/master/example/memcache_c++/) + +**注意**:brpc只支持memcache的二进制协议。memcached在1.3前只有文本协议,但在当前看来支持的意义甚微。如果你的memcached早于1.3,升级版本。 + +相比使用[libmemcached](http://libmemcached.org/libMemcached.html)(官方client)的优势有: + +- 线程安全。用户不需要为每个线程建立独立的client。 +- 支持同步、异步、半同步等访问方式,能使用[ParallelChannel等](combo_channel.md)组合访问方式。 +- 支持多种[连接方式](client.md#连接方式)。支持超时、backup request、取消、tracing、内置服务等一系列brpc提供的福利。 +- 有明确的request和response。而libmemcached是没有的,收到的消息不能直接和发出的消息对应上,用户得做额外开发,而且并没有那么容易做对。 + +当前实现充分利用了RPC的并发机制并尽量避免了拷贝。一个client可以轻松地把一个同机memcached实例(版本1.4.15)压到极限:单连接9万,多连接33万。在大部分情况下,brpc client能充分发挥memcached的性能。 + +# 访问单台memcached + +创建一个访问memcached的Channel: + +```c++ +#include +#include + +brpc::ChannelOptions options; +options.protocol = brpc::PROTOCOL_MEMCACHE; +if (channel.Init("0.0.0.0:11211", &options) != 0) { // 11211是memcached的默认端口 + LOG(FATAL) << "Fail to init channel to memcached"; + return -1; +} +... +``` + +往memcached中设置一份数据。 + +```c++ +// 写入key="hello" value="world" flags=0xdeadbeef,10秒失效,无视cas。 +brpc::MemcacheRequest request; +brpc::MemcacheResponse response; +brpc::Controller cntl; +if (!request.Set("hello", "world", 0xdeadbeef/*flags*/, 10/*expiring seconds*/, 0/*ignore cas*/)) { + LOG(FATAL) << "Fail to SET request"; + return -1; +} +channel.CallMethod(NULL, &cntl, &request, &response, NULL/*done*/); +if (cntl.Failed()) { + LOG(FATAL) << "Fail to access memcached, " << cntl.ErrorText(); + return -1; +} +if (!response.PopSet(NULL)) { + LOG(FATAL) << "Fail to SET memcached, " << response.LastError(); + return -1; +} +... +``` + +上述代码的说明: + +- 请求类型必须为MemcacheRequest,回复类型必须为MemcacheResponse,否则CallMethod会失败。不需要stub,直接调用channel.CallMethod,method填NULL。 +- 调用request.XXX()增加操作,本例XXX=Set,一个request多次调用不同的操作,这些操作会被同时送到memcached(常被称为pipeline模式)。 +- 依次调用response.PopXXX()弹出操作结果,本例XXX=Set,成功返回true,失败返回false,调用response.LastError()可获得错误信息。XXX必须和request的依次对应,否则失败。本例中若用PopGet就会失败,错误信息为“not a GET response"。 +- Pop结果独立于RPC结果。即使“不能把某个值设入memcached”,RPC可能还是成功的。RPC失败指连接断开,超时之类的。如果业务上认为要成功操作才算成功,那么你不仅要判RPC成功,还要判PopXXX是成功的。 + +目前支持的请求操作有: + +```c++ +bool Set(const Slice& key, const Slice& value, uint32_t flags, uint32_t exptime, uint64_t cas_value); +bool Add(const Slice& key, const Slice& value, uint32_t flags, uint32_t exptime, uint64_t cas_value); +bool Replace(const Slice& key, const Slice& value, uint32_t flags, uint32_t exptime, uint64_t cas_value); +bool Append(const Slice& key, const Slice& value, uint32_t flags, uint32_t exptime, uint64_t cas_value); +bool Prepend(const Slice& key, const Slice& value, uint32_t flags, uint32_t exptime, uint64_t cas_value); +bool Delete(const Slice& key); +bool Flush(uint32_t timeout); +bool Increment(const Slice& key, uint64_t delta, uint64_t initial_value, uint32_t exptime); +bool Decrement(const Slice& key, uint64_t delta, uint64_t initial_value, uint32_t exptime); +bool Touch(const Slice& key, uint32_t exptime); +bool Version(); +``` + +对应的回复操作: + +```c++ +// Call LastError() of the response to check the error text when any following operation fails. +bool PopGet(IOBuf* value, uint32_t* flags, uint64_t* cas_value); +bool PopGet(std::string* value, uint32_t* flags, uint64_t* cas_value); +bool PopSet(uint64_t* cas_value); +bool PopAdd(uint64_t* cas_value); +bool PopReplace(uint64_t* cas_value); +bool PopAppend(uint64_t* cas_value); +bool PopPrepend(uint64_t* cas_value); +bool PopDelete(); +bool PopFlush(); +bool PopIncrement(uint64_t* new_value, uint64_t* cas_value); +bool PopDecrement(uint64_t* new_value, uint64_t* cas_value); +bool PopTouch(); +bool PopVersion(std::string* version); +``` + +# 访问memcached集群 + +建立一个使用c_md5负载均衡算法的channel就能访问挂载在对应命名服务下的memcached集群了。注意每个MemcacheRequest应只包含一个操作或确保所有的操作是同一个key。如果request包含了多个操作,在当前实现下这些操作总会送向同一个server,假如对应的key分布在多个server上,那么结果就不对了,这个情况下你必须把一个request分开为多个,每个包含一个操作。 + +或者你可以沿用常见的[twemproxy](https://github.com/twitter/twemproxy)方案。这个方案虽然需要额外部署proxy,还增加了延时,但client端仍可以像访问单点一样的访问它。 diff --git a/docs/cn/memory_management.md b/docs/cn/memory_management.md new file mode 100644 index 0000000..6e9afca --- /dev/null +++ b/docs/cn/memory_management.md @@ -0,0 +1,46 @@ +内存管理总是程序中的重要一环,在多线程时代,一个好的内存分配大都在如下两点间权衡: + +- 线程间竞争少。内存分配的粒度大都比较小,对性能敏感,如果不同的线程在大多数分配时会竞争同一份资源或同一把锁,性能将会非常糟糕,原因无外乎和cache一致性有关,已被大量的malloc方案证明。 +- 浪费的空间少。如果每个线程各申请各的,速度也许不错,但万一一个线程总是申请,另一个线程总是释放,内存就爆炸了。线程之间总是要共享内存的,如何共享就是方案的关键了。 + +一般的应用可以使用[tcmalloc](http://goog-perftools.sourceforge.net/doc/tcmalloc.html)、[jemalloc](https://github.com/jemalloc/jemalloc)等成熟的内存分配方案,但这对于较为底层,关注性能长尾的应用是不够的。多线程框架广泛地通过传递对象的ownership来让问题异步化,如何让分配这些小对象的开销变的更小是值得研究的问题。其中的一个特点较为显著: + +- 大多数结构是等长的。 + +这个属性可以大幅简化内存分配的过程,获得比通用malloc更稳定、快速的性能。brpc中的ResourcePool和ObjectPool即提供这类分配。 + +> 这篇文章不鼓励用户使用ResourcePool或ObjectPool,事实上我们反对用户在程序中使用这两个类。因为”等长“的副作用是某个类型独占了一部分内存,这些内存无法再被其他类型使用,如果不加控制的滥用,反而会在程序中产生大量彼此隔离的内存分配体系,既浪费内存也不见得会有更好的性能。 + +# ResourcePool + +创建一个类型为T的对象并返回一个偏移量,这个偏移量可以在O(1)时间内转换为对象指针。这个偏移量相当于指针,但它的值在一般情况下小于2^32,所以我们可以把它作为64位id的一部分。对象可以被归还,但归还后对象并没有删除,也没有被析构,而是仅仅进入freelist。下次申请时可能会取到这种使用过的对象,需要重置后才能使用。当对象被归还后,通过对应的偏移量仍可以访问到对象,即ResourcePool只负责内存分配,并不解决ABA问题。但对于越界的偏移量,ResourcePool会返回空。 + +由于对象等长,ResourcePool通过批量分配和归还内存以避免全局竞争,并降低单次的开销。每个线程的分配流程如下: + +1. 查看thread-local free block。如果还有free的对象,返回。没有的话步骤2。 +2. 尝试从全局取一个free block,若取到的话回到步骤1,否则步骤3。 +3. 从全局取一个block,返回其中第一个对象。 + +原理是比较简单的。工程实现上数据结构、原子变量、memory fence等问题会复杂一些。下面以bthread_t的生成过程说明ResourcePool是如何被应用的。 + +# ObjectPool + +这是ResourcePool的变种,不返回偏移量,而直接返回对象指针。内部结构和ResourcePool类似,一些代码更加简单。对于用户来说,这就是一个多线程下的对象池,brpc里也是这么用的。比如Socket::Write中把每个待写出的请求包装为WriteRequest,这个对象就是用ObjectPool分配的。 + +# 生成bthread_t + +用户期望通过创建bthread获得更高的并发度,所以创建bthread必须很快。 在目前的实现中创建一个bthread的平均耗时小于200ns。如果每次都要从头创建,是不可能这么快的。创建过程更像是从一个bthread池子中取一个实例,我们又同时需要一个id来指代一个bthread,所以这儿正是ResourcePool的用武之地。bthread在代码中被称作Task,其结构被称为TaskMeta,定义在[task_meta.h](https://github.com/apache/brpc/blob/master/src/bthread/task_meta.h)中,所有的TaskMeta由ResourcePool分配。 + +bthread的大部分函数都需要在O(1)时间内通过bthread_t访问到TaskMeta,并且当bthread_t失效后,访问应返回NULL以让函数做出返回错误。解决方法是:bthread_t由32位的版本和32位的偏移量组成。版本解决[ABA问题](http://en.wikipedia.org/wiki/ABA_problem),偏移量由ResourcePool分配。查找时先通过偏移量获得TaskMeta,再检查版本,如果版本不匹配,说明bthread失效了。注意:这只是大概的说法,在多线程环境下,即使版本相等,bthread仍可能随时失效,在不同的bthread函数中处理方法都是不同的,有些函数会加锁,有些则能忍受版本不相等。 + +![img](../images/resource_pool.png) + +这种id生成方式在brpc中应用广泛,brpc中的SocketId,bthread_id_t也是用类似的方法分配的。 + +# 栈 + +使用ResourcePool加快创建的副作用是:一个pool中所有bthread的栈必须是一样大的。这似乎限制了用户的选择,不过基于我们的观察,大部分用户并不关心栈的具体大小,而只需要两种大小的栈:尺寸普通但数量较少,尺寸小但数量众多。所以我们用不同的pool管理不同大小的栈,用户可以根据场景选择。两种栈分别对应属性BTHREAD_ATTR_NORMAL(栈默认为1M)和BTHREAD_ATTR_SMALL(栈默认为32K)。用户还可以指定BTHREAD_ATTR_LARGE,这个属性的栈大小和pthread一样,由于尺寸较大,bthread不会对其做caching,创建速度较慢。server默认使用BTHREAD_ATTR_NORMAL运行用户代码。 + +栈使用[mmap](http://linux.die.net/man/2/mmap)分配,bthread还会用mprotect分配4K的guard page以检测栈溢出。由于mmap+mprotect不能超过max_map_count(默认为65536),当bthread非常多后可能要调整此参数。另外当有很多bthread时,内存问题可能不仅仅是栈,也包括各类用户和系统buffer。 + +goroutine在1.3前通过[segmented stacks](https://gcc.gnu.org/wiki/SplitStacks)动态地调整栈大小,发现有[hot split](https://docs.google.com/document/d/1wAaf1rYoM4S4gtnPh0zOlGzWtrZFQ5suE8qr2sD8uWQ/pub)问题后换成了变长连续栈(类似于vector resizing,只适合内存托管的语言)。由于bthread基本只会在64位平台上使用,虚存空间庞大,对变长栈需求不明确。加上segmented stacks的性能有影响,bthread暂时没有变长栈的计划。 diff --git a/docs/cn/mysql_client.md b/docs/cn/mysql_client.md new file mode 100644 index 0000000..12e1d48 --- /dev/null +++ b/docs/cn/mysql_client.md @@ -0,0 +1,556 @@ +[MySQL](https://www.mysql.com/)是著名的开源的关系型数据库,为了使用户更快捷地访问mysql并充分利用bthread的并发能力,brpc直接支持mysql协议。示例程序:[example/mysql_c++](https://github.com/brpc/brpc/tree/master/example/mysql_c++/) + +**注意**:只支持MySQL 4.1 及之后的版本的文本协议,支持事务,支持Prepared statement。目前支持的鉴权方式为mysql_native_password,使用事务的时候不支持single模式。 + +相比使用[libmysqlclient](https://dev.mysql.com/downloads/connector/c/)(官方client)的优势有: + +- 线程安全。用户不需要为每个线程建立独立的client。 +- 支持同步、异步、半同步等访问方式,能使用[ParallelChannel等](combo_channel.md)组合访问方式。 +- 支持多种[连接方式](client.md#连接方式)。支持超时、backup request、取消、tracing、内置服务等一系列brpc提供的福利。 +- 明确的返回类型校验,如果使用了不正确的变量接受mysql的数据类型,将抛出异常。 +- 调用mysql标准库会阻塞框架的并发能力,使用本实现将能充分利用brpc框架的并发能力。 +- 使用brpc实现的mysql不会造成pthread的阻塞,使用libmysqlclient会阻塞pthread [线程相关](bthread.md),使用mysql的异步api会使编程变得很复杂。 +# 访问mysql + +创建一个访问mysql的Channel: + +```c++ +# include +# include +# include + +brpc::ChannelOptions options; +options.protocol = brpc::PROTOCOL_MYSQL; +options.connection_type = FLAGS_connection_type; +options.timeout_ms = FLAGS_timeout_ms /*milliseconds*/; +options.max_retry = FLAGS_max_retry; +options.auth = new brpc::policy::MysqlAuthenticator("yangliming01", "123456", "test", + "charset=utf8&collation_connection=utf8_unicode_ci"); +if (channel.Init("127.0.0.1", 3306, &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; +} +``` + +向mysql发起命令。 + +```c++ +// 执行各种mysql命令,可以批量执行命令如:"select * from tab1;select * from tab2" +std::string command = "show databases"; // select,delete,update,insert,create,drop ... +brpc::MysqlRequest request; +if (!request.Query(command)) { + LOG(ERROR) << "Fail to add command"; + return false; +} +brpc::MysqlResponse response; +brpc::Controller cntl; +channel.CallMethod(NULL, &cntl, &request, &response, NULL); +if (!cntl.Failed()) { + std::cout << response << std::endl; +} else { + LOG(ERROR) << "Fail to access mysql, " << cntl.ErrorText(); + return false; +} +return true; +``` + +上述代码的说明: + +- 请求类型必须为MysqlRequest,回复类型必须为MysqlResponse,否则CallMethod会失败。不需要stub,直接调用channel.CallMethod,method填NULL。 +- 调用request.Query()传入要执行的命令,可以批量执行命令,多个命令用分号隔开。 +- 依次调用response.reply(X)弹出操作结果,根据返回类型的不同,选择不同的类型接收,如:MysqlReply::Ok,MysqlReply::Error,const MysqlReply::Columnconst MysqlReply::Row等。 +- 如果只有一条命令则reply为1个,如果为批量操作返回的reply为多个。 + +目前支持的请求操作有: + +```c++ +bool Query(const butil::StringPiece& command); +``` + +对应的回复操作: + +```c++ +// 返回不同类型的结果 +const MysqlReply::Auth& auth() const; +const MysqlReply::Ok& ok() const; +const MysqlReply::Error& error() const; +const MysqlReply::Eof& eof() const; +// 对result set结果集的操作 +// get column number +uint64_t MysqlReply::column_number() const; +// get one column +const MysqlReply::Column& MysqlReply::column(const uint64_t index) const; +// get row number +uint64_t MysqlReply::row_number() const; +// get one row +const MysqlReply::Row& MysqlReply::next() const; +// 结果集中每个字段的操作 +const MysqlReply::Field& MysqlReply::Row::field(const uint64_t index) const; +``` + +# 事务操作 + +事务可以保证在一个事务中的多个RPC请求最终要么都成功,要么都失败。 + +```c++ +rpc::Channel channel; +// Initialize the channel, NULL means using default options. +brpc::ChannelOptions options; +options.protocol = brpc::PROTOCOL_MYSQL; +options.connection_type = FLAGS_connection_type; +options.timeout_ms = FLAGS_timeout_ms /*milliseconds*/; +options.connect_timeout_ms = FLAGS_connect_timeout_ms; +options.max_retry = FLAGS_max_retry; +options.auth = new brpc::policy::MysqlAuthenticator( + FLAGS_user, FLAGS_password, FLAGS_schema, FLAGS_params); +if (channel.Init(FLAGS_server.c_str(), FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; +} + +// create transaction +brpc::MysqlTransactionOptions options; +options.readonly = FLAGS_readonly; +options.isolation_level = brpc::MysqlIsolationLevel(FLAGS_isolation_level); +auto tx(brpc::NewMysqlTransaction(channel, options)); +if (tx == NULL) { + LOG(ERROR) << "Fail to create transaction"; + return false; +} + +brpc::MysqlRequest request(tx.get()); +if (!request.Query(*it)) { + LOG(ERROR) << "Fail to add command"; + tx->rollback(); + return false; +} +brpc::MysqlResponse response; +brpc::Controller cntl; +channel.CallMethod(NULL, &cntl, &request, &response, NULL); +if (cntl.Failed()) { + LOG(ERROR) << "Fail to access mysql, " << cntl.ErrorText(); + tx->rollback(); + return false; +} +// handle response +std::cout << response << std::endl; +bool rc = tx->commit(); +``` + +# Prepared Statement + +Prepared statement对于一个需要执行很多次的SQL语句,它把这个SQL语句注册到mysql-server,避免了每次请求在mysql-server端都去解析这个SQL语句,能得到性能上的提升。 + +```c++ +rpc::Channel channel; +// Initialize the channel, NULL means using default options. +brpc::ChannelOptions options; +options.protocol = brpc::PROTOCOL_MYSQL; +options.connection_type = FLAGS_connection_type; +options.timeout_ms = FLAGS_timeout_ms /*milliseconds*/; +options.connect_timeout_ms = FLAGS_connect_timeout_ms; +options.max_retry = FLAGS_max_retry; +options.auth = new brpc::policy::MysqlAuthenticator( + FLAGS_user, FLAGS_password, FLAGS_schema, FLAGS_params); +if (channel.Init(FLAGS_server.c_str(), FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; +} + +auto stmt(brpc::NewMysqlStatement(channel, "select * from tb where name=?")); +if (stmt == NULL) { + LOG(ERROR) << "Fail to create mysql statement"; + return -1; +} + +brpc::MysqlRequest request(stmt.get()); +if (!request.AddParam("lilei")) { + LOG(ERROR) << "Fail to add name param"; + return NULL; +} + +brpc::MysqlResponse response; +brpc::Controller cntl; +channel->CallMethod(NULL, &cntl, &request, &response, NULL); +if (cntl.Failed()) { + LOG(ERROR) << "Fail to access mysql, " << cntl.ErrorText(); + return NULL; +} + +std::cout << response << std::endl; +``` + + + +# 性能测试 + +我在example/mysql_c++目录下面写了两个测试程序,mysql_press.cpp mysqlclient_press.cpp,mysql_go_press.go 一个是使用了brpc框架,一个是使用了的libmysqlclient访问mysql,一个是使用[go-sql-driver](https://github.com/go-sql-driver)/**go-mysql**访问mysql + +启动单线程测试 + +##### brpc框架访问mysql(单线程) + +./mysql_press -thread_num=1 -op_type=0 // insert + +``` +qps=3071 latency=320 +qps=3156 latency=311 +qps=3166 latency=310 +qps=3151 latency=312 +qps=3093 latency=317 +qps=3146 latency=312 +qps=3139 latency=313 +qps=3114 latency=315 +qps=3055 latency=321 +qps=3135 latency=313 +qps=2611 latency=376 +qps=3072 latency=320 +qps=3026 latency=324 +qps=2792 latency=352 +qps=3181 latency=309 +qps=3181 latency=309 +qps=3197 latency=307 +qps=3024 latency=325 +``` + +./mysql_press -thread_num=1 -op_type=1 + +``` +qps=6414 latency=151 +qps=5292 latency=182 +qps=6700 latency=144 +qps=6858 latency=141 +qps=6915 latency=140 +qps=6822 latency=142 +qps=6722 latency=144 +qps=6852 latency=141 +qps=6713 latency=144 +qps=6741 latency=144 +qps=6734 latency=144 +qps=6611 latency=146 +qps=6554 latency=148 +qps=6810 latency=142 +qps=6787 latency=143 +qps=6737 latency=144 +qps=6579 latency=147 +qps=6634 latency=146 +qps=6716 latency=144 +qps=6711 latency=144 +``` + +./mysql_press -thread_num=1 -op_type=2 // update + +``` +qps=3090 latency=318 +qps=3452 latency=284 +qps=3239 latency=303 +qps=3328 latency=295 +qps=3218 latency=305 +qps=3251 latency=302 +qps=2516 latency=391 +qps=2874 latency=342 +qps=3366 latency=292 +qps=3249 latency=302 +qps=3346 latency=294 +qps=3486 latency=282 +qps=3457 latency=284 +qps=3439 latency=286 +qps=3386 latency=290 +qps=3352 latency=293 +qps=3253 latency=302 +qps=3341 latency=294 +``` + +##### libmysqlclient实现(单线程) + +./mysqlclient_press -thread_num=1 -op_type=0 // insert + +``` +qps=3166 latency=313 +qps=3157 latency=314 +qps=2941 latency=337 +qps=3270 latency=303 +qps=3305 latency=300 +qps=3445 latency=287 +qps=3455 latency=287 +qps=3449 latency=287 +qps=3486 latency=284 +qps=3551 latency=279 +qps=3517 latency=281 +qps=3283 latency=302 +qps=3353 latency=295 +qps=2564 latency=386 +qps=3243 latency=305 +qps=3333 latency=297 +qps=3598 latency=275 +qps=3714 latency=267 +``` + +./mysqlclient_press -thread_num=1 -op_type=1 + +``` +qps=8209 latency=120 +qps=8022 latency=123 +qps=7879 latency=125 +qps=8083 latency=122 +qps=8504 latency=116 +qps=8112 latency=121 +qps=8278 latency=119 +qps=8698 latency=113 +qps=8817 latency=112 +qps=8755 latency=112 +qps=8734 latency=113 +qps=8390 latency=117 +qps=8230 latency=120 +qps=8486 latency=116 +qps=8038 latency=122 +qps=8640 latency=114 +``` + +./mysqlclient_press -thread_num=1 -op_type=2 // update + +``` +qps=3583 latency=276 +qps=3530 latency=280 +qps=3610 latency=274 +qps=3492 latency=283 +qps=3508 latency=282 +qps=3465 latency=286 +qps=3543 latency=279 +qps=3610 latency=274 +qps=3567 latency=278 +qps=3381 latency=293 +qps=3514 latency=282 +qps=3461 latency=286 +qps=3456 latency=286 +qps=3517 latency=281 +qps=3492 latency=284 +``` + +##### golang访问mysql(单线程) + +go run test.go -thread_num=1 + +``` +qps = 6905 latency = 144 +qps = 6922 latency = 143 +qps = 6931 latency = 143 +qps = 6998 latency = 142 +qps = 6780 latency = 146 +qps = 6980 latency = 142 +qps = 6901 latency = 144 +qps = 6887 latency = 144 +qps = 6943 latency = 143 +qps = 6880 latency = 144 +qps = 6815 latency = 146 +qps = 6089 latency = 163 +qps = 6626 latency = 150 +qps = 6361 latency = 156 +qps = 6783 latency = 146 +qps = 6789 latency = 146 +qps = 6883 latency = 144 +qps = 6795 latency = 146 +qps = 6724 latency = 148 +qps = 6861 latency = 145 +qps = 6878 latency = 144 +qps = 6842 latency = 146 +``` + +从以上测试结果看来,使用brpc实现的mysql协议和使用libmysqlclient在插入、修改、删除操作上性能是类似的,但是在查询操作看会逊色于libmysqlclient,查询的性能和golang实现的mysql类似。 + +##### brpc框架访问mysql(50线程) + +./mysql_press -thread_num=50 -op_type=1 -use_bthread=true + +``` +qps=18843 latency=2656 +qps=22426 latency=2226 +qps=22536 latency=2203 +qps=22560 latency=2193 +qps=22270 latency=2226 +qps=22302 latency=2247 +qps=22147 latency=2225 +qps=22517 latency=2228 +qps=22762 latency=2176 +qps=23061 latency=2162 +qps=23819 latency=2070 +qps=23852 latency=2077 +qps=22682 latency=2214 +qps=22381 latency=2213 +qps=24041 latency=2069 +qps=24562 latency=2022 +qps=24874 latency=2004 +qps=24821 latency=1988 +qps=24209 latency=2073 +qps=21706 latency=2281 +``` + +##### libmysqlclient实现(50线程) + +./mysql_press -thread_num=50 -op_type=1 -use_bthread=true + +``` +qps=23656 latency=378 +qps=16190 latency=555 +qps=20136 latency=445 +qps=22238 latency=401 +qps=22229 latency=403 +qps=19109 latency=470 +qps=22569 latency=394 +qps=26250 latency=343 +qps=28208 latency=318 +qps=29649 latency=301 +qps=29874 latency=301 +qps=30033 latency=301 +qps=25911 latency=345 +qps=28048 latency=317 +qps=27398 latency=329 +``` + +##### golang访问mysql(50协程) + +go run ../mysql_go_press.go -thread_num=50 + +``` +qps = 23660 latency = 2049 +qps = 23198 latency = 2160 +qps = 23765 latency = 2181 +qps = 23323 latency = 2149 +qps = 14833 latency = 2136 +qps = 23822 latency = 2853 +qps = 20389 latency = 2474 +qps = 23290 latency = 2151 +qps = 23526 latency = 2153 +qps = 21426 latency = 2613 +qps = 23339 latency = 2155 +qps = 25623 latency = 2084 +qps = 23048 latency = 2210 +qps = 20694 latency = 2423 +qps = 23705 latency = 2122 +qps = 23445 latency = 2125 +qps = 24368 latency = 2054 +qps = 23027 latency = 2175 +qps = 24307 latency = 2063 +qps = 23227 latency = 2096 +qps = 23646 latency = 2173 +``` + +以上是启动50并发的查询请求,看上去qps都比较相似,但是libmysqlclient延时明显低。 + +##### brpc框架访问mysql(100线程) + +./mysql_press -thread_num=100 -op_type=1 -use_bthread=true + +``` +qps=26428 latency=3764 +qps=26305 latency=3780 +qps=26390 latency=3779 +qps=26278 latency=3787 +qps=26326 latency=3787 +qps=26266 latency=3792 +qps=26394 latency=3773 +qps=26263 latency=3797 +qps=26250 latency=3783 +qps=26362 latency=3782 +qps=26212 latency=3796 +qps=26260 latency=3800 +qps=24666 latency=4035 +qps=25569 latency=3896 +qps=26223 latency=3794 +qps=25538 latency=3890 +qps=20065 latency=4958 +qps=23023 latency=4331 +qps=25808 latency=3875 +``` + +##### libmysqlclient实现(100线程) + +./mysql_press -thread_num=50 -op_type=1 -use_bthread=true + +``` +qps=29467 latency=304 +qps=29413 latency=305 +qps=29459 latency=304 +qps=29562 latency=302 +qps=30657 latency=291 +qps=30445 latency=295 +qps=30179 latency=298 +qps=30072 latency=297 +qps=29802 latency=299 +qps=29752 latency=301 +qps=29701 latency=304 +qps=29731 latency=301 +qps=29622 latency=299 +qps=29440 latency=304 +qps=29495 latency=306 +qps=29297 latency=303 +qps=29626 latency=306 +qps=29482 latency=300 +qps=28649 latency=313 +qps=29537 latency=305 +qps=29634 latency=299 +``` + +##### golang访问mysql(100协程) + +go run ../mysql_go_press.go -thread_num=100 + +``` +qps = 22108 latency = 4553 +qps = 21930 latency = 4536 +qps = 20653 latency = 4906 +qps = 22100 latency = 4443 +qps = 21091 latency = 4850 +qps = 21718 latency = 4600 +qps = 21444 latency = 4488 +qps = 17832 latency = 5859 +qps = 18296 latency = 5378 +qps = 20463 latency = 4963 +qps = 21611 latency = 4880 +qps = 18441 latency = 5424 +qps = 20731 latency = 4834 +qps = 20611 latency = 4837 +qps = 20188 latency = 4979 +qps = 15450 latency = 5723 +qps = 20927 latency = 5328 +qps = 19893 latency = 5027 +qps = 21080 latency = 4782 +qps = 20192 latency = 4970 +``` + +以上是启动100并发的查询请求,看上去qps都比较相似,但是libmysqlclient延时明显低。 + +并发调整到150的时候,mysql-server已经报错"Too many connections"。 + +为什么并发数50或者100的时候libmysqlclient的延时会那么低呢?因为libmysqlclient使用的IO模式为阻塞模式,我们运行的mysql_press和mysqlclient_press都是使用的bthread模式(-use_bthread=true),底层默认都是9个pthread,使用阻塞模式的libmysqlclient和mysql交互的相当于并发度是9个线程,mysql会启动9个线程,使用非阻塞模式的rpc访问mysql并发度相当于100个,mysql会启动100个线程,所以会造成mysql的频繁上线文切换。 + +如果将libmysqlclient的执行方式改为不使用bthread,那么100个线程的执行效果为如下: + +``` +qps=26919 latency=1927 +qps=27155 latency=2037 +qps=28054 latency=1784 +qps=26738 latency=1856 +qps=27807 latency=1781 +qps=26734 latency=1730 +qps=26562 latency=1939 +qps=27473 latency=1845 +qps=26677 latency=1806 +qps=27369 latency=1948 +qps=27955 latency=1618 +qps=26574 latency=2151 +qps=27343 latency=1777 +qps=26705 latency=1822 +qps=26668 latency=1807 +qps=25347 latency=2104 +qps=26651 latency=1560 +qps=27815 latency=1979 +qps=27221 latency=1762 +qps=26516 latency=2017 +``` + +这个结果就和brpc框架启动100个bthread访问mysql的效果类似了。 + + + +以上为我的一些简单测试,以及一些简单的分析,在低并发的情况下同步IO的效率高于异步IO,可以阅读[IO相关的内容](io.md)有更多解释,后续还将继续分析性能问题,优化协议,给出更多测试。 \ No newline at end of file diff --git a/docs/cn/new_protocol.md b/docs/cn/new_protocol.md new file mode 100644 index 0000000..7d89f68 --- /dev/null +++ b/docs/cn/new_protocol.md @@ -0,0 +1,156 @@ +# server端多协议 + +brpc server一个端口支持多种协议,大部分时候这对部署和运维更加方便。由于不同协议的格式大相径庭,严格地来说,一个端口很难无二义地支持所有协议。出于解耦和可扩展性的考虑,也不太可能集中式地构建一个针对所有协议的分类器。我们的做法就是把协议归三类后逐个尝试: + +- 第一类协议:标记或特殊字符在最前面,比如[baidu_std](baidu_std.md),hulu_pbrpc的前4个字符分别是PRPC和HULU,解析代码只需要检查前4个字节就可以知道协议是否匹配,最先尝试这类协议。这些协议在同一个连接上也可以共存。 +- 第二类协议:有较为复杂的语法,没有固定的协议标记或特殊字符,可能在解析一段输入后才能判断是否匹配,目前此类协议只有http。 +- 第三类协议:协议标记或特殊字符在中间,比如nshead的magic_num在第25-28字节。由于之前的字段均为二进制,难以判断正确性,在没有读取完28字节前,我们无法判定消息是不是nshead格式的,所以处理起来很麻烦,若其解析排在http之前,那么<=28字节的http消息便可能无法被解析,因为程序以为是“还未完整的nshead消息”。 + +考虑到大多数链接上只会有一种协议,我们会记录前一次的协议选择结果,下次首先尝试。对于长连接,这几乎把甄别协议的开销降到了0;虽然短连接每次都得运行这段逻辑,但由于短连接的瓶颈也往往不在于此,这套方法仍旧是足够快的。在未来如果有大量的协议加入,我们可能得考虑一些更复杂的启发式的区分方法。 + +# client端多协议 + +不像server端必须根据连接上的数据动态地判定协议,client端作为发起端,自然清楚自己的协议格式,只要某种协议只通过连接池或短链接发送,即独占那个连接,那么它可以是任意复杂(或糟糕的)格式。因为client端发出时会记录所用的协议,等到response回来时直接调用对应的解析函数,没有任何甄别代价。像memcache,redis这类协议中基本没有magic number,在server端较难和其他协议区分开,但让client端支持却没什么问题。 + +# 支持新协议 + +brpc就是设计为可随时扩展新协议的,步骤如下: + +> 以nshead开头的协议有统一支持,看[这里](nshead_service.md)。 + +## 增加ProtocolType + +在[options.proto](https://github.com/apache/brpc/blob/master/src/brpc/options.proto)的ProtocolType中增加新协议类型,如果你需要的话可以联系我们增加,以确保不会和其他人的需求重合。 + +目前的ProtocolType(18年中): +```c++ +enum ProtocolType { + PROTOCOL_UNKNOWN = 0; + PROTOCOL_BAIDU_STD = 1; + PROTOCOL_STREAMING_RPC = 2; + PROTOCOL_HULU_PBRPC = 3; + PROTOCOL_SOFA_PBRPC = 4; + PROTOCOL_RTMP = 5; + PROTOCOL_HTTP = 6; + PROTOCOL_PUBLIC_PBRPC = 7; + PROTOCOL_NOVA_PBRPC = 8; + PROTOCOL_NSHEAD_CLIENT = 9; // implemented in brpc-ub + PROTOCOL_NSHEAD = 10; + PROTOCOL_HADOOP_RPC = 11; + PROTOCOL_HADOOP_SERVER_RPC = 12; + PROTOCOL_MONGO = 13; // server side only + PROTOCOL_UBRPC_COMPACK = 14; + PROTOCOL_DIDX_CLIENT = 15; // Client side only + PROTOCOL_REDIS = 16; // Client side only + PROTOCOL_MEMCACHE = 17; // Client side only + PROTOCOL_ITP = 18; + PROTOCOL_NSHEAD_MCPACK = 19; + PROTOCOL_DISP_IDL = 20; // Client side only + PROTOCOL_ERSDA_CLIENT = 21; // Client side only + PROTOCOL_UBRPC_MCPACK2 = 22; // Client side only + // Reserve special protocol for cds-agent, which depends on FIFO right now + PROTOCOL_CDS_AGENT = 23; // Client side only + PROTOCOL_ESP = 24; // Client side only + PROTOCOL_THRIFT = 25; // Server side only +} +``` +## 实现回调 + +均定义在struct Protocol中,该结构定义在[protocol.h](https://github.com/apache/brpc/blob/master/src/brpc/protocol.h)。其中的parse必须实现,除此之外server端至少要实现process_request,client端至少要实现serialize_request,pack_request,process_response。 + +实现协议回调还是比较困难的,这块的代码不会像供普通用户使用的那样,有较好的提示和保护,你得先靠自己搞清楚其他协议中的类似代码,然后再动手,最后发给我们做code review。 + +### parse + +```c++ +typedef ParseResult (*Parse)(butil::IOBuf* source, Socket *socket, bool read_eof, const void *arg); +``` +用于把消息从source上切割下来,client端和server端使用同一个parse函数。返回的消息会被递给process_request(server端)或process_response(client端)。 + +参数:source是读取到的二进制内容,socket是对应的连接,read_eof为true表示连接已被对端关闭,arg在server端是对应server的指针,在client端是NULL。 + +ParseResult可能是错误,也可能包含一个切割下来的message,可能的值有: + +- PARSE_ERROR_TRY_OTHERS :不是这个协议,框架会尝试下一个协议。source不能被消费。 +- PARSE_ERROR_NOT_ENOUGH_DATA : 到目前为止数据内容不违反协议,但不构成完整的消息。等到连接上有新数据时,新数据会被append入source并重新调用parse。如果不确定数据是否一定属于这个协议,source不应被消费,如果确定数据属于这个协议,也可以把source的内容转移到内部的状态中去。比如http协议解析中即使source不包含一个完整的http消息,它也会被http parser消费掉,以避免下一次重复解析。 +- PARSE_ERROR_TOO_BIG_DATA : 消息太大,拒绝掉以保护server,连接会被关闭。 +- PARSE_ERROR_NO_RESOURCE : 内部错误,比如资源分配失败。连接会被关闭。 +- PARSE_ERROR_ABSOLUTELY_WRONG : 应该是这个协议(比如magic number匹配了),但是格式不符合预期。连接会被关闭。 + +### serialize_request +```c++ +typedef bool (*SerializeRequest)(butil::IOBuf* request_buf, + Controller* cntl, + const google::protobuf::Message* request); +``` +把request序列化进request_buf,client端必须实现。发生在pack_request之前,一次RPC中只会调用一次。cntl包含某些协议(比如http)需要的信息。成功返回true,否则false。 + +### pack_request +```c++ +typedef int (*PackRequest)(butil::IOBuf* msg, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* controller, + const butil::IOBuf& request_buf, + const Authenticator* auth); +``` +把request_buf打包入msg,每次向server发送消息前(包括重试)都会调用。当auth不为空时,需要打包认证信息。成功返回0,否则-1。 + +### process_request +```c++ +typedef void (*ProcessRequest)(InputMessageBase* msg_base); +``` +处理server端parse返回的消息,server端必须实现。可能会在和parse()不同的线程中运行。多个process_request可能同时运行。 + +在r34386后必须在处理结束时调用msg_base->Destroy(),为了防止漏调,考虑使用DestroyingPtr<>。 + +### process_response +```c++ +typedef void (*ProcessResponse)(InputMessageBase* msg_base); +``` +处理client端parse返回的消息,client端必须实现。可能会在和parse()不同的线程中运行。多个process_response可能同时运行。 + +在r34386后必须在处理结束时调用msg_base->Destroy(),为了防止漏调,考虑使用DestroyingPtr<>。 + +### verify +```c++ +typedef bool (*Verify)(const InputMessageBase* msg); +``` +处理连接的认证,只会对连接上的第一个消息调用,需要支持认证的server端必须实现,不需要认证或仅支持client端的协议可填NULL。成功返回true,否则false。 + +### parse_server_address +```c++ +typedef bool (*ParseServerAddress)(butil::EndPoint* out, const char* server_addr_and_port); +``` +把server_addr_and_port(Channel.Init的一个参数)转化为butil::EndPoint,可选。一些协议对server地址的表达和理解可能是不同的。 + +### get_method_name +```c++ +typedef const std::string& (*GetMethodName)(const google::protobuf::MethodDescriptor* method, + const Controller*); +``` +定制method name,可选。 + +### supported_connection_type + +标记支持的连接方式。如果支持所有连接方式,设为CONNECTION_TYPE_ALL。如果只支持连接池和短连接,设为CONNECTION_TYPE_POOLED_AND_SHORT。 + +### name + +协议的名称,会出现在各种配置和显示中,越简短越好,必须是字符串常量。 + +## 注册到全局 + +实现好的协议要调用RegisterProtocol[注册到全局](https://github.com/apache/brpc/blob/master/src/brpc/global.cpp),以便brpc发现。就像这样: +```c++ +Protocol http_protocol = { ParseHttpMessage, + SerializeHttpRequest, PackHttpRequest, + ProcessHttpRequest, ProcessHttpResponse, + VerifyHttpRequest, ParseHttpServerAddress, + GetHttpMethodName, + CONNECTION_TYPE_POOLED_AND_SHORT, + "http" }; +if (RegisterProtocol(PROTOCOL_HTTP, http_protocol) != 0) { + exit(1); +} +``` diff --git a/docs/cn/nshead_service.md b/docs/cn/nshead_service.md new file mode 100644 index 0000000..5bc0b45 --- /dev/null +++ b/docs/cn/nshead_service.md @@ -0,0 +1,230 @@ +ub是百度内广泛使用的老RPC框架,在迁移ub服务时不可避免地需要[访问ub-server](ub_client.md)或被ub-client访问。ub使用的协议种类很多,但都以nshead作为二进制包的头部,这类服务在brpc中统称为**“nshead service”**。 + +nshead后大都使用mcpack/compack作为序列化格式,注意这不是“协议”。"协议"除了序列化格式,还涉及到各种特殊字段的定义,一种序列化格式可能会衍生出很多协议。ub没有定义标准协议,所以即使都使用mcpack或compack,产品线的通信协议也是五花八门,无法互通。鉴于此,我们提供了一套接口,让用户能够灵活的处理自己产品线的协议,同时享受brpc提供的builtin services等一系列框架福利。 + +# 使用ubrpc的服务 + +ubrpc协议的基本形式是nshead+compack或mcpack2,但compack或mcpack2中包含一些RPC过程需要的特殊字段。 + +在brpc r31687之后,用protobuf写的服务可以通过mcpack2pb被ubrpc client访问,步骤如下: + +## 把idl文件转化为proto文件 + +使用脚本[idl2proto](https://github.com/apache/brpc/blob/master/tools/idl2proto)把idl文件自动转化为proto文件,下面是转化后的proto文件。 + +```protobuf +// Converted from echo.idl by brpc/tools/idl2proto +import "idl_options.proto"; +option (idl_support) = true; +option cc_generic_services = true; +message EchoRequest { + required string message = 1; +} +message EchoResponse { + required string message = 1; +} + +// 对于有多个参数的idl方法,需要定义一个包含所有request或response的消息,作为对应方法的参数。 +message MultiRequests { + required EchoRequest req1 = 1; + required EchoRequest req2 = 2; +} +message MultiResponses { + required EchoRequest res1 = 1; + required EchoRequest res2 = 2; +} + +service EchoService { + // 对应idl中的void Echo(EchoRequest req, out EchoResponse res); + rpc Echo(EchoRequest) returns (EchoResponse); + + // 对应idl中的EchoWithMultiArgs(EchoRequest req1, EchoRequest req2, out EchoResponse res1, out EchoResponse res2); + rpc EchoWithMultiArgs(MultiRequests) returns (MultiResponses); +} +``` + +原先的echo.idl文件如下: + +```protobuf +struct EchoRequest { + string message; +}; + +struct EchoResponse { + string message; +}; + +service EchoService { + void Echo(EchoRequest req, out EchoResponse res); + uint32_t EchoWithMultiArgs(EchoRequest req1, EchoRequest req2, out EchoResponse res1, out EchoResponse res2); +}; +``` + +## 以插件方式运行protoc + +BRPC_PATH代表brpc产出的路径(包含bin include等目录),PROTOBUF_INCLUDE_PATH代表protobuf的包含路径。注意--mcpack_out要和--cpp_out一致。 + +```shell +protoc --plugin=protoc-gen-mcpack=$BRPC_PATH/bin/protoc-gen-mcpack --cpp_out=. --mcpack_out=. --proto_path=$BRPC_PATH/include --proto_path=PROTOBUF_INCLUDE_PATH +``` + +## 实现生成的Service基类 + +```c++ +class EchoServiceImpl : public EchoService { +public: + + ... + // 对应idl中的void Echo(EchoRequest req, out EchoResponse res); + virtual void Echo(google::protobuf::RpcController* cntl_base, + const EchoRequest* request, + EchoResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = static_cast(cntl_base); + + // 填充response。 + response->set_message(request->message()); + + // 对应的idl方法没有返回值,不需要像下面方法中那样set_idl_result()。 + // 可以看到这个方法和其他protobuf服务没有差别,所以这个服务也可以被ubrpc之外的协议访问。 + } + + virtual void EchoWithMultiArgs(google::protobuf::RpcController* cntl_base, + const MultiRequests* request, + MultiResponses* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = static_cast(cntl_base); + + // 填充response。response是我们定义的包含所有idl response的消息。 + response->mutable_res1()->set_message(request->req1().message()); + response->mutable_res2()->set_message(request->req2().message()); + + // 告诉RPC有多个request和response。 + cntl->set_idl_names(brpc::idl_multi_req_multi_res); + + // 对应idl方法的返回值。 + cntl->set_idl_result(17); + } +}; +``` + +## 设置ServerOptions.nshead_service + +```c++ +#include +... +brpc::ServerOptions option; +option.nshead_service = new brpc::policy::UbrpcCompackAdaptor; // mcpack2用UbrpcMcpack2Adaptor +``` + +例子见[example/echo_c++_ubrpc_compack](https://github.com/apache/brpc/blob/master/example/echo_c++_ubrpc_compack/)。 + +# 使用nshead+blob的服务 + +[NsheadService](https://github.com/apache/brpc/blob/master/src/brpc/nshead_service.h)是brpc中所有处理nshead打头协议的基类,实现好的NsheadService实例得赋值给ServerOptions.nshead_service才能发挥作用。不赋值的话,默认是NULL,代表不支持任何nshead开头的协议,这个server被nshead开头的数据包访问时会报错。明显地,**一个Server只能处理一种以nshead开头的协议。** + +NsheadService的接口如下,基本上用户只需要实现`ProcessNsheadRequest`这个函数。 + +```c++ +// 代表一个nshead请求或回复。 +struct NsheadMessage { + nshead_t head; + butil::IOBuf body; +}; + +// 实现这个类并赋值给ServerOptions.nshead_service来让brpc处理nshead请求。 +class NsheadService : public Describable { +public: + NsheadService(); + NsheadService(const NsheadServiceOptions&); + virtual ~NsheadService(); + + // 实现这个方法来处理nshead请求。注意这个方法可能在调用时controller->Failed()已经为true了。 + // 原因可能是Server.Stop()被调用正在退出(错误码是brpc::ELOGOFF) + // 或触发了ServerOptions.max_concurrency(错误码是brpc::ELIMIT) + // 在这种情况下,这个方法应该通过返回一个代表错误的response让客户端知道这些错误。 + // Parameters: + // server The server receiving the request. + // controller Contexts of the request. + // request The nshead request received. + // response The nshead response that you should fill in. + // done You must call done->Run() to end the processing, brpc::ClosureGuard is preferred. + virtual void ProcessNsheadRequest(const Server& server, + Controller* controller, + const NsheadMessage& request, + NsheadMessage* response, + NsheadClosure* done) = 0; +}; +``` + +完整的example在[example/nshead_extension_c++](https://github.com/apache/brpc/tree/master/example/nshead_extension_c++/)。 + +# 使用nshead+mcpack/compack/idl的服务 + +idl是mcpack/compack的前端,用户只要在idl文件中描述schema,就可以生成一些C++结构体,这些结构体可以打包为mcpack/compack。如果你的服务仍在大量地使用idl生成的结构体,且短期内难以修改,同时想要使用brpc提升性能和开发效率的话,可以实现[NsheadService](https://github.com/apache/brpc/blob/master/src/brpc/nshead_service.h),其接口接受nshead + 二进制包为request,用户填写自己的处理逻辑,最后的response也是nshead+二进制包。流程与protobuf方法保持一致,但过程中不涉及任何protobuf的序列化和反序列化,用户可以自由地理解nshead后的二进制包,包括用idl加载mcpack/compack数据包。 + +不过,你应当充分意识到这么改造的坏处: + +> **这个服务在继续使用mcpack/compack作为序列化格式,相比protobuf占用成倍的带宽和打包时间。** + +为了解决这个问题,我们提供了[mcpack2pb](mcpack2pb.md),允许把protobuf作为mcpack/compack的前端。你只要写一份proto文件,就可以同时解析mcpack/compack和protobuf格式的请求。使用这个方法,使用idl描述的服务的可以平滑地改造为使用proto文件描述,而不用修改上游client(仍然使用mcpack/compack)。你产品线的服务可以逐个地从mcpack/compack/idl切换为protobuf,从而享受到性能提升,带宽节省,全新开发体验等好处。你可以自行在NsheadService使用src/mcpack2pb,也可以联系我们,提供更高质量的协议支持。 + +# 使用nshead+protobuf的服务 + +如果你的协议已经使用了nshead + protobuf,或者你想把你的协议适配为protobuf格式,那可以使用另一种模式:实现[NsheadPbServiceAdaptor](https://github.com/apache/brpc/blob/master/src/brpc/nshead_pb_service_adaptor.h)(NsheadService的子类)。 + +工作步骤: + +- Call ParseNsheadMeta() to understand the nshead header, user must tell RPC which pb method to call in the callback. +- Call ParseRequestFromIOBuf() to convert the body after nshead header to pb request, then call the pb method. +- When user calls server's done to end the RPC, SerializeResponseToIOBuf() is called to convert pb response to binary data that will be appended after nshead header and sent back to client. + +这样做的好处是,这个服务还可以被其他使用protobuf的协议访问,比如baidu_std,hulu_pbrpc,sofa_pbrpc协议等等。NsheadPbServiceAdaptor的主要接口如下。完整的example在[这里](https://github.com/apache/brpc/tree/master/example/nshead_pb_extension_c++/)。 + +```c++ +class NsheadPbServiceAdaptor : public NsheadService { +public: + NsheadPbServiceAdaptor() : NsheadService( + NsheadServiceOptions(false, SendNsheadPbResponseSize)) {} + virtual ~NsheadPbServiceAdaptor() {} + + // Fetch meta from `nshead_req' into `meta'. + // Params: + // server: where the RPC runs. + // nshead_req: the nshead request that server received. + // controller: If something goes wrong, call controller->SetFailed() + // meta: Set meta information into this structure. `full_method_name' + // must be set if controller is not SetFailed()-ed + // FIXME: server is not needed anymore, controller->server() is same + virtual void ParseNsheadMeta(const Server& server, + const NsheadMessage& nshead_req, + Controller* controller, + NsheadMeta* meta) const = 0; + // Transform `nshead_req' to `pb_req'. + // Params: + // meta: was set by ParseNsheadMeta() + // nshead_req: the nshead request that server received. + // controller: you can set attachment into the controller. If something + // goes wrong, call controller->SetFailed() + // pb_req: the pb request should be set by your implementation. + virtual void ParseRequestFromIOBuf(const NsheadMeta& meta, + const NsheadMessage& nshead_req, + Controller* controller, + google::protobuf::Message* pb_req) const = 0; + // Transform `pb_res' (and controller) to `nshead_res'. + // Params: + // meta: was set by ParseNsheadMeta() + // controller: If something goes wrong, call controller->SetFailed() + // pb_res: the pb response that returned by pb method. [NOTE] `pb_res' + // can be NULL or uninitialized when RPC failed (indicated by + // Controller::Failed()), in which case you may put error + // information into `nshead_res'. + // nshead_res: the nshead response that will be sent back to client. + virtual void SerializeResponseToIOBuf(const NsheadMeta& meta, + Controller* controller, + const google::protobuf::Message* pb_res, + NsheadMessage* nshead_res) const = 0; +}; +``` diff --git a/docs/cn/overview.md b/docs/cn/overview.md new file mode 100644 index 0000000..3595998 --- /dev/null +++ b/docs/cn/overview.md @@ -0,0 +1,93 @@ +[English version](../en/overview.md) + +# 什么是RPC? + +互联网上的机器大都通过[TCP/IP协议](http://en.wikipedia.org/wiki/Internet_protocol_suite)相互访问,但TCP/IP只是往远端发送了一段二进制数据,为了建立服务还有很多问题需要抽象: + +- 数据以什么格式传输?不同机器间,网络间可能是不同的字节序,直接传输内存数据显然是不合适的;随着业务变化,数据字段往往要增加或删减,怎么兼容前后不同版本的格式? +- 一个TCP连接可以被多个请求复用以减少开销么?多个请求可以同时发往一个TCP连接么? +- 如何管理和访问很多机器? +- 连接断开时应该干什么? +- 万一server不发送回复怎么办? +- ... + +[RPC](http://en.wikipedia.org/wiki/Remote_procedure_call)可以解决这些问题,它把网络交互类比为“client访问server上的函数”:client向server发送request后开始等待,直到server收到、处理、回复client后,client又再度恢复并根据response做出反应。 + +![rpc.png](../images/rpc.png) + +我们来看看上面的一些问题是如何解决的: + +- 数据需要序列化,[protobuf](https://github.com/google/protobuf)在这方面做的不错。用户填写protobuf::Message类型的request,RPC结束后,从同为protobuf::Message类型的response中取出结果。protobuf有较好的前后兼容性,方便业务调整字段。http广泛使用[json](http://www.json.org/)作为序列化方法。 +- 用户无需关心连接如何建立,但可以选择不同的[连接方式](client.md#连接方式):短连接,连接池,单连接。 +- 大量机器一般通过命名服务被发现,可基于[DNS](https://en.wikipedia.org/wiki/Domain_Name_System), [ZooKeeper](https://zookeeper.apache.org/), [etcd](https://github.com/coreos/etcd)等实现。在百度内,我们使用BNS (Baidu Naming Service)。brpc也提供["list://"和"file://"](client.md#命名服务)。用户可以指定负载均衡算法,让RPC每次选出一台机器发送请求,包括: round-robin, randomized, [consistent-hashing](consistent_hashing.md)(murmurhash3 or md5)和 [locality-aware](lalb.md). +- 连接断开时可以重试。 +- 如果server没有在给定时间内回复,client会返回超时错误。 + +# 哪里可以使用RPC? + +几乎所有的网络交互。 + +RPC不是万能的抽象,否则我们也不需要TCP/IP这一层了。但是在我们绝大部分的网络交互中,RPC既能解决问题,又能隔离更底层的网络问题。 + +对于RPC常见的质疑有: + +- 我的数据非常大,用protobuf序列化太慢了。首先这可能是个伪命题,你得用[profiler](cpu_profiler.md)证明慢了才是真的慢,其次很多协议支持携带二进制数据以绕过序列化。 +- 我传输的是流数据,RPC表达不了。事实上brpc中很多协议支持传递流式数据,包括[http中的ProgressiveReader](http_client.md#持续下载), h2的streams, [streaming rpc](streaming_rpc.md), 和专门的流式协议RTMP。 +- 我的场景不需要回复。简单推理可知,你的场景中请求可丢可不丢,可处理也可不处理,因为client总是无法感知,你真的确认这是OK的?即使场景真的不需要,我们仍然建议用最小的结构体回复,因为这不大会是瓶颈,并且追查复杂bug时可能是很有价值的线索。 + +# 什么是![brpc](../images/logo.png)? + +百度内最常使用的工业级RPC框架, 有1,000,000+个实例(不包含client)和上千种服务, 在百度内叫做"**baidu-rpc**". 目前只开源C++版本。 + +你可以使用它: + +* 搭建能在**一个端口**支持多协议的服务, 或访问各种服务 + * restful http/https, [h2](https://http2.github.io/http2-spec)/[gRPC](https://grpc.io)。使用brpc的http实现比[libcurl](https://curl.haxx.se/libcurl/)方便多了。从其他语言通过HTTP/h2+json访问基于protobuf的协议. + * [redis](redis_client.md)和[memcached](memcache_client.md), 线程安全,比官方client更方便。 + * [rtmp](https://github.com/apache/brpc/blob/master/src/brpc/rtmp.h)/[flv](https://en.wikipedia.org/wiki/Flash_Video)/[hls](https://en.wikipedia.org/wiki/HTTP_Live_Streaming), 可用于搭建[流媒体服务](https://github.com/brpc/media-server). + * hadoop_rpc(可能开源) + * 支持[rdma](https://en.wikipedia.org/wiki/Remote_direct_memory_access)(即将开源) + * 支持[thrift](thrift.md) , 线程安全,比官方client更方便 + * 各种百度内使用的协议: [baidu_std](baidu_std.md), [streaming_rpc](streaming_rpc.md), hulu_pbrpc, [sofa_pbrpc](https://github.com/baidu/sofa-pbrpc), nova_pbrpc, public_pbrpc, ubrpc和使用nshead的各种协议. + * 基于工业级的[RAFT算法](https://raft.github.io)实现搭建[高可用](https://en.wikipedia.org/wiki/High_availability)分布式系统,已在[braft](https://github.com/brpc/braft)开源。 +* Server能[同步](server.md)或[异步](server.md#异步service)处理请求。 +* Client支持[同步](client.md#同步访问)、[异步](client.md#异步访问)、[半同步](client.md#半同步),或使用[组合channels](combo_channel.md)简化复杂的分库或并发访问。 +* [通过http界面](builtin_service.md)调试服务, 使用[cpu](cpu_profiler.md), [heap](heap_profiler.md), [contention](contention_profiler.md) profilers. +* 获得[更好的延时和吞吐](#更好的延时和吞吐). +* 把你组织中使用的协议快速地[加入brpc](new_protocol.md),或定制各类组件, 包括[命名服务](load_balancing.md#命名服务) (dns, zk, etcd), [负载均衡](load_balancing.md#负载均衡) (rr, random, consistent hashing) + +# brpc的优势 + +### 更友好的接口 + +只有三个(主要的)用户类: [Server](https://github.com/apache/brpc/blob/master/src/brpc/server.h), [Channel](https://github.com/apache/brpc/blob/master/src/brpc/channel.h), [Controller](https://github.com/apache/brpc/blob/master/src/brpc/controller.h), 分别对应server端,client端,参数集合. 你不必推敲诸如"如何初始化XXXManager", "如何组合各种组件", "XXXController的XXXContext间的关系是什么"。要做的很简单: + +* 建服务? 包含[brpc/server.h](https://github.com/apache/brpc/blob/master/src/brpc/server.h)并参考注释或[示例](https://github.com/apache/brpc/blob/master/example/echo_c++/server.cpp). +* 访问服务? 包含[brpc/channel.h](https://github.com/apache/brpc/blob/master/src/brpc/channel.h)并参考注释或[示例](https://github.com/apache/brpc/blob/master/example/echo_c++/client.cpp). +* 调整参数? 看看[brpc/controller.h](https://github.com/apache/brpc/blob/master/src/brpc/controller.h). 注意这个类是Server和Channel共用的,分成了三段,分别标记为Client-side, Server-side和Both-side methods。 + +我们尝试让事情变得更加简单,以命名服务为例,在其他RPC实现中,你也许需要复制一长段晦涩的代码才可使用,而在brpc中访问BNS可以这么写`"bns://node-name"`,DNS是`"http://domain-name"`,本地文件列表是`"file:///home/work/server.list"`,相信不用解释,你也能明白这些代表什么。 + +### 使服务更加可靠 + +brpc在百度内被广泛使用: + +* map-reduce服务和table存储 +* 高性能计算和模型训练 +* 各种索引和排序服务 +* …. + +它是一个经历过考验的实现。 + +brpc特别重视开发和维护效率, 你可以通过浏览器或curl[查看server内部状态](builtin_service.md), 分析在线服务的[cpu热点](cpu_profiler.md), [内存分配](heap_profiler.md)和[锁竞争](contention_profiler.md), 通过[bvar](bvar.md)统计各种指标并通过[/vars](vars.md)查看。 + +### 更好的延时和吞吐 + +虽然大部分RPC实现都声称“高性能”,但数字仅仅是数字,要在广泛的场景中做到高性能仍是困难的。为了统一百度内的通信架构,brpc在性能方面比其他RPC走得更深。 + +- 对不同客户端请求的读取和解析是完全并发的,用户也不用区分”IO线程“和”处理线程"。其他实现往往会区分“IO线程”和“处理线程”,并把[fd](http://en.wikipedia.org/wiki/File_descriptor)(对应一个客户端)散列到IO线程中去。当一个IO线程在读取其中的fd时,同一个线程中的fd都无法得到处理。当一些解析变慢时,比如特别大的protobuf message,同一个IO线程中的其他fd都遭殃了。虽然不同IO线程间的fd是并发的,但你不太可能开太多IO线程,因为这类线程的事情很少,大部分时候都是闲着的。如果有10个IO线程,一个fd能影响到的”其他fd“仍有相当大的比例(10个即10%,而工业级在线检索要求99.99%以上的可用性)。这个问题在fd没有均匀地分布在IO线程中,或在多租户(multi-tenancy)环境中会更加恶化。在brpc中,对不同fd的读取是完全并发的,对同一个fd中不同消息的解析也是并发的。解析一个特别大的protobuf message不会影响同一个客户端的其他消息,更不用提其他客户端的消息了。更多细节看[这里](io.md#收消息)。 +- 对同一fd和不同fd的写出是高度并发的。当多个线程都要对一个fd写出时(常见于单连接),第一个线程会直接在原线程写出,其他线程会以[wait-free](http://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom)的方式托付自己的写请求,多个线程在高度竞争下仍可以在1秒内对同一个fd写入500万个16字节的消息。更多细节看[这里](io.md#发消息)。 +- 尽量少的锁。高QPS服务可以充分利用一台机器的CPU。比如为处理请求[创建bthread](memory_management.md), [设置超时](timer_keeping.md), 根据回复[找到RPC上下文](bthread_id.md), [记录性能计数器](bvar.md)都是高度并发的。即使服务的QPS超过50万,用户也很少在[contention profiler](contention_profiler.md))中看到框架造成的锁竞争。 +- 服务器线程数自动调节。传统的服务器需要根据下游延时的调整自身的线程数,否则吞吐可能会受影响。在brpc中,每个请求均运行在新建立的[bthread](bthread.md)中,请求结束后线程就结束了,所以天然会根据负载自动调节线程数。 + +brpc和其他实现的性能对比见[这里](benchmark.md)。 diff --git a/docs/cn/parallel_http.md b/docs/cn/parallel_http.md new file mode 100644 index 0000000..7a3e481 --- /dev/null +++ b/docs/cn/parallel_http.md @@ -0,0 +1 @@ +parallel_http能同时访问大量的http服务(几万个),适合在命令行中查询线上所有server的内置信息,供其他工具进一步过滤和聚合。curl很难做到这点,即使多个curl以后台的方式运行,并行度一般也只有百左右,访问几万台机器需要等待极长的时间。 diff --git a/docs/cn/rdma.md b/docs/cn/rdma.md new file mode 100644 index 0000000..fd70686 --- /dev/null +++ b/docs/cn/rdma.md @@ -0,0 +1,90 @@ +# 编译 + +由于RDMA对驱动与硬件有要求,目前仅支持在Linux系统编译并运行RDMA功能。 + +使用config_brpc: +```bash +sh config_brpc.sh --with-rdma --headers="/usr/include" --libs="/usr/lib64 /usr/bin" +make + +cd example/rdma_performance # 示例程序 +make +``` + +使用cmake: +```bash +mkdir bld && cd bld && cmake -DWITH_RDMA=ON .. +make + +cd example/rdma_performance # 示例程序 +mkdir bld && cd bld && cmake .. +make +``` + +使用bazel: +```bash +# Server +bazel build --define=BRPC_WITH_RDMA=true example:rdma_performance_server +# Client +bazel build --define=BRPC_WITH_RDMA=true example:rdma_performance_client +``` + +# 基本实现 + +RDMA与TCP不同,不使用socket接口进行通信。但是在实现上仍然复用了brpc中原本的Socket类。当用户选择ChannelOptions或ServerOptions中的use_rdma为true时,创建出的Socket类中则有对应的RdmaEndpoint(参见src/brpc/rdma/rdma_endpoint.cpp)。当RDMA被使能时,写入Socket的数据会通过RdmaEndpoint提交给RDMA QP(通过verbs API),而非拷贝到fd。对于数据读取,RdmaEndpoint中则调用verbs API从RDMA CQ中获取对应完成信息(事件获取有独立的fd,复用EventDispatcher,处理函数采用RdmaEndpoint::PollCq),最后复用InputMessenger完成RPC消息解析。 + +brpc内部使用RDMA RC模式,每个RdmaEndpoint对应一个QP。RDMA连接建立依赖于前置TCP建连,TCP建连后双方交换必要参数,如GID、QPN等,再发起RDMA连接并实现数据传输。这个过程我们称为握手(参见RdmaEndpoint)。因为握手需要TCP连接,因此RdmaEndpoint所在的Socket类中,原本的TCP fd仍然有效。握手过程采用了brpc中已有的AppConnect逻辑。注意,握手用的TCP连接在后续数据传输阶段并不会收发数据,但仍保持为EST状态。一旦TCP连接中断,其上对应的RDMA连接同样会置错。 + +RdmaEndpoint数据传输逻辑的第一个重要特性是零拷贝。要发送的所有数据默认都存放在IOBuf的Block中,因此所发送的Block需要等到发送CQE触发后才可以释放,这些Block的引用被存放于RdmaEndpoint::_sbuf中。而要实现接收零拷贝,则需要确保接受端所预提交的接收缓冲区必须直接在IOBuf的Block里面,被存放于RdmaEndpoint::_rbuf。注意,接收端预提交的每一段Block,有一个固定的大小(recv_block_size)。发送端发送时,一个请求最多只能有这么大,否则接收端则无法成功接收。 + +RdmaEndpoint数据传输逻辑的第二个重要特性是滑动窗口流控。这一流控机制是为了避免发送端持续在发送,其速度超过了接收端处理的速度。TCP传输中也有类似的逻辑,但是是由内核协议栈来实现的。RdmaEndpoint内实现了这一流控机制,通过接收端显式回复ACK来确认接收端处理完毕。为了减少ACK本身的开销,让ACK以立即数形式返回,可以被附在数据消息里。 + +RdmaEndpoint数据传输逻辑的第三个重要特性是事件聚合。每个消息的大小被限定在一个recv_block_size,默认为8KB。如果每个消息都触发事件进行处理,会导致性能退化严重,甚至不如TCP传输(TCP拥有GSO、GRO等诸多优化)。因此,RdmaEndpoint综合考虑数据大小、窗口与ACK的情况,对每个发送消息选择性设置solicited标志,来控制是否在发送端触发事件通知。 + +RDMA要求数据收发所使用的内存空间必须被注册(memory register),把对应的页表映射注册给网卡,这一操作非常耗时,所以通常都会使用内存池方案来加速。brpc内部的数据收发都使用IOBuf,为了在兼容IOBuf的情况下实现完全零拷贝,整个IOBuf所使用的内存空间整体由统一内存池接管(参见src/brpc/rdma/block_pool.cpp)。注意,由于IOBuf内存池不由用户直接控制,因此实际使用中需要注意IOBuf所消耗的总内存,建议根据实际业务需求,一次性注册足够的内存池以实现性能最大化。 + +应用程序可以自己管理内存,然后通过IOBuf::append_user_data_with_meta把数据发送出去。在这种情况下,应用程序应该自己使用rdma::RegisterMemoryForRdma注册内存(参见src/brpc/rdma/rdma_helper.h)。注意,RegisterMemoryForRdma会返回注册内存对应的lkey,请在append_user_data_with_meta时以meta形式提供给brpc。 + +RDMA是硬件相关的通信技术,有很多独特的概念,比如device、port、GID、LID、MaxSge等。这些参数在初始化时会从对应的网卡中读取出来,并且做出默认的选择(参见src/brpc/rdma/rdma_helper.cpp)。有时默认的选择并非用户的期望,则可以通过flag参数方式指定。 + +RDMA支持事件驱动和轮询两种模式,默认是事件驱动模式,通过设置rdma_use_polling可以开启轮询模式。轮询模式下还可以设置轮询器数目(rdma_poller_num),以及是否主动放弃CPU(rdma_poller_yield)。轮询模式下还可以设置一个回调函数,在每次轮询时调用,可以配合io_uring/spdk等使用。 + +`event_dispatcher_edisp_unsched` 是全局开关,同时影响普通模式(TCP)和 RDMA 模式的 EventDispatcher 调度行为。 +值为 `true` 时 EventDispatcher 不可被调度,值为 `false` 时保持可调度(默认)。 + +运行时严格按用户配置值生效。 + +推荐使用方式: +1. 默认不配置,保持 `false`。 +2. 需要不可调度行为时,设置 `-event_dispatcher_edisp_unsched=true`。 + +行为示例: +1. `-event_dispatcher_edisp_unsched=false`:TCP 和 RDMA 均可调度。 +2. `-event_dispatcher_edisp_unsched=true`:TCP 和 RDMA 均不可调度。 + +# 参数 + +可配置参数说明: +* rdma_trace_verbose: 日志中打印RDMA建连相关信息,默认false。 +* rdma_recv_zerocopy: 是否启用接收零拷贝,默认true。 +* rdma_zerocopy_min_size: 接收零拷贝最小的msg大小,默认512B。 +* rdma_recv_block_type: 为接收数据预准备的block类型,分为三类default(8KB)/large(64KB)/huge(2MB),默认为default。 +* rdma_prepared_qp_size: 程序启动预生成的QP的大小,默认128。 +* rdma_prepared_qp_cnt: 程序启动预生成的QP的数量,默认1024。 +* rdma_max_sge: 允许的最大发送SGList长度,默认为0,即采用硬件所支持的最大长度。 +* rdma_sq_size: SQ大小,默认128。 +* rdma_rq_size: RQ大小,默认128。 +* rdma_cqe_poll_once: 从CQ中一次性poll出的CQE数量,默认32。 +* rdma_gid_index: 使用本地GID表中的Index,默认为-1,即选用最大的可用GID Index。 +* rdma_port: 使用IB设备的port number,默认为1。 +* rdma_device: 使用IB设备的名称,默认为空,即使用第一个active的设备。 +* rdma_memory_pool_initial_size_mb: 内存池的初始大小,单位MB,默认1024。 +* rdma_memory_pool_increase_size_mb: 内存池每次动态增长的大小,单位MB,默认1024。 +* rdma_memory_pool_max_regions: 最大的内存池块数,默认3。 +* rdma_memory_pool_buckets: 内存池中为避免竞争采用的bucket数目,默认为4。 +* rdma_memory_pool_tls_cache_num: 内存池中thread local的缓存block数目,默认为128。 +* rdma_use_polling: 是否使用RDMA的轮询模式,默认false。 +* rdma_poller_num: 轮询模式下的poller数目,默认1。 +* rdma_poller_yield: 轮询模式下的poller是否主动放弃CPU,默认是false。 +* event_dispatcher_edisp_unsched: 全局开关,控制EventDispatcher是否不可被调度(true时不可调度),默认是false。 +* rdma_disable_bthread: 禁用bthread,默认是false。 diff --git a/docs/cn/redis_client.md b/docs/cn/redis_client.md new file mode 100644 index 0000000..d0ac544 --- /dev/null +++ b/docs/cn/redis_client.md @@ -0,0 +1,320 @@ +[English version](../en/redis_client.md) + +[redis](http://redis.io/)是最近几年比较火的缓存服务,相比memcached在server端提供了更多的数据结构和操作方法,简化了用户的开发工作。为了使用户更快捷地访问redis并充分利用bthread的并发能力,brpc直接支持redis协议。示例程序:[example/redis_c++](https://github.com/apache/brpc/tree/master/example/redis_c++/) + +相比使用[hiredis](https://github.com/redis/hiredis)(官方client)的优势有: + +- 线程安全。用户不需要为每个线程建立独立的client。 +- 支持同步、异步、批量同步、批量异步等访问方式,能使用ParallelChannel等组合访问方式。 +- 支持多种[连接方式](client.md#连接方式)。支持超时、backup request、取消、tracing、内置服务等一系列RPC基本福利。 +- 一个进程中的所有brpc client和一个redis-server只有一个连接。多个线程同时访问一个redis-server时更高效(见[性能](#性能))。无论reply的组成多复杂,内存都会连续成块地分配,并支持短串优化(SSO)进一步提高性能。 + +像http一样,brpc保证在最差情况下解析redis reply的时间复杂度也是O(N),N是reply的字节数,而不是O($N^2$)。当reply是个较大的数组时,这是比较重要的。 + +加上[-redis_verbose](#查看发出的请求和收到的回复)后会打印出所有的redis request和response供调试。 + +# 访问单台redis + +创建一个访问redis的Channel: + +```c++ +#include +#include + +brpc::ChannelOptions options; +options.protocol = brpc::PROTOCOL_REDIS; +brpc::Channel redis_channel; +if (redis_channel.Init("0.0.0.0:6379", &options) != 0) { // 6379是redis-server的默认端口 + LOG(ERROR) << "Fail to init channel to redis-server"; + return -1; +} +... +``` + +执行SET后再INCR: + +```c++ +std::string my_key = "my_key_1"; +int my_number = 1; +... +// 执行"SET " +brpc::RedisRequest set_request; +brpc::RedisResponse response; +brpc::Controller cntl; +set_request.AddCommand("SET %s %d", my_key.c_str(), my_number); +redis_channel.CallMethod(NULL, &cntl, &set_request, &response, NULL/*done*/); +if (cntl.Failed()) { + LOG(ERROR) << "Fail to access redis-server"; + return -1; +} +// 可以通过response.reply(i)访问某个reply +if (response.reply(0).is_error()) { + LOG(ERROR) << "Fail to set"; + return -1; +} +// 可用多种方式打印reply +LOG(INFO) << response.reply(0).c_str() // OK + << response.reply(0) // OK + << response; // OK +... + +// 执行"INCR " +brpc::RedisRequest incr_request; +incr_request.AddCommand("INCR %s", my_key.c_str()); +response.Clear(); +cntl.Reset(); +redis_channel.CallMethod(NULL, &cntl, &incr_request, &response, NULL/*done*/); +if (cntl.Failed()) { + LOG(ERROR) << "Fail to access redis-server"; + return -1; +} +if (response.reply(0).is_error()) { + LOG(ERROR) << "Fail to incr"; + return -1; +} +// 可用多种方式打印结果 +LOG(INFO) << response.reply(0).integer() // 2 + << response.reply(0) // (integer) 2 + << response; // (integer) 2 +``` + +批量执行incr或decr + +```c++ +brpc::RedisRequest request; +brpc::RedisResponse response; +brpc::Controller cntl; +request.AddCommand("INCR counter1"); +request.AddCommand("DECR counter1"); +request.AddCommand("INCRBY counter1 10"); +request.AddCommand("DECRBY counter1 20"); +redis_channel.CallMethod(NULL, &cntl, &request, &response, NULL/*done*/); +if (cntl.Failed()) { + LOG(ERROR) << "Fail to access redis-server"; + return -1; +} +CHECK_EQ(4, response.reply_size()); +for (int i = 0; i < 4; ++i) { + CHECK(response.reply(i).is_integer()); + CHECK_EQ(brpc::REDIS_REPLY_INTEGER, response.reply(i).type()); +} +CHECK_EQ(1, response.reply(0).integer()); +CHECK_EQ(0, response.reply(1).integer()); +CHECK_EQ(10, response.reply(2).integer()); +CHECK_EQ(-10, response.reply(3).integer()); +``` + +# 访问带认证的Redis + +创建一个RedisAuthenticator,并设置到ChannelOptions里即可。 + +```c++ +brpc::ChannelOptions options; +brpc::policy::RedisAuthenticator* auth = new brpc::policy::RedisAuthenticator("my_password"); +options.auth = auth; +``` + +# RedisRequest + +一个[RedisRequest](https://github.com/apache/brpc/blob/master/src/brpc/redis.h)可包含多个Command,调用AddCommand*增加命令,成功返回true,失败返回false**并会打印调用处的栈**。 + +```c++ +bool AddCommand(const char* fmt, ...); +bool AddCommandV(const char* fmt, va_list args); +bool AddCommandByComponents(const butil::StringPiece* components, size_t n); +``` + +格式和hiredis基本兼容:即%b对应二进制数据(指针+length),其他和printf的参数类似。对一些细节做了改进:当某个字段包含空格时,使用单引号或双引号包围起来会被视作一个字段。比如AddCommand("Set 'a key with space' 'a value with space as well'")中的key是a key with space,value是a value with space as well。在hiredis中必须写成redisvCommand(..., "SET %s %s", "a key with space", "a value with space as well"); + +> 注意,AddCommand和AddCommandV的fmt参数如果设置错误,有可能导致程序crash或者数据泄露,请谨慎设置。不应将受用户输入影响的内容作为fmt参数进行调用! + +AddCommandByComponents类似hiredis中的redisCommandArgv,用户通过数组指定命令中的每一个部分。这个方法对AddCommand和AddCommandV可能发生的转义问题免疫,且效率最高。如果你在使用AddCommand和AddCommandV时出现了“Unmatched quote”,“无效格式”等问题且无法定位,可以试下这个方法。 + +如果AddCommand\*失败,后续的AddCommand\*和CallMethod都会失败。一般来说不用判AddCommand*的结果,失败后自然会通过RPC失败体现出来。 + +command_size()可获得(成功)加入的命令个数。 + +调用Clear()后可重用RedisRequest + +# RedisResponse + +[RedisResponse](https://github.com/apache/brpc/blob/master/src/brpc/redis.h)可能包含一个或多个[RedisReply](https://github.com/apache/brpc/blob/master/src/brpc/redis_reply.h),reply_size()可获得reply的个数,reply(i)可获得第i个reply的引用(从0计数)。注意在hiredis中,如果请求包含了N个command,获取结果也要调用N次redisGetReply。但在brpc中这是不必要的,RedisResponse已经包含了N个reply,通过reply(i)获取就行了。只要RPC成功,response.reply_size()应与request.command_size()相等,除非redis-server有bug,redis-server工作的基本前提就是reply和command按序一一对应。 + +每个reply可能是: + +- REDIS_REPLY_NIL:redis中的NULL,代表值不存在。可通过is_nil()判定。 +- REDIS_REPLY_STATUS:在redis文档中称为Simple String。一般是操作的返回状态,比如SET返回的OK。可通过is_string()判定(和string相同),c_str()或data()获得值。 +- REDIS_REPLY_STRING:在redis文档中称为Bulk String。大多数值都是这个类型,包括incr返回的。可通过is_string()判定,c_str()或data()获得值。 +- REDIS_REPLY_ERROR:操作出错时的返回值,包含一段错误信息。可通过is_error()判定,error_message()获得错误信息。 +- REDIS_REPLY_INTEGER:一个64位有符号数。可通过is_integer()判定,integer()获得值。 +- REDIS_REPLY_ARRAY:另一些reply的数组。可通过is_array()判定,size()获得数组大小,[i]获得对应的子reply引用。 + +如果response包含三个reply,分别是integer,string和一个长度为2的array。那么可以分别这么获得值:response.reply(0).integer(),response.reply(1).c_str(), repsonse.reply(2)[0]和repsonse.reply(2)[1]。如果类型对不上,调用处的栈会被打印出来,并返回一个undefined的值。 + +response中的所有reply的ownership属于response。当response析构时,reply也析构了。 + +调用Clear()后RedisResponse可以重用。 + +# 访问redis集群 + +建立一个使用一致性哈希负载均衡算法(c_md5或c_murmurhash)的channel就能访问挂载在对应命名服务下的redis集群了。注意每个RedisRequest应只包含一个操作或确保所有的操作是同一个key。如果request包含了多个操作,在当前实现下这些操作总会送向同一个server,假如对应的key分布在多个server上,那么结果就不对了,这个情况下你必须把一个request分开为多个,每个包含一个操作。 + +或者你可以沿用常见的[twemproxy](https://github.com/twitter/twemproxy)方案。这个方案虽然需要额外部署proxy,还增加了延时,但client端仍可以像访问单点一样的访问它。 + +如果你要直接访问原生 Redis Cluster(按 slot 路由、自动处理 MOVED/ASK,并通过 `CLUSTER SLOTS`/`CLUSTER NODES` 刷新拓扑),可以使用 `brpc::RedisClusterChannel`: + +```c++ +#include + +brpc::RedisClusterChannel channel; +brpc::RedisClusterChannelOptions options; +options.max_redirect = 5; +if (channel.Init("127.0.0.1:7000,127.0.0.1:7001", &options) != 0) { + LOG(ERROR) << "Fail to init redis cluster channel"; +} +``` + +`RedisClusterChannel` 支持同步/异步 `CallMethod`、自动重定向重试和周期性拓扑刷新。多 key 支持 `MGET/MSET/DEL/EXISTS/UNLINK/EVAL/EVALSHA`,暂不支持 `MULTI/EXEC`。 + +## RedisClusterChannel 示例 + +`example/redis_c++/redis_cluster_client.cpp` 覆盖了以下常见场景: + +- 通过多个 seed 节点初始化; +- 自动处理 MOVED/ASK 重定向与重试; +- 先走 `CLUSTER SLOTS`,失败后回退 `CLUSTER NODES`; +- 同一个 channel 同时执行同步 pipeline 和异步请求。 + +构建并运行: + +```bash +cd example/redis_c++ +make redis_cluster_client +./redis_cluster_client \ + --seeds=127.0.0.1:7000,127.0.0.1:7001 \ + --max_redirect=5 \ + --timeout_ms=1000 +``` + +常用选项: + +- `RedisClusterChannelOptions::max_redirect`:单个命令的最大重定向次数; +- `RedisClusterChannelOptions::refresh_interval_s`:周期刷新拓扑的间隔; +- `RedisClusterChannelOptions::topology_refresh_timeout_ms`:拓扑命令超时; +- `RedisClusterChannelOptions::channel_options`:每个 redis 节点的通用 brpc 参数; +- `RedisClusterChannelOptions::enable_periodic_refresh`:是否启用后台周期刷新。 + +说明: + +- `MGET/MSET/DEL/EXISTS/UNLINK` 会按 key 分发后按请求顺序合并返回; +- `EVAL/EVALSHA` 要求声明的 key 位于同一 slot; +- `MULTI/EXEC` 当前会直接返回错误 reply。 + +# 查看发出的请求和收到的回复 + + 打开[-redis_verbose](http://brpc.baidu.com:8765/flags/redis_verbose)即看到所有的redis request和response,注意这应该只用于线下调试,而不是线上程序。 + +打开[-redis_verbose_crlf2space](http://brpc.baidu.com:8765/flags/redis_verbose_crlf2space)可让打印内容中的CRLF (\r\n)变为空格,方便阅读。 + +| Name | Value | Description | Defined At | +| ------------------------ | ----- | ---------------------------------------- | ---------------------------------- | +| redis_verbose | false | [DEBUG] Print EVERY redis request/response | src/brpc/policy/redis_protocol.cpp | +| redis_verbose_crlf2space | false | [DEBUG] Show \r\n as a space | src/brpc/redis.cpp | + +# 性能 + +redis版本:2.6.14 + +分别使用1,50,200个bthread同步压测同机redis-server,延时单位均为微秒。 + +``` +$ ./client -use_bthread -thread_num 1 +TRACE: 02-13 19:42:04: * 0 client.cpp:180] Accessing redis server at qps=18668 latency=50 +TRACE: 02-13 19:42:05: * 0 client.cpp:180] Accessing redis server at qps=17043 latency=52 +TRACE: 02-13 19:42:06: * 0 client.cpp:180] Accessing redis server at qps=16520 latency=54 + +$ ./client -use_bthread -thread_num 50 +TRACE: 02-13 19:42:54: * 0 client.cpp:180] Accessing redis server at qps=301212 latency=164 +TRACE: 02-13 19:42:55: * 0 client.cpp:180] Accessing redis server at qps=301203 latency=164 +TRACE: 02-13 19:42:56: * 0 client.cpp:180] Accessing redis server at qps=302158 latency=164 + +$ ./client -use_bthread -thread_num 200 +TRACE: 02-13 19:43:48: * 0 client.cpp:180] Accessing redis server at qps=411669 latency=483 +TRACE: 02-13 19:43:49: * 0 client.cpp:180] Accessing redis server at qps=411679 latency=483 +TRACE: 02-13 19:43:50: * 0 client.cpp:180] Accessing redis server at qps=412583 latency=482 +``` + +200个线程后qps基本到极限了。这里的极限qps比hiredis高很多,原因在于brpc默认以单链接访问redis-server,多个线程在写出时会[以wait-free的方式合并](io.md#发消息),从而让redis-server就像被批量访问一样,每次都能从那个连接中读出一批请求,从而获得远高于非批量时的qps。下面通过连接池访问redis-server时qps的大幅回落是另外一个证明。 + +分别使用1,50,200个bthread一次发送10个同步压测同机redis-server,延时单位均为微秒。 + +``` +$ ./client -use_bthread -thread_num 1 -batch 10 +TRACE: 02-13 19:46:45: * 0 client.cpp:180] Accessing redis server at qps=15880 latency=59 +TRACE: 02-13 19:46:46: * 0 client.cpp:180] Accessing redis server at qps=16945 latency=57 +TRACE: 02-13 19:46:47: * 0 client.cpp:180] Accessing redis server at qps=16728 latency=57 + +$ ./client -use_bthread -thread_num 50 -batch 10 +TRACE: 02-13 19:47:14: * 0 client.cpp:180] Accessing redis server at qps=38082 latency=1307 +TRACE: 02-13 19:47:15: * 0 client.cpp:180] Accessing redis server at qps=38267 latency=1304 +TRACE: 02-13 19:47:16: * 0 client.cpp:180] Accessing redis server at qps=38070 latency=1305 + + PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND +16878 gejun 20 0 48136 2436 1004 R 93.8 0.0 12:48.56 redis-server // thread_num=50 + +$ ./client -use_bthread -thread_num 200 -batch 10 +TRACE: 02-13 19:49:09: * 0 client.cpp:180] Accessing redis server at qps=29053 latency=6875 +TRACE: 02-13 19:49:10: * 0 client.cpp:180] Accessing redis server at qps=29163 latency=6855 +TRACE: 02-13 19:49:11: * 0 client.cpp:180] Accessing redis server at qps=29271 latency=6838 + + PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND +16878 gejun 20 0 48136 2508 1004 R 99.9 0.0 13:36.59 redis-server // thread_num=200 +``` + +注意redis-server实际处理的qps要乘10。乘10后也差不多在40万左右。另外在thread_num为50或200时,redis-server的CPU已打满。注意redis-server是[单线程reactor](threading_overview.md#单线程reactor),一个核心打满就意味server到极限了。 + +使用50个bthread通过连接池方式同步压测同机redis-server。 + +``` +$ ./client -use_bthread -connection_type pooled +TRACE: 02-13 18:07:40: * 0 client.cpp:180] Accessing redis server at qps=75986 latency=654 +TRACE: 02-13 18:07:41: * 0 client.cpp:180] Accessing redis server at qps=75562 latency=655 +TRACE: 02-13 18:07:42: * 0 client.cpp:180] Accessing redis server at qps=75238 latency=657 + + PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND +16878 gejun 20 0 48136 2520 1004 R 99.9 0.0 9:52.33 redis-server +``` + +可以看到qps相比单链接时有大幅回落,同时redis-server的CPU打满了。原因在于redis-server每次只能从一个连接中读到一个请求,IO开销大幅增加。这也是单个hiredis client的极限性能。 + +# Command Line Interface + +[example/redis_c++/redis_cli](https://github.com/apache/brpc/blob/master/example/redis_c%2B%2B/redis_cli.cpp)是一个类似于官方CLI的命令行工具,以展示brpc对redis协议的处理能力。当使用brpc访问redis-server出现不符合预期的行为时,也可以使用这个CLI进行交互式的调试。 + +如果是原生 Redis Cluster 场景,可直接参考 [example/redis_c++/redis_cluster_client.cpp](https://github.com/apache/brpc/blob/master/example/redis_c%2B%2B/redis_cluster_client.cpp)。 + +和官方CLI类似,`redis_cli `也可以直接运行命令,-server参数可以指定redis-server的地址。 + +``` +$ ./redis_cli + __ _ __ + / /_ ____ _(_)___/ /_ __ _________ _____ + / __ \/ __ `/ / __ / / / /_____/ ___/ __ \/ ___/ + / /_/ / /_/ / / /_/ / /_/ /_____/ / / /_/ / /__ + /_.___/\__,_/_/\__,_/\__,_/ /_/ / .___/\___/ + /_/ +This command-line tool mimics the look-n-feel of official redis-cli, as a +demostration of brpc's capability of talking to redis server. The +output and behavior is not exactly same with the official one. + +redis 127.0.0.1:6379> mset key1 foo key2 bar key3 17 +OK +redis 127.0.0.1:6379> mget key1 key2 key3 +["foo", "bar", "17"] +redis 127.0.0.1:6379> incrby key3 10 +(integer) 27 +redis 127.0.0.1:6379> client setname brpc-cli +OK +redis 127.0.0.1:6379> client getname +"brpc-cli" +``` diff --git a/docs/cn/rpc_press.md b/docs/cn/rpc_press.md new file mode 100644 index 0000000..357340a --- /dev/null +++ b/docs/cn/rpc_press.md @@ -0,0 +1,111 @@ +rpc_press无需写代码就压测各种rpc server,目前支持的协议有: + +- baidu_std +- hulu-pbrpc +- sofa-pbrpc +- public_pbrpc +- nova_pbrpc +- google_grpc + +# 获取工具 + +先按照[Getting Started](getting_started.md)编译好brpc,再去tools/rpc_press编译。 + +在CentOS 6.3上如果出现找不到libssl.so.4的错误,可执行`ln -s /usr/lib64/libssl.so.6 libssl.so.4临时解决` + +# 发压力 + +rpc_press会动态加载proto文件,无需把proto文件编译为c++源文件。rpc_press会加载json格式的输入文件,转为pb请求,发向server,收到pb回复后如有需要会转为json并写入用户指定的文件。 + +rpc_press的所有的选项都来自命令行参数,而不是配置文件. + +如下的命令向下游0.0.0.0:8002用baidu_std重复发送两个pb请求,分别转自'{"message":"hello"}和'{"message":"world"},持续压力直到按ctrl-c,qps为100。 + +json也可以写在文件中,假如./input.json包含了上述两个请求,-input=./input.json也是可以的。 + +必需参数: + +- -proto:指定相关的proto文件名。 +- -method:指定方法名,形式必须是package.service.method。 +- -server:当-lb_policy为空时,是服务器的ip:port;当-lb_policy不为空时,是集群地址,比如bns://node-name, file://server_list等等。具体见[命名服务](client.md#命名服务)。 +- -input: 指定json请求或包含json请求的文件。r32157后json间不需要分隔符,r32157前json间用分号分隔。 + +可选参数: + +- -inc: 包含被import的proto文件的路径。rpc_press默认支持import目录下的其他proto文件,但如果proto文件在其他目录,就要通过这个参数指定,多个路径用分号(;)分隔。 +- -lb_policy: 指定负载均衡算法,默认为空,可选项为: rr random la p2c c_murmurhash c_md5,具体见[负载均衡](client.md#负载均衡)。 +- -timeout_ms: 设定超时,单位是毫秒(milliseconds),默认是1000(1秒) +- -max_retry: 最大的重试次数,默认是3, 一般无需修改. brpc的重试行为具体请见[这里](client.md#重试). +- -protocol: 连接server使用的协议,可选项见[协议](client.md#协议), 默认是baidu_std +- -connection_type: 连接方式,可选项为: single pooled short,具体见[连接方式](client.md#连接方式)。默认会根据协议自动选择,无需指定. +- -output: 如果不为空,response会转为json并写入这个文件,默认为空。 +- -duration:大于0时表示发送这么多秒的压力后退出,否则一直发直到按ctrl-c或进程被杀死。默认是0(一直发送)。 +- -qps:大于0时表示以这个压力发送,否则以最大速度(自适应)发送。默认是100。 +- -dummy_port:修改dummy_server的端口,默认是8888 + +常用的参数组合: + +- 向下游0.0.0.0:8002、用baidu_std重复发送./input.json中的所有请求,持续压力直到按ctrl-c,qps为100。 + ./rpc_press -proto=echo.proto -method=example.EchoService.Echo -server=0.0.0.0:8002 -input=./input.json -qps=100 +- 以round-robin分流算法向bns://node-name代表的所有下游机器、用baidu_std重复发送两个pb请求,持续压力直到按ctrl-c,qps为100。 + ./rpc_press -proto=echo.proto -method=example.EchoService.Echo -server=bns://node-name -lb_policy=rr -input='{"message":"hello"} {"message":"world"}' -qps=100 +- 向下游0.0.0.0:8002、用hulu协议重复发送两个pb请求,持续压力直到按ctrl-c,qps为100。 + ./rpc_press -proto=echo.proto -method=example.EchoService.Echo -server=0.0.0.0:8002 -protocol=hulu_pbrpc -input='{"message":"hello"} {"message":"world"}' -qps=100 +- 向下游0.0.0.0:8002、用baidu_std重复发送两个pb请求,持续最大压力直到按ctrl-c。 + ./rpc_press -proto=echo.proto -method=example.EchoService.Echo -server=0.0.0.0:8002 -input='{"message":"hello"} {"message":"world"}' -qps=0 +- 向下游0.0.0.0:8002、用baidu_std重复发送两个pb请求,持续最大压力10秒钟。 + ./rpc_press -proto=echo.proto -method=example.EchoService.Echo -server=0.0.0.0:8002 -input='{"message":"hello"} {"message":"world"}' -qps=0 -duration=10 +- echo.proto中import了另一个目录下的proto文件 + ./rpc_press -proto=echo.proto -inc= -method=example.EchoService.Echo -server=0.0.0.0:8002 -input='{"message":"hello"} {"message":"world"}' -qps=0 -duration=10 + +rpc_press启动后会默认在8888端口启动一个dummy server,用于观察rpc_press本身的运行情况: + +``` +./rpc_press -proto=echo.proto -method=example.EchoService.Echo -server=0.0.0.0:8002 -input=./input.json -duration=0 -qps=100 +TRACE: 01-30 16:10:04: * 0 src/brpc/server.cpp:733] Server[dummy_servers] is serving on port=8888. +TRACE: 01-30 16:10:04: * 0 src/brpc/server.cpp:742] Check out http://xxx.com:8888 in your web browser. +``` + +dummy_server启动时会在终端打印日志,一般按住ctrl点击那个链接可以直接打开对应的内置服务页面,就像这样: + +![img](../images/rpc_press_1.png) + +切换到vars页面,在Search框中输入rpc_press可以看到当前压力的延时分布情况: + +![img](../images/rpc_press_2.png) + +你可以通过-dummy_port参数修改dummy_server的端口,请确保端口可以在浏览器中访问。 + +如果你无法打开浏览器,命令行中也会定期打印信息: + +``` +2016/01/30-16:19:01 sent:101 success:101 error:0 total_error:0 total_sent:28379 +2016/01/30-16:19:02 sent:101 success:101 error:0 total_error:0 total_sent:28480 +2016/01/30-16:19:03 sent:101 success:101 error:0 total_error:0 total_sent:28581 +2016/01/30-16:19:04 sent:101 success:101 error:0 total_error:0 total_sent:28682 +2016/01/30-16:19:05 sent:101 success:101 error:0 total_error:0 total_sent:28783 +2016/01/30-16:19:06 sent:101 success:101 error:0 total_error:0 total_sent:28884 +2016/01/30-16:19:07 sent:101 success:101 error:0 total_error:0 total_sent:28985 +2016/01/30-16:19:08 sent:101 success:101 error:0 total_error:0 total_sent:29086 +2016/01/30-16:19:09 sent:101 success:101 error:0 total_error:0 total_sent:29187 +2016/01/30-16:19:10 sent:101 success:101 error:0 total_error:0 total_sent:29288 +[Latency] + avg 122 us + 50% 122 us + 70% 135 us + 90% 161 us + 95% 164 us + 97% 166 us + 99% 172 us + 99.9% 199 us + 99.99% 199 us + max 199 us +``` + +上方的字段含义应该是自解释的,在此略过。下方是延时信息,第一项"avg"是10秒内的平均延时,最后一项"max"是10秒内的最大延时,其余以百分号结尾的则代表延时分位值,即有左侧这么多比例的请求延时小于右侧的延时(单位微秒)。一般性能测试需要关注99%之后的长尾区域。 + +# FAQ + +**Q: 如果下游是基于j-protobuf框架的服务模块,压力工具该如何配置?** + +A:因为协议兼容性问题,启动rpc_press的时候需要带上-baidu_protocol_use_fullname=false diff --git a/docs/cn/rpc_replay.md b/docs/cn/rpc_replay.md new file mode 100644 index 0000000..d1b23af --- /dev/null +++ b/docs/cn/rpc_replay.md @@ -0,0 +1,110 @@ +r31658后,brpc能随机地把一部分请求写入一些文件中,并通过rpc_replay工具回放。目前支持的协议有:baidu_std, hulu_pbrpc, sofa_pbrpc, http, nshead。 + +# 获取工具 + +先按照[Getting Started](getting_started.md)编译好brpc,再去tools/rpc_replay编译。 + +在CentOS 6.3上如果出现找不到libssl.so.4的错误,可执行`ln -s /usr/lib64/libssl.so.6 libssl.so.4临时解决` + +# 采样 + +brpc通过如下flags打开和控制如何保存请求,包含(R)后缀的flag都可以动态设置。 + +![img](../images/rpc_replay_1.png) + +![img](../images/rpc_replay_2.png) + +参数说明: + +- -rpc_dump是主开关,关闭时其他以rpc_dump开头的flag都无效。当打开-rpc_dump后,brpc会以一定概率采集请求,如果服务的qps很高,brpc会调节采样比例,使得每秒钟采样的请求个数不超过-bvar_collector_expected_per_second对应的值。这个值在目前同样影响rpcz和contention profiler,一般不用改动,以后会对不同的应用独立开来。 +- -rpc_dump_dir:设置存放被dump请求的目录 +- -rpc_dump_max_files: 设置目录下的最大文件数,当超过限制时,老文件会被删除以腾出空间。 +- -rpc_dump_max_requests_in_one_file:一个文件内的最大请求数,超过后写新文件。 + +brpc通过一个[bvar::Collector](https://github.com/apache/brpc/blob/master/src/bvar/collector.h)来汇总来自不同线程的被采样请求,不同线程之间没有竞争,开销很小。 + +写出的内容依次存放在rpc_dump_dir目录下的多个文件内,这个目录默认在./rpc_dump_,其中是程序名。不同程序在同一个目录下同时采样时会写入不同的目录。如果程序启动时rpc_dump_dir已经存在了,目录将被清空。目录中的每个文件以requests.yyyymmdd_hhmmss_uuuuus命名,以保证按时间有序方便查找,比如: + +![img](../images/rpc_replay_3.png) + +目录下的文件数不超过rpc_dump_max_files,超过后最老的文件被删除从而给新文件腾出位置。 + +文件是二进制格式,格式与baidu_std协议的二进制格式类似,每个请求的binary layout如下: + +``` +"PRPC" (4 bytes magic string) +body_size(4 bytes) +meta_size(4 bytes) +RpcDumpMeta (meta_size bytes) +serialized request (body_size - meta_size bytes, including attachment) +``` + +请求间紧密排列。一个文件内的请求数不超过rpc_dump_max_requests_in_one_file。 + +> 一个文件可能包含多种协议的请求,如果server被多种协议访问的话。回放时被请求的server也将收到不同协议的请求。 + +brpc提供了[SampleIterator](https://github.com/apache/brpc/blob/master/src/brpc/rpc_dump.h)从一个采样目录下的所有文件中依次读取所有的被采样请求,用户可根据需求把serialized request反序列化为protobuf请求,做一些二次开发。 + +```c++ +#include +... +brpc::SampleIterator it("./rpc_data/rpc_dump/echo_server"); +for (brpc::SampledRequest* req = it->Next(); req != NULL; req = it->Next()) { + ... + // req->meta的类型是brpc::RpcDumpMeta,定义在src/brpc/rpc_dump.proto + // req->request的类型是butil::IOBuf,对应格式说明中的"serialized request" + // 使用结束后必须delete req。 +} +``` + +# 回放 + +brpc在[tools/rpc_replay](https://github.com/apache/brpc/tree/master/tools/rpc_replay/)提供了默认的回放工具。运行方式如下: + +![img](../images/rpc_replay_4.png) + +主要参数说明: + +- -dir指定了存放采样文件的目录 +- -times指定循环回放次数。其他参数请加上--help运行查看。 +- -connection_type: 连接server的方式 +- -dummy_port:修改dummy_server的端口 +- -max_retry:最大重试次数,默认3次。 +- -qps:大于0时限制qps,默认为0(不限制) +- -server:server的地址 +- -thread_num:发送线程数,为0时会根据qps自动调节,默认为0。一般不用设置。 +- -timeout_ms:超时 +- -use_bthread:使用bthread发送,默认是。 +- -http_host:指定回放HTTP请求时的Host字段,如果非标准端口,请补全,比如:www.abc.com:8888,不指定该参数时将使用采样的原始Host字段。 + +rpc_replay会默认启动一个仅监控用的dummy server。打开后可查看回放的状况。其中rpc_replay_error是回放失败的次数。 + +![img](../images/rpc_replay_5.png) + +如果你无法打开浏览器,命令行中也会定期打印信息: + +``` +2016/01/30-16:19:01 sent:101 success:101 error:0 total_error:0 total_sent:28379 +2016/01/30-16:19:02 sent:101 success:101 error:0 total_error:0 total_sent:28480 +2016/01/30-16:19:03 sent:101 success:101 error:0 total_error:0 total_sent:28581 +2016/01/30-16:19:04 sent:101 success:101 error:0 total_error:0 total_sent:28682 +2016/01/30-16:19:05 sent:101 success:101 error:0 total_error:0 total_sent:28783 +2016/01/30-16:19:06 sent:101 success:101 error:0 total_error:0 total_sent:28884 +2016/01/30-16:19:07 sent:101 success:101 error:0 total_error:0 total_sent:28985 +2016/01/30-16:19:08 sent:101 success:101 error:0 total_error:0 total_sent:29086 +2016/01/30-16:19:09 sent:101 success:101 error:0 total_error:0 total_sent:29187 +2016/01/30-16:19:10 sent:101 success:101 error:0 total_error:0 total_sent:29288 +[Latency] + avg 122 us + 50% 122 us + 70% 135 us + 90% 161 us + 95% 164 us + 97% 166 us + 99% 172 us + 99.9% 199 us + 99.99% 199 us + max 199 us +``` + +上方的字段含义应该是自解释的,在此略过。下方是延时信息,第一项"avg"是10秒内的平均延时,最后一项"max"是10秒内的最大延时,其余以百分号结尾的则代表延时分位值,即有左侧这么多比例的请求延时小于右侧的延时(单位微秒)。性能测试需要关注99%之后的长尾区域。 diff --git a/docs/cn/rpc_view.md b/docs/cn/rpc_view.md new file mode 100644 index 0000000..b1abaa6 --- /dev/null +++ b/docs/cn/rpc_view.md @@ -0,0 +1,37 @@ +rpc_view可以转发端口被限的server的内置服务。像百度内如果一个服务的端口不在8000-8999,就只能在命令行下使用curl查看它的内置服务,没有历史趋势和动态曲线,也无法点击链接,排查问题不方便。rpc_view是一个特殊的http proxy:把对它的所有访问都转为对目标server的访问。只要把rpc_view的端口能在浏览器中被访问,我们就能通过它看到原本不能直接看到的server了。 + +# 获取工具 + +先按照[Getting Started](getting_started.md)编译好brpc,再去tools/rpc_view编译。 + +在CentOS 6.3上如果出现找不到libssl.so.4的错误,可执行`ln -s /usr/lib64/libssl.so.6 libssl.so.4临时解决` + +# 访问目标server + +确保你的机器能访问目标server,开发机应该都可以,一些测试机可能不行。运行./rpc_view 就可以了。 + +比如: + +``` +$ ./rpc_view 10.46.130.53:9970 +TRACE: 02-14 12:12:20: * 0 src/brpc/server.cpp:762] Server[rpc_view_server] is serving on port=8888. +TRACE: 02-14 12:12:20: * 0 src/brpc/server.cpp:771] Check out http://XXX.com:8888 in web browser. +``` + +打开rpc_view在8888端口提供的页面(在secureCRT中按住ctrl点url): + +![img](../images/rpc_view_1.png) + +这个页面正是目标server的内置服务,右下角的提示告诉我们这是rpc_view提供的。这个页面和真实的内置服务基本是一样的,你可以做任何操作。 + +# 更换目标server + +你可以随时停掉rpc_view并更换目标server,不过你觉得麻烦的话,也可以在浏览器上操作:给url加上?changetarget=就行了。 + +假如我们之前停留在原目标server的/connections页面: + +![img](../images/rpc_view_2.png) + +加上?changetarge后就跳到新目标server的/connections页面了。接下来点击其他tab都会显示新目标server的。 + +![img](../images/rpc_view_3.png) diff --git a/docs/cn/rpcz.md b/docs/cn/rpcz.md new file mode 100644 index 0000000..12ba2ef --- /dev/null +++ b/docs/cn/rpcz.md @@ -0,0 +1,79 @@ +用户能通过/rpcz看到最近请求的详细信息,并可以插入注释(annotation),不同于tracing system(如[dapper](http://static.googleusercontent.com/media/research.google.com/en//pubs/archive/36356.pdf))以全局视角看到整体系统的延时分布,rpcz更多是一个调试工具,虽然角色有所不同,但在brpc中rpcz和tracing的数据来源是一样的。当每秒请求数小于1万时,rpcz会记录所有的请求,超过1万时,rpcz会随机忽略一些请求把采样数控制在1万左右。rpcz可以淘汰时间窗口之前的数据,通过-span_keeping_seconds选项设置,默认1小时。[一个长期运行的例子](http://brpc.baidu.com:8765/rpcz)。 + +关于开销:我们的实现完全规避了线程竞争,开销极小,在qps 30万的测试场景中,观察不到明显的性能变化,对大部分应用而言应该是“free”的。即使采集了几千万条请求,rpcz也不会增加很多内存,一般在50兆以内。rpcz会占用一些磁盘空间(就像日志一样),如果设定为存一个小时的数据,一般在几百兆左右。 + +## 开关方法 + +默认不开启,加入[-enable_rpcz](http://brpc.baidu.com:8765/flags/*rpcz*)选项会在启动后开启。 + +| Name | Value | Description | Defined At | +| -------------------------- | -------------------- | ---------------------------------------- | -------------------------------------- | +| enable_rpcz (R) | true (default:false) | Turn on rpcz | src/baidu/rpc/builtin/rpcz_service.cpp | +| rpcz_hex_log_id (R) | false | Show log_id in hexadecimal | src/baidu/rpc/builtin/rpcz_service.cpp | +| rpcz_database_dir | ./rpc_data/rpcz | For storing requests/contexts collected by rpcz. | src/baidu/rpc/span.cpp | +| rpcz_keep_span_db | false | Don't remove DB of rpcz at program's exit | src/baidu/rpc/span.cpp | +| rpcz_keep_span_seconds (R) | 3600 | Keep spans for at most so many seconds | src/baidu/rpc/span.cpp | +| rpcz_save_span_min_latency_us (R) | 0 (default:0) | The minimum latency microseconds of span saved | src/baidu/rpc/span.cpp | + +若启动时未加-enable_rpcz,则可在启动后访问SERVER_URL/rpcz/enable动态开启rpcz,访问SERVER_URL/rpcz/disable则关闭,这两个链接等价于访问SERVER_URL/flags/enable_rpcz?setvalue=true和SERVER_URL/flags/enable_rpcz?setvalue=false。在r31010之后,rpc在html版本中增加了一个按钮可视化地开启和关闭。 + +![img](../images/rpcz_4.png) + +![img](../images/rpcz_5.png) + +如果只是brpc client或没有使用brpc,看[这里](dummy_server.md)。 + +## 数据展现 + +/rpcz展现的数据分为两层。 + +### 第一层 + +看到最新请求的概况,点击链接进入第二层。 + +![img](../images/rpcz_6.png) + +### 第二层 + +看到某系列(trace)或某个请求(span)的详细信息。一般通过点击链接进入,也可以把trace=和span=作为query-string拼出链接 + +![img](../images/rpcz_7.png) + +内容说明: + +- 时间分为了绝对时间(如2015/01/21-20:20:30.817392,小数点后精确到微秒)和前一个时间的差值(如. 19,代表19微秒)。 +- trace=ID有点像“session id”,对应一个系统中完成一次对外服务牵涉到的所有服务,即上下游server都共用一个trace-id。span=ID对应一个server或client中一个请求的处理过程。trace-id和span-id在概率上唯一。 +- 第一层页面中的request=和response=后的是数据包的字节数,包括附件但不包括协议meta。第二层中request和response的字节数一般在括号里,比如"Responded(13)"中的13。 +- 点击链接可能会访问其他server上的rpcz,点浏览器后退一般会返回到之前的页面位置。 +- I'm the last call, I'm about to ...都是用户的annotation。 + +## Annotation + +只要你使用了brpc,就可以使用[TRACEPRINTF](https://github.com/apache/brpc/blob/master/src/brpc/traceprintf.h)打印内容到事件流中,比如: + +```c++ +TRACEPRINTF("Hello rpcz %d", 123); +``` + +这条annotation会按其发生时间插入到对应请求的rpcz中。从这个角度看,rpcz是请求级的日志。如果你用TRACEPRINTF打印了沿路的上下文,便可看到请求在每个阶段停留的时间,牵涉到的数据集和参数。这是个很有用的功能。 + +## 跨bthread传递trace上下文 + +有的业务在处理server请求的时候,会创建子bthread,在子bthread中发起rpc调用。默认情况下,子bthread中的rpc调用跟原来的请求无法建立关联,trace就会断掉。这种情况下,可以在创建子bthread时,指定BTHREAD_INHERIT_SPAN标志,来显式地建立trace上文关联,如: + +```c++ +bthread_attr_t attr = { BTHREAD_STACKTYPE_NORMAL, BTHREAD_INHERIT_SPAN, NULL }; +bthread_start_urgent(&tid, &attr, thread_proc, arg); +``` + +### Span生命周期管理 + +brpc使用智能指针(`std::shared_ptr`/`std::weak_ptr`)管理Span对象的生命周期,并通过自旋锁保护并发访问,解决了以下问题: + +1. **Use-after-free防护**:父Span通过`shared_ptr`持有子Span的强引用,TLS中使用`weak_ptr`存储,确保Span对象在被访问时仍然有效。即使server在子bthread完成前返回response,也不会导致访问已释放的Span对象。 + +2. **线程安全**:使用自旋锁保护`_client_list`和`_info`的并发修改,支持多个bthread同时创建子span或添加annotation。 + +3. **自动生命周期管理**:当父Span销毁时,会自动清理所有子Span(通过`_client_list.clear()`),无需手动管理。 + +使用`BTHREAD_INHERIT_SPAN`创建子bthread时,不再需要担心Span对象的生命周期问题,可以安全地在异步场景中使用。 diff --git a/docs/cn/sanitizers.md b/docs/cn/sanitizers.md new file mode 100644 index 0000000..bdd3201 --- /dev/null +++ b/docs/cn/sanitizers.md @@ -0,0 +1,50 @@ +# Sanitizers + +新版本的GCC/Clang支持[sanitizers](https://github.com/google/sanitizers),方便开发者排查代码中的bug。 bRPC对sanitizers提供了一定的支持。 + +## AddressSanitizer(ASan) + +ASan提供了[对协程的支持](https://reviews.llvm.org/D20913)。 在bthread创建、切换、销毁时,让ASan知道当前bthread的栈信息,主要用于维护[fake stack](https://github.com/google/sanitizers/wiki/AddressSanitizerUseAfterReturn)。 + +bRPC中启用ASan的方法:给config_brpc.sh增加`--with-asan`选项、给cmake增加`-DWITH_ASAN=ON`选项或者给bazel增加`--define with_asan=true`选项。 + +另外需要注意的是,ASan没法检测非ASan分配内存或者对象池复用内存。所以我们封装了两个宏,让ASan知道内存块是否能被使用。在非ASan环境下,这两个宏什么也不做,没有开销。 + +```c++ +#include + +BUTIL_ASAN_POISON_MEMORY_REGION(addr, size); +BUTIL_ASAN_UNPOISON_MEMORY_REGION(addr, size); +``` + +如果某些对象池在设计上允许操作对象池中的对象,例如ExecutionQueue、Butex,则需要特化ObjectPoolWithASanPoison,表示不对这些对象池的对象内存进行poison/unpoison,例如: + +```c++ +namespace butil { +// TaskNode::cancel() may access the TaskNode object returned to the ObjectPool, +// so ObjectPool can not poison the memory region of TaskNode. +template <> +struct ObjectPoolWithASanPoison : false_type {}; +} // namespace butil + +namespace butil { +// Butex object returned to the ObjectPool may be accessed, +// so ObjectPool can not poison the memory region of Butex. +template <> +struct ObjectPoolWithASanPoison : false_type {}; +} // namespace butil +``` + +其他问题:如果ASan报告中new/delete的调用栈不完整,可以通过设置`fast_unwind_on_malloc=0`回溯出完整的调用栈了。需要注意的是`fast_unwind_on_malloc=0`很耗性能。 + +## ThreadSanitizer(TSan) + +待支持(TODO) + +## LeakSanitizer(LSan) / MemorySanitizer(MSan) / UndefinedBehaviorSanitizer(UBSan) + +bRPC默认支持这三种sanitizers,编译及链接时加上对应的选项即可启用对应的sanitizers: + +1. LSan: `-fsanitize=leak`; +2. MSan: `-fsanitize=memory`; +3. UBSan: `-fsanitize=undefined`; diff --git a/docs/cn/server.md b/docs/cn/server.md new file mode 100644 index 0000000..bbce852 --- /dev/null +++ b/docs/cn/server.md @@ -0,0 +1,1117 @@ +[English version](../en/server.md) + +# 示例程序 + +Echo的[server端代码](https://github.com/apache/brpc/blob/master/example/echo_c++/server.cpp)。 + +# 填写proto文件 + +请求、回复、服务的接口均定义在proto文件中。 + +```C++ +# 告诉protoc要生成C++ Service基类,如果是java或python,则应分别修改为java_generic_services和py_generic_services +option cc_generic_services = true; + +message EchoRequest { + required string message = 1; +}; +message EchoResponse { + required string message = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; +``` + +protobuf的更多用法请阅读[protobuf官方文档](https://developers.google.com/protocol-buffers/docs/proto#options)。 + +# 实现生成的Service接口 + +protoc运行后会生成echo.pb.cc和echo.pb.h文件,你得include echo.pb.h,实现其中的EchoService基类: + +```c++ +#include "echo.pb.h" +... +class MyEchoService : public EchoService { +public: + void Echo(::google::protobuf::RpcController* cntl_base, + const ::example::EchoRequest* request, + ::example::EchoResponse* response, + ::google::protobuf::Closure* done) { + // 这个对象确保在return时自动调用done->Run() + brpc::ClosureGuard done_guard(done); + + brpc::Controller* cntl = static_cast(cntl_base); + + // 填写response + response->set_message(request->message()); + } +}; +``` + +Service在插入[brpc.Server](https://github.com/apache/brpc/blob/master/src/brpc/server.h)后才可能提供服务。 + +当客户端发来请求时,Echo()会被调用。参数的含义分别是: + +**controller** + +在brpc中可以静态转为brpc::Controller(前提是代码运行brpc.Server中),包含了所有request和response之外的参数集合,具体接口查阅[controller.h](https://github.com/apache/brpc/blob/master/src/brpc/controller.h) + +**request** + +请求,只读的,来自client端的数据包。 + +**response** + +回复。需要用户填充,如果存在**required**字段没有被设置,该次调用会失败。 + +**done** + +done由框架创建,递给服务回调,包含了调用服务回调后的后续动作,包括检查response正确性,序列化,打包,发送等逻辑。 + +**不管成功失败,done->Run()必须在请求处理完成后被用户调用一次。** + +为什么框架不自己调用done->Run()?这是为了允许用户把done保存下来,在服务回调之后的某事件发生时再调用,即实现**异步Service**。 + +强烈建议使用**ClosureGuard**确保done->Run()被调用,即在服务回调开头的那句: + +```c++ +brpc::ClosureGuard done_guard(done); +``` + +不管在中间还是末尾脱离服务回调,都会使done_guard析构,其中会调用done->Run()。这个机制称为[RAII](https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization)。没有这个的话你得在每次return前都加上done->Run(),**极易忘记**。 + +在异步Service中,退出服务回调时请求未处理完成,done->Run()不应被调用,done应被保存下来供以后调用,乍看起来,这里并不需要用ClosureGuard。但在实践中,异步Service照样会因各种原因跳出回调,如果不使用ClosureGuard,一些分支很可能会在return前忘记done->Run(),所以我们也建议在异步service中使用done_guard,与同步Service不同的是,为了避免正常脱离函数时done->Run()也被调用,你可以调用done_guard.release()来释放其中的done。 + +一般来说,同步Service和异步Service分别按如下代码处理done: + +```c++ +class MyFooService: public FooService { +public: + // 同步服务 + void SyncFoo(::google::protobuf::RpcController* cntl_base, + const ::example::EchoRequest* request, + ::example::EchoResponse* response, + ::google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + ... + } + + // 异步服务 + void AsyncFoo(::google::protobuf::RpcController* cntl_base, + const ::example::EchoRequest* request, + ::example::EchoResponse* response, + ::google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + ... + done_guard.release(); + } +}; +``` + +ClosureGuard的接口如下: + +```c++ +// RAII: Call Run() of the closure on destruction. +class ClosureGuard { +public: + ClosureGuard(); + // Constructed with a closure which will be Run() inside dtor. + explicit ClosureGuard(google::protobuf::Closure* done); + + // Call Run() of internal closure if it's not NULL. + ~ClosureGuard(); + + // Call Run() of internal closure if it's not NULL and set it to `done'. + void reset(google::protobuf::Closure* done); + + // Set internal closure to NULL and return the one before set. + google::protobuf::Closure* release(); +}; +``` + +## 标记当前调用为失败 + +调用Controller.SetFailed()可以把当前调用设置为失败,当发送过程出现错误时,框架也会调用这个函数。用户一般是在服务的CallMethod里调用这个函数,比如某个处理环节出错,SetFailed()后确认done->Run()被调用了就可以跳出函数了(若使用了ClosureGuard,跳出函数时会自动调用done,不用手动)。Server端的done的逻辑主要是发送response回client,当其发现用户调用了SetFailed()后,会把错误信息送回client。client收到后,它的Controller::Failed()会为true(成功时为false),Controller::ErrorCode()和Controller::ErrorText()则分别是错误码和错误信息。 + +用户可以为http访问设置[status-code](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html),在server端一般是调用`controller.http_response().set_status_code()`,标准的status-code定义在[http_status_code.h](https://github.com/apache/brpc/blob/master/src/brpc/http_status_code.h)中。Controller.SetFailed也会设置status-code,值是与错误码含义最接近的status-code,没有相关的则填500错误(brpc::HTTP_STATUS_INTERNAL_SERVER_ERROR)。如果你要覆盖status_code,设置代码一定要放在SetFailed()后,而不是之前。 + +## 获取Client的地址 + +`controller->remote_side()`可获得发送该请求的client地址和端口,类型是butil::EndPoint。如果client是nginx,remote_side()是nginx的地址。要获取真实client的地址,可以在nginx里设置`proxy_header ClientIp $remote_addr;`, 在rpc中通过`controller->http_request().GetHeader("ClientIp")`获得对应的值。 + +打印方式: + +```c++ +LOG(INFO) << "remote_side=" << cntl->remote_side(); +printf("remote_side=%s\n", butil::endpoint2str(cntl->remote_side()).c_str()); +``` + +## 获取Server的地址 + +controller->local_side()获得server端的地址,类型是butil::EndPoint。 + +打印方式: + +```c++ +LOG(INFO) << "local_side=" << cntl->local_side(); +printf("local_side=%s\n", butil::endpoint2str(cntl->local_side()).c_str()); +``` + +## 异步Service + +即done->Run()在Service回调之外被调用。 + +有些server以等待后端服务返回结果为主,且处理时间特别长,为了及时地释放出线程资源,更好的办法是把done注册到被等待事件的回调中,等到事件发生后再调用done->Run()。 + +异步service的最后一行一般是done_guard.release()以确保正常退出CallMethod时不会调用done->Run()。例子请看[example/session_data_and_thread_local](https://github.com/apache/brpc/tree/master/example/session_data_and_thread_local/)。 + +Service和Channel都可以使用done来表达后续的操作,但它们是**完全不同**的,请勿混淆: + +* Service的done由框架创建,用户处理请求后调用done把response发回给client。 +* Channel的done由用户创建,待RPC结束后被框架调用以执行用户的后续代码。 + +在一个会访问下游服务的异步服务中会同时接触两者,容易搞混,请注意区分。 + +# 加入Service + +默认构造后的Server不包含任何服务,也不会对外提供服务,仅仅是一个对象。 + +通过如下方法插入你的Service实例。 + +```c++ +int AddService(google::protobuf::Service* service, ServiceOwnership ownership); +``` + +若ownership参数为SERVER_OWNS_SERVICE,Server在析构时会一并删除Service,否则应设为SERVER_DOESNT_OWN_SERVICE。 + +插入MyEchoService代码如下: + +```c++ +brpc::Server server; +MyEchoService my_echo_service; +if (server.AddService(&my_echo_service, brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(FATAL) << "Fail to add my_echo_service"; + return -1; +} +``` + +Server启动后你无法再修改其中的Service。 + +# 启动 + +调用以下[Server](https://github.com/apache/brpc/blob/master/src/brpc/server.h)的接口启动服务。 + +```c++ +int Start(const char* ip_and_port_str, const ServerOptions* opt); +int Start(EndPoint ip_and_port, const ServerOptions* opt); +int Start(int port, const ServerOptions* opt); +int Start(const char *ip_str, PortRange port_range, const ServerOptions *opt); // r32009后增加 +``` + +合法的`ip_and_port_str`: + +- 127.0.0.1:80 # IPV4 +- [::1]:8080 # IPV6 +- unix:path.sock # Unix domain socket + +关于IPV6和Unix domain socket的使用,详见 [EndPoint](endpoint.md)。 + +`options`为NULL时所有参数取默认值,如果你要使用非默认值,这么做就行了: + +```c++ +brpc::ServerOptions options; // 包含了默认值 +options.xxx = yyy; +... +server.Start(..., &options); +``` + +## 监听多个端口 + +一个server只能监听一个端口(不考虑ServerOptions.internal_port),需要监听N个端口就起N个Server。 + +## 多进程监听一个端口 + +启动时开启`reuse_port`这个flag,就可以多进程共同监听一个端口(底层是SO_REUSEPORT)。 + +# 停止 + +```c++ +server.Stop(closewait_ms); // closewait_ms实际无效,出于历史原因未删 +server.Join(); +``` + +Stop()不会阻塞,Join()会。分成两个函数的原因在于当多个Server需要退出时,可以先全部Stop再一起Join,如果一个个Stop/Join,可能得花费Server个数倍的等待时间。 + +不管closewait_ms是什么值,server在退出时会等待所有正在被处理的请求完成,同时对新请求立刻回复ELOGOFF错误以防止新请求加入。这么做的原因在于只要server退出时仍有处理线程运行,就有访问到已释放内存的风险。如果你的server“退不掉”,很有可能是由于某个检索线程没结束或忘记调用done了。 + +当client看到ELOGOFF时,会跳过对应的server,并在其他server上重试对应的请求。所以在一般情况下brpc总是“优雅退出”的,重启或上线时几乎不会或只会丢失很少量的流量。 + +RunUntilAskedToQuit()函数可以在大部分情况下简化server的运转和停止代码。在server.Start后,只需如下代码即会让server运行直到按到Ctrl-C。 + +```c++ +// Wait until Ctrl-C is pressed, then Stop() and Join() the server. +server.RunUntilAskedToQuit(); + +// server已经停止了,这里可以写释放资源的代码。 +``` + +Join()完成后可以修改其中的Service,并重新Start。 + +# 被http/h2访问 + +使用Protobuf的服务通常可以通过http/h2+json访问,存于body的json串可与对应protobuf消息相互自动转化。 + +以[echo server](https://github.com/apache/brpc/blob/master/example/echo_c%2B%2B/server.cpp)为例,你可以用[curl](https://curl.haxx.se/)访问这个服务。 + +```shell +# -H 'Content-Type: application/json' is optional +$ curl -d '{"message":"hello"}' http://brpc.baidu.com:8765/EchoService/Echo +{"message":"hello"} +``` + +注意:也可以指定`Content-Type: application/proto`用http/h2+protobuf二进制串访问服务,序列化性能更好。 + +## json<=>pb + +json字段通过匹配的名字和结构与pb字段一一对应。json中一定要包含pb的required字段,否则转化会失败,对应请求会被拒绝。json中可以包含pb中没有定义的字段,但它们会被丢弃而不会存入pb的unknown字段。转化规则详见[json <=> protobuf](json2pb.md)。 + +开启选项-pb_enum_as_number后,pb中的enum会转化为它的数值而不是名字,比如在`enum MyEnum { Foo = 1; Bar = 2; };`中不开启此选项时MyEnum类型的字段会转化为"Foo"或"Bar",开启后为1或2。此选项同时影响client发出的请求和server返回的回复。由于转化为名字相比数值有更好的前后兼容性,此选项只应用于兼容无法处理enum为名字的老代码。 + +## 兼容早期版本client + +早期的brpc允许一个pb service被http协议访问时不填充pb请求,即使里面有required字段。一般来说这种service会自行解析http请求和设置http回复,并不会访问pb请求。但这也是非常危险的行为,毕竟这是pb service,但pb请求却是未定义的。 + +这种服务在升级到新版本rpc时会遇到障碍,因为brpc已不允许这种行为。为了帮助这种服务升级,brpc允许经过一些设置后不把http body自动转化为pb request(从而可自行处理),方法如下: + +```c++ +brpc::ServiceOptions svc_opt; +svc_opt.ownership = ...; +svc_opt.restful_mappings = ...; +svc_opt.allow_http_body_to_pb = false; //关闭http/h2 body至pb request的自动转化 +server.AddService(service, svc_opt); +``` + +如此设置后service收到http/h2请求后不会尝试把body转化为pb请求,所以pb请求总是未定义状态,用户得在`cntl->request_protocol() == brpc::PROTOCOL_HTTP || cntl->request_protocol() == brpc::PROTOCOL_H2`成立时自行解析body。 + +相应地,当cntl->response_attachment()不为空且pb回复不为空时,框架不再报错,而是直接把cntl->response_attachment()作为回复的body。这个功能和设置allow_http_body_to_pb与否无关。如果放开自由度导致过多的用户犯错,可能会有进一步的调整。 + +# 协议支持 + +server端会自动尝试其支持的协议,无需用户指定。`cntl->protocol()`可获得当前协议。server能从一个listen端口建立不同协议的连接,不需要为不同的协议使用不同的listen端口,一个连接上也可以传输多种协议的数据包, 但一般不会这么做(也不建议),支持的协议有: + +- [百度标准协议](baidu_std.md),显示为"baidu_std",默认启用。 + +- [流式RPC协议](streaming_rpc.md),显示为"streaming_rpc", 默认启用。 + +- http/1.0和http/1.1协议,显示为”http“,默认启用。 + +- http/2和gRPC协议,显示为"h2c"(未加密)或"h2"(加密),默认启用。 + +- RTMP协议,显示为"rtmp", 默认启用。 + +- hulu-pbrpc的协议,显示为"hulu_pbrpc",默认启动。 + +- sofa-pbrpc的协议,显示为”sofa_pbrpc“, 默认启用。 + +- 百盟的协议,显示为”nova_pbrpc“, 默认不启用,开启方式: + + ```c++ + #include + ... + ServerOptions options; + ... + options.nshead_service = new brpc::policy::NovaServiceAdaptor; + ``` + +- public_pbrpc协议,显示为"public_pbrpc",默认不启用,开启方式: + + ```c++ + #include + ... + ServerOptions options; + ... + options.nshead_service = new brpc::policy::PublicPbrpcServiceAdaptor; + ``` + +- nshead+mcpack协议,显示为"nshead_mcpack",默认不启用,开启方式: + + ```c++ + #include + ... + ServerOptions options; + ... + options.nshead_service = new brpc::policy::NsheadMcpackAdaptor; + ``` + + 顾名思义,这个协议的数据包由nshead+mcpack构成,mcpack中不包含特殊字段。不同于用户基于NsheadService的实现,这个协议使用了mcpack2pb,使得一份代码可以同时处理mcpack和pb两种格式。由于没有传递ErrorText的字段,当发生错误时server只能关闭连接。 + +- 和UB相关的协议请阅读[实现NsheadService](nshead_service.md)。 + +如果你有更多的协议需求,可以联系我们。 + +# fork without exec +一般来说,[fork](https://linux.die.net/man/3/fork)出的子进程应尽快调用[exec](https://linux.die.net/man/3/exec)以重置所有状态,中间只应调用满足async-signal-safe的函数。这么使用fork的brpc程序在之前的版本也不会有问题。 + +但在一些场景中,用户想直接运行fork出的子进程,而不调用exec。由于fork只复制其调用者的线程,其余线程便随之消失了。对应到brpc中,bvar会依赖一个sampling_thread采样各种信息,在fork后便消失了,现象是很多bvar归零。 + +最新版本的brpc会在fork后重建这个线程(如有必要),从而使bvar在fork后能正常工作,再次fork也可以。已知问题是fork后cpu profiler不正常。然而,这并不意味着用户可随意地fork,不管是brpc还是上层应用都会大量地创建线程,它们在fork后不会被重建,因为: +* 大部分fork会紧接exec,浪费了重建 +* 给代码编写带来很多的麻烦和复杂度 + +brpc的策略是按需创建这类线程,同时fork without exec必须发生在所有可能创建这些线程的代码前。具体地说,至少**发生在初始化所有Server/Channel/应用代码前**,越早越好,不遵守这个约定的fork会导致程序不正常。另外,不支持fork without exec的lib相当普遍,最好避免这种用法。 + +# 设置 + +## 版本 + +Server.set_version(...)可以为server设置一个名称+版本,可通过/version内置服务访问到。虽然叫做"version“,但设置的值请包含服务名,而不仅仅是一个数字版本。 + +## 关闭闲置连接 + +如果一个连接在ServerOptions.idle_timeout_sec对应的时间内没有读取或写出数据,则被视为”闲置”而被server主动关闭。默认值为-1,代表不开启。 + +打开[-log_idle_connection_close](http://brpc.baidu.com:8765/flags/log_idle_connection_close)后关闭前会打印一条日志。 + +| Name | Value | Description | Defined At | +| ------------------------- | ----- | ---------------------------------------- | ------------------- | +| log_idle_connection_close | false | Print log when an idle connection is closed | src/brpc/socket.cpp | + +## pid_file + +如果设置了此字段,Server启动时会创建一个同名文件,内容为进程号。默认为空。 + +## 在每条日志后打印hostname + +此功能只对[butil/logging.h](https://github.com/apache/brpc/blob/master/src/butil/logging.h)中的日志宏有效。 + +打开[-log_hostname](http://brpc.baidu.com:8765/flags/log_hostname)后每条日志后都会带本机名称,如果所有的日志需要汇总到一起进行分析,这个功能可以帮助你了解某条日志来自哪台机器。 + +## 打印FATAL日志后退出程序 + +此功能只对[butil/logging.h](https://github.com/apache/brpc/blob/master/src/butil/logging.h)中的日志宏有效,glog默认在FATAL日志时crash。 + +打开[-crash_on_fatal_log](http://brpc.baidu.com:8765/flags/crash_on_fatal_log)后如果程序使用LOG(FATAL)打印了异常日志或违反了CHECK宏中的断言,那么程序会在打印日志后abort,这一般也会产生coredump文件,默认不打开。这个开关可在对程序的压力测试中打开,以确认程序没有进入过严重错误的分支。 + +> 一般的惯例是,ERROR表示可容忍的错误,FATAL代表不可逆转的错误。 + +## 最低日志级别 + +此功能由[butil/logging.h](https://github.com/apache/brpc/blob/master/src/butil/logging.h)和glog各自实现,为同名选项。 + +只有**不低于**-minloglevel指定的日志级别的日志才会被打印。这个选项可以动态修改。设置值和日志级别的对应关系:0=INFO 1=NOTICE 2=WARNING 3=ERROR 4=FATAL,默认为0。 + +未打印日志的开销只是一次if判断,也不会评估参数(比如某个参数调用了函数,日志不打,这个函数就不会被调用)。如果日志最终打印到自定义LogSink,那么还要经过LogSink的过滤。 + +## 归还空闲内存至系统 + +选项-free_memory_to_system_interval表示每过这么多秒就尝试向系统归还空闲内存,<= 0表示不开启,默认值为0,若开启建议设为10及以上的值。此功能支持tcmalloc,之前程序中对`MallocExtension::instance()->ReleaseFreeMemory()`的定期调用可改成设置此选项。 + +## 打印发送给client的错误 + +server的框架部分一般不针对个别client打印错误日志,因为当大量client出现错误时,可能导致server高频打印日志而严重影响性能。但有时为了调试问题,或就是需要让server打印错误,打开参数[-log_error_text](http://brpc.baidu.com:8765/flags/log_error_text)即可。 + +## 定制延时的分位值 + +显示的服务延时分位值**默认**为**80** (曾经为50), 90, 99, 99.9, 99.99,前三项可分别通过-bvar_latency_p1, -bvar_latency_p2, -bvar_latency_p3三个gflags定制。 + +以下是正确的设置: +```shell +-bvar_latency_p3=97 # p3从默认99修改为97 +-bvar_latency_p1=60 -bvar_latency_p2=80 -bvar_latency_p3=95 +``` +以下是错误的设置: +```shell +-bvar_latency_p3=100 # 设置值必须在[1,99]闭区间内,gflags解析会失败 +-bvar_latency_p1=-1 # 同上 +``` + +## 设置栈大小 + +brpc的Server是运行在bthread之上,默认栈大小为1MB,而pthread默认栈大小为10MB,所以在pthread上正常运行的程序,在bthread上可能遇到栈不足。 + +可设置如下的gflag以调整栈的大小: +```shell +--stack_size_normal=10000000 # 表示调整栈大小为10M左右 +--tc_stack_normal=1 # 默认为8,表示每个worker缓存的栈的个数(以加快分配速度),size越大,缓存数目可以适当调小(以减少内存占用) +``` +注意:不是说程序coredump就意味着”栈不够大“,只是因为这个试起来最容易,所以优先排除掉可能性。事实上百度内如此多的应用也很少碰到栈不够大的情况。 + +## 限制最大消息 + +为了保护server和client,当server收到的request或client收到的response过大时,server或client会拒收并关闭连接。此最大尺寸由[-max_body_size](http://brpc.baidu.com:8765/flags/max_body_size)控制,单位为字节。 + +超过最大消息时会打印如下错误日志: + +``` +FATAL: 05-10 14:40:05: * 0 src/brpc/input_messenger.cpp:89] A message from 127.0.0.1:35217(protocol=baidu_std) is bigger than 67108864 bytes, the connection will be closed. Set max_body_size to allow bigger messages +``` + +protobuf中有[类似的限制](https://github.com/google/protobuf/blob/master/src/google/protobuf/io/coded_stream.h#L364),出错时会打印如下日志: + +``` +FATAL: 05-10 13:35:02: * 0 google/protobuf/io/coded_stream.cc:156] A protocol message was rejected because it was too big (more than 67108864 bytes). To increase the limit (or to disable these warnings), see CodedInputStream::SetTotalBytesLimit() in google/protobuf/io/coded_stream.h. +``` + +brpc移除了protobuf中的限制,全交由此选项控制,只要-max_body_size足够大,用户就不会看到错误日志。此功能对protobuf的版本没有要求。 + +## 压缩 + +set_response_compress_type()设置response的压缩方式,默认不压缩。 + +注意附件不会被压缩。HTTP body的压缩方法见[这里](http_service.md#压缩response-body)。 + +支持的压缩方法有: + +- brpc::CompressTypeSnappy : [snanpy压缩](http://google.github.io/snappy/),压缩和解压显著快于其他压缩方法,但压缩率最低。 +- brpc::CompressTypeGzip : [gzip压缩](http://en.wikipedia.org/wiki/Gzip),显著慢于snappy,但压缩率高 +- brpc::CompressTypeZlib : [zlib压缩](http://en.wikipedia.org/wiki/Zlib),比gzip快10%~20%,压缩率略好于gzip,但速度仍明显慢于snappy。 + +更具体的性能对比见[Client-压缩](client.md#压缩). + +## 附件 + +baidu_std和hulu_pbrpc协议支持传递附件,这段数据由用户自定义,不经过protobuf的序列化。站在server的角度,设置在Controller.response_attachment()的附件会被client端收到,Controller.request_attachment()则包含了client端送来的附件。 + +附件不会被框架压缩。 + +在http协议中,附件对应[message body](http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html),比如要返回的数据就设置在response_attachment()中。 + +## 开启SSL + +要开启SSL,首先确保代码依赖了最新的openssl库。如果openssl版本很旧,会有严重的安全漏洞,支持的加密算法也少,违背了开启SSL的初衷。然后设置`ServerOptions.ssl_options`,具体见[ssl_options.h](https://github.com/apache/brpc/blob/master/src/brpc/ssl_options.h)。 + +```c++ +// Certificate structure +struct CertInfo { + // Certificate in PEM format. + // Note that CN and alt subjects will be extracted from the certificate, + // and will be used as hostnames. Requests to this hostname (provided SNI + // extension supported) will be encrypted using this certifcate. + // Supported both file path and raw string + std::string certificate; + + // Private key in PEM format. + // Supported both file path and raw string based on prefix: + std::string private_key; + + // Additional hostnames besides those inside the certificate. Wildcards + // are supported but it can only appear once at the beginning (i.e. *.xxx.com). + std::vector sni_filters; +}; + +// SSL options at server side +struct ServerSSLOptions { + // Default certificate which will be loaded into server. Requests + // without hostname or whose hostname doesn't have a corresponding + // certificate will use this certificate. MUST be set to enable SSL. + CertInfo default_cert; + + // Additional certificates which will be loaded into server. These + // provide extra bindings between hostnames and certificates so that + // we can choose different certificates according to different hostnames. + // See `CertInfo' for detail. + std::vector certs; + + // When set, requests without hostname or whose hostname can't be found in + // any of the cerficates above will be dropped. Otherwise, `default_cert' + // will be used. + // Default: false + bool strict_sni; + + // ... Other options +}; +``` + +- Server端开启SSL**必须**要设置一张默认证书`default_cert`(默认SSL连接都用此证书),如果希望server能支持动态选择证书(如根据请求中域名,见[SNI](https://en.wikipedia.org/wiki/Server_Name_Indication)机制),则可以将这些证书加载到`certs`。最后用户还可以在Server运行时,动态增减这些动态证书: + + ```c++ + int AddCertificate(const CertInfo& cert); + int RemoveCertificate(const CertInfo& cert); + int ResetCertificates(const std::vector& certs); + ``` + +- 其余选项还包括:密钥套件选择(推荐密钥ECDHE-RSA-AES256-GCM-SHA384,chrome默认第一优先密钥,安全性很高,但比较耗性能)、session复用等。 + +- 如果想支持应用层协议协商,可通过`alpns`选项设置Server端支持的协议字符串,在Server启动时会校验协议的有效性,多个协议间使用逗号分割。具体使用方式如下: + + ```c++ + ServerSSLOptions ssl_options; + ssl_options.alpns = "http, h2, baidu_std"; + ``` + +- SSL层在协议层之下(作用在Socket层),即开启后,所有协议(如HTTP)都支持用SSL加密后传输到Server,Server端会先进行SSL解密后,再把原始数据送到各个协议中去。 + +- SSL开启后,端口仍然支持非SSL的连接访问,Server会自动判断哪些是SSL,哪些不是。如果要屏蔽非SSL访问,用户可通过`Controller::is_ssl()`判断是否是SSL,同时在[connections](connections.md)内置监控上也可以看到连接的SSL信息。 + +## 验证client身份 + +如果server端要开启验证功能,需要实现`Authenticator`中的接口: + +```c++ +class Authenticator { +public: + // Implement this method to verify credential information `auth_str' from + // `client_addr'. You can fill credential context (result) into `*out_ctx' + // and later fetch this pointer from `Controller'. + // Returns 0 on success, error code otherwise + virtual int VerifyCredential(const std::string& auth_str, + const base::EndPoint& client_addr, + AuthContext* out_ctx) const = 0; +}; + +class AuthContext { +public: + const std::string& user() const; + const std::string& group() const; + const std::string& roles() const; + const std::string& starter() const; + bool is_service() const; +}; +``` + +server的验证是基于连接的。当server收到连接上的第一个请求时,会尝试解析出其中的身份信息部分(如baidu_std里的auth字段、HTTP协议里的Authorization头),然后附带client地址信息一起调用`VerifyCredential`。若返回0,表示验证成功,用户可以把验证后的信息填入`AuthContext`,后续可通过`controller->auth_context()`获取,用户不需要关心其分配和释放。否则表示验证失败,连接会被直接关闭,client访问失败。 + +后续请求默认通过验证,没有认证开销。 + +把实现的`Authenticator`实例赋值到`ServerOptions.auth`,即开启验证功能,需要保证该实例在整个server运行周期内都有效,不能被析构。 + +## worker线程数 + +设置ServerOptions.num_threads即可,默认是cpu core的个数(包含超线程的)。 + +注意: ServerOptions.num_threads仅仅是个**提示**。 + +你不能认为Server就用了这么多线程,因为进程内的所有Server和Channel会共享线程资源,线程总数是所有ServerOptions.num_threads和-bthread_concurrency中的最大值。比如一个程序内有两个Server,num_threads分别为24和36,bthread_concurrency为16。那么worker线程数为max(24, 36, 16) = 36。这不同于其他RPC实现中往往是加起来。 + +Channel没有相应的选项,但可以通过选项-bthread_concurrency调整。 + +另外,brpc**不区分IO线程和处理线程**。brpc知道如何编排IO和处理代码,以获得更高的并发度和线程利用率。 + +## 限制最大并发 + +“并发”可能有两种含义,一种是连接数,一种是同时在处理的请求数。这里提到的是后者。 + +在传统的同步server中,最大并发不会超过工作线程数,设定工作线程数量一般也限制了并发。但brpc的请求运行于bthread中,M个bthread会映射至N个worker中(一般M大于N),所以同步server的并发度可能超过worker数量。另一方面,虽然异步server的并发不受线程数控制,但有时也需要根据其他因素控制并发量。 + +brpc支持设置server级和method级的最大并发,当server或method同时处理的请求数超过并发度限制时,它会立刻给client回复**brpc::ELIMIT**错误,而不会调用服务回调。看到ELIMIT错误的client应重试另一个server。这个选项可以防止server出现过度排队,或用于限制server占用的资源。 + +默认不开启。 + +### 为什么超过最大并发要立刻给client返回错误而不是排队? + +当前server达到最大并发并不意味着集群中的其他server也达到最大并发了,立刻让client获知错误,并去尝试另一台server在全局角度是更好的策略。 + +### 为什么不限制QPS? + +QPS是一个秒级的指标,无法很好地控制瞬间的流量爆发。而最大并发和当前可用的重要资源紧密相关:"工作线程",“槽位”等,能更好地抑制排队。 + +另外当server的延时较为稳定时,限制并发的效果和限制QPS是等价的。但前者实现起来容易多了:只需加减一个代表并发度的计数器。这也是大部分流控都限制并发而不是QPS的原因,比如TCP中的“窗口"即是一种并发度。 + +### 计算最大并发数 + +最大并发度 = 极限QPS * 低负载延时 ([little's law](https://en.wikipedia.org/wiki/Little%27s_law)) + +极限QPS指的是server能达到的最大qps,低负载延时指的是server在没有严重积压请求的前提下时的平均延时。一般的服务上线都会有性能压测,把测得的QPS和延时相乘一般就是该服务的最大并发度。 + +### 限制server级别并发度 + +设置ServerOptions.max_concurrency,默认值0代表不限制。访问内置服务不受此选项限制。 + +Server.ResetMaxConcurrency()可在server启动后动态修改server级别的max_concurrency。 + +### 限制method级别并发度 + +server.MaxConcurrencyOf("...") = ...可设置method级别的max_concurrency。也可以通过设置ServerOptions.method_max_concurrency一次性为所有的method设置最大并发。 +当ServerOptions.method_max_concurrency和server.MaxConcurrencyOf("...")=...同时被设置时,使用server.MaxConcurrencyOf()所设置的值。 + +```c++ +ServerOptions.method_max_concurrency = 20; // Set the default maximum concurrency for all methods +server.MaxConcurrencyOf("example.EchoService.Echo") = 10; // Give priority to the value set by server.MaxConcurrencyOf() +server.MaxConcurrencyOf("example.EchoService", "Echo") = 10; +server.MaxConcurrencyOf(&service, "Echo") = 10; +server.MaxConcurrencyOf("example.EchoService.Echo") = "10"; // You can also assign a string value +``` + +此设置一般**发生在AddService后,server启动前**。当设置失败时(比如对应的method不存在),server会启动失败同时提示用户修正MaxConcurrencyOf设置错误。 + +当method级别和server级别的max_concurrency都被设置时,先检查server级别的,再检查method级别的。 + +注意:没有service级别的max_concurrency。 + +### 使用自适应限流算法 +实际生产环境中,最大并发未必一成不变,在每次上线前逐个压测和设置服务的最大并发也很繁琐。这个时候可以使用自适应限流算法。 + +自适应限流是method级别的。要使用自适应限流算法,把method的最大并发度设置为"auto"即可: + +```c++ +// Set auto concurrency limiter for all methods +brpc::ServerOptions options; +options.method_max_concurrency = "auto"; + +// Set auto concurrency limiter for specific method +server.MaxConcurrencyOf("example.EchoService.Echo") = "auto"; +``` +关于自适应限流的更多细节可以看[这里](auto_concurrency_limiter.md) + +## pthread模式 + +用户代码(客户端的done,服务器端的CallMethod)默认在栈为1MB的bthread中运行。但有些用户代码无法在bthread中运行,比如: + +- JNI会检查stack layout而无法在bthread中运行。 +- 代码中广泛地使用pthread local传递session级别全局数据,在RPC前后均使用了相同的pthread local的数据,且数据有前后依赖性。比如在RPC前往pthread-local保存了一个值,RPC后又读出来希望和之前保存的相等,就会有问题。而像tcmalloc虽然也使用了pthread/LWP local,但每次使用之间没有直接的依赖,是安全的。 + +对于这些情况,brpc提供了pthread模式,开启**-usercode_in_pthread**后,用户代码均会在pthread中运行,原先阻塞bthread的函数转而阻塞pthread。 + +注意:开启-usercode_in_pthread后,brpc::thread_local_data()不保证能获取到值。 + +打开pthread模式后在性能上的注意点: + +- 同步RPC都会阻塞worker pthread,server端一般需要设置更多的工作线程(ServerOptions.num_threads),调度效率会略微降低。 +- 运行用户代码的仍然是bthread,只是很特殊,会直接使用pthread worker的栈。这些特殊bthread的调度方式和其他bthread是一致的,这方面性能差异很小。 +- bthread支持一个独特的功能:把当前使用的pthread worker 让给另一个新创建的bthread运行,以消除一次上下文切换。brpc client利用了这点,从而使一次RPC过程中3次上下文切换变为了2次。在高QPS系统中,消除上下文切换可以明显改善性能和延时分布。但pthread模式不具备这个能力,在高QPS系统中性能会有一定下降。 +- pthread模式中线程资源是硬限,一旦线程被打满,请求就会迅速拥塞而造成大量超时。一个常见的例子是:下游服务大量超时后,上游服务可能由于线程大都在等待下游也被打满从而影响性能。开启pthread模式后请考虑设置ServerOptions.max_concurrency以控制server的最大并发。而在bthread模式中bthread个数是软限,对此类问题的反应会更加平滑。 + +pthread模式可以让一些老代码快速尝试brpc,但我们仍然建议逐渐地把代码改造为使用bthread local或最好不用TLS,从而最终能关闭这个开关。 + +## 安全 + +如果你的服务流量来自外部(包括经过nginx等转发),你需要注意一些安全因素: + +### 对外隐藏内置服务 + +内置服务很有用,但包含了大量内部信息,不应对外暴露。有多种方式可以对外隐藏内置服务: + +- 设置内部端口。把ServerOptions.internal_port设为一个**仅允许内网访问**的端口。你可通过internal_port访问到内置服务,但通过对外端口(Server.Start时传入的那个)访问内置服务时将看到如下错误: + + ``` + [a27eda84bcdeef529a76f22872b78305] Not allowed to access builtin services, try ServerOptions.internal_port=... instead if you're inside internal network + ``` + +- http proxy指定转发路径。nginx等可配置URL的映射关系,比如下面的配置把访问/MyAPI的外部流量映射到`target-server`的`/ServiceName/MethodName`。当外部流量尝试访问内置服务,比如说/status时,将直接被nginx拒绝。 +```nginx + location /MyAPI { + ... + proxy_pass http:///ServiceName/MethodName$query_string # $query_string是nginx变量,更多变量请查询http://nginx.org/en/docs/http/ngx_http_core_module.html + ... + } +``` +**请勿在对外服务上开启**-enable_dir_service和-enable_threads_service两个选项,它们虽然很方便,但会严重泄露服务器上的其他信息。检查对外的rpc服务是否打开了这两个开关: + +```shell +curl -s -m 1 :/flags/enable_dir_service,enable_threads_service | awk '{if($3=="false"){++falsecnt}else if($3=="Value"){isrpc=1}}END{if(isrpc!=1||falsecnt==2){print "SAFE"}else{print "NOT SAFE"}}' +``` +### 完全禁用内置服务 + +设置ServerOptions.has_builtin_services = false,可以完全禁用内置服务。 + +### 转义外部可控的URL + +可调用brpc::WebEscape()对url进行转义,防止恶意URI注入攻击。 + +### 不返回内部server地址 + +可以考虑对server地址做签名。比如在设置ServerOptions.internal_port后,server返回的错误信息中的IP信息是其MD5签名,而不是明文。 + +### 不以root用户启动brpc进程 + +由于brpc在运行过程中会写入各种文件(如server pid文件、rpcz、rpc dump、profiling等),如果brpc以root用户运行,攻击者可能利用这一特性进行文件的越权写入。因此,无论brpc是否对外提供网络服务,都不建议以root用户启动brpc进程。 + +## 定制/health页面 + +/health页面默认返回"OK",若需定制/health页面的内容:先继承[HealthReporter](https://github.com/apache/brpc/blob/master/src/brpc/health_reporter.h),在其中实现生成页面的逻辑(就像实现其他http service那样),然后把实例赋给ServerOptions.health_reporter,这个实例不被server拥有,必须保证在server运行期间有效。用户在定制逻辑中可以根据业务的运行状态返回更多样的状态信息。 + +## 线程私有变量 + +百度内的检索程序大量地使用了[thread-local storage](https://en.wikipedia.org/wiki/Thread-local_storage) (缩写TLS),有些是为了缓存频繁访问的对象以避免反复创建,有些则是为了在全局函数间隐式地传递状态。你应当尽量避免后者,这样的函数难以测试,不设置thread-local变量甚至无法运行。brpc中有三套机制解决和thread-local相关的问题。 + +### session-local + +session-local data与一次server端RPC绑定: 从进入service回调开始,到调用server端的done结束,不管该service是同步还是异步处理。 session-local data会尽量被重用,在server停止前不会被删除。 + +设置ServerOptions.session_local_data_factory后访问Controller.session_local_data()即可获得session-local数据。若没有设置,Controller.session_local_data()总是返回NULL。 + +若ServerOptions.reserved_session_local_data大于0,Server会在提供服务前就创建这么多个数据。 + +**示例用法** + +```c++ +struct MySessionLocalData { + MySessionLocalData() : x(123) {} + int x; +}; + +class EchoServiceImpl : public example::EchoService { +public: + ... + void Echo(google::protobuf::RpcController* cntl_base, + const example::EchoRequest* request, + example::EchoResponse* response, + google::protobuf::Closure* done) { + ... + brpc::Controller* cntl = static_cast(cntl_base); + + // Get the session-local data which is created by ServerOptions.session_local_data_factory + // and reused between different RPC. + MySessionLocalData* sd = static_cast(cntl->session_local_data()); + if (sd == NULL) { + cntl->SetFailed("Require ServerOptions.session_local_data_factory to be set with a correctly implemented instance"); + return; + } + ... +``` + +```c++ +struct ServerOptions { + ... + // The factory to create/destroy data attached to each RPC session. + // If this field is NULL, Controller::session_local_data() is always NULL. + // NOT owned by Server and must be valid when Server is running. + // Default: NULL + const DataFactory* session_local_data_factory; + + // Prepare so many session-local data before server starts, so that calls + // to Controller::session_local_data() get data directly rather than + // calling session_local_data_factory->Create() at first time. Useful when + // Create() is slow, otherwise the RPC session may be blocked by the + // creation of data and not served within timeout. + // Default: 0 + size_t reserved_session_local_data; +}; +``` + +session_local_data_factory的类型为[DataFactory](https://github.com/apache/brpc/blob/master/src/brpc/data_factory.h),你需要实现其中的CreateData和DestroyData。 + +注意:CreateData和DestroyData会被多个线程同时调用,必须线程安全。 + +```c++ +class MySessionLocalDataFactory : public brpc::DataFactory { +public: + void* CreateData() const { + return new MySessionLocalData; + } + void DestroyData(void* d) const { + delete static_cast(d); + } +}; + +MySessionLocalDataFactory g_session_local_data_factory; + +int main(int argc, char* argv[]) { + ... + + brpc::Server server; + brpc::ServerOptions options; + ... + options.session_local_data_factory = &g_session_local_data_factory; + ... +``` + +### server-thread-local + +server-thread-local与一次service回调绑定,从进service回调开始,到出service回调结束。所有的server-thread-local data会被尽量重用,在server停止前不会被删除。在实现上server-thread-local是一个特殊的bthread-local。 + +设置ServerOptions.thread_local_data_factory后访问brpc::thread_local_data()即可获得thread-local数据。若没有设置,brpc::thread_local_data()总是返回NULL。 + +若ServerOptions.reserved_thread_local_data大于0,Server会在启动前就创建这么多个数据。 + +**与session-local的区别** + +session-local data得从server端的Controller获得, server-thread-local可以在任意函数中获得,只要这个函数直接或间接地运行在server线程中。 + +当service是同步时,session-local和server-thread-local基本没有差别,除了前者需要Controller创建。当service是异步时,且你需要在done->Run()中访问到数据,这时只能用session-local,因为server-thread-local在service回调外已经失效。 + +**示例用法** + +```c++ +struct MyThreadLocalData { + MyThreadLocalData() : y(0) {} + int y; +}; + +class EchoServiceImpl : public example::EchoService { +public: + ... + void Echo(google::protobuf::RpcController* cntl_base, + const example::EchoRequest* request, + example::EchoResponse* response, + google::protobuf::Closure* done) { + ... + brpc::Controller* cntl = static_cast(cntl_base); + + // Get the thread-local data which is created by ServerOptions.thread_local_data_factory + // and reused between different threads. + // "tls" is short for "thread local storage". + MyThreadLocalData* tls = static_cast(brpc::thread_local_data()); + if (tls == NULL) { + cntl->SetFailed("Require ServerOptions.thread_local_data_factory " + "to be set with a correctly implemented instance"); + return; + } + ... +``` + +```c++ +struct ServerOptions { + ... + // The factory to create/destroy data attached to each searching thread + // in server. + // If this field is NULL, brpc::thread_local_data() is always NULL. + // NOT owned by Server and must be valid when Server is running. + // Default: NULL + const DataFactory* thread_local_data_factory; + + // Prepare so many thread-local data before server starts, so that calls + // to brpc::thread_local_data() get data directly rather than calling + // thread_local_data_factory->Create() at first time. Useful when Create() + // is slow, otherwise the RPC session may be blocked by the creation + // of data and not served within timeout. + // Default: 0 + size_t reserved_thread_local_data; +}; +``` + +thread_local_data_factory的类型为[DataFactory](https://github.com/apache/brpc/blob/master/src/brpc/data_factory.h),你需要实现其中的CreateData和DestroyData。 + +注意:CreateData和DestroyData会被多个线程同时调用,必须线程安全。 + +```c++ +class MyThreadLocalDataFactory : public brpc::DataFactory { +public: + void* CreateData() const { + return new MyThreadLocalData; + } + void DestroyData(void* d) const { + delete static_cast(d); + } +}; + +MyThreadLocalDataFactory g_thread_local_data_factory; + +int main(int argc, char* argv[]) { + ... + + brpc::Server server; + brpc::ServerOptions options; + ... + options.thread_local_data_factory = &g_thread_local_data_factory; + ... +``` + +### bthread-local + +Session-local和server-thread-local对大部分server已经够用。不过在一些情况下,我们需要更通用的thread-local方案。在这种情况下,你可以使用bthread_key_create, bthread_key_destroy, bthread_getspecific, bthread_setspecific等函数,它们的用法类似[pthread中的函数](http://linux.die.net/man/3/pthread_key_create)。 + +这些函数同时支持bthread和pthread,当它们在bthread中被调用时,获得的是bthread私有变量; 当它们在pthread中被调用时,获得的是pthread私有变量。但注意,这里的“pthread私有变量”不是通过pthread_key_create创建的,使用pthread_key_create创建的pthread-local是无法被bthread_getspecific访问到的,这是两个独立的体系。由gcc的__thread,c++11的thread_local等声明的私有变量也无法被bthread_getspecific访问到。 + +由于brpc会为每个请求建立一个bthread,server中的bthread-local行为特殊:一个server创建的bthread在退出时并不删除bthread-local,而是还回server的一个pool中,以被其他bthread复用。这可以避免bthread-local随着bthread的创建和退出而不停地构造和析构。这对于用户是透明的。 + +**主要接口** + +```c++ +// Create a key value identifying a slot in a thread-specific data area. +// Each thread maintains a distinct thread-specific data area. +// `destructor', if non-NULL, is called with the value associated to that key +// when the key is destroyed. `destructor' is not called if the value +// associated is NULL when the key is destroyed. +// Returns 0 on success, error code otherwise. +extern int bthread_key_create(bthread_key_t* key, void (*destructor)(void* data)); + +// Delete a key previously returned by bthread_key_create(). +// It is the responsibility of the application to free the data related to +// the deleted key in any running thread. No destructor is invoked by +// this function. Any destructor that may have been associated with key +// will no longer be called upon thread exit. +// Returns 0 on success, error code otherwise. +extern int bthread_key_delete(bthread_key_t key); + +// Store `data' in the thread-specific slot identified by `key'. +// bthread_setspecific() is callable from within destructor. If the application +// does so, destructors will be repeatedly called for at most +// PTHREAD_DESTRUCTOR_ITERATIONS times to clear the slots. +// NOTE: If the thread is not created by brpc server and lifetime is +// very short(doing a little thing and exit), avoid using bthread-local. The +// reason is that bthread-local always allocate keytable on first call to +// bthread_setspecific, the overhead is negligible in long-lived threads, +// but noticeable in shortly-lived threads. Threads in brpc server +// are special since they reuse keytables from a bthread_keytable_pool_t +// in the server. +// Returns 0 on success, error code otherwise. +// If the key is invalid or deleted, return EINVAL. +extern int bthread_setspecific(bthread_key_t key, void* data); + +// Return current value of the thread-specific slot identified by `key'. +// If bthread_setspecific() had not been called in the thread, return NULL. +// If the key is invalid or deleted, return NULL. +extern void* bthread_getspecific(bthread_key_t key); +``` + +**使用方法** + +用bthread_key_create创建一个bthread_key_t,它代表一种bthread私有变量。 + +用bthread_[get|set]specific查询和设置bthread私有变量。一个线程中第一次访问某个私有变量返回NULL。 + +在所有线程都不使用和某个bthread_key_t相关的私有变量后再删除它。如果删除了一个仍在被使用的bthread_key_t,相关的私有变量就泄露了。 + +```c++ +static void my_data_destructor(void* data) { + ... +} + +bthread_key_t tls_key; + +if (bthread_key_create(&tls_key, my_data_destructor) != 0) { + LOG(ERROR) << "Fail to create tls_key"; + return -1; +} +``` +```c++ +// in some thread ... +MyThreadLocalData* tls = static_cast(bthread_getspecific(tls_key)); +if (tls == NULL) { // First call to bthread_getspecific (and before any bthread_setspecific) returns NULL + tls = new MyThreadLocalData; // Create thread-local data on demand. + CHECK_EQ(0, bthread_setspecific(tls_key, tls)); // set the data so that next time bthread_getspecific in the thread returns the data. +} +``` +**示例代码** + +```c++ +static void my_thread_local_data_deleter(void* d) { + delete static_cast(d); +} + +class EchoServiceImpl : public example::EchoService { +public: + EchoServiceImpl() { + CHECK_EQ(0, bthread_key_create(&_tls2_key, my_thread_local_data_deleter)); + } + ~EchoServiceImpl() { + CHECK_EQ(0, bthread_key_delete(_tls2_key)); + }; + ... +private: + bthread_key_t _tls2_key; +} + +class EchoServiceImpl : public example::EchoService { +public: + ... + void Echo(google::protobuf::RpcController* cntl_base, + const example::EchoRequest* request, + example::EchoResponse* response, + google::protobuf::Closure* done) { + ... + // You can create bthread-local data for your own. + // The interfaces are similar with pthread equivalence: + // pthread_key_create -> bthread_key_create + // pthread_key_delete -> bthread_key_delete + // pthread_getspecific -> bthread_getspecific + // pthread_setspecific -> bthread_setspecific + MyThreadLocalData* tls2 = static_cast(bthread_getspecific(_tls2_key)); + if (tls2 == NULL) { + tls2 = new MyThreadLocalData; + CHECK_EQ(0, bthread_setspecific(_tls2_key, tls2)); + } + ... +``` + +## RPC Protobuf message factory + +Server默认使用`DefaultRpcPBMessageFactory`。它是一个简单的工厂类,通过`new`来创建请求/响应message和`delete`来销毁请求/响应message。 + +如果用户希望自定义创建销毁机制,可以实现`RpcPBMessages`(请求/响应message的封装)和`RpcPBMessageFactory`(工厂类),并设置`ServerOptions.rpc_pb_message_factory`为自定义的`RpcPBMessageFactory`。注意:server启动后,server拥有了`RpcPBMessageFactory`的所有权。 + +接口如下: + +```c++ +// Inherit this class to customize rpc protobuf messages, +// include request and response. +class RpcPBMessages { +public: + virtual ~RpcPBMessages() = default; + // Get protobuf request message. + virtual google::protobuf::Message* Request() = 0; + // Get protobuf response message. + virtual google::protobuf::Message* Response() = 0; +}; + +// Factory to manage `RpcPBMessages'. +class RpcPBMessageFactory { +public: + virtual ~RpcPBMessageFactory() = default; + + // Get `RpcPBMessages' according to `service' and `method'. + // Common practice to create protobuf message: + // service.GetRequestPrototype(&method).New() -> request; + // service.GetResponsePrototype(&method).New() -> response. + virtual RpcPBMessages* Get(const ::google::protobuf::Service& service, + const ::google::protobuf::MethodDescriptor& method) = 0; + // Return `RpcPBMessages' to factory. + virtual void Return(RpcPBMessages* messages) = 0; +}; +``` + +### Protobuf arena + +Protobuf arena是一种Protobuf message内存管理机制,有着提高内存分配效率、减少内存碎片、对缓存友好等优点。详细信息见[C++ Arena Allocation Guide](https://protobuf.dev/reference/cpp/arenas/)。 + +如果用户希望使用protobuf arena来管理Protobuf message内存,可以设置`ServerOptions.rpc_pb_message_factory = brpc::GetArenaRpcPBMessageFactory();`,使用默认的`start_block_size`(256 bytes)和`max_block_size`(8192 bytes)来创建arena。用户可以调用`brpc::GetArenaRpcPBMessageFactory();`自定义arena大小。 + +注意:从Protobuf v3.14.0开始,[默认开启arena](https://github.com/protocolbuffers/protobuf/releases/tag/v3.14.0https://github.com/protocolbuffers/protobuf/releases/tag/v3.14.0)。但是Protobuf v3.14.0之前的版本,用户需要再proto文件中加上选项:`option cc_enable_arenas = true;`,所以为了兼容性,可以统一都加上该选项。 + +## server端忽略eovercrowded +### server级别忽略eovercrowded +设置ServerOptions.ignore_eovercrowded,默认值0代表不忽略 + +### method级别忽略eovercrowded +server.IgnoreEovercrowdedOf("...") = ...可设置method级别的ignore_eovercrowded。也可以通过设置ServerOptions.ignore_eovercrowded一次性为所有的method设置忽略eovercrowded。 + +```c++ +ServerOptions.ignore_eovercrowded = true; // Set the default ignore_eovercrowded for all methods +server.IgnoreEovercrowdedOf("example.EchoService.Echo") = true; +``` + +此设置一般**发生在AddService后,server启动前**。当设置失败时(比如对应的method不存在),server会启动失败同时提示用户修正IgnoreEovercrowdedOf设置错误。 + +当ServerOptions.ignore_eovercrowded和server.IgnoreEovercrowdedOf("...")=...同时被设置时,任何一个设置为true,就表示会忽略eovercrowded。 + +注意:没有service级别的ignore_eovercrowded。 + +# FAQ + +### Q: Fail to write into fd=1865 SocketId=8905@10.208.245.43:54742@8230: Got EOF是什么意思 + +A: 一般是client端使用了连接池或短连接模式,在RPC超时后会关闭连接,server写回response时发现连接已经关了就报这个错。Got EOF就是指之前已经收到了EOF(对端正常关闭了连接)。client端使用单连接模式server端一般不会报这个。 + +### Q: Remote side of fd=9 SocketId=2@10.94.66.55:8000 was closed是什么意思 + +这不是错误,是常见的warning,表示对端关掉连接了(EOF)。这个日志有时对排查问题有帮助。 + +默认关闭,把参数-log_connection_close设置为true就打开了(支持[动态修改](flags.md#change-gflag-on-the-fly))。 + +### Q: 为什么server端线程数设了没用 + +brpc同一个进程中所有的server[共用线程](#worker线程数),如果创建了多个server,最终的工作线程数多半是最大的那个ServerOptions.num_threads。 + +### Q: 为什么client端的延时远大于server端的延时 + +可能是server端的工作线程不够用了,出现了排队现象。排查方法请查看[高效率排查服务卡顿](server_debugging.md)。 + +### Q: Fail to open /proc/self/io + +有些内核没这个文件,不影响服务正确性,但如下几个bvar会无法更新: +``` +process_io_read_bytes_second +process_io_write_bytes_second +process_io_read_second +process_io_write_second +``` +### Q: json串"[1,2,3]"没法直接转为protobuf message + +这不是标准的json。最外层必须是花括号{}包围的json object。 + +# 附:Server端基本流程 + +![img](../images/server_side.png) diff --git a/docs/cn/server_debugging.md b/docs/cn/server_debugging.md new file mode 100644 index 0000000..4200857 --- /dev/null +++ b/docs/cn/server_debugging.md @@ -0,0 +1,161 @@ +# 1.检查工作线程的数量 + +查看 /vars/bthread_worker_**count** 和 /vars/bthread_worker_**usage**。分别是工作线程的个数,和正在被使用的工作线程个数。 + +> 如果usage和count接近,说明线程不够用了。 + +比如,下图中有24个工作线程,正在使用的是23.93个,说明所有的工作线程都被打满了,不够用了。 + +![img](../images/full_worker_usage.png) + +下图中正在使用的只有2.36个,工作线程明显是足够的。 + +![img](../images/normal_worker_usage.png) + +把 /vars/bthread_worker_count;bthread_worker_usage?expand 拼在服务url后直接看到这两幅图,就像[这样](http://brpc.baidu.com:8765/vars/bthread_worker_count;bthread_worker_usage?expand)。 + +# 2.检查CPU的使用程度 + +查看 /vars/system_core_**count** 和 /vars/process_cpu_**usage**。分别是cpu核心的个数,和正在使用的cpu核数。 + +> 如果usage和count接近,说明CPU不够用了。 + +下图中cpu核数为24,正在使用的核心数是20.9个,CPU是瓶颈了。 + +![img](../images/high_cpu_usage.png) + +下图中正在使用的核心数是2.06,CPU是够用的。 + +![img](../images/normal_cpu_usage.png) + +# 3.定位问题 + +如果process_cpu_usage和bthread_worker_usage接近,说明是cpu-bound,工作线程大部分时间在做计算。 + +如果process_cpu_usage明显小于bthread_worker_usage,说明是io-bound,工作线程大部分时间在阻塞。 + +1 - process_cpu_usage / bthread_worker_usage就是大约在阻塞上花费的时间比例,比如process_cpu_usage = 2.4,bthread_worker_usage = 18.5,那么工作线程大约花费了87.1% 的时间在阻塞上。 + +## 3.1 定位cpu-bound问题 + +原因可能是单机性能不足,或上游分流不均。 + +### 排除上游分流不均的嫌疑 + +在不同服务的[vars界面](http://brpc.baidu.com:8765/vars)输入qps,查看不同的qps是否符合预期,就像这样: + +![img](../images/bthread_creation_qps.png) + +或者在命令行中用curl直接访问,像这样: + +```shell +$ curl brpc.baidu.com:8765/vars/*qps* +bthread_creation_qps : 95 +rpc_server_8765_example_echo_service_echo_qps : 57 +``` + +如果不同机器的分流确实不均,且难以解决,可以考虑[限制最大并发](server.md#限制最大并发)。 + +### 优化单机性能 + +请使用[CPU profiler](cpu_profiler.md)分析程序的热点,用数据驱动优化。一般来说一个卡顿的cpu-bound程序一般能看到显著的热点。 + +## 3.2 定位io-bound问题 + +原因可能有: + +- 线程确实配少了 +- 访问下游服务的client不支持bthread,且延时过长 +- 阻塞来自程序内部的锁,IO等等。 + +如果阻塞无法避免,考虑用异步。 + +### 排除工作线程数不够的嫌疑 + +如果线程数不够,你可以尝试动态调大工作线程数,切换到/flags页面,点击bthread_concurrency右边的(R): + +![img](../images/bthread_concurrency_1.png) + +进入后填入新的线程数确认即可: + +![img](../images/bthread_concurrency_2.png) + +回到/flags界面可以看到bthread_concurrency已变成了新值。 + +![img](../images/bthread_concurrency_3.png) + +不过,调大线程数未必有用。如果工作线程是由于访问下游而大量阻塞,调大工作线程数是没有用的。因为真正的瓶颈在于后端的,调大线程后只是让每个线程的阻塞时间变得更长。 + +比如在我们这的例子中,调大线程后新增的工作线程仍然被打满了。 + +![img](../images/full_worker_usage_2.png) + +### 排除锁的嫌疑 + +如果程序被某把锁挡住了,也可能呈现出“io-bound”的特征。先用[contention profiler](contention_profiler.md)排查锁的竞争状况。 + +### 使用rpcz + +rpcz可以帮助你看到最近的所有请求,和处理它们时在每个阶段花费的时间(单位都是微秒)。 + +![img](../images/rpcz.png) + +点击一个span链接后看到该次RPC何时开始,每个阶段花费的时间,何时结束。 + +![img](../images/rpcz_2.png) + +这是一个典型的server在严重阻塞的例子。从接收到请求到开始运行花费了20ms,说明server已经没有足够的工作线程来及时完成工作了。 + +现在这个span的信息比较少,我们去程序里加一些。你可以使用TRACEPRINTF向rpcz打印日志。打印内容会嵌入在rpcz的时间流中。 + +![img](../images/trace_printf.png) + +重新运行后,查看一个span,里面的打印内容果然包含了我们增加的TRACEPRINTF。 + +![img](../images/rpcz_3.png) + +在运行到第一条TRACEPRINTF前,用户回调已运行了2051微秒(假设这符合我们的预期),紧接着foobar()却花费了8036微秒,我们本来以为这个函数会很快返回的。范围进一步缩小了。 + +重复这个过程,直到找到那个造成问题的函数。 + +### 使用bvar + +TRACEPRINTF主要适合若干次的函数调用,如果一个函数调用了很多次,或者函数本身开销很小,每次都往rpcz打印日志是不合适的。这时候你可以使用bvar。 + +[bvar](bvar.md)是一个多线程下的计数库,可以以极低的开销统计用户递来的数值,相比“打日志大法”几乎不影响程序行为。你不用完全了解bvar的完整用法,只要使用bvar::LatencyRecorder即可。 + +仿照如下代码对foobar的运行时间进行监控。 + +```c++ +#include +#include + +bvar::LatencyRecorder g_foobar_latency("foobar"); + +... +void search() { + ... + butil::Timer tm; + tm.start(); + foobar(); + tm.stop(); + g_foobar_latency << tm.u_elapsed(); + ... +} +``` + +重新运行程序后,在vars的搜索框中键入foobar,显示如下: + +![img](../images/foobar_bvar.png) + +点击一个bvar可以看到动态图,比如点击cdf后看到 + +![img](../images/foobar_latency_cdf.png) + +根据延时的分布,你可以推测出这个函数的整体行为,对大多数请求表现如何,对长尾表现如何。 + +你可以在子函数中继续这个过程,增加更多bvar,并比对不同的分布,最后定位来源。 + +### 只使用了brpc client + +得打开dummy server提供内置服务,方法见[这里](dummy_server.md)。 diff --git a/docs/cn/server_push.md b/docs/cn/server_push.md new file mode 100644 index 0000000..5df8e59 --- /dev/null +++ b/docs/cn/server_push.md @@ -0,0 +1,28 @@ +[English version](../en/server_push.md) + +# Server push + +server push指的是server端发生某事件后立刻向client端发送消息,而不是像通常的RPC访问中那样被动地回复client。brpc中推荐以如下两种方式实现推送。 + +# 远程事件 + +和本地事件类似,分为两步:注册和通知。client发送一个代表**事件注册**的异步RPC至server,处理事件的代码写在对应的RPC回调中。此RPC同时也在等待通知,server收到请求后不直接回复,而是等到对应的本地事件触发时才调用done->Run()**通知**client发生了事件。可以看到server也是异步的。这个过程中如果连接断开,client端的RPC一般会很快失败,client可选择重试或结束。server端应通过Controller.NotifyOnCancel()及时获知连接断开的消息,并删除无用的done。 + +这个模式在原理上类似[long polling](https://en.wikipedia.org/wiki/Push_technology#Long_polling),听上去挺古老,但可能仍是最有效的推送方式。“server push“乍看是server访问client,与常见的client访问server方向相反,挺特殊的,但server发回client的response不也和“client访问server”方向相反么?为了理解response和push的区别,我们假定“client随时可能收到server推来的消息“,并推敲其中的细节: + +* client首先得认识server发来的消息,否则是鸡同鸭讲。 +* client还得知道怎么应对server发来的消息,如果client上没有对应的处理代码,仍然没用。 + +换句话说,client得对server消息“有准备”,这种“准备”还往往依赖client当时的上下文。综合来看,由client告知server“我准备好了”(注册),之后server再通知client是更普适的模式,**这个模式中的"push"就是"response"**,一个超时很长或无限长RPC的response。 + +在一些非常明确的场景中,注册可以被简略,如http2中的[push promise](https://tools.ietf.org/html/rfc7540#section-8.2)并不需要浏览器(client)向server注册,因为client和server都知道它们之间的任务就是让client尽快地下载必要的资源。由于每个资源有唯一的URI,server可以直接向client推送资源及其URI,client看到这些资源时会缓存下来避免下次重复访问。类似的,一些协议提供的"双向通信"也是在限定的场景中提高推送效率,而不是实现通用的推送,比如多媒体流,格式固定的key/value对等。client默认能处理server所有可能推送的消息,以至于不需要额外的注册。但推送仍可被视作"response":client和server早已约定好的请求的response。 + +# Restful回调 + +client希望在事件发生时调用一个给定的URL,并附上必要的参数。在这个模式中,server在收到client注册请求时可以直接回复,因为事件不由注册用RPC的结束触发。由于回调只是一个URL,可以存放于数据库或经消息队列流转,这个模式灵活性很高,在业务系统中使用广泛。 + +URL和参数中必须有足够的信息使回调知道这次调用对应某次注册,因为client未必一次只关心一个事件,即使一个事件也可能由于网络抖动、机器重启等因素注册多次。如果回调是固定路径,client应在注册请求中置入一个唯一ID,在回调时带回。或者client为每次注册生成唯一路径,自然也可以区分。本质上这两种形式是一样的,只是唯一标志符出现的位置不同。 + +回调应处理[幂等问题](https://en.wikipedia.org/wiki/Idempotence),server为了确保不漏通知,在网络出现问题时往往会多次重试,如果第一次的通知已经成功了,后续的通知就应该不产生效果。上节“远程事件”模式中的幂等性由RPC代劳,它会确保done只被调用一次。 + +为了避免重要的通知被漏掉,用户往往还需灵活组合RPC和消息队列。RPC的时效性和开销都明显好于消息队列,但由于内存有限,在重试过一些次数后仍然失败的话,server就得把这部分内存空出来去做其他事情了。这时把通知放到消息队列中,利用其持久化能力做较长时间的重试直至成功,辅以回调的幂等性,就能使绝大部分通知既及时又不会被漏掉。 \ No newline at end of file diff --git a/docs/cn/status.md b/docs/cn/status.md new file mode 100644 index 0000000..a9acdb0 --- /dev/null +++ b/docs/cn/status.md @@ -0,0 +1,37 @@ +[English version](../en/status.md) + +[/status](http://brpc.baidu.com:8765/status)可以访问服务的主要统计信息。这些信息和/vars是同源的,但按服务重新组织方便查看。 + +![img](../images/status.png) + +上图中字段的含义分别是: + +- **non_service_error**: 在service处理过程之外的错误个数。当获取到合法的service,之后发生的错误就算*service_error*,否则算*non_service_error*(比如请求解析失败,service名称不存在,请求并发度超限被拒绝等)。作为对比,服务过程中对后端服务的访问错误不是*non_service_error*。即使写出的response代表错误,此error也被记入对应的service,而不是*non_service_error*。 +- **connection_count**: 向该server发起请求的连接个数。不包含记录在/vars/rpc_channel_connection_count的对外连接的个数。 +- **example.EchoService**: 服务的完整名称,包含proto中的包名。 +- **Echo (EchoRequest) returns (EchoResponse)**: 方法签名,一个服务可包含多个方法,点击request/response上的链接可查看对应的protobuf结构体。 +- **count**: 成功处理的请求总个数。 +- **error**: 失败的请求总个数。 +- **latency**: 在html下是*从右到左*分别是过去60秒,60分钟,24小时,30天的平均延时。纯文本下是10秒内([-bvar_dump_interval](http://brpc.baidu.com:8765/flags/bvar_dump_interval)控制)的平均延时。 +- **latency_percentiles**: 是延时的80%, 90%, 99%, 99.9%分位值,统计窗口默认10秒([-bvar_dump_interval](http://brpc.baidu.com:8765/flags/bvar_dump_interval)控制),在html下有曲线。 +- **latency_cdf**: 用[CDF](https://en.wikipedia.org/wiki/Cumulative_distribution_function)展示分位值, 只能在html下查看。 +- **max_latency**: 在html下*从右到左*分别是过去60秒,60分钟,24小时,30天的最大延时。纯文本下是10秒内([-bvar_dump_interval](http://brpc.baidu.com:8765/flags/bvar_dump_interval)控制)的最大延时。 +- **qps**: 在html下从右到左分别是过去60秒,60分钟,24小时,30天的平均qps(Queries Per Second)。纯文本下是10秒内([-bvar_dump_interval](http://brpc.baidu.com:8765/flags/bvar_dump_interval)控制)的平均qps。 +- **processing**: (新版改名为concurrency)正在处理的请求个数。在压力归0后若此指标仍持续不为0,server则很有可能bug,比如忘记调用done了或卡在某个处理步骤上了。 + + +用户可通过让对应Service实现[brpc::Describable](https://github.com/apache/brpc/blob/master/src/brpc/describable.h)自定义在/status页面上的描述. + +```c++ +class MyService : public XXXService, public brpc::Describable { +public: + ... + void Describe(std::ostream& os, const brpc::DescribeOptions& options) const { + os << "my_status: blahblah"; + } +}; +``` + +比如: + +![img](../images/status_2.png) diff --git a/docs/cn/streaming_log.md b/docs/cn/streaming_log.md new file mode 100644 index 0000000..cfda269 --- /dev/null +++ b/docs/cn/streaming_log.md @@ -0,0 +1,280 @@ +# Name + +streaming_log - Print log to std::ostreams + +# SYNOPSIS + +```c++ +#include + +LOG(FATAL) << "Fatal error occurred! contexts=" << ...; +LOG(WARNING) << "Unusual thing happened ..." << ...; +LOG(TRACE) << "Something just took place..." << ...; +LOG(TRACE) << "Items:" << noflush; +LOG_IF(NOTICE, n > 10) << "This log will only be printed when n > 10"; +PLOG(FATAL) << "Fail to call function setting errno"; +VLOG(1) << "verbose log tier 1"; +CHECK_GT(1, 2) << "1 can't be greater than 2"; + +LOG_EVERY_SECOND(INFO) << "High-frequent logs"; +LOG_EVERY_N(ERROR, 10) << "High-frequent logs"; +LOG_FIRST_N(INFO, 20) << "Logs that prints for at most 20 times"; +LOG_ONCE(WARNING) << "Logs that only prints once"; +``` + +# DESCRIPTION + +流式日志是打印复杂对象或模板对象的不二之选。大部分业务对象都很复杂,如果用printf形式的函数打印,你需要先把对象转成string,才能以%s输出。但string组合起来既不方便(比如没法append数字),还得分配大量的临时内存(string导致的)。C++中解决这个问题的方法便是“把日志流式地送入std::ostream对象”。比如为了打印对象A,那么我们得实现如下的函数: + +```c++ +std::ostream& operator<<(std::ostream& os, const A& a); +``` + +这个函数的意思是把对象a打印入os,并返回os。之所以返回os,是因为operator<<对应了二元操作 << (左结合),当我们写下`os << a << b << c;`时,它相当于`operator<<(operator<<(operator<<(os, a), b), c);` 很明显,operator<<需要不断地返回os(的引用),才能完成这个过程。这个过程一般称为chaining。在不支持重载二元运算符的语言中,你可能会看到一些更繁琐的形式,比如`os.print(a).print(b).print(c)`。 + +我们在operator<<的实现中也使用chaining。事实上,流式打印一个复杂对象就像DFS一棵树一样:逐个调用儿子节点的operator<<,儿子又逐个调用孙子节点的operator<<,以此类推。比如对象A有两个成员变量B和C,打印A的过程就是把其中的B和C对象送入ostream中: + +```c++ +struct A { + B b; + C c; +}; +std::ostream& operator<<(std::ostream& os, const A& a) { + return os << "A{b=" << a.b << ", c=" << a.c << "}"; +} +``` + +B和C的结构及打印函数分别如下: + +```c++ +struct B { + int value; +}; +std::ostream& operator<<(std::ostream& os, const B& b) { + return os << "B{value=" << b.value << "}"; +} + +struct C { + string name; +}; +std::ostream& operator<<(std::ostream& os, const C& c) { + return os << "C{name=" << c.name << "}"; +} +``` + +那么打印某个A对象的结果可能是 + +``` +A{b=B{value=10}, c=C{name=tom}} +``` + +在打印过程中,我们不需要分配临时内存,因为对象都被直接送入了最终要送入的那个ostream对象。当然ostream对象自身的内存管理是另一会儿事了。 + +OK,我们通过ostream把对象的打印过程串联起来了,最常见的std::cout和std::cerr都继承了ostream,所以实现了上面函数的对象就可以输出到std::cout和std::cerr了。换句话说如果日志流也继承了ostream,那么那些对象也可以打入日志了。流式日志正是通过继承std::ostream,把对象打入日志的,在目前的实现中,送入日志流的日志被记录在thread-local的缓冲中,在完成一条日志后会被刷入屏幕或logging::LogSink,这个实现是线程安全的。 + +## LOG + +如果你用过glog,应该是不用学习的,因为宏名称和glog是一致的,如下打印一条FATAL。注意不需要加上std::endl。 + +```c++ +LOG(FATAL) << "Fatal error occurred! contexts=" << ...; +LOG(WARNING) << "Unusual thing happened ..." << ...; +LOG(TRACE) << "Something just took place..." << ...; +``` + +streaming log的日志等级与glog映射关系如下: + +| streaming log | glog | 使用场景 | +| ------------- | -------------------- | ---------------------------------------- | +| FATAL | FATAL (coredump) | 致命错误。但由于百度内大部分FATAL实际上非致命,所以streaming log的FATAL默认不像glog那样直接coredump,除非打开了[-crash_on_fatal_log](http://brpc.baidu.com:8765/flags/crash_on_fatal_log) | +| ERROR | ERROR | 不致命的错误。 | +| WARNING | WARNING | 不常见的分支。 | +| NOTICE | - | 一般来说你不应该使用NOTICE,它用于打印重要的业务日志,若要使用务必和检索端同学确认。glog没有NOTICE。 | +| INFO, TRACE | INFO | 打印重要的副作用。比如打开关闭了某某资源之类的。 | +| VLOG(n) | INFO | 打印分层的详细日志。 | +| DEBUG | INFOVLOG(1) (NDEBUG) | 仅为代码兼容性,基本没有用。若要使日志仅在未定义NDEBUG时才打印,用DLOG/DPLOG/DVLOG等即可。 | + +## PLOG + +PLOG和LOG的不同之处在于,它会在日志后加上错误码的信息,类似于printf中的%m。在posix系统中,错误码就是errno。 + +```c++ +int fd = open("foo.conf", O_RDONLY); // foo.conf does not exist, errno was set to ENOENT +if (fd < 0) { + PLOG(FATAL) << "Fail to open foo.conf"; // "Fail to open foo.conf: No such file or directory" + return -1; +} +``` + +## noflush + +如果你暂时不希望刷到屏幕,加上noflush。这一般会用在打印循环中: + +```c++ +LOG(TRACE) << "Items:" << noflush; +for (iterator it = items.begin(); it != items.end(); ++it) { + LOG(TRACE) << ' ' << *it << noflush; +} +LOG(TRACE); +``` + +前两次TRACE日志都没有刷到屏幕,而是还记录在thread-local缓冲中,第三次TRACE日志则把缓冲都刷入了屏幕。如果items里面有三个元素,不加noflush的打印结果可能是这样的: + +``` +TRACE: ... Items: +TRACE: ... item1 +TRACE: ... item2 +TRACE: ... item3 +``` + +加了是这样的: + +``` +TRACE: ... Items: item1 item2 item3 +``` + +noflush支持bthread,可以实现类似于UB的pushnotice的效果,即检索线程一路打印都暂不刷出(加上noflush),直到最后检索结束时再一次性刷出。注意,如果检索过程是异步的,就不应该使用noflush,因为异步显然会跨越bthread,使noflush仍然失效。 + +> 注意:如果编译时开启了glog选项,则不支持noflush。 + +## LOG_IF + +`LOG_IF(log_level, condition)`只有当condition成立时才会打印,相当于if (condition) { LOG() << ...; },但更加简短。比如: + +```c++ +LOG_IF(NOTICE, n > 10) << "This log will only be printed when n > 10"; +``` + +## XXX_EVERY_SECOND + +XXX可以是LOG,LOG_IF,PLOG,SYSLOG,VLOG,DLOG等。这类日志每秒最多打印一次,可放在频繁运行热点处探查运行状态。第一次必打印,比普通LOG增加调用一次gettimeofday(30ns左右)的开销。 + +```c++ +LOG_EVERY_SECOND(INFO) << "High-frequent logs"; +``` + +## XXX_EVERY_N + +XXX可以是LOG,LOG_IF,PLOG,SYSLOG,VLOG,DLOG等。这类日志每触发N次才打印一次,可放在频繁运行热点处探查运行状态。第一次必打印,比普通LOG增加一次relaxed原子加的开销。这个宏是线程安全的,即不同线程同时运行这段代码时对N的限制也是准确的,glog中的不是。 + +```c++ +LOG_EVERY_N(ERROR, 10) << "High-frequent logs"; +``` + +## XXX_FIRST_N + +XXX可以是LOG,LOG_IF,PLOG,SYSLOG,VLOG,DLOG等。这类日志最多打印N次。在N次前比普通LOG增加一次relaxed原子加的开销,N次后基本无开销。 + +```c++ +LOG_FIRST_N(ERROR, 20) << "Logs that prints for at most 20 times"; +``` + +## XXX_ONCE + +XXX可以是LOG,LOG_IF,PLOG,SYSLOG,VLOG,DLOG等。这类日志最多打印1次。等价于XXX_FIRST_N(..., 1) + +```c++ +LOG_ONCE(ERROR) << "Logs that only prints once"; +``` + +## VLOG + +VLOG(verbose_level)是分层的详细日志,通过两个gflags:*--verbose*和*--verbose_module*控制需要打印的层(注意glog是--v和–vmodule)。只有当–verbose指定的值大于等于verbose_level时,对应的VLOG才会打印。比如 + +```c++ +VLOG(0) << "verbose log tier 0"; +VLOG(1) << "verbose log tier 1"; +VLOG(2) << "verbose log tier 2"; +``` + +当`--verbose=1`时,前两条会打印,最后一条不会。--`verbose_module`可以覆盖某个模块的级别,模块指**去掉扩展名的文件名或文件路径**。比如: + +```bash +--verbose=1 --verbose_module="channel=2,server=3" # 打印channel.cpp中<=2,server.cpp中<=3,其他文件<=1的VLOG +--verbose=1 --verbose_module="src/brpc/channel=2,server=3" # 当不同目录下有同名文件时,可以加上路径 +``` + +`--verbose`和`--verbose_module`可以通过`google::SetCommandLineOption`动态设置。 + +VLOG有一个变种VLOG2让用户指定虚拟文件路径,比如: + +```c++ +// public/foo/bar.cpp +VLOG2("a/b/c", 2) << "being filtered by a/b/c rather than public/foo/bar"; +``` + +> VLOG和VLOG2也有相应的VLOG_IF和VLOG2_IF。 + +## DLOG + +所有的日志宏都有debug版本,以D开头,比如DLOG,DVLOG,当定义了**NDEBUG**后,这些日志不会打印。 + +**千万别在D开头的日志流上有重要的副作用。** + +“不会打印”指的是连参数都不会评估。如果你的参数是有副作用的,那么当定义了NDEBUG后,这些副作用都不会发生。比如DLOG(FATAL) << foo(); 其中foo是一个函数,它修改一个字典,反正必不可少,但当定义了NDEBUG后,foo就运行不到了。 + +## CHECK + +日志另一个重要变种是CHECK(expression),当expression为false时,会打印一条FATAL日志。类似gtest中的ASSERT,也有CHECK_EQ, CHECK_GT等变种。当CHECK失败后,其后的日志流会被打印。 + +```c++ +CHECK_LT(1, 2) << "This is definitely true, this log will never be seen"; +CHECK_GT(1, 2) << "1 can't be greater than 2"; +``` + +运行后你应该看到一条FATAL日志和调用处的call stack: + +``` +FATAL: ... Check failed: 1 > 2 (1 vs 2). 1 can't be greater than 2 +#0 0x000000afaa23 butil::debug::StackTrace::StackTrace() +#1 0x000000c29fec logging::LogStream::FlushWithoutReset() +#2 0x000000c2b8e6 logging::LogStream::Flush() +#3 0x000000c2bd63 logging::DestroyLogStream() +#4 0x000000c2a52d logging::LogMessage::~LogMessage() +#5 0x000000a716b2 (anonymous namespace)::StreamingLogTest_check_Test::TestBody() +#6 0x000000d16d04 testing::internal::HandleSehExceptionsInMethodIfSupported<>() +#7 0x000000d19e96 testing::internal::HandleExceptionsInMethodIfSupported<>() +#8 0x000000d08cd4 testing::Test::Run() +#9 0x000000d08dfe testing::TestInfo::Run() +#10 0x000000d08ec4 testing::TestCase::Run() +#11 0x000000d123c7 testing::internal::UnitTestImpl::RunAllTests() +#12 0x000000d16d94 testing::internal::HandleSehExceptionsInMethodIfSupported<>() +``` + +callstack中的第二列是代码地址,你可以使用addr2line查看对应的文件行数: + +``` +$ addr2line -e ./test_base 0x000000a716b2 +/home/gejun/latest_baidu_rpc/public/common/test/test_streaming_log.cpp:223 +``` + +你**应该**根据比较关系使用具体的CHECK_XX,这样当出现错误时,你可以看到更详细的信息,比如: + +```C++ +int x = 1; +int y = 2; +CHECK_GT(x, y); // Check failed: x > y (1 vs 2). +CHECK(x > y); // Check failed: x > y. +``` + +和DLOG类似,你不应该在DCHECK的日志流中包含重要的副作用。 + +## LogSink + +streaming log通过logging::SetLogSink修改日志刷入的目标,默认是屏幕。用户可以继承LogSink,实现自己的日志打印逻辑。我们默认提供了个LogSink实现: + +### StringSink + +同时继承了LogSink和string,把日志内容存放在string中,主要用于单测,这个case说明了StringSink的典型用法: + +```C++ +TEST_F(StreamingLogTest, log_at) { + ::logging::StringSink log_str; + ::logging::LogSink* old_sink = ::logging::SetLogSink(&log_str); + LOG_AT(FATAL, "specified_file.cc", 12345) << "file/line is specified"; + // the file:line part should be using the argument given by us. + ASSERT_NE(std::string::npos, log_str.find("specified_file.cc:12345")); + // restore the old sink. + ::logging::SetLogSink(old_sink); +} +``` diff --git a/docs/cn/streaming_rpc.md b/docs/cn/streaming_rpc.md new file mode 100644 index 0000000..6bdb2f2 --- /dev/null +++ b/docs/cn/streaming_rpc.md @@ -0,0 +1,153 @@ +[English version](../en/streaming_rpc.md) + +# 概述 + +在一些应用场景中, client或server需要向对面发送大量数据,这些数据非常大或者持续地在产生以至于无法放在一个RPC的附件中。比如一个分布式系统的不同节点间传递replica或snapshot。client/server之间虽然可以通过多次RPC把数据切分后传输过去,但存在如下问题: + +- 如果这些RPC是并行的,无法保证接收端有序地收到数据,拼接数据的逻辑相当复杂。 +- 如果这些RPC是串行的,每次传递都得等待一次网络RTT+处理数据的延时,特别是后者的延时可能是难以预估的。 + +为了让大块数据以流水线的方式在client/server之间传递, 我们提供了Streaming RPC这种交互模型。Streaming RPC让用户能够在client/service之间建立用户态连接,称为Stream, 同一个TCP连接之上能同时存在多个Stream。 Stream的传输数据以消息为基本单位, 输入端可以源源不断的往Stream中写入消息, 接收端会按输入端写入顺序收到消息。 + +Streaming RPC保证: + +- 有消息边界。 +- 接收消息的顺序和发送消息的顺序严格一致。 +- 全双工。 +- 支持流控。 +- 提供超时提醒 +- 支持自动切割过大的消息,避免[Head-of-line blocking](https://en.wikipedia.org/wiki/Head-of-line_blocking)问题 + +例子见[example/streaming_echo_c++](https://github.com/apache/brpc/tree/master/example/streaming_echo_c++/)。 + +# 建立Stream + +目前Stream都由Client端建立。Client先在本地创建一个或者多个Stream,再通过一次RPC(必须使用baidu_std协议)与指定的Service建立一个Stream,如果Service在收到请求之后选择接受这批Stream, 那在response返回Client后这批Stream就会建立成功。过程中的任何错误都把RPC标记为失败,同时也意味着Stream创建失败。用linux下建立连接的过程打比方,Client先创建[socket](http://linux.die.net/man/7/socket)(创建Stream),再调用[connect](http://linux.die.net/man/2/connect)尝试与远端建立连接(通过RPC建立Stream),远端[accept](http://linux.die.net/man/2/accept)后连接就建立了(service接受后创建成功)。 + +> 如果Client尝试向不支持Streaming RPC的老Server建立Stream,将总是失败。 + +程序中我们用StreamId代表一个Stream,对Stream的读写,关闭操作都将作用在这个Id上。 + +```c++ +struct StreamOptions + // The max size of unconsumed data allowed at remote side. + // If |max_buf_size| <= 0, there's no limit of buf size + // default: 2097152 (2M) + int max_buf_size; + + // Notify user when there's no data for at least |idle_timeout_ms| + // milliseconds since the last time that on_received_messages or on_idle_timeout + // finished. + // default: -1 + long idle_timeout_ms; + + // Maximum messages in batch passed to handler->on_received_messages + // default: 128 + size_t messages_in_batch; + + // Handle input message, if handler is NULL, the remote side is not allowed to + // write any message, who will get EBADF on writing + // default: NULL + StreamInputHandler* handler; +}; + +// [Called at the client side] +// Create a Stream at client-side along with the |cntl|, which will be connected +// when receiving the response with a Stream from server-side. If |options| is +// NULL, the Stream will be created with default options +// Return 0 on success, -1 otherwise +int StreamCreate(StreamId* request_stream, Controller &cntl, const StreamOptions* options); + +// [Called at the client side for creating multiple streams] +// Create streams at client-side along with the |cntl|, which will be connected +// when receiving the response with streams from server-side. If |options| is +// NULL, the stream will be created with default options +// Return 0 on success, -1 otherwise +int StreamCreate(StreamIds& request_streams, int request_stream_size, Controller& cntl, const StreamOptions* options); +``` + +# 接受Stream + +如果client在RPC上附带了一个或者多个Stream, service在收到RPC后可以通过调用StreamAccept接受。接受后Server端对应产生的Stream存放在response_stream中,Server可通过这个Stream向Client发送数据。 + +```c++ +// [Called at the server side] +// Accept the Stream. If client didn't create a Stream with the request +// (cntl.has_remote_stream() returns false), this method would fail. +// Return 0 on success, -1 otherwise. +int StreamAccept(StreamId* response_stream, Controller &cntl, const StreamOptions* options); + +// [Called at the server side for accepting multiple streams] +// Accept the streams. If client didn't create streams with the request +// (cntl.has_remote_stream() returns false), this method would fail. +// Return 0 on success, -1 otherwise. +int StreamAccept(StreamIds& response_stream, Controller& cntl, const StreamOptions* options); +``` + +# 读取Stream + +在建立或者接受一个Stream的时候, 用户可以继承StreamInputHandler并把这个handler填入StreamOptions中. 通过这个handler,用户可以处理对端的写入数据,连接关闭以及idle timeout + +```c++ +class StreamInputHandler { +public: + // 当接收到消息后被调用 + virtual int on_received_messages(StreamId id, butil::IOBuf *const messages[], size_t size) = 0; + + // 当Stream上长时间没有数据交互后被调用 + virtual void on_idle_timeout(StreamId id) = 0; + + // 当Stream被关闭时被调用 + virtual void on_closed(StreamId id) = 0; +}; +``` + +>***第一次收到请求的时间*** +> +>在client端,如果建立过程是一次同步RPC, 那在等待的线程被唤醒之后,on_received_message就可能会被调用到。 如果是异步RPC请求, 那等到这次请求的done->Run() 执行完毕之后, on_received_message就可能会被调用。 +> +>在server端, 当框架传入的done->Run()被调用完之后, on_received_message就可能会被调用。 + +# 写入Stream + +```c++ +// Write |message| into |stream_id|. The remote-side handler will received the +// message by the written order +// Returns 0 on success, errno otherwise +// Errno: +// - EAGAIN: |stream_id| is created with positive |max_buf_size| and buf size +// which the remote side hasn't consumed yet excceeds the number. +// - EINVAL: |stream_id| is invalied or has been closed +int StreamWrite(StreamId stream_id, const butil::IOBuf &message); +``` + +# 流控 + +当存在较多已发送但未接收的数据时,发送端的Write操作会立即失败(返回EAGAIN), 这时候可以通过同步或异步的方式等待对端消费掉数据。 + +```c++ +// Wait util the pending buffer size is less than |max_buf_size| or error occurs +// Returns 0 on success, errno otherwise +// Errno: +// - ETIMEDOUT: when |due_time| is not NULL and time expired this +// - EINVAL: the Stream was close during waiting +int StreamWait(StreamId stream_id, const timespec* due_time); + +// Async wait +void StreamWait(StreamId stream_id, const timespec *due_time, + void (*on_writable)(StreamId stream_id, void* arg, int error_code), + void *arg); +``` + +# 关闭Stream + +```c++ +// Close |stream_id|, after this function is called: +// - All the following |StreamWrite| would fail +// - |StreamWait| wakes up immediately. +// - Both sides |on_closed| would be notifed after all the pending buffers have +// been received +// This function could be called multiple times without side-effects +int StreamClose(StreamId stream_id); +``` + diff --git a/docs/cn/thread_local.md b/docs/cn/thread_local.md new file mode 100644 index 0000000..41e0247 --- /dev/null +++ b/docs/cn/thread_local.md @@ -0,0 +1,64 @@ +本页说明bthread下使用pthread-local可能会导致的问题。bthread-local的使用方法见[这里](server.md#bthread-local)。 + +# thread-local问题 + +调用阻塞的bthread函数后,所在的pthread很可能改变,这使[pthread_getspecific](http://linux.die.net/man/3/pthread_getspecific),[gcc __thread](https://gcc.gnu.org/onlinedocs/gcc-4.2.4/gcc/Thread_002dLocal.html)和c++11 +thread_local变量,pthread_self()等的值变化了,如下代码的行为是不可预计的: + +``` +thread_local SomeObject obj; +... +SomeObject* p = &obj; +p->bar(); +bthread_usleep(1000); +p->bar(); +``` + +bthread_usleep之后,该bthread很可能身处不同的pthread,这时p指向了之前pthread的thread_local变量,继续访问p的结果无法预计。这种使用模式往往发生在用户使用线程级变量传递业务变量的情况。为了防止这种情况,应该谨记: + +- 不使用线程级变量传递业务数据。这是一种槽糕的设计模式,依赖线程级数据的函数也难以单测。判断是否滥用:如果不使用线程级变量,业务逻辑是否还能正常运行?线程级变量应只用作优化手段,使用过程中不应直接或间接调用任何可能阻塞的bthread函数。比如使用线程级变量的tcmalloc就不会和bthread有任何冲突。 +- 如果一定要(在业务中)使用线程级变量,使用bthread_key_create和bthread_getspecific。 + +# gcc4下的errno问题 + +gcc4会优化[标记为__attribute__((__const__))](https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html#index-g_t_0040code_007bconst_007d-function-attribute-2958)的函数,这个标记大致指只要参数不变,输出就不会变。所以当一个函数中以相同参数出现多次时,gcc4会合并为一次。比如在我们的系统中errno是内容为*__errno_location()的宏,这个函数的签名是: + +``` +/* Function to get address of global `errno' variable. */ +extern int *__errno_location (void) __THROW __attribute__ ((__const__)); +``` + +由于此函数被标记为`__const__`,且没有参数,当你在一个函数中调用多次errno时,可能只有第一次才调用__errno_location(),而之后只是访问其返回的`int*`。在pthread中这没有问题,因为返回的`int*`是thread-local的,一个给定的pthread中是不会变化的。但是在bthread中,这是不成立的,因为一个bthread很可能在调用一些函数后跑到另一个pthread去,如果gcc4做了类似的优化,即一个函数内所有的errno都替换为第一次调用返回的int*,这中间bthread又切换了pthread,那么可能会访问之前pthread的errno,从而造成未定义行为。 + +比如下文是一种errno的使用场景: + +``` +Use errno ... (original pthread) +bthread functions that may switch to another pthread. +Use errno ... (another pthread) +``` + +我们期望看到的行为: + +``` +Use *__errno_location() ... - the thread-local errno of original pthread +bthread may switch another pthread ... +Use *__errno_location() ... - the thread-local errno of another pthread +``` + +使用gcc4时的实际行为: + +``` +int* p= __errno_location(); +Use *p ... - the thread-local errno of original pthread +bthread context switches ... +Use *p ... - still the errno of original pthread, undefined behavior!! +``` + +严格地说这个问题不是gcc4导致的,而是glibc给__errno_location的签名不够准确,一个返回thread-local指针的函数依赖于段寄存器(TLS的一般实现方式),这怎么能算const呢?由于我们还未找到覆盖__errno_location的方法,所以这个问题目前实际的解决方法是: + +**务必在直接或间接使用bthread的项目的gcc编译选项中添加`-D__const__=__unused__`,即把`__const__`定义为一个无副作用的属性,避免gcc4做相关优化。** + +把`__const__`定义为`__unused__`对程序其他部分的影响几乎为0。另外如果你没有**直接**使用errno(即你的项目中没有出现errno),或使用的是gcc 3.4,即使没有定义`-D__const__=__unused__`,程序的正确性也不会受影响,但为了防止未来可能的问题,我们强烈建议加上。 + +需要说明的是,和errno类似,pthread_self也有类似的问题,不过一般pthread_self除了打日志没有其他用途,影响面较小,在`-D__const__=__unused__`后pthread_self也会正常。 diff --git a/docs/cn/threading_overview.md b/docs/cn/threading_overview.md new file mode 100644 index 0000000..8d494dd --- /dev/null +++ b/docs/cn/threading_overview.md @@ -0,0 +1,45 @@ +[English version](../en/threading_overview.md) + +# 常见线程模型 + +## 连接独占线程或进程 + +在这个模型中,线程/进程处理来自绑定连接的消息,在连接断开前不退也不做其他事情。当连接数逐渐增多时,线程/进程占用的资源和上下文切换成本会越来越大,性能很差,这就是[C10K问题](http://en.wikipedia.org/wiki/C10k_problem)的来源。这种方法常见于早期的web server,现在很少使用。 + +## 单线程[reactor](http://en.wikipedia.org/wiki/Reactor_pattern) + +以[libevent](http://libevent.org/), [libev](http://software.schmorp.de/pkg/libev.html)等event-loop库为典型。这个模型一般由一个event dispatcher等待各类事件,待事件发生后**原地**调用对应的event handler,全部调用完后等待更多事件,故为"loop"。这个模型的实质是把多段逻辑按事件触发顺序交织在一个系统线程中。一个event-loop只能使用一个核,故此类程序要么是IO-bound,要么是每个handler有确定的较短的运行时间(比如http server),否则一个耗时漫长的回调就会卡住整个程序,产生高延时。在实践中这类程序不适合多开发者参与,一个人写了阻塞代码可能就会拖慢其他代码的响应。由于event handler不会同时运行,不太会产生复杂的race condition,一些代码不需要锁。此类程序主要靠部署更多进程增加扩展性。 + +单线程reactor的运行方式及问题如下图所示: + +![img](../images/threading_overview_1.png) + +## N:1线程库 + +又称为[Fiber](http://en.wikipedia.org/wiki/Fiber_(computer_science)),以[GNU Pth](http://www.gnu.org/software/pth/pth-manual.html), [StateThreads](http://state-threads.sourceforge.net/index.html)等为典型,一般是把N个用户线程映射入一个系统线程。同时只运行一个用户线程,调用阻塞函数时才会切换至其他用户线程。N:1线程库与单线程reactor在能力上等价,但事件回调被替换为了上下文(栈,寄存器,signals),运行回调变成了跳转至上下文。和event loop库一样,单个N:1线程库无法充分发挥多核性能,只适合一些特定的程序。只有一个系统线程对CPU cache较为友好,加上舍弃对signal mask的支持的话,用户线程间的上下文切换可以很快(100~200ns)。N:1线程库的性能一般和event loop库差不多,扩展性也主要靠多进程。 + +## 多线程reactor + +以[boost::asio](http://www.boost.org/doc/libs/1_56_0/doc/html/boost_asio.html)为典型。一般由一个或多个线程分别运行event dispatcher,待事件发生后把event handler交给一个worker线程执行。 这个模型是单线程reactor的自然扩展,可以利用多核。由于共用地址空间使得线程间交互变得廉价,worker thread间一般会更及时地均衡负载,而多进程一般依赖更前端的服务来分割流量,一个设计良好的多线程reactor程序往往能比同一台机器上的多个单线程reactor进程更均匀地使用不同核心。不过由于[cache一致性](atomic_instructions.md#cacheline)的限制,多线程reactor并不能获得线性于核心数的性能,在特定的场景中,粗糙的多线程reactor实现跑在24核上甚至没有精致的单线程reactor实现跑在1个核上快。由于多线程reactor包含多个worker线程,单个event handler阻塞未必会延缓其他handler,所以event handler未必得非阻塞,除非所有的worker线程都被阻塞才会影响到整体进展。事实上,大部分RPC框架都使用了这个模型,且回调中常有阻塞部分,比如同步等待访问下游的RPC返回。 + +多线程reactor的运行方式及问题如下: + +![img](../images/threading_overview_2.png) + +## M:N线程库 + +即把M个用户线程映射入N个系统线程。M:N线程库可以决定一段代码何时开始在哪运行,并何时结束,相比多线程reactor在调度上具备更多的灵活度。但实现全功能的M:N线程库是困难的,它一直是个活跃的研究话题。我们这里说的M:N线程库特别针对编写网络服务,在这一前提下一些需求可以简化,比如没有时间片抢占,没有(完备的)优先级等。M:N线程库可以在用户态也可以在内核中实现,用户态的实现以新语言为主,比如GHC threads和goroutine,这些语言可以围绕线程库设计全新的关键字并拦截所有相关的API。而在现有语言中的实现往往得修改内核,比如[Windows UMS](https://msdn.microsoft.com/en-us/library/windows/desktop/dd627187(v=vs.85).aspx)和google SwitchTo(虽然是1:1,但基于它可以实现M:N的效果)。相比N:1线程库,M:N线程库在使用上更类似于系统线程,需要用锁或消息传递保证代码的线程安全。 + +# 问题 + +## 多核扩展性 + +理论上代码都写成事件驱动型能最大化reactor模型的能力,但实际由于编码难度和可维护性,用户的使用方式大都是混合的:回调中往往会发起同步操作,阻塞住worker线程使其无法处理其他请求。一个请求往往要经过几十个服务,线程把大量时间花在了等待下游请求上,用户得开几百个线程以维持足够的吞吐,这造成了高强度的调度开销,并降低了TLS相关代码的效率。任务的分发大都是使用全局mutex + condition保护的队列,当所有线程都在争抢时,效率显然好不到哪去。更好的办法也许是使用更多的任务队列,并调整调度算法以减少全局竞争。比如每个系统线程有独立的runqueue,由一个或多个scheduler把用户线程分发到不同的runqueue,每个系统线程优先运行自己runqueue中的用户线程,然后再考虑其他线程的runqueue。这当然更复杂,但比全局mutex + condition有更好的扩展性。这种结构也更容易支持NUMA。 + +当event dispatcher把任务递给worker线程时,用户逻辑很可能从一个核心跳到另一个核心,并等待相应的cacheline同步过来,并不很快。如果worker的逻辑能直接运行于event dispatcher所在的核心上就好了,因为大部分时候尽快运行worker的优先级高于获取新事件。类似的是收到response后最好在当前核心唤醒正在同步等待RPC的线程。 + +## 异步编程 + +异步编程中的流程控制对于专家也充满了陷阱。任何挂起操作,如sleep一会儿或等待某事完成,都意味着用户需要显式地保存状态,并在回调函数中恢复状态。异步代码往往得写成状态机的形式。当挂起较少时,这有点麻烦,但还是可把握的。问题在于一旦挂起发生在条件判断、循环、子函数中,写出这样的状态机并能被很多人理解和维护,几乎是不可能的,而这在分布式系统中又很常见,因为一个节点往往要与多个节点同时交互。另外如果唤醒可由多种事件触发(比如fd有数据或超时了),挂起和恢复的过程容易出现race condition,对多线程编码能力要求很高。语法糖(比如lambda)可以让编码不那么“麻烦”,但无法降低难度。 + +共享指针在异步编程中很普遍,这看似方便,但也使内存的ownership变得难以捉摸,如果内存泄漏了,很难定位哪里没有释放;如果segment fault了,也不知道哪里多释放了一下。大量使用引用计数的用户代码很难控制代码质量,容易长期在内存问题上耗费时间。如果引用计数还需要手动维护,保持质量就更难了,维护者也不会愿意改进。没有上下文会使得[RAII](http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization)无法充分发挥作用, 有时需要在callback之外lock,callback之内unlock,实践中很容易出错。 diff --git a/docs/cn/thrift.md b/docs/cn/thrift.md new file mode 100755 index 0000000..9e1d4c9 --- /dev/null +++ b/docs/cn/thrift.md @@ -0,0 +1,147 @@ +[English Version](../en/thrift.md) + +[thrift](https://thrift.apache.org/)是应用较广的RPC框架,最初由Facebook发布,后交由Apache维护。为了和thrift服务互通,同时解决thrift原生方案在多线程安全、易用性、并发能力等方面的一系列问题,brpc实现并支持thrift在NonBlocking模式下的协议(FramedProtocol), 下文均直接称为thrift协议。 + +示例程序:[example/thrift_extension_c++](https://github.com/apache/brpc/tree/master/example/thrift_extension_c++/) + +相比使用原生方案的优势有: +- 线程安全。用户不需要为每个线程建立独立的client. +- 支持同步、异步、批量同步、批量异步等访问方式,能使用ParallelChannel等组合访问方式. +- 支持多种连接方式(连接池, 短连接), 支持超时、backup request、取消、tracing、内置服务等一系列RPC基本福利. +- 性能更好. + +# 编译 +为了复用解析代码,brpc对thrift的支持仍需要依赖thrift库以及thrift生成的代码,thrift格式怎么写,代码怎么生成,怎么编译等问题请参考thrift官方文档。 + +brpc默认不启用thrift支持也不需要thrift依赖。但如果需用thrift协议, 配置brpc环境的时候需加上--with-thrift或-DWITH_THRIFT=ON. + +Linux下安装thrift依赖 +先参考[官方wiki](https://thrift.apache.org/docs/install/debian)安装好必备的依赖和工具,然后从[官网](https://thrift.apache.org/download)下载thrift源代码,解压编译。 +```bash +wget https://downloads.apache.org/thrift/0.22.0/thrift-0.22.0.tar.gz +tar -xf thrift-0.22.0.tar.gz +cd thrift-0.22.0/ +./bootstrap.sh +./configure --prefix=/usr --with-ruby=no --with-python=no --with-java=no --with-go=no --with-perl=no --with-php=no --with-csharp=no --with-erlang=no --with-lua=no --with-nodejs=no --with-rs=no --with-py3=no CXXFLAGS='-Wno-error' +make CPPFLAGS=-DFORCE_BOOST_SMART_PTR -j 4 -s +sudo make install +``` + +配置brpc支持thrift协议后make。编译完成后会生成libbrpc.a, 其中包含了支持thrift协议的扩展代码, 像正常使用brpc的代码一样链接即可。 +```bash +# Ubuntu +sh config_brpc.sh --headers=/usr/include --libs=/usr/lib --with-thrift +# Fedora/CentOS +sh config_brpc.sh --headers=/usr/include --libs=/usr/lib64 --with-thrift +# Or use cmake +mkdir build && cd build && cmake ../ -DWITH_THRIFT=1 +``` +更多编译选项请阅读[Getting Started](../cn/getting_started.md)。 + +# Client端访问thrift server +基本步骤: +- 创建一个协议设置为brpc::PROTOCOL_THRIFT的Channel +- 创建brpc::ThriftStub +- 使用原生Request和原生Response>发起访问 + +示例代码如下: +```c++ +#include +#include         // 定义了ThriftStub +... + +DEFINE_string(server, "0.0.0.0:8019", "IP Address of thrift server"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +... + +brpc::ChannelOptions options; +options.protocol = brpc::PROTOCOL_THRIFT; +brpc::Channel thrift_channel; +if (thrift_channel.Init(Flags_server.c_str(), FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize thrift channel"; + return -1; +} + +brpc::ThriftStub stub(&thrift_channel); +... + +// example::[EchoRequest/EchoResponse]是thrift生成的消息 +example::EchoRequest req; +example::EchoResponse res; +req.data = "hello"; + +stub.CallMethod("Echo", &cntl, &req, &res, NULL); + +if (cntl.Failed()) { + LOG(ERROR) << "Fail to send thrift request, " << cntl.ErrorText(); + return -1; +} +``` + +# Server端处理thrift请求 +用户通过继承brpc::ThriftService实现处理逻辑,既可以调用thrift生成的handler以直接复用原有的函数入口,也可以像protobuf服务那样直接读取request和设置response。 +```c++ +class EchoServiceImpl : public brpc::ThriftService { +public: + void ProcessThriftFramedRequest(brpc::Controller* cntl, + brpc::ThriftFramedMessage* req, + brpc::ThriftFramedMessage* res, + google::protobuf::Closure* done) override { + // Dispatch calls to different methods + if (cntl->thrift_method_name() == "Echo") { + return Echo(cntl, req->Cast(), + res->Cast(), done); + } else { + cntl->SetFailed(brpc::ENOMETHOD, "Fail to find method=%s", + cntl->thrift_method_name().c_str()); + done->Run(); + } + } + + void Echo(brpc::Controller* cntl, + const example::EchoRequest* req, + example::EchoResponse* res, + google::protobuf::Closure* done) { + // This object helps you to call done->Run() in RAII style. If you need + // to process the request asynchronously, pass done_guard.release(). + brpc::ClosureGuard done_guard(done); + + res->data = req->data + " (processed)"; + } +}; +``` + +实现好thrift service后,设置到ServerOptions.thrift_service并启动服务 +```c++ + brpc::Server server; + brpc::ServerOptions options; + options.thrift_service = new EchoServiceImpl; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + options.max_concurrency = FLAGS_max_concurrency; + + // Start the server. + if (server.Start(FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } +``` + +# 简单的和原生thrift性能对比实验 +测试环境: 48核 2.30GHz +## server端返回client发送的"hello"字符串 +框架 | 线程数 | QPS | 平响 | cpu利用率 +---- | --- | --- | --- | --- +native thrift | 60 | 6.9w | 0.9ms | 2.8% +brpc thrift | 60 | 30w | 0.2ms | 18% + +## server端返回"hello" * 1000 字符串 +框架 | 线程数 | QPS | 平响 | cpu利用率 +---- | --- | --- | --- | --- +native thrift | 60 | 5.2w | 1.1ms | 4.5% +brpc thrift | 60 | 19.5w | 0.3ms | 22% + +## server端做比较复杂的数学计算并返回"hello" * 1000 字符串 +框架 | 线程数 | QPS | 平响 | cpu利用率 +---- | --- | --- | --- | --- +native thrift | 60 | 1.7w | 3.5ms | 76% +brpc thrift | 60 | 2.1w | 2.9ms | 93% diff --git a/docs/cn/timeout_concurrency_limiter.md b/docs/cn/timeout_concurrency_limiter.md new file mode 100644 index 0000000..c9b00fb --- /dev/null +++ b/docs/cn/timeout_concurrency_limiter.md @@ -0,0 +1,25 @@ +# 基于请求超时时间的限流 + +服务的处理能力是有客观上限的。当请求速度超过服务的处理速度时,服务就会过载。 + +如果服务持续过载,会导致越来越多的请求积压,最终所有的请求都必须等待较长时间才能被处理,从而使整个服务处于瘫痪状态。 + +与之相对的,如果直接拒绝掉一部分请求,反而能够让服务能够"及时"处理更多的请求。对应的方法就是[设置最大并发](https://github.com/apache/brpc/blob/master/docs/cn/server.md#%E9%99%90%E5%88%B6%E6%9C%80%E5%A4%A7%E5%B9%B6%E5%8F%91)。 + + +## 算法描述 +在服务正常运营过程中,流量的增减、请求体的大小变化,磁盘的顺序、随机读写,这些都会影响请求的延迟,用户一般情况下不希望请求延迟的波动造成错误,即使会有一些请求的排队造成请求延迟增加,因此,一般用户设置的请求超时时间都会是服务平均延迟的3至4倍。基于请求超时时间的限流是根据统计服务平均延迟和请求设置的超时时间相比较,来估算请求是否能够在设置的超时时间内完成处理,如果能够能完成则接受请求,如果不能完成则拒绝请求。由于统计服务平均延迟和当前请求的实际延迟会有一定的时间差,因此需要设置一个比较宽泛的最大并发度,保证服务不会因为突然的慢请求造成短时间内服务堆积过多的请求。 + +## 开启方法 +目前只有method级别支持基于超时的限流。如果要为某个method开启基于超时的限流,只需要将它的最大并发设置为"timeout"即可,如果客户端没有开启FLAGS_baidu_std_protocol_deliver_timeout_ms,可以设置FLAGS_timeout_cl_default_timeout_ms来调整一个默认的请求超时时间,可以设置FLAGS_timeout_cl_max_concurrency来调整最大并发度。也可以通过设置brpc::TimeoutConcurrencyConf为每个method指定不同的配置。 + +```c++ +// Set timeout concurrency limiter for all methods +brpc::ServerOptions options; +options.method_max_concurrency = "timeout"; +options.method_max_concurrency = brpc::TimeoutConcurrencyConf{1, 100}; + +// Set timeout concurrency limiter for specific method +server.MaxConcurrencyOf("example.EchoService.Echo") = "timeout"; +server.MaxConcurrencyOf("example.EchoService.Echo") = brpc::TimeoutConcurrencyConf{1, 100}; +``` diff --git a/docs/cn/timer_keeping.md b/docs/cn/timer_keeping.md new file mode 100644 index 0000000..31f82e8 --- /dev/null +++ b/docs/cn/timer_keeping.md @@ -0,0 +1,55 @@ +在几点几分做某件事是RPC框架的基本需求,这件事比看上去难。 + +让我们先来看看系统提供了些什么: posix系统能以[signal方式](http://man7.org/linux/man-pages/man2/timer_create.2.html)告知timer触发,不过signal逼迫我们使用全局变量,写[async-signal-safe](https://docs.oracle.com/cd/E19455-01/806-5257/gen-26/index.html)的函数,在面向用户的编程框架中,我们应当尽力避免使用signal。linux自2.6.27后能以[fd方式](http://man7.org/linux/man-pages/man2/timerfd_create.2.html)通知timer触发,这个fd可以放到epoll中和传输数据的fd统一管理。唯一问题是:这是个系统调用,且我们不清楚它在多线程下的表现。 + +为什么这么关注timer的开销?让我们先来看一下RPC场景下一般是怎么使用timer的: + +- 在发起RPC过程中设定一个timer,在超时时间后取消还在等待中的RPC。几乎所有的RPC调用都有超时限制,都会设置这个timer。 +- RPC结束前删除timer。大部分RPC都由正常返回的response导致结束,timer很少触发。 + +你注意到了么,在RPC中timer更像是”保险机制”,在大部分情况下都不会发挥作用,自然地我们希望它的开销越小越好。一个几乎不触发的功能需要两次系统调用似乎并不理想。那在应用框架中一般是如何实现timer的呢?谈论这个问题需要区分“单线程”和“多线程”: + +- 在单线程框架中,比如以[libevent](http://libevent.org/)[, ](http://en.wikipedia.org/wiki/Reactor_pattern)[libev](http://software.schmorp.de/pkg/libev.html)为代表的eventloop类库,或以[GNU Pth](http://www.gnu.org/software/pth/pth-manual.html), [StateThreads](http://state-threads.sourceforge.net/index.html)为代表的coroutine / fiber类库中,一般是以[小顶堆](https://en.wikipedia.org/wiki/Heap_(data_structure))记录触发时间。[epoll_wait](http://man7.org/linux/man-pages/man2/epoll_wait.2.html)前以堆顶的时间计算出参数timeout的值,如果在该时间内没有其他事件,epoll_wait也会醒来,从堆中弹出已超时的元素,调用相应的回调函数。整个框架周而复始地这么运转,timer的建立,等待,删除都发生在一个线程中。只要所有的回调都是非阻塞的,且逻辑不复杂,这套机制就能提供基本准确的timer。不过就像[Threading Overview](threading_overview.md)中说的那样,这不是RPC的场景。 +- 在多线程框架中,任何线程都可能被用户逻辑阻塞较长的时间,我们需要独立的线程实现timer,这种线程我们叫它TimerThread。一个非常自然的做法,就是使用用锁保护的小顶堆。当一个线程需要创建timer时,它先获得锁,然后把对应的时间插入堆,如果插入的元素成为了最早的,唤醒TimerThread。TimerThread中的逻辑和单线程类似,就是等着堆顶的元素超时,如果在等待过程中有更早的时间插入了,自己会被插入线程唤醒,而不会睡过头。这个方法的问题在于每个timer都需要竞争一把全局锁,操作一个全局小顶堆,就像在其他文章中反复谈到的那样,这会触发cache bouncing。同样数量的timer操作比单线程下的慢10倍是非常正常的,尴尬的是这些timer基本不触发。 + +我们重点谈怎么解决多线程下的问题。 + +一个惯例思路是把timer的需求散列到多个TimerThread,但这对TimerThread效果不好。注意我们上面提及到了那个“制约因素”:一旦插入的元素是最早的,要唤醒TimerThread。假设TimerThread足够多,以至于每个timer都散列到独立的TimerThread,那么每次它都要唤醒那个TimerThread。 “唤醒”意味着触发linux的调度函数,触发上下文切换。在非常流畅的系统中,这个开销大约是3-5微秒,这可比抢锁和同步cache还慢。这个因素是提高TimerThread扩展性的一个难点。多个TimerThread减少了对单个小顶堆的竞争压力,但同时也引入了更多唤醒。 + +另一个难点是删除。一般用id指代一个Timer。通过这个id删除Timer有两种方式:1.抢锁,通过一个map查到对应timer在小顶堆中的位置,定点删除,这个map要和堆同步维护。2.通过id找到Timer的内存结构,做个标记,留待TimerThread自行发现和删除。第一种方法让插入逻辑更复杂了,删除也要抢锁,线程竞争更激烈。第二种方法在小顶堆内留了一大堆已删除的元素,让堆明显变大,插入和删除都变慢。 + +第三个难点是TimerThread不应该经常醒。一个极端是TimerThread永远醒着或以较高频率醒过来(比如每1ms醒一次),这样插入timer的线程就不用负责唤醒了,然后我们把插入请求散列到多个堆降低竞争,问题看似解决了。但事实上这个方案提供的timer精度较差,一般高于2ms。你得想这个TimerThread怎么写逻辑,它是没法按堆顶元素的时间等待的,由于插入线程不唤醒,一旦有更早的元素插入,TimerThread就会睡过头。它唯一能做的是睡眠固定的时间,但这和现代OS scheduler的假设冲突:频繁sleep的线程的优先级最低。在linux下的结果就是,即使只sleep很短的时间,最终醒过来也可能超过2ms,因为在OS看来,这个线程不重要。一个高精度的TimerThread有唤醒机制,而不是定期醒。 + +另外,更并发的数据结构也难以奏效,感兴趣的同学可以去搜索"concurrent priority queue"或"concurrent skip list",这些数据结构一般假设插入的数值较为散开,所以可以同时修改结构内的不同部分。但这在RPC场景中也不成立,相互竞争的线程设定的时间往往聚集在同一个区域,因为程序的超时大都是一个值,加上当前时间后都差不多。 + +这些因素让TimerThread的设计相当棘手。由于大部分用户的qps较低,不足以明显暴露这个扩展性问题,在r31791前我们一直沿用“用一把锁保护的TimerThread”。TimerThread是brpc在默认配置下唯一的高频竞争点,这个问题是我们一直清楚的技术债。随着brpc在高qps系统中应用越来越多,是时候解决这个问题了。r31791后的TimerThread解决了上述三个难点,timer操作几乎对RPC性能没有影响,我们先看下性能差异。 + +> 在示例程序example/mutli_threaded_echo_c++中,r31791后TimerThread相比老TimerThread在24核E5-2620上(超线程),以50个bthread同步发送时,节省4%cpu(差不多1个核),qps提升10%左右;在400个bthread同步发送时,qps从30万上升到60万。新TimerThread的表现和完全关闭超时时接近。 + +那新TimerThread是如何做到的? + +- 一个TimerThread而不是多个。 +- 创建的timer散列到多个Bucket以降低线程间的竞争,默认13个Bucket。 +- Bucket内不使用小顶堆管理时间,而是链表 + nearest_run_time字段,当插入的时间早于nearest_run_time时覆盖这个字段,之后去和全局nearest_run_time(和Bucket的nearest_run_time不同)比较,如果也早于这个时间,修改并唤醒TimerThread。链表节点在锁外使用[ResourcePool](memory_management.md)分配。 +- 删除时通过id直接定位到timer内存结构,修改一个标志,timer结构总是由TimerThread释放。 +- TimerThread被唤醒后首先把全局nearest_run_time设置为几乎无限大(max of int64),然后取出所有Bucket内的链表,并把Bucket的nearest_run_time设置为几乎无限大(max of int64)。TimerThread把未删除的timer插入小顶堆中维护,这个堆就它一个线程用。在每次运行回调或准备睡眠前都会检查全局nearest_run_time, 如果全局更早,说明有更早的时间加入了,重复这个过程。 + +这里勾勒了TimerThread的大致工作原理,工程实现中还有不少细节问题,具体请阅读[timer_thread.h](https://github.com/apache/brpc/blob/master/src/bthread/timer_thread.h)和[timer_thread.cpp](https://github.com/apache/brpc/blob/master/src/bthread/timer_thread.cpp)。 + +这个方法之所以有效: + +- Bucket锁内的操作是O(1)的,就是插入一个链表节点,临界区很小。节点本身的内存分配是在锁外的。 +- 由于大部分插入的时间是递增的,早于Bucket::nearest_run_time而参与全局竞争的timer很少。 +- 参与全局竞争的timer也就是和全局nearest_run_time比一下,临界区很小。 +- 和Bucket内类似,极少数Timer会早于全局nearest_run_time并去唤醒TimerThread。唤醒也在全局锁外。 +- 删除不参与全局竞争。 +- TimerThread自己维护小顶堆,没有任何cache bouncing,效率很高。 +- TimerThread醒来的频率大约是RPC超时的倒数,比如超时=100ms,TimerThread一秒内大约醒10次,已经最优。 + +至此brpc在默认配置下不再有全局竞争点,在400个线程同时运行时,profiling也显示几乎没有对锁的等待。 + +下面是一些和linux下时间管理相关的知识: + +- epoll_wait的超时精度是毫秒,较差。pthread_cond_timedwait的超时使用timespec,精度到纳秒,一般是60微秒左右的延时。 +- 出于性能考虑,TimerThread使用wall-time,而不是单调时间,可能受到系统时间调整的影响。具体来说,如果在测试中把系统时间往前或往后调一个小时,程序行为将完全undefined。未来可能会让用户选择单调时间。 +- 在cpu支持nonstop_tsc和constant_tsc的机器上,brpc和bthread会优先使用基于rdtsc的cpuwide_time_us。那两个flag表示rdtsc可作为wall-time使用,不支持的机器上会转而使用较慢的内核时间。我们的机器(Intel Xeon系列)大都有那两个flag。rdtsc作为wall-time使用时是否会受到系统调整时间的影响,未测试不清楚。 diff --git a/docs/cn/ub_client.md b/docs/cn/ub_client.md new file mode 100644 index 0000000..f020b39 --- /dev/null +++ b/docs/cn/ub_client.md @@ -0,0 +1,370 @@ +brpc可通过多种方式访问用ub搭建的服务。 + +# ubrpc (by protobuf) + +r31687后,brpc支持通过protobuf访问ubrpc,不需要baidu-rpc-ub,也不依赖idl-compiler。(也可以让protobuf服务被ubrpc client访问,方法见[使用ubrpc的服务](nshead_service.md#使用ubrpc的服务))。 + +**步骤:** + +1. 用[idl2proto](https://github.com/apache/brpc/blob/master/tools/idl2proto)把idl文件转化为proto文件,老版本idl2proto不会转化idl中的service,需要手动转化。 + + ```protobuf + // Converted from echo.idl by brpc/tools/idl2proto + import "idl_options.proto"; + option (idl_support) = true; + option cc_generic_services = true; + message EchoRequest { + required string message = 1; + } + message EchoResponse { + required string message = 1; + } + + // 对于idl中多个request或response的方法,要建立一个包含所有request或response的消息。 + // 这个例子中就是MultiRequests和MultiResponses。 + message MultiRequests { + required EchoRequest req1 = 1; + required EchoRequest req2 = 2; + } + message MultiResponses { + required EchoRequest res1 = 1; + required EchoRequest res2 = 2; + } + + service EchoService { + // 对应idl中的void Echo(EchoRequest req, out EchoResponse res); + rpc Echo(EchoRequest) returns (EchoResponse); + + // 对应idl中的uint32_t EchoWithMultiArgs(EchoRequest req1, EchoRequest req2, out EchoResponse res1, out EchoResponse res2); + rpc EchoWithMultiArgs(MultiRequests) returns (MultiResponses); + } + ``` + + 原先的echo.idl文件: + + ```idl + struct EchoRequest { + string message; + }; + + struct EchoResponse { + string message; + }; + + service EchoService { + void Echo(EchoRequest req, out EchoResponse res); + uint32_t EchoWithMultiArgs(EchoRequest req1, EchoRequest req2, out EchoResponse res1, out EchoResponse res2); + }; + ``` + +2. 插入如下片段以使用代码生成插件。 + + BRPC_PATH代表brpc产出的路径(包含bin include等目录),PROTOBUF_INCLUDE_PATH代表protobuf的包含路径。注意--mcpack_out要和--cpp_out一致。 + + ```shell + protoc --plugin=protoc-gen-mcpack=$BRPC_PATH/bin/protoc-gen-mcpack --cpp_out=. --mcpack_out=. --proto_path=$BRPC_PATH/include --proto_path=PROTOBUF_INCLUDE_PATH + ``` + +3. 用channel发起访问。 + + idl不同于pb,允许有多个请求,我们先看只有一个请求的情况,和普通的pb访问基本上是一样的。 + + ```c++ + #include + #include "echo.pb.h" + ... + + brpc::Channel channel; + brpc::ChannelOptions opt; + opt.protocol = brpc::PROTOCOL_UBRPC_COMPACK; // or "ubrpc_compack"; + if (channel.Init(..., &opt) != 0) { + LOG(ERROR) << "Fail to init channel"; + return -1; + } + EchoService_Stub stub(&channel); + ... + + EchoRequest request; + EchoResponse response; + brpc::Controller cntl; + + request.set_message("hello world"); + + stub.Echo(&cntl, &request, &response, NULL); + + if (cntl.Failed()) { + LOG(ERROR) << "Fail to send request, " << cntl.ErrorText(); + return; + } + // 取response中的字段 + // [idl] void Echo(EchoRequest req, out EchoResponse res); + // ^ + // response.message(); + ``` + + 多个请求要设置一下set_idl_names。 + + ```c++ + #include + #include "echo.pb.h" + ... + + brpc::Channel channel; + brpc::ChannelOptions opt; + opt.protocol = brpc::PROTOCOL_UBRPC_COMPACK; // or "ubrpc_compack"; + if (channel.Init(..., &opt) != 0) { + LOG(ERROR) << "Fail to init channel"; + return -1; + } + EchoService_Stub stub(&channel); + ... + + MultiRequests multi_requests; + MultiResponses multi_responses; + brpc::Controller cntl; + + multi_requests.mutable_req1()->set_message("hello"); + multi_requests.mutable_req2()->set_message("world"); + cntl.set_idl_names(brpc::idl_multi_req_multi_res); + stub.EchoWithMultiArgs(&cntl, &multi_requests, &multi_responses, NULL); + + if (cntl.Failed()) { + LOG(ERROR) << "Fail to send request, " << cntl.ErrorText(); + return; + } + // 取response中的字段 + // [idl] uint32_t EchoWithMultiArgs(EchoRequest req1, EchoRequest req2, + // ^ out EchoResponse res1, out EchoResponse res2); + // | ^ ^ + // | multi_responses.res1().message(); | + // | multi_responses.res2().message(); + // cntl.idl_result(); + ``` + + 例子详见[example/echo_c++_ubrpc_compack](https://github.com/apache/brpc/blob/master/example/echo_c++_ubrpc_compack/)。 + +# ubrpc (by baidu-rpc-ub) + +server端由public/ubrpc搭建,request/response使用idl文件描述字段,序列化格式是compack或mcpack_v2。 + +**步骤:** + +1. 依赖public/baidu-rpc-ub模块,这个模块是brpc的扩展,不需要的用户不会依赖idl/mcpack/compack等模块。baidu-rpc-ub只包含扩展代码,brpc中的新特性会自动体现在这个模块中。 + +2. 编写一个proto文件,其中定义了service,名字和idl中的相同,但请求类型必须是baidu.rpc.UBRequest,回复类型必须是baidu.rpc.UBResponse。这两个类型定义在brpc/ub.proto中,使用时得import。 + + ```protobuf + import "brpc/ub.proto"; // UBRequest, UBResponse + option cc_generic_services = true; + // Define UB service. request/response must be UBRequest/UBResponse + service EchoService { + rpc Echo(baidu.rpc.UBRequest) returns (baidu.rpc.UBResponse); + }; + ``` + +3. 在COMAKE包含baidu-rpc-ub/src路径。 + + ```python + # brpc/ub.proto的包含路径 + PROTOC(ENV.WorkRoot()+"third-64/protobuf/bin/protoc") + PROTOFLAGS("--proto_path=" + ENV.WorkRoot() + "public/baidu-rpc-ub/src/") + ``` + +4. 用法和访问其他协议类似:创建Channel,ChannelOptions.protocol为**brpc::PROTOCOL_NSHEAD_CLIENT**或**"nshead_client"**。request和response对象必须是baidu-rpc-ub提供的类型 + + ```c++ + #include + ... + + brpc::Channel channel; + brpc::ChannelOptions opt; + opt.protocol = brpc::PROTOCOL_NSHEAD_CLIENT; // or "nshead_client"; + if (channel.Init(..., &opt) != 0) { + LOG(ERROR) << "Fail to init channel"; + return -1; + } + EchoService_Stub stub(&channel); + ... + + const int BUFSIZE = 1024 * 1024; // 1M + char* buf_for_mempool = new char[BUFSIZE]; + bsl::xmempool pool; + if (pool.create(buf_for_mempool, BUFSIZE) != 0) { + LOG(FATAL) << "Fail to create bsl::xmempool"; + return -1; + } + + // 构造UBRPC的request/response,idl结构体作为模块参数传入。为了构造idl结构,需要传入一个bsl::mempool + brpc::UBRPCCompackRequest request(&pool); + brpc::UBRPCCompackResponse response(&pool); + + // 设置字段 + request.mutable_req()->set_message("hello world"); + + // 发起RPC + brpc::Controller cntl; + stub.Echo(&cntl, &request, &response, NULL); + + if (cntl.Failed()) { + LOG(ERROR) << "Fail to Echo, " << cntl.ErrorText(); + return; + } + // 取回复中的字段 + response.result_params().res().message(); + ... + ``` + + 具体example代码可以参考[echo_c++_compack_ubrpc](https://github.com/apache/brpc/tree/master/example/echo_c++_compack_ubrpc/),类似的还有[echo_c++_mcpack_ubrpc](https://github.com/apache/brpc/tree/master/example/echo_c++_mcpack_ubrpc/)。 + +# nshead+idl + +server端是由public/ub搭建,通讯包组成为nshead+idl::compack/idl::mcpack(v2) + +由于不需要指定service和method,无需编写proto文件,直接使用Channel.CallMethod方法发起RPC即可。请求包中的nshead可以填也可以不填,框架会补上正确的magic_num和body_len字段: + +```c++ +#include +... + +brpc::Channel channel; +brpc::ChannelOptions opt; +opt.protocol = brpc::PROTOCOL_NSHEAD_CLIENT; // or "nshead_client"; + +if (channel.Init(..., &opt) != 0) { + LOG(ERROR) << "Fail to init channel"; + return -1; +} +... + +// 构造UB的request/response,完全类似构造原先idl结构,传入一个bsl::mempool(变量pool) +// 将类型作为模板传入,之后在使用上可以直接使用对应idl结构的接口 +brpc::UBCompackRequest request(&pool); +brpc::UBCompackResponse response(&pool); + +// Set `message' field of `EchoRequest' +request.set_message("hello world"); +// Set fields of the request nshead struct if needed +request.mutable_nshead()->version = 99; + +brpc::Controller cntl; +channel.CallMethod(NULL, &cntl, &request, &response, NULL); // 假设channel已经通过之前所述方法Init成功 + +// Get `message' field of `EchoResponse' +response.message(); +``` + +具体example代码可以参考[echo_c++_mcpack_ub](https://github.com/apache/brpc/blob/master/example/echo_c++_mcpack_ub/),compack情况类似,不再赘述 + +# nshead+mcpack(非idl产生的) + +server端是由public/ub搭建,通讯包组成为nshead+mcpack包,但不是idl编译器生成的,RPC前需要先构造RawBuffer将其传入,然后获取mc_pack_t并按之前手工填写mcpack的方式操作: + +```c++ +#include +... + +brpc::Channel channel; +brpc::ChannelOptions opt; +opt.protocol = brpc::PROTOCOL_NSHEAD_CLIENT; // or "nshead_client"; +if (channel.Init(..., &opt) != 0) { + LOG(ERROR) << "Fail to init channel"; + return -1; +} +... + +// 构造RawBuffer,一次RPC结束后RawBuffer可以复用,类似于bsl::mempool +const int BUFSIZE = 10 * 1024 * 1024; +brpc::RawBuffer req_buf(BUFSIZE); +brpc::RawBuffer res_buf(BUFSIZE); + +// 传入RawBuffer来构造request和response +brpc::UBRawMcpackRequest request(&req_buf); +brpc::UBRawMcpackResponse response(&res_buf); + +// Fetch mc_pack_t and fill in variables +mc_pack_t* req_pack = request.McpackHandle(); +int ret = mc_pack_put_str(req_pack, "mystr", "hello world"); +if (ret != 0) { + LOG(FATAL) << "Failed to put string into mcpack: " + << mc_pack_perror((long)req_pack) << (void*)req_pack; + break; +} +// Set fields of the request nshead struct if needed +request.mutable_nshead()->version = 99; + +brpc::Controller cntl; +channel.CallMethod(NULL, &cntl, &request, &response, NULL); // 假设channel已经通过之前所述方法Init成功 + +// Get response from response buffer +const mc_pack_t* res_pack = response.McpackHandle(); +mc_pack_get_str(res_pack, "mystr"); +``` + +具体example代码可以参考[echo_c++_raw_mcpack](https://github.com/apache/brpc/blob/master/example/echo_c++_raw_mcpack/)。 + +# nshead+blob + +r32897后brpc直接支持用nshead+blob访问老server(而不用依赖baidu-rpc-ub)。example代码可以参考[nshead_extension_c++](https://github.com/apache/brpc/blob/master/example/nshead_extension_c++/client.cpp)。 + +```c++ +#include +... + +brpc::Channel; +brpc::ChannelOptions opt; +opt.protocol = brpc::PROTOCOL_NSHEAD; // or "nshead" +if (channel.Init(..., &opt) != 0) { + LOG(ERROR) << "Fail to init channel"; + return -1; +} +... +brpc::NsheadMessage request; +brpc::NsheadMessage response; + +// Append message to `request' +request.body.append("hello world"); +// Set fields of the request nshead struct if needed +request.head.version = 99; + + +brpc::Controller cntl; +channel.CallMethod(NULL, &cntl, &request, &response, NULL); + +if (cntl.Failed()) { + LOG(ERROR) << "Fail to access the server: " << cntl.ErrorText(); + return -1; +} +// response.head and response.body contains nshead_t and blob respectively. +``` + +或者用户也可以使用baidu-rpc-ub中的UBRawBufferRequest和UBRawBufferResponse来访问。example代码可以参考[echo_c++_raw_buffer](https://github.com/apache/brpc/blob/master/example/echo_c++_raw_buffer/)。 + +```c++ +brpc::Channel channel; +brpc::ChannelOptions opt; +opt.protocol = brpc::PROTOCOL_NSHEAD_CLIENT; // or "nshead_client" +if (channel.Init(..., &opt) != 0) { + LOG(ERROR) << "Fail to init channel"; + return -1; +} +... + +// 构造RawBuffer,一次RPC结束后RawBuffer可以复用,类似于bsl::mempool +const int BUFSIZE = 10 * 1024 * 1024; +brpc::RawBuffer req_buf(BUFSIZE); +brpc::RawBuffer res_buf(BUFSIZE); + +// 传入RawBuffer来构造request和response +brpc::UBRawBufferRequest request(&req_buf); +brpc::UBRawBufferResponse response(&res_buf); + +// Append message to `request' +request.append("hello world"); +// Set fields of the request nshead struct if needed +request.mutable_nshead()->version = 99; + +brpc::Controller cntl; +channel.CallMethod(NULL, &cntl, &request, &response, NULL); // 假设channel已经通过之前所述方法Init成功 + +// Process response. response.data() is the buffer, response.size() is the length. +``` diff --git a/docs/cn/vars.md b/docs/cn/vars.md new file mode 100644 index 0000000..b460b44 --- /dev/null +++ b/docs/cn/vars.md @@ -0,0 +1,101 @@ +[English version](../en/vars.md) + +[bvar](../../src/bvar)是多线程环境下的计数器类库,支持[单维度bvar](bvar_c++.md)和[多维度mbvar](mbvar_c++.md),方便记录和查看用户程序中的各类数值,它利用了thread local存储减少了cache bouncing,相比UbMonitor(百度内的老计数器库)几乎不会给程序增加性能开销,也快于竞争频繁的原子操作。brpc集成了bvar,[/vars](vars.md)可查看所有曝光的bvar,[/vars/VARNAME](vars.md)可查阅某个bvar,增加计数器的方法请查看[单维度bvar](bvar_c++.md)和[多维度mbvar](mbvar_c++.md)。brpc大量使用了bvar提供统计数值,当你需要在多线程环境中计数并展现时,应该第一时间想到bvar。但bvar不能代替所有的计数器,它的本质是把写时的竞争转移到了读:读得合并所有写过的线程中的数据,而不可避免地变慢了。当你读写都很频繁或得基于最新值做一些逻辑判断时,你不应该用bvar。 + +## 查询方法 + +[/vars](vars.md) : 列出所有曝光的bvar + +[/vars/NAME](vars.md):查询名字为NAME的bvar + +[/vars/NAME1,NAME2,NAME3](vars.md):查询名字为NAME1或NAME2或NAME3的bvar + +[/vars/foo*,b$r](vars.md):查询名字与某一统配符匹配的bvar,注意用$代替?匹配单个字符,因为?是URL的保留字符。 + +以下动画演示了如何使用过滤功能。你可以把包含过滤表达式的url复制粘贴给他人,他们点开后将看到相同的计数器条目。(数值可能随运行变化) + +![img](../images/vars_1.gif) + +/vars左上角有一个搜索框能加快寻找特定bvar的速度,在这个搜索框你只需键入bvar名称的一部分,框架将补上*进行模糊查找。不同的名称间可以逗号、分号或空格分隔。 + +![img](../images/vars_2.gif) + +你也可以在命令行中访问vars: + +``` +$ curl brpc.baidu.com:8765/vars/bthread* +bthread_creation_count : 125134 +bthread_creation_latency : 3 +bthread_creation_latency_50 : 3 +bthread_creation_latency_90 : 5 +bthread_creation_latency_99 : 7 +bthread_creation_latency_999 : 12 +bthread_creation_latency_9999 : 12 +bthread_creation_latency_cdf : "click to view" +bthread_creation_latency_percentiles : "[3,5,7,12]" +bthread_creation_max_latency : 7 +bthread_creation_qps : 100 +bthread_group_status : "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 " +bthread_num_workers : 24 +bthread_worker_usage : 1.01056 +``` + +## 查看历史趋势 + +点击大部分数值型的bvar会显示其历史趋势。每个可点击的bvar记录了过去60秒,60分钟,24小时,30天总计174个值。当有1000个可点击bvar时大约会占1M内存。 + +![img](../images/vars_3.gif) + +## 统计和查看分位值 + +x%分位值(percentile)是指把一段时间内的N个统计值排序,排在第N * x%位的值。比如一段时间内有1000个值,先从小到大排序,排在第500位(1000 * 50%)的值是50%分位值(即中位数),排在第990位的是99%分位值(1000 * 99%),排在第999位的是99.9%分位值。分位值能比平均值更准确的刻画数值的分布,对理解系统行为有重要意义。工业级应用的SLA一般在99.97%以上(此为百度对二级系统的要求,一级是99.99%以上),一些系统即使平均值不错,但不佳的长尾区域也会明显拉低和打破SLA。分位值能帮助分析长尾区域。 + +分位值可以绘制为CDF曲线和按时间变化的曲线。 + +**下图是分位值的CDF**,横轴是比例(排序位置/总数),纵轴是对应的分位值。比如横轴=50%处对应的纵轴值便是50%分位值。如果系统要求的性能指标是"99.9%的请求在xx毫秒内完成“,那么你就得看下99.9%那儿的值。 + +![img](../images/vars_4.png) + +为什么叫它[CDF](https://en.wikipedia.org/wiki/Cumulative_distribution_function)? 当选定一个纵轴值y时,对应横轴的含义是"数值 <= y的比例”,由于数值一般来自随机采样,横轴也可以理解为“数值 <= y的概率”,或P(数值 <= y),这就是CDF的定义。 + +CDF的导数是[概率密度函数](https://en.wikipedia.org/wiki/Probability_density_function)。如果把CDF的纵轴分为很多小段,对每个小段计算两端对应的横轴值之差,并把这个差作为新的横轴,那么我们便绘制了PDF曲线,就像顺时针旋转了90度的正态分布那样。但中位数的密度往往很高,在PDF中很醒目,这使得边上的长尾很扁而不易查看,所以大部分系统测量结果选择CDF曲线而不是PDF曲线。 + +可用一些简单规则衡量CDF曲线好坏: + +- 越平越好。一条水平线是最理想的,这意味着所有的数值都相等,没有任何等待,拥塞,停顿。当然这是不可能的。 +- 99%和100%间的面积越小越好:99%之后是长尾的聚集地,对大部分系统的SLA有重要影响。 + +一条缓慢上升且长尾区域面积不大的CDF便是不错的曲线。 + +**下图是按分位值按时间变化的曲线**,包含了4条曲线,横轴是时间,纵轴从上到下分别对应99.9%,99%,90%,50%分位值。颜色从上到下也越来越浅(从橘红到土黄)。 + +![img](../images/vars_5.png) + +滑动鼠标可以阅读对应数据点的值,上图中显示的是”39秒种前的99%分位值是330**微秒**”。这幅图中不包含99.99%的曲线,因为99.99%分位值常明显大于99.9%及以下的分位值,画在一起的话会使得其他曲线变得很”矮“,难以辨认。你可以点击以"\_latency\_9999"结尾的bvar独立查看99.99%曲线。按时间变化曲线可以看到分位值的变化趋势,对分析系统的性能变化很实用。 + +brpc的服务都会自动统计延时分布,用户不用自己加了。如下图所示: + +![img](../images/vars_6.png) + +你可以用bvar::LatencyRecorder统计任何代码的延时,这么做(更具体的使用方法请查看[bvar-c++](bvar_c++.md)): + +```c++ +#include + +... +bvar::LatencyRecorder g_latency_recorder("client"); // expose this recorder +... +void foo() { + ... + g_latency_recorder << my_latency; + ... +} +``` + +如果这个程序使用了brpc server,那么你应该已经可以在/vars看到client_latency, client_latency_cdf等变量,点击便可查看动态曲线。如下图所示: + +![img](../images/vars_7.png) + +## 非brpc server + +如果你的程序只是一个brpc client或根本没有使用brpc,并且你也想看到动态曲线,看[这里](dummy_server.md)。 diff --git a/docs/cn/wireshark_baidu_std.md b/docs/cn/wireshark_baidu_std.md new file mode 100644 index 0000000..8dc7c4e --- /dev/null +++ b/docs/cn/wireshark_baidu_std.md @@ -0,0 +1,27 @@ +[English version](../en/wireshark_baidu_std.md) + +# 介绍 + +`wireshark_baidu_std.lua` 是针对 [`baidu_std`](baidu_std.md) 协议的 `Wireshark` 解析插件,同时支持 [`streaming_rpc`](streaming_rpc.md) 协议的解析。 + +请求包的解析示例: + +![request](../images/wireshark_baidu_std_request.png) + +响应包的解析示例: + +![response](../images/wireshark_baidu_std_response.png) + + +## 使用方式 + +1. 将 [`wireshark_baidu_std.lua`](../../tools/wireshark_baidu_std.lua) 放到 "Personal Lua Plugins" 目录下; +1. 将 [`options.proto`](../../src/brpc/options.proto)、[`streaming_rpc_meta.proto`](../../src/brpc/streaming_rpc_meta.proto) 以及 [`baidu_rpc_meta.proto`](../../src/brpc/policy/baidu_rpc_meta.proto) 放到 "Personal configuration" 目录下的 `protobuf` 目录中,目录如不存在可以手动创建; +1. 参考 [Wireshark Protobuf](https://wiki.wireshark.org/Protobuf#protobuf-search-paths-settings) 配置 `Protobuf` 系统 proto 文件路经,如:`/opt/homebrew/opt/protobuf/include` + ![wireshark-protobuf-search-paths](../images/wireshark_protobuf_search_paths.png) +1. 可选,如需想使用相关字段进行过滤,可打开如下选项: + ![wireshark-protobuf-settings](../images/wireshark_protobuf_settings.png) + +上面提到的 "Personal Lua Plugins" 以及 "Personal configuration" 目录可查看 `About Wireshark` 的 `Folders` 页面,相关目录是平台相关的,macOS 下如: + +![About Wireshark](../images/wireshark_folders.png) diff --git a/docs/en/atomic_instructions.md b/docs/en/atomic_instructions.md new file mode 100644 index 0000000..3568713 --- /dev/null +++ b/docs/en/atomic_instructions.md @@ -0,0 +1,107 @@ +[中文版](../cn/atomic_instructions.md) + +We know that locks are extensively used in multi-threaded programming to avoid [race conditions](http://en.wikipedia.org/wiki/Race_condition) when modifying shared data. When the lock becomes a bottleneck, we try to walk around it by using atomic instructions. But it is difficult to write correct code with atomic instructions in generally and it is even hard to understand race conditions, [ABA problems](https://en.wikipedia.org/wiki/ABA_problem) and [memory fences](https://en.wikipedia.org/wiki/Memory_barrier). This article tries to introduce basics on atomic instructions(under [SMP](http://en.wikipedia.org/wiki/Symmetric_multiprocessing)). Since [Atomic instructions](http://en.cppreference.com/w/cpp/atomic/atomic) are formally introduced in C++11, we use the APIs directly. + +As the name implies, atomic instructions cannot be divided into more sub-instructions. For example, `x.fetch(n)` atomically adds n to x, any internal state is not observable **to software**. Common atomic instructions are listed below: + +| Atomic Instructions(type of x is std::atomic\) | Descriptions | +| ---------------------------------------- | ---------------------------------------- | +| x.load() | return the value of x. | +| x.store(n) | store n to x and return nothing. | +| x.exchange(n) | set x to n and return the value just before the modification | +| x.compare_exchange_strong(expected_ref, desired) | If x is equal to expected_ref, set x to desired and return true. Otherwise write current x to expected_ref and return false. | +| x.compare_exchange_weak(expected_ref, desired) | may have [spurious wakeup](http://en.wikipedia.org/wiki/Spurious_wakeup) comparing to compare_exchange_strong | +| x.fetch_add(n), x.fetch_sub(n) | do x += n, x-= n atomically. Return the value just before the modification. | + +You can already use these instructions to count something atomically, such as counting number of operations from multiple threads. However two problems may arise: + +- The operation is not as fast as expected. +- Even if multi-threaded accesses to some resources are controlled by a few atomic instructions that seem to be correct, the program still has great chance to crash. + +# Cacheline + +An atomic instruction is fast when there's no contentions or it's accessed only by one thread. "Contentions" happen when multiple threads access the same [cacheline](https://en.wikipedia.org/wiki/CPU_cache#Cache_entries). Modern CPU extensively use caches and divide caches into multiple levels to get high performance with a low price. The Intel E5-2620 widely used in Baidu has 32K L1 dcache and icache, 256K L2 cache and 15M L3 cache. L1 and L2 cache is owned by each core, while L3 cache is shared by all cores. Although it is very fast for one core to write data into its own L1 cache(4 cycles, ~2ns), make the data in L1 cache seen by other cores is not, because cachelines touched by the data need to be synchronized to other cores. This process is atomic and transparent to software and no instructions can be interleaved between. Applications have to wait for the completion of [cache coherence](https://en.wikipedia.org/wiki/Cache_coherence), which takes much longer time than writing local cache. It involves a complicated hardware algorithm and makes atomic instructions slow under high contentions. A single fetch_add may take more than 700ns in E5-2620 when a few threads are highly contented on the instruction. Accesses to the memory frequently shared and modified by multiple threads are not fast generally. For example, even if a critical section looks small, the spinlock protecting it may still not perform well. The cause is that the instructions used in spinlock such as exchange, fetch_add etc, need to wait for latest cachelines. It's not surprising to see that one or two instructions take several microseconds. + +In order to improve performance, we need to avoid frequently synchronizing cachelines, which not only affects performance of the atomic instruction itself, but also overall performance of the program. The most effective solution is straightforward: **avoid sharing as much as possible**. + +- A program relying on a global multiple-producer-multiple-consumer(MPMC) queue is hard to scale well on many CPU cores, because throughput of the queue is limited by delays of cache coherence, rather than number of cores. It would be better to use multiple SPMC or MPSC queues, or even SPSC queues instead, to avoid contentions from the very beginning. +- Another example is counters. If all threads modify a counter frequently, the performance will be poor because all cores are busy synchronizing the same cacheline. If the counter is only used for printing logs periodically or something low-priority like that, we can let each thread modify its own thread-local variables and combine all thread-local data before reading, yielding [much better performance](bvar.md). + +A related programming trap is false sharing: Accesses to infrequently updated or even read-only variables are significantly slowed down because other variables in the same cacheline are frequently updated. Variables used in multi-threaded environment should be grouped by accessing frequencies or patterns, variables that are modified by that other threads frequently should be put into separated cachelines. To align a variable or struct by cacheline, `include ` and tag it with macro `BAIDU_CACHELINE_ALIGNMENT`, grep source code of brpc for examples. + +# Memory fence + +Just atomic counting cannot synchronize accesses to resources, simple structures like [spinlock](https://en.wikipedia.org/wiki/Spinlock) or [reference counting](https://en.wikipedia.org/wiki/Reference_counting) that seem correct may crash as well. The key is **instruction reordering**, which may change the order of read/write and cause instructions behind to be reordered to front if there are no dependencies. [Compiler](http://preshing.com/20120625/memory-ordering-at-compile-time/) and [CPU](https://en.wikipedia.org/wiki/Out-of-order_execution) both may reorder. + +The motivation is natural: CPU wants to fill each cycle with instructions and execute as many as possible instructions within given time. As said in above section, an instruction for loading memory may cost hundreds of nanoseconds due to cacheline synchronization. A efficient solution for synchronizing multiple cachelines is to move them simultaneously rather than one-by-one. Thus modifications to multiple variables by a thread may be visible to another thread in a different order. On the other hand, different threads need different data, synchronizing on-demand is reasonable and may also change order between cachelines. + +For example: the first variable plays the role of switch, controlling accesses to following variables. When these variables are synchronized to other CPU cores, new values may be visible in a different order, and the first variable may not be the first updated, which causes other threads to think that the following variables are still valid, which are actually not, causing undefined behavior. Check code snippet below: + +```c++ +// Thread 1 +// bool ready was initialized to false +p.init(); +ready = true; +``` + +```c++ +// Thread2 +if (ready) { + p.bar(); +} +``` +From a human perspective, the code is correct because thread2 only accesses `p` when `ready` is true which means p is initialized according to the logic in thread1. But the code may not run as expected on multi-core machines: + +- `ready = true` in thread1 may be reordered before `p.init()` by compiler or CPU, making thread2 see uninitialized `p` when `ready` is true. The same reordering may happen in thread2 as well. Some instructions in `p.bar()` may be reordered before checking `ready`. +- Even if above reorderings do not happen, cachelines of `ready` and `p` may be synchronized independently to the CPU core that thread2 runs, making thread2 see unitialized `p` when `ready` is true. + +Note: On x86/x64, `load` has acquire semantics and `store` has release semantics by default, the code above may run correctly provided that reordering by compiler is turned off. + +With this simple example, you may get a glimpse of the complexity of atomic instructions. In order to solve the reordering issue, CPU and compiler offer [memory fences](http://en.wikipedia.org/wiki/Memory_barrier) to let programmers decide the visibility order between instructions. boost and C++11 conclude memory fence into following types: + +| memory order | Description | +| -------------------- | ---------------------------------------- | +| memory_order_relaxed | there are no synchronization or ordering constraints imposed on other reads or writes, only this operation's atomicity is guaranteed | +| memory_order_consume | no reads or writes in the current thread dependent on the value currently loaded can be reordered before this load. | +| memory_order_acquire | no reads or writes in the current thread can be reordered before this load. | +| memory_order_release | no reads or writes in the current thread can be reordered after this store. | +| memory_order_acq_rel | No memory reads or writes in the current thread can be reordered before or after this store. | +| memory_order_seq_cst | Any operation with this memory order is both an acquire operation and a release operation, plus a single total order exists in which all threads observe all modifications in the same order. | + +Above example can be modified as follows: + +```c++ +// Thread1 +// std::atomic ready was initialized to false +p.init(); +ready.store(true, std::memory_order_release); +``` + +```c++ +// Thread2 +if (ready.load(std::memory_order_acquire)) { + p.bar(); +} +``` + +The acquire fence in thread2 matches the release fence in thread1, making thread2 see all memory operations before the release fence in thread1 when thread2 sees `ready` being set to true. + +Note that memory fence does not guarantee visibility. Even if thread2 reads `ready` just after thread1 sets it to true, thread2 is not guaranteed to see the new value, because cache synchronization takes time. Memory fence guarantees ordering between visibilities: "If I see the new value of a, I should see the new value of b as well". + +A related problem: How to know whether a value is updated or not? Two cases in generally: + +- The value is special. In above example, `ready=true` is a special value. Once `ready` is true, `p` is ready. Reading special values or not both mean something. +- Increasing-only values. Some situations do not have special values, we can use instructions like `fetch_add` to increase variables. As long as the value range is large enough, new values are different from old ones for a long period of time, so that we can distinguish them from each other. + +More examples can be found in [boost.atomic](http://www.boost.org/doc/libs/1_56_0/doc/html/atomic/usage_examples.html). Official descriptions of atomics can be found [here](http://en.cppreference.com/w/cpp/atomic/atomic). + +# wait-free & lock-free + +Atomic instructions provide two important properties: [wait-free](http://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom) and [lock-free](http://en.wikipedia.org/wiki/Non-blocking_algorithm#Lock-freedom). Wait-free means no matter how OS schedules, all threads are doing useful jobs; lock-free, which is weaker than wait-free, means no matter how OS schedules, at least one thread is doing useful jobs. If locks are used, the thread holding the lock might be paused by OS, in which case all threads trying to hold the lock are blocked. So code using locks are neither lock-free nor wait-free. To make tasks done within given time, critical paths in real-time OS is at least lock-free. Miscellaneous online services inside Baidu also pose serious restrictions on running time. If the critical path in brpc is wait-free or lock-free, many services are benefited from better and stable QoS. Actually, both read(in the sense of even dispatching) and write in brpc are wait-free, check [IO](io.md) for details. + +Note that it is common to think that wait-free or lock-free algorithms are faster, which may not be true, because: + +- More complex race conditions and ABA problems must be handled in lock-free and wait-free algorithms, which means the code is often much more complicated than the one using locks. More code, more running time. +- Mutex solves contentions by backoff, which means that when contention happens, another branch is entered to avoid the contention temporarily. Threads failed to lock a mutex are put into sleep, making the thread holding the mutex complete the task or even follow-up tasks exclusively, which may increase the overall throughput. + +Low performance caused by mutex is either because of too large critical sections (which limit the concurrency), or too heavy contentions (overhead of context switches becomes dominating). The real value of lock-free/wait-free algorithms is that they guarantee progress of the system, rather than absolutely high performance. Of course lock-free/wait-free algorithms perform better in some situations: if an algorithm is implemented by just one or two atomic instructions, it's probably faster than the one using mutex which needs more atomic instructions in total. diff --git a/docs/en/auto_concurrency_limiter.md b/docs/en/auto_concurrency_limiter.md new file mode 100644 index 0000000..deaaa58 --- /dev/null +++ b/docs/en/auto_concurrency_limiter.md @@ -0,0 +1,7 @@ +# auto concurrency limiter + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/auto_concurrency_limiter.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/avalanche.md b/docs/en/avalanche.md new file mode 100644 index 0000000..7a8d25e --- /dev/null +++ b/docs/en/avalanche.md @@ -0,0 +1,5 @@ +"Avalanche" refers to the phenomenon that the vast majority of requests are timed out when accessing a service cluster and can not be recovered when the traffic decreases. Next we explain the source of this phenomenon. + +When the number of request exceeds the maximum qps of service, the service will not work properly; when the traffic is back to normal(less than the service processing capacity), the backlog requests will be processed. Although most of them may be timed out due to not being processed timely, the service itself will generally return to normal. This is just like a pool has a water inlet and a water outlet, if the amount of water in is greater than that of water out, the pool will eventually be full and more water will overflow. However, if the amount of water in is less than that of water out, the pool will be eventually empty after a period of time. + + diff --git a/docs/en/backup_request.md b/docs/en/backup_request.md new file mode 100644 index 0000000..e61f361 --- /dev/null +++ b/docs/en/backup_request.md @@ -0,0 +1,120 @@ +Sometimes in order to ensure availability, we need to visit two services at the same time and get the result coming back first. There are several ways to achieve this in brpc: + +# When backend servers can be hung in a naming service + +Channel opens backup request. Channel sends the request to one of the servers and when the response is not returned after ChannelOptions.backup_request_ms ms, it sends to another server, taking the response that coming back first. After backup_request_ms is set up properly, in most of times only one request should be sent, causing no extra pressure to back-end services. + +Read [example/backup_request_c++](https://github.com/apache/brpc/blob/master/example/backup_request_c++) as example code. In this example, client sends backup request after 2ms and server sleeps for 20ms on purpose when the number of requests is even to trigger backup request. + +After running, the log in client and server is as following. "Index" is the number of request. After the server receives the first request, it will sleep for 20ms on purpose. Then the client sends the request with the same index. The final delay is not affected by the intentional sleep. + +![img](../images/backup_request_1.png) + +![img](../images/backup_request_2.png) + +/rpcz also shows that the client triggers backup request after 2ms and sends the second request. + +![img](../images/backup_request_3.png) + +## Choose proper backup_request_ms + +You can look the default cdf(Cumulative Distribution Function) graph of latency provided by brpc, or add it by your own. The y-axis of the cdf graph is a latency(us by default), and the x-axis is the proportion of requests whose latencies are less than the corresponding value in y-aixs. In the following graph, Choosing backup_request_ms=2ms could approximately cover 95.5% of the requests, while choosing backup_request_ms=10ms could cover 99.99% of the requests. + +![img](../images/backup_request_4.png) + +The way of adding it by yourself: + +```c++ +#include +#include +... +bvar::LatencyRecorder my_func_latency("my_func"); +... +butil::Timer tm; +tm.start(); +my_func(); +tm.stop(); +my_func_latency << tm.u_elapsed(); // u represents for microsecond, and s_elapsed(), m_elapsed(), n_elapsed() correspond to second, millisecond, nanosecond. + +// All work is done here. My_func_qps, my_func_latency, my_func_latency_cdf and many other counters would be shown in /vars. +``` + +## Rate-limited backup requests + +To limit the ratio of backup requests sent, use the built-in factory function or implement the `BackupRequestPolicy` interface yourself. + +Priority order: `backup_request_policy` > `backup_request_ms`. + +### Using the built-in rate-limiting policy + +Call `CreateRateLimitedBackupPolicy` and set the result on `ChannelOptions.backup_request_policy`: + +```c++ +#include "brpc/backup_request_policy.h" +#include + +brpc::RateLimitedBackupPolicyOptions opts; +opts.backup_request_ms = 10; // send backup if RPC does not complete within 10ms +opts.max_backup_ratio = 0.3; // cap backup requests at 30% of total +opts.window_size_seconds = 10; // sliding window width in seconds +opts.update_interval_seconds = 5; // how often the cached ratio is refreshed + +// The caller owns the returned pointer. +// The policy must outlive the channel — destroy the channel before the policy. +std::unique_ptr policy( + brpc::CreateRateLimitedBackupPolicy(opts)); + +brpc::ChannelOptions options; +options.backup_request_policy = policy.get(); // NOT owned by channel +channel.Init(..., &options); +// channel must be destroyed before policy goes out of scope. +``` + +`RateLimitedBackupPolicyOptions` fields: + +| Field | Default | Description | +|-------|---------|-------------| +| `backup_request_ms` | -1 | Timeout threshold in ms. -1 means inherit from `ChannelOptions.backup_request_ms` (only works when the policy is set via `ChannelOptions.backup_request_policy`; at controller level there is no channel-level fallback, so set an explicit >= 0 value instead). Must be >= -1. | +| `max_backup_ratio` | 0.1 | Max backup ratio; range (0, 1] | +| `window_size_seconds` | 10 | Sliding window width in seconds; range [1, 3600] | +| `update_interval_seconds` | 5 | Cached-ratio refresh interval in seconds; must be >= 1 | + +`CreateRateLimitedBackupPolicy` returns `NULL` if any parameter is invalid. + +### Using a custom BackupRequestPolicy + +For full control, implement the `BackupRequestPolicy` interface and set it on `ChannelOptions.backup_request_policy`: + +```c++ +#include "brpc/backup_request_policy.h" + +class MyBackupPolicy : public brpc::BackupRequestPolicy { +public: + int32_t GetBackupRequestMs(const brpc::Controller*) const override { + return 10; // send backup after 10ms + } + bool DoBackup(const brpc::Controller*) const override { + return should_allow_backup(); // your logic here + } + void OnRPCEnd(const brpc::Controller*) override { + // called on every RPC completion; update stats if needed + } +}; + +MyBackupPolicy my_policy; +brpc::ChannelOptions options; +options.backup_request_policy = &my_policy; // NOT owned by channel; must outlive channel +channel.Init(..., &options); +``` + +### Implementation notes + +- The ratio is computed over a sliding time window using bvar counters. The cached value is refreshed at most once per `update_interval_seconds` using a lock-free CAS election, so the overhead per RPC is very low (two atomic loads in the common path). +- Backup decisions are counted immediately at decision time (before the RPC completes) to provide faster feedback during latency spikes. Total RPCs are counted on completion. This means the ratio may transiently lag during a spike, but this is intentional — the limiter is designed for approximate, best-effort throttling, not exact enforcement. +- Each channel using rate limiting maintains two `bvar::Window` sampler tasks. Keep this in mind in deployments with a very large number of channels. + +# When backend servers cannot be hung in a naming service + +[Recommended] Define a SelectiveChannel that sets backup request, in which contains two sub channel. The visiting process of this SelectiveChannel is similar to the above situation. It will visit one sub channel first. If the response is not returned after channelOptions.backup_request_ms ms, then another sub channel is visited. If a sub channel corresponds to a cluster, this method does backups between two clusters. An example of SelectiveChannel can be found in [example/selective_echo_c++](https://github.com/apache/brpc/tree/master/example/selective_echo_c++). More details please refer to the above program. + +[Not Recommended] Issue two asynchronous RPC calls and join them. They cancel each other in their done callback. An example is in [example/cancel_c++](https://github.com/apache/brpc/tree/master/example/cancel_c++). The problem of this method is that the program always sends two requests, doubling the pressure to back-end services. It is uneconomical in any sense and should be avoided as much as possible. diff --git a/docs/en/baidu_std.md b/docs/en/baidu_std.md new file mode 100644 index 0000000..c3fcc1f --- /dev/null +++ b/docs/en/baidu_std.md @@ -0,0 +1,7 @@ +# uaidu std + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/baidu_std.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/bazel_support.md b/docs/en/bazel_support.md new file mode 100644 index 0000000..607cf9c --- /dev/null +++ b/docs/en/bazel_support.md @@ -0,0 +1,20 @@ +## bRPC as a Bazel third-party dependency +1. bRPC relies on a number of open source libraries that do not provide bazel support, so you will need to manually add some of these dependencies to your build project. +2. Move the BUILD file /example/build_with_bazel/*.BUILD and brpc_workspace.bzl to the root of your project, and add the contents of +```c++ + load("@//:brpc_workspace.bzl", "brpc_workspace") + brpc_workspace(); +``` +to your WORKSPACE + +3. link apache_brpc like: + ```c++ + ... + deps = [ + "@apache_brpc//:bthread", + "@apache_brpc//:brpc", + "@apache_brpc//:butil", + "@apache_brpc//:bvar", + ] + ... + ``` diff --git a/docs/en/benchmark.md b/docs/en/benchmark.md new file mode 100644 index 0000000..1c5cbe7 --- /dev/null +++ b/docs/en/benchmark.md @@ -0,0 +1,7 @@ +# uenchmark + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/benchmark.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/benchmark_http.md b/docs/en/benchmark_http.md new file mode 100644 index 0000000..905bb7e --- /dev/null +++ b/docs/en/benchmark_http.md @@ -0,0 +1,7 @@ +# uenchmark http + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/benchmark_http.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/brpc_internal.pptx b/docs/en/brpc_internal.pptx new file mode 100644 index 0000000..19cda8a Binary files /dev/null and b/docs/en/brpc_internal.pptx differ diff --git a/docs/en/bthread.md b/docs/en/bthread.md new file mode 100644 index 0000000..7c627f1 --- /dev/null +++ b/docs/en/bthread.md @@ -0,0 +1,7 @@ +# uthread + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/bthread.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/bthread_id.md b/docs/en/bthread_id.md new file mode 100644 index 0000000..ff67747 --- /dev/null +++ b/docs/en/bthread_id.md @@ -0,0 +1,7 @@ +# uthread id + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/bthread_id.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/bthread_or_not.md b/docs/en/bthread_or_not.md new file mode 100644 index 0000000..2450c3c --- /dev/null +++ b/docs/en/bthread_or_not.md @@ -0,0 +1,7 @@ +# uthread or not + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/bthread_or_not.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/bthread_tagged_task_group.md b/docs/en/bthread_tagged_task_group.md new file mode 100644 index 0000000..b509b31 --- /dev/null +++ b/docs/en/bthread_tagged_task_group.md @@ -0,0 +1,7 @@ +# uthread tagged task group + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/bthread_tagged_task_group.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/bthread_tracer.md b/docs/en/bthread_tracer.md new file mode 100644 index 0000000..6df0d8d --- /dev/null +++ b/docs/en/bthread_tracer.md @@ -0,0 +1,7 @@ +# uthread tracer + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/bthread_tracer.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/builtin_service.md b/docs/en/builtin_service.md new file mode 100644 index 0000000..6cab495 --- /dev/null +++ b/docs/en/builtin_service.md @@ -0,0 +1,59 @@ +[中文版](../cn/builtin_service.md) + +# Builtin Services + +Builtin services expose internal status of servers in different pespectives, making development and debugging over brpc more efficient. brpc serves builting services via HTTP, which can be easily accessed through curl and web browsers. Servers respond plain text or html according to `User-Agent` in the request header, or you may append `?console=1` to the uri to force the server to respond in plain text. Check the [example](http://brpc.baidu.com:8765/) running on our dev machine(only accessible from Baidu internal) for more details. If the port is forbidden from where you run curl or web browser (e.g. not all ports are accessible from a web browser inside Baidu), you can use [rpc_view](rpc_view.md) for proxying. + +Following 2 screenshots show accesses to builtin services from a web browser and a terminal respectively. Note that the logo is the codename inside Baidu, and being modified to brpc in opensourced version. + +**From a web browser** + +![img](../images/builtin_service_more.png) + +**From a terminal** + +![img](../images/builtin_service_from_console.png) + +# Security Mode + +To avoid potential attacks and information leaks, builtin services **must** be hidden on servers that may be accessed from public, including the ones proxied by nginx or other http servers. Click [here](server.md#security-mode) for more details. + +# Main services: + +[/status](status.md): displays brief status of all services. + +[/vars](vars.md): lists user-customizable counters on miscellaneous metrics. + +[/connections](../cn/connections.md): lists all connections and their stats. + +[/flags](../cn/flags.md): lists all gflags, some of them are modifiable at run-time. + +[/rpcz](../cn/rpcz.md): traces all RPCs. + +[cpu profiler](../cn/cpu_profiler.md): analyzes CPU hotspots. + +[heap profiler](../cn/heap_profiler.md): shows how memory are allocated. + +[contention profiler](../cn/contention_profiler.md): analyzes lock contentions. + +# Other services + +[/version](http://brpc.baidu.com:8765/version) shows version of the server. Call Server::set_version() to specify version of the server, or brpc would generate a default version like `brpc_server__ ...` + +![img](../images/version_service.png) + +[/health](http://brpc.baidu.com:8765/health) shows whether this server is alive or not. + +![img](../images/health_service.png) + +[/protobufs](http://brpc.baidu.com:8765/protobufs) shows scheme of all protobuf messages inside the server. + +![img](../images/protobufs_service.png) + +[/vlog](http://brpc.baidu.com:8765/vlog) shows all the [VLOG](streaming_log.md#VLOG) that can be enabled(not working with glog). + +![img](../images/vlog_service.png) + +/dir: browses all files on the server, convenient but too dangerous, disabled by default. + +/threads: displays information of all threads of the process, hurting performance significantly when being turned on, disabled by default. diff --git a/docs/en/bvar.md b/docs/en/bvar.md new file mode 100644 index 0000000..c2496a2 --- /dev/null +++ b/docs/en/bvar.md @@ -0,0 +1,81 @@ +[中文版](../cn/bvar.md) + +# What is bvar? + +[bvar](https://github.com/apache/brpc/tree/master/src/bvar/) is a set of counters to record and view miscellaneous statistics conveniently in multi-threaded applications. The implementation reduces cache bouncing by storing data in thread local storage(TLS), being much faster than UbMonitor(a legacy counting library inside Baidu) and even atomic operations in highly contended scenarios. brpc integrates bvar by default, namely all exposed bvars in a server are accessible through [/vars](http://brpc.baidu.com:8765/vars), and a single bvar is addressable by [/vars/VARNAME](http://brpc.baidu.com:8765/vars/rpc_socket_count). Read [vars](vars.md) to know how to query them in brpc servers. brpc extensively use bvar to expose internal status. If you are looking for an utility to collect and display metrics of your application, consider bvar in the first place. bvar definitely can't replace all counters, essentially it moves contentions occurred during write to read: which needs to combine all data written by all threads and becomes much slower than an ordinary read. If read and write on the counter are both frequent or decisions need to be made based on latest values, you should not use bvar. + +To understand how bvar works, read [explaining cacheline](atomic_instructions.md#cacheline) first, in which the mentioned counter example is just bvar. When many threads are modifying a counter, each thread writes into its own area without joining the global contention and all private data are combined at read, which is much slower than an ordinary one, but OK for low-frequency logging or display. The much faster and very-little-overhead write enables users to monitor systems from all aspects without worrying about hurting performance. This is the purpose that we designed bvar. + +Following graph compares overhead of bvar, atomics, static UbMonitor, dynamic UbMonitor when they're accessed by multiple threads simultaneously. We can see that overhead of bvar is not related to number of threads basically, and being constantly low (~20 nanoseconds). As a contrast, dynamic UbMonitor costs 7 microseconds on each operation when there're 24 threads, which is the overhead of using the bvar for 300 times. + +![img](../images/bvar_perf.png) + +# Adding new bvar + +Read [Quick introduction](bvar_c++.md#quick-introduction) to know how to add bvar in C++. bvar already provides stats of many process-level and system-level variables by default, which are prefixed with `process_` and `system_`, such as: + +``` +process_context_switches_involuntary_second : 14 +process_context_switches_voluntary_second : 15760 +process_cpu_usage : 0.428 +process_cpu_usage_system : 0.142 +process_cpu_usage_user : 0.286 +process_disk_read_bytes_second : 0 +process_disk_write_bytes_second : 260902 +process_faults_major : 256 +process_faults_minor_second : 14 +process_memory_resident : 392744960 +system_core_count : 12 +system_loadavg_15m : 0.040 +system_loadavg_1m : 0.000 +system_loadavg_5m : 0.020 +``` + +and miscellaneous bvars used by brpc itself: + +``` +bthread_switch_second : 20422 +bthread_timer_scheduled_second : 4 +bthread_timer_triggered_second : 4 +bthread_timer_usage : 2.64987e-05 +bthread_worker_count : 13 +bthread_worker_usage : 1.33733 +bvar_collector_dump_second : 0 +bvar_collector_dump_thread_usage : 0.000307385 +bvar_collector_grab_second : 0 +bvar_collector_grab_thread_usage : 1.9699e-05 +bvar_collector_pending_samples : 0 +bvar_dump_interval : 10 +bvar_revision : "34975" +bvar_sampler_collector_usage : 0.00106495 +iobuf_block_count : 89 +iobuf_block_count_hit_tls_threshold : 0 +iobuf_block_memory : 729088 +iobuf_newbigview_second : 10 +``` +New exported files overwrite previous files, which is different from regular logs which append new data. + +# Monitoring bvar +Turn on [dump feature](bvar_c++.md#export-all-variables) of bvar to export all exposed bvars to files, which are formatted just like above texts: each line is a pair of "name" and "value". Check if there're data under $PWD/monitor/ after enabling dump, for example: + +``` +$ ls monitor/ +bvar.echo_client.data bvar.echo_server.data + +$ tail -5 monitor/bvar.echo_client.data +process_swaps : 0 +process_time_real : 2580.157680 +process_time_system : 0.380942 +process_time_user : 0.741887 +process_username : "gejun" +``` + +The monitoring system should combine data on every single machine periodically and merge them together to provide on-demand queries. Take the "noah" system inside Baidu as an example, variables defined by bvar appear as metrics in noah, which can be checked by users to view historical curves. + +![img](../images/bvar_noah2.png) + +![img](../images/bvar_noah3.png) + +# Export to Prometheus + +To export to [Prometheus](https://prometheus.io), set the path in scraping target url to `/brpc_metrics`. For example, if brpc server is running on localhost:8080, the scraping target should be `127.0.0.1:8080/brpc_metrics`. diff --git a/docs/en/bvar_c++.md b/docs/en/bvar_c++.md new file mode 100644 index 0000000..160cdfc --- /dev/null +++ b/docs/en/bvar_c++.md @@ -0,0 +1,498 @@ + +# Quick introduction + +The basic usage of bvar is simple: + +```c++ +#include + +namespace foo { +namespace bar { + +// bvar::Adder used for running sum, we define a Adder for read_error as below +bvar::Adder g_read_error; + +// put another bvar inside window so that we can get the value over this period of time +bvar::Window > g_read_error_minute("foo_bar", "read_error", &g_read_error, 60); +// ^ ^ ^ +// prefix1 monitor name time window, 10 by default + +// bvar::LatencyRecorder is a compound varibale, can be used for troughput、qps、avg latency, latency percentile, max latency。 +bvar::LatencyRecorder g_write_latency("foo_bar", "write"); +// ^ ^ +// prefix1 monitor entry, LatencyRecorder includes different bvar, and expose() will add the suffix for them by default, such as write_qps, write_latency etc + +// define a varible for the # of 'been-pushed task' +bvar::Adder g_task_pushed("foo_bar", "task_pushed"); +// put nested bvar into PerSecond so that we can get the value per second within this time window. Over here what we get is the # of tasks pushed per second +bvar::PerSecond > g_task_pushed_second("foo_bar", "task_pushed_second", &g_task_pushed); +// ^ ^ +// different from Window, PerSecond will be divided by the time winodw. time window is the last param, we omit here, its 10 by default + +} // bar +} // foo +``` + +how we use the bvar +```c++ +// run into read errors +foo::bar::g_read_error << 1; + +// record down the latenct, which is 23ms +foo::bar::g_write_latency << 23; + +// one task has been pushed +foo::bar::g_task_pushed << 1; +``` +Remember Window<> and PerSecond<> are derived variables, we don't have to push value to them and they will auto-update. +Obviously, we can take bvar as local or member variables. + +There are essentially 7 commonly used bvar classes, they all extend from the base class bvar:Variable. + +- `bvar::Adder` : counter, 0 by default, varname << N equals to varname += N。 +- `bvar::Maxer` : get the maximum value, std::numeric_limits::min() by default, varname << N equals to varname = max(varname, N)。 +- `bvar::Miner` : get the minimum value, std::numeric_limits::max() by default, varname << N equals to varname = min(varname, N)。 +- `bvar::IntRecorder` : get the mean value since it was in use, notice here we don't use “over a period of time”, since this bvar always comes with Window<> to calculate the mean value within the predefined time window. +- `bvar::Window` : get the running sum over a time window. Window derives from other existing bvar and will auto-update +- `bvar::PerSecond` : get the value per second during a predefined amount of time. PerSecond will also auto-update and derives from other bvar +- `bvar::LatencyRecorder` : intended for recording latency and qps, when we push latency to it, for mean latency/max lantency/qps, we will get all in once. + +**caveat: make sure the name of bvar is globally unique, otherwise, the expose() will fail. When the option -bvar_abort_on_same_name is true(false by default), program will abort.** + +### Best Practice for Naming: +There are different bvar from different module, to avoid duplicating name, we'd better follow the rule as `module_class_indicator +- a module usually refers to the program name, can be the acronym of product line, like inf_ds, ecom_retrbs etc. +- a class usually refers to the class name/ function name, like storage_manager, file_transfer, rank_stage1. +- an indicator usually refers to qps, count, latency etc. + +some legit naming examples +``` +iobuf_block_count : 29 # module=iobuf class=block indicator=count +iobuf_block_memory : 237568 # module=iobuf class=block indicator=memory +process_memory_resident : 34709504 # module=process class=memory indicator=resident +process_memory_shared : 6844416 # module=process class=memory indicator=shared +rpc_channel_connection_count : 0 # module=rpc class=channel_connection indicator=count +rpc_controller_count : 1 # module=rpc class=controller indicator=count +rpc_socket_count : 6 # module=rpc class=socket indicator=count +``` +bvar will normalize the variable name, no matter whether we type foo::BarNum, foo.bar.num, foo bar num , foo-bar-num, they all go to foo_bar_num in the end. + + + +**Things about indicators:** + +- use `_count` as suffix for number, like request_count, error_count +- use `_second` as suffix for number per second is clear enough, no need to use '_count_second' or '_per_second', like request_second, process_inblocks_second +- `_minute` as suffix for number per minute like request_minute, process_inblocks_minute + +if we need to use a counter defined in another file, we have to declare that variable in header file +``` +namespace foo { +namespace bar { +// notice g_read_error_minute and g_task_pushed_second are derived bvar, will auto update, no need to declare +extern bvar::Adder g_read_error; +extern bvar::LatencyRecorder g_write_latency; +extern bvar::Adder g_task_pushed; +} // bar +} // foo +``` +**Don't define golabel Window<> and PerSecond<> across files. The order for the initialization of global variables in different compile units is undefined.** foo.cpp defines `Adder foo_count`, then defining `PerSecond > foo_qps(&foo_count);` in foo_qps.cpp is illegal + +**Things about thread-safety**: + +- bvar is thread-compatible. We can manipulate a bvar in different threads, such as we can expose or hide different bvar in multiple threads simultaneously, they will safely do some job on global shared variables. +- **Excpet read and write API,** any other functions of bvar are not thread-safe:u can not expose or hide a same bvar in different threads, this may cause the program crash. Generally speaking, we don't have to call any other API concurrently except read and write. + +we can use butil::Timer for timer, API is as below: +```C++ +#include +namespace butil { +class Timer { +public: + enum TimerType { STARTED }; + + Timer(); + + // butil::Timer tm(butil::Timer::STARTED); // tm is already started after creation. + explicit Timer(TimerType); + + // Start this timer + void start(); + + // Stop this timer + void stop(); + + // Get the elapse from start() to stop(). + int64_t n_elapsed() const; // in nanoseconds + int64_t u_elapsed() const; // in microseconds + int64_t m_elapsed() const; // in milliseconds + int64_t s_elapsed() const; // in seconds +}; +} // namespace butil +``` + + +# bvar::Variable: + +Varibale is the base class for all bvar, it provides registering, listing and searching functions. + +When user created a bvar with default params, it hasn't been registered into any global structure, it's merely a faster counter, which means we cannot use it elsewhere. The action of putting this bvar into the global registry is called `expose`, can be achieved by calling `expose()` + +The name for globally exposed bvar consists of 'name' or 'name+prefix', can be searched by function with suffix `_exposed` , e.g. Variable::describe_exposed("foo") will return the description of bvar with the name 'foo'. + +When there already exists the name, expose() will print FATAL log and return -1. When the option **-bvar_abort_on_same_name** is true(false by default), program will abort. + +Some examples for expose() as below +``` +bvar::Adder count1; // create a bvar with defalut params +count1 << 10 << 20 << 30; // values add up to 60. +count1.expose("count1"); // expose the variable globally +CHECK_EQ("60", bvar::Variable::describe_exposed("count1")); +count1.expose("another_name_for_count1"); // expose the variable with another name +CHECK_EQ("", bvar::Variable::describe_exposed("count1")); +CHECK_EQ("60", bvar::Variable::describe_exposed("another_name_for_count1")); + +bvar::Adder count2("count2"); // exposed in constructor directly +CHECK_EQ("0", bvar::Variable::describe_exposed("count2")); // default value of Adder is 0 + +bvar::Status status1("count2", "hello"); // the name conflicts. if -bvar_abort_on_same_name is true, + // program aborts, otherwise a fatal log is printed. +``` +To avoid duplicate name, we should have prefix for bvar, we recommend the name as `__` + +For convenience, we provide expose_as() as it will accept a prefix. +```C++ +// Expose this variable with a prefix. +// Example: +// namespace foo { +// namespace bar { +// class ApplePie { +// ApplePie() { +// // foo_bar_apple_pie_error +// _error.expose_as("foo_bar_apple_pie", "error"); +// } +// private: +// bvar::Adder _error; +// }; +// } // foo +// } // bar +int expose_as(const butil::StringPiece& prefix, const butil::StringPiece& name); +``` +# Export All Variables + +Common needs for exporting are querying by HTTP API and writing into local file, the former is provided by brpc [/vars](https://github.com/apache/brpc/blob/master/docs/cn/vars.md) service, the latter has been implemented in bvar, and it's turned off by default. A couple of methods can activate this function: + +- Using [gflags](https://github.com/apache/brpc/blob/master/docs/cn/flags.md) to parse the input params. We can add `-bvar_dump` during the starup of program or we can dynamically change the params thru brpc [/flags](https://github.com/apache/brpc/blob/master/docs/cn/flags.md) service after starup. gflags parsing is as below + ```C++ + #include + ... + int main(int argc, char* argv[]) { + if (google::SetCommandLineOption("bvar_dump", "true").empty()) { + LOG(FATAL) << "Fail to enable bvar dump"; + } + ... + } + ``` + + +- If u dont want to use gflags and expect them opened by default in program + ```C++ + #include + ... + int main(int argc, char* argv[]) { + if (google::SetCommandLineOption("bvar_dump", "true").empty()) { + LOG(FATAL) << "Fail to enable bvar dump"; + } + ... + } + + ``` + +- dump function is controlled by following gflags + | Name | Default Value | Effect | + | ------------------ | ----------------------- | ---------------------------------------- | + | bvar_dump | false | Create a background thread dumping all bvar periodically, all bvar_dump_* flags are not effective when this flag is off | + | bvar_dump_exclude | "" | Dump bvar excluded from these wildcards(separated by comma), empty means no exclusion | + | bvar_dump_file | monitor/bvar.\.data | Dump bvar into this file | + | bvar_dump_include | "" | Dump bvar matching these wildcards(separated by comma), empty means including all | + | bvar_dump_interval | 10 | Seconds between consecutive dump | + | bvar_dump_prefix | \ | Every dumped name starts with this prefix | + | bvar_dump_tabs | \ | Dump bvar into different tabs according to the filters (separated by semicolon), format: *(tab_name=wildcards) | + + + when the bvar_dump_file is not empty, a background thread will be started to update `bvar_dump_file` for the specified time interval called `bvar_dump_interval` , including all the bvar which is matched by `bvar_dump_include` while not matched by `bvar_dump_exclude` + + such like we modify the gflags as below: + + [![img](https://github.com/apache/brpc/raw/master/docs/images/bvar_dump_flags_2.png)](https://github.com/apache/brpc/blob/master/docs/images/bvar_dump_flags_2.png) + + exporting file will be like: + ``` + $ cat bvar.echo_server.data + rpc_server_8002_builtin_service_count : 20 + rpc_server_8002_connection_count : 1 + rpc_server_8002_nshead_service_adaptor : brpc::policy::NovaServiceAdaptor + rpc_server_8002_service_count : 1 + rpc_server_8002_start_time : 2015/07/24-21:08:03 + rpc_server_8002_uptime_ms : 14740954 + ``` + `iobuf_block_count : 8` is filtered out by bvar_dump_include, `rpc_server_8002_error : 0` is ruled out by bvar_dump_exclude. + + if you didn't use brpc in your program, u also need to dynamically change gflags(normally no need), we can call google::SetCommandLineOption(), as below + ```c++ + #include + ... + if (google::SetCommandLineOption("bvar_dump_include", "*service*").empty()) { + LOG(ERROR) << "Fail to set bvar_dump_include"; + return -1; + } + LOG(INFO) << "Successfully set bvar_dump_include to *service*"; + ``` + Do not directly set FLAGS_bvar_dump_file / FLAGS_bvar_dump_include / FLAGS_bvar_dump_exclude. On the one hand, these gflags are std::string, which are not thread-safe to be overridden;On the other hand, the validator will not be triggered(call back to check the correctness), so the exporting thread will not be invoked + + users can also customize dump_exposed() to export all the exposed bvar: + ```c++ + // Implement this class to write variables into different places. + // If dump() returns false, Variable::dump_exposed() stops and returns -1. + class Dumper { + public: + virtual bool dump(const std::string& name, const butil::StringPiece& description) = 0; + }; + + // Options for Variable::dump_exposed(). + struct DumpOptions { + // Contructed with default options. + DumpOptions(); + // If this is true, string-type values will be quoted. + bool quote_string; + // The ? in wildcards. Wildcards in URL need to use another character + // because ? is reserved. + char question_mark; + // Separator for white_wildcards and black_wildcards. + char wildcard_separator; + // Name matched by these wildcards (or exact names) are kept. + std::string white_wildcards; + // Name matched by these wildcards (or exact names) are skipped. + std::string black_wildcards; + }; + + class Variable { + ... + ... + // Find all exposed variables matching `white_wildcards' but + // `black_wildcards' and send them to `dumper'. + // Use default options when `options' is NULL. + // Return number of dumped variables, -1 on error. + static int dump_exposed(Dumper* dumper, const DumpOptions* options); + }; + ``` + + + +# bvar::Reducer + +Reducer uses binary operators over several values to combine them into one final result, which are commutative, associative and without side effect. Only satisfying all of these thress, we can make sure the combined result is not affected by the distribution of the private variables of thread. Like substraciton does not satisfy associative nor commutative, so it cannot be taken as the operator here. +```C++ +// Reduce multiple values into one with `Op': e1 Op e2 Op e3 ... +// `Op' shall satisfy: +// - associative: a Op (b Op c) == (a Op b) Op c +// - commutative: a Op b == b Op a; +// - no side effects: a Op b never changes if a and b are fixed. +// otherwise the result is undefined. +template +class Reducer : public Variable; +``` +reducer << e1 << e2 << e3 equals to reducer = e1 op e2 op e3。 +Common Redcuer subclass: bvar::Adder, bvar::Maxer, bvar::Miner + +## bvar::Adder + +we can tell from its name, it's intended for running sum. Opeator is `+` +```c++ +bvar::Adder value; +value << 1 << 2 << 3 << -4; +CHECK_EQ(2, value.get_value()); + +bvar::Adder fp_value; // may have warning +fp_value << 1.0 << 2.0 << 3.0 << -4.0; +CHECK_DOUBLE_EQ(2.0, fp_value.get_value()); +``` + + +Adder<> can be applied to the non-primitive type, which at least overrides `T operator+(T, T)`, an existing example is `std::string`, the code below will concatenate strings: +```c++ +// This is just proof-of-concept, don't use it for production code because it makes a +// bunch of temporary strings which is not efficient, use std::ostringstream instead. +bvar::Adder concater; +std::string str1 = "world"; +concater << "hello " << str1; +CHECK_EQ("hello world", concater.get_value()); +``` +## bvar::Maxer + +is producing the maximum value, operator is `std::max` 。 +```c++ +bvar::Maxer value; +value << 1 << 2 << 3 << -4; +CHECK_EQ(3, value.get_value()); +``` +Since Maxer<> use std::numeric_limits::min() as the identity, it cannot be applied to generic types unless you specialized std::numeric_limits<> (and overloaded operator<, yes, not operator>). + +## bvar::Miner + +producing minimum value, operator is std::min +```c++ +bvar::Miner value; +value << 1 << 2 << 3 << -4; +CHECK_EQ(-4, value.get_value()); +``` +Since Miner<> use std::numeric_limits::max() as the identity, it cannot be applied to generic types unless you specialized std::numeric_limits<> (and overloaded operator<). + +# bvar::IntRecorder + +used for mean value +```c++ +// For calculating average of numbers. +// Example: +// IntRecorder latency; +// latency << 1 << 3 << 5; +// CHECK_EQ(3, latency.average()); +class IntRecorder : public Variable; +``` +# bvar::LatencyRecoder + +A counter used for latency and qps. We can get latency / max_latency / qps / count as long as the latency data filled in. Time window is the last param, omit by `bvar_dump_interval` + +LatencyRecoder is a compound variable, consisting of several bvar. +```c++ +LatencyRecorder write_latency("table2_my_table_write"); // produces 4 variables: + // table2_my_table_write_latency + // table2_my_table_write_max_latency + // table2_my_table_write_qps + // table2_my_table_write_count +// In your write function +write_latency << the_latency_of_write; + + ``` + +# bvar::Window + +Get data within a time window. Window cannot exist alone, it relies on a counter. Window will auto-update, we don't have to send data to it. For the sake of performance, the data comes from every-second sampling over the original counter, in the worst case, Window has one-second latency +```c++ +// Get data within a time window. +// The time unit is 1 second fixed. +// Window relies on other bvar which should be constructed before this window and destructs after this window. +// R must: +// - have get_sampler() (not require thread-safe) +// - defined value_type and sampler_type +template +class Window : public Variable; +``` + + +# bvar::PerSecond + +Get the mean value over the last amount of time. Its almost the same as Window, except for the value will be divided by the time window +```c++ +bvar::Adder sum; + +// sum_per_second.get_value()is summing every-second value over the last 60 seconds, if we omit the time window, it's set to 'bvar_dump_interval' by default +bvar::PerSecond > sum_per_second(&sum, 60); +``` +**PerSecond does not always make sense** + +There is no Maxer in the above code, since the max value over a period of time divided by the time window is meaningless. +```c++ +bvar::Maxer max_value; + +// WRONG!max value divided by time window is pointless +bvar::PerSecond > max_value_per_second_wrong(&max_value); + +// CORRECT. It's the right way to set the time window to 1s so that we can get the max value for every second +bvar::Window > max_value_per_second(&max_value, 1); +``` + + +## Difference between Window and PerSecond + +Suppose we want the memory change since last minute, if we use Window<>, the meaning for the returning value is "the memory increase over the last minute is 18M" if we use PerSecond<>, the meaning for return value will be "the average memory increase per second over the last minute is 0.3M”. + +Pros of Window is preciseness, it fits in some small-number cases, like “the number of error produced over last minute“, if we use PerSecond, we might get something like "the average error rate per second over the last minute is 0.0167", which is very unclear as opposed to "one error produced over last minute". Some other non-time-related variables also fit in Window<>, such like calculating the CPU ratio over the last minute is using a Adder by summing CPU time and real time, then we use Window<> on top of the Adder to get the last-mintue CPU time and real time, dividing these two value then we get the CPU ratio for the last minute, which is not time-related. It will get wrong when use PerSeond + + + +# bvar::Status + +Record and display one value, has additional set_value() function +```c++ +// Display a rarely or periodically updated value. +// Usage: +// bvar::Status foo_count1(17); +// foo_count1.expose("my_value"); +// +// bvar::Status foo_count2; +// foo_count2.set_value(17); +// +// bvar::Status foo_count3("my_value", 17); +// +// Notice that Tp needs to be std::string or acceptable by boost::atomic. +template +class Status : public Variable; +``` + + +# bvar::PassiveStatus + +Display the value when needed. In some cases, we are not able to actively set_value nor set_value in a certain time interval. We'd better print it out when needed, user can pass in the print-out callback function to achieve this. +```c++ +// Display a updated-by-need value. This is done by passing in an user callback +// which is called to produce the value. +// Example: +// int print_number(void* arg) { +// ... +// return 5; +// } +// +// // number1 : 5 +// bvar::PassiveStatus status1("number1", print_number, arg); +// +// // foo_number2 : 5 +// bvar::PassiveStatus status2(typeid(Foo), "number2", print_number, arg); +template +class PassiveStatus : public Variable; + +even though it looks simple, PassiveStatus is one of the most useful bvar, since most of the statistic values have already existed, we don't have to store it again, just fetch the data according to our need. Declare a bvar which can display user process name as below: + +static void get_username(std::ostream& os, void*) { + char buf[32]; + if (getlogin_r(buf, sizeof(buf)) == 0) { + buf[sizeof(buf)-1] = '\0'; + os << buf; + } else { + os << "unknown"; + } +} +PassiveStatus g_username("process_username", get_username, NULL); +``` + + +# bvar::GFlag + +Expose important gflags as bvar so that they're monitored (in noah). +```c++ +DEFINE_int32(my_flag_that_matters, 8, "..."); + +// Expose the gflag as *same-named* bvar so that it's monitored (in noah). +static bvar::GFlag s_gflag_my_flag_that_matters("my_flag_that_matters"); +// ^ +// the gflag name + +// Expose the gflag as a bvar named "foo_bar_my_flag_that_matters". +static bvar::GFlag s_gflag_my_flag_that_matters_with_prefix("foo_bar", "my_flag_that_matters"); +``` + +# babylon counter bvar + +For details on the principles and performance, see [Use concurrent counter optimize bvar](https://github.com/baidu/babylon/tree/main/example/use-counter-with-bvar). + +Currently, this feature is only supported by the Bazel compilation method: `--define with_babylon_counter=true`. The required Babylon version is 1.4.4 or higher. Once enabled, you can use the higher-performance bvar implementation based on the babylon counter without modifying your code. \ No newline at end of file diff --git a/docs/en/case_apicontrol.md b/docs/en/case_apicontrol.md new file mode 100644 index 0000000..3a9fcf9 --- /dev/null +++ b/docs/en/case_apicontrol.md @@ -0,0 +1,7 @@ +# case apicontrol + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/case_apicontrol.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/case_baidu_dsp.md b/docs/en/case_baidu_dsp.md new file mode 100644 index 0000000..1b47fd3 --- /dev/null +++ b/docs/en/case_baidu_dsp.md @@ -0,0 +1,7 @@ +# case uaidu dsp + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/case_baidu_dsp.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/case_elf.md b/docs/en/case_elf.md new file mode 100644 index 0000000..5f710a7 --- /dev/null +++ b/docs/en/case_elf.md @@ -0,0 +1,7 @@ +# case elf + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/case_elf.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/case_ubrpc.md b/docs/en/case_ubrpc.md new file mode 100644 index 0000000..bd1705d --- /dev/null +++ b/docs/en/case_ubrpc.md @@ -0,0 +1,7 @@ +# case uurpc + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/case_ubrpc.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/circuit_breaker.md b/docs/en/circuit_breaker.md new file mode 100644 index 0000000..a541f11 --- /dev/null +++ b/docs/en/circuit_breaker.md @@ -0,0 +1,7 @@ +# circuit ureaker + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/circuit_breaker.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/client.md b/docs/en/client.md new file mode 100644 index 0000000..8da0c6b --- /dev/null +++ b/docs/en/client.md @@ -0,0 +1,929 @@ +[中文版](../cn/client.md) + +# Example + +[client-side code](https://github.com/apache/brpc/blob/master/example/echo_c++/client.cpp) of echo. + +# Quick facts + +- Channel.Init() is not thread-safe. +- Channel.CallMethod() is thread-safe and a Channel can be used by multiple threads simultaneously. +- Channel can be put on stack. +- Channel can be destructed just after sending asynchronous request. +- No class named brpc::Client. + +# Channel +Client-side of RPC sends requests. It's called [Channel](https://github.com/apache/brpc/blob/master/src/brpc/channel.h) rather than "Client" in brpc. A channel represents a communication line to one server or multiple servers, which can be used for calling services. + +A Channel can be **shared by all threads** in the process. Yon don't need to create separate Channels for each thread, and you don't need to synchronize Channel.CallMethod with lock. However creation and destroying of Channel is **not** thread-safe, make sure the channel is initialized and destroyed only by one thread. + +Some RPC implementations have so-called "ClientManager", including configurations and resource management at the client-side, which is not needed by brpc. "thread-num", "connection-type" such parameters are either in brpc::ChannelOptions or global gflags. Advantages of doing so: + +1. Convenience. You don't have to pass a "ClientManager" when the Channel is created, and you don't have to store the "ClientManager". Otherwise code has to pass "ClientManager" layer by layer, which is troublesome. gflags makes configurations of global behaviors easier. +2. Share resources. For example, servers and channels in brpc share background workers (of bthread). +3. Better management of Lifetime. Destructing a "ClientManager" is very error-prone, which is managed by brpc right now. + +Like most classes, Channel must be **Init()**-ed before usage. Parameters take default values when `options` is NULL. If you want non-default values, code as follows: +```c++ +brpc::ChannelOptions options; // including default values +options.xxx = yyy; +... +channel.Init(..., &options); +``` +Note that Channel neither modifies `options` nor accesses `options` after completion of Init(), thus options can be put on stack safely as in above code. Channel.options() gets options being used by the Channel. + +Init() can connect one server or a cluster(multiple servers). + +# Connect to a server + +```c++ +// Take default values when options is NULL. +int Init(EndPoint server_addr_and_port, const ChannelOptions* options); +int Init(const char* server_addr_and_port, const ChannelOptions* options); +int Init(const char* server_addr, int port, const ChannelOptions* options); +``` +The server connected by these Init() has fixed address generally. The creation does not need NamingService or LoadBalancer, being relatively light-weight. The address could be a hostname, but don't frequently create Channels connecting to a hostname, which requires a DNS lookup taking at most 10 seconds. (default timeout of DNS lookup). Reuse them. + +Valid "server_addr_and_port": +- 127.0.0.1:80 +- www.foo.com:8765 +- localhost:9000 + +Invalid "server_addr_and_port": +- 127.0.0.1:90000 # too large port +- 10.39.2.300:8000 # invalid IP + +# Connect to a cluster + +```c++ +int Init(const char* naming_service_url, + const char* load_balancer_name, + const ChannelOptions* options); +``` +Channels created by above Init() get server list from the NamingService specified by `naming_service_url` periodically or driven-by-events, and send request to one server chosen from the list according to the algorithm specified by `load_balancer_name` . + +You **should not** create such channels ad-hocly each time before a RPC, because creation and destroying of such channels relate to many resources, say NamingService needs to be accessed once at creation otherwise server candidates are unknown. On the other hand, channels are able to be shared by multiple threads safely and has no need to be created frequently. + +If `load_balancer_name` is NULL or empty, this Init() is just the one for connecting single server and `naming_service_url` should be "ip:port" or "host:port" of the server. Thus you can unify initialization of all channels with this Init(). For example, you can put values of `naming_service_url` and `load_balancer_name` in configuration file, and set `load_balancer_name` to empty for single server and a valid algorithm for a cluster. + +## Naming Service + +Naming service maps a name to a modifiable list of servers. It's positioned as follows at client-side: + +![img](../images/ns.png) + +With the help of naming service, the client remembers a name instead of every concrete server. When the servers are added or removed, only mapping in the naming service is changed, rather than telling every client that may access the cluster. This process is called "decoupling up and downstreams". Back to implementation details, the client does remember every server and will access NamingService periodically or be pushed with latest server list. The impl. has minimal impact on RPC latencies and very small pressure on the system providing naming service. + +General form of `naming_service_url` is "**protocol://service_name**". + +### bns://\ + +BNS is the most common naming service inside Baidu. In "bns://rdev.matrix.all", "bns" is protocol and "rdev.matrix.all" is service-name. A related gflag is -ns_access_interval: ![img](../images/ns_access_interval.png) + +If the list in BNS is non-empty, but Channel says "no servers", the status bit of the machine in BNS is probably non-zero, which means the machine is unavailable and as a correspondence not added as server candidates of the Channel. Status bits can be checked by: + +`get_instance_by_service [bns_node_name] -s` + +### file://\ + +Servers are put in the file specified by `path`. For example, in "file://conf/machine_list", "conf/machine_list" is the file: + * in which each line is address of a server. + * contents after \# are comments and ignored. + * non-comment contents after addresses are tags, which are separated from addresses by one or more spaces, same address + different tags are treated as different instances. + * brpc reloads the file when it's updated. +``` +# This line is ignored +10.24.234.17:8080 tag1 # a comment +10.24.234.17:8090 tag2 # an instance different from the instance on last line +10.24.234.18:8080 +10.24.234.19:8080 +``` + +Pros: easy to modify, convenient for unittests. + +Cons: need to update every client when the list changes, not suitable for online deployment. + +### list://\,\... + +Servers are directly written after list://, separated by comma. For example: "list://db-bce-81-3-186.db01:7000,m1-bce-44-67-72.m1:7000,cp01-rd-cos-006.cp01:7000" has 3 addresses. + +Tags can be appended to addresses, separated with one or more spaces. Same address + different tags are treated as different instances. + +Pros: directly configurable in CLI, convenient for unittests. + +Cons: cannot be updated at runtime, not suitable for online deployment at all. + +### http://\ + +Connect all servers under the domain, for example: http://www.baidu.com:80. + +Note: although Init() for connecting single server(2 parameters) accepts hostname as well, it only connects one server under the domain. + +Pros: Versatility of DNS, useable both in private or public network. + +Cons: limited by transmission formats of DNS, unable to implement notification mechanisms. + +### https://\ + +Similar with "http" prefix besides that the connections will be encrypted with SSL. + +### consul://\ + +Get a list of servers with the specified service-name through consul. The default address of consul is localhost:8500, which can be modified by setting -consul\_agent\_addr in gflags. The connection timeout of consul is 200ms by default, which can be modified by -consul\_connect\_timeout\_ms. + +By default, [stale](https://www.consul.io/api/index.html#consistency-modes) and passing(only servers with passing in statuses are returned) are added to [parameters of the consul request]((https://www.consul.io/api/health.html#parameters-2)), which can be modified by -consul\_url\_parameter in gflags. + +Except the first request to consul, the follow-up requests use the [long polling](https://www.consul.io/api/index.html#blocking-queries) feature. That is, the consul responds only when the server list is updated or the request times out. The timeout defaults to 60 seconds, which can be modified by -consul\_blocking\_query\_wait\_secs. + +If the server list returned by the consul does not follow [response format](https://www.consul.io/api/health.html#sample-response-2), or all servers in the list are filtered because the key fields such as the address and port are missing or cannot be parsed, the server list will not be updated and the consul service will be revisited after a period of time(default 500ms, can be modified by -consul\_retry\_interval\_ms). + +If consul is not accessible, the naming service can be automatically downgraded to file naming service. This feature is turned off by default and can be turned on by setting -consul\_enable\_degrade\_to\_file\_naming\_service. After downgrading, in the directory specified by -consul\_file\_naming\_service\_dir, the file whose name is the service-name will be used. This file can be generated by the consul-template, which holds the latest server list before the consul is unavailable. The consul naming service is automatically restored when consul is restored. + + +### nacos://\ + +NacosNamingService gets a list of servers from nacos periodically by [Open-Api](https://nacos.io/en-us/docs/open-api.html). +NacosNamingService supports [simple authentication](https://nacos.io/en-us/docs/auth.html). + +`` is a http uri query,For more detail, refer to `/nacos/v1/ns/instance/list` api document. +NOTE: `` must be url-encoded. +``` +nacos://serviceName=test&groupName=g&namespaceId=n&clusters=c&healthyOnly=true +``` + +The server list is cached for `cacheMillis` milliseconds as specified in the response of `/nacos/v1/ns/instance/list` api. +NOTE: The value of server weight must be an integer. + +| GFlags | Description | Default value | +| ---------------------------------- | ------------------------------------ | ---------------------------- | +| nacos_address | nacos http url | "" | +| nacos_service_discovery_path | path for discovery | "/nacos/v1/ns/instance/list" | +| nacos_service_auth_path | path for login | "/nacos/v1/auth/login" | +| nacos_service_timeout_ms | timeout for connecting to nacos(ms) | 200 | +| nacos_username | url-encoded username | "" | +| nacos_password | url-encoded password | "" | +| nacos_load_balancer | load balancer for nacos clusters | "rr" | + + +### More naming services +User can extend to more naming services by implementing brpc::NamingService, check [this link](https://github.com/apache/brpc/blob/master/docs/cn/load_balancing.md#%E5%91%BD%E5%90%8D%E6%9C%8D%E5%8A%A1) for details. + +### The tag in naming service +Every address can be attached with a tag. The common implementation is that if there're spaces after the address, the content after the spaces is the tag. +Same address with different tag are treated as different instances which are interacted with separate connections. Users can use this feature to establish connections with remote servers in a more flexible way. +If you need weighted round-robin, you should consider using [wrr algorithm](#wrr) first rather than emulate "a coarse-grained version" with tags. + +### VIP related issues +VIP is often the public IP of layer-4 load balancer, which proxies traffic to RS behide. When a client connects to the VIP, a connection is established to a chosen RS. When the client connection is broken, the connection to the RS is reset as well. + +If one client establishes only one connection to the VIP("single" connection type in brpc), all traffic from the client lands on one RS. If number of clients are large enough, each RS should gets many connections and roughly balanced, at least from the cluster perspective. However, if clients are not large enough or workload from clients are very different, some RS may be overloaded. Another issue is that when multiple VIP are listed together, the traffic to them may not be proportional to the number of RS behide them. + +One solution to these issues is to use "pooled" connection type, so that one client may create multiple connections to one VIP (roughly the max concurrency recently) to make traffic land on different RS. If more than one VIP are present, consider using [wrr load balancing](#wrr) to assign weights to different VIP, or add different tags to VIP to form more instances. + +If higher performance is demanded, or number of connections is limited (in a large cluster), consider using single connection and attach same VIP with different tags to create different connections. Comparing to pooled connections, number of connections and overhead of syscalls are often lower, but if tags are not enough, RS hotspots may still present. + +### Naming Service Filter + +Users can filter servers got from the NamingService before pushing to LoadBalancer. + +![img](../images/ns_filter.jpg) + +Interface of the filter: +```c++ +// naming_service_filter.h +class NamingServiceFilter { +public: + // Return true to take this `server' as a candidate to issue RPC + // Return false to filter it out + virtual bool Accept(const ServerNode& server) const = 0; +}; + +// naming_service.h +struct ServerNode { + butil::EndPoint addr; + std::string tag; +}; +``` +The most common usage is filtering by server tags. + +Customized filter is set to ChannelOptions to take effects. NULL by default means not filter. + +```c++ +class MyNamingServiceFilter : public brpc::NamingServiceFilter { +public: + bool Accept(const brpc::ServerNode& server) const { + return server.tag == "main"; + } +}; + +int main() { + ... + MyNamingServiceFilter my_filter; + ... + brpc::ChannelOptions options; + options.ns_filter = &my_filter; + ... +} +``` + +## Load Balancer + +When there're more than one server to access, we need to divide the traffic. The process is called load balancing, which is positioned as follows at client-side. + +![img](../images/lb.png) + +The ideal algorithm is to make every request being processed in-time, and crash of any server makes minimal impact. However clients are not able to know delays or congestions happened at servers in realtime, and load balancing algorithms should be light-weight generally, users need to choose proper algorithms for their use cases. Algorithms provided by brpc (specified by `load_balancer_name`): + +### rr + +which is round robin. Always choose next server inside the list, next of the last server is the first one. No other settings. For example there're 3 servers: a,b,c, brpc will send requests to a, b, c, a, b, c, … and so on. Note that presumption of using this algorithm is the machine specs, network latencies, server loads are similar. + +### wrr + +which is weighted round robin. Choose the next server according to the configured weight. The chances a server is selected is consistent with its weight, and the algorithm can make each server selection scattered. + +The instance tag must be an int32 number representing the weight, eg. tag="50". + +### random + +Randomly choose one server from the list, no other settings. Similarly with round robin, the algorithm assumes that servers to access are similar. + +### wr + +which is weighted random. Choose the next server according to the configured weight. The chances a server is selected is consistent with its weight. + +Requirements of instance tag is the same as wrr. + +### la + +which is locality-aware. Perfer servers with lower latencies, until the latency is higher than others, no other settings. Check out [Locality-aware load balancing](lalb.md) for more details. + +### p2c + +which is power-of-two-choices with peak-EWMA latency scoring. Each selection samples two random servers and routes to the one with the lower `latency * (inflight + 1) / weight` score, where the latency is a peak-sensitive moving average: an upward spike takes effect immediately while recovery decays over `tau_ms`(default 10s). A slow or failing server is shed within one observation and selection cost is O(1) regardless of cluster size. Weight comes from the instance tag as in wrr(default 1). Optional parameters: `p2c:choices=4`(compare 4 sampled servers instead of 2, useful when many servers degrade at once), `p2c:tau_ms=5000`. + +### c_murmurhash or c_md5 + +which is consistent hashing. Adding or removing servers does not make destinations of requests change as dramatically as in simple hashing. It's especially suitable for caching services. + +Need to set Controller.set_request_code() before RPC otherwise the RPC will fail. request_code is often a 32-bit hash code of "key part" of the request, and the hashing algorithm does not need to be same with the one used by load balancer. Say `c_murmurhash` can use md5 to compute request_code of the request as well. + +[src/brpc/policy/hasher.h](https://github.com/apache/brpc/blob/master/src/brpc/policy/hasher.h) includes common hash functions. If `std::string key` stands for key part of the request, controller.set_request_code(brpc::policy::MurmurHash32(key.data(), key.size())) sets request_code correctly. + +Do distinguish "key" and "attributes" of the request. Don't compute request_code by full content of the request just for quick. Minor change in attributes may result in totally different hash code and change destination dramatically. Another cause is padding, for example: `struct Foo { int32_t a; int64_t b; }` has a 4-byte undefined gap between `a` and `b` on 64-bit machines, result of `hash(&foo, sizeof(foo))` is undefined. Fields need to be packed or serialized before hashing. + +Check out [Consistent Hashing](consistent_hashing.md) for more details. + +Other kind of lb does not need to set Controller.set_request_code(). If request code is set, it will not be used by lb. For example, lb=rr, and call Controller.set_request_code(), even if request_code is the same for every request, lb will balance the requests using the rr policy. + +### Client-side throttling for recovery from cluster downtime + +Cluster downtime refers to the state in which all servers in the cluster are unavailable. Due to the health check mechanism, when the cluster returns to normal, server will go online one by one. When a server is online, all traffic will be sent to it, which may cause the service to be overloaded again. If circuit breaker is enabled, server may be offline again before the other servers go online, and the cluster can never be recovered. As a solution, brpc provides a client-side throttling mechanism for recovery after cluster downtime. When no server is available in the cluster, the cluster enters recovery state. Assuming that the minimum number of servers that can serve all requests is min_working_instances, current number of servers available in the cluster is q, then in recovery state, the probability of client accepting the request is q/min_working_instances, otherwise it is discarded. If q remains unchanged for a period of time(hold_seconds), the traffic is resent to all available servers and leaves recovery state. Whether the request is rejected in recovery state is indicated by whether controller.ErrorCode() is equal to brpc::ERJECT, and the rejected request will not be retried by the framework. + +This recovery mechanism requires the capabilities of downstream servers to be similar, so it is currently only valid for rr and random. The way to enable it is to add the values of min_working_instances and hold_seconds parameters after *load_balancer_name*, for example: +```c++ +channel.Init("http://...", "random:min_working_instances=6 hold_seconds=10", &options); +``` + +## Health checking + +Servers whose connections are lost are isolated temporarily to prevent them from being selected by LoadBalancer. brpc connects isolated servers periodically to test if they're healthy again. The interval is controlled by gflag -health_check_interval: + +| Name | Value | Description | Defined At | +| ------------------------- | ----- | ---------------------------------------- | ----------------------- | +| health_check_interval (R) | 3 | seconds between consecutive health-checkings | src/brpc/socket_map.cpp | + +Once a server is connected, it resumes as a server candidate inside LoadBalancer. If a server is removed from NamingService during health-checking, brpc removes it from health-checking as well. + +# Launch RPC + +Generally, we don't use Channel.CallMethod directly, instead we call XXX_Stub generated by protobuf, which feels more like a "method call". The stub has few member fields, being suitable(and recommended) to be put on stack instead of new(). Surely the stub can be saved and re-used as well. Channel.CallMethod and stub are both **thread-safe** and accessible by multiple threads simultaneously. For example: +```c++ +XXX_Stub stub(&channel); +stub.some_method(controller, request, response, done); +``` +Or even: +```c++ +XXX_Stub(&channel).some_method(controller, request, response, done); +``` +A exception is http/h2 client, which is not related to protobuf much. Call CallMethod directly to make a http call, setting all parameters to NULL except for `Controller` and `done`, check [Access http/h2](http_client.md) for details. + +## Synchronous call + +CallMethod blocks until response from server is received or error occurred (including timedout). + +response/controller in synchronous call will not be used by brpc again after CallMethod, they can be put on stack safely. Note: if request/response has many fields and being large on size, they'd better be allocated on heap. +```c++ +MyRequest request; +MyResponse response; +brpc::Controller cntl; +XXX_Stub stub(&channel); + +request.set_foo(...); +cntl.set_timeout_ms(...); +stub.some_method(&cntl, &request, &response, NULL); +if (cntl.Failed()) { + // RPC failed. fields in response are undefined, don't use. +} else { + // RPC succeeded, response has what we want. +} +``` + +> WARNING: Do NOT use synchronous call when you are holding a pthread lock! Otherwise it is easy to cause deadlock. +> +> Solution (choose one of the two): +> 1. Replace pthread lock with bthread lock (bthread_mutex_t) +> 1. Release the lock before CallMethod + +## Asynchronous call + +Pass a callback `done` to CallMethod, which resumes after sending request, rather than completion of RPC. When the response from server is received or error occurred(including timedout), done->Run() is called. Post-processing code of the RPC should be put in done->Run() instead of after CallMethod. + +Because end of CallMethod does not mean completion of RPC, response/controller may still be used by brpc or done->Run(). Generally they should be allocated on heap and deleted in done->Run(). If they're deleted too early, done->Run() may access invalid memory. + +You can new these objects individually and create done by [NewCallback](#use-newcallback), or make response/controller be member of done and [new them together](#Inherit-google::protobuf::Closure). Former one is recommended. + +Request can be destroyed immediately after asynchronous CallMethod. (SelectiveChannel is an exception, in the case of SelectiveChannel, the request object must be released after rpc finish) + +Channel can be destroyed immediately after asynchronous CallMethod. + +Note that "immediately" means destruction of Request/Channel can happen **after** CallMethod, not during CallMethod. Deleting a Channel just being used by another thread results in undefined behavior (crash at best). + +### Use NewCallback +```c++ +static void OnRPCDone(MyResponse* response, brpc::Controller* cntl) { + // unique_ptr helps us to delete response/cntl automatically. unique_ptr in gcc 3.4 is an emulated version. + std::unique_ptr response_guard(response); + std::unique_ptr cntl_guard(cntl); + if (cntl->Failed()) { + // RPC failed. fields in response are undefined, don't use. + } else { + // RPC succeeded, response has what we want. Continue the post-processing. + } + // Closure created by NewCallback deletes itself at the end of Run. +} + +MyResponse* response = new MyResponse; +brpc::Controller* cntl = new brpc::Controller; +MyService_Stub stub(&channel); + +MyRequest request; // you don't have to new request, even in an asynchronous call. +request.set_foo(...); +cntl->set_timeout_ms(...); +stub.some_method(cntl, &request, response, brpc::NewCallback(OnRPCDone, response, cntl)); +``` +Since protobuf 3 changes NewCallback to private, brpc puts NewCallback in [src/brpc/callback.h](https://github.com/apache/brpc/blob/master/src/brpc/callback.h) after r32035 (and adds more overloads). If your program has compilation issues with NewCallback, replace google::protobuf::NewCallback with brpc::NewCallback. + +### Inherit google::protobuf::Closure + +Drawback of using NewCallback is that you have to allocate memory on heap at least 3 times: response, controller, done. If profiler shows that the memory allocation is a hotspot, you can consider inheriting Closure by your own, and enclose response/controller as member fields. Doing so combines 3 new into one, but the code will be worse to read. Don't do this if memory allocation is not an issue. +```c++ +class OnRPCDone: public google::protobuf::Closure { +public: + void Run() { + // unique_ptr helps us to delete response/cntl automatically. unique_ptr in gcc 3.4 is an emulated version. + std::unique_ptr self_guard(this); + + if (cntl->Failed()) { + // RPC failed. fields in response are undefined, don't use. + } else { + // RPC succeeded, response has what we want. Continue the post-processing. + } + } + + MyResponse response; + brpc::Controller cntl; +} + +OnRPCDone* done = new OnRPCDone; +MyService_Stub stub(&channel); + +MyRequest request; // you don't have to new request, even in an asynchronous call. +request.set_foo(...); +done->cntl.set_timeout_ms(...); +stub.some_method(&done->cntl, &request, &done->response, done); +``` + +### What will happen when the callback is very complicated? + +No special impact, the callback will run in separate bthread, without blocking other sessions. You can do all sorts of things in the callback. + +### Does the callback run in the same thread that CallMethod runs? + +The callback runs in a different bthread, even the RPC fails just after entering CallMethod. This avoids deadlock when the RPC is ongoing inside a lock(not recommended). + +## Wait for completion of RPC +NOTE: [ParallelChannel](combo_channel.md#parallelchannel) is probably more convenient to launch multiple RPCs in parallel. + +Following code starts 2 asynchronous RPC and waits them to complete. +```c++ +const brpc::CallId cid1 = controller1->call_id(); +const brpc::CallId cid2 = controller2->call_id(); +... +stub.method1(controller1, request1, response1, done1); +stub.method2(controller2, request2, response2, done2); +... +brpc::Join(cid1); +brpc::Join(cid2); +``` +Call `Controller.call_id()` to get an id **before launching RPC**, join the id after the RPC. + +Join() blocks until completion of RPC **and end of done->Run()**, properties of Join: + +- If the RPC is complete, Join() returns immediately. +- Multiple threads can Join() one id, all of them will be woken up. +- Synchronous RPC can be Join()-ed in another thread, although we rarely do this. + +Join() was called JoinResponse() before, if you meet deprecated issues during compilation, rename to Join(). + +Calling `Join(controller->call_id())` after completion of RPC is **wrong**, do save call_id before RPC, otherwise the controller may be deleted by done at any time. The Join in following code is **wrong**. + +```c++ +static void on_rpc_done(Controller* controller, MyResponse* response) { + ... Handle response ... + delete controller; + delete response; +} + +Controller* controller1 = new Controller; +Controller* controller2 = new Controller; +MyResponse* response1 = new MyResponse; +MyResponse* response2 = new MyResponse; +... +stub.method1(controller1, &request1, response1, google::protobuf::NewCallback(on_rpc_done, controller1, response1)); +stub.method2(controller2, &request2, response2, google::protobuf::NewCallback(on_rpc_done, controller2, response2)); +... +brpc::Join(controller1->call_id()); // WRONG, controller1 may be deleted by on_rpc_done +brpc::Join(controller2->call_id()); // WRONG, controller2 may be deleted by on_rpc_done +``` + +## Semi-synchronous call + +Join can be used for implementing "Semi-synchronous" call: blocks until multiple asynchronous calls to complete. Since the callsite blocks until completion of all RPC, controller/response can be put on stack safely. +```c++ +brpc::Controller cntl1; +brpc::Controller cntl2; +MyResponse response1; +MyResponse response2; +... +stub1.method1(&cntl1, &request1, &response1, brpc::DoNothing()); +stub2.method2(&cntl2, &request2, &response2, brpc::DoNothing()); +... +brpc::Join(cntl1.call_id()); +brpc::Join(cntl2.call_id()); +``` +brpc::DoNothing() gets a closure doing nothing, specifically for semi-synchronous calls. Its lifetime is managed by brpc. + +Note that in above example, we access `controller.call_id()` after completion of RPC, which is safe right here, because DoNothing does not delete controller as in `on_rpc_done` in previous example. + +## Cancel RPC + +`brpc::StartCancel(call_id)` cancels corresponding RPC, call_id must be got from Controller.call_id() **before launching RPC**, race conditions may occur at any other time. + +NOTE: it is `brpc::StartCancel(call_id)`, not `controller->StartCancel()`, which is forbidden and useless. The latter one is provided by protobuf by default and has serious race conditions on lifetime of controller. + +As the name implies, RPC may not complete yet after calling StartCancel, you should not touch any field in Controller or delete any associated resources, they should be handled inside done->Run(). If you have to wait for completion of RPC in-place(not recommended), call Join(call_id). + +Facts about StartCancel: + +- call_id can be cancelled before CallMethod, the RPC will end immediately(and done will be called). +- call_id can be cancelled in another thread. +- Cancel an already-cancelled call_id has no effect. Inference: One call_id can be cancelled by multiple threads simultaneously, but only one of them takes effect. +- Cancel here is a client-only feature, **the server-side may not cancel the operation necessarily**, server cancelation is a separate feature. + +## Get server-side address and port + +remote_side() tells where request was sent to, the return type is [butil::EndPoint](https://github.com/apache/brpc/blob/master/src/butil/endpoint.h), which includes an ipv4 address and port. Calling this method before completion of RPC is undefined. + +How to print: +```c++ +LOG(INFO) << "remote_side=" << cntl->remote_side(); +printf("remote_side=%s\n", butil::endpoint2str(cntl->remote_side()).c_str()); +``` +## Get client-side address and port + +local_side() gets address and port of the client-side sending RPC after r31384 + +How to print: +```c++ +LOG(INFO) << "local_side=" << cntl->local_side(); +printf("local_side=%s\n", butil::endpoint2str(cntl->local_side()).c_str()); +``` +## Should brpc::Controller be reused? + +Not necessary to reuse deliberately. + +Controller has miscellaneous fields, some of them are buffers that can be re-used by calling Reset(). + +In most use cases, constructing a Controller(snippet1) and re-using a Controller(snippet2) perform similarily. +```c++ +// snippet1 +for (int i = 0; i < n; ++i) { + brpc::Controller controller; + ... + stub.CallSomething(..., &controller); +} + +// snippet2 +brpc::Controller controller; +for (int i = 0; i < n; ++i) { + controller.Reset(); + ... + stub.CallSomething(..., &controller); +} +``` +If the Controller in snippet1 is new-ed on heap, snippet1 has extra cost of "heap allcation" and may be a little slower in some cases. + +# Settings + +Client-side settings has 3 parts: + +- brpc::ChannelOptions: defined in [src/brpc/channel.h](https://github.com/apache/brpc/blob/master/src/brpc/channel.h), for initializing Channel, becoming immutable once the initialization is done. +- brpc::Controller: defined in [src/brpc/controller.h](https://github.com/apache/brpc/blob/master/src/brpc/controller.h), for overriding fields in brpc::ChannelOptions for some RPC according to contexts. +- global gflags: for tuning global behaviors, being unchanged generally. Read comments in [/flags](flags.md) before setting. + +Controller contains data and options that request may not have. server and client share the same Controller class, but they may set different fields. Read comments in Controller carefully before using. + +A Controller corresponds to a RPC. A Controller can be re-used by another RPC after Reset(), but a Controller can't be used by multiple RPC simultaneously, no matter the RPCs are started from one thread or not. + +Properties of Controller: +1. A Controller can only have one user. Without explicit statement, methods in Controller are **not** thread-safe by default. +2. Due to the fact that Controller is not shared generally, there's no need to manage Controller by shared_ptr. If you do, something might goes wrong. +3. Controller is constructed before RPC and destructed after RPC, some common patterns: + - Put Controller on stack before synchronous RPC, be destructed when out of scope. Note that Controller of asynchronous RPC **must not** be put on stack, otherwise the RPC may still run when the Controller is being destructed and result in undefined behavior. + - new Controller before asynchronous RPC, delete in done. + +## Number of worker pthreads + +There's **no** independent thread pool for client in brpc. All Channels and Servers share the same backing threads via [bthread](bthread.md). Setting number of worker pthreads in Server works for Client as well if Server is in used. Or just specify the [gflag](flags.md) [-bthread_concurrency](brpc.baidu.com:8765/flags/bthread_concurrency) to set the global number of worker pthreads. + +## Timeout + +**ChannelOptions.timeout_ms** is timeout in milliseconds for all RPCs via the Channel, Controller.set_timeout_ms() overrides value for one RPC. Default value is 1 second, Maximum value is 2^31 (about 24 days), -1 means wait indefinitely for response or connection error. + +**ChannelOptions.connect_timeout_ms** is the timeout in milliseconds for establishing connections of RPCs over the Channel, and -1 means no deadline. This value is limited to be not greater than timeout_ms. Note that this connection timeout is different from the one in TCP, generally this one is smaller. + +NOTE1: timeout_ms in brpc is **deadline**, which means once it's reached, the RPC ends without more retries. As a comparison, other implementations may have session timeouts and deadline timeouts. Do distinguish them before porting to brpc. + +NOTE2: error code of RPC timeout is **ERPCTIMEDOUT (1008) **, ETIMEDOUT is connection timeout and retriable. + +## Retry + +ChannelOptions.max_retry is maximum retrying count for all RPC via the channel, Default value is 3, 0 means no retries. Controller.set_max_retry() overrides value for one RPC. + +Controller.retried_count() returns number of retries. + +Controller.has_backup_request() tells if backup_request was sent. + +**Servers tried before are not retried by best efforts** + +Conditions for retrying (AND relations): +- Broken connection. +- Timeout is not reached. +- Has retrying quota. Controller.set_max_retry(0) or ChannelOptions.max_retry = 0 disables retries. +- The retry makes sense. If the RPC fails due to request(EREQUEST), no retry will be done because server is very likely to reject the request again, retrying makes no sense here. + +### Broken connection + +If the server does not respond and connection is good, retry is not triggered. If you need to send another request after some timeout, use backup request. + +How it works: If response does not return within the timeout specified by backup_request_ms, send another request, take whatever the first returned. New request will be sent to a different server that never tried before by best efforts. NOTE: If backup_request_ms is greater than timeout_ms, backup request will never be sent. backup request consumes one retry. backup request does not imply a server-side cancellation. + +ChannelOptions.backup_request_ms affects all RPC via the Channel, unit is milliseconds, Default value is -1(disabled), Controller.set_backup_request_ms() overrides value for one RPC. + +### Timeout is not reached + +RPC will be ended soon after the timeout. + +### Has retrying quota + +Controller.set_max_retry(0) or ChannelOptions.max_retry = 0 disables retries. + +### The retry makes sense + +If the RPC fails due to request(EREQUEST), no retry will be done because server is very likely to reject the request again, retrying makes no sense here. + +Users can inherit [brpc::RetryPolicy](https://github.com/apache/brpc/blob/master/src/brpc/retry_policy.h) to customize conditions of retrying. For example brpc does not retry for http/h2 related errors by default. If you want to retry for HTTP_STATUS_FORBIDDEN(403) in your app, you can do as follows: + +```c++ +#include + +class MyRetryPolicy : public brpc::RetryPolicy { +public: + bool DoRetry(const brpc::Controller* cntl) const { + if (cntl->ErrorCode() == brpc::EHTTP && // http/h2 error + cntl->http_response().status_code() == brpc::HTTP_STATUS_FORBIDDEN) { + return true; + } + // Leave other cases to brpc. + return brpc::DefaultRetryPolicy()->DoRetry(cntl); + } +}; +... + +// Assign the instance to ChannelOptions.retry_policy. +// NOTE: retry_policy must be kept valid during lifetime of Channel, and Channel does not retry_policy, so in most cases RetryPolicy should be created by singleton.. +brpc::ChannelOptions options; +static MyRetryPolicy g_my_retry_policy; +options.retry_policy = &g_my_retry_policy; +... +``` + +Some tips: + +- Get response of the RPC by cntl->response(). +- RPC deadline represented by ERPCTIMEDOUT is never retried, even it's allowed by your derived RetryPolicy. + +### Retrying should be conservative + +Due to maintaining costs, even very large scale clusters are deployed with "just enough" instances to survive major defects, namely offline of one IDC, which is at most 1/2 of all machines. However aggressive retries may easily make pressures from all clients double or even tripple against servers, and make the whole cluster down: More and more requests stuck in buffers, because servers can't process them in-time. All requests have to wait for a very long time to be processed and finally gets timed out, as if the whole cluster is crashed. The default retrying policy is safe generally: unless the connection is broken, retries are rarely sent. However users are able to customize starting conditions for retries by inheriting RetryPolicy, which may turn retries to be "a storm". When you customized RetryPolicy, you need to carefully consider how clients and servers interact and design corresponding tests to verify that retries work as expected. + +## Circuit breaker + +Check out [circuit_breaker](../cn/circuit_breaker.md) for more details. + +## Protocols + +The default protocol used by Channel is baidu_std, which is changeable by setting ChannelOptions.protocol. The field accepts both enum and string. + + Supported protocols: + +- PROTOCOL_BAIDU_STD or "baidu_std", which is [the standard binary protocol inside Baidu](baidu_std.md), using single connection by default. +- PROTOCOL_HTTP or "http", which is http/1.0 or http/1.1, using pooled connection by default (Keep-Alive). + - Methods for accessing ordinary http services are listed in [Access http/h2](http_client.md). + - Methods for accessing pb services by using http:json or http:proto are listed in [http/h2 derivatives](http_derivatives.md) +- PROTOCOL_H2 or ”h2", which is http/2, using single connection by default. + - Methods for accessing ordinary h2 services are listed in [Access http/h2](http_client.md). + - Methods for accessing pb services by using h2:json or h2:proto are listed in [http/h2 derivatives](http_derivatives.md) +- "h2:grpc", which is the protocol of [gRPC](https://grpc.io) and based on h2, using single connection by default, check out [h2:grpc](http_derivatives.md#h2grpc) for details. +- PROTOCOL_THRIFT or "thrift", which is the protocol of [apache thrift](https://thrift.apache.org), using pooled connection by default, check out [Access thrift](thrift.md) for details. +- PROTOCOL_MEMCACHE or "memcache", which is binary protocol of memcached, using **single connection** by default. Check out [Access memcached](memcache_client.md) for details. +- PROTOCOL_REDIS or "redis", which is protocol of redis 1.2+ (the one supported by hiredis), using **single connection** by default. Check out [Access Redis](redis_client.md) for details. +- PROTOCOL_HULU_PBRPC or "hulu_pbrpc", which is protocol of hulu-pbrpc, using single connection by default. +- PROTOCOL_NOVA_PBRPC or "nova_pbrpc", which is protocol of Baidu ads union, using pooled connection by default. +- PROTOCOL_SOFA_PBRPC or "sofa_pbrpc", which is protocol of sofa-pbrpc, using single connection by default. +- PROTOCOL_PUBLIC_PBRPC or "public_pbrpc", which is protocol of public_pbrpc, using pooled connection by default. +- PROTOCOL_UBRPC_COMPACK or "ubrpc_compack", which is protocol of public/ubrpc, packing with compack, using pooled connection by default. check out [ubrpc (by protobuf)](ub_client.md) for details. A related protocol is PROTOCOL_UBRPC_MCPACK2 or ubrpc_mcpack2, packing with mcpack2. +- PROTOCOL_NSHEAD_CLIENT or "nshead_client", which is required by UBXXXRequest in baidu-rpc-ub, using pooled connection by default. Check out [Access UB](ub_client.md) for details. +- PROTOCOL_NSHEAD or "nshead", which is required by sending NsheadMessage, using pooled connection by default. Check out [nshead+blob](ub_client.md#nshead-blob) for details. +- PROTOCOL_NSHEAD_MCPACK or "nshead_mcpack", which is as the name implies, nshead + mcpack (parsed by protobuf via mcpack2pb), using pooled connection by default. +- PROTOCOL_ESP or "esp", for accessing services with esp protocol, using pooled connection by default. + +## Connection Type + +brpc supports following connection types: + +- short connection: Established before each RPC, closed after completion. Since each RPC has to pay the overhead of establishing connection, this type is used for occasionally launched RPC, not frequently launched ones. No protocol use this type by default. Connections in http/1.0 are handled similarly as short connections. +- pooled connection: Pick an unused connection from a pool before each RPC, return after completion. One connection carries at most one request at the same time. One client may have multiple connections to one server. http/1.1 and the protocols using nshead use this type by default. +- single connection: all clients in one process has at most one connection to one server, one connection may carry multiple requests at the same time. The sequence of received responses does not need to be same as sending requests. This type is used by baidu_std, hulu_pbrpc, sofa_pbrpc by default. + +| | short connection | pooled connection | single connection | +| ---------------------------------------- | ---------------------------------------- | --------------------------------------- | ---------------------------------------- | +| long connection | no | yes | yes | +| \#connection at server-side (from a client) | qps*latency ([little's law](https://en.wikipedia.org/wiki/Little%27s_law)) | qps*latency | 1 | +| peak qps | bad, and limited by max number of ports | medium | high | +| latency | 1.5RTT(connect) + 1RTT + processing time | 1RTT + processing time | 1RTT + processing time | +| cpu usage | high, tcp connect for each RPC | medium, every request needs a sys write | low, writes can be combined to reduce overhead. | + +brpc chooses best connection type for the protocol by default, users generally have no need to change it. If you do, set ChannelOptions.connection_type to: + +- CONNECTION_TYPE_SINGLE or "single" : single connection + +- CONNECTION_TYPE_POOLED or "pooled": pooled connection. Max number of pooled connections from one client to one server is limited by -max_connection_pool_size. Note the number is not same as "max number of connections". New connections are always created when there's no idle ones in the pool; the returned connections are closed immediately when the pool already has max_connection_pool_size connections. Value of max_connection_pool_size should respect the concurrency, otherwise the connnections that can't be pooled are created and closed frequently which behaves similarly as short connections. If max_connection_pool_size is 0, the pool behaves just like fully short connections. + + | Name | Value | Description | Defined At | + | ---------------------------- | ----- | ---------------------------------------- | ------------------- | + | max_connection_pool_size (R) | 100 | Max number of pooled connections to a single endpoint | src/brpc/socket.cpp | + +- CONNECTION_TYPE_SHORT or "short" : short connection + +- "" (empty string) makes brpc chooses the default one. + +brpc also supports [Streaming RPC](streaming_rpc.md) which is an application-level connection for transferring streaming data. + +## Close idle connections in pools + +If a connection has no read or write within the seconds specified by -idle_timeout_second, it's tagged as "idle", and will be closed automatically. Default value is 10 seconds. This feature is only effective to pooled connections. If -log_idle_connection_close is true, a log is printed before closing. + +| Name | Value | Description | Defined At | +| ------------------------- | ----- | ---------------------------------------- | ----------------------- | +| idle_timeout_second | 10 | Pooled connections without data transmission for so many seconds will be closed. No effect for non-positive values | src/brpc/socket_map.cpp | +| log_idle_connection_close | false | Print log when an idle connection is closed | src/brpc/socket.cpp | + +## Defer connection close + +Multiple channels may share a connection via referential counting. When a channel releases last reference of the connection, the connection will be closed. But in some scenarios, channels are created just before sending RPC and destroyed after completion, in which case connections are probably closed and re-open again frequently, as costly as short connections. + +One solution is to cache channels commonly used by user, which avoids frequent creation and destroying of channels. However brpc does not offer an utility for doing this right now, and it's not trivial for users to implement it correctly. + +Another solution is setting gflag -defer_close_second + +| Name | Value | Description | Defined At | +| ------------------ | ----- | ---------------------------------------- | ----------------------- | +| defer_close_second | 0 | Defer close of connections for so many seconds even if the connection is not used by anyone. Close immediately for non-positive values | src/brpc/socket_map.cpp | +| defer_close_respect_idle | false | When defer_close_second > 0, close a connection immediately when the last reference is removed and the socket has already been idle for longer than defer_close_second | src/brpc/socket_map.cpp | + +After setting, connection is not closed immediately after last referential count, instead it will be closed after so many seconds. If a channel references the connection again during the wait, the connection resumes to normal. No matter how frequent channels are created, this flag limits the frequency of closing connections. Side effect of the flag is that file descriptors are not closed immediately after destroying of channels, if the flag is wrongly set to be large, number of active file descriptors in the process may be large as well. When -defer_close_respect_idle is enabled, a connection that has already been idle for longer than defer_close_second may be closed when the last reference is removed. + +## Buffer size of connections + +-socket_recv_buffer_size sets receiving buffer size of all connections, -1 by default (not modified) + +-socket_send_buffer_size sets sending buffer size of all connections, -1 by default (not modified) + +| Name | Value | Description | Defined At | +| ----------------------- | ----- | ---------------------------------------- | ------------------- | +| socket_recv_buffer_size | -1 | Set the recv buffer size of socket if this value is positive | src/brpc/socket.cpp | +| socket_send_buffer_size | -1 | Set send buffer size of sockets if this value is positive | src/brpc/socket.cpp | + +## log_id + +set_log_id() sets a 64-bit integral log_id, which is sent to the server-side along with the request, and often printed in server logs to associate different services accessed in a session. String-type log-id must be converted to 64-bit integer before setting. + +## Attachment + +baidu_std and hulu_pbrpc supports attachments which are sent along with messages and set by users to bypass serialization of protobuf. As a client, data set in Controller::request_attachment() will be received by server and response_attachment() contains attachment sent back by the server. + +Attachment is not compressed by framework. + +In http/h2, attachment corresponds to [message body](http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html), namely the data to post to server is stored in request_attachment(). + +## Turn on SSL + +Update openssl to the latest version before turning on SSL, since older versions of openssl may have severe security problems and support less encryption algorithms, which is against with the purpose of using SSL. +Set ChannelOptions.mutable_ssl_options() to enable SSL. Refer to [ssl_options.h](https://github.com/apache/brpc/blob/master/src/brpc/ssl_options.h) for the detailed options. ChannelOptions.has_ssl_options() checks if ssl_options was set; ChannelOptions.ssl_options() returns const reference to the ssl_options. + +```c++ +// Enable client-side SSL and use default values. +options.mutable_ssl_options(); + +// Enable client-side SSL and customize values. +options.mutable_ssl_options()->ciphers_name = "..."; +options.mutable_ssl_options()->sni_name = "..."; + +// Set the protocol preference of ALPN. +// (By default ALPN is disabled.) +options.mutable_ssl_options()->alpn_protocols = {"h2", "http/1.1"}; +``` + +- Channels connecting to a single server or a cluster both support SSL (the initial implementation does not support cluster) +- After turning on SSL, all requests through this Channel will be encrypted. Users should create another Channel for non-SSL requests if needed. +- Accessibility improvements for HTTPS: Channel.Init recognizes https:// prefix and turns on SSL automatically; -http_verbose prints certificate information when SSL is on. + +## Authentication + +Generally there are 2 ways of authentication at the client side: + +1. Request-based authentication: Each request carries authentication information. It's more flexible since the authentication information can contain fields based on this particular request. However, this leads to a performance loss due to the extra payload in each request. +2. Connection-based authentication: Once a TCP connection has been established, the client sends an authentication packet. After it has been verfied by the server, subsequent requests on this connection no longer needs authentication. Compared with the former, this method can only carry some static information such as local IP in the authentication packet. However, it has better performance especially under single connection / connection pool scenario. + +It's very simple to implement the first method by just adding authentication data format into the request proto definition. Then send it as normal RPC in each request. To achieve the second one, brpc provides an interface for users to implement: + +```c++ +class Authenticator { +public: + virtual ~Authenticator() {} + + // Implement this method to generate credential information + // into `auth_str' which will be sent to `VerifyCredential' + // at server side. This method will be called on client side. + // Returns 0 on success, error code otherwise + virtual int GenerateCredential(std::string* auth_str) const = 0; +}; +``` + +When the user calls the RPC interface with a single connection to the same server, the framework guarantee that once the TCP connection has been established, the first request on the connection will contain the authentication string generated by `GenerateCredential`. Subsequent requests will not carried that string. The entire sending process is still highly concurrent since it won't wait for the authentication result. If the verification succeeds, all requests return without error. Otherwise, if the verification fails, generally the server will close the connection and those requests will receive the corresponding error. + +Currently only those protocols support client authentication: [baidu_std](../cn/baidu_std.md) (default protocol), HTTP, hulu_pbrpc, ESP. For customized protocols, generally speaking, users could call the `Authenticator`'s interface to generate authentication string during the request packing process in order to support authentication. + +## Reset + +This method makes Controller back to the state as if it's just created. + +Don't call Reset() during a RPC, which is undefined. + +## Compression + +set_request_compress_type() sets compress-type of the request, no compression by default. + +NOTE: Attachment is not compressed by brpc. + +Check out [compress request body](http_client.md#compress-request-body) to compress http/h2 body. + +Supported compressions: + +- brpc::CompressTypeSnappy : [snanpy](http://google.github.io/snappy/), compression and decompression are very fast, but compression ratio is low. +- brpc::CompressTypeGzip : [gzip](http://en.wikipedia.org/wiki/Gzip), significantly slower than snappy, with a higher compression ratio. +- brpc::CompressTypeZlib : [zlib](http://en.wikipedia.org/wiki/Zlib), 10%~20% faster than gzip but still significantly slower than snappy, with slightly better compression ratio than gzip. + +Following table lists performance of different methods compressing and decompressing **data with a lot of duplications**, just for reference. + +| Compress method | Compress size(B) | Compress time(us) | Decompress time(us) | Compress throughput(MB/s) | Decompress throughput(MB/s) | Compress ratio | +| --------------- | ---------------- | ----------------- | ------------------- | ------------------------- | --------------------------- | -------------- | +| Snappy | 128 | 0.753114 | 0.890815 | 162.0875 | 137.0322 | 37.50% | +| Gzip | 10.85185 | 1.849199 | 11.2488 | 66.01252 | 47.66% | | +| Zlib | 10.71955 | 1.66522 | 11.38763 | 73.30581 | 38.28% | | +| Snappy | 1024 | 1.404812 | 1.374915 | 695.1555 | 710.2713 | 8.79% | +| Gzip | 16.97748 | 3.950946 | 57.52106 | 247.1718 | 6.64% | | +| Zlib | 15.98913 | 3.06195 | 61.07665 | 318.9348 | 5.47% | | +| Snappy | 16384 | 8.822967 | 9.865008 | 1770.946 | 1583.881 | 4.96% | +| Gzip | 160.8642 | 43.85911 | 97.13162 | 356.2544 | 0.78% | | +| Zlib | 147.6828 | 29.06039 | 105.8011 | 537.6734 | 0.71% | | +| Snappy | 32768 | 16.16362 | 19.43596 | 1933.354 | 1607.844 | 4.82% | +| Gzip | 229.7803 | 82.71903 | 135.9995 | 377.7849 | 0.54% | | +| Zlib | 240.7464 | 54.44099 | 129.8046 | 574.0161 | 0.50% | | + +Following table lists performance of different methods compressing and decompressing **data with very few duplications**, just for reference. + +| Compress method | Compress size(B) | Compress time(us) | Decompress time(us) | Compress throughput(MB/s) | Decompress throughput(MB/s) | Compress ratio | +| --------------- | ---------------- | ----------------- | ------------------- | ------------------------- | --------------------------- | -------------- | +| Snappy | 128 | 0.866002 | 0.718052 | 140.9584 | 170.0021 | 105.47% | +| Gzip | 15.89855 | 4.936242 | 7.678077 | 24.7294 | 116.41% | | +| Zlib | 15.88757 | 4.793953 | 7.683384 | 25.46339 | 107.03% | | +| Snappy | 1024 | 2.087972 | 1.06572 | 467.7087 | 916.3403 | 100.78% | +| Gzip | 32.54279 | 12.27744 | 30.00857 | 79.5412 | 79.79% | | +| Zlib | 31.51397 | 11.2374 | 30.98824 | 86.90288 | 78.61% | | +| Snappy | 16384 | 12.598 | 6.306592 | 1240.276 | 2477.566 | 100.06% | +| Gzip | 537.1803 | 129.7558 | 29.08707 | 120.4185 | 75.32% | | +| Zlib | 519.5705 | 115.1463 | 30.07291 | 135.697 | 75.24% | | +| Snappy | 32768 | 22.68531 | 12.39793 | 1377.543 | 2520.582 | 100.03% | +| Gzip | 1403.974 | 258.9239 | 22.25825 | 120.6919 | 75.25% | | +| Zlib | 1370.201 | 230.3683 | 22.80687 | 135.6524 | 75.21% | | + +# FAQ + +### Q: Does brpc support unix domain socket? + +No. Local TCP sockets performs just a little slower than unix domain socket since traffic over local TCP sockets bypasses network. Some scenarios where TCP sockets can't be used may require unix domain sockets. We may consider the capability in future. + +### Q: Fail to connect to xx.xx.xx.xx:xxxx, Connection refused + +The remote server does not serve any more (probably crashed). + +### Q: often met Connection timedout to another IDC + +![img](../images/connection_timedout.png) + +The TCP connection is not established within connection_timeout_ms, you have to tweak options: + +```c++ +struct ChannelOptions { + ... + // Issue error when a connection is not established after so many + // milliseconds. -1 means wait indefinitely. + // Default: 200 (milliseconds) + // Maximum: 0x7fffffff (roughly 30 days) + int32_t connect_timeout_ms; + + // Max duration of RPC over this Channel. -1 means wait indefinitely. + // Overridable by Controller.set_timeout_ms(). + // Default: 500 (milliseconds) + // Maximum: 0x7fffffff (roughly 30 days) + int32_t timeout_ms; + ... +}; +``` + +NOTE: Connection timeout is not RPC timeout, which is printed as "Reached timeout=...". + +### Q: synchronous call is good, asynchronous call crashes + +Check lifetime of Controller, Response and done. In asynchronous call, finish of CallMethod is not completion of RPC which is entering of done->Run(). So the objects should not deleted just after CallMethod, instead they should be delete in done->Run(). Generally you should allocate the objects on heap instead of putting them on stack. Check out [Asynchronous call](client.md#asynchronous-call) for details. + +### Q: How to make requests be processed once and only once + +This issue is not solved on RPC layer. When response returns and being successful, we know the RPC is processed at server-side. When response returns and being rejected, we know the RPC is not processed at server-side. But when response is not returned, server may or may not process the RPC. If we retry, same request may be processed twice at server-side. Generally RPC services with side effects must consider [idempotence](http://en.wikipedia.org/wiki/Idempotence) of the service, otherwise retries may make side effects be done more than once and result in unexpected behavior. Search services with only read often have no side effects (during a search), being idempotent natually. But storage services that need to write have to design versioning or serial-number mechanisms to reject side effects that already happen, to keep idempoent. + +### Q: Invalid address=`bns://group.user-persona.dumi.nj03' +``` +FATAL 04-07 20:00:03 7778 src/brpc/channel.cpp:123] Invalid address=`bns://group.user-persona.dumi.nj03'. You should use Init(naming_service_name, load_balancer_name, options) to access multiple servers. +``` +Accessing servers under naming service needs the Init() with 3 parameters(the second param is `load_balancer_name`). The Init() here is with 2 parameters and treated by brpc as accessing single server, producing the error. + +### Q: Both sides use protobuf, why can't they communicate with each other + +**protocol != protobuf**. protobuf serializes one package and a message of a protocol may contain multiple packages along with extra lengths, checksums, magic numbers. The capability offered by brpc that "write code once and serve multiple protocols" is implemented by converting data from different protocols to unified API, not on protobuf layer. + +### Q: Why C++ client/server may fail to talk to client/server in other languages + +Check if the C++ version turns on compression (Controller::set_compress_type), Currently RPC impl. in other languages do not support compression yet. + +# PS: Workflow at Client-side + +![img](../images/client_side.png) + +Steps: + +1. Create a [bthread_id](https://github.com/apache/brpc/blob/master/src/bthread/id.h) as correlation_id of current RPC. +2. According to how the Channel is initialized, choose a server from global [SocketMap](https://github.com/apache/brpc/blob/master/src/brpc/socket_map.h) or [LoadBalancer](https://github.com/apache/brpc/blob/master/src/brpc/load_balancer.h) as destination of the request. +3. Choose a [Socket](https://github.com/apache/brpc/blob/master/src/brpc/socket.h) according to connection type (single, pooled, short) +4. If authentication is turned on and the Socket is not authenticated yet, first request enters authenticating branch, other requests block until the branch writes authenticating information into the Socket. Server-side only verifies the first request. +5. According to protocol of the Channel, choose corresponding serialization callback to serialize request into [IOBuf](https://github.com/apache/brpc/blob/master/src/butil/iobuf.h). +6. If timeout is set, setup timer. From this point on, avoid using Controller, since the timer may be triggered at anytime and calls user's callback for timeout, which may delete Controller. +7. Sending phase is completed. If error occurs at any step, Channel::HandleSendFailed is called. +8. Write IOBuf with serialized data into the Socket and add Channel::HandleSocketFailed into id_wait_list of the Socket. The callback will be called when the write is failed or connection is broken before completion of RPC. +9. In synchronous call, Join correlation_id; otherwise CallMethod() returns. +10. Send/receive messages to/from network. +11. After receiving response, get the correlation_id inside, find out associated Controller within O(1) time. The lookup does not need to lock a global hashmap, and scales well. +12. Parse response according to the protocol +13. Call Controller::OnRPCReturned, which may retry errorous RPC, or complete the RPC. Call user's done in asynchronous call. Destroy correlation_id and wakeup joining threads. diff --git a/docs/en/combo_channel.md b/docs/en/combo_channel.md new file mode 100644 index 0000000..ab68188 --- /dev/null +++ b/docs/en/combo_channel.md @@ -0,0 +1,519 @@ +[中文版](../cn/combo_channel.md) + +With the growth of services, access patterns to downstream servers become increasingly complicated and often contain multiple RPCs in parallel or layered accesses. The complications could easily introduce tricky bugs around multi-threaded programming, which may even not be sensed by users and difficult to debug and reproduce. Moreover, implementations either support synchronous patterns only, or have totally different code for asynchronous patterns. Take running some code after completions of multiple asynchronous RPCs as an example, the synchronous pattern is often implemented as issuing multiple RPCs asynchronously and waiting for the completions respectively, while the asynchronous pattern is often implemented by a callback plus a referential count, which is decreased by one when one RPC completes. The callback is called when the count hits zero. Let's see drawbacks of the solution: + +- The code is inconsistent between synchronous and asynchronous pattern and it's not trivial for users to move from one pattern to another. From the designing point of view, inconsistencies implies that the essence is probably not grasped yet. +- Cancellation is unlikely to be supported. It's not easy to cancel a single RPC correctly, let alone combinations of RPC. However cancellations are necessary to end pointless waiting in many scenarios. +- Not composable. It's hard to enclose the implementations above as one part of a "larger" pattern. The code can hardly be reused in a different scenario. + +We need a better abstraction. If several channels are combined into a larger one with different access patterns enclosed, users would be able to do synchronous, asynchronous, cancelling operations with consistent and unified interfaces. The kind of channels are called combo channels in brpc. + +# ParallelChannel + +`ParallelChannel` (referred to as "pchan" sometimes) sends requests to all internal sub channels in parallel and merges the responses. Users can modify requests via `CallMapper` and merge responses with `ResponseMerger`. `ParallelChannel` looks like a `Channel`: + +- Support synchronous and asynchronous accesses. +- Can be destroyed immediately after initiating an asynchronous operation. +- Support cancellation. +- Support timeout. + +Check [example/parallel_echo_c++](https://github.com/apache/brpc/tree/master/example/parallel_echo_c++/) for an example. + +Any subclasses of `brpc::ChannelBase` can be added into `ParallelChannel`, including `ParallelChannel` and other combo channels. + +Set `ParallelChannelOptions.fail_limit` to control maximum number of failures. When number of failed responses reaches the limit, the RPC is ended immediately rather than waiting for timeout. + +Set `ParallelChannelOptions.sucess_limit` to control maximum number of successful responses. When number of successful responses reaches the limit, the RPC is ended immediately.`ParallelChannelOptions.fail_limit` has a higher priority than `ParallelChannelOptions.success_limit`. Success_limit will take effect only when fail_limit is not set. + +A sub channel can be added to the same `ParallelChannel` more than once, which is useful when you need to initiate multiple asynchronous RPC to the same service and wait for their completions. + +Following picture shows internal structure of `ParallelChannel` (Chinese in red: can be different from request/response respectively) + +![img](../images/pchan.png) + +## Add sub channel + +A sub channel can be added into `ParallelChannel` by following API: + +```c++ +int AddChannel(brpc::ChannelBase* sub_channel, + ChannelOwnership ownership, + CallMapper* call_mapper, + ResponseMerger* response_merger); +``` + +When `ownership` is `brpc::OWNS_CHANNEL`, `sub_channel` is destroyed when the `ParallelChannel` destructs. Although a sub channel may be added into a `ParallelChannel` multiple times, it's deleted for at most once when `ownership` in one of the additions is `brpc::OWNS_CHANNEL`. + +Calling ` AddChannel` during a RPC over `ParallelChannel` is **NOT thread safe**. + +## CallMapper + +This class converts RPCs to `ParallelChannel` to the ones to `sub channel`. If `call_mapper` is NULL, requests to the sub channel is just the ones to `ParallelChannel`, and responses are created by calling `New()` on the responses to `ParallelChannel`. `call_mapper` is deleted when `ParallelChannel` destructs. Due to the reference counting inside, one `call_mapper` can be associated with multiple sub channels. + +```c++ +class CallMapper { +public: + virtual ~CallMapper(); + + virtual SubCall Map(int channel_index/*starting from 0*/, + int channel_count, + const google::protobuf::MethodDescriptor* method, + const google::protobuf::Message* request, + google::protobuf::Message* response) = 0; +}; +``` + +### Map + +`channel_index`: The position of the sub channel inside `ParallelChannel`, starting from zero. + +`channel_count`: The sub channel count inside `ParallelChannel`. + +`method/request/response`: Parameters to `ParallelChannel::CallMethod()`. + +The returned `SubCall` configures the calls to the corresponding sub channel and has two special values: + +- `SubCall::Bad()`: The call to ParallelChannel fails immediately and `Controller::ErrorCode()` is set to `EREQUEST`. +- `SubCall::Skip()`: Skip the call to this sub channel. If all sub channels are skipped, the call to ParallelChannel fails immediately and `Controller::ErrorCode()` is set to `ECANCELED`. + +Common implementations of `Map()` are listed below: + +- Broadcast the request. This is also the behavior when `call_mapper` is NULL: + +```c++ + class Broadcaster : public CallMapper { + public: + SubCall Map(int channel_index/*starting from 0*/, + const google::protobuf::MethodDescriptor* method, + const google::protobuf::Message* request, + google::protobuf::Message* response) { + // Use the method/request to pchan. + // response is created by `new` and the last flag tells pchan to delete response after completion of the RPC + return SubCall(method, request, response->New(), DELETE_RESPONSE); + } + }; +``` + +- Modify some fields in the request before sending: + +```c++ + class ModifyRequest : public CallMapper { + public: + SubCall Map(int channel_index/*starting from 0*/, + const google::protobuf::MethodDescriptor* method, + const google::protobuf::Message* request, + google::protobuf::Message* response) { + FooRequest* copied_req = brpc::Clone(request); + copied_req->set_xxx(...); + // Copy and modify the request + // The last flag tells pchan to delete the request and response after completion of the RPC + return SubCall(method, copied_req, response->New(), DELETE_REQUEST | DELETE_RESPONSE); + } + }; +``` + +- request/response already contains sub requests/responses, use them directly. + +```c++ + class UseFieldAsSubRequest : public CallMapper { + public: + SubCall Map(int channel_index/*starting from 0*/, + const google::protobuf::MethodDescriptor* method, + const google::protobuf::Message* request, + google::protobuf::Message* response) { + if (channel_index >= request->sub_request_size()) { + // Not enough sub_request. The caller doesn't provide same number of requests as number of sub channels in pchan + // Return Bad() to end this RPC immediately + return SubCall::Bad(); + } + // Fetch the sub request and add a new sub response. + // The last flag(0) tells pchan that there is nothing to delete. + return SubCall(sub_method, request->sub_request(channel_index), response->add_sub_response(), 0); + } + }; +``` + +### MapController + +`channel_index`: The position of the sub channel inside `ParallelChannel`, starting from zero. + +`channel_count`: The sub channel count inside `ParallelChannel`. + +`main_cntl`:Parameters to `ParallelChannel::CallMethod()`. + +`sub_cntl`:The controller corresponding to the sub-channel's requests. Default implementation: Copy the http_request and request_attachment of `main_cntl` to the `sub_cntl`. + +Note: Modifying `ClientSettings` configurations (such as timeout and retries) is ineffective because all sub controllers use the `ClientSettings` configuration of `main_cntl`. + +## ResponseMerger + +`response_merger` merges responses from all sub channels into one for the `ParallelChannel`. When it's NULL, `response->MergeFrom(*sub_response)` is used instead, whose behavior can be summarized as "merge repeated fields and overwrite the rest". If you need more complex behavior, implement `ResponseMerger`. Multiple `response_merger` are called one by one to merge sub responses so that you do not need to consider the race conditions between merging multiple responses simultaneously. The object is deleted when `ParallelChannel ` destructs. Due to the reference counting inside, `response_merger ` can be associated with multiple sub channels. + +Possible values of `Result` are: + +- MERGED: Successfully merged. +- FAIL: The `sub_response` was not merged successfully, counted as one failure. For example, there are 10 sub channels and `fail_limit` is 4, if 4 merges return FAIL, the RPC would reach fail_limit and end soon. +- FAIL_ALL: Directly fail the RPC. + +## Get the controller to each sub channel + +Sometimes users may need to know the details around sub calls. `Controller.sub(i)` gets the controller corresponding to a sub channel. + +```c++ +// Get the controllers for accessing sub channels in combo channels. +// Ordinary channel: +// sub_count() is 0 and sub() is always NULL. +// ParallelChannel/PartitionChannel: +// sub_count() is #sub-channels and sub(i) is the controller for +// accessing i-th sub channel inside ParallelChannel, if i is outside +// [0, sub_count() - 1], sub(i) is NULL. +// NOTE: You must test sub() against NULL, ALWAYS. Even if i is inside +// range, sub(i) can still be NULL: +// * the rpc call may fail and terminate before accessing the sub channel +// * the sub channel was skipped +// SelectiveChannel/DynamicPartitionChannel: +// sub_count() is always 1 and sub(0) is the controller of successful +// or last call to sub channels. +int sub_count() const; +const Controller* sub(int index) const; +``` + +# SelectiveChannel + +[SelectiveChannel](https://github.com/apache/brpc/blob/master/src/brpc/selective_channel.h) (referred to as "schan" sometimes) accesses one of the internal sub channels with a load balancing algorithm. It's more high-level compared to ordinary channels: The requests are sent to sub channels instead of servers directly. `SelectiveChannel` is mainly for load balancing between groups of machines and shares basic properties of `Channel`: + +- Support synchronous and asynchronous accesses. +- Can be destroyed immediately after initiating an asynchronous operation. +- Support cancellation. +- Support timeout. + +Check [example/selective_echo_c++](https://github.com/apache/brpc/tree/master/example/selective_echo_c++/) for an example. + +Any subclasses of `brpc::ChannelBase` can be added into `SelectiveChannel`, including `SelectiveChannel` and other combo channels. + +Retries done by `SelectiveChannel` are independent from the ones in its sub channels. When a call to one of the sub channels fails(which may have been retried), other sub channels are retried. + +Currently `SelectiveChannel` requires **the request remains valid before completion of the RPC**, while other combo or regular channels do not. If you plan to use `SelectiveChannel` asynchronously, make sure that the request is deleted inside `done`. + +## Using SelectiveChannel + +The initialization of `SelectiveChannel` is almost the same as regular `Channel`, except that it doesn't need a naming service in `Init`, because `SelectiveChannel` adds sub channels dynamically by `AddChannel`, while regular `Channel` adds servers in the naming service. + +```c++ +#include +... +brpc::SelectiveChannel schan; +brpc::ChannelOptions schan_options; +schan_options.timeout_ms = ...; +schan_options.backup_request_ms = ...; +schan_options.max_retry = ...; +if (schan.Init(load_balancer, &schan_options) != 0) { + LOG(ERROR) << "Fail to init SelectiveChannel"; + return -1; +} +``` + +After successful initialization, add sub channels with `AddChannel`. + +```c++ +// The second parameter ChannelHandle is used to delete sub channel, +// which can be NULL if this isn't necessary. +if (schan.AddChannel(sub_channel, NULL/*ChannelHandle*/) != 0) { + LOG(ERROR) << "Fail to add sub_channel"; + return -1; +} +``` + +Note that: + +- Unlike `ParallelChannel`, `SelectiveChannel::AddChannel` can be called at any time, even if a RPC over the SelectiveChannel is going on. (newly added channels take effects at the next RPC). +- `SelectiveChannel` always owns sub channels, which is different from `ParallelChannel`'s configurable ownership. +- If the second parameter to `AddChannel` is not NULL, it's filled with a value typed `brpc::SelectiveChannel::ChannelHandle`, which can be used as the parameter to `RemoveAndDestroyChannel` to remove and destroy a channel dynamically. +- `SelectiveChannel` overrides timeouts in sub channels. For example, having timeout set to 100ms for a sub channel and 500ms for `SelectiveChannel`, the actual timeout is 500ms. + +`SelectiveChannel`s are accessed same as regular channels. + +## Example: divide traffic to multiple naming services + +Sometimes we need to divide traffic to multiple naming services, because: + +- Machines for one service are listed in multiple naming services. +- Machines are split into multiple groups. Requests are sent to one of the groups and then routed to one of the machines inside the group, and traffic are divided differently between groups and machines in a group. + +Above requirements can be achieved by `SelectiveChannel`. + +Following code creates a `SelectiveChannel` and inserts 3 regular channels for different naming services respectively. + +```c++ +brpc::SelectiveChannel channel; +brpc::ChannelOptions schan_options; +schan_options.timeout_ms = FLAGS_timeout_ms; +schan_options.max_retry = FLAGS_max_retry; +if (channel.Init("c_murmurhash", &schan_options) != 0) { + LOG(ERROR) << "Fail to init SelectiveChannel"; + return -1; +} + +for (int i = 0; i < 3; ++i) { + brpc::Channel* sub_channel = new brpc::Channel; + if (sub_channel->Init(ns_node_name[i], "rr", NULL) != 0) { + LOG(ERROR) << "Fail to init sub channel " << i; + return -1; + } + if (channel.AddChannel(sub_channel, NULL/*handle for removal*/) != 0) { + LOG(ERROR) << "Fail to add sub_channel to channel"; + return -1; + } +} +... +XXXService_Stub stub(&channel); +stub.FooMethod(&cntl, &request, &response, NULL); +... +``` + +# PartitionChannel + +[PartitionChannel](https://github.com/apache/brpc/blob/master/src/brpc/partition_channel.h) is a specialized `ParallelChannel` to add sub channels automatically based on tags defined in the naming service. As a result, users can list all machines in one naming service and partition them by tags. Check [example/partition_echo_c++](https://github.com/apache/brpc/tree/master/example/partition_echo_c++/) for an example. + +`ParititonChannel` only supports one kind to partitioning method. When multiple methods need to coexist, or one method needs to be changed to another smoothly, try `DynamicPartitionChannel`, which creates corresponding sub `PartitionChannel` for different partitioning methods, and divide traffic to partitions according to capacities of servers. Check [example/dynamic_partition_echo_c++](https://github.com/apache/brpc/tree/master/example/dynamic_partition_echo_c++/) for an example. + +If partitions are listed in different naming services, users have to implement the partitioning by `ParallelChannel` and include sub channels to corresponding naming services respectively. Refer to [the previous section](#ParallelChannel) for usages of `ParellelChannel`. + +## Using PartitionChannel + +First of all, implement your own `PartitionParser`. In this example, the tag's format is `N/M`, where N is index of the partition and M is total number of partitions. `0/3` means that there're 3 partitions and this is the first one of them. + +```c++ +#include +... +class MyPartitionParser : public brpc::PartitionParser { +public: + bool ParseFromTag(const std::string& tag, brpc::Partition* out) { + // "N/M" : #N partition of M partitions. + size_t pos = tag.find_first_of('/'); + if (pos == std::string::npos) { + LOG(ERROR) << "Invalid tag=" << tag; + return false; + } + char* endptr = NULL; + out->index = strtol(tag.c_str(), &endptr, 10); + if (endptr != tag.data() + pos) { + LOG(ERROR) << "Invalid index=" << butil::StringPiece(tag.data(), pos); + return false; + } + out->num_partition_kinds = strtol(tag.c_str() + pos + 1, &endptr, 10); + if (endptr != tag.c_str() + tag.size()) { + LOG(ERROR) << "Invalid num=" << tag.data() + pos + 1; + return false; + } + return true; + } +}; +``` + +Then initialize the `PartitionChannel`. + +```c++ +#include +... +brpc::PartitionChannel channel; + +brpc::PartitionChannelOptions options; +options.protocol = ...; // PartitionChannelOptions inherits ChannelOptions +options.timeout_ms = ...; // Same as above +options.fail_limit = 1; // PartitionChannel's own settting, which means the same as that of + // ParalellChannel. fail_limit=1 means the overall RPC will fail + // as long as only 1 paratition fails + +if (channel.Init(num_partition_kinds, new MyPartitionParser(), + server_address, load_balancer, &options) != 0) { + LOG(ERROR) << "Fail to init PartitionChannel"; + return -1; +} +// The RPC interface is the same as regular Channel +``` + +## Using DynamicPartitionChannel + +`DynamicPartitionChannel` and `PartitionChannel` are basically same on usages. Implement `PartitionParser` first then initialize the channel, which does not need `num_partition_kinds` since `DynamicPartitionChannel` dynamically creates sub `PartitionChannel` for each partition. + +Following sections demonstrate how to use `DynamicPartitionChannel` to migrate from 3 partitions to 4 partitions smoothly. + +First of all, start 3 servers serving on port 8004, 8005, 8006 respectively. + +``` +$ ./echo_server -server_num 3 +TRACE: 09-06 10:40:39: * 0 server.cpp:159] EchoServer is serving on port=8004 +TRACE: 09-06 10:40:39: * 0 server.cpp:159] EchoServer is serving on port=8005 +TRACE: 09-06 10:40:39: * 0 server.cpp:159] EchoServer is serving on port=8006 +TRACE: 09-06 10:40:40: * 0 server.cpp:192] S[0]=0 S[1]=0 S[2]=0 [total=0] +TRACE: 09-06 10:40:41: * 0 server.cpp:192] S[0]=0 S[1]=0 S[2]=0 [total=0] +TRACE: 09-06 10:40:42: * 0 server.cpp:192] S[0]=0 S[1]=0 S[2]=0 [total=0] +``` + +Note that each server prints summaries on traffic received in last second, which is all 0 now. + +Start a client using `DynamicPartitionChannel`, which is initialized as follows: + +```c++ + ... + brpc::DynamicPartitionChannel channel; + brpc::PartitionChannelOptions options; + // Failure on any single partition fails the RPC immediately. You can use a more relaxed value + options.fail_limit = 1; + if (channel.Init(new MyPartitionParser(), "file://server_list", "rr", &options) != 0) { + LOG(ERROR) << "Fail to init channel"; + return -1; + } + ... +``` + +The content inside the naming service `file://server_list` is: + +``` +0.0.0.0:8004 0/3 # The first partition of the three +0.0.0.0:8004 1/3 # and so forth +0.0.0.0:8004 2/3 +``` + +All 3 partitions are put on the server on port 8004, so the client begins to send requests to 8004 once started. + +``` +$ ./echo_client +TRACE: 09-06 10:51:10: * 0 src/brpc/policy/file_naming_service.cpp:83] Got 3 unique addresses from `server_list' +TRACE: 09-06 10:51:10: * 0 src/brpc/socket.cpp:779] Connected to 0.0.0.0:8004 via fd=3 SocketId=0 self_port=46544 +TRACE: 09-06 10:51:11: * 0 client.cpp:226] Sending EchoRequest at qps=132472 latency=371 +TRACE: 09-06 10:51:12: * 0 client.cpp:226] Sending EchoRequest at qps=132658 latency=370 +TRACE: 09-06 10:51:13: * 0 client.cpp:226] Sending EchoRequest at qps=133208 latency=369 +``` + +At the same time, the server on 8004 received tripled traffic due to the 3 partitions. + +``` +TRACE: 09-06 10:51:11: * 0 server.cpp:192] S[0]=398866 S[1]=0 S[2]=0 [total=398866] +TRACE: 09-06 10:51:12: * 0 server.cpp:192] S[0]=398117 S[1]=0 S[2]=0 [total=398117] +TRACE: 09-06 10:51:13: * 0 server.cpp:192] S[0]=398873 S[1]=0 S[2]=0 [total=398873] +``` + +Add new 4 partitions on the server on port 8005. + +``` +0.0.0.0:8004 0/3 +0.0.0.0:8004 1/3 +0.0.0.0:8004 2/3 + +0.0.0.0:8005 0/4 +0.0.0.0:8005 1/4 +0.0.0.0:8005 2/4 +0.0.0.0:8005 3/4 +``` + +Notice how summaries change. The client is aware of the modification to `server_list` and reloads it, but the QPS hardly changes. + +``` +TRACE: 09-06 10:57:10: * 0 src/brpc/policy/file_naming_service.cpp:83] Got 7 unique addresses from `server_list' +TRACE: 09-06 10:57:10: * 0 src/brpc/socket.cpp:779] Connected to 0.0.0.0:8005 via fd=7 SocketId=768 self_port=39171 +TRACE: 09-06 10:57:11: * 0 client.cpp:226] Sending EchoRequest at qps=135346 latency=363 +TRACE: 09-06 10:57:12: * 0 client.cpp:226] Sending EchoRequest at qps=134201 latency=366 +TRACE: 09-06 10:57:13: * 0 client.cpp:226] Sending EchoRequest at qps=137627 latency=356 +TRACE: 09-06 10:57:14: * 0 client.cpp:226] Sending EchoRequest at qps=136775 latency=359 +TRACE: 09-06 10:57:15: * 0 client.cpp:226] Sending EchoRequest at qps=139043 latency=353 +``` + +The server-side summary changes more obviously. The server on port 8005 has received requests and the proportion between traffic to 8004 and 8005 is roughly 3:4. + +``` +TRACE: 09-06 10:57:09: * 0 server.cpp:192] S[0]=398597 S[1]=0 S[2]=0 [total=398597] +TRACE: 09-06 10:57:10: * 0 server.cpp:192] S[0]=392839 S[1]=0 S[2]=0 [total=392839] +TRACE: 09-06 10:57:11: * 0 server.cpp:192] S[0]=334704 S[1]=83219 S[2]=0 [total=417923] +TRACE: 09-06 10:57:12: * 0 server.cpp:192] S[0]=206215 S[1]=273873 S[2]=0 [total=480088] +TRACE: 09-06 10:57:13: * 0 server.cpp:192] S[0]=204520 S[1]=270483 S[2]=0 [total=475003] +TRACE: 09-06 10:57:14: * 0 server.cpp:192] S[0]=207055 S[1]=273725 S[2]=0 [total=480780] +TRACE: 09-06 10:57:15: * 0 server.cpp:192] S[0]=208453 S[1]=276803 S[2]=0 [total=485256] +``` + +The traffic proportion between 8004 and 8005 is 3:4, considering that each RPC needs 3 calls to 8004 or 4 calls to 8005, the client issues requests to both partitioning methods in 1:1 manner, which depends on capacities calculated recursively: + +- The capacity of a regular `Channel` is sum of capacities of servers that it addresses. Capacity of a server is 1 by default if the naming services does not configure weights. +- The capacity of `ParallelChannel` or `PartitionChannel` is the minimum of all its sub channel's. +- The capacity of `SelectiveChannel` is the sum of all its sub channel's. +- The capacity of `DynamicPartitionChannel` is the sum of all its sub `PartitionChannel`'s. + +In this example, capacities of the 3-partition method and the 4-partition method are both 1, since the 3 partitions are all on the server on 8004 and the 4 partitions are all on the server on 8005. + +Add the server on 8006 to the 4-partition method: + +``` +0.0.0.0:8004 0/3 +0.0.0.0:8004 1/3 +0.0.0.0:8004 2/3 + +0.0.0.0:8005 0/4 +0.0.0.0:8005 1/4 +0.0.0.0:8005 2/4 +0.0.0.0:8005 3/4 + +0.0.0.0:8006 0/4 +0.0.0.0:8006 1/4 +0.0.0.0:8006 2/4 +0.0.0.0:8006 3/4 +``` + +The client still hardly changes. + +``` +TRACE: 09-06 11:11:51: * 0 src/brpc/policy/file_naming_service.cpp:83] Got 11 unique addresses from `server_list' +TRACE: 09-06 11:11:51: * 0 src/brpc/socket.cpp:779] Connected to 0.0.0.0:8006 via fd=8 SocketId=1280 self_port=40759 +TRACE: 09-06 11:11:51: * 0 client.cpp:226] Sending EchoRequest at qps=131799 latency=372 +TRACE: 09-06 11:11:52: * 0 client.cpp:226] Sending EchoRequest at qps=136217 latency=361 +TRACE: 09-06 11:11:53: * 0 client.cpp:226] Sending EchoRequest at qps=133531 latency=368 +TRACE: 09-06 11:11:54: * 0 client.cpp:226] Sending EchoRequest at qps=136072 latency=361 +``` + +Notice the traffic on 8006 at the server side. The capacity of the 3-partition method is still 1 while capacity of the 4-partition method increases to 2 due to the addition of the server on 8006, thus the overall proportion between the methods becomes 3:8. Each partition inside the 4-partition method has 2 instances on 8005 and 8006 respectively, between which the round-robin load balancing is applied to split the traffic. As a result, the traffic proportion between the 3 servers becomes 3:4:4. + +``` +TRACE: 09-06 11:11:51: * 0 server.cpp:192] S[0]=199625 S[1]=263226 S[2]=0 [total=462851] +TRACE: 09-06 11:11:52: * 0 server.cpp:192] S[0]=143248 S[1]=190717 S[2]=159756 [total=493721] +TRACE: 09-06 11:11:53: * 0 server.cpp:192] S[0]=133003 S[1]=178328 S[2]=178325 [total=489656] +TRACE: 09-06 11:11:54: * 0 server.cpp:192] S[0]=135534 S[1]=180386 S[2]=180333 [total=496253] +``` + +See what happens if one partition in the 3-partition method is removed: (You can comment one line in file://server_list by #) + +``` + 0.0.0.0:8004 0/3 + 0.0.0.0:8004 1/3 +#0.0.0.0:8004 2/3 + + 0.0.0.0:8005 0/4 + 0.0.0.0:8005 1/4 + 0.0.0.0:8005 2/4 + 0.0.0.0:8005 3/4 + + 0.0.0.0:8006 0/4 + 0.0.0.0:8006 1/4 + 0.0.0.0:8006 2/4 + 0.0.0.0:8006 3/4 +``` + +The client senses the change in `server_list`: + +``` +TRACE: 09-06 11:17:47: * 0 src/brpc/policy/file_naming_service.cpp:83] Got 10 unique addresses from `server_list' +TRACE: 09-06 11:17:47: * 0 client.cpp:226] Sending EchoRequest at qps=131653 latency=373 +TRACE: 09-06 11:17:48: * 0 client.cpp:226] Sending EchoRequest at qps=120560 latency=407 +TRACE: 09-06 11:17:49: * 0 client.cpp:226] Sending EchoRequest at qps=124100 latency=395 +TRACE: 09-06 11:17:50: * 0 client.cpp:226] Sending EchoRequest at qps=123743 latency=397 +``` + +The traffic on 8004 drops to zero quickly at the server side. The reason is that the 3-partition method is not complete anymore once the last 2/3 partition has been removed. The capacity becomes zero and no requests are sent to the server on 8004 anymore. + +``` +TRACE: 09-06 11:17:47: * 0 server.cpp:192] S[0]=130864 S[1]=174499 S[2]=174548 [total=479911] +TRACE: 09-06 11:17:48: * 0 server.cpp:192] S[0]=20063 S[1]=230027 S[2]=230098 [total=480188] +TRACE: 09-06 11:17:49: * 0 server.cpp:192] S[0]=0 S[1]=245961 S[2]=245888 [total=491849] +TRACE: 09-06 11:17:50: * 0 server.cpp:192] S[0]=0 S[1]=250198 S[2]=250150 [total=500348] +``` + +In real online environments, we gradually increase the number of instances on the 4-partition method and removes instances on the 3-partition method. `DynamicParititonChannel` divides the traffic based on capacities of all partitions dynamically. When capacity of the 3-partition method drops to 0, we've smoothly migrated all servers from 3 partitions to 4 partitions without changing the client-side code. diff --git a/docs/en/connections.md b/docs/en/connections.md new file mode 100644 index 0000000..24ab0c6 --- /dev/null +++ b/docs/en/connections.md @@ -0,0 +1,7 @@ +# connections + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/connections.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/consistent_hashing.md b/docs/en/consistent_hashing.md new file mode 100644 index 0000000..3fff6ce --- /dev/null +++ b/docs/en/consistent_hashing.md @@ -0,0 +1,7 @@ +# consistent hashing + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/consistent_hashing.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/contention_profiler.md b/docs/en/contention_profiler.md new file mode 100644 index 0000000..a393b2b --- /dev/null +++ b/docs/en/contention_profiler.md @@ -0,0 +1,7 @@ +# contention profiler + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/contention_profiler.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/coroutine.md b/docs/en/coroutine.md new file mode 100644 index 0000000..a53414c --- /dev/null +++ b/docs/en/coroutine.md @@ -0,0 +1,7 @@ +# coroutine + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/coroutine.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/couchbase_example.md b/docs/en/couchbase_example.md new file mode 100644 index 0000000..6748580 --- /dev/null +++ b/docs/en/couchbase_example.md @@ -0,0 +1,1209 @@ +## Couchbase bRPC Binary Protocol Integration + +This document explains the implementation of Couchbase Binary Protocol support added to bRPC, and the available high-level operations, collection support, SSL authentication, and how to run the provided example client against either a local Couchbase Server cluster or a Couchbase Capella (cloud) deployment. However, the couchbase binary protocol implementation in bRPC currently do not have fine-grained optimizations which has been already done in the couchbase-cxx-client SDK also having query support, better error handling and much more optimized/reliable operations. So, we also added the support of couchbase using couchbase-cxx-SDK in bRPC and is available at [Couchbaselabs-cb-brpc](https://github.com/couchbaselabs/cb_brpc/tree/couchbase_sdk_brpc). + +--- +### 1. Overview + +The integration provides high-level APIs for communicating with Couchbase Server using its Binary Protocol, using the high-level `CouchbaseOperations` class which provides a simplified interface. + +The core pieces are: +* `src/brpc/policy/couchbase_protocol.[h|cpp]` – framing + parse loop for binary responses, and request serialization. +* `src/brpc/couchbase.[h|cpp]` – high-level `CouchbaseOperations` class with request (`CouchbaseRequest`) and response (`CouchbaseResponse`) builders, parsers and error-handlers. +* `example/couchbase_c++/couchbase_client.cpp` – an end‑to‑end example using the high-level API for authentication, bucket selection, CRUD operations, and collection‑scoped operations. +* `example/couchbase_c++/multithreaded_couchbase_client.cpp` – a multithreaded example where an instance of `CouchbaseOperations` is shared across the threads operating on same bucket. An another block of code where multiple threads have their own `CouchbaseOperations` instance as the threads operate on different buckets. +* `example/couchbase_c++/traditional_brpc_couchbase_client.cpp` – demonstrates the traditional bRPC approach with manual channel, controller, and request/response management for advanced users who need fine-grained control. + +Design goals: +* **SSL Support**: Built-in SSL/TLS support for secure connections to Couchbase Capella. +* **Per-instance Authentication**: Each `CouchbaseOperations` object maintains its own authenticated session if each instance connects to a different bucket, when multiple instances connect/operate on the same bucket then a single TCP socket is shared for these `CouchbaseOperations` instances because separate `connection_groups` are created on the basis of `server_name+bucket`. +* **Collection Support**: Native support for collection-scoped operations. +* Keep wire structs identical to the binary protocol (24‑byte header, network order numeric fields). +* Future extensions for advanced features. + +--- +### 2. Features + +| Category | Supported Operations | Notes | +|----------|----------------------|-------| +| **High-Level API** | `CouchbaseOperations` class | **Recommended**: Simple methods returning `Result` struct | +| **Traditional API** | Manual channel/controller management | **Advanced**: Direct bRPC access for custom configurations | +| **SSL/TLS Support** | Built-in SSL encryption | **Required** for Couchbase Capella, optional for local clusters | +| Authentication | SASL `PLAIN` with/without SSL | `authenticate()` for non-SSL, `authenticateSSL()` for SSL connections | +| Bucket selection | Integrated with authentication | Bucket specified during authentication; `selectBucket()` also available separately | +| Basic KV | `add()`, `upsert()`, `delete_()`, `get()` | Clean API with `Result` struct error handling; | +| **Pipeline Operations** | `beginPipeline()`, `pipelineRequest()`, `executePipeline()` | **NEW**: Batch multiple operations in single network call for improved performance | +| Collections | Collection-scoped CRUD operations | Pass collection name as optional parameter (defaults to "_default") | +| Error Handling | `Result.success` + `Result.error_message` + `Result.status_code` | Human-readable error messages with Couchbase status codes | + +- **Simplified**: No need to manage channels, controllers, or response parsing +- **Flexible Threading**: Share instances across threads for same bucket/server, or create separate instances for different buckets/servers +- **Error Handling**: Simple boolean success with descriptive error messages and error codes +- **SSL Built-in**: SSL handling for secure connections + + +--- +### 3. Binary Protocol Mapping + +Couchbase binary protcol header, for original documentation [click here](https://github.com/couchbase/kv_engine/blob/master/docs/BinaryProtocol.md). The following header format has been used to connect with the couchbase servers. +``` +Byte/ 0 | 1 | 2 | 3 | + / | | | | + |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| + +---------------+---------------+---------------+---------------+ + 0| Magic | Opcode | Key length | + +---------------+---------------+---------------+---------------+ + 4| Extras length | Data type | vbucket id | + +---------------+---------------+---------------+---------------+ + 8| Total body length | + +---------------+---------------+---------------+---------------+ + 12| Opaque | + +---------------+---------------+---------------+---------------+ + 16| CAS | + | | + +---------------+---------------+---------------+---------------+ + Total 24 bytes +``` + +Overall packet structure:- +``` + Byte/ 0 | 1 | 2 | 3 | + / | | | | + |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| + +---------------+---------------+---------------+---------------+ + 0| HEADER | + | | + | | + | | + +---------------+---------------+---------------+---------------+ + 24| COMMAND-SPECIFIC EXTRAS (as needed) | + | (note length in the extras length header field) | + +---------------+---------------+---------------+---------------+ + m| Key (as needed) | + | (note length in key length header field) | + +---------------+---------------+---------------+---------------+ + n| Value (as needed) | + | (note length is total body length header field, minus | + | sum of the extras and key length body fields) | + +---------------+---------------+---------------+---------------+ + Total 24 + x bytes (24 byte header, and x byte body) +``` + +--- +### 4. High-Level API (`CouchbaseOperations`) + +**Approach**: Use the `CouchbaseOperations` class for operations. Instances can be shared across threads when connecting to the same bucket, or you can create separate instances in multi-threading where each thread is connecting to a separate bucket. + +#### Basic Usage: +```cpp +#include + +brpc::CouchbaseOperations couchbase_ops; + +// 1. Authenticate with bucket selection (REQUIRED for each instance) +brpc::CouchbaseOperations::Result auth_result = couchbase_ops.authenticate( + username, password, server_address, bucket_name); +if (!auth_result.success) { + LOG(ERROR) << "Auth failed: " << auth_result.error_message; + return -1; +} + +// 2. Perform operations (bucket is already selected during authentication) +brpc::CouchbaseOperations::Result add_result = couchbase_ops.add("user::123", json_value); +if (add_result.success) { + std::cout << "Document added successfully!" << std::endl; +} else { + std::cout << "Add failed: " << add_result.error_message << std::endl; +} + +// Optional: Switch to a different bucket (if needed) +// brpc::CouchbaseOperations::Result bucket_result = couchbase_ops.selectBucket("another_bucket"); +``` + +#### SSL Authentication (Essential for Couchbase Capella): +To know how to download the security certificate [click here](https://docs.couchbase.com/cloud/security/security-certificates.html). +```cpp +// For Couchbase Capella (cloud) - SSL is REQUIRED +brpc::CouchbaseOperations::Result auth_result = couchbase_ops.authenticateSSL( + username, + password, + "cluster.cloud.couchbase.com:11207", // SSL port + bucket_name, // bucket name + "path/to/certificate.txt" // certificate path(can be downloaded from capella UI) +); +``` + +#### Collection Operations: +```cpp +// Default collection +auto result = couchbase_ops.get("doc::1"); + +// Specific collection +auto result = couchbase_ops.get("doc::1", "my_collection"); +auto add_result = couchbase_ops.add("doc::2", value, "my_collection"); +``` + +#### Pipeline Operations (Performance Optimization): +The pipeline API allows batching multiple operations into a single network call, significantly improving performance for bulk operations: + +#### How Pipeline Operations Work + +1. **Begin Pipeline**: Start a new pipeline session +2. **Add Operations**: Queue multiple operations without executing them +3. **Execute Pipeline**: Send all operations in a single network call +4. **Process Results**: Handle results in the same order as requests + +#### Pipeline API Methods + +| Method | Description | Usage | +|--------|-------------|-------| +| `beginPipeline()` | Start a new pipeline session | Must call before adding operations | +| `pipelineRequest(op_type, key, value, collection)` | Add operation to pipeline | Supports all CRUD operations | +| `executePipeline()` | Execute all queued operations | Returns `vector` in request order | +| `clearPipeline()` | Clear pipeline without executing | Use for cleanup on errors | +| `isPipelineActive()` | Check if pipeline is active | Returns `bool` | +| `getPipelineSize()` | Get number of queued operations | Returns `size_t` | + +```cpp +// Begin a new pipeline +if (!couchbase_ops.beginPipeline()) { + LOG(ERROR) << "Failed to begin pipeline"; + return -1; +} + +// Add multiple operations to the pipeline (not executed yet) +bool success = true; +success &= couchbase_ops.pipelineRequest(brpc::CouchbaseOperations::ADD, "key1", "value1"); +success &= couchbase_ops.pipelineRequest(brpc::CouchbaseOperations::UPSERT, "key2", "value2"); +success &= couchbase_ops.pipelineRequest(brpc::CouchbaseOperations::GET, "key1"); +success &= couchbase_ops.pipelineRequest(brpc::CouchbaseOperations::DELETE, "key3"); + +if (!success) { + couchbase_ops.clearPipeline(); // Clean up on error + return -1; +} + +// Execute all operations in a single network call +std::vector results = couchbase_ops.executePipeline(); + +// Process results in the same order as requests +for (size_t i = 0; i < results.size(); ++i) { + if (results[i].success) { + std::cout << "Operation " << i << " succeeded" << std::endl; + if (!results[i].value.empty()) { + std::cout << "Value: " << results[i].value << std::endl; + } + } else { + std::cout << "Operation " << i << " failed: " << results[i].error_message << std::endl; + } +} +``` + +**Pipeline with Collections**: +```cpp +// Pipeline operations can also use collections +couchbase_ops.beginPipeline(); +couchbase_ops.pipelineRequest(brpc::CouchbaseOperations::ADD, "doc1", "value1", "my_collection"); +couchbase_ops.pipelineRequest(brpc::CouchbaseOperations::GET, "doc1", "", "my_collection"); +auto results = couchbase_ops.executePipeline(); +``` + +#### Error Handling Pattern: +```cpp +brpc::CouchbaseOperations::Result result = couchbase_ops.someOperation(...); +if (!result.success) { + // Handle error + LOG(ERROR) << "Operation failed: " << result.error_message; + LOG(ERROR) << "Error Code: " << result.status_code; // Couchbase status code +} else { + // Use result.value if applicable (for Get operations) + std::cout << "Retrieved value: " << result.value << std::endl; +} +``` + +--- +### 5. Traditional bRPC Couchbase Client (`traditional_brpc_couchbase_client.cpp`) + +For developers who need fine-grained control over the bRPC framework or want to understand the low-level implementation, we provide a traditional bRPC client example. This approach requires manual management of channels, controllers, and response parsing. + +**When to use Traditional API:** +- Advanced bRPC users who need custom channel configurations +- Fine-grained control over connection pooling and retry logic +- Direct access to underlying bRPC controller for debugging +- Learning the internal workings of the high-level API + +**When to use High-Level API (Recommended):** +- Standard CRUD operations and authentication +- Simpler error handling and cleaner code +- Collection based operations with minimal boilerplate +- Pipeline operations for batch processing while also available in traditional approach it is easier to do using High-Level API. + +#### Traditional Client Example Walkthrough + +The traditional client (`example/couchbase_c++/traditional_brpc_couchbase_client.cpp`) demonstrates the low-level bRPC approach: + +**1. Channel Setup and Configuration** +```cpp +brpc::Channel channel; +brpc::ChannelOptions options; +options.protocol = brpc::PROTOCOL_COUCHBASE; // Set Couchbase protocol +options.connection_type = "single"; // Single persistent connection +options.timeout_ms = 1000; // 1 second timeout +options.max_retry = 3; // Retry up to 3 times + +if (channel.Init("localhost:11210", &options) != 0) { + LOG(ERROR) << "Failed to initialize channel"; + return -1; +} +``` + +**2. Authentication with Manual Request/Response Handling** +```cpp +brpc::Controller cntl; +brpc::CouchbaseOperations::CouchbaseRequest req; +brpc::CouchbaseOperations::CouchbaseResponse res; +uint64_t cas; + +// Build authentication request +req.authenticateRequest("Administrator", "password"); + +// Execute the request +channel.CallMethod(NULL, &cntl, &req, &res, NULL); + +// Check controller status +if (cntl.Failed()) { + LOG(ERROR) << "Unable to authenticate: " << cntl.ErrorText(); + return -1; +} + +// Parse response - must call popHello() and popAuthenticate() in order +if (res.popHello(&cas) && res.popAuthenticate(&cas)) { + std::cout << "Authentication Successful" << std::endl; +} else { + std::cout << "Authentication Failed with status code: " + << std::hex << res._status_code << std::endl; + return -1; +} +``` + +**3. Bucket Selection** +```cpp +// IMPORTANT: Reset controller and clear request/response before each operation +cntl.Reset(); +req.Clear(); +res.Clear(); + +// Build bucket selection request +req.selectBucketRequest("testing"); + +// Execute the request +channel.CallMethod(NULL, &cntl, &req, &res, NULL); + +if (cntl.Failed()) { + LOG(ERROR) << "Unable to select bucket: " << cntl.ErrorText(); + return -1; +} + +// Parse response - status_code only updated AFTER calling pop function +if (res.popSelectBucket(&cas)) { + std::cout << "Bucket Selection Successful" << std::endl; +} else { + std::cout << "Bucket Selection Failed with status code: " + << std::hex << res._status_code << std::endl; + std::cout << "Error Message: " << res.lastError() << std::endl; + return -1; +} +``` + +**4. ADD Operation (Create Document)** +```cpp +// Reset for new operation +cntl.Reset(); +req.Clear(); +res.Clear(); + +// Build ADD request +req.addRequest( + "sample_key", // key + R"({"name": "John Doe", "age": 30, "email": "john@example.com"})", // value + 0, // flags + 0, // exptime (0 = no expiration) + 0 // cas (0 for new document) +); + +// Execute the request +channel.CallMethod(NULL, &cntl, &req, &res, NULL); + +if (cntl.Failed()) { + LOG(ERROR) << "Unable to add key-value: " << cntl.ErrorText(); + return -1; +} + +// Parse response +if (res.popAdd(&cas)) { + std::cout << "Key-Value Addition Successful" << std::endl; +} else { + std::cout << "Key-Value Addition Failed with status code: " + << std::hex << res._status_code << std::endl; + std::cout << "Error Message: " << res.lastError() << std::endl; + return -1; +} +``` + +**5. GET Operation (Retrieve Document)** +```cpp +// Reset for new operation +cntl.Reset(); +req.Clear(); +res.Clear(); + +// Build GET request +req.getRequest("sample_key"); + +// Execute the request +channel.CallMethod(NULL, &cntl, &req, &res, NULL); + +if (cntl.Failed()) { + LOG(ERROR) << "Unable to get value for key: " << cntl.ErrorText(); + return -1; +} + +// Parse response - GET returns value and flags +std::string value; +uint32_t flags; +if (res.popGet(&value, &flags, &cas)) { + std::cout << "Key-Value Retrieval Successful" << std::endl; + std::cout << "Retrieved Value: " << value << std::endl; +} else { + std::cout << "Key-Value Retrieval Failed with status code: " + << std::hex << res._status_code << std::endl; + std::cout << "Error Message: " << res.lastError() << std::endl; + return -1; +} +``` + +**6. DELETE Operation (Remove Document)** +```cpp +// Reset for new operation +cntl.Reset(); +req.Clear(); +res.Clear(); + +// Build DELETE request +req.deleteRequest("sample_key"); + +// Execute the request +channel.CallMethod(NULL, &cntl, &req, &res, NULL); + +if (cntl.Failed()) { + LOG(ERROR) << "Unable to delete key-value: " << cntl.ErrorText(); + return -1; +} + +// Parse response +if (res.popDelete()) { + std::cout << "Key-Value Deletion Successful" << std::endl; +} else { + std::cout << "Key-Value Deletion Failed with status code: " + << std::hex << res._status_code << std::endl; + std::cout << "Error Message: " << res.lastError() << std::endl; + return -1; +} +``` + +#### Key Differences: Traditional vs High-Level API + +| Aspect | Traditional API | High-Level API | +|--------|----------------|----------------| +| **Setup** | Manual channel, controller, request/response management | Single `CouchbaseOperations` instance | +| **Error Handling** | Check both `cntl.Failed()` and response status | Simple `Result.success` boolean | +| **Resource Management** | Must call `cntl.Reset()`, `req.Clear()`, `res.Clear()` | Automatic | +| **Response Parsing** | Manual `pop*()` calls with CAS handling | Transparent | +| **Code Verbosity** | ~15-20 lines per operation | ~2-3 lines per operation | +| **Collections** | Manual collection ID retrieval and management | Automatic with collection name parameter | +| **Pipeline Operations** | Complex manual request building | Simple `beginPipeline()`, `pipelineRequest()`, `executePipeline()` | +| **SSL Support** | Manual SSL configuration in channel options | Built-in `authenticateSSL()` method | +| **Threading** | Manual connection pooling management | Automatic connection group management | + +--- +### 6. Request/Response Classes (`CouchbaseRequest`/`CouchbaseResponse`) + +These classes are public in `CouchbaseOperations` and can be used for advanced bRPC programs. The high-level API uses these classes internally, and the traditional client example demonstrates their direct usage. They are responsible for building the request that needs to be sent and received over the channel. + + +#### Response Parsing: +Each `pop*` method consumes the front of the internal response buffer, validating: +1. Header present. +2. Opcode matches expected operation. +3. Status == success (otherwise `_err` filled with formatted message). +4. Body length sufficient. + +--- +### 7. Example Client Walkthrough + +#### Single-Threaded Example (`couchbase_client.cpp`) +Uses the **high-level `CouchbaseOperations` API**: + +1. **Create `CouchbaseOperations` instance** - can create more than one per thread. +```cpp +brpc::CouchbaseOperations couchbase_ops; +``` + +2. **Prompt for credentials** - username/password for authentication. +```cpp +std::string username = "Administrator"; +std::string password = "password"; +while (username.empty() || password.empty()) { + std::cout << "Enter Couchbase username: "; + std::cin >> username; + std::cout << "Enter Couchbase password: "; + std::cin >> password; +} +``` + +3. **Authentication with bucket selection** - `authenticate()` for local, `authenticateSSL()` for Capella. + +**Function Signatures:** +```cpp +// Non-SSL authentication +Result authenticate(const string& username, // Couchbase username + const string& password, // Couchbase password + const string& server_address, // Server host:port (e.g., "localhost:11210") + const string& bucket_name); // Target bucket name + +// SSL authentication +Result authenticateSSL(const string& username, // Couchbase username + const string& password, // Couchbase password + const string& server_address, // Server host:port (e.g., "cluster.cloud.couchbase.com:11207") + const string& bucket_name, // Target bucket name + string path_to_cert); // Path to SSL certificate file +``` + +**Usage Examples:** +```cpp +// For local Couchbase (non-SSL) +brpc::CouchbaseOperations::Result auth_result = + couchbase_ops.authenticate(username, password, FLAGS_server, "testing"); + +// For Couchbase Capella (SSL) +// brpc::CouchbaseOperations::Result auth_result = +// couchbase_ops.authenticateSSL(username, password, "cluster.cloud.couchbase.com:11207", +// "bucket_name", "path/to/cert.txt"); + +if (!auth_result.success) { + LOG(ERROR) << "Authentication failed: " << auth_result.error_message; + return -1; +} +``` + +4. **Basic CRUD operations**: + - Add document (should succeed) + - Try adding same key again (should fail with "key exists") + - Get document (retrieve the added document) + +**Function Signatures:** +```cpp +// ADD operation - creates new document, fails if key exists +Result add(const string& key, // Document key/ID + const string& value, // Document value (JSON string) + string collection_name = "_default"); // Collection name (optional, defaults to "_default") + +// GET operation - retrieves document by key +Result get(const string& key, // Document key/ID to retrieve + string collection_name = "_default"); // Collection name (optional, defaults to "_default") +``` + +**Usage Examples:** +```cpp +std::string add_key = "user::test_brpc_binprot"; +std::string add_value = R"({"name": "John Doe", "age": 30, "email": "john@example.com"})"; + +// First ADD operation (should succeed) +brpc::CouchbaseOperations::Result add_result = couchbase_ops.add(add_key, add_value); +if (add_result.success) { + std::cout << "ADD operation successful" << std::endl; +} else { + std::cout << "ADD operation failed: " << add_result.error_message << std::endl; +} + +// Second ADD operation (should fail - key exists) +brpc::CouchbaseOperations::Result add_result2 = couchbase_ops.add(add_key, add_value); +if (!add_result2.success) { + std::cout << "Second ADD failed as expected: " << add_result2.error_message << std::endl; +} + +// GET operation +brpc::CouchbaseOperations::Result get_result = couchbase_ops.get(add_key); +if (get_result.success) { + std::cout << "GET operation successful" << std::endl; + std::cout << "GET value: " << get_result.value << std::endl; +} +``` + +5. **Multiple document operations** - Add several documents with different keys. +```cpp +std::string item1_key = "binprot_item1"; +std::string item2_key = "binprot_item2"; +std::string item3_key = "binprot_item3"; + +couchbase_ops.add(item1_key, add_value); +couchbase_ops.add(item2_key, add_value); +couchbase_ops.add(item3_key, add_value); +``` + +6. **Upsert operations**: + - Upsert existing document (should update) + - Upsert new document (should create) + - Verify with Get operations + +**Function Signature:** +```cpp +// UPSERT operation - creates new document or updates existing one +Result upsert(const string& key, // Document key/ID + const string& value, // Document value (JSON string) + string collection_name = "_default"); // Collection name (optional, defaults to "_default") +``` + +**Usage Examples:** +```cpp +std::string upsert_key = "upsert_test"; +std::string upsert_value = R"({"operation": "upsert", "version": 1})"; + +// Upsert new document (will create) +brpc::CouchbaseOperations::Result upsert_result = couchbase_ops.upsert(upsert_key, upsert_value); + +// Upsert existing document (will update) +std::string updated_value = R"({"operation": "upsert", "version": 2})"; +brpc::CouchbaseOperations::Result update_result = couchbase_ops.upsert(upsert_key, updated_value); + +// Verify with GET +brpc::CouchbaseOperations::Result check_result = couchbase_ops.get(upsert_key); +``` + +7. **Delete operations**: + - Delete non-existent key (should fail gracefully) + - Delete existing key (should succeed) + +**Function Signature:** +```cpp +// DELETE operation - removes document by key +Result delete_(const string& key, // Document key/ID to delete + string collection_name = "_default"); // Collection name (optional, defaults to "_default") +``` + +**Usage Examples:** +```cpp +// Delete non-existent key +std::string delete_key = "non_existent_key"; +brpc::CouchbaseOperations::Result delete_result = couchbase_ops.delete_(delete_key); +if (!delete_result.success) { + std::cout << "Delete failed as expected: " << delete_result.error_message << std::endl; +} + +// Delete existing key +std::string delete_existing_key = "binprot_item1"; +brpc::CouchbaseOperations::Result delete_existing_result = couchbase_ops.delete_(delete_existing_key); +if (delete_existing_result.success) { + std::cout << "Delete existing key successful" << std::endl; +} +``` + +8. **Collection-scoped operations** - Add/Get/Upsert/Delete in specific collections. + +**Note:** All CRUD operations support an optional collection parameter. When not specified, operations default to the "_default" collection. + +**Usage Examples:** +```cpp +std::string collection_name = "testing_collection"; // Target collection name +std::string coll_key = "collection::doc1"; // Document key +std::string coll_value = R"({"collection_operation": "add", "scope": "custom"})"; // Document value + +// Collection-scoped ADD (key, value, collection_name) +brpc::CouchbaseOperations::Result coll_add_result = + couchbase_ops.add(coll_key, coll_value, collection_name); + +// Collection-scoped GET (key, collection_name) +brpc::CouchbaseOperations::Result coll_get_result = + couchbase_ops.get(coll_key, collection_name); + +// Collection-scoped UPSERT (key, value, collection_name) +brpc::CouchbaseOperations::Result coll_upsert_result = + couchbase_ops.upsert(coll_key, coll_value, collection_name); + +// Collection-scoped DELETE (key, collection_name) +brpc::CouchbaseOperations::Result coll_delete_result = + couchbase_ops.delete_(coll_key, collection_name); +``` + +9. **Pipeline operations demo**: + - Begin pipeline and add multiple operations + - Execute batch operations in single network call + - Process results in order + - Collection-scoped pipeline operations + - Error handling and cleanup + +**Function Signatures:** +```cpp +// Pipeline management functions +bool beginPipeline(); // Start a new pipeline session + +bool pipelineRequest(operation_type op_type, // Operation type (ADD, UPSERT, GET, DELETE, etc.) + const string& key, // Document key/ID + const string& value = "", // Document value (empty for GET/DELETE operations) + string collection_name = "_default"); // Collection name (optional) + +vector executePipeline(); // Execute all queued operations and return results + +bool clearPipeline(); // Clear pipeline without executing (cleanup) + +// Pipeline status functions +bool isPipelineActive() const; // Check if pipeline is active +size_t getPipelineSize() const; // Get number of queued operations +``` + +**Usage Examples:** +```cpp +// Begin pipeline +if (!couchbase_ops.beginPipeline()) { + std::cout << "Failed to begin pipeline" << std::endl; + return -1; +} + +// Add multiple operations to pipeline +std::string pipeline_key1 = "pipeline::doc1"; +std::string pipeline_key2 = "pipeline::doc2"; +std::string pipeline_value1 = R"({"operation": "pipeline_add", "id": 1})"; +std::string pipeline_value2 = R"({"operation": "pipeline_upsert", "id": 2})"; + +bool pipeline_success = true; +// pipelineRequest(operation_type, key, value, collection_name) +pipeline_success &= couchbase_ops.pipelineRequest(brpc::CouchbaseOperations::ADD, pipeline_key1, pipeline_value1); +pipeline_success &= couchbase_ops.pipelineRequest(brpc::CouchbaseOperations::UPSERT, pipeline_key2, pipeline_value2); +pipeline_success &= couchbase_ops.pipelineRequest(brpc::CouchbaseOperations::GET, pipeline_key1); // Empty value for GET + +if (!pipeline_success) { + couchbase_ops.clearPipeline(); // Clean up on error + return -1; +} + +// Execute pipeline - returns results in same order as requests +std::vector pipeline_results = couchbase_ops.executePipeline(); + +// Process results +for (size_t i = 0; i < pipeline_results.size(); ++i) { + if (pipeline_results[i].success) { + std::cout << "Operation " << (i + 1) << " SUCCESS"; + if (!pipeline_results[i].value.empty()) { + std::cout << " - Value: " << pipeline_results[i].value; + } + std::cout << std::endl; + } else { + std::cout << "Operation " << (i + 1) << " FAILED: " + << pipeline_results[i].error_message << std::endl; + } +} + +// Collection-scoped pipeline operations +if (couchbase_ops.beginPipeline()) { + // pipelineRequest(operation_type, key, value, collection_name) + couchbase_ops.pipelineRequest(brpc::CouchbaseOperations::ADD, "coll_pipeline::doc1", + R"({"collection_operation": "pipeline_add", "id": 1})", collection_name); + couchbase_ops.pipelineRequest(brpc::CouchbaseOperations::GET, "coll_pipeline::doc1", "", collection_name); + auto coll_results = couchbase_ops.executePipeline(); +} +``` + +10. **Bucket switching** - Demonstrate changing bucket selection. + +**Function Signature:** +```cpp +// SELECTBUCKET operation - switch to a different bucket on the same server +Result selectBucket(const string& bucket_name); // Target bucket name to switch to +``` + +**Usage Example:** +```cpp +std::string bucket_name = "testing"; +std::cout << "Enter Couchbase bucket name: "; +std::cin >> bucket_name; + +// selectBucket(bucket_name) - switches to the specified bucket +brpc::CouchbaseOperations::Result bucket_result = couchbase_ops.selectBucket(bucket_name); +if (!bucket_result.success) { + LOG(ERROR) << "Bucket selection failed: " << bucket_result.error_message; + return -1; +} else { + std::cout << "Bucket Selection Successful" << std::endl; +} + +// Perform operations on new bucket +performOperations(couchbase_ops); +``` + +#### Multithreaded Example (`multithreaded_couchbase_client.cpp`) +Demonstrates: +- **20 bthreads** (5 threads per bucket across 4 buckets) +- **Multiple threading patterns**: Each thread can create its own instance or share instances +- **Concurrent operations** across multiple buckets and collections +- **Thread-safe statistics tracking** for operations +- **Collection-scoped operations** across threads + +**Global Configuration**: +```cpp +const int NUM_THREADS = 20; +const int THREADS_PER_BUCKET = 5; + +// Global config structure +struct { + std::string username = "Administrator"; + std::string password = "password"; + std::vector bucket_names = {"t0", "t1", "t2", "t3"}; +} g_config; + +// Thread statistics tracking +struct ThreadStats { + std::atomic operations_attempted{0}; + std::atomic operations_successful{0}; + std::atomic operations_failed{0}; +}; + +struct GlobalStats { + ThreadStats total; + std::vector per_thread_stats; + GlobalStats() : per_thread_stats(NUM_THREADS) {} +} g_stats; +``` + +**Thread Worker Function**: +```cpp +struct ThreadArgs { + int thread_id; + int bucket_id; + std::string bucket_name; + ThreadStats* stats; +}; + +void* thread_worker(void* arg) { + ThreadArgs* args = static_cast(arg); + + // Create CouchbaseOperations instance for this thread + brpc::CouchbaseOperations couchbase_ops; + + // Authentication with assigned bucket + brpc::CouchbaseOperations::Result auth_result = couchbase_ops.authenticate( + g_config.username, g_config.password, "127.0.0.1:11210", args->bucket_name); + + // For SSL authentication: + // brpc::CouchbaseOperations::Result auth_result = couchbase_ops.authenticateSSL( + // g_config.username, g_config.password, "127.0.0.1:11207", args->bucket_name, "/path/to/cert.txt"); + + if (!auth_result.success) { + std::cout << "Thread " << args->thread_id << ": Auth failed - " + << auth_result.error_message << std::endl; + return NULL; + } + + // Perform CRUD operations on default collection + std::string base_key = "thread_" + std::to_string(args->thread_id); + perform_crud_operations_default(couchbase_ops, base_key, args->stats); + + // Perform collection-scoped operations + perform_crud_operations_collection(couchbase_ops, base_key, "my_collection", args->stats); + + return NULL; +} +``` + +**CRUD Operations Functions**: +```cpp +void perform_crud_operations_default(brpc::CouchbaseOperations& couchbase_ops, + const std::string& base_key, ThreadStats* stats) { + std::string key = base_key + "_default"; + std::string value = R"({"thread_id": %d, "collection": "default"})"; + + stats->operations_attempted++; + + // UPSERT operation + brpc::CouchbaseOperations::Result result = couchbase_ops.upsert(key, value); + if (result.success) { + stats->operations_successful++; + } else { + stats->operations_failed++; + } + + // GET operation + stats->operations_attempted++; + result = couchbase_ops.get(key); + if (result.success) { + stats->operations_successful++; + } else { + stats->operations_failed++; + } + + // DELETE operation + stats->operations_attempted++; + result = couchbase_ops.delete_(key); + if (result.success) { + stats->operations_successful++; + } else { + stats->operations_failed++; + } +} + +void perform_crud_operations_collection(brpc::CouchbaseOperations& couchbase_ops, + const std::string& base_key, + const std::string& collection_name, + ThreadStats* stats) { + std::string key = base_key + "_collection"; + std::string value = R"({"thread_id": %d, "collection": ")" + collection_name + R"("})"; + + // Collection-scoped operations + stats->operations_attempted++; + brpc::CouchbaseOperations::Result result = couchbase_ops.upsert(key, value, collection_name); + if (result.success) { + stats->operations_successful++; + } else { + stats->operations_failed++; + } + + stats->operations_attempted++; + result = couchbase_ops.get(key, collection_name); + if (result.success) { + stats->operations_successful++; + } else { + stats->operations_failed++; + } +} +``` + +**Main Function - Thread Management**: +```cpp +int main(int argc, char* argv[]) { + std::vector threads(NUM_THREADS); + std::vector thread_args(NUM_THREADS); + + // Create threads - 5 threads per bucket across 4 buckets + for (int i = 0; i < NUM_THREADS; ++i) { + thread_args[i].thread_id = i; + thread_args[i].bucket_id = i / THREADS_PER_BUCKET; + thread_args[i].bucket_name = g_config.bucket_names[thread_args[i].bucket_id]; + thread_args[i].stats = &g_stats.per_thread_stats[i]; + + if (bthread_start_background(&threads[i], NULL, thread_worker, &thread_args[i]) != 0) { + LOG(ERROR) << "Failed to create thread " << i; + return -1; + } + } + + // Wait for all threads to complete + for (int i = 0; i < NUM_THREADS; ++i) { + bthread_join(threads[i], NULL); + } + + // Aggregate and display statistics + g_stats.aggregate_stats(); + std::cout << "Total operations attempted: " << g_stats.total.operations_attempted.load() << std::endl; + std::cout << "Total operations successful: " << g_stats.total.operations_successful.load() << std::endl; + std::cout << "Total operations failed: " << g_stats.total.operations_failed.load() << std::endl; + + return 0; +} +``` + +**Alternative Pattern - Shared Instance Demo**: +```cpp +// Shared instance worker function +void* shared_object_thread_worker(void *arg) { + ThreadArgs* shared_args = static_cast(arg); + brpc::CouchbaseOperations* shared_couchbase_ops = shared_args->couchbase_ops; + + // Perform operations - 10 times on default collection, 10 times on col1 collection + for (int i = 0; i < 10; ++i) { + std::string base_key = butil::string_printf("shared_thread_op_%d_thread_id_%d", + i, shared_args->thread_id); + + // CRUD operations on default collection using shared instance + perform_crud_operations_default(*shared_couchbase_ops, base_key, shared_args->stats); + + // CRUD operations on col1 collection using shared instance + perform_crud_operations_col1(*shared_couchbase_ops, base_key, shared_args->stats); + + // Small delay between operations + bthread_usleep(10000); // 10ms + } + return NULL; +} + +// Main function demonstrates shared instance pattern +int main_shared_demo() { + // Create a shared CouchbaseOperations instance + brpc::CouchbaseOperations shared_couchbase_ops; + brpc::CouchbaseOperations::Result result; + + // Authenticate shared instance + result = shared_couchbase_ops.authenticate( + g_config.username, g_config.password, "127.0.0.1:11210", "t0"); + + if (result.success) { + std::cout << GREEN << "Shared CouchbaseOperations instance authenticated successfully!" + << RESET << std::endl; + } else { + std::cout << RED << "Shared CouchbaseOperations instance authentication failed: " + << result.error_message << RESET << std::endl; + return -1; + } + + // Configure all threads to use the shared instance + std::vector threads(NUM_THREADS); + std::vector args(NUM_THREADS); + + for (int i = 0; i < NUM_THREADS; ++i) { + args[i].thread_id = i; + args[i].couchbase_ops = &shared_couchbase_ops; // Point to shared instance + args[i].bucket_id = 0; + args[i].bucket_name = "t0"; // All threads use same bucket via shared instance + args[i].stats = &g_stats.per_thread_stats[i]; + } + + // Start all threads using shared instance + for (int i = 0; i < NUM_THREADS; ++i) { + if (bthread_start_background(&threads[i], NULL, shared_object_thread_worker, &args[i]) != 0) { + std::cout << RED << "Failed to create shared object thread " << i << RESET << std::endl; + return -1; + } + } + + // Wait for all threads to complete + for (int i = 0; i < NUM_THREADS; ++i) { + bthread_join(threads[i], NULL); + } + + std::cout << GREEN << "All shared object threads completed!" << RESET << std::endl; + return 0; +} +``` + +Key features: +- Demonstrates different connection patterns for multithreaded scenarios +- Shows concurrent access to different buckets and collections +- Proper resource management in multithreaded environments +- Statistics tracking across all threads +- Both separate instance and shared instance patterns + +--- +### 8. Building and Running the Examples + +#### Build both examples: +```bash +cd example/couchbase_c++/ +make +``` + +#### Run Single-Threaded Example (High-Level API): +```bash +./couchbase_client +``` + +#### Run Multithreaded Example (High-Level API): +```bash +./multithreaded_couchbase_client +``` + +#### Run Traditional bRPC Client (Low-Level API): +```bash +./traditional_brpc_couchbase_client +``` + +--- +### 9. Setting Up Couchbase + +#### A. Local Install (Non‑Docker) +Download from: https://www.couchbase.com/downloads/ (Community or Enterprise) and Install. + +Setup steps: +- Open http://localhost:8091 in a browser and follow setup wizard +- Set admin credentials (Administrator / password) +- Accept terms, choose services (Data, Query, Index at minimum) +- Initialize cluster +- Create a bucket (e.g. travel-sample or custom) + +Create collections (7.0+): +- Navigate: Buckets → Your Bucket → Scopes & Collections +- Add a Scope (optional) or use `_default` +- Add a Collection (e.g. `testing_collection`) + +**SSL Configuration (Optional for Local)**: +```cpp +// Local without SSL - authenticate with bucket selection +auto result = couchbase_ops.authenticate(username, password, "localhost:11210", bucket_name); +``` + +#### B. Couchbase Capella (Cloud) - **SSL Required** +1. Sign up / log in: https://cloud.couchbase.com/ +2. Create a Free Trial or Hosted Cluster +3. Create a bucket (or load sample dataset) +4. **Create database access credentials** with appropriate RBAC roles: + - Data Reader/Writer (minimum) + - Bucket Admin (for bucket operations) +5. **Download SSL Certificate**: + - Go to Cluster → Connect → Download Certificate + - Save as `couchbase-cloud-cert.pem` in your project directory +6. **Get connection endpoint**: + - Use the **KV endpoint** (port 11207 for SSL) + - Format: `your-cluster-id.cloud.couchbase.com:11207` + +**Capella SSL Authentication Example**: +```cpp +// Couchbase Capella - SSL is MANDATORY +auto result = couchbase_ops.authenticateSSL( + "your_username", + "your_password", + "your-cluster.cloud.couchbase.com:11207", // SSL port + "your_bucket_name", // bucket name + "couchbase-cloud-cert.pem" // certificate file +); +``` + +**Important Notes for Capella**: +- **SSL is mandatory** - connections without SSL will fail +- Use port **11207** (SSL) instead of 11210 (non-SSL) +- Certificate verification is required for security +- Ensure firewall allows outbound connections on port 11207 + +--- + +### 10. Error Handling Patterns + +#### High-Level API (Recommended) +The `CouchbaseOperations` class uses a simple `Result` struct: + +```cpp +struct Result { + bool success; // true if operation succeeded + string error_message; // human-readable error description + string value; // returned value (for Get operations) + uint16_t status_code; // Couchbase status code (0x00 if success) +}; +``` + +**Recommended Pattern**: +```cpp +auto result = couchbase_ops.add("key", "value"); +if (!result.success) { + LOG(ERROR) << "Add failed: " << result.error_message; + LOG(ERROR) << "Status code: " << result.status_code; + // Handle error appropriately +} else { + std::cout << "Add succeeded!" << std::endl; +} + +// For Get operations, check both success and value +auto get_result = couchbase_ops.get("key"); +if (get_result.success) { + std::cout << "Retrieved: " << get_result.value << std::endl; +} else { + LOG(ERROR) << "Get failed: " << get_result.error_message; + LOG(ERROR) << "Status code: " << get_result.status_code; +} +``` + +--- +### 11. Best Practices + +#### Threading Patterns +> **💡 FLEXIBLE THREADING OPTIONS** +> - **Same bucket/server**: Share a single `CouchbaseOperations` instance across threads +> - **Different buckets**: Create separate instances for each bucket within the same server +> - **Different servers**: Create separate instances for each server connection +> - **Connection isolation**: Each instance uses unique connection groups based on server+bucket combination + +#### SSL Security +- **Always use SSL for Couchbase Capella** (cloud deployments) +- **Verify certificates** - don't disable certificate validation in production +- **Use port 11207** for SSL connections +- **Store certificates securely** and update them when they expire + +#### Performance +- **Reuse `CouchbaseOperations` instances** - they maintain persistent connections +- **Use pipeline operations for bulk operations** +- **Pipeline operations preserve order** - results correspond to request order + +#### Threading Examples +```cpp +// Option 1: Shared instance for same bucket +brpc::CouchbaseOperations shared_ops; +shared_ops.authenticate(username, password, server_address, bucket_name); + +void worker_thread_1() { + shared_ops.add("key1", "value1"); // Safe to share +} +void worker_thread_2() { + shared_ops.get("key2"); // Safe to share +} + +// Option 2: Separate instances for different buckets +brpc::CouchbaseOperations ops_bucket1; +brpc::CouchbaseOperations ops_bucket2; +ops_bucket1.authenticate(username, password, server_address, "bucket1"); +ops_bucket2.authenticate(username, password, server_address, "bucket2"); + +// Option 3: Separate instances for different servers +brpc::CouchbaseOperations ops_server1; +brpc::CouchbaseOperations ops_server2; +ops_server1.authenticate(username, password, "server1:11210", bucket_name); +ops_server2.authenticate(username, password, "server2:11210", bucket_name); +``` + +--- +### 12. Summary and References +This implementation provides high-level APIs for Couchbase KV operations. Couchbase (the company) contributed to this implementation, but it is not officially supported; it is "[Community Supported](https://docs.couchbase.com/server/current/third-party/integrations.html#support-model)". + +--- + +## 💡 **THREADING USAGE PATTERNS** 💡 +> +> **✅ PATTERN 1: Shared instance when multiple threads operating on the same bucket** +> ```cpp +> brpc::CouchbaseOperations shared_ops; +> shared_ops.authenticate(username, password, "server:11210", "my_bucket"); +> +> void worker_thread_1() { +> shared_ops.add("key1", "value1"); // ✅ Safe to share +> } +> void worker_thread_2() { +> shared_ops.get("key2"); // ✅ Safe to share +> } +> ``` +> +> **✅ PATTERN 2: Separate instances when different threads will be operating on different buckets** +> ```cpp +> void worker_thread1() { +> brpc::CouchbaseOperations ops_bucket1; +> ops_bucket1.authenticate(username, password, "server:11210", "bucket1"); +> ops_bucket1.add("key1", "value1"); +> } +> void worker_thread2() { +> brpc::CouchbaseOperations ops_bucket2; +> ops_bucket2.authenticate(username, password, "server:11210", "bucket2"); +> ops_bucket2.add("key1", "value1"); +> } +> ``` +> +> **✅ PATTERN 3: Separate instances when threads are operating on different servers.** +> ```cpp +> void worker_thread1() { +> brpc::CouchbaseOperations ops_bucket1; +> ops_server1.authenticate(username, password, "server1:11210", "bucket1"); +> ops_server1.add("key1", "value1"); +> } +> void worker_thread2() { +> brpc::CouchbaseOperations ops_server2; +> ops_server2.authenticate(username, password, "server2:11210", "bucket2"); +> ops_server2.add("key1", "value1"); +> } +> ``` +> +> **For additional Couchbase features, consider the couchbase-cxx-SDK version of bRPC, which provides a more complete set of Couchbase features and can be accessed at [Couchbaselabs-cb-brpc](https://github.com/couchbaselabs/cb_brpc/tree/couchbase_sdk_brpc).** + + +Contributions and issue reports are welcome! diff --git a/docs/en/cpu_profiler.md b/docs/en/cpu_profiler.md new file mode 100644 index 0000000..2be417a --- /dev/null +++ b/docs/en/cpu_profiler.md @@ -0,0 +1,7 @@ +# cpu profiler + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/cpu_profiler.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/dummy_server.md b/docs/en/dummy_server.md new file mode 100644 index 0000000..a299201 --- /dev/null +++ b/docs/en/dummy_server.md @@ -0,0 +1,24 @@ +If your program only uses client in brpc or doesn't use brpc at all, but you also want to use built-in services in brpc. The thing you should do is to start an empty server, which is called **dummy server**. + +# client in brpc is used + +Create a file named dummy_server.port which contains a port number(such as 8888) in the running directory of program, a dummy server would be started at this port. All of the bvar in the same process can be seen by visiting its built-in service. +![img](../images/dummy_server_1.png) ![img](../images/dummy_server_2.png) + +![img](../images/dummy_server_3.png) + +# brpc is not used at all + +You must manually add the dummy server. First read [Getting Started](getting_started.md) to learn how to download and compile brpc, and then add the following code snippet at the program entry: + +```c++ +#include + +... + +int main() { + ... + brpc::StartDummyServerAt(8888/*port*/); + ... +} +``` diff --git a/docs/en/endpoint.md b/docs/en/endpoint.md new file mode 100644 index 0000000..ad10963 --- /dev/null +++ b/docs/en/endpoint.md @@ -0,0 +1,7 @@ +# endpoint + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/endpoint.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/error_code.md b/docs/en/error_code.md new file mode 100644 index 0000000..db92609 --- /dev/null +++ b/docs/en/error_code.md @@ -0,0 +1,88 @@ +[中文版](../cn/error_code.md) + +brpc use [brpc::Controller](https://github.com/apache/brpc/blob/master/src/brpc/controller.h) to set and get parameters for one RPC. `Controller::ErrorCode()` and `Controller::ErrorText()` return error code and description of the RPC respectively, only accessible after completion of the RPC, otherwise the result is undefined. `ErrorText()` is defined by the base class of the Controller: `google::protobuf::RpcController`, while `ErrorCode()` is defined by `brpc::Controller`. Controller also has a method `Failed()` to tell whether RPC fails or not. Relations between the three methods: + +- When `Failed()` is true, `ErrorCode()` must be non-zero and `ErrorText()` be non-empty. +- When `Failed()` is false, `ErrorCode()` is 0 and `ErrorText()` is undefined (it's empty in brpc currently, but you'd better not rely on this) + +# Mark RPC as failed + +Both client and server in brpc have `Controller`, which can be set with `setFailed()` to modify ErrorCode and ErrorText. Multiple calls to `Controller::SetFailed` leave the last ErrorCode and **concatenate** ErrorTexts rather than leaving the last one. The framework elaborates ErrorTexts by adding extra prefixes: number of retries at client-side and address of the server at server-side. + +`Controller::SetFailed()` at client-side is usually called by the framework, such as sending failure, incomplete response, and so on. Error may be set at client-side under some situations. For example, you may set error to the RPC if an additional check before sending the request is failed. + +`Controller::SetFailed()` at server-side is often called by the user in the service callback. Generally speaking when error occurs, users call `SetFailed()`, release all the resources, and return from the callback. The framework fills the error code and message into the response according to communication protocol. When the response is received, the error inside are set into the client-side Controller so that users can fetch them after end of RPC. Note that **server does not print errors to clients by default**, as frequent loggings may impact performance of the server significantly due to heavy disk IO. A client crazily producing errors could slow the entire server down and affect all other clients, which can even become an attacking method against the server. If you really want to see error messages on the server, turn on the gflag **-log_error_text** (modifiable at run-time), the server will log the ErrorText of corresponding Controller of each failed RPC. + +# Error Code in brpc + +All error codes in brpc are defined in [errno.proto](https://github.com/apache/brpc/blob/master/src/brpc/errno.proto), in which those begin with *SYS_* are defined by linux system and exactly same with the ones defined in `/usr/include/errno.h`. The reason that we put it in .proto is to cross language. The rest of the error codes are defined by brpc. + +[berror(error_code)](https://github.com/apache/brpc/blob/master/src/butil/errno.h) gets description for the error code, and `berror()` gets description for current [system errno](http://www.cplusplus.com/reference/cerrno/errno/). Note that **ErrorText() != berror(ErorCode())** since `ErrorText()` contains more specific information. brpc includes berror by default so that you can use it in your project directly. + +Following table shows common error codes and their descriptions: + +| Error Code | Value | Retry | Description | Logging message | +| -------------- | ----- | ----- | ---------------------------------------- | ---------------------------------------- | +| EAGAIN | 11 | Yes | Too many requests at the same time, hardly happening as it's a soft limit. | Resource temporarily unavailable | +| ENODATA | 61 | 是 | 1. The server list returned by Naming Service is empty. 2. When Naming Service changes with all instances modified, Naming Service updates LB by first Remove all and then Add all, the LB instance list may become empty within a short period of time. | Fail to select server from xxx | +| ETIMEDOUT | 110 | Yes | Connection timeout. | Connection timed out | +| EHOSTDOWN | 112 | Yes | Possible reasons: A. The list returned by Naming Server is not empty, but LB cannot select an available server, and LB returns an EHOSTDOWN error. Specific possible reasons: a. Server is exiting (returned ELOGOFF) b. Server was blocked because of some previous failure, the specific logic of the block: 1. For single connection type, the only connection socket is blocked by SetFail, and there are many occurrences of SetFailed in the code to trigger this block. 2. For pooled/short connection type, only when the error number meets does_error_affect_main_socket (ECONNREFUSED, ENETUNREACH, EHOSTUNREACH or EINVAL) will it be blocked 3. After blocking, there is a CheckHealth thread to do health check, Just try to connect, the check interval is controlled by the health_check_interval_s of SocketOptions, and the Socket will be unblocked if it is connected successfully. B. Use the SingleServer method to initialize the Channel (without LB), and the only connection is LOGOFF or blocked (same as above) | "Fail to select server from …" "Not connected to … yet" | +| ENOSERVICE | 1001 | No | Can't locate the service, hardly happening and usually being ENOMETHOD instead | | +| ENOMETHOD | 1002 | No | Can't locate the method. | Misc forms, common ones are "Fail to find method=…" | +| EREQUEST | 1003 | No | fail to serialize the request, may be set on either client-side or server-side | Misc forms: "Missing required fields in request: …" "Fail to parse request message, …" "Bad request" | +| EAUTH | 1004 | No | Authentication failed | "Authentication failed" | +| ETOOMANYFAILS | 1005 | No | Too many sub-channel failures inside a ParallelChannel | "%d/%d channels failed, fail_limit=%d" | +| EBACKUPREQUEST | 1007 | Yes | Set when backup requests are triggered. Not returned by ErrorCode() directly, viewable from spans in /rpcz | "reached backup timeout=%dms" | +| ERPCTIMEDOUT | 1008 | No | RPC timeout. | "reached timeout=%dms" | +| EFAILEDSOCKET | 1009 | Yes | The connection is broken during RPC | "The socket was SetFailed" | +| EHTTP | 1010 | No | HTTP responses with non 2xx status code are treated as failure and set with this code. No retry by default, changeable by customizing RetryPolicy. | Bad http call | +| EOVERCROWDED | 1011 | Yes | Too many messages to buffer at the sender side. Usually caused by lots of concurrent asynchronous requests. Modifiable by `-socket_max_unwritten_bytes`, 64MB by default. | The server is overcrowded | +| EINTERNAL | 2001 | No | The default error for `Controller::SetFailed` without specifying a one. | Internal Server Error | +| ERESPONSE | 2002 | No | fail to serialize the response, may be set on either client-side or server-side | Misc forms: "Missing required fields in response: …" "Fail to parse response message, " "Bad response" | +| ELOGOFF | 2003 | Yes | Server has been stopped | "Server is going to quit" | +| ELIMIT | 2004 | Yes | Number of requests being processed concurrently exceeds `ServerOptions.max_concurrency` | "Reached server's limit=%d on concurrent requests" | + +# User-defined Error Code + +In C/C++, error code can be defined in macros, constants or enums: + +```c++ +#define ESTOP -114 // C/C++ +static const int EMYERROR = 30; // C/C++ +const int EMYERROR2 = -31; // C++ only +``` + +If you need to get the error description through `berror`, register it in the global scope of your c/cpp file by `BAIDU_REGISTER_ERRNO(error_code, description)`, for example: + +```c++ +BAIDU_REGISTER_ERRNO(ESTOP, "the thread is stopping") +BAIDU_REGISTER_ERRNO(EMYERROR, "my error") +``` + +Note that `strerror` and `strerror_r` do not recognize error codes defined by `BAIDU_REGISTER_ERRNO`. Neither does the `%m` used in `printf`. You must use `%s` paired with `berror`: + +```c++ +errno = ESTOP; +printf("Describe errno: %m\n"); // [Wrong] Describe errno: Unknown error -114 +printf("Describe errno: %s\n", strerror_r(errno, NULL, 0)); // [Wrong] Describe errno: Unknown error -114 +printf("Describe errno: %s\n", berror()); // [Correct] Describe errno: the thread is stopping +printf("Describe errno: %s\n", berror(errno)); // [Correct] Describe errno: the thread is stopping +``` + +When the registration of an error code is duplicated, a linking error is generated provided it's defined in C++: + +``` +redefinition of `class BaiduErrnoHelper<30>' +``` + +Or the program aborts before start: + +``` +Fail to define EMYERROR(30) which is already defined as `Read-only file system', abort +``` + +You have to make sure that different modules have same understandings on same ErrorCode. Otherwise, interactions between two modules that interpret an error code differently may be undefined. To prevent this from happening, you'd better follow these: + +- Prefer system error codes which have fixed values and meanings, generally. +- Share code on error definitions between multiple modules to prevent inconsistencies after modifications. +- Use `BAIDU_REGISTER_ERRNO` to describe new error code to ensure that same error code is defined only once inside a process. diff --git a/docs/en/execution_queue.md b/docs/en/execution_queue.md new file mode 100644 index 0000000..194251f --- /dev/null +++ b/docs/en/execution_queue.md @@ -0,0 +1,7 @@ +# execution queue + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/execution_queue.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/flags.md b/docs/en/flags.md new file mode 100644 index 0000000..74ea5b4 --- /dev/null +++ b/docs/en/flags.md @@ -0,0 +1,7 @@ +# flags + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/flags.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/flatmap.md b/docs/en/flatmap.md new file mode 100644 index 0000000..256fb2b --- /dev/null +++ b/docs/en/flatmap.md @@ -0,0 +1,7 @@ +# flatmap + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/flatmap.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/getting_started.md b/docs/en/getting_started.md new file mode 100644 index 0000000..e4dd403 --- /dev/null +++ b/docs/en/getting_started.md @@ -0,0 +1,396 @@ +[中文版](../cn/getting_started.md) + +# BUILD + +brpc prefers static linkages of deps, so that they don't have to be installed on every machine running the app. + +brpc depends on following packages: + +* [gflags](https://github.com/gflags/gflags): Extensively used to define global options. +* [protobuf](https://github.com/google/protobuf): Serializations of messages, interfaces of services. +* [leveldb](https://github.com/google/leveldb): Required by [/rpcz](rpcz.md) to record RPCs for tracing. + +# Supported Environment + +* [Ubuntu/LinuxMint/WSL](#ubuntulinuxmintwsl) +* [Fedora/CentOS](#fedoracentos) +* [Linux with self-built deps](#linux-with-self-built-deps) +* [MacOS](#macos) +* [Docker](#docker) + +## Ubuntu/LinuxMint/WSL +### Prepare deps + +Install common deps, [gflags](https://github.com/gflags/gflags), [protobuf](https://github.com/google/protobuf), [leveldb](https://github.com/google/leveldb): +```shell +sudo apt-get install -y git g++ make libssl-dev libgflags-dev libprotobuf-dev libprotoc-dev protobuf-compiler libleveldb-dev +``` + +If you need to statically link leveldb: +```shell +sudo apt-get install -y libsnappy-dev +``` + +If you need to enable cpu/heap profilers in examples: +```shell +sudo apt-get install -y libgoogle-perftools-dev +``` + +If you need to run tests, install and compile libgtest-dev (which is not compiled yet): +```shell +sudo apt-get install -y cmake libgtest-dev && cd /usr/src/gtest && sudo cmake . && sudo make && sudo mv lib/libgtest* /usr/lib/ && cd - +``` +The directory of gtest source code may be changed, try `/usr/src/googletest/googletest` if `/usr/src/gtest` is not there. + +### Compile brpc with config_brpc.sh +git clone brpc, cd into the repo and run +```shell +$ sh config_brpc.sh --headers=/usr/include --libs=/usr/lib +$ make +``` +To change compiler to clang, add `--cxx=clang++ --cc=clang`. + +To not link debugging symbols, add `--nodebugsymbols` and compiled binaries will be much smaller. + +To use brpc with glog, add `--with-glog`. + +To enable [thrift support](../en/thrift.md), install thrift first and add `--with-thrift`. + +**Run example** + +```shell +$ cd example/echo_c++ +$ make +$ ./echo_server & +$ ./echo_client +``` + +Examples link brpc statically, if you need to link the shared version, `make clean` and `LINK_SO=1 make` + +**Run tests** +```shell +$ cd test +$ make +$ sh run_tests.sh +``` + +### Compile brpc with cmake +```shell +mkdir build && cd build && cmake .. && cmake --build . -j6 +``` +With CMake 3.13+, we can also use the following commands to build the project: +```shell +cmake -B build && cmake --build build -j6 +``` +To help VSCode or Emacs(LSP) to understand code correctly, add `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` to generate `compile_commands.json` + +To change compiler to clang, overwrite environment variable `CC` and `CXX` to `clang` and `clang++` respectively. + +To not link debugging symbols, remove `build/CMakeCache.txt` and cmake with `-DWITH_DEBUG_SYMBOLS=OFF` + +To use brpc with glog, cmake with `-DWITH_GLOG=ON`. + +To enable [thrift support](../en/thrift.md), install thrift first and cmake with `-DWITH_THRIFT=ON`. + +**Run example with cmake** + +```shell +$ cd example/echo_c++ +$ cmake -B build && cmake --build build -j4 +$ ./echo_server & +$ ./echo_client +``` +Examples link brpc statically, if you need to link the shared version, remove `CMakeCache.txt` and cmake with `-DLINK_SO=ON` + +**Run tests** + +```shell +$ mkdir build && cd build && cmake -DBUILD_UNIT_TESTS=ON .. && make && make test +``` + +### Compile brpc with vcpkg + +[vcpkg](https://github.com/microsoft/vcpkg) is a package manager that supports all platforms, +you can use vcpkg to build brpc with the following step: + +```shell +$ git clone https://github.com/microsoft/vcpkg.git +$ ./bootstrap-vcpkg.bat # for powershell +$ ./bootstrap-vcpkg.sh # for bash +$ ./vcpkg install brpc +``` + +## Fedora/CentOS + +### Prepare deps + +CentOS needs to install EPEL generally otherwise many packages are not available by default. +```shell +sudo yum install epel-release +``` + +Install common deps: +```shell +sudo yum install git gcc-c++ make openssl-devel +``` + +Install [gflags](https://github.com/gflags/gflags), [protobuf](https://github.com/google/protobuf), [leveldb](https://github.com/google/leveldb): +```shell +sudo yum install gflags-devel protobuf-devel protobuf-compiler leveldb-devel +``` + +If you need to enable cpu/heap profilers in examples: +```shell +sudo yum install gperftools-devel +``` + +If you need to run tests, install and compile gtest-devel (which is not compiled yet): +```shell +sudo yum install gtest-devel +``` + +### Compile brpc with config_brpc.sh + +git clone brpc, cd into the repo and run + +```shell +$ sh config_brpc.sh --headers="/usr/include" --libs="/usr/lib64 /usr/bin" +$ make +``` +To change compiler to clang, add `--cxx=clang++ --cc=clang`. + +To not link debugging symbols, add `--nodebugsymbols` and compiled binaries will be much smaller. + +To use brpc with glog, add `--with-glog`. + +To enable [thrift support](../en/thrift.md), install thrift first and add `--with-thrift`. + +**Run example** + +```shell +$ cd example/echo_c++ +$ make +$ ./echo_server & +$ ./echo_client +``` + +Examples link brpc statically, if you need to link the shared version, `make clean` and `LINK_SO=1 make` + +**Run tests** +```shell +$ cd test +$ make +$ sh run_tests.sh +``` + +### Compile brpc with cmake +Same with [here](#compile-brpc-with-cmake) + +## Linux with self-built deps + +### Prepare deps + +brpc builds itself to both static and shared libs by default, so it needs static and shared libs of deps to be built as well. + +Take [gflags](https://github.com/gflags/gflags) as example, which does not build shared lib by default, you need to pass options to `cmake` to change the behavior: +```shell +$ cmake . -DBUILD_SHARED_LIBS=1 -DBUILD_STATIC_LIBS=1 +$ make +``` + +### Compile brpc + +Keep on with the gflags example, let `../gflags_dev` be where gflags is cloned. + +git clone brpc. cd into the repo and run + +```shell +$ sh config_brpc.sh --headers="../gflags_dev /usr/include" --libs="../gflags_dev /usr/lib64" +$ make +``` + +Here we pass multiple paths to `--headers` and `--libs` to make the script search for multiple places. You can also group all deps and brpc into one directory, then pass the directory to --headers/--libs which actually search all subdirectories recursively and will find necessary files. + +To change compiler to clang, add `--cxx=clang++ --cc=clang`. + +To not link debugging symbols, add `--nodebugsymbols` and compiled binaries will be much smaller. + +To use brpc with glog, add `--with-glog`. + +To enable [thrift support](../en/thrift.md), install thrift first and add `--with-thrift`. + +```shell +$ ls my_dev +gflags_dev protobuf_dev leveldb_dev brpc_dev +$ cd brpc_dev +$ sh config_brpc.sh --headers=.. --libs=.. +$ make +``` + +### Compile brpc with cmake +Same with [here](#compile-brpc-with-cmake) + +## MacOS + +Note: With same environment, the performance of the MacOS version is worse than the Linux version. If your service is performance-critical, do not use MacOS as your production environment. + +### Apple Silicon + +The code at master HEAD already supports M1 series chips. M2 series are not tested yet. Please feel free to report remaining warnings/errors to us by issues. + +## Docker +Compile brpc with docker: + +```shell +$ mkdir -p ~/brpc +$ cd ~/brpc +$ git clone https://github.com/apache/brpc.git +$ cd brpc +$ docker build -t brpc:master . +$ docker images +$ docker run -it brpc:master /bin/bash +``` + +### Prepare deps + +Install dependencies: +```shell +brew install ./homebrew-formula/protobuf.rb +brew install openssl git gnu-getopt coreutils gflags leveldb +``` + +If you need to enable cpu/heap profilers in examples: +```shell +brew install gperftools +``` + +If you need to run tests, googletest is required. Run `brew install googletest` first to see if it works. If not (old homebrew does not have googletest), you can download and compile googletest by your own: +```shell +git clone https://github.com/google/googletest -b release-1.10.0 && cd googletest/googletest && mkdir build && cd build && cmake -DCMAKE_CXX_FLAGS="-std=c++11" .. && make +``` +After the compilation, copy `include/` and `lib/` into `/usr/local/include` and `/usr/local/lib` respectively to expose gtest to all apps + +### OpenSSL + +openssl installed in Monterey may not be found at `/usr/local/opt/openssl`, instead it's probably put under `/opt/homebrew/Cellar`. If the compiler cannot find openssl: + +* Run `brew link openssl --force` first and check if `/usr/local/opt/openssl` appears. +* If above command does not work, consider making a soft link using `sudo ln -s /opt/homebrew/Cellar/openssl@3/3.0.3 /usr/local/opt/openssl`. Note that the installed openssl in above command may be put in different places in different environments, which could be revealed by running `brew info openssl`. + +### Compile brpc with config_brpc.sh +git clone brpc, cd into the repo and run +```shell +$ sh config_brpc.sh --headers=/usr/local/include --libs=/usr/local/lib --cc=clang --cxx=clang++ +$ make +``` + +The homebrew in Monterey may install software at different directories from before. If path related errors are reported, try setting headers/libs like below: + +```shell +$ sh config_brpc.sh --headers=/opt/homebrew/include --libs=/opt/homebrew/lib --cc=clang --cxx=clang++ +$ make +``` + +To not link debugging symbols, add `--nodebugsymbols` and compiled binaries will be much smaller. + +To use brpc with glog, add `--with-glog`. + +To enable [thrift support](../en/thrift.md), install thrift first and add `--with-thrift`. + +**Run example** + +```shell +$ cd example/echo_c++ +$ make +$ ./echo_server & +$ ./echo_client +``` + +Examples link brpc statically, if you need to link the shared version, `make clean` and `LINK_SO=1 make` + +**Run tests** +```shell +$ cd test +$ make +$ sh run_tests.sh +``` + +### Compile brpc with cmake +Same with [here](#compile-brpc-with-cmake) + +# Supported deps + +## GCC: 4.8-11.2 + +**Prefer GCC 8.2+** + +c++11 is turned on by default to remove dependencies on boost (atomic). + +The over-aligned issues in GCC7 is suppressed temporarily now. + +Using other versions of gcc may generate warnings, contact us to fix. + +Adding `-D__const__=__unused__` to cxxflags in your makefiles is a must to avoid [errno issue in gcc4+](thread_local.md). + +## Clang: 3.5-4.0 + +no known issues. + +## glibc: 2.12-2.25 + +no known issues. + +## protobuf: 3.0-5.29 + +bRPC uses some protobuf internal APIs, which may be changed upstream. +Please [submit issue](https://github.com/apache/brpc/issues) if you have any problem. + +[#2406](https://github.com/apache/brpc/pull/2406) and [#2493](https://github.com/apache/brpc/pull/2493) in [version 1.8.0]((https://github.com/apache/brpc/releases/tag/1.8.0)) introduce some proto3 syntax, so currently bRPC is no longer compatible with pb 2.x version. If you want to use pb 2.x version, you can use bRPC version before 1.8.0. + +## gflags: 2.0-2.2.2 + +[gflags patch](https://github.com/gflags/gflags/commit/408061b46974cc8377a8a794a048ecae359ad887) is required when compiled with 2.1.1. + +## openssl: 0.97-1.1 + +required by https. + +## tcmalloc: 1.7-2.5 + +brpc does **not** link [tcmalloc](http://goog-perftools.sourceforge.net/doc/tcmalloc.html) by default. Users link tcmalloc on-demand. + +Comparing to ptmalloc embedded in glibc, tcmalloc often improves performance. However different versions of tcmalloc may behave really differently. For example, tcmalloc 2.1 may make multi-threaded examples in brpc perform significantly worse(due to a spinlock in tcmalloc) than the one using tcmalloc 1.7 and 2.5. Even different minor versions may differ. When you program behave unexpectedly, remove tcmalloc or try another version. + +Code compiled with gcc 4.8.2 and linked to a tcmalloc compiled with earlier GCC may crash or deadlock before main(), E.g: + +![img](../images/tcmalloc_stuck.png) + +When you meet the issue, compile tcmalloc with the same GCC. + +Another common issue with tcmalloc is that it does not return memory to system as early as ptmalloc. So when there's an invalid memory access, the program may not crash directly, instead it crashes at a unrelated place, or even not crash. When you program has weird memory issues, try removing tcmalloc. + +If you want to use [cpu profiler](cpu_profiler.md) or [heap profiler](heap_profiler.md), do link `libtcmalloc_and_profiler.a`. These two profilers are based on tcmalloc.[contention profiler](contention_profiler.md) does not require tcmalloc. + +When you remove tcmalloc, not only remove the linkage with tcmalloc but also the macro `-DBRPC_ENABLE_CPU_PROFILER`. + +## glog: 3.3+ + +brpc implements a default [logging utility](../../src/butil/logging.h) which conflicts with glog. To replace this with glog, add `--with-glog` to config_brpc.sh or add `-DWITH_GLOG=ON` to cmake. + +## valgrind: 3.8+ + +brpc detects valgrind automatically (and registers stacks of bthread). Older valgrind(say 3.2) is not supported. + +## thrift: 0.9.3-0.11.0 + +## libunwind: 1.3-1.8.1 + +brpc does **not** link [libunwind](https://github.com/libunwind/libunwind) by default. Users link libunwind on-demand by adding `--with-bthread-tracer` to config_brpc.sh or adding `-DWITH_BTHREAD_TRACER=ON` to cmake, if building with Bazel, please add the `--define with_bthread_tracer=true` option. + +It is recommended to use the latest possible version of libunwind. + +no known issues. + +# Track instances + +We provide a program to help you to track and monitor all brpc instances. Just run [trackme_server](https://github.com/apache/brpc/tree/master/tools/trackme_server/) somewhere and launch need-to-be-tracked instances with -trackme_server=SERVER. The trackme_server will receive pings from instances periodically and print logs when it does. You can aggregate instance addresses from the log and call builtin services of the instances for further information. diff --git a/docs/en/heap_profiler.md b/docs/en/heap_profiler.md new file mode 100644 index 0000000..76eeaeb --- /dev/null +++ b/docs/en/heap_profiler.md @@ -0,0 +1,7 @@ +# heap profiler + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/heap_profiler.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/http_client.md b/docs/en/http_client.md new file mode 100644 index 0000000..6c64431 --- /dev/null +++ b/docs/en/http_client.md @@ -0,0 +1,263 @@ +[中文版](../cn/http_client.md) + +# Example + +[example/http_c++](https://github.com/apache/brpc/blob/master/example/http_c++/http_client.cpp) + +# About h2 + +brpc names the HTTP/2 protocol to "h2", no matter encrypted or not. However HTTP/2 connections without SSL are shown on /connections with the official name "h2c", and the ones with SSL are shown as "h2". + +The APIs for http and h2 in brpc are basically same. Without explicit statement, mentioned http features work for h2 as well. + +# Create Channel + +In order to use `brpc::Channel` to access http/h2 services, `ChannelOptions.protocol` must be set to `PROTOCOL_HTTP` or `PROTOCOL_H2`. + +Once the protocol is set, the first parameter of `Channel::Init` can be any valid URL. *Note*: Only host and port inside the URL are used by Init(), other parts are discarded. Allowing full URL simply saves the user from additional parsing code. + +```c++ +brpc::ChannelOptions options; +options.protocol = brpc::PROTOCOL_HTTP; // or brpc::PROTOCOL_H2 +if (channel.Init("www.baidu.com" /*any url*/, &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; +} +``` + +http/h2 channel also support BNS address or other naming services. + +# GET + +```c++ +brpc::Controller cntl; +cntl.http_request().uri() = "www.baidu.com/index.html"; // Request URL +channel.CallMethod(NULL, &cntl, NULL, NULL, NULL/*done*/); +``` + +http/h2 does not relate to protobuf much, thus all parameters of `CallMethod` are NULL except `Controller` and `done`. Issue asynchronous RPC with non-NULL `done`. + +`cntl.response_attachment()` is body of the http/h2 response and typed `butil::IOBuf`. `IOBuf` can be converted to `std::string` by `to_string()`, which needs to allocate memory and copy all data. If performance is important, the code should consider supporting `IOBuf` directly rather than requiring continuous memory. + +# POST + +The default HTTP Method is GET, which can be changed to POST or [other http methods](https://github.com/apache/brpc/blob/master/src/brpc/http_method.h). The data to POST should be put into `request_attachment()`, which is typed [butil::IOBuf](https://github.com/apache/brpc/blob/master/src/butil/iobuf.h) and able to append `std :: string` or `char *` directly. + +```c++ +brpc::Controller cntl; +cntl.http_request().uri() = "..."; // Request URL +cntl.http_request().set_method(brpc::HTTP_METHOD_POST); +cntl.request_attachment().append("{\"message\":\"hello world!\"}"); +channel.CallMethod(NULL, &cntl, NULL, NULL, NULL/*done*/); +``` + +If the body needs a lot of printing to build, consider using `butil::IOBufBuilder`, which has same interfaces as `std::ostringstream`, probably simpler and more efficient than c-style printf when lots of objects need to be printed. + +```c++ +brpc::Controller cntl; +cntl.http_request().uri() = "..."; // Request URL +cntl.http_request().set_method(brpc::HTTP_METHOD_POST); +butil::IOBufBuilder os; +os << "A lot of printing" << printable_objects << ...; +os.move_to(cntl.request_attachment()); +channel.CallMethod(NULL, &cntl, NULL, NULL, NULL/*done*/); +``` + +# Change HTTP version + +brpc behaves as http/1.1 by default. + +Comparing to http/1.1, http/1.0 lacks of long connections(KeepAlive). To communicate brpc client with some legacy http servers, the client may be configured as follows: +```c++ +cntl.http_request().set_version(1, 0); +``` + +Setting http version does not work for h2, but the versions in h2 responses received by client and h2 requests received by server are set to (2, 0). + +brpc server recognizes http versions automically and responds accordingly without users' aid. + +# URL + +Genaral form of an URL: + +``` +// URI scheme : http://en.wikipedia.org/wiki/URI_scheme +// +// foo://username:password@example.com:8042/over/there/index.dtb?type=animal&name=narwhal#nose +// \_/ \_______________/ \_________/ \__/ \___/ \_/ \______________________/ \__/ +// | | | | | | | | +// | userinfo host port | | query fragment +// | \________________________________/\_____________|____|/ \__/ \__/ +// scheme | | | | | | +// authority | | | | | +// path | | interpretable as keys +// | | +// \_______________________________________________|____|/ \____/ \_____/ +// | | | | | +// hierarchical part | | interpretable as values +// | | +// interpretable as filename | +// | +// | +// interpretable as extension +``` + +As we saw in examples above, `Channel.Init()` and `cntl.http_request().uri()` both need the URL. Why does `uri()` need to be set additionally rather than using the URL to `Init()` directly? + +Indeed, the settings are repeated in simple cases. But they are different in more complex scenes: + +- Access multiple servers under a NamingService (for example BNS), in which case `Channel::Init` accepts a name meaningful to the NamingService(for example node names in BNS), while `uri()` is assigned with the URL. +- Access servers via http/h2 proxy, in which case `Channel::Init` takes the address of the proxy server, while `uri()` is still assigned with the URL. + +## Host header + +If user already sets `Host` header(case insensitive), framework makes no change. + +If user does not set `Host` header and the URL has host, for example http://www.foo.com/path, the http request contains "Host: www.foo.com". + +If user does not set host header and the URL does not have host either, for example "/index.html?name=value", but if the channel is initlized by a http(s) address with valid domain name. framework sets `Host` header with domain name of the target server. if this address is "http://www.foo.com", this http server should see `Host: www.foo.com`, if this address is "http://www.foo.com:8989", this http server should be see `Host: www.foo.com:8989`. + +If user does not set host header and the URL does not have host as well, for example "/index.html?name=value", and the address initialized by the channel doesn't contain domain name. framework sets `Host` header with IP and port of the target server. A http server at 10.46.188.39:8989 should see `Host: 10.46.188.39:8989`. + +The header is named ":authority" in h2. + +# Common usages + +Take http request as an example (similar with http response), common operations are listed as follows: + +Access an HTTP header named `Foo` +```c++ +const std::string* value = cntl->http_request().GetHeader("Foo"); // NULL when not exist +``` + +Set an HTTP header named `Foo` +```c++ +cntl->http_request().SetHeader("Foo", "value"); +``` + +Access a query named `Foo` +```c++ +const std::string* value = cntl->http_request().uri().GetQuery("Foo"); // NULL when not exist +``` + +Set a query named `Foo` +```c++ +cntl->http_request().uri().SetQuery("Foo", "value"); +``` + +Set HTTP method +```c++ +cntl->http_request().set_method(brpc::HTTP_METHOD_POST); +``` + +Set the URL +```c++ +cntl->http_request().uri() = "http://www.baidu.com"; +``` + +Set the `content-type` +```c++ +cntl->http_request().set_content_type("text/plain"); +``` + +Get HTTP body +```c++ +butil::IOBuf& buf = cntl->request_attachment(); +std::string str = cntl->request_attachment().to_string(); // trigger copy underlying +``` + +Set HTTP body +```c++ +cntl->request_attachment().append("...."); +butil::IOBufBuilder os; os << "...."; +os.move_to(cntl->request_attachment()); +``` + +Notes on http header: +- field_name of the header is case-insensitive according to [rfc2616](http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2). brpc supports case-insensitive field names and keeps same cases at printing as users set. +- If multiple headers have same field names, according to [rfc2616](http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2), values should be merged and separated by comma (,). Users figure out how to use this kind of values by their own. +- Queries are separated by "&" and key/value in a query are separated by "=". Values can be omitted. For example, `key1=value1&key2&key3=value3` is a valid query string, in which the value for `key2` is an empty string. + +# Debug HTTP messages + +Turn on [-http_verbose](http://brpc.baidu.com:8765/flags/http_verbose) so that the framework prints each http request and response. Note that this should only be used in tests or debuggings rather than online services. + +# HTTP errors + +When server returns a non-2xx HTTP status code, the HTTP RPC is considered to be failed and `cntl->ErrorCode()` at client-side is set to `EHTTP`, users can check `cntl-> http_response().status_code()` for more specific HTTP error. In addition, server can put html or json describing the error into `cntl->response_attachment()` which is sent back to the client as http body. + +If client-side wants to get the real `ErrorCode` returned by the bRPC server when the HTTP RPC fails, instead of `EHTTP`, you need to set GFlag`-use_http_error_code=true`. + +# Compress Request Body + +`Controller::set_request_compress_type(brpc::COMPRESS_TYPE_GZIP)` makes framework try to gzip the HTTP body. "try to" means the compression may not happen, because: + +* Size of body is smaller than bytes specified by -http_body_compress_threshold, which is 512 by default. The reason is that gzip is not a very fast compression algorithm, when body is small, the delay caused by compression may even larger than the latency saved by faster transportation. + +# Decompress Response Body + +brpc does not decompress bodies of responses automatically due to universality. The decompression code is not complicated and users can do it by themselves. The code is as follows: + +```c++ +#include +... +const std::string* encoding = cntl->http_response().GetHeader("Content-Encoding"); +if (encoding != NULL && *encoding == "gzip") { + butil::IOBuf uncompressed; + if (!brpc::policy::GzipDecompress(cntl->response_attachment(), &uncompressed)) { + LOG(ERROR) << "Fail to un-gzip response body"; + return; + } + cntl->response_attachment().swap(uncompressed); +} +// Now cntl->response_attachment() contains the decompressed data +``` + +# Progressively Download + +http client normally does not complete the RPC until http body has been fully downloaded. During the process http body is stored in memory. If the body is very large or infinitely large(a FLV file for live streaming), memory grows continuously until the RPC is timedout. Such http clients are not suitable for downloading very large files. + +brpc client supports completing RPC before reading the full body, so that users can read http bodies progressively after RPC. Note that this feature does not mean "support for http chunked mode", actually the http implementation in brpc supports chunked mode from the very beginning. The real issue is how to let users handle very or infinitely large http bodies, which does not imply the chunked mode. + +How to use: + +1. Implement ProgressiveReader below: + + ```c++ + #include + ... + class ProgressiveReader { + public: + // Called when one part was read. + // Error returned is treated as *permanent* and the socket where the + // data was read will be closed. + // A temporary error may be handled by blocking this function, which + // may block the HTTP parsing on the socket. + virtual butil::Status OnReadOnePart(const void* data, size_t length) = 0; + + // Called when there's nothing to read anymore. The `status' is a hint for + // why this method is called. + // - status.ok(): the message is complete and successfully consumed. + // - otherwise: socket was broken or OnReadOnePart() failed. + // This method will be called once and only once. No other methods will + // be called after. User can release the memory of this object inside. + virtual void OnEndOfMessage(const butil::Status& status) = 0; + }; + ``` + + `OnReadOnePart` is called each time a piece of data is read. `OnEndOfMessage` is called at the end of data or the connection is broken. Read comments carefully before implementing. + +2. Set `cntl.response_will_be_read_progressively();` before RPC to make brpc end RPC just after reading all headers. + +3. Call `cntl.ReadProgressiveAttachmentBy(new MyProgressiveReader);` after RPC. `MyProgressiveReader` is an instance of user-implemented `ProgressiveReader`. User may delete the object inside `OnEndOfMessage`. + +# Progressively Upload + +Currently the POST data should be intact before launching the http call, thus brpc http client is still not suitable for uploading very large bodies. + +# Access Servers with authentications + +Generate `auth_data` according to authenticating method of the server and set it into `Authorization` header. If you're using curl, add option `-H "Authorization : "`. + +# Send https requests +https is short for "http over SSL", SSL is not exclusive for http, but effective for all protocols. The generic method for turning on client-side SSL is [here](client.md#turn-on-ssl). brpc enables SSL automatically for URIs starting with https:// to make the usage more handy. diff --git a/docs/en/http_derivatives.md b/docs/en/http_derivatives.md new file mode 100644 index 0000000..1bcf6f9 --- /dev/null +++ b/docs/en/http_derivatives.md @@ -0,0 +1,37 @@ +[中文版](../cn/http_derivatives.md) + +Basics for accessing and serving http/h2 in brpc are listed in [http_client](http_client.md) and [http_service](http_service.md). + +Following section names are protocol names that can be directly set to ChannelOptions.protocol. The content after colon is parameters for the protocol to select derivative behaviors dynamically, but the base protocol is still http/1.x or http/2. As a result, these protocols are displayed at server-side as http or h2/h2c only. + +# http:json, h2:json + +Non-empty pb request is serialized to json and set to the body of the http/h2 request. The Controller.request_attachment() must be empty otherwise the RPC fails. + +Non-empty pb response is converted from a json which is parsed from the body of the http/h2 response. + +http/1.x behaves in this way by default, so "http" and "http:json" are just same. + +# http:proto, h2:proto + +Non-empty pb request is serialized (in pb's wire format) and set to the body of the http/h2 request. The Controller.request_attachment() must be empty otherwise the RPC fails. + +Non-empty pb response is parsed from the body of the http/h2 response(in pb's wire format). + +http/2 behaves in this way by default, so "h2" and "h2:proto" are just same. + +# h2:grpc + +Default protocol of [gRPC](https://github.com/grpc). The detailed format is described in [gRPC over HTTP2](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md). + +Clients using brpc should be able to talk with gRPC after changing ChannelOptions.protocol to "h2:grpc". + +Servers using brpc should be accessible by gRPC clients automatically without changing the code. + +gRPC serializes message into pb wire format by default, so "h2:grpc" and "h2:grpc+proto" are just same. + +TODO: Other configurations for gRPC + +# h2:grpc+json + +Comparing to h2:grpc, this protocol serializes messages into json instead of pb, which may not be supported by gRPC directly. For example, grpc-go users may reference [here](https://github.com/johanbrandhorst/grpc-json-example/blob/master/codec/json.go) to register the corresponding codec and turn on the support. diff --git a/docs/en/http_service.md b/docs/en/http_service.md new file mode 100644 index 0000000..bf080ec --- /dev/null +++ b/docs/en/http_service.md @@ -0,0 +1,386 @@ +[中文版](../cn/http_service.md) + +This document talks about ordinary http/h2 services rather than protobuf services accessible via http/h2. +http/h2 services in brpc have to declare interfaces with empty request and response in a .proto file. This requirement keeps all service declarations inside proto files rather than scattering in code, configurations, and proto files. + +# Example + +[http_server.cpp](https://github.com/apache/brpc/blob/master/example/http_c++/http_server.cpp) + +# About h2 + +brpc names the HTTP/2 protocol to "h2", no matter encrypted or not. However HTTP/2 connections without SSL are shown on /connections with the official name "h2c", and the ones with SSL are shown as "h2". + +The APIs for http and h2 in brpc are basically same. Without explicit statement, mentioned http features work for h2 as well. + +# URL types + +## /ServiceName/MethodName as the prefix + +Define a service named `ServiceName`(not including the package name), with a method named `MethodName` and empty request/response, the service will provide http/h2 service on `/ServiceName/MethodName` by default. + +The reason that request and response can be empty is that all data are in Controller: + +- Header of the http/h2 request is in Controller.http_request() and the body is in Controller.request_attachment(). +- Header of the http/h2 response is in Controller.http_response() and the body is in Controller.response_attachment(). + +Implementation steps: + +1. Add the service declaration in a proto file. + +```protobuf +option cc_generic_services = true; + +message HttpRequest { }; +message HttpResponse { }; + +service HttpService { + rpc Echo(HttpRequest) returns (HttpResponse); +}; +``` + +2. Implement the service by inheriting the base class generated in .pb.h, which is same as protobuf services. + +```c++ +class HttpServiceImpl : public HttpService { +public: + ... + virtual void Echo(google::protobuf::RpcController* cntl_base, + const HttpRequest* /*request*/, + HttpResponse* /*response*/, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = static_cast(cntl_base); + + // body is plain text + cntl->http_response().set_content_type("text/plain"); + + // Use printed query string and body as the response. + butil::IOBufBuilder os; + os << "queries:"; + for (brpc::URI::QueryIterator it = cntl->http_request().uri().QueryBegin(); + it != cntl->http_request().uri().QueryEnd(); ++it) { + os << ' ' << it->first << '=' << it->second; + } + os << "\nbody: " << cntl->request_attachment() << '\n'; + os.move_to(cntl->response_attachment()); + } +}; +``` + +3. After adding the implemented instance into the server, the service is accessible via following URLs (Note that the path after `/HttpService/Echo ` is filled into `cntl->http_request().unresolved_path()`, which is always normalized): + +| URL | Protobuf Method | cntl->http_request().uri().path() | cntl->http_request().unresolved_path() | +| -------------------------- | ---------------- | --------------------------------- | -------------------------------------- | +| /HttpService/Echo | HttpService.Echo | "/HttpService/Echo" | "" | +| /HttpService/Echo/Foo | HttpService.Echo | "/HttpService/Echo/Foo" | "Foo" | +| /HttpService/Echo/Foo/Bar | HttpService.Echo | "/HttpService/Echo/Foo/Bar" | "Foo/Bar" | +| /HttpService//Echo///Foo// | HttpService.Echo | "/HttpService//Echo///Foo//" | "Foo" | +| /HttpService | No such method | | | + +## /ServiceName as the prefix + +http/h2 services for managing resources may need this kind of URL, such as `/FileService/foobar.txt` represents `./foobar.txt` and `/FileService/app/data/boot.cfg` represents `./app/data/boot.cfg`. + +Implementation steps: + +1. Use `FileService` as the service name and `default_method` as the method name in the proto file. + +```protobuf +option cc_generic_services = true; + +message HttpRequest { }; +message HttpResponse { }; + +service FileService { + rpc default_method(HttpRequest) returns (HttpResponse); +} +``` + +2. Implement the service. + +```c++ +class FileServiceImpl: public FileService { +public: + ... + virtual void default_method(google::protobuf::RpcController* cntl_base, + const HttpRequest* /*request*/, + HttpResponse* /*response*/, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = static_cast(cntl_base); + cntl->response_attachment().append("Getting file: "); + cntl->response_attachment().append(cntl->http_request().unresolved_path()); + } +}; +``` + +3. After adding the implemented instance into the server, the service is accessible via following URLs (the path after `/FileService` is filled in `cntl->http_request().unresolved_path()`, which is always normalized): + +| URL | Protobuf Method | cntl->http_request().uri().path() | cntl->http_request().unresolved_path() | +| ------------------------------- | -------------------------- | --------------------------------- | -------------------------------------- | +| /FileService | FileService.default_method | "/FileService" | "" | +| /FileService/123.txt | FileService.default_method | "/FileService/123.txt" | "123.txt" | +| /FileService/mydir/123.txt | FileService.default_method | "/FileService/mydir/123.txt" | "mydir/123.txt" | +| /FileService//mydir///123.txt// | FileService.default_method | "/FileService//mydir///123.txt//" | "mydir/123.txt" | + +## Restful URL + +brpc supports specifying a URL for each method in a service. The API is as follows: + +```c++ +// If `restful_mappings' is non-empty, the method in service can +// be accessed by the specified URL rather than /ServiceName/MethodName. +// Mapping rules: "PATH1 => NAME1, PATH2 => NAME2 ..." +// where `PATH' is a valid path and `NAME' is the method name. +int AddService(google::protobuf::Service* service, + ServiceOwnership ownership, + butil::StringPiece restful_mappings); +``` + +`QueueService` defined below contains several methods. If the service is added into the server normally, it's accessible via URLs like `/QueueService/start` and ` /QueueService/stop`. + +```protobuf +service QueueService { + rpc start(HttpRequest) returns (HttpResponse); + rpc stop(HttpRequest) returns (HttpResponse); + rpc get_stats(HttpRequest) returns (HttpResponse); + rpc download_data(HttpRequest) returns (HttpResponse); +}; +``` + +By specifying the 3rd parameter `restful_mappings` to `AddService`, the URL can be customized: + +```c++ +if (server.AddService(&queue_svc, + brpc::SERVER_DOESNT_OWN_SERVICE, + "/v1/queue/start => start," + "/v1/queue/stop => stop," + "/v1/queue/stats/* => get_stats") != 0) { + LOG(ERROR) << "Fail to add queue_svc"; + return -1; +} + +if (server.AddService(&queue_svc, + brpc::SERVER_DOESNT_OWN_SERVICE, + "/v1/*/start => start," + "/v1/*/stop => stop," + "*.data => download_data") != 0) { + LOG(ERROR) << "Fail to add queue_svc"; + return -1; +} +``` + +There are 3 mappings separated by comma in the 3rd parameter (which is a string spanning 3 lines) to the `AddService`. Each mapping tells brpc to call the method at right side of the arrow if the left side matches the URL. The asterisk in `/v1/queue/stats/*` matches any string. + +More about mapping rules: + +- Multiple paths can be mapped to a same method. +- Both http/h2 and protobuf services are supported. +- Un-mapped methods are still accessible via `/ServiceName/MethodName`. Mapped methods are **not** accessible via `/ServiceName/MethodName` anymore. +- `==>` and ` ===>` are both OK, namely extra spaces at the beginning or the end, extra slashes, extra commas at the end, are all accepted. +- Pattern `PATH` and `PATH/*` can coexist. +- Support suffix matching: characters can appear after the asterisk. +- At most one asterisk is allowed in a path. + +The path after asterisk can be obtained by `cntl.http_request().unresolved_path()`, which is always normalized, namely no slashes at the beginning or the end, and no repeated slashes in the middle. For example: + +![img](../images/restful_1.png) + +or: + +![img](../images/restful_2.png) + +in which unresolved_path are both `foo/bar`. The extra slashes at the left, the right, or the middle are removed. + +Note that `cntl.http_request().uri().path()` is not ensured to be normalized, which is `"//v1//queue//stats//foo///bar//////"` and `"//vars///foo////bar/////"` respectively in the above example. + +The built-in service page of `/status` shows customized URLs after the methods, in form of `@URL1 @URL2` ... + +![img](../images/restful_3.png) + +# HTTP Parameters + +## HTTP headers + +HTTP headers are a series of key/value pairs, some of them are defined by the HTTP specification, while others are free to use. + +Query strings are also key/value pairs. Differences between HTTP headers and query strings: + +* Although operations on HTTP headers are accurately defined by the http specification, but http headers cannot be modified directly from an address bar, they are often used for passing parameters of a protocol or framework. +* Query strings is part of the URL and **often** in form of `key1=value1&key2=value2&...`, which is easy to read and modify. They're often used for passing application-level parameters. However format of query strings is not defined in HTTP spec, just a convention. + +```c++ +// Get value for header "User-Agent" (case insensitive) +const std::string* user_agent_str = cntl->http_request().GetHeader("User-Agent"); +if (user_agent_str != NULL) { // has the header + LOG(TRACE) << "User-Agent is " << *user_agent_str; +} +... + +// Add a header "Accept-encoding: gzip" (case insensitive) +cntl->http_response().SetHeader("Accept-encoding", "gzip"); +// Overwrite the previous header "Accept-encoding: deflate" +cntl->http_response().SetHeader("Accept-encoding", "deflate"); +// Append value to the previous header so that it becomes +// "Accept-encoding: deflate,gzip" (values separated by comma) +cntl->http_response().AppendHeader("Accept-encoding", "gzip"); +``` + +## Content-Type + +`Content-type` is a frequently used header for storing type of the HTTP body, and specially processed in brpc and accessible by `cntl->http_request().content_type()` . As a correspondence, `cntl->GetHeader("Content-Type")` returns nothing. + +```c++ +// Get Content-Type +if (cntl->http_request().content_type() == "application/json") { + ... +} +... +// Set Content-Type +cntl->http_response().set_content_type("text/html"); +``` + +If the RPC fails (`Controller` has been `SetFailed`), the framework overwrites `Content-Type` with `text/plain` and sets the response body with `Controller::ErrorText()`. + +## Status Code + +Status code is a special field in HTTP response to store processing result of the http request. Possible values are defined in [http_status_code.h](https://github.com/apache/brpc/blob/master/src/brpc/http_status_code.h). + +```c++ +// Get Status Code +if (cntl->http_response().status_code() == brpc::HTTP_STATUS_NOT_FOUND) { + LOG(FATAL) << "FAILED: " << controller.http_response().reason_phrase(); +} +... +// Set Status code +cntl->http_response().set_status_code(brpc::HTTP_STATUS_INTERNAL_SERVER_ERROR); +cntl->http_response().set_status_code(brpc::HTTP_STATUS_INTERNAL_SERVER_ERROR, "My explanation of the error..."); +``` + +For example, following code implements redirection with status code 302: + +```c++ +cntl->http_response().set_status_code(brpc::HTTP_STATUS_FOUND); +cntl->http_response().SetHeader("Location", "http://bj.bs.bae.baidu.com/family/image001(4979).jpg"); +``` + +![img](../images/302.png) + +## Query String + +As mentioned in above [HTTP headers](#http-headers), query strings are interpreted in common convention, whose form is `key1=value1&key2=value2&…`. Keys without values are acceptable as well and accessible by `GetQuery` which returns an empty string. Such keys are often used as boolean flags. Full API are defined in [uri.h](https://github.com/apache/brpc/blob/master/src/brpc/uri.h). + +```c++ +const std::string* time_value = cntl->http_request().uri().GetQuery("time"); +if (time_value != NULL) { // the query string is present + LOG(TRACE) << "time = " << *time_value; +} + +... +cntl->http_request().uri().SetQuery("time", "2015/1/2"); +``` + +# Debugging + +Turn on [-http_verbose](http://brpc.baidu.com:8765/flags/http_verbose) to print contents of all http requests and responses. Note that this should only be used for debugging rather than online services. + +# Compress the response body + +HTTP services often compress http bodies to reduce transmission latency of web pages and speed up the presentations to end users. + +Call `Controller::set_response_compress_type(brpc::COMPRESS_TYPE_GZIP)` to **try to** compress the http body with gzip. "Try to" means the compression may not happen in following conditions: + +* The request does not set `Accept-encoding` or the value does not contain "gzip". For example, curl does not support compression without option `--compressed`, in which case the server always returns uncompressed results. + +* Body size is less than the bytes specified by -http_body_compress_threshold (512 by default). gzip is not a very fast compression algorithm. When the body is small, the delay added by compression may be larger than the time saved by network transmission. No compression when the body is relatively small is probably a better choice. + + | Name | Value | Description | Defined At | + | ---------------------------- | ----- | ---------------------------------------- | ------------------------------------- | + | http_body_compress_threshold | 512 | Not compress http body when it's less than so many bytes. | src/brpc/policy/http_rpc_protocol.cpp | + +# Decompress the request body + +Due to generality, brpc does not decompress request bodies automatically, but users can do the job by themselves as follows: + +```c++ +#include +... +const std::string* encoding = cntl->http_request().GetHeader("Content-Encoding"); +if (encoding != NULL && *encoding == "gzip") { + butil::IOBuf uncompressed; + if (!brpc::policy::GzipDecompress(cntl->request_attachment(), &uncompressed)) { + LOG(ERROR) << "Fail to un-gzip request body"; + return; + } + cntl->request_attachment().swap(uncompressed); +} +// cntl->request_attachment() contains the data after decompression +``` + +# Serve https requests +https is short for "http over SSL", SSL is not exclusive for http, but effective for all protocols. The generic method for turning on server-side SSL is [here](server.md#turn-on-ssl). + +# Performance + +Productions without extreme performance requirements tend to use HTTP protocol, especially mobile products. Thus we put great emphasis on implementation qualities of HTTP. To be more specific: + +- Use [http parser](https://github.com/apache/brpc/blob/master/src/brpc/details/http_parser.h) of node.js to parse http messages, which is a lightweight, well-written, and extensively used implementation. +- Use [rapidjson](https://github.com/miloyip/rapidjson) to parse json, which is a json library focuses on performance. +- In the worst case, the time complexity of parsing http requests is still O(N), where N is byte size of the request. As a contrast, parsing code that requires the http request to be complete, may cost O(N^2) time in the worst case. This feature is very helpful since many HTTP requests are large. +- Processing HTTP messages from different clients is highly concurrent, even a pretty complicated http message does not block responding other clients. It's difficult to achieve this for other RPC implementations and http servers often based on [single-threaded reactor](threading_overview.md#single-threaded-reactor). + +# Progressive sending + +brpc server is capable of sending large or infinite sized body, in following steps: + +1. Call `Controller::CreateProgressiveAttachment()` to create a body that can be written progressively. The returned `ProgressiveAttachment` object should be managed by `intrusive_ptr` + ```c++ + #include + ... + butil::intrusive_ptr pa = cntl->CreateProgressiveAttachment(); + ``` + +2. Call `ProgressiveAttachment::Write()` to send the data. + + * If the write occurs before running of the server-side done, the sent data is cached until the done is called. + * If the write occurs after running of the server-side done, the sent data is written out in chunked mode immediately. + +3. After usage, destruct all `butil::intrusive_ptr` to release related resources. + +In addition, we can easily implement Server-Sent Events(SSE) with this feature, which enables a client to receive automatic updates from a server via a HTTP connection. SSE could be used to build real-time applications such as chatGPT. Please refer to HttpSSEServiceImpl in [http_server.cpp](https://github.com/apache/brpc/blob/master/example/http_c++/http_server.cpp) for more details. + +# Progressive receiving + +Currently brpc server doesn't support calling the service callback once header part in the http request is parsed. In other words, brpc server is not suitable for receiving large or infinite sized body. + +# FAQ + +### Q: The nginx before brpc encounters final fail + +The error is caused by that brpc server closes the http connection directly without sending a response. + +brpc server supports a variety of protocols on the same port. When a request is failed to be parsed in HTTP, it's hard to tell that the request is definitely in HTTP. If the request is very likely to be one, the server sends HTTP 400 errors and closes the connection. However, if the error is caused HTTP method(at the beginning) or ill-formed serialization (may be caused by bugs at the HTTP client), the server still closes the connection without sending a response, which leads to "final fail" at nginx. + +Solution: When using Nginx to forward traffic, set `$HTTP_method` to allowed HTTP methods or simply specify the HTTP method in `proxy_method`. + +### Q: Does brpc support http chunked mode + +Yes. + +### Q: Why do HTTP requests containing BASE64 encoded query string fail to parse sometimes? + +According to the [HTTP specification](http://tools.ietf.org/html/rfc3986#section-2.2), following characters need to be encoded with `%`. + +``` + reserved = gen-delims / sub-delims + + gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" + + sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + / "*" / "+" / "," / ";" / "=" +``` + +Base64 encoded string may end with `=` which is a reserved character (take `?wi=NDgwMDB8dGVzdA==&anothorkey=anothervalue` as an example). The strings may be parsed successfully, or may be not, depending on the implementation which should not be assumed in principle. + +One solution is to remove the trailing `=` which does not affect the [Base64 decoding](http://en.wikipedia.org/wiki/Base64#Padding). Another method is to [percent-encode](https://en.wikipedia.org/wiki/Percent-encoding) the URI, and do percent-decoding before Base64 decoding. diff --git a/docs/en/io.md b/docs/en/io.md new file mode 100644 index 0000000..6b63f0d --- /dev/null +++ b/docs/en/io.md @@ -0,0 +1,51 @@ +[中文版](../cn/io.md) + +There are three mechanisms to operate IO in general: + +- Blocking IO: once an IO operation is issued, the calling thread is blocked until the IO ends, which is a kind of synchronous IO, such as default actions of posix [read](http://linux.die.net/man/2/read) and [write](http://linux.die.net/man/2/write). +- Non-blocking IO: If there is nothing to read or too much to write, APIs that would block return immediately with an error code. Non-blocking IO is often used with IO multiplexing([poll](http://linux.die.net/man/2/poll), [select](http://linux.die.net/man/2/select), [epoll](http://linux.die.net/man/4/epoll) in Linux or [kqueue](https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2) in BSD). +- Asynchronous IO: Start a read or write operation with a callback, which will be called when the IO is done, such as [OVERLAPPED](https://msdn.microsoft.com/en-us/library/windows/desktop/ms684342(v=vs.85).aspx) + [IOCP](https://msdn.microsoft.com/en-us/library/windows/desktop/aa365198(v=vs.85).aspx) in Windows. Native AIO in Linux only supports files. + +Non-blocking IO is usually used for increasing IO concurrency in Linux. When the IO concurrency is low, non-blocking IO is not necessarily more efficient than blocking IO, which is handled completely by the kernel. System calls like read/write are highly optimized and probably more efficient. But when IO concurrency increases, the drawback of blocking-one-thread in blocking IO arises: the kernel keeps switching between threads to do effective jobs, and a CPU core may only do a little bit of work before being replaced by another thread, causing CPU cache not fully utilized. In addition a large number of threads decrease performance of code dependent on thread-local variables, such as tcmalloc. Once malloc slows down, the overall performance of the program decreases as well. As a contrast, non-blocking IO is typically composed with a relatively small number of event dispatching threads and worker threads(running user code), which are reused by different tasks (in another word, part of scheduling work is moved to userland). Event dispatchers and workers run on different CPU cores simultaneously without frequent switches in the kernel. There's no need to have many threads, so the use of thread-local variables is also more adequate. All these factors make non-blocking IO faster than blocking IO. But non-blocking IO also has its own problems, one of which is more system calls, such as [epoll_ctl](http://man7.org/linux/man-pages/man2/epoll_ctl.2.html). Since epoll is implemented as a red-black tree, epoll_ctl is not a very fast operation, especially in multi-threaded environments. Implementations heavily dependent on epoll_ctl is often confronted with multi-core scalability issues. Non-blocking IO also has to solve a lot of multi-threaded problems, producing more complex code than blocking IO. + +# Receiving messages + +A message is a bounded binary data read from a connection, which may be a request from upstream clients or a response from downstream servers. brpc uses one or several [EventDispatcher](https://github.com/apache/brpc/blob/master/src/brpc/event_dispatcher.cpp)(referred to as EDISP) to wait for events from file descriptors. Unlike the common "IO threads", EDISP is not responsible for reading or writing. The problem of IO threads is that one thread can only read one fd at a given time, other reads may be delayed when many fds in one IO thread are busy. Multi-tenancy, complicated load balancing and [Streaming RPC](streaming_rpc.md) worsen the problem. Under high workloads, regular long delays on one fd may slow down all fds in the IO thread, causing more long tails. + +Because of a [bug](https://web.archive.org/web/20150423184820/https://patchwork.kernel.org/patch/1970231/) of epoll (at the time of developing brpc) and overhead of epoll_ctl, edge triggered mode is used in EDISP. After receiving an event, an atomic variable associated with the fd is added by one atomically. If the variable is zero before addition, a bthread is started to handle the data from the fd. The pthread worker in which EDISP runs is yielded to the newly created bthread to make it start reading ASAP and have a better cache locality. The bthread in which EDISP runs will be stolen to another pthread and keep running, this mechanism is work stealing used in bthreads. To understand exactly how that atomic variable works, you can read [atomic instructions](atomic_instructions.md) first, then check [Socket::StartInputEvent](https://github.com/apache/brpc/blob/master/src/brpc/socket.cpp). These methods make contentions on dispatching events of one fd [wait-free](http://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom). + +In current implementation, `Transport::ProcessEvent` chooses start mode based on `EventDispatcherUnsched()`: `false` uses `bthread_start_urgent`, and `true` uses `bthread_start_background`. In addition, RDMA handles `last_msg` differently between polling and event modes: when `rdma_use_polling=false`, `RdmaTransport::QueueMessage` does not process `last_msg`; in polling mode it continues to process it. And when `EventDispatcherUnsched()` returns `true`, `last_msg` is not processed directly in the current execution flow but in a new bthread. Users can control this behavior through `event_dispatcher_edisp_unsched`. + +[InputMessenger](https://github.com/apache/brpc/blob/master/src/brpc/input_messenger.h) cuts messages and uses customizable callbacks to handle different format of data. `Parse` callback cuts messages from binary data and has relatively stable running time; `Process` parses messages further(such as parsing by protobuf) and calls users' callbacks, which vary in running time. If n(n > 1) messages are read from the fd, InputMessenger launches n-1 bthreads to handle first n-1 messages respectively, and processes the last message in-place. InputMessenger tries protocols one by one. Since one connections often has only one type of messages, InputMessenger remembers current protocol to avoid trying for protocols next time. + +It can be seen that messages from different fds or even same fd are processed concurrently in brpc, which makes brpc good at handling large messages and reducing long tails on processing messages from different sources under high workloads. + +# Sending Messages + +A message is a bounded binary data written to a connection, which may be a response to upstream clients or a request to downstream servers. Multiple threads may send messages to a fd at the same time, however writing to a fd is non-atomic, so how to queue writes from different thread efficiently is a key technique. brpc uses a special wait-free MPSC list to solve the issue. All data ready to write is put into a node of a singly-linked list, whose next pointer points to a special value(`Socket::WriteRequest::UNCONNECTED`). When a thread wants to write out some data, it tries to atomically exchange the node with the list head(Socket::_write_head) first. If the head before exchange is empty, the caller gets the right to write and writes out the data in-place once. Otherwise there must be another thread writing. The caller points the next pointer to the head returned to make the linked list connected. The thread that is writing will see the new head later and write new data. + +This method makes the writing contentions wait-free. Although the thread that gets the right to write is not wait-free nor lock-free in principle and may be blocked by a node that is still UNCONNECTED(the thread issuing write is swapped out by OS just after atomic exchange and before setting the next pointer, within execution time of just one instruction), the blocking rarely happens in practice. In current implementations, if the data cannot be written fully in one call, a KeepWrite bthread is created to write the remaining data. This mechanism is pretty complicated and the principle is depicted below. Read [socket.cpp](https://github.com/apache/brpc/blob/master/src/brpc/socket.cpp) for more details. + +![img](../images/write.png) + +Since writes in brpc always complete within short time, the calling thread can handle new tasks more quickly and background KeepWrite threads also get more tasks to write in one batch, forming pipelines and increasing the efficiency of IO at high throughputs. + +# Socket + +[Socket](https://github.com/apache/brpc/blob/master/src/brpc/socket.h) contains data structures related to fd and is one of the most complex structure in brpc. The unique feature of this structure is that it uses 64-bit SocketId to refer to a Socket object to facilitate usages of fd in multi-threaded environments. Commonly used methods: + +- Create: create a Socket and return its SocketId. +- Address: retrieve Socket from an id, and wrap it into a unique_ptr(SocketUniquePtr) that will be automatically released. When Socket is set failed, the pointer returned is empty. As long as Address returns a non-null pointer, the contents are guaranteed to not change until the pointer is destructed. This function is wait-free. +- SetFailed: Mark a Socket as failed and Address() on corresponding SocketId will return empty pointer (until health checking resumes the socket). Sockets are recycled when no one is referencing it anymore. This function is lock-free. + +We can see that, Socket is similar to [shared_ptr](http://en.cppreference.com/w/cpp/memory/shared_ptr) in the sense of referential counting and SocketId is similar to [weak_ptr](http://en.cppreference.com/w/cpp/memory/weak_ptr). The unique `SetFailed` prevents Socket from being addressed so that the reference count can hit zero finally. Simply using shared_ptr/weak_ptr cannot guarantee this. For example, when a server needs to quit when requests are still coming in frequently, the reference count of Socket may not hit zero and the server is unable to stop quickly. What' more, weak_ptr cannot be directly put into epoll data, but SocketId can. These factors lead to design of Socket which is stable and rarely changed since 2014. + +Using SocketUniquePtr or SocketId depends on if a strong reference is needed. For example, Controller is used thoroughly inside RPC and has a lot of interactions with Socket, it uses SocketUniquePtr. Epoll notifies events on fds and events of a recycled socket can be ignored, so epoll uses SocketId. + +As long as SocketUniquePtr is valid, the Socket enclosed will not be changed so that users have no need to care about race conditions and ABA problems, being safer to operate the shared socket. This method also circumvents implicit referential counting and makes ownership of memory more clear, producing better-quality programs. brpc uses SocketUniquePtr and SocketId a lot to simplify related issues. + +In fact, Socket manages not only the native fd but also other resources, such as SubChannel in SelectiveChannel is also managed by Socket, making SelectiveChannel choose a SubChannel just like a normal channel choosing a downstream server. The faked Socket even implements health checking. Streaming RPC also uses Socket to reuse the code on wait-free write. + +# The full picture + +![img](../images/rpc_flow.png) diff --git a/docs/en/iobuf.md b/docs/en/iobuf.md new file mode 100644 index 0000000..8da47cc --- /dev/null +++ b/docs/en/iobuf.md @@ -0,0 +1,103 @@ +[中文版](../cn/iobuf.md) + +brpc uses [butil::IOBuf](https://github.com/apache/brpc/blob/master/src/butil/iobuf.h) as data structure for attachment in some protocols and HTTP body. It's a non-contiguous zero-copied buffer, proved in previous projects, and good at performance. The interface of `IOBuf` is similar to `std::string`, but not the same. + +If you've used the `BufHandle` in Kylin before, you should notice the convenience of `IOBuf`: the former one is badly encapsulated, leaving the internal structure directly in front of users, who must carefully handle the referential countings, very error prone and leading to bugs. + +# What IOBuf can: + +- Default constructor does not allocate memory. +- Copyable. Modifications to the copy doesn't affect the original one. Copy the managing structure of IOBuf only rather the payload. +- Append another IOBuf without copying payload. +- Can append string, by copying payload. +- Read from or write into file descriptors. +- Serialize to or parse from protobuf messages. +- constructible like a std::ostream using IOBufBuilder. + +# What IOBuf can't: + +- Used as universal string-like structure in the program. Lifetime of IOBuf should be short, to prevent the referentially counted blocks(8K each) in IOBuf lock too many memory. + +# Cut + +Cut 16 bytes from front-side of source_buf and append to dest_buf: + +```c++ +source_buf.cut(&dest_buf, 16); // cut all bytes of source_buf when its length < 16 +``` + +Just pop 16 bytes from front-side of source_buf: + +```c++ +source_buf.pop_front(16); // Empty source_buf when its length < 16 +``` + +# Append + +Append another IOBuf to back-side: + +```c++ +buf.append(another_buf); // no data copy +``` + +Append std::string to back-sie + +```c++ +buf.append(str); // copy data of str into buf +``` + +# Parse + +Parse a protobuf message from the IOBuf + +```c++ +IOBufAsZeroCopyInputStream wrapper(&iobuf); +pb_message.ParseFromZeroCopyStream(&wrapper); +``` + +Parse IOBuf in user-defined formats + +```c++ +IOBufAsZeroCopyInputStream wrapper(&iobuf); +CodedInputStream coded_stream(&wrapper); +coded_stream.ReadLittleEndian32(&value); +... +``` + +# Serialize + +Serialize a protobuf message into the IOBuf + +```c++ +IOBufAsZeroCopyOutputStream wrapper(&iobuf); +pb_message.SerializeToZeroCopyStream(&wrapper); +``` + +Built IOBuf with printable data + +```c++ +IOBufBuilder os; +os << "anything can be sent to std::ostream"; +os.buf(); // IOBuf +``` + +# Print + +Directly printable to std::ostream. Note that the iobuf in following example should only contain printable characters. + +```c++ +std::cout << iobuf << std::endl; +// or +std::string str = iobuf.to_string(); // note: allocating memory +printf("%s\n", str.c_str()); +``` + +# Performance + +IOBuf is good at performance: + +| Action | Throughput | QPS | +| ---------------------------------------- | ----------- | ------- | +| Read from file -> Cut 12+16 bytes -> Copy -> Merge into another buffer ->Write to /dev/null | 240.423MB/s | 8586535 | +| Read from file -> Cut 12+128 bytes -> Copy-> Merge into another buffer ->Write to /dev/null | 790.022MB/s | 5643014 | +| Read from file -> Cut 12+1024 bytes -> Copy-> Merge into another buffer ->Write to /dev/null | 1519.99MB/s | 1467171 | diff --git a/docs/en/json2pb.md b/docs/en/json2pb.md new file mode 100644 index 0000000..fe29483 --- /dev/null +++ b/docs/en/json2pb.md @@ -0,0 +1,7 @@ +# json2pb + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/json2pb.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/lalb.md b/docs/en/lalb.md new file mode 100644 index 0000000..e6b3c0f --- /dev/null +++ b/docs/en/lalb.md @@ -0,0 +1,22 @@ +# Overview + +Locality-aware load balancing(LALB) is an algorithm that can send requests to downstream servers with the lowest latency timely and automatically. The algorithm is originated from the DP system and is now added to brpc! + +Problems that LALB can solve: + +- Round-robin and random policy is not good at scheduling requests since configurations and latencies of downstream servers are different. +- Downstream servers, offline servers and other servers are deployed hybridly and it is hard to predict performance. +- Automatically schedule most of the requests to the server in the same machine. When a problem occurs, try the server acrossing the machines. +- Visit the server in the same server room first. When a problem occurs, try another server room. + +**...** + +# Background + +The most common algorithm to redirect requests is round-robin and random. The premise of these two methods is that the downstream servers and networks are similar. But in the current online environment, especially the hybrid environment, it is difficult to achieve because: + +- Each machine runs a different combination of programs, along with some offline tasks, the available resources of the machine are continuously changing. +- The configurations of machines are different. +- The latencies of network are different. + +These problems have always been there, but are hidden by machine monitoring from hard-working OPs. There are also some attempts in the level of frameworks. For example, [WeightedStrategy](https://svn.baidu.com/public/trunk/ub/ub_client/ubclient_weightstrategy.h) in UB redirects requests based on cpu usage of downstream machines and obviously it cannot solve the latency-related issues, or even cpu issues: since it is implemented as regularly reloading a list of weights, one can imagine that the update frequency cannot be high. A lot of requests may timeout when the load balancer reacts. And there is a math problem here: how to change cpu usage to weight. diff --git a/docs/en/load_balancing.md b/docs/en/load_balancing.md new file mode 100644 index 0000000..4c975da --- /dev/null +++ b/docs/en/load_balancing.md @@ -0,0 +1,7 @@ +# load ualancing + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/load_balancing.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/mbvar_c++.md b/docs/en/mbvar_c++.md new file mode 100644 index 0000000..f31d86c --- /dev/null +++ b/docs/en/mbvar_c++.md @@ -0,0 +1,7 @@ +# muvar c++ + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/mbvar_c++.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/memcache_client.md b/docs/en/memcache_client.md new file mode 100644 index 0000000..c65114c --- /dev/null +++ b/docs/en/memcache_client.md @@ -0,0 +1,102 @@ +[中文版](../cn/memcache_client.md) + +[memcached](http://memcached.org/) is a common caching service. In order to access memcached more conveniently and make full use of bthread's capability of concurrency, brpc directly supports the memcached protocol. Check [example/memcache_c++](https://github.com/apache/brpc/tree/master/example/memcache_c++/) for an example. + +**NOTE**: brpc only supports the binary protocol of memcache. There's little benefit to support the textual protocol which is replaced since memcached 1.3. If your memcached is older than 1.3, upgrade to a newer version. + +Advantages compared to [libmemcached](http://libmemcached.org/libMemcached.html) (the official client): + +- Thread safety. No need to set up separate clients for each thread. +- Support synchronous, asynchronous, semi-synchronous accesses etc. Support [ParallelChannel etc](combo_channel.md) to define access patterns declaratively. +- Support various [connection types](client.md#connection-type). Support timeout, backup request, cancellation, tracing, built-in services, and other benefits offered by brpc. +- Have the concept of requests and responses while libmemcached don't. Users have to do extra bookkeepings to associate received messages with sent messages, which is not trivial. + +The current implementation takes full advantage of the RPC concurrency mechanism and avoids copying as much as possible. A single client can easily pushes a memcached instance (version 1.4.15) on the same machine to its limit: 90,000 QPS for single connection, 330,000 QPS for multiple connections. In most cases, brpc is able to make full use of memcached's capabilities. + +# Request a memcached server + +Create a `Channel` for accessing memcached: + +```c++ +#include +#include + +brpc::ChannelOptions options; +options.protocol = brpc::PROTOCOL_MEMCACHE; +if (channel.Init("0.0.0.0:11211", &options) != 0) { // 11211 is the default port for memcached + LOG(FATAL) << "Fail to init channel to memcached"; + return -1; +} +... +``` + +Following example tries to set data to memcached: + +```c++ +// Set key="hello" value="world" flags=0xdeadbeef, expire in 10s, and ignore cas +brpc::MemcacheRequest request; +brpc::MemcacheResponse response; +brpc::Controller cntl; +if (!request.Set("hello", "world", 0xdeadbeef/*flags*/, 10/*expiring seconds*/, 0/*ignore cas*/)) { + LOG(FATAL) << "Fail to SET request"; + return -1; +} +channel.CallMethod(NULL, &cntl, &request, &response, NULL/*done*/); +if (cntl.Failed()) { + LOG(FATAL) << "Fail to access memcached, " << cntl.ErrorText(); + return -1; +} +if (!response.PopSet(NULL)) { + LOG(FATAL) << "Fail to SET memcached, " << response.LastError(); + return -1; +} +... +``` + +Notes on above code: + +- The class of the request must be `MemcacheRequest`, response must be `MemcacheResponse`, otherwise `CallMethod` fails. `stub` is not necessary, just call `channel.CallMethod` with `method` to NULL. +- Call `request.XXX()` to add an operation, where `XXX` is `Set` in this example. Multiple operations inside a request are sent to a memcached server together (often referred to as "pipeline mode"). +- call `response.PopXXX()` to pop result of an operation from the response, where `XXX` is `Set` in this example. true is returned on success, and false otherwise in which case use `response.LastError()` to get the error message. `XXX` must match the corresponding operation in the request, otherwise the pop is rejected. In above example, a `PopGet` would fail with the error message of "not a GET response". +- Results of `Pop` are independent from the RPC result. Even if "a value cannot be put into the memcached", the RPC may still be successful. RPC failure means things like broken connection, timeout etc. If the business logic requires the memcache operations to be successful, you should test successfulness of both RPC and `PopXXX`. + +Supported operations currently: + +```c++ +bool Set(const Slice& key, const Slice& value, uint32_t flags, uint32_t exptime, uint64_t cas_value); +bool Add(const Slice& key, const Slice& value, uint32_t flags, uint32_t exptime, uint64_t cas_value); +bool Replace(const Slice& key, const Slice& value, uint32_t flags, uint32_t exptime, uint64_t cas_value); +bool Append(const Slice& key, const Slice& value, uint32_t flags, uint32_t exptime, uint64_t cas_value); +bool Prepend(const Slice& key, const Slice& value, uint32_t flags, uint32_t exptime, uint64_t cas_value); +bool Delete(const Slice& key); +bool Flush(uint32_t timeout); +bool Increment(const Slice& key, uint64_t delta, uint64_t initial_value, uint32_t exptime); +bool Decrement(const Slice& key, uint64_t delta, uint64_t initial_value, uint32_t exptime); +bool Touch(const Slice& key, uint32_t exptime); +bool Version(); +``` + +Corresponding operations in replies: + +```c++ +// Call LastError() of the response to check the error text when any following operation fails. +bool PopGet(IOBuf* value, uint32_t* flags, uint64_t* cas_value); +bool PopGet(std::string* value, uint32_t* flags, uint64_t* cas_value); +bool PopSet(uint64_t* cas_value); +bool PopAdd(uint64_t* cas_value); +bool PopReplace(uint64_t* cas_value); +bool PopAppend(uint64_t* cas_value); +bool PopPrepend(uint64_t* cas_value); +bool PopDelete(); +bool PopFlush(); +bool PopIncrement(uint64_t* new_value, uint64_t* cas_value); +bool PopDecrement(uint64_t* new_value, uint64_t* cas_value); +bool PopTouch(); +bool PopVersion(std::string* version); +``` + +# Request a memcached cluster + +Create a `Channel` using the `c_md5` as the load balancing algorithm to access a memcached cluster mounted under a naming service. Note that each `MemcacheRequest` should contain only one operation or all operations have the same key. Under current implementation, multiple operations inside a single request are always sent to a same server. If the keys are located on different servers, the result must be wrong. In which case, you have to divide the request into multilple ones with one operation each. + +Another choice is to use the common [twemproxy](https://github.com/twitter/twemproxy) solution, which makes clients access the cluster just like accessing a single server, although the solution needs to deploy proxies and adds more latency. \ No newline at end of file diff --git a/docs/en/memory_management.md b/docs/en/memory_management.md new file mode 100644 index 0000000..a0a7c4d --- /dev/null +++ b/docs/en/memory_management.md @@ -0,0 +1,7 @@ +# memory management + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/memory_management.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/new_protocol.md b/docs/en/new_protocol.md new file mode 100644 index 0000000..4f6d358 --- /dev/null +++ b/docs/en/new_protocol.md @@ -0,0 +1,153 @@ +# Multi-protocol support in the server side + +brpc server supports all protocols in the same port, and it makes deployment and maintenance more convenient in most of the time. Since the format of different protocols is very different, it is hard to support all protocols in the same port unambiguously. In consider of decoupling and extensibility, it is also hard to build a multiplexer for all protocols. Thus our way is to classify all protocols into three categories and try one by one: + +- First-class protocol: Special characters are marked in front of the protocol data, for example, the data of protocol [baidu_std](baidu_std.md) and hulu_pbrpc begins with 'PRPC' and 'HULU' respectively. Parser just check first four characters to know whether the protocol is matched. This class of protocol is checked first and all these protocols can share one TCP connection. +- Second-class protocol: Some complex protocols without special marked characters can only be detected after several input data are parsed. Currently only HTTP is classified into this category. +- Third-class protocol: Special characters are in the middle of the protocol data, such as the magic number of nshead protocol is the 25th-28th characters. It is complex to handle this case because without reading first 28 bytes, we cannot determine whether the protocol is nshead. If it is tried before http, http messages less than 28 bytes may not be parsed, since the parser consider it as an incomplete nshead message. + +Considering that there will be only one protocol in most connections, we record the result of last selection so that it will be tried first when further data comes. It reduces the overhead of matching protocols to nearly zero for long connections. Although the process of matching protocols will be run every time for short connections, the bottleneck of short connections is not in here and this method is still fast enough. If there are lots of new protocols added into brpc in the future, we may consider some heuristic methods to match protocols. + +# Multi-protocol support in the client side + +Unlike the server side that protocols must be dynamically determined based on the data on the connection, the client side as the originator, naturally know their own protocol format. As long as the protocol data is sent through connection pool or short connection, which means it has exclusive usage of that connection, then the protocol can have any complex (or bad) format. Since the client will record the protocol when sending the data, it will use that recorded protocol to parse the data without any matching overhead when responses come back. There is no magic number in some protocols like memcache, redis, it is hard to distinguish them in the server side, but it has no problem in the client side. + +# Support new protocols + +brpc is designed to add new protocols at any time, just proceed as follows: + +> The protocol that begins with nshead has unified support, just read [this](nshead_service.md). + +## add ProtocolType + +Add new protocol type in ProtocolType in [options.proto](https://github.com/apache/brpc/blob/master/src/brpc/options.proto). If you need to add new protocol, please contact us to add it for you to make sure there is no conflict with protocols of others. + +Currently we support in ProtocolType(at the middle of 2018): +```c++ +enum ProtocolType { + PROTOCOL_UNKNOWN = 0; + PROTOCOL_BAIDU_STD = 1; + PROTOCOL_STREAMING_RPC = 2; + PROTOCOL_HULU_PBRPC = 3; + PROTOCOL_SOFA_PBRPC = 4; + PROTOCOL_RTMP = 5; + PROTOCOL_HTTP = 6; + PROTOCOL_PUBLIC_PBRPC = 7; + PROTOCOL_NOVA_PBRPC = 8; + PROTOCOL_NSHEAD_CLIENT = 9; // implemented in brpc-ub + PROTOCOL_NSHEAD = 10; + PROTOCOL_HADOOP_RPC = 11; + PROTOCOL_HADOOP_SERVER_RPC = 12; + PROTOCOL_MONGO = 13; // server side only + PROTOCOL_UBRPC_COMPACK = 14; + PROTOCOL_DIDX_CLIENT = 15; // Client side only + PROTOCOL_REDIS = 16; // Client side only + PROTOCOL_MEMCACHE = 17; // Client side only + PROTOCOL_ITP = 18; + PROTOCOL_NSHEAD_MCPACK = 19; + PROTOCOL_DISP_IDL = 20; // Client side only + PROTOCOL_ERSDA_CLIENT = 21; // Client side only + PROTOCOL_UBRPC_MCPACK2 = 22; // Client side only + // Reserve special protocol for cds-agent, which depends on FIFO right now + PROTOCOL_CDS_AGENT = 23; // Client side only + PROTOCOL_ESP = 24; // Client side only + PROTOCOL_THRIFT = 25; // Server side only +} +``` +## Implement Callbacks + +All callbacks are defined in struct Protocol, which is defined in [protocol.h](https://github.com/apache/brpc/blob/master/src/brpc/protocol.h). Among all these callbacks, `parse` is a callback that must be implemented. Besides, `process_request` must be implemented in the server side and `serialize_request`, `pack_request`, `process_response` must be implemented in the client side. + +It is difficult to implement callbacks of the protocol. These codes are not like the codes that ordinary users use which has good prompts and protections. You have to figure it out how to handle similar code in other protocols and implement your own protocol, then send it to us to do code review. + +### parse + +```c++ +typedef ParseResult (*Parse)(butil::IOBuf* source, Socket *socket, bool read_eof, const void *arg); +``` +This function is used to cut messages from source. Client side and server side must share the same parse function. The returned message will be passed to `process_request`(server side) or `process_response`(client side). + +Argument: source is the binary content from remote side, socket is the corresponding connection, read_eof is true iff the connection is closed by remote, arg is a pointer to the corresponding server in server client and NULL in client side. + +ParseResult could be an error or a cut message, its possible value contains: + +- PARSE_ERROR_TRY_OTHERS: current protocol is not matched, the framework would try next protocol. The data in source cannot be comsumed. +- PARSE_ERROR_NOT_ENOUGH_DATA: the input data hasn't violated the current protocol yet, but the whole message cannot be detected as well. When there is new data from connection, new data will be appended to source and parse function is called again. If we can determine that data fits current protocol, the content of source can also be transferred to the internal state of protocol. For example, if source doesn't contain a whole http message, it will be consumed by http parser to avoid repeated parsing. +- PARSE_ERROR_TOO_BIG_DATA: message size is too big, the connection will be closed to protect server. +- PARSE_ERROR_NO_RESOURCE: internal error, such as resource allocation failure. Connections will be closed. +- PARSE_ERROR_ABSOLUTELY_WRONG: it is supposed to be some protocols(magic number is matched), but the format is not as expected. Connection will be closed. + +### serialize_request +```c++ +typedef bool (*SerializeRequest)(butil::IOBuf* request_buf, + Controller* cntl, + const google::protobuf::Message* request); +``` +This function is used to serialize request into request_buf that client must implement. It happens before a RPC call and will only be called once. Necessary information needed by some protocols(such as http) is contained in cntl. Return true if succeed, otherwise false. + +### pack_request +```c++ +typedef int (*PackRequest)(butil::IOBuf* msg, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* controller, + const butil::IOBuf& request_buf, + const Authenticator* auth); +``` +This function is used to pack request_buf into msg, which is called every time before sending messages to server(including retrying). When auth is not NULL, authentication information is also needed to be packed. Return 0 if succeed, otherwise -1. + +### process_request +```c++ +typedef void (*ProcessRequest)(InputMessageBase* msg_base); +``` +This function is used to parse request messages in the server side that server must implement. It and parse() may run in different threads. Multiple process_request may run simultaneously. After the processing is done, msg_base->Destroy() must be called. In order to prevent forgetting calling Destroy, consider using DestroyingPtr<>. + +### process_response +```c++ +typedef void (*ProcessResponse)(InputMessageBase* msg); +``` +This function is used to parse message response in client side that client must implement. It and parse() may run in different threads. Multiple process_request may run simultaneously. After the processing is done, msg_base->Destroy() must be called. In order to prevent forgetting calling Destroy, consider using DestroyingPtr<>. + +### verify +```c++ +typedef bool (*Verify)(const InputMessageBase* msg); +``` +This function is used to authenticate connections, it is called when the first message is received. It is must be implemented by servers that need authentication, otherwise the function pointer can be NULL. Return true if succeed, otherwise false. + +### parse_server_address +```c++ +typedef bool (*ParseServerAddress)(butil::EndPoint* out, const char* server_addr_and_port); +``` +This function converts server_addr_and_port(an argument of Channel.Init) to butil::EndPoint, which is optional. Some protocols may differ in the expression and understanding of server addresses. + +### get_method_name +```c++ +typedef const std::string& (*GetMethodName)(const google::protobuf::MethodDescriptor* method, + const Controller*); +``` +This function is used to customize method name, which is optional. + +### supported_connection_type + +Used to mark the supported connection method. If all connection methods are supported, this value should set to CONNECTION_TYPE_ALL. If connection pools and short connections are supported, this value should set to CONNECTION_TYPE_POOLED_AND_SHORT. + +### name + +The name of the protocol, which appears in the various configurations and displays, should be as short as possible and must be a string constant. + +## Register to global + +RegisterProtocol should be called to [register implemented protocol](https://github.com/apache/brpc/blob/master/src/brpc/global.cpp) to brpc, just like: + +```c++ +Protocol http_protocol = { ParseHttpMessage, + SerializeHttpRequest, PackHttpRequest, + ProcessHttpRequest, ProcessHttpResponse, + VerifyHttpRequest, ParseHttpServerAddress, + GetHttpMethodName, + CONNECTION_TYPE_POOLED_AND_SHORT, + "http" }; +if (RegisterProtocol(PROTOCOL_HTTP, http_protocol) != 0) { + exit(1); +} +``` diff --git a/docs/en/nshead_service.md b/docs/en/nshead_service.md new file mode 100644 index 0000000..54651c2 --- /dev/null +++ b/docs/en/nshead_service.md @@ -0,0 +1,7 @@ +# nshead service + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/nshead_service.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/overview.md b/docs/en/overview.md new file mode 100644 index 0000000..0a704dd --- /dev/null +++ b/docs/en/overview.md @@ -0,0 +1,91 @@ +[中文版](../cn/overview.md) + +# What is RPC? + +Most machines on internet communicate with each other via [TCP/IP](https://en.wikipedia.org/wiki/Internet_protocol_suite). However, TCP/IP only guarantees reliable data transmissions. We need to abstract more to build services: + +* What is the format of data transmission? Different machines and networks may have different byte-orders, directly sending in-memory data is not suitable. Fields in the data are added, modified or removed gradually, how do newer services talk with older services? +* Can TCP connection be reused for multiple requests to reduce overhead? Can multiple requests be sent through one TCP connection simultaneously? +* How to talk with a cluster with many machines? +* What should I do when the connection is broken? What if the server does not respond? +* ... + +[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) addresses the above issues by abstracting network communications as "clients accessing functions on servers": client sends a request to server, wait until server receives -> processes -> responds to the request, then do actions according to the result. +![rpc.png](../images/rpc.png) + +Let's see how the issues are solved. + +* RPC needs serialization which is done by [protobuf](https://github.com/google/protobuf) pretty well. Users fill requests in format of protobuf::Message, do RPC, and fetch results from responses in protobuf::Message. protobuf has good forward and backward compatibility for users to change fields and build services incrementally. For http services, [json](http://www.json.org/) is used for serialization extensively. +* Establishment and re-using of connections is transparent to users, but users can make choices like [different connection types](client.md#connection-type): short, pooled, single. +* Machines are discovered by a Naming Service, which can be implemented by [DNS](https://en.wikipedia.org/wiki/Domain_Name_System), [ZooKeeper](https://zookeeper.apache.org/) or [etcd](https://github.com/coreos/etcd). Inside Baidu, we use BNS (Baidu Naming Service). brpc provides ["list://" and "file://"](client.md#naming-service) as well. Users specify load balancing algorithms to choose one machine for each request from all machines, including: round-robin, randomized, [consistent-hashing](../cn/consistent_hashing.md)(murmurhash3 or md5) and [locality-aware](../cn/lalb.md). +* RPC retries when the connection is broken. When server does not respond within the given time, client fails with a timeout error. + +# Where can I use RPC? + +Almost all network communications. + +RPC can't do everything surely, otherwise we don't need the layer of TCP/IP. But in most network communications, RPC meets requirements and isolates the underlying details. + +Common doubts on RPC: + +- My data is binary and large, using protobuf will be slow. First, this is possibly a wrong feeling and you will have to test it and prove it with [profilers](../cn/cpu_profiler.md). Second, many protocols support carrying binary data along with protobuf requests and bypass the serialization. +- I'm sending streaming data which can't be processed by RPC. Actually many protocols in RPC can handle streaming data, including [ProgressiveReader in http](http_client.md#progressively-download), streams in h2, [streaming rpc](streaming_rpc.md), and RTMP which is a specialized streaming protocol. +- I don't need replies. With some inductions, we know that in your scenario requests can be dropped at any stage because the client is always unaware of the situation. Are you really sure this is acceptable? Even if you don't need the reply, we recommend sending back small-sized replies, which are unlikely to be performance bottlenecks and will probably provide valuable clues when debugging complex bugs. + +# What is ![brpc](../images/logo.png)? + +An industrial-grade RPC framework used throughout [Baidu](http://ir.baidu.com/phoenix.zhtml?c=188488&p=irol-irhome), with 1,000,000+ instances(not counting clients) and thousands kinds of services, called "**baidu-rpc**" inside Baidu. Only C++ implementation is opensourced right now. + +You can use it to: +* Build a server that can talk in multiple protocols (**on same port**), or access all sorts of services + * restful http/https, [h2](https://http2.github.io/http2-spec)/[gRPC](https://grpc.io). using http/h2 in brpc is much more friendly than [libcurl](https://curl.haxx.se/libcurl/). Access protobuf-based protocols with HTTP/h2+json, probably from another language. + * [redis](redis_client.md) and [memcached](memcache_client.md), thread-safe, more friendly and performant than the official clients + * [rtmp](https://github.com/apache/brpc/blob/master/src/brpc/rtmp.h)/[flv](https://en.wikipedia.org/wiki/Flash_Video)/[hls](https://en.wikipedia.org/wiki/HTTP_Live_Streaming), for building [streaming services](https://github.com/brpc/media-server). + * hadoop_rpc (may be opensourced) + * [rdma](https://en.wikipedia.org/wiki/Remote_direct_memory_access) support (will be opensourced) + * [thrift](thrift.md) support, thread-safe, more friendly and performant than the official clients. + * all sorts of protocols used in Baidu: [baidu_std](../cn/baidu_std.md), [streaming_rpc](streaming_rpc.md), hulu_pbrpc, [sofa_pbrpc](https://github.com/baidu/sofa-pbrpc), nova_pbrpc, public_pbrpc, ubrpc, and nshead-based ones. + * Build [HA](https://en.wikipedia.org/wiki/High_availability) distributed services using an industrial-grade implementation of [RAFT consensus algorithm](https://raft.github.io) which is opensourced at [braft](https://github.com/brpc/braft) +* Servers can handle requests [synchronously](server.md) or [asynchronously](server.md#asynchronous-service). +* Clients can access servers [synchronously](client.md#synchronus-call), [asynchronously](client.md#asynchronous-call), [semi-synchronously](client.md#semi-synchronous-call), or use [combo channels](combo_channel.md) to simplify sharded or parallel accesses declaratively. +* Debug services [via http](builtin_service.md), and run [cpu](../cn/cpu_profiler.md), [heap](../cn/heap_profiler.md) and [contention](../cn/contention_profiler.md) profilers. +* Get [better latency and throughput](#better-latency-and-throughput). +* [Extend brpc](new_protocol.md) with the protocols used in your organization quickly, or customize components, including [naming services](../cn/load_balancing.md#命名服务) (dns, zk, etcd), [load balancers](../cn/load_balancing.md#负载均衡) (rr, random, consistent hashing) + +# Advantages of brpc + +### More friendly API + +Only 3 (major) user headers: [Server](https://github.com/apache/brpc/blob/master/src/brpc/server.h), [Channel](https://github.com/apache/brpc/blob/master/src/brpc/channel.h), [Controller](https://github.com/apache/brpc/blob/master/src/brpc/controller.h), corresponding to server-side, client-side and parameter-set respectively. You don't have to worry about "How to initialize XXXManager", "How to layer all these components together", "What's the relationship between XXXController and XXXContext". All you need to do is simple: + +* Build service? include [brpc/server.h](https://github.com/apache/brpc/blob/master/src/brpc/server.h) and follow the comments or [examples](https://github.com/apache/brpc/blob/master/example/echo_c++/server.cpp). + +* Access service? include [brpc/channel.h](https://github.com/apache/brpc/blob/master/src/brpc/channel.h) and follow the comments or [examples](https://github.com/apache/brpc/blob/master/example/echo_c++/client.cpp). + +* Tweak parameters? Checkout [brpc/controller.h](https://github.com/apache/brpc/blob/master/src/brpc/controller.h). Note that the class is shared by server and channel. Methods are separated into 3 parts: client-side, server-side and both-side. + +We tried to make simple things simple. Take naming service as an example. In older RPC implementations you may need to copy a pile of obscure code to make it work, however, in brpc accessing BNS is expressed as `Init("bns://node-name", ...)`, DNS is `Init("http://domain-name", ...)` and local machine list is `Init("file:///home/work/server.list", ...)`. Without any explanation, you know what it means. + +### Make services more reliable + +brpc is extensively used in Baidu: + +* map-reduce service & table storages +* high-performance computing & model training +* all sorts of indexing & ranking servers +* …. + +It's been proven. + +brpc pays special attentions to development and maintenance efficency, you can [view internal status of servers](builtin_service.md) in web browser or with curl, analyze [cpu hotspots](../cn/cpu_profiler.md), [heap allocations](../cn/heap_profiler.md) and [lock contentions](../cn/contention_profiler.md) of online services, measure stats by [bvar](bvar.md) which is viewable in [/vars](vars.md). + +### Better latency and throughput + +Although almost all RPC implementations claim that they're "high-performant", the numbers are probably just numbers. Being really high-performant in different scenarios is difficult. To unify communication infra inside Baidu, brpc goes much deeper at performance than other implementations. + +* Reading and parsing requests from different clients is fully parallelized and users don't need to distinguish between "IO-threads" and "Processing-threads". Other implementations probably have "IO-threads" and "Processing-threads" and hash file descriptors(fd) into IO-threads. When a IO-thread handles one of its fds, other fds in the thread can't be handled. If a message is large, other fds are significantly delayed. Although different IO-threads run in parallel, you won't have many IO-threads since they don't have too much to do generally except reading/parsing from fds. If you have 10 IO-threads, one fd may affect 10% of all fds, which is unacceptable to industrial online services (requiring 99.99% availability). The problem will be worse when fds are distributed unevenly across IO-threads (unfortunately common), or the service is multi-tenancy (common in cloud services). In brpc, reading from different fds is parallelized and even processing different messages from one fd is parallelized as well. Parsing a large message does not block other messages from the same fd, not to mention other fds. More details can be found [here](io.md#receiving-messages). +* Writing into one fd and multiple fds is highly concurrent. When multiple threads write into the same fd (common for multiplexed connections), the first thread directly writes in-place and other threads submit their write requests in [wait-free](https://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom) manner. One fd can be written into 5,000,000 16-byte messages per second by a couple of highly-contended threads. More details can be found [here](io.md#sending-messages). +* Minimal locks. High-QPS services can utilize all CPU power on the machine. For example, [creating bthreads](../cn/memory_management.md) for processing requests, [setting up timeout](../cn/timer_keeping.md), [finding RPC contexts](../cn/bthread_id.md) according to response, [recording performance counters](bvar.md) are all highly concurrent. Users see very few contentions (via [contention profiler](../cn/contention_profiler.md)) caused by RPC framework even if the service runs at 500,000+ QPS. +* Server adjusts thread number according to load. Traditional implementations set number of threads according to latency to avoid limiting the throughput. brpc creates a new [bthread](../cn/bthread.md) for each request and ends the bthread when the request is done, which automatically adjusts thread number according to load. + +Check [benchmark](../cn/benchmark.md) for a comparison between brpc and other implementations. diff --git a/docs/en/parallel_http.md b/docs/en/parallel_http.md new file mode 100644 index 0000000..1448d6b --- /dev/null +++ b/docs/en/parallel_http.md @@ -0,0 +1,7 @@ +# parallel http + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/parallel_http.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/rdma.md b/docs/en/rdma.md new file mode 100644 index 0000000..98ac698 --- /dev/null +++ b/docs/en/rdma.md @@ -0,0 +1,88 @@ +# Build + +Since RDMA requires driver and hardware support, only the build on linux is verified. + +With config_brpc: +```bash +sh config_brpc.sh --with-rdma --headers="/usr/include" --libs="/usr/lib64 /usr/bin" +make + +cd example/rdma_performance # example for rdma +make +``` + +With cmake: +```bash +mkdir bld && cd bld && cmake -DWITH_RDMA=ON .. +make + +cd example/rdma_performance # example for rdma +mkdir bld && cd bld && cmake .. +make +``` + +With bazel: +```bash +# Server +bazel build --define=BRPC_WITH_RDMA=true example:rdma_performance_server +# Client +bazel build --define=BRPC_WITH_RDMA=true example:rdma_performance_client +``` + +# Basic Implementation + +RDMA does not use socket API like TCP. However, the brpc::Socket class is still used. If a user sets ChannelOptions.use_rdma or ServerOptions.use_rdma to true, the Socket class created has RdmaEndpoint (see src/brpc/rdma/rdma_endpoint.cpp). When RDMA is enabled, the data which need to transmit will be posted to RDMA QP with verbs API, not written to TCP fd. For data receiving, RdmaEndpoint will get completions from RDMA CQ with verbs API (the event will be generated from a dedicated fd and be added into EventDispatcher, the handling function is RdmaEndpoint::PollCq) before parsing RPC messages with InputMessenger. + +brpc uses RDMA RC mode. Every RdmaEndpoint has its own QP. Before establishing RDMA connection, a TCP connection is necessary to exchange some information such as GID and QPN. We call this procedure handshake. Since handshake needs TCP connection, the TCP fd in the corresponding Socket is still valid. The handshake procedure is completed in the AppConnect way in brpc. The TCP connection will keep in EST state but not be used for data transmission after RDMA connection is established. Once the TCP connection is closed, the corresponding RDMA connection will be set error. + +The first key feature in RdmaEndpoint data transmission is zero copy. All data which need to transmit is in the Blocks of IOBuf. Thus all the Blocks need to be released after the sent CQEs are triggered. The reference of these Blocks are stored in RdmaEndpoint::_sbuf. In order to realize receiving zero copy, the receive side must post receive buffers in Blocks of IOBuf, which are stored in RdmaEndpoint::_rbuf. Note that all the Blocks posted in the receive side has a fixed size (recv_block_size). The transmit side can only send message smaller than that. Otherwise the receive side cannot receive data successfully. + +The second key feature in RdmaEndpoint data transmission is sliding window flow control. The flow control is to avoid fast transmit side overwhelming slow receive side. TCP has similar mechanism in kernel TCP stack. RdmaEndpoint implements this mechanism with explicit ACKs from receive side. to reduce the overhead of ACKs, the ACK number can be piggybacked in ordinary data message as immediate data. + +The third key feature in RdmaEndpoint data transmission is event suppression. The size of every message is limited to recv_block_size (default is 8KB). If every message will generate an event, the performance will be very poor, even worse than TCP (TCP has GSO/GRO). Therefore, RdmaEndpoint set solicited flag for every message according to data size, window and ACKS. The flag can control whether to generate an event in remove side or not. + +All the memory used for data transmission in RDMA must be registered, which is very inefficient. Generally, a memory pool is employed to avoid frequent memory registration. In fact, brpc uses IOBuf for data transmission. In order to realize total zerocopy and compatibility with IOBuf, the memory used by IOBuf is taken over by the RDMA memory pool (see src/brpc/rdma/block_pool.cpp). Since IOBuf buffer cannot be controlled by user directly, the total memory consumption in IOBuf should be carefully managed. It is suggested that the application registers enough memory at one time according to its requirement. + +The application can manage memory by itself and send data with IOBuf::append_user_data_with_meta. In this case, the application should register memory by itself with rdma::RegisterMemoryForRdma (see src/brpc/rdma/rdma_helper.h). Note that RegisterMemoryForRdma returns the lkey for registered memory. Please provide this lkey with data together when calling append_user_data_with_meta. + +RDMA is hardware-related. It has some different concepts such as device, port, GID, LID, MaxSge and so on. These parameters can be read from NICs at initialization, and brpc will make the default choice (see src/brpc/rdma/rdma_helper.cpp). Sometimes the default choice is not the expectation, then it can be changed in the flag way. + +`event_dispatcher_edisp_unsched` is a global flag and affects EventDispatcher scheduling in both normal mode (TCP) and RDMA mode. +`true` means EventDispatcher is unschedulable, and `false` means schedulable (default). + +Runtime behavior is determined directly from user-provided values. + +Recommended usage: +1. Keep the default `false` when unsched is not needed. +2. Set `-event_dispatcher_edisp_unsched=true` when unsched behavior is required. + +Examples: +1. `-event_dispatcher_edisp_unsched=false`: both TCP and RDMA are schedulable. +2. `-event_dispatcher_edisp_unsched=true`: both TCP and RDMA are unschedulable. + +# Parameters + +Configurable parameters: +* rdma_trace_verbose: to print RDMA connection information in log,default is false. +* rdma_recv_zerocopy: enable zero copy in receive side,default is true. +* rdma_zerocopy_min_size: the min message size for receive zero copy (in Byte),default is 512. +* rdma_recv_block_type: the block type used for receiving, can be default(8KB)/large(64KB)/huge(2MB),default is default. +* rdma_prepared_qp_size: the size of QPs created at the beginning of the application,default is 128. +* rdma_prepared_qp_cnt: the number of QPs created at the beginning of the application,default is 1024. +* rdma_max_sge: the max length of sglist, default is 0, which is the max length allowed by the device. +* rdma_sq_size: the size of SQ,default is 128. +* rdma_rq_size: the size of RQ,default is 128. +* rdma_cqe_poll_once: the number of CQE pooled from CQ once,default is 32. +* rdma_gid_index: the index of local GID table used,default is -1,which is the maximum GID index. +* rdma_port: the port number used,default is 1. +* rdma_device: the IB device name,default is empty,which is the first active device. +* rdma_memory_pool_initial_size_mb: the initial region size of RDMA memory pool (in MB),default is 1024. +* rdma_memory_pool_increase_size_mb: the step increase region size of RDMA memory pool (in MB),default is 1024. +* rdma_memory_pool_max_regions: the max number of regions in RDMA memory pool,default is 3. +* rdma_memory_pool_buckets: the number of buckets for avoiding mutex contention in RDMA memory pool,default is 4. +* rdma_memory_pool_tls_cache_num: the number of thread local cached blocks in RDMA memory pool,default is 128. +* rdma_use_polling: Whether to use RDMA polling mode, default is false. +* rdma_poller_num: The number of pollers in polling mode, default is 1. +* rdma_poller_yield: Whether pollers in polling mode voluntarily relinquish the CPU, default is false. +* event_dispatcher_edisp_unsched: Global switch for EventDispatcher scheduling (true means unschedulable), default is false. +* rdma_disable_bthread: Disables bthread, default is false. diff --git a/docs/en/redis_client.md b/docs/en/redis_client.md new file mode 100644 index 0000000..2dbc6bf --- /dev/null +++ b/docs/en/redis_client.md @@ -0,0 +1,321 @@ +[中文版](../cn/redis_client.md) + +[redis](http://redis.io/) is one of the most popular caching service in recent years. Compared to memcached it provides users with more data structures and operations, speeding up developments. In order to access redis servers more conveniently and make full use of bthread's capability of concurrency, brpc directly supports the redis protocol. Check [example/redis_c++](https://github.com/apache/brpc/tree/master/example/redis_c++/) for an example. + +Advantages compared to [hiredis](https://github.com/redis/hiredis) (the official redis client): + +- Thread safety. No need to set up separate clients for each thread. +- Support synchronous, asynchronous, semi-synchronous accesses etc. Support [ParallelChannel etc](combo_channel.md) to define access patterns declaratively. +- Support various [connection types](client.md#connection-type). Support timeout, backup request, cancellation, tracing, built-in services, and other benefits offered by brpc. +- All brpc clients in a process share a single connection to one redis-server, which is more efficient when multiple threads access one redis-server simultaneously (see [performance](#Performance)). Memory is allocated in blocks regardless of complexity of the reply, and short string optimization (SSO) is implemented to further improve performance. + +Similarly with http, brpc guarantees that the time complexity of parsing redis replies is O(N) in worst cases rather than O(N^2) , where N is the number of bytes of reply. This is important when the reply consists of large arrays. + +Turn on [-redis_verbose](#Debug) to print contents of all redis requests and responses, which is for debugging only. + +# Request a redis server + +Create a `Channel` for accessing redis: + +```c++ +#include +#include + +brpc::ChannelOptions options; +options.protocol = brpc::PROTOCOL_REDIS; +brpc::Channel redis_channel; +if (redis_channel.Init("0.0.0.0:6379", &options) != 0) { // 6379 is the default port for redis-server + LOG(ERROR) << "Fail to init channel to redis-server"; + return -1; +} +... +``` + +Execute `SET`, then `INCR`: + +```c++ +std::string my_key = "my_key_1"; +int my_number = 1; +... +// Execute "SET " +brpc::RedisRequest set_request; +brpc::RedisResponse response; +brpc::Controller cntl; +set_request.AddCommand("SET %s %d", my_key.c_str(), my_number); +redis_channel.CallMethod(NULL, &cntl, &set_request, &response, NULL/*done*/); +if (cntl.Failed()) { + LOG(ERROR) << "Fail to access redis-server"; + return -1; +} +// Get a reply by calling response.reply(i) +if (response.reply(0).is_error()) { + LOG(ERROR) << "Fail to set"; + return -1; +} +// A reply is printable in multiple ways +LOG(INFO) << response.reply(0).c_str() // OK + << response.reply(0) // OK + << response; // OK +... + +// Execute "INCR " +brpc::RedisRequest incr_request; +incr_request.AddCommand("INCR %s", my_key.c_str()); +response.Clear(); +cntl.Reset(); +redis_channel.CallMethod(NULL, &cntl, &incr_request, &response, NULL/*done*/); +if (cntl.Failed()) { + LOG(ERROR) << "Fail to access redis-server"; + return -1; +} +if (response.reply(0).is_error()) { + LOG(ERROR) << "Fail to incr"; + return -1; +} +// A reply is printable in multiple ways +LOG(INFO) << response.reply(0).integer() // 2 + << response.reply(0) // (integer) 2 + << response; // (integer) 2 +``` + +Execute `incr` and `decr` in batch: + +```c++ +brpc::RedisRequest request; +brpc::RedisResponse response; +brpc::Controller cntl; +request.AddCommand("INCR counter1"); +request.AddCommand("DECR counter1"); +request.AddCommand("INCRBY counter1 10"); +request.AddCommand("DECRBY counter1 20"); +redis_channel.CallMethod(NULL, &cntl, &request, &response, NULL/*done*/); +if (cntl.Failed()) { + LOG(ERROR) << "Fail to access redis-server"; + return -1; +} +CHECK_EQ(4, response.reply_size()); +for (int i = 0; i < 4; ++i) { + CHECK(response.reply(i).is_integer()); + CHECK_EQ(brpc::REDIS_REPLY_INTEGER, response.reply(i).type()); +} +CHECK_EQ(1, response.reply(0).integer()); +CHECK_EQ(0, response.reply(1).integer()); +CHECK_EQ(10, response.reply(2).integer()); +CHECK_EQ(-10, response.reply(3).integer()); +``` + +# Request redis with authenticator + +Create a RedisAuthenticator, and set to ChannelOptions. + +```c++ +brpc::ChannelOptions options; +brpc::policy::RedisAuthenticator* auth = new brpc::policy::RedisAuthenticator("my_password"); +options.auth = auth; +``` + + +# RedisRequest + +A [RedisRequest](https://github.com/apache/brpc/blob/master/src/brpc/redis.h) may contain multiple commands by calling `AddCommand*`, which returns true on success and false otherwise. **The callsite backtrace is also printed on error**. + +```c++ +bool AddCommand(const char* fmt, ...); +bool AddCommandV(const char* fmt, va_list args); +bool AddCommandByComponents(const butil::StringPiece* components, size_t n); +``` + +Formatting is compatible with hiredis, namely `%b` corresponds to binary data (pointer + length), others are similar to those in `printf`. Some improvements have been made such as characters enclosed by single or double quotes are recognized as one field regardless of the spaces inside. For example, `AddCommand("Set 'a key with space' 'a value with space as well'")` sets value `a value with space as well` to key `a key with space`, while in hiredis the command must be written as `redisvCommand(..., "SET% s% s", "a key with space", "a value with space as well");` + +> Note that if the `fmt` parameter of `AddCommand` and `AddCommandV` is set incorrectly, it may cause the program to crash or lead to data leakage. Please set it with caution. Should not use user input-influenced content as the `fmt` parameter! + +`AddCommandByComponents` is similar to `redisCommandArgv` in hiredis. Users specify each part of the command in an array, which is immune to escaping issues often occurring in `AddCommand` and `AddCommandV`. If you encounter errors such as "Unmatched quote" or "invalid format" when using `AddCommand` and `AddCommandV`, try this method. + +If `AddCommand*` fails, subsequent `AddCommand*` and `CallMethod` also fail. In general, there is no need to check return value of `AddCommand*`, since the RPC fails directly anyway. + +Use `command_size()` to get number of commands added successfully. + +Call `Clear()` before re-using the `RedisRequest` object. + +# RedisResponse + +A [RedisResponse](https://github.com/apache/brpc/blob/master/src/brpc/redis.h) may contain one or multiple [RedisReply](https://github.com/apache/brpc/blob/master/src/brpc/redis_reply.h)s. Use `reply_size()` for total number of replies and `reply(i)` for reference to the i-th reply (counting from 0). Note that in hiredis, if the request contains N commands, you have to call `redisGetReply` N times to get replies, while it's unnecessary in brpc as the `RedisResponse` already includes the N replies which are accessible by reply(i). As long as RPC is successful, `response.reply_size()` should be equal to `request.command_size()`, unless the redis-server is buggy. The precondition that redis works correctly is that replies correspond to commands one by one in the same sequence (positional correspondence). + +Each `RedisReply` may be: + +- REDIS_REPLY_NIL: NULL in redis, which means value does not exist. Testable by `is_nil()`. +- REDIS_REPLY_STATUS: Referred to `Simple String` in the redis document, usually used as the status of operations, such as the `OK` returned by `SET`. Testable by `is_string()` (same function for REDIS_REPLY_STRING). Use `c_str()` or `data()` to get the value. +- REDIS_REPLY_STRING: Referred to `Bulk String` in the redis document. Most return values are of this type, including those returned by `incr`. Testable by `is_string()`. Use `c_str()` or `data()` for the value. +- REDIS_REPLY_ERROR: The error message for a failed operation. Testable by `is_error()`. Use `error_message()` to get the message. +- REDIS_REPLY_INTEGER: A 64-bit signed integer. Testable by `is_integer()`. Use `integer()` to get the value. +- REDIS_REPLY_ARRAY: Array of replies. Testable by `is_array()`. Use `size()` for size of the array and `[i]` for the reference to the corresponding sub-reply. + +If a response contains three replies: an integer, a string and an array with 2 items, we can use `response.reply(0).integer()`, `response.reply(1).c_str()`, and `repsonse.reply(2)[0]`, `repsonse.reply(2)[1]` to fetch values respectively. If the type is not correct, backtrace of the callsite is printed and an undefined value is returned. + +Ownership of all replies belongs to `RedisResponse`. All relies are destroyed when response is destroyed. + +Call `Clear()` before re-using the `RedisRespones` object. + +# Request a redis cluster + +Create a `Channel` using the consistent hashing as the load balancing algorithm(c_md5 or c_murmurhash) to access a redis cluster mounted under a naming service. Note that each `RedisRequest` should contain only one command or all commands have the same key. Under current implementation, multiple commands inside a single request are always sent to a same server. If the keys are located on different servers, the result must be wrong. In which case, you have to divide the request into multilple ones with one command each. + +Another choice is to use the common [twemproxy](https://github.com/twitter/twemproxy) solution, which makes clients access the cluster just like accessing a single server, although the solution needs to deploy proxies and adds more latency. + +For native Redis Cluster (slot based routing, MOVED/ASK redirection and topology refresh from `CLUSTER SLOTS`/`CLUSTER NODES`), use `brpc::RedisClusterChannel`: + +```c++ +#include + +brpc::RedisClusterChannel channel; +brpc::RedisClusterChannelOptions options; +options.max_redirect = 5; +if (channel.Init("127.0.0.1:7000,127.0.0.1:7001", &options) != 0) { + LOG(ERROR) << "Fail to init redis cluster channel"; +} +``` + +`RedisClusterChannel` supports synchronous/asynchronous `CallMethod`, automatic redirection retries and periodic topology refresh. Multi-key support includes `MGET/MSET/DEL/EXISTS/UNLINK/EVAL/EVALSHA`. `MULTI/EXEC` is currently not supported. + +## RedisClusterChannel example + +`example/redis_c++/redis_cluster_client.cpp` demonstrates: + +- bootstrap from multiple seed nodes. +- MOVED/ASK auto-redirection and retry. +- topology refresh from `CLUSTER SLOTS` with `CLUSTER NODES` fallback. +- sync pipeline and async calls using one channel. + +Build and run: + +```bash +cd example/redis_c++ +make redis_cluster_client +./redis_cluster_client \ + --seeds=127.0.0.1:7000,127.0.0.1:7001 \ + --max_redirect=5 \ + --timeout_ms=1000 +``` + +Frequently used options: + +- `RedisClusterChannelOptions::max_redirect`: max redirects per command. +- `RedisClusterChannelOptions::refresh_interval_s`: interval of periodic topology refresh. +- `RedisClusterChannelOptions::topology_refresh_timeout_ms`: timeout for topology commands. +- `RedisClusterChannelOptions::channel_options`: normal brpc channel options for each redis node. +- `RedisClusterChannelOptions::enable_periodic_refresh`: disable this when your app controls refresh explicitly. + +Notes: + +- `MGET/MSET/DEL/EXISTS/UNLINK` are executed per key and merged in request order. +- `EVAL/EVALSHA` requires all declared keys to be in one slot. +- `MULTI/EXEC` returns an error reply by design. + +# Debug + +Turn on [-redis_verbose](http://brpc.baidu.com:8765/flags/redis_verbose) to print contents of all redis requests and responses. Note that this should only be used for debugging rather than online services. + +Turn on [-redis_verbose_crlf2space](http://brpc.baidu.com:8765/flags/redis_verbose_crlf2space) to replace the `CRLF` (\r\n) with spaces in debugging logs for better readability. + +| Name | Value | Description | Defined At | +| ------------------------ | ----- | ---------------------------------------- | ---------------------------------- | +| redis_verbose | false | [DEBUG] Print EVERY redis request/response | src/brpc/policy/redis_protocol.cpp | +| redis_verbose_crlf2space | false | [DEBUG] Show \r\n as a space | src/brpc/redis.cpp | + +# Performance + +redis version: 2.6.14 + +Start a client to send requests to a redis-server on the same machine using 1, 50, 200 bthreads synchronously. The latency is in microseconds. + +``` +$ ./client -use_bthread -thread_num 1 +TRACE: 02-13 19:42:04: * 0 client.cpp:180] Accessing redis server at qps=18668 latency=50 +TRACE: 02-13 19:42:05: * 0 client.cpp:180] Accessing redis server at qps=17043 latency=52 +TRACE: 02-13 19:42:06: * 0 client.cpp:180] Accessing redis server at qps=16520 latency=54 + +$ ./client -use_bthread -thread_num 50 +TRACE: 02-13 19:42:54: * 0 client.cpp:180] Accessing redis server at qps=301212 latency=164 +TRACE: 02-13 19:42:55: * 0 client.cpp:180] Accessing redis server at qps=301203 latency=164 +TRACE: 02-13 19:42:56: * 0 client.cpp:180] Accessing redis server at qps=302158 latency=164 + +$ ./client -use_bthread -thread_num 200 +TRACE: 02-13 19:43:48: * 0 client.cpp:180] Accessing redis server at qps=411669 latency=483 +TRACE: 02-13 19:43:49: * 0 client.cpp:180] Accessing redis server at qps=411679 latency=483 +TRACE: 02-13 19:43:50: * 0 client.cpp:180] Accessing redis server at qps=412583 latency=482 +``` + +The peak QPS at 200 threads is much higher than hiredis since brpc uses a single connection to redis-server by default and requests from multiple threads are [merged in a wait-free way](io.md#sending-messages), making the redis-server receive requests in batch and reach a much higher QPS. The lower QPS in following test that uses pooled connections is another proof. + +Start a client to send requests in batch (10 commands per request) to redis-server on the same machine using 1, 50, 200 bthreads synchronously. The latency is in microseconds. + +``` +$ ./client -use_bthread -thread_num 1 -batch 10 +TRACE: 02-13 19:46:45: * 0 client.cpp:180] Accessing redis server at qps=15880 latency=59 +TRACE: 02-13 19:46:46: * 0 client.cpp:180] Accessing redis server at qps=16945 latency=57 +TRACE: 02-13 19:46:47: * 0 client.cpp:180] Accessing redis server at qps=16728 latency=57 + +$ ./client -use_bthread -thread_num 50 -batch 10 +TRACE: 02-13 19:47:14: * 0 client.cpp:180] Accessing redis server at qps=38082 latency=1307 +TRACE: 02-13 19:47:15: * 0 client.cpp:180] Accessing redis server at qps=38267 latency=1304 +TRACE: 02-13 19:47:16: * 0 client.cpp:180] Accessing redis server at qps=38070 latency=1305 + + PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND +16878 gejun 20 0 48136 2436 1004 R 93.8 0.0 12:48.56 redis-server // thread_num=50 + +$ ./client -use_bthread -thread_num 200 -batch 10 +TRACE: 02-13 19:49:09: * 0 client.cpp:180] Accessing redis server at qps=29053 latency=6875 +TRACE: 02-13 19:49:10: * 0 client.cpp:180] Accessing redis server at qps=29163 latency=6855 +TRACE: 02-13 19:49:11: * 0 client.cpp:180] Accessing redis server at qps=29271 latency=6838 + + PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND +16878 gejun 20 0 48136 2508 1004 R 99.9 0.0 13:36.59 redis-server // thread_num=200 +``` + +Note that the commands processed per second by the redis-server is the QPS times 10, which is about 400K. When thread_num equals 50 or higher, the CPU usage of the redis-server reaches limit. Note that redis-server is a [single-threaded reactor](threading_overview.md#single-threaded-reactor), utilizing one core is the maximum that it can do. + +Now start a client to send requests to redis-server on the same machine using 50 bthreads synchronously through pooled connections. + +``` +$ ./client -use_bthread -connection_type pooled +TRACE: 02-13 18:07:40: * 0 client.cpp:180] Accessing redis server at qps=75986 latency=654 +TRACE: 02-13 18:07:41: * 0 client.cpp:180] Accessing redis server at qps=75562 latency=655 +TRACE: 02-13 18:07:42: * 0 client.cpp:180] Accessing redis server at qps=75238 latency=657 + + PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND +16878 gejun 20 0 48136 2520 1004 R 99.9 0.0 9:52.33 redis-server +``` + +We can see a tremendous drop of QPS compared to the one using single connection above, and the redis-server has reached the CPU cap. The cause is that each time only one request can be read from a connection by the redis-server, which significantly increases cost of IO operations. This is also the peak performance of a hiredis client. + +# Command Line Interface + +[example/redis_c++/redis_cli](https://github.com/apache/brpc/blob/master/example/redis_c%2B%2B/redis_cli.cpp) is a command line tool similar to the official CLI, demostrating brpc's capability to talk with redis servers. When unexpected results are got from a redis-server using a brpc client, you can debug with this tool interactively as well. + +For native Redis Cluster, you can start from [example/redis_c++/redis_cluster_client.cpp](https://github.com/apache/brpc/blob/master/example/redis_c%2B%2B/redis_cluster_client.cpp). + +Like the official CLI, `redis_cli ` runs the command directly, and `-server` which is address of the redis-server can be specified. + +``` +$ ./redis_cli + __ _ __ + / /_ ____ _(_)___/ /_ __ _________ _____ + / __ \/ __ `/ / __ / / / /_____/ ___/ __ \/ ___/ + / /_/ / /_/ / / /_/ / /_/ /_____/ / / /_/ / /__ + /_.___/\__,_/_/\__,_/\__,_/ /_/ / .___/\___/ + /_/ +This command-line tool mimics the look-n-feel of official redis-cli, as a +demostration of brpc's capability of talking to redis server. The +output and behavior is not exactly same with the official one. + +redis 127.0.0.1:6379> mset key1 foo key2 bar key3 17 +OK +redis 127.0.0.1:6379> mget key1 key2 key3 +["foo", "bar", "17"] +redis 127.0.0.1:6379> incrby key3 10 +(integer) 27 +redis 127.0.0.1:6379> client setname brpc-cli +OK +redis 127.0.0.1:6379> client getname +"brpc-cli" +``` diff --git a/docs/en/rpc_press.md b/docs/en/rpc_press.md new file mode 100644 index 0000000..69e0391 --- /dev/null +++ b/docs/en/rpc_press.md @@ -0,0 +1,7 @@ +# rpc press + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/rpc_press.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/rpc_replay.md b/docs/en/rpc_replay.md new file mode 100644 index 0000000..e592738 --- /dev/null +++ b/docs/en/rpc_replay.md @@ -0,0 +1,7 @@ +# rpc replay + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/rpc_replay.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/rpc_view.md b/docs/en/rpc_view.md new file mode 100644 index 0000000..31c5931 --- /dev/null +++ b/docs/en/rpc_view.md @@ -0,0 +1,7 @@ +# rpc view + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/rpc_view.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/rpcz.md b/docs/en/rpcz.md new file mode 100644 index 0000000..89e6abc --- /dev/null +++ b/docs/en/rpcz.md @@ -0,0 +1,7 @@ +# rpcz + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/rpcz.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/sanitizers.md b/docs/en/sanitizers.md new file mode 100644 index 0000000..f7ef588 --- /dev/null +++ b/docs/en/sanitizers.md @@ -0,0 +1,7 @@ +# sanitizers + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/sanitizers.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/server.md b/docs/en/server.md new file mode 100644 index 0000000..b7edd64 --- /dev/null +++ b/docs/en/server.md @@ -0,0 +1,1113 @@ +[中文版](../cn/server.md) + +# Example + +[server-side code](https://github.com/apache/brpc/blob/master/example/echo_c++/server.cpp) of Echo. + +# Fill the .proto + +Interfaces of requests, responses, services are defined in proto files. + +```C++ +# Tell protoc to generate base classes for C++ Service. modify to java_generic_services or py_generic_services for java or python. +option cc_generic_services = true; + +message EchoRequest { + required string message = 1; +}; +message EchoResponse { + required string message = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; +``` + +Read [official documents on protobuf](https://developers.google.com/protocol-buffers/docs/proto#options) for more details about protobuf. + +# Implement generated interface + +protoc generates echo.pb.cc and echo.pb.h. Include echo.pb.h and implement EchoService inside: + +```c++ +#include "echo.pb.h" +... +class MyEchoService : public EchoService { +public: + void Echo(::google::protobuf::RpcController* cntl_base, + const ::example::EchoRequest* request, + ::example::EchoResponse* response, + ::google::protobuf::Closure* done) { + // This RAII object calls done->Run() automatically at exit. + brpc::ClosureGuard done_guard(done); + + brpc::Controller* cntl = static_cast(cntl_base); + + // fill response + response->set_message(request->message()); + } +}; +``` + +Service is not available before insertion into [brpc.Server](https://github.com/apache/brpc/blob/master/src/brpc/server.h). + +When client sends request, Echo() is called. + +Explain parameters: + +**controller** + +Statically convertible to brpc::Controller (provided that the code runs in brpc.Server). Contains parameters that can't be included by request and response, check out [src/brpc/controller.h](https://github.com/apache/brpc/blob/master/src/brpc/controller.h) for details. + +**request** + +read-only message from a client. + +**response** + +Filled by user. If any **required** field is not set, the RPC will fail. + +**done** + +Created by brpc and passed to service's CallMethod(), including all actions after leaving CallMethod(): validating response, serialization, sending back to client etc. + +**No matter the RPC is successful or not, done->Run() must be called by user once and only once when the RPC is done.** + +Why does brpc not call done->Run() automatically? Because users are able to store done somewhere and call done->Run() in some event handlers after leaving CallMethod(), which is an **asynchronous service**. + +We strongly recommend using **ClosureGuard** to make done->Run() always be called. Look at the beginning statement in above code snippet: + +```c++ +brpc::ClosureGuard done_guard(done); +``` + +Not matter the callback is exited from middle or end, done_guard will be destructed, in which done->Run() is called. The mechanism is called [RAII](https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization). Without done_guard, you have to remember to add done->Run() before each `return`, **which is very error-prone**. + +In asynchronous service, request is not processed completely when CallMethod() returns, thus done->Run() should not be called, instead it should be preserved somewhere and called later. At first glance, we don't need ClosureGuard here. However in real applications, asynchronous service may fail in the middle and exit CallMethod() as well. Without ClosureGuard, error branches may forget to call done->Run() before `return`. Thus done_guard is still recommended in asynchronous services. Different from synchronous services, to prevent done->Run() from being called at successful return of CallMethod, you should call done_guard.release() to remove done from the object. + +How synchronous and asynchronous services handle done generally: + +```c++ +class MyFooService: public FooService { +public: + // Synchronous + void SyncFoo(::google::protobuf::RpcController* cntl_base, + const ::example::EchoRequest* request, + ::example::EchoResponse* response, + ::google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + ... + } + + // Aynchronous + void AsyncFoo(::google::protobuf::RpcController* cntl_base, + const ::example::EchoRequest* request, + ::example::EchoResponse* response, + ::google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + ... + done_guard.release(); + } +}; +``` + +Interface of ClosureGuard: + +```c++ +// RAII: Call Run() of the closure on destruction. +class ClosureGuard { +public: + ClosureGuard(); + // Constructed with a closure which will be Run() inside dtor. + explicit ClosureGuard(google::protobuf::Closure* done); + + // Call Run() of internal closure if it's not NULL. + ~ClosureGuard(); + + // Call Run() of internal closure if it's not NULL and set it to `done'. + void reset(google::protobuf::Closure* done); + + // Set internal closure to NULL and return the one before set. + google::protobuf::Closure* release(); +}; +``` + +## Set RPC to be failed + +Call Controller.SetFailed() to set the RPC to be failed. If error occurs during sending response, framework calls the method as well. Users often call the method in services' CallMethod(), For example if a stage of processing fails, user calls SetFailed() and call done->Run(), then quit CallMethod (If ClosureGuard is used, done->Run() is called automatically). The server-side done is created by framework and contains code sending response back to client. If SetFailed() is called, error information is sent to client instead of normal content. When client receives the response, its controller will be SetFailed() as well and Controller::Failed() will be true. In addition, Controller::ErrorCode() and Controller::ErrorText() are error code and error information respectively. + +User may set [status-code](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html) for http calls by calling `controller.http_response().set_status_code()` at server-side. Standard status-code are defined in [http_status_code.h](https://github.com/apache/brpc/blob/master/src/brpc/http_status_code.h). Controller.SetFailed() sets status-code as well with the value closest to the error-code in semantics. brpc::HTTP_STATUS_INTERNAL_SERVER_ERROR(500) is chosen when there's no proper value. + +## Get address of client + +controller->remote_side() gets address of the client which sent the request. The return type is butil::EndPoint. If client is nginx, remote_side() is address of nginx. To get address of the "real" client before nginx, set `proxy_header ClientIp $remote_addr;` in nginx and call `controller->http_request().GetHeader("ClientIp")` in RPC to get the address. + +Printing method: + +```c++ +LOG(INFO) << "remote_side=" << cntl->remote_side(); +printf("remote_side=%s\n", butil::endpoint2str(cntl->remote_side()).c_str()); +``` + +## Get address of server + +controller->local_side() gets server-side address of the RPC connection, return type is butil::EndPoint. + +Printing method: + +```c++ +LOG(INFO) << "local_side=" << cntl->local_side(); +printf("local_side=%s\n", butil::endpoint2str(cntl->local_side()).c_str()); +``` + +## Asynchronous Service + +In which done->Run() is called after leaving service's CallMethod(). + +Some server proxies requests to back-end servers and waits for responses that may come back after a long time. To make better use of threads, save done in corresponding event handlers which are triggered after CallMethod() and call done->Run() inside. This kind of service is **asynchronous**. + +Last line of asynchronous service is often `done_guard.release()` to prevent done->Run() from being called at successful exit from CallMethod(). Check out [example/session_data_and_thread_local](https://github.com/apache/brpc/tree/master/example/session_data_and_thread_local/) for a example. + +Server-side and client-side both use done to represent the continuation code after leaving CallMethod, but they're **totally different**: + +* server-side done is created by framework, called by user after processing of the request to send back response to client. +* client-side done is created by user, called by framework to run post-processing code written by user after completion of RPC. + +In an asynchronous service that may access other services, user probably manipulates both kinds of done, be careful. + +# Add Service + +A just default-constructed Server neither contains service nor serves requests, merely an object. + +Add a service with following method: + +```c++ +int AddService(google::protobuf::Service* service, ServiceOwnership ownership); +``` + +If `ownership` is SERVER_OWNS_SERVICE, server deletes the service at destruction. To prevent the deletion, set `ownership` to SERVER_DOESNT_OWN_SERVICE. + +Following code adds MyEchoService: + +```c++ +brpc::Server server; +MyEchoService my_echo_service; +if (server.AddService(&my_echo_service, brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(FATAL) << "Fail to add my_echo_service"; + return -1; +} +``` + +You cannot add or remove services after the server is started. + +# Start server + +Call following methods of [Server](https://github.com/apache/brpc/blob/master/src/brpc/server.h) to start serving. + +```c++ +int Start(const char* ip_and_port_str, const ServerOptions* opt); +int Start(EndPoint ip_and_port, const ServerOptions* opt); +int Start(int port, const ServerOptions* opt); +int Start(const char *ip_str, PortRange port_range, const ServerOptions *opt); // r32009后增加 +``` + +"localhost:9000", "cq01-cos-dev00.cq01:8000", "127.0.0.1:7000" are valid `ip_and_port_str`. + +All parameters take default values if `options` is NULL. If you need non-default values, code as follows: + +```c++ +brpc::ServerOptions options; // with default values +options.xxx = yyy; +... +server.Start(..., &options); +``` + +## Listen to multiple ports + +One server can only listen to one port (not counting ServerOptions.internal_port). To listen to N ports, start N servers . + +## Multi-process listening to one port + +When the `reuse_port` flag is turned on at startup, multiple processes can listen to one port (use SO_REUSEPORT internal). + +# Stop server + +```c++ +server.Stop(closewait_ms); // closewait_ms is useless actually, not deleted due to compatibility +server.Join(); +``` + +Stop() does not block but Join() does. The reason for dividing them into two methods is: When multiple servers quit, users may Stop() all servers first, then Join() them together. Otherwise servers can only be Stop()+Join() one-by-one and the total waiting time may add up to number-of-servers times at worst. + +Regardless of the value of closewait_ms, server waits for all requests being processed before exiting and returns ELOGOFF errors to new requests immediately to prevent them from entering the service. The reason for the wait is that as long as the server is still processing requests, risk of accessing invalid(released) memory exists. If a Join() to a server "stucks", some thread must be blocked on a request or done->Run() is not called. + +When a client sees ELOGOFF, it skips the corresponding server and retry the request on another server. As a result, restarting a cluster with brpc clients/servers gradually should not lose traffic by default. + +RunUntilAskedToQuit() simplifies the code to run and stop servers in most cases. Following code runs the server until Ctrl-C is pressed. + +```c++ +// Wait until Ctrl-C is pressed, then Stop() and Join() the server. +server.RunUntilAskedToQuit(); + +// server is stopped, write the code for releasing resources. +``` + +Services can be added or removed after Join() returns and server can be Start() again. + +# Accessed by http/h2 + +Services using protobuf can be accessed via http/h2+json generally. The json string stored in body is convertible to/from corresponding protobuf message. + +[echo server](https://github.com/apache/brpc/blob/master/example/echo_c%2B%2B/server.cpp) as an example, is accessible from [curl](https://curl.haxx.se/). + + +```shell +# -H 'Content-Type: application/json' is optional +$ curl -d '{"message":"hello"}' http://brpc.baidu.com:8765/EchoService/Echo +{"message":"hello"} +``` + +Note: Set `Content-Type: application/proto` to access services with http/h2 + protobuf-serialized-data, which performs better at serialization. + +## json<=>pb + +Json fields correspond to pb fields by matched names and message structures. The json must contain required fields in pb, otherwise conversion will fail and corresponding request will be rejected. The json may include undefined fields in pb, but they will be dropped rather than being stored in pb as unknown fields. Check out [json <=> protobuf](json2pb.md) for conversion rules. + +When -pb_enum_as_number is turned on, enums in pb are converted to values instead of names. For example in `enum MyEnum { Foo = 1; Bar = 2; };`, fields typed `MyEnum` are converted to "Foo" or "Bar" when the flag is off, 1 or 2 otherwise. This flag affects requests sent by clients and responses returned by servers both. Since "enum as name" has better forward and backward compatibilities, this flag should only be turned on to adapt legacy code that are unable to parse enumerations from names. + +## Adapt old clients + +Early-version brpc allows pb service being accessed via http without filling the pb request, even if there're required fields. This kind of service often parses http requests and sets http responses by itself, and does not touch the pb request. However this behavior is still very dangerous: a service with an undefined request. + +This kind of services may meet issues after upgrading to latest brpc, which already deprecated the behavior for a long time. To help these services to upgrade, brpc allows bypassing the conversion from http body to pb request (so that users can parse http requests differently), the setting is as follows: + +```c++ +brpc::ServiceOptions svc_opt; +svc_opt.ownership = ...; +svc_opt.restful_mappings = ...; +svc_opt.allow_http_body_to_pb = false; // turn off conversion from http/h2 body to pb request +server.AddService(service, svc_opt); +``` + +After the setting, service does not convert the body to pb request after receiving http/h2 request, which also makes the pb request undefined. Users have to parse the body by themselves when `cntl->request_protocol() == brpc::PROTOCOL_HTTP || cntl->request_protocol() == brpc::PROTOCOL_H2` is true which indicates the request is from http/h2. + +As a correspondence, if cntl->response_attachment() is not empty and pb response is set as well, brpc does not report the ambiguous anymore, instead cntl->response_attachment() will be used as body of the http/h2 response. This behavior is unaffected by setting allow_http_body_to_pb or not. If the relaxation results in more users' errors, we may restrict it in future. + +# Protocols + +Server detects supported protocols automatically, without assignment from users. `cntl->protocol()` gets the protocol being used. Server is able to accept connections with different protocols from one port, users don't need to assign different ports for different protocols. Even one connection may transport messages in multiple protocols, although we rarely do this (and not recommend). Supported protocols: + +- [The standard protocol used in Baidu](baidu_std.md), shown as "baidu_std", enabled by default. + +- [Streaming RPC](streaming_rpc.md), shown as "streaming_rpc", enabled by default. + +- http/1.0 and http/1.1, shown as "http", enabled by default. + +- http/2 and gRPC, shown as "h2c"(unencrypted) or "h2"(encrypted), enabled by default. + +- Protocol of RTMP, shown as "rtmp", enabled by default. + +- Protocol of hulu-pbrpc, shown as "hulu_pbrpc", enabled by default. + +- Protocol of sofa-pbrpc, shown as "sofa_pbrpc", enabled by default. + +- Protocol of Baidu ads union, shown as "nova_pbrpc", disabled by default. Enabling method: + + ```c++ + #include + ... + ServerOptions options; + ... + options.nshead_service = new brpc::policy::NovaServiceAdaptor; + ``` + +- Protocol of public_pbrpc, shown as "public_pbrpc", disabled by default. Enabling method: + + ```c++ + #include + ... + ServerOptions options; + ... + options.nshead_service = new brpc::policy::PublicPbrpcServiceAdaptor; + ``` + +- Protocol of nshead+mcpack, shown as "nshead_mcpack", disabled by default. Enabling method: + + ```c++ + #include + ... + ServerOptions options; + ... + options.nshead_service = new brpc::policy::NsheadMcpackAdaptor; + ``` + + As the name implies, messages in this protocol are composed by nshead+mcpack, the mcpack does not include special fields. Different from implementations based on NsheadService by users, this protocol uses mcpack2pb which makes the service capable of handling both mcpack and pb with one piece of code. Due to lack of fields to carry ErrorText, server can only close connections when errors occur. + +- Read [Implement NsheadService](nshead_service.md) for UB related protocols. + +If you need more protocols, contact us. + +# fork without exec +In general, [forked](https://linux.die.net/man/3/fork) subprocess should call [exec](https://linux.die.net/man/3/exec) ASAP, before which only async-signal-safe functions should be called. brpc programs using fork like this should work correctly even in previous versions. + +But in some scenarios, users continue the subprocess without exec. Since fork only copies its caller's thread, which causes other threads to disappear after fork. In the case of brpc, bvar depends on a sampling_thread to sample various information, which disappears after fork and causes many bvars to be zeros. + +Latest brpc re-creates the thread after fork(when necessary) to make bvar work correctly, and can be forked again. A known problem is that the cpu profiler does not work after fork. However users still can't call fork at any time, since brpc and its applications create threads extensively, which are not re-created after fork: +* most fork continues with exec, which wastes re-creations +* bring too many troubles and complexities to the code + +brpc's strategy is to create these threads on demand and fork without exec should happen before all code that may create the threads. Specifically, **fork without exec should happen before initializing all Servers/Channels/Applications, earlier is better**. fork not obeying this causes the program dysfunctional. BTW, fork without exec better be avoided because many libraries do not support it. + +# Settings + +## Version + +Server.set_version(…) sets name+version for the server, accessible from the builtin service `/version`. Although it's called "version", the string set is recommended to include the service name rather than just a numeric version. + +## Close idle connections + +If a connection does not read or write within the seconds specified by ServerOptions.idle_timeout_sec, it's treated as "idle" and will be closed by server soon. Default value is -1 which disables the feature. + +If [-log_idle_connection_close](http://brpc.baidu.com:8765/flags/log_idle_connection_close) is turned on, a log will be printed before closing. + +| Name | Value | Description | Defined At | +| ------------------------- | ----- | ---------------------------------------- | ------------------- | +| log_idle_connection_close | false | Print log when an idle connection is closed | src/brpc/socket.cpp | + +## pid_file + +If this field is non-empty, Server creates a file named so at start-up, with pid as the content. Empty by default. + +## Print hostname in each line of log + +This feature only affects logging macros in [butil/logging.h](https://github.com/apache/brpc/blob/master/src/butil/logging.h). + +If [-log_hostname](http://brpc.baidu.com:8765/flags/log_hostname) is turned on, each line of log contains the hostname so that users know machines at where each line is generated from aggregated logs. + +## Crash after printing FATAL log + +This feature only affects logging macros in [butil/logging.h](https://github.com/apache/brpc/blob/master/src/butil/logging.h), glog crashes for FATAL log by default. + +If [-crash_on_fatal_log](http://brpc.baidu.com:8765/flags/crash_on_fatal_log) is turned on, program crashes after printing LOG(FATAL) or failed assertions by CHECK*(), and generates coredump (with proper environmental settings). Default value is false. This flag can be turned on in tests to make sure the program never hit critical errors. + +> A common convention: use ERROR for tolerable errors, FATAL for unacceptable and permanent errors. + +## Minimum log level + +This feature is implemented by [butil/logging.h](https://github.com/apache/brpc/blob/master/src/butil/logging.h) and glog separately, as a same-named gflag. + +Only logs with levels **not less than** the level specified by -minloglevel are printed. This flag can be modified at run-time. Correspondence between values and log levels: 0=INFO 1=NOTICE 2=WARNING 3=ERROR 4=FATAL, default value is 0. + +Overhead of unprinted logs is just a "if" test and parameters are not evaluated (For example a parameter calls a function, if the log is not printed, the function is not called). Logs printed to LogSink may be filtered by the sink as well. + +## Return free memory to system + +Set gflag -free_memory_to_system_interval to make the program try to return free memory to system every so many seconds, values <= 0 disable the feature. Default value is 0. To turn it on, values >= 10 are recommended. This feature supports tcmalloc, thus `MallocExtension::instance()->ReleaseFreeMemory()` periodically called in your program can be replaced by setting this flag. + +## Log error to clients + +Framework does not print logs for specific client generally, because a lot of errors caused by clients may slow down server significantly due to frequent printing of logs. If you need to debug or just want the server to log all errors, turn on [-log_error_text](http://brpc.baidu.com:8765/flags/log_error_text). + +## Customize percentiles of latency + +Latency percentiles showed are **80** (was 50 before), 90, 99, 99.9, 99.99 by default. The first 3 ones can be changed by gflags -bvar_latency_p1, -bvar_latency_p2, -bvar_latency_p3 respectively。 + +Following are correct settings: +```shell +-bvar_latency_p3=97 # p3 is changed from default 99 to 97 +-bvar_latency_p1=60 -bvar_latency_p2=80 -bvar_latency_p3=95 +``` +Following are wrong settings: +```shell +-bvar_latency_p3=100 # the value must be inside [1,99] inclusive,otherwise gflags fails to parse +-bvar_latency_p1=-1 # ^ +``` +## Change stacksize + +brpc server runs code in bthreads with stacksize=1MB by default, while stacksize of pthreads is 10MB. It's possible that programs running normally on pthreads may meet stack overflow on bthreads. + +Set following gflags to enlarge the stacksize. +```shell +--stack_size_normal=10000000 # sets stacksize to roughly 10MB +--tc_stack_normal=1 # sets number of stacks cached by each worker pthread to prevent reusing from global pool each time, default value is 8 +``` +NOTE: It does mean that coredump of programs is likely to be caused by "stack overflow" on bthreads. We're talking about this simply because it's easy and quick to verify this factor and exclude the possibility. + +## Limit sizes of messages + +To protect servers and clients, when a request received by a server or a response received by a client is too large, the server or client rejects the message and closes the connection. The limit is controlled by [-max_body_size](http://brpc.baidu.com:8765/flags/max_body_size), in bytes. + +An error log is printed when a message is too large and rejected: + +``` +FATAL: 05-10 14:40:05: * 0 src/brpc/input_messenger.cpp:89] A message from 127.0.0.1:35217(protocol=baidu_std) is bigger than 67108864 bytes, the connection will be closed. Set max_body_size to allow bigger messages +``` + +protobuf has [similar limits](https://github.com/google/protobuf/blob/master/src/google/protobuf/io/coded_stream.h#L364) and the error log is as follows: + +``` +FATAL: 05-10 13:35:02: * 0 google/protobuf/io/coded_stream.cc:156] A protocol message was rejected because it was too big (more than 67108864 bytes). To increase the limit (or to disable these warnings), see CodedInputStream::SetTotalBytesLimit() in google/protobuf/io/coded_stream.h. +``` + +brpc removes the restriction from protobuf and controls the limit by -max_body_size solely: as long as the flag is large enough, messages will not be rejected and error logs will not be printed. This feature works for all versions of protobuf. + +## Compression + +`set_response_compress_type()` sets compression method for the response, no compression by default. + +Attachment is not compressed. Check [here](http_service.md#compress-response-body) for compression of HTTP body. + +Supported compressions: + +- brpc::CompressTypeSnappy : [snanpy](http://google.github.io/snappy/), compression and decompression are very fast, but compression ratio is low. +- brpc::CompressTypeGzip : [gzip](http://en.wikipedia.org/wiki/Gzip), significantly slower than snappy, with a higher compression ratio. +- brpc::CompressTypeZlib : [zlib](http://en.wikipedia.org/wiki/Zlib), 10%~20% faster than gzip but still significantly slower than snappy, with slightly better compression ratio than gzip. + +Read [Client-Compression](client.md#compression) for more comparisons. + +## Attachment + +baidu_std and hulu_pbrpc supports attachments which are sent along with messages and set by users to bypass serialization of protobuf. From a server's perspective, data set in Controller.response_attachment() will be received by the client while Controller.request_attachment() contains attachment sent from the client. + +Attachment is not compressed by framework. + +In http, attachment corresponds to [message body](http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html), namely the data to post to client is stored in response_attachment(). + +## Turn on SSL + +Update openssl to the latest version before turning on SSL, since older versions of openssl may have severe security problems and support less encryption algorithms, which is against with the purpose of using SSL. Setup `ServerOptions.ssl_options` to turn on SSL. Refer to [ssl_options.h](https://github.com/apache/brpc/blob/master/src/brpc/ssl_options.h) for more details. + +```c++ +// Certificate structure +struct CertInfo { + // Certificate in PEM format. + // Note that CN and alt subjects will be extracted from the certificate, + // and will be used as hostnames. Requests to this hostname (provided SNI + // extension supported) will be encrypted using this certifcate. + // Supported both file path and raw string + std::string certificate; + + // Private key in PEM format. + // Supported both file path and raw string based on prefix: + std::string private_key; + + // Additional hostnames besides those inside the certificate. Wildcards + // are supported but it can only appear once at the beginning (i.e. *.xxx.com). + std::vector sni_filters; +}; + +// SSL options at server side +struct ServerSSLOptions { + // Default certificate which will be loaded into server. Requests + // without hostname or whose hostname doesn't have a corresponding + // certificate will use this certificate. MUST be set to enable SSL. + CertInfo default_cert; + + // Additional certificates which will be loaded into server. These + // provide extra bindings between hostnames and certificates so that + // we can choose different certificates according to different hostnames. + // See `CertInfo' for detail. + std::vector certs; + + // When set, requests without hostname or whose hostname can't be found in + // any of the cerficates above will be dropped. Otherwise, `default_cert' + // will be used. + // Default: false + bool strict_sni; + + // ... Other options +}; +``` + +- To turn on SSL, users **MUST** provide a `default_cert`. For dynamic certificate selection (i.e. based on request hostname, a.k.a [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication)), `certs` should be used to store those dynamic certificates. Finally, users can add/remove those certificates when server's running: + + ```c++ + int AddCertificate(const CertInfo& cert); + int RemoveCertificate(const CertInfo& cert); + int ResetCertificates(const std::vector& certs); + ``` + +- Other options include: cipher suites (recommend using `ECDHE-RSA-AES256-GCM-SHA384` which is the default suite used by chrome, and one of the safest suites. The drawback is more CPU cost), session reuse and so on. + +- If you want to support application layer protocol negotiation, you can use the `alpns` option to set the protocol string supported by the server side. When the server starts, the validity of the protocol will be verified, and multiple protocols are separated by commas. The specific usage is as follows: + + ```c++ + ServerSSLOptions ssl_options; + ssl_options.alpns = "http, h2, baidu_std"; + ``` + +- SSL layer works under protocol layer. As a result, all protocols (such as HTTP) can provide SSL access when it's turned on. Server will decrypt the data first and then pass it into each protocol. + +- After turning on SSL, non-SSL access is still available for the same port. Server can automatically distinguish SSL from non-SSL requests. SSL-only mode can be implemented using `Controller::is_ssl()` in service's callback and `SetFailed` if it returns false. In the meanwhile, the builtin-service [connections](../cn/connections.md) also shows the SSL information for each connection. + +## Verify identities of clients + +The server needs to implement `Authenticator` to enable verifications: + +```c++ +class Authenticator { +public: + // Implement this method to verify credential information `auth_str' from + // `client_addr'. You can fill credential context (result) into `*out_ctx' + // and later fetch this pointer from `Controller'. + // Returns 0 on success, error code otherwise + virtual int VerifyCredential(const std::string& auth_str, + const base::EndPoint& client_addr, + AuthContext* out_ctx) const = 0; + }; + +class AuthContext { +public: + const std::string& user() const; + const std::string& group() const; + const std::string& roles() const; + const std::string& starter() const; + bool is_service() const; +}; +``` + +The authentication is connection-specific. When server receives the first request from a connection, it tries to parse related information inside (such as auth field in baidu_std, Authorization header in HTTP), and call `VerifyCredential` along with address of the client. If the method returns 0, which indicates success, user can put verified information into `AuthContext` and access it via `controller->auth_context()` later, whose lifetime is managed by framework. Otherwise the authentication is failed and the connection will be closed, which makes the client-side fail as well. + +Subsequent requests are treated as already verified without authenticating overhead. + +Assigning an instance of implemented `Authenticator` to `ServerOptions.auth` enables authentication. The instance must be valid during lifetime of the server. + +## Number of worker pthreads + +Controlled by `ServerOptions.num_threads` , number of cpu cores by default(including HT). + +NOTE: ServerOptions.num_threads is just a **hint**. + +Don't think that Server uses exactly so many workers because all servers and channels in one process share worker pthreads. Total number of threads is the maximum of all ServerOptions.num_threads and bthread_concurrency. For example, a program has 2 servers with num_threads=24 and 36 respectively, and bthread_concurrency is 16. Then the number of worker pthreads is max (24, 36, 16) = 36, which is different from other RPC implementations which do summations generally. + +Channel does not have a corresponding option, but user can change number of worker pthreads at client-side by setting gflag -bthread_concurrency. + +In addition, brpc **does not separate "IO" and "processing" threads**. brpc knows how to assemble IO and processing code together to achieve better concurrency and efficiency. + +## Limit concurrency + +"Concurrency" may have 2 meanings: one is number of connections, another is number of requests processed simultaneously. Here we're talking about the latter one. + +In traditional synchronous servers, max concurreny is limited by number of worker pthreads. Setting number of workers also limits concurrency. But brpc processes new requests in bthreads and M bthreads are mapped to N workers (M > N generally), synchronous server may have a concurrency higher than number of workers. On the other hand, although concurrency of asynchronous server is not limited by number of workers in principle, we need to limit it by other factors sometimes. + +brpc can limit concurrency at server-level and method-level. When number of requests processed by the server or method simultaneously would exceed the limit, server responds the client with **brpc::ELIMIT** directly instead of invoking the service. A client seeing ELIMIT should retry another server (by best efforts). This options avoids over-queuing of requests at server-side and limits related resources. + +Disabled by default. + +### Why issue error to the client instead of queuing the request when the concurrency hits limit? + +A server reaching max concurrency does not mean that other servers in the same cluster reach the limit as well. Let client be aware of the error ASAP and try another server is a better strategy from a cluster view. + +### Why not limit QPS? + +QPS is a second-level metric, which is not good at limiting sudden request bursts. Max concurrency is closely related to availability of critical resources: number of "workers" or "slots" etc, thus better at preventing over-queuing. + +In addition, when a server has stable latencies, limiting concurrency has similar effect as limiting QPS due to little's law. But the former one is much easier to implement: simple additions and minuses from a counter representing the concurrency. This is also the reason than most flow control is implemented by limiting concurrency rather than QPS. For example the window in TCP is a kind of concurrency. + +### Calculate max concurrency + +max_concurrency = peak_qps * noload_latency ([little's law](https://en.wikipedia.org/wiki/Little%27s_law)) + +peak_qps is the maximum of Queries-Per-Second. +noload_latency is the average latency measured in a server without pushing to its limit(with an acceptable latency). +peak_qps and nolaod_latency can be measured in pre-online performance tests and multiplied to calculate the max_concurrency. + +### Limit server-level concurrency + +Set ServerOptions.max_concurrency. Default value is 0 which means not limited. Accesses to builtin services are not limited by this option. + +Call Server.ResetMaxConcurrency() to modify max_concurrency of the server after starting. + +### Limit method-level concurrency + +server.MaxConcurrencyOf("...") = … sets max_concurrency of the method. Possible settings: + +```c++ +server.MaxConcurrencyOf("example.EchoService.Echo") = 10; +server.MaxConcurrencyOf("example.EchoService", "Echo") = 10; +server.MaxConcurrencyOf(&service, "Echo") = 10; +``` + +The code is generally put **after AddService, before Start() of the server**. When a setting fails(namely the method does not exist), server will fail to start and notify user to fix settings on MaxConcurrencyOf. + +When method-level and server-level max_concurrency are both set, framework checks server-level first, then the method-level one. + +NOTE: No service-level max_concurrency. + +### AutoConcurrencyLimiter +max_concurrency may change over time and measuring and setting max_concurrency for all services before each deployment are probably very troublesome and impractical. + +AutoConcurrencyLimiter addresses on this issue by limiting concurrency for methods. To use the algorithm, set max_concurrency of the method to "auto". +```c++ +// Set auto concurrency limiter for all methods +brpc::ServerOptions options; +options.method_max_concurrency = "auto"; + +// Set auto concurrency limiter for specific method +server.MaxConcurrencyOf("example.EchoService.Echo") = "auto"; +``` +Read [this](../cn/auto_concurrency_limiter.md) to know more about the algorithm. + +## pthread mode + +User code(client-side done, server-side CallMethod) runs in bthreads with 1MB stacksize by default. But some of them cannot run in bthreads: + +- JNI code checks stack layout and cannot be run in bthreads. +- The user code extensively use pthread-local to pass session-level data across functions. If there's a synchronous RPC call or function calls that may block bthread, the resumed bthread may land on a different pthread which does not have the pthread-local data that users expect to have. As a contrast, although tcmalloc uses pthread(or LWP)-local as well, the code inside has nothing to do with bthread, which is safe. + +brpc offers pthread mode to solve the issues. When **-usercode_in_pthread** is turned on, user code will be run in pthreads. Functions that would block bthreads block pthreads. + +Note: With -usercode_in_pthread on, brpc::thread_local_data() does not guarantee to return valid value. + +Performance issues when pthread mode is on: + +- Since synchronous RPCs block worker pthreads, server often needs more workers (ServerOptions.num_threads), and scheduling efficiencies will be slightly lower. +- User code still runs in special bthreads actually, which use stacks of pthread workers. These special bthreads are scheduled same with normal bthreads and performance differences are negligible. +- bthread supports an unique feature: yield pthread worker to a newly created bthread to reduce a context switch. brpc client uses this feature to reduce number of context switches in one RPC from 3 to 2. In a performance-demanding system, reducing context-switches improves performance. However pthread-mode is not capable of doing this. +- Number of threads in pthread-mode is a hard limit. Once all threads are occupied, requests will be queued rapidly and many of them will be timed-out finally. An example: When many requests to downstream servers are timedout, the upstream services may also be severely affected by a lot of blocking threads waiting for responses(within timeout). Consider setting ServerOptions.max_concurrency to protect the server when pthread-mode is on. As a contrast, number of bthreads in bthread mode is a soft limit and reacts more smoothly to such kind of issues. + +pthread-mode lets legacy code to try brpc more easily, but we still recommend refactoring the code with bthread-local or even remove TLS gradually, to turn off the option in future. + +## Security + +If requests are from public(including being proxied by nginx etc), you have to be aware of some security issues. + +### Hide builtin services from public + +Builtin services are useful, on the other hand include a lot of internal information and shouldn't be exposed to public. There're multiple methods to hide builtin services from public: + +- Set internal port. Set ServerOptions.internal_port to a port which can **only be accessible from internal**. You can view builtin services via internal_port, while accesses from the public port (the one passed to Server.Start) should see following error: + + ``` + [a27eda84bcdeef529a76f22872b78305] Not allowed to access builtin services, try ServerOptions.internal_port=... instead if you're inside internal network + ``` + +- http proxies only proxy specified URLs. nginx etc is able to configure how to map different URLs to back-end servers. For example the configure below maps public traffic to /MyAPI to `/ServiceName/MethodName` of `target-server`. If builtin services like /status are accessed from public, nginx rejects the attempts directly. +```nginx + location /MyAPI { + ... + proxy_pass http:///ServiceName/MethodName$query_string # $query_string is a nginx variable, check out http://nginx.org/en/docs/http/ngx_http_core_module.html for more. + ... + } +``` +**Don't turn on** -enable_dir_service and -enable_threads_service on public services. Although they're convenient for debugging, they also expose too many information on the server. The script to check if the public service has enabled the options: + +```shell +curl -s -m 1 :/flags/enable_dir_service,enable_threads_service | awk '{if($3=="false"){++falsecnt}else if($3=="Value"){isrpc=1}}END{if(isrpc!=1||falsecnt==2){print "SAFE"}else{print "NOT SAFE"}}' +``` + +### Disable built-in services completely + +Set ServerOptions.has_builtin_services = false, you can completely disable the built-in services. + +### Escape URLs controllable from public + +brpc::WebEscape() escapes url to prevent injection attacks with malice. + +### Not return addresses of internal servers + +Consider returning signatures of the addresses. For example after setting ServerOptions.internal_port, addresses in error information returned by server is replaced by their MD5 signatures. + +### Not start the brpc process as the root user + +During its operation, brpc writes various files (such as server pid files, rpcz, rpc dump, profiling, etc.). If brpc runs as the root user, attackers may exploit this feature to perform unauthorized file writes. Therefore, regardless of whether brpc provides network services or not, it is not recommended to start the brpc process as the root user. + +## Customize /health + +/health returns "OK" by default. If the content on /health needs to be customized: inherit [HealthReporter](https://github.com/apache/brpc/blob/master/src/brpc/health_reporter.h) and implement code to generate the page (like implementing other http services). Assign an instance to ServerOptions.health_reporter, which is not owned by server and must be valid during lifetime of the server. Users may return richer healthy information according to application requirements. + +## thread-local variables + +Searching services inside Baidu use [thread-local storage](https://en.wikipedia.org/wiki/Thread-local_storage) (TLS) extensively. Some of them cache frequently used objects to reduce repeated creations, some of them pass contexts to global functions implicitly. You should avoid the latter usage as much as possible. Such functions cannot even run without TLS, being hard to test. brpc provides 3 mechanisms to solve issues related to thread-local storage. + +### session-local + +A session-local data is bound to a **server-side RPC**: from entering CallMethod of the service, to calling the server-side done->Run(), no matter the service is synchronous or asynchronous. All session-local data are reused as much as possible and not deleted before stopping the server. + +After setting ServerOptions.session_local_data_factory, call Controller.session_local_data() to get a session-local data. If ServerOptions.session_local_data_factory is unset, Controller.session_local_data() always returns NULL. + +If ServerOptions.reserved_session_local_data is greater than 0, Server creates so many data before serving. + +**Example** + +```c++ +struct MySessionLocalData { + MySessionLocalData() : x(123) {} + int x; +}; + +class EchoServiceImpl : public example::EchoService { +public: + ... + void Echo(google::protobuf::RpcController* cntl_base, + const example::EchoRequest* request, + example::EchoResponse* response, + google::protobuf::Closure* done) { + ... + brpc::Controller* cntl = static_cast(cntl_base); + + // Get the session-local data which is created by ServerOptions.session_local_data_factory + // and reused between different RPC. + MySessionLocalData* sd = static_cast(cntl->session_local_data()); + if (sd == NULL) { + cntl->SetFailed("Require ServerOptions.session_local_data_factory to be set with a correctly implemented instance"); + return; + } + ... +``` + +```c++ +struct ServerOptions { + ... + // The factory to create/destroy data attached to each RPC session. + // If this field is NULL, Controller::session_local_data() is always NULL. + // NOT owned by Server and must be valid when Server is running. + // Default: NULL + const DataFactory* session_local_data_factory; + + // Prepare so many session-local data before server starts, so that calls + // to Controller::session_local_data() get data directly rather than + // calling session_local_data_factory->Create() at first time. Useful when + // Create() is slow, otherwise the RPC session may be blocked by the + // creation of data and not served within timeout. + // Default: 0 + size_t reserved_session_local_data; +}; +``` + +session_local_data_factory is typed [DataFactory](https://github.com/apache/brpc/blob/master/src/brpc/data_factory.h). You have to implement CreateData and DestroyData inside. + +NOTE: CreateData and DestroyData may be called by multiple threads simultaneously. Thread-safety is a must. + +```c++ +class MySessionLocalDataFactory : public brpc::DataFactory { +public: + void* CreateData() const { + return new MySessionLocalData; + } + void DestroyData(void* d) const { + delete static_cast(d); + } +}; + +MySessionLocalDataFactory g_session_local_data_factory; + +int main(int argc, char* argv[]) { + ... + + brpc::Server server; + brpc::ServerOptions options; + ... + options.session_local_data_factory = &g_session_local_data_factory; + ... +``` + +### server-thread-local + +A server-thread-local is bound to **a call to service's CallMethod**, from entering service's CallMethod, to leaving the method. All server-thread-local data are reused as much as possible and will not be deleted before stopping the server. server-thread-local is implemented as a special bthread-local. + +After setting ServerOptions.thread_local_data_factory, call brpc::thread_local_data() to get a thread-local. If ServerOptions.thread_local_data_factory is unset, brpc::thread_local_data() always returns NULL. + +If ServerOptions.reserved_thread_local_data is greater than 0, Server creates so many data before serving. + +**Differences with session-local** + +session-local data is got from server-side Controller, server-thread-local can be got globally from any function running directly or indirectly inside a thread created by the server. + +session-local and server-thread-local are similar in a synchronous service, except that the former one has to be created from a Controller. If the service is asynchronous and the data needs to be accessed from done->Run(), session-local is the only option, because server-thread-local is already invalid after leaving service's CallMethod. + +**Example** + +```c++ +struct MyThreadLocalData { + MyThreadLocalData() : y(0) {} + int y; +}; + +class EchoServiceImpl : public example::EchoService { +public: + ... + void Echo(google::protobuf::RpcController* cntl_base, + const example::EchoRequest* request, + example::EchoResponse* response, + google::protobuf::Closure* done) { + ... + brpc::Controller* cntl = static_cast(cntl_base); + + // Get the thread-local data which is created by ServerOptions.thread_local_data_factory + // and reused between different threads. + // "tls" is short for "thread local storage". + MyThreadLocalData* tls = static_cast(brpc::thread_local_data()); + if (tls == NULL) { + cntl->SetFailed("Require ServerOptions.thread_local_data_factory " + "to be set with a correctly implemented instance"); + return; + } + ... +``` + +```c++ +struct ServerOptions { + ... + // The factory to create/destroy data attached to each searching thread + // in server. + // If this field is NULL, brpc::thread_local_data() is always NULL. + // NOT owned by Server and must be valid when Server is running. + // Default: NULL + const DataFactory* thread_local_data_factory; + + // Prepare so many thread-local data before server starts, so that calls + // to brpc::thread_local_data() get data directly rather than calling + // thread_local_data_factory->Create() at first time. Useful when Create() + // is slow, otherwise the RPC session may be blocked by the creation + // of data and not served within timeout. + // Default: 0 + size_t reserved_thread_local_data; +}; +``` + +thread_local_data_factory is typed [DataFactory](https://github.com/apache/brpc/blob/master/src/brpc/data_factory.h). You need to implement CreateData and DestroyData inside. + +NOTE: CreateData and DestroyData may be called by multiple threads simultaneously. Thread-safety is a must. + +```c++ +class MyThreadLocalDataFactory : public brpc::DataFactory { +public: + void* CreateData() const { + return new MyThreadLocalData; + } + void DestroyData(void* d) const { + delete static_cast(d); + } +}; + +MyThreadLocalDataFactory g_thread_local_data_factory; + +int main(int argc, char* argv[]) { + ... + + brpc::Server server; + brpc::ServerOptions options; + ... + options.thread_local_data_factory = &g_thread_local_data_factory; + ... +``` + +### bthread-local + +Session-local and server-thread-local are enough for most servers. However, in some cases, we need a more general thread-local solution. In which case, you can use bthread_key_create, bthread_key_destroy, bthread_getspecific, bthread_setspecific etc, which are similar to [pthread equivalence](http://linux.die.net/man/3/pthread_key_create). + +These functions support both bthread and pthread. When they are called in bthread, bthread private variables are returned; When they are called in pthread, pthread private variables are returned. Note that the "pthread private" here is not created by pthread_key_create, pthread-local created by pthread_key_create cannot be got by bthread_getspecific. __thread in GCC and thread_local in c++11 etc cannot be got by bthread_getspecific as well. + +Since brpc creates a bthread for each request, the bthread-local in the server behaves specially: a bthread created by server does not delete bthread-local data at exit, instead it returns the data to a pool in the server for later reuse. This prevents bthread-local from constructing and destructing frequently along with creation and destroying of bthreads. This mechanism is transparent to users. + +**Main interfaces** + +```c++ +// Create a key value identifying a slot in a thread-specific data area. +// Each thread maintains a distinct thread-specific data area. +// `destructor', if non-NULL, is called with the value associated to that key +// when the key is destroyed. `destructor' is not called if the value +// associated is NULL when the key is destroyed. +// Returns 0 on success, error code otherwise. +extern int bthread_key_create(bthread_key_t* key, void (*destructor)(void* data)); + +// Delete a key previously returned by bthread_key_create(). +// It is the responsibility of the application to free the data related to +// the deleted key in any running thread. No destructor is invoked by +// this function. Any destructor that may have been associated with key +// will no longer be called upon thread exit. +// Returns 0 on success, error code otherwise. +extern int bthread_key_delete(bthread_key_t key); + +// Store `data' in the thread-specific slot identified by `key'. +// bthread_setspecific() is callable from within destructor. If the application +// does so, destructors will be repeatedly called for at most +// PTHREAD_DESTRUCTOR_ITERATIONS times to clear the slots. +// NOTE: If the thread is not created by brpc server and lifetime is +// very short(doing a little thing and exit), avoid using bthread-local. The +// reason is that bthread-local always allocate keytable on first call to +// bthread_setspecific, the overhead is negligible in long-lived threads, +// but noticeable in shortly-lived threads. Threads in brpc server +// are special since they reuse keytables from a bthread_keytable_pool_t +// in the server. +// Returns 0 on success, error code otherwise. +// If the key is invalid or deleted, return EINVAL. +extern int bthread_setspecific(bthread_key_t key, void* data); + +// Return current value of the thread-specific slot identified by `key'. +// If bthread_setspecific() had not been called in the thread, return NULL. +// If the key is invalid or deleted, return NULL. +extern void* bthread_getspecific(bthread_key_t key); +``` + +**How to use** + +Create a bthread_key_t which represents a kind of bthread-local variable. + +Use bthread_[get|set]specific to get and set bthread-local variables. First-time access to a bthread-local variable from a bthread returns NULL. + +Delete a bthread_key_t after no thread is using bthread-local associated with the key. If a bthread_key_t is deleted during usage, related bthread-local data are leaked. + +```c++ +static void my_data_destructor(void* data) { + ... +} + +bthread_key_t tls_key; + +if (bthread_key_create(&tls_key, my_data_destructor) != 0) { + LOG(ERROR) << "Fail to create tls_key"; + return -1; +} +``` +```c++ +// in some thread ... +MyThreadLocalData* tls = static_cast(bthread_getspecific(tls_key)); +if (tls == NULL) { // First call to bthread_getspecific (and before any bthread_setspecific) returns NULL + tls = new MyThreadLocalData; // Create thread-local data on demand. + CHECK_EQ(0, bthread_setspecific(tls_key, tls)); // set the data so that next time bthread_getspecific in the thread returns the data. +} +``` +**Example** + +```c++ +static void my_thread_local_data_deleter(void* d) { + delete static_cast(d); +} + +class EchoServiceImpl : public example::EchoService { +public: + EchoServiceImpl() { + CHECK_EQ(0, bthread_key_create(&_tls2_key, my_thread_local_data_deleter)); + } + ~EchoServiceImpl() { + CHECK_EQ(0, bthread_key_delete(_tls2_key)); + }; + ... +private: + bthread_key_t _tls2_key; +} + +class EchoServiceImpl : public example::EchoService { +public: + ... + void Echo(google::protobuf::RpcController* cntl_base, + const example::EchoRequest* request, + example::EchoResponse* response, + google::protobuf::Closure* done) { + ... + // You can create bthread-local data for your own. + // The interfaces are similar with pthread equivalence: + // pthread_key_create -> bthread_key_create + // pthread_key_delete -> bthread_key_delete + // pthread_getspecific -> bthread_getspecific + // pthread_setspecific -> bthread_setspecific + MyThreadLocalData* tls2 = static_cast(bthread_getspecific(_tls2_key)); + if (tls2 == NULL) { + tls2 = new MyThreadLocalData; + CHECK_EQ(0, bthread_setspecific(_tls2_key, tls2)); + } + ... +``` + +## RPC Protobuf message factory + +`DefaultRpcPBMessageFactory' is used at server-side by default. It is a simple factory class that uses `new' to create request/response messages and `delete' to destroy request/response messages. Currently, the baidu_std protocol and HTTP protocol support this feature. + +Users can implement `RpcPBMessages' (encapsulation of request/response message) and `RpcPBMessageFactory' (factory class) to customize the creation and destruction mechanism of protobuf message, and then set to `ServerOptions.rpc_pb_message_factory`. Note: After the server is started, the server owns the `RpcPBMessageFactory`. + +The interface is as follows: + +```c++ +// Inherit this class to customize rpc protobuf messages, +// include request and response. +class RpcPBMessages { +public: + virtual ~RpcPBMessages() = default; + // Get protobuf request message. + virtual google::protobuf::Message* Request() = 0; + // Get protobuf response message. + virtual google::protobuf::Message* Response() = 0; +}; + +// Factory to manage `RpcPBMessages'. +class RpcPBMessageFactory { +public: + virtual ~RpcPBMessageFactory() = default; + // Get `RpcPBMessages' according to `service' and `method'. + // Common practice to create protobuf message: + // service.GetRequestPrototype(&method).New() -> request; + // service.GetResponsePrototype(&method).New() -> response. + virtual RpcPBMessages* Get(const ::google::protobuf::Service& service, + const ::google::protobuf::MethodDescriptor& method) = 0; + // Return `RpcPBMessages' to factory. + virtual void Return(RpcPBMessages* protobuf_message) = 0; +}; +``` + +### Protobuf arena + +Protobuf arena is a Protobuf message memory management mechanism with the advantages of improving memory allocation efficiency, reducing memory fragmentation, and being cache-friendly. For more information, see [C++ Arena Allocation Guide](https://protobuf.dev/reference/cpp/arenas/). + +Users can set `ServerOptions.rpc_pb_message_factory = brpc::GetArenaRpcPBMessageFactory();` to manage Protobuf message memory, with the default `start_block_size` (256 bytes) and `max_block_size` (8192 bytes). Alternatively, users can use `brpc::GetArenaRpcPBMessageFactory();` to customize the arena size. + +Note: Since Protocol Buffers v3.14.0, Arenas are now unconditionally enabled. However, for versions prior to Protobuf v3.14.0, users need to add the option `option cc_enable_arenas = true;` to the proto file. so for compatibility, this option can be added uniformly. + +## Ignoring eovercrowded on server-side +### Ignore eovercrowded on server-level + +Set ServerOptions.ignore_eovercrowded. Default value is 0 which means not ignored. + +### Ignore eovercrowded on method-level + +server.IgnoreEovercrowdedOf("...") = … sets ignore_eovercrowded of the method. Possible settings: + +```c++ +ServerOptions.ignore_eovercrowded = true; // Set the default ignore_eovercrowded for all methods +server.IgnoreEovercrowdedOf("example.EchoService.Echo") = true; +``` + +The code is generally put **after AddService, before Start() of the server**. When a setting fails(namely the method does not exist), server will fail to start and notify user to fix settings on IgnoreEovercrowdedOf. + +When method-level and server-level ignore_eovercrowded are both set, if any one of them is set to true, eovercrowded will be ignored. + +NOTE: No service-level ignore_eovercrowded. + +# FAQ + +### Q: Fail to write into fd=1865 SocketId=8905@10.208.245.43:54742@8230: Got EOF + +A: The client-side probably uses pooled or short connections, and closes the connection after RPC timedout, when server writes back response, it finds that the connection has been closed and reports this error. "Got EOF" just means the server has received EOF (remote side closes the connection normally). If the client side uses single connection, server rarely reports this error. + +### Q: Remote side of fd=9 SocketId=2@10.94.66.55:8000 was closed + +It's not an error, it's a common warning representing that remote side has closed the connection(EOF). This log might be useful for debugging problems. + +Disabled by default. Set gflag -log_connection_close to true to enable it. ([modify at run-time](flags.md#change-gflag-on-the-fly) is supported) + +### Q: Why does setting number of threads at server-side not work + +All brpc servers in one process [share worker pthreads](#Number-of-worker-pthreads), If multiple servers are created, number of worker pthreads is probably the maxmium of their ServerOptions.num_threads. + +### Q: Why do client-side latencies much larger than the server-side ones + +server-side worker pthreads may not be enough and requests are significantly delayed. Read [Server debugging](server_debugging.md) for steps on debugging server-side issues quickly. + +### Q: Fail to open /proc/self/io + +Some kernels do not provide this file. Correctness of the service is unaffected, but following bvars are not updated: +``` +process_io_read_bytes_second +process_io_write_bytes_second +process_io_read_second +process_io_write_second +``` +### Q: json string "[1,2,3]" can't be converted to protobuf message + +This is not a valid json string, which must be a json object enclosed with braces {}. + +# PS:Workflow at server-side + +![img](../images/server_side.png) diff --git a/docs/en/server_debugging.md b/docs/en/server_debugging.md new file mode 100644 index 0000000..13fb647 --- /dev/null +++ b/docs/en/server_debugging.md @@ -0,0 +1,161 @@ +# 1. Check the number of worker threads + +Check /vars/bthread_worker_**count** and /vars/bthread_worker_**usage**, which is the number of worker threads in total and being used, respectively. + +> The number of usage and count being close means that worker threads are not enough. + +For example, there are 24 worker threads in the following figure, among which 23.93 worker threads are being used, indicating all the worker threads are full of jobs and not enough. + +![img](../images/full_worker_usage.png) + +There are 2.36 worker threads being used in the following figure. Apparently the worker threads are enough. + +![img](../images/normal_worker_usage.png) + +These two figures can be seen directly by putting /vars/bthread_worker_count;bthread_worker_usage?expand after service url, just like [this](http://brpc.baidu.com:8765/vars/bthread_worker_count;bthread_worker_usage?expand). + +# 2. Check CPU usage + +Check /vars/system_core_**count** and /vars/process_cpu_**usage**, which is the number of cpu core available and being used, respectively. + +> The number of usage and count being close means that cpus are enough. + +In the following figure the number of cores is 24, while the number of cores being used is 20.9, which means CPU is bottleneck. + +![img](../images/high_cpu_usage.png) + +The number of cores being used in the figure below is 2.06, then CPU is sufficient. + +![img](../images/normal_cpu_usage.png) + +# 3. Locate problems + +The number of process_cpu_usage being close to bthread_worker_usage means it is a cpu-bound program and worker threads are doing calculations in most of the time. + +The number of process_cpu_usage being much less than bthread_worker_usage means it is an io-bound program and worker threads are blocking in most of the time. + +(1 - process_cpu_usage / bthread_worker_usage) is the time ratio that spent on blocking. For example, if process_cpu_usage = 2.4, bthread_worker_usage = 18.5, then worker threads spent 87.1% of time on blocking. + +## 3.1 Locate cpu-bound problem + +The possible reason may be the poor performance of single server or uneven distribution to upstreams. + +### exclude the suspect of uneven distribution to upstreams + +Enter qps at [vars]((http://brpc.baidu.com:8765/vars) page of different services to check whether qps is as expected, just like this: + +![img](../images/bthread_creation_qps.png) + +Or directly visit using curl in command line, like this: + +```shell +$ curl brpc.baidu.com:8765/vars/*qps* +bthread_creation_qps : 95 +rpc_server_8765_example_echo_service_echo_qps : 57 +``` + +If the distribution of different machines is indeed uneven and difficult to solve, [Limit concurrency](server.md#user-content-limit-concurrency) can be considered to use. + +### Improve performance of single server + +Please use [CPU profiler](cpu_profiler.md) to analyze hot spots of the program and use data to guide optimization. Generally speaking, some big and obvious hot spots can be found in a cpu-bound program. + +## 3.2 Locate io-bound problem + +The possible reason: + +- working threads are not enough. +- the client that visits downstream servers doesn't support bthread and the latency is too long. +- blocking that caused by internal locks, IO, etc. + +If blocking is inevitable, please consider asynchronous method. + +### exclude the suspect of working threads are not enough + +If working threads are not enough, you can try to dynamically adjust the number of threads. Switch to the /flags page and click the (R) in the right of bthread_concurrency: + +![img](../images/bthread_concurrency_1.png) + +Just enter the new thread number and confirm: + +![img](../images/bthread_concurrency_2.png) + +Back to the /flags page, you can see that bthread_concurrency has become the new value. + +![img](../images/bthread_concurrency_3.png) + +However, adjusting the number of threads may not be useful. If the worker threads are largely blocked by visiting downstreams, it is useless to adjust the thread number since the real bottleneck is in the back-end and adjusting the thread number to a larger value just make the blocking time of each thread become longer. + +For example, in our example, the worker threads are still full of work after the thread number is resized. + +![img](../images/full_worker_usage_2.png) + +### exclude the suspect of lock + +If the program is blocked by some lock, it can also present features of io-bound. First use [contention profiler](contention_profiler.md) to check the contention status of locks. + +### use rpcz + +rpcz can help you see all the recent requests and the time(us) spent in each phase while processing them. + +![img](../images/rpcz.png) + +Click on a span link to see when the RPC started, the spent time in each phase and when it ended. + +![img](../images/rpcz_2.png) + +This is a typical example that server is blocked severely. It takes 20ms from receiving the request to starting running, indicating that the server does not have enough worker threads to get the job done in time. + +For now the information of this span is less, we can add some in the program. You can use TRACEPRINTF print logs to rpcz. Printed content is embedded in the time stream of rpcz. + +![img](../images/trace_printf.png) + +After Re-running, you can check the span and it really contains the content we added by TRACEPRINTF. + +![img](../images/rpcz_3.png) + +Before running to the first TRACEPRINTF, the user callback has already run for 2051ms(suppose it meets our expectation), followed by foobar() that took 8036ms, which is expected to return very fast. The range has been further reduced. + +Repeat this process until you find the function that caused the problem. + +## Use bvar + +TRACEPRINTF is mainly suitable for functions that called several times, so if a function is called many times, or the function itself has a small overhead, it is not appropriate to print logs to rpcz every time. You can use bvar instead. + +[bvar](bvar.md) is a multi-threaded counting library, which can record the value passed from user at an extreme low cost and almost does not affect the program behavior compared to logging. + +Follow the code below to monitor the runtime of foobar. + +```c++ +#include +#include + +bvar::LatencyRecorder g_foobar_latency("foobar"); + +... +void search() { + ... + butil::Timer tm; + tm.start(); + foobar(); + tm.stop(); + g_foobar_latency << tm.u_elapsed(); + ... +} +``` + +After rerunning the program, enter foobar in the search box of vars. The result is shown as below: + +![img](../images/foobar_bvar.png) + +Click on a bvar and you can see a dynamic figure. For example, after clicking on cdf: + +![img](../images/foobar_latency_cdf.png) + +Depending on the distribution of delays, you can infer the overall behavior of this function, how it behaves for most requests and how it behaves for long tails. + +You can continue this process in the subroutine, add more bvar, compare the different distributions, and finally locate the source. + +### Use brpc client only + +You have to open the dummy server to provide built-in services, see [here](dummy_server.md). diff --git a/docs/en/server_push.md b/docs/en/server_push.md new file mode 100644 index 0000000..c00c828 --- /dev/null +++ b/docs/en/server_push.md @@ -0,0 +1,28 @@ +[中文版](../cn/server_push.md) + +# Server push + +What "server push" refers to is: server sends a message to client after occurrence of an event, rather than passively replying the client as in ordinary RPCs. Following two methods are recommended to implement server push in brpc. + +## Remote event + +Similar to local event, remote event is divided into two steps: registration and notification. The client sends an asynchronous RPC to the server for registration, and puts the event-handling code in the RPC callback. The RPC is also a part of the waiting for the notification, namely the server does not respond directly after receiving the request, instead it does not call done->Run() (to notify the client) until the local event triggers. As we see, the server is also asynchronous. If the connection is broken during the process, the client fails soon and may choose to retry another server or end the RPC. Server should be aware of the disconnection by using Controller.NotifyOnCancel() and delete useless done in-time. + +This pattern is similar to [long polling](https://en.wikipedia.org/wiki/Push_technology#Long_polling) in some sense, sounds old but probably still be the most effective method. At first glance "server push" is server visiting client, opposite with ordinary client visiting server. But do you notice that, the response sent from server back to client is also in the opposite direction of "client visiting server"? In order to understand differences between response and push, let's analyze the process with the presumption that "client may receive messages from the server at any time". + +* Client has to understand the messages from server anyway, otherwise the messaging is pointless. +* Client also needs to know how to deal with the message. If the client does not have the handling code, the message is still useless. + +In other words, the client should "be prepared" to the messages from the server, and the "preparation" often relies on programming contexts that client just faces. Regarding different factors, it's more universal for client to inform server for "I am ready" (registered) first, then the server notifies the client with events later, in which case **the "push" is just a "response"**, with a very long or even infinite timeout. + +In some scenarios, the registration can be ignored, such as the [push promise](https://tools.ietf.org/html/rfc7540#section-8.2) in http2, which does not require the web browser(client) to register with the server, because both client and server know that the task between them is to let the client download necessary resources ASAP. Since each resource has a unique URI, the server can directly push resources and URI to the client, which caches the resources to avoid repeated accesses. Similarly, protocols capable of "two-way communication" probably improves efficiencies of pushing in given scenarios such as multimedia streaming or fixed-format key/value pairs etc, rather than implementing universal pushing. The client knows how to handle messages that may be pushed by servers, so extra registrations are not required. The push can still be treated as a "response": to the request that client already and always promised server. + +## Restful callback + +The client wants a given URL to be called with necessary parameters when the event occurs. In this pattern, server can reply the client directly when it receives the registration request, because the event is not triggered by the end of the RPC. In addition, since the callback is just a URL, which can be stored in databases and message queues, this pattern is very flexible and widely used in business systems. + +URL and parameters must contain enough information to make the callback know that which notification corresponds to which request, because the client may care about more than one event at the same time, or an event may be registered more than once due to network jitter or restarting machines etc. If the URL path is fixed, the client should place an unique ID in the registration request and the server should put the ID unchanged in response. Or the client generates an unique path for each registration so that each request is distinguishable from each other naturally. These methods are actually same in essense, just different positions for unique identifiers. + +Callbacks should deal with [idempotence](https://en.wikipedia.org/wiki/Idempotence). The server may retry and send more than one notifications after encountering network problems. If the first notification has been successful, follow-up notifications should not have effects. The "remote event" pattern guarantees idempotency at the layer of RPC, which ensures that the done is called once and only once. + +In order to avoid missing important notifications, users often need to combine RPC and message queues flexibly. RPC is much better at latency and efficiency than message queue, but due to limited memory, server needs to stop sending RPC after several failed retries and do more important things with the memory. It's better to move the notification task into a persistent message queue at the moment, which is capable of retrying during a very long time. With the idempotence in URL callback, most notifications are sent reliably and in-time. \ No newline at end of file diff --git a/docs/en/status.md b/docs/en/status.md new file mode 100644 index 0000000..1af19bb --- /dev/null +++ b/docs/en/status.md @@ -0,0 +1,37 @@ +[中文版](../cn/status.md) + +[/status](http://brpc.baidu.com:8765/status) shows primary statistics of services inside the server. The data sources are same with [/vars](vars.md), but stats are grouped differently. + +![img](../images/status.png) + +Meanings of the fields above: + +- **non_service_error**: number of errors raised outside processing code of the service. When a valid service is obtained, the subsequent error is regarded as *service_error*, otherwise it is regarded as *non_service_error* (such as request parsing failed, service name does not exist, request concurrency exceeding limit, etc.). As a contrast, failing to access back-end servers during the processing is an error of the service, not a *non_service_error*. Even if the response written out successfully stands for failure, the error is counted into the service rather than *non_service_error*. +- **connection_count**: number of connections to the server from clients, not including number of outward connections which are displayed at /vars/rpc_channel_connection_count. +- **example.EchoService**: Full name of the service, including the package name defined in proto. +- **Echo (EchoRequest) returns (EchoResponse)**: Signature of the method. A service can have multiple methods. Click links on request/response to see schemes of the protobuf messages. +- **count**: Number of requests that are successfully processed. +- **error**: Number of requests that are failed to process. +- **latency**: average latency in recent *60s/60m/24h/30d* from *right to left* on html, average latency in recent 10s(by default, specified by [-bvar_dump_interval](http://brpc.baidu.com:8765/flags/bvar_dump_interval)) on plain texts. +- **latency_percentiles**: 80%, 90%, 99%, 99.9% percentiles of latency in 10 seconds(specified by[-bvar_dump_interval](http://brpc.baidu.com:8765/flags/bvar_dump_interval)). Curves with historical values are shown on html. +- **latency_cdf**: shows percentiles as [CDF](https://en.wikipedia.org/wiki/Cumulative_distribution_function), only available on html. +- **max_latency**: max latency in recent *60s/60m/24h/30d* from *right to left* on html, max latency in recent 10s(by default, specified by [-bvar_dump_interval](http://brpc.baidu.com:8765/flags/bvar_dump_interval)) on plain texts. +- **qps**: QPS(Queries Per Second) in recent *60s/60m/24h/30d* from *right to left* on html. QPS in recent 10s(by default, specified by [-bvar_dump_interval](http://brpc.baidu.com:8765/flags/bvar_dump_interval)) on plain texts. +- **processing**: (renamed to concurrency in master) Number of requests being processed by the method. If this counter can't hit zero when the traffic to the service becomes zero, the server probably has bugs, such as forgetting to call done->Run() or stuck on some processing steps. + + +Users may customize descriptions on /status by letting the service implement [brpc::Describable](https://github.com/apache/brpc/blob/master/src/brpc/describable.h). + +```c++ +class MyService : public XXXService, public brpc::Describable { +public: + ... + void Describe(std::ostream& os, const brpc::DescribeOptions& options) const { + os << "my_status: blahblah"; + } +}; +``` + +For example: + +![img](../images/status_2.png) diff --git a/docs/en/streaming_log.md b/docs/en/streaming_log.md new file mode 100644 index 0000000..0412118 --- /dev/null +++ b/docs/en/streaming_log.md @@ -0,0 +1,283 @@ +# Name + +streaming_log - Print log to std::ostreams + +# SYNOPSIS + +```c++ +#include + +LOG(FATAL) << "Fatal error occurred! contexts=" << ...; +LOG(WARNING) << "Unusual thing happened ..." << ...; +LOG(TRACE) << "Something just took place..." << ...; +LOG(TRACE) << "Items:" << noflush; +LOG_IF(NOTICE, n > 10) << "This log will only be printed when n > 10"; +PLOG(FATAL) << "Fail to call function setting errno"; +VLOG(1) << "verbose log tier 1"; +CHECK_GT(1, 2) << "1 can't be greater than 2"; + +LOG_EVERY_SECOND(INFO) << "High-frequent logs"; +LOG_EVERY_N(ERROR, 10) << "High-frequent logs"; +LOG_FIRST_N(INFO, 20) << "Logs that prints for at most 20 times"; +LOG_ONCE(WARNING) << "Logs that only prints once"; +``` + +# DESCRIPTION + +Streaming log is the best choice for printing complex objects or template objects. As most objects are complicate, user needs to convert all the fields to string first in order to use `printf` with `%s`. However it's very inconvenient (can't append numbers) and needs lots of temporary memory (caused by string). The solution in C++ is to send the log as a stream to the `std::ostream` object. For example, in order to print object A, we need to implement the following interface: + +```c++ +std::ostream& operator<<(std::ostream& os, const A& a); +``` + +The signature of the function means to print object `a` to `os` and then return `os`. The return value of `os` enables us to combine binary operator `<<` (left-combine). As a result, `os << a << b << c;` means `operator<<(operator<<(operator<<(os, a), b), c);`. Apparently `operator<<` needs a returning reference to complete this process, which is also called chaining. In languages that don't support operator overloading, you will see a more tedious form, such as `os.print(a).print(b).print(c)`. + +You should also use chaining in your own implementation of `operator<<`. In fact, printing a complex object is like DFS a tree: Call `operator<<` on each child node, and then each child node invokes the function on the grandchild node, and so forth. For example, object A has two member variables: B and C. Printing A becomes the process of putting B and C ostream: + +```c++ +struct A { + B b; + C c; +}; +std::ostream& operator<<(std::ostream& os, const A& a) { + return os << "A{b=" << a.b << ", c=" << a.c << "}"; +} +``` + +Data structure of B and C along with the print function: + +```c++ +struct B { + int value; +}; +std::ostream& operator<<(std::ostream& os, const B& b) { + return os << "B{value=" << b.value << "}"; +} + +struct C { + string name; +}; +std::ostream& operator<<(std::ostream& os, const C& c) { + return os << "C{name=" << c.name << "}"; +} +``` + +Finally the result of printing object A is: + +``` +A{b=B{value=10}, c=C{name=tom}} +``` + +This way we don't need to allocate temporary memory since objects are directly passed into the ostream object. Of course, the memory management of ostream itself is another topic. + +OK, now we connect the whole printing process by ostream. The most common ostream objects are `std::cout` and `std::cerr`, so objects implement the above function can be directly sent to `std::cout` and `std::cerr`. In other words, if a log stream also inherits ostream, then these objects can be written into log. Streaming log is such a log stream that inherits `std::ostream` to send the object into the log. In the current implementation, the logs are recorded in a thread-local buffer, which will be flushed into screen or ` logging::LogSink` after a complete log record. Of course, the implementation is thread safe. + +## LOG + +If you have ever used glog before, you should find it easy to start. The log macro is the same as glog. For example, to print a FATAL log (Note that there is no `std::endl`): + +```c++ +LOG(FATAL) << "Fatal error occurred! contexts=" << ...; +LOG(WARNING) << "Unusual thing happened ..." << ...; +LOG(TRACE) << "Something just took place..." << ...; +``` + +The log level of streaming log in accordance with glog: + +| streaming log | glog | Use Cases | +| ------------- | -------------------- | ---------------------------------------- | +| FATAL | FATAL (coredump) | Fatal error. Since most fatal log inside baidu is not fatal actually, it won't trigger coredump directly as glog, unless you turn on [-crash_on_fatal_log](http://brpc.baidu.com:8765/flags/crash_on_fatal_log) | +| ERROR | ERROR | Non-fatal error. | +| WARNING | WARNING | Unusual branches | +| NOTICE | - | Generally you should not use NOTICE as it's intended for important business logs. Make sure to check with other developers. glog doesn't have NOTICE. | +| INFO, TRACE | INFO | Important side effects such as open/close some resources. | +| VLOG(n) | INFO | Detailed log that support multiple layers. | +| DEBUG | INFOVLOG(1) (NDEBUG) | Just for compatibility. Print logs only when `NDEBUG` is not defined. See DLOG/DPLOG/DVLOG for more reference. | + +## PLOG + +The difference of PLOG and LOG is that it will append error information at the end of log. It's kind of like `%m` in `printf`. Under POSIX environment, the error code is `errno`。 + +```c++ +int fd = open("foo.conf", O_RDONLY); // foo.conf does not exist, errno was set to ENOENT +if (fd < 0) { + PLOG(FATAL) << "Fail to open foo.conf"; // "Fail to open foo.conf: No such file or directory" + return -1; +} +``` + +## noflush + +If you don't want to flush the log at once, append `noflush`. It's commonly used inside a loop: + +```c++ +LOG(TRACE) << "Items:" << noflush; +for (iterator it = items.begin(); it != items.end(); ++it) { + LOG(TRACE) << ' ' << *it << noflush; +} +LOG(TRACE); +``` + +The first two LOG(TRACE) doesn't flush the log to the screen. They are recorded inside the thread-local buffer. The third LOG(TRACE) flush all logs into the screen. If there are 3 elements inside items and we don't append `noflush`, the result would be: + +``` +TRACE: ... Items: +TRACE: ... item1 +TRACE: ... item2 +TRACE: ... item3 +``` + +After we add `noflush`: + +``` +TRACE: ... Items: item1 item2 item3 +``` + +The `noflush` feature also support bthread so that we can push lots of logs from the server's bthreads without actually print them (using `noflush`), and flush the whole log at the end of RPC. Note that you should not use `noflush` when implementing an asynchronous method since it will change the underlying bthread, leaving `noflush` out of function. + +## LOG_IF + +`LOG_IF(log_level, condition)` prints only when condition is true. It's the same as `if (condition) { LOG() << ...; }` with shorter code: + +```c++ +LOG_IF(NOTICE, n > 10) << "This log will only be printed when n > 10"; +``` + +## XXX_EVERY_SECOND + +XXX represents for LOG, LOG_IF, PLOG, SYSLOG, VLOG, DLOG, and so on. These logging macros print log at most once per second. You can use these to check running status inside hotspot area. The first call to this macro prints the log immediately, and costs additional 30ns (caused by gettimeofday) compared to normal LOG. + +```c++ +LOG_EVERY_SECOND(INFO) << "High-frequent logs"; +``` + +## XXX_EVERY_N + +XXX represents for LOG, LOG_IF, PLOG, SYSLOG, VLOG, DLOG, and so on. These logging macros print log every N times. You can use these to check running status inside hotspot area. The first call to this macro prints the log immediately, and costs an additional atomic operation (relaxed order) compared to normal LOG. This macro is thread safe which means counting from multiple threads is also accurate while glog is not. + +```c++ +LOG_EVERY_N(ERROR, 10) << "High-frequent logs"; +``` + +## XXX_FIRST_N + +XXX represents for LOG, LOG_IF, PLOG, SYSLOG, VLOG, DLOG, and so on. These logging macros print log at most N times. It costs an additional atomic operation (relaxed order) compared to normal LOG before N, and zero cost after. + +```c++ +LOG_FIRST_N(ERROR, 20) << "Logs that prints for at most 20 times"; +``` + +## XXX_ONCE + +XX represents for LOG, LOG_IF, PLOG, SYSLOG, VLOG, DLOG, and so on. These logging macros print log at most once. It's the same as `XXX_FIRST_N(..., 1)` + +```c++ +LOG_ONCE(ERROR) << "Logs that only prints once"; +``` + +## VLOG + +VLOG(verbose_level) is detail log that support multiple layers. It uses 2 gflags: *--verbose* and *--verbose_module* to control the logging layer you want (Note that glog uses *--v* and *--vmodule*). The log will be printed only when `--verbose` >= `verbose_level`: + +```c++ +VLOG(0) << "verbose log tier 0"; +VLOG(1) << "verbose log tier 1"; +VLOG(2) << "verbose log tier 2"; +``` + +When `--verbose=1`, the first 2 log will be printed while the last won't. Module means a file or file path without the extension name, and value of `--verbose_module` will overwrite `--verbose`. For example: + +```bash +--verbose=1 --verbose_module="channel=2,server=3" # print VLOG of those with verbose value: + # channel.cpp <= 2 + # server.cpp <= 3 + # other files <= 1 +--verbose=1 --verbose_module="src/brpc/channel=2,server=3" + # For files with same names, add paths +``` + +You can set `--verbose` and `--verbose_module` through `google::SetCommandLineOption` dynamically. + +VLOG has another form VLOG2, which allows user to specify virtual path: + +```c++ +// public/foo/bar.cpp +VLOG2("a/b/c", 2) << "being filtered by a/b/c rather than public/foo/bar"; +``` + +> VLOG and VLOG2 also have corresponding VLOG_IF and VLOG2_IF. + +## DLOG + +All log macros have debug versions, starting with D, such as DLOG, DVLOG. When NDEBUG is defined, these logs will not be printed. + +**Do not put important side effects inside the log streams beginning with D.** + +*No printing* means that even the parameters are not evaluated. If your parameters have side effects, they won't happend when NDEBUG is defined. For example, `DLOG(FATAL) << foo();` where foo is a function or it changes a dictionary, anyway, it's essential. However, it won't be evaluated when NDEBUG is defined. + +## CHECK + +Another import variation of logging is `CHECK(expression)`. When expression evaluates to false, it will print a fatal log. It's kind of like `ASSERT` in gtest, and has other form such as CHECK_EQ, CHECK_GT, and so on. When check fails, the message after will be printed. + +```c++ +CHECK_LT(1, 2) << "This is definitely true, this log will never be seen"; +CHECK_GT(1, 2) << "1 can't be greater than 2"; +``` + +Run the above code you should see a fatal log and the calling stack: + +``` +FATAL: ... Check failed: 1 > 2 (1 vs 2). 1 can't be greater than 2 +#0 0x000000afaa23 butil::debug::StackTrace::StackTrace() +#1 0x000000c29fec logging::LogStream::FlushWithoutReset() +#2 0x000000c2b8e6 logging::LogStream::Flush() +#3 0x000000c2bd63 logging::DestroyLogStream() +#4 0x000000c2a52d logging::LogMessage::~LogMessage() +#5 0x000000a716b2 (anonymous namespace)::StreamingLogTest_check_Test::TestBody() +#6 0x000000d16d04 testing::internal::HandleSehExceptionsInMethodIfSupported<>() +#7 0x000000d19e96 testing::internal::HandleExceptionsInMethodIfSupported<>() +#8 0x000000d08cd4 testing::Test::Run() +#9 0x000000d08dfe testing::TestInfo::Run() +#10 0x000000d08ec4 testing::TestCase::Run() +#11 0x000000d123c7 testing::internal::UnitTestImpl::RunAllTests() +#12 0x000000d16d94 testing::internal::HandleSehExceptionsInMethodIfSupported<>() +``` + +The second column of the callstack is the address of the code segment. You can use `addr2line` to check the corresponding file and line: + +``` +$ addr2line -e ./test_base 0x000000a716b2 +/home/gejun/latest_baidu_rpc/public/common/test/test_streaming_log.cpp:223 +``` + +You **should** use `CHECK_XX` for arithmetic condition so that you can see more detailed information when check failed. + +```c++ +int x = 1; +int y = 2; +CHECK_GT(x, y); // Check failed: x > y (1 vs 2). +CHECK(x > y); // Check failed: x > y. +``` + +Like DLOG, you should NOT include important side effects inside DCHECK. + +## LogSink + +The default destination of streaming log is the screen. You can change it through `logging::SetLogSink`. Users can inherit LogSink and implement their own output logic. We provide an internal LogSink as an example: + +### StringSink + +Inherit both LogSink and string. Store log content inside string and mainly aim for unit test. The following case shows a classic usage of StringSink: + +```c++ +TEST_F(StreamingLogTest, log_at) { + ::logging::StringSink log_str; + ::logging::LogSink* old_sink = ::logging::SetLogSink(&log_str); + LOG_AT(FATAL, "specified_file.cc", 12345) << "file/line is specified"; + // the file:line part should be using the argument given by us. + ASSERT_NE(std::string::npos, log_str.find("specified_file.cc:12345")); + // restore the old sink. + ::logging::SetLogSink(old_sink); +} +``` + diff --git a/docs/en/streaming_rpc.md b/docs/en/streaming_rpc.md new file mode 100644 index 0000000..7a41c24 --- /dev/null +++ b/docs/en/streaming_rpc.md @@ -0,0 +1,155 @@ +[中文版](../cn/streaming_rpc.md) + +# Overview + +There are some scenarios when the client or server needs to send huge amount of data, which may grow over time or is too large to put into the RPC attachment. For example, it could be the replica or snapshot transmitting between different nodes in a distributed system. Although we could send data segmentation across multiple RPC between client and server, this will introduce the following problems: + +- If these RPCs are parallel, there is no guarantee on the order of the data at the receiving side, which leads to complicate code of reassembling. +- If these RPCs are serial, we have to endure the latency of the network RTT for each RPC together with the process time, which is especially unpredictable. + +In order to allow large packets to flow between client and server like a stream, we provide a new communication model: Streaming RPC. Streaming RPC enables users to establish Stream which is a user-space connection between client and service. Multiple Streams can share the same TCP connection at the same time. The basic transmission unit on Stream is message. As a result, the sender can continuously write to messages to a Stream, while the receiver can read them out in the order of sending. + +Streaming RPC ensures/provides: + +- The message order at the receiver is exactly the same as that of the sender +- Boundary for messages +- Full duplex +- Flow control +- Notification on timeout +- We support segment large messages automatically to avoid [Head-of-line blocking](https://en.wikipedia.org/wiki/Head-of-line_bloc +king) problem. + +For examples please refer to [example/streaming_echo_c++](https://github.com/apache/brpc/tree/master/example/streaming_echo_c++/). + +# Create a Stream + +Currently streams are established by the client only. The new Stream objects are created in client and then are used to issue an RPC (through baidu_std protocol) to the specified service. The service could accept these streams by responding to the request without error, thus the Streams are created once the client receives the response successfully. Any error during this process fails the RPC and thus fails the Stream creation. Take the Linux environment as an example, the client creates a [socket](http://linux.die.net/man/7/socket) first (creates a Stream), and then tries to establish a connection with the remote side by [connect](http://linux.die.net/man/2/connect) (establish a Stream through RPC). Finally the stream has been created once the remote side [accept](http://linux.die.net/man/2/accept) the request. + +> If the client tries to establish a stream to a server that doesn't support streaming RPC, it will always return failure. + +In the code we use `StreamId` to represent a Stream, which is the key ID to pass when reading, writing, closing the Stream. + +```c++ +struct StreamOptions + // The max size of unconsumed data allowed at remote side. + // If |max_buf_size| <= 0, there's no limit of buf size + // default: 2097152 (2M) + int max_buf_size; + + // Notify user when there's no data for at least |idle_timeout_ms| + // milliseconds since the last time that on_received_messages or on_idle_timeout + // finished. + // default: -1 + long idle_timeout_ms; + + // Maximum messages in batch passed to handler->on_received_messages + // default: 128 + size_t messages_in_batch; + + // Handle input message, if handler is NULL, the remote side is not allowed to + // write any message, who will get EBADF on writing + // default: NULL + StreamInputHandler* handler; +}; + +// [Called at the client side] +// Create a Stream at client-side along with the |cntl|, which will be connected +// when receiving the response with a Stream from server-side. If |options| is +// NULL, the Stream will be created with default options +// Return 0 on success, -1 otherwise +int StreamCreate(StreamId* request_stream, Controller &cntl, const StreamOptions* options); + +// [Called at the client side for creating multiple streams] +// Create streams at client-side along with the |cntl|, which will be connected +// when receiving the response with streams from server-side. If |options| is +// NULL, the stream will be created with default options +// Return 0 on success, -1 otherwise +int StreamCreate(StreamIds& request_streams, int request_stream_size, Controller& cntl, + const StreamOptions* options); +``` + +# Accept a Stream + +If a Stream is attached inside the request of an RPC, the service can accept the Stream by `StreamAccept`. On success this function fills the created Stream into `response_stream`, which can be used to send message to the client. + +```c++ +// [Called at the server side] +// Accept the Stream. If client didn't create a Stream with the request +// (cntl.has_remote_stream() returns false), this method would fail. +// Return 0 on success, -1 otherwise. +int StreamAccept(StreamId* response_stream, Controller &cntl, const StreamOptions* options); + +// [Called at the server side for accepting multiple streams] +// Accept the streams. If client didn't create streams with the request +// (cntl.has_remote_stream() returns false), this method would fail. +// Return 0 on success, -1 otherwise. +int StreamAccept(StreamIds& response_stream, Controller& cntl, const StreamOptions* options); +``` + +# Read from a Stream + +Upon creating/accepting a Stream, you can fill the `hander` in `StreamOptions` with your own implemented `StreamInputHandler`. Then you will be notified when the stream receives data, is closed by the other end, or reaches idle timeout. + +```c++ +class StreamInputHandler { +public: + // Callback when stream receives data + virtual int on_received_messages(StreamId id, butil::IOBuf *const messages[], size_t size) = 0; + + // Callback when there is no data for a long time on the stream + virtual void on_idle_timeout(StreamId id) = 0; + + // Callback when stream is closed by the other end + virtual void on_closed(StreamId id) = 0; +}; +``` + +> ***The first call to `on_received_message `*** +> +> On the client's side, if the creation process is synchronous, `on_received_message` will be called when the blocking RPC returns. If it's asynchronous, `on_received_message` won't be called until `done->Run()` finishes. +> +> On the server' side, `on_received_message` will be called once `done->Run()` finishes. + +# Write to a Stream + +```c++ +// Write |message| into |stream_id|. The remote-side handler will received the +// message by the written order +// Returns 0 on success, errno otherwise +// Errno: +// - EAGAIN: |stream_id| is created with positive |max_buf_size| and buf size +// which the remote side hasn't consumed yet exceeds the number. +// - EINVAL: |stream_id| is invalid or has been closed +int StreamWrite(StreamId stream_id, const butil::IOBuf &message); +``` + +# Flow Control + +When the amount of unacknowledged data reaches the limit, the `Write` operation at the sender will fail with EAGAIN immediately. At this moment, you should wait for the receiver to consume the data synchronously or asynchronously. + +```c++ +// Wait until the pending buffer size is less than |max_buf_size| or error occurs +// Returns 0 on success, errno otherwise +// Errno: +// - ETIMEDOUT: when |due_time| is not NULL and time expired this +// - EINVAL: the Stream was close during waiting +int StreamWait(StreamId stream_id, const timespec* due_time); + +// Async wait +void StreamWait(StreamId stream_id, const timespec *due_time, + void (*on_writable)(StreamId stream_id, void* arg, int error_code), + void *arg); +``` + +# Close a Stream + +```c++ +// Close |stream_id|, after this function is called: +// - All the following |StreamWrite| would fail +// - |StreamWait| wakes up immediately. +// - Both sides |on_closed| would be notified after all the pending buffers have +// been received +// This function could be called multiple times without side-effects +int StreamClose(StreamId stream_id); +``` + diff --git a/docs/en/thread_local.md b/docs/en/thread_local.md new file mode 100644 index 0000000..e817ff6 --- /dev/null +++ b/docs/en/thread_local.md @@ -0,0 +1,7 @@ +# thread local + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/thread_local.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/threading_overview.md b/docs/en/threading_overview.md new file mode 100644 index 0000000..d67d14d --- /dev/null +++ b/docs/en/threading_overview.md @@ -0,0 +1,45 @@ +[中文版](../cn/threading_overview.md) + +# Common threading models + +## Connections own threads or processes exclusively + +In this model, a thread/process handles all messages from a connection and does not quit or do other jobs before the connection is closed. When number of connections increases, resources occupied by threads/processes and costs of context switches becomes more and more overwhelming, making servers perform poorly. This situation is summarized as the [C10K](http://en.wikipedia.org/wiki/C10k_problem) problem, which was common in early web servers but rarely present today. + +## Single-threaded [reactor](http://en.wikipedia.org/wiki/Reactor_pattern) + +Event-loop libraries such as [libevent](http://libevent.org/), [libev](http://software.schmorp.de/pkg/libev.html) are typical examples. There's usually an event dispatcher in this model responsible for waiting on different kinds of events and calling the corresponding event handler **in-place** when an event occurs. After all handlers(that need to be called) are called, the dispatcher waits for more events again, which forms a "loop". Essentially this model multiplexes(interleaves) code written in different handlers into a system thread. One event-loop can only utilize one core, so this kind of program is either IO-bound or each handler runs within short and deterministic time(such as http servers), otherwise one callback taking long time blocks the whole program and causes high delays. In practice this kind of program is not suitable for involving many developers, because just one person adding inappropriate blocking code may significantly slow down reactivities of all other code. Since event handlers don't run simultaneously, race conditions between callbacks are relatively simple and in some scenarios locks are not needed. These programs are often scaled by deploying more processes. + +How single-threaded reactors work and the problems related are demonstrated below: (The Chinese characters in red: "Uncontrollable! unless the service is specialized") + +![img](../images/threading_overview_1.png) + +## N:1 threading library + +Also known as [Fiber](http://en.wikipedia.org/wiki/Fiber_(computer_science)). Typical examples are [GNU Pth](http://www.gnu.org/software/pth/pth-manual.html), [StateThreads](http://state-threads.sourceforge.net/index.html). This model maps N user threads into a single system thread, in which only one user thread runs at the same time and the running user thread does not switch to other user threads until a blocking primitive is called (cooperative). N:1 threading libraries are equal to single-threaded reactors on capabilities, except that callbacks are replaced by contexts (stacks, registers, signals) and running callbacks becomes jumping to contexts. Similar to event-loop libraries, a N:1 threading library cannot utilize multiple CPU cores, thus only suitable for specialized applications. However a single system thread is more friendly to CPU caches, with removal of the support for signal masks, context switches between user threads can be done very fast(100 ~ 200ns). N:1 threading libraries perform as well as event-loop libraries and are also scaled by deploying more processes in general. + +## Multi-threaded reactor + +[boost::asio](http://www.boost.org/doc/libs/1_56_0/doc/html/boost_asio.html) is a typical example. One or several threads run event dispatchers respectively. When an event occurs, the event handler is queued into a worker thread to run. This model is extensible from single-threaded reactor intuitively and able to make use of multiple CPU cores. Since sharing memory addresses makes interactions between threads much cheaper, the worker threads are able to balance loads between each other frequently, as a contrast multiple single-threaded reactors basically depend on the front-end servers to distribute traffic. A well-implemented multi-threaded reactor is likely to utilize CPU cores more evenly than multiple single-threaded reactors on the same machine. However, due to [cache coherence](atomic_instructions.md#cacheline), multi-threaded reactors are unlikely to achieve linear scalability on CPU cores. In particular scenarios, a badly implemented multi-threaded reactor running on 24 cores is even slower than a well-tuned single-threaded reactor. Because a multi-threaded reactor has multiple worker threads, one blocking event handler may not delay other handlers. As a result, event handlers are not required to be non-blocking unless all worker threads are blocked, in which case the overall progress is affected. In fact, most RPC frameworks are implemented in this model with event handlers that may block, such as synchronously waiting for RPCs to downstream servers. + +How multi-threaded reactors work and problems related are demonstrated below: + +![img](../images/threading_overview_2.png) + +## M:N threading library + +This model maps M user threads into N system threads. An M:N threading library is able to decide when and where to run a piece of code and when to end the execution, which is more flexible at scheduling compared to multi-threaded reactors. But full-featured M:N threading libraries are difficult to implement and remaining as active research topics. The M:N threading library that we're talking about is specialized for building online services, in which case, some of the requirements can be simplified, namely no (complete) preemptions and priorities. M:N threading libraries can be implemented either in userland or OS kernel. New programming languages prefer implementations in userland, such as GHC thread and goroutine, which is capable of adding brand-new keywords and intercepting related APIs on threading. Implementation in existing languages often have to modify the OS kernel, such as [Windows UMS](https://msdn.microsoft.com/en-us/library/windows/desktop/dd627187(v=vs.85).aspx) and google SwicthTo(which is 1:1, however M:N effects can be achieved based on it). Compared to N:1 threading libraries, usages of M:N threading libraries are more similar to system threads, which need locks or message passings to ensure thread safety. + +# Issues + +## Multi-core scalability + +Ideally capabilities of the reactor model are maximized when all source code is programmed in event-driven manner, but in reality because of the difficulties and maintainability, users are likely to mix usages: synchronous IO is often issued in callbacks, blocking worker threads from processing other requests. A request often goes through dozens of services, making worker threads spend a lot of time waiting for responses from downstream servers. Users have to launch hundreds of threads to maintain enough throughput, which imposes intensive pressure on scheduling and lowers efficiencies of TLS related code. Tasks are often pushed into a queue protected with a global mutex and condition, which performs poorly when many threads are contending for it. A better approach is to deploy more task queues and adjust the scheduling algorithm to reduce global contentions. Namely each system thread has its own runqueue, and one or more schedulers dispatch user threads to different runqueues. Each system thread runs user threads from its own runqueue before considering other runqueues, which is more complicated but more scalable than the global mutex+condition solution. This model is also easier to support NUMA. + +When an event dispatcher passes a task to a worker thread, the user code probably jumps from one CPU core to another, which may need to wait for synchronizations of relevant cachelines, which is not very fast. It would be better that the worker is able to run directly on the CPU core where the event dispatcher runs, since at most of the time, priority of running the worker ASAP is higher than getting new events from the dispatcher. Similarly, it's better to wake up the user thread blocking on RPC on the same CPU core where the response is received. + +## Asynchronous programming + +Flow controls in asynchronous programming are even difficult for experts. Any suspending operation such as sleeping for a while or waiting for something to finish, implies that users have to save states explicitly and restore states in callbacks. Asynchronous code is often written as state machines. A few suspensions are troublesome, but still handleable. The problem is that once the suspension occurs inside a condition, loop or sub-function, it's almost impossible to write such a state machine being understood and maintained by many people, although the scenario is quite common in distributed systems where a node often needs to interact with multiple nodes simultaneously. In addition, if the wakeup can be triggered by more than one events (such as either fd has data or timeout is reached), the suspension and resuming are prone to race conditions, which require good multi-threaded programming skills to solve. Syntactic sugars(such as lambda) just make coding less troublesome rather than reducing difficulty. + +Shared pointers are common in asynchronous programming, which seems convenient, but also makes ownerships of memory elusive. If the memory is leaked, it's difficult to locate the code that forgot to release; if segment fault happens, where the double-free occurs is also unknown. Code with a lot of referential countings is hard to remain good-quality and may waste a lot of time on debugging memory related issues. If references are even counted manually, keeping quality of the code is harder and the maintainers are less willing to modify the code. [RAII](http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization) cannot be used in many scenarios in asynchronous programming, sometimes resources need to be locked before a callback and unlocked inside the callback, which is very error-prone in practice. diff --git a/docs/en/thrift.md b/docs/en/thrift.md new file mode 100755 index 0000000..d57f654 --- /dev/null +++ b/docs/en/thrift.md @@ -0,0 +1,146 @@ +[中文版](../cn/thrift.md) + +[thrift](https://thrift.apache.org/) is a RPC framework used widely in various environments, which was developed by Facebook and adopted by Apache later. In order to interact with thrift servers and solves issues on thread-safety, usabilities and concurrencies, brpc directly supports the thrift protocol that is used by thrift in NonBlocking mode. + +Example: [example/thrift_extension_c++](https://github.com/apache/brpc/tree/master/example/thrift_extension_c++/). + +Advantages compared to the official solution: +- Thread safety. No need to set up separate clients for each thread. +- Supports synchronous, asynchronous, batch synchronous, batch asynchronous, and other access methods. Combination channels such as ParallelChannel are also supported. +- Support various connection types(short, connection pool). Support timeout, backup request, cancellation, tracing, built-in services, and other benefits offered by brpc. +- Better performance. + +# Compile +brpc depends on the thrift library and reuses some code generated by thrift tools. Please read official documents to find out how to write thrift files, generate code, compilations etc. + +brpc does not enable thrift support or depend on the thrift lib by default. If the support is needed, compile brpc with extra --with-thrift or -DWITH_THRIFT=ON + +Install thrift under Linux +Read [Official wiki](https://thrift.apache.org/docs/install/debian) to install depended libs and tools, then download thrift source code from [official site](https://thrift.apache.org/download), uncompress and compile。 +```bash +wget https://downloads.apache.org/thrift/0.22.0/thrift-0.22.0.tar.gz +tar -xf thrift-0.22.0.tar.gz +cd thrift-0.22.0/ +./bootstrap.sh +./configure --prefix=/usr --with-ruby=no --with-python=no --with-java=no --with-go=no --with-perl=no --with-php=no --with-csharp=no --with-erlang=no --with-lua=no --with-nodejs=no --with-rs=no --with-py3=no CXXFLAGS='-Wno-error' +make CPPFLAGS=-DFORCE_BOOST_SMART_PTR -j 4 -s +sudo make install +``` + +Config brpc with thrift support, then make. The compiled libbrpc.a includes extended code for thrift support and can be linked normally as in other brpc projects. +```bash +# Ubuntu +sh config_brpc.sh --headers=/usr/include --libs=/usr/lib --with-thrift +# Fedora/CentOS +sh config_brpc.sh --headers=/usr/include --libs=/usr/lib64 --with-thrift +# Or use cmake +mkdir build && cd build && cmake ../ -DWITH_THRIFT=ON +``` +Read [Getting Started](getting_started.md) for more compilation options. + +# Client accesses thrift server +Steps: +- Create a Channel setting protocol to brpc::PROTOCOL_THRIFT +- Create brpc::ThriftStub +- Use native request and response to start RPC directly. + +Example code: +```c++ +#include +#include         // Defines ThriftStub +... + +DEFINE_string(server, "0.0.0.0:8019", "IP Address of thrift server"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +... + +brpc::ChannelOptions options; +options.protocol = brpc::PROTOCOL_THRIFT; +brpc::Channel thrift_channel; +if (thrift_channel.Init(Flags_server.c_str(), FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize thrift channel"; + return -1; +} + +brpc::ThriftStub stub(&thrift_channel); +... + +// example::[EchoRequest/EchoResponse] are types generated by thrift +example::EchoRequest req; +example::EchoResponse res; +req.data = "hello"; + +stub.CallMethod("Echo", &cntl, &req, &res, NULL); +if (cntl.Failed()) { + LOG(ERROR) << "Fail to send thrift request, " << cntl.ErrorText(); + return -1; +} +``` + +# Server processes thrift requests +Inherit brpc::ThriftService to implement the processing code, which may call the native handler generated by thrift to re-use existing entry directly, or read the request and set the response directly just as in other protobuf services. +```c++ +class EchoServiceImpl : public brpc::ThriftService { +public: + void ProcessThriftFramedRequest(brpc::Controller* cntl, + brpc::ThriftFramedMessage* req, + brpc::ThriftFramedMessage* res, + google::protobuf::Closure* done) override { + // Dispatch calls to different methods + if (cntl->thrift_method_name() == "Echo") { + return Echo(cntl, req->Cast(), + res->Cast(), done); + } else { + cntl->SetFailed(brpc::ENOMETHOD, "Fail to find method=%s", + cntl->thrift_method_name().c_str()); + done->Run(); + } + } + + void Echo(brpc::Controller* cntl, + const example::EchoRequest* req, + example::EchoResponse* res, + google::protobuf::Closure* done) { + // This object helps you to call done->Run() in RAII style. If you need + // to process the request asynchronously, pass done_guard.release(). + brpc::ClosureGuard done_guard(done); + + res->data = req->data + " (processed)"; + } +}; +``` + +Set the implemented service to ServerOptions.thrift_service and start the service. +```c++ + brpc::Server server; + brpc::ServerOptions options; + options.thrift_service = new EchoServiceImpl; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + options.max_concurrency = FLAGS_max_concurrency; + + // Start the server. + if (server.Start(FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } +``` + +# Performance test for native thrift compare with brpc thrift implementaion +Test Env: 48 core 2.30GHz +## server side return string "hello" sent from client +Framework | Threads Num | QPS | Avg lantecy | CPU +---- | --- | --- | --- | --- +native thrift | 60 | 6.9w | 0.9ms | 2.8% +brpc thrift | 60 | 30w | 0.2ms | 18% + +## server side return string "hello" * 1000 +Framework | Threads Num | QPS | Avg lantecy | CPU +---- | --- | --- | --- | --- +native thrift | 60 | 5.2w | 1.1ms | 4.5% +brpc thrift | 60 | 19.5w | 0.3ms | 22% + +## server side do some complicated math and return string "hello" * 1000 +Framework | Threads Num | QPS | Avg lantecy | CPU +---- | --- | --- | --- | --- +native thrift | 60 | 1.7w | 3.5ms | 76% +brpc thrift | 60 | 2.1w | 2.9ms | 93% diff --git a/docs/en/timeout_concurrency_limiter.md b/docs/en/timeout_concurrency_limiter.md new file mode 100644 index 0000000..e592df9 --- /dev/null +++ b/docs/en/timeout_concurrency_limiter.md @@ -0,0 +1,7 @@ +# timeout concurrency limiter + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/timeout_concurrency_limiter.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/timer_keeping.md b/docs/en/timer_keeping.md new file mode 100644 index 0000000..f9bb8c7 --- /dev/null +++ b/docs/en/timer_keeping.md @@ -0,0 +1,7 @@ +# timer keeping + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/timer_keeping.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/tutorial_on_building_services.pptx b/docs/en/tutorial_on_building_services.pptx new file mode 100644 index 0000000..01e6e66 Binary files /dev/null and b/docs/en/tutorial_on_building_services.pptx differ diff --git a/docs/en/ub_client.md b/docs/en/ub_client.md new file mode 100644 index 0000000..670a586 --- /dev/null +++ b/docs/en/ub_client.md @@ -0,0 +1,7 @@ +# uu client + +This document has not yet been translated into English. + +Please refer to the [Chinese version](../cn/ub_client.md) for the full content. + +Contributions to translate this document are welcome. See [TRANSLATING](TRANSLATING) for guidelines. diff --git a/docs/en/vars.md b/docs/en/vars.md new file mode 100644 index 0000000..49ad780 --- /dev/null +++ b/docs/en/vars.md @@ -0,0 +1,101 @@ +[中文版](../cn/vars.md) + +[bvar](https://github.com/apache/brpc/tree/master/src/bvar/) is a set of counters to record and view miscellaneous statistics conveniently in multi-threaded applications. The implementation reduces cache bouncing by storing data in thread local storage(TLS), being much faster than UbMonitor(a legacy counting library inside Baidu) and even atomic operations in highly contended scenarios. brpc integrates bvar by default, namely all exposed bvars in a server are accessible through [/vars](http://brpc.baidu.com:8765/vars), and a single bvar is addressable by [/vars/VARNAME](http://brpc.baidu.com:8765/vars/rpc_socket_count). Read [bvar](bvar.md) to know how to add bvars for your program. brpc extensively use bvar to expose internal status. If you are looking for an utility to collect and display metrics of your application, consider bvar in the first place. bvar definitely can't replace all counters, essentially it moves contentions occurred during write to read: which needs to combine all data written by all threads and becomes much slower than an ordinary read. If read and write on the counter are both frequent or decisions need to be made based on latest values, you should not use bvar. + +## Query methods + +[/vars](http://brpc.baidu.com:8765/vars) : List all exposed bvars + +[/vars/NAME](http://brpc.baidu.com:8765/vars/rpc_socket_count):List the bvar whose name is `NAME` + +[/vars/NAME1,NAME2,NAME3](http://brpc.baidu.com:8765/vars/pid;process_cpu_usage;rpc_controller_count):List bvars whose names are either `NAME1`, `NAME2` or `NAME3`. + +[/vars/foo*,b$r](http://brpc.baidu.com:8765/vars/rpc_server*_count;iobuf_blo$k_*): List bvars whose names match given wildcard patterns. Note that `$` matches a single character instead of `?` which is a reserved character in URL. + +Following animation shows how to find bvars with wildcard patterns. You can copy and paste the URL to others who will see same bvars that you see. (values may change) + +![img](../images/vars_1.gif) + +There's a search box in the upper-left corner on /vars page, in which you can type part of the names to locate bvars. Different patterns are separated by `,` `:` or space. + +![img](../images/vars_2.gif) + +/vars is accessible from terminal as well: + +```shell +$ curl brpc.baidu.com:8765/vars/bthread* +bthread_creation_count : 125134 +bthread_creation_latency : 3 +bthread_creation_latency_50 : 3 +bthread_creation_latency_90 : 5 +bthread_creation_latency_99 : 7 +bthread_creation_latency_999 : 12 +bthread_creation_latency_9999 : 12 +bthread_creation_latency_cdf : "click to view" +bthread_creation_latency_percentiles : "[3,5,7,12]" +bthread_creation_max_latency : 7 +bthread_creation_qps : 100 +bthread_group_status : "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 " +bthread_num_workers : 24 +bthread_worker_usage : 1.01056 +``` + +## View historical trends + +Clicking on most of the numerical bvars shows historical trends. Each clickable bvar records values in recent *60 seconds, 60 minutes, 24 hours and 30 days*, which are *174* numbers in total. 1000 clickable bvars take roughly 1M memory. + +![img](../images/vars_3.gif) + +## Calculate and view percentiles + +x-ile (short for x-th percentile) is the value ranked at N * x%-th position amongst a group of ordered values. E.g. If there're 1000 values inside a time window, sort them in ascending order first. The 500-th value(1000 * 50%) in the ordered list is 50-ile(a.k.a median), the 990-th(1000 * 99%) value is 99-ile, the 999-th value is 99.9-ile. Percentiles give more information on how latencies distribute than mean values, and being helpful for analyzing behavior of the system more accurately. Industrial-grade services often require SLA to be not less than 99.97% (the requirement for 2nd-level services inside Baidu, >=99.99% for 1st-level services), even if a system has good average latencies, a bad long-tail area may still break SLA. Percentiles do help analyzing the long-tail area. + +Percentiles can be plotted as a CDF or percentiles-over-time curve. + +**Following diagram plots percentiles as CDF**, where the X-axis is the ratio(ranked-position/total-number) and the Y-axis is the corresponding percentile. E.g. The Y value corresponding to X=50% is 50-ile. If a system requires that "99.9% requests need to be processed within Y milliseconds", you should check the Y at 99.9%. + +![img](../images/vars_4.png) + +Why do we call it [CDF](https://en.wikipedia.org/wiki/Cumulative_distribution_function) ? When a Y=y is chosen, the corresponding X means "percentage of values <= y". Since values are sampled randomly (and uniformly), the X can be viewed as "probability of values <= y", or P(values <= y), which is just the definition of CDF. + +Derivative of the CDF is [PDF](https://en.wikipedia.org/wiki/Probability_density_function). If we divide the Y-axis of the CDF into many small-range segments, calculate the difference between X values of both ends of each segment, and use the difference as new value for X-axis, a PDF curve would be plotted, just like a normal distribution rotated 90 degrees clockwise. However density of the median is often much higher than others in a PDF and probably make long-tail area very flat and hard to read. As a result, systems prefer showing distributions in CDF rather than PDF. + +Here're 2 simple rules to check if a CDF curve is good or not: + +- The flatter the better. A horizontal line is an ideal CDF curve which means that there're no waitings, congestions or pauses, very unlikely in practice. +- The area between 99% and 100% should be as small as possible: right-side of 99% is the long-tail area, which has a significant impact on SLA. + +A CDF with slowly ascending curve and small long-tail area is great in practice. + +**Following diagram plots percentiles over time** and has four curves. The X-axis is time and Y-axis from top to bottom are 99.9% 99% 90% 50% percentiles respectively, plotted in lighter and lighter colors (from orange to yellow). + +![img](../images/vars_5.png) + +Hovering mouse over the curves shows corresponding values at the time. The tooltip in above diagram means "The 99% percentile of latency before 39 seconds is 330 **microseconds**". The diagram does not include the 99.99-ile curve which is usually significantly higher than others, making others hard to read. You may click bvars ended with "\_latency\_9999" to read the 99.99-ile curve separately. This diagram shows how percentiles change over time, which is helpful to analyze performance regressions of systems. + +brpc calculates latency distributions of services automatically, which do not need users to add manually. The metrics are as follows: + +![img](../images/vars_6.png) + +`bvar::LatencyRecorder` is able to calculate latency distributions of any code, as depicted below. (checkout [bvar-c++](bvar_c++.md) for details): + +```c++ +#include + +... +bvar::LatencyRecorder g_latency_recorder("client"); // expose this recorder +... +void foo() { + ... + g_latency_recorder << my_latency; + ... +} +``` + +If the application already starts a brpc server, values like `client_latency`, `client_latency_cdf` can be viewed from `/vars` as follows. Clicking them to see (dynamically-updated) curves: + +![img](../images/vars_7.png) + +## Non brpc server + +If your program only uses brpc client or even not use brpc, and you also want to view the curves, check [here](../cn/dummy_server.md). diff --git a/docs/en/wireshark_baidu_std.md b/docs/en/wireshark_baidu_std.md new file mode 100644 index 0000000..af6a511 --- /dev/null +++ b/docs/en/wireshark_baidu_std.md @@ -0,0 +1,27 @@ +[中文版](../cn/wireshark_baidu_std.md) + +# Overview + +`wireshark_baidu_std.lua` is a Wireshark Lua dissector written for [`baidu_std`](../cn/baidu_std.md) protocol, including the [`streaming_rpc`](streaming_rpc.md) protocol. + +Example for the Echo request: + +![request](../images/wireshark_baidu_std_request.png) + +Example for the Echo response: + +![response](../images/wireshark_baidu_std_response.png) + + +## How to use + +1. Put [`wireshark_baidu_std.lua`](../../tools/wireshark_baidu_std.lua) under "Personal Lua Plugins"; +1. And put [`options.proto`](../../src/brpc/options.proto), [`streaming_rpc_meta.proto`](../../src/brpc/streaming_rpc_meta.proto) and [`baidu_rpc_meta.proto`](../../src/brpc/policy/baidu_rpc_meta.proto) in `protobuf` directory under "Personal configuration", create if not exist; +1. Set `Protobuf Search Paths` for Protobuf official library include directory(e.g. `/opt/homebrew/opt/protobuf/include`), see [Wireshark Protobuf](https://wiki.wireshark.org/Protobuf#protobuf-search-paths-settings) for more details. + ![wireshark-protobuf-search-paths](../images/wireshark_protobuf_search_paths.png) +1. Optional, turn on `Dissect Protobuf fields as Wireshark fields` if you want to use related `baidu_std` fields for filtering: + ![wireshark-protobuf-settings](../images/wireshark_protobuf_settings.png) + +The "Personal Lua Plugins" and "Personal configuration" folders above can be found under the `Folders` page of `About Wireshark`, for macOS: + +![About Wireshark](../images/wireshark_folders.png) diff --git a/docs/images/302.png b/docs/images/302.png new file mode 100644 index 0000000..29feb96 Binary files /dev/null and b/docs/images/302.png differ diff --git a/docs/images/apicontrol_compare_1.png b/docs/images/apicontrol_compare_1.png new file mode 100644 index 0000000..d6baf8e Binary files /dev/null and b/docs/images/apicontrol_compare_1.png differ diff --git a/docs/images/apicontrol_compare_2.png b/docs/images/apicontrol_compare_2.png new file mode 100644 index 0000000..abb3c18 Binary files /dev/null and b/docs/images/apicontrol_compare_2.png differ diff --git a/docs/images/apicontrol_compare_3.png b/docs/images/apicontrol_compare_3.png new file mode 100644 index 0000000..f1cf6a9 Binary files /dev/null and b/docs/images/apicontrol_compare_3.png differ diff --git a/docs/images/apicontrol_compare_4.png b/docs/images/apicontrol_compare_4.png new file mode 100644 index 0000000..6852261 Binary files /dev/null and b/docs/images/apicontrol_compare_4.png differ diff --git a/docs/images/apicontrol_compare_5.png b/docs/images/apicontrol_compare_5.png new file mode 100644 index 0000000..c469718 Binary files /dev/null and b/docs/images/apicontrol_compare_5.png differ diff --git a/docs/images/backup_request_1.png b/docs/images/backup_request_1.png new file mode 100644 index 0000000..7907f46 Binary files /dev/null and b/docs/images/backup_request_1.png differ diff --git a/docs/images/backup_request_2.png b/docs/images/backup_request_2.png new file mode 100644 index 0000000..45ca355 Binary files /dev/null and b/docs/images/backup_request_2.png differ diff --git a/docs/images/backup_request_3.png b/docs/images/backup_request_3.png new file mode 100644 index 0000000..55aee38 Binary files /dev/null and b/docs/images/backup_request_3.png differ diff --git a/docs/images/backup_request_4.png b/docs/images/backup_request_4.png new file mode 100644 index 0000000..f6dc797 Binary files /dev/null and b/docs/images/backup_request_4.png differ diff --git a/docs/images/baidu_dsp_compare_1.png b/docs/images/baidu_dsp_compare_1.png new file mode 100644 index 0000000..318f969 Binary files /dev/null and b/docs/images/baidu_dsp_compare_1.png differ diff --git a/docs/images/baidu_dsp_compare_10.png b/docs/images/baidu_dsp_compare_10.png new file mode 100644 index 0000000..60775cc Binary files /dev/null and b/docs/images/baidu_dsp_compare_10.png differ diff --git a/docs/images/baidu_dsp_compare_2.png b/docs/images/baidu_dsp_compare_2.png new file mode 100644 index 0000000..912a388 Binary files /dev/null and b/docs/images/baidu_dsp_compare_2.png differ diff --git a/docs/images/baidu_dsp_compare_3.png b/docs/images/baidu_dsp_compare_3.png new file mode 100644 index 0000000..6dba357 Binary files /dev/null and b/docs/images/baidu_dsp_compare_3.png differ diff --git a/docs/images/baidu_dsp_compare_4.png b/docs/images/baidu_dsp_compare_4.png new file mode 100644 index 0000000..382daf4 Binary files /dev/null and b/docs/images/baidu_dsp_compare_4.png differ diff --git a/docs/images/baidu_dsp_compare_5.png b/docs/images/baidu_dsp_compare_5.png new file mode 100644 index 0000000..41ab255 Binary files /dev/null and b/docs/images/baidu_dsp_compare_5.png differ diff --git a/docs/images/baidu_dsp_compare_6.png b/docs/images/baidu_dsp_compare_6.png new file mode 100644 index 0000000..3f61dc9 Binary files /dev/null and b/docs/images/baidu_dsp_compare_6.png differ diff --git a/docs/images/baidu_dsp_compare_7.png b/docs/images/baidu_dsp_compare_7.png new file mode 100644 index 0000000..4a442b4 Binary files /dev/null and b/docs/images/baidu_dsp_compare_7.png differ diff --git a/docs/images/baidu_dsp_compare_8.png b/docs/images/baidu_dsp_compare_8.png new file mode 100644 index 0000000..2ee7434 Binary files /dev/null and b/docs/images/baidu_dsp_compare_8.png differ diff --git a/docs/images/baidu_dsp_compare_9.png b/docs/images/baidu_dsp_compare_9.png new file mode 100644 index 0000000..03a9097 Binary files /dev/null and b/docs/images/baidu_dsp_compare_9.png differ diff --git a/docs/images/bthread_concurrency_1.png b/docs/images/bthread_concurrency_1.png new file mode 100644 index 0000000..7cfe02b Binary files /dev/null and b/docs/images/bthread_concurrency_1.png differ diff --git a/docs/images/bthread_concurrency_2.png b/docs/images/bthread_concurrency_2.png new file mode 100644 index 0000000..49dfd9a Binary files /dev/null and b/docs/images/bthread_concurrency_2.png differ diff --git a/docs/images/bthread_concurrency_3.png b/docs/images/bthread_concurrency_3.png new file mode 100644 index 0000000..90b46a7 Binary files /dev/null and b/docs/images/bthread_concurrency_3.png differ diff --git a/docs/images/bthread_creation_qps.png b/docs/images/bthread_creation_qps.png new file mode 100644 index 0000000..302cab3 Binary files /dev/null and b/docs/images/bthread_creation_qps.png differ diff --git a/docs/images/bthread_status_model.svg b/docs/images/bthread_status_model.svg new file mode 100644 index 0000000..46379c9 --- /dev/null +++ b/docs/images/bthread_status_model.svg @@ -0,0 +1,3 @@ + + +
创建
创建
就绪(调度队列)
就绪(调度队列)
运行中
运行中
挂起
挂起
销毁
销毁
sched_to
sched_...
yield/wakeup/timeout/interrupt
yield/wakeup/timeout/interrupt
sched_to
sched_to
start_urgent(sched_to)
start_urgent(sched_to)
butex/sleep
butex/sleep
start_background/start_urgent
start_...
jump_stack
jump_stack
sched_to(ending_sched)
sched_to(ending_sched)
\ No newline at end of file diff --git a/docs/images/bthread_stb_model.svg b/docs/images/bthread_stb_model.svg new file mode 100644 index 0000000..5dcc368 --- /dev/null +++ b/docs/images/bthread_stb_model.svg @@ -0,0 +1,3 @@ + + +
创建
创建
就绪(调度队列)
就绪(调度队列)
将运行(拦截点
将运行(拦截点)
运行
运行
挂起
挂起
挂起中(拦截点
挂起中(拦截点)
销毁
销毁
sched_to
sched_...
yield/wakeup/timeout/interrupt
yield/wakeup/timeout/interrupt
sched_to
sched_to
start_urgent
start_...
butex/sleep
butex/sleep
start_background/start_urgent
start_...
jump_stack
jump_stack
sched_to
sched_...
sched_to(ending_sched)
sched_to(ending_sched)
\ No newline at end of file diff --git a/docs/images/bthread_tagged_increment_all.png b/docs/images/bthread_tagged_increment_all.png new file mode 100644 index 0000000..686b9d2 Binary files /dev/null and b/docs/images/bthread_tagged_increment_all.png differ diff --git a/docs/images/bthread_tagged_increment_tag1.png b/docs/images/bthread_tagged_increment_tag1.png new file mode 100644 index 0000000..291ad80 Binary files /dev/null and b/docs/images/bthread_tagged_increment_tag1.png differ diff --git a/docs/images/bthread_tagged_worker_usage.png b/docs/images/bthread_tagged_worker_usage.png new file mode 100644 index 0000000..748cfcd Binary files /dev/null and b/docs/images/bthread_tagged_worker_usage.png differ diff --git a/docs/images/builtin_service_from_console.png b/docs/images/builtin_service_from_console.png new file mode 100644 index 0000000..2f0647a Binary files /dev/null and b/docs/images/builtin_service_from_console.png differ diff --git a/docs/images/builtin_service_more.png b/docs/images/builtin_service_more.png new file mode 100644 index 0000000..ae3ecdf Binary files /dev/null and b/docs/images/builtin_service_more.png differ diff --git a/docs/images/bvar_dump_flags.png b/docs/images/bvar_dump_flags.png new file mode 100644 index 0000000..7608590 Binary files /dev/null and b/docs/images/bvar_dump_flags.png differ diff --git a/docs/images/bvar_dump_flags_2.png b/docs/images/bvar_dump_flags_2.png new file mode 100644 index 0000000..6837857 Binary files /dev/null and b/docs/images/bvar_dump_flags_2.png differ diff --git a/docs/images/bvar_flow.png b/docs/images/bvar_flow.png new file mode 100644 index 0000000..d1dada0 Binary files /dev/null and b/docs/images/bvar_flow.png differ diff --git a/docs/images/bvar_noah1.png b/docs/images/bvar_noah1.png new file mode 100644 index 0000000..1567e84 Binary files /dev/null and b/docs/images/bvar_noah1.png differ diff --git a/docs/images/bvar_noah2.png b/docs/images/bvar_noah2.png new file mode 100644 index 0000000..cb0317b Binary files /dev/null and b/docs/images/bvar_noah2.png differ diff --git a/docs/images/bvar_noah3.png b/docs/images/bvar_noah3.png new file mode 100644 index 0000000..10d6420 Binary files /dev/null and b/docs/images/bvar_noah3.png differ diff --git a/docs/images/bvar_perf.png b/docs/images/bvar_perf.png new file mode 100644 index 0000000..4d18787 Binary files /dev/null and b/docs/images/bvar_perf.png differ diff --git a/docs/images/chash.png b/docs/images/chash.png new file mode 100644 index 0000000..c1dbcf0 Binary files /dev/null and b/docs/images/chash.png differ diff --git a/docs/images/client_side.png b/docs/images/client_side.png new file mode 100644 index 0000000..2d278f8 Binary files /dev/null and b/docs/images/client_side.png differ diff --git a/docs/images/cmd_jeprof_text.png b/docs/images/cmd_jeprof_text.png new file mode 100644 index 0000000..7669bc8 Binary files /dev/null and b/docs/images/cmd_jeprof_text.png differ diff --git a/docs/images/connection_timedout.png b/docs/images/connection_timedout.png new file mode 100644 index 0000000..5c6803b Binary files /dev/null and b/docs/images/connection_timedout.png differ diff --git a/docs/images/curl_jeprof_flamegraph.png b/docs/images/curl_jeprof_flamegraph.png new file mode 100644 index 0000000..2c6b6b3 Binary files /dev/null and b/docs/images/curl_jeprof_flamegraph.png differ diff --git a/docs/images/curl_jeprof_svg.png b/docs/images/curl_jeprof_svg.png new file mode 100644 index 0000000..ed71499 Binary files /dev/null and b/docs/images/curl_jeprof_svg.png differ diff --git a/docs/images/curl_jeprof_text.png b/docs/images/curl_jeprof_text.png new file mode 100644 index 0000000..b7cf53d Binary files /dev/null and b/docs/images/curl_jeprof_text.png differ diff --git a/docs/images/dummy_server_1.png b/docs/images/dummy_server_1.png new file mode 100644 index 0000000..7bfd418 Binary files /dev/null and b/docs/images/dummy_server_1.png differ diff --git a/docs/images/dummy_server_2.png b/docs/images/dummy_server_2.png new file mode 100644 index 0000000..7e46efa Binary files /dev/null and b/docs/images/dummy_server_2.png differ diff --git a/docs/images/dummy_server_3.png b/docs/images/dummy_server_3.png new file mode 100644 index 0000000..33a9732 Binary files /dev/null and b/docs/images/dummy_server_3.png differ diff --git a/docs/images/echo_cpu_profiling.png b/docs/images/echo_cpu_profiling.png new file mode 100644 index 0000000..7978131 Binary files /dev/null and b/docs/images/echo_cpu_profiling.png differ diff --git a/docs/images/flag_setvalue.png b/docs/images/flag_setvalue.png new file mode 100644 index 0000000..40e5159 Binary files /dev/null and b/docs/images/flag_setvalue.png differ diff --git a/docs/images/foobar_bvar.png b/docs/images/foobar_bvar.png new file mode 100644 index 0000000..e794b8c Binary files /dev/null and b/docs/images/foobar_bvar.png differ diff --git a/docs/images/foobar_latency_cdf.png b/docs/images/foobar_latency_cdf.png new file mode 100644 index 0000000..680e0c7 Binary files /dev/null and b/docs/images/foobar_latency_cdf.png differ diff --git a/docs/images/full_worker_usage.png b/docs/images/full_worker_usage.png new file mode 100644 index 0000000..ce31be9 Binary files /dev/null and b/docs/images/full_worker_usage.png differ diff --git a/docs/images/full_worker_usage_2.png b/docs/images/full_worker_usage_2.png new file mode 100644 index 0000000..5ac680c Binary files /dev/null and b/docs/images/full_worker_usage_2.png differ diff --git a/docs/images/growth_profiler.png b/docs/images/growth_profiler.png new file mode 100644 index 0000000..dcdf5c9 Binary files /dev/null and b/docs/images/growth_profiler.png differ diff --git a/docs/images/health_service.png b/docs/images/health_service.png new file mode 100644 index 0000000..4fdd820 Binary files /dev/null and b/docs/images/health_service.png differ diff --git a/docs/images/heap_profiler_1.png b/docs/images/heap_profiler_1.png new file mode 100644 index 0000000..96f94f6 Binary files /dev/null and b/docs/images/heap_profiler_1.png differ diff --git a/docs/images/heap_profiler_2.png b/docs/images/heap_profiler_2.png new file mode 100644 index 0000000..a015a9e Binary files /dev/null and b/docs/images/heap_profiler_2.png differ diff --git a/docs/images/heap_profiler_3.gif b/docs/images/heap_profiler_3.gif new file mode 100644 index 0000000..7f8f9eb Binary files /dev/null and b/docs/images/heap_profiler_3.gif differ diff --git a/docs/images/high_cpu_usage.png b/docs/images/high_cpu_usage.png new file mode 100644 index 0000000..af1ab47 Binary files /dev/null and b/docs/images/high_cpu_usage.png differ diff --git a/docs/images/je_stats_print.png b/docs/images/je_stats_print.png new file mode 100644 index 0000000..35f8769 Binary files /dev/null and b/docs/images/je_stats_print.png differ diff --git a/docs/images/lalb_1.png b/docs/images/lalb_1.png new file mode 100644 index 0000000..7be5de2 Binary files /dev/null and b/docs/images/lalb_1.png differ diff --git a/docs/images/lalb_2.png b/docs/images/lalb_2.png new file mode 100644 index 0000000..1320f16 Binary files /dev/null and b/docs/images/lalb_2.png differ diff --git a/docs/images/lalb_3.png b/docs/images/lalb_3.png new file mode 100644 index 0000000..fb9e407 Binary files /dev/null and b/docs/images/lalb_3.png differ diff --git a/docs/images/lalb_4.png b/docs/images/lalb_4.png new file mode 100644 index 0000000..bf00b75 Binary files /dev/null and b/docs/images/lalb_4.png differ diff --git a/docs/images/lalb_5.png b/docs/images/lalb_5.png new file mode 100644 index 0000000..decb6bd Binary files /dev/null and b/docs/images/lalb_5.png differ diff --git a/docs/images/latency_cdf.png b/docs/images/latency_cdf.png new file mode 100644 index 0000000..b0bfddb Binary files /dev/null and b/docs/images/latency_cdf.png differ diff --git a/docs/images/lb.png b/docs/images/lb.png new file mode 100644 index 0000000..0e6dc25 Binary files /dev/null and b/docs/images/lb.png differ diff --git a/docs/images/logo-white.png b/docs/images/logo-white.png new file mode 100644 index 0000000..149c9e5 Binary files /dev/null and b/docs/images/logo-white.png differ diff --git a/docs/images/logo.png b/docs/images/logo.png new file mode 100644 index 0000000..c28dfa3 Binary files /dev/null and b/docs/images/logo.png differ diff --git a/docs/images/multi_client_latency_cdf.png b/docs/images/multi_client_latency_cdf.png new file mode 100644 index 0000000..0133e67 Binary files /dev/null and b/docs/images/multi_client_latency_cdf.png differ diff --git a/docs/images/multi_server_latency_cdf.png b/docs/images/multi_server_latency_cdf.png new file mode 100644 index 0000000..6f991a3 Binary files /dev/null and b/docs/images/multi_server_latency_cdf.png differ diff --git a/docs/images/normal_cpu_usage.png b/docs/images/normal_cpu_usage.png new file mode 100644 index 0000000..8509fa3 Binary files /dev/null and b/docs/images/normal_cpu_usage.png differ diff --git a/docs/images/normal_worker_usage.png b/docs/images/normal_worker_usage.png new file mode 100644 index 0000000..6a65c77 Binary files /dev/null and b/docs/images/normal_worker_usage.png differ diff --git a/docs/images/ns.png b/docs/images/ns.png new file mode 100644 index 0000000..792db5e Binary files /dev/null and b/docs/images/ns.png differ diff --git a/docs/images/ns_access_interval.png b/docs/images/ns_access_interval.png new file mode 100644 index 0000000..278ed9e Binary files /dev/null and b/docs/images/ns_access_interval.png differ diff --git a/docs/images/ns_filter.jpg b/docs/images/ns_filter.jpg new file mode 100644 index 0000000..790a6a8 Binary files /dev/null and b/docs/images/ns_filter.jpg differ diff --git a/docs/images/pchan.png b/docs/images/pchan.png new file mode 100644 index 0000000..6606b4e Binary files /dev/null and b/docs/images/pchan.png differ diff --git a/docs/images/pooled_conn.png b/docs/images/pooled_conn.png new file mode 100644 index 0000000..d6e74f8 Binary files /dev/null and b/docs/images/pooled_conn.png differ diff --git a/docs/images/protobufs_service.png b/docs/images/protobufs_service.png new file mode 100644 index 0000000..0de7d07 Binary files /dev/null and b/docs/images/protobufs_service.png differ diff --git a/docs/images/qps_vs_multi_client.png b/docs/images/qps_vs_multi_client.png new file mode 100644 index 0000000..bcb62e2 Binary files /dev/null and b/docs/images/qps_vs_multi_client.png differ diff --git a/docs/images/qps_vs_reqsize.png b/docs/images/qps_vs_reqsize.png new file mode 100644 index 0000000..b091fc8 Binary files /dev/null and b/docs/images/qps_vs_reqsize.png differ diff --git a/docs/images/qps_vs_threadnum.png b/docs/images/qps_vs_threadnum.png new file mode 100644 index 0000000..a60f393 Binary files /dev/null and b/docs/images/qps_vs_threadnum.png differ diff --git a/docs/images/raft_contention_1.png b/docs/images/raft_contention_1.png new file mode 100644 index 0000000..a22ffa3 Binary files /dev/null and b/docs/images/raft_contention_1.png differ diff --git a/docs/images/raft_contention_2.png b/docs/images/raft_contention_2.png new file mode 100644 index 0000000..b7b6818 Binary files /dev/null and b/docs/images/raft_contention_2.png differ diff --git a/docs/images/raft_contention_3.png b/docs/images/raft_contention_3.png new file mode 100644 index 0000000..b1b947f Binary files /dev/null and b/docs/images/raft_contention_3.png differ diff --git a/docs/images/register_lb.png b/docs/images/register_lb.png new file mode 100644 index 0000000..45327d0 Binary files /dev/null and b/docs/images/register_lb.png differ diff --git a/docs/images/register_ns.png b/docs/images/register_ns.png new file mode 100644 index 0000000..6152c4a Binary files /dev/null and b/docs/images/register_ns.png differ diff --git a/docs/images/reloadable_flags.png b/docs/images/reloadable_flags.png new file mode 100644 index 0000000..3ea5af0 Binary files /dev/null and b/docs/images/reloadable_flags.png differ diff --git a/docs/images/resource_pool.png b/docs/images/resource_pool.png new file mode 100644 index 0000000..6c62e06 Binary files /dev/null and b/docs/images/resource_pool.png differ diff --git a/docs/images/restful_1.png b/docs/images/restful_1.png new file mode 100644 index 0000000..6d2accb Binary files /dev/null and b/docs/images/restful_1.png differ diff --git a/docs/images/restful_2.png b/docs/images/restful_2.png new file mode 100644 index 0000000..4aed686 Binary files /dev/null and b/docs/images/restful_2.png differ diff --git a/docs/images/restful_3.png b/docs/images/restful_3.png new file mode 100644 index 0000000..420c109 Binary files /dev/null and b/docs/images/restful_3.png differ diff --git a/docs/images/rpc.png b/docs/images/rpc.png new file mode 100644 index 0000000..fd7fd0b Binary files /dev/null and b/docs/images/rpc.png differ diff --git a/docs/images/rpc_flow.png b/docs/images/rpc_flow.png new file mode 100644 index 0000000..2653268 Binary files /dev/null and b/docs/images/rpc_flow.png differ diff --git a/docs/images/rpc_press_1.png b/docs/images/rpc_press_1.png new file mode 100644 index 0000000..e70ff49 Binary files /dev/null and b/docs/images/rpc_press_1.png differ diff --git a/docs/images/rpc_press_2.png b/docs/images/rpc_press_2.png new file mode 100644 index 0000000..460690c Binary files /dev/null and b/docs/images/rpc_press_2.png differ diff --git a/docs/images/rpc_replay_1.png b/docs/images/rpc_replay_1.png new file mode 100644 index 0000000..6fc2589 Binary files /dev/null and b/docs/images/rpc_replay_1.png differ diff --git a/docs/images/rpc_replay_2.png b/docs/images/rpc_replay_2.png new file mode 100644 index 0000000..986afda Binary files /dev/null and b/docs/images/rpc_replay_2.png differ diff --git a/docs/images/rpc_replay_3.png b/docs/images/rpc_replay_3.png new file mode 100644 index 0000000..f84ff15 Binary files /dev/null and b/docs/images/rpc_replay_3.png differ diff --git a/docs/images/rpc_replay_4.png b/docs/images/rpc_replay_4.png new file mode 100644 index 0000000..b9541af Binary files /dev/null and b/docs/images/rpc_replay_4.png differ diff --git a/docs/images/rpc_replay_5.png b/docs/images/rpc_replay_5.png new file mode 100644 index 0000000..8971e84 Binary files /dev/null and b/docs/images/rpc_replay_5.png differ diff --git a/docs/images/rpc_view_1.png b/docs/images/rpc_view_1.png new file mode 100644 index 0000000..6f02d48 Binary files /dev/null and b/docs/images/rpc_view_1.png differ diff --git a/docs/images/rpc_view_2.png b/docs/images/rpc_view_2.png new file mode 100644 index 0000000..7e8432b Binary files /dev/null and b/docs/images/rpc_view_2.png differ diff --git a/docs/images/rpc_view_3.png b/docs/images/rpc_view_3.png new file mode 100644 index 0000000..cf2738a Binary files /dev/null and b/docs/images/rpc_view_3.png differ diff --git a/docs/images/rpcz.png b/docs/images/rpcz.png new file mode 100644 index 0000000..5067b67 Binary files /dev/null and b/docs/images/rpcz.png differ diff --git a/docs/images/rpcz_2.png b/docs/images/rpcz_2.png new file mode 100644 index 0000000..c645b6d Binary files /dev/null and b/docs/images/rpcz_2.png differ diff --git a/docs/images/rpcz_3.png b/docs/images/rpcz_3.png new file mode 100644 index 0000000..57a8007 Binary files /dev/null and b/docs/images/rpcz_3.png differ diff --git a/docs/images/rpcz_4.png b/docs/images/rpcz_4.png new file mode 100644 index 0000000..7d0034c Binary files /dev/null and b/docs/images/rpcz_4.png differ diff --git a/docs/images/rpcz_5.png b/docs/images/rpcz_5.png new file mode 100644 index 0000000..81432af Binary files /dev/null and b/docs/images/rpcz_5.png differ diff --git a/docs/images/rpcz_6.png b/docs/images/rpcz_6.png new file mode 100644 index 0000000..59796fe Binary files /dev/null and b/docs/images/rpcz_6.png differ diff --git a/docs/images/rpcz_7.png b/docs/images/rpcz_7.png new file mode 100644 index 0000000..53b9874 Binary files /dev/null and b/docs/images/rpcz_7.png differ diff --git a/docs/images/server_side.png b/docs/images/server_side.png new file mode 100644 index 0000000..562be61 Binary files /dev/null and b/docs/images/server_side.png differ diff --git a/docs/images/set_flag_invalid_value.png b/docs/images/set_flag_invalid_value.png new file mode 100644 index 0000000..3d83a47 Binary files /dev/null and b/docs/images/set_flag_invalid_value.png differ diff --git a/docs/images/set_flag_reject.png b/docs/images/set_flag_reject.png new file mode 100644 index 0000000..6f34286 Binary files /dev/null and b/docs/images/set_flag_reject.png differ diff --git a/docs/images/set_flag_with_form.png b/docs/images/set_flag_with_form.png new file mode 100644 index 0000000..af2d466 Binary files /dev/null and b/docs/images/set_flag_with_form.png differ diff --git a/docs/images/set_flag_with_form_2.png b/docs/images/set_flag_with_form_2.png new file mode 100644 index 0000000..275f21b Binary files /dev/null and b/docs/images/set_flag_with_form_2.png differ diff --git a/docs/images/set_flag_with_form_3.png b/docs/images/set_flag_with_form_3.png new file mode 100644 index 0000000..1a192a3 Binary files /dev/null and b/docs/images/set_flag_with_form_3.png differ diff --git a/docs/images/short_conn.png b/docs/images/short_conn.png new file mode 100644 index 0000000..dc57365 Binary files /dev/null and b/docs/images/short_conn.png differ diff --git a/docs/images/single_conn.png b/docs/images/single_conn.png new file mode 100644 index 0000000..2528af1 Binary files /dev/null and b/docs/images/single_conn.png differ diff --git a/docs/images/status.png b/docs/images/status.png new file mode 100644 index 0000000..fdcba48 Binary files /dev/null and b/docs/images/status.png differ diff --git a/docs/images/status_2.png b/docs/images/status_2.png new file mode 100644 index 0000000..c8293d5 Binary files /dev/null and b/docs/images/status_2.png differ diff --git a/docs/images/tcmalloc_stuck.png b/docs/images/tcmalloc_stuck.png new file mode 100644 index 0000000..08a016d Binary files /dev/null and b/docs/images/tcmalloc_stuck.png differ diff --git a/docs/images/the_r_after_flag.png b/docs/images/the_r_after_flag.png new file mode 100644 index 0000000..f002b39 Binary files /dev/null and b/docs/images/the_r_after_flag.png differ diff --git a/docs/images/threading_overview_1.png b/docs/images/threading_overview_1.png new file mode 100644 index 0000000..d106163 Binary files /dev/null and b/docs/images/threading_overview_1.png differ diff --git a/docs/images/threading_overview_2.png b/docs/images/threading_overview_2.png new file mode 100644 index 0000000..5f3a827 Binary files /dev/null and b/docs/images/threading_overview_2.png differ diff --git a/docs/images/trace_printf.png b/docs/images/trace_printf.png new file mode 100644 index 0000000..601cde5 Binary files /dev/null and b/docs/images/trace_printf.png differ diff --git a/docs/images/twolevel_server_latency_cdf.png b/docs/images/twolevel_server_latency_cdf.png new file mode 100644 index 0000000..324205b Binary files /dev/null and b/docs/images/twolevel_server_latency_cdf.png differ diff --git a/docs/images/ubrpc_compare_1.png b/docs/images/ubrpc_compare_1.png new file mode 100644 index 0000000..bebc05c Binary files /dev/null and b/docs/images/ubrpc_compare_1.png differ diff --git a/docs/images/ubrpc_compare_2.png b/docs/images/ubrpc_compare_2.png new file mode 100644 index 0000000..2f53e4c Binary files /dev/null and b/docs/images/ubrpc_compare_2.png differ diff --git a/docs/images/vars_1.gif b/docs/images/vars_1.gif new file mode 100644 index 0000000..b83a689 Binary files /dev/null and b/docs/images/vars_1.gif differ diff --git a/docs/images/vars_2.gif b/docs/images/vars_2.gif new file mode 100644 index 0000000..715d4c7 Binary files /dev/null and b/docs/images/vars_2.gif differ diff --git a/docs/images/vars_3.gif b/docs/images/vars_3.gif new file mode 100644 index 0000000..6f166e0 Binary files /dev/null and b/docs/images/vars_3.gif differ diff --git a/docs/images/vars_4.png b/docs/images/vars_4.png new file mode 100644 index 0000000..04411bf Binary files /dev/null and b/docs/images/vars_4.png differ diff --git a/docs/images/vars_5.png b/docs/images/vars_5.png new file mode 100644 index 0000000..22e64ee Binary files /dev/null and b/docs/images/vars_5.png differ diff --git a/docs/images/vars_6.png b/docs/images/vars_6.png new file mode 100644 index 0000000..391482b Binary files /dev/null and b/docs/images/vars_6.png differ diff --git a/docs/images/vars_7.png b/docs/images/vars_7.png new file mode 100644 index 0000000..474b4c3 Binary files /dev/null and b/docs/images/vars_7.png differ diff --git a/docs/images/version_service.png b/docs/images/version_service.png new file mode 100644 index 0000000..c514fc3 Binary files /dev/null and b/docs/images/version_service.png differ diff --git a/docs/images/vlog_service.png b/docs/images/vlog_service.png new file mode 100644 index 0000000..1d3af56 Binary files /dev/null and b/docs/images/vlog_service.png differ diff --git a/docs/images/wireshark_baidu_std_request.png b/docs/images/wireshark_baidu_std_request.png new file mode 100644 index 0000000..0c6bc9c Binary files /dev/null and b/docs/images/wireshark_baidu_std_request.png differ diff --git a/docs/images/wireshark_baidu_std_response.png b/docs/images/wireshark_baidu_std_response.png new file mode 100644 index 0000000..b7180af Binary files /dev/null and b/docs/images/wireshark_baidu_std_response.png differ diff --git a/docs/images/wireshark_folders.png b/docs/images/wireshark_folders.png new file mode 100644 index 0000000..4b76a7a Binary files /dev/null and b/docs/images/wireshark_folders.png differ diff --git a/docs/images/wireshark_protobuf_search_paths.png b/docs/images/wireshark_protobuf_search_paths.png new file mode 100644 index 0000000..61cd1a5 Binary files /dev/null and b/docs/images/wireshark_protobuf_search_paths.png differ diff --git a/docs/images/wireshark_protobuf_settings.png b/docs/images/wireshark_protobuf_settings.png new file mode 100644 index 0000000..8d8aa1c Binary files /dev/null and b/docs/images/wireshark_protobuf_settings.png differ diff --git a/docs/images/write.png b/docs/images/write.png new file mode 100644 index 0000000..0dd15c8 Binary files /dev/null and b/docs/images/write.png differ diff --git a/example/BUILD.bazel b/example/BUILD.bazel new file mode 100644 index 0000000..4ee7cb1 --- /dev/null +++ b/example/BUILD.bazel @@ -0,0 +1,122 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +load("@rules_cc//cc:defs.bzl", "cc_binary") +load("//bazel/tools:brpc_proto_library.bzl", "brpc_proto_library") + +COPTS = [ + "-D__STDC_FORMAT_MACROS", + "-DBTHREAD_USE_FAST_PTHREAD_MUTEX", + "-D__const__=__unused__", + "-D_GNU_SOURCE", + "-DUSE_SYMBOLIZE", + "-DNO_TCMALLOC", + "-D__STDC_LIMIT_MACROS", + "-D__STDC_CONSTANT_MACROS", + "-fPIC", + "-Wno-unused-parameter", + "-fno-omit-frame-pointer", +] + select({ + "//bazel/config:brpc_with_glog": ["-DBRPC_WITH_GLOG=1"], + "//conditions:default": ["-DBRPC_WITH_GLOG=0"], +}) + select({ + "//bazel/config:brpc_with_rdma": ["-DBRPC_WITH_RDMA=1"], + "//conditions:default": [""], +}) + +brpc_proto_library( + name = "cc_echo_c++_proto", + srcs = ["echo_c++/echo.proto"], + include = "echo_c++", + proto_deps = [], +) + +brpc_proto_library( + name = "cc_rdma_performance_proto", + srcs = ["rdma_performance/test.proto"], + include = "rdma_performance", + proto_deps = [], +) + +cc_binary( + name = "echo_c++_server", + srcs = [ + "echo_c++/server.cpp", + ], + copts = COPTS, + includes = [ + "echo_c++", + ], + deps = [ + ":cc_echo_c++_proto", + "//:brpc", + ], +) + +cc_binary( + name = "echo_c++_client", + srcs = [ + "echo_c++/client.cpp", + ], + copts = COPTS, + includes = [ + "echo_c++", + ], + deps = [ + ":cc_echo_c++_proto", + "//:brpc", + ], +) + +cc_binary( + name = "rdma_performance_server", + srcs = [ + "rdma_performance/server.cpp", + ], + includes = [ + "rdma_performance", + ], + copts = COPTS + ["-DBRPC_WITH_RDMA=1"], + deps = [ + ":cc_rdma_performance_proto", + "//:brpc", + ], +) + +cc_binary( + name = "rdma_performance_client", + srcs = [ + "rdma_performance/client.cpp", + ], + includes = [ + "rdma_performance", + ], + copts = COPTS + ["-DBRPC_WITH_RDMA=1"], + deps = [ + ":cc_rdma_performance_proto", + "//:brpc", + ], +) + +cc_binary( + name = "redis_c++_server", + srcs = [ + "redis_c++/redis_server.cpp", + ], + copts = COPTS, + deps = [ + "//:brpc", + ], +) \ No newline at end of file diff --git a/example/asynchronous_echo_c++/CMakeLists.txt b/example/asynchronous_echo_c++/CMakeLists.txt new file mode 100644 index 0000000..e2d8f1e --- /dev/null +++ b/example/asynchronous_echo_c++/CMakeLists.txt @@ -0,0 +1,110 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(asynchronous_echo_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER echo.proto) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(asynchronous_echo_client client.cpp ${PROTO_SRC}) +brpc_example_configure_target(asynchronous_echo_client) +add_executable(asynchronous_echo_server server.cpp ${PROTO_SRC}) +brpc_example_configure_target(asynchronous_echo_server) + +target_link_libraries(asynchronous_echo_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) +target_link_libraries(asynchronous_echo_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) diff --git a/example/asynchronous_echo_c++/client.cpp b/example/asynchronous_echo_c++/client.cpp new file mode 100644 index 0000000..b52297b --- /dev/null +++ b/example/asynchronous_echo_c++/client.cpp @@ -0,0 +1,110 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending requests to server asynchronously every 1 second. + +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_bool(send_attachment, true, "Carry attachment along with requests"); +DEFINE_string(protocol, "baidu_std", "Protocol type. Defined in src/brpc/options.proto"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(server, "0.0.0.0:8003", "IP Address of server"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); + +void HandleEchoResponse( + brpc::Controller* cntl, + example::EchoResponse* response) { + // std::unique_ptr makes sure cntl/response will be deleted before returning. + std::unique_ptr cntl_guard(cntl); + std::unique_ptr response_guard(response); + + if (cntl->Failed()) { + LOG(WARNING) << "Fail to send EchoRequest, " << cntl->ErrorText(); + return; + } + LOG(INFO) << "Received response from " << cntl->remote_side() + << ": " << response->message() << " (attached=" + << cntl->response_attachment() << ")" + << " latency=" << cntl->latency_us() << "us"; +} + + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = FLAGS_protocol; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + if (channel.Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + // Normally, you should not call a Channel directly, but instead construct + // a stub Service wrapping it. stub can be shared by all threads as well. + example::EchoService_Stub stub(&channel); + + // Send a request and wait for the response every 1 second. + int log_id = 0; + while (!brpc::IsAskedToQuit()) { + // Since we are sending asynchronous RPC (`done' is not NULL), + // these objects MUST remain valid until `done' is called. + // As a result, we allocate these objects on heap + example::EchoResponse* response = new example::EchoResponse(); + brpc::Controller* cntl = new brpc::Controller(); + + // Notice that you don't have to new request, which can be modified + // or destroyed just after stub.Echo is called. + example::EchoRequest request; + request.set_message("hello world"); + + cntl->set_log_id(log_id ++); // set by user + if (FLAGS_send_attachment) { + // Set attachment which is wired to network directly instead of + // being serialized into protobuf messages. + cntl->request_attachment().append("foo"); + } + + // We use protobuf utility `NewCallback' to create a closure object + // that will call our callback `HandleEchoResponse'. This closure + // will automatically delete itself after being called once + google::protobuf::Closure* done = brpc::NewCallback( + &HandleEchoResponse, cntl, response); + stub.Echo(cntl, &request, response, done); + + // This is an asynchronous RPC, so we can only fetch the result + // inside the callback + sleep(1); + } + + LOG(INFO) << "EchoClient is going to quit"; + return 0; +} diff --git a/example/asynchronous_echo_c++/echo.proto b/example/asynchronous_echo_c++/echo.proto new file mode 100644 index 0000000..2b39627 --- /dev/null +++ b/example/asynchronous_echo_c++/echo.proto @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package example; + +option cc_generic_services = true; + +message EchoRequest { + required string message = 1; +}; + +message EchoResponse { + required string message = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; diff --git a/example/asynchronous_echo_c++/server.cpp b/example/asynchronous_echo_c++/server.cpp new file mode 100644 index 0000000..dcce19a --- /dev/null +++ b/example/asynchronous_echo_c++/server.cpp @@ -0,0 +1,118 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive EchoRequest and send back EchoResponse. + +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_bool(send_attachment, true, "Carry attachment along with response"); +DEFINE_int32(port, 8003, "TCP Port of this server"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); + +// Your implementation of example::EchoService +class EchoServiceImpl : public example::EchoService { +public: + EchoServiceImpl() {} + virtual ~EchoServiceImpl() {} + virtual void Echo(google::protobuf::RpcController* cntl_base, + const example::EchoRequest* request, + example::EchoResponse* response, + google::protobuf::Closure* done) { + // This object helps you to call done->Run() in RAII style. If you need + // to process the request asynchronously, pass done_guard.release(). + brpc::ClosureGuard done_guard(done); + + brpc::Controller* cntl = + static_cast(cntl_base); + + // optional: set a callback function which is called after response is sent + // and before cntl/req/res is destructed. + cntl->set_after_rpc_resp_fn(std::bind(&EchoServiceImpl::CallAfterRpc, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + + // The purpose of following logs is to help you to understand + // how clients interact with servers more intuitively. You should + // remove these logs in performance-sensitive servers. + LOG(INFO) << "Received request[log_id=" << cntl->log_id() + << "] from " << cntl->remote_side() + << ": " << request->message() + << " (attached=" << cntl->request_attachment() << ")"; + + // Fill response. + response->set_message(request->message()); + + // You can compress the response by setting Controller, but be aware + // that compression may be costly, evaluate before turning on. + // cntl->set_response_compress_type(brpc::COMPRESS_TYPE_GZIP); + + if (FLAGS_send_attachment) { + // Set attachment which is wired to network directly instead of + // being serialized into protobuf messages. + cntl->response_attachment().append("bar"); + } + } + + // optional + static void CallAfterRpc(brpc::Controller* cntl, + const google::protobuf::Message* req, + const google::protobuf::Message* res) { + // at this time res is already sent to client, but cntl/req/res is not destructed + std::string req_str; + std::string res_str; + json2pb::ProtoMessageToJson(*req, &req_str, NULL); + json2pb::ProtoMessageToJson(*res, &res_str, NULL); + LOG(INFO) << "req:" << req_str + << " res:" << res_str; + } +}; + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // Generally you only need one Server. + brpc::Server server; + + // Instance of your service. + EchoServiceImpl echo_service_impl; + + // Add the service into server. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + if (server.AddService(&echo_service_impl, + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + + // Start the server. + brpc::ServerOptions options; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + if (server.Start(FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + // Wait until Ctrl-C is pressed, then Stop() and Join() the server. + server.RunUntilAskedToQuit(); + return 0; +} diff --git a/example/auto_concurrency_limiter/CMakeLists.txt b/example/auto_concurrency_limiter/CMakeLists.txt new file mode 100644 index 0000000..4563612 --- /dev/null +++ b/example/auto_concurrency_limiter/CMakeLists.txt @@ -0,0 +1,104 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(asynchronous_echo_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${CMAKE_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER cl_test.proto) + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_RegisterThriftProtocol" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(asynchronous_echo_client client.cpp ${PROTO_SRC}) +brpc_example_configure_target(asynchronous_echo_client) +add_executable(asynchronous_echo_server server.cpp ${PROTO_SRC}) +brpc_example_configure_target(asynchronous_echo_server) + +target_link_libraries(asynchronous_echo_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) +target_link_libraries(asynchronous_echo_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) diff --git a/example/auto_concurrency_limiter/cl_test.proto b/example/auto_concurrency_limiter/cl_test.proto new file mode 100644 index 0000000..ec6277a --- /dev/null +++ b/example/auto_concurrency_limiter/cl_test.proto @@ -0,0 +1,61 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package test; + +option cc_generic_services = true; + +message NotifyRequest { + required string message = 1; +}; + +message NotifyResponse { + required string message = 1; +}; + +enum ChangeType { + FLUCTUATE = 1; // Fluctuating between upper and lower bound + SMOOTH = 2; // Smoothly rising from the lower bound to the upper bound +} + +message Stage { + required int32 lower_bound = 1; + required int32 upper_bound = 2; + required int32 duration_sec = 3; + required ChangeType type = 4; +} + +message TestCase { + required string case_name = 1; + required string max_concurrency = 2; + repeated Stage qps_stage_list = 3; + repeated Stage latency_stage_list = 4; +} + +message TestCaseSet { + repeated TestCase test_case = 1; +} + +service ControlService { + rpc Notify(NotifyRequest) returns (NotifyResponse); +} + +service EchoService { + rpc Echo(NotifyRequest) returns (NotifyResponse); +}; + diff --git a/example/auto_concurrency_limiter/client.cpp b/example/auto_concurrency_limiter/client.cpp new file mode 100644 index 0000000..af293af --- /dev/null +++ b/example/auto_concurrency_limiter/client.cpp @@ -0,0 +1,247 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending requests to server asynchronously every 1 second. + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "cl_test.pb.h" + +DEFINE_string(protocol, "baidu_std", "Protocol type. Defined in src/brpc/options.proto"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(cntl_server, "0.0.0.0:9000", "IP Address of server"); +DEFINE_string(echo_server, "0.0.0.0:9001", "IP Address of server"); +DEFINE_int32(timeout_ms, 3000, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 0, "Max retries(not including the first RPC)"); +DEFINE_int32(case_interval, 20, "Intervals for different test cases"); +DEFINE_int32(client_qps_change_interval_us, 50000, + "The interval for client changes the sending speed"); +DEFINE_string(case_file, "", "File path for test_cases"); + +void DisplayStage(const test::Stage& stage) { + std::string type; + switch(stage.type()) { + case test::FLUCTUATE: + type = "Fluctuate"; + break; + case test::SMOOTH: + type = "Smooth"; + break; + default: + type = "Unknown"; + } + std::stringstream ss; + ss + << "Stage:[" << stage.lower_bound() << ':' + << stage.upper_bound() << "]" + << " , Type:" << type; + LOG(INFO) << ss.str(); +} + +uint32_t cast_func(void* arg) { + return *(uint32_t*)arg; +} + +butil::atomic g_timeout(0); +butil::atomic g_error(0); +butil::atomic g_succ(0); +bvar::PassiveStatus g_timeout_bvar(cast_func, &g_timeout); +bvar::PassiveStatus g_error_bvar(cast_func, &g_error); +bvar::PassiveStatus g_succ_bvar(cast_func, &g_succ); +bvar::LatencyRecorder g_latency_rec; + +void LoadCaseSet(test::TestCaseSet* case_set, const std::string& file_path) { + std::ifstream ifs(file_path.c_str(), std::ios::in); + if (!ifs) { + LOG(FATAL) << "Fail to open case set file: " << file_path; + } + std::string case_set_json((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + std::string err; + if (!json2pb::JsonToProtoMessage(case_set_json, case_set, &err)) { + LOG(FATAL) + << "Fail to trans case_set from json to protobuf message: " + << err; + } +} + +void HandleEchoResponse( + brpc::Controller* cntl, + test::NotifyResponse* response) { + // std::unique_ptr makes sure cntl/response will be deleted before returning. + std::unique_ptr cntl_guard(cntl); + std::unique_ptr response_guard(response); + + if (cntl->Failed() && cntl->ErrorCode() == brpc::ERPCTIMEDOUT) { + g_timeout.fetch_add(1, butil::memory_order_relaxed); + LOG_EVERY_N(INFO, 1000) << cntl->ErrorText(); + } else if (cntl->Failed()) { + g_error.fetch_add(1, butil::memory_order_relaxed); + LOG_EVERY_N(INFO, 1000) << cntl->ErrorText(); + } else { + g_succ.fetch_add(1, butil::memory_order_relaxed); + g_latency_rec << cntl->latency_us(); + } + +} + +void Expose() { + g_timeout_bvar.expose_as("cl", "timeout"); + g_error_bvar.expose_as("cl", "failed"); + g_succ_bvar.expose_as("cl", "succ"); + g_latency_rec.expose("cl"); +} + +struct TestCaseContext { + TestCaseContext(const test::TestCase& tc) + : running(true) + , stage_index(0) + , test_case(tc) + , next_stage_sec(test_case.qps_stage_list(0).duration_sec() + + butil::cpuwide_time_s()) { + DisplayStage(test_case.qps_stage_list(stage_index)); + Update(); + } + + bool Update() { + if (butil::cpuwide_time_s() >= next_stage_sec) { + ++stage_index; + if (stage_index < test_case.qps_stage_list_size()) { + next_stage_sec += test_case.qps_stage_list(stage_index).duration_sec(); + DisplayStage(test_case.qps_stage_list(stage_index)); + } else { + return false; + } + } + + int qps = 0; + const test::Stage& qps_stage = test_case.qps_stage_list(stage_index); + const int lower_bound = qps_stage.lower_bound(); + const int upper_bound = qps_stage.upper_bound(); + if (qps_stage.type() == test::FLUCTUATE) { + qps = butil::fast_rand_less_than(upper_bound - lower_bound) + lower_bound; + } else if (qps_stage.type() == test::SMOOTH) { + qps = lower_bound + (upper_bound - lower_bound) / + double(qps_stage.duration_sec()) * (qps_stage.duration_sec() - next_stage_sec + + butil::cpuwide_time_s()); + } + interval_us.store(1.0 / qps * 1000000, butil::memory_order_relaxed); + return true; + } + + butil::atomic running; + butil::atomic interval_us; + int stage_index; + const test::TestCase test_case; + int next_stage_sec; +}; + +void RunUpdateTask(void* data) { + TestCaseContext* context = (TestCaseContext*)data; + bool should_continue = context->Update(); + if (should_continue) { + bthread::get_global_timer_thread()->schedule(RunUpdateTask, data, + butil::microseconds_from_now(FLAGS_client_qps_change_interval_us)); + } else { + context->running.store(false, butil::memory_order_release); + } +} + +void RunCase(test::ControlService_Stub &cntl_stub, + const test::TestCase& test_case) { + LOG(INFO) << "Running case:`" << test_case.case_name() << '\''; + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = FLAGS_protocol; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms; + options.max_retry = FLAGS_max_retry; + if (channel.Init(FLAGS_echo_server.c_str(), &options) != 0) { + LOG(FATAL) << "Fail to initialize channel"; + } + test::EchoService_Stub echo_stub(&channel); + + test::NotifyRequest cntl_req; + test::NotifyResponse cntl_rsp; + brpc::Controller cntl; + cntl_req.set_message("StartCase"); + cntl_stub.Notify(&cntl, &cntl_req, &cntl_rsp, NULL); + CHECK(!cntl.Failed()) << "control failed"; + + TestCaseContext context(test_case); + bthread::get_global_timer_thread()->schedule(RunUpdateTask, &context, + butil::microseconds_from_now(FLAGS_client_qps_change_interval_us)); + + while (context.running.load(butil::memory_order_acquire)) { + test::NotifyRequest echo_req; + echo_req.set_message("hello"); + brpc::Controller* echo_cntl = new brpc::Controller; + test::NotifyResponse* echo_rsp = new test::NotifyResponse; + google::protobuf::Closure* done = brpc::NewCallback( + &HandleEchoResponse, echo_cntl, echo_rsp); + echo_stub.Echo(echo_cntl, &echo_req, echo_rsp, done); + ::usleep(context.interval_us.load(butil::memory_order_relaxed)); + } + + LOG(INFO) << "Waiting to stop case: `" << test_case.case_name() << '\''; + ::sleep(FLAGS_case_interval); + cntl.Reset(); + cntl_req.set_message("StopCase"); + cntl_stub.Notify(&cntl, &cntl_req, &cntl_rsp, NULL); + CHECK(!cntl.Failed()) << "control failed"; + LOG(INFO) << "Case `" << test_case.case_name() << "' finshed:"; +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + Expose(); + + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = FLAGS_protocol; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms; + + if (channel.Init(FLAGS_cntl_server.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + test::ControlService_Stub cntl_stub(&channel); + + test::TestCaseSet case_set; + LoadCaseSet(&case_set, FLAGS_case_file); + + brpc::Controller cntl; + test::NotifyRequest cntl_req; + test::NotifyResponse cntl_rsp; + cntl_req.set_message("ResetCaseSet"); + cntl_stub.Notify(&cntl, &cntl_req, &cntl_rsp, NULL); + CHECK(!cntl.Failed()) << "Cntl Failed"; + for (int i = 0; i < case_set.test_case_size(); ++i) { + RunCase(cntl_stub, case_set.test_case(i)); + } + LOG(INFO) << "EchoClient is going to quit"; + return 0; +} diff --git a/example/auto_concurrency_limiter/dummy_server.port b/example/auto_concurrency_limiter/dummy_server.port new file mode 100644 index 0000000..3c2df07 --- /dev/null +++ b/example/auto_concurrency_limiter/dummy_server.port @@ -0,0 +1 @@ +9999 diff --git a/example/auto_concurrency_limiter/server.cpp b/example/auto_concurrency_limiter/server.cpp new file mode 100644 index 0000000..a161b18 --- /dev/null +++ b/example/auto_concurrency_limiter/server.cpp @@ -0,0 +1,295 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive EchoRequest and send back EchoResponse. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "cl_test.pb.h" + +DEFINE_int32(server_bthread_concurrency, 4, + "Configuring the value of bthread_concurrency, For compute max qps, "); +DEFINE_int32(server_sync_sleep_us, 2500, + "Usleep time, each request will be executed once, For compute max qps"); +// max qps = 1000 / 2.5 * 4 + +DEFINE_int32(control_server_port, 9000, ""); +DEFINE_int32(echo_port, 9001, "TCP Port of echo server"); +DEFINE_int32(cntl_port, 9000, "TCP Port of controller server"); +DEFINE_string(case_file, "", "File path for test_cases"); +DEFINE_int32(latency_change_interval_us, 50000, "Intervalt for server side changes the latency"); +DEFINE_int32(server_max_concurrency, 0, "Echo Server's max_concurrency"); +DEFINE_bool(use_usleep, false, + "EchoServer uses ::usleep or bthread_usleep to simulate latency " + "when processing requests"); + + +bthread::TimerThread g_timer_thread; + +int cast_func(void* arg) { + return *(int*)arg; +} + +void DisplayStage(const test::Stage& stage) { + std::string type; + switch(stage.type()) { + case test::FLUCTUATE: + type = "Fluctuate"; + break; + case test::SMOOTH: + type = "Smooth"; + break; + default: + type = "Unknown"; + } + std::stringstream ss; + ss + << "Stage:[" << stage.lower_bound() << ':' + << stage.upper_bound() << "]" + << " , Type:" << type; + LOG(INFO) << ss.str(); +} + +butil::atomic cnt(0); +butil::atomic atomic_sleep_time(0); +bvar::PassiveStatus atomic_sleep_time_bvar(cast_func, &atomic_sleep_time); + +namespace bthread { +DECLARE_int32(bthread_concurrency); +} + +void TimerTask(void* data); + +class EchoServiceImpl : public test::EchoService { +public: + EchoServiceImpl() + : _stage_index(0) + , _running_case(false) { + }; + + virtual ~EchoServiceImpl() {} + + void SetTestCase(const test::TestCase& test_case) { + _test_case = test_case; + _next_stage_start = _test_case.latency_stage_list(0).duration_sec() + + butil::cpuwide_time_s(); + _stage_index = 0; + _running_case = false; + DisplayStage(_test_case.latency_stage_list(_stage_index)); + } + + void StartTestCase() { + CHECK(!_running_case); + _running_case = true; + UpdateLatency(); + } + + void StopTestCase() { + _running_case = false; + } + + void UpdateLatency() { + if (!_running_case) { + return; + } + ComputeLatency(); + g_timer_thread.schedule(TimerTask, (void*)this, + butil::microseconds_from_now(FLAGS_latency_change_interval_us)); + } + + virtual void Echo(google::protobuf::RpcController* cntl_base, + const test::NotifyRequest* request, + test::NotifyResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + response->set_message("hello"); + ::usleep(FLAGS_server_sync_sleep_us); + if (FLAGS_use_usleep) { + ::usleep(_latency.load(butil::memory_order_relaxed)); + } else { + bthread_usleep(_latency.load(butil::memory_order_relaxed)); + } + } + + void ComputeLatency() { + if (_stage_index < _test_case.latency_stage_list_size() && + butil::cpuwide_time_s() > _next_stage_start) { + ++_stage_index; + if (_stage_index < _test_case.latency_stage_list_size()) { + _next_stage_start += _test_case.latency_stage_list(_stage_index).duration_sec(); + DisplayStage(_test_case.latency_stage_list(_stage_index)); + } + } + + if (_stage_index == _test_case.latency_stage_list_size()) { + const test::Stage& latency_stage = + _test_case.latency_stage_list(_stage_index - 1); + if (latency_stage.type() == test::ChangeType::FLUCTUATE) { + _latency.store((latency_stage.lower_bound() + latency_stage.upper_bound()) / 2, + butil::memory_order_relaxed); + } else if (latency_stage.type() == test::ChangeType::SMOOTH) { + _latency.store(latency_stage.upper_bound(), butil::memory_order_relaxed); + } + return; + } + + const test::Stage& latency_stage = _test_case.latency_stage_list(_stage_index); + const int lower_bound = latency_stage.lower_bound(); + const int upper_bound = latency_stage.upper_bound(); + if (latency_stage.type() == test::FLUCTUATE) { + _latency.store(butil::fast_rand_less_than(upper_bound - lower_bound) + lower_bound, + butil::memory_order_relaxed); + } else if (latency_stage.type() == test::SMOOTH) { + int latency = lower_bound + (upper_bound - lower_bound) / + double(latency_stage.duration_sec()) * + (latency_stage.duration_sec() - _next_stage_start + + butil::cpuwide_time_s()); + _latency.store(latency, butil::memory_order_relaxed); + } else { + LOG(FATAL) << "Wrong Type:" << latency_stage.type(); + } + } + +private: + int _stage_index; + int _next_stage_start; + butil::atomic _latency; + test::TestCase _test_case; + bool _running_case; +}; + +void TimerTask(void* data) { + EchoServiceImpl* echo_service = (EchoServiceImpl*)data; + echo_service->UpdateLatency(); +} + +class ControlServiceImpl : public test::ControlService { +public: + ControlServiceImpl() + : _case_index(0) { + LoadCaseSet(FLAGS_case_file); + _echo_service = new EchoServiceImpl; + if (_server.AddService(_echo_service, + brpc::SERVER_OWNS_SERVICE) != 0) { + LOG(FATAL) << "Fail to add service"; + } + g_timer_thread.start(NULL); + } + virtual ~ControlServiceImpl() { + _echo_service->StopTestCase(); + g_timer_thread.stop_and_join(); + }; + + virtual void Notify(google::protobuf::RpcController* cntl_base, + const test::NotifyRequest* request, + test::NotifyResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + const std::string& message = request->message(); + LOG(INFO) << message; + if (message == "ResetCaseSet") { + _server.Stop(0); + _server.Join(); + _echo_service->StopTestCase(); + + LoadCaseSet(FLAGS_case_file); + _case_index = 0; + response->set_message("CaseSetReset"); + } else if (message == "StartCase") { + CHECK(!_server.IsRunning()) << "Continuous StartCase"; + const test::TestCase& test_case = _case_set.test_case(_case_index++); + _echo_service->SetTestCase(test_case); + brpc::ServerOptions options; + options.max_concurrency = FLAGS_server_max_concurrency; + _server.MaxConcurrencyOf("test.EchoService.Echo") = test_case.max_concurrency(); + + _server.Start(FLAGS_echo_port, &options); + _echo_service->StartTestCase(); + response->set_message("CaseStarted"); + } else if (message == "StopCase") { + CHECK(_server.IsRunning()) << "Continuous StopCase"; + _server.Stop(0); + _server.Join(); + + _echo_service->StopTestCase(); + response->set_message("CaseStopped"); + } else { + LOG(FATAL) << "Invalid message:" << message; + response->set_message("Invalid Cntl Message"); + } + } + +private: + void LoadCaseSet(const std::string& file_path) { + std::ifstream ifs(file_path.c_str(), std::ios::in); + if (!ifs) { + LOG(FATAL) << "Fail to open case set file: " << file_path; + } + std::string case_set_json((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + test::TestCaseSet case_set; + std::string err; + if (!json2pb::JsonToProtoMessage(case_set_json, &case_set, &err)) { + LOG(FATAL) + << "Fail to trans case_set from json to protobuf message: " + << err; + } + _case_set = case_set; + ifs.close(); + } + + brpc::Server _server; + EchoServiceImpl* _echo_service; + test::TestCaseSet _case_set; + int _case_index; +}; + + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + bthread::FLAGS_bthread_concurrency= FLAGS_server_bthread_concurrency; + + brpc::Server server; + + ControlServiceImpl control_service_impl; + + if (server.AddService(&control_service_impl, + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + + if (server.Start(FLAGS_cntl_port, NULL) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + server.RunUntilAskedToQuit(); + return 0; +} + diff --git a/example/auto_concurrency_limiter/settings.flags b/example/auto_concurrency_limiter/settings.flags new file mode 100644 index 0000000..610f092 --- /dev/null +++ b/example/auto_concurrency_limiter/settings.flags @@ -0,0 +1,25 @@ +#client settings +--case_file=test_case.json +--client_qps_change_interval_us=50000 +--max_retry=0 + +#auto_cl settings +--auto_cl_initial_max_concurrency=40 +--auto_cl_max_explore_ratio=0.3 +--auto_cl_min_explore_ratio=0.06 +--auto_cl_change_rate_of_explore_ratio=0.02 +--auto_cl_reduce_ratio_while_remeasure=0.9 +--auto_cl_latency_fluctuation_correction_factor=2 + +#server setings for async sleep +--latency_change_interval_us=50000 +--server_bthread_concurrency=4 +--server_sync_sleep_us=2500 +--use_usleep=false + +#server setings for sync sleep +#--latency_change_interval_us=50000 +#--server_bthread_concurrency=16 +#--server_max_concurrency=15 +#--server_sync_sleep_us=2500 +#--use_usleep=true diff --git a/example/auto_concurrency_limiter/test_case.json b/example/auto_concurrency_limiter/test_case.json new file mode 100644 index 0000000..62bc45d --- /dev/null +++ b/example/auto_concurrency_limiter/test_case.json @@ -0,0 +1,253 @@ +{"test_case":[ + +{ + "case_name":"CheckPeakQps", + "max_concurrency":"140", + "qps_stage_list": + [ + { + "lower_bound":3000, + "upper_bound":3000, + "duration_sec":30, + "type":2 + } + ], + "latency_stage_list": + [ + { + "lower_bound":20000, + "upper_bound":20000, + "duration_sec":30, + "type":2 + } + ] +}, + + +{ + "case_name":"qps_stable_noload, latency_raise_smooth", + "max_concurrency":"auto", + "qps_stage_list": + [ + { + "lower_bound":1500, + "upper_bound":1500, + "duration_sec":190, + "type":2 + } + ], + "latency_stage_list": + [ + { + "lower_bound":2000, + "upper_bound":90000, + "duration_sec":200, + "type":2 + } + ] +}, + + +{ + "case_name":"qps_fluctuate_noload, latency_stable", + "max_concurrency":"auto", + "qps_stage_list": + [ + { + "lower_bound":300, + "upper_bound":1800, + "duration_sec":290, + "type":1 + } + ], + "latency_stage_list": + [ + { + "lower_bound":40000, + "upper_bound":40000, + "duration_sec":300, + "type":1 + } + ] +}, + + +{ + "case_name":"qps_stable_overload, latency_stable", + "max_concurrency":"auto", + "qps_stage_list": + [ + { + "lower_bound":3000, + "upper_bound":3000, + "duration_sec":180, + "type":2 + } + ], + "latency_stage_list": + [ + { + "lower_bound":40000, + "upper_bound":40000, + "duration_sec":200, + "type":2 + } + ] +}, + +{ + "case_name":"qps_stable_overload, latency_raise_smooth", + "max_concurrency":"auto", + "qps_stage_list": + [ + { + "lower_bound":3000, + "upper_bound":3000, + "duration_sec":180, + "type":2 + } + ], + "latency_stage_list": + [ + { + "lower_bound":30000, + "upper_bound":80000, + "duration_sec":200, + "type":2 + } + ] +}, + +{ + "case_name":"qps_overload_then_noload, latency_stable", + "max_concurrency":"auto", + "qps_stage_list": + [ + { + "lower_bound":200, + "upper_bound":2500, + "duration_sec":20, + "type":2 + }, + { + "lower_bound":2500, + "upper_bound":2500, + "duration_sec":150, + "type":2 + }, + { + "lower_bound":2500, + "upper_bound":1000, + "duration_sec":20, + "type":2 + }, + { + "lower_bound":1000, + "upper_bound":1000, + "duration_sec":150, + "type":2 + } + ], + "latency_stage_list": + [ + { + "lower_bound":30000, + "upper_bound":30000, + "duration_sec":200, + "type":2 + } + ] +}, + + +{ + "case_name":"qps_noload_to_overload, latency_stable", + "max_concurrency":"auto", + "qps_stage_list": + [ + { + "lower_bound":200, + "upper_bound":3000, + "duration_sec":150, + "type":2 + }, + { + "lower_bound":3000, + "upper_bound":3000, + "duration_sec":150, + "type":2 + } + ], + "latency_stage_list": + [ + { + "lower_bound":30000, + "upper_bound":30000, + "duration_sec":200, + "type":2 + } + ] +}, + +{ + "case_name":"qps_stable_noload, latency_leap_raise", + "max_concurrency":"auto", + "qps_stage_list": + [ + { + "lower_bound":300, + "upper_bound":1800, + "duration_sec":20, + "type":2 + }, + { + "lower_bound":1800, + "upper_bound":1800, + "duration_sec":220, + "type":2 + } + ], + "latency_stage_list": + [ + { + "lower_bound":30000, + "upper_bound":30000, + "duration_sec":100, + "type":2 + }, + { + "lower_bound":50000, + "upper_bound":50000, + "duration_sec":100, + "type":2 + } + ] +}, + +{ + "case_name":"qps_fluctuate_noload, latency_fluctuate_noload", + "max_concurrency":"auto", + "qps_stage_list": + [ + { + "lower_bound":300, + "upper_bound":1800, + "duration_sec":190, + "type":1 + } + ], + "latency_stage_list": + [ + { + "lower_bound":30000, + "upper_bound":50000, + "duration_sec":200, + "type":1 + } + ] +} + + + + +]} diff --git a/example/backup_request_c++/CMakeLists.txt b/example/backup_request_c++/CMakeLists.txt new file mode 100644 index 0000000..5de9b58 --- /dev/null +++ b/example/backup_request_c++/CMakeLists.txt @@ -0,0 +1,110 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(backup_request_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER echo.proto) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(backup_request_client client.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(backup_request_client) +add_executable(backup_request_server server.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(backup_request_server) + +target_link_libraries(backup_request_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) +target_link_libraries(backup_request_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) diff --git a/example/backup_request_c++/client.cpp b/example/backup_request_c++/client.cpp new file mode 100644 index 0000000..07005e3 --- /dev/null +++ b/example/backup_request_c++/client.cpp @@ -0,0 +1,87 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending requests to server every 1 second. If the response does +// not come back within FLAGS_backup_request_ms, it sends another request +// and ends the RPC when any response comes back. + +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_string(protocol, "baidu_std", "Protocol type. Defined in src/brpc/options.proto"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(server, "0.0.0.0:8000", "IP Address of server"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_int32(backup_request_ms, 2, "Timeout for sending backup request"); + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = FLAGS_protocol; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + options.backup_request_ms = FLAGS_backup_request_ms; + if (channel.Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + // Normally, you should not call a Channel directly, but instead construct + // a stub Service wrapping it. stub can be shared by all threads as well. + example::EchoService_Stub stub(&channel); + + // Send a request and wait for the response every 1 second. + int counter = 0; + while (!brpc::IsAskedToQuit()) { + // We will receive response synchronously, safe to put variables + // on stack. + example::EchoRequest request; + example::EchoResponse response; + brpc::Controller cntl; + + request.set_index(++counter); + + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + stub.Echo(&cntl, &request, &response, NULL); + if (!cntl.Failed()) { + LOG(INFO) << "Received response[index=" << response.index() + << "] from " << cntl.remote_side() + << " to " << cntl.local_side() + << " latency=" << cntl.latency_us() << "us"; + } else { + LOG(WARNING) << cntl.ErrorText(); + } + sleep(1); + } + + LOG(INFO) << "EchoClient is going to quit"; + return 0; +} diff --git a/example/backup_request_c++/echo.proto b/example/backup_request_c++/echo.proto new file mode 100644 index 0000000..fe7f602 --- /dev/null +++ b/example/backup_request_c++/echo.proto @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package example; + +option cc_generic_services = true; + +message EchoRequest { + required int32 index = 1; +}; + +message EchoResponse { + required int32 index = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; diff --git a/example/backup_request_c++/server.cpp b/example/backup_request_c++/server.cpp new file mode 100644 index 0000000..9bc8a57 --- /dev/null +++ b/example/backup_request_c++/server.cpp @@ -0,0 +1,109 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server sleeping for even-th requests to trigger backup request of client. + +#include +#include +#include +#include "echo.pb.h" + +DEFINE_bool(echo_attachment, true, "Echo attachment as well"); +DEFINE_int32(port, 8000, "TCP Port of this server"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); +DEFINE_int32(sleep_ms, 20, "Sleep so many milliseconds on even-th requests"); + +// Your implementation of example::EchoService +// Notice that implementing brpc::Describable grants the ability to put +// additional information in /status. +namespace example { +class SleepyEchoService : public EchoService + , public brpc::Describable { +public: + SleepyEchoService() : _count(0) {} + virtual ~SleepyEchoService() {} + virtual void Echo(google::protobuf::RpcController* cntl_base, + const EchoRequest* request, + EchoResponse* response, + google::protobuf::Closure* done) { + // This object helps you to call done->Run() in RAII style. If you need + // to process the request asynchronously, pass done_guard.release(). + brpc::ClosureGuard done_guard(done); + + brpc::Controller* cntl = + static_cast(cntl_base); + + // The purpose of following logs is to help you to understand + // how clients interact with servers more intuitively. You should + // remove these logs in performance-sensitive servers. + // The noflush prevents the log from being flushed immediately. + LOG(INFO) << "Received request[index=" << request->index() + << "] from " << cntl->remote_side() + << " to " << cntl->local_side() << noflush; + // Sleep a while for 0th, 2nd, 4th, 6th ... requests to trigger backup request + // at client-side. + bool do_sleep = (_count.fetch_add(1, butil::memory_order_relaxed) % 2 == 0); + if (do_sleep) { + LOG(INFO) << ", sleep " << FLAGS_sleep_ms + << " ms to trigger backup request" << noflush; + } + LOG(INFO); + + // Fill response. + response->set_index(request->index()); + + if (do_sleep) { + bthread_usleep(FLAGS_sleep_ms * 1000); + } + } +private: + butil::atomic _count; +}; +} // namespace example + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // Generally you only need one Server. + brpc::Server server; + + // Instance of your service. + example::SleepyEchoService echo_service_impl; + + // Add the service into server. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + if (server.AddService(&echo_service_impl, + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + + // Start the server. + brpc::ServerOptions options; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + if (server.Start(FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + // Wait until Ctrl-C is pressed, then Stop() and Join() the server. + server.RunUntilAskedToQuit(); + return 0; +} diff --git a/example/baidu_proxy_and_generic_call/CMakeLists.txt b/example/baidu_proxy_and_generic_call/CMakeLists.txt new file mode 100644 index 0000000..bd3da92 --- /dev/null +++ b/example/baidu_proxy_and_generic_call/CMakeLists.txt @@ -0,0 +1,105 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(baidu_proxy_and_generic_call C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER echo.proto) + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(echo_client client.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(echo_client) +add_executable(proxy proxy.cpp) +brpc_example_configure_target(proxy) +add_executable(echo_server server.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(echo_server) + +target_link_libraries(echo_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) +target_link_libraries(proxy PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) +target_link_libraries(echo_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) diff --git a/example/baidu_proxy_and_generic_call/client.cpp b/example/baidu_proxy_and_generic_call/client.cpp new file mode 100644 index 0000000..b8fd310 --- /dev/null +++ b/example/baidu_proxy_and_generic_call/client.cpp @@ -0,0 +1,94 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending requests to server every 1 second. + +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_int32(compress_type, 2, "The compress type of request"); +DEFINE_string(attachment, "", "Carry this along with requests"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(proxy_address, "0.0.0.0:8000", "IP Address of proxy"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_int32(interval_ms, 1000, "Milliseconds between consecutive requests"); + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_BAIDU_STD; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + if (channel.Init(FLAGS_proxy_address.c_str(), + FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + // Normally, you should not call a Channel directly, but instead construct + // a stub Service wrapping it. stub can be shared by all threads as well. + example::EchoService_Stub stub(&channel); + + // Send a request and wait for the response every 1 second. + int log_id = 0; + while (!brpc::IsAskedToQuit()) { + // We will receive response synchronously, safe to put variables + // on stack. + example::EchoRequest request; + example::EchoResponse response; + brpc::Controller cntl; + + request.set_message("hello world"); + cntl.set_request_compress_type((brpc::CompressType)FLAGS_compress_type); + + cntl.set_log_id(log_id++); // set by user + // Set attachment which is wired to network directly instead of + // being serialized into protobuf messages. + cntl.request_attachment().append(FLAGS_attachment); + + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + stub.Echo(&cntl, &request, &response, NULL); + if (!cntl.Failed()) { + LOG(INFO) << "Received response from " << cntl.remote_side() + << " to " << cntl.local_side() + << ": " << response.message() + << ", response compress type=" << cntl.response_compress_type() + << ", attached=" << cntl.response_attachment() + << ", latency=" << cntl.latency_us() << "us"; + } else { + LOG(WARNING) << cntl.ErrorText(); + } + usleep(FLAGS_interval_ms * 1000L); + } + + LOG(INFO) << "EchoClient is going to quit"; + return 0; +} diff --git a/example/baidu_proxy_and_generic_call/echo.proto b/example/baidu_proxy_and_generic_call/echo.proto new file mode 100644 index 0000000..2b39627 --- /dev/null +++ b/example/baidu_proxy_and_generic_call/echo.proto @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package example; + +option cc_generic_services = true; + +message EchoRequest { + required string message = 1; +}; + +message EchoResponse { + required string message = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; diff --git a/example/baidu_proxy_and_generic_call/proxy.cpp b/example/baidu_proxy_and_generic_call/proxy.cpp new file mode 100644 index 0000000..a7f24fb --- /dev/null +++ b/example/baidu_proxy_and_generic_call/proxy.cpp @@ -0,0 +1,142 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. +// todo +// A proxy to receive EchoRequest and send back EchoResponse. + +#include +#include +#include +#include +#include +#include +#include + +DEFINE_int32(port, 8000, "TCP Port of this server"); +DEFINE_string(listen_addr, "", "Server listen address, may be IPV4/IPV6/UDS." + " If this is set, the flag port will be ignored"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(server_address, "0.0.0.0:8001", "IP Address of server"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_int32(interval_ms, 1000, "Milliseconds between consecutive requests"); + +// Your implementation of example::EchoService +// Notice that implementing brpc::Describable grants the ability to put +// additional information in /status. +namespace example { +class BaiduMasterServiceImpl : public brpc::BaiduMasterService { +public: + void ProcessRpcRequest(brpc::Controller* cntl, + const brpc::SerializedRequest* request, + brpc::SerializedResponse* response, + ::google::protobuf::Closure* done) override { + // This object helps you to call done->Run() in RAII style. If you need + // to process the request asynchronously, pass done_guard.release(). + brpc::ClosureGuard done_guard(done); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_BAIDU_STD; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + if (channel.Init(FLAGS_server_address.c_str(), + FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + (*cntl->response_user_fields())["x-bd-proxy-error-code"] = + butil::IntToString(brpc::EINTERNAL); + (*cntl->response_user_fields())["x-bd-proxy-error-text"] = + "Fail to initialize channel"; + return; + } + + LOG(INFO) << "Received request[log_id=" << cntl->log_id() + << "] from " << cntl->remote_side() + << " to " << cntl->local_side() + << ", serialized request size=" << request->serialized_data().size() + << ", request compress type=" << cntl->request_compress_type() + << " (attached=" << cntl->request_attachment() << ")"; + + brpc::Controller call_cntl; + call_cntl.set_log_id(cntl->log_id()); + call_cntl.request_attachment().swap(cntl->request_attachment()); + call_cntl.set_request_compress_type(cntl->request_compress_type()); + call_cntl.reset_sampled_request(cntl->release_sampled_request()); + // It is ok to use request and response for sync rpc. + channel.CallMethod(NULL, &call_cntl, request, response, NULL); + (*cntl->response_user_fields())["x-bd-proxy-error-code"] = + butil::IntToString(call_cntl.ErrorCode()); + if (call_cntl.Failed()) { + (*cntl->response_user_fields())["x-bd-proxy-error-text"] = + call_cntl.ErrorText(); + LOG(ERROR) << "Fail to call service=" << call_cntl.sampled_request()->meta.service_name() + << ", method=" << call_cntl.sampled_request()->meta.method_name() + << ", error_code=" << call_cntl.ErrorCode() + << ", error_text=" << call_cntl.ErrorCode(); + return; + } else { + LOG(INFO) << "Received response from " << call_cntl.remote_side() + << " to " << call_cntl.local_side() + << ", serialized response size=" << response->serialized_data().size() + << ", response compress type=" << call_cntl.response_compress_type() + << ", attached=" << call_cntl.response_attachment() + << ", latency=" << call_cntl.latency_us() << "us"; + } + cntl->response_attachment().swap(call_cntl.response_attachment()); + cntl->set_response_compress_type(call_cntl.response_compress_type()); + } +}; +} // namespace example + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // Generally you only need one Server. + brpc::Server server; + + butil::EndPoint point; + if (!FLAGS_listen_addr.empty()) { + if (butil::str2endpoint(FLAGS_listen_addr.c_str(), &point) < 0) { + LOG(ERROR) << "Invalid listen address:" << FLAGS_listen_addr; + return -1; + } + } else { + point = butil::EndPoint(butil::IP_ANY, FLAGS_port); + } + // Start the server. + brpc::ServerOptions options; + // Add the baidu master service into server. + // Notice new operator, because server will delete it in dtor of Server. + options.baidu_master_service = new example::BaiduMasterServiceImpl(); + options.idle_timeout_sec = FLAGS_idle_timeout_s; + if (server.Start(point, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + // Wait until Ctrl-C is pressed, then Stop() and Join() the server. + server.RunUntilAskedToQuit(); + return 0; +} diff --git a/example/baidu_proxy_and_generic_call/server.cpp b/example/baidu_proxy_and_generic_call/server.cpp new file mode 100644 index 0000000..64d4936 --- /dev/null +++ b/example/baidu_proxy_and_generic_call/server.cpp @@ -0,0 +1,118 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive EchoRequest and send back EchoResponse. + +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_int32(compress_type, 2, "The compress type of response"); +DEFINE_bool(echo_attachment, true, "Echo attachment as well"); +DEFINE_int32(port, 8001, "TCP Port of this server"); +DEFINE_string(listen_addr, "", "Server listen address, may be IPV4/IPV6/UDS." + " If this is set, the flag port will be ignored"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); + +// Your implementation of example::EchoService +// Notice that implementing brpc::Describable grants the ability to put +// additional information in /status. +namespace example { +class EchoServiceImpl : public EchoService { +public: + EchoServiceImpl() = default; + ~EchoServiceImpl() override = default; + void Echo(google::protobuf::RpcController* cntl_base, + const EchoRequest* request, + EchoResponse* response, + google::protobuf::Closure* done) override { + // This object helps you to call done->Run() in RAII style. If you need + // to process the request asynchronously, pass done_guard.release(). + brpc::ClosureGuard done_guard(done); + + auto cntl = static_cast(cntl_base); + + // The purpose of following logs is to help you to understand + // how clients interact with servers more intuitively. You should + // remove these logs in performance-sensitive servers. + LOG(INFO) << "Received request[log_id=" << cntl->log_id() + << "] from " << cntl->remote_side() + << " to " << cntl->local_side() + << ": " << request->message() + << ", request compress type=" << cntl->request_compress_type() + << ", attached=" << cntl->request_attachment(); + + // Fill response. + response->set_message(request->message()); + cntl->set_response_compress_type((brpc::CompressType)FLAGS_compress_type); + + // You can compress the response by setting Controller, but be aware + // that compression may be costly, evaluate before turning on. + // cntl->set_response_compress_type(brpc::COMPRESS_TYPE_GZIP); + + if (FLAGS_echo_attachment) { + // Set attachment which is wired to network directly instead of + // being serialized into protobuf messages. + cntl->response_attachment().append(cntl->request_attachment()); + } + } +}; +} // namespace example + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // Generally you only need one Server. + brpc::Server server; + + // Instance of your service. + example::EchoServiceImpl echo_service_impl; + + // Add the service into server. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + if (server.AddService(&echo_service_impl, + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + + butil::EndPoint point; + if (!FLAGS_listen_addr.empty()) { + if (butil::str2endpoint(FLAGS_listen_addr.c_str(), &point) < 0) { + LOG(ERROR) << "Invalid listen address:" << FLAGS_listen_addr; + return -1; + } + } else { + point = butil::EndPoint(butil::IP_ANY, FLAGS_port); + } + // Start the server. + brpc::ServerOptions options; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + if (server.Start(point, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + // Wait until Ctrl-C is pressed, then Stop() and Join() the server. + server.RunUntilAskedToQuit(); + return 0; +} diff --git a/example/bthread_tag_echo_c++/CMakeLists.txt b/example/bthread_tag_echo_c++/CMakeLists.txt new file mode 100644 index 0000000..7543a6a --- /dev/null +++ b/example/bthread_tag_echo_c++/CMakeLists.txt @@ -0,0 +1,115 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(bthread_tag_echo_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER echo.proto) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(GPERFTOOLS_INCLUDE_DIR NAMES gperftools/heap-profiler.h) +find_library(GPERFTOOLS_LIBRARIES NAMES tcmalloc_and_profiler) + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +set(BRPC_EXAMPLE_ENABLE_CPU_PROFILER ON) + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(echo_client client.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(echo_client) +add_executable(echo_server server.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(echo_server) + +target_link_libraries(echo_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${GPERFTOOLS_LIBRARIES}) +target_link_libraries(echo_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${GPERFTOOLS_LIBRARIES}) diff --git a/example/bthread_tag_echo_c++/client.cpp b/example/bthread_tag_echo_c++/client.cpp new file mode 100644 index 0000000..2b5a9e0 --- /dev/null +++ b/example/bthread_tag_echo_c++/client.cpp @@ -0,0 +1,154 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending requests to server by multiple threads. + +#include +#include +#include +#include +#include +#include "echo.pb.h" +#include + +DEFINE_int32(thread_num, 50, "Number of threads to send requests"); +DEFINE_bool(use_bthread, false, "Use bthread to send requests"); +DEFINE_int32(attachment_size, 0, "Carry so many byte attachment along with requests"); +DEFINE_int32(request_size, 16, "Bytes of each request"); +DEFINE_string(protocol, "baidu_std", "Protocol type. Defined in src/brpc/options.proto"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(server, "0.0.0.0:8002", "IP Address of server"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_bool(dont_fail, false, "Print fatal when some call failed"); +DEFINE_int32(dummy_port, -1, "Launch dummy server at this port"); + +std::string g_request; +std::string g_attachment; + +bvar::LatencyRecorder g_latency_recorder("client"); +bvar::Adder g_error_count("client_error_count"); + +static void* sender(void* arg) { + // Normally, you should not call a Channel directly, but instead construct + // a stub Service wrapping it. stub can be shared by all threads as well. + example::EchoService_Stub stub(static_cast(arg)); + + int log_id = 0; + while (!brpc::IsAskedToQuit()) { + // We will receive response synchronously, safe to put variables + // on stack. + example::EchoRequest request; + example::EchoResponse response; + brpc::Controller cntl; + + request.set_message(g_request); + cntl.set_log_id(log_id++); // set by user + // Set attachment which is wired to network directly instead of + // being serialized into protobuf messages. + cntl.request_attachment().append(g_attachment); + + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + stub.Echo(&cntl, &request, &response, NULL); + if (!cntl.Failed()) { + g_latency_recorder << cntl.latency_us(); + } else { + g_error_count << 1; + CHECK(brpc::IsAskedToQuit() || !FLAGS_dont_fail) + << "error=" << cntl.ErrorText() << " latency=" << cntl.latency_us(); + // We can't connect to the server, sleep a while. Notice that this + // is a specific sleeping to prevent this thread from spinning too + // fast. You should continue the business logic in a production + // server rather than sleeping. + bthread_usleep(50000); + } + } + return NULL; +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = FLAGS_protocol; + options.connection_type = FLAGS_connection_type; + options.connect_timeout_ms = std::min(FLAGS_timeout_ms / 2, 100); + options.timeout_ms = FLAGS_timeout_ms; + options.max_retry = FLAGS_max_retry; + if (channel.Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + if (FLAGS_attachment_size > 0) { + g_attachment.resize(FLAGS_attachment_size, 'a'); + } + if (FLAGS_request_size <= 0) { + LOG(ERROR) << "Bad request_size=" << FLAGS_request_size; + return -1; + } + g_request.resize(FLAGS_request_size, 'r'); + + if (FLAGS_dummy_port >= 0) { + brpc::StartDummyServerAt(FLAGS_dummy_port); + } + + std::vector bids; + std::vector pids; + if (!FLAGS_use_bthread) { + pids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (pthread_create(&pids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create pthread"; + return -1; + } + } + } else { + bids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (bthread_start_background(&bids[i], nullptr, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create bthread"; + return -1; + } + } + } + + while (!brpc::IsAskedToQuit()) { + sleep(1); + LOG(INFO) << "Sending EchoRequest at qps=" << g_latency_recorder.qps(1) + << " latency=" << g_latency_recorder.latency(1); + } + + LOG(INFO) << "EchoClient is going to quit"; + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (!FLAGS_use_bthread) { + pthread_join(pids[i], NULL); + } else { + bthread_join(bids[i], NULL); + } + } + + return 0; +} diff --git a/example/bthread_tag_echo_c++/echo.proto b/example/bthread_tag_echo_c++/echo.proto new file mode 100644 index 0000000..e963faf --- /dev/null +++ b/example/bthread_tag_echo_c++/echo.proto @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +option cc_generic_services = true; + +package example; + +message EchoRequest { + required string message = 1; +}; + +message EchoResponse { + required string message = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; diff --git a/example/bthread_tag_echo_c++/server.cpp b/example/bthread_tag_echo_c++/server.cpp new file mode 100644 index 0000000..0d14bd1 --- /dev/null +++ b/example/bthread_tag_echo_c++/server.cpp @@ -0,0 +1,153 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive EchoRequest and send back EchoResponse. + +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_bool(echo_attachment, true, "Echo attachment as well"); +DEFINE_int32(port1, 8002, "TCP Port of this server"); +DEFINE_int32(port2, 8003, "TCP Port of this server"); +DEFINE_int32(tag1, 0, "Server1 tag"); +DEFINE_int32(tag2, 1, "Server2 tag"); +DEFINE_int32(tag3, 2, "Background task tag"); +DEFINE_int32(num_threads1, 6, "Thread number of server1"); +DEFINE_int32(num_threads2, 16, "Thread number of server2"); +DEFINE_int32(idle_timeout_s, -1, + "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); +DEFINE_int32(max_concurrency, 0, "Limit of request processing in parallel"); +DEFINE_int32(internal_port1, -1, "Only allow builtin services at this port"); +DEFINE_int32(internal_port2, -1, "Only allow builtin services at this port"); + +namespace example { +// Your implementation of EchoService +class EchoServiceImpl : public EchoService { +public: + EchoServiceImpl() {} + ~EchoServiceImpl() {} + void Echo(google::protobuf::RpcController* cntl_base, const EchoRequest* request, + EchoResponse* response, google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = static_cast(cntl_base); + + // Echo request and its attachment + response->set_message(request->message()); + if (FLAGS_echo_attachment) { + cntl->response_attachment().append(cntl->request_attachment()); + } + } +}; +} // namespace example + +DEFINE_bool(h, false, "print help information"); + +static void my_tagged_worker_start_fn(bthread_tag_t tag) { + LOG(INFO) << "run tagged worker start function tag=" << tag; +} + +static void* my_background_task(void*) { + while (true) { + LOG(INFO) << "run background task tag=" << bthread_self_tag(); + bthread_usleep(1000000UL); + } + return nullptr; +} + +int main(int argc, char* argv[]) { + std::string help_str = "dummy help infomation"; + GFLAGS_NAMESPACE::SetUsageMessage(help_str); + + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_h) { + fprintf(stderr, "%s\n%s\n%s", help_str.c_str(), help_str.c_str(), help_str.c_str()); + return 0; + } + + // Set tagged worker function + bthread_set_tagged_worker_startfn(my_tagged_worker_start_fn); + + // Generally you only need one Server. + brpc::Server server1; + + // Instance of your service. + example::EchoServiceImpl echo_service_impl1; + + // Add the service into server. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + if (server1.AddService(&echo_service_impl1, brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + + // Start the server. + brpc::ServerOptions options1; + options1.idle_timeout_sec = FLAGS_idle_timeout_s; + options1.max_concurrency = FLAGS_max_concurrency; + options1.internal_port = FLAGS_internal_port1; + options1.bthread_tag = FLAGS_tag1; + options1.num_threads = FLAGS_num_threads1; + if (server1.Start(FLAGS_port1, &options1) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + // Generally you only need one Server. + brpc::Server server2; + + // Instance of your service. + example::EchoServiceImpl echo_service_impl2; + + // Add the service into server. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + if (server2.AddService(&echo_service_impl2, brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + + // Start the server. + brpc::ServerOptions options2; + options2.idle_timeout_sec = FLAGS_idle_timeout_s; + options2.max_concurrency = FLAGS_max_concurrency; + options2.internal_port = FLAGS_internal_port2; + options2.bthread_tag = FLAGS_tag2; + options2.num_threads = FLAGS_num_threads2; + if (server2.Start(FLAGS_port2, &options2) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + // Start backgroup task + bthread_t tid; + bthread_attr_t attr = BTHREAD_ATTR_NORMAL; + attr.tag = FLAGS_tag3; + bthread_start_background(&tid, &attr, my_background_task, nullptr); + + // Wait until Ctrl-C is pressed, then Stop() and Join() the server. + server1.RunUntilAskedToQuit(); + server2.RunUntilAskedToQuit(); + + return 0; +} diff --git a/example/build_with_bazel/BUILD.bazel b/example/build_with_bazel/BUILD.bazel new file mode 100644 index 0000000..9ac3da0 --- /dev/null +++ b/example/build_with_bazel/BUILD.bazel @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# Thie empty BUILD.bazel file is required to make Bazel treat +# this directory as a package. + + +cc_binary( + name = "test", + srcs = ["test.cc"], + deps = [ + "@apache_brpc//:brpc", + "@apache_brpc//:bthread", + "@apache_brpc//:bvar", + "@apache_brpc//:butil", + ], +) diff --git a/example/build_with_bazel/brpc_workspace.bzl b/example/build_with_bazel/brpc_workspace.bzl new file mode 100644 index 0000000..7a0ef40 --- /dev/null +++ b/example/build_with_bazel/brpc_workspace.bzl @@ -0,0 +1,94 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + +load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository", "new_git_repository") +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + + +BAZEL_SKYLIB_VERSION = "1.1.1" # 2021-09-27T17:33:49Z + +BAZEL_SKYLIB_SHA256 = "c6966ec828da198c5d9adbaa94c05e3a1c7f21bd012a0b29ba8ddbccb2c93b0d" + +def brpc_workspace(): + http_archive( + name = "bazel_skylib", + sha256 = BAZEL_SKYLIB_SHA256, + urls = [ + "https://github.com/bazelbuild/bazel-skylib/releases/download/{version}/bazel-skylib-{version}.tar.gz".format(version = BAZEL_SKYLIB_VERSION), + "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/{version}/bazel-skylib-{version}.tar.gz".format(version = BAZEL_SKYLIB_VERSION), + ], + ) + + http_archive( + name = "com_google_protobuf", # 2021-10-29T00:04:02Z + build_file = "//:protobuf.BUILD", + patch_cmds = [ + "sed -i protobuf.bzl -re '4,4d;417,508d'", + ], + patch_cmds_win = [ + """$content = Get-Content 'protobuf.bzl' | Where-Object { + -not ($_.ReadCount -ne 4) -and + -not ($_.ReadCount -ge 418 -and $_.ReadCount -le 509) + } + Set-Content protobuf.bzl -Value $content -Encoding UTF8 + """, + ], + sha256 = "87407cd28e7a9c95d9f61a098a53cf031109d451a7763e7dd1253abf8b4df422", + strip_prefix = "protobuf-3.19.1", + urls = ["https://github.com/protocolbuffers/protobuf/archive/refs/tags/v3.19.1.tar.gz"], + ) + + + http_archive( + name = "com_github_google_leveldb", + build_file = "//:leveldb.BUILD", + strip_prefix = "leveldb-a53934a3ae1244679f812d998a4f16f2c7f309a6", + url = "https://github.com/google/leveldb/archive/a53934a3ae1244679f812d998a4f16f2c7f309a6.tar.gz" + ) + + + + http_archive( + name = "com_github_madler_zlib", # 2017-01-15T17:57:23Z + build_file = "//:zlib.BUILD", + sha256 = "c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1", + strip_prefix = "zlib-1.2.11", + urls = [ + "https://downloads.sourceforge.net/project/libpng/zlib/1.2.11/zlib-1.2.11.tar.gz", + "https://zlib.net/fossils/zlib-1.2.11.tar.gz", + ], + ) + + native.new_local_repository( + name = "openssl", + path = "/usr", + build_file = "//:openssl.BUILD", + ) + + http_archive( + name = "com_github_gflags_gflags", + strip_prefix = "gflags-46f73f88b18aee341538c0dfc22b1710a6abedef", + url = "https://github.com/gflags/gflags/archive/46f73f88b18aee341538c0dfc22b1710a6abedef.tar.gz", + ) + + http_archive( + name = "apache_brpc", + strip_prefix = "brpc-1.3.0", + url = "https://github.com/apache/brpc/archive/refs/tags/1.3.0.tar.gz" + ) + diff --git a/example/build_with_bazel/leveldb.BUILD b/example/build_with_bazel/leveldb.BUILD new file mode 100644 index 0000000..369841e --- /dev/null +++ b/example/build_with_bazel/leveldb.BUILD @@ -0,0 +1,97 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +package(default_visibility = ["//visibility:public"]) + + +config_setting( + name = "darwin", + values = {"cpu": "darwin"}, + visibility = ["//visibility:public"], +) + +SOURCES = ["db/builder.cc", + "db/c.cc", + "db/dbformat.cc", + "db/db_impl.cc", + "db/db_iter.cc", + "db/dumpfile.cc", + "db/filename.cc", + "db/log_reader.cc", + "db/log_writer.cc", + "db/memtable.cc", + "db/repair.cc", + "db/table_cache.cc", + "db/version_edit.cc", + "db/version_set.cc", + "db/write_batch.cc", + "table/block_builder.cc", + "table/block.cc", + "table/filter_block.cc", + "table/format.cc", + "table/iterator.cc", + "table/merger.cc", + "table/table_builder.cc", + "table/table.cc", + "table/two_level_iterator.cc", + "util/arena.cc", + "util/bloom.cc", + "util/cache.cc", + "util/coding.cc", + "util/comparator.cc", + "util/crc32c.cc", + "util/env.cc", + "util/env_posix.cc", + "util/filter_policy.cc", + "util/hash.cc", + "util/histogram.cc", + "util/logging.cc", + "util/options.cc", + "util/status.cc", + "port/port_posix.cc", + "port/port_posix_sse.cc", + "helpers/memenv/memenv.cc", + ] + +cc_library( + name = "leveldb", + srcs = SOURCES, + hdrs = glob([ + "helpers/memenv/*.h", + "util/*.h", + "port/*.h", + "port/win/*.h", + "table/*.h", + "db/*.h", + "include/leveldb/*.h" + ], + exclude = [ + "**/*test.*", + ]), + includes = [ + "include/", + ], + copts = [ + "-fno-builtin-memcmp", + "-DLEVELDB_PLATFORM_POSIX=1", + "-DLEVELDB_ATOMIC_PRESENT", + ], + defines = [ + "LEVELDB_PLATFORM_POSIX", + ] + select({ + ":darwin": ["OS_MACOSX"], + "//conditions:default": [], + }), +) \ No newline at end of file diff --git a/example/build_with_bazel/openssl.BUILD b/example/build_with_bazel/openssl.BUILD new file mode 100644 index 0000000..117144c --- /dev/null +++ b/example/build_with_bazel/openssl.BUILD @@ -0,0 +1,56 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +package( + default_visibility=["//visibility:public"] +) + +config_setting( + name = "macos", + values = { + "cpu": "darwin", + }, + visibility = ["//visibility:private"], +) + +cc_library( + name = "crypto", + srcs = select({ + ":macos": ["lib/libcrypto.dylib"], + "//conditions:default": [] + }), + linkopts = select({ + ":macos" : [], + "//conditions:default": ["-lcrypto"], + }), +) + +cc_library( + name = "ssl", + hdrs = select({ + ":macos": glob(["include/openssl/*.h"]), + "//conditions:default": [] + }), + srcs = select ({ + ":macos": ["lib/libssl.dylib"], + "//conditions:default": [] + }), + includes = ["include"], + linkopts = select({ + ":macos" : [], + "//conditions:default": ["-lssl"], + }), + deps = [":crypto"] +) \ No newline at end of file diff --git a/example/build_with_bazel/protobuf.BUILD b/example/build_with_bazel/protobuf.BUILD new file mode 100644 index 0000000..0d5188e --- /dev/null +++ b/example/build_with_bazel/protobuf.BUILD @@ -0,0 +1,498 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright 2008 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Copied from https://github.com/protocolbuffers/protobuf/blob/v3.19.1/BUILD +# +# Modifications: +# 1. Remove all non-cxx rules. +# 2. Remove android support. +# 3. zlib use @com_github_madler_zlib//:zlib + +# Bazel (https://bazel.build/) BUILD file for Protobuf. + +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", native_cc_proto_library = "cc_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_lang_toolchain", "proto_library") +load(":compiler_config_setting.bzl", "create_compiler_config_setting") +load( + ":protobuf.bzl", + "adapt_proto_library", +) + +licenses(["notice"]) + +exports_files(["LICENSE"]) + +################################################################################ +# build configuration +################################################################################ + +################################################################################ +# ZLIB configuration +################################################################################ + +ZLIB_DEPS = ["@com_github_madler_zlib//:zlib"] + +################################################################################ +# Protobuf Runtime Library +################################################################################ + +MSVC_COPTS = [ + "/wd4018", # -Wno-sign-compare + "/wd4065", # switch statement contains 'default' but no 'case' labels + "/wd4146", # unary minus operator applied to unsigned type, result still unsigned + "/wd4244", # 'conversion' conversion from 'type1' to 'type2', possible loss of data + "/wd4251", # 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2' + "/wd4267", # 'var' : conversion from 'size_t' to 'type', possible loss of data + "/wd4305", # 'identifier' : truncation from 'type1' to 'type2' + "/wd4307", # 'operator' : integral constant overflow + "/wd4309", # 'conversion' : truncation of constant value + "/wd4334", # 'operator' : result of 32-bit shift implicitly converted to 64 bits (was 64-bit shift intended?) + "/wd4355", # 'this' : used in base member initializer list + "/wd4506", # no definition for inline function 'function' + "/wd4800", # 'type' : forcing value to bool 'true' or 'false' (performance warning) + "/wd4996", # The compiler encountered a deprecated declaration. +] + +COPTS = select({ + ":msvc": MSVC_COPTS, + "//conditions:default": [ + "-DHAVE_ZLIB", + "-Wmissing-field-initializers", + "-Woverloaded-virtual", + "-Wno-sign-compare", + ], +}) + +create_compiler_config_setting( + name = "msvc", + value = "msvc-cl", + visibility = [ + # Public, but Protobuf only visibility. + "//:__subpackages__", + ], +) + +# Android and MSVC builds do not need to link in a separate pthread library. +LINK_OPTS = select({ + ":msvc": [ + # Suppress linker warnings about files with no symbols defined. + "-ignore:4221", + ], + "//conditions:default": [ + "-lpthread", + "-lm", + ], +}) + +cc_library( + name = "protobuf_lite", + srcs = [ + # AUTOGEN(protobuf_lite_srcs) + "src/google/protobuf/any_lite.cc", + "src/google/protobuf/arena.cc", + "src/google/protobuf/arenastring.cc", + "src/google/protobuf/extension_set.cc", + "src/google/protobuf/generated_enum_util.cc", + "src/google/protobuf/generated_message_table_driven_lite.cc", + "src/google/protobuf/generated_message_tctable_lite.cc", + "src/google/protobuf/generated_message_util.cc", + "src/google/protobuf/implicit_weak_message.cc", + "src/google/protobuf/inlined_string_field.cc", + "src/google/protobuf/io/coded_stream.cc", + "src/google/protobuf/io/io_win32.cc", + "src/google/protobuf/io/strtod.cc", + "src/google/protobuf/io/zero_copy_stream.cc", + "src/google/protobuf/io/zero_copy_stream_impl.cc", + "src/google/protobuf/io/zero_copy_stream_impl_lite.cc", + "src/google/protobuf/map.cc", + "src/google/protobuf/message_lite.cc", + "src/google/protobuf/parse_context.cc", + "src/google/protobuf/repeated_field.cc", + "src/google/protobuf/repeated_ptr_field.cc", + "src/google/protobuf/stubs/bytestream.cc", + "src/google/protobuf/stubs/common.cc", + "src/google/protobuf/stubs/int128.cc", + "src/google/protobuf/stubs/status.cc", + "src/google/protobuf/stubs/statusor.cc", + "src/google/protobuf/stubs/stringpiece.cc", + "src/google/protobuf/stubs/stringprintf.cc", + "src/google/protobuf/stubs/structurally_valid.cc", + "src/google/protobuf/stubs/strutil.cc", + "src/google/protobuf/stubs/time.cc", + "src/google/protobuf/wire_format_lite.cc", + ], + hdrs = glob([ + "src/google/protobuf/**/*.h", + "src/google/protobuf/**/*.inc", + ]), + copts = COPTS, + includes = ["src/"], + linkopts = LINK_OPTS, + visibility = ["//visibility:public"], +) + +PROTOBUF_DEPS = select({ + ":msvc": [], + "//conditions:default": ZLIB_DEPS, +}) + +cc_library( + name = "protobuf", + srcs = [ + # AUTOGEN(protobuf_srcs) + "src/google/protobuf/any.cc", + "src/google/protobuf/any.pb.cc", + "src/google/protobuf/api.pb.cc", + "src/google/protobuf/compiler/importer.cc", + "src/google/protobuf/compiler/parser.cc", + "src/google/protobuf/descriptor.cc", + "src/google/protobuf/descriptor.pb.cc", + "src/google/protobuf/descriptor_database.cc", + "src/google/protobuf/duration.pb.cc", + "src/google/protobuf/dynamic_message.cc", + "src/google/protobuf/empty.pb.cc", + "src/google/protobuf/extension_set_heavy.cc", + "src/google/protobuf/field_mask.pb.cc", + "src/google/protobuf/generated_message_bases.cc", + "src/google/protobuf/generated_message_reflection.cc", + "src/google/protobuf/generated_message_table_driven.cc", + "src/google/protobuf/generated_message_tctable_full.cc", + "src/google/protobuf/io/gzip_stream.cc", + "src/google/protobuf/io/printer.cc", + "src/google/protobuf/io/tokenizer.cc", + "src/google/protobuf/map_field.cc", + "src/google/protobuf/message.cc", + "src/google/protobuf/reflection_ops.cc", + "src/google/protobuf/service.cc", + "src/google/protobuf/source_context.pb.cc", + "src/google/protobuf/struct.pb.cc", + "src/google/protobuf/stubs/substitute.cc", + "src/google/protobuf/text_format.cc", + "src/google/protobuf/timestamp.pb.cc", + "src/google/protobuf/type.pb.cc", + "src/google/protobuf/unknown_field_set.cc", + "src/google/protobuf/util/delimited_message_util.cc", + "src/google/protobuf/util/field_comparator.cc", + "src/google/protobuf/util/field_mask_util.cc", + "src/google/protobuf/util/internal/datapiece.cc", + "src/google/protobuf/util/internal/default_value_objectwriter.cc", + "src/google/protobuf/util/internal/error_listener.cc", + "src/google/protobuf/util/internal/field_mask_utility.cc", + "src/google/protobuf/util/internal/json_escaping.cc", + "src/google/protobuf/util/internal/json_objectwriter.cc", + "src/google/protobuf/util/internal/json_stream_parser.cc", + "src/google/protobuf/util/internal/object_writer.cc", + "src/google/protobuf/util/internal/proto_writer.cc", + "src/google/protobuf/util/internal/protostream_objectsource.cc", + "src/google/protobuf/util/internal/protostream_objectwriter.cc", + "src/google/protobuf/util/internal/type_info.cc", + "src/google/protobuf/util/internal/utility.cc", + "src/google/protobuf/util/json_util.cc", + "src/google/protobuf/util/message_differencer.cc", + "src/google/protobuf/util/time_util.cc", + "src/google/protobuf/util/type_resolver_util.cc", + "src/google/protobuf/wire_format.cc", + "src/google/protobuf/wrappers.pb.cc", + ], + hdrs = glob([ + "src/**/*.h", + "src/**/*.inc", + ]), + copts = COPTS, + includes = ["src/"], + linkopts = LINK_OPTS, + visibility = ["//visibility:public"], + deps = [":protobuf_lite"] + PROTOBUF_DEPS, +) + +# This provides just the header files for use in projects that need to build +# shared libraries for dynamic loading. This target is available until Bazel +# adds native support for such use cases. +# TODO(keveman): Remove this target once the support gets added to Bazel. +cc_library( + name = "protobuf_headers", + hdrs = glob([ + "src/**/*.h", + "src/**/*.inc", + ]), + includes = ["src/"], + visibility = ["//visibility:public"], +) + +# Map of all well known protos. +# name => (include path, imports) +WELL_KNOWN_PROTO_MAP = { + "any": ("src/google/protobuf/any.proto", []), + "api": ( + "src/google/protobuf/api.proto", + [ + "source_context", + "type", + ], + ), + "compiler_plugin": ( + "src/google/protobuf/compiler/plugin.proto", + ["descriptor"], + ), + "descriptor": ("src/google/protobuf/descriptor.proto", []), + "duration": ("src/google/protobuf/duration.proto", []), + "empty": ("src/google/protobuf/empty.proto", []), + "field_mask": ("src/google/protobuf/field_mask.proto", []), + "source_context": ("src/google/protobuf/source_context.proto", []), + "struct": ("src/google/protobuf/struct.proto", []), + "timestamp": ("src/google/protobuf/timestamp.proto", []), + "type": ( + "src/google/protobuf/type.proto", + [ + "any", + "source_context", + ], + ), + "wrappers": ("src/google/protobuf/wrappers.proto", []), +} + +WELL_KNOWN_PROTOS = [value[0] for value in WELL_KNOWN_PROTO_MAP.values()] + +LITE_WELL_KNOWN_PROTO_MAP = { + "any": ("src/google/protobuf/any.proto", []), + "api": ( + "src/google/protobuf/api.proto", + [ + "source_context", + "type", + ], + ), + "duration": ("src/google/protobuf/duration.proto", []), + "empty": ("src/google/protobuf/empty.proto", []), + "field_mask": ("src/google/protobuf/field_mask.proto", []), + "source_context": ("src/google/protobuf/source_context.proto", []), + "struct": ("src/google/protobuf/struct.proto", []), + "timestamp": ("src/google/protobuf/timestamp.proto", []), + "type": ( + "src/google/protobuf/type.proto", + [ + "any", + "source_context", + ], + ), + "wrappers": ("src/google/protobuf/wrappers.proto", []), +} + +LITE_WELL_KNOWN_PROTOS = [value[0] for value in LITE_WELL_KNOWN_PROTO_MAP.values()] + +filegroup( + name = "well_known_protos", + srcs = WELL_KNOWN_PROTOS, + visibility = ["//visibility:public"], +) + +filegroup( + name = "lite_well_known_protos", + srcs = LITE_WELL_KNOWN_PROTOS, + visibility = ["//visibility:public"], +) + +adapt_proto_library( + name = "cc_wkt_protos_genproto", + visibility = ["//visibility:public"], + deps = [proto + "_proto" for proto in WELL_KNOWN_PROTO_MAP.keys()], +) + +cc_library( + name = "cc_wkt_protos", + deprecation = "Only for backward compatibility. Do not use.", + visibility = ["//visibility:public"], +) + +################################################################################ +# Well Known Types Proto Library Rules +# +# These proto_library rules can be used with one of the language specific proto +# library rules i.e. java_proto_library: +# +# java_proto_library( +# name = "any_java_proto", +# deps = ["@com_google_protobuf//:any_proto], +# ) +################################################################################ + +[proto_library( + name = proto[0] + "_proto", + srcs = [proto[1][0]], + strip_import_prefix = "src", + visibility = ["//visibility:public"], + deps = [dep + "_proto" for dep in proto[1][1]], +) for proto in WELL_KNOWN_PROTO_MAP.items()] + +[native_cc_proto_library( + name = proto + "_cc_proto", + visibility = ["//visibility:private"], + deps = [proto + "_proto"], +) for proto in WELL_KNOWN_PROTO_MAP.keys()] + +################################################################################ +# Protocol Buffers Compiler +################################################################################ + +cc_library( + name = "protoc_lib", + srcs = [ + # AUTOGEN(protoc_lib_srcs) + "src/google/protobuf/compiler/code_generator.cc", + "src/google/protobuf/compiler/command_line_interface.cc", + "src/google/protobuf/compiler/cpp/cpp_enum.cc", + "src/google/protobuf/compiler/cpp/cpp_enum_field.cc", + "src/google/protobuf/compiler/cpp/cpp_extension.cc", + "src/google/protobuf/compiler/cpp/cpp_field.cc", + "src/google/protobuf/compiler/cpp/cpp_file.cc", + "src/google/protobuf/compiler/cpp/cpp_generator.cc", + "src/google/protobuf/compiler/cpp/cpp_helpers.cc", + "src/google/protobuf/compiler/cpp/cpp_map_field.cc", + "src/google/protobuf/compiler/cpp/cpp_message.cc", + "src/google/protobuf/compiler/cpp/cpp_message_field.cc", + "src/google/protobuf/compiler/cpp/cpp_padding_optimizer.cc", + "src/google/protobuf/compiler/cpp/cpp_parse_function_generator.cc", + "src/google/protobuf/compiler/cpp/cpp_primitive_field.cc", + "src/google/protobuf/compiler/cpp/cpp_service.cc", + "src/google/protobuf/compiler/cpp/cpp_string_field.cc", + "src/google/protobuf/compiler/csharp/csharp_doc_comment.cc", + "src/google/protobuf/compiler/csharp/csharp_enum.cc", + "src/google/protobuf/compiler/csharp/csharp_enum_field.cc", + "src/google/protobuf/compiler/csharp/csharp_field_base.cc", + "src/google/protobuf/compiler/csharp/csharp_generator.cc", + "src/google/protobuf/compiler/csharp/csharp_helpers.cc", + "src/google/protobuf/compiler/csharp/csharp_map_field.cc", + "src/google/protobuf/compiler/csharp/csharp_message.cc", + "src/google/protobuf/compiler/csharp/csharp_message_field.cc", + "src/google/protobuf/compiler/csharp/csharp_primitive_field.cc", + "src/google/protobuf/compiler/csharp/csharp_reflection_class.cc", + "src/google/protobuf/compiler/csharp/csharp_repeated_enum_field.cc", + "src/google/protobuf/compiler/csharp/csharp_repeated_message_field.cc", + "src/google/protobuf/compiler/csharp/csharp_repeated_primitive_field.cc", + "src/google/protobuf/compiler/csharp/csharp_source_generator_base.cc", + "src/google/protobuf/compiler/csharp/csharp_wrapper_field.cc", + "src/google/protobuf/compiler/java/java_context.cc", + "src/google/protobuf/compiler/java/java_doc_comment.cc", + "src/google/protobuf/compiler/java/java_enum.cc", + "src/google/protobuf/compiler/java/java_enum_field.cc", + "src/google/protobuf/compiler/java/java_enum_field_lite.cc", + "src/google/protobuf/compiler/java/java_enum_lite.cc", + "src/google/protobuf/compiler/java/java_extension.cc", + "src/google/protobuf/compiler/java/java_extension_lite.cc", + "src/google/protobuf/compiler/java/java_field.cc", + "src/google/protobuf/compiler/java/java_file.cc", + "src/google/protobuf/compiler/java/java_generator.cc", + "src/google/protobuf/compiler/java/java_generator_factory.cc", + "src/google/protobuf/compiler/java/java_helpers.cc", + "src/google/protobuf/compiler/java/java_kotlin_generator.cc", + "src/google/protobuf/compiler/java/java_map_field.cc", + "src/google/protobuf/compiler/java/java_map_field_lite.cc", + "src/google/protobuf/compiler/java/java_message.cc", + "src/google/protobuf/compiler/java/java_message_builder.cc", + "src/google/protobuf/compiler/java/java_message_builder_lite.cc", + "src/google/protobuf/compiler/java/java_message_field.cc", + "src/google/protobuf/compiler/java/java_message_field_lite.cc", + "src/google/protobuf/compiler/java/java_message_lite.cc", + "src/google/protobuf/compiler/java/java_name_resolver.cc", + "src/google/protobuf/compiler/java/java_primitive_field.cc", + "src/google/protobuf/compiler/java/java_primitive_field_lite.cc", + "src/google/protobuf/compiler/java/java_service.cc", + "src/google/protobuf/compiler/java/java_shared_code_generator.cc", + "src/google/protobuf/compiler/java/java_string_field.cc", + "src/google/protobuf/compiler/java/java_string_field_lite.cc", + "src/google/protobuf/compiler/js/js_generator.cc", + "src/google/protobuf/compiler/js/well_known_types_embed.cc", + "src/google/protobuf/compiler/objectivec/objectivec_enum.cc", + "src/google/protobuf/compiler/objectivec/objectivec_enum_field.cc", + "src/google/protobuf/compiler/objectivec/objectivec_extension.cc", + "src/google/protobuf/compiler/objectivec/objectivec_field.cc", + "src/google/protobuf/compiler/objectivec/objectivec_file.cc", + "src/google/protobuf/compiler/objectivec/objectivec_generator.cc", + "src/google/protobuf/compiler/objectivec/objectivec_helpers.cc", + "src/google/protobuf/compiler/objectivec/objectivec_map_field.cc", + "src/google/protobuf/compiler/objectivec/objectivec_message.cc", + "src/google/protobuf/compiler/objectivec/objectivec_message_field.cc", + "src/google/protobuf/compiler/objectivec/objectivec_oneof.cc", + "src/google/protobuf/compiler/objectivec/objectivec_primitive_field.cc", + "src/google/protobuf/compiler/php/php_generator.cc", + "src/google/protobuf/compiler/plugin.cc", + "src/google/protobuf/compiler/plugin.pb.cc", + "src/google/protobuf/compiler/python/python_generator.cc", + "src/google/protobuf/compiler/ruby/ruby_generator.cc", + "src/google/protobuf/compiler/subprocess.cc", + "src/google/protobuf/compiler/zip_writer.cc", + ], + copts = COPTS, + includes = ["src/"], + linkopts = LINK_OPTS, + visibility = ["//visibility:public"], + deps = [":protobuf"], +) + +cc_binary( + name = "protoc", + srcs = ["src/google/protobuf/compiler/main.cc"], + linkopts = LINK_OPTS, + visibility = ["//visibility:public"], + deps = [":protoc_lib"], +) + +proto_lang_toolchain( + name = "cc_toolchain", + blacklisted_protos = [proto + "_proto" for proto in WELL_KNOWN_PROTO_MAP.keys()], + command_line = "--cpp_out=$(OUT)", + runtime = ":protobuf", + visibility = ["//visibility:public"], +) + +alias( + name = "objectivec", + actual = "//objectivec", + visibility = ["//visibility:public"], +) + +alias( + name = "protobuf_objc", + actual = "//objectivec", + visibility = ["//visibility:public"], +) diff --git a/example/build_with_bazel/test.cc b/example/build_with_bazel/test.cc new file mode 100644 index 0000000..b4eb778 --- /dev/null +++ b/example/build_with_bazel/test.cc @@ -0,0 +1,30 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 + +void *PrintHellobRPC(void *arg) { + printf("I Love bRPC"); + return nullptr; +} + +int main(int argc, char **argv) { + bthread_t th_1; + bthread_start_background(&th_1, nullptr, PrintHellobRPC, nullptr); + bthread_join(th_1, nullptr); + return 0; +} diff --git a/example/build_with_bazel/zlib.BUILD b/example/build_with_bazel/zlib.BUILD new file mode 100644 index 0000000..28cc081 --- /dev/null +++ b/example/build_with_bazel/zlib.BUILD @@ -0,0 +1,110 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright 2008 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Copied from https://github.com/protocolbuffers/protobuf/blob/v3.9.1/third_party/zlib.BUILD +load("@rules_cc//cc:defs.bzl", "cc_library") + +_ZLIB_HEADERS = [ + "crc32.h", + "deflate.h", + "gzguts.h", + "inffast.h", + "inffixed.h", + "inflate.h", + "inftrees.h", + "trees.h", + "zconf.h", + "zlib.h", + "zutil.h", +] + +_ZLIB_PREFIXED_HEADERS = ["zlib/include/" + hdr for hdr in _ZLIB_HEADERS] + +# In order to limit the damage from the `includes` propagation +# via `:zlib`, copy the public headers to a subdirectory and +# expose those. +genrule( + name = "copy_public_headers", + srcs = _ZLIB_HEADERS, + outs = _ZLIB_PREFIXED_HEADERS, + cmd = "cp $(SRCS) $(@D)/zlib/include/", + visibility = ["//visibility:private"], +) + +cc_library( + name = "zlib", + srcs = [ + "adler32.c", + "compress.c", + "crc32.c", + "deflate.c", + "gzclose.c", + "gzlib.c", + "gzread.c", + "gzwrite.c", + "infback.c", + "inffast.c", + "inflate.c", + "inftrees.c", + "trees.c", + "uncompr.c", + "zutil.c", + ], + hdrs = _ZLIB_PREFIXED_HEADERS, + copts = select({ + ":windows": [], + "//conditions:default": [ + "-Wno-unused-variable", + "-Wno-implicit-function-declaration", + ], + }), + includes = ["zlib/include/"], + visibility = ["//visibility:public"], +) + +config_setting( + name = "windows", + constraint_values = [ + "@platforms//os:windows", + ], +) diff --git a/example/build_with_old_bazel/BUILD.bazel b/example/build_with_old_bazel/BUILD.bazel new file mode 100644 index 0000000..8394dcb --- /dev/null +++ b/example/build_with_old_bazel/BUILD.bazel @@ -0,0 +1,28 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +genrule( + name = "empty_cc", + outs = ["empty.cc"], + cmd = "echo 'int main(){return 0;}' > $@", +) + +cc_binary( + name = "empty", + srcs = [":empty_cc"], + deps = [ + "@com_github_brpc_brpc//:brpc", + ], +) diff --git a/example/build_with_old_bazel/leveldb.BUILD b/example/build_with_old_bazel/leveldb.BUILD new file mode 100644 index 0000000..e8b2b7a --- /dev/null +++ b/example/build_with_old_bazel/leveldb.BUILD @@ -0,0 +1,97 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +package(default_visibility = ["//visibility:public"]) + + +config_setting( + name = "darwin", + values = {"cpu": "darwin"}, + visibility = ["//visibility:public"], +) + +SOURCES = ["db/builder.cc", + "db/c.cc", + "db/dbformat.cc", + "db/db_impl.cc", + "db/db_iter.cc", + "db/dumpfile.cc", + "db/filename.cc", + "db/log_reader.cc", + "db/log_writer.cc", + "db/memtable.cc", + "db/repair.cc", + "db/table_cache.cc", + "db/version_edit.cc", + "db/version_set.cc", + "db/write_batch.cc", + "table/block_builder.cc", + "table/block.cc", + "table/filter_block.cc", + "table/format.cc", + "table/iterator.cc", + "table/merger.cc", + "table/table_builder.cc", + "table/table.cc", + "table/two_level_iterator.cc", + "util/arena.cc", + "util/bloom.cc", + "util/cache.cc", + "util/coding.cc", + "util/comparator.cc", + "util/crc32c.cc", + "util/env.cc", + "util/env_posix.cc", + "util/filter_policy.cc", + "util/hash.cc", + "util/histogram.cc", + "util/logging.cc", + "util/options.cc", + "util/status.cc", + "port/port_posix.cc", + "port/port_posix_sse.cc", + "helpers/memenv/memenv.cc", + ] + +cc_library( + name = "leveldb", + srcs = SOURCES, + hdrs = glob([ + "helpers/memenv/*.h", + "util/*.h", + "port/*.h", + "port/win/*.h", + "table/*.h", + "db/*.h", + "include/leveldb/*.h" + ], + exclude = [ + "**/*test.*", + ]), + includes = [ + "include/", + ], + copts = [ + "-fno-builtin-memcmp", + "-DLEVELDB_PLATFORM_POSIX=1", + "-DLEVELDB_ATOMIC_PRESENT", + ], + defines = [ + "LEVELDB_PLATFORM_POSIX", + ] + select({ + ":darwin": ["OS_MACOSX"], + "//conditions:default": [], + }), +) diff --git a/example/build_with_old_bazel/openssl.BUILD b/example/build_with_old_bazel/openssl.BUILD new file mode 100644 index 0000000..92a687a --- /dev/null +++ b/example/build_with_old_bazel/openssl.BUILD @@ -0,0 +1,56 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +package( + default_visibility=["//visibility:public"] +) + +config_setting( + name = "macos", + values = { + "cpu": "darwin", + }, + visibility = ["//visibility:private"], +) + +cc_library( + name = "crypto", + srcs = select({ + ":macos": ["lib/libcrypto.dylib"], + "//conditions:default": [] + }), + linkopts = select({ + ":macos" : [], + "//conditions:default": ["-lcrypto"], + }), +) + +cc_library( + name = "ssl", + hdrs = select({ + ":macos": glob(["include/openssl/*.h"]), + "//conditions:default": [] + }), + srcs = select ({ + ":macos": ["lib/libssl.dylib"], + "//conditions:default": [] + }), + includes = ["include"], + linkopts = select({ + ":macos" : [], + "//conditions:default": ["-lssl"], + }), + deps = [":crypto"] +) diff --git a/example/build_with_old_bazel/zlib.BUILD b/example/build_with_old_bazel/zlib.BUILD new file mode 100644 index 0000000..0830cc0 --- /dev/null +++ b/example/build_with_old_bazel/zlib.BUILD @@ -0,0 +1,23 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +package( + default_visibility=["//visibility:public"] +) + +cc_library( + name = "zlib", + linkopts = ["-lz"], +) diff --git a/example/cancel_c++/CMakeLists.txt b/example/cancel_c++/CMakeLists.txt new file mode 100644 index 0000000..3dc198f --- /dev/null +++ b/example/cancel_c++/CMakeLists.txt @@ -0,0 +1,110 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(cancel_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER echo.proto) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(cancel_client client.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(cancel_client) +add_executable(cancel_server server.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(cancel_server) + +target_link_libraries(cancel_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) +target_link_libraries(cancel_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) diff --git a/example/cancel_c++/client.cpp b/example/cancel_c++/client.cpp new file mode 100644 index 0000000..dd23b67 --- /dev/null +++ b/example/cancel_c++/client.cpp @@ -0,0 +1,129 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client to send 2 requests to server and accept the first returned response. + +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_string(protocol, "baidu_std", "Protocol type. Defined in src/brpc/options.proto"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(server, "0.0.0.0:8000", "IP Address of server"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); + +// A special done for canceling another RPC. +class CancelRPC : public google::protobuf::Closure { +public: + explicit CancelRPC(brpc::CallId rpc_id) : _rpc_id(rpc_id) {} + + void Run() { + brpc::StartCancel(_rpc_id); + } + +private: + brpc::CallId _rpc_id; +}; + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = FLAGS_protocol; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + if (channel.Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + // Normally, you should not call a Channel directly, but instead construct + // a stub Service wrapping it. stub can be shared by all threads as well. + example::EchoService_Stub stub(&channel); + + // Send a request and wait for the response every 1 second. + int log_id = 0; + while (!brpc::IsAskedToQuit()) { + example::EchoRequest request1; + example::EchoResponse response1; + brpc::Controller cntl1; + + example::EchoRequest request2; + example::EchoResponse response2; + brpc::Controller cntl2; + + request1.set_message("hello1"); + request2.set_message("hello2"); + + cntl1.set_log_id(log_id ++); // set by user + cntl2.set_log_id(log_id ++); + + const brpc::CallId id1 = cntl1.call_id(); + const brpc::CallId id2 = cntl2.call_id(); + CancelRPC done1(id2); + CancelRPC done2(id1); + + butil::Timer tm; + tm.start(); + // Send 2 async calls and join them. They will cancel each other in + // their done which is run before the RPC being Join()-ed. Canceling + // a finished RPC has no effect. + // For example: + // Time RPC1 RPC2 + // 1 response1 comes back. + // 2 running done1. + // 3 cancel RPC2 + // 4 running done2 (NOTE: done also runs) + // 5 cancel RPC1 (no effect) + stub.Echo(&cntl1, &request1, &response1, &done1); + stub.Echo(&cntl2, &request2, &response2, &done2); + brpc::Join(id1); + brpc::Join(id2); + tm.stop(); + if (cntl1.Failed() && cntl2.Failed()) { + LOG(WARNING) << "Both failed. rpc1:" << cntl1.ErrorText() + << ", rpc2: " << cntl2.ErrorText(); + } else if (!cntl1.Failed()) { + LOG(INFO) << "Received `" << response1.message() << "' from rpc1=" + << id1.value << '@' << cntl1.remote_side() + << " latency=" << tm.u_elapsed() << "us" + << " rpc1_latency=" << cntl1.latency_us() << "us" + << " rpc2_latency=" << cntl2.latency_us() << "us"; + } else { + LOG(INFO) << "Received `" << response2.message() << "' from rpc2=" + << id2.value << '@' << cntl2.remote_side() + << " latency=" << tm.u_elapsed() << "us" + << " rpc1_latency=" << cntl1.latency_us() << "us" + << " rpc2_latency=" << cntl2.latency_us() << "us"; + } + sleep(1); + } + + LOG(INFO) << "EchoClient is going to quit"; + return 0; +} diff --git a/example/cancel_c++/echo.proto b/example/cancel_c++/echo.proto new file mode 100644 index 0000000..2b39627 --- /dev/null +++ b/example/cancel_c++/echo.proto @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package example; + +option cc_generic_services = true; + +message EchoRequest { + required string message = 1; +}; + +message EchoResponse { + required string message = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; diff --git a/example/cancel_c++/server.cpp b/example/cancel_c++/server.cpp new file mode 100644 index 0000000..b85ec09 --- /dev/null +++ b/example/cancel_c++/server.cpp @@ -0,0 +1,104 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive EchoRequest and send back EchoResponse. + +#include +#include +#include +#include "echo.pb.h" + +DEFINE_bool(echo_attachment, true, "Echo attachment as well"); +DEFINE_int32(port, 8000, "TCP Port of this server"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); + +// Your implementation of example::EchoService +// Notice that implementing brpc::Describable grants the ability to put +// additional information in /status. +namespace example { +class EchoServiceImpl : public EchoService { +public: + EchoServiceImpl() {} + virtual ~EchoServiceImpl() {} + virtual void Echo(google::protobuf::RpcController* cntl_base, + const EchoRequest* request, + EchoResponse* response, + google::protobuf::Closure* done) { + // This object helps you to call done->Run() in RAII style. If you need + // to process the request asynchronously, pass done_guard.release(). + brpc::ClosureGuard done_guard(done); + + brpc::Controller* cntl = + static_cast(cntl_base); + + // The purpose of following logs is to help you to understand + // how clients interact with servers more intuitively. You should + // remove these logs in performance-sensitive servers. + LOG(INFO) << "Received request[log_id=" << cntl->log_id() + << "] from " << cntl->remote_side() + << " to " << cntl->local_side() + << ": " << request->message() + << " (attached=" << cntl->request_attachment() << ")"; + + // Fill response. + response->set_message(request->message()); + + // You can compress the response by setting Controller, but be aware + // that compression may be costly, evaluate before turning on. + // cntl->set_response_compress_type(brpc::COMPRESS_TYPE_GZIP); + + if (FLAGS_echo_attachment) { + // Set attachment which is wired to network directly instead of + // being serialized into protobuf messages. + cntl->response_attachment().append(cntl->request_attachment()); + } + } +}; +} // namespace example + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // Generally you only need one Server. + brpc::Server server; + + // Instance of your service. + example::EchoServiceImpl echo_service_impl; + + // Add the service into server. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + if (server.AddService(&echo_service_impl, + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + + // Start the server. + brpc::ServerOptions options; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + if (server.Start(FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + // Wait until Ctrl-C is pressed, then Stop() and Join() the server. + server.RunUntilAskedToQuit(); + return 0; +} diff --git a/example/cascade_echo_c++/CMakeLists.txt b/example/cascade_echo_c++/CMakeLists.txt new file mode 100644 index 0000000..4e9a0d6 --- /dev/null +++ b/example/cascade_echo_c++/CMakeLists.txt @@ -0,0 +1,109 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(cascade_echo_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER echo.proto) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(cascade_echo_client client.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(cascade_echo_client) +add_executable(cascade_echo_server server.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(cascade_echo_server) + +target_link_libraries(cascade_echo_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) +target_link_libraries(cascade_echo_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) diff --git a/example/cascade_echo_c++/client.cpp b/example/cascade_echo_c++/client.cpp new file mode 100644 index 0000000..25a1b52 --- /dev/null +++ b/example/cascade_echo_c++/client.cpp @@ -0,0 +1,149 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending requests to server which will send the request to itself +// again according to the field `depth' + +#include +#include +#include +#include +#include +#include +#include "echo.pb.h" +#include +#include + +DEFINE_int32(thread_num, 2, "Number of threads to send requests"); +DEFINE_bool(use_bthread, false, "Use bthread to send requests"); +DEFINE_string(attachment, "foo", "Carry this along with requests"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(server, "0.0.0.0:8000", "IP Address of server"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_string(protocol, "baidu_std", "Protocol type. Defined in src/brpc/options.proto"); +DEFINE_int32(depth, 0, "number of loop calls"); +// Don't send too frequently in this example +DEFINE_int32(sleep_ms, 1000, "milliseconds to sleep after each RPC"); +DEFINE_int32(dummy_port, -1, "Launch dummy server at this port"); + +bvar::LatencyRecorder g_latency_recorder("client"); + +void* sender(void* arg) { + brpc::Channel* chan = (brpc::Channel*)arg; + // Normally, you should not call a Channel directly, but instead construct + // a stub Service wrapping it. stub can be shared by all threads as well. + example::EchoService_Stub stub(chan); + + // Send a request and wait for the response every 1 second. + while (!brpc::IsAskedToQuit()) { + // We will receive response synchronously, safe to put variables + // on stack. + example::EchoRequest request; + example::EchoResponse response; + brpc::Controller cntl; + + request.set_message("hello world"); + if (FLAGS_depth > 0) { + request.set_depth(FLAGS_depth); + } + + // Set request_id to be a random string + cntl.set_request_id(butil::fast_rand_printable(9)); + + // Set attachment which is wired to network directly instead of + // being serialized into protobuf messages. + cntl.request_attachment().append(FLAGS_attachment); + + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + stub.Echo(&cntl, &request, &response, NULL); + if (cntl.Failed()) { + //LOG_EVERY_SECOND(WARNING) << "Fail to send EchoRequest, " << cntl.ErrorText(); + } else { + g_latency_recorder << cntl.latency_us(); + } + if (FLAGS_sleep_ms != 0) { + bthread_usleep(FLAGS_sleep_ms * 1000L); + } + } + return NULL; +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::SetUsageMessage("Send EchoRequest to server every second"); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = FLAGS_protocol; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + + // Initialize the channel, NULL means using default options. + // options, see `brpc/channel.h'. + if (channel.Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + std::vector bids; + std::vector pids; + if (!FLAGS_use_bthread) { + pids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (pthread_create(&pids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create pthread"; + return -1; + } + } + } else { + bids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (bthread_start_background( + &bids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create bthread"; + return -1; + } + } + } + + if (FLAGS_dummy_port >= 0) { + brpc::StartDummyServerAt(FLAGS_dummy_port); + } + + while (!brpc::IsAskedToQuit()) { + sleep(1); + LOG(INFO) << "Sending EchoRequest at qps=" << g_latency_recorder.qps(1) + << " latency=" << g_latency_recorder.latency(1); + } + + LOG(INFO) << "EchoClient is going to quit"; + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (!FLAGS_use_bthread) { + pthread_join(pids[i], NULL); + } else { + bthread_join(bids[i], NULL); + } + } + return 0; +} diff --git a/example/cascade_echo_c++/echo.proto b/example/cascade_echo_c++/echo.proto new file mode 100644 index 0000000..9e51c86 --- /dev/null +++ b/example/cascade_echo_c++/echo.proto @@ -0,0 +1,34 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package example; + +option cc_generic_services = true; + +message EchoRequest { + required string message = 1; + optional int32 depth = 2; +}; + +message EchoResponse { + required string message = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; diff --git a/example/cascade_echo_c++/server.cpp b/example/cascade_echo_c++/server.cpp new file mode 100644 index 0000000..c329073 --- /dev/null +++ b/example/cascade_echo_c++/server.cpp @@ -0,0 +1,139 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include "echo.pb.h" + +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); + +DEFINE_bool(echo_attachment, true, "Echo attachment as well"); +DEFINE_int32(port, 8000, "TCP Port of this server"); +DEFINE_string(server, "", "The server to connect, localhost:FLAGS_port as default"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_bool(use_http, false, "Use http protocol to transfer messages"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); + +brpc::Channel channel; + +// Your implementation of example::EchoService +namespace example { +class CascadeEchoService : public EchoService { +public: + CascadeEchoService() {} + virtual ~CascadeEchoService() {} + virtual void Echo(google::protobuf::RpcController* cntl_base, + const EchoRequest* request, + EchoResponse* response, + google::protobuf::Closure* done) { + // This object helps you to call done->Run() in RAII style. If you need + // to process the request asynchronously, pass done_guard.release(). + brpc::ClosureGuard done_guard(done); + + brpc::Controller* cntl = + static_cast(cntl_base); + + if (request->depth() > 0) { + CLOGI(cntl) << "I'm about to call myself for another time, depth=" << request->depth(); + example::EchoService_Stub stub(&channel); + example::EchoRequest request2; + example::EchoResponse response2; + brpc::Controller cntl2(cntl->inheritable()); + request2.set_message(request->message()); + request2.set_depth(request->depth() - 1); + + cntl2.set_timeout_ms(FLAGS_timeout_ms); + cntl2.set_max_retry(FLAGS_max_retry); + stub.Echo(&cntl2, &request2, &response2, NULL); + if (cntl2.Failed()) { + CLOGE(&cntl2) << "Fail to send EchoRequest, " << cntl2.ErrorText(); + cntl->SetFailed(cntl2.ErrorCode(), "%s", cntl2.ErrorText().c_str()); + return; + } + response->set_message(response2.message()); + } else { + CLOGI(cntl) << "I'm the last call"; + response->set_message(request->message()); + } + + if (FLAGS_echo_attachment && !FLAGS_use_http) { + // Set attachment which is wired to network directly instead of + // being serialized into protobuf messages. + cntl->response_attachment().append(cntl->request_attachment()); + } + } +}; +} // namespace example + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::SetUsageMessage("A server that may call itself"); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::ChannelOptions coption; + if (FLAGS_use_http) { + coption.protocol = brpc::PROTOCOL_HTTP; + } + + // Initialize the channel, NULL means using default options. + // options, see `brpc/channel.h'. + if (FLAGS_server.empty()) { + if (channel.Init("localhost", FLAGS_port, &coption) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + } else { + if (channel.Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), &coption) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + } + + // Generally you only need one Server. + brpc::Server server; + // For more options see `brpc/server.h'. + brpc::ServerOptions options; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + + // Instance of your service. + example::CascadeEchoService echo_service_impl; + + // Add the service into server. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + if (server.AddService(&echo_service_impl, + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + + // Start the server. + if (server.Start(FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + // Wait until Ctrl-C is pressed, then Stop() and Join() the server. + server.RunUntilAskedToQuit(); + return 0; +} diff --git a/example/cmake/BrpcExample.cmake b/example/cmake/BrpcExample.cmake new file mode 100644 index 0000000..a0af0ed --- /dev/null +++ b/example/cmake/BrpcExample.cmake @@ -0,0 +1,75 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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_guard(GLOBAL) + +function(brpc_example_append_existing_dirs out_var) + set(_dirs) + foreach(_dir IN LISTS ARGN) + if(_dir AND NOT _dir MATCHES "-NOTFOUND$") + list(APPEND _dirs ${_dir}) + endif() + endforeach() + set(${out_var} ${_dirs} PARENT_SCOPE) +endfunction() + +function(brpc_example_configure_target target_name) + brpc_example_append_existing_dirs(_include_dirs + ${CMAKE_CURRENT_BINARY_DIR} + ${BRPC_INCLUDE_PATH} + ${GFLAGS_INCLUDE_PATH} + ${LEVELDB_INCLUDE_PATH} + ${OPENSSL_INCLUDE_DIR} + ${GPERFTOOLS_INCLUDE_DIR} + ${RDMA_INCLUDE_PATH} + ) + + if(_include_dirs) + target_include_directories(${target_name} PRIVATE ${_include_dirs}) + endif() + + target_compile_features(${target_name} PRIVATE cxx_std_11) + target_compile_definitions(${target_name} PRIVATE + NDEBUG + __const__=__unused__ + ) + target_compile_options(${target_name} PRIVATE + -O2 + -pipe + -W + -Wall + -Wno-unused-parameter + -fPIC + -fno-omit-frame-pointer + ) + + if(BRPC_EXAMPLE_ENABLE_CPU_PROFILER) + target_compile_definitions(${target_name} PRIVATE BRPC_ENABLE_CPU_PROFILER) + endif() + + if(BRPC_EXAMPLE_WITH_RDMA) + target_compile_definitions(${target_name} PRIVATE BRPC_WITH_RDMA=1) + endif() + + if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + include(CheckFunctionExists) + check_function_exists(clock_gettime BRPC_EXAMPLE_HAVE_CLOCK_GETTIME) + if(NOT BRPC_EXAMPLE_HAVE_CLOCK_GETTIME) + target_compile_definitions(${target_name} PRIVATE NO_CLOCK_GETTIME_IN_MAC) + endif() + endif() +endfunction() diff --git a/example/coroutine/coroutine_server.cpp b/example/coroutine/coroutine_server.cpp new file mode 100644 index 0000000..51ef7fc --- /dev/null +++ b/example/coroutine/coroutine_server.cpp @@ -0,0 +1,145 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive EchoRequest and send back EchoResponse. + +#include +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_int32(port, 8000, "TCP Port of this server"); +DEFINE_int32(sleep_us, 1000000, "Server sleep us"); +DEFINE_bool(enable_coroutine, true, "Enable coroutine"); + +using brpc::experimental::Awaitable; +using brpc::experimental::AwaitableDone; +using brpc::experimental::Coroutine; + +namespace example { +class EchoServiceImpl : public EchoService { +public: + EchoServiceImpl() { + brpc::ChannelOptions options; + options.timeout_ms = FLAGS_sleep_us / 1000 * 2 + 100; + options.max_retry = 0; + CHECK(_channel.Init(butil::EndPoint(butil::IP_ANY, FLAGS_port), &options) == 0); + } + + virtual ~EchoServiceImpl() {} + + void Echo(google::protobuf::RpcController* cntl_base, + const EchoRequest* request, + EchoResponse* response, + google::protobuf::Closure* done) override { + // brpc::Controller* cntl = + // static_cast(cntl_base); + + if (FLAGS_enable_coroutine) { + Coroutine(EchoAsync(request, response, done), true); + } else { + brpc::ClosureGuard done_guard(done); + bthread_usleep(FLAGS_sleep_us); + response->set_message(request->message()); + } + } + + Awaitable EchoAsync(const EchoRequest* request, + EchoResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + co_await Coroutine::usleep(FLAGS_sleep_us); + response->set_message(request->message()); + } + + void Proxy(google::protobuf::RpcController* cntl_base, + const EchoRequest* request, + EchoResponse* response, + google::protobuf::Closure* done) override { + // brpc::Controller* cntl = + // static_cast(cntl_base); + + if (FLAGS_enable_coroutine) { + Coroutine(ProxyAsync(request, response, done), true); + } else { + brpc::ClosureGuard done_guard(done); + EchoService_Stub stub(&_channel); + brpc::Controller cntl; + stub.Echo(&cntl, request, response, NULL); + if (cntl.Failed()) { + response->set_message(cntl.ErrorText()); + } + } + } + + Awaitable ProxyAsync(const EchoRequest* request, + EchoResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + EchoService_Stub stub(&_channel); + brpc::Controller cntl; + AwaitableDone done2; + stub.Echo(&cntl, request, response, &done2); + co_await done2.awaitable(); + if (cntl.Failed()) { + response->set_message(cntl.ErrorText()); + } + } + +private: + brpc::Channel _channel; +}; +} // namespace example + +int main(int argc, char* argv[]) { + bthread_setconcurrency(BTHREAD_MIN_CONCURRENCY); + + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + if (FLAGS_enable_coroutine) { + GFLAGS_NAMESPACE::SetCommandLineOption("usercode_in_coroutine", "true"); + } + + // Generally you only need one Server. + brpc::Server server; + + // Instance of your service. + example::EchoServiceImpl echo_service_impl; + + // Add the service into server. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + if (server.AddService(&echo_service_impl, + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + + // Start the server. + brpc::ServerOptions options; + options.num_threads = BTHREAD_MIN_CONCURRENCY; + if (server.Start(FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + // Wait until Ctrl-C is pressed, then Stop() and Join() the server. + server.RunUntilAskedToQuit(); + return 0; +} \ No newline at end of file diff --git a/example/coroutine/echo.proto b/example/coroutine/echo.proto new file mode 100644 index 0000000..ef5cc8a --- /dev/null +++ b/example/coroutine/echo.proto @@ -0,0 +1,34 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package example; + +option cc_generic_services = true; + +message EchoRequest { + required string message = 1; +}; + +message EchoResponse { + required string message = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); + rpc Proxy(EchoRequest) returns (EchoResponse); +}; diff --git a/example/couchbase_c++/couchbase_client.cpp b/example/couchbase_c++/couchbase_client.cpp new file mode 100644 index 0000000..b1dc906 --- /dev/null +++ b/example/couchbase_c++/couchbase_client.cpp @@ -0,0 +1,451 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include + +#include +#include + +// ANSI color codes for console output +#define GREEN "\033[32m" +#define RED "\033[31m" +#define RESET "\033[0m" + +DEFINE_string(server, "localhost:11210", "IP Address of server"); +int performOperations(brpc::CouchbaseOperations& couchbase_ops) { + std::string add_key = "user::test_brpc_binprot"; + std::string add_value = + R"({"name": "John Doe", "age": 30, "email": "john@example.com"})"; + + brpc::CouchbaseOperations::Result add_result = + couchbase_ops.add(add_key, add_value); + if (add_result.success) { + std::cout << GREEN << "ADD operation successful" << RESET << std::endl; + } else { + std::cout << RED << "ADD operation failed: " << add_result.error_message + << RESET << std::endl; + } + + // Try to ADD the same key again (should fail with key exists) + brpc::CouchbaseOperations::Result add_result2 = + couchbase_ops.add(add_key, add_value); + if (add_result2.success) { + std::cout << GREEN << "Second ADD operation unexpectedly successful" + << RESET << std::endl; + } else { + std::cout << RED << "Second ADD operation failed as expected: " + << add_result2.error_message << RESET << std::endl; + } + // Get operation using high-level method + brpc::CouchbaseOperations::Result get_result = couchbase_ops.get(add_key); + if (get_result.success) { + std::cout << GREEN << "GET operation successful" << RESET << std::endl; + std::cout << "GET value: " << get_result.value << std::endl; + } else { + std::cout << RED << "GET operation failed: " << get_result.error_message + << RESET << std::endl; + } + + // Add binprot item1 using high-level method + std::string item1_key = "binprot_item1"; + brpc::CouchbaseOperations::Result item1_result = + couchbase_ops.add(item1_key, add_value); + if (item1_result.success) { + std::cout << GREEN << "ADD binprot item1 successful" << RESET << std::endl; + } else { + std::cout << RED + << "ADD binprot item1 failed: " << item1_result.error_message + << RESET << std::endl; + } + + // Add binprot item2 using high-level method + std::string item2_key = "binprot_item2"; + brpc::CouchbaseOperations::Result item2_result = + couchbase_ops.add(item2_key, add_value); + if (item2_result.success) { + std::cout << GREEN << "ADD binprot item2 successful" << RESET << std::endl; + } else { + std::cout << RED + << "ADD binprot item2 failed: " << item2_result.error_message + << RESET << std::endl; + } + + // Add binprot item3 using high-level method + std::string item3_key = "binprot_item3"; + brpc::CouchbaseOperations::Result item3_result = + couchbase_ops.add(item3_key, add_value); + if (item3_result.success) { + std::cout << GREEN << "ADD binprot item3 successful" << RESET << std::endl; + } else { + std::cout << RED + << "ADD binprot item3 failed: " << item3_result.error_message + << RESET << std::endl; + } + + // Perform an UPSERT on the existing key using high-level method + std::string upsert_key = "user::test_brpc_binprot"; + std::string upsert_value = + R"({"name": "Upserted Jane Doe", "age": 28, "email": "upserted.doe@example.com"})"; + brpc::CouchbaseOperations::Result upsert_result = + couchbase_ops.upsert(upsert_key, upsert_value); + if (upsert_result.success) { + std::cout + << GREEN + << "UPSERT operation successful when the document exists in the server" + << RESET << std::endl; + } else { + std::cout + << RED + << "UPSERT operation failed when the document exists in the server: " + << upsert_result.error_message << RESET << std::endl; + } + // Do UPSERT operation on a new document using high-level method + std::string new_upsert_key = "user::test_brpc_new_upsert"; + std::string new_upsert_value = + R"({"name": "Jane Doe", "age": 28, "email": "jane.doe@example.com"})"; + brpc::CouchbaseOperations::Result new_upsert_result = + couchbase_ops.upsert(new_upsert_key, new_upsert_value); + if (new_upsert_result.success) { + std::cout << GREEN + << "UPSERT operation successful when the document doesn't exist " + "in the server" + << RESET << std::endl; + } else { + std::cout << RED + << "UPSERT operation failed when document does not exist in the " + "server: " + << new_upsert_result.error_message << RESET << std::endl; + } + + // Check the upserted data using high-level method + std::string check_key = "user::test_brpc_new_upsert"; + brpc::CouchbaseOperations::Result check_result = couchbase_ops.get(check_key); + if (check_result.success) { + std::cout << GREEN << "GET after UPSERT operation successful - Value: " + << check_result.value << RESET << std::endl; + } else { + std::cout << RED << "GET after UPSERT operation failed: " + << check_result.error_message << RESET << std::endl; + } + + // Delete a non-existent key using high-level method + std::string delete_key = "Nonexistent_key"; + brpc::CouchbaseOperations::Result delete_result = + couchbase_ops.delete_(delete_key); + if (delete_result.success) { + std::cout << GREEN << "DELETE operation successful" << RESET << std::endl; + } else { + std::cout << RED << "DELETE operation failed: as expected " + << delete_result.error_message << RESET << std::endl; + } + + // Delete the existing key using high-level method + std::string delete_existing_key = "user::test_brpc_binprot"; + brpc::CouchbaseOperations::Result delete_existing_result = + couchbase_ops.delete_(delete_existing_key); + if (delete_existing_result.success) { + std::cout << GREEN << "DELETE operation successful" << RESET << std::endl; + } else { + std::cout << RED << "DELETE operation failed: " + << delete_existing_result.error_message << RESET << std::endl; + } + + // Retrieve Collection ID for scope `_default` and collection + // `col1` + const std::string scope_name = "_default"; // default scope + std::string collection_name = "col1"; // target collection + // ------------------------------------------------------------------ + // Collection-scoped CRUD operations (only if collection id was retrieved) + // ------------------------------------------------------------------ + // 1. ADD in collection using high-level method + std::string coll_key = "user::collection_doc"; + std::string coll_value = R"({"type":"collection","op":"add","v":1})"; + brpc::CouchbaseOperations::Result coll_add_result = + couchbase_ops.add(coll_key, coll_value, collection_name); + if (coll_add_result.success) { + std::cout << GREEN << "Collection ADD success" << RESET << std::endl; + } else { + std::cout << RED + << "Collection ADD failed: " << coll_add_result.error_message + << RESET << std::endl; + } + // 2. GET from collection using high-level method + brpc::CouchbaseOperations::Result coll_get_result = + couchbase_ops.get(coll_key, collection_name); + if (coll_get_result.success) { + std::cout << GREEN + << "Collection GET success value=" << coll_get_result.value + << RESET << std::endl; + } else { + std::cout << RED + << "Collection GET failed: " << coll_get_result.error_message + << RESET << std::endl; + } + + // 3. UPSERT in collection using high-level method + std::string coll_upsert_value = + R"({"type":"collection","op":"upsert","v":2})"; + brpc::CouchbaseOperations::Result coll_upsert_result = + couchbase_ops.upsert(coll_key, coll_upsert_value, collection_name); + if (coll_upsert_result.success) { + std::cout << GREEN << "Collection UPSERT success" << RESET << std::endl; + } else { + std::cout << RED << "Collection UPSERT failed: " + << coll_upsert_result.error_message << RESET << std::endl; + } + + // 4. GET again to verify upsert using high-level method + brpc::CouchbaseOperations::Result coll_get2_result = + couchbase_ops.get(coll_key, collection_name); + if (coll_get2_result.success) { + std::cout << GREEN + << "Collection GET(after upsert) value=" << coll_get2_result.value + << RESET << std::endl; + } + + // 5. DELETE from collection using high-level method + brpc::CouchbaseOperations::Result coll_del_result = + couchbase_ops.delete_(coll_key, collection_name); + if (coll_del_result.success) { + std::cout << GREEN << "Collection DELETE success" << RESET << std::endl; + } else { + std::cout << RED + << "Collection DELETE failed: " << coll_del_result.error_message + << RESET << std::endl; + } + + // ------------------------------------------------------------------ + // Pipeline Operations Demo + // ------------------------------------------------------------------ + std::cout << GREEN << "\n=== Pipeline Operations Demo ===" << RESET + << std::endl; + + // Begin a new pipeline + if (!couchbase_ops.beginPipeline()) { + std::cout << RED << "Failed to begin pipeline" << RESET << std::endl; + return -1; + } + + std::cout << "Pipeline started. Adding multiple operations..." << std::endl; + + // Add multiple operations to the pipeline + std::string pipeline_key1 = "pipeline::doc1"; + std::string pipeline_key2 = "pipeline::doc2"; + std::string pipeline_key3 = "pipeline::doc3"; + std::string pipeline_value1 = R"({"operation": "pipeline_add", "id": 1})"; + std::string pipeline_value2 = R"({"operation": "pipeline_upsert", "id": 2})"; + std::string pipeline_value3 = R"({"operation": "pipeline_add", "id": 3})"; + + // Pipeline operations - all prepared but not yet executed + bool pipeline_success = true; + pipeline_success &= couchbase_ops.pipelineRequest( + brpc::CouchbaseOperations::ADD, pipeline_key1, pipeline_value1); + pipeline_success &= couchbase_ops.pipelineRequest( + brpc::CouchbaseOperations::UPSERT, pipeline_key2, pipeline_value2); + pipeline_success &= couchbase_ops.pipelineRequest( + brpc::CouchbaseOperations::ADD, pipeline_key3, pipeline_value3); + pipeline_success &= couchbase_ops.pipelineRequest( + brpc::CouchbaseOperations::GET, pipeline_key1); + pipeline_success &= couchbase_ops.pipelineRequest( + brpc::CouchbaseOperations::GET, pipeline_key2); + + if (!pipeline_success) { + std::cout << RED << "Failed to add operations to pipeline" << RESET + << std::endl; + couchbase_ops.clearPipeline(); + return -1; + } + + std::cout << "Added " << couchbase_ops.getPipelineSize() + << " operations to pipeline" << std::endl; + + // Execute all operations in a single network call + std::cout << "Executing pipeline operations..." << std::endl; + std::vector pipeline_results = + couchbase_ops.executePipeline(); + + // Process results in order + std::cout << GREEN << "Pipeline execution completed. Results:" << RESET + << std::endl; + for (size_t i = 0; i < pipeline_results.size(); ++i) { + const auto& result = pipeline_results[i]; + if (result.success) { + if (!result.value.empty()) { + std::cout << GREEN << " Operation " << (i + 1) + << " SUCCESS - Value: " << result.value << RESET << std::endl; + } else { + std::cout << GREEN << " Operation " << (i + 1) << " SUCCESS" << RESET + << std::endl; + } + } else { + std::cout << RED << " Operation " << (i + 1) + << " FAILED: " << result.error_message << RESET << std::endl; + } + } + + // Demonstrate pipeline with collection operations + std::cout << GREEN << "\n=== Pipeline with Collection Operations ===" << RESET + << std::endl; + + if (!couchbase_ops.beginPipeline()) { + std::cout << RED << "Failed to begin collection pipeline" << RESET + << std::endl; + return -1; + } + + std::string coll_pipeline_key1 = "coll_pipeline::doc1"; + std::string coll_pipeline_key2 = "coll_pipeline::doc2"; + std::string coll_pipeline_value1 = + R"({"collection_operation": "pipeline_add", "id": 1})"; + std::string coll_pipeline_value2 = + R"({"collection_operation": "pipeline_upsert", "id": 2})"; + + // Add collection-scoped operations to pipeline + bool coll_pipeline_success = true; + coll_pipeline_success &= couchbase_ops.pipelineRequest( + brpc::CouchbaseOperations::ADD, coll_pipeline_key1, coll_pipeline_value1, + collection_name); + coll_pipeline_success &= couchbase_ops.pipelineRequest( + brpc::CouchbaseOperations::UPSERT, coll_pipeline_key2, + coll_pipeline_value2, collection_name); + coll_pipeline_success &= couchbase_ops.pipelineRequest( + brpc::CouchbaseOperations::GET, coll_pipeline_key1, "", collection_name); + coll_pipeline_success &= + couchbase_ops.pipelineRequest(brpc::CouchbaseOperations::DELETE, + coll_pipeline_key1, "", collection_name); + + if (!coll_pipeline_success) { + std::cout << RED << "Failed to add collection operations to pipeline" + << RESET << std::endl; + couchbase_ops.clearPipeline(); + return -1; + } + + // Execute collection pipeline + std::vector coll_pipeline_results = + couchbase_ops.executePipeline(); + + std::cout << GREEN + << "Collection pipeline execution completed. Results:" << RESET + << std::endl; + for (size_t i = 0; i < coll_pipeline_results.size(); ++i) { + const auto& result = coll_pipeline_results[i]; + if (result.success) { + if (!result.value.empty()) { + std::cout << GREEN << " Collection Operation " << (i + 1) + << " SUCCESS - Value: " << result.value << RESET << std::endl; + } else { + std::cout << GREEN << " Collection Operation " << (i + 1) << " SUCCESS" + << RESET << std::endl; + } + } else { + std::cout << RED << " Collection Operation " << (i + 1) + << " FAILED: " << result.error_message << RESET << std::endl; + } + } + + // Clean up remaining pipeline documents + std::cout << GREEN << "\n=== Cleanup Pipeline Demo ===" << RESET << std::endl; + if (couchbase_ops.beginPipeline()) { + couchbase_ops.pipelineRequest(brpc::CouchbaseOperations::DELETE, + pipeline_key1); + couchbase_ops.pipelineRequest(brpc::CouchbaseOperations::DELETE, + pipeline_key2); + couchbase_ops.pipelineRequest(brpc::CouchbaseOperations::DELETE, + pipeline_key3); + couchbase_ops.pipelineRequest(brpc::CouchbaseOperations::DELETE, + coll_pipeline_key2, "", collection_name); + + std::vector cleanup_results = + couchbase_ops.executePipeline(); + std::cout << "Cleanup completed (" << cleanup_results.size() + << " operations)" << std::endl; + } + + std::cout << GREEN + << "\n=== All operations completed successfully! ===" << RESET + << std::endl; +} +int main() { + // Create CouchbaseOperations instance for high-level operations + brpc::CouchbaseOperations couchbase_ops; + + // std::cout << GREEN << "Using high-level CouchbaseOperations interface" + // << RESET << std::endl; + + // Ask username and password for authentication + std::string username = "Administrator"; + std::string password = "password"; + while (username.empty() || password.empty()) { + std::cout << "Enter Couchbase username: "; + std::cin >> username; + if (username.empty()) { + std::cout << "Username cannot be empty. Please enter again." << std::endl; + continue; + } + std::cout << "Enter Couchbase password: "; + std::cin >> password; + if (password.empty()) { + std::cout << "Password cannot be empty. Please enter again." << std::endl; + continue; + } + } + + // Use high-level authentication method + // when connecting to capella use couchbase_ops.authenticate(username, + // password, FLAGS_server, true, "path/to/cert.txt"); + brpc::CouchbaseOperations::Result auth_result = + couchbase_ops.authenticate(username, password, FLAGS_server, "testing"); + if (!auth_result.success) { + LOG(ERROR) << "Authentication failed: " << auth_result.error_message; + return -1; + } + + std::cout + << GREEN + << "Authentication successful, proceeding with Couchbase operations..." + << RESET << std::endl; + + performOperations(couchbase_ops); + + // Change bucket Selection + std::string bucket_name = "testing"; + while (bucket_name.empty()) { + std::cout << "Enter Couchbase bucket name: "; + std::cin >> bucket_name; + if (bucket_name.empty()) { + std::cout << "Bucket name cannot be empty. Please enter again." + << std::endl; + continue; + } + } + + // Use high-level bucket selection method + brpc::CouchbaseOperations::Result bucket_result = + couchbase_ops.selectBucket(bucket_name); + if (!bucket_result.success) { + LOG(ERROR) << "Bucket selection failed: " << bucket_result.error_message; + return -1; + } else { + std::cout << GREEN << "Bucket Selection Successful" << RESET << std::endl; + } + // Add operation using high-level method + performOperations(couchbase_ops); + return 0; +} diff --git a/example/couchbase_c++/multithreaded_couchbase_client.cpp b/example/couchbase_c++/multithreaded_couchbase_client.cpp new file mode 100644 index 0000000..d6dd9e5 --- /dev/null +++ b/example/couchbase_c++/multithreaded_couchbase_client.cpp @@ -0,0 +1,375 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include + +#include +#include +#include +#include + +// ANSI color codes +#define GREEN "\033[32m" +#define RED "\033[31m" +#define BLUE "\033[34m" +#define YELLOW "\033[33m" +#define CYAN "\033[36m" +#define RESET "\033[0m" + +const int NUM_THREADS = 20; +const int THREADS_PER_BUCKET = 5; + +// Simple global config +struct { + std::string username = "Administrator"; + std::string password = "password"; + std::vector bucket_names = {"t0", "t1", "t2", "t3"}; +} g_config; + +// Simple thread statistics +struct ThreadStats { + std::atomic operations_attempted{0}; + std::atomic operations_successful{0}; + std::atomic operations_failed{0}; + + void reset() { + operations_attempted = 0; + operations_successful = 0; + operations_failed = 0; + } +}; + +// Global statistics +struct GlobalStats { + ThreadStats total; + std::vector per_thread_stats; + + GlobalStats() : per_thread_stats(NUM_THREADS) {} + + void aggregate_stats() { + total.reset(); + for (const auto& stats : per_thread_stats) { + total.operations_attempted += stats.operations_attempted.load(); + total.operations_successful += stats.operations_successful.load(); + total.operations_failed += stats.operations_failed.load(); + } + } +} g_stats; + +// Simple thread arguments +struct ThreadArgs { + int thread_id; + int bucket_id; + std::string bucket_name; + brpc::CouchbaseOperations* couchbase_ops; + ThreadStats* stats; +}; + +// Simple CRUD operations on default collection +void perform_crud_operations_default(brpc::CouchbaseOperations& couchbase_ops, + const std::string& base_key, + ThreadStats* stats) { + std::string key = base_key + "_default"; + std::string value = butil::string_printf( + R"({"thread_id": %d, "collection": "default"})", (int)bthread_self()); + + stats->operations_attempted++; + + // UPSERT + brpc::CouchbaseOperations::Result result = couchbase_ops.upsert(key, value); + if (result.success) { + stats->operations_successful++; + } else { + stats->operations_failed++; + return; + } + + stats->operations_attempted++; + + // GET + result = couchbase_ops.get(key); + if (result.success) { + stats->operations_successful++; + } else { + stats->operations_failed++; + return; + } + + stats->operations_attempted++; + + // DELETE + result = couchbase_ops.delete_(key); + if (result.success) { + stats->operations_successful++; + } else { + stats->operations_failed++; + } +} + +// Simple CRUD operations on col1 collection +void perform_crud_operations_col1(brpc::CouchbaseOperations& couchbase_ops, + const std::string& base_key, + ThreadStats* stats) { + std::string key = base_key + "_col1"; + std::string value = butil::string_printf( + R"({"thread_id": %d, "collection": "col1"})", (int)bthread_self()); + + stats->operations_attempted++; + + // UPSERT + brpc::CouchbaseOperations::Result result = + couchbase_ops.upsert(key, value, "col1"); + if (result.success) { + stats->operations_successful++; + } else { + stats->operations_failed++; + std::cout << "UPSERT failed: " << result.error_message << std::endl; + return; + } + + stats->operations_attempted++; + + // GET + result = couchbase_ops.get(key, "col1"); + if (result.success) { + stats->operations_successful++; + } else { + stats->operations_failed++; + std::cout << "GET failed: " << result.error_message << std::endl; + return; + } + + stats->operations_attempted++; + + // DELETE + result = couchbase_ops.delete_(key, "col1"); + if (result.success) { + stats->operations_successful++; + } else { + stats->operations_failed++; + } +} + +// Simple thread worker function +void* thread_worker(void* arg) { + ThreadArgs* args = static_cast(arg); + + std::cout << CYAN << "Thread " << args->thread_id << " starting on bucket " + << args->bucket_name << RESET << std::endl; + + // Create CouchbaseOperations instance for this thread + brpc::CouchbaseOperations couchbase_ops; + + // Authentication + brpc::CouchbaseOperations::Result auth_result = couchbase_ops.authenticate( + g_config.username, g_config.password, "127.0.0.1:11210", args->bucket_name); + // for SSL authentication use below line instead + // brpc::CouchbaseOperations::Result auth_result = couchbase_ops.authenticateSSL(username, password, "127.0.0.1:11210", args->bucket_name, "/path/to/cert.txt"); + + if (!auth_result.success) { + std::cout << RED << "Thread " << args->thread_id << ": Auth failed - " + << auth_result.error_message << RESET << std::endl; + return NULL; + } + + // // Select bucket + // brpc::CouchbaseOperations::Result bucket_result = + // couchbase_ops.selectBucket(args->bucket_name); + + // if (!bucket_result.success) { + // std::cout << RED << "Thread " << args->thread_id + // << ": Bucket selection failed - " << bucket_result.error_message + // << RESET << std::endl; + // return NULL; + // } + + std::cout << GREEN << "Thread " << args->thread_id << " connected to bucket " + << args->bucket_name << RESET << std::endl; + + // Perform operations - 10 times on default collection, 10 times on col1 + // collection + for (int i = 0; i < 10; ++i) { + std::string base_key = + butil::string_printf("thread_%d_op_%d", args->thread_id, i); + + // CRUD operations on default collection + perform_crud_operations_default(couchbase_ops, base_key, args->stats); + + // CRUD operations on col1 collection + perform_crud_operations_col1(couchbase_ops, base_key, args->stats); + + // Small delay between operations + bthread_usleep(10000); // 10ms + } + + int successful = args->stats->operations_successful.load(); + int attempted = args->stats->operations_attempted.load(); + int failed = args->stats->operations_failed.load(); + + std::cout << GREEN << "Thread " << args->thread_id + << " completed: " << successful << "/" << attempted + << " operations successful, " << failed << " failed" << RESET + << std::endl; + + return NULL; +} + +void* shared_object_thread_worker(void *arg){ + ThreadArgs* shared_args = static_cast(arg); + brpc::CouchbaseOperations* shared_couchbase_ops = shared_args->couchbase_ops; + // Perform operations - 10 times on default collection, 10 times on col1 + // collection + for (int i = 0; i < 10; ++i) { + std::string base_key = + butil::string_printf("shared_thread_op_%d_thread_id_%d", i, shared_args->thread_id); + // CRUD operations on default collection + perform_crud_operations_default(*shared_couchbase_ops, base_key, shared_args->stats); + // CRUD operations on col1 collection + perform_crud_operations_col1(*shared_couchbase_ops, base_key, shared_args->stats); + // Small delay between operations + bthread_usleep(10000); // 10ms + } + return NULL; +} + +// Print simple statistics +void print_stats() { + g_stats.aggregate_stats(); + + std::cout << std::endl; + std::cout << CYAN << "=== TEST RESULTS ===" << RESET << std::endl; + + int total_attempted = g_stats.total.operations_attempted.load(); + int total_successful = g_stats.total.operations_successful.load(); + int total_failed = g_stats.total.operations_failed.load(); + + double success_rate = total_attempted > 0 + ? (double)total_successful / total_attempted * 100.0 + : 0.0; + + std::cout << GREEN << "Overall Performance:" << RESET << std::endl; + std::cout << " Total Operations: " << total_attempted << std::endl; + std::cout << " Successful: " << total_successful << " (" << success_rate + << "%)" << std::endl; + std::cout << " Failed: " << total_failed << std::endl; + std::cout << std::endl; + + // Per-thread breakdown + std::cout << YELLOW << "Per-Thread Performance:" << RESET << std::endl; + for (int i = 0; i < NUM_THREADS; ++i) { + const auto& stats = g_stats.per_thread_stats[i]; + int attempted = stats.operations_attempted.load(); + int successful = stats.operations_successful.load(); + int failed = stats.operations_failed.load(); + + std::cout << " Thread " << i << ": " << attempted << " ops, " << successful + << " success, " << failed << " failed" << std::endl; + } + std::cout << std::endl; +} + +int main(int argc, char* argv[]) { + std::cout << GREEN << "Starting Simple Multithreaded Couchbase Client" + << RESET << std::endl; + std::cout + << YELLOW + << "20 threads: 5 per bucket (testing0, testing1, testing2, testing3)" + << RESET << std::endl; + std::cout << BLUE + << "Each thread performs CRUD operations on default collection and " + "col1 collection" + << RESET << std::endl; + + // Create threads and arguments + std::vector threads(NUM_THREADS); + std::vector args(NUM_THREADS); + + // Assign threads to buckets + for (int i = 0; i < NUM_THREADS; ++i) { + args[i].thread_id = i; + args[i].bucket_id = i / THREADS_PER_BUCKET; + args[i].bucket_name = g_config.bucket_names[args[i].bucket_id]; + args[i].stats = &g_stats.per_thread_stats[i]; + } + + // Print thread assignments + std::cout << "Thread Assignments:" << RESET << std::endl; + for (int i = 0; i < NUM_THREADS; ++i) { + std::cout << "Thread " << i << " -> Bucket: " << args[i].bucket_name + << std::endl; + } + std::cout << std::endl; + + // Start all threads + for (int i = 0; i < NUM_THREADS; ++i) { + if (bthread_start_background(&threads[i], NULL, thread_worker, &args[i]) != + 0) { + std::cout << RED << "Failed to create thread " << i << RESET << std::endl; + return -1; + } + } + + std::cout << GREEN << "All 20 threads started!" << RESET << std::endl; + + // Wait for all threads to complete + std::cout << YELLOW << "Waiting for all threads to complete..." << RESET + << std::endl; + for (int i = 0; i < NUM_THREADS; ++i) { + bthread_join(threads[i], NULL); + } + + std::cout << GREEN << "All threads completed!" << RESET << std::endl; + // create a shared CouchbaseOperations instance + brpc::CouchbaseOperations shared_couchbase_ops; + brpc::CouchbaseOperations::Result result; + result = shared_couchbase_ops.authenticate( + g_config.username, g_config.password, "127.0.0.1:11210", "t0"); + if(result.success){ + std::cout << GREEN << "Shared CouchbaseOperations instance authenticated successfully!" << RESET << std::endl; + } else { + std::cout << RED << "Shared CouchbaseOperations instance authentication failed: " << result.error_message << RESET << std::endl; + return -1; + } + + for (int i = 0; i < NUM_THREADS; ++i) { + args[i].thread_id = i; + args[i].couchbase_ops = &shared_couchbase_ops; + args[i].bucket_id = 0; + args[i].bucket_name = "t0"; + // args[i].stats = &g_stats.per_thread_stats[i]; + } + + for(int i = 0; i < NUM_THREADS; ++i){ + if (bthread_start_background(&threads[i], NULL, shared_object_thread_worker, &args[i]) != + 0) { + std::cout << RED << "Failed to create shared object thread " << i << RESET << std::endl; + return -1; + } + } + for(int i = 0; i < NUM_THREADS; ++i){ + bthread_join(threads[i], NULL); + } + std::cout << GREEN << "All shared object threads completed!" << RESET << std::endl; + + // Print statistics + print_stats(); + + return 0; +} diff --git a/example/couchbase_c++/traditional_brpc_couchbase_client.cpp b/example/couchbase_c++/traditional_brpc_couchbase_client.cpp new file mode 100644 index 0000000..c63c98c --- /dev/null +++ b/example/couchbase_c++/traditional_brpc_couchbase_client.cpp @@ -0,0 +1,171 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 + +#include +#include + +// ANSI color codes for console output +#define GREEN "\033[32m" +#define RED "\033[31m" +#define RESET "\033[0m" + +int main() { + // traditional bRPC Couchbase client + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_COUCHBASE; + options.connection_type = "single"; + options.timeout_ms = 1000; // 1 second + options.max_retry = 3; + if (channel.Init("localhost:11210", &options) != 0) { + LOG(ERROR) << "Failed to initialize channel"; + return -1; + } + brpc::Controller cntl; + brpc::CouchbaseOperations::CouchbaseRequest req; + brpc::CouchbaseOperations::CouchbaseResponse res; + uint64_t cas; + req.authenticateRequest("Administrator", "password"); + channel.CallMethod(NULL, &cntl, &req, &res, NULL); + if (cntl.Failed()) { + LOG(ERROR) << "Unable to authenticate: Something went wrong" + << cntl.ErrorText(); + return -1; + } else { + if (res.popHello(&cas) && res.popAuthenticate(&cas)) { + std::cout << "Traditional bRPC Couchbase Client Authentication Successful" + << std::endl; + } else { + std::cout << "Client Authentication Failed with status code: " << std::hex + << res._status_code << std::endl; + return -1; + } + } + cntl.Reset(); + // clearing request and response + + req.Clear(); + res.Clear(); + req.selectBucketRequest("testing"); + channel.CallMethod(NULL, &cntl, &req, &res, NULL); + if (cntl.Failed()) { + LOG(ERROR) << "Unable to select bucket: Something went wrong" + << cntl.ErrorText(); + return -1; + } else { + if (res.popSelectBucket(&cas)) { + std::cout + << "Traditional bRPC Couchbase Client Bucket Selection Successful" + << std::endl; + } else { + // the status code will be updated only after you do + // popFunctionName(param). + std::cout << "Traditional bRPC Couchbase Client Bucket Selection Failed " + "with status code: " + << std::hex << res._status_code << std::endl; + std::cout << "Error Message: " << res.lastError() << std::endl; + return -1; + } + } + cntl.Reset(); + // clearing request and response + + req.Clear(); + res.Clear(); + req.addRequest( + "sample_key", + R"({"name": "John Doe", "age": 30, "email": "john@example.com"})", + 0 /*flags*/, 0 /*exptime*/, 0 /*cas*/); + channel.CallMethod(NULL, &cntl, &req, &res, NULL); + if (cntl.Failed()) { + LOG(ERROR) << "Unable to add key-value: Something went wrong" + << cntl.ErrorText(); + return -1; + } else { + if (res.popAdd(&cas)) { + std::cout + << "Traditional bRPC Couchbase Client Key-Value Addition Successful" + << std::endl; + } else { + // the status code will be updated only after you do + // popFunctionName(param). + std::cout << "Traditional bRPC Couchbase Client Key-Value Addition " + "Failed with status code: " + << std::hex << res._status_code << std::endl; + std::cout << "Error Message: " << res.lastError() << std::endl; + return -1; + } + } + cntl.Reset(); + + // clearing request and response before doing a getRequest + req.Clear(); + res.Clear(); + req.getRequest("sample_key"); + channel.CallMethod(NULL, &cntl, &req, &res, NULL); + if (cntl.Failed()) { + LOG(ERROR) << "Unable to get value for key: Something went wrong" + << cntl.ErrorText(); + return -1; + } else { + std::string value; + uint32_t flags; + if (res.popGet(&value, &flags, &cas)) { + std::cout + << "Traditional bRPC Couchbase Client Key-Value Retrieval Successful" + << std::endl; + std::cout << "Retrieved Value: " << value << std::endl; + } else { + // note the status code will be updated only after you do + // popFunctionName(param). + std::cout << "Traditional bRPC Couchbase Client Key-Value Retrieval " + "Failed with status code: " + << std::hex << res._status_code << std::endl; + std::cout << "Error Message: " << res.lastError() << std::endl; + return -1; + } + } + cntl.Reset(); + // clearing request and response before doing a deleteRequest + + req.Clear(); + res.Clear(); + req.deleteRequest("sample_key"); + channel.CallMethod(NULL, &cntl, &req, &res, NULL); + if (cntl.Failed()) { + LOG(ERROR) << "Unable to delete key-value: Something went wrong" + << cntl.ErrorText(); + return -1; + } else { + if (res.popDelete()) { + std::cout + << "Traditional bRPC Couchbase Client Key-Value Deletion Successful" + << std::endl; + } else { + // the status code will be updated only after you do + // popFunctionName(param). + std::cout << "Traditional bRPC Couchbase Client Key-Value Deletion " + "Failed with status code: " + << std::hex << res._status_code << std::endl; + std::cout << "Error Message: " << res.lastError() << std::endl; + return -1; + } + } + return 0; +} \ No newline at end of file diff --git a/example/dynamic_partition_echo_c++/CMakeLists.txt b/example/dynamic_partition_echo_c++/CMakeLists.txt new file mode 100644 index 0000000..df370a8 --- /dev/null +++ b/example/dynamic_partition_echo_c++/CMakeLists.txt @@ -0,0 +1,118 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(dynamic_partition_echo_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER echo.proto) + +find_path(GPERFTOOLS_INCLUDE_DIR NAMES gperftools/heap-profiler.h) +find_library(GPERFTOOLS_LIBRARIES NAMES tcmalloc_and_profiler) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +set(BRPC_EXAMPLE_ENABLE_CPU_PROFILER ON) + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(dynamic_partition_echo_client client.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(dynamic_partition_echo_client) +add_executable(dynamic_partition_echo_server server.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(dynamic_partition_echo_server) + +target_link_libraries(dynamic_partition_echo_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${GPERFTOOLS_LIBRARIES}) +target_link_libraries(dynamic_partition_echo_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${GPERFTOOLS_LIBRARIES}) + +file(COPY ${PROJECT_SOURCE_DIR}/server_list + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/example/dynamic_partition_echo_c++/client.cpp b/example/dynamic_partition_echo_c++/client.cpp new file mode 100644 index 0000000..fd25514 --- /dev/null +++ b/example/dynamic_partition_echo_c++/client.cpp @@ -0,0 +1,213 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending requests to server in parallel by multiple threads. + +#include +#include +#include +#include +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_int32(thread_num, 50, "Number of threads to send requests"); +DEFINE_bool(use_bthread, false, "Use bthread to send requests"); +DEFINE_int32(attachment_size, 0, "Carry so many byte attachment along with requests"); +DEFINE_int32(request_size, 16, "Bytes of each request"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(protocol, "baidu_std", "Protocol type. Defined in src/brpc/options.proto"); +DEFINE_string(server, "file://server_list", "Mapping to servers"); +DEFINE_string(load_balancer, "rr", "Name of load balancer"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_bool(dont_fail, false, "Print fatal when some call failed"); + +std::string g_request; +std::string g_attachment; +pthread_mutex_t g_latency_mutex = PTHREAD_MUTEX_INITIALIZER; +struct BAIDU_CACHELINE_ALIGNMENT SenderInfo { + size_t nsuccess; + int64_t latency_sum; +}; +std::deque g_sender_info; + +static void* sender(void* arg) { + // Normally, you should not call a Channel directly, but instead construct + // a stub Service wrapping it. stub can be shared by all threads as well. + example::EchoService_Stub stub(static_cast(arg)); + + SenderInfo* info = NULL; + { + BAIDU_SCOPED_LOCK(g_latency_mutex); + g_sender_info.push_back(SenderInfo()); + info = &g_sender_info.back(); + } + + int log_id = 0; + while (!brpc::IsAskedToQuit()) { + // We will receive response synchronously, safe to put variables + // on stack. + example::EchoRequest request; + example::EchoResponse response; + brpc::Controller cntl; + + request.set_message(g_request); + cntl.set_log_id(log_id++); // set by user + if (!g_attachment.empty()) { + // Set attachment which is wired to network directly instead of + // being serialized into protobuf messages. + cntl.request_attachment().append(g_attachment); + } + + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + stub.Echo(&cntl, &request, &response, NULL); + if (!cntl.Failed()) { + info->latency_sum += cntl.latency_us(); + ++info->nsuccess; + } else { + CHECK(brpc::IsAskedToQuit() || !FLAGS_dont_fail) + << "error=" << cntl.ErrorText() << " latency=" << cntl.latency_us(); + // We can't connect to the server, sleep a while. Notice that this + // is a specific sleeping to prevent this thread from spinning too + // fast. You should continue the business logic in a production + // server rather than sleeping. + bthread_usleep(50000); + } + } + return NULL; +} + +class MyPartitionParser : public brpc::PartitionParser { +public: + bool ParseFromTag(const std::string& tag, brpc::Partition* out) { + // "N/M" : #N partition of M partitions. + size_t pos = tag.find_first_of('/'); + if (pos == std::string::npos) { + LOG(ERROR) << "Invalid tag=" << tag; + return false; + } + char* endptr = NULL; + out->index = strtol(tag.c_str(), &endptr, 10); + if (endptr != tag.data() + pos) { + LOG(ERROR) << "Invalid index=" << butil::StringPiece(tag.data(), pos); + return false; + } + out->num_partition_kinds = strtol(tag.c_str() + pos + 1, &endptr, 10); + if (endptr != tag.c_str() + tag.size()) { + LOG(ERROR) << "Invalid num=" << tag.data() + pos + 1; + return false; + } + return true; + } +}; + + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::DynamicPartitionChannel channel; + + brpc::PartitionChannelOptions options; + options.protocol = FLAGS_protocol; + options.connection_type = FLAGS_connection_type; + options.succeed_without_server = true; + options.fail_limit = 1; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + + if (channel.Init(new MyPartitionParser(), + FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), + &options) != 0) { + LOG(ERROR) << "Fail to init channel"; + return -1; + } + if (FLAGS_attachment_size > 0) { + g_attachment.resize(FLAGS_attachment_size, 'a'); + } + if (FLAGS_request_size <= 0) { + LOG(ERROR) << "Bad request_size=" << FLAGS_request_size; + return -1; + } + g_request.resize(FLAGS_request_size, 'r'); + + std::vector bids; + std::vector pids; + if (!FLAGS_use_bthread) { + pids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (pthread_create(&pids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create pthread"; + return -1; + } + } + } else { + bids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (bthread_start_background( + &bids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create bthread"; + return -1; + } + } + } + + int64_t last_counter = 0; + int64_t last_latency_sum = 0; + std::vector last_nsuccess(FLAGS_thread_num); + while (!brpc::IsAskedToQuit()) { + sleep(1); + int64_t latency_sum = 0; + int64_t nsuccess = 0; + pthread_mutex_lock(&g_latency_mutex); + CHECK_EQ(g_sender_info.size(), (size_t)FLAGS_thread_num); + for (size_t i = 0; i < g_sender_info.size(); ++i) { + const SenderInfo& info = g_sender_info[i]; + latency_sum += info.latency_sum; + nsuccess += info.nsuccess; + if (FLAGS_dont_fail) { + CHECK(info.nsuccess > last_nsuccess[i]) << "i=" << i; + } + last_nsuccess[i] = info.nsuccess; + } + pthread_mutex_unlock(&g_latency_mutex); + + const int64_t avg_latency = (latency_sum - last_latency_sum) / + std::max(nsuccess - last_counter, (int64_t)1); + LOG(INFO) << "Sending EchoRequest at qps=" << nsuccess - last_counter + << " latency=" << avg_latency; + last_counter = nsuccess; + last_latency_sum = latency_sum; + } + + LOG(INFO) << "EchoClient is going to quit"; + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (!FLAGS_use_bthread) { + pthread_join(pids[i], NULL); + } else { + bthread_join(bids[i], NULL); + } + } + + return 0; +} diff --git a/example/dynamic_partition_echo_c++/echo.proto b/example/dynamic_partition_echo_c++/echo.proto new file mode 100644 index 0000000..2b39627 --- /dev/null +++ b/example/dynamic_partition_echo_c++/echo.proto @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package example; + +option cc_generic_services = true; + +message EchoRequest { + required string message = 1; +}; + +message EchoResponse { + required string message = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; diff --git a/example/dynamic_partition_echo_c++/server.cpp b/example/dynamic_partition_echo_c++/server.cpp new file mode 100644 index 0000000..eda57b4 --- /dev/null +++ b/example/dynamic_partition_echo_c++/server.cpp @@ -0,0 +1,173 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive EchoRequest and send back EchoResponse. + +#include +#include +#include +#include +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_bool(echo_attachment, true, "Echo attachment as well"); +DEFINE_int32(port, 8004, "TCP Port of this server"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); +DEFINE_int32(max_concurrency, 0, "Limit of request processing in parallel"); +DEFINE_int32(server_num, 1, "Number of servers"); +DEFINE_string(sleep_us, "", "Sleep so many microseconds before responding"); +DEFINE_bool(spin, false, "spin rather than sleep"); +DEFINE_double(exception_ratio, 0.1, "Percentage of irregular latencies"); +DEFINE_double(min_ratio, 0.2, "min_sleep / sleep_us"); +DEFINE_double(max_ratio, 10, "max_sleep / sleep_us"); + +// Your implementation of example::EchoService +class EchoServiceImpl : public example::EchoService { +public: + EchoServiceImpl() : _index(0) {} + virtual ~EchoServiceImpl() {} + void set_index(size_t index, int64_t sleep_us) { + _index = index; + _sleep_us = sleep_us; + } + virtual void Echo(google::protobuf::RpcController* cntl_base, + const example::EchoRequest* request, + example::EchoResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = + static_cast(cntl_base); + if (_sleep_us > 0) { + double delay = _sleep_us; + const double a = FLAGS_exception_ratio * 0.5; + if (a >= 0.0001) { + double x = butil::RandDouble(); + if (x < a) { + const double min_sleep_us = FLAGS_min_ratio * _sleep_us; + delay = min_sleep_us + (_sleep_us - min_sleep_us) * x / a; + } else if (x + a > 1) { + const double max_sleep_us = FLAGS_max_ratio * _sleep_us; + delay = _sleep_us + (max_sleep_us - _sleep_us) * (x + a - 1) / a; + } + } + if (FLAGS_spin) { + int64_t end_time = butil::cpuwide_time_us() + (int64_t)delay; + while (butil::cpuwide_time_us() < end_time) {} + } else { + bthread_usleep((int64_t)delay); + } + } + + // Echo request and its attachment + response->set_message(request->message()); + if (FLAGS_echo_attachment) { + cntl->response_attachment().append(cntl->request_attachment()); + } + _nreq << 1; + } + + size_t num_requests() const { return _nreq.get_value(); } + +private: + size_t _index; + int64_t _sleep_us; + bvar::Adder _nreq; +}; + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_server_num <= 0) { + LOG(ERROR) << "server_num must be positive"; + return -1; + } + + // We need multiple servers in this example. + brpc::Server* servers = new brpc::Server[FLAGS_server_num]; + // For more options see `brpc/server.h'. + brpc::ServerOptions options; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + options.max_concurrency = FLAGS_max_concurrency; + + butil::StringSplitter sp(FLAGS_sleep_us.c_str(), ','); + std::vector sleep_list; + for (; sp; ++sp) { + sleep_list.push_back(strtoll(sp.field(), NULL, 10)); + } + if (sleep_list.empty()) { + sleep_list.push_back(0); + } + + // Instance of your services. + EchoServiceImpl* echo_service_impls = new EchoServiceImpl[FLAGS_server_num]; + // Add the service into servers. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + for (int i = 0; i < FLAGS_server_num; ++i) { + int64_t sleep_us = sleep_list[(size_t)i < sleep_list.size() ? i : (sleep_list.size() - 1)]; + echo_service_impls[i].set_index(i, sleep_us); + // will be shown on /version page + servers[i].set_version(butil::string_printf( + "example/dynamic_partition_echo_c++[%d]", i)); + if (servers[i].AddService(&echo_service_impls[i], + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + // Start the server. + int port = FLAGS_port + i; + if (servers[i].Start(port, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + } + + // Service logic are running in separate worker threads, for main thread, + // we don't have much to do, just spinning. + std::vector last_num_requests(FLAGS_server_num); + while (!brpc::IsAskedToQuit()) { + sleep(1); + + size_t cur_total = 0; + for (int i = 0; i < FLAGS_server_num; ++i) { + const size_t current_num_requests = + echo_service_impls[i].num_requests(); + size_t diff = current_num_requests - last_num_requests[i]; + cur_total += diff; + last_num_requests[i] = current_num_requests; + LOG(INFO) << "S[" << i << "]=" << diff << ' ' << noflush; + } + LOG(INFO) << "[total=" << cur_total << ']'; + } + + // Don't forget to stop and join the server otherwise still-running + // worker threads may crash your program. + for (int i = 0; i < FLAGS_server_num; ++i) { + servers[i].Stop(0/*not used now*/); + } + for (int i = 0; i < FLAGS_server_num; ++i) { + servers[i].Join(); + } + delete [] servers; + delete [] echo_service_impls; + return 0; +} diff --git a/example/echo_c++/CMakeLists.txt b/example/echo_c++/CMakeLists.txt new file mode 100644 index 0000000..5250959 --- /dev/null +++ b/example/echo_c++/CMakeLists.txt @@ -0,0 +1,110 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(echo_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER echo.proto) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(echo_client client.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(echo_client) +add_executable(echo_server server.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(echo_server) + +target_link_libraries(echo_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) +target_link_libraries(echo_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) diff --git a/example/echo_c++/client.cpp b/example/echo_c++/client.cpp new file mode 100644 index 0000000..4a3aee9 --- /dev/null +++ b/example/echo_c++/client.cpp @@ -0,0 +1,97 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending requests to server every 1 second. + +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_string(attachment, "", "Carry this along with requests"); +DEFINE_string(protocol, "baidu_std", "Protocol type. Defined in src/brpc/options.proto"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(server, "0.0.0.0:8000", "IP Address of server"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_int32(interval_ms, 1000, "Milliseconds between consecutive requests"); +DEFINE_bool(enable_checksum, false, "Enable checksum or not"); + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = FLAGS_protocol; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + if (channel.Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + // Normally, you should not call a Channel directly, but instead construct + // a stub Service wrapping it. stub can be shared by all threads as well. + example::EchoService_Stub stub(&channel); + + // Send a request and wait for the response every 1 second. + int log_id = 0; + while (!brpc::IsAskedToQuit()) { + // We will receive response synchronously, safe to put variables + // on stack. + example::EchoRequest request; + example::EchoResponse response; + brpc::Controller cntl; + + request.set_message("hello world"); + + cntl.set_log_id(log_id ++); // set by user + // Set attachment which is wired to network directly instead of + // being serialized into protobuf messages. + cntl.request_attachment().append(FLAGS_attachment); + + // Use checksum, only support CRC32C now. + if (FLAGS_enable_checksum) { + cntl.set_request_checksum_type(brpc::CHECKSUM_TYPE_CRC32C); + } + + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + stub.Echo(&cntl, &request, &response, NULL); + if (!cntl.Failed()) { + LOG(INFO) << "Received response from " << cntl.remote_side() + << " to " << cntl.local_side() + << ": " << response.message() << " (attached=" + << cntl.response_attachment() << ")" + << " latency=" << cntl.latency_us() << "us"; + } else { + LOG(WARNING) << cntl.ErrorText(); + } + usleep(FLAGS_interval_ms * 1000L); + } + + LOG(INFO) << "EchoClient is going to quit"; + return 0; +} diff --git a/example/echo_c++/echo.proto b/example/echo_c++/echo.proto new file mode 100644 index 0000000..2b39627 --- /dev/null +++ b/example/echo_c++/echo.proto @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package example; + +option cc_generic_services = true; + +message EchoRequest { + required string message = 1; +}; + +message EchoResponse { + required string message = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; diff --git a/example/echo_c++/server.cpp b/example/echo_c++/server.cpp new file mode 100644 index 0000000..4113114 --- /dev/null +++ b/example/echo_c++/server.cpp @@ -0,0 +1,140 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive EchoRequest and send back EchoResponse. + +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_bool(echo_attachment, true, "Echo attachment as well"); +DEFINE_int32(port, 8000, "TCP Port of this server"); +DEFINE_string(listen_addr, "", "Server listen address, may be IPV4/IPV6/UDS." + " If this is set, the flag port will be ignored"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); +DEFINE_bool(enable_checksum, false, "Enable checksum or not"); + +// Your implementation of example::EchoService +// Notice that implementing brpc::Describable grants the ability to put +// additional information in /status. +namespace example { +class EchoServiceImpl : public EchoService { +public: + EchoServiceImpl() {} + virtual ~EchoServiceImpl() {} + virtual void Echo(google::protobuf::RpcController* cntl_base, + const EchoRequest* request, + EchoResponse* response, + google::protobuf::Closure* done) { + // This object helps you to call done->Run() in RAII style. If you need + // to process the request asynchronously, pass done_guard.release(). + brpc::ClosureGuard done_guard(done); + + brpc::Controller* cntl = + static_cast(cntl_base); + + // optional: set a callback function which is called after response is sent + // and before cntl/req/res is destructed. + cntl->set_after_rpc_resp_fn(std::bind(&EchoServiceImpl::CallAfterRpc, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + + // The purpose of following logs is to help you to understand + // how clients interact with servers more intuitively. You should + // remove these logs in performance-sensitive servers. + LOG(INFO) << "Received request[log_id=" << cntl->log_id() + << "] from " << cntl->remote_side() + << " to " << cntl->local_side() + << ": " << request->message() + << " (attached=" << cntl->request_attachment() << ")"; + + // Fill response. + response->set_message(request->message()); + + // You can compress the response by setting Controller, but be aware + // that compression may be costly, evaluate before turning on. + // cntl->set_response_compress_type(brpc::COMPRESS_TYPE_GZIP); + + if (FLAGS_echo_attachment) { + // Set attachment which is wired to network directly instead of + // being serialized into protobuf messages. + cntl->response_attachment().append(cntl->request_attachment()); + } + + // Use checksum, only support CRC32C now. + if (FLAGS_enable_checksum) { + cntl->set_response_checksum_type(brpc::CHECKSUM_TYPE_CRC32C); + } + } + + // optional + static void CallAfterRpc(brpc::Controller* cntl, + const google::protobuf::Message* req, + const google::protobuf::Message* res) { + // at this time res is already sent to client, but cntl/req/res is not destructed + std::string req_str; + std::string res_str; + json2pb::ProtoMessageToJson(*req, &req_str, NULL); + json2pb::ProtoMessageToJson(*res, &res_str, NULL); + LOG(INFO) << "req:" << req_str + << " res:" << res_str; + } +}; +} // namespace example + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // Generally you only need one Server. + brpc::Server server; + + // Instance of your service. + example::EchoServiceImpl echo_service_impl; + + // Add the service into server. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + if (server.AddService(&echo_service_impl, + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + + butil::EndPoint point; + if (!FLAGS_listen_addr.empty()) { + if (butil::str2endpoint(FLAGS_listen_addr.c_str(), &point) < 0) { + LOG(ERROR) << "Invalid listen address:" << FLAGS_listen_addr; + return -1; + } + } else { + point = butil::EndPoint(butil::IP_ANY, FLAGS_port); + } + // Start the server. + brpc::ServerOptions options; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + if (server.Start(point, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + // Wait until Ctrl-C is pressed, then Stop() and Join() the server. + server.RunUntilAskedToQuit(); + return 0; +} diff --git a/example/grpc_c++/CMakeLists.txt b/example/grpc_c++/CMakeLists.txt new file mode 100644 index 0000000..9d3340d --- /dev/null +++ b/example/grpc_c++/CMakeLists.txt @@ -0,0 +1,109 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(grpc_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER helloworld.proto) + +find_path(GPERFTOOLS_INCLUDE_DIR NAMES gperftools/heap-profiler.h) +find_library(GPERFTOOLS_LIBRARIES NAMES tcmalloc_and_profiler) + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +set(BRPC_EXAMPLE_ENABLE_CPU_PROFILER ON) + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_RegisterThriftProtocol" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(server server.cpp ${PROTO_SRC} ${PROTO_HEADER} ) +brpc_example_configure_target(server) +add_executable(client client.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(client) + +target_link_libraries(server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${GPERFTOOLS_LIBRARIES}) +target_link_libraries(client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${GPERFTOOLS_LIBRARIES}) diff --git a/example/grpc_c++/client.cpp b/example/grpc_c++/client.cpp new file mode 100644 index 0000000..4318ed8 --- /dev/null +++ b/example/grpc_c++/client.cpp @@ -0,0 +1,86 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending requests to server every 1 second using grpc. + +#include +#include +#include +#include +#include "helloworld.pb.h" + +DEFINE_string(protocol, "h2:grpc", "Protocol type. Defined in src/brpc/options.proto"); +DEFINE_string(server, "0.0.0.0:50051", "IP Address of server"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_int32(interval_ms, 1000, "Milliseconds between consecutive requests"); +DEFINE_bool(gzip, false, "compress body using gzip"); + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + if (FLAGS_gzip) { + GFLAGS_NAMESPACE::SetCommandLineOption("http_body_compress_threshold", 0); + } + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = FLAGS_protocol; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + if (channel.Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + // Normally, you should not call a Channel directly, but instead construct + // a stub Service wrapping it. stub can be shared by all threads as well. + helloworld::Greeter_Stub stub(&channel); + + // Send a request and wait for the response every 1 second. + while (!brpc::IsAskedToQuit()) { + // We will receive response synchronously, safe to put variables + // on stack. + helloworld::HelloRequest request; + helloworld::HelloReply response; + brpc::Controller cntl; + + request.set_name("grpc_req_from_brpc"); + if (FLAGS_gzip) { + cntl.set_request_compress_type(brpc::COMPRESS_TYPE_GZIP); + } + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + stub.SayHello(&cntl, &request, &response, NULL); + if (!cntl.Failed()) { + LOG(INFO) << "Received response from " << cntl.remote_side() + << " to " << cntl.local_side() + << ": " << response.message() + << " latency=" << cntl.latency_us() << "us"; + } else { + LOG(WARNING) << cntl.ErrorText(); + } + usleep(FLAGS_interval_ms * 1000L); + } + + return 0; +} diff --git a/example/grpc_c++/helloworld.proto b/example/grpc_c++/helloworld.proto new file mode 100644 index 0000000..b94d750 --- /dev/null +++ b/example/grpc_c++/helloworld.proto @@ -0,0 +1,38 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax = "proto2"; + +package helloworld; + +option cc_generic_services = true; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + required string name = 1; +} + +// The response message containing the greetings +message HelloReply { + required string message = 1; +} diff --git a/example/grpc_c++/server.cpp b/example/grpc_c++/server.cpp new file mode 100644 index 0000000..7658556 --- /dev/null +++ b/example/grpc_c++/server.cpp @@ -0,0 +1,77 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive HelloRequest and send back HelloReply + +#include +#include +#include +#include +#include "helloworld.pb.h" + +DEFINE_int32(port, 50051, "TCP Port of this server"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); +DEFINE_bool(gzip, false, "compress body using gzip"); + +class GreeterImpl : public helloworld::Greeter { +public: + GreeterImpl() {} + virtual ~GreeterImpl() {} + void SayHello(google::protobuf::RpcController* cntl_base, + const helloworld::HelloRequest* req, + helloworld::HelloReply* res, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = static_cast(cntl_base); + if (FLAGS_gzip) { + cntl->set_response_compress_type(brpc::COMPRESS_TYPE_GZIP); + } + res->set_message("Hello " + req->name()); + } +}; + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // Generally you only need one Server. + brpc::Server server; + + GreeterImpl http_svc; + + // Add services into server. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + if (server.AddService(&http_svc, + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add http_svc"; + return -1; + } + + // Start the server. + brpc::ServerOptions options; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + if (server.Start(FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to start HttpServer"; + return -1; + } + + // Wait until Ctrl-C is pressed, then Stop() and Join() the server. + server.RunUntilAskedToQuit(); + return 0; +} diff --git a/example/http_c++/CMakeLists.txt b/example/http_c++/CMakeLists.txt new file mode 100644 index 0000000..ee20fe6 --- /dev/null +++ b/example/http_c++/CMakeLists.txt @@ -0,0 +1,123 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(http_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER http.proto) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(GPERFTOOLS_INCLUDE_DIR NAMES gperftools/heap-profiler.h) +find_library(GPERFTOOLS_LIBRARIES NAMES tcmalloc_and_profiler) + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +set(BRPC_EXAMPLE_ENABLE_CPU_PROFILER ON) + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(http_client http_client.cpp) +brpc_example_configure_target(http_client) +add_executable(http_server http_server.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(http_server) +add_executable(benchmark_http benchmark_http.cpp) +brpc_example_configure_target(benchmark_http) + +target_link_libraries(http_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${GPERFTOOLS_LIBRARIES}) +target_link_libraries(http_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${GPERFTOOLS_LIBRARIES}) +target_link_libraries(benchmark_http PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${GPERFTOOLS_LIBRARIES}) + +file(COPY ${PROJECT_SOURCE_DIR}/key.pem + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +file(COPY ${PROJECT_SOURCE_DIR}/cert.pem + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/example/http_c++/benchmark_http.cpp b/example/http_c++/benchmark_http.cpp new file mode 100644 index 0000000..7fc879d --- /dev/null +++ b/example/http_c++/benchmark_http.cpp @@ -0,0 +1,135 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Benchmark http-server by multiple threads. + +#include +#include +#include +#include +#include +#include + +DEFINE_string(data, "", "POST this data to the http server"); +DEFINE_int32(thread_num, 50, "Number of threads to send requests"); +DEFINE_bool(use_bthread, false, "Use bthread to send requests"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(url, "0.0.0.0:8010/HttpService/Echo", "url of server"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_bool(dont_fail, false, "Print fatal when some call failed"); +DEFINE_int32(dummy_port, -1, "Launch dummy server at this port"); +DEFINE_string(protocol, "http", "Client-side protocol"); + +bvar::LatencyRecorder g_latency_recorder("client"); + +static void* sender(void* arg) { + brpc::Channel* channel = static_cast(arg); + + while (!brpc::IsAskedToQuit()) { + // We will receive response synchronously, safe to put variables + // on stack. + brpc::Controller cntl; + + cntl.set_timeout_ms(FLAGS_timeout_ms/*milliseconds*/); + cntl.set_max_retry(FLAGS_max_retry); + cntl.http_request().uri() = FLAGS_url; + if (!FLAGS_data.empty()) { + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append(FLAGS_data); + } + + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + channel->CallMethod(NULL, &cntl, NULL, NULL, NULL); + if (!cntl.Failed()) { + g_latency_recorder << cntl.latency_us(); + } else { + CHECK(brpc::IsAskedToQuit() || !FLAGS_dont_fail) + << "error=" << cntl.ErrorText() << " latency=" << cntl.latency_us(); + // We can't connect to the server, sleep a while. Notice that this + // is a specific sleeping to prevent this thread from spinning too + // fast. You should continue the business logic in a production + // server rather than sleeping. + bthread_usleep(100000); + } + } + return NULL; +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = FLAGS_protocol; + options.connection_type = FLAGS_connection_type; + + // Initialize the channel, NULL means using default options. + // options, see `brpc/channel.h'. + if (channel.Init(FLAGS_url.c_str(), FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + std::vector bids; + std::vector pids; + if (!FLAGS_use_bthread) { + pids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (pthread_create(&pids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create pthread"; + return -1; + } + } + } else { + bids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (bthread_start_background( + &bids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create bthread"; + return -1; + } + } + } + + if (FLAGS_dummy_port >= 0) { + brpc::StartDummyServerAt(FLAGS_dummy_port); + } + + while (!brpc::IsAskedToQuit()) { + sleep(1); + LOG(INFO) << "Sending " << FLAGS_protocol << " requests at qps=" + << g_latency_recorder.qps(1) + << " latency=" << g_latency_recorder.latency(1); + } + + LOG(INFO) << "benchmark_http is going to quit"; + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (!FLAGS_use_bthread) { + pthread_join(pids[i], NULL); + } else { + bthread_join(bids[i], NULL); + } + } + + return 0; +} diff --git a/example/http_c++/cert.pem b/example/http_c++/cert.pem new file mode 100644 index 0000000..28bcc21 --- /dev/null +++ b/example/http_c++/cert.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEUTCCAzmgAwIBAgIBADANBgkqhkiG9w0BAQQFADB9MQswCQYDVQQGEwJDTjER +MA8GA1UECBMIU2hhbmdoYWkxETAPBgNVBAcTCFNoYW5naGFpMQ4wDAYDVQQKEwVC +YWlkdTEMMAoGA1UECxMDSU5GMQwwCgYDVQQDEwNTQVQxHDAaBgkqhkiG9w0BCQEW +DXNhdEBiYWlkdS5jb20wHhcNMTUwNzE2MDMxOTUxWhcNMTgwNTA1MDMxOTUxWjB9 +MQswCQYDVQQGEwJDTjERMA8GA1UECBMIU2hhbmdoYWkxETAPBgNVBAcTCFNoYW5n +aGFpMQ4wDAYDVQQKEwVCYWlkdTEMMAoGA1UECxMDSU5GMQwwCgYDVQQDEwNTQVQx +HDAaBgkqhkiG9w0BCQEWDXNhdEBiYWlkdS5jb20wggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCqdyAeHY39tqY1RYVbfpqZjZlJDtZb04znxjgQrX+mKmLb +mwvXgJojlfn2Qcgp4NKYFqDFb9tU/Gbb436dRvkHyWOz0RPMspR0TTRU1NIY8wRy +0A1LOCgLHsbRJHqktGjylejALdgsspFWyDY9bEfb4oWsnKGzJqcvIDXrPmMOOY4o +pbA9SufSzwRZN7Yzc5jAedpaF9SK78RQXtvV0+JfCUwBsBWPKevRFFUrN7rQBYjP +cgV/HgDuquPrqnESVSYyfEBKZba6cmNb+xzO3cB1brPTtobSXh+0o/0CtRA+2m63 +ODexxCLntgkPm42IYCJLM15xTatcfVX/3LHQ31DrAgMBAAGjgdswgdgwHQYDVR0O +BBYEFGcd7lA//bSAoSC/NbWRx/H+O1zpMIGoBgNVHSMEgaAwgZ2AFGcd7lA//bSA +oSC/NbWRx/H+O1zpoYGBpH8wfTELMAkGA1UEBhMCQ04xETAPBgNVBAgTCFNoYW5n +aGFpMREwDwYDVQQHEwhTaGFuZ2hhaTEOMAwGA1UEChMFQmFpZHUxDDAKBgNVBAsT +A0lORjEMMAoGA1UEAxMDU0FUMRwwGgYJKoZIhvcNAQkBFg1zYXRAYmFpZHUuY29t +ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBAKfoCn8SpLk3uQyT +X+oygcRWfTeJtN3D5J69NCMJ7wB+QPfpEBPwiqMgdbp4bRJ98H7x5UQsHT+EDOT/ +9OmipomHInFY4W1ew11zNKwuENeRrnZwTcCiVLZsxZsAU41ZeI5Yq+2WdtxnePCR +VL1/NjKOq+WoRdb2nLSNDWgYMkLRVlt32hyzryyrBbmaxUl8BxnPqUiWduMwsZUz +HNpXkoa1xTSd+En1SHYWfMg8BOVuV0I0/fjUUG9AXVqYpuogfbjAvibVNWAmxOfo +fOjCPCGoJC1ET3AxYkgXGwioobz0pK/13k2pV+wu7W4g+6iTfz+hwZbPsUk2a/5I +f6vXFB0= +-----END CERTIFICATE----- diff --git a/example/http_c++/http.proto b/example/http_c++/http.proto new file mode 100644 index 0000000..8581294 --- /dev/null +++ b/example/http_c++/http.proto @@ -0,0 +1,43 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package example; + +option cc_generic_services = true; + +message HttpRequest {}; +message HttpResponse {}; + +service HttpService { + rpc Echo(HttpRequest) returns (HttpResponse); + rpc EchoProtobuf(HttpRequest) returns (HttpResponse); +}; + +service FileService { + rpc default_method(HttpRequest) returns (HttpResponse); +}; + +service QueueService { + rpc start(HttpRequest) returns (HttpResponse); + rpc stop(HttpRequest) returns (HttpResponse); + rpc getstats(HttpRequest) returns (HttpResponse); +}; + +service HttpSSEService { + rpc stream(HttpRequest) returns (HttpResponse); +}; diff --git a/example/http_c++/http_client.cpp b/example/http_c++/http_client.cpp new file mode 100644 index 0000000..23222de --- /dev/null +++ b/example/http_c++/http_client.cpp @@ -0,0 +1,86 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// - Access pb services via HTTP +// ./http_client http://www.foo.com:8765/EchoService/Echo -d '{"message":"hello"}' +// - Access builtin services +// ./http_client http://www.foo.com:8765/vars/rpc_server* +// - Access www.foo.com +// ./http_client www.foo.com + +#include +#include +#include + +DEFINE_string(d, "", "POST this data to the http server"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_int32(timeout_ms, 2000, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_string(protocol, "http", "Client-side protocol"); + +namespace brpc { +DECLARE_bool(http_verbose); +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + if (argc != 2) { + LOG(ERROR) << "Usage: ./http_client \"http(s)://www.foo.com\""; + return -1; + } + char* url = argv[1]; + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = FLAGS_protocol; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + + // Initialize the channel, NULL means using default options. + // options, see `brpc/channel.h'. + if (channel.Init(url, FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + // We will receive response synchronously, safe to put variables + // on stack. + brpc::Controller cntl; + + cntl.http_request().uri() = url; + if (!FLAGS_d.empty()) { + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append(FLAGS_d); + } + + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + if (cntl.Failed()) { + std::cerr << cntl.ErrorText() << std::endl; + return -1; + } + // If -http_verbose is on, brpc already prints the response to stderr. + if (!brpc::FLAGS_http_verbose) { + std::cout << cntl.response_attachment() << std::endl; + } + return 0; +} diff --git a/example/http_c++/http_server.cpp b/example/http_c++/http_server.cpp new file mode 100644 index 0000000..05c9a0e --- /dev/null +++ b/example/http_c++/http_server.cpp @@ -0,0 +1,279 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive HttpRequest and send back HttpResponse. + +#include +#include +#include +#include +#include +#include "http.pb.h" + +DEFINE_int32(port, 8010, "TCP Port of this server"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); + +DEFINE_string(certificate, "cert.pem", "Certificate file path to enable SSL"); +DEFINE_string(private_key, "key.pem", "Private key file path to enable SSL"); +DEFINE_string(ciphers, "", "Cipher suite used for SSL connections"); + +namespace example { + +// Service with static path. +class HttpServiceImpl : public HttpService { +public: + HttpServiceImpl() {} + virtual ~HttpServiceImpl() {} + void Echo(google::protobuf::RpcController* cntl_base, + const HttpRequest*, + HttpResponse*, + google::protobuf::Closure* done) { + // This object helps you to call done->Run() in RAII style. If you need + // to process the request asynchronously, pass done_guard.release(). + brpc::ClosureGuard done_guard(done); + + brpc::Controller* cntl = + static_cast(cntl_base); + + // optional: set a callback function which is called after response is sent + // and before cntl/req/res is destructed. + cntl->set_after_rpc_resp_fn(std::bind(&HttpServiceImpl::CallAfterRpc, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + + // Fill response. + cntl->http_response().set_content_type("text/plain"); + butil::IOBufBuilder os; + os << "queries:"; + for (brpc::URI::QueryIterator it = cntl->http_request().uri().QueryBegin(); + it != cntl->http_request().uri().QueryEnd(); ++it) { + os << ' ' << it->first << '=' << it->second; + } + os << "\nbody: " << cntl->request_attachment() << '\n'; + os.move_to(cntl->response_attachment()); + } + + // optional + static void CallAfterRpc(brpc::Controller* cntl, + const google::protobuf::Message* req, + const google::protobuf::Message* res) { + // at this time res is already sent to client, but cntl/req/res is not destructed + std::string req_str; + std::string res_str; + json2pb::ProtoMessageToJson(*req, &req_str, NULL); + json2pb::ProtoMessageToJson(*res, &res_str, NULL); + LOG(INFO) << "req:" << req_str + << " res:" << res_str; + } +}; + +// Service with dynamic path. +class FileServiceImpl : public FileService { +public: + FileServiceImpl() {} + virtual ~FileServiceImpl() {} + + struct Args { + butil::intrusive_ptr pa; + }; + + static void* SendLargeFile(void* raw_args) { + std::unique_ptr args(static_cast(raw_args)); + if (args->pa == NULL) { + LOG(ERROR) << "ProgressiveAttachment is NULL"; + return NULL; + } + for (int i = 0; i < 100; ++i) { + char buf[16]; + int len = snprintf(buf, sizeof(buf), "part_%d ", i); + args->pa->Write(buf, len); + + // sleep a while to send another part. + bthread_usleep(10000); + } + return NULL; + } + + void default_method(google::protobuf::RpcController* cntl_base, + const HttpRequest*, + HttpResponse*, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = + static_cast(cntl_base); + const std::string& filename = cntl->http_request().unresolved_path(); + if (filename == "largefile") { + // Send the "largefile" with ProgressiveAttachment. + std::unique_ptr args(new Args); + args->pa = cntl->CreateProgressiveAttachment(); + bthread_t th; + bthread_start_background(&th, NULL, SendLargeFile, args.release()); + } else { + cntl->response_attachment().append("Getting file: "); + cntl->response_attachment().append(filename); + } + } +}; + +// Restful service. (The service implementation is exactly same with regular +// services, the difference is that you need to pass a `restful_mappings' +// when adding the service into server). +class QueueServiceImpl : public example::QueueService { +public: + QueueServiceImpl() {} + virtual ~QueueServiceImpl() {} + void start(google::protobuf::RpcController* cntl_base, + const HttpRequest*, + HttpResponse*, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = + static_cast(cntl_base); + cntl->response_attachment().append("queue started"); + } + void stop(google::protobuf::RpcController* cntl_base, + const HttpRequest*, + HttpResponse*, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = + static_cast(cntl_base); + cntl->response_attachment().append("queue stopped"); + } + void getstats(google::protobuf::RpcController* cntl_base, + const HttpRequest*, + HttpResponse*, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = + static_cast(cntl_base); + const std::string& unresolved_path = cntl->http_request().unresolved_path(); + if (unresolved_path.empty()) { + cntl->response_attachment().append("Require a name after /stats"); + } else { + cntl->response_attachment().append("Get stats: "); + cntl->response_attachment().append(unresolved_path); + } + } +}; + +class HttpSSEServiceImpl : public HttpSSEService { +public: + HttpSSEServiceImpl() {} + virtual ~HttpSSEServiceImpl() {} + + struct PredictJobArgs { + std::vector input_ids; + butil::intrusive_ptr pa; + }; + + static void* Predict(void* raw_args) { + std::unique_ptr args(static_cast(raw_args)); + if (args->pa == NULL) { + LOG(ERROR) << "ProgressiveAttachment is NULL"; + return NULL; + } + for (int i = 0; i < 100; ++i) { + char buf[48]; + int len = snprintf(buf, sizeof(buf), "event: foo\ndata: Hello, world! (%d)\n\n", i); + args->pa->Write(buf, len); + + // sleep a while to send another part. + bthread_usleep(10000 * 10); + } + return NULL; + } + + void stream(google::protobuf::RpcController* cntl_base, + const HttpRequest*, + HttpResponse*, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = + static_cast(cntl_base); + + // Send the first SSE response + cntl->http_response().set_content_type("text/event-stream"); + cntl->http_response().set_status_code(200); + cntl->http_response().SetHeader("Connection", "keep-alive"); + cntl->http_response().SetHeader("Cache-Control", "no-cache"); + + // Send the generated words with progressiveAttachment + std::unique_ptr args(new PredictJobArgs); + args->pa = cntl->CreateProgressiveAttachment(); + args->input_ids = {101, 102}; + bthread_t th; + bthread_start_background(&th, NULL, Predict, args.release()); + } +}; + +} // namespace example + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // Generally you only need one Server. + brpc::Server server; + + example::HttpServiceImpl http_svc; + example::FileServiceImpl file_svc; + example::QueueServiceImpl queue_svc; + example::HttpSSEServiceImpl sse_svc; + + // Add services into server. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + if (server.AddService(&http_svc, + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add http_svc"; + return -1; + } + if (server.AddService(&file_svc, + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add file_svc"; + return -1; + } + if (server.AddService(&queue_svc, + brpc::SERVER_DOESNT_OWN_SERVICE, + "/v1/queue/start => start," + "/v1/queue/stop => stop," + "/v1/queue/stats/* => getstats") != 0) { + LOG(ERROR) << "Fail to add queue_svc"; + return -1; + } + if (server.AddService(&sse_svc, + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add sse_svc"; + return -1; + } + + // Start the server. + brpc::ServerOptions options; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + options.mutable_ssl_options()->default_cert.certificate = FLAGS_certificate; + options.mutable_ssl_options()->default_cert.private_key = FLAGS_private_key; + options.mutable_ssl_options()->ciphers = FLAGS_ciphers; + if (server.Start(FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to start HttpServer"; + return -1; + } + + // Wait until Ctrl-C is pressed, then Stop() and Join() the server. + server.RunUntilAskedToQuit(); + return 0; +} diff --git a/example/http_c++/key.pem b/example/http_c++/key.pem new file mode 100644 index 0000000..e3f64d1 --- /dev/null +++ b/example/http_c++/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAqncgHh2N/bamNUWFW36amY2ZSQ7WW9OM58Y4EK1/pipi25sL +14CaI5X59kHIKeDSmBagxW/bVPxm2+N+nUb5B8ljs9ETzLKUdE00VNTSGPMEctAN +SzgoCx7G0SR6pLRo8pXowC3YLLKRVsg2PWxH2+KFrJyhsyanLyA16z5jDjmOKKWw +PUrn0s8EWTe2M3OYwHnaWhfUiu/EUF7b1dPiXwlMAbAVjynr0RRVKze60AWIz3IF +fx4A7qrj66pxElUmMnxASmW2unJjW/sczt3AdW6z07aG0l4ftKP9ArUQPtputzg3 +scQi57YJD5uNiGAiSzNecU2rXH1V/9yx0N9Q6wIDAQABAoIBADN3khflnnhKzDXr +To9IU08nRG+dbjT9U16rJ0RJze+SfpSFZHblWiSCZJzoUZHrUkofEt1pn1QyfK/J +KPI9enTSZirlZk/4XwAaS0GNm/1yahZsIIdkZhqtaSO+GtVdrw4HGuXjMZCVPXJx +MocrCSsnYmqyQ9P+SJ3e4Mis5mVllwDiUVlnTIamSSt16qkPdamLSJrxvI4LirQK +9MZWNLoDFpRU1MJxQ/QzrEC3ONTq4j++AfbGzYTmDDtLeM8OSH5o72YXZ2JkaA4c +xCzHFT+NaJYxF7esn/ctzGg50LYl8IF2UQtzOkX2l3l/OktIB1w+jGV6ONb1EWx5 +4zkkzNkCgYEA2EXj7GMsyNE3OYdMw8zrqQKUMON2CNnD+mBseGlr22/bhXtzpqK8 +uNel8WF1ezOnVvNsU8pml/W/mKUu6KQt5JfaDzen3OKjzTABVlbJxwFhPvwAeaIA +q/tmSKyqiCgOMbR7Cq4UEwGf2A9/RII4JEC0/aipRU5srF65OYPUOJcCgYEAycco +DFVG6jUw9w68t/X4f7NT4IYP96hSAqLUPuVz2fWwXKLWEX8JiMI+Ue3PbMz6mPcs +4vMu364u4R3IuzrrI+PRK9iTa/pahBP6eF6ZpbY1ObI8CVLTrqUS9p22rr9lBm8V +EZA9hwcHLYt+PWzaKcsFpbP4+AeY7nBBbL9CAM0CgYAzuJsmeB1ItUgIuQOxu7sM +AzLfcjZTLYkBwreOIGAL7XdJN9nTmw2ZAvGLhWwsF5FIaRSaAUiBxOKaJb7PIhxb +k7kxdHTvjT/xHS7ksAK3VewkvO18KTMR7iBq9ugdgb7LQkc+qZzhYr0QVbxw7Ndy +TAs8sm4wxe2VV13ilFVXZwKBgDfU6ZnwBr1Llo7l/wYQA4CiSDU6IzTt2DNuhrgY +mWPX/cLEM+OHeUXkKYZV/S0n0rd8vWjWzUOLWOFlcmOMPAAkS36MYM5h6aXeOVIR +KwaVUkjyrnYN+xC6EHM41JGp1/RdzECd3sh8A1pw3K92bS9fQ+LD18IZqBFh8lh6 +23KJAoGAe48SwAsaGvqRO61Taww/Wf+YpGc9lnVbCvNFGScYaycPMqaRBUBmz/U3 +QQgpQY8T7JIECbA8sf78SlAZ9x93r0UQ70RekV3WzKAQHfHK8nqTjd3T0+i4aySO +yQpYYCgE24zYO6rQgwrhzI0S4rWe7izDDlg0RmLtQh7Xw+rlkAQ= +-----END RSA PRIVATE KEY----- diff --git a/example/memcache_c++/CMakeLists.txt b/example/memcache_c++/CMakeLists.txt new file mode 100644 index 0000000..658e602 --- /dev/null +++ b/example/memcache_c++/CMakeLists.txt @@ -0,0 +1,107 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(memcache_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER echo.proto) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(memcache_client client.cpp) +brpc_example_configure_target(memcache_client) + +target_link_libraries(memcache_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) diff --git a/example/memcache_c++/client.cpp b/example/memcache_c++/client.cpp new file mode 100644 index 0000000..5f32198 --- /dev/null +++ b/example/memcache_c++/client.cpp @@ -0,0 +1,204 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A multi-threaded client getting keys from a memcache server constantly. + +#include +#include +#include +#include +#include +#include +#include + +DEFINE_int32(thread_num, 10, "Number of threads to send requests"); +DEFINE_bool(use_bthread, false, "Use bthread to send requests"); +DEFINE_bool(use_couchbase, false, "Use couchbase."); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(server, "0.0.0.0:11211", "IP Address of server"); +DEFINE_string(bucket_name, "", "Couchbase bucktet name"); +DEFINE_string(bucket_password, "", "Couchbase bucket password"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_bool(dont_fail, false, "Print fatal when some call failed"); +DEFINE_int32(exptime, 0, "The to-be-got data will be expired after so many seconds"); +DEFINE_string(key, "hello", "The key to be get"); +DEFINE_string(value, "world", "The value associated with the key"); +DEFINE_int32(batch, 1, "Pipelined Operations"); + +bvar::LatencyRecorder g_latency_recorder("client"); +bvar::Adder g_error_count("client_error_count"); +butil::static_atomic g_sender_count = BUTIL_STATIC_ATOMIC_INIT(0); + +static void* sender(void* arg) { + google::protobuf::RpcChannel* channel = + static_cast(arg); + const int base_index = g_sender_count.fetch_add(1, butil::memory_order_relaxed); + + std::string value; + std::vector > kvs; + kvs.resize(FLAGS_batch); + for (int i = 0; i < FLAGS_batch; ++i) { + kvs[i].first = butil::string_printf("%s%d", FLAGS_key.c_str(), base_index + i); + kvs[i].second = butil::string_printf("%s%d", FLAGS_value.c_str(), base_index + i); + } + brpc::MemcacheRequest request; + for (int i = 0; i < FLAGS_batch; ++i) { + CHECK(request.Get(kvs[i].first)); + } + while (!brpc::IsAskedToQuit()) { + // We will receive response synchronously, safe to put variables + // on stack. + brpc::MemcacheResponse response; + brpc::Controller cntl; + + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + channel->CallMethod(NULL, &cntl, &request, &response, NULL); + const int64_t elp = cntl.latency_us(); + if (!cntl.Failed()) { + g_latency_recorder << cntl.latency_us(); + for (int i = 0; i < FLAGS_batch; ++i) { + uint32_t flags; + if (!response.PopGet(&value, &flags, NULL)) { + LOG(INFO) << "Fail to GET the key, " << response.LastError(); + brpc::AskToQuit(); + return NULL; + } + CHECK(flags == 0xdeadbeef + base_index + i) + << "flags=" << flags; + CHECK(kvs[i].second == value) + << "base=" << base_index << " i=" << i << " value=" << value; + } + } else { + g_error_count << 1; + CHECK(brpc::IsAskedToQuit() || !FLAGS_dont_fail) + << "error=" << cntl.ErrorText() << " latency=" << elp; + // We can't connect to the server, sleep a while. Notice that this + // is a specific sleeping to prevent this thread from spinning too + // fast. You should continue the business logic in a production + // server rather than sleeping. + bthread_usleep(50000); + } + } + return NULL; +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + if (FLAGS_exptime < 0) { + FLAGS_exptime = 0; + } + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_MEMCACHE; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + if (FLAGS_use_couchbase && !FLAGS_bucket_name.empty()) { + brpc::policy::CouchbaseAuthenticator* auth = + new brpc::policy::CouchbaseAuthenticator(FLAGS_bucket_name, + FLAGS_bucket_password); + options.auth = auth; + } + + if (channel.Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + // Pipeline #batch * #thread_num SET requests into memcache so that we + // have keys to get. + brpc::MemcacheRequest request; + brpc::MemcacheResponse response; + brpc::Controller cntl; + for (int i = 0; i < FLAGS_batch * FLAGS_thread_num; ++i) { + if (!request.Set(butil::string_printf("%s%d", FLAGS_key.c_str(), i), + butil::string_printf("%s%d", FLAGS_value.c_str(), i), + 0xdeadbeef + i, FLAGS_exptime, 0)) { + LOG(ERROR) << "Fail to SET " << i << "th request"; + return -1; + } + } + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + if (cntl.Failed()) { + LOG(ERROR) << "Fail to access memcache, " << cntl.ErrorText(); + return -1; + } + for (int i = 0; i < FLAGS_batch * FLAGS_thread_num; ++i) { + if (!response.PopSet(NULL)) { + LOG(ERROR) << "Fail to SET memcache, i=" << i + << ", " << response.LastError(); + return -1; + } + } + if (FLAGS_exptime > 0) { + LOG(INFO) << "Set " << FLAGS_batch * FLAGS_thread_num + << " values, expired after " << FLAGS_exptime << " seconds"; + } else { + LOG(INFO) << "Set " << FLAGS_batch * FLAGS_thread_num + << " values, never expired"; + } + + std::vector bids; + std::vector pids; + if (!FLAGS_use_bthread) { + pids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (pthread_create(&pids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create pthread"; + return -1; + } + } + } else { + bids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (bthread_start_background( + &bids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create bthread"; + return -1; + } + } + } + + while (!brpc::IsAskedToQuit()) { + sleep(1); + LOG(INFO) << "Accessing memcache server at qps=" << g_latency_recorder.qps(1) + << " latency=" << g_latency_recorder.latency(1); + } + + LOG(INFO) << "memcache_client is going to quit"; + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (!FLAGS_use_bthread) { + pthread_join(pids[i], NULL); + } else { + bthread_join(bids[i], NULL); + } + } + if (options.auth) { + delete options.auth; + } + + return 0; +} diff --git a/example/multi_threaded_echo_c++/CMakeLists.txt b/example/multi_threaded_echo_c++/CMakeLists.txt new file mode 100644 index 0000000..1c17fa9 --- /dev/null +++ b/example/multi_threaded_echo_c++/CMakeLists.txt @@ -0,0 +1,120 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(multi_threaded_echo_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER echo.proto) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(GPERFTOOLS_INCLUDE_DIR NAMES gperftools/heap-profiler.h) +find_library(GPERFTOOLS_LIBRARIES NAMES tcmalloc_and_profiler) + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +set(BRPC_EXAMPLE_ENABLE_CPU_PROFILER ON) + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(echo_client client.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(echo_client) +add_executable(echo_server server.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(echo_server) + +target_link_libraries(echo_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${GPERFTOOLS_LIBRARIES}) +target_link_libraries(echo_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${GPERFTOOLS_LIBRARIES}) + +file(COPY ${PROJECT_SOURCE_DIR}/key.pem + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +file(COPY ${PROJECT_SOURCE_DIR}/cert.pem + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/example/multi_threaded_echo_c++/cert.pem b/example/multi_threaded_echo_c++/cert.pem new file mode 100644 index 0000000..28bcc21 --- /dev/null +++ b/example/multi_threaded_echo_c++/cert.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEUTCCAzmgAwIBAgIBADANBgkqhkiG9w0BAQQFADB9MQswCQYDVQQGEwJDTjER +MA8GA1UECBMIU2hhbmdoYWkxETAPBgNVBAcTCFNoYW5naGFpMQ4wDAYDVQQKEwVC +YWlkdTEMMAoGA1UECxMDSU5GMQwwCgYDVQQDEwNTQVQxHDAaBgkqhkiG9w0BCQEW +DXNhdEBiYWlkdS5jb20wHhcNMTUwNzE2MDMxOTUxWhcNMTgwNTA1MDMxOTUxWjB9 +MQswCQYDVQQGEwJDTjERMA8GA1UECBMIU2hhbmdoYWkxETAPBgNVBAcTCFNoYW5n +aGFpMQ4wDAYDVQQKEwVCYWlkdTEMMAoGA1UECxMDSU5GMQwwCgYDVQQDEwNTQVQx +HDAaBgkqhkiG9w0BCQEWDXNhdEBiYWlkdS5jb20wggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCqdyAeHY39tqY1RYVbfpqZjZlJDtZb04znxjgQrX+mKmLb +mwvXgJojlfn2Qcgp4NKYFqDFb9tU/Gbb436dRvkHyWOz0RPMspR0TTRU1NIY8wRy +0A1LOCgLHsbRJHqktGjylejALdgsspFWyDY9bEfb4oWsnKGzJqcvIDXrPmMOOY4o +pbA9SufSzwRZN7Yzc5jAedpaF9SK78RQXtvV0+JfCUwBsBWPKevRFFUrN7rQBYjP +cgV/HgDuquPrqnESVSYyfEBKZba6cmNb+xzO3cB1brPTtobSXh+0o/0CtRA+2m63 +ODexxCLntgkPm42IYCJLM15xTatcfVX/3LHQ31DrAgMBAAGjgdswgdgwHQYDVR0O +BBYEFGcd7lA//bSAoSC/NbWRx/H+O1zpMIGoBgNVHSMEgaAwgZ2AFGcd7lA//bSA +oSC/NbWRx/H+O1zpoYGBpH8wfTELMAkGA1UEBhMCQ04xETAPBgNVBAgTCFNoYW5n +aGFpMREwDwYDVQQHEwhTaGFuZ2hhaTEOMAwGA1UEChMFQmFpZHUxDDAKBgNVBAsT +A0lORjEMMAoGA1UEAxMDU0FUMRwwGgYJKoZIhvcNAQkBFg1zYXRAYmFpZHUuY29t +ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBAKfoCn8SpLk3uQyT +X+oygcRWfTeJtN3D5J69NCMJ7wB+QPfpEBPwiqMgdbp4bRJ98H7x5UQsHT+EDOT/ +9OmipomHInFY4W1ew11zNKwuENeRrnZwTcCiVLZsxZsAU41ZeI5Yq+2WdtxnePCR +VL1/NjKOq+WoRdb2nLSNDWgYMkLRVlt32hyzryyrBbmaxUl8BxnPqUiWduMwsZUz +HNpXkoa1xTSd+En1SHYWfMg8BOVuV0I0/fjUUG9AXVqYpuogfbjAvibVNWAmxOfo +fOjCPCGoJC1ET3AxYkgXGwioobz0pK/13k2pV+wu7W4g+6iTfz+hwZbPsUk2a/5I +f6vXFB0= +-----END CERTIFICATE----- diff --git a/example/multi_threaded_echo_c++/client.cpp b/example/multi_threaded_echo_c++/client.cpp new file mode 100644 index 0000000..fa9768c --- /dev/null +++ b/example/multi_threaded_echo_c++/client.cpp @@ -0,0 +1,159 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending requests to server by multiple threads. + +#include +#include +#include +#include +#include +#include "echo.pb.h" +#include + +DEFINE_int32(thread_num, 50, "Number of threads to send requests"); +DEFINE_bool(use_bthread, false, "Use bthread to send requests"); +DEFINE_int32(attachment_size, 0, "Carry so many byte attachment along with requests"); +DEFINE_int32(request_size, 16, "Bytes of each request"); +DEFINE_string(protocol, "baidu_std", "Protocol type. Defined in src/brpc/options.proto"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(server, "0.0.0.0:8002", "IP Address of server"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_bool(dont_fail, false, "Print fatal when some call failed"); +DEFINE_bool(enable_ssl, false, "Use SSL connection"); +DEFINE_int32(dummy_port, -1, "Launch dummy server at this port"); + +std::string g_request; +std::string g_attachment; + +bvar::LatencyRecorder g_latency_recorder("client"); +bvar::Adder g_error_count("client_error_count"); + +static void* sender(void* arg) { + // Normally, you should not call a Channel directly, but instead construct + // a stub Service wrapping it. stub can be shared by all threads as well. + example::EchoService_Stub stub(static_cast(arg)); + + int log_id = 0; + while (!brpc::IsAskedToQuit()) { + // We will receive response synchronously, safe to put variables + // on stack. + example::EchoRequest request; + example::EchoResponse response; + brpc::Controller cntl; + + request.set_message(g_request); + cntl.set_log_id(log_id++); // set by user + // Set attachment which is wired to network directly instead of + // being serialized into protobuf messages. + cntl.request_attachment().append(g_attachment); + + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + stub.Echo(&cntl, &request, &response, NULL); + if (!cntl.Failed()) { + g_latency_recorder << cntl.latency_us(); + } else { + g_error_count << 1; + CHECK(brpc::IsAskedToQuit() || !FLAGS_dont_fail) + << "error=" << cntl.ErrorText() << " latency=" << cntl.latency_us(); + // We can't connect to the server, sleep a while. Notice that this + // is a specific sleeping to prevent this thread from spinning too + // fast. You should continue the business logic in a production + // server rather than sleeping. + bthread_usleep(50000); + } + } + return NULL; +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + if (FLAGS_enable_ssl) { + options.mutable_ssl_options(); + } + options.protocol = FLAGS_protocol; + options.connection_type = FLAGS_connection_type; + options.connect_timeout_ms = std::min(FLAGS_timeout_ms / 2, 100); + options.timeout_ms = FLAGS_timeout_ms; + options.max_retry = FLAGS_max_retry; + if (channel.Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + if (FLAGS_attachment_size > 0) { + g_attachment.resize(FLAGS_attachment_size, 'a'); + } + if (FLAGS_request_size <= 0) { + LOG(ERROR) << "Bad request_size=" << FLAGS_request_size; + return -1; + } + g_request.resize(FLAGS_request_size, 'r'); + + if (FLAGS_dummy_port >= 0) { + brpc::StartDummyServerAt(FLAGS_dummy_port); + } + + std::vector bids; + std::vector pids; + if (!FLAGS_use_bthread) { + pids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (pthread_create(&pids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create pthread"; + return -1; + } + } + } else { + bids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (bthread_start_background( + &bids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create bthread"; + return -1; + } + } + } + + while (!brpc::IsAskedToQuit()) { + sleep(1); + LOG(INFO) << "Sending EchoRequest at qps=" << g_latency_recorder.qps(1) + << " latency=" << g_latency_recorder.latency(1); + } + + LOG(INFO) << "EchoClient is going to quit"; + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (!FLAGS_use_bthread) { + pthread_join(pids[i], NULL); + } else { + bthread_join(bids[i], NULL); + } + } + + return 0; +} diff --git a/example/multi_threaded_echo_c++/echo.proto b/example/multi_threaded_echo_c++/echo.proto new file mode 100644 index 0000000..e963faf --- /dev/null +++ b/example/multi_threaded_echo_c++/echo.proto @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +option cc_generic_services = true; + +package example; + +message EchoRequest { + required string message = 1; +}; + +message EchoResponse { + required string message = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; diff --git a/example/multi_threaded_echo_c++/key.pem b/example/multi_threaded_echo_c++/key.pem new file mode 100644 index 0000000..e3f64d1 --- /dev/null +++ b/example/multi_threaded_echo_c++/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAqncgHh2N/bamNUWFW36amY2ZSQ7WW9OM58Y4EK1/pipi25sL +14CaI5X59kHIKeDSmBagxW/bVPxm2+N+nUb5B8ljs9ETzLKUdE00VNTSGPMEctAN +SzgoCx7G0SR6pLRo8pXowC3YLLKRVsg2PWxH2+KFrJyhsyanLyA16z5jDjmOKKWw +PUrn0s8EWTe2M3OYwHnaWhfUiu/EUF7b1dPiXwlMAbAVjynr0RRVKze60AWIz3IF +fx4A7qrj66pxElUmMnxASmW2unJjW/sczt3AdW6z07aG0l4ftKP9ArUQPtputzg3 +scQi57YJD5uNiGAiSzNecU2rXH1V/9yx0N9Q6wIDAQABAoIBADN3khflnnhKzDXr +To9IU08nRG+dbjT9U16rJ0RJze+SfpSFZHblWiSCZJzoUZHrUkofEt1pn1QyfK/J +KPI9enTSZirlZk/4XwAaS0GNm/1yahZsIIdkZhqtaSO+GtVdrw4HGuXjMZCVPXJx +MocrCSsnYmqyQ9P+SJ3e4Mis5mVllwDiUVlnTIamSSt16qkPdamLSJrxvI4LirQK +9MZWNLoDFpRU1MJxQ/QzrEC3ONTq4j++AfbGzYTmDDtLeM8OSH5o72YXZ2JkaA4c +xCzHFT+NaJYxF7esn/ctzGg50LYl8IF2UQtzOkX2l3l/OktIB1w+jGV6ONb1EWx5 +4zkkzNkCgYEA2EXj7GMsyNE3OYdMw8zrqQKUMON2CNnD+mBseGlr22/bhXtzpqK8 +uNel8WF1ezOnVvNsU8pml/W/mKUu6KQt5JfaDzen3OKjzTABVlbJxwFhPvwAeaIA +q/tmSKyqiCgOMbR7Cq4UEwGf2A9/RII4JEC0/aipRU5srF65OYPUOJcCgYEAycco +DFVG6jUw9w68t/X4f7NT4IYP96hSAqLUPuVz2fWwXKLWEX8JiMI+Ue3PbMz6mPcs +4vMu364u4R3IuzrrI+PRK9iTa/pahBP6eF6ZpbY1ObI8CVLTrqUS9p22rr9lBm8V +EZA9hwcHLYt+PWzaKcsFpbP4+AeY7nBBbL9CAM0CgYAzuJsmeB1ItUgIuQOxu7sM +AzLfcjZTLYkBwreOIGAL7XdJN9nTmw2ZAvGLhWwsF5FIaRSaAUiBxOKaJb7PIhxb +k7kxdHTvjT/xHS7ksAK3VewkvO18KTMR7iBq9ugdgb7LQkc+qZzhYr0QVbxw7Ndy +TAs8sm4wxe2VV13ilFVXZwKBgDfU6ZnwBr1Llo7l/wYQA4CiSDU6IzTt2DNuhrgY +mWPX/cLEM+OHeUXkKYZV/S0n0rd8vWjWzUOLWOFlcmOMPAAkS36MYM5h6aXeOVIR +KwaVUkjyrnYN+xC6EHM41JGp1/RdzECd3sh8A1pw3K92bS9fQ+LD18IZqBFh8lh6 +23KJAoGAe48SwAsaGvqRO61Taww/Wf+YpGc9lnVbCvNFGScYaycPMqaRBUBmz/U3 +QQgpQY8T7JIECbA8sf78SlAZ9x93r0UQ70RekV3WzKAQHfHK8nqTjd3T0+i4aySO +yQpYYCgE24zYO6rQgwrhzI0S4rWe7izDDlg0RmLtQh7Xw+rlkAQ= +-----END RSA PRIVATE KEY----- diff --git a/example/multi_threaded_echo_c++/server.cpp b/example/multi_threaded_echo_c++/server.cpp new file mode 100644 index 0000000..54ca096 --- /dev/null +++ b/example/multi_threaded_echo_c++/server.cpp @@ -0,0 +1,99 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive EchoRequest and send back EchoResponse. + +#include +#include +#include +#include "echo.pb.h" + +DEFINE_bool(echo_attachment, true, "Echo attachment as well"); +DEFINE_int32(port, 8002, "TCP Port of this server"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); +DEFINE_int32(max_concurrency, 0, "Limit of request processing in parallel"); +DEFINE_int32(internal_port, -1, "Only allow builtin services at this port"); + +namespace example { +// Your implementation of EchoService +class EchoServiceImpl : public EchoService { +public: + EchoServiceImpl() {} + ~EchoServiceImpl() {} + void Echo(google::protobuf::RpcController* cntl_base, + const EchoRequest* request, + EchoResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = + static_cast(cntl_base); + + // Echo request and its attachment + response->set_message(request->message()); + if (FLAGS_echo_attachment) { + cntl->response_attachment().append(cntl->request_attachment()); + } + } +}; +} // namespace example + +DEFINE_bool(h, false, "print help information"); + +int main(int argc, char* argv[]) { + std::string help_str = "dummy help infomation"; + GFLAGS_NAMESPACE::SetUsageMessage(help_str); + + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_h) { + fprintf(stderr, "%s\n%s\n%s", help_str.c_str(), help_str.c_str(), help_str.c_str()); + return 0; + } + + // Generally you only need one Server. + brpc::Server server; + + // Instance of your service. + example::EchoServiceImpl echo_service_impl; + + // Add the service into server. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + if (server.AddService(&echo_service_impl, + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + + // Start the server. + brpc::ServerOptions options; + options.mutable_ssl_options()->default_cert.certificate = "cert.pem"; + options.mutable_ssl_options()->default_cert.private_key = "key.pem"; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + options.max_concurrency = FLAGS_max_concurrency; + options.internal_port = FLAGS_internal_port; + if (server.Start(FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + // Wait until Ctrl-C is pressed, then Stop() and Join() the server. + server.RunUntilAskedToQuit(); + return 0; +} diff --git a/example/multi_threaded_echo_fns_c++/CMakeLists.txt b/example/multi_threaded_echo_fns_c++/CMakeLists.txt new file mode 100644 index 0000000..e184148 --- /dev/null +++ b/example/multi_threaded_echo_fns_c++/CMakeLists.txt @@ -0,0 +1,115 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(multi_threaded_echo_fns_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER echo.proto) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(GPERFTOOLS_INCLUDE_DIR NAMES gperftools/heap-profiler.h) +find_library(GPERFTOOLS_LIBRARIES NAMES tcmalloc_and_profiler) + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +set(BRPC_EXAMPLE_ENABLE_CPU_PROFILER ON) + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(multi_threaded_echo_fns_client client.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(multi_threaded_echo_fns_client) +add_executable(multi_threaded_echo_fns_server server.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(multi_threaded_echo_fns_server) + +target_link_libraries(multi_threaded_echo_fns_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${GPERFTOOLS_LIBRARIES}) +target_link_libraries(multi_threaded_echo_fns_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${GPERFTOOLS_LIBRARIES}) diff --git a/example/multi_threaded_echo_fns_c++/client.cpp b/example/multi_threaded_echo_fns_c++/client.cpp new file mode 100644 index 0000000..895b3e5 --- /dev/null +++ b/example/multi_threaded_echo_fns_c++/client.cpp @@ -0,0 +1,157 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending requests to servers(discovered by naming service) by multiple threads. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_int32(thread_num, 50, "Number of threads to send requests"); +DEFINE_bool(use_bthread, false, "Use bthread to send requests"); +DEFINE_int32(attachment_size, 0, "Carry so many byte attachment along with requests"); +DEFINE_string(protocol, "baidu_std", "Protocol type. Defined in src/brpc/options.proto"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(server, "file://server_list", "Addresses of servers"); +DEFINE_string(load_balancer, "rr", "Name of load balancer"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(backup_timeout_ms, -1, "backup timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_bool(dont_fail, false, "Print fatal when some call failed"); +DEFINE_int32(dummy_port, -1, "Launch dummy server at this port"); + +std::string g_attachment; + +bvar::LatencyRecorder g_latency_recorder("client"); +bvar::Adder g_error_count("client_error_count"); +butil::static_atomic g_sender_count = BUTIL_STATIC_ATOMIC_INIT(0); + +static void* sender(void* arg) { + // Normally, you should not call a Channel directly, but instead construct + // a stub Service wrapping it. stub can be shared by all threads as well. + example::EchoService_Stub stub(static_cast(arg)); + + int log_id = 0; + while (!brpc::IsAskedToQuit()) { + // We will receive response synchronously, safe to put variables + // on stack. + example::EchoRequest request; + example::EchoResponse response; + brpc::Controller cntl; + + const int thread_index = g_sender_count.fetch_add(1, butil::memory_order_relaxed); + const int input = ((thread_index & 0xFFF) << 20) | (log_id & 0xFFFFF); + request.set_value(input); + cntl.set_log_id(log_id ++); // set by user + // Set attachment which is wired to network directly instead of + // being serialized into protobuf messages. + cntl.request_attachment().append(g_attachment); + + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + stub.Echo(&cntl, &request, &response, NULL); + if (!cntl.Failed()) { + CHECK(response.value() == request.value() + 1); + g_latency_recorder << cntl.latency_us(); + } else { + g_error_count << 1; + CHECK(brpc::IsAskedToQuit() || !FLAGS_dont_fail) + << "input=(" << thread_index << "," << (input & 0xFFFFF) + << ") error=" << cntl.ErrorText() << " latency=" << cntl.latency_us(); + // We can't connect to the server, sleep a while. Notice that this + // is a specific sleeping to prevent this thread from spinning too + // fast. You should continue the business logic in a production + // server rather than sleeping. + bthread_usleep(50000); + } + } + return NULL; +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.backup_request_ms = FLAGS_backup_timeout_ms; + options.protocol = FLAGS_protocol; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + if (channel.Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + if (FLAGS_attachment_size > 0) { + g_attachment.resize(FLAGS_attachment_size, 'a'); + } + + if (FLAGS_dummy_port >= 0) { + brpc::StartDummyServerAt(FLAGS_dummy_port); + } + + std::vector bids; + std::vector pids; + if (!FLAGS_use_bthread) { + pids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (pthread_create(&pids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create pthread"; + return -1; + } + } + } else { + bids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (bthread_start_background( + &bids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create bthread"; + return -1; + } + } + } + + while (!brpc::IsAskedToQuit()) { + sleep(1); + LOG(INFO) << "Sending EchoRequest at qps=" << g_latency_recorder.qps(1) + << " latency=" << g_latency_recorder.latency(1); + } + + LOG(INFO) << "EchoClient is going to quit"; + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (!FLAGS_use_bthread) { + pthread_join(pids[i], NULL); + } else { + bthread_join(bids[i], NULL); + } + } + + return 0; +} diff --git a/example/multi_threaded_echo_fns_c++/echo.proto b/example/multi_threaded_echo_fns_c++/echo.proto new file mode 100644 index 0000000..e4765a9 --- /dev/null +++ b/example/multi_threaded_echo_fns_c++/echo.proto @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package example; + +option cc_generic_services = true; + +message EchoRequest { + required int32 value = 1; +}; + +message EchoResponse { + required int32 value = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; diff --git a/example/multi_threaded_echo_fns_c++/random_kill.sh b/example/multi_threaded_echo_fns_c++/random_kill.sh new file mode 100644 index 0000000..96b8abe --- /dev/null +++ b/example/multi_threaded_echo_fns_c++/random_kill.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env sh + +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +make -sj8 || exit -1 + +function exit_func { + kill -- -$$ + echo "Killed all" + exit 0 +} +trap "exit_func" EXIT SIGINT SIGTERM + +echo > server_list +starting_port=8004 +pids=() +num_servers=5 +interval=2 + +for ((i = 0; i < $num_servers; ++i)); do + ./echo_server -server_num 1 -port $((starting_port+i)) & + pids[$i]=$! +done +usleep 100000 +./echo_client -use_bthread -thread_num 400 -timeout_ms -1 --health_check_interval 1 -dont_fail > echo_client.log 2>&1 & + +echo ${pids[*]} + +while true; do + echo "Running for $interval seconds..." + sleep $interval + index=$((RANDOM%num_servers)) + server_pid=${pids[$index]} + echo "Kill $server_pid(index=$index)" + kill -INT $server_pid + echo "Wait for another $interval seconds..." + sleep $interval + if grep -q "BAD\!\|^FATAL:" echo_client.log; then + echo "************** Found bug! ***************" + echo "Check echo_client.log for client logs" + exit 1 + fi + ./echo_server -server_num 1 -port $((starting_port+index)) & + pids[$index]=$! + echo "Create echo_server=${pids[$index]} at index=$index" +done diff --git a/example/multi_threaded_echo_fns_c++/server.cpp b/example/multi_threaded_echo_fns_c++/server.cpp new file mode 100644 index 0000000..a25ef96 --- /dev/null +++ b/example/multi_threaded_echo_fns_c++/server.cpp @@ -0,0 +1,188 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive EchoRequest and send back EchoResponse. + +#include // O_RDONLY +#include +#include +#include +#include +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_bool(send_attachment, false, "Carry attachment along with response"); +DEFINE_int32(port, 8004, "TCP Port of this server"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); +DEFINE_int32(max_concurrency, 0, "Limit of request processing in parallel"); +DEFINE_int32(server_num, 1, "Number of servers"); +DEFINE_string(sleep_us, "", "Sleep so many microseconds before responding"); +DEFINE_bool(spin, false, "spin rather than sleep"); +DEFINE_double(exception_ratio, 0.1, "Percentage of irregular latencies"); +DEFINE_double(min_ratio, 0.2, "min_sleep / sleep_us"); +DEFINE_double(max_ratio, 10, "max_sleep / sleep_us"); + +// Your implementation of example::EchoService +class EchoServiceImpl : public example::EchoService { +public: + EchoServiceImpl() : _index(0) {} + virtual ~EchoServiceImpl() {} + void set_index(size_t index, int64_t sleep_us) { + _index = index; + _sleep_us = sleep_us; + } + virtual void Echo(google::protobuf::RpcController* cntl_base, + const example::EchoRequest* request, + example::EchoResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = + static_cast(cntl_base); + if (_sleep_us > 0) { + double delay = _sleep_us; + const double a = FLAGS_exception_ratio * 0.5; + if (a >= 0.0001) { + double x = butil::RandDouble(); + if (x < a) { + const double min_sleep_us = FLAGS_min_ratio * _sleep_us; + delay = min_sleep_us + (_sleep_us - min_sleep_us) * x / a; + } else if (x + a > 1) { + const double max_sleep_us = FLAGS_max_ratio * _sleep_us; + delay = _sleep_us + (max_sleep_us - _sleep_us) * (x + a - 1) / a; + } + } + if (FLAGS_spin) { + int64_t end_time = butil::cpuwide_time_us() + (int64_t)delay; + while (butil::cpuwide_time_us() < end_time) {} + } else { + bthread_usleep((int64_t)delay); + } + } + + // Fill response. + response->set_value(request->value() + 1); + if (FLAGS_send_attachment) { + // Set attachment which is wired to network directly instead of + // being serialized into protobuf messages. + cntl->response_attachment().append("bar"); + } + _nreq << 1; + } + + size_t num_requests() const { return _nreq.get_value(); } + +private: + size_t _index; + int64_t _sleep_us; + bvar::Adder _nreq; +}; + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_server_num <= 0) { + LOG(ERROR) << "server_num must be positive"; + return -1; + } + + // We need multiple servers in this example. + brpc::Server* servers = new brpc::Server[FLAGS_server_num]; + + butil::StringSplitter sp(FLAGS_sleep_us.c_str(), ','); + std::vector sleep_list; + for (; sp; ++sp) { + sleep_list.push_back(strtoll(sp.field(), NULL, 10)); + } + if (sleep_list.empty()) { + sleep_list.push_back(0); + } + + // Instance of your services. + EchoServiceImpl* echo_service_impls = new EchoServiceImpl[FLAGS_server_num]; + + // Add the service into servers. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + for (int i = 0; i < FLAGS_server_num; ++i) { + int64_t sleep_us = sleep_list[(size_t)i < sleep_list.size() ? i : (sleep_list.size() - 1)]; + echo_service_impls[i].set_index(i, sleep_us); + servers[i].set_version(butil::string_printf( + "example/multi_threaded_echo_fns_c++[%d]", i)); + if (servers[i].AddService(&echo_service_impls[i], + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + // Start the server. + brpc::ServerOptions options; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + options.max_concurrency = FLAGS_max_concurrency; + const int port = FLAGS_port + i; + if (servers[i].Start(port, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + // Intended no truncate so that multiple servers can be added to list + int fd = open("./server_list", O_APPEND | O_WRONLY | O_CREAT, 0666); + if (fd < 0) { + PLOG(ERROR) << "Fail to open server_list"; + return -1; + } + char buf[64]; + int nw = snprintf(buf, sizeof(buf), "%s:%d\n", butil::my_ip_cstr(), port); + if (write(fd, buf, nw) != nw) { + LOG(ERROR) << "Fail to fully write int fd=" << fd; + } + close(fd); + } + + // Service logic are running in separate worker threads, for main thread, + // we don't have much to do, just spinning. + std::vector last_num_requests(FLAGS_server_num); + while (!brpc::IsAskedToQuit()) { + sleep(1); + + size_t cur_total = 0; + for (int i = 0; i < FLAGS_server_num; ++i) { + const size_t current_num_requests = + echo_service_impls[i].num_requests(); + size_t diff = current_num_requests - last_num_requests[i]; + cur_total += diff; + last_num_requests[i] = current_num_requests; + LOG(INFO) << "S[" << i << "]=" << diff << ' ' << noflush; + } + LOG(INFO) << "[total=" << cur_total << ']'; + } + + // Don't forget to stop and join the server otherwise still-running + // worker threads may crash your program. + for (int i = 0; i < FLAGS_server_num; ++i) { + servers[i].Stop(0/*not used now*/); + } + for (int i = 0; i < FLAGS_server_num; ++i) { + servers[i].Join(); + } + delete [] servers; + delete [] echo_service_impls; + return 0; +} diff --git a/example/mysql_c++/CMakeLists.txt b/example/mysql_c++/CMakeLists.txt new file mode 100644 index 0000000..1e0b953 --- /dev/null +++ b/example/mysql_c++/CMakeLists.txt @@ -0,0 +1,148 @@ +cmake_minimum_required(VERSION 2.8.10) +project(mysql_c++ C CXX) + +# Install dependencies: +# With apt: +# sudo apt-get install libreadline-dev +# sudo apt-get install ncurses-dev +# With yum: +# sudo yum install readline-devel +# sudo yum install ncurses-devel + +option(EXAMPLE_LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +set(CMAKE_PREFIX_PATH ${OUTPUT_PATH}) + +include(FindThreads) +include(FindProtobuf) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() +find_library(THRIFTNB_LIB NAMES thriftnb) +if (NOT THRIFTNB_LIB) + set(THRIFTNB_LIB "") +endif() + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(EXAMPLE_LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() +include_directories(${BRPC_INCLUDE_PATH}) + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() +include_directories(${GFLAGS_INCLUDE_PATH}) + +execute_process( + COMMAND bash -c "grep \"namespace [_A-Za-z0-9]\\+ {\" ${GFLAGS_INCLUDE_PATH}/gflags/gflags_declare.h | head -1 | awk '{print $2}' | tr -d '\n'" + OUTPUT_VARIABLE GFLAGS_NS +) +if(${GFLAGS_NS} STREQUAL "GFLAGS_NAMESPACE") + execute_process( + COMMAND bash -c "grep \"#define GFLAGS_NAMESPACE [_A-Za-z0-9]\\+\" ${GFLAGS_INCLUDE_PATH}/gflags/gflags_declare.h | head -1 | awk '{print $3}' | tr -d '\n'" + OUTPUT_VARIABLE GFLAGS_NS + ) +endif() +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + include(CheckFunctionExists) + CHECK_FUNCTION_EXISTS(clock_gettime HAVE_CLOCK_GETTIME) + if(NOT HAVE_CLOCK_GETTIME) + set(DEFINE_CLOCK_GETTIME "-DNO_CLOCK_GETTIME_IN_MAC") + endif() +endif() + +set(CMAKE_CPP_FLAGS "${DEFINE_CLOCK_GETTIME} -DGFLAGS_NS=${GFLAGS_NS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CPP_FLAGS} -DNDEBUG -O2 -D__const__= -pipe -W -Wall -Wno-unused-parameter -fPIC -fno-omit-frame-pointer") + +if(CMAKE_VERSION VERSION_LESS "3.1.3") + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + endif() + if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + endif() +else() + set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() +include_directories(${LEVELDB_INCLUDE_PATH}) + +find_library(SSL_LIB NAMES ssl) +if (NOT SSL_LIB) + message(FATAL_ERROR "Fail to find ssl") +endif() + +find_library(CRYPTO_LIB NAMES crypto) +if (NOT CRYPTO_LIB) + message(FATAL_ERROR "Fail to find crypto") +endif() + +# find_path(MYSQL_INCLUDE_PATH NAMES mysql/mysql.h) +# find_library(MYSQL_LIB NAMES mysqlclient) +# if (NOT MYSQL_LIB) +# message(FATAL_ERROR "Fail to find mysqlclient") +# endif() +# include_directories(${MYSQL_INCLUDE_PATH}) + +set(DYNAMIC_LIB + ${CMAKE_THREAD_LIBS_INIT} + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${SSL_LIB} + ${CRYPTO_LIB} + ${THRIFT_LIB} + ${THRIFTNB_LIB} +# ${MYSQL_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop") +endif() + +add_executable(mysql_cli mysql_cli.cpp) +add_executable(mysql_tx mysql_tx.cpp) +add_executable(mysql_stmt mysql_stmt.cpp) +add_executable(mysql_press mysql_press.cpp) +# add_executable(mysqlclient_press mysqlclient_press.cpp) + +set(AUX_LIB readline ncurses) +target_link_libraries(mysql_cli ${BRPC_LIB} ${DYNAMIC_LIB} ${AUX_LIB}) +target_link_libraries(mysql_tx ${BRPC_LIB} ${DYNAMIC_LIB}) +target_link_libraries(mysql_stmt ${BRPC_LIB} ${DYNAMIC_LIB}) +target_link_libraries(mysql_press ${BRPC_LIB} ${DYNAMIC_LIB}) +# target_link_libraries(mysqlclient_press ${BRPC_LIB} ${DYNAMIC_LIB}) diff --git a/example/mysql_c++/mysql_cli.cpp b/example/mysql_c++/mysql_cli.cpp new file mode 100644 index 0000000..85f57d6 --- /dev/null +++ b/example/mysql_c++/mysql_cli.cpp @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +// A brpc based command-line interface to talk with mysql-server + +#include +#include +#include +#include +#include +#include +#include +#include "brpc/policy/mysql/mysql.h" +#include "brpc/policy/mysql/mysql_authenticator.h" + +DEFINE_string(connection_type, "pooled", "Connection type. Available values: pooled, short"); +DEFINE_string(server, "127.0.0.1", "IP Address of server"); +DEFINE_int32(port, 3306, "Port of server"); +DEFINE_string(user, "brpcuser", "user name"); +DEFINE_string(password, "12345678", "password"); +DEFINE_string(schema, "brpc_test", "schema"); +DEFINE_string(params, "", "params"); +DEFINE_string(collation, "utf8mb4_general_ci", "collation"); +DEFINE_int32(timeout_ms, 5000, "RPC timeout in milliseconds"); +DEFINE_int32(connect_timeout_ms, 5000, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 0, "Max retries(not including the first RPC)"); + +namespace brpc { +const char* logo(); +} + +// Send `command' to mysql-server via `channel' +static bool access_mysql(brpc::Channel& channel, const char* command) { + brpc::MysqlRequest request; + if (!request.Query(command)) { + LOG(ERROR) << "Fail to add command"; + return false; + } + brpc::MysqlResponse response; + brpc::Controller cntl; + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + if (!cntl.Failed()) { + std::cout << response << std::endl; + } else { + LOG(ERROR) << "Fail to access mysql, " << cntl.ErrorText(); + return false; + } + return true; +} + +// For freeing the memory returned by readline(). +struct Freer { + void operator()(char* mem) { + free(mem); + } +}; + +static void dummy_handler(int) {} + +// The getc for readline. The default getc retries reading when meeting +// EINTR, which is not what we want. +static bool g_canceled = false; +static int cli_getc(FILE* stream) { + int c = getc(stream); + if (c == EOF && errno == EINTR) { + g_canceled = true; + return '\n'; + } + return c; +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NS::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_MYSQL; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms /*milliseconds*/; + options.connect_timeout_ms = FLAGS_connect_timeout_ms; + options.max_retry = FLAGS_max_retry; + options.auth = new brpc::policy::MysqlAuthenticator( + FLAGS_user, FLAGS_password, FLAGS_schema, FLAGS_params, FLAGS_collation); + if (channel.Init(FLAGS_server.c_str(), FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + if (argc <= 1) { // interactive mode + // We need this dummy signal hander to interrupt getc (and returning + // EINTR), SIG_IGN did not work. + signal(SIGINT, dummy_handler); + + // Hook getc of readline. + rl_getc_function = cli_getc; + + // Print welcome information. + printf("%s\n", brpc::logo()); + printf( + "This command-line tool mimics the look-n-feel of official " + "mysql-cli, as a demostration of brpc's capability of" + " talking to mysql-server. The output and behavior is " + "not exactly same with the official one.\n\n"); + + for (;;) { + char prompt[128]; + snprintf(prompt, sizeof(prompt), "mysql %s> ", FLAGS_server.c_str()); + std::unique_ptr command(readline(prompt)); + if (command == NULL || *command == '\0') { + if (g_canceled) { + // No input after the prompt and user pressed Ctrl-C, + // quit the CLI. + return 0; + } + // User entered an empty command by just pressing Enter. + continue; + } + if (g_canceled) { + // User entered sth. and pressed Ctrl-C, start a new prompt. + g_canceled = false; + continue; + } + // Add user's command to history so that it's browse-able by + // UP-key and search-able by Ctrl-R. + add_history(command.get()); + + if (!strcmp(command.get(), "help")) { + printf("This is a mysql CLI written in brpc.\n"); + continue; + } + if (!strcmp(command.get(), "quit")) { + // Although quit is a valid mysql command, it does not make + // too much sense to run it in this CLI, just quit. + return 0; + } + access_mysql(channel, command.get()); + } + } else { + std::string command; + command.reserve(argc * 16); + for (int i = 1; i < argc; ++i) { + if (i != 1) { + command.push_back(';'); + } + command.append(argv[i]); + } + if (!access_mysql(channel, command.c_str())) { + return -1; + } + } + return 0; +} diff --git a/example/mysql_c++/mysql_go_press.go b/example/mysql_c++/mysql_go_press.go new file mode 100644 index 0000000..7309413 --- /dev/null +++ b/example/mysql_c++/mysql_go_press.go @@ -0,0 +1,80 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +package main + +import ( + "database/sql" + "flag" + "fmt" + _ "github.com/go-sql-driver/mysql" + "log" + "sync/atomic" + "time" +) + +var thread_num int + +func init() { + flag.IntVar(&thread_num, "thread_num", 1, "thread number") +} + +var cost int64 +var qps int64 = 1 + +func main() { + flag.Parse() + + db, err := sql.Open("mysql", "brpcuser:12345678@tcp(127.0.0.1:3306)/brpc_test?charset=utf8") + if err != nil { + log.Fatal(err) + } + + for i := 0; i < thread_num; i++ { + go func() { + for { + var ( + id int + col1 string + col2 string + col3 string + col4 string + ) + start := time.Now() + rows, err := db.Query("select * from brpc_press where id = 1") + if err != nil { + log.Fatal(err) + } + for rows.Next() { + if err := rows.Scan(&id, &col1, &col2, &col3, &col4); err != nil { + log.Fatal(err) + } + } + atomic.AddInt64(&cost, time.Since(start).Nanoseconds()) + atomic.AddInt64(&qps, 1) + } + }() + } + + var q int64 = 0 + for { + fmt.Println("qps =", qps-q, "latency =", cost/(qps-q)/1000) + q = atomic.LoadInt64(&qps) + atomic.StoreInt64(&cost, 0) + time.Sleep(1 * time.Second) + } +} diff --git a/example/mysql_c++/mysql_press.cpp b/example/mysql_c++/mysql_press.cpp new file mode 100644 index 0000000..d1cc060 --- /dev/null +++ b/example/mysql_c++/mysql_press.cpp @@ -0,0 +1,242 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +// A brpc based command-line interface to talk with mysql-server + +#include +#include +#include +#include +#include "brpc/policy/mysql/mysql.h" +#include "brpc/policy/mysql/mysql_authenticator.h" +#include +#include +#include + +DEFINE_string(connection_type, "pooled", "Connection type. Available values: pooled, short"); +DEFINE_string(server, "127.0.0.1", "IP Address of server"); +DEFINE_int32(port, 3306, "Port of server"); +DEFINE_string(user, "brpcuser", "user name"); +DEFINE_string(password, "12345678", "password"); +DEFINE_string(schema, "brpc_test", "schema"); +DEFINE_string(params, "", "params"); +DEFINE_string(collation, "utf8mb4_general_ci", "collation"); +DEFINE_string(data, "ABCDEF", "data"); +DEFINE_int32(timeout_ms, 5000, "RPC timeout in milliseconds"); +DEFINE_int32(connect_timeout_ms, 5000, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_int32(thread_num, 50, "Number of threads to send requests"); +DEFINE_bool(use_bthread, false, "Use bthread to send requests"); +DEFINE_int32(dummy_port, -1, "port of dummy server(for monitoring)"); +DEFINE_int32(op_type, 0, "CRUD operation, 0:INSERT, 1:SELECT, 2:UPDATE"); +DEFINE_bool(dont_fail, false, "Print fatal when some call failed"); + +bvar::LatencyRecorder g_latency_recorder("client"); +bvar::Adder g_error_count("client_error_count"); + +struct SenderArgs { + int base_index; + brpc::Channel* mysql_channel; +}; + +const std::string insert = + "insert into brpc_press(col1,col2,col3,col4) values " + "('" + "ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCA" + "BCABCABCABCABCABCABCA', '" + + FLAGS_data + + "' ,1.5, " + "now())"; +// Send `command' to mysql-server via `channel' +static void* sender(void* void_args) { + SenderArgs* args = (SenderArgs*)void_args; + std::stringstream command; + if (FLAGS_op_type == 0) { + command << insert; + } else if (FLAGS_op_type == 1) { + command << "select * from brpc_press where id = " << args->base_index + 1; + } else if (FLAGS_op_type == 2) { + command << "update brpc_press set col2 = '" + FLAGS_data + "' where id = " + << args->base_index + 1; + } else { + LOG(ERROR) << "wrong op type " << FLAGS_op_type; + } + + brpc::MysqlRequest request; + if (!request.Query(command.str())) { + LOG(ERROR) << "Fail to execute command"; + return NULL; + } + + while (!brpc::IsAskedToQuit()) { + brpc::MysqlResponse response; + brpc::Controller cntl; + args->mysql_channel->CallMethod(NULL, &cntl, &request, &response, NULL); + const int64_t elp = cntl.latency_us(); + if (!cntl.Failed()) { + g_latency_recorder << elp; + if (FLAGS_op_type == 0) { + CHECK_EQ(response.reply(0).is_ok(), true); + } else if (FLAGS_op_type == 1) { + CHECK_EQ(response.reply(0).row_count(), 1); + } else if (FLAGS_op_type == 2) { + CHECK_EQ(response.reply(0).is_ok(), true); + } + } else { + g_error_count << 1; + CHECK(brpc::IsAskedToQuit() || !FLAGS_dont_fail) + << "error=" << cntl.ErrorText() << " latency=" << elp; + // We can't connect to the server, sleep a while. Notice that this + // is a specific sleeping to prevent this thread from spinning too + // fast. You should continue the business logic in a production + // server rather than sleeping. + bthread_usleep(50000); + } + } + return NULL; +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NS::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_MYSQL; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms /*milliseconds*/; + options.connect_timeout_ms = FLAGS_connect_timeout_ms; + options.max_retry = FLAGS_max_retry; + options.auth = new brpc::policy::MysqlAuthenticator( + FLAGS_user, FLAGS_password, FLAGS_schema, FLAGS_params, FLAGS_collation); + if (channel.Init(FLAGS_server.c_str(), FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + // create table brpc_press + { + brpc::MysqlRequest request; + if (!request.Query( + "CREATE TABLE IF NOT EXISTS `brpc_press`(`id` INT UNSIGNED AUTO_INCREMENT, `col1` " + "VARCHAR(100) NOT NULL, `col2` VARCHAR(1024) NOT NULL, `col3` decimal(10,0) NOT " + "NULL, `col4` DATE, PRIMARY KEY ( `id` )) ENGINE=InnoDB DEFAULT CHARSET=utf8;")) { + LOG(ERROR) << "Fail to create table"; + return -1; + } + brpc::MysqlResponse response; + brpc::Controller cntl; + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + if (!cntl.Failed()) { + std::cout << response << std::endl; + } else { + LOG(ERROR) << "Fail to access mysql, " << cntl.ErrorText(); + return -1; + } + } + + // truncate table + { + brpc::MysqlRequest request; + if (!request.Query("truncate table brpc_press")) { + LOG(ERROR) << "Fail to truncate table"; + return -1; + } + brpc::MysqlResponse response; + brpc::Controller cntl; + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + if (!cntl.Failed()) { + std::cout << response << std::endl; + } else { + LOG(ERROR) << "Fail to access mysql, " << cntl.ErrorText(); + return -1; + } + } + + // prepare data for select, update + if (FLAGS_op_type != 0) { + for (int i = 0; i < FLAGS_thread_num; ++i) { + brpc::MysqlRequest request; + if (!request.Query(insert)) { + LOG(ERROR) << "Fail to execute command"; + return -1; + } + brpc::MysqlResponse response; + brpc::Controller cntl; + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + if (cntl.Failed()) { + LOG(ERROR) << cntl.ErrorText(); + return -1; + } + if (!response.reply(0).is_ok()) { + LOG(ERROR) << "prepare data failed"; + return -1; + } + } + } + + if (FLAGS_dummy_port >= 0) { + brpc::StartDummyServerAt(FLAGS_dummy_port); + } + + // test CRUD operations + std::vector bids; + std::vector pids; + bids.resize(FLAGS_thread_num); + pids.resize(FLAGS_thread_num); + std::vector args; + args.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + args[i].base_index = i; + args[i].mysql_channel = &channel; + if (!FLAGS_use_bthread) { + if (pthread_create(&pids[i], NULL, sender, &args[i]) != 0) { + LOG(ERROR) << "Fail to create pthread"; + return -1; + } + } else { + if (bthread_start_background(&bids[i], NULL, sender, &args[i]) != 0) { + LOG(ERROR) << "Fail to create bthread"; + return -1; + } + } + } + + while (!brpc::IsAskedToQuit()) { + sleep(1); + + LOG(INFO) << "Accessing mysql-server at qps=" << g_latency_recorder.qps(1) + << " latency=" << g_latency_recorder.latency(1); + } + + LOG(INFO) << "mysql_client is going to quit"; + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (!FLAGS_use_bthread) { + pthread_join(pids[i], NULL); + } else { + bthread_join(bids[i], NULL); + } + } + + return 0; +} diff --git a/example/mysql_c++/mysql_stmt.cpp b/example/mysql_c++/mysql_stmt.cpp new file mode 100644 index 0000000..274e6ba --- /dev/null +++ b/example/mysql_c++/mysql_stmt.cpp @@ -0,0 +1,205 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +// A brpc based mysql transaction example +#include +#include +#include +#include +#include "brpc/policy/mysql/mysql.h" +#include "brpc/policy/mysql/mysql_authenticator.h" + +DEFINE_string(connection_type, "pooled", "Connection type. Available values: pooled, short"); +DEFINE_string(server, "127.0.0.1", "IP Address of server"); +DEFINE_int32(port, 3306, "Port of server"); +DEFINE_string(user, "brpcuser", "user name"); +DEFINE_string(password, "12345678", "password"); +DEFINE_string(schema, "brpc_test", "schema"); +DEFINE_string(params, "", "params"); +DEFINE_string(collation, "utf8mb4_general_ci", "collation"); +DEFINE_int32(timeout_ms, 5000, "RPC timeout in milliseconds"); +DEFINE_int32(connect_timeout_ms, 5000, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 0, "Max retries(not including the first RPC)"); +DEFINE_int32(thread_num, 1, "Number of threads to send requests"); +DEFINE_int32(count, 1, "Number of request to send pre thread"); + +namespace brpc { +const char* logo(); +} + +struct SenderArgs { + brpc::Channel* mysql_channel; + brpc::MysqlStatement* mysql_stmt; + std::vector commands; +}; + +// Send `command' to mysql-server via `channel' +static void* access_mysql(void* void_args) { + SenderArgs* args = (SenderArgs*)void_args; + brpc::Channel* channel = args->mysql_channel; + brpc::MysqlStatement* stmt = args->mysql_stmt; + const std::vector& commands = args->commands; + + for (int i = 0; i < FLAGS_count; ++i) { + brpc::MysqlRequest request(stmt); + for (size_t i = 1; i < commands.size(); i += 2) { + if (commands[i] == "int8") { + int8_t val = strtol(commands[i + 1].c_str(), NULL, 10); + if (!request.AddParam(val)) { + LOG(ERROR) << "Fail to add int8 param"; + return NULL; + } + } else if (commands[i] == "uint8") { + uint8_t val = strtoul(commands[i + 1].c_str(), NULL, 10); + if (!request.AddParam(val)) { + LOG(ERROR) << "Fail to add uint8 param"; + return NULL; + } + } else if (commands[i] == "int16") { + int16_t val = strtol(commands[i + 1].c_str(), NULL, 10); + if (!request.AddParam(val)) { + LOG(ERROR) << "Fail to add uint16 param"; + return NULL; + } + } else if (commands[i] == "uint16") { + uint16_t val = strtoul(commands[i + 1].c_str(), NULL, 10); + if (!request.AddParam(val)) { + LOG(ERROR) << "Fail to add uint16 param"; + return NULL; + } + } else if (commands[i] == "int32") { + int32_t val = strtol(commands[i + 1].c_str(), NULL, 10); + if (!request.AddParam(val)) { + LOG(ERROR) << "Fail to add int32 param"; + return NULL; + } + } else if (commands[i] == "uint32") { + uint32_t val = strtoul(commands[i + 1].c_str(), NULL, 10); + if (!request.AddParam(val)) { + LOG(ERROR) << "Fail to add uint32 param"; + return NULL; + } + } else if (commands[i] == "int64") { + int64_t val = strtol(commands[i + 1].c_str(), NULL, 10); + if (!request.AddParam(val)) { + LOG(ERROR) << "Fail to add int64 param"; + return NULL; + } + } else if (commands[i] == "uint64") { + uint64_t val = strtoul(commands[i + 1].c_str(), NULL, 10); + if (!request.AddParam(val)) { + LOG(ERROR) << "Fail to add uint64 param"; + return NULL; + } + } else if (commands[i] == "float") { + float val = strtof(commands[i + 1].c_str(), NULL); + if (!request.AddParam(val)) { + LOG(ERROR) << "Fail to add float param"; + return NULL; + } + } else if (commands[i] == "double") { + double val = strtod(commands[i + 1].c_str(), NULL); + if (!request.AddParam(val)) { + LOG(ERROR) << "Fail to add double param"; + return NULL; + } + } else if (commands[i] == "string") { + if (!request.AddParam(commands[i + 1])) { + LOG(ERROR) << "Fail to add string param"; + return NULL; + } + } else { + LOG(ERROR) << "Wrong param type " << commands[i]; + } + } + + brpc::MysqlResponse response; + brpc::Controller cntl; + channel->CallMethod(NULL, &cntl, &request, &response, NULL); + if (cntl.Failed()) { + LOG(ERROR) << "Fail to access mysql, " << cntl.ErrorText(); + return NULL; + } + + std::cout << response << std::endl; + } + + return NULL; +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NS::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_MYSQL; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms /*milliseconds*/; + options.connect_timeout_ms = FLAGS_connect_timeout_ms; + options.max_retry = FLAGS_max_retry; + options.auth = new brpc::policy::MysqlAuthenticator( + FLAGS_user, FLAGS_password, FLAGS_schema, FLAGS_params, FLAGS_collation); + if (channel.Init(FLAGS_server.c_str(), FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + if (argc <= 1) { + LOG(ERROR) << "No sql statement args"; + } else { + std::vector commands; + commands.reserve(argc * 16); + for (int i = 1; i < argc; ++i) { + commands.push_back(argv[i]); + } + auto stmt(brpc::NewMysqlStatement(channel, commands[0])); + if (stmt == NULL) { + LOG(ERROR) << "Fail to create mysql statement"; + return -1; + } + + std::vector args; + std::vector bids; + args.resize(FLAGS_thread_num); + bids.resize(FLAGS_thread_num); + + for (int i = 0; i < FLAGS_thread_num; ++i) { + args[i].mysql_channel = &channel; + args[i].mysql_stmt = stmt.get(); + args[i].commands = commands; + if (bthread_start_background(&bids[i], NULL, access_mysql, &args[i]) != 0) { + LOG(ERROR) << "Fail to create bthread"; + return -1; + } + } + + for (int i = 0; i < FLAGS_thread_num; ++i) { + bthread_join(bids[i], NULL); + } + } + + return 0; +} + +/* vim: set expandtab ts=4 sw=4 sts=4 tw=100: */ diff --git a/example/mysql_c++/mysql_tx.cpp b/example/mysql_c++/mysql_tx.cpp new file mode 100644 index 0000000..53b3a7d --- /dev/null +++ b/example/mysql_c++/mysql_tx.cpp @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +// A brpc based mysql transaction example +#include +#include +#include +#include "brpc/policy/mysql/mysql.h" +#include "brpc/policy/mysql/mysql_authenticator.h" + +DEFINE_string(connection_type, "pooled", "Connection type. Available values: pooled, short"); +DEFINE_string(server, "127.0.0.1", "IP Address of server"); +DEFINE_int32(port, 3306, "Port of server"); +DEFINE_string(user, "brpcuser", "user name"); +DEFINE_string(password, "12345678", "password"); +DEFINE_string(schema, "brpc_test", "schema"); +DEFINE_string(params, "", "params"); +DEFINE_string(collation, "utf8mb4_general_ci", "collation"); +DEFINE_int32(timeout_ms, 5000, "RPC timeout in milliseconds"); +DEFINE_int32(connect_timeout_ms, 5000, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 0, "Max retries(not including the first RPC)"); +DEFINE_bool(readonly, false, "readonly transaction"); +DEFINE_int32(isolation_level, 0, "transaction isolation level"); + +namespace brpc { +const char* logo(); +} + +// Send `command' to mysql-server via `channel' +static bool access_mysql(brpc::Channel& channel, const std::vector& commands) { + brpc::MysqlTransactionOptions options; + options.readonly = FLAGS_readonly; + options.isolation_level = brpc::MysqlIsolationLevel(FLAGS_isolation_level); + auto tx(brpc::NewMysqlTransaction(channel, options)); + if (tx == NULL) { + LOG(ERROR) << "Fail to create transaction"; + return false; + } + + for (auto it = commands.begin(); it != commands.end(); ++it) { + brpc::MysqlRequest request(tx.get()); + if (!request.Query(*it)) { + LOG(ERROR) << "Fail to add command"; + tx->rollback(); + return false; + } + brpc::MysqlResponse response; + brpc::Controller cntl; + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + if (cntl.Failed()) { + LOG(ERROR) << "Fail to access mysql, " << cntl.ErrorText(); + tx->rollback(); + return false; + } + // check response + std::cout << response << std::endl; + for (size_t i = 0; i < response.reply_size(); ++i) { + if (response.reply(i).is_error()) { + tx->rollback(); + return false; + } + } + } + tx->commit(); + return true; +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NS::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_MYSQL; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms /*milliseconds*/; + options.connect_timeout_ms = FLAGS_connect_timeout_ms; + options.max_retry = FLAGS_max_retry; + options.auth = new brpc::policy::MysqlAuthenticator( + FLAGS_user, FLAGS_password, FLAGS_schema, FLAGS_params, FLAGS_collation); + if (channel.Init(FLAGS_server.c_str(), FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + if (argc <= 1) { + LOG(ERROR) << "No sql statement args"; + } else { + std::vector commands; + commands.reserve(argc * 16); + for (int i = 1; i < argc; ++i) { + commands.push_back(argv[i]); + } + if (!access_mysql(channel, commands)) { + return -1; + } + } + return 0; +} + +/* vim: set expandtab ts=4 sw=4 sts=4 tw=100: */ diff --git a/example/mysql_c++/mysqlclient_press.cpp b/example/mysql_c++/mysqlclient_press.cpp new file mode 100644 index 0000000..7ed198f --- /dev/null +++ b/example/mysql_c++/mysqlclient_press.cpp @@ -0,0 +1,244 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +// A brpc based command-line interface to talk with mysql-server + +#include +#include +#include +extern "C" { +#include +} +#include +#include +#include +#include + +DEFINE_string(server, "127.0.0.1", "IP Address of server"); +DEFINE_int32(port, 3306, "Port of server"); +DEFINE_string(user, "brpcuser", "user name"); +DEFINE_string(password, "12345678", "password"); +DEFINE_string(schema, "brpc_test", "schema"); +DEFINE_string(params, "", "params"); +DEFINE_string(data, "ABCDEF", "data"); +DEFINE_int32(thread_num, 50, "Number of threads to send requests"); +DEFINE_bool(use_bthread, false, "Use bthread to send requests"); +DEFINE_int32(dummy_port, -1, "port of dummy server(for monitoring)"); +DEFINE_int32(op_type, 0, "CRUD operation, 0:INSERT, 1:SELECT, 3:UPDATE"); +DEFINE_bool(dont_fail, false, "Print fatal when some call failed"); + +bvar::LatencyRecorder g_latency_recorder("client"); +bvar::Adder g_error_count("client_error_count"); + +struct SenderArgs { + int base_index; + MYSQL* mysql_conn; +}; + +const std::string insert = + "insert into mysqlclient_press(col1,col2,col3,col4) values " + "('" + "ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCA" + "BCABCABCABCABCABCABCA', '" + + FLAGS_data + + "' ,1.5, " + "now())"; +// Send `command' to mysql-server via `channel' +static void* sender(void* void_args) { + SenderArgs* args = (SenderArgs*)void_args; + std::stringstream command; + if (FLAGS_op_type == 0) { + command << insert; + } else if (FLAGS_op_type == 1) { + command << "select * from mysqlclient_press where id = " << args->base_index + 1; + } else if (FLAGS_op_type == 2) { + command << "update brpc_press set col2 = '" + FLAGS_data + "' where id = " + << args->base_index + 1; + } else { + LOG(ERROR) << "wrong op type " << FLAGS_op_type; + } + + std::string command_str = command.str(); + + while (!brpc::IsAskedToQuit()) { + const int64_t begin_time_us = butil::cpuwide_time_us(); + const int rc = mysql_real_query(args->mysql_conn, command_str.c_str(), command_str.size()); + if (rc != 0) { + goto ERROR; + } + + if (mysql_errno(args->mysql_conn) == 0) { + if (FLAGS_op_type == 0) { + CHECK_EQ(mysql_affected_rows(args->mysql_conn), 1); + } else if (FLAGS_op_type == 1) { + MYSQL_RES* res = mysql_store_result(args->mysql_conn); + if (res == NULL) { + LOG(INFO) << "not found"; + } else { + CHECK_EQ(mysql_num_rows(res), 1); + mysql_free_result(res); + } + } else if (FLAGS_op_type == 2) { + } + const int64_t elp = butil::cpuwide_time_us() - begin_time_us; + g_latency_recorder << elp; + } else { + goto ERROR; + } + + if (false) { + ERROR: + const int64_t elp = butil::cpuwide_time_us() - begin_time_us; + g_error_count << 1; + CHECK(brpc::IsAskedToQuit() || !FLAGS_dont_fail) + << "error=" << mysql_error(args->mysql_conn) << " latency=" << elp; + // We can't connect to the server, sleep a while. Notice that this + // is a specific sleeping to prevent this thread from spinning too + // fast. You should continue the business logic in a production + // server rather than sleeping. + bthread_usleep(50000); + } + } + return NULL; +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NS::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_dummy_port >= 0) { + brpc::StartDummyServerAt(FLAGS_dummy_port); + } + + MYSQL* conn = mysql_init(NULL); + if (!mysql_real_connect(conn, + FLAGS_server.c_str(), + FLAGS_user.c_str(), + FLAGS_password.c_str(), + FLAGS_schema.c_str(), + FLAGS_port, + NULL, + 0)) { + LOG(ERROR) << mysql_error(conn); + return -1; + } + + // create table mysqlclient_press + { + const char* sql = + "CREATE TABLE IF NOT EXISTS `mysqlclient_press`(`id` INT UNSIGNED AUTO_INCREMENT, " + "`col1` " + "VARCHAR(100) NOT NULL, `col2` VARCHAR(1024) NOT NULL, `col3` decimal(10,0) NOT " + "NULL, `col4` DATE, PRIMARY KEY ( `id` )) ENGINE=InnoDB DEFAULT CHARSET=utf8;"; + const int rc = mysql_real_query(conn, sql, strlen(sql)); + if (rc != 0) { + LOG(ERROR) << "Fail to execute sql, " << mysql_error(conn); + return -1; + } + + if (mysql_errno(conn) != 0) { + LOG(ERROR) << "Fail to store result, " << mysql_error(conn); + return -1; + } + } + + // truncate table + { + const char* sql = "truncate table mysqlclient_press"; + const int rc = mysql_real_query(conn, sql, strlen(sql)); + if (rc != 0) { + LOG(ERROR) << "Fail to execute sql, " << mysql_error(conn); + return -1; + } + + if (mysql_errno(conn) != 0) { + LOG(ERROR) << "Fail to store result, " << mysql_error(conn); + return -1; + } + } + + // prepare data for select, update + if (FLAGS_op_type != 0) { + for (int i = 0; i < FLAGS_thread_num; ++i) { + const int rc = mysql_real_query(conn, insert.c_str(), insert.size()); + if (rc != 0) { + LOG(ERROR) << "Fail to execute sql, " << mysql_error(conn); + return -1; + } + + if (mysql_errno(conn) != 0) { + LOG(ERROR) << "Fail to store result, " << mysql_error(conn); + return -1; + } + } + } + + // test CRUD operations + std::vector bids; + std::vector pids; + bids.resize(FLAGS_thread_num); + pids.resize(FLAGS_thread_num); + std::vector args; + args.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + MYSQL* conn = mysql_init(NULL); + if (!mysql_real_connect(conn, + FLAGS_server.c_str(), + FLAGS_user.c_str(), + FLAGS_password.c_str(), + FLAGS_schema.c_str(), + FLAGS_port, + NULL, + 0)) { + LOG(ERROR) << mysql_error(conn); + return -1; + } + args[i].base_index = i; + args[i].mysql_conn = conn; + if (!FLAGS_use_bthread) { + if (pthread_create(&pids[i], NULL, sender, &args[i]) != 0) { + LOG(ERROR) << "Fail to create pthread"; + return -1; + } + } else { + if (bthread_start_background(&bids[i], NULL, sender, &args[i]) != 0) { + LOG(ERROR) << "Fail to create bthread"; + return -1; + } + } + } + + while (!brpc::IsAskedToQuit()) { + sleep(1); + + LOG(INFO) << "Accessing mysql-server at qps=" << g_latency_recorder.qps(1) + << " latency=" << g_latency_recorder.latency(1); + } + + LOG(INFO) << "mysql_client is going to quit"; + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (!FLAGS_use_bthread) { + pthread_join(pids[i], NULL); + } else { + bthread_join(bids[i], NULL); + } + } + + return 0; +} diff --git a/example/nshead_extension_c++/CMakeLists.txt b/example/nshead_extension_c++/CMakeLists.txt new file mode 100644 index 0000000..780e909 --- /dev/null +++ b/example/nshead_extension_c++/CMakeLists.txt @@ -0,0 +1,110 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(nshead_extension_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER echo.proto) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(nshead_extension_client client.cpp) +brpc_example_configure_target(nshead_extension_client) +add_executable(nshead_extension_server server.cpp) +brpc_example_configure_target(nshead_extension_server) + +target_link_libraries(nshead_extension_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) +target_link_libraries(nshead_extension_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) diff --git a/example/nshead_extension_c++/client.cpp b/example/nshead_extension_c++/client.cpp new file mode 100644 index 0000000..bfc48b6 --- /dev/null +++ b/example/nshead_extension_c++/client.cpp @@ -0,0 +1,82 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending requests to server every 1 second. + +#include + +#include +#include +#include +#include +#include +#include + +bvar::LatencyRecorder g_latency_recorder("client"); + +DEFINE_string(server, "0.0.0.0:8010", "IP Address of server"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_NSHEAD; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + if (channel.Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + // Send a request and wait for the response every 1 second. + int log_id = 0; + while (!brpc::IsAskedToQuit()) { + brpc::NsheadMessage request; + brpc::NsheadMessage response; + brpc::Controller cntl; + + // Append message to `request' + request.body.append("hello world"); + + cntl.set_log_id(log_id ++); // set by user + + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + if (cntl.Failed()) { + LOG(ERROR) << "Fail to send nshead request, " << cntl.ErrorText(); + sleep(1); // Remove this sleep in production code. + } else { + g_latency_recorder << cntl.latency_us(); + } + LOG_EVERY_SECOND(INFO) + << "Sending nshead requests at qps=" << g_latency_recorder.qps(1) + << " latency=" << g_latency_recorder.latency(1); + } + + LOG(INFO) << "EchoClient is going to quit"; + return 0; +} diff --git a/example/nshead_extension_c++/server.cpp b/example/nshead_extension_c++/server.cpp new file mode 100644 index 0000000..1cbd572 --- /dev/null +++ b/example/nshead_extension_c++/server.cpp @@ -0,0 +1,71 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive EchoRequest and send back EchoResponse. + +#include +#include +#include +#include + +DEFINE_int32(port, 8010, "TCP Port of this server"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); +DEFINE_int32(max_concurrency, 0, "Limit of request processing in parallel"); + +// Adapt your own nshead-based protocol to use brpc +class MyNsheadProtocol : public brpc::NsheadService { +public: + void ProcessNsheadRequest(const brpc::Server&, + brpc::Controller* cntl, + const brpc::NsheadMessage& request, + brpc::NsheadMessage* response, + brpc::NsheadClosure* done) { + // This object helps you to call done->Run() in RAII style. If you need + // to process the request asynchronously, pass done_guard.release(). + brpc::ClosureGuard done_guard(done); + + if (cntl->Failed()) { + // NOTE: You can send back a response containing error information + // back to client instead of closing the connection. + cntl->CloseConnection("Close connection due to previous error"); + return; + } + *response = request; // Just echo the request to client + } +}; + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + brpc::Server server; + brpc::ServerOptions options; + options.nshead_service = new MyNsheadProtocol; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + options.max_concurrency = FLAGS_max_concurrency; + + // Start the server. + if (server.Start(FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + // Wait until Ctrl-C is pressed, then Stop() and Join() the server. + server.RunUntilAskedToQuit(); + return 0; +} diff --git a/example/nshead_pb_extension_c++/CMakeLists.txt b/example/nshead_pb_extension_c++/CMakeLists.txt new file mode 100644 index 0000000..203dfa7 --- /dev/null +++ b/example/nshead_pb_extension_c++/CMakeLists.txt @@ -0,0 +1,110 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(nshead_pb_extension_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER echo.proto) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(nshead_pb_extension_client client.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(nshead_pb_extension_client) +add_executable(nshead_pb_extension_server server.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(nshead_pb_extension_server) + +target_link_libraries(nshead_pb_extension_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) +target_link_libraries(nshead_pb_extension_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) diff --git a/example/nshead_pb_extension_c++/client.cpp b/example/nshead_pb_extension_c++/client.cpp new file mode 100644 index 0000000..bfc48b6 --- /dev/null +++ b/example/nshead_pb_extension_c++/client.cpp @@ -0,0 +1,82 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending requests to server every 1 second. + +#include + +#include +#include +#include +#include +#include +#include + +bvar::LatencyRecorder g_latency_recorder("client"); + +DEFINE_string(server, "0.0.0.0:8010", "IP Address of server"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_NSHEAD; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + if (channel.Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + // Send a request and wait for the response every 1 second. + int log_id = 0; + while (!brpc::IsAskedToQuit()) { + brpc::NsheadMessage request; + brpc::NsheadMessage response; + brpc::Controller cntl; + + // Append message to `request' + request.body.append("hello world"); + + cntl.set_log_id(log_id ++); // set by user + + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + if (cntl.Failed()) { + LOG(ERROR) << "Fail to send nshead request, " << cntl.ErrorText(); + sleep(1); // Remove this sleep in production code. + } else { + g_latency_recorder << cntl.latency_us(); + } + LOG_EVERY_SECOND(INFO) + << "Sending nshead requests at qps=" << g_latency_recorder.qps(1) + << " latency=" << g_latency_recorder.latency(1); + } + + LOG(INFO) << "EchoClient is going to quit"; + return 0; +} diff --git a/example/nshead_pb_extension_c++/echo.proto b/example/nshead_pb_extension_c++/echo.proto new file mode 100644 index 0000000..2b39627 --- /dev/null +++ b/example/nshead_pb_extension_c++/echo.proto @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package example; + +option cc_generic_services = true; + +message EchoRequest { + required string message = 1; +}; + +message EchoResponse { + required string message = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; diff --git a/example/nshead_pb_extension_c++/server.cpp b/example/nshead_pb_extension_c++/server.cpp new file mode 100644 index 0000000..d87c9a2 --- /dev/null +++ b/example/nshead_pb_extension_c++/server.cpp @@ -0,0 +1,126 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive EchoRequest and send back EchoResponse. + +#include +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_int32(port, 8010, "TCP Port of this server"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); + +// Your implementation of example::EchoService +namespace example { +class EchoServiceImpl : public EchoService { +public: + EchoServiceImpl() {} + virtual ~EchoServiceImpl() {} + virtual void Echo(google::protobuf::RpcController*, + const EchoRequest* request, + EchoResponse* response, + google::protobuf::Closure* done) { + // This object helps you to call done->Run() in RAII style. If you need + // to process the request asynchronously, pass done_guard.release(). + brpc::ClosureGuard done_guard(done); + + // Echo response. + response->set_message(request->message()); + } +}; +} // namespace example + +// Adapt your own nshead-based protocol to use pbrpc interface +class MyNsheadProtocol : public brpc::NsheadPbServiceAdaptor { +public: + void ParseNsheadMeta( + const brpc::Server&, const brpc::NsheadMessage&, + brpc::Controller*, + brpc::NsheadMeta* out_meta) const { + // Always use EchoService::Echo + const google::protobuf::ServiceDescriptor* svc = + example::EchoService::descriptor(); + out_meta->set_full_method_name(svc->method(0)->full_name()); + } + + void ParseRequestFromIOBuf(const brpc::NsheadMeta&, + const brpc::NsheadMessage& raw_req, + brpc::Controller* cntl, + google::protobuf::Message* pb_req) const { + // `req' MUST be EchoRequest here since we have only one RPCMethod + example::EchoRequest* echo_req = + dynamic_cast(pb_req); + if (!echo_req) { + cntl->SetFailed(brpc::EREQUEST, "Fail to parse request"); + return; + } + echo_req->set_message(raw_req.body.to_string()); + } + + void SerializeResponseToIOBuf( + const brpc::NsheadMeta&, brpc::Controller* cntl, + const google::protobuf::Message* pb_res, + brpc::NsheadMessage* raw_res) const { + if (cntl->Failed()) { + // Can't send failure feedback in this protocol + cntl->CloseConnection("Close connection due to previous error"); + return; + } + // `res' MUST be EchoResponse here since we have only one RPCMethod + const example::EchoResponse* echo_res = + dynamic_cast(pb_res); + if (!echo_res) { + cntl->CloseConnection("Close connection due to bad response"); + return; + } + raw_res->body.append(echo_res->message()); + } +}; + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + brpc::Server server; + example::EchoServiceImpl echo_service_impl; + + // Add the service into server. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + if (server.AddService(&echo_service_impl, + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + + // Start the server. + brpc::ServerOptions options; + options.nshead_service = new MyNsheadProtocol; // the adaptor + options.idle_timeout_sec = FLAGS_idle_timeout_s; + if (server.Start(FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + // Wait until Ctrl-C is pressed, then Stop() and Join() the server. + server.RunUntilAskedToQuit(); + return 0; +} diff --git a/example/parallel_echo_c++/CMakeLists.txt b/example/parallel_echo_c++/CMakeLists.txt new file mode 100644 index 0000000..9fe5e66 --- /dev/null +++ b/example/parallel_echo_c++/CMakeLists.txt @@ -0,0 +1,115 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(parallel_echo_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER echo.proto) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(GPERFTOOLS_INCLUDE_DIR NAMES gperftools/heap-profiler.h) +find_library(GPERFTOOLS_LIBRARIES NAMES tcmalloc_and_profiler) + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +set(BRPC_EXAMPLE_ENABLE_CPU_PROFILER ON) + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(parallel_echo_client client.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(parallel_echo_client) +add_executable(parallel_echo_server server.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(parallel_echo_server) + +target_link_libraries(parallel_echo_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${GPERFTOOLS_LIBRARIES}) +target_link_libraries(parallel_echo_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${GPERFTOOLS_LIBRARIES}) diff --git a/example/parallel_echo_c++/client.cpp b/example/parallel_echo_c++/client.cpp new file mode 100644 index 0000000..54ce661 --- /dev/null +++ b/example/parallel_echo_c++/client.cpp @@ -0,0 +1,214 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending requests to server in parallel by multiple threads. + +#include +#include +#include +#include +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_int32(thread_num, 50, "Number of threads to send requests"); +DEFINE_int32(channel_num, 3, "Number of sub channels"); +DEFINE_bool(same_channel, false, "Add the same sub channel multiple times"); +DEFINE_bool(use_bthread, false, "Use bthread to send requests"); +DEFINE_int32(attachment_size, 0, "Carry so many byte attachment along with requests"); +DEFINE_int32(request_size, 16, "Bytes of each request"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(protocol, "baidu_std", "Protocol type. Defined in src/brpc/options.proto"); +DEFINE_string(server, "0.0.0.0:8002", "IP Address of server"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_bool(dont_fail, false, "Print fatal when some call failed"); +DEFINE_int32(dummy_port, -1, "Launch dummy server at this port"); + +std::string g_request; +std::string g_attachment; +bvar::LatencyRecorder g_latency_recorder("client"); +bvar::Adder g_error_count("client_error_count"); +bvar::LatencyRecorder* g_sub_channel_latency = NULL; + +static void* sender(void* arg) { + // Normally, you should not call a Channel directly, but instead construct + // a stub Service wrapping it. stub can be shared by all threads as well. + example::EchoService_Stub stub(static_cast(arg)); + + int log_id = 0; + while (!brpc::IsAskedToQuit()) { + // We will receive response synchronously, safe to put variables + // on stack. + example::EchoRequest request; + example::EchoResponse response; + brpc::Controller cntl; + + request.set_value(log_id++); + if (!g_attachment.empty()) { + // Set attachment which is wired to network directly instead of + // being serialized into protobuf messages. + cntl.request_attachment().append(g_attachment); + } + + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + stub.Echo(&cntl, &request, &response, NULL); + if (!cntl.Failed()) { + g_latency_recorder << cntl.latency_us(); + for (int i = 0; i < cntl.sub_count(); ++i) { + if (cntl.sub(i) && !cntl.sub(i)->Failed()) { + g_sub_channel_latency[i] << cntl.sub(i)->latency_us(); + } + } + } else { + g_error_count << 1; + CHECK(brpc::IsAskedToQuit() || !FLAGS_dont_fail) + << "error=" << cntl.ErrorText() << " latency=" << cntl.latency_us(); + // We can't connect to the server, sleep a while. Notice that this + // is a specific sleeping to prevent this thread from spinning too + // fast. You should continue the business logic in a production + // server rather than sleeping. + bthread_usleep(50000); + } + } + return NULL; +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::ParallelChannel channel; + brpc::ParallelChannelOptions pchan_options; + pchan_options.timeout_ms = FLAGS_timeout_ms; + if (channel.Init(&pchan_options) != 0) { + LOG(ERROR) << "Fail to init ParallelChannel"; + return -1; + } + + brpc::ChannelOptions sub_options; + sub_options.protocol = FLAGS_protocol; + sub_options.connection_type = FLAGS_connection_type; + sub_options.max_retry = FLAGS_max_retry; + // Setting sub_options.timeout_ms does not work because timeout of sub + // channels are disabled in ParallelChannel. + + if (FLAGS_same_channel) { + // For brpc >= 1.0.155.31351, a sub channel can be added into + // a ParallelChannel more than once. + brpc::Channel* sub_channel = new brpc::Channel; + // Initialize the channel, NULL means using default options. + // options, see `brpc/channel.h'. + if (sub_channel->Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), &sub_options) != 0) { + LOG(ERROR) << "Fail to initialize sub_channel"; + return -1; + } + for (int i = 0; i < FLAGS_channel_num; ++i) { + if (channel.AddChannel(sub_channel, brpc::OWNS_CHANNEL, + NULL, NULL) != 0) { + LOG(ERROR) << "Fail to AddChannel, i=" << i; + return -1; + } + } + } else { + for (int i = 0; i < FLAGS_channel_num; ++i) { + brpc::Channel* sub_channel = new brpc::Channel; + // Initialize the channel, NULL means using default options. + // options, see `brpc/channel.h'. + if (sub_channel->Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), &sub_options) != 0) { + LOG(ERROR) << "Fail to initialize sub_channel[" << i << "]"; + return -1; + } + if (channel.AddChannel(sub_channel, brpc::OWNS_CHANNEL, + NULL, NULL) != 0) { + LOG(ERROR) << "Fail to AddChannel, i=" << i; + return -1; + } + } + } + + // Initialize bvar for sub channel + g_sub_channel_latency = new bvar::LatencyRecorder[FLAGS_channel_num]; + for (int i = 0; i < FLAGS_channel_num; ++i) { + std::string name; + butil::string_printf(&name, "client_sub_%d", i); + g_sub_channel_latency[i].expose(name); + } + + if (FLAGS_attachment_size > 0) { + g_attachment.resize(FLAGS_attachment_size, 'a'); + } + if (FLAGS_request_size <= 0) { + LOG(ERROR) << "Bad request_size=" << FLAGS_request_size; + return -1; + } + g_request.resize(FLAGS_request_size, 'r'); + + if (FLAGS_dummy_port >= 0) { + brpc::StartDummyServerAt(FLAGS_dummy_port); + } + + std::vector bids; + std::vector pids; + if (!FLAGS_use_bthread) { + pids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (pthread_create(&pids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create pthread"; + return -1; + } + } + } else { + bids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (bthread_start_background( + &bids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create bthread"; + return -1; + } + } + } + + while (!brpc::IsAskedToQuit()) { + sleep(1); + LOG(INFO) << "Sending EchoRequest at qps=" << g_latency_recorder.qps(1) + << " latency=" << g_latency_recorder.latency(1) << noflush; + for (int i = 0; i < FLAGS_channel_num; ++i) { + LOG(INFO) << " latency_" << i << "=" + << g_sub_channel_latency[i].latency(1) + << noflush; + } + LOG(INFO); + } + + LOG(INFO) << "EchoClient is going to quit"; + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (!FLAGS_use_bthread) { + pthread_join(pids[i], NULL); + } else { + bthread_join(bids[i], NULL); + } + } + + return 0; +} diff --git a/example/parallel_echo_c++/echo.proto b/example/parallel_echo_c++/echo.proto new file mode 100644 index 0000000..e4765a9 --- /dev/null +++ b/example/parallel_echo_c++/echo.proto @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package example; + +option cc_generic_services = true; + +message EchoRequest { + required int32 value = 1; +}; + +message EchoResponse { + required int32 value = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; diff --git a/example/parallel_echo_c++/server.cpp b/example/parallel_echo_c++/server.cpp new file mode 100644 index 0000000..c57e001 --- /dev/null +++ b/example/parallel_echo_c++/server.cpp @@ -0,0 +1,84 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive EchoRequest and send back EchoResponse. + +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_bool(echo_attachment, true, "Echo attachment as well"); +DEFINE_int32(port, 8002, "TCP Port of this server"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); +DEFINE_int32(max_concurrency, 0, "Limit of request processing in parallel"); + +// Your implementation of example::EchoService +class EchoServiceImpl : public example::EchoService { +public: + EchoServiceImpl() {} + ~EchoServiceImpl() {} + void Echo(google::protobuf::RpcController* cntl_base, + const example::EchoRequest* request, + example::EchoResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = + static_cast(cntl_base); + + // Echo request and its attachment + response->set_value(request->value()); + if (FLAGS_echo_attachment) { + cntl->response_attachment().append(cntl->request_attachment()); + } + } +}; + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // Generally you only need one Server. + brpc::Server server; + + // Instance of your service. + EchoServiceImpl echo_service_impl; + + // Add the service into server. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + if (server.AddService(&echo_service_impl, + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + + // Start the server. + brpc::ServerOptions options; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + options.max_concurrency = FLAGS_max_concurrency; + if (server.Start(FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + // Wait until Ctrl-C is pressed, then Stop() and Join() the server. + server.RunUntilAskedToQuit(); + return 0; +} diff --git a/example/partition_echo_c++/CMakeLists.txt b/example/partition_echo_c++/CMakeLists.txt new file mode 100644 index 0000000..0501f5c --- /dev/null +++ b/example/partition_echo_c++/CMakeLists.txt @@ -0,0 +1,118 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(partition_echo_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER echo.proto) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(GPERFTOOLS_INCLUDE_DIR NAMES gperftools/heap-profiler.h) +find_library(GPERFTOOLS_LIBRARIES NAMES tcmalloc_and_profiler) + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +set(BRPC_EXAMPLE_ENABLE_CPU_PROFILER ON) + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(client client.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(client) +add_executable(server server.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(server) + +target_link_libraries(client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${GPERFTOOLS_LIBRARIES}) +target_link_libraries(server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${GPERFTOOLS_LIBRARIES}) + +file(COPY ${PROJECT_SOURCE_DIR}/server_list + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/example/partition_echo_c++/client.cpp b/example/partition_echo_c++/client.cpp new file mode 100644 index 0000000..b60a63d --- /dev/null +++ b/example/partition_echo_c++/client.cpp @@ -0,0 +1,215 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending requests to server in parallel by multiple threads. + +#include +#include +#include +#include +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_int32(thread_num, 1, "Number of threads to send requests"); +DEFINE_int32(partition_num, 3, "Number of partitions"); +DEFINE_bool(use_bthread, false, "Use bthread to send requests"); +DEFINE_int32(attachment_size, 0, "Carry so many byte attachment along with requests"); +DEFINE_int32(request_size, 16, "Bytes of each request"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(protocol, "baidu_std", "Protocol type. Defined in src/brpc/options.proto"); +DEFINE_string(server, "file://server_list", "Mapping to servers"); +DEFINE_string(load_balancer, "rr", "Name of load balancer"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_bool(dont_fail, false, "Print fatal when some call failed"); + +std::string g_request; +std::string g_attachment; +pthread_mutex_t g_latency_mutex = PTHREAD_MUTEX_INITIALIZER; +struct BAIDU_CACHELINE_ALIGNMENT SenderInfo { + size_t nsuccess; + int64_t latency_sum; +}; +std::deque g_sender_info; + +static void* sender(void* arg) { + // Normally, you should not call a Channel directly, but instead construct + // a stub Service wrapping it. stub can be shared by all threads as well. + example::EchoService_Stub stub(static_cast(arg)); + + SenderInfo* info = NULL; + { + BAIDU_SCOPED_LOCK(g_latency_mutex); + g_sender_info.push_back(SenderInfo()); + info = &g_sender_info.back(); + } + + int log_id = 0; + while (!brpc::IsAskedToQuit()) { + // We will receive response synchronously, safe to put variables + // on stack. + example::EchoRequest request; + example::EchoResponse response; + brpc::Controller cntl; + + request.set_message(g_request); + cntl.set_log_id(log_id++); // set by user + if (!g_attachment.empty()) { + // Set attachment which is wired to network directly instead of + // being serialized into protobuf messages. + cntl.request_attachment().append(g_attachment); + } + + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + stub.Echo(&cntl, &request, &response, NULL); + if (!cntl.Failed()) { + info->latency_sum += cntl.latency_us(); + ++info->nsuccess; + } else { + CHECK(brpc::IsAskedToQuit() || !FLAGS_dont_fail) + << "error=" << cntl.ErrorText() << " latency=" << cntl.latency_us(); + // We can't connect to the server, sleep a while. Notice that this + // is a specific sleeping to prevent this thread from spinning too + // fast. You should continue the business logic in a production + // server rather than sleeping. + bthread_usleep(50000); + } + } + return NULL; +} + +class MyPartitionParser : public brpc::PartitionParser { +public: + bool ParseFromTag(const std::string& tag, brpc::Partition* out) { + // "N/M" : #N partition of M partitions. + size_t pos = tag.find_first_of('/'); + if (pos == std::string::npos) { + LOG(ERROR) << "Invalid tag=`" << tag << '\''; + return false; + } + char* endptr = NULL; + out->index = strtol(tag.c_str(), &endptr, 10); + if (endptr != tag.data() + pos) { + LOG(ERROR) << "Invalid index=" << butil::StringPiece(tag.data(), pos); + return false; + } + out->num_partition_kinds = strtol(tag.c_str() + pos + 1, &endptr, 10); + if (endptr != tag.c_str() + tag.size()) { + LOG(ERROR) << "Invalid num=" << tag.data() + pos + 1; + return false; + } + return true; + } +}; + + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::PartitionChannel channel; + + brpc::PartitionChannelOptions options; + options.protocol = FLAGS_protocol; + options.connection_type = FLAGS_connection_type; + options.succeed_without_server = true; + options.fail_limit = 1; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + + if (channel.Init(FLAGS_partition_num, new MyPartitionParser(), + FLAGS_server.c_str(), + FLAGS_load_balancer.c_str(), + &options) != 0) { + LOG(ERROR) << "Fail to init channel"; + return -1; + } + if (FLAGS_attachment_size > 0) { + g_attachment.resize(FLAGS_attachment_size, 'a'); + } + if (FLAGS_request_size <= 0) { + LOG(ERROR) << "Bad request_size=" << FLAGS_request_size; + return -1; + } + g_request.resize(FLAGS_request_size, 'r'); + + std::vector bids; + std::vector pids; + if (!FLAGS_use_bthread) { + pids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (pthread_create(&pids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create pthread"; + return -1; + } + } + } else { + bids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (bthread_start_background( + &bids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create bthread"; + return -1; + } + } + } + + int64_t last_counter = 0; + int64_t last_latency_sum = 0; + std::vector last_nsuccess(FLAGS_thread_num); + while (!brpc::IsAskedToQuit()) { + sleep(1); + int64_t latency_sum = 0; + int64_t nsuccess = 0; + pthread_mutex_lock(&g_latency_mutex); + CHECK_EQ(g_sender_info.size(), (size_t)FLAGS_thread_num); + for (size_t i = 0; i < g_sender_info.size(); ++i) { + const SenderInfo& info = g_sender_info[i]; + latency_sum += info.latency_sum; + nsuccess += info.nsuccess; + if (FLAGS_dont_fail) { + CHECK(info.nsuccess > last_nsuccess[i]); + } + last_nsuccess[i] = info.nsuccess; + } + pthread_mutex_unlock(&g_latency_mutex); + + const int64_t avg_latency = (latency_sum - last_latency_sum) / + std::max(nsuccess - last_counter, (int64_t)1); + LOG(INFO) << "Sending EchoRequest at qps=" << nsuccess - last_counter + << " latency=" << avg_latency; + last_counter = nsuccess; + last_latency_sum = latency_sum; + } + + LOG(INFO) << "EchoClient is going to quit"; + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (!FLAGS_use_bthread) { + pthread_join(pids[i], NULL); + } else { + bthread_join(bids[i], NULL); + } + } + + return 0; +} diff --git a/example/partition_echo_c++/echo.proto b/example/partition_echo_c++/echo.proto new file mode 100644 index 0000000..2b39627 --- /dev/null +++ b/example/partition_echo_c++/echo.proto @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package example; + +option cc_generic_services = true; + +message EchoRequest { + required string message = 1; +}; + +message EchoResponse { + required string message = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; diff --git a/example/partition_echo_c++/server.cpp b/example/partition_echo_c++/server.cpp new file mode 100644 index 0000000..7dbcf8f --- /dev/null +++ b/example/partition_echo_c++/server.cpp @@ -0,0 +1,173 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive EchoRequest and send back EchoResponse. + +#include +#include +#include +#include +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_bool(echo_attachment, true, "Echo attachment as well"); +DEFINE_int32(port, 8002, "TCP Port of this server"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); +DEFINE_int32(max_concurrency, 0, "Limit of request processing in parallel"); +DEFINE_int32(server_num, 1, "Number of servers"); +DEFINE_string(sleep_us, "", "Sleep so many microseconds before responding"); +DEFINE_bool(spin, false, "spin rather than sleep"); +DEFINE_double(exception_ratio, 0.1, "Percentage of irregular latencies"); +DEFINE_double(min_ratio, 0.2, "min_sleep / sleep_us"); +DEFINE_double(max_ratio, 10, "max_sleep / sleep_us"); + +// Your implementation of example::EchoService +class EchoServiceImpl : public example::EchoService { +public: + EchoServiceImpl() : _index(0) {} + virtual ~EchoServiceImpl() {} + void set_index(size_t index, int64_t sleep_us) { + _index = index; + _sleep_us = sleep_us; + } + virtual void Echo(google::protobuf::RpcController* cntl_base, + const example::EchoRequest* request, + example::EchoResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = + static_cast(cntl_base); + if (_sleep_us > 0) { + double delay = _sleep_us; + const double a = FLAGS_exception_ratio * 0.5; + if (a >= 0.0001) { + double x = butil::RandDouble(); + if (x < a) { + const double min_sleep_us = FLAGS_min_ratio * _sleep_us; + delay = min_sleep_us + (_sleep_us - min_sleep_us) * x / a; + } else if (x + a > 1) { + const double max_sleep_us = FLAGS_max_ratio * _sleep_us; + delay = _sleep_us + (max_sleep_us - _sleep_us) * (x + a - 1) / a; + } + } + if (FLAGS_spin) { + int64_t end_time = butil::cpuwide_time_us() + (int64_t)delay; + while (butil::cpuwide_time_us() < end_time) {} + } else { + bthread_usleep((int64_t)delay); + } + } + + // Echo request and its attachment + response->set_message(request->message()); + if (FLAGS_echo_attachment) { + cntl->response_attachment().append(cntl->request_attachment()); + } + _nreq << 1; + } + + size_t num_requests() const { return _nreq.get_value(); } + +private: + size_t _index; + int64_t _sleep_us; + bvar::Adder _nreq; +}; + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_server_num <= 0) { + LOG(ERROR) << "server_num must be positive"; + return -1; + } + + // We need multiple servers in this example. + brpc::Server* servers = new brpc::Server[FLAGS_server_num]; + // For more options see `brpc/server.h'. + brpc::ServerOptions options; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + options.max_concurrency = FLAGS_max_concurrency; + + butil::StringSplitter sp(FLAGS_sleep_us.c_str(), ','); + std::vector sleep_list; + for (; sp; ++sp) { + sleep_list.push_back(strtoll(sp.field(), NULL, 10)); + } + if (sleep_list.empty()) { + sleep_list.push_back(0); + } + + // Instance of your services. + EchoServiceImpl* echo_service_impls = new EchoServiceImpl[FLAGS_server_num]; + // Add the service into servers. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + for (int i = 0; i < FLAGS_server_num; ++i) { + int64_t sleep_us = sleep_list[(size_t)i < sleep_list.size() ? i : (sleep_list.size() - 1)]; + echo_service_impls[i].set_index(i, sleep_us); + // will be shown on /version page + servers[i].set_version(butil::string_printf( + "example/dynamic_partition_echo_c++[%d]", i)); + if (servers[i].AddService(&echo_service_impls[i], + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + // Start the server. + int port = FLAGS_port + i; + if (servers[i].Start(port, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + } + + // Service logic are running in separate worker threads, for main thread, + // we don't have much to do, just spinning. + std::vector last_num_requests(FLAGS_server_num); + while (!brpc::IsAskedToQuit()) { + sleep(1); + + size_t cur_total = 0; + for (int i = 0; i < FLAGS_server_num; ++i) { + const size_t current_num_requests = + echo_service_impls[i].num_requests(); + size_t diff = current_num_requests - last_num_requests[i]; + cur_total += diff; + last_num_requests[i] = current_num_requests; + LOG(INFO) << "S[" << i << "]=" << diff << ' ' << noflush; + } + LOG(INFO) << "[total=" << cur_total << ']'; + } + + // Don't forget to stop and join the server otherwise still-running + // worker threads may crash your program. + for (int i = 0; i < FLAGS_server_num; ++i) { + servers[i].Stop(0/*not used now*/); + } + for (int i = 0; i < FLAGS_server_num; ++i) { + servers[i].Join(); + } + delete [] servers; + delete [] echo_service_impls; + return 0; +} diff --git a/example/rdma_performance/CMakeLists.txt b/example/rdma_performance/CMakeLists.txt new file mode 100644 index 0000000..02b20a5 --- /dev/null +++ b/example/rdma_performance/CMakeLists.txt @@ -0,0 +1,118 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(rdma_performance C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER test.proto) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +set(BRPC_EXAMPLE_WITH_RDMA ON) + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +find_path(RDMA_INCLUDE_PATH NAMES infiniband/verbs.h) +find_library(RDMA_LIB NAMES ibverbs) +if ((NOT RDMA_INCLUDE_PATH) OR (NOT RDMA_LIB)) + message(FATAL_ERROR "Fail to find ibverbs") +endif() + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(client client.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(client) +add_executable(server server.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(server) + +target_link_libraries(client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) +target_link_libraries(server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) diff --git a/example/rdma_performance/client.cpp b/example/rdma_performance/client.cpp new file mode 100644 index 0000000..2e8acc4 --- /dev/null +++ b/example/rdma_performance/client.cpp @@ -0,0 +1,321 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include "butil/atomicops.h" +#include "butil/fast_rand.h" +#include "butil/logging.h" +#include "brpc/rdma/rdma_helper.h" +#include "brpc/server.h" +#include "brpc/channel.h" +#include "bthread/bthread.h" +#include "bvar/latency_recorder.h" +#include "bvar/variable.h" +#include "test.pb.h" + +#ifdef BRPC_WITH_RDMA + +DEFINE_int32(thread_num, 0, "How many threads are used"); +DEFINE_int32(queue_depth, 1, "How many requests can be pending in the queue"); +DEFINE_int32(expected_qps, 0, "The expected QPS"); +DEFINE_int32(max_thread_num, 16, "The max number of threads are used"); +DEFINE_int32(attachment_size, -1, "Attachment size is used (in Bytes)"); +DEFINE_bool(echo_attachment, false, "Select whether attachment should be echo"); +DEFINE_string(connection_type, "single", "Connection type of the channel"); +DEFINE_string(protocol, "baidu_std", "Protocol type."); +DEFINE_string(servers, "0.0.0.0:8002+0.0.0.0:8002", "IP Address of servers"); +DEFINE_bool(use_rdma, true, "Use RDMA or not"); +DEFINE_int32(rpc_timeout_ms, 2000, "RPC call timeout"); +DEFINE_int32(test_seconds, 20, "Test running time"); +DEFINE_int32(test_iterations, 0, "Test iterations"); +DEFINE_int32(dummy_port, 8001, "Dummy server port number"); + +bvar::LatencyRecorder g_latency_recorder("client"); +bvar::LatencyRecorder g_server_cpu_recorder("server_cpu"); +bvar::LatencyRecorder g_client_cpu_recorder("client_cpu"); +butil::atomic g_last_time(0); +butil::atomic g_total_bytes; +butil::atomic g_total_cnt; +std::vector g_servers; +int rr_index = 0; +volatile bool g_stop = false; + +butil::atomic g_token(10000); + +static void* GenerateToken(void* arg) { + int64_t start_time = butil::monotonic_time_ns(); + int64_t accumulative_token = g_token.load(butil::memory_order_relaxed); + while (!g_stop) { + bthread_usleep(100000); + int64_t now = butil::monotonic_time_ns(); + if (accumulative_token * 1000000000 / (now - start_time) < FLAGS_expected_qps) { + int64_t delta = FLAGS_expected_qps * (now - start_time) / 1000000000 - accumulative_token; + g_token.fetch_add(delta, butil::memory_order_relaxed); + accumulative_token += delta; + } + } + return NULL; +} + +class PerformanceTest { +public: + PerformanceTest(int attachment_size, bool echo_attachment) + : _addr(NULL) + , _channel(NULL) + , _start_time(0) + , _iterations(0) + , _stop(false) + { + if (attachment_size > 0) { + _addr = malloc(attachment_size); + butil::fast_rand_bytes(_addr, attachment_size); + _attachment.append(_addr, attachment_size); + } + _echo_attachment = echo_attachment; + } + + ~PerformanceTest() { + if (_addr) { + free(_addr); + } + delete _channel; + } + + inline bool IsStop() { return _stop; } + + int Init() { + brpc::ChannelOptions options; + options.socket_mode = FLAGS_use_rdma? brpc::SOCKET_MODE_RDMA : brpc::SOCKET_MODE_TCP; + options.protocol = FLAGS_protocol; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_rpc_timeout_ms; + options.max_retry = 0; + std::string server = g_servers[(rr_index++) % g_servers.size()]; + _channel = new brpc::Channel(); + if (_channel->Init(server.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + brpc::Controller cntl; + test::PerfTestResponse response; + test::PerfTestRequest request; + request.set_echo_attachment(_echo_attachment); + test::PerfTestService_Stub stub(_channel); + stub.Test(&cntl, &request, &response, NULL); + if (cntl.Failed()) { + LOG(ERROR) << "RPC call failed: " << cntl.ErrorText(); + return -1; + } + return 0; + } + + struct RespClosure { + brpc::Controller* cntl; + test::PerfTestResponse* resp; + PerformanceTest* test; + }; + + void SendRequest() { + if (FLAGS_expected_qps > 0) { + while (g_token.load(butil::memory_order_relaxed) <= 0) { + bthread_usleep(10); + } + g_token.fetch_sub(1, butil::memory_order_relaxed); + } + RespClosure* closure = new RespClosure; + test::PerfTestRequest request; + closure->resp = new test::PerfTestResponse(); + closure->cntl = new brpc::Controller(); + request.set_echo_attachment(_echo_attachment); + closure->cntl->request_attachment().append(_attachment); + closure->test = this; + google::protobuf::Closure* done = brpc::NewCallback(&HandleResponse, closure); + test::PerfTestService_Stub stub(_channel); + stub.Test(closure->cntl, &request, closure->resp, done); + } + + static void HandleResponse(RespClosure* closure) { + std::unique_ptr cntl_guard(closure->cntl); + std::unique_ptr response_guard(closure->resp); + if (closure->cntl->Failed()) { + LOG(ERROR) << "RPC call failed: " << closure->cntl->ErrorText(); + closure->test->_stop = true; + return; + } + + g_latency_recorder << closure->cntl->latency_us(); + if (closure->resp->cpu_usage().size() > 0) { + g_server_cpu_recorder << atof(closure->resp->cpu_usage().c_str()) * 100; + } + g_total_bytes.fetch_add(closure->cntl->request_attachment().size(), butil::memory_order_relaxed); + g_total_cnt.fetch_add(1, butil::memory_order_relaxed); + + cntl_guard.reset(NULL); + response_guard.reset(NULL); + + if (closure->test->_iterations == 0 && FLAGS_test_iterations > 0) { + closure->test->_stop = true; + return; + } + --closure->test->_iterations; + uint64_t last = g_last_time.load(butil::memory_order_relaxed); + uint64_t now = butil::cpuwide_time_us(); + if (now > last && now - last > 100000) { + if (g_last_time.exchange(now, butil::memory_order_relaxed) == last) { + g_client_cpu_recorder << + atof(bvar::Variable::describe_exposed("process_cpu_usage").c_str()) * 100; + } + } + if (now - closure->test->_start_time > FLAGS_test_seconds * 1000000u) { + closure->test->_stop = true; + return; + } + closure->test->SendRequest(); + } + + static void* RunTest(void* arg) { + PerformanceTest* test = (PerformanceTest*)arg; + test->_start_time = butil::cpuwide_time_us(); + test->_iterations = FLAGS_test_iterations; + + for (int i = 0; i < FLAGS_queue_depth; ++i) { + test->SendRequest(); + } + + return NULL; + } + +private: + void* _addr; + brpc::Channel* _channel; + uint64_t _start_time; + uint32_t _iterations; + volatile bool _stop; + butil::IOBuf _attachment; + bool _echo_attachment; +}; + +static void* DeleteTest(void* arg) { + PerformanceTest* test = (PerformanceTest*)arg; + delete test; + return NULL; +} + +void Test(int thread_num, int attachment_size) { + std::cout << "[Threads: " << thread_num + << ", Depth: " << FLAGS_queue_depth + << ", Attachment: " << attachment_size << "B" + << ", RDMA: " << (FLAGS_use_rdma ? "yes" : "no") + << ", Echo: " << (FLAGS_echo_attachment ? "yes]" : "no]") + << std::endl; + g_total_bytes.store(0, butil::memory_order_relaxed); + g_total_cnt.store(0, butil::memory_order_relaxed); + std::vector tests; + for (int k = 0; k < thread_num; ++k) { + PerformanceTest* t = new PerformanceTest(attachment_size, FLAGS_echo_attachment); + if (t->Init() < 0) { + exit(1); + } + tests.push_back(t); + } + uint64_t start_time = butil::cpuwide_time_us(); + bthread_t tid[thread_num]; + if (FLAGS_expected_qps > 0) { + bthread_t tid; + bthread_start_background(&tid, &BTHREAD_ATTR_NORMAL, GenerateToken, NULL); + } + for (int k = 0; k < thread_num; ++k) { + bthread_start_background(&tid[k], &BTHREAD_ATTR_NORMAL, + PerformanceTest::RunTest, tests[k]); + } + for (int k = 0; k < thread_num; ++k) { + while (!tests[k]->IsStop()) { + bthread_usleep(10000); + } + } + uint64_t end_time = butil::cpuwide_time_us(); + double throughput = g_total_bytes / 1.048576 / (end_time - start_time); + if (FLAGS_test_iterations == 0) { + std::cout << "Avg-Latency: " << g_latency_recorder.latency(10) + << ", 90th-Latency: " << g_latency_recorder.latency_percentile(0.9) + << ", 99th-Latency: " << g_latency_recorder.latency_percentile(0.99) + << ", 99.9th-Latency: " << g_latency_recorder.latency_percentile(0.999) + << ", Throughput: " << throughput << "MB/s" + << ", QPS: " << (g_total_cnt.load(butil::memory_order_relaxed) * 1000 / (end_time - start_time)) << "k" + << ", Server CPU-utilization: " << g_server_cpu_recorder.latency(10) << "\%" + << ", Client CPU-utilization: " << g_client_cpu_recorder.latency(10) << "\%" + << std::endl; + } else { + std::cout << " Throughput: " << throughput << "MB/s" << std::endl; + } + g_stop = true; + for (int k = 0; k < thread_num; ++k) { + bthread_start_background(&tid[k], &BTHREAD_ATTR_NORMAL, DeleteTest, tests[k]); + } +} + +int main(int argc, char* argv[]) { + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // Initialize RDMA environment in advance. + if (FLAGS_use_rdma) { + brpc::rdma::GlobalRdmaInitializeOrDie(); + } + + brpc::StartDummyServerAt(FLAGS_dummy_port); + + std::string::size_type pos1 = 0; + std::string::size_type pos2 = FLAGS_servers.find('+'); + while (pos2 != std::string::npos) { + g_servers.push_back(FLAGS_servers.substr(pos1, pos2 - pos1)); + pos1 = pos2 + 1; + pos2 = FLAGS_servers.find('+', pos1); + } + g_servers.push_back(FLAGS_servers.substr(pos1)); + + if (FLAGS_thread_num > 0 && FLAGS_attachment_size >= 0) { + Test(FLAGS_thread_num, FLAGS_attachment_size); + } else if (FLAGS_thread_num <= 0 && FLAGS_attachment_size >= 0) { + for (int i = 1; i <= FLAGS_max_thread_num; i *= 2) { + Test(i, FLAGS_attachment_size); + } + } else if (FLAGS_thread_num > 0 && FLAGS_attachment_size < 0) { + for (int i = 1; i <= 1024; i *= 4) { + Test(FLAGS_thread_num, i); + } + } else { + for (int j = 1; j <= 1024; j *= 4) { + for (int i = 1; i <= FLAGS_max_thread_num; i *= 2) { + Test(i, j); + } + } + } + + return 0; +} + +#else + +int main(int argc, char* argv[]) { + LOG(ERROR) << " brpc is not compiled with rdma. To enable it, please refer to https://github.com/apache/brpc/blob/master/docs/en/rdma.md"; + return 0; +} + +#endif diff --git a/example/rdma_performance/server.cpp b/example/rdma_performance/server.cpp new file mode 100644 index 0000000..2e93e1e --- /dev/null +++ b/example/rdma_performance/server.cpp @@ -0,0 +1,97 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/atomicops.h" +#include "butil/logging.h" +#include "butil/time.h" +#include "brpc/server.h" +#include "bvar/variable.h" +#include "test.pb.h" + +#ifdef BRPC_WITH_RDMA + +DEFINE_int32(port, 8002, "TCP Port of this server"); +DEFINE_bool(use_rdma, true, "Use RDMA or not"); + +butil::atomic g_last_time(0); + +namespace test { +class PerfTestServiceImpl : public PerfTestService { +public: + PerfTestServiceImpl() {} + ~PerfTestServiceImpl() {} + + void Test(google::protobuf::RpcController* cntl_base, + const PerfTestRequest* request, + PerfTestResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + uint64_t last = g_last_time.load(butil::memory_order_relaxed); + uint64_t now = butil::monotonic_time_us(); + if (now > last && now - last > 100000) { + if (g_last_time.exchange(now, butil::memory_order_relaxed) == last) { + response->set_cpu_usage(bvar::Variable::describe_exposed("process_cpu_usage")); + } else { + response->set_cpu_usage(""); + } + } else { + response->set_cpu_usage(""); + } + if (request->echo_attachment()) { + brpc::Controller* cntl = + static_cast(cntl_base); + cntl->response_attachment().append(cntl->request_attachment()); + } + } +}; +} + +int main(int argc, char* argv[]) { + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + brpc::Server server; + test::PerfTestServiceImpl perf_test_service_impl; + + if (server.AddService(&perf_test_service_impl, + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + g_last_time.store(0, butil::memory_order_relaxed); + + brpc::ServerOptions options; + options.socket_mode = FLAGS_use_rdma? brpc::SOCKET_MODE_RDMA : brpc::SOCKET_MODE_TCP; + if (server.Start(FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + server.RunUntilAskedToQuit(); + return 0; +} + +#else + + +int main(int argc, char* argv[]) { + LOG(ERROR) << " brpc is not compiled with rdma. To enable it, please refer to https://github.com/apache/brpc/blob/master/docs/en/rdma.md"; + return 0; +} + +#endif diff --git a/example/rdma_performance/test.proto b/example/rdma_performance/test.proto new file mode 100644 index 0000000..8e33009 --- /dev/null +++ b/example/rdma_performance/test.proto @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +option cc_generic_services = true; + +package test; + +message PerfTestRequest { + required bool echo_attachment = 1; +}; + +message PerfTestResponse { + required string cpu_usage = 1; +}; + +service PerfTestService { + rpc Test(PerfTestRequest) returns (PerfTestResponse); +}; diff --git a/example/redis_c++/CMakeLists.txt b/example/redis_c++/CMakeLists.txt new file mode 100644 index 0000000..93a9cce --- /dev/null +++ b/example/redis_c++/CMakeLists.txt @@ -0,0 +1,129 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(redis_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +# Install dependencies: +# With apt: +# sudo apt-get install libreadline-dev +# sudo apt-get install ncurses-dev +# With yum: +# sudo yum install readline-devel +# sudo yum install ncurses-devel + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) + +find_path(GPERFTOOLS_INCLUDE_DIR NAMES gperftools/heap-profiler.h) +find_library(GPERFTOOLS_LIBRARIES NAMES tcmalloc_and_profiler) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + ${GPERFTOOLS_LIBRARIES} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(redis_cli redis_cli.cpp) +brpc_example_configure_target(redis_cli) +add_executable(redis_press redis_press.cpp) +brpc_example_configure_target(redis_press) +add_executable(redis_server redis_server.cpp) +brpc_example_configure_target(redis_server) +add_executable(redis_cluster_client redis_cluster_client.cpp) +brpc_example_configure_target(redis_cluster_client) + +set(AUX_LIB readline ncurses) + +target_link_libraries(redis_cli PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${AUX_LIB}) +target_link_libraries(redis_press PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) +target_link_libraries(redis_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) +target_link_libraries(redis_cluster_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) diff --git a/example/redis_c++/redis_cli.cpp b/example/redis_c++/redis_cli.cpp new file mode 100644 index 0000000..02124aa --- /dev/null +++ b/example/redis_c++/redis_cli.cpp @@ -0,0 +1,159 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A brpc based command-line interface to talk with redis-server + +#include +#include +#include +#include +#include +#include +#include +#include + +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(server, "127.0.0.1:6379", "IP Address of server"); +DEFINE_int32(timeout_ms, 1000, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); + +namespace brpc { +const char* logo(); +} + +// Send `command' to redis-server via `channel' +static bool access_redis(brpc::Channel& channel, const char* command) { + brpc::RedisRequest request; + if (!request.AddCommand(command)) { + LOG(ERROR) << "Fail to add command"; + return false; + } + brpc::RedisResponse response; + brpc::Controller cntl; + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + if (cntl.Failed()) { + LOG(ERROR) << "Fail to access redis, " << cntl.ErrorText(); + return false; + } else { + std::cout << response << std::endl; + return true; + } +} + +// For freeing the memory returned by readline(). +struct Freer { + void operator()(char* mem) { + free(mem); + } +}; + +static void dummy_handler(int) {} + +// The getc for readline. The default getc retries reading when meeting +// EINTR, which is not what we want. +static bool g_canceled = false; +static int cli_getc(FILE *stream) { + int c = getc(stream); + if (c == EOF && errno == EINTR) { + g_canceled = true; + return '\n'; + } + return c; +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_REDIS; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + if (channel.Init(FLAGS_server.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + if (argc <= 1) { // interactive mode + // We need this dummy signal hander to interrupt getc (and returning + // EINTR), SIG_IGN did not work. + signal(SIGINT, dummy_handler); + + // Hook getc of readline. + rl_getc_function = cli_getc; + + // Print welcome information. + printf("%s\n", brpc::logo()); + printf("This command-line tool mimics the look-n-feel of official " + "redis-cli, as a demostration of brpc's capability of" + " talking to redis-server. The output and behavior is " + "not exactly same with the official one.\n\n"); + + for (;;) { + char prompt[64]; + snprintf(prompt, sizeof(prompt), "redis %s> ", FLAGS_server.c_str()); + std::unique_ptr command(readline(prompt)); + if (command == NULL || *command == '\0') { + if (g_canceled) { + // No input after the prompt and user pressed Ctrl-C, + // quit the CLI. + return 0; + } + // User entered an empty command by just pressing Enter. + continue; + } + if (g_canceled) { + // User entered sth. and pressed Ctrl-C, start a new prompt. + g_canceled = false; + continue; + } + // Add user's command to history so that it's browse-able by + // UP-key and search-able by Ctrl-R. + add_history(command.get()); + + if (!strcmp(command.get(), "help")) { + printf("This is a redis CLI written in brpc.\n"); + continue; + } + if (!strcmp(command.get(), "quit")) { + // Although quit is a valid redis command, it does not make + // too much sense to run it in this CLI, just quit. + return 0; + } + access_redis(channel, command.get()); + } + } else { + std::string command; + command.reserve(argc * 16); + for (int i = 1; i < argc; ++i) { + if (i != 1) { + command.push_back(' '); + } + command.append(argv[i]); + } + if (!access_redis(channel, command.c_str())) { + return -1; + } + } + return 0; +} diff --git a/example/redis_c++/redis_cluster_client.cpp b/example/redis_c++/redis_cluster_client.cpp new file mode 100644 index 0000000..2a9f43b --- /dev/null +++ b/example/redis_c++/redis_cluster_client.cpp @@ -0,0 +1,119 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A basic client for native Redis Cluster using brpc::RedisClusterChannel. + +#include +#include +#include +#include +#include +#include + +DEFINE_string(seeds, "127.0.0.1:7000,127.0.0.1:7001", + "Comma-separated redis cluster seed endpoints"); +DEFINE_string(key_prefix, "brpc_cluster_demo", "Prefix for demo keys"); +DEFINE_int32(timeout_ms, 1000, "RPC timeout in milliseconds"); +DEFINE_int32(rpc_max_retry, 1, "Max retries for a single sub RPC"); +DEFINE_int32(max_redirect, 5, "Max MOVED/ASK redirect retries"); +DEFINE_int32(refresh_interval_s, 30, "Periodic topology refresh interval"); +DEFINE_int32(topology_refresh_timeout_ms, 1000, + "Timeout of CLUSTER SLOTS/NODES request"); +DEFINE_bool(disable_periodic_refresh, false, "Disable periodic topology refresh"); + +namespace { + +class Done : public google::protobuf::Closure { +public: + explicit Done(bthread::CountdownEvent* event) : _event(event) {} + void Run() override { _event->signal(); } + +private: + bthread::CountdownEvent* _event; +}; + +int PrintResponse(const brpc::RedisResponse& response) { + for (int i = 0; i < response.reply_size(); ++i) { + const brpc::RedisReply& reply = response.reply(i); + if (reply.is_error()) { + LOG(ERROR) << "reply[" << i << "] error=" << reply.error_message(); + return -1; + } + LOG(INFO) << "reply[" << i << "] " << reply; + } + return 0; +} + +} // namespace + +int main(int argc, char* argv[]) { + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + brpc::RedisClusterChannelOptions options; + options.max_redirect = FLAGS_max_redirect; + options.refresh_interval_s = FLAGS_refresh_interval_s; + options.enable_periodic_refresh = !FLAGS_disable_periodic_refresh; + options.topology_refresh_timeout_ms = FLAGS_topology_refresh_timeout_ms; + options.channel_options.timeout_ms = FLAGS_timeout_ms; + options.channel_options.max_retry = FLAGS_rpc_max_retry; + + brpc::RedisClusterChannel channel; + if (channel.Init(FLAGS_seeds, &options) != 0) { + LOG(ERROR) << "Fail to init redis cluster channel, seeds=" << FLAGS_seeds; + return -1; + } + + const std::string key1 = FLAGS_key_prefix + "_1"; + const std::string key2 = FLAGS_key_prefix + "_2"; + + // Sync pipeline. + brpc::RedisRequest request; + brpc::RedisResponse response; + brpc::Controller cntl; + CHECK(request.AddCommand("set %s v1", key1.c_str())); + CHECK(request.AddCommand("set %s v2", key2.c_str())); + CHECK(request.AddCommand("mget %s %s", key1.c_str(), key2.c_str())); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + if (cntl.Failed()) { + LOG(ERROR) << "Sync call failed: " << cntl.ErrorText(); + return -1; + } + if (PrintResponse(response) != 0) { + return -1; + } + + // Async single request. + brpc::RedisRequest async_request; + brpc::RedisResponse async_response; + brpc::Controller async_cntl; + CHECK(async_request.AddCommand("get %s", key1.c_str())); + + bthread::CountdownEvent event(1); + Done done(&event); + channel.CallMethod(NULL, &async_cntl, &async_request, &async_response, &done); + event.wait(); + if (async_cntl.Failed()) { + LOG(ERROR) << "Async call failed: " << async_cntl.ErrorText(); + return -1; + } + if (PrintResponse(async_response) != 0) { + return -1; + } + + LOG(INFO) << "Redis cluster demo finished"; + return 0; +} diff --git a/example/redis_c++/redis_press.cpp b/example/redis_c++/redis_press.cpp new file mode 100644 index 0000000..165b8bf --- /dev/null +++ b/example/redis_c++/redis_press.cpp @@ -0,0 +1,188 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A multi-threaded client getting keys from a redis-server constantly. + +#include +#include +#include +#include +#include +#include +#include +#include + +DEFINE_int32(thread_num, 50, "Number of threads to send requests"); +DEFINE_bool(use_bthread, false, "Use bthread to send requests"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(server, "0.0.0.0:6379", "IP Address of server"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_bool(dont_fail, false, "Print fatal when some call failed"); +DEFINE_string(key, "hello", "The key to be get"); +DEFINE_string(value, "world", "The value associated with the key"); +DEFINE_int32(batch, 1, "Pipelined Operations"); +DEFINE_int32(dummy_port, -1, "port of dummy server(for monitoring)"); +DEFINE_int32(backup_request_ms, -1, "Timeout for sending a backup request"); + +bvar::LatencyRecorder g_latency_recorder("client"); +bvar::Adder g_error_count("client_error_count"); + +struct SenderArgs { + int base_index; + brpc::Channel* redis_channel; +}; + +static void* sender(void* void_args) { + SenderArgs* args = (SenderArgs*)void_args; + + std::string value; + std::vector > kvs; + kvs.resize(FLAGS_batch); + for (int i = 0; i < FLAGS_batch; ++i) { + kvs[i].first = butil::string_printf( + "%s_%04d", FLAGS_key.c_str(), args->base_index + i); + kvs[i].second = butil::string_printf( + "%s_%04d", FLAGS_value.c_str(), args->base_index + i); + } + + brpc::RedisRequest request; + for (int i = 0; i < FLAGS_batch; ++i) { + CHECK(request.AddCommand("GET %s", kvs[i].first.c_str())); + } + while (!brpc::IsAskedToQuit()) { + // We will receive response synchronously, safe to put variables + // on stack. + brpc::RedisResponse response; + brpc::Controller cntl; + + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + args->redis_channel->CallMethod(NULL, &cntl, &request, &response, NULL); + const int64_t elp = cntl.latency_us(); + if (!cntl.Failed()) { + g_latency_recorder << elp; + CHECK_EQ(response.reply_size(), FLAGS_batch); + for (int i = 0; i < FLAGS_batch; ++i) { + CHECK_EQ(kvs[i].second.c_str(), response.reply(i).data()) + << "base=" << args->base_index << " i=" << i; + } + } else { + g_error_count << 1; + CHECK(brpc::IsAskedToQuit() || !FLAGS_dont_fail) + << "error=" << cntl.ErrorText() << " latency=" << elp; + // We can't connect to the server, sleep a while. Notice that this + // is a specific sleeping to prevent this thread from spinning too + // fast. You should continue the business logic in a production + // server rather than sleeping. + bthread_usleep(50000); + } + } + return NULL; +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_REDIS; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + options.backup_request_ms = FLAGS_backup_request_ms; + if (channel.Init(FLAGS_server.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + // Pipeline #batch * #thread_num SET requests into redis so that we + // have keys to get. + brpc::RedisRequest request; + brpc::RedisResponse response; + brpc::Controller cntl; + for (int i = 0; i < FLAGS_batch * FLAGS_thread_num; ++i) { + if (!request.AddCommand("SET %s_%04d %s_%04d", + FLAGS_key.c_str(), i, + FLAGS_value.c_str(), i)) { + LOG(ERROR) << "Fail to SET " << i << "th request"; + return -1; + } + } + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + if (cntl.Failed()) { + LOG(ERROR) << "Fail to access redis, " << cntl.ErrorText(); + return -1; + } + if (FLAGS_batch * FLAGS_thread_num != response.reply_size()) { + LOG(ERROR) << "Fail to set"; + return -1; + } + for (int i = 0; i < FLAGS_batch * FLAGS_thread_num; ++i) { + CHECK_EQ("OK", response.reply(i).data()); + } + LOG(INFO) << "Set " << FLAGS_batch * FLAGS_thread_num << " values"; + + if (FLAGS_dummy_port >= 0) { + brpc::StartDummyServerAt(FLAGS_dummy_port); + } + + std::vector bids; + std::vector pids; + bids.resize(FLAGS_thread_num); + pids.resize(FLAGS_thread_num); + std::vector args; + args.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + args[i].base_index = i * FLAGS_batch; + args[i].redis_channel = &channel; + if (!FLAGS_use_bthread) { + if (pthread_create(&pids[i], NULL, sender, &args[i]) != 0) { + LOG(ERROR) << "Fail to create pthread"; + return -1; + } + } else { + if (bthread_start_background( + &bids[i], NULL, sender, &args[i]) != 0) { + LOG(ERROR) << "Fail to create bthread"; + return -1; + } + } + } + + while (!brpc::IsAskedToQuit()) { + sleep(1); + + LOG(INFO) << "Accessing redis-server at qps=" << g_latency_recorder.qps(1) + << " latency=" << g_latency_recorder.latency(1); + } + + LOG(INFO) << "redis_client is going to quit"; + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (!FLAGS_use_bthread) { + pthread_join(pids[i], NULL); + } else { + bthread_join(bids[i], NULL); + } + } + return 0; +} diff --git a/example/redis_c++/redis_server.cpp b/example/redis_c++/redis_server.cpp new file mode 100644 index 0000000..a53ee26 --- /dev/null +++ b/example/redis_c++/redis_server.cpp @@ -0,0 +1,219 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. +// +// A brpc based redis-server. Currently just implement set and +// get, but it's sufficient that you can get the idea how to +// implement brpc::RedisCommandHandler. + +#include +#include +#include +#include +#include +#include +#include + +#include + +DEFINE_int32(port, 6379, "TCP Port of this server"); + +class AuthSession : public brpc::Destroyable { +public: + explicit AuthSession(const std::string& user_name, const std::string& password) + : _user_name(user_name), _password(password) {} + + void Destroy() override { + delete this; + } + + const std::string _user_name; + const std::string _password; +}; + +class RedisServiceImpl : public brpc::RedisService { +public: + RedisServiceImpl() { + _user_password["db1"] = "123456"; + _user_password["db2"] = "123456"; + _db_map["db1"].resize(kHashSlotNum); + _db_map["db2"].resize(kHashSlotNum); + } + + bool Set(const std::string& db_name, const std::string& key, const std::string& value) { + int slot = butil::crc32c::Value(key.c_str(), key.size()) % kHashSlotNum; + _mutex[slot].lock(); + auto& kv = _db_map[db_name]; + kv[slot][key] = value; + _mutex[slot].unlock(); + return true; + } + + bool Auth(const std::string& db_name, const std::string& password) { + if (_user_password.find(db_name) == _user_password.end()) { + return false; + } else { + if (_user_password[db_name] != password) { + return false; + } + } + return true; + } + + bool Get(const std::string& db_name, const std::string& key, std::string* value) { + int slot = butil::crc32c::Value(key.c_str(), key.size()) % kHashSlotNum; + _mutex[slot].lock(); + auto& kv = _db_map[db_name]; + auto it = kv[slot].find(key); + if (it == kv[slot].end()) { + _mutex[slot].unlock(); + return false; + } + *value = it->second; + _mutex[slot].unlock(); + return true; + } + +private: + const static int kHashSlotNum = 32; + typedef std::unordered_map KVStore; + std::unordered_map> _db_map; + std::unordered_map _user_password; + butil::Mutex _mutex[kHashSlotNum]; +}; + +class GetCommandHandler : public brpc::RedisCommandHandler { +public: + explicit GetCommandHandler(RedisServiceImpl* rsimpl) + : _rsimpl(rsimpl) {} + + brpc::RedisCommandHandlerResult Run(brpc::RedisConnContext* ctx, + const std::vector& args, + brpc::RedisReply* output, + bool /*flush_batched*/) override { + + AuthSession* session = static_cast(ctx->get_session()); + if (session == nullptr) { + output->FormatError("No auth session"); + return brpc::REDIS_CMD_HANDLED; + } + if (session->_user_name.empty()) { + output->FormatError("No user name"); + return brpc::REDIS_CMD_HANDLED; + } + if (args.size() != 2ul) { + output->FormatError("Expect 1 arg for 'get', actually %lu", args.size()-1); + return brpc::REDIS_CMD_HANDLED; + } + const std::string key(args[1].data(), args[1].size()); + std::string value; + if (_rsimpl->Get(session->_user_name, key, &value)) { + output->SetString(value); + } else { + output->SetNullString(); + } + return brpc::REDIS_CMD_HANDLED; + } + +private: + RedisServiceImpl* _rsimpl; +}; + +class SetCommandHandler : public brpc::RedisCommandHandler { +public: + explicit SetCommandHandler(RedisServiceImpl* rsimpl) + : _rsimpl(rsimpl) {} + + brpc::RedisCommandHandlerResult Run(brpc::RedisConnContext* ctx, + const std::vector& args, + brpc::RedisReply* output, + bool /*flush_batched*/) override { + AuthSession* session = static_cast(ctx->get_session()); + if (session == nullptr) { + output->FormatError("No auth session"); + return brpc::REDIS_CMD_HANDLED; + } + if (session->_user_name.empty()) { + output->FormatError("No user name"); + return brpc::REDIS_CMD_HANDLED; + } + if (args.size() != 3ul) { + output->FormatError("Expect 2 args for 'set', actually %lu", args.size()-1); + return brpc::REDIS_CMD_HANDLED; + } + const std::string key(args[1].data(), args[1].size()); + const std::string value(args[2].data(), args[2].size()); + _rsimpl->Set(session->_user_name, key, value); + output->SetStatus("OK"); + return brpc::REDIS_CMD_HANDLED; + } + +private: + RedisServiceImpl* _rsimpl; +}; + + + +class AuthCommandHandler : public brpc::RedisCommandHandler { +public: + explicit AuthCommandHandler(RedisServiceImpl* rsimpl) + : _rsimpl(rsimpl) {} + brpc::RedisCommandHandlerResult Run(brpc::RedisConnContext* ctx, + const std::vector& args, + brpc::RedisReply* output, + bool /*flush_batched*/) override { + if (args.size() != 3ul) { + output->FormatError("Expect 2 args for 'auth', actually %lu", args.size()-1); + return brpc::REDIS_CMD_HANDLED; + } + + const std::string db_name(args[1].data(), args[1].size()); + const std::string password(args[2].data(), args[2].size()); + + if (_rsimpl->Auth(db_name, password)) { + output->SetStatus("OK"); + auto auth_session = new AuthSession(db_name, password); + ctx->reset_session(auth_session); + } else { + output->FormatError("Invalid password for database '%s'", db_name.c_str()); + } + return brpc::REDIS_CMD_HANDLED; + } + +private: + RedisServiceImpl* _rsimpl; +}; + +int main(int argc, char* argv[]) { + google::ParseCommandLineFlags(&argc, &argv, true); + RedisServiceImpl *rsimpl = new RedisServiceImpl; + auto get_handler =std::unique_ptr(new GetCommandHandler(rsimpl)); + auto set_handler =std::unique_ptr( new SetCommandHandler(rsimpl)); + auto auth_handler = std::unique_ptr(new AuthCommandHandler(rsimpl)); + rsimpl->AddCommandHandler("get", get_handler.get()); + rsimpl->AddCommandHandler("set", set_handler.get()); + rsimpl->AddCommandHandler("auth", auth_handler.get()); + + brpc::Server server; + brpc::ServerOptions server_options; + server_options.redis_service = rsimpl; + if (server.Start(FLAGS_port, &server_options) != 0) { + LOG(ERROR) << "Fail to start server"; + return -1; + } + server.RunUntilAskedToQuit(); + return 0; +} diff --git a/example/rpcz_echo_c++/CMakeLists.txt b/example/rpcz_echo_c++/CMakeLists.txt new file mode 100644 index 0000000..9ca90dc --- /dev/null +++ b/example/rpcz_echo_c++/CMakeLists.txt @@ -0,0 +1,111 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(rpcz_echo_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER echo.proto) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(rpcz_echo_client client.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(rpcz_echo_client) +add_executable(rpcz_echo_server server.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(rpcz_echo_server) + +target_link_libraries(rpcz_echo_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) +target_link_libraries(rpcz_echo_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) + diff --git a/example/rpcz_echo_c++/client.cpp b/example/rpcz_echo_c++/client.cpp new file mode 100644 index 0000000..8cb8686 --- /dev/null +++ b/example/rpcz_echo_c++/client.cpp @@ -0,0 +1,96 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending requests to server every 1 second. + +#include +#include +#include +#include +#include "echo.pb.h" + +namespace brpc { +DECLARE_bool(enable_rpcz); +} +DEFINE_string(attachment, "", "Carry this along with requests"); +DEFINE_string(protocol, "baidu_std", "Protocol type. Defined in src/brpc/options.proto"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(server, "0.0.0.0:8000", "IP Address of server"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_int32(interval_ms, 1000, "Milliseconds between consecutive requests"); + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // brpc::FLAGS_enable_rpcz = true; + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = FLAGS_protocol; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + if (channel.Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + // Normally, you should not call a Channel directly, but instead construct + // a stub Service wrapping it. stub can be shared by all threads as well. + example::EchoService_Stub stub(&channel); + + // Send a request and wait for the response every 1 second. + int log_id = 0; + while (!brpc::IsAskedToQuit()) { + // We will receive response synchronously, safe to put variables + // on stack. + example::EchoRequest request; + example::EchoResponse response; + brpc::Controller cntl; + + request.set_message("hello world"); + + cntl.set_log_id(log_id ++); // set by user + // Set attachment which is wired to network directly instead of + // being serialized into protobuf messages. + cntl.request_attachment().append(FLAGS_attachment); + + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + stub.Echo(&cntl, &request, &response, NULL); + if (!cntl.Failed()) { + LOG(INFO) << "Received response from " << cntl.remote_side() + << " to " << cntl.local_side() + << ": " << response.message() << " (attached=" + << cntl.response_attachment() << ")" + << " latency=" << cntl.latency_us() << "us"; + } else { + LOG(WARNING) << cntl.ErrorText(); + } + usleep(FLAGS_interval_ms * 1000L); + } + + LOG(INFO) << "EchoClient is going to quit"; + return 0; +} + diff --git a/example/rpcz_echo_c++/echo.proto b/example/rpcz_echo_c++/echo.proto new file mode 100644 index 0000000..2b39627 --- /dev/null +++ b/example/rpcz_echo_c++/echo.proto @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package example; + +option cc_generic_services = true; + +message EchoRequest { + required string message = 1; +}; + +message EchoResponse { + required string message = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; diff --git a/example/rpcz_echo_c++/server.cpp b/example/rpcz_echo_c++/server.cpp new file mode 100644 index 0000000..8503e99 --- /dev/null +++ b/example/rpcz_echo_c++/server.cpp @@ -0,0 +1,171 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive EchoRequest and send back EchoResponse. + +#include +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_bool(echo_attachment, true, "Echo attachment as well"); +DEFINE_int32(port, 8000, "TCP Port of this server"); +DEFINE_string(listen_addr, "", + "Server listen address, may be IPV4/IPV6/UDS." + " If this is set, the flag port will be ignored"); +DEFINE_int32(idle_timeout_s, -1, + "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); +DEFINE_string(server, "0.0.0.0:8001", "IP Address of server"); +DEFINE_string(protocol, "baidu_std", "Protocol type. Defined in src/brpc/options.proto"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +// Your implementation of example::EchoService +// Notice that implementing brpc::Describable grants the ability to put +// additional information in /status. +namespace example { + +static const bthread_attr_t BTHREAD_ATTR_NORMAL_WITH_SPAN = { + BTHREAD_STACKTYPE_NORMAL, BTHREAD_INHERIT_SPAN, NULL, BTHREAD_TAG_INVALID}; + +void* RunThreadFunc(void*) { + TRACEPRINTF("RunThreadFunc %lu", bthread_self()); + // brpc::FLAGS_enable_rpcz = true; + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = FLAGS_protocol; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms /*milliseconds*/; + options.max_retry = FLAGS_max_retry; + if (channel.Init(FLAGS_server.c_str(), "", &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return nullptr; + } + example::EchoService_Stub stub(&channel); + // We will receive response synchronously, safe to put variables + // on stack. + example::EchoRequest request; + example::EchoResponse response; + brpc::Controller cntl; + request.set_message("hello world"); + + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + stub.Echo(&cntl, &request, &response, NULL); + if (!cntl.Failed()) { + LOG(INFO) << "Received response from " << cntl.remote_side() << " to " << cntl.local_side() + << ": " << response.message() << " (attached=" << cntl.response_attachment() + << ")" + << " latency=" << cntl.latency_us() << "us"; + } else { + LOG(WARNING) << cntl.ErrorText(); + } + + return nullptr; +} + +class EchoServiceImpl : public EchoService { +public: + EchoServiceImpl() {} + virtual ~EchoServiceImpl() {} + virtual void Echo(google::protobuf::RpcController* cntl_base, const EchoRequest* request, + EchoResponse* response, google::protobuf::Closure* done) { + bthread_list_t list; + bthread_list_init(&list, 0, 0); + for (int i = 0; i < 2; ++i) { + bthread_t tid; + bthread_start_background(&tid, &BTHREAD_ATTR_NORMAL_WITH_SPAN, RunThreadFunc, nullptr); + bthread_list_add(&list, tid); + } + bthread_list_join(&list); + + TRACEPRINTF("Handle request"); + + // This object helps you to call done->Run() in RAII style. If you need + // to process the request asynchronously, pass done_guard.release(). + brpc::ClosureGuard done_guard(done); + + brpc::Controller* cntl = static_cast(cntl_base); + + // The purpose of following logs is to help you to understand + // how clients interact with servers more intuitively. You should + // remove these logs in performance-sensitive servers. + LOG(INFO) << "Received request[log_id=" << cntl->log_id() << "] from " + << cntl->remote_side() << " to " << cntl->local_side() << ": " + << request->message() << " (attached=" << cntl->request_attachment() << ")"; + + // Fill response. + response->set_message(request->message()); + + // You can compress the response by setting Controller, but be aware + // that compression may be costly, evaluate before turning on. + // cntl->set_response_compress_type(brpc::COMPRESS_TYPE_GZIP); + + if (FLAGS_echo_attachment) { + // Set attachment which is wired to network directly instead of + // being serialized into protobuf messages. + cntl->response_attachment().append(cntl->request_attachment()); + } + } +}; +} // namespace example + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // Generally you only need one Server. + brpc::Server server; + + // Instance of your service. + example::EchoServiceImpl echo_service_impl; + + // Add the service into server. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + if (server.AddService(&echo_service_impl, brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + + butil::EndPoint point; + if (!FLAGS_listen_addr.empty()) { + if (butil::str2endpoint(FLAGS_listen_addr.c_str(), &point) < 0) { + LOG(ERROR) << "Invalid listen address:" << FLAGS_listen_addr; + return -1; + } + } else { + point = butil::EndPoint(butil::IP_ANY, FLAGS_port); + } + // Start the server. + brpc::ServerOptions options; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + if (server.Start(point, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + // Wait until Ctrl-C is pressed, then Stop() and Join() the server. + server.RunUntilAskedToQuit(); + return 0; +} diff --git a/example/selective_echo_c++/CMakeLists.txt b/example/selective_echo_c++/CMakeLists.txt new file mode 100644 index 0000000..2299002 --- /dev/null +++ b/example/selective_echo_c++/CMakeLists.txt @@ -0,0 +1,115 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(selective_echo_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER echo.proto) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(GPERFTOOLS_INCLUDE_DIR NAMES gperftools/heap-profiler.h) +find_library(GPERFTOOLS_LIBRARIES NAMES tcmalloc_and_profiler) + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +set(BRPC_EXAMPLE_ENABLE_CPU_PROFILER ON) + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(selective_echo_client client.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(selective_echo_client) +add_executable(selective_echo_server server.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(selective_echo_server) + +target_link_libraries(selective_echo_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${GPERFTOOLS_LIBRARIES}) +target_link_libraries(selective_echo_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${GPERFTOOLS_LIBRARIES}) diff --git a/example/selective_echo_c++/client.cpp b/example/selective_echo_c++/client.cpp new file mode 100644 index 0000000..6f0c413 --- /dev/null +++ b/example/selective_echo_c++/client.cpp @@ -0,0 +1,240 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending requests to server in parallel by multiple threads. + +#include +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_int32(thread_num, 50, "Number of threads to send requests"); +DEFINE_bool(use_bthread, false, "Use bthread to send requests"); +DEFINE_int32(attachment_size, 0, "Carry so many byte attachment along with requests"); +DEFINE_int32(request_size, 16, "Bytes of each request"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(protocol, "baidu_std", "Protocol type. Defined in src/brpc/options.proto"); +DEFINE_string(starting_server, "0.0.0.0:8114", "IP Address of the first server, port of i-th server is `first-port + i'"); +DEFINE_string(load_balancer, "rr", "Name of load balancer"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(backup_ms, -1, "backup timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_bool(dont_fail, false, "Print fatal when some call failed"); + +std::string g_request; +std::string g_attachment; + +bvar::LatencyRecorder g_latency_recorder("client"); +bvar::Adder g_error_count("client_error_count"); + +static void* sender(void* arg) { + // Normally, you should not call a Channel directly, but instead construct + // a stub Service wrapping it. stub can be shared by all threads as well. + example::EchoService_Stub stub(static_cast(arg)); + + int log_id = 0; + while (!brpc::IsAskedToQuit()) { + // We will receive response synchronously, safe to put variables + // on stack. + example::EchoRequest request; + example::EchoResponse response; + brpc::Controller cntl; + + request.set_message(g_request); + cntl.set_log_id(log_id++); // set by user + + if (!g_attachment.empty()) { + // Set attachment which is wired to network directly instead of + // being serialized into protobuf messages. + cntl.request_attachment().append(g_attachment); + } + + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + stub.Echo(&cntl, &request, &response, NULL); + const int64_t elp = cntl.latency_us(); + if (!cntl.Failed()) { + g_latency_recorder << cntl.latency_us(); + } else { + g_error_count << 1; + CHECK(brpc::IsAskedToQuit() || !FLAGS_dont_fail) + << "error=" << cntl.ErrorText() << " latency=" << elp; + // We can't connect to the server, sleep a while. Notice that this + // is a specific sleeping to prevent this thread from spinning too // fast. You should continue the business logic in a production + // server rather than sleeping. + bthread_usleep(50000); + } + } + return NULL; +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::SelectiveChannel channel; + brpc::ChannelOptions schan_options; + schan_options.timeout_ms = FLAGS_timeout_ms; + schan_options.backup_request_ms = FLAGS_backup_ms; + schan_options.max_retry = FLAGS_max_retry; + if (channel.Init(FLAGS_load_balancer.c_str(), &schan_options) != 0) { + LOG(ERROR) << "Fail to init SelectiveChannel"; + return -1; + } + + // Add sub channels. + // ================ + std::vector sub_channels; + + // Add an ordinary channel. + brpc::Channel* sub_channel1 = new brpc::Channel; + butil::EndPoint pt; + if (str2endpoint(FLAGS_starting_server.c_str(), &pt) != 0 && + hostname2endpoint(FLAGS_starting_server.c_str(), &pt) != 0) { + LOG(ERROR) << "Invalid address=`" << FLAGS_starting_server << "'"; + return -1; + } + brpc::ChannelOptions options; + options.protocol = FLAGS_protocol; + options.connection_type = FLAGS_connection_type; + std::ostringstream os; + os << "list://"; + for (int i = 0; i < 3; ++i) { + os << butil::EndPoint(pt.ip, pt.port++) << ","; + } + if (sub_channel1->Init(os.str().c_str(), FLAGS_load_balancer.c_str(), + &options) != 0) { + LOG(ERROR) << "Fail to init ordinary channel"; + return -1; + } + sub_channels.push_back(sub_channel1); + + // Add a parallel channel. + brpc::ParallelChannel* sub_channel2 = new brpc::ParallelChannel; + brpc::ParallelChannelOptions pchan_options; + pchan_options.fail_limit = 1; + if (sub_channel2->Init(&pchan_options) != 0) { + LOG(ERROR) << "Fail to init sub_channel2"; + return -1; + } + for (int i = 0; i < 3; ++i) { + brpc::ChannelOptions options; + options.protocol = FLAGS_protocol; + options.connection_type = FLAGS_connection_type; + brpc::Channel* c = new brpc::Channel; + if (c->Init(butil::EndPoint(pt.ip, pt.port++), &options) != 0) { + LOG(ERROR) << "Fail to init sub channel[" << i << "] of pchan"; + return -1; + } + if (sub_channel2->AddChannel(c, brpc::OWNS_CHANNEL, NULL, NULL) != 0) { + LOG(ERROR) << "Fail to add sub channel[" << i << "] into pchan"; + return -1; + } + } + sub_channels.push_back(sub_channel2); + + // Add another selective channel with default options. + brpc::SelectiveChannel* sub_channel3 = new brpc::SelectiveChannel; + if (sub_channel3->Init(FLAGS_load_balancer.c_str(), NULL) != 0) { + LOG(ERROR) << "Fail to init schan"; + return -1; + } + for (int i = 0; i < 3; ++i) { + brpc::Channel* c = new brpc::Channel; + if (i == 0) { + os.str(""); + os << "list://"; + for (int j = 0; j < 3; ++j) { + os << butil::EndPoint(pt.ip, pt.port++) << ","; + } + if (c->Init(os.str().c_str(), FLAGS_load_balancer.c_str(), + &options) != 0) { + LOG(ERROR) << "Fail to init sub channel[" << i << "] of schan"; + return -1; + } + } else { + if (c->Init(butil::EndPoint(pt.ip, pt.port++), &options) != 0) { + LOG(ERROR) << "Fail to init sub channel[" << i << "] of schan"; + return -1; + } + } + if (sub_channel3->AddChannel(c, NULL)) { + LOG(ERROR) << "Fail to add sub channel[" << i << "] into schan"; + return -1; + } + } + sub_channels.push_back(sub_channel3); + + // Add all sub channels into schan. + for (size_t i = 0; i < sub_channels.size(); ++i) { + // note: we don't need the handle for channel removal; + if (channel.AddChannel(sub_channels[i], NULL/*note*/) != 0) { + LOG(ERROR) << "Fail to add sub_channel[" << i << "]"; + return -1; + } + } + if (FLAGS_attachment_size > 0) { + g_attachment.resize(FLAGS_attachment_size, 'a'); + } + if (FLAGS_request_size <= 0) { + LOG(ERROR) << "Bad request_size=" << FLAGS_request_size; + return -1; + } + g_request.resize(FLAGS_request_size, 'r'); + + std::vector bids; + std::vector pids; + if (!FLAGS_use_bthread) { + pids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (pthread_create(&pids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create pthread"; + return -1; + } + } + } else { + bids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (bthread_start_background( + &bids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create bthread"; + return -1; + } + } + } + + while (!brpc::IsAskedToQuit()) { + sleep(1); + LOG(INFO) << "Sending EchoRequest at qps=" << g_latency_recorder.qps(1) + << " latency=" << g_latency_recorder.latency(1); + } + + LOG(INFO) << "EchoClient is going to quit"; + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (!FLAGS_use_bthread) { + pthread_join(pids[i], NULL); + } else { + bthread_join(bids[i], NULL); + } + } + + return 0; +} diff --git a/example/selective_echo_c++/echo.proto b/example/selective_echo_c++/echo.proto new file mode 100644 index 0000000..2b39627 --- /dev/null +++ b/example/selective_echo_c++/echo.proto @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package example; + +option cc_generic_services = true; + +message EchoRequest { + required string message = 1; +}; + +message EchoResponse { + required string message = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; diff --git a/example/selective_echo_c++/server.cpp b/example/selective_echo_c++/server.cpp new file mode 100644 index 0000000..d1ed8bb --- /dev/null +++ b/example/selective_echo_c++/server.cpp @@ -0,0 +1,172 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive EchoRequest and send back EchoResponse. + +#include +#include +#include +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_bool(echo_attachment, true, "Echo attachment as well"); +DEFINE_int32(port, 8114, "TCP Port of this server"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); +DEFINE_int32(max_concurrency, 0, "Limit of request processing in parallel"); +DEFINE_int32(server_num, 7, "Number of servers"); +DEFINE_string(sleep_us, "", "Sleep so many microseconds before responding"); +DEFINE_bool(spin, false, "spin rather than sleep"); +DEFINE_double(exception_ratio, 0.1, "Percentage of irregular latencies"); +DEFINE_double(min_ratio, 0.2, "min_sleep / sleep_us"); +DEFINE_double(max_ratio, 10, "max_sleep / sleep_us"); + +// Your implementation of example::EchoService +class EchoServiceImpl : public example::EchoService { +public: + EchoServiceImpl() : _index(0) {} + virtual ~EchoServiceImpl() {} + void set_index(size_t index, int64_t sleep_us) { + _index = index; + _sleep_us = sleep_us; + } + virtual void Echo(google::protobuf::RpcController* cntl_base, + const example::EchoRequest* request, + example::EchoResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = + static_cast(cntl_base); + if (_sleep_us > 0) { + double delay = _sleep_us; + const double a = FLAGS_exception_ratio * 0.5; + if (a >= 0.0001) { + double x = butil::RandDouble(); + if (x < a) { + const double min_sleep_us = FLAGS_min_ratio * _sleep_us; + delay = min_sleep_us + (_sleep_us - min_sleep_us) * x / a; + } else if (x + a > 1) { + const double max_sleep_us = FLAGS_max_ratio * _sleep_us; + delay = _sleep_us + (max_sleep_us - _sleep_us) * (x + a - 1) / a; + } + } + if (FLAGS_spin) { + int64_t end_time = butil::cpuwide_time_us() + (int64_t)delay; + while (butil::cpuwide_time_us() < end_time) {} + } else { + bthread_usleep((int64_t)delay); + } + } + + // Echo request and its attachment + response->set_message(request->message()); + if (FLAGS_echo_attachment) { + cntl->response_attachment().append(cntl->request_attachment()); + } + _nreq << 1; + } + + size_t num_requests() const { return _nreq.get_value(); } + +private: + size_t _index; + int64_t _sleep_us; + bvar::Adder _nreq; +}; + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_server_num <= 0) { + LOG(ERROR) << "server_num must be positive"; + return -1; + } + + // We need multiple servers in this example. + brpc::Server* servers = new brpc::Server[FLAGS_server_num]; + // For more options see `brpc/server.h'. + brpc::ServerOptions options; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + options.max_concurrency = FLAGS_max_concurrency; + + butil::StringSplitter sp(FLAGS_sleep_us.c_str(), ','); + std::vector sleep_list; + for (; sp; ++sp) { + sleep_list.push_back(strtoll(sp.field(), NULL, 10)); + } + if (sleep_list.empty()) { + sleep_list.push_back(0); + } + + // Instance of your services. + EchoServiceImpl* echo_service_impls = new EchoServiceImpl[FLAGS_server_num]; + // Add the service into servers. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + for (int i = 0; i < FLAGS_server_num; ++i) { + int64_t sleep_us = sleep_list[(size_t)i < sleep_list.size() ? i : (sleep_list.size() - 1)]; + echo_service_impls[i].set_index(i, sleep_us); + // will be shown on /version page + servers[i].set_version(butil::string_printf( + "example/selective_echo_c++[%d]", i)); + if (servers[i].AddService(&echo_service_impls[i], + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + // Start the server. + int port = FLAGS_port + i; + if (servers[i].Start(port, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + } + + // Service logic are running in separate worker threads, for main thread, + // we don't have much to do, just spinning. + std::vector last_num_requests(FLAGS_server_num); + while (!brpc::IsAskedToQuit()) { + sleep(1); + + size_t cur_total = 0; + for (int i = 0; i < FLAGS_server_num; ++i) { + const size_t current_num_requests = + echo_service_impls[i].num_requests(); + size_t diff = current_num_requests - last_num_requests[i]; + cur_total += diff; + last_num_requests[i] = current_num_requests; + LOG(INFO) << "S[" << i << "]=" << diff << ' ' << noflush; + } + LOG(INFO) << "[total=" << cur_total << ']'; + } + + // Don't forget to stop and join the server otherwise still-running + // worker threads may crash your program. + for (int i = 0; i < FLAGS_server_num; ++i) { + servers[i].Stop(0/*not used now*/); + } + for (int i = 0; i < FLAGS_server_num; ++i) { + servers[i].Join(); + } + delete [] servers; + delete [] echo_service_impls; + return 0; +} diff --git a/example/session_data_and_thread_local/CMakeLists.txt b/example/session_data_and_thread_local/CMakeLists.txt new file mode 100644 index 0000000..de2302a --- /dev/null +++ b/example/session_data_and_thread_local/CMakeLists.txt @@ -0,0 +1,115 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(session_data_and_thread_local C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER echo.proto) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(GPERFTOOLS_INCLUDE_DIR NAMES gperftools/heap-profiler.h) +find_library(GPERFTOOLS_LIBRARIES NAMES tcmalloc_and_profiler) + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +set(BRPC_EXAMPLE_ENABLE_CPU_PROFILER ON) + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(session_data_and_thread_local_client client.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(session_data_and_thread_local_client) +add_executable(session_data_and_thread_local_server server.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(session_data_and_thread_local_server) + +target_link_libraries(session_data_and_thread_local_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${GPERFTOOLS_LIBRARIES}) +target_link_libraries(session_data_and_thread_local_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB} ${GPERFTOOLS_LIBRARIES}) diff --git a/example/session_data_and_thread_local/client.cpp b/example/session_data_and_thread_local/client.cpp new file mode 100644 index 0000000..f075e70 --- /dev/null +++ b/example/session_data_and_thread_local/client.cpp @@ -0,0 +1,151 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending requests to server by multiple threads. + +#include +#include +#include +#include +#include "echo.pb.h" +#include + +DEFINE_int32(thread_num, 50, "Number of threads to send requests"); +DEFINE_bool(use_bthread, false, "Use bthread to send requests"); +DEFINE_int32(attachment_size, 0, "Carry so many byte attachment along with requests"); +DEFINE_int32(request_size, 16, "Bytes of each request"); +DEFINE_string(protocol, "baidu_std", "Protocol type. Defined in src/brpc/options.proto"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(server, "0.0.0.0:8002", "IP Address of server"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_bool(dont_fail, false, "Print fatal when some call failed"); + +std::string g_request; +std::string g_attachment; + +bvar::LatencyRecorder g_latency_recorder("client"); + +static void* sender(void* arg) { + // Normally, you should not call a Channel directly, but instead construct + // a stub Service wrapping it. stub can be shared by all threads as well. + example::EchoService_Stub stub(static_cast(arg)); + + int log_id = 0; + while (!brpc::IsAskedToQuit()) { + // We will receive response synchronously, safe to put variables + // on stack. + example::EchoRequest request; + example::EchoResponse response; + brpc::Controller cntl; + + request.set_message(g_request); + cntl.set_log_id(log_id++); // set by user + if (!g_attachment.empty()) { + // Set attachment which is wired to network directly instead of + // being serialized into protobuf messages. + cntl.request_attachment().append(g_attachment); + } + + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + const int64_t start_time = butil::cpuwide_time_us(); + stub.Echo(&cntl, &request, &response, NULL); + const int64_t end_time = butil::cpuwide_time_us(); + const int64_t elp = end_time - start_time; + if (!cntl.Failed()) { + g_latency_recorder << elp; + } else { + CHECK(brpc::IsAskedToQuit() || !FLAGS_dont_fail) + << "error=" << cntl.ErrorText() << " latency=" << elp; + // We can't connect to the server, sleep a while. Notice that this + // is a specific sleeping to prevent this thread from spinning too + // fast. You should continue the business logic in a production + // server rather than sleeping. + bthread_usleep(50000); + } + } + return NULL; +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = FLAGS_protocol; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + if (channel.Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + if (FLAGS_attachment_size > 0) { + g_attachment.resize(FLAGS_attachment_size, 'a'); + } + if (FLAGS_request_size <= 0) { + LOG(ERROR) << "Bad request_size=" << FLAGS_request_size; + return -1; + } + g_request.resize(FLAGS_request_size, 'r'); + + std::vector bids; + std::vector pids; + if (!FLAGS_use_bthread) { + pids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (pthread_create(&pids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create pthread"; + return -1; + } + } + } else { + bids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (bthread_start_background( + &bids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create bthread"; + return -1; + } + } + } + + while (!brpc::IsAskedToQuit()) { + sleep(1); + LOG(INFO) << "Sending EchoRequest at qps=" << g_latency_recorder.qps(1) + << " latency=" << g_latency_recorder.latency(1); + } + + LOG(INFO) << "EchoClient is going to quit"; + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (!FLAGS_use_bthread) { + pthread_join(pids[i], NULL); + } else { + bthread_join(bids[i], NULL); + } + } + + return 0; +} diff --git a/example/session_data_and_thread_local/echo.proto b/example/session_data_and_thread_local/echo.proto new file mode 100644 index 0000000..2b39627 --- /dev/null +++ b/example/session_data_and_thread_local/echo.proto @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package example; + +option cc_generic_services = true; + +message EchoRequest { + required string message = 1; +}; + +message EchoResponse { + required string message = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; diff --git a/example/session_data_and_thread_local/server.cpp b/example/session_data_and_thread_local/server.cpp new file mode 100644 index 0000000..c196c33 --- /dev/null +++ b/example/session_data_and_thread_local/server.cpp @@ -0,0 +1,253 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive EchoRequest and send back EchoResponse asynchronously. + +#include +#include +#include +#include "echo.pb.h" + +DEFINE_bool(echo_attachment, true, "Echo attachment as well"); +DEFINE_int32(port, 8002, "TCP Port of this server"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); +DEFINE_int32(max_concurrency, 0, "Limit of request processing in parallel"); + +butil::atomic nsd(0); +struct MySessionLocalData { + MySessionLocalData() : x(123) { + nsd.fetch_add(1, butil::memory_order_relaxed); + } + ~MySessionLocalData() { + nsd.fetch_sub(1, butil::memory_order_relaxed); + } + + int x; +}; + +class MySessionLocalDataFactory : public brpc::DataFactory { +public: + void* CreateData() const { + return new MySessionLocalData; + } + + void DestroyData(void* d) const { + delete static_cast(d); + } +}; + +butil::atomic ntls(0); +struct MyThreadLocalData { + MyThreadLocalData() : y(0) { + ntls.fetch_add(1, butil::memory_order_relaxed); + } + ~MyThreadLocalData() { + ntls.fetch_sub(1, butil::memory_order_relaxed); + } + static void deleter(void* d) { + delete static_cast(d); + } + + int y; +}; + +class MyThreadLocalDataFactory : public brpc::DataFactory { +public: + void* CreateData() const { + return new MyThreadLocalData; + } + + void DestroyData(void* d) const { + MyThreadLocalData::deleter(d); + } +}; + +struct AsyncJob { + MySessionLocalData* expected_session_local_data; + int expected_session_value; + brpc::Controller* cntl; + const example::EchoRequest* request; + example::EchoResponse* response; + google::protobuf::Closure* done; + + void run(); + + void run_and_delete() { + run(); + delete this; + } +}; + +static void* process_thread(void* args) { + AsyncJob* job = static_cast(args); + job->run_and_delete(); + return NULL; +} + +// Your implementation of example::EchoService +class EchoServiceWithThreadAndSessionLocal : public example::EchoService { +public: + EchoServiceWithThreadAndSessionLocal() { + CHECK_EQ(0, bthread_key_create(&_tls2_key, MyThreadLocalData::deleter)); + } + ~EchoServiceWithThreadAndSessionLocal() { + CHECK_EQ(0, bthread_key_delete(_tls2_key)); + }; + void Echo(google::protobuf::RpcController* cntl_base, + const example::EchoRequest* request, + example::EchoResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = + static_cast(cntl_base); + + // Get the session-local data which is created by ServerOptions.session_local_data_factory + // and reused between different RPC. All session-local data are + // destroyed upon server destruction. + MySessionLocalData* sd = static_cast(cntl->session_local_data()); + if (sd == NULL) { + cntl->SetFailed("Require ServerOptions.session_local_data_factory to be" + " set with a correctly implemented instance"); + LOG(ERROR) << cntl->ErrorText(); + return; + } + const int expected_value = sd->x + (((uintptr_t)cntl) & 0xFFFFFFFF); + sd->x = expected_value; + + // Get the thread-local data which is created by ServerOptions.thread_local_data_factory + // and reused between different threads. All thread-local data are + // destroyed upon server destruction. + // "tls" is short for "thread local storage". + MyThreadLocalData* tls = + static_cast(brpc::thread_local_data()); + if (tls == NULL) { + cntl->SetFailed("Require ServerOptions.thread_local_data_factory " + "to be set with a correctly implemented instance"); + LOG(ERROR) << cntl->ErrorText(); + return; + } + tls->y = expected_value; + + // You can create bthread-local data for your own. + // The interfaces are similar with pthread equivalence: + // pthread_key_create -> bthread_key_create + // pthread_key_delete -> bthread_key_delete + // pthread_getspecific -> bthread_getspecific + // pthread_setspecific -> bthread_setspecific + MyThreadLocalData* tls2 = + static_cast(bthread_getspecific(_tls2_key)); + if (tls2 == NULL) { + tls2 = new MyThreadLocalData; + CHECK_EQ(0, bthread_setspecific(_tls2_key, tls2)); + } + tls2->y = expected_value + 1; + + // sleep awhile to force context switching. + bthread_usleep(10000); + + // tls is unchanged after context switching. + CHECK_EQ(tls, brpc::thread_local_data()); + CHECK_EQ(expected_value, tls->y); + + CHECK_EQ(tls2, bthread_getspecific(_tls2_key)); + CHECK_EQ(expected_value + 1, tls2->y); + + // Process the request asynchronously. + AsyncJob* job = new AsyncJob; + job->expected_session_local_data = sd; + job->expected_session_value = expected_value; + job->cntl = cntl; + job->request = request; + job->response = response; + job->done = done; + bthread_t th; + CHECK_EQ(0, bthread_start_background(&th, NULL, process_thread, job)); + + // We don't want to call done->Run() here, release the guard. + done_guard.release(); + + LOG_EVERY_SECOND(INFO) << "ntls=" << ntls.load(butil::memory_order_relaxed) + << " nsd=" << nsd.load(butil::memory_order_relaxed); + } + +private: + bthread_key_t _tls2_key; +}; + +void AsyncJob::run() { + brpc::ClosureGuard done_guard(done); + + // Sleep some time to make sure that Echo() exits. + bthread_usleep(10000); + + // Still the session-local data that we saw in Echo(). + // This is the major difference between session-local data and thread-local + // data which was already destroyed upon Echo() exit. + MySessionLocalData* sd = static_cast(cntl->session_local_data()); + CHECK_EQ(expected_session_local_data, sd); + CHECK_EQ(expected_session_value, sd->x); + + // Echo request and its attachment + response->set_message(request->message()); + if (FLAGS_echo_attachment) { + cntl->response_attachment().append(cntl->request_attachment()); + } +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // The factory to create MySessionLocalData. Must be valid when server is running. + MySessionLocalDataFactory session_local_data_factory; + + MyThreadLocalDataFactory thread_local_data_factory; + + // Generally you only need one Server. + brpc::Server server; + // For more options see `brpc/server.h'. + brpc::ServerOptions options; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + options.max_concurrency = FLAGS_max_concurrency; + options.session_local_data_factory = &session_local_data_factory; + options.thread_local_data_factory = &thread_local_data_factory; + + // Instance of your service. + EchoServiceWithThreadAndSessionLocal echo_service_impl; + + // Add the service into server. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + if (server.AddService(&echo_service_impl, + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + + // Start the server. + if (server.Start(FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + // Wait until Ctrl-C is pressed, then Stop() and Join() the server. + server.RunUntilAskedToQuit(); + CHECK_EQ(ntls, 0); + CHECK_EQ(nsd, 0); + return 0; +} diff --git a/example/streaming_batch_echo_c++/CMakeLists.txt b/example/streaming_batch_echo_c++/CMakeLists.txt new file mode 100644 index 0000000..f009335 --- /dev/null +++ b/example/streaming_batch_echo_c++/CMakeLists.txt @@ -0,0 +1,107 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(streaming_batch_echo_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER echo.proto) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl +) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii") +endif() + +add_executable(streaming_batch_echo_client client.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(streaming_batch_echo_client) +add_executable(streaming_batch_echo_server server.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(streaming_batch_echo_server) + +target_link_libraries(streaming_batch_echo_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) +target_link_libraries(streaming_batch_echo_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) diff --git a/example/streaming_batch_echo_c++/client.cpp b/example/streaming_batch_echo_c++/client.cpp new file mode 100644 index 0000000..23f2b06 --- /dev/null +++ b/example/streaming_batch_echo_c++/client.cpp @@ -0,0 +1,118 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending requests to server in batch every 1 second. + +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_bool(send_attachment, true, "Carry attachment along with requests"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(server, "0.0.0.0:8001", "IP Address of server"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); + +class StreamClientReceiver : public brpc::StreamInputHandler { +public: + virtual int on_received_messages(brpc::StreamId id, + butil::IOBuf *const messages[], + size_t size) { + std::ostringstream os; + for (size_t i = 0; i < size; ++i) { + os << "msg[" << i << "]=" << *messages[i]; + } + LOG(INFO) << "Received from Stream=" << id << ": " << os.str(); + return 0; + } + virtual void on_idle_timeout(brpc::StreamId id) { + LOG(INFO) << "Stream=" << id << " has no data transmission for a while"; + } + virtual void on_closed(brpc::StreamId id) { + LOG(INFO) << "Stream=" << id << " is closed"; + } + + virtual void on_finished(brpc::StreamId id, int32_t finish_code) { + LOG(INFO) << "Stream=" << id << " is finished, code " << finish_code; + } +}; + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_BAIDU_STD; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + if (channel.Init(FLAGS_server.c_str(), NULL) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + // Normally, you should not call a Channel directly, but instead construct + // a stub Service wrapping it. stub can be shared by all threads as well. + example::EchoService_Stub stub(&channel); + StreamClientReceiver receiver; + brpc::Controller cntl; + brpc::StreamIds streams; + brpc::StreamOptions stream_options; + stream_options.handler = &receiver; + if (brpc::StreamCreate(streams, 3, cntl, &stream_options) != 0) { + LOG(ERROR) << "Fail to create stream"; + return -1; + } + for(size_t i = 0; i < streams.size(); ++i) { + LOG(INFO) << "Created Stream=" << streams[i]; + } + example::EchoRequest request; + example::EchoResponse response; + request.set_message("I'm a RPC to connect stream"); + stub.Echo(&cntl, &request, &response, NULL); + if (cntl.Failed()) { + LOG(ERROR) << "Fail to connect stream, " << cntl.ErrorText(); + return -1; + } + + while (!brpc::IsAskedToQuit()) { + butil::IOBuf msg1; + msg1.append("abcdefghijklmnopqrstuvwxyz"); + CHECK_EQ(0, brpc::StreamWrite(streams[0], msg1)); + butil::IOBuf msg2; + msg2.append("0123456789"); + CHECK_EQ(0, brpc::StreamWrite(streams[1], msg2)); + sleep(1); + butil::IOBuf msg3; + msg3.append("hello world"); + CHECK_EQ(0, brpc::StreamWrite(streams[2], msg3)); + sleep(1); + } + + CHECK_EQ(0, brpc::StreamClose(streams[0])); + CHECK_EQ(0, brpc::StreamClose(streams[1])); + CHECK_EQ(0, brpc::StreamClose(streams[2])); + LOG(INFO) << "EchoClient is going to quit"; + return 0; +} diff --git a/example/streaming_batch_echo_c++/echo.proto b/example/streaming_batch_echo_c++/echo.proto new file mode 100644 index 0000000..2b39627 --- /dev/null +++ b/example/streaming_batch_echo_c++/echo.proto @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package example; + +option cc_generic_services = true; + +message EchoRequest { + required string message = 1; +}; + +message EchoResponse { + required string message = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; diff --git a/example/streaming_batch_echo_c++/server.cpp b/example/streaming_batch_echo_c++/server.cpp new file mode 100644 index 0000000..88ea898 --- /dev/null +++ b/example/streaming_batch_echo_c++/server.cpp @@ -0,0 +1,122 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive EchoRequest and send back EchoResponse. + +#include +#include +#include +#include "echo.pb.h" +#include + +DEFINE_bool(send_attachment, true, "Carry attachment along with response"); +DEFINE_int32(port, 8001, "TCP Port of this server"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); + +class StreamReceiver : public brpc::StreamInputHandler { +public: + virtual int on_received_messages(brpc::StreamId id, + butil::IOBuf *const messages[], + size_t size) { + std::ostringstream os; + for (size_t i = 0; i < size; ++i) { + os << "msg[" << i << "]=" << *messages[i]; + } + auto res = brpc::StreamWrite(id, *messages[0]); + LOG(INFO) << "Received from Stream=" << id << ": " << os.str() << " and write back result: " << res; + return 0; + } + virtual void on_idle_timeout(brpc::StreamId id) { + LOG(INFO) << "Stream=" << id << " has no data transmission for a while"; + } + virtual void on_closed(brpc::StreamId id) { + LOG(INFO) << "Stream=" << id << " is closed"; + } + + virtual void on_finished(brpc::StreamId id, int32_t finish_code) { + LOG(INFO) << "Stream=" << id << " is finished, code " << finish_code; + } +}; + +// Your implementation of example::EchoService +class StreamingBatchEchoService : public example::EchoService { +public: + virtual ~StreamingBatchEchoService() { + closeStreams(); + }; + virtual void Echo(google::protobuf::RpcController* controller, + const example::EchoRequest* /*request*/, + example::EchoResponse* response, + google::protobuf::Closure* done) { + // This object helps you to call done->Run() in RAII style. If you need + // to process the request asynchronously, pass done_guard.release(). + brpc::ClosureGuard done_guard(done); + closeStreams(); + brpc::Controller* cntl = + static_cast(controller); + brpc::StreamOptions stream_options; + stream_options.handler = &_receiver; + if (brpc::StreamAccept(_sds, *cntl, &stream_options) != 0) { + cntl->SetFailed("Fail to accept stream"); + return; + } + response->set_message("Accepted stream"); + } + +private: + void closeStreams() { + for(auto i = 0; i < _sds.size(); ++i) { + brpc::StreamClose(_sds[i]); + } + _sds.clear(); + } + StreamReceiver _receiver; + brpc::StreamIds _sds; +}; + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // Generally you only need one Server. + brpc::Server server; + + // Instance of your service. + StreamingBatchEchoService echo_service_impl; + + // Add the service into server. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + if (server.AddService(&echo_service_impl, + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + + // Start the server. + brpc::ServerOptions options; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + if (server.Start(FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + // Wait until Ctrl-C is pressed, then Stop() and Join() the server. + server.RunUntilAskedToQuit(); + return 0; +} diff --git a/example/streaming_echo_c++/CMakeLists.txt b/example/streaming_echo_c++/CMakeLists.txt new file mode 100644 index 0000000..47f9373 --- /dev/null +++ b/example/streaming_echo_c++/CMakeLists.txt @@ -0,0 +1,110 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +cmake_minimum_required(VERSION 3.16...3.28) +project(streaming_echo_c++ C CXX) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/BrpcExample.cmake) + +option(LINK_SO "Whether examples are linked dynamically" OFF) + +execute_process( + COMMAND bash -c "find ${PROJECT_SOURCE_DIR}/../.. -type d -regex \".*output/include$\" | head -n1 | xargs dirname | tr -d '\n'" + OUTPUT_VARIABLE OUTPUT_PATH +) + +if(OUTPUT_PATH) + list(PREPEND CMAKE_PREFIX_PATH ${OUTPUT_PATH}) +endif() + +find_package(Threads REQUIRED) +find_package(Protobuf REQUIRED) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER echo.proto) + +# Search for libthrift* by best effort. If it is not found and brpc is +# compiled with thrift protocol enabled, a link error would be reported. +find_library(THRIFT_LIB NAMES thrift) +if (NOT THRIFT_LIB) + set(THRIFT_LIB "") +endif() + +find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) +if(LINK_SO) + find_library(BRPC_LIB NAMES brpc) +else() + find_library(BRPC_LIB NAMES libbrpc.a brpc) +endif() +if((NOT BRPC_INCLUDE_PATH) OR (NOT BRPC_LIB)) + message(FATAL_ERROR "Fail to find brpc") +endif() + +find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) +find_library(GFLAGS_LIBRARY NAMES gflags libgflags) +if((NOT GFLAGS_INCLUDE_PATH) OR (NOT GFLAGS_LIBRARY)) + message(FATAL_ERROR "Fail to find gflags") +endif() + +find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) +find_library(LEVELDB_LIB NAMES leveldb) +if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB)) + message(FATAL_ERROR "Fail to find leveldb") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR + "/usr/local/opt/openssl" # Homebrew installed OpenSSL + ) +endif() + +find_package(OpenSSL REQUIRED) + +set(DYNAMIC_LIB + Threads::Threads + ${GFLAGS_LIBRARY} + ${PROTOBUF_LIBRARIES} + ${LEVELDB_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${THRIFT_LIB} + dl + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} + pthread + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework CoreData" + "-framework CoreText" + "-framework Security" + "-framework Foundation" + "-Wl,-U,_MallocExtension_ReleaseFreeMemory" + "-Wl,-U,_ProfilerStart" + "-Wl,-U,_ProfilerStop" + "-Wl,-U,__Z13GetStackTracePPvii" + "-Wl,-U,_mallctl" + "-Wl,-U,_malloc_stats_print" + ) +endif() + +add_executable(streaming_echo_client client.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(streaming_echo_client) +add_executable(streaming_echo_server server.cpp ${PROTO_SRC} ${PROTO_HEADER}) +brpc_example_configure_target(streaming_echo_server) + +target_link_libraries(streaming_echo_client PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) +target_link_libraries(streaming_echo_server PRIVATE ${BRPC_LIB} ${DYNAMIC_LIB}) diff --git a/example/streaming_echo_c++/client.cpp b/example/streaming_echo_c++/client.cpp new file mode 100644 index 0000000..619f579 --- /dev/null +++ b/example/streaming_echo_c++/client.cpp @@ -0,0 +1,83 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending requests to server in batch every 1 second. + +#include +#include +#include +#include +#include "echo.pb.h" + +DEFINE_bool(send_attachment, true, "Carry attachment along with requests"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(server, "0.0.0.0:8001", "IP Address of server"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_BAIDU_STD; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + if (channel.Init(FLAGS_server.c_str(), NULL) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + // Normally, you should not call a Channel directly, but instead construct + // a stub Service wrapping it. stub can be shared by all threads as well. + example::EchoService_Stub stub(&channel); + brpc::Controller cntl; + brpc::StreamId stream; + if (brpc::StreamCreate(&stream, cntl, NULL) != 0) { + LOG(ERROR) << "Fail to create stream"; + return -1; + } + LOG(INFO) << "Created Stream=" << stream; + example::EchoRequest request; + example::EchoResponse response; + request.set_message("I'm a RPC to connect stream"); + stub.Echo(&cntl, &request, &response, NULL); + if (cntl.Failed()) { + LOG(ERROR) << "Fail to connect stream, " << cntl.ErrorText(); + return -1; + } + + while (!brpc::IsAskedToQuit()) { + butil::IOBuf msg1; + msg1.append("abcdefghijklmnopqrstuvwxyz"); + CHECK_EQ(0, brpc::StreamWrite(stream, msg1)); + butil::IOBuf msg2; + msg2.append("0123456789"); + CHECK_EQ(0, brpc::StreamWrite(stream, msg2)); + sleep(1); + } + + CHECK_EQ(0, brpc::StreamClose(stream)); + LOG(INFO) << "EchoClient is going to quit"; + return 0; +} diff --git a/example/streaming_echo_c++/echo.proto b/example/streaming_echo_c++/echo.proto new file mode 100644 index 0000000..2b39627 --- /dev/null +++ b/example/streaming_echo_c++/echo.proto @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package example; + +option cc_generic_services = true; + +message EchoRequest { + required string message = 1; +}; + +message EchoResponse { + required string message = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; diff --git a/example/streaming_echo_c++/server.cpp b/example/streaming_echo_c++/server.cpp new file mode 100644 index 0000000..808d88d --- /dev/null +++ b/example/streaming_echo_c++/server.cpp @@ -0,0 +1,113 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive EchoRequest and send back EchoResponse. + +#include +#include +#include +#include "echo.pb.h" +#include + +DEFINE_bool(send_attachment, true, "Carry attachment along with response"); +DEFINE_int32(port, 8001, "TCP Port of this server"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); + +class StreamReceiver : public brpc::StreamInputHandler { +public: + virtual int on_received_messages(brpc::StreamId id, + butil::IOBuf *const messages[], + size_t size) { + std::ostringstream os; + for (size_t i = 0; i < size; ++i) { + os << "msg[" << i << "]=" << *messages[i]; + } + LOG(INFO) << "Received from Stream=" << id << ": " << os.str(); + return 0; + } + virtual void on_idle_timeout(brpc::StreamId id) { + LOG(INFO) << "Stream=" << id << " has no data transmission for a while"; + } + virtual void on_closed(brpc::StreamId id) { + LOG(INFO) << "Stream=" << id << " is closed"; + } + +}; + +// Your implementation of example::EchoService +class StreamingEchoService : public example::EchoService { +public: + StreamingEchoService() : _sd(brpc::INVALID_STREAM_ID) {} + virtual ~StreamingEchoService() { + brpc::StreamClose(_sd); + }; + virtual void Echo(google::protobuf::RpcController* controller, + const example::EchoRequest* /*request*/, + example::EchoResponse* response, + google::protobuf::Closure* done) { + // This object helps you to call done->Run() in RAII style. If you need + // to process the request asynchronously, pass done_guard.release(). + brpc::ClosureGuard done_guard(done); + + brpc::Controller* cntl = + static_cast(controller); + brpc::StreamOptions stream_options; + stream_options.handler = &_receiver; + if (brpc::StreamAccept(&_sd, *cntl, &stream_options) != 0) { + cntl->SetFailed("Fail to accept stream"); + return; + } + response->set_message("Accepted stream"); + } + +private: + StreamReceiver _receiver; + brpc::StreamId _sd; +}; + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // Generally you only need one Server. + brpc::Server server; + + // Instance of your service. + StreamingEchoService echo_service_impl; + + // Add the service into server. Notice the second parameter, because the + // service is put on stack, we don't want server to delete it, otherwise + // use brpc::SERVER_OWNS_SERVICE. + if (server.AddService(&echo_service_impl, + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + + // Start the server. + brpc::ServerOptions options; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + if (server.Start(FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + // Wait until Ctrl-C is pressed, then Stop() and Join() the server. + server.RunUntilAskedToQuit(); + return 0; +} diff --git a/example/thrift_extension_c++/README.md b/example/thrift_extension_c++/README.md new file mode 100644 index 0000000..a9ea994 --- /dev/null +++ b/example/thrift_extension_c++/README.md @@ -0,0 +1,10 @@ +Note: + Only thrift framed transport supported now, in another words, only working on thrift nonblocking mode. + +summary: + echo_client/echo_server: + brpc + thrift protocol version + native_client/native_server: + native thrift cpp version + + diff --git a/example/thrift_extension_c++/client.cpp b/example/thrift_extension_c++/client.cpp new file mode 100755 index 0000000..b358792 --- /dev/null +++ b/example/thrift_extension_c++/client.cpp @@ -0,0 +1,88 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending thrift requests to server every 1 second. + +#include + +#include "gen-cpp/echo_types.h" + +#include +#include +#include +#include +#include + +bvar::LatencyRecorder g_latency_recorder("client"); + +DEFINE_string(server, "0.0.0.0:8019", "IP Address of server"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + google::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_THRIFT; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + if (channel.Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + brpc::ThriftStub stub(&channel); + + // Send a request and wait for the response every 1 second. + while (!brpc::IsAskedToQuit()) { + brpc::Controller cntl; + example::EchoRequest req; + example::EchoResponse res; + + req.__set_data("hello"); + req.__set_need_by_proxy(10); + + stub.CallMethod("Echo", &cntl, &req, &res, NULL); + + if (cntl.Failed()) { + LOG(ERROR) << "Fail to send thrift request, " << cntl.ErrorText(); + sleep(1); // Remove this sleep in production code. + } else { + g_latency_recorder << cntl.latency_us(); + LOG(INFO) << "Thrift Response: " << res; + } + + LOG_EVERY_SECOND(INFO) + << "Sending thrift requests at qps=" << g_latency_recorder.qps(1) + << " latency=" << g_latency_recorder.latency(1); + + sleep(1); + + } + + LOG(INFO) << "EchoClient is going to quit"; + return 0; +} + + diff --git a/example/thrift_extension_c++/client2.cpp b/example/thrift_extension_c++/client2.cpp new file mode 100644 index 0000000..c6caa0a --- /dev/null +++ b/example/thrift_extension_c++/client2.cpp @@ -0,0 +1,147 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A client sending requests to server by multiple threads. + +#include "gen-cpp/echo_types.h" + +#include +#include +#include +#include +#include +#include +#include + +DEFINE_int32(thread_num, 50, "Number of threads to send requests"); +DEFINE_bool(use_bthread, false, "Use bthread to send requests"); +DEFINE_int32(request_size, 16, "Bytes of each request"); +DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short"); +DEFINE_string(server, "0.0.0.0:8019", "IP Address of server"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_bool(dont_fail, false, "Print fatal when some call failed"); +DEFINE_int32(dummy_port, -1, "Launch dummy server at this port"); + +std::string g_request; + +bvar::LatencyRecorder g_latency_recorder("client"); +bvar::Adder g_error_count("client_error_count"); + +static void* sender(void* arg) { + // Normally, you should not call a Channel directly, but instead construct + // a stub Service wrapping it. stub can be shared by all threads as well. + brpc::ThriftStub stub(static_cast(arg)); + + while (!brpc::IsAskedToQuit()) { + // We will receive response synchronously, safe to put variables + // on stack. + example::EchoRequest req; + example::EchoResponse res; + brpc::Controller cntl; + + req.__set_data(g_request); + req.__set_need_by_proxy(10); + + // Because `done'(last parameter) is NULL, this function waits until + // the response comes back or error occurs(including timedout). + stub.CallMethod("Echo", &cntl, &req, &res, NULL); + if (!cntl.Failed()) { + g_latency_recorder << cntl.latency_us(); + } else { + g_error_count << 1; + CHECK(brpc::IsAskedToQuit() || !FLAGS_dont_fail) + << "error=" << cntl.ErrorText() << " latency=" << cntl.latency_us(); + // We can't connect to the server, sleep a while. Notice that this + // is a specific sleeping to prevent this thread from spinning too + // fast. You should continue the business logic in a production + // server rather than sleeping. + bthread_usleep(50000); + } + } + return NULL; +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // A Channel represents a communication line to a Server. Notice that + // Channel is thread-safe and can be shared by all threads in your program. + brpc::Channel channel; + + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_THRIFT; + options.connection_type = FLAGS_connection_type; + options.connect_timeout_ms = std::min(FLAGS_timeout_ms / 2, 100); + options.timeout_ms = FLAGS_timeout_ms; + options.max_retry = FLAGS_max_retry; + if (channel.Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + + if (FLAGS_request_size <= 0) { + LOG(ERROR) << "Bad request_size=" << FLAGS_request_size; + return -1; + } + g_request.resize(FLAGS_request_size, 'r'); + + if (FLAGS_dummy_port >= 0) { + brpc::StartDummyServerAt(FLAGS_dummy_port); + } + + std::vector bids; + std::vector pids; + if (!FLAGS_use_bthread) { + pids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (pthread_create(&pids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create pthread"; + return -1; + } + } + } else { + bids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (bthread_start_background( + &bids[i], NULL, sender, &channel) != 0) { + LOG(ERROR) << "Fail to create bthread"; + return -1; + } + } + } + + while (!brpc::IsAskedToQuit()) { + sleep(1); + LOG(INFO) << "Sending EchoRequest at qps=" << g_latency_recorder.qps(1) + << " latency=" << g_latency_recorder.latency(1); + } + + LOG(INFO) << "EchoClient is going to quit"; + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (!FLAGS_use_bthread) { + pthread_join(pids[i], NULL); + } else { + bthread_join(bids[i], NULL); + } + } + + return 0; +} diff --git a/example/thrift_extension_c++/echo.thrift b/example/thrift_extension_c++/echo.thrift new file mode 100644 index 0000000..2edfa58 --- /dev/null +++ b/example/thrift_extension_c++/echo.thrift @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +namespace cpp example + +struct EchoRequest { + 1: optional string data; + 2: optional i32 need_by_proxy; +} + +struct ProxyRequest { + 2: optional i32 need_by_proxy; +} + +struct EchoResponse { + 1: required string data; +} + +service EchoService { + EchoResponse Echo(1:EchoRequest request); +} + diff --git a/example/thrift_extension_c++/native_client.cpp b/example/thrift_extension_c++/native_client.cpp new file mode 100644 index 0000000..29d9e5c --- /dev/null +++ b/example/thrift_extension_c++/native_client.cpp @@ -0,0 +1,78 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A thrift client sending requests to server every 1 second. + +#include +#include "gen-cpp/EchoService.h" +#include "gen-cpp/echo_types.h" +#include +#include +#include + +#include + +// _THRIFT_STDCXX_H_ is defined by thrift/stdcxx.h which was added since thrift 0.11.0 +// but deprecated after 0.13.0 +#ifndef THRIFT_STDCXX + #if defined(_THRIFT_STDCXX_H_) + # define THRIFT_STDCXX apache::thrift::stdcxx + #elif defined(_THRIFT_VERSION_LOWER_THAN_0_11_0_) + # define THRIFT_STDCXX boost + # include + #else + # define THRIFT_STDCXX std + #endif +#endif + +DEFINE_string(server, "0.0.0.0", "IP Address of server"); +DEFINE_int32(port, 8019, "Port of server"); + +int main(int argc, char **argv) { + + // Parse gflags. We recommend you to use gflags as well. + google::ParseCommandLineFlags(&argc, &argv, true); + + THRIFT_STDCXX::shared_ptr socket( + new apache::thrift::transport::TSocket(FLAGS_server, FLAGS_port)); + THRIFT_STDCXX::shared_ptr transport( + new apache::thrift::transport::TFramedTransport(socket)); + THRIFT_STDCXX::shared_ptr protocol( + new apache::thrift::protocol::TBinaryProtocol(transport)); + + example::EchoServiceClient client(protocol); + transport->open(); + + example::EchoRequest req; + req.__set_data("hello"); + req.__set_need_by_proxy(10); + + example::EchoResponse res; + + while (1) { + try { + client.Echo(res, req); + LOG(INFO) << "Req=" << req << " Res=" << res; + } catch (std::exception& e) { + LOG(ERROR) << "Fail to rpc, " << e.what(); + } + sleep(1); + } + transport->close(); + + return 0; +} diff --git a/example/thrift_extension_c++/native_server.cpp b/example/thrift_extension_c++/native_server.cpp new file mode 100755 index 0000000..1ba3d1d --- /dev/null +++ b/example/thrift_extension_c++/native_server.cpp @@ -0,0 +1,109 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A thrift server to receive EchoRequest and send back EchoResponse. + +#include + +#include + +#include "gen-cpp/EchoService.h" + +#include +#include +#include +#include +#include + +// _THRIFT_STDCXX_H_ is defined by thrift/stdcxx.h which was added since thrift 0.11.0 +// but deprecated after 0.13.0, PosixThreadFactory was also deprecated in 0.13.0 +#include // to include stdcxx.h if present +#ifndef THRIFT_STDCXX + #if defined(_THRIFT_STDCXX_H_) + # define THRIFT_STDCXX apache::thrift::stdcxx + #include + #include + #elif defined(_THRIFT_VERSION_LOWER_THAN_0_11_0_) + # define THRIFT_STDCXX boost + #include + #include + #else + # define THRIFT_STDCXX std + #include + #include + #endif +#endif + +DEFINE_int32(port, 8019, "Port of server"); + +class EchoServiceHandler : virtual public example::EchoServiceIf { +public: + EchoServiceHandler() {} + + void Echo(example::EchoResponse& res, const example::EchoRequest& req) { + // Process request, just attach a simple string. + res.data = req.data + " world"; + return; + } + +}; + +int main(int argc, char *argv[]) { + // Parse gflags. We recommend you to use gflags as well. + google::ParseCommandLineFlags(&argc, &argv, true); + + THRIFT_STDCXX::shared_ptr handler(new EchoServiceHandler()); +#if THRIFT_STDCXX != std + // For thrift version less than 0.13.0 + THRIFT_STDCXX::shared_ptr thread_factory( + new apache::thrift::concurrency::PosixThreadFactory( + apache::thrift::concurrency::PosixThreadFactory::ROUND_ROBIN, + apache::thrift::concurrency::PosixThreadFactory::NORMAL, 1, false)); +#else + // For thrift version greater equal than 0.13.0 + THRIFT_STDCXX::shared_ptr thread_factory( + new apache::thrift::concurrency::ThreadFactory(false)); +#endif + + THRIFT_STDCXX::shared_ptr processor( + new example::EchoServiceProcessor(handler)); + THRIFT_STDCXX::shared_ptr protocol_factory( + new apache::thrift::protocol::TBinaryProtocolFactory()); + THRIFT_STDCXX::shared_ptr transport_factory( + new apache::thrift::transport::TBufferedTransportFactory()); + THRIFT_STDCXX::shared_ptr thread_mgr( + apache::thrift::concurrency::ThreadManager::newSimpleThreadManager(2)); + + thread_mgr->threadFactory(thread_factory); + + thread_mgr->start(); + +#if defined(_THRIFT_STDCXX_H_) || !defined (_THRIFT_VERSION_LOWER_THAN_0_11_0_) + THRIFT_STDCXX::shared_ptr server_transport = + THRIFT_STDCXX::make_shared(FLAGS_port); + + apache::thrift::server::TNonblockingServer server(processor, + transport_factory, transport_factory, protocol_factory, + protocol_factory, server_transport); +#else + apache::thrift::server::TNonblockingServer server(processor, + transport_factory, transport_factory, protocol_factory, + protocol_factory, FLAGS_port); +#endif + server.serve(); + return 0; +} diff --git a/example/thrift_extension_c++/server.cpp b/example/thrift_extension_c++/server.cpp new file mode 100755 index 0000000..ef2ff2b --- /dev/null +++ b/example/thrift_extension_c++/server.cpp @@ -0,0 +1,81 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive EchoRequest and send back EchoResponse. + +#include +#include +#include +#include +#include "gen-cpp/echo_types.h" + +DEFINE_int32(port, 8019, "TCP Port of this server"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); +DEFINE_int32(max_concurrency, 0, "Limit of request processing in parallel"); + +// Adapt your own thrift-based protocol to use brpc +class EchoServiceImpl : public brpc::ThriftService { +public: + void ProcessThriftFramedRequest(brpc::Controller* cntl, + brpc::ThriftFramedMessage* req, + brpc::ThriftFramedMessage* res, + google::protobuf::Closure* done) override { + // Dispatch calls to different methods + if (cntl->thrift_method_name() == "Echo") { + return Echo(cntl, req->Cast(), + res->Cast(), done); + } else { + cntl->SetFailed(brpc::ENOMETHOD, "Fail to find method=%s", + cntl->thrift_method_name().c_str()); + done->Run(); + } + } + + void Echo(brpc::Controller* cntl, + const example::EchoRequest* req, + example::EchoResponse* res, + google::protobuf::Closure* done) { + // This object helps you to call done->Run() in RAII style. If you need + // to process the request asynchronously, pass done_guard.release(). + brpc::ClosureGuard done_guard(done); + + res->data = req->data + " (Echo)"; + } +}; + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + google::ParseCommandLineFlags(&argc, &argv, true); + + brpc::Server server; + brpc::ServerOptions options; + + options.thrift_service = new EchoServiceImpl; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + options.max_concurrency = FLAGS_max_concurrency; + + // Start the server. + if (server.Start(FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + // Wait until Ctrl-C is pressed, then Stop() and Join() the server. + server.RunUntilAskedToQuit(); + return 0; +} diff --git a/example/thrift_extension_c++/server2.cpp b/example/thrift_extension_c++/server2.cpp new file mode 100755 index 0000000..9206053 --- /dev/null +++ b/example/thrift_extension_c++/server2.cpp @@ -0,0 +1,104 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A server to receive EchoRequest and send back EchoResponse. + +#include +#include +#include +#include +#include +#include +#include "gen-cpp/echo_types.h" + +DEFINE_int32(port, 8019, "TCP Port of this server"); +DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no " + "read/write operations during the last `idle_timeout_s'"); +DEFINE_int32(max_concurrency, 0, "Limit of request processing in parallel"); + +// Adapt your own thrift-based protocol to use brpc +class EchoServiceImpl : public brpc::ThriftService { +public: + EchoServiceImpl() { + // Initialize the channel, NULL means using default options. + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_THRIFT; + if (_channel.Init("0.0.0.0", FLAGS_port , &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + } + } + + void ProcessThriftFramedRequest(brpc::Controller* cntl, + brpc::ThriftFramedMessage* req, + brpc::ThriftFramedMessage* res, + google::protobuf::Closure* done) override { + // Dispatch calls to different methods + if (cntl->thrift_method_name() == "Echo") { + // Proxy request/response to RealEcho, note that as a proxy we + // don't need to Cast the messages to native types. + brpc::Controller cntl; + brpc::ThriftStub stub(&_channel); + // TODO: Following Cast<> drops data field from ProxyRequest which + // does not recognize the field, should be debugged further. + // LOG(INFO) << "req=" << *req->Cast(); + stub.CallMethod("RealEcho", &cntl, req, res, NULL); + done->Run(); + } else if (cntl->thrift_method_name() == "RealEcho") { + return RealEcho(cntl, req->Cast(), + res->Cast(), done); + } else { + cntl->SetFailed(brpc::ENOMETHOD, "Fail to find method=%s", + cntl->thrift_method_name().c_str()); + done->Run(); + } + } + + void RealEcho(brpc::Controller* cntl, + const example::EchoRequest* req, + example::EchoResponse* res, + google::protobuf::Closure* done) { + // This object helps you to call done->Run() in RAII style. If you need + // to process the request asynchronously, pass done_guard.release(). + brpc::ClosureGuard done_guard(done); + + res->data = req->data + " (RealEcho)"; + } +private: + brpc::Channel _channel; +}; + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + google::ParseCommandLineFlags(&argc, &argv, true); + + brpc::Server server; + brpc::ServerOptions options; + + options.thrift_service = new EchoServiceImpl; + options.idle_timeout_sec = FLAGS_idle_timeout_s; + options.max_concurrency = FLAGS_max_concurrency; + + // Start the server. + if (server.Start(FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to start EchoServer"; + return -1; + } + + // Wait until Ctrl-C is pressed, then Stop() and Join() the server. + server.RunUntilAskedToQuit(); + return 0; +} diff --git a/homebrew-formula/protobuf.rb b/homebrew-formula/protobuf.rb new file mode 100644 index 0000000..15521ad --- /dev/null +++ b/homebrew-formula/protobuf.rb @@ -0,0 +1,100 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +class Protobuf < Formula + desc "Protocol buffers (Google's data interchange format)" + homepage "https://github.com/protocolbuffers/protobuf/" + url "https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protobuf-all-3.19.4.tar.gz" + sha256 "ba0650be1b169d24908eeddbe6107f011d8df0da5b1a5a4449a913b10e578faf" + license "BSD-3-Clause" + + livecheck do + url :stable + strategy :github_latest + end + + bottle do + sha256 cellar: :any, arm64_monterey: "a467da7231471d7913ed291e83852e1ca950db86d142b2a67e0839743dc132b7" + sha256 cellar: :any, arm64_big_sur: "188863a706dd31a59ce0f4bdcf7d77d46e681ed8e72a8ab9ba28e771b52b58fd" + sha256 cellar: :any, monterey: "ca9840b58a314543c0f45490e6a543eb330eb772f0db385ef005d82b6b169047" + sha256 cellar: :any, big_sur: "a6e39ca1c9418561aa2e576a62c86fe11674b81c922a8f610c75aa9211646863" + sha256 cellar: :any, catalina: "5cc145bfca99db8fbe89d8b24394297bde7075aaa3d564cd24478c5762563ef6" + sha256 cellar: :any_skip_relocation, x86_64_linux: "7c3e53cb5448c38183693262da84e5e100a11c3d08de6b5088ed2d1a7f00e106" + end + + head do + url "https://github.com/protocolbuffers/protobuf.git" + + depends_on "autoconf" => :build + depends_on "automake" => :build + depends_on "libtool" => :build + end + + depends_on "python@3.10" => [:build, :test] + # The Python3.9 bindings can be removed when Python3.9 is made keg-only. + depends_on "python@3.9" => [:build, :test] + + uses_from_macos "zlib" + + def install + # Don't build in debug mode. See: + # https://github.com/Homebrew/homebrew/issues/9279 + # https://github.com/protocolbuffers/protobuf/blob/5c24564811c08772d090305be36fae82d8f12bbe/configure.ac#L61 + ENV.prepend "CXXFLAGS", "-DNDEBUG" + ENV.cxx11 + + system "./autogen.sh" if build.head? + system "./configure", "--disable-debug", "--disable-dependency-tracking", + "--prefix=#{prefix}", "--with-zlib" + system "make" + system "make", "check" + system "make", "install" + + # Install editor support and examples + pkgshare.install "editors/proto.vim", "examples" + elisp.install "editors/protobuf-mode.el" + + ENV.append_to_cflags "-I#{include}" + ENV.append_to_cflags "-L#{lib}" + + cd "python" do + ["3.9", "3.10"].each do |xy| + site_packages = prefix/Language::Python.site_packages("python#{xy}") + system "python#{xy}", *Language::Python.setup_install_args(prefix), + "--install-lib=#{site_packages}", + "--cpp_implementation" + end + end + end + + test do + testdata = <<~EOS + syntax = "proto3"; + package test; + message TestCase { + string name = 4; + } + message Test { + repeated TestCase case = 1; + } + EOS + (testpath/"test.proto").write testdata + system bin/"protoc", "test.proto", "--cpp_out=." + system Formula["python@3.9"].opt_bin/"python3", "-c", "import google.protobuf" + system Formula["python@3.10"].opt_bin/"python3", "-c", "import google.protobuf" + end +end diff --git a/package/rpm/brpc.spec b/package/rpm/brpc.spec new file mode 100644 index 0000000..19c2d83 --- /dev/null +++ b/package/rpm/brpc.spec @@ -0,0 +1,125 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +Name: brpc +Version: 1.17.0 +Release: 1%{?dist} +Summary: Industrial-grade RPC framework using C++ Language. + +Group: Development +License: Apache2 +URL: https://github.com/apache/brpc +Source0: https://downloads.apache.org/brpc/%{version}/apache-brpc-%{version}-src.tar.gz + +# https://access.redhat.com/solutions/519993 +%global _filter_GLIBC_PRIVATE 1 +%global __filter_GLIBC_PRIVATE 1 + +%if 0%{?fedora} >= 15 || 0%{?rhel} >= 8 +%global use_devtoolset 0 +%else +%global use_devtoolset 1 +%endif + +%if 0%{?use_devtoolset} +BuildRequires: devtoolset-8-gcc-c++ +%define __strip /opt/rh/devtoolset-8/root/usr/bin/strip +%endif + +BuildRequires: cmake +BuildRequires: gcc +BuildRequires: gcc-c++ +BuildRequires: gflags-devel >= 2.1 +BuildRequires: protobuf-devel >= 2.4 +BuildRequires: leveldb-devel +BuildRequires: openssl-devel + +%description +Apache bRPC is an Industrial-grade RPC framework using C++ Language, +which is often used in high performance systems such as Search, Storage, +Machine learning, Advertisement, Recommendation etc. + +%package tools +Summary: The %{name} tools. +Requires: %{name} = %{version}-%{release} +%description tools +The %{name} tools. + +%package devel +Summary: The %{name} headers and shared development libraries +Requires: %{name} = %{version}-%{release} +%description devel +Headers and shared object symbolic links for the %{name} library. + +%package static +Summary: The %{name} static development libraries +Requires: brpc-devel = %{version}-%{release} +%description static +Static %{name} libraries. + +%prep +%setup -n apache-%{name}-%{version}-src + +%build +%if 0%{?use_devtoolset} +. /opt/rh/devtoolset-8/enable +%endif + +%if 0%{?fedora} >= 33 || 0%{?rhel} >= 8 +%{cmake} -DBUILD_BRPC_TOOLS:BOOLEAN=ON -DDOWNLOAD_GTEST:BOOLEAN=OFF +%{cmake_build} +%else +mkdir -p %{_target_platform} +pushd %{_target_platform} + +%{cmake} -DBUILD_BRPC_TOOLS:BOOLEAN=ON -DDOWNLOAD_GTEST:BOOLEAN=OFF .. +make %{?_smp_mflags} + +popd +%endif + +%install +rm -rf $RPM_BUILD_ROOT + +%if 0%{?fedora} >= 33 || 0%{?rhel} >= 8 +%{cmake_install} +%else +pushd %{_target_platform} +%make_install +popd +%endif + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%{_libdir}/libbrpc.so + +%files tools +%{_bindir}/* + +%files devel +%{_includedir}/* +%{_libdir}/pkgconfig/* + +%files static +%{_libdir}/libbrpc.a + +%changelog + diff --git a/registry/modules/libunwind/1.8.1.brpc-no-unwind/MODULE.bazel b/registry/modules/libunwind/1.8.1.brpc-no-unwind/MODULE.bazel new file mode 100644 index 0000000..1540330 --- /dev/null +++ b/registry/modules/libunwind/1.8.1.brpc-no-unwind/MODULE.bazel @@ -0,0 +1,21 @@ +# Forked from the Bazel Central Registry: +# https://github.com/bazelbuild/bazel-central-registry/blob/main/modules/libunwind/1.8.1/MODULE.bazel +# Distributed under the Apache License, Version 2.0. See the `registry/modules/ +# libunwind/**` entry near the bottom of brpc's LICENSE file. +# +# brpc modification (Apache License 2.0 §4(b)): +# - `version` suffixed with `.brpc-no-unwind` to distinguish this brpc +# variant from the upstream BCR version. + +module( + name = "libunwind", + version = "1.8.1.brpc-no-unwind", + bazel_compatibility = [">=7.2.1"], # need support for "overlay" directory + compatibility_level = 0, +) + +bazel_dep(name = "bazel_skylib", version = "1.7.1") +bazel_dep(name = "platforms", version = "0.0.11") +bazel_dep(name = "rules_cc", version = "0.1.1") +bazel_dep(name = "xz", version = "5.4.5.bcr.5") +bazel_dep(name = "zlib", version = "1.3.1.bcr.5") diff --git a/registry/modules/libunwind/1.8.1.brpc-no-unwind/overlay/BUILD.bazel b/registry/modules/libunwind/1.8.1.brpc-no-unwind/overlay/BUILD.bazel new file mode 100644 index 0000000..ec5ecd1 --- /dev/null +++ b/registry/modules/libunwind/1.8.1.brpc-no-unwind/overlay/BUILD.bazel @@ -0,0 +1,781 @@ +# This file is forked from the Bazel Central Registry: +# https://github.com/bazelbuild/bazel-central-registry/blob/main/modules/libunwind/1.8.1/overlay/BUILD.bazel +# +# The original file is distributed under the Apache License, Version 2.0 +# (https://www.apache.org/licenses/LICENSE-2.0). See the `registry/modules/ +# libunwind/**` entry near the bottom of brpc's LICENSE file for the full +# notice and the list of brpc's modifications. +# +# Modifications by brpc maintainers (Apache License 2.0 §4(b)): +# - Add the `hide_unwind_symbols` config_setting (gated by +# `--define=libunwind_hide_unwind_symbols=true`). +# - When the switch is on, drop `src/unwind/*.c` (the GCC `_Unwind_*` ABI +# compatibility layer) from `unwind_srcs` so the resulting libunwind +# does not export `_Unwind_*` symbols. See docs/cn/bthread_tracer.md +# for the rationale. + +load("@bazel_skylib//lib:selects.bzl", "selects") +load("@bazel_skylib//rules:expand_template.bzl", "expand_template") +load("@rules_cc//cc:defs.bzl", "cc_library") + +# Targets in this file are based off configure.ac, Makefile.am, and +# src/Makefile.am with the default configurations. +# +# Only supports aarch64, arm, and x86_64 on Linux for now. + +### Config settings ############################################################ + +# Bazel does not support nested selects, so we need config settings with the +# various combinations of OS and CPU. + +# Switch to drop the GCC `_Unwind_*` ABI compatibility layer (src/unwind/*.c) +# from the build. These sources implement `_Unwind_GetLanguageSpecificData`, +# `_Unwind_ForcedUnwind`, `_Unwind_Resume`, etc. - the very symbols that +# libgcc_s also provides. When Bazel builds libunwind as a dylib (its default +# behavior in fastbuild), these symbols get exported and at runtime the dynamic +# loader resolves the libstdc++ / pthread_exit unwinding paths to libunwind's +# DWARF-based implementations instead of libgcc_s's, causing crashes such as: +# _Unwind_ForcedUnwind -> __gxx_personality_v0 -> +# __libunwind_Unwind_GetLanguageSpecificData -> SEGV in +# _ULx86_64_dwarf_find_proc_info. +# +# brpc only consumes libunwind's native `unw_*` API (provided by src/mi/), so +# omitting src/unwind/*.c is safe and is the same effect autoconf gets via +# `--version-script` in the upstream Makefile (the make-built libunwind.so does +# not export `_Unwind_*` either - see CI's init-ut-make-config action). +# +# Enable with: --define=libunwind_hide_unwind_symbols=true +config_setting( + name = "hide_unwind_symbols", + values = {"define": "libunwind_hide_unwind_symbols=true"}, +) + +selects.config_setting_group( + name = "linux_arm64", + match_all = [ + "@platforms//os:linux", + "@platforms//cpu:arm64", + ], +) + +selects.config_setting_group( + name = "linux_arm", + match_all = [ + "@platforms//os:linux", + "@platforms//cpu:arm", + ], +) + +selects.config_setting_group( + name = "linux_x86_64", + match_all = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], +) + +### Common defines ############################################################# + +libunwind_defines = [ + # Defaults based on configure.ac assuming we're on a modern Linux OS. + "_GNU_SOURCE", + "CONFIG_BLOCK_SIGNALS", + "CONFIG_WEAK_BACKTRACE", + "CONSERVATIVE_CHECKS", + "HAVE__BUILTIN___CLEAR_CACHE", + "HAVE__BUILTIN_UNREACHABLE", + "HAVE_ELF_H", + "HAVE_LINK_H", + "HAVE_LZMA", + "HAVE_ZLIB", +] + +### Common source files ######################################################## + +dwarf_srcs = glob(["src/dwarf/*.c"]) + +dwarf_textual_hdrs = glob(["src/dwarf/G*.c"]) + +mi_srcs = glob( + ["src/mi/*.c"], + exclude = [ + # Only included if Linux. + "src/mi/_ReadSLEB.c", + "src/mi/_ReadULEB.c", + # The Makefile does not include this, it's also broken as it can include + # Gdyn-remote.c in certain situations, which uses WSIZE, which will not + # be defined in the situations Gdyn-remote.c is included. + "src/mi/Ldyn-remote.c", + # TODO: for some reason these complain about duplicate definitions if + # included. + "src/mi/Gaddress_validator.c", + "src/mi/Gget_accessors.c", + ], +) + +mi_textual_hdrs = glob(["src/mi/G*.c"]) + +# When `--define=libunwind_hide_unwind_symbols=true`, drop src/unwind/ entirely +# so libunwind does not provide its own `_Unwind_*` implementations. See the +# comment on `:hide_unwind_symbols` config_setting above. +unwind_srcs = select({ + ":hide_unwind_symbols": [], + "//conditions:default": glob([ + "src/unwind/*.c", + "src/unwind/*.h", + ]), +}) + +### Features source files ###################################################### + +coredump_srcs = glob( + [ + "src/coredump/*.c", + "src/coredump/*.h", + ], + exclude = [ + "src/coredump/_UCD_access_reg_freebsd.c", + "src/coredump/_UCD_access_reg_linux.c", + "src/coredump/_UCD_access_reg_qnx.c", + "src/coredump/_UCD_get_mapinfo_generic.c", + "src/coredump/_UCD_get_mapinfo_linux.c", + "src/coredump/_UCD_get_mapinfo_qnx.c", + "src/coredump/_UCD_get_threadinfo_prstatus.c", + ], +) + select({ + "@platforms//os:linux": [ + "src/coredump/_UCD_access_reg_linux.c", + "src/coredump/_UCD_get_mapinfo_linux.c", + "src/coredump/_UCD_get_threadinfo_prstatus.c", + ], + "//conditions:default": [], +}) + +ptrace_srcs = glob([ + "src/ptrace/*.c", + "src/ptrace/*.h", +]) + +setjmp_srcs = glob([ + "src/setjmp/*.c", + "src/setjmp/*.h", +]) + select({ + "@platforms//cpu:aarch64": [ + "src/aarch64/longjmp.S", + "src/aarch64/siglongjmp.S", + ], + "@platforms//cpu:arm": [ + "src/arm/siglongjmp.S", + ], + "@platforms//cpu:x86_64": [ + "src/x86_64/longjmp.S", + "src/x86_64/siglongjmp.S", + ], +}) + +### Arch specific source files ################################################# + +arm64_srcs = select({ + "@platforms//cpu:aarch64": glob( + [ + "src/aarch64/*.c", + "src/aarch64/*.h", + ], + exclude = [ + "src/aarch64/Los-freebsd.c", + "src/aarch64/Los-linux.c", + "src/aarch64/Los-qnx.c", + "src/aarch64/Gos-freebsd.c", + "src/aarch64/Gos-linux.c", + "src/aarch64/Gos-qnx.c", + ], + ) + [ + # The Makefile doesn't include this and only includes it if the OS + # is FreeBSD. This is inconsistent with the other archs, not sure + # whether this is intended. + # "src/aarch64/setcontext.S", + "src/aarch64/getcontext.S", + "src/elf64.h", + ], + "//conditions:default": [], +}) + +arm64_textual_hdrs = select({ + "@platforms//cpu:aarch64": glob( + [ + "src/aarch64/G*.c", + ], + exclude = [ + "src/aarch64/Gos-freebsd.c", + "src/aarch64/Gos-linux.c", + "src/aarch64/Gos-qnx.c", + ], + ) + [ + "src/elf64.c", + ], + "//conditions:default": [], +}) + +arm_srcs = select({ + "@platforms//cpu:arm": glob( + [ + "src/arm/*.c", + "src/arm/*.h", + ], + exclude = [ + "src/arm/Los-freebsd.c", + "src/arm/Los-linux.c", + "src/arm/Los-other.c", + "src/arm/Gos-freebsd.c", + "src/arm/Gos-linux.c", + "src/arm/Gos-other.c", + ], + ) + [ + "src/arm/getcontext.S", + "src/elf32.h", + ], + "//conditions:default": [], +}) + +arm_textual_hdrs = select({ + "@platforms//cpu:arm": glob( + [ + "src/arm/G*.c", + ], + exclude = [ + "src/arm/Gos-freebsd.c", + "src/arm/Gos-linux.c", + "src/arm/Gos-other.c", + ], + ) + [ + "src/elf32.c", + ], + "//conditions:default": [], +}) + +x86_64_srcs = select({ + "@platforms//cpu:x86_64": glob( + [ + "src/x86_64/*.c", + "src/x86_64/*.h", + ], + exclude = [ + "src/x86_64/Los-freebsd.c", + "src/x86_64/Los-linux.c", + "src/x86_64/Los-qnx.c", + "src/x86_64/Los-solaris.c", + "src/x86_64/Gos-freebsd.c", + "src/x86_64/Gos-linux.c", + "src/x86_64/Gos-qnx.c", + "src/x86_64/Gos-solaris.c", + ], + ) + [ + "src/elf64.h", + "src/x86_64/getcontext.S", + "src/x86_64/setcontext.S", + ], + "//conditions:default": [], +}) + +x86_64_textual_hdrs = select({ + "@platforms//cpu:x86_64": glob( + [ + "src/x86_64/G*.c", + ], + exclude = [ + "src/x86_64/Gos-freebsd.c", + "src/x86_64/Gos-linux.c", + "src/x86_64/Gos-qnx.c", + "src/x86_64/Gos-solaris.c", + ], + ) + [ + "src/elf64.c", + ], + "//conditions:default": [], +}) + +### OS specific source files ################################################### + +linux_srcs = select({ + "@platforms//os:linux": [ + "src/dl-iterate-phdr.c", + "src/mi/_ReadSLEB.c", + "src/mi/_ReadULEB.c", + "src/os-linux.c", + "src/os-linux.h", + ], + "//conditions:default": [], +}) + select({ + ":linux_arm64": [ + "src/aarch64/Los-linux.c", + "src/aarch64/Gos-linux.c", + ], + "//conditions:default": [], +}) + select({ + ":linux_arm": [ + "src/arm/Los-linux.c", + "src/arm/Gos-linux.c", + ], + "//conditions:default": [], +}) + select({ + ":linux_x86_64": [ + "src/x86_64/Los-linux.c", + "src/x86_64/Gos-linux.c", + ], + "//conditions:default": [], +}) + +linux_textual_hdrs = select({ + ":linux_arm64": ["src/aarch64/Gos-linux.c"], + "//conditions:default": [], +}) + select({ + ":linux_arm": ["src/arm/Gos-linux.c"], + "//conditions:default": [], +}) + select({ + ":linux_x86_64": ["src/x86_64/Gos-linux.c"], + "//conditions:default": [], +}) + +### libunwind ################################################################## + +libunwind_srcs = [ + "src/elfxx.h", + "src/elfxx.c", +] + dwarf_srcs + mi_srcs + unwind_srcs + arm64_srcs + arm_srcs + x86_64_srcs + linux_srcs + +libunwind_textual_hdrs = [ + "src/elfxx.c", +] + dwarf_textual_hdrs + mi_textual_hdrs + arm64_textual_hdrs + arm_textual_hdrs + x86_64_textual_hdrs + linux_textual_hdrs + +expand_template( + name = "libunwind_common_h", + out = "include/libunwind-common.h", + substitutions = { + "@PKG_MAJOR@": "1", + "@PKG_MINOR@": "8", + "@PKG_EXTRA@": "1", + }, + template = "include/libunwind-common.h.in", +) + +cc_library( + name = "unwind", + srcs = libunwind_srcs, + hdrs = glob(["include/tdep/*.h"]) + [ + "include/compiler.h", + "include/dwarf.h", + "include/dwarf-eh.h", + "include/dwarf_i.h", + "include/libunwind.h", + "include/libunwind-dynamic.h", + "include/libunwind_i.h", + "include/mempool.h", + "include/remote.h", + "include/unwind.h", + ":libunwind_common_h", + ] + select({ + "@platforms//cpu:arm64": glob(["include/tdep-aarch64/*.h"]) + ["include/libunwind-aarch64.h"], + "@platforms//cpu:arm": glob(["include/tdep-arm/*.h"]) + ["include/libunwind-arm.h"], + "@platforms//cpu:x86_64": ["include/libunwind-x86_64.h"] + glob(["include/tdep-x86_64/*.h"]), + }), + includes = [ + "include", + "src", + ] + select({ + "@platforms//cpu:arm64": [ + "include/tdep-aarch64", + ], + "@platforms//cpu:arm": [ + "include/tdep-arm", + ], + "@platforms//cpu:x86_64": [ + "include/tdep-x86_64", + ], + }), + local_defines = libunwind_defines + ["HAVE_DL_ITERATE_PHDR"], + textual_hdrs = libunwind_textual_hdrs, + deps = [ + "@xz//:lzma", + "@zlib", + ], +) + +cc_library( + name = "unwind_coredump", + srcs = coredump_srcs + ["src/mi/init.c"], + hdrs = ["include/libunwind-coredump.h"], + includes = ["include"], + local_defines = libunwind_defines + [ + "HAVE_STRUCT_ELF_PRSTATUS", + "HAVE_SYS_PROCFS_H", + ] + select({ + "@platforms//cpu:arm64": [ + "CONFIG_DEBUG_FRAME", + "SIZEOF_OFF_T=8", + ], + "@platforms//cpu:arm": [ + "CONFIG_DEBUG_FRAME", + "SIZEOF_OFF_T=4", + ], + "@platforms//cpu:x86_64": [ + "SIZEOF_OFF_T=8", + ], + }), + deps = [ + ":unwind", + "@xz//:lzma", + "@zlib", + ], +) + +cc_library( + name = "unwind_ptrace", + srcs = ptrace_srcs + ["src/mi/init.c"], + hdrs = ["include/libunwind-ptrace.h"], + includes = ["include"], + local_defines = libunwind_defines + ["HAVE_TTRACE"], + deps = [ + ":unwind", + "@xz//:lzma", + "@zlib", + ], +) + +cc_library( + name = "unwind_setjmp", + srcs = setjmp_srcs + ["src/mi/init.c"], + local_defines = libunwind_defines, + deps = [ + ":unwind", + "@xz//:lzma", + "@zlib", + ], +) + +alias( + name = "libunwind", + actual = ":unwind", + visibility = ["//visibility:public"], +) + +alias( + name = "libunwind_coredump", + actual = ":unwind_coredump", + visibility = ["//visibility:public"], +) + +alias( + name = "libunwind_ptrace", + actual = ":unwind_ptrace", + visibility = ["//visibility:public"], +) + +alias( + name = "libunwind_setjmp", + actual = ":unwind_setjmp", + visibility = ["//visibility:public"], +) + +### Tests ##################################################################### + +# TODO: the following tests currently do not work: +# crasher.c +# forker.c +# test-coredump-unwind.c +# test-init-remote.c +# test-proc-info.c +# test-ptrace-misc.c +# test-ptrace.c +# test-setjmp.c + +# Some tests require these test helper files. +cc_library( + name = "unwind_test_helpers", + testonly = True, + srcs = [ + "tests/flush-cache.S", + "tests/ident.c", + ], + hdrs = glob(["tests/*G*.c"]) + [ + "tests/Gtest-init.cxx", + "tests/flush-cache.h", + "tests/ident.h", + ], + defines = ["_GNU_SOURCE"], +) + +cc_test( + name = "frame_record_test", + srcs = ["tests/aarch64-test-frame-record.c"], + target_compatible_with = ["@platforms//cpu:arm64"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "plt_test", + srcs = ["tests/aarch64-test-plt.c"], + target_compatible_with = ["@platforms//cpu:arm64"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "arm64_sve_signal_test", + srcs = ["tests/Larm64-test-sve-signal.c"], + target_compatible_with = ["@platforms//cpu:arm64"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "perf_simple_test", + srcs = ["tests/Lperf-simple.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "perf_trace_test", + srcs = ["tests/Lperf-trace.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "rs_race_test", + srcs = ["tests/Lrs-race.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "bt_test", + srcs = ["tests/Ltest-bt.c"], + local_defines = ["UNW_LOCAL_ONLY"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "concurrent_test", + srcs = ["tests/Ltest-concurrent.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "cxx_exceptions_test", + srcs = ["tests/Ltest-cxx-exceptions.cxx"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +# TODO: fails on Debian 11 and Ubuntu 20.04. +# cc_test( +# name = "dyn1_test", +# srcs = ["tests/Ltest-dyn1.c"], +# deps = [ +# ":unwind", +# ":unwind_test_helpers", +# ], +# linkopts = ["-ldl"], +# ) + +cc_test( + name = "exc_test", + srcs = ["tests/Ltest-exc.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "init_local_signal_test", + srcs = [ + "tests/Ltest-init-local-signal.c", + "tests/Ltest-init-local-signal-lib.c", + ], + local_defines = ["UNW_LOCAL_ONLY"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "init_test", + srcs = ["tests/Ltest-init.cxx"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "mem_validate_test", + srcs = ["tests/Ltest-mem-validate.c"], + local_defines = ["UNW_LOCAL_ONLY"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "nocalloc_test", + srcs = ["tests/Ltest-nocalloc.c"], + linkopts = ["-ldl"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "nomalloc_test", + srcs = ["tests/Ltest-nomalloc.c"], + linkopts = ["-ldl"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "resume_sig_rt_test", + srcs = ["tests/Ltest-resume-sig-rt.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "resume_sig_test", + srcs = ["tests/Ltest-resume-sig.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "trace_test", + srcs = ["tests/Ltest-trace.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "varargs_test", + srcs = ["tests/Ltest-varargs.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "dwarf_expressions_test", + srcs = [ + "tests/Lx64-test-dwarf-expressions.c", + "tests/x64-test-dwarf-expressions.S", + ], + target_compatible_with = ["@platforms//cpu:x86_64"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "async_sig_test", + srcs = ["tests/test-async-sig.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "flush_cache_test", + srcs = ["tests/test-flush-cache.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "mem_test", + srcs = ["tests/test-mem.c"], + local_defines = ["UNW_LOCAL_ONLY"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "reg_state_test", + srcs = ["tests/test-reg-state.c"], + local_defines = ["UNW_LOCAL_ONLY"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "static_link_loc_test", + srcs = [ + "tests/test-static-link-gen.c", + "tests/test-static-link-loc.c", + ], + local_defines = ["UNW_LOCAL_ONLY"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "strerror_test", + srcs = ["tests/test-strerror.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "unwind_badjmp_signal_frame_test", + srcs = ["tests/x64-unwind-badjmp-signal-frame.c"], + target_compatible_with = ["@platforms//cpu:x86_64"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) diff --git a/registry/modules/libunwind/1.8.1.brpc-no-unwind/overlay/MODULE.bazel b/registry/modules/libunwind/1.8.1.brpc-no-unwind/overlay/MODULE.bazel new file mode 100644 index 0000000..13035df --- /dev/null +++ b/registry/modules/libunwind/1.8.1.brpc-no-unwind/overlay/MODULE.bazel @@ -0,0 +1,21 @@ +# Forked from the Bazel Central Registry: +# https://github.com/bazelbuild/bazel-central-registry/blob/main/modules/libunwind/1.8.1/overlay/MODULE.bazel +# Distributed under the Apache License, Version 2.0. See the `registry/modules/ +# libunwind/**` entry near the bottom of brpc's LICENSE file. +# +# brpc modification (Apache License 2.0 §4(b)): +# - `version` suffixed with `.brpc-no-unwind` to distinguish this brpc +# variant from the upstream BCR version. + +module( + name = "libunwind", + version = "1.8.1.brpc-no-unwind", + bazel_compatibility = [">=7.2.1"], # need support for "overlay" directory + compatibility_level = 0, +) + +bazel_dep(name = "bazel_skylib", version = "1.7.1") +bazel_dep(name = "platforms", version = "0.0.11") +bazel_dep(name = "rules_cc", version = "0.1.1") +bazel_dep(name = "xz", version = "5.4.5.bcr.5") +bazel_dep(name = "zlib", version = "1.3.1.bcr.5") diff --git a/registry/modules/libunwind/1.8.1.brpc-no-unwind/presubmit.yml b/registry/modules/libunwind/1.8.1.brpc-no-unwind/presubmit.yml new file mode 100644 index 0000000..3a05441 --- /dev/null +++ b/registry/modules/libunwind/1.8.1.brpc-no-unwind/presubmit.yml @@ -0,0 +1,18 @@ +matrix: + platform: + - debian11 + - ubuntu2004 + - ubuntu2204 + - ubuntu2404 + bazel: [7.x, 8.x, rolling] +tasks: + verify_targets: + platform: ${{ platform }} + bazel: ${{ bazel }} + build_targets: + - "@libunwind//:libunwind" + - "@libunwind//:libunwind_coredump" + - "@libunwind//:libunwind_ptrace" + - "@libunwind//:libunwind_setjmp" + test_targets: + - "@libunwind//..." diff --git a/registry/modules/libunwind/1.8.1.brpc-no-unwind/source.json b/registry/modules/libunwind/1.8.1.brpc-no-unwind/source.json new file mode 100644 index 0000000..54bbb9f --- /dev/null +++ b/registry/modules/libunwind/1.8.1.brpc-no-unwind/source.json @@ -0,0 +1,9 @@ +{ + "url": "https://github.com/libunwind/libunwind/releases/download/v1.8.1/libunwind-1.8.1.tar.gz", + "integrity": "sha256-3fDjLdX6/lKDGY035L+d7Pe6F3C25+AGwz5t955qYVc=", + "strip_prefix": "libunwind-1.8.1", + "overlay": { + "BUILD.bazel": "sha256-X2B6q+gpb5U0BGoD8LJPoBPCpyIvT5QFSwsjFxZ9R+0=", + "MODULE.bazel": "sha256-Yu1CCsCC+A2NXfQ3bgzpbtQcYhM+bPPb2YAPKPojdIY=" + } +} diff --git a/registry/modules/libunwind/1.8.3.brpc-no-unwind/MODULE.bazel b/registry/modules/libunwind/1.8.3.brpc-no-unwind/MODULE.bazel new file mode 100644 index 0000000..c85228a --- /dev/null +++ b/registry/modules/libunwind/1.8.3.brpc-no-unwind/MODULE.bazel @@ -0,0 +1,21 @@ +# Forked from the Bazel Central Registry: +# https://github.com/bazelbuild/bazel-central-registry/blob/main/modules/libunwind/1.8.3/MODULE.bazel +# Distributed under the Apache License, Version 2.0. See the `registry/modules/ +# libunwind/**` entry near the bottom of brpc's LICENSE file. +# +# brpc modification (Apache License 2.0 §4(b)): +# - `version` suffixed with `.brpc-no-unwind` to distinguish this brpc +# variant from the upstream BCR version. + +module( + name = "libunwind", + version = "1.8.3.brpc-no-unwind", + bazel_compatibility = [">=7.2.1"], # need support for "overlay" directory + compatibility_level = 0, +) + +bazel_dep(name = "bazel_skylib", version = "1.7.1") +bazel_dep(name = "platforms", version = "0.0.11") +bazel_dep(name = "rules_cc", version = "0.1.1") +bazel_dep(name = "xz", version = "5.4.5.bcr.8") +bazel_dep(name = "zlib", version = "1.3.1.bcr.8") diff --git a/registry/modules/libunwind/1.8.3.brpc-no-unwind/overlay/BUILD.bazel b/registry/modules/libunwind/1.8.3.brpc-no-unwind/overlay/BUILD.bazel new file mode 100644 index 0000000..f8f96c2 --- /dev/null +++ b/registry/modules/libunwind/1.8.3.brpc-no-unwind/overlay/BUILD.bazel @@ -0,0 +1,781 @@ +# This file is forked from the Bazel Central Registry: +# https://github.com/bazelbuild/bazel-central-registry/blob/main/modules/libunwind/1.8.3/overlay/BUILD.bazel +# +# The original file is distributed under the Apache License, Version 2.0 +# (https://www.apache.org/licenses/LICENSE-2.0). See the `registry/modules/ +# libunwind/**` entry near the bottom of brpc's LICENSE file for the full +# notice and the list of brpc's modifications. +# +# Modifications by brpc maintainers (Apache License 2.0 §4(b)): +# - Add the `hide_unwind_symbols` config_setting (gated by +# `--define=libunwind_hide_unwind_symbols=true`). +# - When the switch is on, drop `src/unwind/*.c` (the GCC `_Unwind_*` ABI +# compatibility layer) from `unwind_srcs` so the resulting libunwind +# does not export `_Unwind_*` symbols. See docs/cn/bthread_tracer.md +# for the rationale. + +load("@bazel_skylib//lib:selects.bzl", "selects") +load("@bazel_skylib//rules:expand_template.bzl", "expand_template") +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") + +# Targets in this file are based off configure.ac, Makefile.am, and +# src/Makefile.am with the default configurations. +# +# Only supports aarch64, arm, and x86_64 on Linux for now. + +### Config settings ############################################################ + +# Bazel does not support nested selects, so we need config settings with the +# various combinations of OS and CPU. + +# Switch to drop the GCC `_Unwind_*` ABI compatibility layer (src/unwind/*.c) +# from the build. These sources implement `_Unwind_GetLanguageSpecificData`, +# `_Unwind_ForcedUnwind`, `_Unwind_Resume`, etc. - the very symbols that +# libgcc_s also provides. When Bazel builds libunwind as a dylib (its default +# behavior in fastbuild), these symbols get exported and at runtime the dynamic +# loader resolves the libstdc++ / pthread_exit unwinding paths to libunwind's +# DWARF-based implementations instead of libgcc_s's, causing crashes such as: +# _Unwind_ForcedUnwind -> __gxx_personality_v0 -> +# __libunwind_Unwind_GetLanguageSpecificData -> SEGV in +# _ULx86_64_dwarf_find_proc_info. +# +# brpc only consumes libunwind's native `unw_*` API (provided by src/mi/), so +# omitting src/unwind/*.c is safe and is the same effect autoconf gets via +# `--version-script` in the upstream Makefile (the make-built libunwind.so does +# not export `_Unwind_*` either - see CI's init-ut-make-config action). +# +# Enable with: --define=libunwind_hide_unwind_symbols=true +config_setting( + name = "hide_unwind_symbols", + values = {"define": "libunwind_hide_unwind_symbols=true"}, +) + +selects.config_setting_group( + name = "linux_arm64", + match_all = [ + "@platforms//os:linux", + "@platforms//cpu:arm64", + ], +) + +selects.config_setting_group( + name = "linux_arm", + match_all = [ + "@platforms//os:linux", + "@platforms//cpu:arm", + ], +) + +selects.config_setting_group( + name = "linux_x86_64", + match_all = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], +) + +### Common defines ############################################################# + +libunwind_defines = [ + # Defaults based on configure.ac assuming we're on a modern Linux OS. + "_GNU_SOURCE", + "CONFIG_BLOCK_SIGNALS", + "CONFIG_WEAK_BACKTRACE", + "CONSERVATIVE_CHECKS", + "HAVE__BUILTIN___CLEAR_CACHE", + "HAVE__BUILTIN_UNREACHABLE", + "HAVE_ELF_H", + "HAVE_LINK_H", + "HAVE_LZMA", + "HAVE_ZLIB", +] + +### Common source files ######################################################## + +dwarf_srcs = glob(["src/dwarf/*.c"]) + +dwarf_textual_hdrs = glob(["src/dwarf/G*.c"]) + +mi_srcs = glob( + ["src/mi/*.c"], + exclude = [ + # Only included if Linux. + "src/mi/_ReadSLEB.c", + "src/mi/_ReadULEB.c", + # The Makefile does not include this, it's also broken as it can include + # Gdyn-remote.c in certain situations, which uses WSIZE, which will not + # be defined in the situations Gdyn-remote.c is included. + "src/mi/Ldyn-remote.c", + # TODO: for some reason these complain about duplicate definitions if + # included. + "src/mi/Gaddress_validator.c", + "src/mi/Gget_accessors.c", + ], +) + +mi_textual_hdrs = glob(["src/mi/G*.c"]) + +# When `--define=libunwind_hide_unwind_symbols=true`, drop src/unwind/ entirely +# so libunwind does not provide its own `_Unwind_*` implementations. See the +# comment on `:hide_unwind_symbols` config_setting above. +unwind_srcs = select({ + ":hide_unwind_symbols": [], + "//conditions:default": glob([ + "src/unwind/*.c", + "src/unwind/*.h", + ]), +}) + +### Features source files ###################################################### + +coredump_srcs = glob( + [ + "src/coredump/*.c", + "src/coredump/*.h", + ], + exclude = [ + "src/coredump/_UCD_access_reg_freebsd.c", + "src/coredump/_UCD_access_reg_linux.c", + "src/coredump/_UCD_access_reg_qnx.c", + "src/coredump/_UCD_get_mapinfo_generic.c", + "src/coredump/_UCD_get_mapinfo_linux.c", + "src/coredump/_UCD_get_mapinfo_qnx.c", + "src/coredump/_UCD_get_threadinfo_prstatus.c", + ], +) + select({ + "@platforms//os:linux": [ + "src/coredump/_UCD_access_reg_linux.c", + "src/coredump/_UCD_get_mapinfo_linux.c", + "src/coredump/_UCD_get_threadinfo_prstatus.c", + ], + "//conditions:default": [], +}) + +ptrace_srcs = glob([ + "src/ptrace/*.c", + "src/ptrace/*.h", +]) + +setjmp_srcs = glob([ + "src/setjmp/*.c", + "src/setjmp/*.h", +]) + select({ + "@platforms//cpu:aarch64": [ + "src/aarch64/longjmp.S", + "src/aarch64/siglongjmp.S", + ], + "@platforms//cpu:arm": [ + "src/arm/siglongjmp.S", + ], + "@platforms//cpu:x86_64": [ + "src/x86_64/longjmp.S", + "src/x86_64/siglongjmp.S", + ], +}) + +### Arch specific source files ################################################# + +arm64_srcs = select({ + "@platforms//cpu:aarch64": glob( + [ + "src/aarch64/*.c", + "src/aarch64/*.h", + ], + exclude = [ + "src/aarch64/Los-freebsd.c", + "src/aarch64/Los-linux.c", + "src/aarch64/Los-qnx.c", + "src/aarch64/Gos-freebsd.c", + "src/aarch64/Gos-linux.c", + "src/aarch64/Gos-qnx.c", + ], + ) + [ + # The Makefile doesn't include this and only includes it if the OS + # is FreeBSD. This is inconsistent with the other archs, not sure + # whether this is intended. + # "src/aarch64/setcontext.S", + "src/aarch64/getcontext.S", + "src/elf64.h", + ], + "//conditions:default": [], +}) + +arm64_textual_hdrs = select({ + "@platforms//cpu:aarch64": glob( + [ + "src/aarch64/G*.c", + ], + exclude = [ + "src/aarch64/Gos-freebsd.c", + "src/aarch64/Gos-linux.c", + "src/aarch64/Gos-qnx.c", + ], + ) + [ + "src/elf64.c", + ], + "//conditions:default": [], +}) + +arm_srcs = select({ + "@platforms//cpu:arm": glob( + [ + "src/arm/*.c", + "src/arm/*.h", + ], + exclude = [ + "src/arm/Los-freebsd.c", + "src/arm/Los-linux.c", + "src/arm/Los-other.c", + "src/arm/Gos-freebsd.c", + "src/arm/Gos-linux.c", + "src/arm/Gos-other.c", + ], + ) + [ + "src/arm/getcontext.S", + "src/elf32.h", + ], + "//conditions:default": [], +}) + +arm_textual_hdrs = select({ + "@platforms//cpu:arm": glob( + [ + "src/arm/G*.c", + ], + exclude = [ + "src/arm/Gos-freebsd.c", + "src/arm/Gos-linux.c", + "src/arm/Gos-other.c", + ], + ) + [ + "src/elf32.c", + ], + "//conditions:default": [], +}) + +x86_64_srcs = select({ + "@platforms//cpu:x86_64": glob( + [ + "src/x86_64/*.c", + "src/x86_64/*.h", + ], + exclude = [ + "src/x86_64/Los-freebsd.c", + "src/x86_64/Los-linux.c", + "src/x86_64/Los-qnx.c", + "src/x86_64/Los-solaris.c", + "src/x86_64/Gos-freebsd.c", + "src/x86_64/Gos-linux.c", + "src/x86_64/Gos-qnx.c", + "src/x86_64/Gos-solaris.c", + ], + ) + [ + "src/elf64.h", + "src/x86_64/getcontext.S", + "src/x86_64/setcontext.S", + ], + "//conditions:default": [], +}) + +x86_64_textual_hdrs = select({ + "@platforms//cpu:x86_64": glob( + [ + "src/x86_64/G*.c", + ], + exclude = [ + "src/x86_64/Gos-freebsd.c", + "src/x86_64/Gos-linux.c", + "src/x86_64/Gos-qnx.c", + "src/x86_64/Gos-solaris.c", + ], + ) + [ + "src/elf64.c", + ], + "//conditions:default": [], +}) + +### OS specific source files ################################################### + +linux_srcs = select({ + "@platforms//os:linux": [ + "src/dl-iterate-phdr.c", + "src/mi/_ReadSLEB.c", + "src/mi/_ReadULEB.c", + "src/os-linux.c", + "src/os-linux.h", + ], + "//conditions:default": [], +}) + select({ + ":linux_arm64": [ + "src/aarch64/Los-linux.c", + "src/aarch64/Gos-linux.c", + ], + "//conditions:default": [], +}) + select({ + ":linux_arm": [ + "src/arm/Los-linux.c", + "src/arm/Gos-linux.c", + ], + "//conditions:default": [], +}) + select({ + ":linux_x86_64": [ + "src/x86_64/Los-linux.c", + "src/x86_64/Gos-linux.c", + ], + "//conditions:default": [], +}) + +linux_textual_hdrs = select({ + ":linux_arm64": ["src/aarch64/Gos-linux.c"], + "//conditions:default": [], +}) + select({ + ":linux_arm": ["src/arm/Gos-linux.c"], + "//conditions:default": [], +}) + select({ + ":linux_x86_64": ["src/x86_64/Gos-linux.c"], + "//conditions:default": [], +}) + +### libunwind ################################################################## + +libunwind_srcs = [ + "src/elfxx.h", + "src/elfxx.c", +] + dwarf_srcs + mi_srcs + unwind_srcs + arm64_srcs + arm_srcs + x86_64_srcs + linux_srcs + +libunwind_textual_hdrs = [ + "src/elfxx.c", +] + dwarf_textual_hdrs + mi_textual_hdrs + arm64_textual_hdrs + arm_textual_hdrs + x86_64_textual_hdrs + linux_textual_hdrs + +expand_template( + name = "libunwind_common_h", + out = "include/libunwind-common.h", + substitutions = { + "@PKG_MAJOR@": "1", + "@PKG_MINOR@": "8", + "@PKG_EXTRA@": "1", + }, + template = "include/libunwind-common.h.in", +) + +cc_library( + name = "unwind", + srcs = libunwind_srcs, + hdrs = glob(["include/tdep/*.h"]) + [ + "include/compiler.h", + "include/dwarf.h", + "include/dwarf-eh.h", + "include/dwarf_i.h", + "include/libunwind.h", + "include/libunwind-dynamic.h", + "include/libunwind_i.h", + "include/mempool.h", + "include/remote.h", + "include/unwind.h", + ":libunwind_common_h", + ] + select({ + "@platforms//cpu:arm64": glob(["include/tdep-aarch64/*.h"]) + ["include/libunwind-aarch64.h"], + "@platforms//cpu:arm": glob(["include/tdep-arm/*.h"]) + ["include/libunwind-arm.h"], + "@platforms//cpu:x86_64": ["include/libunwind-x86_64.h"] + glob(["include/tdep-x86_64/*.h"]), + }), + includes = [ + "include", + "src", + ] + select({ + "@platforms//cpu:arm64": [ + "include/tdep-aarch64", + ], + "@platforms//cpu:arm": [ + "include/tdep-arm", + ], + "@platforms//cpu:x86_64": [ + "include/tdep-x86_64", + ], + }), + local_defines = libunwind_defines + ["HAVE_DL_ITERATE_PHDR"], + textual_hdrs = libunwind_textual_hdrs, + deps = [ + "@xz//:lzma", + "@zlib", + ], +) + +cc_library( + name = "unwind_coredump", + srcs = coredump_srcs + ["src/mi/init.c"], + hdrs = ["include/libunwind-coredump.h"], + includes = ["include"], + local_defines = libunwind_defines + [ + "HAVE_STRUCT_ELF_PRSTATUS", + "HAVE_SYS_PROCFS_H", + ] + select({ + "@platforms//cpu:arm64": [ + "CONFIG_DEBUG_FRAME", + "SIZEOF_OFF_T=8", + ], + "@platforms//cpu:arm": [ + "CONFIG_DEBUG_FRAME", + "SIZEOF_OFF_T=4", + ], + "@platforms//cpu:x86_64": [ + "SIZEOF_OFF_T=8", + ], + }), + deps = [ + ":unwind", + "@xz//:lzma", + "@zlib", + ], +) + +cc_library( + name = "unwind_ptrace", + srcs = ptrace_srcs + ["src/mi/init.c"], + hdrs = ["include/libunwind-ptrace.h"], + includes = ["include"], + local_defines = libunwind_defines + ["HAVE_TTRACE"], + deps = [ + ":unwind", + "@xz//:lzma", + "@zlib", + ], +) + +cc_library( + name = "unwind_setjmp", + srcs = setjmp_srcs + ["src/mi/init.c"], + local_defines = libunwind_defines, + deps = [ + ":unwind", + "@xz//:lzma", + "@zlib", + ], +) + +alias( + name = "libunwind", + actual = ":unwind", + visibility = ["//visibility:public"], +) + +alias( + name = "libunwind_coredump", + actual = ":unwind_coredump", + visibility = ["//visibility:public"], +) + +alias( + name = "libunwind_ptrace", + actual = ":unwind_ptrace", + visibility = ["//visibility:public"], +) + +alias( + name = "libunwind_setjmp", + actual = ":unwind_setjmp", + visibility = ["//visibility:public"], +) + +### Tests ##################################################################### + +# TODO: the following tests currently do not work: +# crasher.c +# forker.c +# test-coredump-unwind.c +# test-init-remote.c +# test-proc-info.c +# test-ptrace-misc.c +# test-ptrace.c +# test-setjmp.c + +# Some tests require these test helper files. +cc_library( + name = "unwind_test_helpers", + testonly = True, + srcs = [ + "tests/flush-cache.S", + "tests/ident.c", + ], + hdrs = glob(["tests/*G*.c"]) + [ + "tests/Gtest-init.cxx", + "tests/flush-cache.h", + "tests/ident.h", + ], + defines = ["_GNU_SOURCE"], +) + +cc_test( + name = "frame_record_test", + srcs = ["tests/aarch64-test-frame-record.c"], + target_compatible_with = ["@platforms//cpu:arm64"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "plt_test", + srcs = ["tests/aarch64-test-plt.c"], + target_compatible_with = ["@platforms//cpu:arm64"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "arm64_sve_signal_test", + srcs = ["tests/Larm64-test-sve-signal.c"], + target_compatible_with = ["@platforms//cpu:arm64"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "perf_simple_test", + srcs = ["tests/Lperf-simple.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "perf_trace_test", + srcs = ["tests/Lperf-trace.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "rs_race_test", + srcs = ["tests/Lrs-race.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "bt_test", + srcs = ["tests/Ltest-bt.c"], + local_defines = ["UNW_LOCAL_ONLY"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "concurrent_test", + srcs = ["tests/Ltest-concurrent.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "cxx_exceptions_test", + srcs = ["tests/Ltest-cxx-exceptions.cxx"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +# TODO: fails on Debian 11 and Ubuntu 20.04. +# cc_test( +# name = "dyn1_test", +# srcs = ["tests/Ltest-dyn1.c"], +# deps = [ +# ":unwind", +# ":unwind_test_helpers", +# ], +# linkopts = ["-ldl"], +# ) + +cc_test( + name = "exc_test", + srcs = ["tests/Ltest-exc.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "init_local_signal_test", + srcs = [ + "tests/Ltest-init-local-signal.c", + "tests/Ltest-init-local-signal-lib.c", + ], + local_defines = ["UNW_LOCAL_ONLY"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "init_test", + srcs = ["tests/Ltest-init.cxx"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "mem_validate_test", + srcs = ["tests/Ltest-mem-validate.c"], + local_defines = ["UNW_LOCAL_ONLY"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "nocalloc_test", + srcs = ["tests/Ltest-nocalloc.c"], + linkopts = ["-ldl"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "nomalloc_test", + srcs = ["tests/Ltest-nomalloc.c"], + linkopts = ["-ldl"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "resume_sig_rt_test", + srcs = ["tests/Ltest-resume-sig-rt.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "resume_sig_test", + srcs = ["tests/Ltest-resume-sig.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "trace_test", + srcs = ["tests/Ltest-trace.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "varargs_test", + srcs = ["tests/Ltest-varargs.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "dwarf_expressions_test", + srcs = [ + "tests/Lx64-test-dwarf-expressions.c", + "tests/x64-test-dwarf-expressions.S", + ], + target_compatible_with = ["@platforms//cpu:x86_64"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "async_sig_test", + srcs = ["tests/test-async-sig.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "flush_cache_test", + srcs = ["tests/test-flush-cache.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "mem_test", + srcs = ["tests/test-mem.c"], + local_defines = ["UNW_LOCAL_ONLY"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "reg_state_test", + srcs = ["tests/test-reg-state.c"], + local_defines = ["UNW_LOCAL_ONLY"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "static_link_loc_test", + srcs = [ + "tests/test-static-link-gen.c", + "tests/test-static-link-loc.c", + ], + local_defines = ["UNW_LOCAL_ONLY"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "strerror_test", + srcs = ["tests/test-strerror.c"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) + +cc_test( + name = "unwind_badjmp_signal_frame_test", + srcs = ["tests/x64-unwind-badjmp-signal-frame.c"], + target_compatible_with = ["@platforms//cpu:x86_64"], + deps = [ + ":unwind", + ":unwind_test_helpers", + ], +) diff --git a/registry/modules/libunwind/1.8.3.brpc-no-unwind/overlay/MODULE.bazel b/registry/modules/libunwind/1.8.3.brpc-no-unwind/overlay/MODULE.bazel new file mode 100644 index 0000000..948094e --- /dev/null +++ b/registry/modules/libunwind/1.8.3.brpc-no-unwind/overlay/MODULE.bazel @@ -0,0 +1,21 @@ +# Forked from the Bazel Central Registry: +# https://github.com/bazelbuild/bazel-central-registry/blob/main/modules/libunwind/1.8.3/overlay/MODULE.bazel +# Distributed under the Apache License, Version 2.0. See the `registry/modules/ +# libunwind/**` entry near the bottom of brpc's LICENSE file. +# +# brpc modification (Apache License 2.0 §4(b)): +# - `version` suffixed with `.brpc-no-unwind` to distinguish this brpc +# variant from the upstream BCR version. + +module( + name = "libunwind", + version = "1.8.3.brpc-no-unwind", + bazel_compatibility = [">=7.2.1"], # need support for "overlay" directory + compatibility_level = 0, +) + +bazel_dep(name = "bazel_skylib", version = "1.7.1") +bazel_dep(name = "platforms", version = "0.0.11") +bazel_dep(name = "rules_cc", version = "0.1.1") +bazel_dep(name = "xz", version = "5.4.5.bcr.8") +bazel_dep(name = "zlib", version = "1.3.1.bcr.8") diff --git a/registry/modules/libunwind/1.8.3.brpc-no-unwind/presubmit.yml b/registry/modules/libunwind/1.8.3.brpc-no-unwind/presubmit.yml new file mode 100644 index 0000000..3a05441 --- /dev/null +++ b/registry/modules/libunwind/1.8.3.brpc-no-unwind/presubmit.yml @@ -0,0 +1,18 @@ +matrix: + platform: + - debian11 + - ubuntu2004 + - ubuntu2204 + - ubuntu2404 + bazel: [7.x, 8.x, rolling] +tasks: + verify_targets: + platform: ${{ platform }} + bazel: ${{ bazel }} + build_targets: + - "@libunwind//:libunwind" + - "@libunwind//:libunwind_coredump" + - "@libunwind//:libunwind_ptrace" + - "@libunwind//:libunwind_setjmp" + test_targets: + - "@libunwind//..." diff --git a/registry/modules/libunwind/1.8.3.brpc-no-unwind/source.json b/registry/modules/libunwind/1.8.3.brpc-no-unwind/source.json new file mode 100644 index 0000000..aa6ba90 --- /dev/null +++ b/registry/modules/libunwind/1.8.3.brpc-no-unwind/source.json @@ -0,0 +1,9 @@ +{ + "url": "https://github.com/libunwind/libunwind/releases/download/v1.8.3/libunwind-1.8.3.tar.gz", + "strip_prefix": "libunwind-1.8.3", + "overlay": { + "BUILD.bazel": "sha256-ozAABv0I3+tacve4z2m+lAh/lhJWh7t7L3unh8FSfp8=", + "MODULE.bazel": "sha256-qimDkl8soxNM/JuqndzGOOcn5ZwGvo2J6CSsNS2/IQs=" + }, + "integrity": "sha256-vjDZEOZ/WNgudTIx8TV/MmoaCIrPEmsh/3fmCqsZuQs=" +} diff --git a/registry/modules/libunwind/metadata.json b/registry/modules/libunwind/metadata.json new file mode 100644 index 0000000..a25cccf --- /dev/null +++ b/registry/modules/libunwind/metadata.json @@ -0,0 +1,19 @@ +{ + "homepage": "https://www.nongnu.org/libunwind/", + "maintainers": [ + { + "email": "vtsao@openai.com", + "github": "vtsao-openai", + "github_user_id": 176426301, + "name": "Vincent Tsao" + } + ], + "repository": [ + "github:libunwind/libunwind" + ], + "versions": [ + "1.8.1.brpc-no-unwind", + "1.8.3.brpc-no-unwind" + ], + "yanked_versions": {} +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..a9a4d11 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,106 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +add_library(BUTIL_LIB OBJECT ${BUTIL_SOURCES}) +add_library(SOURCES_LIB OBJECT ${SOURCES}) +add_dependencies(SOURCES_LIB PROTO_LIB) +target_link_libraries(BUTIL_LIB PRIVATE brpc_common_config) +target_link_libraries(SOURCES_LIB PRIVATE brpc_common_config) + +# shared library needs POSITION_INDEPENDENT_CODE +set_property(TARGET ${SOURCES_LIB} PROPERTY POSITION_INDEPENDENT_CODE 1) +set_property(TARGET ${BUTIL_LIB} PROPERTY POSITION_INDEPENDENT_CODE 1) + +add_library(brpc-static STATIC $ + $ + $) +target_link_libraries(brpc-static PUBLIC brpc_common_config) + +function(check_thrift_version target_arg) + #use thrift command to get version + execute_process( + COMMAND thrift --version + OUTPUT_VARIABLE THRIFT_VERSION_OUTPUT + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" THRIFT_VERSION ${THRIFT_VERSION_OUTPUT}) + string(REGEX REPLACE "\\." ";" THRIFT_VERSION_LIST ${THRIFT_VERSION}) + + list(GET THRIFT_VERSION_LIST 0 THRIFT_MAJOR_VERSION) + list(GET THRIFT_VERSION_LIST 1 THRIFT_MINOR_VERSION) + + if (THRIFT_MAJOR_VERSION EQUAL 0 AND THRIFT_MINOR_VERSION LESS 11) + message(STATUS "Thrift version is less than 0.11.0") + target_compile_definitions(${target_arg} PRIVATE _THRIFT_VERSION_LOWER_THAN_0_11_0_) + else() + message(STATUS "Thrift version is equal to or greater than 0.11.0") + endif() +endfunction() + + +if(WITH_THRIFT) + target_link_libraries(brpc-static PRIVATE ${THRIFT_LIB}) + check_thrift_version(brpc-static) +endif() + +SET_TARGET_PROPERTIES(brpc-static PROPERTIES OUTPUT_NAME brpc CLEAN_DIRECT_OUTPUT 1) + +# for protoc-gen-mcpack +set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/output/bin) + +set(protoc_gen_mcpack_SOURCES + ${PROJECT_SOURCE_DIR}/src/mcpack2pb/generator.cpp + ) + +add_executable(protoc-gen-mcpack ${protoc_gen_mcpack_SOURCES}) +target_link_libraries(protoc-gen-mcpack PRIVATE brpc_common_config) + +if(BUILD_SHARED_LIBS) + add_library(brpc-shared SHARED $ + $ + $) + target_link_libraries(brpc-shared PUBLIC brpc_common_config) + target_link_libraries(brpc-shared PRIVATE ${DYNAMIC_LIB}) + if(WITH_GLOG) + target_link_libraries(brpc-shared PRIVATE ${GLOG_LIB}) + endif() + if(WITH_THRIFT) + target_link_libraries(brpc-shared PRIVATE ${THRIFT_LIB}) + check_thrift_version(brpc-shared) + endif() + SET_TARGET_PROPERTIES(brpc-shared PROPERTIES OUTPUT_NAME brpc CLEAN_DIRECT_OUTPUT 1) + + target_link_libraries(protoc-gen-mcpack PRIVATE brpc-shared ${DYNAMIC_LIB} pthread) + + install(TARGETS brpc-shared + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) +else() + target_link_libraries(protoc-gen-mcpack PRIVATE brpc-static ${DYNAMIC_LIB} pthread) +endif() + + + +install(TARGETS brpc-static + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) diff --git a/src/brpc/acceptor.cpp b/src/brpc/acceptor.cpp new file mode 100644 index 0000000..f9c22a6 --- /dev/null +++ b/src/brpc/acceptor.cpp @@ -0,0 +1,357 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include "butil/fd_guard.h" // fd_guard +#include "butil/fd_utility.h" // make_close_on_exec +#include "butil/time.h" // gettimeofday_us +#include "brpc/acceptor.h" +#include "brpc/transport_factory.h" + + +namespace brpc { + +static const int INITIAL_CONNECTION_CAP = 65536; + +Acceptor::Acceptor(bthread_keytable_pool_t* pool) + : InputMessenger() + , _keytable_pool(pool) + , _status(UNINITIALIZED) + , _idle_timeout_sec(-1) + , _close_idle_tid(INVALID_BTHREAD) + , _listened_fd(-1) + , _acception_id(0) + , _empty_cond(&_map_mutex) + , _force_ssl(false) + , _ssl_ctx(NULL) + , _socket_mode(SOCKET_MODE_TCP) + , _bthread_tag(BTHREAD_TAG_DEFAULT) { +} + +Acceptor::~Acceptor() { + StopAccept(0); + Join(); +} + +int Acceptor::StartAccept(int listened_fd, int idle_timeout_sec, + const std::shared_ptr& ssl_ctx, + bool force_ssl) { + if (listened_fd < 0) { + LOG(FATAL) << "Invalid listened_fd=" << listened_fd; + return -1; + } + + if (!ssl_ctx && force_ssl) { + LOG(ERROR) << "Fail to force SSL for all connections " + " because ssl_ctx is NULL"; + return -1; + } + + BAIDU_SCOPED_LOCK(_map_mutex); + if (_status == UNINITIALIZED) { + if (Initialize() != 0) { + LOG(FATAL) << "Fail to initialize Acceptor"; + return -1; + } + _status = READY; + } + if (_status != READY) { + LOG(FATAL) << "Acceptor hasn't stopped yet: status=" << status(); + return -1; + } + if (idle_timeout_sec > 0) { + bthread_attr_t tmp = BTHREAD_ATTR_NORMAL; + tmp.tag = _bthread_tag; + bthread_attr_set_name(&tmp, "CloseIdleConnections"); + if (bthread_start_background(&_close_idle_tid, &tmp, CloseIdleConnections, this) != 0) { + LOG(FATAL) << "Fail to start bthread"; + return -1; + } + } + _idle_timeout_sec = idle_timeout_sec; + _force_ssl = force_ssl; + _ssl_ctx = ssl_ctx; + + // Creation of _acception_id is inside lock so that OnNewConnections + // (which may run immediately) should see sane fields set below. + SocketOptions options; + options.fd = listened_fd; + options.user = this; + options.bthread_tag = _bthread_tag; + options.on_edge_triggered_events = OnNewConnections; + if (Socket::Create(options, &_acception_id) != 0) { + // Close-idle-socket thread will be stopped inside destructor + LOG(FATAL) << "Fail to create _acception_id"; + return -1; + } + + _listened_fd = listened_fd; + _status = RUNNING; + return 0; +} + +void* Acceptor::CloseIdleConnections(void* arg) { + Acceptor* am = static_cast(arg); + std::vector checking_fds; + const uint64_t CHECK_INTERVAL_US = 1000000UL; + while (bthread_usleep(CHECK_INTERVAL_US) == 0) { + // TODO: this is not efficient for a lot of connections(>100K) + am->ListConnections(&checking_fds); + for (size_t i = 0; i < checking_fds.size(); ++i) { + SocketUniquePtr s; + if (Socket::Address(checking_fds[i], &s) == 0) { + s->ReleaseReferenceIfIdle(am->_idle_timeout_sec); + } + } + } + return NULL; +} + +void Acceptor::StopAccept(int /*closewait_ms*/) { + // Currently `closewait_ms' is useless since we have to wait until + // existing requests are finished. Otherwise, contexts depended by + // the requests may be deleted and invalid. + + { + BAIDU_SCOPED_LOCK(_map_mutex); + if (_status != RUNNING) { + return; + } + _status = STOPPING; + } + + // Don't set _acception_id to 0 because BeforeRecycle needs it. + Socket::SetFailed(_acception_id); + + // SetFailed all existing connections. Connections added after this piece + // of code will be SetFailed directly in OnNewConnectionsUntilEAGAIN + std::vector erasing_ids; + ListConnections(&erasing_ids); + + for (size_t i = 0; i < erasing_ids.size(); ++i) { + SocketUniquePtr socket; + if (Socket::Address(erasing_ids[i], &socket) == 0) { + if (socket->shall_fail_me_at_server_stop()) { + // Mainly streaming connections, should be SetFailed() to + // trigger callbacks to NotifyOnFailed() to remove references, + // otherwise the sockets are often referenced by corresponding + // objects and delay server's stopping which requires all + // existing sockets to be recycled. + socket->SetFailed(ELOGOFF, "Server is stopping"); + } else { + // Message-oriented RPC connections. Just release the addtional + // reference in the socket, which will be recycled when current + // requests have been processed. + socket->ReleaseAdditionalReference(); + } + } // else: This socket already called `SetFailed' before + } +} + +int Acceptor::Initialize() { + if (_socket_map.init(INITIAL_CONNECTION_CAP) != 0) { + LOG(WARNING) << "Fail to initialize FlatMap, size=" + << INITIAL_CONNECTION_CAP; + } + return 0; +} + +// NOTE: Join() can happen before StopAccept() +void Acceptor::Join() { + std::unique_lock mu(_map_mutex); + if (_status != STOPPING && _status != RUNNING) { // no need to join. + return; + } + // `_listened_fd' will be set to -1 once it has been recycled + while (_listened_fd > 0 || !_socket_map.empty()) { + _empty_cond.Wait(); + } + const int saved_idle_timeout_sec = _idle_timeout_sec; + _idle_timeout_sec = 0; + const bthread_t saved_close_idle_tid = _close_idle_tid; + mu.unlock(); + + // Join the bthread outside lock. + if (saved_idle_timeout_sec > 0) { + bthread_stop(saved_close_idle_tid); + bthread_join(saved_close_idle_tid, NULL); + } + + { + BAIDU_SCOPED_LOCK(_map_mutex); + _status = READY; + } +} + +size_t Acceptor::ConnectionCount() const { + // Notice that _socket_map may be modified concurrently. This actually + // assumes that size() is safe to call concurrently. + return _socket_map.size(); +} + +void Acceptor::ListConnections(std::vector* conn_list, + size_t max_copied) { + if (conn_list == NULL) { + LOG(FATAL) << "Param[conn_list] is NULL"; + return; + } + conn_list->clear(); + // Add additional 10(randomly small number) so that even if + // ConnectionCount is inaccurate, enough space is reserved + conn_list->reserve(ConnectionCount() + 10); + + std::unique_lock mu(_map_mutex); + // Copy all the SocketId (protected by mutex) into a temporary + // container to avoid dealing with sockets inside the mutex. + size_t ntotal = 0; + size_t n = 0; + for (SocketMap::const_iterator it = _socket_map.begin(); + it != _socket_map.end(); ++it, ++ntotal) { + if (ntotal >= max_copied) { + return; + } + if (++n >= 256/*max iterated one pass*/) { + SocketMap::PositionHint hint; + _socket_map.save_iterator(it, &hint); + n = 0; + mu.unlock(); // yield + mu.lock(); + it = _socket_map.restore_iterator(hint); + if (it == _socket_map.begin()) { // resized + conn_list->clear(); + } + if (it == _socket_map.end()) { + break; + } + } + conn_list->push_back(it->first); + } +} + +void Acceptor::ListConnections(std::vector* conn_list) { + return ListConnections(conn_list, std::numeric_limits::max()); +} + +void Acceptor::OnNewConnectionsUntilEAGAIN(Socket* acception) { + while (1) { + struct sockaddr_storage in_addr; + bzero(&in_addr, sizeof(in_addr)); + socklen_t in_len = sizeof(in_addr); + butil::fd_guard in_fd(accept(acception->fd(), (sockaddr*)&in_addr, &in_len)); + if (in_fd < 0) { + // no EINTR because listened fd is non-blocking. + if (errno == EAGAIN) { + return; + } + // Do NOT return -1 when `accept' failed, otherwise `_listened_fd' + // will be closed. Continue to consume all the events until EAGAIN + // instead. + // If the accept was failed, the error may repeat constantly, + // limit frequency of logging. + PLOG_EVERY_SECOND(ERROR) + << "Fail to accept from listened_fd=" << acception->fd(); + continue; + } + + Acceptor* am = dynamic_cast(acception->user()); + if (NULL == am) { + LOG(FATAL) << "Impossible! acception->user() MUST be Acceptor"; + acception->SetFailed(EINVAL, "Impossible! acception->user() MUST be Acceptor"); + return; + } + + SocketId socket_id; + SocketOptions options; + options.keytable_pool = am->_keytable_pool; + options.fd = in_fd; + butil::sockaddr2endpoint(&in_addr, in_len, &options.remote_side); + options.user = acception->user(); + options.need_on_edge_trigger = true; + options.force_ssl = am->_force_ssl; + options.initial_ssl_ctx = am->_ssl_ctx; + options.socket_mode = am->_socket_mode; + options.bthread_tag = am->_bthread_tag; + if (Socket::Create(options, &socket_id) != 0) { + LOG(ERROR) << "Fail to create Socket"; + continue; + } + in_fd.release(); // transfer ownership to socket_id + + // There's a funny race condition here. After Socket::Create, messages + // from the socket are already handled and a RPC is possibly done + // before the socket is added into _socket_map below. This is found in + // ChannelTest.skip_parallel in test/brpc_channel_unittest.cpp (running + // on machines with few cores) where the _messenger.ConnectionCount() + // may surprisingly be 0 even if the RPC is already done. + + SocketUniquePtr sock; + if (Socket::AddressFailedAsWell(socket_id, &sock) >= 0) { + bool is_running = true; + { + BAIDU_SCOPED_LOCK(am->_map_mutex); + is_running = (am->status() == RUNNING); + // Always add this socket into `_socket_map' whether it + // has been `SetFailed' or not, whether `Acceptor' is + // running or not. Otherwise, `Acceptor::BeforeRecycle' + // may be called (inside Socket::BeforeRecycled) after `Acceptor' + // has been destroyed + am->_socket_map.insert(socket_id, ConnectStatistics()); + } + if (!is_running) { + LOG(WARNING) << "Acceptor on fd=" << acception->fd() + << " has been stopped, discard newly created " << *sock; + sock->SetFailed(ELOGOFF, "Acceptor on fd=%d has been stopped, " + "discard newly created %s", acception->fd(), + sock->description().c_str()); + return; + } + } // else: The socket has already been destroyed, Don't add its id + // into _socket_map + } +} + +void Acceptor::OnNewConnections(Socket* acception) { + int progress = Socket::PROGRESS_INIT; + do { + OnNewConnectionsUntilEAGAIN(acception); + if (acception->Failed()) { + return; + } + } while (acception->MoreReadEvents(&progress)); +} + +void Acceptor::BeforeRecycle(Socket* sock) { + BAIDU_SCOPED_LOCK(_map_mutex); + if (sock->id() == _acception_id) { + // Set _listened_fd to -1 when acception socket has been recycled + // so that we are ensured no more events will arrive (and `Join' + // will return to its caller) + _listened_fd = -1; + _empty_cond.Broadcast(); + return; + } + // If a Socket could not be addressed shortly after its creation, it + // was not added into `_socket_map'. + _socket_map.erase(sock->id()); + if (_socket_map.empty()) { + _empty_cond.Broadcast(); + } +} + +} // namespace brpc diff --git a/src/brpc/acceptor.h b/src/brpc/acceptor.h new file mode 100644 index 0000000..77942be --- /dev/null +++ b/src/brpc/acceptor.h @@ -0,0 +1,124 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_ACCEPTOR_H +#define BRPC_ACCEPTOR_H + +#include "bthread/bthread.h" // bthread_t +#include "butil/synchronization/condition_variable.h" +#include "butil/containers/flat_map.h" +#include "brpc/input_messenger.h" +#include "brpc/socket_mode.h" + + +namespace brpc { + +struct ConnectStatistics { +}; + +// Accept connections from a specific port and then +// process messages from which it reads +class Acceptor : public InputMessenger { +friend class Server; +public: + typedef butil::FlatMap SocketMap; + + enum Status { + UNINITIALIZED = 0, + READY = 1, + RUNNING = 2, + STOPPING = 3, + }; + +public: + explicit Acceptor(bthread_keytable_pool_t* pool = NULL); + ~Acceptor(); + + // [thread-safe] Accept connections from `listened_fd'. Ownership of + // `listened_fd' is also transferred to `Acceptor'. Can be called + // multiple times if the last `StartAccept' has been completely stopped + // by calling `StopAccept' and `Join'. Connections that has no data + // transmission for `idle_timeout_sec' will be closed automatically iff + // `idle_timeout_sec' > 0 + // Return 0 on success, -1 otherwise. + int StartAccept(int listened_fd, int idle_timeout_sec, + const std::shared_ptr& ssl_ctx, + bool force_ssl); + + // [thread-safe] Stop accepting connections. + // `closewait_ms' is not used anymore. + void StopAccept(int /*closewait_ms*/); + + // Wait until all existing Sockets(defined in socket.h) are recycled. + void Join(); + + // The parameter to StartAccept. Negative when acceptor is stopped. + int listened_fd() const { return _listened_fd; } + + // Get number of existing connections. + size_t ConnectionCount() const; + + // Clear `conn_list' and append all connections into it. + void ListConnections(std::vector* conn_list); + + // Clear `conn_list' and append all most `max_copied' connections into it. + void ListConnections(std::vector* conn_list, size_t max_copied); + + Status status() const { return _status; } + +private: + // Accept connections. + static void OnNewConnectionsUntilEAGAIN(Socket* m); + static void OnNewConnections(Socket* m); + + static void* CloseIdleConnections(void* arg); + + // Initialize internal structure. + int Initialize(); + + // Remove the accepted socket `sock' from inside + void BeforeRecycle(Socket* sock) override; + + bthread_keytable_pool_t* _keytable_pool; // owned by Server + Status _status; + int _idle_timeout_sec; + bthread_t _close_idle_tid; + + int _listened_fd; + // The Socket tso accept connections. + SocketId _acception_id; + + butil::Mutex _map_mutex; + butil::ConditionVariable _empty_cond; + + // The map containing all the accepted sockets + SocketMap _socket_map; + + bool _force_ssl; + std::shared_ptr _ssl_ctx; + + // Choose to use a certain socket: 0 TCP, 1 RDMA + SocketMode _socket_mode; + + // Acceptor belongs to this tag + bthread_tag_t _bthread_tag; +}; + +} // namespace brpc + + +#endif // BRPC_ACCEPTOR_H diff --git a/src/brpc/adaptive_connection_type.cpp b/src/brpc/adaptive_connection_type.cpp new file mode 100644 index 0000000..539a177 --- /dev/null +++ b/src/brpc/adaptive_connection_type.cpp @@ -0,0 +1,72 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "butil/logging.h" +#include "brpc/adaptive_connection_type.h" + + +namespace brpc { + +inline bool CompareStringPieceWithoutCase( + const butil::StringPiece& s1, const char* s2) { + if (strlen(s2) != s1.size()) { + return false; + } + return strncasecmp(s1.data(), s2, s1.size()) == 0; +} + +ConnectionType StringToConnectionType(const butil::StringPiece& type, + bool print_log_on_unknown) { + if (CompareStringPieceWithoutCase(type, "single")) { + return CONNECTION_TYPE_SINGLE; + } else if (CompareStringPieceWithoutCase(type, "pooled")) { + return CONNECTION_TYPE_POOLED; + } else if (CompareStringPieceWithoutCase(type, "short")) { + return CONNECTION_TYPE_SHORT; + } + LOG_IF(ERROR, print_log_on_unknown && !type.empty()) + << "Unknown connection_type `" << type + << "', supported types: single pooled short"; + return CONNECTION_TYPE_UNKNOWN; +} + +const char* ConnectionTypeToString(ConnectionType type) { + switch (type) { + case CONNECTION_TYPE_UNKNOWN: + return "unknown"; + case CONNECTION_TYPE_SINGLE: + return "single"; + case CONNECTION_TYPE_POOLED: + return "pooled"; + case CONNECTION_TYPE_SHORT: + return "short"; + } + return "unknown"; +} + +void AdaptiveConnectionType::operator=(const butil::StringPiece& name) { + if (name.empty()) { + _type = CONNECTION_TYPE_UNKNOWN; + _error = false; + } else { + _type = StringToConnectionType(name); + _error = (_type == CONNECTION_TYPE_UNKNOWN); + } +} + +} // namespace brpc diff --git a/src/brpc/adaptive_connection_type.h b/src/brpc/adaptive_connection_type.h new file mode 100644 index 0000000..36a4954 --- /dev/null +++ b/src/brpc/adaptive_connection_type.h @@ -0,0 +1,70 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_ADAPTIVE_CONNECTION_TYPE_H +#define BRPC_ADAPTIVE_CONNECTION_TYPE_H + +// To brpc developers: This is a header included by user, don't depend +// on internal structures, use opaque pointers instead. + +#include "butil/strings/string_piece.h" +#include "brpc/options.pb.h" + +namespace brpc { + +// Convert a case-insensitive string to corresponding ConnectionType +// Possible options are: short, pooled, single +// Returns: CONNECTION_TYPE_UNKNOWN on error. +ConnectionType StringToConnectionType(const butil::StringPiece& type, + bool print_log_on_unknown); +inline ConnectionType StringToConnectionType(const butil::StringPiece& type) +{ return StringToConnectionType(type, true); } + +// Convert a ConnectionType to a c-style string. +const char* ConnectionTypeToString(ConnectionType); + +// Assignable by both ConnectionType and names. +class AdaptiveConnectionType { +public: + AdaptiveConnectionType() : _type(CONNECTION_TYPE_UNKNOWN), _error(false) {} + AdaptiveConnectionType(ConnectionType type) : _type(type), _error(false) {} + ~AdaptiveConnectionType() {} + + void operator=(ConnectionType type) { + _type = type; + _error = false; + } + void operator=(const butil::StringPiece& name); + + operator ConnectionType() const { return _type; } + const char* name() const { return ConnectionTypeToString(_type); } + bool has_error() const { return _error; } + +private: + ConnectionType _type; + // Since this structure occupies 8 bytes in 64-bit machines anyway, + // we add a field to mark if last operator=(name) failed so that + // channel can print a error log before re-selecting a valid + // ConnectionType for user. + bool _error; +}; + +} // namespace brpc + + +#endif // BRPC_ADAPTIVE_CONNECTION_TYPE_H diff --git a/src/brpc/adaptive_max_concurrency.cpp b/src/brpc/adaptive_max_concurrency.cpp new file mode 100644 index 0000000..6a5977b --- /dev/null +++ b/src/brpc/adaptive_max_concurrency.cpp @@ -0,0 +1,119 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include "butil/string_printf.h" +#include "butil/logging.h" +#include "butil/strings/string_number_conversions.h" +#include "brpc/adaptive_max_concurrency.h" +#include "brpc/concurrency_limiter.h" + +namespace brpc { + +AdaptiveMaxConcurrency::AdaptiveMaxConcurrency() + : _value(UNLIMITED()) + , _max_concurrency(0) { +} + +AdaptiveMaxConcurrency::AdaptiveMaxConcurrency(int max_concurrency) + : _max_concurrency(0) { + if (max_concurrency <= 0) { + _value = UNLIMITED(); + _max_concurrency = 0; + } else { + _value = butil::string_printf("%d", max_concurrency); + _max_concurrency = max_concurrency; + } +} + +AdaptiveMaxConcurrency::AdaptiveMaxConcurrency( + const TimeoutConcurrencyConf& value) + : _value("timeout"), _max_concurrency(-1), _timeout_conf(value) {} + +inline bool CompareStringPieceWithoutCase( + const butil::StringPiece& s1, const char* s2) { + DCHECK(s2 != NULL); + if (std::strlen(s2) != s1.size()) { + return false; + } + return ::strncasecmp(s1.data(), s2, s1.size()) == 0; +} + +AdaptiveMaxConcurrency::AdaptiveMaxConcurrency(const butil::StringPiece& value) + : _max_concurrency(0) { + int max_concurrency = 0; + if (butil::StringToInt(value, &max_concurrency)) { + operator=(max_concurrency); + } else { + value.CopyToString(&_value); + _max_concurrency = -1; + } +} + +void AdaptiveMaxConcurrency::operator=(const butil::StringPiece& value) { + int max_concurrency = 0; + if (butil::StringToInt(value, &max_concurrency)) { + return operator=(max_concurrency); + } else { + value.CopyToString(&_value); + _max_concurrency = -1; + } + if (_cl) { + _cl->ResetMaxConcurrency(*this); + } +} + +void AdaptiveMaxConcurrency::operator=(int max_concurrency) { + if (max_concurrency <= 0) { + _value = UNLIMITED(); + _max_concurrency = 0; + } else { + _value = butil::string_printf("%d", max_concurrency); + _max_concurrency = max_concurrency; + } + if (_cl) { + _cl->ResetMaxConcurrency(*this); + } +} + +void AdaptiveMaxConcurrency::operator=(const TimeoutConcurrencyConf& value) { + _value = "timeout"; + _max_concurrency = -1; + _timeout_conf = value; + if (_cl) { + _cl->ResetMaxConcurrency(*this); + } +} + +const std::string& AdaptiveMaxConcurrency::type() const { + if (_max_concurrency > 0) { + return CONSTANT(); + } else if (_max_concurrency == 0) { + return UNLIMITED(); + } else { + return _value; + } +} + +bool operator==(const AdaptiveMaxConcurrency& adaptive_concurrency, + const butil::StringPiece& concurrency) { + return CompareStringPieceWithoutCase(concurrency, + adaptive_concurrency.value().c_str()); +} + +} // namespace brpc diff --git a/src/brpc/adaptive_max_concurrency.h b/src/brpc/adaptive_max_concurrency.h new file mode 100644 index 0000000..2bdcd90 --- /dev/null +++ b/src/brpc/adaptive_max_concurrency.h @@ -0,0 +1,115 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_ADAPTIVE_MAX_CONCURRENCY_H +#define BRPC_ADAPTIVE_MAX_CONCURRENCY_H + +// To brpc developers: This is a header included by user, don't depend +// on internal structures, use opaque pointers instead. + +#include "butil/strings/string_piece.h" +#include "brpc/options.pb.h" + +namespace brpc { + +// timeout concurrency limiter config +struct TimeoutConcurrencyConf { + int64_t timeout_ms; + int max_concurrency; +}; + +class ConcurrencyLimiter; +class AdaptiveMaxConcurrency{ +public: + explicit AdaptiveMaxConcurrency(); + explicit AdaptiveMaxConcurrency(int max_concurrency); + explicit AdaptiveMaxConcurrency(const butil::StringPiece& value); + explicit AdaptiveMaxConcurrency(const TimeoutConcurrencyConf& value); + + // Non-trivial destructor to prevent AdaptiveMaxConcurrency from being + // passed to variadic arguments without explicit type conversion. + // eg: + // printf("%d", options.max_concurrency) // compile error + // printf("%s", options.max_concurrency.value().c_str()) // ok + ~AdaptiveMaxConcurrency() {} + + void operator=(int max_concurrency); + void operator=(const butil::StringPiece& value); + void operator=(const TimeoutConcurrencyConf& value); + + // 0 for type="unlimited" + // >0 for type="constant" + // <0 for type="user-defined" + operator int() const { return _max_concurrency; } + operator TimeoutConcurrencyConf() const { return _timeout_conf; } + + // "unlimited" for type="unlimited" + // "10" "20" "30" for type="constant" + // "user-defined" for type="user-defined" + const std::string& value() const { return _value; } + + // "unlimited", "constant" or "user-defined" + const std::string& type() const; + + // Use Meyers' Singleton to avoid static initialization order fiasco: + // global AdaptiveMaxConcurrency objects in other translation units may + // depend on these strings during their construction. + static const std::string& UNLIMITED() { + static const std::string s_unlimited = "unlimited"; + return s_unlimited; + } + static const std::string& CONSTANT() { + static const std::string s_constant = "constant"; + return s_constant; + } + + void SetConcurrencyLimiter(ConcurrencyLimiter* cl) { _cl = cl; } + +private: + std::string _value; + int _max_concurrency; + TimeoutConcurrencyConf + _timeout_conf; // TODO std::varient for different type + ConcurrencyLimiter* _cl{nullptr}; +}; + +inline std::ostream& operator<<(std::ostream& os, const AdaptiveMaxConcurrency& amc) { + return os << amc.value(); +} + +bool operator==(const AdaptiveMaxConcurrency& adaptive_concurrency, + const butil::StringPiece& concurrency); + +inline bool operator==(const butil::StringPiece& concurrency, + const AdaptiveMaxConcurrency& adaptive_concurrency) { + return adaptive_concurrency == concurrency; +} + +inline bool operator!=(const AdaptiveMaxConcurrency& adaptive_concurrency, + const butil::StringPiece& concurrency) { + return !(adaptive_concurrency == concurrency); +} + +inline bool operator!=(const butil::StringPiece& concurrency, + const AdaptiveMaxConcurrency& adaptive_concurrency) { + return !(adaptive_concurrency == concurrency); +} + +} // namespace brpc + + +#endif // BRPC_ADAPTIVE_MAX_CONCURRENCY_H diff --git a/src/brpc/adaptive_protocol_type.h b/src/brpc/adaptive_protocol_type.h new file mode 100644 index 0000000..36674a7 --- /dev/null +++ b/src/brpc/adaptive_protocol_type.h @@ -0,0 +1,93 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_ADAPTIVE_PROTOCOL_TYPE_H +#define BRPC_ADAPTIVE_PROTOCOL_TYPE_H + +// To brpc developers: This is a header included by user, don't depend +// on internal structures, use opaque pointers instead. + +#include "butil/strings/string_piece.h" +#include "brpc/options.pb.h" + +namespace brpc { + +// NOTE: impl. are in brpc/protocol.cpp + +// Convert a case-insensitive string to corresponding ProtocolType which is +// defined in src/brpc/options.proto +// Returns: PROTOCOL_UNKNOWN on error. +ProtocolType StringToProtocolType(const butil::StringPiece& type, + bool print_log_on_unknown); +inline ProtocolType StringToProtocolType(const butil::StringPiece& type) +{ return StringToProtocolType(type, true); } + +// Convert a ProtocolType to a c-style string. +const char* ProtocolTypeToString(ProtocolType); + +// Assignable by both ProtocolType and names. +class AdaptiveProtocolType { +public: + explicit AdaptiveProtocolType() : _type(PROTOCOL_UNKNOWN) {} + explicit AdaptiveProtocolType(ProtocolType type) : _type(type) {} + explicit AdaptiveProtocolType(butil::StringPiece name) { *this = name; } + ~AdaptiveProtocolType() {} + + void operator=(ProtocolType type) { + _type = type; + _name.clear(); + _param.clear(); + } + + void operator=(butil::StringPiece name) { + butil::StringPiece param; + const size_t pos = name.find(':'); + if (pos != butil::StringPiece::npos) { + param = name.substr(pos + 1); + name.remove_suffix(name.size() - pos); + } + _type = StringToProtocolType(name); + if (_type == PROTOCOL_UNKNOWN) { + _name.assign(name.data(), name.size()); + } else { + _name.clear(); + } + if (!param.empty()) { + _param.assign(param.data(), param.size()); + } else { + _param.clear(); + } + }; + + operator ProtocolType() const { return _type; } + + const char* name() const { + return _name.empty() ? ProtocolTypeToString(_type) : _name.c_str(); + } + + bool has_param() const { return !_param.empty(); } + const std::string& param() const { return _param; } + +private: + ProtocolType _type; + std::string _name; + std::string _param; +}; + +} // namespace brpc + +#endif // BRPC_ADAPTIVE_PROTOCOL_TYPE_H diff --git a/src/brpc/amf.cpp b/src/brpc/amf.cpp new file mode 100644 index 0000000..219199c --- /dev/null +++ b/src/brpc/amf.cpp @@ -0,0 +1,1296 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/sys_byteorder.h" +#include "butil/logging.h" +#include "butil/find_cstr.h" +#include "brpc/log.h" +#include "brpc/amf.h" +#include "gflags/gflags.h" + +namespace brpc { + +DEFINE_int32(amf_max_depth, 128, "Maximum nesting depth for AMF objects and arrays"); +DEFINE_int32(amf_max_string_size, 64 * 1024 * 1024, + "Maximum byte size for AMF strings"); +DEFINE_int32(amf_max_array_size, 1024 * 1024, + "Maximum element count for AMF arrays"); + +static bool CheckAMFDepth(int depth) { + if (depth > FLAGS_amf_max_depth) { + LOG(ERROR) << "AMF exceeds max depth! max=" + << FLAGS_amf_max_depth << ", actually=" << depth; + return false; + } + return true; +} + +static bool CheckAMFStringSize(uint32_t len) { + if (FLAGS_amf_max_string_size < 0 || + len > (uint32_t)FLAGS_amf_max_string_size) { + LOG(ERROR) << "AMF string exceeds max size! max=" + << FLAGS_amf_max_string_size << ", actually=" << len; + return false; + } + return true; +} + +static bool CheckAMFArraySize(uint32_t count) { + if (FLAGS_amf_max_array_size < 0 || + count > (uint32_t)FLAGS_amf_max_array_size) { + LOG(ERROR) << "AMF array exceeds max size! max=" + << FLAGS_amf_max_array_size << ", actually=" << count; + return false; + } + return true; +} + +const char* marker2str(AMFMarker marker) { + switch (marker) { + case AMF_MARKER_NUMBER: return "number"; + case AMF_MARKER_BOOLEAN: return "boolean"; + case AMF_MARKER_STRING: return "string"; + case AMF_MARKER_OBJECT: return "object"; + case AMF_MARKER_MOVIECLIP: return "movieclip"; + case AMF_MARKER_NULL: return "null"; + case AMF_MARKER_UNDEFINED: return "undefined"; + case AMF_MARKER_REFERENCE: return "reference"; + case AMF_MARKER_ECMA_ARRAY: return "ecma-array"; + case AMF_MARKER_OBJECT_END: return "object-end"; + case AMF_MARKER_STRICT_ARRAY: return "strict-array"; + case AMF_MARKER_DATE: return "date"; + case AMF_MARKER_LONG_STRING: return "long-string"; + case AMF_MARKER_UNSUPPORTED: return "unsupported"; + case AMF_MARKER_RECORDSET: return "recordset"; + case AMF_MARKER_XML_DOCUMENT: return "xml-document"; + case AMF_MARKER_TYPED_OBJECT: return "typed-object"; + case AMF_MARKER_AVMPLUS_OBJECT: return "avmplus-object"; + } + return "Unknown marker"; +} + +const char* marker2str(uint8_t marker) { + return marker2str((AMFMarker)marker); +} + +// =============== AMFField ============== + +AMFField::AMFField() + : _type(AMF_MARKER_UNDEFINED), _is_shortstr(false), _strsize(0) { +} + +AMFField::AMFField(const AMFField& rhs) + : _type(rhs._type), _is_shortstr(rhs._is_shortstr), _strsize(rhs._strsize) { + _num = rhs._num; + if (rhs.IsString()) { + if (!rhs._is_shortstr) { + _str = (char*)malloc(rhs._strsize + 1); + memcpy(_str, rhs._str, rhs._strsize + 1); + } + } else if (rhs.IsObject()) { + _obj = new AMFObject(*rhs._obj); + } else if (rhs.IsArray()) { + _arr = new AMFArray(*rhs._arr); + } +} + +AMFField& AMFField::operator=(const AMFField& rhs) { + Clear(); + _type = rhs._type; + _is_shortstr = rhs._is_shortstr; + _strsize = rhs._strsize; + _num = rhs._num; + if (rhs.IsString()) { + if (!_is_shortstr) { + _str = (char*)malloc(rhs._strsize + 1); + memcpy(_str, rhs._str, rhs._strsize + 1); + } + } else if (rhs.IsObject()) { + _obj = new AMFObject(*rhs._obj); + } else if (rhs.IsArray()) { + _arr = new AMFArray(*rhs._arr); + } + return *this; +} + +void AMFField::SlowerClear() { + switch (type()) { + case AMF_MARKER_NUMBER: + case AMF_MARKER_BOOLEAN: + case AMF_MARKER_MOVIECLIP: + case AMF_MARKER_NULL: + case AMF_MARKER_UNDEFINED: + case AMF_MARKER_REFERENCE: + case AMF_MARKER_OBJECT_END: + case AMF_MARKER_DATE: + case AMF_MARKER_UNSUPPORTED: + case AMF_MARKER_RECORDSET: + case AMF_MARKER_XML_DOCUMENT: + case AMF_MARKER_TYPED_OBJECT: + case AMF_MARKER_AVMPLUS_OBJECT: + break; + case AMF_MARKER_STRICT_ARRAY: + delete _arr; + _arr = NULL; + break; + case AMF_MARKER_STRING: + case AMF_MARKER_LONG_STRING: + if (!_is_shortstr) { + free(_str); + _str = NULL; + } + _strsize = 0; + _is_shortstr = false; + break; + case AMF_MARKER_OBJECT: + case AMF_MARKER_ECMA_ARRAY: + delete _obj; + _obj = NULL; + break; + } + _type = AMF_MARKER_UNDEFINED; +} + +const AMFField* AMFObject::Find(const char* name) const { + std::map::const_iterator it = + butil::find_cstr(_fields, name); + if (it != _fields.end()) { + return &it->second; + } + return NULL; +} + +void AMFField::SetString(const butil::StringPiece& str) { + // TODO: Try to reuse the space. + Clear(); + if (str.size() < SSO_LIMIT) { + _type = AMF_MARKER_STRING; + _is_shortstr = true; + _strsize = str.size(); + memcpy(_shortstr, str.data(), str.size()); + _shortstr[str.size()] = '\0'; + } else { + _type = (str.size() < 65536u ? + AMF_MARKER_STRING : AMF_MARKER_LONG_STRING); + char* buf = (char*)malloc(str.size() + 1); + memcpy(buf, str.data(), str.size()); + buf[str.size()] = '\0'; + _is_shortstr = false; + _strsize = str.size(); + _str = buf; + } +} + +void AMFField::SetBool(bool val) { + if (_type != AMF_MARKER_BOOLEAN) { + Clear(); + _type = AMF_MARKER_BOOLEAN; + } + _b = val; +} + +void AMFField::SetNumber(double val) { + if (_type != AMF_MARKER_NUMBER) { + Clear(); + _type = AMF_MARKER_NUMBER; + } + _num = val; +} + +void AMFField::SetNull() { + if (_type != AMF_MARKER_NULL) { + Clear(); + _type = AMF_MARKER_NULL; + } +} + +void AMFField::SetUndefined() { + if (_type != AMF_MARKER_UNDEFINED) { + Clear(); + _type = AMF_MARKER_UNDEFINED; + } +} + +void AMFField::SetUnsupported() { + if (_type != AMF_MARKER_UNSUPPORTED) { + Clear(); + _type = AMF_MARKER_UNSUPPORTED; + } +} + +AMFObject* AMFField::MutableObject() { + if (!IsObject()) { + Clear(); + _type = AMF_MARKER_OBJECT; + _obj = new AMFObject; + } + return _obj; +} + +AMFArray* AMFField::MutableArray() { + if (!IsArray()) { + Clear(); + _type = AMF_MARKER_STRICT_ARRAY; + _arr = new AMFArray; + } + return _arr; +} + +// ============= AMFObject ============= + +void AMFObject::SetString(const std::string& name, const butil::StringPiece& str) { + _fields[name].SetString(str); +} + +void AMFObject::SetBool(const std::string& name, bool val) { + _fields[name].SetBool(val); +} + +void AMFObject::SetNumber(const std::string& name, double val) { + _fields[name].SetNumber(val); +} + +void AMFObject::SetNull(const std::string& name) { + _fields[name].SetNull(); +} + +void AMFObject::SetUndefined(const std::string& name) { + _fields[name].SetUndefined(); +} + +void AMFObject::SetUnsupported(const std::string& name) { + _fields[name].SetUnsupported(); +} + +AMFObject* AMFObject::MutableObject(const std::string& name) { + return _fields[name].MutableObject(); +} + +AMFArray* AMFObject::MutableArray(const std::string& name) { + return _fields[name].MutableArray(); +} + +static bool ReadAMFShortStringBody(std::string* str, AMFInputStream* stream) { + uint16_t len = 0; + if (stream->cut_u16(&len) != 2u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + if (!CheckAMFStringSize(len)) { + return false; + } + str->resize(len); + if (len != 0 && stream->cutn(&(*str)[0], len) != len) { + str->clear(); + LOG(ERROR) << "stream is not long enough"; + return false; + } + return true; +} + +static bool ReadAMFLongStringBody(std::string* str, AMFInputStream* stream) { + uint32_t len = 0; + if (stream->cut_u32(&len) != 4u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + if (!CheckAMFStringSize(len)) { + return false; + } + str->resize(len); + if (len != 0 && stream->cutn(&(*str)[0], len) != len) { + str->clear(); + LOG(ERROR) << "stream is not long enough"; + return false; + } + return true; +} + +bool ReadAMFString(std::string* str, AMFInputStream* stream) { + uint8_t marker; + if (stream->cut_u8(&marker) != 1u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + if ((AMFMarker)marker == AMF_MARKER_STRING) { + return ReadAMFShortStringBody(str, stream); + } else if ((AMFMarker)marker == AMF_MARKER_LONG_STRING) { + return ReadAMFLongStringBody(str, stream); + } + LOG(ERROR) << "Expected string, actually " << marker2str(marker); + return false; +} + +bool ReadAMFBool(bool* val, AMFInputStream* stream) { + uint8_t marker; + if (stream->cut_u8(&marker) != 1u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + if ((AMFMarker)marker == AMF_MARKER_BOOLEAN) { + uint8_t tmp; + if (stream->cut_u8(&tmp) != 1u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + *val = tmp; + return true; + } + LOG(ERROR) << "Expected boolean, actually " << marker2str(marker); + return false; +} + +bool ReadAMFNumber(double* val, AMFInputStream* stream) { + uint8_t marker; + if (stream->cut_u8(&marker) != 1u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + if ((AMFMarker)marker == AMF_MARKER_NUMBER) { + if (stream->cut_u64((uint64_t*)val) != 8u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + return true; + } + LOG(ERROR) << "Expected number, actually " << marker2str(marker); + return false; +} + +bool ReadAMFUint32(uint32_t* val, AMFInputStream* stream) { + double d; + if (!ReadAMFNumber(&d, stream)) { + return false; + } + *val = (uint32_t)d; + return true; +} + +bool ReadAMFNull(AMFInputStream* stream) { + uint8_t marker; + if (stream->cut_u8(&marker) != 1u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + if ((AMFMarker)marker == AMF_MARKER_NULL) { + return true; + } + LOG(ERROR) << "Expected null, actually " << marker2str(marker); + return false; +} + +bool ReadAMFUndefined(AMFInputStream* stream) { + uint8_t marker; + if (stream->cut_u8(&marker) != 1u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + if ((AMFMarker)marker == AMF_MARKER_UNDEFINED) { + return true; + } + LOG(ERROR) << "Expected undefined, actually " << marker2str(marker); + return false; +} + +bool ReadAMFUnsupported(AMFInputStream* stream) { + uint8_t marker; + if (stream->cut_u8(&marker) != 1u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + if ((AMFMarker)marker == AMF_MARKER_UNSUPPORTED) { + return true; + } + LOG(ERROR) << "Expected unsupported, actually " << marker2str(marker); + return false; +} + +static bool ReadAMFObjectBody(google::protobuf::Message* message, + AMFInputStream* stream, + int depth); +static bool SkipAMFObjectBody(AMFInputStream* stream, int depth); + +static bool ReadAMFObjectField(AMFInputStream* stream, + google::protobuf::Message* message, + const google::protobuf::FieldDescriptor* field, + int depth) { + if (!CheckAMFDepth(depth)) { + return false; + } + const google::protobuf::Reflection* reflection = NULL; + if (field) { + reflection = message->GetReflection(); + } + uint8_t marker; + if (stream->cut_u8(&marker) != 1u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + switch ((AMFMarker)marker) { + case AMF_MARKER_NUMBER: { + uint64_t val = 0; + if (stream->cut_u64(&val) != 8u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + if (field) { + if (field->cpp_type() != google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE) { + LOG(WARNING) << "Can't set double=" << val << " to " + << field->full_name(); + } else { + double* dptr = (double*)&val; + reflection->SetDouble(message, field, *dptr); + } + } + } break; + case AMF_MARKER_BOOLEAN: { + uint8_t val = 0; + if (stream->cut_u8(&val) != 1u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + if (field) { + if (field->cpp_type() != google::protobuf::FieldDescriptor::CPPTYPE_BOOL) { + LOG(WARNING) << "Can't set bool to " << field->full_name(); + } else { + reflection->SetBool(message, field, !!val); + } + } + } break; + case AMF_MARKER_STRING: { + std::string val; + if (!ReadAMFShortStringBody(&val, stream)) { + return false; + } + if (field) { + if (field->cpp_type() != google::protobuf::FieldDescriptor::CPPTYPE_STRING) { + LOG(WARNING) << "Can't set string=`" << val << "' to " + << field->full_name(); + } else { + reflection->SetString(message, field, val); + } + } + } break; + case AMF_MARKER_TYPED_OBJECT: { + std::string class_name; + if (!ReadAMFShortStringBody(&class_name, stream)) { + LOG(ERROR) << "Fail to read class_name"; + } + } + // fall through + case AMF_MARKER_OBJECT: { + if (field) { + if (field->cpp_type() != google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE) { + LOG(WARNING) << "Can't set object to " << field->full_name(); + } else { + google::protobuf::Message* m = reflection->MutableMessage(message, field); + if (!ReadAMFObjectBody(m, stream, depth + 1)) { + return false; + } + } + } else { + if (!SkipAMFObjectBody(stream, depth + 1)) { + return false; + } + } + } break; + case AMF_MARKER_NULL: + case AMF_MARKER_UNDEFINED: + case AMF_MARKER_UNSUPPORTED: + // Nothing to do + break; + case AMF_MARKER_MOVIECLIP: + case AMF_MARKER_REFERENCE: + case AMF_MARKER_ECMA_ARRAY: + case AMF_MARKER_STRICT_ARRAY: + case AMF_MARKER_DATE: + case AMF_MARKER_RECORDSET: + case AMF_MARKER_XML_DOCUMENT: + case AMF_MARKER_AVMPLUS_OBJECT: + LOG(ERROR) << marker2str(marker) << " is not supported yet"; + return false; + case AMF_MARKER_OBJECT_END: + CHECK(false) << "object-end shouldn't be present here"; + return false; + case AMF_MARKER_LONG_STRING: { + std::string val; + if (!ReadAMFLongStringBody(&val, stream)) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + if (field) { + if (field->cpp_type() != google::protobuf::FieldDescriptor::CPPTYPE_STRING) { + LOG(WARNING) << "Can't set string=`" << val << "' to " + << field->full_name(); + } else { + reflection->SetString(message, field, val); + } + } + } break; + } // switch + return true; +} + +static bool ReadAMFObjectBody(google::protobuf::Message* message, + AMFInputStream* stream, + int depth) { + if (!CheckAMFDepth(depth)) { + return false; + } + const google::protobuf::Descriptor* desc = message->GetDescriptor(); + std::string name; + while (ReadAMFShortStringBody(&name, stream)) { + if (name.empty()) { + uint8_t marker; + if (stream->cut_u8(&marker) != 1u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + if ((AMFMarker)marker != AMF_MARKER_OBJECT_END) { + LOG(ERROR) << "marker=" << marker + << " after empty name is not object end"; + return false; + } + break; + } + const google::protobuf::FieldDescriptor* field = desc->FindFieldByName(name); + RPC_VLOG_IF(field == NULL) << "Unknown field=" << desc->full_name() + << "." << name; + if (!ReadAMFObjectField(stream, message, field, depth)) { + return false; + } + } + return true; +} + +static bool SkipAMFObjectBody(AMFInputStream* stream, int depth) { + if (!CheckAMFDepth(depth)) { + return false; + } + std::string name; + while (ReadAMFShortStringBody(&name, stream)) { + if (name.empty()) { + uint8_t marker; + if (stream->cut_u8(&marker) != 1u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + if ((AMFMarker)marker != AMF_MARKER_OBJECT_END) { + LOG(ERROR) << "marker=" << marker + << " after empty name is not object end"; + return false; + } + break; + } + if (!ReadAMFObjectField(stream, NULL, NULL, depth)) { + return false; + } + } + return true; +} + +static bool ReadAMFEcmaArrayBody(google::protobuf::Message* message, + AMFInputStream* stream, + int depth) { + if (!CheckAMFDepth(depth)) { + return false; + } + uint32_t count = 0; + if (stream->cut_u32(&count) != 4u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + if (!CheckAMFArraySize(count)) { + return false; + } + const google::protobuf::Descriptor* desc = message->GetDescriptor(); + std::string name; + for (uint32_t i = 0; i < count; ++i) { + if (!ReadAMFShortStringBody(&name, stream)) { + LOG(ERROR) << "Fail to read name from the stream"; + return false; + } + const google::protobuf::FieldDescriptor* field = desc->FindFieldByName(name); + RPC_VLOG_IF(field == NULL) << "Unknown field=" << desc->full_name() + << "." << name; + if (!ReadAMFObjectField(stream, message, field, depth)) { + return false; + } + } + return true; +} + +bool ReadAMFObject(google::protobuf::Message* msg, AMFInputStream* stream) { + uint8_t marker; + if (stream->cut_u8(&marker) != 1u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + if ((AMFMarker)marker == AMF_MARKER_OBJECT) { + if (!ReadAMFObjectBody(msg, stream, 0)) { + return false; + } + } else if ((AMFMarker)marker == AMF_MARKER_ECMA_ARRAY) { + if (!ReadAMFEcmaArrayBody(msg, stream, 0)) { + return false; + } + } else if ((AMFMarker)marker != AMF_MARKER_NULL) { + // Notice that NULL is treated as an object w/o any fields. + LOG(ERROR) << "Expected object/null, actually " << marker2str(marker); + return false; + } + if (!msg->IsInitialized()) { + LOG(ERROR) << "Missing required fields: " + << msg->InitializationErrorString(); + return false; + } + return true; +} + +// [Reading AMFObject] + +static bool ReadAMFObjectBody(AMFObject* obj, AMFInputStream* stream, int depth); +static bool ReadAMFEcmaArrayBody(AMFObject* obj, AMFInputStream* stream, int depth); +static bool ReadAMFArrayBody(AMFArray* arr, AMFInputStream* stream, int depth); + +static bool ReadAMFObjectField(AMFInputStream* stream, + AMFObject* obj, + const std::string& name, + int depth) { + if (!CheckAMFDepth(depth)) { + return false; + } + uint8_t marker; + if (stream->cut_u8(&marker) != 1u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + switch ((AMFMarker)marker) { + case AMF_MARKER_NUMBER: { + uint64_t val = 0; + if (stream->cut_u64(&val) != 8u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + double* dptr = (double*)&val; + obj->SetNumber(name, *dptr); + } break; + case AMF_MARKER_BOOLEAN: { + uint8_t val = 0; + if (stream->cut_u8(&val) != 1u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + obj->SetBool(name, val); + } break; + case AMF_MARKER_STRING: { + std::string val; + if (!ReadAMFShortStringBody(&val, stream)) { + return false; + } + obj->SetString(name, val); + } break; + case AMF_MARKER_TYPED_OBJECT: { + std::string class_name; + if (!ReadAMFShortStringBody(&class_name, stream)) { + LOG(ERROR) << "Fail to read class_name"; + } + } + // fall through + case AMF_MARKER_OBJECT: { + if (!ReadAMFObjectBody(obj->MutableObject(name), stream, depth + 1)) { + return false; + } + } break; + case AMF_MARKER_ECMA_ARRAY: { + if (!ReadAMFEcmaArrayBody(obj->MutableObject(name), stream, depth + 1)) { + return false; + } + } break; + case AMF_MARKER_STRICT_ARRAY: { + if (!ReadAMFArrayBody(obj->MutableArray(name), stream, depth + 1)) { + return false; + } + } break; + case AMF_MARKER_NULL: + obj->SetNull(name); + break; + case AMF_MARKER_UNDEFINED: + obj->SetUndefined(name); + break; + case AMF_MARKER_UNSUPPORTED: + obj->SetUnsupported(name); + break; + case AMF_MARKER_MOVIECLIP: + case AMF_MARKER_REFERENCE: + case AMF_MARKER_DATE: + case AMF_MARKER_RECORDSET: + case AMF_MARKER_XML_DOCUMENT: + case AMF_MARKER_AVMPLUS_OBJECT: + LOG(ERROR) << marker2str(marker) << " is not supported yet"; + return false; + case AMF_MARKER_OBJECT_END: + CHECK(false) << "object-end shouldn't be present here"; + break; + case AMF_MARKER_LONG_STRING: { + std::string val; + if (!ReadAMFLongStringBody(&val, stream)) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + obj->SetString(name, val); + } break; + } // switch + return true; +} + +static bool ReadAMFObjectBody(AMFObject* obj, AMFInputStream* stream, int depth) { + if (!CheckAMFDepth(depth)) { + return false; + } + std::string name; + while (ReadAMFShortStringBody(&name, stream)) { + if (name.empty()) { + uint8_t marker; + if (stream->cut_u8(&marker) != 1u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + if ((AMFMarker)marker != AMF_MARKER_OBJECT_END) { + LOG(ERROR) << "marker=" << marker + << " after empty name is not object end"; + return false; + } + break; + } + if (!ReadAMFObjectField(stream, obj, name, depth)) { + return false; + } + } + return true; +} + +static bool ReadAMFEcmaArrayBody(AMFObject* obj, AMFInputStream* stream, int depth) { + if (!CheckAMFDepth(depth)) { + return false; + } + uint32_t count = 0; + if (stream->cut_u32(&count) != 4u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + if (!CheckAMFArraySize(count)) { + return false; + } + std::string name; + for (uint32_t i = 0; i < count; ++i) { + if (!ReadAMFShortStringBody(&name, stream)) { + LOG(ERROR) << "Fail to read name from the stream"; + return false; + } + if (!ReadAMFObjectField(stream, obj, name, depth)) { + return false; + } + } + return true; +} + +bool ReadAMFObject(AMFObject* obj, AMFInputStream* stream) { + uint8_t marker; + if (stream->cut_u8(&marker) != 1u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + if ((AMFMarker)marker == AMF_MARKER_OBJECT) { + if (!ReadAMFObjectBody(obj, stream, 0)) { + return false; + } + } else if ((AMFMarker)marker == AMF_MARKER_ECMA_ARRAY) { + if (!ReadAMFEcmaArrayBody(obj, stream, 0)) { + return false; + } + } else if ((AMFMarker)marker != AMF_MARKER_NULL) { + // NOTE: NULL is treated as an object w/o any fields. + LOG(ERROR) << "Expected object/null, actually " << marker2str(marker); + return false; + } + return true; +} + +static bool ReadAMFArrayItem(AMFInputStream* stream, AMFArray* arr, int depth) { + if (!CheckAMFDepth(depth)) { + return false; + } + uint8_t marker; + if (stream->cut_u8(&marker) != 1u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + switch ((AMFMarker)marker) { + case AMF_MARKER_NUMBER: { + uint64_t val = 0; + if (stream->cut_u64(&val) != 8u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + double* dptr = (double*)&val; + arr->AddNumber(*dptr); + } break; + case AMF_MARKER_BOOLEAN: { + uint8_t val = 0; + if (stream->cut_u8(&val) != 1u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + arr->AddBool(val); + } break; + case AMF_MARKER_STRING: { + std::string val; + if (!ReadAMFShortStringBody(&val, stream)) { + return false; + } + arr->AddString(val); + } break; + case AMF_MARKER_TYPED_OBJECT: { + std::string class_name; + if (!ReadAMFShortStringBody(&class_name, stream)) { + LOG(ERROR) << "Fail to read class_name"; + } + } + // fall through + case AMF_MARKER_OBJECT: { + if (!ReadAMFObjectBody(arr->AddObject(), stream, depth + 1)) { + return false; + } + } break; + case AMF_MARKER_ECMA_ARRAY: { + if (!ReadAMFEcmaArrayBody(arr->AddObject(), stream, depth + 1)) { + return false; + } + } break; + case AMF_MARKER_STRICT_ARRAY: { + if (!ReadAMFArrayBody(arr->AddArray(), stream, depth + 1)) { + return false; + } + } break; + case AMF_MARKER_NULL: + arr->AddNull(); + break; + case AMF_MARKER_UNDEFINED: + arr->AddUndefined(); + break; + case AMF_MARKER_UNSUPPORTED: + arr->AddUnsupported(); + break; + case AMF_MARKER_MOVIECLIP: + case AMF_MARKER_REFERENCE: + case AMF_MARKER_DATE: + case AMF_MARKER_RECORDSET: + case AMF_MARKER_XML_DOCUMENT: + case AMF_MARKER_AVMPLUS_OBJECT: + LOG(ERROR) << marker2str(marker) << " is not supported yet"; + return false; + case AMF_MARKER_OBJECT_END: + CHECK(false) << "object-end shouldn't be present here"; + break; + case AMF_MARKER_LONG_STRING: { + std::string val; + if (!ReadAMFLongStringBody(&val, stream)) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + arr->AddString(val); + } break; + } // switch + return true; +} + +static bool ReadAMFArrayBody(AMFArray* arr, AMFInputStream* stream, int depth) { + if (!CheckAMFDepth(depth)) { + return false; + } + uint32_t count = 0; + if (stream->cut_u32(&count) != 4u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + if (!CheckAMFArraySize(count)) { + return false; + } + for (uint32_t i = 0; i < count; ++i) { + if (!ReadAMFArrayItem(stream, arr, depth)) { + return false; + } + } + return true; +} + +bool ReadAMFArray(AMFArray* arr, AMFInputStream* stream) { + uint8_t marker; + if (stream->cut_u8(&marker) != 1u) { + LOG(ERROR) << "stream is not long enough"; + return false; + } + if ((AMFMarker)marker == AMF_MARKER_STRICT_ARRAY) { + if (!ReadAMFArrayBody(arr, stream, 0)) { + return false; + } + } else if ((AMFMarker)marker != AMF_MARKER_NULL) { + // NOTE: NULL is treated as an array w/o any items. + LOG(ERROR) << "Expected array/null, actually " << marker2str(marker); + return false; + } + return true; +} + +// ======== AMFArray ========= + +AMFArray::AMFArray() : _size(0) { +} + +AMFArray::AMFArray(const AMFArray& rhs) + : _size(rhs._size) { + const size_t inline_size = std::min((size_t)_size, arraysize(_fields)); + for (size_t i = 0; i < inline_size; ++i) { + _fields[i] = rhs._fields[i]; + } + if (_size > arraysize(_fields)) { + _morefields = rhs._morefields; + } +} + +AMFArray& AMFArray::operator=(const AMFArray& rhs) { + if (_size < rhs._size) { + this->~AMFArray(); + return *new (this) AMFArray(rhs); + } + for (size_t i = 0; i < rhs._size; ++i) { + (*this)[i] = rhs[i]; + } + for (size_t i = rhs._size; i < _size; ++i) { + RemoveLastField(); + } + return *this; +} + +void AMFArray::Clear() { + const size_t inline_size = std::min((size_t)_size, arraysize(_fields)); + for (size_t i = 0; i < inline_size; ++i) { + _fields[i].Clear(); + } + _size = 0; + _morefields.clear(); +} + +AMFField* AMFArray::AddField() { + if (_size < arraysize(_fields)) { + return &_fields[_size++]; + } + size_t more_size = _size - arraysize(_fields); + if (more_size < _morefields.size()) { + ++_size; + return &_morefields[more_size]; + } + _morefields.resize(_morefields.size() + 1); + ++_size; + return &_morefields.back(); +} + +void AMFArray::RemoveLastField() { + if (_size == 0) { + return; + } + if (_size <= arraysize(_fields)) { + _fields[--_size].Clear(); + return; + } + _morefields.pop_back(); + --_size; +} + +// [ Write ] + +void WriteAMFString(const butil::StringPiece& str, AMFOutputStream* stream) { + if (str.size() < 65536u) { + stream->put_u8(AMF_MARKER_STRING); + stream->put_u16(str.size()); + stream->putn(str.data(), str.size()); + } else { + stream->put_u8(AMF_MARKER_LONG_STRING); + stream->put_u32(str.size()); + stream->putn(str.data(), str.size()); + } +} + +void WriteAMFBool(bool val, AMFOutputStream* stream) { + stream->put_u8(AMF_MARKER_BOOLEAN); + stream->put_u8(val); +} + +void WriteAMFNumber(double val, AMFOutputStream* stream) { + stream->put_u8(AMF_MARKER_NUMBER); + uint64_t* uptr = (uint64_t*)&val; + stream->put_u64(*uptr); +} + +void WriteAMFUint32(uint32_t val, AMFOutputStream* stream) { + return WriteAMFNumber((double)val, stream); +} + +void WriteAMFNull(AMFOutputStream* stream) { + stream->put_u8(AMF_MARKER_NULL); +} + +void WriteAMFUndefined(AMFOutputStream* stream) { + stream->put_u8(AMF_MARKER_UNDEFINED); +} + +void WriteAMFUnsupported(AMFOutputStream* stream) { + stream->put_u8(AMF_MARKER_UNSUPPORTED); +} + +void WriteAMFObject(const google::protobuf::Message& message, + AMFOutputStream* stream) { + stream->put_u8(AMF_MARKER_OBJECT); + const google::protobuf::Descriptor* desc = message.GetDescriptor(); + const google::protobuf::Reflection* reflection = message.GetReflection(); + for (int i = 0; i < desc->field_count(); ++i) { + const google::protobuf::FieldDescriptor* field = desc->field(i); + if (field->is_repeated()) { + LOG(ERROR) << "Repeated fields are not supported yet"; + return stream->set_bad(); + } + const bool has_field = reflection->HasField(message, field); + if (!has_field) { + if (field->is_required()) { + LOG(ERROR) << "Missing required field=" << field->full_name(); + return stream->set_bad(); + } else { + continue; + } + } + const auto& name = field->name(); + if (name.size() >= 65536u) { + LOG(ERROR) << "name is too long!"; + return stream->set_bad(); + } + stream->put_u16(name.size()); + stream->putn(name.data(), name.size()); + switch (field->cpp_type()) { + case google::protobuf::FieldDescriptor::CPPTYPE_INT32: + case google::protobuf::FieldDescriptor::CPPTYPE_INT64: + case google::protobuf::FieldDescriptor::CPPTYPE_UINT32: + case google::protobuf::FieldDescriptor::CPPTYPE_UINT64: + LOG(ERROR) << "AMF does not have integers"; + return stream->set_bad(); + case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE: { + stream->put_u8(AMF_MARKER_NUMBER); + const double val = reflection->GetDouble(message, field); + uint64_t* uptr = (uint64_t*)&val; + stream->put_u64(*uptr); + } break; + case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT: + LOG(ERROR) << "AMF does not have float, use double instead"; + return stream->set_bad(); + case google::protobuf::FieldDescriptor::CPPTYPE_BOOL: { + stream->put_u8(AMF_MARKER_BOOLEAN); + const bool val = reflection->GetBool(message, field); + stream->put_u8(val); + } break; + case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: + LOG(ERROR) << "AMF does not have enum"; + return stream->set_bad(); + case google::protobuf::FieldDescriptor::CPPTYPE_STRING: { + const std::string val = reflection->GetString(message, field); + if (val.size() < 65536u) { + stream->put_u8(AMF_MARKER_STRING); + stream->put_u16(val.size()); + stream->putn(val.data(), val.size()); + } else { + stream->put_u8(AMF_MARKER_LONG_STRING); + stream->put_u32(val.size()); + stream->putn(val.data(), val.size()); + } + } break; + case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: + WriteAMFObject(reflection->GetMessage(message, field), stream); + break; + } // switch + if (!stream->good()) { + LOG(ERROR) << "Fail to serialize field=" << field->full_name(); + return; + } + } + stream->put_u16(0); + stream->put_u8(AMF_MARKER_OBJECT_END); +} + +static void WriteAMFField(const AMFField& field, AMFOutputStream* stream) { + switch (field.type()) { + case AMF_MARKER_NUMBER: { + stream->put_u8(AMF_MARKER_NUMBER); + const double val = field.AsNumber(); + uint64_t* uptr = (uint64_t*)&val; + stream->put_u64(*uptr); + } break; + case AMF_MARKER_BOOLEAN: + stream->put_u8(AMF_MARKER_BOOLEAN); + stream->put_u8(field.AsBool()); + break; + case AMF_MARKER_STRING: { + stream->put_u8(AMF_MARKER_STRING); + const butil::StringPiece str = field.AsString(); + stream->put_u16(str.size()); + stream->putn(str.data(), str.size()); + } break; + case AMF_MARKER_LONG_STRING: { + stream->put_u8(AMF_MARKER_LONG_STRING); + const butil::StringPiece str = field.AsString(); + stream->put_u32(str.size()); + stream->putn(str.data(), str.size()); + } break; + case AMF_MARKER_OBJECT: + case AMF_MARKER_ECMA_ARRAY: + WriteAMFObject(field.AsObject(), stream); + break; + case AMF_MARKER_STRICT_ARRAY: + WriteAMFArray(field.AsArray(), stream); + break; + case AMF_MARKER_NULL: + stream->put_u8(AMF_MARKER_NULL); + break; + case AMF_MARKER_UNDEFINED: + stream->put_u8(AMF_MARKER_UNDEFINED); + break; + case AMF_MARKER_UNSUPPORTED: + stream->put_u8(AMF_MARKER_UNSUPPORTED); + break; + case AMF_MARKER_MOVIECLIP: + case AMF_MARKER_REFERENCE: + case AMF_MARKER_DATE: + case AMF_MARKER_RECORDSET: + case AMF_MARKER_XML_DOCUMENT: + case AMF_MARKER_TYPED_OBJECT: + case AMF_MARKER_AVMPLUS_OBJECT: + LOG(ERROR) << marker2str(field.type()) << " is not supported yet"; + break; + case AMF_MARKER_OBJECT_END: + CHECK(false) << "object-end shouldn't be present here"; + break; + } // switch +} + +void WriteAMFObject(const AMFObject& obj, AMFOutputStream* stream) { + stream->put_u8(AMF_MARKER_OBJECT); + for (AMFObject::const_iterator it = obj.begin(); it != obj.end(); ++it) { + const std::string& name = it->first; + if (name.size() >= 65536u) { + LOG(ERROR) << "name is too long!"; + return stream->set_bad(); + } + stream->put_u16(name.size()); + stream->putn(name.data(), name.size()); + WriteAMFField(it->second, stream); + if (!stream->good()) { + LOG(ERROR) << "Fail to serialize field=" << name; + return; + } + } + stream->put_u16(0); + stream->put_u8(AMF_MARKER_OBJECT_END); +} + +void WriteAMFArray(const AMFArray& arr, AMFOutputStream* stream) { + stream->put_u8(AMF_MARKER_STRICT_ARRAY); + stream->put_u32(arr.size()); + for (size_t i = 0; i < arr.size(); ++i) { + WriteAMFField(arr[i], stream); + if (!stream->good()) { + LOG(ERROR) << "Fail to serialize item[" << i << ']'; + return; + } + } +} + +std::ostream& operator<<(std::ostream& os, const AMFField& field) { + switch (field.type()) { + case AMF_MARKER_NUMBER: + return os << field.AsNumber(); + case AMF_MARKER_BOOLEAN: + return os << (field.AsBool() ? "true" : "false"); + case AMF_MARKER_NULL: + return os << "null"; + case AMF_MARKER_UNDEFINED: + return os << "undefined"; + case AMF_MARKER_UNSUPPORTED: + return os << "unsupported"; + case AMF_MARKER_MOVIECLIP: + case AMF_MARKER_REFERENCE: + case AMF_MARKER_OBJECT_END: + case AMF_MARKER_DATE: + case AMF_MARKER_RECORDSET: + case AMF_MARKER_XML_DOCUMENT: + case AMF_MARKER_TYPED_OBJECT: + case AMF_MARKER_AVMPLUS_OBJECT: + return os << marker2str(field.type()); + case AMF_MARKER_STRING: + case AMF_MARKER_LONG_STRING: + return os << '"' << field.AsString() << '"'; + case AMF_MARKER_OBJECT: + case AMF_MARKER_ECMA_ARRAY: + return os << field.AsObject(); + case AMF_MARKER_STRICT_ARRAY: + return os << field.AsArray(); + } + return os; +} + +std::ostream& operator<<(std::ostream& os, const AMFObject& obj) { + bool first = true; + os << "AMFObject{"; + for (AMFObject::const_iterator it = obj.begin(); it != obj.end(); ++it) { + if (!first) { + os << ' '; + } else { + first = false; + } + os << it->first << '=' << it->second; + } + return os << '}'; +} + +std::ostream& operator<<(std::ostream& os, const AMFArray& arr) { + bool first = true; + os << "AMFArray["; + for (size_t i = 0; i < arr.size(); ++i) { + if (i >= 512) { + os << "..."; + break; + } + if (!first) { + os << ' '; + } else { + first = false; + } + os << arr[i]; + } + return os << ']'; +} + +} // namespace brpc diff --git a/src/brpc/amf.h b/src/brpc/amf.h new file mode 100644 index 0000000..e9777e8 --- /dev/null +++ b/src/brpc/amf.h @@ -0,0 +1,304 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_AMF_H +#define BRPC_AMF_H + +#include +#include +#include +#include +#include "butil/sys_byteorder.h" +#include "butil/strings/string_piece.h" + + +namespace brpc { + +// For parsing and serializing Action Message Format used throughout RTMP. + +// Buffer ZeroCopyInputStream as efficient input of parsing AMF. +class AMFInputStream { +public: + AMFInputStream(google::protobuf::io::ZeroCopyInputStream* stream) + : _good(true) + , _size(0) + , _data(NULL) + , _zc_stream(stream) + , _popped_bytes(0) + {} + + ~AMFInputStream() { } + + // Cut off at-most n bytes from front side and copy to `out'. + // Returns bytes cut. + size_t cutn(void* out, size_t n); + + size_t cut_u8(uint8_t* u16); + size_t cut_u16(uint16_t* u16); + size_t cut_u32(uint32_t* u32); + size_t cut_u64(uint64_t* u64); + + // Returns bytes popped and cut since creation of this stream. + size_t popped_bytes() const { return _popped_bytes; } + + // Returns false if error occurred in other consuming functions. + bool good() const { return _good; } + + // If the error prevents parsing from going on, call this method. + // This method is also called in other functions in this class. + void set_bad() { _good = false; } + + // Return true if the stream is empty. Notice that this function + // may update _data and _size. + bool check_emptiness(); + +private: + bool _good; + int _size; + const void* _data; + google::protobuf::io::ZeroCopyInputStream* _zc_stream; + size_t _popped_bytes; +}; + +// Buffer serialize data of AMF to ZeroCopyOutputStream +class AMFOutputStream { +public: + AMFOutputStream(google::protobuf::io::ZeroCopyOutputStream* stream) + : _good(true) + , _size(0) + , _data(NULL) + , _zc_stream(stream) + , _pushed_bytes(0) + {} + + ~AMFOutputStream() { done(); } + + // Append n bytes. + void putn(const void* data, int n); + + void put_u8(uint8_t u16); + void put_u16(uint16_t u16); + void put_u32(uint32_t u32); + void put_u64(uint64_t u64); + + // Returns bytes pushed and cut since creation of this stream. + size_t pushed_bytes() const { return _pushed_bytes; } + + // Returns false if error occurred during serialization. + bool good() { return _good; } + + void set_bad() { _good = false; } + + // Optionally called to backup buffered bytes to zero-copy stream. + void done(); + +private: + bool _good; + int _size; + void* _data; + google::protobuf::io::ZeroCopyOutputStream* _zc_stream; + size_t _pushed_bytes; +}; + +// There are 16 core type markers in AMF 0. A type marker is one byte in +// length and describes the kind of encoded data that may follow. +enum AMFMarker { + AMF_MARKER_NUMBER = 0x00, + AMF_MARKER_BOOLEAN = 0x01, + AMF_MARKER_STRING = 0x02, + AMF_MARKER_OBJECT = 0x03, + AMF_MARKER_MOVIECLIP = 0x04, + AMF_MARKER_NULL = 0x05, + AMF_MARKER_UNDEFINED = 0x06, + AMF_MARKER_REFERENCE = 0x07, + AMF_MARKER_ECMA_ARRAY = 0x08, + AMF_MARKER_OBJECT_END = 0x09, + AMF_MARKER_STRICT_ARRAY = 0x0A, + AMF_MARKER_DATE = 0x0B, + AMF_MARKER_LONG_STRING = 0x0C, + AMF_MARKER_UNSUPPORTED = 0x0D, + AMF_MARKER_RECORDSET = 0x0E, + AMF_MARKER_XML_DOCUMENT = 0x0F, + AMF_MARKER_TYPED_OBJECT = 0x10, + AMF_MARKER_AVMPLUS_OBJECT = 0x11 +}; + +const char* marker2str(AMFMarker marker); +const char* marker2str(uint8_t marker); + +class AMFObject; +class AMFArray; + +// A field inside a AMF object. +class AMFField { +friend class AMFObject; +public: + static const size_t SSO_LIMIT = 8; + + AMFField(); + AMFField(const AMFField&); + AMFField& operator=(const AMFField&); + ~AMFField() { Clear(); } + void Clear() { if (_type != AMF_MARKER_UNDEFINED) { SlowerClear(); } } + + AMFMarker type() const { return (AMFMarker)_type; } + + bool IsString() const + { return _type == AMF_MARKER_STRING || _type == AMF_MARKER_LONG_STRING; } + bool IsBool() const { return _type == AMF_MARKER_BOOLEAN; } + bool IsNumber() const { return _type == AMF_MARKER_NUMBER; } + bool IsObject() const + { return _type == AMF_MARKER_OBJECT || _type == AMF_MARKER_ECMA_ARRAY; } + bool IsArray() const { return _type == AMF_MARKER_STRICT_ARRAY; } + + butil::StringPiece AsString() const + { return butil::StringPiece((_is_shortstr ? _shortstr : _str), _strsize); } + bool AsBool() const { return _b; } + double AsNumber() const { return _num; } + const AMFObject& AsObject() const { return *_obj; } + const AMFArray& AsArray() const { return *_arr; } + + void SetString(const butil::StringPiece& str); + void SetBool(bool val); + void SetNumber(double val); + void SetNull(); + void SetUndefined(); + void SetUnsupported(); + AMFObject* MutableObject(); + AMFArray* MutableArray(); + +private: + void SlowerClear(); + + uint8_t _type; + bool _is_shortstr; + uint32_t _strsize; + union { + double _num; + char* _str; + char _shortstr[SSO_LIMIT]; // SSO + bool _b; + AMFObject* _obj; + AMFArray* _arr; + }; +}; +std::ostream& operator<<(std::ostream& os, const AMFField& field); + +// A general AMF object. +class AMFObject { +public: + typedef std::map::iterator iterator; + typedef std::map::const_iterator const_iterator; + + const AMFField* Find(const char* name) const; + void Remove(const std::string& name) { _fields.erase(name); } + void Clear() { _fields.clear(); } + + void SetString(const std::string& name, const butil::StringPiece& val); + void SetBool(const std::string& name, bool val); + void SetNumber(const std::string& name, double val); + void SetNull(const std::string& name); + void SetUndefined(const std::string& name); + void SetUnsupported(const std::string& name); + AMFObject* MutableObject(const std::string& name); + AMFArray* MutableArray(const std::string& name); + + iterator begin() { return _fields.begin(); } + const_iterator begin() const { return _fields.begin(); } + iterator end() { return _fields.end(); } + const_iterator end() const { return _fields.end(); } + +private: + std::map _fields; +}; +std::ostream& operator<<(std::ostream& os, const AMFObject&); + +// An AMF strict array (not ecma array) +class AMFArray { +public: + AMFArray(); + AMFArray(const AMFArray&); + AMFArray& operator=(const AMFArray&); + ~AMFArray() { Clear(); } + void Clear(); + + const AMFField& operator[](size_t index) const; + AMFField& operator[](size_t index); + size_t size() const { return _size; } + + void AddString(const butil::StringPiece& val) { AddField()->SetString(val); } + void AddBool(bool val) { AddField()->SetBool(val); } + void AddNumber(double val) { AddField()->SetNumber(val); } + void AddNull() { AddField()->SetNull(); } + void AddUndefined() { AddField()->SetUndefined(); } + void AddUnsupported() { AddField()->SetUnsupported(); } + AMFObject* AddObject() { return AddField()->MutableObject(); } + AMFArray* AddArray() { return AddField()->MutableArray(); } + +private: + AMFField* AddField(); + void RemoveLastField(); + + uint32_t _size; + AMFField _fields[4]; + std::deque _morefields; +}; +std::ostream& operator<<(std::ostream& os, const AMFArray&); + +inline const AMFField& AMFArray::operator[](size_t index) const { + return (index < arraysize(_fields) ? _fields[index] : + _morefields[index - arraysize(_fields)]); +} +inline AMFField& AMFArray::operator[](size_t index) { + return (index < arraysize(_fields) ? _fields[index] : + _morefields[index - arraysize(_fields)]); +} + +// Parse types of the stream. +bool ReadAMFString(std::string* val, AMFInputStream* stream); +bool ReadAMFBool(bool* val, AMFInputStream* stream); +bool ReadAMFNumber(double* val, AMFInputStream* stream); +bool ReadAMFUint32(uint32_t* val, AMFInputStream* stream); +bool ReadAMFNull(AMFInputStream* stream); +bool ReadAMFUndefined(AMFInputStream* stream); +bool ReadAMFUnsupported(AMFInputStream* stream); +// The pb version just loads known fields (defined in proto) +bool ReadAMFObject(google::protobuf::Message* msg, AMFInputStream* stream); +bool ReadAMFObject(AMFObject* obj, AMFInputStream* stream); +bool ReadAMFArray(AMFArray* arr, AMFInputStream* stream); + +// Serialize types into the stream. +// Check stream->good() for successfulness after one or multiple WriteAMFxxx. +void WriteAMFString(const butil::StringPiece& val, AMFOutputStream* stream); +void WriteAMFBool(bool val, AMFOutputStream* stream); +void WriteAMFNumber(double val, AMFOutputStream* stream); +void WriteAMFUint32(uint32_t val, AMFOutputStream* stream); +void WriteAMFNull(AMFOutputStream* stream); +void WriteAMFUndefined(AMFOutputStream* stream); +void WriteAMFUnsupported(AMFOutputStream* stream); +void WriteAMFObject(const google::protobuf::Message& msg, + AMFOutputStream* stream); +void WriteAMFObject(const AMFObject& obj, AMFOutputStream* stream); +void WriteAMFArray(const AMFArray& arr, AMFOutputStream* stream); + +} // namespace brpc + + +#include "brpc/amf_inl.h" + +#endif // BRPC_AMF_H diff --git a/src/brpc/amf_inl.h b/src/brpc/amf_inl.h new file mode 100644 index 0000000..d9d3a56 --- /dev/null +++ b/src/brpc/amf_inl.h @@ -0,0 +1,171 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_AMF_INL_H +#define BRPC_AMF_INL_H + +void* fast_memcpy(void *__restrict dest, const void *__restrict src, size_t n); + + +namespace brpc { + +inline size_t AMFInputStream::cutn(void* out, size_t n) { + const size_t saved_n = n; + do { + if (_size >= (int64_t)n) { + memcpy(out, _data, n); + _data = (const char*)_data + n; + _size -= n; + _popped_bytes += saved_n; + return saved_n; + } + if (_size) { + memcpy(out, _data, _size); + out = (char*)out + _size; + n -= _size; + } + } while (_zc_stream->Next(&_data, &_size)); + _data = NULL; + _size = 0; + _popped_bytes += saved_n - n; + return saved_n - n; +} + +inline bool AMFInputStream::check_emptiness() { + return _size == 0 && !_zc_stream->Next(&_data, &_size); +} + +inline size_t AMFInputStream::cut_u8(uint8_t* val) { + if (_size >= 1) { + *val = *(uint8_t*)_data; + _data = (const char*)_data + 1; + _size -= 1; + _popped_bytes += 1; + return 1; + } + return cutn(val, 1); +} + +inline size_t AMFInputStream::cut_u16(uint16_t* val) { + if (_size >= 2) { + const uint16_t netval = *(uint16_t*)_data; + *val = butil::NetToHost16(netval); + _data = (const char*)_data + 2; + _size -= 2; + _popped_bytes += 2; + return 2; + } + uint16_t netval = 0; + const size_t ret = cutn(&netval, 2); + *val = butil::NetToHost16(netval); + return ret; +} + +inline size_t AMFInputStream::cut_u32(uint32_t* val) { + if (_size >= 4) { + const uint32_t netval = *(uint32_t*)_data; + *val = butil::NetToHost32(netval); + _data = (const char*)_data + 4; + _size -= 4; + _popped_bytes += 4; + return 4; + } + uint32_t netval = 0; + const size_t ret = cutn(&netval, 4); + *val = butil::NetToHost32(netval); + return ret; +} + +inline size_t AMFInputStream::cut_u64(uint64_t* val) { + if (_size >= 8) { + const uint64_t netval = *(uint64_t*)_data; + *val = butil::NetToHost64(netval); + _data = (const char*)_data + 8; + _size -= 8; + _popped_bytes += 8; + return 8; + } + uint64_t netval = 0; + const size_t ret = cutn(&netval, 8); + *val = butil::NetToHost64(netval); + return ret; +} + +inline void AMFOutputStream::done() { + if (_good && _size) { + _zc_stream->BackUp(_size); + _size = 0; + } +} + +inline void AMFOutputStream::putn(const void* data, int n) { + const int saved_n = n; + do { + if (n <= _size) { + fast_memcpy(_data, data, n); + _data = (char*)_data + n; + _size -= n; + _pushed_bytes += saved_n; + return; + } + fast_memcpy(_data, data, _size); + data = (const char*)data + _size; + n -= _size; + } while (_zc_stream->Next(&_data, &_size)); + _data = NULL; + _size = 0; + _pushed_bytes += (saved_n - n); + if (n) { + set_bad(); + } +} + +inline void AMFOutputStream::put_u8(uint8_t val) { + do { + if (_size > 0) { + *(uint8_t*)_data = val; + _data = (char*)_data + 1; + --_size; + ++_pushed_bytes; + return; + } + } while (_zc_stream->Next(&_data, &_size)); + _data = NULL; + _size = 0; + set_bad(); +} + +inline void AMFOutputStream::put_u16(uint16_t val) { + uint16_t netval = butil::HostToNet16(val); + return putn(&netval, sizeof(netval)); +} + +inline void AMFOutputStream::put_u32(uint32_t val) { + uint32_t netval = butil::HostToNet32(val); + return putn(&netval, sizeof(netval)); +} + +inline void AMFOutputStream::put_u64(uint64_t val) { + uint64_t netval = butil::HostToNet64(val); + return putn(&netval, sizeof(netval)); +} + +} // namespace brpc + + +#endif // BRPC_AMF_INL_H diff --git a/src/brpc/authenticator.h b/src/brpc/authenticator.h new file mode 100644 index 0000000..e08a3f1 --- /dev/null +++ b/src/brpc/authenticator.h @@ -0,0 +1,98 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_AUTHENTICATOR_H +#define BRPC_AUTHENTICATOR_H + +#include +#include "butil/endpoint.h" // butil::EndPoint +#include "butil/macros.h" // BAIDU_CONCAT +#include "brpc/extension.h" // Extension + + +namespace brpc { + +class AuthContext { +public: + AuthContext() : _is_service(false) {} + ~AuthContext() {} + + const std::string& user() const { return _user; } + void set_user(const std::string& user) { _user = user; } + + const std::string& group() const { return _group; } + void set_group(const std::string& group) { _group = group; } + + const std::string& roles() const { return _roles; } + void set_roles(const std::string& roles) { _roles = roles; } + + const std::string& starter() const { return _starter; } + void set_starter(const std::string& starter) { _starter = starter; } + + bool is_service() const { return _is_service; } + void set_is_service(bool is_service) { _is_service = is_service; } + +private: + bool _is_service; + std::string _user; + std::string _group; + std::string _roles; + std::string _starter; +}; + +class Authenticator { +public: + virtual ~Authenticator() = default; + + // Implement this method to generate credential information + // into `auth_str' which will be sent to `VerifyCredential' + // at server side. This method will be called on client side. + // Returns 0 on success, error code otherwise + virtual int GenerateCredential(std::string* auth_str) const = 0; + + // Implement this method to verify credential information + // `auth_str' from `client_addr'. You can fill credential + // context (result) into `*out_ctx' and later fetch this + // pointer from `Controller'. + // Returns 0 on success, error code otherwise + virtual int VerifyCredential(const std::string& auth_str, + const butil::EndPoint& client_addr, + AuthContext* out_ctx) const = 0; + + // Implement this method to unauthorized error text which + // will be sent as a part of error text in baidu_std/hulu_pbrpc + // protocol or body in http protocol. + virtual std::string GetUnauthorizedErrorText() const { + return ""; + } + +}; + +inline std::ostream& operator<<(std::ostream& os, const AuthContext& ctx) { + return os << "[name=" << ctx.user() << " [This is a " + << (ctx.is_service() ? "service" : "user") + << "], group=" << ctx.group() << ", roles=" << ctx.roles() + << ", starter=" << ctx.starter() << "]"; +} + + +} // namespace brpc + + + +#endif // BRPC_AUTHENTICATOR_H diff --git a/src/brpc/backup_request_policy.cpp b/src/brpc/backup_request_policy.cpp new file mode 100644 index 0000000..851537b --- /dev/null +++ b/src/brpc/backup_request_policy.cpp @@ -0,0 +1,177 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/backup_request_policy.h" + +#include "butil/logging.h" +#include "bvar/reducer.h" +#include "bvar/window.h" +#include "butil/atomicops.h" +#include "butil/time.h" + +namespace brpc { + +// Standalone statistics module for tracking backup/total request ratio +// within a sliding time window. Each instance schedules two bvar::Window +// sampler tasks; keep this in mind for high channel-count deployments. +class BackupRateLimiter { +public: + BackupRateLimiter(double max_backup_ratio, + int window_size_seconds, + int update_interval_seconds) + : _max_backup_ratio(max_backup_ratio) + , _update_interval_us(update_interval_seconds * 1000000LL) + , _total_count() + , _backup_count() + , _total_window(&_total_count, window_size_seconds) + , _backup_window(&_backup_count, window_size_seconds) + , _cached_ratio(0.0) + , _last_update_us(0) { + } + + // All atomic operations use relaxed ordering intentionally. + // This is best-effort rate limiting: a slightly stale ratio is + // acceptable for approximate throttling. Within a single update interval, + // the cached ratio is not updated, so bursts up to update_interval_seconds + // in duration can exceed the configured max_backup_ratio transiently. + bool ShouldAllow() const { + const int64_t now_us = butil::cpuwide_time_us(); + int64_t last_us = _last_update_us.load(butil::memory_order_relaxed); + double ratio = _cached_ratio.load(butil::memory_order_relaxed); + + if (now_us - last_us >= _update_interval_us) { + if (_last_update_us.compare_exchange_strong( + last_us, now_us, butil::memory_order_relaxed)) { + int64_t total = _total_window.get_value(); + int64_t backup = _backup_window.get_value(); + // Fall back to cumulative counts when the window has no + // sampled data yet (cold-start within the first few seconds). + if (total <= 0) { + total = _total_count.get_value(); + backup = _backup_count.get_value(); + } + if (total > 0) { + ratio = static_cast(backup) / total; + } else if (backup > 0) { + // Backups issued but no completions in window yet (latency spike). + // Be conservative to prevent backup storms. + ratio = 1.0; + } else { + // True cold-start: no traffic yet. Allow freely. + ratio = 0.0; + } + _cached_ratio.store(ratio, butil::memory_order_relaxed); + } + } + + bool allow = ratio < _max_backup_ratio; + if (allow) { + // Count backup decisions immediately for faster feedback + // during latency spikes (before RPCs complete). + _backup_count << 1; + } + return allow; + } + + void OnRPCEnd(const Controller* /*controller*/) { + // Count each completed user-level RPC (called once per RPC, not per leg). + // Backup decisions are counted in ShouldAllow() at decision time for + // faster feedback. As a result, the effective suppression threshold is + // (backup_count / total_count), where total_count is the number of + // user RPCs that have completed. + _total_count << 1; + } + +private: + double _max_backup_ratio; + int64_t _update_interval_us; + + bvar::Adder _total_count; + mutable bvar::Adder _backup_count; + bvar::Window> _total_window; + bvar::Window> _backup_window; + + mutable butil::atomic _cached_ratio; + mutable butil::atomic _last_update_us; +}; + +// Internal BackupRequestPolicy that composes a BackupRateLimiter +// for ratio-based suppression. +class RateLimitedBackupPolicy : public BackupRequestPolicy { +public: + RateLimitedBackupPolicy(int32_t backup_request_ms, + double max_backup_ratio, + int window_size_seconds, + int update_interval_seconds) + : _backup_request_ms(backup_request_ms) + , _rate_limiter(max_backup_ratio, window_size_seconds, + update_interval_seconds) { + } + + int32_t GetBackupRequestMs(const Controller* /*controller*/) const override { + return _backup_request_ms; + } + + bool DoBackup(const Controller* /*controller*/) const override { + return _rate_limiter.ShouldAllow(); + } + + void OnRPCEnd(const Controller* controller) override { + _rate_limiter.OnRPCEnd(controller); + } + +private: + int32_t _backup_request_ms; + BackupRateLimiter _rate_limiter; +}; + +BackupRequestPolicy* CreateRateLimitedBackupPolicy( + const RateLimitedBackupPolicyOptions& options) { + if (options.backup_request_ms < -1) { + LOG(ERROR) << "Invalid backup_request_ms=" << options.backup_request_ms + << ", must be >= -1 (-1 means inherit from ChannelOptions)"; + return NULL; + } + if (options.max_backup_ratio <= 0 || options.max_backup_ratio > 1.0) { + LOG(ERROR) << "Invalid max_backup_ratio=" << options.max_backup_ratio + << ", must be in (0, 1]"; + return NULL; + } + if (options.window_size_seconds < 1 || options.window_size_seconds > 3600) { + LOG(ERROR) << "Invalid window_size_seconds=" << options.window_size_seconds + << ", must be in [1, 3600]"; + return NULL; + } + if (options.update_interval_seconds < 1) { + LOG(ERROR) << "Invalid update_interval_seconds=" + << options.update_interval_seconds << ", must be >= 1"; + return NULL; + } + if (options.update_interval_seconds > options.window_size_seconds) { + LOG(WARNING) << "update_interval_seconds=" << options.update_interval_seconds + << " exceeds window_size_seconds=" << options.window_size_seconds + << "; the ratio window will rarely refresh within its own period"; + } + // Plain new (without std::nothrow): brpc follows the project-wide convention + // of letting OOM throw/abort rather than returning NULL. NULL return from + // this factory already signals invalid parameters, not allocation failure. + return new RateLimitedBackupPolicy( + options.backup_request_ms, options.max_backup_ratio, + options.window_size_seconds, options.update_interval_seconds); +} + +} // namespace brpc diff --git a/src/brpc/backup_request_policy.h b/src/brpc/backup_request_policy.h new file mode 100644 index 0000000..13da2a5 --- /dev/null +++ b/src/brpc/backup_request_policy.h @@ -0,0 +1,80 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_BACKUP_REQUEST_POLICY_H +#define BRPC_BACKUP_REQUEST_POLICY_H + +#include "brpc/controller.h" + +namespace brpc { + +class BackupRequestPolicy { +public: + virtual ~BackupRequestPolicy() = default; + + // Return the time in milliseconds in which another request + // will be sent if RPC does not finish. + // Returning -1 means "inherit the backup_request_ms from ChannelOptions". + // Returning any other negative value disables backup for this RPC. + virtual int32_t GetBackupRequestMs(const Controller* controller) const = 0; + + // Return true if the backup request should be sent. + virtual bool DoBackup(const Controller* controller) const = 0; + + // Called when an RPC ends; user can collect call information to adjust policy. + virtual void OnRPCEnd(const Controller* controller) = 0; +}; + +// Options for CreateRateLimitedBackupPolicy(). +// All fields have defaults matching the recommended starting values. +struct RateLimitedBackupPolicyOptions { + // Time in milliseconds after which a backup request is sent if the RPC + // has not completed. + // Use -1 (the default) to inherit the value from ChannelOptions.backup_request_ms. + // Use >= 0 to override it explicitly. + // Default: -1 + int32_t backup_request_ms = -1; + + // Maximum ratio of backup requests to total requests in the sliding + // window. Must be in (0, 1]. + // Default: 0.1 + double max_backup_ratio = 0.1; + + // Width of the sliding time window in seconds. Must be in [1, 3600]. + // Default: 10 + int window_size_seconds = 10; + + // Interval in seconds between cached-ratio refreshes. Must be >= 1. + // Default: 5 + int update_interval_seconds = 5; +}; + +// Create a BackupRequestPolicy that limits the ratio of backup requests +// to total requests within a sliding time window. When the ratio reaches +// or exceeds options.max_backup_ratio, DoBackup() returns false. +// NOTE: Backup decisions are counted immediately at DoBackup() time for +// fast feedback. Total RPCs are counted on completion (OnRPCEnd). During +// latency spikes the ratio may temporarily lag until RPCs complete. +// Returns NULL on invalid parameters. +// The caller owns the returned pointer. +BackupRequestPolicy* CreateRateLimitedBackupPolicy( + const RateLimitedBackupPolicyOptions& options); + +} + +#endif // BRPC_BACKUP_REQUEST_POLICY_H diff --git a/src/brpc/baidu_master_service.cpp b/src/brpc/baidu_master_service.cpp new file mode 100644 index 0000000..b1b0fa0 --- /dev/null +++ b/src/brpc/baidu_master_service.cpp @@ -0,0 +1,52 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/baidu_master_service.h" +#include "butil/logging.h" + +namespace brpc { + +BaiduMasterService::BaiduMasterService() + : _status(new (std::nothrow) MethodStatus), _ignore_eovercrowded(false) { + LOG_IF(FATAL, NULL == _status) << "Fail to new MethodStatus"; +} + +BaiduMasterService::~BaiduMasterService() { + delete _status; + _status = NULL; +} + +void BaiduMasterService::Describe(std::ostream &os, + const DescribeOptions&) const { + os << butil::class_name_str(*this); +} + +void BaiduMasterService::Expose(const butil::StringPiece& prefix) { + if (NULL == _status) { + return; + } + std::string s; + const std::string& cached_name = butil::class_name_str(*this); + s.reserve(prefix.size() + 1 + cached_name.size()); + s.append(prefix.data(), prefix.size()); + s.push_back('_'); + s.append(cached_name); + _status->Expose(s); +} + +} \ No newline at end of file diff --git a/src/brpc/baidu_master_service.h b/src/brpc/baidu_master_service.h new file mode 100644 index 0000000..11846fd --- /dev/null +++ b/src/brpc/baidu_master_service.h @@ -0,0 +1,108 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_BAIDU_MASTER_SERVICE_H +#define BRPC_BAIDU_MASTER_SERVICE_H + +#include +#include +#include "brpc/details/method_status.h" +#include "brpc/proto_base.pb.h" +#include "brpc/controller.h" +#include "brpc/serialized_request.h" +#include "brpc/serialized_response.h" + +namespace brpc { + +namespace policy { +void ProcessRpcRequest(InputMessageBase* msg_base); +} + +class BaiduMasterService : public ::google::protobuf::Service + , public Describable { +public: + BaiduMasterService(); + ~BaiduMasterService() override; + + DISALLOW_COPY_AND_ASSIGN(BaiduMasterService); + + AdaptiveMaxConcurrency& MaxConcurrency() { + return _max_concurrency; + } + + bool ignore_eovercrowded() { + return _ignore_eovercrowded; + } + + void set_ignore_eovercrowded(bool ignore_eovercrowded) { + _ignore_eovercrowded = ignore_eovercrowded; + } + + virtual void ProcessRpcRequest(Controller* cntl, + const SerializedRequest* request, + SerializedResponse* response, + ::google::protobuf::Closure* done) = 0; + + + // Put descriptions into the stream. + void Describe(std::ostream &os, const DescribeOptions&) const override; + + // implements Service ---------------------------------------------- + + void CallMethod(const ::google::protobuf::MethodDescriptor*, + ::google::protobuf::RpcController* controller, + const ::google::protobuf::Message* request, + ::google::protobuf::Message* response, + ::google::protobuf::Closure* done) override { + ProcessRpcRequest((Controller*)controller, + (const SerializedRequest*)request, + (SerializedResponse*)response, done); + } + + const ::google::protobuf::ServiceDescriptor* GetDescriptor() override { + return BaiduMasterServiceBase::descriptor(); + } + + const ::google::protobuf::Message& GetRequestPrototype( + const ::google::protobuf::MethodDescriptor*) const override { + return _default_request; + } + + const ::google::protobuf::Message& GetResponsePrototype( + const ::google::protobuf::MethodDescriptor*) const override { + return _default_response; + } + +private: +friend void policy::ProcessRpcRequest(InputMessageBase* msg_base); +friend class StatusService; +friend class Server; + + void Expose(const butil::StringPiece& prefix); + + SerializedRequest _default_request; + SerializedResponse _default_response; + + MethodStatus* _status; + AdaptiveMaxConcurrency _max_concurrency; + bool _ignore_eovercrowded; +}; + +} + +#endif //BRPC_BAIDU_MASTER_SERVICE_H diff --git a/src/brpc/builtin/bad_method_service.cpp b/src/brpc/builtin/bad_method_service.cpp new file mode 100644 index 0000000..346b68d --- /dev/null +++ b/src/brpc/builtin/bad_method_service.cpp @@ -0,0 +1,67 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include + +#include "brpc/closure_guard.h" // ClosureGuard +#include "brpc/controller.h" // Controller +#include "brpc/builtin/common.h" +#include "brpc/server.h" +#include "brpc/errno.pb.h" +#include "brpc/builtin/bad_method_service.h" +#include "brpc/details/server_private_accessor.h" + + +namespace brpc { + +void BadMethodService::no_method(::google::protobuf::RpcController* cntl_base, + const BadMethodRequest* request, + BadMethodResponse*, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller *cntl = static_cast(cntl_base); + const Server* server = cntl->server(); + const bool use_html = UseHTML(cntl->http_request()); + const char* newline = (use_html ? "
\n" : "\n"); + cntl->http_response().set_content_type( + use_html ? "text/html" : "text/plain"); + + std::ostringstream os; + os << "Missing method name for service=" << request->service_name() << '.'; + const Server::ServiceProperty* sp = ServerPrivateAccessor(server) + .FindServicePropertyAdaptively(request->service_name()); + if (sp != NULL && sp->service != NULL) { + const google::protobuf::ServiceDescriptor* sd = + sp->service->GetDescriptor(); + os << " Available methods are: " << newline << newline; + for (int i = 0; i < sd->method_count(); ++i) { + os << "rpc " << sd->method(i)->name() + << " (" << sd->method(i)->input_type()->name() + << ") returns (" << sd->method(i)->output_type()->name() + << ");" << newline; + } + } + if (sp != NULL && sp->restful_map != NULL) { + os << " This path is associated with a RestfulMap!"; + } + cntl->SetFailed(ENOMETHOD, "%s", os.str().c_str()); +} + +} // namespace brpc diff --git a/src/brpc/builtin/bad_method_service.h b/src/brpc/builtin/bad_method_service.h new file mode 100644 index 0000000..a127f62 --- /dev/null +++ b/src/brpc/builtin/bad_method_service.h @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_BADMETHOD_SERVICE_H +#define BRPC_BADMETHOD_SERVICE_H + +#include "brpc/builtin_service.pb.h" + + +namespace brpc { + +class BadMethodService : public badmethod { +public: + void no_method(::google::protobuf::RpcController* cntl_base, + const ::brpc::BadMethodRequest* request, + ::brpc::BadMethodResponse* response, + ::google::protobuf::Closure* done); +}; + +} // namespace brpc + + + +#endif // BRPC_BADMETHOD_SERVICE_H diff --git a/src/brpc/builtin/bthreads_service.cpp b/src/brpc/builtin/bthreads_service.cpp new file mode 100644 index 0000000..fca86bc --- /dev/null +++ b/src/brpc/builtin/bthreads_service.cpp @@ -0,0 +1,75 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "bthread/bthread.h" +#include "brpc/closure_guard.h" // ClosureGuard +#include "brpc/controller.h" // Controller +#include "brpc/builtin/common.h" +#include "brpc/builtin/bthreads_service.h" + +namespace bthread { +extern void print_task(std::ostream& os, bthread_t tid, bool enable_trace, + bool ignore_not_matched = false); +extern void print_living_tasks(std::ostream& os, bool enable_trace); +} + + +namespace brpc { + +void BthreadsService::default_method(::google::protobuf::RpcController* cntl_base, + const ::brpc::BthreadsRequest*, + ::brpc::BthreadsResponse*, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller *cntl = static_cast(cntl_base); + cntl->http_response().set_content_type("text/plain"); + butil::IOBufBuilder os; + const std::string& constraint = cntl->http_request().unresolved_path(); + if (constraint.empty()) { +#ifdef BRPC_BTHREAD_TRACER + os << "Use /bthreads/ or /bthreads/?st=1 for stack trace\n"; + os << "To check all living bthread, use /bthreads/all or /bthreads/all?st=1 for stack trace\n"; +#else + os << "Use /bthreads/\n"; + os << "To check all living bthread, use /bthreads/all\n"; +#endif // BRPC_BTHREAD_TRACER + } else { + bool enable_trace = false; +#ifdef BRPC_BTHREAD_TRACER + const std::string* st = cntl->http_request().uri().GetQuery("st"); + if (NULL != st && *st == "1") { + enable_trace = true; + } +#endif // BRPC_BTHREAD_TRACER + char* endptr = NULL; + bthread_t tid = strtoull(constraint.c_str(), &endptr, 10); + if (*endptr == '\0' || *endptr == '/' || *endptr == '?') { + ::bthread::print_task(os, tid, enable_trace); + } + else if (constraint != "all" && constraint != "all?st=1") { + cntl->SetFailed(ENOMETHOD, "path=%s is not a bthread id or all, or all?st=1\n", + constraint.c_str()); + } else { + ::bthread::print_living_tasks(os, enable_trace); + } + + } + os.move_to(cntl->response_attachment()); +} + +} // namespace brpc diff --git a/src/brpc/builtin/bthreads_service.h b/src/brpc/builtin/bthreads_service.h new file mode 100644 index 0000000..813a3a0 --- /dev/null +++ b/src/brpc/builtin/bthreads_service.h @@ -0,0 +1,38 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_BTHREADS_SERVICE_H +#define BRPC_BTHREADS_SERVICE_H + +#include "brpc/builtin_service.pb.h" + + +namespace brpc { + +class BthreadsService : public bthreads { +public: + void default_method(::google::protobuf::RpcController* cntl_base, + const ::brpc::BthreadsRequest* request, + ::brpc::BthreadsResponse* response, + ::google::protobuf::Closure* done); +}; + +} // namespace brpc + + +#endif // BRPC_BTHREADS_SERVICE_H diff --git a/src/brpc/builtin/common.cpp b/src/brpc/builtin/common.cpp new file mode 100644 index 0000000..8663682 --- /dev/null +++ b/src/brpc/builtin/common.cpp @@ -0,0 +1,399 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include // O_RDONLY +#include +#include "butil/logging.h" +#include "butil/fd_guard.h" // fd_guard +#include "butil/file_util.h" // butil::FilePath +#include "butil/third_party/murmurhash3/murmurhash3.h" +#include "butil/process_util.h" // ReadCommandLine +#include "brpc/server.h" +#include "brpc/builtin/common.h" + +namespace brpc { + +DEFINE_string(rpc_profiling_dir, "./rpc_data/profiling", + "For storing profiling results."); + +bool UseHTML(const HttpHeader& header) { + const std::string* console = header.uri().GetQuery(CONSOLE_STR); + if (console != NULL) { + return atoi(console->c_str()) == 0; + } + // [curl header] + // User-Agent: curl/7.12.1 (x86_64-redhat-linux-gnu) libcurl/7.12.1 ... + const std::string* agent = header.GetHeader(USER_AGENT_STR); + if (agent == NULL) { // use text when user-agent is absent + return false; + } + return agent->find("curl/") == std::string::npos; +} + +// Written by Jack Handy +// jakkhandy@hotmail.com +inline bool url_wildcmp(const char* wild, const char* str) { + const char* cp = NULL; + const char* mp = NULL; + + while (*str && *wild != '*') { + if (*wild != *str && *wild != '$') { + return false; + } + ++wild; + ++str; + } + + while (*str) { + if (*wild == '*') { + if (!*++wild) { + return true; + } + mp = wild; + cp = str+1; + } else if (*wild == *str || *wild == '$') { + ++wild; + ++str; + } else { + wild = mp; + str = cp++; + } + } + + while (*wild == '*') { + ++wild; + } + return !*wild; +} + +bool MatchAnyWildcard(const std::string& name, + const std::vector& wildcards) { + for (size_t i = 0; i < wildcards.size(); ++i) { + if (url_wildcmp(wildcards[i].c_str(), name.c_str())) { + return true; + } + } + return false; +} + +void PrintRealDateTime(std::ostream& os, int64_t tm) { + char buf[32]; + const time_t tm_s = tm / 1000000L; + struct tm lt; + strftime(buf, sizeof(buf), "%Y/%m/%d-%H:%M:%S.", localtime_r(&tm_s, <)); + const char old_fill = os.fill('0'); + os << buf << std::setw(6) << tm % 1000000L; + os.fill(old_fill); +} + +void PrintRealDateTime(std::ostream& os, int64_t tm, + bool ignore_microseconds) { + char buf[32]; + const time_t tm_s = tm / 1000000L; + struct tm lt; + strftime(buf, sizeof(buf), "%Y/%m/%d-%H:%M:%S", localtime_r(&tm_s, <)); + if (ignore_microseconds) { + os << buf; + } else { + const char old_fill = os.fill('0'); + os << buf << '.' << std::setw(6) << tm % 1000000L; + os.fill(old_fill); + } +} + +std::ostream& operator<<(std::ostream& os, const PrintedAsDateTime& d) { + PrintRealDateTime(os, d.realtime); + return os; +} + +std::ostream& operator<<(std::ostream& os, const Path& link) { + if (link.html_addr) { + if (link.html_addr != Path::LOCAL) { + os << ""; + } else { + os << ""; + } + } + if (link.text) { + os << link.text; + } else { + os << link.uri; + } + if (link.html_addr) { + os << ""; + } + return os; +} + +const butil::EndPoint *Path::LOCAL = (butil::EndPoint *)0x01; + +void AppendFileName(std::string* dir, const std::string& filename) { + if (dir->empty()) { + dir->append(filename); + return; + } + const size_t len = filename.size(); + if (len >= 3) { + if (butil::back_char(*dir) != '/') { + dir->push_back('/'); + } + dir->append(filename); + } else if (len == 1) { + if (filename[0] != '.') { + if (butil::back_char(*dir) != '/') { + dir->push_back('/'); + } + dir->append(filename); + } + } else if (len == 2) { + if (filename[0] != '.' || filename[1] != '.') { + if (butil::back_char(*dir) != '/') { + dir->push_back('/'); + } + dir->append(filename); + } else { + const bool is_abs = (dir->c_str()[0] == '/'); + int npop = 1; + while (npop > 0) { + const char* p = dir->c_str() + dir->size() - 1; + for (; p != dir->c_str() && *p == '/'; --p); + if (p == dir->c_str()) { + dir->clear(); + break; + } + dir->resize(p - dir->c_str() + 1); + + size_t slash_pos = dir->find_last_of('/'); + if (slash_pos == std::string::npos) { + --npop; + dir->clear(); + break; + } + if (strcmp(dir->data() + slash_pos + 1, ".") != 0) { + if (strcmp(dir->data() + slash_pos + 1, "..") == 0) { + ++npop; + } else { + --npop; + } + } + ssize_t new_pos = (ssize_t)slash_pos - 1; + for (; new_pos >= 0 && (*dir)[new_pos] == '/'; --new_pos); + dir->resize(new_pos + 1); + if (dir->empty()) { + break; + } + } + if (dir->empty()) { + if (is_abs) { + dir->push_back('/'); + } else { + if (npop > 0) { + dir->append(".."); + for (int i = 1; i < npop; ++i) { + dir->append("/.."); + } + } + } + } + } + } // else len == 0, nothing to do +} + +const char* gridtable_style() { + return + "\n"; +} + +const char* TabsHead() { + return + "\n" + "\n"; +} + +const char* logo() { + return + " __\n" + " / /_ _________ _____\n" + " / __ \\/ ___/ __ \\/ ___/\n" + " / /_/ / / / /_/ / /__\n" + "/_.___/_/ / .___/\\___/\n" + " /_/\n"; +} + +const char* ProfilingType2String(ProfilingType t) { + switch (t) { + case PROFILING_CPU: return "cpu"; + case PROFILING_HEAP: return "heap"; + case PROFILING_GROWTH: return "growth"; + case PROFILING_CONTENTION: return "contention"; + case PROFILING_IOBUF: return "iobuf"; + } + return "unknown"; +} + +int FileChecksum(const char* file_path, unsigned char* checksum) { + butil::fd_guard fd(open(file_path, O_RDONLY)); + if (fd < 0) { + PLOG(ERROR) << "Fail to open `" << file_path << "'"; + return -1; + } + char block[16*1024]; // 16k each time + ssize_t size = 0L; + butil::MurmurHash3_x64_128_Context mm_ctx; + butil::MurmurHash3_x64_128_Init(&mm_ctx, 0); + while ((size = read(fd, block, sizeof(block))) > 0) { + butil::MurmurHash3_x64_128_Update(&mm_ctx, block, size); + } + butil::MurmurHash3_x64_128_Final(checksum, &mm_ctx); + return 0; +} + +static pthread_once_t create_program_name_once = PTHREAD_ONCE_INIT; +static const char* s_program_name = "unknown"; +static char s_cmdline[256]; +static void CreateProgramName() { + const ssize_t nr = butil::ReadCommandLine(s_cmdline, sizeof(s_cmdline) - 1, false); + if (nr > 0) { + s_cmdline[nr] = '\0'; + s_program_name = s_cmdline; + } +} + +const char* GetProgramName() { + pthread_once(&create_program_name_once, CreateProgramName); + return s_program_name; +} + +static pthread_once_t create_program_path_once = PTHREAD_ONCE_INIT; +static const char* s_program_path = "unknown"; +static char s_program_exec_path[PATH_MAX]; +static void CreateProgramPath() { + const ssize_t nr = butil::GetProcessAbsolutePath(s_program_exec_path, sizeof(s_program_exec_path) - 1); + if (nr > 0) { + s_program_exec_path[nr] = '\0'; + s_program_path = s_program_exec_path; + } +} + +const char* GetProgramPath() { + pthread_once(&create_program_path_once, CreateProgramPath); + return s_program_path; +} + + +static pthread_once_t compute_program_checksum_once = PTHREAD_ONCE_INIT; +static char s_program_checksum[33]; +static const char s_alphabet[] = "0123456789abcdef"; +static void ComputeProgramCHECKSUM() { + unsigned char checksum[16]; + FileChecksum(GetProgramPath(), checksum); + for (size_t i = 0, j = 0; i < 16; ++i, j+=2) { + s_program_checksum[j] = s_alphabet[checksum[i] >> 4]; + s_program_checksum[j+1] = s_alphabet[checksum[i] & 0xF]; + } + s_program_checksum[32] = '\0'; + +} +const char* GetProgramChecksum() { + pthread_once(&compute_program_checksum_once, ComputeProgramCHECKSUM); + return s_program_checksum; +} + +bool SupportGzip(Controller* cntl) { + const std::string* encodings = + cntl->http_request().GetHeader("Accept-Encoding"); + if (encodings == NULL) { + return false; + } + return encodings->find("gzip") != std::string::npos; +} + +void Time2GMT(time_t t, char* buf, size_t size) { + struct tm tm; + gmtime_r(&t, &tm); + strftime(buf, size, "%a, %d %b %Y %H:%M:%S %Z", &tm); +} + +} // namespace brpc diff --git a/src/brpc/builtin/common.h b/src/brpc/builtin/common.h new file mode 100644 index 0000000..0c1af84 --- /dev/null +++ b/src/brpc/builtin/common.h @@ -0,0 +1,150 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_BUILTIN_COMMON_H +#define BRPC_BUILTIN_COMMON_H + +#include // std::vector +#include +#include "butil/endpoint.h" +#include "brpc/http_header.h" + + +namespace brpc { + +class Controller; + +// These static strings are referenced more than once in brpc. +// Don't turn them to std::strings whose constructing sequences are undefined. +const char* const UNKNOWN_METHOD_STR = "unknown_method"; +const char* const TRACE_ID_STR = "trace"; +const char* const SPAN_ID_STR = "span"; +const char* const TIME_STR = "time"; +const char* const MAX_SCAN_STR = "max_scan"; +const char* const MIN_LATENCY_STR = "min_latency"; +const char* const MIN_REQUEST_SIZE_STR = "min_request_size"; +const char* const MIN_RESPONSE_SIZE_STR = "min_response_size"; +const char* const LOG_ID_STR = "log_id"; +const char* const ERROR_CODE_STR = "error_code"; +const char* const CONSOLE_STR = "console"; +const char* const USER_AGENT_STR = "user-agent"; +const char* const SETVALUE_STR = "setvalue"; + +const size_t MAX_READ = 1024 * 1024; + +enum ProfilingType { + PROFILING_CPU = 0, + PROFILING_HEAP = 1, + PROFILING_GROWTH = 2, + PROFILING_CONTENTION = 3, + PROFILING_IOBUF = 4, +}; + +DECLARE_string(rpc_profiling_dir); + +bool UseHTML(const HttpHeader& header); +bool MatchAnyWildcard(const std::string& name, + const std::vector& wildcards); + +void PrintRealDateTime(std::ostream& os, int64_t tm); +void PrintRealDateTime(std::ostream& os, int64_t tm, bool ignore_microseconds); + +struct PrintedAsDateTime { + PrintedAsDateTime(int64_t realtime2) : realtime(realtime2) {} + int64_t realtime; +}; +std::ostream& operator<<(std::ostream& os, const PrintedAsDateTime&); + +struct Path { + static const butil::EndPoint *LOCAL; + Path(const char* uri2, const butil::EndPoint* html_addr2) + : uri(uri2), html_addr(html_addr2), text(NULL) {} + + Path(const char* uri2, const butil::EndPoint* html_addr2, const char* text2) + : uri(uri2), html_addr(html_addr2), text(text2) {} + + const char* uri; + const butil::EndPoint* html_addr; + const char* text; +}; +std::ostream& operator<<(std::ostream& os, const Path& link); + +// Append `filename' to `dir' according to unix directory rules: +// "foo/bar" + ".." -> "foo" +// "foo/bar/." + ".." -> "foo" +// "foo" + "." -> "foo" +// "foo/" + ".." -> "" +// "foo/../" + ".." -> ".." +// "/foo/../" + ".." -> "/" +// "foo/./" + ".." -> "" +void AppendFileName(std::string* dir, const std::string& filename); + +// style of class=gridtable, wrapped with \n" + "\n" + "\n"; + cntl->server()->PrintTabsBody(os, ProfilingType2String(type)); + } + + const std::string* view = cntl->http_request().uri().GetQuery("view"); + if (view) { + if (!ValidProfilePath(*view)) { + return cntl->SetFailed(EINVAL, "Invalid query `view'"); + } + if (!butil::PathExists(butil::FilePath(*view))) { + return cntl->SetFailed( + EINVAL, "The profile denoted by `view' does not exist"); + } + DisplayResult(cntl, done_guard.release(), view->c_str(), os.buf(), type); + return; + } + + const int seconds = ReadSeconds(cntl); + if ((type == PROFILING_CPU || type == PROFILING_CONTENTION)) { + if (seconds < 0) { + os << "Invalid seconds" << (use_html ? "" : "\n"); + os.move_to(cntl->response_attachment()); + cntl->http_response().set_status_code(HTTP_STATUS_BAD_REQUEST); + return; + } + } + + // Log requester + std::ostringstream client_info; + client_info << cntl->remote_side(); + if (cntl->auth_context()) { + client_info << "(auth=" << cntl->auth_context()->user() << ')'; + } else { + client_info << "(no auth)"; + } + client_info << " requests for profiling " << ProfilingType2String(type); + if (type == PROFILING_CPU || type == PROFILING_CONTENTION) { + LOG(INFO) << client_info.str() << " for " << seconds << " seconds"; + } else { + LOG(INFO) << client_info.str(); + } + int64_t prof_id = 0; + const std::string* prof_id_str = + cntl->http_request().uri().GetQuery("profiling_id"); + if (prof_id_str != NULL) { + char* endptr = NULL; + prof_id = strtoll(prof_id_str->c_str(), &endptr, 10); + LOG_IF(ERROR, *endptr != '\0') << "Invalid profiling_id=" << prof_id; + } + + { + BAIDU_SCOPED_LOCK(g_env[type].mutex); + if (g_env[type].client) { + if (NULL == g_env[type].waiters) { + g_env[type].waiters = new std::vector; + } + ProfilingWaiter waiter = { cntl, done_guard.release() }; + g_env[type].waiters->push_back(waiter); + RPC_VLOG << "Queue request from " << cntl->remote_side(); + return; + } + if (g_env[type].cached_result != NULL && + g_env[type].cached_result->id == prof_id) { + cntl->http_response().set_status_code( + g_env[type].cached_result->status_code); + cntl->response_attachment().append( + g_env[type].cached_result->result); + RPC_VLOG << "Hit cached result, id=" << prof_id; + return; + } + CHECK(NULL == g_env[type].client); + g_env[type].client = new ProfilingClient; + g_env[type].client->end_us = butil::cpuwide_time_us() + seconds * 1000000L; + g_env[type].client->seconds = seconds; + // This id work arounds an issue of chrome (or jquery under chrome) that + // the ajax call in another tab may be delayed until ajax call in + // current tab finishes. We assign a increasing-only id to each + // profiling and save last profiling result along with the assigned id. + // If the delay happens, the viewr should send the ajax call with an + // id matching the id in cached result, then the result will be returned + // directly instead of running another profiling which may take long + // time. + if (0 == ++ g_env[type].cur_id) { // skip 0 + ++ g_env[type].cur_id; + } + g_env[type].client->id = g_env[type].cur_id; + g_env[type].client->point = cntl->remote_side(); + } + + RPC_VLOG << "Apply request from " << cntl->remote_side(); + + char prof_name[128]; + if (MakeProfName(type, prof_name, sizeof(prof_name)) != 0) { + os << "Fail to create prof name: " << berror() + << (use_html ? "" : "\n"); + os.move_to(resp); + cntl->http_response().set_status_code(HTTP_STATUS_INTERNAL_SERVER_ERROR); + return NotifyWaiters(type, cntl, view); + } + +#if defined(OS_MACOSX) + if (!has_GOOGLE_PPROF_BINARY_PATH()) { + os << "no GOOGLE_PPROF_BINARY_PATH in env" + << (use_html ? "" : "\n"); + os.move_to(resp); + cntl->http_response().set_status_code(HTTP_STATUS_FORBIDDEN); + return NotifyWaiters(type, cntl, view); + } +#endif + if (type == PROFILING_CPU) { + if ((void*)ProfilerStart == NULL || (void*)ProfilerStop == NULL) { + os << "CPU profiler is not enabled" + << (use_html ? "" : "\n"); + os.move_to(resp); + cntl->http_response().set_status_code(HTTP_STATUS_FORBIDDEN); + return NotifyWaiters(type, cntl, view); + } + butil::File::Error error; + const butil::FilePath dir = butil::FilePath(prof_name).DirName(); + if (!butil::CreateDirectoryAndGetError(dir, &error)) { + os << "Fail to create directory=`" << dir.value() << ", " + << error << (use_html ? "" : "\n"); + os.move_to(resp); + cntl->http_response().set_status_code( + HTTP_STATUS_INTERNAL_SERVER_ERROR); + return NotifyWaiters(type, cntl, view); + } + if (!ProfilerStart(prof_name)) { + os << "Another profiler (not via /hotspots/cpu) is running, " + "try again later" << (use_html ? "" : "\n"); + os.move_to(resp); + cntl->http_response().set_status_code(HTTP_STATUS_SERVICE_UNAVAILABLE); + return NotifyWaiters(type, cntl, view); + } + if (bthread_usleep(seconds * 1000000L) != 0) { + PLOG(WARNING) << "Profiling has been interrupted"; + } + ProfilerStop(); + } else if (type == PROFILING_CONTENTION) { + if (!bthread::ContentionProfilerStart(prof_name)) { + os << "Another profiler (not via /hotspots/contention) is running, " + "try again later" << (use_html ? "" : "\n"); + os.move_to(resp); + cntl->http_response().set_status_code(HTTP_STATUS_SERVICE_UNAVAILABLE); + return NotifyWaiters(type, cntl, view); + } + if (bthread_usleep(seconds * 1000000L) != 0) { + PLOG(WARNING) << "Profiling has been interrupted"; + } + bthread::ContentionProfilerStop(); + } else if (type == PROFILING_IOBUF) { + if (!butil::IsIOBufProfilerEnabled()) { + os << "IOBuf profiler is not enabled" + << (use_html ? "" : "\n"); + os.move_to(resp); + cntl->http_response().set_status_code(HTTP_STATUS_FORBIDDEN); + return NotifyWaiters(type, cntl, view); + } + butil::IOBufProfilerFlush(prof_name); + } else if (type == PROFILING_HEAP) { + MallocExtension* malloc_ext = MallocExtension::instance(); + if (malloc_ext == NULL || !has_TCMALLOC_SAMPLE_PARAMETER()) { + os << "Heap profiler is not enabled"; + if (malloc_ext != NULL) { + os << " (no TCMALLOC_SAMPLE_PARAMETER in env)"; + } + os << '.' << (use_html ? "" : "\n"); + os.move_to(resp); + cntl->http_response().set_status_code(HTTP_STATUS_FORBIDDEN); + return NotifyWaiters(type, cntl, view); + } + std::string obj; + malloc_ext->GetHeapSample(&obj); + if (!WriteSmallFile(prof_name, obj)) { + os << "Fail to write " << prof_name + << (use_html ? "" : "\n"); + os.move_to(resp); + cntl->http_response().set_status_code( + HTTP_STATUS_INTERNAL_SERVER_ERROR); + return NotifyWaiters(type, cntl, view); + } + } else if (type == PROFILING_GROWTH) { + MallocExtension* malloc_ext = MallocExtension::instance(); + if (malloc_ext == NULL) { + os << "Growth profiler is not enabled." + << (use_html ? "" : "\n"); + os.move_to(resp); + cntl->http_response().set_status_code(HTTP_STATUS_FORBIDDEN); + return NotifyWaiters(type, cntl, view); + } + std::string obj; + malloc_ext->GetHeapGrowthStacks(&obj); + if (!WriteSmallFile(prof_name, obj)) { + os << "Fail to write " << prof_name + << (use_html ? "" : "\n"); + os.move_to(resp); + cntl->http_response().set_status_code( + HTTP_STATUS_INTERNAL_SERVER_ERROR); + return NotifyWaiters(type, cntl, view); + } + } else { + os << "Unknown ProfilingType=" << type + << (use_html ? "" : "\n"); + os.move_to(resp); + cntl->http_response().set_status_code( + HTTP_STATUS_INTERNAL_SERVER_ERROR); + return NotifyWaiters(type, cntl, view); + } + + std::vector waiters; + // NOTE: Must be called before DisplayResult which calls done->Run() and + // deletes cntl. + ConsumeWaiters(type, cntl, &waiters); + DisplayResult(cntl, done_guard.release(), prof_name, os.buf(), type); + + for (size_t i = 0; i < waiters.size(); ++i) { + DisplayResult(waiters[i].cntl, waiters[i].done, prof_name, os.buf(), type); + } +} + +static void StartProfiling(ProfilingType type, + ::google::protobuf::RpcController* cntl_base, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller *cntl = static_cast(cntl_base); + butil::IOBuf& resp = cntl->response_attachment(); + const bool use_html = UseHTML(cntl->http_request()); + butil::IOBufBuilder os; + bool enabled = false; + const char* extra_desc = ""; + if (type == PROFILING_CPU) { + enabled = cpu_profiler_enabled; + } else if (type == PROFILING_CONTENTION) { + enabled = true; + } else if (type == PROFILING_IOBUF) { + enabled = butil::IsIOBufProfilerEnabled(); + if (!enabled) { + extra_desc = " (no ENABLE_IOBUF_PROFILER=1 in env or no link tcmalloc )"; + } + } else if (type == PROFILING_HEAP) { + enabled = IsHeapProfilerEnabled(); + if (enabled && !has_TCMALLOC_SAMPLE_PARAMETER()) { + enabled = false; + extra_desc = " (no TCMALLOC_SAMPLE_PARAMETER in env)"; + } + } else if (type == PROFILING_GROWTH) { + enabled = IsHeapProfilerEnabled(); + } + const char* const type_str = ProfilingType2String(type); + +#if defined(OS_MACOSX) + if (!has_GOOGLE_PPROF_BINARY_PATH()) { + enabled = false; + extra_desc = "(no GOOGLE_PPROF_BINARY_PATH in env)"; + } +#endif + + if (!use_html) { + if (!enabled) { + os << "Error: " << type_str << " profiler is not enabled." + << extra_desc << "\n" + "Read the docs: docs/cn/{cpu_profiler.md,heap_profiler.md}\n"; + os.move_to(cntl->response_attachment()); + cntl->http_response().set_status_code(HTTP_STATUS_FORBIDDEN); + return; + } + // Console can only use non-responsive version, namely the curl + // blocks until profiling is done. + return DoProfiling(type, cntl, done_guard.release()); + } + + const int seconds = ReadSeconds(cntl); + const std::string* view = cntl->http_request().uri().GetQuery("view"); + const bool show_ccount = cntl->http_request().uri().GetQuery("ccount"); + const std::string* base_name = cntl->http_request().uri().GetQuery("base"); + const std::string* display_type_query = cntl->http_request().uri().GetQuery("display_type"); + DisplayType display_type = DisplayType::kDot; + if (display_type_query) { + display_type = StringToDisplayType(*display_type_query); + if (display_type == DisplayType::kUnknown) { + return cntl->SetFailed(EINVAL, "Invalid display_type=%s", display_type_query->c_str()); + } +#if defined(OS_LINUX) + const char* flamegraph_tool = getenv("FLAMEGRAPH_PL_PATH"); + if (display_type == DisplayType::kFlameGraph && !flamegraph_tool) { + return cntl->SetFailed(EINVAL, "Failed to find environment variable " + "FLAMEGRAPH_PL_PATH, please read cpu_profiler doc" + "(https://github.com/apache/brpc/blob/master/docs/cn/cpu_profiler.md)"); + } +#endif + } + + ProfilingClient profiling_client; + size_t nwaiters = 0; + ProfilingEnvironment & env = g_env[type]; + if (view == NULL) { + BAIDU_SCOPED_LOCK(env.mutex); + if (env.client) { + profiling_client = *env.client; + nwaiters = (env.waiters ? env.waiters->size() : 0); + } + } + + cntl->http_response().set_content_type("text/html"); + os << "\n" + "\n" + << TabsHead() + << "\n" + "\n" + "\n" + "\n"; + cntl->server()->PrintTabsBody(os, type_str); + + TRACEPRINTF("Begin to enumerate profiles"); + std::vector past_profs; + butil::FilePath prof_dir(FLAGS_rpc_profiling_dir); + prof_dir = prof_dir.Append(GetProgramChecksum()); + std::string file_pattern; + file_pattern.reserve(15); + file_pattern.append("*."); + file_pattern.append(type_str); + butil::FileEnumerator prof_enum(prof_dir, false/*non recursive*/, + butil::FileEnumerator::FILES, + file_pattern); + std::string file_path; + for (butil::FilePath name = prof_enum.Next(); !name.empty(); + name = prof_enum.Next()) { + // NOTE: name already includes dir. + if (past_profs.empty()) { + past_profs.reserve(16); + } + past_profs.push_back(name.value()); + } + if (!past_profs.empty()) { + TRACEPRINTF("Sort %lu profiles in decending order", past_profs.size()); + std::sort(past_profs.begin(), past_profs.end(), std::greater()); + int max_profiles = FLAGS_max_profiles_kept/*may be reloaded*/; + if (max_profiles < 0) { + max_profiles = 0; + } + if (past_profs.size() > (size_t)max_profiles) { + TRACEPRINTF("Remove %lu profiles", + past_profs.size() - (size_t)max_profiles); + for (size_t i = max_profiles; i < past_profs.size(); ++i) { + CHECK(butil::DeleteFile(butil::FilePath(past_profs[i]), false)); + std::string cache_path; + cache_path.reserve(past_profs[i].size() + 7); + cache_path += past_profs[i]; + cache_path += ".cache"; + CHECK(butil::DeleteFile(butil::FilePath(cache_path), true)); + } + past_profs.resize(max_profiles); + } + } + TRACEPRINTF("End enumeration"); + + os << "
View: 
" + ""; + os << "
Display: 
" + ""; + if (type == PROFILING_CONTENTION) { + os << "   "; + } + if (type != PROFILING_IOBUF) { + os << "
Diff: 
" + "
"; + } + + if (!enabled && view == NULL) { + os << "

Error: " + << type_str << " profiler is not enabled." << extra_desc << "

" + "

To enable all profilers, link tcmalloc and define macros BRPC_ENABLE_CPU_PROFILER" + "

Or read docs: cpu_profiler" + " and heap_profiler" + "

"; + os.move_to(cntl->response_attachment()); + cntl->http_response().set_status_code(HTTP_STATUS_FORBIDDEN); + return; + } + + if ((type == PROFILING_CPU || type == PROFILING_CONTENTION) && view == NULL) { + if (seconds < 0) { + os << "Invalid seconds"; + os.move_to(cntl->response_attachment()); + cntl->http_response().set_status_code(HTTP_STATUS_BAD_REQUEST); + return; + } + } + + if (nwaiters >= CONCURRENT_PROFILING_LIMIT) { + os << "Your profiling request is rejected because of " + "too many concurrent profiling requests"; + os.move_to(cntl->response_attachment()); + cntl->http_response().set_status_code(HTTP_STATUS_SERVICE_UNAVAILABLE); + return; + } + + os << "
"; + if (profiling_client.seconds != 0) { + const int wait_seconds = + (int)ceil((profiling_client.end_us - butil::cpuwide_time_us()) + / 1000000.0); + os << "Your request is merged with the request from " + << profiling_client.point; + if (type == PROFILING_CPU || type == PROFILING_CONTENTION) { + os << ", showing in about " << wait_seconds << " seconds ..."; + } + } else { + if ((type == PROFILING_CPU || type == PROFILING_CONTENTION) && view == NULL) { + os << "Profiling " << ProfilingType2String(type) << " for " + << seconds << " seconds ..."; + } else { + os << "Generating " << type_str << " profile ..."; + } + } + os << "
\n"; + if (display_type == DisplayType::kDot) { + // don't need viz.js in text mode. + os << "\n"; + } + os << ""; + os.move_to(resp); +} + +void HotspotsService::cpu( + ::google::protobuf::RpcController* cntl_base, + const ::brpc::HotspotsRequest*, + ::brpc::HotspotsResponse*, + ::google::protobuf::Closure* done) { + return StartProfiling(PROFILING_CPU, cntl_base, done); +} + +void HotspotsService::heap( + ::google::protobuf::RpcController* cntl_base, + const ::brpc::HotspotsRequest*, + ::brpc::HotspotsResponse*, + ::google::protobuf::Closure* done) { + return StartProfiling(PROFILING_HEAP, cntl_base, done); +} + +void HotspotsService::growth( + ::google::protobuf::RpcController* cntl_base, + const ::brpc::HotspotsRequest*, + ::brpc::HotspotsResponse*, + ::google::protobuf::Closure* done) { + return StartProfiling(PROFILING_GROWTH, cntl_base, done); +} + +void HotspotsService::contention( + ::google::protobuf::RpcController* cntl_base, + const ::brpc::HotspotsRequest*, + ::brpc::HotspotsResponse*, + ::google::protobuf::Closure* done) { + return StartProfiling(PROFILING_CONTENTION, cntl_base, done); +} + +void HotspotsService::iobuf(::google::protobuf::RpcController* cntl_base, + const ::brpc::HotspotsRequest* request, + ::brpc::HotspotsResponse* response, + ::google::protobuf::Closure* done) { + return StartProfiling(PROFILING_IOBUF, cntl_base, done); +} + + +void HotspotsService::cpu_non_responsive( + ::google::protobuf::RpcController* cntl_base, + const ::brpc::HotspotsRequest*, + ::brpc::HotspotsResponse*, + ::google::protobuf::Closure* done) { + return DoProfiling(PROFILING_CPU, cntl_base, done); +} + +void HotspotsService::heap_non_responsive( + ::google::protobuf::RpcController* cntl_base, + const ::brpc::HotspotsRequest*, + ::brpc::HotspotsResponse*, + ::google::protobuf::Closure* done) { + return DoProfiling(PROFILING_HEAP, cntl_base, done); +} + +void HotspotsService::growth_non_responsive( + ::google::protobuf::RpcController* cntl_base, + const ::brpc::HotspotsRequest*, + ::brpc::HotspotsResponse*, + ::google::protobuf::Closure* done) { + return DoProfiling(PROFILING_GROWTH, cntl_base, done); +} + +void HotspotsService::contention_non_responsive( + ::google::protobuf::RpcController* cntl_base, + const ::brpc::HotspotsRequest*, + ::brpc::HotspotsResponse*, + ::google::protobuf::Closure* done) { + return DoProfiling(PROFILING_CONTENTION, cntl_base, done); +} + +void HotspotsService::iobuf_non_responsive(::google::protobuf::RpcController* cntl_base, + const ::brpc::HotspotsRequest* request, + ::brpc::HotspotsResponse* response, + ::google::protobuf::Closure* done) { + return DoProfiling(PROFILING_IOBUF, cntl_base, done); +} + +void HotspotsService::GetTabInfo(TabInfoList* info_list) const { + TabInfo* info = info_list->add(); + info->path = "/hotspots/cpu"; + info->tab_name = "cpu"; + + info = info_list->add(); + info->path = "/hotspots/heap"; + info->tab_name = "heap"; + + info = info_list->add(); + info->path = "/hotspots/growth"; + info->tab_name = "growth"; + + info = info_list->add(); + info->path = "/hotspots/contention"; + info->tab_name = "contention"; + + info = info_list->add(); + info->path = "/hotspots/iobuf"; + info->tab_name = "iobuf"; +} + +} // namespace brpc diff --git a/src/brpc/builtin/hotspots_service.h b/src/brpc/builtin/hotspots_service.h new file mode 100644 index 0000000..cdd90b6 --- /dev/null +++ b/src/brpc/builtin/hotspots_service.h @@ -0,0 +1,90 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_HOTSPOTS_SERVICE_H +#define BRPC_HOTSPOTS_SERVICE_H + +#include "brpc/builtin/common.h" +#include "brpc/builtin_service.pb.h" +#include "brpc/builtin/tabbed.h" + + +namespace brpc { + +class Server; + +class HotspotsService : public hotspots, public Tabbed { +public: + void cpu(::google::protobuf::RpcController* cntl_base, + const ::brpc::HotspotsRequest* request, + ::brpc::HotspotsResponse* response, + ::google::protobuf::Closure* done); + + void heap(::google::protobuf::RpcController* cntl_base, + const ::brpc::HotspotsRequest* request, + ::brpc::HotspotsResponse* response, + ::google::protobuf::Closure* done); + + void growth(::google::protobuf::RpcController* cntl_base, + const ::brpc::HotspotsRequest* request, + ::brpc::HotspotsResponse* response, + ::google::protobuf::Closure* done); + + void contention(::google::protobuf::RpcController* cntl_base, + const ::brpc::HotspotsRequest* request, + ::brpc::HotspotsResponse* response, + ::google::protobuf::Closure* done); + + void iobuf(::google::protobuf::RpcController* cntl_base, + const ::brpc::HotspotsRequest* request, + ::brpc::HotspotsResponse* response, + ::google::protobuf::Closure* done); + + void cpu_non_responsive(::google::protobuf::RpcController* cntl_base, + const ::brpc::HotspotsRequest* request, + ::brpc::HotspotsResponse* response, + ::google::protobuf::Closure* done); + + void heap_non_responsive(::google::protobuf::RpcController* cntl_base, + const ::brpc::HotspotsRequest* request, + ::brpc::HotspotsResponse* response, + ::google::protobuf::Closure* done); + + void growth_non_responsive(::google::protobuf::RpcController* cntl_base, + const ::brpc::HotspotsRequest* request, + ::brpc::HotspotsResponse* response, + ::google::protobuf::Closure* done); + + void contention_non_responsive(::google::protobuf::RpcController* cntl_base, + const ::brpc::HotspotsRequest* request, + ::brpc::HotspotsResponse* response, + ::google::protobuf::Closure* done); + + void iobuf_non_responsive(::google::protobuf::RpcController* cntl_base, + const ::brpc::HotspotsRequest* request, + ::brpc::HotspotsResponse* response, + ::google::protobuf::Closure* done); + + void GetTabInfo(brpc::TabInfoList*) const; +}; + +} // namespace brpc + + + +#endif // BRPC_HOTSPOTS_SERVICE_H diff --git a/src/brpc/builtin/ids_service.cpp b/src/brpc/builtin/ids_service.cpp new file mode 100644 index 0000000..ba6a25f --- /dev/null +++ b/src/brpc/builtin/ids_service.cpp @@ -0,0 +1,60 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "brpc/closure_guard.h" // ClosureGuard +#include "brpc/controller.h" // Controller +#include "brpc/builtin/common.h" +#include "brpc/builtin/ids_service.h" + +namespace bthread { +void id_status(bthread_id_t id, std::ostream& os); +void id_pool_status(std::ostream& os); +} + + +namespace brpc { + +void IdsService::default_method(::google::protobuf::RpcController* cntl_base, + const ::brpc::IdsRequest*, + ::brpc::IdsResponse*, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller *cntl = static_cast(cntl_base); + cntl->http_response().set_content_type("text/plain"); + butil::IOBufBuilder os; + const std::string& constraint = cntl->http_request().unresolved_path(); + + if (constraint.empty()) { + os << "# Use /ids/\n"; + bthread::id_pool_status(os); + } else { + char* endptr = NULL; + bthread_id_t id = { strtoull(constraint.c_str(), &endptr, 10) }; + if (*endptr == '\0' || *endptr == '/') { + bthread::id_status(id, os); + } else { + cntl->SetFailed(ENOMETHOD, "path=%s is not a bthread_id", + constraint.c_str()); + return; + } + } + os.move_to(cntl->response_attachment()); +} + +} // namespace brpc diff --git a/src/brpc/builtin/ids_service.h b/src/brpc/builtin/ids_service.h new file mode 100644 index 0000000..f645a9e --- /dev/null +++ b/src/brpc/builtin/ids_service.h @@ -0,0 +1,38 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_IDS_SERVICE_H +#define BRPC_IDS_SERVICE_H + +#include "brpc/builtin_service.pb.h" + + +namespace brpc { + +class IdsService: public ids { +public: + void default_method(::google::protobuf::RpcController* cntl_base, + const ::brpc::IdsRequest* request, + ::brpc::IdsResponse* response, + ::google::protobuf::Closure* done); +}; + +} // namespace brpc + + +#endif // BRPC_IDS_SERVICE_H diff --git a/src/brpc/builtin/index_service.cpp b/src/brpc/builtin/index_service.cpp new file mode 100644 index 0000000..3b1aa3b --- /dev/null +++ b/src/brpc/builtin/index_service.cpp @@ -0,0 +1,176 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // DECLARE_xxx +#include +#include "butil/time.h" // gettimeofday_us +#include "brpc/server.h" // Server +#include "brpc/builtin/index_service.h" +#include "brpc/builtin/status_service.h" +#include "brpc/builtin/common.h" +#include "brpc/details/tcmalloc_extension.h" + +namespace brpc { + +void IndexService::GetTabInfo(TabInfoList* info_list) const { + TabInfo* info = info_list->add(); + info->path = "/index?as_more"; + info->tab_name = "more"; +} + +DECLARE_bool(enable_rpcz); +DECLARE_bool(enable_dir_service); +DECLARE_bool(enable_threads_service); + +// Set in ProfilerLinker. +bool cpu_profiler_enabled = false; + +void IndexService::default_method(::google::protobuf::RpcController* controller, + const IndexRequest*, + IndexResponse*, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller *cntl = (Controller*)controller; + cntl->http_response().set_content_type("text/plain"); + const Server* server = cntl->server(); + const bool use_html = UseHTML(cntl->http_request()); + const bool as_more = cntl->http_request().uri().GetQuery("as_more"); + if (use_html && !as_more) { + google::protobuf::Service* svc = server->FindServiceByFullName( + StatusService::descriptor()->full_name()); + StatusService* st_svc = dynamic_cast(svc); + if (st_svc == NULL) { + cntl->SetFailed("Fail to find StatusService"); + return; + } + return st_svc->default_method(cntl, NULL, NULL, done_guard.release()); + } + cntl->http_response().set_content_type( + use_html ? "text/html" : "text/plain"); + const butil::EndPoint* const html_addr = (use_html ? Path::LOCAL : NULL); + const char* const NL = (use_html ? "
\n" : "\n"); + const char* const SP = (use_html ? " " : " "); + + butil::IOBufBuilder os; + if (use_html) { + os << ""; + if (as_more) { + os << "\n" + "\n" + << TabsHead() + << "\n"; + } + os << "\n"; + if (as_more) { + cntl->server()->PrintTabsBody(os, "more"); + }; + os << "
";
+    }
+    os << logo();
+    if (use_html) {
+        os << "
"; + } + os << '\n'; + if (use_html) { + os << "github"; + } else { + os << "github : https://github.com/apache/brpc"; + } + os << NL << NL; + if (!as_more) { + os << Path("/status", html_addr) << " : Status of services" << NL + << Path("/connections", html_addr) << " : List all connections" << NL + << Path("/flags", html_addr) << " : List all gflags" << NL + << SP << Path("/flags/port", html_addr) << " : List the gflag" << NL + << SP << Path("/flags/guard_page_size;help*", html_addr) + << " : List multiple gflags with glob patterns" + " (Use $ instead of ? to match single character)" << NL << SP + << "/flags/NAME?setvalue=VALUE : Change a gflag, validator will be called." + " User is responsible for thread-safety and consistency issues." << NL + + << Path("/vars", html_addr) << " : List all exposed bvars" << NL + << SP << Path("/vars/rpc_num_sockets", html_addr) + << " : List the bvar" << NL + << SP << Path("/vars/rpc_server*_count;iobuf_blo$k_*", html_addr) + << " : List multiple bvars with glob patterns" + " (Use $ instead of ? to match single character)" << NL + + << Path("/rpcz", html_addr) << " : Recent RPC calls" + << (!FLAGS_enable_rpcz ? "(disabled)" : "") << NL + << SP << Path("/rpcz/stats", html_addr) << " : Statistics of rpcz" << NL; + + std::ostringstream tmp_oss; + const int64_t seconds_before = butil::gettimeofday_us() - 30 * 1000000L; + tmp_oss << "/rpcz?" << TIME_STR << '='; + PrintRealDateTime(tmp_oss, seconds_before, true); + os << SP << Path(tmp_oss.str().c_str(), html_addr) + << " : RPC calls before the time" << NL; + tmp_oss.str(""); + tmp_oss << "/rpcz?" << TIME_STR << '='; + PrintRealDateTime(tmp_oss, seconds_before, true); + tmp_oss << '&' << MAX_SCAN_STR << "=10"; + os << SP << Path(tmp_oss.str().c_str(), html_addr) + << " : N RPC calls at most before the time" << NL << SP + << "Other filters: " << MIN_LATENCY_STR << ", " << MIN_REQUEST_SIZE_STR + << ", " << MIN_RESPONSE_SIZE_STR << ", " << LOG_ID_STR + << ", " << ERROR_CODE_STR << NL + << SP << "/rpcz?" << TRACE_ID_STR + << "=N : Recent RPC calls whose trace_id is N" << NL + << SP << "/rpcz?" << TRACE_ID_STR << "=N&" << SPAN_ID_STR + << "=M : Recent RPC calls whose trace_id is N and span_id is M" << NL + + << Path("/hotspots/cpu", html_addr) << " : Profiling CPU" + << (!cpu_profiler_enabled ? " (disabled)" : "") << NL + << Path("/hotspots/heap", html_addr) << " : Profiling heap" + << (!IsHeapProfilerEnabled() ? " (disabled)" : "") << NL + << Path("/hotspots/growth", html_addr) + << " : Profiling growth of heap" + << (!IsHeapProfilerEnabled() ? " (disabled)" : "") << NL + << Path("/hotspots/contention", html_addr) + << " : Profiling contention of lock" << NL; + } + os << "curl -H 'Content-Type: application/json' -d 'JSON' "; + if (butil::is_endpoint_extended(server->listen_address())) { + os << ""; + } else { + const butil::EndPoint my_addr(butil::my_ip(), server->listen_address().port); + os << my_addr; + } + os << "/ServiceName/MethodName : Call method by http+json" << NL + + << Path("/version", html_addr) + << " : Version of this server, set by Server::set_version()" << NL + << Path("/health", html_addr) << " : Test healthy" << NL + << Path("/vlog", html_addr) << " : List all VLOG callsites" << NL + << Path("/sockets", html_addr) << " : Check status of a Socket" << NL + << Path("/bthreads", html_addr) << " : Check status of a bthread or all living bthreads" << NL + << Path("/ids", html_addr) << " : Check status of a bthread_id" << NL + << Path("/protobufs", html_addr) << " : List all protobuf services and messages" << NL + << Path("/list", html_addr) << " : json signature of methods" << NL + << Path("/threads", html_addr) << " : Check pstack" + << (!FLAGS_enable_threads_service ? " (disabled)" : "") << NL + << Path("/dir", html_addr) << " : Browse directories and files" + << (!FLAGS_enable_dir_service ? " (disabled)" : "") << NL + << Path("/memory", html_addr) << " : Get malloc allocator information" << NL; + if (use_html) { + os << ""; + } + os.move_to(cntl->response_attachment()); +} + +} // namespace brpc diff --git a/src/brpc/builtin/index_service.h b/src/brpc/builtin/index_service.h new file mode 100644 index 0000000..f7e2b68 --- /dev/null +++ b/src/brpc/builtin/index_service.h @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_INDEX_SERVICE_H +#define BRPC_INDEX_SERVICE_H + +#include +#include "brpc/builtin_service.pb.h" +#include "brpc/builtin/tabbed.h" + + +namespace brpc { + +class IndexService : public index, public Tabbed { +public: + void default_method(::google::protobuf::RpcController* cntl_base, + const ::brpc::IndexRequest* request, + ::brpc::IndexResponse* response, + ::google::protobuf::Closure* done); + + void GetTabInfo(brpc::TabInfoList*) const; +}; + +} // namespace brpc + + +#endif //BRPC_INDEX_SERVICE_H diff --git a/src/brpc/builtin/jquery_min_js.cpp b/src/brpc/builtin/jquery_min_js.cpp new file mode 100644 index 0000000..818e399 --- /dev/null +++ b/src/brpc/builtin/jquery_min_js.cpp @@ -0,0 +1,51 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/logging.h" +#include "brpc/policy/gzip_compress.h" +#include "brpc/builtin/jquery_min_js.h" + + +namespace brpc { + +static pthread_once_t s_jquery_min_buf_once = PTHREAD_ONCE_INIT; +static butil::IOBuf* s_jquery_min_buf = NULL; +static butil::IOBuf* s_jquery_min_buf_gzip = NULL; +static void InitJQueryMinBuf() { + s_jquery_min_buf = new butil::IOBuf; + s_jquery_min_buf->append(jquery_min_js()); + s_jquery_min_buf_gzip = new butil::IOBuf; + CHECK(policy::GzipCompress(*s_jquery_min_buf, s_jquery_min_buf_gzip, NULL)); +} +const butil::IOBuf& jquery_min_js_iobuf() { + pthread_once(&s_jquery_min_buf_once, InitJQueryMinBuf); + return *s_jquery_min_buf; +} +const butil::IOBuf& jquery_min_js_iobuf_gzip() { + pthread_once(&s_jquery_min_buf_once, InitJQueryMinBuf); + return *s_jquery_min_buf_gzip; +} + + +/*! jQuery v1.8.3 jquery.com | jquery.org/license */ +const char* jquery_min_js() { + return "(function(e,t){function _(e){var t=M[e]={};return v.each(e.split(y),function(e,n){t[n]=!0}),t}function H(e,n,r){if(r===t&&e.nodeType===1){var i=\"data-\"+n.replace(P,\"-$1\").toLowerCase();r=e.getAttribute(i);if(typeof r==\"string\"){try{r=r===\"true\"?!0:r===\"false\"?!1:r===\"null\"?null:+r+\"\"===r?+r:D.test(r)?v.parseJSON(r):r}catch(s){}v.data(e,n,r)}else r=t}return r}function B(e){var t;for(t in e){if(t===\"data\"&&v.isEmptyObject(e[t]))continue;if(t!==\"toJSON\")return!1}return!0}function et(){return!1}function tt(){return!0}function ut(e){return!e||!e.parentNode||e.parentNode.nodeType===11}function at(e,t){do e=e[t];while(e&&e.nodeType!==1);return e}function ft(e,t,n){t=t||0;if(v.isFunction(t))return v.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return v.grep(e,function(e,r){return e===t===n});if(typeof t==\"string\"){var r=v.grep(e,function(e){return e.nodeType===1});if(it.test(t))return v.filter(t,r,!n);t=v.filter(t,r)}return v.grep(e,function(e,r){return v.inArray(e,t)>=0===n})}function lt(e){var t=ct.split(\"|\"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function At(e,t){if(t.nodeType!==1||!v.hasData(e))return;var n,r,i,s=v._data(e),o=v._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r\").appendTo(i.body),n=t.css(\"display\");t.remove();if(n===\"none\"||n===\"\"){Pt=i.body.appendChild(Pt||v.extend(i.createElement(\"iframe\"),{frameBorder:0,width:0,height:0}));if(!Ht||!Pt.createElement)Ht=(Pt.contentWindow||Pt.contentDocument).document,Ht.write(\"\"),Ht.close();t=Ht.body.appendChild(Ht.createElement(e)),n=Dt(t,\"display\"),i.body.removeChild(Pt)}return Wt[e]=n,n}function fn(e,t,n,r){var i;if(v.isArray(t))v.each(t,function(t,i){n||sn.test(e)?r(e,i):fn(e+\"[\"+(typeof i==\"object\"?t:\"\")+\"]\",i,n,r)});else if(!n&&v.type(t)===\"object\")for(i in t)fn(e+\"[\"+i+\"]\",t[i],n,r);else r(e,t)}function Cn(e){return function(t,n){typeof t!=\"string\"&&(n=t,t=\"*\");var r,i,s,o=t.toLowerCase().split(y),u=0,a=o.length;if(v.isFunction(n))for(;u)[^>]*$|#([\\w\\-]*)$)/,E=/^<(\\w+)\\s*\\/?>(?:<\\/\\1>|)$/,S=/^[\\],:{}\\s]*$/,x=/(?:^|:|,)(?:\\s*\\[)+/g,T=/\\\\(?:[\"\\\\\\/bfnrt]|u[\\da-fA-F]{4})/g,N=/\"[^\"\\\\\\r\\n]*\"|true|false|null|-?(?:\\d\\d*\\.|)\\d+(?:[eE][\\-+]?\\d+|)/g,C=/^-ms-/,k=/-([\\da-z])/gi,L=function(e,t){return(t+\"\").toUpperCase()},A=function(){i.addEventListener?(i.removeEventListener(\"DOMContentLoaded\",A,!1),v.ready()):i.readyState===\"complete\"&&(i.detachEvent(\"onreadystatechange\",A),v.ready())},O={};v.fn=v.prototype={constructor:v,init:function(e,n,r){var s,o,u,a;if(!e)return this;if(e.nodeType)return this.context=this[0]=e,this.length=1,this;if(typeof e==\"string\"){e.charAt(0)===\"<\"&&e.charAt(e.length-1)===\">\"&&e.length>=3?s=[null,e,null]:s=w.exec(e);if(s&&(s[1]||!n)){if(s[1])return n=n instanceof v?n[0]:n,a=n&&n.nodeType?n.ownerDocument||n:i,e=v.parseHTML(s[1],a,!0),E.test(s[1])&&v.isPlainObject(n)&&this.attr.call(e,n,!0),v.merge(this,e);o=i.getElementById(s[2]);if(o&&o.parentNode){if(o.id!==s[2])return r.find(e);this.length=1,this[0]=o}return this.context=i,this.selector=e,this}return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e)}return v.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),v.makeArray(e,this))},selector:\"\",jquery:\"1.8.3\",length:0,size:function(){return this.length},toArray:function(){return l.call(this)},get:function(e){return e==null?this.toArray():e<0?this[this.length+e]:this[e]},pushStack:function(e,t,n){var r=v.merge(this.constructor(),e);return r.prevObject=this,r.context=this.context,t===\"find\"?r.selector=this.selector+(this.selector?\" \":\"\")+n:t&&(r.selector=this.selector+\".\"+t+\"(\"+n+\")\"),r},each:function(e,t){return v.each(this,e,t)},ready:function(e){return v.ready.promise().done(e),this},eq:function(e){return e=+e,e===-1?this.slice(e):this.slice(e,e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(l.apply(this,arguments),\"slice\",l.call(arguments).join(\",\"))},map:function(e){return this.pushStack(v.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:[].sort,splice:[].splice},v.fn.init.prototype=v.fn,v.extend=v.fn.extend=function(){var e,n,r,i,s,o,u=arguments[0]||{},a=1,f=arguments.length,l=!1;typeof u==\"boolean\"&&(l=u,u=arguments[1]||{},a=2),typeof u!=\"object\"&&!v.isFunction(u)&&(u={}),f===a&&(u=this,--a);for(;a0)return;r.resolveWith(i,[v]),v.fn.trigger&&v(i).trigger(\"ready\").off(\"ready\")},isFunction:function(e){return v.type(e)===\"function\"},isArray:Array.isArray||function(e){return v.type(e)===\"array\"},isWindow:function(e){return e!=null&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return e==null?String(e):O[h.call(e)]||\"object\"},isPlainObject:function(e){if(!e||v.type(e)!==\"object\"||e.nodeType||v.isWindow(e))return!1;try{if(e.constructor&&!p.call(e,\"constructor\")&&!p.call(e.constructor.prototype,\"isPrototypeOf\"))return!1}catch(n){return!1}var r;for(r in e);return r===t||p.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw new Error(e)},parseHTML:function(e,t,n){var r;return!e||typeof e!=\"string\"?null:(typeof t==\"boolean\"&&(n=t,t=0),t=t||i,(r=E.exec(e))?[t.createElement(r[1])]:(r=v.buildFragment([e],t,n?null:[]),v.merge([],(r.cacheable?v.clone(r.fragment):r.fragment).childNodes)))},parseJSON:function(t){if(!t||typeof t!=\"string\")return null;t=v.trim(t);if(e.JSON&&e.JSON.parse)return e.JSON.parse(t);if(S.test(t.replace(T,\"@\").replace(N,\"]\").replace(x,\"\")))return(new Function(\"return \"+t))();v.error(\"Invalid JSON: \"+t)},parseXML:function(n){var r,i;if(!n||typeof n!=\"string\")return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,\"text/xml\")):(r=new ActiveXObject(\"Microsoft.XMLDOM\"),r.async=\"false\",r.loadXML(n))}catch(s){r=t}return(!r||!r.documentElement||r.getElementsByTagName(\"parsererror\").length)&&v.error(\"Invalid XML: \"+n),r},noop:function(){},globalEval:function(t){t&&g.test(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(C,\"ms-\").replace(k,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,n,r){var i,s=0,o=e.length,u=o===t||v.isFunction(e);if(r){if(u){for(i in e)if(n.apply(e[i],r)===!1)break}else for(;s0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u-1)a.splice(n,1),i&&(n<=o&&o--,n<=u&&u--)}),this},has:function(e){return v.inArray(e,a)>-1},empty:function(){return a=[],this},disable:function(){return a=f=n=t,this},disabled:function(){return!a},lock:function(){return f=t,n||c.disable(),this},locked:function(){return!f},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],a&&(!r||f)&&(i?f.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},v.extend({Deferred:function(e){var t=[[\"resolve\",\"done\",v.Callbacks(\"once memory\"),\"resolved\"],[\"reject\",\"fail\",v.Callbacks(\"once memory\"),\"rejected\"],[\"notify\",\"progress\",v.Callbacks(\"memory\")]],n=\"pending\",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return v.Deferred(function(n){v.each(t,function(t,r){var s=r[0],o=e[t];i[r[1]](v.isFunction(o)?function(){var e=o.apply(this,arguments);e&&v.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+\"With\"](this===i?n:this,[e])}:n[s])}),e=null}).promise()},promise:function(e){return e!=null?v.extend(e,r):r}},i={};return r.pipe=r.then,v.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=o.fire,i[s[0]+\"With\"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=l.call(arguments),r=n.length,i=r!==1||e&&v.isFunction(e.promise)?r:0,s=i===1?e:v.Deferred(),o=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?l.call(arguments):r,n===u?s.notifyWith(t,n):--i||s.resolveWith(t,n)}},u,a,f;if(r>1){u=new Array(r),a=new Array(r),f=new Array(r);for(;t
a\",n=p.getElementsByTagName(\"*\"),r=p.getElementsByTagName(\"a\")[0];if(!n||!r||!n.length)return{};s=i.createElement(\"select\"),o=s.appendChild(i.createElement(\"option\")),u=p.getElementsByTagName(\"input\")[0],r.style.cssText=\"top:1px;float:left;opacity:.5\",t={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName(\"tbody\").length,htmlSerialize:!!p.getElementsByTagName(\"link\").length,style:/top/.test(r.getAttribute(\"style\")),hrefNormalized:r.getAttribute(\"href\")===\"/a\",opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:u.value===\"on\",optSelected:o.selected,getSetAttribute:p.className!==\"t\",enctype:!!i.createElement(\"form\").enctype,html5Clone:i.createElement(\"nav\").cloneNode(!0).outerHTML!==\"<:nav>\",boxModel:i.compatMode===\"CSS1Compat\",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},u.checked=!0,t.noCloneChecked=u.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!o.disabled;try{delete p.test}catch(d){t.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent(\"onclick\",h=function(){t.noCloneEvent=!1}),p.cloneNode(!0).fireEvent(\"onclick\"),p.detachEvent(\"onclick\",h)),u=i.createElement(\"input\"),u.value=\"t\",u.setAttribute(\"type\",\"radio\"),t.radioValue=u.value===\"t\",u.setAttribute(\"checked\",\"checked\"),u.setAttribute(\"name\",\"t\"),p.appendChild(u),a=i.createDocumentFragment(),a.appendChild(p.lastChild),t.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,t.appendChecked=u.checked,a.removeChild(u),a.appendChild(p);if(p.attachEvent)for(l in{submit:!0,change:!0,focusin:!0})f=\"on\"+l,c=f in p,c||(p.setAttribute(f,\"return;\"),c=typeof p[f]==\"function\"),t[l+\"Bubbles\"]=c;return v(function(){var n,r,s,o,u=\"padding:0;margin:0;border:0;display:block;overflow:hidden;\",a=i.getElementsByTagName(\"body\")[0];if(!a)return;n=i.createElement(\"div\"),n.style.cssText=\"visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px\",a.insertBefore(n,a.firstChild),r=i.createElement(\"div\"),n.appendChild(r),r.innerHTML=\"
t
\",s=r.getElementsByTagName(\"td\"),s[0].style.cssText=\"padding:0;margin:0;border:0;display:none\",c=s[0].offsetHeight===0,s[0].style.display=\"\",s[1].style.display=\"none\",t.reliableHiddenOffsets=c&&s[0].offsetHeight===0,r.innerHTML=\"\",r.style.cssText=\"box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;\",t.boxSizing=r.offsetWidth===4,t.doesNotIncludeMarginInBodyOffset=a.offsetTop!==1,e.getComputedStyle&&(t.pixelPosition=(e.getComputedStyle(r,null)||{}).top!==\"1%\",t.boxSizingReliable=(e.getComputedStyle(r,null)||{width:\"4px\"}).width===\"4px\",o=i.createElement(\"div\"),o.style.cssText=r.style.cssText=u,o.style.marginRight=o.style.width=\"0\",r.style.width=\"1px\",r.appendChild(o),t.reliableMarginRight=!parseFloat((e.getComputedStyle(o,null)||{}).marginRight)),typeof r.style.zoom!=\"undefined\"&&(r.innerHTML=\"\",r.style.cssText=u+\"width:1px;padding:1px;display:inline;zoom:1\",t.inlineBlockNeedsLayout=r.offsetWidth===3,r.style.display=\"block\",r.style.overflow=\"visible\",r.innerHTML=\"
\",r.firstChild.style.width=\"5px\",t.shrinkWrapBlocks=r.offsetWidth!==3,n.style.zoom=1),a.removeChild(n),n=r=s=o=null}),a.removeChild(p),n=r=s=o=u=a=p=null,t}();var D=/(?:\\{[\\s\\S]*\\}|\\[[\\s\\S]*\\])$/,P=/([A-Z])/g;v.extend({cache:{},deletedIds:[],uuid:0,expando:\"jQuery\"+(v.fn.jquery+Math.random()).replace(/\\D/g,\"\"),noData:{embed:!0,object:\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\",applet:!0},hasData:function(e){return e=e.nodeType?v.cache[e[v.expando]]:e[v.expando],!!e&&!B(e)},data:function(e,n,r,i){if(!v.acceptData(e))return;var s,o,u=v.expando,a=typeof n==\"string\",f=e.nodeType,l=f?v.cache:e,c=f?e[u]:e[u]&&u;if((!c||!l[c]||!i&&!l[c].data)&&a&&r===t)return;c||(f?e[u]=c=v.deletedIds.pop()||v.guid++:c=u),l[c]||(l[c]={},f||(l[c].toJSON=v.noop));if(typeof n==\"object\"||typeof n==\"function\")i?l[c]=v.extend(l[c],n):l[c].data=v.extend(l[c].data,n);return s=l[c],i||(s.data||(s.data={}),s=s.data),r!==t&&(s[v.camelCase(n)]=r),a?(o=s[n],o==null&&(o=s[v.camelCase(n)])):o=s,o},removeData:function(e,t,n){if(!v.acceptData(e))return;var r,i,s,o=e.nodeType,u=o?v.cache:e,a=o?e[v.expando]:v.expando;if(!u[a])return;if(t){r=n?u[a]:u[a].data;if(r){v.isArray(t)||(t in r?t=[t]:(t=v.camelCase(t),t in r?t=[t]:t=t.split(\" \")));for(i=0,s=t.length;i1,null,!1))},removeData:function(e){return this.each(function(){v.removeData(this,e)})}}),v.extend({queue:function(e,t,n){var r;if(e)return t=(t||\"fx\")+\"queue\",r=v._data(e,t),n&&(!r||v.isArray(n)?r=v._data(e,t,v.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||\"fx\";var n=v.queue(e,t),r=n.length,i=n.shift(),s=v._queueHooks(e,t),o=function(){v.dequeue(e,t)};i===\"inprogress\"&&(i=n.shift(),r--),i&&(t===\"fx\"&&n.unshift(\"inprogress\"),delete s.stop,i.call(e,o,s)),!r&&s&&s.empty.fire()},_queueHooks:function(e,t){var n=t+\"queueHooks\";return v._data(e,n)||v._data(e,n,{empty:v.Callbacks(\"once memory\").add(function(){v.removeData(e,t+\"queue\",!0),v.removeData(e,n,!0)})})}}),v.fn.extend({queue:function(e,n){var r=2;return typeof e!=\"string\"&&(n=e,e=\"fx\",r--),arguments.length1)},removeAttr:function(e){return this.each(function(){v.removeAttr(this,e)})},prop:function(e,t){return v.access(this,v.prop,e,t,arguments.length>1)},removeProp:function(e){return e=v.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,s,o,u;if(v.isFunction(e))return this.each(function(t){v(this).addClass(e.call(this,t,this.className))});if(e&&typeof e==\"string\"){t=e.split(y);for(n=0,r=this.length;n=0)r=r.replace(\" \"+n[s]+\" \",\" \");i.className=e?v.trim(r):\"\"}}}return this},toggleClass:function(e,t){var n=typeof e,r=typeof t==\"boolean\";return v.isFunction(e)?this.each(function(n){v(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if(n===\"string\"){var i,s=0,o=v(this),u=t,a=e.split(y);while(i=a[s++])u=r?u:!o.hasClass(i),o[u?\"addClass\":\"removeClass\"](i)}else if(n===\"undefined\"||n===\"boolean\")this.className&&v._data(this,\"__className__\",this.className),this.className=this.className||e===!1?\"\":v._data(this,\"__className__\")||\"\"})},hasClass:function(e){var t=\" \"+e+\" \",n=0,r=this.length;for(;n=0)return!0;return!1},val:function(e){var n,r,i,s=this[0];if(!arguments.length){if(s)return n=v.valHooks[s.type]||v.valHooks[s.nodeName.toLowerCase()],n&&\"get\"in n&&(r=n.get(s,\"value\"))!==t?r:(r=s.value,typeof r==\"string\"?r.replace(R,\"\"):r==null?\"\":r);return}return i=v.isFunction(e),this.each(function(r){var s,o=v(this);if(this.nodeType!==1)return;i?s=e.call(this,r,o.val()):s=e,s==null?s=\"\":typeof s==\"number\"?s+=\"\":v.isArray(s)&&(s=v.map(s,function(e){return e==null?\"\":e+\"\"})),n=v.valHooks[this.type]||v.valHooks[this.nodeName.toLowerCase()];if(!n||!(\"set\"in n)||n.set(this,s,\"value\")===t)this.value=s})}}),v.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,s=e.type===\"select-one\"||i<0,o=s?null:[],u=s?i+1:r.length,a=i<0?u:s?i:0;for(;a=0}),n.length||(e.selectedIndex=-1),n}}},attrFn:{},attr:function(e,n,r,i){var s,o,u,a=e.nodeType;if(!e||a===3||a===8||a===2)return;if(i&&v.isFunction(v.fn[n]))return v(e)[n](r);if(typeof e.getAttribute==\"undefined\")return v.prop(e,n,r);u=a!==1||!v.isXMLDoc(e),u&&(n=n.toLowerCase(),o=v.attrHooks[n]||(X.test(n)?F:j));if(r!==t){if(r===null){v.removeAttr(e,n);return}return o&&\"set\"in o&&u&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+\"\"),r)}return o&&\"get\"in o&&u&&(s=o.get(e,n))!==null?s:(s=e.getAttribute(n),s===null?t:s)},removeAttr:function(e,t){var n,r,i,s,o=0;if(t&&e.nodeType===1){r=t.split(y);for(;o=0}})});var $=/^(?:textarea|input|select)$/i,J=/^([^\\.]*|)(?:\\.(.+)|)$/,K=/(?:^|\\s)hover(\\.\\S+|)\\b/,Q=/^key/,G=/^(?:mouse|contextmenu)|click/,Y=/^(?:focusinfocus|focusoutblur)$/,Z=function(e){return v.event.special.hover?e:e.replace(K,\"mouseenter$1 mouseleave$1\")};v.event={add:function(e,n,r,i,s){var o,u,a,f,l,c,h,p,d,m,g;if(e.nodeType===3||e.nodeType===8||!n||!r||!(o=v._data(e)))return;r.handler&&(d=r,r=d.handler,s=d.selector),r.guid||(r.guid=v.guid++),a=o.events,a||(o.events=a={}),u=o.handle,u||(o.handle=u=function(e){return typeof v==\"undefined\"||!!e&&v.event.triggered===e.type?t:v.event.dispatch.apply(u.elem,arguments)},u.elem=e),n=v.trim(Z(n)).split(\" \");for(f=0;f=0&&(y=y.slice(0,-1),a=!0),y.indexOf(\".\")>=0&&(b=y.split(\".\"),y=b.shift(),b.sort());if((!s||v.event.customEvent[y])&&!v.event.global[y])return;n=typeof n==\"object\"?n[v.expando]?n:new v.Event(y,n):new v.Event(y),n.type=y,n.isTrigger=!0,n.exclusive=a,n.namespace=b.join(\".\"),n.namespace_re=n.namespace?new RegExp(\"(^|\\\\.)\"+b.join(\"\\\\.(?:.*\\\\.|)\")+\"(\\\\.|$)\"):null,h=y.indexOf(\":\")<0?\"on\"+y:\"\";if(!s){u=v.cache;for(f in u)u[f].events&&u[f].events[y]&&v.event.trigger(n,r,u[f].handle.elem,!0);return}n.result=t,n.target||(n.target=s),r=r!=null?v.makeArray(r):[],r.unshift(n),p=v.event.special[y]||{};if(p.trigger&&p.trigger.apply(s,r)===!1)return;m=[[s,p.bindType||y]];if(!o&&!p.noBubble&&!v.isWindow(s)){g=p.delegateType||y,l=Y.test(g+y)?s:s.parentNode;for(c=s;l;l=l.parentNode)m.push([l,g]),c=l;c===(s.ownerDocument||i)&&m.push([c.defaultView||c.parentWindow||e,g])}for(f=0;f=0:v.find(h,this,null,[s]).length),u[h]&&f.push(c);f.length&&w.push({elem:s,matches:f})}d.length>m&&w.push({elem:this,matches:d.slice(m)});for(r=0;r0?this.on(t,null,e,n):this.trigger(t)},Q.test(t)&&(v.event.fixHooks[t]=v.event.keyHooks),G.test(t)&&(v.event.fixHooks[t]=v.event.mouseHooks)}),function(e,t){function nt(e,t,n,r){n=n||[],t=t||g;var i,s,a,f,l=t.nodeType;if(!e||typeof e!=\"string\")return n;if(l!==1&&l!==9)return[];a=o(t);if(!a&&!r)if(i=R.exec(e))if(f=i[1]){if(l===9){s=t.getElementById(f);if(!s||!s.parentNode)return n;if(s.id===f)return n.push(s),n}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(f))&&u(t,s)&&s.id===f)return n.push(s),n}else{if(i[2])return S.apply(n,x.call(t.getElementsByTagName(e),0)),n;if((f=i[3])&&Z&&t.getElementsByClassName)return S.apply(n,x.call(t.getElementsByClassName(f),0)),n}return vt(e.replace(j,\"$1\"),t,n,r,a)}function rt(e){return function(t){var n=t.nodeName.toLowerCase();return n===\"input\"&&t.type===e}}function it(e){return function(t){var n=t.nodeName.toLowerCase();return(n===\"input\"||n===\"button\")&&t.type===e}}function st(e){return N(function(t){return t=+t,N(function(n,r){var i,s=e([],n.length,t),o=s.length;while(o--)n[i=s[o]]&&(n[i]=!(r[i]=n[i]))})})}function ot(e,t,n){if(e===t)return n;var r=e.nextSibling;while(r){if(r===t)return-1;r=r.nextSibling}return 1}function ut(e,t){var n,r,s,o,u,a,f,l=L[d][e+\" \"];if(l)return t?0:l.slice(0);u=e,a=[],f=i.preFilter;while(u){if(!n||(r=F.exec(u)))r&&(u=u.slice(r[0].length)||u),a.push(s=[]);n=!1;if(r=I.exec(u))s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=r[0].replace(j,\" \");for(o in i.filter)(r=J[o].exec(u))&&(!f[o]||(r=f[o](r)))&&(s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=o,n.matches=r);if(!n)break}return t?u.length:u?nt.error(e):L(e,a).slice(0)}function at(e,t,r){var i=t.dir,s=r&&t.dir===\"parentNode\",o=w++;return t.first?function(t,n,r){while(t=t[i])if(s||t.nodeType===1)return e(t,n,r)}:function(t,r,u){if(!u){var a,f=b+\" \"+o+\" \",l=f+n;while(t=t[i])if(s||t.nodeType===1){if((a=t[d])===l)return t.sizset;if(typeof a==\"string\"&&a.indexOf(f)===0){if(t.sizset)return t}else{t[d]=l;if(e(t,r,u))return t.sizset=!0,t;t.sizset=!1}}}else while(t=t[i])if(s||t.nodeType===1)if(e(t,r,u))return t}}function ft(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function lt(e,t,n,r,i){var s,o=[],u=0,a=e.length,f=t!=null;for(;u-1&&(s[f]=!(o[f]=c))}}else g=lt(g===o?g.splice(d,g.length):g),i?i(null,o,g,a):S.apply(o,g)})}function ht(e){var t,n,r,s=e.length,o=i.relative[e[0].type],u=o||i.relative[\" \"],a=o?1:0,f=at(function(e){return e===t},u,!0),l=at(function(e){return T.call(t,e)>-1},u,!0),h=[function(e,n,r){return!o&&(r||n!==c)||((t=n).nodeType?f(e,n,r):l(e,n,r))}];for(;a1&&ft(h),a>1&&e.slice(0,a-1).join(\"\").replace(j,\"$1\"),n,a0,s=e.length>0,o=function(u,a,f,l,h){var p,d,v,m=[],y=0,w=\"0\",x=u&&[],T=h!=null,N=c,C=u||s&&i.find.TAG(\"*\",h&&a.parentNode||a),k=b+=N==null?1:Math.E;T&&(c=a!==g&&a,n=o.el);for(;(p=C[w])!=null;w++){if(s&&p){for(d=0;v=e[d];d++)if(v(p,a,f)){l.push(p);break}T&&(b=k,n=++o.el)}r&&((p=!v&&p)&&y--,u&&x.push(p))}y+=w;if(r&&w!==y){for(d=0;v=t[d];d++)v(x,m,a,f);if(u){if(y>0)while(w--)!x[w]&&!m[w]&&(m[w]=E.call(l));m=lt(m)}S.apply(l,m),T&&!u&&m.length>0&&y+t.length>1&&nt.uniqueSort(l)}return T&&(b=k,c=N),x};return o.el=0,r?N(o):o}function dt(e,t,n){var r=0,i=t.length;for(;r2&&(f=u[0]).type===\"ID\"&&t.nodeType===9&&!s&&i.relative[u[1].type]){t=i.find.ID(f.matches[0].replace($,\"\"),t,s)[0];if(!t)return n;e=e.slice(u.shift().length)}for(o=J.POS.test(e)?-1:u.length-1;o>=0;o--){f=u[o];if(i.relative[l=f.type])break;if(c=i.find[l])if(r=c(f.matches[0].replace($,\"\"),z.test(u[0].type)&&t.parentNode||t,s)){u.splice(o,1),e=r.length&&u.join(\"\");if(!e)return S.apply(n,x.call(r,0)),n;break}}}return a(e,h)(r,t,s,n,z.test(e)),n}function mt(){}var n,r,i,s,o,u,a,f,l,c,h=!0,p=\"undefined\",d=(\"sizcache\"+Math.random()).replace(\".\",\"\"),m=String,g=e.document,y=g.documentElement,b=0,w=0,E=[].pop,S=[].push,x=[].slice,T=[].indexOf||function(e){var t=0,n=this.length;for(;ti.cacheLength&&delete e[t.shift()],e[n+\" \"]=r},e)},k=C(),L=C(),A=C(),O=\"[\\\\x20\\\\t\\\\r\\\\n\\\\f]\",M=\"(?:\\\\\\\\.|[-\\\\w]|[^\\\\x00-\\\\xa0])+\",_=M.replace(\"w\",\"w#\"),D=\"([*^$|!~]?=)\",P=\"\\\\[\"+O+\"*(\"+M+\")\"+O+\"*(?:\"+D+O+\"*(?:(['\\\"])((?:\\\\\\\\.|[^\\\\\\\\])*?)\\\\3|(\"+_+\")|)|)\"+O+\"*\\\\]\",H=\":(\"+M+\")(?:\\\\((?:(['\\\"])((?:\\\\\\\\.|[^\\\\\\\\])*?)\\\\2|([^()[\\\\]]*|(?:(?:\"+P+\")|[^:]|\\\\\\\\.)*|.*))\\\\)|)\",B=\":(even|odd|eq|gt|lt|nth|first|last)(?:\\\\(\"+O+\"*((?:-\\\\d)?\\\\d*)\"+O+\"*\\\\)|)(?=[^-]|$)\",j=new RegExp(\"^\"+O+\"+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)\"+O+\"+$\",\"g\"),F=new RegExp(\"^\"+O+\"*,\"+O+\"*\"),I=new RegExp(\"^\"+O+\"*([\\\\x20\\\\t\\\\r\\\\n\\\\f>+~])\"+O+\"*\"),q=new RegExp(H),R=/^(?:#([\\w\\-]+)|(\\w+)|\\.([\\w\\-]+))$/,U=/^:not/,z=/[\\x20\\t\\r\\n\\f]*[+~]/,W=/:not\\($/,X=/h\\d/i,V=/input|select|textarea|button/i,$=/\\\\(?!\\\\)/g,J={ID:new RegExp(\"^#(\"+M+\")\"),CLASS:new RegExp(\"^\\\\.(\"+M+\")\"),NAME:new RegExp(\"^\\\\[name=['\\\"]?(\"+M+\")['\\\"]?\\\\]\"),TAG:new RegExp(\"^(\"+M.replace(\"w\",\"w*\")+\")\"),ATTR:new RegExp(\"^\"+P),PSEUDO:new RegExp(\"^\"+H),POS:new RegExp(B,\"i\"),CHILD:new RegExp(\"^:(only|nth|first|last)-child(?:\\\\(\"+O+\"*(even|odd|(([+-]|)(\\\\d*)n|)\"+O+\"*(?:([+-]|)\"+O+\"*(\\\\d+)|))\"+O+\"*\\\\)|)\",\"i\"),needsContext:new RegExp(\"^\"+O+\"*[>+~]|\"+B,\"i\")},K=function(e){var t=g.createElement(\"div\");try{return e(t)}catch(n){return!1}finally{t=null}},Q=K(function(e){return e.appendChild(g.createComment(\"\")),!e.getElementsByTagName(\"*\").length}),G=K(function(e){return e.innerHTML=\"\",e.firstChild&&typeof e.firstChild.getAttribute!==p&&e.firstChild.getAttribute(\"href\")===\"#\"}),Y=K(function(e){e.innerHTML=\"\";var t=typeof e.lastChild.getAttribute(\"multiple\");return t!==\"boolean\"&&t!==\"string\"}),Z=K(function(e){return e.innerHTML=\"\",!e.getElementsByClassName||!e.getElementsByClassName(\"e\").length?!1:(e.lastChild.className=\"e\",e.getElementsByClassName(\"e\").length===2)}),et=K(function(e){e.id=d+0,e.innerHTML=\"
\",y.insertBefore(e,y.firstChild);var t=g.getElementsByName&&g.getElementsByName(d).length===2+g.getElementsByName(d+0).length;return r=!g.getElementById(d),y.removeChild(e),t});try{x.call(y.childNodes,0)[0].nodeType}catch(tt){x=function(e){var t,n=[];for(;t=this[e];e++)n.push(t);return n}}nt.matches=function(e,t){return nt(e,null,null,t)},nt.matchesSelector=function(e,t){return nt(t,null,null,[e]).length>0},s=nt.getText=function(e){var t,n=\"\",r=0,i=e.nodeType;if(i){if(i===1||i===9||i===11){if(typeof e.textContent==\"string\")return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=s(e)}else if(i===3||i===4)return e.nodeValue}else for(;t=e[r];r++)n+=s(t);return n},o=nt.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?t.nodeName!==\"HTML\":!1},u=nt.contains=y.contains?function(e,t){var n=e.nodeType===9?e.documentElement:e,r=t&&t.parentNode;return e===r||!!(r&&r.nodeType===1&&n.contains&&n.contains(r))}:y.compareDocumentPosition?function(e,t){return t&&!!(e.compareDocumentPosition(t)&16)}:function(e,t){while(t=t.parentNode)if(t===e)return!0;return!1},nt.attr=function(e,t){var n,r=o(e);return r||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):r||Y?e.getAttribute(t):(n=e.getAttributeNode(t),n?typeof e[t]==\"boolean\"?e[t]?t:null:n.specified?n.value:null:null)},i=nt.selectors={cacheLength:50,createPseudo:N,match:J,attrHandle:G?{}:{href:function(e){return e.getAttribute(\"href\",2)},type:function(e){return e.getAttribute(\"type\")}},find:{ID:r?function(e,t,n){if(typeof t.getElementById!==p&&!n){var r=t.getElementById(e);return r&&r.parentNode?[r]:[]}}:function(e,n,r){if(typeof n.getElementById!==p&&!r){var i=n.getElementById(e);return i?i.id===e||typeof i.getAttributeNode!==p&&i.getAttributeNode(\"id\").value===e?[i]:t:[]}},TAG:Q?function(e,t){if(typeof t.getElementsByTagName!==p)return t.getElementsByTagName(e)}:function(e,t){var n=t.getElementsByTagName(e);if(e===\"*\"){var r,i=[],s=0;for(;r=n[s];s++)r.nodeType===1&&i.push(r);return i}return n},NAME:et&&function(e,t){if(typeof t.getElementsByName!==p)return t.getElementsByName(name)},CLASS:Z&&function(e,t,n){if(typeof t.getElementsByClassName!==p&&!n)return t.getElementsByClassName(e)}},relative:{\">\":{dir:\"parentNode\",first:!0},\" \":{dir:\"parentNode\"},\"+\":{dir:\"previousSibling\",first:!0},\"~\":{dir:\"previousSibling\"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace($,\"\"),e[3]=(e[4]||e[5]||\"\").replace($,\"\"),e[2]===\"~=\"&&(e[3]=\" \"+e[3]+\" \"),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),e[1]===\"nth\"?(e[2]||nt.error(e[0]),e[3]=+(e[3]?e[4]+(e[5]||1):2*(e[2]===\"even\"||e[2]===\"odd\")),e[4]=+(e[6]+e[7]||e[2]===\"odd\")):e[2]&&nt.error(e[0]),e},PSEUDO:function(e){var t,n;if(J.CHILD.test(e[0]))return null;if(e[3])e[2]=e[3];else if(t=e[4])q.test(t)&&(n=ut(t,!0))&&(n=t.indexOf(\")\",t.length-n)-t.length)&&(t=t.slice(0,n),e[0]=e[0].slice(0,n)),e[2]=t;return e.slice(0,3)}},filter:{ID:r?function(e){return e=e.replace($,\"\"),function(t){return t.getAttribute(\"id\")===e}}:function(e){return e=e.replace($,\"\"),function(t){var n=typeof t.getAttributeNode!==p&&t.getAttributeNode(\"id\");return n&&n.value===e}},TAG:function(e){return e===\"*\"?function(){return!0}:(e=e.replace($,\"\").toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[d][e+\" \"];return t||(t=new RegExp(\"(^|\"+O+\")\"+e+\"(\"+O+\"|$)\"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==p&&e.getAttribute(\"class\")||\"\")})},ATTR:function(e,t,n){return function(r,i){var s=nt.attr(r,e);return s==null?t===\"!=\":t?(s+=\"\",t===\"=\"?s===n:t===\"!=\"?s!==n:t===\"^=\"?n&&s.indexOf(n)===0:t===\"*=\"?n&&s.indexOf(n)>-1:t===\"$=\"?n&&s.substr(s.length-n.length)===n:t===\"~=\"?(\" \"+s+\" \").indexOf(n)>-1:t===\"|=\"?s===n||s.substr(0,n.length+1)===n+\"-\":!1):!0}},CHILD:function(e,t,n,r){return e===\"nth\"?function(e){var t,i,s=e.parentNode;if(n===1&&r===0)return!0;if(s){i=0;for(t=s.firstChild;t;t=t.nextSibling)if(t.nodeType===1){i++;if(e===t)break}}return i-=r,i===n||i%n===0&&i/n>=0}:function(t){var n=t;switch(e){case\"only\":case\"first\":while(n=n.previousSibling)if(n.nodeType===1)return!1;if(e===\"first\")return!0;n=t;case\"last\":while(n=n.nextSibling)if(n.nodeType===1)return!1;return!0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||nt.error(\"unsupported pseudo: \"+e);return r[d]?r(t):r.length>1?(n=[e,e,\"\",t],i.setFilters.hasOwnProperty(e.toLowerCase())?N(function(e,n){var i,s=r(e,t),o=s.length;while(o--)i=T.call(e,s[o]),e[i]=!(n[i]=s[o])}):function(e){return r(e,0,n)}):r}},pseudos:{not:N(function(e){var t=[],n=[],r=a(e.replace(j,\"$1\"));return r[d]?N(function(e,t,n,i){var s,o=r(e,null,i,[]),u=e.length;while(u--)if(s=o[u])e[u]=!(t[u]=s)}):function(e,i,s){return t[0]=e,r(t,null,s,n),!n.pop()}}),has:N(function(e){return function(t){return nt(e,t).length>0}}),contains:N(function(e){return function(t){return(t.textContent||t.innerText||s(t)).indexOf(e)>-1}}),enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return t===\"input\"&&!!e.checked||t===\"option\"&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},parent:function(e){return!i.pseudos.empty(e)},empty:function(e){var t;e=e.firstChild;while(e){if(e.nodeName>\"@\"||(t=e.nodeType)===3||t===4)return!1;e=e.nextSibling}return!0},header:function(e){return X.test(e.nodeName)},text:function(e){var t,n;return e.nodeName.toLowerCase()===\"input\"&&(t=e.type)===\"text\"&&((n=e.getAttribute(\"type\"))==null||n.toLowerCase()===t)},radio:rt(\"radio\"),checkbox:rt(\"checkbox\"),file:rt(\"file\"),password:rt(\"password\"),image:rt(\"image\"),submit:it(\"submit\"),reset:it(\"reset\"),button:function(e){var t=e.nodeName.toLowerCase();return t===\"input\"&&e.type===\"button\"||t===\"button\"},input:function(e){return V.test(e.nodeName)},focus:function(e){var t=e.ownerDocument;return e===t.activeElement&&(!t.hasFocus||t.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},active:function(e){return e===e.ownerDocument.activeElement},first:st(function(){return[0]}),last:st(function(e,t){return[t-1]}),eq:st(function(e,t,n){return[n<0?n+t:n]}),even:st(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:st(function(e,t,n){for(var r=n<0?n+t:n;++r\",e.querySelectorAll(\"[selected]\").length||i.push(\"\\\\[\"+O+\"*(?:checked|disabled|ismap|multiple|readonly|selected|value)\"),e.querySelectorAll(\":checked\").length||i.push(\":checked\")}),K(function(e){e.innerHTML=\"

\",e.querySelectorAll(\"[test^='']\").length&&i.push(\"[*^$]=\"+O+\"*(?:\\\"\\\"|'')\"),e.innerHTML=\"\",e.querySelectorAll(\":enabled\").length||i.push(\":enabled\",\":disabled\")}),i=new RegExp(i.join(\"|\")),vt=function(e,r,s,o,u){if(!o&&!u&&!i.test(e)){var a,f,l=!0,c=d,h=r,p=r.nodeType===9&&e;if(r.nodeType===1&&r.nodeName.toLowerCase()!==\"object\"){a=ut(e),(l=r.getAttribute(\"id\"))?c=l.replace(n,\"\\\\$&\"):r.setAttribute(\"id\",c),c=\"[id='\"+c+\"'] \",f=a.length;while(f--)a[f]=c+a[f].join(\"\");h=z.test(e)&&r.parentNode||r,p=a.join(\",\")}if(p)try{return S.apply(s,x.call(h.querySelectorAll(p),0)),s}catch(v){}finally{l||r.removeAttribute(\"id\")}}return t(e,r,s,o,u)},u&&(K(function(t){e=u.call(t,\"div\");try{u.call(t,\"[test!='']:sizzle\"),s.push(\"!=\",H)}catch(n){}}),s=new RegExp(s.join(\"|\")),nt.matchesSelector=function(t,n){n=n.replace(r,\"='$1']\");if(!o(t)&&!s.test(n)&&!i.test(n))try{var a=u.call(t,n);if(a||e||t.document&&t.document.nodeType!==11)return a}catch(f){}return nt(n,null,null,[t]).length>0})}(),i.pseudos.nth=i.pseudos.eq,i.filters=mt.prototype=i.pseudos,i.setFilters=new mt,nt.attr=v.attr,v.find=nt,v.expr=nt.selectors,v.expr[\":\"]=v.expr.pseudos,v.unique=nt.uniqueSort,v.text=nt.getText,v.isXMLDoc=nt.isXML,v.contains=nt.contains}(e);var nt=/Until$/,rt=/^(?:parents|prev(?:Until|All))/,it=/^.[^:#\\[\\.,]*$/,st=v.expr.match.needsContext,ot={children:!0,contents:!0,next:!0,prev:!0};v.fn.extend({find:function(e){var t,n,r,i,s,o,u=this;if(typeof e!=\"string\")return v(e).filter(function(){for(t=0,n=u.length;t0)for(i=r;i=0:v.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,s=[],o=st.test(e)||typeof e!=\"string\"?v(e,t||this.context):0;for(;r-1:v.find.matchesSelector(n,e)){s.push(n);break}n=n.parentNode}}return s=s.length>1?v.unique(s):s,this.pushStack(s,\"closest\",e)},index:function(e){return e?typeof e==\"string\"?v.inArray(this[0],v(e)):v.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(e,t){var n=typeof e==\"string\"?v(e,t):v.makeArray(e&&e.nodeType?[e]:e),r=v.merge(this.get(),n);return this.pushStack(ut(n[0])||ut(r[0])?r:v.unique(r))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),v.fn.andSelf=v.fn.addBack,v.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return v.dir(e,\"parentNode\")},parentsUntil:function(e,t,n){return v.dir(e,\"parentNode\",n)},next:function(e){return at(e,\"nextSibling\")},prev:function(e){return at(e,\"previousSibling\")},nextAll:function(e){return v.dir(e,\"nextSibling\")},prevAll:function(e){return v.dir(e,\"previousSibling\")},nextUntil:function(e,t,n){return v.dir(e,\"nextSibling\",n)},prevUntil:function(e,t,n){return v.dir(e,\"previousSibling\",n)},siblings:function(e){return v.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return v.sibling(e.firstChild)},contents:function(e){return v.nodeName(e,\"iframe\")?e.contentDocument||e.contentWindow.document:v.merge([],e.childNodes)}},function(e,t){v.fn[e]=function(n,r){var i=v.map(this,t,n);return nt.test(e)||(r=n),r&&typeof r==\"string\"&&(i=v.filter(r,i)),i=this.length>1&&!ot[e]?v.unique(i):i,this.length>1&&rt.test(e)&&(i=i.reverse()),this.pushStack(i,e,l.call(arguments).join(\",\"))}}),v.extend({filter:function(e,t,n){return n&&(e=\":not(\"+e+\")\"),t.length===1?v.find.matchesSelector(t[0],e)?[t[0]]:[]:v.find.matches(e,t)},dir:function(e,n,r){var i=[],s=e[n];while(s&&s.nodeType!==9&&(r===t||s.nodeType!==1||!v(s).is(r)))s.nodeType===1&&i.push(s),s=s[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}});var ct=\"abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video\",ht=/ jQuery\\d+=\"(?:null|\\d+)\"/g,pt=/^\\s+/,dt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\\w:]+)[^>]*)\\/>/gi,vt=/<([\\w:]+)/,mt=/]\",\"i\"),Et=/^(?:checkbox|radio)$/,St=/checked\\s*(?:[^=]|=\\s*.checked.)/i,xt=/\\/(java|ecma)script/i,Tt=/^\\s*\\s*$/g,Nt={option:[1,\"\"],legend:[1,\"
\",\"
\"],thead:[1,\"\",\"
\"],tr:[2,\"\",\"
\"],td:[3,\"\",\"
\"],col:[2,\"\",\"
\"],area:[1,\"\",\"\"],_default:[0,\"\",\"\"]},Ct=lt(i),kt=Ct.appendChild(i.createElement(\"div\"));Nt.optgroup=Nt.option,Nt.tbody=Nt.tfoot=Nt.colgroup=Nt.caption=Nt.thead,Nt.th=Nt.td,v.support.htmlSerialize||(Nt._default=[1,\"X
\",\"
\"]),v.fn.extend({text:function(e){return v.access(this,function(e){return e===t?v.text(this):this.empty().append((this[0]&&this[0].ownerDocument||i).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(v.isFunction(e))return this.each(function(t){v(this).wrapAll(e.call(this,t))});if(this[0]){var t=v(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return v.isFunction(e)?this.each(function(t){v(this).wrapInner(e.call(this,t))}):this.each(function(){var t=v(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v.isFunction(e);return this.each(function(n){v(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){v.nodeName(this,\"body\")||v(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(e,this.firstChild)})},before:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(e,this),\"before\",this.selector)}},after:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this.nextSibling)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(this,e),\"after\",this.selector)}},remove:function(e,t){var n,r=0;for(;(n=this[r])!=null;r++)if(!e||v.filter(e,[n]).length)!t&&n.nodeType===1&&(v.cleanData(n.getElementsByTagName(\"*\")),v.cleanData([n])),n.parentNode&&n.parentNode.removeChild(n);return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&v.cleanData(e.getElementsByTagName(\"*\"));while(e.firstChild)e.removeChild(e.firstChild)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return v.clone(this,e,t)})},html:function(e){return v.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return n.nodeType===1?n.innerHTML.replace(ht,\"\"):t;if(typeof e==\"string\"&&!yt.test(e)&&(v.support.htmlSerialize||!wt.test(e))&&(v.support.leadingWhitespace||!pt.test(e))&&!Nt[(vt.exec(e)||[\"\",\"\"])[1].toLowerCase()]){e=e.replace(dt,\"<$1>\");try{for(;r1&&typeof f==\"string\"&&St.test(f))return this.each(function(){v(this).domManip(e,n,r)});if(v.isFunction(f))return this.each(function(i){var s=v(this);e[0]=f.call(this,i,n?s.html():t),s.domManip(e,n,r)});if(this[0]){i=v.buildFragment(e,this,l),o=i.fragment,s=o.firstChild,o.childNodes.length===1&&(o=s);if(s){n=n&&v.nodeName(s,\"tr\");for(u=i.cacheable||c-1;a0?this.clone(!0):this).get(),v(o[i])[t](r),s=s.concat(r);return this.pushStack(s,e,o.selector)}}),v.extend({clone:function(e,t,n){var r,i,s,o;v.support.html5Clone||v.isXMLDoc(e)||!wt.test(\"<\"+e.nodeName+\">\")?o=e.cloneNode(!0):(kt.innerHTML=e.outerHTML,kt.removeChild(o=kt.firstChild));if((!v.support.noCloneEvent||!v.support.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!v.isXMLDoc(e)){Ot(e,o),r=Mt(e),i=Mt(o);for(s=0;r[s];++s)i[s]&&Ot(r[s],i[s])}if(t){At(e,o);if(n){r=Mt(e),i=Mt(o);for(s=0;r[s];++s)At(r[s],i[s])}}return r=i=null,o},clean:function(e,t,n,r){var s,o,u,a,f,l,c,h,p,d,m,g,y=t===i&&Ct,b=[];if(!t||typeof t.createDocumentFragment==\"undefined\")t=i;for(s=0;(u=e[s])!=null;s++){typeof u==\"number\"&&(u+=\"\");if(!u)continue;if(typeof u==\"string\")if(!gt.test(u))u=t.createTextNode(u);else{y=y||lt(t),c=t.createElement(\"div\"),y.appendChild(c),u=u.replace(dt,\"<$1>\"),a=(vt.exec(u)||[\"\",\"\"])[1].toLowerCase(),f=Nt[a]||Nt._default,l=f[0],c.innerHTML=f[1]+u+f[2];while(l--)c=c.lastChild;if(!v.support.tbody){h=mt.test(u),p=a===\"table\"&&!h?c.firstChild&&c.firstChild.childNodes:f[1]===\"\"&&!h?c.childNodes:[];for(o=p.length-1;o>=0;--o)v.nodeName(p[o],\"tbody\")&&!p[o].childNodes.length&&p[o].parentNode.removeChild(p[o])}!v.support.leadingWhitespace&&pt.test(u)&&c.insertBefore(t.createTextNode(pt.exec(u)[0]),c.firstChild),u=c.childNodes,c.parentNode.removeChild(c)}u.nodeType?b.push(u):v.merge(b,u)}c&&(u=c=y=null);if(!v.support.appendChecked)for(s=0;(u=b[s])!=null;s++)v.nodeName(u,\"input\")?_t(u):typeof u.getElementsByTagName!=\"undefined\"&&v.grep(u.getElementsByTagName(\"input\"),_t);if(n){m=function(e){if(!e.type||xt.test(e.type))return r?r.push(e.parentNode?e.parentNode.removeChild(e):e):n.appendChild(e)};for(s=0;(u=b[s])!=null;s++)if(!v.nodeName(u,\"script\")||!m(u))n.appendChild(u),typeof u.getElementsByTagName!=\"undefined\"&&(g=v.grep(v.merge([],u.getElementsByTagName(\"script\")),m),b.splice.apply(b,[s+1,0].concat(g)),s+=g.length)}return b},cleanData:function(e,t){var n,r,i,s,o=0,u=v.expando,a=v.cache,f=v.support.deleteExpando,l=v.event.special;for(;(i=e[o])!=null;o++)if(t||v.acceptData(i)){r=i[u],n=r&&a[r];if(n){if(n.events)for(s in n.events)l[s]?v.event.remove(i,s):v.removeEvent(i,s,n.handle);a[r]&&(delete a[r],f?delete i[u]:i.removeAttribute?i.removeAttribute(u):i[u]=null,v.deletedIds.push(r))}}}}),function(){var e,t;v.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \\/]([\\w.]+)/.exec(e)||/(webkit)[ \\/]([\\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \\/]([\\w.]+)/.exec(e)||/(msie) ([\\w.]+)/.exec(e)||e.indexOf(\"compatible\")<0&&/(mozilla)(?:.*? rv:([\\w.]+)|)/.exec(e)||[];return{browser:t[1]||\"\",version:t[2]||\"0\"}},e=v.uaMatch(o.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),v.browser=t,v.sub=function(){function e(t,n){return new e.fn.init(t,n)}v.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(r,i){return i&&i instanceof v&&!(i instanceof e)&&(i=e(i)),v.fn.init.call(this,r,i,t)},e.fn.init.prototype=e.fn;var t=e(i);return e}}();var Dt,Pt,Ht,Bt=/alpha\\([^)]*\\)/i,jt=/opacity=([^)]*)/,Ft=/^(top|right|bottom|left)$/,It=/^(none|table(?!-c[ea]).+)/,qt=/^margin/,Rt=new RegExp(\"^(\"+m+\")(.*)$\",\"i\"),Ut=new RegExp(\"^(\"+m+\")(?!px)[a-z%]+$\",\"i\"),zt=new RegExp(\"^([-+])=(\"+m+\")\",\"i\"),Wt={BODY:\"block\"},Xt={position:\"absolute\",visibility:\"hidden\",display:\"block\"},Vt={letterSpacing:0,fontWeight:400},$t=[\"Top\",\"Right\",\"Bottom\",\"Left\"],Jt=[\"Webkit\",\"O\",\"Moz\",\"ms\"],Kt=v.fn.toggle;v.fn.extend({css:function(e,n){return v.access(this,function(e,n,r){return r!==t?v.style(e,n,r):v.css(e,n)},e,n,arguments.length>1)},show:function(){return Yt(this,!0)},hide:function(){return Yt(this)},toggle:function(e,t){var n=typeof e==\"boolean\";return v.isFunction(e)&&v.isFunction(t)?Kt.apply(this,arguments):this.each(function(){(n?e:Gt(this))?v(this).show():v(this).hide()})}}),v.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Dt(e,\"opacity\");return n===\"\"?\"1\":n}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{\"float\":v.support.cssFloat?\"cssFloat\":\"styleFloat\"},style:function(e,n,r,i){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var s,o,u,a=v.camelCase(n),f=e.style;n=v.cssProps[a]||(v.cssProps[a]=Qt(f,a)),u=v.cssHooks[n]||v.cssHooks[a];if(r===t)return u&&\"get\"in u&&(s=u.get(e,!1,i))!==t?s:f[n];o=typeof r,o===\"string\"&&(s=zt.exec(r))&&(r=(s[1]+1)*s[2]+parseFloat(v.css(e,n)),o=\"number\");if(r==null||o===\"number\"&&isNaN(r))return;o===\"number\"&&!v.cssNumber[a]&&(r+=\"px\");if(!u||!(\"set\"in u)||(r=u.set(e,r,i))!==t)try{f[n]=r}catch(l){}},css:function(e,n,r,i){var s,o,u,a=v.camelCase(n);return n=v.cssProps[a]||(v.cssProps[a]=Qt(e.style,a)),u=v.cssHooks[n]||v.cssHooks[a],u&&\"get\"in u&&(s=u.get(e,!0,i)),s===t&&(s=Dt(e,n)),s===\"normal\"&&n in Vt&&(s=Vt[n]),r||i!==t?(o=parseFloat(s),r||v.isNumeric(o)?o||0:s):s},swap:function(e,t,n){var r,i,s={};for(i in t)s[i]=e.style[i],e.style[i]=t[i];r=n.call(e);for(i in t)e.style[i]=s[i];return r}}),e.getComputedStyle?Dt=function(t,n){var r,i,s,o,u=e.getComputedStyle(t,null),a=t.style;return u&&(r=u.getPropertyValue(n)||u[n],r===\"\"&&!v.contains(t.ownerDocument,t)&&(r=v.style(t,n)),Ut.test(r)&&qt.test(n)&&(i=a.width,s=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=r,r=u.width,a.width=i,a.minWidth=s,a.maxWidth=o)),r}:i.documentElement.currentStyle&&(Dt=function(e,t){var n,r,i=e.currentStyle&&e.currentStyle[t],s=e.style;return i==null&&s&&s[t]&&(i=s[t]),Ut.test(i)&&!Ft.test(t)&&(n=s.left,r=e.runtimeStyle&&e.runtimeStyle.left,r&&(e.runtimeStyle.left=e.currentStyle.left),s.left=t===\"fontSize\"?\"1em\":i,i=s.pixelLeft+\"px\",s.left=n,r&&(e.runtimeStyle.left=r)),i===\"\"?\"auto\":i}),v.each([\"height\",\"width\"],function(e,t){v.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&It.test(Dt(e,\"display\"))?v.swap(e,Xt,function(){return tn(e,t,r)}):tn(e,t,r)},set:function(e,n,r){return Zt(e,n,r?en(e,t,r,v.support.boxSizing&&v.css(e,\"boxSizing\")===\"border-box\"):0)}}}),v.support.opacity||(v.cssHooks.opacity={get:function(e,t){return jt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||\"\")?.01*parseFloat(RegExp.$1)+\"\":t?\"1\":\"\"},set:function(e,t){var n=e.style,r=e.currentStyle,i=v.isNumeric(t)?\"alpha(opacity=\"+t*100+\")\":\"\",s=r&&r.filter||n.filter||\"\";n.zoom=1;if(t>=1&&v.trim(s.replace(Bt,\"\"))===\"\"&&n.removeAttribute){n.removeAttribute(\"filter\");if(r&&!r.filter)return}n.filter=Bt.test(s)?s.replace(Bt,i):s+\" \"+i}}),v(function(){v.support.reliableMarginRight||(v.cssHooks.marginRight={get:function(e,t){return v.swap(e,{display:\"inline-block\"},function(){if(t)return Dt(e,\"marginRight\")})}}),!v.support.pixelPosition&&v.fn.position&&v.each([\"top\",\"left\"],function(e,t){v.cssHooks[t]={get:function(e,n){if(n){var r=Dt(e,t);return Ut.test(r)?v(e).position()[t]+\"px\":r}}}})}),v.expr&&v.expr.filters&&(v.expr.filters.hidden=function(e){return e.offsetWidth===0&&e.offsetHeight===0||!v.support.reliableHiddenOffsets&&(e.style&&e.style.display||Dt(e,\"display\"))===\"none\"},v.expr.filters.visible=function(e){return!v.expr.filters.hidden(e)}),v.each({margin:\"\",padding:\"\",border:\"Width\"},function(e,t){v.cssHooks[e+t]={expand:function(n){var r,i=typeof n==\"string\"?n.split(\" \"):[n],s={};for(r=0;r<4;r++)s[e+$t[r]+t]=i[r]||i[r-2]||i[0];return s}},qt.test(e)||(v.cssHooks[e+t].set=Zt)});var rn=/%20/g,sn=/\\[\\]$/,on=/\\r?\\n/g,un=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,an=/^(?:select|textarea)/i;v.fn.extend({serialize:function(){return v.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?v.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||an.test(this.nodeName)||un.test(this.type))}).map(function(e,t){var n=v(this).val();return n==null?null:v.isArray(n)?v.map(n,function(e,n){return{name:t.name,value:e.replace(on,\"\\r\\n\")}}):{name:t.name,value:n.replace(on,\"\\r\\n\")}}).get()}}),v.param=function(e,n){var r,i=[],s=function(e,t){t=v.isFunction(t)?t():t==null?\"\":t,i[i.length]=encodeURIComponent(e)+\"=\"+encodeURIComponent(t)};n===t&&(n=v.ajaxSettings&&v.ajaxSettings.traditional);if(v.isArray(e)||e.jquery&&!v.isPlainObject(e))v.each(e,function(){s(this.name,this.value)});else for(r in e)fn(r,e[r],n,s);return i.join(\"&\").replace(rn,\"+\")};var ln,cn,hn=/#.*$/,pn=/^(.*?):[ \\t]*([^\\r\\n]*)\\r?$/mg,dn=/^(?:about|app|app\\-storage|.+\\-extension|file|res|widget):$/,vn=/^(?:GET|HEAD)$/,mn=/^\\/\\//,gn=/\\?/,yn=/)<[^<]*)*<\\/script>/gi,bn=/([?&])_=[^&]*/,wn=/^([\\w\\+\\.\\-]+:)(?:\\/\\/([^\\/?#:]*)(?::(\\d+)|)|)/,En=v.fn.load,Sn={},xn={},Tn=[\"*/\"]+[\"*\"];try{cn=s.href}catch(Nn){cn=i.createElement(\"a\"),cn.href=\"\",cn=cn.href}ln=wn.exec(cn.toLowerCase())||[],v.fn.load=function(e,n,r){if(typeof e!=\"string\"&&En)return En.apply(this,arguments);if(!this.length)return this;var i,s,o,u=this,a=e.indexOf(\" \");return a>=0&&(i=e.slice(a,e.length),e=e.slice(0,a)),v.isFunction(n)?(r=n,n=t):n&&typeof n==\"object\"&&(s=\"POST\"),v.ajax({url:e,type:s,dataType:\"html\",data:n,complete:function(e,t){r&&u.each(r,o||[e.responseText,t,e])}}).done(function(e){o=arguments,u.html(i?v(\"
\").append(e.replace(yn,\"\")).find(i):e)}),this},v.each(\"ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend\".split(\" \"),function(e,t){v.fn[t]=function(e){return this.on(t,e)}}),v.each([\"get\",\"post\"],function(e,n){v[n]=function(e,r,i,s){return v.isFunction(r)&&(s=s||i,i=r,r=t),v.ajax({type:n,url:e,data:r,success:i,dataType:s})}}),v.extend({getScript:function(e,n){return v.get(e,t,n,\"script\")},getJSON:function(e,t,n){return v.get(e,t,n,\"json\")},ajaxSetup:function(e,t){return t?Ln(e,v.ajaxSettings):(t=e,e=v.ajaxSettings),Ln(e,t),e},ajaxSettings:{url:cn,isLocal:dn.test(ln[1]),global:!0,type:\"GET\",contentType:\"application/x-www-form-urlencoded; charset=UTF-8\",processData:!0,async:!0,accepts:{xml:\"application/xml, text/xml\",html:\"text/html\",text:\"text/plain\",json:\"application/json, text/javascript\",\"*\":Tn},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:\"responseXML\",text:\"responseText\"},converters:{\"* text\":e.String,\"text html\":!0,\"text json\":v.parseJSON,\"text xml\":v.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:Cn(Sn),ajaxTransport:Cn(xn),ajax:function(e,n){function T(e,n,s,a){var l,y,b,w,S,T=n;if(E===2)return;E=2,u&&clearTimeout(u),o=t,i=a||\"\",x.readyState=e>0?4:0,s&&(w=An(c,x,s));if(e>=200&&e<300||e===304)c.ifModified&&(S=x.getResponseHeader(\"Last-Modified\"),S&&(v.lastModified[r]=S),S=x.getResponseHeader(\"Etag\"),S&&(v.etag[r]=S)),e===304?(T=\"notmodified\",l=!0):(l=On(c,w),T=l.state,y=l.data,b=l.error,l=!b);else{b=T;if(!T||e)T=\"error\",e<0&&(e=0)}x.status=e,x.statusText=(n||T)+\"\",l?d.resolveWith(h,[y,T,x]):d.rejectWith(h,[x,T,b]),x.statusCode(g),g=t,f&&p.trigger(\"ajax\"+(l?\"Success\":\"Error\"),[x,c,l?y:b]),m.fireWith(h,[x,T]),f&&(p.trigger(\"ajaxComplete\",[x,c]),--v.active||v.event.trigger(\"ajaxStop\"))}typeof e==\"object\"&&(n=e,e=t),n=n||{};var r,i,s,o,u,a,f,l,c=v.ajaxSetup({},n),h=c.context||c,p=h!==c&&(h.nodeType||h instanceof v)?v(h):v.event,d=v.Deferred(),m=v.Callbacks(\"once memory\"),g=c.statusCode||{},b={},w={},E=0,S=\"canceled\",x={readyState:0,setRequestHeader:function(e,t){if(!E){var n=e.toLowerCase();e=w[n]=w[n]||e,b[e]=t}return this},getAllResponseHeaders:function(){return E===2?i:null},getResponseHeader:function(e){var n;if(E===2){if(!s){s={};while(n=pn.exec(i))s[n[1].toLowerCase()]=n[2]}n=s[e.toLowerCase()]}return n===t?null:n},overrideMimeType:function(e){return E||(c.mimeType=e),this},abort:function(e){return e=e||S,o&&o.abort(e),T(0,e),this}};d.promise(x),x.success=x.done,x.error=x.fail,x.complete=m.add,x.statusCode=function(e){if(e){var t;if(E<2)for(t in e)g[t]=[g[t],e[t]];else t=e[x.status],x.always(t)}return this},c.url=((e||c.url)+\"\").replace(hn,\"\").replace(mn,ln[1]+\"//\"),c.dataTypes=v.trim(c.dataType||\"*\").toLowerCase().split(y),c.crossDomain==null&&(a=wn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===ln[1]&&a[2]===ln[2]&&(a[3]||(a[1]===\"http:\"?80:443))==(ln[3]||(ln[1]===\"http:\"?80:443)))),c.data&&c.processData&&typeof c.data!=\"string\"&&(c.data=v.param(c.data,c.traditional)),kn(Sn,c,n,x);if(E===2)return x;f=c.global,c.type=c.type.toUpperCase(),c.hasContent=!vn.test(c.type),f&&v.active++===0&&v.event.trigger(\"ajaxStart\");if(!c.hasContent){c.data&&(c.url+=(gn.test(c.url)?\"&\":\"?\")+c.data,delete c.data),r=c.url;if(c.cache===!1){var N=v.now(),C=c.url.replace(bn,\"$1_=\"+N);c.url=C+(C===c.url?(gn.test(c.url)?\"&\":\"?\")+\"_=\"+N:\"\")}}(c.data&&c.hasContent&&c.contentType!==!1||n.contentType)&&x.setRequestHeader(\"Content-Type\",c.contentType),c.ifModified&&(r=r||c.url,v.lastModified[r]&&x.setRequestHeader(\"If-Modified-Since\",v.lastModified[r]),v.etag[r]&&x.setRequestHeader(\"If-None-Match\",v.etag[r])),x.setRequestHeader(\"Accept\",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+(c.dataTypes[0]!==\"*\"?\", \"+Tn+\"; q=0.01\":\"\"):c.accepts[\"*\"]);for(l in c.headers)x.setRequestHeader(l,c.headers[l]);if(!c.beforeSend||c.beforeSend.call(h,x,c)!==!1&&E!==2){S=\"abort\";for(l in{success:1,error:1,complete:1})x[l](c[l]);o=kn(xn,c,n,x);if(!o)T(-1,\"No Transport\");else{x.readyState=1,f&&p.trigger(\"ajaxSend\",[x,c]),c.async&&c.timeout>0&&(u=setTimeout(function(){x.abort(\"timeout\")},c.timeout));try{E=1,o.send(b,T)}catch(k){if(!(E<2))throw k;T(-1,k)}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var Mn=[],_n=/\\?/,Dn=/(=)\\?(?=&|$)|\\?\\?/,Pn=v.now();v.ajaxSetup({jsonp:\"callback\",jsonpCallback:function(){var e=Mn.pop()||v.expando+\"_\"+Pn++;return this[e]=!0,e}}),v.ajaxPrefilter(\"json jsonp\",function(n,r,i){var s,o,u,a=n.data,f=n.url,l=n.jsonp!==!1,c=l&&Dn.test(f),h=l&&!c&&typeof a==\"string\"&&!(n.contentType||\"\").indexOf(\"application/x-www-form-urlencoded\")&&Dn.test(a);if(n.dataTypes[0]===\"jsonp\"||c||h)return s=n.jsonpCallback=v.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,o=e[s],c?n.url=f.replace(Dn,\"$1\"+s):h?n.data=a.replace(Dn,\"$1\"+s):l&&(n.url+=(_n.test(f)?\"&\":\"?\")+n.jsonp+\"=\"+s),n.converters[\"script json\"]=function(){return u||v.error(s+\" was not called\"),u[0]},n.dataTypes[0]=\"json\",e[s]=function(){u=arguments},i.always(function(){e[s]=o,n[s]&&(n.jsonpCallback=r.jsonpCallback,Mn.push(s)),u&&v.isFunction(o)&&o(u[0]),u=o=t}),\"script\"}),v.ajaxSetup({accepts:{script:\"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript\"},contents:{script:/javascript|ecmascript/},converters:{\"text script\":function(e){return v.globalEval(e),e}}}),v.ajaxPrefilter(\"script\",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type=\"GET\",e.global=!1)}),v.ajaxTransport(\"script\",function(e){if(e.crossDomain){var n,r=i.head||i.getElementsByTagName(\"head\")[0]||i.documentElement;return{send:function(s,o){n=i.createElement(\"script\"),n.async=\"async\",e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,i){if(i||!n.readyState||/loaded|complete/.test(n.readyState))n.onload=n.onreadystatechange=null,r&&n.parentNode&&r.removeChild(n),n=t,i||o(200,\"success\")},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(0,1)}}}});var Hn,Bn=e.ActiveXObject?function(){for(var e in Hn)Hn[e](0,1)}:!1,jn=0;v.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&Fn()||In()}:Fn,function(e){v.extend(v.support,{ajax:!!e,cors:!!e&&\"withCredentials\"in e})}(v.ajaxSettings.xhr()),v.support.ajax&&v.ajaxTransport(function(n){if(!n.crossDomain||v.support.cors){var r;return{send:function(i,s){var o,u,a=n.xhr();n.username?a.open(n.type,n.url,n.async,n.username,n.password):a.open(n.type,n.url,n.async);if(n.xhrFields)for(u in n.xhrFields)a[u]=n.xhrFields[u];n.mimeType&&a.overrideMimeType&&a.overrideMimeType(n.mimeType),!n.crossDomain&&!i[\"X-Requested-With\"]&&(i[\"X-Requested-With\"]=\"XMLHttpRequest\");try{for(u in i)a.setRequestHeader(u,i[u])}catch(f){}a.send(n.hasContent&&n.data||null),r=function(e,i){var u,f,l,c,h;try{if(r&&(i||a.readyState===4)){r=t,o&&(a.onreadystatechange=v.noop,Bn&&delete Hn[o]);if(i)a.readyState!==4&&a.abort();else{u=a.status,l=a.getAllResponseHeaders(),c={},h=a.responseXML,h&&h.documentElement&&(c.xml=h);try{c.text=a.responseText}catch(p){}try{f=a.statusText}catch(p){f=\"\"}!u&&n.isLocal&&!n.crossDomain?u=c.text?200:404:u===1223&&(u=204)}}}catch(d){i||s(-1,d)}c&&s(u,f,c,l)},n.async?a.readyState===4?setTimeout(r,0):(o=++jn,Bn&&(Hn||(Hn={},v(e).unload(Bn)),Hn[o]=r),a.onreadystatechange=r):r()},abort:function(){r&&r(0,1)}}}});var qn,Rn,Un=/^(?:toggle|show|hide)$/,zn=new RegExp(\"^(?:([-+])=|)(\"+m+\")([a-z%]*)$\",\"i\"),Wn=/queueHooks$/,Xn=[Gn],Vn={\"*\":[function(e,t){var n,r,i=this.createTween(e,t),s=zn.exec(t),o=i.cur(),u=+o||0,a=1,f=20;if(s){n=+s[2],r=s[3]||(v.cssNumber[e]?\"\":\"px\");if(r!==\"px\"&&u){u=v.css(i.elem,e,!0)||n||1;do a=a||\".5\",u/=a,v.style(i.elem,e,u+r);while(a!==(a=i.cur()/o)&&a!==1&&--f)}i.unit=r,i.start=u,i.end=s[1]?u+(s[1]+1)*n:n}return i}]};v.Animation=v.extend(Kn,{tweener:function(e,t){v.isFunction(e)?(t=e,e=[\"*\"]):e=e.split(\" \");var n,r=0,i=e.length;for(;r-1,f={},l={},c,h;a?(l=i.position(),c=l.top,h=l.left):(c=parseFloat(o)||0,h=parseFloat(u)||0),v.isFunction(t)&&(t=t.call(e,n,s)),t.top!=null&&(f.top=t.top-s.top+c),t.left!=null&&(f.left=t.left-s.left+h),\"using\"in t?t.using.call(e,f):i.css(f)}},v.fn.extend({position:function(){if(!this[0])return;var e=this[0],t=this.offsetParent(),n=this.offset(),r=er.test(t[0].nodeName)?{top:0,left:0}:t.offset();return n.top-=parseFloat(v.css(e,\"marginTop\"))||0,n.left-=parseFloat(v.css(e,\"marginLeft\"))||0,r.top+=parseFloat(v.css(t[0],\"borderTopWidth\"))||0,r.left+=parseFloat(v.css(t[0],\"borderLeftWidth\"))||0,{top:n.top-r.top,left:n.left-r.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||i.body;while(e&&!er.test(e.nodeName)&&v.css(e,\"position\")===\"static\")e=e.offsetParent;return e||i.body})}}),v.each({scrollLeft:\"pageXOffset\",scrollTop:\"pageYOffset\"},function(e,n){var r=/Y/.test(n);v.fn[e]=function(i){return v.access(this,function(e,i,s){var o=tr(e);if(s===t)return o?n in o?o[n]:o.document.documentElement[i]:e[i];o?o.scrollTo(r?v(o).scrollLeft():s,r?s:v(o).scrollTop()):e[i]=s},e,i,arguments.length,null)}}),v.each({Height:\"height\",Width:\"width\"},function(e,n){v.each({padding:\"inner\"+e,content:n,\"\":\"outer\"+e},function(r,i){v.fn[i]=function(i,s){var o=arguments.length&&(r||typeof i!=\"boolean\"),u=r||(i===!0||s===!0?\"margin\":\"border\");return v.access(this,function(n,r,i){var s;return v.isWindow(n)?n.document.documentElement[\"client\"+e]:n.nodeType===9?(s=n.documentElement,Math.max(n.body[\"scroll\"+e],s[\"scroll\"+e],n.body[\"offset\"+e],s[\"offset\"+e],s[\"client\"+e])):i===t?v.css(n,r,i,u):v.style(n,r,i,u)},n,o?i:t,o,null)}})}),e.jQuery=e.$=v,typeof define==\"function\"&&define.amd&&define.amd.jQuery&&define(\"jquery\",[],function(){return v})})(window);"; +} + +} // namespace brpc diff --git a/src/brpc/builtin/jquery_min_js.h b/src/brpc/builtin/jquery_min_js.h new file mode 100644 index 0000000..dc6b9ce --- /dev/null +++ b/src/brpc/builtin/jquery_min_js.h @@ -0,0 +1,37 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_BUILTIN_JQUERY_MIN_JS_H +#define BRPC_BUILTIN_JQUERY_MIN_JS_H + +#include "butil/iobuf.h" + + +namespace brpc { + +// Get the jquery.min.js as string or IOBuf. +// We need to pack all js inside C++ code so that builtin services can be +// accessed without external resources and network connection. +const char* jquery_min_js(); +const butil::IOBuf& jquery_min_js_iobuf(); +const butil::IOBuf& jquery_min_js_iobuf_gzip(); + +} // namespace brpc + + +#endif // BRPC_BUILTIN_JQUERY_MIN_JS_H diff --git a/src/brpc/builtin/list_service.cpp b/src/brpc/builtin/list_service.cpp new file mode 100644 index 0000000..17fa941 --- /dev/null +++ b/src/brpc/builtin/list_service.cpp @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // std::vector +#include // ServiceDescriptor +#include "brpc/controller.h" // Controller +#include "brpc/server.h" // Server +#include "brpc/closure_guard.h" // ClosureGuard +#include "brpc/builtin/list_service.h" + + +namespace brpc { + +void ListService::default_method(::google::protobuf::RpcController*, + const ::brpc::ListRequest*, + ::brpc::ListResponse* response, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + std::vector services; + _server->ListServices(&services); + for (size_t i = 0; i < services.size(); ++i) { + google::protobuf::ServiceDescriptorProto *svc = response->add_service(); + services[i]->GetDescriptor()->CopyTo(svc); + } +} + +} // namespace brpc diff --git a/src/brpc/builtin/list_service.h b/src/brpc/builtin/list_service.h new file mode 100644 index 0000000..fafb419 --- /dev/null +++ b/src/brpc/builtin/list_service.h @@ -0,0 +1,44 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_LIST_SERVICE_H +#define BRPC_LIST_SERVICE_H + +#include +#include "brpc/builtin_service.pb.h" + +namespace brpc { + +class Server; + +class ListService : public list { +public: + explicit ListService(Server* server) : _server(server) {} + + void default_method(::google::protobuf::RpcController* cntl_base, + const ::brpc::ListRequest* request, + ::brpc::ListResponse* response, + ::google::protobuf::Closure* done); +private: + Server* _server; +}; + +} // namespace brpc + + +#endif //BRPC_LIST_SERVICE_H diff --git a/src/brpc/builtin/memory_service.cpp b/src/brpc/builtin/memory_service.cpp new file mode 100644 index 0000000..13589d0 --- /dev/null +++ b/src/brpc/builtin/memory_service.cpp @@ -0,0 +1,93 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "butil/time.h" +#include "butil/logging.h" +#include "brpc/controller.h" // Controller +#include "brpc/closure_guard.h" // ClosureGuard +#include "brpc/builtin/memory_service.h" +#include "brpc/details/tcmalloc_extension.h" +#include "brpc/details/jemalloc_profiler.h" + +namespace brpc { + +DEFINE_int32(max_tc_stats_buf_len, 32 * 1024, "max length of TCMalloc stats"); +BRPC_VALIDATE_GFLAG(max_tc_stats_buf_len, PositiveInteger); + +static inline void get_tcmalloc_num_prop(MallocExtension* malloc_ext, + const char* prop_name, + butil::IOBufBuilder& os) { + size_t value; + if (malloc_ext->GetNumericProperty(prop_name, &value)) { + os << prop_name << ": " << value << "\n"; + } +} + +static void get_tcmalloc_memory_info(butil::IOBuf& out) { + MallocExtension* malloc_ext = MallocExtension::instance(); + butil::IOBufBuilder os; + os << "------------------------------------------------\n"; + get_tcmalloc_num_prop(malloc_ext, "generic.total_physical_bytes", os); + get_tcmalloc_num_prop(malloc_ext, "generic.current_allocated_bytes", os); + get_tcmalloc_num_prop(malloc_ext, "generic.heap_size", os); + get_tcmalloc_num_prop(malloc_ext, "tcmalloc.current_total_thread_cache_bytes", os); + get_tcmalloc_num_prop(malloc_ext, "tcmalloc.central_cache_free_bytes", os); + get_tcmalloc_num_prop(malloc_ext, "tcmalloc.transfer_cache_free_bytes", os); + get_tcmalloc_num_prop(malloc_ext, "tcmalloc.thread_cache_free_bytes", os); + get_tcmalloc_num_prop(malloc_ext, "tcmalloc.pageheap_free_bytes", os); + get_tcmalloc_num_prop(malloc_ext, "tcmalloc.pageheap_unmapped_bytes", os); + + int32_t len = FLAGS_max_tc_stats_buf_len; + std::unique_ptr buf(new char[len]); + malloc_ext->GetStats(buf.get(), len); + os << buf.get(); + + os.move_to(out); +} + +static void get_jemalloc_memory_info(Controller* cntl) { + const brpc::URI& uri = cntl->http_request().uri(); + cntl->http_response().set_content_type("text/plain"); + + const std::string* uri_opts = uri.GetQuery("opts"); + std::string opts = !uri_opts || uri_opts->empty() ? "Ja" : *uri_opts; + cntl->response_attachment().append(StatsPrint(opts)); +} + +void MemoryService::default_method(::google::protobuf::RpcController* cntl_base, + const ::brpc::MemoryRequest*, + ::brpc::MemoryResponse*, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + auto cntl = static_cast(cntl_base); + cntl->http_response().set_content_type("text/plain"); + butil::IOBuf& resp = cntl->response_attachment(); + + if (IsTCMallocEnabled()) { + butil::IOBufBuilder os; + get_tcmalloc_memory_info(resp); + } else if (HasJemalloc()) { + // support ip:port/memory?opts=Ja + get_jemalloc_memory_info(cntl); + } else { + resp.append("tcmalloc or jemalloc is not enabled"); + cntl->http_response().set_status_code(HTTP_STATUS_FORBIDDEN); + return; + } +} + +} // namespace brpc diff --git a/src/brpc/builtin/memory_service.h b/src/brpc/builtin/memory_service.h new file mode 100644 index 0000000..4c54a3a --- /dev/null +++ b/src/brpc/builtin/memory_service.h @@ -0,0 +1,36 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_MALLOC_SERVICE_H +#define BRPC_MALLOC_SERVICE_H + +#include "brpc/builtin_service.pb.h" + +namespace brpc { + +class MemoryService : public memory { +public: + void default_method(::google::protobuf::RpcController* cntl_base, + const ::brpc::MemoryRequest* request, + ::brpc::MemoryResponse* response, + ::google::protobuf::Closure* done) override; +}; + +} // namespace brpc + + +#endif // BRPC_MALLOC_SERVICE_H diff --git a/src/brpc/builtin/pprof_perl.cpp b/src/brpc/builtin/pprof_perl.cpp new file mode 100644 index 0000000..0d85916 --- /dev/null +++ b/src/brpc/builtin/pprof_perl.cpp @@ -0,0 +1,5510 @@ +// Copyright (c) 1998-2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +#include "brpc/builtin/pprof_perl.h" + +namespace brpc { + +const char* pprof_perl() { + return "#! /usr/bin/env perl\n" + "use strict;\n" + "use warnings;\n" + "use Getopt::Long;\n" + "use Cwd;\n" + "use POSIX;\n" + "\n" + "my $PPROF_VERSION = \"2.0\";\n" + "\n" + "# These are the object tools we use which can come from a\n" + "# user-specified location using --tools, from the PPROF_TOOLS\n" + "# environment variable, or from the environment.\n" + "my %obj_tool_map = (\n" + " \"objdump\" => \"objdump\",\n" + " \"nm\" => \"nm\",\n" + " \"addr2line\" => \"addr2line\",\n" + " \"c++filt\" => \"c++filt\",\n" + " ## ConfigureObjTools may add architecture-specific entries:\n" + " #\"nm_pdb\" => \"nm-pdb\", # for reading windows (PDB-format) executables\n" + " #\"addr2line_pdb\" => \"addr2line-pdb\", # ditto\n" + " #\"otool\" => \"otool\", # equivalent of objdump on OS X\n" + ");\n" + "# NOTE: these are lists, so you can put in commandline flags if you want.\n" + "my @DOT = (\"dot\"); # leave non-absolute, since it may be in /usr/local\n" + "my @GV = (\"gv\");\n" + "my @EVINCE = (\"evince\"); # could also be xpdf or perhaps acroread\n" + "my @KCACHEGRIND = (\"kcachegrind\");\n" + "my @PS2PDF = (\"ps2pdf\");\n" + "# These are used for dynamic profiles\n" + "my @URL_FETCHER = (\"curl\", \"-s\");\n" + "\n" + "# These are the web pages that servers need to support for dynamic profiles\n" + "my $HEAP_PAGE = \"/pprof/heap\";\n" + "my $PROFILE_PAGE = \"/pprof/profile\"; # must support cgi-param \"?seconds=#\"\n" + "my $PMUPROFILE_PAGE = \"/pprof/pmuprofile(?:\\\\?.*)?\"; # must support cgi-param\n" + " # ?seconds=#&event=x&period=n\n" + "my $GROWTH_PAGE = \"/pprof/growth\";\n" + "my $CONTENTION_PAGE = \"/pprof/contention\";\n" + "my $WALL_PAGE = \"/pprof/wall(?:\\\\?.*)?\"; # accepts options like namefilter\n" + "my $FILTEREDPROFILE_PAGE = \"/pprof/filteredprofile(?:\\\\?.*)?\";\n" + "my $CENSUSPROFILE_PAGE = \"/pprof/censusprofile(?:\\\\?.*)?\"; # must support " + "cgi-param\n" + " # \"?seconds=#\",\n" + " # \"?tags_regexp=#\" and\n" + " # \"?type=#\".\n" + "my $SYMBOL_PAGE = \"/pprof/symbol\"; # must support symbol lookup via POST\n" + "my $PROGRAM_NAME_PAGE = \"/pprof/cmdline\";\n" + "\n" + "# These are the web pages that can be named on the command line.\n" + "# All the alternatives must begin with /.\n" + "my $PROFILES = \"($HEAP_PAGE|$PROFILE_PAGE|$PMUPROFILE_PAGE|\" .\n" + " \"$GROWTH_PAGE|$CONTENTION_PAGE|$WALL_PAGE|\" .\n" + " \"$FILTEREDPROFILE_PAGE|$CENSUSPROFILE_PAGE)\";\n" + "\n" + "# default binary name\n" + "my $UNKNOWN_BINARY = \"(unknown)\";\n" + "\n" + "# There is a pervasive dependency on the length (in hex characters,\n" + "# i.e., nibbles) of an address, distinguishing between 32-bit and\n" + "# 64-bit profiles. To err on the safe size, default to 64-bit here:\n" + "my $address_length = 16;\n" + "\n" + "my $dev_null = \"/dev/null\";\n" + "if (! -e $dev_null && $^O =~ /MSWin/) { # $^O is the OS perl was built for\n" + " $dev_null = \"nul\";\n" + "}\n" + "\n" + "# A list of paths to search for shared object files\n" + "my @prefix_list = ();\n" + "\n" + "# Special routine name that should not have any symbols.\n" + "# Used as separator to parse \"addr2line -i\" output.\n" + "my $sep_symbol = '_fini';\n" + "my $sep_address = undef;\n" + "\n" + "my @stackTraces;\n" + "\n" + "##### Argument parsing #####\n" + "\n" + "sub usage_string {\n" + " return < \n" + " is a space separated list of profile names.\n" + "$0 [options] \n" + " is a list of profile files where each file contains\n" + " the necessary symbol mappings as well as profile data (likely generated\n" + " with --raw).\n" + "$0 [options] \n" + " is a remote form. Symbols are obtained from host:port$SYMBOL_PAGE\n" + " Each name can be:\n" + " /path/to/profile - a path to a profile file\n" + " host:port[/] - a location of a service to get profile from\n" + " The / can be $HEAP_PAGE, $PROFILE_PAGE, /pprof/pmuprofile,\n" + " $GROWTH_PAGE, $CONTENTION_PAGE, /pprof/wall,\n" + " $CENSUSPROFILE_PAGE, or /pprof/filteredprofile.\n" + " For instance:\n" + " $0 http://myserver.com:80$HEAP_PAGE\n" + " If / is omitted, the service defaults to $PROFILE_PAGE (cpu profiling).\n" + "$0 --symbols \n" + " Maps addresses to symbol names. In this mode, stdin should be a\n" + " list of library mappings, in the same format as is found in the heap-\n" + " and cpu-profile files (this loosely matches that of /proc/self/maps\n" + " on linux), followed by a list of hex addresses to map, one per line.\n" + " For more help with querying remote servers, including how to add the\n" + " necessary server-side support code, see this filename (or one like it):\n" + " /usr/doc/gperftools-$PPROF_VERSION/pprof_remote_servers.html\n" + "Options:\n" + " --cum Sort by cumulative data\n" + " --base= Subtract from before display\n" + " --interactive Run in interactive mode (interactive \"help\" gives help) " + "[default]\n" + " --seconds= Length of time for dynamic profiles [default=30 secs]\n" + " --add_lib= Read additional symbols and line info from the given library\n" + " --lib_prefix= Comma separated list of library path prefixes\n" + " --no_strip_temp Do not strip template arguments from function names\n" + "Reporting Granularity:\n" + " --addresses Report at address level\n" + " --lines Report at source line level\n" + " --functions Report at function level [default]\n" + " --files Report at source file level\n" + "Output type:\n" + " --text Generate text report\n" + " --stacks Generate stack traces similar to the heap profiler (requires " + "--text)\n" + " --callgrind Generate callgrind format to stdout\n" + " --gv Generate Postscript and display\n" + " --evince Generate PDF and display\n" + " --web Generate SVG and display\n" + " --list= Generate source listing of matching routines\n" + " --disasm= Generate disassembly of matching routines\n" + " --symbols Print demangled symbol names found at given addresses\n" + " --dot Generate DOT file to stdout\n" + " --ps Generate Postscript to stdout\n" + " --pdf Generate PDF to stdout\n" + " --svg Generate SVG to stdout\n" + " --gif Generate GIF to stdout\n" + " --raw Generate symbolized pprof data (useful with remote fetch)\n" + " --collapsed Generate collapsed stacks for building flame graphs\n" + " (see http://www.brendangregg.com/flamegraphs.html)\n" + "Heap-Profile Options:\n" + " --inuse_space Display in-use (mega)bytes [default]\n" + " --inuse_objects Display in-use objects\n" + " --alloc_space Display allocated (mega)bytes\n" + " --alloc_objects Display allocated objects\n" + " --show_bytes Display space in bytes\n" + " --drop_negative Ignore negative differences\n" + "Contention-profile options:\n" + " --total_delay Display total delay at each region [default]\n" + " --contentions Display number of delays at each region\n" + " --mean_delay Display mean delay at each region\n" + "Call-graph Options:\n" + " --nodecount= Show at most so many nodes [default=80]\n" + " --nodefraction= Hide nodes below *total [default=.005]\n" + " --edgefraction= Hide edges below *total [default=.001]\n" + " --maxdegree= Max incoming/outgoing edges per node [default=8]\n" + " --focus= Focus on nodes matching \n" + " --ignore= Ignore nodes matching \n" + " --scale= Set GV scaling [default=0]\n" + " --heapcheck Make nodes with non-0 object counts\n" + " (i.e. direct leak generators) more visible\n" + "Miscellaneous:\n" + " --no-auto-signal-frm Automatically drop 2nd frame that is always same (cpu-only)\n" + " (assuming that it is artifact of bad stack captures\n" + " which include signal handler frames)\n" + " --show_addresses Always show addresses when applicable\n" + " --tools=[,...] \\$PATH for object tool pathnames\n" + " --test Run unit tests\n" + " --help This message\n" + " --version Version information\n" + "Environment Variables:\n" + " PPROF_TMPDIR Profiles directory. Defaults to \\$HOME/pprof\n" + " PPROF_TOOLS Prefix for object tools pathnames\n" + "Examples:\n" + "$0 /bin/ls ls.prof\n" + " Enters \"interactive\" mode\n" + "$0 --text /bin/ls ls.prof\n" + " Outputs one line per procedure\n" + "$0 --web /bin/ls ls.prof\n" + " Displays annotated call-graph in web browser\n" + "$0 --gv /bin/ls ls.prof\n" + " Displays annotated call-graph via 'gv'\n" + "$0 --gv --focus=Mutex /bin/ls ls.prof\n" + " Restricts to code paths including a .*Mutex.* entry\n" + "$0 --gv --focus=Mutex --ignore=string /bin/ls ls.prof\n" + " Code paths including Mutex but not string\n" + "$0 --list=getdir /bin/ls ls.prof\n" + " (Per-line) annotated source listing for getdir()\n" + "$0 --disasm=getdir /bin/ls ls.prof\n" + " (Per-PC) annotated disassembly for getdir()\n" + "$0 http://localhost:1234/\n" + " Enters \"interactive\" mode\n" + "$0 --text localhost:1234\n" + " Outputs one line per procedure for localhost:1234\n" + "$0 --raw localhost:1234 > ./local.raw\n" + "$0 --text ./local.raw\n" + " Fetches a remote profile for later analysis and then\n" + " analyzes it in text mode.\n" + "EOF\n" + "}\n" + "\n" + "sub version_string {\n" + " return < \\$main::opt_help,\n" + " \"version!\" => \\$main::opt_version,\n" + " \"show_addresses!\"=> \\$main::opt_show_addresses,\n" + " \"no-auto-signal-frm!\"=> \\$main::opt_no_auto_signal_frames,\n" + " \"cum!\" => \\$main::opt_cum,\n" + " \"base=s\" => \\$main::opt_base,\n" + " \"seconds=i\" => \\$main::opt_seconds,\n" + " \"add_lib=s\" => \\$main::opt_lib,\n" + " \"lib_prefix=s\" => \\$main::opt_lib_prefix,\n" + " \"functions!\" => \\$main::opt_functions,\n" + " \"lines!\" => \\$main::opt_lines,\n" + " \"addresses!\" => \\$main::opt_addresses,\n" + " \"files!\" => \\$main::opt_files,\n" + " \"text!\" => \\$main::opt_text,\n" + " \"stacks!\" => \\$main::opt_stacks,\n" + " \"callgrind!\" => \\$main::opt_callgrind,\n" + " \"list=s\" => \\$main::opt_list,\n" + " \"disasm=s\" => \\$main::opt_disasm,\n" + " \"symbols!\" => \\$main::opt_symbols,\n" + " \"gv!\" => \\$main::opt_gv,\n" + " \"evince!\" => \\$main::opt_evince,\n" + " \"web!\" => \\$main::opt_web,\n" + " \"dot!\" => \\$main::opt_dot,\n" + " \"ps!\" => \\$main::opt_ps,\n" + " \"pdf!\" => \\$main::opt_pdf,\n" + " \"svg!\" => \\$main::opt_svg,\n" + " \"gif!\" => \\$main::opt_gif,\n" + " \"raw!\" => \\$main::opt_raw,\n" + " \"collapsed!\" => \\$main::opt_collapsed,\n" + " \"interactive!\" => \\$main::opt_interactive,\n" + " \"nodecount=i\" => \\$main::opt_nodecount,\n" + " \"nodefraction=f\" => \\$main::opt_nodefraction,\n" + " \"edgefraction=f\" => \\$main::opt_edgefraction,\n" + " \"maxdegree=i\" => \\$main::opt_maxdegree,\n" + " \"focus=s\" => \\$main::opt_focus,\n" + " \"ignore=s\" => \\$main::opt_ignore,\n" + " \"scale=i\" => \\$main::opt_scale,\n" + " \"heapcheck\" => \\$main::opt_heapcheck,\n" + " \"inuse_space!\" => \\$main::opt_inuse_space,\n" + " \"inuse_objects!\" => \\$main::opt_inuse_objects,\n" + " \"alloc_space!\" => \\$main::opt_alloc_space,\n" + " \"alloc_objects!\" => \\$main::opt_alloc_objects,\n" + " \"show_bytes!\" => \\$main::opt_show_bytes,\n" + " \"drop_negative!\" => \\$main::opt_drop_negative,\n" + " \"total_delay!\" => \\$main::opt_total_delay,\n" + " \"contentions!\" => \\$main::opt_contentions,\n" + " \"mean_delay!\" => \\$main::opt_mean_delay,\n" + " \"tools=s\" => \\$main::opt_tools,\n" + " \"no_strip_temp!\" => \\$main::opt_no_strip_temp,\n" + " \"test!\" => \\$main::opt_test,\n" + " \"debug!\" => \\$main::opt_debug,\n" + " # Undocumented flags used only by unittests:\n" + " \"test_stride=i\" => \\$main::opt_test_stride,\n" + " ) || usage(\"Invalid option(s)\");\n" + "\n" + " # Deal with the standard --help and --version\n" + " if ($main::opt_help) {\n" + " print usage_string();\n" + " exit(0);\n" + " }\n" + "\n" + " if ($main::opt_version) {\n" + " print version_string();\n" + " exit(0);\n" + " }\n" + "\n" + " # Disassembly/listing/symbols mode requires address-level info\n" + " if ($main::opt_disasm || $main::opt_list || $main::opt_symbols) {\n" + " $main::opt_functions = 0;\n" + " $main::opt_lines = 0;\n" + " $main::opt_addresses = 1;\n" + " $main::opt_files = 0;\n" + " }\n" + "\n" + " # Check heap-profiling flags\n" + " if ($main::opt_inuse_space +\n" + " $main::opt_inuse_objects +\n" + " $main::opt_alloc_space +\n" + " $main::opt_alloc_objects > 1) {\n" + " usage(\"Specify at most on of --inuse/--alloc options\");\n" + " }\n" + "\n" + " # Check output granularities\n" + " my $grains =\n" + " $main::opt_functions +\n" + " $main::opt_lines +\n" + " $main::opt_addresses +\n" + " $main::opt_files +\n" + " 0;\n" + " if ($grains > 1) {\n" + " usage(\"Only specify one output granularity option\");\n" + " }\n" + " if ($grains == 0) {\n" + " $main::opt_functions = 1;\n" + " }\n" + "\n" + " # Check output modes\n" + " my $modes =\n" + " $main::opt_text +\n" + " $main::opt_callgrind +\n" + " ($main::opt_list eq '' ? 0 : 1) +\n" + " ($main::opt_disasm eq '' ? 0 : 1) +\n" + " ($main::opt_symbols == 0 ? 0 : 1) +\n" + " $main::opt_gv +\n" + " $main::opt_evince +\n" + " $main::opt_web +\n" + " $main::opt_dot +\n" + " $main::opt_ps +\n" + " $main::opt_pdf +\n" + " $main::opt_svg +\n" + " $main::opt_gif +\n" + " $main::opt_raw +\n" + " $main::opt_collapsed +\n" + " $main::opt_interactive +\n" + " 0;\n" + " if ($modes > 1) {\n" + " usage(\"Only specify one output mode\");\n" + " }\n" + " if ($modes == 0) {\n" + " if (-t STDOUT) { # If STDOUT is a tty, activate interactive mode\n" + " $main::opt_interactive = 1;\n" + " } else {\n" + " $main::opt_text = 1;\n" + " }\n" + " }\n" + "\n" + " if ($main::opt_test) {\n" + " RunUnitTests();\n" + " # Should not return\n" + " exit(1);\n" + " }\n" + "\n" + " # Binary name and profile arguments list\n" + " $main::prog = \"\";\n" + " @main::pfile_args = ();\n" + "\n" + " # Remote profiling without a binary (using $SYMBOL_PAGE instead)\n" + " if (@ARGV > 0) {\n" + " if (IsProfileURL($ARGV[0])) {\n" + " printf STDERR \"Using remote profile at $ARGV[0].\\n\";\n" + " $main::use_symbol_page = 1;\n" + " } elsif (IsSymbolizedProfileFile($ARGV[0])) {\n" + " $main::use_symbolized_profile = 1;\n" + " $main::prog = $UNKNOWN_BINARY; # will be set later from the profile file\n" + " }\n" + " }\n" + "\n" + " if ($main::use_symbol_page || $main::use_symbolized_profile) {\n" + " # We don't need a binary!\n" + " my %disabled = ('--lines' => $main::opt_lines,\n" + " '--disasm' => $main::opt_disasm);\n" + " for my $option (keys %disabled) {\n" + " usage(\"$option cannot be used without a binary\") if $disabled{$option};\n" + " }\n" + " # Set $main::prog later...\n" + " scalar(@ARGV) || usage(\"Did not specify profile file\");\n" + " } elsif ($main::opt_symbols) {\n" + " # --symbols needs a binary-name (to run nm on, etc) but not profiles\n" + " $main::prog = shift(@ARGV) || usage(\"Did not specify program\");\n" + " } else {\n" + " $main::prog = shift(@ARGV) || usage(\"Did not specify program\");\n" + " scalar(@ARGV) || usage(\"Did not specify profile file\");\n" + " }\n" + "\n" + " # Parse profile file/location arguments\n" + " foreach my $farg (@ARGV) {\n" + " if ($farg =~ m/(.*)\\@([0-9]+)(|\\/.*)$/ ) {\n" + " my $machine = $1;\n" + " my $num_machines = $2;\n" + " my $path = $3;\n" + " for (my $i = 0; $i < $num_machines; $i++) {\n" + " unshift(@main::pfile_args, \"$i.$machine$path\");\n" + " }\n" + " } else {\n" + " unshift(@main::pfile_args, $farg);\n" + " }\n" + " }\n" + "\n" + " if ($main::use_symbol_page) {\n" + " unless (IsProfileURL($main::pfile_args[0])) {\n" + " error(\"The first profile should be a remote form to use $SYMBOL_PAGE\\n\");\n" + " }\n" + " CheckSymbolPage();\n" + " $main::prog = FetchProgramName();\n" + " } elsif (!$main::use_symbolized_profile) { # may not need objtools!\n" + " ConfigureObjTools($main::prog)\n" + " }\n" + "\n" + " # Break the opt_lib_prefix into the prefix_list array\n" + " @prefix_list = split (',', $main::opt_lib_prefix);\n" + "\n" + " # Remove trailing / from the prefixes, in the list to prevent\n" + " # searching things like /my/path//lib/mylib.so\n" + " foreach (@prefix_list) {\n" + " s|/+$||;\n" + " }\n" + "}\n" + "\n" + "sub Main() {\n" + " Init();\n" + " $main::collected_profile = undef;\n" + " @main::profile_files = ();\n" + " $main::op_time = time();\n" + "\n" + " # Printing symbols is special and requires a lot less info that most.\n" + " if ($main::opt_symbols) {\n" + " PrintSymbols(*STDIN); # Get /proc/maps and symbols output from stdin\n" + " return;\n" + " }\n" + "\n" + " # Fetch all profile data\n" + " FetchDynamicProfiles();\n" + "\n" + " # this will hold symbols that we read from the profile files\n" + " my $symbol_map = {};\n" + "\n" + " # Read one profile, pick the last item on the list\n" + " my $data = ReadProfile($main::prog, pop(@main::profile_files));\n" + " my $profile = $data->{profile};\n" + " my $pcs = $data->{pcs};\n" + " my $libs = $data->{libs}; # Info about main program and shared libraries\n" + " $symbol_map = MergeSymbols($symbol_map, $data->{symbols});\n" + "\n" + " # Add additional profiles, if available.\n" + " if (scalar(@main::profile_files) > 0) {\n" + " foreach my $pname (@main::profile_files) {\n" + " my $data2 = ReadProfile($main::prog, $pname);\n" + " $profile = AddProfile($profile, $data2->{profile});\n" + " $pcs = AddPcs($pcs, $data2->{pcs});\n" + " $symbol_map = MergeSymbols($symbol_map, $data2->{symbols});\n" + " }\n" + " }\n" + "\n" + " # Subtract base from profile, if specified\n" + " if ($main::opt_base ne '') {\n" + " my $base = ReadProfile($main::prog, $main::opt_base);\n" + " $profile = SubtractProfile($profile, $base->{profile});\n" + " $pcs = AddPcs($pcs, $base->{pcs});\n" + " $symbol_map = MergeSymbols($symbol_map, $base->{symbols});\n" + " }\n" + "\n" + " # Get total data in profile\n" + " my $total = TotalProfile($profile);\n" + "\n" + " # Collect symbols\n" + " my $symbols;\n" + " if ($main::use_symbolized_profile) {\n" + " $symbols = FetchSymbols($pcs, $symbol_map);\n" + " } elsif ($main::use_symbol_page) {\n" + " $symbols = FetchSymbols($pcs);\n" + " } else {\n" + " # TODO(csilvers): $libs uses the /proc/self/maps data from profile1,\n" + " # which may differ from the data from subsequent profiles, especially\n" + " # if they were run on different machines. Use appropriate libs for\n" + " # each pc somehow.\n" + " $symbols = ExtractSymbols($libs, $pcs);\n" + " }\n" + "\n" + " # Remove uniniteresting stack items\n" + " $profile = RemoveUninterestingFrames($symbols, $profile);\n" + "\n" + " # Focus?\n" + " if ($main::opt_focus ne '') {\n" + " $profile = FocusProfile($symbols, $profile, $main::opt_focus);\n" + " }\n" + "\n" + " # Ignore?\n" + " if ($main::opt_ignore ne '') {\n" + " $profile = IgnoreProfile($symbols, $profile, $main::opt_ignore);\n" + " }\n" + "\n" + " my $calls = ExtractCalls($symbols, $profile);\n" + "\n" + " # Reduce profiles to required output granularity, and also clean\n" + " # each stack trace so a given entry exists at most once.\n" + " my $reduced = ReduceProfile($symbols, $profile);\n" + "\n" + " # Get derived profiles\n" + " my $flat = FlatProfile($reduced);\n" + " my $cumulative = CumulativeProfile($reduced);\n" + "\n" + " # Print\n" + " if (!$main::opt_interactive) {\n" + " if ($main::opt_disasm) {\n" + " PrintDisassembly($libs, $flat, $cumulative, $main::opt_disasm);\n" + " } elsif ($main::opt_list) {\n" + " PrintListing($total, $libs, $flat, $cumulative, $main::opt_list, 0);\n" + " } elsif ($main::opt_text) {\n" + " # Make sure the output is empty when have nothing to report\n" + " # (only matters when --heapcheck is given but we must be\n" + " # compatible with old branches that did not pass --heapcheck always):\n" + " if ($total != 0) {\n" + " printf(\"Total: %s %s\\n\", Unparse($total), Units());\n" + " }\n" + " if ($main::opt_stacks) {\n" + " printf(\"Stacks:\\n\\n\");\n" + " PrintStacksForText($symbols, $profile);\n" + " }\n" + " PrintText($symbols, $flat, $cumulative, -1);\n" + " } elsif ($main::opt_raw) {\n" + " PrintSymbolizedProfile($symbols, $profile, $main::prog);\n" + " } elsif ($main::opt_collapsed) {\n" + " PrintCollapsedStacks($symbols, $profile);\n" + " } elsif ($main::opt_callgrind) {\n" + " PrintCallgrind($calls);\n" + " } else {\n" + " if (PrintDot($main::prog, $symbols, $profile, $flat, $cumulative, $total)) {\n" + " if ($main::opt_gv) {\n" + " RunGV(TempName($main::next_tmpfile, \"ps\"), \"\");\n" + " } elsif ($main::opt_evince) {\n" + " RunEvince(TempName($main::next_tmpfile, \"pdf\"), \"\");\n" + " } elsif ($main::opt_web) {\n" + " my $tmp = TempName($main::next_tmpfile, \"svg\");\n" + " RunWeb($tmp);\n" + " # The command we run might hand the file name off\n" + " # to an already running browser instance and then exit.\n" + " # Normally, we'd remove $tmp on exit (right now),\n" + " # but fork a child to remove $tmp a little later, so that the\n" + " # browser has time to load it first.\n" + " delete $main::tempnames{$tmp};\n" + " if (fork() == 0) {\n" + " sleep 5;\n" + " unlink($tmp);\n" + " exit(0);\n" + " }\n" + " }\n" + " } else {\n" + " cleanup();\n" + " exit(1);\n" + " }\n" + " }\n" + " } else {\n" + " InteractiveMode($profile, $symbols, $libs, $total);\n" + " }\n" + "\n" + " cleanup();\n" + " exit(0);\n" + "}\n" + "\n" + "##### Entry Point #####\n" + "\n" + "Main();\n" + "\n" + "# Temporary code to detect if we're running on a Goobuntu system.\n" + "# These systems don't have the right stuff installed for the special\n" + "# Readline libraries to work, so as a temporary workaround, we default\n" + "# to using the normal stdio code, rather than the fancier readline-based\n" + "# code\n" + "sub ReadlineMightFail {\n" + " if (-e '/lib/libtermcap.so.2') {\n" + " return 0; # libtermcap exists, so readline should be okay\n" + " } else {\n" + " return 1;\n" + " }\n" + "}\n" + "\n" + "sub RunGV {\n" + " my $fname = shift;\n" + " my $bg = shift; # \"\" or \" &\" if we should run in background\n" + " if (!system(ShellEscape(@GV, \"--version\") . \" >$dev_null 2>&1\")) {\n" + " # Options using double dash are supported by this gv version.\n" + " # Also, turn on noantialias to better handle bug in gv for\n" + " # postscript files with large dimensions.\n" + " # TODO: Maybe we should not pass the --noantialias flag\n" + " # if the gv version is known to work properly without the flag.\n" + " system(ShellEscape(@GV, \"--scale=$main::opt_scale\", \"--noantialias\", $fname)\n" + " . $bg);\n" + " } else {\n" + " # Old gv version - only supports options that use single dash.\n" + " print STDERR ShellEscape(@GV, \"-scale\", $main::opt_scale) . \"\\n\";\n" + " system(ShellEscape(@GV, \"-scale\", \"$main::opt_scale\", $fname) . $bg);\n" + " }\n" + "}\n" + "\n" + "sub RunEvince {\n" + " my $fname = shift;\n" + " my $bg = shift; # \"\" or \" &\" if we should run in background\n" + " system(ShellEscape(@EVINCE, $fname) . $bg);\n" + "}\n" + "\n" + "sub RunWeb {\n" + " my $fname = shift;\n" + " print STDERR \"Loading web page file:///$fname\\n\";\n" + "\n" + " if (`uname` =~ /Darwin/) {\n" + " # OS X: open will use standard preference for SVG files.\n" + " system(\"/usr/bin/open\", $fname);\n" + " return;\n" + " }\n" + "\n" + " if (`uname` =~ /MINGW/) {\n" + " # Windows(MinGW): open will use standard preference for SVG files.\n" + " system(\"cmd\", \"/c\", \"start\", $fname);\n" + " return;\n" + " }\n" + "\n" + " # Some kind of Unix; try generic symlinks, then specific browsers.\n" + " # (Stop once we find one.)\n" + " # Works best if the browser is already running.\n" + " my @alt = (\n" + " \"/etc/alternatives/gnome-www-browser\",\n" + " \"/etc/alternatives/x-www-browser\",\n" + " \"google-chrome\",\n" + " \"firefox\",\n" + " );\n" + " foreach my $b (@alt) {\n" + " if (system($b, $fname) == 0) {\n" + " return;\n" + " }\n" + " }\n" + "\n" + " print STDERR \"Could not load web browser.\\n\";\n" + "}\n" + "\n" + "sub RunKcachegrind {\n" + " my $fname = shift;\n" + " my $bg = shift; # \"\" or \" &\" if we should run in background\n" + " print STDERR \"Starting '@KCACHEGRIND \" . $fname . $bg . \"'\\n\";\n" + " system(ShellEscape(@KCACHEGRIND, $fname) . $bg);\n" + "}\n" + "\n" + "\n" + "##### Interactive helper routines #####\n" + "\n" + "sub InteractiveMode {\n" + " $| = 1; # Make output unbuffered for interactive mode\n" + " my ($orig_profile, $symbols, $libs, $total) = @_;\n" + "\n" + " print STDERR \"Welcome to pprof! For help, type 'help'.\\n\";\n" + "\n" + " # Use ReadLine if it's installed and input comes from a console.\n" + " if ( -t STDIN &&\n" + " !ReadlineMightFail() &&\n" + " defined(eval {require Term::ReadLine}) ) {\n" + " my $term = new Term::ReadLine 'pprof';\n" + " while ( defined ($_ = $term->readline('(pprof) '))) {\n" + " $term->addhistory($_) if /\\S/;\n" + " if (!InteractiveCommand($orig_profile, $symbols, $libs, $total, $_)) {\n" + " last; # exit when we get an interactive command to quit\n" + " }\n" + " }\n" + " } else { # don't have readline\n" + " while (1) {\n" + " print STDERR \"(pprof) \";\n" + " $_ = ;\n" + " last if ! defined $_ ;\n" + " s/\\r//g; # turn windows-looking lines into unix-looking lines\n" + "\n" + " # Save some flags that might be reset by InteractiveCommand()\n" + " my $save_opt_lines = $main::opt_lines;\n" + "\n" + " if (!InteractiveCommand($orig_profile, $symbols, $libs, $total, $_)) {\n" + " last; # exit when we get an interactive command to quit\n" + " }\n" + "\n" + " # Restore flags\n" + " $main::opt_lines = $save_opt_lines;\n" + " }\n" + " }\n" + "}\n" + "\n" + "# Takes two args: orig profile, and command to run.\n" + "# Returns 1 if we should keep going, or 0 if we were asked to quit\n" + "sub InteractiveCommand {\n" + " my($orig_profile, $symbols, $libs, $total, $command) = @_;\n" + " $_ = $command; # just to make future m//'s easier\n" + " if (!defined($_)) {\n" + " print STDERR \"\\n\";\n" + " return 0;\n" + " }\n" + " if (m/^\\s*quit/) {\n" + " return 0;\n" + " }\n" + " if (m/^\\s*help/) {\n" + " InteractiveHelpMessage();\n" + " return 1;\n" + " }\n" + " # Clear all the mode options -- mode is controlled by \"$command\"\n" + " $main::opt_text = 0;\n" + " $main::opt_callgrind = 0;\n" + " $main::opt_disasm = 0;\n" + " $main::opt_list = 0;\n" + " $main::opt_gv = 0;\n" + " $main::opt_evince = 0;\n" + " $main::opt_cum = 0;\n" + "\n" + " if (m/^\\s*(text|top)(\\d*)\\s*(.*)/) {\n" + " $main::opt_text = 1;\n" + "\n" + " my $line_limit = ($2 ne \"\") ? int($2) : 10;\n" + "\n" + " my $routine;\n" + " my $ignore;\n" + " ($routine, $ignore) = ParseInteractiveArgs($3);\n" + "\n" + " my $profile = ProcessProfile($total, $orig_profile, $symbols, \"\", $ignore);\n" + " my $reduced = ReduceProfile($symbols, $profile);\n" + "\n" + " # Get derived profiles\n" + " my $flat = FlatProfile($reduced);\n" + " my $cumulative = CumulativeProfile($reduced);\n" + "\n" + " PrintText($symbols, $flat, $cumulative, $line_limit);\n" + " return 1;\n" + " }\n" + " if (m/^\\s*callgrind\\s*([^ \\n]*)/) {\n" + " $main::opt_callgrind = 1;\n" + "\n" + " # Get derived profiles\n" + " my $calls = ExtractCalls($symbols, $orig_profile);\n" + " my $filename = $1;\n" + " if ( $1 eq '' ) {\n" + " $filename = TempName($main::next_tmpfile, \"callgrind\");\n" + " }\n" + " PrintCallgrind($calls, $filename);\n" + " if ( $1 eq '' ) {\n" + " RunKcachegrind($filename, \" & \");\n" + " $main::next_tmpfile++;\n" + " }\n" + "\n" + " return 1;\n" + " }\n" + " if (m/^\\s*(web)?list\\s*(.+)/) {\n" + " my $html = (defined($1) && ($1 eq \"web\"));\n" + " $main::opt_list = 1;\n" + "\n" + " my $routine;\n" + " my $ignore;\n" + " ($routine, $ignore) = ParseInteractiveArgs($2);\n" + "\n" + " my $profile = ProcessProfile($total, $orig_profile, $symbols, \"\", $ignore);\n" + " my $reduced = ReduceProfile($symbols, $profile);\n" + "\n" + " # Get derived profiles\n" + " my $flat = FlatProfile($reduced);\n" + " my $cumulative = CumulativeProfile($reduced);\n" + "\n" + " PrintListing($total, $libs, $flat, $cumulative, $routine, $html);\n" + " return 1;\n" + " }\n" + " if (m/^\\s*disasm\\s*(.+)/) {\n" + " $main::opt_disasm = 1;\n" + "\n" + " my $routine;\n" + " my $ignore;\n" + " ($routine, $ignore) = ParseInteractiveArgs($1);\n" + "\n" + " # Process current profile to account for various settings\n" + " my $profile = ProcessProfile($total, $orig_profile, $symbols, \"\", $ignore);\n" + " my $reduced = ReduceProfile($symbols, $profile);\n" + "\n" + " # Get derived profiles\n" + " my $flat = FlatProfile($reduced);\n" + " my $cumulative = CumulativeProfile($reduced);\n" + "\n" + " PrintDisassembly($libs, $flat, $cumulative, $routine);\n" + " return 1;\n" + " }\n" + " if (m/^\\s*(gv|web|evince)\\s*(.*)/) {\n" + " $main::opt_gv = 0;\n" + " $main::opt_evince = 0;\n" + " $main::opt_web = 0;\n" + " if ($1 eq \"gv\") {\n" + " $main::opt_gv = 1;\n" + " } elsif ($1 eq \"evince\") {\n" + " $main::opt_evince = 1;\n" + " } elsif ($1 eq \"web\") {\n" + " $main::opt_web = 1;\n" + " }\n" + "\n" + " my $focus;\n" + " my $ignore;\n" + " ($focus, $ignore) = ParseInteractiveArgs($2);\n" + "\n" + " # Process current profile to account for various settings\n" + " my $profile = ProcessProfile($total, $orig_profile, $symbols,\n" + " $focus, $ignore);\n" + " my $reduced = ReduceProfile($symbols, $profile);\n" + "\n" + " # Get derived profiles\n" + " my $flat = FlatProfile($reduced);\n" + " my $cumulative = CumulativeProfile($reduced);\n" + "\n" + " if (PrintDot($main::prog, $symbols, $profile, $flat, $cumulative, $total)) {\n" + " if ($main::opt_gv) {\n" + " RunGV(TempName($main::next_tmpfile, \"ps\"), \" &\");\n" + " } elsif ($main::opt_evince) {\n" + " RunEvince(TempName($main::next_tmpfile, \"pdf\"), \" &\");\n" + " } elsif ($main::opt_web) {\n" + " RunWeb(TempName($main::next_tmpfile, \"svg\"));\n" + " }\n" + " $main::next_tmpfile++;\n" + " }\n" + " return 1;\n" + " }\n" + " if (m/^\\s*$/) {\n" + " return 1;\n" + " }\n" + " print STDERR \"Unknown command: try 'help'.\\n\";\n" + " return 1;\n" + "}\n" + "\n" + "\n" + "sub ProcessProfile {\n" + " my $total_count = shift;\n" + " my $orig_profile = shift;\n" + " my $symbols = shift;\n" + " my $focus = shift;\n" + " my $ignore = shift;\n" + "\n" + " # Process current profile to account for various settings\n" + " my $profile = $orig_profile;\n" + " printf(\"Total: %s %s\\n\", Unparse($total_count), Units());\n" + " if ($focus ne '') {\n" + " $profile = FocusProfile($symbols, $profile, $focus);\n" + " my $focus_count = TotalProfile($profile);\n" + " printf(\"After focusing on '%s': %s %s of %s (%0.1f%%)\\n\",\n" + " $focus,\n" + " Unparse($focus_count), Units(),\n" + " Unparse($total_count), ($focus_count*100.0) / $total_count);\n" + " }\n" + " if ($ignore ne '') {\n" + " $profile = IgnoreProfile($symbols, $profile, $ignore);\n" + " my $ignore_count = TotalProfile($profile);\n" + " printf(\"After ignoring '%s': %s %s of %s (%0.1f%%)\\n\",\n" + " $ignore,\n" + " Unparse($ignore_count), Units(),\n" + " Unparse($total_count),\n" + " ($ignore_count*100.0) / $total_count);\n" + " }\n" + "\n" + " return $profile;\n" + "}\n" + "\n" + "sub InteractiveHelpMessage {\n" + " print STDERR <{$k};\n" + " my @addrs = split(/\\n/, $k);\n" + " if ($#addrs >= 0) {\n" + " my $depth = $#addrs + 1;\n" + " # int(foo / 2**32) is the only reliable way to get rid of bottom\n" + " # 32 bits on both 32- and 64-bit systems.\n" + " if ($big_endian) {\n" + " print pack('L*', int($count / 2**32), $count & 0xFFFFFFFF);\n" + " print pack('L*', int($depth / 2**32), $depth & 0xFFFFFFFF);\n" + " }\n" + " else {\n" + " print pack('L*', $count & 0xFFFFFFFF, int($count / 2**32));\n" + " print pack('L*', $depth & 0xFFFFFFFF, int($depth / 2**32));\n" + " }\n" + "\n" + " foreach my $full_addr (@addrs) {\n" + " my $addr = $full_addr;\n" + " $addr =~ s/0x0*//; # strip off leading 0x, zeroes\n" + " if (length($addr) > 16) {\n" + " print STDERR \"Invalid address in profile: $full_addr\\n\";\n" + " next;\n" + " }\n" + " my $low_addr = substr($addr, -8); # get last 8 hex chars\n" + " my $high_addr = substr($addr, -16, 8); # get up to 8 more hex chars\n" + " if ($big_endian) {\n" + " print pack('L*', hex('0x' . $high_addr), hex('0x' . $low_addr));\n" + " }\n" + " else {\n" + " print pack('L*', hex('0x' . $low_addr), hex('0x' . $high_addr));\n" + " }\n" + " }\n" + " }\n" + " }\n" + "}\n" + "\n" + "# Print symbols and profile data\n" + "sub PrintSymbolizedProfile {\n" + " my $symbols = shift;\n" + " my $profile = shift;\n" + " my $prog = shift;\n" + "\n" + " $SYMBOL_PAGE =~ m,[^/]+$,; # matches everything after the last slash\n" + " my $symbol_marker = $&;\n" + "\n" + " print '--- ', $symbol_marker, \"\\n\";\n" + " if (defined($prog)) {\n" + " print 'binary=', $prog, \"\\n\";\n" + " }\n" + " while (my ($pc, $name) = each(%{$symbols})) {\n" + " my $sep = ' ';\n" + " print '0x', $pc;\n" + " # We have a list of function names, which include the inlined\n" + " # calls. They are separated (and terminated) by --, which is\n" + " # illegal in function names.\n" + " for (my $j = 2; $j <= $#{$name}; $j += 3) {\n" + " print $sep, $name->[$j];\n" + " $sep = '--';\n" + " }\n" + " print \"\\n\";\n" + " }\n" + " print '---', \"\\n\";\n" + "\n" + " $PROFILE_PAGE =~ m,[^/]+$,; # matches everything after the last slash\n" + " my $profile_marker = $&;\n" + " print '--- ', $profile_marker, \"\\n\";\n" + " if (defined($main::collected_profile)) {\n" + " # if used with remote fetch, simply dump the collected profile to output.\n" + " open(SRC, \"<$main::collected_profile\");\n" + " while () {\n" + " print $_;\n" + " }\n" + " close(SRC);\n" + " } else {\n" + " # dump a cpu-format profile to standard out\n" + " PrintProfileData($profile);\n" + " }\n" + "}\n" + "\n" + "# Print text output\n" + "sub PrintText {\n" + " my $symbols = shift;\n" + " my $flat = shift;\n" + " my $cumulative = shift;\n" + " my $line_limit = shift;\n" + "\n" + " if ($main::opt_stacks && @stackTraces) {\n" + " foreach (sort { (split \" \", $b)[1] <=> (split \" \", $a)[1]; } @stackTraces) " + "{\n" + " print \"$_\\n\" if $main::opt_debug;\n" + " my ($n1, $s1, $n2, $s2, @addrs) = split;\n" + " print \"Leak of $s1 bytes in $n1 objects allocated from:\\n\";\n" + " foreach my $pcstr (@addrs) {\n" + " $pcstr =~ s/^0x//;\n" + " my $sym;\n" + " if (! defined $symbols->{$pcstr}) {\n" + " $sym = \"unknown\";\n" + " } else {\n" + " $sym = \"$symbols->{$pcstr}[0] $symbols->{$pcstr}[1]\";\n" + " }\n" + " print \"\\t@ $pcstr $sym\\n\";\n" + " }\n" + " }\n" + " print \"\\n\";\n" + " }\n" + "\n" + " my $total = TotalProfile($flat);\n" + "\n" + " # Which profile to sort by?\n" + " my $s = $main::opt_cum ? $cumulative : $flat;\n" + "\n" + " my $running_sum = 0;\n" + " my $lines = 0;\n" + " foreach my $k (sort { GetEntry($s, $b) <=> GetEntry($s, $a) || $a cmp $b }\n" + " keys(%{$cumulative})) {\n" + " my $f = GetEntry($flat, $k);\n" + " my $c = GetEntry($cumulative, $k);\n" + " $running_sum += $f;\n" + "\n" + " my $sym = $k;\n" + " if (exists($symbols->{$k})) {\n" + " $sym = $symbols->{$k}->[0] . \" \" . $symbols->{$k}->[1];\n" + " if ($main::opt_addresses) {\n" + " $sym = $k . \" \" . $sym;\n" + " }\n" + " }\n" + "\n" + " if ($f != 0 || $c != 0) {\n" + " printf(\"%8s %6s %6s %8s %6s %s\\n\",\n" + " Unparse($f),\n" + " Percent($f, $total),\n" + " Percent($running_sum, $total),\n" + " Unparse($c),\n" + " Percent($c, $total),\n" + " $sym);\n" + " }\n" + " $lines++;\n" + " last if ($line_limit >= 0 && $lines >= $line_limit);\n" + " }\n" + "}\n" + "\n" + "# Callgrind format has a compression for repeated function and file\n" + "# names. You show the name the first time, and just use its number\n" + "# subsequently. This can cut down the file to about a third or a\n" + "# quarter of its uncompressed size. $key and $val are the key/value\n" + "# pair that would normally be printed by callgrind; $map is a map from\n" + "# value to number.\n" + "sub CompressedCGName {\n" + " my($key, $val, $map) = @_;\n" + " my $idx = $map->{$val};\n" + " # For very short keys, providing an index hurts rather than helps.\n" + " if (length($val) <= 3) {\n" + " return \"$key=$val\\n\";\n" + " } elsif (defined($idx)) {\n" + " return \"$key=($idx)\\n\";\n" + " } else {\n" + " # scalar(keys $map) gives the number of items in the map.\n" + " $idx = scalar(keys(%{$map})) + 1;\n" + " $map->{$val} = $idx;\n" + " return \"$key=($idx) $val\\n\";\n" + " }\n" + "}\n" + "\n" + "# Print the call graph in a way that's suiteable for callgrind.\n" + "sub PrintCallgrind {\n" + " my $calls = shift;\n" + " my $filename;\n" + " my %filename_to_index_map;\n" + " my %fnname_to_index_map;\n" + "\n" + " if ($main::opt_interactive) {\n" + " $filename = shift;\n" + " print STDERR \"Writing callgrind file to '$filename'.\\n\"\n" + " } else {\n" + " $filename = \"&STDOUT\";\n" + " }\n" + " open(CG, \">$filename\");\n" + " print CG (\"events: Hits\\n\\n\");\n" + " foreach my $call ( map { $_->[0] }\n" + " sort { $a->[1] cmp $b ->[1] ||\n" + " $a->[2] <=> $b->[2] }\n" + " map { /([^:]+):(\\d+):([^ ]+)( -> ([^:]+):(\\d+):(.+))?/;\n" + " [$_, $1, $2] }\n" + " keys %$calls ) {\n" + " my $count = int($calls->{$call});\n" + " $call =~ /([^:]+):(\\d+):([^ ]+)( -> ([^:]+):(\\d+):(.+))?/;\n" + " my ( $caller_file, $caller_line, $caller_function,\n" + " $callee_file, $callee_line, $callee_function ) =\n" + " ( $1, $2, $3, $5, $6, $7 );\n" + "\n" + " # TODO(csilvers): for better compression, collect all the\n" + " # caller/callee_files and functions first, before printing\n" + " # anything, and only compress those referenced more than once.\n" + " print CG CompressedCGName(\"fl\", $caller_file, \\%filename_to_index_map);\n" + " print CG CompressedCGName(\"fn\", $caller_function, \\%fnname_to_index_map);\n" + " if (defined $6) {\n" + " print CG CompressedCGName(\"cfl\", $callee_file, \\%filename_to_index_map);\n" + " print CG CompressedCGName(\"cfn\", $callee_function, \\%fnname_to_index_map);\n" + " print CG (\"calls=$count $callee_line\\n\");\n" + " }\n" + " print CG (\"$caller_line $count\\n\\n\");\n" + " }\n" + "}\n" + "\n" + "# Print disassembly for all all routines that match $main::opt_disasm\n" + "sub PrintDisassembly {\n" + " my $libs = shift;\n" + " my $flat = shift;\n" + " my $cumulative = shift;\n" + " my $disasm_opts = shift;\n" + "\n" + " my $total = TotalProfile($flat);\n" + "\n" + " foreach my $lib (@{$libs}) {\n" + " my $symbol_table = GetProcedureBoundaries($lib->[0], $disasm_opts);\n" + " my $offset = AddressSub($lib->[1], $lib->[3]);\n" + " foreach my $routine (sort ByName keys(%{$symbol_table})) {\n" + " my $start_addr = $symbol_table->{$routine}->[0];\n" + " my $end_addr = $symbol_table->{$routine}->[1];\n" + " # See if there are any samples in this routine\n" + " my $length = hex(AddressSub($end_addr, $start_addr));\n" + " my $addr = AddressAdd($start_addr, $offset);\n" + " for (my $i = 0; $i < $length; $i++) {\n" + " if (defined($cumulative->{$addr})) {\n" + " PrintDisassembledFunction($lib->[0], $offset,\n" + " $routine, $flat, $cumulative,\n" + " $start_addr, $end_addr, $total);\n" + " last;\n" + " }\n" + " $addr = AddressInc($addr);\n" + " }\n" + " }\n" + " }\n" + "}\n" + "\n" + "# Return reference to array of tuples of the form:\n" + "# [start_address, filename, linenumber, instruction, limit_address]\n" + "# E.g.,\n" + "# [\"0x806c43d\", \"/foo/bar.cc\", 131, \"ret\", \"0x806c440\"]\n" + "sub Disassemble {\n" + " my $prog = shift;\n" + " my $offset = shift;\n" + " my $start_addr = shift;\n" + " my $end_addr = shift;\n" + "\n" + " my $objdump = $obj_tool_map{\"objdump\"};\n" + " my $cmd = ShellEscape($objdump, \"-C\", \"-d\", \"-l\", \"--no-show-raw-insn\",\n" + " \"--start-address=0x$start_addr\",\n" + " \"--stop-address=0x$end_addr\", $prog);\n" + " open(OBJDUMP, \"$cmd |\") || error(\"$cmd: $!\\n\");\n" + " my @result = ();\n" + " my $filename = \"\";\n" + " my $linenumber = -1;\n" + " my $last = [\"\", \"\", \"\", \"\"];\n" + " while () {\n" + " s/\\r//g; # turn windows-looking lines into unix-looking lines\n" + " chop;\n" + " if (m|\\s*([^:\\s]+):(\\d+)\\s*$|) {\n" + " # Location line of the form:\n" + " # :\n" + " $filename = $1;\n" + " $linenumber = $2;\n" + " } elsif (m/^ +([0-9a-f]+):\\s*(.*)/) {\n" + " # Disassembly line -- zero-extend address to full length\n" + " my $addr = HexExtend($1);\n" + " my $k = AddressAdd($addr, $offset);\n" + " $last->[4] = $k; # Store ending address for previous instruction\n" + " $last = [$k, $filename, $linenumber, $2, $end_addr];\n" + " push(@result, $last);\n" + " }\n" + " }\n" + " close(OBJDUMP);\n" + " return @result;\n" + "}\n" + "\n" + "# The input file should contain lines of the form /proc/maps-like\n" + "# output (same format as expected from the profiles) or that looks\n" + "# like hex addresses (like \"0xDEADBEEF\"). We will parse all\n" + "# /proc/maps output, and for all the hex addresses, we will output\n" + "# \"short\" symbol names, one per line, in the same order as the input.\n" + "sub PrintSymbols {\n" + " my $maps_and_symbols_file = shift;\n" + "\n" + " # ParseLibraries expects pcs to be in a set. Fine by us...\n" + " my @pclist = (); # pcs in sorted order\n" + " my $pcs = {};\n" + " my $map = \"\";\n" + " foreach my $line (<$maps_and_symbols_file>) {\n" + " $line =~ s/\\r//g; # turn windows-looking lines into unix-looking lines\n" + " if ($line =~ /\\b(0x[0-9a-f]+)\\b/i) {\n" + " push(@pclist, HexExtend($1));\n" + " $pcs->{$pclist[-1]} = 1;\n" + " } else {\n" + " $map .= $line;\n" + " }\n" + " }\n" + "\n" + " my $libs = ParseLibraries($main::prog, $map, $pcs);\n" + " my $symbols = ExtractSymbols($libs, $pcs);\n" + "\n" + " foreach my $pc (@pclist) {\n" + " # ->[0] is the shortname, ->[2] is the full name\n" + " print(($symbols->{$pc}->[0] || \"\?\?\") . \"\\n\");\n" + " }\n" + "}\n" + "\n" + "\n" + "# For sorting functions by name\n" + "sub ByName {\n" + " return ShortFunctionName($a) cmp ShortFunctionName($b);\n" + "}\n" + "\n" + "# Print source-listing for all all routines that match $list_opts\n" + "sub PrintListing {\n" + " my $total = shift;\n" + " my $libs = shift;\n" + " my $flat = shift;\n" + " my $cumulative = shift;\n" + " my $list_opts = shift;\n" + " my $html = shift;\n" + "\n" + " my $output = \\*STDOUT;\n" + " my $fname = \"\";\n" + "\n" + " if ($html) {\n" + " # Arrange to write the output to a temporary file\n" + " $fname = TempName($main::next_tmpfile, \"html\");\n" + " $main::next_tmpfile++;\n" + " if (!open(TEMP, \">$fname\")) {\n" + " print STDERR \"$fname: $!\\n\";\n" + " return;\n" + " }\n" + " $output = \\*TEMP;\n" + " print $output HtmlListingHeader();\n" + " printf $output (\"
%s
Total: %s %s
\\n\",\n" + " $main::prog, Unparse($total), Units());\n" + " }\n" + "\n" + " my $listed = 0;\n" + " foreach my $lib (@{$libs}) {\n" + " my $symbol_table = GetProcedureBoundaries($lib->[0], $list_opts);\n" + " my $offset = AddressSub($lib->[1], $lib->[3]);\n" + " foreach my $routine (sort ByName keys(%{$symbol_table})) {\n" + " # Print if there are any samples in this routine\n" + " my $start_addr = $symbol_table->{$routine}->[0];\n" + " my $end_addr = $symbol_table->{$routine}->[1];\n" + " my $length = hex(AddressSub($end_addr, $start_addr));\n" + " my $addr = AddressAdd($start_addr, $offset);\n" + " for (my $i = 0; $i < $length; $i++) {\n" + " if (defined($cumulative->{$addr})) {\n" + " $listed += PrintSource(\n" + " $lib->[0], $offset,\n" + " $routine, $flat, $cumulative,\n" + " $start_addr, $end_addr,\n" + " $html,\n" + " $output);\n" + " last;\n" + " }\n" + " $addr = AddressInc($addr);\n" + " }\n" + " }\n" + " }\n" + "\n" + " if ($html) {\n" + " if ($listed > 0) {\n" + " print $output HtmlListingFooter();\n" + " close($output);\n" + " RunWeb($fname);\n" + " } else {\n" + " close($output);\n" + " unlink($fname);\n" + " }\n" + " }\n" + "}\n" + "\n" + "sub HtmlListingHeader {\n" + " return <<'EOF';\n" + "\n" + "\n" + "\n" + "Pprof listing\n" + "\n" + "\n" + "\n" + "\n" + "EOF\n" + "}\n" + "\n" + "sub HtmlListingFooter {\n" + " return <<'EOF';\n" + "\n" + "\n" + "EOF\n" + "}\n" + "\n" + "sub HtmlEscape {\n" + " my $text = shift;\n" + " $text =~ s/&/&/g;\n" + " $text =~ s//>/g;\n" + " return $text;\n" + "}\n" + "\n" + "# Returns the indentation of the line, if it has any non-whitespace\n" + "# characters. Otherwise, returns -1.\n" + "sub Indentation {\n" + " my $line = shift;\n" + " if (m/^(\\s*)\\S/) {\n" + " return length($1);\n" + " } else {\n" + " return -1;\n" + " }\n" + "}\n" + "\n" + "# If the symbol table contains inlining info, Disassemble() may tag an\n" + "# instruction with a location inside an inlined function. But for\n" + "# source listings, we prefer to use the location in the function we\n" + "# are listing. So use MapToSymbols() to fetch full location\n" + "# information for each instruction and then pick out the first\n" + "# location from a location list (location list contains callers before\n" + "# callees in case of inlining).\n" + "#\n" + "# After this routine has run, each entry in $instructions contains:\n" + "# [0] start address\n" + "# [1] filename for function we are listing\n" + "# [2] line number for function we are listing\n" + "# [3] disassembly\n" + "# [4] limit address\n" + "# [5] most specific filename (may be different from [1] due to inlining)\n" + "# [6] most specific line number (may be different from [2] due to inlining)\n" + "sub GetTopLevelLineNumbers {\n" + " my ($lib, $offset, $instructions) = @_;\n" + " my $pcs = [];\n" + " for (my $i = 0; $i <= $#{$instructions}; $i++) {\n" + " push(@{$pcs}, $instructions->[$i]->[0]);\n" + " }\n" + " my $symbols = {};\n" + " MapToSymbols($lib, $offset, $pcs, $symbols);\n" + " for (my $i = 0; $i <= $#{$instructions}; $i++) {\n" + " my $e = $instructions->[$i];\n" + " push(@{$e}, $e->[1]);\n" + " push(@{$e}, $e->[2]);\n" + " my $addr = $e->[0];\n" + " my $sym = $symbols->{$addr};\n" + " if (defined($sym)) {\n" + " if ($#{$sym} >= 2 && $sym->[1] =~ m/^(.*):(\\d+)$/) {\n" + " $e->[1] = $1; # File name\n" + " $e->[2] = $2; # Line number\n" + " }\n" + " }\n" + " }\n" + "}\n" + "\n" + "# Print source-listing for one routine\n" + "sub PrintSource {\n" + " my $prog = shift;\n" + " my $offset = shift;\n" + " my $routine = shift;\n" + " my $flat = shift;\n" + " my $cumulative = shift;\n" + " my $start_addr = shift;\n" + " my $end_addr = shift;\n" + " my $html = shift;\n" + " my $output = shift;\n" + "\n" + " # Disassemble all instructions (just to get line numbers)\n" + " my @instructions = Disassemble($prog, $offset, $start_addr, $end_addr);\n" + " GetTopLevelLineNumbers($prog, $offset, \\@instructions);\n" + "\n" + " # Hack 1: assume that the first source file encountered in the\n" + " # disassembly contains the routine\n" + " my $filename = undef;\n" + " for (my $i = 0; $i <= $#instructions; $i++) {\n" + " if ($instructions[$i]->[2] >= 0) {\n" + " $filename = $instructions[$i]->[1];\n" + " last;\n" + " }\n" + " }\n" + " if (!defined($filename)) {\n" + " print STDERR \"no filename found in $routine\\n\";\n" + " return 0;\n" + " }\n" + "\n" + " # Hack 2: assume that the largest line number from $filename is the\n" + " # end of the procedure. This is typically safe since if P1 contains\n" + " # an inlined call to P2, then P2 usually occurs earlier in the\n" + " # source file. If this does not work, we might have to compute a\n" + " # density profile or just print all regions we find.\n" + " my $lastline = 0;\n" + " for (my $i = 0; $i <= $#instructions; $i++) {\n" + " my $f = $instructions[$i]->[1];\n" + " my $l = $instructions[$i]->[2];\n" + " if (($f eq $filename) && ($l > $lastline)) {\n" + " $lastline = $l;\n" + " }\n" + " }\n" + "\n" + " # Hack 3: assume the first source location from \"filename\" is the start of\n" + " # the source code.\n" + " my $firstline = 1;\n" + " for (my $i = 0; $i <= $#instructions; $i++) {\n" + " if ($instructions[$i]->[1] eq $filename) {\n" + " $firstline = $instructions[$i]->[2];\n" + " last;\n" + " }\n" + " }\n" + "\n" + " # Hack 4: Extend last line forward until its indentation is less than\n" + " # the indentation we saw on $firstline\n" + " my $oldlastline = $lastline;\n" + " {\n" + " if (!open(FILE, \"<$filename\")) {\n" + " print STDERR \"$filename: $!\\n\";\n" + " return 0;\n" + " }\n" + " my $l = 0;\n" + " my $first_indentation = -1;\n" + " while () {\n" + " s/\\r//g; # turn windows-looking lines into unix-looking lines\n" + " $l++;\n" + " my $indent = Indentation($_);\n" + " if ($l >= $firstline) {\n" + " if ($first_indentation < 0 && $indent >= 0) {\n" + " $first_indentation = $indent;\n" + " last if ($first_indentation == 0);\n" + " }\n" + " }\n" + " if ($l >= $lastline && $indent >= 0) {\n" + " if ($indent >= $first_indentation) {\n" + " $lastline = $l+1;\n" + " } else {\n" + " last;\n" + " }\n" + " }\n" + " }\n" + " close(FILE);\n" + " }\n" + "\n" + " # Assign all samples to the range $firstline,$lastline,\n" + " # Hack 4: If an instruction does not occur in the range, its samples\n" + " # are moved to the next instruction that occurs in the range.\n" + " my $samples1 = {}; # Map from line number to flat count\n" + " my $samples2 = {}; # Map from line number to cumulative count\n" + " my $running1 = 0; # Unassigned flat counts\n" + " my $running2 = 0; # Unassigned cumulative counts\n" + " my $total1 = 0; # Total flat counts\n" + " my $total2 = 0; # Total cumulative counts\n" + " my %disasm = (); # Map from line number to disassembly\n" + " my $running_disasm = \"\"; # Unassigned disassembly\n" + " my $skip_marker = \"---\\n\";\n" + " if ($html) {\n" + " $skip_marker = \"\";\n" + " for (my $l = $firstline; $l <= $lastline; $l++) {\n" + " $disasm{$l} = \"\";\n" + " }\n" + " }\n" + " my $last_dis_filename = '';\n" + " my $last_dis_linenum = -1;\n" + " my $last_touched_line = -1; # To detect gaps in disassembly for a line\n" + " foreach my $e (@instructions) {\n" + " # Add up counts for all address that fall inside this instruction\n" + " my $c1 = 0;\n" + " my $c2 = 0;\n" + " for (my $a = $e->[0]; $a lt $e->[4]; $a = AddressInc($a)) {\n" + " $c1 += GetEntry($flat, $a);\n" + " $c2 += GetEntry($cumulative, $a);\n" + " }\n" + "\n" + " if ($html) {\n" + " my $dis = sprintf(\" %6s %6s \\t\\t%8s: %s \",\n" + " HtmlPrintNumber($c1),\n" + " HtmlPrintNumber($c2),\n" + " UnparseAddress($offset, $e->[0]),\n" + " CleanDisassembly($e->[3]));\n" + " \n" + " # Append the most specific source line associated with this instruction\n" + " if (length($dis) < 80) { $dis .= (' ' x (80 - length($dis))) };\n" + " $dis = HtmlEscape($dis);\n" + " my $f = $e->[5];\n" + " my $l = $e->[6];\n" + " if ($f ne $last_dis_filename) {\n" + " $dis .= sprintf(\"%s:%d\", \n" + " HtmlEscape(CleanFileName($f)), $l);\n" + " } elsif ($l ne $last_dis_linenum) {\n" + " # De-emphasize the unchanged file name portion\n" + " $dis .= sprintf(\"%s\" .\n" + " \":%d\", \n" + " HtmlEscape(CleanFileName($f)), $l);\n" + " } else {\n" + " # De-emphasize the entire location\n" + " $dis .= sprintf(\"%s:%d\", \n" + " HtmlEscape(CleanFileName($f)), $l);\n" + " }\n" + " $last_dis_filename = $f;\n" + " $last_dis_linenum = $l;\n" + " $running_disasm .= $dis;\n" + " $running_disasm .= \"\\n\";\n" + " }\n" + "\n" + " $running1 += $c1;\n" + " $running2 += $c2;\n" + " $total1 += $c1;\n" + " $total2 += $c2;\n" + " my $file = $e->[1];\n" + " my $line = $e->[2];\n" + " if (($file eq $filename) &&\n" + " ($line >= $firstline) &&\n" + " ($line <= $lastline)) {\n" + " # Assign all accumulated samples to this line\n" + " AddEntry($samples1, $line, $running1);\n" + " AddEntry($samples2, $line, $running2);\n" + " $running1 = 0;\n" + " $running2 = 0;\n" + " if ($html) {\n" + " if ($line != $last_touched_line && $disasm{$line} ne '') {\n" + " $disasm{$line} .= \"\\n\";\n" + " }\n" + " $disasm{$line} .= $running_disasm;\n" + " $running_disasm = '';\n" + " $last_touched_line = $line;\n" + " }\n" + " }\n" + " }\n" + "\n" + " # Assign any leftover samples to $lastline\n" + " AddEntry($samples1, $lastline, $running1);\n" + " AddEntry($samples2, $lastline, $running2);\n" + " if ($html) {\n" + " if ($lastline != $last_touched_line && $disasm{$lastline} ne '') {\n" + " $disasm{$lastline} .= \"\\n\";\n" + " }\n" + " $disasm{$lastline} .= $running_disasm;\n" + " }\n" + "\n" + " if ($html) {\n" + " printf $output (\n" + " \"

%s

%s\\n
\\n\" .\n"
+        "      \"Total:%6s %6s (flat / cumulative %s)\\n\",\n"
+        "      HtmlEscape(ShortFunctionName($routine)),\n"
+        "      HtmlEscape(CleanFileName($filename)),\n"
+        "      Unparse($total1),\n"
+        "      Unparse($total2),\n"
+        "      Units());\n"
+        "  } else {\n"
+        "    printf $output (\n"
+        "      \"ROUTINE ====================== %s in %s\\n\" .\n"
+        "      \"%6s %6s Total %s (flat / cumulative)\\n\",\n"
+        "      ShortFunctionName($routine),\n"
+        "      CleanFileName($filename),\n"
+        "      Unparse($total1),\n"
+        "      Unparse($total2),\n"
+        "      Units());\n"
+        "  }\n"
+        "  if (!open(FILE, \"<$filename\")) {\n"
+        "    print STDERR \"$filename: $!\\n\";\n"
+        "    return 0;\n"
+        "  }\n"
+        "  my $l = 0;\n"
+        "  while () {\n"
+        "    s/\\r//g;         # turn windows-looking lines into unix-looking lines\n"
+        "    $l++;\n"
+        "    if ($l >= $firstline - 5 &&\n"
+        "        (($l <= $oldlastline + 5) || ($l <= $lastline))) {\n"
+        "      chop;\n"
+        "      my $text = $_;\n"
+        "      if ($l == $firstline) { print $output $skip_marker; }\n"
+        "      my $n1 = GetEntry($samples1, $l);\n"
+        "      my $n2 = GetEntry($samples2, $l);\n"
+        "      if ($html) {\n"
+        "        # Emit a span that has one of the following classes:\n"
+        "        #    livesrc -- has samples\n"
+        "        #    deadsrc -- has disassembly, but with no samples\n"
+        "        #    nop     -- has no matching disasembly\n"
+        "        # Also emit an optional span containing disassembly.\n"
+        "        my $dis = $disasm{$l};\n"
+        "        my $asm = \"\";\n"
+        "        if (defined($dis) && $dis ne '') {\n"
+        "          $asm = \"\" . $dis . \"\";\n"
+        "        }\n"
+        "        my $source_class = (($n1 + $n2 > 0) \n"
+        "                            ? \"livesrc\" \n"
+        "                            : (($asm ne \"\") ? \"deadsrc\" : \"nop\"));\n"
+        "        printf $output (\n"
+        "          \"%5d \" .\n"
+        "          \"%6s %6s %s%s\\n\",\n"
+        "          $l, $source_class,\n"
+        "          HtmlPrintNumber($n1),\n"
+        "          HtmlPrintNumber($n2),\n"
+        "          HtmlEscape($text),\n"
+        "          $asm);\n"
+        "      } else {\n"
+        "        printf $output(\n"
+        "          \"%6s %6s %4d: %s\\n\",\n"
+        "          UnparseAlt($n1),\n"
+        "          UnparseAlt($n2),\n"
+        "          $l,\n"
+        "          $text);\n"
+        "      }\n"
+        "      if ($l == $lastline)  { print $output $skip_marker; }\n"
+        "    };\n"
+        "  }\n"
+        "  close(FILE);\n"
+        "  if ($html) {\n"
+        "    print $output \"
\\n\";\n" + " }\n" + " return 1;\n" + "}\n" + "\n" + "# Return the source line for the specified file/linenumber.\n" + "# Returns undef if not found.\n" + "sub SourceLine {\n" + " my $file = shift;\n" + " my $line = shift;\n" + "\n" + " # Look in cache\n" + " if (!defined($main::source_cache{$file})) {\n" + " if (100 < scalar keys(%main::source_cache)) {\n" + " # Clear the cache when it gets too big\n" + " $main::source_cache = ();\n" + " }\n" + "\n" + " # Read all lines from the file\n" + " if (!open(FILE, \"<$file\")) {\n" + " print STDERR \"$file: $!\\n\";\n" + " $main::source_cache{$file} = []; # Cache the negative result\n" + " return undef;\n" + " }\n" + " my $lines = [];\n" + " push(@{$lines}, \"\"); # So we can use 1-based line numbers as indices\n" + " while () {\n" + " push(@{$lines}, $_);\n" + " }\n" + " close(FILE);\n" + "\n" + " # Save the lines in the cache\n" + " $main::source_cache{$file} = $lines;\n" + " }\n" + "\n" + " my $lines = $main::source_cache{$file};\n" + " if (($line < 0) || ($line > $#{$lines})) {\n" + " return undef;\n" + " } else {\n" + " return $lines->[$line];\n" + " }\n" + "}\n" + "\n" + "# Print disassembly for one routine with interspersed source if available\n" + "sub PrintDisassembledFunction {\n" + " my $prog = shift;\n" + " my $offset = shift;\n" + " my $routine = shift;\n" + " my $flat = shift;\n" + " my $cumulative = shift;\n" + " my $start_addr = shift;\n" + " my $end_addr = shift;\n" + " my $total = shift;\n" + "\n" + " # Disassemble all instructions\n" + " my @instructions = Disassemble($prog, $offset, $start_addr, $end_addr);\n" + "\n" + " # Make array of counts per instruction\n" + " my @flat_count = ();\n" + " my @cum_count = ();\n" + " my $flat_total = 0;\n" + " my $cum_total = 0;\n" + " foreach my $e (@instructions) {\n" + " # Add up counts for all address that fall inside this instruction\n" + " my $c1 = 0;\n" + " my $c2 = 0;\n" + " for (my $a = $e->[0]; $a lt $e->[4]; $a = AddressInc($a)) {\n" + " $c1 += GetEntry($flat, $a);\n" + " $c2 += GetEntry($cumulative, $a);\n" + " }\n" + " push(@flat_count, $c1);\n" + " push(@cum_count, $c2);\n" + " $flat_total += $c1;\n" + " $cum_total += $c2;\n" + " }\n" + "\n" + " # Print header with total counts\n" + " printf(\"ROUTINE ====================== %s\\n\" .\n" + " \"%6s %6s %s (flat, cumulative) %.1f%% of total\\n\",\n" + " ShortFunctionName($routine),\n" + " Unparse($flat_total),\n" + " Unparse($cum_total),\n" + " Units(),\n" + " ($cum_total * 100.0) / $total);\n" + "\n" + " # Process instructions in order\n" + " my $current_file = \"\";\n" + " for (my $i = 0; $i <= $#instructions; ) {\n" + " my $e = $instructions[$i];\n" + "\n" + " # Print the new file name whenever we switch files\n" + " if ($e->[1] ne $current_file) {\n" + " $current_file = $e->[1];\n" + " my $fname = $current_file;\n" + " $fname =~ s|^\\./||; # Trim leading \"./\"\n" + "\n" + " # Shorten long file names\n" + " if (length($fname) >= 58) {\n" + " $fname = \"...\" . substr($fname, -55);\n" + " }\n" + " printf(\"-------------------- %s\\n\", $fname);\n" + " }\n" + "\n" + " # TODO: Compute range of lines to print together to deal with\n" + " # small reorderings.\n" + " my $first_line = $e->[2];\n" + " my $last_line = $first_line;\n" + " my %flat_sum = ();\n" + " my %cum_sum = ();\n" + " for (my $l = $first_line; $l <= $last_line; $l++) {\n" + " $flat_sum{$l} = 0;\n" + " $cum_sum{$l} = 0;\n" + " }\n" + "\n" + " # Find run of instructions for this range of source lines\n" + " my $first_inst = $i;\n" + " while (($i <= $#instructions) &&\n" + " ($instructions[$i]->[2] >= $first_line) &&\n" + " ($instructions[$i]->[2] <= $last_line)) {\n" + " $e = $instructions[$i];\n" + " $flat_sum{$e->[2]} += $flat_count[$i];\n" + " $cum_sum{$e->[2]} += $cum_count[$i];\n" + " $i++;\n" + " }\n" + " my $last_inst = $i - 1;\n" + "\n" + " # Print source lines\n" + " for (my $l = $first_line; $l <= $last_line; $l++) {\n" + " my $line = SourceLine($current_file, $l);\n" + " if (!defined($line)) {\n" + " $line = \"?\\n\";\n" + " next;\n" + " } else {\n" + " $line =~ s/^\\s+//;\n" + " }\n" + " printf(\"%6s %6s %5d: %s\",\n" + " UnparseAlt($flat_sum{$l}),\n" + " UnparseAlt($cum_sum{$l}),\n" + " $l,\n" + " $line);\n" + " }\n" + "\n" + " # Print disassembly\n" + " for (my $x = $first_inst; $x <= $last_inst; $x++) {\n" + " my $e = $instructions[$x];\n" + " printf(\"%6s %6s %8s: %6s\\n\",\n" + " UnparseAlt($flat_count[$x]),\n" + " UnparseAlt($cum_count[$x]),\n" + " UnparseAddress($offset, $e->[0]),\n" + " CleanDisassembly($e->[3]));\n" + " }\n" + " }\n" + "}\n" + "\n" + "# Print DOT graph\n" + "sub PrintDot {\n" + " my $prog = shift;\n" + " my $symbols = shift;\n" + " my $raw = shift;\n" + " my $flat = shift;\n" + " my $cumulative = shift;\n" + " my $overall_total = shift;\n" + "\n" + " # Get total\n" + " my $local_total = TotalProfile($flat);\n" + " my $nodelimit = int($main::opt_nodefraction * $local_total);\n" + " my $edgelimit = int($main::opt_edgefraction * $local_total);\n" + " my $nodecount = $main::opt_nodecount;\n" + "\n" + " # Find nodes to include\n" + " my @list = (sort { abs(GetEntry($cumulative, $b)) <=>\n" + " abs(GetEntry($cumulative, $a))\n" + " || $a cmp $b }\n" + " keys(%{$cumulative}));\n" + " my $last = $nodecount - 1;\n" + " if ($last > $#list) {\n" + " $last = $#list;\n" + " }\n" + " while (($last >= 0) &&\n" + " (abs(GetEntry($cumulative, $list[$last])) <= $nodelimit)) {\n" + " $last--;\n" + " }\n" + " if ($last < 0) {\n" + " print STDERR \"No nodes to print\\n\";\n" + " return 0;\n" + " }\n" + "\n" + " if ($nodelimit > 0 || $edgelimit > 0) {\n" + " printf STDERR (\"Dropping nodes with <= %s %s; edges with <= %s abs(%s)\\n\",\n" + " Unparse($nodelimit), Units(),\n" + " Unparse($edgelimit), Units());\n" + " }\n" + "\n" + " # Open DOT output file\n" + " my $output;\n" + " my $escaped_dot = ShellEscape(@DOT);\n" + " my $escaped_ps2pdf = ShellEscape(@PS2PDF);\n" + " if ($main::opt_gv) {\n" + " my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, \"ps\"));\n" + " $output = \"| $escaped_dot -Tps2 >$escaped_outfile\";\n" + " } elsif ($main::opt_evince) {\n" + " my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, \"pdf\"));\n" + " $output = \"| $escaped_dot -Tps2 | $escaped_ps2pdf - $escaped_outfile\";\n" + " } elsif ($main::opt_ps) {\n" + " $output = \"| $escaped_dot -Tps2\";\n" + " } elsif ($main::opt_pdf) {\n" + " $output = \"| $escaped_dot -Tps2 | $escaped_ps2pdf - -\";\n" + " } elsif ($main::opt_web || $main::opt_svg) {\n" + " # We need to post-process the SVG, so write to a temporary file always.\n" + " my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, \"svg\"));\n" + " $output = \"| $escaped_dot -Tsvg >$escaped_outfile\";\n" + " } elsif ($main::opt_gif) {\n" + " $output = \"| $escaped_dot -Tgif\";\n" + " } else {\n" + " $output = \">&STDOUT\";\n" + " }\n" + " open(DOT, $output) || error(\"$output: $!\\n\");\n" + "\n" + " # Title\n" + " printf DOT (\"digraph \\\"%s; %s %s\\\" {\\n\",\n" + " $prog,\n" + " Unparse($overall_total),\n" + " Units());\n" + " if ($main::opt_pdf) {\n" + " # The output is more printable if we set the page size for dot.\n" + " printf DOT (\"size=\\\"8,11\\\"\\n\");\n" + " }\n" + " printf DOT (\"node [width=0.375,height=0.25];\\n\");\n" + "\n" + " # Print legend\n" + " printf DOT (\"Legend [shape=box,fontsize=24,shape=plaintext,\" .\n" + " \"label=\\\"%s\\\\l%s\\\\l%s\\\\l%s\\\\l%s\\\\l\\\"];\\n\",\n" + " $prog,\n" + " sprintf(\"Total %s: %s\", Units(), Unparse($overall_total)),\n" + " sprintf(\"Focusing on: %s\", Unparse($local_total)),\n" + " sprintf(\"Dropped nodes with <= %s abs(%s)\",\n" + " Unparse($nodelimit), Units()),\n" + " sprintf(\"Dropped edges with <= %s %s\",\n" + " Unparse($edgelimit), Units())\n" + " );\n" + "\n" + " # Print nodes\n" + " my %node = ();\n" + " my $nextnode = 1;\n" + " foreach my $a (@list[0..$last]) {\n" + " # Pick font size\n" + " my $f = GetEntry($flat, $a);\n" + " my $c = GetEntry($cumulative, $a);\n" + "\n" + " my $fs = 8;\n" + " if ($local_total > 0) {\n" + " $fs = 8 + (50.0 * sqrt(abs($f * 1.0 / $local_total)));\n" + " }\n" + "\n" + " $node{$a} = $nextnode++;\n" + " my $sym = $a;\n" + " $sym =~ s/\\s+/\\\\n/g;\n" + " $sym =~ s/::/\\\\n/g;\n" + "\n" + " # Extra cumulative info to print for non-leaves\n" + " my $extra = \"\";\n" + " if ($f != $c) {\n" + " $extra = sprintf(\"\\\\rof %s (%s)\",\n" + " Unparse($c),\n" + " Percent($c, $local_total));\n" + " }\n" + " my $style = \"\";\n" + " if ($main::opt_heapcheck) {\n" + " if ($f > 0) {\n" + " # make leak-causing nodes more visible (add a background)\n" + " $style = \",style=filled,fillcolor=gray\"\n" + " } elsif ($f < 0) {\n" + " # make anti-leak-causing nodes (which almost never occur)\n" + " # stand out as well (triple border)\n" + " $style = \",peripheries=3\"\n" + " }\n" + " }\n" + "\n" + " printf DOT (\"N%d [label=\\\"%s\\\\n%s (%s)%s\\\\r\" .\n" + " \"\\\",shape=box,fontsize=%.1f%s];\\n\",\n" + " $node{$a},\n" + " $sym,\n" + " Unparse($f),\n" + " Percent($f, $local_total),\n" + " $extra,\n" + " $fs,\n" + " $style,\n" + " );\n" + " }\n" + "\n" + " # Get edges and counts per edge\n" + " my %edge = ();\n" + " my $n;\n" + " my $fullname_to_shortname_map = {};\n" + " FillFullnameToShortnameMap($symbols, $fullname_to_shortname_map);\n" + " foreach my $k (keys(%{$raw})) {\n" + " # TODO: omit low %age edges\n" + " $n = $raw->{$k};\n" + " my @translated = TranslateStack($symbols, $fullname_to_shortname_map, $k);\n" + " for (my $i = 1; $i <= $#translated; $i++) {\n" + " my $src = $translated[$i];\n" + " my $dst = $translated[$i-1];\n" + " #next if ($src eq $dst); # Avoid self-edges?\n" + " if (exists($node{$src}) && exists($node{$dst})) {\n" + " my $edge_label = \"$src\\001$dst\";\n" + " if (!exists($edge{$edge_label})) {\n" + " $edge{$edge_label} = 0;\n" + " }\n" + " $edge{$edge_label} += $n;\n" + " }\n" + " }\n" + " }\n" + "\n" + " # Print edges (process in order of decreasing counts)\n" + " my %indegree = (); # Number of incoming edges added per node so far\n" + " my %outdegree = (); # Number of outgoing edges added per node so far\n" + " foreach my $e (sort { $edge{$b} <=> $edge{$a} } keys(%edge)) {\n" + " my @x = split(/\\001/, $e);\n" + " $n = $edge{$e};\n" + "\n" + " # Initialize degree of kept incoming and outgoing edges if necessary\n" + " my $src = $x[0];\n" + " my $dst = $x[1];\n" + " if (!exists($outdegree{$src})) { $outdegree{$src} = 0; }\n" + " if (!exists($indegree{$dst})) { $indegree{$dst} = 0; }\n" + "\n" + " my $keep;\n" + " if ($indegree{$dst} == 0) {\n" + " # Keep edge if needed for reachability\n" + " $keep = 1;\n" + " } elsif (abs($n) <= $edgelimit) {\n" + " # Drop if we are below --edgefraction\n" + " $keep = 0;\n" + " } elsif ($outdegree{$src} >= $main::opt_maxdegree ||\n" + " $indegree{$dst} >= $main::opt_maxdegree) {\n" + " # Keep limited number of in/out edges per node\n" + " $keep = 0;\n" + " } else {\n" + " $keep = 1;\n" + " }\n" + "\n" + " if ($keep) {\n" + " $outdegree{$src}++;\n" + " $indegree{$dst}++;\n" + "\n" + " # Compute line width based on edge count\n" + " my $fraction = abs($local_total ? (3 * ($n / $local_total)) : 0);\n" + " if ($fraction > 1) { $fraction = 1; }\n" + " my $w = $fraction * 2;\n" + " if ($w < 1 && ($main::opt_web || $main::opt_svg)) {\n" + " # SVG output treats line widths < 1 poorly.\n" + " $w = 1;\n" + " }\n" + "\n" + " # Dot sometimes segfaults if given edge weights that are too large, so\n" + " # we cap the weights at a large value\n" + " my $edgeweight = abs($n) ** 0.7;\n" + " if ($edgeweight > 100000) { $edgeweight = 100000; }\n" + " $edgeweight = int($edgeweight);\n" + "\n" + " my $style = sprintf(\"setlinewidth(%f)\", $w);\n" + " if ($x[1] =~ m/\\(inline\\)/) {\n" + " $style .= \",dashed\";\n" + " }\n" + "\n" + " # Use a slightly squashed function of the edge count as the weight\n" + " printf DOT (\"N%s -> N%s [label=%s, weight=%d, style=\\\"%s\\\"];\\n\",\n" + " $node{$x[0]},\n" + " $node{$x[1]},\n" + " Unparse($n),\n" + " $edgeweight,\n" + " $style);\n" + " }\n" + " }\n" + "\n" + " print DOT (\"}\\n\");\n" + " close(DOT);\n" + "\n" + " if ($main::opt_web || $main::opt_svg) {\n" + " # Rewrite SVG to be more usable inside web browser.\n" + " RewriteSvg(TempName($main::next_tmpfile, \"svg\"));\n" + " }\n" + "\n" + " return 1;\n" + "}\n" + "\n" + "sub RewriteSvg {\n" + " my $svgfile = shift;\n" + "\n" + " open(SVG, $svgfile) || die \"open temp svg: $!\";\n" + " my @svg = ;\n" + " close(SVG);\n" + " unlink $svgfile;\n" + " my $svg = join('', @svg);\n" + "\n" + " # Dot's SVG output is\n" + " #\n" + " # \n" + " # \n" + " # ...\n" + " # \n" + " # \n" + " #\n" + " # Change it to\n" + " #\n" + " # \n" + " # $svg_javascript\n" + " # \n" + " # \n" + " # ...\n" + " # \n" + " # \n" + " # \n" + "\n" + " # Fix width, height; drop viewBox.\n" + " $svg =~ s/(?s) above first \n" + " my $svg_javascript = SvgJavascript();\n" + " my $viewport = \"\\n\";\n" + " $svg =~ s/ above .\n" + " $svg =~ s/(.*)(<\\/svg>)/$1<\\/g>$2/;\n" + " $svg =~ s/$svgfile\") || die \"open $svgfile: $!\";\n" + " print SVG $svg;\n" + " close(SVG);\n" + " }\n" + "}\n" + "\n" + "sub SvgJavascript {\n" + " return <<'EOF';\n" + "\n" + "EOF\n" + "}\n" + "\n" + "# Provides a map from fullname to shortname for cases where the\n" + "# shortname is ambiguous. The symlist has both the fullname and\n" + "# shortname for all symbols, which is usually fine, but sometimes --\n" + "# such as overloaded functions -- two different fullnames can map to\n" + "# the same shortname. In that case, we use the address of the\n" + "# function to disambiguate the two. This function fills in a map that\n" + "# maps fullnames to modified shortnames in such cases. If a fullname\n" + "# is not present in the map, the 'normal' shortname provided by the\n" + "# symlist is the appropriate one to use.\n" + "sub FillFullnameToShortnameMap {\n" + " my $symbols = shift;\n" + " my $fullname_to_shortname_map = shift;\n" + " my $shortnames_seen_once = {};\n" + " my $shortnames_seen_more_than_once = {};\n" + "\n" + " foreach my $symlist (values(%{$symbols})) {\n" + " # TODO(csilvers): deal with inlined symbols too.\n" + " my $shortname = $symlist->[0];\n" + " my $fullname = $symlist->[2];\n" + " if ($fullname !~ /<[0-9a-fA-F]+>$/) { # fullname doesn't end in an address\n" + " next; # the only collisions we care about are when addresses differ\n" + " }\n" + " if (defined($shortnames_seen_once->{$shortname}) &&\n" + " $shortnames_seen_once->{$shortname} ne $fullname) {\n" + " $shortnames_seen_more_than_once->{$shortname} = 1;\n" + " } else {\n" + " $shortnames_seen_once->{$shortname} = $fullname;\n" + " }\n" + " }\n" + "\n" + " foreach my $symlist (values(%{$symbols})) {\n" + " my $shortname = $symlist->[0];\n" + " my $fullname = $symlist->[2];\n" + " # TODO(csilvers): take in a list of addresses we care about, and only\n" + " # store in the map if $symlist->[1] is in that list. Saves space.\n" + " next if defined($fullname_to_shortname_map->{$fullname});\n" + " if (defined($shortnames_seen_more_than_once->{$shortname})) {\n" + " if ($fullname =~ /<0*([^>]*)>$/) { # fullname has address at end of it\n" + " $fullname_to_shortname_map->{$fullname} = \"$shortname\\@$1\";\n" + " }\n" + " }\n" + " }\n" + "}\n" + "\n" + "# Return a small number that identifies the argument.\n" + "# Multiple calls with the same argument will return the same number.\n" + "# Calls with different arguments will return different numbers.\n" + "sub ShortIdFor {\n" + " my $key = shift;\n" + " my $id = $main::uniqueid{$key};\n" + " if (!defined($id)) {\n" + " $id = keys(%main::uniqueid) + 1;\n" + " $main::uniqueid{$key} = $id;\n" + " }\n" + " return $id;\n" + "}\n" + "\n" + "# Translate a stack of addresses into a stack of symbols\n" + "sub TranslateStack {\n" + " my $symbols = shift;\n" + " my $fullname_to_shortname_map = shift;\n" + " my $k = shift;\n" + "\n" + " my @addrs = split(/\\n/, $k);\n" + " my @result = ();\n" + " for (my $i = 0; $i <= $#addrs; $i++) {\n" + " my $a = $addrs[$i];\n" + "\n" + " # Skip large addresses since they sometimes show up as fake entries on RH9\n" + " if (length($a) > 8 && $a gt \"7fffffffffffffff\") {\n" + " next;\n" + " }\n" + "\n" + " if ($main::opt_disasm || $main::opt_list) {\n" + " # We want just the address for the key\n" + " push(@result, $a);\n" + " next;\n" + " }\n" + "\n" + " my $symlist = $symbols->{$a};\n" + " if (!defined($symlist)) {\n" + " $symlist = [$a, \"\", $a];\n" + " }\n" + "\n" + " # We can have a sequence of symbols for a particular entry\n" + " # (more than one symbol in the case of inlining). Callers\n" + " # come before callees in symlist, so walk backwards since\n" + " # the translated stack should contain callees before callers.\n" + " for (my $j = $#{$symlist}; $j >= 2; $j -= 3) {\n" + " my $func = $symlist->[$j-2];\n" + " my $fileline = $symlist->[$j-1];\n" + " my $fullfunc = $symlist->[$j];\n" + " if (defined($fullname_to_shortname_map->{$fullfunc})) {\n" + " $func = $fullname_to_shortname_map->{$fullfunc};\n" + " }\n" + " if ($j > 2) {\n" + " $func = \"$func (inline)\";\n" + " }\n" + "\n" + " # Do not merge nodes corresponding to Callback::Run since that\n" + " # causes confusing cycles in dot display. Instead, we synthesize\n" + " # a unique name for this frame per caller.\n" + " if ($func =~ m/Callback.*::Run$/) {\n" + " my $caller = ($i > 0) ? $addrs[$i-1] : 0;\n" + " $func = \"Run#\" . ShortIdFor($caller);\n" + " }\n" + "\n" + " if ($main::opt_addresses) {\n" + " push(@result, \"$a $func $fileline\");\n" + " } elsif ($main::opt_lines) {\n" + " if ($func eq '\?\?' && $fileline eq '\?\?:0') {\n" + " push(@result, \"$a\");\n" + " } elsif (!$main::opt_show_addresses) {\n" + " push(@result, \"$func $fileline\");\n" + " } else {\n" + " push(@result, \"$func $fileline ($a)\");\n" + " }\n" + " } elsif ($main::opt_functions) {\n" + " if ($func eq '\?\?') {\n" + " push(@result, \"$a\");\n" + " } elsif (!$main::opt_show_addresses) {\n" + " push(@result, $func);\n" + " } else {\n" + " push(@result, \"$func ($a)\");\n" + " }\n" + " } elsif ($main::opt_files) {\n" + " if ($fileline eq '\?\?:0' || $fileline eq '') {\n" + " push(@result, \"$a\");\n" + " } else {\n" + " my $f = $fileline;\n" + " $f =~ s/:\\d+$//;\n" + " push(@result, $f);\n" + " }\n" + " } else {\n" + " push(@result, $a);\n" + " last; # Do not print inlined info\n" + " }\n" + " }\n" + " }\n" + "\n" + " # print join(\",\", @addrs), \" => \", join(\",\", @result), \"\\n\";\n" + " return @result;\n" + "}\n" + "\n" + "# Generate percent string for a number and a total\n" + "sub Percent {\n" + " my $num = shift;\n" + " my $tot = shift;\n" + " if ($tot != 0) {\n" + " return sprintf(\"%.1f%%\", $num * 100.0 / $tot);\n" + " } else {\n" + " return ($num == 0) ? \"nan\" : (($num > 0) ? \"+inf\" : \"-inf\");\n" + " }\n" + "}\n" + "\n" + "# Generate pretty-printed form of number\n" + "sub Unparse {\n" + " my $num = shift;\n" + " if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') {\n" + " if ($main::opt_inuse_objects || $main::opt_alloc_objects) {\n" + " return sprintf(\"%d\", $num);\n" + " } else {\n" + " if ($main::opt_show_bytes) {\n" + " return sprintf(\"%d\", $num);\n" + " } else {\n" + " return sprintf(\"%.1f\", $num / 1048576.0);\n" + " }\n" + " }\n" + " } elsif ($main::profile_type eq 'contention' && !$main::opt_contentions) {\n" + " return sprintf(\"%.3f\", $num / 1e9); # Convert nanoseconds to seconds\n" + " } else {\n" + " return sprintf(\"%d\", $num);\n" + " }\n" + "}\n" + "\n" + "# Alternate pretty-printed form: 0 maps to \".\"\n" + "sub UnparseAlt {\n" + " my $num = shift;\n" + " if ($num == 0) {\n" + " return \".\";\n" + " } else {\n" + " return Unparse($num);\n" + " }\n" + "}\n" + "\n" + "# Alternate pretty-printed form: 0 maps to \"\"\n" + "sub HtmlPrintNumber {\n" + " my $num = shift;\n" + " if ($num == 0) {\n" + " return \"\";\n" + " } else {\n" + " return Unparse($num);\n" + " }\n" + "}\n" + "\n" + "# Return output units\n" + "sub Units {\n" + " if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') {\n" + " if ($main::opt_inuse_objects || $main::opt_alloc_objects) {\n" + " return \"objects\";\n" + " } else {\n" + " if ($main::opt_show_bytes) {\n" + " return \"B\";\n" + " } else {\n" + " return \"MB\";\n" + " }\n" + " }\n" + " } elsif ($main::profile_type eq 'contention' && !$main::opt_contentions) {\n" + " return \"seconds\";\n" + " } else {\n" + " return \"samples\";\n" + " }\n" + "}\n" + "\n" + "##### Profile manipulation code #####\n" + "\n" + "# Generate flattened profile:\n" + "# If count is charged to stack [a,b,c,d], in generated profile,\n" + "# it will be charged to [a]\n" + "sub FlatProfile {\n" + " my $profile = shift;\n" + " my $result = {};\n" + " foreach my $k (keys(%{$profile})) {\n" + " my $count = $profile->{$k};\n" + " my @addrs = split(/\\n/, $k);\n" + " if ($#addrs >= 0) {\n" + " AddEntry($result, $addrs[0], $count);\n" + " }\n" + " }\n" + " return $result;\n" + "}\n" + "\n" + "# Generate cumulative profile:\n" + "# If count is charged to stack [a,b,c,d], in generated profile,\n" + "# it will be charged to [a], [b], [c], [d]\n" + "sub CumulativeProfile {\n" + " my $profile = shift;\n" + " my $result = {};\n" + " foreach my $k (keys(%{$profile})) {\n" + " my $count = $profile->{$k};\n" + " my @addrs = split(/\\n/, $k);\n" + " foreach my $a (@addrs) {\n" + " AddEntry($result, $a, $count);\n" + " }\n" + " }\n" + " return $result;\n" + "}\n" + "\n" + "# If the second-youngest PC on the stack is always the same, returns\n" + "# that pc. Otherwise, returns undef.\n" + "sub IsSecondPcAlwaysTheSame {\n" + " my $profile = shift;\n" + "\n" + " my $second_pc = undef;\n" + " foreach my $k (keys(%{$profile})) {\n" + " my @addrs = split(/\\n/, $k);\n" + " if ($#addrs < 1) {\n" + " return undef;\n" + " }\n" + " if (not defined $second_pc) {\n" + " $second_pc = $addrs[1];\n" + " } else {\n" + " if ($second_pc ne $addrs[1]) {\n" + " return undef;\n" + " }\n" + " }\n" + " }\n" + " return $second_pc;\n" + "}\n" + "\n" + "sub ExtractSymbolLocationInlineStack {\n" + " my $symbols = shift;\n" + " my $address = shift;\n" + " my $stack = shift;\n" + " # 'addr2line' outputs \"\?\?:0\" for unknown locations; we do the\n" + " # same to be consistent.\n" + " if (exists $symbols->{$address}) {\n" + " my @localinlinestack = @{$symbols->{$address}};\n" + " for (my $i = $#localinlinestack; $i > 0; $i-=3) {\n" + " my $file = $localinlinestack[$i-1];\n" + " my $fn = $localinlinestack[$i-2];\n" + " if ($file eq \"?\" || $file eq \":0\") {\n" + " $file = \"\?\?:0\";\n" + " }\n" + " my $suffix = \"[inline]\";\n" + " if ($i == 2) {\n" + " $suffix = \"\";\n" + " }\n" + " push (@$stack, $file.\":\".$fn.$suffix);\n" + " }\n" + " }\n" + " else {\n" + " push (@$stack, \"\?\?:0:unknown\");\n" + " }\n" + "}\n" + "\n" + "sub ExtractSymbolNameInlineStack {\n" + " my $symbols = shift;\n" + " my $address = shift;\n" + "\n" + " my @stack = ();\n" + "\n" + " if (exists $symbols->{$address}) {\n" + " my @localinlinestack = @{$symbols->{$address}};\n" + " for (my $i = $#localinlinestack; $i > 0; $i-=3) {\n" + " my $file = $localinlinestack[$i-1];\n" + " my $fn = $localinlinestack[$i-0];\n" + "\n" + " if ($file eq \"?\" || $file eq \":0\") {\n" + " $file = \"\?\?:0\";\n" + " }\n" + " if ($fn eq '\?\?') {\n" + " # If we can't get the symbol name, at least use the file information.\n" + " $fn = $file;\n" + " }\n" + " my $suffix = \"[inline]\";\n" + " if ($i == 2) {\n" + " $suffix = \"\";\n" + " }\n" + " push (@stack, $fn.$suffix);\n" + " }\n" + " }\n" + " else {\n" + " # If we can't get a symbol name, at least fill in the address.\n" + " push (@stack, $address);\n" + " }\n" + "\n" + " return @stack;\n" + "}\n" + "\n" + "sub ExtractSymbolLocation {\n" + " my $symbols = shift;\n" + " my $address = shift;\n" + " # 'addr2line' outputs \"\?\?:0\" for unknown locations; we do the\n" + " # same to be consistent.\n" + " my $location = \"\?\?:0:unknown\";\n" + " if (exists $symbols->{$address}) {\n" + " my $file = $symbols->{$address}->[1];\n" + " if ($file eq \"?\" || $file eq \":0\") {\n" + " $file = \"\?\?:0\"\n" + " }\n" + " $location = $file . \":\" . $symbols->{$address}->[0];\n" + " }\n" + " return $location;\n" + "}\n" + "\n" + "# Extracts a graph of calls.\n" + "sub ExtractCalls {\n" + " my $symbols = shift;\n" + " my $profile = shift;\n" + " my $calls = {};\n" + " while( my ($stack_trace, $count) = each %$profile ) {\n" + " my @address = split(/\\n/, $stack_trace);\n" + " my @stack = ();\n" + " ExtractSymbolLocationInlineStack($symbols, $address[0], \\@stack);\n" + " for (my $i = 1; $i <= $#address; $i++) {\n" + " ExtractSymbolLocationInlineStack($symbols, $address[$i], \\@stack);\n" + " }\n" + " AddEntry($calls, $stack[0], $count);\n" + " for (my $i = 1; $i < $#address; $i++) {\n" + " AddEntry($calls, \"$stack[$i] -> $stack[$i-1]\", $count);\n" + " }\n" + " }\n" + " return $calls;\n" + "}\n" + "\n" + "sub PrintStacksForText {\n" + " my $symbols = shift;\n" + " my $profile = shift;\n" + "\n" + " while (my ($stack_trace, $count) = each %$profile) {\n" + " my @address = split(/\\n/, $stack_trace);\n" + " for (my $i = 0; $i <= $#address; $i++) {\n" + " $address[$i] = sprintf(\"(%s) %s\", $address[$i], " + "ExtractSymbolLocation($symbols, $address[$i]));\n" + " }\n" + " printf(\"%-8d %s\\n\\n\", $count, join(\"\\n \", @address));\n" + " }\n" + "}\n" + "\n" + "sub PrintCollapsedStacks {\n" + " my $symbols = shift;\n" + " my $profile = shift;\n" + "\n" + " while (my ($stack_trace, $count) = each %$profile) {\n" + " my @address = split(/\\n/, $stack_trace);\n" + " my @names = reverse ( map { ExtractSymbolNameInlineStack($symbols, $_) } @address " + ");\n" + " printf(\"%s %d\\n\", join(\";\", @names), $count);\n" + " }\n" + "}\n" + "\n" + "sub RemoveUninterestingFrames {\n" + " my $symbols = shift;\n" + " my $profile = shift;\n" + "\n" + " # List of function names to skip\n" + " my %skip = ();\n" + " my $skip_regexp = 'NOMATCH';\n" + " if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') {\n" + " foreach my $name ('calloc',\n" + " 'cfree',\n" + " 'malloc',\n" + " 'free',\n" + " 'memalign',\n" + " 'posix_memalign',\n" + " 'pvalloc',\n" + " 'valloc',\n" + " 'realloc',\n" + " 'tc_calloc',\n" + " 'tc_cfree',\n" + " 'tc_malloc',\n" + " 'tc_free',\n" + " 'tc_memalign',\n" + " 'tc_posix_memalign',\n" + " 'tc_pvalloc',\n" + " 'tc_valloc',\n" + " 'tc_realloc',\n" + " 'tc_new',\n" + " 'tc_delete',\n" + " 'tc_newarray',\n" + " 'tc_deletearray',\n" + " 'tc_new_nothrow',\n" + " 'tc_newarray_nothrow',\n" + " 'do_malloc',\n" + " '::do_malloc', # new name -- got moved to an unnamed ns\n" + " '::do_malloc_or_cpp_alloc',\n" + " 'DoSampledAllocation',\n" + " 'simple_alloc::allocate',\n" + " '__malloc_alloc_template::allocate',\n" + " '__builtin_delete',\n" + " '__builtin_new',\n" + " '__builtin_vec_delete',\n" + " '__builtin_vec_new',\n" + " 'operator new',\n" + " 'operator new[]',\n" + " # The entry to our memory-allocation routines on OS X\n" + " 'malloc_zone_malloc',\n" + " 'malloc_zone_calloc',\n" + " 'malloc_zone_valloc',\n" + " 'malloc_zone_realloc',\n" + " 'malloc_zone_memalign',\n" + " 'malloc_zone_free',\n" + " # These mark the beginning/end of our custom sections\n" + " '__start_google_malloc',\n" + " '__stop_google_malloc',\n" + " '__start_malloc_hook',\n" + " '__stop_malloc_hook') {\n" + " $skip{$name} = 1;\n" + " $skip{\"_\" . $name} = 1; # Mach (OS X) adds a _ prefix to everything\n" + " }\n" + " # TODO: Remove TCMalloc once everything has been\n" + " # moved into the tcmalloc:: namespace and we have flushed\n" + " # old code out of the system.\n" + " $skip_regexp = \"TCMalloc|^tcmalloc::\";\n" + " } elsif ($main::profile_type eq 'contention') {\n" + " foreach my $vname ('base::RecordLockProfileData',\n" + " 'base::SubmitMutexProfileData',\n" + " 'base::SubmitSpinLockProfileData',\n" + " 'Mutex::Unlock',\n" + " 'Mutex::UnlockSlow',\n" + " 'Mutex::ReaderUnlock',\n" + " 'MutexLock::~MutexLock',\n" + " 'SpinLock::Unlock',\n" + " 'SpinLock::SlowUnlock',\n" + " 'SpinLockHolder::~SpinLockHolder') {\n" + " $skip{$vname} = 1;\n" + " }\n" + " } elsif ($main::profile_type eq 'cpu' && !$main::opt_no_auto_signal_frames) {\n" + " # Drop signal handlers used for CPU profile collection\n" + " # TODO(dpeng): this should not be necessary; it's taken\n" + " # care of by the general 2nd-pc mechanism below.\n" + " foreach my $name ('ProfileData::Add', # historical\n" + " 'ProfileData::prof_handler', # historical\n" + " 'CpuProfiler::prof_handler',\n" + " '__FRAME_END__',\n" + " '__pthread_sighandler',\n" + " '__restore') {\n" + " $skip{$name} = 1;\n" + " }\n" + " } else {\n" + " # Nothing skipped for unknown types\n" + " }\n" + "\n" + " if ($main::profile_type eq 'cpu') {\n" + " # If all the second-youngest program counters are the same,\n" + " # this STRONGLY suggests that it is an artifact of measurement,\n" + " # i.e., stack frames pushed by the CPU profiler signal handler.\n" + " # Hence, we delete them.\n" + " # (The topmost PC is read from the signal structure, not from\n" + " # the stack, so it does not get involved.)\n" + " while (my $second_pc = IsSecondPcAlwaysTheSame($profile)) {\n" + " my $result = {};\n" + " my $func = '';\n" + " if (exists($symbols->{$second_pc})) {\n" + " $second_pc = $symbols->{$second_pc}->[0];\n" + " }\n" + " if ($main::opt_no_auto_signal_frames) {\n" + " print STDERR \"All second stack frames are same: `$second_pc'.\\nMight be " + "stack trace capturing bug.\\n\";\n" + " last;\n" + " }\n" + " print STDERR \"Removing $second_pc from all stack traces.\\n\";\n" + " foreach my $k (keys(%{$profile})) {\n" + " my $count = $profile->{$k};\n" + " my @addrs = split(/\\n/, $k);\n" + " my $topaddr = POSIX::strtoul($addrs[0], 16);\n" + " splice @addrs, 1, 1;\n" + " if ($#addrs > 1) {\n" + " my $subtopaddr = POSIX::strtoul($addrs[1], 16);\n" + " if ($subtopaddr + 1 == $topaddr) {\n" + " splice @addrs, 1, 1;\n" + " }\n" + " }\n" + " my $reduced_path = join(\"\\n\", @addrs);\n" + " AddEntry($result, $reduced_path, $count);\n" + " }\n" + " $profile = $result;\n" + " }\n" + " }\n" + "\n" + " my $result = {};\n" + " foreach my $k (keys(%{$profile})) {\n" + " my $count = $profile->{$k};\n" + " my @addrs = split(/\\n/, $k);\n" + " my @path = ();\n" + " foreach my $a (@addrs) {\n" + " if (exists($symbols->{$a})) {\n" + " my $func = $symbols->{$a}->[0];\n" + " if ($skip{$func} || ($func =~ m/$skip_regexp/)) {\n" + " next;\n" + " }\n" + " }\n" + " push(@path, $a);\n" + " }\n" + " my $reduced_path = join(\"\\n\", @path);\n" + " AddEntry($result, $reduced_path, $count);\n" + " }\n" + " return $result;\n" + "}\n" + "\n" + "# Reduce profile to granularity given by user\n" + "sub ReduceProfile {\n" + " my $symbols = shift;\n" + " my $profile = shift;\n" + " my $result = {};\n" + " my $fullname_to_shortname_map = {};\n" + " FillFullnameToShortnameMap($symbols, $fullname_to_shortname_map);\n" + " foreach my $k (keys(%{$profile})) {\n" + " my $count = $profile->{$k};\n" + " my @translated = TranslateStack($symbols, $fullname_to_shortname_map, $k);\n" + " my @path = ();\n" + " my %seen = ();\n" + " $seen{''} = 1; # So that empty keys are skipped\n" + " foreach my $e (@translated) {\n" + " # To avoid double-counting due to recursion, skip a stack-trace\n" + " # entry if it has already been seen\n" + " if (!$seen{$e}) {\n" + " $seen{$e} = 1;\n" + " push(@path, $e);\n" + " }\n" + " }\n" + " my $reduced_path = join(\"\\n\", @path);\n" + " AddEntry($result, $reduced_path, $count);\n" + " }\n" + " return $result;\n" + "}\n" + "\n" + "# Does the specified symbol array match the regexp?\n" + "sub SymbolMatches {\n" + " my $sym = shift;\n" + " my $re = shift;\n" + " if (defined($sym)) {\n" + " for (my $i = 0; $i < $#{$sym}; $i += 3) {\n" + " if ($sym->[$i] =~ m/$re/ || $sym->[$i+1] =~ m/$re/) {\n" + " return 1;\n" + " }\n" + " }\n" + " }\n" + " return 0;\n" + "}\n" + "\n" + "# Focus only on paths involving specified regexps\n" + "sub FocusProfile {\n" + " my $symbols = shift;\n" + " my $profile = shift;\n" + " my $focus = shift;\n" + " my $result = {};\n" + " foreach my $k (keys(%{$profile})) {\n" + " my $count = $profile->{$k};\n" + " my @addrs = split(/\\n/, $k);\n" + " foreach my $a (@addrs) {\n" + " # Reply if it matches either the address/shortname/fileline\n" + " if (($a =~ m/$focus/) || SymbolMatches($symbols->{$a}, $focus)) {\n" + " AddEntry($result, $k, $count);\n" + " last;\n" + " }\n" + " }\n" + " }\n" + " return $result;\n" + "}\n" + "\n" + "# Focus only on paths not involving specified regexps\n" + "sub IgnoreProfile {\n" + " my $symbols = shift;\n" + " my $profile = shift;\n" + " my $ignore = shift;\n" + " my $result = {};\n" + " foreach my $k (keys(%{$profile})) {\n" + " my $count = $profile->{$k};\n" + " my @addrs = split(/\\n/, $k);\n" + " my $matched = 0;\n" + " foreach my $a (@addrs) {\n" + " # Reply if it matches either the address/shortname/fileline\n" + " if (($a =~ m/$ignore/) || SymbolMatches($symbols->{$a}, $ignore)) {\n" + " $matched = 1;\n" + " last;\n" + " }\n" + " }\n" + " if (!$matched) {\n" + " AddEntry($result, $k, $count);\n" + " }\n" + " }\n" + " return $result;\n" + "}\n" + "\n" + "# Get total count in profile\n" + "sub TotalProfile {\n" + " my $profile = shift;\n" + " my $result = 0;\n" + " foreach my $k (keys(%{$profile})) {\n" + " $result += $profile->{$k};\n" + " }\n" + " return $result;\n" + "}\n" + "\n" + "# Add A to B\n" + "sub AddProfile {\n" + " my $A = shift;\n" + " my $B = shift;\n" + "\n" + " my $R = {};\n" + " # add all keys in A\n" + " foreach my $k (keys(%{$A})) {\n" + " my $v = $A->{$k};\n" + " AddEntry($R, $k, $v);\n" + " }\n" + " # add all keys in B\n" + " foreach my $k (keys(%{$B})) {\n" + " my $v = $B->{$k};\n" + " AddEntry($R, $k, $v);\n" + " }\n" + " return $R;\n" + "}\n" + "\n" + "# Merges symbol maps\n" + "sub MergeSymbols {\n" + " my $A = shift;\n" + " my $B = shift;\n" + "\n" + " my $R = {};\n" + " foreach my $k (keys(%{$A})) {\n" + " $R->{$k} = $A->{$k};\n" + " }\n" + " if (defined($B)) {\n" + " foreach my $k (keys(%{$B})) {\n" + " $R->{$k} = $B->{$k};\n" + " }\n" + " }\n" + " return $R;\n" + "}\n" + "\n" + "\n" + "# Add A to B\n" + "sub AddPcs {\n" + " my $A = shift;\n" + " my $B = shift;\n" + "\n" + " my $R = {};\n" + " # add all keys in A\n" + " foreach my $k (keys(%{$A})) {\n" + " $R->{$k} = 1\n" + " }\n" + " # add all keys in B\n" + " foreach my $k (keys(%{$B})) {\n" + " $R->{$k} = 1\n" + " }\n" + " return $R;\n" + "}\n" + "\n" + "# Subtract B from A\n" + "sub SubtractProfile {\n" + " my $A = shift;\n" + " my $B = shift;\n" + "\n" + " my $R = {};\n" + " foreach my $k (keys(%{$A})) {\n" + " my $v = $A->{$k} - GetEntry($B, $k);\n" + " if ($v < 0 && $main::opt_drop_negative) {\n" + " $v = 0;\n" + " }\n" + " AddEntry($R, $k, $v);\n" + " }\n" + " if (!$main::opt_drop_negative) {\n" + " # Take care of when subtracted profile has more entries\n" + " foreach my $k (keys(%{$B})) {\n" + " if (!exists($A->{$k})) {\n" + " AddEntry($R, $k, 0 - $B->{$k});\n" + " }\n" + " }\n" + " }\n" + " return $R;\n" + "}\n" + "\n" + "# Get entry from profile; zero if not present\n" + "sub GetEntry {\n" + " my $profile = shift;\n" + " my $k = shift;\n" + " if (exists($profile->{$k})) {\n" + " return $profile->{$k};\n" + " } else {\n" + " return 0;\n" + " }\n" + "}\n" + "\n" + "# Add entry to specified profile\n" + "sub AddEntry {\n" + " my $profile = shift;\n" + " my $k = shift;\n" + " my $n = shift;\n" + " if (!exists($profile->{$k})) {\n" + " $profile->{$k} = 0;\n" + " }\n" + " $profile->{$k} += $n;\n" + "}\n" + "\n" + "# Add a stack of entries to specified profile, and add them to the $pcs\n" + "# list.\n" + "sub AddEntries {\n" + " my $profile = shift;\n" + " my $pcs = shift;\n" + " my $stack = shift;\n" + " my $count = shift;\n" + " my @k = ();\n" + "\n" + " foreach my $e (split(/\\s+/, $stack)) {\n" + " my $pc = HexExtend($e);\n" + " $pcs->{$pc} = 1;\n" + " push @k, $pc;\n" + " }\n" + " AddEntry($profile, (join \"\\n\", @k), $count);\n" + "}\n" + "\n" + "##### Code to profile a server dynamically #####\n" + "\n" + "sub CheckSymbolPage {\n" + " my $url = SymbolPageURL();\n" + " my $command = ShellEscape(@URL_FETCHER, $url);\n" + " open(SYMBOL, \"$command |\") or error($command);\n" + " my $line = ;\n" + " $line =~ s/\\r//g; # turn windows-looking lines into unix-looking lines\n" + " close(SYMBOL);\n" + " unless (defined($line)) {\n" + " error(\"$url doesn't exist\\n\");\n" + " }\n" + "\n" + " if ($line =~ /^num_symbols:\\s+(\\d+)$/) {\n" + " if ($1 == 0) {\n" + " error(\"Stripped binary. No symbols available.\\n\");\n" + " }\n" + " } else {\n" + " error(\"Failed to get the number of symbols from $url\\n\");\n" + " }\n" + "}\n" + "\n" + "sub IsProfileURL {\n" + " my $profile_name = shift;\n" + " if (-f $profile_name) {\n" + " printf STDERR \"Using local file $profile_name.\\n\";\n" + " return 0;\n" + " }\n" + " return 1;\n" + "}\n" + "\n" + "sub ParseProfileURL {\n" + " my $profile_name = shift;\n" + "\n" + " if (!defined($profile_name) || $profile_name eq \"\") {\n" + " return ();\n" + " }\n" + "\n" + " # Split profile URL - matches all non-empty strings, so no test.\n" + " $profile_name =~ m,^(https?://)?([^/]+)(.*?)(/|$PROFILES)?$,;\n" + "\n" + " my $proto = $1 || \"http://\";\n" + " my $hostport = $2;\n" + " my $prefix = $3;\n" + " my $profile = $4 || \"/\";\n" + "\n" + " my $host = $hostport;\n" + " $host =~ s/:.*//;\n" + "\n" + " my $baseurl = \"$proto$hostport$prefix\";\n" + " return ($host, $baseurl, $profile);\n" + "}\n" + "\n" + "# We fetch symbols from the first profile argument.\n" + "sub SymbolPageURL {\n" + " my ($host, $baseURL, $path) = ParseProfileURL($main::pfile_args[0]);\n" + " return \"$baseURL$SYMBOL_PAGE\";\n" + "}\n" + "\n" + "sub FetchProgramName() {\n" + " my ($host, $baseURL, $path) = ParseProfileURL($main::pfile_args[0]);\n" + " my $url = \"$baseURL$PROGRAM_NAME_PAGE\";\n" + " my $command_line = ShellEscape(@URL_FETCHER, $url);\n" + " open(CMDLINE, \"$command_line |\") or error($command_line);\n" + " my $cmdline = ;\n" + " $cmdline =~ s/\\r//g; # turn windows-looking lines into unix-looking lines\n" + " close(CMDLINE);\n" + " error(\"Failed to get program name from $url\\n\") unless defined($cmdline);\n" + " $cmdline =~ s/\\x00.+//; # Remove argv[1] and latters.\n" + " $cmdline =~ s!\\n!!g; # Remove LFs.\n" + " return $cmdline;\n" + "}\n" + "\n" + "# Gee, curl's -L (--location) option isn't reliable at least\n" + "# with its 7.12.3 version. Curl will forget to post data if\n" + "# there is a redirection. This function is a workaround for\n" + "# curl. Redirection happens on borg hosts.\n" + "sub ResolveRedirectionForCurl {\n" + " my $url = shift;\n" + " my $command_line = ShellEscape(@URL_FETCHER, \"--head\", $url);\n" + " open(CMDLINE, \"$command_line |\") or error($command_line);\n" + " while () {\n" + " s/\\r//g; # turn windows-looking lines into unix-looking lines\n" + " if (/^Location: (.*)/) {\n" + " $url = $1;\n" + " }\n" + " }\n" + " close(CMDLINE);\n" + " return $url;\n" + "}\n" + "\n" + "# Add a timeout flat to URL_FETCHER. Returns a new list.\n" + "sub AddFetchTimeout {\n" + " my $timeout = shift;\n" + " my @fetcher = @_;\n" + " if (defined($timeout)) {\n" + " if (join(\" \", @fetcher) =~ m/\\bcurl -s/) {\n" + " push(@fetcher, \"--max-time\", sprintf(\"%d\", $timeout));\n" + " } elsif (join(\" \", @fetcher) =~ m/\\brpcget\\b/) {\n" + " push(@fetcher, sprintf(\"--deadline=%d\", $timeout));\n" + " }\n" + " }\n" + " return @fetcher;\n" + "}\n" + "\n" + "# Reads a symbol map from the file handle name given as $1, returning\n" + "# the resulting symbol map. Also processes variables relating to symbols.\n" + "# Currently, the only variable processed is 'binary=' which updates\n" + "# $main::prog to have the correct program name.\n" + "sub ReadSymbols {\n" + " my $in = shift;\n" + " my $map = {};\n" + " while (<$in>) {\n" + " s/\\r//g; # turn windows-looking lines into unix-looking lines\n" + " # Removes all the leading zeroes from the symbols, see comment below.\n" + " if (m/^0x0*([0-9a-f]+)\\s+(.+)/) {\n" + " $map->{$1} = $2;\n" + " } elsif (m/^---/) {\n" + " last;\n" + " } elsif (m/^([a-z][^=]*)=(.*)$/ ) {\n" + " my ($variable, $value) = ($1, $2);\n" + " for ($variable, $value) {\n" + " s/^\\s+//;\n" + " s/\\s+$//;\n" + " }\n" + " if ($variable eq \"binary\") {\n" + " if ($main::prog ne $UNKNOWN_BINARY && $main::prog ne $value) {\n" + " printf STDERR (\"Warning: Mismatched binary name '%s', using '%s'.\\n\",\n" + " $main::prog, $value);\n" + " }\n" + " $main::prog = $value;\n" + " } else {\n" + " printf STDERR (\"Ignoring unknown variable in symbols list: \" .\n" + " \"'%s' = '%s'\\n\", $variable, $value);\n" + " }\n" + " }\n" + " }\n" + " return $map;\n" + "}\n" + "\n" + "# Fetches and processes symbols to prepare them for use in the profile output\n" + "# code. If the optional 'symbol_map' arg is not given, fetches symbols from\n" + "# $SYMBOL_PAGE for all PC values found in profile. Otherwise, the raw symbols\n" + "# are assumed to have already been fetched into 'symbol_map' and are simply\n" + "# extracted and processed.\n" + "sub FetchSymbols {\n" + " my $pcset = shift;\n" + " my $symbol_map = shift;\n" + "\n" + " my %seen = ();\n" + " my @pcs = grep { !$seen{$_}++ } keys(%$pcset); # uniq\n" + "\n" + " if (!defined($symbol_map)) {\n" + " my $post_data = join(\"+\", sort((map {\"0x\" . \"$_\"} @pcs)));\n" + "\n" + " open(POSTFILE, \">$main::tmpfile_sym\");\n" + " print POSTFILE $post_data;\n" + " close(POSTFILE);\n" + "\n" + " my $url = SymbolPageURL();\n" + "\n" + " my $command_line;\n" + " if (join(\" \", @URL_FETCHER) =~ m/\\bcurl -s/) {\n" + " $url = ResolveRedirectionForCurl($url);\n" + " $command_line = ShellEscape(@URL_FETCHER, \"-d\", \"\\@$main::tmpfile_sym\",\n" + " $url);\n" + " } else {\n" + " $command_line = (ShellEscape(@URL_FETCHER, \"--post\", $url)\n" + " . \" < \" . ShellEscape($main::tmpfile_sym));\n" + " }\n" + " # We use c++filt in case $SYMBOL_PAGE gives us mangled symbols.\n" + " my $escaped_cppfilt = ShellEscape($obj_tool_map{\"c++filt\"});\n" + " open(SYMBOL, \"$command_line | $escaped_cppfilt |\") or error($command_line);\n" + " $symbol_map = ReadSymbols(*SYMBOL{IO});\n" + " close(SYMBOL);\n" + " }\n" + "\n" + " my $symbols = {};\n" + " foreach my $pc (@pcs) {\n" + " my $fullname;\n" + " # For 64 bits binaries, symbols are extracted with 8 leading zeroes.\n" + " # Then /symbol reads the long symbols in as uint64, and outputs\n" + " # the result with a \"0x%08llx\" format which get rid of the zeroes.\n" + " # By removing all the leading zeroes in both $pc and the symbols from\n" + " # /symbol, the symbols match and are retrievable from the map.\n" + " my $shortpc = $pc;\n" + " $shortpc =~ s/^0*//;\n" + " # Each line may have a list of names, which includes the function\n" + " # and also other functions it has inlined. They are separated (in\n" + " # PrintSymbolizedProfile), by --, which is illegal in function names.\n" + " my $fullnames;\n" + " if (defined($symbol_map->{$shortpc})) {\n" + " $fullnames = $symbol_map->{$shortpc};\n" + " } else {\n" + " $fullnames = \"0x\" . $pc; # Just use addresses\n" + " }\n" + " my $sym = [];\n" + " $symbols->{$pc} = $sym;\n" + " foreach my $fullname (split(\"--\", $fullnames)) {\n" + " my $name = ShortFunctionName($fullname);\n" + " push(@{$sym}, $name, \"?\", $fullname);\n" + " }\n" + " }\n" + " return $symbols;\n" + "}\n" + "\n" + "sub BaseName {\n" + " my $file_name = shift;\n" + " $file_name =~ s!^.*/!!; # Remove directory name\n" + " return $file_name;\n" + "}\n" + "\n" + "sub MakeProfileBaseName {\n" + " my ($binary_name, $profile_name) = @_;\n" + " my ($host, $baseURL, $path) = ParseProfileURL($profile_name);\n" + " my $binary_shortname = BaseName($binary_name);\n" + " return sprintf(\"%s.%s.%s\",\n" + " $binary_shortname, $main::op_time, $host);\n" + "}\n" + "\n" + "sub FetchDynamicProfile {\n" + " my $binary_name = shift;\n" + " my $profile_name = shift;\n" + " my $fetch_name_only = shift;\n" + " my $encourage_patience = shift;\n" + "\n" + " if (!IsProfileURL($profile_name)) {\n" + " return $profile_name;\n" + " } else {\n" + " my ($host, $baseURL, $path) = ParseProfileURL($profile_name);\n" + " if ($path eq \"\" || $path eq \"/\") {\n" + " # Missing type specifier defaults to cpu-profile\n" + " $path = $PROFILE_PAGE;\n" + " }\n" + "\n" + " my $profile_file = MakeProfileBaseName($binary_name, $profile_name);\n" + "\n" + " my $url = \"$baseURL$path\";\n" + " my $fetch_timeout = undef;\n" + " if ($path =~ m/$PROFILE_PAGE|$PMUPROFILE_PAGE/) {\n" + " if ($path =~ m/[?]/) {\n" + " $url .= \"&\";\n" + " } else {\n" + " $url .= \"?\";\n" + " }\n" + " $url .= sprintf(\"seconds=%d\", $main::opt_seconds);\n" + " $fetch_timeout = $main::opt_seconds * 1.01 + 60;\n" + " } else {\n" + " # For non-CPU profiles, we add a type-extension to\n" + " # the target profile file name.\n" + " my $suffix = $path;\n" + " $suffix =~ s,/,.,g;\n" + " $profile_file .= $suffix;\n" + " }\n" + "\n" + " my $profile_dir = $ENV{\"PPROF_TMPDIR\"} || ($ENV{HOME} . \"/pprof\");\n" + " if (! -d $profile_dir) {\n" + " mkdir($profile_dir)\n" + " || die(\"Unable to create profile directory $profile_dir: $!\\n\");\n" + " }\n" + " my $tmp_profile = \"$profile_dir/.tmp.$profile_file\";\n" + " my $real_profile = \"$profile_dir/$profile_file\";\n" + "\n" + " if ($fetch_name_only > 0) {\n" + " return $real_profile;\n" + " }\n" + "\n" + " my @fetcher = AddFetchTimeout($fetch_timeout, @URL_FETCHER);\n" + " my $cmd = ShellEscape(@fetcher, $url) . \" > \" . ShellEscape($tmp_profile);\n" + " if ($path =~ m/$PROFILE_PAGE|$PMUPROFILE_PAGE|$CENSUSPROFILE_PAGE/){\n" + " print STDERR \"Gathering CPU profile from $url for $main::opt_seconds seconds " + "to\\n ${real_profile}\\n\";\n" + " if ($encourage_patience) {\n" + " print STDERR \"Be patient...\\n\";\n" + " }\n" + " } else {\n" + " print STDERR \"Fetching $path profile from $url to\\n ${real_profile}\\n\";\n" + " }\n" + "\n" + " (system($cmd) == 0) || error(\"Failed to get profile: $cmd: $!\\n\");\n" + " (system(\"mv\", $tmp_profile, $real_profile) == 0) || error(\"Unable to rename " + "profile\\n\");\n" + " print STDERR \"Wrote profile to $real_profile\\n\";\n" + " $main::collected_profile = $real_profile;\n" + " return $main::collected_profile;\n" + " }\n" + "}\n" + "\n" + "# Collect profiles in parallel\n" + "sub FetchDynamicProfiles {\n" + " my $items = scalar(@main::pfile_args);\n" + " my $levels = log($items) / log(2);\n" + "\n" + " if ($items == 1) {\n" + " $main::profile_files[0] = FetchDynamicProfile($main::prog, $main::pfile_args[0], " + "0, 1);\n" + " } else {\n" + " # math rounding issues\n" + " if ((2 ** $levels) < $items) {\n" + " $levels++;\n" + " }\n" + " my $count = scalar(@main::pfile_args);\n" + " for (my $i = 0; $i < $count; $i++) {\n" + " $main::profile_files[$i] = FetchDynamicProfile($main::prog, " + "$main::pfile_args[$i], 1, 0);\n" + " }\n" + " print STDERR \"Fetching $count profiles, Be patient...\\n\";\n" + " FetchDynamicProfilesRecurse($levels, 0, 0);\n" + " $main::collected_profile = join(\" \\\\\\n \", @main::profile_files);\n" + " }\n" + "}\n" + "\n" + "# Recursively fork a process to get enough processes\n" + "# collecting profiles\n" + "sub FetchDynamicProfilesRecurse {\n" + " my $maxlevel = shift;\n" + " my $level = shift;\n" + " my $position = shift;\n" + "\n" + " if (my $pid = fork()) {\n" + " $position = 0 | ($position << 1);\n" + " TryCollectProfile($maxlevel, $level, $position);\n" + " wait;\n" + " } else {\n" + " $position = 1 | ($position << 1);\n" + " TryCollectProfile($maxlevel, $level, $position);\n" + " cleanup();\n" + " exit(0);\n" + " }\n" + "}\n" + "\n" + "# Collect a single profile\n" + "sub TryCollectProfile {\n" + " my $maxlevel = shift;\n" + " my $level = shift;\n" + " my $position = shift;\n" + "\n" + " if ($level >= ($maxlevel - 1)) {\n" + " if ($position < scalar(@main::pfile_args)) {\n" + " FetchDynamicProfile($main::prog, $main::pfile_args[$position], 0, 0);\n" + " }\n" + " } else {\n" + " FetchDynamicProfilesRecurse($maxlevel, $level+1, $position);\n" + " }\n" + "}\n" + "\n" + "##### Parsing code #####\n" + "\n" + "# Provide a small streaming-read module to handle very large\n" + "# cpu-profile files. Stream in chunks along a sliding window.\n" + "# Provides an interface to get one 'slot', correctly handling\n" + "# endian-ness differences. A slot is one 32-bit or 64-bit word\n" + "# (depending on the input profile). We tell endianness and bit-size\n" + "# for the profile by looking at the first 8 bytes: in cpu profiles,\n" + "# the second slot is always 3 (we'll accept anything that's not 0).\n" + "BEGIN {\n" + " package CpuProfileStream;\n" + "\n" + " sub new {\n" + " my ($class, $file, $fname) = @_;\n" + " my $self = { file => $file,\n" + " base => 0,\n" + " stride => 512 * 1024, # must be a multiple of bitsize/8\n" + " slots => [],\n" + " unpack_code => \"\", # N for big-endian, V for little\n" + " perl_is_64bit => 1, # matters if profile is 64-bit\n" + " };\n" + " bless $self, $class;\n" + " # Let unittests adjust the stride\n" + " if ($main::opt_test_stride > 0) {\n" + " $self->{stride} = $main::opt_test_stride;\n" + " }\n" + " # Read the first two slots to figure out bitsize and endianness.\n" + " my $slots = $self->{slots};\n" + " my $str;\n" + " read($self->{file}, $str, 8);\n" + " # Set the global $address_length based on what we see here.\n" + " # 8 is 32-bit (8 hexadecimal chars); 16 is 64-bit (16 hexadecimal chars).\n" + " $address_length = ($str eq (chr(0)x8)) ? 16 : 8;\n" + " if ($address_length == 8) {\n" + " if (substr($str, 6, 2) eq chr(0)x2) {\n" + " $self->{unpack_code} = 'V'; # Little-endian.\n" + " } elsif (substr($str, 4, 2) eq chr(0)x2) {\n" + " $self->{unpack_code} = 'N'; # Big-endian\n" + " } else {\n" + " ::error(\"$fname: header size >= 2**16\\n\");\n" + " }\n" + " @$slots = unpack($self->{unpack_code} . \"*\", $str);\n" + " } else {\n" + " # If we're a 64-bit profile, check if we're a 64-bit-capable\n" + " # perl. Otherwise, each slot will be represented as a float\n" + " # instead of an int64, losing precision and making all the\n" + " # 64-bit addresses wrong. We won't complain yet, but will\n" + " # later if we ever see a value that doesn't fit in 32 bits.\n" + " my $has_q = 0;\n" + " eval { $has_q = pack(\"Q\", \"1\") ? 1 : 1; };\n" + " if (!$has_q) {\n" + " $self->{perl_is_64bit} = 0;\n" + " }\n" + " read($self->{file}, $str, 8);\n" + " if (substr($str, 4, 4) eq chr(0)x4) {\n" + " # We'd love to use 'Q', but it's a) not universal, b) not endian-proof.\n" + " $self->{unpack_code} = 'V'; # Little-endian.\n" + " } elsif (substr($str, 0, 4) eq chr(0)x4) {\n" + " $self->{unpack_code} = 'N'; # Big-endian\n" + " } else {\n" + " ::error(\"$fname: header size >= 2**32\\n\");\n" + " }\n" + " my @pair = unpack($self->{unpack_code} . \"*\", $str);\n" + " # Since we know one of the pair is 0, it's fine to just add them.\n" + " @$slots = (0, $pair[0] + $pair[1]);\n" + " }\n" + " return $self;\n" + " }\n" + "\n" + " # Load more data when we access slots->get(X) which is not yet in memory.\n" + " sub overflow {\n" + " my ($self) = @_;\n" + " my $slots = $self->{slots};\n" + " $self->{base} += $#$slots + 1; # skip over data we're replacing\n" + " my $str;\n" + " read($self->{file}, $str, $self->{stride});\n" + " if ($address_length == 8) { # the 32-bit case\n" + " # This is the easy case: unpack provides 32-bit unpacking primitives.\n" + " @$slots = unpack($self->{unpack_code} . \"*\", $str);\n" + " } else {\n" + " # We need to unpack 32 bits at a time and combine.\n" + " my @b32_values = unpack($self->{unpack_code} . \"*\", $str);\n" + " my @b64_values = ();\n" + " for (my $i = 0; $i < $#b32_values; $i += 2) {\n" + " # TODO(csilvers): if this is a 32-bit perl, the math below\n" + " # could end up in a too-large int, which perl will promote\n" + " # to a double, losing necessary precision. Deal with that.\n" + " # Right now, we just die.\n" + " my ($lo, $hi) = ($b32_values[$i], $b32_values[$i+1]);\n" + " if ($self->{unpack_code} eq 'N') { # big-endian\n" + " ($lo, $hi) = ($hi, $lo);\n" + " }\n" + " my $value = $lo + $hi * (2**32);\n" + " if (!$self->{perl_is_64bit} && # check value is exactly represented\n" + " (($value % (2**32)) != $lo || int($value / (2**32)) != $hi)) {\n" + " ::error(\"Need a 64-bit perl to process this 64-bit profile.\\n\");\n" + " }\n" + " push(@b64_values, $value);\n" + " }\n" + " @$slots = @b64_values;\n" + " }\n" + " }\n" + "\n" + " # Access the i-th long in the file (logically), or -1 at EOF.\n" + " sub get {\n" + " my ($self, $idx) = @_;\n" + " my $slots = $self->{slots};\n" + " while ($#$slots >= 0) {\n" + " if ($idx < $self->{base}) {\n" + " # The only time we expect a reference to $slots[$i - something]\n" + " # after referencing $slots[$i] is reading the very first header.\n" + " # Since $stride > |header|, that shouldn't cause any lookback\n" + " # errors. And everything after the header is sequential.\n" + " print STDERR \"Unexpected look-back reading CPU profile\";\n" + " return -1; # shrug, don't know what better to return\n" + " } elsif ($idx > $self->{base} + $#$slots) {\n" + " $self->overflow();\n" + " } else {\n" + " return $slots->[$idx - $self->{base}];\n" + " }\n" + " }\n" + " # If we get here, $slots is [], which means we've reached EOF\n" + " return -1; # unique since slots is supposed to hold unsigned numbers\n" + " }\n" + "}\n" + "\n" + "# Reads the top, 'header' section of a profile, and returns the last\n" + "# line of the header, commonly called a 'header line'. The header\n" + "# section of a profile consists of zero or more 'command' lines that\n" + "# are instructions to pprof, which pprof executes when reading the\n" + "# header. All 'command' lines start with a %. After the command\n" + "# lines is the 'header line', which is a profile-specific line that\n" + "# indicates what type of profile it is, and perhaps other global\n" + "# information about the profile. For instance, here's a header line\n" + "# for a heap profile:\n" + "# heap profile: 53: 38236 [ 5525: 1284029] @ heapprofile\n" + "# For historical reasons, the CPU profile does not contain a text-\n" + "# readable header line. If the profile looks like a CPU profile,\n" + "# this function returns \"\". If no header line could be found, this\n" + "# function returns undef.\n" + "#\n" + "# The following commands are recognized:\n" + "# %warn -- emit the rest of this line to stderr, prefixed by 'WARNING:'\n" + "#\n" + "# The input file should be in binmode.\n" + "sub ReadProfileHeader {\n" + " local *PROFILE = shift;\n" + " my $firstchar = \"\";\n" + " my $line = \"\";\n" + " read(PROFILE, $firstchar, 1);\n" + " seek(PROFILE, -1, 1); # unread the firstchar\n" + " if ($firstchar !~ /[[:print:]]/) { # is not a text character\n" + " return \"\";\n" + " }\n" + " while (defined($line = )) {\n" + " $line =~ s/\\r//g; # turn windows-looking lines into unix-looking lines\n" + " if ($line =~ /^%warn\\s+(.*)/) { # 'warn' command\n" + " # Note this matches both '%warn blah\\n' and '%warn\\n'.\n" + " print STDERR \"WARNING: $1\\n\"; # print the rest of the line\n" + " } elsif ($line =~ /^%/) {\n" + " print STDERR \"Ignoring unknown command from profile header: $line\";\n" + " } else {\n" + " # End of commands, must be the header line.\n" + " return $line;\n" + " }\n" + " }\n" + " return undef; # got to EOF without seeing a header line\n" + "}\n" + "\n" + "sub IsSymbolizedProfileFile {\n" + " my $file_name = shift;\n" + " if (!(-e $file_name) || !(-r $file_name)) {\n" + " return 0;\n" + " }\n" + " # Check if the file contains a symbol-section marker.\n" + " open(TFILE, \"<$file_name\");\n" + " binmode TFILE;\n" + " my $firstline = ReadProfileHeader(*TFILE);\n" + " close(TFILE);\n" + " if (!$firstline) {\n" + " return 0;\n" + " }\n" + " $SYMBOL_PAGE =~ m,[^/]+$,; # matches everything after the last slash\n" + " my $symbol_marker = $&;\n" + " return $firstline =~ /^--- *$symbol_marker/;\n" + "}\n" + "\n" + "# Parse profile generated by common/profiler.cc and return a reference\n" + "# to a map:\n" + "# $result->{version} Version number of profile file\n" + "# $result->{period} Sampling period (in microseconds)\n" + "# $result->{profile} Profile object\n" + "# $result->{map} Memory map info from profile\n" + "# $result->{pcs} Hash of all PC values seen, key is hex address\n" + "sub ReadProfile {\n" + " my $prog = shift;\n" + " my $fname = shift;\n" + " my $result; # return value\n" + "\n" + " $CONTENTION_PAGE =~ m,[^/]+$,; # matches everything after the last slash\n" + " my $contention_marker = $&;\n" + " $GROWTH_PAGE =~ m,[^/]+$,; # matches everything after the last slash\n" + " my $growth_marker = $&;\n" + " $SYMBOL_PAGE =~ m,[^/]+$,; # matches everything after the last slash\n" + " my $symbol_marker = $&;\n" + " $PROFILE_PAGE =~ m,[^/]+$,; # matches everything after the last slash\n" + " my $profile_marker = $&;\n" + "\n" + " # Look at first line to see if it is a heap or a CPU profile.\n" + " # CPU profile may start with no header at all, and just binary data\n" + " # (starting with \\0\\0\\0\\0) -- in that case, don't try to read the\n" + " # whole firstline, since it may be gigabytes(!) of data.\n" + " open(PROFILE, \"<$fname\") || error(\"$fname: $!\\n\");\n" + " binmode PROFILE; # New perls do UTF-8 processing\n" + " my $header = ReadProfileHeader(*PROFILE);\n" + " if (!defined($header)) { # means \"at EOF\"\n" + " error(\"Profile is empty.\\n\");\n" + " }\n" + "\n" + " my $symbols;\n" + " if ($header =~ m/^--- *$symbol_marker/o) {\n" + " # Verify that the user asked for a symbolized profile\n" + " if (!$main::use_symbolized_profile) {\n" + " # we have both a binary and symbolized profiles, abort\n" + " error(\"FATAL ERROR: Symbolized profile\\n $fname\\ncannot be used with \" .\n" + " \"a binary arg. Try again without passing\\n $prog\\n\");\n" + " }\n" + " # Read the symbol section of the symbolized profile file.\n" + " $symbols = ReadSymbols(*PROFILE{IO});\n" + " # Read the next line to get the header for the remaining profile.\n" + " $header = ReadProfileHeader(*PROFILE) || \"\";\n" + " }\n" + "\n" + " $main::profile_type = '';\n" + " if ($header =~ m/^heap profile:.*$growth_marker/o) {\n" + " $main::profile_type = 'growth';\n" + " $result = ReadHeapProfile($prog, *PROFILE, $header);\n" + " } elsif ($header =~ m/^heap profile:/) {\n" + " $main::profile_type = 'heap';\n" + " $result = ReadHeapProfile($prog, *PROFILE, $header);\n" + " } elsif ($header =~ m/^--- *$contention_marker/o) {\n" + " $main::profile_type = 'contention';\n" + " $result = ReadSynchProfile($prog, *PROFILE);\n" + " } elsif ($header =~ m/^--- *Stacks:/) {\n" + " print STDERR\n" + " \"Old format contention profile: mistakenly reports \" .\n" + " \"condition variable signals as lock contentions.\\n\";\n" + " $main::profile_type = 'contention';\n" + " $result = ReadSynchProfile($prog, *PROFILE);\n" + " } elsif ($header =~ m/^--- *$profile_marker/) {\n" + " # the binary cpu profile data starts immediately after this line\n" + " $main::profile_type = 'cpu';\n" + " $result = ReadCPUProfile($prog, $fname, *PROFILE);\n" + " } else {\n" + " if (defined($symbols)) {\n" + " # a symbolized profile contains a format we don't recognize, bail out\n" + " error(\"$fname: Cannot recognize profile section after symbols.\\n\");\n" + " }\n" + " # no ascii header present -- must be a CPU profile\n" + " $main::profile_type = 'cpu';\n" + " $result = ReadCPUProfile($prog, $fname, *PROFILE);\n" + " }\n" + "\n" + " close(PROFILE);\n" + "\n" + " # if we got symbols along with the profile, return those as well\n" + " if (defined($symbols)) {\n" + " $result->{symbols} = $symbols;\n" + " }\n" + "\n" + " return $result;\n" + "}\n" + "\n" + "# Subtract one from caller pc so we map back to call instr.\n" + "# However, don't do this if we're reading a symbolized profile\n" + "# file, in which case the subtract-one was done when the file\n" + "# was written.\n" + "#\n" + "# We apply the same logic to all readers, though ReadCPUProfile uses an\n" + "# independent implementation.\n" + "sub FixCallerAddresses {\n" + " my $stack = shift;\n" + " if ($main::use_symbolized_profile) {\n" + " return $stack;\n" + " } else {\n" + " $stack =~ /(\\s)/;\n" + " my $delimiter = $1;\n" + " my @addrs = split(' ', $stack);\n" + " my @fixedaddrs;\n" + " $#fixedaddrs = $#addrs;\n" + " if ($#addrs >= 0) {\n" + " $fixedaddrs[0] = $addrs[0];\n" + " }\n" + " for (my $i = 1; $i <= $#addrs; $i++) {\n" + " $fixedaddrs[$i] = AddressSub($addrs[$i], \"0x1\");\n" + " }\n" + " return join $delimiter, @fixedaddrs;\n" + " }\n" + "}\n" + "\n" + "# CPU profile reader\n" + "sub ReadCPUProfile {\n" + " my $prog = shift;\n" + " my $fname = shift; # just used for logging\n" + " local *PROFILE = shift;\n" + " my $version;\n" + " my $period;\n" + " my $i;\n" + " my $profile = {};\n" + " my $pcs = {};\n" + "\n" + " # Parse string into array of slots.\n" + " my $slots = CpuProfileStream->new(*PROFILE, $fname);\n" + "\n" + " # Read header. The current header version is a 5-element structure\n" + " # containing:\n" + " # 0: header count (always 0)\n" + " # 1: header \"words\" (after this one: 3)\n" + " # 2: format version (0)\n" + " # 3: sampling period (usec)\n" + " # 4: unused padding (always 0)\n" + " if ($slots->get(0) != 0 ) {\n" + " error(\"$fname: not a profile file, or old format profile file\\n\");\n" + " }\n" + " $i = 2 + $slots->get(1);\n" + " $version = $slots->get(2);\n" + " $period = $slots->get(3);\n" + " # Do some sanity checking on these header values.\n" + " if ($version > (2**32) || $period > (2**32) || $i > (2**32) || $i < 5) {\n" + " error(\"$fname: not a profile file, or corrupted profile file\\n\");\n" + " }\n" + "\n" + " # Parse profile\n" + " while ($slots->get($i) != -1) {\n" + " my $n = $slots->get($i++);\n" + " my $d = $slots->get($i++);\n" + " if ($d > (2**16)) { # TODO(csilvers): what's a reasonable max-stack-depth?\n" + " my $addr = sprintf(\"0%o\", $i * ($address_length == 8 ? 4 : 8));\n" + " print STDERR \"At index $i (address $addr):\\n\";\n" + " error(\"$fname: stack trace depth >= 2**32\\n\");\n" + " }\n" + " if ($slots->get($i) == 0) {\n" + " # End of profile data marker\n" + " $i += $d;\n" + " last;\n" + " }\n" + "\n" + " # Make key out of the stack entries\n" + " my @k = ();\n" + " for (my $j = 0; $j < $d; $j++) {\n" + " my $pc = $slots->get($i+$j);\n" + " # Subtract one from caller pc so we map back to call instr.\n" + " # However, don't do this if we're reading a symbolized profile\n" + " # file, in which case the subtract-one was done when the file\n" + " # was written.\n" + " if ($j > 0 && !$main::use_symbolized_profile) {\n" + " $pc--;\n" + " }\n" + " $pc = sprintf(\"%0*x\", $address_length, $pc);\n" + " $pcs->{$pc} = 1;\n" + " push @k, $pc;\n" + " }\n" + "\n" + " AddEntry($profile, (join \"\\n\", @k), $n);\n" + " $i += $d;\n" + " }\n" + "\n" + " # Parse map\n" + " my $map = '';\n" + " seek(PROFILE, $i * ($address_length / 2), 0);\n" + " read(PROFILE, $map, (stat PROFILE)[7]);\n" + "\n" + " my $r = {};\n" + " $r->{version} = $version;\n" + " $r->{period} = $period;\n" + " $r->{profile} = $profile;\n" + " $r->{libs} = ParseLibraries($prog, $map, $pcs);\n" + " $r->{pcs} = $pcs;\n" + "\n" + " return $r;\n" + "}\n" + "\n" + "sub ReadHeapProfile {\n" + " my $prog = shift;\n" + " local *PROFILE = shift;\n" + " my $header = shift;\n" + "\n" + " my $index = 1;\n" + " if ($main::opt_inuse_space) {\n" + " $index = 1;\n" + " } elsif ($main::opt_inuse_objects) {\n" + " $index = 0;\n" + " } elsif ($main::opt_alloc_space) {\n" + " $index = 3;\n" + " } elsif ($main::opt_alloc_objects) {\n" + " $index = 2;\n" + " }\n" + "\n" + " # Find the type of this profile. The header line looks like:\n" + " # heap profile: 1246: 8800744 [ 1246: 8800744] @ /266053\n" + " # There are two pairs , the first inuse objects/space, and the\n" + " # second allocated objects/space. This is followed optionally by a profile\n" + " # type, and if that is present, optionally by a sampling frequency.\n" + " # For remote heap profiles (v1):\n" + " # The interpretation of the sampling frequency is that the profiler, for\n" + " # each sample, calculates a uniformly distributed random integer less than\n" + " # the given value, and records the next sample after that many bytes have\n" + " # been allocated. Therefore, the expected sample interval is half of the\n" + " # given frequency. By default, if not specified, the expected sample\n" + " # interval is 128KB. Only remote-heap-page profiles are adjusted for\n" + " # sample size.\n" + " # For remote heap profiles (v2):\n" + " # The sampling frequency is the rate of a Poisson process. This means that\n" + " # the probability of sampling an allocation of size X with sampling rate Y\n" + " # is 1 - exp(-X/Y)\n" + " # For version 2, a typical header line might look like this:\n" + " # heap profile: 1922: 127792360 [ 1922: 127792360] @ _v2/524288\n" + " # the trailing number (524288) is the sampling rate. (Version 1 showed\n" + " # double the 'rate' here)\n" + " my $sampling_algorithm = 0;\n" + " my $sample_adjustment = 0;\n" + " chomp($header);\n" + " my $type = \"unknown\";\n" + " if ($header =~ m\"^heap " + "profile:\\s*(\\d+):\\s+(\\d+)\\s+\\[\\s*(\\d+):\\s+(\\d+)\\](\\s*@\\s*([^/]*)(/" + "(\\d+))?)?\") {\n" + " if (defined($6) && ($6 ne '')) {\n" + " $type = $6;\n" + " my $sample_period = $8;\n" + " # $type is \"heapprofile\" for profiles generated by the\n" + " # heap-profiler, and either \"heap\" or \"heap_v2\" for profiles\n" + " # generated by sampling directly within tcmalloc. It can also\n" + " # be \"growth\" for heap-growth profiles. The first is typically\n" + " # found for profiles generated locally, and the others for\n" + " # remote profiles.\n" + " if (($type eq \"heapprofile\") || ($type !~ /heap/) ) {\n" + " # No need to adjust for the sampling rate with heap-profiler-derived data\n" + " $sampling_algorithm = 0;\n" + " } elsif ($type =~ /_v2/) {\n" + " $sampling_algorithm = 2; # version 2 sampling\n" + " if (defined($sample_period) && ($sample_period ne '')) {\n" + " $sample_adjustment = int($sample_period);\n" + " }\n" + " } else {\n" + " $sampling_algorithm = 1; # version 1 sampling\n" + " if (defined($sample_period) && ($sample_period ne '')) {\n" + " $sample_adjustment = int($sample_period)/2;\n" + " }\n" + " }\n" + " } else {\n" + " # We detect whether or not this is a remote-heap profile by checking\n" + " # that the total-allocated stats ($n2,$s2) are exactly the\n" + " # same as the in-use stats ($n1,$s1). It is remotely conceivable\n" + " # that a non-remote-heap profile may pass this check, but it is hard\n" + " # to imagine how that could happen.\n" + " # In this case it's so old it's guaranteed to be remote-heap version 1.\n" + " my ($n1, $s1, $n2, $s2) = ($1, $2, $3, $4);\n" + " if (($n1 == $n2) && ($s1 == $s2)) {\n" + " # This is likely to be a remote-heap based sample profile\n" + " $sampling_algorithm = 1;\n" + " }\n" + " }\n" + " }\n" + "\n" + " if ($sampling_algorithm > 0) {\n" + " # For remote-heap generated profiles, adjust the counts and sizes to\n" + " # account for the sample rate (we sample once every 128KB by default).\n" + " if ($sample_adjustment == 0) {\n" + " # Turn on profile adjustment.\n" + " $sample_adjustment = 128*1024;\n" + " print STDERR \"Adjusting heap profiles for 1-in-128KB sampling rate\\n\";\n" + " } else {\n" + " printf STDERR (\"Adjusting heap profiles for 1-in-%d sampling rate\\n\",\n" + " $sample_adjustment);\n" + " }\n" + " if ($sampling_algorithm > 1) {\n" + " # We don't bother printing anything for the original version (version 1)\n" + " printf STDERR \"Heap version $sampling_algorithm\\n\";\n" + " }\n" + " }\n" + "\n" + " my $profile = {};\n" + " my $pcs = {};\n" + " my $map = \"\";\n" + "\n" + " while () {\n" + " s/\\r//g; # turn windows-looking lines into unix-looking lines\n" + " if (/^MAPPED_LIBRARIES:/) {\n" + " # Read the /proc/self/maps data\n" + " while () {\n" + " s/\\r//g; # turn windows-looking lines into unix-looking lines\n" + " $map .= $_;\n" + " }\n" + " last;\n" + " }\n" + "\n" + " if (/^--- Memory map:/) {\n" + " # Read /proc/self/maps data as formatted by DumpAddressMap()\n" + " my $buildvar = \"\";\n" + " while () {\n" + " s/\\r//g; # turn windows-looking lines into unix-looking lines\n" + " # Parse \"build=\" specification if supplied\n" + " if (m/^\\s*build=(.*)\\n/) {\n" + " $buildvar = $1;\n" + " }\n" + "\n" + " # Expand \"$build\" variable if available\n" + " $_ =~ s/\\$build\\b/$buildvar/g;\n" + "\n" + " $map .= $_;\n" + " }\n" + " last;\n" + " }\n" + "\n" + " # Read entry of the form:\n" + " # : [: ] @ a1 a2 a3 ... an\n" + " s/^\\s*//;\n" + " s/\\s*$//;\n" + " if (m/^\\s*(\\d+):\\s+(\\d+)\\s+\\[\\s*(\\d+):\\s+(\\d+)\\]\\s+@\\s+(.*)$/) {\n" + " my $stack = $5;\n" + " my ($n1, $s1, $n2, $s2) = ($1, $2, $3, $4);\n" + "\n" + " if ($sample_adjustment) {\n" + " if ($sampling_algorithm == 2) {\n" + " # Remote-heap version 2\n" + " # The sampling frequency is the rate of a Poisson process.\n" + " # This means that the probability of sampling an allocation of\n" + " # size X with sampling rate Y is 1 - exp(-X/Y)\n" + " if ($n1 != 0) {\n" + " my $ratio = (($s1*1.0)/$n1)/($sample_adjustment);\n" + " my $scale_factor = 1/(1 - exp(-$ratio));\n" + " $n1 *= $scale_factor;\n" + " $s1 *= $scale_factor;\n" + " }\n" + " if ($n2 != 0) {\n" + " my $ratio = (($s2*1.0)/$n2)/($sample_adjustment);\n" + " my $scale_factor = 1/(1 - exp(-$ratio));\n" + " $n2 *= $scale_factor;\n" + " $s2 *= $scale_factor;\n" + " }\n" + " } else {\n" + " # Remote-heap version 1\n" + " my $ratio;\n" + " $ratio = (($s1*1.0)/$n1)/($sample_adjustment);\n" + " if ($ratio < 1) {\n" + " $n1 /= $ratio;\n" + " $s1 /= $ratio;\n" + " }\n" + " $ratio = (($s2*1.0)/$n2)/($sample_adjustment);\n" + " if ($ratio < 1) {\n" + " $n2 /= $ratio;\n" + " $s2 /= $ratio;\n" + " }\n" + " }\n" + " }\n" + "\n" + " my @counts = ($n1, $s1, $n2, $s2);\n" + " $stack = FixCallerAddresses($stack);\n" + " push @stackTraces, \"$n1 $s1 $n2 $s2 $stack\";\n" + " AddEntries($profile, $pcs, $stack, $counts[$index]);\n" + " }\n" + " }\n" + "\n" + " my $r = {};\n" + " $r->{version} = \"heap\";\n" + " $r->{period} = 1;\n" + " $r->{profile} = $profile;\n" + " $r->{libs} = ParseLibraries($prog, $map, $pcs);\n" + " $r->{pcs} = $pcs;\n" + " return $r;\n" + "}\n" + "\n" + "sub ReadSynchProfile {\n" + " my $prog = shift;\n" + " local *PROFILE = shift;\n" + " my $header = shift;\n" + "\n" + " my $map = '';\n" + " my $profile = {};\n" + " my $pcs = {};\n" + " my $sampling_period = 1;\n" + " my $cyclespernanosec = 2.8; # Default assumption for old binaries\n" + " my $seen_clockrate = 0;\n" + " my $line;\n" + "\n" + " my $index = 0;\n" + " if ($main::opt_total_delay) {\n" + " $index = 0;\n" + " } elsif ($main::opt_contentions) {\n" + " $index = 1;\n" + " } elsif ($main::opt_mean_delay) {\n" + " $index = 2;\n" + " }\n" + "\n" + " while ( $line = ) {\n" + " $line =~ s/\\r//g; # turn windows-looking lines into unix-looking lines\n" + " # Support negative count for IOBuf Profiler\n" + " if ( $line =~ /^\\s*(\\d+)\\s+(-?\\d+) \\@\\s*(.*?)\\s*$/ ) {\n" + " my ($cycles, $count, $stack) = ($1, $2, $3);\n" + "\n" + " # Convert cycles to nanoseconds\n" + " $cycles /= $cyclespernanosec;\n" + "\n" + " # Adjust for sampling done by application\n" + " $cycles *= $sampling_period;\n" + " $count *= $sampling_period;\n" + "\n" + " my @values = ($cycles, $count, $cycles / $count);\n" + " AddEntries($profile, $pcs, FixCallerAddresses($stack), $values[$index]);\n" + "\n" + " } elsif ( $line =~ /^(slow release).*thread \\d+ \\@\\s*(.*?)\\s*$/ ||\n" + " $line =~ /^\\s*(\\d+) \\@\\s*(.*?)\\s*$/ ) {\n" + " my ($cycles, $stack) = ($1, $2);\n" + " if ($cycles !~ /^\\d+$/) {\n" + " next;\n" + " }\n" + "\n" + " # Convert cycles to nanoseconds\n" + " $cycles /= $cyclespernanosec;\n" + "\n" + " # Adjust for sampling done by application\n" + " $cycles *= $sampling_period;\n" + "\n" + " AddEntries($profile, $pcs, FixCallerAddresses($stack), $cycles);\n" + "\n" + " } elsif ( $line =~ m/^([a-z][^=]*)=(.*)$/ ) {\n" + " my ($variable, $value) = ($1,$2);\n" + " for ($variable, $value) {\n" + " s/^\\s+//;\n" + " s/\\s+$//;\n" + " }\n" + " if ($variable eq \"cycles/second\") {\n" + " $cyclespernanosec = $value / 1e9;\n" + " $seen_clockrate = 1;\n" + " } elsif ($variable eq \"sampling period\") {\n" + " $sampling_period = $value;\n" + " } elsif ($variable eq \"ms since reset\") {\n" + " # Currently nothing is done with this value in pprof\n" + " # So we just silently ignore it for now\n" + " } elsif ($variable eq \"discarded samples\") {\n" + " # Currently nothing is done with this value in pprof\n" + " # So we just silently ignore it for now\n" + " } else {\n" + " printf STDERR (\"Ignoring unnknown variable in /contention output: \" .\n" + " \"'%s' = '%s'\\n\",$variable,$value);\n" + " }\n" + " } else {\n" + " # Memory map entry\n" + " $map .= $line;\n" + " }\n" + " }\n" + "\n" + " if (!$seen_clockrate) {\n" + " printf STDERR (\"No cycles/second entry in profile; Guessing %.1f GHz\\n\",\n" + " $cyclespernanosec);\n" + " }\n" + "\n" + " my $r = {};\n" + " $r->{version} = 0;\n" + " $r->{period} = $sampling_period;\n" + " $r->{profile} = $profile;\n" + " $r->{libs} = ParseLibraries($prog, $map, $pcs);\n" + " $r->{pcs} = $pcs;\n" + " return $r;\n" + "}\n" + "\n" + "# Given a hex value in the form \"0x1abcd\" or \"1abcd\", return either\n" + "# \"0001abcd\" or \"000000000001abcd\", depending on the current (global)\n" + "# address length.\n" + "sub HexExtend {\n" + " my $addr = shift;\n" + "\n" + " $addr =~ s/^(0x)?0*//;\n" + " my $zeros_needed = $address_length - length($addr);\n" + " if ($zeros_needed < 0) {\n" + " printf STDERR \"Warning: address $addr is longer than address length " + "$address_length\\n\";\n" + " return $addr;\n" + " }\n" + " return (\"0\" x $zeros_needed) . $addr;\n" + "}\n" + "\n" + "##### Symbol extraction #####\n" + "\n" + "# Aggressively search the lib_prefix values for the given library\n" + "# If all else fails, just return the name of the library unmodified.\n" + "# If the lib_prefix is \"/my/path,/other/path\" and $file is \"/lib/dir/mylib.so\"\n" + "# it will search the following locations in this order, until it finds a file:\n" + "# /my/path/lib/dir/mylib.so\n" + "# /other/path/lib/dir/mylib.so\n" + "# /my/path/dir/mylib.so\n" + "# /other/path/dir/mylib.so\n" + "# /my/path/mylib.so\n" + "# /other/path/mylib.so\n" + "# /lib/dir/mylib.so (returned as last resort)\n" + "sub FindLibrary {\n" + " my $file = shift;\n" + " my $suffix = $file;\n" + "\n" + " # Search for the library as described above\n" + " do {\n" + " foreach my $prefix (@prefix_list) {\n" + " my $fullpath = $prefix . $suffix;\n" + " if (-e $fullpath) {\n" + " return $fullpath;\n" + " }\n" + " }\n" + " } while ($suffix =~ s|^/[^/]+/|/|);\n" + " return $file;\n" + "}\n" + "\n" + "# Return path to library with debugging symbols.\n" + "# For libc libraries, the copy in /usr/lib/debug contains debugging symbols\n" + "sub DebuggingLibrary {\n" + " my $file = shift;\n" + " if ($file =~ m|^/| && -f \"/usr/lib/debug$file\") {\n" + " return \"/usr/lib/debug$file\";\n" + " }\n" + " if ($file =~ m|^/| && -f \"/usr/lib/debug$file.debug\") {\n" + " return \"/usr/lib/debug$file.debug\";\n" + " }\n" + " return undef;\n" + "}\n" + "\n" + "# Parse text section header of a library using objdump\n" + "sub ParseTextSectionHeaderFromObjdump {\n" + " my $lib = shift;\n" + "\n" + " my $size = undef;\n" + " my $vma;\n" + " my $file_offset;\n" + " # Get objdump output from the library file to figure out how to\n" + " # map between mapped addresses and addresses in the library.\n" + " my $cmd = ShellEscape($obj_tool_map{\"objdump\"}, \"-h\", $lib);\n" + " open(OBJDUMP, \"$cmd |\") || error(\"$cmd: $!\\n\");\n" + " while () {\n" + " s/\\r//g; # turn windows-looking lines into unix-looking lines\n" + " # Idx Name Size VMA LMA File off Algn\n" + " # 10 .text 00104b2c 420156f0 420156f0 000156f0 2**4\n" + " # For 64-bit objects, VMA and LMA will be 16 hex digits, size and file\n" + " # offset may still be 8. But AddressSub below will still handle that.\n" + " my @x = split;\n" + " if (($#x >= 6) && ($x[1] eq '.text')) {\n" + " $size = $x[2];\n" + " $vma = $x[3];\n" + " $file_offset = $x[5];\n" + " last;\n" + " }\n" + " }\n" + " close(OBJDUMP);\n" + "\n" + " if (!defined($size)) {\n" + " return undef;\n" + " }\n" + "\n" + " my $r = {};\n" + " $r->{size} = $size;\n" + " $r->{vma} = $vma;\n" + " $r->{file_offset} = $file_offset;\n" + "\n" + " return $r;\n" + "}\n" + "\n" + "# Parse text section header of a library using otool (on OS X)\n" + "sub ParseTextSectionHeaderFromOtool {\n" + " my $lib = shift;\n" + "\n" + " my $size = undef;\n" + " my $vma = undef;\n" + " my $file_offset = undef;\n" + " # Get otool output from the library file to figure out how to\n" + " # map between mapped addresses and addresses in the library.\n" + " my $command = ShellEscape($obj_tool_map{\"otool\"}, \"-l\", $lib);\n" + " open(OTOOL, \"$command |\") || error(\"$command: $!\\n\");\n" + " my $cmd = \"\";\n" + " my $sectname = \"\";\n" + " my $segname = \"\";\n" + " foreach my $line () {\n" + " $line =~ s/\\r//g; # turn windows-looking lines into unix-looking lines\n" + " # Load command <#>\n" + " # cmd LC_SEGMENT\n" + " # [...]\n" + " # Section\n" + " # sectname __text\n" + " # segname __TEXT\n" + " # addr 0x000009f8\n" + " # size 0x00018b9e\n" + " # offset 2552\n" + " # align 2^2 (4)\n" + " # We will need to strip off the leading 0x from the hex addresses,\n" + " # and convert the offset into hex.\n" + " if ($line =~ /Load command/) {\n" + " $cmd = \"\";\n" + " $sectname = \"\";\n" + " $segname = \"\";\n" + " } elsif ($line =~ /Section/) {\n" + " $sectname = \"\";\n" + " $segname = \"\";\n" + " } elsif ($line =~ /cmd (\\w+)/) {\n" + " $cmd = $1;\n" + " } elsif ($line =~ /sectname (\\w+)/) {\n" + " $sectname = $1;\n" + " } elsif ($line =~ /segname (\\w+)/) {\n" + " $segname = $1;\n" + " } elsif (!(($cmd eq \"LC_SEGMENT\" || $cmd eq \"LC_SEGMENT_64\") &&\n" + " $sectname eq \"__text\" &&\n" + " $segname eq \"__TEXT\")) {\n" + " next;\n" + " } elsif ($line =~ /\\baddr 0x([0-9a-fA-F]+)/) {\n" + " $vma = $1;\n" + " } elsif ($line =~ /\\bsize 0x([0-9a-fA-F]+)/) {\n" + " $size = $1;\n" + " } elsif ($line =~ /\\boffset ([0-9]+)/) {\n" + " $file_offset = sprintf(\"%016x\", $1);\n" + " }\n" + " if (defined($vma) && defined($size) && defined($file_offset)) {\n" + " last;\n" + " }\n" + " }\n" + " close(OTOOL);\n" + "\n" + " if (!defined($vma) || !defined($size) || !defined($file_offset)) {\n" + " return undef;\n" + " }\n" + "\n" + " my $r = {};\n" + " $r->{size} = $size;\n" + " $r->{vma} = $vma;\n" + " $r->{file_offset} = $file_offset;\n" + "\n" + " return $r;\n" + "}\n" + "\n" + "sub ParseTextSectionHeader {\n" + " # obj_tool_map(\"otool\") is only defined if we're in a Mach-O environment\n" + " if (defined($obj_tool_map{\"otool\"})) {\n" + " my $r = ParseTextSectionHeaderFromOtool(@_);\n" + " if (defined($r)){\n" + " return $r;\n" + " }\n" + " }\n" + " # If otool doesn't work, or we don't have it, fall back to objdump\n" + " return ParseTextSectionHeaderFromObjdump(@_);\n" + "}\n" + "\n" + "# Split /proc/pid/maps dump into a list of libraries\n" + "sub ParseLibraries {\n" + " return if $main::use_symbol_page; # We don't need libraries info.\n" + " my $prog = Cwd::abs_path(shift);\n" + " my $map = shift;\n" + " my $pcs = shift;\n" + "\n" + " my $result = [];\n" + " my $h = \"[a-f0-9]+\";\n" + " my $zero_offset = HexExtend(\"0\");\n" + "\n" + " my $buildvar = \"\";\n" + " foreach my $l (split(\"\\n\", $map)) {\n" + " if ($l =~ m/^\\s*build=(.*)$/) {\n" + " $buildvar = $1;\n" + " }\n" + "\n" + " my $start;\n" + " my $finish;\n" + " my $offset;\n" + " my $lib;\n" + " if ($l =~ " + "/^($h)-($h)\\s+..x.\\s+($h)\\s+\\S+:\\S+\\s+\\d+\\s+(.+\\.(so|dll|dylib|bundle|node)((" + "\\.\\d+)+\\w*(\\.\\d+){0,3})?)$/i) {\n" + " # Full line from /proc/self/maps. Example:\n" + " # 40000000-40015000 r-xp 00000000 03:01 12845071 /lib/ld-2.3.2.so\n" + " $start = HexExtend($1);\n" + " $finish = HexExtend($2);\n" + " $offset = HexExtend($3);\n" + " $lib = $4;\n" + " $lib =~ s|\\\\|/|g; # turn windows-style paths into unix-style paths\n" + " } elsif ($l =~ /^\\s*($h)-($h):\\s*(\\S+\\.so(\\.\\d+)*)/) {\n" + " # Cooked line from DumpAddressMap. Example:\n" + " # 40000000-40015000: /lib/ld-2.3.2.so\n" + " $start = HexExtend($1);\n" + " $finish = HexExtend($2);\n" + " $offset = $zero_offset;\n" + " $lib = $3;\n" + " } elsif (($l =~ /^($h)-($h)\\s+..x.\\s+($h)\\s+\\S+:\\S+\\s+\\d+\\s+(\\S+)$/i) && " + "($4 eq $prog)) {\n" + " # PIEs and address space randomization do not play well with our\n" + " # default assumption that main executable is at lowest\n" + " # addresses. So we're detecting main executable in\n" + " # /proc/self/maps as well.\n" + " $start = HexExtend($1);\n" + " $finish = HexExtend($2);\n" + " $offset = HexExtend($3);\n" + " $lib = $4;\n" + " $lib =~ s|\\\\|/|g; # turn windows-style paths into unix-style paths\n" + " } else {\n" + " next;\n" + " }\n" + "\n" + " # Expand \"$build\" variable if available\n" + " $lib =~ s/\\$build\\b/$buildvar/g;\n" + "\n" + " $lib = FindLibrary($lib);\n" + "\n" + " # Check for pre-relocated libraries, which use pre-relocated symbol tables\n" + " # and thus require adjusting the offset that we'll use to translate\n" + " # VM addresses into symbol table addresses.\n" + " # Only do this if we're not going to fetch the symbol table from a\n" + " # debugging copy of the library.\n" + " if (!DebuggingLibrary($lib)) {\n" + " my $text = ParseTextSectionHeader($lib);\n" + " if (defined($text)) {\n" + " my $vma_offset = AddressSub($text->{vma}, $text->{file_offset});\n" + " $offset = AddressAdd($offset, $vma_offset);\n" + " }\n" + " }\n" + "\n" + " push(@{$result}, [$lib, $start, $finish, $offset]);\n" + " }\n" + "\n" + " # Append special entry for additional library (not relocated)\n" + " if ($main::opt_lib ne \"\") {\n" + " my $text = ParseTextSectionHeader($main::opt_lib);\n" + " if (defined($text)) {\n" + " my $start = $text->{vma};\n" + " my $finish = AddressAdd($start, $text->{size});\n" + "\n" + " push(@{$result}, [$main::opt_lib, $start, $finish, $start]);\n" + " }\n" + " }\n" + "\n" + " # Append special entry for the main program. This covers\n" + " # 0..max_pc_value_seen, so that we assume pc values not found in one\n" + " # of the library ranges will be treated as coming from the main\n" + " # program binary.\n" + " my $min_pc = HexExtend(\"0\");\n" + " my $max_pc = $min_pc; # find the maximal PC value in any sample\n" + " foreach my $pc (keys(%{$pcs})) {\n" + " if (HexExtend($pc) gt $max_pc) { $max_pc = HexExtend($pc); }\n" + " }\n" + " push(@{$result}, [$prog, $min_pc, $max_pc, $zero_offset]);\n" + "\n" + " return $result;\n" + "}\n" + "\n" + "# Add two hex addresses of length $address_length.\n" + "# Run pprof --test for unit test if this is changed.\n" + "sub AddressAdd {\n" + " my $addr1 = shift;\n" + " my $addr2 = shift;\n" + " my $sum;\n" + "\n" + " if ($address_length == 8) {\n" + " # Perl doesn't cope with wraparound arithmetic, so do it explicitly:\n" + " $sum = (hex($addr1)+hex($addr2)) % (0x10000000 * 16);\n" + " return sprintf(\"%08x\", $sum);\n" + "\n" + " } else {\n" + " # Do the addition in 7-nibble chunks to trivialize carry handling.\n" + "\n" + " if ($main::opt_debug and $main::opt_test) {\n" + " print STDERR \"AddressAdd $addr1 + $addr2 = \";\n" + " }\n" + "\n" + " my $a1 = substr($addr1,-7);\n" + " $addr1 = substr($addr1,0,-7);\n" + " my $a2 = substr($addr2,-7);\n" + " $addr2 = substr($addr2,0,-7);\n" + " $sum = hex($a1) + hex($a2);\n" + " my $c = 0;\n" + " if ($sum > 0xfffffff) {\n" + " $c = 1;\n" + " $sum -= 0x10000000;\n" + " }\n" + " my $r = sprintf(\"%07x\", $sum);\n" + "\n" + " $a1 = substr($addr1,-7);\n" + " $addr1 = substr($addr1,0,-7);\n" + " $a2 = substr($addr2,-7);\n" + " $addr2 = substr($addr2,0,-7);\n" + " $sum = hex($a1) + hex($a2) + $c;\n" + " $c = 0;\n" + " if ($sum > 0xfffffff) {\n" + " $c = 1;\n" + " $sum -= 0x10000000;\n" + " }\n" + " $r = sprintf(\"%07x\", $sum) . $r;\n" + "\n" + " $sum = hex($addr1) + hex($addr2) + $c;\n" + " if ($sum > 0xff) { $sum -= 0x100; }\n" + " $r = sprintf(\"%02x\", $sum) . $r;\n" + "\n" + " if ($main::opt_debug and $main::opt_test) { print STDERR \"$r\\n\"; }\n" + "\n" + " return $r;\n" + " }\n" + "}\n" + "\n" + "\n" + "# Subtract two hex addresses of length $address_length.\n" + "# Run pprof --test for unit test if this is changed.\n" + "sub AddressSub {\n" + " my $addr1 = shift;\n" + " my $addr2 = shift;\n" + " my $diff;\n" + "\n" + " if ($address_length == 8) {\n" + " # Perl doesn't cope with wraparound arithmetic, so do it explicitly:\n" + " $diff = (hex($addr1)-hex($addr2)) % (0x10000000 * 16);\n" + " return sprintf(\"%08x\", $diff);\n" + "\n" + " } else {\n" + " # Do the addition in 7-nibble chunks to trivialize borrow handling.\n" + " # if ($main::opt_debug) { print STDERR \"AddressSub $addr1 - $addr2 = \"; }\n" + "\n" + " my $a1 = hex(substr($addr1,-7));\n" + " $addr1 = substr($addr1,0,-7);\n" + " my $a2 = hex(substr($addr2,-7));\n" + " $addr2 = substr($addr2,0,-7);\n" + " my $b = 0;\n" + " if ($a2 > $a1) {\n" + " $b = 1;\n" + " $a1 += 0x10000000;\n" + " }\n" + " $diff = $a1 - $a2;\n" + " my $r = sprintf(\"%07x\", $diff);\n" + "\n" + " $a1 = hex(substr($addr1,-7));\n" + " $addr1 = substr($addr1,0,-7);\n" + " $a2 = hex(substr($addr2,-7)) + $b;\n" + " $addr2 = substr($addr2,0,-7);\n" + " $b = 0;\n" + " if ($a2 > $a1) {\n" + " $b = 1;\n" + " $a1 += 0x10000000;\n" + " }\n" + " $diff = $a1 - $a2;\n" + " $r = sprintf(\"%07x\", $diff) . $r;\n" + "\n" + " $a1 = hex($addr1);\n" + " $a2 = hex($addr2) + $b;\n" + " if ($a2 > $a1) { $a1 += 0x100; }\n" + " $diff = $a1 - $a2;\n" + " $r = sprintf(\"%02x\", $diff) . $r;\n" + "\n" + " # if ($main::opt_debug) { print STDERR \"$r\\n\"; }\n" + "\n" + " return $r;\n" + " }\n" + "}\n" + "\n" + "# Increment a hex addresses of length $address_length.\n" + "# Run pprof --test for unit test if this is changed.\n" + "sub AddressInc {\n" + " my $addr = shift;\n" + " my $sum;\n" + "\n" + " if ($address_length == 8) {\n" + " # Perl doesn't cope with wraparound arithmetic, so do it explicitly:\n" + " $sum = (hex($addr)+1) % (0x10000000 * 16);\n" + " return sprintf(\"%08x\", $sum);\n" + "\n" + " } else {\n" + " # Do the addition in 7-nibble chunks to trivialize carry handling.\n" + " # We are always doing this to step through the addresses in a function,\n" + " # and will almost never overflow the first chunk, so we check for this\n" + " # case and exit early.\n" + "\n" + " # if ($main::opt_debug) { print STDERR \"AddressInc $addr1 = \"; }\n" + "\n" + " my $a1 = substr($addr,-7);\n" + " $addr = substr($addr,0,-7);\n" + " $sum = hex($a1) + 1;\n" + " my $r = sprintf(\"%07x\", $sum);\n" + " if ($sum <= 0xfffffff) {\n" + " $r = $addr . $r;\n" + " # if ($main::opt_debug) { print STDERR \"$r\\n\"; }\n" + " return HexExtend($r);\n" + " } else {\n" + " $r = \"0000000\";\n" + " }\n" + "\n" + " $a1 = substr($addr,-7);\n" + " $addr = substr($addr,0,-7);\n" + " $sum = hex($a1) + 1;\n" + " $r = sprintf(\"%07x\", $sum) . $r;\n" + " if ($sum <= 0xfffffff) {\n" + " $r = $addr . $r;\n" + " # if ($main::opt_debug) { print STDERR \"$r\\n\"; }\n" + " return HexExtend($r);\n" + " } else {\n" + " $r = \"00000000000000\";\n" + " }\n" + "\n" + " $sum = hex($addr) + 1;\n" + " if ($sum > 0xff) { $sum -= 0x100; }\n" + " $r = sprintf(\"%02x\", $sum) . $r;\n" + "\n" + " # if ($main::opt_debug) { print STDERR \"$r\\n\"; }\n" + " return $r;\n" + " }\n" + "}\n" + "\n" + "# Extract symbols for all PC values found in profile\n" + "sub ExtractSymbols {\n" + " my $libs = shift;\n" + " my $pcset = shift;\n" + "\n" + " my $symbols = {};\n" + "\n" + " # Map each PC value to the containing library. To make this faster,\n" + " # we sort libraries by their starting pc value (highest first), and\n" + " # advance through the libraries as we advance the pc. Sometimes the\n" + " # addresses of libraries may overlap with the addresses of the main\n" + " # binary, so to make sure the libraries 'win', we iterate over the\n" + " # libraries in reverse order (which assumes the binary doesn't start\n" + " # in the middle of a library, which seems a fair assumption).\n" + " my @pcs = (sort { $a cmp $b } keys(%{$pcset})); # pcset is 0-extended strings\n" + " foreach my $lib (sort {$b->[1] cmp $a->[1]} @{$libs}) {\n" + " my $libname = $lib->[0];\n" + " my $start = $lib->[1];\n" + " my $finish = $lib->[2];\n" + " my $offset = $lib->[3];\n" + "\n" + " # Get list of pcs that belong in this library.\n" + " my $contained = [];\n" + " my ($start_pc_index, $finish_pc_index);\n" + " # Find smallest finish_pc_index such that $finish < $pc[$finish_pc_index].\n" + " for ($finish_pc_index = $#pcs + 1; $finish_pc_index > 0;\n" + " $finish_pc_index--) {\n" + " last if $pcs[$finish_pc_index - 1] le $finish;\n" + " }\n" + " # Find smallest start_pc_index such that $start <= $pc[$start_pc_index].\n" + " for ($start_pc_index = $finish_pc_index; $start_pc_index > 0;\n" + " $start_pc_index--) {\n" + " last if $pcs[$start_pc_index - 1] lt $start;\n" + " }\n" + " # This keeps PC values higher than $pc[$finish_pc_index] in @pcs,\n" + " # in case there are overlaps in libraries and the main binary.\n" + " @{$contained} = splice(@pcs, $start_pc_index,\n" + " $finish_pc_index - $start_pc_index);\n" + " # Map to symbols\n" + " MapToSymbols($libname, AddressSub($start, $offset), $contained, $symbols);\n" + " }\n" + "\n" + " return $symbols;\n" + "}\n" + "\n" + "# Map list of PC values to symbols for a given image\n" + "sub MapToSymbols {\n" + " my $image = shift;\n" + " my $offset = shift;\n" + " my $pclist = shift;\n" + " my $symbols = shift;\n" + "\n" + " my $debug = 0;\n" + "\n" + " # For libc (and other) libraries, the copy in /usr/lib/debug contains debugging " + "symbols\n" + " my $debugging = DebuggingLibrary($image);\n" + " if ($debugging) {\n" + " $image = $debugging;\n" + " }\n" + "\n" + " # Ignore empty binaries\n" + " if ($#{$pclist} < 0) { return; }\n" + "\n" + " # Figure out the addr2line command to use\n" + " my $addr2line = $obj_tool_map{\"addr2line\"};\n" + " my $cmd = ShellEscape($addr2line, \"-f\", \"-C\", \"-e\", $image);\n" + " if (exists $obj_tool_map{\"addr2line_pdb\"}) {\n" + " $addr2line = $obj_tool_map{\"addr2line_pdb\"};\n" + " $cmd = ShellEscape($addr2line, \"--demangle\", \"-f\", \"-C\", \"-e\", $image);\n" + " }\n" + "\n" + " # If \"addr2line\" isn't installed on the system at all, just use\n" + " # nm to get what info we can (function names, but not line numbers).\n" + " if (system(ShellEscape($addr2line, \"--help\") . \" >$dev_null 2>&1\") != 0) {\n" + " MapSymbolsWithNM($image, $offset, $pclist, $symbols);\n" + " return;\n" + " }\n" + "\n" + " # \"addr2line -i\" can produce a variable number of lines per input\n" + " # address, with no separator that allows us to tell when data for\n" + " # the next address starts. So we find the address for a special\n" + " # symbol (_fini) and interleave this address between all real\n" + " # addresses passed to addr2line. The name of this special symbol\n" + " # can then be used as a separator.\n" + " $sep_address = undef; # May be filled in by MapSymbolsWithNM()\n" + " my $nm_symbols = {};\n" + " MapSymbolsWithNM($image, $offset, $pclist, $nm_symbols);\n" + " if (defined($sep_address)) {\n" + " # Only add \" -i\" to addr2line if the binary supports it.\n" + " # addr2line --help returns 0, but not if it sees an unknown flag first.\n" + " if (system(\"$cmd -i --help >$dev_null 2>&1\") == 0) {\n" + " $cmd .= \" -i\";\n" + " } else {\n" + " $sep_address = undef; # no need for sep_address if we don't support -i\n" + " }\n" + " }\n" + "\n" + " # Make file with all PC values with intervening 'sep_address' so\n" + " # that we can reliably detect the end of inlined function list\n" + " open(ADDRESSES, \">$main::tmpfile_sym\") || error(\"$main::tmpfile_sym: $!\\n\");\n" + " if ($debug) { print(\"---- $image ---\\n\"); }\n" + " for (my $i = 0; $i <= $#{$pclist}; $i++) {\n" + " # addr2line always reads hex addresses, and does not need '0x' prefix.\n" + " if ($debug) { printf STDERR (\"%s\\n\", $pclist->[$i]); }\n" + " printf ADDRESSES (\"%s\\n\", AddressSub($pclist->[$i], $offset));\n" + " if (defined($sep_address)) {\n" + " printf ADDRESSES (\"%s\\n\", $sep_address);\n" + " }\n" + " }\n" + " close(ADDRESSES);\n" + " if ($debug) {\n" + " print(\"----\\n\");\n" + " system(\"cat\", $main::tmpfile_sym);\n" + " print(\"---- $cmd ---\\n\");\n" + " system(\"$cmd < \" . ShellEscape($main::tmpfile_sym));\n" + " print(\"----\\n\");\n" + " }\n" + "\n" + " open(SYMBOLS, \"$cmd <\" . ShellEscape($main::tmpfile_sym) . \" |\")\n" + " || error(\"$cmd: $!\\n\");\n" + " my $count = 0; # Index in pclist\n" + " while () {\n" + " # Read fullfunction and filelineinfo from next pair of lines\n" + " s/\\r?\\n$//g;\n" + " my $fullfunction = $_;\n" + " $_ = ;\n" + " s/\\r?\\n$//g;\n" + " my $filelinenum = $_;\n" + "\n" + " if (defined($sep_address) && $fullfunction eq $sep_symbol) {\n" + " # Terminating marker for data for this address\n" + " $count++;\n" + " next;\n" + " }\n" + "\n" + " $filelinenum =~ s|\\\\|/|g; # turn windows-style paths into unix-style paths\n" + "\n" + " # Remove discriminator markers as this comes after the line number and\n" + " # confuses the rest of this script.\n" + " $filelinenum =~ s/ \\(discriminator \\d+\\)$//;\n" + " # Convert unknown line numbers into line 0.\n" + " $filelinenum =~ s/:\\?$/:0/;\n" + "\n" + " my $pcstr = $pclist->[$count];\n" + " my $function = ShortFunctionName($fullfunction);\n" + " my $nms = $nm_symbols->{$pcstr};\n" + " if (defined($nms)) {\n" + " if ($fullfunction eq '\?\?') {\n" + " # nm found a symbol for us.\n" + " $function = $nms->[0];\n" + " $fullfunction = $nms->[2];\n" + " } else {\n" + " # MapSymbolsWithNM tags each routine with its starting address,\n" + " # useful in case the image has multiple occurrences of this\n" + " # routine. (It uses a syntax that resembles template parameters,\n" + " # that are automatically stripped out by ShortFunctionName().)\n" + " # addr2line does not provide the same information. So we check\n" + " # if nm disambiguated our symbol, and if so take the annotated\n" + " # (nm) version of the routine-name. TODO(csilvers): this won't\n" + " # catch overloaded, inlined symbols, which nm doesn't see.\n" + " # Better would be to do a check similar to nm's, in this fn.\n" + " if ($nms->[2] =~ m/^\\Q$function\\E/) { # sanity check it's the right fn\n" + " $function = $nms->[0];\n" + " $fullfunction = $nms->[2];\n" + " }\n" + " }\n" + " }\n" + " \n" + " # Prepend to accumulated symbols for pcstr\n" + " # (so that caller comes before callee)\n" + " my $sym = $symbols->{$pcstr};\n" + " if (!defined($sym)) {\n" + " $sym = [];\n" + " $symbols->{$pcstr} = $sym;\n" + " }\n" + " unshift(@{$sym}, $function, $filelinenum, $fullfunction);\n" + " if ($debug) { printf STDERR (\"%s => [%s]\\n\", $pcstr, join(\" \", @{$sym})); }\n" + " if (!defined($sep_address)) {\n" + " # Inlining is off, so this entry ends immediately\n" + " $count++;\n" + " }\n" + " }\n" + " close(SYMBOLS);\n" + "}\n" + "\n" + "# Use nm to map the list of referenced PCs to symbols. Return true iff we\n" + "# are able to read procedure information via nm.\n" + "sub MapSymbolsWithNM {\n" + " my $image = shift;\n" + " my $offset = shift;\n" + " my $pclist = shift;\n" + " my $symbols = shift;\n" + "\n" + " # Get nm output sorted by increasing address\n" + " my $symbol_table = GetProcedureBoundaries($image, \".\");\n" + " if (!%{$symbol_table}) {\n" + " return 0;\n" + " }\n" + " # Start addresses are already the right length (8 or 16 hex digits).\n" + " my @names = sort { $symbol_table->{$a}->[0] cmp $symbol_table->{$b}->[0] }\n" + " keys(%{$symbol_table});\n" + "\n" + " if ($#names < 0) {\n" + " # No symbols: just use addresses\n" + " foreach my $pc (@{$pclist}) {\n" + " my $pcstr = \"0x\" . $pc;\n" + " $symbols->{$pc} = [$pcstr, \"?\", $pcstr];\n" + " }\n" + " return 0;\n" + " }\n" + "\n" + " # Sort addresses so we can do a join against nm output\n" + " my $index = 0;\n" + " my $fullname = $names[0];\n" + " my $name = ShortFunctionName($fullname);\n" + " foreach my $pc (sort { $a cmp $b } @{$pclist}) {\n" + " # Adjust for mapped offset\n" + " my $mpc = AddressSub($pc, $offset);\n" + " while (($index < $#names) && ($mpc ge $symbol_table->{$fullname}->[1])){\n" + " $index++;\n" + " $fullname = $names[$index];\n" + " $name = ShortFunctionName($fullname);\n" + " }\n" + " if ($mpc lt $symbol_table->{$fullname}->[1]) {\n" + " $symbols->{$pc} = [$name, \"?\", $fullname];\n" + " } else {\n" + " my $pcstr = \"0x\" . $pc;\n" + " $symbols->{$pc} = [$pcstr, \"?\", $pcstr];\n" + " }\n" + " }\n" + " return 1;\n" + "}\n" + "\n" + "sub ShortFunctionName {\n" + " my $function = shift;\n" + " while ($function =~ s/\\([^()]*\\)(\\s*const)?//g) { } # Argument types\n" + " $function =~ s/<[0-9a-f]*>$//g; # Remove Address\n" + " if (!$main::opt_no_strip_temp) {\n" + " while ($function =~ s/<[^<>]*>//g) { } # Remove template arguments\n" + " }\n" + " $function =~ s/^.*\\s+(\\w+::)/$1/; # Remove leading type\n" + " return $function;\n" + "}\n" + "\n" + "# Trim overly long symbols found in disassembler output\n" + "sub CleanDisassembly {\n" + " my $d = shift;\n" + " while ($d =~ s/\\([^()%]*\\)(\\s*const)?//g) { } # Argument types, not (%rax)\n" + " while ($d =~ s/(\\w+)<[^<>]*>/$1/g) { } # Remove template arguments\n" + " return $d;\n" + "}\n" + "\n" + "# Clean file name for display\n" + "sub CleanFileName {\n" + " my ($f) = @_;\n" + " $f =~ s|^/proc/self/cwd/||;\n" + " $f =~ s|^\\./||;\n" + " return $f;\n" + "}\n" + "\n" + "# Make address relative to section and clean up for display\n" + "sub UnparseAddress {\n" + " my ($offset, $address) = @_;\n" + " $address = AddressSub($address, $offset);\n" + " $address =~ s/^0x//;\n" + " $address =~ s/^0*//;\n" + " return $address;\n" + "}\n" + "\n" + "##### Miscellaneous #####\n" + "\n" + "# Find the right versions of the above object tools to use. The\n" + "# argument is the program file being analyzed, and should be an ELF\n" + "# 32-bit or ELF 64-bit executable file. The location of the tools\n" + "# is determined by considering the following options in this order:\n" + "# 1) --tools option, if set\n" + "# 2) PPROF_TOOLS environment variable, if set\n" + "# 3) the environment\n" + "sub ConfigureObjTools {\n" + " my $prog_file = shift;\n" + "\n" + " # Check for the existence of $prog_file because /usr/bin/file does not\n" + " # predictably return error status in prod.\n" + " (-e $prog_file) || error(\"$prog_file does not exist.\\n\");\n" + "\n" + " my $file_type = undef;\n" + " if (-e \"/usr/bin/file\") {\n" + " # Follow symlinks (at least for systems where \"file\" supports that).\n" + " my $escaped_prog_file = ShellEscape($prog_file);\n" + " $file_type = `/usr/bin/file -L $escaped_prog_file 2>$dev_null ||\n" + " /usr/bin/file $escaped_prog_file`;\n" + " } elsif ($^O == \"MSWin32\") {\n" + " $file_type = \"MS Windows\";\n" + " } else {\n" + " print STDERR \"WARNING: Can't determine the file type of $prog_file\";\n" + " }\n" + "\n" + " if ($file_type =~ /64-bit/) {\n" + " # Change $address_length to 16 if the program file is ELF 64-bit.\n" + " # We can't detect this from many (most?) heap or lock contention\n" + " # profiles, since the actual addresses referenced are generally in low\n" + " # memory even for 64-bit programs.\n" + " $address_length = 16;\n" + " }\n" + "\n" + " if ($file_type =~ /MS Windows/) {\n" + " # For windows, we provide a version of nm and addr2line as part of\n" + " # the opensource release, which is capable of parsing\n" + " # Windows-style PDB executables. It should live in the path, or\n" + " # in the same directory as pprof.\n" + " $obj_tool_map{\"nm_pdb\"} = \"nm-pdb\";\n" + " $obj_tool_map{\"addr2line_pdb\"} = \"addr2line-pdb\";\n" + " }\n" + "\n" + " if ($file_type =~ /Mach-O/) {\n" + " # OS X uses otool to examine Mach-O files, rather than objdump.\n" + " $obj_tool_map{\"otool\"} = \"otool\";\n" + " $obj_tool_map{\"addr2line\"} = \"false\"; # no addr2line\n" + " $obj_tool_map{\"objdump\"} = \"false\"; # no objdump\n" + " }\n" + "\n" + " # Go fill in %obj_tool_map with the pathnames to use:\n" + " foreach my $tool (keys %obj_tool_map) {\n" + " $obj_tool_map{$tool} = ConfigureTool($obj_tool_map{$tool});\n" + " }\n" + "}\n" + "\n" + "# Returns the path of a caller-specified object tool. If --tools or\n" + "# PPROF_TOOLS are specified, then returns the full path to the tool\n" + "# with that prefix. Otherwise, returns the path unmodified (which\n" + "# means we will look for it on PATH).\n" + "sub ConfigureTool {\n" + " my $tool = shift;\n" + " my $path;\n" + "\n" + " # --tools (or $PPROF_TOOLS) is a comma separated list, where each\n" + " # item is either a) a pathname prefix, or b) a map of the form\n" + " # :. First we look for an entry of type (b) for our\n" + " # tool. If one is found, we use it. Otherwise, we consider all the\n" + " # pathname prefixes in turn, until one yields an existing file. If\n" + " # none does, we use a default path.\n" + " my $tools = $main::opt_tools || $ENV{\"PPROF_TOOLS\"} || \"\";\n" + " if ($tools =~ m/(,|^)\\Q$tool\\E:([^,]*)/) {\n" + " $path = $2;\n" + " # TODO(csilvers): sanity-check that $path exists? Hard if it's relative.\n" + " } elsif ($tools ne '') {\n" + " foreach my $prefix (split(',', $tools)) {\n" + " next if ($prefix =~ /:/); # ignore \"tool:fullpath\" entries in the list\n" + " if (-x $prefix . $tool) {\n" + " $path = $prefix . $tool;\n" + " last;\n" + " }\n" + " }\n" + " if (!$path) {\n" + " error(\"No '$tool' found with prefix specified by \" .\n" + " \"--tools (or \\$PPROF_TOOLS) '$tools'\\n\");\n" + " }\n" + " } else {\n" + " # ... otherwise use the version that exists in the same directory as\n" + " # pprof. If there's nothing there, use $PATH.\n" + " $0 =~ m,[^/]*$,; # this is everything after the last slash\n" + " my $dirname = $`; # this is everything up to and including the last slash\n" + " if (-x \"$dirname$tool\") {\n" + " $path = \"$dirname$tool\";\n" + " } else { \n" + " $path = $tool;\n" + " }\n" + " }\n" + " if ($main::opt_debug) { print STDERR \"Using '$path' for '$tool'.\\n\"; }\n" + " return $path;\n" + "}\n" + "\n" + "sub ShellEscape {\n" + " my @escaped_words = ();\n" + " foreach my $word (@_) {\n" + " my $escaped_word = $word;\n" + " if ($word =~ m![^a-zA-Z0-9/.,_=-]!) { # check for anything not in whitelist\n" + " $escaped_word =~ s/'/'\\\\''/;\n" + " $escaped_word = \"'$escaped_word'\";\n" + " }\n" + " push(@escaped_words, $escaped_word);\n" + " }\n" + " return join(\" \", @escaped_words);\n" + "}\n" + "\n" + "sub cleanup {\n" + " unlink($main::tmpfile_sym);\n" + " unlink(keys %main::tempnames);\n" + "\n" + " # We leave any collected profiles in $HOME/pprof in case the user wants\n" + " # to look at them later. We print a message informing them of this.\n" + " if ((scalar(@main::profile_files) > 0) &&\n" + " defined($main::collected_profile)) {\n" + " if (scalar(@main::profile_files) == 1) {\n" + " print STDERR \"Dynamically gathered profile is in " + "$main::collected_profile\\n\";\n" + " }\n" + " print STDERR \"If you want to investigate this profile further, you can do:\\n\";\n" + " print STDERR \"\\n\";\n" + " print STDERR \" $0 \\\\\\n\";\n" + " print STDERR \" $main::prog \\\\\\n\";\n" + " print STDERR \" $main::collected_profile\\n\";\n" + " print STDERR \"\\n\";\n" + " }\n" + "}\n" + "\n" + "sub sighandler {\n" + " cleanup();\n" + " exit(1);\n" + "}\n" + "\n" + "sub error {\n" + " my $msg = shift;\n" + " print STDERR $msg;\n" + " cleanup();\n" + " exit(1);\n" + "}\n" + "\n" + "\n" + "# Run $nm_command and get all the resulting procedure boundaries whose\n" + "# names match \"$regexp\" and returns them in a hashtable mapping from\n" + "# procedure name to a two-element vector of [start address, end address]\n" + "sub GetProcedureBoundariesViaNm {\n" + " my $escaped_nm_command = shift; # shell-escaped\n" + " my $regexp = shift;\n" + " my $image = shift;\n" + "\n" + " my $symbol_table = {};\n" + " open(NM, \"$escaped_nm_command |\") || error(\"$escaped_nm_command: $!\\n\");\n" + " my $last_start = \"0\";\n" + " my $routine = \"\";\n" + " while () {\n" + " s/\\r//g; # turn windows-looking lines into unix-looking lines\n" + " if (m/^\\s*([0-9a-f]+) (.) (..*)/) {\n" + " my $start_val = $1;\n" + " my $type = $2;\n" + " my $this_routine = $3;\n" + "\n" + " # It's possible for two symbols to share the same address, if\n" + " # one is a zero-length variable (like __start_google_malloc) or\n" + " # one symbol is a weak alias to another (like __libc_malloc).\n" + " # In such cases, we want to ignore all values except for the\n" + " # actual symbol, which in nm-speak has type \"T\". The logic\n" + " # below does this, though it's a bit tricky: what happens when\n" + " # we have a series of lines with the same address, is the first\n" + " # one gets queued up to be processed. However, it won't\n" + " # *actually* be processed until later, when we read a line with\n" + " # a different address. That means that as long as we're reading\n" + " # lines with the same address, we have a chance to replace that\n" + " # item in the queue, which we do whenever we see a 'T' entry --\n" + " # that is, a line with type 'T'. If we never see a 'T' entry,\n" + " # we'll just go ahead and process the first entry (which never\n" + " # got touched in the queue), and ignore the others.\n" + " if ($start_val eq $last_start && $type =~ /t/i) {\n" + " # We are the 'T' symbol at this address, replace previous symbol.\n" + " $routine = $this_routine;\n" + " next;\n" + " } elsif ($start_val eq $last_start) {\n" + " # We're not the 'T' symbol at this address, so ignore us.\n" + " next;\n" + " }\n" + "\n" + " if ($this_routine eq $sep_symbol) {\n" + " $sep_address = HexExtend($start_val);\n" + " }\n" + "\n" + " # Tag this routine with the starting address in case the image\n" + " # has multiple occurrences of this routine. We use a syntax\n" + " # that resembles template parameters that are automatically\n" + " # stripped out by ShortFunctionName()\n" + " $this_routine .= \"<$start_val>\";\n" + "\n" + " if (defined($routine) && $routine =~ m/$regexp/) {\n" + " $symbol_table->{$routine} = [HexExtend($last_start),\n" + " HexExtend($start_val)];\n" + " }\n" + " $last_start = $start_val;\n" + " $routine = $this_routine;\n" + " } elsif (m/^Loaded image name: (.+)/) {\n" + " # The win32 nm workalike emits information about the binary it is using.\n" + " if ($main::opt_debug) { print STDERR \"Using Image $1\\n\"; }\n" + " } elsif (m/^PDB file name: (.+)/) {\n" + " # The win32 nm workalike emits information about the pdb it is using.\n" + " if ($main::opt_debug) { print STDERR \"Using PDB $1\\n\"; }\n" + " }\n" + " }\n" + " close(NM);\n" + " # Handle the last line in the nm output. Unfortunately, we don't know\n" + " # how big this last symbol is, because we don't know how big the file\n" + " # is. For now, we just give it a size of 0.\n" + " # TODO(csilvers): do better here.\n" + " if (defined($routine) && $routine =~ m/$regexp/) {\n" + " $symbol_table->{$routine} = [HexExtend($last_start),\n" + " HexExtend($last_start)];\n" + " }\n" + "\n" + " # Verify if addr2line can find the $sep_symbol. If not, we use objdump\n" + " # to find the address for the $sep_symbol on code section which addr2line\n" + " # can find.\n" + " if (defined($sep_address)){\n" + " my $start_val = $sep_address;\n" + " my $addr2line = $obj_tool_map{\"addr2line\"};\n" + " my $cmd = ShellEscape($addr2line, \"-f\", \"-C\", \"-e\", $image, \"-i\");\n" + " open(FINI, \"echo $start_val | $cmd |\")\n" + " || error(\"echo $start_val | $cmd: $!\\n\");\n" + " $_ = ;\n" + " s/\\r?\\n$//g;\n" + " my $fini = $_;\n" + " close(FINI);\n" + " if ($fini ne $sep_symbol){\n" + " my $objdump = $obj_tool_map{\"objdump\"};\n" + " $cmd = ShellEscape($objdump, \"-d\", $image);\n" + " my $grep = ShellEscape(\"grep\", $sep_symbol);\n" + " my $tail = ShellEscape(\"tail\", \"-n\", \"1\");\n" + " open(FINI, \"$cmd | $grep | $tail |\")\n" + " || error(\"$cmd | $grep | $tail: $!\\n\");\n" + " s/\\r//g; # turn windows-looking lines into unix-looking lines\n" + " my $data = ;\n" + " if (defined($data)){\n" + " ($start_val, $fini) = split(/ $dev_null 2>&1\";\n" + " if (system(ShellEscape($nm, \"--demangle\", $image) . $to_devnull) == 0) {\n" + " # In this mode, we do \"nm --demangle \"\n" + " $demangle_flag = \"--demangle\";\n" + " $cppfilt_flag = \"\";\n" + " } elsif (system(ShellEscape($cppfilt, $image) . $to_devnull) == 0) {\n" + " # In this mode, we do \"nm | c++filt\"\n" + " $cppfilt_flag = \" | \" . ShellEscape($cppfilt);\n" + " };\n" + " my $flatten_flag = \"\";\n" + " if (system(ShellEscape($nm, \"-f\", $image) . $to_devnull) == 0) {\n" + " $flatten_flag = \"-f\";\n" + " }\n" + "\n" + " # Finally, in the case $imagie isn't a debug library, we try again with\n" + " # -D to at least get *exported* symbols. If we can't use --demangle,\n" + " # we use c++filt instead, if it exists on this system.\n" + " my @nm_commands = (ShellEscape($nm, \"-n\", $flatten_flag, $demangle_flag,\n" + " $image) . \" 2>$dev_null $cppfilt_flag\",\n" + " ShellEscape($nm, \"-D\", \"-n\", $flatten_flag, $demangle_flag,\n" + " $image) . \" 2>$dev_null $cppfilt_flag\",\n" + " # 6nm is for Go binaries\n" + " ShellEscape(\"6nm\", \"$image\") . \" 2>$dev_null | sort\",\n" + " );\n" + "\n" + " # If the executable is an MS Windows PDB-format executable, we'll\n" + " # have set up obj_tool_map(\"nm_pdb\"). In this case, we actually\n" + " # want to use both unix nm and windows-specific nm_pdb, since\n" + " # PDB-format executables can apparently include dwarf .o files.\n" + " if (exists $obj_tool_map{\"nm_pdb\"}) {\n" + " push(@nm_commands,\n" + " ShellEscape($obj_tool_map{\"nm_pdb\"}, \"--demangle\", $image)\n" + " . \" 2>$dev_null\");\n" + " }\n" + "\n" + " foreach my $nm_command (@nm_commands) {\n" + " my $symbol_table = GetProcedureBoundariesViaNm($nm_command, $regexp, $image);\n" + " return $symbol_table if (%{$symbol_table});\n" + " }\n" + " my $symbol_table = {};\n" + " return $symbol_table;\n" + "}\n" + "\n" + "\n" + "# The test vectors for AddressAdd/Sub/Inc are 8-16-nibble hex strings.\n" + "# To make them more readable, we add underscores at interesting places.\n" + "# This routine removes the underscores, producing the canonical representation\n" + "# used by pprof to represent addresses, particularly in the tested routines.\n" + "sub CanonicalHex {\n" + " my $arg = shift;\n" + " return join '', (split '_',$arg);\n" + "}\n" + "\n" + "\n" + "# Unit test for AddressAdd:\n" + "sub AddressAddUnitTest {\n" + " my $test_data_8 = shift;\n" + " my $test_data_16 = shift;\n" + " my $error_count = 0;\n" + " my $fail_count = 0;\n" + " my $pass_count = 0;\n" + " # print STDERR \"AddressAddUnitTest: \", 1+$#{$test_data_8}, \" tests\\n\";\n" + "\n" + " # First a few 8-nibble addresses. Note that this implementation uses\n" + " # plain old arithmetic, so a quick sanity check along with verifying what\n" + " # happens to overflow (we want it to wrap):\n" + " $address_length = 8;\n" + " foreach my $row (@{$test_data_8}) {\n" + " if ($main::opt_debug and $main::opt_test) { print STDERR \"@{$row}\\n\"; }\n" + " my $sum = AddressAdd ($row->[0], $row->[1]);\n" + " if ($sum ne $row->[2]) {\n" + " printf STDERR \"ERROR: %s != %s + %s = %s\\n\", $sum,\n" + " $row->[0], $row->[1], $row->[2];\n" + " ++$fail_count;\n" + " } else {\n" + " ++$pass_count;\n" + " }\n" + " }\n" + " printf STDERR \"AddressAdd 32-bit tests: %d passes, %d failures\\n\",\n" + " $pass_count, $fail_count;\n" + " $error_count = $fail_count;\n" + " $fail_count = 0;\n" + " $pass_count = 0;\n" + "\n" + " # Now 16-nibble addresses.\n" + " $address_length = 16;\n" + " foreach my $row (@{$test_data_16}) {\n" + " if ($main::opt_debug and $main::opt_test) { print STDERR \"@{$row}\\n\"; }\n" + " my $sum = AddressAdd (CanonicalHex($row->[0]), CanonicalHex($row->[1]));\n" + " my $expected = join '', (split '_',$row->[2]);\n" + " if ($sum ne CanonicalHex($row->[2])) {\n" + " printf STDERR \"ERROR: %s != %s + %s = %s\\n\", $sum,\n" + " $row->[0], $row->[1], $row->[2];\n" + " ++$fail_count;\n" + " } else {\n" + " ++$pass_count;\n" + " }\n" + " }\n" + " printf STDERR \"AddressAdd 64-bit tests: %d passes, %d failures\\n\",\n" + " $pass_count, $fail_count;\n" + " $error_count += $fail_count;\n" + "\n" + " return $error_count;\n" + "}\n" + "\n" + "\n" + "# Unit test for AddressSub:\n" + "sub AddressSubUnitTest {\n" + " my $test_data_8 = shift;\n" + " my $test_data_16 = shift;\n" + " my $error_count = 0;\n" + " my $fail_count = 0;\n" + " my $pass_count = 0;\n" + " # print STDERR \"AddressSubUnitTest: \", 1+$#{$test_data_8}, \" tests\\n\";\n" + "\n" + " # First a few 8-nibble addresses. Note that this implementation uses\n" + " # plain old arithmetic, so a quick sanity check along with verifying what\n" + " # happens to overflow (we want it to wrap):\n" + " $address_length = 8;\n" + " foreach my $row (@{$test_data_8}) {\n" + " if ($main::opt_debug and $main::opt_test) { print STDERR \"@{$row}\\n\"; }\n" + " my $sum = AddressSub ($row->[0], $row->[1]);\n" + " if ($sum ne $row->[3]) {\n" + " printf STDERR \"ERROR: %s != %s - %s = %s\\n\", $sum,\n" + " $row->[0], $row->[1], $row->[3];\n" + " ++$fail_count;\n" + " } else {\n" + " ++$pass_count;\n" + " }\n" + " }\n" + " printf STDERR \"AddressSub 32-bit tests: %d passes, %d failures\\n\",\n" + " $pass_count, $fail_count;\n" + " $error_count = $fail_count;\n" + " $fail_count = 0;\n" + " $pass_count = 0;\n" + "\n" + " # Now 16-nibble addresses.\n" + " $address_length = 16;\n" + " foreach my $row (@{$test_data_16}) {\n" + " if ($main::opt_debug and $main::opt_test) { print STDERR \"@{$row}\\n\"; }\n" + " my $sum = AddressSub (CanonicalHex($row->[0]), CanonicalHex($row->[1]));\n" + " if ($sum ne CanonicalHex($row->[3])) {\n" + " printf STDERR \"ERROR: %s != %s - %s = %s\\n\", $sum,\n" + " $row->[0], $row->[1], $row->[3];\n" + " ++$fail_count;\n" + " } else {\n" + " ++$pass_count;\n" + " }\n" + " }\n" + " printf STDERR \"AddressSub 64-bit tests: %d passes, %d failures\\n\",\n" + " $pass_count, $fail_count;\n" + " $error_count += $fail_count;\n" + "\n" + " return $error_count;\n" + "}\n" + "\n" + "\n" + "# Unit test for AddressInc:\n" + "sub AddressIncUnitTest {\n" + " my $test_data_8 = shift;\n" + " my $test_data_16 = shift;\n" + " my $error_count = 0;\n" + " my $fail_count = 0;\n" + " my $pass_count = 0;\n" + " # print STDERR \"AddressIncUnitTest: \", 1+$#{$test_data_8}, \" tests\\n\";\n" + "\n" + " # First a few 8-nibble addresses. Note that this implementation uses\n" + " # plain old arithmetic, so a quick sanity check along with verifying what\n" + " # happens to overflow (we want it to wrap):\n" + " $address_length = 8;\n" + " foreach my $row (@{$test_data_8}) {\n" + " if ($main::opt_debug and $main::opt_test) { print STDERR \"@{$row}\\n\"; }\n" + " my $sum = AddressInc ($row->[0]);\n" + " if ($sum ne $row->[4]) {\n" + " printf STDERR \"ERROR: %s != %s + 1 = %s\\n\", $sum,\n" + " $row->[0], $row->[4];\n" + " ++$fail_count;\n" + " } else {\n" + " ++$pass_count;\n" + " }\n" + " }\n" + " printf STDERR \"AddressInc 32-bit tests: %d passes, %d failures\\n\",\n" + " $pass_count, $fail_count;\n" + " $error_count = $fail_count;\n" + " $fail_count = 0;\n" + " $pass_count = 0;\n" + "\n" + " # Now 16-nibble addresses.\n" + " $address_length = 16;\n" + " foreach my $row (@{$test_data_16}) {\n" + " if ($main::opt_debug and $main::opt_test) { print STDERR \"@{$row}\\n\"; }\n" + " my $sum = AddressInc (CanonicalHex($row->[0]));\n" + " if ($sum ne CanonicalHex($row->[4])) {\n" + " printf STDERR \"ERROR: %s != %s + 1 = %s\\n\", $sum,\n" + " $row->[0], $row->[4];\n" + " ++$fail_count;\n" + " } else {\n" + " ++$pass_count;\n" + " }\n" + " }\n" + " printf STDERR \"AddressInc 64-bit tests: %d passes, %d failures\\n\",\n" + " $pass_count, $fail_count;\n" + " $error_count += $fail_count;\n" + "\n" + " return $error_count;\n" + "}\n" + "\n" + "\n" + "# Driver for unit tests.\n" + "# Currently just the address add/subtract/increment routines for 64-bit.\n" + "sub RunUnitTests {\n" + " my $error_count = 0;\n" + "\n" + " # This is a list of tuples [a, b, a+b, a-b, a+1]\n" + " my $unit_test_data_8 = [\n" + " [qw(aaaaaaaa 50505050 fafafafa 5a5a5a5a aaaaaaab)],\n" + " [qw(50505050 aaaaaaaa fafafafa a5a5a5a6 50505051)],\n" + " [qw(ffffffff aaaaaaaa aaaaaaa9 55555555 00000000)],\n" + " [qw(00000001 ffffffff 00000000 00000002 00000002)],\n" + " [qw(00000001 fffffff0 fffffff1 00000011 00000002)],\n" + " ];\n" + " my $unit_test_data_16 = [\n" + " # The implementation handles data in 7-nibble chunks, so those are the\n" + " # interesting boundaries.\n" + " [qw(aaaaaaaa 50505050\n" + " 00_000000f_afafafa 00_0000005_a5a5a5a 00_000000a_aaaaaab)],\n" + " [qw(50505050 aaaaaaaa\n" + " 00_000000f_afafafa ff_ffffffa_5a5a5a6 00_0000005_0505051)],\n" + " [qw(ffffffff aaaaaaaa\n" + " 00_000001a_aaaaaa9 00_0000005_5555555 00_0000010_0000000)],\n" + " [qw(00000001 ffffffff\n" + " 00_0000010_0000000 ff_ffffff0_0000002 00_0000000_0000002)],\n" + " [qw(00000001 fffffff0\n" + " 00_000000f_ffffff1 ff_ffffff0_0000011 00_0000000_0000002)],\n" + "\n" + " [qw(00_a00000a_aaaaaaa 50505050\n" + " 00_a00000f_afafafa 00_a000005_a5a5a5a 00_a00000a_aaaaaab)],\n" + " [qw(0f_fff0005_0505050 aaaaaaaa\n" + " 0f_fff000f_afafafa 0f_ffefffa_5a5a5a6 0f_fff0005_0505051)],\n" + " [qw(00_000000f_fffffff 01_800000a_aaaaaaa\n" + " 01_800001a_aaaaaa9 fe_8000005_5555555 00_0000010_0000000)],\n" + " [qw(00_0000000_0000001 ff_fffffff_fffffff\n" + " 00_0000000_0000000 00_0000000_0000002 00_0000000_0000002)],\n" + " [qw(00_0000000_0000001 ff_fffffff_ffffff0\n" + " ff_fffffff_ffffff1 00_0000000_0000011 00_0000000_0000002)],\n" + " ];\n" + "\n" + " $error_count += AddressAddUnitTest($unit_test_data_8, $unit_test_data_16);\n" + " $error_count += AddressSubUnitTest($unit_test_data_8, $unit_test_data_16);\n" + " $error_count += AddressIncUnitTest($unit_test_data_8, $unit_test_data_16);\n" + " if ($error_count > 0) {\n" + " print STDERR $error_count, \" errors: FAILED\\n\";\n" + " } else {\n" + " print STDERR \"PASS\\n\";\n" + " }\n" + " exit ($error_count);\n" + "}\n"; +} + +} // namespace brpc diff --git a/src/brpc/builtin/pprof_perl.h b/src/brpc/builtin/pprof_perl.h new file mode 100644 index 0000000..bb6d79d --- /dev/null +++ b/src/brpc/builtin/pprof_perl.h @@ -0,0 +1,30 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_BUILTIN_PPROF_PERL_H +#define BRPC_BUILTIN_PPROF_PERL_H + + +namespace brpc { + +const char* pprof_perl(); + +} // namespace brpc + + +#endif // BRPC_BUILTIN_PPROF_PERL_H diff --git a/src/brpc/builtin/pprof_service.cpp b/src/brpc/builtin/pprof_service.cpp new file mode 100644 index 0000000..e22144f --- /dev/null +++ b/src/brpc/builtin/pprof_service.cpp @@ -0,0 +1,579 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // strftime +#include +#include +#include +#include +#include // O_RDONLY +#include "butil/string_printf.h" // string_printf +#include "butil/string_splitter.h" // StringSplitter +#include "butil/file_util.h" // butil::FilePath +#include "butil/files/scoped_file.h" // ScopedFILE +#include "butil/time.h" +#include "butil/popen.h" // butil::read_command_output +#include "butil/process_util.h" // butil::ReadCommandLine +#include "brpc/log.h" +#include "brpc/controller.h" // Controller +#include "brpc/closure_guard.h" // ClosureGuard +#include "brpc/builtin/pprof_service.h" +#include "brpc/builtin/common.h" +#include "brpc/details/tcmalloc_extension.h" +#include "brpc/details/jemalloc_profiler.h" +#include "bthread/bthread.h" // bthread_usleep +#include "butil/fd_guard.h" + +extern "C" { +#if defined(OS_LINUX) +extern char *program_invocation_name; +#endif +int BAIDU_WEAK ProfilerStart(const char* fname); +void BAIDU_WEAK ProfilerStop(); +} + +namespace bthread { +bool ContentionProfilerStart(const char* filename); +void ContentionProfilerStop(); +} + + +namespace brpc { + +static int ReadSeconds(Controller* cntl) { + int seconds = 0; + const std::string* param = + cntl->http_request().uri().GetQuery("seconds"); + if (param != NULL) { + char* endptr = NULL; + const long sec = strtol(param->c_str(), &endptr, 10); + if (endptr == param->c_str() + param->length()) { + seconds = sec; + } else { + cntl->SetFailed(EINVAL, "Invalid seconds=%s", param->c_str()); + } + } + + return seconds; +} + +int MakeProfName(ProfilingType type, char* buf, size_t buf_len) { + // Add pprof_ prefix to separate from /hotspots + int nr = snprintf(buf, buf_len, "%s/pprof_%s/", FLAGS_rpc_profiling_dir.c_str(), + GetProgramChecksum()); + if (nr < 0) { + return -1; + } + buf += nr; + buf_len -= nr; + + time_t rawtime; + time(&rawtime); + struct tm* timeinfo = localtime(&rawtime); + const size_t nw = strftime(buf, buf_len, "%Y%m%d.%H%M%S", timeinfo); + buf += nw; + buf_len -= nw; + + // We have checksum in the path, getpid() is not necessary now. + snprintf(buf, buf_len, ".%s", ProfilingType2String(type)); + return 0; +} + +void PProfService::profile( + ::google::protobuf::RpcController* controller_base, + const ::brpc::ProfileRequest* /*request*/, + ::brpc::ProfileResponse* /*response*/, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller* cntl = static_cast(controller_base); + cntl->http_response().set_content_type("text/plain"); + if ((void*)ProfilerStart == NULL || (void*)ProfilerStop == NULL) { + cntl->SetFailed(ENOMETHOD, "%s, to enable cpu profiler, check out " + "docs/cn/cpu_profiler.md", + berror(ENOMETHOD)); + return; + } + int sleep_sec = ReadSeconds(cntl); + if (sleep_sec <= 0) { + if (!cntl->Failed()) { + cntl->SetFailed(EINVAL, "You have to specify ?seconds=N. If you're " + "using pprof, add --seconds=N"); + } + return; + } + // Log requester + std::ostringstream client_info; + client_info << cntl->remote_side(); + if (cntl->auth_context()) { + client_info << "(auth=" << cntl->auth_context()->user() << ')'; + } else { + client_info << "(no auth)"; + } + LOG(INFO) << client_info.str() << " requests for cpu profile for " + << sleep_sec << " seconds"; + + char prof_name[256]; + if (MakeProfName(PROFILING_CPU, prof_name, sizeof(prof_name)) != 0) { + cntl->SetFailed(errno, "Fail to create .prof file, %s", berror()); + return; + } + butil::File::Error error; + const butil::FilePath dir = butil::FilePath(prof_name).DirName(); + if (!butil::CreateDirectoryAndGetError(dir, &error)) { + cntl->SetFailed(EPERM, "Fail to create directory=`%s'",dir.value().c_str()); + return; + } + if (!ProfilerStart(prof_name)) { + cntl->SetFailed(EAGAIN, "Another profiler is running, try again later"); + return; + } + if (bthread_usleep(sleep_sec * 1000000L) != 0) { + PLOG(WARNING) << "Profiling has been interrupted"; + } + ProfilerStop(); + + butil::fd_guard fd(open(prof_name, O_RDONLY)); + if (fd < 0) { + cntl->SetFailed(ENOENT, "Fail to open %s", prof_name); + return; + } + butil::IOPortal portal; + portal.append_from_file_descriptor(fd, ULONG_MAX); + cntl->response_attachment().swap(portal); +} + +void PProfService::contention( + ::google::protobuf::RpcController* controller_base, + const ::brpc::ProfileRequest* /*request*/, + ::brpc::ProfileResponse* /*response*/, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller* cntl = static_cast(controller_base); + cntl->http_response().set_content_type("text/plain"); + int sleep_sec = ReadSeconds(cntl); + if (sleep_sec <= 0) { + if (!cntl->Failed()) { + cntl->SetFailed(EINVAL, "You have to specify ?seconds=N. If you're " + "using pprof, add --seconds=N"); + } + return; + } + // Log requester + std::ostringstream client_info; + client_info << cntl->remote_side(); + if (cntl->auth_context()) { + client_info << "(auth=" << cntl->auth_context()->user() << ')'; + } else { + client_info << "(no auth)"; + } + LOG(INFO) << client_info.str() << " requests for contention profile for " + << sleep_sec << " seconds"; + + char prof_name[256]; + if (MakeProfName(PROFILING_CONTENTION, prof_name, sizeof(prof_name)) != 0) { + cntl->SetFailed(errno, "Fail to create .prof file, %s", berror()); + return; + } + if (!bthread::ContentionProfilerStart(prof_name)) { + cntl->SetFailed(EAGAIN, "Another profiler is running, try again later"); + return; + } + if (bthread_usleep(sleep_sec * 1000000L) != 0) { + PLOG(WARNING) << "Profiling has been interrupted"; + } + bthread::ContentionProfilerStop(); + + butil::fd_guard fd(open(prof_name, O_RDONLY)); + if (fd < 0) { + cntl->SetFailed(ENOENT, "Fail to open %s", prof_name); + return; + } + butil::IOPortal portal; + portal.append_from_file_descriptor(fd, ULONG_MAX); + cntl->response_attachment().swap(portal); +} + +void PProfService::heap( + ::google::protobuf::RpcController* controller_base, + const ::brpc::ProfileRequest* /*request*/, + ::brpc::ProfileResponse* /*response*/, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller* cntl = static_cast(controller_base); + + if (HasJemalloc()) { + JeControlProfile(cntl); + return; + } + + MallocExtension* malloc_ext = MallocExtension::instance(); + if (malloc_ext == NULL || !has_TCMALLOC_SAMPLE_PARAMETER()) { + const char* extra_desc = ""; + if (malloc_ext != NULL) { + extra_desc = " (no TCMALLOC_SAMPLE_PARAMETER in env)"; + } + cntl->SetFailed(ENOMETHOD, "Heap profiler is not enabled%s," + "check out https://github.com/apache/brpc/blob/master/docs/cn/heap_profiler.md", + extra_desc); + return; + } + // Log requester + std::ostringstream client_info; + client_info << cntl->remote_side(); + if (cntl->auth_context()) { + client_info << "(auth=" << cntl->auth_context()->user() << ')'; + } else { + client_info << "(no auth)"; + } + LOG(INFO) << client_info.str() << " requests for heap profile"; + + std::string obj; + malloc_ext->GetHeapSample(&obj); + cntl->http_response().set_content_type("text/plain"); + cntl->response_attachment().append(obj); +} + +void PProfService::growth( + ::google::protobuf::RpcController* controller_base, + const ::brpc::ProfileRequest* /*request*/, + ::brpc::ProfileResponse* /*response*/, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller* cntl = static_cast(controller_base); + MallocExtension* malloc_ext = MallocExtension::instance(); + if (malloc_ext == NULL) { + cntl->SetFailed(ENOMETHOD, "%s, to enable growth profiler, check out " + "docs/cn/heap_profiler.md", + berror(ENOMETHOD)); + return; + } + // Log requester + std::ostringstream client_info; + client_info << cntl->remote_side(); + if (cntl->auth_context()) { + client_info << "(auth=" << cntl->auth_context()->user() << ')'; + } else { + client_info << "(no auth)"; + } + LOG(INFO) << client_info.str() << " requests for growth profile"; + + std::string obj; + malloc_ext->GetHeapGrowthStacks(&obj); + cntl->http_response().set_content_type("text/plain"); + cntl->response_attachment().append(obj); +} + +typedef std::map SymbolMap; +struct LibInfo { + uintptr_t start_addr; + uintptr_t end_addr; + size_t offset; + std::string path; +}; +static SymbolMap symbol_map; +static pthread_once_t s_load_symbolmap_once = PTHREAD_ONCE_INIT; + +static bool HasExt(const std::string& name, const std::string& ext) { + size_t index = name.find(ext); + if (index == std::string::npos) { + return false; + } + return (index + ext.size() == name.size() || + name[index + ext.size()] == '.'); +} + +static int ExtractSymbolsFromBinary( + std::map& addr_map, + const LibInfo& lib_info) { + butil::Timer tm; + tm.start(); + std::string cmd = "nm -C -p "; + cmd.append(lib_info.path); + std::stringstream ss; + const int rc = butil::read_command_output(ss, cmd.c_str()); + if (rc < 0) { + LOG(ERROR) << "Fail to popen `" << cmd << "'"; + return -1; + } + std::string line; + while (std::getline(ss, line)) { + butil::StringSplitter sp(line.c_str(), ' '); + if (sp == NULL) { + continue; + } + char* endptr = NULL; + uintptr_t addr = strtoull(sp.field(), &endptr, 16); + if (*endptr != ' ') { + continue; + } + if (addr < lib_info.start_addr) { + addr = addr + lib_info.start_addr - lib_info.offset; + } + if (addr >= lib_info.end_addr) { + continue; + } + ++sp; + if (sp == NULL) { + continue; + } + if (sp.length() != 1UL) { + continue; + } + //const char c = *sp.field(); + + ++sp; + if (sp == NULL) { + continue; + } + const char* name_begin = sp.field(); + if (strncmp(name_begin, "typeinfo ", 9) == 0 || + strncmp(name_begin, "VTT ", 4) == 0 || + strncmp(name_begin, "vtable ", 7) == 0 || + strncmp(name_begin, "global ", 7) == 0 || + strncmp(name_begin, "guard ", 6) == 0) { + addr_map[addr] = std::string(); + continue; + } + + const char* name_end = sp.field(); + bool stop = false; + char last_char = '\0'; + while (1) { + switch (*name_end) { + case 0: + case '\r': + case '\n': + stop = true; + break; + case '(': + case '<': + // \(.*\w\)[(<]... -> \1 + // foo(..) -> foo + // foo<...>(...) -> foo + // a::b::foo(...) -> a::b::foo + // a::(b)::foo(...) -> a::(b)::foo + if (isalpha(last_char) || isdigit(last_char) || + last_char == '_') { + stop = true; + } + default: + break; + } + if (stop) { + break; + } + last_char = *name_end++; + } + // If address conflicts, choose a shorter name (not necessarily to be + // T type in nm). This works fine because aliases often have more + // prefixes. + const size_t name_len = name_end - name_begin; + SymbolMap::iterator it = addr_map.find(addr); + if (it != addr_map.end()) { + if (name_len < it->second.size()) { + it->second.assign(name_begin, name_len); + } + } else { + addr_map[addr] = std::string(name_begin, name_len); + } + } + if (addr_map.find(lib_info.end_addr) == addr_map.end()) { + addr_map[lib_info.end_addr] = std::string(); + } + tm.stop(); + RPC_VLOG << "Loaded " << lib_info.path << " in " << tm.m_elapsed() << "ms"; + return 0; +} + +static void LoadSymbols() { + butil::Timer tm; + tm.start(); + butil::ScopedFILE fp(fopen("/proc/self/maps", "r")); + if (fp == NULL) { + return; + } + char* line = NULL; + size_t line_len = 0; + ssize_t nr = 0; + while ((nr = getline(&line, &line_len, fp.get())) != -1) { + butil::StringSplitter sp(line, line + nr, ' '); + if (sp == NULL) { + continue; + } + char* endptr; + uintptr_t start_addr = strtoull(sp.field(), &endptr, 16); + if (*endptr != '-') { + continue; + } + ++endptr; + uintptr_t end_addr = strtoull(endptr, &endptr, 16); + if (*endptr != ' ') { + continue; + } + ++sp; + // ..x. must be executable + if (sp == NULL || sp.length() != 4 || sp.field()[2] != 'x') { + continue; + } + ++sp; + if (sp == NULL) { + continue; + } + size_t offset = strtoull(sp.field(), &endptr, 16); + if (*endptr != ' ') { + continue; + } + //skip $4~$5 + for (int i = 0; i < 3; ++i) { + ++sp; + } + if (sp == NULL) { + continue; + } + size_t n = sp.length(); + if (sp.field()[n-1] == '\n') { + --n; + } + std::string path(sp.field(), n); + if (!HasExt(path, ".so") && !HasExt(path, ".dll") && + !HasExt(path, ".dylib") && !HasExt(path, ".bundle")) { + continue; + } + LibInfo info; + info.start_addr = start_addr; + info.end_addr = end_addr; + info.offset = offset; + info.path = path; + ExtractSymbolsFromBinary(symbol_map, info); + } + free(line); + + LibInfo info; + info.start_addr = 0; + info.end_addr = std::numeric_limits::max(); + info.offset = 0; +#if defined(OS_LINUX) + info.path = program_invocation_name; +#elif defined(OS_MACOSX) + info.path = getprogname(); +#endif + ExtractSymbolsFromBinary(symbol_map, info); + + butil::Timer tm2; + tm2.start(); + size_t num_removed = 0; + bool last_is_empty = false; + for (SymbolMap::iterator + it = symbol_map.begin(); it != symbol_map.end();) { + if (it->second.empty()) { + if (last_is_empty) { + symbol_map.erase(it++); + ++num_removed; + } else { + ++it; + } + last_is_empty = true; + } else { + ++it; + } + } + tm2.stop(); + RPC_VLOG_IF(num_removed) << "Removed " << num_removed << " entries in " + << tm2.m_elapsed() << "ms"; + + tm.stop(); + RPC_VLOG << "Loaded all symbols in " << tm.m_elapsed() << "ms"; +} + +static void FindSymbols(butil::IOBuf* out, std::vector& addr_list) { + char buf[32]; + for (size_t i = 0; i < addr_list.size(); ++i) { + int len = snprintf(buf, sizeof(buf), "0x%08lx\t", addr_list[i]); + out->append(buf, len); + SymbolMap::const_iterator it = symbol_map.lower_bound(addr_list[i]); + if (it == symbol_map.end() || it->first != addr_list[i]) { + if (it != symbol_map.begin()) { + --it; + } else { + len = snprintf(buf, sizeof(buf), "0x%08lx\n", addr_list[i]); + out->append(buf, len); + continue; + } + } + if (it->second.empty()) { + len = snprintf(buf, sizeof(buf), "0x%08lx\n", addr_list[i]); + out->append(buf, len); + } else { + out->append(it->second); + out->push_back('\n'); + } + } +} + +void PProfService::symbol( + ::google::protobuf::RpcController* controller_base, + const ::brpc::ProfileRequest* /*request*/, + ::brpc::ProfileResponse* /*response*/, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller* cntl = static_cast(controller_base); + cntl->http_response().set_content_type("text/plain"); + + // Load /proc/self/maps + pthread_once(&s_load_symbolmap_once, LoadSymbols); + + if (cntl->http_request().method() != HTTP_METHOD_POST) { + char buf[64]; + snprintf(buf, sizeof(buf), "num_symbols: %lu\n", symbol_map.size()); + cntl->response_attachment().append(buf); + } else { + // addr_str is addressed separated by + + std::string addr_str = cntl->request_attachment().to_string(); + // May be quoted + const char* addr_cstr = addr_str.c_str(); + if (*addr_cstr == '\'' || *addr_cstr == '"') { + ++addr_cstr; + } + std::vector addr_list; + addr_list.reserve(32); + butil::StringSplitter sp(addr_cstr, '+'); + for ( ; sp != NULL; ++sp) { + char* endptr; + uintptr_t addr = strtoull(sp.field(), &endptr, 16); + addr_list.push_back(addr); + } + FindSymbols(&cntl->response_attachment(), addr_list); + } +} + +void PProfService::cmdline(::google::protobuf::RpcController* controller_base, + const ::brpc::ProfileRequest* /*request*/, + ::brpc::ProfileResponse* /*response*/, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller* cntl = static_cast(controller_base); + cntl->http_response().set_content_type("text/plain" /*FIXME*/); + char buf[1024]; // should be enough? + const ssize_t nr = butil::ReadCommandLine(buf, sizeof(buf), true); + if (nr < 0) { + cntl->SetFailed(ENOENT, "Fail to read cmdline"); + return; + } + cntl->response_attachment().append(buf, nr); +} + +} // namespace brpc diff --git a/src/brpc/builtin/pprof_service.h b/src/brpc/builtin/pprof_service.h new file mode 100644 index 0000000..b61bb03 --- /dev/null +++ b/src/brpc/builtin/pprof_service.h @@ -0,0 +1,62 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_PPROF_SERVICE_H +#define BRPC_PPROF_SERVICE_H + +#include "brpc/builtin_service.pb.h" + + +namespace brpc { + +class PProfService : public pprof { +public: + void profile(::google::protobuf::RpcController* controller, + const ::brpc::ProfileRequest* request, + ::brpc::ProfileResponse* response, + ::google::protobuf::Closure* done); + + void contention(::google::protobuf::RpcController* controller, + const ::brpc::ProfileRequest* request, + ::brpc::ProfileResponse* response, + ::google::protobuf::Closure* done); + + void heap(::google::protobuf::RpcController* controller, + const ::brpc::ProfileRequest* request, + ::brpc::ProfileResponse* response, + ::google::protobuf::Closure* done); + + void growth(::google::protobuf::RpcController* controller, + const ::brpc::ProfileRequest* request, + ::brpc::ProfileResponse* response, + ::google::protobuf::Closure* done); + + void symbol(::google::protobuf::RpcController* controller, + const ::brpc::ProfileRequest* request, + ::brpc::ProfileResponse* response, + ::google::protobuf::Closure* done); + + void cmdline(::google::protobuf::RpcController* controller, + const ::brpc::ProfileRequest* request, + ::brpc::ProfileResponse* response, + ::google::protobuf::Closure* done); +}; + +} // namespace brpc + + +#endif //BRPC_PPROF_SERVICE_H diff --git a/src/brpc/builtin/prometheus_metrics_service.cpp b/src/brpc/builtin/prometheus_metrics_service.cpp new file mode 100644 index 0000000..4efc24b --- /dev/null +++ b/src/brpc/builtin/prometheus_metrics_service.cpp @@ -0,0 +1,244 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include "brpc/controller.h" // Controller +#include "brpc/server.h" // Server +#include "brpc/closure_guard.h" // ClosureGuard +#include "brpc/builtin/prometheus_metrics_service.h" +#include "brpc/builtin/common.h" +#include "bvar/bvar.h" + +namespace bvar { +DECLARE_int32(bvar_latency_p1); +DECLARE_int32(bvar_latency_p2); +DECLARE_int32(bvar_latency_p3); +DECLARE_int32(bvar_max_dump_multi_dimension_metric_number); +} + +namespace brpc { + +// Defined in server.cpp +extern const char* const g_server_info_prefix; + +// This is a class that convert bvar result to prometheus output. +// Currently the output only includes gauge and summary for two +// reasons: +// 1) We cannot tell gauge and counter just from name and what's +// more counter is just another gauge. +// 2) Histogram and summary is equivalent except that histogram +// calculates quantiles in the server side. +class PrometheusMetricsDumper : public bvar::Dumper { +public: + explicit PrometheusMetricsDumper(butil::IOBufBuilder* os, + const std::string& server_prefix) + : _os(os) + , _server_prefix(server_prefix) { + } + + bool dump(const std::string& name, const butil::StringPiece& desc) override; + bool dump_mvar(const std::string& name, const butil::StringPiece& desc) override; + bool dump_comment(const std::string& name, const std::string& type) override; + +private: + DISALLOW_COPY_AND_ASSIGN(PrometheusMetricsDumper); + + // Return true iff name ends with suffix output by LatencyRecorder. + bool DumpLatencyRecorderSuffix(const butil::StringPiece& name, + const butil::StringPiece& desc); + + // 6 is the number of bvars in LatencyRecorder that indicating percentiles + static const int NPERCENTILES = 6; + + struct SummaryItems { + std::string latency_percentiles[NPERCENTILES]; + int64_t latency_avg; + int64_t count; + std::string metric_name; + + bool IsComplete() const { return !metric_name.empty(); } + }; + const SummaryItems* ProcessLatencyRecorderSuffix(const butil::StringPiece& name, + const butil::StringPiece& desc); + +private: + butil::IOBufBuilder* _os; + const std::string _server_prefix; + std::map _m; +}; + +butil::StringPiece GetMetricsName(const std::string& name) { + auto pos = name.find_first_of('{'); + int size = (pos == std::string::npos) ? name.size() : pos; + return butil::StringPiece(name.data(), size); +} + +bool PrometheusMetricsDumper::dump(const std::string& name, + const butil::StringPiece& desc) { + if (!desc.empty() && desc[0] == '"') { + // there is no necessary to monitor string in prometheus + return true; + } + if (DumpLatencyRecorderSuffix(name, desc)) { + // Has encountered name with suffix exposed by LatencyRecorder, + // Leave it to DumpLatencyRecorderSuffix to output Summary. + return true; + } + + auto metrics_name = GetMetricsName(name); + + *_os << "# HELP " << metrics_name << '\n' + << "# TYPE " << metrics_name << " gauge" << '\n' + << name << " " << desc << '\n'; + return true; +} + +bool PrometheusMetricsDumper::dump_mvar(const std::string& name, const butil::StringPiece& desc) { + if (!desc.empty() && desc[0] == '"') { + // there is no necessary to monitor string in prometheus + return true; + } + *_os << name << " " << desc << "\n"; + return true; +} + +bool PrometheusMetricsDumper::dump_comment(const std::string& name, const std::string& type) { + *_os << "# HELP " << name << '\n' + << "# TYPE " << name << " " << type << '\n'; + return true; +} + +const PrometheusMetricsDumper::SummaryItems* +PrometheusMetricsDumper::ProcessLatencyRecorderSuffix(const butil::StringPiece& name, + const butil::StringPiece& desc) { + static std::string latency_names[] = { + butil::string_printf("_latency_%d", (int)bvar::FLAGS_bvar_latency_p1), + butil::string_printf("_latency_%d", (int)bvar::FLAGS_bvar_latency_p2), + butil::string_printf("_latency_%d", (int)bvar::FLAGS_bvar_latency_p3), + "_latency_999", "_latency_9999", "_max_latency" + }; + CHECK(NPERCENTILES == arraysize(latency_names)); + const std::string desc_str = desc.as_string(); + butil::StringPiece metric_name(name); + for (int i = 0; i < NPERCENTILES; ++i) { + if (!metric_name.ends_with(latency_names[i])) { + continue; + } + metric_name.remove_suffix(latency_names[i].size()); + SummaryItems* si = &_m[metric_name.as_string()]; + si->latency_percentiles[i] = desc_str; + if (i == NPERCENTILES - 1) { + // '_max_latency' is the last suffix name that appear in the sorted bvar + // list, which means all related percentiles have been gathered and we are + // ready to output a Summary. + si->metric_name = metric_name.as_string(); + } + return si; + } + // Get the average of latency in recent window size + if (metric_name.ends_with("_latency")) { + metric_name.remove_suffix(8); + SummaryItems* si = &_m[metric_name.as_string()]; + si->latency_avg = strtoll(desc_str.data(), NULL, 10); + return si; + } + if (metric_name.ends_with("_count")) { + metric_name.remove_suffix(6); + SummaryItems* si = &_m[metric_name.as_string()]; + si->count = strtoll(desc_str.data(), NULL, 10); + return si; + } + return NULL; +} + +bool PrometheusMetricsDumper::DumpLatencyRecorderSuffix( + const butil::StringPiece& name, + const butil::StringPiece& desc) { + if (!name.starts_with(_server_prefix)) { + return false; + } + const SummaryItems* si = ProcessLatencyRecorderSuffix(name, desc); + if (!si) { + return false; + } + if (!si->IsComplete()) { + return true; + } + *_os << "# HELP " << si->metric_name << '\n' + << "# TYPE " << si->metric_name << " summary\n" + << si->metric_name << "{quantile=\"" + << (double)(bvar::FLAGS_bvar_latency_p1) / 100 << "\"} " + << si->latency_percentiles[0] << '\n' + << si->metric_name << "{quantile=\"" + << (double)(bvar::FLAGS_bvar_latency_p2) / 100 << "\"} " + << si->latency_percentiles[1] << '\n' + << si->metric_name << "{quantile=\"" + << (double)(bvar::FLAGS_bvar_latency_p3) / 100 << "\"} " + << si->latency_percentiles[2] << '\n' + << si->metric_name << "{quantile=\"0.999\"} " + << si->latency_percentiles[3] << '\n' + << si->metric_name << "{quantile=\"0.9999\"} " + << si->latency_percentiles[4] << '\n' + << si->metric_name << "{quantile=\"1\"} " + << si->latency_percentiles[5] << '\n' + << si->metric_name << "{quantile=\"avg\"} " + << si->latency_avg << '\n' + << si->metric_name << "_sum " + // There is no sum of latency in bvar output, just use + // average * count as approximation + << si->latency_avg * si->count << '\n' + << si->metric_name << "_count " << si->count << '\n'; + return true; +} + +void PrometheusMetricsService::default_method(::google::protobuf::RpcController* cntl_base, + const ::brpc::MetricsRequest*, + ::brpc::MetricsResponse*, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller *cntl = static_cast(cntl_base); + cntl->http_response().set_content_type("text/plain"); + if (DumpPrometheusMetricsToIOBuf(&cntl->response_attachment()) != 0) { + cntl->SetFailed("Fail to dump metrics"); + return; + } +} + +int DumpPrometheusMetricsToIOBuf(butil::IOBuf* output) { + butil::IOBufBuilder os; + PrometheusMetricsDumper dumper(&os, g_server_info_prefix); + const int ndump = bvar::Variable::dump_exposed(&dumper, NULL); + if (ndump < 0) { + return -1; + } + os.move_to(*output); + + if (bvar::FLAGS_bvar_max_dump_multi_dimension_metric_number > 0) { + PrometheusMetricsDumper dumper_md(&os, g_server_info_prefix); + const int ndump_md = bvar::MVariableBase::dump_exposed(&dumper_md, NULL); + if (ndump_md < 0) { + return -1; + } + output->append(butil::IOBuf::Movable(os.buf())); + } + return 0; +} + +} // namespace brpc diff --git a/src/brpc/builtin/prometheus_metrics_service.h b/src/brpc/builtin/prometheus_metrics_service.h new file mode 100644 index 0000000..541b395 --- /dev/null +++ b/src/brpc/builtin/prometheus_metrics_service.h @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_PROMETHEUS_METRICS_SERVICE_H +#define BRPC_PROMETHEUS_METRICS_SERVICE_H + +#include "brpc/builtin_service.pb.h" + +namespace brpc { + +class PrometheusMetricsService : public brpc_metrics { +public: + void default_method(::google::protobuf::RpcController* cntl_base, + const ::brpc::MetricsRequest* request, + ::brpc::MetricsResponse* response, + ::google::protobuf::Closure* done) override; +}; + +butil::StringPiece GetMetricsName(const std::string& name); +int DumpPrometheusMetricsToIOBuf(butil::IOBuf* output); + +} // namepace brpc + +#endif // BRPC_PROMETHEUS_METRICS_SERVICE_H diff --git a/src/brpc/builtin/protobufs_service.cpp b/src/brpc/builtin/protobufs_service.cpp new file mode 100644 index 0000000..99a3fea --- /dev/null +++ b/src/brpc/builtin/protobufs_service.cpp @@ -0,0 +1,118 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // ServiceDescriptor + +#include "brpc/builtin/protobufs_service.h" + +#include "brpc/controller.h" // Controller +#include "brpc/server.h" // Server +#include "brpc/closure_guard.h" // ClosureGuard +#include "brpc/details/method_status.h"// MethodStatus +#include "brpc/builtin/common.h" + +#include "butil/strings/string_util.h" + +namespace brpc { + +ProtobufsService::ProtobufsService(Server* server) : _server(server) { + CHECK_EQ(0, Init()); +} + +int ProtobufsService::Init() { + Server::ServiceMap &services = _server->_fullname_service_map; + std::vector stack; + stack.reserve(services.size() * 3); + for (Server::ServiceMap::iterator + iter = services.begin(); iter != services.end(); ++iter) { + if (!iter->second.is_user_service()) { + continue; + } + const google::protobuf::ServiceDescriptor* d = + iter->second.service->GetDescriptor(); + _map[butil::EnsureString(d->full_name())] = d->DebugString(); + const int method_count = d->method_count(); + for (int j = 0; j < method_count; ++j) { + const google::protobuf::MethodDescriptor* md = d->method(j); + stack.push_back(md->input_type()); + stack.push_back(md->output_type()); + } + } + while (!stack.empty()) { + const google::protobuf::Descriptor* d = stack.back(); + stack.pop_back(); + _map[butil::EnsureString(d->full_name())] = d->DebugString(); + for (int i = 0; i < d->field_count(); ++i) { + const google::protobuf::FieldDescriptor* f = d->field(i); + if (f->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE || + f->type() == google::protobuf::FieldDescriptor::TYPE_GROUP) { + const google::protobuf::Descriptor* sub_d = f->message_type(); + if (sub_d != d && _map.find(butil::EnsureString(sub_d->full_name())) == _map.end()) { + stack.push_back(sub_d); + } + } + } + } + return 0; +} + +void ProtobufsService::default_method(::google::protobuf::RpcController* cntl_base, + const ProtobufsRequest*, + ProtobufsResponse*, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller *cntl = static_cast(cntl_base); + butil::IOBufBuilder os; + const std::string& filter = cntl->http_request().unresolved_path(); + if (filter.empty()) { + const bool use_html = UseHTML(cntl->http_request()); + cntl->http_response().set_content_type( + use_html ? "text/html" : "text/plain"); + if (use_html) { + os << "\n"; + } + // list all structures. + for (Map::iterator it = _map.begin(); it != _map.end(); ++it) { + if (use_html) { + os << "

first << "\">"; + } + os << it->first; + if (use_html) { + os << "

"; + } + os << '\n'; + } + if (use_html) { + os << ""; + } + } else { + // already text. + cntl->http_response().set_content_type("text/plain"); + Map::iterator it = _map.find(filter); + if (it == _map.end()) { + cntl->SetFailed(ENOMETHOD, + "Fail to find any protobuf message by `%s'", + filter.c_str()); + return; + } + os << it->second; + } + os.move_to(cntl->response_attachment()); +} + +} // namespace brpc diff --git a/src/brpc/builtin/protobufs_service.h b/src/brpc/builtin/protobufs_service.h new file mode 100644 index 0000000..2f0c597 --- /dev/null +++ b/src/brpc/builtin/protobufs_service.h @@ -0,0 +1,53 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_PROTOBUFS_SERVICE_H +#define BRPC_PROTOBUFS_SERVICE_H + +#include +#include "brpc/builtin_service.pb.h" + + +namespace brpc { + +class Server; + +// Show DebugString of protobuf messages used in the server. +// /protobufs : list all supported messages. +// /protobufs// : Show DebugString() of + +class ProtobufsService : public protobufs { +public: + explicit ProtobufsService(Server* server); + + void default_method(::google::protobuf::RpcController* cntl_base, + const ::brpc::ProtobufsRequest* request, + ::brpc::ProtobufsResponse* response, + ::google::protobuf::Closure* done); +private: + int Init(); + + Server* _server; + typedef std::map Map; + Map _map; +}; + +} // namespace brpc + + +#endif //BRPC_PROTOBUFS_SERVICE_H diff --git a/src/brpc/builtin/rpcz_service.cpp b/src/brpc/builtin/rpcz_service.cpp new file mode 100644 index 0000000..e5111ac --- /dev/null +++ b/src/brpc/builtin/rpcz_service.cpp @@ -0,0 +1,742 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include "butil/string_printf.h" +#include "butil/string_splitter.h" +#include "butil/macros.h" +#include "butil/time.h" +#include "brpc/closure_guard.h" // ClosureGuard +#include "brpc/controller.h" // Controller +#include "brpc/builtin/common.h" +#include "brpc/server.h" +#include "brpc/errno.pb.h" +#include "brpc/span.h" +#include "brpc/builtin/rpcz_service.h" + + +namespace brpc { + +// Defined in span.cpp +bool has_span_db(); + +DEFINE_bool(enable_rpcz, false, "Turn on rpcz"); +BRPC_VALIDATE_GFLAG(enable_rpcz, PassValidate); + +DEFINE_bool(rpcz_hex_log_id, false, "Show log_id in hexadecimal"); +BRPC_VALIDATE_GFLAG(rpcz_hex_log_id, PassValidate); + +struct Hex { + explicit Hex(uint64_t val) : _val(val) {} + uint64_t _val; +}; + +inline std::ostream& operator<<(std::ostream& os, const Hex& h) { + const std::ios::fmtflags old_flags = + os.setf(std::ios::hex, std::ios::basefield); + os << h._val; + os.flags(old_flags); + return os; +} + +void RpczService::enable(::google::protobuf::RpcController* cntl_base, + const ::brpc::RpczRequest*, + ::brpc::RpczResponse*, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller *cntl = static_cast(cntl_base); + const bool use_html = UseHTML(cntl->http_request()); + cntl->http_response().set_content_type( + use_html ? "text/html" : "text/plain"); + if (!GFLAGS_NAMESPACE::SetCommandLineOption("enable_rpcz", "true").empty()) { + if (use_html) { + // Redirect to /rpcz + cntl->response_attachment().append( + "" + "" + ""); + } + cntl->response_attachment().append("rpcz is enabled"); + } else { + if (use_html) { + cntl->response_attachment().append(""); + } + cntl->response_attachment().append("Fail to set --enable_rpcz"); + } + if (use_html) { + cntl->response_attachment().append(""); + } +} + +void RpczService::disable(::google::protobuf::RpcController* cntl_base, + const ::brpc::RpczRequest*, + ::brpc::RpczResponse*, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller *cntl = static_cast(cntl_base); + const bool use_html = UseHTML(cntl->http_request()); + cntl->http_response().set_content_type( + use_html ? "text/html" : "text/plain"); + if (!GFLAGS_NAMESPACE::SetCommandLineOption("enable_rpcz", "false").empty()) { + if (use_html) { + // Redirect to /rpcz + cntl->response_attachment().append( + "" + "" + ""); + } + cntl->response_attachment().append("rpcz is disabled"); + } else { + if (use_html) { + cntl->response_attachment().append(""); + } + cntl->response_attachment().append("Fail to set --enable_rpcz"); + } + if (use_html) { + cntl->response_attachment().append(""); + } +} + +void RpczService::hex_log_id(::google::protobuf::RpcController* cntl_base, + const ::brpc::RpczRequest*, + ::brpc::RpczResponse*, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller *cntl = static_cast(cntl_base); + cntl->http_response().set_content_type("text/plain"); + FLAGS_rpcz_hex_log_id = true; + cntl->response_attachment().append("log_id is hexadecimal"); +} + +void RpczService::dec_log_id(::google::protobuf::RpcController* cntl_base, + const ::brpc::RpczRequest*, + ::brpc::RpczResponse*, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller *cntl = static_cast(cntl_base); + cntl->http_response().set_content_type("text/plain"); + FLAGS_rpcz_hex_log_id = false; + cntl->response_attachment().append("log_id is decimal"); +} + +void RpczService::stats(::google::protobuf::RpcController* cntl_base, + const ::brpc::RpczRequest*, + ::brpc::RpczResponse*, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller *cntl = static_cast(cntl_base); + cntl->http_response().set_content_type("text/plain"); + + if (!FLAGS_enable_rpcz && !has_span_db()) { + cntl->response_attachment().append( + "rpcz is not enabled yet. You can turn on/off rpcz by accessing " + "/rpcz/enable and /rpcz/disable respectively"); + return; + } + + butil::IOBufBuilder os; + DescribeSpanDB(os); + os.move_to(cntl->response_attachment()); +} + +inline void PrintRealTime(std::ostream& os, int64_t tm) { + char buf[16]; + const time_t tm_s = tm / 1000000L; + struct tm lt; + strftime(buf, sizeof(buf), "%H:%M:%S.", localtime_r(&tm_s, <)); + const char old_fill = os.fill('0'); + os << buf << std::setw(6) << tm % 1000000L; + os.fill(old_fill); +} + +static void PrintElapse(std::ostream& os, int64_t cur_time, + int64_t* last_time) { + const int64_t elp = cur_time - *last_time; + *last_time = cur_time; + if (elp < 0) { + os << std::fixed << std::setw(11) << std::setprecision(6) + << elp / 1000000.0; + } else { + if (elp >= 1000000L) { + os << std::setw(4) << elp / 1000000L << '.'; + } else { + os << " ."; + } + os << std::setw(6) << (elp % 1000000L); + } +} + +static void PrintAnnotations( + std::ostream& os, int64_t cur_time, int64_t* last_time, + SpanInfoExtractor** extractors, int num_extr, const RpczSpan* span) { + int64_t anno_time; + std::string a; + const char* span_type_str = "Span"; + if (span) { + switch (span->type()) { + case SPAN_TYPE_SERVER: + span_type_str = "ServerSpan"; + break; + case SPAN_TYPE_CLIENT: + span_type_str = "ClientSpan"; + break; + case SPAN_TYPE_BTHREAD: + span_type_str = "BthreadSpan"; + break; + } + } + + // TODO: Going through all extractors is not strictly correct because + // later extractors may have earlier annotations. + for (int i = 0; i < num_extr; ++i) { + while (extractors[i]->PopAnnotation(cur_time, &anno_time, &a)) { + PrintRealTime(os, anno_time); + PrintElapse(os, anno_time, last_time); + os << ' '; + if (span) { + const char* short_type = "SPAN"; + if (span->type() == SPAN_TYPE_SERVER) { + short_type = "Server"; + } else if (span->type() == SPAN_TYPE_CLIENT) { + short_type = "Client"; + } else if (span->type() == SPAN_TYPE_BTHREAD) { + short_type = "Bthread"; + } + os << '[' << short_type << " SPAN#" << Hex(span->span_id()) << "] "; + } + os << WebEscape(a); + if (a.empty() || butil::back_char(a) != '\n') { + os << '\n'; + } + } + } +} + +static bool PrintAnnotationsAndRealTimeSpan( + std::ostream& os, int64_t cur_time, int64_t* last_time, + SpanInfoExtractor** extr, int num_extr, const RpczSpan* span) { + if (cur_time == 0) { + // the field was not set. + return false; + } + PrintAnnotations(os, cur_time, last_time, extr, num_extr, span); + PrintRealTime(os, cur_time); + PrintElapse(os, cur_time, last_time); + return true; +} + +inline int64_t GetStartRealTime(const RpczSpan& span) { + return span.type() == SPAN_TYPE_SERVER ? + span.received_real_us() : span.start_send_real_us(); +} + +struct CompareByStartRealTime { + bool operator()(const RpczSpan& s1, const RpczSpan& s2) const { + return GetStartRealTime(s1) < GetStartRealTime(s2); + } +}; + +static butil::ip_t loopback_ip = butil::IP_ANY; +static int ALLOW_UNUSED init_loopback_ip_dummy = butil::str2ip("127.0.0.1", &loopback_ip); + +static void PrintClientSpan( + std::ostream& os, const RpczSpan& span, + int64_t* last_time, SpanInfoExtractor* server_extr, bool use_html) { + SpanInfoExtractor client_extr(span.info().c_str()); + int num_extr = 0; + SpanInfoExtractor* extr[2]; + if (server_extr) { + extr[num_extr++] = server_extr; + } + extr[num_extr++] = &client_extr; + if (!PrintAnnotationsAndRealTimeSpan(os, span.start_send_real_us(), + last_time, extr, num_extr, &span)) { + os << " start_send_real_us:not-set"; + } + const Protocol* protocol = FindProtocol(span.protocol()); + const char* protocol_name = (protocol ? protocol->name : "Unknown"); + const butil::EndPoint remote_side(butil::int2ip(span.remote_ip()), span.remote_port()); + butil::EndPoint abs_remote_side = remote_side; + if (abs_remote_side.ip == loopback_ip) { + abs_remote_side.ip = butil::my_ip(); + } + os << " Requesting " << WebEscape(span.full_method_name()) << '@' << remote_side + << ' ' << protocol_name << ' ' << LOG_ID_STR << '='; + if (FLAGS_rpcz_hex_log_id) { + os << Hex(span.log_id()); + } else { + os << span.log_id(); + } + os << " call_id=" << span.base_cid() + << ' ' << TRACE_ID_STR << '=' << Hex(span.trace_id()) + << ' ' << SPAN_ID_STR << '='; + if (use_html) { + os << "::max(), + last_time, extr, num_extr, &span); +} + +static void PrintClientSpan(std::ostream& os,const RpczSpan& span, + bool use_html) { + int64_t last_time = span.start_send_real_us(); + PrintClientSpan(os, span, &last_time, NULL, use_html); +} + +static void PrintBthreadSpan(std::ostream& os, const RpczSpan& span, int64_t* last_time, + SpanInfoExtractor* server_extr, bool use_html) { + SpanInfoExtractor client_extr(span.info().c_str()); + int num_extr = 0; + SpanInfoExtractor* extr[2]; + if (server_extr) { + extr[num_extr++] = server_extr; + } + extr[num_extr++] = &client_extr; + + // Print span id for bthread span context identification + os << " [Bthread SPAN#" << Hex(span.span_id()); + if (span.parent_span_id() != 0) { + os << " parent#" << Hex(span.parent_span_id()); + } + os << "] "; + + PrintAnnotations(os, std::numeric_limits::max(), last_time, extr, num_extr, &span); +} + +static void PrintServerSpan(std::ostream& os, const RpczSpan& span, + bool use_html) { + SpanInfoExtractor server_extr(span.info().c_str()); + SpanInfoExtractor* extr[1] = { &server_extr }; + int64_t last_time = span.received_real_us(); + const butil::EndPoint remote_side( + butil::int2ip(span.remote_ip()), span.remote_port()); + PrintRealDateTime(os, last_time); + const Protocol* protocol = FindProtocol(span.protocol()); + const char* protocol_name = (protocol ? protocol->name : "Unknown"); + os << " Received request(" << span.request_size() << ") from " + << remote_side << ' ' << protocol_name << ' ' << LOG_ID_STR << '='; + if (FLAGS_rpcz_hex_log_id) { + os << Hex(span.log_id()); + } else { + os << span.log_id(); + } + // TODO: We can't hyperlink parent_span now because there's no generic + // way to get the port of upstream server yet. + os << ' ' << TRACE_ID_STR << '=' << Hex(span.trace_id()) + << ' ' << SPAN_ID_STR << '=' << Hex(span.span_id()); + if (span.parent_span_id() != 0) { + os << " parent_span=" << Hex(span.parent_span_id()); + } + os << std::endl; + if (PrintAnnotationsAndRealTimeSpan( + os, span.start_parse_real_us(), + &last_time, extr, ARRAY_SIZE(extr), &span)) { + os << " [Server SPAN#" << Hex(span.span_id()) << "] Processing the request in a new bthread" << std::endl; + } + + bool entered_user_method = false; + if (PrintAnnotationsAndRealTimeSpan( + os, span.start_callback_real_us(), + &last_time, extr, ARRAY_SIZE(extr), &span)) { + entered_user_method = true; + os << " [Server SPAN#" << Hex(span.span_id()) << "] Enter " << WebEscape(span.full_method_name()) << std::endl; + } + + const int nclient = span.client_spans_size(); + for (int i = 0; i < nclient; ++i) { + auto& client_span = span.client_spans(i); + if (client_span.type() == SPAN_TYPE_CLIENT) { + PrintClientSpan(os, client_span, &last_time, &server_extr, use_html); + } else { + PrintBthreadSpan(os, client_span, &last_time, &server_extr, use_html); + } + } + + if (PrintAnnotationsAndRealTimeSpan( + os, span.start_send_real_us(), + &last_time, extr, ARRAY_SIZE(extr), &span)) { + if (entered_user_method) { + os << " [Server SPAN#" << Hex(span.span_id()) << "] Leave " << WebEscape(span.full_method_name()) << std::endl; + } else { + os << " [Server SPAN#" << Hex(span.span_id()) << "] Responding" << std::endl; + } + } + + if (PrintAnnotationsAndRealTimeSpan( + os, span.sent_real_us(), + &last_time, extr, ARRAY_SIZE(extr), &span)) { + os << " [Server SPAN#" << Hex(span.span_id()) << "] Responded(" << span.response_size() << ')' << std::endl; + } + + PrintAnnotations(os, std::numeric_limits::max(), + &last_time, extr, ARRAY_SIZE(extr), &span); +} + +class RpczSpanFilter : public SpanFilter { +public: + RpczSpanFilter() + : _min_latency(std::numeric_limits::min()) + , _min_request_size(std::numeric_limits::min()) + , _min_response_size(std::numeric_limits::min()) + , _log_id(0) + , _check_log_id(false) + , _check_error_code(false) + , _error_code(0) + {} + + void CheckLatency(int64_t min_latency) { _min_latency = min_latency; } + + void CheckRequest(int64_t min_request_size) { + _min_request_size = min_request_size; + } + + void CheckResponse(int64_t min_response_size) { + _min_response_size = min_response_size; + } + + void CheckLogId(uint64_t log_id) { + _check_log_id = true; + _log_id = log_id; + } + + void CheckErrorCode(int error_code) { + _check_error_code = true; + _error_code = error_code; + } + + bool Keep(const BriefSpan& span) { + return span.latency_us() >= _min_latency && + span.request_size() >= _min_request_size && + span.response_size() >= _min_response_size && + (!_check_log_id || span.log_id() == _log_id) && + (!_check_error_code || span.error_code() == _error_code); + } + +private: + int64_t _min_latency; + int _min_request_size; + int _min_response_size; + uint64_t _log_id; + bool _check_log_id; + bool _check_error_code; + int _error_code; +}; + +static int64_t ParseDateTime(const std::string& time_str) { + struct tm timeinfo; + int64_t microseconds = 999999; + char* endptr = strptime(time_str.c_str(), "%Y/%m/%d-%H:%M:%S", &timeinfo); + if (endptr == NULL) { + time_t now; + time(&now); + if (localtime_r(&now, &timeinfo) == NULL) { + return -1; + } + endptr = strptime(time_str.c_str(), "%H:%M:%S", &timeinfo); + if (endptr == NULL) { + return -1; + } + } + if (*endptr == '.') { + char* endptr2; + microseconds = strtol(endptr + 1, &endptr2, 10); + if (*endptr2 != '\0') { + microseconds = 999999; + } + } + return timelocal(&timeinfo) * 1000000L + microseconds; +} + +static bool ParseUint64(const std::string* str, uint64_t* val) { + if (NULL == str) { + return false; + } + const char* p = str->c_str(); + char* endptr = NULL; + if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { + *val = strtoull(p + 2, &endptr, 16); + return (*endptr == '\0'); + } + *val = strtoull(p, &endptr, 10); + if (*endptr == '\0') { + return true; + } + if ((*endptr >= 'a' && *endptr <= 'f') || + (*endptr >= 'A' && *endptr <= 'F')) { + *val = strtoull(p, &endptr, 16); + return (*endptr == '\0'); + } + return false; +} + +void RpczService::default_method(::google::protobuf::RpcController* cntl_base, + const ::brpc::RpczRequest*, + ::brpc::RpczResponse*, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller *cntl = static_cast(cntl_base); + uint64_t trace_id = 0; + const bool use_html = UseHTML(cntl->http_request()); + cntl->http_response().set_content_type( + use_html ? "text/html" : "text/plain"); + + butil::IOBufBuilder os; + if (use_html) { + os << "\n" + << "\n" + << TabsHead() + << ""; + cntl->server()->PrintTabsBody(os, "rpcz"); + } + + if (!FLAGS_enable_rpcz && !has_span_db()) { + if (use_html) { + os << "" + " rpcz to track recent RPC calls with small overhead, " + "you can turn it off at any time."; + } else { + os << "rpcz is not enabled yet. You can turn on/off rpcz by accessing " + "/rpcz/enable and /rpcz/disable respectively."; + } + os.move_to(cntl->response_attachment()); + return; + } + butil::EndPoint my_addr(butil::my_ip(), + cntl->server()->listen_address().port); + + const std::string* trace_id_str = + cntl->http_request().uri().GetQuery(TRACE_ID_STR); + if (trace_id_str) { + char* endptr; + trace_id = strtoull(trace_id_str->c_str(), &endptr, 16); + if (*endptr != '\0') { + trace_id = 0; + } + } + if (trace_id != 0) { // Point search + uint64_t span_id = 0; + const std::string* span_id_str = + cntl->http_request().uri().GetQuery(SPAN_ID_STR); + if (span_id_str) { + char* endptr; + span_id = strtoull(span_id_str->c_str(), &endptr, 16); + if (*endptr != '\0') { + span_id = 0; + } + } + std::deque spans; + if (span_id == 0) { + FindSpans(trace_id, &spans); + std::sort(spans.begin(), spans.end(), CompareByStartRealTime()); + } else { + spans.resize(1); + if (FindSpan(trace_id, span_id, &spans[0]) != 0) { + spans.clear(); + } + } + if (spans.empty()) { + os << "Fail to find any spans" + << (use_html ? "" : ""); + os.move_to(cntl->response_attachment()); + return; + } + if (use_html) { + os << "
\n";
+        }
+        for (size_t i = 0; i < spans.size(); ++i) {
+            RpczSpan& span = spans[i];
+            if (span.type() == SPAN_TYPE_SERVER) {
+                PrintServerSpan(os, span, use_html);
+            } else {
+                PrintClientSpan(os, span, use_html);
+            }
+            os << std::endl;
+        }
+        if (use_html) {
+            os << "
"; + } + os.move_to(cntl->response_attachment()); + return; + } else { + const std::string* time_str = + cntl->http_request().uri().GetQuery(TIME_STR); + int64_t start_tm; + if (time_str == NULL) { + start_tm = butil::gettimeofday_us(); + } else { + start_tm = ParseDateTime(*time_str); + if (start_tm < 0) { + os << "Invalid " << TIME_STR << "=`" << time_str << '\'' + << (use_html ? "" : ""); + os.move_to(cntl->response_attachment()); + return; + } + } + int max_count = 100; + const std::string* max_scan_str = + cntl->http_request().uri().GetQuery(MAX_SCAN_STR); + if (max_scan_str) { + char* endptr; + int max_count2 = strtol(max_scan_str->c_str(), &endptr, 10); + if (*endptr == '\0') { + max_count = max_count2; + } + } + + // Set up SpanFilter. + RpczSpanFilter filter; + const std::string* min_latency_str = + cntl->http_request().uri().GetQuery(MIN_LATENCY_STR); + if (min_latency_str) { + char* endptr; + filter.CheckLatency(strtoll(min_latency_str->c_str(), &endptr, 10)); + } + const std::string* min_reqsize_str = + cntl->http_request().uri().GetQuery(MIN_REQUEST_SIZE_STR); + if (min_reqsize_str) { + char* endptr; + filter.CheckRequest(strtol(min_reqsize_str->c_str(), &endptr, 10)); + } + const std::string* min_respsize_str = + cntl->http_request().uri().GetQuery(MIN_RESPONSE_SIZE_STR); + if (min_respsize_str) { + char* endptr; + filter.CheckResponse(strtol(min_respsize_str->c_str(), &endptr, 10)); + } + uint64_t log_id = 0; + if (ParseUint64(cntl->http_request().uri().GetQuery(LOG_ID_STR), + &log_id)) { + filter.CheckLogId(log_id); + } + const std::string* error_code_str = + cntl->http_request().uri().GetQuery(ERROR_CODE_STR); + if (error_code_str) { + char* endptr; + filter.CheckErrorCode(strtol(error_code_str->c_str(), &endptr, 10)); + } + + max_count = std::max(std::min(max_count, 10000), 1); + std::deque spans; + ListSpans(start_tm, max_count, &spans, &filter); + if (spans.empty()) { + os << "Fail to find matched spans" + << (use_html ? "" : ""); + os.move_to(cntl->response_attachment()); + return; + } + if (use_html) { + const char* action = (FLAGS_enable_rpcz ? "disable" : "enable"); + os << "
" "
\n";
+        }
+        for (size_t i = 0; i < spans.size(); ++i) {
+            BriefSpan& span = spans[i];
+            int64_t last_time = span.start_real_us();
+            PrintRealDateTime(os, last_time);
+            PrintElapse(os, span.latency_us() + last_time, &last_time);
+            os << ' ' << (span.type() == SPAN_TYPE_SERVER ? 'S' : 'C')
+               << ' ' << TRACE_ID_STR << '=';
+            if (use_html) {
+                os << "";
+        }
+        os.move_to(cntl->response_attachment());
+    }
+}
+
+void RpczService::GetTabInfo(TabInfoList* info_list) const {
+    TabInfo* info = info_list->add();
+    info->path = "/rpcz";
+    info->tab_name = "rpcz";
+}
+
+} // namespace brpc
diff --git a/src/brpc/builtin/rpcz_service.h b/src/brpc/builtin/rpcz_service.h
new file mode 100644
index 0000000..3254050
--- /dev/null
+++ b/src/brpc/builtin/rpcz_service.h
@@ -0,0 +1,66 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+
+#ifndef BRPC_RPCZ_SERVICE_H
+#define BRPC_RPCZ_SERVICE_H
+
+#include "brpc/builtin_service.pb.h"
+#include "brpc/builtin/tabbed.h"
+
+
+namespace brpc {
+
+class RpczService : public rpcz, public Tabbed {
+public:
+    void enable(::google::protobuf::RpcController* cntl_base,
+                const ::brpc::RpczRequest* request,
+                ::brpc::RpczResponse* response,
+                ::google::protobuf::Closure* done);
+
+    void disable(::google::protobuf::RpcController* cntl_base,
+                 const ::brpc::RpczRequest* request,
+                 ::brpc::RpczResponse* response,
+                 ::google::protobuf::Closure* done);
+
+    void stats(::google::protobuf::RpcController* cntl_base,
+               const ::brpc::RpczRequest* request,
+               ::brpc::RpczResponse* response,
+               ::google::protobuf::Closure* done);
+
+    void hex_log_id(::google::protobuf::RpcController* cntl_base,
+                    const ::brpc::RpczRequest* request,
+                    ::brpc::RpczResponse* response,
+                    ::google::protobuf::Closure* done);
+
+    void dec_log_id(::google::protobuf::RpcController* cntl_base,
+                    const ::brpc::RpczRequest* request,
+                    ::brpc::RpczResponse* response,
+                    ::google::protobuf::Closure* done);
+
+    void default_method(::google::protobuf::RpcController* cntl_base,
+                        const ::brpc::RpczRequest* request,
+                        ::brpc::RpczResponse* response,
+                        ::google::protobuf::Closure* done);
+
+    void GetTabInfo(brpc::TabInfoList*) const;
+};
+
+} // namespace brpc
+
+
+#endif // BRPC_RPCZ_SERVICE_H
diff --git a/src/brpc/builtin/sockets_service.cpp b/src/brpc/builtin/sockets_service.cpp
new file mode 100644
index 0000000..deedf65
--- /dev/null
+++ b/src/brpc/builtin/sockets_service.cpp
@@ -0,0 +1,55 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 
+#include "brpc/closure_guard.h"        // ClosureGuard
+#include "brpc/controller.h"           // Controller
+#include "brpc/builtin/common.h"
+#include "brpc/builtin/sockets_service.h"
+#include "brpc/socket.h"
+
+
+namespace brpc {
+
+void SocketsService::default_method(::google::protobuf::RpcController* cntl_base,
+                                     const ::brpc::SocketsRequest*,
+                                     ::brpc::SocketsResponse*,
+                                     ::google::protobuf::Closure* done) {
+    ClosureGuard done_guard(done);
+    Controller *cntl = static_cast(cntl_base);
+    cntl->http_response().set_content_type("text/plain");
+    butil::IOBufBuilder os;
+    const std::string& constraint = cntl->http_request().unresolved_path();
+    
+    if (constraint.empty()) {
+        os << "# Use /sockets/\n"
+           << butil::describe_resources() << '\n';
+    } else {
+        char* endptr = NULL;
+        SocketId sid = strtoull(constraint.c_str(), &endptr, 10);
+        if (*endptr == '\0' || *endptr == '/') {
+            Socket::DebugSocket(os, sid);
+        } else {
+            cntl->SetFailed(ENOMETHOD, "path=%s is not a SocketId",
+                            constraint.c_str());
+        }
+    }
+    os.move_to(cntl->response_attachment());
+}
+
+} // namespace brpc
diff --git a/src/brpc/builtin/sockets_service.h b/src/brpc/builtin/sockets_service.h
new file mode 100644
index 0000000..ff9926b
--- /dev/null
+++ b/src/brpc/builtin/sockets_service.h
@@ -0,0 +1,38 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+
+#ifndef BRPC_SOCKETS_SERVICE_H
+#define BRPC_SOCKETS_SERVICE_H
+
+#include "brpc/builtin_service.pb.h"
+
+
+namespace brpc {
+
+class SocketsService : public sockets {
+public:
+    void default_method(::google::protobuf::RpcController* cntl_base,
+                        const ::brpc::SocketsRequest* request,
+                        ::brpc::SocketsResponse* response,
+                        ::google::protobuf::Closure* done);
+};
+
+} // namespace brpc
+
+
+#endif // BRPC_SOCKETS_SERVICE_H
diff --git a/src/brpc/builtin/sorttable_js.cpp b/src/brpc/builtin/sorttable_js.cpp
new file mode 100644
index 0000000..a37bac7
--- /dev/null
+++ b/src/brpc/builtin/sorttable_js.cpp
@@ -0,0 +1,529 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 
+#include "brpc/builtin/sorttable_js.h"
+
+
+namespace brpc {
+
+static pthread_once_t s_sorttable_buf_once = PTHREAD_ONCE_INIT; 
+static butil::IOBuf* s_sorttable_buf = NULL;
+static void InitSortTableBuf() {
+    s_sorttable_buf = new butil::IOBuf;
+    s_sorttable_buf->append(sorttable_js());
+}
+const butil::IOBuf& sorttable_js_iobuf() {
+    pthread_once(&s_sorttable_buf_once, InitSortTableBuf);
+    return *s_sorttable_buf;
+}
+
+/*
+  SortTable
+  version 2
+  7th April 2007
+  Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/
+
+  Instructions:
+  Download this file
+  Add  to your HTML
+  Add class=\"sortable\" to any table you'd like to make sortable
+  Click on the headers to sort
+
+  Thanks to many, many people for contributions and suggestions.
+  Licenced as X11: http://www.kryogenix.org/code/browser/licence.html
+  This basically means: do what you want with it.
+*/
+
+const char* sorttable_js() {
+    return "var stIsIE = /*@cc_on!@*/false;\n"
+"sorttable = {\n"
+"  init: function() {\n"
+"    // quit if this function has already been called\n"
+"    if (arguments.callee.done) return;\n"
+"    // flag this function so we don't do the same thing twice\n"
+"    arguments.callee.done = true;\n"
+"    // kill the timer\n"
+"    if (_timer) clearInterval(_timer);\n"
+"\n"
+"    if (!document.createElement || !document.getElementsByTagName) return;\n"
+"\n"
+"    sorttable.DATE_RE = /^(\\d\\d?)[\\/\\.-](\\d\\d?)[\\/\\.-]((\\d\\d)?\\d\\d)$/;\n"
+"\n"
+"    forEach(document.getElementsByTagName('table'), function(table) {\n"
+"      if (table.className.search(/\\bsortable\\b/) != -1) {\n"
+"        sorttable.makeSortable(table);\n"
+"      }\n"
+"    });\n"
+"\n"
+"  },\n"
+"\n"
+"  makeSortable: function(table) {\n"
+"    if (table.getElementsByTagName('thead').length == 0) {\n"
+"      // table doesn't have a tHead. Since it should have, create one and\n"
+"      // put the first table row in it.\n"
+"      the = document.createElement('thead');\n"
+"      the.appendChild(table.rows[0]);\n"
+"      table.insertBefore(the,table.firstChild);\n"
+"    }\n"
+"    // Safari doesn't support table.tHead, sigh\n"
+"    if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0];\n"
+"\n"
+"    if (table.tHead.rows.length != 1) return; // can't cope with two header rows\n"
+"\n"
+"    // Sorttable v1 put rows with a class of \"sortbottom\" at the bottom (as\n"
+"    // \"total\" rows, for example). This is B&R, since what you're supposed\n"
+"    // to do is put them in a tfoot. So, if there are sortbottom rows,\n"
+"    // for backwards compatibility, move them to tfoot (creating it if needed).\n"
+"    sortbottomrows = [];\n"
+"    for (var i=0; i5' :\n"
+"' ▴';\n"
+"            this.appendChild(sortrevind);\n"
+"            return;\n"
+"          }\n"
+"          if (this.className.search(/\\bsorttable_sorted_reverse\\b/) != -1) {\n"
+"            // if we're already sorted by this column in reverse, just\n"
+"            // re-reverse the table, which is quicker\n"
+"            sorttable.reverse(this.sorttable_tbody);\n"
+"            this.className = this.className.replace('sorttable_sorted_reverse',\n"
+"                                                    'sorttable_sorted');\n"
+"            this.removeChild(document.getElementById('sorttable_sortrevind'));\n"
+"            sortfwdind = document.createElement('span');\n"
+"            sortfwdind.id = \"sorttable_sortfwdind\";\n"
+"            sortfwdind.innerHTML = stIsIE ? ' 6' :\n"
+"' ▾';\n"
+"            this.appendChild(sortfwdind);\n"
+"            return;\n"
+"          }\n"
+"\n"
+"          // remove sorttable_sorted classes\n"
+"          theadrow = this.parentNode;\n"
+"          forEach(theadrow.childNodes, function(cell) {\n"
+"            if (cell.nodeType == 1) { // an element\n"
+"              cell.className = cell.className.replace('sorttable_sorted_reverse','');\n"
+"              cell.className = cell.className.replace('sorttable_sorted','');\n"
+"            }\n"
+"          });\n"
+"          sortfwdind = document.getElementById('sorttable_sortfwdind');\n"
+"          if (sortfwdind) { sortfwdind.parentNode.removeChild(sortfwdind); }\n"
+"          sortrevind = document.getElementById('sorttable_sortrevind');\n"
+"          if (sortrevind) { sortrevind.parentNode.removeChild(sortrevind); }\n"
+"\n"
+"          this.className += ' sorttable_sorted';\n"
+"          sortfwdind = document.createElement('span');\n"
+"          sortfwdind.id = \"sorttable_sortfwdind\";\n"
+"          sortfwdind.innerHTML = stIsIE ? ' 6' : ' ▾';\n"
+"          this.appendChild(sortfwdind);\n"
+"\n"
+"            // build an array to sort. This is a Schwartzian transform thing,\n"
+"            // i.e., we \"decorate\" each row with the actual sort key,\n"
+"            // sort based on the sort keys, and then put the rows back in order\n"
+"            // which is a lot faster because you only do getInnerText once per row\n"
+"            row_array = [];\n"
+"            col = this.sorttable_columnindex;\n"
+"            rows = this.sorttable_tbody.rows;\n"
+"            for (var j=0; j 12) {\n"
+"            // definitely dd/mm\n"
+"            return sorttable.sort_ddmm;\n"
+"          } else if (second > 12) {\n"
+"            return sorttable.sort_mmdd;\n"
+"          } else {\n"
+"            // looks like a date, but we can't tell which, so assume\n"
+"            // that it's dd/mm (English imperialism!) and keep looking\n"
+"            sortfn = sorttable.sort_ddmm;\n"
+"          }\n"
+"        }\n"
+"      }\n"
+"    }\n"
+"    return sortfn;\n"
+"  },\n"
+"\n"
+"  getInnerText: function(node) {\n"
+"    // gets the text we want to use for sorting for a cell.\n"
+"    // strips leading and trailing whitespace.\n"
+"    // this is *not* a generic getInnerText function; it's special to sorttable.\n"
+"    // for example, you can override the cell text with a customkey attribute.\n"
+"    // it also gets .value for  fields.\n"
+"\n"
+"    if (!node) return \"\";\n"
+"\n"
+"    hasInputs = (typeof node.getElementsByTagName == 'function') &&\n"
+"                 node.getElementsByTagName('input').length;\n"
+"\n"
+"    if (node.getAttribute(\"sorttable_customkey\") != null) {\n"
+"      return node.getAttribute(\"sorttable_customkey\");\n"
+"    }\n"
+"    else if (typeof node.textContent != 'undefined' && !hasInputs) {\n"
+"      return node.textContent.replace(/^\\s+|\\s+$/g, '');\n"
+"    }\n"
+"    else if (typeof node.innerText != 'undefined' && !hasInputs) {\n"
+"      return node.innerText.replace(/^\\s+|\\s+$/g, '');\n"
+"    }\n"
+"    else if (typeof node.text != 'undefined' && !hasInputs) {\n"
+"      return node.text.replace(/^\\s+|\\s+$/g, '');\n"
+"    }\n"
+"    else {\n"
+"      switch (node.nodeType) {\n"
+"        case 3:\n"
+"          if (node.nodeName.toLowerCase() == 'input') {\n"
+"            return node.value.replace(/^\\s+|\\s+$/g, '');\n"
+"          }\n"
+"        case 4:\n"
+"          return node.nodeValue.replace(/^\\s+|\\s+$/g, '');\n"
+"          break;\n"
+"        case 1:\n"
+"        case 11:\n"
+"          var innerText = '';\n"
+"          for (var i = 0; i < node.childNodes.length; i++) {\n"
+"            innerText += sorttable.getInnerText(node.childNodes[i]);\n"
+"          }\n"
+"          return innerText.replace(/^\\s+|\\s+$/g, '');\n"
+"          break;\n"
+"        default:\n"
+"          return '';\n"
+"      }\n"
+"    }\n"
+"  },\n"
+"\n"
+"  reverse: function(tbody) {\n"
+"    // reverse the rows in a tbody\n"
+"    newrows = [];\n"
+"    for (var i=0; i=0; i--) {\n"
+"       tbody.appendChild(newrows[i]);\n"
+"    }\n"
+"    delete newrows;\n"
+"  },\n"
+"\n"
+"  sort_numeric: function(a,b) {\n"
+"    aa = parseFloat(a[0].replace(/[^0-9.-]/g,''));\n"
+"    if (isNaN(aa)) aa = 0;\n"
+"    bb = parseFloat(b[0].replace(/[^0-9.-]/g,''));\n"
+"    if (isNaN(bb)) bb = 0;\n"
+"    return aa-bb;\n"
+"  },\n"
+"  sort_alpha: function(a,b) {\n"
+"    if (a[0]==b[0]) return 0;\n"
+"    if (a[0] 0 ) {\n"
+"                var q = list[i]; list[i] = list[i+1]; list[i+1] = q;\n"
+"                swap = true;\n"
+"            }\n"
+"        } // for\n"
+"        t--;\n"
+"\n"
+"        if (!swap) break;\n"
+"\n"
+"        for(var i = t; i > b; --i) {\n"
+"            if ( comp_func(list[i], list[i-1]) < 0 ) {\n"
+"                var q = list[i]; list[i] = list[i-1]; list[i-1] = q;\n"
+"                swap = true;\n"
+"            }\n"
+"        } // for\n"
+"        b++;\n"
+"\n"
+"    } // while(swap)\n"
+"  }\n"
+"}\n"
+"\n"
+"/* ******************************************************************\n"
+"   Supporting functions: bundled here to avoid depending on a library\n"
+"   ****************************************************************** */\n"
+"\n"
+"// Dean Edwards/Matthias Miller/John Resig\n"
+"\n"
+"/* for Mozilla/Opera9 */\n"
+"if (document.addEventListener) {\n"
+"    document.addEventListener(\"DOMContentLoaded\", sorttable.init, false);\n"
+"}\n"
+"\n"
+"/* for Internet Explorer */\n"
+"/*@cc_on @*/\n"
+"/*@if (@_win32)\n"
+"    document.write(\"\n"
+//          << brpc::TabsHead() << "";
+//       cntl->server()->PrintTabsBody(os, "my_tab");
+//     }
+//     ...
+//     if (use_html) {
+//       os << "";
+//     }
+//   }
+// Note: don't forget the jquery.
+class Tabbed {
+public:
+    virtual ~Tabbed() = default;
+    virtual void GetTabInfo(TabInfoList* info_list) const = 0;
+};
+
+} // namespace brpc
+
+
+#endif // BRPC_BUILTIN_TABBED_H
diff --git a/src/brpc/builtin/threads_service.cpp b/src/brpc/builtin/threads_service.cpp
new file mode 100644
index 0000000..458c1c3
--- /dev/null
+++ b/src/brpc/builtin/threads_service.cpp
@@ -0,0 +1,53 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 "butil/time.h"
+#include "butil/logging.h"
+#include "butil/popen.h"
+#include "brpc/controller.h"           // Controller
+#include "brpc/closure_guard.h"        // ClosureGuard
+#include "brpc/builtin/threads_service.h"
+#include "brpc/builtin/common.h"
+#include "butil/string_printf.h"
+
+namespace brpc {
+
+void ThreadsService::default_method(::google::protobuf::RpcController* cntl_base,
+                                    const ::brpc::ThreadsRequest*,
+                                    ::brpc::ThreadsResponse*,
+                                    ::google::protobuf::Closure* done) {
+    ClosureGuard done_guard(done);
+    Controller *cntl = static_cast(cntl_base);
+    cntl->http_response().set_content_type("text/plain");
+    butil::IOBuf& resp = cntl->response_attachment();
+
+    std::string cmd = butil::string_printf("pstack %lld", (long long)getpid());
+    butil::Timer tm;
+    tm.start();
+    butil::IOBufBuilder pstack_output;
+    const int rc = butil::read_command_output(pstack_output, cmd.c_str());
+    if (rc < 0) {
+        LOG(ERROR) << "Fail to popen `" << cmd << "'";
+        return;
+    }
+    pstack_output.move_to(resp);
+    tm.stop();
+    resp.append(butil::string_printf("\n\ntime=%" PRId64 "ms", tm.m_elapsed()));
+}
+
+} // namespace brpc
diff --git a/src/brpc/builtin/threads_service.h b/src/brpc/builtin/threads_service.h
new file mode 100644
index 0000000..d9978e1
--- /dev/null
+++ b/src/brpc/builtin/threads_service.h
@@ -0,0 +1,39 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+
+#ifndef  BRPC_THREADS_SERVICE_H
+#define  BRPC_THREADS_SERVICE_H
+
+#include 
+#include "brpc/builtin_service.pb.h"
+
+
+namespace brpc {
+
+class ThreadsService : public threads {
+public:
+    void default_method(::google::protobuf::RpcController* cntl_base,
+                        const ::brpc::ThreadsRequest* request,
+                        ::brpc::ThreadsResponse* response,
+                        ::google::protobuf::Closure* done);
+};
+
+} // namespace brpc
+
+
+#endif  //BRPC_THREADS_SERVICE_H
diff --git a/src/brpc/builtin/vars_service.cpp b/src/brpc/builtin/vars_service.cpp
new file mode 100644
index 0000000..00235e2
--- /dev/null
+++ b/src/brpc/builtin/vars_service.cpp
@@ -0,0 +1,438 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 
+#include                            // std::vector
+#include "butil/string_splitter.h"
+#include "bvar/bvar.h"
+
+#include "brpc/closure_guard.h"        // ClosureGuard
+#include "brpc/controller.h"           // Controller
+#include "brpc/errno.pb.h"
+#include "brpc/server.h"
+#include "brpc/builtin/common.h"
+#include "brpc/builtin/vars_service.h"
+
+namespace bvar {
+DECLARE_bool(quote_vector);
+}
+
+
+namespace brpc {
+
+// TODO(gejun): parameterize.
+// This function returns the script to make bvar plot-able.
+// The idea: flot graphs were attached to plot-able bvar as the next 
+// when the html was generated. When user clicks a bvar, send a request to +// server to get the value series of the bvar. When the response comes back, +// plot and show the graph. Requests will be sent to server every 1 second +// until user clicks the bvar and hide the graph. +void PutVarsHeading(std::ostream& os, bool expand_all) { + os << "\n" + "\n" + << TabsHead() + << "\n" + + "\n"; +} + +// We need the space before colon so that user does not have to remove +// trailing colon from $1 +static const std::string VAR_SEP = " : "; + +class VarsDumper : public bvar::Dumper { +public: + explicit VarsDumper(butil::IOBufBuilder& os, bool use_html) + : _os(os), _use_html(use_html) {} + + bool dump(const std::string& name, const butil::StringPiece& desc) { + bool plot = false; + if (_use_html) { + bvar::SeriesOptions series_options; + series_options.test_only = true; + const int rc = bvar::Variable::describe_series_exposed( + name, _os, series_options); + plot = (rc == 0); + if (plot) { + _os << "

"; + } else { + _os << "

"; + } + } + _os << name << VAR_SEP; + if (_use_html) { + _os << ""; + } + _os << desc; + if (_use_html) { + _os << "

\n"; + if (plot) { + _os << "
\n"; + } + } else { + _os << "\r\n"; + } + return true; + } + + void move_to(butil::IOBuf& buf) { + _os.move_to(buf); + } + +private: + DISALLOW_COPY_AND_ASSIGN(VarsDumper); + + butil::IOBufBuilder & _os; + bool _use_html; +}; + +void VarsService::default_method(::google::protobuf::RpcController* cntl_base, + const ::brpc::VarsRequest*, + ::brpc::VarsResponse*, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller *cntl = static_cast(cntl_base); + if (cntl->http_request().uri().GetQuery("series") != NULL) { + butil::IOBufBuilder os; + bvar::SeriesOptions series_options; + const int rc = bvar::Variable::describe_series_exposed( + cntl->http_request().unresolved_path(), os, series_options); + if (rc == 0) { + cntl->http_response().set_content_type("application/json"); + os.move_to(cntl->response_attachment()); + } else if (rc < 0) { + cntl->SetFailed(ENOMETHOD, "Fail to find any bvar by `%s'", + cntl->http_request().unresolved_path().c_str()); + } else { + cntl->SetFailed(ENODATA, "`%s' does not have value series", + cntl->http_request().unresolved_path().c_str()); + } + return; + } + const bool use_html = UseHTML(cntl->http_request()); + bool with_tabs = false; + if (use_html && cntl->http_request().uri().GetQuery("dataonly") == NULL) { + with_tabs = true; + } + cntl->http_response().set_content_type( + use_html ? "text/html" : "text/plain"); + + butil::IOBufBuilder os; + if (with_tabs) { + os << "\n" + "\n"; + PutVarsHeading(os, cntl->http_request().uri().GetQuery("expand")); + os << "\n" + "\n\n"; + cntl->server()->PrintTabsBody(os, "vars"); + os << "

Search :

" + "
\n"; + } + VarsDumper dumper(os, use_html); + bvar::DumpOptions options; + options.question_mark = '$'; + options.display_filter = + (use_html ? bvar::DISPLAY_ON_HTML : bvar::DISPLAY_ON_PLAIN_TEXT); + options.white_wildcards = cntl->http_request().unresolved_path(); + const int ndump = bvar::Variable::dump_exposed(&dumper, &options); + if (ndump < 0) { + cntl->SetFailed("Fail to dump vars"); + return; + } + if (!options.white_wildcards.empty() && ndump == 0) { + cntl->SetFailed(ENOMETHOD, "Fail to find any bvar by `%s'", + options.white_wildcards.c_str()); + } + if (with_tabs) { + os << "
"; + } + os.move_to(cntl->response_attachment()); + cntl->set_response_compress_type(COMPRESS_TYPE_GZIP); +} + +void VarsService::GetTabInfo(TabInfoList* info_list) const { + TabInfo* info = info_list->add(); + info->path = "/vars"; + info->tab_name = "vars"; +} + +} // namespace brpc diff --git a/src/brpc/builtin/vars_service.h b/src/brpc/builtin/vars_service.h new file mode 100644 index 0000000..ed5e1f1 --- /dev/null +++ b/src/brpc/builtin/vars_service.h @@ -0,0 +1,41 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_VARS_SERVICE_H +#define BRPC_VARS_SERVICE_H + +#include "brpc/builtin_service.pb.h" +#include "brpc/builtin/tabbed.h" + + +namespace brpc { + +class VarsService : public vars, public Tabbed { +public: + void default_method(::google::protobuf::RpcController* cntl_base, + const ::brpc::VarsRequest* request, + ::brpc::VarsResponse* response, + ::google::protobuf::Closure* done); + + void GetTabInfo(TabInfoList* info_list) const; +}; + +} // namespace brpc + + +#endif // BRPC_VARS_SERVICE_H diff --git a/src/brpc/builtin/version_service.cpp b/src/brpc/builtin/version_service.cpp new file mode 100644 index 0000000..fed6dc2 --- /dev/null +++ b/src/brpc/builtin/version_service.cpp @@ -0,0 +1,41 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/controller.h" // Controller +#include "brpc/server.h" // Server +#include "brpc/closure_guard.h" // ClosureGuard +#include "brpc/builtin/version_service.h" + + +namespace brpc { + +void VersionService::default_method(::google::protobuf::RpcController* controller, + const ::brpc::VersionRequest*, + ::brpc::VersionResponse*, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller *cntl = (Controller *)controller; + cntl->http_response().set_content_type("text/plain"); + if (_server->version().empty()) { + cntl->response_attachment().append("unknown"); + } else { + cntl->response_attachment().append(_server->version()); + } +} + +} // namespace brpc diff --git a/src/brpc/builtin/version_service.h b/src/brpc/builtin/version_service.h new file mode 100644 index 0000000..eeccd7b --- /dev/null +++ b/src/brpc/builtin/version_service.h @@ -0,0 +1,45 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_VERSION_SERVICE_H +#define BRPC_VERSION_SERVICE_H + +#include +#include "brpc/builtin_service.pb.h" + + +namespace brpc { + +class Server; + +class VersionService : public version { +public: + explicit VersionService(Server* server) : _server(server) {} + + void default_method(::google::protobuf::RpcController* cntl_base, + const ::brpc::VersionRequest* request, + ::brpc::VersionResponse* response, + ::google::protobuf::Closure* done); +private: + Server* _server; +}; + +} // namespace brpc + + +#endif //BRPC_VERSION_SERVICE_H diff --git a/src/brpc/builtin/viz_min_js.cpp b/src/brpc/builtin/viz_min_js.cpp new file mode 100644 index 0000000..36970ef --- /dev/null +++ b/src/brpc/builtin/viz_min_js.cpp @@ -0,0 +1,61 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/logging.h" +#include "brpc/policy/gzip_compress.h" +#include "brpc/builtin/viz_min_js.h" + + +namespace brpc { + +static pthread_once_t s_viz_min_buf_once = PTHREAD_ONCE_INIT; +static butil::IOBuf* s_viz_min_buf = NULL; +static void InitVizMinBuf() { + s_viz_min_buf = new butil::IOBuf; + s_viz_min_buf->append(viz_min_js()); +} +const butil::IOBuf& viz_min_js_iobuf() { + pthread_once(&s_viz_min_buf_once, InitVizMinBuf); + return *s_viz_min_buf; +} + +// viz.js is huge. We separate the creation of gzip version from uncompress +// version so that at most time we only keep gzip version in memory. +static pthread_once_t s_viz_min_buf_gzip_once = PTHREAD_ONCE_INIT; +static butil::IOBuf* s_viz_min_buf_gzip = NULL; +static void InitVizMinBufGzip() { + butil::IOBuf viz_min; + viz_min.append(viz_min_js()); + s_viz_min_buf_gzip = new butil::IOBuf; + CHECK(policy::GzipCompress(viz_min, s_viz_min_buf_gzip, NULL)); +} +const butil::IOBuf& viz_min_js_iobuf_gzip() { + pthread_once(&s_viz_min_buf_gzip_once, InitVizMinBufGzip); + return *s_viz_min_buf_gzip; +} + +const char* viz_min_js() { +return "function Ub(nr){throw nr}var cc=void 0,wc=!0,xc=null,ee=!1;function bk(){return(function(){})}" + + + +"(\"undefined\"!==typeof exports&&exports!==xc?exports:this).Viz=(function(nr,rQa){function ck(a,b){var i=16;if(b>1]=r;break;case\"i32\":a[x>>2]=r;break;case\"i64\":ji=[r>>>0,Math.min(Math.floor(r/4294967296),4294967295)];a[x>>2]=ji[0];a[x+4>>2]=ji[1];break;case\"float\":ib[x>>2]=r;break;case\"double\":f[0]=r;a[x>>2]=b[0];a[x+4>>2]=b[1];break;default:Yg(\"invalid type for setValue: \"+i)}}function ki(x,r){r=r||\"i8\";\"*\"===r.charAt(r.length-1)&&(r=\"i32\");switch(r){case\"i1\":return m[x];case\"i8\":return m[x];case\"i16\":return D[x>>1];case\"i32\":return a[x>>2];case\"i64\":return a[x>>2];case\"float\":return ib[x>>2];case\"double\":return b[0]=a[x>>2],b[1]=a[x+4>>2],f[0];default:Yg(\"invalid type for setValue: \"+r)}return xc}function d(a,b,i,g){var q,Xa;\"number\"===typeof a?(q=wc,Xa=a):(q=ee,Xa=a.length);var d=\"string\"===typeof b?b:xc,i=i==cC?g:[Gb,Qa.fa,Qa.Ba][i===cc?c:i](Math.max(Xa,d?1:b.length));if(q){return li(i,Xa),i}for(q=0;q=b?2*Math.abs(1<=a){return a}var i=32>=b?Math.abs(1<=i&&(32>=b||a>i)){a=-2*i+a}return a}function pr(a){Zg++;K.monitorRunDependencies&&K.monitorRunDependencies(Zg);a?(Ae(!ek[a]),ek[a]=1,fk===xc&&\"undefined\"!==typeof setInterval&&(fk=setInterval((function(){var a=ee,b;for(b in ek){a||(a=wc,K.n(\"still waiting on run dependencies:\")),K.n(\"dependency: \"+b)}a&&K.n(\"(end of list)\")}),6e3))):K.n(\"warning: run dependency added without ID\")}function rn(a){Zg--;K.monitorRunDependencies&&K.monitorRunDependencies(Zg);a?(Ae(ek[a]),delete ek[a]):K.n(\"warning: run dependency removed without ID\");0==Zg&&(fk!==xc&&(clearInterval(fk),fk=xc),eC||qr())}function td(a,b,i){for(var g=0;gXa?1:-1}}return 0}function ka(a,b){return td(a,b,gk)}function Ve(a,b,i){for(var g=0;gXa?1:-1}}return 0}function tf(b,r,i){if(20<=i&&r%2==b%2){if(r%4==b%4){for(i=r+i;r%4;){m[b++]=m[r++]}for(var r=r>>2,b=b>>2,g=i>>2;r>=1;b>>=1;for(g=i>>1;r>2]=b}function fC(a,b,i){var g=Q.a[a];if(g){if(g.q){if(0>i){return Ea(va.i),-1}if(g.object.d){if(g.object.l){for(var q=0;qi||0>q){Ea(va.i),b=-1}else{for(var d=a.object.b;d.length>2],b[1]=a[r+(q+4)>>2],f[0]):\"i64\"==x?i=[a[r+q>>2],a[r+(q+4)>>2]]:(x=\"i32\",i=a[r+q>>2]);q+=Qa.W(x);return i}for(var g=x,q=0,Xa=[],d,c;;){var n=g;d=m[g];if(0===d){break}c=m[g+1];if(37==d){var z=ee,h=ee,e=ee,j=ee;a:for(;;){switch(c){case 43:z=wc;break;case 45:h=wc;break;case 35:e=wc;break;case 48:if(j){break a}else{j=wc;break};default:break a}g++;c=m[g+1]}var t=0;if(42==c){t=i(\"i32\"),g++,c=m[g+1]}else{for(;48<=c&&57>=c;){t=10*t+(c-48),g++,c=m[g+1]}}var u=ee;if(46==c){var w=0,u=wc;g++;c=m[g+1];if(42==c){w=i(\"i32\"),g++}else{for(;;){c=m[g+1];if(48>c||57=A&&(d=(n?dC:or)(d&Math.pow(256,A)-1,8*A));var P=Math.abs(d),n=\"\";if(100==c||105==c){C=8==A&&hk?hk.stringify(B[0],B[1],xc):dC(d,8*A).toString(10)}else{if(117==c){C=8==A&&hk?hk.stringify(B[0],B[1],wc):or(d,8*A).toString(10),d=Math.abs(d)}else{if(111==c){C=(e?\"0\":\"\")+P.toString(8)}else{if(120==c||88==c){n=e?\"0x\":\"\";if(8==A&&hk){C=(B[1]>>>0).toString(16)+(B[0]>>>0).toString(16)}else{if(0>d){d=-d;C=(P-1).toString(16);B=[];for(e=0;ed?\"-\"+n:\"+\"+n);n.length+C.lengthA&&-4<=A?(c=(103==c?\"f\":\"F\").charCodeAt(0),w-=A+1):(c=(103==c?\"e\":\"E\").charCodeAt(0),w--),A=Math.min(w,20)}if(101==c||69==c){C=d.toExponential(A),/[eE][-+]\\d$/.test(C)&&(C=C.slice(0,-1)+\"0\"+C.slice(-1))}else{if(102==c||70==c){C=d.toFixed(A)}}n=C.split(\"e\");if(u&&!e){for(;1A++;){n[0]+=\"0\"}}C=n[0]+(1d?\"-\":\"\")+\"inf\",j=ee}}for(;C.lengthc&&(C=C.toUpperCase());C.split(\"\").forEach((function(a){Xa.push(a.charCodeAt(0))}))}else{if(115==c){z=i(\"i8*\")||tQa;j=Ba(z);u&&(j=Math.min(j,w));if(!h){for(;j>2]=Xa.length}else{if(37==c){Xa.push(d)}else{for(e=n;ei&&(i+=256);for(var q=b>>2,c=g>>2,d=i|i<<8|i<<16|i<<24;q>2],z=z+Qa.W(\"void*\");a[e>>2]=q;k+=2}else{for(;;){h=r();if(0==h){return n}if(!(h in He.whiteSpace)){break}}i();if(\"%\"===x[k]){k++;for(var j=k;48<=x[k].charCodeAt(0)&&57>=x[k].charCodeAt(0);){k++}var t;k!=j&&(t=parseInt(x.slice(j,k),10));var u=j=ee,w=ee;\"l\"==x[k]?(j=wc,k++,\"l\"==x[k]&&(w=wc,k++)):\"h\"==x[k]&&(u=wc,k++);var A=x[k];k++;var B=0,e=[];if(\"f\"==A){B=0;for(h=r();0=h||C&&45==h)||\"x\"===A&&(48<=h&&57>=h||97<=h&&102>=h||65<=h&&70>=h))&&(k>=x.length||h!==x[k].charCodeAt(0))){e.push(String.fromCharCode(h)),h=r(),B++,C=ee}else{break}}i()}if(0===e.length){return 0}h=e.join(\"\");e=a[g+z>>2];z+=Qa.W(\"void*\");switch(A){case\"d\":;case\"u\":;case\"i\":u?D[e>>1]=parseInt(h,10):w?(ji=[parseInt(h,10)>>>0,Math.min(Math.floor(parseInt(h,10)/4294967296),4294967295)],a[e>>2]=ji[0],a[e+4>>2]=ji[1]):a[e>>2]=parseInt(h,10);break;case\"x\":a[e>>2]=parseInt(h,16);break;case\"f\":j?(f[0]=parseFloat(h),a[e>>2]=b[0],a[e+4>>2]=b[1]):ib[e>>2]=parseFloat(h);break;case\"s\":j=Nd(h);for(u=0;u=h){break a}h=r()}i(h)}else{if(h=r(),x[k].charCodeAt(0)!==h){i(h);break a}}k++}}}return n}function Cd(a,b,i){var g=0;return He(b,(function(){return m[a+g++]}),(function(){g--}),i)}function he(a,b){rr||(rr=Gb(4));var i;a:{i=a;var g=rr,q,c,d,f;if(0==i&&0==(i=ki(g,\"i8*\"))){i=0}else{b:for(;;){c=ki(i++,\"i8\");for(q=b;0!=(d=ki(q++,\"i8\"));){if(c==d){continue b}}break}if(0==c){dk(g,0,\"i8*\"),i=0}else{for(f=i-1;;){c=ki(i++,\"i8\");q=b;do{if((d=ki(q++,\"i8\"))==c){0==c?i=0:dk(i-1,0,\"i8\");dk(g,i,\"i8*\");i=f;break a}}while(0!=d)}Yg(\"strtok_r error!\");i=cc}}}return i}function ah(a){return a in{32:0,9:0,10:0,11:0,12:0,13:0}}function Rf(a,b){var i=0;do{m[a+i]=m[b+i],i++}while(0!=m[b+(i-1)])}function uf(a,b){var i=Ba(a),g=0;do{m[a+i+g]=m[b+g],g++}while(0!=m[b+(g-1)])}function Jc(a,b){a--;do{a++;var i=m[a];if(i==b){return a}}while(i);return 0}function wg(b,r){for(var i=b;ah(m[b]);){b++}var g=1;45==m[b]?(g=-1,b++):43==m[b]&&b++;for(var q,c=0,d=ee;;){q=m[b];if(!(48<=q&&57>=q)){break}d=wc;c=10*c+q-48;b++}var f=ee;if(46==m[b]){b++;for(var n=.1;;){q=m[b];if(!(48<=q&&57>=q)){break}f=wc;c+=n*(q-48);n/=10;b++}}if(!d&&!f){return r&&(a[r>>2]=i),0}q=m[b];if(101==q||69==q){b++;i=0;d=ee;q=m[b];45==q?(d=wc,b++):43==q&&b++;for(q=m[b];48<=q&&57>=q;){i=10*i+q-48,b++,q=m[b]}d&&(i=-i);c*=Math.pow(10,i)}r&&(a[r>>2]=b);return c*g}function bh(a){for(;ah(m[a]);){a++}var b=1;45==m[a]?(b=-1,a++):43==m[a]&&a++;var i=10;!i&&48==m[a]&&(120==m[a+1]||88==m[a+1]?(i=16,a+=2):(i=8,a++));i||(i=10);for(var g,q=0;0!=(g=m[a])&&!(g=parseInt(String.fromCharCode(g),i),isNaN(g));){q=q*i+g,a++}q*=b;if(2147483647q){q=2147483647i||0>g){return Ea(va.i),-1}for(a=0;q.g.length&&0i){return Ea(va.i),-1}if(g.object.d){if(g.object.input){for(a=0;g.g.length&&0=a);return 0}function vQa(a){a=Oe(a);a=Q.o(a);return a===xc?-1:!a.v?(Ea(va.h),-1):0}function jk(a,b){for(var i=ee,g,q=0;63>q;q++){g=i?0:m[b+q],m[a+q]=g,i=i||0==m[b+q]}}function tn(a,b,i,g,q){for(var q=J[q],c=0,d,f,n;c>>1,n=b+d*g,f=q(a,n),0>f){i=d}else{if(0=a?a-65+97:a}function S(){Ub(\"abort() at \"+Error().stack)}function kk(a,b){var i=or(a&255);m[kk.c]=i;return-1==fC(b,kk.c,1)?(Q.a[b]&&(Q.a[b].error=wc),-1):i}function pi(a,b){return Math.sqrt(a*a+b*b)}function wQa(b,r,i){var g=a[i>>2],q=r&3,i=0!=q,q=1!=q,c=Boolean(r&512),d=Boolean(r&2048),f=Boolean(r&1024),n=Boolean(r&8),b=Q.D(Oe(b));if(!b.L){return Ea(b.error),-1}if(r=b.object||xc){if(c&&d){return Ea(va.ha),-1}if((i||c||f)&&r.e){return Ea(va.N),-1}if(q&&!r.v||i&&!r.write){return Ea(va.h),-1}if(f&&!r.d){r.b=[]}else{if(!Q.ua(r)){return Ea(va.r),-1}}b=b.path}else{if(!c){return Ea(va.O),-1}if(!b.u.write){return Ea(va.h),-1}r=Q.S(b.u,b.name,[],g&256,g&128);b=b.ba+\"/\"+b.name}g=Q.a.length;if(r.e){i=0;jC&&(i=Gb(jC.A));var q=[],h;for(h in r.b){q.push(h)}Q.a[g]={path:b,object:r,position:-2,p:wc,q:ee,K:ee,error:ee,f:ee,g:[],b:q,qa:i}}else{Q.a[g]={path:b,object:r,position:0,p:q,q:i,K:n,error:ee,f:ee,g:[]}}return g}function qi(a,b){var i,b=Oe(b);if(\"r\"==b[0]){i=-1!=b.indexOf(\"+\")?2:0}else{if(\"w\"==b[0]){i=-1!=b.indexOf(\"+\")?2:1,i|=1536}else{if(\"a\"==b[0]){i=-1!=b.indexOf(\"+\")?2:1,i|=512,i|=8}else{return Ea(va.i),0}}}i=wQa(a,i,d([511,0,0,0],\"i32\",$g));return-1==i?0:i}function ri(a){Q.a[a]||Ea(va.j);Q.a[a]?(Q.a[a].qa&&G(Q.a[a].qa),Q.a[a]=xc):Ea(va.j)}function un(a,b){var i=0,g;do{i||(g=a,i=b);var q=m[a++],c=m[i++];if(0==c){return g}c!=q&&(a=g+1,i=0)}while(q);return 0}function kC(b,r){if(Q.a[b]){var i=Nd(Q.a[b].path);i=d(i,\"i8\",$g);i=Q.o(Oe(i),cc);if(i!==xc&&Q.ua(i)){var g=xQa;a[r+g.yb>>2]=1;a[r+g.Bb>>2]=0;a[r+g.ub>>2]=0;a[r+g.qb>>2]=4096;a[r+g.vb>>2]=i.$;var q=Math.floor(i.timestamp/1e3);if(g.ea===cc){g.ea=g.pb.Da;g.za=g.xb.Da;g.ya=g.sb.Da;var c=1e3*(i.timestamp%1e3);a[r+g.pb.Ca>>2]=c;a[r+g.xb.Ca>>2]=c;a[r+g.sb.Ca>>2]=c}a[r+g.ea>>2]=q;a[r+g.za>>2]=q;a[r+g.ya>>2]=q;var l=0,f=q=0,n=c=0;i.d?(c=n=i.$,q=f=0,l=8192):(c=1,n=0,i.e?(q=4096,f=1,l=16384):(l=i.b||i.link,q=l.length,f=Math.ceil(l.length/4096),l=i.link===cc?32768:40960));a[r+g.tb>>2]=c;a[r+g.zb>>2]=n;a[r+g.Ab>>2]=q;a[r+g.rb>>2]=f;i.v&&(l|=365);i.write&&(l|=146);a[r+g.wb>>2]=l}}else{Ea(va.j)}}function xg(a,b,i){if(Q.a[a]&&!Q.a[a].object.d){var g=Q.a[a];1===i?b+=g.position:2===i&&(b+=g.object.b.length);0>b?(Ea(va.i),i=-1):(g.g=[],i=g.position=b)}else{Ea(va.j),i=-1}-1!=i&&(Q.a[a].f=ee)}function lC(a,b,i,g){i*=b;if(0==i){return 0}a=sr(g,a,i);g=Q.a[g];if(-1==a){return g&&(g.error=wc),0}a>2]=r,ur=d([i],\"i8**\",c)):(i=a[ur>>2],r=a[i>>2]);var g=[],q=0,Xa;for(Xa in b){if(\"string\"===typeof b[Xa]){var l=Xa+\"=\"+b[Xa];g.push(l);q+=l.length}}1024>2]=r;r+=l.length+1}a[i+4*g.length>>2]=0}function lk(a){if(0===a){return 0}a=Oe(a);if(!Ee.hasOwnProperty(a)){return 0}lk.c&&G(lk.c);lk.c=d(Nd(Ee[a]),\"i8\",sn);return lk.c}function mk(a){0!==a&&li(a,yQa.A)}function si(a){si.buffer||(si.buffer=Gb(256));var b=si.buffer;if(a in vr){if(255=a||97<=a&&122>=a||65<=a&&90>=a}function ti(a){return 97<=a&&122>=a||65<=a&&90>=a}function zQa(a){if(Q.a[a]){return a=Q.a[a],a.object.d?(Ea(va.Fa),-1):a.position}Ea(va.j);return-1}function vn(a,b,i){var g=Q.o(b||\"/tmp\");if(!g||!g.e){if(b=\"/tmp\",g=Q.o(b),!g||!g.e){return 0}}i=i||\"file\";do{i+=String.fromCharCode(65+Math.floor(25*Math.random()))}while(i in g.b);b=b+\"/\"+i;vn.buffer||(vn.buffer=Gb(256));a||(a=vn.buffer);for(g=0;g>12<<12,b.Oa=wc,Ie.Eb=We);b=We;0!=a&&Qa.Ba(a);return b}function pC(){return a[pC.La>>2]}function qC(b){var r;r=(b+8|0)>>2;var b=a[r]>>2,i=a[b];if(0!=(i&4096|0)){var g=a[b+1];return g}var q=0==(i&3|0);a:do{if(q){if(0!=(i&112|0)){g=a[b+2]}else{var c=a[b+1];if(0==(c|0)){g=0}else{var d=c+4|0,f=a[d>>2],n=0==(f|0);b:do{if(n){var h=c,e=a[c>>2]}else{for(var s=c,j=d,t=f;;){var m=t|0;a[j>>2]=a[m>>2];a[m>>2]=s;j=t+4|0;m=a[j>>2];if(0==(m|0)){h=t;e=s;break b}else{s=t,t=m}}}}while(0);if(0==(e|0)){g=h}else{c=h|0;for(f=e;;){d=a[f+4>>2];if(0==(d|0)){c=f}else{for(;!(n=d|0,a[f+4>>2]=a[n>>2],a[n>>2]=f,n=a[d+4>>2],0==(n|0));){f=d,d=n}c=a[c>>2]=d}c|=0;d=a[c>>2];if(0==(d|0)){g=h;break a}else{f=d}}}}}}else{if(d=a[b+2],f=a[b+3],c=(f<<2)+d|0,0<(f|0)){for(n=f=0;;){s=a[d>>2];if(0!=(s|0)){0==(f|0)?f=n=s:a[f>>2]=s;for(;!(s=a[f>>2],0==(s|0));){f=s}a[d>>2]=f}d=d+4|0;if(d>>>0>=c>>>0){g=n;break a}}}else{g=0}}}while(0);a[a[r]+4>>2]=g;h=a[r]|0;a[h>>2]|=4096;return g}function rC(b,r){var i=h;if(0==(a[qa+12>>2]|0)){a[qa>>2]=288;a[qa+4>>2]=304;a[qa+8>>2]=184;m[qa+31|0]=1;var g;g=xn(sC|0,0);a[qa+12>>2]=g;g=(g+40|0)>>2;0!=(a[ui(a[a[g]+4>>2]|0,wr|0,Y|0)+8>>2]|0)&&S();var q=ui(a[a[g]+4>>2]|0,vi|0,Y|0);1!=(a[q+8>>2]|0)&&S();m[q+12|0]=0;g=ui(a[a[g]+4>>2]|0,wi|0,Y|0);2==(a[g+8>>2]|0)?m[g+12|0]=0:S()}else{288==(a[qa>>2]|0)&304==(a[qa+4>>2]|0)&184==(a[qa+8>>2]|0)||la(0,tC|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j))}h=i;xi(0,dh|0,yg|0);i=fa(340);g=i>>2;0!=(i|0)&&(a[g]=nk|0,a[g+4]=94,a[g+8]=0,a[g+9]=1);g=a[i+32>>2];if(0!=(g|0)&&(q=a[g>>2],0!=(q|0))){for(;!(103==m[q]<<24>>24&&0!=(un(q,uC|0)|0)&&xr(i,a[g+4>>2]),g=g+8|0,q=a[g>>2],0==(q|0));){}}m[i+44|0]=0;g=eh(i,2,yr|0);0!=(g|0)&&(a[i+132>>2]=a[a[g+16>>2]+12>>2]);xr(i,yn);xr(i,zn);g=a[qa+32>>2];a[qa+32>>2]=22;vC(b);zr();a[qa+32>>2]=g;g=a[qa+16>>2];An(i,g,ok|0);var c=a[zg>>2],d,q=a[g+32>>2];wC(i,r);var f=a[i+124>>2];d=f>>2;a[d+14]=Bn(f,a[d+13]);0==(a[q+44>>2]|0)&&0==(a[d+37]&67108864|0)?Lc(Ar|0,20,1,a[oa>>2]):(a[d+9]=c,0==(c|0)&&(c=f+148|0,a[c>>2]|=134217728),Br(i,q),Cr(f),Dr(i));q=g+176|0;f=a[q>>2];0!=(f|0)&&(J[f](g),a[q>>2]=0);q=g+44|0;0!=(a[q>>2]|0)&&(xC(g),a[q>>2]=0,a[a[g+32>>2]+44>>2]=0);Sf(g);g=a[yi>>2];0!=(g|0)&&(Kc(g),a[yi>>2]=0);g=a[i+52>>2];q=0==(g|0);b:do{if(!q){for(f=g;;){if(c=a[f+4>>2],G(f),0==(c|0)){break b}else{f=c}}}}while(0);q=a[i+100>>2];f=0==(q|0);b:do{if(!f){c=q;for(g=c>>2;;){if(d=a[g],G(a[g+1]),G(a[g+2]),G(c),0==(d|0)){break b}else{c=d,g=c>>2}}}}while(0);Dr(i);g=a[i+40>>2];0!=(g|0)&&G(g);g=a[i+48>>2];0!=(g|0)&&G(g);G(i)}function Kc(b){var r=b>>2;if(0==(b|0)||0<(a[r+6]|0)){return-1}var i=a[r+1],g=i+32|0,q=a[g>>2];if(0==(q|0)){q=0}else{if(q=J[q](b,2,0,i),0>(q|0)){return-1}}0!=(a[r+7]|0)&&yC(b,0);if(q=0==(q|0)){J[a[a[r+4]>>2]](b,0,64);if(0<(rc(b)|0)){return-1}var c=b+8|0,d=a[c>>2],f=b+12|0;0<(a[d+12>>2]|0)?(J[a[f>>2]](b,a[d+8>>2],0,i),c=a[c>>2]):c=d;J[a[f>>2]](b,c,0,i)}f=a[r+5];if(0==(f|0)){G(b)}else{if(q&1==(f|0)){J[a[r+3]](b,b,0,i)}}r=a[g>>2];if(0==(r|0)){return 0}J[r](b,6,0,i);return 0}function pk(b,r,i){var g,q;q=(b+4|0)>>2;var c=a[q];if(0==(c|0)){a[q]=r;c=a[r+28>>2];a[b+12>>2]=0==(c|0)?144:c;var d;return r}if(0==(r|0)){return c}var f=a[a[b+16>>2]>>2];g=(b+8|0)>>2;0!=(a[a[g]>>2]&4096|0)&&Ag(b,0);var n=a[c+32>>2];if(0!=(n|0)&&0>(J[n](b,3,r,c)|0)){return 0}a[q]=r;q=a[r+28>>2];a[b+12>>2]=0==(q|0)?144:q;q=a[a[g]>>2];if(0!=(q&112|0)){return c}if(0==(q&2|0)){if(0==(q&3|0)){if(0!=(i&1|0)){return c}}else{if(3==(i&3|0)){return c}}}else{if(0!=(i&2|0)){return c}}q=qC(b);n=a[g]|0;a[n>>2]&=-4097;a[a[g]+4>>2]=0;a[a[g]+16>>2]=0;g=a[g]>>2;n=0==(a[g]&3|0);a:do{if(!n){var h=a[g+2],e=a[g+3],s=(e<<2)+h|0;if(0<(e|0)){for(;;){if(e=h+4|0,a[h>>2]=0,e>>>0>>0){h=e}else{break a}}}}}while(0);if(0==(q|0)){return c}g=r+8|0;n=r+4|0;s=r|0;h=r+24|0;if(0==(i&2|0)){i=q>>2}else{for(r=q;;){if(i=a[r>>2],J[f](b,r,32),0==(i|0)){d=c;break}else{r=i}}return d}for(;;){var e=a[i],j=a[g>>2],t=a[n>>2],j=(0>(j|0)?a[i+2]:q+ -j|0)+a[s>>2]|0,j=0>(t|0)?a[j>>2]:j,m=a[h>>2],t=0==(m|0)?Cn(0,j,t):J[m](b,j,r);a[i+1]=t;J[f](b,q,32);if(0==(e|0)){d=c;break}else{q=e,i=q>>2}}return d}function zC(b,r,i){var g,q,c,d,f,n,h,e,s,j,t,m,w,A=b>>2,B;w=(b+8|0)>>2;0!=(a[a[w]>>2]&4096|0)&&Ag(b,0);var C=a[A+1];m=C>>2;var P=a[m],T=a[m+1];t=(C+8|0)>>2;var gb=a[t],$e=a[m+5];j=(b+20|0)>>2;a[j]&=-32769;var M=0==(r|0);a:do{if(M){if(0==(i&24|0)){var X=a[w];s=X>>2;if(1>(a[s+4]|0)){var O=0;return O}if(0==(i&448|0)){return O=0}var y=a[s+2],Da=a[s+3],ia=(Da<<2)+y|0,D=0<(Da|0);if(0==(i&64|0)){b:do{if(D){var F=0==(i&256|0);c:do{if(F){for(var Tf=y;;){var sc=Tf+4|0,E=a[Tf>>2],G=0==(E|0);if(sc>>>0>>0&G){Tf=sc}else{var H=E,hc=G;break c}}}else{for(var ca=ia;;){var I=ca-4|0,K=a[I>>2],zb=0==(K|0);if(y>>>0>>0&zb){ca=I}else{H=K;hc=zb;break c}}}}while(0);if(hc|F){var ja=hc?0:H}else{for(var aa=H;;){var da=a[aa>>2];if(0==(da|0)){ja=aa;break b}else{aa=da}}}}else{ja=0}}while(0);var ea=X+20|0;a[ea>>2]=a[ea>>2]+1|0;a[a[w]+4>>2]=ja;return 0==(ja|0)?O=0:O=0>(gb|0)?a[ja+8>>2]:ja+ -gb|0}if(D){for(var xa=C+16|0,L=b+12|0,Od=0>(gb|0),ha=-gb|0,ga=y;;){var N=a[ga>>2];a[ga>>2]=0;var Rb=a[xa>>2];if(0==(Rb|0)){if(!(-1<(a[t]|0)|0==(N|0))){var Xe=N,tb=Rb;B=121}}else{0!=(N|0)&&(Xe=N,tb=Rb,B=121)}b:do{if(121==B){for(;;){B=0;var ya=a[Xe>>2];if(0!=(tb|0)){J[tb](b,Od?a[Xe+8>>2]:Xe+ha|0,C)}if(0>(a[t]|0)){J[a[L>>2]](b,Xe,0,C)}if(0==(ya|0)){break b}Xe=ya;tb=a[xa>>2]}}}while(0);var Uf=ga+4|0;if(Uf>>>0>>0){ga=Uf}else{break}}var wa=a[w]}else{wa=X}a[wa+4>>2]=0;a[a[w]+16>>2]=0;return O=a[a[w]+20>>2]=0}}else{var Ab=2==(a[a[A+4]+4>>2]|0);b:do{if(Ab){if(0==(i&4098|0)){B=150}else{if(0==(J[a[A]](b,r,4)|0)){return O=0}e=a[w]>>2;for(var Fa=a[e+1],Ga=((a[e+3]-1&a[Fa+4>>2])<<2)+a[e+2]|0,Bg=0>(gb|0),ta=-gb|0,Ka=Ga,za=0,ma=0;;){var pa=a[Ka>>2];if(0==(pa|0)){var wf=Ga,Ha=Fa;h=Ha>>2;var Ra=za;break b}if(((Bg?a[pa+8>>2]:pa+ta|0)|0)==(r|0)){wf=Ga;Ha=pa;h=Ha>>2;Ra=ma;break b}Ka=pa|0;za=(pa|0)==(Fa|0)?ma:za;ma=pa}}}else{B=150}}while(0);b:do{if(150==B){var W=0==(i&2565|0);c:do{if(W){if(0!=(i&1056|0)){var BC=0>(gb|0)?a[r+8>>2]:r+ -gb|0,La=BC+P|0,Vf=BC,Ya=r,Q=0>(T|0)?a[La>>2]:La,Za=a[r+4>>2]}else{n=a[w]>>2;var S=a[n+1];do{if(0!=(S|0)&&((0>(gb|0)?a[S+8>>2]:S+ -gb|0)|0)==(r|0)){var ab=a[S+4>>2],$a=r,jb=((a[n+3]-1&ab)<<2)+a[n+2]|0,Ca=S,Ia=0,eb=ab;B=192;break c}}while(0);var ub=r+P|0,Sa=0>(T|0)?a[ub>>2]:ub,U=a[m+6];0==(U|0)?(Vf=r,Q=Sa,Za=Cn(0,Sa,T)):(Vf=r,Q=Sa,Za=J[U](b,Sa,C))}}else{if(0==(i&512|0)){var ua=r+P|0,Oa=0>(T|0)?a[ua>>2]:ua}else{Oa=r}var Wa=a[m+6];0==(Wa|0)?(Vf=r,Q=Oa,Za=Cn(0,Oa,T)):(Vf=r,Q=Oa,Za=J[Wa](b,Oa,C))}B=175}while(0);c:do{if(175==B){var pb=a[w],ob=a[pb+12>>2];if(1>(ob|0)){var bb=0}else{var qb=((ob-1&Za)<<2)+a[pb+8>>2]|0,bb=a[qb>>2],V=qb}var kb=0>(gb|0),Y=0>(T|0),vb=0==($e|0),xb=1>(T|0),la=-gb|0,nb=bb;f=nb>>2;for(var rb=0;;){if(0==(nb|0)){var lb=Za,Ta=rb,cb=Ya,fb=0;d=fb>>2;var Ua=V;c=Ua>>2;var sb=Vf,Na=0;break c}if((Za|0)==(a[f+1]|0)){var R=(kb?a[f+2]:nb+la|0)+P|0,Db=Y?a[R>>2]:R;if(0==((vb?xb?ka(Q,Db):Ve(Q,Db,T):J[$e](b,Q,Db,C))|0)){var $a=Vf,jb=V,Ca=nb,Ob=Ya,Ia=rb,eb=Za;B=192;break c}}rb=nb;nb=a[f];f=nb>>2}}}while(0);192==B&&(0==(Ca|0)?(lb=eb,Ta=Ia,cb=Ob,fb=0,d=fb>>2,Ua=jb,c=Ua>>2,sb=$a,Na=0):(a[j]|=32768,lb=eb,Ta=Ia,cb=Ob,fb=Ca,d=fb>>2,Ua=jb,c=Ua>>2,sb=$a,Na=1));if(0!=(i&1540|0)){if(!Na){return O=0}if(0!=(Ta|0)){var Eb=a[w];if(0!=(a[Eb>>2]&1|0)&&1>(a[Eb+20>>2]|0)){var $=fb|0;a[Ta>>2]=a[$>>2];a[$>>2]=a[c];a[c]=fb}}a[a[w]+4>>2]=fb;return O=0>(gb|0)?a[d+2]:fb+ -gb|0}var Bb=0==(i&2049|0);c:do{if(Bb){var Ja=0==(i&8|0);d:do{if(Ja){if(0!=(i&16|0)){if(!(Na&0==(Ta|0))){var fa=Ta;B=251;break}var Z=a[c];if((Z|0)!=(fb|0)){for(var sa=Z;;){var CC=a[sa>>2];if((CC|0)==(fb|0)){fa=sa;B=251;break d}else{sa=CC}}}for(var DC=a[w],Wf=a[DC+8>>2],ic=Ua;;){var ra=ic-4|0;if(ra>>>0>>0){B=247;break}var ba=a[ra>>2];if(0==(ba|0)){ic=ra}else{var qa=ba;B=249;break}}if(247==B){a[DC+4>>2]=0;break a}else{if(249==B){for(;;){B=0;var na=a[qa>>2];if(0==(na|0)){var oa=qa;B=250;break d}else{qa=na,B=249}}}}}if(0==(i&32|0)){wf=Ua;Ha=fb;h=Ha>>2;Ra=Ta;break b}if(!Na){var pc=sb,Wc=cb;break c}if(0!=(a[a[w]>>2]&2|0)){pc=sb;Wc=cb;break c}var Vb=a[m+4];if(0!=(Vb|0)){J[Vb](b,sb,C)}if(0>(a[t]|0)){J[a[A+3]](b,cb,0,C)}return O=0>(gb|0)?a[d+2]:fb+ -gb|0}if(Na){var Xd=a[d];if(0!=(Xd|0)){oa=Xd,B=250}else{q=a[w]>>2;for(var Ba=(a[q+3]<<2)+a[q+2]|0,Ea=Ua;;){var Aa=Ea+4|0;if(Aa>>>0>=Ba>>>0){break}var Cb=a[Aa>>2];if(0==(Cb|0)){Ea=Aa}else{oa=Cb;B=250;break d}}a[q+1]=0;break a}}else{fa=Ta,B=251}}while(0);if(250==B){var Ma=a[a[w]+4>>2]=oa}else{if(251==B){if(a[a[w]+4>>2]=fa,0==(fa|0)){break a}else{Ma=fa}}}var lc=a[w]|0;a[lc>>2]|=8192;return O=0>(gb|0)?a[Ma+8>>2]:Ma+ -gb|0}if(Na){var wb=a[w];if(0!=(a[wb>>2]&1|0)){return a[wb+4>>2]=fb,O=0>(gb|0)?a[d+2]:fb+ -gb|0}}var Jd=C+12|0,va=a[Jd>>2];if(0==(va|0)){var Hc=sb}else{if(0==(i&1|0)){Hc=sb}else{var Pa=J[va](b,sb,C);if(0==(Pa|0)){return O=0}Hc=Pa}}if(-1<(gb|0)){var oe=Hc+gb|0}else{var Qa=J[a[A+3]](b,0,12,C);if(0!=(Qa|0)){a[Qa+8>>2]=Hc,oe=Qa}else{if(0==(a[Jd>>2]|0)){return O=0}var Qb=a[m+4];if(0==(Qb|0)||0==(i&1|0)){return O=0}J[Qb](b,Hc,C);return O=0}}a[oe+4>>2]=lb;pc=Hc;Wc=oe}while(0);var tc=a[w]+16|0,Sc=a[tc>>2]+1|0;a[tc>>2]=Sc;var Gb=a[w],Va=a[Gb+12>>2];if((Sc|0)>(Va<<1|0)){if(1>(a[Gb+20>>2]|0)){Er(b);var hb=a[w],rd=hb,oc=a[hb+12>>2]}else{var rd=Gb,oc=Va}}else{rd=Gb,oc=Va}if(0!=(oc|0)){var ib=((oc-1&lb)<<2)+a[rd+8>>2]|0;if(Na){var Ib=fb|0;a[Wc>>2]=a[Ib>>2];a[Ib>>2]=Wc}else{a[Wc>>2]=a[ib>>2],a[ib>>2]=Wc}a[a[w]+4>>2]=Wc;return O=pc}var ve=rd+16|0;a[ve>>2]=a[ve>>2]-1|0;var Rd=a[m+4];if(0!=(Rd|0)&&0!=(i&1|0)){J[Rd](b,pc,C)}if(0<=(a[t]|0)){return O=0}J[a[m+7]](b,Wc,0,C);return O=0}}while(0);if(0==(Ha|0)){return O=0}do{if(0==(Ra|0)){var Bc=a[wf>>2];if((Bc|0)==(Ha|0)){var Sd=a[h],fd=a[wf>>2]=Sd}else{for(var yb=Bc;;){var yc=yb|0,Nb=a[yc>>2];if((Nb|0)==(Ha|0)){break}else{yb=Nb}}a[yc>>2]=a[h];fd=yb}}else{a[Ra>>2]=a[h],fd=Ra}}while(0);var Hb=0>(gb|0)?a[h+2]:Ha+ -gb|0,Tc=a[w]+16|0;a[Tc>>2]=a[Tc>>2]-1|0;a[a[w]+4>>2]=fd;var mb=a[m+4];if(0!=(mb|0)&&0!=(i&2|0)){J[mb](b,Hb,C)}if(0<=(a[t]|0)){return O=Hb}J[a[A+3]](b,Ha,0,C);return O=Hb}}while(0);var Mc=a[w]+20|0,id=a[Mc>>2]-1|0;a[Mc>>2]=id;0>(id|0)&&(a[a[w]+20>>2]=0);g=a[w]>>2;if((a[g+4]|0)<=(a[g+3]<<1|0)||1<=(a[g+5]|0)){return O=0}Er(b);return O=0}function Er(b){var r,i,g=h;h+=4;var q;i=g>>2;r=(b+8|0)>>2;var c=a[r],d=c+24|0;if(!(0<(a[d>>2]|0)&&0<(a[c+12>>2]|0))){a[d>>2]=0;a[i]=a[a[r]+12>>2];c=b+4|0;d=a[c>>2];do{if(0==(d|0)){q=325}else{var f=a[d+32>>2];if(0==(f|0)){q=325}else{if(0<(J[f](b,7,g,d)|0)){f=a[i];do{if(0>(f|0)){a[a[r]+24>>2]=1;if(0<(a[a[r]+12>>2]|0)){h=g;return}var n=a[i]}else{for(n=2;;){if((n|0)<(f|0)){n<<=1}else{break}}a[i]=n}}while(0);if(1>(n|0)){q=329}else{var z=n,e=a[r]}}else{q=325}}}}while(0);325==q&&(a[i]=0,q=329);a:do{if(329==q){d=a[r];f=a[d+12>>2];n=0==(f|0)?256:f;a[i]=n;var f=d+16|0,s=n<<1;if((a[f>>2]|0)>(s|0)){for(n=s;;){if(a[i]=n,s=n<<1,(a[f>>2]|0)>(s|0)){n=s}else{z=n;e=d;break a}}}else{z=n,e=d}}}while(0);q=a[e+12>>2];if((z|0)!=(q|0)&&(b=J[a[b+12>>2]](b,0==(q|0)?0:a[e+8>>2],z<<2,a[c>>2]),0!=(b|0))){q=a[r];e=a[q+12>>2];z=(e<<2)+b|0;a[q+8>>2]=b;a[a[r]+12>>2]=a[i];r=a[i]-1|0;e=(r|0)<(e|0);a:do{if(!e){for(q=(r<<2)+b|0;;){if(a[q>>2]=0,q=q-4|0,q>>>0>>0){break a}}}}while(0);if(b>>>0>>0){for(r=b;;){e=a[r>>2];q=0;a:for(;;){c=0==(q|0);d=q|0;for(f=e;;){if(0==(f|0)){break a}var n=f|0,s=a[n>>2],j=((a[i]-1&a[f+4>>2])<<2)+b|0;if((j|0)==(r|0)){e=s;q=f;continue a}c?a[r>>2]=s:a[d>>2]=s;a[n>>2]=a[j>>2];a[j>>2]=f;f=s}}r=r+4|0;if(r>>>0>=z>>>0){break}}}}}h=g}function FC(b,r,i){var g,q,c,d,f,n;d=(b+8|0)>>2;0!=(a[a[d]>>2]&4096|0)&&Ag(b,0);var h=a[b+4>>2];q=h>>2;var e=a[q],s=a[q+1];n=(h+8|0)>>2;var j=a[n],m=a[q+5];f=(b+20|0)>>2;a[f]&=-32769;do{if(0==(r|0)){if(0!=(i&384|0)){d=a[d]>>2;b=a[d+2];if(0==(b|0)){var u=0;return u}if(0==(i&256|0)){d=a[d+1]=b}else{i=a[b+4>>2];a[d+1]=i;if(0==(i|0)){return u=0}d=i}return u=0>(j|0)?a[d+8>>2]:d+ -j|0}if(0!=(i&4098|0)){g=a[d];if(0!=(a[g>>2]&144|0)){return u=0}g=a[g+8>>2];if(0==(g|0)){u=0}else{var w=g;c=w>>2;break}return u}if(0==(i&64|0)){return u=0}i=h+16|0;q=a[i>>2];0==(q|0)?0>(a[n]|0)&&(g=372):g=372;a:do{if(372==g&&(m=a[a[d]+8>>2],0!=(m|0))){f=b+12|0;e=0>(j|0);s=-j|0;for(u=q;;){w=a[m>>2];if(0!=(u|0)){J[u](b,e?a[m+8>>2]:m+s|0,h)}if(0>(a[n]|0)){J[a[f>>2]](b,m,0,h)}if(0==(w|0)){break a}m=w;u=a[i>>2]}}}while(0);a[a[d]+4>>2]=0;a[a[d]+8>>2]=0;u=a[a[d]+16>>2]=0}else{if(0==(i&2049|0)){w=a[d];do{if(0==(i&512|0)){c=a[w+4>>2];if(0!=(c|0)&&((0>(j|0)?a[c+8>>2]:c+ -j|0)|0)==(r|0)){var A=c;break}g=r+e|0;var B=0>(s|0)?a[g>>2]:g}else{B=r}g=428}while(0);a:do{if(428==g){g=0>(j|0);c=0>(s|0);for(var r=0==(m|0),A=1>(s|0),C=-j|0,w=w+8|0;;){w=a[w>>2];if(0==(w|0)){u=0;break}var P=(g?a[w+8>>2]:w+C|0)+e|0,P=c?a[P>>2]:P;if(0==((r?A?ka(B,P):Ve(B,P,s):J[m](b,B,P,h))|0)){A=w;break a}else{w|=0}}return u}}while(0);if(0==(A|0)){return u=0}a[f]|=32768;if(0!=(i&4098|0)){w=A;c=w>>2;break}if(0==(i&8|0)){if(0==(i&16|0)){i=A}else{if(i=a[d],(A|0)!=(a[i+8>>2]|0)){i=a[A+4>>2]}else{return u=a[i+4>>2]=0}}}else{i=a[A>>2]}a[a[d]+4>>2]=i;return 0==(i|0)?u=0:u=0>(j|0)?a[i+8>>2]:i+ -j|0}n=h+12|0;f=a[n>>2];if(0==(f|0)){f=r}else{if(0==(i&1|0)){f=r}else{if(f=J[f](b,r,h),0==(f|0)){return u=0}}}if(-1<(j|0)){h=f+j|0,b=h>>2}else{if(e=J[a[b+12>>2]](b,0,12,h),0!=(e|0)){a[e+8>>2]=f,h=e,b=h>>2}else{if(0==(a[n>>2]|0)){return u=0}j=a[q+4];if(0==(j|0)||0==(i&1|0)){return u=0}J[j](b,f,h);return u=0}}n=h;f=a[d];q=f>>2;e=a[q];0==(e&128|0)?0==(e&16|0)?g=0==(e&32|0)?411:407:(e=a[q+1],s=0!=(e|0),0==(i&8192|0)?s?(e|0)==(a[q+2]|0)?g=407:(i=e+4|0,s=a[i>>2],a[b+1]=s,a[s>>2]=n,a[b]=e,a[i>>2]=h):g=407:s?(i=e|0,s=a[i>>2],0==(s|0)?g=411:(a[b]=s,a[s+4>>2]=h,a[b+1]=e,a[i>>2]=n)):g=411):g=0==(i&8192|0)?407:411;407==g?(q=a[q+2],i=h,a[i>>2]=q,0==(q|0)?a[b+1]=n:(q=q+4|0,a[b+1]=a[q>>2],a[q>>2]=h),a[a[d]+8>>2]=i):411==g&&(i=f+8|0,q=a[i>>2],0==(q|0)?(q=h,a[i>>2]=q,a[b+1]=n,i=q):(q=i=q+4|0,a[a[q>>2]>>2]=n,a[b+1]=a[q>>2],i=a[i>>2]=h),a[i>>2]=0);i=a[d];q=i+16|0;g=a[q>>2];-1<(g|0)?(a[q>>2]=g+1|0,d=a[d]):d=i;a[d+4>>2]=n;u=0>(j|0)?a[b+2]:h+ -j|0}return u}while(0);g=(w|0)>>2;f=a[g];0==(f|0)?f=0:(a[f+4>>2]=a[c+1],f=a[g]);s=a[d]+8|0;e=a[s>>2];(w|0)==(e|0)?(a[s>>2]=f|0,f=a[a[d]+8>>2],0!=(f|0)&&(a[f+4>>2]=a[c+1])):(s=w+4|0,a[a[s>>2]>>2]=f,f=e+4|0,(w|0)==(a[f>>2]|0)&&(a[f>>2]=a[s>>2]));f=a[d]+4|0;a[f>>2]=(w|0)==(a[f>>2]|0)?a[g]:0;d=a[d]+16|0;a[d>>2]=a[d>>2]-1|0;j=0>(j|0)?a[c+2]:w+ -j|0;d=a[q+4];if(0!=(d|0)&&0!=(i&2|0)){J[d](b,j,h)}if(0<=(a[n]|0)){return j}J[a[b+12>>2]](b,w,0,h);return j}function Cn(a,b,i){if(1>(i|0)){var i=m[b],g=0==i<<24>>24;a:do{if(g){var q=b,c=a}else{for(var d=b,f=a,n=i;;){var h=m[d+1|0],f=17109811*(((n&255)<<8)+f+(h&255))|0,d=d+(0!=h<<24>>24?2:1)|0,h=m[d];if(0==h<<24>>24){q=d;c=f;break a}else{n=h}}}}while(0);a=c+(q-b|0)|0;return 17109811*a|0}c=i-1|0;q=b+c|0;c=0<(c|0);a:do{if(c){f=b;for(h=a;;){if(h=17109811*(((m[f]&255)<<8)+(m[f+1|0]&255)+h)|0,f=f+2|0,f>>>0>=q>>>0){g=f;d=h;break a}}}else{g=b,d=a}}while(0);if(g>>>0>q>>>0){return a=d+i|0,17109811*a|0}a=17109811*(((m[g]&255)<<8)+d)|0;a=a+i|0;return 17109811*a|0}function Nc(b,r){var i,g,q,c,d,f=h;h+=4;var n;d=f>>2;if(0==(b|0)|0==(r|0)){return h=f,0}var z=Gb(40);c=z>>2;if(0==(z|0)){return h=f,0}a[c]=0;a[c+4]=0;a[c+1]=0;pk(z,b,0);q=(z+20|0)>>2;c=b+32|0;a[q]=0;a[q+1]=0;a[q+2]=0;a[q+3]=0;a[q+4]=0;q=a[c>>2];if(0==(q|0)){i=z,n=566}else{if(a[d]=0,q=J[q](z,1,f,b),0>(q|0)){var e=z;n=567}else{if(0<(q|0)){if(q=a[d],0!=(q|0)){if(0==(a[r+4>>2]&a[q>>2]|0)){e=z,n=567}else{var s=z;g=s>>2;var j=q}}else{if(n=b+28|0,0==(a[n>>2]|0)){e=z,n=567}else{G(z);n=J[a[n>>2]](0,0,40,b);i=n>>2;if(0==(n|0)){return h=f,0}a[i]=0;a[i+4]=0;a[i+1]=0;pk(n,b,0);a[i+5]=1;a[i+6]=0;a[i+8]=0;a[i+7]=0;i=n;n=566}}}else{i=z,n=566}}}566==n&&(z=J[a[i+12>>2]](i,0,28,b),a[d]=z,0==(z|0)?(e=i,n=567):(a[z>>2]=a[r+4>>2],a[a[d]+4>>2]=0,a[a[d]+8>>2]=0,a[a[d]+20>>2]=0,a[a[d]+16>>2]=0,a[a[d]+12>>2]=0,a[a[d]+24>>2]=0,s=i,g=s>>2,j=a[d]));if(567==n){return G(e),h=f,0}a[g+2]=j;a[g]=a[r>>2];a[g+4]=r;g=a[c>>2];if(0==(g|0)){return h=f,s}J[g](s,5,s,b);h=f;return s}function rc(b){var r,i;i=(b+8|0)>>2;r=a[i];0!=(a[r>>2]&4096|0)&&(Ag(b,0),r=a[i]);b=r>>2;r=(r+16|0)>>2;do{if(0>(a[r]|0)){var g=a[b];if(0!=(g&12|0)){a[r]=Fr(a[b+1])}else{if(0!=(g&112|0)){var g=a[b+2],q=0==(g|0);a:do{if(q){var c=0}else{for(var d=0,f=g;;){if(d=d+1|0,f=a[f>>2],0==(f|0)){c=d;break a}}}}while(0);a[r]=c}}}}while(0);return a[a[i]+16>>2]}function Fr(b){return 0==(b|0)?0:Fr(a[b+4>>2])+Fr(a[b>>2])+1|0}function GC(b,r,i){var g,q,c,d,f,n,z,e,s,j,m,u,w,A,B,C,P=h;h+=128;var T;C=P>>2;B=P+8>>2;A=(b+8|0)>>2;var gb=a[A];if(0==(a[gb>>2]&4096|0)){var $e=gb}else{Ag(b,0),$e=a[A]}var M=a[b+4>>2];w=M>>2;var X=a[w],O=a[w+1];u=(M+8|0)>>2;var y=a[u],Da=a[w+5];m=(b+20|0)>>2;a[m]&=-32769;var ia=a[$e+4>>2];if(0==(r|0)){if(0==(ia|0)){var D=0;h=P;return D}if(0==(i&448|0)){return D=0,h=P,D}if(0==(i&64|0)){var F=0==(i&256|0);a:do{if(F){var Tf=ia+4|0,sc=a[Tf>>2];if(0==(sc|0)){var E=ia}else{for(var G=ia,H=Tf,hc=sc;;){var ca=hc|0;a[H>>2]=a[ca>>2];a[ca>>2]=G;var I=hc+4|0,K=a[I>>2];if(0==(K|0)){E=hc;break a}else{G=hc,H=I,hc=K}}}}else{var zb=ia|0,ja=a[zb>>2];if(0==(ja|0)){E=ia}else{for(var aa=ia,da=zb,ea=ja;;){var xa=ea+4|0;a[da>>2]=a[xa>>2];a[xa>>2]=aa;var L=ea|0,N=a[L>>2];if(0==(N|0)){E=ea;break a}else{aa=ea,da=L,ea=N}}}}}while(0);a[a[A]+4>>2]=E;D=0>(y|0)?a[E+8>>2]:E+ -y|0;h=P;return D}var ha=M+16|0;if(0==(a[ha>>2]|0)){if(0>(a[u]|0)){T=599}else{var ga=$e}}else{T=599}if(599==T){for(var Zd=b+12|0,Rb=0>(y|0),Xe=-y|0,tb=ia;;){var ya=tb+4|0,Uf=a[ya>>2];if(0!=(Uf|0)){var wa=Uf|0;a[ya>>2]=a[wa>>2];a[wa>>2]=tb;tb=Uf}else{var Ab=a[tb>>2],Fa=a[ha>>2];if(0!=(Fa|0)){J[Fa](b,Rb?a[tb+8>>2]:tb+Xe|0,M)}if(0>(a[u]|0)){J[a[Zd>>2]](b,tb,0,M)}if(0==(Ab|0)){break}else{tb=Ab}}}ga=a[A]}a[ga+16>>2]=0;D=a[a[A]+4>>2]=0;h=P;return D}j=(b+16|0)>>2;var Ga=8==(a[a[j]+4>>2]|0);a:do{if(Ga){if(0==(i&4098|0)){T=637}else{for(var Bg=0>(O|0),ta=r+X|0,Ka=Bg?a[ta>>2]:ta,za=b,ma=0==(Da|0),pa=1>(O|0),wf=J[a[za>>2]](b,r,4);;){if(0==(wf|0)){T=637;break a}var Ha=wf+X|0,Ra=Bg?a[Ha>>2]:Ha;if(0!=((ma?pa?ka(Ka,Ra):Ve(Ka,Ra,O):J[Da](b,Ka,Ra,M))|0)){T=637;break a}if((wf|0)==(r|0)){break}wf=J[a[za>>2]](b,wf,8)}var W=a[a[A]+4>>2];a[C]=a[W+4>>2];a[C+1]=a[W>>2];var Q=W,La=P;T=779}}else{T=637}}while(0);a:do{if(637==T){if(0==(i&2565|0)){if(0!=(i&32|0)){var S=r,Ya=0>(y|0)?a[r+8>>2]:r+ -y|0,U=Ya+X|0,Za=0>(O|0)?a[U>>2]:U;if(0==(ia|0)){var V=P,ab=P,$a=S,jb=Ya;T=799}else{var Ca=Ya,Ia=S,eb=Za;T=656}}else{if(0==(ia|0)){ab=V=P,jb=r,T=799}else{if(((0>(y|0)?a[ia+8>>2]:ia+ -y|0)|0)==(r|0)){var ub=r,Sa=ia,Y=P,ua=P;T=736}else{var Oa=r+X|0;0>(O|0)?(Ca=r,eb=a[Oa>>2]):(Ca=r,eb=Oa);T=656}}}}else{if(0==(i&512|0)){var Wa=r+X|0,pb=0>(O|0)?a[Wa>>2]:Wa}else{pb=r}0==(ia|0)?(ab=V=P,jb=r,T=799):(Ca=r,eb=pb,T=656)}b:do{if(656==T){var ob=4==(a[a[j]+4>>2]|0);c:do{if(ob){var bb=a[a[A]+24>>2];if(0==(bb|0)){var qb=ia,R=P,kb=P}else{if(0==(i&516|0)){qb=ia,kb=R=P}else{for(var la=0>(y|0),vb=0>(O|0),xb=0==(Da|0),$=1>(O|0),nb=-y|0,rb=0,lb=ia;;){if((rb|0)>=(bb|0)){T=661;break}var Ta=(la?a[lb+8>>2]:lb+nb|0)+X|0,cb=vb?a[Ta>>2]:Ta,fb=xb?$?ka(eb,cb):Ve(eb,cb,O):J[Da](b,eb,cb,M);if(0==(fb|0)){T=673;break}a[(rb<<2>>2)+B]=fb;var Ua=a[(0>(fb|0)?lb+4|0:lb|0)>>2];if(0==(Ua|0)){D=0;T=825;break}else{rb=rb+1|0,lb=Ua}}if(673==T){return D=la?a[lb+8>>2]:lb+nb|0,h=P,D}if(661==T){if(0<(bb|0)){var sb=P;s=sb>>2;var Na=P;e=Na>>2;for(var Fb=ia,Db=0;;){if(0>(a[(Db<<2>>2)+B]|0)){var Ob=Fb+4|0,Eb=a[Ob>>2];if(0>(a[((Db|1)<<2>>2)+B]|0)){var fa=Eb|0;a[Ob>>2]=a[fa>>2];a[fa>>2]=Fb;a[e+1]=Eb;var Bb=Eb+4|0,Ja=Eb,Z=sb}else{a[s]=Eb,a[e+1]=Fb,Bb=Eb|0,Ja=Fb,Z=Eb}}else{var sa=Fb|0,xf=a[sa>>2];if(0<(a[((Db|1)<<2>>2)+B]|0)){var ra=xf+4|0;a[sa>>2]=a[ra>>2];a[ra>>2]=Fb;a[s]=xf;Bb=xf|0;Ja=Na;Z=xf}else{a[e+1]=xf,a[s]=Fb,Bb=xf+4|0,Ja=xf,Z=Fb}}var qa=a[Bb>>2],ba=Db+2|0;if((ba|0)<(bb|0)){sb=Z,s=sb>>2,Na=Ja,e=Na>>2,Fb=qa,Db=ba}else{qb=qa;R=Ja;kb=Z;break c}}}else{qb=ia,kb=R=P}}else{if(825==T){return h=P,D}}}}}else{qb=ia,kb=R=P}}while(0);var ic=0>(y|0),na=0>(O|0),oa=0!=(Da|0),Ba=1>(O|0),Ea=-y|0,Aa=qb,pc=R,Wc=kb;z=Wc>>2;c:for(;;){var Vb=Aa,Xd=pc;for(n=Xd>>2;;){var Cb=(ic?a[Vb+8>>2]:Vb+Ea|0)+X|0,Ma=na?a[Cb>>2]:Cb,wb=oa?J[Da](b,eb,Ma,M):Ba?ka(eb,Ma):Ve(eb,Ma,O);if(0==(wb|0)){var ub=Ca,va=Ia,Sa=Vb,Y=Xd,ua=Wc;T=736;break b}if(0<=(wb|0)){break}var Pa=Vb+4|0,lc=a[Pa>>2];f=lc>>2;if(0==(lc|0)){T=718;break c}var Gb=(ic?a[f+2]:lc+Ea|0)+X|0,Jd=na?a[Gb>>2]:Gb,Qa=oa?J[Da](b,eb,Jd,M):Ba?ka(eb,Jd):Ve(eb,Jd,O);if(0<=(Qa|0)){T=715;break}var Hc=lc|0;a[Pa>>2]=a[Hc>>2];a[Hc>>2]=Vb;a[n+1]=lc;var Va=a[f+1];if(0==(Va|0)){V=Wc;ab=lc;$a=Ia;jb=Ca;T=799;break b}else{Vb=Va,Xd=lc,n=Xd>>2}}if(715==T){T=0;if(0==(Qa|0)){T=716;break}a[z]=lc;a[n+1]=Vb;var oe=a[f];if(0==(oe|0)){V=lc;ab=Vb;$a=Ia;jb=Ca;T=799;break b}else{Aa=oe;pc=Vb;Wc=lc;z=Wc>>2;continue}}var hb=Vb|0,Qb=a[hb>>2];d=Qb>>2;if(0==(Qb|0)){T=735;break}var tc=(ic?a[d+2]:Qb+Ea|0)+X|0,Sc=na?a[tc>>2]:tc,ib=oa?J[Da](b,eb,Sc,M):Ba?ka(eb,Sc):Ve(eb,Sc,O);if(0<(ib|0)){var Ib=Qb+4|0;a[hb>>2]=a[Ib>>2];a[Ib>>2]=Vb;a[z]=Qb;var yb=a[d];if(0==(yb|0)){V=Qb;ab=Xd;$a=Ia;jb=Ca;T=799;break b}else{Aa=yb;pc=Xd;Wc=Qb;z=Wc>>2;continue}}if(0==(ib|0)){T=733;break}a[n+1]=Qb;a[z]=Vb;var rd=a[d+1];if(0==(rd|0)){V=Vb;ab=Qb;$a=Ia;jb=Ca;T=799;break b}else{Aa=rd,pc=Qb,Wc=Vb,z=Wc>>2}}733==T?(a[z]=Vb,ub=Ca,va=Ia,Sa=Qb,Y=Xd,ua=Vb,T=736):716==T?(a[n+1]=Vb,ub=Ca,va=Ia,Sa=lc,Y=Vb,ua=Wc,T=736):718==T?(a[n+1]=Vb,V=Wc,ab=Vb,$a=Ia,jb=Ca,T=799):735==T&&(V=a[z]=Vb,ab=Xd,$a=Ia,jb=Ca,T=799)}}while(0);do{if(736==T){if(0==(Sa|0)){V=ua,ab=Y,$a=va,jb=ub,T=799}else{if(a[m]|=32768,c=(Sa+4|0)>>2,a[ua>>2]=a[c],q=(Sa|0)>>2,a[Y+4>>2]=a[q],0!=(i&516|0)){var oc=Sa;g=oc>>2}else{if(0!=(i&8|0)){var Hb=P|0;a[c]=a[Hb>>2];a[q]=0;a[Hb>>2]=Sa;var Nb=Y,ve=ub;T=766}else{if(0!=(i&16|0)){var Rd=P+4|0;a[q]=a[Rd>>2];a[c]=0;a[Rd>>2]=Sa;var Bc=Y,Sd=ub;T=773}else{if(0!=(i&4098|0)){Q=Sa;La=Y;T=779;break a}if(0!=(i&2049|0)){if(0!=(a[a[j]+4>>2]&4|0)){oc=Sa,g=oc>>2}else{a[c]=0;var fd=P+4|0;a[q]=a[fd>>2];a[fd>>2]=Sa;var mb=Y,yc=Sa,db=ub;T=806}}else{if(0==(i&32|0)){return D=0,h=P,D}if(0==(a[a[j]+4>>2]&4|0)){a[va+4>>2]=0;var Lb=P+4|0;a[va>>2]=a[Lb>>2];a[Lb>>2]=va;var Tc=a[A]+16|0;a[Tc>>2]=a[Tc>>2]+1|0}else{var $b=a[w+4];if(0!=($b|0)){J[$b](b,ub,M)}if(0>(a[u]|0)){J[a[b+12>>2]](b,va,0,M)}}oc=Sa;g=oc>>2}}}}}}}while(0);do{if(799==T){if(a[ab+4>>2]=0,a[V>>2]=0,0!=(i&8|0)){Nb=ab,ve=jb,T=766}else{if(0!=(i&16|0)){Bc=ab,Sd=jb,T=773}else{if(0!=(i&516|0)){var Mc=jb,id=ab;break a}if(0!=(i&2049|0)){mb=ab,yc=0,db=jb,T=806}else{if(0==(i&32|0)){Mc=0;id=ab;break a}var Pb=a[A]+16|0;a[Pb>>2]=a[Pb>>2]+1|0;oc=$a;g=oc>>2}}}}}while(0);do{if(806==T){var Jb=M+12|0,Yb=a[Jb>>2],mf=0==(Yb|0)?db:0==(i&1|0)?db:J[Yb](b,db,M);if(0==(mf|0)){var nf=yc}else{if(-1<(y|0)){nf=mf+y|0}else{var Kb=J[a[b+12>>2]](b,0,12,M),af=Kb;if(0!=(Kb|0)){a[Kb+8>>2]=mf}else{if(0!=(a[Jb>>2]|0)){var yf=a[w+4];if(0!=(yf|0)&&0!=(i&1|0)){J[yf](b,mf,M)}}}nf=af}}if(0==(nf|0)){Mc=mf;id=mb;break a}var Td=a[A]+16|0,Fe=a[Td>>2];-1<(Fe|0)&&(a[Td>>2]=Fe+1|0);oc=nf;g=oc>>2}else{if(766==T){var gf=P+4|0,fe=a[gf>>2];if(0==(fe|0)){Mc=ve;id=Nb;break a}var fh=fe+4|0,df=a[fh>>2],jd=0==(df|0);b:do{if(jd){var md=fe,je=a[fe>>2]}else{for(var Qe=fe,Xf=fh,Uc=df;;){var bf=Uc|0;a[Xf>>2]=a[bf>>2];a[bf>>2]=Qe;var rk=Uc+4|0,Mb=a[rk>>2];if(0==(Mb|0)){md=Uc;je=Qe;break b}else{Qe=Uc,Xf=rk,Uc=Mb}}}}while(0);a[gf>>2]=je;oc=md;g=oc>>2}else{if(773==T){var Wb=P|0,ef=a[Wb>>2];if(0==(ef|0)){Mc=Sd;id=Bc;break a}var kd=ef|0,Tb=a[kd>>2],En=0==(Tb|0);b:do{if(En){var sk=ef,HC=a[ef+4>>2]}else{for(var Gr=ef,nd=kd,zd=Tb;;){var cf=zd+4|0;a[nd>>2]=a[cf>>2];a[cf>>2]=Gr;var uc=zd|0,ec=a[uc>>2];if(0==(ec|0)){sk=zd;HC=Gr;break b}else{Gr=zd,nd=uc,zd=ec}}}}while(0);a[Wb>>2]=HC;oc=sk;g=oc>>2}}}}while(0);a[g+1]=a[C];a[g]=a[C+1];var zi=0==(a[a[j]+4>>2]&8|0);b:do{if(zi){var we=oc}else{if(0==(i&516|0)){we=oc}else{for(var pe=0>(y|0),IC=0>(O|0),Kd=(pe?a[g+2]:oc+ -y|0)+X|0,Dd=IC?a[Kd>>2]:Kd,bc=0==(Da|0),rc=1>(O|0),Ye=-y|0,Ad=oc;;){var Ud=Ad+4|0,Vd=a[Ud>>2];if(0==(Vd|0)){we=Ad;break b}var jc=Vd|0,mc=a[jc>>2],gd=0==(mc|0);c:do{if(gd){var ud=Vd,Zb=jc}else{for(var qc=Vd,of=jc,Sb=mc;;){var kc=Sb+4|0;a[of>>2]=a[kc>>2];a[kc>>2]=qc;var Xb=Sb|0,Ac=a[Xb>>2];if(0==(Ac|0)){ud=Sb;Zb=Xb;break c}else{qc=Sb,of=Xb,Sb=Ac}}}}while(0);var ac=Ud|0;a[ac>>2]=ud;var dc=(pe?a[ud+8>>2]:ud+Ye|0)+X|0,fc=IC?a[dc>>2]:dc;if(0!=((bc?rc?ka(Dd,fc):Ve(Dd,fc,O):J[Da](b,Dd,fc,M))|0)){we=Ad;break b}a[ac>>2]=a[Zb>>2];a[Zb>>2]=Ad;Ad=ud}}}}while(0);a[a[A]+4>>2]=we;D=0>(y|0)?a[we+8>>2]:we+ -y|0;h=P;return D}}while(0);if(779==T){var nc=0>(y|0)?a[Q+8>>2]:Q+ -y|0,gc=a[w+4];if(0!=(gc|0)&&0!=(i&2|0)){J[gc](b,nc,M)}if(0>(a[u]|0)){J[a[b+12>>2]](b,Q,0,M)}var $c=a[A]+16|0,zc=a[$c>>2]-1|0;a[$c>>2]=zc;0>(zc|0)&&(a[a[A]+16>>2]=-1);Mc=nc;id=La}for(var Cc=id;;){var Ic=Cc+4|0,vc=a[Ic>>2];if(0==(vc|0)){break}else{Cc=vc}}a[Ic>>2]=a[C];a[a[A]+4>>2]=a[C+1];D=0!=(i&2|0)?Mc:0;h=P;return D}function Ag(b,r){var i,g=a[a[b+16>>2]>>2];i=(b+8|0)>>2;var q=a[i],c=q|0,d=a[c>>2],f=d&4096;if(0==(r|0)){if(0==(f|0)){var n;return-1}q=a[q+4>>2]}else{if(0==(a[q+16>>2]|0)){f=0,q=r}else{return-1}}a[c>>2]=d&-4097;d=a[i];c=a[d>>2];if(0==(c&3|0)){n=d+4|0;0==(c&12|0)?(a[n>>2]=0,a[a[i]+8>>2]=q|0):a[n>>2]=q;if(0!=(f|0)){return 0}a[a[i]+16>>2]=-1;return 0}a[d+4>>2]=0;i=a[i]>>2;if(0==(f|0)){a[i+4]=0;if(0==(q|0)){return 0}for(;;){if(i=a[q>>2],J[g](b,q,32),0==(i|0)){n=0;break}else{q=i}}return n}f=a[i+2];i=a[i+3];g=(i<<2)+f|0;if(0<(i|0)){i=f}else{return 0}for(;;){if(f=a[i>>2],0!=(f|0)&&(a[i>>2]=q,q=f|0,f=a[q>>2],a[q>>2]=0,q=f),i=i+4|0,i>>>0>=g>>>0){n=0;break}}return n}function yC(b,r){var i,g=b>>2;0!=(a[a[g+2]>>2]&4096|0)&&Ag(b,0);var q=0!=(r|0);if(q){if(0!=(a[a[r+8>>2]>>2]&4096|0)&&Ag(r,0),(a[r+16>>2]|0)==(a[g+4]|0)){var c=r}else{var d;return 0}}else{c=0}for(;0!=(c|0);){if((c|0)==(b|0)){d=0;i=896;break}c=a[c+28>>2]}if(896==i){return d}i=(b+28|0)>>2;c=a[i];0!=(c|0)&&(d=c+24|0,a[d>>2]=a[d>>2]-1|0);a[g+8]=0;a[i]=0;q?(a[i]=r,a[g]=372,g=r+24|0,a[g>>2]=a[g>>2]+1|0,d=r):(a[g]=a[a[g+4]>>2],d=c);return d}function JC(b,r,i){var g,q,c;if(0!=(i&99|0)){var d=J[a[a[b+16>>2]>>2]](b,r,i);return d}q=0==(i&516|0);a:do{if(q){var f=a[b+16>>2],n=0==(a[f+4>>2]&12|0);do{if(0==(i&384|0)){if(n){if(0==(i&24|0)){return d=0}q=(b+32|0)>>2;var h=a[q];if(0==(h|0)){var e=b;c=931}else{var s=a[a[h+4>>2]+8>>2];g=a[a[h+8>>2]+4>>2];if(((0>(s|0)?a[g+8>>2]:g+ -s|0)|0)==(r|0)){var j=r,m=h}else{e=b,c=931}}do{if(931==c){for(;;){c=0;if(0==(e|0)){break}var u=J[a[a[e+16>>2]>>2]](e,r,4);if(0!=(u|0)){c=933;break}e=a[e+28>>2]}if(933==c){a[q]=e,j=u,m=e}else{return d=a[q]=0}}}while(0);var h=0==(i&8|0),w=J[a[a[m+16>>2]>>2]](m,j,i),s=m;b:for(;;){for(g=s+16|0;0!=(w|0);){var A=b;for(;;){if((A|0)==(s|0)){d=w;c=949;break b}if(0!=(J[a[a[A+16>>2]>>2]](A,w,4)|0)){break}A=a[A+28>>2]}w=J[a[a[g>>2]>>2]](s,w,i)}s=a[s+28>>2];a[q]=s;if(0==(s|0)){d=0;c=955;break}g=a[a[s+16>>2]>>2];w=h?J[g](s,0,256):J[g](s,0,128)}if(955==c||949==c){return d}}}else{if(n){f=b;break a}}}while(0);if(0==(i&408|0)){return d=0}var B=0==(b|0);b:do{if(B){var C=0,P=0}else{c=0!=(i&136|0);d=0!=(i&272|0);e=0;j=b;u=m=0;for(q=f;;){q=J[a[q>>2]](j,r,i);do{if(0==(q|0)){n=u,h=m,s=e}else{n=a[j+4>>2];g=n>>2;h=a[g+1];s=a[g+5];g=q+a[g]|0;g=0>(h|0)?a[g>>2]:g;if(0!=(m|0)&&(n=0==(s|0)?1>(h|0)?ka(g,u):Ve(g,u,h):J[s](j,g,u,n),!(c&0>(n|0)|d&0<(n|0)))){n=u;h=m;s=e;break}n=g;h=q;s=j}}while(0);q=a[j+28>>2];if(0==(q|0)){C=s;P=h;break b}e=s;j=q;m=h;u=n;q=a[q+16>>2]}}}while(0);a[b+32>>2]=C;return d=P}f=b}while(0);for(;;){if(0==(f|0)){B=0;break}C=J[a[a[f+16>>2]>>2]](f,r,i);if(0!=(C|0)){B=C;break}f=a[f+28>>2]}a[b+32>>2]=f;return B}function Gn(b,r){for(var i=b|0,g=b+32|0,q=J[a[i>>2]](b,0,128);0!=(q|0);){var c=a[g>>2],d=J[a[i>>2]](b,q,8);if(0>(J[r](0==(c|0)?b:c,q,0)|0)){break}else{q=d}}}function tk(b,r,i){var g,q=r>>2,c=h;h+=8;var d=c+4;a[q]=0;var f=b+16|0,n=a[a[a[f>>2]+20>>2]>>2]>>>4&1;a[i>>2]=n;var e=a[Hr>>2],p=0==(e|0);a:do{if(p){g=n}else{var s=mb(b|0,a[e+8>>2]),j=m[s];if(0==j<<24>>24){g=n}else{var t=Ai|0;for(g=t>>2;;){var u=a[g];if(0==(u|0)){g=n;break a}if(j<<24>>24==m[u]<<24>>24&&0==(ka(s,u)|0)){break}t=t+12|0;g=t>>2}a[q]=a[g+1];g=a[g+2];a[i>>2]=g}}}while(0);n=a[Ir>>2];0!=(n|0)&1==(g|0)&&(n=mb(b|0,a[n+8>>2]),0!=m[n]<<24>>24&&KC(n,i));n=a[Jr>>2];0!=(n|0)&&1==(a[q]|0)&&(n=mb(b|0,a[n+8>>2]),0!=m[n]<<24>>24&&KC(n,r));0!=m[b+161|0]<<24>>24&&(b=a[b+12>>2],tk(Kr(a[b+20>>2],b,a[f>>2]),c,d),a[i>>2]|=a[c>>2],a[q]|=a[d>>2]);h=c}function KC(b,r){var i=h;h+=4;a[r>>2]=0;if(0!=m[b]<<24>>24){for(var g=b,q=0;;){a[i>>2]=0;var c=g,g=i,d=cc,f=h;h+=4;d=f>>2;a[d]=0;var n=Lr(c,Mr|0,f);if((n|0)==(c|0)){for(;!(n=Lr(c,gh|0,f),(c|0)==(n|0));){c=n}c=Lr(c,xe|0,f)}else{c=n}n=a[d];0!=(n|0)&0==(n&7|0)&&(n|=1,a[d]=n);d=n;n=a[g>>2];d|=n;a[g>>2]=d;h=f;g=c;a[r>>2]|=a[i>>2]<<(q<<3);q=q+1|0;if(!(0!=m[g]<<24>>24&4>(q|0))){break}}}h=i}function Cg(x,r){for(var i,g=r&7,q=Bi|0;;){if(0==(a[q+12>>2]|0)){var c=0;break}if((g|0)==(a[q>>2]|0)){i=989;break}else{q=q+16|0}}989==i&&(c=q+4|0,c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]));g=r>>>8&7;for(q=Bi|0;;){if(0==(a[q+12>>2]|0)){var d=c;break}if((g|0)==(a[q>>2]|0)){i=993;break}else{q=q+16|0}}993==i&&(d=q+4|0,d=c+(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]));c=r>>>16&7;for(g=Bi|0;;){if(0==(a[g+12>>2]|0)){var k=d;break}if((c|0)==(a[g>>2]|0)){i=997;break}else{g=g+16|0}}997==i&&(k=g+4|0,k=d+(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]));d=r>>>24&7;for(c=Bi|0;;){if(0==(a[c+12>>2]|0)){var n=k;i=1003;break}if((d|0)==(a[c>>2]|0)){break}else{c=c+16|0}}if(1003==i){return i=10*n,k=a[uk>>2],k=Xb(x|0,k,1,0),i*k}i=c+4|0;n=k+(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);i=10*n;k=a[uk>>2];k=Xb(x|0,k,1,0);return i*k}function Hn(x,r,i,g,q,c){var d,k,n,e,p=h;h+=80;d=p+8;var s=p+72,x=Cg(x,c),x=x*x;f[0]=x;a[s>>2]=b[0];a[s+4>>2]=b[1];a[q+12>>2]=c;c=g+3|0;k=(c<<4)+r|0;q=(q+32|0)>>2;n=k>>2;a[q]=a[n];a[q+1]=a[n+1];a[q+2]=a[n+2];a[q+3]=a[n+3];(g|0)>(i|0)&&(i=(g<<4)+r|0,k|=0,i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])-(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),k=(g<<4)+r+8|0,c=(c<<4)+r+8|0,c=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])-(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),g=i*i+c*c>2;n=((g<<4)+r|0)>>2;a[e]=a[n];a[e+1]=a[n+1];a[e+2]=a[n+2];a[e+3]=a[n+3];k=(d+32|0)>>2;i=((g+1<<4)+r|0)>>2;a[k]=a[i];a[k+1]=a[i+1];a[k+2]=a[i+2];a[k+3]=a[i+3];c=(d+16|0)>>2;x=((g+2<<4)+r|0)>>2;a[c]=a[x];a[c+1]=a[x+1];a[c+2]=a[x+2];a[c+3]=a[x+3];var j=d|0;d>>=2;a[d]=a[q];a[d+1]=a[q+1];a[d+2]=a[q+2];a[d+3]=a[q+3];a[p>>2]=j;a[p+4>>2]=s;vk(p,338,j,1);a[n]=a[e];a[n+1]=a[e+1];a[n+2]=a[e+2];a[n+3]=a[e+3];a[i]=a[k];a[i+1]=a[k+1];a[i+2]=a[k+2];a[i+3]=a[k+3];a[x]=a[c];a[x+1]=a[c+1];a[x+2]=a[c+2];a[x+3]=a[c+3];r=((g+3<<4)+r|0)>>2;a[r]=a[d];a[r+1]=a[d+1];a[r+2]=a[d+2];a[r+3]=a[d+3];h=p;return g}function In(x,r,i,g,q,c){var d,k,n,e=h;h+=80;var p=e+8,s=e+72,x=Cg(x,c),x=x*x;f[0]=x;a[s>>2]=b[0];a[s+4>>2]=b[1];a[q+8>>2]=c;c=(i<<4)+r|0;q=(q+16|0)>>2;d=c>>2;a[q]=a[d];a[q+1]=a[d+1];a[q+2]=a[d+2];a[q+3]=a[d+3];if((g|0)>(i|0)){c|=0;g=i+3|0;d=(g<<4)+r|0;c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])-(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]);d=(i<<4)+r+8|0;k=(g<<4)+r+8|0;d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0])-(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);var j=c*c+d*d>2;k=((j+3<<4)+r|0)>>2;a[n]=a[k];a[n+1]=a[k+1];a[n+2]=a[k+2];a[n+3]=a[k+3];d=(p+16|0)>>2;c=((j+2<<4)+r|0)>>2;a[d]=a[c];a[d+1]=a[c+1];a[d+2]=a[c+2];a[d+3]=a[c+3];g=(p+32|0)>>2;x=((j+1<<4)+r|0)>>2;a[g]=a[x];a[g+1]=a[x+1];a[g+2]=a[x+2];a[g+3]=a[x+3];var m=p+48|0,i=m>>2;a[i]=a[q];a[i+1]=a[q+1];a[i+2]=a[q+2];a[i+3]=a[q+3];a[e>>2]=m;a[e+4>>2]=s;vk(e,338,p|0,0);r=((j<<4)+r|0)>>2;a[r]=a[i];a[r+1]=a[i+1];a[r+2]=a[i+2];a[r+3]=a[i+3];a[x]=a[g];a[x+1]=a[g+1];a[x+2]=a[g+2];a[x+3]=a[g+3];a[c]=a[d];a[c+1]=a[d+1];a[c+2]=a[d+2];a[c+3]=a[d+3];a[k]=a[n];a[k+1]=a[n+1];a[k+2]=a[n+2];a[k+3]=a[n+3];h=e;return j}function LC(x,r,i,g,q,c,d){var k,n,h,e,s,j,m;n=0!=(c|0);m=0==(d|0);if(n&(m^1)&(g|0)==(i|0)){h=(g<<4)+r|0;var i=h|0,u=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),i=(g<<4)+r+8|0;j=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);n=g+3|0;k=(n<<4)+r|0;m=(k|0)>>2;i=(b[0]=a[m],b[1]=a[m+1],f[0]);s=((n<<4)+r+8|0)>>2;n=(b[0]=a[s],b[1]=a[s+1],f[0]);e=Cg(x,c);var x=Cg(x,d),w=u-i,A=j-n,w=$c(w*w+A*A);x+e>2]=b[0];a[B+4>>2]=b[1];e=(A<<4)+r+8|0;f[0]=w;a[e>>2]=b[0];a[e+4>>2]=b[1];h>>=2;j>>=2;a[h]=a[j];a[h+1]=a[j+1];a[h+2]=a[j+2];a[h+3]=a[j+3];f[0]=x;a[m]=b[0];a[m+1]=b[1];f[0]=u;a[s]=b[0];a[s+1]=b[1];r=((g+2<<4)+r|0)>>2;g=k>>2;a[r]=a[g];a[r+1]=a[g+1];a[r+2]=a[g+2];a[r+3]=a[g+3];a[q+12>>2]=d;r=q+32|0;d=q+40|0;a[q+8>>2]=c;f[0]=i;a[r>>2]=b[0];a[r+4>>2]=b[1];f[0]=n;a[d>>2]=b[0];a[d+4>>2]=b[1]}else{if(!m){w=Cg(x,d);k=(g<<4)+r|0;u=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);k=(g<<4)+r+8|0;j=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);m=g+3|0;h=(m<<4)+r|0;s=(h|0)>>2;k=(b[0]=a[s],b[1]=a[s+1],f[0]);e=((m<<4)+r+8|0)>>2;m=(b[0]=a[e],b[1]=a[e+1],f[0]);A=u-k;B=j-m;A=.9*$c(A*A+B*B);w=w>2]=b[0];a[P+4>>2]=b[1];u=(C<<4)+r+8|0;f[0]=j;a[u>>2]=b[0];a[u+4>>2]=b[1];f[0]=A;a[s]=b[0];a[s+1]=b[1];f[0]=B;a[e]=b[0];a[e+1]=b[1];g=((g+2<<4)+r|0)>>2;h>>=2;a[g]=a[h];a[g+1]=a[h+1];a[g+2]=a[h+2];a[g+3]=a[h+3];a[q+12>>2]=d;d=q+32|0;f[0]=k;a[d>>2]=b[0];a[d+4>>2]=b[1];d=q+40|0;f[0]=m;a[d>>2]=b[0];a[d+4>>2]=b[1];k=w}n&&(h=Cg(x,c),n=(i<<4)+r|0,d=n|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),g=(i<<4)+r+8|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),m=i+3|0,x=(m<<4)+r|0,x=(b[0]=a[x>>2],b[1]=a[x+4>>2],f[0]),m=(m<<4)+r+8|0,m=(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0]),s=d-x,u=g-m,s=.9*$c(s*s+u*u),g==m?(h=d>2]=b[0],a[j+4>>2]=b[1],h=(u<<4)+r+8|0,f[0]=s,a[h>>2]=b[0],a[h+4>>2]=b[1],n>>=2,k>>=2,a[n]=a[k],a[n+1]=a[k+1],a[n+2]=a[k+2],a[n+3]=a[k+3],i=i+2|0,n=(i<<4)+r|0,f[0]=x,a[n>>2]=b[0],a[n+4>>2]=b[1],r=(i<<4)+r+8|0,f[0]=m,a[r>>2]=b[0],a[r+4>>2]=b[1],a[q+8>>2]=c,c=q+16|0,f[0]=d,a[c>>2]=b[0],a[c+4>>2]=b[1],q=q+24|0,f[0]=g,a[q>>2]=b[0],a[q+4>>2]=b[1])}}function hh(x,r,i,g,q,c){var g=g-r,q=q-i,c=10*c/($c(g*g+q*q)+1e-4),d=(g+(0<=g?1e-4:-1e-4))*c,k=(q+(0<=q?1e-4:-1e-4))*c,n=.5*d,q=.5*k,g=r-q,c=i-n,q=q+r,n=n+i,h=g+d,e=c+k,s=q+d,d=n+k,i=h>s?h:s,i=q>i?q:i,r=e>d?e:d,r=n>r?n:r,h=h>2]=b[0];a[e+4>>2]=b[1];q=x+8|0;f[0]=c>2]=b[0];a[q+4>>2]=b[1];q=x+16|0;f[0]=g>i?g:i;a[q>>2]=b[0];a[q+4>>2]=b[1];x=x+24|0;f[0]=c>r?c:r;a[x>>2]=b[0];a[x+4>>2]=b[1]}function Ci(x,r,i,g,q,c,d,k,n){var e,p=h;h+=16;var s;e=(a[x+16>>2]+12|0)>>2;var j=a[e];a[e]=r;Ke(x,a[a[x>>2]+296>>2]);for(var r=q-i,q=c-g,m=10/($c(r*r+q*q)+1e-4),c=(r+(0<=r?1e-4:-1e-4))*m,r=(q+(0<=q?1e-4:-1e-4))*m,q=p|0,m=p+8|0,u=0,w=i,A=g;;){if(4<=(u|0)){s=1054;break}var B=n>>(u<<3)&255;if(0==(B|0)){s=1053;break}for(var g=p,i=x,C=c,P=r,T=d,gb=k,$e=cc,M=B&7,y=Bi|0;;){var O=a[y>>2];if(0==(O|0)){var D=w,Da=A;break}if((M|0)==(O|0)){$e=1058;break}else{y=y+16|0}}1058==$e&&(D=y+4|0,D=(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0])*T,Da=D*C,P*=D,J[a[y+12>>2]](i,w,A,Da,P,T,gb,B),D=Da+w,Da=P+A);i=g|0;f[0]=D;a[i>>2]=b[0];a[i+4>>2]=b[1];g=g+8|0;f[0]=Da;a[g>>2]=b[0];a[g+4>>2]=b[1];u=u+1|0;w=(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]);A=(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0])}1053==s?(a[e]=j,h=p):1054==s&&(a[e]=j,h=p)}function MC(x,r,i,g,q,c,d,k){var n,e,c=h;h+=80;var d=4>2;0==(k&16|0)?(f[0]=g,a[e]=b[0],a[e+1]=b[1],e=c+72|0,f[0]=q,a[e>>2]=b[0],a[e+4>>2]=b[1],e=c>>2,n>>=2,a[e]=a[n],a[e+1]=a[n+1],a[e+2]=a[n+2],a[e+3]=a[n+3],n=c+16|0,f[0]=g-p,a[n>>2]=b[0],a[n+4>>2]=b[1],n=c+24|0,f[0]=q-d,a[n>>2]=b[0],a[n+4>>2]=b[1],n=c+32|0,f[0]=r,a[n>>2]=b[0],a[n+4>>2]=b[1],r=c+40|0,f[0]=i,a[r>>2]=b[0],a[r+4>>2]=b[1],i=c+48|0,f[0]=g+p,a[i>>2]=b[0],a[i+4>>2]=b[1],i=c+56|0,f[0]=q+d,a[i>>2]=b[0],a[i+4>>2]=b[1]):(f[0]=r,a[e]=b[0],a[e+1]=b[1],e=c+72|0,f[0]=i,a[e>>2]=b[0],a[e+4>>2]=b[1],e=c>>2,n>>=2,a[e]=a[n],a[e+1]=a[n+1],a[e+2]=a[n+2],a[e+3]=a[n+3],n=c+16|0,f[0]=r-p,a[n>>2]=b[0],a[n+4>>2]=b[1],n=c+24|0,f[0]=i-d,a[n>>2]=b[0],a[n+4>>2]=b[1],n=c+32|0,f[0]=g,a[n>>2]=b[0],a[n+4>>2]=b[1],g=c+40|0,f[0]=q,a[g>>2]=b[0],a[g+4>>2]=b[1],q=c+48|0,f[0]=p+r,a[q>>2]=b[0],a[q+4>>2]=b[1],r=c+56|0,f[0]=d+i,a[r>>2]=b[0],a[r+4>>2]=b[1]);0!=(k&32|0)?zc(x,c|0,3,(k>>>3&1^1)&255):0==(k&64|0)?zc(x,c+16|0,3,(k>>>3&1^1)&255):zc(x,c+32|0,3,(k>>>3&1^1)&255);h=c}function NC(x,r,i,g,q,c,d,k){var n,e,p,s=h;h+=144;var j=4*c,m=k&16,j=j>=d|0==(m|0)?.45:.45*(d/j);if(1>2;0==(u|0)?(f[0]=n,a[p]=b[0],a[p+1]=b[1],n=s+136|0,f[0]=e,a[n>>2]=b[0],a[n+4>>2]=b[1],e=s>>2,n=A>>2,a[e]=a[n],a[e+1]=a[n+1],a[e+2]=a[n+2],a[e+3]=a[n+3],e=s+16|0,f[0]=r-m,a[e>>2]=b[0],a[e+4>>2]=b[1],e=s+24|0,f[0]=i-j,a[e>>2]=b[0],a[e+4>>2]=b[1],e=s+32|0,f[0]=w-d,a[e>>2]=b[0],a[e+4>>2]=b[1],e=s+40|0,f[0]=g-c,a[e>>2]=b[0],a[e+4>>2]=b[1],e=s+48|0,f[0]=r,a[e>>2]=b[0],a[e+4>>2]=b[1],e=s+56|0,f[0]=i,a[e>>2]=b[0],a[e+4>>2]=b[1],e=s+64|0,f[0]=r,a[e>>2]=b[0],a[e+4>>2]=b[1],e=s+72|0,f[0]=i,a[e>>2]=b[0],a[e+4>>2]=b[1],e=s+80|0,f[0]=r,a[e>>2]=b[0],a[e+4>>2]=b[1],e=s+88|0,f[0]=i,a[e>>2]=b[0],a[e+4>>2]=b[1],e=s+96|0,f[0]=w+d,a[e>>2]=b[0],a[e+4>>2]=b[1],d=s+104|0,f[0]=g+c,a[d>>2]=b[0],a[d+4>>2]=b[1],c=s+112|0,f[0]=m+r,a[c>>2]=b[0],a[c+4>>2]=b[1],m=s+120|0,f[0]=j+i):(f[0]=r,a[p]=b[0],a[p+1]=b[1],r=s+136|0,f[0]=i,a[r>>2]=b[0],a[r+4>>2]=b[1],i=s>>2,r=A>>2,a[i]=a[r],a[i+1]=a[r+1],a[i+2]=a[r+2],a[i+3]=a[r+3],i=s+16|0,f[0]=n-m,a[i>>2]=b[0],a[i+4>>2]=b[1],i=s+24|0,f[0]=e-j,a[i>>2]=b[0],a[i+4>>2]=b[1],i=s+32|0,f[0]=w-d,a[i>>2]=b[0],a[i+4>>2]=b[1],i=s+40|0,f[0]=g-c,a[i>>2]=b[0],a[i+4>>2]=b[1],i=s+48|0,f[0]=n-d,a[i>>2]=b[0],a[i+4>>2]=b[1],i=s+56|0,f[0]=e-c,a[i>>2]=b[0],a[i+4>>2]=b[1],i=s+64|0,f[0]=n,a[i>>2]=b[0],a[i+4>>2]=b[1],i=s+72|0,f[0]=e,a[i>>2]=b[0],a[i+4>>2]=b[1],i=s+80|0,f[0]=n+d,a[i>>2]=b[0],a[i+4>>2]=b[1],i=s+88|0,f[0]=e+c,a[i>>2]=b[0],a[i+4>>2]=b[1],i=s+96|0,f[0]=w+d,a[i>>2]=b[0],a[i+4>>2]=b[1],d=s+104|0,f[0]=g+c,a[d>>2]=b[0],a[d+4>>2]=b[1],c=s+112|0,f[0]=n+m,a[c>>2]=b[0],a[c+4>>2]=b[1],m=s+120|0,f[0]=e+j);a[m>>2]=b[0];a[m+4>>2]=b[1];0!=(k&32|0)?zc(x,q,6,1):0==(k&64|0)?zc(x,q,9,1):zc(x,s+48|0,6,1);h=s}function OC(x,r,i,g,q,c,d,k){var n,e,p,s,j,m=h;h+=64;var u=r+g,w=i+q,A=.2*g+r,B=.2*q+i,C=.6*g+r,P=.6*q+i,T=m|0;j=(m|0)>>2;f[0]=A-q;a[j]=b[0];a[j+1]=b[1];s=(m+8|0)>>2;f[0]=B+g;a[s]=b[0];a[s+1]=b[1];p=(m+16|0)>>2;f[0]=A+q;a[p]=b[0];a[p+1]=b[1];e=(m+24|0)>>2;f[0]=B-g;a[e]=b[0];a[e+1]=b[1];n=(m+32|0)>>2;f[0]=C+q;a[n]=b[0];a[n+1]=b[1];d=(m+40|0)>>2;f[0]=P-g;a[d]=b[0];a[d+1]=b[1];c=(m+48|0)>>2;f[0]=C-q;a[c]=b[0];a[c+1]=b[1];q=(m+56|0)>>2;f[0]=P+g;a[q]=b[0];a[q+1]=b[1];0==(k&32|0)?0!=(k&64|0)&&(f[0]=A,a[p]=b[0],a[p+1]=b[1],f[0]=B,a[e]=b[0],a[e+1]=b[1],f[0]=C,a[n]=b[0],a[n+1]=b[1],f[0]=P,a[d]=b[0],a[d+1]=b[1]):(f[0]=A,a[j]=b[0],a[j+1]=b[1],f[0]=B,a[s]=b[0],a[s+1]=b[1],f[0]=C,a[c]=b[0],a[c+1]=b[1],f[0]=P,a[q]=b[0],a[q+1]=b[1]);zc(x,T,4,1);f[0]=r;a[j]=b[0];a[j+1]=b[1];f[0]=i;a[s]=b[0];a[s+1]=b[1];f[0]=u;a[p]=b[0];a[p+1]=b[1];f[0]=w;a[e]=b[0];a[e+1]=b[1];Gd(x,T,2);h=m}function PC(x,r,i,g,q,c,d,k){var n,e,p,s,j,m,u,c=h;h+=64;n=-.4*q;var d=.4*g,w=.8*g+r,A=.8*q+i,g=r+g,B=i+q,C=c|0;u=(c|0)>>2;f[0]=n+r;a[u]=b[0];a[u+1]=b[1];m=(c+8|0)>>2;f[0]=d+i;a[m]=b[0];a[m+1]=b[1];j=(c+16|0)>>2;f[0]=r-n;a[j]=b[0];a[j+1]=b[1];s=(c+24|0)>>2;f[0]=i-d;a[s]=b[0];a[s+1]=b[1];p=(c+32|0)>>2;f[0]=w-n;a[p]=b[0];a[p+1]=b[1];e=(c+40|0)>>2;f[0]=A-d;a[e]=b[0];a[e+1]=b[1];q=(c+48|0)>>2;f[0]=w+n;a[q]=b[0];a[q+1]=b[1];n=(c+56|0)>>2;f[0]=A+d;a[n]=b[0];a[n+1]=b[1];0==(k&32|0)?0!=(k&64|0)&&(f[0]=r,a[j]=b[0],a[j+1]=b[1],f[0]=i,a[s]=b[0],a[s+1]=b[1],f[0]=w,a[p]=b[0],a[p+1]=b[1],f[0]=A,a[e]=b[0],a[e+1]=b[1]):(f[0]=r,a[u]=b[0],a[u+1]=b[1],f[0]=i,a[m]=b[0],a[m+1]=b[1],f[0]=w,a[q]=b[0],a[q+1]=b[1],f[0]=A,a[n]=b[0],a[n+1]=b[1]);zc(x,C,4,(k>>>3&1^1)&255);f[0]=w;a[u]=b[0];a[u+1]=b[1];f[0]=A;a[m]=b[0];a[m+1]=b[1];f[0]=g;a[j]=b[0];a[j+1]=b[1];f[0]=B;a[s]=b[0];a[s+1]=b[1];Gd(x,C,2);h=c}function QC(x,r,i,g,q,c,d,k){c=h;h+=80;var n=-q/3,e=g/3,p=.5*g+r,s=.5*q+i,d=c|0,j=c+64|0,m=j|0;f[0]=r+g;a[m>>2]=b[0];a[m+4>>2]=b[1];g=c+72|0;f[0]=i+q;a[g>>2]=b[0];a[g+4>>2]=b[1];q=c>>2;g=j>>2;a[q]=a[g];a[q+1]=a[g+1];a[q+2]=a[g+2];a[q+3]=a[g+3];q=c+16|0;f[0]=p+n;a[q>>2]=b[0];a[q+4>>2]=b[1];q=c+24|0;f[0]=s+e;a[q>>2]=b[0];a[q+4>>2]=b[1];q=c+32|0;g=q|0;f[0]=r;a[g>>2]=b[0];a[g+4>>2]=b[1];r=c+40|0;f[0]=i;a[r>>2]=b[0];a[r+4>>2]=b[1];i=c+48|0;f[0]=p-n;a[i>>2]=b[0];a[i+4>>2]=b[1];i=c+56|0;f[0]=s-e;a[i>>2]=b[0];a[i+4>>2]=b[1];0!=(k&32|0)?zc(x,q,3,(k>>>3&1^1)&255):(i=(k>>>3&1^1)&255,0==(k&64|0)?zc(x,d,4,i):zc(x,d,3,i));h=c}function Lr(b,r,i){for(var g;;){var q=a[r>>2];if(0==(q|0)){var c=b;g=1124;break}var d=Ba(q);if(0==(td(b,q,d)|0)){break}else{r=r+8|0}}if(1124==g){return c}a[i>>2]|=a[r+4>>2];return b+d|0}function RC(x){var r,i=h;h+=52;r=i>>2;var g=V(x|0,Di|0);if(0==(g|0)||0==m[g]<<24>>24){return h=i,0}0!=m[ld]<<24>>24&&mk(ih);var q=DQa(g,0,80);0==(q|0)&&(la(0,SC|0,(j=h,h+=4,a[j>>2]=a[x+12>>2],j)),la(3,TC|0,(j=h,h+=4,a[j>>2]=g,j)));if(0==m[ld]<<24>>24){return r=q,h=i,r}x=Jn();EQa(q,i);Va(a[oa>>2],UC|0,(j=h,h+=12,a[j>>2]=a[r],f[0]=x,a[j+4>>2]=b[0],a[j+8>>2]=b[1],j));x=a[r+3];Va(a[oa>>2],VC|0,(j=h,h+=8,a[j>>2]=a[r+2],a[j+4>>2]=x,j));x=a[r+5];Va(a[oa>>2],WC|0,(j=h,h+=8,a[j>>2]=a[r+4],a[j+4>>2]=x,j));x=a[r+7];Va(a[oa>>2],XC|0,(j=h,h+=8,a[j>>2]=a[r+6],a[j+4>>2]=x,j));Va(a[oa>>2],YC|0,(j=h,h+=4,a[j>>2]=a[r+1],j));Va(a[oa>>2],ZC|0,(j=h,h+=4,a[j>>2]=a[r+8],j));r=q;h=i;return r}function wk(x){var r,i=h,g=fa(244);r=g>>2;0==(g|0)&&la(1,$C|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j));var q=x+16|0,x=a[q>>2];a[r]=x;a[q>>2]=g;if(0==(x|0)){a[r+22]=3,a[r+23]=0,r=g+96|0,f[0]=1,a[r>>2]=b[0],a[r+4>>2]=b[1]}else{for(var q=(x+16|0)>>2,c=(g+16|0)>>2,d=q+9;q>2;c=(g+52|0)>>2;for(d=q+9;q>2];a[r+23]=a[x+92>>2];r=x+96|0;r=(b[0]=a[r>>2],b[1]=a[r+4>>2],f[0]);x=g+96|0;f[0]=r;a[x>>2]=b[0];a[x+4>>2]=b[1]}h=i;return g}function Ei(b){var r=b+16|0,i=a[r>>2],b=i>>2;0==(i|0)&&sa(Fi|0,110,aD|0,Nr|0);G(a[b+38]);G(a[b+37]);G(a[b+39]);G(a[b+40]);G(a[b+41]);G(a[b+42]);G(a[b+43]);G(a[b+44]);G(a[b+45]);G(a[b+46]);G(a[b+47]);G(a[b+48]);G(a[b+49]);G(a[b+53]);G(a[b+56]);G(a[b+55]);a[r>>2]=a[b];G(i)}function Or(b,r,i,g,q,c,d){var f,n=a[b+16>>2];f=n>>2;b=a[b+148>>2];0==(b&32768|0)|0==(r|0)||(a[f+33]=r);0==(b&65536|0)?r=0:(a[f+38]=ec(c,d),0==(i|0)?r=0:0==m[i]<<24>>24?r=0:(a[f+37]=ec(i,d),r=1));i=0==(b&4194304|0);a:do{if(i){c=r}else{do{if(0!=(g|0)&&0!=m[g]<<24>>24){a[f+42]=ec(g,d);g=n+200|0;a[g>>2]|=1;c=1;break a}}while(0);c=a[f+33];0==(c|0)?c=r:(a[f+42]=Hb(c),c=1)}}while(0);if(0==(b&8388608|0)|0==(q|0)||0==m[q]<<24>>24){return c}a[f+46]=ec(q,d);return 1}function Kn(b,r,i){var g=h;h+=32;var b=a[a[a[a[b>>2]+128>>2]+44>>2]+92>>2],q=V(r,Pr|0);if(0!=(q|0)&&0!=m[q]<<24>>24){return h=g,q}q=a[r>>2]<<28>>28;if(1==(q|0)){var c=jh|0,d=a[r+16>>2]}else{2==(q|0)?(c=Dg|0,d=a[r+20>>2]):3==(q|0)&&(c=Yf|0,d=a[a[r+36>>2]+16>>2])}if(0==(b|0)){r=(i+4|0)>>2,b=i+8|0}else{db(i,b);var q=i+4|0,r=q>>2,f=a[r],b=i+8|0;f>>>0>2]>>>0||(na(i,1),f=a[r]);a[r]=f+1|0;m[f]=95;r=q>>2}db(i,c);c=g|0;Ma(c,bD|0,(j=h,h+=4,a[j>>2]=d,j));db(i,c);d=a[r];d>>>0>2]>>>0||(na(i,1),d=a[r]);m[d]=0;i=a[i>>2];a[r]=i;h=g;return i}function Ln(b,r){var i,g,q,c,d=h;c=r>>2;r=h;h+=32;a[r>>2]=a[c];a[r+4>>2]=a[c+1];a[r+8>>2]=a[c+2];a[r+12>>2]=a[c+3];a[r+16>>2]=a[c+4];a[r+20>>2]=a[c+5];a[r+24>>2]=a[c+6];a[r+28>>2]=a[c+7];i=a[b+16>>2];q=i>>2;c=a[b+148>>2];if(0!=(c&4259840|0)){var f=0!=(c&131072|0);g=i+204|0;f?(a[g>>2]=0,a[q+52]=2):(a[g>>2]=2,a[q+52]=4);g=i+212|0;G(a[g>>2]);q=fa(a[q+52]<<4);i=q>>2;a[g>>2]=q;g=r>>2;a[i]=a[g];a[i+1]=a[g+1];a[i+2]=a[g+2];a[i+3]=a[g+3];g=(q+16|0)>>2;i=(r+16|0)>>2;a[g]=a[i];a[g+1]=a[i+1];a[g+2]=a[i+2];a[g+3]=a[i+3];0==(c&8192|0)&&Re(b,q,q,2);f||Mn(q)}h=d}function xk(x,r){var i,g,q,c,d=h;h+=144;q=d+64;g=d+128;c=r|0;var k=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]);c=(x+16|0)>>2;var n=(b[0]=a[c],b[1]=a[c+1],f[0]),e=k>n;if(!e&&(i=x|0,i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),k>=i)){var p=r+8|0,s=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),p=x+24|0,p=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]);if(s<=p){var j=x+8|0,j=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]);if(s>=j&&(s=r+16|0,s=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]),!(s>n|s>2],b[1]=a[s+4>>2],f[0]),!(s>p|s>2],b[1]=a[s+4>>2],f[0]),!(s>n|s>2],b[1]=a[s+4>>2],f[0]),!(s>p|s>2],b[1]=a[s+4>>2],f[0]),!(s>n|s>2],b[1]=a[i+4>>2],f[0]),!(i>p|i>2;g=(x+8|0)>>2;i=(x|0)>>2;if(e){f[0]=k,a[c]=b[0],a[c+1]=b[1]}else{if(k<(b[0]=a[i],b[1]=a[i+1],f[0])){f[0]=k,a[i]=b[0],a[i+1]=b[1]}k=n}n=r+8|0;n=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]);e=(b[0]=a[q],b[1]=a[q+1],f[0]);if(n>e){f[0]=n,a[q]=b[0],a[q+1]=b[1]}else{if(n<(b[0]=a[g],b[1]=a[g+1],f[0])){f[0]=n,a[g]=b[0],a[g+1]=b[1]}n=e}e=r+16|0;e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);if(e>k){f[0]=e,a[c]=b[0],a[c+1]=b[1],k=e}else{if(e<(b[0]=a[i],b[1]=a[i+1],f[0])){f[0]=e,a[i]=b[0],a[i+1]=b[1]}}e=r+24|0;e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);if(e>n){f[0]=e,a[q]=b[0],a[q+1]=b[1],n=e}else{if(e<(b[0]=a[g],b[1]=a[g+1],f[0])){f[0]=e,a[g]=b[0],a[g+1]=b[1]}}e=r+32|0;e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);if(e>k){f[0]=e,a[c]=b[0],a[c+1]=b[1],k=e}else{if(e<(b[0]=a[i],b[1]=a[i+1],f[0])){f[0]=e,a[i]=b[0],a[i+1]=b[1]}}e=r+40|0;e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);if(e>n){f[0]=e,a[q]=b[0],a[q+1]=b[1],n=e}else{if(e<(b[0]=a[g],b[1]=a[g+1],f[0])){f[0]=e,a[g]=b[0],a[g+1]=b[1]}}e=r+48|0;e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);if(e>k){f[0]=e,a[c]=b[0],a[c+1]=b[1]}else{if(e<(b[0]=a[i],b[1]=a[i+1],f[0])){f[0]=e,a[i]=b[0],a[i+1]=b[1]}}c=r+56|0;c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]);if(c>n){f[0]=c,a[q]=b[0],a[q+1]=b[1]}else{if(c<(b[0]=a[g],b[1]=a[g+1],f[0])){f[0]=c,a[g]=b[0],a[g+1]=b[1]}}}h=d}function cD(x){var r=x|0,r=(b[0]=a[r>>2],b[1]=a[r+4>>2],f[0]),i=x+8|0,i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),g=x+48|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),q=x+56|0,q=(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]),c=x+16|0,d=x+24|0,c=dD(r,i,g,q,(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0])),d=x+32|0,x=x+40|0;return 4>c&4>dD(r,i,g,q,(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),(b[0]=a[x>>2],b[1]=a[x+4>>2],f[0]))&1}function Qr(b){var r=a[b+192>>2];-1<(r|0)?(r|0)<(a[b+160>>2]|0)?(r=a[b+196>>2],b=-1<(r|0)?(r|0)<(a[b+164>>2]|0):0):b=0:b=0;return b&1}function Rr(x,r){var i,g,q=x>>2;i=a[q+37];var c=x+348|0,d=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),c=x+424|0;g=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]);var c=d*g/72,k=x+480|0;f[0]=c;a[k>>2]=b[0];a[k+4>>2]=b[1];var k=x+432|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),d=d*k/72,n=x+488|0;f[0]=d;a[n>>2]=b[0];a[n+4>>2]=b[1];n=x+512|0;f[0]=g/72;a[n>>2]=b[0];a[n+4>>2]=b[1];k/=72;g=(x+520|0)>>2;f[0]=k;a[g]=b[0];a[g+1]=b[1];0!=(i&4096|a[Ic>>2]|0)&&(f[0]=-1*k,a[g]=b[0],a[g+1]=b[1]);g=a[q+110]>>>0;0==(a[q+89]|0)?(k=x+360|0,f[0]=g/c,a[k>>2]=b[0],a[k+4>>2]=b[1],c=x+368|0,f[0]=(a[q+111]>>>0)/d,a[c>>2]=b[0],a[c+4>>2]=b[1]):(k=x+368|0,f[0]=g/d,a[k>>2]=b[0],a[k+4>>2]=b[1],d=x+360|0,f[0]=(a[q+111]>>>0)/c,a[d>>2]=b[0],a[d+4>>2]=b[1]);q=r|0;yk(x,jc(q,$(q,Nn|0),Y|0));q=wk(x)>>2;a[q+1]=0;a[q+2]=r;a[q+3]=0;Sr(x,a[r+48>>2],r|0);c=a[x>>2];q=a[x+60>>2];if(0!=(q|0)){d=a[q+8>>2];if(0!=(d|0)){J[d](x)}d=V(r|0,zk|0);if(0!=(d|0)&&0!=m[d]<<24>>24&&(c=c+300|0,Tr(a[x+68>>2],d,c),q=a[q+92>>2],0!=(q|0))){J[q](x,c)}}0!=(i&2|0)&&eD(x,r);i=ra(r);q=0==(i|0);a:do{if(!q){for(c=i;;){if(m[c+132|0]=0,c=ba(r,c),0==(c|0)){break a}}}}while(0);i=h;q=a[a[x>>2]+280>>2];c=x+152|0;a[c>>2]=q;1<(q|0)&&0==(a[x+148>>2]&64|0)&&(la(0,fD|0,(j=h,h+=4,a[j>>2]=a[x+52>>2],j)),a[c>>2]=1);a[x+156>>2]=1;h=i;i=(x+152|0)>>2;c=a[i];q=x+156|0;if(0!=((a[q>>2]|0)<=(c|0)&1)<<24>>24){for(;;){1<(c|0)&&(c=a[x+60>>2],0!=(c|0)&&(c=a[c+16>>2],0!=(c|0)&&(d=a[x+156>>2],J[c](x,a[a[a[x>>2]+276>>2]+(d<<2)>>2],d,a[x+152>>2]))));c=x+168|0;d=x+192|0;g=a[c+4>>2];a[d>>2]=a[c>>2];a[d+4>>2]=g;c=0==Qr(x)<<24>>24;a:do{if(!c){for(;;){if(gD(x,r),hD(x),0==Qr(x)<<24>>24){break a}}}}while(0);if(1<(a[i]|0)&&(c=a[x+60>>2],0!=(c|0)&&(c=a[c+20>>2],0!=(c|0)))){J[c](x)}c=x+156|0;a[c>>2]=a[c>>2]+1|0;c=a[i];if(0==((a[q>>2]|0)<=(c|0)&1)<<24>>24){break}}}i=a[x+60>>2];if(0!=(i|0)&&(i=a[i+12>>2],0!=(i|0))){J[i](x)}i=a[x+76>>2];if(0!=(i|0)&&(i=a[i+4>>2],0!=(i|0))){J[i](x)}iD(x);Ei(x)}function eD(a,b){$b(a,qe|0);var i=b|0,g=V(i,zk|0);0!=(g|0)&&0!=m[g]<<24>>24&&$b(a,g);i=V(i,Eg|0);0!=(i|0)&&0!=m[i]<<24>>24&&Pa(a,i);jD(a,b);i=ra(b);if(0!=(i|0)){for(;;){var g=i|0,q=V(g,kh|0);0!=(q|0)&&0!=m[q]<<24>>24&&Pa(a,q);q=V(g,Ak|0);0!=(q|0)&&0!=m[q]<<24>>24&&$b(a,q);g=V(g,Eg|0);0!=(g|0)&&0!=m[g]<<24>>24&&Pa(a,g);g=Ib(b,i);q=0==(g|0);a:do{if(!q){for(var c=g;;){var d=c|0,f=V(d,kh|0);do{if(0!=(f|0)&&0!=m[f]<<24>>24){if(0==(Jc(f,58)|0)){Pa(a,f)}else{var n=Hb(f),h=he(n,zf|0),e=0==(h|0);b:do{if(!e){for(var j=h;;){if(0!=m[j]<<24>>24&&Pa(a,j),j=he(0,zf|0),0==(j|0)){break b}}}}while(0);G(n)}}}while(0);d=V(d,Eg|0);0!=(d|0)&&0!=m[d]<<24>>24&&Pa(a,d);c=yb(b,c);if(0==(c|0)){break a}}}}while(0);i=ba(b,i);if(0==(i|0)){break}}}}function gD(b,r){var i,g,q,c=a[b+16>>2];q=c>>2;var d=a[b+148>>2];Bk(V(r|0,lh|0));kD(b);var f=a[b+60>>2];if(0!=(f|0)&&(f=a[f+24>>2],0!=(f|0))){J[f](b)}Pa(b,Ac|0);$b(b,qe|0);if(0!=(d&4259840|0)&&!(0==(a[q+37]|0)&&0==(a[q+50]&1|0))){if(0==(d&655360|0)){var n=0,f=0}else{var n=d&131072,h=n>>>16^2,f=h+2|0;a[q+51]=h;h=fa(f<<4);g=h>>2;i=(b+284|0)>>2;a[g]=a[i];a[g+1]=a[i+1];a[g+2]=a[i+2];a[g+3]=a[i+3];g=(h+16|0)>>2;i=(b+300|0)>>2;a[g]=a[i];a[g+1]=a[i+1];a[g+2]=a[i+2];a[g+3]=a[i+3];0==(n|0)&&Mn(h);n=h}0==(d&8192|0)&&Re(b,n,n,f);a[q+53]=n;a[q+52]=f}0!=(d&32768|0)&&(f=a[r+48>>2],0!=(f|0)&&(a[q+33]=a[f>>2]));f=0!=(d&4|0);f||(c=c+148|0,0==(a[c>>2]|0)&&0==(a[q+50]&1|0)||(Ln(b,b+252|0),ad(b,a[c>>2],a[q+42],a[q+46],a[q+38])));if(1==(a[b+152>>2]|0)){var e,n=V(r|0,zk|0);0==(n|0)?(c=1,n=hf|0):(h=0==m[n]<<24>>24,c=h&1,n=h?hf|0:n);h=a[b+148>>2];i=116==m[n]<<24>>24;if(0==(h&256|0)){var j=i?0==(ka(n,On|0)|0)?hf|0:n:n;e=2410}else{i?0!=(ka(n,On|0)|0)&&(j=n,e=2410):(j=n,e=2410)}2410==e&&0==(h&33554432|0)|0==(c|0)&&($b(b,j),Pa(b,j),mh(b,b+252|0,1));e=a[a[r+44>>2]+88>>2];0!=(e|0)&&lD(b,e)}e=a[r+48>>2];0!=(e|0)&&Fg(b,4,e);if(!f){if(0==(a[q+37]|0)&&0==(a[q+50]&1|0)){Ur(b,r,d);mD(b);return}Se(b)}Ur(b,r,d);mD(b)}function hD(b){var r,i=b>>2,g=h;h+=16;var q=g+8;r=b+192|0;var c=r|0,d=b+196|0,f=a[d>>2],n=a[i+47];a[g>>2]=a[i+46]+a[c>>2]|0;a[g+4>>2]=n+f|0;r>>=2;n=a[g>>2];f=a[g+4>>2];a[r]=n;a[r+1]=f;0==Qr(b)<<24>>24&&(b=a[i+45],0==(b|0)?(c=a[i+43],a[d>>2]=c,d=n):(d=a[i+42],a[c>>2]=d,c=f),a[q>>2]=a[i+44]+d|0,a[q+4>>2]=b+c|0,i=a[q+4>>2],a[r]=a[q>>2],a[r+1]=i);h=g}function nD(b){var r=a[yi>>2];0==(r|0)&&(r=Nc(oD,a[Pn>>2]),a[yi>>2]=r);if(0!=(J[a[r>>2]](r,b,4)|0)){return 0}var r=a[yi>>2],i=a[r>>2],b=Dc(b);J[i](r,b,1);return 1}function Ck(x,r,i){var g,q,c,d,k,n=h;h+=68;k=n>>2;var e=n+4,p=r+208|0;if(1<=(a[p>>2]|0)){var j=r+212|0,v=0!=(i&4|0),t=x+16|0,u=0==(i&8|0),w=e|0;d=e>>2;for(var A=e+32|0,r=A>>2,A=A|0,B=e+16|0,C=e+8|0,P=e+24|0,T=e|0,gb=e+48|0,$e=e+40|0,e=e+56|0,M=1;;){var y=a[a[j>>2]+(M<<2)>>2];do{c=x;q=y;var O=2>(a[c+152>>2]|0);a:do{if(O){var D=1}else{if(g=q|0,g=jc(g,$(g,Qn|0),Y|0),0!=Gi(c,g)<<24>>24){D=1}else{if(0!=m[g]<<24>>24){D=0}else{g=q;for(var Da=ra(g);;){if(0==(Da|0)){D=0;break a}if(0!=pD(c,q,Da)<<24>>24){D=1;break a}Da=ba(g,Da)}}}}}while(0);if(0!=D<<24>>24){v&&Ck(x,y,i);c=x;q=y;O=cc;O=wk(c)>>2;a[O+1]=1;a[O+2]=q;a[O+3]=1;Sr(c,a[q+48>>2],q|0);q=a[c+60>>2];if(0!=(q|0)&&(q=a[q+32>>2],0!=(q|0))){J[q](c)}q=a[t>>2];c=q>>2;q=(q+148|0)>>2;O=0==(a[q]|0)?0!=(a[c+50]&1|0):1;g=y|0;Bk(V(g,lh|0));v|O^1||(Ln(x,y+52|0),ad(x,a[q],a[c+42],a[c+46],a[c+38]));a[k]=0;var Da=n,ia=cc,Je=V(y|0,Rn|0),F=0==(Je|0);a:do{if(F){var E=0,sc=0}else{if(0==m[Je]<<24>>24){sc=E=0}else{nh(Je);for(var G=Cc|0,lf=0;;){for(;;){var H=a[G>>2];if(0==(H|0)){E=lf;sc=Cc|0;break a}if(0==(ka(H,Hi|0)|0)){ia=1489;break}if(0==(ka(H,Vr|0)|0)){var hc=G;break}else{G=G+4|0}}if(1489==ia){ia=0,G=G+4|0,lf|=1}else{for(;;){var H=hc+4|0,ca=a[H>>2];a[hc>>2]=ca;if(0==(ca|0)){break}else{hc=H}}lf|=2}}}}}while(0);a[Da>>2]=E;Da=sc;0==(Da|0)?Da=0:(Ke(x,Da),Da=a[k]&1);ia=m[y+148|0]&255;0==(ia&1|0)?0!=(ia&2|0)?(ia=Aa(g,a[Wr>>2],Dk|0),Je=1,F=Aa(g,a[Xr>>2],Ek|0)):0!=(ia&8|0)?(ia=Aa(g,a[qD>>2],Fk|0),Je=1,F=Aa(g,a[rD>>2],Gk|0)):0!=(ia&4|0)?(ia=Aa(g,a[sD>>2],Hk|0),Je=1,F=Aa(g,a[tD>>2],Ik|0)):(ia=V(g,kh|0),Je=0==(ia|0)?0:0==m[ia]<<24>>24?0:ia,ia=V(g,uD|0),ia=0==(ia|0)?Je:0==m[ia]<<24>>24?Je:ia,F=V(g,Ak|0),F=0==(F|0)?Je:0==m[F]<<24>>24?Je:F,0!=(Da|0)?Je=Da:(Da=V(g,zk|0),0==(Da|0)?Je=0:(lf=0==m[Da]<<24>>24,Je=lf&1^1,F=lf?F:Da))):(ia=Aa(g,a[Wr>>2],Jk|0),Je=1,F=Aa(g,a[Xr>>2],Kk|0));Da=0==(ia|0)?Ac|0:ia;ia=0==(F|0)?qe|0:F;F=a[Yr>>2];0!=(F|0)&&(lf=mb(g,a[F+8>>2]),0!=(lf|0)&&0!=m[lf]<<24>>24&&Zr(x,Xb(g,F,1,0)));F=a[k];0==(F&2|0)?(Pa(x,Da),$b(x,ia),0!=(Zf(g,a[Sn>>2],1)|0)?mh(x,y+52|0,Je&255):0!=(Je|0)&&((ia|0)!=(Da|0)&&Pa(x,ia),mh(x,y+52|0,Je&255))):0!=(Zf(g,a[Sn>>2],1)|Je|0)&&(g=(y+52|0)>>2,a[d]=a[g],a[d+1]=a[g+1],a[d+2]=a[g+2],a[d+3]=a[g+3],g=(y+68|0)>>2,a[r]=a[g],a[r+1]=a[g+1],a[r+2]=a[g+2],a[r+3]=a[g+3],g=(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0]),f[0]=g,a[B>>2]=b[0],a[B+4>>2]=b[1],g=(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0]),f[0]=g,a[P>>2]=b[0],a[P+4>>2]=b[1],g=(b[0]=a[T>>2],b[1]=a[T+4>>2],f[0]),f[0]=g,a[gb>>2]=b[0],a[gb+4>>2]=b[1],g=(b[0]=a[$e>>2],b[1]=a[$e+4>>2],f[0]),f[0]=g,a[e>>2]=b[0],a[e+4>>2]=b[1],Ii(x,ia,Da,w,4,F,F&1));g=a[y+48>>2];0!=(g|0)&&Fg(x,5,g);O&&(v&&(Ln(x,y+52|0),ad(x,a[q],a[c+42],a[c+46],a[c+38])),Se(x));a:do{if(!u&&(c=y,q=ra(c),0!=(q|0))){for(;;){oh(x,q);O=Ib(c,q);g=0==(O|0);b:do{if(!g){for(Da=O;;){if(Ji(x,Da),Da=yb(c,Da),0==(Da|0)){break b}}}}while(0);q=ba(c,q);if(0==(q|0)){break a}}}}while(0);c=x;q=a[c+60>>2];if(0!=(q|0)&&(q=a[q+36>>2],0!=(q|0))){J[q](c)}Ei(c);v||Ck(x,y,i)}}while(0);M=M+1|0;if((M|0)>(a[p>>2]|0)){break}}}h=n}function oh(b,r){var i=a[b>>2],g=r+24|0,q;if(q=0!=(a[g>>2]|0)){if(q=0!=pD(b,a[r+20>>2],r)<<24>>24){var c,d;q=h;d=(b+252|0)>>2;c=h;h+=32;a[c>>2]=a[d];a[c+4>>2]=a[d+1];a[c+8>>2]=a[d+2];a[c+12>>2]=a[d+3];a[c+16>>2]=a[d+4];a[c+20>>2]=a[d+5];a[c+24>>2]=a[d+6];a[c+28>>2]=a[d+7];c=ph(r+64|0,c)&255;h=q;q=0!=c<<24>>24}}if(q&&(q=r+132|0,i=a[i+28>>2],(m[q]<<24>>24|0)!=(i|0))){m[q]=i&255;yk(b,a[r+12>>2]);i=r|0;q=jc(i,a[$r>>2],Y|0);0!=m[q]<<24>>24&&yk(b,q);i=jc(i,a[Ki>>2],Y|0);q=0==m[i]<<24>>24;a:do{if(!q){nh(i);for(q=Cc|0;;){i=q+4|0;q=a[q>>2];if(0==(q|0)){break a}if(105!=m[q]<<24>>24){q=i}else{if(0==(ka(q,qh|0)|0)){break}else{q=i}}}return}}while(0);vD(b,r);J[a[a[a[g>>2]+4>>2]+20>>2]](b,r);g=a[r+124>>2];0!=(g|0)&&Fg(b,10,g);g=a[b+60>>2];if(0!=(g|0)&&(g=a[g+60>>2],0!=(g|0))){J[g](b)}Ei(b)}}function Ji(b,r){var i,g,q=h;g=(b+252|0)>>2;i=h;h+=32;a[i>>2]=a[g];a[i+4>>2]=a[g+1];a[i+8>>2]=a[g+2];a[i+12>>2]=a[g+3];a[i+16>>2]=a[g+4];a[i+20>>2]=a[g+5];a[i+24>>2]=a[g+6];a[i+28>>2]=a[g+7];var c;g=a[r+24>>2];if(0==(g|0)){c=1823}else{if(0==(ph(g+8|0,i)|0)){c=1823}else{var d=1}}1823==c&&(c=a[r+108>>2],0!=(c|0)&&0!=as(c,i)<<24>>24?d=1:(c=a[r+120>>2],d=0!=(c|0)&&0!=as(c,i)<<24>>24?1:0));h=q;if(0!=d<<24>>24){i=(r+12|0)>>2;q=2>(a[b+152>>2]|0);a:do{if(q){var f=1}else{if(c=jc(r|0,a[Tn>>2],Y|0),0!=Gi(b,c)<<24>>24){f=1}else{if(0!=m[c]<<24>>24){f=0}else{c=r+16|0;d=r+12|0;for(g=0;;){if(2<=(g|0)){f=0;break a}var n=jc(a[(1>(g|0)?c:d)>>2]|0,a[Un>>2],Y|0);if(0==m[n]<<24>>24){f=1;break a}if(0==Gi(b,n)<<24>>24){g=g+1|0}else{f=1;break a}}}}}}while(0);if(0!=f<<24>>24){q=r+16|0;f=Gb(Ba(a[a[q>>2]+12>>2])+Ba(a[a[i]+12>>2])+3|0);Rf(f,a[a[q>>2]+12>>2]);q=f+Ba(f)|0;0==(a[a[a[i]+20>>2]>>2]&16|0)?(m[q]=m[Af|0],m[q+1]=m[(Af|0)+1],m[q+2]=m[(Af|0)+2]):(m[q]=m[Bf|0],m[q+1]=m[(Bf|0)+1],m[q+2]=m[(Bf|0)+2]);uf(f,a[a[i]+12>>2]);yk(b,f);G(f);f=r|0;i=jc(f,a[bs>>2],Y|0);0!=m[i]<<24>>24&&yk(b,i);f=jc(f,a[Vn>>2],Y|0);i=0==m[f]<<24>>24;a:do{if(i){q=0}else{nh(f);for(i=Cc|0;;){f=i+4|0;i=a[i>>2];if(0==(i|0)){q=Cc|0;break a}if(105!=m[i]<<24>>24){i=f}else{if(0==(ka(i,qh|0)|0)){break}else{i=f}}}return}}while(0);wD(b,r,q);xD(b,r,q);yD(b)}}}function nh(b){var r,i=h;h+=148;var g,q=i+128,c=i+132;m[cs]||(fc(Ec,128,zD|0),m[cs]=1);fc(c,128,i|0);a[q>>2]=b;r=(c+4|0)>>2;var d=c+8|0,f=c|0,n=0,e=0;a:for(;;){for(var p=n;;){for(var n=q,s=c,v=cc,t=a[n>>2];;){v=m[t];if(0==v<<24>>24){var u=0;break}if(0==(ah(v<<24>>24)|0)&&(v=m[t],44!=v<<24>>24)){u=v;break}t=t+1|0}var w=u<<24>>24;b:do{if(40==(w|0)||41==(w|0)){var A=w,B=t+1|0}else{if(0==(w|0)){A=0,B=t}else{if(0!=AD(w)<<24>>24){A=1,B=t}else{for(var v=(s+4|0)>>2,C=s+8|0,P=t,T=u;;){var gb=a[v];gb>>>0>2]>>>0||(na(s,1),gb=a[v]);a[v]=gb+1|0;m[gb]=T;P=P+1|0;T=m[P];if(0!=AD(T<<24>>24)<<24>>24){A=1;B=P;break b}}}}}}while(0);a[n>>2]=B;n=A;if(0==(n|0)){g=1553;break a}else{if(41==(n|0)){if(0==p<<24>>24){g=1541;break a}else{p=0}}else{if(40==(n|0)){if(0==p<<24>>24){p=1}else{g=1539;break a}}else{break}}}}if(0==p<<24>>24){if(63==(e|0)){g=1544;break}n=a[Ec+4>>2];n>>>0>2]>>>0||(na(Ec,1),n=a[Ec+4>>2]);a[Ec+4>>2]=n+1|0;m[n]=0;a[Cc+(e<<2)>>2]=a[Ec+4>>2];e=e+1|0}n=a[r];n>>>0>2]>>>0||(na(c,1),n=a[r]);m[n]=0;n=a[f>>2];a[r]=n;db(Ec,n);n=a[Ec+4>>2];n>>>0>2]>>>0||(na(Ec,1),n=a[Ec+4>>2]);a[Ec+4>>2]=n+1|0;m[n]=0;n=p}if(1544==g){return la(0,BD|0,(j=h,h+=4,a[j>>2]=b,j)),a[Cc+252>>2]=0,uc(c),h=i,Cc|0}if(1539==g){return la(1,CD|0,(j=h,h+=4,a[j>>2]=b,j)),a[Cc>>2]=0,uc(c),h=i,Cc|0}if(1553==g){if(0!=p<<24>>24){return la(1,DD|0,(j=h,h+=4,a[j>>2]=b,j)),a[Cc>>2]=0,uc(c),h=i,Cc|0}a[Cc+(e<<2)>>2]=0;uc(c);b=a[Ec+4>>2];b>>>0>2]>>>0||(na(Ec,1),b=a[Ec+4>>2]);m[b]=0;a[Ec+4>>2]=a[Ec>>2];h=i;return Cc|0}if(1541==g){return la(1,ED|0,(j=h,h+=4,a[j>>2]=b,j)),a[Cc>>2]=0,uc(c),h=i,Cc|0}}function Gg(b){var r=a[Wn>>2];0!=(b|0)?(a[Wn>>2]=r+1|0,0==(r|0)&&(b=Hb(mi()),a[ds>>2]=b,mi())):0<(r|0)&&(b=r-1|0,a[Wn>>2]=b,0==(b|0)&&(mi(),G(a[ds>>2])))}function Br(x,r){var i,g,q,c,d=h,k;c=(r+44|0)>>2;if(0==(a[c]|0)){return la(1,FD|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),h=d,-1}q=ra(r);if(0!=(q|0)){for(;;){var n=r,e=q,p=e+32|0,p=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),s=e+104|0,s=p-(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]);g=e+64|0;f[0]=s;a[g>>2]=b[0];a[g+4>>2]=b[1];s=e+40|0;s=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]);g=e+96|0;g=.5*(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);var v=e+72|0;f[0]=s-g;a[v>>2]=b[0];a[v+4>>2]=b[1];v=e+112|0;p+=(b[0]=a[v>>2],b[1]=a[v+4>>2],f[0]);v=e+80|0;f[0]=p;a[v>>2]=b[0];a[v+4>>2]=b[1];p=e+88|0;f[0]=s+g;a[p>>2]=b[0];a[p+4>>2]=b[1];e=Ib(n,e);if(0!=(e|0)){for(;!(p=a[e+24>>2],0!=(p|0)&&GD(p),e=yb(n,e),0==(e|0));){}}q=ba(r,q);if(0==(q|0)){break}}}HD(x,r);q=x+272|0;n=a[q>>2];0!=(n|0)&&(G(n),a[q>>2]=0);q=(x+276|0)>>2;n=a[q];0!=(n|0)&&(G(n),a[q]=0);n=V(r|0,ID|0);0==(n|0)?(a[q]=0,a[x+280>>2]=1):a[x+280>>2]=JD(x,r,n);a[x+104>>2]=Oc|0;a[x+108>>2]=a[KD>>2];Gg(1);n=x+56|0;e=x|0;p=x+156|0;q=(x+164|0)>>2;s=x+28|0;g=a[x+120>>2];v=a[x+124>>2]=g;for(g=v>>2;;){if(0==(v|0)){k=1616;break}k=a[n>>2];0==(k|0)?(a[g+5]=0,a[g+6]=0):(a[g+5]=a[k+8>>2],a[g+6]=a[a[n>>2]+12>>2]);var t=v+12|0;a[t>>2]=e;a[g+7]=a[p>>2];if(0==(a[c]|0)){k=1597;break}i=(v+52|0)>>2;k=Bn(v,a[i]);a[g+14]=k;if(21==(k|0)){k=v+148|0,a[k>>2]|=1}else{if(24==(k|0)){k=v+148|0,a[k>>2]|=520}else{if(999==(k|0)){k=1599;break}else{k=v+148|0;var u=a,w=k>>2,A;a:{A=V(r|0,LD|0);if(0!=(A|0)){var B=m[A];if(110==B<<24>>24){if(0==(ka(A+1|0,MD|0)|0)){A=1;break a}}else{if(101==B<<24>>24&&0==(ka(A+1|0,ND|0)|0)){A=16;break a}}}A=0}u[w]=A|a[k>>2]}}}u=a[q];w=0==(u|0);a:do{if(!w){do{if(0!=(a[u+148>>2]&32|0)&&0==(ka(a[i],a[u+52>>2])|0)){k=a[Xn>>2];if(0==(k|0)){k=1610;break a}a[k+8>>2]=v;a[g+9]=a[k+36>>2];k=1612;break a}}while(0);Cr(u);a[q]=0;a[s>>2]=0}a[Xn>>2]=0;k=1610}while(0);if(1610==k){k=0;u=v;w=a[u+60>>2];if(0==(OD(u)|0)){if(0!=(w|0)&&(w=a[w>>2],0!=(w|0))){J[w](u)}u=0}else{u=1}0==(u|0)&&(a[q]=v,k=1612)}1612==k&&(k=0,a[g+2]=0,a[g+26]=PD,u=v,w=A=cc,A=a[u>>2],0!=m[A+264|0]<<24>>24?(w=(u+236|0)>>2,A=(A+188|0)>>2,a[w]=a[A],a[w+1]=a[A+1],a[w+2]=a[A+2],a[w+3]=a[A+3]):(300==(a[u+56>>2]|0)?(w=a[u+68>>2]+4|0,w=(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0]),A=u+244|0,f[0]=w,a[A>>2]=b[0],a[A+4>>2]=b[1],u=u+236|0,f[0]=w):(w=u+244|0,f[0]=4,a[w>>2]=b[0],a[w+4>>2]=b[1],u=u+236|0,f[0]=4),a[u>>2]=b[0],a[u+4>>2]=b[1]),u=v,w=A=A=w=w=cc,A=a[u>>2],0!=m[A+265|0]<<24>>24?(w=(u+408|0)>>2,A=(A+172|0)>>2,a[w]=a[A],a[w+1]=a[A+1],a[w+2]=a[A+2],a[w+3]=a[A+3]):(w=a[u+56>>2],300==(w|0)?(A=(u+408|0)>>2,w=(a[u+84>>2]+4|0)>>2,a[A]=a[w],a[A+1]=a[w+1],a[A+2]=a[w+2],a[A+3]=a[w+3]):2==(w|0)||3==(w|0)||4==(w|0)||22==(w|0)||21==(w|0)||30==(w|0)?(w=u+416|0,f[0]=36,a[w>>2]=b[0],a[w+4>>2]=b[1],u=u+408|0,f[0]=36,a[u>>2]=b[0],a[u+4>>2]=b[1]):(w=(u+408|0)>>2,a[w]=0,a[w+1]=0,a[w+2]=0,a[w+3]=0)),w=a[c]+24|0,u=v,w=(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0]),A=B=B=A=cc,B=a[a[u>>2]+164>>2],0!=w?(A=u+432|0,f[0]=w,a[A>>2]=b[0],a[A+4>>2]=b[1],u=u+424|0,f[0]=w,a[u>>2]=b[0],a[u+4>>2]=b[1]):0!=(B|0)&&0!=m[B+124|0]<<24>>24?(A=(u+424|0)>>2,B=(B+108|0)>>2,a[A]=a[B],a[A+1]=a[B+1],a[A+2]=a[B+2],a[A+3]=a[B+3]):(w=u+424|0,300==(a[u+56>>2]|0)?(B=w>>2,A=(a[u+84>>2]+36|0)>>2,a[B]=a[A],a[B+1]=a[A+1],a[B+2]=a[A+2],a[B+3]=a[A+3]):(u=u+432|0,f[0]=96,a[u>>2]=b[0],a[u+4>>2]=b[1],u=w|0,f[0]=96,a[u>>2]=b[0],a[u+4>>2]=b[1])),QD(v,r),RD(v,r),0==(a[g+37]&128|0)&&(a[a[t>>2]+20>>2]=a[Yn>>2],Rr(v,r)),a[Xn>>2]=v);g=cc;g=(x+124|0)>>2;v=a[g];t=a[v+4>>2];0!=(t|0)&&(u=t+52|0,0==(a[u>>2]|0)&&(a[u>>2]=a[v+52>>2]));v=a[g]=t;g=v>>2}if(1597==k){return la(1,SD|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),Gg(0),h=d,-1}if(1599==k){return la(1,TD|0,(j=h,h+=4,a[j>>2]=a[i],j)),Gg(0),h=d,-1}if(1616==k){return Gg(0),h=d,0}}function HD(x,r){var i,g,q,c,d,k,n=x>>2,e=h;h+=16;d=e>>2;q=e+8;g=q>>2;a[n+32]=r;c=x+265|0;m[c]=0;i=r|0;k=V(i,Zn|0);if(0!=(k|0)){var p=Cd(k,Lk|0,(j=h,h+=8,a[j>>2]=e,a[j+4>>2]=q,j));if(0<(p|0)){var s=72*(b[0]=a[d],b[1]=a[d+1],f[0]);k=(x+180|0)>>2;f[0]=s;a[k]=b[0];a[k+1]=b[1];var v=x+172|0;f[0]=s;a[v>>2]=b[0];a[v+4>>2]=b[1];1<(p|0)&&(p=72*(b[0]=a[g],b[1]=a[g+1],f[0]),f[0]=p,a[k]=b[0],a[k+1]=b[1]);m[c]=1}}c=x+264|0;m[c]=0;k=V(i,UD|0);0!=(k|0)&&(q=Cd(k,Lk|0,(j=h,h+=8,a[j>>2]=e,a[j+4>>2]=q,j)),0<(q|0)&&(k=72*(b[0]=a[d],b[1]=a[d+1],f[0]),d=(x+196|0)>>2,f[0]=k,a[d]=b[0],a[d+1]=b[1],p=x+188|0,f[0]=k,a[p>>2]=b[0],a[p+4>>2]=b[1],1<(q|0)&&(g=72*(b[0]=a[g],b[1]=a[g+1],f[0]),f[0]=g,a[d]=b[0],a[d+1]=b[1]),m[c]=1));d=x+266|0;m[d]=0;g=(r+44|0)>>2;c=(x+204|0)>>2;q=(a[g]+48|0)>>2;a[c]=a[q];a[c+1]=a[q+1];a[c+2]=a[q+2];a[c+3]=a[q+3];q=a[g];c=q+48|0;.001<(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])?(c=q+56|0,.001<(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])?(m[d]=1,g=a[g]):g=q):g=q;a[n+65]=0==m[g+81|0]<<24>>24?0:90;g=x+168|0;a[g>>2]=VD|0;i=V(i,WD|0);0!=(i|0)&&0!=m[i]<<24>>24&&(a[g>>2]=i);g=(x+228|0)>>2;i=(r+52|0)>>2;a[g]=a[i];a[g+1]=a[i+1];a[g+2]=a[i+2];a[g+3]=a[i+3];a[g+4]=a[i+4];a[g+5]=a[i+5];a[g+6]=a[i+6];a[g+7]=a[i+7];i=r+32|0;g=$(a[i>>2]|0,es|0);a[Sn>>2]=g;i=$(a[i>>2]|0,$n|0);a[Yr>>2]=i;i=r+40|0;a[n+71]=Aa(a[a[i>>2]>>2]|0,a[ao>>2],Li|0);i=Xb(a[a[i>>2]>>2]|0,a[bo>>2],14,1);g=x+288|0;f[0]=i;a[g>>2]=b[0];a[g+4>>2]=b[1];a[n+74]=co|0;a[n+40]=a[r+12>>2];h=e}function QD(x,r){var i,g,q,c,d,k=h;h+=40;d=k>>2;var n=k+8;c=n>>2;var e=k+16;q=e>>2;var p=k+24;i=p>>2;var s=k+32;g=s>>2;var v=a[x>>2],t=v+244|0,t=(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0]),u=v+252|0,u=(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0]),w=v+228|0,w=(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0]),A=v+236|0,A=(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0]),B=x+236|0,C=(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0]),B=w-C,P=x+204|0;f[0]=B;a[P>>2]=b[0];a[P+4>>2]=b[1];var P=x+244|0,T=(b[0]=a[P>>2],b[1]=a[P+4>>2],f[0]),P=A-T,gb=x+212|0;f[0]=P;a[gb>>2]=b[0];a[gb+4>>2]=b[1];C=t+C;gb=x+220|0;f[0]=C;a[gb>>2]=b[0];a[gb+4>>2]=b[1];T=u+T;gb=x+228|0;f[0]=T;a[gb>>2]=b[0];a[gb+4>>2]=b[1];B=C-B;P=T-P;f[0]=1;a[q]=b[0];a[q+1]=b[1];C=a[r+44>>2];T=C+64|0;T=(b[0]=a[T>>2],b[1]=a[T+4>>2],f[0]);.001>2],b[1]=a[gb+4>>2],f[0]),.001>24&T>B&gb>P)?C=1:(C=T/B,T=gb/P,C=C>2]=a[v+260>>2];v=B*C;f[0]=v;a[d]=b[0];a[d+1]=b[1];w=P*C;f[0]=w;a[c]=b[0];a[c+1]=b[1];A=V(r|0,XD|0);0==(A|0)?(g=u,i=(x+360|0)>>2,f[0]=v,a[i]=b[0],a[i+1]=b[1],i=(x+368|0)>>2,f[0]=w,a[i]=b[0],a[i+1]=b[1],i=(x+348|0)>>2,f[0]=C,a[i]=b[0],a[i+1]=b[1],i=(x+332|0)>>2,f[0]=t):(v=Gb(Ba(A)+1|0),t=Gb(Ba(A)+1|0),4==(Cd(A,YD|0,(j=h,h+=16,a[j>>2]=k,a[j+4>>2]=n,a[j+8>>2]=e,a[j+12>>2]=v,j))|0)?(n=Mi(a[r+32>>2],v),0!=(n|0)&&(n=n+24|0,e=n+8|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),f[0]=e,a[i]=b[0],a[i+1]=b[1],n=n+16|0,n=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),f[0]=n,a[g]=b[0],a[g+1]=b[1])):4!=(Cd(A,ZD|0,(j=h,h+=20,a[j>>2]=k,a[j+4>>2]=n,a[j+8>>2]=e,a[j+12>>2]=v,a[j+16>>2]=t,j))|0)?Cd(A,$D|0,(j=h,h+=20,a[j>>2]=k,a[j+4>>2]=n,a[j+8>>2]=e,a[j+12>>2]=p,a[j+16>>2]=s,j)):(n=Mi(a[r+32>>2],v),0!=(n|0)&&(n=n+24|0,e=n+8|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),f[0]=e,a[i]=b[0],a[i+1]=b[1],n=n+16|0,n=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),f[0]=n,a[g]=b[0],a[g+1]=b[1])),G(v),G(t),d=(b[0]=a[d],b[1]=a[d+1],f[0]),c=(b[0]=a[c],b[1]=a[c+1],f[0]),n=(b[0]=a[q],b[1]=a[q+1],f[0]),q=(b[0]=a[i],b[1]=a[i+1],f[0]),g=(b[0]=a[g],b[1]=a[g+1],f[0]),i=(x+360|0)>>2,f[0]=d,a[i]=b[0],a[i+1]=b[1],i=(x+368|0)>>2,f[0]=c,a[i]=b[0],a[i+1]=b[1],i=(x+348|0)>>2,f[0]=n,a[i]=b[0],a[i+1]=b[1],i=(x+332|0)>>2,f[0]=q);a[i]=b[0];a[i+1]=b[1];i=x+340|0;i>>=2;f[0]=g;a[i]=b[0];a[i+1]=b[1];h=k}function AD(a){return 40==(a|0)||41==(a|0)||44==(a|0)||0==(a|0)?1:0}function Mk(x,r,i){var g=x|0;f[0]=i;a[g>>2]=b[0];a[g+4>>2]=b[1];x=x+8|0;f[0]=r;a[x>>2]=b[0];a[x+4>>2]=b[1]}function eo(b,r,i){i=i<<24>>24;66==(i|0)?(r=0,i=1):82==(i|0)?(a[r+168>>2]=a[r+160>>2]-1|0,r=-1,i=0):84==(i|0)?(a[r+172>>2]=a[r+164>>2]-1|0,r=0,i=-1):(r=76==(i|0)?1:0,i=0);a[b>>2]=r;a[b+4>>2]=i}function fo(b,r,i){a[b>>2]=i;a[b+4>>2]=r}function RD(x,r){var i,g,q,c,d,k,n,e=x>>2,p=h;h+=128;var s,v=p+16,t=p+24,u=p+32,w=p+40,A=p+48,B=p+64,C=p+80,P=p+96,T=p+112,gb=p+120,y=a[e],M=x+360|0,X=(b[0]=a[M>>2],b[1]=a[M+4>>2],f[0]),O=x+368|0,D=(b[0]=a[O>>2],b[1]=a[O+4>>2],f[0]);n=(x+356|0)>>2;if(0==(a[n]|0)){var Da=X,ia=D}else{Mk(p,X,D);var F=p|0,E=p+8|0,Da=(b[0]=a[F>>2],b[1]=a[F+4>>2],f[0]),ia=(b[0]=a[E>>2],b[1]=a[E+4>>2],f[0])}var G=x+408|0,sc=(b[0]=a[G>>2],b[1]=a[G+4>>2],f[0]),J=x+416|0,H=(b[0]=a[J>>2],b[1]=a[J+4>>2],f[0]);if(0==m[y+266|0]<<24>>24){s=1737}else{if(0==(a[e+37]&32|0)){s=1737}else{var I=y+204|0,hc=(b[0]=a[I>>2],b[1]=a[I+4>>2],f[0])-2*sc,ca=y+212|0,K=(b[0]=a[ca>>2],b[1]=a[ca+4>>2],f[0])-2*H;if(1e-4>hc){var L=a[e+40]=1}else{var zb=Da/hc&-1,ja=x+160|0;a[ja>>2]=zb;if(1e-4>2]=aa}else{L=zb}}if(1e-4>K){var da=a[e+41]=1}else{var ea=ia/K&-1,xa=x+164|0;a[xa>>2]=ea;if(1e-4>2]=N}else{da=ea}}a[e+50]=da*L|0;var Od=hc,ha=K,ga=Da>2],b[1]=a[ya+4>>2],f[0])-2*sc,wa=0>Uf?0:Uf,Ab=tb+28|0,Fa=(b[0]=a[Ab>>2],b[1]=a[Ab+4>>2],f[0])-2*H;0>Fa?(Rb=wa,Xe=0):(Rb=wa,Xe=Fa)}a[e+50]=1;a[e+41]=1;a[e+40]=1;var Ga=Rb>2;var Bg=(Od+2*sc)*(b[0]=a[k],b[1]=a[k+1],f[0])/72;a[e+110]=(0>Bg?Bg-.5:Bg+.5)&-1;d=(x+432|0)>>2;var ta=(ha+2*H)*(b[0]=a[d],b[1]=a[d+1],f[0])/72;a[e+111]=(0>ta?ta-.5:ta+.5)&-1;var Ka=x+176|0;c=(y+168|0)>>2;q=(x+168|0)>>2;a[q]=0;a[q+1]=0;a[q+2]=0;a[q+3]=0;a[q+4]=0;a[q+5]=0;eo(v,x,m[a[c]]);var za=v|0;g=za>>2;var ma=v+4|0;i=ma>>2;var pa=a[i],Q=Ka|0;a[Q>>2]=a[g];var Ha=Ka+4|0;a[Ha>>2]=pa;eo(t,x,m[a[c]+1|0]);var Ra=x+184|0,W=a[t>>2],V=a[t+4>>2];a[Ra>>2]=W;a[Ra+4>>2]=V;var La=W+a[Ka>>2]|0;if(1==((-1<(La|0)?La:-La|0)|0)){var S=V+a[e+45]|0;if(1!=((-1<(S|0)?S:-S|0)|0)){s=1750}}else{s=1750}if(1750==s){eo(u,x,66);var Ya=a[u+4>>2];a[Ka>>2]=a[u>>2];a[Ka+4>>2]=Ya;eo(w,x,76);var Y=a[w+4>>2],za=Ra|0;g=za>>2;a[g]=a[w>>2];ma=Ra+4|0;i=ma>>2;a[i]=Y;la(0,aE|0,(j=h,h+=4,a[j>>2]=a[c],j))}if(0==m[a[r+44>>2]+82|0]<<24>>24){var Za=0,U=0}else{var ab=Od>ga?.5*(Od-ga):0;ha>Zd?(Za=ab,U=.5*(ha-Zd)):(Za=ab,U=0)}if(0==(a[n]|0)){var $a=ga,jb=Zd,Ca=sc,Ia=H,eb=Za,ub=U}else{Mk(A,ga,Zd);var Sa=A|0,R=(b[0]=a[Sa>>2],b[1]=a[Sa+4>>2],f[0]),ua=A+8|0,Oa=(b[0]=a[ua>>2],b[1]=a[ua+4>>2],f[0]);Mk(B,Od,ha);Mk(C,sc,H);var Wa=C|0,pb=(b[0]=a[Wa>>2],b[1]=a[Wa+4>>2],f[0]),ob=C+8|0,bb=(b[0]=a[ob>>2],b[1]=a[ob+4>>2],f[0]);Mk(P,Za,U);var qb=P|0,$=P+8|0,$a=R,jb=Oa,Ca=pb,Ia=bb,eb=(b[0]=a[qb>>2],b[1]=a[qb+4>>2],f[0]),ub=(b[0]=a[$>>2],b[1]=a[$+4>>2],f[0])}var kb=Ca+eb,fa=x+376|0;f[0]=kb;a[fa>>2]=b[0];a[fa+4>>2]=b[1];var vb=Ia+ub,xb=x+384|0;f[0]=vb;a[xb>>2]=b[0];a[xb+4>>2]=b[1];var Z=kb+$a,nb=x+392|0;f[0]=Z;a[nb>>2]=b[0];a[nb+4>>2]=b[1];var rb=vb+jb,lb=x+400|0;f[0]=rb;a[lb>>2]=b[0];a[lb+4>>2]=b[1];var Ta=x+348|0,cb=(b[0]=a[Ta>>2],b[1]=a[Ta+4>>2],f[0]),fb=x+316|0;f[0]=$a/cb;a[fb>>2]=b[0];a[fb+4>>2]=b[1];var Ua=x+324|0;f[0]=jb/cb;a[Ua>>2]=b[0];a[Ua+4>>2]=b[1];var sb=(b[0]=a[k],b[1]=a[k+1],f[0]),Na=kb*sb/72,Fb=(0>Na?Na-.5:Na+.5)&-1,Db=x+448|0;a[Db>>2]=Fb;var Ob=(b[0]=a[d],b[1]=a[d+1],f[0]),Eb=vb*Ob/72,ka=(0>Eb?Eb-.5:Eb+.5)&-1;a[e+113]=ka;var Bb=Z*sb/72,Ja=x+456|0,sa=Ja|0;a[sa>>2]=(0>Bb?Bb-.5:Bb+.5)&-1;var ra=rb*Ob/72,xf=x+460|0;a[xf>>2]=(0>ra?ra-.5:ra+.5)&-1;if(0!=(a[n]|0)){fo(T,Fb,ka);var qa=a[T+4>>2];a[Db>>2]=a[T>>2];a[Db+4>>2]=qa;fo(gb,a[sa>>2],a[xf>>2]);za=gb|0;g=za>>2;ma=gb+4|0;i=ma>>2;var ba=a[i],Q=Ja|0;a[Q>>2]=a[g];Ha=Ja+4|0;a[Ha>>2]=ba}h=p}function JD(b,r,i){var g=V(r|0,bE|0),r=(b+268|0)>>2;a[r]=0==(g|0)?cE|0:g;i=Hb(i);a[b+272>>2]=i;g=he(i,a[r]);if(0==(g|0)){return 0}for(var b=(b+276|0)>>2,q=i=0;;){var c=i+1|0;if((c|0)>(q|0)){var q=q+128|0,d=a[b],d=0==(d|0)?Cb(q<<2):wb(d,q<<2);a[b]=d}else{d=a[b]}a[d+(c<<2)>>2]=g;g=he(0,a[r]);if(0==(g|0)){break}else{i=c}}if(0==(c|0)){return 0}r=wb(a[b],(c<<2)+8|0);a[b]=r;a[r>>2]=0;a[a[b]+(i+2<<2)>>2]=0;return c}function GD(x){var r,i,g,q,c,d,k,n=h;h+=128;var e=n+32,p=n+64,j=n+96;k=(x+4|0)>>2;0<(a[k]|0)||sa(Fi|0,3408,dE|0,eE|0);var m=x|0,t=a[m>>2];d=t>>2;var u=a[d],w=a[d+1],A=a[d+2],B=a[d+3],C=t+16|0,P=(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0]),T=t+24|0,gb=(b[0]=a[T>>2],b[1]=a[T+4>>2],f[0]),y=t+32|0,M=(b[0]=a[y>>2],b[1]=a[y+4>>2],f[0]),X=t+40|0,O=(b[0]=a[X>>2],b[1]=a[X+4>>2],f[0]);fs(n,t);var D=n|0,Da=(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0]),ia=n+8|0,F=(b[0]=a[ia>>2],b[1]=a[ia+4>>2],f[0]),E=n+16|0,G=(b[0]=a[E>>2],b[1]=a[E+4>>2],f[0]),sc=n+24|0,J=(b[0]=a[sc>>2],b[1]=a[sc+4>>2],f[0]);if(0<(a[k]|0)){for(var H=e|0,I=e+8|0,hc=e+16|0,ca=e+24|0,K=j|0,L=j+8|0,zb=j+16|0,ja=j+24|0,aa=p|0,da=p+8|0,ea=p+16|0,xa=p+24|0,N=J,Od=G,ha=F,ga=Da,Zd=0,Rb=u,Xe=w,tb=A,ya=B,Q=P,wa=gb,Ab=M,Fa=O;;){if(0<(Zd|0)){var Ga=a[m>>2];r=Ga>>2;var W=Ga+48*Zd|0,ta=a[W>>2],Ka=a[r+(12*Zd|0)+1],za=a[r+(12*Zd|0)+2],ma=a[r+(12*Zd|0)+3],pa=Ga+48*Zd+16|0,S=(b[0]=a[pa>>2],b[1]=a[pa+4>>2],f[0]),Ha=Ga+48*Zd+24|0,Ra=(b[0]=a[Ha>>2],b[1]=a[Ha+4>>2],f[0]),V=Ga+48*Zd+32|0,U=(b[0]=a[V>>2],b[1]=a[V+4>>2],f[0]),La=Ga+48*Zd+40|0,Y=(b[0]=a[La>>2],b[1]=a[La+4>>2],f[0]);fs(e,W);var Ya=(b[0]=a[H>>2],b[1]=a[H+4>>2],f[0]),R=(b[0]=a[I>>2],b[1]=a[I+4>>2],f[0]),Za=(b[0]=a[hc>>2],b[1]=a[hc+4>>2],f[0]),la=(b[0]=a[ca>>2],b[1]=a[ca+4>>2],f[0]),ab=gaZa?Od:Za,Ca=N>la?N:la,Ia=ta,eb=Ka,ub=za,Sa=ma,$=S,ua=Ra,Oa=U,Wa=Y}else{ab=ga,$a=ha,jb=Od,Ca=N,Ia=Rb,eb=Xe,ub=tb,Sa=ya,$=Q,ua=wa,Oa=Ab,Wa=Fa}if(0==(ub|0)){var pb=ab,ob=$a,bb=jb,qb=Ca}else{var fa=Ia|0,kb=Ia+8|0;hh(p,$,ua,(b[0]=a[fa>>2],b[1]=a[fa+4>>2],f[0]),(b[0]=a[kb>>2],b[1]=a[kb+4>>2],f[0]),1);var Z=(b[0]=a[aa>>2],b[1]=a[aa+4>>2],f[0]),vb=(b[0]=a[da>>2],b[1]=a[da+4>>2],f[0]),xb=(b[0]=a[ea>>2],b[1]=a[ea+4>>2],f[0]),ka=(b[0]=a[xa>>2],b[1]=a[xa+4>>2],f[0]),pb=abxb?jb:xb,qb=Ca>ka?Ca:ka}if(0==(Sa|0)){var nb=pb,rb=ob,lb=bb,Ta=qb}else{var cb=eb-1|0,fb=(cb<<4)+Ia|0,Ua=(cb<<4)+Ia+8|0;hh(j,Oa,Wa,(b[0]=a[fb>>2],b[1]=a[fb+4>>2],f[0]),(b[0]=a[Ua>>2],b[1]=a[Ua+4>>2],f[0]),1);var sb=(b[0]=a[K>>2],b[1]=a[K+4>>2],f[0]),Na=(b[0]=a[L>>2],b[1]=a[L+4>>2],f[0]),Fb=(b[0]=a[zb>>2],b[1]=a[zb+4>>2],f[0]),Db=(b[0]=a[ja>>2],b[1]=a[ja+4>>2],f[0]),nb=pbFb?bb:Fb,Ta=qb>Db?qb:Db}var Ob=Zd+1|0;if((Ob|0)<(a[k]|0)){N=Ta,Od=lb,ha=rb,ga=nb,Zd=Ob,Rb=Ia,Xe=eb,tb=ub,ya=Sa,Q=$,wa=ua,Ab=Oa,Fa=Wa}else{Eb=Ta;ne=lb;Bb=rb;Ja=nb;break}}ra=x+8|0;c=ra>>2;f[0]=Ja;a[c]=b[0];a[c+1]=b[1];qa=x+16|0;q=qa>>2;f[0]=Bb;a[q]=b[0];a[q+1]=b[1];ba=x+24|0;g=ba>>2;f[0]=ne;a[g]=b[0];a[g+1]=b[1];na=x+32|0}else{var Eb=J,ne=G,Bb=F,Ja=Da,ra=x+8|0;c=ra>>2;f[0]=Ja;a[c]=b[0];a[c+1]=b[1];var qa=x+16|0;q=qa>>2;f[0]=Bb;a[q]=b[0];a[q+1]=b[1];var ba=x+24|0;g=ba>>2;f[0]=ne;a[g]=b[0];a[g+1]=b[1];var na=x+32|0}i=na>>2;f[0]=Eb;a[i]=b[0];a[i+1]=b[1];h=n}function fs(x,r){var i=h,g=r,r=h;h+=48;for(var g=g>>2,q=r>>2,c=g+12;g>2];0<(g|0)||sa(Fi|0,3382,gs|0,fE|0);1!=(g%3|0)&&sa(Fi|0,3383,gs|0,gE|0);var q=a[r>>2],c=q|0,c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),d=q+8|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),k=1<(g|0);a:do{if(k){for(var n=c,e=d,p=c,j=d,m=1;;){var t=(m<<4)+q|0,u=(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0]),t=(m<<4)+q+8|0,w=(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0]),t=m+1|0,A=(t<<4)+q|0,B=(t<<4)+q+8|0,t=m+2|0,u=.5*(u+(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0])),w=.5*(w+(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0])),n=nu?p:u,j=j>w?j:w,u=(t<<4)+q|0,u=(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0]),t=(t<<4)+q+8|0,t=(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0]),n=nu?p:u,j=j>t?j:t,m=m+3|0;if((m|0)>=(g|0)){var C=n,P=e,T=p,gb=j;break a}}}else{C=c,P=d,T=c,gb=d}}while(0);g=x|0;f[0]=C;a[g>>2]=b[0];a[g+4>>2]=b[1];C=x+8|0;f[0]=P;a[C>>2]=b[0];a[C+4>>2]=b[1];P=x+16|0;f[0]=T;a[P>>2]=b[0];a[P+4>>2]=b[1];T=x+24|0;f[0]=gb;a[T>>2]=b[0];a[T+4>>2]=b[1];h=i}function wD(x,r,i){var g,q,c,d=r>>2,k=h;h+=156;var n;c=k>>2;var e=k+4,p=k+8,j=k+12,v=k+28,t=a[x+148>>2];a[c]=0;a[e>>2]=0;a[p>>2]=0;var u=wk(x);q=u>>2;a[q+1]=3;a[q+2]=r;a[q+3]=9;0!=(i|0)&&0!=(a[d+6]|0)&&Ke(x,i);var w=a[hs>>2];if(0!=(w|0)){var A=r|0,B=mb(A,a[w+8>>2]);0!=(B|0)&&0!=m[B]<<24>>24&&Zr(x,Xb(A,w,1,0))}if(0!=(t&16777216|0)){var C=a[d+4];if(2<(D[a[C+20>>2]+206>>1]&65535)){var P=a[C+148>>2]+16|0,T=72*(b[0]=a[P>>2],b[1]=a[P+4>>2],f[0]),gb=u+116|0;f[0]=(0>T?T-.5:T+.5)&-1|0;a[gb>>2]=b[0];a[gb+4>>2]=b[1];var y=a[a[d+3]+148>>2]+16|0,M=72*(b[0]=a[y>>2],b[1]=a[y+4>>2],f[0]),X=u+124|0;f[0]=(0>M?M-.5:M+.5)&-1|0;a[X>>2]=b[0];a[X+4>>2]=b[1]}else{g=(u+116|0)>>2,a[g]=0,a[g+1]=0,a[g+2]=0,a[g+3]=0}}if(0!=(t&32768|0)){var O=a[d+27];if(0==(O|0)){var Dn=a[q+33]}else{var Da=a[O>>2],Dn=a[q+33]=Da}var ia=u+136|0;a[ia>>2]=Dn;var F=u+144|0;a[F>>2]=Dn;var E=u+140|0;a[E>>2]=Dn;var Tf=a[d+30];0!=(Tf|0)&&(a[ia>>2]=a[Tf>>2]);var sc=a[d+29];0!=(sc|0)&&(a[E>>2]=a[sc>>2]);var AC=a[d+28];0!=(AC|0)&&(a[F>>2]=a[AC>>2])}var H=0==(t&65536|0);a:do{if(H){var I=0}else{fc(j,128,v|0);var hc=r|0;a[q+38]=ec(Kn(x,hc,j),hc);uc(j);var ca=V(hc,Ni|0);if(0==(ca|0)){n=1867}else{if(0==m[ca]<<24>>24){n=1867}else{var K=ca;n=1869}}if(1867==n){var L=V(hc,Oi|0);if(0==(L|0)){var zb=0}else{0==m[L]<<24>>24?zb=0:(K=L,n=1869)}}1869==n&&(zb=ec(K,hc));var ja=V(hc,hE|0);if(0==(ja|0)){n=1872}else{if(0==m[ja]<<24>>24){n=1872}else{var aa=ja;n=1874}}if(1872==n){var da=V(hc,iE|0);0!=(da|0)&&0!=m[da]<<24>>24?(aa=da,n=1874):0!=(zb|0)&&(a[q+37]=Hb(zb))}1874==n&&(a[q+37]=ec(aa,hc));var ea=V(hc,jE|0);if(0==(ea|0)){n=1879}else{if(0==m[ea]<<24>>24){n=1879}else{var xa=ea;n=1881}}if(1879==n){var N=V(hc,kE|0);0!=(N|0)&&0!=m[N]<<24>>24?(xa=N,n=1881):0!=(zb|0)&&(a[q+39]=Hb(zb))}1881==n&&(a[q+39]=ec(xa,hc));var Od=V(hc,lE|0);if(0==(Od|0)){n=1886}else{if(0==m[Od]<<24>>24){n=1886}else{var ha=Od;n=1888}}if(1886==n){var ga=V(hc,mE|0);0!=(ga|0)&&0!=m[ga]<<24>>24?(ha=ga,n=1888):0!=(zb|0)&&(a[q+40]=Hb(zb))}if(1888==n){a[q+40]=ec(ha,hc);var Zd=u+200|0;a[Zd>>2]|=128}var Rb=V(hc,nE|0);if(0==(Rb|0)){n=1893}else{if(0==m[Rb]<<24>>24){n=1893}else{var Q=Rb}}do{if(1893==n){var tb=V(hc,oE|0);if(0!=(tb|0)&&0!=m[tb]<<24>>24){Q=tb}else{if(0==(zb|0)){I=0;break a}a[q+41]=Hb(zb);I=zb;break a}}}while(0);a[q+41]=ec(Q,hc);var ya=u+200|0;a[ya>>2]|=256;I=zb}}while(0);var W=0==(t&8388608|0);a:do{if(W){var wa=0}else{var Ab=r|0,Fa=V(Ab,is|0),Ga=0==(Fa|0)?0:0==m[Fa]<<24>>24?0:ec(Fa,Ab),S=V(Ab,pE|0);if(0==(S|0)){n=1905}else{if(0==m[S]<<24>>24){n=1905}else{var ta=u+200|0;a[ta>>2]|=64;a[q+46]=ec(S,Ab)}}1905==n&&0!=(Ga|0)&&(a[q+46]=Hb(Ga));var Ka=V(Ab,qE|0);0==(Ka|0)?n=1910:0==m[Ka]<<24>>24?n=1910:a[q+47]=ec(Ka,Ab);1910==n&&0!=(Ga|0)&&(a[q+47]=Hb(Ga));var za=V(Ab,rE|0);if(0==(za|0)){n=1915}else{if(0==m[za]<<24>>24){n=1915}else{a[q+48]=ec(za,Ab);var ma=u+200|0;a[ma>>2]|=16}}1915==n&&0!=(Ga|0)&&(a[q+48]=Hb(Ga));var pa=V(Ab,sE|0);do{if(0!=(pa|0)&&0!=m[pa]<<24>>24){var U=u+200|0;a[U>>2]|=32;a[q+49]=ec(pa,Ab);wa=Ga;break a}}while(0);0==(Ga|0)?wa=0:(a[q+49]=Hb(Ga),wa=Ga)}}while(0);var Ha=0==(t&4194304|0);a:do{if(!Ha){var Ra=r|0,Y=V(Ra,Pi|0);if(0==(Y|0)){n=1925}else{if(0==m[Y]<<24>>24){n=1925}else{var R=Y;n=1927}}if(1925==n){var La=V(Ra,tE|0);if(0!=(La|0)&&0!=m[La]<<24>>24){R=La,n=1927}else{var la=a[q+33];0!=(la|0)&&(a[q+42]=Hb(la))}}if(1927==n){a[q+42]=ec(R,Ra);var Ya=u+200|0;a[Ya>>2]|=1}var $=V(Ra,uE|0);if(0==($|0)){n=1933}else{if(0==m[$]<<24>>24){n=1933}else{a[q+43]=ec($,Ra);var Za=u+200|0;a[Za>>2]|=8}}if(1933==n){var fa=a[q+33];0!=(fa|0)&&(a[q+43]=Hb(fa))}var ab=V(Ra,vE|0);if(0==(ab|0)){n=1938}else{if(0==m[ab]<<24>>24){n=1938}else{a[q+44]=ec(ab,Ra);var $a=u+200|0;a[$a>>2]|=2}}if(1938==n){var jb=a[q+35];0!=(jb|0)&&(a[q+44]=Hb(jb))}var Ca=V(Ra,wE|0);do{if(0!=(Ca|0)&&0!=m[Ca]<<24>>24){a[q+45]=ec(Ca,Ra);var Ia=u+200|0;a[Ia>>2]|=4;break a}}while(0);var eb=a[q+36];0!=(eb|0)&&(a[q+45]=Hb(eb))}}while(0);G(I);G(wa);do{if(0!=(t&4259840|0)){var ub=a[d+6];if(0!=(ub|0)&&!(0==(a[q+37]|0)&&0==(a[q+42]|0))&&0!=(t&524288|0)){var Sa=a[x+16>>2]+96|0,Z=.5*(b[0]=a[Sa>>2],b[1]=a[Sa+4>>2],f[0]),ua=2>2];if(0<(Oa|0)){for(var Wa=ub|0,pb=0;;){xE(k,e,p,a[Wa>>2]+48*pb|0,ua);var ob=pb+1|0;if((ob|0)==(Oa|0)){break}else{pb=ob}}var bb=a[p>>2],qb=a[e>>2]}else{qb=bb=0}a[q+54]=bb;a[q+55]=qb;if(0==(t&8192|0)){var ka=0<(bb|0);a:do{if(ka){for(var kb=0,sa=0;;){var vb=a[qb+(kb<<2)>>2]+sa|0,xb=kb+1|0;if((xb|0)<(bb|0)){kb=xb,sa=vb}else{var hd=vb;break a}}}else{hd=0}}while(0);var nb=a[c];Re(x,nb,nb,hd);var rb=nb}else{rb=a[c]}a[q+56]=rb;a[q+51]=2;a[q+53]=rb;a[q+52]=a[qb>>2]}}}while(0);var lb=a[x+60>>2];if(0!=(lb|0)){var Ta=a[lb+64>>2];if(0!=(Ta|0)){J[Ta](x)}}var cb=a[q+37];0==(cb|0)&&0==(a[q+50]&1|0)||ad(x,cb,a[q+42],a[q+46],a[q+38]);h=k}function xD(x,r,i){var g,q,c,d,k,n,e,p,j,v,t,u,w,A,B,C,P=h;h+=64;var T,gb=P+16,y=P+32,M=P+48,X=a[x+16>>2]+96|0,O=(b[0]=a[X>>2],b[1]=a[X+4>>2],f[0]),D=r|0;Bk(V(D,lh|0));C=(r+24|0)>>2;if(0!=(a[C]|0)){for(var Da=Xb(D,a[uk>>2],1,0),ia=jc(D,a[go>>2],Y|0),F=ia,E=0,J=0;;){var sc=m[F];if(58==sc<<24>>24){var H=E+1|0,I=J}else{if(44==sc<<24>>24){H=E,I=J+1|0}else{if(0==sc<<24>>24){break}else{H=E,I=J}}}F=F+1|0;E=H;J=I}var K=0==(E|0);if(0==(J|0)|K){var hc=ia}else{if(0!=(yE(x,r,i,ia,E+1|0,Da,O)|0)){hc=Ac|0}else{h=P;return}}var ca=r+127|0,L=m[ca]&255;if(0==(L&1|0)){if(0!=(L&2|0)){var N=Aa(D,a[zE>>2],ho(hc,Dk|0)),zb=Aa(D,a[AE>>2],Ek|0);T=1982}else{if(0!=(L&8|0)){N=Aa(D,a[BE>>2],ho(hc,Fk|0)),zb=Aa(D,a[CE>>2],Gk|0),T=1982}else{if(0==(L&4|0)){var ja=hc}else{N=Aa(D,a[DE>>2],ho(hc,Hk|0)),zb=Aa(D,a[EE>>2],Ik|0),T=1982}}}}else{N=Aa(D,a[FE>>2],ho(hc,Jk|0)),zb=Aa(D,a[GE>>2],Kk|0),T=1982}1982==T&&((N|0)!=(hc|0)&&Pa(x,N),(zb|0)!=(hc|0)&&$b(x,zb),ja=N);if(K){0==(m[ca]&3)<<24>>24&&(0==m[ja]<<24>>24?(Pa(x,Ac|0),$b(x,Ac|0)):(Pa(x,ja),$b(x,ja)));var aa=a[C];if(0<(a[aa+4>>2]|0)){for(var da=x+148|0,ea=0==(i|0),xa=0,Q=aa;;){var Od=a[Q>>2];B=Od>>2;var ha=a[B+(12*xa|0)],ga=a[B+(12*xa|0)+1],Zd=a[B+(12*xa|0)+2],Rb=a[B+(12*xa|0)+3],W=Od+48*xa+16|0,tb=(b[0]=a[W>>2],b[1]=a[W+4>>2],f[0]),ya=Od+48*xa+24|0,S=(b[0]=a[ya>>2],b[1]=a[ya+4>>2],f[0]),wa=Od+48*xa+32|0,Ab=(b[0]=a[wa>>2],b[1]=a[wa+4>>2],f[0]),Fa=Od+48*xa+40|0,Ga=(b[0]=a[Fa>>2],b[1]=a[Fa+4>>2],f[0]);if(0==(a[da>>2]&16384|0)){$f(x,ha,ga,0,0,0);if(0!=(Zd|0)){var U=ha|0,ta=ha+8|0;Ci(x,2,tb,S,(b[0]=a[U>>2],b[1]=a[U+4>>2],f[0]),(b[0]=a[ta>>2],b[1]=a[ta+4>>2],f[0]),Da,O,Zd)}if(0!=(Rb|0)){var Ka=ga-1|0,za=(Ka<<4)+ha|0,ma=(Ka<<4)+ha+8|0;Ci(x,3,Ab,Ga,(b[0]=a[za>>2],b[1]=a[za+4>>2],f[0]),(b[0]=a[ma>>2],b[1]=a[ma+4>>2],f[0]),Da,O,Rb)}1<(a[a[C]+4>>2]|0)&&(0==(Rb|Zd|0)|ea||Ke(x,i))}else{$f(x,ha,ga,Zd,Rb,0)}var pa=xa+1|0,R=a[C];if((pa|0)<(a[R+4>>2]|0)){xa=pa,Q=R}else{break}}}}else{var Ha=a[a[C]+4>>2],Ra=48*Ha|0,la=Gb(Ra);A=la>>2;var $=Gb(Ra);w=$>>2;var La=.5*(E+2|0),fa=0<(Ha|0);a:do{if(fa){u=M>>2;t=P>>2;v=y>>2;j=gb>>2;for(var Ya=0,Z=0,Za=0;;){var ka=a[a[C]>>2];p=ka>>2;var ab=a[p+(12*Za|0)],$a=a[p+(12*Za|0)+1],jb=a[p+(12*Za|0)+2],Ca=a[p+(12*Za|0)+3],Ia=ka+48*Za+16|0,eb=(b[0]=a[Ia>>2],b[1]=a[Ia+4>>2],f[0]),ub=ka+48*Za+24|0,Sa=(b[0]=a[ub>>2],b[1]=a[ub+4>>2],f[0]),sa=ka+48*Za+32|0,ua=(b[0]=a[sa>>2],b[1]=a[sa+4>>2],f[0]),Oa=ka+48*Za+40|0,Wa=(b[0]=a[Oa>>2],b[1]=a[Oa+4>>2],f[0]);a[A+(12*Za|0)+1]=$a;a[w+(12*Za|0)+1]=$a;var pb=$a<<4,ob=Gb(pb);a[A+(12*Za|0)]=ob;var bb=Gb(pb);a[w+(12*Za|0)]=bb;var qb=ab|0,ra=(b[0]=a[qb>>2],b[1]=a[qb+4>>2],f[0]),kb=ab+8|0,ie=(b[0]=a[kb>>2],b[1]=a[kb+4>>2],f[0]),vb=$a-1|0,xb=0<(vb|0);b:do{if(xb){for(var hd=Ya,nb=Z,rb=ra,lb=ie,Ta=0;;){var cb=Ta+1|0,fb=(cb<<4)+ab|0,Ua=(b[0]=a[fb>>2],b[1]=a[fb+4>>2],f[0]),sb=(cb<<4)+ab+8|0,Na=(b[0]=a[sb>>2],b[1]=a[sb+4>>2],f[0]),Fb=(Ta<<4)+ob|0;0==(Ta|0)?(js(P,rb,lb,Ua,Na),e=Fb>>2,a[e]=a[t],a[e+1]=a[t+1],a[e+2]=a[t+2],a[e+3]=a[t+3]):(js(gb,hd,nb,Ua,Na),n=Fb>>2,a[n]=a[j],a[n+1]=a[j+1],a[n+2]=a[j+2],a[n+3]=a[j+3]);var Db=Ta+2|0,Ob=(Db<<4)+ab|0,Eb=(b[0]=a[Ob>>2],b[1]=a[Ob+4>>2],f[0]),ne=(Db<<4)+ab+8|0,Bb=(b[0]=a[ne>>2],b[1]=a[ne+4>>2],f[0]),Ja=Ta+3|0,qa=(Ja<<4)+ab|0,ba=(b[0]=a[qa>>2],b[1]=a[qa+4>>2],f[0]),na=(Ja<<4)+ab+8|0,oa=(b[0]=a[na>>2],b[1]=a[na+4>>2],f[0]),Ba=(cb<<4)+ob|0,Wf=(Db<<4)+ob|0,ic=y,Ea=rb,va=lb,Cb=ba,Ma=oa,wb=Ua-Eb,pc=Na-Bb,Wc=$c(wb*wb+pc*pc);if(1e-4>Wc){var Vb=Ea-Cb,Xd=va-Ma,Qa=$c(Vb*Vb+Xd*Xd+1e-4),Va=Vb,hb=Xd}else{Qa=Wc,Va=wb,hb=pc}var ib=2/Qa,Ib=ib*-Va,lc=ic|0;f[0]=hb*ib;a[lc>>2]=b[0];a[lc+4>>2]=b[1];var yb=ic+8|0;f[0]=Ib;a[yb>>2]=b[0];a[yb+4>>2]=b[1];k=Wf>>2;a[k]=a[v];a[k+1]=a[v+1];a[k+2]=a[v+2];a[k+3]=a[v+3];d=Ba>>2;a[d]=a[v];a[d+1]=a[v+1];a[d+2]=a[v+2];a[d+3]=a[v+3];var Jd=Fb|0,Nb=rb-La*(b[0]=a[Jd>>2],b[1]=a[Jd+4>>2],f[0]),Hc=(Ta<<4)+bb|0;f[0]=Nb;a[Hc>>2]=b[0];a[Hc+4>>2]=b[1];var mb=(Ta<<4)+ob+8|0,oe=lb-La*(b[0]=a[mb>>2],b[1]=a[mb+4>>2],f[0]),db=(Ta<<4)+bb+8|0;f[0]=oe;a[db>>2]=b[0];a[db+4>>2]=b[1];var Qb=Ba|0,tc=Ua-La*(b[0]=a[Qb>>2],b[1]=a[Qb+4>>2],f[0]),Sc=(cb<<4)+bb|0;f[0]=tc;a[Sc>>2]=b[0];a[Sc+4>>2]=b[1];var Lb=(cb<<4)+ob+8|0,Pb=Na-La*(b[0]=a[Lb>>2],b[1]=a[Lb+4>>2],f[0]),Jb=(cb<<4)+bb+8|0;f[0]=Pb;a[Jb>>2]=b[0];a[Jb+4>>2]=b[1];var rd=Wf|0,oc=Eb-La*(b[0]=a[rd>>2],b[1]=a[rd+4>>2],f[0]),EC=(Db<<4)+bb|0;f[0]=oc;a[EC>>2]=b[0];a[EC+4>>2]=b[1];var qk=(Db<<4)+ob+8|0,ve=Bb-La*(b[0]=a[qk>>2],b[1]=a[qk+4>>2],f[0]),Rd=(Db<<4)+bb+8|0;f[0]=ve;a[Rd>>2]=b[0];a[Rd+4>>2]=b[1];if((Ja|0)<(vb|0)){hd=Eb,nb=Bb,rb=ba,lb=oa,Ta=Ja}else{var Bc=Eb,Sd=Bb,fd=ba,Kb=oa,yc=Ja;break b}}}else{Bc=Ya,Sd=Z,fd=ra,Kb=ie,yc=0}}while(0);var Yb=(yc<<4)+ob|0;js(M,Bc,Sd,fd,Kb);c=Yb>>2;a[c]=a[u];a[c+1]=a[u+1];a[c+2]=a[u+2];a[c+3]=a[u+3];var Mb=Yb|0,Tc=fd-La*(b[0]=a[Mb>>2],b[1]=a[Mb+4>>2],f[0]),Wb=(yc<<4)+bb|0;f[0]=Tc;a[Wb>>2]=b[0];a[Wb+4>>2]=b[1];var Mc=(yc<<4)+ob+8|0,id=Kb-La*(b[0]=a[Mc>>2],b[1]=a[Mc+4>>2],f[0]),Tb=(yc<<4)+bb+8|0;f[0]=id;a[Tb>>2]=b[0];a[Tb+4>>2]=b[1];var Sb=Za+1|0;if((Sb|0)==(Ha|0)){var uc=Wa,mf=ua,nf=Sa,ec=eb,af=Ca,yf=jb,Td=vb,Fe=ab;break a}else{Ya=Bc,Z=Sd,Za=Sb}}}}while(0);var gf=Hb(ja),fe=he(gf,zf|0),fh=0==(fe|0);a:do{if(fh){var df=ja,jd=ja}else{for(var md=ja,je=ja,Qe=ja,Xf=fe,Uc=0;;){var bf=0==m[Xf]<<24>>24?Ac|0:Xf;if((bf|0)==(md|0)){var rk=md}else{0==(m[ca]&3)<<24>>24&&(Pa(x,bf),$b(x,bf)),rk=bf}var Zb=0==(Uc|0),mc=Zb?bf:Qe,ef=1==(Uc|0)?bf:Zb?bf:je;b:do{if(fa){for(var kd=0;;){var qc=a[w+(12*kd|0)],En=a[A+(12*kd|0)],sk=$+48*kd+4|0,kc=a[sk>>2],bc=0<(kc|0);c:do{if(bc){for(var nd=0;;){var zd=(nd<<4)+En|0;q=((nd<<4)+qc|0)>>2;var cf=(b[0]=a[zd>>2],b[1]=a[zd+4>>2],f[0])+(b[0]=a[q],b[1]=a[q+1],f[0]);f[0]=cf;a[q]=b[0];a[q+1]=b[1];var rc=(nd<<4)+En+8|0;g=((nd<<4)+qc+8|0)>>2;var fc=(b[0]=a[rc>>2],b[1]=a[rc+4>>2],f[0])+(b[0]=a[g],b[1]=a[g+1],f[0]);f[0]=fc;a[g]=b[0];a[g+1]=b[1];var zi=nd+1|0,we=a[sk>>2];if((zi|0)<(we|0)){nd=zi}else{var pe=we;break c}}}else{pe=kc}}while(0);$f(x,qc,pe,0,0,0);var ac=kd+1|0;if((ac|0)==(Ha|0)){break b}else{kd=ac}}}}while(0);var Kd=he(0,zf|0);if(0==(Kd|0)){df=ef;jd=mc;break a}else{md=rk,je=ef,Qe=mc,Xf=Kd,Uc=Uc+1|0}}}}while(0);if(0==(yf|0)){var Dd=0}else{if(0==(df|0)){var dc=0}else{0==(m[ca]&3)<<24>>24&&(Pa(x,df),$b(x,df)),dc=df}var nc=Fe|0,Ye=Fe+8|0;Ci(x,2,ec,nf,(b[0]=a[nc>>2],b[1]=a[nc+4>>2],f[0]),(b[0]=a[Ye>>2],b[1]=a[Ye+4>>2],f[0]),Da,O,yf);Dd=dc}if(0!=(af|0)){(Dd|0)!=(jd|0)&&0==(m[ca]&3)<<24>>24&&(Pa(x,jd),$b(x,jd));var Ad=(Td<<4)+Fe|0,Ud=(Td<<4)+Fe+8|0;Ci(x,3,mf,uc,(b[0]=a[Ad>>2],b[1]=a[Ad+4>>2],f[0]),(b[0]=a[Ud>>2],b[1]=a[Ud+4>>2],f[0]),Da,O,af)}G(gf);a:do{if(fa){for(var Vd=0;;){G(a[A+(12*Vd|0)]);G(a[w+(12*Vd|0)]);var gc=Vd+1|0;if((gc|0)==(Ha|0)){break a}else{Vd=gc}}}}while(0);G(la);G($)}}h=P}function yD(x){var r,i,g,q,c=a[x+16>>2];r=c>>2;var d=a[r+2];g=c+148|0;0==(a[g>>2]|0)?0!=(a[r+50]&1|0)&&(q=2041):q=2041;a:do{if(2041==q){Se(x);i=c+216|0;var k=a[i>>2];if(0!=(k|0)){var n=c+220|0;if(1<(k|0)){for(var e=a[n>>2],k=c+208|0,h=c+224|0,j=c+212|0,m=c+168|0,t=c+184|0,u=c+152|0,w=1,A=a[e>>2];;){if(a[k>>2]=a[e+(w<<2)>>2],a[j>>2]=(A<<4)+a[h>>2]|0,ad(x,a[g>>2],a[m>>2],a[t>>2],a[u>>2]),Se(x),e=a[n>>2],A=a[e+(w<<2)>>2]+A|0,w=w+1|0,(w|0)>=(a[i>>2]|0)){break a}}}}}}while(0);a[r+52]=0;a[r+53]=0;q=(d+24|0)>>2;g=a[q];0==(g|0)?r=c+200|0:(g=a[g>>2],n=a[g>>2],0==(a[g+8>>2]|0)?(i=n|0,n=n+8|0):(i=g+16|0,n=g+24|0),g=c+200|0,k=a[g>>2],HE(x,(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),k<<24>>31&255,a[r+40],k<<30>>31&255),i=a[q],n=a[i+4>>2]-1|0,k=a[i>>2],i=k>>2,0==(a[i+(12*n|0)+3]|0)?(k=a[i+(12*n|0)],n=a[i+(12*n|0)+1]-1|0,i=(n<<4)+k|0,n=(n<<4)+k+8|0):(i=k+48*n+32|0,n=k+48*n+40|0),k=a[g>>2],HE(x,(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),k<<23>>31&255,a[r+41],k<<29>>31&255),r=g);g=r>>2;j=a[d+108>>2];m=a[g]<<28>>31;h=c+156|0;t=a[h>>2];k=c+172|0;u=a[k>>2];n=c+188|0;w=a[n>>2];r=(c+152|0)>>2;A=a[r];i=d|0;e=0==re(jc(i,a[io>>2],Ze|0))<<24>>24?0:a[q];Qi(x,j,11,m,t,u,w,A,e);j=a[d+120>>2];m=a[g]<<28>>31;h=a[h>>2];k=a[k>>2];n=a[n>>2];t=a[r];0==re(jc(i,a[io>>2],Ze|0))<<24>>24?Qi(x,j,11,m,h,k,n,t,0):(q=a[q],Qi(x,j,11,m,h,k,n,t,q));q=a[(d+112|0)>>2];i=a[g];i=i<<29>>31;n=a[(c+164|0)>>2];k=a[(c+180|0)>>2];h=a[(c+196|0)>>2];j=a[r];Qi(x,q,7,i,n,k,h,j,0);d=a[(d+116|0)>>2];q=a[g];q=q<<30>>31;g=a[(c+160|0)>>2];i=a[(c+176|0)>>2];c=a[(c+192|0)>>2];r=a[r];Qi(x,d,6,q,g,i,c,r,0);c=a[x+60>>2];if(0!=(c|0)&&(c=a[c+68>>2],0!=(c|0))){J[c](x)}Ei(x)}function HE(b,r,i,g,q,c){var d=a[b+16>>2],g=0==g<<24>>24?a[d+148>>2]:q;0==c<<24>>24&&0==(g|0)&0==(a[d+200>>2]&1|0)||IE(b,r,i)}function Qi(b,r,i,g,q,c,d,f,n){var e=h,p=a[b+148>>2];if(0!=(r|0)){if(0==(f|0)){f=0}else{var s=fa(Ba(f)+11|0);if(11==(i|0)){var m=dh|0}else{7==(i|0)?m=ks|0:6==(i|0)?m=ls|0:sa(Fi|0,2201,JE|0,vd|0)}Ma(s,KE|0,(j=h,h+=8,a[j>>2]=f,a[j+4>>2]=m,j));f=s}var s=b+16|0,t=a[s>>2]+12|0,m=a[t>>2];a[t>>2]=i;g=0==(q|0)&0==(g|0);!g&&0==(p&4|0)&&(ms(b,r),ad(b,q,c,d,f));Fg(b,i,r);0!=(n|0)&&LE(b,r,n);g||(0!=(p&4|0)&&(ms(b,r),ad(b,q,c,d,f)),Se(b));0!=(f|0)&&G(f);a[a[s>>2]+12>>2]=m}h=e}function ME(x,r,i){var g=x|0;f[0]=r;a[g>>2]=b[0];a[g+4>>2]=b[1];x=x+8|0;f[0]=i;a[x>>2]=b[0];a[x+4>>2]=b[1]}function ms(x,r){var i,g,q,c,d;i=a[x+16>>2];d=i>>2;var k=a[x+148>>2];if(0!=(k&4259840|0)){var n=0!=(k&131072|0);g=i+204|0;n?(a[g>>2]=0,a[d+52]=2):(a[g>>2]=2,a[d+52]=4);i=i+212|0;G(a[i>>2]);d=fa(a[d+52]<<4);a[i>>2]=d;c=(r+56|0)>>2;q=(r+24|0)>>2;i=(b[0]=a[c],b[1]=a[c+1],f[0])-.5*(b[0]=a[q],b[1]=a[q+1],f[0]);f[0]=i;a[d>>2]=b[0];a[d+4>>2]=b[1];g=(r+64|0)>>2;i=(r+32|0)>>2;var e=(b[0]=a[g],b[1]=a[g+1],f[0])-.5*(b[0]=a[i],b[1]=a[i+1],f[0]),h=d+8|0;f[0]=e;a[h>>2]=b[0];a[h+4>>2]=b[1];q=(b[0]=a[c],b[1]=a[c+1],f[0])+.5*(b[0]=a[q],b[1]=a[q+1],f[0]);c=d+16|0;f[0]=q;a[c>>2]=b[0];a[c+4>>2]=b[1];i=(b[0]=a[g],b[1]=a[g+1],f[0])+.5*(b[0]=a[i],b[1]=a[i+1],f[0]);g=d+24|0;f[0]=i;a[g>>2]=b[0];a[g+4>>2]=b[1];0==(k&8192|0)&&Re(x,d,d,2);n||Mn(d)}}function LE(x,r,i){var g,q,c,d,k,n,e=h;h+=96;c=e+48;q=e+64;g=e+80;for(n=a[r>>2];;){var p=m[n];if(0==p<<24>>24){k=2112;break}if(0==(ah(p&255)|0)){break}else{n=n+1|0}}2112!=k&&0!=m[n]<<24>>24&&(k=r+24|0,p=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),k=r+32|0,d=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),n=(r+56|0)>>2,k=(r+64|0)>>2,ME(c,(b[0]=a[n],b[1]=a[n+1],f[0])+.5*p,(b[0]=a[k],b[1]=a[k+1],f[0])-.5*d),d=e>>2,c>>=2,a[d]=a[c],a[d+1]=a[c+1],a[d+2]=a[c+2],a[d+3]=a[c+3],c=e|0,d=e+8|0,ME(q,(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])-p,(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0])),c=(e+16|0)>>2,q>>=2,a[c]=a[q],a[c+1]=a[q+1],a[c+2]=a[q+2],a[c+3]=a[q+3],NE(g,i,(b[0]=a[n],b[1]=a[n+1],f[0]),(b[0]=a[k],b[1]=a[k+1],f[0])),i=(e+32|0)>>2,g>>=2,a[i]=a[g],a[i+1]=a[g+1],a[i+2]=a[g+2],a[i+3]=a[g+3],Ke(x,a[a[x>>2]+296>>2]),Pa(x,a[r+8>>2]),Gd(x,e|0,3));h=e}function IE(x,r,i){var g,q=a[x+16>>2];g=q>>2;var c=a[x+148>>2];if(0!=(c&4259840|0)){var d=0!=(c&131072|0),k=q+204|0;d?(a[k>>2]=0,a[g+52]=2):(a[k>>2]=2,a[g+52]=4);q=q+212|0;G(a[q>>2]);g=fa(a[g+52]<<4);a[q>>2]=g;f[0]=r-3;a[g>>2]=b[0];a[g+4>>2]=b[1];q=g+8|0;f[0]=i-3;a[q>>2]=b[0];a[q+4>>2]=b[1];q=g+16|0;f[0]=r+3;a[q>>2]=b[0];a[q+4>>2]=b[1];r=g+24|0;f[0]=i+3;a[r>>2]=b[0];a[r+4>>2]=b[1];0==(c&8192|0)&&Re(x,g,g,2);d||Mn(g)}}function yE(x,r,i,g,q,c,d){var k=h;h+=196;var n=k+48,e=k+96,p=k+144,s=k+192,g=OE(g,q,s);if(1<(g|0)){var q=a[r+16>>2],m=0!=(a[a[q+20>>2]>>2]&16|0)?ns|0:os|0,t=a[a[r+12>>2]+12>>2];la(3,PE|0,(j=h,h+=12,a[j>>2]=a[q+12>>2],a[j+4>>2]=m,a[j+8>>2]=t,j));if(2==(g|0)){return h=k,1}}else{if(1==(g|0)){return h=k,1}}g=(r+24|0)>>2;q=a[g];m=0<(a[q+4>>2]|0);a:do{if(m){for(var t=k,u=k+8|0,w=k+12|0,A=0==(i|0),B=k+4|0,C=k|0,P=k+32|0,T=k+40|0,gb=k+16|0,y=k+24|0,M=e|0,X=e+4|0,O=n,D=p,Da=n|0,ia=p|0,F=p+4|0,E=a[s>>2],r=(E+4|0)>>2,J=0,sc=q;;){for(var H,I=(a[sc>>2]+48*J|0)>>2,K=t>>2,hc=I+12;I>2],L=0==(ca|0);b:do{if(L){var N=H}else{for(var zb=H,ja=sc,aa=ca;;){Pa(x,aa);aa=ja+4|0;I=ib[aa>>2];if((ja|0)==(a[r]|0)){ps(k,I,e,p),aa=a[M>>2],$f(x,aa,a[X>>2],0,0,0)}else{if(1>I){I=D>>2;K=O>>2;for(hc=I+12;I>2],e,p);G(a[Da>>2]);aa=a[M>>2];$f(x,aa,a[X>>2],0,0,0)}else{zb=a[ja>>2],aa=a[ia>>2],$f(x,aa,a[F>>2],0,0,0)}}G(aa);ja=ja+8|0;aa=a[ja>>2];if(0==(aa|0)){N=zb;break b}}}}while(0);sc=a[u>>2];H=0==(sc|0);H||(Pa(x,a[a[r]>>2]),$b(x,a[a[r]>>2]),zb=a[C>>2],ca=(b[0]=a[gb>>2],b[1]=a[gb+4>>2],f[0]),L=(b[0]=a[y>>2],b[1]=a[y+4>>2],f[0]),ja=zb|0,zb=zb+8|0,Ci(x,2,ca,L,(b[0]=a[ja>>2],b[1]=a[ja+4>>2],f[0]),(b[0]=a[zb>>2],b[1]=a[zb+4>>2],f[0]),c,d,sc));sc=a[w>>2];0==(sc|0)?ca=0:(Pa(x,N),$b(x,N),zb=a[B>>2]-1|0,aa=a[C>>2],ca=(b[0]=a[P>>2],b[1]=a[P+4>>2],f[0]),L=(b[0]=a[T>>2],b[1]=a[T+4>>2],f[0]),ja=(zb<<4)+aa|0,zb=(zb<<4)+aa+8|0,Ci(x,3,ca,L,(b[0]=a[ja>>2],b[1]=a[ja+4>>2],f[0]),(b[0]=a[zb>>2],b[1]=a[zb+4>>2],f[0]),c,d,sc),ca=sc);L=a[g];ja=a[L+4>>2];do{if(1<(ja|0)){if(H){if(0==(ca|0)|A){sc=L;zb=ja;break}}else{if(A){sc=L;zb=ja;break}}Ke(x,i);sc=zb=a[g];zb=a[zb+4>>2]}else{sc=L,zb=ja}}while(0);J=J+1|0;if((J|0)<(zb|0)){H=N}else{var da=E;break a}}}else{da=a[s>>2]}}while(0);G(da);h=k;return 0}function ho(b,r){for(var i=1,g=b;;){var q=m[g];if(0==q<<24>>24){break}else{i=58==q<<24>>24?i+1|0:i}g=g+1|0}g=(Ba(r)+1)*i|0;(a[qs>>2]|0)<(g|0)&&(g=g+10|0,a[qs>>2]=g,g=mc(a[Ri>>2],g),a[Ri>>2]=g);Rf(a[Ri>>2],r);i=i-1|0;g=a[Ri>>2];if(0==(i|0)){var c;return g}for(;;){if(q=g+Ba(g)|0,Pb=58,m[q]=Pb&255,Pb>>=8,m[q+1]=Pb&255,uf(g,r),i=i-1|0,g=a[Ri>>2],0==(i|0)){c=g;break}}return c}function js(x,r,i,g,q){r-=g;q=i-q;i=2/$c(r*r+q*q+1e-4);g=x|0;f[0]=q*i;a[g>>2]=b[0];a[g+4>>2]=b[1];x=x+8|0;f[0]=i*-r;a[x>>2]=b[0];a[x+4>>2]=b[1]}function OE(b,r,i){var g,q=h,c,d=fa(8);g=(r<<3)+8|0;fa(g);var f=Hb(b);a[d>>2]=f;var n=fa(g);g=n>>2;a[d+4>>2]=n;var e=n=0,p=0,f=he(f,zf|0);a:for(;0!=(f|0);){var s=r-1|0;do{if((p|0)==(s|0)){if(1>e){var v=Jc(f,44);0!=(v|0)&&(m[v]=0);a[(p<<3>>2)+g]=f;ib[((p<<3)+4>>2)+g]=1;v=n;t=r;u=e;w=p+1|0}else{var v=n,t=r,u=e,w=p}}else{w=f;t=e;v=h;h+=4;u=Jc(w,44);0==(u|0)?(la(1,QE|0,(j=h,h+=4,a[j>>2]=w,j)),t=-1):(w=u+1|0,m[u]=0,u=wg(w,v),t=(a[v>>2]|0)!=(w|0)&&u>=t&1>=u?u:-1);h=v;u=t;if(0>u){c=2180;break a}e>2)+g]=f,ib[((p<<3)+4>>2)+g]=(u-e)/(1-e),v=n,t=r,w=p+1|0):(m[Nk]?v=n:(la(0,RE|0,(j=h,h+=4,a[j>>2]=b,j)),m[Nk]=1,v=3),t=s,u=e,w=p)}}while(0);n=v;r=t;e=u;p=w;f=he(0,zf|0)}if(2180==c){return m[Nk]?b=1:(la(1,SE|0,(j=h,h+=4,a[j>>2]=b,j)),m[Nk]=1,b=2),TE(d),h=q,b}0==(p|0)?(TE(d),d=1):(a[i>>2]=d,d=n);h=q;return d}function ps(x,r,i,g){var q,c,d,k,n,e=h;h+=32;var p=e+16,j=a[x+4>>2],m=j-1|0;k=(m|0)/3&-1;if(3>(j-4|0)>>>0){a[i+4>>2]=4;p=i|0;a[p>>2]=fa(64);a[g+4>>2]=4;var t=fa(64);a[g>>2]=t;Ld(e,a[x>>2],3,r,a[p>>2],t)}else{j=fa(k<<3);x=(x|0)>>2;m=2<(m|0);a:do{if(m){var u=0;d=0;for(n=a[x];;){q=n;c=q|0;var w=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]);c=q+16|0;c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]);var A=w-c,w=q+8|0,B=(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0]),w=q+24|0,w=(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0]),B=B-w,B=$c(A*A+B*B),A=q+32|0,A=(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0]),C=c-A;c=q+40|0;c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]);w-=c;w=B+$c(C*C+w*w);B=q+48|0;A-=(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0]);q=q+56|0;q=c-(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]);q=w+$c(A*A+q*q);c=(u<<3)+j|0;f[0]=q;a[c>>2]=b[0];a[c+4>>2]=b[1];d+=q;u=u+1|0;if((u|0)<(k|0)){n=n+48|0}else{var P=d;break a}}}else{P=0}}while(0);r*=P;for(P=m=0;;){if((P|0)>=(k|0)){t=m;break}n=(P<<3)+j|0;m+=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]);if(m>2;a[n]=u;i=(i|0)>>2;a[i]=fa(u<<4);u=3*(k-P)+1|0;k=(g+4|0)>>2;a[k]=u;g=(g|0)>>2;a[g]=fa(u<<4);if(0<(a[n]|0)){for(u=0;;){if(q=((u<<4)+a[i]|0)>>2,d=((u<<4)+a[x]|0)>>2,a[q]=a[d],a[q+1]=a[d+1],a[q+2]=a[d+2],a[q+3]=a[d+3],d=u+1|0,(d|0)<(a[n]|0)){u=d}else{break}}n=u-3|0}else{n=-4}u=0<(a[k]|0);a:do{if(u){w=0;for(d=n;;){c=((w<<4)+a[g]|0)>>2;q=((d<<4)+a[x]|0)>>2;a[c]=a[q];a[c+1]=a[q+1];a[c+2]=a[q+2];a[c+3]=a[q+3];q=w+1|0;if((q|0)>=(a[k]|0)){break a}w=q;d=d+1|0}}}while(0);k=(P<<3)+j|0;k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);Ld(p,(m<<4)+a[x]|0,3,(r-(t-k))/k,(m<<4)+a[i]|0,a[g]);G(j)}h=e}function TE(b){G(a[b>>2]);G(a[b+4>>2]);G(b)}function ph(x,r){var i,g=h;i=x>>2;x=h;h+=32;a[x>>2]=a[i];a[x+4>>2]=a[i+1];a[x+8>>2]=a[i+2];a[x+12>>2]=a[i+3];a[x+16>>2]=a[i+4];a[x+20>>2]=a[i+5];a[x+24>>2]=a[i+6];a[x+28>>2]=a[i+7];i=r>>2;r=h;h+=32;a[r>>2]=a[i];a[r+4>>2]=a[i+1];a[r+8>>2]=a[i+2];a[r+12>>2]=a[i+3];a[r+16>>2]=a[i+4];a[r+20>>2]=a[i+5];a[r+24>>2]=a[i+6];a[r+28>>2]=a[i+7];i=x+16|0;var c=r|0;if((b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])<(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])){return h=g,0}i=r+16|0;c=x|0;if((b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])<(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])){return h=g,0}i=x+24|0;c=r+8|0;if((b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])<(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])){return h=g,0}i=r+24|0;c=x+8|0;i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])>=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]);h=g;return i&1}function xE(x,r,i,g,c){var d,l,k,n,e,p,j,m,t,u=h;h+=1664;var w=u+64,A=u+864,B=Cb(20);a[B+16>>2]=1;p=a[g+4>>2]-1|0;k=(p|0)/3&-1;d=2<(p|0);a:do{if(d){l=g|0;var C=u|0;t=u>>2;m=(u+16|0)>>2;j=(u+32|0)>>2;p=(u+48|0)>>2;for(var P=B,T=0;;){n=3*T|0;var gb=a[l>>2];e=((n<<4)+gb|0)>>2;a[t]=a[e];a[t+1]=a[e+1];a[t+2]=a[e+2];a[t+3]=a[e+3];e=((n+1<<4)+gb|0)>>2;a[m]=a[e];a[m+1]=a[e+1];a[m+2]=a[e+2];a[m+3]=a[e+3];e=((n+2<<4)+gb|0)>>2;a[j]=a[e];a[j+1]=a[e+1];a[j+2]=a[e+2];a[j+3]=a[e+3];n=((n+3<<4)+gb|0)>>2;a[p]=a[n];a[p+1]=a[n+1];a[p+2]=a[n+2];a[p+3]=a[n+3];T=T+1|0;if((T|0)<(k|0)){P=rs(C,P)}else{break a}}}}while(0);if(0!=(B|0)){p=A|0;j=w|0;k=w>>2;g=A>>2;m=B;for(C=P=0;;){t=a[m+16>>2];l=(C<<4)+w|0;d=(C<<4)+A|0;n=m|0;var T=m+8|0,y=P,P=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]);n=(b[0]=a[T>>2],b[1]=a[T+4>>2],f[0]);var M=t,gb=l,T=d;e=c;if(0==(y|0)){var y=M|0,X=(b[0]=a[y>>2],b[1]=a[y+4>>2],f[0]),M=M+8|0,O=(b[0]=a[M>>2],b[1]=a[M+4>>2],f[0]),M=O,y=X,X=2*P-X,O=2*n-O}else{X=y|0,X=(b[0]=a[X>>2],b[1]=a[X+4>>2],f[0]),y=y+8|0,O=(b[0]=a[y>>2],b[1]=a[y+4>>2],f[0]),0==(M|0)?(M=2*n-O,y=2*P-X):(y=M|0,y=(b[0]=a[y>>2],b[1]=a[y+4>>2],f[0]),M=M+8|0,M=(b[0]=a[M>>2],b[1]=a[M+4>>2],f[0]))}var D=P,Da=n,M=Cf(M-Da,y-D),y=Cf(O-Da,X-D),M=M-y,y=y+.5*(0>2]=b[0];a[X+4>>2]=b[1];gb=gb+8|0;f[0]=y;a[gb>>2]=b[0];a[gb+4>>2]=b[1];n-=e;gb=T|0;f[0]=P-M;a[gb>>2]=b[0];a[gb+4>>2]=b[1];P=T+8|0;f[0]=n;a[P>>2]=b[0];a[P+4>>2]=b[1];P=C+1|0;C=0==(t|0);C|50==(P|0)?(UE(x,r,i,P,j,p),l>>=2,a[k]=a[l],a[k+1]=a[l+1],a[k+2]=a[l+2],a[k+3]=a[l+3],d>>=2,a[g]=a[d],a[g+1]=a[d+1],a[g+2]=a[d+2],a[g+3]=a[d+3],d=1):d=P;if(C){var ia=B;break}else{P=m,m=t,C=d}}for(;!(x=a[ia+16>>2],G(ia),0==(x|0));){ia=x}}h=u}function rs(x,r){var i,g,c=h;h+=144;if(0==(cD(x)|0)){return i=c|0,g=c+64|0,Ld(c+128,x,3,.5,i,g),i=rs(g,rs(i,r)),h=c,i}i=r+16|0;1==(a[i>>2]|0)&&(a[i>>2]=0,g=r>>2,i=x>>2,a[g]=a[i],a[g+1]=a[i+1],a[g+2]=a[i+2],a[g+3]=a[i+3]);i=x+48|0;g=x+56|0;i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);var d=Cb(20);a[d+16>>2]=0;f[0]=i;a[d>>2]=b[0];a[d+4>>2]=b[1];i=d+8|0;f[0]=g;a[i>>2]=b[0];a[i+4>>2]=b[1];a[r+16>>2]=d;h=c;return d}function UE(b,r,i,g,c,d){var f,k=g<<1;f=a[i>>2];if(0<(f|0)){for(var n=a[r>>2],e=0,h=0;;){var j=a[n+(e<<2)>>2]+h|0,e=e+1|0;if((e|0)<(f|0)){h=j}else{break}}n=1<(f|0)?f:1}else{j=n=0}f=f+1|0;a[i>>2]=f;i=wb(a[r>>2],f<<2);a[r>>2]=i;a[i+(n<<2)>>2]=k;i=wb(a[b>>2],j+k<<4);a[b>>2]=i;if(0<(g|0)){k=k-1+j|0;for(r=0;;){f=((r+j<<4)+i|0)>>2;i=((r<<4)+c|0)>>2;a[f]=a[i];a[f+1]=a[i+1];a[f+2]=a[i+2];a[f+3]=a[i+3];f=((k-r<<4)+a[b>>2]|0)>>2;i=((r<<4)+d|0)>>2;a[f]=a[i];a[f+1]=a[i+1];a[f+2]=a[i+2];a[f+3]=a[i+3];r=r+1|0;if((r|0)==(g|0)){break}i=a[b>>2]}}}function Gi(b,r){var i,g=h;h+=144;var c=g+128,d=a[b>>2];fc(c,128,g|0);db(c,r);i=(c+4|0)>>2;var f=a[i];f>>>0>2]>>>0||(na(c,1),f=a[i]);m[f]=0;var k=a[c>>2];a[i]=k;f=d+268|0;i=he(k,a[f>>2]);f=(k=0!=(i|0))?he(0,a[f>>2]):0;k=(0!=(f|0)&1)+(k&1)|0;1==(k|0)?(f=a[b+156>>2],d=(ss(d,i,f)|0)==(f|0)&1):2==(k|0)?(i=ss(d,i,0),d=ss(d,f,a[b+152>>2]),f=-1<(d|i|0)&(i|0)>(d|0),k=a[b+156>>2],d=(k|0)<=((f?i:d)|0)&((f?d:i)|0)<=(k|0)&1):d=0;uc(c);h=g;return d}function ss(b,r,i){var g,c=a[b+124>>2],d=m[r];if(97==d<<24>>24&&0==(ka(r,VE|0)|0)){var f;return i}for(var k,i=r;;){var n=m[i];if(0==n<<24>>24){var e=1;k=2222;break}if(10>((n&255)-48|0)>>>0){i=i+1|0}else{e=0;k=2221;break}}if(0!=(2221==k||2222==k?e:cc)<<24>>24){return f=bh(r)}b=a[b+276>>2];if(0==(b|0)){return-1}c=a[c+152>>2];for(k=1;;){if((k|0)>(c|0)){f=-1;g=2297;break}e=a[b+(k<<2)>>2];if(d<<24>>24==m[e]<<24>>24&&0==(ka(r,e)|0)){f=k;g=2298;break}k=k+1|0}if(2298==g||2297==g){return f}}function pD(b,r,i){var g=2>(a[b+152>>2]|0);a:do{if(g){var c=1}else{var d=jc(i|0,a[Un>>2],Y|0);if(0!=Gi(b,d)<<24>>24){c=1}else{if(0!=m[d]<<24>>24){c=0}else{var d=r,f=i;if(0==(Ok(d,f)|0)){c=1}else{for(var k=Ok(d,f);;){if(0==(k|0)){c=0;break a}var n=jc(k|0,a[Tn>>2],Y|0);if(0==m[n]<<24>>24){c=1;break a}if(0!=Gi(b,n)<<24>>24){c=1;break a}k=Pk(d,k,f)}}}}}}while(0);return c}function vD(x,r){var i,g,c,d,l,k,n,e,h,j=a[x+148>>2],v=wk(x);e=v>>2;a[e+1]=2;a[e+2]=r;a[e+3]=8;if(0!=(j&16777216|0)){if(2<(D[a[r+20>>2]+206>>1]&65535)){var t=a[r+148>>2]+16|0,u=72*(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0]),w=v+108|0;f[0]=(0>u?u-.5:u+.5)&-1|0;a[w>>2]=b[0];a[w+4>>2]=b[1]}else{var A=v+108|0;f[0]=0;a[A>>2]=b[0];a[A+4>>2]=b[1]}}var B=r|0;Sr(x,a[r+120>>2],B);if(0==(j&4259840|0)){var C=V(B,lh|0)}else{if(0==(a[e+37]|0)&&0==(a[e+50]&1|0)){C=V(B,lh|0);Bk(C);WE(x);return}var P=ts(r),T=r+32|0,gb=(b[0]=a[T>>2],b[1]=a[T+4>>2],f[0]),y=r+40|0,M=(b[0]=a[y>>2],b[1]=a[y+4>>2],f[0]),X;var O=Aa(r|0,a[Ki>>2],Y|0);if(0==m[O]<<24>>24){var F=0}else{nh(O);var Da=a[Cc>>2];if(0==(Da|0)){F=0}else{for(var ia=Cc|0,E=0,G=Da;;){var J=0==(ka(G,Hi|0)|0)?1:E,sc=ia+4|0,H=a[sc>>2];if(0==(H|0)){F=J;break}else{ia=sc,E=J,G=H}}}}X=F;a:do{if(3==(P|0)||1==(P|0)){var I=a[r+28>>2];n=I>>2;var K=0==FQa(I)<<24>>24?1:0==(a[n+1]|X|0);if(0!=(I|0)&K){if(0==(j&524288|0)){h=2342}else{k=(I+8|0)>>2;var hc=a[k],ca=3>(hc|0)?1:hc,L=I+4|0,N=a[L>>2],zb=1<(N|0)?N:1,ja=a[n+10],aa=V(B,us|0),da=0==(aa|0)?0:bh(aa),ea=56<(da-4|0)>>>0?20:da;if(0==(a[L>>2]|X|0)){a[e+51]=0;var xa=fa(32),Q=xa;l=(r+104|0)>>2;var Od=gb-(b[0]=a[l],b[1]=a[l+1],f[0]),ha=xa;f[0]=Od;a[ha>>2]=b[0];a[ha+4>>2]=b[1];d=(r+96|0)>>2;var ga=M-.5*(b[0]=a[d],b[1]=a[d+1],f[0]),W=xa+8|0;f[0]=ga;a[W>>2]=b[0];a[W+4>>2]=b[1];var Rb=gb+(b[0]=a[l],b[1]=a[l+1],f[0]),S=xa+16|0;f[0]=Rb;a[S>>2]=b[0];a[S+4>>2]=b[1];var tb=M+.5*(b[0]=a[d],b[1]=a[d+1],f[0]),ya=xa+24|0;f[0]=tb;a[ya>>2]=b[0];a[ya+4>>2]=b[1];var U=Q,wa=2}else{var Ab=a[k];do{if(3>(Ab|0)){var Fa=I+28|0;if(0==(b[0]=a[Fa>>2],b[1]=a[Fa+4>>2],f[0])){var Ga=I+20|0;if(0==(b[0]=a[Ga>>2],b[1]=a[Ga+4>>2],f[0])){var R=v+204|0;if(0!=(a[n]|0)){a[R>>2]=1;var ta=fa(32),Ka=ta,za=ta;f[0]=gb;a[za>>2]=b[0];a[za+4>>2]=b[1];var ma=ta+8|0;f[0]=M;a[ma>>2]=b[0];a[ma+4>>2]=b[1];var pa=(zb<<1)-1|0,la=(pa<<4)+ja|0,Ha=gb+(b[0]=a[la>>2],b[1]=a[la+4>>2],f[0]),Ra=ta+16|0;f[0]=Ha;a[Ra>>2]=b[0];a[Ra+4>>2]=b[1];var $=(pa<<4)+ja+8|0,Z=M+(b[0]=a[$>>2],b[1]=a[$+4>>2],f[0]),La=ta+24|0;f[0]=Z;a[La>>2]=b[0];a[La+4>>2]=b[1];U=Ka;wa=2;break a}a[R>>2]=2;var Vf=(zb<<1)-1|0,Ya=(Vf<<4)+ja|0,sa=(Vf<<4)+ja+8|0,Za;var ra=(b[0]=a[Ya>>2],b[1]=a[Ya+4>>2],f[0]),ab=(b[0]=a[sa>>2],b[1]=a[sa+4>>2],f[0]),$a=ea,jb=6.283185307179586/($a|0),Ca=fa($a<<4);if(0<($a|0)){for(var Ia=0,eb=0;;){var ub=se(Ia)*ra,Sa=(eb<<4)+Ca|0;f[0]=ub;a[Sa>>2]=b[0];a[Sa+4>>2]=b[1];var CQa=Ce(Ia)*ab,ua=(eb<<4)+Ca+8|0;f[0]=CQa;a[ua>>2]=b[0];a[ua+4>>2]=b[1];var Oa=eb+1|0;if((Oa|0)==($a|0)){break}else{Ia+=jb,eb=Oa}}}Za=Ca;if(0<(ea|0)){var Wa=0}else{U=Za;wa=ea;break a}for(;;){c=((Wa<<4)+Za|0)>>2;var pb=(b[0]=a[c],b[1]=a[c+1],f[0])+gb;f[0]=pb;a[c]=b[0];a[c+1]=b[1];g=((Wa<<4)+Za+8|0)>>2;var ob=(b[0]=a[g],b[1]=a[g+1],f[0])+M;f[0]=ob;a[g]=b[0];a[g+1]=b[1];var bb=Wa+1|0;if((bb|0)==(ea|0)){U=Za;wa=ea;break a}else{Wa=bb}}}}}}while(0);var qb=Ab*(zb-1)|0;a[e+51]=2;var ba=a[k];if((ba|0)<(ea|0)){var kb=fa(ca<<4);if(0<(ca|0)){for(var ie=0;;){var vb=ie+qb|0,xb=(vb<<4)+ja|0,hd=gb+(b[0]=a[xb>>2],b[1]=a[xb+4>>2],f[0]),nb=(ie<<4)+kb|0;f[0]=hd;a[nb>>2]=b[0];a[nb+4>>2]=b[1];var rb=(vb<<4)+ja+8|0,lb=M+(b[0]=a[rb>>2],b[1]=a[rb+4>>2],f[0]),Ta=(ie<<4)+kb+8|0;f[0]=lb;a[Ta>>2]=b[0];a[Ta+4>>2]=b[1];var cb=ie+1|0;if((cb|0)==(ca|0)){U=kb;wa=ca;break a}else{ie=cb}}}else{U=kb,wa=ca}}else{var fb=(ba|0)/(ea|0)&-1,Ua=fa(ea<<4);if(0<(ea|0)){for(var sb=0,Na=0;;){var Fb=Na+qb|0,Db=(Fb<<4)+ja|0,Ob=gb+(b[0]=a[Db>>2],b[1]=a[Db+4>>2],f[0]),Eb=(sb<<4)+Ua|0;f[0]=Ob;a[Eb>>2]=b[0];a[Eb+4>>2]=b[1];var ne=(Fb<<4)+ja+8|0,Bb=M+(b[0]=a[ne>>2],b[1]=a[ne+4>>2],f[0]),Ja=(sb<<4)+Ua+8|0;f[0]=Bb;a[Ja>>2]=b[0];a[Ja+4>>2]=b[1];var qa=sb+1|0;if((qa|0)==(ea|0)){U=Ua;wa=ea;break a}else{sb=qa,Na=Na+fb|0}}}else{U=Ua,wa=ea}}}}}else{h=2342}}else{h=2342}}while(0);if(2342==h){a[e+51]=0;var na=fa(32),oa=r+104|0,Ba=gb-(b[0]=a[oa>>2],b[1]=a[oa+4>>2],f[0]);f[0]=Ba;a[na>>2]=b[0];a[na+4>>2]=b[1];i=(r+96|0)>>2;var Ea=M-.5*(b[0]=a[i],b[1]=a[i+1],f[0]),Wf=na+8|0;f[0]=Ea;a[Wf>>2]=b[0];a[Wf+4>>2]=b[1];var ic=r+112|0,va=gb+(b[0]=a[ic>>2],b[1]=a[ic+4>>2],f[0]),Cb=na+16|0;f[0]=va;a[Cb>>2]=b[0];a[Cb+4>>2]=b[1];var Ma=M+.5*(b[0]=a[i],b[1]=a[i+1],f[0]),wb=na+24|0;f[0]=Ma;a[wb>>2]=b[0];a[wb+4>>2]=b[1];U=na;wa=2}0==(j&8192|0)&&Re(x,U,U,wa);a[e+53]=U;a[e+52]=wa;C=V(B,lh|0)}Bk(C);WE(x)}function FQa(x){if(4!=(a[x+8>>2]|0)){return 0}var r=x+12|0,r=(b[0]=a[r>>2],b[1]=a[r+4>>2],f[0]);if(0!=(((0>r?r-.5:r+.5)&-1)%90|0)){return 0}r=x+20|0;if(0!=(b[0]=a[r>>2],b[1]=a[r+4>>2],f[0])){return 0}x=x+28|0;x=0==(b[0]=a[x>>2],b[1]=a[x+4>>2],f[0]);return x&1}function Sr(b,r,i){var g=h;h+=144;var c,d=g+128,f=V(i,Ni|0),k=V(i,Pi|0),n=V(i,is|0);fc(d,128,g|0);r=0==(r|0)?0:a[r>>2];if(0==(f|0)){c=2368}else{if(0==m[f]<<24>>24){c=2368}else{var e=f}}2368==c&&(e=V(i,Oi|0));Or(b,r,e,k,n,Kn(b,i,d),i);uc(d);h=g}function kD(x){var r,i,g=x>>2,c=h;h+=16;var d=c+8,l=a[g+48],k=a[g+49],n=a[g+40],e=a[g+41],p=x+356|0;0==(a[p>>2]|0)?d=e:(fo(c,l,k),l=a[c>>2],k=a[c+4>>2],fo(d,n,e),n=a[d>>2],d=a[d+4>>2]);var l=l|0,e=x+316|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),j=x+236|0;r=l*e-(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]);j=x+284|0;f[0]=r;a[j>>2]=b[0];a[j+4>>2]=b[1];j=k|0;k=x+324|0;k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);i=x+244|0;i=j*k-(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);var m=x+292|0;f[0]=i;a[m>>2]=b[0];a[m+4>>2]=b[1];m=x+300|0;f[0]=r+e;a[m>>2]=b[0];a[m+4>>2]=b[1];r=x+308|0;f[0]=i+k;a[r>>2]=b[0];a[r+4>>2]=b[1];r=x+464|0;0==(a[a[g+3]+28>>2]|0)?(i=r>>2,r=(x+448|0)>>2,a[i]=a[r],a[i+1]=a[r+1],a[i+2]=a[r+2],a[i+3]=a[r+3]):(r|=0,i=a[r>>2],m=a[g+112],a[r>>2]=(i|0)<(m|0)?i:m,r=x+468|0,i=a[r>>2],m=a[g+113],a[r>>2]=(i|0)<(m|0)?i:m,r=x+472|0,i=a[r>>2],m=a[g+114],a[r>>2]=(i|0)>(m|0)?i:m,r=x+476|0,i=a[r>>2],m=a[g+115],a[r>>2]=(i|0)>(m|0)?i:m);g=a[g+37];r=x+332|0;r=(b[0]=a[r>>2],b[1]=a[r+4>>2],f[0]);0==(g&128|0)?(n=r+e*(l-.5*(n|0)),l=x+252|0,f[0]=n,a[l>>2]=b[0],a[l+4>>2]=b[1],l=x+340|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])+k*(j-.5*(d|0))-1,d=x+260|0,f[0]=l,a[d>>2]=b[0],a[d+4>>2]=b[1],j=n+(e+1),d=x+268|0,f[0]=j,a[d>>2]=b[0],a[d+4>>2]=b[1],e=l+(k+1),d=x+276|0,f[0]=e,a[d>>2]=b[0],a[d+4>>2]=b[1],d=n,n=l,k=j):(d=x+360|0,n=.5*(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),d=r-n,e=x+252|0,f[0]=d,a[e>>2]=b[0],a[e+4>>2]=b[1],e=x+340|0,l=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),e=x+368|0,j=.5*(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),e=l-j,k=x+260|0,f[0]=e,a[k>>2]=b[0],a[k+4>>2]=b[1],k=r+n,n=x+268|0,f[0]=k,a[n>>2]=b[0],a[n+4>>2]=b[1],l+=j,n=x+276|0,f[0]=l,a[n>>2]=b[0],a[n+4>>2]=b[1],n=e,e=l);0==(a[p>>2]|0)?(p=x+376|0,k=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),p=x+348|0,p=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),l=x+496|0,f[0]=k/p-d,a[l>>2]=b[0],a[l+4>>2]=b[1],0==(g&4096|a[Ic>>2]|0)?(g=x+384|0,p=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])/p-n):(g=x+384|0,p=-e-(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])/p),x=x+504|0,f[0]=p,a[x>>2]=b[0],a[x+4>>2]=b[1]):(n=x+376|0,p=x+384|0,j=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),p=x+348|0,l=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),p=x+496|0,x=x+504|0,f[0]=-e-j/l,a[x>>2]=b[0],a[x+4>>2]=b[1],0==(g&4096|a[Ic>>2]|0)?(x=n|0,x=(b[0]=a[x>>2],b[1]=a[x+4>>2],f[0])/l-d):(x=n|0,x=-k-(b[0]=a[x>>2],b[1]=a[x+4>>2],f[0])/l),p|=0,f[0]=x,a[p>>2]=b[0],a[p+4>>2]=b[1]);h=c}function Ur(b,r,i){var g=a[b>>2]+28|0;a[g>>2]=a[g>>2]+1|0;(g=0!=(i&4|0))||Ck(b,r,i);var c=0==(i&1|0);a:do{if(c){if(0!=(i&16|0)){vs(b);var d=r,f=ra(d),k=0==(f|0);b:do{if(!k){for(var n=f;;){var e=Ib(d,n),h=0==(e|0);c:do{if(!h){for(var j=e;;){if(Ji(b,j),j=yb(d,j),0==(j|0)){break c}}}}while(0);n=ba(d,n);if(0==(n|0)){break b}}}}while(0);ws(b);xs(b);f=ra(d);k=0==(f|0);b:do{if(!k){for(n=f;;){if(oh(b,n),n=ba(d,n),0==(n|0)){break b}}}}while(0);ys(b)}else{if(0==(i&8|0)){d=r;f=ra(d);if(0==(f|0)){break}for(;;){oh(b,f);k=Ib(d,f);n=0==(k|0);b:do{if(!n){for(e=k;;){if(oh(b,a[e+12>>2]),Ji(b,e),e=yb(d,e),0==(e|0)){break b}}}}while(0);f=ba(d,f);if(0==(f|0)){break a}}}xs(b);d=r;f=ra(d);k=0==(f|0);b:do{if(!k){for(n=f;;){e=n;for(var h=cc,j=r+212|0,m=r+208|0,t=e|0,u=1;;){if((u|0)>(a[m>>2]|0)){var w=1,h=2460;break}if(0==(Ed(a[a[j>>2]+(u<<2)>>2],t)|0)){u=u+1|0}else{w=0;h=2461;break}}h=2461==h||2460==h?w:cc;0!=h<<24>>24&&oh(b,e);n=ba(d,n);if(0==(n|0)){break b}}}}while(0);ys(b);vs(b);f=ra(d);k=0==(f|0);b:do{if(!k){for(n=f;;){e=Ib(d,n);h=0==(e|0);c:do{if(!h){for(j=e;;){m=j;for(var t=cc,u=r+212|0,A=r+208|0,B=m|0,C=1;;){if((C|0)>(a[A>>2]|0)){var P=1,t=2466;break}if(0==(Ed(a[a[u>>2]+(C<<2)>>2],B)|0)){C=C+1|0}else{P=0;t=2467;break}}t=2466==t||2467==t?P:cc;0!=t<<24>>24&&Ji(b,m);j=yb(d,j);if(0==(j|0)){break c}}}}while(0);n=ba(d,n);if(0==(n|0)){break b}}}}while(0);ws(b)}}else{xs(b);d=r;f=ra(d);k=0==(f|0);b:do{if(!k){for(n=f;;){if(oh(b,n),n=ba(d,n),0==(n|0)){break b}}}}while(0);ys(b);vs(b);f=ra(d);k=0==(f|0);b:do{if(!k){for(n=f;;){e=Ib(d,n);h=0==(e|0);c:do{if(!h){for(j=e;;){if(Ji(b,j),j=yb(d,j),0==(j|0)){break c}}}}while(0);n=ba(d,n);if(0==(n|0)){break b}}}}while(0);ws(b)}}while(0);g&&Ck(b,r,i)}function lD(x,r){var i,g,c,d=h;h+=4;a[d>>2]=1e3;var l=Cb(16e3),k=r|0;if(0<(a[k>>2]|0)){var n=x+252|0,e=a[r+8>>2];g=e>>2;for(var p=l,l=1,s=0,m=0;;){c=(e|0)>>2;i=a[c];if(0==(i|0)||1==(i|0)){if(0!=(ph(e+44|0,n)|0)){i=e+4|0;g=(e+20|0)>>2;i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])-(b[0]=a[g],b[1]=a[g+1],f[0]);var t=p|0;f[0]=i;a[t>>2]=b[0];a[t+4>>2]=b[1];t=e+12|0;i=(e+28|0)>>2;var t=(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0])-(b[0]=a[i],b[1]=a[i+1],f[0]),u=p+8|0;f[0]=t;a[u>>2]=b[0];a[u+4>>2]=b[1];g=(b[0]=a[g],b[1]=a[g+1],f[0]);t=p+16|0;f[0]=g;a[t>>2]=b[0];a[t+4>>2]=b[1];g=(b[0]=a[i],b[1]=a[i+1],f[0]);i=p+24|0;f[0]=g;a[i>>2]=b[0];a[i+4>>2]=b[1];Qk(x,p,0==(a[c]|0)&1)}c=m}else{if(11==(i|0)){nh(a[g+1]),Ke(x,Cc|0),c=Cc|0}else{if(6==(i|0)){0!=(ph(e+44|0,n)|0)&&(c=g=e+4|0,p=zs(p,d,a[g+4>>2],a[c>>2]),Gd(x,p,a[c>>2])),c=m}else{if(12==(i|0)){0!=(l|0)&&la(0,XE|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),c=m,l=0}else{if(2==(i|0)||3==(i|0)){0!=(ph(e+44|0,n)|0)&&(g=i=e+4|0,p=zs(p,d,a[i+4>>2],a[g>>2]),zc(x,p,a[g>>2],2==(a[c]|0)&1))}else{if(7==(i|0)){0!=(ph(e+44|0,n)|0)&&(c=e+4|0,c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),i=p|0,f[0]=c,a[i>>2]=b[0],a[i+4>>2]=b[1],i=e+12|0,i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),t=p+8|0,f[0]=i,a[t>>2]=b[0],a[t+4>>2]=b[1],As(x,c,i,a[g+19]))}else{if(9==(i|0)){Pa(x,a[g+1])}else{if(8==(i|0)){$b(x,a[g+1])}else{if((4==(i|0)||5==(i|0))&&0!=(ph(e+44|0,n)|0)){g=i=e+4|0,p=zs(p,d,a[i+4>>2],a[g>>2]),$f(x,p,a[g>>2],0,0,4==(a[c]|0)&1)}}}}}c=m}}}}i=p;s=s+1|0;if((s|0)<(a[k>>2]|0)){e=e+80|0,g=e>>2,p=i,m=c}else{break}}0!=(c|0)&&Ke(x,a[a[x>>2]+296>>2]);k=i}else{k=l}G(k);h=d}function Rk(b,r){var i;i=(b+12|0)>>2;var g=a[i];a[i]=g+4|0;a[g>>2]=r;a[i]>>>0>2]>>>0||(a[i]=a[b>>2])}function Sk(b){var r;r=(b+8|0)>>2;var i=a[r];if((i|0)==(a[b+12>>2]|0)){return 0}var g=i+4|0;a[r]=g;i=a[i>>2];if(g>>>0>2]>>>0){return i}a[r]=a[b>>2];return i}function Zb(b){var r,i=b+224|0,g=a[i>>2];if((g|0)!=(b|0)&0!=(g|0)){b=i,i=g}else{var c;return b}for(;;){g=a[i+224>>2];if(0==(g|0)){c=i;r=2510;break}a[b>>2]=g;b=g+224|0;i=a[b>>2];if(!((i|0)!=(g|0)&0!=(i|0))){c=g;r=2509;break}}if(2509==r||2510==r){return c}}function YE(b){a[b+220>>2]=1;a[b+224>>2]=0;m[b+165|0]=0}function zs(x,r,i,g){var c=a[r>>2];(c|0)<(g|0)&&(c<<=1,c=(c|0)>(g|0)?c:g,x=wb(x,c<<4),a[r>>2]=c);r=x;if(0<(g|0)){x=0}else{return r}for(;;){var c=i+24*x|0,c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),d=(x<<4)+r|0;f[0]=c;a[d>>2]=b[0];a[d+4>>2]=b[1];c=i+24*x+8|0;c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]);d=(x<<4)+r+8|0;f[0]=c;a[d>>2]=b[0];a[d+4>>2]=b[1];x=x+1|0;if((x|0)==(g|0)){break}}return r}function jD(b,r){var i=r+208|0;if(1<=(a[i>>2]|0)){for(var g=r+212|0,c=1;;){var d=a[a[g>>2]+(c<<2)>>2];jD(b,d);var d=d|0,f=V(d,kh|0);0!=(f|0)&&0!=m[f]<<24>>24&&Pa(b,f);f=V(d,Ak|0);0!=(f|0)&&0!=m[f]<<24>>24&&$b(b,f);d=V(d,Eg|0);0!=(d|0)&&0!=m[d]<<24>>24&&Pa(b,d);c=c+1|0;if((c|0)>(a[i>>2]|0)){break}}}}function ZE(b){var r,i=fa(16);r=i>>2;var b=2>(b|0)?2:b,g=fa(b<<2);a[r]=g;a[r+3]=g;a[r+2]=g;a[r+1]=(b<<2)+g|0;return i}function Zf(b,r,i){0!=(r|0)&&(b=mb(b,a[r+8>>2]),0!=(b|0)&&0!=m[b]<<24>>24&&(i=bh(b),i=0>(i|0)?0:i));return i}function Xb(b,r,i,g){0==(r|0)|0==(b|0)?g=i:(b=mb(b,a[r+8>>2]),0==(b|0)?g=i:0==m[b]<<24>>24?g=i:(i=wg(b,xc),g=i>2])}function Aa(a,b,i){a=jc(a,b,i);return 0!=(a|0)&&0!=m[a]<<24>>24?a:i}function jo(a){return re(a)}function Tk(b,r){var i,g;if((b|0)==(r|0)){return b}g=b+224|0;if(0==(a[g>>2]|0)){a[g>>2]=b;a[b+220>>2]=1;var c=b}else{c=Zb(b)}g=c>>2;i=r+224|0;if(0==(a[i>>2]|0)){a[i>>2]=r;a[r+220>>2]=1;var d=r}else{d=Zb(r)}i=d>>2;(a[g+4]|0)>(a[i+4]|0)?(a[g+56]=d,c=d+220|0,a[c>>2]=a[c>>2]+a[g+55]|0,g=d):(a[i+56]=c,g=c+220|0,a[g>>2]=a[g>>2]+a[i+55]|0,g=c);return g}function Ld(x,r,i,g,c,d){var l,k,n=h;h+=576;if(0<=(i|0)){for(var e=i+1|0,p=0;!(k=((p<<4)+n|0)>>2,l=((p<<4)+r|0)>>2,a[k]=a[l],a[k+1]=a[l+1],a[k+2]=a[l+2],a[k+3]=a[l+3],l=p+1|0,(l|0)==(e|0));){p=l}r=1>(i|0);a:do{if(!r){l=1-g;p=1;for(k=i;;){var j=0>(i-p|0);b:do{if(!j){for(var m=p-1|0,t=n+96*m|0,u=n+96*m+8|0,w=0,A=(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0]),u=(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0]);;){var t=w+1|0,B=(t<<4)+n+96*m|0,B=(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0]),C=(w<<4)+n+96*p|0;f[0]=l*A+B*g;a[C>>2]=b[0];a[C+4>>2]=b[1];A=(t<<4)+n+96*m+8|0;C=(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0]);w=(w<<4)+n+96*p+8|0;f[0]=l*u+C*g;a[w>>2]=b[0];a[w+4>>2]=b[1];if((t|0)==(k|0)){break b}else{w=t,A=B,u=C}}}}while(0);p=p+1|0;if((p|0)==(e|0)){break a}else{k=k-1|0}}}}while(0);g=0==(c|0);a:do{if(!g){for(k=0;;){if(l=((k<<4)+c|0)>>2,r=(n+96*k|0)>>2,a[l]=a[r],a[l+1]=a[r+1],a[l+2]=a[r+2],a[l+3]=a[r+3],r=k+1|0,(r|0)==(e|0)){break a}else{k=r}}}}while(0);if(0!=(d|0)){for(r=0;!(g=((r<<4)+d|0)>>2,c=((r<<4)+n+96*(i-r)|0)>>2,a[g]=a[c],a[g+1]=a[c+1],a[g+2]=a[c+2],a[g+3]=a[c+3],c=r+1|0,(c|0)==(e|0));){r=c}}}x>>=2;i=n+96*i|0;i>>=2;a[x]=a[i];a[x+1]=a[i+1];a[x+2]=a[i+2];a[x+3]=a[i+3];h=n}function $E(b){for(var r,i=0;;){var g=a[ko>>2];if(1024>(g-i|0)){g=g+1024|0;a[ko>>2]=g;var c=wb(a[Si>>2],g);a[Si>>2]=c;g=a[ko>>2]}else{c=a[Si>>2]}g=oi(c+i|0,g-i|0,b);if(0==(g|0)){break}g=Ba(g)+i|0;c=a[Si>>2];if(10==m[c+(g-1)|0]<<24>>24){var d=g,f=c;r=2612;break}else{i=g}}if(2612==r){return f=0<(d|0)?f:0}f=a[Si>>2];return f=(b=0<(i|0))?f:0}function Uk(b){var r=h,i;if(0==(b|0)){var g;h=r;return 0}if(0==m[b]<<24>>24){return h=r,0}var c=a[aF>>2];if(0==(c|0)){return h=r,b}var d=a[Bs>>2];if(0==(d|0)){if(m[Vk]){return h=r,0}la(0,bF|0,(j=h,h+=4,a[j>>2]=c,j));m[Vk]=1;h=r;return 0}if(!m[Cs]){var c=he(Hb(d),zf|0),d=0==(c|0),f=a[Wk>>2];a:do{if(d){var k=0,n=f}else{for(var e=0,p=c,s=f;;){var v=0==(s|0)?Cb((e<<2)+8|0):wb(s,(e<<2)+8|0);a[Wk>>2]=v;s=e+1|0;a[v+(e<<2)>>2]=p;e=a[lo>>2];p=Ba(p);a[lo>>2]=e>>>0>p>>>0?e:p;p=he(0,zf|0);v=a[Wk>>2];if(0==(p|0)){k=s;n=v;break a}else{e=s,s=v}}}}while(0);a[n+(k<<2)>>2]=0;m[Cs]=1}k=ik(b,47);k=0==(k|0)?b:k+1|0;n=ik(k,92);k=0==(n|0)?k:n+1|0;n=ik(k,58);k=0==(n|0)?k:n+1|0;m[Vk]|(k|0)==(b|0)||(n=a[Bs>>2],la(0,cF|0,(j=h,h+=8,a[j>>2]=b,a[j+4>>2]=n,j)),m[Vk]=1);b=mc(a[Ti>>2],a[lo>>2]+Ba(k)+2|0);a[Ti>>2]=b;for(b=a[Wk>>2];;){n=a[b>>2];if(0==(n|0)){g=0;i=2639;break}Ma(a[Ti>>2],dF|0,(j=h,h+=12,a[j>>2]=n,a[j+4>>2]=eF|0,a[j+8>>2]=k,j));if(0==(vQa(a[Ti>>2])|0)){break}else{b=b+4|0}}if(2639==i){return h=r,g}g=a[Ti>>2];h=r;return g}function Ds(b,r,i){var g=0==(b|0),c=0;a:for(;;){var d=a[r+(c<<2)>>2];if(0==(d|0)){break}do{if(!g&&m[b]<<24>>24==m[d]<<24>>24&&0==(ka(b,d)|0)){break a}}while(0);c=c+1|0}return a[i+(c<<2)>>2]}function re(a){return 0==(a|0)?0:0==m[a]<<24>>24?0:0==(Lb(a,Ze|0)|0)?0:0==(Lb(a,fF|0)|0)?0:0==(Lb(a,Es|0)|0)?1:0==(Lb(a,gF|0)|0)?1:10>((m[a]<<24>>24)-48|0)>>>0?bh(a)&255:0}function Lb(a,b){for(var i,g=a,c=b;;){var d=m[g];if(0==d<<24>>24){var f=0;break}if((vf(d&255)|0)!=(vf(m[c]&255)|0)){i=2659;break}g=g+1|0;c=c+1|0}2659==i&&(f=m[g]&255);return vf(f)-vf(m[c]&255)|0}function NE(x,r,i,g){var c=h;h+=80;var d,l=c+64,k=a[r+4>>2],n=0<(k|0),r=a[r>>2]>>2;a:do{if(n){for(var e=1e+38,p=-1,j=-1,m=0;;){var t=a[r+(12*m|0)],u=a[r+(12*m|0)+1],w=0<(u|0);b:do{if(w){for(var A=e,B=p,C=j,P=0;;){var T=(P<<4)+t|0,gb=(P<<4)+t+8|0,T=(b[0]=a[T>>2],b[1]=a[T+4>>2],f[0])-i,gb=(b[0]=a[gb>>2],b[1]=a[gb+4>>2],f[0])-g,T=T*T+gb*gb,A=(gb=-1==(B|0)|T=(k|0))<<31>>31)+O|0;k=(y<<4)+n|0;k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);O=c|0;f[0]=k;a[O>>2]=b[0];a[O+4>>2]=b[1];O=(y<<4)+n+8|0;O=(b[0]=a[O>>2],b[1]=a[O+4>>2],f[0]);M=c+8|0;f[0]=O;a[M>>2]=b[0];a[M+4>>2]=b[1];M=y+1|0;X=(M<<4)+n|0;X=(b[0]=a[X>>2],b[1]=a[X+4>>2],f[0]);D=c+16|0;f[0]=X;a[D>>2]=b[0];a[D+4>>2]=b[1];M=(M<<4)+n+8|0;M=(b[0]=a[M>>2],b[1]=a[M+4>>2],f[0]);X=c+24|0;f[0]=M;a[X>>2]=b[0];a[X+4>>2]=b[1];M=y+2|0;X=(M<<4)+n|0;X=(b[0]=a[X>>2],b[1]=a[X+4>>2],f[0]);D=c+32|0;f[0]=X;a[D>>2]=b[0];a[D+4>>2]=b[1];M=(M<<4)+n+8|0;M=(b[0]=a[M>>2],b[1]=a[M+4>>2],f[0]);X=c+40|0;f[0]=M;a[X>>2]=b[0];a[X+4>>2]=b[1];M=y+3|0;y=(M<<4)+n|0;y=(b[0]=a[y>>2],b[1]=a[y+4>>2],f[0]);X=c+48|0;f[0]=y;a[X>>2]=b[0];a[X+4>>2]=b[1];n=(M<<4)+n+8|0;M=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]);n=c+56|0;f[0]=M;a[n>>2]=b[0];a[n+4>>2]=b[1];n=c|0;X=k-i;D=O-g;r=y-i;e=M-g;k=l|0;O=l+8|0;y=1;M=0;X=X*X+D*D;for(D=r*r+e*e;;){r=.5*(M+y);Ld(l,n,3,r,0,0);var Da=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),ia=(b[0]=a[O>>2],b[1]=a[O+4>>2],f[0]);if(1>be(X-D)){d=2672;break}if(1e-5>be(y-M)){d=2673;break}e=X>2,f[0]=Da,a[i]=b[0],a[i+1]=b[1],x=(x+8|0)>>2,f[0]=ia,a[x]=b[0],a[x+1]=b[1],h=c):2672==d&&(i=(x|0)>>2,f[0]=Da,a[i]=b[0],a[i+1]=b[1],x=(x+8|0)>>2,f[0]=ia,a[x]=b[0],a[x+1]=b[1],h=c)}function hF(x){var r=x|0,i=Xb(r,a[rh>>2],.75,.01),g=x+48|0;f[0]=i;a[g>>2]=b[0];a[g+4>>2]=b[1];i=Xb(r,a[sh>>2],.5,.02);g=x+56|0;f[0]=i;a[g>>2]=b[0];a[g+4>>2]=b[1];var i=x+24|0,g=a,c=i>>2,d;a:{d=Aa(r,a[Fs>>2],Gs|0);var l;d=0==(Uk(V(x|0,Xk|0))|0)?d:101==m[d]<<24>>24&&0==(ka(d,Hs|0)|0)?d:th|0;var k=m[d];if(99==k<<24>>24){if(0!=(ka(d,th|0)|0)){var n=W;l=1910}}else{n=W,l=1910}b:do{if(1910==l){for(;;){l=0;var e=a[n>>2];if(0==(e|0)){break b}if(m[e]<<24>>24==k<<24>>24&&0==(ka(e,d)|0)){break}n=n+16|0}if(0!=(n|0)){d=n;break a}}}while(0);l=h;n=Is(d);0!=(n|0)?d=n:(n=a[mo>>2],k=n+1|0,a[mo>>2]=k,e=a[Yk>>2],k=0==(e|0)?Cb(k<<2):wb(e,k<<2),a[Yk>>2]=k,k=fa(16),e=k>>2,a[a[Yk>>2]+(n<<2)>>2]=k,a[e]=a[W>>2],a[e+1]=a[W+4>>2],a[e+2]=a[W+8>>2],a[e+3]=a[W+12>>2],n=Hb(d),a[e]=n,0==(a[iF>>2]|0)&&!(99==m[d]<<24>>24&&0==(ka(d,th|0)|0))?(d=a[W>>2],la(0,jF|0,(j=h,h+=8,a[j>>2]=d,a[j+4>>2]=n,j)),m[k+12|0]=0):m[k+12|0]=1,d=k);h=l}g[c]=d;l=mb(r,a[a[no>>2]+8>>2]);g=Xb(r,a[bo>>2],14,1);c=Aa(r,a[ao>>2],Li|0);d=Aa(r,a[Js>>2],Ac|0);a[x+120>>2]=ag(r,l,(0!=(bg(l)|0)?2:0)|(2==(ts(x)|0)?4:0),g,c,d);l=a[Ks>>2];0!=(l|0)&&(l=mb(r,a[l+8>>2]),0!=(l|0)&&0!=m[l]<<24>>24&&(a[x+124>>2]=ag(r,l,0!=(bg(l)|0)?2:0,g,c,d),g=a[x+20>>2]+149|0,m[g]|=16));m[x+160|0]=Zf(r,a[Ls>>2],0)&255;J[a[a[a[i>>2]+4>>2]>>2]](x)}function kF(x){var r,i,g,c=x>>2,d=h;h+=112;var l=d+16,k=d+32,n=d+72;g=(x+16|0)>>2;var e=a[a[g]+20>>2];i=(d+8|0)>>2;a[i]=0;r=(l+8|0)>>2;a[r]=0;var p=a[Ms>>2];if(0==(p|0)){var j=0,v=0}else{if(j=x|0,p=mb(j,a[p+8>>2]),0==(p|0)){v=j=0}else{if(0==m[p]<<24>>24){v=j=0}else{Ns(x,d);var t=0!=(bg(p)|0)?2:0,u=d|0,w=(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0]),v=a[i],u=a[d+12>>2];a[c+27]=ag(j,p,t,w,v,u);p=e+149|0;m[p]|=1;m[x+126|0]=re(jc(j,a[Os>>2],Ze|0));j=1}}}t=a[Ps>>2];0!=(t|0)&&(p=x|0,t=mb(p,a[t+8>>2]),0!=(t|0)&&0!=m[t]<<24>>24&&(0==(v|0)?(Ns(x,d),u=d|0,w=(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0]),i=a[i],u=a[d+12>>2]):i=v,a[c+30]=ag(p,t,0!=(bg(t)|0)?2:0,w,i,u),i=e+149|0,m[i]|=32));u=a[Zk>>2];i=x|0;if(0==(u|0)){u=0}else{if(u=mb(i,a[u+8>>2]),0==(u|0)){u=0}else{if(0==m[u]<<24>>24){u=0}else{lF(x,d,l);var w=0!=(bg(u)|0)?2:0,A=l|0,B=(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0]),p=a[r],A=a[l+12>>2];a[c+28]=ag(i,u,w,B,p,A);u=e+149|0;m[u]|=2;u=p}}}w=a[$k>>2];0!=(w|0)&&(w=mb(i,a[w+8>>2]),0!=(w|0)&&0!=m[w]<<24>>24&&(0==(u|0)?(lF(x,d,l),A=l|0,B=(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0]),r=a[r],l=a[l+12>>2]):(r=u,l=A),a[c+29]=ag(i,w,0!=(bg(w)|0)?2:0,B,r,l),e=e+149|0,m[e]|=4));e=V(i,vi|0);0!=(e|0)&&0!=m[e]<<24>>24&&(m[a[g]+161|0]=1);g=a[g];Qs(k,a[a[a[g+24>>2]+4>>2]+8>>2],g,e);k>>=2;g=(x+28|0)>>2;for(e=k+10;k>2])<<24>>24&&(m[x+58|0]=0);k=V(i,wi|0);0!=(k|0)&&0!=m[k]<<24>>24&&(m[a[c+3]+161|0]=1);c=a[c+3];Qs(n,a[a[a[c+24>>2]+4>>2]+8>>2],c,k);k=n>>2;g=(x+68|0)>>2;for(e=k+10;k>2])<<24>>24){return h=d,j}m[x+98|0]=0;h=d;return j}function Ns(x,r){var i=x|0,g=Xb(i,a[Ts>>2],14,1),c=r|0;f[0]=g;a[c>>2]=b[0];a[c+4>>2]=b[1];a[r+8>>2]=Aa(i,a[Us>>2],Li|0);a[r+12>>2]=Aa(i,a[Vs>>2],Ac|0)}function lF(x,r,i){var g=r+8|0;0==(a[g>>2]|0)&&Ns(x,r);var x=x|0,c=r|0,c=Xb(x,a[Ws>>2],(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),1),d=i|0;f[0]=c;a[d>>2]=b[0];a[d+4>>2]=b[1];a[i+8>>2]=Aa(x,a[Xs>>2],a[g>>2]);a[i+12>>2]=Aa(x,a[Ys>>2],a[r+12>>2])}function Qs(b,r,i,g){var c,d=h;h+=80;var f=d+40,k=Jc(g,58);if(0==(k|0)){J[r](f,i,g,0);var r=f>>2,k=a[r+9],f=a[r+8],n=a[r+7],e=a[r+6],p=a[r+5],j=a[r+4],v=a[r+3],t=a[r+2],i=a[r+1],r=a[r]}else{m[k]=0;f=k+1|0;J[r](d,i,g,f);c=d>>2;var r=a[c],i=a[c+1],t=a[c+2],v=a[c+3],j=a[c+4],p=a[c+5],e=a[c+6],n=a[c+7],u=a[c+8];c=a[c+9];m[k]=58;k=c&0|f;f=u&-1|0;n=n&-1|0;e=e&-1|0;p=p&-1|0;j=j&-1|0;v=v&-1|0;t=t&-1|0;i=i&-1|0;r=r&-1|0}b>>=2;a[b]=r&-1|0;a[b+1]=i&-1|0;a[b+2]=t&-1|0;a[b+3]=v&-1|0;a[b+4]=j&-1|0;a[b+5]=p&-1|0;a[b+6]=e&-1|0;a[b+7]=n&-1|0;a[b+8]=f&-1|0;a[b+9]=k&0|g;h=d}function mF(b,r){if(0==(r|0)){var i=0}else{i=mb(b|0,a[r+8>>2]),i=0==(i|0)?0:0==m[i]<<24>>24?0:0==re(i)<<24>>24&1}return i}function al(x,r){var i,g,c=h;h+=32;i=x+52|0;var d,l=a[x+152>>2]&1,k,n,e,p;g=h;k=i>>2;d=h;h+=32;a[d>>2]=a[k];a[d+4>>2]=a[k+1];a[d+8>>2]=a[k+2];a[d+12>>2]=a[k+3];a[d+16>>2]=a[k+4];a[d+20>>2]=a[k+5];a[d+24>>2]=a[k+6];a[d+28>>2]=a[k+7];k=r+56|0;n=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);k=r+64|0;k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);var l=0==l<<24>>24,j=r+24|0,j=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]),m=r+32|0,m=(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0]);p=.5*(l?j:m);e=n-p;n+=p;p=(d|0)>>2;if(e<(b[0]=a[p],b[1]=a[p+1],f[0])){f[0]=e,a[p]=b[0],a[p+1]=b[1]}e=(d+16|0)>>2;if(n>(b[0]=a[e],b[1]=a[e+1],f[0])){f[0]=n,a[e]=b[0],a[e+1]=b[1]}n=.5*(l?m:j);l=k-n;k+=n;n=(d+8|0)>>2;if(l<(b[0]=a[n],b[1]=a[n+1],f[0])){f[0]=l,a[n]=b[0],a[n+1]=b[1]}l=(d+24|0)>>2;if(k>(b[0]=a[l],b[1]=a[l+1],f[0])){f[0]=k,a[l]=b[0],a[l+1]=b[1]}k=c>>2;d>>=2;a[k]=a[d];a[k+1]=a[d+1];a[k+2]=a[d+2];a[k+3]=a[d+3];a[k+4]=a[d+4];a[k+5]=a[d+5];a[k+6]=a[d+6];a[k+7]=a[d+7];h=g;g=i>>2;i=c>>2;a[g]=a[i];a[g+1]=a[i+1];a[g+2]=a[i+2];a[g+3]=a[i+3];a[g+4]=a[i+4];a[g+5]=a[i+5];a[g+6]=a[i+6];a[g+7]=a[i+7];h=c}function nF(x,r,i,g,c){var d=x|0;f[0]=r-g;a[d>>2]=b[0];a[d+4>>2]=b[1];x=x+8|0;f[0]=i-c;a[x>>2]=b[0];a[x+4>>2]=b[1]}function Df(a,b,i){var g;if(0==(i|0)){var c;return 0}for(;;){var d=i-1|0;if(0==(i|0)){g=2817;break}if((vf(m[a]&255)|0)!=(vf(m[b]&255)|0)){g=2817;break}if(0==(d|0)){c=0;g=2819;break}if(0==m[a]<<24>>24){c=0;g=2822;break}if(0==m[b]<<24>>24){c=0;g=2820;break}a=a+1|0;b=b+1|0;i=d}if(2819==g||2822==g){return c}if(2817==g){return c=vf(m[a]&255)-vf(m[b]&255)|0}if(2820==g){return c}}function oF(b){var r=oo(b,pF|0),i=ra(b),g=0==(i|0);a:do{if(!g){for(var c=i;;){var d=Ib(b,c),f=0==(d|0);b:do{if(!f){for(var k=d;;){var n=r,e=a[k+16>>2],h=a[k+12>>2];if(!(0==m[e+134|0]<<24>>24&&0==m[h+134|0]<<24>>24)){var j=k,e=Zs(e,n),n=Zs(h,n),n=uh(a[e+20>>2],e,n);po(j|0,n|0)}k=yb(b,k);if(0==(k|0)){break b}}}}while(0);c=ba(b,c);if(0==(c|0)){break a}}}}while(0);i=ra(r);if(0!=(i|0)){for(;!(bl(b,i|0),i=ba(r,i),0==(i|0));){}}Sf(r)}function ac(a,b,i,g,c){b=$(b,i);return 0!=(b|0)?b:a=J[c](a,i,g)}function GQa(x,r){var i,g=h;i=x>>2;x=h;h+=32;a[x>>2]=a[i];a[x+4>>2]=a[i+1];a[x+8>>2]=a[i+2];a[x+12>>2]=a[i+3];a[x+16>>2]=a[i+4];a[x+20>>2]=a[i+5];a[x+24>>2]=a[i+6];a[x+28>>2]=a[i+7];i=r>>2;r=h;h+=32;a[r>>2]=a[i];a[r+4>>2]=a[i+1];a[r+8>>2]=a[i+2];a[r+12>>2]=a[i+3];a[r+16>>2]=a[i+4];a[r+20>>2]=a[i+5];a[r+24>>2]=a[i+6];a[r+28>>2]=a[i+7];i=x+16|0;var c=r|0;if((b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])<(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])){return h=g,0}i=r+16|0;c=x|0;if((b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])<(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])){return h=g,0}i=x+24|0;c=r+8|0;if((b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])<(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])){return h=g,0}i=r+24|0;c=x+8|0;i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])>=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]);h=g;return i&1}function $s(b){var r=h;h+=20;var i=r+8,g=a[b>>2],c=35==m[g]<<24>>24;a:do{if(c){var d=m[g+1|0],f=d&255;b:do{if(120==d<<24>>24||88==d<<24>>24){for(var k=0,e=2,z=f;;){if(8<=(e|0)){var p=k,j=e,v=z;break b}var z=m[g+e|0],t=z&255;do{if(6>(z-65&255)){var u=t-55|0}else{if(6>(z-97&255)){u=t-87|0}else{if(10<=(z-48&255)){p=k;j=e;v=t;break b}u=t-48|0}}}while(0);k=(k<<4)+u|0;e=e+1|0;z=u}}else{k=0;e=1;for(z=f;;){if(8<=(e|0)){p=k;j=e;v=z;break b}z=m[g+e|0];t=z&255;if(10<=(z-48&255)){p=k;j=e;v=t;break b}k=10*k-48+t|0;e=e+1|0;z=t}}}while(0);59!=(v|0)?(f=0,d=g):(f=p,d=j+(g+1)|0)}else{d=i|0;f=a[r>>2]=d;for(d=0;;){if(8<=(d|0)){f=0;d=g;break a}k=m[g+d|0];if(0==k<<24>>24){f=0;d=g;break a}else{if(59==k<<24>>24){break}}m[f]=k;f=f+1|0;d=d+1|0}m[f]=0;f=tn(r,F,252,8,110);0==(f|0)?(f=0,d=g):(f=a[f+4>>2],d=d+(g+1)|0)}}while(0);a[b>>2]=d;h=r;return f}function at(b){var r,i,g=h;h+=1044;i=g>>2;var c=g+4;fc(c,1024,g+20|0);a[i]=b+1|0;var d=m[b],f=0==d<<24>>24,b=(c+4|0)>>2;r=(c+8|0)>>2;a:do{if(!f){for(var k=d;;){38==k<<24>>24?(k=$s(g),k=0==(k|0)?38:k):k&=255;if(127>k>>>0){var e=a[b];e>>>0>>0||(na(c,1),e=a[b]);a[b]=e+1|0;m[e]=k&255}else{var z=a[b],e=a[r],p=z>>>0>=e>>>0;2047>k>>>0?(p&&(na(c,1),z=a[b],e=a[r]),p=z+1|0,a[b]=p,m[z]=(k>>>6|192)&255):(p&&(na(c,1),z=a[b],e=a[r]),p=z+1|0,a[b]=p,m[z]=(k>>>12|224)&255,p>>>0>>0?z=p:(na(c,1),z=a[b],e=a[r]),p=z+1|0,a[b]=p,m[z]=(k>>>6&63|128)&255);p>>>0>>0?e=p:(na(c,1),e=a[b]);a[b]=e+1|0;m[e]=(k&63|128)&255}k=a[i];a[i]=k+1|0;k=m[k];if(0==k<<24>>24){break a}}}}while(0);i=a[b];if(i>>>0>>0){return m[i]=0,r=a[(c|0)>>2],a[b]=r,b=Hb(r),uc(c),h=g,b}na(c,1);r=a[b];m[r]=0;r=a[(c|0)>>2];a[b]=r;b=Hb(r);uc(c);h=g;return b}function bt(b){var r,i=h;h+=1040;fc(i,1024,i+16|0);var g=m[b],c=0==g<<24>>24;r=(i+4|0)>>2;a:do{if(c){var d=a[r],f=i+8|0}else{for(var k=i+8|0,e=b,z=g;;){var p=e+1|0;if(127>(z&255)){e=a[r];if(e>>>0>2]>>>0){var j=e}else{na(i,1),j=a[r]}e=j+1|0;a[r]=e;m[j]=z;z=p}else{z=m[p]&63|z<<6,p=a[r],p>>>0>2]>>>0?j=p:(na(i,1),j=a[r]),p=j+1|0,a[r]=p,m[j]=z,z=e+2|0,e=p}p=m[z];if(0==p<<24>>24){d=e;f=k;break a}else{e=z,z=p}}}}while(0);if(d>>>0>2]>>>0){return m[d]=0,b=a[(i|0)>>2],a[r]=b,r=Hb(b),uc(i),h=i,r}na(i,1);b=a[r];m[b]=0;b=a[(i|0)>>2];a[r]=b;r=Hb(b);uc(i);h=i;return r}function qF(x,r){var i,g=h;h+=40;i=r>>2;r=h;h+=32;a[r>>2]=a[i];a[r+4>>2]=a[i+1];a[r+8>>2]=a[i+2];a[r+12>>2]=a[i+3];a[r+16>>2]=a[i+4];a[r+20>>2]=a[i+5];a[r+24>>2]=a[i+6];a[r+28>>2]=a[i+7];var c=g+8;i=g+24;var d=r+16|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),l=x+64|0;if(d<(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])){return h=g,0}var l=x+80|0,k=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),l=r|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);if(k>2],b[1]=a[k+4>>2],f[0]),e=x+72|0;if(k<(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])){return h=g,0}var e=x+88|0,z=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),e=r+8|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);if(z>2]=b[0];a[z+4>>2]=b[1];d=c+8|0;f[0]=.5*(k+e);a[d>>2]=b[0];a[d+4>>2]=b[1];d=x+32|0;d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]);l=x+40|0;l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);k=c|0;c=c+8|0;nF(i,d,l,(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]));c=i|0;c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]);i=i+8|0;i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);a[g>>2]=x;a[g+4>>2]=0;i=J[a[a[a[x+24>>2]+4>>2]+12>>2]](g,c,i);h=g;return i}function as(x,r){var i,g,c,d=h;h+=32;c=r>>2;r=h;h+=32;a[r>>2]=a[c];a[r+4>>2]=a[c+1];a[r+8>>2]=a[c+2];a[r+12>>2]=a[c+3];a[r+16>>2]=a[c+4];a[r+20>>2]=a[c+5];a[r+24>>2]=a[c+6];a[r+28>>2]=a[c+7];c=d+16;var l=x+24|0,k=.5*(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),l=x+32|0,e=.5*(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);g=(x+56|0)>>2;i=(x+64|0)>>2;nF(d,(b[0]=a[g],b[1]=a[g+1],f[0]),(b[0]=a[i],b[1]=a[i+1],f[0]),k,e);var l=d|0,z=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),l=d+8|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);g=(b[0]=a[g],b[1]=a[g+1],f[0]);i=(b[0]=a[i],b[1]=a[i+1],f[0])+e;e=c|0;f[0]=g+k;a[e>>2]=b[0];a[e+4>>2]=b[1];k=c+8|0;f[0]=i;a[k>>2]=b[0];a[k+4>>2]=b[1];k=c+8|0;k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);i=r+16|0;if((b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])>2],b[1]=a[c+4>>2],f[0])<(b[0]=a[z>>2],b[1]=a[z+4>>2],f[0])){return h=d,0}c=r+24|0;if((b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]);h=d;return c&1}function rF(x,r){var i,g=h;i=x>>2;x=h;h+=48;for(var c=x>>2,d=i+12;i>2;r=h;h+=32;a[r>>2]=a[i];a[r+4>>2]=a[i+1];a[r+8>>2]=a[i+2];a[r+12>>2]=a[i+3];a[r+16>>2]=a[i+4];a[r+20>>2]=a[i+5];a[r+24>>2]=a[i+6];a[r+28>>2]=a[i+7];var l,c=a[x+4>>2];0==(c|0)&&sa(qo|0,1634,sF|0,tF|0);i=a[x>>2];for(var d=i|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),k=i+8|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),e=1,z=k,p=d;(e|0)<(c|0);){var j=(e<<4)+i|0,j=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]),m=(e<<4)+i+8|0,m=(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0]);if(-1==(uF(j,m,p,z,r)|0)){e=e+1|0,z=m,p=j}else{var t=1;l=3034;break}}if(3034==l){return h=g,t}if(0!=(a[x+8>>2]|0)&&(l=x+16|0,t=x+24|0,0!=vF((b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0]),d,k,r)<<24>>24)||0!=(a[x+12>>2]|0)&&(l=c-1|0,t=x+32|0,t=(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0]),c=x+40|0,c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),d=(l<<4)+i|0,i=(l<<4)+i+8|0,0!=vF(t,c,(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),r)<<24>>24)){return h=g,1}h=g;return 0}function ct(b){var r,i,g,c=h;h+=1044;var d;g=c>>2;var f=c+4;a[g]=b;fc(f,1024,c+20|0);i=(f+4|0)>>2;r=(f+8|0)>>2;a:for(;;){var k=b+1|0;a[g]=k;var e=m[b];if(0==e<<24>>24){d=3071;break}do{if(192>(e&255)){if(38!=e<<24>>24){var z=e}else{if(z=$s(c),0==(z|0)){z=38}else{if(127>z>>>0){z&=255}else{var p=a[i],s=a[r],v=p>>>0>=s>>>0;2047>z>>>0?(v?(na(f,1),s=a[i]):s=p,a[i]=s+1|0,m[s]=(z>>>6|192)&255):(v&&(na(f,1),p=a[i],s=a[r]),v=p+1|0,a[i]=v,m[p]=(z>>>12|224)&255,v>>>0>>0?s=v:(na(f,1),s=a[i]),a[i]=s+1|0,m[s]=(z>>>6&63|128)&255);z=(z&63|128)&255}}}}else{if(224>(e&255)){if(-128!=(m[k]&-64)<<24>>24){d=3057;break a}z=a[i];z>>>0>>0||(na(f,1),z=a[i]);a[i]=z+1|0;m[z]=e;a[g]=b+2|0;z=m[k]}else{if(240<=(e&255)){d=3067;break a}if(-128!=(m[k]&-64)<<24>>24){d=3074;break a}z=b+2|0;if(-128!=(m[z]&-64)<<24>>24){d=3075;break a}s=a[i];p=a[r];s>>>0

>>0||(na(f,1),s=a[i],p=a[r]);v=s+1|0;a[i]=v;m[s]=e;a[g]=z;s=m[k];v>>>0

>>0?p=v:(na(f,1),p=a[i]);a[i]=p+1|0;m[p]=s;a[g]=b+3|0;z=m[z]}}}while(0);b=a[i];b>>>0>>0||(na(f,1),b=a[i]);a[i]=b+1|0;m[b]=z;b=a[g]}if(3067==d){la(1,wF|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),Pe()}else{if(3074==d){la(1,dt|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),Pe()}else{if(3075==d){la(1,dt|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),Pe()}else{if(3071==d){g=a[i];if(g>>>0>>0){return m[g]=0,r=a[(f|0)>>2],a[i]=r,i=Hb(r),uc(f),h=c,i}na(f,1);r=a[i];m[r]=0;r=a[(f|0)>>2];a[i]=r;i=Hb(r);uc(f);h=c;return i}3057==d&&(la(1,xF|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),Pe())}}}}function et(x,r){if(0==r<<24>>24){var i=x+48|0,i=36*(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),g=x+112|0;f[0]=i;a[g>>2]=b[0];a[g+4>>2]=b[1];g=x+104|0;f[0]=i;a[g>>2]=b[0];a[g+4>>2]=b[1];i=x+56|0}else{i=x+56|0,i=36*(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),g=x+112|0,f[0]=i,a[g>>2]=b[0],a[g+4>>2]=b[1],g=x+104|0,f[0]=i,a[g>>2]=b[0],a[g+4>>2]=b[1],i=x+48|0}i=72*(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);g=x+96|0;f[0]=i;a[g>>2]=b[0];a[g+4>>2]=b[1]}function yF(b,r){var i=h,g,c=0==(b|0);a:do{if(c){var d=r}else{d=m[b];if(0!=d<<24>>24){d=d<<24>>24;do{if(116==(d|0)||84==(d|0)){var f=0==(Lb(b+1|0,zF|0)|0)?8:0;g=3098}else{if(110==(d|0)||78==(d|0)){var k=b+1|0;if(0==(Lb(k,AF|0)|0)){d=0;break a}if(0==(Lb(k,ft|0)|0)){d=2;break a}}else{if(48==(d|0)){d=2;break a}else{if(99==(d|0)||67==(d|0)){f=0==(Lb(b+1|0,BF|0)|0)?10:0,g=3098}else{if(112==(d|0)||80==(d|0)){f=0==(Lb(b+1|0,CF|0)|0)?4:0,g=3098}else{if(115==(d|0)||83==(d|0)){f=0==(Lb(b+1|0,DF|0)|0)?8:0,g=3098}else{if(108==(d|0)||76==(d|0)){f=0==(Lb(b+1|0,EF|0)|0)?2:0,g=3098}else{if(49==(d|0)||50==(d|0)||51==(d|0)||52==(d|0)||53==(d|0)||54==(d|0)||55==(d|0)||56==(d|0)||57==(d|0)){d=8;break a}else{if(102==(d|0)||70==(d|0)){f=0==(Lb(b+1|0,FF|0)|0)?2:0,g=3098}else{if(111==(d|0)||79==(d|0)){f=0==(Lb(b+1|0,GF|0)|0)?6:0,g=3098}else{if(121==(d|0)||89==(d|0)){f=0==(Lb(b+1|0,HF|0)|0)?8:0,g=3098}}}}}}}}}}}}while(0);if(3098==g&&0!=(f|0)){d=f;break}la(0,IF|0,(j=h,h+=4,a[j>>2]=b,j))}d=r}}while(0);h=i;return d}function JF(a,b){var i=V(a|0,KF|0),i=0==(i|0)?b:0==m[i]<<24>>24?0:yF(i,b),g=a+164|0;D[g>>1]=(D[g>>1]&65535|i)&65535}function vF(x,r,i,g,c){var d,l=h;h+=128;d=c>>2;c=h;h+=32;a[c>>2]=a[d];a[c+4>>2]=a[d+1];a[c+8>>2]=a[d+2];a[c+12>>2]=a[d+3];a[c+16>>2]=a[d+4];a[c+20>>2]=a[d+5];a[c+24>>2]=a[d+6];a[c+28>>2]=a[d+7];var k=l+32,e=l+64;d=l+96;var z=c+16|0,z=(b[0]=a[z>>2],b[1]=a[z+4>>2],f[0]);hh(l,x,r,i,g,1);var p=l|0;if(z>=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0])){if(hh(k,x,r,i,g,1),k=k+16|0,z=c|0,(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])>=(b[0]=a[z>>2],b[1]=a[z+4>>2],f[0])){if(k=c+24|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),hh(e,x,r,i,g,1),e=e+8|0,k>=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])){if(hh(d,x,r,i,g,1),x=d+24|0,c=c+8|0,(b[0]=a[x>>2],b[1]=a[x+4>>2],f[0])>=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])){return h=l,1}}}}h=l;return 0}function Zs(b,r){var i=a[b+20>>2];if(0==m[b+134|0]<<24>>24){var g;return b}var c=b|0;gt(r,c);var d=Jc(a[b+12>>2],58);0==(d|0)&&sa(qo|0,1243,LF|0,MF|0);var d=d+1|0,f=Mi(i,d);if(0!=(f|0)){return f}i=cl(i,d);d=a[ro(c)+8>>2];f=a[d>>2];if(0==(f|0)){return i}for(c=i|0;;){var d=d+4|0,k=a[f+8>>2],f=a[f+4>>2];(mb(c,k)|0)!=(f|0)&&qc(c,k,f);f=a[d>>2];if(0==(f|0)){g=i;break}}return g}function ht(b,r){var i,g,c=a[r>>2];g=(r+24|0)>>2;a[g]=0;a[g+1]=0;a[g+2]=0;a[g+3]=0;if(0!=m[c]<<24>>24){var d=Cb(Ba(c)+1|0);m[d]=0;var f=r+12|0,k=d;a:for(;;){for(;;){var e=c+1|0,h=m[c];if(0==h<<24>>24){var p=k;break a}if(!(161>(h&255)|2!=(a[f>>2]|0)|-1==h<<24>>24)){m[k]=h;var h=m[e],j=k+2|0;m[k+1|0]=h;if(0==h<<24>>24){p=j;break a}else{k=j;c=c+2|0;continue}}if(10==h<<24>>24){i=3161;break}else{if(92==h<<24>>24){break}}m[k]=h;k=k+1|0;c=e}3161==i?(i=0,h=k+1|0,m[k]=0,so(b,r,d,110),k=h,c=e,d=h):(h=m[e],j=h<<24>>24,110==(j|0)||108==(j|0)||114==(j|0)?(h=k+1|0,m[k]=0,so(b,r,d,m[e]),d=k=h):(m[k]=h,k=k+1|0),c=0==m[e]<<24>>24?e:c+2|0)}(d|0)!=(p|0)&&(m[p]=0,so(b,r,d,110));i=(r+40|0)>>2;a[i]=a[g];a[i+1]=a[g+1];a[i+2]=a[g+2];a[i+3]=a[g+3]}}function so(x,r,i,g){var c,d=h;h+=16;var l;c=(r+76|0)>>1;var k=D[c]<<16>>16,e=r+72|0,z=a[e>>2],k=0==(z|0)?fa(76*k+152|0):NF(z,k+2|0,76,k+1|0);a[e>>2]=k;e=D[c]<<16>>16;z=k+76*e|0;a[z>>2]=i;m[k+76*e+72|0]=g;if(0==(i|0)){l=3175}else{if(0==m[i]<<24>>24){l=3175}else{var p=r+16|0,i=a[r+4>>2],g=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),s,p=h;h+=4;a[p>>2]=0;s=(z+20|0)>>2;a[s]=i;var v=z+24|0;f[0]=g;a[v>>2]=b[0];a[v+4>>2]=b[1];g=a;v=z+4>>2;var t=a[to>>2];0!=(t|0)&&0==(Lb(t,i)|0)?i=a[it>>2]:(a[to>>2]=i,i=tn(to,H,35,36,310),a[it>>2]=i);g[v]=i;i=0==m[ld]<<24>>24?0:0==(nD(a[s])|0)?0:p;x=a[a[x+172>>2]+132>>2];0==(x|0)?x=0:(x=a[x>>2],x=0==(x|0)?0:J[x](z,i));0==x<<24>>24&&OF(z,i);0!=(i|0)&&(x=a[p>>2],i=a[oa>>2],s=a[s],0==(x|0)?Va(i,PF|0,(j=h,h+=4,a[j>>2]=s,j)):Va(i,QF|0,(j=h,h+=8,a[j>>2]=s,a[j+4>>2]=x,j)));x=z+56|0;x=(b[0]=a[x>>2],b[1]=a[x+4>>2],f[0]);z=z+64|0;z=(b[0]=a[z>>2],b[1]=a[z+4>>2],f[0]);s=d|0;f[0]=x;a[s>>2]=b[0];a[s+4>>2]=b[1];x=d+8|0;f[0]=z;a[x>>2]=b[0];a[x+4>>2]=b[1];h=p;p=d|0;z=d+8|0;p=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]);s=(b[0]=a[z>>2],b[1]=a[z+4>>2],f[0])}}3175==l&&(l=r+16|0,l=1.2*(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])&-1|0,k=k+76*e+64|0,f[0]=l,a[k>>2]=b[0],a[k+4>>2]=b[1],p=0,s=l);D[c]=D[c]+1&65535;c=(r+24|0)>>2;l=(b[0]=a[c],b[1]=a[c+1],f[0]);f[0]=l>p?l:p;a[c]=b[0];a[c+1]=b[1];r=(r+32|0)>>2;c=(b[0]=a[r],b[1]=a[r+1],f[0])+s;f[0]=c;a[r]=b[0];a[r+1]=b[1];h=d}function ag(x,r,i,g,c,d){var l,k,e=h,z=fa(84);k=z>>2;var p=a[x>>2]<<28>>28;if(3==(p|0)){var s=l=0,v=x,p=a[x+32>>2]}else{1==(p|0)?(l=0,s=x,v=0,p=a[a[x+20>>2]+32>>2]):2==(p|0)?(l=x,v=s=0,p=a[a[a[x+12>>2]+20>>2]+32>>2]):p=v=s=l=0}a[k+1]=c;a[k+2]=d;c=z+16|0;f[0]=g;a[c>>2]=b[0];a[c+4>>2]=b[1];g=z+12|0;a[g>>2]=m[p+151|0]&255;if(0!=(i&4|0)){a[k]=Hb(r);if(0==(i&2|0)){return h=e,z}m[z+82|0]=1;h=e;return z}if(2==(i|0)){a[k]=Hb(r);m[z+82|0]=1;if(0==(RF(x,z)|0)){return h=e,z}x=a[x>>2]<<28>>28;3==(x|0)?la(3,SF|0,(j=h,h+=4,a[j>>2]=a[v+12>>2],j)):1==(x|0)?la(3,TF|0,(j=h,h+=4,a[j>>2]=a[s+12>>2],j)):2==(x|0)&&(p=0!=(a[p>>2]&16|0)?Bf|0:Af|0,x=a[a[l+12>>2]+12>>2],la(3,UF|0,(j=h,h+=12,a[j>>2]=a[a[l+16>>2]+12>>2],a[j+4>>2]=p,a[j+8>>2]=x,j)));h=e;return z}0!=(i|0)&&sa(VF|0,169,WF|0,XF|0);x=jt(r,x,0);l=z>>2;a[l]=x;x=1==(a[g>>2]|0)?at(x):ct(x);G(a[l]);a[l]=x;ht(p,z);h=e;return z}function jt(b,r,i){var g=r>>2,c=a[g]<<28>>28;if(3==(c|0)){var d=a[g+3],f=Ba(d),k=a[g+12];if(0==(k|0)){var e=0,h=0,p=Y|0,j=Y|0,v=d,t=yg|0,u=cg|0,w=Hg|0,A=Ig|0,B=dl|0,C=f,P=2,T=2,gb=2,y=2,M=2,D=0}else{var O=a[k>>2];if(0==(b|0)){h=e=0,p=Y|0,j=Y|0,v=d,t=yg|0,u=cg|0,w=Hg|0,A=Ig|0,B=O,C=f,M=y=gb=T=P=2}else{var F=Ba(O),h=e=0,p=Y|0,j=Y|0,v=d,t=yg|0,u=cg|0,w=Hg|0,A=Ig|0,B=O,C=f,y=gb=T=P=2,M=F}D=0}}else{if(2==(c|0)){var Da=a[g+4],ia=a[a[Da+20>>2]+32>>2],E=a[ia+12>>2],G=Ba(E),J=a[Da+12>>2],sc=Ba(J),H=a[g+16],I=0==(H|0)?0:Ba(H),K=a[a[g+3]+12>>2],L=a[g+26],ca=0==(L|0)?0:Ba(L),N=Ba(K),U=a[g+27];if(0==(U|0)){var zb=dl|0,ja=2}else{var aa=a[U>>2];0==(b|0)?(zb=aa,ja=2):(zb=aa,ja=Ba(aa))}e=1;h=ca;p=H;j=L;v=E;t=yg|0;u=0==(a[ia>>2]&16|0)?Af|0:Bf|0;w=K;A=J;B=zb;C=G;P=2;T=sc+(0==(I|0)?0:I+1|0)+N+(0==(ca|0)?0:ca+1|0)+2|0;gb=N;y=sc;M=ja;D=I}else{if(1==(c|0)){var da=a[a[g+5]+12>>2],ea=Ba(da),xa=a[g+3],Q=Ba(xa),Od=a[g+30];if(0==(Od|0)){h=e=0,p=Y|0,j=Y|0,v=da,t=xa,u=cg|0,w=Hg|0,A=Ig|0,B=dl|0,C=ea,P=Q,M=y=gb=T=2}else{var ha=a[Od>>2];if(0==(b|0)){h=e=0,p=Y|0,j=Y|0,v=da,t=xa,u=cg|0,w=Hg|0,A=Ig|0,B=ha,C=ea,P=Q,M=y=gb=T=2}else{var ga=Ba(ha),h=e=0,p=Y|0,j=Y|0,v=da,t=xa,u=cg|0,w=Hg|0,A=Ig|0,B=ha,C=ea,P=Q,y=gb=T=2,M=ga}}}else{h=e=0,p=Y|0,j=Y|0,v=YF|0,t=yg|0,u=cg|0,w=Hg|0,A=Ig|0,B=dl|0,M=y=gb=T=P=C=2}D=0}}var W=0==(i|0),Rb=0,S=b;a:for(;;){var tb=S+1|0,ya=m[S];if(0==ya<<24>>24){break}else{if(92!=ya<<24>>24){Rb=Rb+1|0;S=tb;continue}}var V=S+2|0,wa=m[tb]<<24>>24;do{if(71==(wa|0)){Rb=Rb+C|0;S=V;continue a}else{if(78==(wa|0)){Rb=Rb+P|0;S=V;continue a}else{if(69==(wa|0)){Rb=Rb+T|0;S=V;continue a}else{if(72==(wa|0)){Rb=Rb+gb|0;S=V;continue a}else{if(84==(wa|0)){Rb=Rb+y|0;S=V;continue a}else{if(76==(wa|0)){Rb=Rb+M|0;S=V;continue a}else{if(92==(wa|0)&&!W){Rb=Rb+1|0;S=V;continue a}}}}}}}}while(0);Rb=Rb+2|0;S=V}var Ab=Cb(Rb+1|0),Fa=0==(e|0),Ga=0==(D|0),R=0==(h|0),ta=b,Ka=Ab;a:for(;;){var za=ta+1|0,ma=m[ta];if(0==ma<<24>>24){break}else{if(92!=ma<<24>>24){m[Ka]=ma;ta=za;Ka=Ka+1|0;continue}}var pa=ta+2|0,la=m[za],Ha=la<<24>>24;do{if(76==(Ha|0)){var Ra=m[B];m[Ka]=Ra;if(0==Ra<<24>>24){ta=pa;continue a}else{var $=B,fa=Ka}for(;;){var La=$+1|0,Z=fa+1|0,Ya=m[La];m[Z]=Ya;if(0==Ya<<24>>24){ta=pa;Ka=Z;continue a}else{$=La,fa=Z}}}else{if(72==(Ha|0)){var ka=m[w];m[Ka]=ka;if(0==ka<<24>>24){ta=pa;continue a}else{var Za=w,sa=Ka}for(;;){var ab=Za+1|0,$a=sa+1|0,jb=m[ab];m[$a]=jb;if(0==jb<<24>>24){ta=pa;Ka=$a;continue a}else{Za=ab,sa=$a}}}else{if(84==(Ha|0)){var Ca=m[A];m[Ka]=Ca;if(0==Ca<<24>>24){ta=pa;continue a}else{var Ia=A,eb=Ka}for(;;){var ub=Ia+1|0,Sa=eb+1|0,ra=m[ub];m[Sa]=ra;if(0==ra<<24>>24){ta=pa;Ka=Sa;continue a}else{Ia=ub,eb=Sa}}}else{if(78==(Ha|0)){var ua=m[t];m[Ka]=ua;if(0==ua<<24>>24){ta=pa;continue a}else{var Oa=t,Wa=Ka}for(;;){var pb=Oa+1|0,ob=Wa+1|0,bb=m[pb];m[ob]=bb;if(0==bb<<24>>24){ta=pa;Ka=ob;continue a}else{Oa=pb,Wa=ob}}}else{if(71==(Ha|0)){var qb=m[v];m[Ka]=qb;if(0==qb<<24>>24){ta=pa;continue a}else{var ba=v,kb=Ka}for(;;){var qa=ba+1|0,vb=kb+1|0,xb=m[qa];m[vb]=xb;if(0==xb<<24>>24){ta=pa;Ka=vb;continue a}else{ba=qa,kb=vb}}}else{if(69==(Ha|0)){if(Fa){ta=pa;continue a}var hd=m[A];m[Ka]=hd;var nb=0==hd<<24>>24;b:do{if(nb){var rb=Ka}else{for(var lb=A,Ta=Ka;;){var cb=lb+1|0,fb=Ta+1|0,Ua=m[cb];m[fb]=Ua;if(0==Ua<<24>>24){rb=fb;break b}else{lb=cb,Ta=fb}}}}while(0);b:do{if(Ga){var sb=rb}else{m[rb]=58;for(var Na=rb,Fb=p;;){var Db=Na+1|0,Ob=m[Fb];m[Db]=Ob;if(0==Ob<<24>>24){sb=Db;break b}else{Na=Db,Fb=Fb+1|0}}}}while(0);var Eb=m[u];m[sb]=Eb;var na=0==Eb<<24>>24;b:do{if(na){var Bb=sb}else{for(var Ja=u,oa=sb;;){var Ea=Ja+1|0,va=oa+1|0,Ma=m[Ea];m[va]=Ma;if(0==Ma<<24>>24){Bb=va;break b}else{Ja=Ea,oa=va}}}}while(0);var Aa=m[w];m[Bb]=Aa;var Wf=0==Aa<<24>>24;b:do{if(Wf){var ic=Bb}else{for(var wb=w,Qa=Bb;;){var Va=wb+1|0,Pa=Qa+1|0,hb=m[Va];m[Pa]=hb;if(0==hb<<24>>24){ic=Pa;break b}else{wb=Va,Qa=Pa}}}}while(0);if(R){ta=pa;Ka=ic;continue a}m[ic]=58;for(var pc=ic,Wc=j;;){var Vb=pc+1|0,Xd=m[Wc];m[Vb]=Xd;if(0==Xd<<24>>24){ta=pa;Ka=Vb;continue a}else{pc=Vb,Wc=Wc+1|0}}}else{if(92==(Ha|0)&&!W){m[Ka]=92;ta=pa;Ka=Ka+1|0;continue a}}}}}}}}while(0);m[Ka]=92;m[Ka+1|0]=la;ta=pa;Ka=Ka+2|0}m[Ka]=0;return Ab}function ZF(b,r){var i;if(0!=(b|0)){var g=0<(r|0);a:do{if(g){var c=0,d=b;for(i=d>>2;;){if(0==(c|0)){var f=a[i];0!=(f|0)&&G(f)}f=a[i+2];if(0!=(f|0)&&(i=a[i+3],0!=(i|0))){J[i](f)}c=c+1|0;if((c|0)==(r|0)){break a}else{d=d+76|0,i=d>>2}}}}while(0);G(b)}}function vh(b){if(0!=(b|0)){G(a[b>>2]);var r=b+72|0;0==m[b+82|0]<<24>>24?ZF(a[r>>2],D[b+76>>1]<<16>>16):$F(a[r>>2],1);G(b)}}function Fg(x,r,i){var g,c,d;d=(a[x+16>>2]+12|0)>>2;var l=a[d];a[d]=r;if(0!=m[i+82|0]<<24>>24){aG(x,a[i+72>>2],i),a[d]=l}else{if(r=(i+76|0)>>1,1<=D[r]<<16>>16){bG(x,0);Pa(x,a[i+8>>2]);g=m[i+80|0]<<24>>24;if(116==(g|0)){g=i+64|0;c=i+48|0;var k=i+16|0,k=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])+.5*(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])-(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])}else{if(98==(g|0)){g=i+64|0;c=i+48|0;var k=i+32|0,e=i+16|0,k=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])-.5*(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])+(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])-(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])}else{g=i+64|0,c=i+32|0,k=i+16|0,k=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])+.5*(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])-(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])}}e=0>16;a:do{if(e){var h=i+72|0;c=(i+56|0)>>2;g=(i+40|0)>>2;for(var p=k,j=0,v=a[h>>2];;){var t=m[v+76*j+72|0]<<24>>24,t=114==(t|0)?(b[0]=a[c],b[1]=a[c+1],f[0])+.5*(b[0]=a[g],b[1]=a[g+1],f[0]):108==(t|0)?(b[0]=a[c],b[1]=a[c+1],f[0])-.5*(b[0]=a[g],b[1]=a[g+1],f[0]):(b[0]=a[c],b[1]=a[c+1],f[0]);As(x,t,p,v+76*j|0);v=a[h>>2];t=v+76*j+64|0;j=j+1|0;if((j|0)<(D[r]<<16>>16|0)){p-=(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0])}else{break a}}}}while(0);cG(x);a[d]=l}}}function ec(a,b){return jt(a,b,1)}function nc(b){var r,i=a[uo>>2];if(0==(i|0)){a[vo>>2]=64;var i=Cb(64),g=a[uo>>2]=i,c=0,d=0}else{g=i,d=c=0}for(;;){if(0==(b|0)){r=148;break}var f=m[b];if(0==f<<24>>24){r=147;break}var k=a[vo>>2];(d|0)>(k-8|0)&&(g=k<<1,a[vo>>2]=g,i=wb(i,g),a[uo>>2]=i,g=i+d|0,f=m[b]);if(60==f<<24>>24){var e=wo|0,h=4}else{38==f<<24>>24?0==(dG(b)|0)?(e=xo|0,h=5):r=142:62==f<<24>>24?(e=yo|0,h=4):45==f<<24>>24?(e=kt|0,h=5):32==f<<24>>24?0==(c|0)?r=142:32==m[c]<<24>>24?(e=lt|0,h=6):r=142:34==f<<24>>24?(e=zo|0,h=6):39!=f<<24>>24?r=142:(e=Ao|0,h=5)}142==r&&(r=0,e=b,h=1);d=h+d|0;k=h;c=e;for(f=g;!(k=k-1|0,m[f]=m[c],0==(k|0));){c=c+1|0,f=f+1|0}c=b;b=b+1|0;g=g+h|0}if(147==r||148==r){return m[g]=0,i}}function dG(a){var b=a+1|0,i=m[b];if(35!=i<<24>>24){for(a=b;;){b=a+1|0;if(!(26>(i-97&255)|26>(i-65&255))){var g=i;break}a=b;i=m[b]}return g=59==g<<24>>24&1}b=a+2|0;i=m[b];if(120==i<<24>>24||88==i<<24>>24){for(a=a+3|0;;){if(i=m[a],10>(i-48&255)|6>(i-97&255)|6>(i-65&255)){a=a+1|0}else{g=i;break}}}else{for(a=b;;){b=a+1|0;if(10<=(i-48&255)){g=i;break}a=b;i=m[b]}}return g=59==g<<24>>24&1}function uF(x,r,i,g,c){var d,l=h;d=c>>2;c=h;h+=32;a[c>>2]=a[d];a[c+4>>2]=a[d+1];a[c+8>>2]=a[d+2];a[c+12>>2]=a[d+3];a[c+16>>2]=a[d+4];a[c+20>>2]=a[d+5];a[c+24>>2]=a[d+6];a[c+28>>2]=a[d+7];var k;d=c|0;d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]);var e=d>x;if(e){var j=0}else{j=c+16|0,(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0])>2],b[1]=a[j+4>>2],f[0])>r?j=0:(j=c+24|0,j=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0])>=r))}if(d>i){k=170}else{var p=c+16|0;if((b[0]=a[p>>2],b[1]=a[p+4>>2],f[0])>2],b[1]=a[p+4>>2],f[0])>g){k=170}else{p=c+24|0;p=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0])>=g;if(j^p){return h=l,0}if(j&p){return h=l,1}}}}if(170==k&&j){return h=l,0}do{if(x==i){if(k=c+8|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),!(e|k<=r^k<=g^1)&&(k=c+16|0,(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])>=x)){return r=0,h=l,r}}else{if(r==g){if(!(d<=x^d<=i)){break}x=c+8|0;if((b[0]=a[x>>2],b[1]=a[x+4>>2],f[0])>r){break}c=c+24|0;if((b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])>2],b[1]=a[j+4>>2],f[0]);if(!(dv|t>2],b[1]=a[p+4>>2],f[0]))){return r=0,h=l,r}p=c+16|0;p=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]);t+=(p-d)*k;if(t>=j){var u=c+24|0;if(!(t>(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0])|pv)){return r=0,h=l,r}}m=(v=r=d&&!(t>p|jv)){return r=0,h=l,r}u=c+24|0;u=(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0]);k=t+(u-j)/k;if(k>=d&&!(k>p|uv)){return r=0,h=l,r}}}while(0);h=l;return-1}function Mn(x){var r;r=(x+16|0)>>2;var i=(b[0]=a[r],b[1]=a[r+1],f[0]),g=x+32|0;f[0]=i;a[g>>2]=b[0];a[g+4>>2]=b[1];g=x+48|0;f[0]=i;a[g>>2]=b[0];a[g+4>>2]=b[1];i=x+24|0;i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);g=x+40|0;f[0]=i;a[g>>2]=b[0];a[g+4>>2]=b[1];i=x+8|0;i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);g=x+56|0;f[0]=i;a[g>>2]=b[0];a[g+4>>2]=b[1];x|=0;x=(b[0]=a[x>>2],b[1]=a[x+4>>2],f[0]);f[0]=x;a[r]=b[0];a[r+1]=b[1]}function Bo(b){var r,i=a[Co>>2];if(0==(i|0)){a[Do>>2]=64;var i=Cb(64),g=a[Co>>2]=i,c=0}else{g=i,c=0}for(;;){if(0==(b|0)){r=217;break}var d=m[b];if(0==d<<24>>24){r=218;break}var f=a[Do>>2];(c|0)>(f-8|0)&&(g=f<<1,a[Do>>2]=g,i=wb(i,g),a[Co>>2]=i,g=i+c|0,d=m[b]);if(60==d<<24>>24){var k=wo|0,e=4}else{34==d<<24>>24?(k=zo|0,e=6):39==d<<24>>24?(k=Ao|0,e=5):38==d<<24>>24?0==(dG(b)|0)?(k=xo|0,e=5):r=212:62==d<<24>>24?(k=yo|0,e=4):r=212}212==r&&(r=0,k=b,e=1);for(var c=e+c|0,h=e,d=k,f=g;!(h=h-1|0,m[f]=m[d],0==(h|0));){d=d+1|0,f=f+1|0}b=b+1|0;g=g+e|0}if(217==r||218==r){return m[g]=0,i}}function fa(a){if(0==(a|0)){a=0}else{var b=Cb(a);li(b,a);a=b}return a}function OF(x,r){var i,g,c;c=(x+56|0)>>2;f[0]=0;a[c]=b[0];a[c+1]=b[1];g=(x+24|0)>>2;i=(b[0]=a[g],b[1]=a[g+1],f[0]);var d=x+64|0;f[0]=1.2*i;a[d>>2]=b[0];a[d+4>>2]=b[1];d=x+40|0;f[0]=0;a[d>>2]=b[0];a[d+4>>2]=b[1];d=x+48|0;f[0]=.1*i;a[d>>2]=b[0];a[d+4>>2]=b[1];i=(x+20|0)>>2;d=a[i];a[x+8>>2]=d;a[x+12>>2]=0;0==(Df(d,eG|0,4)|0)?(d=fG|0,i=gG|0):0==(Df(a[i],hG|0,5)|0)?(d=mt|0,i=nt|0):(d=(i=0==(Df(a[i],iG|0,9)|0))?mt|0:jG|0,i=i?nt|0:kG|0);0!=(r|0)&&(a[r>>2]=d);d=a[x>>2];if(0!=(d|0)){var l=m[d],k=0==l<<24>>24,e=(b[0]=a[c],b[1]=a[c+1],f[0]);a:do{if(k){var h=e}else{for(var p=d,j=l,v=e;;){if(p=p+1|0,j=((j&255)<<3)+i|0,v+=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]),f[0]=v,a[c]=b[0],a[c+1]=b[1],j=m[p],0==j<<24>>24){h=v;break a}}}}while(0);g=h*(b[0]=a[g],b[1]=a[g+1],f[0]);f[0]=g;a[c]=b[0];a[c+1]=b[1]}}function lG(x,r,i,g){for(var c;;){if(180==(g|0)){c=291;break}else{if(0==(g|0)){var d=r,l=i;break}else{if(90==(g|0)){c=290;break}else{if(270==(g|0)){c=297;break}}}}if(0>(g|0)){c=293;break}if(360>=(g|0)){c=296;break}g=(g|0)%360}if(291==c){d=r,l=-i}else{if(293==c){Ui(x,r,i,-g|0);return}if(290==c){d=i,l=-r}else{if(296==c){mG(x,r,i,g);return}297==c&&(d=i,l=r)}}r=x|0;f[0]=d;a[r>>2]=b[0];a[r+4>>2]=b[1];x=x+8|0;f[0]=l;a[x>>2]=b[0];a[x+4>>2]=b[1]}function Ui(x,r,i,g){for(var c;;){if(270==(g|0)){c=312;break}else{if(180==(g|0)){c=306;break}else{if(90==(g|0)){c=305;break}else{if(0==(g|0)){var d=r,l=i;break}}}}if(0>(g|0)){c=308;break}if(360>=(g|0)){c=311;break}g=(g|0)%360}if(312==c){d=i,l=r}else{if(306==c){d=r,l=-i}else{if(308==c){lG(x,r,i,-g|0);return}if(311==c){mG(x,r,i,360-g|0);return}305==c&&(d=-i,l=r)}}r=x|0;f[0]=d;a[r>>2]=b[0];a[r+4>>2]=b[1];x=x+8|0;f[0]=l;a[x>>2]=b[0];a[x+4>>2]=b[1]}function mG(x,r,i,g){if((a[ot>>2]|0)==(g|0)){var g=(b[0]=a[el>>2],b[1]=a[el+4>>2],f[0]),c=(b[0]=a[fl>>2],b[1]=a[fl+4>>2],f[0])}else{var d=(g|0)/6.283185307179586,c=Ce(d);f[0]=c;a[fl>>2]=b[0];a[fl+4>>2]=b[1];d=se(d);f[0]=d;a[el>>2]=b[0];a[el+4>>2]=b[1];a[ot>>2]=g;g=d}d=x|0;f[0]=g*r-c*i;a[d>>2]=b[0];a[d+4>>2]=b[1];x=x+8|0;f[0]=g*i+c*r;a[x>>2]=b[0];a[x+4>>2]=b[1]}function Cb(b){if(0==(b|0)){return 0}b=Gb(b);if(0==(b|0)){Lc(Eo|0,14,1,a[oa>>2]),S()}else{return b}}function NF(b,r,i,g){b=mc(b,i*r|0);0!=(b|0)|0==(r|0)||(Lc(Eo|0,14,1,a[oa>>2]),S());if(g>>>0>=r>>>0){return b}li(b+g*i|0,(r-g)*i|0);return b}function wb(b,r){var i=mc(b,r);if(0!=(i|0)|0==(r|0)){return i}Lc(Eo|0,14,1,a[oa>>2]);S()}function dD(a,b,i,g,c,d){i-=a;g-=b;a=(d-b)*i-(c-a)*g;a*=a;return 1e-10>a?0:a/(i*i+g*g)}function aG(x,r,i){var g,c,d=h;h+=60;var l;nG(x);c=d>>2;g=(i+56|0)>>2;a[c]=a[g];a[c+1]=a[g+1];a[c+2]=a[g+2];a[c+3]=a[g+3];a[d+20>>2]=a[i+8>>2];a[d+16>>2]=a[i+4>>2];i=i+16|0;i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);g=d+32|0;f[0]=i;a[g>>2]=b[0];a[g+4>>2]=b[1];g=x+16|0;c=V(a[a[g>>2]+8>>2]|0,pt|0);var k=d+48|0;a[k>>2]=c;i=d+52|0;a[i>>2]=a[a[g>>2]+152>>2];g=d+56|0;m[g]=0;0==(c|0)?l=551:0==m[c]<<24>>24&&(l=551);551==l&&(a[k>>2]=Ze|0);1==m[r+4|0]<<24>>24?(r=a[r>>2],Ke(x,a[a[x>>2]+296>>2]),l=a[r+24>>2],0==(l|0)?Pa(x,Ac|0):Pa(x,l),qt(x,r,d)):oG(x,a[r>>2],d);0!=m[g]<<24>>24&&G(a[i>>2]);r=a[x+16>>2]>>2;a[r+37]=0;a[r+42]=0;a[r+46]=0;a[r+38]=0;Ei(x);h=d}function nG(b){var r,i=wk(b);r=i>>2;var b=a[r]>>2,g=a[b+1];a[r+1]=g;a[r+3]=a[b+3];0==(g|0)?a[r+2]=a[b+2]:3==(g|0)?a[r+2]=a[b+2]:1==(g|0)?a[r+2]=a[b+2]:2==(g|0)&&(a[r+2]=a[b+2]);a[r+37]=a[b+37];a[r+42]=a[b+42];a[r+46]=a[b+46];r=i+200|0;a[r>>2]=a[r>>2]&-2|a[b+50]&1}function qt(x,r,i){var g,c,d,l,k,e=r>>2,j=h;h+=132;var p=j+32,s=j+68,v=r|0;c=j>>2;k=(r+40|0)>>2;a[c]=a[k];a[c+1]=a[k+1];a[c+2]=a[k+2];a[c+3]=a[k+3];a[c+4]=a[k+4];a[c+5]=a[k+5];a[c+6]=a[k+6];a[c+7]=a[k+7];k=i|0;l=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);k=i+8|0;var t=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);k=a[e+19];var u=0==(a[e]|0)?0!=(a[e+2]|0):1,w=r+100|0;g=a[w>>2];if(0!=(g|0)){d=i+16|0;var A=a[d>>2];if(0!=(A|0)){var B=a[g>>2];0==(B|0)?a[Fo>>2]=0:(a[Fo>>2]=A,a[d>>2]=B)}d=i+20|0;A=a[d>>2];0!=(A|0)&&(B=a[g+4>>2],0==(B|0)?a[Go>>2]=0:(a[Go>>2]=A,a[d>>2]=B));d=(i+32|0)>>2;A=(b[0]=a[d],b[1]=a[d+1],f[0]);0>A||(g=g+16|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),0>g?(f[0]=-1,a[wh>>2]=b[0],a[wh+4>>2]=b[1]):(f[0]=A,a[wh>>2]=b[0],a[wh+4>>2]=b[1],f[0]=g,a[d]=b[0],a[d+1]=b[1]))}g=(j|0)>>2;d=(b[0]=a[g],b[1]=a[g+1],f[0])+l;f[0]=d;a[g]=b[0];a[g+1]=b[1];g=j+16|0;d=(g|0)>>2;l=(b[0]=a[d],b[1]=a[d+1],f[0])+l;f[0]=l;a[d]=b[0];a[d+1]=b[1];l=(j+8|0)>>2;d=(b[0]=a[l],b[1]=a[l+1],f[0])+t;f[0]=d;a[l]=b[0];a[l+1]=b[1];l=(j+24|0)>>2;t=(b[0]=a[l],b[1]=a[l+1],f[0])+t;f[0]=t;a[l]=b[0];a[l+1]=b[1];t=u?0!=(a[x+148>>2]&4|0)?0:gl(x,i,v,j,p,1):0;l=m[r+104|0];0==(l&2)<<24>>24?(s=a[e+5],0!=(s|0)&&pG(x,s,j),s=m[r+29|0],0!=s<<24>>24&&qG(x,a[e+6],s&255,j)):(r=a[e+6],r=0==(r|0)?Ac|0:r,d=s>>2,a[d]=a[c],a[d+1]=a[c+1],a[d+2]=a[c+2],a[d+3]=a[c+3],d=s+32|0,c=d>>2,g>>=2,a[c]=a[g],a[c+1]=a[g+1],a[c+2]=a[g+2],a[c+3]=a[g+3],c=d|0,c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),g=s+16|0,f[0]=c,a[g>>2]=b[0],a[g+4>>2]=b[1],c=s+8|0,c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),g=s+24|0,f[0]=c,a[g>>2]=b[0],a[g+4>>2]=b[1],c=s|0,c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),g=s+48|0,f[0]=c,a[g>>2]=b[0],a[g+4>>2]=b[1],c=s+40|0,c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),g=s+56|0,f[0]=c,a[g>>2]=b[0],a[g+4>>2]=b[1],e=a[e+5],Ii(x,e,r,s|0,4,l&255,0!=(e|0)&1));e=a[k>>2];s=0==(e|0);a:do{if(!s){c=k;for(r=e;;){if(rG(x,r,i),c=c+4|0,r=a[c>>2],0==(r|0)){break a}}}}while(0);0!=(t|0)&&hl(x,p,1);u&&0!=(a[x+148>>2]&4|0)&&0!=(gl(x,i,v,j,p,0)|0)&&hl(x,p,0);0!=(a[w>>2]|0)&&(x=a[Fo>>2],0!=(x|0)&&(a[i+16>>2]=x),x=a[Go>>2],0!=(x|0)&&(a[i+20>>2]=x),x=(b[0]=a[wh>>2],b[1]=a[wh+4>>2],f[0]),0>x||(i=i+32|0,f[0]=x,a[i>>2]=b[0],a[i+4>>2]=b[1]));h=j}function oG(x,r,i){var g=D[r+4>>1];if(1<=g<<16>>16){var c=r+24|0,c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),d=r+8|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),l=i|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])+.5*(c+d),k=i+8|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),e=r+32|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),h=r+16|0,h=(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0]);sG(x,g<<16>>16,a[r>>2],l,k+.5*(e+h),.5*(c-d),i+16|0,h,e)}}function Ho(b){var r=b+12|0,i=a[r>>2]-1|0;a[r>>2]=i;0==(i|0)&&(r=a[b>>2],0!=(r|0)&&G(r),r=a[b+4>>2],0!=(r|0)&&G(r),G(b))}function Io(b){b>>=2;G(a[b]);G(a[b+1]);G(a[b+2]);G(a[b+4]);G(a[b+3]);G(a[b+5]);G(a[b+6])}function rt(b){var r;if(0!=(b|0)){var i=b|0,g=b+4|0,c=D[g>>1],d=0>16;a:do{if(d){for(var f=0,k=a[i>>2],e=c;;){var h=k+4|0;if(0>1]<<16>>16){e=a[k>>2];r=e>>2;for(var j=0;;){var m=a[r];0!=(m|0)&&G(m);m=a[r+4];0!=(m|0)&&Ho(m);m=a[r+2];if(0!=(m|0)&&(r=a[r+3],0!=(r|0))){J[r](m)}j=j+1|0;if((j|0)<(D[h>>1]<<16>>16|0)){e=e+76|0,r=e>>2}else{break}}h=D[g>>1]}else{h=e}f=f+1|0;if((f|0)<(h<<16>>16|0)){k=k+24|0,e=h}else{break a}}}}while(0);i=a[i>>2];0!=(i|0)&&G(i);G(b)}}function $F(b,r){var i=m[b+4|0];if(3==i<<24>>24){i=a[b>>2],G(a[i+32>>2]),G(i)}else{if(1==i<<24>>24){var i=a[b>>2],g=i+76|0,c=a[g>>2];if(-1==(a[i+92>>2]|0)){Kc(c)}else{G(a[i+84>>2]);G(a[i+88>>2]);var d=a[c>>2],f=0==(d|0);a:do{if(!f){for(var k=c,e=d;;){if($F(e+80|0,0),Io(e|0),G(e),k=k+4|0,e=a[k>>2],0==(e|0)){break a}}}}while(0);G(a[g>>2])}g=a[i+100>>2];0!=(g|0)&&Ho(g);Io(i|0);G(i)}else{rt(a[b>>2])}}0!=(r|0)&&G(b)}function tG(b,r){var i,g=a[b+4>>2];if(0!=(g|0)&&0==(Lb(g,r)|0)){var c;return b|0}for(g=a[b+76>>2];;){var d=a[g>>2];if(0==(d|0)){c=0;i=668;break}var f=a[d+4>>2],d=0!=(f|0)&&0==(Lb(f,r)|0)?d|0:1!=m[d+84|0]<<24>>24?0:tG(a[d+80>>2],r);if(0==(d|0)){g=g+4|0}else{c=d;i=667;break}}if(668==i||667==i){return c}}function RF(b,r){var i,g=h;h+=148;var c=g+4,d=g+20;i=a[b>>2]<<28>>28;if(1==(i|0)){var f=a[b+20>>2]}else{2==(i|0)?f=a[a[b+12>>2]+20>>2]:3==(i|0)&&(f=a[b+32>>2])}f=a[f+32>>2];i=(r|0)>>2;var k=h;h+=152;var e=k+128,z=k+144;a[z>>2]=0;a[z+4>>2]=0;a[st>>2]=z;a[uG>>2]=0;a[vG>>2]=0;z=Nc(wG,a[Jo>>2]);a[Ko>>2]=z;z=Nc(xG,a[Jo>>2]);a[Lo>>2]=z;fc(e,128,k|0);a[yG>>2]=e;z=h;0==(a[Mo>>2]|0)&&(la(0,zG|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),a[Mo>>2]=a[Mo>>2]+1|0);h=z;a[g>>2]=2;Kc(a[Ko>>2]);Kc(a[Lo>>2]);a[Ko>>2]=0;a[Lo>>2]=0;a[st>>2]=0;uc(e);h=k;fc(c,128,d|0);m[r+82|0]=0;d=Hb(AG(b,c));a[i]=d;d=1==(a[r+12>>2]|0)?at(d):ct(d);k=a[i];G(k);a[i]=d;ht(f,r);uc(c);c=a[g>>2];h=g;return c}function AG(b,r){var i;i=a[b>>2]<<28>>28;2==(i|0)?(db(r,a[a[b+16>>2]+12>>2]),i=b+12|0,db(r,a[a[i>>2]+12>>2]),0==(a[a[a[i>>2]+20>>2]>>2]&16|0)?db(r,Af|0):db(r,Bf|0)):3==(i|0)?db(r,a[b+12>>2]):1==(i|0)&&db(r,a[b+12>>2]);i=(r+4|0)>>2;var g=a[i];if(g>>>0>2]>>>0){return m[g]=0,g=a[(r|0)>>2],a[i]=g}na(r,1);g=a[i];m[g]=0;g=a[(r|0)>>2];return a[i]=g}function sG(x,r,i,g,c,d,l,k,e){var j,p,s=h;h+=76;p=l>>2;l=h;h+=24;a[l>>2]=a[p];a[l+4>>2]=a[p+1];a[l+8>>2]=a[p+2];a[l+12>>2]=a[p+3];a[l+16>>2]=a[p+4];a[l+20>>2]=a[p+5];var v;p=g-d;var t=g+d;bG(x,1);if(0<(r|0)){for(var d=(l+16|0)>>2,u=l|0,l=l+4|0,w=s|0,A=s+20|0,B=s+24|0,C=s+40|0,P=s+48|0,T=s+4|0,y=s+8|0,$e=s+56|0,M=s+64|0,X=s+72|0,O=.5*(e-k)+c,c=0;;){k=m[i+24*c+6|0]<<24>>24;114==(k|0)?(k=i+24*c+8|0,e=t-(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])):108==(k|0)?e=p:(k=i+24*c+8|0,e=g-.5*(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]));var k=(i+24*c+16|0)>>2,O=O-(b[0]=a[k],b[1]=a[k+1],f[0]),F=i+24*c+4|0,Da=0>1]<<16>>16;a:do{if(Da){var ia=e,E=0,J=a[(i>>2)+(6*c|0)];for(j=J>>2;;){var G=a[j+4];if(0==(G|0)){var sc=(b[0]=a[d],b[1]=a[d+1],f[0]),H=a[u>>2];v=844}else{var I=G+16|0,I=(b[0]=a[I>>2],b[1]=a[I+4>>2],f[0]),I=0>2],K=0==(K|0)?a[u>>2]:K,G=a[G+4>>2];if(0==(G|0)){sc=I,H=K,v=844}else{var L=G,ca=I,N=K}}844==v&&(v=0,L=a[l>>2],ca=sc,N=H);Pa(x,L);a[w>>2]=a[j];a[A>>2]=N;f[0]=ca;a[B>>2]=b[0];a[B+4>>2]=b[1];G=J+40|0;G=(b[0]=a[G>>2],b[1]=a[G+4>>2],f[0]);f[0]=G;a[C>>2]=b[0];a[C+4>>2]=b[1];f[0]=1;a[P>>2]=b[0];a[P+4>>2]=b[1];a[T>>2]=a[j+1];a[y>>2]=a[j+2];j=(J+32|0)>>2;G=(b[0]=a[j],b[1]=a[j+1],f[0]);f[0]=G;a[$e>>2]=b[0];a[$e+4>>2]=b[1];G=(b[0]=a[k],b[1]=a[k+1],f[0]);f[0]=G;a[M>>2]=b[0];a[M+4>>2]=b[1];m[X]=108;As(x,ia,O,s);E=E+1|0;if((E|0)<(D[F>>1]<<16>>16|0)){ia+=(b[0]=a[j],b[1]=a[j+1],f[0]),J=J+76|0,j=J>>2}else{break a}}}}while(0);c=c+1|0;if((c|0)==(r|0)){break}}}cG(x);h=s}function gl(b,r,i,g,c,d){var f,k=h;h+=176;f=g>>2;g=h;h+=32;a[g>>2]=a[f];a[g+4>>2]=a[f+1];a[g+8>>2]=a[f+2];a[g+12>>2]=a[f+3];a[g+16>>2]=a[f+4];a[g+20>>2]=a[f+5];a[g+24>>2]=a[f+6];a[g+28>>2]=a[f+7];var e,z=k+16,p=k+48,s=a[b+16>>2];f=(s+148|0)>>2;var v=c|0;a[v>>2]=a[f];var t=s+168|0;a[c+4>>2]=a[t>>2];var u=s+184|0;a[c+8>>2]=a[u>>2];var w=s+152|0;a[c+12>>2]=a[w>>2];var A=s+200|0,c=c+16|0;m[c]=a[A>>2]<<31>>31&255;var B=a[i+16>>2];if(0==(B|0)){e=852}else{if(0==m[B]<<24>>24){e=852}else{var C=0,P=B}}852==e&&(fc(k,128,p|0),C=r+52|0,e=a[C>>2],0==(e|0)&&(e=Hb(Kn(b,a[s+8>>2]|0,k)),a[C>>2]=e,m[r+56|0]=1),r=e,db(k,r),r=z|0,z=a[tt>>2],a[tt>>2]=z+1|0,Ma(r,BG|0,(j=h,h+=4,a[j>>2]=z,j)),db(k,r),r=(k+4|0)>>2,z=a[r],z>>>0>2]>>>0||(na(k,1),z=a[r]),m[z]=0,z=a[k>>2],a[r]=z,C=1,P=z);i=Or(b,0,a[i>>2],a[i+12>>2],a[i+8>>2],P,a[s+8>>2]|0);C&&uc(k);if(0==(i|0)){return h=k,i}0!=(d|0)&&!(0==(a[v>>2]|0)&&0==m[c]<<24>>24)&&Se(b);if(0==(a[f]|0)&&0==(a[A>>2]&1|0)){return h=k,i}Ln(b,g);ad(b,a[f],a[t>>2],a[u>>2],a[w>>2]);h=k;return i}function pG(b,r,i){var g,c=h;g=i>>2;i=h;h+=32;a[i>>2]=a[g];a[i+4>>2]=a[g+1];a[i+8>>2]=a[g+2];a[i+12>>2]=a[g+3];a[i+16>>2]=a[g+4];a[i+20>>2]=a[g+5];a[i+24>>2]=a[g+6];a[i+28>>2]=a[g+7];$b(b,r);Pa(b,r);mh(b,i,1);h=c}function qG(x,r,i,g){var c,d=h;c=g>>2;g=h;h+=32;a[g>>2]=a[c];a[g+4>>2]=a[c+1];a[g+8>>2]=a[c+2];a[g+12>>2]=a[c+3];a[g+16>>2]=a[c+4];a[g+20>>2]=a[c+5];a[g+24>>2]=a[c+6];a[g+28>>2]=a[c+7];r=0==(r|0)?Ac|0:r;$b(x,r);Pa(x,r);if(1==(i|0)){mh(x,g,0)}else{r=g+24|0;r=(b[0]=a[r>>2],b[1]=a[r+4>>2],f[0]);c=g+8|0;c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]);var l=r-c,k=g+16|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),g=g|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),e=k-g,j=i-1|0;No(x,g,c,j,l);i=1-i|0;No(x,g,r,e,i);No(x,k,r,i,-l);No(x,k,c,-e,j)}h=d}function rG(x,r,i){var g,c,d,l,k=h;h+=68;var e=k+36,j=r|0;l=e>>2;d=(r+40|0)>>2;a[l]=a[d];a[l+1]=a[d+1];a[l+2]=a[d+2];a[l+3]=a[d+3];a[l+4]=a[d+4];a[l+5]=a[d+5];a[l+6]=a[d+6];a[l+7]=a[d+7];d=i|0;g=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]);d=i+8|0;l=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]);d=0==(a[r>>2]|0)?0!=(a[r+8>>2]|0):1;c=(e|0)>>2;var p=(b[0]=a[c],b[1]=a[c+1],f[0])+g;f[0]=p;a[c]=b[0];a[c+1]=b[1];c=(e+16|0)>>2;g=(b[0]=a[c],b[1]=a[c+1],f[0])+g;f[0]=g;a[c]=b[0];a[c+1]=b[1];g=(e+8|0)>>2;c=(b[0]=a[g],b[1]=a[g+1],f[0])+l;f[0]=c;a[g]=b[0];a[g+1]=b[1];g=(e+24|0)>>2;l=(b[0]=a[g],b[1]=a[g+1],f[0])+l;f[0]=l;a[g]=b[0];a[g+1]=b[1];l=d?0!=(a[x+148>>2]&4|0)?0:gl(x,i,j,e,k,1):0;g=a[r+20>>2];0!=(g|0)&&pG(x,g,e);g=m[r+29|0];0!=g<<24>>24&&qG(x,a[r+24>>2],g&255,e);g=(r+80|0)>>2;r=m[r+84|0];if(3==r<<24>>24){g=a[g];r=h;h+=64;c=g|0;p=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]);c=g+8|0;c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]);var s=g+16|0,s=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]),v=g+24|0,v=(b[0]=a[v>>2],b[1]=a[v+4>>2],f[0]),t=i|0,t=(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0]),p=p+t,u=i+8|0,u=(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0]);c+=u;s+=t;v+=u;t=r|0;f[0]=s;a[t>>2]=b[0];a[t+4>>2]=b[1];t=r+8|0;f[0]=v;a[t>>2]=b[0];a[t+4>>2]=b[1];t=r+32|0;f[0]=p;a[t>>2]=b[0];a[t+4>>2]=b[1];t=r+40|0;f[0]=c;a[t>>2]=b[0];a[t+4>>2]=b[1];t=r+16|0;f[0]=p;a[t>>2]=b[0];a[t+4>>2]=b[1];p=r+24|0;f[0]=v;a[p>>2]=b[0];a[p+4>>2]=b[1];p=r+48|0;f[0]=s;a[p>>2]=b[0];a[p+4>>2]=b[1];p=r+56|0;f[0]=c;a[p>>2]=b[0];a[p+4>>2]=b[1];c=a[g+36>>2];0==(c|0)&&(c=a[i+48>>2]);g=a[(g+32|0)>>2];ut(x,g,r|0,4,1,c);h=r}else{1==r<<24>>24?qt(x,a[g],i):oG(x,a[g],i)}0!=(l|0)&&hl(x,k,1);d&&0!=(a[x+148>>2]&4|0)&&0!=(gl(x,i,j,e,k,0)|0)&&hl(x,k,0);h=k}function hl(b,r,i){var g,c,d,f=a[b+16>>2];d=(f+148|0)>>2;0==(a[d]|0)?0==(a[f+200>>2]&1|0)?c=0:g=911:g=911;911==g&&(Se(b),c=a[d]);g=r|0;(c|0)!=(a[g>>2]|0)&&(G(c),a[d]=a[g>>2]);c=(f+168|0)>>2;g=a[c];var k=r+4|0;(g|0)!=(a[k>>2]|0)&&(G(g),a[c]=a[k>>2]);g=(f+184|0)>>2;var k=a[g],e=r+8|0;(k|0)!=(a[e>>2]|0)&&(G(k),a[g]=a[e>>2]);var k=f+152|0,e=a[k>>2],h=r+12|0;(e|0)!=(a[h>>2]|0)&&(G(e),e=a[h>>2],a[k>>2]=e);k=e;r=m[r+16|0]&1;f=f+200|0;a[f>>2]=a[f>>2]&-2|r;0!=(i|0)&&(i=a[d],0==(i|0)&0==(r|0)||ad(b,i,a[c],a[g],k))}function No(x,r,i,g,c){var d=h;h+=32;var l=d|0;f[0]=r;a[l>>2]=b[0];a[l+4>>2]=b[1];l=d+8|0;f[0]=i;a[l>>2]=b[0];a[l+4>>2]=b[1];l=d+16|0;f[0]=r+g;a[l>>2]=b[0];a[l+4>>2]=b[1];r=d+24|0;f[0]=i+c;a[r>>2]=b[0];a[r+4>>2]=b[1];mh(x,d,1);h=d}function Oo(x,r,i){var g=h;h+=8;var c=g+4;if(0!=m[ld]<<24>>24){var d=a[x+216>>2];if(0==(d|0)){var l=0,k=0}else{for(var e=0,z=0;;){var e=e+1|0,p=a[d+184>>2],s=0==(a[p>>2]|0);a:do{if(s){var v=z}else{for(var t=0,u=z;;){if(u=u+1|0,t=t+1|0,0==(a[p+(t<<2)>>2]|0)){v=u;break a}}}}while(0);d=a[d+168>>2];if(0==(d|0)){l=e;k=v;break}else{z=v}}}a[g>>2]=l;a[c>>2]=k;l=a[g>>2];c=a[c>>2];Va(a[oa>>2],CG|0,(j=h,h+=20,a[j>>2]=Po|0,a[j+4>>2]=l,a[j+8>>2]=c,a[j+12>>2]=i,a[j+16>>2]=r,j));mk(ih)}0==(DG(x)|0)&&EG();if(1>(i|0)){return vt(),h=g,0}x=V(x|0,FG|0);x=0==(x|0)?30:bh(x);a[Qo>>2]=x;if(0==(GG()|0)){x=0}else{return vt(),h=g,1}for(;;){c=HG();if(0==(c|0)){var w=x;break}IG(c,JG(a[c+12>>2],a[c+16>>2]));x=x+1|0;0!=m[ld]<<24>>24&&0==(x%100|0)&&(c=(x|0)%1e3,l=a[oa>>2],100==(c|0)?(Lc(Po|0,17,1,l),Va(a[oa>>2],wt|0,(j=h,h+=4,a[j>>2]=x,j))):(Va(l,wt|0,(j=h,h+=4,a[j>>2]=x,j)),0==(c|0)&&kk(10,a[oa>>2])));if((x|0)>=(i|0)){w=x;break}}1==(r|0)?KG():2==(r|0)?LG():MG();if(0==m[ld]<<24>>24){return h=g,0}99<(w|0)&&kk(10,a[oa>>2]);r=a[oa>>2];i=a[Le>>2];x=a[Ro>>2];c=Jn();Va(r,NG|0,(j=h,h+=24,a[j>>2]=Po|0,a[j+4>>2]=i,a[j+8>>2]=x,a[j+12>>2]=w,f[0]=c,a[j+16>>2]=b[0],a[j+20>>2]=b[1],j));h=g;return 0}function DG(b){var r;a[Ef>>2]=b;a[il>>2]=0;a[Ro>>2]=0;a[Le>>2]=0;var b=b+216|0,i=a[b>>2];if(0==(i|0)){r=0}else{for(var g=0,c=0;;){m[i+163|0]=0;r=g+1|0;a[Le>>2]=r;var g=a[i+184>>2],d=0==(a[g>>2]|0);a:do{if(d){var f=c}else{for(var e=0,h=c;;){if(h=h+1|0,a[Ro>>2]=h,e=e+1|0,0==(a[g+(e<<2)>>2]|0)){f=h;break a}}}}while(0);i=a[i+168>>2];if(0==(i|0)){break}else{g=r,c=f}}r<<=2}f=a[Vi>>2];r=0==(f|0)?Cb(r):wb(f,r);a[Vi>>2]=r;a[dg>>2]=0;r=a[eg>>2];r=0==(r|0)?Cb(a[Le>>2]<<2):wb(r,a[Le>>2]<<2);a[eg>>2]=r;a[jf>>2]=0;b=a[b>>2];if(0==(b|0)){var j;return 1}i=1;f=b;for(b=f>>2;;){r=(f+292|0)>>2;a[r]=0;f=f+176|0;g=a[a[f>>2]>>2];if(0==(g|0)){r=i,f=4}else{c=i;for(d=i=1;;){a[r]=d;a[g+168>>2]=0;a[g+172>>2]=-1;var p=0==(c|0)?0:(a[a[g+12>>2]+236>>2]-a[a[g+16>>2]+236>>2]|0)<(D[g+178>>1]&65535|0)?0:c,g=a[a[f>>2]+(i<<2)>>2];if(0==(g|0)){break}c=p;i=i+1|0;d=a[r]+1|0}r=p;f=(i<<2)+4|0}a[b+66]=fa(f);a[b+67]=0;f=a[b+46];for(i=0;;){var s=i+1|0;if(0==(a[f+(i<<2)>>2]|0)){break}else{i=s}}a[b+68]=fa(s<<2);a[b+69]=0;b=a[b+42];if(0==(b|0)){j=r;break}else{i=r,f=b,b=f>>2}}return j}function EG(){var b,r=h,i=ZE(a[Le>>2]),g=a[a[Ef>>2]+216>>2],c=0==(g|0);a:do{if(!c){for(var d=g;;){if(0==(a[d+292>>2]|0)&&Rk(i,d),d=a[d+168>>2],0==(d|0)){break a}}}}while(0);g=Sk(i);c=0==(g|0);a:do{if(c){b=0}else{for(var d=0,f=g;;){var e=f+236|0;a[e>>2]=0;var d=d+1|0,n=a[f+176>>2],z=a[n>>2],p=0==(z|0);b:do{if(!p){for(var m=0,v=z,t=0;;){if(v=(D[v+178>>1]&65535)+a[a[v+16>>2]+236>>2]|0,t=(t|0)>(v|0)?t:v,a[e>>2]=t,m=m+1|0,v=a[n+(m<<2)>>2],0==(v|0)){break b}}}}while(0);f=f+184|0;e=a[a[f>>2]>>2];n=0==(e|0);b:do{if(!n){z=0;for(p=e;;){if(p=p+12|0,m=a[p>>2]+292|0,t=a[m>>2]-1|0,a[m>>2]=t,1>(t|0)&&Rk(i,a[p>>2]),z=z+1|0,p=a[a[f>>2]+(z<<2)>>2],0==(p|0)){break b}}}}while(0);f=Sk(i);if(0==(f|0)){b=d;break a}}}}while(0);if((b|0)!=(a[Le>>2]|0)&&(la(1,OG|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),b=a[a[Ef>>2]+216>>2],0!=(b|0))){for(b>>=2;!(g=a[b+73],0!=(g|0)&&la(3,PG|0,(j=h,h+=8,a[j>>2]=a[b+3],a[j+4>>2]=g,j)),b=a[b+42],0==(b|0));){b>>=2}}G(a[i>>2]);G(i);h=r}function vt(){var b;b=a[a[Ef>>2]+216>>2];if(0!=(b|0)){var r=b;for(b=r>>2;;){var i=a[b+66];0!=(i|0)&&G(i);i=a[b+68];0!=(i|0)&&G(i);m[r+163|0]=0;b=a[b+42];if(0==(b|0)){break}else{r=b,b=r>>2}}}}function HG(){for(var b,r=a[il>>2],i=a[jf>>2],g=0,c=0,d=r;(d|0)<(i|0);){var f=a[a[eg>>2]+(d<<2)>>2],e=a[f+168>>2];if(0>(e|0)){if(e=0==(g|0)?f:(a[g+168>>2]|0)>(e|0)?f:g,f=c+1|0,(f|0)<(a[Qo>>2]|0)){g=e,c=f}else{var h=e;b=1036;break}}d=d+1|0;a[il>>2]=d}if(1036==b){return h}if(0<(r|0)){i=0}else{return g}for(;;){a[il>>2]=i;if((i|0)>=(r|0)){h=g;b=1033;break}d=a[a[eg>>2]+(i<<2)>>2];f=a[d+168>>2];if(0>(f|0)&&(g=0==(g|0)?d:(a[g+168>>2]|0)>(f|0)?d:g,c=c+1|0,(c|0)>=(a[Qo>>2]|0))){h=g;b=1035;break}i=i+1|0}if(1033==b||1035==b){return h}}function MG(){a[So>>2]=2147483647;a[xh>>2]=-2147483647;var b=a[a[Ef>>2]+216>>2],r=0==(b|0);a:do{if(r){var i=2147483647,g=-2147483647}else{for(var c=b,d=2147483647,f=-2147483647;;){if(0==m[c+162|0]<<24>>24){var e=a[c+236>>2],h=(d|0)<(e|0)?d:e;a[So>>2]=h;f=(f|0)>(e|0)?f:e;a[xh>>2]=f;e=h}else{e=d}h=f;c=a[c+168>>2];if(0==(c|0)){break}else{d=e,f=h}}if(0==(e|0)){return}for(c=b;;){if(f=c+236|0,a[f>>2]=a[f>>2]-e|0,c=a[c+168>>2],0==(c|0)){i=e;g=h;break a}}}}while(0);a[xh>>2]=g-i|0;a[So>>2]=0}function QG(b,r,i,g){var c,r=r+288|0,d=-i|0,f=0==(g|0)&1;for(c=b>>2;;){var e=a[r>>2];if((a[c+71]|0)<=(e|0)&&(e|0)<=(a[c+72]|0)){break}e=a[c+70];c=a[e+16>>2];var h=e+168|0;a[h>>2]=(0==(((b|0)==(c|0)?g:f)|0)?d:i)+a[h>>2]|0;b=a[e+12>>2];b=(a[c+288>>2]|0)>(a[b+288>>2]|0)?c:b;c=b>>2}return b}function RG(b,r){var i,g;g=(b+172|0)>>2;a[r+172>>2]=a[g];a[a[eg>>2]+(a[g]<<2)>>2]=r;a[g]=-1;var c=a[b+16>>2],d=c+276|0;g=a[d>>2]-1|0;a[d>>2]=g;for(var c=c+272|0,d=a[c>>2],f=0;;){var e=(f<<2)+d|0;if((f|0)>(g|0)){var h=e;break}if((a[e>>2]|0)==(b|0)){h=e;break}else{f=f+1|0}}a[h>>2]=a[d+(g<<2)>>2];a[a[c>>2]+(g<<2)>>2]=0;g=a[b+12>>2];c=g+268|0;h=a[c>>2]-1|0;a[c>>2]=h;g=g+264|0;c=a[g>>2];for(d=0;;){f=(d<<2)+c|0;if((d|0)>(h|0)){i=f;break}if((a[f>>2]|0)==(b|0)){i=f;break}else{d=d+1|0}}a[i>>2]=a[c+(h<<2)>>2];a[a[g>>2]+(h<<2)>>2]=0;g=a[r+16>>2];i=(g+276|0)>>2;h=a[i];a[i]=h+1|0;g=g+272|0;a[a[g>>2]+(h<<2)>>2]=r;a[a[g>>2]+(a[i]<<2)>>2]=0;g=a[r+12>>2];i=(g+268|0)>>2;h=a[i];a[i]=h+1|0;g=g+264|0;a[a[g>>2]+(h<<2)>>2]=r;a[a[g>>2]+(a[i]<<2)>>2]=0}function GG(){var b;if(2>(a[Le>>2]|0)){var r;return 0}a:for(;;){if((SG()|0)>=(a[Le>>2]|0)){b=1075;break}var i=a[a[Ef>>2]+216>>2];if(0==(i|0)){r=1;b=1078;break}else{var g=0}for(;;){var c=a[i+184>>2],d=a[c>>2],f=0==(d|0);b:do{if(f){var e=g}else{for(var h=g,j=0,p=d;;){if(0>(a[p+172>>2]|0)){var m=a[p+12>>2],v=a[p+16>>2],h=0==(TG(m,v)|0)?h:0!=(h|0)&&(a[m+236>>2]-a[v+236>>2]-(D[p+178>>1]&65535)|0)>=(a[a[h+12>>2]+236>>2]-a[a[h+16>>2]+236>>2]-(D[h+178>>1]&65535)|0)?h:p}j=j+1|0;p=a[c+(j<<2)>>2];if(0==(p|0)){e=h;break b}}}}while(0);i=a[i+168>>2];if(0==(i|0)){break}else{g=e}}if(0==(e|0)){r=1;b=1080;break}g=a[e+12>>2];i=a[e+16>>2];c=a[g+236>>2]-a[i+236>>2]|0;d=D[e+178>>1]&65535;f=c-d|0;if((c|0)!=(d|0)&&(g=(TG(g,i)|0)==(g|0)?-f|0:f,i=a[dg>>2],0<(i|0))){c=a[Vi>>2];for(d=0;;){if(f=a[c+(d<<2)>>2]+236|0,a[f>>2]=a[f>>2]+g|0,d=d+1|0,(d|0)>=(i|0)){continue a}}}}if(1080==b){return r}if(1075==b){return b=a[Ef>>2]+216|0,jl(a[b>>2],0,1),xt(a[b>>2],0),0}if(1078==b){return r}}function JG(b,r){var i=(a[r+288>>2]|0)<(a[b+288>>2]|0),g=i?r:b;a[yh>>2]=0;a[Ff>>2]=2147483647;a[To>>2]=a[g+284>>2];a[Uo>>2]=a[g+288>>2];i?Vo(g):Wo(g);return i=a[yh>>2]}function KG(){var b,r;MG();var i=fa((a[xh>>2]<<2)+4|0);r=i>>2;b=a[xh>>2];var g=0>(b|0);a:do{if(!g){for(var c=0;;){if(a[(c<<2>>2)+r]=0,c=c+1|0,(c|0)>(b|0)){break a}}}}while(0);b=a[Ef>>2]+216|0;g=a[b>>2];if(0!=(g|0)){for(;!(0==m[g+162|0]<<24>>24&&(c=(a[g+236>>2]<<2)+i|0,a[c>>2]=a[c>>2]+1|0),g=a[g+168>>2],0==(g|0));){}b=a[b>>2];if(0!=(b|0)){g=b;for(b=g>>2;;){if(0==m[g+162|0]<<24>>24){var c=a[xh>>2],d=a[b+44],f=a[d>>2],e=0==(f|0);a:do{if(e){var h=0,j=0}else{for(var p=0,s=0,v=0,t=f;;){if(p=p+ib[t+164>>2]&-1,t=(D[t+178>>1]&65535)+a[a[t+16>>2]+236>>2]|0,s=(s|0)>(t|0)?s:t,v=v+1|0,t=a[d+(v<<2)>>2],0==(t|0)){h=p;j=s;break a}}}}while(0);d=a[b+46];f=a[d>>2];e=0==(f|0);a:do{if(e){var u=0,w=c}else{p=0;s=c;v=0;for(t=f;;){if(p=p+ib[t+164>>2]&-1,t=a[a[t+12>>2]+236>>2]-(D[t+178>>1]&65535)|0,s=(s|0)<(t|0)?s:t,v=v+1|0,t=a[d+(v<<2)>>2],0==(t|0)){u=p;w=s;break a}}}}while(0);c=0>(j|0)?0:j;if((h|0)==(u|0)){d=c+1|0;f=(d|0)>(w|0);a:do{if(f){var A=c}else{e=c;for(s=d;;){if(e=(a[(s<<2>>2)+r]|0)<(a[(e<<2>>2)+r]|0)?s:e,s=s+1|0,(s|0)>(w|0)){A=e;break a}}}}while(0);c=g+236|0;d=(a[c>>2]<<2)+i|0;a[d>>2]=a[d>>2]-1|0;d=(A<<2)+i|0;a[d>>2]=a[d>>2]+1|0;a[c>>2]=A}c=a[b+66];0!=(c|0)&&G(c);c=a[b+68];0!=(c|0)&&G(c);m[g+163|0]=0}b=a[b+42];if(0==(b|0)){break}else{g=b,b=g>>2}}}}G(i)}function LG(){var b=a[jf>>2];if(0<(b|0)){for(var r=a[eg>>2],i=0;;){var g=a[r+(i<<2)>>2];if(0==(a[g+168>>2]|0)){var c=g+12|0,d=g+16|0,g=JG(a[c>>2],a[d>>2]);0!=(g|0)&&(g=a[a[g+12>>2]+236>>2]-a[a[g+16>>2]+236>>2]-(D[g+178>>1]&65535)|0,2>(g|0)||(d=a[d>>2],c=a[c>>2],(a[d+288>>2]|0)<(a[c+288>>2]|0)?fg(d,(g|0)/2&-1):fg(c,(g|0)/-2&-1)))}i=i+1|0;if((i|0)>=(b|0)){break}}}vt()}function fg(b,r){var i=b+236|0;a[i>>2]=a[i>>2]-r|0;var i=b+272|0,g=a[i>>2],c=a[g>>2],d=0==(c|0);a:do{if(!d){for(var f=b+280|0,e=1,h=c,j=g;;){(h|0)!=(a[f>>2]|0)&&(fg(a[h+12>>2],r),j=a[i>>2]);h=a[j+(e<<2)>>2];if(0==(h|0)){break a}e=e+1|0}}}while(0);i=b+264|0;f=a[i>>2];d=a[f>>2];if(0!=(d|0)){g=b+280|0;for(c=1;;){(d|0)!=(a[g>>2]|0)&&(fg(a[d+16>>2],r),f=a[i>>2]);d=a[f+(c<<2)>>2];if(0==(d|0)){break}c=c+1|0}}}function IG(b,r){var i,g,c,d;d=(r+12|0)>>2;c=(r+16|0)>>2;var f=a[a[d]+236>>2]-a[a[c]+236>>2]-(D[r+178>>1]&65535)|0;if(0<(f|0)){var e=a[b+16>>2];g=e>>2;if(1==(a[g+69]+a[g+67]|0)){fg(e,f)}else{var h=a[b+12>>2];i=h>>2;1==(a[i+69]+a[i+67]|0)?fg(h,-f|0):(a[g+72]|0)<(a[i+72]|0)?fg(e,f):fg(h,-f|0)}}i=b+168|0;g=a[i>>2];f=QG(a[c],a[d],g,1);(QG(a[d],a[c],g,0)|0)==(f|0)?(a[r+168>>2]=-g|0,a[i>>2]=0,RG(b,r),jl(f,a[f+280>>2],a[f+284>>2])):S()}function TG(a,b){var i,g=0==m[a+163|0]<<24>>24;if(0==m[b+163|0]<<24>>24){if(g){i=1154}else{var c=a}}else{g?c=b:i=1154}1154==i&&(c=0);return c}function yt(b,r,i){var b=b>>2,g,c=a[b+4],d=(c|0)==(r|0),c=a[(d?a[b+3]:c)+288>>2];if((a[r+284>>2]|0)>(c|0)){g=1160}else{if((c|0)>(a[r+288>>2]|0)){g=1160}else{var f=0,e=(-1<(a[b+43]|0)?a[b+42]|0:0)-ib[b+41]}}1160==g&&(f=1,e=ib[b+41]);g=e&-1;r=0<(i|0)?(a[b+3]|0)==(r|0)?1:-1:d?1:-1;f=0==(f|0)?r:-r|0;return f=0>(f|0)?-g|0:g}function jl(b,r,i){a[b+280>>2]=r;a[b+284>>2]=i;var g=b+272|0,c=a[g>>2],d=a[c>>2],f=0==(d|0);a:do{if(f){var e=i}else{for(var h=0,j=i,p=d,m=c;;){if((p|0)!=(r|0)&&(j=jl(a[p+12>>2],p,j),m=a[g>>2]),h=h+1|0,p=a[m+(h<<2)>>2],0==(p|0)){e=j;break a}}}}while(0);i=b+264|0;c=a[i>>2];d=a[c>>2];if(0==(d|0)){var v=e;a[(b+288|0)>>2]=v;return v+1|0}for(g=0;;){if((d|0)!=(r|0)&&(e=jl(a[d+16>>2],d,e),c=a[i>>2]),g=g+1|0,d=a[c+(g<<2)>>2],0==(d|0)){v=e;break}}a[(b+288|0)>>2]=v;return v+1|0}function Wo(b){var r=b+184|0,i=a[r>>2],g=a[i>>2],c=0==(g|0);a:do{if(!c){for(var d=b+288|0,f=0,e=g,h=i;;){if(0>(a[e+172>>2]|0)){var j=a[e+12>>2],p=a[j+288>>2];(a[To>>2]|0)>(p|0)|(p|0)>(a[Uo>>2]|0)&&(j=a[j+236>>2]-a[a[e+16>>2]+236>>2]-(D[e+178>>1]&65535)|0,(j|0)<(a[Ff>>2]|0)|0==(a[yh>>2]|0)&&(a[yh>>2]=e,a[Ff>>2]=j))}else{e=a[e+12>>2],(a[e+288>>2]|0)<(a[d>>2]|0)&&(Wo(e),h=a[r>>2])}f=f+1|0;e=a[h+(f<<2)>>2];if(0==(e|0)){break a}}}}while(0);r=b+264|0;d=a[r>>2];g=a[d>>2];c=a[Ff>>2];if(0!=(g|0)&0<(c|0)){b=b+288|0;for(i=1;;){g=a[g+16>>2];(a[g+288>>2]|0)<(a[b>>2]|0)&&(Wo(g),d=a[r>>2],c=a[Ff>>2]);g=a[d+(i<<2)>>2];if(!(0!=(g|0)&0<(c|0))){break}i=i+1|0}}}function Vo(b){var c=b+176|0,i=a[c>>2],g=a[i>>2],d=0==(g|0);a:do{if(!d){for(var f=b+288|0,e=0,k=g,h=i;;){if(0>(a[k+172>>2]|0)){var j=a[k+16>>2],p=a[j+288>>2];(a[To>>2]|0)>(p|0)|(p|0)>(a[Uo>>2]|0)&&(j=a[a[k+12>>2]+236>>2]-a[j+236>>2]-(D[k+178>>1]&65535)|0,(j|0)<(a[Ff>>2]|0)|0==(a[yh>>2]|0)&&(a[yh>>2]=k,a[Ff>>2]=j))}else{k=a[k+16>>2],(a[k+288>>2]|0)<(a[f>>2]|0)&&(Vo(k),h=a[c>>2])}e=e+1|0;k=a[h+(e<<2)>>2];if(0==(k|0)){break a}}}}while(0);c=b+272|0;f=a[c>>2];g=a[f>>2];d=a[Ff>>2];if(0!=(g|0)&0<(d|0)){b=b+288|0;for(i=1;;){g=a[g+12>>2];(a[g+288>>2]|0)<(a[b>>2]|0)&&(Vo(g),f=a[c>>2],d=a[Ff>>2]);g=a[f+(i<<2)>>2];if(!(0!=(g|0)&0<(d|0))){break}i=i+1|0}}}function SG(){var b,c=a[Ef>>2]+216|0,i=a[c>>2],g=0==(i|0);a:do{if(!g){var d=i;for(b=d>>2;;){if(m[d+163|0]=0,a[a[b+68]>>2]=0,a[a[b+66]>>2]=0,a[b+69]=0,a[b+67]=0,b=a[b+42],0==(b|0)){break a}else{d=b,b=d>>2}}}}while(0);i=a[jf>>2];g=0<(i|0);a:do{if(g){b=a[eg>>2];for(d=0;;){if(a[a[b+(d<<2)>>2]+172>>2]=-1,d=d+1|0,(d|0)>=(i|0)){break a}}}}while(0);a[jf>>2]=0;a[dg>>2]=0;c=a[c>>2];if(0==(c|0)){return 0}for(;!(Xo(c),c=a[c+168>>2],!(0!=(c|0)&0==(a[jf>>2]|0)));){}return c=a[dg>>2]}function xt(b,c){var i=b+272|0,g=a[i>>2],d=a[g>>2],f=0==(d|0);a:do{if(!f){for(var e=0,k=d,h=g;;){if((k|0)!=(c|0)&&(xt(a[k+12>>2],k),h=a[i>>2]),e=e+1|0,k=a[h+(e<<2)>>2],0==(k|0)){break a}}}}while(0);i=b+264|0;g=a[i>>2];d=a[g>>2];f=0==(d|0);a:do{if(!f){e=0;k=d;for(h=g;;){if((k|0)!=(c|0)&&(xt(a[k+16>>2],k),h=a[i>>2]),e=e+1|0,k=a[h+(e<<2)>>2],0==(k|0)){break a}}}}while(0);if(0!=(c|0)){g=a[c+16>>2];(a[g+280>>2]|0)==(c|0)?i=1:(i=-1,g=a[c+12>>2]);d=a[g+184>>2];f=a[d>>2];e=0==(f|0);a:do{if(e){var j=0}else{for(var k=h=0,p=f;;){if(h=yt(p,g,i)+h|0,k=k+1|0,p=a[d+(k<<2)>>2],0==(p|0)){j=h;break a}}}}while(0);d=a[g+176>>2];e=a[d>>2];if(0==(e|0)){var m=j}else{for(f=0;;){if(j=yt(e,g,i)+j|0,f=f+1|0,e=a[d+(f<<2)>>2],0==(e|0)){m=j;break}}}j=c+168|0;a[j>>2]=m}}function Xo(b){var c,i=b+184|0,g=0;a:for(;;){var d=a[a[i>>2]+(g<<2)>>2];if(0==(d|0)){c=1253;break}var f=d+12|0,e=a[f>>2];do{if(0==m[e+163|0]<<24>>24&&(a[e+236>>2]-a[a[d+16>>2]+236>>2]|0)==(D[d+178>>1]&65535|0)){zt(d);if((a[jf>>2]|0)==(a[Le>>2]-1|0)){var k=1;c=1268;break a}if(0!=(Xo(a[f>>2])|0)){k=1;c=1269;break a}}}while(0);g=g+1|0}if(1268==c){return k}if(1253==c){b=b+176|0;i=0;a:for(;;){g=a[a[b>>2]+(i<<2)>>2];if(0==(g|0)){k=0;c=1266;break}d=g+16|0;f=a[d>>2];do{if(0==m[f+163|0]<<24>>24&&(a[a[g+12>>2]+236>>2]-a[f+236>>2]|0)==(D[g+178>>1]&65535|0)){zt(g);if((a[jf>>2]|0)==(a[Le>>2]-1|0)){k=1;c=1267;break a}if(0!=(Xo(a[d>>2])|0)){k=1;c=1270;break a}}}while(0);i=i+1|0}if(1270==c||1266==c||1267==c){return k}}else{if(1269==c){return k}}}function At(x,c){var i,g,d,e,l,k=h;h+=144;var n=k+16,j=k+32,p=k+48,m=k+64,v=k+80,t=k+96,u=k+112;i=k+128;l=(x+52|0)>>2;var w=(b[0]=a[l],b[1]=a[l+1],f[0]);e=(x+60|0)>>2;var A=(b[0]=a[e],b[1]=a[e+1],f[0]);d=(x+68|0)>>2;var B=(b[0]=a[d],b[1]=a[d+1],f[0]);g=(x+76|0)>>2;var C=(b[0]=a[g],b[1]=a[g+1],f[0]);2>(c-1|0)>>>0?(zh(k,w,C),u=k|0,t=k+8|0,ce(n,(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0]),(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0])),u=n|0,u=(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0]),t=n+8|0,t=(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0]),zh(j,B,A),B=j|0,j=j+8|0,ce(p,(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0]),(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0])),B=p|0,j=(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0]),B=p+8|0,B=(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0]),A=j,j=t,p=u):(zh(m,w,A),p=m|0,j=m+8|0,ce(v,(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0])),p=v|0,p=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),j=v+8|0,j=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]),zh(t,B,C),B=t|0,t=t+8|0,ce(u,(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0]),(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0])),B=u|0,t=(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0]),B=u+8|0,B=(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0]),A=t);f[0]=p;a[l]=b[0];a[l+1]=b[1];f[0]=j;a[e]=b[0];a[e+1]=b[1];f[0]=A;a[d]=b[0];a[d+1]=b[1];f[0]=B;a[g]=b[0];a[g+1]=b[1];e=a[x+48>>2];0!=(e|0)&&(g=e+56|0,d=g|0,e=e+64|0,ce(i,(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])),g>>=2,i>>=2,a[g]=a[i],a[g+1]=a[i+1],a[g+2]=a[i+2],a[g+3]=a[i+3]);i=x+208|0;if(1<=(a[i>>2]|0)){g=x+212|0;for(d=1;!(At(a[a[g>>2]+(d<<2)>>2],c),d=d+1|0,(d|0)>(a[i>>2]|0));){}}h=k}function zt(b){var c,i;c=b+172|0;-1<(a[c>>2]|0)&&S();i=a[jf>>2];a[c>>2]=i;a[jf>>2]=i+1|0;a[a[eg>>2]+(i<<2)>>2]=b;i=b+16|0;c=a[i>>2];if(0==m[c+163|0]<<24>>24){var g=a[dg>>2];a[dg>>2]=g+1|0;a[a[Vi>>2]+(g<<2)>>2]=c}c=b+12|0;g=a[c>>2];if(0==m[g+163|0]<<24>>24){var d=a[dg>>2];a[dg>>2]=d+1|0;a[a[Vi>>2]+(d<<2)>>2]=g}g=a[i>>2];m[g+163|0]=1;i=(g+276|0)>>2;d=a[i];a[i]=d+1|0;var f=g+272|0;a[a[f>>2]+(d<<2)>>2]=b;a[a[f>>2]+(a[i]<<2)>>2]=0;0==(a[a[g+184>>2]+(a[i]-1<<2)>>2]|0)&&S();i=a[c>>2];m[i+163|0]=1;c=(i+268|0)>>2;g=a[c];a[c]=g+1|0;d=i+264|0;a[a[d>>2]+(g<<2)>>2]=b;a[a[d>>2]+(a[c]<<2)>>2]=0;0==(a[a[i+176>>2]+(a[c]-1<<2)>>2]|0)&&S()}function zh(x,c,i){var g=x|0;f[0]=c;a[g>>2]=b[0];a[g+4>>2]=b[1];x=x+8|0;f[0]=i;a[x>>2]=b[0];a[x+4>>2]=b[1]}function ce(x,c,i){var g=h;h+=16;Ui(g,c,i,90*a[kl>>2]|0);var c=g|0,c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),i=g+8|0,i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),c=c-(b[0]=a[Yb>>2],b[1]=a[Yb+4>>2],f[0]),i=i-(b[0]=a[Yb+8>>2],b[1]=a[Yb+12>>2],f[0]),d=x|0;f[0]=c;a[d>>2]=b[0];a[d+4>>2]=b[1];x=x+8|0;f[0]=i;a[x>>2]=b[0];a[x+4>>2]=b[1];h=g}function Bt(x,c){var i,g,d,e=h;h+=1072;i=e+16;var l=e+32,k=e+48,n=ra(x);if(0!=(n|0)){for(;;){var z;g=n+24|0;var p=a[n+124>>2];if(0!=(p|0)){var s=g+8|0,v=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]),s=g+16|0,s=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]);g=g+88|0;d=p+24|0;z=z+(v+(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]))+.5*(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]);v=p+56|0;f[0]=z;a[v>>2]=b[0];a[v+4>>2]=b[1];v=p+64|0;f[0]=s;a[v>>2]=b[0];a[v+4>>2]=b[1];m[p+81|0]=1}p=z;n=ba(x,n);if(0==(n|0)){break}else{z=p}}}n=a[x+152>>2];p=n&3;a[kl>>2]=p;n&=1;m[Ct]=n;(g=0==n<<24>>24)?Dt(x):Et(x);n=x+48|0;z=a[n>>2];if(0==(z|0)){v=s=0}else{if(0!=m[z+81|0]<<24>>24){v=s=0}else{s=z+24|0;v=z+32|0;s=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0])+16;v=(b[0]=a[v>>2],b[1]=a[v+4>>2],f[0])+8;d=0!=(m[x+283|0]&1)<<24>>24;if(g){g=0==(p|0);d?g?(g=(x+76|0)>>2,d=(b[0]=a[g],b[1]=a[g+1],f[0])+v):(g=(x+60|0)>>2,d=(b[0]=a[g],b[1]=a[g+1],f[0])-v):g?(g=(x+60|0)>>2,d=(b[0]=a[g],b[1]=a[g+1],f[0])-v):(g=(x+76|0)>>2,d=(b[0]=a[g],b[1]=a[g+1],f[0])+v);f[0]=d;a[g]=b[0];a[g+1]=b[1];d=(x+68|0)>>2;var t=(b[0]=a[d],b[1]=a[d+1],f[0]);g=(x+52|0)>>2;var u=(b[0]=a[g],b[1]=a[g+1],f[0]),w=t-u}else{d?(g=(x+68|0)>>2,d=(b[0]=a[g],b[1]=a[g+1],f[0])+v):(g=(x+52|0)>>2,d=(b[0]=a[g],b[1]=a[g+1],f[0])-v),f[0]=d,a[g]=b[0],a[g+1]=b[1],d=(x+76|0)>>2,t=(b[0]=a[d],b[1]=a[d+1],f[0]),g=(x+60|0)>>2,u=(b[0]=a[g],b[1]=a[g+1],f[0]),w=t-u}s>w&&(w=.5*(s-w),f[0]=u-w,a[g]=b[0],a[g+1]=b[1],f[0]=t+w,a[d]=b[0],a[d+1]=b[1])}}0==(c|0)?i=z:(1==(p|0)?(i=x+76|0,l=x+52|0,zh(e,-(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])),i=e>>2,a[Yb>>2]=a[i],a[Yb+4>>2]=a[i+1],a[Yb+8>>2]=a[i+2],a[Yb+12>>2]=a[i+3]):0==(p|0)?(i=(x+52|0)>>2,a[Yb>>2]=a[i],a[Yb+4>>2]=a[i+1],a[Yb+8>>2]=a[i+2],a[Yb+12>>2]=a[i+3]):2==(p|0)?(l=x+52|0,p=x+76|0,zh(i,(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),-(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0])),i>>=2,a[Yb>>2]=a[i],a[Yb+4>>2]=a[i+1],a[Yb+8>>2]=a[i+2],a[Yb+12>>2]=a[i+3]):3==(p|0)&&(i=x+60|0,p=x+52|0,zh(l,(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0])),i=l>>2,a[Yb>>2]=a[i],a[Yb+4>>2]=a[i+1],a[Yb+8>>2]=a[i+2],a[Yb+12>>2]=a[i+3]),UG(x),i=a[n>>2]);0!=(i|0)&&0==m[i+81|0]<<24>>24&&(n=s,i=v,l=m[x+283|0],p=l<<24>>24,0==(p&4|0)?(z=x+52|0,z=(b[0]=a[z>>2],b[1]=a[z+4>>2],f[0]),0==(p&2|0)?(n=x+68|0,n=.5*(z+(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]))):n=z+.5*n):(p=x+68|0,n=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0])-.5*n),0==(l&1)<<24>>24?(l=x+60|0,i=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])+.5*i):(l=x+76|0,i=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])-.5*i),l=x+48|0,p=a[l>>2],z=p+56|0,f[0]=n,a[z>>2]=b[0],a[z+4>>2]=b[1],n=p+64|0,f[0]=i,a[n>>2]=b[0],a[n+4>>2]=b[1],m[a[l>>2]+81|0]=1);0!=(a[Yn>>2]|0)&&(k|=0,0==m[Ct]<<24>>24?(i=(b[0]=a[Yb+8>>2],b[1]=a[Yb+12>>2],f[0]),l=(b[0]=a[Yb>>2],b[1]=a[Yb+4>>2],f[0]),Ma(k,VG|0,(j=h,h+=48,f[0]=i,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=l,a[j+8>>2]=b[0],a[j+12>>2]=b[1],f[0]=i,a[j+16>>2]=b[0],a[j+20>>2]=b[1],f[0]=l,a[j+24>>2]=b[0],a[j+28>>2]=b[1],f[0]=-l,a[j+32>>2]=b[0],a[j+36>>2]=b[1],f[0]=-i,a[j+40>>2]=b[0],a[j+44>>2]=b[1],j))):(i=(b[0]=a[Yb>>2],b[1]=a[Yb+4>>2],f[0]),l=(b[0]=a[Yb+8>>2],b[1]=a[Yb+12>>2],f[0]),Ma(k,WG|0,(j=h,h+=32,f[0]=i,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=l,a[j+8>>2]=b[0],a[j+12>>2]=b[1],f[0]=i,a[j+16>>2]=b[0],a[j+20>>2]=b[1],f[0]=l,a[j+24>>2]=b[0],a[j+28>>2]=b[1],j))),k=Hb(k),a[a[Yn>>2]>>2]=k);h=e}function Et(x){if((a[x+32>>2]|0)!=(x|0)){var c=x+48|0,i=a[c>>2];if(0!=(i|0)&&0==m[i+81|0]<<24>>24){var g=m[x+283|0];if(0==(g&1)<<24>>24){var d=x+132|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),e=x+52|0,d=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])+.5*d,e=x+140|0}else{d=x+100|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),e=x+68|0,d=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])-.5*d,e=x+108|0}e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);g=g<<24>>24;0==(g&4|0)?0==(g&2|0)?(g=x+60|0,e=x+76|0,g=.5*((b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])+(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]))):(g=x+76|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])-.5*e):(g=x+60|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])+.5*e);e=i+56|0;f[0]=d;a[e>>2]=b[0];a[e+4>>2]=b[1];i=i+64|0;f[0]=g;a[i>>2]=b[0];a[i+4>>2]=b[1];m[a[c>>2]+81|0]=1}}c=x+208|0;if(1<=(a[c>>2]|0)){x=x+212|0;for(i=1;!(Et(a[a[x>>2]+(i<<2)>>2]),i=i+1|0,(i|0)>(a[c>>2]|0));){}}}function Dt(x){if((a[x+32>>2]|0)!=(x|0)){var c=x+48|0,i=a[c>>2];if(0!=(i|0)&&0==m[i+81|0]<<24>>24){var g=m[x+283|0];if(0==(g&1)<<24>>24){var d=x+92|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),e=x+60|0,d=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])+.5*d,e=x+84|0}else{d=x+124|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),e=x+76|0,d=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])-.5*d,e=x+116|0}e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);g=g<<24>>24;if(0==(g&4|0)){var l=x+52|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);0==(g&2|0)?(g=x+68|0,g=.5*(l+(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]))):g=l+.5*e}else{g=x+68|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])-.5*e}e=i+56|0;f[0]=g;a[e>>2]=b[0];a[e+4>>2]=b[1];i=i+64|0;f[0]=d;a[i>>2]=b[0];a[i+4>>2]=b[1];m[a[c>>2]+81|0]=1}}c=x+208|0;if(1<=(a[c>>2]|0)){x=x+212|0;for(i=1;!(Dt(a[a[x>>2]+(i<<2)>>2]),i=i+1|0,(i|0)>(a[c>>2]|0));){}}}function UG(x){var c,i,g,d=h;h+=32;var e=d+16;if(0==(b[0]=a[Yb>>2],b[1]=a[Yb+4>>2],f[0])){if(!(0!=(b[0]=a[Yb+8>>2],b[1]=a[Yb+12>>2],f[0])|0!=(a[kl>>2]|0))){h=d;return}}var l=ra(x),k=0==(l|0);a:do{if(!k){g=d>>2;i=e>>2;for(var n=l;;){0!=(a[kl>>2]|0)&&et(n,0);var j=c=n+32|0,p=n+40|0;ce(d,(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]),(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]));c>>=2;a[c]=a[g];a[c+1]=a[g+1];a[c+2]=a[g+2];a[c+3]=a[g+3];j=a[n+124>>2];0!=(j|0)&&(p=j+56|0,c=p>>2,j=j+64|0,ce(e,(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0])),a[c]=a[i],a[c+1]=a[i+1],a[c+2]=a[i+2],a[c+3]=a[i+3]);c=1==(a[Ah>>2]|0);b:do{if(c&&(j=Ib(x,n),0!=(j|0))){for(;;){if(XG(j),j=yb(x,j),0==(j|0)){break b}}}}while(0);n=ba(x,n);if(0==(n|0)){break a}}}}while(0);At(x,a[x+152>>2]&3);h=d}function XG(x){var c,i,g,d,e,l,k,n,z,p,s;i=x>>2;var v=h;h+=112;var t=v+16,u=v+32;e=v+48;d=v+64;g=v+80;c=v+96;l=(x+24|0)>>2;var w=a[l];if(0==(w|0)){if(0!=m[Wi]<<24>>24&&6==m[x+124|0]<<24>>24){h=v;return}c=a[a[i+3]+12>>2];la(1,YG|0,(j=h,h+=8,a[j>>2]=a[a[i+4]+12>>2],a[j+4>>2]=c,j))}else{var A=0<(a[w+4>>2]|0);a:do{if(A){s=u>>2;p=t>>2;var x=v>>2,B=0;for(k=w;;){k=a[k>>2]>>2;n=a[k+(12*B|0)];var C=a[k+(12*B|0)+1],P=a[k+(12*B|0)+2];k=a[k+(12*B|0)+3];var T=0<(C|0);b:do{if(T){for(var y=0;;){z=(y<<4)+n|0;var D=z|0,M=(y<<4)+n+8|0;ce(v,(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0]),(b[0]=a[M>>2],b[1]=a[M+4>>2],f[0]));z>>=2;a[z]=a[x];a[z+1]=a[x+1];a[z+2]=a[x+2];a[z+3]=a[x+3];y=y+1|0;if((y|0)==(C|0)){break b}}}}while(0);0!=(P|0)&&(P=a[a[l]>>2],n=P+48*B+16|0,C=n|0,P=P+48*B+24|0,ce(t,(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0]),(b[0]=a[P>>2],b[1]=a[P+4>>2],f[0])),n>>=2,a[n]=a[p],a[n+1]=a[p+1],a[n+2]=a[p+2],a[n+3]=a[p+3]);0!=(k|0)&&(C=a[a[l]>>2],k=C+48*B+32|0,n=k|0,C=C+48*B+40|0,ce(u,(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0])),k>>=2,a[k]=a[s],a[k+1]=a[s+1],a[k+2]=a[s+2],a[k+3]=a[s+3]);B=B+1|0;k=a[l];if((B|0)>=(a[k+4>>2]|0)){break a}}}}while(0);u=a[i+27];0!=(u|0)&&(l=u+56|0,t=l|0,u=u+64|0,ce(e,(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0]),(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0])),l>>=2,e>>=2,a[l]=a[e],a[l+1]=a[e+1],a[l+2]=a[e+2],a[l+3]=a[e+3]);t=a[i+30];0!=(t|0)&&(e=t+56|0,l=e|0,t=t+64|0,ce(d,(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0])),e>>=2,d>>=2,a[e]=a[d],a[e+1]=a[d+1],a[e+2]=a[d+2],a[e+3]=a[d+3]);l=a[i+28];0!=(l|0)&&(d=l+56|0,e=d|0,l=l+64|0,ce(g,(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])),d>>=2,g>>=2,a[d]=a[g],a[d+1]=a[g+1],a[d+2]=a[g+2],a[d+3]=a[g+3]);d=a[i+29];0!=(d|0)&&(i=d+56|0,g=i|0,d=d+64|0,ce(c,(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0])),i>>=2,c>>=2,a[i]=a[c],a[i+1]=a[c+1],a[i+2]=a[c+2],a[i+3]=a[c+3])}h=v}function Ft(x,c,i,g,d,e,l,k){var n,j,p=h;h+=88;var m=p+8;j=m>>2;var v=p+16;n=p+24;var t=p+56,u=p|0;a[u>>2]=d;d=p+4|0;a[d>>2]=e;e=n|0;f[0]=x;a[e>>2]=b[0];a[e+4>>2]=b[1];x=n+8|0;f[0]=c;a[x>>2]=b[0];a[x+4>>2]=b[1];c=n+16|0;f[0]=i;a[c>>2]=b[0];a[c+4>>2]=b[1];i=n+24|0;f[0]=g;a[i>>2]=b[0];a[i+4>>2]=b[1];if(-1==(Gt(p,n|0,m)|0)){return h=p,0}do{if(0==(k|0)){g=a[d>>2];m=a[Bh>>2];(g|0)>(a[ll>>2]|0)&&(m=0==(m|0)?Cb(g<<5):wb(m,g<<5),a[Bh>>2]=m,a[ll>>2]=g);n=0<(g|0);a:do{if(n){i=a[u>>2];for(c=0;;){if(e=((c<<5)+m|0)>>2,x=((c<<4)+i|0)>>2,a[e]=a[x],a[e+1]=a[x+1],a[e+2]=a[x+2],a[e+3]=a[x+3],x=c+1|0,e=((c<<5)+m+16|0)>>2,c=((x%g<<4)+i|0)>>2,a[e]=a[c],a[e+1]=a[c+1],a[e+2]=a[c+2],a[e+3]=a[c+3],(x|0)<(g|0)){c=x}else{break a}}}}while(0);n=t>>2;a[n]=0;a[n+1]=0;a[n+2]=0;a[n+3]=0;a[n+4]=0;a[n+5]=0;a[n+6]=0;a[n+7]=0;if(-1==(ZG(m,g,a[j],a[j+1],t|0,v)|0)){return l=0,h=p,l}}else{Ht(a[j],a[j+1],v)}}while(0);k=a[v+4>>2];$G(k);j=0<(k|0);t=a[Ch>>2];a:do{if(j){u=a[v>>2];for(m=0;;){if(g=((m<<4)+t|0)>>2,d=((m<<4)+u|0)>>2,a[g]=a[d],a[g+1]=a[d+1],a[g+2]=a[d+2],a[g+3]=a[d+3],d=m+1|0,(d|0)<(k|0)){m=d}else{var w=t;break a}}}else{w=t}}while(0);a[l>>2]=k;h=p;return w}function $G(b){var c=h,i=a[Yo>>2];(i|0)<(b|0)&&(b=i+(b+300)-b%300|0,i=wb(a[Ch>>2],b<<4),a[Ch>>2]=i,0==(i|0)&&(la(1,aH|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),S()),a[Yo>>2]=b);h=c}function ff(x,c,i){var g,d,e,l,k,n,z,p,s,v,t,u,w,A,B,C,P,T,y,D,M,X,O,F,Da,ia,E=h;h+=88;var G,J=E+8;ia=J>>2;var sc=E+16,H=E+24,I=E+56;a[ml>>2]=a[ml>>2]+1|0;var K=a[x+80>>2];a[nl>>2]=a[nl>>2]+K|0;var L=a[x+88>>2];for(Da=L>>2;;){if(0==(L|0)){G=1477;break}if(0==m[L+124|0]<<24>>24){break}L=a[Da+32];Da=L>>2}1477==G&&(la(1,bH|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),S());var ca=a[x+84>>2];cH(K,ca,x);var N=K<<3;if((N|0)>(a[It>>2]|0)){var U=a[ol>>2],zb=0==(U|0)?Cb(K<<7):wb(U,K<<7);a[ol>>2]=zb;a[It>>2]=N}var ja=1<(K|0);a:do{if(ja){var aa=ca+8|0,da=(b[0]=a[aa>>2],b[1]=a[aa+4>>2],f[0]),ea=ca+40|0;if(da>(b[0]=a[ea>>2],b[1]=a[ea+4>>2],f[0])){if(0<(K|0)){for(var xa=0,Q=da;;){F=((xa<<5)+ca+24|0)>>2;var Od=(b[0]=a[F],b[1]=a[F+1],f[0]),ha=(xa<<5)+ca+8|0;f[0]=-1*Q;a[F]=b[0];a[F+1]=b[1];f[0]=-Od;a[ha>>2]=b[0];a[ha+4>>2]=b[1];var ga=xa+1|0;if((ga|0)==(K|0)){Rb=1;break a}var W=(ga<<5)+ca+8|0,xa=ga,Q=(b[0]=a[W>>2],b[1]=a[W+4>>2],f[0])}}else{Rb=1}}else{var Rb=0}}else{Rb=0}}while(0);(a[Da+4]|0)==(a[Da+3]|0)&&S();var V=K-1|0,tb=a[ol>>2],ya=0,R=0;a:for(;;){if((R|0)>=(K|0)){var wa=ya,Ab=V;break}if(0<(R|0)){var Fa=(R<<5)+ca+8|0,Ga=(R-1<<5)+ca+8|0,Y=(b[0]=a[Fa>>2],b[1]=a[Fa+4>>2],f[0])>(b[0]=a[Ga>>2],b[1]=a[Ga+4>>2],f[0])?-1:1}else{Y=0}if((R|0)<(V|0)){var ta=(R+1<<5)+ca+8|0,Ka=(R<<5)+ca+8|0,za=(b[0]=a[ta>>2],b[1]=a[ta+4>>2],f[0])>(b[0]=a[Ka>>2],b[1]=a[Ka+4>>2],f[0])?1:-1}else{za=0}do{if((Y|0)==(za|0)){if(-1==(Y|0)){var ma=ya}else{if(0!=(Y|0)){G=1501;break a}O=((R<<5)+ca|0)>>2;var pa=(b[0]=a[O],b[1]=a[O+1],f[0]),$=(ya<<4)+tb|0;f[0]=pa;a[$>>2]=b[0];a[$+4>>2]=b[1];var Ha=(R<<5)+ca+24|0,Ra=(b[0]=a[Ha>>2],b[1]=a[Ha+4>>2],f[0]),fa=ya+1|0,Z=(ya<<4)+tb+8|0;f[0]=Ra;a[Z>>2]=b[0];a[Z+4>>2]=b[1];var La=(b[0]=a[O],b[1]=a[O+1],f[0]),ka=(fa<<4)+tb|0;f[0]=La;a[ka>>2]=b[0];a[ka+4>>2]=b[1];var Ya=(R<<5)+ca+8|0,sa=(b[0]=a[Ya>>2],b[1]=a[Ya+4>>2],f[0]),Za=ya+2|0,ra=(fa<<4)+tb+8|0;f[0]=sa;a[ra>>2]=b[0];a[ra+4>>2]=b[1];ma=Za}}else{if(-1==(za|0)|1==(Y|0)){X=((R<<5)+ca|0)>>2;var ab=(b[0]=a[X],b[1]=a[X+1],f[0]),$a=(ya<<4)+tb|0;f[0]=ab;a[$a>>2]=b[0];a[$a+4>>2]=b[1];var jb=(R<<5)+ca+24|0,Ca=(b[0]=a[jb>>2],b[1]=a[jb+4>>2],f[0]),Ia=ya+1|0,eb=(ya<<4)+tb+8|0;f[0]=Ca;a[eb>>2]=b[0];a[eb+4>>2]=b[1];var ub=(b[0]=a[X],b[1]=a[X+1],f[0]),Sa=(Ia<<4)+tb|0;f[0]=ub;a[Sa>>2]=b[0];a[Sa+4>>2]=b[1];var ba=(R<<5)+ca+8|0,ua=(b[0]=a[ba>>2],b[1]=a[ba+4>>2],f[0]),Oa=ya+2|0,Wa=(Ia<<4)+tb+8|0;f[0]=ua;a[Wa>>2]=b[0];a[Wa+4>>2]=b[1];ma=Oa}else{M=((R<<5)+ca+16|0)>>2;var pb=(b[0]=a[M],b[1]=a[M+1],f[0]),ob=(ya<<4)+tb|0;f[0]=pb;a[ob>>2]=b[0];a[ob+4>>2]=b[1];var bb=(R<<5)+ca+8|0,qb=(b[0]=a[bb>>2],b[1]=a[bb+4>>2],f[0]),qa=ya+1|0,kb=(ya<<4)+tb+8|0;f[0]=qb;a[kb>>2]=b[0];a[kb+4>>2]=b[1];var na=(b[0]=a[M],b[1]=a[M+1],f[0]),vb=(qa<<4)+tb|0;f[0]=na;a[vb>>2]=b[0];a[vb+4>>2]=b[1];var xb=(R<<5)+ca+24|0,hd=(b[0]=a[xb>>2],b[1]=a[xb+4>>2],f[0]),nb=ya+2|0,rb=(qa<<4)+tb+8|0;f[0]=hd;a[rb>>2]=b[0];a[rb+4>>2]=b[1];ma=nb}}}while(0);ya=ma;R=R+1|0}1501==G&&S();a:for(;-1<(Ab|0);){if((Ab|0)<(V|0)){var lb=(Ab<<5)+ca+8|0,Ta=(Ab+1<<5)+ca+8|0,cb=(b[0]=a[lb>>2],b[1]=a[lb+4>>2],f[0])>(b[0]=a[Ta>>2],b[1]=a[Ta+4>>2],f[0])?-1:1}else{cb=0}if(0<(Ab|0)){var fb=(Ab-1<<5)+ca+8|0,Ua=(Ab<<5)+ca+8|0,sb=(b[0]=a[fb>>2],b[1]=a[fb+4>>2],f[0])>(b[0]=a[Ua>>2],b[1]=a[Ua+4>>2],f[0])?1:-1}else{sb=0}do{if((cb|0)==(sb|0)){if(0==(cb|0)){D=((Ab<<5)+ca+16|0)>>2;var Na=(b[0]=a[D],b[1]=a[D+1],f[0]),Fb=(wa<<4)+tb|0;f[0]=Na;a[Fb>>2]=b[0];a[Fb+4>>2]=b[1];var Db=(Ab<<5)+ca+8|0,Ob=(b[0]=a[Db>>2],b[1]=a[Db+4>>2],f[0]),Eb=wa+1|0,ne=(wa<<4)+tb+8|0;f[0]=Ob;a[ne>>2]=b[0];a[ne+4>>2]=b[1];var Bb=(b[0]=a[D],b[1]=a[D+1],f[0]),Ja=(Eb<<4)+tb|0;f[0]=Bb;a[Ja>>2]=b[0];a[Ja+4>>2]=b[1];var oa=(Ab<<5)+ca+24|0,Ba=(b[0]=a[oa>>2],b[1]=a[oa+4>>2],f[0]),Ea=wa+2|0,va=(Eb<<4)+tb+8|0;f[0]=Ba;a[va>>2]=b[0];a[va+4>>2]=b[1];var Ma=Ea}else{if(-1==(cb|0)){y=((Ab<<5)+ca+16|0)>>2;var Aa=(b[0]=a[y],b[1]=a[y+1],f[0]),ic=(wa<<4)+tb|0;f[0]=Aa;a[ic>>2]=b[0];a[ic+4>>2]=b[1];T=((Ab<<5)+ca+8|0)>>2;var Pa=(b[0]=a[T],b[1]=a[T+1],f[0]),Qa=wa+1|0,Va=(wa<<4)+tb+8|0;f[0]=Pa;a[Va>>2]=b[0];a[Va+4>>2]=b[1];var hb=(b[0]=a[y],b[1]=a[y+1],f[0]),ib=(Qa<<4)+tb|0;f[0]=hb;a[ib>>2]=b[0];a[ib+4>>2]=b[1];P=((Ab<<5)+ca+24|0)>>2;var pc=(b[0]=a[P],b[1]=a[P+1],f[0]),Wc=wa+2|0,Vb=(Qa<<4)+tb+8|0;f[0]=pc;a[Vb>>2]=b[0];a[Vb+4>>2]=b[1];C=((Ab<<5)+ca|0)>>2;var Xd=(b[0]=a[C],b[1]=a[C+1],f[0]),Gb=(Wc<<4)+tb|0;f[0]=Xd;a[Gb>>2]=b[0];a[Gb+4>>2]=b[1];var Ib=(b[0]=a[P],b[1]=a[P+1],f[0]),yb=wa+3|0,Nb=(Wc<<4)+tb+8|0;f[0]=Ib;a[Nb>>2]=b[0];a[Nb+4>>2]=b[1];var mb=(b[0]=a[C],b[1]=a[C+1],f[0]),lc=(yb<<4)+tb|0;f[0]=mb;a[lc>>2]=b[0];a[lc+4>>2]=b[1];var Hb=(b[0]=a[T],b[1]=a[T+1],f[0]),Jd=wa+4|0,db=(yb<<4)+tb+8|0;f[0]=Hb;a[db>>2]=b[0];a[db+4>>2]=b[1];Ma=Jd}else{G=1514;break a}}}else{if(-1==(sb|0)|1==(cb|0)){B=((Ab<<5)+ca|0)>>2;var Hc=(b[0]=a[B],b[1]=a[B+1],f[0]),Lb=(wa<<4)+tb|0;f[0]=Hc;a[Lb>>2]=b[0];a[Lb+4>>2]=b[1];var oe=(Ab<<5)+ca+24|0,Pb=(b[0]=a[oe>>2],b[1]=a[oe+4>>2],f[0]),Qb=wa+1|0,tc=(wa<<4)+tb+8|0;f[0]=Pb;a[tc>>2]=b[0];a[tc+4>>2]=b[1];var Sc=(b[0]=a[B],b[1]=a[B+1],f[0]),Kb=(Qb<<4)+tb|0;f[0]=Sc;a[Kb>>2]=b[0];a[Kb+4>>2]=b[1];var Jb=(Ab<<5)+ca+8|0,$b=(b[0]=a[Jb>>2],b[1]=a[Jb+4>>2],f[0]),rd=wa+2|0,oc=(Qb<<4)+tb+8|0;f[0]=$b;a[oc>>2]=b[0];a[oc+4>>2]=b[1];Ma=rd}else{A=((Ab<<5)+ca+16|0)>>2;var Yb=(b[0]=a[A],b[1]=a[A+1],f[0]),qk=(wa<<4)+tb|0;f[0]=Yb;a[qk>>2]=b[0];a[qk+4>>2]=b[1];var ve=(Ab<<5)+ca+8|0,Rd=(b[0]=a[ve>>2],b[1]=a[ve+4>>2],f[0]),Bc=wa+1|0,Sd=(wa<<4)+tb+8|0;f[0]=Rd;a[Sd>>2]=b[0];a[Sd+4>>2]=b[1];var fd=(b[0]=a[A],b[1]=a[A+1],f[0]),Mb=(Bc<<4)+tb|0;f[0]=fd;a[Mb>>2]=b[0];a[Mb+4>>2]=b[1];var yc=(Ab<<5)+ca+24|0,Wb=(b[0]=a[yc>>2],b[1]=a[yc+4>>2],f[0]),Tb=wa+2|0,Tc=(Bc<<4)+tb+8|0;f[0]=Wb;a[Tc>>2]=b[0];a[Tc+4>>2]=b[1];Ma=Tb}}}while(0);wa=Ma;Ab=Ab-1|0}1514==G&&(a[c>>2]=0,S());var Sb=0==(Rb|0);a:do{if(!Sb){var Mc=0<(K|0);b:do{if(Mc){for(var id=0;;){w=((id<<5)+ca+24|0)>>2;var uc=(b[0]=a[w],b[1]=a[w+1],f[0])&-1;u=((id<<5)+ca+8|0)>>2;var Zb=-1*(b[0]=a[u],b[1]=a[u+1],f[0]);f[0]=Zb;a[w]=b[0];a[w+1]=b[1];f[0]=-uc|0;a[u]=b[0];a[u+1]=b[1];var qc=id+1|0;if((qc|0)==(K|0)){break b}else{id=qc}}}}while(0);if(0<(wa|0)){for(var mf=0;;){t=((mf<<4)+tb+8|0)>>2;var nf=-1*(b[0]=a[t],b[1]=a[t+1],f[0]);f[0]=nf;a[t]=b[0];a[t+1]=b[1];var ec=mf+1|0;if((ec|0)==(wa|0)){break a}else{mf=ec}}}}}while(0);var af=0<(K|0);a:do{if(af){for(var yf=0;;){var Td=(yf<<5)+ca|0;f[0]=2147483647;a[Td>>2]=b[0];a[Td+4>>2]=b[1];var Fe=(yf<<5)+ca+16|0;f[0]=-2147483648;a[Fe>>2]=b[0];a[Fe+4>>2]=b[1];var gf=yf+1|0;if((gf|0)==(K|0)){break a}else{yf=gf}}}}while(0);a[E>>2]=tb;var fe=E+4|0;a[fe>>2]=wa;var fh=x|0,df=(b[0]=a[fh>>2],b[1]=a[fh+4>>2],f[0]),jd=H|0,md=H|0;f[0]=df;a[md>>2]=b[0];a[md+4>>2]=b[1];var je=x+8|0,Qe=(b[0]=a[je>>2],b[1]=a[je+4>>2],f[0]),Xf=H+8|0;f[0]=Qe;a[Xf>>2]=b[0];a[Xf+4>>2]=b[1];var Uc=x+40|0,bf=(b[0]=a[Uc>>2],b[1]=a[Uc+4>>2],f[0]),mc=H+16|0;f[0]=bf;a[mc>>2]=b[0];a[mc+4>>2]=b[1];var kc=x+48|0,rc=(b[0]=a[kc>>2],b[1]=a[kc+4>>2],f[0]),ef=H+24|0;f[0]=rc;a[ef>>2]=b[0];a[ef+4>>2]=b[1];-1==(Gt(E,jd,J)|0)&&S();do{if(0==(i|0)){var kd=a[fe>>2];if((kd|0)>(a[ll>>2]|0)){var ac=a[Bh>>2],dc=0==(ac|0)?Cb(kd<<5):wb(ac,kd<<5);a[Bh>>2]=dc;a[ll>>2]=kd}var bc=0<(kd|0);a:do{if(bc){for(var jc=a[Bh>>2],fc=a[ol>>2],nd=0;;){v=((nd<<5)+jc|0)>>2;s=((nd<<4)+fc|0)>>2;a[v]=a[s];a[v+1]=a[s+1];a[v+2]=a[s+2];a[v+3]=a[s+3];var zd=nd+1|0;p=((nd<<5)+jc+16|0)>>2;z=((zd%kd<<4)+fc|0)>>2;a[p]=a[z];a[p+1]=a[z+1];a[p+2]=a[z+2];a[p+3]=a[z+3];if((zd|0)<(kd|0)){nd=zd}else{break a}}}}while(0);if(0==m[x+29|0]<<24>>24){n=I>>2,a[n]=0,a[n+1]=0,a[n+2]=0,a[n+3]=0}else{var cf=x+16|0,nc=(b[0]=a[cf>>2],b[1]=a[cf+4>>2],f[0]),gc=se(nc),Xb=I|0;f[0]=gc;a[Xb>>2]=b[0];a[Xb+4>>2]=b[1];var we=Ce(nc),pe=I+8|0;f[0]=we;a[pe>>2]=b[0];a[pe+4>>2]=b[1]}if(0==m[x+69|0]<<24>>24){k=(I+16|0)>>2,a[k]=0,a[k+1]=0,a[k+2]=0,a[k+3]=0}else{var Ac=x+56|0,Kd=(b[0]=a[Ac>>2],b[1]=a[Ac+4>>2],f[0]),Dd=-se(Kd),$c=I+16|0;f[0]=Dd;a[$c>>2]=b[0];a[$c+4>>2]=b[1];var zc=-Ce(Kd),Ye=I+24|0;f[0]=zc;a[Ye>>2]=b[0];a[Ye+4>>2]=b[1]}-1==(ZG(a[Bh>>2],kd,a[ia],a[ia+1],I|0,sc)|0)&&S()}else{Ht(a[ia],a[ia+1],sc)}}while(0);var Ad=a[sc+4>>2];$G(Ad);a:do{if(af){for(var Ud=0;;){var Vd=(Ud<<5)+ca|0;f[0]=2147483647;a[Vd>>2]=b[0];a[Vd+4>>2]=b[1];var Cc=(Ud<<5)+ca+16|0;f[0]=-2147483648;a[Cc>>2]=b[0];a[Cc+4>>2]=b[1];var Ic=Ud+1|0;if((Ic|0)==(K|0)){break a}else{Ud=Ic}}}}while(0);var gd=0<(Ad|0),ud=a[Ch>>2];a:do{if(gd){for(var vc=a[sc>>2],Fn=0;;){l=((Fn<<4)+ud|0)>>2;e=((Fn<<4)+vc|0)>>2;a[l]=a[e];a[l+1]=a[e+1];a[l+2]=a[e+2];a[l+3]=a[e+3];var of=Fn+1|0;if((of|0)<(Ad|0)){Fn=of}else{break a}}}}while(0);var ld=3<(Ad|0),Dc=10;a:for(;;){b:do{if(ld){for(var Qc=Dc*K|0,Yc=0>(Qc|0),Jc=Qc|0,Kc=0,Nc=3;;){c:do{if(!Yc){for(var Ec=(Kc<<4)+ud|0,Pc=(Kc<<4)+ud+8|0,Fc=Kc+1|0,Gc=(Fc<<4)+ud|0,Zc=(Fc<<4)+ud+8|0,Vc=Kc+2|0,Bd=(Vc<<4)+ud|0,Rc=(Vc<<4)+ud+8|0,Cd=(Nc<<4)+ud|0,vd=(Nc<<4)+ud+8|0,bd=0;;){var te=(bd|0)/Jc,pd=(b[0]=a[Ec>>2],b[1]=a[Ec+4>>2],f[0]),cd=(b[0]=a[Pc>>2],b[1]=a[Pc+4>>2],f[0]),qd=(b[0]=a[Gc>>2],b[1]=a[Gc+4>>2],f[0]),Jt=(b[0]=a[Zc>>2],b[1]=a[Zc+4>>2],f[0]),Gd=(b[0]=a[Bd>>2],b[1]=a[Bd+4>>2],f[0]),ad=(b[0]=a[Rc>>2],b[1]=a[Rc+4>>2],f[0]),Fd=pd+te*(qd-pd),yd=cd+te*(Jt-cd),Xc=qd+te*(Gd-qd),dd=Jt+te*(ad-Jt),od=Fd+te*(Xc-Fd),Md=yd+te*(dd-yd),ed=od+te*(Xc+te*(Gd+te*((b[0]=a[Cd>>2],b[1]=a[Cd+4>>2],f[0])-Gd)-Xc)-od),Ld=Md+te*(dd+te*(ad+te*((b[0]=a[vd>>2],b[1]=a[vd+4>>2],f[0])-ad)-dd)-Md);d:do{if(af){for(var Oc=0;;){var be=(Oc<<5)+ca+24|0;if(Ld<=(b[0]=a[be>>2],b[1]=a[be+4>>2],f[0])+1e-4){var wd=(Oc<<5)+ca+8|0;if(Ld>=(b[0]=a[wd>>2],b[1]=a[wd+4>>2],f[0])-1e-4){d=((Oc<<5)+ca|0)>>2;if((b[0]=a[d],b[1]=a[d+1],f[0])>ed){f[0]=ed,a[d]=b[0],a[d+1]=b[1]}g=((Oc<<5)+ca+16|0)>>2;if((b[0]=a[g],b[1]=a[g+1],f[0])(Qc|0)){break c}else{bd=ke}}}}while(0);var sd=Nc+3|0;if((sd|0)<(Ad|0)){Kc=Nc,Nc=sd}else{var Lc=0;break b}}}else{Lc=0}}while(0);for(;;){if((Lc|0)>=(K|0)){break a}var Ed=(Lc<<5)+ca|0;if(2147483647==(b[0]=a[Ed>>2],b[1]=a[Ed+4>>2],f[0])){break}var Wd=(Lc<<5)+ca+16|0;if(-2147483648==(b[0]=a[Wd>>2],b[1]=a[Wd+4>>2],f[0])){break}else{Lc=Lc+1|0}}Dc<<=1}a[c>>2]=Ad;h=E;return ud}function dH(a,b,i,g){if(!((b|0)>(i|0)&(a|0)<(g|0))){return 0}if(!((i|0)>(a|0)|(a|0)>(g|0))){return g-a|0}(i|0)>(b|0)|(b|0)>(g|0)?(a=b-a|0,i=g-i|0,i=(a|0)<(i|0)?a:i):i=b-i|0;return i}function Jg(x){var c,i=h;c=(x+80|0)>>2;Va(a[oa>>2],eH|0,(j=h,h+=4,a[j>>2]=a[c],j));var g=0<(a[c]|0),d=a[oa>>2];a:do{if(g){for(var e=x+84|0,l=0,k=d;;){var n=a[e>>2],z=(l<<5)+n|0,z=(b[0]=a[z>>2],b[1]=a[z+4>>2],f[0]),p=(l<<5)+n+8|0,p=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),s=(l<<5)+n+16|0,s=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]),n=(l<<5)+n+24|0,n=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]);Va(k,fH|0,(j=h,h+=36,a[j>>2]=l,f[0]=z,a[j+4>>2]=b[0],a[j+8>>2]=b[1],f[0]=p,a[j+12>>2]=b[0],a[j+16>>2]=b[1],f[0]=s,a[j+20>>2]=b[0],a[j+24>>2]=b[1],f[0]=n,a[j+28>>2]=b[0],a[j+32>>2]=b[1],j));l=l+1|0;k=a[oa>>2];if((l|0)>=(a[c]|0)){var v=k;break a}}}else{v=d}}while(0);c=x|0;c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]);g=x+8|0;g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);d=x+16|0;d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]);e=0!=m[x+29|0]<<24>>24?Kt|0:Lt|0;Va(v,gH|0,(j=h,h+=28,f[0]=c,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=g,a[j+8>>2]=b[0],a[j+12>>2]=b[1],f[0]=d,a[j+16>>2]=b[0],a[j+20>>2]=b[1],a[j+24>>2]=e,j));v=a[oa>>2];c=x+40|0;c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]);g=x+48|0;g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);d=x+56|0;d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]);x=0!=m[x+69|0]<<24>>24?Kt|0:Lt|0;Va(v,hH|0,(j=h,h+=28,f[0]=c,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=g,a[j+8>>2]=b[0],a[j+12>>2]=b[1],f[0]=d,a[j+16>>2]=b[0],a[j+20>>2]=b[1],a[j+24>>2]=x,j));h=i}function cH(x,c,i){var g,d,e,l,k,n,z,p,s,v,t,u,w,A,B,C,P,T,y,D=h,M,X=0<(x|0);a:do{if(X){for(var O=0,F=0;;){var Da=(F<<5)+c|0,ia=(F<<5)+c+8|0,E=(F<<5)+c+24|0,G=(b[0]=a[ia>>2],b[1]=a[ia+4>>2],f[0])-(b[0]=a[E>>2],b[1]=a[E+4>>2],f[0]);if(.01>(0>G?-G:G)){var J=O}else{var sc=Da|0,I=(F<<5)+c+16|0,H=(b[0]=a[sc>>2],b[1]=a[sc+4>>2],f[0])-(b[0]=a[I>>2],b[1]=a[I+4>>2],f[0]);.01>(0>H?-H:H)?J=O:((O|0)!=(F|0)&&(y=((O<<5)+c|0)>>2,T=Da>>2,a[y]=a[T],a[y+1]=a[T+1],a[y+2]=a[T+2],a[y+3]=a[T+3],a[y+4]=a[T+4],a[y+5]=a[T+5],a[y+6]=a[T+6],a[y+7]=a[T+7]),J=O+1|0)}var K=F+1|0;if((K|0)==(x|0)){var L=J;break a}else{O=J,F=K}}}else{L=0}}while(0);P=(c|0)>>2;var ca=(b[0]=a[P],b[1]=a[P+1],f[0]);C=(c+16|0)>>2;var N=(b[0]=a[C],b[1]=a[C+1],f[0]);ca>N&&(la(1,Mt|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),Jg(i),S());B=(c+8|0)>>2;var U=(b[0]=a[B],b[1]=a[B+1],f[0]);A=(c+24|0)>>2;var zb=(b[0]=a[A],b[1]=a[A+1],f[0]);U>zb&&(la(1,Mt|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),Jg(i),S());for(var ja=L-1|0,aa=0,da=N,ea=ca,xa=zb,Q=U;;){if((aa|0)>=(ja|0)){M=1642;break}var R=aa+1|0;w=((R<<5)+c|0)>>2;var ha=(b[0]=a[w],b[1]=a[w+1],f[0]);u=((R<<5)+c+16|0)>>2;var ga=(b[0]=a[u],b[1]=a[u+1],f[0]);if(ha>ga){M=1673;break}t=((R<<5)+c+8|0)>>2;var W=(b[0]=a[t],b[1]=a[t+1],f[0]);v=((R<<5)+c+24|0)>>2;var Rb=(b[0]=a[v],b[1]=a[v+1],f[0]);if(W>Rb){M=1674;break}s=((aa<<5)+c+16|0)>>2;var V=da>2;var ya=ea>ga,Y=ya&1;z=((aa<<5)+c+24|0)>>2;var wa=xa>2;var Fa=Q>Rb,Ga=Fa&1,$=Y+tb+Ab+Ga|0,ta=0<($|0);0==m[ld]<<24>>24|ta^1||(Va(a[oa>>2],iH|0,(j=h,h+=8,a[j>>2]=aa,a[j+4>>2]=R,j)),Jg(i));a:do{if(ta){if(V){var Ka=(b[0]=a[s],b[1]=a[s+1],f[0])&-1,za=(b[0]=a[w],b[1]=a[w+1],f[0]);f[0]=za;a[s]=b[0];a[s+1]=b[1];f[0]=Ka|0;a[w]=b[0];a[w+1]=b[1];var ma=Ga,pa=Ab,fa=Y,Ha=0}else{if(ya){var Ra=(b[0]=a[p],b[1]=a[p+1],f[0])&-1,Z=(b[0]=a[u],b[1]=a[u+1],f[0]);f[0]=Z;a[p]=b[0];a[p+1]=b[1];f[0]=Ra|0;a[u]=b[0];a[u+1]=b[1];ma=Ga;pa=Ab;fa=0}else{if(wa){var ka=(b[0]=a[z],b[1]=a[z+1],f[0])&-1,La=(b[0]=a[t],b[1]=a[t+1],f[0]);f[0]=La;a[z]=b[0];a[z+1]=b[1];f[0]=ka|0;a[t]=b[0];a[t+1]=b[1];ma=Ga;pa=0}else{if(Fa){var sa=(b[0]=a[n],b[1]=a[n+1],f[0])&-1,Ya=(b[0]=a[v],b[1]=a[v+1],f[0]);f[0]=Ya;a[n]=b[0];a[n+1]=b[1];f[0]=sa|0;a[v]=b[0];a[v+1]=b[1];ma=0}else{ma=Ga}pa=Ab}fa=Y}Ha=tb}if(0<($-1|0)){for(var ra=((ya^1)<<31>>31)+tb+Ab+Ga|0,Za=Ha,ba=fa,ab=0,$a=pa,jb=ma;;){if(1==(Za|0)){var Ca=.5*((b[0]=a[s],b[1]=a[s+1],f[0])+(b[0]=a[w],b[1]=a[w+1],f[0]))+.5&-1|0;f[0]=Ca;a[w]=b[0];a[w+1]=b[1];f[0]=Ca;a[s]=b[0];a[s+1]=b[1];var Ia=jb,eb=$a,ub=ba,Sa=0}else{if(1==(ba|0)){var qa=.5*((b[0]=a[p],b[1]=a[p+1],f[0])+(b[0]=a[u],b[1]=a[u+1],f[0]))+.5&-1|0;f[0]=qa;a[u]=b[0];a[u+1]=b[1];f[0]=qa;a[p]=b[0];a[p+1]=b[1];Ia=jb;eb=$a;ub=0}else{if(1==($a|0)){var ua=.5*((b[0]=a[z],b[1]=a[z+1],f[0])+(b[0]=a[t],b[1]=a[t+1],f[0]))+.5&-1|0;f[0]=ua;a[t]=b[0];a[t+1]=b[1];f[0]=ua;a[z]=b[0];a[z+1]=b[1];Ia=jb;eb=0}else{if(1!=(jb|0)){Ia=jb}else{var Oa=.5*((b[0]=a[n],b[1]=a[n+1],f[0])+(b[0]=a[v],b[1]=a[v+1],f[0]))+.5&-1|0;f[0]=Oa;a[v]=b[0];a[v+1]=b[1];f[0]=Oa;a[n]=b[0];a[n+1]=b[1];Ia=0}eb=$a}ub=ba}Sa=Za}var Wa=ab+1|0;if((Wa|0)==(ra|0)){break a}else{Za=Sa,ba=ub,ab=Wa,$a=eb,jb=Ia}}}}}while(0);var pb=(b[0]=a[p],b[1]=a[p+1],f[0]),ob=(b[0]=a[s],b[1]=a[s+1],f[0]),bb=(b[0]=a[w],b[1]=a[w+1],f[0]),qb=(b[0]=a[u],b[1]=a[u+1],f[0]),na=dH(pb&-1,ob&-1,bb&-1,qb&-1),kb=(b[0]=a[n],b[1]=a[n+1],f[0]),ie=(b[0]=a[z],b[1]=a[z+1],f[0]),vb=(b[0]=a[t],b[1]=a[t+1],f[0]),xb=(b[0]=a[v],b[1]=a[v+1],f[0]),hd=dH(kb&-1,ie&-1,vb&-1,xb&-1);if(0==(na|0)|0==(hd|0)){aa=R,da=qb,ea=bb,xa=xb,Q=vb}else{if((na|0)<(hd|0)){var nb=obqb-bb?(nb?(f[0]=bb,a[s]=b[0],a[s+1]=b[1]):(f[0]=qb,a[p]=b[0],a[p+1]=b[1]),aa=R,da=qb,ea=bb):nb?(f[0]=ob,a[w]=b[0],a[w+1]=b[1],aa=R,da=qb,ea=ob):(f[0]=pb,a[u]=b[0],a[u+1]=b[1],aa=R,da=pb,ea=bb);xa=xb;Q=vb}else{var rb=iexb-vb?(rb?(f[0]=vb,a[z]=b[0],a[z+1]=b[1]):(f[0]=xb,a[n]=b[0],a[n+1]=b[1]),aa=R,da=qb,ea=bb,xa=xb,Q=vb):rb?(f[0]=ie,a[t]=b[0],a[t+1]=b[1],aa=R,da=qb,ea=bb,xa=xb,Q=ie):(f[0]=kb,a[v]=b[0],a[v+1]=b[1],aa=R,da=qb,ea=bb,xa=kb,Q=vb)}}}if(1642==M){k=(i|0)>>2;var lb=(b[0]=a[k],b[1]=a[k+1],f[0]),Ta=(b[0]=a[P],b[1]=a[P+1],f[0]);if(lb(b[0]=a[C],b[1]=a[C+1],f[0])){M=1646}else{var cb=i+8|0,fb=(b[0]=a[cb>>2],b[1]=a[cb+4>>2],f[0]);if(fb<(b[0]=a[B],b[1]=a[B+1],f[0])){M=1646}else{if(fb>(b[0]=a[A],b[1]=a[A+1],f[0])){M=1646}}}}if(1646==M){if(0==m[ld]<<24>>24){var Ua=lb,sb=Ta}else{Lc(jH|0,42,1,a[oa>>2]),Jg(i),Ua=(b[0]=a[k],b[1]=a[k+1],f[0]),sb=(b[0]=a[P],b[1]=a[P+1],f[0])}if(UaFb&&(f[0]=Fb,a[k]=b[0],a[k+1]=b[1]);l=(i+8|0)>>2;var Db=(b[0]=a[l],b[1]=a[l+1],f[0]),Ob=(b[0]=a[B],b[1]=a[B+1],f[0]);if(Dbne&&(f[0]=ne,a[l]=b[0],a[l+1]=b[1])}e=(i+40|0)>>2;var Bb=(b[0]=a[e],b[1]=a[e+1],f[0]);d=((ja<<5)+c|0)>>2;var Ja=(b[0]=a[d],b[1]=a[d+1],f[0]);if(Bb>=Ja){var Ba=(ja<<5)+c+16|0;if(Bb<=(b[0]=a[Ba>>2],b[1]=a[Ba+4>>2],f[0])){var Ea=i+48|0,va=(b[0]=a[Ea>>2],b[1]=a[Ea+4>>2],f[0]),Ma=(ja<<5)+c+8|0;if(va>=(b[0]=a[Ma>>2],b[1]=a[Ma+4>>2],f[0])){var Aa=(ja<<5)+c+24|0;if(va<=(b[0]=a[Aa>>2],b[1]=a[Aa+4>>2],f[0])){h=D;return}}}}if(0==m[ld]<<24>>24){var wb=Bb,ic=Ja}else{Lc(kH|0,39,1,a[oa>>2]),Jg(i),wb=(b[0]=a[e],b[1]=a[e+1],f[0]),ic=(b[0]=a[d],b[1]=a[d+1],f[0])}if(wb>2],b[1]=a[Qa+4>>2],f[0]);Cb>Pa&&(f[0]=Pa,a[e]=b[0],a[e+1]=b[1]);g=(i+48|0)>>2;var hb=(b[0]=a[g],b[1]=a[g+1],f[0]),ib=(ja<<5)+c+8|0,pc=(b[0]=a[ib>>2],b[1]=a[ib+4>>2],f[0]);if(hb>2],b[1]=a[Vb+4>>2],f[0]);Wc>Xd&&(f[0]=Xd,a[g]=b[0],a[g+1]=b[1]);h=D}else{1673==M?(la(1,Nt|0,(j=h,h+=4,a[j>>2]=R,j)),Jg(i),S()):1674==M&&(la(1,Nt|0,(j=h,h+=4,a[j>>2]=R,j)),Jg(i),S())}}function Zo(x,c,i,g,d,e){var l=x|0;f[0]=(d-i)*c+i;a[l>>2]=b[0];a[l+4>>2]=b[1];x=x+8|0;f[0]=(e-g)*c+g;a[x>>2]=b[0];a[x+4>>2]=b[1]}function ts(b){b=a[b+24>>2];0==(b|0)?b=0:(b=a[a[b+4>>2]>>2],b=270==(b|0)?1:56==(b|0)?2:54==(b|0)?3:356==(b|0)?4:0);return b}function Ii(x,c,i,g,d,e,l){var k,n,j,p,m,v,t,u,w,A,B,C,P,T,y,D,M,X,O,F,Da,ia,E,J,H,I,K,L,N,hc,ca,R,Q,zb,ja,aa,da,ea,xa,U,S,ha,ga,W,Rb,V,tb,ya,Y,wa,Ab,Fa,Ga,$,ta,Ka,za,ma,pa,la,Ha,Ra,Z,ka,La,ba,Ya,ra,Za,qa,ab,$a,jb,Ca,Ia,eb,ub,Sa,na,ua,Oa,Wa,pb,ob,bb,qb,oa,kb,ie,vb,xb,hd,nb,rb,lb,Ta,cb,fb,Ua,sb,Na=h;h+=128;var Fb=Na+64,Db=Na+80,Ob=Na+96,Eb=Na+112;if(0==(e&4|0)){var ne=e&992,Bb=0==(ne|0)?2:ne}else{Bb=4}var Ja=fa((d<<6)+64|0);sb=Ja>>2;var Ba=0<(d|0);a:do{if(Ba){for(var Ea=d-1|0,va=g+8|0,Ma=12,Aa=0;;){var wb=(Aa<<4)+g|0,ic=(b[0]=a[wb>>2],b[1]=a[wb+4>>2],f[0]),Qa=(Aa<<4)+g+8|0,hb=(b[0]=a[Qa>>2],b[1]=a[Qa+4>>2],f[0]),Va=Aa+1|0;if((Aa|0)<(Ea|0)){var ib=(Va<<4)+g|0,Gb=(Va<<4)+g+8|0}else{ib=g,Gb=va}var pc=ib|0,Wc=(b[0]=a[pc>>2],b[1]=a[pc+4>>2],f[0])-ic,Vb=(b[0]=a[Gb>>2],b[1]=a[Gb+4>>2],f[0])-hb,Xd=$c(Wc*Wc+Vb*Vb)/3,yb=Ma>2;fb=Db>>2;cb=Ob>>2;Ta=Eb>>2;for(var db=0,lc=0;;){var Hb=(lc<<4)+g|0,Jd=(b[0]=a[Hb>>2],b[1]=a[Hb+4>>2],f[0]),Lb=(lc<<4)+g+8|0,Hc=(b[0]=a[Lb>>2],b[1]=a[Lb+4>>2],f[0]);if((lc|0)<(Ea|0)){var Kb=lc+1|0,oe=(Kb<<4)+g|0,Jb=(Kb<<4)+g+8|0}else{oe=g,Jb=va}var Qb=oe|0,tc=(b[0]=a[Qb>>2],b[1]=a[Qb+4>>2],f[0]),Sc=(b[0]=a[Jb>>2],b[1]=a[Jb+4>>2],f[0]),Pb=tc-Jd,Yb=Sc-Hc,Mb=yb/$c(Pb*Pb+Yb*Yb),rd=Ib?Nb?Mb:.5*Mb:Mb/3;if(mb){Zo(Fb,.5*rd,Jd,Hc,tc,Sc),lb=((db<<4)+Ja|0)>>2,a[lb]=a[Ua],a[lb+1]=a[Ua+1],a[lb+2]=a[Ua+2],a[lb+3]=a[Ua+3]}else{var oc=(db<<4)+Ja|0;f[0]=Jd;a[oc>>2]=b[0];a[oc+4>>2]=b[1];var Wb=(db<<4)+Ja+8|0;f[0]=Hc;a[Wb>>2]=b[0];a[Wb+4>>2]=b[1]}Zo(Db,rd,Jd,Hc,tc,Sc);rb=((db+1<<4)+Ja|0)>>2;a[rb]=a[fb];a[rb+1]=a[fb+1];a[rb+2]=a[fb+2];a[rb+3]=a[fb+3];var Tb=db+3|0;Zo(Ob,1-rd,Jd,Hc,tc,Sc);nb=((db+2<<4)+Ja|0)>>2;a[nb]=a[cb];a[nb+1]=a[cb+1];a[nb+2]=a[cb+2];a[nb+3]=a[cb+3];if(mb){Zo(Eb,1-.5*rd,Jd,Hc,tc,Sc);hd=((Tb<<4)+Ja|0)>>2;a[hd]=a[Ta];a[hd+1]=a[Ta+1];a[hd+2]=a[Ta+2];a[hd+3]=a[Ta+3];var ve=db+4|0}else{ve=Tb}var Rd=lc+1|0;if((Rd|0)==(d|0)){var Bc=ve;break a}else{db=ve,lc=Rd}}}else{Bc=0}}while(0);xb=((Bc<<4)+Ja|0)>>2;a[xb]=a[sb];a[xb+1]=a[sb+1];a[xb+2]=a[sb+2];a[xb+3]=a[sb+3];var Sd=Ja+16|0;vb=Sd>>2;ie=((Bc+1<<4)+Ja|0)>>2;a[ie]=a[vb];a[ie+1]=a[vb+1];a[ie+2]=a[vb+2];a[ie+3]=a[vb+3];var fd=Ja+32|0;kb=fd>>2;oa=((Bc+2<<4)+Ja|0)>>2;a[oa]=a[kb];a[oa+1]=a[kb+1];a[oa+2]=a[kb+2];a[oa+3]=a[kb+3];if(256==(Bb|0)){4!=(d|0)&&sa(Ot|0,562,Pt|0,Qt|0);Pa(x,i);0!=(l|0)&&$b(x,c);var Sb=d+2|0,yc=fa(Sb<<4);qb=yc>>2;bb=g>>2;a[qb]=a[bb];a[qb+1]=a[bb+1];a[qb+2]=a[bb+2];a[qb+3]=a[bb+3];ob=(yc+16|0)>>2;a[ob]=a[kb];a[ob+1]=a[kb+1];a[ob+2]=a[kb+2];a[ob+3]=a[kb+3];pb=(yc+32|0)>>2;Wa=(Ja+64|0)>>2;a[pb]=a[Wa];a[pb+1]=a[Wa+1];a[pb+2]=a[Wa+2];a[pb+3]=a[Wa+3];Oa=(yc+48|0)>>2;ua=(g+32|0)>>2;a[Oa]=a[ua];a[Oa+1]=a[ua+1];a[Oa+2]=a[ua+2];a[Oa+3]=a[ua+3];na=(yc+64|0)>>2;Sa=(Ja+128|0)>>2;a[na]=a[Sa];a[na+1]=a[Sa+1];a[na+2]=a[Sa+2];a[na+3]=a[Sa+3];ub=(yc+80|0)>>2;eb=(Ja+160|0)>>2;a[ub]=a[eb];a[ub+1]=a[eb+1];a[ub+2]=a[eb+2];a[ub+3]=a[eb+3];zc(x,yc,Sb,l&255);G(yc);var qc=(b[0]=a[Sd>>2],b[1]=a[Sd+4>>2],f[0]),ec=Ja+176|0,Tc=qc+((b[0]=a[ec>>2],b[1]=a[ec+4>>2],f[0])-(b[0]=a[Ja>>2],b[1]=a[Ja+4>>2],f[0])),uc=Na|0,Mc=Na|0;f[0]=Tc;a[Mc>>2]=b[0];a[Mc+4>>2]=b[1];var id=Ja+24|0,mc=(b[0]=a[id>>2],b[1]=a[id+4>>2],f[0]),Zb=Ja+184|0,kc=Ja+8|0,ac=mc+((b[0]=a[Zb>>2],b[1]=a[Zb+4>>2],f[0])-(b[0]=a[kc>>2],b[1]=a[kc+4>>2],f[0])),nf=Na+8|0;f[0]=ac;a[nf>>2]=b[0];a[nf+4>>2]=b[1];Ia=(Na+16|0)>>2;a[Ia]=a[Wa];a[Ia+1]=a[Wa+1];a[Ia+2]=a[Wa+2];a[Ia+3]=a[Wa+3];Gd(x,uc,2);a[Ia]=a[Sa];a[Ia+1]=a[Sa+1];a[Ia+2]=a[Sa+2];a[Ia+3]=a[Sa+3];Gd(x,uc,2);a[Ia]=a[sb];a[Ia+1]=a[sb+1];a[Ia+2]=a[sb+2];a[Ia+3]=a[sb+3];Gd(x,uc,2)}else{if(2==(Bb|0)){var rc=0==(l|0);a:do{if(!rc){var af=d<<1,jc=Cb(d<<5),Td=jc;Pa(x,c);$b(x,c);if(Ba){var Fe=0,gf=0}else{zc(x,Td,af,1);G(jc);Pa(x,i);G(Ja);h=Na;return}for(;;){var fe=Fe<<2;Ca=((gf<<4)+Td|0)>>2;jb=(((fe|1)<<4)+Ja|0)>>2;a[Ca]=a[jb];a[Ca+1]=a[jb+1];a[Ca+2]=a[jb+2];a[Ca+3]=a[jb+3];$a=(((gf|1)<<4)+Td|0)>>2;ab=(((fe|2)<<4)+Ja|0)>>2;a[$a]=a[ab];a[$a+1]=a[ab+1];a[$a+2]=a[ab+2];a[$a+3]=a[ab+3];var fc=Fe+1|0;if((fc|0)==(d|0)){break}else{Fe=fc,gf=gf+2|0}}zc(x,Td,af,1);G(jc);for(var df=0;;){$f(x,((df<<2|2)<<4)+Ja|0,4,0,0,1);var jd=df+1|0;if((jd|0)==(d|0)){break a}else{df=jd}}}}while(0);Pa(x,i);if(Ba){for(var md=0;;){var je=md<<2;Gd(x,((je|1)<<4)+Ja|0,2);$f(x,((je|2)<<4)+Ja|0,4,0,0,0);var Qe=md+1|0;if((Qe|0)==(d|0)){break}else{md=Qe}}}}else{if(128==(Bb|0)){Pa(x,i);0!=(l|0)&&$b(x,c);var nc=d+3|0,Uc=fa(nc<<4);qa=Uc>>2;Za=g>>2;a[qa]=a[Za];a[qa+1]=a[Za+1];a[qa+2]=a[Za+2];a[qa+3]=a[Za+3];ra=(g|0)>>2;var bf=(b[0]=a[ra],b[1]=a[ra+1],f[0]);Ya=Sd>>2;var dc=bf-.25*(bf-(b[0]=a[Ya],b[1]=a[Ya+1],f[0])),bc=Uc+16|0;f[0]=dc;a[bc>>2]=b[0];a[bc+4>>2]=b[1];var Xb=g+8|0,gc=(b[0]=a[Xb>>2],b[1]=a[Xb+4>>2],f[0]),kd=Ja+48|0;ba=(Ja+56|0)>>2;var Ac=Ja+72|0,Cc=gc+((b[0]=a[ba],b[1]=a[ba+1],f[0])-(b[0]=a[Ac>>2],b[1]=a[Ac+4>>2],f[0]))/3,Ic=Uc+24|0;f[0]=Cc;a[Ic>>2]=b[0];a[Ic+4>>2]=b[1];var vc=(b[0]=a[ra],b[1]=a[ra+1],f[0]),Kc=vc-2*(vc-(b[0]=a[Ya],b[1]=a[Ya+1],f[0])),nd=Uc+32|0;f[0]=Kc;a[nd>>2]=b[0];a[nd+4>>2]=b[1];var zd=Uc+40|0;f[0]=Cc;a[zd>>2]=b[0];a[zd+4>>2]=b[1];var cf=(b[0]=a[ra],b[1]=a[ra+1],f[0]),Nc=cf-2.25*(cf-(b[0]=a[Ya],b[1]=a[Ya+1],f[0])),Dc=Uc+48|0;f[0]=Nc;a[Dc>>2]=b[0];a[Dc+4>>2]=b[1];var zi=(b[0]=a[ba],b[1]=a[ba+1],f[0]),we=Uc+56|0;f[0]=zi;a[we>>2]=b[0];a[we+4>>2]=b[1];var pe=(b[0]=a[kd>>2],b[1]=a[kd+4>>2],f[0]),Qc=Uc+64|0;f[0]=pe;a[Qc>>2]=b[0];a[Qc+4>>2]=b[1];var Kd=(b[0]=a[ba],b[1]=a[ba+1],f[0]),Dd=Uc+72|0;f[0]=Kd;a[Dd>>2]=b[0];a[Dd+4>>2]=b[1];var ld=4<(nc|0);a:do{if(ld){for(var Ec=4;;){La=((Ec<<4)+Uc|0)>>2;ka=((Ec-3<<4)+g|0)>>2;a[La]=a[ka];a[La+1]=a[ka+1];a[La+2]=a[ka+2];a[La+3]=a[ka+3];var Ye=Ec+1|0;if((Ye|0)==(nc|0)){break a}else{Ec=Ye}}}}while(0);zc(x,Uc,nc,l&255);G(Uc)}else{if(4==(Bb|0)){if(Pa(x,i),0!=(l|0)&&$b(x,c),zc(x,g,d,l&255),Ba){var Ad=Na|0;Z=Na>>2;Ra=(Na+16|0)>>2;for(var Ud=0;;){var Vd=3*Ud|0;Ha=((Vd+2<<4)+Ja|0)>>2;a[Z]=a[Ha];a[Z+1]=a[Ha+1];a[Z+2]=a[Ha+2];a[Z+3]=a[Ha+3];la=((Vd+4<<4)+Ja|0)>>2;a[Ra]=a[la];a[Ra+1]=a[la+1];a[Ra+2]=a[la+2];a[Ra+3]=a[la+3];Gd(x,Ad,2);var Oc=Ud+1|0;if((Oc|0)==(d|0)){break}else{Ud=Oc}}}}else{if(512==(Bb|0)){4!=(d|0)&&sa(Ot|0,588,Pt|0,Qt|0);Pa(x,i);0!=(l|0)&&$b(x,c);var Pc=d+8|0,gd=fa(Pc<<4);pa=gd>>2;ma=g>>2;a[pa]=a[ma];a[pa+1]=a[ma+1];a[pa+2]=a[ma+2];a[pa+3]=a[ma+3];za=(gd+16|0)>>2;Ka=(g+16|0)>>2;a[za]=a[Ka];a[za+1]=a[Ka+1];a[za+2]=a[Ka+2];a[za+3]=a[Ka+3];ta=(Ja+48|0)>>2;var ud=(b[0]=a[ta],b[1]=a[ta+1],f[0]);$=(Ja+64|0)>>2;var Fc=ud+((b[0]=a[$],b[1]=a[$+1],f[0])-ud),Yc=gd+32|0;Ga=Yc>>2;Fa=Yc>>2;f[0]=Fc;a[Fa]=b[0];a[Fa+1]=b[1];Ab=(Ja+56|0)>>2;var of=(b[0]=a[Ab],b[1]=a[Ab+1],f[0]);wa=(Ja+72|0)>>2;var Gc=of+((b[0]=a[wa],b[1]=a[wa+1],f[0])-of);Y=(gd+40|0)>>2;f[0]=Gc;a[Y]=b[0];a[Y+1]=b[1];var Zc=Fc+((b[0]=a[ta],b[1]=a[ta+1],f[0])-(b[0]=a[fd>>2],b[1]=a[fd+4>>2],f[0]));ya=(gd+48|0)>>2;f[0]=Zc;a[ya]=b[0];a[ya+1]=b[1];var Lc=Ja+40|0,Jc=Gc+((b[0]=a[Ab],b[1]=a[Ab+1],f[0])-(b[0]=a[Lc>>2],b[1]=a[Lc+4>>2],f[0]));tb=(gd+56|0)>>2;f[0]=Jc;a[tb]=b[0];a[tb+1]=b[1];var bd=Zc+((b[0]=a[$],b[1]=a[$+1],f[0])-(b[0]=a[ta],b[1]=a[ta+1],f[0]));V=(gd+64|0)>>2;f[0]=bd;a[V]=b[0];a[V+1]=b[1];var Vc=Jc+((b[0]=a[wa],b[1]=a[wa+1],f[0])-(b[0]=a[Ab],b[1]=a[Ab+1],f[0]));Rb=(gd+72|0)>>2;f[0]=Vc;a[Rb]=b[0];a[Rb+1]=b[1];var Bd=bd+(Fc-Zc),Rc=gd+80|0;W=Rc>>2;f[0]=Bd;a[Rc>>2]=b[0];a[Rc+4>>2]=b[1];var ed=gd+88|0;f[0]=Vc+(Gc-Jc);a[ed>>2]=b[0];a[ed+4>>2]=b[1];ga=(Ja+96|0)>>2;var qd=(b[0]=a[ga],b[1]=a[ga+1],f[0]);ha=(Ja+80|0)>>2;var Cd=qd+((b[0]=a[ha],b[1]=a[ha+1],f[0])-qd),ad=gd+144|0;S=ad>>2;f[0]=Cd;a[ad>>2]=b[0];a[ad+4>>2]=b[1];U=(Ja+104|0)>>2;var vd=(b[0]=a[U],b[1]=a[U+1],f[0]);xa=(Ja+88|0)>>2;var Xc=vd+((b[0]=a[xa],b[1]=a[xa+1],f[0])-vd),dd=gd+152|0;f[0]=Xc;a[dd>>2]=b[0];a[dd+4>>2]=b[1];var pd=Ja+112|0,cd=Cd+((b[0]=a[ga],b[1]=a[ga+1],f[0])-(b[0]=a[pd>>2],b[1]=a[pd+4>>2],f[0]));ea=(gd+128|0)>>2;f[0]=cd;a[ea]=b[0];a[ea+1]=b[1];var Fd=Ja+120|0,te=Xc+((b[0]=a[U],b[1]=a[U+1],f[0])-(b[0]=a[Fd>>2],b[1]=a[Fd+4>>2],f[0]));da=(gd+136|0)>>2;f[0]=te;a[da]=b[0];a[da+1]=b[1];var yd=cd+((b[0]=a[ha],b[1]=a[ha+1],f[0])-(b[0]=a[ga],b[1]=a[ga+1],f[0]));aa=(gd+112|0)>>2;f[0]=yd;a[aa]=b[0];a[aa+1]=b[1];var od=te+((b[0]=a[xa],b[1]=a[xa+1],f[0])-(b[0]=a[U],b[1]=a[U+1],f[0]));ja=(gd+120|0)>>2;f[0]=od;a[ja]=b[0];a[ja+1]=b[1];var Md=yd+(Cd-cd),Ld=gd+96|0;zb=Ld>>2;Q=Ld>>2;f[0]=Md;a[Q]=b[0];a[Q+1]=b[1];var be=od+(Xc-te);R=(gd+104|0)>>2;f[0]=be;a[R]=b[0];a[R+1]=b[1];ca=(gd+160|0)>>2;hc=(g+32|0)>>2;a[ca]=a[hc];a[ca+1]=a[hc+1];a[ca+2]=a[hc+2];a[ca+3]=a[hc+3];N=(gd+176|0)>>2;L=(g+48|0)>>2;a[N]=a[L];a[N+1]=a[L+1];a[N+2]=a[L+2];a[N+3]=a[L+3];zc(x,gd,Pc,l&255);var wd=Na|0;K=Na>>2;a[K]=a[Ga];a[K+1]=a[Ga+1];a[K+2]=a[Ga+2];a[K+3]=a[Ga+3];var xd=(b[0]=a[Fa],b[1]=a[Fa+1],f[0]),ke=xd-((b[0]=a[ya],b[1]=a[ya+1],f[0])-xd);I=(Na+16|0)>>2;f[0]=ke;a[I]=b[0];a[I+1]=b[1];var sd=(b[0]=a[Y],b[1]=a[Y+1],f[0]),Ed=sd-((b[0]=a[tb],b[1]=a[tb+1],f[0])-sd);H=(Na+24|0)>>2;f[0]=Ed;a[H]=b[0];a[H+1]=b[1];var Wd=ke+((b[0]=a[V],b[1]=a[V+1],f[0])-(b[0]=a[ya],b[1]=a[ya+1],f[0]));J=(Na+32|0)>>2;f[0]=Wd;a[J]=b[0];a[J+1]=b[1];var se=Ed+((b[0]=a[Rb],b[1]=a[Rb+1],f[0])-(b[0]=a[tb],b[1]=a[tb+1],f[0]));E=(Na+40|0)>>2;f[0]=se;a[E]=b[0];a[E+1]=b[1];ia=(Na+48|0)>>2;a[ia]=a[W];a[ia+1]=a[W+1];a[ia+2]=a[W+2];a[ia+3]=a[W+3];Gd(x,wd,4);a[K]=a[zb];a[K+1]=a[zb+1];a[K+2]=a[zb+2];a[K+3]=a[zb+3];var Pd=(b[0]=a[Q],b[1]=a[Q+1],f[0]),de=Pd-((b[0]=a[aa],b[1]=a[aa+1],f[0])-Pd);f[0]=de;a[I]=b[0];a[I+1]=b[1];var Id=(b[0]=a[R],b[1]=a[R+1],f[0]),le=Id-((b[0]=a[ja],b[1]=a[ja+1],f[0])-Id);f[0]=le;a[H]=b[0];a[H+1]=b[1];var he=de+((b[0]=a[ea],b[1]=a[ea+1],f[0])-(b[0]=a[aa],b[1]=a[aa+1],f[0]));f[0]=he;a[J]=b[0];a[J+1]=b[1];var Ce=le+((b[0]=a[da],b[1]=a[da+1],f[0])-(b[0]=a[ja],b[1]=a[ja+1],f[0]));f[0]=Ce;a[E]=b[0];a[E+1]=b[1];a[ia]=a[S];a[ia+1]=a[S+1];a[ia+2]=a[S+2];a[ia+3]=a[S+3];Gd(x,wd,4);G(gd)}else{if(32==(Bb|0)){Pa(x,i);0!=(l|0)&&$b(x,c);var ce=d+1|0,Hd=fa(ce<<4);Da=Hd>>2;var ue=1<(d|0);a:do{if(ue){for(var Yd=1;;){F=((Yd<<4)+Hd|0)>>2;O=((Yd<<4)+g|0)>>2;a[F]=a[O];a[F+1]=a[O+1];a[F+2]=a[O+2];a[F+3]=a[O+3];var Qd=Yd+1|0;if((Qd|0)==(d|0)){break a}else{Yd=Qd}}}}while(0);var $d=3*d|0;X=(($d+1<<4)+Ja|0)>>2;a[Da]=a[X];a[Da+1]=a[X+1];a[Da+2]=a[X+2];a[Da+3]=a[X+3];M=((d<<4)+Hd|0)>>2;D=(($d-1<<4)+Ja|0)>>2;a[M]=a[D];a[M+1]=a[D+1];a[M+2]=a[D+2];a[M+3]=a[D+3];zc(x,Hd,ce,l&255);G(Hd);var De=Na|0;y=Na>>2;a[y]=a[D];a[y+1]=a[D+1];a[y+2]=a[D+2];a[y+3]=a[D+3];var ae=Na+16|0;T=ae>>2;a[T]=a[X];a[T+1]=a[X+1];a[T+2]=a[X+2];a[T+3]=a[X+3];var Ae=ae|0,He=(b[0]=a[Ae>>2],b[1]=a[Ae+4>>2],f[0]),Ee=Na|0,xe=($d<<4)+Ja|0,Ke=He+((b[0]=a[Ee>>2],b[1]=a[Ee+4>>2],f[0])-(b[0]=a[xe>>2],b[1]=a[xe+4>>2],f[0])),qe=Na+32|0,re=qe|0;f[0]=Ke;a[re>>2]=b[0];a[re+4>>2]=b[1];var Ge=Na+24|0,ye=(b[0]=a[Ge>>2],b[1]=a[Ge+4>>2],f[0]),ze=Na+8|0,me=($d<<4)+Ja+8|0,Oe=ye+((b[0]=a[ze>>2],b[1]=a[ze+4>>2],f[0])-(b[0]=a[me>>2],b[1]=a[me+4>>2],f[0])),Be=Na+40|0;f[0]=Oe;a[Be>>2]=b[0];a[Be+4>>2]=b[1];Gd(x,ae,2);P=qe>>2;a[T]=a[P];a[T+1]=a[P+1];a[T+2]=a[P+2];a[T+3]=a[P+3];Gd(x,De,2)}else{if(64==(Bb|0)){Pa(x,i);0!=(l|0)&&$b(x,c);var Nd=d+2|0,td=fa(Nd<<4);C=td>>2;B=g>>2;a[C]=a[B];a[C+1]=a[B+1];a[C+2]=a[B+2];a[C+3]=a[B+3];A=(td+16|0)>>2;a[A]=a[kb];a[A+1]=a[kb+1];a[A+2]=a[kb+2];a[A+3]=a[kb+3];var Pe=(b[0]=a[fd>>2],b[1]=a[fd+4>>2],f[0]),Ie=Ja+48|0;w=Ie>>2;u=Ie>>2;t=(Ja+64|0)>>2;var We=Pe+((b[0]=a[u],b[1]=a[u+1],f[0])-(b[0]=a[t],b[1]=a[t+1],f[0]))/3,Le=td+32|0;f[0]=We;a[Le>>2]=b[0];a[Le+4>>2]=b[1];var Me=Ja+40|0,Te=(b[0]=a[Me>>2],b[1]=a[Me+4>>2],f[0]);v=(Ja+56|0)>>2;m=(Ja+72|0)>>2;var Ue=Te+((b[0]=a[v],b[1]=a[v+1],f[0])-(b[0]=a[m],b[1]=a[m+1],f[0]))/3,Ve=td+40|0;f[0]=Ue;a[Ve>>2]=b[0];a[Ve+4>>2]=b[1];var Re=(b[0]=a[u],b[1]=a[u+1],f[0]),hf=Re+(Re-(b[0]=a[t],b[1]=a[t+1],f[0]))/3,Se=td+48|0;f[0]=hf;a[Se>>2]=b[0];a[Se+4>>2]=b[1];var Ze=(b[0]=a[v],b[1]=a[v+1],f[0]),jf=Ze+(Ze-(b[0]=a[m],b[1]=a[m+1],f[0]))/3,ff=td+56|0;f[0]=jf;a[ff>>2]=b[0];a[ff+4>>2]=b[1];var kf=4<(Nd|0);a:do{if(kf){for(var ge=4;;){p=((ge<<4)+td|0)>>2;j=((ge-2<<4)+g|0)>>2;a[p]=a[j];a[p+1]=a[j+1];a[p+2]=a[j+2];a[p+3]=a[j+3];var Ne=ge+1|0;if((Ne|0)==(Nd|0)){break a}else{ge=Ne}}}}while(0);zc(x,td,Nd,l&255);G(td);n=Na>>2;a[n]=a[w];a[n+1]=a[w+1];a[n+2]=a[w+2];a[n+3]=a[w+3];k=(Na+16|0)>>2;a[k]=a[kb];a[k+1]=a[kb+1];a[k+2]=a[kb+2];a[k+3]=a[kb+3];Gd(x,Na|0,2)}}}}}}}G(Ja);h=Na}function lH(x){var c,i,g,d,e,l,k,n,z=h;h+=32;var p;n=z>>2;var s=z+8;k=s>>2;var v=z+16,t=z+24,u=fa(44);l=u>>2;var w=x+24|0,A=a[a[w>>2]+8>>2];e=A>>2;var B=a[e],C=a[e+1],P=a[e+2],T=A+12|0,y=(b[0]=a[T>>2],b[1]=a[T+4>>2],f[0]),D=A+28|0,M=(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0]),X=A+20|0,O=(b[0]=a[X>>2],b[1]=a[X+4>>2],f[0]),F=x|0,Da=jo(V(F,mH|0))&255|B,ia=0!=(Da|0);if(ia){var E,G=x|0,J=Xb(G,a[rh>>2],0,.01),H=Xb(G,a[sh>>2],0,.02),I=72*(J>H?J:H);E=(0>I?I-.5:I+.5)&-1|0;if(0>2],b[1]=a[N+4>>2],f[0]),R=x+56|0,Q=(b[0]=a[R>>2],b[1]=a[R+4>>2],f[0]),zb=72*(cazb?zb-.5:zb+.5)&-1|0,L=K=ja}}else{var aa=x+48|0,da=72*(b[0]=a[aa>>2],b[1]=a[aa+4>>2],f[0]),ea=0>da?da-.5:da+.5,xa=x+56|0,U=72*(b[0]=a[xa>>2],b[1]=a[xa+4>>2],f[0]),K=(0>U?U-.5:U+.5)&-1|0,L=ea&-1|0}var S=Zf(F,a[$o>>2],C),ha=y+Xb(F,a[Rt>>2],0,-360);if(0==(P|0)){var ga=Xb(F,a[St>>2],0,-100),W=Zf(F,a[Tt>>2],4),Rb=Xb(F,a[Ut>>2],0,-100),Y=ga,tb=W}else{Rb=O,Y=M,tb=P}d=(x+120|0)>>2;var ya=a[d],$=ya+24|0,wa=(b[0]=a[$>>2],b[1]=a[$+4>>2],f[0]),Ab=ya+32|0,Fa=(b[0]=a[Ab>>2],b[1]=a[Ab+4>>2],f[0]),Ga=wa&-1,Z=-1<(Ga|0)?Ga:-Ga|0,ta=Z|0;p=-1<(Z|0)?0==(ta+.5&-1|0)?1774:1777:0==(ta-.5&-1|0)?1774:1777;if(1774==p){var Ka=Fa&-1,za=-1<(Ka|0)?Ka:-Ka|0,ma=za|0;if(-1<(za|0)){if(0==(ma+.5&-1|0)){var pa=wa,sa=Fa}else{p=1777}}else{0==(ma-.5&-1|0)?(pa=wa,sa=Fa):p=1777}}if(1777==p){var Ha=V(F,Zn|0);if(0==(Ha|0)){pa=wa+16,sa=Fa+8}else{var Ra=Cd(Ha,Lk|0,(j=h,h+=8,a[j>>2]=z,a[j+4>>2]=s,j)),ba=(b[0]=a[n],b[1]=a[n+1],f[0]);if(0>ba){f[0]=0;a[n]=b[0];a[n+1]=b[1];var ra=0}else{ra=ba}var La=(b[0]=a[k],b[1]=a[k+1],f[0]);if(0>La){f[0]=0;a[k]=b[0];a[k+1]=b[1];var qa=0}else{qa=La}if(0<(Ra|0)){var Ya=72*ra,na=0>Ya,Za=wa+(((na?Ya-.5:Ya+.5)&-1)<<1|0);if(1<(Ra|0)){var oa=72*qa,ab=0>oa?oa-.5:oa+.5,pa=Za,sa=Fa+((ab&-1)<<1|0)}else{var $a=na?Ya-.5:Ya+.5,pa=Za,sa=Fa+(($a&-1)<<1|0)}}else{pa=wa+16,sa=Fa+8}}}var jb=a[d]+24|0,Ca=pa-(b[0]=a[jb>>2],b[1]=a[jb+4>>2],f[0]);g=(x+20|0)>>2;var Ia=a[a[g]+44>>2]|0,eb=(b[0]=a[Ia>>2],b[1]=a[Ia+4>>2],f[0]);if(0ub?ub-.5:ub+.5)&-1|0,Ba=nH(pa,Sa),ua=nH(sa,Sa)}else{Ba=pa,ua=sa}var Oa=a[w>>2];if(0==m[Oa+12|0]<<24>>24){var Wa=V(F,ap|0);if(0==(Wa|0)){var pb=0,ob=0}else{if(0==m[Wa]<<24>>24){ob=pb=0}else{oH(t,a[g],Wa);var bb=a[t>>2],qb=a[t+4>>2];if(-1==(bb|0)&-1==(qb|0)){var Ma=a[x+12>>2];la(0,pH|0,(j=h,h+=8,a[j>>2]=Wa,a[j+4>>2]=Ma,j));ob=pb=0}else{m[a[g]+150|0]=1,pb=bb+2|0,ob=qb+2|0}}}}else{var kb=a[Oa>>2];if(99!=m[kb]<<24>>24){ob=pb=0}else{if(0!=(ka(kb,th|0)|0)){ob=pb=0}else{var ie=V(F,Xk|0);oH(v,a[g],ie);var vb=a[v>>2],xb=a[v+4>>2];if(-1==(vb|0)&-1==(xb|0)){var hd=0!=(ie|0)?ie:qH|0,nb=a[x+12>>2];la(0,rH|0,(j=h,h+=8,a[j>>2]=hd,a[j+4>>2]=nb,j));ob=pb=0}else{m[a[g]+150|0]=1,pb=vb+2|0,ob=xb+2|0}}}}var rb=pb|0,lb=Ba>rb?Ba:rb,Ta=ob|0,cb=ua>Ta?ua:Ta,fb=3>(tb|0)?0!=Rb|0!=Y?120:tb:tb,Ua=V(F,Vt|0);if(0==(Ua|0)){p=1818}else{var sb=m[Ua];116==sb<<24>>24||98==sb<<24>>24?m[a[d]+80|0]=sb:p=1818}1818==p&&(m[a[d]+80|0]=99);if(4==(fb|0)){if(0==(((0>ha?ha-.5:ha+.5)&-1)%90|0)&0==Rb&0==Y){var Na=lb,Fb=cb,Db=1}else{p=1824}}else{p=1824}if(1824==p){var Ob=1.4142135623730951*cb;if(K>Ob){if(99!=m[a[d]+80|0]<<24>>24){var Eb=1.4142135623730951,ne=Ob}else{var Bb=cb/K,Eb=$c(1/(1-Bb*Bb)),ne=cb}}else{Eb=1.4142135623730951,ne=Ob}var Ja=lb*Eb;if(2<(fb|0)){var Ea=se(3.141592653589793/(fb|0)),Na=Ja/Ea,Fb=ne/Ea}else{Na=Ja,Fb=ne}Db=0}if(0==re(jc(F,a[bp>>2],Ze|0))<<24>>24){var va=L>Na?L:Na,Aa=K>Fb?K:Fb}else{if(L>2];la(0,sH|0,(j=h,h+=8,a[j>>2]=a[x+12>>2],a[j+4>>2]=wb,j))}va=L;Aa=K}if(ia){var Cb=va>Aa?va:Aa,Qa=Cb,ic=Cb}else{Qa=va,ic=Aa}if(0==re(jc(F,a[cp>>2],Ze|0))<<24>>24){if(Db){var Va=a[d]+40|0;f[0]=(Ba>Qa?Ba:Qa)-Ca;a[Va>>2]=b[0];a[Va+4>>2]=b[1]}else{if(uaPa?Ba:Pa)-Ca;a[hb>>2]=b[0];a[hb+4>>2]=b[1]}else{var ib=a[d]+40|0;f[0]=Ba-Ca;a[ib>>2]=b[0];a[ib+4>>2]=b[1]}}}else{var Gb=a[d]+40|0;f[0]=Ba-Ca;a[Gb>>2]=b[0];a[Gb+4>>2]=b[1]}var pc=ic-Fb,Wc=a[d]+48|0;f[0]=ua+(ua>2]=b[0];a[Wc+4>>2]=b[1];var Vb=1>(S|0)?1:S,Xd=3>(fb|0);a:do{if(Xd){var yb=fa(Vb<<5),db=yb,Ib=.5*Qa,Nb=.5*ic,mb=yb;f[0]=-Ib;a[mb>>2]=b[0];a[mb+4>>2]=b[1];var lc=yb+8|0;f[0]=-Nb;a[lc>>2]=b[0];a[lc+4>>2]=b[1];var Hb=yb+16|0;f[0]=Ib;a[Hb>>2]=b[0];a[Hb+4>>2]=b[1];var Jd=yb+24|0;f[0]=Nb;a[Jd>>2]=b[0];a[Jd+4>>2]=b[1];if(1<(S|0)){for(var Lb=Nb,Hc=Ib,Jb=2,oe=1;;){var Kb=Hc+4,Qb=Lb+4,tc=(Jb<<4)+db|0;f[0]=-Kb;a[tc>>2]=b[0];a[tc+4>>2]=b[1];var Sc=(Jb<<4)+db+8|0;f[0]=-Qb;a[Sc>>2]=b[0];a[Sc+4>>2]=b[1];var Pb=Jb|1,Yb=(Pb<<4)+db|0;f[0]=Kb;a[Yb>>2]=b[0];a[Yb+4>>2]=b[1];var Mb=(Pb<<4)+db+8|0;f[0]=Qb;a[Mb>>2]=b[0];a[Mb+4>>2]=b[1];var rd=oe+1|0;if((rd|0)==(S|0)){break}else{Lb=Qb,Hc=Kb,Jb=Jb+2|0,oe=rd}}oc=db;Wb=2;Tb=2*Kb;ve=2*Qb}else{var oc=db,Wb=2,Tb=Qa,ve=ic}}else{for(var Rd=fa((Vb<<4)*fb|0),Bc=Rd,Sd=6.283185307179586/(fb|0),fd=.5*Sd,$b=Ce(fd),yc=pi(be(Rb)+be(Y),1),Sb=1.4142135623730951*Rb/se(fd),uc=.5*Y,Tc=.5*(Sd-3.141592653589793),ec=3.141592653589793*(ha/180),Mc=0,id=Tc+.5*(3.141592653589793-Sd),Zb=0,qc=0,nc=.5*Ce(Tc),kc=.5*se(Tc);;){if((Mc|0)>=(fb|0)){var mc=Zb,fc=qc;break}var af=id+Sd,ac=Ce(af),Td=kc+$b*se(af),Fe=nc+$b*ac,bc=Td*(yc+Fe*Sb)+Fe*uc,fe=ec+Cf(Fe,bc),rc=Ce(fe),dc=se(fe),jd=pi(bc,Fe),md=jd*dc*Qa,je=jd*rc*ic,Qe=be(md)>Zb?be(md):Zb,Ac=be(je)>qc?be(je):qc,Uc=(Mc<<4)+Bc|0;f[0]=md;a[Uc>>2]=b[0];a[Uc+4>>2]=b[1];var gc=(Mc<<4)+Bc+8|0;f[0]=je;a[gc>>2]=b[0];a[gc+4>>2]=b[1];if(Db){p=1855;break}else{Mc=Mc+1|0,id=af,Zb=Qe,qc=Ac,nc=Fe,kc=Td}}if(1855==p){var Cc=-md,Ic=Rd+16|0;f[0]=Cc;a[Ic>>2]=b[0];a[Ic+4>>2]=b[1];var vc=Rd+24|0;f[0]=je;a[vc>>2]=b[0];a[vc+4>>2]=b[1];var ef=Rd+32|0;f[0]=Cc;a[ef>>2]=b[0];a[ef+4>>2]=b[1];var kd=-je,Kc=Rd+40|0;f[0]=kd;a[Kc>>2]=b[0];a[Kc+4>>2]=b[1];var Nc=Rd+48|0;f[0]=md;a[Nc>>2]=b[0];a[Nc+4>>2]=b[1];var zc=Rd+56|0;f[0]=kd;a[zc>>2]=b[0];a[zc+4>>2]=b[1];mc=Qe;fc=Ac}var Dc=2*mc,Ec=2*fc,nd=Qa>Dc?Qa:Dc,zd=ic>Ec?ic:Ec,cf=nd/Dc,Qc=zd/Ec,Fc=0<(fb|0);b:do{if(Fc){for(var Gc=0;;){i=((Gc<<4)+Bc|0)>>2;c=((Gc<<4)+Bc+8|0)>>2;var we=(b[0]=a[i],b[1]=a[i+1],f[0])*cf,pe=(b[0]=a[c],b[1]=a[c+1],f[0])*Qc;f[0]=we;a[i]=b[0];a[i+1]=b[1];f[0]=pe;a[c]=b[0];a[c+1]=b[1];var Zc=Gc+1|0;if((Zc|0)==(fb|0)){break b}else{Gc=Zc}}}}while(0);if(1<(S|0)){var Kd=fb-1|0,Dd=(Kd<<4)+Bc|0,Jc=(b[0]=a[Dd>>2],b[1]=a[Dd+4>>2],f[0]),Oc=(Kd<<4)+Bc+8|0,Ye=(b[0]=a[Oc>>2],b[1]=a[Oc+4>>2],f[0]),Ad=Rd,Ud=(b[0]=a[Ad>>2],b[1]=a[Ad+4>>2],f[0]),Vd=Rd+8|0,Pc=(b[0]=a[Vd>>2],b[1]=a[Vd+4>>2],f[0]);if(Fc){for(var Yc=Ud,gd=Pc,ud=0,Rc=Cf(Pc-Ye,Ud-Jc);;){for(var Lc=ud+1|0,of=(Lc|0)==(fb|0),ld=of?0:Lc,ad=(ld<<4)+Bc|0,bd=(b[0]=a[ad>>2],b[1]=a[ad+4>>2],f[0]),Xc=(ld<<4)+Bc+8|0,Vc=(b[0]=a[Xc>>2],b[1]=a[Xc+4>>2],f[0]),cd=Cf(Vc-gd,bd-Yc),ed=.5*(Rc+3.141592653589793-cd),qd=4/Ce(ed),vd=Rc-ed,dd=Ce(vd)*qd,Bd=se(vd)*qd,pd=gd,Fd=Yc,yd=1;;){var od=Fd+Bd,td=pd+dd,Ld=yd*fb+ud|0,wd=(Ld<<4)+Bc|0;f[0]=od;a[wd>>2]=b[0];a[wd+4>>2]=b[1];var te=(Ld<<4)+Bc+8|0;f[0]=td;a[te>>2]=b[0];a[te+4>>2]=b[1];var xd=yd+1|0;if((xd|0)==(S|0)){break}else{pd=td,Fd=od,yd=xd}}if(of){break}else{Yc=bd,gd=Vc,ud=Lc,Rc=cd}}for(var ke=fb*(S-1)|0,sd=zd,Md=nd,Gd=0;;){var Ed=Gd+ke|0,Wd=(Ed<<4)+Bc|0,Hd=(b[0]=a[Wd>>2],b[1]=a[Wd+4>>2],f[0]),Yd=(Ed<<4)+Bc+8|0,$d=(b[0]=a[Yd>>2],b[1]=a[Yd+4>>2],f[0]),Pd=2*be(Hd)>Md?2*be(Hd):Md,de=2*be($d)>sd?2*be($d):sd,Id=Gd+1|0;if((Id|0)==(fb|0)){oc=Bc;Wb=fb;Tb=Pd;ve=de;break a}else{sd=de,Md=Pd,Gd=Id}}}else{oc=Bc,Wb=fb,Tb=nd,ve=zd}}else{oc=Bc,Wb=fb,Tb=nd,ve=zd}}}while(0);a[l]=Da;a[l+1]=S;a[l+2]=Wb;var le=u+12|0;f[0]=ha;a[le>>2]=b[0];a[le+4>>2]=b[1];var ae=u+28|0;f[0]=Y;a[ae>>2]=b[0];a[ae+4>>2]=b[1];var Nd=u+20|0;f[0]=Rb;a[Nd>>2]=b[0];a[Nd+4>>2]=b[1];a[l+10]=oc;var ce=x+48|0;f[0]=Tb/72;a[ce>>2]=b[0];a[ce+4>>2]=b[1];var Qd=x+56|0;f[0]=ve/72;a[Qd>>2]=b[0];a[Qd+4>>2]=b[1];a[x+28>>2]=u;h=z}function tH(x){var c,i,g,d=h;h+=32;var e=d+16,l=a[a[x+20>>2]+152>>2]>>>2&1^1;i=x+120|0;g=a[a[i>>2]>>2];a[Fd>>2]=g;g=Ba(g);g=fa(1<(g|0)?g+1|0:2);c=dp(x,l,1,g);0==(c|0)?(la(1,uH|0,(j=h,h+=4,a[j>>2]=a[a[i>>2]>>2],j)),a[Fd>>2]=yg|0,l=dp(x,l,1,g)):l=c;G(g);Wt(d,x,l);g=(x+48|0)>>2;i=72*(b[0]=a[g],b[1]=a[g+1],f[0]);var k=(0>i?i-.5:i+.5)&-1|0;i=(x+56|0)>>2;c=72*(b[0]=a[i],b[1]=a[i+1],f[0]);var n=(0>c?c-.5:c+.5)&-1|0,z=x|0;c=(l|0)>>2;if(0==re(jc(z,a[bp>>2],Ze|0))<<24>>24){var p=(b[0]=a[c],b[1]=a[c+1],f[0]),m=l+8|0,v=(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0]),n=v>n?v:n,p=p>k?p:k,k=m}else{p=k,k=l+8|0}Xt(l,p,n,re(jc(z,a[cp>>2],Ze|0))&255);Xi(e,-.5*p,.5*n);z=e|0;e=e+8|0;Yt(l,(b[0]=a[z>>2],b[1]=a[z+4>>2],f[0]),(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),15);e=(b[0]=a[c],b[1]=a[c+1],f[0])/72;f[0]=e;a[g]=b[0];a[g+1]=b[1];e=((b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])+1)/72;f[0]=e;a[i]=b[0];a[i+1]=b[1];a[x+28>>2]=l;h=d}function vH(c){var d,i,g,q=fa(44);g=q>>2;var e=a[a[a[c+24>>2]+8>>2]+4>>2];d=c|0;i=Xb(d,a[rh>>2],1.7976931348623157e+308,3e-4);var l=Xb(d,a[sh>>2],1.7976931348623157e+308,3e-4),k=i>2;1.7976931348623157e+308==k&1.7976931348623157e+308==l?(f[0]=.05,a[i]=b[0],a[i+1]=b[1],l=c+48|0,f[0]=.05,a[l>>2]=b[0],a[l+4>>2]=b[1],k=3.6):(f[0]=k,a[i]=b[0],a[i+1]=b[1],l=c+48|0,f[0]=k,a[l>>2]=b[0],a[l+4>>2]=b[1],k*=72);l=c+48|0;d=Zf(d,a[$o>>2],e);var e=fa(1>(d|0)?32:d<<5),h=.5*k,j=-h;f[0]=j;a[e>>2]=b[0];a[e+4>>2]=b[1];var p=e+8|0;f[0]=j;a[p>>2]=b[0];a[p+4>>2]=b[1];j=e+16|0;f[0]=h;a[j>>2]=b[0];a[j+4>>2]=b[1];j=e+24|0;f[0]=h;a[j>>2]=b[0];a[j+4>>2]=b[1];if(1<(d|0)){p=j=h;k=2;for(h=1;;){var m=p+4,j=j+4,p=(k<<4)+e|0;f[0]=-m;a[p>>2]=b[0];a[p+4>>2]=b[1];p=(k<<4)+e+8|0;f[0]=-j;a[p>>2]=b[0];a[p+4>>2]=b[1];var p=k|1,v=(p<<4)+e|0;f[0]=m;a[v>>2]=b[0];a[v+4>>2]=b[1];p=(p<<4)+e+8|0;f[0]=j;a[p>>2]=b[0];a[p+4>>2]=b[1];h=h+1|0;if((h|0)==(d|0)){break}else{p=m,k=k+2|0}}m*=2}else{m=k}a[g]=1;a[g+1]=d;a[g+2]=2;d=(q+12|0)>>2;a[d]=0;a[d+1]=0;a[d+2]=0;a[d+3]=0;a[d+4]=0;a[d+5]=0;a[g+10]=e;g=m/72;f[0]=g;a[l>>2]=b[0];a[l+4>>2]=b[1];f[0]=g;a[i]=b[0];a[i+1]=b[1];a[c+28>>2]=q}function Is(b){var c,i=a[Yk>>2];if(0==(i|0)){var g;return 0}for(var d=a[mo>>2],f=0;;){if((f|0)>=(d|0)){g=0;c=1901;break}var e=a[i+(f<<2)>>2],k=a[e>>2];if(m[k]<<24>>24==m[b]<<24>>24&&0==(ka(k,b)|0)){g=e;c=1902;break}f=f+1|0}if(1902==c||1901==c){return g}}function wH(b,c,i,g){var d=h;h+=40;i=xH(c,i,g);a[d+36>>2]=a[g+36>>2];Yi(c,a[g+24>>2],d,i,m[g+33|0]&255,0);c=d>>2;b>>=2;for(g=c+10;c>2]+32>>2]+152>>2]&3,k=c+32|0,n=c+40|0;yH(g,(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),l);k=d+32|0;d=d+40|0;yH(q,(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),l);d=m[i+33|0];l=d&255;if(15==d<<24>>24||0==d<<24>>24){return h=g,0}i=a[i+24>>2];if(0==(i|0)){var e=0==(a[a[e>>2]+152>>2]&1|0),i=c+96|0,i=.5*(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),d=-i,c=c+104|0,k=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),n=-k,c=e?n:d,j=e?d:n,p=e?k:i,n=e?i:k}else{c=i|0,c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),e=i+8|0,d=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),e=i+16|0,i=i+24|0,j=d,p=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),n=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])}var i=a[g>>2],e=a[g+4>>2],d=a[q>>2],q=a[q+4>>2],s=.5*(c+p)&-1,k=.5*(j+n)&-1;if(0==(l&1|0)){var v=0,j=0}else{v=i+s-d|0,j=e+(j&-1)-q|0,v=j*j+v*v|0,j=zH|0}if(0==(l&2|0)){p=v}else{var p=i+(p&-1)-d|0,t=e+k-q|0,p=t*t+p*p|0,p=(t=0==(j|0)|(p|0)<(v|0))?p:v,j=t?Zt|0:j}0==(l&4|0)?(n=p,s=j):(s=i+s-d|0,n=e+(n&-1)-q|0,n=n*n+s*s|0,n=(s=0==(j|0)|(n|0)<(p|0))?n:p,s=s?$t|0:j);if(0==(l&8|0)){return h=g,s}l=i+(c&-1)-d|0;c=e+k-q|0;l=0==(s|0)|(c*c+l*l|0)<(n|0)?au|0:s;h=g;return l}function Yi(c,d,i,g,q,e){var l=h;h+=128;var k=l+16,n=l+32,j=l+48,p=l+64,s=l+80,v=l+96,t=l+112;if(0==(d|0)){var u=0==(a[a[c+20>>2]+152>>2]&1|0),w=c+96|0,w=.5*(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0]),A=c+104|0,A=(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0]),B=u?w:A,w=u?A:w,A=0,u=-w,C=-B,P=B,T=B=0}else{var u=d|0,u=(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0]),w=d+8|0,w=(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0]),A=d+16|0,P=(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0]),A=d+24|0,y=(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0]);Xi(l,.5*(u+P),.5*(w+y));B=l|0;T=l+8|0;A=1;C=w;w=P;P=y;B=(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0]);T=(b[0]=a[T>>2],b[1]=a[T+4>>2],f[0])}0==(g|0)?(e=A,k=1,j=p=n=q=s=0,v=B,u=T):(y=m[g],0==y<<24>>24?(e=A,k=1,j=p=n=q=s=0,v=B,u=T):(y=y<<24>>24,g=g+1|0,119==(y|0)?(v=0==m[g]<<24>>24,j=v&1,e=v?1:A,k=j^1,s=v?q&8:0,q=0,n=v&1^1,p=v?3.141592653589793:0,v=v?u:B,u=T):115==(y|0)?(p=m[g]<<24>>24,119==(p|0)?(0==(e|0)?(v=u,u=C):(Zi(j,e,-2147483647,-2147483647),e=j|0,k=j+8|0,v=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),u=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])),e=1,k=0,s=q&9,n=q=0,p=-2.356194490192345,j=1):0==(p|0)?(0==(e|0)?(v=B,u=C):(Zi(k,e,-2147483647,B),e=k|0,k=k+8|0,v=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),u=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])),e=1,k=0,s=q&1,n=q=0,p=-1.5707963267948966,j=1):101==(p|0)?(0==(e|0)?(v=w,u=C):(Zi(n,e,-2147483647,2147483647),e=n|0,k=n+8|0,v=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),u=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])),e=1,k=0,s=q&3,n=q=0,p=-.7853981633974483,j=1):(e=A,k=1,q=s=0,n=1,j=p=0,v=B,u=T)):101==(y|0)?(v=0==m[g]<<24>>24,j=v&1,e=v?1:A,k=j^1,s=v?q&2:0,q=0,n=v&1^1,p=0,v=v?w:B,u=T):99==(y|0)?(e=A,k=1,j=p=n=q=s=0,v=B,u=T):95==(y|0)?(e=A,k=1,s=q,q=1,j=p=n=0,v=B,u=T):110==(y|0)?(k=m[g]<<24>>24,101==(k|0)?(0==(e|0)?(v=w,u=P):(Zi(s,e,2147483647,2147483647),e=s|0,k=s+8|0,v=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),u=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])),e=1,k=0,s=q&6,n=q=0,p=.7853981633974483,j=1):0==(k|0)?(0==(e|0)?(v=B,u=P):(Zi(p,e,2147483647,B),e=p|0,k=p+8|0,v=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),u=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])),e=1,k=0,s=q&4,n=q=0,p=1.5707963267948966,j=1):119==(k|0)?(0==(e|0)?(v=u,u=P):(Zi(v,e,2147483647,-2147483647),e=v|0,k=v+8|0,v=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),u=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])),e=1,k=0,s=q&12,n=q=0,p=2.356194490192345,j=1):(e=A,k=1,q=s=0,n=1,j=p=0,v=B,u=T)):(e=A,k=1,q=s=0,n=1,j=p=0,v=B,u=T)));c=(c+20|0)>>2;lG(t,v,u,90*(a[a[c]+152>>2]&3)|0);v=t|0;v=(b[0]=a[v>>2],b[1]=a[v+4>>2],f[0]);t=t+8|0;t=(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0]);m[i+33|0]=0==q<<24>>24?(2==(a[a[c]+152>>2]&3|0)?4==(s|0)?1:1==(s|0)?4:s:3==(a[a[c]+152>>2]&3|0)?4==(s|0)?2:8==(s|0)?1:2==(s|0)?4:1==(s|0)?8:s:1==(a[a[c]+152>>2]&3|0)?1==(s|0)?8:4==(s|0)?2:8==(s|0)?4:2==(s|0)?1:s:s)&255:s&255;a[i+24>>2]=d;d=i|0;f[0]=(0>v?v-.5:v+.5)&-1|0;a[d>>2]=b[0];a[d+4>>2]=b[1];d=i+8|0;f[0]=(0>t?t-.5:t+.5)&-1|0;a[d>>2]=b[0];a[d+4>>2]=b[1];d=i+16|0;a:{if(c=a[a[c]+152>>2]&3,2==(c|0)){c=-1*p}else{if(3==(c|0)){if(3.141592653589793==p){c=-1.5707963267948966}else{if(2.356194490192345==p){c=-.7853981633974483}else{if(1.5707963267948966==p){c=0}else{if(.7853981633974483==p){c=p}else{if(0==p){c=1.5707963267948966}else{if(-.7853981633974483==p){c=2.356194490192345}else{c=-1.5707963267948966==p?3.141592653589793:p;break a}}}}}}}else{c=1==(c|0)?p-1.5707963267948966:p}}}f[0]=c;a[d>>2]=b[0];a[d+4>>2]=b[1];if(0==v&0==t){return m[i+32|0]=-128,m[i+29|0]=j,m[i+28|0]=e,m[i+30|0]=k,m[i+31|0]=q,h=l,n}d=Cf(t,v)+4.71238898038469;m[i+32|0]=256*(6.283185307179586>d?d:d-6.283185307179586)/6.283185307179586&255;m[i+29|0]=j;m[i+28|0]=e;m[i+30|0]=k;m[i+31|0]=q;h=l;return n}function Xi(c,d,i){var g=c|0;f[0]=d;a[g>>2]=b[0];a[g+4>>2]=b[1];c=c+8|0;f[0]=i;a[c>>2]=b[0];a[c+4>>2]=b[1]}function yH(b,c,i,g){if(2==(g|0)){var d=-i,f=c,c=2026}else{if(0==(g|0)){d=i,f=c,c=2026}else{if(1==(g|0)){d=c,f=-i,c=2026}else{if(3==(g|0)){d=c,f=i,c=2026}else{var e=0,k=0,c=2027}}}}if(2026==c){if(0>f){var h=f-.5,j=d}else{e=f,k=d,c=2027}}2027==c&&(h=e+.5,j=k);a[b>>2]=h&-1;a[b+4>>2]=(0>j?j-.5:j+.5)&-1}function Zi(c,d,i,g){var q,e=h;h+=64;var l=a[d>>2],k=e+16|0;q=e>>2;a[q]=0;a[q+1]=0;a[q+2]=0;a[q+3]=0;f[0]=g/3;a[k>>2]=b[0];a[k+4>>2]=b[1];k=e+24|0;f[0]=i/3;a[k>>2]=b[0];a[k+4>>2]=b[1];k=e+32|0;f[0]=2*g/3;a[k>>2]=b[0];a[k+4>>2]=b[1];k=e+40|0;f[0]=2*i/3;a[k>>2]=b[0];a[k+4>>2]=b[1];k=e+48|0;f[0]=g;a[k>>2]=b[0];a[k+4>>2]=b[1];g=e+56|0;f[0]=i;a[g>>2]=b[0];a[g+4>>2]=b[1];vk(d,a[a[a[l+24>>2]+4>>2]+12>>2],e|0,1);c>>=2;a[c]=a[q];a[c+1]=a[q+1];a[c+2]=a[q+2];a[c+3]=a[q+3];h=e}function AH(b,c,i,g){var d,f=h;h+=52;var e,k=f+40;d=k>>2;var n=f+44;if(0==m[i]<<24>>24){c=bu>>2}else{g=0==(g|0)?cu|0:g;a[d]=15;if(0==m[a[c+120>>2]+82|0]<<24>>24){e=2047}else{var z=a[a[c+120>>2]+72>>2];2==m[z+4|0]<<24>>24?k=0:(z=tG(a[z>>2],i),0==(z|0)?k=0:(a[k>>2]=m[z+31|0]&255,k=z+40|0));0==(k|0)?e=2047:0!=(Yi(c,k,f,g,a[d],0)|0)&&la(0,du|0,(j=h,h+=12,a[j>>2]=a[c+12>>2],a[j+4>>2]=i,a[j+8>>2]=g,j))}2047==e&&((a[a[c+24>>2]+8>>2]|0)==(pl|0)?e=0:(a[n>>2]=c,a[n+4>>2]=0,e=n),0!=(Yi(c,0,f,i,a[d],e)|0)&&BH(a[c+12>>2],i));c=f>>2}b>>=2;for(i=c+10;c>2];q=l>>2;l=l+148|0;g=a[l>>2];var k=0==(g|0)?0!=(a[q+50]&1|0):1,n=a[d+28>>2];i=n>>2;if(0!=(n|0)){k&&0==(a[c+148>>2]&4|0)&&ad(c,g,a[q+42],a[q+46],a[q+38]);g=a[c+36>>2];var n=d+32|0,n=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0])+(a[i+1]|0),z=d+40|0,z=(b[0]=a[z>>2],b[1]=a[z+4>>2],f[0])+(a[i+2]|0);i=a[i];Va(g,DH|0,(j=h,h+=20,f[0]=n,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=z,a[j+8>>2]=b[0],a[j+12>>2]=b[1],a[j+16>>2]=i,j));n=d+120|0;g=(a[n>>2]+56|0)>>2;i=(d+32|0)>>2;a[g]=a[i];a[g+1]=a[i+1];a[g+2]=a[i+2];a[g+3]=a[i+3];Fg(c,10,a[n>>2]);k&&(0!=(a[c+148>>2]&4|0)&&ad(c,a[l>>2],a[q+42],a[q+46],a[q+38]),Se(c))}h=e}function BH(b,c){var i=h;la(0,EH|0,(j=h,h+=8,a[j>>2]=b,a[j+4>>2]=c,j));h=i}function FH(c,d,i){var g=h;h+=16;var q=a[c+4>>2],c=a[c>>2];Ui(g,d,i,90*(a[a[c+20>>2]+152>>2]&3)|0);d=g|0;d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]);i=g+8|0;i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);if(0==(q|0)){var q=a[c+28>>2],e=q+16|0,l=q+24|0,c=q+32|0,q=q+40|0}else{e=q,l=q+8|0,c=q+16|0,q=q+24|0}e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);q=(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]);if(e>d){return h=g,0}l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);if(d>(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])|l>i){return h=g,0}d=i<=q&1;h=g;return d}function GH(c,d,i,g,q){var e,l,k=h;h+=32;var n;if(0==m[d+28|0]<<24>>24){var j;h=k;return 0}for(var d=d|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),p=a[c+28>>2],s=a[p+48>>2],v=c+20|0,p=p+56|0,t=0;;){if((t|0)>=(s|0)){j=i;n=2114;break}var u=0==(a[a[v>>2]+152>>2]&1|0);l=a[a[p>>2]+(t<<2)>>2];if(u){var w=l+32|0,A=l+16|0}else{w=l+40|0,A=l+24|0}A=(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0])&-1|0;if(A<=d&&(e=(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0])&-1|0,d<=e)){break}t=t+1|0}if(2114==n){return h=k,j}u?(l=(c+32|0)>>2,n=(b[0]=a[l],b[1]=a[l+1],f[0])+A,j=g|0,f[0]=n,a[j>>2]=b[0],a[j+4>>2]=b[1],j=c+40|0,n=c+96|0,j=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0])-.5*(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),u=g+8|0,f[0]=j,a[u>>2]=b[0],a[u+4>>2]=b[1],e=(b[0]=a[l],b[1]=a[l+1],f[0])+e,l=g+16|0,f[0]=e,a[l>>2]=b[0],a[l+4>>2]=b[1],e=n):(e=c+32|0,n=c+40|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),n=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),j=h,l=(l+16|0)>>2,A=h,h+=32,a[A>>2]=a[l],a[A+4>>2]=a[l+1],a[A+8>>2]=a[l+2],a[A+12>>2]=a[l+3],a[A+16>>2]=a[l+4],a[A+20>>2]=a[l+5],a[A+24>>2]=a[l+6],a[A+28>>2]=a[l+7],l=A+24|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),u=A+16|0,u=(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0]),d=A+8|0,s=A|0,A=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0])+e,d=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0])+n,s=k|0,f[0]=A,a[s>>2]=b[0],a[s+4>>2]=b[1],A=k+8|0,f[0]=d,a[A>>2]=b[0],a[A+4>>2]=b[1],A=k+16|0,f[0]=l+e,a[A>>2]=b[0],a[A+4>>2]=b[1],e=k+24|0,f[0]=u+n,a[e>>2]=b[0],a[e+4>>2]=b[1],h=j,l=g>>2,e=k>>2,a[l]=a[e],a[l+1]=a[e+1],a[l+2]=a[e+2],a[l+3]=a[e+3],a[l+4]=a[e+4],a[l+5]=a[e+5],a[l+6]=a[e+6],a[l+7]=a[e+7],e=c+96|0);c=c+40|0;c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])+.5*(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);g=g+24|0;f[0]=c;a[g>>2]=b[0];a[g+4>>2]=b[1];a[q>>2]=1;h=k;return i}function HH(c,d){var i,g,q,e,l,k=h;h+=96;var n=k+32,j=a[c+16>>2];l=j>>2;var j=j+148|0,p=a[j>>2],s=0==(p|0)?0!=(a[l+50]&1|0):1,v=a[d+28>>2];i=(v+16|0)>>2;g=k>>2;a[g]=a[i];a[g+1]=a[i+1];a[g+2]=a[i+2];a[g+3]=a[i+3];a[g+4]=a[i+4];a[g+5]=a[i+5];a[g+6]=a[i+6];a[g+7]=a[i+7];i=d+32|0;q=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);i=(k|0)>>2;var t=(b[0]=a[i],b[1]=a[i+1],f[0])+q;f[0]=t;a[i]=b[0];a[i+1]=b[1];i=d+40|0;t=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);i=(k+8|0)>>2;e=(b[0]=a[i],b[1]=a[i+1],f[0])+t;f[0]=e;a[i]=b[0];a[i+1]=b[1];i=k+16|0;e=(i|0)>>2;q=(b[0]=a[e],b[1]=a[e+1],f[0])+q;f[0]=q;a[e]=b[0];a[e+1]=b[1];q=(k+24|0)>>2;t=(b[0]=a[q],b[1]=a[q+1],f[0])+t;f[0]=t;a[q]=b[0];a[q+1]=b[1];s&&0==(a[c+148>>2]&4|0)&&ad(c,p,a[l+42],a[l+46],a[l+38]);p=IH(c,d);ep(c,d);0!=(p&1|0)&&$b(c,Dh(d,qe|0));q=a[a[d+24>>2]>>2];p=77==m[q]<<24>>24?0==(ka(q,eu|0)|0)?p|2:p:p;0==(p&998|0)?mh(c,k,p&1):(q=n>>2,a[q]=a[g],a[q+1]=a[g+1],a[q+2]=a[g+2],a[q+3]=a[g+3],q=n+32|0,g=q>>2,i>>=2,a[g]=a[i],a[g+1]=a[i+1],a[g+2]=a[i+2],a[g+3]=a[i+3],g=q|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),i=n+16|0,f[0]=g,a[i>>2]=b[0],a[i+4>>2]=b[1],g=n+8|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),i=n+24|0,f[0]=g,a[i>>2]=b[0],a[i+4>>2]=b[1],g=n|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),i=n+48|0,f[0]=g,a[i>>2]=b[0],a[i+4>>2]=b[1],g=n+40|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),i=n+56|0,f[0]=g,a[i>>2]=b[0],a[i+4>>2]=b[1],Ii(c,Dh(d,qe|0),fu(d),n|0,4,p,p&1));gu(c,d,v);s&&(0!=(a[c+148>>2]&4|0)&&ad(c,a[j>>2],a[l+42],a[l+46],a[l+38]),Se(c));h=k}function IH(b,c){var i=h;h+=4;var g=hu(c,i);0!=(g|0)&&Ke(b,g);g=a[iu>>2];if(0!=(g|0)){var d=c|0,f=mb(d,a[g+8>>2]);0!=(f|0)&&0!=m[f]<<24>>24&&Zr(b,Xb(d,g,1,0))}h=i;return a[i>>2]}function ep(b,c){var i=Aa(c|0,a[Eh>>2],Y|0);0==m[i]<<24>>24?Pa(b,Ac|0):Pa(b,i)}function fp(c,d,i,g,q){var e=c|0;f[0]=d+g;a[e>>2]=b[0];a[e+4>>2]=b[1];c=c+8|0;f[0]=i+q;a[c>>2]=b[0];a[c+4>>2]=b[1]}function gu(c,d,i){var g,q,e,l,k,n,j,p,s,v,t=h;h+=96;var u=t+32;s=t+48;var w=t+64,A=t+80,B=i+52|0;v=a[B>>2];0==(v|0)?(B=d+32|0,s=d+40|0):(l=i+16|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),k=i+24|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),n=i+32|0,j=i+40|0,n=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),j=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]),p=u|0,f[0]=.5*(l+n),a[p>>2]=b[0],a[p+4>>2]=b[1],l=u+8|0,f[0]=.5*(k+j),a[l>>2]=b[0],a[l+4>>2]=b[1],l=u|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),u=u+8|0,n=(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0]),k=d+32|0,u=d+40|0,fp(s,l,n,(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0])),v=(v+56|0)>>2,s>>=2,a[v]=a[s],a[v+1]=a[s+1],a[v+2]=a[s+2],a[v+3]=a[s+3],Fg(c,10,a[B>>2]),ep(c,d),B=k,s=u);B=(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0]);s=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]);v=i+48|0;if(0<(a[v>>2]|0)){u=i+64|0;e=t+16|0;var C=i+56|0;p=e>>2;j=(t|0)>>2;n=(t+24|0)>>2;k=(t+8|0)>>2;var P=t|0;l=t>>2;var i=w>>2,T=e|0;e=A>>2;for(var y=t+16|0,D=0;;){if(0<(D|0)){q=((D<<2)+a[C>>2]|0)>>2;g=a[q];if(0==m[u]<<24>>24){g=(g+32|0)>>2;a[p]=a[g];a[p+1]=a[g+1];a[p+2]=a[g+2];a[p+3]=a[g+3];q=a[q]+16|0;q=(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]);f[0]=q;a[j]=b[0];a[j+1]=b[1];var M=(b[0]=a[n],b[1]=a[n+1],f[0]);f[0]=M;a[k]=b[0];a[k+1]=b[1];g=M;var X=(b[0]=a[T>>2],b[1]=a[T+4>>2],f[0])}else{g=(g+16|0)>>2,a[l]=a[g],a[l+1]=a[g+1],a[l+2]=a[g+2],a[l+3]=a[g+3],X=(b[0]=a[j],b[1]=a[j+1],f[0]),f[0]=X,a[y>>2]=b[0],a[y+4>>2]=b[1],q=a[q]+40|0,M=(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]),f[0]=M,a[n]=b[0],a[n+1]=b[1],q=X,g=(b[0]=a[k],b[1]=a[k+1],f[0])}fp(w,q,g,B,s);a[l]=a[i];a[l+1]=a[i+1];a[l+2]=a[i+2];a[l+3]=a[i+3];fp(A,X,M,B,s);a[p]=a[e];a[p+1]=a[e+1];a[p+2]=a[e+2];a[p+3]=a[e+3];Gd(c,P,2)}gu(c,d,a[a[C>>2]+(D<<2)>>2]);D=D+1|0;if((D|0)>=(a[v>>2]|0)){break}}}h=t}function fu(b){b=Aa(b|0,a[Eh>>2],Y|0);return 0==m[b]<<24>>24?Ac|0:b}function Dh(b,c){var i=b|0,g=Aa(i,a[gp>>2],Y|0);return 0==m[g]<<24>>24?(i=Aa(i,a[Eh>>2],Y|0),0==m[i]<<24>>24?c:i):g}function hu(b,c){var i,g=Aa(b|0,a[Ki>>2],Y|0),d=0==m[g]<<24>>24;a:do{if(d){var f=0,e=0}else{nh(g);for(var k=Cc|0,h=0;;){for(var j=k;;){var p=a[j>>2];if(0==(p|0)){f=h;e=Cc|0;break a}var s=m[p];if(114==s<<24>>24){if(0==(ka(p,Vr|0)|0)){var v=j;i=2181;break}}else{if(105==s<<24>>24){if(0==(ka(p,qh|0)|0)){i=2185;break}}else{if(102==s<<24>>24){if(0==(ka(p,Hi|0)|0)){i=2179;break}}else{if(100==s<<24>>24&&0==(ka(p,JH|0)|0)){var t=j;i=2183;break}}}}j=j+4|0}if(2181==i){for(;!(i=0,k=v+4|0,p=a[k>>2],a[v>>2]=p,0==(p|0));){v=k,i=2181}k=j;h|=2}else{if(2179==i){i=0,k=j+4|0,h|=1}else{if(2185==i){i=0,k=j+4|0,h|=16}else{if(2183==i){for(;!(i=0,k=t+4|0,p=a[k>>2],a[t>>2]=p,0==(p|0));){t=k,i=2183}k=j;h|=4}}}}}}}while(0);i=a[a[b+24>>2]+8>>2];if(0==(i|0)){return a[c>>2]=f,e}f|=a[i+36>>2];a[c>>2]=f;return e}function KH(b,c){var i,g=a[b+60>>2];if(0!=(g|0)&&m[g]<<24>>24==m[c]<<24>>24&&0==(ka(g,c)|0)){var d;return b}for(var g=b+56|0,f=a[b+48>>2],e=0;;){if((e|0)>=(f|0)){d=0;i=2201;break}var k=KH(a[a[g>>2]+(e<<2)>>2],c);if(0==(k|0)){e=e+1|0}else{d=k;i=2199;break}}if(2199==i||2201==i){return d}}function ju(b){var c=b+48|0,i=0<(a[c>>2]|0),g=b+56|0;a:do{if(i){for(var d=0;;){if(ju(a[a[g>>2]+(d<<2)>>2]),d=d+1|0,(d|0)>=(a[c>>2]|0)){break a}}}}while(0);G(a[b+60>>2]);vh(a[b+52>>2]);G(a[g>>2]);G(b)}function LH(c,d,i){var g=h;h+=16;c=a[c>>2];Ui(g,d,i,90*(a[a[c+20>>2]+152>>2]&3)|0);d=g|0;d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]);i=g+8|0;i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);if((c|0)!=(a[ku>>2]|0)){var q=a[c+28>>2],e=(a[q+4>>2]<<1)-2|0,q=((0>(e|0)?1:e|1)<<4)+a[q+40>>2]|0,q=(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]);f[0]=q;a[gg>>2]=b[0];a[gg+4>>2]=b[1];a[ku>>2]=c}if(be(d)>(b[0]=a[gg>>2],b[1]=a[gg+4>>2],f[0])||be(i)>(b[0]=a[gg>>2],b[1]=a[gg+4>>2],f[0])){return h=g,0}c=pi(d,i)<=(b[0]=a[gg>>2],b[1]=a[gg+4>>2],f[0])&1;h=g;return c}function MH(c,d){var i,g,q=h;h+=4;var e,l=a[c+16>>2];g=l>>2;var l=l+148|0,k=a[l>>2];if(0==(k|0)){if(0==(a[g+50]&1|0)){var n=0}else{e=2221}}else{e=2221}2221==e&&(0==(a[c+148>>2]&4|0)&&ad(c,k,a[g+42],a[g+46],a[g+38]),n=1);i=a[d+28>>2]>>2;var k=a[i+10],j=a[i+2];i=a[i+1];if((a[lu>>2]|0)<(j|0)){var p=j+2|0;a[lu>>2]=p;var s=a[ql>>2],p=0==(s|0)?Cb(p<<4):wb(s,p<<4);a[ql>>2]=p}hu(d,q);0==(a[q>>2]&16|0)?Ke(c,rl+4|0):Ke(c,rl|0);p=m[d+133|0]&255;0==(p&1|0)?0!=(p&2|0)?(p=d|0,Pa(c,Aa(p,a[mu>>2],Dk|0)),p=Aa(p,a[nu>>2],Ek|0),$b(c,p)):0!=(p&8|0)?(p=d|0,Pa(c,Aa(p,a[ou>>2],Fk|0)),p=Aa(p,a[pu>>2],Gk|0),$b(c,p)):0==(p&4|0)?(p=Dh(d,Ac|0),$b(c,p),ep(c,d)):(p=d|0,Pa(c,Aa(p,a[qu>>2],Hk|0)),p=Aa(p,a[ru>>2],Ik|0),$b(c,p)):(p=d|0,Pa(c,Aa(p,a[su>>2],Jk|0)),p=Aa(p,a[tu>>2],Kk|0),$b(c,p));if(0==(i|0)){if(0==m[p]<<24>>24){var v=1}else{Pa(c,p),v=1}e=2244}else{0<(i|0)&&(v=i,e=2244)}a:do{if(2244==e){i=0<(j|0);for(var p=d+32|0,s=d+40|0,t=0,u=1;;){b:do{if(i){for(var w=t*j|0,A=a[ql>>2],B=0;;){var C=B+w|0,P=(C<<4)+k|0,P=(b[0]=a[P>>2],b[1]=a[P+4>>2],f[0]),C=(C<<4)+k+8|0,C=(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0]),P=P+(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),T=(B<<4)+A|0;f[0]=P;a[T>>2]=b[0];a[T+4>>2]=b[1];P=C+(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]);C=(B<<4)+A+8|0;f[0]=P;a[C>>2]=b[0];a[C+4>>2]=b[1];B=B+1|0;if((B|0)==(j|0)){var y=A;break b}}}else{y=a[ql>>2]}}while(0);Qk(c,y,u);t=t+1|0;if((t|0)==(v|0)){break a}else{u=0}}}}while(0);n&&(0!=(a[c+148>>2]&4|0)&&ad(c,a[l>>2],a[g+42],a[g+46],a[g+38]),Se(c));h=q}function NH(c,d,i){var g,q=h;h+=16;var e;g=a[c+4>>2];var c=a[c>>2],l=c+20|0;Ui(q,d,i,90*(a[a[l>>2]+152>>2]&3)|0);d=q|0;d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]);i=q+8|0;i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);if(0!=(g|0)){var k=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);e=g+24|0;e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);k>d?e=0:(k=g+8|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),c=g+16|0,e=d>(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])|k>i?0:i<=e);h=q;return e&1}if((c|0)==(a[uu>>2]|0)){c=(b[0]=a[sl>>2],b[1]=a[sl+4>>2],f[0]),g=(b[0]=a[tl>>2],b[1]=a[tl+4>>2],f[0])}else{g=a[c+28>>2]>>2;a[vu>>2]=a[g+10];var n=a[g+2];a[wu>>2]=n;var j=0==(a[a[l>>2]+152>>2]&1|0),l=c+104|0,p=c+112|0,p=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])+(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),l=c+96|0,m=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),l=j?m:p,m=j?p:m,j=c+48|0,j=72*(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]),p=0>j,m=((p?j-.5:j+.5)&-1|0)/(0==m?1:m);f[0]=m;a[sl>>2]=b[0];a[sl+4>>2]=b[1];var v=c+56|0,v=72*(b[0]=a[v>>2],b[1]=a[v+4>>2],f[0]),t=0>v,l=((t?v-.5:v+.5)&-1|0)/(0==l?1:l);f[0]=l;a[tl>>2]=b[0];a[tl+4>>2]=b[1];f[0]=.5*((p?j-.5:j+.5)&-1|0);a[Fh>>2]=b[0];a[Fh+4>>2]=b[1];f[0]=.5*((t?v-.5:v+.5)&-1|0);a[ul>>2]=b[0];a[ul+4>>2]=b[1];g=(a[g+1]-1)*n|0;a[xu>>2]=0>(g|0)?0:g;a[uu>>2]=c;c=m;g=l}d*=c;i*=g;if(be(d)>(b[0]=a[Fh>>2],b[1]=a[Fh+4>>2],f[0])){return h=q,0}c=be(i);g=(b[0]=a[ul>>2],b[1]=a[ul+4>>2],f[0]);if(c>g){return h=q,0}c=a[wu>>2];if(3>(c|0)){return e=1>pi(d/(b[0]=a[Fh>>2],b[1]=a[Fh+4>>2],f[0]),i/g)&1,h=q,e}m=(a[hp>>2]|0)%(c|0);j=(m+1|0)%(c|0);g=a[xu>>2];var l=g+m|0,n=a[vu>>2],p=(l<<4)+n|0,p=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),l=(l<<4)+n+8|0,v=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),l=g+j|0,t=(l<<4)+n|0,t=(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0]),l=(l<<4)+n+8|0,u=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);if(0==(ip(d,i,0,0,p,v,t,u)|0)){return h=q,0}if(l=0!=(ip(d,i,p,v,t,u,0,0)|0)){if(0==(ip(d,i,t,u,0,0,p,v)|0)){p=1}else{return h=q,1}}else{p=1}for(;;){if((p|0)>=(c|0)){e=2290;break}l?(k=j,j=(j+1|0)%(c|0)):(k=(m-1+c|0)%(c|0),j=m);var u=g+k|0,v=g+j|0,t=(u<<4)+n|0,t=(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0]),u=(u<<4)+n+8|0,u=(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0]),w=(v<<4)+n|0,v=(v<<4)+n+8|0;if(0==(ip(d,i,0,0,t,u,(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0]),(b[0]=a[v>>2],b[1]=a[v+4>>2],f[0]))|0)){e=2289;break}else{p=p+1|0,m=k}}if(2290==e){return a[hp>>2]=m,h=q,1}if(2289==e){return a[hp>>2]=k,h=q,0}}function ip(a,b,i,c,d,f,e,k){k=-(k-f);e-=d;d=k*d+e*f;return(0<=k*a+e*b-d^0<=k*i+e*c-d)&1^1}function OH(c,d){var i,g,q,e,l,k=a[c+16>>2];e=k>>2;var k=k+148|0,h=a[k>>2];if(0==(h|0)){if(0==(a[e+50]&1|0)){var j=0}else{l=2303}}else{l=2303}2303==l&&(0==(a[c+148>>2]&4|0)&&ad(c,h,a[e+42],a[e+46],a[e+38]),j=1);q=a[d+28>>2]>>2;var h=a[q+10],p=a[q+2],s=a[q+1];if((a[yu>>2]|0)<(p|0)){q=p+5|0;a[yu>>2]=q;var v=a[hg>>2];q=0==(v|0)?Cb(q<<4):wb(v,q<<4);a[hg>>2]=q}q=d+120|0;v=d+32|0;g=(a[q>>2]+56|0)>>2;i=v>>2;a[g]=a[i];a[g+1]=a[i+1];a[g+2]=a[i+2];a[g+3]=a[i+3];i=d+104|0;g=d+112|0;i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])+(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);g=d+48|0;g=72*(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);i/=(0>g?g-.5:g+.5)&-1|0;g=d+96|0;g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);var t=d+56|0,t=72*(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0]);g/=(0>t?t-.5:t+.5)&-1|0;var t=IH(c,d),u=m[d+133|0]&255;0==(u&1|0)?0!=(u&2|0)?(u=d|0,Pa(c,Aa(u,a[mu>>2],Dk|0)),$b(c,Aa(u,a[nu>>2],Ek|0)),u=1):0!=(u&8|0)?(u=d|0,Pa(c,Aa(u,a[ou>>2],Fk|0)),$b(c,Aa(u,a[pu>>2],Gk|0)),u=1):0!=(u&4|0)?(u=d|0,Pa(c,Aa(u,a[qu>>2],Hk|0)),$b(c,Aa(u,a[ru>>2],Ik|0)),u=1):(0==(t&1|0)?u=0:($b(c,Dh(d,qe|0)),u=1),ep(c,d)):(u=d|0,Pa(c,Aa(u,a[su>>2],Jk|0)),$b(c,Aa(u,a[tu>>2],Kk|0)),u=1);var w=d+24|0,A=a[w>>2],B=m[A+12|0];if(0==B<<24>>24){var C=0;l=2331}else{if(l=a[A>>2],99==m[l]<<24>>24){C=0!=(ka(l,th|0)|0),l=2331}else{var P=l,T=1,y=0==u<<24>>24,D=s;l=2339}}if(2331==l){var M=0==u<<24>>24;0!=(s|0)|M|C?w=M:(C=Dh(d,qe|0),0==m[C]<<24>>24?(s=1,C=w=0):(Pa(c,C),B=a[w>>2],s=1,C=w=0,A=B,B=m[B+12|0]));if(0==B<<24>>24){if(B=V(d|0,ap|0),0==(B|0)){var X=u,O=s}else{P=B,T=C,y=w,D=s,l=2339}}else{P=a[A>>2],P=99!=m[P]<<24>>24?P:0!=(ka(P,th|0)|0)?P:V(d|0,Xk|0),T=C,y=w,D=s,l=2339}}if(2339==l){X=0<(p|0);a:do{if(X){O=v|0;C=a[hg>>2];s=d+40|0;for(l=0;;){if(B=(l<<4)+h|0,A=(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0]),B=(l<<4)+h+8|0,B=(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0]),A=A*i+(b[0]=a[O>>2],b[1]=a[O+4>>2],f[0]),w=(l<<4)+C|0,f[0]=A,a[w>>2]=b[0],a[w+4>>2]=b[1],B=B*g+(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]),A=(l<<4)+C+8|0,f[0]=B,a[A>>2]=b[0],a[A+4>>2]=b[1],l=l+1|0,(l|0)==(p|0)){break a}}}}while(0);X=u&255;y|T||(3>(p|0)?(Qk(c,a[hg>>2],u),0!=(t&4|0)&&PH(c,d)):(O=a[hg>>2],0==(t&6|0)?zc(c,O,p,u):Ii(c,Dh(d,qe|0),fu(d),O,p,t,X)));ut(c,P,a[hg>>2],p,u,jc(d|0,a[zu>>2],Ze|0));X=0;O=D}P=0<(O|0);a:do{if(P){D=0<(p|0);T=3>(p|0);y=0==(t&4|0);u=0==(t&998|0);C=v|0;s=d+40|0;l=0;for(B=X;;){b:do{if(D){A=l*p|0;w=a[hg>>2];for(M=0;;){var F=M+A|0,Da=(F<<4)+h|0,Da=(b[0]=a[Da>>2],b[1]=a[Da+4>>2],f[0]),F=(F<<4)+h+8|0,F=(b[0]=a[F>>2],b[1]=a[F+4>>2],f[0]),Da=Da*i+(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0]),ia=(M<<4)+w|0;f[0]=Da;a[ia>>2]=b[0];a[ia+4>>2]=b[1];Da=F*g+(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]);F=(M<<4)+w+8|0;f[0]=Da;a[F>>2]=b[0];a[F+4>>2]=b[1];M=M+1|0;if((M|0)==(p|0)){var E=w;break b}}}else{E=a[hg>>2]}}while(0);T?(Qk(c,E,B),y||PH(c,d)):u?zc(c,E,p,B):Ii(c,Dh(d,qe|0),fu(d),E,p,t,B&255);l=l+1|0;if((l|0)==(O|0)){break a}else{B=0}}}}while(0);Fg(c,10,a[q>>2]);j&&(0!=(a[c+148>>2]&4|0)&&ad(c,a[k>>2],a[e+42],a[e+46],a[e+38]),Se(c))}function PH(c,d){var i,g,q=h;h+=48;i=q+32;var e=d+96|0,e=.375*(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),l=d+112|0,k=.6614*(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),l=q|0;g=d+32|0;var n=d+40|0;fp(i,k,e,(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]));g=q>>2;i>>=2;a[g]=a[i];a[g+1]=a[i+1];a[g+2]=a[i+2];a[g+3]=a[i+3];g=(q+8|0)>>2;n=(b[0]=a[g],b[1]=a[g+1],f[0]);i=(q+24|0)>>2;f[0]=n;a[i]=b[0];a[i+1]=b[1];n=q|0;k=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0])-2*k;n=q+16|0;f[0]=k;a[n>>2]=b[0];a[n+4>>2]=b[1];Gd(c,l,2);e=(b[0]=a[g],b[1]=a[g+1],f[0])-2*e;f[0]=e;a[g]=b[0];a[g+1]=b[1];f[0]=e;a[i]=b[0];a[i+1]=b[1];Gd(c,l,2);h=q}function nH(a,b){var i=a/b&-1;return(((i|0)*b+1e-5>2],h=0,j=a[Fd>>2],p=1;a:for(;;){var s=m[j];do{if(0==s<<24>>24){var v=p;break a}else{if(92==s<<24>>24){var t=j+1|0,u=m[t];if(92==u<<24>>24||123==u<<24>>24||125==u<<24>>24||124==u<<24>>24){var w=h,A=t,B=p}else{var C=t,P=u;e=2375}}else{C=j,P=s,e=2375}}}while(0);if(2375==e){e=0;if(124==P<<24>>24){var T=h,y=(0==(h|0)&1)+p|0}else{T=125==P<<24>>24?h-1|0:123==P<<24>>24?h+1|0:h,y=p}if(0>(T|0)){v=y;break}else{w=T,A=C,B=y}}h=w;j=A+1|0;p=B}q=(l+56|0)>>2;a[q]=fa(v<<2);m[l+64|0]=d&255;var D=0==(d|0)&1,M=k+82|0,X=g+1|0,O=0==(i|0),F=c|0,Da=k+16|0,ia=k+4|0,E=k+8|0,G=0,J=0,I=0,H=0,K=0,L=g,N=0,ca=g,R=0;a:for(;;){for(var S=G,zb=J,ja=H,aa=K,da=L,ea=N,xa=ca,Q=R;;){var U=S,ha=zb,ga=ja,W=da,V=ea,Y=xa,tb=Q,ya=0;b:for(;;){var $=U,wa=ha,Ab=ga,Fa=W,Ga=V,la=Y,ta=tb;c:for(;;){var Ka=$,za=Ab,ma=Ga,pa=ta;d:for(;;){for(var Z=Ka,Ha=za;;){if(I){e=2448;break a}var Ra=a[Fd>>2],ka=m[Ra],sa=ka<<24>>24;if(60==(sa|0)){break}else{if(62==(sa|0)){e=2393;break d}else{if(92==(sa|0)){e=2422;break d}else{if(125==(sa|0)||124==(sa|0)||0==(sa|0)){break b}else{if(123!=(sa|0)){var La=wa,ba=Ha,Ya=Fa,ra=Ra;break d}}}}}var Za=Ra+1|0;a[Fd>>2]=Za;if(0!=(Ha|0)){e=2402;break a}if(0==m[Za]<<24>>24){e=2402;break a}var qa=dp(c,D,0,g);a[a[q]+(Z<<2)>>2]=qa;if(0==(qa|0)){e=2404;break a}else{Z=Z+1|0,Ha=4}}if(0!=(Ha&6|0)){e=2390;break a}if(0!=m[M]<<24>>24){La=wa;ba=Ha;Ya=Fa;ra=Ra;break}a[Fd>>2]=Ra+1|0;Ka=Z;za=Ha|18;pa=ma=g}d:do{if(2393==e){if(e=0,0==m[M]<<24>>24){break c}else{La=wa,ba=Ha,Ya=Fa,ra=Ra}}else{if(2422==e){e=0;var ab=Ra+1|0,$a=m[ab];if(0==$a<<24>>24){La=wa,ba=Ha,Ya=Fa,ra=Ra}else{if(123==$a<<24>>24||125==$a<<24>>24||124==$a<<24>>24||60==$a<<24>>24||62==$a<<24>>24){a[Fd>>2]=ab,La=wa,ba=Ha,Ya=Fa}else{32==$a<<24>>24&&(e=2424);do{if(2424==e&&(e=0,0==m[M]<<24>>24)){a[Fd>>2]=ab;La=1;ba=Ha;Ya=Fa;ra=ab;break d}}while(0);m[Fa]=92;a[Fd>>2]=ab;La=wa;ba=Ha|9;Ya=Fa+1|0}ra=ab}}}}while(0);if(0!=(ba&4|0)&&32!=m[ra]<<24>>24){e=2429;break a}var jb=0==(ba&24|0)?32==m[ra]<<24>>24?ba:ba|9:ba;if(0==(jb&8|0)){if(0==(jb&16|0)){var Ca=Ya,Ia=ma,eb=la,ub=pa}else{var Sa=m[ra],na=0==(La|0);if(32==Sa<<24>>24&na){if((ma|0)==(g|0)){var ua=ma}else{32==m[ma-1|0]<<24>>24?ua=ma:e=2443}}else{e=2443}2443==e&&(e=0,m[ma]=Sa,ua=ma+1|0);na?(Ca=Ya,Ia=ua,eb=la,ub=pa):(Ca=Ya,Ia=ua,eb=la,ub=ua-1|0)}}else{var Oa=m[ra],Wa=0==(La|0);if(32==Oa<<24>>24&Wa){if(32!=m[Ya-1|0]<<24>>24){e=2436}else{if(0==m[M]<<24>>24){var pb=Ya}else{e=2436}}}else{e=2436}2436==e&&(e=0,m[Ya]=Oa,pb=Ya+1|0);Wa?(Ca=pb,Ia=ma,eb=la):(Ca=pb,Ia=ma,eb=pb-1|0);ub=pa}var ob=ra+1|0;a[Fd>>2]=ob;var bb=m[ob];if(0>bb<<24>>24){for(var qb=Ca,oa=ob,kb=bb;;){var Ba=oa+1|0;a[Fd>>2]=Ba;var vb=qb+1|0;m[qb]=kb;var xb=m[Ba];if(0>xb<<24>>24){qb=vb,oa=Ba,kb=xb}else{$=Z;wa=La;Ab=jb;Fa=vb;Ga=Ia;la=eb;ta=ub;continue c}}}else{$=Z,wa=La,Ab=jb,Fa=Ca,Ga=Ia,la=eb,ta=ub}}if(0==(Ha&16|0)){e=2395;break a}if(ma>>>0>X>>>0){var hd=ma-1|0,nb=(hd|0)==(pa|0)?ma:32==m[hd]<<24>>24?hd:ma}else{nb=ma}m[nb]=0;var rb=Hb(g);a[Fd>>2]=a[Fd>>2]+1|0;U=Z;ha=wa;ga=Ha&-17;W=Fa;V=nb;Y=la;tb=pa;ya=rb}if(0==ka<<24>>24&O){e=2407;break a}if(0!=(Ha&16|0)){e=2407;break a}if(0==(Ha&4|0)){var lb=fa(68);a[a[q]+(Z<<2)>>2]=lb;var Ta=Z+1|0,cb=lb}else{Ta=Z,cb=aa}0!=(ya|0)&&(a[cb+60>>2]=ya);if(0==(Ha&5|0)){m[Fa]=32;var fb=Ha|1,Ua=Fa+1|0}else{fb=Ha,Ua=Fa}if(0==(fb&1|0)){var sb=Ua,Na=la}else{if(Ua>>>0>X>>>0){var Fb=Ua-1|0,Db=(Fb|0)==(la|0)?Ua:32==m[Fb]<<24>>24?Fb:Ua}else{Db=Ua}m[Db]=0;var Ob=Hb(g);a[cb+52>>2]=ag(F,Ob,0!=m[M]<<24>>24?2:0,(b[0]=a[Da>>2],b[1]=a[Da+4>>2],f[0]),a[ia>>2],a[E>>2]);m[cb+64|0]=1;Na=sb=g}var Eb=a[Fd>>2],va=m[Eb];if(0==va<<24>>24){G=Ta;J=wa;I=1;H=fb;K=cb;L=sb;N=ma;ca=Na;R=pa;continue a}else{if(125==va<<24>>24){e=2420;break a}}a[Fd>>2]=Eb+1|0;S=Ta;zb=wa;ja=0;aa=cb;da=sb;ea=ma;xa=Na;Q=pa}}if(2390==e){$i(l,ya);var Bb=0;return Bb}if(2407==e||2395==e||2404==e||2429==e){return $i(l,ya),Bb=0}if(2448==e){return a[l+48>>2]=Z,Bb=l}if(2420==e){return a[Fd>>2]=Eb+1|0,a[l+48>>2]=Ta,Bb=l}if(2402==e){return $i(l,ya),Bb=0}}function Wt(c,d,i){var g=h;h+=32;var q=g+8,e=g+16,l=a[i+52>>2],k=0==(l|0);a:do{if(k){var n=i+48|0;if(0<(a[n>>2]|0)){for(var z=i+56|0,p=e|0,s=e+8|0,v=i+64|0,t=0,u=0,w=0;;){Wt(e,d,a[a[z>>2]+(w<<2)>>2]);var A=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),B=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]);0==m[v]<<24>>24?(u=u>A?u:A,t+=B):(u+=A,t=t>B?t:B);w=w+1|0;if((w|0)>=(a[n>>2]|0)){C=u;P=t;break a}}}else{var C=0,P=0}}else{C=l+24|0,n=(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0]),C=l+32|0,P=(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0]),0>2]=g,a[j+4>>2]=q,j)),0<(C|0)?(z=72*(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),n+=((0>z?z-.5:z+.5)&-1)<<1|0,z=72*(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]),z=(p=0<=z)?z+.5:z-.5,C=n,P+=(z&-1)<<1|0):(C=n+16,P+=8))):C=n}}while(0);d=i|0;f[0]=C;a[d>>2]=b[0];a[d+4>>2]=b[1];i=i+8|0;f[0]=P;a[i>>2]=b[0];a[i+4>>2]=b[1];i=c|0;f[0]=C;a[i>>2]=b[0];a[i+4>>2]=b[1];c=c+8|0;f[0]=P;a[c>>2]=b[0];a[c+4>>2]=b[1];h=g}function Xt(c,d,i,g){var q,e,l=h;h+=32;var k=l+16;e=(c|0)>>2;var n=d-(b[0]=a[e],b[1]=a[e+1],f[0]);q=(c+8|0)>>2;var j=i-(b[0]=a[q],b[1]=a[q+1],f[0]);f[0]=d;a[e]=b[0];a[e+1]=b[1];f[0]=i;a[q]=b[0];a[q+1]=b[1];q=c+52|0;e=a[q>>2];if(0!=(e|0)&0==(g|0)){e=(e+40|0)>>2;var p=(b[0]=a[e],b[1]=a[e+1],f[0])+n;f[0]=p;a[e]=b[0];a[e+1]=b[1];q=(a[q>>2]+48|0)>>2;e=(b[0]=a[q],b[1]=a[q+1],f[0])+j;f[0]=e;a[q]=b[0];a[q+1]=b[1]}q=c+48|0;p=a[q>>2];if(0!=(p|0)){e=c+64|0;var s=m[e],n=(0==s<<24>>24?j:n)/(p|0);if(0<(p|0)){for(var c=c+56|0,j=k|0,p=k+8|0,v=l|0,t=l+8|0,u=0,w=s;;){var s=a[a[c>>2]+(u<<2)>>2],A=u+1|0,u=(A*n&-1)-(u*n&-1)|0;0==w<<24>>24?(w=s+8|0,Xi(k,d,(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0])+(u|0)),w=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]),u=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0])):(w=s|0,Xi(l,(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0])+(u|0),i),w=(b[0]=a[v>>2],b[1]=a[v+4>>2],f[0]),u=(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0]));Xt(s,w,u,g);if((A|0)>=(a[q>>2]|0)){break}u=A;w=m[e]}}}h=l}function Yt(c,d,i,g){var q,e,l,k=h;h+=32;q=k+16;m[c+65|0]=g&255;e=c+8|0;Xi(k,d,i-(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]));l=(c+16|0)>>2;e=k>>2;a[l]=a[e];a[l+1]=a[e+1];a[l+2]=a[e+2];a[l+3]=a[e+3];e=c|0;Xi(q,(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])+d,i);e=(c+32|0)>>2;q>>=2;a[e]=a[q];a[e+1]=a[q+1];a[e+2]=a[q+2];a[e+3]=a[q+3];q=a[c+48>>2];e=q-1|0;if(0<=(e|0)){l=0==(g|0);for(var n=c+56|0,c=c+64|0,j=d,d=0;;){if(l){var p=0}else{var p=0==(d|0),s=(d|0)==(e|0),p=0==m[c]<<24>>24?p?s?15:14:s?11:10:p?s?15:13:s?7:5}Yt(a[a[n>>2]+(d<<2)>>2],j,i,p&g);p=a[a[n>>2]+(d<<2)>>2];0==m[c]<<24>>24?(p=p+8|0,i-=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0])):(p|=0,j+=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]));d=d+1|0;if((d|0)==(q|0)){break}}}h=k}function $i(a,b){ju(a);0!=(b|0)&&G(b)}function vk(c,d,i,g){var q,e,l,k=h;h+=96;var n=k+64;l=n>>2;var j=k+72;e=j>>2;var p=k+80;q=k|0;if(0==g<<24>>24){var g=n,m=q,v=0,n=i+48|0;q=i+56|0}else{g=j,j=n,m=0,v=q,n=i|0,q=i+8|0}var t=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),u=(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]);f[0]=0;a[l]=b[0];a[l+1]=b[1];f[0]=1;a[e]=b[0];a[e+1]=b[1];var w=p|0,A=p+8|0,B=k|0,C=k+8|0,P=k+16|0,T=P|0,y=k+24|0,n=k+32|0,D=n|0,M=k+40|0;q=k+48|0;for(var X=q|0,O=k+56|0,F=0,Da=1,ia=0;;){var E,G,I,H,K,L,N,R,ca=.5*(Da+ia);Ld(p,i,3,ca,m,v);Da=(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0]);ia=(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0]);if(0==J[d](c,Da,ia)<<24>>24){var S=(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0]),Q=(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0]),zb=(b[0]=a[T>>2],b[1]=a[T+4>>2],f[0]),ja=(b[0]=a[y>>2],b[1]=a[y+4>>2],f[0]),aa=(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0]),da=(b[0]=a[M>>2],b[1]=a[M+4>>2],f[0]),ea=(b[0]=a[X>>2],b[1]=a[X+4>>2],f[0]),xa=(b[0]=a[O>>2],b[1]=a[O+4>>2],f[0]);f[0]=ca;a[g>>2]=b[0];a[g+4>>2]=b[1];ca=1}else{f[0]=ca,a[j>>2]=b[0],a[j+4>>2]=b[1],ca=F,S=R,Q=N,zb=L,ja=K,aa=H,da=I,ea=G,xa=E}t-=Da;if(.5>=(0>t?-t:t)){if(t=u-ia,.5>=(0>t?-t:t)){break}}F=ca;t=Da;u=ia;Da=(b[0]=a[e],b[1]=a[e+1],f[0]);ia=(b[0]=a[l],b[1]=a[l+1],f[0]);R=S;N=Q;L=zb;K=ja;H=aa;I=da;G=ea;E=xa}0==ca<<24>>24?(d=i>>2,c=k>>2,a[d]=a[c],a[d+1]=a[c+1],a[d+2]=a[c+2],a[d+3]=a[c+3],c=(i+16|0)>>2,d=P>>2,a[c]=a[d],a[c+1]=a[d+1],a[c+2]=a[d+2],a[c+3]=a[d+3],c=(i+32|0)>>2,d=n>>2,a[c]=a[d],a[c+1]=a[d+1],a[c+2]=a[d+2],a[c+3]=a[d+3],i=(i+48|0)>>2,q>>=2,a[i]=a[q],a[i+1]=a[q+1],a[i+2]=a[q+2],a[i+3]=a[q+3]):(q=i|0,f[0]=S,a[q>>2]=b[0],a[q+4>>2]=b[1],q=i+8|0,f[0]=Q,a[q>>2]=b[0],a[q+4>>2]=b[1],q=i+16|0,f[0]=zb,a[q>>2]=b[0],a[q+4>>2]=b[1],q=i+24|0,f[0]=ja,a[q>>2]=b[0],a[q+4>>2]=b[1],q=i+32|0,f[0]=aa,a[q>>2]=b[0],a[q+4>>2]=b[1],q=i+40|0,f[0]=da,a[q>>2]=b[0],a[q+4>>2]=b[1],q=i+48|0,f[0]=ea,a[q>>2]=b[0],a[q+4>>2]=b[1],i=i+56|0,f[0]=xa,a[i>>2]=b[0],a[i+4>>2]=b[1]);h=k}function jp(c,d,i,g){var q,e,l,k,n,j,p,m,v,t,u,w,A,B,C,P,T,y,D=h;h+=64;y=(d+112|0)>>2;var M=(b[0]=a[y],b[1]=a[y+1],f[0]);T=(d+32|0)>>2;e=(b[0]=a[T],b[1]=a[T+1],f[0]);P=(d+40|0)>>2;q=(b[0]=a[P],b[1]=a[P+1],f[0]);C=(i|0)>>2;l=(b[0]=a[C],b[1]=a[C+1],f[0])-e;B=(D|0)>>2;f[0]=l;a[B]=b[0];a[B+1]=b[1];A=(i+8|0)>>2;l=(b[0]=a[A],b[1]=a[A+1],f[0])-q;w=(D+8|0)>>2;f[0]=l;a[w]=b[0];a[w+1]=b[1];u=(i+16|0)>>2;l=(b[0]=a[u],b[1]=a[u+1],f[0])-e;t=(D+16|0)>>2;f[0]=l;a[t]=b[0];a[t+1]=b[1];v=(i+24|0)>>2;l=(b[0]=a[v],b[1]=a[v+1],f[0])-q;m=(D+24|0)>>2;f[0]=l;a[m]=b[0];a[m+1]=b[1];p=(i+32|0)>>2;l=(b[0]=a[p],b[1]=a[p+1],f[0])-e;j=(D+32|0)>>2;f[0]=l;a[j]=b[0];a[j+1]=b[1];n=(i+40|0)>>2;l=(b[0]=a[n],b[1]=a[n+1],f[0])-q;k=(D+40|0)>>2;f[0]=l;a[k]=b[0];a[k+1]=b[1];l=(i+48|0)>>2;var X=(b[0]=a[l],b[1]=a[l+1],f[0])-e;e=(D+48|0)>>2;f[0]=X;a[e]=b[0];a[e+1]=b[1];i=(i+56|0)>>2;X=(b[0]=a[i],b[1]=a[i+1],f[0])-q;q=(D+56|0)>>2;f[0]=X;a[q]=b[0];a[q+1]=b[1];M&=-1;vk(c,a[a[a[d+24>>2]+4>>2]+12>>2],D|0,g);c=(b[0]=a[B],b[1]=a[B+1],f[0])+(b[0]=a[T],b[1]=a[T+1],f[0]);f[0]=c;a[C]=b[0];a[C+1]=b[1];C=(b[0]=a[w],b[1]=a[w+1],f[0])+(b[0]=a[P],b[1]=a[P+1],f[0]);f[0]=C;a[A]=b[0];a[A+1]=b[1];A=(b[0]=a[t],b[1]=a[t+1],f[0])+(b[0]=a[T],b[1]=a[T+1],f[0]);f[0]=A;a[u]=b[0];a[u+1]=b[1];u=(b[0]=a[m],b[1]=a[m+1],f[0])+(b[0]=a[P],b[1]=a[P+1],f[0]);f[0]=u;a[v]=b[0];a[v+1]=b[1];v=(b[0]=a[j],b[1]=a[j+1],f[0])+(b[0]=a[T],b[1]=a[T+1],f[0]);f[0]=v;a[p]=b[0];a[p+1]=b[1];p=(b[0]=a[k],b[1]=a[k+1],f[0])+(b[0]=a[P],b[1]=a[P+1],f[0]);f[0]=p;a[n]=b[0];a[n+1]=b[1];T=(b[0]=a[e],b[1]=a[e+1],f[0])+(b[0]=a[T],b[1]=a[T+1],f[0]);f[0]=T;a[l]=b[0];a[l+1]=b[1];P=(b[0]=a[q],b[1]=a[q+1],f[0])+(b[0]=a[P],b[1]=a[P+1],f[0]);f[0]=P;a[i]=b[0];a[i+1]=b[1];f[0]=M|0;a[y]=b[0];a[y+1]=b[1];h=D}function Au(b,c){var i,g;i=0==m[b+124|0]<<24>>24;a:do{if(i){g=b}else{for(var d=b;;){if(d=a[d+128>>2],0==m[d+124|0]<<24>>24){g=d;break a}}}}while(0);i=(g+24|0)>>2;g=a[i];0==(g|0)&&(g=fa(40),a[i]=g);g>>=2;d=a[g];g=0==(d|0)?Cb(48*a[g+1]+48|0):wb(d,48*a[g+1]+48|0);a[a[i]>>2]=g;d=a[i]+4|0;g=a[d>>2];a[d>>2]=g+1|0;d=a[a[i]>>2];i=d>>2;d=d+48*g|0;a[d>>2]=fa(c<<4);a[i+(12*g|0)+1]=c;a[i+(12*g|0)+3]=0;a[i+(12*g|0)+2]=0;return d}function Wd(c,d,i,g,q){var e,l,k,n,j,p,s,v=h;h+=80;var t;j=v>>2;p=v+4;n=p>>2;var u=v+8;l=u>>2;k=v+16;var w=a[c+16>>2];s=w>>2;var A=a[s+5],B=Au(c,g),C=0==m[c+124|0]<<24>>24;a:do{if(C){var P=c}else{for(var y=c;;){if(y=a[y+128>>2],0==m[y+124|0]<<24>>24){P=y;break a}}}}while(0);0==m[q+8|0]<<24>>24?(a[s+59]|0)!=(a[d+236>>2]|0)?s=d:(s=(C=(a[s+60]|0)>(a[d+240>>2]|0))?w:d,w=C?d:w):s=d;if((w|0)==(a[P+16>>2]|0)){var d=P+92|0,y=P+52|0,D=P+98|0,C=P+58|0}else{d=P+52|0,y=P+92|0,D=P+58|0,C=P+98|0}P=m[D];d=a[d>>2];y=a[y>>2];do{if(0==m[C]<<24>>24){t=2574}else{var D=w+24|0,F=a[D>>2];if(0==(F|0)){t=2574}else{if(0==(a[a[F+4>>2]+12>>2]|0)){t=2574}else{a[l]=w;a[l+1]=y|0;for(var M=g-4|0,F=w+32|0,X=w+40|0,O=0;(O|0)<(M|0);){var E=O+3|0,Da=(E<<4)+i|0,Da=(b[0]=a[Da>>2],b[1]=a[Da+4>>2],f[0])-(b[0]=a[F>>2],b[1]=a[F+4>>2],f[0]),ia=(E<<4)+i+8|0;if(0==J[a[a[a[D>>2]+4>>2]+12>>2]](u,Da,(b[0]=a[ia>>2],b[1]=a[ia+4>>2],f[0])-(b[0]=a[X>>2],b[1]=a[X+4>>2],f[0]))<<24>>24){break}else{O=E}}a[j]=O;jp(u,w,(O<<4)+i|0,1);M=O}}}}while(0);2574==t&&(M=a[j]=0);do{if(0==P<<24>>24){t=2583}else{if(w=s+24|0,C=a[w>>2],0==(C|0)){t=2583}else{if(0==(a[a[C+4>>2]+12>>2]|0)){t=2583}else{a[l]=s;a[l+1]=d|0;e=s+32|0;C=s+40|0;for(y=g-4|0;0<(y|0);){D=(y<<4)+i|0;D=(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0])-(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);F=(y<<4)+i+8|0;if(0==J[a[a[a[w>>2]+4>>2]+12>>2]](u,D,(b[0]=a[F>>2],b[1]=a[F+4>>2],f[0])-(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0]))<<24>>24){break}y=y-3|0}a[n]=y;jp(u,s,(y<<4)+i|0,0);e=y}}}}while(0);2583==t&&(e=g-4|0,a[n]=e);for(g=g-4|0;;){if((M|0)>=(g|0)){var G=e;break}u=(M<<4)+i|0;l=M+3|0;P=(l<<4)+i|0;u=(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0])-(b[0]=a[P>>2],b[1]=a[P+4>>2],f[0]);M=(M<<4)+i+8|0;P=(l<<4)+i+8|0;M=(b[0]=a[M>>2],b[1]=a[M+4>>2],f[0])-(b[0]=a[P>>2],b[1]=a[P+4>>2],f[0]);if(1e-6<=u*u+M*M){G=e;break}M=a[j]=l}for(;0<(G|0);){g=(G<<4)+i|0;e=G+3|0;l=(e<<4)+i|0;g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])-(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);l=(G<<4)+i+8|0;e=(e<<4)+i+8|0;e=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])-(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);if(1e-6<=g*g+e*e){break}G=G-3|0;a[n]=G}QH(c,s,i,v,p,B,q);c=a[j];G=a[n]+4|0;p=(B|0)>>2;j=k>>2;s=k|0;q=(k+16|0)>>2;n=(k+32|0)>>2;k=(k+48|0)>>2;A=A+52|0;for(e=c;;){if((e|0)>=(G|0)){t=2597;break}l=((e-c<<4)+a[p]|0)>>2;g=((e<<4)+i|0)>>2;a[l]=a[g];a[l+1]=a[g+1];a[l+2]=a[g+2];a[l+3]=a[g+3];a[j]=a[g];a[j+1]=a[g+1];a[j+2]=a[g+2];a[j+3]=a[g+3];l=e+1|0;if((l|0)>=(G|0)){t=2596;break}g=((l-c<<4)+a[p]|0)>>2;l=((l<<4)+i|0)>>2;a[g]=a[l];a[g+1]=a[l+1];a[g+2]=a[l+2];a[g+3]=a[l+3];a[q]=a[l];a[q+1]=a[l+1];a[q+2]=a[l+2];a[q+3]=a[l+3];l=e+2|0;g=((l-c<<4)+a[p]|0)>>2;l=((l<<4)+i|0)>>2;a[g]=a[l];a[g+1]=a[l+1];a[g+2]=a[l+2];a[g+3]=a[l+3];a[n]=a[l];a[n+1]=a[l+1];a[n+2]=a[l+2];a[n+3]=a[l+3];g=e+3|0;e=((g<<4)+i|0)>>2;a[k]=a[e];a[k+1]=a[e+1];a[k+2]=a[e+2];a[k+3]=a[e+3];xk(A,s);e=g}2596==t?(a[(B+4|0)>>2]=G-c|0,h=v):2597==t&&(a[(B+4|0)>>2]=G-c|0,h=v)}function QH(b,c,i,g,d,f,e){var k,n,j=h;h+=8;n=j>>2;var p=j+4;k=p>>2;for(var s=b;;){var v=a[s+128>>2];if(0==(v|0)){break}else{s=v}}v=0==m[e+8|0]<<24>>24?J[a[e>>2]](s)&255:0;tk(s,j,p);p=e+4|0;0!=J[a[p>>2]](c)<<24>>24&&(a[k]=0);0!=J[a[p>>2]](a[b+16>>2])<<24>>24&&(a[n]=0);0!=(v|0)&&(b=a[n],a[n]=a[k],a[k]=b);0!=m[e+9|0]<<24>>24?(k=a[k],n=a[n],0!=(k|n|0)&&LC(s,i,a[g>>2],a[d>>2],f,n,k)):(n=a[n],0!=(n|0)&&(a[g>>2]=In(s,i,a[g>>2],a[d>>2],f,n)),k=a[k],0!=(k|0)&&(a[d>>2]=Hn(s,i,a[g>>2],a[d>>2],f,k)));h=j}function bd(c,d){var i,g,q=h;i=d>>2;d=h;h+=32;a[d>>2]=a[i];a[d+4>>2]=a[i+1];a[d+8>>2]=a[i+2];a[d+12>>2]=a[i+3];a[d+16>>2]=a[i+4];a[d+20>>2]=a[i+5];a[d+24>>2]=a[i+6];a[d+28>>2]=a[i+7];i=d|0;g=d+16|0;if((b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])<(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])){if(i=d+8|0,g=d+24|0,(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])<(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])){i=c+80|0,g=a[i>>2],a[i>>2]=g+1|0,g=((g<<5)+a[c+84>>2]|0)>>2,i=d>>2,a[g]=a[i],a[g+1]=a[i+1],a[g+2]=a[i+2],a[g+3]=a[i+3],a[g+4]=a[i+4],a[g+5]=a[i+5],a[g+6]=a[i+6],a[g+7]=a[i+7]}}h=q}function RH(c,d,i,g,q){var e=c|0;f[0]=d+g;a[e>>2]=b[0];a[e+4>>2]=b[1];c=c+8|0;f[0]=i+q;a[c>>2]=b[0];a[c+4>>2]=b[1]}function vl(c,d,i,g,q){var e,l,k,n,j,p,s,v,t,u,w,A,B,C=g>>2,P=h;h+=56;var y,D=P+40,F=d+16|0,M=a[F>>2];B=M>>2;var X=d+28|0;if(0!=m[d+59|0]<<24>>24){wH(P,M,a[d+12>>2],X);for(var O=P>>2,E=X>>2,Da=O+10;O>2]+16>>2];A=(M+32|0)>>2;var I=(b[0]=a[A],b[1]=a[A+1],f[0]);w=(M+40|0)>>2;var H=(b[0]=a[w],b[1]=a[w+1],f[0]),K=X|0,L=d+36|0;RH(D,I,H,(b[0]=a[K>>2],b[1]=a[K+4>>2],f[0]),(b[0]=a[L>>2],b[1]=a[L+4>>2],f[0]));u=c>>2;t=D>>2;a[u]=a[t];a[u+1]=a[t+1];a[u+2]=a[t+2];a[u+3]=a[t+3];if(0==q<<24>>24){if(0==m[d+57|0]<<24>>24){m[c+29|0]=0}else{var N=d+44|0,R=(b[0]=a[N>>2],b[1]=a[N+4>>2],f[0]),S=c+16|0;f[0]=R;a[S>>2]=b[0];a[S+4>>2]=b[1];m[c+29|0]=1}}else{var ca=Bu(a[F>>2]),Q=c+16|0;f[0]=ca;a[Q>>2]=b[0];a[Q+4>>2]=b[1];m[c+29|0]=1}a[c+80>>2]=0;a[c+88>>2]=d|0;v=(g+32|0)>>2;a[v]=a[u];a[v+1]=a[u+1];a[v+2]=a[u+2];a[v+3]=a[u+3];var U=1==(i|0);do{if(U){if(0!=m[M+162|0]<<24>>24){var zb=1}else{var ja=m[d+61|0],aa=ja&255;if(0==ja<<24>>24){y=2673}else{var da=g|0,ea=(b[0]=a[da>>2],b[1]=a[da+4>>2],f[0]),xa=g+8|0,W=(b[0]=a[xa>>2],b[1]=a[xa+4>>2],f[0]),V=g+16|0,ha=(b[0]=a[V>>2],b[1]=a[V+4>>2],f[0]),ga=g+24|0,Y=(b[0]=a[ga>>2],b[1]=a[ga+4>>2],f[0]);if(0==(aa&4|0)){if(0!=(aa&1|0)){a[C+12]=1;s=(c+8|0)>>2;var Rb=(b[0]=a[s],b[1]=a[s+1],f[0]),Z=Y>Rb?Y:Rb,tb=g+56|0;f[0]=ea;a[tb>>2]=b[0];a[tb+4>>2]=b[1];var ya=g+64|0;f[0]=W;a[ya>>2]=b[0];a[ya+4>>2]=b[1];var $=g+72|0;f[0]=ha;a[$>>2]=b[0];a[$+4>>2]=b[1];var wa=g+80|0;f[0]=Z;a[wa>>2]=b[0];a[wa+4>>2]=b[1];a[C+13]=1;var la=(b[0]=a[s],b[1]=a[s+1],f[0])-1;f[0]=la;a[s]=b[0];a[s+1]=b[1]}else{var Fa=g+48|0;if(0==(aa&8|0)){a[Fa>>2]=2;p=(c|0)>>2;var Ga=(b[0]=a[p],b[1]=a[p+1],f[0]),fa=(b[0]=a[w],b[1]=a[w+1],f[0]),ta=M+96|0,Ka=(b[0]=a[ta>>2],b[1]=a[ta+4>>2],f[0]),za=fa-((((0>Ka?Ka-.5:Ka+.5)&-1)+1|0)/2&-1|0),ma=c+8|0,pa=(b[0]=a[ma>>2],b[1]=a[ma+4>>2],f[0]),ba=g+56|0;f[0]=Ga;a[ba>>2]=b[0];a[ba+4>>2]=b[1];var Ha=g+64|0;f[0]=za;a[Ha>>2]=b[0];a[Ha+4>>2]=b[1];var Ra=g+72|0;f[0]=ha;a[Ra>>2]=b[0];a[Ra+4>>2]=b[1];var ka=g+80|0;f[0]=pa;a[ka>>2]=b[0];a[ka+4>>2]=b[1];a[C+13]=1;var ra=(b[0]=a[p],b[1]=a[p+1],f[0])+1;f[0]=ra;a[p]=b[0];a[p+1]=b[1]}else{a[Fa>>2]=8;j=(c|0)>>2;var La=(b[0]=a[j],b[1]=a[j+1],f[0]),qa=(b[0]=a[w],b[1]=a[w+1],f[0]),Ya=M+96|0,na=(b[0]=a[Ya>>2],b[1]=a[Ya+4>>2],f[0]),Za=qa-((((0>na?na-.5:na+.5)&-1)+1|0)/2&-1|0),oa=c+8|0,ab=(b[0]=a[oa>>2],b[1]=a[oa+4>>2],f[0]),$a=g+56|0;f[0]=ea;a[$a>>2]=b[0];a[$a+4>>2]=b[1];var jb=g+64|0;f[0]=Za;a[jb>>2]=b[0];a[jb+4>>2]=b[1];var Ca=g+72|0;f[0]=La;a[Ca>>2]=b[0];a[Ca+4>>2]=b[1];var Ia=g+80|0;f[0]=ab;a[Ia>>2]=b[0];a[Ia+4>>2]=b[1];a[C+13]=1;var eb=(b[0]=a[j],b[1]=a[j+1],f[0])-1;f[0]=eb;a[j]=b[0];a[j+1]=b[1]}}}else{a[C+12]=4;var ub=c|0,Sa=(b[0]=a[ub>>2],b[1]=a[ub+4>>2],f[0]),Ba=(b[0]=a[A],b[1]=a[A+1],f[0]);if(Sa>2],b[1]=a[Oa+4>>2],f[0]),pb=(b[0]=a[w],b[1]=a[w+1],f[0]),ob=M+96|0,bb=(b[0]=a[ob>>2],b[1]=a[ob+4>>2],f[0]),qb=0>bb,va=pb+((((qb?bb-.5:bb+.5)&-1)+1|0)/2&-1|0)+((a[a[B+5]+260>>2]|0)/2&-1|0),kb=M+104|0,Aa=Ba-(b[0]=a[kb>>2],b[1]=a[kb+4>>2],f[0]),vb=pb-((((qb?bb-.5:bb+.5)&-1)+1|0)/2&-1|0),xb=g+56|0;f[0]=ua;a[xb>>2]=b[0];a[xb+4>>2]=b[1];var hd=g+64|0;f[0]=Wa;a[hd>>2]=b[0];a[hd+4>>2]=b[1];var nb=g+72|0;f[0]=ha;a[nb>>2]=b[0];a[nb+4>>2]=b[1];var rb=g+80|0;f[0]=va;a[rb>>2]=b[0];a[rb+4>>2]=b[1];var lb=g+88|0;f[0]=ua;a[lb>>2]=b[0];a[lb+4>>2]=b[1];var Ta=g+96|0;f[0]=vb;a[Ta>>2]=b[0];a[Ta+4>>2]=b[1];var cb=g+104|0;f[0]=Aa;a[cb>>2]=b[0];a[cb+4>>2]=b[1];var fb=g+112|0;f[0]=Wa;a[fb>>2]=b[0];a[fb+4>>2]=b[1]}else{var Ua=c+8|0,sb=(b[0]=a[Ua>>2],b[1]=a[Ua+4>>2],f[0]),Na=ha+1,Fb=(b[0]=a[w],b[1]=a[w+1],f[0]),Db=M+96|0,Ob=(b[0]=a[Db>>2],b[1]=a[Db+4>>2],f[0]),Eb=0>Ob,Ea=Fb+((((Eb?Ob-.5:Ob+.5)&-1)+1|0)/2&-1|0)+((a[a[B+5]+260>>2]|0)/2&-1|0),Bb=M+112|0,Ja=Ba+(b[0]=a[Bb>>2],b[1]=a[Bb+4>>2],f[0]),Ma=Fb-((((Eb?Ob-.5:Ob+.5)&-1)+1|0)/2&-1|0),Qa=g+56|0;f[0]=ea;a[Qa>>2]=b[0];a[Qa+4>>2]=b[1];var wb=g+64|0;f[0]=sb;a[wb>>2]=b[0];a[wb+4>>2]=b[1];var Cb=g+72|0;f[0]=Na;a[Cb>>2]=b[0];a[Cb+4>>2]=b[1];var Va=g+80|0;f[0]=Ea;a[Va>>2]=b[0];a[Va+4>>2]=b[1];var Pa=g+88|0;f[0]=Ja;a[Pa>>2]=b[0];a[Pa+4>>2]=b[1];var ic=g+96|0;f[0]=Ma;a[ic>>2]=b[0];a[ic+4>>2]=b[1];var hb=g+104|0;f[0]=Na;a[hb>>2]=b[0];a[hb+4>>2]=b[1];var ib=g+112|0;f[0]=sb;a[ib>>2]=b[0];a[ib+4>>2]=b[1]}n=(c+8|0)>>2;var yb=(b[0]=a[n],b[1]=a[n+1],f[0])+1;f[0]=yb;a[n]=b[0];a[n+1]=b[1];a[C+13]=2}var Gb=0==m[d+124|0]<<24>>24;a:do{if(Gb){var db=d}else{for(var pc=d;;){var Wc=a[pc+128>>2];if(0==m[Wc+124|0]<<24>>24){db=Wc;break a}else{pc=Wc}}}}while(0);(M|0)==(a[db+16>>2]|0)?m[db+58|0]=0:m[db+98|0]=0;h=P;return}}}else{y=2673}}while(0);do{if(2673==y){do{if(2==(i|0)){var Vb=m[d+61|0],Ib=Vb&255;if(0!=Vb<<24>>24){var Nb=g|0,mb=(b[0]=a[Nb>>2],b[1]=a[Nb+4>>2],f[0]),Jb=g+8|0,Hb=(b[0]=a[Jb>>2],b[1]=a[Jb+4>>2],f[0]),Lb=g+16|0,lc=(b[0]=a[Lb>>2],b[1]=a[Lb+4>>2],f[0]),Kb=g+24|0,Jd=(b[0]=a[Kb>>2],b[1]=a[Kb+4>>2],f[0]);if(0==(Ib&4|0)){if(0!=(Ib&1|0)){if(4!=(a[C+12]|0)){var Pb=c+8|0,Hc=(b[0]=a[Pb>>2],b[1]=a[Pb+4>>2],f[0]),Wb=Jd>Hc?Jd:Hc,oe=g+56|0;f[0]=mb;a[oe>>2]=b[0];a[oe+4>>2]=b[1];var Tb=g+64|0;f[0]=Hb;a[Tb>>2]=b[0];a[Tb+4>>2]=b[1];var Qb=g+72|0;f[0]=lc;a[Qb>>2]=b[0];a[Qb+4>>2]=b[1];var tc=g+80|0;f[0]=Wb;a[tc>>2]=b[0];a[tc+4>>2]=b[1];a[C+13]=1}else{var Sc=(b[0]=a[w],b[1]=a[w+1],f[0]),Yb=M+96|0,Mb=(b[0]=a[Yb>>2],b[1]=a[Yb+4>>2],f[0]),$b=0>Mb,rd=Sc-(((($b?Mb-.5:Mb+.5)&-1)+1|0)/2&-1|0),oc=lc+1,Zb=c|0,qc=(b[0]=a[Zb>>2],b[1]=a[Zb+4>>2],f[0]),ve=rd-((a[a[B+5]+260>>2]|0)/2&-1|0),Rd=M+112|0,Bc=(b[0]=a[A],b[1]=a[A+1],f[0])+(b[0]=a[Rd>>2],b[1]=a[Rd+4>>2],f[0]),Sd=Sc+(((($b?Mb-.5:Mb+.5)&-1)+1|0)/2&-1|0),fd=g+56|0;f[0]=qc;a[fd>>2]=b[0];a[fd+4>>2]=b[1];var Sb=g+64|0;f[0]=ve;a[Sb>>2]=b[0];a[Sb+4>>2]=b[1];var yc=g+72|0;f[0]=oc;a[yc>>2]=b[0];a[yc+4>>2]=b[1];var uc=g+80|0;f[0]=rd;a[uc>>2]=b[0];a[uc+4>>2]=b[1];var nc=g+88|0;f[0]=Bc;a[nc>>2]=b[0];a[nc+4>>2]=b[1];var Tc=g+96|0;f[0]=rd;a[Tc>>2]=b[0];a[Tc+4>>2]=b[1];var kc=g+104|0;f[0]=oc;a[kc>>2]=b[0];a[kc+4>>2]=b[1];var Mc=g+112|0;f[0]=Sd;a[Mc>>2]=b[0];a[Mc+4>>2]=b[1];a[C+13]=2}}else{var id=c|0,ec=(b[0]=a[id>>2],b[1]=a[id+4>>2],f[0]);if(0==(Ib&8|0)){var mc=4==(a[C+12]|0),fc=(b[0]=a[w],b[1]=a[w+1],f[0]),bc=M+96|0,ac=(b[0]=a[bc>>2],b[1]=a[bc+4>>2],f[0]),rc=0<=ac;if(mc){var jc=c+8|0,Ac=fc+((((rc?ac+.5:ac-.5)&-1)+1|0)/2&-1|0),Td=(b[0]=a[jc>>2],b[1]=a[jc+4>>2],f[0])}else{var gc=rc?ac+.5:ac-.5,dc=c+8|0,Ac=(b[0]=a[dc>>2],b[1]=a[dc+4>>2],f[0])+1,Td=fc-(((gc&-1)+1|0)/2&-1|0)}var fe=g+56|0;f[0]=ec;a[fe>>2]=b[0];a[fe+4>>2]=b[1];var Xb=g+64|0;f[0]=Td;a[Xb>>2]=b[0];a[Xb+4>>2]=b[1];var Cc=g+72|0;f[0]=lc;a[Cc>>2]=b[0];a[Cc+4>>2]=b[1];var jd=g+80|0;f[0]=Ac;a[jd>>2]=b[0];a[jd+4>>2]=b[1]}else{var md=ec+1,je=4==(a[C+12]|0),Qe=(b[0]=a[w],b[1]=a[w+1],f[0]),Ic=M+96|0,Uc=(b[0]=a[Ic>>2],b[1]=a[Ic+4>>2],f[0]),vc=0<=Uc;if(je){var Dc=c+8|0,Ec=Qe+((((vc?Uc+.5:Uc-.5)&-1)+1|0)/2&-1|0),Gc=(b[0]=a[Dc>>2],b[1]=a[Dc+4>>2],f[0])-1}else{var Kc=vc?Uc+.5:Uc-.5,kd=c+8|0,Ec=(b[0]=a[kd>>2],b[1]=a[kd+4>>2],f[0])+1,Gc=Qe-(((Kc&-1)+1|0)/2&-1|0)}var Nc=g+56|0;f[0]=mb;a[Nc>>2]=b[0];a[Nc+4>>2]=b[1];var zc=g+64|0;f[0]=Gc;a[zc>>2]=b[0];a[zc+4>>2]=b[1];var Fc=g+72|0;f[0]=md;a[Fc>>2]=b[0];a[Fc+4>>2]=b[1];var Qc=g+80|0;f[0]=Ec;a[Qc>>2]=b[0];a[Qc+4>>2]=b[1]}a[C+13]=1}}else{var Yc=c+8|0,nd=(b[0]=a[Yc>>2],b[1]=a[Yc+4>>2],f[0]),zd=Hb>2]=b[0];a[cf+4>>2]=b[1];var Zc=g+64|0;f[0]=zd;a[Zc>>2]=b[0];a[Zc+4>>2]=b[1];var Rc=g+72|0;f[0]=lc;a[Rc>>2]=b[0];a[Rc+4>>2]=b[1];var Lc=g+80|0;f[0]=Jd;a[Lc>>2]=b[0];a[Lc+4>>2]=b[1];a[C+13]=1}var $c=0==m[d+124|0]<<24>>24;a:do{if($c){var pe=d}else{for(var Oc=d;;){var Kd=a[Oc+128>>2];if(0==m[Kd+124|0]<<24>>24){pe=Kd;break a}else{Oc=Kd}}}}while(0);(M|0)==(a[pe+16>>2]|0)?m[pe+58|0]=0:m[pe+98|0]=0;a[C+12]=Ib;h=P;return}}}while(0);zb=U?1:a[C+12]}}while(0);var Dd=g+56|0,Pc=g+52|0;if(0!=(G|0)){var Jc=J[G](M,X,zb,Dd,Pc);if(0!=(Jc|0)){a[C+12]=Jc;h=P;return}}k=Dd>>2;l=g>>2;a[k]=a[l];a[k+1]=a[l+1];a[k+2]=a[l+2];a[k+3]=a[l+3];a[k+4]=a[l+4];a[k+5]=a[l+5];a[k+6]=a[l+6];a[k+7]=a[l+7];a[Pc>>2]=1;if(2==(i|0)){var ld=4==(a[C+12]|0),Ad=c+8|0,Ud=(b[0]=a[Ad>>2],b[1]=a[Ad+4>>2],f[0]);if(ld){var Vd=g+64|0;f[0]=Ud;a[Vd>>2]=b[0];a[Vd+4>>2]=b[1]}else{var ad=g+80|0;f[0]=Ud;a[ad>>2]=b[0];a[ad+4>>2]=b[1]}}else{if(1==(i|0)){e=(c+8|0)>>2;var bd=(b[0]=a[e],b[1]=a[e+1],f[0]),gd=g+80|0;f[0]=bd;a[gd>>2]=b[0];a[gd+4>>2]=b[1];a[C+12]=1;var ud=(b[0]=a[e],b[1]=a[e+1],f[0])-1;f[0]=ud;a[e]=b[0];a[e+1]=b[1]}else{if(8==(i|0)){sa(wl|0,565,SH|0,vd|0);var Xc=c+8|0,Vc=(b[0]=a[Xc>>2],b[1]=a[Xc+4>>2],f[0])-1,cd=g+80|0;f[0]=Vc;a[cd>>2]=b[0];a[cd+4>>2]=b[1];a[C+12]=1}}}h=P}function Bu(c){var d,i=a[c+176>>2],g=a[i>>2];if(0==(g|0)){var q=0,e=0}else{for(var e=q=0,l=g;;){var k=a[l+16>>2]+32|0,k=e+(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),h=q+1|0,l=a[i+(h<<2)>>2];if(0==(l|0)){break}else{q=h,e=k}}q=h|0;e=k}var i=c+184|0,k=a[i>>2],j=a[k>>2];if(0==(j|0)){var p=0,k=0}else{for(l=h=0;!(d=a[j+12>>2]+32|0,d=l+(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),p=h+1|0,j=a[k+(p<<2)>>2],0==(j|0));){h=p,l=d}p|=0;k=d}d=(c+32|0)>>2;q=(b[0]=a[d],b[1]=a[d+1],f[0])-e/q;c=(c+40|0)>>2;g=a[g+16>>2]+40|0;g=Cf((b[0]=a[c],b[1]=a[c+1],f[0])-(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),q);d=k/p-(b[0]=a[d],b[1]=a[d+1],f[0]);i=a[a[a[i>>2]>>2]+12>>2]+40|0;return.5*(g+Cf((b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])-(b[0]=a[c],b[1]=a[c+1],f[0]),d))}function HQa(c){var d,i=a[c+108>>2];0==m[c+56|0]<<24>>24?0!=m[c+96|0]<<24>>24&&(d=2744):d=2744;if(2744==d){d=m[c+61|0];var g=d&255;if(0!=(g&8|0)){return 0}var q=m[c+101|0];if(0!=(q&8)<<24>>24||d<<24>>24==q<<24>>24&&0!=(g&5|0)){return 0}}if(0==(i|0)){return 18}c=0==(a[a[a[c+12>>2]+20>>2]+152>>2]&1|0)?i+24|0:i+32|0;return c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])+18&-1}function xl(c,d,i,g,q){var e,l,k,n,j,p,s,v,t,u,w,A,B,C=g>>2,P=h;h+=56;var y,D=P+40,F=d+12|0,M=a[F>>2];B=M>>2;var X=d+68|0;if(0!=m[d+99|0]<<24>>24){wH(P,M,a[d+16>>2],X);for(var O=P>>2,E=X>>2,Da=O+10;O>2]+16>>2],I=c+40|0;A=(M+32|0)>>2;var H=(b[0]=a[A],b[1]=a[A+1],f[0]);w=(M+40|0)>>2;var K=(b[0]=a[w],b[1]=a[w+1],f[0]),L=X|0,N=d+76|0;RH(D,H,K,(b[0]=a[L>>2],b[1]=a[L+4>>2],f[0]),(b[0]=a[N>>2],b[1]=a[N+4>>2],f[0]));u=I>>2;t=D>>2;a[u]=a[t];a[u+1]=a[t+1];a[u+2]=a[t+2];a[u+3]=a[t+3];if(0==q<<24>>24){if(0==m[d+97|0]<<24>>24){m[c+69|0]=0}else{var R=d+84|0,S=(b[0]=a[R>>2],b[1]=a[R+4>>2],f[0]),ca=c+56|0;f[0]=S;a[ca>>2]=b[0];a[ca+4>>2]=b[1];m[c+69|0]=1}}else{var Q=Bu(a[F>>2])+3.141592653589793,U=c+56|0;f[0]=Q;a[U>>2]=b[0];a[U+4>>2]=b[1];6.283185307179586>Q||sa(wl|0,602,Cu|0,TH|0);m[c+69|0]=1}v=(g+32|0)>>2;a[v]=a[u];a[v+1]=a[u+1];a[v+2]=a[u+2];a[v+3]=a[u+3];var zb=1==(i|0);do{if(zb){if(0!=m[M+162|0]<<24>>24){var ja=4}else{var aa=m[d+101|0],da=aa&255;if(0==aa<<24>>24){y=2804}else{var ea=g|0,xa=(b[0]=a[ea>>2],b[1]=a[ea+4>>2],f[0]),W=g+8|0,V=(b[0]=a[W>>2],b[1]=a[W+4>>2],f[0]),ha=g+16|0,ga=(b[0]=a[ha>>2],b[1]=a[ha+4>>2],f[0]);if(0==(da&4|0)){if(0==(da&1|0)){var Y=g+48|0;if(0==(da&8|0)){a[Y>>2]=2;s=(I|0)>>2;var Rb=(b[0]=a[s],b[1]=a[s+1],f[0]),$=(b[0]=a[w],b[1]=a[w+1],f[0]),tb=M+96|0,ya=(b[0]=a[tb>>2],b[1]=a[tb+4>>2],f[0]),Z=$+((((0>ya?ya-.5:ya+.5)&-1)+1|0)/2&-1|0),wa=c+48|0,la=(b[0]=a[wa>>2],b[1]=a[wa+4>>2],f[0]),Fa=g+56|0;f[0]=Rb;a[Fa>>2]=b[0];a[Fa+4>>2]=b[1];var Ga=g+64|0;f[0]=la;a[Ga>>2]=b[0];a[Ga+4>>2]=b[1];var fa=g+72|0;f[0]=ga;a[fa>>2]=b[0];a[fa+4>>2]=b[1];var ta=g+80|0;f[0]=Z;a[ta>>2]=b[0];a[ta+4>>2]=b[1];a[C+13]=1;var Ka=(b[0]=a[s],b[1]=a[s+1],f[0])+1;f[0]=Ka;a[s]=b[0];a[s+1]=b[1]}else{a[Y>>2]=8;p=(I|0)>>2;var za=(b[0]=a[p],b[1]=a[p+1],f[0]),ma=(b[0]=a[w],b[1]=a[w+1],f[0]),pa=M+96|0,ba=(b[0]=a[pa>>2],b[1]=a[pa+4>>2],f[0]),Ha=ma+((((0>ba?ba-.5:ba+.5)&-1)+1|0)/2&-1|0),Ra=c+48|0,ka=(b[0]=a[Ra>>2],b[1]=a[Ra+4>>2],f[0]),ra=g+56|0;f[0]=xa;a[ra>>2]=b[0];a[ra+4>>2]=b[1];var La=g+64|0;f[0]=ka;a[La>>2]=b[0];a[La+4>>2]=b[1];var qa=g+72|0;f[0]=za;a[qa>>2]=b[0];a[qa+4>>2]=b[1];var Ya=g+80|0;f[0]=Ha;a[Ya>>2]=b[0];a[Ya+4>>2]=b[1];a[C+13]=1;var na=(b[0]=a[p],b[1]=a[p+1],f[0])-1;f[0]=na;a[p]=b[0];a[p+1]=b[1]}}else{a[C+12]=1;var Za=I|0,oa=(b[0]=a[Za>>2],b[1]=a[Za+4>>2],f[0]),ab=(b[0]=a[A],b[1]=a[A+1],f[0]);if(oa>2],b[1]=a[jb+4>>2],f[0]),Ia=(b[0]=a[w],b[1]=a[w+1],f[0]),eb=M+96|0,ub=(b[0]=a[eb>>2],b[1]=a[eb+4>>2],f[0]),Sa=0>ub,Ba=Ia-((((Sa?ub-.5:ub+.5)&-1)+1|0)/2&-1|0)-((a[a[B+5]+260>>2]|0)/2&-1|0),ua=M+104|0,Oa=ab-(b[0]=a[ua>>2],b[1]=a[ua+4>>2],f[0]),Wa=Ia+((((Sa?ub-.5:ub+.5)&-1)+1|0)/2&-1|0),pb=g+56|0;f[0]=$a;a[pb>>2]=b[0];a[pb+4>>2]=b[1];var ob=g+64|0;f[0]=Ba;a[ob>>2]=b[0];a[ob+4>>2]=b[1];var bb=g+72|0;f[0]=ga;a[bb>>2]=b[0];a[bb+4>>2]=b[1];var qb=g+80|0;f[0]=Ca;a[qb>>2]=b[0];a[qb+4>>2]=b[1];var va=g+88|0;f[0]=$a;a[va>>2]=b[0];a[va+4>>2]=b[1];var kb=g+96|0;f[0]=Ca;a[kb>>2]=b[0];a[kb+4>>2]=b[1];var Aa=g+104|0;f[0]=Oa;a[Aa>>2]=b[0];a[Aa+4>>2]=b[1];var vb=g+112|0;f[0]=Wa;a[vb>>2]=b[0];a[vb+4>>2]=b[1]}else{var xb=c+48|0,hd=(b[0]=a[xb>>2],b[1]=a[xb+4>>2],f[0]),nb=ga+1,rb=(b[0]=a[w],b[1]=a[w+1],f[0]),lb=M+96|0,Ta=(b[0]=a[lb>>2],b[1]=a[lb+4>>2],f[0]),cb=0>Ta,fb=rb-((((cb?Ta-.5:Ta+.5)&-1)+1|0)/2&-1|0)-((a[a[B+5]+260>>2]|0)/2&-1|0),Ua=M+112|0,sb=ab+(b[0]=a[Ua>>2],b[1]=a[Ua+4>>2],f[0]),Na=rb+((((cb?Ta-.5:Ta+.5)&-1)+1|0)/2&-1|0),Fb=g+56|0;f[0]=xa;a[Fb>>2]=b[0];a[Fb+4>>2]=b[1];var Db=g+64|0;f[0]=fb;a[Db>>2]=b[0];a[Db+4>>2]=b[1];var Ob=g+72|0;f[0]=nb;a[Ob>>2]=b[0];a[Ob+4>>2]=b[1];var Eb=g+80|0;f[0]=hd;a[Eb>>2]=b[0];a[Eb+4>>2]=b[1];var Ea=g+88|0;f[0]=sb;a[Ea>>2]=b[0];a[Ea+4>>2]=b[1];var Bb=g+96|0;f[0]=hd;a[Bb>>2]=b[0];a[Bb+4>>2]=b[1];var Ja=g+104|0;f[0]=nb;a[Ja>>2]=b[0];a[Ja+4>>2]=b[1];var Ma=g+112|0;f[0]=Na;a[Ma>>2]=b[0];a[Ma+4>>2]=b[1]}a[C+13]=2;j=(c+48|0)>>2;var Qa=(b[0]=a[j],b[1]=a[j+1],f[0])-1;f[0]=Qa;a[j]=b[0];a[j+1]=b[1]}}else{var wb=g+24|0,Cb=(b[0]=a[wb>>2],b[1]=a[wb+4>>2],f[0]);a[C+12]=4;n=(c+48|0)>>2;var Va=(b[0]=a[n],b[1]=a[n+1],f[0]),Pa=V>2]=b[0];a[ic+4>>2]=b[1];var hb=g+64|0;f[0]=Pa;a[hb>>2]=b[0];a[hb+4>>2]=b[1];var ib=g+72|0;f[0]=ga;a[ib>>2]=b[0];a[ib+4>>2]=b[1];var db=g+80|0;f[0]=Cb;a[db>>2]=b[0];a[db+4>>2]=b[1];a[C+13]=1;var yb=(b[0]=a[n],b[1]=a[n+1],f[0])+1;f[0]=yb;a[n]=b[0];a[n+1]=b[1]}var Gb=0==m[d+124|0]<<24>>24;a:do{if(Gb){var pc=d}else{for(var Ib=d;;){var Vb=a[Ib+128>>2];if(0==m[Vb+124|0]<<24>>24){pc=Vb;break a}else{Ib=Vb}}}}while(0);(M|0)==(a[pc+12>>2]|0)?m[pc+98|0]=0:m[pc+58|0]=0;a[C+12]=da;h=P;return}}}else{y=2804}}while(0);do{if(2804==y){do{if(2==(i|0)){var Nb=m[d+101|0],mb=Nb&255;if(0!=Nb<<24>>24){var Hb=g|0,Jb=(b[0]=a[Hb>>2],b[1]=a[Hb+4>>2],f[0]),Lb=g+8|0,Kb=(b[0]=a[Lb>>2],b[1]=a[Lb+4>>2],f[0]),lc=g+16|0,Mb=(b[0]=a[lc>>2],b[1]=a[lc+4>>2],f[0]),Jd=g+24|0,Pb=(b[0]=a[Jd>>2],b[1]=a[Jd+4>>2],f[0]);if(0==(mb&4|0)){if(0!=(mb&1|0)){if(4!=(a[C+12]|0)){var Hc=c+8|0,Tb=(b[0]=a[Hc>>2],b[1]=a[Hc+4>>2],f[0]),Yb=Pb>Tb?Pb:Tb,Wb=g+56|0;f[0]=Jb;a[Wb>>2]=b[0];a[Wb+4>>2]=b[1];var Qb=g+64|0;f[0]=Kb;a[Qb>>2]=b[0];a[Qb+4>>2]=b[1];var tc=g+72|0;f[0]=Mb;a[tc>>2]=b[0];a[tc+4>>2]=b[1];var Sc=g+80|0;f[0]=Yb;a[Sc>>2]=b[0];a[Sc+4>>2]=b[1];a[C+13]=1}else{var $b=Jb-1,Zb=(b[0]=a[w],b[1]=a[w+1],f[0]),qc=M+96|0,rd=(b[0]=a[qc>>2],b[1]=a[qc+4>>2],f[0]),oc=0>rd,Sb=Zb-((((oc?rd-.5:rd+.5)&-1)+1|0)/2&-1|0),uc=I|0,ac=(b[0]=a[uc>>2],b[1]=a[uc+4>>2],f[0]),nc=Sb-((a[a[B+5]+260>>2]|0)/2&-1|0),Bc=M+104|0,Sd=(b[0]=a[A],b[1]=a[A+1],f[0])-(b[0]=a[Bc>>2],b[1]=a[Bc+4>>2],f[0])-2,fd=Zb+((((oc?rd-.5:rd+.5)&-1)+1|0)/2&-1|0),kc=g+56|0;f[0]=$b;a[kc>>2]=b[0];a[kc+4>>2]=b[1];var yc=g+64|0;f[0]=nc;a[yc>>2]=b[0];a[yc+4>>2]=b[1];var ec=g+72|0;f[0]=ac;a[ec>>2]=b[0];a[ec+4>>2]=b[1];var fc=g+80|0;f[0]=Sb;a[fc>>2]=b[0];a[fc+4>>2]=b[1];var Tc=g+88|0;f[0]=$b;a[Tc>>2]=b[0];a[Tc+4>>2]=b[1];var mc=g+96|0;f[0]=Sb;a[mc>>2]=b[0];a[mc+4>>2]=b[1];var Mc=g+104|0;f[0]=Sd;a[Mc>>2]=b[0];a[Mc+4>>2]=b[1];var id=g+112|0;f[0]=fd;a[id>>2]=b[0];a[id+4>>2]=b[1];a[C+13]=2}}else{var bc=I|0,rc=(b[0]=a[bc>>2],b[1]=a[bc+4>>2],f[0]);if(0==(mb&8|0)){var jc=rc-1,Ac=4==(a[C+12]|0),dc=(b[0]=a[w],b[1]=a[w+1],f[0]),gc=M+96|0,Xb=(b[0]=a[gc>>2],b[1]=a[gc+4>>2],f[0]),Cc=0<=Xb;if(Ac){var Td=c+48|0,Ic=dc+((((Cc?Xb+.5:Xb-.5)&-1)+1|0)/2&-1|0),vc=(b[0]=a[Td>>2],b[1]=a[Td+4>>2],f[0])-1}else{var fe=Cc?Xb+.5:Xb-.5,Dc=c+48|0,Ic=(b[0]=a[Dc>>2],b[1]=a[Dc+4>>2],f[0]),vc=dc-(((fe&-1)+1|0)/2&-1|0)}var Ec=g+56|0;f[0]=jc;a[Ec>>2]=b[0];a[Ec+4>>2]=b[1];var jd=g+64|0;f[0]=vc;a[jd>>2]=b[0];a[jd+4>>2]=b[1];var md=g+72|0;f[0]=Mb;a[md>>2]=b[0];a[md+4>>2]=b[1];var je=g+80|0;f[0]=Ic;a[je>>2]=b[0];a[je+4>>2]=b[1]}else{var Gc=rc+1,Nc=4==(a[C+12]|0),Uc=(b[0]=a[w],b[1]=a[w+1],f[0]),zc=M+96|0,Kc=(b[0]=a[zc>>2],b[1]=a[zc+4>>2],f[0]),Fc=0<=Kc;if(Nc){var Qc=c+48|0,Yc=Uc+((((Fc?Kc+.5:Kc-.5)&-1)+1|0)/2&-1|0),kd=(b[0]=a[Qc>>2],b[1]=a[Qc+4>>2],f[0])-1}else{var Zc=Fc?Kc+.5:Kc-.5,Rc=c+48|0,Yc=(b[0]=a[Rc>>2],b[1]=a[Rc+4>>2],f[0])+1,kd=Uc-(((Zc&-1)+1|0)/2&-1|0)}var Lc=g+56|0;f[0]=Jb;a[Lc>>2]=b[0];a[Lc+4>>2]=b[1];var Oc=g+64|0;f[0]=kd;a[Oc>>2]=b[0];a[Oc+4>>2]=b[1];var Pc=g+72|0;f[0]=Gc;a[Pc>>2]=b[0];a[Pc+4>>2]=b[1];var nd=g+80|0;f[0]=Yc;a[nd>>2]=b[0];a[nd+4>>2]=b[1]}a[C+13]=1}}else{var zd=c+48|0,Jc=(b[0]=a[zd>>2],b[1]=a[zd+4>>2],f[0]),$c=Kb>2]=b[0];a[ad+4>>2]=b[1];var Xc=g+64|0;f[0]=$c;a[Xc>>2]=b[0];a[Xc+4>>2]=b[1];var cd=g+72|0;f[0]=Mb;a[cd>>2]=b[0];a[cd+4>>2]=b[1];var ld=g+80|0;f[0]=Pb;a[ld>>2]=b[0];a[ld+4>>2]=b[1];a[C+13]=1}var bd=0==m[d+124|0]<<24>>24;a:do{if(bd){var Kd=d}else{for(var Dd=d;;){var Vc=a[Dd+128>>2];if(0==m[Vc+124|0]<<24>>24){Kd=Vc;break a}else{Dd=Vc}}}}while(0);(M|0)==(a[Kd+12>>2]|0)?m[Kd+98|0]=0:m[Kd+58|0]=0;a[C+12]=mb;h=P;return}}}while(0);ja=zb?4:a[C+12]}}while(0);var ed=g+56|0,qd=g+52|0;if(0!=(G|0)){var Ad=J[G](M,X,ja,ed,qd);if(0!=(Ad|0)){a[C+12]=Ad;h=P;return}}k=ed>>2;l=g>>2;a[k]=a[l];a[k+1]=a[l+1];a[k+2]=a[l+2];a[k+3]=a[l+3];a[k+4]=a[l+4];a[k+5]=a[l+5];a[k+6]=a[l+6];a[k+7]=a[l+7];a[qd>>2]=1;if(1==(i|0)){e=(c+48|0)>>2;var Ud=(b[0]=a[e],b[1]=a[e+1],f[0]),Vd=g+64|0;f[0]=Ud;a[Vd>>2]=b[0];a[Vd+4>>2]=b[1];a[C+12]=4;var yd=(b[0]=a[e],b[1]=a[e+1],f[0])+1;f[0]=yd;a[e]=b[0];a[e+1]=b[1]}else{if(8==(i|0)){sa(wl|0,757,Cu|0,vd|0);var pd=c+48|0,gd=(b[0]=a[pd>>2],b[1]=a[pd+4>>2],f[0])+1,ud=g+64|0;f[0]=gd;a[ud>>2]=b[0];a[ud+4>>2]=b[1];a[C+12]=4}else{if(2==(i|0)){var Fd=4==(a[C+12]|0),dd=c+48|0,od=(b[0]=a[dd>>2],b[1]=a[dd+4>>2],f[0]);if(Fd){var sd=g+64|0;f[0]=od;a[sd>>2]=b[0];a[sd+4>>2]=b[1]}else{var Bd=g+80|0;f[0]=od;a[Bd>>2]=b[0];a[Bd+4>>2]=b[1]}}}}h=P}function UH(b,c,i,g,d,f,e){var k,b=a[c+(i<<2)>>2];0==m[b+56|0]<<24>>24?0!=m[b+96|0]<<24>>24&&(k=2867):k=2867;a:do{if(2867==k){k=m[b+61|0];var h=k&255;do{if(0==(h&8|0)){var j=m[b+101|0],p=0==(j&8)<<24>>24;if(p){if(k<<24>>24!=j<<24>>24){break a}if(0==(h&5|0)){break a}if(p){if(0!=(h&4|0)){Du(c,i,g,d,f,e);return}0==(h&1|0)?sa(wl|0,1114,VH|0,vd|0):WH(c,i,g,d,f,e);return}}}}while(0);if(0==(k&2)<<24>>24&&0==(m[b+101|0]&2)<<24>>24){XH(c,i,g,d,f,e);return}Du(c,i,g,d,f,e);return}}while(0);YH(c,i,g,d,f,e)}function YH(c,d,i,g,q,e){var l,k,n,j,p,s,v,t,u,w,A,B,C=h;h+=16080;var P=C+16e3,y=C+16016,D=C+16032,F=C+16048,M=C+16064,X=a[c+(d<<2)>>2];k=a[X+16>>2];var q=.5*q/(i|0),O=2>2;var E=(b[0]=a[B],b[1]=a[B+1],f[0]),q=(k+40|0)>>2,Da=(b[0]=a[q],b[1]=a[q+1],f[0]),ia=X+28|0,G=X+36|0,ia=(b[0]=a[ia>>2],b[1]=a[ia+4>>2],f[0])+E,G=(b[0]=a[G>>2],b[1]=a[G+4>>2],f[0])+Da,J=X+68|0;n=X+76|0;J=(b[0]=a[J>>2],b[1]=a[J+4>>2],f[0])+E;Da=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0])+Da;k=k+112|0;var I=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);k=E+I;var H=3*(k-ia),K=3*(k-J);if(0<(i|0)){var L=(G>2;w=P>>2;u=(C+32|0)>>2;t=y>>2;var ca=.5*(G+Da);v=(C+48|0)>>2;s=D>>2;p=(C+64|0)>>2;j=F>>2;n=(C+80|0)>>2;k=M>>2;var Q=C+96|0,S=C+104|0,U=C|0,i=i+d|0,ja=I,H=I>2]=b[0];a[N+4>>2]=b[1];f[0]=G;a[R>>2]=b[0];a[R+4>>2]=b[1];var aa=G+K;Pc(P,ia+H/3,aa);a[A]=a[w];a[A+1]=a[w+1];a[A+2]=a[w+2];a[A+3]=a[w+3];l=E+ja;Pc(y,l,aa);a[u]=a[t];a[u+1]=a[t+1];a[u+2]=a[t+2];a[u+3]=a[t+3];Pc(D,l,ca);a[v]=a[s];a[v+1]=a[s+1];a[v+2]=a[s+2];a[v+3]=a[s+3];aa=Da-K;Pc(F,l,aa);a[p]=a[j];a[p+1]=a[j+1];a[p+2]=a[j+2];a[p+3]=a[j+3];Pc(M,J+I/3,aa);a[n]=a[k];a[n+1]=a[k+1];a[n+2]=a[k+2];a[n+3]=a[k+3];f[0]=J;a[Q>>2]=b[0];a[Q+4>>2]=b[1];f[0]=Da;a[S>>2]=b[0];a[S+4>>2]=b[1];l=(X+108|0)>>2;var da=a[l];if(0!=(da|0)){var ea=da+24|0;0==(a[a[a[X+16>>2]+20>>2]+152>>2]&1|0)?(aa=da+32|0,ea|=0):(aa=ea|0,ea=da+32|0);var aa=(b[0]=a[aa>>2],b[1]=a[aa+4>>2],f[0]),ea=(b[0]=a[ea>>2],b[1]=a[ea+4>>2],f[0]),xa=(b[0]=a[B],b[1]=a[B+1],f[0])+ja+.5*ea,da=da+56|0;f[0]=xa;a[da>>2]=b[0];a[da+4>>2]=b[1];da=(b[0]=a[q],b[1]=a[q+1],f[0]);xa=a[l]+64|0;f[0]=da;a[xa>>2]=b[0];a[xa+4>>2]=b[1];m[a[l]+81|0]=1;ja=ea>g?ja+(ea-g):ja;K=K+O>2],U,7,e);if((d|0)==(i|0)){break}l=d;X=a[c+(d<<2)>>2]}}h=C}function Du(c,d,i,g,q,e){var l,k,n,j,p,s,v,t,u,w,A,B,C=h;h+=16080;var P=C+16e3,y=C+16016,D=C+16032,F=C+16048,M=C+16064,X=a[c+(d<<2)>>2];k=a[X+16>>2];var g=.5*g/(i|0),O=2>2;var E=(b[0]=a[B],b[1]=a[B+1],f[0]),g=(k+40|0)>>2,Da=(b[0]=a[g],b[1]=a[g+1],f[0]),ia=X+28|0,G=X+36|0,ia=(b[0]=a[ia>>2],b[1]=a[ia+4>>2],f[0])+E,G=(b[0]=a[G>>2],b[1]=a[G+4>>2],f[0])+Da;n=X+68|0;var J=X+76|0,E=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0])+E,J=(b[0]=a[J>>2],b[1]=a[J+4>>2],f[0])+Da;k=k+96|0;var I=.5*(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);k=Da+I;var H=3*(k-G),K=3*(k-J);if(0<(i|0)){var L=(ia>2;w=P>>2;u=(C+32|0)>>2;t=y>>2;var ca=.5*(ia+E);v=(C+48|0)>>2;s=D>>2;p=(C+64|0)>>2;j=F>>2;n=(C+80|0)>>2;k=M>>2;var Q=C+96|0,S=C+104|0,U=C|0,i=i+d|0,ja=0,H=I>2]=b[0];a[N+4>>2]=b[1];f[0]=G;a[R>>2]=b[0];a[R+4>>2]=b[1];var aa=ia+ja;Pc(P,aa,G+H/3);a[A]=a[w];a[A+1]=a[w+1];a[A+2]=a[w+2];a[A+3]=a[w+3];l=Da+I;Pc(y,aa,l);a[u]=a[t];a[u+1]=a[t+1];a[u+2]=a[t+2];a[u+3]=a[t+3];Pc(D,ca,l);a[v]=a[s];a[v+1]=a[s+1];a[v+2]=a[s+2];a[v+3]=a[s+3];aa=E-ja;Pc(F,aa,l);a[p]=a[j];a[p+1]=a[j+1];a[p+2]=a[j+2];a[p+3]=a[j+3];Pc(M,aa,J+K/3);a[n]=a[k];a[n+1]=a[k+1];a[n+2]=a[k+2];a[n+3]=a[k+3];f[0]=E;a[Q>>2]=b[0];a[Q+4>>2]=b[1];f[0]=J;a[S>>2]=b[0];a[S+4>>2]=b[1];l=(X+108|0)>>2;var da=a[l];if(0!=(da|0)){var ea=da+24|0;if(0==(a[a[a[X+16>>2]+20>>2]+152>>2]&1|0)){var aa=da+32|0,xa=ea|0}else{aa=ea|0,xa=da+32|0}ea=(b[0]=a[aa>>2],b[1]=a[aa+4>>2],f[0]);aa=(b[0]=a[xa>>2],b[1]=a[xa+4>>2],f[0]);xa=(b[0]=a[g],b[1]=a[g+1],f[0])+I+.5*ea;da=da+64|0;f[0]=xa;a[da>>2]=b[0];a[da+4>>2]=b[1];da=(b[0]=a[B],b[1]=a[B+1],f[0]);xa=a[l]+56|0;f[0]=da;a[xa>>2]=b[0];a[xa+4>>2]=b[1];m[a[l]+81|0]=1;I=ea>q?I+(ea-q):I;ja+O>2],U,7,e);if((d|0)==(i|0)){break}l=d;X=a[c+(d<<2)>>2]}}h=C}function XH(c,d,i,g,q,e){var l,k,n,j,p,s,v,t,u,w,A,B,C=h;h+=16080;var P=C+16e3,y=C+16016,D=C+16032,F=C+16048,M=C+16064,X=a[c+(d<<2)>>2];k=a[X+16>>2];var q=.5*q/(i|0),O=2>2;var E=(b[0]=a[B],b[1]=a[B+1],f[0]),q=(k+40|0)>>2,G=(b[0]=a[q],b[1]=a[q+1],f[0]),ia=X+28|0,J=X+36|0,ia=(b[0]=a[ia>>2],b[1]=a[ia+4>>2],f[0])+E,J=(b[0]=a[J>>2],b[1]=a[J+4>>2],f[0])+G,I=X+68|0;n=X+76|0;I=(b[0]=a[I>>2],b[1]=a[I+4>>2],f[0])+E;G=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0])+G;k=k+104|0;var H=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),K=3*(ia+H-E),L=3*(I+H-E);if(0<(i|0)){var N=(J>2;w=P>>2;u=(C+32|0)>>2;t=y>>2;var ca=.5*(J+G);v=(C+48|0)>>2;s=D>>2;p=(C+64|0)>>2;j=F>>2;n=(C+80|0)>>2;k=M>>2;var S=C+96|0,U=C+104|0,zb=C|0,i=i+d|0,ja=H,K=H>2]=b[0];a[R+4>>2]=b[1];f[0]=J;a[Q>>2]=b[0];a[Q+4>>2]=b[1];var aa=J+L;Pc(P,ia-K/3,aa);a[A]=a[w];a[A+1]=a[w+1];a[A+2]=a[w+2];a[A+3]=a[w+3];l=E-ja;Pc(y,l,aa);a[u]=a[t];a[u+1]=a[t+1];a[u+2]=a[t+2];a[u+3]=a[t+3];Pc(D,l,ca);a[v]=a[s];a[v+1]=a[s+1];a[v+2]=a[s+2];a[v+3]=a[s+3];aa=G-L;Pc(F,l,aa);a[p]=a[j];a[p+1]=a[j+1];a[p+2]=a[j+2];a[p+3]=a[j+3];Pc(M,I-H/3,aa);a[n]=a[k];a[n+1]=a[k+1];a[n+2]=a[k+2];a[n+3]=a[k+3];f[0]=I;a[S>>2]=b[0];a[S+4>>2]=b[1];f[0]=G;a[U>>2]=b[0];a[U+4>>2]=b[1];l=(X+108|0)>>2;var da=a[l];if(0!=(da|0)){var ea=da+24|0;0==(a[a[a[X+16>>2]+20>>2]+152>>2]&1|0)?(aa=da+32|0,ea|=0):(aa=ea|0,ea=da+32|0);var aa=(b[0]=a[aa>>2],b[1]=a[aa+4>>2],f[0]),ea=(b[0]=a[ea>>2],b[1]=a[ea+4>>2],f[0]),xa=(b[0]=a[B],b[1]=a[B+1],f[0])-ja-.5*ea,da=da+56|0;f[0]=xa;a[da>>2]=b[0];a[da+4>>2]=b[1];da=(b[0]=a[q],b[1]=a[q+1],f[0]);xa=a[l]+64|0;f[0]=da;a[xa>>2]=b[0];a[xa+4>>2]=b[1];m[a[l]+81|0]=1;ja=ea>g?ja+(ea-g):ja;L=L+O>2],zb,7,e);if((d|0)==(i|0)){break}l=d;X=a[c+(d<<2)>>2]}}h=C}function Pc(c,d,i){var g=c|0;f[0]=d;a[g>>2]=b[0];a[g+4>>2]=b[1];c=c+8|0;f[0]=i;a[c>>2]=b[0];a[c+4>>2]=b[1]}function WH(c,d,i,g,q,e){var l,k,n,j,p,s,v,t,u,w,A,B,C=h;h+=16080;var P=C+16e3,y=C+16016,D=C+16032,F=C+16048,M=C+16064,X=a[c+(d<<2)>>2];k=a[X+16>>2];var g=.5*g/(i|0),O=2>2;var E=(b[0]=a[B],b[1]=a[B+1],f[0]),g=(k+40|0)>>2,G=(b[0]=a[g],b[1]=a[g+1],f[0]),ia=X+28|0,J=X+36|0,ia=(b[0]=a[ia>>2],b[1]=a[ia+4>>2],f[0])+E,J=(b[0]=a[J>>2],b[1]=a[J+4>>2],f[0])+G;n=X+68|0;var I=X+76|0,E=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0])+E,I=(b[0]=a[I>>2],b[1]=a[I+4>>2],f[0])+G;k=k+96|0;var H=.5*(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),K=3*(J+H-G),L=3*(I+H-G);if(0<(i|0)){var N=(ia>2;w=P>>2;u=(C+32|0)>>2;t=y>>2;var ca=.5*(ia+E);v=(C+48|0)>>2;s=D>>2;p=(C+64|0)>>2;j=F>>2;n=(C+80|0)>>2;k=M>>2;var S=C+96|0,U=C+104|0,zb=C|0,i=i+d|0,K=H>2]=b[0];a[R+4>>2]=b[1];f[0]=J;a[Q>>2]=b[0];a[Q+4>>2]=b[1];var aa=ia+ja;Pc(P,aa,J-K/3);a[A]=a[w];a[A+1]=a[w+1];a[A+2]=a[w+2];a[A+3]=a[w+3];l=G-H;Pc(y,aa,l);a[u]=a[t];a[u+1]=a[t+1];a[u+2]=a[t+2];a[u+3]=a[t+3];Pc(D,ca,l);a[v]=a[s];a[v+1]=a[s+1];a[v+2]=a[s+2];a[v+3]=a[s+3];aa=E-ja;Pc(F,aa,l);a[p]=a[j];a[p+1]=a[j+1];a[p+2]=a[j+2];a[p+3]=a[j+3];Pc(M,aa,I-L/3);a[n]=a[k];a[n+1]=a[k+1];a[n+2]=a[k+2];a[n+3]=a[k+3];f[0]=E;a[S>>2]=b[0];a[S+4>>2]=b[1];f[0]=I;a[U>>2]=b[0];a[U+4>>2]=b[1];l=(X+108|0)>>2;var da=a[l];if(0!=(da|0)){var ea=da+24|0;if(0==(a[a[a[X+16>>2]+20>>2]+152>>2]&1|0)){var aa=da+32|0,xa=ea|0}else{aa=ea|0,xa=da+32|0}ea=(b[0]=a[aa>>2],b[1]=a[aa+4>>2],f[0]);aa=(b[0]=a[xa>>2],b[1]=a[xa+4>>2],f[0]);xa=(b[0]=a[g],b[1]=a[g+1],f[0])-H-.5*ea;da=da+64|0;f[0]=xa;a[da>>2]=b[0];a[da+4>>2]=b[1];da=(b[0]=a[B],b[1]=a[B+1],f[0]);xa=a[l]+56|0;f[0]=da;a[xa>>2]=b[0];a[xa+4>>2]=b[1];m[a[l]+81|0]=1;H=ea>q?H+(ea-q):H;ja+O>2],zb,7,e);if((d|0)==(i|0)){break}l=d;X=a[c+(d<<2)>>2]}}h=C}function Eu(c,d){var i,g,q,e,l,k,n=h;h+=96;var j=n+64,p=n+80;if(6!=m[c+124|0]<<24>>24){k=0==d<<24>>24;var s=a[(k?c+116|0:c+112|0)>>2];i=yl(c)>>2;k?(i=a[i],k=i>>2,0==(a[k+2]|0)?(i=a[k],p=i|0,p=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),k=i+8|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),g=n>>2,l=i>>2,a[g]=a[l],a[g+1]=a[l+1],a[g+2]=a[l+2],a[g+3]=a[l+3],g=(n+16|0)>>2,l=(i+16|0)>>2,a[g]=a[l],a[g+1]=a[l+1],a[g+2]=a[l+2],a[g+3]=a[l+3],g=(n+32|0)>>2,l=(i+32|0)>>2,a[g]=a[l],a[g+1]=a[l+1],a[g+2]=a[l+2],a[g+3]=a[l+3],l=(n+48|0)>>2,i=(i+48|0)>>2,a[l]=a[i],a[l+1]=a[i+1],a[l+2]=a[i+2],a[l+3]=a[i+3],Ld(j,n|0,3,.1,0,0),i=j|0,l=j+8|0,j=k,k=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])):(p=i+16|0,p=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),j=i+24|0,j=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]),i=a[k],k=i|0,i=i+8|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),l=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]))):(k=a[i+1]-1|0,i=a[i],l=i>>2,j=i+48*k|0,0==(a[l+(12*k|0)+3]|0)?(g=a[l+(12*k|0)+1],i=g-1|0,l=a[j>>2],j=(i<<4)+l|0,j=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]),k=(i<<4)+l+8|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),e=n>>2,q=((g-4<<4)+l|0)>>2,a[e]=a[q],a[e+1]=a[q+1],a[e+2]=a[q+2],a[e+3]=a[q+3],e=(n+16|0)>>2,q=((g-3<<4)+l|0)>>2,a[e]=a[q],a[e+1]=a[q+1],a[e+2]=a[q+2],a[e+3]=a[q+3],q=(n+32|0)>>2,g=((g-2<<4)+l|0)>>2,a[q]=a[g],a[q+1]=a[g+1],a[q+2]=a[g+2],a[q+3]=a[g+3],g=(n+48|0)>>2,i=((i<<4)+l|0)>>2,a[g]=a[i],a[g+1]=a[i+1],a[g+2]=a[i+2],a[g+3]=a[i+3],Ld(p,n|0,3,.9,0,0),i=p|0,l=p+8|0,p=j,j=k,k=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])):(p=i+48*k+32|0,p=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),i=i+48*k+40|0,i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),l=a[l+(12*k|0)+1]-1|0,j=a[j>>2],k=(l<<4)+j|0,l=(l<<4)+j+8|0,j=i,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])),l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]));i=c|0;k=Cf(l-j,k-p)+3.141592653589793*(Xb(i,a[Fu>>2],-25,-180)/180);i=10*Xb(i,a[Gu>>2],1,0);p+=i*se(k);l=s+56|0;f[0]=p;a[l>>2]=b[0];a[l+4>>2]=b[1];p=j+i*Ce(k);j=s+64|0;f[0]=p;a[j>>2]=b[0];a[j+4>>2]=b[1];m[s+81|0]=1}h=n}function ZH(b){var c=Ba(b);if((c|0)<(a[Hu>>2]|0)){c=a[zl>>2]}else{if(c=c+11|0,a[Hu>>2]=c,c=wb(a[zl>>2],c),a[zl>>2]=c,0==(c|0)){return 0}}var i=m[b];if(0==i<<24>>24){var g=c}else{for(;;){var b=b+1|0,d=i&255,g=c+1|0;m[c]=0==((65<=d&&90>=d)|0)?i:vf(d)&255;i=m[b];if(0==i<<24>>24){break}else{c=g}}c=a[zl>>2]}m[g]=0;return c}function yl(b){for(var c;;){var i=a[b+24>>2];if(0!=(i|0)){c=105;break}if(0==m[b+124|0]<<24>>24){c=104;break}b=a[b+128>>2]}if(104==c){S()}else{if(105==c){return i}}}function Bk(b){a[aj>>2]=b}function Iu(c,d,i,g,q,e,l){l>>=2;e>>=2;q>>=2;g>>=2;f[0]=1-c;a[g]=b[0];a[g+1]=b[1];f[0]=1-d;a[q]=b[0];a[q+1]=b[1];f[0]=1-i;a[e]=b[0];a[e+1]=b[1];c=(b[0]=a[g],b[1]=a[g+1],f[0]);d=(b[0]=a[q],b[1]=a[q+1],f[0]);c=c>=2;q>>=2;g>>=2;if(0c?6*c:0,c=l&-1,k=l-(c|0),l=(1-d)*i,h=(1-k*d)*i,d=(1-(1-k)*d)*i;0==(c|0)?(f[0]=i,a[g]=b[0],a[g+1]=b[1],f[0]=d,a[q]=b[0],a[q+1]=b[1],f[0]=l,a[e]=b[0],a[e+1]=b[1]):1==(c|0)?(f[0]=h,a[g]=b[0],a[g+1]=b[1],f[0]=i,a[q]=b[0],a[q+1]=b[1],f[0]=l,a[e]=b[0],a[e+1]=b[1]):2==(c|0)?(f[0]=l,a[g]=b[0],a[g+1]=b[1],f[0]=i,a[q]=b[0],a[q+1]=b[1],f[0]=d,a[e]=b[0],a[e+1]=b[1]):3==(c|0)?(f[0]=l,a[g]=b[0],a[g+1]=b[1],f[0]=h,a[q]=b[0],a[q+1]=b[1],f[0]=i,a[e]=b[0],a[e+1]=b[1]):5==(c|0)?(f[0]=i,a[g]=b[0],a[g+1]=b[1],f[0]=l,a[q]=b[0],a[q+1]=b[1],f[0]=h,a[e]=b[0],a[e+1]=b[1]):4==(c|0)&&(f[0]=d,a[g]=b[0],a[g+1]=b[1],f[0]=l,a[q]=b[0],a[q+1]=b[1],f[0]=i,a[e]=b[0],a[e+1]=b[1])}else{f[0]=i,a[g]=b[0],a[g+1]=b[1],f[0]=i,a[q]=b[0],a[q+1]=b[1],f[0]=i,a[e]=b[0],a[e+1]=b[1]}}function $H(c,d,i){var g,q,Xa,l,k,n,z,p,s,v,t,u,w,A,B,C,P=d>>2,y=h;h+=108;var D,F=y+12;C=F>>2;var M=y+20;B=M>>2;var X=y+28;A=X>>2;var O=y+36;w=O>>2;var E=y+44;u=E>>2;var G=y+52;t=G>>2;var ia=y+60;v=ia>>2;var J=y+68;s=J>>2;var I=y+76;p=I>>2;var H=y+84;z=H>>2;var K=y+92;n=K>>2;var L=y+96;k=L>>2;var N=y+100;l=N>>2;var R=y+104;Xa=R>>2;a[P+8]=i;for(var Q=c;;){if(32==m[Q]<<24>>24){Q=Q+1|0}else{break}}a[Xa]=255;var ca=m[Q];if(35==ca<<24>>24){if(2<(Cd(Q,aI|0,(j=h,h+=16,a[j>>2]=K,a[j+4>>2]=L,a[j+8>>2]=N,a[j+12>>2]=R,j))|0)){if(0==(i|0)){var S=(a[n]>>>0)/255;f[0]=S;a[w]=b[0];a[w+1]=b[1];var U=(a[k]>>>0)/255;f[0]=U;a[u]=b[0];a[u+1]=b[1];var zb=(a[l]>>>0)/255;f[0]=zb;a[t]=b[0];a[t+1]=b[1];var ja=(a[Xa]>>>0)/255,aa=Uzb?U:zb,ea=daS?S:aa),W=xa/ea;if(0Y?Y+360:Y,Z=W}else{var $=0,Z=W}}else{Z=$=0}f[0]=$/360;a[F>>2]=b[0];a[F+4>>2]=b[1];f[0]=ea;a[X>>2]=b[0];a[X+4>>2]=b[1];f[0]=Z;a[M>>2]=b[0];a[M+4>>2]=b[1];var tb=(b[0]=a[C],b[1]=a[C+1],f[0]),ya=d|0;f[0]=tb;a[ya>>2]=b[0];a[ya+4>>2]=b[1];var la=(b[0]=a[B],b[1]=a[B+1],f[0]),wa=d+8|0;f[0]=la;a[wa>>2]=b[0];a[wa+4>>2]=b[1];var Ab=(b[0]=a[A],b[1]=a[A+1],f[0]),Fa=d+16|0;f[0]=Ab;a[Fa>>2]=b[0];a[Fa+4>>2]=b[1];var Ga=d+24|0;f[0]=ja;a[Ga>>2]=b[0];a[Ga+4>>2]=b[1];var ba=0}else{if(1==(i|0)){m[d]=a[n]&255,m[d+1|0]=a[k]&255,m[d+2|0]=a[l]&255,m[d+3|0]=a[Xa]&255}else{if(3==(i|0)){var ta=(a[n]>>>0)/255;f[0]=ta;a[w]=b[0];a[w+1]=b[1];var Ka=(a[k]>>>0)/255;f[0]=Ka;a[u]=b[0];a[u+1]=b[1];var za=(a[l]>>>0)/255;f[0]=za;a[t]=b[0];a[t+1]=b[1];Iu(ta,Ka,za,ia,J,I,H);m[d]=255*((b[0]=a[v],b[1]=a[v+1],f[0])&-1)&255;m[d+1|0]=255*((b[0]=a[s],b[1]=a[s+1],f[0])&-1)&255;m[d+2|0]=255*((b[0]=a[p],b[1]=a[p+1],f[0])&-1)&255;m[d+3|0]=255*((b[0]=a[z],b[1]=a[z+1],f[0])&-1)&255}else{if(2==(i|0)){a[P]=Math.floor(((65535*a[n]|0)>>>0)/255),a[d+4>>2]=Math.floor(((65535*a[k]|0)>>>0)/255),a[P+2]=Math.floor(((65535*a[l]|0)>>>0)/255),a[d+12>>2]=Math.floor(((65535*a[Xa]|0)>>>0)/255)}else{if(4==(i|0)){var ma=d|0;f[0]=(a[n]>>>0)/255;a[ma>>2]=b[0];a[ma+4>>2]=b[1];var pa=d+8|0;f[0]=(a[k]>>>0)/255;a[pa>>2]=b[0];a[pa+4>>2]=b[1];var fa=d+16|0;f[0]=(a[l]>>>0)/255;a[fa>>2]=b[0];a[fa+4>>2]=b[1];var Ha=d+24|0;f[0]=(a[Xa]>>>0)/255;a[Ha>>2]=b[0];a[Ha+4>>2]=b[1]}}}}ba=0}h=y;return ba}var Ra=m[Q]}else{Ra=ca}46==Ra<<24>>24?D=150:10>((Ra&255)-48|0)>>>0&&(D=150);do{if(150==D){var sa=Ba(Q);if((sa|0)<(a[Ju>>2]|0)){var ra=a[lp>>2]}else{var La=sa+11|0;a[Ju>>2]=La;var qa=wb(a[lp>>2],La);a[lp>>2]=qa;if(0==(qa|0)){return ba=-1,h=y,ba}ra=qa}for(var Ya=Q,na=ra;;){var Za=Ya+1|0,oa=m[Ya];if(44==oa<<24>>24){var ab=32}else{if(0==oa<<24>>24){break}else{ab=oa}}m[na]=ab;Ya=Za;na=na+1|0}m[na]=0;if(3==(Cd(ra,bI|0,(j=h,h+=12,a[j>>2]=F,a[j+4>>2]=M,a[j+8>>2]=X,j))|0)){var $a=(b[0]=a[C],b[1]=a[C+1],f[0]),jb=1>$a?$a:1,Ca=0Ia?Ia:1,ub=0Sa?Sa:1,ua=0>2]=65535*(b[0]=a[u],b[1]=a[u+1],f[0])&-1;a[P+2]=65535*(b[0]=a[t],b[1]=a[t+1],f[0])&-1;a[Oa+12>>2]=65535}else{if(4==(i|0)){kp(Ca,ub,ua,O,E,G);var Wa=(b[0]=a[w],b[1]=a[w+1],f[0]),pb=d|0;f[0]=Wa;a[pb>>2]=b[0];a[pb+4>>2]=b[1];var ob=(b[0]=a[u],b[1]=a[u+1],f[0]),bb=d+8|0;f[0]=ob;a[bb>>2]=b[0];a[bb+4>>2]=b[1];var qb=(b[0]=a[t],b[1]=a[t+1],f[0]),Aa=d+16|0;f[0]=qb;a[Aa>>2]=b[0];a[Aa+4>>2]=b[1];var kb=d+24|0;f[0]=1;a[kb>>2]=b[0];a[kb+4>>2]=b[1]}else{if(0==(i|0)){var Ea=d|0;f[0]=Ca;a[Ea>>2]=b[0];a[Ea+4>>2]=b[1];var vb=d+8|0;f[0]=ub;a[vb>>2]=b[0];a[vb+4>>2]=b[1];var xb=d+16|0;f[0]=ua;a[xb>>2]=b[0];a[xb+4>>2]=b[1];var hd=d+24|0;f[0]=1;a[hd>>2]=b[0];a[hd+4>>2]=b[1]}else{if(1==(i|0)){kp(Ca,ub,ua,O,E,G);var nb=d;m[d]=255*(b[0]=a[w],b[1]=a[w+1],f[0])&255;m[nb+1|0]=255*(b[0]=a[u],b[1]=a[u+1],f[0])&255;m[nb+2|0]=255*(b[0]=a[t],b[1]=a[t+1],f[0])&255;m[nb+3|0]=-1}else{if(3==(i|0)){kp(Ca,ub,ua,O,E,G);var rb=(b[0]=a[w],b[1]=a[w+1],f[0]);Iu(rb,(b[0]=a[u],b[1]=a[u+1],f[0]),(b[0]=a[t],b[1]=a[t+1],f[0]),ia,J,I,H);var lb=d;m[d]=255*((b[0]=a[v],b[1]=a[v+1],f[0])&-1)&255;m[lb+1|0]=255*((b[0]=a[s],b[1]=a[s+1],f[0])&-1)&255;m[lb+2|0]=255*((b[0]=a[p],b[1]=a[p+1],f[0])&-1)&255;m[lb+3|0]=255*((b[0]=a[z],b[1]=a[z+1],f[0])&-1)&255}}}}}ba=0;h=y;return ba}}}while(0);var Ta=cI(Q);a[y>>2]=Ta;if(0==(Ta|0)){return ba=-1,h=y,ba}var cb=a[Ku>>2];if(0==(cb|0)){D=168}else{var fb=a[cb>>2];if(m[fb]<<24>>24!=m[Ta]<<24>>24){D=168}else{if(0==(ka(fb,Ta)|0)){var Ua=cb}else{D=168}}}if(168==D){var sb=tn(y,e,2491,12,314),Ua=a[Ku>>2]=sb}if(0==(Ua|0)){if(0==(i|0)){var Na=d+24|0;q=d>>2;a[q]=0;a[q+1]=0;a[q+2]=0;a[q+3]=0;a[q+4]=0;a[q+5]=0;f[0]=1;a[Na>>2]=b[0];a[Na+4>>2]=b[1]}else{if(1==(i|0)){m[d+2|0]=0,m[d+1|0]=0,m[d]=0,m[d+3|0]=-1}else{if(3==(i|0)){Pb=0,m[d]=Pb&255,Pb>>=8,m[d+1]=Pb&255,Pb>>=8,m[d+2]=Pb&255,Pb>>=8,m[d+3]=Pb&255}else{if(2==(i|0)){a[P+2]=0,a[d+4>>2]=0,a[P]=0,a[d+12>>2]=65535}else{if(4==(i|0)){var Fb=d+24|0;g=d>>2;a[g]=0;a[g+1]=0;a[g+2]=0;a[g+3]=0;a[g+4]=0;a[g+5]=0;f[0]=1;a[Fb>>2]=b[0];a[Fb+4>>2]=b[1]}}}}}ba=1}else{if(0==(i|0)){var Db=d|0;f[0]=(m[Ua+4|0]&255)/255;a[Db>>2]=b[0];a[Db+4>>2]=b[1];var Ob=d+8|0;f[0]=(m[Ua+5|0]&255)/255;a[Ob>>2]=b[0];a[Ob+4>>2]=b[1];var Eb=d+16|0;f[0]=(m[Ua+6|0]&255)/255;a[Eb>>2]=b[0];a[Eb+4>>2]=b[1];var Ma=d+24|0;f[0]=(m[Ua+10|0]&255)/255;a[Ma>>2]=b[0];a[Ma+4>>2]=b[1]}else{if(1==(i|0)){m[d]=m[Ua+7|0],m[d+1|0]=m[Ua+8|0],m[d+2|0]=m[Ua+9|0],m[d+3|0]=m[Ua+10|0]}else{if(3==(i|0)){var Bb=(m[Ua+7|0]&255|0)/255;f[0]=Bb;a[w]=b[0];a[w+1]=b[1];var Ja=(m[Ua+8|0]&255|0)/255;f[0]=Ja;a[u]=b[0];a[u+1]=b[1];var Qa=(m[Ua+9|0]&255|0)/255;f[0]=Qa;a[t]=b[0];a[t+1]=b[1];Iu(Bb,Ja,Qa,ia,J,I,H);m[d]=255*((b[0]=a[v],b[1]=a[v+1],f[0])&-1)&255;m[d+1|0]=255*((b[0]=a[s],b[1]=a[s+1],f[0])&-1)&255;m[d+2|0]=255*((b[0]=a[p],b[1]=a[p+1],f[0])&-1)&255;m[d+3|0]=255*((b[0]=a[z],b[1]=a[z+1],f[0])&-1)&255}else{if(2==(i|0)){a[P]=Math.floor(((65535*(m[Ua+7|0]&255)|0)>>>0)/255),a[d+4>>2]=Math.floor(((65535*(m[Ua+8|0]&255)|0)>>>0)/255),a[P+2]=Math.floor(((65535*(m[Ua+9|0]&255)|0)>>>0)/255),a[d+12>>2]=Math.floor(((65535*(m[Ua+10|0]&255)|0)>>>0)/255)}else{if(4==(i|0)){var Cb=d|0;f[0]=(m[Ua+7|0]&255|0)/255;a[Cb>>2]=b[0];a[Cb+4>>2]=b[1];var Va=d+8|0;f[0]=(m[Ua+8|0]&255|0)/255;a[Va>>2]=b[0];a[Va+4>>2]=b[1];var hb=d+16|0;f[0]=(m[Ua+9|0]&255|0)/255;a[hb>>2]=b[0];a[hb+4>>2]=b[1];var Pa=d+24|0;f[0]=(m[Ua+10|0]&255|0)/255;a[Pa>>2]=b[0];a[Pa+4>>2]=b[1]}}}}}ba=0}h=y;return ba}function cI(b){var c=m[b];if(98==c<<24>>24){return b}var i=b+1|0;if(0==(td(i,dI|0,4)|0)|119==c<<24>>24||0==(td(i,eI|0,4)|0)|108==c<<24>>24||0==(td(i,fI|0,8)|0)){return b}c=47==c<<24>>24;a:do{if(c){var d=Jc(i,47);if(0==(d|0)){d=i}else{if(47!=m[i]<<24>>24){d=0==(Df(mp|0,i,4)|0)?d+1|0:b}else{d=a[aj>>2];do{if(0!=(d|0)&&0!=m[d]<<24>>24&&0!=(Df(mp|0,d,3)|0)){d=gI(a[aj>>2],b+2|0);break a}}while(0);d=b+2|0}}}else{d=a[aj>>2],d=0==(d|0)?b:0==m[d]<<24>>24?b:0==(Df(mp|0,d,3)|0)?b:gI(a[aj>>2],b)}}while(0);return b=ZH(d)}function gI(b,c){var i=h,d=Ba(b)+Ba(c)|0;(d+3|0)<(a[Lu>>2]|0)?d=a[Al>>2]:(d=d+13|0,a[Lu>>2]=d,d=wb(a[Al>>2],d),a[Al>>2]=d);Ma(d,hI|0,(j=h,h+=8,a[j>>2]=b,a[j+4>>2]=c,j));d=a[Al>>2];h=i;return d}function iI(b){var c,i=h;h+=1112;var d=i+1024,f=i+1096,e=i+1100,l=i+1104,k=i+1108,n=a[bj>>2];0==(n|0)&&(n=Nc(Mu,a[Pn>>2]),a[bj>>2]=n);n=J[a[n>>2]](n,b,512);if(0!=(n|0)){return h=i,n}n=qi(b,cj|0);if(0==(n|0)){return la(0,jI|0,(j=h,h+=4,a[j>>2]=b,j)),h=i,0}c=i|0;for(var z=0,p=0;;){if(0==(oi(c,1024,n)|0)){var s=z,v=p;break}p=4==(Cd(c,Nu|0,(j=h,h+=16,a[j>>2]=f,a[j+4>>2]=e,a[j+8>>2]=l,a[j+12>>2]=k,j))|0)?1:p;z=37==m[c]<<24>>24?z:0==(un(c,kI|0)|0)?z:1;if(!(0==(p|0)|0==(z|0))){s=z;v=p;break}}0==(v|0)?(la(0,lI|0,(j=h,h+=4,a[j>>2]=b,j)),s=0):(v=Cb(64),c=v>>2,a[c+8]=a[f>>2],z=v+36|0,a[z>>2]=a[e>>2],a[c+10]=a[l>>2]-a[f>>2]|0,a[z>>2]=a[k>>2]-a[e>>2]|0,a[c+2]=b,b=a[Ou>>2],a[Ou>>2]=b+1|0,a[c+3]=b,kC(n,d),d=a[d+28>>2],b=Cb(d+1|0),a[c+13]=b,xg(n,0,0),lC(b,d,1,n),m[b+d|0]=0,d=a[bj>>2],J[a[d>>2]](d,v,1),m[v+16|0]=s&255,s=v);ri(n);h=i;return s}function np(b,c,i){var d=h,f,e=0!=(c|0);a:do{if(e){for(var l=1,k=0;;){if(0==l<<24>>24){break a}var n=a[c+(k<<2)>>2];if(0==(n|0)){f=280;break a}l=0==m[n]<<24>>24?0:l;k=k+1|0}}else{f=280}}while(0);a:do{if(280==f&&(n=a[i>>2],0!=(n|0))){l=b;for(k=i;;){if(y(l,n),y(l,wd|0),k=k+4|0,n=a[k>>2],0==(n|0)){break a}}}}while(0);if(e&&(f=a[c>>2],0!=(f|0))){for(i=0;;){do{if(0!=m[f]<<24>>24){if(l=Uk(f),0==(l|0)){la(0,mI|0,(j=h,h+=4,a[j>>2]=f,j))}else{if(e=qi(l,cj|0),0==(e|0)){la(0,nI|0,(j=h,h+=4,a[j>>2]=l,j))}else{l=$E(e);k=0==(l|0);a:do{if(!k){for(n=l;;){if(y(b,n),n=$E(e),0==(n|0)){break a}}}}while(0);y(b,wd|0);ri(e)}}}}while(0);i=i+1|0;f=a[c+(i<<2)>>2];if(0==(f|0)){break}}}h=d}function Pu(b,c){var i,d=a[c+52>>2];a:for(;;){var f=m[d];b:do{if(37==f<<24>>24){if(37!=m[d+1|0]<<24>>24){var e=d}else{f=d+2|0;do{if(0==(Df(f,oI|0,3)|0)){e=d}else{if(0==(Df(f,pI|0,5)|0)){e=d}else{if(0==(Df(f,qI|0,3)|0)){e=d}else{if(0==(Df(f,rI|0,7)|0)){e=d}else{e=d;break b}}}}}while(0);for(;;){var l=m[e];if(13==l<<24>>24){i=311;break}else{if(0==l<<24>>24||10==l<<24>>24){i=313;break}}e=e+1|0}do{if(311==i){if(i=0,d=e+1|0,10!=m[d]<<24>>24){var k=d}else{d=e+2|0;continue a}}else{313==i&&(i=0,k=e+1|0)}}while(0);d=0==l<<24>>24?e:k;continue a}}else{if(0==f<<24>>24){break a}else{e=d}}}while(0);for(;;){var h=m[e];if(0==h<<24>>24||10==h<<24>>24){i=319;break}else{if(13==h<<24>>24){i=317;break}}sI(b,h<<24>>24);e=e+1|0}if(319==i){var j=e+1|0;i=320}else{if(317==i){if(i=0,d=e+1|0,10!=m[d]<<24>>24){j=d,i=320}else{var p=e+2|0}}}320==i&&(i=0,p=0==h<<24>>24?e:j);sI(b,10);d=p}}function Qu(b,c){var i=h;if(0==(c|0)){var d;var f=b,e=0;b:for(;;){for(;;){var l=m[f];if(0==l<<24>>24){var k=e;d=246;break b}if(127>(l&255)){f=f+1|0}else{break}}if(-64==(l&-4)<<24>>24){f=f+2|0,e=1}else{k=2;d=245;break}}d=245==d||246==d?k:cc;1==(d|0)?d=bt(b):(2==(d|0)&&!m[Ru]&&(la(0,tI|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),m[Ru]=1),d=b)}else{d=bt(b)}0==(a[Mb>>2]|0)&&fc(Mb,0,0);e=a[Mb+4>>2];e>>>0>2]>>>0||(na(Mb,1),e=a[Mb+4>>2]);a[Mb+4>>2]=e+1|0;m[e]=40;for(e=d;;){l=m[e];if(40==l<<24>>24||41==l<<24>>24||92==l<<24>>24){l=a[Mb+4>>2],l>>>0>2]>>>0||(na(Mb,1),l=a[Mb+4>>2]),a[Mb+4>>2]=l+1|0,m[l]=92}else{if(0==l<<24>>24){break}}l=a[Mb+4>>2];l>>>0>2]>>>0||(na(Mb,1),l=a[Mb+4>>2]);k=m[e];a[Mb+4>>2]=l+1|0;m[l]=k;e=e+1|0}e=a[Mb+4>>2];e>>>0>2]>>>0||(na(Mb,1),e=a[Mb+4>>2]);a[Mb+4>>2]=e+1|0;m[e]=41;(d|0)!=(b|0)&&G(d);d=a[Mb+4>>2];if(d>>>0>2]>>>0){return m[d]=0,d=a[Mb>>2],a[Mb+4>>2]=d,h=i,d}na(Mb,1);d=a[Mb+4>>2];m[d]=0;d=a[Mb>>2];a[Mb+4>>2]=d;h=i;return d}function uI(c,d){var i,g,q,e,l=c>>2,k=h;h+=8;var n;e=k>>2;q=(c+44|0)>>2;a[q]=fa(96);var z=c|0,p=V(z,vI|0);if(0==(p|0)){var s=lk(wI|0);if(0!=(s|0)){var v=s;n=516}}else{v=p,n=516}if(516==n){var t=wb(a[op>>2],Ba(v)+12|0);a[op>>2]=t;for(var u=xI|0,w=t,A=u+12;u>2];if(0===B){Ea(va.i)}else{var B=Oe(B),C=B.indexOf(\"=\");if(\"\"===B||-1===B.indexOf(\"=\")){Ea(va.i)}else{var P=B.slice(0,C),y=B.slice(C+1);if(!(P in Ee)||Ee[P]!==y){Ee[P]=y,mC(Ee)}}}}var D=m,F=c+151|0,M=h,X=Aa(c|0,$(a[c+32>>2]|0,yI|0),Su|0);if(0==(Lb(X,zI|0)|0)){var O=1}else{0==(Lb(X,AI|0)|0)?O=1:0==(Lb(X,BI|0)|0)?O=1:0==(Lb(X,CI|0)|0)?O=1:0==(Lb(X,DI|0)|0)?O=1:0==(Lb(X,EI|0)|0)?O=1:0==(Lb(X,FI|0)|0)?O=1:0==(Lb(X,GI|0)|0)?O=2:0==(Lb(X,HI|0)|0)?O=2:(0!=(Lb(X,Su|0)|0)&&0!=(Lb(X,II|0)|0)&&la(0,JI|0,(j=h,h+=4,a[j>>2]=X,j)),O=0)}h=M;D[F]=O&255;g=(c+32|0)>>2;var E=Xb(z,$(a[g]|0,KI|0),0,0),G=a[q]|0;f[0]=E;a[G>>2]=b[0];a[G+4>>2]=b[1];var ia=V(z,LI|0);do{if(0==(ia|0)){var J=0}else{var I=m[ia];if(76==I<<24>>24){if(0==(ka(ia,MI|0)|0)){J=1;break}}else{if(82==I<<24>>24){J=0==(ka(ia,NI|0)|0)?3:0;break}else{if(66==I<<24>>24){if(0==(ka(ia,OI|0)|0)){J=2;break}}else{J=0;break}}}J=0}}while(0);var H=J<<2;a[l+38]=0==d<<24>>24?H:H|J;var K=Xb(z,$(a[g]|0,PI|0),.25,.02);f[0]=K;a[e]=b[0];a[e+1]=b[1];var L=72*K;a[l+64]=(0>L?L-.5:L+.5)&-1;var N=jc(z,$(a[g]|0,QI|0),0);if(0==(N|0)){f[0]=.5;a[e]=b[0];a[e+1]=b[1];var Q=.5}else{if(0==(Cd(N,RI|0,(j=h,h+=4,a[j>>2]=k,j))|0)){f[0]=.5;a[e]=b[0];a[e+1]=b[1];var S=.5}else{var ca=(b[0]=a[e],b[1]=a[e+1],f[0]);.02>ca?(f[0]=.02,a[e]=b[0],a[e+1]=b[1],S=.02):S=ca}0!=(un(N,SI|0)|0)&&(m[c+284|0]=1);Q=S}var R=72*Q;a[l+65]=(0>R?R-.5:R+.5)&-1;m[c+249|0]=Zf(z,$(a[g]|0,pp|0),0)&255;a[l+63]=Ds(jc(z,$(a[g]|0,TI|0),0),Bl|0,UI|0);VI(c);var U=Tu(c,WI|0,a[q]+64|0);m[a[q]+80|0]=U;Tu(c,XI|0,a[q]+48|0);var W=jo(V(z,YI|0));m[a[q]+82|0]=W;var ja=V(z,ZI|0);if(0==(ja|0)){var aa=V(z,Uu|0);if(0!=(aa|0)){var da=m[aa];m[a[q]+81|0]=(108==da<<24>>24|76==da<<24>>24)&1}else{var ea=V(z,$I|0);if(0!=(ea|0)){var xa=re(ea);m[a[q]+81|0]=xa}}}else{var Y=90==(bh(ja)|0)&1;m[a[q]+81|0]=Y}var Z=Ds(V(z,aJ|0),Cl|0,bJ|0);a[Dl>>2]=Z;var ha=jo(V(z,cJ|0));m[Wi]=ha;a[Ah>>2]=0;var ga=a[q]+24|0;f[0]=0;a[ga>>2]=b[0];a[ga+4>>2]=b[1];var ba=V(z,dJ|0);if(0==(ba|0)){n=548}else{if(0==m[ba]<<24>>24){n=548}else{var Rb=ba;n=550}}if(548==n){var sa=V(z,eJ|0);0!=(sa|0)&&0!=m[sa]<<24>>24&&(Rb=sa,n=550)}if(550==n){var tb=wg(Rb,xc),ya=a[q]+24|0;f[0]=tb;a[ya>>2]=b[0];a[ya+4>>2]=b[1]}Vu(c);f[0]=1e+37;a[Wu>>2]=b[0];a[Wu+4>>2]=b[1];var ra=$(a[g]|0,Xu|0);a[Yu>>2]=ra;i=(c+40|0)>>2;var wa=$(a[a[i]>>2]|0,qp|0);a[sh>>2]=wa;var Ab=$(a[a[i]>>2]|0,rp|0);a[rh>>2]=Ab;var Fa=$(a[a[i]>>2]|0,fJ|0);a[Fs>>2]=Fa;var Ga=$(a[a[i]>>2]|0,kh|0);a[Eh>>2]=Ga;var qa=$(a[a[i]>>2]|0,Ak|0);a[gp>>2]=qa;var ta=$(a[a[i]>>2]|0,Rn|0);a[Ki>>2]=ta;var Ka=$(a[a[i]>>2]|0,sp|0);a[bo>>2]=Ka;var za=$(a[a[i]>>2]|0,tp|0);a[ao>>2]=za;var ma=$(a[a[i]>>2]|0,Eg|0);a[Js>>2]=ma;var pa=$(a[a[i]>>2]|0,dh|0);a[no>>2]=pa;var na=$(a[a[i]>>2]|0,Zu|0);a[Ks>>2]=na;var Ha=$(a[a[i]>>2]|0,pp|0);a[Ls>>2]=Ha;var Ra=$(a[a[i]>>2]|0,$n|0);a[iu>>2]=Ra;var oa=$(a[a[i]>>2]|0,Xu|0);a[El>>2]=oa;var Ma=$(a[a[i]>>2]|0,gJ|0);a[Tt>>2]=Ma;var La=$(a[a[i]>>2]|0,es|0);a[$o>>2]=La;var Qa=$(a[a[i]>>2]|0,hJ|0);a[St>>2]=Qa;var Ya=$(a[a[i]>>2]|0,Uu|0);a[Rt>>2]=Ya;var Cb=$(a[a[i]>>2]|0,iJ|0);a[Ut>>2]=Cb;var Za=$(a[a[i]>>2]|0,jJ|0);a[bp>>2]=Za;var Va=$(a[a[i]>>2]|0,pt|0);a[zu>>2]=Va;var ab=$(a[a[i]>>2]|0,kJ|0);a[cp>>2]=ab;var $a=$(a[a[i]>>2]|0,Qn|0);a[Un>>2]=$a;var jb=$(a[a[i]>>2]|0,lJ|0);a[Gh>>2]=jb;var Ca=$(a[a[i]>>2]|0,Nn|0);a[$r>>2]=Ca;var Ia=$(a[a[i]>>2]|0,mJ|0);a[up>>2]=Ia;var eb=$(a[a[i]>>2]|0,nJ|0);a[oJ>>2]=eb;var ub=$(a[a[i]+4>>2]|0,vp|0);a[Kg>>2]=ub;var Sa=$(a[a[i]+4>>2]|0,kh|0);a[go>>2]=Sa;var hb=$(a[a[i]+4>>2]|0,sp|0);a[Ts>>2]=hb;var ua=$(a[a[i]+4>>2]|0,tp|0);a[Us>>2]=ua;var Oa=$(a[a[i]+4>>2]|0,Eg|0);a[Vs>>2]=Oa;var Wa=$(a[a[i]+4>>2]|0,dh|0);a[Ms>>2]=Wa;var pb=$(a[a[i]+4>>2]|0,Zu|0);a[Ps>>2]=pb;var ob=$(a[a[i]+4>>2]|0,pJ|0);a[Os>>2]=ob;var bb=$(a[a[i]+4>>2]|0,qJ|0);a[Hr>>2]=bb;var qb=$(a[a[i]+4>>2]|0,rJ|0);a[Ir>>2]=qb;var Pa=$(a[a[i]+4>>2]|0,sJ|0);a[Jr>>2]=Pa;var kb=$(a[a[i]+4>>2]|0,ks|0);a[Zk>>2]=kb;var ib=$(a[a[i]+4>>2]|0,ls|0);a[$k>>2]=ib;var vb=$(a[a[i]+4>>2]|0,tJ|0);a[Ws>>2]=vb;var xb=$(a[a[i]+4>>2]|0,uJ|0);a[Xs>>2]=xb;var hd=$(a[a[i]+4>>2]|0,vJ|0);a[Ys>>2]=hd;var nb=$(a[a[i]+4>>2]|0,wJ|0);a[Gu>>2]=nb;var rb=$(a[a[i]+4>>2]|0,xJ|0);a[Fu>>2]=rb;var lb=$(a[a[i]+4>>2]|0,yJ|0);a[Hh>>2]=lb;var Ta=$(a[a[i]+4>>2]|0,pp|0);a[$u>>2]=Ta;var cb=$(a[a[i]+4>>2]|0,Rn|0);a[Vn>>2]=cb;var fb=$(a[a[i]+4>>2]|0,zJ|0);a[io>>2]=fb;var Ua=$(a[a[i]+4>>2]|0,AJ|0);a[uk>>2]=Ua;var sb=$(a[a[i]+4>>2]|0,BJ|0);a[dj>>2]=sb;var Na=$(a[a[i]+4>>2]|0,Qn|0);a[Tn>>2]=Na;var Fb=$(a[a[i]+4>>2]|0,Nn|0);a[bs>>2]=Fb;var Db=$(a[a[i]+4>>2]|0,CJ|0);a[Rs>>2]=Db;var Ob=$(a[a[i]+4>>2]|0,DJ|0);a[Ss>>2]=Ob;var Eb=$(a[a[i]+4>>2]|0,$n|0);a[hs>>2]=Eb;var db=RC(c);a[a[q]+88>>2]=db;var Bb=V(z,Pr|0);if(0!=(Bb|0)&&0!=m[Bb]<<24>>24){var Ja=ec(Bb,z);a[a[q]+92>>2]=Ja}h=k}function VI(c){var d=V(c|0,EJ|0);if(0!=(d|0)){var i=m[d];if(0!=i<<24>>24){var g=i<<24>>24;101==(g|0)?101==i<<24>>24&&0==(ka(d,FJ|0)|0)&&(a[a[c+44>>2]+84>>2]=5):99==(g|0)?99==i<<24>>24&&0==(ka(d,GJ|0)|0)&&(a[a[c+44>>2]+84>>2]=3):102==(g|0)?102==i<<24>>24&&0==(ka(d,HJ|0)|0)&&(a[a[c+44>>2]+84>>2]=2):97==(g|0)?97==i<<24>>24&&0==(ka(d,IJ|0)|0)&&(a[a[c+44>>2]+84>>2]=4):(d=wg(d,xc),0>2]+84>>2]=1,c=a[c>>2]+16|0,f[0]=d,a[c>>2]=b[0],a[c+4>>2]=b[1]))}}}function Tu(c,d,i){var g,q=h;h+=20;g=q>>2;var e=q+8,l=q+16;m[l]=0;c=V(c|0,d);if(0==(c|0)){return h=q,0}if(1<(Cd(c,JJ|0,(j=h,h+=12,a[j>>2]=q,a[j+4>>2]=e,a[j+8>>2]=l,j))|0)){if(d=(b[0]=a[g],b[1]=a[g+1],f[0]),0>2],b[1]=a[e+4>>2],f[0]),0g?g-.5:g+.5)&-1|0,a[c>>2]=b[0],a[c+4>>2]=b[1],g=72*e,i=i+8|0,f[0]=(0>g?g-.5:g+.5)&-1|0,a[i>>2]=b[0],a[i+4>>2]=b[1],l=33==m[l]<<24>>24&1,h=q,l}}m[l]=0;if(0>=(Cd(c,KJ|0,(j=h,h+=8,a[j>>2]=q,a[j+4>>2]=l,j))|0)){return h=q,0}g=(b[0]=a[g],b[1]=a[g+1],f[0]);if(0>=g){return h=q,0}g*=72;g=(0>g?g-.5:g+.5)&-1|0;e=i|0;f[0]=g;a[e>>2]=b[0];a[e+4>>2]=b[1];i=i+8|0;f[0]=g;a[i>>2]=b[0];a[i+4>>2]=b[1];h=q;return 33==m[l]<<24>>24&1}function LJ(c){if(0!=(a[Ic>>2]|0)){var d=c+76|0,c=c+60|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0])+(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]);f[0]=d;a[Nb>>2]=b[0];a[Nb+4>>2]=b[1];f[0]=d/72;a[Ih>>2]=b[0];a[Ih+4>>2]=b[1]}}function Vu(c){var d,i=c|0,g=V(i,dh|0);if(0!=(g|0)&&0!=m[g]<<24>>24){d=(c+32|0)>>2;var q=a[d]+149|0;m[q]|=8;var e=0!=(bg(g)|0)?2:0,l=Xb(i,$(a[d]|0,sp|0),14,1),q=c+48|0;a[q>>2]=ag(i,g,e,l,Aa(i,$(a[d]|0,tp|0),Li|0),Aa(i,$(a[d]|0,Eg|0),Ac|0));g=V(i,Vt|0);e=0!=(g|0);g=(a[d]|0)==(c|0)?e&&116==m[g]<<24>>24?1:0:e&&98==m[g]<<24>>24?0:1;i=V(i,MJ|0);0==(i|0)?i=g:(i=m[i],i=108==i<<24>>24?g|2:114==i<<24>>24?g|4:g);m[c+283|0]=i;g=a[d];(g|0)!=(c|0)&&(q=a[q>>2],d=q+24|0,q=q+32|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0])+16,q=(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0])+8,i=0!=(i&1)<<24>>24,0==(a[g+152>>2]&1|0)?(i=i?2:0,g=(i<<4)+c+84|0,f[0]=d,a[g>>2]=b[0],a[g+4>>2]=b[1],c=(i<<4)+c+92|0,f[0]=q):(i=i?1:3,g=(i<<4)+c+84|0,f[0]=q,a[g>>2]=b[0],a[g+4>>2]=b[1],c=(i<<4)+c+92|0,f[0]=d),a[c>>2]=b[0],a[c+4>>2]=b[1])}}function xC(b){var c,i=b+44|0;c=(i|0)>>2;var d=a[c],f=a[d+88>>2];0!=(f|0)&&(IQa(f),d=a[c]);f=a[d+92>>2];0!=(f|0)&&(G(f),d=a[c]);G(d);a[c]=0;vh(a[b+48>>2]);b=i>>2;for(c=b+61;b>2]+a[b>>2]-a[ih>>2]-a[ih+4>>2]|0)>>>0)/60;h=b;return c}function av(c,d,i,g){var q,e,l;LJ(d);var k=d+68|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),n=d+76|0,n=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),c=c+348|0;Jh(i,NJ|0,(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]));Jh(i,gc|0,k/72);Jh(i,gc|0,n/72);Ge(10,i);c=ra(d);k=0==(c|0);a:do{if(!k){n=c;for(l=n>>2;;){if(0==m[n+134|0]<<24>>24){e=xd(a[l+3]);Me(i,OJ|0,e);e=n+32|0;var z=n+40|0;bv(i,(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),(b[0]=a[z>>2],b[1]=a[z+4>>2],f[0]));e=a[l+30];0==m[e+82|0]<<24>>24?(e=cv(a[e>>2]),z=n|0):(z=n|0,e=xd(mb(z,a[a[no>>2]+8>>2])));q=n+48|0;Jh(i,gc|0,(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]));q=n+56|0;Jh(i,gc|0,(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]));Me(i,gc|0,e);Me(i,gc|0,Aa(z,a[Ki>>2],wp|0));Me(i,gc|0,a[a[l+6]>>2]);Me(i,gc|0,Aa(z,a[Eh>>2],Ac|0));l=Aa(z,a[gp>>2],Y|0);Me(i,gc|0,0==m[l]<<24>>24?Aa(z,a[Eh>>2],qe|0):l);Ge(10,i)}n=ba(d,n);if(0==(n|0)){break a}else{l=n>>2}}}}while(0);c=ra(d);if(0!=(c|0)){for(g=0==g<<24>>24;;){k=Ib(d,c);n=0==(k|0);a:do{if(!n){l=k;for(e=l>>2;;){if(g){var z=Y|0,p=Y|0}else{q=a[e+1],0==(q|0)?(z=Y|0,p=Y|0):(z=a[q+4>>2],p=a[q+8>>2])}q=(l+24|0)>>2;var s=a[q],v=0==(s|0);b:do{if(!v){var t=a[s+4>>2],u=0<(t|0);c:do{if(u){for(var w=a[s>>2],A=0,B=0;;){if(A=a[(w+4>>2)+(12*B|0)]+A|0,B=B+1|0,(B|0)>=(t|0)){var C=A;break c}}}else{C=0}}while(0);Me(i,0,Dg|0);t=a[e+4];PJ(i,a[t+12>>2],m[t+134|0],z);t=a[e+3];PJ(i,a[t+12>>2],m[t+134|0],p);t=i;u=C;w=h;h+=1024;hb(gc|0,t);A=w|0;Ma(A,dv|0,(j=h,h+=4,a[j>>2]=u,j));hb(A,t);h=w;u=a[q];if(0<(a[u+4>>2]|0)){t=0;for(A=u;;){w=a[A>>2];u=a[(w>>2)+(12*t|0)];w=a[(w+4>>2)+(12*t|0)];if(0<(w|0)){for(A=0;;){var B=(A<<4)+u|0,P=(A<<4)+u+8|0;bv(i,(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0]),(b[0]=a[P>>2],b[1]=a[P+4>>2],f[0]));A=A+1|0;if((A|0)==(w|0)){break}}u=a[q]}else{u=A}t=t+1|0;if((t|0)<(a[u+4>>2]|0)){A=u}else{break b}}}}}while(0);e=l+108|0;z=a[e>>2];0!=(z|0)&&(z=cv(a[z>>2]),Me(i,gc|0,z),z=a[e>>2],e=z+56|0,z=z+64|0,bv(i,(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),(b[0]=a[z>>2],b[1]=a[z+4>>2],f[0])));e=l|0;Me(i,gc|0,Aa(e,a[Vn>>2],wp|0));Me(i,gc|0,Aa(e,a[go>>2],Ac|0));Ge(10,i);l=yb(d,l);if(0==(l|0)){break a}else{e=l>>2}}}}while(0);c=ba(d,c);if(0==(c|0)){break}}}hb(QJ|0,i)}function Jh(c,d,i){var g=h;h+=1024;0!=(d|0)&&hb(d,c);d=g|0;Ma(d,xp|0,(j=h,h+=8,f[0]=i,a[j>>2]=b[0],a[j+4>>2]=b[1],j));hb(d,c);h=g}function Me(a,b,i){0!=(b|0)&&hb(b,a);hb(i,a)}function bv(c,d,i){Jh(c,gc|0,d/72);d=0==(a[Ic>>2]|0)?i:(b[0]=a[Nb>>2],b[1]=a[Nb+4>>2],f[0])-i;Jh(c,gc|0,d/72)}function cv(a){var a=Dc(a),b=xd(a);dc(a);return b}function PJ(a,b,i,c){b=0==i<<24>>24?xd(b):cv(Jc(b,58)+1|0);Me(a,gc|0,b);0!=(c|0)&&0!=m[c]<<24>>24&&(c=xd(c),Me(a,zf|0,c))}function ev(c,d,i){var g,q,e,l,k,n,z,p,s,v,t=h;h+=2064;var u,w=t+2048;v=(c+206|0)>>1;var A=2<(D[v]&65535);Gg(1);LJ(c);fc(w,1024,t+1024|0);s=(c+40|0)>>2;ac(c,a[a[s]>>2]|0,ej|0,Y|0,212);ac(c,a[a[s]>>2]|0,fv|0,Y|0,212);var B=ac(c,a[a[s]>>2]|0,rp|0,Y|0,212);a[rh>>2]=B;var C=ac(c,a[a[s]>>2]|0,qp|0,Y|0,212);a[sh>>2]=C;ac(c,a[a[s]+4>>2]|0,ej|0,Y|0,286);var P=c+149|0,y=m[P];if(0==(y&16)<<24>>24){var gb=y}else{ac(c,a[a[s]>>2]|0,Fl|0,Y|0,212),gb=m[P]}if(0==(gb&1)<<24>>24){var F=gb}else{ac(c,a[a[s]+4>>2]|0,fj|0,Y|0,286),F=m[P]}if(0==(F&32)<<24>>24){var M=F}else{ac(c,a[a[s]+4>>2]|0,Fl|0,Y|0,286),M=m[P]}if(0==(M&2)<<24>>24){var X=M}else{ac(c,a[a[s]+4>>2]|0,gv|0,Y|0,286),X=m[P]}0!=(X&4)<<24>>24&&ac(c,a[a[s]+4>>2]|0,hv|0,Y|0,286);p=(c+48|0)>>2;var O=c|0;if(0!=(a[p]|0)){ac(c,O,fj|0,Y|0,202);ac(c,O,yp|0,Y|0,202);ac(c,O,zp|0,Y|0,202);var E=a[p];if(0!=m[a[E>>2]]<<24>>24){var G=E+56|0,ia=(b[0]=a[G>>2],b[1]=a[G+4>>2],f[0]),J=E+64|0,I=(b[0]=a[J>>2],b[1]=a[J+4>>2],f[0]),H=t|0,K=0==(a[Ic>>2]|0)?I:(b[0]=a[Nb>>2],b[1]=a[Nb+4>>2],f[0])-I;Ma(H,Gf|0,(j=h,h+=16,f[0]=ia,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=K,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));yd(O,fj|0,H);var L=a[p],N=L+24|0,Q=(b[0]=a[N>>2],b[1]=a[N+4>>2],f[0]),S=L+32|0,ca=(b[0]=a[S>>2],b[1]=a[S+4>>2],f[0]);Ma(H,Gl|0,(j=h,h+=8,f[0]=Q/72,a[j>>2]=b[0],a[j+4>>2]=b[1],j));yd(O,yp|0,H);Ma(H,Gl|0,(j=h,h+=8,f[0]=ca/72,a[j>>2]=b[0],a[j+4>>2]=b[1],j));yd(O,zp|0,H)}}var R=ac(c,O,iv|0,Y|0,202),U=ra(c),W=0==(U|0);a:do{if(W){var ja=0,aa=0}else{var da=t|0;z=(w+4|0)>>2;n=(w+8|0)>>2;k=(w|0)>>2;for(var ea=0,xa=0,Z=U;;){var $=Z,ha=Z+24|0;l=ha>>2;var ga=Z+32|0,la=ga,Rb=(b[0]=a[la>>2],b[1]=a[la+4>>2],f[0]),sa=0!=(a[Ic>>2]|0);if(A){if(sa){var tb=ga+8|0,ya=(b[0]=a[Nb>>2],b[1]=a[Nb+4>>2],f[0])-(b[0]=a[tb>>2],b[1]=a[tb+4>>2],f[0])}else{var fa=ga+8|0,ya=(b[0]=a[fa>>2],b[1]=a[fa+4>>2],f[0])}var wa=ha+124|0,Ab=a[wa>>2]+16|0,Fa=72*(b[0]=a[Ab>>2],b[1]=a[Ab+4>>2],f[0]);Ma(da,RJ|0,(j=h,h+=24,f[0]=Rb,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=ya,a[j+8>>2]=b[0],a[j+12>>2]=b[1],f[0]=Fa,a[j+16>>2]=b[0],a[j+20>>2]=b[1],j));db(w,da);var Ga=3<(D[v]&65535);b:do{if(Ga){for(var qa=3;;){var ta=(qa<<3)+a[wa>>2]|0;Ma(da,SJ|0,(j=h,h+=8,f[0]=72*(b[0]=a[ta>>2],b[1]=a[ta+4>>2],f[0]),a[j>>2]=b[0],a[j+4>>2]=b[1],j));db(w,da);var Ka=qa+1|0;if((Ka|0)<(D[v]&65535|0)){qa=Ka}else{break b}}}}while(0);var za=a[z];if(za>>>0>>0){var ma=za}else{na(w,1),ma=a[z]}m[ma]=0;var pa=a[k];a[z]=pa;yd(Z|0,ej|0,pa)}else{if(sa){var oa=ga+8|0,Ha=(b[0]=a[Nb>>2],b[1]=a[Nb+4>>2],f[0])-(b[0]=a[oa>>2],b[1]=a[oa+4>>2],f[0])}else{var Ra=Z+40|0,Ha=(b[0]=a[Ra>>2],b[1]=a[Ra+4>>2],f[0])}Ma(da,Gf|0,(j=h,h+=16,f[0]=Rb,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=Ha,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));yd(Z|0,ej|0,da)}var Ba=Z+96|0;Ma(da,xp|0,(j=h,h+=8,f[0]=(b[0]=a[Ba>>2],b[1]=a[Ba+4>>2],f[0])/72,a[j>>2]=b[0],a[j+4>>2]=b[1],j));var va=Z|0;qc(va,a[a[sh>>2]+8>>2],da);var La=Z+104|0,Aa=Z+112|0;Ma(da,xp|0,(j=h,h+=8,f[0]=((b[0]=a[La>>2],b[1]=a[La+4>>2],f[0])+(b[0]=a[Aa>>2],b[1]=a[Aa+4>>2],f[0]))/72,a[j>>2]=b[0],a[j+4>>2]=b[1],j));qc(va,a[a[rh>>2]+8>>2],da);var Ya=a[Z+124>>2];if(0!=(Ya|0)){var Ea=Ya+56|0,Za=(b[0]=a[Ea>>2],b[1]=a[Ea+4>>2],f[0]),Qa=Ya+64|0,ab=(b[0]=a[Qa>>2],b[1]=a[Qa+4>>2],f[0]),$a=0==(a[Ic>>2]|0)?ab:(b[0]=a[Nb>>2],b[1]=a[Nb+4>>2],f[0])-ab;Ma(da,Gf|0,(j=h,h+=16,f[0]=Za,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=$a,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));yd(va,Fl|0,da)}do{if(0==(ka(a[a[l]>>2],jv|0)|0)){kv($,a[l+1],w);var jb=w+4|0,Ca=a[jb>>2];Ca>>>0>a[w>>2]>>>0&&(a[jb>>2]=Ca-1|0);var Ia=a[z];if(Ia>>>0>>0){var eb=Ia}else{na(w,1),eb=a[z]}m[eb]=0;var ub=a[k];a[z]=ub;yd(va,fv|0,ub)}else{var Sa;if(Sa=0!=(a[up>>2]|0)){var Cb;var ua=a[$+24>>2];if(0==(ua|0)){var Oa=0,Wa=Oa&1}else{Oa=270==(a[a[ua+4>>2]>>2]|0),Wa=Oa&1}Cb=Wa;Sa=0!=Cb<<24>>24}if(Sa){var pb=a[l+1],ob=pb+8|0,bb=a[ob>>2];if(3>(bb|0)){var qb=V(va,us|0),wb=0==(qb|0)?8:bh(qb),kb=3>(wb|0)?8:wb;if(0<(kb|0)){var Va=kb;u=763}}else{Va=bb,u=763}b:do{if(763==u){u=0;var vb=pb+40|0,xb=ha+24|0,hd=Va|0;e=(ha+32|0)>>2;for(var nb=0;;){if(0<(nb|0)){var rb=a[z];if(rb>>>0>>0){var lb=rb}else{na(w,1),lb=a[z]}a[z]=lb+1|0;m[lb]=32}if(2<(a[ob>>2]|0)){var Ta=a[vb>>2],cb=(nb<<4)+Ta|0,fb=(b[0]=a[cb>>2],b[1]=a[cb+4>>2],f[0])/72;if(0==(a[Ic>>2]|0)){var Ua=(nb<<4)+Ta+8|0,sb=(b[0]=a[Ua>>2],b[1]=a[Ua+4>>2],f[0])/72}else{var Na=(nb<<4)+Ta+8|0,sb=(b[0]=a[Ih>>2],b[1]=a[Ih+4>>2],f[0])-(b[0]=a[Na>>2],b[1]=a[Na+4>>2],f[0])/72}Ma(da,lv|0,(j=h,h+=16,f[0]=fb,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=sb,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j))}else{var Fb=6.283185307179586*((nb|0)/hd),Db=.5*(b[0]=a[xb>>2],b[1]=a[xb+4>>2],f[0])*se(Fb),Ob=0==(a[Ic>>2]|0)?.5*(b[0]=a[e],b[1]=a[e+1],f[0])*Ce(Fb):(b[0]=a[Ih>>2],b[1]=a[Ih+4>>2],f[0])-.5*(b[0]=a[e],b[1]=a[e+1],f[0])*Ce(Fb);Ma(da,lv|0,(j=h,h+=16,f[0]=Db,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=Ob,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j))}db(w,da);var Eb=nb+1|0;if((Eb|0)==(Va|0)){break b}else{nb=Eb}}}}while(0);var hb=a[a[up>>2]+8>>2],Bb=a[z];if(Bb>>>0>>0){var Ja=Bb}else{na(w,1),Ja=a[z]}m[Ja]=0;var Pa=a[k];a[z]=Pa;qc(va,hb,Pa)}}}while(0);var ib=0<(a[Ah>>2]|0);b:do{if(ib){var Gb=Ib(c,Z);if(0==(Gb|0)){var mb=xa,Jb=ea}else{var Hb=ea,ic=xa,Kb=Gb;for(q=Kb>>2;;){do{if(6==m[Kb+124|0]<<24>>24){var Lb=ic,Mb=Hb}else{g=(Kb+24|0)>>2;var Pb=a[g];if(0==(Pb|0)){Lb=ic,Mb=Hb}else{var Tb=0<(a[Pb+4>>2]|0);c:do{if(Tb){for(var pc=0,Wc=Hb,Vb=ic,Wb=Pb;;){if(0<(pc|0)){var Yb=a[z];if(Yb>>>0>>0){var $b=Yb}else{na(w,1),$b=a[z]}a[z]=$b+1|0;m[$b]=59;var Zb=a[g]}else{Zb=Wb}var Sb=a[Zb>>2];if(0==(a[(Sb+8>>2)+(12*pc|0)]|0)){var kc=Wc,lc=Zb,ec=Sb}else{var Jd=Sb+48*pc+16|0,nc=(b[0]=a[Jd>>2],b[1]=a[Jd+4>>2],f[0]);if(0==(a[Ic>>2]|0)){var Hc=Sb+48*pc+24|0,mc=(b[0]=a[Hc>>2],b[1]=a[Hc+4>>2],f[0])}else{var oe=Sb+48*pc+24|0,mc=(b[0]=a[Nb>>2],b[1]=a[Nb+4>>2],f[0])-(b[0]=a[oe>>2],b[1]=a[oe+4>>2],f[0])}Ma(da,TJ|0,(j=h,h+=16,f[0]=nc,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=mc,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));db(w,da);var bc=a[g],kc=1,lc=bc,ec=a[bc>>2]}if(0==(a[(ec+12>>2)+(12*pc|0)]|0)){var Qb=Vb,tc=lc,Sc=ec}else{var Xb=ec+48*pc+32|0,rc=(b[0]=a[Xb>>2],b[1]=a[Xb+4>>2],f[0]);if(0==(a[Ic>>2]|0)){var dc=ec+48*pc+40|0,rd=(b[0]=a[dc>>2],b[1]=a[dc+4>>2],f[0])}else{var oc=ec+48*pc+40|0,rd=(b[0]=a[Nb>>2],b[1]=a[Nb+4>>2],f[0])-(b[0]=a[oc>>2],b[1]=a[oc+4>>2],f[0])}Ma(da,UJ|0,(j=h,h+=16,f[0]=rc,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=rd,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));db(w,da);var jc=a[g],Qb=1,tc=jc,Sc=a[jc>>2]}var Ac=0<(a[(Sc+4>>2)+(12*pc|0)]|0);d:do{if(Ac){for(var gc=0,Rd=tc;;){if(0<(gc|0)){var Bc=a[z];if(Bc>>>0>>0){var Sd=Bc}else{na(w,1),Sd=a[z]}a[z]=Sd+1|0;m[Sd]=32;var fd=a[g]}else{fd=Rd}var Cc=a[(a[fd>>2]>>2)+(12*pc|0)],yc=(gc<<4)+Cc|0,Kc=(b[0]=a[yc>>2],b[1]=a[yc+4>>2],f[0]),vc=(gc<<4)+Cc+8|0,Tc=(b[0]=a[vc>>2],b[1]=a[vc+4>>2],f[0]),Dc=0==(a[Ic>>2]|0)?Tc:(b[0]=a[Nb>>2],b[1]=a[Nb+4>>2],f[0])-Tc;Ma(da,Gf|0,(j=h,h+=16,f[0]=Kc,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=Dc,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));db(w,da);var Mc=gc+1|0,id=a[g];if((Mc|0)<(a[(a[id>>2]+4>>2)+(12*pc|0)]|0)){gc=Mc,Rd=id}else{var Ec=id;break d}}}else{Ec=tc}}while(0);var zc=pc+1|0;if((zc|0)<(a[Ec+4>>2]|0)){pc=zc,Wc=kc,Vb=Qb,Wb=Ec}else{var Gc=kc,Nc=Qb;break c}}}else{Gc=Hb,Nc=ic}}while(0);var Fc=Kb|0,Qc=a[z];if(Qc>>>0>>0){var Yc=Qc}else{na(w,1),Yc=a[z]}m[Yc]=0;var Rc=a[k];a[z]=Rc;yd(Fc,ej|0,Rc);var Td=a[q+27];if(0!=(Td|0)){var Lc=Td+56|0,Oc=(b[0]=a[Lc>>2],b[1]=a[Lc+4>>2],f[0]),fe=Td+64|0,Pc=(b[0]=a[fe>>2],b[1]=a[fe+4>>2],f[0]),Zc=0==(a[Ic>>2]|0)?Pc:(b[0]=a[Nb>>2],b[1]=a[Nb+4>>2],f[0])-Pc;Ma(da,Gf|0,(j=h,h+=16,f[0]=Oc,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=Zc,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));yd(Fc,fj|0,da)}var jd=a[q+30];if(0!=(jd|0)){var md=jd+56|0,je=(b[0]=a[md>>2],b[1]=a[md+4>>2],f[0]),Jc=jd+64|0,ad=(b[0]=a[Jc>>2],b[1]=a[Jc+4>>2],f[0]),Uc=0==(a[Ic>>2]|0)?ad:(b[0]=a[Nb>>2],b[1]=a[Nb+4>>2],f[0])-ad;Ma(da,Gf|0,(j=h,h+=16,f[0]=je,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=Uc,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));yd(Fc,Fl|0,da)}var Xc=a[q+28];if(0!=(Xc|0)){var Vc=Xc+56|0,cd=(b[0]=a[Vc>>2],b[1]=a[Vc+4>>2],f[0]),$c=Xc+64|0,ld=(b[0]=a[$c>>2],b[1]=a[$c+4>>2],f[0]),kd=0==(a[Ic>>2]|0)?ld:(b[0]=a[Nb>>2],b[1]=a[Nb+4>>2],f[0])-ld;Ma(da,Gf|0,(j=h,h+=16,f[0]=cd,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=kd,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));yd(Fc,gv|0,da)}var bd=a[q+29];if(0!=(bd|0)){var ed=bd+56|0,qd=(b[0]=a[ed>>2],b[1]=a[ed+4>>2],f[0]),pd=bd+64|0,dd=(b[0]=a[pd>>2],b[1]=a[pd+4>>2],f[0]),nd=0==(a[Ic>>2]|0)?dd:(b[0]=a[Nb>>2],b[1]=a[Nb+4>>2],f[0])-dd;Ma(da,Gf|0,(j=h,h+=16,f[0]=qd,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=nd,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));yd(Fc,hv|0,da)}Lb=Nc;Mb=Gc}}}while(0);var zd=yb(c,Kb);if(0==(zd|0)){mb=Lb;Jb=Mb;break b}else{Hb=Mb,ic=Lb,Kb=zd,q=Kb>>2}}}}else{mb=xa,Jb=ea}}while(0);var od=ba(c,Z);if(0==(od|0)){ja=Jb;aa=mb;break a}else{ea=Jb,xa=mb,Z=od}}}}while(0);mv(c,R);uc(w);0!=(D[c+164>>1]&1)<<16>>16&&oF(c);a[d>>2]=ja;a[i>>2]=aa;Gg(0);h=t}function kv(c,d,i){var g,q=h;h+=1024;g=(d+48|0)>>2;var e=a[g];if(0==(e|0)){var e=q|0,l=d+16|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),k=c+32|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);if(0==(a[Ic>>2]|0)){var n=d+24|0,m=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),n=c+40|0,p=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),n=d+40|0,n=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0])+p,m=m+p}else{var m=(b[0]=a[Nb>>2],b[1]=a[Nb+4>>2],f[0]),n=d+24|0,p=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),n=c+40|0,s=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),n=d+40|0,n=m-((b[0]=a[n>>2],b[1]=a[n+4>>2],f[0])+s),m=m-(p+s)}p=d+32|0;p=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0])+k;Ma(e,VJ|0,(j=h,h+=32,f[0]=l+k,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=m,a[j+8>>2]=b[0],a[j+12>>2]=b[1],f[0]=p,a[j+16>>2]=b[0],a[j+20>>2]=b[1],f[0]=n,a[j+24>>2]=b[0],a[j+28>>2]=b[1],j));db(i,e);e=a[g]}if(0<(e|0)){d=d+56|0;for(e=0;!(kv(c,a[a[d>>2]+(e<<2)>>2],i),e=e+1|0,(e|0)>=(a[g]|0));){}}h=q}function mv(c,d){var i=h;h+=1024;var g=i|0,q=c+52|0,q=(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]);if(0==(a[Ic>>2]|0)){var e=c+60|0,l=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),e=c+76|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])}else{var e=(b[0]=a[Nb>>2],b[1]=a[Nb+4>>2],f[0]),l=c+60|0,l=e-(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),k=c+76|0,e=e-(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])}k=c+68|0;k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);Ma(g,WJ|0,(j=h,h+=32,f[0]=q,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=l,a[j+8>>2]=b[0],a[j+12>>2]=b[1],f[0]=k,a[j+16>>2]=b[0],a[j+20>>2]=b[1],f[0]=e,a[j+24>>2]=b[0],a[j+28>>2]=b[1],j));q=c|0;qc(q,a[d+8>>2],g);e=c+48|0;k=a[e>>2];0!=(k|0)&&0!=m[a[k>>2]]<<24>>24&&(l=k+56|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),k=k+64|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),k=0==(a[Ic>>2]|0)?k:(b[0]=a[Nb>>2],b[1]=a[Nb+4>>2],f[0])-k,Ma(g,Gf|0,(j=h,h+=16,f[0]=l,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=k,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j)),yd(q,fj|0,g),l=a[e>>2],e=l+24|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),l=l+32|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),Ma(g,Gl|0,(j=h,h+=8,f[0]=e/72,a[j>>2]=b[0],a[j+4>>2]=b[1],j)),yd(q,yp|0,g),Ma(g,Gl|0,(j=h,h+=8,f[0]=l/72,a[j>>2]=b[0],a[j+4>>2]=b[1],j)),yd(q,zp|0,g));g=c+208|0;if(1<=(a[g>>2]|0)){q=c+212|0;for(e=1;!(mv(a[a[q>>2]+(e<<2)>>2],d),e=e+1|0,(e|0)>(a[g>>2]|0));){}}h=i}function Ap(c,d,i){var g=h;h+=1024;var q=g|0;if(0==(a[Ic>>2]|0)){var e=i,l=0}else{e=(b[0]=a[Nb>>2],b[1]=a[Nb+4>>2],f[0])-i,l=1}i=0>e?(l?(b[0]=a[Nb>>2],b[1]=a[Nb+4>>2],f[0])-i:i)-.5:(l?(b[0]=a[Nb>>2],b[1]=a[Nb+4>>2],f[0])-i:i)+.5;Ma(q,Hl|0,(j=h,h+=8,a[j>>2]=(0>d?d-.5:d+.5)&-1,a[j+4>>2]=i&-1,j));db(c,q);h=g}function An(c,d,i){var g=h;h+=256;if(999==(XJ(c,i)|0)){return d=nv(c,1,i),la(1,ov|0,(j=h,h+=8,a[j>>2]=i,a[j+4>>2]=d,j)),h=g,-1}YJ(c,d);var i=g|0,q=d+52|0;if(0==m[a[d+44>>2]+81|0]<<24>>24){var c=q|0,c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),q=d+60|0,q=(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]),e=d+68|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),l=d+76|0}else{c=d+60|0,c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),q|=0,q=(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]),e=d+76|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),l=d+68|0}l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);Ma(i,ZJ|0,(j=h,h+=16,a[j>>2]=(0>c?c-.5:c+.5)&-1,a[j+4>>2]=(0>q?q-.5:q+.5)&-1,a[j+8>>2]=(0>e?e-.5:e+.5)&-1,a[j+12>>2]=(0>l?l-.5:l+.5)&-1,j));$J(d|0,iv|0,i,Y|0);h=g;return 0}function xr(b,c){var i;i=a[c>>2];var d,f=Cb(12);d=f>>2;a[d+1]=0;a[d+2]=Hb(i);i=b+100|0;a[d]=a[i>>2];a[i>>2]=f;d=a[c+4>>2];i=a[d+4>>2];if(0!=(i|0)){var e=i;for(i=e>>2;;){var l=a[i+1],k=0==(l|0);a:do{if(!k){for(var h=d|0,j=0,p=l;;){if(aK(b,a[h>>2],p,a[i+(5*j|0)+2],f,e+20*j|0),j=j+1|0,p=a[i+(5*j|0)+1],0==(p|0)){break a}}}}while(0);i=a[d+12>>2];if(0==(i|0)){break}else{d=d+8|0,e=i,i=e>>2}}}}function OD(b){var c,i=b>>2,d=h,f=a[i+19],e=a[i];if(0==(f|0)){c=994}else{if(f=a[f>>2],0==(f|0)){c=994}else{J[f](b)}}if(994==c&&0==(a[i+10]|0)&&(c=(b+36|0)>>2,0==(a[c]|0))){if(0!=m[e+13|0]<<24>>24&&bK(b),b=b+32|0,e=a[b>>2],0==(e|0)){a[c]=a[zg>>2]}else{if(e=qi(e,au|0),a[c]=e,0==(e|0)){return i=a[a[i+3]+16>>2],b=a[b>>2],c=si(a[Ea.c>>2]),J[i](cK|0,(j=h,h+=8,a[j>>2]=b,a[j+4>>2]=c,j)),h=d,1}}}if(0==(a[i+37]&1024|0)){return h=d,0}J[a[a[i+3]+16>>2]](pv|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j));h=d;return 1}function Bp(b,c){if(-1e15>c){a[b>>2]=19;var i=qv|0;return i}if(1e15>2]=18,i=qv+1|0}var i=100*c,d=(0>i?i-.5:i+.5)&-1;if(0==(d|0)){return a[b>>2]=1,i=vd|0}for(var i=0>(d|0),f=0,d=i?-d|0:d,e=rv+20|0,l=2;;){var k=(d|0)%10,h=(d|0)/10&-1;0==(k|0)&0==f<<24>>24?k=e:(f=e-1|0,m[f]=(k|48)&255,k=f,f=1);1==(l|0)&&(0!=f<<24>>24&&(k=k-1|0,m[k]=46),f=1);l=l-1|0;if(18<(d+9|0)>>>0|0<(l|0)){d=h,e=k}else{break}}i?(i=k-1|0,m[i]=45):i=k;a[b>>2]=rv+20-i|0;return i}function bK(b){var c=h;h+=100;var i=a[b+24>>2],d=c|0;0==(i|0)?m[d]=0:Ma(d,dK|0,(j=h,h+=4,a[j>>2]=i+1|0,j));var i=a[b+20>>2],f=0==(i|0)?eK|0:i,i=b+52|0,e=Ba(f)+Ba(d)+Ba(a[i>>2])+1|0;a[sv>>2]>>>0<(e+1|0)>>>0?(e=e+11|0,a[sv>>2]=e,e=mc(a[ig>>2],e),a[ig>>2]=e):e=a[ig>>2];Rf(e,f);uf(a[ig>>2],d);d=a[ig>>2];d=d+Ba(d)|0;Pb=46;m[d]=Pb&255;Pb>>=8;m[d+1]=Pb&255;d=Hb(a[i>>2]);i=ik(d,58);f=a[ig>>2];if(0==(i|0)){var l;uf(f,d)}else{for(;;){if(uf(f,i+1|0),f=a[ig>>2],e=f+Ba(f)|0,Pb=46,m[e]=Pb&255,Pb>>=8,m[e+1]=Pb&255,m[i]=0,i=ik(d,58),0==(i|0)){l=f;break}}uf(l,d)}G(d);l=a[ig>>2];b=b+32|0;a[b>>2]=l;h=c}function y(a,b){jg(a,b,Ba(b))}function sI(a,b){var i=h;h+=4;m[i]=b&255;jg(a,i,1);h=i}function iD(b){var c=a[b+36>>2];if(0!=(c|0)&&0==m[b+140|0]<<24>>24&&0==(a[a[b>>2]+116>>2]|0)){b=(function(a){Q.a[a]&&Q.a[a].object.l&&(Q.a[a].aa||Q.a[a].object.l(xc))});try{if(0===c){for(c=0;c>2]=arguments[N.length];var f=i|0;jg(b,f,JQa(f,c,a[d>>2]));h=i}function gj(b,c){var i=h;h+=4;jg(b,Bp(i,c),a[i>>2]);h=i}function Hd(b,c,i){var d=h;h+=4;jg(b,Bp(d,c),a[d>>2]);jg(b,gc|0,1);jg(b,Bp(d,i),a[d>>2]);h=d}function kf(c,d,i){var g=d|0,q=d+8|0;Hd(c,(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]));if(1<(i|0)){for(g=1;;){jg(c,gc|0,1);var q=(g<<4)+d|0,e=(g<<4)+d+8|0;Hd(c,(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]),(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]));g=g+1|0;if((g|0)==(i|0)){break}}}}function XJ(b,c){var i;i=eh(b,1,c);if(0==(i|0)){return 999}i=a[i+16>>2]>>2;a[b+156>>2]=a[i+1];a[b+144>>2]=a[i+3];a[b+148>>2]=a[i];a[b+152>>2]=a[i+4];return 300}function YJ(b,c){var i=h;a[c+172>>2]=b;var d=c+32|0,f=a[d>>2];(f|0)!=(c|0)&&(a[f+172>>2]=b);f=V(c|0,tv|0);if(0!=(f|0)&&999==(XJ(b,f)|0)){return d=nv(b,1,f),la(1,ov|0,(j=h,h+=8,a[j>>2]=f,a[j+4>>2]=d,j)),h=i,-1}f=a[b+144>>2];if(0==(f|0)){return h=i,-1}Gg(1);uI(c,a[a[b+152>>2]>>2]&1);a[a[d>>2]+44>>2]=a[c+44>>2];d=a[f>>2];0!=(d|0)&&(J[d](c),d=a[f+4>>2],0!=(d|0)&&(a[c+176>>2]=d));Gg(0);h=i;return 0}function jg(b,c,i){var d=h;if(0==(i|0)|0==(c|0)){return h=d,0}0!=(a[b+148>>2]&1024|0)&&(J[a[a[b+12>>2]+16>>2]](pv|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),Pe());if((fK(b,c,i)|0)==(i|0)){return h=d,i}J[a[a[b+12>>2]+16>>2]](gK|0,(j=h,h+=4,a[j>>2]=i,j));Pe()}function fK(b,c,i){var d,f,e=h;d=a[a[b>>2]+116>>2];if(0!=(d|0)){return i=J[d](b,c,i),h=e,i}f=(b+40|0)>>2;var l=a[f];if(0==(l|0)){return i=Lc(c,1,i,a[b+36>>2]),h=e,i}var k=b+44|0;d=(b+48|0)>>2;var n=a[d];if((a[k>>2]-1-n|0)>>>0>>0){if(n=n+(i+4096)&-4096,a[k>>2]=n,l=mc(l,n),a[f]=l,0==(l|0)){J[a[a[b+12>>2]+16>>2]](hK|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),Pe()}else{var z=l,p=a[d]}}else{z=l,p=n}tf(z+p|0,c,i);b=a[d]+i|0;a[d]=b;m[a[f]+b|0]=0;h=e;return i}function iK(b){var c=h,i=a[b+76>>2];0!=(a[b+148>>2]&1024|0)&&(J[a[a[b+12>>2]+16>>2]](jK|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),Pe());if(0!=(i|0)&&(i=a[i+8>>2],0!=(i|0))){J[i](b);h=c;return}iD(b);i=b+32|0;if(0!=(a[i>>2]|0)){var d=b+36|0,f=a[d>>2];(f|0)!=(a[zg>>2]|0)&&0==m[b+140|0]<<24>>24&&(0!=(f|0)&&(ri(f),a[d>>2]=0),a[i>>2]=0)}h=c}function kK(c,d,i,g){var q,e;q=c>>2;if(3==(d|0)){uv(c,i,g),m[c+530|0]=1,m[c+533|0]=3,m[c+529|0]=1}else{if(2==(d|0)){m[c+530|0]=1,m[c+533|0]=2,m[c+529|0]=1}else{if(4==(d|0)){m[c+528|0]=0;if(0==(a[q+89]|0)){var l=.10000000000000009*(i-.5*(a[q+110]>>>0)),d=c+348|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),k=c+512|0;e=(c+332|0)>>2;l=l/(d*(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]))+(b[0]=a[e],b[1]=a[e+1],f[0]);f[0]=l;a[e]=b[0];a[e+1]=b[1];l=c+520|0;e=(c+340|0)>>2;q=.10000000000000009*(g-.5*(a[q+111]>>>0))/(d*(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]))+(b[0]=a[e],b[1]=a[e+1],f[0])}else{e=.10000000000000009*(g-.5*(a[q+111]>>>0)),d=c+348|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),l=c+520|0,l=e/(d*(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])),e=(c+332|0)>>2,l=(b[0]=a[e],b[1]=a[e+1],f[0])-l,f[0]=l,a[e]=b[0],a[e+1]=b[1],l=c+512|0,e=(c+340|0)>>2,q=.10000000000000009*(i-.5*(a[q+110]>>>0))/(d*(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]))+(b[0]=a[e],b[1]=a[e+1],f[0])}f[0]=q;a[e]=b[0];a[e+1]=b[1];q=d;d=c+348|0;f[0]=1.1*q;a[d>>2]=b[0];a[d+4>>2]=b[1];m[c+529|0]=1}else{5==(d|0)?(m[c+528|0]=0,e=(c+348|0)>>2,d=(b[0]=a[e],b[1]=a[e+1],f[0])/1.1,f[0]=d,a[e]=b[0],a[e+1]=b[1],0==(a[q+89]|0)?(e=c+512|0,l=.10000000000000009*(i-.5*(a[q+110]>>>0))/(d*(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])),e=(c+332|0)>>2,l=(b[0]=a[e],b[1]=a[e+1],f[0])-l,f[0]=l,a[e]=b[0],a[e+1]=b[1],e=c+520|0,d=.10000000000000009*(g-.5*(a[q+111]>>>0))/(d*(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]))):(l=c+520|0,e=(c+332|0)>>2,l=.10000000000000009*(g-.5*(a[q+111]>>>0))/(d*(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]))+(b[0]=a[e],b[1]=a[e+1],f[0]),f[0]=l,a[e]=b[0],a[e+1]=b[1],e=c+512|0,d=.10000000000000009*(i-.5*(a[q+110]>>>0))/(d*(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]))),q=(c+340|0)>>2,d=(b[0]=a[q],b[1]=a[q+1],f[0])-d,f[0]=d,a[q]=b[0],a[q+1]=b[1],m[c+529|0]=1):1==(d|0)&&(uv(c,i,g),lK(c),m[c+530|0]=1,m[c+533|0]=1,m[c+529|0]=1)}}}q=c+552|0;f[0]=i;a[q>>2]=b[0];a[q+4>>2]=b[1];c=c+560|0;f[0]=g;a[c>>2]=b[0];a[c+4>>2]=b[1]}function mK(c,d,i){var g,q,e,l,k;k=(c+552|0)>>2;l=c+512|0;e=(d-(b[0]=a[k],b[1]=a[k+1],f[0]))/(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);l=(c+560|0)>>2;g=c+520|0;g=(i-(b[0]=a[l],b[1]=a[l+1],f[0]))/(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);var h=e&-1;if(1>((-1<(h|0)?h:-h|0)|0)){if(h=g&-1,1>((-1<(h|0)?h:-h|0)|0)){return}}h=m[c+533|0]&255;0==(h|0)?uv(c,d,i):2==(h|0)&&(q=0==(a[c+356>>2]|0),h=c+348|0,h=(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0]),q?(q=(c+332|0)>>2,e=(b[0]=a[q],b[1]=a[q+1],f[0])-e/h,f[0]=e,a[q]=b[0],a[q+1]=b[1],e=(c+340|0)>>2,g=(b[0]=a[e],b[1]=a[e+1],f[0])-g/h,f[0]=g,a[e]=b[0],a[e+1]=b[1]):(q=(c+332|0)>>2,g=(b[0]=a[q],b[1]=a[q+1],f[0])-g/h,f[0]=g,a[q]=b[0],a[q+1]=b[1],g=(c+340|0)>>2,e=(b[0]=a[g],b[1]=a[g+1],f[0])+e/h,f[0]=e,a[g]=b[0],a[g+1]=b[1]),m[c+529|0]=1);f[0]=d;a[k]=b[0];a[k+1]=b[1];f[0]=i;a[l]=b[0];a[l+1]=b[1]}function nK(b,c,i){var d=a[b>>2];if(0==(c|0)){c=xn(oK|0,1);a[b+32>>2]=pK|0;var f=c}else{c=qi(c,cj|0);if(0==(c|0)){return}vC(c);zr();f=a[qa+16>>2];ri(c)}if(0!=(f|0)){var c=(d+128|0)>>2,e=a[c];if(0!=(e|0)){var l=a[d+144>>2];0!=(l|0)&&(l=a[l+4>>2],0!=(l|0)&&(J[l](e),e=a[c]));xC(e);Sf(a[c])}a[c]=f;a[f+172>>2]=d;An(d,f,i);a[b+572>>2]=0;a[b+568>>2]=0;m[b+529|0]=1}}function uv(c,d,i){var g=h;h+=48;var q=g+32,e;e=0==(a[c+356>>2]|0);var l=c+348|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);if(e){e=c+512|0;var k=c+496|0,d=d/(l*(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]))-(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);e=c+520|0;k=c+504|0;l=i/(l*(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]))-(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);e=(q|0)>>2;f[0]=d}else{e=c+520|0,k=c+496|0,i=i/(l*(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]))-(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),e=c+512|0,k=c+504|0,l=-d/(l*(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]))-(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),e=(q|0)>>2,f[0]=i}a[e]=b[0];a[e+1]=b[1];d=q+8|0;d>>=2;f[0]=l;a[d]=b[0];a[d+1]=b[1];l=q|0;l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);q=q+8|0;q=(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]);d=c+348|0;d=1/(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]);i=g+16|0;f[0]=l+d;a[i>>2]=b[0];a[i+4>>2]=b[1];i=g+24|0;f[0]=q+d;a[i>>2]=b[0];a[i+4>>2]=b[1];i=g|0;f[0]=l-d;a[i>>2]=b[0];a[i+4>>2]=b[1];l=g+8|0;f[0]=q-d;a[l>>2]=b[0];a[l+4>>2]=b[1];var n,q=a[a[c>>2]+128>>2],d=h,i=g>>2,l=h;h+=32;a[l>>2]=a[i];a[l+4>>2]=a[i+1];a[l+8>>2]=a[i+2];a[l+12>>2]=a[i+3];a[l+16>>2]=a[i+4];a[l+20>>2]=a[i+5];a[l+24>>2]=a[i+6];a[l+28>>2]=a[i+7];i=ra(q);a:for(;;){if(0==(i|0)){n=1222;break}for(var j=Ib(q,i);0!=(j|0);){b:{var p=j;e=l;var s=cc,k=h,s=e>>2;e=h;h+=32;a[e>>2]=a[s];a[e+4>>2]=a[s+1];a[e+8>>2]=a[s+2];a[e+12>>2]=a[s+3];a[e+16>>2]=a[s+4];a[e+20>>2]=a[s+5];a[e+24>>2]=a[s+6];a[e+28>>2]=a[s+7];var v=a[p+24>>2],s=0==(v|0);c:do{if(!s&&0!=(GQa(v+8|0,e)|0)){for(var s=v+4|0,v=v|0,t=0;;){if((t|0)>=(a[s>>2]|0)){break c}if(0==rF(a[v>>2]+48*t|0,e)<<24>>24){t=t+1|0}else{var u=1;break}}h=k;e=u;break b}}while(0);u=a[p+108>>2];u=0!=(u|0)&&0!=as(u,e)<<24>>24?1:0;h=k;e=u}if(0!=e<<24>>24){n=1219;break a}j=yb(q,j)}i=ba(q,i)}do{if(1222==n){var w;w=a[q+20>>2];for(w=J[a[w>>2]](w,0,256);0!=(w|0);){if(0!=qF(w,l)<<24>>24){n=1225;break}u=a[q+20>>2];w=J[a[u>>2]](u,w|0,16)}1225==n?w|=0:(w=qK(q,l),w=0==(w|0)?q|0:w|0)}else{1219==n&&(w=j|0)}}while(0);h=d;n=w;j=c+568|0;(n|0)!=(a[j>>2]|0)&&(w=a[c+568>>2],0!=(w|0)&&(u=a[w>>2]<<28>>28,1==(u|0)?(w=w+133|0,m[w]&=-2):2==(u|0)?(w=w+127|0,m[w]&=-2):3==(u|0)&&(w=w+148|0,m[w]&=-2)),a[c+576>>2]=0,a[j>>2]=n,rK(c),m[c+529|0]=1);h=g}function rK(b){var c,i;i=(b+576|0)>>2;c=a[i];0!=(c|0)&&(G(c),a[i]=0);b=a[b+568>>2];c=b>>2;if(0!=(b|0)){var d=a[c]<<28>>28;2==(d|0)?(d=b+127|0,m[d]|=1,c=$(a[a[a[a[c+3]+20>>2]+40>>2]+4>>2]|0,Pi|0),0!=(c|0)&&(a[i]=ec(mb(b,a[c+8>>2]),b))):1==(d|0)?(d=b+133|0,m[d]|=1,c=$(a[a[a[c+5]+40>>2]>>2]|0,Pi|0),0!=(c|0)&&(a[i]=ec(mb(b,a[c+8>>2]),b))):3==(d|0)&&(d=b+148|0,m[d]|=1,c=$(a[c+8]|0,Pi|0),0!=(c|0)&&(a[i]=ec(mb(b,a[c+8>>2]),b)))}}function qK(c,d){var i,g=h;i=d>>2;d=h;h+=32;a[d>>2]=a[i];a[d+4>>2]=a[i+1];a[d+8>>2]=a[i+2];a[d+12>>2]=a[i+3];a[d+16>>2]=a[i+4];a[d+20>>2]=a[i+5];a[d+24>>2]=a[i+6];a[d+28>>2]=a[i+7];var q;i=a[c+208>>2];for(var e=c+212|0,l=1;(l|0)<=(i|0);){var k=qK(a[a[e>>2]+(l<<2)>>2],d);if(0==(k|0)){l=l+1|0}else{var n=k;q=1259;break}}if(1259==q){return h=g,n}q=c+52|0;i=(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]);q=c+60|0;q=(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]);n=c+76|0;n=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]);e=d+16|0;if((b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])>=i){if(i=c+68|0,e=d|0,(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])>=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])){if(i=d+24|0,(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])>=q){if(q=d+8|0,n>=(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0])){return h=g,c}}}}h=g;return 0}function lK(b){var c=b+572|0,i=a[c>>2];if(0!=(i|0)){var d=a[i>>2]<<28>>28;1==(d|0)?(i=i+133|0,m[i]=m[i]&-7|4):2==(d|0)?(i=i+127|0,m[i]=m[i]&-7|4):3==(d|0)&&(i=i+148|0,m[i]=m[i]&-7|4)}i=b+580|0;d=a[i>>2];0!=(d|0)&&(G(d),a[i>>2]=0);i=a[b+568>>2];a[c>>2]=i;0!=(i|0)&&(c=a[i>>2]<<28>>28,3==(c|0)?(c=i+148|0,m[c]|=2,vv(b,i)):2==(c|0)?(c=i+127|0,m[c]|=2,sK(b,i)):1==(c|0)&&(c=i+133|0,m[c]|=2,tK(b,i)))}function vv(b,c){var i,d;i=b+584|0;d=(c+32|0)>>2;(a[d]|0)==(c|0)?0==(a[c>>2]&16|0)?Fc(i,0,Yf|0):Fc(i,0,wv|0):Fc(i,0,uK|0);Fc(i,1,a[c+12>>2]);a[b+588>>2]=2;var f=b+596|0;i=(c+16|0)>>2;var e=0<(rc(a[a[a[i]+12>>2]+4>>2])|0);a:do{if(e){for(var l=c|0,k=0,h=0;;){var j=a[a[a[a[i]+12>>2]+8>>2]+(h<<2)>>2];Fc(f,k,a[j>>2]);Fc(f,k+1|0,mb(l,a[j+8>>2]));j=k+3|0;Fc(f,k+2|0,0);h=h+1|0;if((h|0)<(rc(a[a[a[i]+12>>2]+4>>2])|0)){k=j}else{var p=j;break a}}}else{p=0}}while(0);a[b+600>>2]=p;p=$(a[d]|0,Ni|0);if(0==(p|0)){if(d=$(a[d]|0,Oi|0),0==(d|0)){return}}else{d=p}p=c|0;a[b+580>>2]=ec(mb(p,a[d+8>>2]),p)}function tK(b,c){var i,d;d=b+584|0;Fc(d,0,jh|0);Fc(d,1,a[c+12>>2]);a[b+588>>2]=2;var f=b+596|0;d=(c+20|0)>>2;i=(a[a[d]+32>>2]+16|0)>>2;var e=0<(rc(a[a[a[i]+4>>2]+4>>2])|0);a:do{if(e){for(var l=c|0,k=0,h=0;;){var j=a[a[a[a[i]+4>>2]+8>>2]+(h<<2)>>2];Fc(f,k,a[j>>2]);var p=k+2|0;Fc(f,k|1,mb(l,a[j+8>>2]));h=h+1|0;if((h|0)<(rc(a[a[a[i]+4>>2]+4>>2])|0)){k=p}else{var m=p;break a}}}else{m=0}}while(0);a[b+600>>2]=m;m=$(a[a[a[d]+40>>2]>>2]|0,Ni|0);if(0==(m|0)){if(d=$(a[a[a[d]+40>>2]>>2]|0,Oi|0),0==(d|0)){return}}else{d=m}m=c|0;a[b+580>>2]=ec(mb(m,a[d+8>>2]),m)}function sK(b,c){var i,d,f,e=b+584|0;Fc(e,0,Dg|0);d=c+16|0;Fc(e,1,a[a[d>>2]+12>>2]);Fc(e,3,0!=(a[a[a[d>>2]+20>>2]>>2]&16|0)?Bf|0:Af|0);d=(c+12|0)>>2;Fc(e,4,a[a[d]+12>>2]);a[b+588>>2]=7;var l=b+596|0;i=(a[a[a[d]+20>>2]+32>>2]+16|0)>>2;var k=0<(rc(a[a[a[i]+8>>2]+4>>2])|0);a:do{if(k){for(var h=c|0,j=0,p=0;;){var m=a[a[a[a[i]+8>>2]+8>>2]+(j<<2)>>2],v=m|0,t=a[v>>2];if(0==(ka(t,vi|0)|0)){f=m+8|0;Fc(e,2,mb(h,a[f>>2]));var u=f;f=1322}else{if(0==(ka(t,wi|0)|0)){f=m+8|0,Fc(e,5,mb(h,a[f>>2])),u=f,f=1322}else{if(m=m+8|0,0!=(ka(t,wr|0)|0)){u=m,f=1322}else{Fc(e,6,mb(h,a[m>>2]));var w=p}}}1322==f&&(f=0,Fc(l,p,a[v>>2]),Fc(l,p+1|0,mb(h,a[u>>2])),w=p+2|0);j=j+1|0;if((j|0)<(rc(a[a[a[i]+8>>2]+4>>2])|0)){p=w}else{var A=w;break a}}}else{A=0}}while(0);a[b+600>>2]=A;e=$(a[a[a[a[d]+20>>2]+40>>2]+4>>2]|0,Ni|0);if(0==(e|0)){if(d=$(a[a[a[a[d]+20>>2]+40>>2]+4>>2]|0,Oi|0),0==(d|0)){return}}else{d=e}e=c|0;a[b+580>>2]=ec(mb(e,a[d+8>>2]),e)}function wC(b,c){var i=b+120|0,d=a[i>>2];0==(d|0)?(d=fa(624),a[i>>2]=d,a[b+124>>2]=d,i=a[Kh>>2]=d):(i=a[Kh>>2],0==(i|0)?i=a[Kh>>2]=d:(i=a[i+4>>2],0==(i|0)&&(i=fa(624),a[a[Kh>>2]+4>>2]=i),a[Kh>>2]=i));a[i+52>>2]=c;a[i>>2]=b;eh(b,3,c)}function Fc(b,c,i){var d=b+8|0;if((a[d>>2]|0)>(c|0)){var f=a[b>>2]}else{f=c+10|0,a[d>>2]=f,b|=0,f=wb(a[b>>2],f<<2),a[b>>2]=f}c=(c<<2)+f|0;a[c>>2]=i}function vK(b){var c=b|0,i=a[c>>2];0!=(i|0)&&G(i);a[c>>2]=0;a[b+8>>2]=0;a[b+4>>2]=0}function Dr(b){var c,i=b+120|0,d=a[i>>2],f=0==(d|0);a:do{if(!f){var e=d;for(c=e>>2;;){var l=a[c+1];vK(e+596|0);vK(e+584|0);var k=a[c+144];0!=(k|0)&&G(k);c=a[c+145];0!=(c|0)&&G(c);G(e);if(0==(l|0)){break a}else{e=l,c=e>>2}}}}while(0);a[Kh>>2]=0;a[Lh>>2]=0;a[b+164>>2]=0;a[b+124>>2]=0;a[i>>2]=0;a[b+28>>2]=0}function aK(b,c,i,d,f,e){var l,k,n=h;h+=128;l=n|0;jk(l,i);var j=Jc(l,58);0!=(j|0)&&(m[j]=0);j=n+64|0;for(b=(c<<2)+b+60|0;;){c=a[b>>2];if(0==(c|0)){k=b;k>>=2;break}jk(j,a[c+4>>2]);c=Jc(j,58);0!=(c|0)&&(m[c]=0);if(1>(ka(l,j)|0)){k=b;k>>=2;break}b=a[b>>2]|0}for(;;){b=a[k];if(0==(b|0)){break}jk(j,a[b+4>>2]);b=Jc(j,58);0!=(b|0)&&(m[b]=0);if(0!=(ka(l,j)|0)){break}b=a[k];if((a[b+8>>2]|0)<=(d|0)){break}k=b|0;k>>=2}j=Cb(20);l=j>>2;a[l]=a[k];a[k]=j;a[l+1]=i;a[l+2]=d;a[l+3]=f;a[l+4]=e;h=n;return 1}function eh(b,c,i){var d,f=h;h+=128;var e,l=f+64,k=2>(c-3|0)>>>0?0:c,n=f|0;jk(n,i);i=Jc(n,58);if(0==(i|0)){var z=i=0}else{z=i+1|0,m[i]=0,i=Jc(z,58),0==(i|0)?i=0:(m[i]=0,i=i+1|0)}var l=l|0,p=0==(z|0),s=0==(i|0),v=(k|0)==(c|0);d=((c<<2)+b+60|0)>>2;a:for(;;){var t=a[d];if(0==(t|0)){var u=0;e=1413;break}jk(l,a[t+4>>2]);t=Jc(l,58);0==(t|0)?t=0:(m[t]=0,t=t+1|0);do{if(0==(ka(l,n)|0)){var w=0==(t|0);if(w|p||0==(ka(t,z)|0)){if(s||0==(ka(i,a[a[a[d]+12>>2]+8>>2])|0)){if(w|v){break a}if(0!=(eh(b,k,t)|0)){break a}}}}}while(0);d=a[d]|0;d>>=2}if(1413==e){return b=((c<<2)+b+80|0)>>2,a[b]=u,h=f,u}u=a[d];if(0==(u|0)){return u=0,b=((c<<2)+b+80|0)>>2,a[b]=u,h=f,u}e=u+16|0;k=a[e>>2];0==(k|0)?(k=h,la(1,wK|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),h=k,e=a[e>>2]):e=k;u=0==(e|0)?0:u;if(0==(u|0)){return u=0,b=((c<<2)+b+80|0)>>2,a[b]=u,h=f,u}if(0>=(a[b+8>>2]|0)){return b=((c<<2)+b+80|0)>>2,a[b]=u,h=f,u}e=a[Mh+(c<<2)>>2];k=a[u+4>>2];n=a[a[u+12>>2]+8>>2];Va(a[oa>>2],xK|0,(j=h,h+=12,a[j>>2]=e,a[j+4>>2]=k,a[j+8>>2]=n,j));b=((c<<2)+b+80|0)>>2;a[b]=u;h=f;return u}function nv(b,c,i){var d,f;m[xv]||(fc(Sb,0,0),m[xv]=1);var i=Hb(i),e=Jc(i,58);do{if(0==(e|0)){var l=(c<<2)+b+60|0;f=1432}else{m[e]=0;var k=(c<<2)+b+60|0;if(0==(e+1|0)){l=k,f=1432}else{var h=a[k>>2];if(0==(h|0)){G(i);var j=1,p=k;f=1434}else{d=k;d>>=2;for(var s=1;;){var h=Hb(a[h+4>>2]),v=Jc(h,58);0!=(v|0)&&(m[v]=0);if(0==m[i]<<24>>24){f=1426}else{if(0==(Lb(i,h)|0)){f=1426}else{var t=s}}1426==f&&(f=0,t=a[Sb+4>>2],t>>>0>2]>>>0||(na(Sb,1),t=a[Sb+4>>2]),a[Sb+4>>2]=t+1|0,m[t]=32,db(Sb,a[a[d]+4>>2]),t=a[Sb+4>>2],t>>>0>2]>>>0||(na(Sb,1),t=a[Sb+4>>2]),a[Sb+4>>2]=t+1|0,m[t]=58,db(Sb,a[a[a[d]+12>>2]+8>>2]),t=0);G(h);d=a[d]|0;h=a[d>>2];if(0==(h|0)){break}else{d>>=2,s=t}}G(i);0!=t<<24>>24&&(j=t,p=k,f=1434)}}}}while(0);1432==f&&(G(i),j=1,p=l,f=1434);do{if(1434==f){b=a[p>>2];do{if(0==(b|0)){var u=j;f=1445}else{c=p;l=0;i=j;for(e=b;;){var w=Hb(a[e+4>>2]),e=Jc(w,58);0!=(e|0)&&(m[e]=0);if(e=0!=(l|0)){if(0==(Lb(l,w)|0)){var A=i}else{f=1439}}else{f=1439}1439==f&&(f=0,A=a[Sb+4>>2],A>>>0>2]>>>0||(na(Sb,1),A=a[Sb+4>>2]),a[Sb+4>>2]=A+1|0,m[A]=32,db(Sb,w),e||G(0),A=0);c=a[c>>2]|0;e=a[c>>2];if(0==(e|0)){break}else{l=w,i=A}}if(0==(w|0)){u=A,f=1445}else{var B=A}}}while(0);1445==f&&(G(0),B=u);if(0!=B<<24>>24){return f=Y|0}}}while(0);f=a[Sb+4>>2];f>>>0>2]>>>0||(na(Sb,1),f=a[Sb+4>>2]);m[f]=0;f=a[Sb>>2];return a[Sb+4>>2]=f}function yv(c,d,i,g){var e=d+496|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),h=d+504|0,l=(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0]),h=d+348|0,k=(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0]),h=d+512|0,h=k*(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0]),n=d+520|0;0==(a[d+356>>2]|0)?(d=l+g,i=e+i):(d=e+i,i=-(l+g));g=d*k*(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]);d=c|0;f[0]=i*h;a[d>>2]=b[0];a[d+4>>2]=b[1];c=c+8|0;f[0]=g;a[c>>2]=b[0];a[c+4>>2]=b[1]}function Re(c,d,i,g){var e=c+496|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),h=c+504|0,h=(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0]),l=c+348|0,k=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),l=c+512|0,l=k*(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),n=c+520|0,k=k*(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),n=0<(g|0);if(0==(a[c+356>>2]|0)){if(n){c=0}else{return i}for(;;){var n=(c<<4)+d|0,n=((b[0]=a[n>>2],b[1]=a[n+4>>2],f[0])+e)*l,j=(c<<4)+i|0;f[0]=n;a[j>>2]=b[0];a[j+4>>2]=b[1];n=(c<<4)+d+8|0;n=((b[0]=a[n>>2],b[1]=a[n+4>>2],f[0])+h)*k;j=(c<<4)+i+8|0;f[0]=n;a[j>>2]=b[0];a[j+4>>2]=b[1];c=c+1|0;if((c|0)==(g|0)){break}}}else{if(n){c=0}else{return i}for(;;){var n=(c<<4)+d+8|0,n=l*-((b[0]=a[n>>2],b[1]=a[n+4>>2],f[0])+h),j=(c<<4)+d|0,j=((b[0]=a[j>>2],b[1]=a[j+4>>2],f[0])+e)*k,p=(c<<4)+i+8|0;f[0]=j;a[p>>2]=b[0];a[p+4>>2]=b[1];j=(c<<4)+i|0;f[0]=n;a[j>>2]=b[0];a[j+4>>2]=b[1];c=c+1|0;if((c|0)==(g|0)){break}}}return i}function Bn(b,c){var d,g,f,e=b>>2;d=a[e];eh(d,3,c);g=a[d+92>>2];if(0==(g|0)){return 999}f=a[g+16>>2]>>2;var l=a[f+3];a[e+19]=l;var k=a[f+4];a[e+21]=k;f=a[f];a[e+20]=f;a[e+22]=a[g+4>>2];g=(b+148|0)>>2;k=a[g]|a[k>>2];a[g]=k;var h=a[d+80>>2];if(0==(h|0)){return a[e+15]=0,999}d=a[h+16>>2]>>2;a[e+15]=a[d+3];var j=a[d+4];a[e+17]=j;a[e+18]=a[h+4>>2];a[g]=k|a[j>>2];a[e+16]=0==(l|0)?f:a[d];return 300}function Cr(b){var c=a[b+60>>2];if(0!=(c|0)&&(c=a[c+4>>2],0!=(c|0))){J[c](b)}a[a[b>>2]+24>>2]=0;iK(b)}function Tr(b,c,d){var g=h;h+=4;a[d>>2]=c;a[d+32>>2]=5;a[g>>2]=ZH(c);var f=a[b+12>>2];0!=(f|0)&&0!=(tn(g,f,a[b+16>>2],4,128)|0)||(b=$H(c,d,a[b+20>>2]),1==(b|0)?(b=Cb(Ba(c)+16|0),Ma(b,yK|0,(j=h,h+=4,a[j>>2]=c,j)),0!=(nD(b)|0)&&la(0,zK|0,(j=h,h+=4,a[j>>2]=c,j)),G(b)):0!=(b|0)&&la(1,AK|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)));h=g}function mD(b){var c=a[b+60>>2];if(0!=(c|0)&&(c=a[c+28>>2],0!=(c|0))){J[c](b)}}function xs(b){var c=a[b+60>>2];if(0!=(c|0)&&(c=a[c+40>>2],0!=(c|0))){J[c](b)}}function ys(b){var c=a[b+60>>2];if(0!=(c|0)&&(c=a[c+44>>2],0!=(c|0))){J[c](b)}}function vs(b){var c=a[b+60>>2];if(0!=(c|0)&&(c=a[c+48>>2],0!=(c|0))){J[c](b)}}function ws(b){var c=a[b+60>>2];if(0!=(c|0)&&(c=a[c+52>>2],0!=(c|0))){J[c](b)}}function WE(b){var c=a[b+60>>2];if(0!=(c|0)&&(c=a[c+56>>2],0!=(c|0))){J[c](b)}}function ad(b,c,d,g,f){var e=a[b+60>>2];if(0!=(e|0)&&(e=a[e+72>>2],0!=(e|0))){J[e](b,c,d,g,f)}}function Se(b){var c=a[b+60>>2];if(0!=(c|0)&&(c=a[c+76>>2],0!=(c|0))){J[c](b)}}function bG(b,c){var d=a[b+60>>2];if(0!=(d|0)&&(d=a[d+80>>2],0!=(d|0))){J[d](b,c)}}function cG(b){var c=a[b+60>>2];if(0!=(c|0)&&(c=a[c+84>>2],0!=(c|0))){J[c](b)}}function As(c,d,i,g){var e=h;h+=16;var j=a[c+60>>2],l=a[g>>2];if(0!=(l|0)&&0!=m[l]<<24>>24){l=a[c+16>>2];if(0!=(l|0)&&0==(a[l+88>>2]|0)){h=e;return}0==(a[c+148>>2]&8192|0)&&(yv(e,c,d,i),d=e|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),i=e+8|0,i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]));if(0!=(j|0)&&(j=a[j+88>>2],0!=(j|0))){J[j](c,d,i,g)}}h=e}function Pa(b,c){var d=a[b+60>>2],g=a[b+16>>2]+16|0,f=Jc(c,58),e=0!=(f|0);e&&(m[f]=0);if(0!=(d|0)&&(Tr(a[b+68>>2],c,g),d=a[d+92>>2],0!=(d|0))){J[d](b,g)}e&&(m[f]=58)}function $b(b,c){var d=a[b+60>>2],g=a[b+16>>2]+52|0,f=Jc(c,58),e=0!=(f|0);e&&(m[f]=0);if(0!=(d|0)&&(Tr(a[b+68>>2],c,g),d=a[d+92>>2],0!=(d|0))){J[d](b,g)}e&&(m[f]=58)}function Zr(c,d){if(0!=(a[c+60>>2]|0)){var i=a[c+16>>2]+96|0;f[0]=d;a[i>>2]=b[0];a[i+4>>2]=b[1]}}function BK(b,c,d,g){if(0==(c|0)){c=g=-1}else{var f=a[c+48>>2];0!=(f|0)&&(d=g=f|0);f=g;g=(72*a[c+40>>2]|0)/d&-1;c=(72*a[c+44>>2]|0)/f&-1}a[b>>2]=g;a[b+4>>2]=c}function Ke(c,d){var i,g,e=h,Xa;i=a[c+60>>2];var l=a[c+16>>2];a[l+104>>2]=d;if(!(0==(i|0)|0==(d|0))){var k=a[d>>2];if(0!=(k|0)){g=(l+88|0)>>2;i=(l+96|0)>>2;for(var l=l+92|0,n=d;;){var n=n+4|0,z=m[k];a:do{if(100==z<<24>>24){0==(ka(k,CK|0)|0)?a[g]=1:0==(ka(k,DK|0)|0)?a[g]=2:Xa=102==z<<24>>24?1818:117==z<<24>>24?1820:1822}else{if(115==z<<24>>24){if(0==(ka(k,wp|0)|0)){a[g]=3}else{if(0==(ka(k,Cp|0)|0)){for(var p=k,s=0;;){var v=p+1|0;if(s){break}p=v;s=0==m[v]<<24>>24}p=wg(v,xc);f[0]=p;a[i]=b[0];a[i+1]=b[1]}else{Xa=1822}}}else{if(105==z<<24>>24){do{if(0!=(ka(k,qh|0)|0)&&0!=(ka(k,EK|0)|0)){Xa=98==z<<24>>24?1811:102==z<<24>>24?1818:117==z<<24>>24?1820:1822;break a}}while(0);a[g]=0}else{Xa=98==z<<24>>24?1811:102==z<<24>>24?1818:117==z<<24>>24?1820:1822}}}}while(0);1811==Xa?(Xa=0,0!=(ka(k,Wb|0)|0)?Xa=1822:(f[0]=2,a[i]=b[0],a[i+1]=b[1])):1818==Xa?(Xa=0,0!=(ka(k,Hi|0)|0)?Xa=1822:a[l>>2]=1):1820==Xa&&(Xa=0,0!=(ka(k,FK|0)|0)?Xa=1822:a[l>>2]=0);1822==Xa&&(Xa=0,la(0,GK|0,(j=h,h+=4,a[j>>2]=k,j)));k=a[n>>2];if(0==(k|0)){break}}}}h=e}function Qk(c,d,i){var g,e=h;h+=32;var j=a[c+60>>2];if(0!=(j|0)&&(j=j+96|0,0!=(a[j>>2]|0)&&0!=(a[a[c+16>>2]+88>>2]|0))){var l=d|0;g=d+16|0;var k=g|0,k=.5*((b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])+(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])),l=e|0,n=e|0;f[0]=k;a[n>>2]=b[0];a[n+4>>2]=b[1];k=d+8|0;d=d+24|0;d=.5*((b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])+(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]));k=e+8|0;f[0]=d;a[k>>2]=b[0];a[k+4>>2]=b[1];d=(e+16|0)>>2;g>>=2;a[d]=a[g];a[d+1]=a[g+1];a[d+2]=a[g+2];a[d+3]=a[g+3];0==(a[c+148>>2]&8192|0)&&Re(c,l,l,2);J[a[j>>2]](c,l,i&255)}h=e}function zc(b,c,d,g){var f=a[b+60>>2];if(0!=(f|0)){var f=f+100|0,e=a[f>>2];if(0!=(e|0)&&0!=(a[a[b+16>>2]+88>>2]|0)){if(0!=(a[b+148>>2]&8192|0)){J[e](b,c,d,g&255)}else{(a[Nh>>2]|0)<(d|0)?(e=d+10|0,a[Nh>>2]=e,e=wb(a[Hf>>2],e<<4),a[Hf>>2]=e):e=a[Hf>>2],Re(b,c,e,d),J[a[f>>2]](b,e,d,g&255)}}}}function mh(c,d,i){var g,e,j=h;h+=64;e=d>>2;d=h;h+=32;a[d>>2]=a[e];a[d+4>>2]=a[e+1];a[d+8>>2]=a[e+2];a[d+12>>2]=a[e+3];a[d+16>>2]=a[e+4];a[d+20>>2]=a[e+5];a[d+24>>2]=a[e+6];a[d+28>>2]=a[e+7];g=j>>2;e=d>>2;a[g]=a[e];a[g+1]=a[e+1];a[g+2]=a[e+2];a[g+3]=a[e+3];e=j+32|0;g=e>>2;d=(d+16|0)>>2;a[g]=a[d];a[g+1]=a[d+1];a[g+2]=a[d+2];a[g+3]=a[d+3];d=j|0;d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]);g=j+16|0;f[0]=d;a[g>>2]=b[0];a[g+4>>2]=b[1];d=j+40|0;d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]);g=j+24|0;f[0]=d;a[g>>2]=b[0];a[g+4>>2]=b[1];e|=0;e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);d=j+48|0;f[0]=e;a[d>>2]=b[0];a[d+4>>2]=b[1];e=j+8|0;e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);d=j+56|0;f[0]=e;a[d>>2]=b[0];a[d+4>>2]=b[1];zc(c,j|0,4,i);h=j}function $f(b,c,d,g,f,e){var l=a[b+60>>2];if(0!=(l|0)){var l=l+104|0,k=a[l>>2];if(0!=(k|0)&&0!=(a[a[b+16>>2]+88>>2]|0)){if(0!=(a[b+148>>2]&8192|0)){J[k](b,c,d,g,f,e&255)}else{(a[Nh>>2]|0)<(d|0)?(k=d+10|0,a[Nh>>2]=k,k=wb(a[Hf>>2],k<<4),a[Hf>>2]=k):k=a[Hf>>2],Re(b,c,k,d),J[a[l>>2]](b,k,d,g,f,e&255)}}}}function Gd(b,c,d){var g=a[b+60>>2];if(0!=(g|0)){var g=g+108|0,f=a[g>>2];if(0!=(f|0)&&0!=(a[a[b+16>>2]+88>>2]|0)){if(0!=(a[b+148>>2]&8192|0)){J[f](b,c,d)}else{(a[Nh>>2]|0)<(d|0)?(f=d+10|0,a[Nh>>2]=f,f=wb(a[Hf>>2],f<<4),a[Hf>>2]=f):f=a[Hf>>2],Re(b,c,f,d),J[a[g>>2]](b,f,d)}}}}function yk(b,c){var d=a[b+60>>2];if(0!=(c|0)&&!(0==m[c]<<24>>24|0==(d|0))&&(d=a[d+112>>2],0!=(d|0))){J[d](b,c)}}function ut(c,d,i,g,e,Xa){var l,k,n,z,p,s,v,t,u,w=h;h+=72;u=w+32;k=w+40;l=w+56;var A=a[c+60>>2],B=HK(d);if(0==(B|0)){if(!(0==(Is(d)|0)|0==(A|0))&&(B=a[A+116>>2],0!=(B|0))){J[B](c,d,i,g,e&255)}}else{var d=c+424|0,C=c+432|0;BK(u,B,(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0]));var C=a[u>>2],y=a[u+4>>2];if(!(1>(C|0)&1>(y|0))){n=w+16|0;u=n>>2;p=i>>2;a[u]=a[p];a[u+1]=a[p+1];a[u+2]=a[p+2];a[u+3]=a[p+3];d=w>>2;a[d]=a[p];a[d+1]=a[p+1];a[d+2]=a[p+2];a[d+3]=a[p+3];if(1<(g|0)){t=(w|0)>>2;v=(w+8|0)>>2;s=(n|0)>>2;p=(w+24|0)>>2;for(var T=(b[0]=a[t],b[1]=a[t+1],f[0]),D=(b[0]=a[v],b[1]=a[v+1],f[0]),F=1,M=T,T=(b[0]=a[s],b[1]=a[s+1],f[0]),X=(b[0]=a[p],b[1]=a[p+1],f[0]);;){z=(F<<4)+i|0;var O=(b[0]=a[z>>2],b[1]=a[z+4>>2],f[0]),E=M>2],b[1]=a[z+4>>2],f[0]);z=DO?T:O;G=X>G?X:G;F=F+1|0;if((F|0)==(g|0)){break}else{M=E,D=z,T=O,X=G}}f[0]=G;a[p]=b[0];a[p+1]=b[1];f[0]=O;a[s]=b[0];a[s+1]=b[1];f[0]=z;a[v]=b[0];a[v+1]=b[1];f[0]=E;a[t]=b[0];a[t+1]=b[1];p=E;E=G;G=z}else{i=n|0,z=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),i=w|0,E=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),g=w+24|0,i=w+8|0,O=z,p=E,E=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),G=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])}z=(n|0)>>2;g=(w|0)>>2;v=O-p;i=(w+24|0)>>2;n=(w+8|0)>>2;s=E-G;y|=0;C|=0;t=v/C;F=s/y;Xa=0==m[Xa]<<24>>24?0:0==(Lb(Xa,rp|0)|0)?2:0==(Lb(Xa,qp|0)|0)?3:0==(Lb(Xa,zv|0)|0)?4:0!=re(Xa)<<24>>24&1;1==(Xa|0)?t>2]&8192|0)?(yv(k,c,C,Xa),k>>=2,a[d]=a[k],a[d+1]=a[k+1],a[d+2]=a[k+2],a[d+3]=a[k+3],yv(l,c,(b[0]=a[z],b[1]=a[z+1],f[0]),(b[0]=a[i],b[1]=a[i+1],f[0])),l>>=2,a[u]=a[l],a[u+1]=a[l+1],a[u+2]=a[l+2],a[u+3]=a[l+3],l=(b[0]=a[g],b[1]=a[g+1],f[0]),k=(b[0]=a[z],b[1]=a[z+1],f[0])):(l=C,k=y);l>k&&(f[0]=k,a[g]=b[0],a[g+1]=b[1],f[0]=l,a[z]=b[0],a[z+1]=b[1]);l=(b[0]=a[n],b[1]=a[n+1],f[0]);k=(b[0]=a[i],b[1]=a[i+1],f[0]);l>k&&(f[0]=k,a[n]=b[0],a[n+1]=b[1],f[0]=l,a[i]=b[0],a[i+1]=b[1]);if(0!=(A|0)){u=a[c+72>>2];l=h;h+=128;k=w>>2;A=h;h+=32;a[A>>2]=a[k];a[A+4>>2]=a[k+1];a[A+8>>2]=a[k+2];a[A+12>>2]=a[k+3];a[A+16>>2]=a[k+4];a[A+20>>2]=a[k+5];a[A+24>>2]=a[k+6];a[A+28>>2]=a[k+7];k=l|0;Rf(k,a[B+28>>2]);d=l+Ba(k)|0;Pb=58;m[d]=Pb&255;Pb>>=8;m[d+1]=Pb&255;uf(k,u);u=eh(a[c>>2],4,k);0==(u|0)?u=999:(u=a[u+16>>2],a[c+92>>2]=a[u+12>>2],a[c+96>>2]=a[u>>2],u=300);999==(u|0)&&la(0,IK|0,(j=h,h+=4,a[j>>2]=k,j));k=a[c+92>>2];if(0!=(k|0)&&(k=a[k>>2],0!=(k|0))){J[k](c,B,A,e)}h=l}}}h=w}function HK(b){var c=h;h+=64;var d=a[Il>>2];if(0==(d|0)){return h=c,0}a[c+8>>2]=b;b=J[a[d>>2]](d,c,4);h=c;return b}function JK(b){var c=h;0==(b|0)&&sa(Av|0,363,Bv|0,Oh|0);var d=b+8|0;0==(a[d>>2]|0)&&sa(Av|0,364,Bv|0,Ph|0);var g=b+20|0,f=a[g>>2];0==(f|0)?(d=Uk(a[d>>2]),0==(d|0)?b=1:(f=qi(d,cj|0),a[g>>2]=f,0==(f|0)?(b=si(a[Ea.c>>2]),la(0,KK|0,(j=h,h+=8,a[j>>2]=b,a[j+4>>2]=d,j)),b=0):(g=a[Cv>>2],49<(g|0)?m[b+17|0]=1:a[Cv>>2]=g+1|0,b=1))):(xg(f,0,0),b=1);h=c;return b}function LK(b){if(0!=m[b+17|0]<<24>>24){var b=b+20|0,c=a[b>>2];0!=(c|0)&&(ri(c),a[b>>2]=0)}}function oH(c,d,i){0!=(i|0)&&0!=m[i]<<24>>24?(d=a[d+44>>2]+24|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),d=1>d?96:d,BK(c,MK(i),d,d)):(a[c>>2]=-1,a[c+4>>2]=-1)}function MK(b){var c=h;if(0==(a[Il>>2]|0)){var d=Nc(NK,a[Xc>>2]);a[Il>>2]=d}d=HK(b);if(0==(d|0)){d=fa(64);if(0==(d|0)){return h=c,0}var g=d+8|0;a[g>>2]=b;if(0==JK(d)<<24>>24){return h=c,0}b=OK(d);if(4==(b|0)){PK(d)}else{if(3==(b|0)){var f,b=h;h+=8;g=b+4;a[d+48>>2]=0;f=(d+20|0)>>2;xg(a[f],16,0);0!=If(a[f],4,b)<<24>>24&&0!=If(a[f],4,g)<<24>>24&&(a[d+40>>2]=a[b>>2],a[d+44>>2]=a[g>>2]);h=b}else{if(0==(b|0)){return f=a[g>>2],b=Is(f),a[d+52>>2]=b,0==(b|0)&&la(0,QK|0,(j=h,h+=4,a[j>>2]=f,j)),G(d),h=c,0}if(6==(b|0)){b=d>>2;g=h;h+=1040;var e=g+1024,l=g+1028,k=g+1032,n=g+1036;a[b+12]=72;var m=d+20|0;xg(a[m>>2],0,0);for(var p=g|0;;){if(0==(oi(p,1024,a[m>>2])|0)){f=2073;break}var s=un(p,RK|0);if(0!=(s|0)&&4==(Cd(s,Nu|0,(j=h,h+=16,a[j>>2]=e,a[j+4>>2]=l,a[j+8>>2]=k,a[j+12>>2]=n,j))|0)){break}}2073!=f&&(f=a[e>>2],a[b+8]=f,l=a[l>>2],a[b+9]=l,a[b+10]=a[k>>2]-f|0,a[b+11]=a[n>>2]-l|0);h=g}else{1==(b|0)?(b=h,h+=16,g=b+4,k=b+8,n=b+12,a[d+48>>2]=0,f=(d+20|0)>>2,xg(a[f],16,0),0!=hj(a[f],b)<<24>>24&&0!=hj(a[f],g)<<24>>24&&0!=hj(a[f],k)<<24>>24&&0!=hj(a[f],n)<<24>>24&&(a[d+40>>2]=a[b>>2]<<16|a[g>>2],a[d+44>>2]=a[k>>2]<<16|a[n>>2]),h=b):2==(b|0)?(b=h,h+=8,g=b+4,a[d+48>>2]=0,f=(d+20|0)>>2,xg(a[f],6,0),0!=hj(a[f],b)<<24>>24&&0!=hj(a[f],g)<<24>>24&&(a[d+40>>2]=a[b>>2],a[d+44>>2]=a[g>>2]),h=b):8==(b|0)&&SK(d)}}}f=a[Il>>2];J[a[f>>2]](f,d,1)}LK(d);h=c;return d}function OK(b){var c=h;h+=220;var d,g=c+20,f=b+20|0,e=a[f>>2],l=0==(e|0);a:do{if(!l){var k=c|0;if(20==(lC(k,1,20,e)|0)){var e=b+28|0,l=b+24|0,g=g|0,n=0;b:for(;;){if(8<=n>>>0){break a}var j=0==(Ve(k,a[Qc+(n<<4)>>2],a[Qc+(n<<4)+4>>2])|0);c:do{if(j){a[e>>2]=a[Qc+(n<<4)+12>>2];var p=a[Qc+(n<<4)+8>>2];a[l>>2]=p;if(7!=(n|0)){var m=p;d=2012;break b}for(;;){if(0==(oi(g,200,a[f>>2])|0)){break c}if(0==(Ve(g,TK|0,4)|0)){break b}}}}while(0);n=n+1|0}if(2012==d){return h=c,m}a[e>>2]=Dp|0;m=a[l>>2]=8;h=c;return m}}}while(0);a[b+28>>2]=UK|0;a[b+24>>2]=0;h=c;return 0}function PK(b){var c=b>>2,d=h;h+=20;var g,f=d+4,e=d+8,l=d+12,k=d+16;a[c+12]=0;for(b=(b+20|0)>>2;;){if(0==If(a[b],1,d)<<24>>24){g=2057;break}var n=a[d>>2];if(255!=(n|0)&&0==(Jc(VK|0,n)|0)){if(192==(n|0)){g=2044;break}var j=a[b];if(194==(n|0)){g=2049;break}if(0==If(j,2,f)<<24>>24){g=2064;break}xg(a[b],a[f>>2]-2|0,1)}}2064==g?h=d:2049==g?(0!=If(j,3,k)<<24>>24&&0!=If(a[b],2,e)<<24>>24&&0!=If(a[b],2,l)<<24>>24&&(a[c+11]=a[e>>2],a[c+10]=a[l>>2]),h=d):2044==g?(0!=If(a[b],3,k)<<24>>24&&0!=If(a[b],2,e)<<24>>24&&0!=If(a[b],2,l)<<24>>24&&(a[c+11]=a[e>>2],a[c+10]=a[l>>2]),h=d):2057==g&&(h=d)}function SK(c){var d,i=h;h+=220;var g;d=i>>2;var e=c+20|0,Xa=i+20|0;xg(a[e>>2],-Ba(Xa)|0,1);var l=i+8|0,k=0,n=0,z=0,p=0;a:for(;;){if(0==(oi(Xa,200,a[e>>2])|0)){g=2087;break}if(!(0==p<<24>>24|0==k<<24>>24)){g=2086;break}for(var s=k,v=n,t=z,u=he(Xa,gc|0),w=p;;){if(0==(u|0)){k=s;n=v;z=t;p=w;continue a}if(62==m[u+(Ba(u)-1)|0]<<24>>24){k=s;n=v;z=t;p=w;continue a}if(2==(Cd(u,WK|0,(j=h,h+=8,a[j>>2]=i,a[j+4>>2]=l,j))|0)){if(v=Dv((b[0]=a[d],b[1]=a[d+1],f[0]),l),0==s<<24>>24){w=1}else{k=s;n=v;z=t;p=1;continue a}}if(2==(Cd(u,XK|0,(j=h,h+=8,a[j>>2]=i,a[j+4>>2]=l,j))|0)){if(t=Dv((b[0]=a[d],b[1]=a[d+1],f[0]),l),0==w<<24>>24){s=1}else{k=1;n=v;z=t;p=w;continue a}}u=he(0,gc|0)}}2087==g?(a[(c+48|0)>>2]=72,a[(c+40|0)>>2]=n,a[(c+44|0)>>2]=z,h=i):2086==g&&(a[(c+48|0)>>2]=72,a[(c+40|0)>>2]=n,a[(c+44|0)>>2]=z,h=i)}function Dv(a,b){if(0==(ka(b,Ep|0)|0)){var c=72*a;return(0>c?c-.5:c+.5)&-1}if(0==(ka(b,YK|0)|0)){return c=72*a/96,(0>c?c-.5:c+.5)&-1}if(0==(ka(b,ZK|0)|0)){return c=72*a/6,(0>c?c-.5:c+.5)&-1}if(0!=(ka(b,$K|0)|0)&&0!=(ka(b,ue|0)|0)){if(0==(ka(b,aL|0)|0)){return c=28.346456664*a,(0>c?c-.5:c+.5)&-1}if(0!=(ka(b,bL|0)|0)){return 0}c=2.8346456663999997*a;return(0>c?c-.5:c+.5)&-1}return(0>a?a-.5:a+.5)&-1}function If(b,c,d){for(var g,f=a[d>>2]=0;;){if(f>>>0>=c>>>0){var e=1;g=2133;break}var l=ni(b);if(0!=(Number(Q.a[b]&&Q.a[b].f)|0)){e=0;g=2132;break}a[d>>2]=a[d>>2]<<8|l;f=f+1|0}if(2132==g||2133==g){return e}}function hj(b,c){for(var d,g=a[c>>2]=0;;){if(2<=g>>>0){var f=1;d=2139;break}var e=ni(b);if(0!=(Number(Q.a[b]&&Q.a[b].f)|0)){f=0;d=2140;break}a[c>>2]|=e<<(g<<3);g=g+1|0}if(2139==d||2140==d){return f}}function cL(a){var b=1-a;return 3*a*b*b}function Jf(c,d,i,g){var e=c|0;f[0]=d*g;a[e>>2]=b[0];a[e+4>>2]=b[1];c=c+8|0;f[0]=i*g;a[c>>2]=b[0];a[c+4>>2]=b[1]}function Ev(c,d,i,g,e){var h=c|0;f[0]=d+g;a[h>>2]=b[0];a[h+4>>2]=b[1];c=c+8|0;f[0]=i+e;a[c>>2]=b[0];a[c+4>>2]=b[1]}function Fp(c,d,i,g,e){var h=c|0;f[0]=d-g;a[h>>2]=b[0];a[h+4>>2]=b[1];c=c+8|0;f[0]=i-e;a[c>>2]=b[0];a[c+4>>2]=b[1]}function ZG(c,d,i,g,e,j){var l,k,n,m,p,s=h;h+=32;l=s+16;p=(e|0)>>2;m=(e+8|0)>>2;Jl(s,(b[0]=a[p],b[1]=a[p+1],f[0]),(b[0]=a[m],b[1]=a[m+1],f[0]));k=e>>2;n=s>>2;a[k]=a[n];a[k+1]=a[n+1];a[k+2]=a[n+2];a[k+3]=a[n+3];k=e+16|0;n=(k|0)>>2;e=(e+24|0)>>2;Jl(l,(b[0]=a[n],b[1]=a[n+1],f[0]),(b[0]=a[e],b[1]=a[e+1],f[0]));k>>=2;l>>=2;a[k]=a[l];a[k+1]=a[l+1];a[k+2]=a[l+2];a[k+3]=a[l+3];a[ge>>2]=0;Fv(4);l=a[ge>>2];a[ge>>2]=l+1|0;k=((l<<4)+a[Lg>>2]|0)>>2;l=i>>2;a[k]=a[l];a[k+1]=a[l+1];a[k+2]=a[l+2];a[k+3]=a[l+3];p=(b[0]=a[p],b[1]=a[p+1],f[0]);m=(b[0]=a[m],b[1]=a[m+1],f[0]);if(-1==(Gp(c,d,i,g,p,m,(b[0]=a[n],b[1]=a[n+1],f[0]),(b[0]=a[e],b[1]=a[e+1],f[0]))|0)){return h=s,-1}a[j+4>>2]=a[ge>>2];a[j>>2]=a[Lg>>2];h=s;return 0}function Jl(c,d,i){var g=d*d+i*i;1e-6>2]=b[0];a[g+4>>2]=b[1];c=c+8|0;f[0]=i;a[c>>2]=b[0];a[c+4>>2]=b[1]}function Gp(c,d,i,g,e,j,l,k){var n,m,p,s,v,t,u,w,A=h;h+=256;var B=A+16,C=A+32,y=A+48,T=A+64,D=A+80,F=A+96,M=A+112,X=A+128,O=A+144,E=A+160,G=A+176,ia=A+192,J=A+208,I=A+224,H=A+240,K=a[Kl>>2];if((a[Gv>>2]|0)<(g|0)){if(0==(K|0)){var L=Gb(40*g|0);a[Kl>>2]=L;if(0==(L|0)){var N=-1;h=A;return N}var Q=L}else{var S=mc(K,40*g|0);a[Kl>>2]=S;if(0==(S|0)){return N=-1,h=A,N}Q=S}a[Gv>>2]=g;var ca=Q}else{ca=K}var R=ca|0;f[0]=0;a[R>>2]=b[0];a[R+4>>2]=b[1];var U=1<(g|0);a:do{if(U){for(var W=1,ja=0;;){var aa=W-1|0,da=(W<<4)+i|0,ea=(b[0]=a[da>>2],b[1]=a[da+4>>2],f[0]),xa=(W<<4)+i+8|0,Z=(b[0]=a[xa>>2],b[1]=a[xa+4>>2],f[0]),Y=(aa<<4)+i|0,ha=(aa<<4)+i+8|0,ga=ja+Hv(ea,Z,(b[0]=a[Y>>2],b[1]=a[Y+4>>2],f[0]),(b[0]=a[ha>>2],b[1]=a[ha+4>>2],f[0])),V=ca+40*W|0;f[0]=ga;a[V>>2]=b[0];a[V+4>>2]=b[1];var $=W+1|0;if(($|0)==(g|0)){break}else{W=$,ja=ga}}for(var la=ca+40*(g-1)|0,ba=1;;){var ya=(b[0]=a[la>>2],b[1]=a[la+4>>2],f[0]);w=(ca+40*ba|0)>>2;var sa=(b[0]=a[w],b[1]=a[w+1],f[0])/ya;f[0]=sa;a[w]=b[0];a[w+1]=b[1];var wa=ba+1|0;if((wa|0)==(g|0)){break a}else{ba=wa}}}}while(0);var Ab=0<(g|0);a:do{if(Ab){u=T>>2;t=D>>2;for(var Fa=0;;){v=(ca+40*Fa|0)>>2;Jf(T,e,j,cL((b[0]=a[v],b[1]=a[v+1],f[0])));s=(ca+40*Fa+8|0)>>2;a[s]=a[u];a[s+1]=a[u+1];a[s+2]=a[u+2];a[s+3]=a[u+3];var Ga=D,fa=l,ta=k,Ka=(b[0]=a[v],b[1]=a[v+1],f[0]);Jf(Ga,fa,ta,3*Ka*Ka*(1-Ka));p=(ca+40*Fa+24|0)>>2;a[p]=a[t];a[p+1]=a[t+1];a[p+2]=a[t+2];a[p+3]=a[t+3];var za=Fa+1|0;if((za|0)==(g|0)){break a}else{Fa=za}}}}while(0);dL(i,g,ca,e,j,l,k,A,C,B,y);var ma=A|0,pa=(b[0]=a[ma>>2],b[1]=a[ma+4>>2],f[0]),ra=A+8|0,Ha=(b[0]=a[ra>>2],b[1]=a[ra+4>>2],f[0]),Ra=C|0,ka=(b[0]=a[Ra>>2],b[1]=a[Ra+4>>2],f[0]),qa=C+8|0,La=(b[0]=a[qa>>2],b[1]=a[qa+4>>2],f[0]),na=B|0,Ya=(b[0]=a[na>>2],b[1]=a[na+4>>2],f[0]),oa=B+8|0,Za=(b[0]=a[oa>>2],b[1]=a[oa+4>>2],f[0]),Ba=y|0,ab=(b[0]=a[Ba>>2],b[1]=a[Ba+4>>2],f[0]),$a=y+8|0,jb=(b[0]=a[$a>>2],b[1]=a[$a+4>>2],f[0]);if(0!=(eL(c,d,pa,Ha,ka,La,Ya,Za,ab,jb,i,g)|0)){return N=0,h=A,N}Jf(F,ka,La,.3333333333333333);var Ca=F|0,Ia=F+8|0;Ev(M,pa,Ha,(b[0]=a[Ca>>2],b[1]=a[Ca+4>>2],f[0]),(b[0]=a[Ia>>2],b[1]=a[Ia+4>>2],f[0]));var eb=M|0,ub=(b[0]=a[eb>>2],b[1]=a[eb+4>>2],f[0]),Sa=M+8|0,va=(b[0]=a[Sa>>2],b[1]=a[Sa+4>>2],f[0]);Jf(X,ab,jb,.3333333333333333);var ua=X|0,Oa=X+8|0;Fp(O,Ya,Za,(b[0]=a[ua>>2],b[1]=a[ua+4>>2],f[0]),(b[0]=a[Oa>>2],b[1]=a[Oa+4>>2],f[0]));var Wa=O|0,pb=(b[0]=a[Wa>>2],b[1]=a[Wa+4>>2],f[0]),ob=O+8|0,bb=(b[0]=a[ob>>2],b[1]=a[ob+4>>2],f[0]),qb=g-1|0,Aa=1<(qb|0);a:do{if(Aa){for(var kb=a[Kl>>2],Ea=-1,vb=-1,xb=1;;){var Qa=kb+40*xb|0,nb=(b[0]=a[Qa>>2],b[1]=a[Qa+4>>2],f[0]),rb,lb=1-nb;rb=lb*lb*lb;var Ta=cL(nb),cb=3*nb*nb*(1-nb),fb=nb*nb*nb,Ua=(xb<<4)+i|0,sb=(xb<<4)+i+8|0,Na=Hv(rb*pa+Ta*ub+cb*pb+fb*Ya,rb*Ha+Ta*va+cb*bb+fb*Za,(b[0]=a[Ua>>2],b[1]=a[Ua+4>>2],f[0]),(b[0]=a[sb>>2],b[1]=a[sb+4>>2],f[0])),Fb=Na>Ea,Db=Fb?xb:vb,Ob=xb+1|0;if((Ob|0)==(qb|0)){var Eb=Db;break a}else{Ea=Fb?Na:Ea,vb=Db,xb=Ob}}}else{Eb=-1}}while(0);var Ma=(Eb<<4)+i|0,Bb=Eb-1|0;m=(Ma|0)>>2;var Ja=(b[0]=a[m],b[1]=a[m+1],f[0]);n=((Eb<<4)+i+8|0)>>2;var Va=(b[0]=a[n],b[1]=a[n+1],f[0]),Cb=(Bb<<4)+i|0,wb=(Bb<<4)+i+8|0;Fp(E,Ja,Va,(b[0]=a[Cb>>2],b[1]=a[Cb+4>>2],f[0]),(b[0]=a[wb>>2],b[1]=a[wb+4>>2],f[0]));var Pa=E|0,hb=E+8|0;Jl(G,(b[0]=a[Pa>>2],b[1]=a[Pa+4>>2],f[0]),(b[0]=a[hb>>2],b[1]=a[hb+4>>2],f[0]));var ib=G|0,ic=(b[0]=a[ib>>2],b[1]=a[ib+4>>2],f[0]),db=G+8|0,Ib=(b[0]=a[db>>2],b[1]=a[db+4>>2],f[0]),yb=Eb+1|0,mb=(yb<<4)+i|0,Kb=(b[0]=a[mb>>2],b[1]=a[mb+4>>2],f[0]),pc=(yb<<4)+i+8|0,Jb=(b[0]=a[pc>>2],b[1]=a[pc+4>>2],f[0]);Fp(ia,Kb,Jb,(b[0]=a[m],b[1]=a[m+1],f[0]),(b[0]=a[n],b[1]=a[n+1],f[0]));var Vb=ia|0,Hb=ia+8|0;Jl(J,(b[0]=a[Vb>>2],b[1]=a[Vb+4>>2],f[0]),(b[0]=a[Hb>>2],b[1]=a[Hb+4>>2],f[0]));var Lb=J|0,Mb=J+8|0;Ev(I,ic,Ib,(b[0]=a[Lb>>2],b[1]=a[Lb+4>>2],f[0]),(b[0]=a[Mb>>2],b[1]=a[Mb+4>>2],f[0]));var Nb=I|0,Pb=I+8|0;Jl(H,(b[0]=a[Nb>>2],b[1]=a[Nb+4>>2],f[0]),(b[0]=a[Pb>>2],b[1]=a[Pb+4>>2],f[0]));var Wb=H|0,lc=(b[0]=a[Wb>>2],b[1]=a[Wb+4>>2],f[0]),Tb=H+8|0,Yb=(b[0]=a[Tb>>2],b[1]=a[Tb+4>>2],f[0]);Gp(c,d,i,yb,e,j,lc,Yb);Gp(c,d,Ma,g-Eb|0,lc,Yb,l,k);N=0;h=A;return N}function Hv(a,b,c,d){a=c-a;b=d-b;return $c(a*a+b*b)}function dL(c,d,i,g,e,j,l,k,n,m,p){var s,v,t,u,w,A,B,C,y,T,D,F,M,X=h;h+=96;var O=X+16,E=X+32,G=X+48,ia=X+64,J=X+80,I=0<(d|0);a:do{if(I){for(var H=c|0,K=c+8|0,L=d-1|0,N=(L<<4)+c|0,S=(L<<4)+c+8|0,Q=O|0,ca=O+8|0,R=E|0,U=E+8|0,W=X|0,ja=X+8|0,aa=G|0,da=G+8|0,ea=0,xa=0,Y=0,Z=0,ha=0,ga=0;;){M=(i+40*ga+8|0)>>2;var V=(b[0]=a[M],b[1]=a[M+1],f[0]);F=(i+40*ga+16|0)>>2;var $=(b[0]=a[F],b[1]=a[F+1],f[0]),la=ea+(V*V+$*$);D=(i+40*ga+24|0)>>2;var ba=(b[0]=a[D],b[1]=a[D+1],f[0]);T=(i+40*ga+32|0)>>2;var ya=(b[0]=a[T],b[1]=a[T+1],f[0]),sa=xa+(V*ba+$*ya),wa=Y+(ba*ba+ya*ya);y=(i+40*ga|0)>>2;var fa,Fa=(b[0]=a[y],b[1]=a[y+1],f[0]),Ga=1-Fa;fa=Ga*Ga*(Ga+3*Fa);Jf(O,(b[0]=a[H>>2],b[1]=a[H+4>>2],f[0]),(b[0]=a[K>>2],b[1]=a[K+4>>2],f[0]),fa);var ra,ta=(b[0]=a[y],b[1]=a[y+1],f[0]);ra=ta*ta*(3*(1-ta)+ta);Jf(E,(b[0]=a[N>>2],b[1]=a[N+4>>2],f[0]),(b[0]=a[S>>2],b[1]=a[S+4>>2],f[0]),ra);var Ka=(b[0]=a[Q>>2],b[1]=a[Q+4>>2],f[0]),za=(b[0]=a[ca>>2],b[1]=a[ca+4>>2],f[0]);Ev(X,Ka,za,(b[0]=a[R>>2],b[1]=a[R+4>>2],f[0]),(b[0]=a[U>>2],b[1]=a[U+4>>2],f[0]));var ma=(ga<<4)+c|0,pa=(b[0]=a[ma>>2],b[1]=a[ma+4>>2],f[0]),ka=(ga<<4)+c+8|0,Ha=(b[0]=a[ka>>2],b[1]=a[ka+4>>2],f[0]);Fp(G,pa,Ha,(b[0]=a[W>>2],b[1]=a[W+4>>2],f[0]),(b[0]=a[ja>>2],b[1]=a[ja+4>>2],f[0]));var Ra=(b[0]=a[aa>>2],b[1]=a[aa+4>>2],f[0]),qa=(b[0]=a[da>>2],b[1]=a[da+4>>2],f[0]),na=Z,La=(b[0]=a[M],b[1]=a[M+1],f[0]),oa=(b[0]=a[F],b[1]=a[F+1],f[0]),Ya=na+(La*Ra+oa*qa),Ba=ha,Za=(b[0]=a[D],b[1]=a[D+1],f[0]),va=(b[0]=a[T],b[1]=a[T+1],f[0]),ab=Ba+(Za*Ra+va*qa),$a=ga+1|0;if(($a|0)==(d|0)){var jb=la,Ca=sa,Ia=wa,eb=Ya,ub=ab;break a}else{ea=la,xa=sa,Y=wa,Z=Ya,ha=ab,ga=$a}}}else{ub=eb=Ia=Ca=jb=0}}while(0);var Sa=jb*Ia-Ca*Ca,Ea=0<=Sa;if(1e-6>(Ea?Sa:-Sa)){var ua=0,Oa=0}else{ua=(jb*ub-Ca*eb)/Sa,Oa=(eb*Ia-ub*Ca)/Sa}var Wa=d-1|0;if(1e-6<=(Ea?Sa:-Sa)&0>2;var qb=c;B=qb>>2;a[C]=a[B];a[C+1]=a[B+1];a[C+2]=a[B+2];a[C+3]=a[B+3];Jf(ia,g,e,ob);var Aa=n;A=Aa>>2;var kb=ia;w=kb>>2;a[A]=a[w];a[A+1]=a[w+1];a[A+2]=a[w+2];a[A+3]=a[w+3];var Qa=(Wa<<4)+c|0,vb=m;u=vb>>2;var xb=Qa;t=xb>>2;a[u]=a[t];a[u+1]=a[t+1];a[u+2]=a[t+2];a[u+3]=a[t+3];Jf(J,j,l,pb);var Ma=p;v=Ma>>2;var nb=J}else{var rb=c|0,lb=(b[0]=a[rb>>2],b[1]=a[rb+4>>2],f[0]),Ta=c+8|0,cb=(b[0]=a[Ta>>2],b[1]=a[Ta+4>>2],f[0]),fb=(Wa<<4)+c|0,Ua=(Wa<<4)+c+8|0,sb=Hv(lb,cb,(b[0]=a[fb>>2],b[1]=a[fb+4>>2],f[0]),(b[0]=a[Ua>>2],b[1]=a[Ua+4>>2],f[0]))/3,ob=pb=sb,bb=k;C=bb>>2;qb=c;B=qb>>2;a[C]=a[B];a[C+1]=a[B+1];a[C+2]=a[B+2];a[C+3]=a[B+3];Jf(ia,g,e,ob);Aa=n;A=Aa>>2;kb=ia;w=kb>>2;a[A]=a[w];a[A+1]=a[w+1];a[A+2]=a[w+2];a[A+3]=a[w+3];Qa=(Wa<<4)+c|0;vb=m;u=vb>>2;xb=Qa;t=xb>>2;a[u]=a[t];a[u+1]=a[t+1];a[u+2]=a[t+2];a[u+3]=a[t+3];Jf(J,j,l,pb);Ma=p;v=Ma>>2;nb=J}s=nb>>2;a[v]=a[s];a[v+1]=a[s+1];a[v+2]=a[s+2];a[v+3]=a[s+3];h=X}function eL(c,d,i,g,e,j,l,k,n,m,p,s){var v,t,u,w,A,B,C=h;h+=64;var y,T=2==(s|0),D=C|0,F=C|0,M=C+8|0;B=(C+16|0)>>2;A=(C+24|0)>>2;w=(C+32|0)>>2;u=(C+40|0)>>2;t=(C+48|0)>>2;v=(C+56|0)>>2;for(var E=1,O=4,G=4;;){f[0]=i;a[F>>2]=b[0];a[F+4>>2]=b[1];f[0]=g;a[M>>2]=b[0];a[M+4>>2]=b[1];f[0]=G*e/3+i;a[B]=b[0];a[B+1]=b[1];f[0]=G*j/3+g;a[A]=b[0];a[A+1]=b[1];f[0]=l-O*n/3;a[w]=b[0];a[w+1]=b[1];f[0]=k-O*m/3;a[u]=b[0];a[u+1]=b[1];f[0]=l;a[t]=b[0];a[t+1]=b[1];f[0]=k;a[v]=b[0];a[v+1]=b[1];if(E&&fL(D,4)>2]+4|0),d=a[Lg>>2],c=a[ge>>2],B=(b[0]=a[B],b[1]=a[B+1],f[0]),i=(c<<4)+d|0,f[0]=B,a[i>>2]=b[0],a[i+4>>2]=b[1],B=(b[0]=a[A],b[1]=a[A+1],f[0]),A=c+1|0,i=(c<<4)+d+8|0,f[0]=B,a[i>>2]=b[0],a[i+4>>2]=b[1],w=(b[0]=a[w],b[1]=a[w+1],f[0]),B=(A<<4)+d|0,f[0]=w,a[B>>2]=b[0],a[B+4>>2]=b[1],w=(b[0]=a[u],b[1]=a[u+1],f[0]),u=c+2|0,A=(A<<4)+d+8|0,f[0]=w,a[A>>2]=b[0],a[A+4>>2]=b[1],t=(b[0]=a[t],b[1]=a[t+1],f[0]),w=(u<<4)+d|0,f[0]=t,a[w>>2]=b[0],a[w+4>>2]=b[1],v=(b[0]=a[v],b[1]=a[v+1],f[0]),t=(u<<4)+d+8|0,f[0]=v,a[t>>2]=b[0],a[t+4>>2]=b[1],a[ge>>2]=c+3|0,h=C,1}if(2268==y){return h=C,J}if(2261==y){if(!T){return h=C,0}Fv(a[ge>>2]+4|0);d=a[Lg>>2];c=a[ge>>2];B=(b[0]=a[B],b[1]=a[B+1],f[0]);i=(c<<4)+d|0;f[0]=B;a[i>>2]=b[0];a[i+4>>2]=b[1];B=(b[0]=a[A],b[1]=a[A+1],f[0]);A=c+1|0;i=(c<<4)+d+8|0;f[0]=B;a[i>>2]=b[0];a[i+4>>2]=b[1];w=(b[0]=a[w],b[1]=a[w+1],f[0]);B=(A<<4)+d|0;f[0]=w;a[B>>2]=b[0];a[B+4>>2]=b[1];w=(b[0]=a[u],b[1]=a[u+1],f[0]);u=c+2|0;A=(A<<4)+d+8|0;f[0]=w;a[A>>2]=b[0];a[A+4>>2]=b[1];t=(b[0]=a[t],b[1]=a[t+1],f[0]);w=(u<<4)+d|0;f[0]=t;a[w>>2]=b[0];a[w+4>>2]=b[1];v=(b[0]=a[v],b[1]=a[v+1],f[0]);t=(u<<4)+d+8|0;f[0]=v;a[t>>2]=b[0];a[t+4>>2]=b[1];a[ge>>2]=c+3|0;h=C;return 1}}function Fv(b){var c=h;if((a[Iv>>2]|0)<(b|0)){var d=a[Lg>>2];0==(d|0)?(d=Gb(b<<4),a[Lg>>2]=d,0==(d|0)&&(Va(a[oa>>2],dd|0,(j=h,h+=12,a[j>>2]=Jv|0,a[j+4>>2]=519,a[j+8>>2]=Kv|0,j)),S())):(d=mc(d,b<<4),a[Lg>>2]=d,0==(d|0)&&(Va(a[oa>>2],dd|0,(j=h,h+=12,a[j>>2]=Jv|0,a[j+4>>2]=525,a[j+8>>2]=Lv|0,j)),S()));a[Iv>>2]=b}h=c}function Ll(c,d,i,g,e){var i=3*i,h=e+24|0;f[0]=3*d+g-(i+c);a[h>>2]=b[0];a[h+4>>2]=b[1];g=e+16|0;f[0]=3*c+i-6*d;a[g>>2]=b[0];a[g+4>>2]=b[1];g=e+8|0;f[0]=3*(d-c);a[g>>2]=b[0];a[g+4>>2]=b[1];f[0]=c;a[e>>2]=b[0];a[e+4>>2]=b[1]}function Ml(c,d,i){0<=c&1>=c&&(d=(a[i>>2]<<3)+d|0,f[0]=c,a[d>>2]=b[0],a[d+4>>2]=b[1],a[i>>2]=a[i>>2]+1|0)}function fL(c,d){if(1>=(d|0)){var i;return 0}for(var g=c|0,e=c+8|0,h=1,l=0,k=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),n=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);;){if(e=(h<<4)+c|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),k=e-k,g=(h<<4)+c+8|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),n=g-n,l+=$c(k*k+n*n),h=h+1|0,(h|0)==(d|0)){i=l;break}else{k=e,n=g}}return i}function gL(c,d,i){var g,e,j,l=h;h+=64;var k,n=l+32,m=n|0;j=n>>2;var p=n+16|0;e=p>>2;var s=l|0,v=i|0,t=i+16|0,u=i+32|0,w=i+48|0,A=i+8|0,B=i+24|0,C=i+40|0,y=i+56|0,T=n|0,D=n+8|0,p=p|0,n=n+24|0,F=0;a:for(;;){if((F|0)>=(d|0)){var M=1;k=2306;break}g=((F<<5)+c|0)>>2;a[j]=a[g];a[j+1]=a[g+1];a[j+2]=a[g+2];a[j+3]=a[g+3];g=((F<<5)+c+16|0)>>2;a[e]=a[g];a[e+1]=a[g+1];a[e+2]=a[g+2];a[e+3]=a[g+3];g=hL(i,m,s);var E=4==(g|0);b:do{if(!E){for(var O=(b[0]=a[T>>2],b[1]=a[T+4>>2],f[0]),G=(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0]),J=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),ia=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),H=0;;){if((H|0)>=(g|0)){break b}var I=(H<<3)+l|0,I=(b[0]=a[I>>2],b[1]=a[I+4>>2],f[0]);do{if(!(1e-6>I|.999999>2],b[1]=a[v+4>>2],f[0])+L*(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0])+S*(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0])+K*(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0]),K=Q*(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0])+L*(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0])+S*(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0])+K*(b[0]=a[y>>2],b[1]=a[y+4>>2],f[0]),S=N-O,L=K-G;if(.001<=S*S+L*L&&(N-=J,K-=ia,.001<=N*N+K*K)){M=0;k=2307;break a}}}while(0);H=H+1|0}}}while(0);F=F+1|0}if(2306==k||2307==k){return h=l,M}}function hL(c,d,i){var g,e,j,l,k,n=h;h+=84;var m=n+32;l=n+56;var p=n+80;k=p>>2;var s=d|0,v=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]),s=d+16|0;g=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0])-v;s=d+8|0;s=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]);d=d+24|0;j=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0])-s;a[k]=0;if(0!=g){var t=j/g,d=c+8|0;l=(c|0)>>2;var u=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0])-t*(b[0]=a[l],b[1]=a[l+1],f[0]);j=c+24|0;var d=(c+16|0)>>2,w=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0])-t*(b[0]=a[d],b[1]=a[d+1],f[0]);e=c+40|0;j=(c+32|0)>>2;var A=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])-t*(b[0]=a[j],b[1]=a[j+1],f[0]),B=c+56|0;e=(c+48|0)>>2;var C=n|0,c=C>>2;Ll(u,w,A,(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0])-t*(b[0]=a[e],b[1]=a[e+1],f[0]),C);s=(b[0]=a[c],b[1]=a[c+1],f[0])+(t*v-s);f[0]=s;a[c]=b[0];a[c+1]=b[1];s=Hp(C,m|0);if(4==(s|0)){return h=n,4}if(0>=(s|0)){return h=n,0}t=n+8|0;u=n+16|0;w=n+24|0;for(A=0;;){B=(A<<3)+m|0;B=(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0]);if(0<=B&1>=B){var y=(b[0]=a[l],b[1]=a[l+1],f[0]),T=(b[0]=a[d],b[1]=a[d+1],f[0]);Ll(y,T,(b[0]=a[j],b[1]=a[j+1],f[0]),(b[0]=a[e],b[1]=a[e+1],f[0]),C);y=(b[0]=a[c],b[1]=a[c+1],f[0]);T=(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0]);y=(y+B*(T+B*((b[0]=a[u>>2],b[1]=a[u+4>>2],f[0])+B*(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0])))-v)/g;0<=y&1>=y&&Ml(B,i,p)}A=A+1|0;if((A|0)==(s|0)){break}}i=a[k];h=n;return i}e=0==j;g=c|0;C=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);g=c+16|0;t=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);u=c+32|0;w=c+48|0;d=n|0;g=d>>2;Ll(C,t,(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0]),(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0]),d);v=(b[0]=a[g],b[1]=a[g+1],f[0])-v;f[0]=v;a[g]=b[0];a[g+1]=b[1];v=Hp(d,m|0);if(!e){if(4==(v|0)){return h=n,4}if(0>=(v|0)){return h=n,0}l=c+8|0;e=c+24|0;C=c+40|0;c=c+56|0;t=n+8|0;u=n+16|0;w=n+24|0;for(A=0;!(B=(A<<3)+m|0,B=(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0]),0<=B&1>=B&&(y=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),T=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),Ll(y,T,(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0]),(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),d),y=(b[0]=a[g],b[1]=a[g+1],f[0]),T=(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0]),y=(y+B*(T+B*((b[0]=a[u>>2],b[1]=a[u+4>>2],f[0])+B*(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0])))-s)/j,0<=y&1>=y&&Ml(B,i,p)),A=A+1|0,(A|0)==(v|0));){}i=a[k];h=n;return i}j=c+8|0;j=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]);e=c+24|0;e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);C=c+40|0;c=c+56|0;Ll(j,e,(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0]),(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),d);s=(b[0]=a[g],b[1]=a[g+1],f[0])-s;f[0]=s;a[g]=b[0];a[g+1]=b[1];s=Hp(d,l|0);c=4==(v|0);g=4==(s|0);a:do{if(c){if(g){return i=4,h=n,i}if(0<(s|0)){for(d=0;;){if(j=(d<<3)+l|0,Ml((b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]),i,p),d=d+1|0,(d|0)==(s|0)){break a}}}}else{d=0<(v|0);if(g){if(d){j=0}else{break}for(;;){if(e=(j<<3)+m|0,Ml((b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),i,p),j=j+1|0,(j|0)==(v|0)){break a}}}if(d){d=0<(s|0);for(j=0;;){b:do{if(d){e=(j<<3)+m|0;e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);for(C=0;;){if(t=(C<<3)+l|0,e==(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0])&&Ml(e,i,p),C=C+1|0,(C|0)==(s|0)){break b}}}}while(0);j=j+1|0;if((j|0)==(v|0)){break a}}}}}while(0);i=a[k];h=n;return i}function od(a,b,c,d,f,e){a=(b-d)*(f-c)-(e-d)*(a-c);return 0a?2:3}function iL(b,c){var d,g,f,e=a[kg>>2];g=e>>2;for(var l=0;;){var k=e+52*b|0,h=e+52*c|0;d=((l<<4)+e+52*b+4|0)>>2;var j=a[a[d]>>2],p=a[a[g+(13*c|0)+1]>>2];if((j|0)==(p|0)){if(f=a[a[g+(13*c|0)+2]>>2],(a[a[((l<<4)+8>>2)+g+(13*b|0)]>>2]|0)==(f|0)){f=2362}else{var m=f;f=2360}}else{m=a[a[g+(13*c|0)+2]>>2],f=2360}if(2360==f){if(f=0,(j|0)!=(m|0)){var v=j}else{(a[a[((l<<4)+8>>2)+g+(13*b|0)]>>2]|0)==(p|0)?f=2362:v=j}}2362==f&&(a[((l<<4)+16>>2)+g+(13*b|0)]=h,a[g+(13*c|0)+4]=k,v=a[a[d]>>2]);j=a[a[g+(13*c|0)+5]>>2];if((v|0)==(j|0)){if(f=a[a[g+(13*c|0)+6]>>2],(a[a[((l<<4)+8>>2)+g+(13*b|0)]>>2]|0)==(f|0)){f=2369}else{var t=f;f=2367}}else{t=a[a[g+(13*c|0)+6]>>2],f=2367}if(2367==f){if(f=0,(v|0)!=(t|0)){var u=v}else{(a[a[((l<<4)+8>>2)+g+(13*b|0)]>>2]|0)==(j|0)?f=2369:u=v}}2369==f&&(a[((l<<4)+16>>2)+g+(13*b|0)]=h,a[g+(13*c|0)+8]=k,u=a[a[d]>>2]);d=a[a[g+(13*c|0)+9]>>2];if((u|0)==(d|0)){if(f=a[a[g+(13*c|0)+10]>>2],(a[a[((l<<4)+8>>2)+g+(13*b|0)]>>2]|0)==(f|0)){f=2375}else{var w=f;f=2373}}else{w=a[a[g+(13*c|0)+10]>>2],f=2373}2373==f&&(f=0,(u|0)==(w|0)&&(a[a[((l<<4)+8>>2)+g+(13*b|0)]>>2]|0)==(d|0)&&(f=2375));2375==f&&(a[((l<<4)+16>>2)+g+(13*b|0)]=h,a[g+(13*c|0)+12]=k);l=l+1|0;if(3==(l|0)){break}}}function Gt(c,d,i){var g,e,Xa,l,k,n,m,p,s,v,t,u,w,A,B,C,y=i>>2,T=h;h+=16;var D;C=T>>2;B=(c+4|0)>>2;jL(a[B]);a[Qh>>2]=0;a[ij>>2]=0;var F=a[B]<<1,M=h;if((a[Ip>>2]|0)<(F|0)){var E=a[Mg>>2];if(0==(E|0)){var O=Gb(F<<2);a[Mg>>2]=O;0==(O|0)&&(Va(a[oa>>2],dd|0,(j=h,h+=12,a[j>>2]=Pd|0,a[j+4>>2]=564,a[j+8>>2]=kL|0,j)),S())}else{var G=mc(E,F<<2);a[Mg>>2]=G;0==(G|0)&&(Va(a[oa>>2],dd|0,(j=h,h+=12,a[j>>2]=Pd|0,a[j+4>>2]=571,a[j+8>>2]=lL|0,j)),S())}a[Ip>>2]=F}h=M;var J=(a[Ip>>2]|0)/2&-1;a[lg>>2]=J;a[Ng>>2]=J-1|0;var I=a[B],H=0<(I|0);A=(c|0)>>2;var K=a[A];a:do{if(H){for(var L=Infinity,N=-1,Q=0;;){var R=(Q<<4)+K|0,U=(b[0]=a[R>>2],b[1]=a[R+4>>2],f[0]),W=L>U,ca=W?Q:N,Y=Q+1|0;if((Y|0)<(I|0)){L=W?U:L,N=ca,Q=Y}else{var Z=ca;break a}}}else{Z=-1}}while(0);var V=(Z<<4)+K|0,ja=(b[0]=a[V>>2],b[1]=a[V+4>>2],f[0]),aa=(Z<<4)+K+8|0,da=(b[0]=a[aa>>2],b[1]=a[aa+4>>2],f[0]),ea=(0==(Z|0)?I:Z)-1|0,xa=(ea<<4)+K|0,$=(b[0]=a[xa>>2],b[1]=a[xa+4>>2],f[0]),la=(Z|0)==(I-1|0)?0:Z+1|0,ha=(la<<4)+K|0,ga=(b[0]=a[ha>>2],b[1]=a[ha+4>>2],f[0]),ba=(la<<4)+K+8|0,sa=(b[0]=a[ba>>2],b[1]=a[ba+4>>2],f[0]),fa=$==ja&ja==ga&sa>da;a:do{if(fa){D=2382}else{var tb=(ea<<4)+K+8|0;if(1!=(od($,(b[0]=a[tb>>2],b[1]=a[tb+4>>2],f[0]),ja,da,ga,sa)|0)){D=2382}else{if(H){for(var ya=0,ra=I,wa=K;;){if(0<(ya|0)){var Ab=(ya<<4)+wa|0,Fa=ya-1|0,Ga=(Fa<<4)+wa|0;if((b[0]=a[Ab>>2],b[1]=a[Ab+4>>2],f[0])!=(b[0]=a[Ga>>2],b[1]=a[Ga+4>>2],f[0])){D=2392}else{var ka=(ya<<4)+wa+8|0,ta=(Fa<<4)+wa+8|0;if((b[0]=a[ka>>2],b[1]=a[ka+4>>2],f[0])==(b[0]=a[ta>>2],b[1]=a[ta+4>>2],f[0])){var Ka=ra}else{D=2392}}}else{D=2392}if(2392==D){D=0;var za=a[Qh>>2],ma=a[jj>>2];a[ma+(za<<3)>>2]=(ya<<4)+wa|0;a[ma+(za<<3)+4>>2]=(za%a[B]<<3)+ma|0;a[a[Rh>>2]+(za<<2)>>2]=(za<<3)+ma|0;a[Qh>>2]=za+1|0;Ka=a[B]}var pa=ya+1|0;if((pa|0)>=(Ka|0)){break a}ya=pa;ra=Ka;wa=a[A]}}}}}while(0);a:do{if(2382==D&&H){for(var qa=I,Ha=I,Ra=K;;){var na=qa-1|0;if((na|0)<(Ha-1|0)){var Ba=(na<<4)+Ra|0,La=(qa<<4)+Ra|0;if((b[0]=a[Ba>>2],b[1]=a[Ba+4>>2],f[0])!=(b[0]=a[La>>2],b[1]=a[La+4>>2],f[0])){D=2388}else{var va=(na<<4)+Ra+8|0,Ya=(qa<<4)+Ra+8|0;if((b[0]=a[va>>2],b[1]=a[va+4>>2],f[0])!=(b[0]=a[Ya>>2],b[1]=a[Ya+4>>2],f[0])){D=2388}}}else{D=2388}if(2388==D){D=0;var Aa=a[Qh>>2],Za=a[jj>>2];a[Za+(Aa<<3)>>2]=(na<<4)+Ra|0;a[Za+(Aa<<3)+4>>2]=(Aa%a[B]<<3)+Za|0;a[a[Rh>>2]+(Aa<<2)>>2]=(Aa<<3)+Za|0;a[Qh>>2]=Aa+1|0}if(0>=(na|0)){break a}qa=na;Ha=a[B];Ra=a[A]}}}while(0);var Ea=a[Rh>>2],ab=a[Qh>>2],$a=Ea>>2,jb=h,Ca,Ia=ab,eb=ab;a:for(;;){var ub=eb-1|0;if(3<(Ia|0)){var Sa=0}else{Ca=2454;break}for(;;){if((Sa|0)>=(Ia|0)){Ca=2453;break a}var Qa=Sa+1|0,ua=(Sa+2|0)%(Ia|0);if(0==(mL(Sa,ua,Ea,Ia)|0)){Sa=Qa}else{break}}var Oa=(Qa|0)%(Ia|0);nL(a[(Sa<<2>>2)+$a],a[(Oa<<2>>2)+$a],a[(ua<<2>>2)+$a]);var Wa=Ia-1|0;if((Oa|0)<(Wa|0)){for(var pb=Oa;;){var ob=pb+1|0;a[(pb<<2>>2)+$a]=a[(ob<<2>>2)+$a];if((ob|0)==(ub|0)){Ia=Wa;eb=ub;continue a}else{pb=ob}}}else{Ia=Wa,eb=ub}}2454==Ca?(nL(a[$a],a[$a+1],a[$a+2]),h=jb):2453==Ca&&(Va(a[oa>>2],dd|0,(j=h,h+=12,a[j>>2]=Pd|0,a[j+4>>2]=315,a[j+8>>2]=oL|0,j)),h=jb);var bb=a[ij>>2],qb=0<(bb|0);a:do{if(qb){for(var Ma=0;;){var kb=Ma+1|0,Cb=(kb|0)<(bb|0);if(Cb){var vb=kb}else{break a}for(;;){iL(Ma,vb);var xb=vb+1|0;if((xb|0)<(bb|0)){vb=xb}else{break}}if(Cb){Ma=kb}else{break a}}}}while(0);for(var wb=d|0,nb=d+8|0,rb=0;(rb|0)<(bb|0);){if(0==(Mv(rb,(b[0]=a[wb>>2],b[1]=a[wb+4>>2],f[0]),(b[0]=a[nb>>2],b[1]=a[nb+4>>2],f[0]))|0)){rb=rb+1|0}else{break}}if((rb|0)==(bb|0)){Va(a[oa>>2],dd|0,(j=h,h+=12,a[j>>2]=Pd|0,a[j+4>>2]=183,a[j+8>>2]=pL|0,j));var lb=-1;h=T;return lb}var Ta=d+16|0;w=(Ta|0)>>2;u=(d+24|0)>>2;for(var cb=0;(cb|0)<(bb|0);){if(0==(Mv(cb,(b[0]=a[w],b[1]=a[w+1],f[0]),(b[0]=a[u],b[1]=a[u+1],f[0]))|0)){cb=cb+1|0}else{break}}if((cb|0)==(bb|0)){return Va(a[oa>>2],dd|0,(j=h,h+=12,a[j>>2]=Pd|0,a[j+4>>2]=191,a[j+8>>2]=qL|0,j)),lb=-1,h=T,lb}if(0==(rL(rb,cb)|0)){Va(a[oa>>2],dd|0,(j=h,h+=12,a[j>>2]=Pd|0,a[j+4>>2]=198,a[j+8>>2]=sL|0,j));Nv(2);a[y+1]=2;var fb=a[Sh>>2];t=fb>>2;v=d>>2;a[t]=a[v];a[t+1]=a[v+1];a[t+2]=a[v+2];a[t+3]=a[v+3];s=(fb+16|0)>>2;p=Ta>>2;a[s]=a[p];a[s+1]=a[p+1];a[s+2]=a[p+2];a[s+3]=a[p+3];a[y]=fb;lb=0;h=T;return lb}if((rb|0)==(cb|0)){Nv(2);a[y+1]=2;var Ua=a[Sh>>2];m=Ua>>2;n=d>>2;a[m]=a[n];a[m+1]=a[n+1];a[m+2]=a[n+2];a[m+3]=a[n+3];k=(Ua+16|0)>>2;l=Ta>>2;a[k]=a[l];a[k+1]=a[l+1];a[k+2]=a[l+2];a[k+3]=a[l+3];a[y]=Ua;lb=0;h=T;return lb}a[C]=d;a[C+1]=0;var sb=T+8|0;a[sb>>2]=Ta;a[C+3]=0;Nl(1,T|0);var Na=a[lg>>2];a[Ol>>2]=Na;var Fb=-1==(rb|0);a:do{if(Fb){var Db=0,Ob=sb}else{var Eb=a[kg>>2];Xa=Eb>>2;for(var Pa=rb,Bb=Na;;){a[Xa+(13*Pa|0)]=2;for(var Ja=0;3>(Ja|0);){var hb=a[((Ja<<4)+16>>2)+Xa+(13*Pa|0)];if(0!=(hb|0)&&1==(a[hb>>2]|0)){break}Ja=Ja+1|0}if(3==(Ja|0)){var ib=a[Mg>>2],db=a[a[ib+(a[lg>>2]<<2)>>2]>>2],yb=a[ib+(a[Ng>>2]<<2)>>2],mb=a[yb>>2],Ib=(b[0]=a[w],b[1]=a[w+1],f[0]),ic=(b[0]=a[u],b[1]=a[u+1],f[0]),Hb=db|0,Kb=(b[0]=a[Hb>>2],b[1]=a[Hb+4>>2],f[0]),Jb=db+8|0,Lb=(b[0]=a[Jb>>2],b[1]=a[Jb+4>>2],f[0]),Mb=mb|0,pc=mb+8|0,Nb=1==(od(Ib,ic,Kb,Lb,(b[0]=a[Mb>>2],b[1]=a[Mb+4>>2],f[0]),(b[0]=a[pc>>2],b[1]=a[pc+4>>2],f[0]))|0),Vb=Nb?sb:yb,Pb=Nb?yb:sb}else{var Wb=a[((Ja<<4)+4>>2)+Xa+(13*Pa|0)],Tb=a[Wb>>2],Yb=a[a[(((Ja+1)%3<<4)+8>>2)+Xa+(13*Pa|0)]>>2],Sb=a[((Ja<<4)+8>>2)+Xa+(13*Pa|0)],$b=a[Sb>>2],lc=Tb|0,ec=(b[0]=a[lc>>2],b[1]=a[lc+4>>2],f[0]),Zb=Tb+8|0,kc=(b[0]=a[Zb>>2],b[1]=a[Zb+4>>2],f[0]),Hc=Yb|0,qc=(b[0]=a[Hc>>2],b[1]=a[Hc+4>>2],f[0]),nc=Yb+8|0,gc=(b[0]=a[nc>>2],b[1]=a[nc+4>>2],f[0]),Qb=$b|0,tc=$b+8|0,Sc=1==(od(ec,kc,qc,gc,(b[0]=a[Qb>>2],b[1]=a[Qb+4>>2],f[0]),(b[0]=a[tc>>2],b[1]=a[tc+4>>2],f[0]))|0),Vb=Sc?Wb:Sb,Pb=Sc?Sb:Wb}var bc=(Pa|0)==(rb|0);b:do{if(bc){Nl(2,Pb);Nl(1,Vb);var Xb=Bb}else{var uc=a[Mg>>2];do{if((a[uc+(a[lg>>2]<<2)>>2]|0)!=(Vb|0)&&(a[uc+(a[Ng>>2]<<2)>>2]|0)!=(Vb|0)){var rd=Ov(Vb);a[lg>>2]=rd;Nl(1,Vb);if((rd|0)<=(Bb|0)){Xb=Bb;break b}Xb=a[Ol>>2]=rd;break b}}while(0);var oc=Ov(Pb);a[Ng>>2]=oc;Nl(2,Pb);Xb=(oc|0)<(Bb|0)?a[Ol>>2]=oc:Bb}}while(0);for(var ac=0;;){if(3<=(ac|0)){Db=0;Ob=sb;break a}var dc=a[((ac<<4)+16>>2)+Xa+(13*Pa|0)];if(0!=(dc|0)&&1==(a[dc>>2]|0)){break}ac=ac+1|0}var rc=dc-Eb|0;if(-52==(rc|0)){Db=0;Ob=sb;break a}else{Pa=(rc|0)/52&-1,Bb=Xb}}}}while(0);for(;;){var jc=Db+1|0,Bc=a[Ob+4>>2];if(0==(Bc|0)){break}else{Db=jc,Ob=Bc}}Nv(jc);a[y+1]=jc;for(var fc=a[Sh>>2],fd=sb,Ac=Db;;){e=((Ac<<4)+fc|0)>>2;g=a[fd>>2]>>2;a[e]=a[g];a[e+1]=a[g+1];a[e+2]=a[g+2];a[e+3]=a[g+3];var yc=a[fd+4>>2];if(0==(yc|0)){break}else{fd=yc,Ac=Ac-1|0}}a[y]=fc;lb=0;h=T;return lb}function Mv(c,d,i){var g;g=a[kg>>2]>>2;var e=a[a[g+(13*c|0)+1]>>2],h=a[a[g+(13*c|0)+2]>>2],l=e|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),e=e+8|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),k=h|0,h=h+8|0,h=2!=(od(l,e,(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0]),d,i)|0)&1,k=a[a[g+(13*c|0)+5]>>2],l=a[a[g+(13*c|0)+6]>>2],e=k|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),k=k+8|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),n=l|0,l=l+8|0,h=(2!=(od(e,k,(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),d,i)|0)&1)+h|0,l=a[a[g+(13*c|0)+9]>>2],c=a[a[g+(13*c|0)+10]>>2];g=l|0;g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);l=l+8|0;l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);e=c|0;c=c+8|0;d=(2!=(od(g,l,(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),d,i)|0)&1)+h|0;return(3==(d|0)|0==(d|0))&1}function jL(b){var c=h;if((a[Pv>>2]|0)<(b|0)){var d=a[jj>>2];0==(d|0)?(d=Gb(b<<3),a[jj>>2]=d,0==(d|0)&&(Va(a[oa>>2],dd|0,(j=h,h+=12,a[j>>2]=Pd|0,a[j+4>>2]=514,a[j+8>>2]=tL|0,j)),S()),d=Gb(b<<2),a[Rh>>2]=d,0==(d|0)&&(Va(a[oa>>2],dd|0,(j=h,h+=12,a[j>>2]=Pd|0,a[j+4>>2]=518,a[j+8>>2]=uL|0,j)),S())):(d=mc(d,b<<3),a[jj>>2]=d,0==(d|0)&&(Va(a[oa>>2],dd|0,(j=h,h+=12,a[j>>2]=Pd|0,a[j+4>>2]=524,a[j+8>>2]=vL|0,j)),S()),d=mc(a[Rh>>2],b<<2),a[Rh>>2]=d,0==(d|0)&&(Va(a[oa>>2],dd|0,(j=h,h+=12,a[j>>2]=Pd|0,a[j+4>>2]=530,a[j+8>>2]=wL|0,j)),S()));a[Pv>>2]=b}h=c}function Nl(b,c){var d,g=a[Ng>>2],f=a[lg>>2],e=-1<(g-f|0);d=a[Mg>>2]>>2;1==(b|0)?(e&&(a[c+4>>2]=a[(f<<2>>2)+d]),g=f-1|0,a[lg>>2]=g):(e&&(a[c+4>>2]=a[(g<<2>>2)+d]),g=g+1|0,a[Ng>>2]=g);a[(g<<2>>2)+d]=c}function rL(b,c){var d,g,f=a[kg>>2];d=(f+52*b|0)>>2;if(0!=(a[d]|0)){var e;return 0}a[d]=1;if((b|0)==(c|0)){return 1}for(var l=0;3>(l|0);){var k=a[((l<<4)+f+16>>2)+(13*b|0)];if(0!=(k|0)&&0!=(rL((k-f|0)/52&-1,c)|0)){e=1;g=2515;break}l=l+1|0}return 2515==g?e:a[d]=0}function Ov(c){var d,i,g=a[Ol>>2];d=a[Mg>>2]>>2;for(var c=c|0,e=a[lg>>2];(e|0)<(g|0);){var h=e+1|0,l=a[a[(h<<2>>2)+d]>>2],k=a[a[(e<<2>>2)+d]>>2],n=a[c>>2],j=l|0,j=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]),l=l+8|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),p=k|0,p=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),k=k+8|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),m=n|0,n=n+8|0;if(1==(od(j,l,p,k,(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0]),(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]))|0)){var v=e;i=2525;break}else{e=h}}if(2525==i){return v}for(e=a[Ng>>2];;){if((e|0)<=(g|0)){v=g;i=2526;break}h=e-1|0;l=a[a[(h<<2>>2)+d]>>2];k=a[a[(e<<2>>2)+d]>>2];n=a[c>>2];j=l|0;j=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]);l=l+8|0;l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);p=k|0;p=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]);k=k+8|0;k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);m=n|0;n=n+8|0;if(2==(od(j,l,p,k,(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0]),(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]))|0)){v=e;i=2524;break}else{e=h}}if(2524==i||2526==i){return v}}function mL(c,d,i,g){var i=i>>2,e,h=a[a[((c-1+g)%g<<2>>2)+i]>>2],l=a[a[(c<<2>>2)+i]>>2],k=a[a[((c+1)%g<<2>>2)+i]>>2],n=h|0,j=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),h=h+8|0,p=(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0]),h=l|0,h=(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0]),l=l+8|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),n=k|0,m=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),k=k+8|0,v=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),t=1==(od(j,p,h,l,m,v)|0),n=a[a[(d<<2>>2)+i]>>2],k=n|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),n=n+8|0,n=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]);if(0==((t?(1==(od(h,l,k,n,j,p)|0)?1==(od(k,n,h,l,m,v)|0):0)&1:2==(od(h,l,k,n,m,v)|0)&1)|0)){var u;return 0}for(p=0;;){if((p|0)>=(g|0)){u=1;e=2537;break}j=p+1|0;m=(j|0)%(g|0);if((p|0)==(c|0)|(m|0)==(c|0)|(p|0)==(d|0)|(m|0)==(d|0)){p=j}else{var p=a[a[(p<<2>>2)+i]>>2],w=a[a[(m<<2>>2)+i]>>2],A=p|0,B=p+8|0;a:{var p=h,m=l,v=k,t=n,A=(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0]),B=(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0]),C=od(p,m,v,t,A,B);if(3!=(C|0)){var y=w|0,y=(b[0]=a[y>>2],b[1]=a[y+4>>2],f[0]),D=w+8|0,F=(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0]),D=od(p,m,v,t,y,F);if(3!=(D|0)){var E=od(A,B,y,F,p,m);if(3!=(E|0)&&(y=od(A,B,y,F,v,t),3!=(y|0))){p=(1==(C|0)^1==(D|0)?1==(E|0)^1==(y|0):0)&1;break a}}}0!=(Jp(p,m,v,t,A,B)|0)?p=1:(C=w|0,C=(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0]),w=w+8|0,w=(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0]),p=0!=(Jp(p,m,v,t,C,w)|0)?1:0==(Jp(A,B,C,w,p,m)|0)?0!=(Jp(A,B,C,w,v,t)|0)&1:1)}if(0==(p|0)){p=j}else{u=0;e=2538;break}}}if(2538==e||2537==e){return u}}function nL(b,c,d){var g;g=a[ij>>2];var f=a[Kp>>2];if((g|0)<(f|0)){f=g}else{g=f+20|0;f=h;if((a[Kp>>2]|0)<(g|0)){var e=a[kg>>2];0==(e|0)?(e=Gb(52*g|0),a[kg>>2]=e,0==(e|0)&&(Va(a[oa>>2],dd|0,(j=h,h+=12,a[j>>2]=Pd|0,a[j+4>>2]=543,a[j+8>>2]=xL|0,j)),S())):(e=mc(e,52*g|0),a[kg>>2]=e,0==(e|0)&&(Va(a[oa>>2],dd|0,(j=h,h+=12,a[j>>2]=Pd|0,a[j+4>>2]=549,a[j+8>>2]=yL|0,j)),S()));a[Kp>>2]=g}h=f;f=a[ij>>2]}a[ij>>2]=f+1|0;e=a[kg>>2];g=e>>2;e=e+52*f|0;a[e>>2]=0;a[g+(13*f|0)+1]=b;a[g+(13*f|0)+2]=c;a[g+(13*f|0)+4]=0;a[g+(13*f|0)+5]=c;a[g+(13*f|0)+6]=d;a[g+(13*f|0)+8]=0;a[g+(13*f|0)+9]=d;a[g+(13*f|0)+10]=b;a[g+(13*f|0)+12]=0;a[g+(13*f|0)+3]=e;a[g+(13*f|0)+7]=e;a[g+(13*f|0)+11]=e}function Jp(a,b,c,d,f,e){var l=c-a,k=d-b,h=f-a,j=e-b;return 3!=(od(a,b,c,d,f,e)|0)?0:(0>h*l+j*k?0:h*h+j*j<=l*l+k*k)&1}function Hp(c,d){var i=c+24|0,g=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);if(1e-7>g&-1e-7>2],b[1]=a[e+4>>2],f[0]);1e-7>i&-1e-7>2],b[1]=a[e+4>>2],f[0]),i=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),1e-7>e&-1e-7i&-1e-7>2]=b[0],a[d+4>>2]=b[1],e=1)):(e=c+8|0,g=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])/(2*i),i=e*e-g/i,0>i?e=0:0==i?(f[0]=-e,a[d>>2]=b[0],a[d+4>>2]=b[1],e=1):(i=$c(i)-e,f[0]=i,a[d>>2]=b[0],a[d+4>>2]=b[1],g=d+8|0,f[0]=-2*e-i,a[g>>2]=b[0],a[g+4>>2]=b[1],e=2));return e}var h=c+8|0,i=c+16|0,l=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),h=(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0]),i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])/(3*g),h=h/g,k=i*i,l=2*i*k-i*h+l/g,g=h/3-k,h=l*l,g=h+4*g*g*g;if(0>g){h=.5*$c(h-g);g=Cf($c(-g),-l);h=2*(0>h?-1*kj(-h,.3333333333333333):kj(h,.3333333333333333));l=h*se(g/3);f[0]=l;a[d>>2]=b[0];a[d+4>>2]=b[1];var k=h*se((g+6.283185307179586)/3),n=d+8|0;f[0]=k;a[n>>2]=b[0];a[n+4>>2]=b[1];g=h*se((g-3.141592653589793-3.141592653589793)/3);h=d+16|0;f[0]=g;a[h>>2]=b[0];a[h+4>>2]=b[1];g=3}else{h=.5*($c(g)-l),l=-l-h,h=0>h?-1*kj(-h,.3333333333333333):kj(h,.3333333333333333),l=0>l?-1*kj(-l,.3333333333333333):kj(l,.3333333333333333),l=h+l,f[0]=l,a[d>>2]=b[0],a[d+4>>2]=b[1],0>2]=b[0],a[h+4>>2]=b[1],h=d+8|0,f[0]=g,a[h>>2]=b[0],a[h+4>>2]=b[1],g=3)}h=l;for(l=0;;){k=(l<<3)+d|0;f[0]=h-i;a[k>>2]=b[0];a[k+4>>2]=b[1];l=l+1|0;if((l|0)>=(g|0)){e=g;break}h=(l<<3)+d|0;h=(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0])}return e}function zL(c,d,i,g){var i=i>>2,e,h=a[((c-1+g)%g<<2>>2)+i],l=a[(c<<2>>2)+i],k=a[((c+1)%g<<2>>2)+i],n=h|0,j=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),h=h+8|0,p=(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0]),h=l|0,h=(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0]),l=l+8|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),n=k|0,m=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),k=k+8|0,v=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),t=1==(mg(j,p,h,l,m,v)|0),n=a[(d<<2>>2)+i],k=n|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),n=n+8|0,n=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]);if(0==((t?(1==(mg(h,l,k,n,j,p)|0)?1==(mg(k,n,h,l,m,v)|0):0)&1:2==(mg(h,l,k,n,m,v)|0)&1)|0)){var u;return 0}for(p=0;;){if((p|0)>=(g|0)){u=1;e=2614;break}j=p+1|0;m=(j|0)%(g|0);if((p|0)==(c|0)|(m|0)==(c|0)|(p|0)==(d|0)|(m|0)==(d|0)){p=j}else{var p=a[(p<<2>>2)+i],w=a[(m<<2>>2)+i],A=p|0,B=p+8|0;a:{var p=h,m=l,v=k,t=n,A=(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0]),B=(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0]),C=mg(p,m,v,t,A,B);if(3!=(C|0)){var y=w|0,y=(b[0]=a[y>>2],b[1]=a[y+4>>2],f[0]),D=w+8|0,F=(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0]),D=mg(p,m,v,t,y,F);if(3!=(D|0)){var E=mg(A,B,y,F,p,m);if(3!=(E|0)&&(y=mg(A,B,y,F,v,t),3!=(y|0))){p=(1==(C|0)^1==(D|0)?1==(E|0)^1==(y|0):0)&1;break a}}}0!=(Lp(p,m,v,t,A,B)|0)?p=1:(C=w|0,C=(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0]),w=w+8|0,w=(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0]),p=0!=(Lp(p,m,v,t,C,w)|0)?1:0==(Lp(A,B,C,w,p,m)|0)?0!=(Lp(A,B,C,w,v,t)|0)&1:1)}if(0==(p|0)){p=j}else{u=0;e=2616;break}}}if(2616==e||2614==e){return u}}function Nv(b){var c=h;if((a[Qv>>2]|0)<(b|0)){var d=a[Sh>>2];0==(d|0)?(d=Gb(b<<4),a[Sh>>2]=d,0==(d|0)&&(Va(a[oa>>2],dd|0,(j=h,h+=12,a[j>>2]=Pd|0,a[j+4>>2]=584,a[j+8>>2]=Kv|0,j)),S())):(d=mc(d,b<<4),a[Sh>>2]=d,0==(d|0)&&(Va(a[oa>>2],dd|0,(j=h,h+=12,a[j>>2]=Pd|0,a[j+4>>2]=590,a[j+8>>2]=Lv|0,j)),S()));a[Qv>>2]=b}h=c}function AL(b,c,d,g){var f,e,l,k;k=b>>2;var n=h;h+=48;var j;if(3<(c|0)){for(e=0;;){if((e|0)>=(c|0)){j=2645;break}l=e+1|0;f=(e+2|0)%(c|0);if(0==(zL(e,f,b,c)|0)){e=l}else{break}}2645==j&&S();j=(l|0)%(c|0);l=n>>2;e=a[(e<<2>>2)+k]>>2;a[l]=a[e];a[l+1]=a[e+1];a[l+2]=a[e+2];a[l+3]=a[e+3];l=(n+16|0)>>2;e=a[(j<<2>>2)+k]>>2;a[l]=a[e];a[l+1]=a[e+1];a[l+2]=a[e+2];a[l+3]=a[e+3];e=(n+32|0)>>2;f=a[(f<<2>>2)+k]>>2;a[e]=a[f];a[e+1]=a[f+1];a[e+2]=a[f+2];a[e+3]=a[f+3];J[d](g,n|0);f=0<(c|0);a:do{if(f){for(l=e=0;;){if((e|0)!=(j|0)&&(a[(l<<2>>2)+k]=a[(e<<2>>2)+k],l=l+1|0),e=e+1|0,(e|0)==(c|0)){break a}}}}while(0);AL(b,c-1|0,d,g)}else{c=n>>2,b=a[k]>>2,a[c]=a[b],a[c+1]=a[b+1],a[c+2]=a[b+2],a[c+3]=a[b+3],c=(n+16|0)>>2,b=a[k+1]>>2,a[c]=a[b],a[c+1]=a[b+1],a[c+2]=a[b+2],a[c+3]=a[b+3],b=(n+32|0)>>2,k=a[k+2]>>2,a[b]=a[k],a[b+1]=a[k+1],a[b+2]=a[k+2],a[b+3]=a[k+3],J[d](g,n|0)}h=n}function mg(a,b,c,d,f,e){a=(b-d)*(f-c)-(e-d)*(a-c);return 0a?1:3}function Lp(a,b,c,d,f,e){var l=c-a,k=d-b,h=f-a,j=e-b;return 3!=(mg(a,b,c,d,f,e)|0)?0:(0>h*l+j*k?0:h*h+j*j<=l*l+k*k)&1}function Ht(b,c,d){var g,f,e,l,k,h;g=3*c|0;var j=g-2|0,p=a[Rv>>2];(j|0)>(a[Sv>>2]|0)&&(p=0==(p|0)?Gb(j<<4):mc(p,j<<4),a[Rv>>2]=p,a[Sv>>2]=j);h=p>>2;k=b>>2;a[h]=a[k];a[h+1]=a[k+1];a[h+2]=a[k+2];a[h+3]=a[k+3];ck(p+16|0,b);c=c-1|0;if(1<(c|0)){h=1;for(k=2;!(l=((k<<4)+p|0)>>2,f=(h<<4)+b|0,e=f>>2,a[l]=a[e],a[l+1]=a[e+1],a[l+2]=a[e+2],a[l+3]=a[e+3],l=(k+1<<4)+p|0,e=l>>2,ck(l,f),f=((k+2<<4)+p|0)>>2,a[f]=a[e],a[f+1]=a[e+1],a[f+2]=a[e+2],a[f+3]=a[e+3],h=h+1|0,(h|0)==(c|0));){k=k+3|0}k=c;c=g-4|0}else{k=1,c=2}g=((c<<4)+p|0)>>2;k=(k<<4)+b|0;b=k>>2;a[g]=a[b];a[g+1]=a[b+1];a[g+2]=a[b+2];a[g+3]=a[b+3];ck((c+1<<4)+p|0,k);a[d+4>>2]=j;a[d>>2]=p}function ro(b){var c=a[b>>2]<<28>>28;return 3==(c|0)?a[a[b+16>>2]+12>>2]:2==(c|0)?a[a[a[a[b+16>>2]+20>>2]+16>>2]+8>>2]:1==(c|0)?a[a[a[b+20>>2]+16>>2]+4>>2]:0}function mb(b,c){if(-1>=(c|0)){var d;return 0}return d=a[a[b+4>>2]+(c<<2)>>2]}function fc(b,c,d){b>>=2;0==(d|0)?(c=0==(c|0)?1024:c,a[b+3]=1,d=Gb(c),a[b]=d):(a[b]=d,a[b+3]=0);a[b+2]=d+c|0;a[b+1]=d;m[d]=0}function na(b,c){var d,g=b+8|0;d=(b|0)>>2;var f=a[d],e=a[g>>2]-f|0,l=e<<1,e=e+c|0,l=e>>>0>l>>>0?e:l,e=b+4|0,k=a[e>>2]-f|0,h=b+12|0;0==(a[h>>2]|0)?(f=Gb(l),tf(f,a[d],k),a[h>>2]=1):f=mc(f,l);a[d]=f;a[e>>2]=f+k|0;a[g>>2]=f+l|0}function db(b,c){var d=Ba(c),g;g=(b+4|0)>>2;var f=a[g];(f+d|0)>>>0>a[b+8>>2]>>>0&&(na(b,d),f=a[g]);tf(f,c,d);a[g]=a[g]+d|0}function uc(b){0!=(a[b+12>>2]|0)&&G(a[b>>2])}function BL(b,c,d){var g,f=Yc(1,16);g=f>>2;a[g]=Dc(c);a[g+1]=Dc(d);m[f+12|0]=1;c=b+4|0;d=rc(a[c>>2]);a[g+2]=d;b=(b+8|0)>>2;g=a[b];g=0==(g|0)?Gb((d<<2)+8|0):mc(g,(d<<2)+8|0);a[b]=g;a[g+(d<<2)>>2]=f;a[a[b]+(d+1<<2)>>2]=0;c=a[c>>2];J[a[c>>2]](c,f,1);return f}function ui(b,c,d){var g,f=$(b,c);if(0==(f|0)){var e=1,l=BL(ro(b),c,d);g=2857}else{var c=f+4|0,k=a[c>>2];if(0==(ka(k,d)|0)){var h=f}else{dc(k),a[c>>2]=Dc(d),e=0,l=f,g=2857}}if(2857==g){if(0==(l|0)){h=0}else{d=a[b>>2]<<28>>28;if(2==(d|0)){CL(a[a[b+12>>2]+20>>2],l,e)}else{if(1==(d|0)){DL(a[b+20>>2],l,e)}else{if(3==(d|0)){if(d=l,g=b+36|0,f=a[g>>2],0==(f|0)){ng(b|0,d,e)}else{if(b=ra(a[f+20>>2]),0!=(b|0)){for(;!(ng(Yd(b)|0,d,e),b=ba(a[a[g>>2]+20>>2],b),0==(b|0));){}}}}}}h=l}}return h}function $(b,c){var d=a[ro(b)+4>>2];return J[a[d>>2]](d,c,512)}function DL(b,c,d){var g=ra(b),f=0==(g|0);a:do{if(!f){for(var e=g;;){if(ng(e|0,c,d),e=ba(b,e),0==(e|0)){break a}}}}while(0);g=b+36|0;f=a[g>>2];if(0==(f|0)){if(g=a[b+40>>2],0!=(g|0)){for(;!(ng(a[g>>2]|0,c,d),g=a[g+8>>2],0==(g|0));){}}}else{if(b=ra(a[f+20>>2]),0!=(b|0)){for(;;){f=a[Yd(b)+40>>2];e=0==(f|0);a:do{if(!e){for(var l=f;;){if(ng(a[l>>2]|0,c,d),l=a[l+8>>2],0==(l|0)){break a}}}}while(0);b=ba(a[a[g>>2]+20>>2],b);if(0==(b|0)){break}}}}}function CL(b,c,d){var g=ra(b),f=0==(g|0);a:do{if(!f){for(var e=g;;){var l=Ib(b,e),h=0==(l|0);b:do{if(!h){for(var n=l;;){if(ng(n|0,c,d),n=yb(b,n),0==(n|0)){break b}}}}while(0);e=ba(b,e);if(0==(e|0)){break a}}}}while(0);g=b+36|0;f=a[g>>2];if(0==(f|0)){if(b=a[b+40>>2],0!=(b|0)){for(;!(ng(a[b+4>>2]|0,c,d),b=a[b+8>>2],0==(b|0));){}}}else{if(b=ra(a[f+20>>2]),0!=(b|0)){for(;;){f=a[Yd(b)+40>>2];e=0==(f|0);a:do{if(!e){for(l=f;;){if(ng(a[l+4>>2]|0,c,d),l=a[l+8>>2],0==(l|0)){break a}}}}while(0);b=ba(a[a[g>>2]+20>>2],b);if(0==(b|0)){break}}}}}function Mp(b,c,d){b=0==(b|0)?a[qa+12>>2]:b;return(b|0)!=(a[b+32>>2]|0)?0:c=ui(b|0,c,d)}function xi(b,c,d){b=0==(b|0)?a[qa+12>>2]:b;return(b|0)!=(a[b+32>>2]|0)?0:c=ui(a[a[b+40>>2]>>2]|0,c,d)}function Th(b,c,d){b=0==(b|0)?a[qa+12>>2]:b;return(b|0)!=(a[b+32>>2]|0)?0:c=ui(a[a[b+40>>2]+4>>2]|0,c,d)}function Tv(b){Kc(a[b+4>>2]);var c=b+8|0,d=a[c>>2];if(0!=(d|0)){var g=a[d>>2],f=0==(g|0);a:do{if(f){var e=d}else{for(var l=0,h=g;;){l=l+1|0;dc(a[h>>2]);dc(a[h+4>>2]);G(h);var n=a[c>>2],h=a[n+(l<<2)>>2];if(0==(h|0)){e=n;break a}}}}while(0);G(e)}G(b)}function Uv(b){var c,d=Yc(1,12);c=d>>2;a[c]=b;a[c+1]=Nc(EL,a[Xc>>2]);a[c+2]=0;return d}function Vv(b,c){var d=rc(a[c+4>>2]);if(0<(d|0)){for(var g=c+8|0,f=0;;){var e=a[a[g>>2]+(f<<2)>>2],l=BL(b,a[e>>2],a[e+4>>2]);m[l+12|0]=m[e+12|0];m[l+13|0]=m[e+13|0];f=f+1|0;if((f|0)==(d|0)){break}}}}function V(a,b){return mb(a,FL(a,b))}function FL(b,c){var d=$(b,c);return 0==(d|0)?-1:d=a[d+8>>2]}function yd(a,b,c){qc(a,FL(a,b),c)}function qc(b,c,d){if(-1>=(c|0)){return-1}var g=(c<<2)+a[b+4>>2]|0;dc(a[g>>2]);a[g>>2]=Dc(d);b=a[b+8>>2]+((c|0)/8&-1)|0;m[b]=(m[b]&255|1<<(c&7))&255;return 0}function $J(b,c,d,g){var f=$(b,c);0==(f|0)?(g=0==(g|0)?Y|0:g,f=a[b>>2]<<28>>28,c=2==(f|0)?Th(a[a[b+12>>2]+20>>2],c,g):3==(f|0)?Mp(a[b+32>>2],c,g):1==(f|0)?xi(a[b+20>>2],c,g):0):c=f;qc(b,a[c+8>>2],d)}function po(b,c){var d,g=a[b>>2]<<28>>28,f=2==(g|0);if((g|0)==(a[c>>2]<<28>>28|0)){var g=0,e=a[ro(b)+8>>2];a:for(;;){for(g=0==(g|0);;){if(!g){break a}var l=e+4|0,e=a[e>>2];d=e>>2;if(0==(e|0)){break a}if(!f){break}if(0==(a[d+2]|0)){e=l}else{break}}g=$(c,a[d]);if(0==(g|0)){break}g=qc(c,a[g+8>>2],mb(b,a[d+2]));e=l}}}function ng(b,c,d){var g=a[c+8>>2];if(0==(d|0)){0==(m[a[b+8>>2]+((g|0)/8&-1)|0]<<24>>24&1<<(g&7)|0)&&(b=b+4|0,dc(a[a[b>>2]+(g<<2)>>2]),c=Dc(a[c+4>>2]),a[a[b>>2]+(g<<2)>>2]=c)}else{var d=(b+4|0)>>2,f=a[d],f=0==(f|0)?Gb((g<<2)+4|0):mc(f,(g<<2)+4|0);a[d]=f;c=Dc(a[c+4>>2]);a[a[d]+(g<<2)>>2]=c;0==(g&7|0)&&(b=b+8|0,c=a[b>>2],g=(g|0)/8&-1,d=g+1|0,c=0==(c|0)?Gb(d):mc(c,d),a[b>>2]=c,m[c+g|0]=0)}}function Kr(b,c,d){var g=b+24|0,f=lj(a[g>>2],c,d,0);return 0!=(f|0)?f:0!=(a[b>>2]&16|0)?0:b=lj(a[g>>2],d,c,0)}function lj(b,c,d,g){var f,e=h;h+=32;f=e>>2;var l=e+28|0;a[l>>2]=g;a[f+4]=c;a[f+3]=d;g=0!=(g|0);a[f+1]=g?l:0;f=a[b>>2];l=e|0;if(g){return c=J[f](b,l,4),h=e,c}b=J[f](b,l,8);if(0==(b|0)||(a[b+16>>2]|0)==(c|0)&&(a[b+12>>2]|0)==(d|0)){return h=e,b}h=e;return 0}function GL(b,c){var d=a[c>>2];16==(d&240|0)&&(a[c>>2]=d&-241|32);Pl(b,c)}function Pl(b,c){var d=b+24|0,g=a[d>>2],f=c|0;if(0==(J[a[g>>2]](g,f,4)|0)){g=c+16|0;mj(b,a[g>>2]);var e=c+12|0;mj(b,a[e>>2]);var l=b+28|0,h=a[l>>2];J[a[h>>2]](h,f,1);d=a[d>>2];J[a[d>>2]](d,f,1);d=a[l>>2];f=J[a[d>>2]](d,f,16);0!=(f|0)&&(a[f+16>>2]|0)==(a[g>>2]|0)&&(a[f+12>>2]|0)==(a[e>>2]|0)&&(f=a[c>>2],268435456>f<<24>>>0&&(a[c>>2]=f&-241|16));if(0==(a[b>>2]&64|0)&&(g=a[b+36>>2],f=a[g+20>>2],g=Og(f,g),0!=(g|0))){for(;!(Pl(Yd(a[g+16>>2]),c),g=Ql(f,g),0==(g|0));){}}}}function Ok(a,b){if(0==(a|0)|0==(b|0)){var c=0}else{c=Ib(a,b),c=0!=(c|0)?c:Og(a,b)}return c}function Ib(b,c){var d,g=h;h+=28;d=g>>2;if(0==(b|0)|0==(c|0)){return h=g,0}a[d+4]=c;a[d+3]=0;a[d+1]=0;d=a[b+28>>2];d=J[a[d>>2]](d,g|0,8);if(0==(d|0)){return h=g,d}h=g;return(a[d+16>>2]|0)==(c|0)?d:0}function Og(b,c){var d,g=h;h+=28;d=g>>2;if(0==(b|0)|0==(c|0)){return h=g,0}a[d+3]=c;a[d+4]=0;a[d+1]=0;d=a[b+24>>2];d=J[a[d>>2]](d,g|0,8);if(0==(d|0)){return h=g,d}h=g;return(a[d+12>>2]|0)==(c|0)?d:0}function Pk(b,c,d){var g;if(0==(b|0)|0==(c|0)|0==(d|0)){var f;return 0}var e=(a[c+16>>2]|0)==(d|0);a:do{if(e){var l=a[b+28>>2],l=J[a[l>>2]](l,c|0,8);if(0!=(l|0)&&(a[l+16>>2]|0)==(d|0)){return f=l}for(var l=b+24|0,h=Og(b,d);;){if(0==(h|0)){var n=0;break a}var j=a[h+12>>2];if(!((j|0)==(a[h+16>>2]|0)&(j|0)==(d|0))){n=h;break a}j=a[l>>2];h=J[a[j>>2]](j,h|0,8)}}else{if((a[c+12>>2]|0)==(d|0)){n=a[b+24>>2],n=J[a[n>>2]](n,c|0,8)}else{return f=0}}}while(0);b=b+24|0;for(c=n;;){if(0==(c|0)){f=0;g=101;break}var e=a[c+12>>2],p=(e|0)==(d|0);if(!((e|0)==(a[c+16>>2]|0)&p)){g=99;break}e=a[b>>2];c=J[a[e>>2]](e,c|0,8)}if(99==g){return p?c:0}if(101==g){return f}}function yb(b,c){var d=a[b+28>>2],d=J[a[d>>2]](d,c|0,8);return 0==(d|0)?d:(a[d+16>>2]|0)==(a[c+16>>2]|0)?d:0}function Ql(b,c){var d=a[b+24>>2],d=J[a[d>>2]](d,c|0,8);return 0==(d|0)?d:(a[d+12>>2]|0)==(a[c+12>>2]|0)?d:0}function Wv(b,c,d,g){var f,e=Yc(1,a[qa+8>>2]);f=e>>2;a[e>>2]=a[e>>2]&-16|2;a[f+4]=c;a[f+3]=d;d=(b+16|0)>>2;b=a[d]+20|0;c=a[b>>2];a[b>>2]=c+1|0;a[f+5]=c;c=rc(a[a[a[d]+8>>2]+4>>2]);if(0==(c|0)){return a[f+1]=0,a[f+2]=0,e}b=(e+4|0)>>2;a[b]=Yc(c,4);a[f+2]=Yc((c+7|0)/8&-1,1);if(0>=(c|0)){return e}f=g+4|0;if(0==(g|0)){for(g=0;!(f=Dc(a[a[a[a[a[d]+8>>2]+8>>2]+(g<<2)>>2]+4>>2]),a[a[b]+(g<<2)>>2]=f,g=g+1|0,(g|0)==(c|0));){}}else{for(g=0;!(d=Dc(a[a[f>>2]+(g<<2)>>2]),a[a[b]+(g<<2)>>2]=d,g=g+1|0,(g|0)==(c|0));){}}return e}function uh(b,c,d){var g,f,e=h;h+=128;g=(b+40|0)>>2;f=a[a[g]+4>>2];var l=a[a[f+4>>2]>>2];a[f+12>>2]=d;a[a[a[g]+4>>2]+16>>2]=c;f=b>>2;do{if(0==(a[f]&32|0)){if(0==m[l]<<24>>24){var k=e|0,n=a[Xv>>2];a[Xv>>2]=n+1|0;Ma(k,dv|0,(j=h,h+=4,a[j>>2]=n,j));a[a[a[a[g]+4>>2]+4>>2]>>2]=k;k=0}else{k=b+24|0;n=lj(a[k>>2],c,d,l);if(0==(n|0)){if(0!=(a[f]&16|0)){k=32;break}f=lj(a[k>>2],d,c,l);if(0==(f|0)){k=32;break}else{c=f}}else{c=n}GL(b,c);b=c;g=a[g];g=g+4|0;g>>=2;g=a[g];g=g+4|0;g>>=2;g=a[g];g>>=2;a[g]=l;h=e;return b}}else{k=b+24|0;n=lj(a[k>>2],c,d,0);if(0==(n|0)){if(0!=(a[f]&16|0)){k=0;break}f=lj(a[k>>2],d,c,0);if(0==(f|0)){k=0;break}else{c=f}}else{c=n}Pl(b,c);b=c;g=a[g];g=g+4|0;g>>=2;g=a[g];g=g+4|0;g>>=2;g=a[g];g>>=2;a[g]=l;h=e;return b}}while(0);c=Wv(b,c,d,a[a[g]+4>>2]);Pl(b,c);b=a[g];d=a[b>>2];a[a[b+4>>2]+16>>2]=d;a[a[a[g]+4>>2]+12>>2]=d;a[c>>2]=a[c>>2]&-241|k;g=a[g];g=(g+4|0)>>2;g=a[g];g=(g+4|0)>>2;g=a[g];a[g>>2]=l;h=e;return c}function HL(b){var c=b|0;a[b>>2]|=15;var d=rc(a[a[a[a[a[b+16>>2]+20>>2]+16>>2]+8>>2]+4>>2]),g=b+4|0,f=a[g>>2];if(0<(d|0)){for(var e=0;;){if(dc(a[f+(e<<2)>>2]),e=e+1|0,f=a[g>>2],(e|0)==(d|0)){l=f;break}}G(l)}else{var l,d=f;G(d)}b=a[(b+8|0)>>2];G(b);G(c)}function Np(b,c){var d,g=h;d=(b+24|0)>>2;var f=a[d],e=c|0;if(0==(J[a[f>>2]](f,e,4)|0)){la(1,IL|0,(j=h,h+=4,a[j>>2]=c,j)),Gn(a[d],70)}else{f=0==(a[b>>2]&64|0);a:do{if(f){var l=a[b+36>>2],k=a[l+20>>2],l=Ib(k,l);if(0!=(l|0)){for(;;){var n=Yd(a[l+12>>2]),m=a[n+24>>2];0!=(J[a[m>>2]](m,e,4)|0)&&Np(n,c);l=yb(k,l);if(0==(l|0)){break a}}}}}while(0);d=a[d];J[a[d>>2]](d,e,2);d=a[b+28>>2];J[a[d>>2]](d,e,2);(a[b+32>>2]|0)==(b|0)&&HL(c)}h=g}function Yd(b){return 0!=(a[a[b+20>>2]+36>>2]|0)?0:b=a[a[b+4>>2]>>2]}function JL(b,c){var d=0==(b|0)?0:a[b>>2],g=a[c+4>>2],g=0==(g|0)?0:a[g>>2];return 0==(d|0)?(0!=(g|0))<<31>>31:0==(g|0)?1:d=ka(d,g)}function Yv(b,c){var d,g=Yc(1,12);d=g>>2;var f=b+40|0;a[d+2]=a[f>>2];if(0==(c|0)){var e=0,l=0}else{e=a[c+4>>2],l=a[c>>2]}l=Zv(b,KL|0,l);a[d]=l;a[d+1]=Wv(b,l,l,e);a[f>>2]=g}function LL(b){var c;c=b+40|0;b=a[c>>2];if(0!=(b|0)){a[c>>2]=a[b+8>>2];var d=b|0,g=a[d>>2];c=(b+4|0)>>2;a[a[c]+12>>2]=g;a[a[c]+16>>2]=g;HL(a[c]);$v(a[d>>2]);G(b)}}function xn(b,c){var d=Op(b,0,c),g=Op(b,0,7);if(0==(d|0)|0==(g|0)){return 0}xi(g,ML|0,0);g=cl(g,b);a[d+36>>2]=g;a[a[g+4>>2]>>2]=d|0;return d}function Op(b,c,d){var g,f=h;if(0==m[qa+31|0]<<24>>24){return la(1,NL|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),h=f,0}var e=Yc(1,a[qa>>2]);g=e>>2;a[e>>2]=d<<4&240|a[e>>2]&-256|3;a[g+5]=Nc(Uh,a[Xc>>2]);a[g+6]=Nc(aw,a[Xc>>2]);a[g+7]=Nc(nj,a[Xc>>2]);d=0==(c|0);a:do{if(d){var l=OL();a[g+4]=l;a[g+8]=e;l=rc(a[a[l+12>>2]+4>>2]);if(0==(l|0)){a[g+1]=0,a[g+2]=0}else{var k=e+4|0;a[k>>2]=Yc(l,4);a[g+2]=Yc((l+7|0)/8&-1,1);if(0<(l|0)){for(var n=0;;){var z=Dc(a[a[a[qa+12>>2]+4>>2]+(n<<2)>>2]);a[a[k>>2]+(n<<2)>>2]=z;n=n+1|0;if((n|0)==(l|0)){break a}}}}}else{if(l=c+16|0,a[g+4]=a[l>>2],a[g+8]=a[c+32>>2],l=rc(a[a[a[l>>2]+12>>2]+4>>2]),0==(l|0)){a[g+1]=0,a[g+2]=0}else{if(k=e+4|0,a[k>>2]=Yc(l,4),a[g+2]=Yc((l+7|0)/8&-1,1),0<(l|0)){n=c+4|0;for(z=0;;){var p=Dc(a[a[n>>2]+(z<<2)>>2]);a[a[k>>2]+(z<<2)>>2]=p;z=z+1|0;if((z|0)==(l|0)){break a}}}}}}while(0);a[g+9]=0;a[g+3]=Dc(b);a[g+10]=0;d?Yv(e,a[e+40>>2]):Yv(e,a[c+40>>2]);h=f;return e}function oo(b,c){var d=a[a[b+36>>2]+20>>2],g=Mi(d,c);if(0==(g|0)){g=Op(c,b,a[b>>2]<<24>>28);if(0==(g|0)){return 0}d=cl(d,c);a[g+36>>2]=d;a[a[d+4>>2]>>2]=g|0;d=g}else{d=Yd(g)}PL(b,d);return d}function PL(b,c){var d=a[b+36>>2],g=a[c+36>>2];0==(d|0)|0==(g|0)||0==(QL(g,d)|0)&&uh(a[d+20>>2],d,g)}function RL(b,c){var d=a[b+36>>2];0==(d|0)?d=0:(d=Mi(a[d+20>>2],c),d=0==(d|0)?0:Yd(d));return d}function QL(b,c){var d=Nc(Uh,a[Xc>>2]),g=SL(d,b,c);Kc(d);return g}function Sf(b){var c,d=b>>2;if(0!=(b|0)){c=b>>2;var g=a[c];if(3==(g&15|0)){g=0==(g&64|0);a:do{if(g){for(var f=b+36|0,e=a[f>>2],l=a[e+20>>2],h=l,n=e;;){e=0;n=Ib(h,n);b:for(;;){for(;;){if(0==(n|0)){break b}var j=yb(h,n),p=n+12|0;if(0==(Ql(h,Og(h,a[p>>2]))|0)){break}else{n=j}}Sf(Yd(a[p>>2]));e=1;n=j}if(0==(e|0)){var m=l;break a}n=a[f>>2]}}else{m=0}}while(0);j=b+40|0;p=0==(a[j>>2]|0);a:do{if(!p){for(;;){if(LL(b),0==(a[j>>2]|0)){break a}}}}while(0);a:do{if(g&&(j=rc(a[a[a[d+4]+12>>2]+4>>2]),0<(j|0))){p=b+4|0;for(f=0;;){if(dc(a[a[p>>2]+(f<<2)>>2]),f=f+1|0,(f|0)==(j|0)){break a}}}}while(0);j=a[d+1];0!=(j|0)&&G(j);j=a[d+2];0!=(j|0)&&G(j);do{if((a[d+8]|0)==(b|0)){j=b;p=ra(j);f=0==(p|0);a:do{if(!f){for(l=p;;){if(h=ba(j,l),Pp(j,l),0==(h|0)){break a}else{l=h}}}}while(0);g&&Sf(a[a[d+9]+20>>2]);j=cc;j=(b+16|0)>>2;Tv(a[a[j]+12>>2]);Tv(a[a[j]+4>>2]);Tv(a[a[j]+8>>2]);Kc(a[a[j]>>2]);G(a[j])}else{g&&bl(m,a[d+9]|0)}}while(0);Kc(a[d+5]);Kc(a[d+6]);Kc(a[d+7]);dc(a[d+3]);a[c]|=15;G(b|0)}}}function bl(b,c){var d=a[c>>2]<<28>>28;1==(d|0)?Pp(b,c):2==(d|0)?Np(b,c):3==(d|0)&&Sf(c)}function Ed(b,c){var d=a[c>>2]<<28>>28;1==(d|0)?d=0!=(TL(b,a[c+16>>2])|0)&1:3==(d|0)?d=QL(a[b+36>>2],a[c+36>>2]):2==(d|0)?(d=a[b+24>>2],d=0!=(J[a[d>>2]](d,c,4)|0)&1):d=0;return d}function gt(b,c){var d=a[c>>2]<<28>>28;1==(d|0)?mj(b,c):2==(d|0)?GL(b,c):3==(d|0)&&PL(b,c)}function Vh(b){return rc(a[b+20>>2])}function UL(b,c){0!=(b|0)&&(a[qa+36>>2]=b);0!=(c|0)&&(a[qa+40>>2]=c)}function SL(b,c,d){var g,f;if((c|0)==(d|0)){var e;return 1}g=(c+20|0)>>2;if(0!=(Kr(a[a[g]+32>>2],c,d)|0)){return 1}var l=b|0;J[a[l>>2]](b,c|0,1);for(c=Ib(a[g],c);;){if(0==(c|0)){e=0;f=354;break}var h=c+12|0;if(0==(J[a[l>>2]](b,a[h>>2]|0,4)|0)&&0!=(SL(b,a[h>>2],d)|0)){e=1;f=353;break}c=yb(a[g],c)}if(353==f||354==f){return e}}function OL(){var b=Yc(1,24);a[b>>2]=Nc(bw,a[Xc>>2]);var c=b+12|0;a[c>>2]=Uv(Yf|0);var d=b+4|0;a[d>>2]=Uv(jh|0);var g=b+8|0;a[g>>2]=Uv(Dg|0);var f=a[qa+12>>2];if(0==(f|0)){return b}Vv(a[c>>2],a[a[f+16>>2]+12>>2]);Vv(a[d>>2],a[a[a[qa+12>>2]+16>>2]+4>>2]);Vv(a[g>>2],a[a[a[qa+12>>2]+16>>2]+8>>2]);return b}function VL(a,b){if(0==(bg(a)|0)){var c=Rl(a,b);return c}m[b]=60;var c=b+1|0,d=m[a],f=0==d<<24>>24;a:do{if(f){var e=b,l=c}else{for(var h=a,n=c,j=d;;){h=h+1|0;m[n]=j;var j=n+1|0,p=m[h];if(0==p<<24>>24){e=n;l=j;break a}else{n=j,j=p}}}}while(0);m[l]=62;m[e+2|0]=0;return b}function Rl(a,b){var c;if(0==(a|0)||0==m[a]<<24>>24){return c=oj|0}m[b]=34;var d=m[a],f=0==(pj(d)|0),e=1,l=0,h=a+1|0,n=d,d=b+1|0,j=0;a:for(;;){var p=l,l=n,s=d;b:for(;;){do{if(0==l<<24>>24){break a}else{if(34==l<<24>>24){m[s]=92;var d=1,v=s+1|0}else{v=95!=l<<24>>24&0==(oC(l&255)|0)&-1>24,d=v|f?v?1:p:0==(pj(l)|0)?1:p,v=s}}}while(0);var t=v+1|0;m[v]=l;var u=h+1|0,w=m[h],A=j+1|0,h=w&255,j=0!=w<<24>>24;do{if(!(e|j^1)){do{if(0==(pj(l)|0)&&0==(ti(l<<24>>24)|0)&&92!=m[v]<<24>>24){break b}}while(0);if(0==(pj(w)|0)&&0==(ti(h)|0)){break b}}}while(0);if(j){if(0!=(A&127|0)){p=d,h=u,l=w,s=t}else{l=m[v];0==(pj(l)|0)?0!=(ti(l<<24>>24)|0)?c=392:92==m[v]<<24>>24&&(c=392):c=392;if(392==c){c=0;if(0!=(pj(w)|0)){e=0;l=d;h=u;n=w;d=t;j=A;continue a}if(0!=(ti(h)|0)){e=0;l=d;h=u;n=w;d=t;j=A;continue a}}m[t]=92;m[v+2|0]=10;p=1;h=u;l=w;s=v+3|0}}else{p=d,h=u,l=0,s=t}j=A}m[t]=92;m[v+2|0]=10;l=e=1;h=u;n=w;d=v+3|0;j=A}m[s]=34;m[s+1|0]=0;return 0!=(p|0)?b:c=-1<(cw(a)|0)?b:a}function hb(b,c){J[a[qa+36>>2]](b,1,Ba(b),c)}function Ge(b,c){var d=h;h+=4;m[d]=b&255;J[a[qa+36>>2]](d,1,1,c);h=d}function xd(a){return VL(a,Sl(a))}function Sl(b){var b=(Ba(b)<<1)+2|0,b=1024>>0?b:1024,c=a[dw>>2];if((b|0)<=(a[ew>>2]|0)){return c}c=0==(c|0)?Gb(b):mc(c,b);a[dw>>2]=c;a[ew>>2]=b;return c}function fw(b,c,d,g,f){var e,l=h;h+=4;a[l>>2]=0;var k=d+20|0,n=a[a[a[k>>2]+16>>2]+4>>2];do{if(0!=(g|0)){var j=n+4|0;if(0<(rc(a[j>>2])|0)){for(var p=n+8|0,s=d|0,v=d+12|0,t=b+40|0,u=0,w=0;;){var A=a[a[p>>2]+(u<<2)>>2];if(0!=m[A+12|0]<<24>>24){e=(A|0)>>2;var B=V(s,a[e]),A=(a[k>>2]|0)==(b|0)?a[A+4>>2]:V(a[a[t>>2]>>2]|0,a[e]);0!=(ka(A,B)|0)&&(0==(w|0)&&(Pg(c,f),hb(xd(a[v>>2]),c),w=1),WL(c,l,a[e],B))}e=w;u=u+1|0;if((u|0)<(rc(a[j>>2])|0)){w=e}else{break}}if(0!=(e|0)){hb(0<(a[l>>2]|0)?Tl|0:Wh|0,c);h=l;return}}}}while(0);0==(Ib(b,d)|0)&&0==(Og(b,d)|0)&&(Pg(c,f),hb(xd(a[d+12>>2]),c),hb(Wh|0,c));h=l}function Pg(a,b){if(0!=(b|0)){for(var c=b;!(c=c-1|0,Ge(9,a),0==(c|0));){}}}function WL(b,c,d,g){var f=a[c>>2];a[c>>2]=f+1|0;hb(0<(f|0)?gw|0:Qp|0,b);hb(xd(d),b);Ge(61,b);hb(xd(g),b)}function hw(b,c,d,g){var f,e=h;h+=4;a[e>>2]=0;var l=a[d+16>>2],k=a[a[a[l+20>>2]+16>>2]+8>>2],n=a[d+4>>2];if(0==(n|0)){var j=Y|0,n=Y|0}else{j=a[n+8>>2],n=a[n+4>>2]}XL(c,a[l+12>>2],n);hb(0!=(a[b>>2]&16|0)?ns|0:os|0,c);XL(c,a[a[d+12>>2]+12>>2],j);if(0==(g|0)){b=Wh|0}else{if(g=k+4|0,0<(rc(a[g>>2])|0)){for(var k=k+8|0,l=d|0,j=b+32|0,n=b+40|0,p=0;;){var s=a[a[k>>2]+(p<<2)>>2];if(0!=m[s+12|0]<<24>>24&&!(0==(p|0)&&32!=(a[d>>2]&240|0))){f=(s|0)>>2;var v=V(l,a[f]),s=(a[j>>2]|0)==(b|0)?a[s+4>>2]:V(a[a[n>>2]+4>>2]|0,a[f]);0!=(ka(s,v)|0)&&WL(c,e,a[f],v)}f=p+1|0;if((f|0)<(rc(a[g>>2])|0)){p=f}else{break}}b=0<(a[e>>2]|0)?Tl|0:Wh|0}else{b=Wh|0}}hb(b,c);h=e}function XL(a,b,c){hb(xd(b),a);0!=(c|0)&&0!=m[c]<<24>>24&&(0!=(bg(c)|0)?(Ge(58,a),hb(VL(c,Sl(c)),a)):(b=Jc(c,58),0==(b|0)?(Ge(58,a),hb(Rl(c,Sl(c)),a)):(m[b]=0,Ge(58,a),hb(Rl(c,Sl(c)),a),Ge(58,a),c=b+1|0,hb(Rl(c,Sl(c)),a),m[b]=58)))}function iw(b,c){var d;0==(a[qa+36>>2]|0)&&(a[qa+36>>2]=182);0==(a[qa+40>>2]|0)&&(a[qa+40>>2]=60);hb(0!=(a[b>>2]&32|0)?YL|0:Y|0,c);hb(0!=(a[b>>2]&16|0)?wv|0:Yf|0,c);d=b+12|0;0!=(td(a[d>>2],jw|0,10)|0)&&(Ge(32,c),hb(xd(a[d>>2]),c));hb(kw|0,c);d=(b+16|0)>>2;lw(a[a[d]+12>>2],c);lw(a[a[d]+4>>2],c);lw(a[a[d]+8>>2],c);var g;d=Yc(1,20);g=d>>2;var f=Nc(Uh,a[Xc>>2]);a[g]=f;var e=a[b+20>>2];a[Ul>>2]=f;Gn(e,230);f=Nc(Rp,a[Xc>>2]);a[g+1]=f;e=a[b+28>>2];a[Ul>>2]=f;Gn(e,230);a[g+4]=Nc(Uh,a[Xc>>2]);a[g+3]=Nc(nj,a[Xc>>2]);f=Nc(Uh,a[Xc>>2]);a[g+2]=f;g=a[a[a[b+36>>2]+20>>2]+20>>2];a[Ul>>2]=f;Gn(g,230);mw(b,c,0,0,d);hb(Sp|0,c);g=d>>2;Kc(a[g]);Kc(a[g+4]);Kc(a[g+1]);Kc(a[g+3]);Kc(a[g+2]);G(d);return J[a[qa+40>>2]](c)}function lw(b,c){var d=b+4|0;if(0<(rc(a[d>>2])|0)){for(var g=b+8|0,f=b|0,e=0,l=0;;){var h=a[a[g>>2]+(e<<2)>>2],j=h+4|0,z=a[j>>2];0==(z|0)?h=l:0==m[z]<<24>>24?h=l:(0==(l|0)?(Ge(9,c),hb(a[f>>2],c),hb(Qp|0,c)):hb(gw|0,c),hb(a[h>>2],c),Ge(61,c),hb(xd(a[j>>2]),c),h=l+1|0);e=e+1|0;if((e|0)<(rc(a[d>>2])|0)){l=h}else{break}}0<(h|0)&&hb(Tl|0,c)}}function pj(a){return 10>((a<<24>>24)-48|0)>>>0?1:46==a<<24>>24||45==a<<24>>24||43==a<<24>>24?1:0}function mw(b,c,d,g,f){var e,l,h,j,m=g+1|0;if(0==(g|0)){Vl(c,m,b|0,0,a[a[b+16>>2]+12>>2]);var p=b+36|0}else{Pg(c,g);h=f+8|0;j=a[h>>2];l=b+36|0;if(0==(J[a[j>>2]](j,a[l>>2]|0,4)|0)){hb(nw|0,c);hb(xd(a[b+12>>2]),c);hb(Wh|0,c);return}j=b+12|0;0==(td(a[j>>2],jw|0,10)|0)?hb(ZL|0,c):(hb(nw|0,c),hb(xd(a[j>>2]),c),hb(kw|0,c));j=(b+16|0)>>2;Vl(c,m,b|0,d|0,a[a[j]+12>>2]);(a[b+32>>2]|0)==(d|0)?e=d=0:(e=a[d+40>>2],d=a[e+4>>2],e=a[e>>2]);p=b+40|0;Vl(c,m,a[a[p>>2]>>2]|0,e|0,a[a[j]+4>>2]);Vl(c,m,a[a[p>>2]+4>>2]|0,d|0,a[a[j]+8>>2]);h=a[h>>2];J[a[h>>2]](h,a[l>>2]|0,2);p=l}h=(f+16|0)>>2;j=a[h];l=(f+12|0)>>2;d=a[l];e=a[a[p>>2]+20>>2];a[h]=Nc(bw,a[Xc>>2]);a[l]=Nc(nj,a[Xc>>2]);var p=Ib(e,a[p>>2]),s=0==(p|0);a:do{if(!s){for(var v=p;;){if(mw(Yd(a[v+12>>2]),c,b,m,f),v=yb(e,v),0==(v|0)){break a}}}}while(0);e=ra(b);p=0==(e|0);a:do{if(!p){for(var s=f|0,v=j|0,t=e;;){var u=a[s>>2],w=t|0;0==(J[a[u>>2]](u,w,4)|0)?(u=a[h],0==(J[a[u>>2]](u,w,4)|0)&&fw(b,c,t,0,m)):(fw(b,c,t,1,m),u=a[s>>2],J[a[u>>2]](u,w,2));J[a[v>>2]](j,w,1);t=ba(b,t);if(0==(t|0)){break a}}}}while(0);e=(b+28|0)>>2;pk(a[e],Rp,0);p=a[e];p=J[a[p>>2]](p,0,128);s=0==(p|0);a:do{if(!s){v=f+4|0;t=d|0;for(w=p;;){var u=w,A=a[v>>2];0==(J[a[A>>2]](A,w,4)|0)?(A=a[l],0==(J[a[A>>2]](A,w,4)|0)&&(Pg(c,m),hw(b,c,u,0))):(Pg(c,m),hw(b,c,u,1),u=a[v>>2],J[a[u>>2]](u,w,2));J[a[t>>2]](d,w,1);u=a[e];w=J[a[u>>2]](u,w,8);if(0==(w|0)){break a}}}}while(0);pk(a[e],nj,0);Kc(a[h]);a[h]=j;Kc(a[l]);a[l]=d;0<(g|0)&&(Pg(c,g),hb(Sp|0,c))}function Vl(b,c,d,g,f){var e,l=f+4|0;if(0<(rc(a[l>>2])|0)){for(var h=f+8|0,j=0==(g|0),f=f|0,z=c+1|0,p=0,s=0;;){var v=a[a[h>>2]+(p<<2)>>2];e=v>>2;if(0==m[v+12|0]<<24>>24){e=s}else{var v=a[e+2],t=mb(d,v);0==(ka(t,j?a[e+1]:mb(g,v))|0)?e=s:(0==(s|0)?(Pg(b,c),hb(a[f>>2],b),hb(Qp|0,b)):(hb($L|0,b),Pg(b,z)),hb(xd(a[e]),b),Ge(61,b),hb(xd(t),b),e=s+1|0)}p=p+1|0;if((p|0)<(rc(a[l>>2])|0)){s=e}else{break}}0<(e|0)&&hb(Tl|0,b)}}function vC(b){a[ow>>2]=b;var c=a[qa+32>>2];var d=0!=(c|0)?c:a[qa+32>>2]=386;a[Gc>>2]=0;c=a[qa+24>>2];0==(c|0)&&(a[Xh>>2]=1024,c=Yc(1024,1),a[qa+24>>2]=c,c=Yc(a[Xh>>2],1),a[Wl>>2]=c,d=a[qa+32>>2],c=a[qa+24>>2]);J[d](c,0,b);D[qa+28>>1]=0}function aM(){var b,c=h;h+=1040;var d,g=c+16;if(0!=m[qa+30|0]<<24>>24){m[qa+30|0]=0;var f;h=c;return-1}for(var e=a[Gc>>2];;){if(0==(e|0)){d=576}else{if(0==m[e]<<24>>24){d=576}else{var l=e}}if(576==d&&(d=0,l=pw(),a[Gc>>2]=l,0==(l|0))){d=577;break}if(1==(a[Qd>>2]|0)){if(0!=(td(l,bM|0,3)|0)){var k=l}else{k=l+3|0,a[Gc>>2]=k}}else{k=l}k=cM(k);a[Gc>>2]=k;var n=m[k];if(0==n<<24>>24){e=k}else{break}}if(577==d){if(!m[Xl]){return h=c,-1}f=m[qw]&255;la(0,dM|0,(j=h,h+=4,a[j>>2]=f,j));h=c;return-1}d=a[Wl>>2];if(34==n<<24>>24){f=eM(k,d),a[Gc>>2]=f,f=Dc(d),a[Yh>>2]=f,f=265}else{if(60==n<<24>>24){fc(c,1024,g|0);l=a[Gc>>2];m[rw]=a[Qd>>2]&255;d=h;g=(c+4|0)>>2;k=c+8|0;l=l+1|0;n=1;b:for(;;){for(;;){e=m[l];if(62==e<<24>>24){f=779;break}else{if(60==e<<24>>24){f=780;break}else{if(0!=e<<24>>24){var z=n,p=e;break}}}l=pw();if(0==(l|0)){break b}}if(779==f){if(f=0,z=n-1|0,0==(z|0)){b=l;f=787;break}else{p=62}}else{780==f&&(f=0,z=n+1|0,p=60)}n=a[g];n>>>0>2]>>>0||(na(c,1),n=a[g]);a[g]=n+1|0;m[n]=p;l=l+1|0;n=z}787!=f&&(f=m[rw]&255,b=a[qj>>2],b=0!=(b|0)?b:Yl|0,la(0,fM|0,(j=h,h+=8,a[j>>2]=f,a[j+4>>2]=b,j)),b=0);h=d;f=b;a[Gc>>2]=0==(f|0)?0:f+1|0;b=(c+4|0)>>2;f=a[b];f>>>0>2]>>>0||(na(c,1),f=a[b]);m[f]=0;f=a[c>>2];a[b]=f;0==(a[Ne>>2]|0)&&gM();0==(f|0)?f=0:(b=a[Ne>>2],b=J[a[b>>2]](b,f-12|0,4),0==(b|0)?(b=Gb(Ba(f)+16|0),a[b+8>>2]=m[Tp]?-2147483647:1,Rf(b+12|0,f),f=a[Ne>>2],J[a[f>>2]](f,b,1)):(f=b+8|0,a[f>>2]=a[f>>2]+1|0),f=b,f=f+12|0);a[Yh>>2]=f;uc(c)}else{f=a[qa+20>>2];if(0!=(f|0)&&(b=Ba(f),0==(td(k,f,b)|0))){return a[Gc>>2]=k+b|0,h=c,263}f=hM(k,d);if(0!=(f|0)){return a[Gc>>2]=f,f=Dc(d),a[Yh>>2]=f,h=c,264}f=m[a[Gc>>2]];b=a[Gc>>2];if(!(0==((33<=(f&255)&&47>=(f&255)||58<=(f&255)&&64>=(f&255)||91<=(f&255)&&96>=(f&255)||123<=(f&255)&&126>=(f&255))|0)|95==f<<24>>24)){return a[Gc>>2]=b+1|0,f=m[b]<<24>>24,h=c,f}if(0==(b|0)){f=0}else{f=b;for(b=d;;){z=m[f];if(0==(oC(m[f]&255)|0)&&!(95==z<<24>>24|0>z<<24>>24)){break}m[b]=z;f=f+1|0;b=b+1|0}m[b]=0}a[Gc>>2]=f;f=cw(d);if(-1!=(f|0)){return h=c,f}f=Dc(d);a[Yh>>2]=f}f=264}h=c;return f}function cw(b){D[rj>>1]=0;var c=m[b];do{if(0==c<<24>>24){var d=0}else{for(var d=b,g=c,f=0;;){var g=0>g<<24>>24?127:g,e=-1>16;a:do{if(e){var l=g&255;do{if(0==((65<=l&&90>=l)|0)){if(0!=((97<=l&&122>=l)|0)){var h=g}else{h=D[rj>>1]=-1;break a}}else{h=vf(l)&255}}while(0);l=D[rj>>1]<<16>>16;h&=255;if(0==(a[iM+(h-97<<2)>>2]&a[Up+(l<<3)+4>>2]|0)){h=D[rj>>1]=-1}else{for(l=D[Up+(l<<3)+2>>1];;){var j=l<<16>>16;if((D[sw+(j<<2)>>1]<<16>>16|0)==(h|0)){break}else{l=l+1&65535}}h=D[sw+(j<<2)+2>>1];D[rj>>1]=h}}else{h=f}}while(0);d=d+1|0;f=m[d];if(0==f<<24>>24){break}else{g=f,f=h}}if(0>h<<16>>16){return b=-1}d=h}}while(0);return b=D[Up+(d<<16>>16<<3)>>1]<<16>>16}function pw(){var b=h;h+=8;for(var c,d=b+4|0,g=0;;){var f=a[Xh>>2];(g+128|0)<(f|0)||(f=f+1024|0,a[Xh>>2]=f,f=mc(a[qa+24>>2],f),a[qa+24>>2]=f,f=mc(a[Wl>>2],a[Xh>>2]),a[Wl>>2]=f,f=a[Xh>>2]);f=J[a[qa+32>>2]](a[qa+24>>2]+g+1|0,f+(g^-1)|0,a[ow>>2]);if(0==(f|0)){var e=g;break}var l=Ba(f);do{if(10==m[f+(l-1)|0]<<24>>24){if(35==m[f]<<24>>24&0==(g|0)){var k=f+1|0,k=0==(td(k,jM|0,4)|0)?f+5|0:k,n=Cd(k,kM|0,(j=h,h+=12,a[j>>2]=Qd,a[j+4>>2]=d,a[j+8>>2]=b,j)),z=a[Qd>>2];do{if(1>(n|0)){a[Qd>>2]=z+1|0}else{if(a[Qd>>2]=z-1|0,1<(n|0)){for(var p=k+a[b>>2]|0,s=p;;){var v=m[s];if(34==v<<24>>24||0==v<<24>>24){break}s=s+1|0}(s|0)!=(p|0)&&(m[s]=0,v=p,p=s-p|0,s=a[tw>>2],(s|0)<(p|0)?(s=0==(s|0)?Gb(p+1|0):mc(a[Zl>>2],p+1|0),a[Zl>>2]=s,a[tw>>2]=p,p=s):p=a[Zl>>2],Rf(p,v),a[qj>>2]=a[Zl>>2])}}}while(0);m[f]=0;k=1;n=0}else{a[Qd>>2]=a[Qd>>2]+1|0;c=l-2|0;var t=f+c|0;92!=m[t]<<24>>24?t=l:(m[t]=0,t=c);c=642}}else{t=l,c=642}}while(0);642==c&&(c=0,k=t,n=t+g|0);if(10==m[f+(k-1)|0]<<24>>24){e=n;break}else{g=n}}if(0>=(e|0)){return h=b,0}d=a[qa+24>>2]+1|0;h=b;return d}function la(b,c){var d=h;h+=4;a[d>>2]=arguments[la.length];var g=uw(b,c,a[d>>2]);h=d;return g}function cM(b){var c;a:for(;;){var d=m[b];do{if(0!=d<<24>>24&&!(0==(ah(d&255)|0)&&0==((0<=(m[b]&255)&&31>=(m[b]&255)||127===(m[b]&255))|0))){b=b+1|0;continue a}}while(0);var g=m[Xl];b:do{if(g){for(var d=b,f=m[b];;){if(0==f<<24>>24){break b}else{var e=d}for(;;){var h=0==f<<24>>24,k=e+1|0;if(!(42!=f<<24>>24&(h^1))){break}e=k;f=m[k]}if(h){d=e}else{if(f=m[k],47==f<<24>>24){break}else{d=k}}}m[Xl]=0;d=e+2|0}else{d=b}}while(0);b=m[d];b:do{if(0==b<<24>>24){var j=d;c=676;break a}else{if(47==b<<24>>24){g=m[d+1|0];if(47==g<<24>>24){g=d,f=0}else{if(42==g<<24>>24){m[Xl]=1;m[qw]=a[Qd>>2]&255;var z=d+2|0;break}else{j=d;c=677;break a}}for(;;){var p=g+1|0;if(f){z=g;break b}g=p;f=0==m[p]<<24>>24}}else{if(0!=(ah(b&255)|0)){z=d}else{if(0==((0<=(m[d]&255)&&31>=(m[d]&255)||127===(m[d]&255))|0)){j=d;c=678;break a}else{z=d}}}}}while(0);if(0==m[z]<<24>>24){j=z;c=679;break}else{b=z}}if(679==c||677==c||678==c||676==c){return j}}function eM(b,c){var d=h,g=m[b],f=b+1|0,e=m[f],l=0==e<<24>>24,k=e<<24>>24!=g<<24>>24&(l^1);a:do{if(k){for(var n=c,z=b,p=f,s=e;;){if(92==s<<24>>24){var s=z+2|0,v=m[s];v<<24>>24==g<<24>>24?(p=s,s=n):92!=v<<24>>24?s=n:(m[n]=92,p=s,s=n+1|0)}else{s=n}n=s+1|0;m[s]=m[p];v=p+1|0;s=m[v];z=0==s<<24>>24;if(s<<24>>24!=g<<24>>24&(z^1)){z=p,p=v}else{var t=n,u=p,w=v,A=z;break a}}}else{t=c,u=b,w=f,A=l}}while(0);A?(g=a[qj>>2],g=0!=(g|0)?g:Yl|0,f=a[Qd>>2],la(0,lM|0,(j=h,h+=8,a[j>>2]=g,a[j+4>>2]=f,j))):w=u+2|0;m[t]=0;h=d;return w}function hM(b,c){var d=h,g,f=m[b];if(45==f<<24>>24){f=b+1|0;m[c]=45;var e=c+1|0,l=f,k=m[f]}else{e=c,l=b,k=f}46==k<<24>>24?(k=l+1|0,m[e]=46,f=e+1|0,l=k,e=1,k=m[k]):(f=e,e=0);var n=10>((k&255)-48|0)>>>0;a:do{if(n){for(var z=l,p=f,s=k;;){var z=z+1|0,v=p+1|0;m[p]=s;s=m[z];if(10>((s&255)-48|0)>>>0){p=v}else{var t=z,u=v,w=1,A=s;break a}}}else{t=l,u=f,w=0,A=k}}while(0);do{if(46==A<<24>>24&0==(e|0)){if(m[u]=46,k=u+1|0,l=t+1|0,f=m[l],10>((f&255)-48|0)>>>0){for(var B=k;;){m[B]=f;var C=B+1|0,y=l+1|0,f=m[y];if(10>((f&255)-48|0)>>>0){B=C,l=y}else{break}}m[C]=0;B=y}else{var D=k,F=l;g=703}}else{D=u,F=t,g=703}}while(0);if(703==g){m[D]=0;if(0==(w|0)){return h=d,0}B=F}g=m[B];if(0==g<<24>>24){return h=d,B}if(0==(ti(g&255)|0)){if(95==m[B]<<24>>24){g=B}else{return h=d,B}}else{g=B}for(;;){var E=g+1|0,M=m[E];if(0==M<<24>>24){break}if(95==M<<24>>24|0!=(ti(M&255)|0)){g=E}else{break}}m[E]=0;g=a[qj>>2];g=0!=(g|0)?g:Yl|0;t=a[Qd>>2];la(0,mM|0,(j=h,h+=20,a[j>>2]=g,a[j+4>>2]=t,a[j+8>>2]=b,a[j+12>>2]=c,a[j+16>>2]=B,j));m[E]=M;h=d;return B}function vw(b){var c=h,d=D[qa+28>>1];D[qa+28>>1]=d+1&65535;if(0==d<<16>>16){var d=a[qj>>2],d=0!=(d|0)?d:Yl|0,g=a[Qd>>2];la(1,nM|0,(j=h,h+=16,a[j>>2]=d,a[j+4>>2]=g,a[j+8>>2]=b,a[j+12>>2]=g,j));var b=h,f,d=a[qa+24>>2]+1|0;if(0!=(a[Gc>>2]|0)){la(3,oM|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j));for(g=a[Gc>>2];;){var e=g-1|0;if(e>>>0<=d>>>0){break}if(0==(ah(m[e]&255)|0)){g=e}else{f=732;break}}732==f&&(f=m[e],m[e]=0,la(3,d,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),m[e]=f);la(3,pM|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j));f=a[Gc>>2];d=m[f];m[f]=0;la(3,e,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j));m[a[Gc>>2]]=d;la(3,qM|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j));la(3,a[Gc>>2],(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j))}h=b}h=c}function uw(b,c,d){var g,f=h;h+=4;g=f>>2;a[g]=d;var e=3==(b|0),l=e?a[ww>>2]:2==(b|0)?1:b;a[ww>>2]=l;var k=a[xw>>2];a[xw>>2]=k>>>0>l>>>0?k:l;if(l>>>0>2]>>>0){b=a[Vp>>2];if(0==(b|0)&&(b=wn(),a[Vp>>2]=b,0==(b|0))){return h=f,1}e?e=b:(e=zQa(b),a[sM>>2]=e,e=a[Vp>>2]);tM(e,c,a[g])}else{if(0!=(a[$l>>2]|0)){return uM(b,c,d),h=f,0}e?g=d:(e=1==(b|0)?yw|0:zw|0,Va(a[oa>>2],vM|0,(j=h,h+=4,a[j>>2]=e,j)),g=a[g]);tM(a[oa>>2],c,g)}h=f;return 0}function wM(b){var c=h;h+=4;a[c>>2]=arguments[wM.length];uw(1,b,a[c>>2]);h=c}function uM(b,c,d){var g=h;h+=4;var f;a[g>>2]=d;if(0==(a[sj>>2]|0)&&(d=Gb(a[am>>2]),a[sj>>2]=d,0==(d|0))){Lc(Aw|0,35,1,a[oa>>2]);h=g;return}3!=(b|0)&&(J[a[$l>>2]](1==(b|0)?yw|0:zw|0),J[a[$l>>2]](xM|0));for(;;){b=KQa(a[sj>>2],a[am>>2],c,a[g>>2]);d=a[am>>2];if(-1<(b|0)&(b|0)<(d|0)){f=769;break}d<<=1;b=b+1|0;b=(d|0)>(b|0)?d:b;a[am>>2]=b;if(0==(mc(a[sj>>2],b)|0)){f=771;break}}771==f?(Lc(Aw|0,35,1,a[oa>>2]),h=g):769==f&&(J[a[$l>>2]](a[sj>>2]),h=g)}function Mi(b,c){var d=a[a[b+16>>2]>>2],d=J[a[d>>2]](d,c,512);if(0==(d|0)||(a[b+32>>2]|0)==(b|0)){return d}var g=a[b+20>>2];return d=J[a[g>>2]](g,d,4)}function TL(b,c){var d=h;h+=4;a[d>>2]=c;var g=a[b+20>>2],g=J[a[g>>2]](g,d,512);h=d;return g}function cl(b,c){var d=Mi(a[b+32>>2],c);if(0!=(d|0)){return mj(b,d),d}var d=Zv(b,c,a[a[b+40>>2]>>2]),g=a[a[b+16>>2]>>2];J[a[g>>2]](g,d|0,1);mj(b,d);return d}function Zv(b,c,d){var g,f=Yc(1,a[qa+4>>2]);g=f>>2;a[f>>2]=a[f>>2]&-16|1;a[g+3]=Dc(c);var c=(b+16|0)>>2,e=a[c]+16|0,h=a[e>>2];a[e>>2]=h+1|0;a[g+4]=h;a[g+5]=a[b+32>>2];e=rc(a[a[a[c]+4>>2]+4>>2]);if(0==(e|0)){return a[g+1]=0,a[g+2]=0,f}b=(f+4|0)>>2;a[b]=Yc(e,4);a[g+2]=Yc((e+7|0)/8&-1,1);if(0>=(e|0)){return f}g=d+4|0;if(0==(d|0)){for(d=0;!(g=Dc(a[a[a[a[a[c]+4>>2]+8>>2]+(d<<2)>>2]+4>>2]),a[a[b]+(d<<2)>>2]=g,d=d+1|0,(d|0)==(e|0));){}}else{for(c=0;!(d=Dc(a[a[g>>2]+(c<<2)>>2]),a[a[b]+(c<<2)>>2]=d,c=c+1|0,(c|0)==(e|0));){}}return f}function mj(b,c){if(0==(TL(b,a[c+16>>2])|0)){var d=a[b+20>>2];J[a[d>>2]](d,c|0,1);if(0==(a[b>>2]&64|0)){var g=a[b+36>>2],d=a[g+20>>2],g=Og(d,g);if(0!=(g|0)){for(;!(mj(Yd(a[g+16>>2]),c),g=Ql(d,g),0==(g|0));){}}}}}function Pp(b,c){var d=Ok(b,c),g=0==(d|0);a:do{if(!g){for(var f=d;;){var e=Pk(b,f,c);Np(b,f);if(0==(e|0)){break a}else{f=e}}}}while(0);d=0==(a[b>>2]&64|0);a:do{if(d&&(f=a[b+36>>2],g=a[f+20>>2],e=Ib(g,f),0!=(e|0))){for(f=c|0;;){var h=Yd(a[e+12>>2]),k=a[h+20>>2];0!=(J[a[k>>2]](k,f,4)|0)&&Pp(h,c);e=yb(g,e);if(0==(e|0)){break a}}}}while(0);d=a[b+20>>2];J[a[d>>2]](d,c|0,2);(a[b+32>>2]|0)==(b|0)&&$v(c)}function $v(b){var c=b|0,d=b+20|0,g=a[a[a[d>>2]+16>>2]>>2];J[a[g>>2]](g,c,2);a[b>>2]|=15;dc(a[b+12>>2]);d=a[d>>2];g=0==(a[d>>2]&64|0);a:do{if(g){var f=rc(a[a[a[d+16>>2]+4>>2]+4>>2]);if(0<(f|0)){for(var e=b+4|0,h=0;;){if(dc(a[a[e>>2]+(h<<2)>>2]),h=h+1|0,(h|0)==(f|0)){break a}}}}}while(0);G(a[b+4>>2]);G(a[b+8>>2]);G(c)}function ra(b){b=a[b+20>>2];return J[a[b>>2]](b,0,128)}function ba(b,c){var d=a[b+20>>2];return J[a[d>>2]](d,c|0,8)}function zr(){var b,c,d=h;h+=2032;var g,f=d+2e3,e=d+2008,l=d+2016,k=d+2024,n=d|0,z=d+400|0;a[Wp>>2]=0;a[Qg>>2]=-2;var p=f|0,s=f+4|0,v=e|0,t=e+4|0,u=l|0,w=l+4|0,A=k|0,B=k+4|0,C=0,y=0,T=n,F=n,E=z,M=z,X=200;a:for(;;){D[F>>1]=C&65535;if(((X-1<<1)+T|0)>>>0>F>>>0){var O=T,I=F,J=E,H=M;c=H>>2;var K=X}else{var L=F-T>>1,N=L+1|0;if(9999>>0){g=941;break}var Q=X<<1,S=1e4>>0?1e4:Q,R=Gb(10*S+7|0);if(0==(R|0)){g=941;break}var U=R,W=T;tf(R,W,N<<1);var ca=(((S<<1)+7|0)>>>3<<3)+R|0;tf(ca,E,N<<3);(T|0)!=(n|0)&&G(W);if((S-1|0)>(L|0)){O=U,I=(L<<1)+U|0,J=ca,H=(L<<3)+ca|0,c=H>>2,K=S}else{var Z=U,Y=1;break}}if(9==(C|0)){Z=O;Y=0;break}var V=m[Bw+C|0],ja=V<<24>>24;if(-68==V<<24>>24){g=873}else{var aa=a[Qg>>2];if(-2==(aa|0)){var da=aM(),ea=a[Qg>>2]=da}else{ea=aa}var xa=1>(ea|0)?a[Qg>>2]=0:267>ea>>>0?m[yM+ea|0]&255:2;var $=xa+ja|0;if(80<$>>>0){g=873}else{if((m[Xp+$|0]<<24>>24|0)!=(xa|0)){g=873}else{var ba=m[Yp+$|0],ha=ba<<24>>24;if(1>ba<<24>>24){if(0==ba<<24>>24){g=929}else{var ga=-ha|0;g=874}}else{a[Qg>>2]=-2;var sa=H+8|0,fa=sa,ra=Yh,tb=a[ra+4>>2];a[fa>>2]=a[ra>>2];a[fa+4>>2]=tb;var ya=ha,ka=0==(y|0)?0:y-1|0,wa=I,Ab=sa}}}}if(873==g){var Fa=m[zM+C|0];0==Fa<<24>>24?g=929:(ga=Fa&255,g=874)}b:do{if(874==g){g=0;var Ga=m[AM+ga|0]&255,na=1-Ga|0,ta=a[(na<<3>>2)+c],Ka=(na<<3)+H+4|0,za=a[Ka>>2];if(5==(ga|0)){a[qa+16>>2]=0;var ma=ta,pa=za}else{if(6==(ga|0)){ma=a[c],pa=za}else{if(7==(ga|0)){ma=0,pa=za}else{if(8==(ga|0)){a[tj>>2]=0,a[qa+20>>2]=Af|0,ma=ta,pa=za}else{if(9==(ga|0)){a[tj>>2]=2,a[qa+20>>2]=Af|0,ma=ta,pa=za}else{if(11==(ga|0)){a[tj>>2]=3,a[qa+20>>2]=Bf|0,ma=ta,pa=za}else{if(12==(ga|0)){a[ye>>2]=3,ma=ta,pa=za}else{if(13==(ga|0)){a[ye>>2]=1,a[Zp>>2]=a[a[a[Vc>>2]+40>>2]>>2],ma=ta,pa=za}else{if(10==(ga|0)){a[tj>>2]=1,a[qa+20>>2]=Bf|0,ma=ta,pa=za}else{if(4==(ga|0)){var oa=a[qa+16>>2];0!=(oa|0)&&Sf(oa);a[qa+16>>2]=0;ma=ta;pa=za}else{if(2==(ga|0)){var Ha=H|0,Ra=a[Ha>>2],va=h;h+=128;if(0==(Ra|0)){var Aa=va|0;BM(Aa);var La=Aa}else{La=Ra}var Ea=xn(La,a[tj>>2]);a[qa+16>>2]=Ea;a[ye>>2]=3;Cw(Ea);m[Kf]=1;h=va;dc(a[Ha>>2]);ma=ta;pa=za}else{if(3==(ga|0)){m[qa+30|0]=1,CM(),ma=ta,pa=za}else{if(14==(ga|0)){a[ye>>2]=2,a[bm>>2]=a[a[a[Vc>>2]+40>>2]+4>>2],ma=ta,pa=za}else{if(23==(ga|0)){var Ya=H-16|0,Ma=H|0;Dw(a[Ya>>2],a[Ma>>2]);dc(a[Ya>>2]);dc(a[Ma>>2]);ma=ta;pa=za}else{if(25==(ga|0)){var Za=H|0;Dw(a[Za>>2],Es|0);dc(a[Za>>2]);ma=ta;pa=za}else{if(32==(ga|0)){vw(DM|0),ma=ta,pa=za}else{if(37==(ga|0)){a[ye>>2]=3,ma=ta,pa=za}else{if(38==(ga|0)){a[ye>>2]=3,ma=ta,pa=za}else{if(39==(ga|0)){var Qa=a[H-8>>2],ab=a[cm>>2];a[cm>>2]=0;ma=Qa;pa=ab}else{if(40==(ga|0)){var $a=H|0,jb,Ca=cl(a[Vc>>2],a[$a>>2]);m[Kf]=0;jb=Ca;dc(a[$a>>2]);ma=jb;pa=za}else{if(42==(ga|0)){a[cm>>2]=a[c],ma=ta,pa=za}else{if(43==(ga|0)){var Ia=H-16|0,eb=H|0,ub;var Sa=a[Ia>>2],Pa=a[eb>>2],ua=h;h+=1024;var Oa=Ba(Sa)+Ba(Pa)+2|0;if(1025>(Oa|0)){var Wa=ua|0,pb=Wa,ob=Wa}else{pb=Gb(Oa),ob=ua|0}Rf(pb,Sa);var bb=pb+Ba(pb)|0;Pb=58;m[bb]=Pb&255;Pb>>=8;m[bb+1]=Pb&255;uf(pb,Pa);var qb=Dc(pb);(pb|0)!=(ob|0)&&G(pb);h=ua;ub=qb;a[cm>>2]=ub;dc(a[Ia>>2]);dc(a[eb>>2]);ma=ta;pa=za}else{if(44==(ga|0)){a[ye>>2]=1,a[Zp>>2]=a[c],ma=ta,pa=za}else{if(45==(ga|0)){dc(a[H-16+4>>2]),a[ye>>2]=3,ma=ta,pa=za}else{if(46==(ga|0)){EM(a[c],a[c+1]),ma=ta,pa=za}else{if(47==(ga|0)){a[bm>>2]=a[a[a[a[Te>>2]>>2]+40>>2]+4>>2],a[ye>>2]=2,ma=ta,pa=za}else{if(48==(ga|0)){Ew(),ma=ta,pa=za}else{if(49==(ga|0)){EM(a[c],a[c+1]),ma=ta,pa=za}else{if(50==(ga|0)){a[bm>>2]=a[a[a[a[Te>>2]>>2]+40>>2]+4>>2],a[ye>>2]=2,ma=ta,pa=za}else{if(51==(ga|0)){Ew(),ma=ta,pa=za}else{if(52==(ga|0)){$p(a[c],a[c+1]),ma=ta,pa=za}else{if(53==(ga|0)){$p(a[c],a[c+1]),ma=ta,pa=za}else{if(55==(ga|0)){$p(a[c],a[c+1]),ma=ta,pa=za}else{if(56==(ga|0)){$p(a[c],a[c+1]),ma=ta,pa=za}else{if(58==(ga|0)){aq(f),ma=a[p>>2],pa=a[s>>2]}else{if(59==(ga|0)){FM(),ma=ta,pa=za}else{if(60==(ga|0)){aq(e),ma=a[v>>2],pa=a[t>>2]}else{if(61==(ga|0)){FM(),ma=ta,pa=za}else{if(62==(ga|0)){aq(l),ma=a[u>>2],pa=a[w>>2]}else{if(63==(ga|0)){var Cb=h,kb=a[a[Vc>>2]+12>>2],wb=a[Qd>>2];la(0,GM|0,(j=h,h+=8,a[j>>2]=kb,a[j+4>>2]=wb,j));la(3,HM|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j));var vb=a[a[dm+(a[uj>>2]-2<<2)>>2]+12>>2];la(3,IM|0,(j=h,h+=4,a[j>>2]=vb,j));h=Cb;aq(k);ma=a[A>>2];pa=a[B>>2]}else{if(64==(ga|0)){b=(H|0)>>2;var xb=RL(a[qa+16>>2],a[b]),Va=a[Vc>>2];if(0==(xb|0)){var nb=oo(Va,a[b])}else{gt(Va,xb|0),nb=xb}Cw(nb);m[Kf]=0;dc(a[b]);ma=ta}else{if(65==(ga|0)){ma=a[c]}else{if(66==(ga|0)){ma=a[c]}else{if(67==(ga|0)){ma=a[c]}else{if(68==(ga|0)){var rb=H-16|0,lb=H|0,Ta;var cb=a[rb>>2],fb=a[lb>>2],Ua=h;h+=1024;var sb=Ba(cb)+Ba(fb)+1|0;if(1025>(sb|0)){var Na=Ua|0,Fb=Na,Db=Na}else{Fb=Gb(sb),Db=Ua|0}Rf(Fb,cb);uf(Fb,fb);var Ob=Dc(Fb);(Fb|0)!=(Db|0)&&G(Fb);h=Ua;Ta=Ob;dc(a[rb>>2]);dc(a[lb>>2]);ma=Ta}else{ma=ta}}}}}pa=za}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}var Eb=(-Ga<<1)+I|0,hb=(na<<3)+H|0;a[hb>>2]=ma;a[Ka>>2]=pa;var Bb=(m[JM+ga|0]&255)-21|0,Ja=D[Eb>>1]<<16>>16,ib=(m[KM+Bb|0]<<24>>24)+Ja|0;do{if(81>ib>>>0&&(m[Xp+ib|0]<<24>>24|0)==(Ja|0)){ya=m[Yp+ib|0]<<24>>24;ka=y;wa=Eb;Ab=hb;break b}}while(0);ya=m[LM+Bb|0]<<24>>24;ka=y;wa=Eb;Ab=hb}else{if(929==g){g=0;do{if(0==(y|0)){a[Wp>>2]=a[Wp>>2]+1|0;vw(MM|0);var db=I,yb=H,mb=V}else{if(3==(y|0)){var Ib=a[Qg>>2];if(1>(Ib|0)){if(0==(Ib|0)){Z=O;Y=1;break a}else{db=I,yb=H,mb=V}}else{a[Qg>>2]=-2,db=I,yb=H,mb=V}}else{db=I,yb=H,mb=V}}}while(0);c:for(;;){do{if(-68!=mb<<24>>24){var Hb=(mb<<24>>24)+1|0;if(81>Hb>>>0&&1==m[Xp+Hb|0]<<24>>24){var ic=m[Yp+Hb|0];if(0>24){break c}}}}while(0);if((db|0)==(O|0)){Z=O;Y=1;break a}var Jb=db-2|0,db=Jb,yb=yb-8|0,mb=m[(D[Jb>>1]<<16>>16)+Bw|0]}var Kb=yb+8|0,Lb=Kb,Mb=Yh,Nb=a[Mb+4>>2];a[Lb>>2]=a[Mb>>2];a[Lb+4>>2]=Nb;ya=ic<<24>>24;ka=3;wa=db;Ab=Kb}}}while(0);C=ya;y=ka;T=O;F=wa+2|0;E=J;M=Ab;X=K}941==g&&(vw(NM|0),Z=T,Y=2);if((Z|0)==(n|0)){return h=d,Y}G(Z);h=d;return Y}function bg(b){return 0==(a[Ne>>2]|0)|0==(b|0)?0:b=(m[Tp]?-2147483648:0)&a[b-4>>2]}function Dw(b,c){if(m[Kf]){var d=a[Vc>>2],d=(a[d+32>>2]|0)==(d|0)?c:Y|0}else{d=Y|0}var g=a[ye>>2];if(2==(g|0)){g=$(a[a[a[Vc>>2]+40>>2]+4>>2]|0,b);if(0==(g|0)){d=Th(a[qa+16>>2],b,d)}else{if(0==m[g+13|0]<<24>>24){d=g}else{if(m[Kf]){if(d=a[Vc>>2],(a[d+32>>2]|0)!=(d|0)){d=g}else{return}}else{d=g}}}qc(a[bm>>2]|0,a[d+8>>2],c)}else{if(1==(g|0)){g=$(a[a[a[Vc>>2]+40>>2]>>2]|0,b);if(0==(g|0)){d=xi(a[qa+16>>2],b,d)}else{if(0!=m[g+13|0]<<24>>24&&m[Kf]){return}d=g}qc(a[Zp>>2]|0,a[d+8>>2],c)}else{if(0==(g|0)||3==(g|0)){g=$(a[Vc>>2]|0,b);if(0==(g|0)){d=Mp(a[qa+16>>2],b,d)}else{if(0!=m[g+13|0]<<24>>24&&m[Kf]){return}d=g}qc(a[Vc>>2]|0,a[d+8>>2],c)}}}}function EM(b,c){var d;d=Yc(1,20);a[d+16>>2]=a[Te>>2];a[Te>>2]=d;var g=Yc(1,12),f=a[Te>>2];d=f>>2;a[d+2]=g;f=f+4|0;a[f>>2]=g;a[g>>2]=b;a[g+4>>2]=c;a[a[f>>2]+8>>2]=0;a[d+3]=m[bq]<<24>>24;g=a[Vc>>2];a[d]=g;Yv(g,a[g+40>>2]);m[bq]=1}function $p(b,c){var d,g=Yc(1,12);d=(a[Te>>2]+8|0)>>2;a[a[d]+8>>2]=g;g=a[a[d]+8>>2];a[d]=g;a[g>>2]=b;a[g+4>>2]=c;a[a[d]+8>>2]=0}function aq(b){a[b>>2]=CM()|0;a[b+4>>2]=0}function FM(){var b=h;h+=128;m[Kf]=0;var c=b|0;BM(c);Cw(oo(a[Vc>>2],c));h=b}function BM(b){var c=h,d=a[Fw>>2];a[Fw>>2]=d+1|0;Ma(b,OM|0,(j=h,h+=4,a[j>>2]=d,j));h=c}function Dc(b){0==(a[Ne>>2]|0)&&gM();if(0==(b|0)){return 0}var c=a[Ne>>2],c=J[a[c>>2]](c,b-12|0,4);0==(c|0)?(c=Gb(Ba(b)+16|0),a[c+8>>2]=1,Rf(c+12|0,b),b=a[Ne>>2],J[a[b>>2]](b,c,1)):(b=c+8|0,a[b>>2]=a[b>>2]+1|0);b=c;return b+12|0}function gM(){var b=Nc(PM,a[Xc>>2]);a[Ne>>2]=b;m[Tp]=1;m[Gw]=1}function dc(b){var c=h,d=a[Ne>>2];if(!(0==(d|0)|0==(b|0))){if(d=J[a[d>>2]](d,b-12|0,4),0==(d|0)){la(1,QM|0,(j=h,h+=4,a[j>>2]=b,j))}else{var b=d+8|0,g=a[b>>2]-1|0;a[b>>2]=g;m[Gw]&0!=(g|0)||(b=a[Ne>>2],J[a[b>>2]](b,d,2),G(d))}}h=c}function Hw(b){pf(b);var c=a[b+12>>2],d=a[b+16>>2],g=Zh(c,d);0==(g|0)?de(c,d,b):og(b,g)}function RM(b){var c=b+163|0;if(0==m[c]<<24>>24){m[c]=1;c=b+164|0;m[c]=1;var b=b+184|0,d=a[a[b>>2]>>2],g=0==(d|0);a:do{if(!g){for(var f=0,e=d;;){var h=a[e+12>>2];0==m[h+164|0]<<24>>24?0==m[h+163|0]<<24>>24&&RM(h):(Hw(e),f=f-1|0);f=f+1|0;e=a[a[b>>2]+(f<<2)>>2];if(0==(e|0)){break a}}}}while(0);m[c]=0}}function Ew(){var b=a[Te>>2],c=a[b+4>>2],d=c+8|0,g=a[d>>2];if(0==(g|0)){g=b}else{for(var b=c,c=d,f=g;;){var g=a[b+4>>2],d=a[f+4>>2],e=a[b>>2],h=e;1==(a[e>>2]&15|0)?b=0:(b=h,e=ra(h));h=f=a[f>>2];1==(a[f>>2]&15|0)?h=0:f=ra(h);var k=0==(e|0);a:do{if(!k){for(var j=0==(f|0),z=0==(b|0),p=0==(h|0),s=e;;){b:do{if(!j){for(var v=f;;){var t=uh(a[Vc>>2],s,v);if(0!=(t|0)){var u=a[t+12>>2],w=(a[t+16>>2]|0)!=(u|0)&(u|0)==(s|0),u=w?d:g,w=w?g:d;0!=(u|0)&&0!=m[u]<<24>>24&&(qc(t|0,1,u),dc(u));0!=(w|0)&&0!=m[w]<<24>>24&&(qc(t|0,2,w),dc(w))}if(p){break b}v=ba(h,v);if(0==(v|0)){break b}}}}while(0);if(z){break a}s=ba(b,s);if(0==(s|0)){break a}}}}while(0);d=a[c>>2];c=d+8|0;g=a[c>>2];if(0==(g|0)){break}else{b=d,f=g}}g=c=a[Te>>2];c=a[c+4>>2]}if(0==(c|0)){c=g}else{for(;!(g=a[c+8>>2],1==(a[a[c>>2]>>2]&15|0)&&G(c),0==(g|0));){c=g}c=a[Te>>2]}g=a[Vc>>2];(g|0)==(a[c>>2]|0)?(LL(g),c=a[Te>>2],m[bq]=a[c+12>>2]&255,a[Te>>2]=a[c+16>>2],m[Kf]=0,G(c),a[ye>>2]=3):S()}function Cw(b){var c=h,d=a[uj>>2];63<(d|0)?(la(1,SM|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),Pe()):(a[uj>>2]=d+1|0,a[dm+(d<<2)>>2]=b,a[Vc>>2]=b,h=c)}function CM(){var b=h,c=a[uj>>2];0==(c|0)&&(la(1,TM|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),Pe());var d=c-1|0;a[uj>>2]=d;var g=a[dm+(d<<2)>>2];if(0>=(d|0)){return a[Vc>>2]=0,h=b,g}c=a[dm+(c-2<<2)>>2];a[Vc>>2]=c;h=b;return g}function UM(){var b=a[$h>>2];if(0<(b|0)){for(var c=a[Id>>2],d=0,g=0,f=0;;){var e=c+36*f|0,h=c+36*f+12|0,k=a[h>>2],j=0==(k|0),m=0==(d|0);a:do{if(j){var p=m?a[e>>2]:g,s=d+1|0}else{if(m){p=g,s=0}else{if((a[e>>2]|0)>(g|0)&0<(k|0)){for(var v=0,t=k;;){var u=a[a[(c+4>>2)+(9*f|0)]+(v<<2)>>2],w=u+4|0;if(0<(a[w>>2]|0)){t=u|0;for(u=0;;){var A=a[a[t>>2]+(u<<2)>>2]+236|0;a[A>>2]=a[A>>2]-d|0;u=u+1|0;if((u|0)>=(a[w>>2]|0)){break}}w=a[h>>2]}else{w=t}v=v+1|0;if((v|0)<(w|0)){t=w}else{p=g;s=d;break a}}}else{p=g,s=d}}}}while(0);f=f+1|0;if((f|0)<(b|0)){d=s,g=p}else{break}}}}function VM(c,d){var i,g=h,e,Xa=a[d+24>>2];WM(c);var l=-1==(Xa|0);i=(d+8|0)>>2;for(var k=d|0,n=0;;){if(!((n|0)<(Xa|0)|l)){e=1151;break}var z=ra(c),p=0==(z|0);a:do{if(!p){for(var s=z;;){if(a[s+236>>2]=0,s=ba(c,s),0==(s|0)){break a}}}}while(0);Iw(c);z=XM(c);f[0]=z;a[i]=b[0];a[i+1]=b[1];0!=m[ld]<<24>>24&&Va(a[oa>>2],YM|0,(j=h,h+=8,f[0]=z,a[j>>2]=b[0],a[j+4>>2]=b[1],j));if(l&&(b[0]=a[i],b[1]=a[i+1],f[0])<=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])){break}z=c;p=fa(Vh(z)<<2);a[em>>2]=p;if(0<(Vh(z)|0)){for(p=0;!(a[a[em>>2]+(p<<2)>>2]=p,p=p+1|0,(p|0)>=(Vh(z)|0));){}}cq(z);ch(a[em>>2],Vh(z),334);ZM(z);n=n+1|0}1151!=e&&(e=d+20|0,a[d+16>>2]=a[e>>2],a[e>>2]=n);Iw(c);cq(c);UM();n=XM(c);f[0]=n;a[i]=b[0];a[i+1]=b[1];h=g}function WM(c){var d,i,g=Cb(24*Vh(c)|0);a[vj>>2]=g;a[ze>>2]=0;var g=ra(c),e=0==(g|0);a:do{if(!e){for(d=g;;){if(a[d+136>>2]=-1,d=ba(c,d),0==(d|0)){break a}}}}while(0);g=ra(c);if(0!=(g|0)){for(;;){i=g;e=g+24|0;if(0==(a[g+220>>2]|0)){var h=fa(4);d=a[ze>>2];var l=a[vj>>2];a[(l>>2)+(6*d|0)]=h;a[h>>2]=i;a[(l+4>>2)+(6*d|0)]=1;i=e+24|0;i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);h=l+24*d+8|0;f[0]=i;a[h>>2]=b[0];a[h+4>>2]=b[1];i=e+32|0;i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);l=l+24*d+16|0;f[0]=i;a[l>>2]=b[0];a[l+4>>2]=b[1];a[e+112>>2]=d;a[ze>>2]=d+1|0}else{if(h=Zb(i),d=h+136|0,l=a[d>>2],-1<(l|0)){d=a[vj>>2];var h=d+24*l+4|0,k=a[h>>2];a[h>>2]=k+1|0;a[a[(d>>2)+(6*l|0)]+(k<<2)>>2]=i;h=e+24|0;i=(d+24*l+8|0)>>2;h=(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0])+(b[0]=a[i],b[1]=a[i+1],f[0]);f[0]=h;a[i]=b[0];a[i+1]=b[1];d=(d+24*l+16|0)>>2;i=(b[0]=a[d],b[1]=a[d+1],f[0]);h=e+32|0;h=(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0]);f[0]=i>2]=l}else{var k=fa(a[h+220>>2]<<2),e=a[ze>>2],l=a[vj>>2],j=l+24*e|0;a[j>>2]=k;a[k>>2]=h;(h|0)==(i|0)?(a[(l+4>>2)+(6*e|0)]=1,i=g+48|0,i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),h=l+24*e+8|0,f[0]=i,a[h>>2]=b[0],a[h+4>>2]=b[1],i=g+56|0,i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),l=l+24*e+16|0,f[0]=i):(a[a[j>>2]+4>>2]=i,a[(l+4>>2)+(6*e|0)]=2,i=h+48|0,k=g+48|0,i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])+(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),k=l+24*e+8|0,f[0]=i,a[k>>2]=b[0],a[k+4>>2]=b[1],i=h+56|0,i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),h=g+56|0,h=(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0]),l=l+24*e+16|0,f[0]=i>2]=b[0];a[l+4>>2]=b[1];a[d>>2]=e;a[g+136>>2]=e;a[ze>>2]=e+1|0}}g=ba(c,g);if(0==(g|0)){break}}}}function XM(c){cq(c);var d=a[$h>>2],i=(d-1)*a[c+260>>2]|0;if(0>=(d|0)){var g,e;return d=0/i}for(var h=a[Id>>2],c=a[c+256>>2],l=0,k=0;;){var j=h+36*k+20|0,j=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0])+(c*a[(h+16>>2)+(9*k|0)]|0),l=l>2],b[1]=a[j+4>>2],f[0]),k=k+1|0;if((k|0)>=(d|0)){g=l;e=i;break}}return d=g/e}function cq(c){var d,i,g;a[$h>>2]=0;var e=a[Id>>2];if(0!=(e|0)){var h=0<(a[ze>>2]|0);a:do{if(h){i=0;for(var l=e;;){var k=a[(l+4>>2)+(9*i|0)];0!=(k|0)&&(G(k),l=a[Id>>2]);k=a[(l+8>>2)+(9*i|0)];0!=(k|0)&&(G(k),l=a[Id>>2]);i=i+1|0;if((i|0)>=(a[ze>>2]|0)){g=l;break a}}}else{g=e}}while(0);G(g)}e=fa(36*a[ze>>2]|0);a[Id>>2]=e;e=a[ze>>2];h=0<(e|0);a:do{if(h){g=0;for(i=e;;){if(i=fa(i<<2),a[(a[Id>>2]+4>>2)+(9*g|0)]=i,i=fa(a[ze>>2]<<2),l=a[Id>>2],a[(l+8>>2)+(9*g|0)]=i,a[(l>>2)+(9*g|0)]=g,i=g+1|0,g=(l+36*g+12|0)>>2,a[g]=0,a[g+1]=0,a[g+2]=0,a[g+3]=0,a[g+4]=0,a[g+5]=0,l=a[ze>>2],(i|0)<(l|0)){g=i,i=l}else{break a}}}}while(0);e=ra(c);h=0==(e|0);a:do{if(!h){for(g=e;;){i=Ib(c,g);l=0==(i|0);b:do{if(!l){for(k=i;;){d=a[a[k+16>>2]+236>>2]+1|0;var j=k+12|0,m=(d|0)<(a[a[j>>2]+236>>2]|0);c:do{if(m){for(var p=a[Id>>2],s=d;;){var v=p+36*s+16|0;a[v>>2]=a[v>>2]+1|0;s=s+1|0;if((s|0)>=(a[a[j>>2]+236>>2]|0)){break c}}}}while(0);k=yb(c,k);if(0==(k|0)){break b}}}}while(0);g=ba(c,g);if(0==(g|0)){break a}}}}while(0);e=a[ze>>2];if(0<(e|0)){c=c+256|0;h=a[vj>>2];g=0;for(l=a[$h>>2];!(i=(a[a[(h>>2)+(6*g|0)]>>2]+236|0)>>2,d=a[i],k=d+1|0,(k|0)>(l|0)&&(l=a[$h>>2]=k),k=h+24*g+8|0,j=72*(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),k=a[Id>>2],d=(k+36*d+20|0)>>2,m=(b[0]=a[d],b[1]=a[d+1],f[0]),f[0]=m+j+((0>31&a[c>>2]|0),a[d]=b[0],a[d+1]=b[1],j=a[i],d=(k+36*j+28|0)>>2,m=(b[0]=a[d],b[1]=a[d+1],f[0]),p=h+24*g+16|0,p=72*(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),m>2)+(9*d|0)]+(a[(k+12>>2)+(9*d|0)]<<2)>>2]=h+24*g|0,i=k+36*a[i]+12|0,a[i>>2]=a[i>>2]+1|0,g=g+1|0,(g|0)>=(e|0));){}}}function ZM(c){var d,i,g,e=a[$h>>2],h=a[em>>2];d=a[Id>>2];i=d>>2;for(var l=0;(l|0)<(e|0);){var k=a[h+(l<<2)>>2],j=l+1|0;if(2>(a[i+(9*k|0)+3]|0)){l=j}else{g=1215;break}}if(1215==g){if((e|0)>(j|0)){var z=d+36*a[h+(j<<2)>>2]+20|0,p=k,z=(b[0]=a[z>>2],b[1]=a[z+4>>2],f[0])}else{var p=k,z=0}}if((l|0)!=(e|0)){ch(a[i+(9*p|0)+1],a[i+(9*p|0)+3],14);k=a[Id>>2];i=k+36*p+20|0;i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);if(z>.25*i){if(z<.75*i){var s=z}else{g=1220}}else{g=1220}1220==g&&(s=.5*i);g=a[(k+12>>2)+(9*p|0)];if(0<(g|0)){c=c+256|0;for(l=e=i=z=0;;){var v,h=a[(k+8>>2)+(9*p|0)];do{if(0==(a[h+(l<<2)>>2]|0)){var t=a[a[(k+4>>2)+(9*p|0)]+(l<<2)>>2];d=(t+8|0)>>2;var u=72*(b[0]=a[d],b[1]=a[d+1],f[0]),w=a[c>>2]&(0>31|0,j=0==(z|0);if(e+u+w<=s|j){d=e+u+w,u=i,w=j?1:z,t=j?t:v,j=k}else{var j=v+4|0,A=a[j>>2];if(0<(A|0)){for(var u=t+4|0,w=v|0,t=t|0,B=0,C=a[u>>2];;){if(0<(C|0)){for(C=0;;){m[de(a[a[w>>2]+(B<<2)>>2],a[a[t>>2]+(C<<2)>>2],0)+124|0]=1;var C=C+1|0,y=a[u>>2];if((C|0)>=(y|0)){break}}C=y;A=a[j>>2]}B=B+1|0;if((B|0)>=(A|0)){break}}j=a[Id>>2];u=a[(j+8>>2)+(9*p|0)]}else{u=h,j=k}a[u+(l<<2)>>2]=1;u=j+36*p+12|0;a[u>>2]=a[u>>2]-1|0;u=j+36*p+16|0;a[u>>2]=a[u>>2]+1|0;u=72*(b[0]=a[d],b[1]=a[d+1],f[0])+(a[c>>2]|0);d=(j+36*p+20|0)>>2;u=(b[0]=a[d],b[1]=a[d+1],f[0])-u;f[0]=u;a[d]=b[0];a[d+1]=b[1];d=e;u=i;w=z;t=v}}else{d=e,u=i+1|0,w=z,t=v,j=k}}while(0);l=l+1|0;if((l|0)<(u+g|0)){v=t,z=w,i=u,e=d,k=j}else{break}}}}}function $M(b){var c=b+4|0;if(0>=(a[c>>2]|0)){var d;return 0}for(var b=b|0,g=0,f=0;;){var e=a[a[b>>2]+(f<<2)>>2],h=a[e+20>>2],e=Ib(h,e),k=0==(e|0);a:do{if(k){var j=g}else{for(var m=g,p=e;;){if(m=m+1|0,p=yb(h,p),0==(p|0)){j=m;break a}}}}while(0);f=f+1|0;if((f|0)<(a[c>>2]|0)){g=j}else{d=j;break}}return d}function aN(b){var c=a[dj>>2];return 0!=(c|0)&&(b=mb(b|0,a[c+8>>2]),0!=(b|0)&&0!=m[b]<<24>>24&&0==re(b)<<24>>24)?1:0}function bN(c,d){var i,g=(c|0)/2&-1|0;i=(d+104|0)>>2;var e=(b[0]=a[i],b[1]=a[i+1],f[0])+g;f[0]=e;a[i]=b[0];a[i+1]=b[1];i=(d+112|0)>>2;g=(b[0]=a[i],b[1]=a[i+1],f[0])+g;f[0]=g;a[i]=b[0];a[i+1]=b[1]}function cN(b){Jw(b);var c=ra(b);if(0!=(c|0)){for(;;){var d=Ib(b,c),g=0==(d|0);a:do{if(!g){for(var f=d;;){var e=f,h=0==(a[f+180>>2]|0);b:do{if(h&&0==(aN(e)|0)){var k=f+16|0,j=Zb(a[k>>2]),m=f+12|0,p=Zb(a[m>>2]);if((j|0)!=(p|0)){do{if(0==(a[j+216>>2]|0)&&0==(a[p+216>>2]|0)){h=Zh(j,p);0==(h|0)?de(j,p,e):og(e,h);break b}}while(0);dN(b,a[k>>2],a[m>>2],e)}}}while(0);f=yb(b,f);if(0==(f|0)){break a}}}}while(0);c=ba(b,c);if(0==(c|0)){break}}}}function dN(b,c,d,g){var f=a[g+16>>2],e=a[f+216>>2],h=a[g+12>>2],k=a[h+216>>2],e=(0==(e|0)?0:a[f+236>>2]-a[a[e+272>>2]+236>>2]|0)-(0==(k|0)?0:a[h+236>>2]-a[a[k+272>>2]+236>>2]|0)+(D[g+178>>1]&65535)|0;0<(e|0)?(f=e|0,e=0):(f=0,e=-e|0);b=Lf(b);m[b+162|0]=2;c=Zb(c);d=Zb(d);h=g+164|0;c=Zc(b,c,e,10*ib[h>>2]&-1);a[Zc(b,d,f,ib[h>>2]&-1)+128>>2]=g;a[c+128>>2]=g}function wj(b,c,d,g){var f=a[a[c+16>>2]+236>>2],e=a[a[c+12>>2]+236>>2],f=(f|0)>(e|0)?f:e,e=c+180|0;0!=(a[e>>2]|0)&&sa(dq|0,149,eN|0,Kw|0);a[e>>2]=d;for(var b=b+256|0,g=0==(g|0),e=c+162|0,h=c+164|0,c=c+176|0;;){if(!g){var k=d+176|0;D[k>>1]=D[k>>1]+D[c>>1]&65535}k=d+162|0;D[k>>1]=D[k>>1]+D[e>>1]&65535;k=d+164|0;ib[k>>2]+=ib[h>>2];d=d+12|0;k=a[d>>2];if((a[k+236>>2]|0)==(f|0)){break}bN(a[b>>2],k);d=a[a[a[d>>2]+184>>2]>>2];if(0==(d|0)){break}}}function fN(b,c){return!(0==(b|0)|0==(c|0))&&(a[b+16>>2]|0)==(a[c+16>>2]|0)&&(a[b+12>>2]|0)==(a[c+12>>2]|0)&&(a[b+108>>2]|0)==(a[c+108>>2]|0)&&0!=(fm(b,c)|0)?1:0}function Lw(b){var c,d,g,f,e,h=b+216|0;a[h>>2]=0;e=(b+240|0)>>2;a[e]=0;Jw(b);var k=b+208|0,j=1>(a[k>>2]|0);a:do{if(!j){var z=b+212|0;for(f=1;;){if(gN(b,a[a[z>>2]+(f<<2)>>2]),f=f+1|0,(f|0)>(a[k>>2]|0)){break a}}}}while(0);k=ra(b);j=0==(k|0);a:do{if(!j){for(z=k;;){f=Ib(b,z);var p=0==(f|0);b:do{if(!p){for(var s=f;;){var v=a[s+12>>2]+166|0;g=m[v];3>g<<24>>24&&(m[v]=g+1&255);v=a[s+16>>2]+166|0;g=m[v];3>g<<24>>24&&(m[v]=g+1&255);s=yb(b,s);if(0==(s|0)){break b}}}}while(0);z=ba(b,z);if(0==(z|0)){break a}}}}while(0);k=ra(b);j=0==(k|0);a:do{if(!j){for(z=k;;){f=z;0==(a[z+216>>2]|0)&&(f|0)==(Zb(f)|0)&&(Mw(b,f),a[e]=a[e]+1|0);p=Ib(b,z);s=0==(p|0);b:do{if(!s){v=0;f=v>>2;var t=p;for(g=t>>2;;){var u=t,w=t+24|0,A=0==(a[g+45]|0);c:do{if(A){if(d=7==m[a[u+16>>2]+165|0]<<24>>24?1:7==m[a[u+12>>2]+165|0]<<24>>24,d&=1,0!=(d|0)){0==(fN(v,u)|0)?(hN(b,u),d=u):(d=a[f+45],0!=(d|0)?(wj(b,u,d,0),Rg(u)):(a[a[g+4]+236>>2]|0)==(a[a[g+3]+236>>2]|0)&&(og(u,v),Rg(u)),d=v)}else{d=(t+16|0)>>2;var B=a[d];do{if(0!=(v|0)&&(B|0)==(a[f+4]|0)&&(c=a[g+3],(c|0)==(a[f+3]|0))){if((a[B+236>>2]|0)==(a[c+236>>2]|0)){og(u,v);Rg(u);d=v;break c}if(0==(a[w+84>>2]|0)&&0==(a[f+27]|0)&&0!=(fm(u,v)|0)){0==m[Wi]<<24>>24?(wj(b,u,a[f+45],1),Rg(u)):m[w+100|0]=6;d=v;break c}}}while(0);c=(t+12|0)>>2;if((B|0)==(a[c]|0)){Rg(u),d=u}else{var B=Zb(B),C=Zb(a[c]),y=a[d];if((y|0)!=(B|0)){d=v}else{var D=a[c];if((D|0)!=(C|0)){d=v}else{var F=a[y+236>>2],E=a[D+236>>2];if((F|0)==(E|0)){gm(b,u)}else{if((E|0)>(F|0)){hm(b,B,C,u)}else{C=B=Kr(b,D,y);do{if(!(0==(B|0)|(B|0)==(t|0))&&(y=B+24|0,D=y+156|0,0==(a[D>>2]|0)&&hm(b,a[B+16>>2],a[B+12>>2],C),0==(a[w+84>>2]|0)&&0==(a[y+84>>2]|0)&&0!=(fm(u,C)|0))){0==m[Wi]<<24>>24?(Rg(u),wj(b,u,a[D>>2],1)):(m[w+100|0]=6,m[y+137|0]=1);d=v;break c}}while(0);hm(b,a[c],a[d],u)}}d=u}}}}}else{d=u}}while(0);g=yb(b,t);if(0==(g|0)){break b}else{v=d,f=v>>2,t=g,g=t>>2}}}}while(0);z=ba(b,z);if(0==(z|0)){break a}}}}while(0);(a[b+32>>2]|0)!=(b|0)&&(b=b+224|0,e=a[b>>2],e=0==(e|0)?Cb(4):wb(e,4),a[b>>2]=e,a[e>>2]=a[h>>2])}function hN(b,c){var d=iN(a[c+16>>2]),g=iN(a[c+12>>2]),f=(a[d+236>>2]|0)>(a[g+236>>2]|0),e=f?g:d,g=f?d:g;if((a[e+216>>2]|0)!=(a[g+216>>2]|0)){if(d=Zh(e,g),0!=(d|0)){wj(b,c,d,1)}else{if(d=g+236|0,(a[e+236>>2]|0)!=(a[d>>2]|0)){hm(b,e,g,c);for(e=c+180|0;;){e=a[e>>2];if(0==(e|0)){break}g=a[e+12>>2];if((a[g+236>>2]|0)>(a[d>>2]|0)){break}m[e+124|0]=5;e=a[g+184>>2]}}}}}function hm(c,d,i,g){var e=0==(a[g+108>>2]|0)?-1:(a[i+236>>2]+a[d+236>>2]|0)/2&-1,h=g+180|0;0!=(a[h>>2]|0)&&sa(dq|0,90,Nw|0,jN|0);var l=a[d+236>>2]+1|0,k=i+236|0,j=a[k>>2],z=(l|0)>(j|0);a:do{if(!z){for(var p=d,s=l,v=j;;){if((s|0)<(v|0)){if((s|0)==(e|0)){var v=c,t=g,u=cc,u=t+108|0,w=a[u>>2],A=w+24|0,A=(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0]),w=w+32|0,B=(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0]),w=Lf(v);a[w+120>>2]=a[u>>2];u=w+104|0;f[0]=a[a[a[w+20>>2]+32>>2]+256>>2]|0;a[u>>2]=b[0];a[u+4>>2]=b[1];0==m[t+126|0]<<24>>24&&(u=(w+96|0)>>2,0==(a[a[v+32>>2]+152>>2]&1|0)?(f[0]=B,a[u]=b[0],a[u+1]=b[1],v=w+112|0,f[0]=A):(f[0]=A,a[u]=b[0],a[u+1]=b[1],v=w+112|0,f[0]=B),a[v>>2]=b[0],a[v+4>>2]=b[1]);v=w}else{v=c,t=Lf(v),bN(a[v+256>>2],t),v=t}a[v+236>>2]=s}else{v=i}p=de(p,v,g);t=p+164|0;ib[t>>2]*=a[((kN(a[p+12>>2])<<2)+lN>>2)+(3*kN(a[p+16>>2])|0)]|0;s=s+1|0;t=a[k>>2];if((s|0)>(t|0)){break a}else{p=v,v=t}}}}while(0);0==(a[h>>2]|0)&&sa(dq|0,104,Nw|0,mN|0)}function iN(b){return 7==m[b+165|0]<<24>>24?a[a[a[b+216>>2]+276>>2]+(a[b+236>>2]<<2)>>2]:Zb(b)}function nN(b){var c,d;d=(b+32|0)>>2;var g=a[d],f=ra(b);if(0!=(f|0)){for(;;){var e=Ok(a[d],f),h=0==(e|0);a:do{if(!h){var k=0,j=e;for(c=j>>2;;){var m=j;if(0==(Ed(b,j|0)|0)){var p=j,s=0==(fN(k,p)|0),v=j+16|0,t=a[v>>2],u=a[t+236>>2],w=j+12|0,A=a[w>>2],B=a[A+236>>2],C=(u|0)==(B|0);s?C?(u=oN(t,A),0==(u|0)?gm(g,p):((j|0)!=(u|0)&&(pN(p,a[p+16>>2]+208|0),0==(a[c+45]|0)&&og(p,u)),m=k)):(0==(a[c+45]|0)?(sa(Ow|0,234,qN|0,rN|0),p=a[w>>2],u=a[v>>2],k=p,p=a[p+236>>2],t=u,u=a[u+236>>2]):(k=A,p=B),(p|0)>(u|0)?sN(t,k,m):sN(k,t,m)):(a[c+45]=C?k:0,m=a[k+180>>2],0!=(m|0)&&(wj(b,p,m,0),pN(p,a[p+16>>2]+208|0)),m=k)}else{m=k}j=Pk(a[d],j,f);if(0==(j|0)){break a}else{k=m,c=j>>2}}}}while(0);f=ba(b,f);if(0==(f|0)){break}}}}function sN(b,c,d){var g=tN(b),f=tN(c);uN(g,f,d,a[d+180>>2],(g|0)==(b|0)&(f|0)==(c|0)?1:5)}function vN(b){var c,d,g;g=(b+32|0)>>2;var f=a[g],e=b+244|0,h=D[e>>1];0>16?(m[a[f+220>>2]+44*((h<<16>>16)-1)+33|0]=0,c=D[e>>1]):c=h;var e=c<<16>>16,h=b+246|0,k=c<<16>>16>D[h>>1]<<16>>16;a:do{if(k){var j=e}else{d=(b+220|0)>>2;var z=b+276|0;c=(f+220|0)>>2;for(var p=b,s=e;;){var v=a[a[a[z>>2]+(s<<2)>>2]+240>>2];Pw(f,s,v,a[(a[d]>>2)+(11*s|0)]);var t=a[d],u=0<(a[(t>>2)+(11*s|0)]|0);b:do{if(u){for(var w=0,A=v,B=t;;){if(B=a[a[(B+4>>2)+(11*s|0)]+(w<<2)>>2],a[a[(a[c]+4>>2)+(11*s|0)]+(A<<2)>>2]=B,a[B+240>>2]=A,a[B+20>>2]=a[g],Qw(p,B),Mw(a[g],B),B=a[g]+240|0,a[B>>2]=a[B>>2]+1|0,w=w+1|0,B=a[d],(w|0)<(a[(B>>2)+(11*s|0)]|0)){A=A+1|0}else{var C=B;break b}}}else{C=t}}while(0);a[(C+4>>2)+(11*s|0)]=(v<<2)+a[(a[c]+4>>2)+(11*s|0)]|0;m[a[c]+44*s+33|0]=0;s=s+1|0;if((s|0)>(D[h>>1]<<16>>16|0)){j=s;break a}}}}while(0);(j|0)<(D[f+246>>1]<<16>>16|0)&&(m[a[f+220>>2]+44*j+33|0]=0);b=b+280|0;m[b]=1}function wN(b){var c=D[b+244>>1],d=b+246|0;if(c<<16>>16<=D[d>>1]<<16>>16){for(var g=b+276|0,b=b+32|0,c=c<<16>>16;;){var f=a[a[g>>2]+(c<<2)>>2],e=f+184|0,h=a[a[e>>2]>>2],k=0==(h|0);a:do{if(!k){for(var j=h;;){if(pf(j),j=a[a[e>>2]>>2],0==(j|0)){break a}}}}while(0);e=f+176|0;h=a[a[e>>2]>>2];k=0==(h|0);a:do{if(!k){for(j=h;;){if(pf(j),j=a[a[e>>2]>>2],0==(j|0)){break a}}}}while(0);Qw(a[b>>2],f);a[a[g>>2]+(c<<2)>>2]=0;c=c+1|0;if((c|0)>(D[d>>1]<<16>>16|0)){break}}}}function Jw(b){var c=h,d=ra(b),g=0==(d|0);a:do{if(!g){for(var f=d;;){if(7==m[f+165|0]<<24>>24&&YE(f),a[f+216>>2]=0,f=ba(b,f),0==(f|0)){break a}}}}while(0);d=b+208|0;if(1<=(a[d>>2]|0)){g=b+212|0;b=b+12|0;for(f=1;;){var e=a[a[g>>2]+(f<<2)>>2],l=e,k=ra(l),n=0==(k|0);a:do{if(!n){for(var z=e+272|0,p=e,s=k;;){var v=ba(l,s),t=s+24|0,u=t+141|0,w=0==m[u]<<24>>24;b:do{if(w){var A=s,B=a[z>>2];(Zb(A)|0)!=(A|0)&&sa(qo|0,198,xN|0,yN|0);a[A+224>>2]=B;B=B+220|0;a[B>>2]=a[B>>2]+a[A+220>>2]|0;a[t+192>>2]=e;m[u]=7;A=Ib(l,s);if(0!=(A|0)){for(;;){var B=a[A+180>>2],C=0==(B|0);c:do{if(!C){for(var y=B;;){if(0==(y|0)){break c}var y=y+12|0,D=a[y>>2];if(1!=m[D+162|0]<<24>>24){break c}a[D+216>>2]=e;y=a[a[a[y>>2]+184>>2]>>2]}}}while(0);A=yb(l,A);if(0==(A|0)){break b}}}}else{A=a[b>>2],la(0,zN|0,(j=h,h+=8,a[j>>2]=a[s+12>>2],a[j+4>>2]=A,j)),bl(p,s|0)}}while(0);if(0==(v|0)){break a}else{s=v}}}}while(0);f=f+1|0;if((f|0)>(a[d>>2]|0)){break}}}h=c}function gN(b,c){var d,g;g=(c+246|0)>>1;d=(c+276|0)>>2;a[d]=fa((D[g]<<16>>16<<2)+8|0);var f=c+244|0,e=D[f>>1],h=e<<16>>16>D[g]<<16>>16;a:do{if(!h){for(var k=b,j=c,z=0,p=e<<16>>16;;){var s=Lf(k),v=s;a[a[d]+(p<<2)>>2]=v;a[s+236>>2]=p;m[s+165|0]=7;a[s+216>>2]=j;0!=(z|0)&&(z=de(z,s,0)+162|0,D[z>>1]=1e3*D[z>>1]&65535);p=p+1|0;if((p|0)>(D[g]<<16>>16|0)){break a}else{z=v}}}}while(0);e=ra(c);h=0==(e|0);a:do{if(!h){for(k=e;;){j=a[a[d]+(a[k+236>>2]<<2)>>2];v=j+220|0;a[v>>2]=a[v>>2]+1|0;v=Ib(c,k);z=0==(v|0);b:do{if(!z){p=j+184|0;for(s=v;;){var t=a[a[s+16>>2]+236>>2],u=s+12|0,w=(t|0)<(a[a[u>>2]+236>>2]|0);c:do{if(w){for(var A=t;;){var B=a[a[p>>2]>>2]+176|0;D[B>>1]=D[B>>1]+1&65535;A=A+1|0;if((A|0)>=(a[a[u>>2]+236>>2]|0)){break c}}}}while(0);s=yb(c,s);if(0==(s|0)){break b}}}}while(0);k=ba(c,k);if(0==(k|0)){break a}}}}while(0);f=D[f>>1];e=D[g];if(f<<16>>16<=e<<16>>16){for(f=f<<16>>16;!(h=a[a[d]+(f<<2)>>2]+220|0,k=a[h>>2],1<(k|0)&&(a[h>>2]=k-1|0,e=D[g]),f=f+1|0,(f|0)>(e<<16>>16|0));){}}}function AN(b,c,d,g){var f=a[c+216>>2],e=f+281|0,h=d+1|0;if((m[e]<<24>>24|0)!=(h|0)){var k=f+244|0,j=D[k>>1],c=(f+246|0)>>1,z=D[c];if(j<<16>>16>z<<16>>16){var b=j,p=z}else{z=f+276|0;for(j=j<<16>>16;!(Rw(b,a[a[z>>2]+(j<<2)>>2]),j=j+1|0,p=D[c],(j|0)>(p<<16>>16|0));){}b=D[k>>1]}p=b<<16>>16>p<<16>>16;a:do{if(!p){k=f+276|0;z=g;for(j=b<<16>>16;;){if(Sw(z,a[a[k>>2]+(j<<2)>>2],d),j=j+1|0,(j|0)>(D[c]<<16>>16|0)){break a}}}}while(0);m[e]=h&255}}function Tw(b){var c=ra(b);if(0!=(c|0)){for(;;){a[c+216>>2]=0;var d=Ib(b,c),g=0==(d|0);a:do{if(!g){for(var f=d;;){var e=a[f+180>>2],h=0==(e|0);b:do{if(!h){for(var k=e;;){if(0==(k|0)){break b}var k=k+12|0,j=a[k>>2];if(1!=m[j+162|0]<<24>>24){break b}a[j+216>>2]=0;k=a[a[a[k>>2]+184>>2]>>2]}}}while(0);f=yb(b,f);if(0==(f|0)){break a}}}}while(0);c=ba(b,c);if(0==(c|0)){break}}}Uw(b)}function Uw(b){var c=b+208|0,d=1>(a[c>>2]|0);a:do{if(!d){for(var g=b+212|0,f=1;;){if(Uw(a[a[g>>2]+(f<<2)>>2]),f=f+1|0,(f|0)>(a[c>>2]|0)){break a}}}}while(0);c=ra(b);if(0!=(c|0)){for(;;){d=c+216|0;0==(a[d>>2]|0)&&(a[d>>2]=b);d=Ib(b,c);g=0==(d|0);a:do{if(!g){for(f=d;;){var e=a[f+180>>2],h=0==(e|0);b:do{if(!h){for(var k=e;;){if(0==(k|0)){break b}var k=k+12|0,j=a[k>>2];if(1!=m[j+162|0]<<24>>24){break b}var z=j+216|0;0==(a[z>>2]|0)?(a[z>>2]=b,k=a[k>>2]):k=j;k=a[a[k+184>>2]>>2]}}}while(0);f=yb(b,f);if(0==(f|0)){break a}}}}while(0);c=ba(b,c);if(0==(c|0)){break}}}}function tN(b){var c=a[b+216>>2];return 0==(c|0)||0!=m[c+280|0]<<24>>24?b:b=a[a[c+276>>2]+(a[b+236>>2]<<2)>>2]}function Pw(b,c,d,g){var f,e,b=(b+220|0)>>2,h=a[b];e=h>>2;var k=a[e+(11*c|0)+1];f=k>>2;if(1>(g|0)){d=d-g|0;k=d+1|0;e=a[e+(11*c|0)];var j=(k|0)<(e|0);a:do{if(j){for(var m=d,p=k;;){var s=a[(p<<2>>2)+f],m=m+g|0;a[s+240>>2]=m;a[(m<<2>>2)+f]=s;var s=p+1|0,m=a[b],v=a[(m>>2)+(11*c|0)];if((s|0)<(v|0)){m=p,p=s}else{var t=m,u=v;break a}}}else{t=h,u=e}}while(0);g=g-1|0;h=g+u|0;if((h|0)<(u|0)){for(t=h;;){if(a[(t<<2>>2)+f]=0,t=t+1|0,u=a[b],(t|0)>=(a[(u>>2)+(11*c|0)]|0)){w=u;A=g;break}}c=(w+44*c|0)>>2;b=a[c];b=A+b|0}else{var w,A,c=t+44*c|0,c=c>>2,b=a[c],b=g+b|0}}else{A=a[e+(11*c|0)]-1|0;w=(A|0)>(d|0);a:do{if(w){t=g-1|0;for(u=A;;){if(h=a[(u<<2>>2)+f],e=t+u|0,a[h+240>>2]=e,a[(e<<2>>2)+f]=h,u=u-1|0,(u|0)<=(d|0)){break a}}}}while(0);f=d+1|0;(f|0)<(g+d|0)&&li((f<<2)+k|0,(g<<2)-4|0);w=a[b];c=w+44*c|0;c>>=2;b=a[c];b=(g-1|0)+b|0}a[c]=b}function uN(c,d,i,g,e){var h,l;l=(c+236|0)>>2;h=(d+236|0)>>2;(a[l]|0)<(a[h]|0)||sa(Ow|0,109,BN|0,CN|0);if(!((a[g+16>>2]|0)==(c|0)&&(a[g+12>>2]|0)==(d|0))){if(1>1]<<16>>16){a[i+180>>2]=0;var k=a[h],j=a[l];if(1==(k-j|0)){var z=Zh(c,d);if(0!=(z|0)&&0!=(fm(i,z)|0)){og(i,z);if(0!=m[c+162|0]<<24>>24||0!=m[d+162|0]<<24>>24){return}Rg(i);return}}if((j|0)<(k|0)){z=c+20|0;e&=255;for(l=k;;){if((j|0)<(l-1|0)){l=g+12|0;var k=a[z>>2],p=a[l>>2],s=p+236|0,v=a[s>>2],t=p+240|0;Pw(k,v,a[t>>2],2);var u=Lf(k),w=p+104|0,w=(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0]),A=u+104|0;f[0]=w;a[A>>2]=b[0];a[A+4>>2]=b[1];p=p+112|0;p=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]);w=u+112|0;f[0]=p;a[w>>2]=b[0];a[w+4>>2]=b[1];a[u+236>>2]=a[s>>2];s=a[t>>2]+1|0;a[u+240>>2]=s;k=a[a[(a[k+220>>2]+4>>2)+(11*v|0)]+(s<<2)>>2]=u}else{k=d,l=g+12|0}m[de(c,k,i)+124|0]=e;g=g+176|0;D[g>>1]=D[g>>1]-1&65535;j=j+1|0;v=a[h];if((j|0)<(v|0)){g=a[a[a[l>>2]+184>>2]>>2],c=k,l=v}else{break}}}}else{k=1==(a[h]-a[l]|0);a:do{if(k){s=c;t=d;v=p=Zh(s,t);u=i;do{if(0!=(p|0)&&0!=(fm(u,p)|0)){a[i+180>>2]=v;m[p+124|0]=e&255;g=p+176|0;D[g>>1]=D[g>>1]+1&65535;if(0!=m[c+162|0]<<24>>24){break a}if(0!=m[d+162|0]<<24>>24){break a}Rg(u);break a}}while(0);a[i+180>>2]=0;v=de(s,t,u);m[v+124|0]=e&255}else{v=g}}while(0);k=a[h];if(1<(k-a[l]|0)){(a[v+16>>2]|0)==(c|0)?(g=v,h=k):(g=i+180|0,a[g>>2]=0,c=de(c,a[v+12>>2],i),a[g>>2]=c,pf(v),g=c,h=a[h]);c=a[g+12>>2];l=(a[c+236>>2]|0)==(h|0);a:do{if(l){z=g,j=c}else{for(k=c;;){if(k=a[a[k+184>>2]>>2],v=a[k+12>>2],(a[v+236>>2]|0)==(h|0)){z=k;j=v;break a}else{k=v}}}}while(0);(j|0)!=(d|0)&&(m[de(a[z+16>>2],d,i)+124|0]=e&255,pf(z))}}}}function xj(c,d,i){var g=i|0;(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])>c?d=0:(g=i+16|0,(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])>2],b[1]=a[c+4>>2],f[0])>d?d=0:(i=i+24|0,d=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])>=d)));return d&1}function yj(c,d,i,g,e){var h=c|0;f[0]=.5*(d+g);a[h>>2]=b[0];a[h+4>>2]=b[1];c=c+8|0;f[0]=.5*(i+e);a[c>>2]=b[0];a[c+4>>2]=b[1]}function DN(c,d){var i,g,e,m,l,k,n,z,p,s,v,t,u,w,A,B,C,y,D,F,E,M,X,O,H,I,J,K,L,N,S,Q,R,U,W,ca,Z,Y,$,ja,aa,da,ea,xa,ba,fa=h;h+=224;var ha,ga=fa+64,ra=fa+80,ka=fa+96,qa=fa+112,tb=fa+128,ya=fa+144,na=fa+160,wa=fa+176,Ab=fa+192,Fa=fa+208,Ga=d|0,oa=EN(c,V(Ga,eq|0)),ta=EN(c,V(Ga,fq|0)),Ka=0!=(ta|0),za=0==(oa|0);if(!(za&(Ka^1))){var ma=d+24|0,pa=a[ma>>2];if(0!=(pa|0)){if(1<(a[pa+4>>2]|0)){var Ba=a[a[d+12>>2]+12>>2];la(0,FN|0,(j=h,h+=8,a[j>>2]=a[a[d+16>>2]+12>>2],a[j+4>>2]=Ba,j))}else{var Ha=a[pa>>2],Ra=a[Ha+4>>2];ba=(d+12|0)>>2;var va=a[ba];xa=(d+16|0)>>2;var Aa=a[xa],La=Cb(48);ea=(Ha+12|0)>>2;a[La+12>>2]=a[ea];da=(Ha+8|0)>>2;a[La+8>>2]=a[da];do{if(za){ha=1647}else{var Ea=oa+52|0,Ya=va+32|0,Ma=va+40|0;if(0==(xj((b[0]=a[Ya>>2],b[1]=a[Ya+4>>2],f[0]),(b[0]=a[Ma>>2],b[1]=a[Ma+4>>2],f[0]),Ea)|0)){var Za=a[a[xa]+12>>2],Qa=a[a[ba]+12>>2],ab=V(Ga,eq|0);la(0,GN|0,(j=h,h+=12,a[j>>2]=Za,a[j+4>>2]=Qa,a[j+8>>2]=ab,j));ha=1647}else{aa=(Ha|0)>>2;var $a=a[aa],jb=$a|0,Ca=(b[0]=a[jb>>2],b[1]=a[jb+4>>2],f[0]),Ia=$a+8|0,eb=(b[0]=a[Ia>>2],b[1]=a[Ia+4>>2],f[0]);if(0!=(xj(Ca,eb,Ea)|0)){var ub=Aa+32|0,Sa=Aa+40|0;if(0!=(xj((b[0]=a[ub>>2],b[1]=a[ub+4>>2],f[0]),(b[0]=a[Sa>>2],b[1]=a[Sa+4>>2],f[0]),Ea)|0)){var Pa=a[a[xa]+12>>2],ua=a[a[ba]+12>>2],Oa=V(Ga,eq|0);la(0,HN|0,(j=h,h+=12,a[j>>2]=Pa,a[j+4>>2]=ua,a[j+8>>2]=Oa,j));ha=1647}else{if(0==(a[da]|0)){sa(zj|0,364,im|0,Vw|0);var Wa=a[aa],pb=Wa|0,ob=Wa+8|0,bb=(b[0]=a[pb>>2],b[1]=a[pb+4>>2],f[0]),qb=(b[0]=a[ob>>2],b[1]=a[ob+4>>2],f[0])}else{bb=Ca,qb=eb}ja=(Ha+16|0)>>2;$=(Ha+24|0)>>2;jm(ga,bb,qb,(b[0]=a[ja],b[1]=a[ja+1],f[0]),(b[0]=a[$],b[1]=a[$+1],f[0]),Ea);var wb=ga|0,kb=(b[0]=a[wb>>2],b[1]=a[wb+4>>2],f[0]),hb=ga+8|0,vb=(b[0]=a[hb>>2],b[1]=a[hb+4>>2],f[0]),xb=a[aa],Va=xb+48|0;f[0]=kb;a[Va>>2]=b[0];a[Va+4>>2]=b[1];var nb=xb+56|0;f[0]=vb;a[nb>>2]=b[0];a[nb+4>>2]=b[1];var rb=a[aa]+16|0;yj(ra,kb,vb,(b[0]=a[ja],b[1]=a[ja+1],f[0]),(b[0]=a[$],b[1]=a[$+1],f[0]));Y=rb>>2;Z=ra>>2;a[Y]=a[Z];a[Y+1]=a[Z+1];a[Y+2]=a[Z+2];a[Y+3]=a[Z+3];var lb=a[aa],Ta=lb+16|0,cb=(b[0]=a[Ta>>2],b[1]=a[Ta+4>>2],f[0]),fb=lb+24|0,Ua=(b[0]=a[fb>>2],b[1]=a[fb+4>>2],f[0]);yj(ka,cb,Ua,(b[0]=a[ja],b[1]=a[ja+1],f[0]),(b[0]=a[$],b[1]=a[$+1],f[0]));ca=lb>>2;W=ka>>2;a[ca]=a[W];a[ca+1]=a[W+1];a[ca+2]=a[W+2];a[ca+3]=a[W+3];var sb=a[aa],Na=sb+16|0,Fb=sb+24|0;yj(qa,(b[0]=a[Na>>2],b[1]=a[Na+4>>2],f[0]),(b[0]=a[Fb>>2],b[1]=a[Fb+4>>2],f[0]),kb,vb);U=(sb+32|0)>>2;R=qa>>2;a[U]=a[R];a[U+1]=a[R+1];a[U+2]=a[R+2];a[U+3]=a[R+3];var Db=a[ea],Ob=0==(Db|0)?3:Hn(d,a[aa],0,0,La,Db)+3|0}}else{for(var Eb=Ra-1|0,db=0;(db|0)<(Eb|0);){if(0==(Ww((db<<4)+a[aa]|0,Ea)|0)){db=db+3|0}else{break}}var Bb=a[ea],Ja=0!=(Bb|0);if((db|0)==(Eb|0)){Ja||sa(zj|0,382,im|0,Xw|0);Q=(La+32|0)>>2;var ib=a[aa],yb=Ha+32|0,mb=(b[0]=a[yb>>2],b[1]=a[yb+4>>2],f[0]),Hb=Ha+40|0,Ib=(b[0]=a[Hb>>2],b[1]=a[Hb+4>>2],f[0]),Jb=(Eb<<4)+ib|0,ic=(Eb<<4)+ib+8|0;jm(tb,mb,Ib,(b[0]=a[Jb>>2],b[1]=a[Jb+4>>2],f[0]),(b[0]=a[ic>>2],b[1]=a[ic+4>>2],f[0]),Ea);S=tb>>2;a[Q]=a[S];a[Q+1]=a[S+1];a[Q+2]=a[S+2];a[Q+3]=a[S+3];Ob=Eb}else{Ob=(Ja?Hn(d,a[aa],0,db,La,Bb):db)+3|0}}}}}while(0);if(1647==ha){var Kb=Ra-1|0;0!=(a[ea]|0)&&(N=(La+32|0)>>2,L=(Ha+32|0)>>2,a[N]=a[L],a[N+1]=a[L+1],a[N+2]=a[L+2],a[N+3]=a[L+3]);Ob=Kb}a:do{if(Ka){var Gb=ta+52|0,Lb=Aa+32|0,Mb=Aa+40|0;if(0==(xj((b[0]=a[Lb>>2],b[1]=a[Lb+4>>2],f[0]),(b[0]=a[Mb>>2],b[1]=a[Mb+4>>2],f[0]),Gb)|0)){var Nb=a[a[xa]+12>>2],Pb=a[a[ba]+12>>2],Wb=V(Ga,fq|0);la(0,IN|0,(j=h,h+=12,a[j>>2]=Nb,a[j+4>>2]=Pb,a[j+8>>2]=Wb,j));ha=1670}else{K=(Ha|0)>>2;var Vb=a[K],Tb=(Ob<<4)+Vb|0,Yb=(b[0]=a[Tb>>2],b[1]=a[Tb+4>>2],f[0]),Sb=(Ob<<4)+Vb+8|0,$b=(b[0]=a[Sb>>2],b[1]=a[Sb+4>>2],f[0]);if(0!=(xj(Yb,$b,Gb)|0)){var Zb=va+32|0,ec=va+40|0;if(0!=(xj((b[0]=a[Zb>>2],b[1]=a[Zb+4>>2],f[0]),(b[0]=a[ec>>2],b[1]=a[ec+4>>2],f[0]),Gb)|0)){var lc=a[a[xa]+12>>2],Xb=a[a[ba]+12>>2],kc=V(Ga,fq|0);la(0,JN|0,(j=h,h+=12,a[j>>2]=lc,a[j+4>>2]=Xb,a[j+8>>2]=kc,j));ha=1670}else{if(0==(a[ea]|0)){sa(zj|0,424,im|0,Xw|0);var ac=a[K],Hc=(Ob<<4)+ac|0,nc=(Ob<<4)+ac+8|0,uc=(b[0]=a[Hc>>2],b[1]=a[Hc+4>>2],f[0]),qc=(b[0]=a[nc>>2],b[1]=a[nc+4>>2],f[0])}else{uc=Yb,qc=$b}J=(La+32|0)>>2;I=(La+40|0)>>2;jm(ya,uc,qc,(b[0]=a[J],b[1]=a[J+1],f[0]),(b[0]=a[I],b[1]=a[I+1],f[0]),Gb);var Qb=ya|0,tc=(b[0]=a[Qb>>2],b[1]=a[Qb+4>>2],f[0]),Sc=ya+8|0,gc=(b[0]=a[Sc>>2],b[1]=a[Sc+4>>2],f[0]),dc=Ob-3|0,bc=a[K],jc=(dc<<4)+bc|0;f[0]=tc;a[jc>>2]=b[0];a[jc+4>>2]=b[1];var oc=(dc<<4)+bc+8|0;f[0]=gc;a[oc>>2]=b[0];a[oc+4>>2]=b[1];var rc=Ob-1|0,Ac=(rc<<4)+a[K]|0;yj(na,tc,gc,(b[0]=a[J],b[1]=a[J+1],f[0]),(b[0]=a[I],b[1]=a[I+1],f[0]));H=Ac>>2;O=na>>2;a[H]=a[O];a[H+1]=a[O+1];a[H+2]=a[O+2];a[H+3]=a[O+3];var fc=a[K],mc=(rc<<4)+fc|0,Bc=(b[0]=a[mc>>2],b[1]=a[mc+4>>2],f[0]),Cc=(rc<<4)+fc+8|0,fd=(b[0]=a[Cc>>2],b[1]=a[Cc+4>>2],f[0]);yj(wa,Bc,fd,(b[0]=a[J],b[1]=a[J+1],f[0]),(b[0]=a[I],b[1]=a[I+1],f[0]));X=((Ob<<4)+fc|0)>>2;M=wa>>2;a[X]=a[M];a[X+1]=a[M+1];a[X+2]=a[M+2];a[X+3]=a[M+3];var Ec=a[K],yc=(rc<<4)+Ec|0,Fc=(rc<<4)+Ec+8|0;yj(Ab,(b[0]=a[yc>>2],b[1]=a[yc+4>>2],f[0]),(b[0]=a[Fc>>2],b[1]=a[Fc+4>>2],f[0]),tc,gc);E=((Ob-2<<4)+Ec|0)>>2;F=Ab>>2;a[E]=a[F];a[E+1]=a[F+1];a[E+2]=a[F+2];a[E+3]=a[F+3];var vc=a[da],Tc=0==(vc|0)?dc:In(d,a[K],dc,dc,La,vc)}}else{var Kc=fa|0;D=fa>>2;y=(fa+16|0)>>2;C=(fa+32|0)>>2;B=(fa+48|0)>>2;for(var Mc=Ob;;){if(0>=(Mc|0)){ha=1663;break}A=((Mc<<4)+a[K]|0)>>2;a[D]=a[A];a[D+1]=a[A+1];a[D+2]=a[A+2];a[D+3]=a[A+3];var id=Mc-1|0;w=((id<<4)+a[K]|0)>>2;a[y]=a[w];a[y+1]=a[w+1];a[y+2]=a[w+2];a[y+3]=a[w+3];var zc=Mc-2|0;u=((zc<<4)+a[K]|0)>>2;a[C]=a[u];a[C+1]=a[u+1];a[C+2]=a[u+2];a[C+3]=a[u+3];var Ic=Mc-3|0;t=((Ic<<4)+a[K]|0)>>2;a[B]=a[t];a[B+1]=a[t+1];a[B+2]=a[t+2];a[B+3]=a[t+3];if(0==(Ww(Kc,Gb)|0)){Mc=Ic}else{ha=1662;break}}do{if(1662==ha){v=((Mc<<4)+a[K]|0)>>2;a[v]=a[D];a[v+1]=a[D+1];a[v+2]=a[D+2];a[v+3]=a[D+3];s=((id<<4)+a[K]|0)>>2;a[s]=a[y];a[s+1]=a[y+1];a[s+2]=a[y+2];a[s+3]=a[y+3];p=((zc<<4)+a[K]|0)>>2;a[p]=a[C];a[p+1]=a[C+1];a[p+2]=a[C+2];a[p+3]=a[C+3];z=((Ic<<4)+a[K]|0)>>2;a[z]=a[B];a[z+1]=a[B+1];a[z+2]=a[B+2];a[z+3]=a[B+3];var Dc=Ic}else{if(1663==ha){if(0!=(Mc|0)){Dc=Mc-3|0}else{0==(a[da]|0)&&sa(zj|0,447,im|0,Vw|0);n=(La+16|0)>>2;var Gc=a[K],Nc=Ha+16|0,Qc=(b[0]=a[Nc>>2],b[1]=a[Nc+4>>2],f[0]),Yc=Ha+24|0,Rc=(b[0]=a[Yc>>2],b[1]=a[Yc+4>>2],f[0]),Td=Gc|0,Lc=Gc+8|0;jm(Fa,Qc,Rc,(b[0]=a[Td>>2],b[1]=a[Td+4>>2],f[0]),(b[0]=a[Lc>>2],b[1]=a[Lc+4>>2],f[0]),Gb);k=Fa>>2;a[n]=a[k];a[n+1]=a[k+1];a[n+2]=a[k+2];a[n+3]=a[k+3];Tc=0;break a}}}}while(0);var Pc=a[da],Tc=0==(Pc|0)?Dc:In(d,a[K],Dc,Ob-3|0,La,Pc)}}}else{ha=1670}}while(0);1670==ha&&(0!=(a[da]|0)&&(l=(La+16|0)>>2,m=(Ha+16|0)>>2,a[l]=a[m],a[l+1]=a[m+1],a[l+2]=a[m+2],a[l+3]=a[m+3]),Tc=0);var Xc=Ob-Tc+1|0;e=(La+4|0)>>2;a[e]=Xc;var Jc=Cb(Xc<<4);a[La>>2]=Jc;var ad=0<(a[e]|0),jd=Ha|0;a:do{if(ad){for(var md=0,Oc=Tc,Vc=Jc;;){g=((md<<4)+Vc|0)>>2;i=((Oc<<4)+a[jd>>2]|0)>>2;a[g]=a[i];a[g+1]=a[i+1];a[g+2]=a[i+2];a[g+3]=a[i+3];var Zc=md+1|0;if((Zc|0)>=(a[e]|0)){break a}md=Zc;Oc=Oc+1|0;Vc=a[La>>2]}}}while(0);G(a[jd>>2]);G(Ha);a[a[ma>>2]>>2]=La}}}h=fa}function EN(b,c){var d=h;if(0==(c|0)){var g=0}else{0==m[c]<<24>>24?g=0:(g=RL(b,c),0==(g|0)&&la(0,KN|0,(j=h,h+=4,a[j>>2]=c,j)))}h=d;return g}function jm(c,d,i,g,e,m){var l=h;h+=400;var k=l+100,n=l+200,z=l+300,p=m|0,p=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),s=m+8|0,s=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]),v=m+16|0,v=(b[0]=a[v>>2],b[1]=a[v+4>>2],f[0]),m=m+24|0,m=(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0]);if(p>g){var t=((p-d)*(i-e)/(d-g)&-1|0)+i;if(!(tm)){z=c|0;f[0]=p;a[z>>2]=b[0];a[z+4>>2]=b[1];c=c+8|0;f[0]=t;a[c>>2]=b[0];a[c+4>>2]=b[1];h=l;return}}if(vm){var u=v}else{z=c|0;f[0]=v;a[z>>2]=b[0];a[z+4>>2]=b[1];c=c+8|0;f[0]=t;a[c>>2]=b[0];a[c+4>>2]=b[1];h=l;return}}else{u=p}if(s>e){if(u=((s-i)*(d-g)/(i-e)&-1|0)+d,uv){t=s}else{z=c|0;f[0]=u;a[z>>2]=b[0];a[z+4>>2]=b[1];c=c+8|0;f[0]=s;a[c>>2]=b[0];a[c+4>>2]=b[1];h=l;return}}if(mv){t=m}else{z=c|0;f[0]=u;a[z>>2]=b[0];a[z+4>>2]=b[1];c=c+8|0;f[0]=m;a[c>>2]=b[0];a[c+4>>2]=b[1];h=l;return}}d=gq(d,i,l|0);g=gq(g,e,k|0);n=gq(p,s,n|0);z=gq(v,m,z|0);la(1,LN|0,(j=h,h+=16,a[j>>2]=d,a[j+4>>2]=g,a[j+8>>2]=n,a[j+12>>2]=z,j));sa(zj|0,78,MN|0,vd|0);z=c|0;f[0]=u;a[z>>2]=b[0];a[z+4>>2]=b[1];c=c+8|0;f[0]=t;a[c>>2]=b[0];a[c+4>>2]=b[1];h=l}function Ww(c,d){var i,g,e,j,l=h;h+=128;var k=l|0;g=l>>2;j=c>>2;a[g]=a[j];a[g+1]=a[j+1];a[g+2]=a[j+2];a[g+3]=a[j+3];g=(l+16|0)>>2;j=(c+16|0)>>2;a[g]=a[j];a[g+1]=a[j+1];a[g+2]=a[j+2];a[g+3]=a[j+3];g=(l+32|0)>>2;j=(c+32|0)>>2;a[g]=a[j];a[g+1]=a[j+1];a[g+2]=a[j+2];a[g+3]=a[j+3];g=(l+48|0)>>2;j=(c+48|0)>>2;a[g]=a[j];a[g+1]=a[j+1];a[g+2]=a[j+2];a[g+3]=a[j+3];j=(d|0)>>2;i=(b[0]=a[j],b[1]=a[j+1],f[0]);e=(d+8|0)>>2;g=(d+24|0)>>2;i=hq(c,0,1,i,(b[0]=a[e],b[1]=a[e+1],f[0]),(b[0]=a[g],b[1]=a[g+1],f[0]));if(0<=i&2>i){Ld(l+64,k,3,i,c,0);var n=i}else{n=2}i=(d+16|0)>>2;var m=(b[0]=a[i],b[1]=a[i+1],f[0]),m=hq(c,0,1e,h=l,k&1}Ld(l+112,k,3,j,c,0);k=2>j;h=l;return k&1}function hq(c,d,i,g,e,j){var l=h;h+=144;var k,n=l+64,m=l+128,p=NN(c,g);if(1==(p|0)){k=1717}else{if(0==(p|0)){return h=l,-1}}if(1717==k&&(k=c+48|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),((0>k?k-.5:k+.5)&-1|0)==((0>g?g-.5:g+.5)&-1|0))){return g=c+56|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),h=l,gj?-1:i}k=l|0;n|=0;Ld(m,c,3,.5,k,n);c=.5*(d+i);d=hq(k,d,c,g,e,j);if(0<=d){return h=l,d}i=hq(n,c,i,g,e,j);h=l;return i}function ON(c,d){var i=c+8|0,i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),i=id&1,g=c+24|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),e=gd&1,g=c+40|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),g=gd&1,i=((g|0)!=(e|0)&0!=(e|0)&1)+((e|0)!=(i|0)&0!=(i|0)&1)+(0==(i|0)&1)|0,e=c+56|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);if(ed&1|0)!=(g|0);g=e&0!=(g|0);return i=(g&1)+i|0}function NN(c,d){var i=c|0,i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),i=id&1,g=c+16|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),e=gd&1,g=c+32|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),g=gd&1,i=((g|0)!=(e|0)&0!=(e|0)&1)+((e|0)!=(i|0)&0!=(i|0)&1)+(0==(i|0)&1)|0,e=c+48|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);if(ed&1|0)!=(g|0);g=e&0!=(g|0);return i=(g&1)+i|0}function PN(b){return(1==m[b+162|0]<<24>>24?1!=(a[b+180>>2]|0)?0:1!=(a[b+188>>2]|0)?0:0==(a[b+120>>2]|0):0)&1}function QN(b){return(1==m[b+162|0]<<24>>24?1!=(a[b+188>>2]|0)?0:1!=(a[b+180>>2]|0)?0:0==(a[b+120>>2]|0):0)&1}function iq(c,d,i,g,e,j){var l=h;h+=144;var k,n=l+64,m=l+128,p=ON(c,g);if(1==(p|0)){k=1764}else{if(0==(p|0)){return h=l,-1}}if(1764==k&&(k=c+56|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),((0>k?k-.5:k+.5)&-1|0)==((0>g?g-.5:g+.5)&-1|0))){return g=c+48|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),h=l,gj?-1:i}k=l|0;n|=0;Ld(m,c,3,.5,k,n);c=.5*(d+i);d=iq(k,d,c,g,e,j);if(0<=d){return h=l,d}i=iq(n,c,i,g,e,j);h=l;return i}function gq(c,d,i){var g=h;Ma(i,RN|0,(j=h,h+=16,f[0]=c,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=d,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));h=g;return i}function SN(b){var c,d;if(2<=((D[b+246>>1]<<16>>16)-(D[b+244>>1]<<16>>16)|0)){c=(b+220|0)>>2;var g=a[c];do{if(0==(a[g+88>>2]|0)){var f=1,e=g;d=1791}else{for(var h=1,k=2,j=g;;){var m=a[(j>>2)+(11*h|0)],p=0<(m|0);a:do{if(p){for(var s=0,v=j,t=m;;){var u=a[(v+4>>2)+(11*h|0)],w=a[u+(s<<2)>>2];do{if(0==PN(w)<<24>>24){var A=v,B=t}else{A=w+176|0;for(B=s;;){var C=B+1|0;if((C|0)>=(t|0)){break}var y;y=a[a[A>>2]>>2];var T=a[u+(C<<2)>>2],F=a[a[T+176>>2]>>2];y=0==PN(T)<<24>>24?0:(a[y+16>>2]|0)!=(a[F+16>>2]|0)?0:(0==Yw(y,F)<<24>>24?0:0==(Aj(y+28|0,F+28|0)|0))&1;if(0==y<<24>>24){break}else{B=C}}1<(C-s|0)?(Zw(b,h,s,B,1),A=B=a[c],B=a[(B>>2)+(11*h|0)]):(A=v,B=t)}}while(0);s=s+1|0;if((s|0)<(B|0)){v=A,t=B}else{var E=A;break a}}}else{E=j}}while(0);j=k+1|0;if(0==(a[(E>>2)+(11*j|0)]|0)){break}else{h=k,k=j,j=E}}0<(k|0)&&(f=k,e=E,d=1791)}}while(0);a:do{if(1791==d){for(;;){d=a[(e>>2)+(11*f|0)];g=0<(d|0);b:do{if(g){h=0;k=e;for(C=d;;){E=a[(k+4>>2)+(11*f|0)];j=a[E+(h<<2)>>2];do{if(0==QN(j)<<24>>24){m=k,p=C}else{m=j+184|0;for(p=h;;){var M=p+1|0;if((M|0)>=(C|0)){break}s=a[a[m>>2]>>2];v=a[E+(M<<2)>>2];t=a[a[v+184>>2]>>2];s=0==QN(v)<<24>>24?0:(a[s+12>>2]|0)!=(a[t+12>>2]|0)?0:(0==Yw(s,t)<<24>>24?0:0==(Aj(s+68|0,t+68|0)|0))&1;if(0==s<<24>>24){break}else{p=M}}1<(M-h|0)?(Zw(b,f,h,p,0),m=p=a[c],p=a[(p>>2)+(11*f|0)]):(m=k,p=C)}}while(0);h=h+1|0;if((h|0)<(p|0)){k=m,C=p}else{var G=m;break b}}}else{G=e}}while(0);f=f-1|0;if(0<(f|0)){e=G,d=1791}else{break a}}}}while(0);c=b+208|0;if(1<=(a[c>>2]|0)){b=b+212|0;for(M=1;!($w(a[a[b>>2]+(M<<2)>>2]),M=M+1|0,(M|0)>(a[c>>2]|0));){}}}}function Zw(b,c,d,g,f){var e,h;e=(b+220|0)>>2;var k=a[e],j=a[(k+4>>2)+(11*c|0)],m=a[j+(d<<2)>>2],d=d+1|0,p=(d|0)>(g|0);a:do{if(p){var s=k}else{for(var v=1==(f|0),t=b,u=m+184|0,w=m,A=m+176|0,B=d,C=j;;){C=a[C+(B<<2)>>2];b:do{if(v){var y=C+184|0,D=a[a[y>>2]>>2];if(0!=(D|0)){for(var F=C+176|0;;){for(var E=a[u>>2],M=D+12|0,G=0;;){var O=a[E+(G<<2)>>2];if(0==(O|0)){h=1827;break}if((a[O+12>>2]|0)==(a[M>>2]|0)){var H=O;break}else{G=G+1|0}}1827==h&&(h=0,H=de(w,a[M>>2],D));E=a[a[F>>2]>>2];M=0==(E|0);c:do{if(!M){G=H;for(O=E;;){if(og(O,G),pf(O),O=a[a[F>>2]>>2],0==(O|0)){break c}}}}while(0);pf(D);D=a[a[y>>2]>>2];if(0==(D|0)){break b}}}}else{if(y=C+176|0,D=a[a[y>>2]>>2],0!=(D|0)){for(F=C+184|0;;){E=a[A>>2];M=D+16|0;for(G=0;;){O=a[E+(G<<2)>>2];if(0==(O|0)){h=1835;break}if((a[O+16>>2]|0)==(a[M>>2]|0)){var I=O;break}else{G=G+1|0}}1835==h&&(h=0,I=de(a[M>>2],w,D));E=a[a[F>>2]>>2];M=0==(E|0);c:do{if(!M){G=I;for(O=E;;){if(og(O,G),pf(O),O=a[a[F>>2]>>2],0==(O|0)){break c}}}}while(0);pf(D);D=a[a[y>>2]>>2];if(0==(D|0)){break b}}}}}while(0);(a[C+180>>2]|0)!=(-a[C+188>>2]|0)&&sa(TN|0,113,UN|0,VN|0);Qw(t,C);B=B+1|0;C=a[e];if((B|0)>(g|0)){s=C;break a}C=a[(C+4>>2)+(11*c|0)]}}}while(0);b=g+1|0;g=s+44*c|0;if((b|0)<(a[g>>2]|0)){for(;;){if(s=a[(s+4>>2)+(11*c|0)],g=a[s+(b<<2)>>2],a[s+(d<<2)>>2]=g,a[g+240>>2]=d,d=d+1|0,b=b+1|0,s=a[e],g=s+44*c|0,(b|0)>=(a[g>>2]|0)){J=d;K=g;break}}a[K>>2]=J}else{var J=d,K;a[g>>2]=J}e=a[e];c=a[(e+44*c+4|0)>>2];J=(J<<2)+c|0;a[J>>2]=0}function WN(b,c){var d=(a[c+236>>2]<<2)+b|0,g=a[d>>2];if(0==(g|0)||(a[g+240>>2]|0)>(a[c+240>>2]|0)){a[d>>2]=c}}function Yw(b,c){var d=0==m[b+124|0]<<24>>24;a:do{if(d){var g=b}else{for(var f=b;;){if(f=a[f+128>>2],0==m[f+124|0]<<24>>24){g=f;break a}}}}while(0);d=0==m[c+124|0]<<24>>24;a:do{if(d){var e=c}else{for(f=c;;){if(f=a[f+128>>2],0==m[f+124|0]<<24>>24){e=f;break a}}}}while(0);return 0!=m[g+161|0]<<24>>24||0!=m[e+161|0]<<24>>24?0:g=0<((a[a[g+16>>2]+236>>2]-a[a[g+12>>2]+236>>2])*(a[a[e+16>>2]+236>>2]-a[a[e+12>>2]+236>>2])|0)&1}function jq(b,c){var d;a[Bj>>2]=b;var g=m[pg]+1&255;m[pg]=0==g<<24>>24?1:g;a[b+228>>2]=0;a[b+240>>2]=0;var f=ra(b);if(0!=(f|0)){for(g=0<(c|0);;){var e=f;if(g){d=f+24|0;var h=a[d+192>>2];if(0==(h|0)){d=1886}else{var k=a[a[h+276>>2]+(a[d+212>>2]<<2)>>2];d=1887}}else{d=1886}1886==d&&(d=0,(e|0)==(Zb(e)|0)&&(k=e,d=1887));if(1887==d&&m[k+163|0]<<24>>24!=m[pg]<<24>>24){a[a[Bj>>2]+216>>2]=0;a[kq>>2]=0;Cj(k);var h=a[Bj>>2],j=h+228|0,e=a[j>>2];d=e+1|0;a[j>>2]=d;h=a[h+224>>2];d=0==(h|0)?Cb(d<<2):wb(h,d<<2);h=a[Bj>>2];a[h+224>>2]=d;a[d+(e<<2)>>2]=a[h+216>>2]}f=ba(b,f);if(0==(f|0)){break}}}}function Cj(b){var c=a[Bj>>2],d=c+240|0;a[d>>2]=a[d>>2]+1|0;m[b+163|0]=m[pg];var d=a[kq>>2],g=b+172|0;0==(d|0)?(a[g>>2]=0,a[c+216>>2]=b):(a[g>>2]=d,a[d+168>>2]=b);a[kq>>2]=b;c=b+168|0;a[c>>2]=0;var f=a[(b+184|0)>>2],e=0==(f|0),g=a[(b+176|0)>>2],d=a[(b+192|0)>>2],c=a[(b+200|0)>>2];a:do{if(!e){var h=a[f>>2];if(0!=(h|0)){for(var k=0;;){var j=a[h+12>>2],h=(j|0)==(b|0)?a[h+16>>2]:j;m[h+163|0]<<24>>24!=m[pg]<<24>>24&&(h|0)==(Zb(h)|0)&&Cj(h);k=k+1|0;h=a[f+(k<<2)>>2];if(0==(h|0)){break a}}}}}while(0);f=0==(g|0);a:do{if(!f&&(k=a[g>>2],0!=(k|0))){for(e=0;;){if(h=a[k+12>>2],k=(h|0)==(b|0)?a[k+16>>2]:h,m[k+163|0]<<24>>24!=m[pg]<<24>>24&&(k|0)==(Zb(k)|0)&&Cj(k),e=e+1|0,k=a[g+(e<<2)>>2],0==(k|0)){break a}}}}while(0);g=0==(d|0);a:do{if(!g&&(e=a[d>>2],0!=(e|0))){for(f=0;;){if(k=a[e+12>>2],e=(k|0)==(b|0)?a[e+16>>2]:k,m[e+163|0]<<24>>24!=m[pg]<<24>>24&&(e|0)==(Zb(e)|0)&&Cj(e),f=f+1|0,e=a[d+(f<<2)>>2],0==(e|0)){break a}}}}while(0);if(0!=(c|0)&&(g=a[c>>2],0!=(g|0))){for(d=0;!(f=a[g+12>>2],g=(f|0)==(b|0)?a[g+16>>2]:f,m[g+163|0]<<24>>24!=m[pg]<<24>>24&&(g|0)==(Zb(g)|0)&&Cj(g),d=d+1|0,g=a[c+(d<<2)>>2],0==(g|0));){}}}function XN(b){var c=ra(b),d=0==(c|0);a:do{if(!d){for(var g=c;;){var f=g,e=f>>2;hF(f);et(f,a[a[e+5]+152>>2]&1);a[e+45]=0;a[e+44]=fa(20);a[e+47]=0;a[e+46]=fa(20);a[e+51]=0;a[e+50]=fa(12);a[e+49]=0;a[e+48]=fa(12);a[e+53]=0;a[e+52]=fa(12);a[e+55]=1;g=ba(b,g);if(0==(g|0)){break a}}}}while(0);c=ra(b);if(0!=(c|0)){for(;;){d=Ib(b,c);g=0==(d|0);a:do{if(!g){for(f=d;;){var e=f,h=cc,k=cc;kF(e);var j=e|0,z=Xb(j,a[Kg>>2],1,0),k=(e+164|0)>>2;ib[k]=z;var h=a[Gh>>2],p=jc(a[e+16>>2]|0,h,Y|0),s=jc(a[e+12>>2]|0,h,Y|0),h=(e+162|0)>>1;D[h]=1;D[e+176>>1]=1;0!=m[p]<<24>>24&(p|0)==(s|0)&&(D[h]=1e3,ib[k]=100*z);0!=(aN(e)|0)&&(D[h]=0,ib[k]=0);m[e+160|0]=Zf(j,a[$u>>2],0)&255;D[e+178>>1]=Zf(j,a[Hh>>2],1)&65535;f=yb(b,f);if(0==(f|0)){break a}}}}while(0);c=ba(b,c);if(0==(c|0)){break}}}}function ax(b){var c=a[b+216>>2],d;if(0!=(c|0)){for(d=c>>2;;){var g=a[d+42],f=c,e=a[f+180>>2],h=0<(e|0);a:do{if(h){for(var k=f+176|0,j=e;;){var j=j-1|0,z=a[a[k>>2]+(j<<2)>>2];pf(z);G(z|0);if(0>=(j|0)){break a}}}}while(0);e=a[f+188>>2];if(0<(e|0)){for(f=f+184|0;!(e=e-1|0,h=a[a[f>>2]+(e<<2)>>2],pf(h),G(h|0),0>=(e|0));){}}1==m[c+162|0]<<24>>24&&(f=a[d+46],0!=(f|0)&&G(f),d=a[d+44],0!=(d|0)&&G(d),G(c|0));if(0==(g|0)){break}else{c=g,d=c>>2}}}c=ra(b);if(0!=(c|0)){for(;;){g=c;d=Ib(b,c);f=0==(d|0);a:do{if(!f){for(e=d;;){h=e;k=cc;k=(h+24|0)>>2;z=a[k];if(0!=(z|0)){j=0<(a[z+4>>2]|0);z=a[z>>2];b:do{if(j){for(var p=0,s=z;;){G(a[(s>>2)+(12*p|0)]);var p=p+1|0,v=a[k],s=a[v>>2];if((p|0)>=(a[v+4>>2]|0)){var t=s;break b}}}else{t=z}}while(0);G(t);G(a[k])}a[k]=0;vh(a[h+108>>2]);vh(a[h+120>>2]);vh(a[h+112>>2]);vh(a[h+116>>2]);h=(h+24|0)>>2;for(k=h+40;h>1];f=(b+246|0)>>1;var n=d<<16>>16>D[f]<<16>>16;a:do{if(n){g=b+276|0,g>>=2}else{for(var z=b+276|0,p=d<<16>>16;;){if(a[a[z>>2]+(p<<2)>>2]=0,p=p+1|0,(p|0)>(D[f]<<16>>16|0)){g=z;g>>=2;break a}}}}while(0);$N(b);d=ra(b);n=0==(d|0);a:do{if(!n){for(z=d;;){WN(a[g],z);var p=Ib(b,z),s=0==(p|0);b:do{if(!s){for(var v=p;;){for(var t=v;!(c=a[t+180>>2],0==(c|0));){t=c}t=t+12|0;c=a[t>>2];var u=v+12|0,w=(a[c+236>>2]|0)<(a[a[u>>2]+236>>2]|0);c:do{if(w){for(var A=t,B=c;;){if(WN(a[g],B),A=a[a[a[A>>2]+184>>2]>>2]+12|0,B=a[A>>2],(a[B+236>>2]|0)>=(a[a[u>>2]+236>>2]|0)){break c}}}}while(0);v=yb(b,v);if(0==(v|0)){break b}}}}while(0);z=ba(b,z);if(0==(z|0)){break a}}}}while(0);n=b+32|0;d=(b+220|0)>>2;z=b+12|0;for(k=D[k>>1]<<16>>16;(k|0)<=(D[f]<<16>>16|0);){p=a[a[g]+(k<<2)>>2];s=(a[p+240>>2]<<2)+a[(a[a[n>>2]+220>>2]+4>>2)+(11*k|0)]|0;if((a[s>>2]|0)!=(p|0)){l=1983;break}a[(a[d]+4>>2)+(11*k|0)]=s;p=0;s=-1;a:for(;;){v=a[d];if((p|0)>=(a[(v>>2)+(11*k|0)]|0)){break}v=a[a[(v+4>>2)+(11*k|0)]+(p<<2)>>2];if(0==(v|0)){break}t=0==m[v+162|0]<<24>>24;b:do{if(t){if(0==(Ed(b,v|0)|0)){break a}else{c=p}}else{u=a[a[v+176>>2]>>2];for(c=u>>2;;){if(0==(u|0)){c=s;break b}u=a[c+32];if(0==(u|0)){break}else{c=u>>2}}c=0==(Ed(b,a[c+4]|0)|0)?s:0==(Ed(b,a[c+3]|0)|0)?s:p}}while(0);p=p+1|0;s=c}-1==(s|0)&&la(0,aO|0,(j=h,h+=8,a[j>>2]=a[z>>2],a[j+4>>2]=k,j));a[(a[d]>>2)+(11*k|0)]=s+1|0;k=k+1|0}1983==l&&S();f=b+208|0;if(1<=(a[f>>2]|0)){b=b+212|0;for(l=1;!($w(a[a[b>>2]+(l<<2)>>2]),l=l+1|0,(l|0)>(a[f>>2]|0));){}}h=e}function Aj(c,d){var i=h,g=c,c=h;h+=40;for(var g=g>>2,e=c>>2,j=g+10;g>=2;e=d>>2;for(j=g+10;g>24){return h=i,0!=g<<24>>24&1}if(0==g<<24>>24){return h=i,-1}g=c|0;e=d|0;g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])-(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])&-1;if(0!=(g|0)){return h=i,g}g=c+8|0;e=d+8|0;g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])-(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])&-1;h=i;return g}function YN(b){var c=b>>2,d=b+24|0,g=a[c+44];0!=(g|0)&&G(g);g=a[c+46];0!=(g|0)&&G(g);g=a[c+48];0!=(g|0)&&G(g);g=a[c+50];0!=(g|0)&&G(g);g=a[c+52];0!=(g|0)&&G(g);vh(a[c+30]);c=a[d>>2];if(0!=(c|0)){J[a[a[c+4>>2]+4>>2]](b)}b=d>>2;for(d=b+70;b(a[c>>2]|0),f=b+212|0,e=a[f>>2];a:do{if(g){var h=e}else{for(var k=1,j=e;;){if(j=a[j+(k<<2)>>2],m[j+250|0]=0,ax(j),k=k+1|0,j=a[f>>2],(k|0)>(a[c>>2]|0)){h=j;break a}}}}while(0);0!=(h|0)&&G(h);c=a[b+276>>2];0!=(c|0)&&G(c);c=a[b+224>>2];0!=(c|0)&&G(c);c=(b+220|0)>>2;g=a[c];do{if(0!=(g|0)){f=b+244|0;h=D[f>>1];e=b+246|0;if(h<<16>>16>D[e>>1]<<16>>16){f=h,e=g}else{h=h<<16>>16;for(k=g;;){G(a[(k+12>>2)+(11*h|0)]);h=h+1|0;if((h|0)>(D[e>>1]<<16>>16|0)){break}k=a[c]}f=D[f>>1];e=a[c]}-1==f<<16>>16?G(e-44|0):G(e)}}while(0);if((a[b+32>>2]|0)!=(b|0)){b=d>>2;for(d=b+61;b>2;if(0==(c|0)){var c=a[f+4],e=a[f+3],b=(c|0)!=(e|0)?(a[c+236>>2]|0)==(a[e+236>>2]|0)?2:1:0!=m[b+56|0]<<24>>24?4:0==m[b+96|0]<<24>>24?8:4}else{b=c}a[f+43]=b|g|(0==(d|0)?2==(b|0)?(a[a[f+4]+240>>2]|0)<(a[a[f+3]+240>>2]|0)?16:32:1==(b|0)?(a[a[f+4]+236>>2]|0)<(a[a[f+3]+236>>2]|0)?16:32:16:d)}function bx(c,d){var i,g,e,Xa,l,k,n,z,p,s,v,t=h;h+=388;var u,w=t+184,A=t+368,B=D[c+164>>1]&14;if(0!=(B|0)){Tw(c);var C=h,y=a[km>>2];a[km>>2]=y+1|0;if(0>=(y|0)){var F=Cb(4800);a[Ch>>2]=F;0==(F|0)&&(la(1,bO|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),S());a[Yo>>2]=300;a[ml>>2]=0;a[nl>>2]=0;0!=m[ld]<<24>>24&&mk(ih)}h=C;var E=fa(92),H=a[c+256>>2];a[A+8>>2]=(H|0)/4&-1;var M=A+12|0;a[M>>2]=H;var I=fa(512);v=(A+4|0)>>2;a[v]=0;s=(A|0)>>2;a[s]=0;var O=c+244|0,K=D[O>>1],L=K<<16>>16;p=(c+246|0)>>1;var ia=D[p];if(K<<16>>16>ia<<16>>16){var N=I,Q=0,R=11520,U=L}else{for(var W=c+220|0,Z=I,Y=0,V=0,ca=L,$=a[W>>2],ka=ia;;){z=($+44*ca|0)>>2;var zb=a[z],ja=zb+V|0,aa=$+44*ca+4|0,da=a[a[aa>>2]>>2];if(0==(da|0)){var ea=zb}else{var xa=a[s]|0,qa=da+32|0,na=da+104|0,ha=(b[0]=a[qa>>2],b[1]=a[qa+4>>2],f[0])-(b[0]=a[na>>2],b[1]=a[na+4>>2],f[0]);a[s]=(xa>2]+(ea-1<<2)>>2];if(0!=(ga|0)){var va=a[v]|0,Rb=ga+32|0,Aa=ga+112|0,tb=(b[0]=a[Rb>>2],b[1]=a[Rb+4>>2],f[0])+(b[0]=a[Aa>>2],b[1]=a[Aa+4>>2],f[0]);a[v]=(va>tb?va:tb)&-1}}a[s]=a[s]-16|0;a[v]=a[v]+16|0;if(0<(a[z]|0)){for(var ya=Z,Ba=Y,wa=0,Ab=$;;){var Fa=a[a[(Ab+4>>2)+(11*ca|0)]+(wa<<2)>>2],Ga=a[Fa+128>>2];if(0!=(Ga|0)){n=(Ga+108|0)>>2;var Ea=a[n];if(0==(Ea|0)){sa(cO|0,318,dO|0,eO|0);var ta=a[n]}else{ta=Ea}k=(ta+56|0)>>2;l=(Fa+32|0)>>2;a[k]=a[l];a[k+1]=a[l+1];a[k+2]=a[l+2];a[k+3]=a[l+3];m[a[n]+81|0]=1}var Ka=Fa+162|0;if(0==m[Ka]<<24>>24){u=2106}else{if(0==J[a[ke+4>>2]](Fa)<<24>>24){var za=Ba,ma=ya}else{u=2106}}a:do{if(2106==u){u=0;var pa=Fa+184|0,Ma=a[a[pa>>2]>>2],Ha=0==(Ma|0);b:do{if(Ha){var Ra=ya,Qa=Ba}else{for(var Pa=ya,La=Ba,db=0,Ya=Ma;;){var hb=m[Ya+124|0];if(4==hb<<24>>24||6==hb<<24>>24){var Za=La,ib=Pa}else{lq(Ya,1,16,64);var ab=La+1|0;a[Pa+(La<<2)>>2]=Ya;if(0!=(ab&127|0)){Za=ab,ib=Pa}else{var $a=0==(Pa|0)?Cb((La<<2)+516|0):wb(Pa,(La<<2)+516|0),Za=ab,ib=$a}}var jb=db+1|0,Ca=a[a[pa>>2]+(jb<<2)>>2];if(0==(Ca|0)){Ra=ib;Qa=Za;break b}else{Pa=ib,La=Za,db=jb,Ya=Ca}}}}while(0);var Ia=Fa+192|0,eb=a[Ia>>2],ub=0==(eb|0);b:do{if(ub){var Sa=Qa,mb=Ra}else{var ua=a[eb>>2];if(0==(ua|0)){Sa=Qa,mb=Ra}else{for(var Oa=Ra,Wa=Qa,pb=0,ob=ua;;){lq(ob,2,0,128);var bb=Wa+1|0;a[Oa+(Wa<<2)>>2]=ob;var qb=0==(bb&127|0)?0==(Oa|0)?Cb((Wa<<2)+516|0):wb(Oa,(Wa<<2)+516|0):Oa,Hb=pb+1|0,kb=a[a[Ia>>2]+(Hb<<2)>>2];if(0==(kb|0)){Sa=bb;mb=qb;break b}else{Oa=qb,Wa=bb,pb=Hb,ob=kb}}}}}while(0);var Gb=Fa+208|0,vb=a[Gb>>2];if(0==(vb|0)){za=Sa,ma=mb}else{if(0==m[Ka]<<24>>24){Xa=(Fa+112|0)>>2;var xb=(b[0]=a[Xa],b[1]=a[Xa+1],f[0]),Jb=Fa+244|0;f[0]=a[Jb>>2]|0;a[Xa]=b[0];a[Xa+1]=b[1];a[Jb>>2]=xb&-1}var nb=a[vb>>2];if(0==(nb|0)){za=Sa,ma=mb}else{for(var rb=mb,lb=Sa,Ta=0,cb=nb;;){lq(cb,0,0,128);var fb=lb+1|0;a[rb+(lb<<2)>>2]=cb;var Ua=0==(fb&127|0)?0==(rb|0)?Cb((lb<<2)+516|0):wb(rb,(lb<<2)+516|0):rb,sb=Ta+1|0,Na=a[a[Gb>>2]+(sb<<2)>>2];if(0==(Na|0)){za=fb;ma=Ua;break a}else{rb=Ua,lb=fb,Ta=sb,cb=Na}}}}}}while(0);var Fb=wa+1|0,Db=a[W>>2];if((Fb|0)<(a[(Db>>2)+(11*ca|0)]|0)){ya=ma,Ba=za,wa=Fb,Ab=Db}else{break}}var Ob=ma,Eb=za,Kb=Db,Bb=D[p]}else{Ob=Z,Eb=Y,Kb=$,Bb=ka}var Ja=ca+1|0;if((Ja|0)>(Bb<<16>>16|0)){break}else{Z=Ob,Y=Eb,V=ja,ca=Ja,$=Kb,ka=Bb}}N=Ob;Q=Eb;R=(ja<<5)+11520|0;U=Ja}var Lb=N;ch(Lb,Q,322);var Mb=E+84|0;a[Mb>>2]=fa(R);var Nb=A+16|0;a[Nb>>2]=fa(U<<5);var Pb=2==(B|0);a:do{if(Pb){var Wb=a[c+216>>2];if(0!=(Wb|0)){for(var Tb=Wb;;){1==m[Tb+162|0]<<24>>24&&0!=(a[Tb+120>>2]|0)&&fO(Tb);var ic=a[Tb+168>>2];if(0==(ic|0)){break a}else{Tb=ic}}}}}while(0);var Yb=0<(Q|0);a:do{if(Yb){var Sb=w|0,$b=w+16|0,Zb=w+12|0,ec=w+28|0,pc=w+68|0,dc=w+124|0,Vb=w+128|0;e=(c+220|0)>>2;for(var Xb=N,kc=c,rc=t|0,ac=t+16|0,nc=t+12|0,gc=t+28|0,lc=t+68|0,uc=t+124|0,qc=t+128|0,bc=0;;){var Hc=a[N+(bc<<2)>>2],jc=lm(Hc),fc=0==m[Hc+56|0]<<24>>24?0==m[Hc+96|0]<<24>>24?jc:Hc:Hc;g=fc>>2;if(0==(a[g+43]&32|0)){var mc=fc}else{for(var Qb=(fc|0)>>2,tc=rc>>2,Sc=Qb+46;Qb>2]=a[g+3];a[nc>>2]=a[g+4];Qb=(fc+68|0)>>2;tc=gc>>2;for(Sc=Qb+10;Qb>2;tc=lc>>2;for(Sc=Qb+10;Qb>2]=fc;mc=t}for(var Ac=mc+68|0,Cc=Hc+108|0,Ec=Hc+125|0,Ic=mc+28|0,oc=Hc+172|0,Fc=bc,vc=1;;){var Dc=Fc+1|0,Kc=(Dc|0)<(Q|0);if(!Kc){break}var Bc=(Dc<<2)+N|0,zc=a[Bc>>2];if((jc|0)!=(lm(zc)|0)){break}if(0==m[Ec]<<24>>24){var fd=0==m[zc+56|0]<<24>>24?0==m[zc+96|0]<<24>>24?jc:zc:zc;i=fd>>2;if(0==(a[i+43]&32|0)){var Gc=fd}else{Qb=(fd|0)>>2;tc=Sb>>2;for(Sc=Qb+46;Qb>2]=a[i+3];a[Zb>>2]=a[i+4];Qb=(fd+68|0)>>2;tc=ec>>2;for(Sc=Qb+10;Qb>2;tc=pc>>2;for(Sc=Qb+10;Qb>2]=fd;Gc=w}if(0!=(Aj(Ic,Gc+28|0)|0)){break}if(0!=(Aj(Ac,Gc+68|0)|0)){break}if(2==(a[oc>>2]&15|0)&&(a[Cc>>2]|0)!=(a[zc+108>>2]|0)){break}if(0!=(a[a[Bc>>2]+172>>2]&64|0)){break}}Fc=Dc;vc=vc+1|0}var yc=a[Hc+16>>2],Nc=a[Hc+12>>2],Yc=(yc|0)==(Nc|0),Tc=a[yc+236>>2];b:do{if(Yc){if((Tc|0)==(D[p]<<16>>16|0)){if(0<(Tc|0)){var Lc=a[a[(a[e]+4>>2)+(11*(Tc-1)|0)]>>2]+40|0,Mc=yc+40|0,id=(b[0]=a[Lc>>2],b[1]=a[Lc+4>>2],f[0])-(b[0]=a[Mc>>2],b[1]=a[Mc+4>>2],f[0])&-1}else{var Oc=yc+96|0,id=(b[0]=a[Oc>>2],b[1]=a[Oc+4>>2],f[0])&-1}}else{if((Tc|0)==(D[O>>1]<<16>>16|0)){var Pc=yc+40|0,Xc=a[a[(a[e]+4>>2)+(11*(Tc+1)|0)]>>2]+40|0,id=(b[0]=a[Pc>>2],b[1]=a[Pc+4>>2],f[0])-(b[0]=a[Xc>>2],b[1]=a[Xc+4>>2],f[0])&-1}else{var Qc=a[e],Rc=a[a[(Qc+4>>2)+(11*(Tc-1)|0)]>>2]+40|0,Jc=(b[0]=a[Rc>>2],b[1]=a[Rc+4>>2],f[0]),Vc=yc+40|0,Zc=(b[0]=a[Vc>>2],b[1]=a[Vc+4>>2],f[0]),ad=Jc-Zc&-1,$c=a[a[(Qc+4>>2)+(11*(Tc+1)|0)]>>2]+40|0,bd=Zc-(b[0]=a[$c>>2],b[1]=a[$c+4>>2],f[0])&-1,id=(ad|0)<(bd|0)?ad:bd}}UH(0,Xb,bc,vc,a[M>>2]|0,(id|0)/2&-1|0,ke);if(0<(vc|0)){for(var cd=0;;){var ed=a[a[N+(cd+bc<<2)>>2]+108>>2];0!=(ed|0)&&al(kc,ed);var pd=cd+1|0;if((pd|0)==(vc|0)){break b}else{cd=pd}}}}else{(Tc|0)==(a[Nc+236>>2]|0)?gO(A,E,N,bc,vc,B):hO(A,E,N,bc,vc,B)}}while(0);if(Kc){bc=Dc}else{break a}}}}while(0);var jd=a[c+216>>2],md=0==(jd|0);a:do{if(!md){for(var qd=c,dd=jd;;){if(1==m[dd+162|0]<<24>>24){var od=dd+120|0;0!=(a[od>>2]|0)&&(fO(dd),al(qd,a[od>>2]))}var Uc=a[dd+168>>2];if(0==(Uc|0)){break a}else{dd=Uc}}}}while(0);if(0!=(d|0)){var yd=ra(c);if(0!=(yd|0)){for(var sd=yd;;){var Bd=Ib(c,sd),Fd=0==(Bd|0);a:do{if(!Fd){for(var vd=Bd;;){if(0!=J[a[ke>>2]](vd)<<24>>24){var kd=a[vd+24>>2];if(0!=(kd|0)){var Md=kd,td=cc,Cd=a[Md+4>>2],Gd=Cb(48*Cd|0),td=(Md|0)>>2;if(0<(Cd|0)){for(var Ld=Gd,nd=a[td]+48*(Cd-1)|0,zd=0;;){var wd=nd,xd=Ld,Ed=cc,Wd=cc,we=cc,pe=cc,Hd=cc,Kd=cc,Dd=a[wd+4>>2],Yd=Cb(Dd<<4),$d=0<(Dd|0);b:do{if($d){for(var Ye=Yd,Ad=(Dd-1<<4)+a[wd>>2]|0,Ud=0;;){Kd=Ye>>2;Hd=Ad>>2;a[Kd]=a[Hd];a[Kd+1]=a[Hd+1];a[Kd+2]=a[Hd+2];a[Kd+3]=a[Hd+3];var Vd=Ud+1|0;if((Vd|0)==(Dd|0)){break b}else{Ye=Ye+16|0,Ad=Ad-16|0,Ud=Vd}}}}while(0);a[xd>>2]=Yd;a[xd+4>>2]=Dd;a[xd+8>>2]=a[wd+12>>2];a[xd+12>>2]=a[wd+8>>2];pe=(xd+16|0)>>2;we=(wd+32|0)>>2;a[pe]=a[we];a[pe+1]=a[we+1];a[pe+2]=a[we+2];a[pe+3]=a[we+3];Wd=(xd+32|0)>>2;Ed=(wd+16|0)>>2;a[Wd]=a[Ed];a[Wd+1]=a[Ed+1];a[Wd+2]=a[Ed+2];a[Wd+3]=a[Ed+3];var be=zd+1|0;if((be|0)==(Cd|0)){break}else{Ld=Ld+48|0,nd=nd-48|0,zd=be}}for(var Pd=0,gd=a[td];;){G(a[(gd>>2)+(12*Pd|0)]);var ud=Pd+1|0,de=a[td];if((ud|0)==(Cd|0)){Id=de;break}else{Pd=ud,gd=de}}le=Id}else{var Id=a[td],le=Id}G(le);a[td]=Gd}}var ae=yb(c,vd);if(0==(ae|0)){break a}else{vd=ae}}}}while(0);var Nd=ba(c,sd);if(0==(Nd|0)){break}else{sd=Nd}}}}var ce=0!=(a[Zk>>2]|0)|0!=(a[$k>>2]|0);a:do{if(ce){var Qd=c,se=ra(Qd);if(0!=(se|0)){for(var Ae=c,he=se;;){var Ee=0==(a[Zk>>2]|0);b:do{if(!Ee){var xe=Og(Qd,he);if(0!=(xe|0)){for(var qe=xe;;){var re=qe+112|0;0!=(a[re>>2]|0)&&(Eu(qe,1),al(Ae,a[re>>2]));var Ce=Ql(Qd,qe);if(0==(Ce|0)){break b}else{qe=Ce}}}}}while(0);var Ge=0==(a[$k>>2]|0);b:do{if(!Ge){var ue=Ib(Qd,he);if(0!=(ue|0)){for(var ge=ue;;){var ze=ge+116|0;0!=(a[ze>>2]|0)&&(Eu(ge,0),al(Ae,a[ze>>2]));var De=yb(Qd,ge);if(0==(De|0)){break b}else{ge=De}}}}}while(0);var me=ba(Qd,he);if(0==(me|0)){break a}else{he=me}}}}}while(0);G(Lb);G(a[Mb>>2]);G(E);G(a[Nb>>2]);var te=h,Be=a[km>>2]-1|0;a[km>>2]=Be;if(0>=(Be|0)&&(G(a[Ch>>2]),0!=m[ld]<<24>>24)){var He=a[oa>>2],Ie=a[ml>>2],Ke=a[nl>>2],Le=Jn();Va(He,iO|0,(j=h,h+=16,a[j>>2]=Ie,a[j+4>>2]=Ke,f[0]=Le,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j))}h=te;a[Ah>>2]=1}h=t}function fO(c){var d;if(0!=(a[c+180>>2]|0)){for(d=a[c+184>>2];;){var i=a[d>>2];if(0==m[i+124|0]<<24>>24){break}else{d=i+128|0}}d=(i+108|0)>>2;var i=a[d],g=0==(a[a[c+20>>2]+152>>2]&1|0)?i+24|0:i+32|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),e=c+32|0,g=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])+.5*g,i=i+56|0;f[0]=g;a[i>>2]=b[0];a[i+4>>2]=b[1];c=c+40|0;c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]);i=a[d]+64|0;f[0]=c;a[i>>2]=b[0];a[i+4>>2]=b[1];m[a[d]+81|0]=1}}function lm(b){for(;;){var c=a[b+180>>2];if(0==(c|0)){var d=b;break}else{b=c}}for(;!(b=a[d+128>>2],0==(b|0));){d=b}return d}function jO(c,d){var i,g,e,j,l=h;h+=368;j=l>>2;var k=l+184;e=k>>2;var n=a[c>>2],z=a[d>>2],p=a[n+172>>2],s=p&15,v=a[z+172>>2],t=v&15;if((s|0)!=(t|0)){return h=l,t-s|0}t=lm(n);g=t>>2;s=lm(z);i=s>>2;var u=a[g+4],w=a[g+3],A=a[u+236>>2]-a[w+236>>2]|0,B=a[i+4],C=a[i+3],y=a[B+236>>2]-a[C+236>>2]|0,A=-1<(A|0)?A:-A|0,y=-1<(y|0)?y:-y|0;if((A|0)!=(y|0)){return h=l,A-y|0}u=u+32|0;w=w+32|0;w=(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0])-(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0]);B=B+32|0;C=C+32|0;w&=-1;w=-1<(w|0)?w:-w|0;C=(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0])-(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0])&-1;C=-1<(C|0)?C:-C|0;if((w|0)!=(C|0)){return h=l,w-C|0}g=a[g+5];i=a[i+5];if((g|0)!=(i|0)){return h=l,g-i|0}0==m[n+56|0]<<24>>24?(t=i=0==m[n+96|0]<<24>>24?t:n,i=a[i+172>>2]):(t=n,i=p);if(0==(i&32|0)){j=t}else{i=(t|0)>>2;g=(l|0)>>2;for(C=i+46;i>2];a[j+3]=a[t+16>>2];i=(t+68|0)>>2;g=(l+28|0)>>2;for(C=i+10;i>2;g=(l+68|0)>>2;for(C=i+10;i>24?(s=t=0==m[z+96|0]<<24>>24?s:z,t=a[t+172>>2]):(s=z,t=v);if(0==(t&32|0)){e=s}else{i=(s|0)>>2;g=(k|0)>>2;for(C=i+46;i>2];a[e+3]=a[s+16>>2];i=(s+68|0)>>2;g=(k+28|0)>>2;for(C=i+10;i>2;g=(k+68|0)>>2;for(C=i+10;i>2]-a[z+20>>2]|0:p-v|0;h=l;return n}function gO(c,d,i,g,e,j){var l,k,n,z=h;h+=1580;var p;n=z>>2;var s=z+184,v=z+188,t=z+884,u=a[i+(g<<2)>>2];k=u>>2;if(0==(a[k+43]&32|0)){n=u}else{var w=(u|0)>>2;l=(z|0)>>2;for(var A=w+46;w>2;l=(z+28|0)>>2;for(A=w+10;w>2;l=(z+68|0)>>2;for(A=w+10;w>2;if(0!=m[u+125|0]<<24>>24){kO(i,g,e,a[k+3],a[k+4],j)}else{if(0!=(a[k+27]|0)){lO(c,d,n,j)}else{if(2==(j|0)){cx(a[k+4],a[k+3],i,g,e,2)}else{u=m[n+61|0];w=m[n+101|0];do{if(1!=u<<24>>24|4==w<<24>>24&&1!=w<<24>>24|4==u<<24>>24){var B=a[k+4],C=a[k+3],y=a[B+20>>2],A=a[B+236>>2];if(0<(A|0)){l=a[y+220>>2]>>2;var y=(0==(m[y+149|0]&1)<<24>>24?-1:-2)+A|0,D=a[a[l+(11*y|0)+1]>>2]+40|0,F=B+40|0,A=(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0])-(a[l+(11*y|0)+4]|0)-(b[0]=a[F>>2],b[1]=a[F+4>>2],f[0])-(a[l+(11*A|0)+5]|0)}else{A=a[y+260>>2]|0}y=e+1|0;l=(a[c+12>>2]|0)/y;A/=y;mm(c,d,B,n,v,1);mm(c,d,C,n,t,0);for(var B=v+52|0,C=t+52|0,y=8==(j|0),D=d+80|0,E=0;;){if((E|0)>=(e|0)){p=2272;break}var F=a[i+(E+g<<2)>>2],M=a[B>>2],G=M-1|0,O=(G<<5)+v+56|0,O=(b[0]=a[O>>2],b[1]=a[O+4>>2],f[0]),H=(G<<5)+v+72|0,H=(b[0]=a[H>>2],b[1]=a[H+4>>2],f[0]),G=(G<<5)+v+80|0,I=(b[0]=a[G>>2],b[1]=a[G+4>>2],f[0]);f[0]=O;a[Z>>2]=b[0];a[Z+4>>2]=b[1];f[0]=I;a[Z+8>>2]=b[0];a[Z+12>>2]=b[1];var E=E+1|0,J=E|0,G=J*l;f[0]=H+G;a[Z+16>>2]=b[0];a[Z+20>>2]=b[1];H=I+J*A;f[0]=H;a[Z+24>>2]=b[0];a[Z+28>>2]=b[1];f[0]=O;a[Z+32>>2]=b[0];a[Z+36>>2]=b[1];f[0]=H;a[Z+40>>2]=b[0];a[Z+44>>2]=b[1];I=a[C>>2]-1|0;O=(I<<5)+t+72|0;O=(b[0]=a[O>>2],b[1]=a[O+4>>2],f[0]);f[0]=O;a[Z+48>>2]=b[0];a[Z+52>>2]=b[1];f[0]=H+A;a[Z+56>>2]=b[0];a[Z+60>>2]=b[1];J=(I<<5)+t+56|0;J=(b[0]=a[J>>2],b[1]=a[J+4>>2],f[0]);I=(I<<5)+t+80|0;I=(b[0]=a[I>>2],b[1]=a[I+4>>2],f[0]);f[0]=O;a[Z+80>>2]=b[0];a[Z+84>>2]=b[1];f[0]=I;a[Z+72>>2]=b[0];a[Z+76>>2]=b[1];f[0]=J-G;a[Z+64>>2]=b[0];a[Z+68>>2]=b[1];f[0]=H;a[Z+88>>2]=b[0];a[Z+92>>2]=b[1];M=0<(M|0);a:do{if(M){for(G=0;;){if(bd(d,(G<<5)+v+56|0),G=G+1|0,(G|0)>=(a[B>>2]|0)){break a}}}}while(0);bd(d,Z|0);bd(d,Z+32|0);bd(d,Z+64|0);M=a[C>>2];G=0<(M|0);a:do{if(G){for(O=M;;){if(O=O-1|0,bd(d,(O<<5)+t+56|0),0>=(O|0)){break a}}}}while(0);M=y?ff(d,s,0):ff(d,s,1);G=a[s>>2];if(0==(G|0)){p=2276;break}Wd(F,a[F+12>>2],M,G,ke);a[D>>2]=0}if(2272==p){h=z;return}if(2276==p){h=z;return}}}while(0);mO(c,d,i,g,e,n,8==(j|0)&1)}}}h=z}function hO(c,d,i,g,e,j){var l,k,n,z,p,s,v,t,u,w,A,B,C,y,D,F,E,M,G,O,H,I,K,L,N,Q,S,R,U,W,Y,ca,V,$,ba,ja,aa,da,ea,xa,fa,la,ha,ga,ka,sa,ra,qa,ya,na,wa,Ab,Fa,Ga=h;h+=2276;var oa;Fa=Ga>>2;var ta=Ga+4;Ab=ta>>2;var Ka=Ga+188;wa=Ka>>2;var za=Ga+372,ma=Ga+556,pa=Ga+1252,va=Ga+1948,Ha=Ga+1980;na=Ha>>2;var Ra=Ga+1984;ya=Ra>>2;var Ba=Ga+1988,Aa=Ga+2020,La=Ga+2052,Ea=Ga+2084,Ya=Ga+2116,Ma=Ga+2148,Za=Ga+2180,Qa=Ga+2212,ab=Ga+2244;if(0==(a[le>>2]|0)){var $a=Cb(32e3);a[le>>2]=$a;var jb=Cb(32e3);a[Dj>>2]=jb;a[ai>>2]=2e3;a[mq>>2]=2e3}var Ca=a[i+(g<<2)>>2];qa=(Ca+16|0)>>2;var Ia=a[qa],eb=a[Ia+20>>2];ra=(Ca+12|0)>>2;var ub=a[ra],Sa=a[Ia+236>>2]-a[ub+236>>2]|0;do{if(1<((-1<(Sa|0)?Sa:-Sa|0)|0)){for(var Pa=Ca|0,ua=Pa>>2,Oa=(ta|0)>>2,Wa=ua+46;ua>2]&32|0),ob=Ka|0,ua=Pa>>2,Oa=ob>>2,Wa=ua+46;ua>2,Oa=db>>2,Wa=ua+10;ua>2,Oa=hb>>2,Wa=ua+10;ua>2,Oa=xb>>2,Wa=ua+10;ua>2];if(0==(nb|0)){break}else{ib=nb}}var rb=a[ib+12>>2];a[Ab+3]=rb;m[ta+96|0]=0;m[ta+124|0]=1;sa=(ta+68|0)>>2;a[sa]=0;a[sa+1]=0;a[sa+2]=0;a[sa+3]=0;a[Ab+32]=Ca;var lb=1,Ta=ta,cb=qb,fb=rb}else{if(0==(a[Ca+172>>2]&32|0)){lb=0,Ta=Ca,cb=Ia,fb=ub}else{for(var Ua=ta|0,ua=(Ca|0)>>2,Oa=Ua>>2,Wa=ua+46;ua>2,Oa=Fb>>2,Wa=ua+10;ua>2,Oa=Db>>2,Wa=ua+10;ua>2],Ga);a[ya]=Eb;if(0==(Eb|0)){var Va=a[Ta+16>>2],Bb=a[Ta+12>>2];oa=2291}}else{Va=cb,Bb=fb,oa=2291}if(2291==oa){var Ja=8==(j|0);a[ya]=0;a[Fa]=Bb;Sg(Ba,c,Va,0,Ta);ka=ma>>2;ga=Ba>>2;a[ka]=a[ga];a[ka+1]=a[ga+1];a[ka+2]=a[ga+2];a[ka+3]=a[ga+3];a[ka+4]=a[ga+4];a[ka+5]=a[ga+5];a[ka+6]=a[ga+6];a[ka+7]=a[ga+7];ha=va>>2;a[ha]=a[ga];a[ha+1]=a[ga+1];a[ha+2]=a[ga+2];a[ha+3]=a[ga+3];a[ha+4]=a[ga+4];a[ha+5]=a[ga+5];a[ha+6]=a[ga+6];a[ha+7]=a[ga+7];vl(d,Ta,1,ma,nm(Va));la=(ma+52|0)>>2;var mb=a[la]-1|0,yb=(mb<<5)+ma+80|0,Hb=(b[0]=a[yb>>2],b[1]=a[yb+4>>2],f[0]);fa=(va+24|0)>>2;f[0]=Hb;a[fa]=b[0];a[fa+1]=b[1];var Gb=(mb<<5)+ma+64|0,Jb=(b[0]=a[Gb>>2],b[1]=a[Gb+4>>2],f[0]);xa=(va+8|0)>>2;f[0]=Jb;a[xa]=b[0];a[xa+1]=b[1];var Kb=Va+40|0;bi(Aa,va,1,(b[0]=a[Kb>>2],b[1]=a[Kb+4>>2],f[0])-a[(a[a[Va+20>>2]+220>>2]+16>>2)+(11*a[Va+236>>2]|0)]&-1);ea=Aa>>2;a[ha]=a[ea];a[ha+1]=a[ea+1];a[ha+2]=a[ea+2];a[ha+3]=a[ea+3];a[ha+4]=a[ea+4];a[ha+5]=a[ea+5];a[ha+6]=a[ea+6];a[ha+7]=a[ea+7];da=(va|0)>>2;aa=(va+16|0)>>2;if((b[0]=a[da],b[1]=a[da+1],f[0])<(b[0]=a[aa],b[1]=a[aa+1],f[0])){if((b[0]=a[xa],b[1]=a[xa+1],f[0])<(b[0]=a[fa],b[1]=a[fa+1],f[0])){var Ib=a[la];a[la]=Ib+1|0;ja=((Ib<<5)+ma+56|0)>>2;a[ja]=a[ha];a[ja+1]=a[ha+1];a[ja+2]=a[ha+2];a[ja+3]=a[ha+3];a[ja+4]=a[ha+4];a[ja+5]=a[ha+5];a[ja+6]=a[ha+6];a[ja+7]=a[ha+7]}}ba=La>>2;var Lb=eb+149|0;$=Ea>>2;V=pa>>2;ca=Ya>>2;Y=(pa+52|0)>>2;var Mb=d+56|0,Nb=d+69|0;W=Ma>>2;var Pb=d+16|0,Tb=d+29|0,Wb=-1,Yb=0,Vb=Va,Sb=Ta,$b=Bb;a:for(;;){for(var Zb=0,bc=Wb,ec=Yb,fc=Vb,lc=Sb,dc=0,Xb=$b;;){if(1!=m[Xb+162|0]<<24>>24){break a}if(0!=J[a[ke+4>>2]](Xb)<<24>>24){break a}var kc=dc|1,rc=(dc<<5)+Z|0;dx(La,c,eb,a[fc+236>>2]);U=rc>>2;a[U]=a[ba];a[U+1]=a[ba+1];a[U+2]=a[ba+2];a[U+3]=a[ba+3];a[U+4]=a[ba+4];a[U+5]=a[ba+5];a[U+6]=a[ba+6];a[U+7]=a[ba+7];if(0==(Zb|0)){var jc;for(var ac=cc,nc=Xb+32|0,Qb=Xb,tc=0;;){var gc=a[a[a[Qb+184>>2]>>2]+12>>2];if(1!=m[gc+162|0]<<24>>24){ac=2393;break}if(1!=(a[gc+188>>2]|0)){ac=2396;break}if(1!=(a[gc+180>>2]|0)){ac=2394;break}var uc=gc+32|0;if((b[0]=a[uc>>2],b[1]=a[uc+4>>2],f[0])!=(b[0]=a[nc>>2],b[1]=a[nc+4>>2],f[0])){ac=2395;break}else{Qb=gc,tc=tc+1|0}}jc=2396==ac||2393==ac||2395==ac||2394==ac?tc:cc;var mc=(jc|0)<((0!=(m[Lb]&1)<<24>>24?5:3)|0),qc=mc&1^1,vc=mc?bc:1,oc=mc?jc:jc-2|0}else{qc=Zb,vc=bc,oc=ec}if(!(0==(qc|0)|0<(vc|0))){break}var Ac=(kc<<5)+Z|0;Sg(Ea,c,Xb,lc,a[a[Xb+184>>2]>>2]);R=Ac>>2;a[R]=a[$];a[R+1]=a[$+1];a[R+2]=a[$+2];a[R+3]=a[$+3];a[R+4]=a[$+4];a[R+5]=a[$+5];a[R+6]=a[$+6];a[R+7]=a[$+7];var zc=a[a[a[Fa]+184>>2]>>2],Dc=a[zc+16>>2],Cc=a[zc+12>>2];a[Fa]=Cc;Zb=qc;bc=vc-1|0;ec=oc;fc=Dc;lc=zc;dc=dc+2|0;Xb=Cc}Sg(Ya,c,Xb,lc,a[a[Xb+184>>2]>>2]);a[V]=a[ca];a[V+1]=a[ca+1];a[V+2]=a[ca+2];a[V+3]=a[ca+3];a[V+4]=a[ca+4];a[V+5]=a[ca+5];a[V+6]=a[ca+6];a[V+7]=a[ca+7];xl(d,lc,1,pa,nm(a[lc+12>>2]));var Bc=a[Fa],Ec=Bc+40|0;bi(va,(a[Y]-1<<5)+pa+56|0,4,(b[0]=a[Ec>>2],b[1]=a[Ec+4>>2],f[0])+a[(a[a[Bc+20>>2]+220>>2]+20>>2)+(11*a[Bc+236>>2]|0)]&-1);if((b[0]=a[da],b[1]=a[da+1],f[0])<(b[0]=a[aa],b[1]=a[aa+1],f[0])){if((b[0]=a[xa],b[1]=a[xa+1],f[0])<(b[0]=a[fa],b[1]=a[fa+1],f[0])){var Fc=a[Y];a[Y]=Fc+1|0;S=((Fc<<5)+pa+56|0)>>2;a[S]=a[ha];a[S+1]=a[ha+1];a[S+2]=a[ha+2];a[S+3]=a[ha+3];a[S+4]=a[ha+4];a[S+5]=a[ha+5];a[S+6]=a[ha+6];a[S+7]=a[ha+7]}}f[0]=1.5707963267948966;a[Mb>>2]=b[0];a[Mb+4>>2]=b[1];m[Nb]=1;ex(d,Sb,lc,ma,pa,kc);if(Ja){var Ic=ff(d,Ha,0);oa=2310}else{var yc=ff(d,Ha,1);if(Ob){var Gc=a[na];if(4<(Gc|0)){Q=(yc+16|0)>>2;N=yc>>2;a[Q]=a[N];a[Q+1]=a[N+1];a[Q+2]=a[N+2];a[Q+3]=a[N+3];L=(yc+32|0)>>2;var Kc=(Gc-1<<4)+yc|0;K=Kc>>2;a[L]=a[K];a[L+1]=a[K+1];a[L+2]=a[K+2];a[L+3]=a[K+3];ck(yc+48|0,Kc);var Tc=a[na]=4,Nc=yc}else{var Mc=yc,Lc=Gc;oa=2311}}else{Ic=yc,oa=2310}}2310==oa&&(oa=0,Mc=Ic,Lc=a[na],oa=2311);if(2311==oa){if(oa=0,0==(Lc|0)){oa=2367;break}else{Tc=Lc,Nc=Mc}}var Oc=a[ya],Pc=Oc+Tc|0;if((Pc|0)>(a[ai>>2]|0)){a[ai>>2]=Pc<<1;var Xc=wb(a[le>>2],Pc<<5),Qc=a[le>>2]=Xc}else{Qc=a[le>>2]}if(0<(Tc|0)){for(var Yc=1<(Tc|0)?Tc:1,Rc=0,Vc=Oc;;){I=((Vc<<4)+Qc|0)>>2;H=((Rc<<4)+Nc|0)>>2;a[I]=a[H];a[I+1]=a[H+1];a[I+2]=a[H+2];a[I+3]=a[H+3];var Zc=Rc+1|0;if((Zc|0)<(Tc|0)){Rc=Zc,Vc=Vc+1|0}else{break}}a[ya]=Oc+Yc|0}var Jc,ad=a[a[a[Fa]+184>>2]>>2],cd=Qc,$c=cc,bd=cc,dd=cc,jd=cc,md=cc,ed=Ra>>2,ld=a[ed],pd=0==(oc|0);b:do{if(pd){var Uc=ad}else{for(var od=oc,qd=ad;;){var sd=od-1|0,td=a[a[a[qd+12>>2]+184>>2]>>2];if(0==(sd|0)){Uc=td;break b}else{od=sd,qd=td}}}}while(0);a[ed]=ld+1|0;md=((ld<<4)+cd|0)>>2;jd=((ld-1<<4)+cd|0)>>2;a[md]=a[jd];a[md+1]=a[jd+1];a[md+2]=a[jd+2];a[md+3]=a[jd+3];var vd=a[ed];a[ed]=vd+1|0;dd=((vd<<4)+cd|0)>>2;a[dd]=a[jd];a[dd+1]=a[jd+1];a[dd+2]=a[jd+2];a[dd+3]=a[jd+3];bd=((a[ed]<<4)+cd|0)>>2;$c=(a[Uc+16>>2]+32|0)>>2;a[bd]=a[$c];a[bd+1]=a[$c+1];a[bd+2]=a[$c+2];a[bd+3]=a[$c+3];Jc=Uc;fx(Sb,d);var kd=a[Jc+16>>2];O=kd>>2;var wd=a[Jc+12>>2];a[Fa]=wd;Sg(Ma,c,kd,a[a[O+44]>>2],Jc);a[ka]=a[W];a[ka+1]=a[W+1];a[ka+2]=a[W+2];a[ka+3]=a[W+3];a[ka+4]=a[W+4];a[ka+5]=a[W+5];a[ka+6]=a[W+6];a[ka+7]=a[W+7];vl(d,Jc,1,ma,nm(kd));var xd=kd+40|0;bi(va,(a[la]-1<<5)+ma+56|0,1,(b[0]=a[xd>>2],b[1]=a[xd+4>>2],f[0])-a[(a[a[O+5]+220>>2]+16>>2)+(11*a[O+59]|0)]&-1);if((b[0]=a[da],b[1]=a[da+1],f[0])<(b[0]=a[aa],b[1]=a[aa+1],f[0])){if((b[0]=a[xa],b[1]=a[xa+1],f[0])<(b[0]=a[fa],b[1]=a[fa+1],f[0])){var yd=a[la];a[la]=yd+1|0;G=((yd<<5)+ma+56|0)>>2;a[G]=a[ha];a[G+1]=a[ha+1];a[G+2]=a[ha+2];a[G+3]=a[ha+3];a[G+4]=a[ha+4];a[G+5]=a[ha+5];a[G+6]=a[ha+6];a[G+7]=a[ha+7]}}f[0]=-1.5707963267948966;a[Pb>>2]=b[0];a[Pb+4>>2]=b[1];m[Tb]=1;Wb=vc;Yb=oc;Vb=kd;Sb=Jc;$b=wd}if(2367==oa){h=Ga;return}var Bd=dc|1,Cd=(dc<<5)+Z|0;dx(Za,c,eb,a[fc+236>>2]);M=Cd>>2;E=Za>>2;a[M]=a[E];a[M+1]=a[E+1];a[M+2]=a[E+2];a[M+3]=a[E+3];a[M+4]=a[E+4];a[M+5]=a[E+5];a[M+6]=a[E+6];a[M+7]=a[E+7];Sg(Qa,c,Xb,lc,0);F=Qa>>2;a[V]=a[F];a[V+1]=a[F+1];a[V+2]=a[F+2];a[V+3]=a[F+3];a[V+4]=a[F+4];a[V+5]=a[F+5];a[V+6]=a[F+6];a[V+7]=a[F+7];a[ha]=a[F];a[ha+1]=a[F+1];a[ha+2]=a[F+2];a[ha+3]=a[F+3];a[ha+4]=a[F+4];a[ha+5]=a[F+5];a[ha+6]=a[F+6];a[ha+7]=a[F+7];var nd=0!=(lb|0),zd=lc+12|0;xl(d,nd?Ka:lc,1,pa,nm(a[zd>>2]));var Ed=a[Y]-1|0,Fd=(Ed<<5)+pa+80|0,Ld=(b[0]=a[Fd>>2],b[1]=a[Fd+4>>2],f[0]);f[0]=Ld;a[fa]=b[0];a[fa+1]=b[1];var Hd=(Ed<<5)+pa+64|0,Md=(b[0]=a[Hd>>2],b[1]=a[Hd+4>>2],f[0]);f[0]=Md;a[xa]=b[0];a[xa+1]=b[1];var Gd=a[Fa],Qd=Gd+40|0;bi(ab,va,4,(b[0]=a[Qd>>2],b[1]=a[Qd+4>>2],f[0])+a[(a[a[Gd+20>>2]+220>>2]+20>>2)+(11*a[Gd+236>>2]|0)]&-1);D=ab>>2;a[ha]=a[D];a[ha+1]=a[D+1];a[ha+2]=a[D+2];a[ha+3]=a[D+3];a[ha+4]=a[D+4];a[ha+5]=a[D+5];a[ha+6]=a[D+6];a[ha+7]=a[D+7];if((b[0]=a[da],b[1]=a[da+1],f[0])<(b[0]=a[aa],b[1]=a[aa+1],f[0])){if((b[0]=a[xa],b[1]=a[xa+1],f[0])<(b[0]=a[fa],b[1]=a[fa+1],f[0])){var Kd=a[Y];a[Y]=Kd+1|0;y=((Kd<<5)+pa+56|0)>>2;a[y]=a[ha];a[y+1]=a[ha+1];a[y+2]=a[ha+2];a[y+3]=a[ha+3];a[y+4]=a[ha+4];a[y+5]=a[ha+5];a[y+6]=a[ha+6];a[y+7]=a[ha+7]}}ex(d,Sb,lc,ma,pa,Bd);var Dd=Ja?ff(d,Ha,0):ff(d,Ha,1),Pd=a[na];if(Ob&4<(Pd|0)){C=(Dd+16|0)>>2;B=Dd>>2;a[C]=a[B];a[C+1]=a[B+1];a[C+2]=a[B+2];a[C+3]=a[B+3];A=(Dd+32|0)>>2;var Yd=(Pd-1<<4)+Dd|0;w=Yd>>2;a[A]=a[w];a[A+1]=a[w+1];a[A+2]=a[w+2];a[A+3]=a[w+3];ck(Dd+48|0,Yd);var Id=a[na]=4}else{if(0!=(Pd|0)){Id=Pd}else{h=Ga;return}}var Ad=a[ya],Ud=Ad+Id|0;if((Ud|0)>(a[ai>>2]|0)){a[ai>>2]=Ud<<1;var Vd=wb(a[le>>2],Ud<<5);a[le>>2]=Vd}if(0<(Id|0)){for(var be=a[le>>2],de=1<(Id|0)?Id:1,gd=0,ud=Ad;;){u=((ud<<4)+be|0)>>2;t=((gd<<4)+Dd|0)>>2;a[u]=a[t];a[u+1]=a[t+1];a[u+2]=a[t+2];a[u+3]=a[t+3];var $d=gd+1|0;if(($d|0)<(Id|0)){gd=$d,ud=ud+1|0}else{break}}a[ya]=Ad+de|0}fx(Sb,d);a[Fa]=nd?a[wa+3]:a[zd>>2]}if(1==(e|0)){Wd(Ta,a[Fa],a[le>>2],a[ya],ke)}else{var ae=c+12|0,Nd=a[ya],ce=Nd-1|0,he=1<(ce|0);a:do{if(he){for(var qe=(a[ae>>2]*(e-1)|0)/2&-1|0,se=a[le>>2],ge=1;;){v=((ge<<4)+se|0)>>2;var Ae=(b[0]=a[v],b[1]=a[v+1],f[0])-qe;f[0]=Ae;a[v]=b[0];a[v+1]=b[1];var xe=ge+1|0;if((xe|0)<(ce|0)){ge=xe}else{break a}}}}while(0);var re=a[ai>>2];if((re|0)>(a[mq>>2]|0)){a[mq>>2]=re;var Ce=wb(a[Dj>>2],re<<4),ue=a[Dj>>2]=Ce}else{ue=a[Dj>>2]}var ze=0<(Nd|0);a:do{if(ze){for(var De=a[le>>2],me=0;;){s=((me<<4)+ue|0)>>2;p=((me<<4)+De|0)>>2;a[s]=a[p];a[s+1]=a[p+1];a[s+2]=a[p+2];a[s+3]=a[p+3];var Be=me+1|0;if((Be|0)<(Nd|0)){me=Be}else{break a}}}}while(0);Wd(Ta,a[Fa],ue,Nd,ke);if(1<(e|0)){for(var Ee=za|0,Ge=za+16|0,He=za+12|0,Ie=za+28|0,te=za+68|0,Ke=za+124|0,Le=za+128|0,Me=1;;){var ye=a[i+(Me+g<<2)>>2];z=ye>>2;if(0==(a[z+43]&32|0)){var Oe=ye}else{ua=(ye|0)>>2;Oa=Ee>>2;for(Wa=ua+46;ua>2]=a[z+3];a[He>>2]=a[z+4];ua=(ye+68|0)>>2;Oa=Ie>>2;for(Wa=ua+10;ua>2;Oa=te>>2;for(Wa=ua+10;ua>2]=ye;Oe=za}a:do{if(he){for(var Ve=a[le>>2],Pe=1;;){n=((Pe<<4)+Ve|0)>>2;var We=(a[ae>>2]|0)+(b[0]=a[n],b[1]=a[n+1],f[0]);f[0]=We;a[n]=b[0];a[n+1]=b[1];var Re=Pe+1|0;if((Re|0)==(ce|0)){break a}else{Pe=Re}}}}while(0);var Se=a[Dj>>2];a:do{if(ze){for(var Ze=a[le>>2],Ne=0;;){k=((Ne<<4)+Se|0)>>2;l=((Ne<<4)+Ze|0)>>2;a[k]=a[l];a[k+1]=a[l+1];a[k+2]=a[l+2];a[k+3]=a[l+3];var Te=Ne+1|0;if((Te|0)==(Nd|0)){break a}else{Ne=Te}}}}while(0);Wd(Oe,a[Oe+12>>2],Se,Nd,ke);var Ue=Me+1|0;if((Ue|0)==(e|0)){break}else{Me=Ue}}}}h=Ga}function nm(b){return(1==m[b+162|0]<<24>>24?1<(a[b+180>>2]|0)?1:1<(a[b+188>>2]|0):0)&1}function dx(c,d,i,g){var e,h,l,k,j;h=a[d+16>>2];j=((g<<5)+h|0)>>2;var m=(b[0]=a[j],b[1]=a[j+1],f[0]);k=((g<<5)+h+8|0)>>2;var p=(b[0]=a[k],b[1]=a[k+1],f[0]);l=((g<<5)+h+16|0)>>2;var s=(b[0]=a[l],b[1]=a[l+1],f[0]);h=((g<<5)+h+24|0)>>2;e=(b[0]=a[h],b[1]=a[h+1],f[0]);m==s?(e=a[i+220>>2]>>2,i=a[a[e+(11*g|0)+1]>>2],p=g+1|0,s=a[d>>2]|0,m=a[a[e+(11*p|0)+1]>>2]+40|0,p=(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0])+(a[e+(11*p|0)+5]|0),d=a[d+4>>2]|0,i=i+40|0,g=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])-(a[e+(11*g|0)+4]|0),f[0]=s,a[j]=b[0],a[j+1]=b[1],f[0]=p,a[k]=b[0],a[k+1]=b[1],f[0]=d,a[l]=b[0],a[l+1]=b[1],f[0]=g,a[h]=b[0],a[h+1]=b[1],l=s,k=p,j=d):(l=m,k=p,j=s,g=e);h=c|0;f[0]=l;a[h>>2]=b[0];a[h+4>>2]=b[1];l=c+8|0;f[0]=k;a[l>>2]=b[0];a[l+4>>2]=b[1];l=c+16|0;f[0]=j;a[l>>2]=b[0];a[l+4>>2]=b[1];c=c+24|0;f[0]=g;a[c>>2]=b[0];a[c+4>>2]=b[1]}function nO(c,d,i){var g,e,j,l,k,n,z,p,s,v,t,u,w,A=h;h+=64;var B,C=A+16,y=A+32,D=A+48,F=0==m[c+124|0]<<24>>24;a:do{if(F){var E=c;w=E>>2}else{for(var G=c;;){var H=a[G+128>>2];if(0==m[H+124|0]<<24>>24){E=H;w=E>>2;break a}else{G=H}}}}while(0);var O=a[w+3];u=O>>2;var I=a[w+4],J=a[u+59]-a[I+236>>2]|0,K=-1<(J|0)?J:-J|0;if(2==(K|0)){B=2413}else{if(1==(K|0)){var L=0;h=A;return L}}if(2413==B&&0!=(m[a[u+5]+149|0]&1)<<24>>24){return L=0,h=A,L}if((a[c+16>>2]|0)==(I|0)){a[i>>2]=O;var N=I+32|0,Q=(b[0]=a[N>>2],b[1]=a[N+4>>2],f[0]),S=I+40|0,R=(b[0]=a[S>>2],b[1]=a[S+4>>2],f[0]),U=E+28|0,W=E+36|0;qf(A,Q,R,(b[0]=a[U>>2],b[1]=a[U+4>>2],f[0]),(b[0]=a[W>>2],b[1]=a[W+4>>2],f[0]));var V=A|0,ca=(b[0]=a[V>>2],b[1]=a[V+4>>2],f[0]),Y=A+8|0,Z=(b[0]=a[Y>>2],b[1]=a[Y+4>>2],f[0]),$=O+32|0,ja=(b[0]=a[$>>2],b[1]=a[$+4>>2],f[0]),aa=O+40|0,da=(b[0]=a[aa>>2],b[1]=a[aa+4>>2],f[0]),ea=E+68|0,ba=E+76|0;qf(C,ja,da,(b[0]=a[ea>>2],b[1]=a[ea+4>>2],f[0]),(b[0]=a[ba>>2],b[1]=a[ba+4>>2],f[0]));var fa=C|0,la=C+8|0,ha=ca,ga=Z,ka=(b[0]=a[fa>>2],b[1]=a[fa+4>>2],f[0]),sa=(b[0]=a[la>>2],b[1]=a[la+4>>2],f[0])}else{a[i>>2]=I;var ra=O+32|0,na=(b[0]=a[ra>>2],b[1]=a[ra+4>>2],f[0]),ya=O+40|0,qa=(b[0]=a[ya>>2],b[1]=a[ya+4>>2],f[0]),wa=E+68|0,oa=E+76|0;qf(y,na,qa,(b[0]=a[wa>>2],b[1]=a[wa+4>>2],f[0]),(b[0]=a[oa>>2],b[1]=a[oa+4>>2],f[0]));var Fa=y|0,Ga=(b[0]=a[Fa>>2],b[1]=a[Fa+4>>2],f[0]),va=y+8|0,ta=(b[0]=a[va>>2],b[1]=a[va+4>>2],f[0]),Ka=I+32|0,za=(b[0]=a[Ka>>2],b[1]=a[Ka+4>>2],f[0]),ma=I+40|0,pa=(b[0]=a[ma>>2],b[1]=a[ma+4>>2],f[0]),Ba=E+28|0,Ha=E+36|0;qf(D,za,pa,(b[0]=a[Ba>>2],b[1]=a[Ba+4>>2],f[0]),(b[0]=a[Ha>>2],b[1]=a[Ha+4>>2],f[0]));var Ra=D|0,Aa=D+8|0,ha=Ga,ga=ta,ka=(b[0]=a[Ra>>2],b[1]=a[Ra+4>>2],f[0]),sa=(b[0]=a[Aa>>2],b[1]=a[Aa+4>>2],f[0])}var Ea=a[w+27];if(0==(Ea|0)){var La=d+16|0,Ma=d|0;f[0]=ha;a[Ma>>2]=b[0];a[Ma+4>>2]=b[1];var Ya=d+8|0;f[0]=ga;a[Ya>>2]=b[0];a[Ya+4>>2]=b[1];t=La>>2;v=d>>2;a[t]=a[v];a[t+1]=a[v+1];a[t+2]=a[v+2];a[t+3]=a[v+3];var Qa=d+48|0,Za=d+32|0,Pa=Za|0;f[0]=ka;a[Pa>>2]=b[0];a[Pa+4>>2]=b[1];var ab=d+40|0;f[0]=sa;a[ab>>2]=b[0];a[ab+4>>2]=b[1];s=Qa>>2;p=Za>>2;a[s]=a[p];a[s+1]=a[p+1];a[s+2]=a[p+2];a[s+3]=a[p+3];L=4;h=A;return L}var $a=Ea+24|0,jb=(b[0]=a[$a>>2],b[1]=a[$a+4>>2],f[0]),Ca=Ea+32|0,Ia=(b[0]=a[Ca>>2],b[1]=a[Ca+4>>2],f[0]),eb=0==(a[a[u+5]+152>>2]&1|0),ub=eb?Ia:jb,Sa=Ea+56|0,Va=(b[0]=a[Sa>>2],b[1]=a[Sa+4>>2],f[0]),ua=Ea+64|0,Oa=(b[0]=a[ua>>2],b[1]=a[ua+4>>2],f[0]),Wa=.5*(eb?jb:Ia);if(0==(0<((sa-ga)*(Va-ha)-(Oa-ga)*(ka-ha)&-1|0)&1|0)){var pb=Va-Wa,ob=Oa+.5*ub}else{pb=Va+Wa,ob=Oa-.5*ub}var bb=d+16|0,qb=d|0;f[0]=ha;a[qb>>2]=b[0];a[qb+4>>2]=b[1];var db=d+8|0;f[0]=ga;a[db>>2]=b[0];a[db+4>>2]=b[1];z=bb>>2;n=d>>2;a[z]=a[n];a[z+1]=a[n+1];a[z+2]=a[n+2];a[z+3]=a[n+3];var kb=d+32|0,hb=d+48|0,vb=d+64|0,xb=vb|0;f[0]=pb;a[xb>>2]=b[0];a[xb+4>>2]=b[1];var ib=d+72|0;f[0]=ob;a[ib>>2]=b[0];a[ib+4>>2]=b[1];k=hb>>2;l=vb>>2;a[k]=a[l];a[k+1]=a[l+1];a[k+2]=a[l+2];a[k+3]=a[l+3];j=kb>>2;a[j]=a[l];a[j+1]=a[l+1];a[j+2]=a[l+2];a[j+3]=a[l+3];var nb=d+80|0,rb=d+96|0,lb=rb|0;f[0]=ka;a[lb>>2]=b[0];a[lb+4>>2]=b[1];var Ta=d+104|0;f[0]=sa;a[Ta>>2]=b[0];a[Ta+4>>2]=b[1];e=nb>>2;g=rb>>2;a[e]=a[g];a[e+1]=a[g+1];a[e+2]=a[g+2];a[e+3]=a[g+3];L=7;h=A;return L}function Sg(c,d,i,g,e){var h,l=i>>2,k=d>>2,j;h=a[l+5]>>2;var d=i+32|0,z=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),d=i+104|0,p=z-(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0])-4,d=a[l+59],s=a[l+60],v=a[h+55],t=oO(v,d,s,g,e,-1);if(0==(t|0)){var t=0<=p,u=a[k],p=((t?p+.5:p-.5)&-1|0)<(u|0)?t?p+.5&-1:p-.5&-1:u}else{u=gx(i,t),0==(u|0)?(u=t+32|0,u=(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0])+(a[t+244>>2]|0),t=0==m[t+162|0]<<24>>24?u+.5*(a[h+64]|0):u+(a[k+2]|0)):(t=u+68|0,t=(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0])+(a[k+2]|0)),p=tp?p-.5:p+.5)&-1}p|=0;if(t=1==m[i+162|0]<<24>>24){if(0==(a[l+30]|0)){j=2448}else{var w=z+10}}else{j=2448}2448==j&&(w=i+112|0,w=z+(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0])+4);g=oO(v,d,s,g,e,1);0==(g|0)?(h=0<=w,k=a[k+1],w=((h?w+.5:w-.5)&-1|0)>(k|0)?h?w+.5&-1:w-.5&-1:k):(e=gx(i,g),0==(e|0)?(e=g+32|0,j=g+104|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])-(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]),k=0==m[g+162|0]<<24>>24?e-.5*(a[h+64]|0):e-(a[k+2]|0)):(h=e+52|0,k=(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0])-(a[k+2]|0)),w=k>w?k:w,w=(0>w?w-.5:w+.5)&-1);w|=0;t?0==(a[l+30]|0)?l=w:(l=i+112|0,l=w-(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])):l=w;i=i+40|0;w=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);i=w-(a[(v+16>>2)+(11*d|0)]|0);d=w+(a[(v+20>>2)+(11*d|0)]|0);v=c|0;f[0]=p;a[v>>2]=b[0];a[v+4>>2]=b[1];v=c+8|0;f[0]=i;a[v>>2]=b[0];a[v+4>>2]=b[1];i=c+16|0;f[0]=l;a[i>>2]=b[0];a[i+4>>2]=b[1];c=c+24|0;f[0]=d;a[c>>2]=b[0];a[c+4>>2]=b[1]}function bi(c,d,i,g){var e,j=h;h+=64;e=d>>2;d=h;h+=32;a[d>>2]=a[e];a[d+4>>2]=a[e+1];a[d+8>>2]=a[e+2];a[d+12>>2]=a[e+3];a[d+16>>2]=a[e+4];a[d+20>>2]=a[e+5];a[d+24>>2]=a[e+6];a[d+28>>2]=a[e+7];e=j+32;if(1==(i|0)){e=d|0;e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);var l=d+16|0,d=d+8|0;pO(j,e,g|0,(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]));g=j|0;l=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);g=j+8|0;d=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);e=j+16|0;var g=j+24|0,k=d,n=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),m=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])}else{4==(i|0)&&(l=d|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),k=d+24|0,d=d+16|0,pO(e,l,(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),g|0),g=e|0,l=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),g=e+8|0,d=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),g=e+16|0,e=e+24|0,k=d,n=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),m=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]))}g=c|0;f[0]=l;a[g>>2]=b[0];a[g+4>>2]=b[1];g=c+8|0;f[0]=k;a[g>>2]=b[0];a[g+4>>2]=b[1];g=c+16|0;f[0]=n;a[g>>2]=b[0];a[g+4>>2]=b[1];c=c+24|0;f[0]=m;a[c>>2]=b[0];a[c+4>>2]=b[1];h=j}function ex(b,c,d,g,f,e){var h=hx(c,-1),c=hx(c,1);0!=(h|0)&&yl(h);0!=(c|0)&&yl(c);h=ix(d,-1);d=ix(d,1);0!=(h|0)&&yl(h);0!=(d|0)&&yl(d);d=g+52|0;h=0<(a[d>>2]|0);a:do{if(h){for(c=0;;){if(bd(b,(c<<5)+g+56|0),c=c+1|0,(c|0)>=(a[d>>2]|0)){break a}}}}while(0);g=a[b+80>>2]+1|0;d=e-3+g|0;h=0<(e|0);a:do{if(h){for(c=0;;){if(bd(b,(c<<5)+Z|0),c=c+1|0,(c|0)==(e|0)){break a}}}}while(0);e=a[f+52>>2];if(0<(e|0)){for(;!(e=e-1|0,bd(b,(e<<5)+f+56|0),0>=(e|0));){}}qO(b,g,d)}function rO(c,d,i,g){var e=c+32|0;f[0]=i|0;a[e>>2]=b[0];a[e+4>>2]=b[1];e=c+104|0;f[0]=i-d|0;a[e>>2]=b[0];a[e+4>>2]=b[1];c=c+112|0;f[0]=g-i|0;a[c>>2]=b[0];a[c+4>>2]=b[1]}function hx(b,c){var d,g=a[a[b+16>>2]+184>>2],f=a[g>>2];if(0==(f|0)){var e;return 0}var h=a[a[b+12>>2]+240>>2],k=0,j=0,m=f;for(d=m>>2;;){var p=a[a[d+3]+240>>2];do{if(1>((p-h)*c|0)){f=k}else{if(0==(a[d+6]|0)){f=a[d+32];if(0==(f|0)){f=k;break}if(0==(a[f+24>>2]|0)){f=k;break}}f=0!=(k|0)&&0>=((a[a[k+12>>2]+240>>2]-p)*c|0)?k:m}}while(0);j=j+1|0;d=a[g+(j<<2)>>2];if(0==(d|0)){e=f;break}else{k=f,m=d,d=m>>2}}return e}function ix(b,c){var d,g=a[a[b+12>>2]+176>>2],f=a[g>>2];if(0==(f|0)){var e;return 0}var h=a[a[b+16>>2]+240>>2],k=0,j=0,m=f;for(d=m>>2;;){var p=a[a[d+4]+240>>2];do{if(1>((p-h)*c|0)){f=k}else{if(0==(a[d+6]|0)){f=a[d+32];if(0==(f|0)){f=k;break}if(0==(a[f+24>>2]|0)){f=k;break}}f=0!=(k|0)&&0>=((a[a[k+16>>2]+240>>2]-p)*c|0)?k:m}}while(0);j=j+1|0;d=a[g+(j<<2)>>2];if(0==(d|0)){e=f;break}else{k=f,m=d,d=m>>2}}return e}function qO(c,d,i){var g,e,h,l,k,j,m=d-1|0,p=i+1|0,s=(m|0)<(p|0);a:do{if(s){g=c+84|0;for(e=m;;){l=a[g>>2];k=((e<<5)+l|0)>>2;h=(b[0]=a[k],b[1]=a[k+1],f[0]);if(0==(e-d&1|0)){l=((e<<5)+l+16|0)>>2;var v=(b[0]=a[l],b[1]=a[l+1],f[0]);h>2,v=(b[0]=a[l],b[1]=a[l+1],f[0]),h+16>v&&(h=.5*(h+v)&-1,f[0]=h-8|0,a[k]=b[0],a[k+1]=b[1],f[0]=h+8|0,a[l]=b[0],a[l+1]=b[1])}k=e+1|0;if((k|0)==(p|0)){break a}else{e=k}}}}while(0);m=c+80|0;if(0<(a[m>>2]-1|0)){c=c+84|0;for(g=0;;){k=a[c>>2];e=(g<<5)+k|0;p=g+1|0;s=(p<<5)+k|0;if((g|0)<(d|0)|(g|0)>(i|0)){j=2538}else{if(0!=(g-d&1|0)){j=2538}else{h=e|0;l=(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0])+16;h=((p<<5)+k+16|0)>>2;if(l>(b[0]=a[h],b[1]=a[h+1],f[0])){f[0]=l,a[h]=b[0],a[h+1]=b[1]}h=(g<<5)+k+16|0;l=(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0])-16;h=(s|0)>>2;if(l<(b[0]=a[h],b[1]=a[h+1],f[0])){f[0]=l,a[h]=b[0],a[h+1]=b[1]}}}2538==j&&(j=0,(p|0)>=(d|0)&(g|0)<(i|0)&&0==(p-d&1|0)&&(e=(e|0)>>2,h=(b[0]=a[e],b[1]=a[e+1],f[0])+16,l=(p<<5)+k+16|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),h>l&&(f[0]=l-16,a[e]=b[0],a[e+1]=b[1]),g=((g<<5)+k+16|0)>>2,k=(b[0]=a[g],b[1]=a[g+1],f[0])-16,s|=0,s=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]),k>2]-1|0)){g=p}else{break}}}}function pO(c,d,i,g,e){var h=c|0;f[0]=d;a[h>>2]=b[0];a[h+4>>2]=b[1];d=c+8|0;f[0]=i;a[d>>2]=b[0];a[d+4>>2]=b[1];i=c+16|0;f[0]=g;a[i>>2]=b[0];a[i+4>>2]=b[1];c=c+24|0;f[0]=e;a[c>>2]=b[0];a[c+4>>2]=b[1]}function sO(c,d){var i=c+52|0,i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]),g=d+32|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);if(i>g){return 0}i=c+68|0;if(g>(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])){return 0}i=c+60|0;i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);g=d+40|0;g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);if(i>g){return 0}i=c+76|0;i=g<=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);return i&1}function tO(b,c,d,g){var f,b=b>>2,e,c=(a[b+60]|0)>(c|0);if(1!=(a[b+47]|0)){var h;return 0}var k=0==(g|0);a:do{if(!k){k=0;f=g;for(g=a[a[b+46]>>2];;){if(2<=(k|0)){break a}var j=a[g+12>>2],g=j>>2,z=a[f+12>>2];f=z>>2;if((j|0)==(z|0)){break a}if(c^(a[g+60]|0)>(a[f+60]|0)){h=1;break}if(1!=(a[g+47]|0)){break a}if(0==m[j+162|0]<<24>>24){break a}if(1!=(a[f+47]|0)){break a}if(0==m[z+162|0]<<24>>24){break a}k=k+1|0;f=a[a[f+46]>>2];g=a[a[g+46]>>2]}return h}}while(0);if(1!=(a[b+45]|0)|0==(d|0)){return 0}k=0;g=d;for(d=a[a[b+44]>>2];;){if(2<=(k|0)){h=0;e=2589;break}b=a[d+16>>2];d=b>>2;j=a[g+16>>2];g=j>>2;if((b|0)==(j|0)){h=0;e=2582;break}if(c^(a[d+60]|0)>(a[g+60]|0)){h=1;e=2584;break}if(1!=(a[d+45]|0)){h=0;e=2585;break}if(0==m[b+162|0]<<24>>24){h=0;e=2583;break}if(1!=(a[g+45]|0)){h=0;e=2586;break}if(0==m[j+162|0]<<24>>24){h=0;e=2588;break}k=k+1|0;g=a[a[g+44]>>2];d=a[a[d+44]>>2]}if(2583==e||2584==e||2582==e||2589==e||2585==e||2586==e||2588==e){return h}}function qf(c,d,i,g,e){var h=c|0;f[0]=d+g;a[h>>2]=b[0];a[h+4>>2]=b[1];c=c+8|0;f[0]=i+e;a[c>>2]=b[0];a[c+4>>2]=b[1]}function fx(c,d){var i=d+84|0,g=d+80|0,e=c,h=0;a:for(;;){e=a[e+12>>2];if(1!=m[e+162|0]<<24>>24){break}if(0!=J[a[ke+4>>2]](e)<<24>>24){break}for(var l=a[g>>2],k=e+40|0;;){if((h|0)>=(l|0)){break a}var j=a[i>>2],z=(h<<5)+j+8|0,p=(b[0]=a[z>>2],b[1]=a[z+4>>2],f[0]),z=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);if(p>z){h=h+1|0}else{break}}l=(h<<5)+j+24|0;if((b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])>=z){var l=0==(a[e+120>>2]|0),k=(h<<5)+j|0,p=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),k=p&-1,s=(h<<5)+j+16|0,s=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]);l?rO(e,k,.5*(p+s)&-1,s&-1):(l=e+112|0,rO(e,k,s&-1,s+(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])&-1))}e=a[a[e+184>>2]>>2]}}function oO(b,c,d,g,f,e){for(var h,k=b+44*c|0,b=b+44*c+4|0,c=d;;){c=c+e|0;if(-1>=(c|0)){var j=0;h=2613;break}if((c|0)>=(a[k>>2]|0)){j=0;h=2617;break}var z=a[a[b>>2]+(c<<2)>>2],p=m[z+162|0];if(1==p<<24>>24){if(0!=(a[z+120>>2]|0)){j=z;h=2614;break}}else{if(0==p<<24>>24){j=z;h=2615;break}}if(0==tO(z,d,g,f)<<24>>24){j=z;h=2616;break}}if(2615==h||2617==h||2616==h||2614==h||2613==h){return j}}function gx(b,c){if(0==m[b+162|0]<<24>>24){var d=a[b+216>>2],g=d}else{d=a[a[a[b+184>>2]>>2]+128>>2],g=a[a[d+12>>2]+216>>2],d=a[a[d+16>>2]+216>>2]}if(0==m[c+162|0]<<24>>24){var f=a[c+216>>2];return 0==(f|0)|(f|0)==(d|0)|(f|0)==(g|0)?0:f}var f=a[a[a[c+184>>2]>>2]+128>>2],e=a[a[f+16>>2]+216>>2];if(!(0==(e|0)|(e|0)==(d|0)|(e|0)==(g|0))&&0!=(sO(e,c)|0)){return e}f=a[a[f+12>>2]+216>>2];return 0==(f|0)|(f|0)==(d|0)|(f|0)==(g|0)?0:0==(sO(f,c)|0)?0:f}function kO(c,d,i,g,e,j){var l,k,n,z,p,s,v,t,u,w,A,B,C,y,D,F,E,G,I,O,H,J,K,L,N,S,Q=h;h+=176;var R,U=Q+16,W=Q+32,V=Q+96,ca=Q+112,Y=Q+128,Z=Q+144,$=Q+160,ja=a[e+20>>2];do{if(0<(i|0)){for(var aa=0,da=0,ea=0;;){var ba=a[c+(ea+d<<2)>>2],fa=(0!=(a[ba+108>>2]|0)&1)+aa|0;if(0==m[ba+56|0]<<24>>24){if(0==m[ba+96|0]<<24>>24){var ka=da}else{R=2633}}else{R=2633}2633==R&&(R=0,ka=1);var ha=ea+1|0;if((ha|0)==(i|0)){break}else{aa=fa,da=ka,ea=ha}}if(0==(ka|0)){if(0==(fa|0)){break}uO(e,g,c,d,i,j,fa);h=Q;return}var ga=vO(ja),la=oo(ga,wO|0);yd(la|0,nq|0,jx|0);var sa=g+32|0,ra=(b[0]=a[sa>>2],b[1]=a[sa+4>>2],f[0])&-1,na=e+32|0,ya=(b[0]=a[na>>2],b[1]=a[na+4>>2],f[0])&-1;S=(ja+152|0)>>2;for(var qa=0==(a[S]&1|0),wa=qa?g:e,oa=qa?e:g,Fa=xO(la,oa),Ga=xO(ga,wa),va=0,ta=0;;){for(var Ka=(va+d<<2)+c|0;;){var za=a[Ka>>2];if(0==m[za+124|0]<<24>>24){break}else{Ka=za+128|0}}var ma;if((a[za+16>>2]|0)==(oa|0)){var pa=za,Ba=uh(ga,Fa,Ga);po(pa|0,Ba|0);ma=Ba}else{var Ha=za,Ra=uh(ga,Ga,Fa);po(Ha|0,Ra|0);ma=Ra}var Ea=ma;a[za+132>>2]=Ea|0;if(0==(ta|0)){if(0!=m[za+56|0]<<24>>24){var Aa=0}else{0!=m[za+96|0]<<24>>24?Aa=0:(a[Ea+132>>2]=za|0,Aa=Ea)}}else{Aa=ta}var La=va+1|0;if((La|0)==(i|0)){break}else{va=La,ta=Aa}}var Ma=0==(Aa|0)?uh(ga,Fa,Ga):Aa;qc(Ma|0,a[a[Kg>>2]+8>>2],yO|0);a[ga+172>>2]=a[ja+172>>2];var Ya=ga;JF(Ya,j);XN(ga);kx(ga,0);lx(Ya,0);zO(Ya,0);N=(oa+32|0)>>2;var Qa=Fa+32|0;L=Qa>>2;K=(Ga+32|0)>>2;var Za=a[ga+216>>2],Pa=0==(Za|0);a:do{if(!Pa){for(var ab=.5*((b[0]=a[K],b[1]=a[K+1],f[0])+(b[0]=a[L],b[1]=a[L+1],f[0]))&-1,$a=oa+112|0,jb=wa+32|0,Ca=(b[0]=a[$a>>2],b[1]=a[$a+4>>2],f[0]),Ia=(b[0]=a[N],b[1]=a[N+1],f[0]),eb=wa+104|0,ub=ra|0,Sa=ab|0,Va=ya|0,ua=.5*((b[0]=a[jb>>2],b[1]=a[jb+4>>2],f[0])+(Ia-Ca)+(b[0]=a[eb>>2],b[1]=a[eb+4>>2],f[0]))&-1|0,Oa=Fa+40|0,Wa=Ga+40|0,pb=Za;;){if((pb|0)==(Fa|0)){f[0]=ub,a[Oa>>2]=b[0],a[Oa+4>>2]=b[1],f[0]=Sa,a[L]=b[0],a[L+1]=b[1]}else{if((pb|0)==(Ga|0)){f[0]=Va,a[Wa>>2]=b[0],a[Wa+4>>2]=b[1],f[0]=Sa,a[K]=b[0],a[K+1]=b[1]}else{var ob=pb+40|0;f[0]=ua;a[ob>>2]=b[0];a[ob+4>>2]=b[1]}}var bb=a[pb+168>>2];if(0==(bb|0)){break a}else{pb=bb}}}}while(0);mx(ga);bx(ga,0);Bt(Ya,1);var qb=0==(a[S]&1|0),db=(b[0]=a[N],b[1]=a[N+1],f[0]);if(qb){var kb=oa+40|0,ib=Fa+40|0,vb=Qa,xb=(b[0]=a[kb>>2],b[1]=a[kb+4>>2],f[0])-(b[0]=a[ib>>2],b[1]=a[ib+4>>2],f[0])}else{var hb=oa+40|0,vb=Fa+40|0,xb=(b[0]=a[hb>>2],b[1]=a[hb+4>>2],f[0])+(b[0]=a[L],b[1]=a[L+1],f[0])}var nb=db-(b[0]=a[vb>>2],b[1]=a[vb+4>>2],f[0]);J=Q>>2;H=U>>2;O=V>>2;I=W>>2;var rb=W|0;G=ca>>2;E=(W+16|0)>>2;F=Y>>2;D=(W+32|0)>>2;y=(W+48|0)>>2;C=Z>>2;var lb=ja+52|0;B=$>>2;for(var Ta=ja,cb=0;;){for(var fb=(cb+d<<2)+c|0;;){var Ua=a[fb>>2];if(0==m[Ua+124|0]<<24>>24){break}else{fb=Ua+128|0}}var sb=a[Ua+132>>2];A=sb>>2;do{if(!((sb|0)==(Ma|0)&0==(a[A+33]|0))){var Na=a[a[A+6]>>2];w=(Na+4|0)>>2;var Fb=Au(Ua,a[w]);a[Fb+8>>2]=a[Na+8>>2];var Db=a[S]&1,wb=Na+16|0,Eb=Na+24|0;ci(Q,(b[0]=a[wb>>2],b[1]=a[wb+4>>2],f[0]),(b[0]=a[Eb>>2],b[1]=a[Eb+4>>2],f[0]),nb,xb,Db);u=(Fb+16|0)>>2;a[u]=a[J];a[u+1]=a[J+1];a[u+2]=a[J+2];a[u+3]=a[J+3];a[Fb+12>>2]=a[Na+12>>2];var Cb=a[S]&1,Bb=Na+32|0,Ja=Na+40|0;ci(U,(b[0]=a[Bb>>2],b[1]=a[Bb+4>>2],f[0]),(b[0]=a[Ja>>2],b[1]=a[Ja+4>>2],f[0]),nb,xb,Cb);t=(Fb+32|0)>>2;a[t]=a[H];a[t+1]=a[H+1];a[t+2]=a[H+2];a[t+3]=a[H+3];v=(Fb|0)>>2;s=(Na|0)>>2;for(var mb=0;(mb|0)<(a[w]|0);){var yb=(mb<<4)+a[v]|0,Gb=a[s],Hb=a[S]&1,Jb=(mb<<4)+Gb|0,Kb=(mb<<4)+Gb+8|0;ci(V,(b[0]=a[Jb>>2],b[1]=a[Jb+4>>2],f[0]),(b[0]=a[Kb>>2],b[1]=a[Kb+4>>2],f[0]),nb,xb,Hb);p=yb>>2;a[p]=a[O];a[p+1]=a[O+1];a[p+2]=a[O+2];a[p+3]=a[O+3];a[I]=a[O];a[I+1]=a[O+1];a[I+2]=a[O+2];a[I+3]=a[O+3];var Ib=mb+1|0;if((Ib|0)>=(a[w]|0)){break}var Mb=(Ib<<4)+a[v]|0,Lb=a[s],Nb=a[S]&1,Pb=(Ib<<4)+Lb|0,Tb=(Ib<<4)+Lb+8|0;ci(ca,(b[0]=a[Pb>>2],b[1]=a[Pb+4>>2],f[0]),(b[0]=a[Tb>>2],b[1]=a[Tb+4>>2],f[0]),nb,xb,Nb);z=Mb>>2;a[z]=a[G];a[z+1]=a[G+1];a[z+2]=a[G+2];a[z+3]=a[G+3];a[E]=a[G];a[E+1]=a[G+1];a[E+2]=a[G+2];a[E+3]=a[G+3];var Sb=mb+2|0,Wb=(Sb<<4)+a[v]|0,Vb=a[s],Yb=a[S]&1,Xb=(Sb<<4)+Vb|0,$b=(Sb<<4)+Vb+8|0;ci(Y,(b[0]=a[Xb>>2],b[1]=a[Xb+4>>2],f[0]),(b[0]=a[$b>>2],b[1]=a[$b+4>>2],f[0]),nb,xb,Yb);n=Wb>>2;a[n]=a[F];a[n+1]=a[F+1];a[n+2]=a[F+2];a[n+3]=a[F+3];a[D]=a[F];a[D+1]=a[F+1];a[D+2]=a[F+2];a[D+3]=a[F+3];var Zb=mb+3|0,ac=a[s],dc=a[S]&1,bc=(Zb<<4)+ac|0,ec=(Zb<<4)+ac+8|0;ci(Z,(b[0]=a[bc>>2],b[1]=a[bc+4>>2],f[0]),(b[0]=a[ec>>2],b[1]=a[ec+4>>2],f[0]),nb,xb,dc);a[y]=a[C];a[y+1]=a[C+1];a[y+2]=a[C+2];a[y+3]=a[C+3];xk(lb,rb);mb=Zb}k=(Ua+108|0)>>2;var fc=a[k];if(0!=(fc|0)){var gc=a[A+27],jc=a[S]&1,kc=gc+56|0,mc=gc+64|0;ci($,(b[0]=a[kc>>2],b[1]=a[kc+4>>2],f[0]),(b[0]=a[mc>>2],b[1]=a[mc+4>>2],f[0]),nb,xb,jc);l=(fc+56|0)>>2;a[l]=a[B];a[l+1]=a[B+1];a[l+2]=a[B+2];a[l+3]=a[B+3];m[a[k]+81|0]=1;al(Ta,a[k])}}}while(0);var nc=cb+1|0;if((nc|0)==(i|0)){break}else{cb=nc}}var Qb=ga;a[dj>>2]=a[nx>>2];a[di>>2]=a[ox>>2];a[Ej>>2]=a[px>>2];a[Kg>>2]=a[qx>>2];a[Hh>>2]=a[rx>>2];a[Gh>>2]=a[sx>>2];a[Ah>>2]=a[tx>>2];ax(Qb);Sf(Qb);h=Q;return}}while(0);cx(e,g,c,d,i,j);h=Q}function lO(c,d,i,g){var e,j,l,k,n,z,p,s,v,t,u=h;h+=1540;for(var w=u+696,A=u+1392,B=u+1396,C=u+1508,y=u+1524,D=a[i+16>>2],F=i+12|0,E=a[F>>2],G=a[D+20>>2],I=a[i+180>>2];;){var O=a[I+180>>2];if(0==(O|0)){break}else{I=O}}var H=a[I+16>>2];t=(i+108|0)>>2;var J=H+32|0;v=(a[t]+56|0)>>2;s=J>>2;a[v]=a[s];a[v+1]=a[s+1];a[v+2]=a[s+2];a[v+3]=a[s+3];m[a[t]+81|0]=1;do{if(2==(g|0)){var K=D+32|0,L=(b[0]=a[K>>2],b[1]=a[K+4>>2],f[0]),N=D+40|0,Q=(b[0]=a[N>>2],b[1]=a[N+4>>2],f[0]),S=i+28|0,R=i+36|0;qf(C,L,Q,(b[0]=a[S>>2],b[1]=a[S+4>>2],f[0]),(b[0]=a[R>>2],b[1]=a[R+4>>2],f[0]));var U=C|0,W=(b[0]=a[U>>2],b[1]=a[U+4>>2],f[0]),V=C+8|0,ca=(b[0]=a[V>>2],b[1]=a[V+4>>2],f[0]),Y=E+32|0,$=(b[0]=a[Y>>2],b[1]=a[Y+4>>2],f[0]),ba=E+40|0,ja=(b[0]=a[ba>>2],b[1]=a[ba+4>>2],f[0]),aa=i+68|0,da=i+76|0;qf(y,$,ja,(b[0]=a[aa>>2],b[1]=a[aa+4>>2],f[0]),(b[0]=a[da>>2],b[1]=a[da+4>>2],f[0]));var ea=y|0,fa=(b[0]=a[ea>>2],b[1]=a[ea+4>>2],f[0]),ka=y+8|0,la=(b[0]=a[ka>>2],b[1]=a[ka+4>>2],f[0]),ha=a[t],ga=ha+56|0,sa=(b[0]=a[ga>>2],b[1]=a[ga+4>>2],f[0]),ra=ha+64|0,na=ha+32|0,qa=(b[0]=a[ra>>2],b[1]=a[ra+4>>2],f[0])-.5*(b[0]=a[na>>2],b[1]=a[na+4>>2],f[0]),ya=B+16|0,oa=B|0,wa=B|0;f[0]=W;a[wa>>2]=b[0];a[wa+4>>2]=b[1];var va=B+8|0;f[0]=ca;a[va>>2]=b[0];a[va+4>>2]=b[1];p=ya>>2;z=B>>2;a[p]=a[z];a[p+1]=a[z+1];a[p+2]=a[z+2];a[p+3]=a[z+3];var Fa=B+32|0,Ga=B+48|0,Aa=B+64|0,ta=Aa|0;f[0]=sa;a[ta>>2]=b[0];a[ta+4>>2]=b[1];var Ka=B+72|0;f[0]=qa;a[Ka>>2]=b[0];a[Ka+4>>2]=b[1];n=Ga>>2;k=Aa>>2;a[n]=a[k];a[n+1]=a[k+1];a[n+2]=a[k+2];a[n+3]=a[k+3];l=Fa>>2;a[l]=a[k];a[l+1]=a[k+1];a[l+2]=a[k+2];a[l+3]=a[k+3];var za=B+80|0,ma=B+96|0,pa=ma|0;f[0]=fa;a[pa>>2]=b[0];a[pa+4>>2]=b[1];var Ea=B+104|0;f[0]=la;a[Ea>>2]=b[0];a[Ea+4>>2]=b[1];j=za>>2;e=ma>>2;a[j]=a[e];a[j+1]=a[e+1];a[j+2]=a[e+2];a[j+3]=a[e+3];a[A>>2]=7;var Ha=oa,Ra=7}else{var Ba=J|0,Ma=(b[0]=a[Ba>>2],b[1]=a[Ba+4>>2],f[0]),La=H+104|0,Qa=Ma-(b[0]=a[La>>2],b[1]=a[La+4>>2],f[0]),Ya=H+112|0,Pa=Ma+(b[0]=a[Ya>>2],b[1]=a[Ya+4>>2],f[0]),Za=H+40|0,Va=(b[0]=a[Za>>2],b[1]=a[Za+4>>2],f[0]),ab=H+96|0,$a=Va+.5*(b[0]=a[ab>>2],b[1]=a[ab+4>>2],f[0]),jb=a[D+236>>2],Ca=a[G+220>>2],Ia=D+40|0,eb=(Va-(a[(Ca+16>>2)+(11*jb|0)]|0)-(b[0]=a[Ia>>2],b[1]=a[Ia+4>>2],f[0])+a[(Ca+20>>2)+(11*jb|0)]&-1|0)/6&-1,ub=$a-(5>(eb|0)?5:eb|0);mm(c,d,D,i,u,1);mm(c,d,E,i,w,0);var Sa=u+52|0,db=a[Sa>>2],ua=db-1|0,Oa=(ua<<5)+u+56|0,Wa=(b[0]=a[Oa>>2],b[1]=a[Oa+4>>2],f[0]);f[0]=Wa;a[Z>>2]=b[0];a[Z+4>>2]=b[1];var pb=(ua<<5)+u+80|0,ob=(b[0]=a[pb>>2],b[1]=a[pb+4>>2],f[0]);f[0]=ob;a[Z+8>>2]=b[0];a[Z+12>>2]=b[1];f[0]=Qa;a[Z+16>>2]=b[0];a[Z+20>>2]=b[1];f[0]=ub;a[Z+24>>2]=b[0];a[Z+28>>2]=b[1];f[0]=Wa;a[Z+32>>2]=b[0];a[Z+36>>2]=b[1];f[0]=ub;a[Z+40>>2]=b[0];a[Z+44>>2]=b[1];var bb=w+52|0,qb=a[bb>>2]-1|0,ib=(qb<<5)+w+72|0,kb=(b[0]=a[ib>>2],b[1]=a[ib+4>>2],f[0]);f[0]=kb;a[Z+48>>2]=b[0];a[Z+52>>2]=b[1];f[0]=$a;a[Z+56>>2]=b[0];a[Z+60>>2]=b[1];f[0]=Pa;a[Z+64>>2]=b[0];a[Z+68>>2]=b[1];f[0]=ub;a[Z+88>>2]=b[0];a[Z+92>>2]=b[1];var hb=(qb<<5)+w+80|0,vb=(b[0]=a[hb>>2],b[1]=a[hb+4>>2],f[0]);f[0]=vb;a[Z+72>>2]=b[0];a[Z+76>>2]=b[1];f[0]=kb;a[Z+80>>2]=b[0];a[Z+84>>2]=b[1];var xb=0<(db|0);a:do{if(xb){for(var mb=0;;){bd(d,(mb<<5)+u+56|0);var nb=mb+1|0;if((nb|0)<(a[Sa>>2]|0)){mb=nb}else{break a}}}}while(0);bd(d,Z|0);bd(d,Z+32|0);bd(d,Z+64|0);var rb=a[bb>>2],lb=0<(rb|0);a:do{if(lb){for(var Ta=rb;;){var cb=Ta-1|0;bd(d,(cb<<5)+w+56|0);if(0<(cb|0)){Ta=cb}else{break a}}}}while(0);var fb=8==(g|0)?ff(d,A,0):ff(d,A,1),Ua=a[A>>2];if(0!=(Ua|0)){Ha=fb,Ra=Ua}else{h=u;return}}}while(0);Wd(i,a[F>>2],Ha,Ra,ke);h=u}function cx(c,d,i,g,e,j){var l,k,n,m,p,s,v,t,u,w,A,B,C,y,D=h;h+=320;var F=D+160,E=D+176,G=D+192,I=D+208,O=D+224,H=D+240,J=D+256,K=D+272,L=D+288,N=D+304,Q=a[i+(g<<2)>>2],S=c+32|0,S=(b[0]=a[S>>2],b[1]=a[S+4>>2],f[0]);m=c+40|0;m=(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0]);p=Q+28|0;s=Q+36|0;qf(F,S,m,(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]));S=F|0;S=(b[0]=a[S>>2],b[1]=a[S+4>>2],f[0]);F=F+8|0;F=(b[0]=a[F>>2],b[1]=a[F+4>>2],f[0]);m=d+32|0;m=(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0]);d=d+40|0;d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]);p=Q+68|0;Q=Q+76|0;qf(E,m,d,(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),(b[0]=a[Q>>2],b[1]=a[Q+4>>2],f[0]));Q=E|0;Q=(b[0]=a[Q>>2],b[1]=a[Q+4>>2],f[0]);E=E+8|0;E=(b[0]=a[E>>2],b[1]=a[E+4>>2],f[0]);if(1<(e|0)){var c=c+96|0,d=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),c=d/(e-1|0),R=.5*d}else{if(0<(e|0)){R=c=0}else{h=D;return}}var U=D|0,W=D+8|0;l=D+16|0;var V=l|0,Y=D+24|0,Z=(2*S+Q)/3;y=(D+32|0)>>2;C=O>>2;B=(D+48|0)>>2;A=H>>2;w=(D+64|0)>>2;u=J>>2;var $=(2*Q+S)/3;t=(D+80|0)>>2;v=K>>2;s=(D+96|0)>>2;p=L>>2;m=(D+112|0)>>2;var d=N>>2,ba=D+128|0,ja=D+136|0,aa=D+144|0,da=D+152|0,ea=D|0;n=l>>2;k=G>>2;l=I>>2;for(var fa=D+48|0,ka=D+56|0,la=0,R=F-R;;){var ha=a[i+(la+g<<2)>>2];f[0]=S;a[U>>2]=b[0];a[U+4>>2]=b[1];f[0]=F;a[W>>2]=b[0];a[W+4>>2]=b[1];if(8==(j|0)||2==(j|0)){Tg(G,Z,R);a[n]=a[k];a[n+1]=a[k+1];a[n+2]=a[k+2];a[n+3]=a[k+3];Tg(I,$,R);a[y]=a[l];a[y+1]=a[l+1];a[y+2]=a[l+2];a[y+3]=a[l+3];f[0]=Q;a[fa>>2]=b[0];a[fa+4>>2]=b[1];f[0]=E;a[ka>>2]=b[0];a[ka+4>>2]=b[1];var ga=4}else{f[0]=S,a[V>>2]=b[0],a[V+4>>2]=b[1],f[0]=F,a[Y>>2]=b[0],a[Y+4>>2]=b[1],Tg(O,Z,R),a[y]=a[C],a[y+1]=a[C+1],a[y+2]=a[C+2],a[y+3]=a[C+3],Tg(H,Z,R),a[B]=a[A],a[B+1]=a[A+1],a[B+2]=a[A+2],a[B+3]=a[A+3],Tg(J,Z,R),a[w]=a[u],a[w+1]=a[u+1],a[w+2]=a[u+2],a[w+3]=a[u+3],Tg(K,$,R),a[t]=a[v],a[t+1]=a[v+1],a[t+2]=a[v+2],a[t+3]=a[v+3],Tg(L,$,R),a[s]=a[p],a[s+1]=a[p+1],a[s+2]=a[p+2],a[s+3]=a[p+3],Tg(N,$,R),a[m]=a[d],a[m+1]=a[d+1],a[m+2]=a[d+2],a[m+3]=a[d+3],f[0]=Q,a[ba>>2]=b[0],a[ba+4>>2]=b[1],f[0]=E,a[ja>>2]=b[0],a[ja+4>>2]=b[1],f[0]=Q,a[aa>>2]=b[0],a[aa+4>>2]=b[1],f[0]=E,a[da>>2]=b[0],a[da+4>>2]=b[1],ga=10}Wd(ha,a[ha+12>>2],ea,ga,ke);la=la+1|0;if((la|0)==(e|0)){break}else{R+=c}}h=D}function Tg(c,d,i){var g=c|0;f[0]=d;a[g>>2]=b[0];a[g+4>>2]=b[1];c=c+8|0;f[0]=i;a[c>>2]=b[0];a[c+4>>2]=b[1]}function mO(c,d,i,g,e,j,l){var k,n=h;h+=1396;var m,p=n+4,s=n+700,v=a[j+16>>2],t=a[j+12>>2];k=a[v+20>>2];var u=a[v+236>>2];if((u|0)<(D[k+246>>1]<<16>>16|0)){k=a[k+220>>2]>>2;var w=u+1|0,A=v+40|0,B=a[a[k+(11*w|0)+1]>>2]+40|0;k=(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0])-(a[k+(11*u|0)+6]|0)-((b[0]=a[B>>2],b[1]=a[B+4>>2],f[0])+(a[k+(11*w|0)+7]|0))}else{k=a[k+260>>2]|0}w=e+1|0;u=(a[c+12>>2]|0)/w;k/=w;ux(c,d,v,j,p,1);ux(c,d,t,j,s,0);c=p+52|0;j=s+52|0;l=0==(l|0);v=d+80|0;for(w=0;;){if((w|0)>=(e|0)){m=2738;break}var t=a[i+(w+g<<2)>>2],A=a[c>>2],B=A-1|0,C=(B<<5)+p+56|0,C=(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0]),y=(B<<5)+p+64|0,y=(b[0]=a[y>>2],b[1]=a[y+4>>2],f[0]),B=(B<<5)+p+72|0,F=(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0]);f[0]=C;a[Z>>2]=b[0];a[Z+4>>2]=b[1];f[0]=y;a[Z+24>>2]=b[0];a[Z+28>>2]=b[1];var w=w+1|0,E=w|0,B=E*u;f[0]=F+B;a[Z+16>>2]=b[0];a[Z+20>>2]=b[1];y-=E*k;f[0]=y;a[Z+8>>2]=b[0];a[Z+12>>2]=b[1];f[0]=C;a[Z+32>>2]=b[0];a[Z+36>>2]=b[1];f[0]=y;a[Z+56>>2]=b[0];a[Z+60>>2]=b[1];F=a[j>>2]-1|0;C=(F<<5)+s+72|0;C=(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0]);f[0]=C;a[Z+48>>2]=b[0];a[Z+52>>2]=b[1];f[0]=y-k;a[Z+40>>2]=b[0];a[Z+44>>2]=b[1];E=(F<<5)+s+56|0;E=(b[0]=a[E>>2],b[1]=a[E+4>>2],f[0]);F=(F<<5)+s+64|0;F=(b[0]=a[F>>2],b[1]=a[F+4>>2],f[0]);f[0]=C;a[Z+80>>2]=b[0];a[Z+84>>2]=b[1];f[0]=F;a[Z+88>>2]=b[0];a[Z+92>>2]=b[1];f[0]=E-B;a[Z+64>>2]=b[0];a[Z+68>>2]=b[1];f[0]=y;a[Z+72>>2]=b[0];a[Z+76>>2]=b[1];A=0<(A|0);a:do{if(A){for(B=0;;){if(bd(d,(B<<5)+p+56|0),B=B+1|0,(B|0)>=(a[c>>2]|0)){break a}}}}while(0);bd(d,Z|0);bd(d,Z+32|0);bd(d,Z+64|0);A=a[j>>2];B=0<(A|0);a:do{if(B){for(C=A;;){if(C=C-1|0,bd(d,(C<<5)+s+56|0),0>=(C|0)){break a}}}}while(0);A=l?ff(d,n,1):ff(d,n,0);B=a[n>>2];if(0==(B|0)){m=2739;break}Wd(t,a[t+12>>2],A,B,ke);a[v>>2]=0}2738==m?h=n:2739==m&&(h=n)}function mm(c,d,i,g,e,j){var l,k=h;h+=96;l=k+32;var n=k+64,m=a[i+20>>2];Sg(l,c,i,0,g);c=e>>2;l>>=2;a[c]=a[l];a[c+1]=a[l+1];a[c+2]=a[l+2];a[c+3]=a[l+3];a[c+4]=a[l+4];a[c+5]=a[l+5];a[c+6]=a[l+6];a[c+7]=a[l+7];c=k>>2;a[c]=a[l];a[c+1]=a[l+1];a[c+2]=a[l+2];a[c+3]=a[l+3];a[c+4]=a[l+4];a[c+5]=a[l+5];a[c+6]=a[l+6];a[c+7]=a[l+7];a[e+48>>2]=4;0==j<<24>>24?xl(d,g,2,e,0):vl(d,g,2,e,0);d=(e+52|0)>>2;j=a[d]-1|0;g=(j<<5)+e+80|0;l=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);g=(k+24|0)>>2;f[0]=l;a[g]=b[0];a[g+1]=b[1];j=(j<<5)+e+64|0;l=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]);j=(k+8|0)>>2;f[0]=l;a[j]=b[0];a[j+1]=b[1];l=i+40|0;bi(n,k,4,(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])+a[(a[m+220>>2]+20>>2)+(11*a[i+236>>2]|0)]&-1);i=n>>2;a[c]=a[i];a[c+1]=a[i+1];a[c+2]=a[i+2];a[c+3]=a[i+3];a[c+4]=a[i+4];a[c+5]=a[i+5];a[c+6]=a[i+6];a[c+7]=a[i+7];i=k|0;n=k+16|0;if((b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])<(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0])){if((b[0]=a[j],b[1]=a[j+1],f[0])<(b[0]=a[g],b[1]=a[g+1],f[0])){i=a[d],a[d]=i+1|0,e=((i<<5)+e+56|0)>>2,a[e]=a[c],a[e+1]=a[c+1],a[e+2]=a[c+2],a[e+3]=a[c+3],a[e+4]=a[c+4],a[e+5]=a[c+5],a[e+6]=a[c+6],a[e+7]=a[c+7]}}h=k}function ux(c,d,i,g,e,j){var l,k=h;h+=96;l=k+32;var n=k+64,m=a[i+20>>2];Sg(l,c,i,0,g);c=e>>2;l>>=2;a[c]=a[l];a[c+1]=a[l+1];a[c+2]=a[l+2];a[c+3]=a[l+3];a[c+4]=a[l+4];a[c+5]=a[l+5];a[c+6]=a[l+6];a[c+7]=a[l+7];c=k>>2;a[c]=a[l];a[c+1]=a[l+1];a[c+2]=a[l+2];a[c+3]=a[l+3];a[c+4]=a[l+4];a[c+5]=a[l+5];a[c+6]=a[l+6];a[c+7]=a[l+7];a[e+48>>2]=1;0==j<<24>>24?xl(d,g,2,e,0):vl(d,g,2,e,0);d=(e+52|0)>>2;j=a[d]-1|0;g=(j<<5)+e+80|0;l=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);g=(k+24|0)>>2;f[0]=l;a[g]=b[0];a[g+1]=b[1];j=(j<<5)+e+64|0;l=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]);j=(k+8|0)>>2;f[0]=l;a[j]=b[0];a[j+1]=b[1];l=i+40|0;bi(n,k,1,(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])-a[(a[m+220>>2]+20>>2)+(11*a[i+236>>2]|0)]&-1);i=n>>2;a[c]=a[i];a[c+1]=a[i+1];a[c+2]=a[i+2];a[c+3]=a[i+3];a[c+4]=a[i+4];a[c+5]=a[i+5];a[c+6]=a[i+6];a[c+7]=a[i+7];i=k|0;n=k+16|0;if((b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])<(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0])){if((b[0]=a[j],b[1]=a[j+1],f[0])<(b[0]=a[g],b[1]=a[g+1],f[0])){i=a[d],a[d]=i+1|0,e=((i<<5)+e+56|0)>>2,a[e]=a[c],a[e+1]=a[c+1],a[e+2]=a[c+2],a[e+3]=a[c+3],a[e+4]=a[c+4],a[e+5]=a[c+5],a[e+6]=a[c+6],a[e+7]=a[c+7]}}h=k}function uO(c,d,i,g,e,j,l){var k,n,z,p,s,v,t,u,w,A,B,C,y,D,F,E,M,I,O=h;h+=196;var H,J=O+4,K=O+164,L=O+180,N=a[i+(g<<2)>>2],Q=fa(e<<2),S=0<(e|0);a:do{if(S){for(var R=0;;){a[Q+(R<<2)>>2]=a[i+(R+g<<2)>>2];var U=R+1|0;if((U|0)==(e|0)){break a}else{R=U}}}}while(0);ch(Q,e,364);var W=c+32|0,V=(b[0]=a[W>>2],b[1]=a[W+4>>2],f[0]),Y=c+40|0,Z=(b[0]=a[Y>>2],b[1]=a[Y+4>>2],f[0]),$=N+28|0,ba=N+36|0;qf(K,V,Z,(b[0]=a[$>>2],b[1]=a[$+4>>2],f[0]),(b[0]=a[ba>>2],b[1]=a[ba+4>>2],f[0]));var ja=K|0,aa=(b[0]=a[ja>>2],b[1]=a[ja+4>>2],f[0]),da=K+8|0,ea=(b[0]=a[da>>2],b[1]=a[da+4>>2],f[0]),ka=d+32|0,la=(b[0]=a[ka>>2],b[1]=a[ka+4>>2],f[0]),sa=d+40|0,ha=(b[0]=a[sa>>2],b[1]=a[sa+4>>2],f[0]),ga=N+68|0,ra=N+76|0;qf(L,la,ha,(b[0]=a[ga>>2],b[1]=a[ga+4>>2],f[0]),(b[0]=a[ra>>2],b[1]=a[ra+4>>2],f[0]));var na=L|0,qa=(b[0]=a[na>>2],b[1]=a[na+4>>2],f[0]),oa=L+8|0,ya=(b[0]=a[oa>>2],b[1]=a[oa+4>>2],f[0]),va=c+112|0,wa=aa+(b[0]=a[va>>2],b[1]=a[va+4>>2],f[0]),Aa=d+104|0,Fa=qa-(b[0]=a[Aa>>2],b[1]=a[Aa+4>>2],f[0]),Ga=.5*(wa+Fa);I=(J|0)>>2;f[0]=aa;a[I]=b[0];a[I+1]=b[1];M=(J+8|0)>>2;f[0]=ea;a[M]=b[0];a[M+1]=b[1];E=(J+16|0)>>2;f[0]=aa;a[E]=b[0];a[E+1]=b[1];F=(J+24|0)>>2;f[0]=ea;a[F]=b[0];a[F+1]=b[1];D=(J+32|0)>>2;f[0]=qa;a[D]=b[0];a[D+1]=b[1];y=(J+40|0)>>2;f[0]=ya;a[y]=b[0];a[y+1]=b[1];C=(J+48|0)>>2;f[0]=qa;a[C]=b[0];a[C+1]=b[1];B=(J+56|0)>>2;f[0]=ya;a[B]=b[0];a[B+1]=b[1];var Ea=J|0;Wd(N,a[N+12>>2],Ea,4,ke);A=(N+108|0)>>2;var ta=a[A]+56|0;f[0]=Ga;a[ta>>2]=b[0];a[ta+4>>2]=b[1];var Ka=a[A],za=Ka+32|0,ma=ea+.5*((b[0]=a[za>>2],b[1]=a[za+4>>2],f[0])+6),pa=Ka+64|0;f[0]=ma;a[pa>>2]=b[0];a[pa+4>>2]=b[1];m[a[A]+81|0]=1;var Ba=ea+3,Ha=a[A],Ra=Ha+32|0,Ma=Ba+(b[0]=a[Ra>>2],b[1]=a[Ra+4>>2],f[0]),Qa=Ha+24|0,La=.5*(b[0]=a[Qa>>2],b[1]=a[Qa+4>>2],f[0]),Pa=Ga-La,Ya=Ga+La;w=(J+64|0)>>2;u=(J+72|0)>>2;t=(J+80|0)>>2;v=(J+88|0)>>2;s=(J+96|0)>>2;p=(J+104|0)>>2;z=(J+112|0)>>2;n=(J+120|0)>>2;for(var Va=4==(j|0)&1,Za=Ma,db=Ba,ab=1;;){var $a,jb;if((ab|0)>=(l|0)){break}var Ca=a[i+(ab+g<<2)>>2];if(0==(ab&1|0)){f[0]=aa;a[I]=b[0];a[I+1]=b[1];f[0]=ea;a[M]=b[0];a[M+1]=b[1];f[0]=Pa;a[E]=b[0];a[E+1]=b[1];f[0]=ea;a[F]=b[0];a[F+1]=b[1];f[0]=Pa;a[D]=b[0];a[D+1]=b[1];f[0]=Za;a[y]=b[0];a[y+1]=b[1];f[0]=Ya;a[C]=b[0];a[C+1]=b[1];f[0]=Za;a[B]=b[0];a[B+1]=b[1];f[0]=Ya;a[w]=b[0];a[w+1]=b[1];f[0]=ya;a[u]=b[0];a[u+1]=b[1];f[0]=qa;a[t]=b[0];a[t+1]=b[1];f[0]=ya;a[v]=b[0];a[v+1]=b[1];f[0]=qa;a[s]=b[0];a[s+1]=b[1];var Ia=Za+6;f[0]=Ia;a[p]=b[0];a[p+1]=b[1];f[0]=aa;a[z]=b[0];a[z+1]=b[1];f[0]=Ia;a[n]=b[0];a[n+1]=b[1];var eb=a[Ca+108>>2]+32|0,ub=(b[0]=a[eb>>2],b[1]=a[eb+4>>2],f[0]),Sa=jb,ib=$a,ua=Za+(ub+6),Oa=db,Wa=Za+.5*ub+6}else{var pb=Ca+108|0,ob=a[pb>>2];if(1==(ab|0)){var bb=ob+24|0,qb=.5*(b[0]=a[bb>>2],b[1]=a[bb+4>>2],f[0]),hb=Ga+qb,kb=Ga-qb}else{hb=jb,kb=$a}var mb=ob+32|0,vb=db-((b[0]=a[mb>>2],b[1]=a[mb+4>>2],f[0])+6);f[0]=aa;a[I]=b[0];a[I+1]=b[1];f[0]=ea;a[M]=b[0];a[M+1]=b[1];f[0]=aa;a[E]=b[0];a[E+1]=b[1];var xb=vb-6;f[0]=xb;a[F]=b[0];a[F+1]=b[1];f[0]=qa;a[D]=b[0];a[D+1]=b[1];f[0]=xb;a[y]=b[0];a[y+1]=b[1];f[0]=qa;a[C]=b[0];a[C+1]=b[1];f[0]=ya;a[B]=b[0];a[B+1]=b[1];f[0]=hb;a[w]=b[0];a[w+1]=b[1];f[0]=ya;a[u]=b[0];a[u+1]=b[1];f[0]=hb;a[t]=b[0];a[t+1]=b[1];f[0]=vb;a[v]=b[0];a[v+1]=b[1];f[0]=kb;a[s]=b[0];a[s+1]=b[1];f[0]=vb;a[p]=b[0];a[p+1]=b[1];f[0]=kb;a[z]=b[0];a[z+1]=b[1];f[0]=ea;a[n]=b[0];a[n+1]=b[1];var wb=a[pb>>2]+32|0,Sa=hb,ib=kb,ua=Za,Oa=vb,Wa=vb+.5*(b[0]=a[wb>>2],b[1]=a[wb+4>>2],f[0])}var nb=Ft(aa,ea,qa,ya,Ea,8,O,Va),rb=a[O>>2];if(0==(rb|0)){H=2780;break}k=(Ca+108|0)>>2;var lb=a[k]+56|0;f[0]=Ga;a[lb>>2]=b[0];a[lb+4>>2]=b[1];var Ta=a[k]+64|0;f[0]=Wa;a[Ta>>2]=b[0];a[Ta+4>>2]=b[1];m[a[k]+81|0]=1;Wd(Ca,a[Ca+12>>2],nb,rb,ke);jb=Sa;$a=ib;Za=ua;db=Oa;ab=ab+1|0}if(2780!=H){for(var cb=(2*wa+Fa)/3,fb=(wa+2*Fa)/3,Ua=jb,sb=$a,Na=Za,Fb=db,Db=ab;(Db|0)<(e|0);){var Cb=a[i+(Db+g<<2)>>2];if(0==(Db&1|0)){f[0]=aa;a[I]=b[0];a[I+1]=b[1];f[0]=ea;a[M]=b[0];a[M+1]=b[1];f[0]=Pa;a[E]=b[0];a[E+1]=b[1];f[0]=ea;a[F]=b[0];a[F+1]=b[1];f[0]=Pa;a[D]=b[0];a[D+1]=b[1];f[0]=Na;a[y]=b[0];a[y+1]=b[1];f[0]=Ya;a[C]=b[0];a[C+1]=b[1];f[0]=Na;a[B]=b[0];a[B+1]=b[1];f[0]=Ya;a[w]=b[0];a[w+1]=b[1];f[0]=ya;a[u]=b[0];a[u+1]=b[1];f[0]=qa;a[t]=b[0];a[t+1]=b[1];f[0]=ya;a[v]=b[0];a[v+1]=b[1];f[0]=qa;a[s]=b[0];a[s+1]=b[1];var Eb=Na+6,yb=Ua,Bb=sb,Ja=Eb,Gb=Fb,Hb=Eb,Ib=aa,Jb=Eb}else{var Kb=1==(Db|0),Lb=Kb?cb:sb,Mb=Kb?fb:Ua,Nb=Fb-6;f[0]=aa;a[I]=b[0];a[I+1]=b[1];f[0]=ea;a[M]=b[0];a[M+1]=b[1];f[0]=aa;a[E]=b[0];a[E+1]=b[1];var Pb=Nb-6;f[0]=Pb;a[F]=b[0];a[F+1]=b[1];f[0]=qa;a[D]=b[0];a[D+1]=b[1];f[0]=Pb;a[y]=b[0];a[y+1]=b[1];f[0]=qa;a[C]=b[0];a[C+1]=b[1];f[0]=ya;a[B]=b[0];a[B+1]=b[1];f[0]=Mb;a[w]=b[0];a[w+1]=b[1];f[0]=ya;a[u]=b[0];a[u+1]=b[1];f[0]=Mb;a[t]=b[0];a[t+1]=b[1];f[0]=Nb;a[v]=b[0];a[v+1]=b[1];f[0]=Lb;a[s]=b[0];a[s+1]=b[1];yb=Mb;Bb=Lb;Ja=Na;Gb=Nb;Hb=ea;Ib=Lb;Jb=Nb}f[0]=Jb;a[p]=b[0];a[p+1]=b[1];f[0]=Ib;a[z]=b[0];a[z+1]=b[1];f[0]=Hb;a[n]=b[0];a[n+1]=b[1];var Tb=Ft(aa,ea,qa,ya,Ea,8,O,Va),Sb=a[O>>2];if(0==(Sb|0)){H=2781;break}Wd(Cb,a[Cb+12>>2],Tb,Sb,ke);Ua=yb;sb=Bb;Na=Ja;Fb=Gb;Db=Db+1|0}2781!=H&&G(Q)}h=O}function vO(c){var d,i=xn(AO|0,a[c>>2]>>>4&1);Mp(i,nq|0,Y|0);var g=fa(96),e=i+44|0;d=e>>2;a[e>>2]=g;var h=c+44|0,l=a[h>>2]|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);f[0]=l;a[g>>2]=b[0];a[g+4>>2]=b[1];g=a[h>>2]+24|0;g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);h=a[e>>2]+24|0;f[0]=g;a[h>>2]=b[0];a[h+4>>2]=b[1];m[e+107|0]=m[c+151|0];a[d+27]=a[c+152>>2]&1^1;a[d+53]=a[c+256>>2];a[d+54]=a[c+260>>2];c=c+32|0;e=a[a[c>>2]+16>>2];g=a[a[e+4>>2]+8>>2];d=a[g>>2];if(0==(d|0)){c=e}else{for(e=g;!(e=e+4|0,xi(i,a[d>>2],a[d+4>>2]),d=a[e>>2],0==(d|0));){}c=a[a[c>>2]+16>>2]}c=a[a[c+8>>2]+8>>2];d=a[c>>2];e=0==(d|0);a:do{if(!e){g=c;for(h=d;;){if(g=g+4|0,Th(i,a[h>>2],a[h+4>>2]),h=a[g>>2],0==(h|0)){break a}}}}while(0);c=(i+40|0)>>2;0==($(a[a[c]+4>>2]|0,wi|0)|0)&&Th(i,wi|0,Y|0);0==($(a[a[c]+4>>2]|0,vi|0)|0)&&Th(i,vi|0,Y|0);a[nx>>2]=a[dj>>2];a[ox>>2]=a[di>>2];a[px>>2]=a[Ej>>2];a[qx>>2]=a[Kg>>2];a[rx>>2]=a[Hh>>2];a[sx>>2]=a[Gh>>2];a[tx>>2]=a[Ah>>2];a[dj>>2]=0;d=$(a[a[c]+4>>2]|0,vx|0);a[di>>2]=d;d=$(a[a[c]+4>>2]|0,wx|0);a[Ej>>2]=d;c=$(a[a[c]+4>>2]|0,vp|0);a[Kg>>2]=c;if(0!=(c|0)){return a[Hh>>2]=0,a[Gh>>2]=0,i}c=Th(i,vp|0,Y|0);a[Kg>>2]=c;a[Hh>>2]=0;a[Gh>>2]=0;return i}function xO(b,c){var d=h,g=cl(b,a[c+12>>2]),f=g|0;po(c|0,f);if(2!=(ts(c)|0)){return h=d,g}var e=c+120|0,l=Cb(Ba(a[a[e>>2]>>2])+3|0);Ma(l,BO|0,(j=h,h+=4,a[j>>2]=a[a[e>>2]>>2],j));yd(f,dh|0,l);h=d;return g}function ci(a,b,c,d,f,e){0==(e|0)?(e=b,b=c):(e=c,b=-b);qf(a,e,b,d,f)}function CO(b,c,d,g,f,e){var h;if(!(0<(d|0)&0<(e|0))){var k;return 0}if((d|0)<(e|0)){for(b=0;;){f=a[c+(b<<2)>>2];if(0==(f|0)){k=0;h=2824;break}if((a[f+12>>2]|0)==(g|0)){k=f;h=2825;break}else{b=b+1|0}}if(2825==h||2824==h){return k}}else{for(c=0;;){g=a[f+(c<<2)>>2];if(0==(g|0)){k=0;h=2826;break}if((a[g+16>>2]|0)==(b|0)){k=g;h=2827;break}else{c=c+1|0}}if(2826==h||2827==h){return k}}}function Fj(b,c){var d,g;d=(b+4|0)>>2;for(var f=a[d],e=b|0,h=0;;){if((h|0)>=(f|0)){g=2833;break}var k=a[e>>2],j=(h<<2)+k|0;if((a[j>>2]|0)==(c|0)){break}else{h=h+1|0}}2833!=g&&(g=f-1|0,a[d]=g,a[j>>2]=a[k+(g<<2)>>2],a[a[e>>2]+(a[d]<<2)>>2]=0)}function DO(a,b){var c=a+176|0;D[c>>1]=D[c>>1]-D[b+176>>1]&65535;c=a+162|0;D[c>>1]=D[c>>1]-D[b+162>>1]&65535;c=a+164|0;ib[c>>2]-=ib[b+164>>2]}function Zh(b,c){return CO(b,a[b+184>>2],a[b+188>>2],c,a[c+176>>2],a[c+180>>2])}function oN(b,c){return CO(b,a[b+192>>2],a[b+196>>2],c,a[c+200>>2],a[c+204>>2])}function xx(b){var c,d;d=(b+16|0)>>2;c=a[d]>>2;var g=a[c+46];c=0==(g|0)?Cb((a[c+47]<<2)+8|0):wb(g,(a[c+47]<<2)+8|0);a[a[d]+184>>2]=c;c=a[d]+188|0;g=a[c>>2];a[c>>2]=g+1|0;a[a[a[d]+184>>2]+(g<<2)>>2]=b;d=a[d];a[a[d+184>>2]+(a[d+188>>2]<<2)>>2]=0;d=(b+12|0)>>2;c=a[d]>>2;g=a[c+44];c=0==(g|0)?Cb((a[c+45]<<2)+8|0):wb(g,(a[c+45]<<2)+8|0);a[a[d]+176>>2]=c;c=a[d]+180|0;g=a[c>>2];a[c>>2]=g+1|0;a[a[a[d]+176>>2]+(g<<2)>>2]=b;d=a[d];a[a[d+176>>2]+(a[d+180>>2]<<2)>>2]=0;return b}function pf(b){0==(b|0)&&sa(qg|0,117,EO|0,oq|0);Fj(a[b+16>>2]+184|0,b);Fj(a[b+12>>2]+176|0,b)}function Rg(b){var c,d;d=(b+16|0)>>2;c=a[d]>>2;var g=a[c+52];c=0==(g|0)?Cb((a[c+53]<<2)+8|0):wb(g,(a[c+53]<<2)+8|0);a[a[d]+208>>2]=c;c=a[d]+212|0;g=a[c>>2];a[c>>2]=g+1|0;a[a[a[d]+208>>2]+(g<<2)>>2]=b;b=a[d];a[a[b+208>>2]+(a[b+212>>2]<<2)>>2]=0}function pN(b,c){var d,g,f;g=(c+4|0)>>2;var e=a[g];d=(c|0)>>2;for(var h=a[d],k=0;(k|0)<(e|0);){if((a[h+(k<<2)>>2]|0)==(b|0)){f=2869;break}else{k=k+1|0}}2869!=f&&(f=0==(h|0)?Cb((e<<2)+8|0):wb(h,(e<<2)+8|0),a[d]=f,e=a[g],a[g]=e+1|0,a[f+(e<<2)>>2]=b,a[a[d]+(a[g]<<2)>>2]=0)}function pq(b,c,d){var g=fa(184);a[g+16>>2]=b;a[g+12>>2]=c;m[g+124|0]=1;if(0==(d|0)){return ib[g+164>>2]=1,D[g+162>>1]=1,D[g+176>>1]=1,D[g+178>>1]=1,g}a[g+20>>2]=a[d+20>>2];D[g+176>>1]=D[d+176>>1];D[g+162>>1]=D[d+162>>1];ib[g+164>>2]=ib[d+164>>2];D[g+178>>1]=D[d+178>>1];var f=d+16|0;if((a[f>>2]|0)==(b|0)){for(var b=(d+28|0)>>2,e=(g+28|0)>>2,h=b+10;b>2]|0)==(b|0)){b=(d+68|0)>>2;e=(g+28|0)>>2;for(h=b+10;b>2]|0)==(c|0)){b=(d+68|0)>>2;e=(g+68|0)>>2;for(h=b+10;b>2]|0)==(c|0)){b=(d+28|0)>>2;e=(g+68|0)>>2;for(h=b+10;b>2]|0)&&(a[c>>2]=g);a[g+128>>2]=d;return g}function de(a,b,c){return xx(pq(a,b,c))}function Mw(b,c){var d=b+216|0,g=a[d>>2],f=c+168|0;a[f>>2]=g;0!=(g|0)&&(a[g+172>>2]=c);a[d>>2]=c;a[c+172>>2]=0;(a[f>>2]|0)==(c|0)&&sa(qg|0,215,FO|0,GO|0)}function Qw(b,c){for(var d=b+216|0;;){var g=a[d>>2];if(0==(g|0)|(g|0)==(c|0)){break}else{d=g+168|0}}0==(g|0)&&sa(qg|0,231,HO|0,IO|0);var g=c+168|0,f=a[g>>2],d=c+172|0;0==(f|0)?g=0:(a[f+172>>2]=a[d>>2],g=a[g>>2]);d=a[d>>2];0==(d|0)?a[b+216>>2]=g:a[d+168>>2]=g}function Lf(c){var d,i=fa(304);d=i>>2;a[d+3]=JO|0;a[d+5]=c;m[i+162|0]=1;var g=i+112|0;f[0]=1;a[g>>2]=b[0];a[g+4>>2]=b[1];g=i+104|0;f[0]=1;a[g>>2]=b[0];a[g+4>>2]=b[1];g=i+96|0;f[0]=1;a[g>>2]=b[0];a[g+4>>2]=b[1];a[d+55]=1;a[d+45]=0;a[d+44]=fa(20);a[d+47]=0;a[d+46]=fa(20);Mw(c,i);c=c+240|0;a[c>>2]=a[c>>2]+1|0;return i}function gm(b,c){var d,g;g=(c+16|0)>>2;d=a[g]>>2;var f=a[d+48];d=0==(f|0)?Cb((a[d+49]<<2)+8|0):wb(f,(a[d+49]<<2)+8|0);a[a[g]+192>>2]=d;d=a[g]+196|0;f=a[d>>2];a[d>>2]=f+1|0;a[a[a[g]+192>>2]+(f<<2)>>2]=c;g=a[g];a[a[g+192>>2]+(a[g+196>>2]<<2)>>2]=0;g=(c+12|0)>>2;d=a[g]>>2;f=a[d+50];d=0==(f|0)?Cb((a[d+51]<<2)+8|0):wb(f,(a[d+51]<<2)+8|0);a[a[g]+200>>2]=d;d=a[g]+204|0;f=a[d>>2];a[d>>2]=f+1|0;a[a[a[g]+200>>2]+(f<<2)>>2]=c;g=a[g];a[a[g+200>>2]+(a[g+204>>2]<<2)>>2]=0;m[b+248|0]=1;m[a[b+32>>2]+248|0]=1}function yx(b){0==(b|0)&&sa(qg|0,272,KO|0,oq|0);var c=a[b+128>>2];0!=(c|0)&&(c=c+180|0,(a[c>>2]|0)==(b|0)&&(a[c>>2]=0));Fj(a[b+16>>2]+192|0,b);Fj(a[b+12>>2]+200|0,b)}function og(b,c){var d=h,g=b+180|0,f=a[g>>2];if((f|0)==(c|0)){la(0,LO|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j))}else{0!=(f|0)&&sa(qg|0,343,MO|0,Kw|0);a[g>>2]=c;g=c+178|0;f=D[b+178>>1];(D[g>>1]&65535)<(f&65535)&&(D[g>>1]=f);for(var g=b+176|0,f=b+162|0,e=b+164|0,l=c;;){var k=l+176|0;D[k>>1]=D[k>>1]+D[g>>1]&65535;k=l+162|0;D[k>>1]=D[k>>1]+D[f>>1]&65535;k=l+164|0;ib[k>>2]+=ib[e>>2];l=a[l+180>>2];if(0==(l|0)){break}}}h=d}function NO(b){var c;c=(b+180|0)>>2;var d=a[c];if(0!=(d|0)){for(var g=d;;){DO(g,b);d=a[g+180>>2];for(0==D[g+176>>1]<<16>>16&&OO(g);1==m[g+124|0]<<24>>24;){g=a[g+12>>2];if(1!=m[g+162|0]<<24>>24){break}if(1!=(a[g+188>>2]|0)){break}g=a[a[g+184>>2]>>2];DO(g,b)}if(0==(d|0)){break}else{g=d}}}a[c]=0}function PO(b,c,d,g){var f=(b|0)>(c|0);a[d>>2]=f?c:b;a[g>>2]=f?b:c}function OO(b){0==(b|0)&&sa(qg|0,128,QO|0,oq|0);var c=b+16|0,d=a[c>>2],g=d+184|0,f=a[a[g>>2]>>2],e=0==(f|0);a:do{if(!e){for(var h=0,k=g,j=f,m=d;;){if((j|0)==(b|0)&&(Fj(k,b),m=a[c>>2]),h=h+1|0,k=m+184|0,j=a[a[k>>2]+(h<<2)>>2],0==(j|0)){break a}}}}while(0);c=b+12|0;f=a[c>>2];g=f+176|0;e=a[a[g>>2]>>2];if(0!=(e|0)){for(d=0;!((e|0)==(b|0)&&(Fj(g,b),f=a[c>>2]),d=d+1|0,g=f+176|0,e=a[a[g>>2]+(d<<2)>>2],0==(e|0));){}}}function RO(c){var d,i,g,e,h=c+216|0,l=a[h>>2],k=0==(l|0);a:do{if(!k){for(var j=l;;){var z=j+192|0,p=a[z>>2],s=0==(p|0);b:do{if(!s){var v=a[p>>2];if(0!=(v|0)){i=0;for(var t=p;;){g=a[v+16>>2]>>2;d=a[a[v+12>>2]+240>>2];var u=a[g+60],w=(u|0)<(d|0),A=w?d:u;g=a[a[g+5]+220>>2]+44*a[g+59]+4|0;for(d=w?u:d;;){var B=d+1|0;if((B|0)>=(A|0)){break}d=a[a[g>>2]+(B<<2)>>2];u=m[d+162|0];if(0==u<<24>>24){break}else{if(1!=u<<24>>24){d=B;continue}}if(0==(a[d+120>>2]|0)){d=B}else{break}}0!=((B|0)==(A|0)&1|0)&&(m[v+125|0]=1,t=a[z>>2]);i=i+1|0;v=a[t+(i<<2)>>2];if(0==(v|0)){break b}}}}}while(0);j=a[j+168>>2];if(0==(j|0)){break a}}}}while(0);l=a[c+220>>2];0==(a[l+40>>2]|0)?0<(a[c+208>>2]|0)&&(e=29):e=29;a:do{if(29==e){k=a[l+4>>2];B=0;b:for(;;){j=a[k+(B<<2)>>2];if(0==(j|0)){break a}j=a[j+200>>2];for(z=0;;){p=a[j+(z<<2)>>2];if(0==(p|0)){break}if(0!=(a[p+108>>2]|0)&&0==m[p+125|0]<<24>>24){break b}z=z+1|0}B=B+1|0}SO(c)}}while(0);TO(c);l=a[h>>2];if(0==(l|0)){return 0}e=c+152|0;for(h=0;;){k=l+192|0;B=a[k>>2];j=0==(B|0);a:do{if(j){var C=h}else{z=a[B>>2];p=0==(z|0);b:do{if(p){var y=h}else{s=h;i=0;for(v=z;;){if(t=a[v+108>>2],0!=(t|0)&&(0==m[v+125|0]<<24>>24?(zx(v),s=1):(t=0==(a[e>>2]&1|0)?t+24|0:t+32|0,t=(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0]),v=v+144|0,f[0]=t,a[v>>2]=b[0],a[v+4>>2]=b[1])),i=i+1|0,v=a[a[k>>2]+(i<<2)>>2],0==(v|0)){y=s;break b}}}}while(0);z=l+212|0;if(0<(a[z>>2]|0)){p=l+208|0;s=y;for(v=0;;){t=a[a[p>>2]+(v<<2)>>2];i=t>>2;A=a[i+4];g=a[i+3];if((a[A+236>>2]|0)!=(a[g+236>>2]|0)|(A|0)==(g|0)){d=s}else{for(d=t;!(u=a[d+180>>2],0==(u|0));){d=u}u=m[d+125|0];m[t+125|0]=u;w=a[i+27];0==(w|0)?d=s:0==u<<24>>24?(zx(t),d=1):(u=0==(a[e>>2]&1|0)?w+24|0:w+32|0,u=(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0]),d=(d+144|0)>>2,w=(b[0]=a[d],b[1]=a[d+1],f[0]),f[0]=u>w?u:w,a[d]=b[0],a[d+1]=b[1],d=s)}i=v+1|0;if((i|0)<(a[z>>2]|0)){s=d,v=i}else{C=d;break a}}}else{C=y}}}while(0);l=a[l+168>>2];if(0==(l|0)){break}else{h=C}}if(0==(C|0)){return 0}qq(c);return C}function SO(b){var c;c=(b+244|0)>>1;0!=D[c]<<16>>16&&sa(Ax|0,190,UO|0,VO|0);var d=b+246|0,g=(D[d>>1]<<16>>16)+3|0,b=(b+220|0)>>2,f=a[b],g=(0==(f|0)?Cb(44*g|0):wb(f,44*g|0))+44|0;a[b]=g;f=D[d>>1];d=f<<16>>16;f=-1>16;a:do{if(f){for(var e=d,h=g;;){for(var k=e-1|0,j=(h+44*k|0)>>2,h=(h+44*e|0)>>2,m=j+11;j>2)+(11*p|0)]=0;a[(a[b]>>2)+(11*p|0)]=0;s=fa(8);a[(a[b]+12>>2)+(11*p|0)]=s;a[(a[b]+4>>2)+(11*p|0)]=s;a[(a[b]+40>>2)+(11*p|0)]=0;a[(a[b]+20>>2)+(11*p|0)]=1;a[(a[b]+16>>2)+(11*p|0)]=1;a[(a[b]+28>>2)+(11*p|0)]=1;a[(a[b]+24>>2)+(11*p|0)]=1;D[c]=D[c]-1&65535}function zx(c){var d,i,g,e,h,l,k,j;j=(c+108|0)>>2;if(0!=(a[j]|0)){k=(c+16|0)>>2;l=a[k]>>2;var z=a[l+5];i=a[l+59];h=(z+220|0)>>2;e=(c+12|0)>>2;var p=WO(a[h],a[a[e]+240>>2],i,a[l+60]);l=i-1|0;g=a[h]>>2;d=a[a[g+(11*l|0)+1]>>2];0==(d|0)?(d=a[a[g+(11*i|0)+1]>>2]+40|0,g=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0])+(a[g+(11*i|0)+5]|0)+(a[z+260>>2]|0)):(i=d+40|0,g=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0])-(a[g+(11*l|0)+4]|0));g&=-1;p=XO(z,l,p);d=a[j];i=d+24|0;i=(b[0]=a[i>>2],b[1]=a[i+4>>2],f[0]);d=d+32|0;d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]);var s=0==(a[z+152>>2]&1|0),z=s?d:i,v=p+96|0;f[0]=z;a[v>>2]=b[0];a[v+4>>2]=b[1];z=.5*z&-1;s=.5*(s?i:d);i=(p+112|0)>>2;f[0]=s;a[i]=b[0];a[i+1]=b[1];d=(p+104|0)>>2;f[0]=s;a[d]=b[0];a[d+1]=b[1];a[p+120>>2]=a[j];j=p+40|0;f[0]=z+g|0;a[j>>2]=b[0];a[j+4>>2]=b[1];j=de(p,a[k],c);g=-(b[0]=a[d],b[1]=a[d+1],f[0]);d=j+28|0;f[0]=g;a[d>>2]=b[0];a[d+4>>2]=b[1];k=a[k]+112|0;k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);g=j+68|0;f[0]=k;a[g>>2]=b[0];a[g+4>>2]=b[1];m[j+124|0]=4;k=de(p,a[e],c);j=(b[0]=a[i],b[1]=a[i+1],f[0]);g=k+28|0;f[0]=j;a[g>>2]=b[0];a[g+4>>2]=b[1];e=a[e]+104|0;e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);j=k+68|0;f[0]=e;a[j>>2]=b[0];a[j+4>>2]=b[1];m[k+124|0]=4;e=a[h];k=e+44*l+16|0;(a[k>>2]|0)<(z|0)?(a[k>>2]=z,h=a[h]):h=e;h=h+44*l+20|0;(a[h>>2]|0)<(z|0)&&(a[h>>2]=z);a[p+128>>2]=c|0}}function WO(b,c,d,g){var f=h;h+=24;var e=f+16,j=f+20,k=d-1|0,d=a[(b+4>>2)+(11*k|0)],n=a[(b>>2)+(11*k|0)],b=f+8|0;a[b>>2]=-1;k=f|0;a[k>>2]=-1;var m=f+12|0;a[m>>2]=n;var p=f+4|0;a[p>>2]=n;PO(g,c,e,j);for(var c=a[e>>2],j=a[j>>2],e=n,g=0,s=-1,v=n;;){n=e-1|0;if((g|0)>(n|0)){var t=s,u=v;break}Bx(a[d+(g<<2)>>2],k,c,j);(g|0)!=(n|0)&&Bx(a[d+(n<<2)>>2],k,c,j);v=a[p>>2];s=a[k>>2];if(2>(v-s|0)){t=s;u=v;break}else{e=n,g=g+1|0}}(t|0)>(u|0)?(t=a[m>>2]+a[b>>2]|0,t=(t+1|0)/2&-1):(t=(u+t|0)+1|0,t=(t|0)/2&-1);h=f;return t}function XO(b,c,d){var g,f;f=(b+220|0)>>2;var e=a[f];g=e+44*c|0;e=a[(e+4>>2)+(11*c|0)];e=0==(e|0)?Cb((a[g>>2]<<2)+8|0):wb(e,(a[g>>2]<<2)+8|0);g=e>>2;a[(a[f]+4>>2)+(11*c|0)]=e;var h=a[(a[f]>>2)+(11*c|0)],k=(h|0)>(d|0);a:do{if(k){for(var j=h;;){var m=j-1|0,p=a[(m<<2>>2)+g];a[(j<<2>>2)+g]=p;j=p+240|0;a[j>>2]=a[j>>2]+1|0;if((m|0)>(d|0)){j=m}else{break a}}}}while(0);h=Lf(b);b=(d<<2)+e|0;a[b>>2]=h;a[h+240>>2]=d;a[h+236>>2]=c;c=a[f]+44*c|0;d=a[c>>2]+1|0;a[c>>2]=d;a[(d<<2>>2)+g]=0;return a[b>>2]}function Bx(b,c,d,g){var c=c>>2,f=b>>2,e=h;h+=8;var j,k=e+4;if(1==m[b+162|0]<<24>>24){if(b=a[f+60],0!=(a[f+45]|0)){j=a[f+46];var n=a[j>>2];if(0!=(n|0)){for(var z=f=k=0;;){var p=a[a[n+12>>2]+240>>2];if((p|0)>(d|0)){var p=(p|0)<(g|0)?z:1,s=f}else{p=z,s=1}k=k+1|0;n=a[j+(k<<2)>>2];if(0==(n|0)){break}else{f=s,z=p}}0!=s<<24>>24&0==p<<24>>24&&(a[c]=b+1|0);0!=p<<24>>24&0==s<<24>>24&&(a[c+1]=b-1|0)}}else{if(2!=(a[f+47]|0)&&sa(Ax|0,63,YO|0,ZO|0),p=a[f+46],PO(a[a[a[p>>2]+12>>2]+240>>2],a[a[a[p+4>>2]+12>>2]+240>>2],e,k),p=a[k>>2],(p|0)>(d|0)){if(s=a[e>>2],(s|0)<(g|0)){if(k=(s|0)<(d|0),f=(p|0)>(g|0),!(k&f)){k?j=114:(s|0)==(d|0)&(p|0)<(g|0)&&(j=114);114==j&&(a[c+2]=b);if(!f&&!((p|0)==(g|0)&(s|0)>(d|0))){h=e;return}a[c+3]=b}}else{a[c+1]=b,a[c+3]=b}}else{a[c]=b,a[c+2]=b}}}h=e}function Cx(b){var c=b+276|0,d=a[c>>2];if(0!=(d|0)){var g=D[b+244>>1],f=b+246|0;if(g<<16>>16<=D[f>>1]<<16>>16){b=b+220|0;for(g=g<<16>>16;;){a[d+(g<<2)>>2]=a[a[(a[b>>2]+4>>2)+(11*g|0)]>>2];d=g+1|0;if((d|0)>(D[f>>1]<<16>>16|0)){break}g=d;d=a[c>>2]}}}}function lx(c,d){0!=m[ld]<<24>>24&&mk(ih);m[rq]=0;a[sd>>2]=c;var i=(rc(a[a[c+32>>2]+28>>2])<<2)+4|0,g=fa(i);a[om>>2]=g;i=fa(i);a[pm>>2]=i;a[qm>>2]=8;a[rm>>2]=24;f[0]=.995;a[sm>>2]=b[0];a[sm+4>>2]=b[1];i=V(c|0,$O|0);0!=(i|0)&&(i=wg(i,xc),0>2]|0)*i,a[qm>>2]=1>g?1:g&-1,i*=a[rm>>2]|0,a[rm>>2]=1>i?1:i&-1));Lw(c);jq(c,1);Dx(c);sq(c);a[Ex>>2]=D[c+244>>1]<<16>>16;a[Fx>>2]=D[c+246>>1]<<16>>16;i=c+228|0;g=0<(a[i>>2]|0);a:do{if(g){for(var e=0,h=0;;){var j=c,k=e;a[j+216>>2]=a[a[j+224>>2]+(k<<2)>>2];if(0<(k|0)){var n=D[j+244>>1],k=j+246|0;if(n<<16>>16<=D[k>>1]<<16>>16){j=j+220|0;for(n=n<<16>>16;;){var z=a[j>>2],p=z+44*n+4|0;a[p>>2]=(a[(z>>2)+(11*n|0)]<<2)+a[p>>2]|0;a[(a[j>>2]>>2)+(11*n|0)]=0;n=n+1|0;if((n|0)>(D[k>>1]<<16>>16|0)){break}}}}h=tq(c,0,d)+h|0;e=e+1|0;if((e|0)>=(a[i>>2]|0)){var s=h;break a}}}else{s=0}}while(0);aP(c);i=c+208|0;if(1>(a[i>>2]|0)){var v=s}else{g=c+212|0;for(e=1;;){var v=bP(a[a[g>>2]+(e<<2)>>2],d)+s|0,s=e+1|0,t=a[i>>2];if((s|0)>(t|0)){break}else{e=s,s=v}}if(0<(t|0)){t=V(c|0,cP|0);if(0!=(t|0)&&0==re(t)<<24>>24){Gx(c,v);return}Tw(c);m[rq]=1;v=tq(c,2,d)}}Gx(c,v)}function tq(c,d,i){var g=h;if(1<(d|0)){var e=tm(),Xa=c+216|0;Hx(a[Xa>>2]);var l=e}else{l=2147483647,Xa=c+216|0}for(var e=c+32|0,k=l;;){var n;if(3<=(d|0)){var z=k,p=l,s=n;break}n=a[rm>>2];if(2>(d|0)){n=4<(n|0)?4:n;(a[e>>2]|0)==(c|0)&&Ix(c,d);0==(d|0)&&Jx(c);Kx(c);var v=tm();(v|0)>(k|0)?l=k:(Hx(a[Xa>>2]),l=v)}else{(l|0)>(k|0)&&dP(c),v=l=k}for(var k=l,t=v,u=0,l=0;(l|0)<(n|0);){0!=m[ld]<<24>>24&&Va(a[oa>>2],eP|0,(j=h,h+=20,a[j>>2]=d,a[j+4>>2]=l,a[j+8>>2]=u,a[j+12>>2]=t,a[j+16>>2]=k,j));v=u+1|0;if((u|0)>=(a[qm>>2]|0)|0==(t|0)){break}t=c;u=2>(l%4|0)&1;if(0==(l&1|0)){var w=D[t+244>>1],A=1,B=t+246|0,w=(w<<16>>16)+(w<<16>>16<=D[a[sd>>2]+244>>1]<<16>>16&1)|0}else{w=D[t+246>>1],A=-1,B=t+244|0,w=((w<<16>>16>=D[a[sd>>2]+246>>1]<<16>>16)<<31>>31)+(w<<16>>16)|0}B=(D[B>>1]<<16>>16)+A|0;if((w|0)!=(B|0)){for(;!(fP(t,w,u,gP(t,w,w-A|0)&255),w=w+A|0,(w|0)==(B|0));){}}u^=1;uq(t,u);t=tm();(t|0)>(k|0)?u=k:(Hx(a[Xa>>2]),u=t,v=(t|0)<(b[0]=a[sm>>2],b[1]=a[sm+4>>2],f[0])*(k|0)?0:v);k=u;u=v;l=l+1|0}if(0==(t|0)){z=k;p=0;s=n;break}else{l=t,d=d+1|0}}(p|0)>(z|0)&&dP(c);0<(z|0)&&(uq(c,0),z=tm());if(0!=(i|0)&0<(s|0)){i=0}else{return h=g,z}for(;!(hP(c),i=i+1|0,(i|0)==(s|0));){}h=g;return z}function aP(b){var c,d=h,g;c=(b+228|0)>>2;if(2<=(a[c]|0)){for(var f=b+224|0,e=0,l=0;;){var k=a[a[f>>2]+(l<<2)>>2];0!=(e|0)&&(a[e+168>>2]=k);a[k+172>>2]=e;for(e=k;!(k=a[e+168>>2],0==(k|0));){e=k}l=l+1|0;if((l|0)>=(a[c]|0)){break}}a[c]=1;a[b+216>>2]=a[a[f>>2]>>2];D[b+244>>1]=a[Ex>>2]&65535;D[b+246>>1]=a[Fx>>2]&65535}l=D[b+244>>1];f=b+246|0;if(l<<16>>16<=D[f>>1]<<16>>16){c=(b+220|0)>>2;b=b+12|0;for(l=l<<16>>16;;){e=a[c];a[(e>>2)+(11*l|0)]=a[(e+8>>2)+(11*l|0)];e=a[c];a[(e+4>>2)+(11*l|0)]=a[(e+12>>2)+(11*l|0)];for(e=0;;){var n=a[c],z=a[(n>>2)+(11*l|0)];if((e|0)>=(z|0)){break}k=a[a[(n+4>>2)+(11*l|0)]+(e<<2)>>2];if(0==(k|0)){g=206;break}a[k+240>>2]=e;e=e+1|0}206==g&&(g=0,0==m[ld]<<24>>24?k=n:(Va(a[oa>>2],iP|0,(j=h,h+=16,a[j>>2]=a[b>>2],a[j+4>>2]=l,a[j+8>>2]=e,a[j+12>>2]=z,j)),k=a[c]),a[(k>>2)+(11*l|0)]=e);l=l+1|0;if((l|0)>(D[f>>1]<<16>>16|0)){break}}}h=d}function bP(b,c){Lw(b);a[b+228>>2]=1;a[a[b+224>>2]>>2]=a[b+216>>2];Dx(b);Ix(b,0);vN(b);nN(b);wN(b);sq(b);Jx(b);Kx(b);var d=tq(b,2,c),g=b+208|0;if(1>(a[g>>2]|0)){var f=d;Cx(b);return f}for(var e=b+212|0,h=1;;){if(d=bP(a[a[e>>2]+(h<<2)>>2],c)+d|0,h=h+1|0,(h|0)>(a[g>>2]|0)){f=d;break}}Cx(b);return f}function Gx(c,d){var i=h,g=a[pm>>2];0!=(g|0)&&(G(g),a[pm>>2]=0);g=a[om>>2];0!=(g|0)&&(G(g),a[om>>2]=0);var g=c+208|0,e=1>(a[g>>2]|0);a:do{if(!e){for(var Xa=c+212|0,l=1;;){if(qq(a[a[Xa>>2]+(l<<2)>>2]),l=l+1|0,(l|0)>(a[g>>2]|0)){break a}}}}while(0);g=D[c+244>>1];e=c+246|0;Xa=g<<16>>16>D[e>>1]<<16>>16;a:do{if(!Xa){for(var l=c+220|0,k=g<<16>>16;;){var n=a[l>>2],z=0<(a[(n>>2)+(11*k|0)]|0);b:do{if(z){for(var p=0,s=n;;){s=a[a[(s+4>>2)+(11*k|0)]+(p<<2)>>2];a[s+240>>2]=p;var s=s+192|0,v=a[s>>2],t=0==(v|0);c:do{if(!t){var u=a[v>>2];if(0!=(u|0)){for(var w=0,A=u,u=v;;){if(4==m[A+124|0]<<24>>24&&(yx(A),G(A|0),w=w-1|0,u=a[s>>2]),w=w+1|0,A=a[u+(w<<2)>>2],0==(A|0)){break c}}}}}while(0);p=p+1|0;s=a[l>>2];if((p|0)>=(a[(s>>2)+(11*k|0)]|0)){var B=s;break b}}}else{B=n}}while(0);n=a[(B+40>>2)+(11*k|0)];0!=(n|0)&&(G(a[n+8>>2]),G(n));k=k+1|0;if((k|0)>(D[e>>1]<<16>>16|0)){break a}}}}while(0);0!=m[ld]<<24>>24&&(B=a[oa>>2],g=a[c+12>>2],e=Jn(),Va(B,jP|0,(j=h,h+=16,a[j>>2]=g,a[j+4>>2]=d,f[0]=e,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j)));h=i}function TO(b){Cx(b);var c=b+208|0;if(1<=(a[c>>2]|0)){for(var b=b+212|0,d=1;!(TO(a[a[b>>2]+(d<<2)>>2]),d=d+1|0,(d|0)>(a[c>>2]|0));){}}}function qq(b){var c;c=b+208|0;var d=1>(a[c>>2]|0);a:do{if(!d){for(var g=b+212|0,f=1;;){if(qq(a[a[g>>2]+(f<<2)>>2]),f=f+1|0,(f|0)>(a[c>>2]|0)){break a}}}}while(0);c=(b+276|0)>>2;var e=a[c];if(0!=(e|0)){var h=D[b+244>>1],d=b+246|0;if(h<<16>>16<=D[d>>1]<<16>>16){g=b+32|0;f=b+220|0;for(h=h<<16>>16;;){var k=a[e+(h<<2)>>2],e=kP(b,k,-1),k=kP(b,k,1);a[a[c]+(h<<2)>>2]=e;e=e+240|0;a[(a[f>>2]+4>>2)+(11*h|0)]=(a[e>>2]<<2)+a[(a[a[g>>2]+220>>2]+4>>2)+(11*h|0)]|0;a[(a[f>>2]>>2)+(11*h|0)]=a[k+240>>2]+1-a[e>>2]|0;h=h+1|0;if((h|0)>(D[d>>1]<<16>>16|0)){break}e=a[c]}}}}function kP(a,b,c){var d=lP(b,c);if(0==(d|0)){var f;return b}for(;;){if(b=0==(Lx(a,d)|0)?0==(Mx(a,d)|0)?b:d:d,d=lP(d,c),0==(d|0)){f=b;break}}return f}function Nx(b,c){var d,g,f=a[c+12>>2],e=a[f+192>>2],h=0==(e|0);g=(c+16|0)>>2;a:do{if(!h){for(h=0;;){d=a[e+(h<<2)>>2];if(0==(d|0)){break a}if((a[d+12>>2]|0)==(a[g]|0)){break}else{h=h+1|0}}og(c,d);f=c+180|0;0==(a[f>>2]|0)&&(a[f>>2]=d);4==m[d+124|0]<<24>>24&&(d=d+128|0,0==(a[d>>2]|0)&&(a[d>>2]=c));d=a[g]>>2;f=a[d+52];d=0==(f|0)?Cb((a[d+53]<<2)+8|0):wb(f,(a[d+53]<<2)+8|0);a[a[g]+208>>2]=d;d=a[g]+212|0;f=a[d>>2];a[d>>2]=f+1|0;a[a[a[g]+208>>2]+(f<<2)>>2]=c;g=a[g];a[a[g+208>>2]+(a[g+212>>2]<<2)>>2]=0;return}}while(0);g=pq(f,a[g],c);m[g+124|0]=4==m[c+124|0]<<24>>24?4:3;a[g+108>>2]=a[c+108>>2];gm(b,g)}function um(b,c){var d=a[b+236>>2],g=b+240|0,f=a[g>>2],e=c+240|0,h=a[e>>2];a[g>>2]=h;g=a[sd>>2]+220|0;a[a[(a[g>>2]+4>>2)+(11*d|0)]+(h<<2)>>2]=b;a[e>>2]=f;a[a[(a[g>>2]+4>>2)+(11*d|0)]+(f<<2)>>2]=c}function kN(a){return 1==m[a+162|0]<<24>>24?2:a=2>m[a+166|0]<<24>>24&1}function Dx(b){var c,d;d=(b+246|0)>>1;var g=fa((D[d]<<16>>16<<2)+8|0);c=ra(b);var f=0==(c|0);a:do{if(!f){for(var e=c;;){var h=(a[e+236>>2]<<2)+g|0;a[h>>2]=a[h>>2]+1|0;var h=Ib(b,e),k=0==(h|0);b:do{if(!k){for(var j=h;;){var m=a[a[j+16>>2]+236>>2],p=a[a[j+12>>2]+236>>2],s=(m|0)>(p|0),v=s?m:p,m=(s?p:m)+1|0,p=(m|0)<(v|0);c:do{if(p){for(s=m;;){var t=(s<<2)+g|0;a[t>>2]=a[t>>2]+1|0;s=s+1|0;if((s|0)>=(v|0)){break c}}}}while(0);j=yb(b,j);if(0==(j|0)){break b}}}}while(0);e=ba(b,e);if(0==(e|0)){break a}}}}while(0);f=fa(44*(D[d]<<16>>16)+88|0);c=(b+220|0)>>2;a[c]=f;b=D[b+244>>1];if(b<<16>>16<=D[d]<<16>>16){for(b=b<<16>>16;;){e=(b<<2)+g|0;h=a[e>>2];a[(f>>2)+(11*b|0)]=h;a[(a[c]+8>>2)+(11*b|0)]=h;f=fa((a[e>>2]<<2)+4|0);a[(a[c]+4>>2)+(11*b|0)]=f;a[(a[c]+12>>2)+(11*b|0)]=f;b=b+1|0;if((b|0)>(D[d]<<16>>16|0)){break}f=a[c]}}G(g)}function Ix(b,c){var d,g=h,f=ZE(a[b+240>>2]),e=b+216|0;d=a[e>>2];var l=0==(d|0);a:do{if(!l){for(var k=d;;){if(m[k+163|0]=0,k=a[k+168>>2],0==(k|0)){break a}}}}while(0);l=b+244|0;k=D[l>>1];d=(b+246|0)>>1;var n=k<<16>>16>D[d]<<16>>16;a:do{if(!n){for(var z=b+220|0,p=k<<16>>16;;){if(a[(a[z>>2]>>2)+(11*p|0)]=0,p=p+1|0,(p|0)>(D[d]<<16>>16|0)){break a}}}}while(0);e=a[e>>2];k=0==(e|0);a:do{if(!k){for(var n=0==(c|0),z=b,p=f,s=e;;){var v=0==(a[a[(n?s+176|0:s+184|0)>>2]>>2]|0);b:do{if(v){var t=s+163|0;if(0==m[t]<<24>>24&&(m[t]=1,Rk(f,s),t=Sk(f),0!=(t|0))){for(;;){if(7==m[t+165|0]<<24>>24?AN(z,t,c,p):(Rw(b,t),Sw(f,t,c)),t=Sk(f),0==(t|0)){break b}}}}}while(0);s=a[s+168>>2];if(0==(s|0)){break a}}}}while(0);0!=(Sk(f)|0)&&la(1,mP|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j));l=D[l>>1];e=l<<16>>16>D[d]<<16>>16;a:do{if(!e){k=b+152|0;n=b+220|0;z=a[sd>>2];for(p=l<<16>>16;;){m[a[z+220>>2]+44*p+33|0]=0;s=0==(a[k>>2]&1|0);b:do{if(!s&&(v=a[n>>2],t=a[(v>>2)+(11*p|0)],0<(t|0))){for(var v=a[(v+4>>2)+(11*p|0)],t=t-1|0,u=(t|0)/2&-1,w=0;;){if(um(a[v+(w<<2)>>2],a[v+(t-w<<2)>>2]),w=w+1|0,(w|0)>(u|0)){break b}}}}while(0);p=p+1|0;if((p|0)>(D[d]<<16>>16|0)){break a}}}}while(0);(a[b+32>>2]|0)==(b|0)&&0<(tm()|0)&&uq(b,0);G(a[f>>2]);G(f);h=g}function Sw(b,c,d){if(0==(d|0)){var d=c+188|0,g=a[d>>2];if(0<(g|0)){for(var c=c+184|0,f=0;;){var e=a[a[c>>2]+(f<<2)>>2]+12|0,h=a[e>>2]+163|0;0==m[h]<<24>>24&&(m[h]=1,Rk(b,a[e>>2]),g=a[d>>2]);f=f+1|0;if((f|0)>=(g|0)){break}}}}else{if(d=c+180|0,g=a[d>>2],0<(g|0)){c=c+176|0;for(f=0;!(e=a[a[c>>2]+(f<<2)>>2]+16|0,h=a[e>>2]+163|0,0==m[h]<<24>>24&&(m[h]=1,Rk(b,a[e>>2]),g=a[d>>2]),f=f+1|0,(f|0)>=(g|0));){}}}}function tm(){var b,c=a[sd>>2],d=D[c+244>>1],g=c+246|0,f=D[g>>1];if(d<<16>>16>=f<<16>>16){var e;return 0}b=(c+220|0)>>2;for(var h=0,d=d<<16>>16;;){var k=a[b];0==m[k+44*d+33|0]<<24>>24?(f=nP(c,d),a[(a[b]+36>>2)+(11*d|0)]=f,m[a[b]+44*d+33|0]=1,k=f,f=D[g>>1]):k=a[(k+36>>2)+(11*d|0)];h=k+h|0;d=d+1|0;if((d|0)>=(f<<16>>16|0)){e=h;break}}return e}function uq(b,c){var d,g=b+244|0,f=D[g>>1];d=(b+246|0)>>1;var e=D[d],h=f<<16>>16>e<<16>>16;a:do{if(h){var k=e,j=b+220|0}else{for(var z=b+220|0,p=f<<16>>16;;){m[a[z>>2]+44*p+32|0]=1;var p=p+1|0,s=D[d];if((p|0)>(s<<16>>16|0)){k=s;j=z;break a}}}}while(0);for(f=k;;){e=D[g>>1];if(e<<16>>16>f<<16>>16){break}e=e<<16>>16;for(h=0;;){if(0==m[a[j>>2]+44*e+32|0]<<24>>24){var v=h,t=f}else{v=oP(b,e,c)+h|0,t=D[d]}f=e+1|0;if((f|0)>(t<<16>>16|0)){break}else{e=f,h=v,f=t}}if(0<(v|0)){f=t}else{break}}}function nP(b,c){var d,g;d=(b+220|0)>>2;var f=a[d],e=a[(f+4>>2)+(11*c|0)],h=c+1|0,k=a[(a[a[sd>>2]+220>>2]>>2)+(11*h|0)];(a[Ox>>2]|0)>(k|0)||(f=k+1|0,a[Ox>>2]=f,k=a[Gj>>2],f=0==(k|0)?Cb(f<<2):wb(k,f<<2),a[Gj>>2]=f,f=a[d]);k=0<(a[(f>>2)+(11*h|0)]|0);a:do{if(k){for(var j=a[Gj>>2],z=0;;){a[j+(z<<2)>>2]=0;var z=z+1|0,p=a[d];if((z|0)>=(a[(p>>2)+(11*h|0)]|0)){var s=p;break a}}}else{s=f}}while(0);f=0<(a[(s>>2)+(11*c|0)]|0);a:do{if(f){for(var v=k=0,j=0,z=s;;){var t=0<(k|0),u=(j<<2)+e|0,w=a[a[u>>2]+184>>2],p=a[w>>2];b:do{if(t){if(0==(p|0)){var A=k,B=z,C=v}else{for(var y=a[Gj>>2],F=0,E=v,G=p;;){var I=a[a[G+12>>2]+240>>2]+1|0,H=(I|0)>(k|0);c:do{if(H){var O=E}else{for(var J=D[G+162>>1]<<16>>16,K=E,L=I;;){if(K=J*a[y+(L<<2)>>2]+K|0,L=L+1|0,(L|0)>(k|0)){O=K;break c}}}}while(0);F=F+1|0;G=a[w+(F<<2)>>2];if(0==(G|0)){var N=O;g=390;break b}else{E=O}}}}else{N=v,g=390}}while(0);do{if(390==g){g=0;if(0==(p|0)){A=k,B=z}else{A=a[Gj>>2];B=1;C=k;for(v=p;;){var t=a[a[v+12>>2]+240>>2],Q=(t|0)>(C|0)?t:C,C=(t<<2)+A|0;a[C>>2]=(D[v+162>>1]<<16>>16)+a[C>>2]|0;v=a[a[a[u>>2]+184>>2]+(B<<2)>>2];if(0==(v|0)){break}B=B+1|0;C=Q}A=Q;B=a[d]}C=N}}while(0);j=j+1|0;u=a[(B>>2)+(11*c|0)];if((j|0)<(u|0)){k=A,v=C,z=B}else{break}}if(0<(u|0)){k=a[(B+4>>2)+(11*c|0)];j=C;for(z=0;;){if(p=a[k+(z<<2)>>2],j=0==m[p+161|0]<<24>>24?j:Px(a[p+184>>2],1)+j|0,z=z+1|0,(z|0)>=(u|0)){S=j;R=B;break a}}}else{var S=C,R=B}}else{S=0,R=s}}while(0);d=a[(R>>2)+(11*h|0)];if(0>=(d|0)){var U;return S}h=a[(R+4>>2)+(11*h|0)];for(R=0;;){if(g=a[h+(R<<2)>>2],S=0==m[g+161|0]<<24>>24?S:Px(a[g+176>>2],-1)+S|0,R=R+1|0,(R|0)>=(d|0)){U=S;break}}return U}function Rw(b,c){var d,g,f=h,e=a[c+236>>2];g=(b+220|0)>>2;d=a[g]>>2;var l=a[d+(11*e|0)];if(1>(a[d+(11*e|0)+2]|0)){var k=a[c+12>>2];la(1,pP|0,(j=h,h+=16,a[j>>2]=a[b+12>>2],a[j+4>>2]=k,a[j+8>>2]=e,a[j+12>>2]=l,j));S()}a[a[d+(11*e|0)+1]+(l<<2)>>2]=c;d=c+240|0;a[d>>2]=l;l=a[g]+44*e|0;a[l>>2]=a[l>>2]+1|0;l=a[g];(a[(l>>2)+(11*e|0)]|0)>(a[(l+8>>2)+(11*e|0)]|0)&&sa(rg|0,1028,qP|0,rP|0);l=a[d>>2];d=a[(a[a[sd>>2]+220>>2]+8>>2)+(11*e|0)];(l|0)>(d|0)&&S();(e|0)<(D[b+244>>1]<<16>>16|0)&&S();(e|0)>(D[b+246>>1]<<16>>16|0)&&S();g=a[g];((l<<2)+a[(g+4>>2)+(11*e|0)]|0)>>>0>((d<<2)+a[(g+12>>2)+(11*e|0)]|0)>>>0?S():h=f}function Px(c,d){var i,g=0<(d|0),e=a[c>>2];if(0==(e|0)){var h;return 0}var j=0,k=0;a:for(;;){var k=k+1|0,n=a[c+(k<<2)>>2],m=0==(n|0);b:do{if(g){if(m){h=j;i=436;break a}for(var p=a[a[e+12>>2]+240>>2],s=e+28|0,s=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]),v=e+162|0,t=j,u=k,w=n;;){var A=w+28|0,t=0>(a[a[w+12>>2]+240>>2]-p|0)*((b[0]=a[A>>2],b[1]=a[A+4>>2],f[0])-s)?(D[w+162>>1]<<16>>16)*(D[v>>1]<<16>>16)+t|0:t,u=u+1|0,w=a[c+(u<<2)>>2];if(0==(w|0)){var B=t;break b}}}else{if(m){h=j;i=437;break a}p=a[a[e+16>>2]+240>>2];s=e+68|0;s=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]);v=e+162|0;t=j;u=k;for(w=n;;){if(A=w+68|0,t=0>(a[a[w+16>>2]+240>>2]-p|0)*((b[0]=a[A>>2],b[1]=a[A+4>>2],f[0])-s)?(D[w+162>>1]<<16>>16)*(D[v>>1]<<16>>16)+t|0:t,u=u+1|0,w=a[c+(u<<2)>>2],0==(w|0)){B=t;break b}}}}while(0);if(m){h=B;i=438;break}else{j=B,e=n}}if(436==i||437==i||438==i){return h}}function vq(b,c,d){var g=a[c+216>>2],f=a[d+216>>2],e=(g|0)!=(f|0);if(m[rq]){if(e){return 1}}else{if(!(0==(g|0)|e^1|0==(f|0))){return 7==m[c+165|0]<<24>>24&&1==m[c+162|0]<<24>>24||7==m[d+165|0]<<24>>24&&1==m[d+162|0]<<24>>24?0:1}}g=a[(a[b+220>>2]+40>>2)+(11*a[c+236>>2]|0)];if(0==(g|0)){return 0}b=0==(a[b+152>>2]&1|0);return c=m[a[g+8>>2]+a[g+4>>2]*a[(b?c:d)+284>>2]+a[(b?d:c)+284>>2]|0]<<24>>24}function vm(c,d){var i,g=a[d>>2];if(0==(g|0)){var e;return 0}for(var h=a[c+176>>2],j=a[h>>2],k=0==(j|0),n=d,m=0;;){var p=D[g+162>>1]<<16>>16,s=a[a[g+16>>2]+240>>2];a:do{if(k){var v=m}else{for(var t=g+28|0,u=h,w=m,A=j;;){var B=a[a[A+16>>2]+240>>2];if(0<(B-s|0)){i=464}else{if((B|0)!=(s|0)){var C=w}else{A=A+28|0,(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0])>(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0])?i=464:C=w}}464==i&&(i=0,C=(D[a[u>>2]+162>>1]<<16>>16)*p+w|0);u=u+4|0;A=a[u>>2];if(0==(A|0)){v=C;break a}else{w=C}}}}while(0);n=n+4|0;g=a[n>>2];if(0==(g|0)){e=v;break}else{m=v}}return e}function wm(c,d){var i,g=a[d>>2];if(0==(g|0)){var e;return 0}for(var h=a[c+184>>2],j=a[h>>2],k=0==(j|0),n=d,m=0;;){var p=D[g+162>>1]<<16>>16,s=a[a[g+12>>2]+240>>2];a:do{if(k){var v=m}else{for(var t=g+68|0,u=h,w=m,A=j;;){var B=a[a[A+12>>2]+240>>2];if(0<(B-s|0)){i=477}else{if((B|0)!=(s|0)){var C=w}else{A=A+68|0,(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0])>(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0])?i=477:C=w}}477==i&&(i=0,C=(D[a[u>>2]+162>>1]<<16>>16)*p+w|0);u=u+4|0;A=a[u>>2];if(0==(A|0)){v=C;break a}else{w=C}}}}while(0);n=n+4|0;g=a[n>>2];if(0==(g|0)){e=v;break}else{m=v}}return e}function sP(b){for(b>>=2;;){var c=a[b+32];if(0==(c|0)){break}else{b=c,b>>=2}}return(a[a[b+4]+216>>2]|0)!=(a[a[b+3]+216>>2]|0)&1}function lP(b,c){var d=a[b+240>>2];return 0<=(c|0)?d=a[a[(a[a[sd>>2]+220>>2]+4>>2)+(11*a[b+236>>2]|0)]+(d+1<<2)>>2]:0>=(d|0)?0:d=a[a[(a[a[sd>>2]+220>>2]+4>>2)+(11*a[b+236>>2]|0)]+(d-1<<2)>>2]}function oP(b,c,d){var g,f,e,h;h=(b+220|0)>>2;m[a[h]+44*c+32|0]=0;var k=0<(c|0),j=c+1|0,d=0!=(d|0),z=b+244|0,p=c-1|0,s=b+246|0;e=g=0;a:for(;;){for(f=e;;){var v=a[h];if((f|0)>=(a[(v>>2)+(11*c|0)]-1|0)){break a}var t=a[(v+4>>2)+(11*c|0)],v=a[t+(f<<2)>>2];e=v>>2;var u=f+1|0,t=a[t+(u<<2)>>2];f=t>>2;(a[e+60]|0)<(a[f+60]|0)||sa(rg|0,523,tP|0,Qx|0);if(0!=(vq(b,v,t)|0)){f=u}else{if(k){var w=vm(v,a[f+44]),A=vm(t,a[e+44])}else{w=A=0}0<(a[(a[h]>>2)+(11*j|0)]|0)&&(w=wm(v,a[f+46])+w|0,A=wm(t,a[e+46])+A|0);if((A|0)<(w|0)){break}if(d&0<(w|0)&(A|0)==(w|0)){break}else{f=u}}}um(v,t);e=w-A+g|0;g=(a[sd>>2]+220|0)>>2;m[a[g]+44*c+33|0]=0;m[a[h]+44*c+32|0]=1;(D[z>>1]<<16>>16|0)<(c|0)&&(m[a[g]+44*p+33|0]=0,m[a[h]+44*p+32|0]=1);(D[s>>1]<<16>>16|0)>(c|0)&&(m[a[g]+44*j+33|0]=0,m[a[h]+44*j+32|0]=1);g=e;e=u}return g}function sq(b){var c=h,d=a[Yu>>2];if(0!=(d|0)|0!=(a[El>>2]|0)){if(d=jc(b|0,d,0),0==(d|0)){var g=a[b+36>>2],d=a[g+20>>2],g=Ib(d,g),f=0==(g|0);a:do{if(!f){for(var e=g;;){var l=Yd(a[e+12>>2]);0==(0==(td(a[l+12>>2],Rx|0,7)|0)&1|0)&&sq(l);e=yb(d,e);if(0==(e|0)){break a}}}}while(0);if(0!=(a[El>>2]|0)){d=h;g=ra(b);if(0!=(g|0)){for(;;){f=g;e=jc(g|0,a[El>>2],0);l=0==(e|0);a:do{if(!l){var k=m[e];do{if(105==k<<24>>24){if(0==(ka(e,Ep|0)|0)){wq(b,f,0);break a}}else{if(111==k<<24>>24){if(0==(ka(e,Sx|0)|0)){wq(b,f,1);break a}}else{if(0==k<<24>>24){break a}}}}while(0);k=a[g+12>>2];la(1,uP|0,(j=h,h+=8,a[j>>2]=e,a[j+4>>2]=k,j))}}while(0);g=ba(b,g);if(0==(g|0)){break}}}h=d}}else{g=m[d];if(111==g<<24>>24){if(0==(ka(d,Sx|0)|0)){vP(b,1);h=c;return}}else{if(105==g<<24>>24){if(0==(ka(d,Ep|0)|0)){vP(b,0);h=c;return}}else{if(0==g<<24>>24){h=c;return}}}la(1,wP|0,(j=h,h+=4,a[j>>2]=d,j))}}h=c}function vP(a,b){var c=ra(a);if(0!=(c|0)){for(;!(wq(a,c,b),c=ba(a,c),0==(c|0));){}}}function wq(b,c,d){var g,f=a[om>>2];g=f>>2;if(0==(a[c+216>>2]|0)){d=0!=(d|0);a:do{if(d){var e=c+184|0,h=a[e>>2],k=a[h>>2];if(0==(k|0)){return}for(var j=0,z=0;;){if(0==(sP(k)|0)&&(a[(j<<2>>2)+g]=k,j=j+1|0,h=a[e>>2]),z=z+1|0,k=a[h+(z<<2)>>2],0==(k|0)){var p=j;break a}}}else{e=c+176|0;h=a[e>>2];k=a[h>>2];if(0==(k|0)){return}for(z=j=0;;){if(0==(sP(k)|0)&&(a[(j<<2>>2)+g]=k,j=j+1|0,h=a[e>>2]),z=z+1|0,k=a[h+(z<<2)>>2],0==(k|0)){p=j;break a}}}}while(0);if(2<=(p|0)){a[(p<<2>>2)+g]=0;ch(f,p,120);for(c=1;;){p=a[(c<<2>>2)+g];if(0==(p|0)){break}f=a[(c-1<<2>>2)+g];d?(p=p+12|0,f=f+12|0):(p=p+16|0,f=f+16|0);p=a[p>>2];f=a[f>>2];if(0!=(oN(f,p)|0)){break}f=pq(f,p,0);m[f+124|0]=4;gm(b,f);c=c+1|0}}}}function Lx(a,b){return(0==m[b+162|0]<<24>>24?0!=(Ed(a,b|0)|0):0)&1}function Mx(b,c){do{if(1==m[c+162|0]<<24>>24&&1==(a[c+180>>2]|0)&&1==(a[c+188>>2]|0)){for(var d=a[c+184>>2];;){var g=a[d>>2];if(0==m[g+124|0]<<24>>24){break}else{d=g+128|0}}if(0!=(Ed(b,g|0)|0)){return d=1}}}while(0);return 0}function Hx(c){if(0!=(c|0)){for(;;){var d=c+32|0;f[0]=a[c+240>>2]|0;a[d>>2]=b[0];a[d+4>>2]=b[1];c=a[c+168>>2];if(0==(c|0)){break}}}}function Jx(b){var c,d=D[b+244>>1],g=b+246|0;if(d<<16>>16<=D[g>>1]<<16>>16){c=(b+220|0)>>2;for(var d=d<<16>>16,f=a[c];;){var e=0<(a[(f>>2)+(11*d|0)]|0);a:do{if(e){for(var h=0,k=0,j=f;;){var z=a[a[(j+4>>2)+(11*d|0)]+(k<<2)>>2];m[z+164|0]=0;m[z+163|0]=0;a[z+284>>2]=k;if(0<(a[z+196>>2]|0)&0==(h|0)){var p=a[(a[c]>>2)+(11*d|0)],z=p,s=cc,h=fa(12),s=h>>2;a[s]=z;a[s+1]=p;a[s+2]=fa(p*z|0);a[(a[c]+40>>2)+(11*d|0)]=h;z=1}else{z=h}k=k+1|0;p=a[c];s=a[(p>>2)+(11*d|0)];if((k|0)<(s|0)){h=z,j=p}else{break}}if(0!=(z|0)&0<(s|0)){k=0;for(h=p;;){if(j=a[a[(h+4>>2)+(11*d|0)]+(k<<2)>>2],0==m[j+163|0]<<24>>24&&(Tx(b,j),h=a[c]),k=k+1|0,(k|0)>=(a[(h>>2)+(11*d|0)]|0)){v=h;break a}}}else{var v=p}}else{v=f}}while(0);d=d+1|0;if((d|0)>(D[g>>1]<<16>>16|0)){break}else{f=v}}}}function Kx(b){var c;if(0!=m[b+248|0]<<24>>24){var d=D[b+244>>1],g=b+246|0;if(d<<16>>16<=D[g>>1]<<16>>16){c=(b+220|0)>>2;for(var f=b+152|0,e=0,d=d<<16>>16;;){var h=a[c];if(0<(a[(h>>2)+(11*d|0)]|0)){for(var j=0;;){m[a[a[(h+4>>2)+(11*d|0)]+(j<<2)>>2]+163|0]=0;var n=j+1|0,h=a[c];if((n|0)<(a[(h>>2)+(11*d|0)]|0)){j=n}else{break}}j=(n<<2)+4|0}else{j=4}var e=j=0==(e|0)?Cb(j):wb(e,j),h=a[c],z=0<(a[(h>>2)+(11*d|0)]|0);a:do{if(z){for(var p=0,s=0,v=h;;){var t=a[a[(v+4>>2)+(11*d|0)]+(s<<2)>>2],u=t+204|0,w=a[u>>2],v=0<(w|0);b:do{if(v){for(var A=t+200|0,B=0,C=0,y=w;;){var F=a[a[A>>2]+(C<<2)>>2];0>2]&&(B=(0!=(Lx(b,a[F+16>>2])|Mx(b,a[F+16>>2])|0)&1)+B|0,y=a[u>>2]);C=C+1|0;if((C|0)>=(y|0)){var E=B;break b}}}else{E=0}}while(0);u=t+196|0;w=a[u>>2];v=0<(w|0);b:do{if(v){A=t+192|0;C=B=0;for(y=w;;){if(F=a[a[A>>2]+(C<<2)>>2],0>2]&&(B=(0!=(Lx(b,a[F+12>>2])|Mx(b,a[F+12>>2])|0)&1)+B|0,y=a[u>>2]),C=C+1|0,(C|0)>=(y|0)){var I=B;break b}}}else{I=0}}while(0);do{if(0==(I|E|0)){a[e+(p<<2)>>2]=t,u=p+1|0}else{if(0!=m[t+163|0]<<24>>24|0!=(E|0)){u=p}else{u=(p<<2)+e|0;w=Ux(b,t,u,d);v=0==(a[f>>2]&1|0);b:do{if(v&&(A=p-1+w|0,(p|0)<(A|0))){A=(A<<2)+e|0;for(B=u;;){if(C=a[B>>2],a[B>>2]=a[A>>2],a[A>>2]=C,B=B+4|0,A=A-4|0,B>>>0>=A>>>0){break b}}}}while(0);u=w+p|0}}}while(0);s=s+1|0;t=a[c];w=a[(t>>2)+(11*d|0)];if((s|0)<(w|0)){p=u,v=t}else{break}}if(0!=(u|0)&0<(w|0)){p=a[sd>>2];s=0;for(v=t;;){var H=a[e+(s<<2)>>2];a[a[(v+4>>2)+(11*d|0)]+(s<<2)>>2]=H;a[H+240>>2]=(a[(a[c]+4>>2)+(11*d|0)]-a[(a[p+220>>2]+4>>2)+(11*d|0)]>>2)+s|0;var s=s+1|0,H=a[c],J=a[(H>>2)+(11*d|0)];if((s|0)<(J|0)){v=H}else{break}}if(0<(J|0)){p=0;for(s=H;;){v=a[a[(s+4>>2)+(11*d|0)]+(p<<2)>>2]+192|0;A=a[v>>2];if(0==(A|0)){B=s}else{if(C=a[A>>2],0==(C|0)){B=s}else{B=0;y=C;for(C=A;!((a[a[y+12>>2]+240>>2]|0)<(a[a[y+16>>2]+240>>2]|0)&&(yx(y),Nx(b,y),B=B-1|0,C=a[v>>2]),B=B+1|0,y=a[C+(B<<2)>>2],0==(y|0));){}B=a[c]}}p=p+1|0;if((p|0)<(a[(B>>2)+(11*d|0)]|0)){s=B}else{break a}}}}}}while(0);m[a[a[sd>>2]+220>>2]+44*d+33|0]=0;d=d+1|0;if((d|0)>(D[g>>1]<<16>>16|0)){break}}0!=(j|0)&&G(j)}}}function dP(c){var d=a[c+216>>2],e=0==(d|0);a:do{if(!e){for(var g=d;;){var h=g+32|0;a[g+240>>2]=(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0])&-1;g=a[g+168>>2];if(0==(g|0)){break a}}}}while(0);e=D[c+244>>1];d=c+246|0;if(e<<16>>16<=D[d>>1]<<16>>16){c=c+220|0;for(e=e<<16>>16;!(m[a[a[sd>>2]+220>>2]+44*e+33|0]=0,g=a[c>>2],ch(a[(g+4>>2)+(11*e|0)],a[(g>>2)+(11*e|0)],380),e=e+1|0,(e|0)>(D[d>>1]<<16>>16|0));){}}}function hP(b){var c,d,g,f=D[b+246>>1],e=b+244|0;if(f<<16>>16>=D[e>>1]<<16>>16){g=(b+220|0)>>2;for(var f=f<<16>>16,h=a[g];;){m[h+44*f+32|0]=0;var h=a[g],j=0<(a[(h>>2)+(11*f|0)]-1|0);a:do{if(j){for(var n=0<(f|0),z=f+1|0,p=0,s=h;;){var v=a[(s+4>>2)+(11*f|0)],s=a[v+(p<<2)>>2];d=s>>2;p=p+1|0;v=a[v+(p<<2)>>2];c=v>>2;(a[d+60]|0)<(a[c+60]|0)||sa(rg|0,474,xP|0,Qx|0);if(0==(vq(b,s,v)|0)){if(n){var t=vm(s,a[c+44]),u=vm(v,a[d+44])}else{u=t=0}0<(a[(a[g]>>2)+(11*z|0)]|0)?(c=wm(s,a[c+46])+t|0,d=wm(v,a[d+46])+u|0):(c=t,d=u);(d|0)>(c|0)||yP(b,f,s,v)}s=a[g];if((p|0)>=(a[(s>>2)+(11*f|0)]-1|0)){var w=s;break a}}}else{w=h}}while(0);f=f-1|0;if((f|0)<(D[e>>1]<<16>>16|0)){break}else{h=w}}}}function yP(b,c,d,g){var f,e=m[d+162|0];if(e<<24>>24!=m[g+162|0]<<24>>24){var b=b+220|0,h=a[b>>2],j=a[(h>>2)+(11*c|0)],n=0<(j|0);do{if(n){for(var z=a[(h+4>>2)+(11*c|0)],p=0,s=0,v=0;;){var t=0==m[a[z+(v<<2)>>2]+162|0]<<24>>24&1,u=t+p|0,t=(t^1)+s|0,v=v+1|0;if((v|0)<(j|0)){p=u,s=t}else{break}}z=0==e<<24>>24;if((u|0)<(t|0)){var w=z?d:g}else{var A=z;f=724}}else{A=0==e<<24>>24,f=724}}while(0);724==f&&(w=A?g:d);f=h+44*c+4|0;a:do{if(n){e=a[f>>2];for(u=0;;){var B;B=(a[e+(u<<2)>>2]|0)==(w|0)?u:B;u=u+1|0;if((u|0)>=(j|0)){var C=B,y=f;break a}}}else{y=f}}while(0);B=0==m[w+162|0]<<24>>24&1;f=C;for(n=0;;){e=f-1|0;if(0>=(f|0)){var D=C,F=0;break}if((m[a[a[y>>2]+(e<<2)>>2]+162|0]<<24>>24|0)==(B|0)){f=e,n=n+1|0}else{D=C;F=0;break}}for(;;){D=D+1|0;if((D|0)>=(j|0)){break}if((m[a[a[y>>2]+(D<<2)>>2]+162|0]<<24>>24|0)==(B|0)){F=F+1|0}else{break}}um(d,g);j=a[b>>2];y=a[(j>>2)+(11*c|0)];b=0<(y|0);c=j+44*c+4|0;a:do{if(b){j=a[c>>2];D=C;for(f=0;;){if(D=(a[j+(f<<2)>>2]|0)==(w|0)?f:D,f=f+1|0,(f|0)>=(y|0)){var E=D,G=c;break a}}}else{E=C,G=c}}while(0);C=E;for(w=0;;){c=C-1|0;if(0>=(C|0)){var I=E,H=0;break}if((m[a[a[G>>2]+(c<<2)>>2]+162|0]<<24>>24|0)==(B|0)){C=c,w=w+1|0}else{I=E;H=0;break}}for(;;){E=I+1|0;if((E|0)>=(y|0)){break}if((m[a[a[G>>2]+(E<<2)>>2]+162|0]<<24>>24|0)==(B|0)){I=E,H=H+1|0}else{break}}G=w-H|0;F=n-F|0;((-1<(G|0)?G:-G|0)|0)>((-1<(F|0)?F:-F|0)|0)&&um(d,g)}}function zP(b){var c,d,b=b>>2;if(0<(a[b+51]|0)){d=a[b+50]>>2;var g=a[a[d]+16>>2],f=a[d+1],e=0==(f|0);a:do{if(e){c=g}else{for(var h=1,j=g,n=f;;){if(n=a[n+16>>2],j=(a[n+240>>2]|0)>(a[j+240>>2]|0)?n:j,h=h+1|0,n=a[(h<<2>>2)+d],0==(n|0)){c=j;break a}}}}while(0);c=a[c+244>>2];if(-1>=(c|0)){return 1}a[b+61]=c+1|0;return 0}if(0>=(a[b+49]|0)){return 1}c=a[b+48]>>2;g=a[a[c]+12>>2];f=a[c+1];e=0==(f|0);a:do{if(e){d=g}else{h=1;j=g;for(n=f;;){if(n=a[n+12>>2],j=(a[n+240>>2]|0)<(a[j+240>>2]|0)?n:j,h=h+1|0,n=a[(h<<2>>2)+c],0==(n|0)){d=j;break a}}}}while(0);c=a[d+244>>2];if(0>=(c|0)){return 1}a[b+61]=c-1|0;return 0}function gP(b,c,d){var g,f,e,h=a[pm>>2];f=h>>2;var b=(b+220|0)>>2,j=a[b],n=a[(j+4>>2)+(11*c|0)];if(0>=(a[(j>>2)+(11*c|0)]|0)){var z;return 0}for(var d=(d|0)>(c|0),j=h+4|0,p=0;;){var s=a[n+(p<<2)>>2];g=s>>2;a:do{if(d){var v=s+184|0,t=a[v>>2],u=a[t>>2];if(0==(u|0)){e=778}else{for(var w=0,A=0;;){if(0>1]<<16>>16&&(a[(A<<2>>2)+f]=m[u+100|0]&255|a[a[u+12>>2]+240>>2]<<8,A=A+1|0,t=a[v>>2]),w=w+1|0,u=a[t+(w<<2)>>2],0==(u|0)){var B=A;e=777;break a}}}}else{if(v=s+176|0,t=a[v>>2],u=a[t>>2],0==(u|0)){e=778}else{for(A=w=0;;){if(0>1]<<16>>16&&(a[(A<<2>>2)+f]=m[u+60|0]&255|a[a[u+16>>2]+240>>2]<<8,A=A+1|0,t=a[v>>2]),w=w+1|0,u=a[t+(w<<2)>>2],0==(u|0)){B=A;e=777;break a}}}}}while(0);777==e&&(e=0,0==(B|0)?e=778:2==(B|0)?a[g+61]=(a[j>>2]+a[f]|0)/2&-1:1==(B|0)?a[g+61]=a[f]:(ch(h,B,18),w=(B|0)/2&-1,0!=(B&1|0)?a[g+61]=a[(w<<2>>2)+f]:(s=a[(w<<2>>2)+f],v=a[(B-1<<2>>2)+f]-s|0,w=a[(w-1<<2>>2)+f],A=w-a[f]|0,a[g+61]=(A|0)==(v|0)?(w+s|0)/2&-1:(A*s+w*v|0)/(A+v|0)&-1)));778==e&&(e=0,a[g+61]=-1);p=p+1|0;g=a[b];s=a[(g>>2)+(11*c|0)];if((p|0)>=(s|0)){break}}if(0<(s|0)){e=f=0,h=g}else{return 0}for(;;){if(B=a[n+(f<<2)>>2],0==(a[B+188>>2]|0)&&0==(a[B+180>>2]|0)&&(e=(zP(B)|e&255)&255,h=a[b]),f=f+1|0,(f|0)>=(a[(h>>2)+(11*c|0)]|0)){z=e;break}}return z}function fP(b,c,d,g){var f,e=a[b+220>>2],h=a[(e+4>>2)+(11*c|0)],j=a[(e>>2)+(11*c|0)];if(0<(j|0)){for(var e=0==(d|0),d=0==(g|d|0),n=0,g=(j<<2)+h|0;;){var j=j-1|0,z=h,p=n;a:for(;;){n=z;b:for(;;){if(n>>>0>=g>>>0){break a}for(;;){if(n>>>0>=g>>>0){break a}var s=a[n>>2],v=a[s+244>>2];if(0>(v|0)){n=n+4|0}else{var t=n,u=0;break}}for(;;){n=0==u<<24>>24;for(z=t;;){var w=z+4|0;if(w>>>0>=g>>>0){break a}var A=a[w>>2];f=A>>2;if(n){break}if(0==(a[f+54]|0)){break}else{z=w}}if(0!=(vq(b,s,A)|0)){n=w;continue b}var B=a[f+61];if(-1<(B|0)){break}t=w;u=0==(a[f+54]|0)?u:1}if((v|0)<=(B|0)&((v|0)!=(B|0)|e)){n=w}else{break}}um(s,A);z=w;p=p+1|0}if(0<(j|0)){n=p,g=d?g-4|0:g}else{break}}0!=(p|0)&&(b=a[sd>>2]+220|0,m[a[b>>2]+44*c+33|0]=0,0<(c|0)&&(m[a[b>>2]+44*(c-1)+33|0]=0))}}function Ux(b,c,d,g){m[c+163|0]=1;var f=0<(a[c+196>>2]|0);a:do{if(f){var e=c+192|0,h=a[a[e>>2]>>2];if(0==(h|0)){var j=0}else{for(var n=b,z=0,p=0;;){if(0!=ib[h+164>>2]){var s=h+12|0,v=a[s>>2];0==m[v+162|0]<<24>>24&0==(Ed(n,v|0)|0)||(s=a[s>>2],p=(a[s+216>>2]|0)!=(a[a[h+16>>2]+216>>2]|0)?p:0!=m[s+163|0]<<24>>24?p:Ux(b,s,(p<<2)+d|0,g)+p|0)}z=z+1|0;h=a[a[e>>2]+(z<<2)>>2];if(0==(h|0)){j=p;break a}}}}else{j=0}}while(0);if((a[c+236>>2]|0)==(g|0)){return a[((j<<2)+d|0)>>2]=c,j+1|0}sa(rg|0,1161,AP|0,BP|0);a[((j<<2)+d|0)>>2]=c;return j+1|0}function Tx(b,c){var d,g,f,e,h=a[(a[b+220>>2]+40>>2)+(11*a[c+236>>2]|0)];m[c+163|0]=1;var j=c+164|0;m[j]=1;var n=0<(a[a[b+32>>2]+208>>2]|0),z=c+192|0;f=a[z>>2];if(0!=(f|0)){var p=a[f>>2];if(0!=(p|0)){var s=h|0;f=(h+4|0)>>2;var h=h+8|0,v=0;for(g=p>>2;;){n?0==(Ed(b,a[g+4]|0)|0)?d=v:0==(Ed(b,a[g+3]|0)|0)?d=v:e=842:e=842;if(842==e){e=0;if(0!=ib[g+41]){d=(p+12|0)>>2;g=a[d];var t=(a[g+284>>2]|0)<(a[s>>2]|0);if(1==m[g+164|0]<<24>>24){t||sa(rg|0,933,xm|0,Vx|0);g=p+16|0;var t=a[a[g>>2]+284>>2],u=a[f];(t|0)<(u|0)?g=t:(sa(rg|0,934,xm|0,Wx|0),u=a[f],g=a[a[g>>2]+284>>2]);m[a[h>>2]+g+u*a[a[d]+284>>2]|0]=1;yx(p);v=v-1|0;4!=m[p+124|0]<<24>>24&&Nx(b,p)}else{t||sa(rg|0,942,xm|0,Vx|0),g=p+16|0,t=a[a[g>>2]+284>>2],p=a[f],(t|0)<(p|0)?g=t:(sa(rg|0,943,xm|0,Wx|0),g=a[a[g>>2]+284>>2],p=a[f]),m[a[h>>2]+a[a[d]+284>>2]+p*g|0]=1,d=a[d],0==m[d+163|0]<<24>>24&&Tx(b,d)}}d=v}v=d+1|0;p=a[a[z>>2]+(v<<2)>>2];if(0==(p|0)){break}else{g=p>>2}}}}m[j]=0}function zO(c,d){if(0!=(a[c+216>>2]|0)){Tw(c);Xx(c);0!=m[Wi]<<24>>24&&SN(c);var e;CP(c);e=a[c+216>>2];if(0!=(e|0)){for(e>>=2;;){var g=a[e+57];0!=(g|0)&&Yx(c,g);g=a[e+58];0!=(g|0)&&Yx(c,g);var g=a[e+52],h=0==(g|0);a:do{if(!h){for(var j=0;;){if(0==(a[g+(j<<2)>>2]|0)){break a}else{j=j+1|0}}}}while(0);e=a[e+42];if(0==(e|0)){break}else{e>>=2}}}0!=(RO(c)|0)&&Xx(c);DP(a[c+216>>2]);EP(c);FP(c);0<(a[c+208>>2]|0)&&(GP(c),Zx(c),HP(c),$x(c));g=a[c+44>>2];3==(a[g+84>>2]|0)&&(e=g+64|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),g=g+72|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),1>2]&1|0)?e:g,Zc(a[c+264>>2],a[c+268>>2],65535>e?e:65535,1e3)));0!=(Oo(c,2,IP(c))|0)&&(JP(c),0!=(Oo(c,2,IP(c))|0)&&sa(by|0,133,KP|0,LP|0));e=a[c+220>>2];h=D[c+244>>1];g=c+246|0;j=D[g>>1];if(h<<16>>16<=j<<16>>16){for(var h=h<<16>>16,l=j;;){j=e+44*h|0;if(0<(a[j>>2]|0)){for(var l=e+44*h+4|0,k=0;;){var n=a[a[l>>2]+(k<<2)>>2],z=n+236|0,n=n+32|0;f[0]=a[z>>2]|0;a[n>>2]=b[0];a[n+4>>2]=b[1];a[z>>2]=h;k=k+1|0;if((k|0)>=(a[j>>2]|0)){break}}j=D[g>>1]}else{j=l}h=h+1|0;if((h|0)>(j<<16>>16|0)){break}else{l=j}}}MP(c,d);NP(c)}}function Xx(c){var d,e,g,h,j=a[c+220>>2];h=j>>2;g=(c+244|0)>>1;var l=D[g];e=(c+246|0)>>1;var k=D[e],n=l<<16>>16>k<<16>>16;a:do{if(!n){for(var z=l<<16>>16,p=k;;){var s=j+44*z|0;if(0<(a[s>>2]|0)){for(var p=j+44*z+4|0,v=j+44*z+28|0,t=j+44*z+20|0,u=j+44*z+24|0,w=j+44*z+16|0,A=0;;){var B=a[a[p>>2]+(A<<2)>>2],C=B+96|0,C=(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0]),C=(((0>C?C-.5:C+.5)&-1)+1|0)/2&-1,y=a[B+208>>2],F=0==(y|0);b:do{if(F){var E=C}else{if(d=a[y>>2],0==(d|0)){E=C}else{var G=C,I=0;for(d>>=2;;){if((a[d+4]|0)==(a[d+3]|0)&&(d=a[d+27],0!=(d|0)&&(G|=0,d=d+32|0,d=.5*(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),G=(G>d?G:d)&-1)),I=I+1|0,d=a[y+(I<<2)>>2],0==(d|0)){E=G;break b}else{d>>=2}}}}}while(0);(a[v>>2]|0)<(E|0)&&(a[t>>2]=E,a[v>>2]=E);(a[u>>2]|0)<(E|0)&&(a[w>>2]=E,a[u>>2]=E);y=a[B+216>>2];0!=(y|0)&&(C=(y|0)==(c|0)?0:8,B=B+236|0,F=a[B>>2],(F|0)==(D[y+244>>1]<<16>>16|0)?(F=y+160|0,I=a[F>>2],G=C+E|0,a[F>>2]=(I|0)>(G|0)?I:G,B=a[B>>2]):B=F,(B|0)==(D[y+246>>1]<<16>>16|0)&&(B=y+156|0,y=a[B>>2],C=C+E|0,a[B>>2]=(y|0)>(C|0)?y:C));A=A+1|0;if((A|0)>=(a[s>>2]|0)){break}}s=D[e]}else{s=p}z=z+1|0;if((z|0)>(s<<16>>16|0)){break a}else{p=s}}}}while(0);j=cy(c);n=D[e]<<16>>16;l=a[a[h+(11*n|0)+1]>>2]+40|0;f[0]=a[h+(11*n|0)+4]|0;a[l>>2]=b[0];a[l+4>>2]=b[1];s=n-1|0;z=D[g];if((s|0)<(z<<16>>16|0)){var H=0,J=z}else{l=c+260|0;k=0;E=n;for(n=s;!(J=a[h+(11*n|0)+6]+a[h+(11*E|0)+7]+a[l>>2]|0,H=a[h+(11*E|0)+5]+a[h+(11*n|0)+4]+8|0,H=(J|0)>(H|0)?J:H,0<(a[h+(11*n|0)]|0)?(J=a[a[h+(11*E|0)+1]>>2]+40|0,J=(b[0]=a[J>>2],b[1]=a[J+4>>2],f[0])+(H|0),E=a[a[h+(11*n|0)+1]>>2]+40|0,f[0]=J,a[E>>2]=b[0],a[E+4>>2]=b[1],J=D[g]):J=z,H=(k|0)>(H|0)?k:H,z=n-1|0,(z|0)<(J<<16>>16|0));){k=H,E=n,n=z,z=J}H|=0}l=c+284|0;k=0==m[l]<<24>>24;a:do{if(!k&&(E=D[e]<<16>>16,n=E-1|0,(n|0)>=(J<<16>>16|0))){for(z=J;;){if(0<(a[h+(11*n|0)]|0)&&(E=a[a[h+(11*E|0)+1]>>2]+40|0,E=(b[0]=a[E>>2],b[1]=a[E+4>>2],f[0])+H,z=a[a[h+(11*n|0)+1]>>2]+40|0,f[0]=E,a[z>>2]=b[0],a[z+4>>2]=b[1],z=D[g]),s=n-1|0,(s|0)<(z<<16>>16|0)){break a}else{E=n,n=s}}}}while(0);0!=(j|0)&&0!=(a[c+152>>2]&1|0)&&dy(c,m[l]&255);c=a[c+216>>2];if(0!=(c|0)){for(;!(e=a[a[h+(11*a[c+236>>2]|0)+1]>>2]+40|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),g=c+40|0,f[0]=e,a[g>>2]=b[0],a[g+4>>2]=b[1],c=a[c+168>>2],0==(c|0));){}}}function IP(a){var b=V(a|0,OP|0);return 0==(b|0)?2147483647:a=wg(b,xc)*Vh(a)&-1}function JP(b){var c,d=D[b+244>>1],g=b+246|0,f=D[g>>1];if(d<<16>>16<=f<<16>>16){for(var e=b+220|0,d=d<<16>>16;;){var h=a[e>>2],j=a[(h>>2)+(11*d|0)],n=h+44*d+4|0,z=0,p=0;a:for(;;){if((z|0)>=(j|0)){c=957;break}var s=a[a[n>>2]+(z<<2)>>2],v=a[s+256>>2],t=0==(v|0);b:do{if(!t){for(var u=0;;){var w=a[v+(u<<2)>>2];if(0==(w|0)){break b}if((a[a[w+12>>2]+236>>2]|0)>(d|0)){var A=f;break a}if((a[a[w+16>>2]+236>>2]|0)>(d|0)){A=f;break a}else{u=u+1|0}}}}while(0);v=a[s+248>>2];t=0==(v|0);b:do{if(!t){for(u=0;;){w=a[v+(u<<2)>>2];if(0==(w|0)){break b}if((a[a[w+16>>2]+236>>2]|0)>(d|0)){A=f;break a}if((a[a[w+12>>2]+236>>2]|0)>(d|0)){A=f;break a}else{u=u+1|0}}}}while(0);z=z+1|0;p=s}957==c&&(c=0,0==(p|0)?A=f:(A=a[a[n>>2]>>2],h=a[a[(h+4>>2)+(11*(((d|0)<(f<<16>>16|0)?1:-1)+d)|0)]>>2],0==(h|0)&&sa(by|0,110,PP|0,QP|0),f=Lf(b),m[f+162|0]=2,Zc(f,A,0,0),Zc(f,h,0,0),A=a[A+236>>2],h=a[h+236>>2],a[f+236>>2]=(A|0)<(h|0)?A:h,A=D[g>>1]));d=d+1|0;if((d|0)>(A<<16>>16|0)){break}else{f=A}}}}function MP(c,d){var e,g,h;RP(c,c);var j=0>1]<<16>>16;a:do{if(j){var l=c+44|0,k=a[l>>2];e=a[k+84>>2];if(0!=(e|0)){h=(c+68|0)>>2;g=c+52|0;var n=(b[0]=a[h],b[1]=a[h+1],f[0])-(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])&-1;g=(c+76|0)>>2;var m=c+60|0,p=(b[0]=a[g],b[1]=a[g+1],f[0])-(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0])&-1,m=c+152|0,s=0==(a[m>>2]&1|0),v=s?p:n,n=s?n:p;4==(e|0)?(e=SP(c),l=a[l>>2]):(e=2==(e|0)&1,l=k);do{if(0==e<<24>>24){if(k=a[l+84>>2],5==(k|0)){k=l+64|0;k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);if(0>=k){break a}k/=(b[0]=a[h],b[1]=a[h+1],f[0]);p=l+72|0;p=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0])/(b[0]=a[g],b[1]=a[g+1],f[0]);if(!(1>2],b[1]=a[k+4>>2],f[0]),s=(v|0)/(n|0),s>2],b[1]=a[k+4>>2],f[0]);if(0>=k){break a}p=k/(n|0);k=l+72|0;s=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])/(v|0);1>p|1>s?p>2]&1|0))?p:k;g=g?k:p;m=a[c+216>>2];v=0==(m|0);b:do{if(!v){for(n=m;;){if(e=(n+32|0)>>2,l=(b[0]=a[e],b[1]=a[e+1],f[0])*g,f[0]=(0>l?l-.5:l+.5)&-1|0,a[e]=b[0],a[e+1]=b[1],e=(n+40|0)>>2,l=(b[0]=a[e],b[1]=a[e+1],f[0])*h,f[0]=(0>l?l-.5:l+.5)&-1|0,a[e]=b[0],a[e+1]=b[1],n=a[n+168>>2],0==(n|0)){break b}}}}while(0);TP(c,g,h)}}}while(0);0!=(d|0)&&UP(c,d)}function NP(b){var b=(b+216|0)>>2,c=a[b];do{if(0==(c|0)){var d=0,g=0}else{for(d=c;;){var g=d+184|0,f=g|0,e=a[f>>2],h=a[e>>2],j=0==(h|0);a:do{if(j){var n=e}else{for(var z=0,p=h;;){G(p|0);var z=z+1|0,s=a[f>>2],p=a[s+(z<<2)>>2];if(0==(p|0)){n=s;break a}}}}while(0);0!=(n|0)&&G(n);f=d+176|0;e=a[f>>2];0!=(e|0)&&G(e);e=d+256|0;h=a[e+4>>2];a[g>>2]=a[e>>2];a[g+4>>2]=h;g=d+248|0;e=a[g+4>>2];a[f>>2]=a[g>>2];a[f+4>>2]=e;d=a[d+168>>2];if(0==(d|0)){break}}d=0;g=a[b]}}while(0);a:for(;;){n=0==(d|0);c=d+168|0;for(f=g;;){if(0==(f|0)){break a}e=a[f+168>>2];if(2!=m[f+162|0]<<24>>24){d=f;g=e;continue a}n?a[b]=e:a[c>>2]=e;G(f|0);f=e}}a[a[b]+172>>2]=0}function Zc(b,c,d,g){var f=fa(184);a[f+16>>2]=b;a[f+12>>2]=c;65535>1]=(0>d?d-.5:d+.5)&65535;ib[f+164>>2]=g|0;xx(f);return f}function VP(c){la(1,WP|0,(j=h,h+=12,f[0]=c,a[j>>2]=b[0],a[j+4>>2]=b[1],a[j+8>>2]=65535,j));Pe()}function fm(c,d){var e=m[c+96|0];if(e<<24>>24!=m[d+96|0]<<24>>24){return 0}var g=c+68|0,h=d+68|0;if((b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])==(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0])){if(g=c+76|0,h=d+76|0,!((b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])==(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0])|0==e<<24>>24)){return 0}}else{if(0!=e<<24>>24){return 0}}e=c+28|0;g=d+28|0;if((b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])==(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])){if(e=c+36|0,g=d+36|0,(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])==(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])){return 1}}e=0==m[c+56|0]<<24>>24;return e&1}function XP(c,d,e,g,h){var j=c|0;f[0]=d-g;a[j>>2]=b[0];a[j+4>>2]=b[1];c=c+8|0;f[0]=e-h;a[c>>2]=b[0];a[c+4>>2]=b[1]}function YP(c,d){var e,g=(a[c+32>>2]|0)==(c|0);a:do{if(g){var h=D[c+244>>1],j=D[c+246>>1],l=h<<16>>16>j<<16>>16;b:do{if(l){var k=-2147483647,n=2147483647}else{for(var z=a[c+220>>2],p=j<<16>>16,s=-2147483647,v=2147483647,t=h<<16>>16;;){var u=a[(z>>2)+(11*t|0)];do{if(0==(u|0)){e=v;var w=s}else{if(e=a[(z+4>>2)+(11*t|0)]>>2,w=a[e],0==(w|0)){e=v,w=s}else{var A=m[w+162|0],B=0!=A<<24>>24&1<(u|0);c:do{if(B){for(var y=1;;){var F=a[(y<<2>>2)+e],y=y+1|0,E=m[F+162|0];if(!(0!=E<<24>>24&(y|0)<(u|0))){var G=F,H=E;break c}}}else{G=w,H=A}}while(0);if(0!=H<<24>>24){e=v,w=s}else{w=G+32|0;A=G+104|0;w=(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0])-(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0])&-1|0;w=v>2)+e];B=0==m[A+162|0]<<24>>24;c:do{if(B){var I=A}else{for(F=u-2|0;;){if(y=a[(F<<2>>2)+e],0==m[y+162|0]<<24>>24){I=y;break c}else{F=F-1|0}}}}while(0);e=I+32|0;A=I+112|0;A=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])+(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0])&-1|0;e=w;w=s>A?s:A}}}}while(0);t=t+1|0;if((t|0)>(p|0)){k=w;n=e;break b}else{s=w,v=e}}}}while(0);l=a[c+208>>2];if(1>(l|0)){var J=n,O=k,K=j,L=h}else{z=a[c+212>>2];p=k;v=n;for(s=1;;){if(t=a[z+(s<<2)>>2],u=t+52|0,u=(b[0]=a[u>>2],b[1]=a[u+4>>2],f[0])-8&-1|0,v=v>2],b[1]=a[t+4>>2],f[0])+8&-1|0,p=p>t?p:t,s=s+1|0,(s|0)>(l|0)){J=v;O=p;K=j;L=h;break a}}}}else{J=a[a[c+264>>2]+236>>2]|0,O=a[a[c+268>>2]+236>>2]|0,K=D[c+246>>1],L=D[c+244>>1]}}while(0);g=a[d+220>>2];K=a[a[(g+4>>2)+(11*(K<<16>>16)|0)]>>2]+40|0;K=(b[0]=a[K>>2],b[1]=a[K+4>>2],f[0])-(a[c+156>>2]|0);L=a[a[(g+4>>2)+(11*(L<<16>>16)|0)]>>2]+40|0;L=(b[0]=a[L>>2],b[1]=a[L+4>>2],f[0])+(a[c+160>>2]|0);g=c+52|0;f[0]=J;a[g>>2]=b[0];a[g+4>>2]=b[1];J=c+60|0;f[0]=K;a[J>>2]=b[0];a[J+4>>2]=b[1];J=c+68|0;f[0]=O;a[J>>2]=b[0];a[J+4>>2]=b[1];O=c+76|0;f[0]=L;a[O>>2]=b[0];a[O+4>>2]=b[1]}function CP(b){var c=D[b+244>>1],d=b+246|0;if(c<<16>>16<=D[d>>1]<<16>>16){b=(b+220|0)>>2;for(c=c<<16>>16;;){var g=a[b],f=a[(g>>2)+(11*c|0)],e=0<(f|0);a:do{if(e){for(var h=0,j=0,n=g;;){n=a[a[(n+4>>2)+(11*c|0)]+(j<<2)>>2];a[n+240>>2]=h;var h=(6==m[n+165|0]<<24>>24?a[n+220>>2]:1)+h|0,j=j+1|0,n=a[b],z=a[(n>>2)+(11*c|0)];if((j|0)>=(z|0)){var p=h,s=n,v=z;break a}}}else{p=0,s=g,v=f}}while(0);if((p|0)>(v|0)){g=a[(s+4>>2)+(11*c|0)];g=0==(g|0)?Cb((p<<2)+4|0):wb(g,(p<<2)+4|0);a[(a[b]+4>>2)+(11*c|0)]=g;g=a[b];f=a[(g>>2)+(11*c|0)];e=0<(f|0);a:do{if(e){h=f;for(j=g;;){if(h=h-1|0,j=a[(j+4>>2)+(11*c|0)],n=a[j+(h<<2)>>2],a[j+(a[n+240>>2]<<2)>>2]=n,j=a[b],0>=(h|0)){var t=j;break a}}}else{t=g}}while(0);a[(t>>2)+(11*c|0)]=p;a[a[(a[b]+4>>2)+(11*c|0)]+(p<<2)>>2]=0}c=c+1|0;if((c|0)>(D[d>>1]<<16>>16|0)){break}}}}function Yx(c,d){var e,g,j=d>>2,m=h;h+=24;var l=m+8,k=m+16;if(2<=(a[j+55]|0)){var n=d+32|0,z=d+104|0,p=d+40|0;ZP(m,d,(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0])-(b[0]=a[z>>2],b[1]=a[z+4>>2],f[0])&-1,(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0])&-1);n=a[m>>2];z=a[m+4>>2];if(0<(a[j+47]|0)){if(p=a[j+60],g=Og(c,a[a[a[j+46]>>2]+12>>2]),0!=(g|0)){for(var k=l|0,j=l+4|0,s=z,v=n,z=p+1|0,n=g;;){p=n;g=n+16|0;e=a[g>>2];if((e|0)!=(d|0)&&(Zb(e)|0)==(d|0)){$P(l,a[g>>2],v,s,z);v=a[k>>2];s=a[j>>2];NO(p);g=(n+12|0)>>2;e=(a[g]+24|0)>>2;var t=a[e+38];e=0==(t|0)?Cb((a[e+39]<<2)+8|0):wb(t,(a[e+39]<<2)+8|0);a[a[g]+176>>2]=e;e=a[g]+180|0;t=a[e>>2];a[e>>2]=t+1|0;a[a[a[g]+176>>2]+(t<<2)>>2]=p;p=a[g]+24|0;a[a[p+152>>2]+(a[p+156>>2]<<2)>>2]=0;z=z+1|0}p=v;n=Ql(c,n);if(0==(n|0)){break}else{v=p}}}}else{if(p=a[j+60],g=Ib(c,a[a[a[j+44]>>2]+16>>2]),0!=(g|0)){l=k|0;j=k+4|0;s=z;v=n;z=p+1|0;for(n=g;!(p=n,g=n+12|0,e=a[g>>2],(e|0)!=(d|0)&&(Zb(e)|0)==(d|0)&&($P(k,a[g>>2],v,s,z),v=a[l>>2],s=a[j>>2],NO(p),g=(n+16|0)>>2,e=(a[g]+24|0)>>2,t=a[e+40],e=0==(t|0)?Cb((a[e+41]<<2)+8|0):wb(t,(a[e+41]<<2)+8|0),a[a[g]+184>>2]=e,e=a[g]+188|0,t=a[e>>2],a[e>>2]=t+1|0,a[a[a[g]+184>>2]+(t<<2)>>2]=p,p=a[g]+24|0,a[a[p+160>>2]+(a[p+164>>2]<<2)>>2]=0,z=z+1|0),p=v,n=yb(c,n),0==(n|0));){v=p}}}}h=m}function ZP(c,d,e,g){var h=d+20|0;et(d,a[a[h>>2]+152>>2]&1);var j=d+40|0;f[0]=g|0;a[j>>2]=b[0];a[j+4>>2]=b[1];j=d+104|0;e=(e|0)+(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]);j=d+32|0;f[0]=e;a[j>>2]=b[0];a[j+4>>2]=b[1];d=d+112|0;a[c>>2]=e+(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0])+a[a[h>>2]+256>>2]&-1;a[c+4>>2]=g}function $P(b,c,d,g,e){var f=a[c+20>>2],h=Zb(c);if((h|0)!=(c|0)){var j;(h|0)==(c|0)&&sa(qg|0,220,ey|0,aQ|0);var n=c+168|0;0!=(a[n>>2]|0)&&sa(qg|0,221,ey|0,bQ|0);j=(h+168|0)>>2;a[n>>2]=a[j];n=a[j];0!=(n|0)&&(a[n+172>>2]=c);n=c+172|0;a[n>>2]=h;a[j]=c}a[c+240>>2]=e;h=a[h+236>>2];a[c+236>>2]=h;a[a[(a[f+220>>2]+4>>2)+(11*h|0)]+(e<<2)>>2]=c;ZP(b,c,d,g)}function RP(b,c){var d=b+208|0;if(1<=(a[d>>2]|0)){for(var g=b+212|0,e=1;!(RP(a[a[g>>2]+(e<<2)>>2],c),e=e+1|0,(e|0)>(a[d>>2]|0));){}}YP(b,c)}function SP(c){var d,e=h;h+=32;var g=e+16;d=(c+44|0)>>2;var j=a[d],m=j+48|0,m=(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0]),l=j+56|0,k=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);if(.001>m|.001>k){return h=e,0}l=j+32|0;l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);j=j+40|0;j=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]);XP(e,m,k,l,j);m=e|0;k=e+8|0;XP(g,(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0]),(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),l,j);m=g|0;m=(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0]);g=g+8|0;l=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);g=c+68|0;g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);c=c+76|0;c=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]);j=m/g;k=l/c;if(!(1>j|1>k)){return h=e,0}j=j>2]=b[0];a[j+4>>2]=b[1];d=a[d]+72|0;f[0]=c*l;a[d>>2]=b[0];a[d+4>>2]=b[1];h=e;return 1}function TP(c,d,e){var g;g=c+208|0;var h=1>(a[g>>2]|0);a:do{if(!h){for(var j=c+212|0,l=1;;){if(TP(a[a[j>>2]+(l<<2)>>2],d,e),l=l+1|0,(l|0)>(a[g>>2]|0)){break a}}}}while(0);g=(c+52|0)>>2;h=(b[0]=a[g],b[1]=a[g+1],f[0])*d;f[0]=h;a[g]=b[0];a[g+1]=b[1];g=(c+60|0)>>2;h=(b[0]=a[g],b[1]=a[g+1],f[0])*e;f[0]=h;a[g]=b[0];a[g+1]=b[1];g=(c+68|0)>>2;d*=(b[0]=a[g],b[1]=a[g+1],f[0]);f[0]=d;a[g]=b[0];a[g+1]=b[1];c=(c+76|0)>>2;e*=(b[0]=a[c],b[1]=a[c+1],f[0]);f[0]=e;a[c]=b[0];a[c+1]=b[1]}function UP(c,d){var e=h,g=c+68|0,q=c+52|0,q=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])-(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]),g=c+76|0,y=c+60|0,y=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])-(b[0]=a[y>>2],b[1]=a[y+4>>2],f[0]),g=q/y;if(0!=m[ld]<<24>>24){Va(a[oa>>2],dQ|0,(j=h,h+=16,f[0]=g,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=q*y/1e4,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));q=a[oa>>2];y=ra(c);if(0==(y|0)){var l=0}else{for(var k=0;;){var n=Ib(c,y),z=0==(n|0);b:do{if(z){var p=k}else{for(var s=k,v=n;;){var t=a[a[v+12>>2]+236>>2],u=a[a[v+16>>2]+236>>2];(t|0)!=(u|0)&&(t=t-u|0,s=s-1+(-1<(t|0)?t:-t|0)|0);v=yb(c,v);if(0==(v|0)){p=s;break b}}}}while(0);y=ba(c,y);if(0==(y|0)){l=p;break}else{k=p}}}Va(q,eQ|0,(j=h,h+=4,a[j>>2]=l,j))}l=d|0;l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);if(g>1.1*l){a[d+24>>2]=l*(a[d+20>>2]-a[d+16>>2]|0)/g&-1}else{if(p=d+24|0,g>.8*l){a[p>>2]=0}else{a[p>>2]=-1;if(0==m[ld]<<24>>24){h=e;return}Lc(fQ|0,34,1,a[oa>>2])}}0!=m[ld]<<24>>24&&Va(a[oa>>2],gQ|0,(j=h,h+=4,a[j>>2]=a[d+24>>2],j));h=e}function hQ(c,d){var e,g,h,j,l=a[c+32>>2];j=a[l+220>>2]>>2;var k=D[c+246>>1]<<16>>16,n=D[c+244>>1]<<16>>16,m=a[j+(11*n|0)+5];h=(c+160|0)>>2;var p=a[h];e=m-p|0;var s=a[j+(11*k|0)+4];g=(c+156|0)>>2;var v=a[g],t=s-v|0;if((t+e|0)<(d|0)){m=k-n|0;m=(m+(d+1)|0)/(m+2|0)&-1;e=(D[l+246>>1]<<16>>16)-1|0;l=l+244|0;s=D[l>>1];if((e|0)<(s<<16>>16|0)){j=p}else{v=m;p=e;for(e=s;!(0<(a[j+(11*p|0)]|0)&&(e=(a[a[j+(11*p|0)+1]>>2]+40|0)>>2,s=(b[0]=a[e],b[1]=a[e+1],f[0])+(v|0),f[0]=s,a[e]=b[0],a[e+1]=b[1],e=D[l>>1]),p=p-1|0,(p|0)<(e<<16>>16|0));){v=v+m|0}j=a[h];v=a[g]}a[h]=j+m|0;a[g]=v+m|0}else{j=(d+1|0)/2&-1,(e|0)>(t|0)?(j|0)>(t|0)?(a[g]=s,a[h]=d-t+p|0):(a[g]=j+v|0,a[h]=d-j+p|0):(j|0)>(e|0)?(a[h]=m,a[g]=d-e+v|0):(a[h]=j+p|0,a[g]=d-j+v|0)}}function iQ(c,d){var e,g,h,j,l=a[c+32>>2];g=a[l+220>>2]>>2;var k=D[c+246>>1];e=k<<16>>16;var n=D[c+244>>1],m=n<<16>>16,p=(d+1|0)/2&-1;j=(c+156|0)>>2;var s=a[j]+p-a[g+(11*e|0)+4]|0;if(0<(s|0)){k=k<<16>>16>16;a:do{if(!k){for(var n=s|0,v=e;;){if(0<(a[g+(11*v|0)]|0)){h=(a[a[g+(11*v|0)+1]>>2]+40|0)>>2;var t=(b[0]=a[h],b[1]=a[h+1],f[0])+n;f[0]=t;a[h]=b[0];a[h+1]=b[1]}h=v-1|0;if((h|0)<(m|0)){break a}else{v=h}}}}while(0);e=a[c+160>>2];s=d-p+s+e-a[g+(11*m|0)+5]|0}else{e=a[c+160>>2],s=d-p+e-a[g+(11*m|0)+5]|0}if(0<(s|0)){k=m-1|0;l=l+244|0;n=D[l>>1];if((k|0)<(n<<16>>16|0)){g=e,l=c+160|0}else{m=s|0;s=k;for(e=n;!(0<(a[g+(11*s|0)]|0)&&(e=(a[a[g+(11*s|0)+1]>>2]+40|0)>>2,k=(b[0]=a[e],b[1]=a[e+1],f[0])+m,f[0]=k,a[e]=b[0],a[e+1]=b[1],e=D[l>>1]),s=s-1|0,(s|0)<(e<<16>>16|0));){}l=c+160|0;g=a[l>>2]}m=(d-p|0)+g|0;a[l>>2]=m}else{g=(c+160|0)>>2,m=(d-p|0)+e|0,a[g]=m}g=a[j];p=g+p|0;a[j]=p}function cy(c){var d,e,g=c+32|0,h=a[g>>2],j=a[h+220>>2];e=(c+156|0)>>2;var l=a[e];d=(c+160|0)>>2;var k=a[d],n=c+208|0;if(1>(a[n>>2]|0)){var n=k,m=l,p=0,g=h}else{for(var h=c+212|0,s=c+246|0,v=c+244|0,t=1,u=0;;){var w=a[a[h>>2]+(t<<2)>>2],p=cy(w)|u;D[w+246>>1]<<16>>16==D[s>>1]<<16>>16?(m=a[w+156>>2]+8|0,m=(l|0)>(m|0)?l:m):m=l;D[w+244>>1]<<16>>16==D[v>>1]<<16>>16?(w=a[w+160>>2]+8|0,w=(k|0)>(w|0)?k:w):w=k;t=t+1|0;if((t|0)>(a[n>>2]|0)){break}else{k=w,l=m,u=p}}n=w;g=a[g>>2]}if((g|0)==(c|0)){return a[e]=m,a[d]=n,p}0==(a[c+48>>2]|0)?(a[e]=m,d=a[d]=n,e=m,g=p):(0==(a[g+152>>2]&1|0)?(p=c+92|0,g=c+124|0,p=m+(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0])&-1,g=n+(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])&-1):(p=m,g=n),a[e]=p,d=a[d]=g,e=p,g=1);p=j+44*(D[c+244>>1]<<16>>16)+20|0;n=a[p>>2];a[p>>2]=(n|0)>(d|0)?n:d;c=j+44*(D[c+246>>1]<<16>>16)+16|0;j=a[c>>2];a[c>>2]=(j|0)>(e|0)?j:e;return g}function dy(c,d){var e,g,h;h=(c+32|0)>>2;var j=a[h],l=a[j+220>>2];g=(c+156|0)>>2;var k=a[g];e=(c+160|0)>>2;var n=a[e],m=c+208|0;if(1>(a[m>>2]|0)){var m=k,p=n}else{for(var p=c+212|0,j=c+246|0,s=c+244|0,v=1;;){var t=a[a[p>>2]+(v<<2)>>2];dy(t,d);if(D[t+246>>1]<<16>>16==D[j>>1]<<16>>16){var u=a[t+156>>2]+8|0,u=(k|0)>(u|0)?k:u}else{u=k}D[t+244>>1]<<16>>16==D[s>>1]<<16>>16?(t=a[t+160>>2]+8|0,t=(n|0)>(t|0)?n:t):t=n;v=v+1|0;if((v|0)>(a[m>>2]|0)){break}else{k=u,n=t}}m=u;p=t;j=a[h]}a[g]=m;a[e]=p;(j|0)!=(c|0)&&0!=(a[c+48>>2]|0)&&(j=c+140|0,j=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]),s=c+108|0,s=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]),u=a[a[(l+4>>2)+(11*(D[c+244>>1]<<16>>16)|0)]>>2]+40|0,v=a[a[(l+4>>2)+(11*(D[c+246>>1]<<16>>16)|0)]>>2]+40|0,m=((j>s?j:s)&-1)-(p+m)-((b[0]=a[u>>2],b[1]=a[u+4>>2],f[0])-(b[0]=a[v>>2],b[1]=a[v+4>>2],f[0])&-1)|0,0<(m|0)&&(0==(d|0)?iQ(c,m):hQ(c,m)));(a[h]|0)!=(c|0)&&(h=l+44*(D[c+244>>1]<<16>>16)+20|0,m=a[h>>2],e=a[e],a[h>>2]=(m|0)>(e|0)?m:e,l=l+44*(D[c+246>>1]<<16>>16)+16|0,e=a[l>>2],g=a[g],a[l>>2]=(e|0)>(g|0)?e:g)}function DP(b){if(0!=(b|0)){for(var c=b,b=c>>2;;){var d=c+176|0,g=d,e=c+248|0,f=a[g>>2],g=a[g+4>>2];a[e>>2]=f;a[e+4>>2]=g;var h=e=c+184|0,c=c+256|0,g=a[h>>2],h=a[h+4>>2];a[c>>2]=g;a[c+4>>2]=h;c=e|0;for(e=0;0!=(a[g+(e<<2)>>2]|0);){e=e+1|0}d|=0;for(g=0;0!=(a[f+(g<<2)>>2]|0);){g=g+1|0}a[b+45]=0;a[d>>2]=fa((g+e<<2)+16|0);a[b+47]=0;a[c>>2]=fa(16);b=a[b+42];if(0==(b|0)){break}else{c=b,b=c>>2}}}}function EP(c){var d,e,g,j,y=h;h+=8;var l;j=y>>2;var k=a[c+220>>2],n=0==(m[c+149|0]&1)<<24>>24;g=(c+256|0)>>2;var z=a[g];a[j]=z;a[j+1]=n?z:5;n=c+246|0;z=D[c+244>>1]<<16>>16;a:for(;;){if((z|0)>(D[n>>1]<<16>>16|0)){l=1254;break}c=(k+44*z+4|0)>>2;a[a[a[c]>>2]+236>>2]=0;var p=k+44*z|0,s=a[((z&1)<<2>>2)+j]|0,v=0,t=0;b:for(;(t|0)<(a[p>>2]|0);){var u=a[a[c]+(t<<2)>>2];e=u>>2;d=(u+112|0)>>2;var w=(b[0]=a[d],b[1]=a[d+1],f[0]);a[e+61]=w&-1;if(0<(a[e+53]|0)){var A=a[e+52],B=a[A>>2],C=0==(B|0);c:do{if(C){var F=0}else{for(var E=0,G=0,H=B;;){if(E=(a[H+16>>2]|0)==(a[H+12>>2]|0)?HQa(H)+E|0:E,G=G+1|0,H=a[A+(G<<2)>>2],0==(H|0)){F=E;break c}}}}while(0);w+=F|0;f[0]=w;a[d]=b[0];a[d+1]=b[1]}A=w;d=t+1|0;w=a[a[c]+(d<<2)>>2];0==(w|0)?w=v:(B=w+104|0,A=A+(b[0]=a[B>>2],b[1]=a[B+4>>2],f[0])+s,Zc(u,w,A,0),A=v+A&-1,a[w+236>>2]=A,w=A|0);A=a[e+32];0!=(A|0)&&(e=a[e+64],B=a[e>>2],C=a[e+4>>2],e=(E=(a[a[B+12>>2]+240>>2]|0)>(a[a[C+12>>2]+240>>2]|0))?B:C,E=E?C:B,B=((D[A+178>>1]&65535)*a[g]|0)/2&-1|0,C=a[E+12>>2],E=a[E+16>>2],0==(fy(E,C)|0)&&(G=E+104|0,H=C+112|0,Zc(C,E,(b[0]=a[G>>2],b[1]=a[G+4>>2],f[0])+(B+(b[0]=a[H>>2],b[1]=a[H+4>>2],f[0]))&-1|0,ib[A+164>>2]&-1)),C=a[e+16>>2],e=a[e+12>>2],0==(fy(e,C)|0)&&(E=e+104|0,G=C+112|0,Zc(C,e,(b[0]=a[E>>2],b[1]=a[E+4>>2],f[0])+(B+(b[0]=a[G>>2],b[1]=a[G+4>>2],f[0]))&-1|0,ib[A+164>>2]&-1)));e=u+196|0;u=u+192|0;for(A=0;;){if((A|0)>=(a[e>>2]|0)){v=w;t=d;continue b}var B=a[a[u>>2]+(A<<2)>>2],E=a[B+16>>2],G=a[B+12>>2],C=(H=(a[E+240>>2]|0)<(a[G+240>>2]|0))?E:G,E=H?G:E,G=C+112|0,H=E+104|0,G=(b[0]=a[G>>2],b[1]=a[G+4>>2],f[0])+(b[0]=a[H>>2],b[1]=a[H+4>>2],f[0]),H=a[g],I=(D[B+178>>1]&65535)*H+G&-1,J=Zh(C,E);do{if(0==(J|0)){0==(a[B+108>>2]|0)&&Zc(C,E,I|0,ib[B+164>>2]&-1)}else{var K=I|0,L=G+(H|0),N=B+144|0,N=(b[0]=a[N>>2],b[1]=a[N+4>>2],f[0]),Q=0>N,K=(K>L+((Q?N-.5:N+.5)&-1|0)?K:L+((Q?N-.5:N+.5)&-1|0))&-1;if(65535<(K|0)){l=1248;break a}L=J+178|0;N=D[L>>1]&65535;D[L>>1]=((N|0)>(K|0)?N:K)&65535}}while(0);A=A+1|0}}z=z+1|0}1254==l?h=y:1248==l&&VP(K|0)}function FP(c){var d=a[c+216>>2];if(0!=(d|0)){for(;;){var e=d+256|0,g=a[e>>2],h=0==(g|0);a:do{if(!h){var j=a[g>>2];if(0!=(j|0)){for(var l=0,k=j;;){j=Lf(c);m[j+162|0]=2;var n=k+68|0,z=k+28|0,z=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0])-(b[0]=a[z>>2],b[1]=a[z+4>>2],f[0])&-1,p=0<(z|0),n=p?0:-z|0,z=p?z:0,p=k+16|0,s=k+164|0;Zc(j,a[p>>2],z+1|0,ib[s>>2]&-1);k=k+12|0;Zc(j,a[k>>2],n+1|0,ib[s>>2]&-1);z=a[a[p>>2]+236>>2]-z-1|0;k=a[a[k>>2]+236>>2]-n-1|0;a[j+236>>2]=(z|0)<(k|0)?z:k;l=l+1|0;j=a[a[e>>2]+(l<<2)>>2];if(0==(j|0)){break a}else{k=j}}}}}while(0);d=a[d+168>>2];if(0==(d|0)){break}}}}function ay(c){var d=h;xq(c);var e=a[c+264>>2],g=a[c+268>>2],q=D[c+244>>1],m=c+246|0;if(q<<16>>16<=D[m>>1]<<16>>16){for(var l=c+220|0,k=c+12|0,n=c+132|0,c=c+100|0,q=q<<16>>16;;){var z=a[l>>2];if(0!=(a[(z>>2)+(11*q|0)]|0)){if(z=a[a[(z+4>>2)+(11*q|0)]>>2],0==(z|0)){la(1,jQ|0,(j=h,h+=8,a[j>>2]=a[k>>2],a[j+4>>2]=q,j))}else{var p=z+104|0;Zc(e,z,(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0])+8+(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),0);z=a[l>>2];z=a[a[(z+4>>2)+(11*q|0)]+(a[(z>>2)+(11*q|0)]-1<<2)>>2];p=z+112|0;Zc(z,g,(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0])+8+(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]),0)}}q=q+1|0;if((q|0)>(D[m>>1]<<16>>16|0)){break}}}h=d}function xq(c){var d,e=c+264|0;if(0==(a[e>>2]|0)){d=(c+32|0)>>2;var g=Lf(a[d]);m[g+162|0]=2;var h=Lf(a[d]);m[h+162|0]=2;if(0!=(a[c+48>>2]|0)&&(d=a[d],(d|0)!=(c|0)&&0==(a[d+152>>2]&1|0))){d=c+84|0;d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]);var j=c+116|0,j=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]);Zc(g,h,(d>j?d:j)&-1|0,0)}a[e>>2]=g;a[c+268>>2]=h}}function GP(b){if((a[b+32>>2]|0)!=(b|0)){ay(b);var c=a[b+264>>2],d=a[b+268>>2],g=Zh(c,d);0==(g|0)?Zc(c,d,1,128):(c=g+164|0,ib[c>>2]+=128)}c=b+208|0;if(1<=(a[c>>2]|0)){b=b+212|0;for(d=1;!(GP(a[a[b>>2]+(d<<2)>>2]),d=d+1|0,(d|0)>(a[c>>2]|0));){}}}function Zx(c){var d,e=D[c+244>>1],g=c+246|0,h=e<<16>>16>D[g>>1]<<16>>16;a:do{if(!h){for(var j=c+220|0,l=c+32|0,k=c+264|0,n=c+268|0,z=e<<16>>16;;){var p=a[j>>2],s=0==(a[(p>>2)+(11*z|0)]|0);b:do{if(!s){var v=a[a[(p+4>>2)+(11*z|0)]>>2];if(0!=(v|0)){for(var v=v+240|0,t=a[v>>2];;){var u=t-1|0;if(0>=(t|0)){break}var w=a[a[(a[a[l>>2]+220>>2]+4>>2)+(11*z|0)]+(u<<2)>>2];if(0==m[w+162|0]<<24>>24){d=1314;break}if(0==(kQ(c,w)|0)){t=u}else{d=1314;break}}1314==d&&(d=0,t=w+112|0,Zc(w,a[k>>2],(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0])+8,0));for(v=a[(a[j>>2]>>2)+(11*z|0)]+a[v>>2]|0;;){t=a[a[l>>2]+220>>2];if((v|0)>=(a[(t>>2)+(11*z|0)]|0)){break b}var A=a[a[(t+4>>2)+(11*z|0)]+(v<<2)>>2];if(0==m[A+162|0]<<24>>24){break}if(0!=(kQ(c,A)|0)){break}v=v+1|0}v=A+104|0;Zc(a[n>>2],A,(b[0]=a[v>>2],b[1]=a[v+4>>2],f[0])+8,0)}}}while(0);z=z+1|0;if((z|0)>(D[g>>1]<<16>>16|0)){break a}}}}while(0);d=c+208|0;if(1<=(a[d>>2]|0)){c=c+212|0;for(e=1;!(Zx(a[a[c>>2]+(e<<2)>>2]),e=e+1|0,(e|0)>(a[d>>2]|0));){}}}function HP(c){xq(c);var d=c+208|0;if(1<=(a[d>>2]|0)){for(var e=c+212|0,g=c+264|0,h=c+132|0,j=c+268|0,c=c+100|0,l=1;;){var k=a[a[e>>2]+(l<<2)>>2];xq(k);Zc(a[g>>2],a[k+264>>2],(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0])+8,0);Zc(a[k+268>>2],a[j>>2],(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])+8,0);HP(k);l=l+1|0;if((l|0)>(a[d>>2]|0)){break}}}}function $x(b){var c;c=(b+208|0)>>2;if(1<=(a[c]|0)){for(var b=(b+212|0)>>2,d=1;;){xq(a[a[b]+(d<<2)>>2]);var d=d+1|0,g=a[c];if((d|0)>(g|0)){break}}if(1<=(g|0)){for(var d=1,e=g;;){var g=d+1|0,f=(g|0)>(e|0),h=a[b],j=a[h+(d<<2)>>2];a:do{if(f){var n=j}else{for(var m=g,p=h,s=j,v=e;;){var t=a[p+(m<<2)>>2],u=D[s+244>>1]<<16>>16>D[t+244>>1]<<16>>16,w=u?s:t,s=u?t:s,t=D[w+244>>1],u=t<<16>>16;D[s+246>>1]<<16>>16>16?w=v:(p=(a[a[a[(a[s+220>>2]+4>>2)+(11*u|0)]>>2]+240>>2]|0)<(a[a[a[(a[w+220>>2]+4>>2)+(11*u|0)]>>2]+240>>2]|0),Zc(a[(p?s:w)+268>>2],a[(p?w:s)+264>>2],8,0),w=a[c],p=a[b]);m=m+1|0;v=a[p+(d<<2)>>2];if((m|0)>(w|0)){n=v;break a}else{s=v,v=w}}}}while(0);$x(n);e=a[c];if((g|0)>(e|0)){break}else{d=g}}}}}function kQ(b,c){var d;if(1!=m[c+162|0]<<24>>24){return 0}d=a[a[c+256>>2]>>2];for(d>>=2;;){var g=a[d+32];if(0==(g|0)){break}else{d=g,d>>=2}}return 0!=(Ed(b,a[d+4]|0)|0)?0:d=0==(Ed(b,a[d+3]|0)|0)&1}function fy(b,c){var d;if((b|0)==(c|0)){var g;return 1}for(var e=a[b+184>>2],f=0;;){var h=a[e+(f<<2)>>2];if(0==(h|0)){g=0;d=1361;break}if(0==(fy(a[h+12>>2],c)|0)){f=f+1|0}else{g=1;d=1362;break}}if(1361==d||1362==d){return g}}function $N(b){var c,d,g;g=(b+244|0)>>1;D[g]=32767;d=(b+246|0)>>1;D[d]=-1;var e=ra(b);if(0==(e|0)){var f=0}else{for(var h=0;;){var j=e;c=(e+236|0)>>2;var n=a[c];(D[d]<<16>>16|0)<(n|0)&&(D[d]=n&65535,n=a[c]);(D[g]<<16>>16|0)>(n|0)&&(D[g]=n&65535);h=0==(h|0)?j:(a[c]|0)<(a[h+236>>2]|0)?j:h;e=ba(b,e);if(0==(e|0)){f=h;break}}}b=b+272|0;a[b>>2]=f}function Iw(b){var c=V(b|0,lQ|0),c=0==(c|0)?2147483647:wg(c,xc)*Vh(b)&-1,d=b+228|0;if(0<(a[d>>2]|0)){for(var g=b+224|0,e=b+216|0,f=b+208|0,h=0;!(a[e>>2]=a[a[g>>2]+(h<<2)>>2],Oo(b,0==(a[f>>2]|0)&1,c),h=h+1|0,(h|0)>=(a[d>>2]|0));){}}}function kx(b,c){var d=h;h+=8;if(0!=(m[b+149|0]&1)<<24>>24){var g=ra(b),e=0==(g|0);a:do{if(!e){for(var f=g;;){var j=Ib(b,f),k=0==(j|0);b:do{if(!k){for(var n=j;;){var z=n+178|0;D[z>>1]<<=1;n=yb(b,n);if(0==(n|0)){break b}}}}while(0);f=ba(b,f);if(0==(f|0)){break a}}}}while(0);g=b+260|0;a[g>>2]=(a[g>>2]+1|0)/2&-1}if(j=0!=(c|0)){g=ra(b);if(0!=(g|0)){for(;!(a[g+220>>2]=0,g=ba(b,g),0==(g|0));){}}g=ra(b);if(0!=(g|0)){for(;;){e=g+180|0;f=0<(a[e>>2]|0);a:do{if(f){k=g+176|0;for(n=0;;){if(m[a[a[k>>2]+(n<<2)>>2]+124|0]=0,n=n+1|0,(n|0)>=(a[e>>2]|0)){break a}}}}while(0);g=ba(b,g);if(0==(g|0)){break}}}}mQ(b,b);cN(b);nQ(d,b);g=a[d>>2];e=a[d+4>>2];jq(b,0);do{if(j){if(1>=(a[b+228>>2]|0)&&0>=(a[b+208>>2]|0)){f=c;break}a[c+32>>2]=1}f=0}while(0);j=b+228|0;if(0<(a[j>>2]|0)){k=b+224|0;n=b+216|0;for(z=0;;){var p=a[a[k>>2]+(z<<2)>>2];a[n>>2]=p;var s=0==(p|0);a:do{if(!s){for(var v=p;!(m[v+163|0]=0,v=a[v+168>>2],0==(v|0));){}v=a[n>>2];if(0!=(v|0)){for(;;){if(RM(v),v=a[v+168>>2],0==(v|0)){break a}}}}}while(0);z=z+1|0;if((z|0)>=(a[j>>2]|0)){break}}}0!=(oQ(b,g,e)|0)&&jq(b,0);0==(f|0)?Iw(b):VM(b,f);pQ(b,f);qQ(b);h=d}function mQ(b,c){var d=a[c+36>>2],g=a[d+20>>2],d=Ib(g,d);if(0!=(d|0)){for(;;){var e=Yd(a[d+12>>2]),f;f=e;if(0!=(0==(td(a[f+12>>2],Rx|0,7)|0)&1|0)){f=7}else{var h=Ds(V(f|0,nq|0),ei|0,rQ|0);m[f+282|0]=h&255;f=h}0==(f|0)?mQ(b,e):7==(f|0)&100==(a[Dl>>2]|0)?sQ(b,e):tQ(b,e,f);d=yb(g,d);if(0==(d|0)){break}}}}function uQ(b){var c;c=(b+4|0)>>2;var d=a[c];if(-1<(d|0)){for(b|=0;;){if(a[a[b>>2]+(d<<2)>>2]=0,0<(d|0)){d=d-1|0}else{break}}}a[c]=0}function nQ(b,c){var d,g,e;g=(c+236|0)>>2;e=a[g];d=(c+232|0)>>2;var f=a[d],h=0==(f|0);if(0==(e|0)){if(h){a[b>>2]=0;a[b+4>>2]=0;return}e=1425}else{if(h){var j=e;e=1426}else{e=1425}}if(1425==e){if(f=Zb(f),a[d]=f,h=a[g],0==(h|0)){var n=0,z=f}else{j=h,e=1426}}if(1426==e){n=Zb(j);a[g]=n;z=0==(n|0);a:do{if(z){var p=0}else{if(g=5==m[n+165|0]<<24>>24&1,j=n+184|0,e=a[a[j>>2]>>2],0==(e|0)){p=g}else{for(;;){if(f=a[e+12>>2],(f|0)!=(Zb(f)|0)&&sa(ym|0,363,gy|0,vQ|0),Hw(e),e=a[a[j>>2]>>2],0==(e|0)){p=g;break a}}}}}while(0);n=p;z=a[d]}d=0==(z|0);a:do{if(d){var s=0}else{if(p=3==m[z+165|0]<<24>>24&1,g=z+176|0,j=a[a[g>>2]>>2],0==(j|0)){s=p}else{for(;;){if(e=a[j+16>>2],(e|0)!=(Zb(e)|0)&&sa(ym|0,370,gy|0,wQ|0),Hw(j),j=a[a[g>>2]>>2],0==(j|0)){s=p;break a}}}}}while(0);a[b>>2]=s;a[b+4>>2]=n}function oQ(b,c,d){var g=b+236|0;if(0==(a[g>>2]|0)&&0==(a[b+232>>2]|0)){var e;return b=0}var f=ra(b);if(0==(f|0)){return b=0}for(var d=d&65535,h=b+232|0,c=c&65535,j=0;;){var n=f,m=f;if((f|0)==(Zb(m)|0)){if(0==(a[f+188>>2]|0)){var p=a[g>>2];0==(p|0)|(n|0)==(p|0)||(j=de(m,p,0),D[j+178>>1]=d)}0!=(a[f+180>>2]|0)?n=j:(p=a[h>>2],0==(p|0)|(n|0)==(p|0)?n=j:(n=de(p,m,0),D[n+178>>1]=c))}else{n=j}f=ba(b,f);if(0==(f|0)){e=n;break}else{j=n}}return b=0!=(e|0)&1}function pQ(b,c){var d,g,e=ra(b);if(0==(e|0)){D[b+246>>1]=0,D[b+244>>1]=0}else{g=(b+244|0)>>1;D[g]=32767;d=(b+246|0)>>1;D[d]=-1;for(var f=0==(c|0);;){var h=e,j=e,n=Zb(j);do{if((n|0)==(j|0)){var z=a[e+236>>2]}else{var z=e+236|0,p=a[z>>2];if(!f){if(0==(p|0)){p=0}else{z=p;break}}p=p+a[n+236>>2]|0;z=a[z>>2]=p}}while(0);(D[d]<<16>>16|0)<(z|0)?(D[d]=z&65535,n=a[e+236>>2]):n=z;(D[g]<<16>>16|0)>(n|0)&&(D[g]=n&65535);h=m[h+165|0];0==h<<24>>24||6==h<<24>>24||YE(j);e=ba(b,e);if(0==(e|0)){break}}if((a[b+32>>2]|0)==(b|0)){if(100!=(a[Dl>>2]|0)){if(g=a[b+36>>2],d=a[g+20>>2],g=Ib(d,g),0!=(g|0)){for(;!(f=Yd(a[g+12>>2]),7==m[f+282|0]<<24>>24&&sQ(b,f),g=yb(d,g),0==(g|0));){}}}else{if(d=b+208|0,1<=(a[d>>2]|0)){g=b+212|0;for(f=1;!(xQ(a[a[g>>2]+(f<<2)>>2]),f=f+1|0,(f|0)>(a[d>>2]|0));){}}}}}}function qQ(b){var c,d=b+224|0;c=(b+228|0)>>2;var g=a[c],e=0<(g|0);a:do{if(e){for(var f=d|0,h=b+216|0,j=0,n=g;;){var z=a[a[f>>2]+(j<<2)>>2];a[h>>2]=z;if(0!=(z|0)){for(n=z;!(uQ(n+176|0),uQ(n+184|0),m[n+163|0]=0,n=a[n+168>>2],0==(n|0));){}n=a[c]}j=j+1|0;if((j|0)>=(n|0)){break a}}}}while(0);g=ra(b);if(0!=(g|0)){for(;;){e=Ib(b,g);f=0==(e|0);a:do{if(!f){for(h=e;;){j=h+180|0;z=n=a[j>>2];do{if(0!=(n|0)&&(h|0)==(a[n+128>>2]|0)){var p=Ib(b,g),s=0==(p|0);b:do{if(!s){for(var v=p;;){if((h|0)!=(v|0)){var t=v+180|0,u=a[t>>2];0!=(u|0)&(z|0)==(u|0)&&(a[t>>2]=0)}v=yb(b,v);if(0==(v|0)){break b}}}}while(0);G(n|0)}}while(0);a[j>>2]=0;h=yb(b,h);if(0==(h|0)){break a}}}}while(0);g=ba(b,g);if(0==(g|0)){break}}}b=(d|0)>>2;d=a[b];G(d);a[b]=0;a[c]=0}function xQ(b){var c=a[b+272>>2]+236|0,d=b+244|0;D[d>>1]=(D[d>>1]&65535)+a[c>>2]&65535;d=b+246|0;D[d>>1]=(D[d>>1]&65535)+a[c>>2]&65535;c=b+208|0;if(1<=(a[c>>2]|0)){b=b+212|0;for(d=1;!(xQ(a[a[b>>2]+(d<<2)>>2]),d=d+1|0,(d|0)>(a[c>>2]|0));){}}}function sQ(b,c){var d=c+250|0;if(0==m[d]<<24>>24&&(m[d]=1,yQ(b,c),0!=(ra(c)|0))){var g=b+208|0,e=a[g>>2],d=e+1|0;a[g>>2]=d;var g=b+212|0,f=a[g>>2],e=0==(f|0)?fa((e<<2)+8|0):NF(f,e+2|0,4,d);a[g>>2]=e;a[e+(d<<2)>>2]=c;Vu(c);100==(a[Dl>>2]|0)?(kx(c,0),zQ(c)):$N(c)}}function yQ(b,c){var d,g=ra(c),e=0==(g|0);a:do{if(!e){for(var f=c,h=b+212|0,j=b+208|0,n=g;;){var z=ba(c,n),p=n+24|0,n=n|0;if(0==m[p+141|0]<<24>>24){for(var s=1;;){var v=a[j>>2];if((s|0)>=(v|0)){var t=v;break}if(0==(Ed(a[a[h>>2]+(s<<2)>>2],n)|0)){s=s+1|0}else{d=1535;break}}1535==d&&(d=0,t=a[j>>2]);(s|0)<(t|0)&&bl(f,n);a[p+192>>2]=0}else{bl(f,n)}if(0==(z|0)){break a}else{n=z}}}}while(0);g=ra(c);if(0!=(g|0)){for(d=c+32|0;;){e=Ib(a[d>>2],g);f=0==(e|0);a:do{if(!f){for(h=e;;){if(0!=(Ed(c,a[h+12>>2]|0)|0)&>(c,h|0),h=yb(a[d>>2],h),0==(h|0)){break a}}}}while(0);g=ba(c,g);if(0==(g|0)){break}}}}function zQ(b){var c,d=a[b+216>>2];do{if(0==(d|0)){c=1559}else{for(var g=0,e=d;;){var f=0==(a[e+236>>2]|0)?0==m[e+162|0]<<24>>24?e:g:g,e=a[e+168>>2];if(0==(e|0)){break}else{g=f}}if(0==(f|0)){c=1559}else{var h=f}}}while(0);1559==c&&(sa(ym|0,235,hy|0,AQ|0),h=0);a[b+272>>2]=h;d=ra(b);if(0!=(d|0)){for(c=h;!(g=d,2>(a[d+220>>2]|0)|(g|0)==(h|0)||sa(ym|0,239,hy|0,BQ|0),Tk(d,c),m[g+165|0]=7,d=ba(b,d),0==(d|0));){}}}function tQ(b,c,d){var g=ra(c);if(0!=(g|0)){var e=d&255,f=g+165|0;m[f]=e;var h=ba(c,g),j=0==(h|0);a:do{if(!j){for(var n=g,z=h;;){if(Tk(n,z),m[z+165|0]=m[f],z=ba(c,z),0==(z|0)){break a}}}}while(0);if(4==(d|0)||5==(d|0)){c=(b+236|0)>>2,f=a[c],a[c]=0==(f|0)?g:Tk(f,g)}else{if(2==(d|0)||3==(d|0)){c=(b+232|0)>>2,f=a[c],a[c]=0==(f|0)?g:Tk(f,g)}else{return}}5==(d|0)?m[a[b+236>>2]+165|0]=e:3==(d|0)&&(m[a[b+232>>2]+165|0]=e)}}function mx(b){var c=h;h+=120;var d,g=b+40|0,e=$(a[a[g>>2]+4>>2]|0,vx|0);a[di>>2]=e;g=$(a[a[g>>2]+4>>2]|0,wx|0);a[Ej>>2]=g;if(0!=(a[di>>2]|0)|0!=(g|0)&&(e=ra(b),0!=(e|0))){for(g=c|0;;){var f=e;a[Hj>>2]=0;var j=Ok(b,e),k=0==(j|0);a:do{if(!k){for(var n=j;;){var z=n,p=a[di>>2];(a[n+12>>2]|0)==(e|0)&0!=(p|0)?(p=mb(n|0,a[p+8>>2]),0==m[p]<<24>>24?d=1600:iy(g,f,z,p)):d=1600;1600==d&&(d=0,p=a[Ej>>2],(a[n+16>>2]|0)==(e|0)&0!=(p|0)&&(p=mb(n|0,a[p+8>>2]),0!=m[p]<<24>>24&&iy(g,f,z,p)));n=Pk(b,n,e);if(0==(n|0)){break a}}}}while(0);j=0<(a[Hj>>2]|0);a:do{if(j){for(k=0;;){if(n=c+24*k+4|0,1<(a[(c+8>>2)+(6*k|0)]|0)&&CQ(f,n),n=a[n>>2],0!=(n|0)&&G(n),k=k+1|0,(k|0)>=(a[Hj>>2]|0)){break a}}}}while(0);e=ba(b,e);if(0==(e|0)){break}}}h=c}function iy(c,d,e,g){var q,y;y=c>>2;var l=h;h+=8;for(var k=l+4,n=a[Hj>>2],z=0;;){if((z|0)>=(n|0)){q=1624;break}var p=a[y+(6*z|0)];if(m[p]<<24>>24==m[g]<<24>>24&&0==(ka(p,g)|0)){q=1619;break}z=z+1|0}if(1619==q){g=(c+24*z+4|0)>>2,q=a[g],q=0==(q|0)?Cb((a[y+(6*z|0)+2]<<2)+8|0):wb(q,(a[y+(6*z|0)+2]<<2)+8|0),a[g]=q,y=(c+24*z+8|0)>>2,n=a[y],a[y]=n+1|0,a[q+(n<<2)>>2]=e,a[a[g]+(a[y]<<2)>>2]=0}else{if(1624==q){q=n+1|0;a[Hj>>2]=q;if(5<(q|0)){la(1,DQ|0,(j=h,h+=4,a[j>>2]=a[d+12>>2],j));h=l;return}q=(c+24*z+8|0)>>2;a[q]=0;n=fa(8);p=c+24*z+4|0;a[p>>2]=n;var s=a[q];a[q]=s+1|0;a[n+(s<<2)>>2]=e;a[a[p>>2]+(a[q]<<2)>>2]=0;a[y+(6*z|0)]=g;a[y+(6*z|0)+3]=0;y=c+24*z+16|0;f[0]=0;a[y>>2]=b[0];a[y+4>>2]=b[1]}}tk(e,l,k);d=(a[e+12>>2]|0)==(d|0)?a[k>>2]:a[l>>2];0!=(d|0)&&(k=c+24*z+12|0,y=a[k>>2],a[k>>2]=y+1|0,e=0==(y|0)?Cg(e,d):0,c=c+24*z+16|0,f[0]=e,a[c>>2]=b[0],a[c+4>>2]=b[1]);h=l}function CQ(c,d){var e,g,j,y,l,k,n,z,p,s=h;h+=72;var v=s+4,t=s+8;p=(d+4|0)>>2;l=0<(a[p]|0);a:do{if(l){var u=d|0,w=c+32|0;k=c+40|0;for(var A=g=0,B=0;;){var C=a[a[u>>2]+(B<<2)>>2],D=a[C+12>>2],D=(D|0)==(c|0)?a[C+16>>2]:D,C=D+32|0,C=(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0])-(b[0]=a[w>>2],b[1]=a[w+4>>2],f[0]),D=D+40|0,D=(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0])-(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),F=pi(C,D),A=A+C/F;g+=D/F;B=B+1|0;if((B|0)>=(a[p]|0)){j=g;y=A;n=w;z=n>>2;n=k;n>>=2;break a}}}else{y=j=0,n=c+32|0,z=n>>2,n=c+40|0,n>>=2}}while(0);u=pi(y,j);g=(b[0]=a[z],b[1]=a[z+1],f[0]);w=(b[0]=a[n],b[1]=a[n+1],f[0]);k=(c+104|0)>>2;l=(c+112|0)>>2;B=(b[0]=a[k],b[1]=a[k+1],f[0])+(b[0]=a[l],b[1]=a[l+1],f[0]);A=c+96|0;A=(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0])+(a[a[c+20>>2]+260>>2]|0);A=B>A?B:A;B=y/u*A+g;A=j/u*A+w;u=t|0;y=(t|0)>>2;f[0]=g;a[y]=b[0];a[y+1]=b[1];j=(t+8|0)>>2;f[0]=w;a[j]=b[0];a[j+1]=b[1];C=t+16|0;f[0]=(2*g+B)/3;a[C>>2]=b[0];a[C+4>>2]=b[1];C=t+24|0;f[0]=(2*w+A)/3;a[C>>2]=b[0];a[C+4>>2]=b[1];C=t+32|0;f[0]=(2*B+g)/3;a[C>>2]=b[0];a[C+4>>2]=b[1];g=t+40|0;f[0]=(2*A+w)/3;a[g>>2]=b[0];a[g+4>>2]=b[1];w=t+48|0;f[0]=B;a[w>>2]=b[0];a[w+4>>2]=b[1];t=t+56|0;f[0]=A;a[t>>2]=b[0];a[t+4>>2]=b[1];t=h;h+=8;w=a[c+24>>2];0!=(w|0)&&(B=w+4|0,0!=(a[a[B>>2]+12>>2]|0)&&(a[t>>2]=c,a[t+4>>2]=0,w=(c+112|0)>>2,g=(b[0]=a[w],b[1]=a[w+1],f[0])&-1,A=u|0,C=c+32|0,A=(b[0]=a[A>>2],b[1]=a[A+4>>2],f[0])-(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0]),C=u+8|0,D=c+40|0,B=J[a[a[B>>2]+12>>2]](t,A,(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0])-(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0])),f[0]=g|0,a[w]=b[0],a[w+1]=b[1],jp(t,c,u,B)));h=t;z=(b[0]=a[y],b[1]=a[y+1],f[0])-(b[0]=a[z],b[1]=a[z+1],f[0]);t=(b[0]=a[j],b[1]=a[j+1],f[0])-(b[0]=a[n],b[1]=a[n+1],f[0]);n=(0>z?z-.5:z+.5)&-1|0;z=(0>t?t-.5:t+.5)&-1|0;t=(b[0]=a[k],b[1]=a[k+1],f[0]);t=256*(t+n)/(t+(b[0]=a[l],b[1]=a[l+1],f[0]));t=0<=t?Math.floor(t):Math.ceil(t);if(0<(a[p]|0)){l=d|0;for(k=0;;){j=a[a[l>>2]+(k<<2)>>2];tk(j,s,v);y=0==(j|0);a:do{if(!y){for(u=j;;){w=0==(u|0);b:do{if(!w){B=u;for(g=B>>2;;){A=a[g+3];(A|0)==(c|0)&&(C=B+68|0,f[0]=n,a[C>>2]=b[0],a[C+4>>2]=b[1],C=B+76|0,f[0]=z,a[C>>2]=b[0],a[C+4>>2]=b[1],C=B+84|0,f[0]=0,a[C>>2]=b[0],a[C+4>>2]=b[1],a[g+23]=0,m[B+96|0]=1,m[B+97|0]=0,m[B+98|0]=0,m[B+99|0]=0,m[B+100|0]=t,m[B+101|0]=0,a[g+26]=0);(a[g+4]|0)==(c|0)&&(C=B+28|0,f[0]=n,a[C>>2]=b[0],a[C+4>>2]=b[1],C=B+36|0,f[0]=z,a[C>>2]=b[0],a[C+4>>2]=b[1],C=B+44|0,f[0]=0,a[C>>2]=b[0],a[C+4>>2]=b[1],a[g+13]=0,m[B+56|0]=1,m[B+57|0]=0,m[B+58|0]=0,m[B+59|0]=0,m[B+60|0]=t,m[B+61|0]=0,a[g+16]=0);if(1!=m[B+124|0]<<24>>24){var E=u;e=E>>2;break}if(1!=m[A+162|0]<<24>>24){E=u;e=E>>2;break}if(1!=(a[A+188>>2]|0)){E=u;e=E>>2;break}g=a[a[A+184>>2]>>2];if(0==(g|0)){E=u;e=E>>2;break}else{B=g,g=B>>2}}for(;;){(a[e+3]|0)==(c|0)&&(g=E+68|0,f[0]=n,a[g>>2]=b[0],a[g+4>>2]=b[1],g=E+76|0,f[0]=z,a[g>>2]=b[0],a[g+4>>2]=b[1],g=E+84|0,f[0]=0,a[g>>2]=b[0],a[g+4>>2]=b[1],a[e+23]=0,m[E+96|0]=1,m[E+97|0]=0,m[E+98|0]=0,m[E+99|0]=0,m[E+100|0]=t,m[E+101|0]=0,a[e+26]=0);g=a[e+4];(g|0)==(c|0)&&(B=E+28|0,f[0]=n,a[B>>2]=b[0],a[B+4>>2]=b[1],B=E+36|0,f[0]=z,a[B>>2]=b[0],a[B+4>>2]=b[1],B=E+44|0,f[0]=0,a[B>>2]=b[0],a[B+4>>2]=b[1],a[e+13]=0,m[E+56|0]=1,m[E+57|0]=0,m[E+58|0]=0,m[E+59|0]=0,m[E+60|0]=t,m[E+61|0]=0,a[e+16]=0);if(1!=m[E+124|0]<<24>>24){break b}if(1!=m[g+162|0]<<24>>24){break b}if(1!=(a[g+180>>2]|0)){break b}g=a[a[g+176>>2]>>2];if(0==(g|0)){break b}else{E=g,e=E>>2}}}}while(0);u=a[u+180>>2];if(0==(u|0)){break a}}}}while(0);k=k+1|0;if((k|0)>=(a[p]|0)){break}}}e=c+161|0;m[e]=1;h=s}function fi(b,c,d){var g=h;h+=1024;var e=g|0,f=Ba(d);Ma(e,EQ|0,(j=h,h+=8,a[j>>2]=c,a[j+4>>2]=f,j));b=a[Bd+(b<<2)>>2];db(b,e);db(b,d);d=(b+4|0)>>2;e=a[d];e>>>0>2]>>>0||(na(b,1),e=a[d]);b=e+1|0;a[d]=b;m[e]=32;h=g}function FQ(c){var c=a[a[c+16>>2]+8>>2],d=a[U+20>>2];if((d|0)!=(a[U+16>>2]|0)){var e=a[a[a[Kb>>2]+8>>2]+8>>2];d>>>0>2]>>>0||(na(U+16|0,1),d=a[U+20>>2]);m[d]=0;d=a[U+16>>2];a[U+20>>2]=d;qc(c|0,e,d)}d=a[U+84>>2];(d|0)!=(a[U+80>>2]|0)&&(e=a[a[a[Kb>>2]+12>>2]+8>>2],d>>>0>2]>>>0||(na(U+80|0,1),d=a[U+84>>2]),m[d]=0,d=a[U+80>>2],a[U+84>>2]=d,qc(c|0,e,d));f[0]=1;a[kc+64>>2]=b[0];a[kc+68>>2]=b[1];f[0]=1;a[kc+80>>2]=b[0];a[kc+84>>2]=b[1]}function GQ(c){var c=a[a[c+16>>2]+8>>2],d=a[U+20>>2];if((d|0)!=(a[U+16>>2]|0)){var e=a[a[a[Kb>>2]+16>>2]+8>>2];d>>>0>2]>>>0||(na(U+16|0,1),d=a[U+20>>2]);m[d]=0;d=a[U+16>>2];a[U+20>>2]=d;qc(c|0,e,d)}d=a[U+36>>2];(d|0)!=(a[U+32>>2]|0)&&(e=a[a[a[Kb>>2]+24>>2]+8>>2],d>>>0>2]>>>0||(na(U+32|0,1),d=a[U+36>>2]),m[d]=0,d=a[U+32>>2],a[U+36>>2]=d,qc(c|0,e,d));d=a[U+52>>2];(d|0)!=(a[U+48>>2]|0)&&(e=a[a[a[Kb>>2]+20>>2]+8>>2],d>>>0>2]>>>0||(na(U+48|0,1),d=a[U+52>>2]),m[d]=0,d=a[U+48>>2],a[U+52>>2]=d,qc(c|0,e,d));d=a[U+84>>2];(d|0)!=(a[U+80>>2]|0)&&(e=a[a[a[Kb>>2]+28>>2]+8>>2],d>>>0>2]>>>0||(na(U+80|0,1),d=a[U+84>>2]),m[d]=0,d=a[U+80>>2],a[U+84>>2]=d,qc(c|0,e,d));d=a[U+100>>2];(d|0)!=(a[U+96>>2]|0)&&(e=a[a[a[Kb>>2]+36>>2]+8>>2],d>>>0>2]>>>0||(na(U+96|0,1),d=a[U+100>>2]),m[d]=0,d=a[U+96>>2],a[U+100>>2]=d,qc(c|0,e,d));d=a[U+116>>2];(d|0)!=(a[U+112>>2]|0)&&(e=a[a[a[Kb>>2]+32>>2]+8>>2],d>>>0>2]>>>0||(na(U+112|0,1),d=a[U+116>>2]),m[d]=0,d=a[U+112>>2],a[U+116>>2]=d,qc(c|0,e,d));f[0]=1;a[kc+72>>2]=b[0];a[kc+76>>2]=b[1];f[0]=1;a[kc+88>>2]=b[0];a[kc+92>>2]=b[1];f[0]=1;a[kc+16>>2]=b[0];a[kc+20>>2]=b[1];f[0]=1;a[kc+24>>2]=b[0];a[kc+28>>2]=b[1];f[0]=1;a[kc+48>>2]=b[0];a[kc+52>>2]=b[1];f[0]=1;a[kc+56>>2]=b[0];a[kc+60>>2]=b[1]}function HQ(c,d,e){var g,q,m=h;h+=1024;g=a[a[c+16>>2]+12>>2];zm(c);Am(c);0==(e|0)?(c=a[Bd+(g<<2)>>2],db(c,IQ|0)):(jy(c),c=a[Bd+(g<<2)>>2],db(c,JQ|0));q=(d|0)>>2;g=(d+8|0)>>2;Ap(c,(b[0]=a[q],b[1]=a[q+1],f[0]),(b[0]=a[g],b[1]=a[g+1],f[0]));var e=m|0,l=d+16|0;q=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])-(b[0]=a[q],b[1]=a[q+1],f[0]);d=d+24|0;d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0])-(b[0]=a[g],b[1]=a[g+1],f[0]);d=0>d?d-.5&-1:d+.5&-1;Ma(e,Hl|0,(j=h,h+=8,a[j>>2]=(0>q?q-.5:q+.5)&-1,a[j+4>>2]=d,j));db(c,e);h=m}function zm(c){var d,e,g,q=h;h+=1040;var y,l=q+1024,c=(c+16|0)>>2;d=a[c];e=d+96|0;e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);g=((a[d+12>>2]<<3)+kc|0)>>2;if(e!=(b[0]=a[g],b[1]=a[g+1],f[0])){f[0]=e,a[g]=b[0],a[g+1]=b[1],d=q|0,Ma(d,KQ|0,(j=h,h+=8,f[0]=e,a[j>>2]=b[0],a[j+4>>2]=b[1],j)),fi(a[a[c]+12>>2],ky|0,d),d=a[c]}g=a[d+104>>2];if(0!=(g|0)){fc(l,1024,q|0);var k=a[g>>2],n=0==(k|0);a:do{if(!n){e=(l+4|0)>>2;d=(l+8|0)>>2;for(var z=l|0,p=g,s=k;;){var p=p+4|0,v=m[s];102==v<<24>>24?0!=(ka(s,Hi|0)|0)&&(y=1783):115==v<<24>>24?0!=(ka(s,Cp|0)|0)&&(y=1783):98==v<<24>>24?0!=(ka(s,Wb|0)|0)&&(y=1783):y=1783;if(1783==y){y=0;for(db(l,s);;){var t=s+1|0;if(0==m[s]<<24>>24){break}else{s=t}}if(0!=m[t]<<24>>24){s=a[e];s>>>0>>0||(na(l,1),s=a[e]);v=s+1|0;a[e]=v;m[s]=40;if(0==m[t]<<24>>24){s=v}else{v=t;for(s=0;;){if(0!=(s|0)){var u=a[e];u>>>0>>0||(na(l,1),u=a[e]);a[e]=u+1|0;m[u]=44}for(db(l,v);;){var w=v+1|0;if(0==m[v]<<24>>24){break}else{v=w}}if(0==m[w]<<24>>24){break}else{v=w,s=s+1|0}}s=a[e]}s>>>0>>0||(na(l,1),s=a[e]);a[e]=s+1|0;m[s]=41}s=a[e];s>>>0>>0||(na(l,1),s=a[e]);m[s]=0;s=a[z>>2];a[e]=s;fi(a[a[c]+12>>2],ky|0,s)}s=a[p>>2];if(0==(s|0)){break a}}}}while(0);uc(l)}h=q}function Am(b){b=b+16|0;LQ(a[b>>2]+16|0);fi(a[a[b>>2]+12>>2],ly|0,yq|0)}function Bm(c,d,e,g){var q,y=h;h+=1024;c=a[Bd+(c<<2)>>2];q=(c+4|0)>>2;var l=a[q];l>>>0>2]>>>0||(na(c,1),l=a[q]);a[q]=l+1|0;m[l]=d;d=y|0;Ma(d,MQ|0,(j=h,h+=4,a[j>>2]=g,j));db(c,d);if(0<(g|0)){for(d=0;!(q=(d<<4)+e|0,l=(d<<4)+e+8|0,Ap(c,(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]),(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])),d=d+1|0,(d|0)==(g|0));){}}h=y}function LQ(b){var c=h,d=m[b+1|0]&255,g=m[b+2|0]&255,e=m[b+3|0]&255;Ma(yq|0,NQ|0,(j=h,h+=16,a[j>>2]=m[b]&255,a[j+4>>2]=d,a[j+8>>2]=g,a[j+12>>2]=e,j));h=c}function jy(b){b=b+16|0;LQ(a[b>>2]+52|0);fi(a[a[b>>2]+12>>2],OQ|0,yq|0)}function PQ(c){var d=a[U+4>>2];if((d|0)==(a[U>>2]|0)){var e=c|0}else{var g=a[a[Kb>>2]>>2],e=c|0;if(0==(g|0)){var d=ac(c,e,Di|0,Y|0,202),g=a[a[Kb>>2]>>2]=d,h=a[U+4>>2]}else{h=d}d=a[g+8>>2];h>>>0>2]>>>0?g=h:(na(U|0,1),g=a[U+4>>2]);m[g]=0;g=a[U>>2];a[U+4>>2]=g;qc(e,d,g)}0!=(a[c+48>>2]|0)&&(c=a[a[a[Kb>>2]+4>>2]+8>>2],d=a[U+68>>2],d>>>0>2]>>>0||(na(U+64|0,1),d=a[U+68>>2]),m[d]=0,d=a[U+64>>2],a[U+68>>2]=d,qc(e,c,d));$J(e,QQ|0,RQ|0,Y|0);uc(U|0);uc(U+16|0);uc(U+32|0);uc(U+48|0);uc(U+64|0);uc(U+80|0);uc(U+96|0);uc(U+112|0);G(a[Kb>>2]);f[0]=1;a[kc>>2]=b[0];a[kc+4>>2]=b[1];f[0]=1;a[kc+32>>2]=b[0];a[kc+36>>2]=b[1]}function SQ(b,c,d){var g=Gb(8232);a[Kb>>2]=g;if(0==(a[b+208>>2]|0)){a[g>>2]=0;var e=g}else{g=ac(b,b|0,Di|0,Y|0,202),e=a[Kb>>2],a[e>>2]=g}g=b+149|0;0==(m[g]&8)<<24>>24?a[e+4>>2]=0:(e=ac(b,b|0,zq|0,Y|0,202),a[a[Kb>>2]+4>>2]=e);var e=b,b=(b+40|0)>>2,f=ac(e,a[a[b]>>2]|0,Di|0,Y|0,212);a[a[Kb>>2]+8>>2]=f;f=ac(e,a[a[b]>>2]|0,zq|0,Y|0,212);a[a[Kb>>2]+12>>2]=f;var f=ac(e,a[a[b]+4>>2]|0,Di|0,Y|0,286),h=a[Kb>>2];a[h+16>>2]=f;0==(d|0)?(a[h+20>>2]=0,d=h):(d=ac(e,a[a[b]+4>>2]|0,TQ|0,Y|0,286),f=a[Kb>>2],a[f+20>>2]=d,d=f);0==(c|0)?a[d+24>>2]=0:(c=ac(e,a[a[b]+4>>2]|0,UQ|0,Y|0,286),d=a[Kb>>2],a[d+24>>2]=c);c=d;0==(m[g]&1)<<24>>24?a[c+28>>2]=0:(c=ac(e,a[a[b]+4>>2]|0,zq|0,Y|0,286),d=a[Kb>>2],a[d+28>>2]=c,c=d);0==(m[g]&2)<<24>>24?a[c+32>>2]=0:(c=ac(e,a[a[b]+4>>2]|0,VQ|0,Y|0,286),d=a[Kb>>2],a[d+32>>2]=c,c=d);0==(m[g]&4)<<24>>24?(a[c+36>>2]=0,g=c+40|0):(g=ac(e,a[a[b]+4>>2]|0,WQ|0,Y|0,286),e=a[Kb>>2],a[e+36>>2]=g,g=e+40|0);fc(U|0,1024,g);g=a[Kb>>2];fc(U+16|0,1024,g+1064|0);g=a[Kb>>2];fc(U+32|0,1024,g+2088|0);g=a[Kb>>2];fc(U+48|0,1024,g+3112|0);g=a[Kb>>2];fc(U+64|0,1024,g+4136|0);g=a[Kb>>2];fc(U+80|0,1024,g+5160|0);g=a[Kb>>2];fc(U+96|0,1024,g+6184|0);g=a[Kb>>2];g=g+7208|0;fc(U+112|0,1024,g)}function Aq(c,d,e){e>>=2;1==(c|0)?(a[d>>2]=1,f[0]=10):2==(c|0)?(a[d>>2]=2,f[0]=10):(a[d>>2]=0,f[0]=0);a[e]=b[0];a[e+1]=b[1]}function XQ(b,c,d,g){var e;a[b>>2]=0;for(var f=a[my>>2],h=195075,j=-1,n=0;(n|0)<(f|0);){var m=(D[ny+(n<<1)>>1]<<16>>16)-c|0,p=(D[oy+(n<<1)>>1]<<16>>16)-d|0,s=(D[py+(n<<1)>>1]<<16>>16)-g|0,m=p*p+m*m+s*s|0;if((m|0)<(h|0)){if(0==(m|0)){var v=n;e=1876;break}else{h=m,j=n}}n=n+1|0}if(1876==e){return v}a[my>>2]=f+1|0;if(256==(f|0)){return j}D[ny+(n<<1)>>1]=c&65535;D[oy+(n<<1)>>1]=d&65535;D[py+(n<<1)>>1]=g&65535;a[b>>2]=1;return n}function YQ(c,d,e,g){var q=h,y=a[a[c+16>>2]+16>>2],l=a[rf>>2],k=g+24|0,n=c+348|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])*(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),n=0!=(a[c+356>>2]|0)?1.5707963267948966:0,z=a[g+4>>2],z=0==(z|0)?-1:a[z+20>>2],p=m[g+72|0]<<24>>24,p=108==(p|0)?0:114==(p|0)?2:1,d=0>d?d-.5:d+.5,e=0>e?e-.5:e+.5,g=ZQ(a[g>>2]);N(c,$Q|0,(j=h,h+=72,a[j>>2]=4,a[j+4>>2]=p,a[j+8>>2]=y,a[j+12>>2]=l,a[j+16>>2]=0,a[j+20>>2]=z,f[0]=k,a[j+24>>2]=b[0],a[j+28>>2]=b[1],f[0]=n,a[j+32>>2]=b[0],a[j+36>>2]=b[1],a[j+40>>2]=6,f[0]=0,a[j+44>>2]=b[0],a[j+48>>2]=b[1],f[0]=0,a[j+52>>2]=b[0],a[j+56>>2]=b[1],a[j+60>>2]=d&-1,a[j+64>>2]=e&-1,a[j+68>>2]=g,j));h=q}function aR(b,c){var d,g=h;h+=4;var e;d=(c+32|0)>>2;var f=a[d];if(5==(f|0)){for(f=0;;){var l=a[Mf+(f<<2)>>2];if(8==(f|0)){e=1906;break}var k=a[c>>2];if(m[l]<<24>>24==m[k]<<24>>24&&0==(ka(l,k)|0)){break}f=f+1|0}1906!=e&&(a[c>>2]=f)}else{1==(f|0)?(k=c+1|0,l=c+2|0,e=XQ(g,m[c]&255,m[k]&255,m[l]&255)+32|0,0!=(a[g>>2]|0)&&(f=m[c]&255,k=m[k]&255,l=m[l]&255,N(b,bR|0,(j=h,h+=20,a[j>>2]=0,a[j+4>>2]=e,a[j+8>>2]=f,a[j+12>>2]=k,a[j+16>>2]=l,j))),a[c>>2]=e):sa(qy|0,165,cR|0,vd|0)}a[d]=6;h=g}function dR(c,d,e){var g,q=h;h+=12;var m=q+4,l=a[c+16>>2];g=l>>2;var l=l+96|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])&-1,k=a[g+4],n=a[g+13],z=a[rf>>2];Aq(a[g+22],q,m);g=d|0;var p=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);g=(0>p?p-.5:p+.5)&-1;var s=d+8|0,v=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]),s=(0>v?v-.5:v+.5)&-1,t=d+16|0,t=(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0]),p=t-p,d=d+24|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),v=d-v,u=a[q>>2],m=(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0]);N(c,eR|0,(j=h,h+=88,a[j>>2]=1,a[j+4>>2]=1,a[j+8>>2]=u,a[j+12>>2]=l,a[j+16>>2]=k,a[j+20>>2]=n,a[j+24>>2]=z,a[j+28>>2]=0,a[j+32>>2]=0!=(e|0)?20:-1,f[0]=m,a[j+36>>2]=b[0],a[j+40>>2]=b[1],a[j+44>>2]=0,f[0]=0,a[j+48>>2]=b[0],a[j+52>>2]=b[1],a[j+56>>2]=g,a[j+60>>2]=s,a[j+64>>2]=(0>p?p-.5:p+.5)&-1,a[j+68>>2]=(0>v?v-.5:v+.5)&-1,a[j+72>>2]=g,a[j+76>>2]=s,a[j+80>>2]=(0>t?t-.5:t+.5)&-1,a[j+84>>2]=(0>d?d-.5:d+.5)&-1,j));h=q}function fR(c,d,e,g,q,m){var l,k,n,g=h;h+=92;var q=g+4,z=g+12,p=g+76,s=c+16|0,v=a[s>>2],t=v+96|0,t=(b[0]=a[t>>2],b[1]=a[t+4>>2],f[0])&-1,u=a[v+16>>2],w=a[rf>>2],A=3<(e|0);A||sa(qy|0,354,gR|0,hR|0);var B=Gb(140*e+140|0);Aq(a[v+88>>2],g,q);0==(m|0)?(m=-1,s=0,v=4):(m=20,s=a[a[s>>2]+52>>2],v=5);var C=d|0;l=(b[0]=a[C>>2],b[1]=a[C+4>>2],f[0]);C=z+48|0;n=C|0;f[0]=l;a[n>>2]=b[0];a[n+4>>2]=b[1];n=d+8|0;k=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]);n=(z+56|0)>>2;f[0]=k;a[n]=b[0];a[n+1]=b[1];var D=Ma(B,Cm|0,(j=h,h+=8,a[j>>2]=(0>l?l-.5:l+.5)&-1,a[j+4>>2]=(0>k?k-.5:k+.5)&-1,j));a:do{if(A){var E=z|0;k=z>>2;l=C>>2;for(var F=p|0,H=p+8|0,I=z+16|0,J=z+24|0,K=z+32|0,L=z+40|0,Q=z+48|0,S=0,R=1,U=B+D|0,W=3;;){a[k]=a[l];a[k+1]=a[l+1];a[k+2]=a[l+2];a[k+3]=a[l+3];var V=S+1|0,Y=(V<<4)+d|0,Y=(b[0]=a[Y>>2],b[1]=a[Y+4>>2],f[0]);f[0]=Y;a[I>>2]=b[0];a[I+4>>2]=b[1];V=(V<<4)+d+8|0;V=(b[0]=a[V>>2],b[1]=a[V+4>>2],f[0]);f[0]=V;a[J>>2]=b[0];a[J+4>>2]=b[1];V=S+2|0;Y=(V<<4)+d|0;Y=(b[0]=a[Y>>2],b[1]=a[Y+4>>2],f[0]);f[0]=Y;a[K>>2]=b[0];a[K+4>>2]=b[1];V=(V<<4)+d+8|0;V=(b[0]=a[V>>2],b[1]=a[V+4>>2],f[0]);f[0]=V;a[L>>2]=b[0];a[L+4>>2]=b[1];S=S+3|0;V=(S<<4)+d|0;V=(b[0]=a[V>>2],b[1]=a[V+4>>2],f[0]);f[0]=V;a[Q>>2]=b[0];a[Q+4>>2]=b[1];S=(S<<4)+d+8|0;S=(b[0]=a[S>>2],b[1]=a[S+4>>2],f[0]);f[0]=S;a[n]=b[0];a[n+1]=b[1];for(S=1;;){Ld(p,E,3,(S|0)/6,0,0);var V=(b[0]=a[F>>2],b[1]=a[F+4>>2],f[0]),Z=(b[0]=a[H>>2],b[1]=a[H+4>>2],f[0]),V=0>V?V-.5:V+.5,Z=0>Z?Z-.5:Z+.5,Z=U+Ma(U,Cm|0,(j=h,h+=8,a[j>>2]=V&-1,a[j+4>>2]=Z&-1,j))|0,U=S+1|0;if(7==(U|0)){break}else{S=U,U=Z}}R=R+6|0;V=W+3|0;if((V|0)<(e|0)){S=W,U=Z,W=V}else{var $=R;break a}}}else{$=1}}while(0);d=a[g>>2];e=(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]);N(c,iR|0,(j=h,h+=60,a[j>>2]=3,a[j+4>>2]=v,a[j+8>>2]=d,a[j+12>>2]=t,a[j+16>>2]=u,a[j+20>>2]=s,a[j+24>>2]=w,a[j+28>>2]=0,a[j+32>>2]=m,f[0]=e,a[j+36>>2]=b[0],a[j+40>>2]=b[1],a[j+44>>2]=0,a[j+48>>2]=0,a[j+52>>2]=0,a[j+56>>2]=$,j));N(c,jR|0,(j=h,h+=4,a[j>>2]=B,j));G(B);if(0<($|0)){d=$-1|0;for(e=0;!(N(c,kR|0,(j=h,h+=4,a[j>>2]=0!=(e%d|0)&1,j)),e=e+1|0,(e|0)==($|0));){}}y(c,wd|0);h=g}function ry(c,d,e,g){var q=h,m=0<(e|0);a:do{if(m){for(var l=c,k=0;;){var n=(k<<4)+d|0,n=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),n=0>n?n-.5:n+.5,z=(k<<4)+d+8|0,z=(b[0]=a[z>>2],b[1]=a[z+4>>2],f[0]),z=0>z?z-.5:z+.5;N(l,Cm|0,(j=h,h+=8,a[j>>2]=n&-1,a[j+4>>2]=z&-1,j));k=k+1|0;if((k|0)==(e|0)){break a}}}}while(0);0!=(g|0)&&(e=d|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),d=d+8|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),N(c,Cm|0,(j=h,h+=8,a[j>>2]=(0>e?e-.5:e+.5)&-1,a[j+4>>2]=(0>d?d-.5:d+.5)&-1,j)));y(c,wd|0);h=q}function ZQ(b){var c=h,d=a[Ij>>2];0==(d|0)&&(a[Bq>>2]=64,d=Gb(64),a[Ij>>2]=d);var g=m[b];if(0==g<<24>>24){return m[d]=0,h=c,d}for(var e=d,d=0;;){var b=b+1|0,f=a[Bq>>2];(d|0)>(f-8|0)?(f<<=1,a[Bq>>2]=f,f=mc(a[Ij>>2],f),a[Ij>>2]=f,f=f+d|0):f=e;-1>24?(92==g<<24>>24&&(m[f]=92,d=d+1|0,f=f+1|0),m[f]=g,d=d+1|0,f=f+1|0):(m[f]=92,Ma(f+1|0,lR|0,(j=h,h+=4,a[j>>2]=g&255,j)),d=d+4|0,f=f+4|0);g=m[b];if(0==g<<24>>24){break}else{e=f}}d=a[Ij>>2];m[f]=0;h=c;return d}function mR(b){var c=a[b+16>>2],d=a[b+64>>2];if(1==(d|0)){var d=c+148|0,g=a[d>>2];0!=(g|0)&&0!=m[g]<<24>>24&&(y(b,sy|0),y(b,nc(a[d>>2])),y(b,gc|0),y(b,nc(a[a[c+8>>2]+12>>2])),y(b,wd|0))}else{3==(d|0)?(c=nc(a[a[c+8>>2]+12>>2]),y(b,nR|0),y(b,c),y(b,oR|0),y(b,c),y(b,ty|0)):0==(d|0)&&(y(b,pR|0),c=c+148|0,d=a[c>>2],0!=(d|0)&&0!=m[d]<<24>>24&&(y(b,sy|0),y(b,nc(a[c>>2])),y(b,wd|0)))}}function Cq(c,d,e,g,q,D,l,k){var n,z=h;if(!(0==(e|0)|0==(g|0))){(a[uy>>2]|0)<(g|0)&&(n=g+10|0,a[uy>>2]=n,n=mc(a[Ue>>2],n<<3),a[Ue>>2]=n);n=0<(g|0);a:do{if(n){for(var p=0;;){var s=(p<<4)+e|0,v=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]),s=a[Ue>>2];a[s+(p<<3)>>2]=(0>v?v-.5:v+.5)&-1;v=(p<<4)+e+8|0;v=(b[0]=a[v>>2],b[1]=a[v+4>>2],f[0]);a[s+(p<<3)+4>>2]=(0>v?v-.5:v+.5)&-1;p=p+1|0;if((p|0)==(g|0)){break a}}}}while(0);e=c+64|0;p=a[e>>2];s=0==(q|0);do{if(!(0!=(p|0)|s)&&0!=m[q]<<24>>24){if(2==(d|0)){N(c,qR|0,(j=h,h+=4,a[j>>2]=q,j));a:do{if(n){for(d=0;;){if(q=a[Ue>>2],D=a[q+(d<<3)+4>>2],N(c,rR|0,(j=h,h+=8,a[j>>2]=a[q+(d<<3)>>2],a[j+4>>2]=D,j)),d=d+1|0,(d|0)==(g|0)){break a}}}}while(0);y(c,wd|0)}else{0==(d|0)?(e=a[Ue>>2]>>2,g=a[e],d=a[e+3],D=a[e+2],e=a[e+1],N(c,sR|0,(j=h,h+=20,a[j>>2]=q,a[j+4>>2]=g,a[j+8>>2]=d,a[j+12>>2]=D,a[j+16>>2]=e,j))):1==(d|0)?(D=a[Ue>>2]>>2,g=a[D],d=a[D+1],D=a[D+2]-g|0,N(c,tR|0,(j=h,h+=16,a[j>>2]=q,a[j+4>>2]=g,a[j+8>>2]=d,a[j+12>>2]=D,j))):sa(Dq|0,65,Eq|0,vd|0)}h=z;return}}while(0);if(!(1!=(p|0)|s)&&0!=m[q]<<24>>24){0==(d|0)?(g=a[Ue>>2]>>2,d=a[g+3],e=a[g+2],n=a[g+1],N(c,uR|0,(j=h,h+=24,a[j>>2]=a[g],a[j+4>>2]=d,a[j+8>>2]=e,a[j+12>>2]=n,a[j+16>>2]=q,a[j+20>>2]=D,j))):sa(Dq|0,77,Eq|0,vd|0);h=z;return}if(2>(p-2|0)>>>0){1==(d|0)?y(c,vR|0):0==(d|0)?y(c,wR|0):2==(d|0)?y(c,xR|0):sa(Dq|0,93,Eq|0,vd|0);0!=(k|0)&&0!=m[k]<<24>>24&&(y(c,yR|0),y(c,Bo(k)),y(c,ue|0));!s&&0!=m[q]<<24>>24&&(y(c,zR|0),y(c,Bo(q)),y(c,ue|0));0!=(l|0)&&0!=m[l]<<24>>24&&(y(c,vy|0),y(c,nc(l)),y(c,ue|0));0!=(D|0)&&0!=m[D]<<24>>24&&(y(c,AR|0),y(c,nc(D)),y(c,ue|0));y(c,BR|0);y(c,CR|0);a:do{if(2==(d|0)){if(q=a[Ue>>2],D=a[q+4>>2],N(c,DR|0,(j=h,h+=8,a[j>>2]=a[q>>2],a[j+4>>2]=D,j)),1<(g|0)){for(q=1;;){if(D=a[Ue>>2],n=a[D+(q<<3)+4>>2],N(c,ER|0,(j=h,h+=8,a[j>>2]=a[D+(q<<3)>>2],a[j+4>>2]=n,j)),q=q+1|0,(q|0)==(g|0)){break a}}}}else{1==(d|0)?(n=a[Ue>>2]>>2,q=a[n],D=a[n+1],n=a[n+2]-q|0,N(c,FR|0,(j=h,h+=12,a[j>>2]=q,a[j+4>>2]=D,a[j+8>>2]=n,j))):0==(d|0)&&(q=a[Ue>>2]>>2,D=a[q+3],n=a[q+2],l=a[q+1],N(c,GR|0,(j=h,h+=16,a[j>>2]=a[q],a[j+4>>2]=D,a[j+8>>2]=n,a[j+12>>2]=l,j)))}}while(0);3==(a[e>>2]|0)?y(c,Dm|0):y(c,ty|0)}}h=z}function HR(b){var c,d=b>>2,e=h;h+=8;var f=a[d+4];m[Fq]=0;c=(b+12|0)>>2;var D=f+8|0;if(0==(a[a[c]+28>>2]|0)){N(b,IR|0,(j=h,h+=4,a[j>>2]=a[a[D>>2]+12>>2],j));var l=b+64|0;2==(a[l>>2]|0)?y(b,JR|0):y(b,KR|0);if(0==(a[a[c]+20>>2]|0)){if(2==(a[l>>2]|0)){var l=a[d+113],k=a[d+114],n=a[d+115];N(b,wy|0,(j=h,h+=16,a[j>>2]=a[d+112],a[j+4>>2]=l,a[j+8>>2]=k,a[j+12>>2]=n,j))}else{y(b,LR|0)}}y(b,MR|0);np(b,a[a[c]+24>>2],I|0);d=h;l=a[bj>>2];if(0!=(l|0)&&(l=J[a[l>>2]](l,0,128),0!=(l|0))){for(;!(0==m[l+16|0]<<24>>24&&(N(b,NR|0,(j=h,h+=4,a[j>>2]=a[l+12>>2],j)),y(b,OR|0),Pu(b,l),y(b,PR|0),y(b,QR|0)),k=a[bj>>2],l=J[a[k>>2]](k,l,8),0==(l|0));){}}h=d;c=a[a[c]+20>>2];0!=(c|0)&&(d=e|0,a[d>>2]=a[c>>2],a[e+4>>2]=0,np(b,0,d))}a[Gq>>2]=1==m[a[D>>2]+151|0]<<24>>24&1;m[Fq]||(y(b,RR|0),m[Fq]=1);f=a[f+148>>2];0!=(f|0)&&N(b,SR|0,(j=h,h+=4,a[j>>2]=f,j));h=e}function TR(c){var d,e=c>>2,g=h,q=a[e+112],m=a[e+113],l=a[e+114],k=a[e+115];d=(c+12|0)>>2;var n=a[a[d]+28>>2]+1|0;N(c,UR|0,(j=h,h+=8,a[j>>2]=n,a[j+4>>2]=n,j));0==(a[a[d]+20>>2]|0)&&N(c,VR|0,(j=h,h+=16,a[j>>2]=q,a[j+4>>2]=m,a[j+8>>2]=l,a[j+12>>2]=k,j));var z=c+356|0,n=0!=(a[z>>2]|0)?WR|0:XR|0;N(c,YR|0,(j=h,h+=4,a[j>>2]=n,j));n=c+64|0;1==(a[n>>2]|0)&&N(c,ZR|0,(j=h,h+=8,a[j>>2]=l,a[j+4>>2]=k,j));var p=a[e+49],s=a[e+50];N(c,$R|0,(j=h,h+=12,a[j>>2]=a[e+48],a[j+4>>2]=p,a[j+8>>2]=s,j));0==(a[a[d]+20>>2]|0)&&N(c,aS|0,(j=h,h+=16,a[j>>2]=q,a[j+4>>2]=m,a[j+8>>2]=l-q|0,a[j+12>>2]=k-m|0,j));var e=c+480|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),p=c+488|0,p=(b[0]=a[p>>2],b[1]=a[p+4>>2],f[0]),z=a[z>>2],s=c+496|0,s=(b[0]=a[s>>2],b[1]=a[s+4>>2],f[0]),v=c+504|0,v=(b[0]=a[v>>2],b[1]=a[v+4>>2],f[0]);N(c,bS|0,(j=h,h+=36,f[0]=e,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=p,a[j+8>>2]=b[0],a[j+12>>2]=b[1],a[j+16>>2]=z,f[0]=s,a[j+20>>2]=b[0],a[j+24>>2]=b[1],f[0]=v,a[j+28>>2]=b[0],a[j+32>>2]=b[1],j));if(1==(a[n>>2]|0)){if(14399<(l|0)|14399<(k|0)){J[a[a[d]+16>>2]](cS|0,(j=h,h+=12,a[j>>2]=l,a[j+4>>2]=k,a[j+8>>2]=14400,j))}N(c,dS|0,(j=h,h+=16,a[j>>2]=q,a[j+4>>2]=m,a[j+8>>2]=l,a[j+12>>2]=k,j))}h=g}function eS(c,d,e,g){var h;h=(c+16|0)>>2;do{if(0!=(g|0)){var j=a[h],l=j+76|0;if(.5<(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])){Nf(c,j+52|0);j=c;y(j,Jj|0);var l=d|0,k=d+8|0;Hd(j,(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]));y(j,Kj|0);l=1<(e|0);a:do{if(l){for(k=1;;){var n=(k<<4)+d|0,m=(k<<4)+d+8|0;Hd(j,(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0]));y(j,Hq|0);k=k+1|0;if((k|0)==(e|0)){break a}}}}while(0);y(j,xy|0)}}}while(0);g=a[h]+40|0;if(.5<(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])){Em(c);Nf(c,a[h]+16|0);y(c,Jj|0);h=d|0;g=d+8|0;Hd(c,(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0]),(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]));y(c,Kj|0);h=1<(e|0);a:do{if(h){for(g=1;;){if(j=(g<<4)+d|0,l=(g<<4)+d+8|0,Hd(c,(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]),(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])),y(c,Hq|0),g=g+1|0,(g|0)==(e|0)){break a}}}}while(0);y(c,fS|0)}}function gS(c,d,e,g,h,j){g=(c+16|0)>>2;do{if(0!=(j|0)){var h=a[g],l=h+76|0;if(.5<(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])){Nf(c,h+52|0);h=c;y(h,Jj|0);var l=d|0,k=d+8|0;Hd(h,(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]));y(h,Kj|0);l=1<(e|0);a:do{if(l){for(k=1;;){if(kf(h,(k<<4)+d|0,3),y(h,yy|0),k=k+3|0,(k|0)>=(e|0)){break a}}}}while(0);y(h,xy|0)}}}while(0);j=a[g]+40|0;if(.5<(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0])){Em(c);Nf(c,a[g]+16|0);y(c,Jj|0);j=d|0;g=d+8|0;Hd(c,(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]),(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]));y(c,Kj|0);j=1<(e|0);a:do{if(j){for(g=1;;){if(kf(c,(g<<4)+d|0,3),y(c,yy|0),g=g+3|0,(g|0)>=(e|0)){break a}}}}while(0);y(c,zy|0)}}function Nf(c,d){var e=h;if(0!=(d|0)){var g=a[a[c+16>>2]+4>>2],g=2==(g|0)?jh|0:3==(g|0)?Dg|0:0==(g|0)||1==(g|0)?Yf|0:hS|0,m=d|0,m=(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0]),y=d+8|0,y=(b[0]=a[y>>2],b[1]=a[y+4>>2],f[0]),l=d+16|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);N(c,iS|0,(j=h,h+=28,f[0]=m,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=y,a[j+8>>2]=b[0],a[j+12>>2]=b[1],f[0]=l,a[j+16>>2]=b[0],a[j+20>>2]=b[1],a[j+24>>2]=g,j))}h=e}function Em(c){var d=h,e,g=c+16|0,q=a[g>>2],D=q+96|0,q=a[q+104>>2];gj(c,(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0]));for(y(c,jS|0);;){if(0==(q|0)){e=2216;break}D=q+4|0;q=a[q>>2];if(0==(q|0)){e=2217;break}if(0!=(ka(q,Cp|0)|0)){for(var l=q;;){var k=l+1|0;if(0==m[l]<<24>>24){break}else{l=k}}l=0==m[k]<<24>>24;a:do{if(!l){for(var n=k;;){for(N(c,kS|0,(j=h,h+=4,a[j>>2]=n,j));;){var z=n+1|0;if(0==m[n]<<24>>24){break}else{n=z}}if(0==m[z]<<24>>24){break a}else{n=z}}}}while(0);0==(ka(q,qh|0)|0)&&(l=a[g>>2]+96|0,f[0]=0,a[l>>2]=b[0],a[l+4>>2]=b[1]);N(c,lS|0,(j=h,h+=4,a[j>>2]=q,j))}q=D}2216==e?h=d:2217==e&&(h=d)}function mS(c,d,e,g){var q=h,D=a[c+16>>2];y(c,nS|0);var l=m[g+72|0]<<24>>24;108==(l|0)?y(c,oS|0):114==(l|0)?y(c,pS|0):y(c,qS|0);l=g+48|0;e=-((b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])+e);N(c,rS|0,(j=h,h+=16,f[0]=d,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=e,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));d=a[g+4>>2];if(0==(d|0)){N(c,sS|0,(j=h,h+=4,a[j>>2]=a[g+20>>2],j))}else{e=a[a[a[c>>2]+128>>2]+252>>2];if(1==(e|0)){var l=d+16|0,k=d+8|0,e=d|0}else{2==(e|0)?(l=d+32|0,k=d+28|0,e=d+24|0):(l=d+16|0,k=d+8|0,e=d+4|0)}var l=a[l>>2],k=a[k>>2],n=a[d+12>>2];N(c,tS|0,(j=h,h+=4,a[j>>2]=a[e>>2],j));d=a[d+24>>2];0!=(d|0)&&N(c,uS|0,(j=h,h+=4,a[j>>2]=d,j));y(c,ue|0);0!=(k|0)&&N(c,vS|0,(j=h,h+=4,a[j>>2]=k,j));0!=(n|0)&&N(c,wS|0,(j=h,h+=4,a[j>>2]=n,j));0!=(l|0)&&N(c,xS|0,(j=h,h+=4,a[j>>2]=l,j))}d=g+24|0;N(c,yS|0,(j=h,h+=8,f[0]=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),a[j>>2]=b[0],a[j+4>>2]=b[1],j));d=D+16|0;D=a[D+48>>2];5==(D|0)?0!=(Lb(a[d>>2],Ac|0)|0)&&N(c,zS|0,(j=h,h+=4,a[j>>2]=a[d>>2],j)):1==(D|0)?(D=m[d+1|0]&255,e=m[d+2|0]&255,N(c,AS|0,(j=h,h+=12,a[j>>2]=m[d]&255,a[j+4>>2]=D,a[j+8>>2]=e,j))):sa(Ay|0,379,BS|0,vd|0);y(c,CS|0);g=(g|0)>>2;g=a[g];g=nc(g);y(c,g);y(c,DS|0);h=q}function Fm(c,d){var e,g=h,q=a[c+16>>2];e=q>>2;y(c,ES|0);if(0==(d|0)){y(c,sg|0)}else{var D=q+52|0;FS(c,D);1==(a[e+21]|0)&&(D=m[D+3|0],0==D<<24>>24||-1==D<<24>>24||N(c,GS|0,(j=h,h+=8,f[0]=(D&255)/255,a[j>>2]=b[0],a[j+4>>2]=b[1],j)))}y(c,HS|0);D=q+16|0;FS(c,D);q=q+96|0;q=(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]);1!=q&&N(c,IS|0,(j=h,h+=8,f[0]=q,a[j>>2]=b[0],a[j+4>>2]=b[1],j));q=a[e+22];1==(q|0)?N(c,By|0,(j=h,h+=4,a[j>>2]=JS|0,j)):2==(q|0)&&N(c,By|0,(j=h,h+=4,a[j>>2]=KS|0,j));1==(a[e+12]|0)&&(e=m[D+3|0],0==e<<24>>24||-1==e<<24>>24||N(c,LS|0,(j=h,h+=8,f[0]=(e&255)/255,a[j>>2]=b[0],a[j+4>>2]=b[1],j)));y(c,ue|0);h=g}function FS(b,c){var d=h,e=c,c=h;h+=36;for(var e=e>>2,f=c>>2,D=e+9;e>2];5==(e|0)?y(b,a[c>>2]):1==(e|0)?0==m[c+3|0]<<24>>24?y(b,sg|0):(e=a[c>>2],N(b,Iq|0,(j=h,h+=12,a[j>>2]=e&255,a[j+4>>2]=e>>>8&255,a[j+8>>2]=e>>>16&255,j))):sa(Ay|0,86,MS|0,vd|0);h=d}function NS(c,d,e,g){var q=h,D=a[c+16>>2];if(0!=(a[D+88>>2]|0)){var l=g+24|0,k=c+348|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])*(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])&-1;0!=(l|0)&&(Gm(c),y(c,OS|0),Hd(c,d,e-.55*(l|0)),y(c,PS|0),y(c,a[g>>2]),y(c,QS|0),y(c,Lj|0),gi(c,D+16|0),y(c,RS|0),d=a[g+4>>2],d=a[(0==(d|0)?g+20|0:d+4|0)>>2],y(c,ue|0),y(c,d),y(c,ue|0),N(c,SS|0,(j=h,h+=4,a[j>>2]=l,j)),g=m[g+72|0]<<24>>24,108==(g|0)?y(c,TS|0):114==(g|0)&&y(c,US|0),Mj(c),y(c,wd|0))}h=q}function VS(c,d,e){var g,h,j,l=a[c+16>>2];j=(l+88|0)>>2;if(0!=(a[j]|0)){h=d+16|0;var k=(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0]);h=(d|0)>>2;var n=(b[0]=a[h],b[1]=a[h+1],f[0]);g=d+24|0;var m=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);g=(d+8|0)>>2;var p=(b[0]=a[g],b[1]=a[g+1],f[0]);f[0]=n-(k-n);a[h]=b[0];a[h+1]=b[1];f[0]=p-(m-p);a[g]=b[0];a[g+1]=b[1];Gm(c);y(c,WS|0);kf(c,d,2);y(c,Lj|0);0==(e|0)?0==(a[tg>>2]|0)?y(c,oj|0):y(c,hf|0):gi(c,l+52|0);1==(a[tg>>2]|0)&&(a[tg>>2]=0);y(c,Jq|0);d=l+96|0;gj(c,(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]));y(c,Cy|0);gi(c,l+16|0);l=a[j];1==(l|0)?(y(c,Hm|0),j=a[j]):j=l;2==(j|0)&&y(c,Im|0);Mj(c);y(c,wd|0)}}function XS(c,d,e,g){var h,j=a[c+16>>2];h=(j+88|0)>>2;0!=(a[h]|0)&&(Gm(c),y(c,YS|0),kf(c,d,e),y(c,Lj|0),0==(g|0)?0==(a[tg>>2]|0)?y(c,oj|0):y(c,hf|0):gi(c,j+52|0),1==(a[tg>>2]|0)&&(a[tg>>2]=0),y(c,Jq|0),d=j+96|0,gj(c,(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0])),y(c,Cy|0),gi(c,j+16|0),j=a[h],1==(j|0)?(y(c,Hm|0),h=a[h]):h=j,2==(h|0)&&y(c,Im|0),Mj(c),y(c,wd|0))}function Gm(b){0==m[b+140|0]<<24>>24?y(b,ZS|0):y(b,a[b+144>>2])}function gi(b,c){var d=h,e=c,c=h;h+=36;for(var e=e>>2,f=c>>2,D=e+9;e>2];1==(e|0)?0==m[c+3|0]<<24>>24?y(b,oj|0):(e=a[c>>2],N(b,Iq|0,(j=h,h+=12,a[j>>2]=e&255,a[j+4>>2]=e>>>8&255,a[j+8>>2]=e>>>16&255,j))):5==(e|0)?y(b,a[c>>2]):sa(Dy|0,51,$S|0,vd|0);h=d}function Mj(b){var c,d=h;c=a[b+16>>2]>>2;var e=a[c+3];if(9==(e|0)||2==(e|0)||3==(e|0)){var f=a[a[c+2]>>2]>>8,m=Dg|0,l=1}else{8==(e|0)?(f=a[a[c+2]>>2]>>8,m=jh|0,l=1):0==(e|0)?(f=a[a[c+2]>>2]>>8,m=Yf|0,l=1):4==(e|0)?(f=a[a[c+2]>>2]>>8,m=aT|0,l=0):10==(e|0)?(f=a[a[c+2]>>2]>>8,m=jh|0,l=0):11==(e|0)||6==(e|0)||7==(e|0)?(f=a[a[c+2]>>2]>>8,m=Dg|0,l=0):5==(e|0)?(f=a[a[c+2]>>2]>>8,m=Yf|0,l=0):1==(e|0)?(f=a[a[c+2]>>2]>>8,m=Yf|0,l=1):sa(Dy|0,148,bT|0,vd|0)}N(b,cT|0,(j=h,h+=12,a[j>>2]=l,a[j+4>>2]=m,a[j+8>>2]=f,j));h=d}function Of(b){var c=h;h+=16;var d,e=a[Kq>>2];if(0==(e|0)){a[Lq>>2]=64;var e=Cb(64),f=a[Kq>>2]=e}else{f=e}var e=c+15|0,j=c+14|0,l=b,k=f,n=b=0,z=0,p=0,s=f;a:for(;;){for(var v=0==(b|0),f=l,t=k,l=z,k=p,u=s;;){if(0==(f|0)){d=2456;break a}var w=m[f];if(0==w<<24>>24){d=2455;break a}var A=a[Lq>>2];(n|0)>(A-8|0)?(A<<=1,a[Lq>>2]=A,w=wb(u,A),a[Kq>>2]=w,A=w+n|0,p=m[f]):(A=t,p=w,w=u);if(32==p<<24>>24){d=2433;break}else{if(38==p<<24>>24){d=2432;break}else{if(60==p<<24>>24){var y=wo|0,C=4;d=2447;break}else{if(45==p<<24>>24){var D=k,E=l,F=5,G=kt|0;break}else{if(62==p<<24>>24){var H=4,I=yo|0;d=2449;break}}}}}if(34==p<<24>>24){y=zo|0;C=6;d=2447;break}else{if(39==p<<24>>24){H=5;I=Ao|0;d=2449;break}}if(0<=p<<24>>24){y=f;C=1;d=2447;break}for(var J=0,K=127,L=p&255;;){var N=J+1|0,S=K&L,K=K>>>1;if(K>>>0>>0){J=N,L=S}else{break}}0<(J|0)?(K=N,J=S):(K=l,J=(k<<6)+S|0);K=K-1|0;L=f+1|0;if(0<(K|0)){f=L,t=A,l=K,k=J,u=w}else{d=2443;break}}do{if(2433==d){v?d=2436:32==m[b]<<24>>24?(y=lt|0,C=6,d=2447):d=2436}else{if(2432==d){d=f+1|0;p=m[d];if(35!=p<<24>>24){for(;;){s=d+1|0;if(!(26>(p-97&255)|26>(p-65&255))){var Q=p;break}d=s;p=m[s]}}else{if(d=f+2|0,p=m[d],120==p<<24>>24||88==p<<24>>24){for(d=f+3|0;;){if(p=m[d],10>(p-48&255)|6>(p-97&255)|6>(p-65&255)){d=d+1|0}else{Q=p;break}}}else{for(;;){s=d+1|0;if(10<=(p-48&255)){Q=p;break}d=s;p=m[s]}}}d=59==Q<<24>>24;d&=1;0==(d|0)?(y=xo|0,C=5,d=2447):d=2436}else{if(2443==d){d=0;m[e]=59;p=j;s=3;for(z=J;;){var R=p-1|0;m[p]=((z>>>0)%10|48)&255;var U=Math.floor((z>>>0)/10),V=s+1|0;if(12<(V|0)){d=2445;break a}if(9>>0){p=R,s=V,z=U}else{break}}p=p-2|0;m[R]=35;m[p]=38;if(0==(V|0)){l=L;k=A;b=f;z=K;p=U;s=w;continue a}else{D=U,E=K,F=V,G=p}}else{2449==d&&(d=0,D=k,E=l,F=H,G=I)}}}}while(0);2436==d&&(y=f,C=1,d=2447);2447==d&&(d=0,D=k,E=l,F=C,G=y);n=F+n|0;l=F;b=G;for(v=A;;){l=l-1|0;m[v]=m[b];if(0==(l|0)){break}b=b+1|0;v=v+1|0}l=f+1|0;k=A+F|0;b=f;z=E;p=D;s=w}if(2445==d){Lc(dT|0,46,1,a[oa>>2]),Pe()}else{if(2455==d||2456==d){return m[t]=0,h=c,u}}}function eT(c){var d=h,e=a[c+16>>2],g=c+228|0,q=c+212|0;a[$d>>2]=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])-(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0])&-1;g=c+220|0;q=c+204|0;a[Ug>>2]=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])-(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0])&-1;y(c,fT|0);y(c,gT|0);e=a[a[e+8>>2]+12>>2];0!=m[e]<<24>>24&&(y(c,hT|0),y(c,Of(e)),y(c,iT|0));N(c,jT|0,(j=h,h+=4,a[j>>2]=a[c+164>>2]*a[c+160>>2]|0,j));y(c,kT|0);y(c,lT|0);y(c,mT|0);y(c,nT|0);y(c,oT|0);y(c,pT|0);y(c,qT|0);y(c,rT|0);y(c,sT|0);y(c,tT|0);y(c,uT|0);y(c,vT|0);y(c,wT|0);y(c,Ey|0);y(c,Fy|0);y(c,Jm|0);y(c,Gy|0);y(c,Km|0);y(c,Lm|0);y(c,Hy|0);y(c,Iy|0);y(c,Jm|0);y(c,Jy|0);y(c,Km|0);y(c,Lm|0);y(c,xT|0);y(c,Ey|0);y(c,Fy|0);y(c,Jm|0);y(c,Jy|0);y(c,Km|0);y(c,Lm|0);y(c,Hy|0);y(c,Iy|0);y(c,Jm|0);y(c,Gy|0);y(c,Km|0);y(c,Lm|0);y(c,yT|0);y(c,zT|0);y(c,AT|0);y(c,BT|0);y(c,CT|0);y(c,DT|0);e=a[Ug>>2];g=a[$d>>2]+10|0;N(c,ET|0,(j=h,h+=8,a[j>>2]=e,a[j+4>>2]=g,j));y(c,FT|0);y(c,GT|0);y(c,HT|0);y(c,IT|0);y(c,JT|0);e=a[Ug>>2];g=a[$d>>2];N(c,KT|0,(j=h,h+=8,a[j>>2]=e,a[j+4>>2]=g,j));e=a[Ug>>2];g=a[$d>>2];N(c,LT|0,(j=h,h+=8,a[j>>2]=e,a[j+4>>2]=g,j));h=d}function MT(c,d,e,g){var q,D=h,l=a[c+16>>2],k=m[g+72|0]<<24>>24;if(108==(k|0)){var n=g+56|0,k=d,n=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0])}else{114==(k|0)?(k=g+56|0,n=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),k=d-n):(k=g+56|0,n=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),k=d-.5*n)}q=(g+64|0)>>2;var z=(b[0]=a[q],b[1]=a[q+1],f[0]),d=(g+24|0)>>2,p=(b[0]=a[d],b[1]=a[d+1],f[0]);z>2]>>>0)-e,s=p/5,p=12>p?s+1.4:s+2,z=e-z+p;N(c,NT|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j));N(c,Ky|0,(j=h,h+=16,f[0]=q,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=z,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));N(c,Ly|0,(j=h,h+=16,f[0]=k+(n+8)-q,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=e+p-z,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));y(c,OT|0);y(c,PT|0);e=(g+4|0)>>2;k=a[e];0==(k|0)?N(c,My|0,(j=h,h+=4,a[j>>2]=a[g+20>>2],j)):(N(c,My|0,(j=h,h+=4,a[j>>2]=a[k+4>>2],j)),k=a[e],n=a[k+8>>2],0!=(n|0)&&(N(c,QT|0,(j=h,h+=4,a[j>>2]=n,j)),k=a[e]),n=a[k+12>>2],0==(n|0)?e=k:(N(c,RT|0,(j=h,h+=4,a[j>>2]=n,j)),e=a[e]),e=a[e+16>>2],0!=(e|0)&&N(c,ST|0,(j=h,h+=4,a[j>>2]=e,j)));N(c,TT|0,(j=h,h+=8,f[0]=(b[0]=a[d],b[1]=a[d+1],f[0]),a[j>>2]=b[0],a[j+4>>2]=b[1],j));d=l+16|0;l=a[l+48>>2];1==(l|0)?(l=m[d+1|0]&255,e=m[d+2|0]&255,N(c,UT|0,(j=h,h+=12,a[j>>2]=m[d]&255,a[j+4>>2]=l,a[j+8>>2]=e,j))):5==(l|0)?0!=(Lb(a[d>>2],Ac|0)|0)&&N(c,VT|0,(j=h,h+=4,a[j>>2]=a[d>>2],j)):sa(Ny|0,442,WT|0,vd|0);y(c,XT|0);g=(g|0)>>2;g=a[g];g=Of(g);y(c,g);y(c,YT|0);y(c,ZT|0);h=D}function Mq(c){var d=h,e=a[c+16>>2];y(c,$T|0);aU(c,e+16|0);var g=e+96|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);1!=g&&N(c,bU|0,(j=h,h+=8,f[0]=g,a[j>>2]=b[0],a[j+4>>2]=b[1],j));e=a[e+88>>2];2==(e|0)?y(c,cU|0):1==(e|0)&&y(c,dU|0);y(c,eU|0);h=d}function aU(b,c){var d=h,e=c,c=h;h+=36;for(var e=e>>2,f=c>>2,D=e+9;e>2];1==(e|0)?0==m[c+3|0]<<24>>24?y(b,sg|0):(e=a[c>>2],N(b,Iq|0,(j=h,h+=12,a[j>>2]=e&255,a[j+4>>2]=e>>>8&255,a[j+8>>2]=e>>>16&255,j))):5==(e|0)?y(b,a[c>>2]):sa(Ny|0,95,fU|0,vd|0);h=d}function Oy(b,c){if(0==(c|0)){y(b,gU|0)}else{var d=a[b+16>>2];y(b,hU|0);aU(b,d+52|0);y(b,iU|0)}}function jU(c,d,e){var g,q=d>>2,y=h;h+=72;g=e>>2;e=h;h+=32;a[e>>2]=a[g];a[e+4>>2]=a[g+1];a[e+8>>2]=a[g+2];a[e+12>>2]=a[g+3];a[e+16>>2]=a[g+4];a[e+20>>2]=a[g+5];a[e+24>>2]=a[g+6];a[e+28>>2]=a[g+7];var l;0==(c|0)&&sa(pd|0,146,Nq|0,Nj|0);0==(d|0)&&sa(pd|0,147,Nq|0,Oh|0);0==(a[q+2]|0)&&sa(pd|0,148,Nq|0,Ph|0);g=(d+52|0)>>2;if(0==(a[g]|0)){l=2592}else{var k=d+60|0,n=a[k>>2];100!=(n|0)&&(J[n](d),a[g]=0,a[k>>2]=0,a[q+14]=0,l=2592)}if(2592==l){if(0==JK(d)<<24>>24){h=y;return}l=a[q+5];2>(a[q+6]-6|0)>>>0&&(kC(l,y),k=a[y+28>>2],a[q+14]=k,n=Gb(k),a[g]=n,sr(l,n,k),m[d+16|0]=1);0!=(a[g]|0)&&(a[q+15]=100);LK(d);if(0==(a[g]|0)){h=y;return}}g=e|0;g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])-(a[q+8]|0);e=e+8|0;e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])-(a[q+9]|0);N(c,kU|0,(j=h,h+=16,f[0]=g,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=e,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));0==m[d+16|0]<<24>>24?N(c,lU|0,(j=h,h+=4,a[j>>2]=a[q+3],j)):Pu(c,d);N(c,Mm|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j));h=y}function mU(c,d,e){var g,m=h;g=e>>2;e=h;h+=32;a[e>>2]=a[g];a[e+4>>2]=a[g+1];a[e+8>>2]=a[g+2];a[e+12>>2]=a[g+3];a[e+16>>2]=a[g+4];a[e+20>>2]=a[g+5];a[e+24>>2]=a[g+6];a[e+28>>2]=a[g+7];0==(c|0)&&sa(pd|0,90,Oq|0,Nj|0);0==(d|0)&&sa(pd|0,91,Oq|0,Oh|0);d=d+8|0;0==(a[d>>2]|0)&&sa(pd|0,92,Oq|0,Ph|0);g=e|0;g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);g=(0>g?g-.5:g+.5)&-1;var y=e+8|0,y=(b[0]=a[y>>2],b[1]=a[y+4>>2],f[0]),y=(0>y?y-.5:y+.5)&-1,l=e+16|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),l=(0>l?l-.5:l+.5)&-1,e=e+24|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),e=(0>e?e-.5:e+.5)&-1,d=a[d>>2];N(c,nU|0,(j=h,h+=76,a[j>>2]=2,a[j+4>>2]=5,a[j+8>>2]=0,a[j+12>>2]=0,a[j+16>>2]=0,a[j+20>>2]=-1,a[j+24>>2]=1,a[j+28>>2]=-1,a[j+32>>2]=0,f[0]=0,a[j+36>>2]=b[0],a[j+40>>2]=b[1],a[j+44>>2]=0,a[j+48>>2]=0,a[j+52>>2]=0,a[j+56>>2]=0,a[j+60>>2]=0,a[j+64>>2]=5,a[j+68>>2]=0,a[j+72>>2]=d,j));N(c,oU|0,(j=h,h+=40,a[j>>2]=g,a[j+4>>2]=y,a[j+8>>2]=g,a[j+12>>2]=e,a[j+16>>2]=l,a[j+20>>2]=e,a[j+24>>2]=l,a[j+28>>2]=y,a[j+32>>2]=g,a[j+36>>2]=y,j));h=m}function pU(c,d,e){var g,m=h;g=e>>2;e=h;h+=32;a[e>>2]=a[g];a[e+4>>2]=a[g+1];a[e+8>>2]=a[g+2];a[e+12>>2]=a[g+3];a[e+16>>2]=a[g+4];a[e+20>>2]=a[g+5];a[e+24>>2]=a[g+6];a[e+28>>2]=a[g+7];0==(c|0)&&sa(pd|0,49,Pq|0,Nj|0);0==(d|0)&&sa(pd|0,50,Pq|0,Oh|0);d=d+8|0;0==(a[d>>2]|0)&&sa(pd|0,51,Pq|0,Ph|0);y(c,qU|0);y(c,a[d>>2]);var d=c+356|0,D=e+16|0;if(0==(a[d>>2]|0)){d=D|0;d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]);g=e|0;g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);var l=e+24|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),e=e+8|0,e=l-(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);N(c,rU|0,(j=h,h+=32,f[0]=d-g,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=e,a[j+8>>2]=b[0],a[j+12>>2]=b[1],f[0]=g,a[j+16>>2]=b[0],a[j+20>>2]=b[1],f[0]=-l,a[j+24>>2]=b[0],a[j+28>>2]=b[1],j))}else{g=e+24|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),l=e+8|0,l=g-(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),D|=0,D=(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0]),e|=0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),N(c,sU|0,(j=h,h+=32,f[0]=l,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=D-e,a[j+8>>2]=b[0],a[j+12>>2]=b[1],f[0]=e,a[j+16>>2]=b[0],a[j+20>>2]=b[1],f[0]=g,a[j+24>>2]=b[0],a[j+28>>2]=b[1],j)),N(c,tU|0,(j=h,h+=20,a[j>>2]=a[d>>2],f[0]=e,a[j+4>>2]=b[0],a[j+8>>2]=b[1],f[0]=g,a[j+12>>2]=b[0],a[j+16>>2]=b[1],j))}y(c,Py|0);h=m}function Gb(b){do{if(245>b>>>0){var c=11>b>>>0?16:b+11&-8,d=c>>>3,e=a[E>>2],f=e>>>(d>>>0);if(0!=(f&3|0)){var h=(f&1^1)+d|0,j=h<<1,c=(j<<2)+E+40|0,d=(j+2<<2)+E+40|0,b=a[d>>2],j=b+8|0,f=a[j>>2];(c|0)==(f|0)?a[E>>2]=e&(1<>>0>2]>>>0?S():(a[d>>2]=f,a[f+12>>2]=c);h<<=3;a[b+4>>2]=h|3;h=b+(h|4)|0;a[h>>2]|=1;return j}if(c>>>0>a[E+8>>2]>>>0){if(0==(f|0)){if(0==(a[E+4>>2]|0)){e=c;break}j=uU(c);if(0==(j|0)){e=c;break}return j}var b=2<>>12&16,f=d>>>(b>>>0),d=f>>>5&8,k=f>>>(d>>>0),f=k>>>2&4,n=k>>>(f>>>0),k=n>>>1&2,n=n>>>(k>>>0),m=n>>>1&1,f=(d|b|f|k|m)+(n>>>(m>>>0))|0,b=f<<1,k=(b<<2)+E+40|0,n=(b+2<<2)+E+40|0,d=a[n>>2],b=d+8|0,m=a[b>>2];(k|0)==(m|0)?a[E>>2]=e&(1<>>0>2]>>>0?S():(a[n>>2]=m,a[m+12>>2]=k);f<<=3;e=f-c|0;a[d+4>>2]=c|3;k=d;d=k+c|0;a[k+(c|4)>>2]=e|1;a[k+f>>2]=e;m=a[E+8>>2];0!=(m|0)&&(c=a[E+20>>2],k=m>>>2&1073741822,f=(k<<2)+E+40|0,n=a[E>>2],m=1<<(m>>>3),0==(n&m|0)?(a[E>>2]=n|m,j=f,h=(k+2<<2)+E+40|0):(k=(k+2<<2)+E+40|0,n=a[k>>2],n>>>0>2]>>>0?S():(j=n,h=k)),a[h>>2]=c,a[j+12>>2]=c,a[c+8>>2]=j,a[c+12>>2]=f);a[E+8>>2]=e;a[E+20>>2]=d;return j=b}e=c}else{if(4294967231>>0){e=-1}else{if(e=b+11&-8,0!=(a[E+4>>2]|0)&&(c=vU(e),0!=(c|0))){return j=c}}}}while(0);h=a[E+8>>2];e>>>0>h>>>0?(j=a[E+12>>2],e>>>0>>0?(j=j-e|0,a[E+12>>2]=j,h=a[E+24>>2],a[E+24>>2]=h+e|0,a[e+(h+4)>>2]=j|1,a[h+4>>2]=e|3,j=h+8|0):j=wU(e)):(c=h-e|0,j=a[E+20>>2],15>>0?(a[E+20>>2]=j+e|0,a[E+8>>2]=c,a[e+(j+4)>>2]=c|1,a[j+h>>2]=c,a[j+4>>2]=e|3):(a[E+8>>2]=0,a[E+20>>2]=0,a[j+4>>2]=h|3,h=h+(j+4)|0,a[h>>2]|=1),j=j+8|0);return j}function uU(b){var c,d,e=a[E+4>>2],f=(e&-e)-1|0,e=f>>>12&16,h=f>>>(e>>>0),f=h>>>5&8;d=h>>>(f>>>0);var h=d>>>2&4,j=d>>>(h>>>0);d=j>>>1&2;var j=j>>>(d>>>0),k=j>>>1&1,e=h=f=a[E+((f|e|h|d|k)+(j>>>(k>>>0))<<2)+304>>2];d=e>>2;for(f=(a[f+4>>2]&-8)-b|0;;){j=a[h+16>>2];if(0==(j|0)){if(h=a[h+20>>2],0==(h|0)){break}else{d=h}}else{d=j}j=(a[d+4>>2]&-8)-b|0;k=j>>>0>>0;h=d;e=k?d:e;d=e>>2;f=k?j:f}var j=e,n=a[E+16>>2];j>>>0>>0&&S();h=j+b|0;j>>>0>>0||S();var k=a[d+6],m=a[d+3],p=(m|0)==(e|0);a:do{if(p){var s=e+20|0,v=a[s>>2];do{if(0==(v|0)){var t=e+16|0,u=a[t>>2];if(0==(u|0)){var w=0;c=w>>2;break a}}else{t=s,u=v}}while(0);for(;;){if(s=u+20|0,v=a[s>>2],0!=(v|0)){t=s,u=v}else{if(s=u+16|0,v=a[s>>2],0==(v|0)){break}else{t=s,u=v}}}t>>>0>>0?S():(a[t>>2]=0,w=u,c=w>>2)}else{t=a[d+2],t>>>0>>0?S():(a[t+12>>2]=m,a[m+8>>2]=t,w=m,c=w>>2)}}while(0);n=0==(k|0);a:do{if(!n){m=e+28|0;p=(a[m>>2]<<2)+E+304|0;do{if((e|0)==(a[p>>2]|0)){if(a[p>>2]=w,0==(w|0)){a[E+4>>2]&=1<>2]^-1;break a}}else{if(k>>>0>2]>>>0&&S(),t=k+16|0,(a[t>>2]|0)==(e|0)?a[t>>2]=w:a[k+20>>2]=w,0==(w|0)){break a}}}while(0);w>>>0>2]>>>0&&S();a[c+6]=k;m=a[d+4];0!=(m|0)&&(m>>>0>2]>>>0?S():(a[c+4]=m,a[m+24>>2]=w));m=a[d+5];0!=(m|0)&&(m>>>0>2]>>>0?S():(a[c+5]=m,a[m+24>>2]=w))}}while(0);if(16>f>>>0){var A=f+b|0;a[d+1]=A|3;A=A+(j+4)|0;a[A>>2]|=1;return A=e+8|0}a[d+1]=b|3;a[b+(j+4)>>2]=f|1;a[j+f+b>>2]=f;j=a[E+8>>2];if(0!=(j|0)){b=a[E+20>>2];w=j>>>2&1073741822;c=(w<<2)+E+40|0;d=a[E>>2];j=1<<(j>>>3);if(0==(d&j|0)){a[E>>2]=d|j;var A=c,y=(w+2<<2)+E+40|0}else{w=(w+2<<2)+E+40|0,d=a[w>>2],d>>>0>2]>>>0?S():(A=d,y=w)}a[y>>2]=b;a[A+12>>2]=b;a[b+8>>2]=A;a[b+12>>2]=c}a[E+8>>2]=f;a[E+20>>2]=h;return A=e+8|0}function wU(b){var c,d;0==(a[Rc>>2]|0)&&xU();var e=0==(a[E+440>>2]&4|0);a:do{if(e){var f=a[E+24>>2];if(0==(f|0)){d=2751}else{if(f=Qy(f),0==(f|0)){d=2751}else{var h=a[Rc+8>>2],h=b+47-a[E+12>>2]+h&-h;if(2147483647>h>>>0){d=Ie(h);var j=(d|0)==(a[f>>2]+a[f+4>>2]|0);c=j?d:-1;var j=j?h:0,k=h,n=d;d=2758}else{var m=0}}}if(2751==d){if(f=Ie(0),-1==(f|0)){m=0}else{var h=a[Rc+8>>2],h=h+(b+47)&-h,p=f,s=a[Rc+4>>2],v=s-1|0,h=0==(v&p|0)?h:h-p+(v+p&-s)|0;2147483647>h>>>0?(d=Ie(h),c=(j=(d|0)==(f|0))?f:-1,j=j?h:0,k=h,n=d,d=2758):m=0}}b:do{if(2758==d){d=-k|0;if(-1!=(c|0)){var t=j,u=c;d=2771;break a}do{if(-1!=(n|0)&2147483647>k>>>0){if(k>>>0<(b+48|0)>>>0){if(m=a[Rc+8>>2],m=b+47-k+m&-m,2147483647>m>>>0){if(-1==(Ie(m)|0)){Ie(d);m=j;break b}else{m=m+k|0}}else{m=k}}else{m=k}}else{m=k}}while(0);if(-1!=(n|0)){t=m;u=n;d=2771;break a}a[E+440>>2]|=4;var w=j;d=2768;break a}}while(0);a[E+440>>2]|=4;w=m}else{w=0}d=2768}while(0);2768==d&&(e=a[Rc+8>>2],e=e+(b+47)&-e,2147483647>e>>>0&&(e=Ie(e),c=Ie(0),-1!=(c|0)&-1!=(e|0)&e>>>0>>0&&(c=c-e|0,e=(j=c>>>0>(b+40|0)>>>0)?e:-1,-1!=(e|0)&&(t=j?c:w,u=e,d=2771))));do{if(2771==d){w=a[E+432>>2]+t|0;a[E+432>>2]=w;w>>>0>a[E+436>>2]>>>0&&(a[E+436>>2]=w);w=a[E+24>>2];e=0==(w|0);a:do{if(e){c=a[E+16>>2];0==(c|0)|u>>>0>>0&&(a[E+16>>2]=u);a[E+444>>2]=u;a[E+448>>2]=t;a[E+456>>2]=0;a[E+36>>2]=a[Rc>>2];a[E+32>>2]=-1;for(c=0;!(j=c<<1,k=(j<<2)+E+40|0,a[E+(j+3<<2)+40>>2]=k,a[E+(j+2<<2)+40>>2]=k,c=c+1|0,32==(c|0));){}Qq(u,t-40|0)}else{j=E+444|0;for(c=j>>2;0!=(j|0);){var A=a[c],y=j+4|0,C=a[y>>2];if((u|0)==(A+C|0)){d=2780;break}j=a[c+2];c=j>>2}do{if(2780==d&&0==(a[c+3]&8|0)&&(j=w,j>>>0>=A>>>0&j>>>0>>0)){a[y>>2]=C+t|0;Qq(a[E+24>>2],a[E+12>>2]+t|0);break a}}while(0);u>>>0>2]>>>0&&(a[E+16>>2]=u);c=u+t|0;for(j=E+444|0;0!=(j|0);){var D=j|0;if((a[D>>2]|0)==(c|0)){d=2789;break}j=a[j+8>>2]}if(2789==d&&0==(a[j+12>>2]&8|0)){return a[D>>2]=u,A=j+4|0,a[A>>2]=a[A>>2]+t|0,b=yU(u,c,b)}zU(u,t)}}while(0);w=a[E+12>>2];if(w>>>0>b>>>0){return t=w-b|0,a[E+12>>2]=t,A=u=a[E+24>>2],a[E+24>>2]=A+b|0,a[b+(A+4)>>2]=t|1,a[u+4>>2]=b|3,b=u+8|0}}}while(0);a[Ea.c>>2]=12;return 0}function vU(b){var c,d,e,f,h,j=b>>2,k,n=-b|0,m=b>>>8;if(0==(m|0)){var p=0}else{if(16777215>>0){p=31}else{var s=(m+1048320|0)>>>16&8,v=m<>>16&4,u=v<>>16&2,A=14-(t|s|w)+(u<>>15)|0,p=b>>>((A+7|0)>>>0)&1|A<<1}}var y=a[E+(p<<2)+304>>2],C=0==(y|0);a:do{if(C){var D=0,F=n,G=0}else{var H=31==(p|0)?0:25-(p>>>1)|0,I=0,J=n,K=y;h=K>>2;for(var L=b<>>0>>0){if((Q|0)==(b|0)){D=K;F=R;G=K;break a}else{var U=K,V=R}}else{U=I,V=J}var W=a[h+5],Y=a[((L>>>31<<2)+16>>2)+h],Z=0==(W|0)|(W|0)==(Y|0)?N:W;if(0==(Y|0)){D=U;F=V;G=Z;break a}else{I=U,J=V,K=Y,h=K>>2,L<<=1,N=Z}}}}while(0);if(0==(G|0)&0==(D|0)){var $=2<>2]&($|-$);if(0==(ba|0)){var ca=0;return ca}var fa=(ba&-ba)-1|0,ka=fa>>>12&16,la=fa>>>(ka>>>0),ja=la>>>5&8,aa=la>>>(ja>>>0),da=aa>>>2&4,ea=aa>>>(da>>>0),qa=ea>>>1&2,sa=ea>>>(qa>>>0),ra=sa>>>1&1,ha=a[E+((ja|ka|da|qa|ra)+(sa>>>(ra>>>0))<<2)+304>>2]}else{ha=G}var ga=0==(ha|0);a:do{if(ga){var na=F,oa=D;f=oa>>2}else{var va=ha;e=va>>2;for(var Aa=F,ya=D;;){var Ea=(a[e+1]&-8)-b|0,wa=Ea>>>0>>0,Ba=wa?Ea:Aa,Fa=wa?va:ya,Ga=a[e+4];if(0!=(Ga|0)){va=Ga,e=va>>2,Aa=Ba,ya=Fa}else{var Ma=a[e+5];if(0==(Ma|0)){na=Ba;oa=Fa;f=oa>>2;break a}else{va=Ma,e=va>>2,Aa=Ba,ya=Fa}}}}}while(0);if(0==(oa|0)||na>>>0>=(a[E+8>>2]-b|0)>>>0){return ca=0}var ta=oa;d=ta>>2;var Ka=a[E+16>>2];ta>>>0>>0&&S();var za=ta+b|0;ta>>>0>>0||S();var ma=a[f+6],pa=a[f+3],Qa=(pa|0)==(oa|0);a:do{if(Qa){var Ha=oa+20|0,Ra=a[Ha>>2];do{if(0==(Ra|0)){var Pa=oa+16|0,db=a[Pa>>2];if(0==(db|0)){var La=0;c=La>>2;break a}else{var Va=Pa,Ya=db}}else{Va=Ha,Ya=Ra}}while(0);for(;;){var hb=Ya+20|0,Za=a[hb>>2];if(0!=(Za|0)){Va=hb,Ya=Za}else{var ib=Ya+16|0,ab=a[ib>>2];if(0==(ab|0)){break}else{Va=ib,Ya=ab}}}Va>>>0>>0?S():(a[Va>>2]=0,La=Ya,c=La>>2)}else{var $a=a[f+2];$a>>>0>>0?S():(a[$a+12>>2]=pa,a[pa+8>>2]=$a,La=pa,c=La>>2)}}while(0);var jb=0==(ma|0);a:do{if(jb){var Ca=oa}else{var Ia=oa+28|0,eb=(a[Ia>>2]<<2)+E+304|0;do{if((oa|0)==(a[eb>>2]|0)){if(a[eb>>2]=La,0==(La|0)){a[E+4>>2]&=1<>2]^-1;Ca=oa;break a}}else{ma>>>0>2]>>>0&&S();var ub=ma+16|0;(a[ub>>2]|0)==(oa|0)?a[ub>>2]=La:a[ma+20>>2]=La;if(0==(La|0)){Ca=oa;break a}}}while(0);La>>>0>2]>>>0&&S();a[c+6]=ma;var Sa=a[f+4];0!=(Sa|0)&&(Sa>>>0>2]>>>0?S():(a[c+4]=Sa,a[Sa+24>>2]=La));var mb=a[f+5];0==(mb|0)?Ca=oa:mb>>>0>2]>>>0?S():(a[c+5]=mb,a[mb+24>>2]=La,Ca=oa)}}while(0);do{if(16>na>>>0){var ua=na+b|0;a[Ca+4>>2]=ua|3;var Oa=ua+(ta+4)|0;a[Oa>>2]|=1}else{if(a[Ca+4>>2]=b|3,a[j+(d+1)]=na|1,a[(na>>2)+d+j]=na,256>na>>>0){var Wa=na>>>2&1073741822,pb=(Wa<<2)+E+40|0,ob=a[E>>2],bb=1<<(na>>>3);if(0==(ob&bb|0)){a[E>>2]=ob|bb;var qb=pb,wb=(Wa+2<<2)+E+40|0}else{var kb=(Wa+2<<2)+E+40|0,Cb=a[kb>>2];Cb>>>0>2]>>>0?S():(qb=Cb,wb=kb)}a[wb>>2]=za;a[qb+12>>2]=za;a[j+(d+2)]=qb;a[j+(d+3)]=pb}else{var vb=za,xb=na>>>8;if(0==(xb|0)){var yb=0}else{if(16777215>>0){yb=31}else{var nb=(xb+1048320|0)>>>16&8,rb=xb<>>16&4,Ta=rb<>>16&2,fb=14-(lb|nb|cb)+(Ta<>>15)|0,yb=na>>>((fb+7|0)>>>0)&1|fb<<1}}var Ua=(yb<<2)+E+304|0;a[j+(d+7)]=yb;a[j+(d+5)]=0;a[j+(d+4)]=0;var sb=a[E+4>>2],Na=1<>2]=sb|Na,a[Ua>>2]=vb,a[j+(d+6)]=Ua,a[j+(d+3)]=vb,a[j+(d+2)]=vb}else{for(var Fb=na<<(31==(yb|0)?0:25-(yb>>>1)|0),Db=a[Ua>>2];(a[Db+4>>2]&-8|0)!=(na|0);){var Gb=(Fb>>>31<<2)+Db+16|0,Eb=a[Gb>>2];if(0==(Eb|0)){k=2868;break}else{Fb<<=1,Db=Eb}}if(2868==k){if(Gb>>>0>2]>>>0){S()}else{a[Gb>>2]=vb;a[j+(d+6)]=Db;a[j+(d+3)]=vb;a[j+(d+2)]=vb;break}}var Hb=Db+8|0,Bb=a[Hb>>2],Ja=a[E+16>>2];Db>>>0>>0&&S();Bb>>>0>>0?S():(a[Bb+12>>2]=vb,a[Hb>>2]=vb,a[j+(d+2)]=Bb,a[j+(d+3)]=Db,a[j+(d+6)]=0)}}}}while(0);return ca=Ca+8|0}function AU(b){var c;0==(a[Rc>>2]|0)&&xU();if(4294967232<=b>>>0){return 0}c=a[E+24>>2];if(0==(c|0)){return 0}var d=a[E+12>>2];if(d>>>0>(b+40|0)>>>0){var e=a[Rc+8>>2],d=(Math.floor(((-40-b-1+d+e|0)>>>0)/(e>>>0))-1)*e|0,f=Qy(c);if(0==(a[f+12>>2]&8|0)&&(b=Ie(0),c=(f+4|0)>>2,(b|0)==(a[f>>2]+a[c]|0)&&(d=Ie(-(2147483646>>0?-2147483648-e|0:d)|0),e=Ie(0),-1!=(d|0)&e>>>0>>0&&(d=b-e|0,(b|0)!=(e|0))))){return a[c]=a[c]-d|0,a[E+432>>2]=a[E+432>>2]-d|0,Qq(a[E+24>>2],a[E+12>>2]-d|0),1}}if(a[E+12>>2]>>>0<=a[E+28>>2]>>>0){return 0}a[E+28>>2]=-1;return 0}function G(b){var c,d,e,f,h,j,k,n=b>>2,m;if(0!=(b|0)){var p=b-8|0,s=a[E+16>>2];p>>>0>>0&&S();var v=a[b-4>>2],t=v&3;1==(t|0)&&S();var u=v&-8;k=u>>2;var w=b+(u-8)|0,A=0==(v&1|0);a:do{if(A){var y=a[p>>2];if(0==(t|0)){return}var C=-8-y|0;j=C>>2;var D=b+C|0,F=D,G=y+u|0;D>>>0>>0&&S();if((F|0)==(a[E+20>>2]|0)){h=(b+(u-4)|0)>>2;if(3!=(a[h]&3|0)){var H=F;f=H>>2;var I=G;break}a[E+8>>2]=G;a[h]&=-2;a[j+(n+1)]=G|1;a[w>>2]=G;return}var J=y>>>3;if(256>y>>>0){var K=a[j+(n+2)],L=a[j+(n+3)];if((K|0)==(L|0)){a[E>>2]&=1<>2;I=G;break}var N=((y>>>2&1073741822)<<2)+E+40|0;(K|0)!=(N|0)&K>>>0>>0&&S();if((L|0)==(N|0)|L>>>0>=s>>>0){a[K+12>>2]=L;a[L+8>>2]=K;H=F;f=H>>2;I=G;break}else{S()}}var Q=D,R=a[j+(n+6)],U=a[j+(n+3)],V=(U|0)==(Q|0);b:do{if(V){var W=C+(b+20)|0,Y=a[W>>2];do{if(0==(Y|0)){var Z=C+(b+16)|0,$=a[Z>>2];if(0==($|0)){var ba=0;e=ba>>2;break b}else{var ca=Z,fa=$}}else{ca=W,fa=Y}}while(0);for(;;){var ka=fa+20|0,la=a[ka>>2];if(0!=(la|0)){ca=ka,fa=la}else{var ja=fa+16|0,aa=a[ja>>2];if(0==(aa|0)){break}else{ca=ja,fa=aa}}}ca>>>0>>0?S():(a[ca>>2]=0,ba=fa,e=ba>>2)}else{var da=a[j+(n+2)];da>>>0>>0?S():(a[da+12>>2]=U,a[U+8>>2]=da,ba=U,e=ba>>2)}}while(0);if(0==(R|0)){H=F,f=H>>2,I=G}else{var ea=C+(b+28)|0,na=(a[ea>>2]<<2)+E+304|0;do{if((Q|0)==(a[na>>2]|0)){if(a[na>>2]=ba,0==(ba|0)){a[E+4>>2]&=1<>2]^-1;H=F;f=H>>2;I=G;break a}}else{R>>>0>2]>>>0&&S();var qa=R+16|0;(a[qa>>2]|0)==(Q|0)?a[qa>>2]=ba:a[R+20>>2]=ba;if(0==(ba|0)){H=F;f=H>>2;I=G;break a}}}while(0);ba>>>0>2]>>>0&&S();a[e+6]=R;var oa=a[j+(n+4)];0!=(oa|0)&&(oa>>>0>2]>>>0?S():(a[e+4]=oa,a[oa+24>>2]=ba));var ha=a[j+(n+5)];0==(ha|0)?(H=F,f=H>>2,I=G):ha>>>0>2]>>>0?S():(a[e+5]=ha,a[ha+24>>2]=ba,H=F,f=H>>2,I=G)}}else{H=p,f=H>>2,I=u}}while(0);var ga=H;d=ga>>2;ga>>>0>>0||S();var sa=b+(u-4)|0,ra=a[sa>>2];0==(ra&1|0)&&S();do{if(0==(ra&2|0)){if((w|0)==(a[E+24>>2]|0)){var va=a[E+12>>2]+I|0;a[E+12>>2]=va;a[E+24>>2]=H;a[f+1]=va|1;(H|0)==(a[E+20>>2]|0)&&(a[E+20>>2]=0,a[E+8>>2]=0);if(va>>>0<=a[E+28>>2]>>>0){return}AU(0);return}if((w|0)==(a[E+20>>2]|0)){var Aa=a[E+8>>2]+I|0;a[E+8>>2]=Aa;a[E+20>>2]=H;a[f+1]=Aa|1;a[(Aa>>2)+d]=Aa;return}var ya=(ra&-8)+I|0,Ea=ra>>>3,wa=256>ra>>>0;a:do{if(wa){var Ba=a[n+k],Fa=a[((u|4)>>2)+n];if((Ba|0)==(Fa|0)){a[E>>2]&=1<>>2&1073741822)<<2)+E+40|0;(Ba|0)!=(Ga|0)&&Ba>>>0>2]>>>0&&S();(Fa|0)!=(Ga|0)&&Fa>>>0>2]>>>0&&S();a[Ba+12>>2]=Fa;a[Fa+8>>2]=Ba}}else{var Ma=w,ta=a[k+(n+4)],Ka=a[((u|4)>>2)+n],za=(Ka|0)==(Ma|0);b:do{if(za){var ma=u+(b+12)|0,pa=a[ma>>2];do{if(0==(pa|0)){var Qa=u+(b+8)|0,Ha=a[Qa>>2];if(0==(Ha|0)){var Ra=0;c=Ra>>2;break b}else{var Pa=Qa,Va=Ha}}else{Pa=ma,Va=pa}}while(0);for(;;){var La=Va+20|0,db=a[La>>2];if(0!=(db|0)){Pa=La,Va=db}else{var Ya=Va+16|0,hb=a[Ya>>2];if(0==(hb|0)){break}else{Pa=Ya,Va=hb}}}Pa>>>0>2]>>>0?S():(a[Pa>>2]=0,Ra=Va,c=Ra>>2)}else{var Za=a[n+k];Za>>>0>2]>>>0?S():(a[Za+12>>2]=Ka,a[Ka+8>>2]=Za,Ra=Ka,c=Ra>>2)}}while(0);if(0!=(ta|0)){var ib=u+(b+20)|0,ab=(a[ib>>2]<<2)+E+304|0;do{if((Ma|0)==(a[ab>>2]|0)){if(a[ab>>2]=Ra,0==(Ra|0)){a[E+4>>2]&=1<>2]^-1;break a}}else{ta>>>0>2]>>>0&&S();var $a=ta+16|0;(a[$a>>2]|0)==(Ma|0)?a[$a>>2]=Ra:a[ta+20>>2]=Ra;if(0==(Ra|0)){break a}}}while(0);Ra>>>0>2]>>>0&&S();a[c+6]=ta;var jb=a[k+(n+2)];0!=(jb|0)&&(jb>>>0>2]>>>0?S():(a[c+4]=jb,a[jb+24>>2]=Ra));var Ca=a[k+(n+3)];0!=(Ca|0)&&(Ca>>>0>2]>>>0?S():(a[c+5]=Ca,a[Ca+24>>2]=Ra))}}}while(0);a[f+1]=ya|1;a[(ya>>2)+d]=ya;if((H|0)!=(a[E+20>>2]|0)){var Ia=ya}else{a[E+8>>2]=ya;return}}else{a[sa>>2]=ra&-2,a[f+1]=I|1,Ia=a[(I>>2)+d]=I}}while(0);if(256>Ia>>>0){var eb=Ia>>>2&1073741822,mb=(eb<<2)+E+40|0,Sa=a[E>>2],wb=1<<(Ia>>>3);if(0==(Sa&wb|0)){a[E>>2]=Sa|wb;var ua=mb,Oa=(eb+2<<2)+E+40|0}else{var Wa=(eb+2<<2)+E+40|0,pb=a[Wa>>2];pb>>>0>2]>>>0?S():(ua=pb,Oa=Wa)}a[Oa>>2]=H;a[ua+12>>2]=H;a[f+2]=ua;a[f+3]=mb}else{var ob=H,bb=Ia>>>8;if(0==(bb|0)){var qb=0}else{if(16777215>>0){qb=31}else{var yb=(bb+1048320|0)>>>16&8,kb=bb<>>16&4,vb=kb<>>16&2,Gb=14-(Cb|yb|xb)+(vb<>>15)|0,qb=Ia>>>((Gb+7|0)>>>0)&1|Gb<<1}}var nb=(qb<<2)+E+304|0;a[f+7]=qb;a[f+5]=0;a[f+4]=0;var rb=a[E+4>>2],lb=1<>2]=rb|lb,a[nb>>2]=ob,a[f+6]=nb,a[f+3]=H,a[f+2]=H}else{for(var Ta=Ia<<(31==(qb|0)?0:25-(qb>>>1)|0),cb=a[nb>>2];(a[cb+4>>2]&-8|0)!=(Ia|0);){var fb=(Ta>>>31<<2)+cb+16|0,Ua=a[fb>>2];if(0==(Ua|0)){m=3020;break}else{Ta<<=1,cb=Ua}}if(3020==m){if(fb>>>0>2]>>>0){S()}else{a[fb>>2]=ob;a[f+6]=cb;a[f+3]=H;a[f+2]=H;break}}var sb=cb+8|0,Na=a[sb>>2],Fb=a[E+16>>2];cb>>>0>>0&&S();Na>>>0>>0?S():(a[Na+12>>2]=ob,a[sb>>2]=ob,a[f+2]=Na,a[f+3]=cb,a[f+6]=0)}}while(0);var Db=a[E+32>>2]-1|0;a[E+32>>2]=Db;if(0==(Db|0)){for(var Hb=E+452|0;;){var Eb=a[Hb>>2];if(0==(Eb|0)){break}else{Hb=Eb+8|0}}a[E+32>>2]=-1}}}}function Yc(b,c){if(0==(b|0)){var d=0}else{d=c*b|0,d=65535<(c|b)>>>0?(Math.floor((d>>>0)/(b>>>0))|0)==(c|0)?d:-1:d}var e=Gb(d);if(0==(e|0)||0==(a[e-4>>2]&3|0)){return e}li(e,d);return e}function mc(a,b){return 0==(a|0)?Gb(b):BU(a,b)}function BU(b,c){var d,e,f;if(4294967231>>0){return a[Ea.c>>2]=12,0}var h=b-8|0;e=(b-4|0)>>2;var j=a[e],k=j&-8,n=k-8|0,m=b+n|0;h>>>0>2]>>>0&&S();var p=j&3;1!=(p|0)&-8<(n|0)||S();d=(b+(k-4)|0)>>2;0==(a[d]&1|0)&&S();n=11>c>>>0?16:c+11&-8;if(0==(p|0)){var s=0,v,j=a[h+4>>2]&-8;v=256>n>>>0?0:j>>>0>=(n+4|0)>>>0&&(j-n|0)>>>0<=a[Rc+8>>2]<<1>>>0?h:0;f=150}else{k>>>0>>0?(m|0)==(a[E+24>>2]|0)&&(d=a[E+12>>2]+k|0,d>>>0>n>>>0&&(s=d-n|0,a[e]=n|j&1|2,a[b+(n-4)>>2]=s|1,a[E+24>>2]=b+(n-8)|0,a[E+12>>2]=s,s=0,v=h,f=150)):(s=k-n|0,15>>0?(a[e]=n|j&1|2,a[b+(n-4)>>2]=s|3,a[d]|=1,s=b+n|0):s=0,v=h,f=150)}if(150==f&&0!=(v|0)){return 0!=(s|0)&&G(s),v+8|0}h=Gb(c);if(0==(h|0)){return 0}e=k-(0==(a[e]&3|0)?8:4)|0;tf(h,b,e>>>0>>0?e:c);G(b);return h}function xU(){if(0==(a[Rc>>2]|0)){var b=AQa();0!=(b-1&b|0)&&S();a[Rc+8>>2]=b;a[Rc+4>>2]=b;a[Rc+12>>2]=-1;a[Rc+16>>2]=2097152;a[Rc+20>>2]=0;a[E+440>>2]=0;b=Math.floor(Date.now()/1e3);a[Rc>>2]=b&-16^1431655768}}function Qy(b){var c,d,e=E+444|0;for(c=e>>2;;){var f=a[c];if(f>>>0<=b>>>0&&(f+a[c+1]|0)>>>0>b>>>0){var h=e;d=185;break}c=a[c+2];if(0==(c|0)){h=0;d=186;break}else{e=c,c=e>>2}}if(185==d||186==d){return h}}function Qq(b,c){var d=b+8|0,d=0==(d&7|0)?0:-d&7,e=c-d|0;a[E+24>>2]=b+d|0;a[E+12>>2]=e;a[d+(b+4)>>2]=e|1;a[c+(b+4)>>2]=40;a[E+28>>2]=a[Rc+16>>2]}function yU(b,c,d){var e,f,h,j=c>>2,k=b>>2,n,m=b+8|0,m=0==(m&7|0)?0:-m&7;f=c+8|0;var p=0==(f&7|0)?0:-f&7;h=p>>2;var s=c+p|0,v=m+d|0;f=v>>2;var v=b+v|0,t=s-(b+m)-d|0;a[(m+4>>2)+k]=d|3;if((s|0)==(a[E+24>>2]|0)){return n=a[E+12>>2]+t|0,a[E+12>>2]=n,a[E+24>>2]=v,a[f+(k+1)]=n|1,b=b+(m|8)|0}if((s|0)==(a[E+20>>2]|0)){return n=a[E+8>>2]+t|0,a[E+8>>2]=n,a[E+20>>2]=v,a[f+(k+1)]=n|1,a[(n>>2)+k+f]=n,b=b+(m|8)|0}var u=a[h+(j+1)];if(1==(u&3|0)){var d=u&-8,w=u>>>3,A=256>u>>>0;a:do{if(A){var y=a[((p|8)>>2)+j],C=a[h+(j+3)];if((y|0)==(C|0)){a[E>>2]&=1<>>2&1073741822)<<2)+E+40|0;(y|0)!=(D|0)&&y>>>0>2]>>>0&&S();(C|0)!=(D|0)&&C>>>0>2]>>>0&&S();a[y+12>>2]=C;a[C+8>>2]=y}}else{var y=s,C=a[((p|24)>>2)+j],D=a[h+(j+3)],F=(D|0)==(y|0);b:do{if(F){var G=p|16,H=G+(c+4)|0,I=a[H>>2];do{if(0==(I|0)){var J=c+G|0,K=a[J>>2];if(0==(K|0)){var L=0;e=L>>2;break b}}else{J=H,K=I}}while(0);for(;;){if(G=K+20|0,H=a[G>>2],0!=(H|0)){J=G,K=H}else{if(G=K+16|0,H=a[G>>2],0==(H|0)){break}else{J=G,K=H}}}J>>>0>2]>>>0?S():(a[J>>2]=0,L=K,e=L>>2)}else{J=a[((p|8)>>2)+j],J>>>0>2]>>>0?S():(a[J+12>>2]=D,a[D+8>>2]=J,L=D,e=L>>2)}}while(0);if(0!=(C|0)){D=p+(c+28)|0;F=(a[D>>2]<<2)+E+304|0;do{if((y|0)==(a[F>>2]|0)){if(a[F>>2]=L,0==(L|0)){a[E+4>>2]&=1<>2]^-1;break a}}else{if(C>>>0>2]>>>0&&S(),J=C+16|0,(a[J>>2]|0)==(y|0)?a[J>>2]=L:a[C+20>>2]=L,0==(L|0)){break a}}}while(0);L>>>0>2]>>>0&&S();a[e+6]=C;y=p|16;C=a[(y>>2)+j];0!=(C|0)&&(C>>>0>2]>>>0?S():(a[e+4]=C,a[C+24>>2]=L));y=a[(y+4>>2)+j];0!=(y|0)&&(y>>>0>2]>>>0?S():(a[e+5]=y,a[y+24>>2]=L))}}}while(0);e=c+(d|p)|0;c=d+t|0}else{e=s,c=t}e=e+4|0;a[e>>2]&=-2;a[f+(k+1)]=c|1;a[(c>>2)+k+f]=c;if(256>c>>>0){var N=c>>>2&1073741822;n=(N<<2)+E+40|0;e=a[E>>2];c=1<<(c>>>3);if(0==(e&c|0)){a[E>>2]=e|c;var Q=n,R=(N+2<<2)+E+40|0}else{N=(N+2<<2)+E+40|0,c=a[N>>2],c>>>0>2]>>>0?S():(Q=c,R=N)}a[R>>2]=v;a[Q+12>>2]=v;a[f+(k+2)]=Q;a[f+(k+3)]=n;return b=b+(m|8)|0}R=c>>>8;0==(R|0)?R=0:16777215>>0?R=31:(Q=(R+1048320|0)>>>16&8,e=R<>>16&4,e<<=R,j=(e+245760|0)>>>16&2,Q=14-(R|Q|j)+(e<>>15)|0,R=c>>>((Q+7|0)>>>0)&1|Q<<1);Q=(R<<2)+E+304|0;a[f+(k+7)]=R;a[f+(k+5)]=0;a[f+(k+4)]=0;e=a[E+4>>2];j=1<>2]=e|j,a[Q>>2]=v,a[f+(k+6)]=Q,a[f+(k+3)]=v,a[f+(k+2)]=v,b=b+(m|8)|0}R=c<<(31==(R|0)?0:25-(R>>>1)|0);for(Q=a[Q>>2];(a[Q+4>>2]&-8|0)!=(c|0);){if(N=(R>>>31<<2)+Q+16|0,e=a[N>>2],0==(e|0)){n=259;break}else{R<<=1,Q=e}}if(259==n){return N>>>0>2]>>>0&&S(),a[N>>2]=v,a[f+(k+6)]=Q,a[f+(k+3)]=v,a[f+(k+2)]=v,b=b+(m|8)|0}n=Q+8|0;N=a[n>>2];R=a[E+16>>2];Q>>>0>>0&&S();N>>>0>>0&&S();a[N+12>>2]=v;a[n>>2]=v;a[f+(k+2)]=N;a[f+(k+3)]=Q;a[f+(k+6)]=0;return b=b+(m|8)|0}function zU(b,c){var d,e,f,h=a[E+24>>2];e=h>>2;var j=Qy(h),k=a[j>>2];d=a[j+4>>2];var j=k+d|0,n=k+(d-39)|0,k=k+(d-47)+(0==(n&7|0)?0:-n&7)|0,k=k>>>0<(h+16|0)>>>0?h:k,n=k+8|0;d=n>>2;Qq(b,c-40|0);a[k+4>>2]=27;a[d]=a[E+444>>2];a[d+1]=a[E+448>>2];a[d+2]=a[E+452>>2];a[d+3]=a[E+456>>2];a[E+444>>2]=b;a[E+448>>2]=c;a[E+456>>2]=0;a[E+452>>2]=n;d=k+28|0;a[d>>2]=7;n=(k+32|0)>>>0>>0;a:do{if(n){for(var m=d;;){var p=m+4|0;a[p>>2]=7;if((m+8|0)>>>0>>0){m=p}else{break a}}}}while(0);if((k|0)!=(h|0)){if(j=k-h|0,k=j+(h+4)|0,a[k>>2]&=-2,a[e+1]=j|1,a[h+j>>2]=j,256>j>>>0){var s=j>>>2&1073741822;f=(s<<2)+E+40|0;k=a[E>>2];j=1<<(j>>>3);if(0==(k&j|0)){a[E>>2]=k|j;var v=f,t=(s+2<<2)+E+40|0}else{s=(s+2<<2)+E+40|0,j=a[s>>2],j>>>0>2]>>>0?S():(v=j,t=s)}a[t>>2]=h;a[v+12>>2]=h;a[e+2]=v;a[e+3]=f}else{if(t=j>>>8,0==(t|0)?t=0:16777215>>0?t=31:(v=(t+1048320|0)>>>16&8,k=t<>>16&4,k<<=t,d=(k+245760|0)>>>16&2,v=14-(t|v|d)+(k<>>15)|0,t=j>>>((v+7|0)>>>0)&1|v<<1),v=(t<<2)+E+304|0,a[e+7]=t,a[e+5]=0,a[e+4]=0,k=a[E+4>>2],d=1<>2]=k|d,a[v>>2]=h,a[e+6]=v,a[e+3]=h,a[e+2]=h}else{t=j<<(31==(t|0)?0:25-(t>>>1)|0);for(v=a[v>>2];(a[v+4>>2]&-8|0)!=(j|0);){if(s=(t>>>31<<2)+v+16|0,k=a[s>>2],0==(k|0)){f=320;break}else{t<<=1,v=k}}320==f?(s>>>0>2]>>>0&&S(),a[s>>2]=h,a[e+6]=v,a[e+3]=h,a[e+2]=h):(f=v+8|0,s=a[f>>2],t=a[E+16>>2],v>>>0>>0&&S(),s>>>0>>0&&S(),a[s+12>>2]=h,a[f>>2]=h,a[e+2]=s,a[e+3]=v,a[e+6]=0)}}}}function qr(a){function b(){var c=0;eC=wc;K._main&&(qn(CU),c=K.Na(a),K.noExitRuntime||(qn(tr),iC.print()));if(K.postRun){for(\"function\"==typeof K.postRun&&(K.postRun=[K.postRun]);0>\"+c+\")<<\"+c+\")\"}return\"Math.ceil((\"+a+\")/\"+b+\")*\"+b}),Ya:(function(a){return a in Qa.Ha||a in Qa.Ga}),Za:(function(a){return\"*\"==a[a.length-1]}),ab:(function(a){return isPointerType(a)?ee:/^\\[\\d+\\ x\\ (.*)\\]/.test(a)||/?/.test(a)?wc:\"%\"==a[0]}),Ha:{i1:0,i8:0,i16:0,i32:0,i64:0},Ga:{\"float\":0,\"double\":0},Xc:(function(a,b,c,d){var e=Math.pow(2,d)-1;if(32>d){switch(c){case\"shl\":return[a<>>32-d];case\"ashr\":return[(a>>>d|(b&e)<<32-d)>>0>>>0,b>>d>>>0];case\"lshr\":return[(a>>>d|(b&e)<<32-d)>>>0,b>>>d]}}else{if(32==d){switch(c){case\"shl\":return[0,a];case\"ashr\":return[b,0>(b|0)?e:0];case\"lshr\":return[b,0]}}else{switch(c){case\"shl\":return[0,a<>d-32>>>0,0>(b|0)?e:0];case\"lshr\":return[b>>>d-32,0]}}}Yg(\"unknown bitshift64 op: \"+[value,c,d])}),jd:(function(a,b){return(a|0|b|0)+4294967296*(Math.round(a/4294967296)|Math.round(b/4294967296))}),Wc:(function(a,b){return((a|0)&(b|0))+4294967296*(Math.round(a/4294967296)&Math.round(b/4294967296))}),Cd:(function(a,b){return((a|0)^(b|0))+4294967296*(Math.round(a/4294967296)^Math.round(b/4294967296))}),Y:(function(a){if(1==Qa.z){return 1}var b={\"%i1\":1,\"%i8\":1,\"%i16\":2,\"%i32\":4,\"%i64\":8,\"%float\":4,\"%double\":8}[\"%\"+a];b||(\"*\"==a.charAt(a.length-1)?b=Qa.z:\"i\"==a[0]&&(a=parseInt(a.substr(1)),Ae(0==a%8),b=a/8));return b}),W:(function(a){return Math.max(Qa.Y(a),Qa.z)}),Ta:(function(a,b){var c={};return b?a.filter((function(a){return c[a[b]]?ee:c[a[b]]=wc})):a.filter((function(a){return c[a]?ee:c[a]=wc}))}),set:(function(){for(var a=\"object\"===typeof arguments[0]?arguments[0]:arguments,b={},c=0;cc){return String.fromCharCode(c)}a.push(c);b=191c?1:2;return\"\"}if(0c?String.fromCharCode((c&31)<<6|d&63):String.fromCharCode((c&15)<<12|(d&63)<<6|e&63);a.length=0;return c});this.ib=(function(a){for(var a=unescape(encodeURIComponent(a)),b=[],c=0;c>2<<2;return b}),Ba:(function(a){var b=We;We+=a;We=We+3>>2<<2;We>=gk&&Yg(\"Cannot enlarge memory arrays. Either (1) compile with -s TOTAL_MEMORY=X with X higher than the current value ( \"+gk+\"), (2) compile with ALLOW_MEMORY_GROWTH which adjusts the size at runtime but prevents some optimizations, or (3) set Module.TOTAL_MEMORY before the program runs.\");return b}),B:(function(a,b){return Math.ceil(a/(b?b:4))*(b?b:4)}),cb:(function(a,b,c){return c?(a>>>0)+4294967296*(b>>>0):(a>>>0)+4294967296*(b|0)}),z:4,Uc:0},iC={Ia:0,na:0,pd:{},hd:(function(a,b){b||(this.na++,this.na>=this.Ia&&Yg(\"\\n\\nToo many corrections!\"))}),print:bk()},j,Pb,ji,sQa=this;K.ccall=(function(a,b,c,d){return $B(ZB(a),b,c,d)});K.cwrap=(function(a,b,c){var d=ZB(a);return(function(){return $B(d,b,c,Array.prototype.slice.call(arguments))})});K.setValue=dk;K.getValue=ki;var sn=0,$g=1,c=2,cC=3;K.ALLOC_NORMAL=sn;K.ALLOC_STACK=$g;K.ALLOC_STATIC=c;K.ALLOC_NONE=cC;K.allocate=d;K.Pointer_stringify=Oe;K.Array_stringify=(function(a){for(var b=\"\",c=0;c>2);ib.subarray(Nm>>2);var f=Sy.subarray(Nm>>3);Sq=Nm+8;We=Sq+4095>>12<<12;Ae(We>2]=U|0;a[Bd+4>>2]=U+16|0;a[Bd+8>>2]=U+32|0;a[Bd+12>>2]=U+48|0;a[Bd+16>>2]=U+64|0;a[Bd+20>>2]=U+80|0;a[Bd+24>>2]=U+96|0;a[Bd+28>>2]=U+112|0;a[Bd+32>>2]=U+16|0;a[Bd+36>>2]=U+16|0;a[Bd+40>>2]=U+80|0;a[Bd+44>>2]=U+80|0;a[cd>>2]=OB|0;a[cd+4>>2]=Ac|0;a[cd+8>>2]=Zj|0;a[cd+12>>2]=iB|0;a[cd+16>>2]=ir|0;a[cd+20>>2]=Yj|0;a[cd+24>>2]=RA|0;a[cd+28>>2]=hr|0;a[cd+32>>2]=fr|0;a[cd+36>>2]=MA|0;a[cd+40>>2]=er|0;a[cd+44>>2]=Vj|0;a[cd+48>>2]=HA|0;a[cd+52>>2]=GA|0;a[cd+56>>2]=hf|0;a[cd+60>>2]=Uj|0;a[L>>2]=IA|0;a[L+4>>2]=AA|0;a[L+8>>2]=OB|0;a[L+12>>2]=Xz|0;a[L+16>>2]=Mz|0;a[L+20>>2]=Bz|0;a[L+24>>2]=qz|0;a[L+28>>2]=Ac|0;a[L+32>>2]=SB|0;a[L+36>>2]=Zj|0;a[L+40>>2]=wB|0;a[L+44>>2]=mB|0;a[L+48>>2]=bB|0;a[L+52>>2]=WA|0;a[L+56>>2]=SA|0;a[L+60>>2]=QA|0;a[L+64>>2]=OA|0;a[L+68>>2]=NA|0;a[L+72>>2]=LA|0;a[L+76>>2]=JA|0;a[L+80>>2]=gn|0;a[L+84>>2]=gka|0;a[L+88>>2]=iia|0;a[L+92>>2]=EA|0;a[L+96>>2]=ega|0;a[L+100>>2]=DA|0;a[L+104>>2]=$ea|0;a[L+108>>2]=CA|0;a[L+112>>2]=dea|0;a[L+116>>2]=BA|0;a[L+120>>2]=zA|0;a[L+124>>2]=xA|0;a[L+128>>2]=Bca|0;a[L+132>>2]=wA|0;a[L+136>>2]=uA|0;a[L+140>>2]=sA|0;a[L+144>>2]=rA|0;a[L+148>>2]=qA|0;a[L+152>>2]=pA|0;a[L+156>>2]=nA|0;a[L+160>>2]=mA|0;a[L+164>>2]=lA|0;a[L+168>>2]=kA|0;a[L+172>>2]=hA|0;a[L+176>>2]=fA|0;a[L+180>>2]=dA|0;a[L+184>>2]=cA|0;a[L+188>>2]=aA|0;a[L+192>>2]=iB|0;a[L+196>>2]=Zz|0;a[L+200>>2]=Wz|0;a[L+204>>2]=Uz|0;a[L+208>>2]=Tz|0;a[L+212>>2]=ir|0;a[L+216>>2]=Yj|0;a[L+220>>2]=Sz|0;a[L+224>>2]=Rz|0;a[L+228>>2]=Pz|0;a[L+232>>2]=Oz|0;a[L+236>>2]=Nz|0;a[L+240>>2]=Lz|0;a[L+244>>2]=Kz|0;a[L+248>>2]=Jz|0;a[L+252>>2]=Iz|0;a[L+256>>2]=Hz|0;a[L+260>>2]=Gz|0;a[L+264>>2]=Fz|0;a[L+268>>2]=Ez|0;a[L+272>>2]=Dz|0;a[L+276>>2]=Cz|0;a[L+280>>2]=Az|0;a[L+284>>2]=zz|0;a[L+288>>2]=B1|0;a[L+292>>2]=qe|0;a[L+296>>2]=yz|0;a[L+300>>2]=xz|0;a[L+304>>2]=wz|0;a[L+308>>2]=uz|0;a[L+312>>2]=sz|0;a[L+316>>2]=rz|0;a[L+320>>2]=pz|0;a[L+324>>2]=oz|0;a[L+328>>2]=RA|0;a[L+332>>2]=nz|0;a[L+336>>2]=mz|0;a[L+340>>2]=Zm|0;a[L+344>>2]=hr|0;a[L+348>>2]=lz|0;a[L+352>>2]=kz|0;a[L+356>>2]=jz|0;a[L+360>>2]=iz|0;a[L+364>>2]=hz|0;a[L+368>>2]=gz|0;a[L+372>>2]=fz|0;a[L+376>>2]=ez|0;a[L+380>>2]=dz|0;a[L+384>>2]=cz|0;a[L+388>>2]=bz|0;a[L+392>>2]=UB|0;a[L+396>>2]=TB|0;a[L+400>>2]=RB|0;a[L+404>>2]=fr|0;a[L+408>>2]=PB|0;a[L+412>>2]=MA|0;a[L+416>>2]=MB|0;a[L+420>>2]=LB|0;a[L+424>>2]=KB|0;a[L+428>>2]=JB|0;a[L+432>>2]=IB|0;a[L+436>>2]=HB|0;a[L+440>>2]=GB|0;a[L+444>>2]=FB|0;a[L+448>>2]=EB|0;a[L+452>>2]=BB|0;a[L+456>>2]=AB|0;a[L+460>>2]=zB|0;a[L+464>>2]=yB|0;a[L+468>>2]=xB|0;a[L+472>>2]=er|0;a[L+476>>2]=Vj|0;a[L+480>>2]=vB|0;a[L+484>>2]=uB|0;a[L+488>>2]=tB|0;a[L+492>>2]=sB|0;a[L+496>>2]=rB|0;a[L+500>>2]=qB|0;a[L+504>>2]=pB|0;a[L+508>>2]=oB|0;a[L+512>>2]=HA|0;a[L+516>>2]=nB|0;a[L+520>>2]=lB|0;a[L+524>>2]=kB|0;a[L+528>>2]=jB|0;a[L+532>>2]=hB|0;a[L+536>>2]=gB|0;a[L+540>>2]=fB|0;a[L+544>>2]=eB|0;a[L+548>>2]=GA|0;a[L+552>>2]=dB|0;a[L+556>>2]=cB|0;a[L+560>>2]=aB|0;a[L+564>>2]=$A|0;a[L+568>>2]=ZA|0;a[L+572>>2]=hf|0;a[L+576>>2]=YA|0;a[L+580>>2]=Uj|0;a[L+584>>2]=XA|0;a[NU+16>>2]=208;a[Ty+12>>2]=cd|0;a[Uy+12>>2]=L|0;a[Vy+12>>2]=Pf|0;a[ei>>2]=p2|0;a[ei+4>>2]=H_|0;a[ei+8>>2]=jx|0;a[ei+12>>2]=BPa|0;a[ei+16>>2]=WMa|0;a[I>>2]=JMa|0;a[I+4>>2]=YMa|0;a[I+8>>2]=Foa|0;a[I+12>>2]=Y|0;a[I+16>>2]=daa|0;a[I+20>>2]=c8|0;a[I+24>>2]=m5|0;a[I+28>>2]=t2|0;a[I+32>>2]=Y|0;a[I+36>>2]=K_|0;a[I+40>>2]=WX|0;a[I+44>>2]=Y|0;a[I+48>>2]=FPa|0;a[I+52>>2]=aNa|0;a[I+56>>2]=vKa|0;a[I+60>>2]=PHa|0;a[I+64>>2]=gFa|0;a[I+68>>2]=GCa|0;a[I+72>>2]=lAa|0;a[I+76>>2]=ln|0;a[I+80>>2]=iva|0;a[I+84>>2]=jta|0;a[I+88>>2]=lra|0;a[I+92>>2]=Joa|0;a[I+96>>2]=ima|0;a[I+100>>2]=rka|0;a[I+104>>2]=tia|0;a[I+108>>2]=Lga|0;a[I+112>>2]=jga|0;a[I+116>>2]=Gfa|0;a[I+120>>2]=dfa|0;a[I+124>>2]=Bea|0;a[I+128>>2]=iea|0;a[I+132>>2]=ii|0;a[I+136>>2]=Y|0;a[I+140>>2]=tda|0;a[I+144>>2]=ada|0;a[I+148>>2]=Eca|0;a[I+152>>2]=jca|0;a[I+156>>2]=Y|0;a[I+160>>2]=Sba|0;a[I+164>>2]=yba|0;a[I+168>>2]=$aa|0;a[I+172>>2]=Jaa|0;a[I+176>>2]=ii|0;a[I+180>>2]=Y|0;a[I+184>>2]=raa|0;a[I+188>>2]=$$|0;a[I+192>>2]=L$|0;a[I+196>>2]=s$|0;a[I+200>>2]=b$|0;a[I+204>>2]=N9|0;a[I+208>>2]=y9|0;a[I+212>>2]=i9|0;a[I+216>>2]=U8|0;a[I+220>>2]=F8|0;a[I+224>>2]=Y|0;a[I+228>>2]=p8|0;a[I+232>>2]=$7|0;a[I+236>>2]=L7|0;a[I+240>>2]=x7|0;a[I+244>>2]=i7|0;a[I+248>>2]=Y|0;a[I+252>>2]=W6|0;a[I+256>>2]=H6|0;a[I+260>>2]=s6|0;a[I+264>>2]=e6|0;a[I+268>>2]=Q5|0;a[I+272>>2]=A5|0;a[I+276>>2]=j5|0;a[I+280>>2]=T4|0;a[I+284>>2]=E4|0;a[I+288>>2]=r4|0;a[I+292>>2]=c4|0;a[I+296>>2]=O3|0;a[I+300>>2]=ii|0;a[I+304>>2]=Y|0;a[I+308>>2]=y3|0;a[I+312>>2]=i3|0;a[I+316>>2]=V2|0;a[I+320>>2]=ln|0;a[I+324>>2]=Y|0;a[I+328>>2]=G2|0;a[I+332>>2]=v2|0;a[I+336>>2]=b2|0;a[I+340>>2]=N1|0;a[I+344>>2]=z1|0;a[I+348>>2]=l1|0;a[I+352>>2]=Y0|0;a[I+356>>2]=K0|0;a[I+360>>2]=x0|0;a[I+364>>2]=k0|0;a[I+368>>2]=ln|0;a[I+372>>2]=Y|0;a[I+376>>2]=X_|0;a[I+380>>2]=I_|0;a[I+384>>2]=t_|0;a[I+388>>2]=e_|0;a[I+392>>2]=RZ|0;a[I+396>>2]=BZ|0;a[I+400>>2]=nZ|0;a[I+404>>2]=ZY|0;a[I+408>>2]=ii|0;a[I+412>>2]=Y|0;a[I+416>>2]=LY|0;a[I+420>>2]=xY|0;a[I+424>>2]=iY|0;a[I+428>>2]=UX|0;a[I+432>>2]=GX|0;a[I+436>>2]=rX|0;a[I+440>>2]=dX|0;a[I+444>>2]=PW|0;a[I+448>>2]=BW|0;a[I+452>>2]=nW|0;a[I+456>>2]=aW|0;a[I+460>>2]=ii|0;a[I+464>>2]=Y|0;a[I+468>>2]=OV|0;a[I+472>>2]=RPa|0;a[I+476>>2]=Y|0;a[I+480>>2]=DPa|0;a[I+484>>2]=pPa|0;a[I+488>>2]=bPa|0;a[I+492>>2]=POa|0;a[I+496>>2]=AOa|0;a[I+500>>2]=mOa|0;a[I+504>>2]=ZNa|0;a[I+508>>2]=MNa|0;a[I+512>>2]=zNa|0;a[I+516>>2]=Y|0;a[I+520>>2]=mNa|0;a[I+524>>2]=Y|0;a[I+528>>2]=ZMa|0;a[I+532>>2]=NMa|0;a[I+536>>2]=wMa|0;a[I+540>>2]=jMa|0;a[I+544>>2]=WLa|0;a[I+548>>2]=HLa|0;a[I+552>>2]=ii|0;a[I+556>>2]=Y|0;a[I+560>>2]=tLa|0;a[I+564>>2]=Y|0;a[I+568>>2]=gLa|0;a[I+572>>2]=UKa|0;a[I+576>>2]=HKa|0;a[I+580>>2]=tKa|0;a[I+584>>2]=gKa|0;a[I+588>>2]=UJa|0;a[I+592>>2]=FJa|0;a[I+596>>2]=ln|0;a[I+600>>2]=Y|0;a[I+604>>2]=qJa|0;a[I+608>>2]=Y|0;a[I+612>>2]=cJa|0;a[I+616>>2]=SIa|0;a[I+620>>2]=CIa|0;a[I+624>>2]=pIa|0;a[I+628>>2]=aIa|0;a[I+632>>2]=NHa|0;a[I+636>>2]=AHa|0;a[I+640>>2]=Y|0;a[I+644>>2]=mHa|0;a[I+648>>2]=$Ga|0;a[I+652>>2]=NGa|0;a[I+656>>2]=BGa|0;a[I+660>>2]=hGa|0;a[I+664>>2]=TFa|0;a[I+668>>2]=GFa|0;a[I+672>>2]=tFa|0;a[I+676>>2]=Y|0;a[I+680>>2]=eFa|0;a[H>>2]=b_|0;a[H+4>>2]=Ym|0;a[H+8>>2]=QB|0;a[H+24>>2]=me|0;a[H+36>>2]=RJa|0;a[H+40>>2]=Ym|0;a[H+44>>2]=QB|0;a[H+52>>2]=vg|0;a[H+60>>2]=me|0;a[H+68>>2]=bc|0;a[H+72>>2]=dCa|0;a[H+76>>2]=Ym|0;a[H+80>>2]=mn|0;a[H+96>>2]=me|0;a[H+100>>2]=Wb|0;a[H+108>>2]=Eua|0;a[H+112>>2]=Ym|0;a[H+116>>2]=mn|0;a[H+124>>2]=vg|0;a[H+132>>2]=me|0;a[H+136>>2]=Wb|0;a[H+140>>2]=bc|0;a[H+144>>2]=Psa|0;a[H+148>>2]=jn|0;a[H+152>>2]=mn|0;a[H+168>>2]=qd|0;a[H+172>>2]=Wb|0;a[H+180>>2]=Jla|0;a[H+184>>2]=jn|0;a[H+188>>2]=mn|0;a[H+196>>2]=bc|0;a[H+204>>2]=qd|0;a[H+208>>2]=Wb|0;a[H+212>>2]=bc|0;a[H+216>>2]=Uja|0;a[H+220>>2]=jn|0;a[H+224>>2]=FA|0;a[H+240>>2]=qd|0;a[H+252>>2]=Fga|0;a[H+256>>2]=jn|0;a[H+260>>2]=FA|0;a[H+268>>2]=bc|0;a[H+276>>2]=qd|0;a[H+284>>2]=bc|0;a[H+288>>2]=Tj|0;a[H+292>>2]=Tj|0;a[H+312>>2]=fn|0;a[H+324>>2]=Vea|0;a[H+328>>2]=Tj|0;a[H+332>>2]=Wb|0;a[H+348>>2]=fn|0;a[H+352>>2]=Wb|0;a[H+360>>2]=xea|0;a[H+364>>2]=Tj|0;a[H+368>>2]=Wb|0;a[H+376>>2]=vg|0;a[H+384>>2]=fn|0;a[H+388>>2]=Wb|0;a[H+392>>2]=bc|0;a[H+396>>2]=aea|0;a[H+400>>2]=Tj|0;a[H+412>>2]=vg|0;a[H+420>>2]=fn|0;a[H+428>>2]=bc|0;a[H+432>>2]=Qf|0;a[H+436>>2]=Qf|0;a[H+456>>2]=me|0;a[H+468>>2]=pda|0;a[H+472>>2]=Qf|0;a[H+476>>2]=Wb|0;a[H+492>>2]=me|0;a[H+496>>2]=Wb|0;a[H+504>>2]=Uca|0;a[H+508>>2]=Qf|0;a[H+512>>2]=Wb|0;a[H+520>>2]=vg|0;a[H+528>>2]=me|0;a[H+532>>2]=Wb|0;a[H+536>>2]=bc|0;a[H+540>>2]=yca|0;a[H+544>>2]=Qf|0;a[H+552>>2]=en|0;a[H+564>>2]=me|0;a[H+576>>2]=Mba|0;a[H+580>>2]=Qf|0;a[H+584>>2]=Wb|0;a[H+588>>2]=en|0;a[H+600>>2]=me|0;a[H+604>>2]=Wb|0;a[H+612>>2]=sba|0;a[H+616>>2]=Qf|0;a[H+620>>2]=Wb|0;a[H+624>>2]=en|0;a[H+628>>2]=vg|0;a[H+636>>2]=me|0;a[H+640>>2]=Wb|0;a[H+644>>2]=bc|0;a[H+648>>2]=Zaa|0;a[H+652>>2]=Qf|0;a[H+660>>2]=en|0;a[H+664>>2]=vg|0;a[H+672>>2]=me|0;a[H+680>>2]=bc|0;a[H+684>>2]=Iaa|0;a[H+688>>2]=Qf|0;a[H+700>>2]=vg|0;a[H+708>>2]=me|0;a[H+716>>2]=bc|0;a[H+720>>2]=qaa|0;a[H+724>>2]=dn|0;a[H+728>>2]=Wb|0;a[H+744>>2]=qd|0;a[H+748>>2]=Wb|0;a[H+756>>2]=K$|0;a[H+760>>2]=dn|0;a[H+764>>2]=Wb|0;a[H+772>>2]=bc|0;a[H+780>>2]=qd|0;a[H+784>>2]=Wb|0;a[H+788>>2]=bc|0;a[H+792>>2]=r$|0;a[H+796>>2]=dn|0;a[H+808>>2]=bc|0;a[H+816>>2]=qd|0;a[H+824>>2]=bc|0;a[H+828>>2]=a$|0;a[H+832>>2]=dn|0;a[H+836>>2]=gA|0;a[H+852>>2]=qd|0;a[H+864>>2]=x9|0;a[H+868>>2]=cn|0;a[H+872>>2]=Wb|0;a[H+888>>2]=qd|0;a[H+892>>2]=Wb|0;a[H+900>>2]=T8|0;a[H+904>>2]=cn|0;a[H+908>>2]=Wb|0;a[H+916>>2]=bc|0;a[H+924>>2]=qd|0;a[H+928>>2]=Wb|0;a[H+932>>2]=bc|0;a[H+936>>2]=E8|0;a[H+940>>2]=cn|0;a[H+952>>2]=bc|0;a[H+960>>2]=qd|0;a[H+968>>2]=bc|0;a[H+972>>2]=o8|0;a[H+976>>2]=cn|0;a[H+980>>2]=gA|0;a[H+996>>2]=qd|0;a[H+1008>>2]=Yz|0;a[H+1012>>2]=Yz|0;a[H+1032>>2]=Vz|0;a[H+1044>>2]=w7|0;a[H+1048>>2]=bn|0;a[H+1052>>2]=Wb|0;a[H+1068>>2]=qd|0;a[H+1072>>2]=Wb|0;a[H+1080>>2]=V6|0;a[H+1084>>2]=bn|0;a[H+1088>>2]=Wb|0;a[H+1096>>2]=bc|0;a[H+1104>>2]=qd|0;a[H+1108>>2]=Wb|0;a[H+1112>>2]=bc|0;a[H+1116>>2]=G6|0;a[H+1120>>2]=bn|0;a[H+1132>>2]=bc|0;a[H+1140>>2]=qd|0;a[H+1148>>2]=bc|0;a[H+1152>>2]=Li|0;a[H+1156>>2]=bn|0;a[H+1176>>2]=qd|0;a[H+1188>>2]=c6|0;a[H+1192>>2]=O5|0;a[H+1196>>2]=y5|0;a[H+1204>>2]=bc|0;a[H+1212>>2]=qd|0;a[H+1220>>2]=bc|0;a[H+1224>>2]=h5|0;a[H+1228>>2]=R4|0;a[H+1248>>2]=Vz|0;a[rl>>2]=J3|0;a[rl+4>>2]=t3|0;a[vc>>2]=vd|0;a[vc+4>>2]=jqa|0;a[vc+8>>2]=Ena|0;a[vc+12>>2]=pla|0;a[vc+16>>2]=zja|0;a[vc+20>>2]=Bha|0;a[vc+24>>2]=Aga|0;a[vc+28>>2]=Xfa|0;a[vc+32>>2]=ufa|0;a[vc+36>>2]=Qea|0;a[vc+40>>2]=tea|0;a[vc+44>>2]=Xda|0;a[vc+48>>2]=Eda|0;a[vc+52>>2]=mda|0;a[vc+56>>2]=Qca|0;a[vc+60>>2]=tca|0;a[vc+64>>2]=bca|0;a[vc+68>>2]=Iba|0;a[vc+72>>2]=oba|0;a[vc+76>>2]=Waa|0;a[vc+80>>2]=Faa|0;a[Tq+12>>2]=164;a[Tq+16>>2]=252;a[Tq+20>>2]=132;a[vV>>2]=368;a[Qc>>2]=Hoa|0;a[Qc+12>>2]=gma|0;a[Qc+16>>2]=pka|0;a[Qc+28>>2]=jr|0;a[Qc+32>>2]=Kga|0;a[Qc+44>>2]=iga|0;a[Qc+48>>2]=Ffa|0;a[Qc+60>>2]=cfa|0;a[Qc+64>>2]=Aea|0;a[Qc+76>>2]=hea|0;a[Qc+80>>2]=Lda|0;a[Qc+92>>2]=sda|0;a[Qc+96>>2]=$ca|0;a[Qc+108>>2]=Dca|0;a[Qc+112>>2]=ica|0;a[Qc+124>>2]=Rba|0;a[Qm+4>>2]=JZ|0;a[Qm+12>>2]=KU;a[Qm+16>>2]=Ty;a[Rm+4>>2]=C$|0;a[Rm+12>>2]=LU;a[Rm+16>>2]=PU;a[Sm+4>>2]=Dp|0;a[Sm+12>>2]=MU;a[Sm+16>>2]=Uy;a[Tm+4>>2]=jr|0;a[Tm+12>>2]=TU;a[Tm+16>>2]=QU;a[Um+4>>2]=q2|0;a[Um+12>>2]=uV;a[Um+16>>2]=RU;a[Vm+4>>2]=Xca|0;a[Vm+12>>2]=wV;a[Vm+16>>2]=Vy;a[Wg+4>>2]=ok|0;a[Wg+12>>2]=AV;a[Wg+16>>2]=SU;a[Wg+24>>2]=x$|0;a[Wg+32>>2]=JU;a[Wg+36>>2]=OU;a[zn>>2]=gea|0;a[zn+4>>2]=az|0;a[yn>>2]=eea|0;a[yn+4>>2]=ed|0;a[R+4>>2]=c5|0;a[R+12>>2]=hi;a[R+24>>2]=eAa|0;a[R+32>>2]=hi;a[R+44>>2]=hga|0;a[R+52>>2]=hi;a[R+64>>2]=Pba|0;a[R+72>>2]=hi;a[R+84>>2]=A9|0;a[R+92>>2]=hi;a[R+104>>2]=J6|0;a[R+112>>2]=Rj;a[R+124>>2]=Q3|0;a[R+132>>2]=Rj;a[R+144>>2]=$0|0;a[R+152>>2]=Rj;a[R+164>>2]=pZ|0;a[R+172>>2]=Rj;a[R+184>>2]=DW|0;a[R+192>>2]=Rj;a[R+204>>2]=oOa|0;a[R+212>>2]=Pj;a[R+224>>2]=KLa|0;a[R+232>>2]=Pj;a[R+244>>2]=eJa|0;a[R+252>>2]=Pj;a[R+264>>2]=yGa|0;a[R+272>>2]=Pj;a[R+284>>2]=TDa|0;a[R+292>>2]=Pj;a[R+304>>2]=TA|0;a[R+312>>2]=Xm;a[R+324>>2]=cza|0;a[R+332>>2]=Xm;a[R+344>>2]=PA|0;a[R+352>>2]=Xm;a[R+364>>2]=fua|0;a[R+372>>2]=Xm;a[R+384>>2]=psa|0;a[R+392>>2]=xV;a[R+404>>2]=Ypa|0;a[R+412>>2]=Md;a[R+424>>2]=rna|0;a[R+432>>2]=Md;a[R+444>>2]=hla|0;a[R+452>>2]=Md;a[R+464>>2]=rja|0;a[R+472>>2]=Md;a[R+484>>2]=sha|0;a[R+492>>2]=Md;a[R+504>>2]=wga|0;a[R+512>>2]=Md;a[R+524>>2]=Tfa|0;a[R+532>>2]=Md;a[R+544>>2]=qfa|0;a[R+552>>2]=Md;a[R+564>>2]=Nea|0;a[R+572>>2]=Md;a[R+584>>2]=rea|0;a[R+592>>2]=Md;a[R+604>>2]=Vda|0;a[R+612>>2]=Md;a[R+624>>2]=Cda|0;a[R+632>>2]=Md;a[R+644>>2]=jda|0;a[R+652>>2]=Md;a[R+664>>2]=Oca|0;a[R+672>>2]=Md;a[R+684>>2]=rca|0;a[R+692>>2]=Md;a[R+704>>2]=$ba|0;a[R+712>>2]=Md;a[R+724>>2]=Gba|0;a[R+732>>2]=ug;a[R+744>>2]=lba|0;a[R+752>>2]=ug;a[R+764>>2]=Uaa|0;a[R+772>>2]=ug;a[R+784>>2]=Daa|0;a[R+792>>2]=ug;a[R+804>>2]=maa|0;a[R+812>>2]=ug;a[R+824>>2]=W$|0;a[R+832>>2]=ug;a[R+844>>2]=G$|0;a[R+852>>2]=ug;a[R+864>>2]=n$|0;a[R+872>>2]=ug;a[R+884>>2]=iA|0;a[R+892>>2]=hi;a[R+904>>2]=J9|0;a[R+912>>2]=Qj;a[R+924>>2]=t9|0;a[R+932>>2]=Qj;a[R+944>>2]=e9|0;a[R+952>>2]=Qj;a[R+964>>2]=P8|0;a[R+972>>2]=Qj;a[R+984>>2]=z8|0;a[R+992>>2]=Qj;a[Wm+4>>2]=ok|0;a[Wm+12>>2]=zV;a[Wm+16>>2]=yV;a[Oc>>2]=vba|0;a[Oc+8>>2]=VFa|0;a[Oc+16>>2]=Oia|0;a[Oc+24>>2]=Gca|0;a[Oc+32>>2]=f$|0;a[Oc+40>>2]=k7|0;a[Oc+48>>2]=q4|0;a[Oc+56>>2]=x1|0;a[Oc+64>>2]=OZ|0;a[Oc+72>>2]=$W|0;a[Oc+80>>2]=KOa|0;a[Oc+88>>2]=DB|0;a[Oc+96>>2]=xJa|0;a[Oc+104>>2]=xHa|0;a[Uq+4>>2]=WW|0;a[Uq+16>>2]=BV;a[Vq+4>>2]=IEa|0;a[Vq+16>>2]=CV;a[Wq+4>>2]=iA|0;a[Wq+16>>2]=DV;a[Xg+4>>2]=PA|0;a[Xg+16>>2]=$y;a[Xg+24>>2]=rEa|0;a[Xg+36>>2]=$y;a[Xg+44>>2]=TA|0;a[Xg+56>>2]=FV;a[De+4>>2]=Xwa|0;a[De+16>>2]=Zq;a[De+24>>2]=vfa|0;a[De+36>>2]=$q;a[De+44>>2]=mba|0;a[De+56>>2]=$q;a[De+64>>2]=c9|0;a[De+76>>2]=$q;a[De+84>>2]=l6|0;a[De+96>>2]=Zq;a[De+104>>2]=o3|0;a[De+116>>2]=Zq;a[Xq+4>>2]=xGa|0;a[Xq+16>>2]=EV;a[ae+4>>2]=C7|0;a[ae+16>>2]=Sj;a[ae+24>>2]=J4|0;a[ae+36>>2]=Sj;a[ae+44>>2]=S1|0;a[ae+56>>2]=GV;a[ae+64>>2]=j_|0;a[ae+76>>2]=Sj;a[ae+84>>2]=wX|0;a[ae+96>>2]=Sj;a[ae+104>>2]=gPa|0;a[ae+116>>2]=Sj;a[Cl>>2]=cea|0;a[Cl+4>>2]=Jda|0;a[Cl+8>>2]=sg|0;a[Bl>>2]=Yca|0;a[Bl+4>>2]=jr|0;a[Bl+8>>2]=Dp|0;a[Mf>>2]=Ac|0;a[Mf+4>>2]=Zj|0;a[Mf+8>>2]=Yj|0;a[Mf+12>>2]=gn|0;a[Mf+16>>2]=Vj|0;a[Mf+20>>2]=Zm|0;a[Mf+24>>2]=Uj|0;a[Mf+28>>2]=hf|0;a[Pf>>2]=Ac|0;a[Pf+4>>2]=Zj|0;a[Pf+8>>2]=gn|0;a[Pf+12>>2]=Yj|0;a[Pf+16>>2]=Zm|0;a[Pf+20>>2]=Vj|0;a[Pf+24>>2]=hf|0;a[Pf+28>>2]=Uj|0;a[Yq>>2]=356;a[Yq+4>>2]=82;a[F>>2]=cba|0;a[F+8>>2]=Maa|0;a[F+16>>2]=vaa|0;a[F+24>>2]=eaa|0;a[F+32>>2]=O$|0;a[F+40>>2]=v$|0;a[F+48>>2]=e$|0;a[F+56>>2]=Q9|0;a[F+64>>2]=C9|0;a[F+72>>2]=m9|0;a[F+80>>2]=X8|0;a[F+88>>2]=I8|0;a[F+96>>2]=s8|0;a[F+104>>2]=d8|0;a[F+112>>2]=O7|0;a[F+120>>2]=A7|0;a[F+128>>2]=m7|0;a[F+136>>2]=Z6|0;a[F+144>>2]=L6|0;a[F+152>>2]=w6|0;a[F+160>>2]=h6|0;a[F+168>>2]=T5|0;a[F+176>>2]=D5|0;a[F+184>>2]=n5|0;a[F+192>>2]=W4|0;a[F+200>>2]=H4|0;a[F+208>>2]=u4|0;a[F+216>>2]=f4|0;a[F+224>>2]=S3|0;a[F+232>>2]=C3|0;a[F+240>>2]=l3|0;a[F+248>>2]=Y2|0;a[F+256>>2]=J2|0;a[F+264>>2]=u2|0;a[F+272>>2]=e2|0;a[F+280>>2]=Q1|0;a[F+288>>2]=D1|0;a[F+296>>2]=o1|0;a[F+304>>2]=b1|0;a[F+312>>2]=O0|0;a[F+320>>2]=A0|0;a[F+328>>2]=n0|0;a[F+336>>2]=$_|0;a[F+344>>2]=M_|0;a[F+352>>2]=w_|0;a[F+360>>2]=h_|0;a[F+368>>2]=UZ|0;a[F+376>>2]=EZ|0;a[F+384>>2]=rZ|0;a[F+392>>2]=bZ|0;a[F+400>>2]=NY|0;a[F+408>>2]=zY|0;a[F+416>>2]=kY|0;a[F+424>>2]=XX|0;a[F+432>>2]=IX|0;a[F+440>>2]=tX|0;a[F+448>>2]=fX|0;a[F+456>>2]=RW|0;a[F+464>>2]=EW|0;a[F+472>>2]=qW|0;a[F+480>>2]=cW|0;a[F+488>>2]=QV|0;a[F+496>>2]=TPa|0;a[F+504>>2]=GPa|0;a[F+512>>2]=rPa|0;a[F+520>>2]=dPa|0;a[F+528>>2]=ROa|0;a[F+536>>2]=COa|0;a[F+544>>2]=pOa|0;a[F+552>>2]=bOa|0;a[F+560>>2]=ONa|0;a[F+568>>2]=BNa|0;a[F+576>>2]=oNa|0;a[F+584>>2]=bNa|0;a[F+592>>2]=LMa|0;a[F+600>>2]=yMa|0;a[F+608>>2]=oMa|0;a[F+616>>2]=bMa|0;a[F+624>>2]=OLa|0;a[F+632>>2]=zLa|0;a[F+640>>2]=lLa|0;a[F+648>>2]=ZKa|0;a[F+656>>2]=MKa|0;a[F+664>>2]=wKa|0;a[F+672>>2]=lKa|0;a[F+680>>2]=ZJa|0;a[F+688>>2]=LJa|0;a[F+696>>2]=vJa|0;a[F+704>>2]=iJa|0;a[F+712>>2]=VIa|0;a[F+720>>2]=HIa|0;a[F+728>>2]=uIa|0;a[F+736>>2]=gIa|0;a[F+744>>2]=THa|0;a[F+752>>2]=FHa|0;a[F+760>>2]=rHa|0;a[F+768>>2]=eHa|0;a[F+776>>2]=JGa|0;a[F+784>>2]=EGa|0;a[F+792>>2]=nGa|0;a[F+800>>2]=ZFa|0;a[F+808>>2]=LFa|0;a[F+816>>2]=yFa|0;a[F+824>>2]=kFa|0;a[F+832>>2]=XEa|0;a[F+840>>2]=KEa|0;a[F+848>>2]=wEa|0;a[F+856>>2]=jEa|0;a[F+864>>2]=YDa|0;a[F+872>>2]=KDa|0;a[F+880>>2]=xDa|0;a[F+888>>2]=kDa|0;a[F+896>>2]=WCa|0;a[F+904>>2]=KCa|0;a[F+912>>2]=wCa|0;a[F+920>>2]=kCa|0;a[F+928>>2]=YBa|0;a[F+936>>2]=MBa|0;a[F+944>>2]=ABa|0;a[F+952>>2]=nBa|0;a[F+960>>2]=aBa|0;a[F+968>>2]=PAa|0;a[F+976>>2]=CAa|0;a[F+984>>2]=pAa|0;a[F+992>>2]=bAa|0;a[F+1e3>>2]=Qza|0;a[F+1008>>2]=ap|0;a[F+1016>>2]=tza|0;a[F+1024>>2]=hza|0;a[F+1032>>2]=Uya|0;a[F+1040>>2]=Hya|0;a[F+1048>>2]=uya|0;a[F+1056>>2]=fya|0;a[F+1064>>2]=Sxa|0;a[F+1072>>2]=Exa|0;a[F+1080>>2]=qxa|0;a[F+1088>>2]=cxa|0;a[F+1096>>2]=Owa|0;a[F+1104>>2]=Awa|0;a[F+1112>>2]=mwa|0;a[F+1120>>2]=$va|0;a[F+1128>>2]=Ova|0;a[F+1136>>2]=Ava|0;a[F+1144>>2]=nva|0;a[F+1152>>2]=$ua|0;a[F+1160>>2]=Nua|0;a[F+1168>>2]=Bua|0;a[F+1176>>2]=rua|0;a[F+1184>>2]=hua|0;a[F+1192>>2]=$ta|0;a[F+1200>>2]=Pta|0;a[F+1208>>2]=Fta|0;a[F+1216>>2]=wta|0;a[F+1224>>2]=DB|0;a[F+1232>>2]=eta|0;a[F+1240>>2]=Vsa|0;a[F+1248>>2]=Msa|0;a[F+1256>>2]=Esa|0;a[F+1264>>2]=usa|0;a[F+1272>>2]=hsa|0;a[F+1280>>2]=$ra|0;a[F+1288>>2]=Pra|0;a[F+1296>>2]=Cra|0;a[F+1304>>2]=qra|0;a[F+1312>>2]=cra|0;a[F+1320>>2]=Rqa|0;a[F+1328>>2]=Fqa|0;a[F+1336>>2]=sqa|0;a[F+1344>>2]=dqa|0;a[F+1352>>2]=Ppa|0;a[F+1360>>2]=Apa|0;a[F+1368>>2]=npa|0;a[F+1376>>2]=bpa|0;a[F+1384>>2]=Ooa|0;a[F+1392>>2]=yoa|0;a[F+1400>>2]=moa|0;a[F+1408>>2]=$na|0;a[F+1416>>2]=Mna|0;a[F+1424>>2]=yna|0;a[F+1432>>2]=ina|0;a[F+1440>>2]=Vma|0;a[F+1448>>2]=Ima|0;a[F+1456>>2]=xma|0;a[F+1464>>2]=nma|0;a[F+1472>>2]=$la|0;a[F+1480>>2]=Sla|0;a[F+1488>>2]=Gla|0;a[F+1496>>2]=vla|0;a[F+1504>>2]=kla|0;a[F+1512>>2]=ala|0;a[F+1520>>2]=Qka|0;a[F+1528>>2]=Ika|0;a[F+1536>>2]=Aka|0;a[F+1544>>2]=uka|0;a[F+1552>>2]=kka|0;a[F+1560>>2]=aka|0;a[F+1568>>2]=Pja|0;a[F+1576>>2]=Gja|0;a[F+1584>>2]=vja|0;a[F+1592>>2]=lja|0;a[F+1600>>2]=aja|0;a[F+1608>>2]=Ria|0;a[F+1616>>2]=Fia|0;a[F+1624>>2]=xia|0;a[F+1632>>2]=mia|0;a[F+1640>>2]=bia|0;a[F+1648>>2]=Sha|0;a[F+1656>>2]=Iha|0;a[F+1664>>2]=nha|0;a[F+1672>>2]=lha|0;a[F+1680>>2]=$ga|0;a[F+1688>>2]=Sga|0;a[F+1696>>2]=Oga|0;a[F+1704>>2]=Mga|0;a[F+1712>>2]=Iga|0;a[F+1720>>2]=Gga|0;a[F+1728>>2]=Dga|0;a[F+1736>>2]=Bga|0;a[F+1744>>2]=yga|0;a[F+1752>>2]=tga|0;a[F+1760>>2]=qga|0;a[F+1768>>2]=oga|0;a[F+1776>>2]=mga|0;a[F+1784>>2]=kga|0;a[F+1792>>2]=fga|0;a[F+1800>>2]=cga|0;a[F+1808>>2]=aga|0;a[F+1816>>2]=Zfa|0;a[F+1824>>2]=Vfa|0;a[F+1832>>2]=Qfa|0;a[F+1840>>2]=Nfa|0;a[F+1848>>2]=Lfa|0;a[F+1856>>2]=Jfa|0;a[F+1864>>2]=Hfa|0;a[F+1872>>2]=Cfa|0;a[F+1880>>2]=Afa|0;a[F+1888>>2]=yfa|0;a[F+1896>>2]=wfa|0;a[F+1904>>2]=sfa|0;a[F+1912>>2]=nfa|0;a[F+1920>>2]=kfa|0;a[F+1928>>2]=ifa|0;a[F+1936>>2]=gfa|0;a[F+1944>>2]=efa|0;a[F+1952>>2]=afa|0;a[F+1960>>2]=Xea|0;a[F+1968>>2]=Uea|0;a[F+1976>>2]=Sea|0;a[F+1984>>2]=Pea|0;a[F+1992>>2]=Kea|0;a[F+2e3>>2]=Hea|0;a[F+2008>>2]=Fea|0;a[co>>2]=q5|0;a[co+4>>2]=Z4|0;a[e>>2]=Efa|0;a[e+12>>2]=wba|0;a[e+24>>2]=k9|0;a[e+36>>2]=u6|0;a[e+48>>2]=A3|0;a[e+60>>2]=M0|0;a[e+72>>2]=aZ|0;a[e+84>>2]=pW|0;a[e+96>>2]=aOa|0;a[e+108>>2]=vLa|0;a[e+120>>2]=QIa|0;a[e+132>>2]=iGa|0;a[e+144>>2]=FDa|0;a[e+156>>2]=iBa|0;a[e+168>>2]=Pya|0;a[e+180>>2]=hwa|0;a[e+192>>2]=Wta|0;a[e+204>>2]=gsa|0;a[e+216>>2]=Jpa|0;a[e+228>>2]=cna|0;a[e+240>>2]=Xka|0;a[e+252>>2]=gja|0;a[e+264>>2]=gha|0;a[e+276>>2]=sga|0;a[e+288>>2]=Pfa|0;a[e+300>>2]=mfa|0;a[e+312>>2]=Iea|0;a[e+324>>2]=nea|0;a[e+336>>2]=Rda|0;a[e+348>>2]=yda|0;a[e+360>>2]=fda|0;a[e+372>>2]=Kca|0;a[e+384>>2]=oca|0;a[e+396>>2]=Xba|0;a[e+408>>2]=Dba|0;a[e+420>>2]=iba|0;a[e+432>>2]=Raa|0;a[e+444>>2]=Aaa|0;a[e+456>>2]=jaa|0;a[e+468>>2]=T$|0;a[e+480>>2]=B$|0;a[e+492>>2]=k$|0;a[e+504>>2]=V9|0;a[e+516>>2]=H9|0;a[e+528>>2]=r9|0;a[e+540>>2]=b9|0;a[e+552>>2]=N8|0;a[e+564>>2]=x8|0;a[e+576>>2]=i8|0;a[e+588>>2]=T7|0;a[e+600>>2]=G7|0;a[e+612>>2]=r7|0;a[e+624>>2]=d7|0;a[e+636>>2]=Q6|0;a[e+648>>2]=B6|0;a[e+660>>2]=n6|0;a[e+672>>2]=Y5|0;a[e+684>>2]=I5|0;a[e+696>>2]=t5|0;a[e+708>>2]=b5|0;a[e+720>>2]=N4|0;a[e+732>>2]=z4|0;a[e+744>>2]=k4|0;a[e+756>>2]=X3|0;a[e+768>>2]=H3|0;a[e+780>>2]=r3|0;a[e+792>>2]=c3|0;a[e+804>>2]=P2|0;a[e+816>>2]=A2|0;a[e+828>>2]=j2|0;a[e+840>>2]=W1|0;a[e+852>>2]=I1|0;a[e+864>>2]=t1|0;a[e+876>>2]=g1|0;a[e+888>>2]=T0|0;a[e+900>>2]=F0|0;a[e+912>>2]=s0|0;a[e+924>>2]=f0|0;a[e+936>>2]=R_|0;a[e+948>>2]=B_|0;a[e+960>>2]=n_|0;a[e+972>>2]=YZ|0;a[e+984>>2]=IZ|0;a[e+996>>2]=vZ|0;a[e+1008>>2]=gZ|0;a[e+1020>>2]=SY|0;a[e+1032>>2]=EY|0;a[e+1044>>2]=qY|0;a[e+1056>>2]=bY|0;a[e+1068>>2]=NX|0;a[e+1080>>2]=zX|0;a[e+1092>>2]=kX|0;a[e+1104>>2]=XW|0;a[e+1116>>2]=JW|0;a[e+1128>>2]=vW|0;a[e+1140>>2]=hW|0;a[e+1152>>2]=VV|0;a[e+1164>>2]=IV|0;a[e+1176>>2]=LPa|0;a[e+1188>>2]=wPa|0;a[e+1200>>2]=jPa|0;a[e+1212>>2]=WOa|0;a[e+1224>>2]=HOa|0;a[e+1236>>2]=uOa|0;a[e+1248>>2]=gOa|0;a[e+1260>>2]=TNa|0;a[e+1272>>2]=GNa|0;a[e+1284>>2]=tNa|0;a[e+1296>>2]=gNa|0;a[e+1308>>2]=RMa|0;a[e+1320>>2]=DMa|0;a[e+1332>>2]=qMa|0;a[e+1344>>2]=dMa|0;a[e+1356>>2]=QLa|0;a[e+1368>>2]=BLa|0;a[e+1380>>2]=nLa|0;a[e+1392>>2]=aLa|0;a[e+1404>>2]=OKa|0;a[e+1416>>2]=BKa|0;a[e+1428>>2]=nKa|0;a[e+1440>>2]=aKa|0;a[e+1452>>2]=NJa|0;a[e+1464>>2]=zJa|0;a[e+1476>>2]=kJa|0;a[e+1488>>2]=XIa|0;a[e+1500>>2]=JIa|0;a[e+1512>>2]=wIa|0;a[e+1524>>2]=jIa|0;a[e+1536>>2]=VHa|0;a[e+1548>>2]=HHa|0;a[e+1560>>2]=tHa|0;a[e+1572>>2]=gHa|0;a[e+1584>>2]=UGa|0;a[e+1596>>2]=GGa|0;a[e+1608>>2]=pGa|0;a[e+1620>>2]=aGa|0;a[e+1632>>2]=NFa|0;a[e+1644>>2]=AFa|0;a[e+1656>>2]=mFa|0;a[e+1668>>2]=ZEa|0;a[e+1680>>2]=MEa|0;a[e+1692>>2]=yEa|0;a[e+1704>>2]=lEa|0;a[e+1716>>2]=$Da|0;a[e+1728>>2]=MDa|0;a[e+1740>>2]=zDa|0;a[e+1752>>2]=mDa|0;a[e+1764>>2]=ZCa|0;a[e+1776>>2]=MCa|0;a[e+1788>>2]=yCa|0;a[e+1800>>2]=mCa|0;a[e+1812>>2]=$Ba|0;a[e+1824>>2]=OBa|0;a[e+1836>>2]=CBa|0;a[e+1848>>2]=pBa|0;a[e+1860>>2]=cBa|0;a[e+1872>>2]=RAa|0;a[e+1884>>2]=FAa|0;a[e+1896>>2]=rAa|0;a[e+1908>>2]=dAa|0;a[e+1920>>2]=Sza|0;a[e+1932>>2]=Gza|0;a[e+1944>>2]=vza|0;a[e+1956>>2]=jza|0;a[e+1968>>2]=Wya|0;a[e+1980>>2]=Jya|0;a[e+1992>>2]=wya|0;a[e+2004>>2]=iya|0;a[e+2016>>2]=Uxa|0;a[e+2028>>2]=Gxa|0;a[e+2040>>2]=sxa|0;a[e+2052>>2]=exa|0;a[e+2064>>2]=Qwa|0;a[e+2076>>2]=Cwa|0;a[e+2088>>2]=owa|0;a[e+2100>>2]=bwa|0;a[e+2112>>2]=Qva|0;a[e+2124>>2]=Dva|0;a[e+2136>>2]=pva|0;a[e+2148>>2]=bva|0;a[e+2160>>2]=Pua|0;a[e+2172>>2]=Cua|0;a[e+2184>>2]=sua|0;a[e+2196>>2]=iua|0;a[e+2208>>2]=aua|0;a[e+2220>>2]=Qta|0;a[e+2232>>2]=Gta|0;a[e+2244>>2]=xta|0;a[e+2256>>2]=nta|0;a[e+2268>>2]=fta|0;a[e+2280>>2]=Wsa|0;a[e+2292>>2]=Nsa|0;a[e+2304>>2]=Fsa|0;a[e+2316>>2]=vsa|0;a[e+2328>>2]=isa|0;a[e+2340>>2]=asa|0;a[e+2352>>2]=Qra|0;a[e+2364>>2]=Era|0;a[e+2376>>2]=rra|0;a[e+2388>>2]=dra|0;a[e+2400>>2]=Sqa|0;a[e+2412>>2]=Gqa|0;a[e+2424>>2]=tqa|0;a[e+2436>>2]=eqa|0;a[e+2448>>2]=Qpa|0;a[e+2460>>2]=Bpa|0;a[e+2472>>2]=opa|0;a[e+2484>>2]=cpa|0;a[e+2496>>2]=Poa|0;a[e+2508>>2]=zoa|0;a[e+2520>>2]=noa|0;a[e+2532>>2]=aoa|0;a[e+2544>>2]=Nna|0;a[e+2556>>2]=zna|0;a[e+2568>>2]=jna|0;a[e+2580>>2]=Wma|0;a[e+2592>>2]=Jma|0;a[e+2604>>2]=yma|0;a[e+2616>>2]=oma|0;a[e+2628>>2]=bma|0;a[e+2640>>2]=Tla|0;a[e+2652>>2]=Hla|0;a[e+2664>>2]=wla|0;a[e+2676>>2]=lla|0;a[e+2688>>2]=bla|0;a[e+2700>>2]=Rka|0;a[e+2712>>2]=Jka|0;a[e+2724>>2]=Bka|0;a[e+2736>>2]=vka|0;a[e+2748>>2]=lka|0;a[e+2760>>2]=cka|0;a[e+2772>>2]=Qja|0;a[e+2784>>2]=Hja|0;a[e+2796>>2]=wja|0;a[e+2808>>2]=mja|0;a[e+2820>>2]=bja|0;a[e+2832>>2]=Sia|0;a[e+2844>>2]=Hia|0;a[e+2856>>2]=yia|0;a[e+2868>>2]=nia|0;a[e+2880>>2]=cia|0;a[e+2892>>2]=Tha|0;a[e+2904>>2]=Jha|0;a[e+2916>>2]=yha|0;a[e+2928>>2]=mha|0;a[e+2940>>2]=aha|0;a[e+2952>>2]=Tga|0;a[e+2964>>2]=Pga|0;a[e+2976>>2]=Nga|0;a[e+2988>>2]=Jga|0;a[e+3e3>>2]=Hga|0;a[e+3012>>2]=Ega|0;a[e+3024>>2]=Cga|0;a[e+3036>>2]=zga|0;a[e+3048>>2]=uga|0;a[e+3060>>2]=rga|0;a[e+3072>>2]=pga|0;a[e+3084>>2]=nga|0;a[e+3096>>2]=lga|0;a[e+3108>>2]=gga|0;a[e+3120>>2]=dga|0;a[e+3132>>2]=bga|0;a[e+3144>>2]=$fa|0;a[e+3156>>2]=Wfa|0;a[e+3168>>2]=Rfa|0;a[e+3180>>2]=Ofa|0;a[e+3192>>2]=Mfa|0;a[e+3204>>2]=Kfa|0;a[e+3216>>2]=Ifa|0;a[e+3228>>2]=Dfa|0;a[e+3240>>2]=Bfa|0;a[e+3252>>2]=zfa|0;a[e+3264>>2]=xfa|0;a[e+3276>>2]=tfa|0;a[e+3288>>2]=ofa|0;a[e+3300>>2]=lfa|0;a[e+3312>>2]=jfa|0;a[e+3324>>2]=hfa|0;a[e+3336>>2]=ffa|0;a[e+3348>>2]=bfa|0;a[e+3360>>2]=Yea|0;a[e+3372>>2]=Wea|0;a[e+3384>>2]=Tea|0;a[e+3396>>2]=Rea|0;a[e+3408>>2]=Oea|0;a[e+3420>>2]=Jea|0;a[e+3432>>2]=Gea|0;a[e+3444>>2]=Eea|0;a[e+3456>>2]=Dea|0;a[e+3468>>2]=Cea|0;a[e+3480>>2]=zea|0;a[e+3492>>2]=yea|0;a[e+3504>>2]=wea|0;a[e+3516>>2]=uea|0;a[e+3528>>2]=sea|0;a[e+3540>>2]=oea|0;a[e+3552>>2]=mea|0;a[e+3564>>2]=lea|0;a[e+3576>>2]=kea|0;a[e+3588>>2]=jea|0;a[e+3600>>2]=fea|0;a[e+3612>>2]=bea|0;a[e+3624>>2]=$da|0;a[e+3636>>2]=Zda|0;a[e+3648>>2]=Wda|0;a[e+3660>>2]=Sda|0;a[e+3672>>2]=Qda|0;a[e+3684>>2]=Pda|0;a[e+3696>>2]=Oda|0;a[e+3708>>2]=Nda|0;a[e+3720>>2]=Kda|0;a[e+3732>>2]=Ida|0;a[e+3744>>2]=Hda|0;a[e+3756>>2]=Gda|0;a[e+3768>>2]=Dda|0;a[e+3780>>2]=zda|0;a[e+3792>>2]=xda|0;a[e+3804>>2]=wda|0;a[e+3816>>2]=vda|0;a[e+3828>>2]=uda|0;a[e+3840>>2]=rda|0;a[e+3852>>2]=qda|0;a[e+3864>>2]=oda|0;a[e+3876>>2]=nda|0;a[e+3888>>2]=lda|0;a[e+3900>>2]=gda|0;a[e+3912>>2]=eda|0;a[e+3924>>2]=dda|0;a[e+3936>>2]=cda|0;a[e+3948>>2]=bda|0;a[e+3960>>2]=Zca|0;a[e+3972>>2]=Wca|0;a[e+3984>>2]=Tca|0;a[e+3996>>2]=Sca|0;a[e+4008>>2]=Pca|0;a[e+4020>>2]=Mca|0;a[e+4032>>2]=Jca|0;a[e+4044>>2]=Ica|0;a[e+4056>>2]=Hca|0;a[e+4068>>2]=Fca|0;a[e+4080>>2]=Cca|0;a[e+4092>>2]=Aca|0;a[e+4104>>2]=xca|0;a[e+4116>>2]=vca|0;a[e+4128>>2]=sca|0;a[e+4140>>2]=pca|0;a[e+4152>>2]=nca|0;a[e+4164>>2]=mca|0;a[e+4176>>2]=lca|0;a[e+4188>>2]=kca|0;a[e+4200>>2]=hca|0;a[e+4212>>2]=gca|0;a[e+4224>>2]=eca|0;a[e+4236>>2]=dca|0;a[e+4248>>2]=aca|0;a[e+4260>>2]=Yba|0;a[e+4272>>2]=Wba|0;a[e+4284>>2]=Vba|0;a[e+4296>>2]=Uba|0;a[e+4308>>2]=Tba|0;a[e+4320>>2]=Qba|0;a[e+4332>>2]=Oba|0;a[e+4344>>2]=Lba|0;a[e+4356>>2]=Jba|0;a[e+4368>>2]=Hba|0;a[e+4380>>2]=Eba|0;a[e+4392>>2]=Cba|0;a[e+4404>>2]=Bba|0;a[e+4416>>2]=Aba|0;a[e+4428>>2]=zba|0;a[e+4440>>2]=xba|0;a[e+4452>>2]=uba|0;a[e+4464>>2]=rba|0;a[e+4476>>2]=pba|0;a[e+4488>>2]=nba|0;a[e+4500>>2]=jba|0;a[e+4512>>2]=hba|0;a[e+4524>>2]=gba|0;a[e+4536>>2]=eba|0;a[e+4548>>2]=dba|0;a[e+4560>>2]=bba|0;a[e+4572>>2]=aba|0;a[e+4584>>2]=Yaa|0;a[e+4596>>2]=Xaa|0;a[e+4608>>2]=Vaa|0;a[e+4620>>2]=Saa|0;a[e+4632>>2]=Qaa|0;a[e+4644>>2]=Paa|0;a[e+4656>>2]=Oaa|0;a[e+4668>>2]=Naa|0;a[e+4680>>2]=Laa|0;a[e+4692>>2]=Kaa|0;a[e+4704>>2]=Haa|0;a[e+4716>>2]=Gaa|0;a[e+4728>>2]=Eaa|0;a[e+4740>>2]=Baa|0;a[e+4752>>2]=zaa|0;a[e+4764>>2]=yaa|0;a[e+4776>>2]=xaa|0;a[e+4788>>2]=waa|0;a[e+4800>>2]=uaa|0;a[e+4812>>2]=saa|0;a[e+4824>>2]=paa|0;a[e+4836>>2]=oaa|0;a[e+4848>>2]=naa|0;a[e+4860>>2]=kaa|0;a[e+4872>>2]=iaa|0;a[e+4884>>2]=haa|0;a[e+4896>>2]=gaa|0;a[e+4908>>2]=faa|0;a[e+4920>>2]=caa|0;a[e+4932>>2]=aaa|0;a[e+4944>>2]=Z$|0;a[e+4956>>2]=Y$|0;a[e+4968>>2]=X$|0;a[e+4980>>2]=U$|0;a[e+4992>>2]=S$|0;a[e+5004>>2]=R$|0;a[e+5016>>2]=Q$|0;a[e+5028>>2]=P$|0;a[e+5040>>2]=N$|0;a[e+5052>>2]=M$|0;a[e+5064>>2]=J$|0;a[e+5076>>2]=I$|0;a[e+5088>>2]=H$|0;a[e+5100>>2]=E$|0;a[e+5112>>2]=A$|0;a[e+5124>>2]=z$|0;a[e+5136>>2]=y$|0;a[e+5148>>2]=w$|0;a[e+5160>>2]=u$|0;a[e+5172>>2]=t$|0;a[e+5184>>2]=q$|0;a[e+5196>>2]=p$|0;a[e+5208>>2]=o$|0;a[e+5220>>2]=l$|0;a[e+5232>>2]=j$|0;a[e+5244>>2]=i$|0;a[e+5256>>2]=h$|0;a[e+5268>>2]=g$|0;a[e+5280>>2]=d$|0;a[e+5292>>2]=c$|0;a[e+5304>>2]=$9|0;a[e+5316>>2]=Z9|0;a[e+5328>>2]=Y9|0;a[e+5340>>2]=W9|0;a[e+5352>>2]=U9|0;a[e+5364>>2]=T9|0;a[e+5376>>2]=S9|0;a[e+5388>>2]=R9|0;a[e+5400>>2]=P9|0;a[e+5412>>2]=O9|0;a[e+5424>>2]=M9|0;a[e+5436>>2]=L9|0;a[e+5448>>2]=K9|0;a[e+5460>>2]=I9|0;a[e+5472>>2]=G9|0;a[e+5484>>2]=F9|0;a[e+5496>>2]=E9|0;a[e+5508>>2]=D9|0;a[e+5520>>2]=B9|0;a[e+5532>>2]=z9|0;a[e+5544>>2]=w9|0;a[e+5556>>2]=v9|0;a[e+5568>>2]=u9|0;a[e+5580>>2]=s9|0;a[e+5592>>2]=q9|0;a[e+5604>>2]=p9|0;a[e+5616>>2]=o9|0;a[e+5628>>2]=n9|0;a[e+5640>>2]=l9|0;a[e+5652>>2]=j9|0;a[e+5664>>2]=h9|0;a[e+5676>>2]=g9|0;a[e+5688>>2]=f9|0;a[e+5700>>2]=d9|0;a[e+5712>>2]=a9|0;a[e+5724>>2]=$8|0;a[e+5736>>2]=Z8|0;a[e+5748>>2]=Y8|0;a[e+5760>>2]=W8|0;a[e+5772>>2]=V8|0;a[e+5784>>2]=S8|0;a[e+5796>>2]=R8|0;a[e+5808>>2]=Q8|0;a[e+5820>>2]=O8|0;a[e+5832>>2]=M8|0;a[e+5844>>2]=L8|0;a[e+5856>>2]=K8|0;a[e+5868>>2]=J8|0;a[e+5880>>2]=H8|0;a[e+5892>>2]=G8|0;a[e+5904>>2]=D8|0;a[e+5916>>2]=C8|0;a[e+5928>>2]=A8|0;a[e+5940>>2]=y8|0;a[e+5952>>2]=w8|0;a[e+5964>>2]=v8|0;a[e+5976>>2]=u8|0;a[e+5988>>2]=t8|0;a[e+6e3>>2]=r8|0;a[e+6012>>2]=q8|0;a[e+6024>>2]=n8|0;a[e+6036>>2]=m8|0;a[e+6048>>2]=l8|0;a[e+6060>>2]=j8|0;a[e+6072>>2]=h8|0;a[e+6084>>2]=g8|0;a[e+6096>>2]=f8|0;a[e+6108>>2]=e8|0;a[e+6120>>2]=b8|0;a[e+6132>>2]=a8|0;a[e+6144>>2]=Z7|0;a[e+6156>>2]=Y7|0;a[e+6168>>2]=W7|0;a[e+6180>>2]=U7|0;a[e+6192>>2]=S7|0;a[e+6204>>2]=R7|0;a[e+6216>>2]=Q7|0;a[e+6228>>2]=P7|0;a[e+6240>>2]=N7|0;a[e+6252>>2]=M7|0;a[e+6264>>2]=K7|0;a[e+6276>>2]=J7|0;a[e+6288>>2]=I7|0;a[e+6300>>2]=H7|0;a[e+6312>>2]=F7|0;a[e+6324>>2]=E7|0;a[e+6336>>2]=D7|0;a[e+6348>>2]=B7|0;a[e+6360>>2]=z7|0;a[e+6372>>2]=y7|0;a[e+6384>>2]=v7|0;a[e+6396>>2]=u7|0;a[e+6408>>2]=t7|0;a[e+6420>>2]=s7|0;a[e+6432>>2]=q7|0;a[e+6444>>2]=p7|0;a[e+6456>>2]=o7|0;a[e+6468>>2]=n7|0;a[e+6480>>2]=l7|0;a[e+6492>>2]=j7|0;a[e+6504>>2]=h7|0;a[e+6516>>2]=g7|0;a[e+6528>>2]=f7|0;a[e+6540>>2]=e7|0;a[e+6552>>2]=c7|0;a[e+6564>>2]=b7|0;a[e+6576>>2]=a7|0;a[e+6588>>2]=$6|0;a[e+6600>>2]=Y6|0;a[e+6612>>2]=X6|0;a[e+6624>>2]=U6|0;a[e+6636>>2]=T6|0;a[e+6648>>2]=S6|0;a[e+6660>>2]=R6|0;a[e+6672>>2]=P6|0;a[e+6684>>2]=O6|0;a[e+6696>>2]=N6|0;a[e+6708>>2]=M6|0;a[e+6720>>2]=K6|0;a[e+6732>>2]=I6|0;a[e+6744>>2]=F6|0;a[e+6756>>2]=E6|0;a[e+6768>>2]=D6|0;a[e+6780>>2]=C6|0;a[e+6792>>2]=A6|0;a[e+6804>>2]=z6|0;a[e+6816>>2]=y6|0;a[e+6828>>2]=x6|0;a[e+6840>>2]=v6|0;a[e+6852>>2]=t6|0;a[e+6864>>2]=r6|0;a[e+6876>>2]=q6|0;a[e+6888>>2]=p6|0;a[e+6900>>2]=o6|0;a[e+6912>>2]=m6|0;a[e+6924>>2]=k6|0;a[e+6936>>2]=j6|0;a[e+6948>>2]=i6|0;a[e+6960>>2]=g6|0;a[e+6972>>2]=f6|0;a[e+6984>>2]=d6|0;a[e+6996>>2]=b6|0;a[e+7008>>2]=a6|0;a[e+7020>>2]=Z5|0;a[e+7032>>2]=X5|0;a[e+7044>>2]=W5|0;a[e+7056>>2]=V5|0;a[e+7068>>2]=U5|0;a[e+7080>>2]=S5|0;a[e+7092>>2]=R5|0;a[e+7104>>2]=P5|0;a[e+7116>>2]=N5|0;a[e+7128>>2]=L5|0;a[e+7140>>2]=J5|0;a[e+7152>>2]=H5|0;a[e+7164>>2]=G5|0;a[e+7176>>2]=F5|0;a[e+7188>>2]=E5|0;a[e+7200>>2]=C5|0;a[e+7212>>2]=B5|0;a[e+7224>>2]=z5|0;a[e+7236>>2]=x5|0;a[e+7248>>2]=v5|0;a[e+7260>>2]=u5|0;a[e+7272>>2]=s5|0;a[e+7284>>2]=r5|0;a[e+7296>>2]=p5|0;a[e+7308>>2]=o5|0;a[e+7320>>2]=l5|0;a[e+7332>>2]=k5|0;a[e+7344>>2]=i5|0;a[e+7356>>2]=g5|0;a[e+7368>>2]=e5|0;a[e+7380>>2]=d5|0;a[e+7392>>2]=a5|0;a[e+7404>>2]=$4|0;a[e+7416>>2]=Y4|0;a[e+7428>>2]=X4|0;a[e+7440>>2]=V4|0;a[e+7452>>2]=U4|0;a[e+7464>>2]=S4|0;a[e+7476>>2]=Q4|0;a[e+7488>>2]=P4|0;a[e+7500>>2]=O4|0;a[e+7512>>2]=M4|0;a[e+7524>>2]=L4|0;a[e+7536>>2]=K4|0;a[e+7548>>2]=I4|0;a[e+7560>>2]=G4|0;a[e+7572>>2]=F4|0;a[e+7584>>2]=D4|0;a[e+7596>>2]=C4|0;a[e+7608>>2]=B4|0;a[e+7620>>2]=A4|0;a[e+7632>>2]=y4|0;a[e+7644>>2]=x4|0;a[e+7656>>2]=w4|0;a[e+7668>>2]=v4|0;a[e+7680>>2]=t4|0;a[e+7692>>2]=s4|0;a[e+7704>>2]=p4|0;a[e+7716>>2]=o4|0;a[e+7728>>2]=m4|0;a[e+7740>>2]=l4|0;a[e+7752>>2]=j4|0;a[e+7764>>2]=i4|0;a[e+7776>>2]=h4|0;a[e+7788>>2]=g4|0;a[e+7800>>2]=e4|0;a[e+7812>>2]=d4|0;a[e+7824>>2]=b4|0;a[e+7836>>2]=a4|0;a[e+7848>>2]=$3|0;a[e+7860>>2]=Y3|0;a[e+7872>>2]=W3|0;a[e+7884>>2]=V3|0;a[e+7896>>2]=U3|0;a[e+7908>>2]=T3|0;a[e+7920>>2]=R3|0;a[e+7932>>2]=P3|0;a[e+7944>>2]=N3|0;a[e+7956>>2]=M3|0;a[e+7968>>2]=L3|0;a[e+7980>>2]=I3|0;a[e+7992>>2]=G3|0;a[e+8004>>2]=F3|0;a[e+8016>>2]=E3|0;a[e+8028>>2]=D3|0;a[e+8040>>2]=B3|0;a[e+8052>>2]=z3|0;a[e+8064>>2]=x3|0;a[e+8076>>2]=w3|0;a[e+8088>>2]=v3|0;a[e+8100>>2]=s3|0;a[e+8112>>2]=q3|0;a[e+8124>>2]=p3|0;a[e+8136>>2]=n3|0;a[e+8148>>2]=m3|0;a[e+8160>>2]=k3|0;a[e+8172>>2]=j3|0;a[e+8184>>2]=h3|0;a[e+8196>>2]=g3|0;a[e+8208>>2]=f3|0;a[e+8220>>2]=d3|0;a[e+8232>>2]=b3|0;a[e+8244>>2]=a3|0;a[e+8256>>2]=$2|0;a[e+8268>>2]=Z2|0;a[e+8280>>2]=X2|0;a[e+8292>>2]=W2|0;a[e+8304>>2]=U2|0;a[e+8316>>2]=T2|0;a[e+8328>>2]=S2|0;a[e+8340>>2]=Q2|0;a[e+8352>>2]=O2|0;a[e+8364>>2]=N2|0;a[e+8376>>2]=L2|0;a[e+8388>>2]=K2|0;a[e+8400>>2]=I2|0;a[e+8412>>2]=H2|0;a[e+8424>>2]=F2|0;a[e+8436>>2]=E2|0;a[e+8448>>2]=D2|0;a[e+8460>>2]=B2|0;a[e+8472>>2]=z2|0;a[e+8484>>2]=y2|0;a[e+8496>>2]=x2|0;a[e+8508>>2]=w2|0;a[e+8520>>2]=s2|0;a[e+8532>>2]=r2|0;a[e+8544>>2]=o2|0;a[e+8556>>2]=n2|0;a[e+8568>>2]=m2|0;a[e+8580>>2]=k2|0;a[e+8592>>2]=i2|0;a[e+8604>>2]=h2|0;a[e+8616>>2]=g2|0;a[e+8628>>2]=f2|0;a[e+8640>>2]=d2|0;a[e+8652>>2]=c2|0;a[e+8664>>2]=a2|0;a[e+8676>>2]=$1|0;a[e+8688>>2]=Z1|0;a[e+8700>>2]=X1|0;a[e+8712>>2]=V1|0;a[e+8724>>2]=U1|0;a[e+8736>>2]=T1|0;a[e+8748>>2]=R1|0;a[e+8760>>2]=P1|0;a[e+8772>>2]=O1|0;a[e+8784>>2]=M1|0;a[e+8796>>2]=L1|0;a[e+8808>>2]=K1|0;a[e+8820>>2]=J1|0;a[e+8832>>2]=H1|0;a[e+8844>>2]=G1|0;a[e+8856>>2]=F1|0;a[e+8868>>2]=E1|0;a[e+8880>>2]=C1|0;a[e+8892>>2]=A1|0;a[e+8904>>2]=y1|0;a[e+8916>>2]=w1|0;a[e+8928>>2]=v1|0;a[e+8940>>2]=u1|0;a[e+8952>>2]=s1|0;a[e+8964>>2]=r1|0;a[e+8976>>2]=q1|0;a[e+8988>>2]=p1|0;a[e+9e3>>2]=n1|0;a[e+9012>>2]=m1|0;a[e+9024>>2]=k1|0;a[e+9036>>2]=j1|0;a[e+9048>>2]=i1|0;a[e+9060>>2]=h1|0;a[e+9072>>2]=f1|0;a[e+9084>>2]=e1|0;a[e+9096>>2]=d1|0;a[e+9108>>2]=c1|0;a[e+9120>>2]=a1|0;a[e+9132>>2]=Z0|0;a[e+9144>>2]=X0|0;a[e+9156>>2]=W0|0;a[e+9168>>2]=V0|0;a[e+9180>>2]=U0|0;a[e+9192>>2]=S0|0;a[e+9204>>2]=R0|0;a[e+9216>>2]=Q0|0;a[e+9228>>2]=P0|0;a[e+9240>>2]=N0|0;a[e+9252>>2]=L0|0;a[e+9264>>2]=J0|0;a[e+9276>>2]=I0|0;a[e+9288>>2]=H0|0;a[e+9300>>2]=G0|0;a[e+9312>>2]=E0|0;a[e+9324>>2]=D0|0;a[e+9336>>2]=C0|0;a[e+9348>>2]=B0|0;a[e+9360>>2]=z0|0;a[e+9372>>2]=y0|0;a[e+9384>>2]=w0|0;a[e+9396>>2]=v0|0;a[e+9408>>2]=u0|0;a[e+9420>>2]=t0|0;a[e+9432>>2]=r0|0;a[e+9444>>2]=q0|0;a[e+9456>>2]=p0|0;a[e+9468>>2]=o0|0;a[e+9480>>2]=m0|0;a[e+9492>>2]=l0|0;a[e+9504>>2]=j0|0;a[e+9516>>2]=i0|0;a[e+9528>>2]=h0|0;a[e+9540>>2]=g0|0;a[e+9552>>2]=e0|0;a[e+9564>>2]=d0|0;a[e+9576>>2]=b0|0;a[e+9588>>2]=a0|0;a[e+9600>>2]=Z_|0;a[e+9612>>2]=Y_|0;a[e+9624>>2]=W_|0;a[e+9636>>2]=V_|0;a[e+9648>>2]=T_|0;a[e+9660>>2]=S_|0;a[e+9672>>2]=Q_|0;a[e+9684>>2]=P_|0;a[e+9696>>2]=O_|0;a[e+9708>>2]=N_|0;a[e+9720>>2]=L_|0;a[e+9732>>2]=J_|0;a[e+9744>>2]=G_|0;a[e+9756>>2]=F_|0;a[e+9768>>2]=D_|0;a[e+9780>>2]=C_|0;a[e+9792>>2]=A_|0;a[e+9804>>2]=z_|0;a[e+9816>>2]=y_|0;a[e+9828>>2]=x_|0;a[e+9840>>2]=v_|0;a[e+9852>>2]=u_|0;a[e+9864>>2]=s_|0;a[e+9876>>2]=r_|0;a[e+9888>>2]=p_|0;a[e+9900>>2]=o_|0;a[e+9912>>2]=m_|0;a[e+9924>>2]=l_|0;a[e+9936>>2]=k_|0;a[e+9948>>2]=i_|0;a[e+9960>>2]=g_|0;a[e+9972>>2]=f_|0;a[e+9984>>2]=d_|0;a[e+9996>>2]=c_|0;a[e+10008>>2]=a_|0;a[e+10020>>2]=$Z|0;a[e+10032>>2]=ZZ|0;a[e+10044>>2]=XZ|0;a[e+10056>>2]=WZ|0;a[e+10068>>2]=VZ|0;a[e+10080>>2]=TZ|0;a[e+10092>>2]=SZ|0;a[e+10104>>2]=QZ|0;a[e+10116>>2]=PZ|0;a[e+10128>>2]=MZ|0;a[e+10140>>2]=LZ|0;a[e+10152>>2]=KZ|0;a[e+10164>>2]=HZ|0;a[e+10176>>2]=GZ|0;a[e+10188>>2]=FZ|0;a[e+10200>>2]=DZ|0;a[e+10212>>2]=CZ|0;a[e+10224>>2]=AZ|0;a[e+10236>>2]=zZ|0;a[e+10248>>2]=yZ|0;a[e+10260>>2]=xZ|0;a[e+10272>>2]=wZ|0;a[e+10284>>2]=uZ|0;a[e+10296>>2]=tZ|0;a[e+10308>>2]=sZ|0;a[e+10320>>2]=qZ|0;a[e+10332>>2]=oZ|0;a[e+10344>>2]=mZ|0;a[e+10356>>2]=lZ|0;a[e+10368>>2]=jZ|0;a[e+10380>>2]=iZ|0;a[e+10392>>2]=hZ|0;a[e+10404>>2]=fZ|0;a[e+10416>>2]=eZ|0;a[e+10428>>2]=dZ|0;a[e+10440>>2]=cZ|0;a[e+10452>>2]=$Y|0;a[e+10464>>2]=YY|0;a[e+10476>>2]=XY|0;a[e+10488>>2]=VY|0;a[e+10500>>2]=UY|0;a[e+10512>>2]=TY|0;a[e+10524>>2]=RY|0;a[e+10536>>2]=QY|0;a[e+10548>>2]=PY|0;a[e+10560>>2]=OY|0;a[e+10572>>2]=MY|0;a[e+10584>>2]=KY|0;a[e+10596>>2]=JY|0;a[e+10608>>2]=HY|0;a[e+10620>>2]=GY|0;a[e+10632>>2]=FY|0;a[e+10644>>2]=DY|0;a[e+10656>>2]=CY|0;a[e+10668>>2]=BY|0;a[e+10680>>2]=AY|0;a[e+10692>>2]=yY|0;a[e+10704>>2]=wY|0;a[e+10716>>2]=vY|0;a[e+10728>>2]=tY|0;a[e+10740>>2]=sY|0;a[e+10752>>2]=rY|0;a[e+10764>>2]=pY|0;a[e+10776>>2]=nY|0;a[e+10788>>2]=mY|0;a[e+10800>>2]=lY|0;a[e+10812>>2]=jY|0;a[e+10824>>2]=hY|0;a[e+10836>>2]=gY|0;a[e+10848>>2]=eY|0;a[e+10860>>2]=dY|0;a[e+10872>>2]=cY|0;a[e+10884>>2]=aY|0;a[e+10896>>2]=$X|0;a[e+10908>>2]=ZX|0;a[e+10920>>2]=YX|0;a[e+10932>>2]=VX|0;a[e+10944>>2]=TX|0;a[e+10956>>2]=SX|0;a[e+10968>>2]=QX|0;a[e+10980>>2]=PX|0;a[e+10992>>2]=OX|0;a[e+11004>>2]=MX|0;a[e+11016>>2]=LX|0;a[e+11028>>2]=KX|0;a[e+11040>>2]=JX|0;a[e+11052>>2]=HX|0;a[e+11064>>2]=FX|0;a[e+11076>>2]=EX|0;a[e+11088>>2]=CX|0;a[e+11100>>2]=BX|0;a[e+11112>>2]=AX|0;a[e+11124>>2]=yX|0;a[e+11136>>2]=xX|0;a[e+11148>>2]=vX|0;a[e+11160>>2]=uX|0;a[e+11172>>2]=sX|0;a[e+11184>>2]=qX|0;a[e+11196>>2]=pX|0;a[e+11208>>2]=nX|0;a[e+11220>>2]=mX|0;a[e+11232>>2]=lX|0;a[e+11244>>2]=jX|0;a[e+11256>>2]=iX|0;a[e+11268>>2]=hX|0;a[e+11280>>2]=gX|0;a[e+11292>>2]=eX|0;a[e+11304>>2]=cX|0;a[e+11316>>2]=bX|0;a[e+11328>>2]=aX|0;a[e+11340>>2]=ZW|0;a[e+11352>>2]=YW|0;a[e+11364>>2]=VW|0;a[e+11376>>2]=UW|0;a[e+11388>>2]=TW|0;a[e+11400>>2]=SW|0;a[e+11412>>2]=QW|0;a[e+11424>>2]=OW|0;a[e+11436>>2]=NW|0;a[e+11448>>2]=MW|0;a[e+11460>>2]=LW|0;a[e+11472>>2]=KW|0;a[e+11484>>2]=IW|0;a[e+11496>>2]=HW|0;a[e+11508>>2]=GW|0;a[e+11520>>2]=FW|0;a[e+11532>>2]=CW|0;a[e+11544>>2]=AW|0;a[e+11556>>2]=zW|0;a[e+11568>>2]=yW|0;a[e+11580>>2]=xW|0;a[e+11592>>2]=wW|0;a[e+11604>>2]=uW|0;a[e+11616>>2]=tW|0;a[e+11628>>2]=sW|0;a[e+11640>>2]=rW|0;a[e+11652>>2]=oW|0;a[e+11664>>2]=mW|0;a[e+11676>>2]=lW|0;a[e+11688>>2]=kW|0;a[e+11700>>2]=jW|0;a[e+11712>>2]=iW|0;a[e+11724>>2]=gW|0;a[e+11736>>2]=fW|0;a[e+11748>>2]=eW|0;a[e+11760>>2]=dW|0;a[e+11772>>2]=bW|0;a[e+11784>>2]=$V|0;a[e+11796>>2]=ZV|0;a[e+11808>>2]=YV|0;a[e+11820>>2]=XV|0;a[e+11832>>2]=WV|0;a[e+11844>>2]=UV|0;a[e+11856>>2]=TV|0;a[e+11868>>2]=SV|0;a[e+11880>>2]=RV|0;a[e+11892>>2]=PV|0;a[e+11904>>2]=NV|0;a[e+11916>>2]=MV|0;a[e+11928>>2]=LV|0;a[e+11940>>2]=KV|0;a[e+11952>>2]=JV|0;a[e+11964>>2]=HV|0;a[e+11976>>2]=WPa|0;a[e+11988>>2]=VPa|0;a[e+12e3>>2]=UPa|0;a[e+12012>>2]=SPa|0;a[e+12024>>2]=QPa|0;a[e+12036>>2]=PPa|0;a[e+12048>>2]=OPa|0;a[e+12060>>2]=NPa|0;a[e+12072>>2]=MPa|0;a[e+12084>>2]=KPa|0;a[e+12096>>2]=JPa|0;a[e+12108>>2]=IPa|0;a[e+12120>>2]=HPa|0;a[e+12132>>2]=EPa|0;a[e+12144>>2]=CPa|0;a[e+12156>>2]=APa|0;a[e+12168>>2]=zPa|0;a[e+12180>>2]=yPa|0;a[e+12192>>2]=xPa|0;a[e+12204>>2]=vPa|0;a[e+12216>>2]=uPa|0;a[e+12228>>2]=tPa|0;a[e+12240>>2]=sPa|0;a[e+12252>>2]=qPa|0;a[e+12264>>2]=oPa|0;a[e+12276>>2]=nPa|0;a[e+12288>>2]=mPa|0;a[e+12300>>2]=lPa|0;a[e+12312>>2]=kPa|0;a[e+12324>>2]=iPa|0;a[e+12336>>2]=hPa|0;a[e+12348>>2]=fPa|0;a[e+12360>>2]=ePa|0;a[e+12372>>2]=cPa|0;a[e+12384>>2]=aPa|0;a[e+12396>>2]=$Oa|0;a[e+12408>>2]=ZOa|0;a[e+12420>>2]=YOa|0;a[e+12432>>2]=XOa|0;a[e+12444>>2]=VOa|0;a[e+12456>>2]=UOa|0;a[e+12468>>2]=TOa|0;a[e+12480>>2]=SOa|0;a[e+12492>>2]=QOa|0;a[e+12504>>2]=OOa|0;a[e+12516>>2]=NOa|0;a[e+12528>>2]=MOa|0;a[e+12540>>2]=LOa|0;a[e+12552>>2]=IOa|0;a[e+12564>>2]=GOa|0;a[e+12576>>2]=FOa|0;a[e+12588>>2]=EOa|0;a[e+12600>>2]=DOa|0;a[e+12612>>2]=BOa|0;a[e+12624>>2]=zOa|0;a[e+12636>>2]=yOa|0;a[e+12648>>2]=xOa|0;a[e+12660>>2]=wOa|0;a[e+12672>>2]=vOa|0;a[e+12684>>2]=tOa|0;a[e+12696>>2]=sOa|0;a[e+12708>>2]=rOa|0;a[e+12720>>2]=qOa|0;a[e+12732>>2]=nOa|0;a[e+12744>>2]=lOa|0;a[e+12756>>2]=kOa|0;a[e+12768>>2]=jOa|0;a[e+12780>>2]=iOa|0;a[e+12792>>2]=hOa|0;a[e+12804>>2]=fOa|0;a[e+12816>>2]=eOa|0;a[e+12828>>2]=dOa|0;a[e+12840>>2]=cOa|0;a[e+12852>>2]=$Na|0;a[e+12864>>2]=YNa|0;a[e+12876>>2]=XNa|0;a[e+12888>>2]=WNa|0;a[e+12900>>2]=VNa|0;a[e+12912>>2]=UNa|0;a[e+12924>>2]=SNa|0;a[e+12936>>2]=RNa|0;a[e+12948>>2]=QNa|0;a[e+12960>>2]=PNa|0;a[e+12972>>2]=NNa|0;a[e+12984>>2]=LNa|0;a[e+12996>>2]=KNa|0;a[e+13008>>2]=JNa|0;a[e+13020>>2]=INa|0;a[e+13032>>2]=HNa|0;a[e+13044>>2]=FNa|0;a[e+13056>>2]=ENa|0;a[e+13068>>2]=DNa|0;a[e+13080>>2]=CNa|0;a[e+13092>>2]=ANa|0;a[e+13104>>2]=yNa|0;a[e+13116>>2]=xNa|0;a[e+13128>>2]=wNa|0;a[e+13140>>2]=vNa|0;a[e+13152>>2]=uNa|0;a[e+13164>>2]=sNa|0;a[e+13176>>2]=rNa|0;a[e+13188>>2]=qNa|0;a[e+13200>>2]=pNa|0;a[e+13212>>2]=nNa|0;a[e+13224>>2]=lNa|0;a[e+13236>>2]=kNa|0;a[e+13248>>2]=jNa|0;a[e+13260>>2]=iNa|0;a[e+13272>>2]=hNa|0;a[e+13284>>2]=fNa|0;a[e+13296>>2]=eNa|0;a[e+13308>>2]=dNa|0;a[e+13320>>2]=cNa|0;a[e+13332>>2]=$Ma|0;a[e+13344>>2]=XMa|0;a[e+13356>>2]=VMa|0;a[e+13368>>2]=UMa|0;a[e+13380>>2]=TMa|0;a[e+13392>>2]=SMa|0;a[e+13404>>2]=QMa|0;a[e+13416>>2]=PMa|0;a[e+13428>>2]=OMa|0;a[e+13440>>2]=MMa|0;a[e+13452>>2]=KMa|0;a[e+13464>>2]=IMa|0;a[e+13476>>2]=HMa|0;a[e+13488>>2]=GMa|0;a[e+13500>>2]=FMa|0;a[e+13512>>2]=EMa|0;a[e+13524>>2]=CMa|0;a[e+13536>>2]=BMa|0;a[e+13548>>2]=AMa|0;a[e+13560>>2]=zMa|0;a[e+13572>>2]=xMa|0;a[e+13584>>2]=vMa|0;a[e+13596>>2]=uMa|0;a[e+13608>>2]=tMa|0;a[e+13620>>2]=sMa|0;a[e+13632>>2]=rMa|0;a[e+13644>>2]=pMa|0;a[e+13656>>2]=nMa|0;a[e+13668>>2]=mMa|0;a[e+13680>>2]=lMa|0;a[e+13692>>2]=kMa|0;a[e+13704>>2]=iMa|0;a[e+13716>>2]=hMa|0;a[e+13728>>2]=gMa|0;a[e+13740>>2]=fMa|0;a[e+13752>>2]=eMa|0;a[e+13764>>2]=cMa|0;a[e+13776>>2]=aMa|0;a[e+13788>>2]=$La|0;a[e+13800>>2]=ZLa|0;a[e+13812>>2]=XLa|0;a[e+13824>>2]=VLa|0;a[e+13836>>2]=ULa|0;a[e+13848>>2]=TLa|0;a[e+13860>>2]=SLa|0;a[e+13872>>2]=RLa|0;a[e+13884>>2]=PLa|0;a[e+13896>>2]=NLa|0;a[e+13908>>2]=MLa|0;a[e+13920>>2]=LLa|0;a[e+13932>>2]=JLa|0;a[e+13944>>2]=GLa|0;a[e+13956>>2]=FLa|0;a[e+13968>>2]=ELa|0;a[e+13980>>2]=DLa|0;a[e+13992>>2]=CLa|0;a[e+14004>>2]=ALa|0;a[e+14016>>2]=yLa|0;a[e+14028>>2]=xLa|0;a[e+14040>>2]=wLa|0;a[e+14052>>2]=uLa|0;a[e+14064>>2]=sLa|0;a[e+14076>>2]=rLa|0;a[e+14088>>2]=qLa|0;a[e+14100>>2]=pLa|0;a[e+14112>>2]=oLa|0;a[e+14124>>2]=mLa|0;a[e+14136>>2]=kLa|0;a[e+14148>>2]=jLa|0;a[e+14160>>2]=iLa|0;a[e+14172>>2]=hLa|0;a[e+14184>>2]=fLa|0;a[e+14196>>2]=eLa|0;a[e+14208>>2]=dLa|0;a[e+14220>>2]=cLa|0;a[e+14232>>2]=bLa|0;a[e+14244>>2]=$Ka|0;a[e+14256>>2]=YKa|0;a[e+14268>>2]=XKa|0;a[e+14280>>2]=WKa|0;a[e+14292>>2]=VKa|0;a[e+14304>>2]=TKa|0;a[e+14316>>2]=SKa|0;a[e+14328>>2]=RKa|0;a[e+14340>>2]=QKa|0;a[e+14352>>2]=PKa|0;a[e+14364>>2]=NKa|0;a[e+14376>>2]=LKa|0;a[e+14388>>2]=KKa|0;a[e+14400>>2]=JKa|0;a[e+14412>>2]=IKa|0;a[e+14424>>2]=GKa|0;a[e+14436>>2]=FKa|0;a[e+14448>>2]=EKa|0;a[e+14460>>2]=DKa|0;a[e+14472>>2]=CKa|0;a[e+14484>>2]=AKa|0;a[e+14496>>2]=zKa|0;a[e+14508>>2]=yKa|0;a[e+14520>>2]=xKa|0;a[e+14532>>2]=uKa|0;a[e+14544>>2]=sKa|0;a[e+14556>>2]=rKa|0;a[e+14568>>2]=qKa|0;a[e+14580>>2]=pKa|0;a[e+14592>>2]=oKa|0;a[e+14604>>2]=mKa|0;a[e+14616>>2]=kKa|0;a[e+14628>>2]=jKa|0;a[e+14640>>2]=iKa|0;a[e+14652>>2]=hKa|0;a[e+14664>>2]=fKa|0;a[e+14676>>2]=eKa|0;a[e+14688>>2]=dKa|0;a[e+14700>>2]=cKa|0;a[e+14712>>2]=bKa|0;a[e+14724>>2]=$Ja|0;a[e+14736>>2]=YJa|0;a[e+14748>>2]=XJa|0;a[e+14760>>2]=WJa|0;a[e+14772>>2]=VJa|0;a[e+14784>>2]=TJa|0;a[e+14796>>2]=SJa|0;a[e+14808>>2]=QJa|0;a[e+14820>>2]=PJa|0;a[e+14832>>2]=OJa|0;a[e+14844>>2]=MJa|0;a[e+14856>>2]=KJa|0;a[e+14868>>2]=JJa|0;a[e+14880>>2]=IJa|0;a[e+14892>>2]=GJa|0;a[e+14904>>2]=EJa|0;a[e+14916>>2]=DJa|0;a[e+14928>>2]=CJa|0;a[e+14940>>2]=BJa|0;a[e+14952>>2]=AJa|0;a[e+14964>>2]=yJa|0;a[e+14976>>2]=uJa|0;a[e+14988>>2]=tJa|0;a[e+15e3>>2]=sJa|0;a[e+15012>>2]=rJa|0;a[e+15024>>2]=pJa|0;a[e+15036>>2]=oJa|0;a[e+15048>>2]=nJa|0;a[e+15060>>2]=mJa|0;a[e+15072>>2]=lJa|0;a[e+15084>>2]=jJa|0;a[e+15096>>2]=hJa|0;a[e+15108>>2]=gJa|0;a[e+15120>>2]=fJa|0;a[e+15132>>2]=dJa|0;a[e+15144>>2]=bJa|0;a[e+15156>>2]=aJa|0;a[e+15168>>2]=$Ia|0;a[e+15180>>2]=ZIa|0;a[e+15192>>2]=YIa|0;a[e+15204>>2]=WIa|0;a[e+15216>>2]=UIa|0;a[e+15228>>2]=TIa|0;a[e+15240>>2]=RIa|0;a[e+15252>>2]=PIa|0;a[e+15264>>2]=OIa|0;a[e+15276>>2]=NIa|0;a[e+15288>>2]=MIa|0;a[e+15300>>2]=LIa|0;a[e+15312>>2]=KIa|0;a[e+15324>>2]=IIa|0;a[e+15336>>2]=GIa|0;a[e+15348>>2]=FIa|0;a[e+15360>>2]=EIa|0;a[e+15372>>2]=DIa|0;a[e+15384>>2]=BIa|0;a[e+15396>>2]=AIa|0;a[e+15408>>2]=zIa|0;a[e+15420>>2]=yIa|0;a[e+15432>>2]=xIa|0;a[e+15444>>2]=vIa|0;a[e+15456>>2]=tIa|0;a[e+15468>>2]=sIa|0;a[e+15480>>2]=rIa|0;a[e+15492>>2]=qIa|0;a[e+15504>>2]=oIa|0;a[e+15516>>2]=nIa|0;a[e+15528>>2]=mIa|0;a[e+15540>>2]=lIa|0;a[e+15552>>2]=kIa|0;a[e+15564>>2]=iIa|0;a[e+15576>>2]=fIa|0;a[e+15588>>2]=eIa|0;a[e+15600>>2]=dIa|0;a[e+15612>>2]=bIa|0;a[e+15624>>2]=$Ha|0;a[e+15636>>2]=ZHa|0;a[e+15648>>2]=YHa|0;a[e+15660>>2]=XHa|0;a[e+15672>>2]=WHa|0;a[e+15684>>2]=UHa|0;a[e+15696>>2]=SHa|0;a[e+15708>>2]=RHa|0;a[e+15720>>2]=QHa|0;a[e+15732>>2]=OHa|0;a[e+15744>>2]=MHa|0;a[e+15756>>2]=LHa|0;a[e+15768>>2]=KHa|0;a[e+15780>>2]=JHa|0;a[e+15792>>2]=IHa|0;a[e+15804>>2]=GHa|0;a[e+15816>>2]=EHa|0;a[e+15828>>2]=DHa|0;a[e+15840>>2]=CHa|0;a[e+15852>>2]=BHa|0;a[e+15864>>2]=zHa|0;a[e+15876>>2]=yHa|0;a[e+15888>>2]=wHa|0;a[e+15900>>2]=vHa|0;a[e+15912>>2]=uHa|0;a[e+15924>>2]=sHa|0;a[e+15936>>2]=qHa|0;a[e+15948>>2]=pHa|0;a[e+15960>>2]=oHa|0;a[e+15972>>2]=nHa|0;a[e+15984>>2]=lHa|0;a[e+15996>>2]=kHa|0;a[e+16008>>2]=jHa|0;a[e+16020>>2]=iHa|0;a[e+16032>>2]=hHa|0;a[e+16044>>2]=fHa|0;a[e+16056>>2]=dHa|0;a[e+16068>>2]=cHa|0;a[e+16080>>2]=bHa|0;a[e+16092>>2]=aHa|0;a[e+16104>>2]=ZGa|0;a[e+16116>>2]=YGa|0;a[e+16128>>2]=XGa|0;a[e+16140>>2]=WGa|0;a[e+16152>>2]=VGa|0;a[e+16164>>2]=TGa|0;a[e+16176>>2]=RGa|0;a[e+16188>>2]=QGa|0;a[e+16200>>2]=PGa|0;a[e+16212>>2]=OGa|0;a[e+16224>>2]=MGa|0;a[e+16236>>2]=LGa|0;a[e+16248>>2]=KGa|0;a[e+16260>>2]=IGa|0;a[e+16272>>2]=HGa|0;a[e+16284>>2]=FGa|0;a[e+16296>>2]=DGa|0;a[e+16308>>2]=CGa|0;a[e+16320>>2]=AGa|0;a[e+16332>>2]=zGa|0;a[e+16344>>2]=vGa|0;a[e+16356>>2]=uGa|0;a[e+16368>>2]=tGa|0;a[e+16380>>2]=rGa|0;a[e+16392>>2]=qGa|0;a[e+16404>>2]=oGa|0;a[e+16416>>2]=mGa|0;a[e+16428>>2]=lGa|0;a[e+16440>>2]=kGa|0;a[e+16452>>2]=jGa|0;a[e+16464>>2]=gGa|0;a[e+16476>>2]=fGa|0;a[e+16488>>2]=eGa|0;a[e+16500>>2]=cGa|0;a[e+16512>>2]=bGa|0;a[e+16524>>2]=$Fa|0;a[e+16536>>2]=YFa|0;a[e+16548>>2]=XFa|0;a[e+16560>>2]=WFa|0;a[e+16572>>2]=UFa|0;a[e+16584>>2]=SFa|0;a[e+16596>>2]=RFa|0;a[e+16608>>2]=QFa|0;a[e+16620>>2]=PFa|0;a[e+16632>>2]=OFa|0;a[e+16644>>2]=MFa|0;a[e+16656>>2]=KFa|0;a[e+16668>>2]=JFa|0;a[e+16680>>2]=IFa|0;a[e+16692>>2]=HFa|0;a[e+16704>>2]=FFa|0;a[e+16716>>2]=EFa|0;a[e+16728>>2]=DFa|0;a[e+16740>>2]=CFa|0;a[e+16752>>2]=BFa|0;a[e+16764>>2]=zFa|0;a[e+16776>>2]=xFa|0;a[e+16788>>2]=wFa|0;a[e+16800>>2]=vFa|0;a[e+16812>>2]=uFa|0;a[e+16824>>2]=sFa|0;a[e+16836>>2]=rFa|0;a[e+16848>>2]=qFa|0;a[e+16860>>2]=oFa|0;a[e+16872>>2]=nFa|0;a[e+16884>>2]=lFa|0;a[e+16896>>2]=jFa|0;a[e+16908>>2]=iFa|0;a[e+16920>>2]=hFa|0;a[e+16932>>2]=fFa|0;a[e+16944>>2]=dFa|0;a[e+16956>>2]=cFa|0;a[e+16968>>2]=bFa|0;a[e+16980>>2]=aFa|0;a[e+16992>>2]=$Ea|0;a[e+17004>>2]=YEa|0;a[e+17016>>2]=WEa|0;a[e+17028>>2]=VEa|0;a[e+17040>>2]=UEa|0;a[e+17052>>2]=TEa|0;a[e+17064>>2]=SEa|0;a[e+17076>>2]=REa|0;a[e+17088>>2]=PEa|0;a[e+17100>>2]=OEa|0;a[e+17112>>2]=NEa|0;a[e+17124>>2]=LEa|0;a[e+17136>>2]=JEa|0;a[e+17148>>2]=HEa|0;a[e+17160>>2]=GEa|0;a[e+17172>>2]=FEa|0;a[e+17184>>2]=DEa|0;a[e+17196>>2]=CEa|0;a[e+17208>>2]=BEa|0;a[e+17220>>2]=AEa|0;a[e+17232>>2]=zEa|0;a[e+17244>>2]=xEa|0;a[e+17256>>2]=vEa|0;a[e+17268>>2]=uEa|0;a[e+17280>>2]=tEa|0;a[e+17292>>2]=sEa|0;a[e+17304>>2]=qEa|0;a[e+17316>>2]=pEa|0;a[e+17328>>2]=oEa|0;a[e+17340>>2]=nEa|0;a[e+17352>>2]=mEa|0;a[e+17364>>2]=kEa|0;a[e+17376>>2]=iEa|0;a[e+17388>>2]=hEa|0;a[e+17400>>2]=gEa|0;a[e+17412>>2]=fEa|0;a[e+17424>>2]=eEa|0;a[e+17436>>2]=dEa|0;a[e+17448>>2]=cEa|0;a[e+17460>>2]=bEa|0;a[e+17472>>2]=aEa|0;a[e+17484>>2]=ZDa|0;a[e+17496>>2]=XDa|0;a[e+17508>>2]=WDa|0;a[e+17520>>2]=VDa|0;a[e+17532>>2]=UDa|0;a[e+17544>>2]=RDa|0;a[e+17556>>2]=QDa|0;a[e+17568>>2]=PDa|0;a[e+17580>>2]=ODa|0;a[e+17592>>2]=NDa|0;a[e+17604>>2]=LDa|0;a[e+17616>>2]=JDa|0;a[e+17628>>2]=IDa|0;a[e+17640>>2]=HDa|0;a[e+17652>>2]=GDa|0;a[e+17664>>2]=EDa|0;a[e+17676>>2]=DDa|0;a[e+17688>>2]=CDa|0;a[e+17700>>2]=BDa|0;a[e+17712>>2]=ADa|0;a[e+17724>>2]=yDa|0;a[e+17736>>2]=wDa|0;a[e+17748>>2]=vDa|0;a[e+17760>>2]=uDa|0;a[e+17772>>2]=tDa|0;a[e+17784>>2]=rDa|0;a[e+17796>>2]=qDa|0;a[e+17808>>2]=pDa|0;a[e+17820>>2]=oDa|0;a[e+17832>>2]=nDa|0;a[e+17844>>2]=lDa|0;a[e+17856>>2]=jDa|0;a[e+17868>>2]=iDa|0;a[e+17880>>2]=hDa|0;a[e+17892>>2]=gDa|0;a[e+17904>>2]=dDa|0;a[e+17916>>2]=cDa|0;a[e+17928>>2]=bDa|0;a[e+17940>>2]=aDa|0;a[e+17952>>2]=$Ca|0;a[e+17964>>2]=YCa|0;a[e+17976>>2]=VCa|0;a[e+17988>>2]=UCa|0;a[e+18e3>>2]=TCa|0;a[e+18012>>2]=SCa|0;a[e+18024>>2]=RCa|0;a[e+18036>>2]=QCa|0;a[e+18048>>2]=PCa|0;a[e+18060>>2]=OCa|0;a[e+18072>>2]=NCa|0;a[e+18084>>2]=LCa|0;a[e+18096>>2]=JCa|0;a[e+18108>>2]=ICa|0;a[e+18120>>2]=HCa|0;a[e+18132>>2]=FCa|0;a[e+18144>>2]=DCa|0;a[e+18156>>2]=CCa|0;a[e+18168>>2]=BCa|0;a[e+18180>>2]=ACa|0;a[e+18192>>2]=zCa|0;a[e+18204>>2]=xCa|0;a[e+18216>>2]=vCa|0;a[e+18228>>2]=uCa|0;a[e+18240>>2]=tCa|0;a[e+18252>>2]=sCa|0;a[e+18264>>2]=rCa|0;a[e+18276>>2]=qCa|0;a[e+18288>>2]=pCa|0;a[e+18300>>2]=oCa|0;a[e+18312>>2]=nCa|0;a[e+18324>>2]=lCa|0;a[e+18336>>2]=jCa|0;a[e+18348>>2]=iCa|0;a[e+18360>>2]=hCa|0;a[e+18372>>2]=gCa|0;a[e+18384>>2]=fCa|0;a[e+18396>>2]=eCa|0;a[e+18408>>2]=cCa|0;a[e+18420>>2]=bCa|0;a[e+18432>>2]=aCa|0;a[e+18444>>2]=ZBa|0;a[e+18456>>2]=XBa|0;a[e+18468>>2]=WBa|0;a[e+18480>>2]=VBa|0;a[e+18492>>2]=UBa|0;a[e+18504>>2]=TBa|0;a[e+18516>>2]=SBa|0;a[e+18528>>2]=RBa|0;a[e+18540>>2]=QBa|0;a[e+18552>>2]=PBa|0;a[e+18564>>2]=NBa|0;a[e+18576>>2]=LBa|0;a[e+18588>>2]=KBa|0;a[e+18600>>2]=JBa|0;a[e+18612>>2]=IBa|0;a[e+18624>>2]=HBa|0;a[e+18636>>2]=GBa|0;a[e+18648>>2]=FBa|0;a[e+18660>>2]=EBa|0;a[e+18672>>2]=DBa|0;a[e+18684>>2]=BBa|0;a[e+18696>>2]=zBa|0;a[e+18708>>2]=yBa|0;a[e+18720>>2]=xBa|0;a[e+18732>>2]=wBa|0;a[e+18744>>2]=uBa|0;a[e+18756>>2]=tBa|0;a[e+18768>>2]=sBa|0;a[e+18780>>2]=rBa|0;a[e+18792>>2]=qBa|0;a[e+18804>>2]=oBa|0;a[e+18816>>2]=mBa|0;a[e+18828>>2]=lBa|0;a[e+18840>>2]=kBa|0;a[e+18852>>2]=jBa|0;a[e+18864>>2]=hBa|0;a[e+18876>>2]=gBa|0;a[e+18888>>2]=fBa|0;a[e+18900>>2]=eBa|0;a[e+18912>>2]=dBa|0;a[e+18924>>2]=bBa|0;a[e+18936>>2]=$Aa|0;a[e+18948>>2]=ZAa|0;a[e+18960>>2]=YAa|0;a[e+18972>>2]=XAa|0;a[e+18984>>2]=WAa|0;a[e+18996>>2]=VAa|0;a[e+19008>>2]=UAa|0;a[e+19020>>2]=TAa|0;a[e+19032>>2]=SAa|0;a[e+19044>>2]=QAa|0;a[e+19056>>2]=OAa|0;a[e+19068>>2]=NAa|0;a[e+19080>>2]=MAa|0;a[e+19092>>2]=LAa|0;a[e+19104>>2]=KAa|0;a[e+19116>>2]=JAa|0;a[e+19128>>2]=IAa|0;a[e+19140>>2]=HAa|0;a[e+19152>>2]=GAa|0;a[e+19164>>2]=EAa|0;a[e+19176>>2]=BAa|0;a[e+19188>>2]=AAa|0;a[e+19200>>2]=zAa|0;a[e+19212>>2]=yAa|0;a[e+19224>>2]=wAa|0;a[e+19236>>2]=vAa|0;a[e+19248>>2]=uAa|0;a[e+19260>>2]=tAa|0;a[e+19272>>2]=sAa|0;a[e+19284>>2]=qAa|0;a[e+19296>>2]=oAa|0;a[e+19308>>2]=nAa|0;a[e+19320>>2]=mAa|0;a[e+19332>>2]=kAa|0;a[e+19344>>2]=jAa|0;a[e+19356>>2]=iAa|0;a[e+19368>>2]=hAa|0;a[e+19380>>2]=gAa|0;a[e+19392>>2]=fAa|0;a[e+19404>>2]=cAa|0;a[e+19416>>2]=aAa|0;a[e+19428>>2]=$za|0;a[e+19440>>2]=Zza|0;a[e+19452>>2]=Yza|0;a[e+19464>>2]=Xza|0;a[e+19476>>2]=Wza|0;a[e+19488>>2]=Vza|0;a[e+19500>>2]=Uza|0;a[e+19512>>2]=Tza|0;a[e+19524>>2]=Rza|0;a[e+19536>>2]=Pza|0;a[e+19548>>2]=Oza|0;a[e+19560>>2]=Nza|0;a[e+19572>>2]=Mza|0;a[e+19584>>2]=Lza|0;a[e+19596>>2]=Kza|0;a[e+19608>>2]=Jza|0;a[e+19620>>2]=Iza|0;a[e+19632>>2]=Hza|0;a[e+19644>>2]=Fza|0;a[e+19656>>2]=Eza|0;a[e+19668>>2]=Dza|0;a[e+19680>>2]=Cza|0;a[e+19692>>2]=Bza|0;a[e+19704>>2]=Aza|0;a[e+19716>>2]=zza|0;a[e+19728>>2]=yza|0;a[e+19740>>2]=xza|0;a[e+19752>>2]=wza|0;a[e+19764>>2]=uza|0;a[e+19776>>2]=sza|0;a[e+19788>>2]=rza|0;a[e+19800>>2]=qza|0;a[e+19812>>2]=pza|0;a[e+19824>>2]=oza|0;a[e+19836>>2]=nza|0;a[e+19848>>2]=mza|0;a[e+19860>>2]=lza|0;a[e+19872>>2]=kza|0;a[e+19884>>2]=iza|0;a[e+19896>>2]=gza|0;a[e+19908>>2]=fza|0;a[e+19920>>2]=eza|0;a[e+19932>>2]=dza|0;a[e+19944>>2]=aza|0;a[e+19956>>2]=$ya|0;a[e+19968>>2]=Zya|0;a[e+19980>>2]=Yya|0;a[e+19992>>2]=Xya|0;a[e+20004>>2]=Vya|0;a[e+20016>>2]=Tya|0;a[e+20028>>2]=Sya|0;a[e+20040>>2]=Rya|0;a[e+20052>>2]=Qya|0;a[e+20064>>2]=Oya|0;a[e+20076>>2]=Nya|0;a[e+20088>>2]=Mya|0;a[e+20100>>2]=Lya|0;a[e+20112>>2]=Kya|0;a[e+20124>>2]=Iya|0;a[e+20136>>2]=Gya|0;a[e+20148>>2]=Fya|0;a[e+20160>>2]=Eya|0;a[e+20172>>2]=Dya|0;a[e+20184>>2]=Bya|0;a[e+20196>>2]=Aya|0;a[e+20208>>2]=zya|0;a[e+20220>>2]=yya|0;a[e+20232>>2]=xya|0;a[e+20244>>2]=vya|0;a[e+20256>>2]=tya|0;a[e+20268>>2]=sya|0;a[e+20280>>2]=rya|0;a[e+20292>>2]=qya|0;a[e+20304>>2]=nya|0;a[e+20316>>2]=mya|0;a[e+20328>>2]=lya|0;a[e+20340>>2]=kya|0;a[e+20352>>2]=jya|0;a[e+20364>>2]=hya|0;a[e+20376>>2]=eya|0;a[e+20388>>2]=dya|0;a[e+20400>>2]=cya|0;a[e+20412>>2]=bya|0;a[e+20424>>2]=Zxa|0;a[e+20436>>2]=Yxa|0;a[e+20448>>2]=Xxa|0;a[e+20460>>2]=Wxa|0;a[e+20472>>2]=Vxa|0;a[e+20484>>2]=Txa|0;a[e+20496>>2]=Rxa|0;a[e+20508>>2]=Qxa|0;a[e+20520>>2]=Pxa|0;a[e+20532>>2]=Oxa|0;a[e+20544>>2]=Lxa|0;a[e+20556>>2]=Kxa|0;a[e+20568>>2]=Jxa|0;a[e+20580>>2]=Ixa|0;a[e+20592>>2]=Hxa|0;a[e+20604>>2]=Fxa|0;a[e+20616>>2]=Dxa|0;a[e+20628>>2]=Cxa|0;a[e+20640>>2]=Bxa|0;a[e+20652>>2]=Axa|0;a[e+20664>>2]=xxa|0;a[e+20676>>2]=wxa|0;a[e+20688>>2]=vxa|0;a[e+20700>>2]=uxa|0;a[e+20712>>2]=txa|0;a[e+20724>>2]=rxa|0;a[e+20736>>2]=pxa|0;a[e+20748>>2]=oxa|0;a[e+20760>>2]=nxa|0;a[e+20772>>2]=mxa|0;a[e+20784>>2]=jxa|0;a[e+20796>>2]=ixa|0;a[e+20808>>2]=hxa|0;a[e+20820>>2]=gxa|0;a[e+20832>>2]=fxa|0;a[e+20844>>2]=dxa|0;a[e+20856>>2]=bxa|0;a[e+20868>>2]=axa|0;a[e+20880>>2]=Zwa|0;a[e+20892>>2]=Ywa|0;a[e+20904>>2]=Vwa|0;a[e+20916>>2]=Uwa|0;a[e+20928>>2]=Twa|0;a[e+20940>>2]=Swa|0;a[e+20952>>2]=Rwa|0;a[e+20964>>2]=Pwa|0;a[e+20976>>2]=Nwa|0;a[e+20988>>2]=Mwa|0;a[e+21e3>>2]=Lwa|0;a[e+21012>>2]=Kwa|0;a[e+21024>>2]=Hwa|0;a[e+21036>>2]=Gwa|0;a[e+21048>>2]=Fwa|0;a[e+21060>>2]=Ewa|0;a[e+21072>>2]=Dwa|0;a[e+21084>>2]=Bwa|0;a[e+21096>>2]=zwa|0;a[e+21108>>2]=ywa|0;a[e+21120>>2]=xwa|0;a[e+21132>>2]=wwa|0;a[e+21144>>2]=twa|0;a[e+21156>>2]=swa|0;a[e+21168>>2]=rwa|0;a[e+21180>>2]=qwa|0;a[e+21192>>2]=pwa|0;a[e+21204>>2]=nwa|0;a[e+21216>>2]=lwa|0;a[e+21228>>2]=kwa|0;a[e+21240>>2]=jwa|0;a[e+21252>>2]=iwa|0;a[e+21264>>2]=gwa|0;a[e+21276>>2]=fwa|0;a[e+21288>>2]=ewa|0;a[e+21300>>2]=dwa|0;a[e+21312>>2]=cwa|0;a[e+21324>>2]=awa|0;a[e+21336>>2]=Zva|0;a[e+21348>>2]=Yva|0;a[e+21360>>2]=Xva|0;a[e+21372>>2]=Wva|0;a[e+21384>>2]=Vva|0;a[e+21396>>2]=Uva|0;a[e+21408>>2]=Tva|0;a[e+21420>>2]=Sva|0;a[e+21432>>2]=Rva|0;a[e+21444>>2]=Pva|0;a[e+21456>>2]=Nva|0;a[e+21468>>2]=Mva|0;a[e+21480>>2]=Lva|0;a[e+21492>>2]=Kva|0;a[e+21504>>2]=Iva|0;a[e+21516>>2]=Hva|0;a[e+21528>>2]=Gva|0;a[e+21540>>2]=Fva|0;a[e+21552>>2]=Eva|0;a[e+21564>>2]=Cva|0;a[e+21576>>2]=zva|0;a[e+21588>>2]=yva|0;a[e+21600>>2]=xva|0;a[e+21612>>2]=wva|0;a[e+21624>>2]=uva|0;a[e+21636>>2]=tva|0;a[e+21648>>2]=sva|0;a[e+21660>>2]=rva|0;a[e+21672>>2]=qva|0;a[e+21684>>2]=ova|0;a[e+21696>>2]=mva|0;a[e+21708>>2]=lva|0;a[e+21720>>2]=kva|0;a[e+21732>>2]=jva|0;a[e+21744>>2]=gva|0;a[e+21756>>2]=fva|0;a[e+21768>>2]=eva|0;a[e+21780>>2]=dva|0;a[e+21792>>2]=cva|0;a[e+21804>>2]=ava|0;a[e+21816>>2]=Zua|0;a[e+21828>>2]=Yua|0;a[e+21840>>2]=Xua|0;a[e+21852>>2]=Wua|0;a[e+21864>>2]=Uua|0;a[e+21876>>2]=Tua|0;a[e+21888>>2]=Sua|0;a[e+21900>>2]=Rua|0;a[e+21912>>2]=Qua|0;a[e+21924>>2]=Oua|0;a[e+21936>>2]=Mua|0;a[e+21948>>2]=Lua|0;a[e+21960>>2]=Kua|0;a[e+21972>>2]=Jua|0;a[e+21984>>2]=Hua|0;a[e+21996>>2]=Gua|0;a[e+22008>>2]=Fua|0;a[e+22020>>2]=Dua|0;a[e+22032>>2]=IA|0;a[e+22044>>2]=AA|0;a[e+22056>>2]=Aua|0;a[e+22068>>2]=zua|0;a[e+22080>>2]=yua|0;a[e+22092>>2]=xua|0;a[e+22104>>2]=Xz|0;a[e+22116>>2]=wua|0;a[e+22128>>2]=vua|0;a[e+22140>>2]=uua|0;a[e+22152>>2]=tua|0;a[e+22164>>2]=Mz|0;a[e+22176>>2]=qua|0;a[e+22188>>2]=pua|0;a[e+22200>>2]=oua|0;a[e+22212>>2]=nua|0;a[e+22224>>2]=Bz|0;a[e+22236>>2]=qz|0;a[e+22248>>2]=mua|0;a[e+22260>>2]=lua|0;a[e+22272>>2]=kua|0;a[e+22284>>2]=jua|0;a[e+22296>>2]=Ac|0;a[e+22308>>2]=SB|0;a[e+22320>>2]=Zj|0;a[e+22332>>2]=gua|0;a[e+22344>>2]=eua|0;a[e+22356>>2]=dua|0;a[e+22368>>2]=cua|0;a[e+22380>>2]=wB|0;a[e+22392>>2]=mB|0;a[e+22404>>2]=bua|0;a[e+22416>>2]=Zta|0;a[e+22428>>2]=Yta|0;a[e+22440>>2]=Xta|0;a[e+22452>>2]=bB|0;a[e+22464>>2]=Vta|0;a[e+22476>>2]=Uta|0;a[e+22488>>2]=Tta|0;a[e+22500>>2]=Sta|0;a[e+22512>>2]=WA|0;a[e+22524>>2]=Rta|0;a[e+22536>>2]=Ota|0;a[e+22548>>2]=Nta|0;a[e+22560>>2]=Mta|0;a[e+22572>>2]=SA|0;a[e+22584>>2]=Lta|0;a[e+22596>>2]=Kta|0;a[e+22608>>2]=Jta|0;a[e+22620>>2]=Ita|0;a[e+22632>>2]=QA|0;a[e+22644>>2]=Hta|0;a[e+22656>>2]=Eta|0;a[e+22668>>2]=Dta|0;a[e+22680>>2]=Cta|0;a[e+22692>>2]=OA|0;a[e+22704>>2]=Bta|0;a[e+22716>>2]=Ata|0;a[e+22728>>2]=zta|0;a[e+22740>>2]=yta|0;a[e+22752>>2]=NA|0;a[e+22764>>2]=LA|0;a[e+22776>>2]=vta|0;a[e+22788>>2]=uta|0;a[e+22800>>2]=tta|0;a[e+22812>>2]=sta|0;a[e+22824>>2]=JA|0;a[e+22836>>2]=gn|0;a[e+22848>>2]=rta|0;a[e+22860>>2]=qta|0;a[e+22872>>2]=pta|0;a[e+22884>>2]=ota|0;a[e+22896>>2]=EA|0;a[e+22908>>2]=mta|0;a[e+22920>>2]=lta|0;a[e+22932>>2]=kta|0;a[e+22944>>2]=ita|0;a[e+22956>>2]=DA|0;a[e+22968>>2]=CA|0;a[e+22980>>2]=BA|0;a[e+22992>>2]=hta|0;a[e+23004>>2]=gta|0;a[e+23016>>2]=dta|0;a[e+23028>>2]=cta|0;a[e+23040>>2]=zA|0;a[e+23052>>2]=bta|0;a[e+23064>>2]=ata|0;a[e+23076>>2]=$sa|0;a[e+23088>>2]=Zsa|0;a[e+23100>>2]=xA|0;a[e+23112>>2]=Ysa|0;a[e+23124>>2]=Xsa|0;a[e+23136>>2]=Usa|0;a[e+23148>>2]=Tsa|0;a[e+23160>>2]=wA|0;a[e+23172>>2]=uA|0;a[e+23184>>2]=Ssa|0;a[e+23196>>2]=Rsa|0;a[e+23208>>2]=Qsa|0;a[e+23220>>2]=Osa|0;a[e+23232>>2]=sA|0;a[e+23244>>2]=rA|0;a[e+23256>>2]=Lsa|0;a[e+23268>>2]=Ksa|0;a[e+23280>>2]=Jsa|0;a[e+23292>>2]=Isa|0;a[e+23304>>2]=qA|0;a[e+23316>>2]=pA|0;a[e+23328>>2]=nA|0;a[e+23340>>2]=mA|0;a[e+23352>>2]=Hsa|0;a[e+23364>>2]=Gsa|0;a[e+23376>>2]=Dsa|0;a[e+23388>>2]=Csa|0;a[e+23400>>2]=lA|0;a[e+23412>>2]=Bsa|0;a[e+23424>>2]=zsa|0;a[e+23436>>2]=xsa|0;a[e+23448>>2]=wsa|0;a[e+23460>>2]=kA|0;a[e+23472>>2]=hA|0;a[e+23484>>2]=fA|0;a[e+23496>>2]=tsa|0;a[e+23508>>2]=ssa|0;a[e+23520>>2]=rsa|0;a[e+23532>>2]=qsa|0;a[e+23544>>2]=dA|0;a[e+23556>>2]=msa|0;a[e+23568>>2]=lsa|0;a[e+23580>>2]=ksa|0;a[e+23592>>2]=jsa|0;a[e+23604>>2]=cA|0;a[e+23616>>2]=aA|0;a[e+23628>>2]=Zz|0;a[e+23640>>2]=Wz|0;a[e+23652>>2]=Uz|0;a[e+23664>>2]=fsa|0;a[e+23676>>2]=esa|0;a[e+23688>>2]=dsa|0;a[e+23700>>2]=csa|0;a[e+23712>>2]=Tz|0;a[e+23724>>2]=bsa|0;a[e+23736>>2]=Zra|0;a[e+23748>>2]=Yra|0;a[e+23760>>2]=Xra|0;a[e+23772>>2]=ir|0;a[e+23784>>2]=Wra|0;a[e+23796>>2]=Vra|0;a[e+23808>>2]=Ura|0;a[e+23820>>2]=Tra|0;a[e+23832>>2]=Sra|0;a[e+23844>>2]=Rra|0;a[e+23856>>2]=Ora|0;a[e+23868>>2]=Nra|0;a[e+23880>>2]=Mra|0;a[e+23892>>2]=Lra|0;a[e+23904>>2]=Kra|0;a[e+23916>>2]=Jra|0;a[e+23928>>2]=Ira|0;a[e+23940>>2]=Hra|0;a[e+23952>>2]=Gra|0;a[e+23964>>2]=Fra|0;a[e+23976>>2]=Bra|0;a[e+23988>>2]=Ara|0;a[e+24e3>>2]=zra|0;a[e+24012>>2]=yra|0;a[e+24024>>2]=xra|0;a[e+24036>>2]=wra|0;a[e+24048>>2]=vra|0;a[e+24060>>2]=ura|0;a[e+24072>>2]=tra|0;a[e+24084>>2]=sra|0;a[e+24096>>2]=pra|0;a[e+24108>>2]=ora|0;a[e+24120>>2]=nra|0;a[e+24132>>2]=mra|0;a[e+24144>>2]=kra|0;a[e+24156>>2]=ira|0;a[e+24168>>2]=hra|0;a[e+24180>>2]=gra|0;a[e+24192>>2]=fra|0;a[e+24204>>2]=era|0;a[e+24216>>2]=bra|0;a[e+24228>>2]=ara|0;a[e+24240>>2]=$qa|0;a[e+24252>>2]=Zqa|0;a[e+24264>>2]=Yqa|0;a[e+24276>>2]=Xqa|0;a[e+24288>>2]=Wqa|0;a[e+24300>>2]=Vqa|0;a[e+24312>>2]=Uqa|0;a[e+24324>>2]=Tqa|0;a[e+24336>>2]=Qqa|0;a[e+24348>>2]=Pqa|0;a[e+24360>>2]=Oqa|0;a[e+24372>>2]=Nqa|0;a[e+24384>>2]=Mqa|0;a[e+24396>>2]=Lqa|0;a[e+24408>>2]=Kqa|0;a[e+24420>>2]=Jqa|0;a[e+24432>>2]=Iqa|0;a[e+24444>>2]=Hqa|0;a[e+24456>>2]=Eqa|0;a[e+24468>>2]=Dqa|0;a[e+24480>>2]=Cqa|0;a[e+24492>>2]=Bqa|0;a[e+24504>>2]=zqa|0;a[e+24516>>2]=yqa|0;a[e+24528>>2]=xqa|0;a[e+24540>>2]=wqa|0;a[e+24552>>2]=vqa|0;a[e+24564>>2]=uqa|0;a[e+24576>>2]=rqa|0;a[e+24588>>2]=qqa|0;a[e+24600>>2]=pqa|0;a[e+24612>>2]=oqa|0;a[e+24624>>2]=mqa|0;a[e+24636>>2]=kqa|0;a[e+24648>>2]=iqa|0;a[e+24660>>2]=hqa|0;a[e+24672>>2]=gqa|0;a[e+24684>>2]=fqa|0;a[e+24696>>2]=cqa|0;a[e+24708>>2]=bqa|0;a[e+24720>>2]=aqa|0;a[e+24732>>2]=$pa|0;a[e+24744>>2]=Xpa|0;a[e+24756>>2]=Vpa|0;a[e+24768>>2]=Upa|0;a[e+24780>>2]=Tpa|0;a[e+24792>>2]=Spa|0;a[e+24804>>2]=Rpa|0;a[e+24816>>2]=Opa|0;a[e+24828>>2]=Npa|0;a[e+24840>>2]=Mpa|0;a[e+24852>>2]=Lpa|0;a[e+24864>>2]=Ipa|0;a[e+24876>>2]=Gpa|0;a[e+24888>>2]=Fpa|0;a[e+24900>>2]=Epa|0;a[e+24912>>2]=Dpa|0;a[e+24924>>2]=Cpa|0;a[e+24936>>2]=zpa|0;a[e+24948>>2]=ypa|0;a[e+24960>>2]=xpa|0;a[e+24972>>2]=wpa|0;a[e+24984>>2]=upa|0;a[e+24996>>2]=Yj|0;a[e+25008>>2]=spa|0;a[e+25020>>2]=rpa|0;a[e+25032>>2]=qpa|0;a[e+25044>>2]=ppa|0;a[e+25056>>2]=Sz|0;a[e+25068>>2]=Rz|0;a[e+25080>>2]=mpa|0;a[e+25092>>2]=lpa|0;a[e+25104>>2]=jpa|0;a[e+25116>>2]=hpa|0;a[e+25128>>2]=gpa|0;a[e+25140>>2]=fpa|0;a[e+25152>>2]=epa|0;a[e+25164>>2]=dpa|0;a[e+25176>>2]=apa|0;a[e+25188>>2]=$oa|0;a[e+25200>>2]=Zoa|0;a[e+25212>>2]=Yoa|0;a[e+25224>>2]=Woa|0;a[e+25236>>2]=Uoa|0;a[e+25248>>2]=Toa|0;a[e+25260>>2]=Soa|0;a[e+25272>>2]=Roa|0;a[e+25284>>2]=Qoa|0;a[e+25296>>2]=Noa|0;a[e+25308>>2]=Moa|0;a[e+25320>>2]=Loa|0;a[e+25332>>2]=Koa|0;a[e+25344>>2]=Ioa|0;a[e+25356>>2]=Eoa|0;a[e+25368>>2]=Doa|0;a[e+25380>>2]=Coa|0;a[e+25392>>2]=Boa|0;a[e+25404>>2]=Aoa|0;a[e+25416>>2]=xoa|0;a[e+25428>>2]=woa|0;a[e+25440>>2]=voa|0;a[e+25452>>2]=uoa|0;a[e+25464>>2]=toa|0;a[e+25476>>2]=soa|0;a[e+25488>>2]=roa|0;a[e+25500>>2]=qoa|0;a[e+25512>>2]=poa|0;a[e+25524>>2]=ooa|0;a[e+25536>>2]=loa|0;a[e+25548>>2]=koa|0;a[e+25560>>2]=joa|0;a[e+25572>>2]=ioa|0;a[e+25584>>2]=goa|0;a[e+25596>>2]=foa|0;a[e+25608>>2]=eoa|0;a[e+25620>>2]=doa|0;a[e+25632>>2]=coa|0;a[e+25644>>2]=boa|0;a[e+25656>>2]=Zna|0;a[e+25668>>2]=Yna|0;a[e+25680>>2]=Xna|0;a[e+25692>>2]=Wna|0;a[e+25704>>2]=Una|0;a[e+25716>>2]=Tna|0;a[e+25728>>2]=Rna|0;a[e+25740>>2]=Qna|0;a[e+25752>>2]=Pna|0;a[e+25764>>2]=Ona|0;a[e+25776>>2]=Lna|0;a[e+25788>>2]=Kna|0;a[e+25800>>2]=Jna|0;a[e+25812>>2]=Ina|0;a[e+25824>>2]=Gna|0;a[e+25836>>2]=Fna|0;a[e+25848>>2]=Dna|0;a[e+25860>>2]=Cna|0;a[e+25872>>2]=Bna|0;a[e+25884>>2]=Ana|0;a[e+25896>>2]=xna|0;a[e+25908>>2]=wna|0;a[e+25920>>2]=vna|0;a[e+25932>>2]=tna|0;a[e+25944>>2]=qna|0;a[e+25956>>2]=ona|0;a[e+25968>>2]=nna|0;a[e+25980>>2]=mna|0;a[e+25992>>2]=lna|0;a[e+26004>>2]=kna|0;a[e+26016>>2]=hna|0;a[e+26028>>2]=gna|0;a[e+26040>>2]=fna|0;a[e+26052>>2]=ena|0;a[e+26064>>2]=bna|0;a[e+26076>>2]=ana|0;a[e+26088>>2]=$ma|0;a[e+26100>>2]=Zma|0;a[e+26112>>2]=Yma|0;a[e+26124>>2]=Xma|0;a[e+26136>>2]=Uma|0;a[e+26148>>2]=Tma|0;a[e+26160>>2]=Sma|0;a[e+26172>>2]=Rma|0;a[e+26184>>2]=Pma|0;a[e+26196>>2]=Oma|0;a[e+26208>>2]=Nma|0;a[e+26220>>2]=Mma|0;a[e+26232>>2]=Lma|0;a[e+26244>>2]=Kma|0;a[e+26256>>2]=Hma|0;a[e+26268>>2]=Gma|0;a[e+26280>>2]=Fma|0;a[e+26292>>2]=Pz|0;a[e+26304>>2]=Dma|0;a[e+26316>>2]=Cma|0;a[e+26328>>2]=Bma|0;a[e+26340>>2]=Ama|0;a[e+26352>>2]=Oz|0;a[e+26364>>2]=zma|0;a[e+26376>>2]=wma|0;a[e+26388>>2]=vma|0;a[e+26400>>2]=uma|0;a[e+26412>>2]=Nz|0;a[e+26424>>2]=sma|0;a[e+26436>>2]=rma|0;a[e+26448>>2]=qma|0;a[e+26460>>2]=pma|0;a[e+26472>>2]=Lz|0;a[e+26484>>2]=qh|0;a[e+26496>>2]=Kz|0;a[e+26508>>2]=mma|0;a[e+26520>>2]=lma|0;a[e+26532>>2]=kma|0;a[e+26544>>2]=hma|0;a[e+26556>>2]=Jz|0;a[e+26568>>2]=fma|0;a[e+26580>>2]=ema|0;a[e+26592>>2]=dma|0;a[e+26604>>2]=cma|0;a[e+26616>>2]=Iz|0;a[e+26628>>2]=Hz|0;a[e+26640>>2]=ama|0;a[e+26652>>2]=Zla|0;a[e+26664>>2]=Xla|0;a[e+26676>>2]=Wla|0;a[e+26688>>2]=Gz|0;a[e+26700>>2]=Fz|0;a[e+26712>>2]=Vla|0;a[e+26724>>2]=Ula|0;a[e+26736>>2]=Rla|0;a[e+26748>>2]=Pla|0;a[e+26760>>2]=Ez|0;a[e+26772>>2]=Ola|0;a[e+26784>>2]=Mla|0;a[e+26796>>2]=Lla|0;a[e+26808>>2]=Kla|0;a[e+26820>>2]=Dz|0;a[e+26832>>2]=Cz|0;a[e+26844>>2]=Ila|0;a[e+26856>>2]=Fla|0;a[e+26868>>2]=Ela|0;a[e+26880>>2]=Dla|0;a[e+26892>>2]=Cla|0;a[e+26904>>2]=Ala|0;a[e+26916>>2]=zla|0;a[e+26928>>2]=yla|0;a[e+26940>>2]=xla|0;a[e+26952>>2]=Az|0;a[e+26964>>2]=zz|0;a[e+26976>>2]=qe|0;a[e+26988>>2]=yz|0;a[e+27e3>>2]=ula|0;a[e+27012>>2]=sla|0;a[e+27024>>2]=rla|0;a[e+27036>>2]=qla|0;a[e+27048>>2]=xz|0;a[e+27060>>2]=ola|0;a[e+27072>>2]=nla|0;a[e+27084>>2]=mla|0;a[e+27096>>2]=jla|0;a[e+27108>>2]=wz|0;a[e+27120>>2]=uz|0;a[e+27132>>2]=ila|0;a[e+27144>>2]=gla|0;a[e+27156>>2]=ela|0;a[e+27168>>2]=dla|0;a[e+27180>>2]=cla|0;a[e+27192>>2]=sz|0;a[e+27204>>2]=rz|0;a[e+27216>>2]=pz|0;a[e+27228>>2]=$ka|0;a[e+27240>>2]=Zka|0;a[e+27252>>2]=Yka|0;a[e+27264>>2]=Wka|0;a[e+27276>>2]=oz|0;a[e+27288>>2]=Vka|0;a[e+27300>>2]=Uka|0;a[e+27312>>2]=Tka|0;a[e+27324>>2]=Ska|0;a[e+27336>>2]=nz|0;a[e+27348>>2]=mz|0;a[e+27360>>2]=Zm|0;a[e+27372>>2]=Pka|0;a[e+27384>>2]=Oka|0;a[e+27396>>2]=Nka|0;a[e+27408>>2]=Mka|0;a[e+27420>>2]=hr|0;a[e+27432>>2]=Lka|0;a[e+27444>>2]=Kka|0;a[e+27456>>2]=Hka|0;a[e+27468>>2]=Gka|0;a[e+27480>>2]=lz|0;a[e+27492>>2]=kz|0;a[e+27504>>2]=jz|0;a[e+27516>>2]=Fka|0;a[e+27528>>2]=Eka|0;a[e+27540>>2]=Dka|0;a[e+27552>>2]=Cka|0;a[e+27564>>2]=iz|0;a[e+27576>>2]=zka|0;a[e+27588>>2]=yka|0;a[e+27600>>2]=xka|0;a[e+27612>>2]=wka|0;a[e+27624>>2]=hz|0;a[e+27636>>2]=gz|0;a[e+27648>>2]=fz|0;a[e+27660>>2]=ez|0;a[e+27672>>2]=dz|0;a[e+27684>>2]=cz|0;a[e+27696>>2]=bz|0;a[e+27708>>2]=UB|0;a[e+27720>>2]=tka|0;a[e+27732>>2]=ska|0;a[e+27744>>2]=qka|0;a[e+27756>>2]=oka|0;a[e+27768>>2]=TB|0;a[e+27780>>2]=RB|0;a[e+27792>>2]=nka|0;a[e+27804>>2]=mka|0;a[e+27816>>2]=jka|0;a[e+27828>>2]=ika|0;a[e+27840>>2]=fr|0;a[e+27852>>2]=hka|0;a[e+27864>>2]=sg|0;a[e+27876>>2]=PB|0;a[e+27888>>2]=MB|0;a[e+27900>>2]=fka|0;a[e+27912>>2]=eka|0;a[e+27924>>2]=dka|0;a[e+27936>>2]=$ja|0;a[e+27948>>2]=LB|0;a[e+27960>>2]=Yja|0;a[e+27972>>2]=Xja|0;a[e+27984>>2]=Wja|0;a[e+27996>>2]=Vja|0;a[e+28008>>2]=KB|0;a[e+28020>>2]=Tja|0;a[e+28032>>2]=Sja|0;a[e+28044>>2]=Rja|0;a[e+28056>>2]=Oja|0;a[e+28068>>2]=JB|0;a[e+28080>>2]=Nja|0;a[e+28092>>2]=Mja|0;a[e+28104>>2]=Lja|0;a[e+28116>>2]=Kja|0;a[e+28128>>2]=IB|0;a[e+28140>>2]=HB|0;a[e+28152>>2]=Jja|0;a[e+28164>>2]=Ija|0;a[e+28176>>2]=Fja|0;a[e+28188>>2]=Eja|0;a[e+28200>>2]=GB|0;a[e+28212>>2]=Dja|0;a[e+28224>>2]=Cja|0;a[e+28236>>2]=Bja|0;a[e+28248>>2]=Aja|0;a[e+28260>>2]=FB|0;a[e+28272>>2]=yja|0;a[e+28284>>2]=xja|0;a[e+28296>>2]=uja|0;a[e+28308>>2]=tja|0;a[e+28320>>2]=EB|0;a[e+28332>>2]=BB|0;a[e+28344>>2]=sja|0;a[e+28356>>2]=pja|0;a[e+28368>>2]=oja|0;a[e+28380>>2]=nja|0;a[e+28392>>2]=AB|0;a[e+28404>>2]=zB|0;a[e+28416>>2]=kja|0;a[e+28428>>2]=jja|0;a[e+28440>>2]=ija|0;a[e+28452>>2]=hja|0;a[e+28464>>2]=yB|0;a[e+28476>>2]=fja|0;a[e+28488>>2]=eja|0;a[e+28500>>2]=dja|0;a[e+28512>>2]=cja|0;a[e+28524>>2]=xB|0;a[e+28536>>2]=er|0;a[e+28548>>2]=$ia|0;a[e+28560>>2]=Zia|0;a[e+28572>>2]=Yia|0;a[e+28584>>2]=Xia|0;a[e+28596>>2]=Vj|0;a[e+28608>>2]=Wia|0;a[e+28620>>2]=Via|0;a[e+28632>>2]=Uia|0;a[e+28644>>2]=Tia|0;a[e+28656>>2]=vB|0;a[e+28668>>2]=Qia|0;a[e+28680>>2]=Pia|0;a[e+28692>>2]=Nia|0;a[e+28704>>2]=Mia|0;a[e+28716>>2]=uB|0;a[e+28728>>2]=Lia|0;a[e+28740>>2]=Kia|0;a[e+28752>>2]=Jia|0;a[e+28764>>2]=Iia|0;a[e+28776>>2]=tB|0;a[e+28788>>2]=sB|0;a[e+28800>>2]=Eia|0;a[e+28812>>2]=Dia|0;a[e+28824>>2]=Cia|0;a[e+28836>>2]=Bia|0;a[e+28848>>2]=rB|0;a[e+28860>>2]=qB|0;a[e+28872>>2]=Aia|0;a[e+28884>>2]=zia|0;a[e+28896>>2]=wia|0;a[e+28908>>2]=via|0;a[e+28920>>2]=pB|0;a[e+28932>>2]=uia|0;a[e+28944>>2]=sia|0;a[e+28956>>2]=ria|0;a[e+28968>>2]=qia|0;a[e+28980>>2]=oB|0;a[e+28992>>2]=pia|0;a[e+29004>>2]=oia|0;a[e+29016>>2]=lia|0;a[e+29028>>2]=kia|0;a[e+29040>>2]=nB|0;a[e+29052>>2]=jia|0;a[e+29064>>2]=hia|0;a[e+29076>>2]=gia|0;a[e+29088>>2]=fia|0;a[e+29100>>2]=lB|0;a[e+29112>>2]=eia|0;a[e+29124>>2]=dia|0;a[e+29136>>2]=aia|0;a[e+29148>>2]=$ha|0;a[e+29160>>2]=kB|0;a[e+29172>>2]=Zha|0;a[e+29184>>2]=Xha|0;a[e+29196>>2]=Wha|0;a[e+29208>>2]=Vha|0;a[e+29220>>2]=jB|0;a[e+29232>>2]=hB|0;a[e+29244>>2]=Uha|0;a[e+29256>>2]=Rha|0;a[e+29268>>2]=Qha|0;a[e+29280>>2]=Pha|0;a[e+29292>>2]=gB|0;a[e+29304>>2]=Oha|0;a[e+29316>>2]=Nha|0;a[e+29328>>2]=Mha|0;a[e+29340>>2]=Lha|0;a[e+29352>>2]=fB|0;a[e+29364>>2]=Kha|0;a[e+29376>>2]=Hha|0;a[e+29388>>2]=Gha|0;a[e+29400>>2]=Fha|0;a[e+29412>>2]=eB|0;a[e+29424>>2]=Eha|0;a[e+29436>>2]=Dha|0;a[e+29448>>2]=Cha|0;a[e+29460>>2]=Aha|0;a[e+29472>>2]=dB|0;a[e+29484>>2]=zha|0;a[e+29496>>2]=xha|0;a[e+29508>>2]=wha|0;a[e+29520>>2]=uha|0;a[e+29532>>2]=cB|0;a[e+29544>>2]=tha|0;a[e+29556>>2]=qha|0;a[e+29568>>2]=pha|0;a[e+29580>>2]=oha|0;a[e+29592>>2]=On|0;a[e+29604>>2]=aB|0;a[e+29616>>2]=kha|0;a[e+29628>>2]=jha|0;a[e+29640>>2]=iha|0;a[e+29652>>2]=hha|0;a[e+29664>>2]=$A|0;a[e+29676>>2]=fha|0;a[e+29688>>2]=eha|0;a[e+29700>>2]=dha|0;a[e+29712>>2]=cha|0;a[e+29724>>2]=bha|0;a[e+29736>>2]=ZA|0;a[e+29748>>2]=Zga|0;a[e+29760>>2]=Yga|0;a[e+29772>>2]=Xga|0;a[e+29784>>2]=Wga|0;a[e+29796>>2]=hf|0;a[e+29808>>2]=YA|0;a[e+29820>>2]=Uj|0;a[e+29832>>2]=Vga|0;a[e+29844>>2]=Uga|0;a[e+29856>>2]=Rga|0;a[e+29868>>2]=Qga|0;a[e+29880>>2]=XA|0;a[ed+4>>2]=ae|0;a[ed+12>>2]=Xq|0;a[ed+20>>2]=De|0;a[ed+28>>2]=Xg|0;a[ed+36>>2]=Wq|0;a[ed+44>>2]=Vq|0;a[ed+52>>2]=Uq|0;a[ed+60>>2]=Wg|0;a[ed+68>>2]=Vm|0;a[ed+76>>2]=Um|0;a[ed+84>>2]=Tm|0;a[ed+92>>2]=Sm|0;a[ed+100>>2]=Rm|0;a[ed+108>>2]=Qm|0;a[ed+116>>2]=R|0;a[az+4>>2]=Wm|0;a[Mh>>2]=fba|0;a[Mh+4>>2]=tv|0;a[Mh+8>>2]=yr|0;a[Mh+12>>2]=taa|0;a[Mh+16>>2]=baa|0;a[nj+20>>2]=328;a[Uh+20>>2]=244;a[aw+20>>2]=290;a[Rp+20>>2]=244;a[nn+4>>2]=ak;a[nn+8>>2]=374;a[nn+12>>2]=298;a[nn+16>>2]=42;a[on+4>>2]=pn;a[on+8>>2]=24;a[on+12>>2]=294;a[on+16>>2]=180;VB=d([2,0,0,0],[\"i8*\",0,0,0],c);a[ak>>2]=VB+8|0;a[ak+4>>2]=YPa|0;a[ak+8>>2]=cc;a[pn>>2]=VB+8|0;a[pn+4>>2]=ZPa|0;a[pn+8>>2]=ak;a[W>>2]=NB|0;a[W+4>>2]=Jb;a[W+8>>2]=pl;a[W+16>>2]=ILa|0;a[W+20>>2]=Jb;a[W+24>>2]=aV;a[W+32>>2]=Gs|0;a[W+36>>2]=Jb;a[W+40>>2]=Yy;a[W+48>>2]=wGa|0;a[W+52>>2]=Jb;a[W+56>>2]=Yy;a[W+64>>2]=SDa|0;a[W+68>>2]=Jb;a[W+72>>2]=Zy;a[W+80>>2]=vBa|0;a[W+84>>2]=UU;a[W+88>>2]=Zy;a[W+96>>2]=bza|0;a[W+100>>2]=Jb;a[W+104>>2]=lV;a[W+112>>2]=uwa|0;a[W+116>>2]=Jb;a[W+120>>2]=WU;a[W+128>>2]=sg|0;a[W+132>>2]=Jb;a[W+136>>2]=Xy;a[W+144>>2]=osa|0;a[W+148>>2]=Jb;a[W+152>>2]=Xy;a[W+160>>2]=KA|0;a[W+164>>2]=Jb;a[W+168>>2]=oV;a[W+176>>2]=pna|0;a[W+180>>2]=Jb;a[W+184>>2]=XU;a[W+192>>2]=fla|0;a[W+196>>2]=Jb;a[W+200>>2]=cV;a[W+208>>2]=qja|0;a[W+212>>2]=Jb;a[W+216>>2]=iV;a[W+224>>2]=rha|0;a[W+228>>2]=Jb;a[W+232>>2]=bV;a[W+240>>2]=vga|0;a[W+244>>2]=Jb;a[W+248>>2]=jV;a[W+256>>2]=Sfa|0;a[W+260>>2]=Jb;a[W+264>>2]=$U;a[W+272>>2]=pfa|0;a[W+276>>2]=Jb;a[W+280>>2]=dV;a[W+288>>2]=Mea|0;a[W+292>>2]=Jb;a[W+296>>2]=eV;a[W+304>>2]=qea|0;a[W+308>>2]=Jb;a[W+312>>2]=YU;a[W+320>>2]=Uda|0;a[W+324>>2]=Jb;a[W+328>>2]=kV;a[W+336>>2]=Bda|0;a[W+340>>2]=Jb;a[W+344>>2]=qV;a[W+352>>2]=ida|0;a[W+356>>2]=Jb;a[W+360>>2]=pV;a[W+368>>2]=Nca|0;a[W+372>>2]=Jb;a[W+376>>2]=pl;a[W+384>>2]=qca|0;a[W+388>>2]=Jb;a[W+392>>2]=pl;a[W+400>>2]=Zba|0;a[W+404>>2]=Jb;a[W+408>>2]=ZU;a[W+416>>2]=Fba|0;a[W+420>>2]=Jb;a[W+424>>2]=nV;a[W+432>>2]=kba|0;a[W+436>>2]=Jb;a[W+440>>2]=mV;a[W+448>>2]=Taa|0;a[W+452>>2]=Jb;a[W+456>>2]=VU;a[W+464>>2]=Caa|0;a[W+468>>2]=Jb;a[W+472>>2]=fV;a[W+480>>2]=laa|0;a[W+484>>2]=Jb;a[W+488>>2]=gV;a[W+496>>2]=V$|0;a[W+500>>2]=Jb;a[W+504>>2]=hV;a[W+512>>2]=F$|0;a[W+516>>2]=Jb;a[W+520>>2]=sV;a[W+528>>2]=m$|0;a[W+532>>2]=Jb;a[W+536>>2]=rV;a[W+544>>2]=X9|0;a[W+548>>2]=Jb;a[W+552>>2]=tV;a[W+560>>2]=jv|0;a[W+564>>2]=Wy;a[W+576>>2]=eu|0;a[W+580>>2]=Wy;a[W+592>>2]=Hs|0;a[W+596>>2]=Yq;a[nk>>2]=QEa|0;a[nk+4>>2]=YLa|0;a[nk+8>>2]=una|0;a[Mu+16>>2]=246;a[Xc>>2]=WB;a[iQa>>2]=$Pa;a[jQa>>2]=aQa;a[Jo>>2]=bQa;a[Pn>>2]=cQa;a[kQa>>2]=WB;a[lQa>>2]=dQa;a[mQa>>2]=eQa;a[nQa>>2]=fQa;a[oQa>>2]=gQa;a[pQa>>2]=hQa;a[Mr>>2]=DAa|0;a[xe>>2]=Dra|0;a[xe+8>>2]=hIa|0;a[xe+16>>2]=Gia|0;a[xe+24>>2]=NB|0;a[xe+32>>2]=KA|0;a[xe+40>>2]=ok|0;a[xe+48>>2]=sg|0;a[xe+56>>2]=M2|0;a[xe+64>>2]=c0|0;a[xe+72>>2]=oY|0;a[xe+80>>2]=XPa|0;a[gh>>2]=ft|0;a[gh+8>>2]=cj|0;a[gh+16>>2]=SGa|0;a[gh+24>>2]=Zt|0;a[gh+32>>2]=XCa|0;a[Ai>>2]=gya|0;a[Ai+12>>2]=Bva|0;a[Ai+24>>2]=zv|0;a[Ai+36>>2]=sg|0;var $c=Math.sqrt,DQa,EQa,va={Fb:7,h:13,Gb:98,Hb:99,Ib:97,Jb:11,Kb:114,j:9,Lb:74,Mb:16,Nb:125,Ob:10,Pb:103,Qb:111,Rb:104,Sb:35,Tb:89,Ub:33,Vb:122,ha:17,Wb:14,Xb:27,Yb:113,Zb:43,$b:84,ac:115,bc:4,i:22,r:5,cc:106,N:21,Ea:40,dc:24,ec:31,fc:90,gc:72,hc:36,ic:100,jc:102,kc:101,lc:23,mc:105,nc:61,oc:19,O:2,pc:8,qc:37,rc:67,sc:12,tc:42,uc:92,vc:28,wc:63,xc:60,yc:38,zc:107,ia:20,Ac:39,Bc:131,Cc:88,Dc:95,Ec:25,ja:6,Fc:75,Gc:130,Hc:1,Ic:32,Jc:71,Kc:93,Lc:91,ka:34,Mc:30,Fa:29,Nc:3,Oc:116,Pc:62,Qc:110,Rc:26,Sc:11,Tc:18};oa=zg=Om=0;var Q={Sa:\"/\",gb:2,a:[xc],va:wc,bb:(function(a,b){for(var c=a[0],d=1;dthis.length-1||0>a)){var b=a%m;return this.Xa(Math.floor(a/m))[b]}});f.prototype.ob=(function(a){this.Xa=a});var h=new XMLHttpRequest;h.open(\"HEAD\",c,ee);h.send(xc);200<=h.status&&300>h.status||304===h.status||Ub(Error(\"Couldn't load \"+c+\". Status: \"+h.status));var j=Number(h.getResponseHeader(\"Content-length\")),n,m=1048576;if(!((n=h.getResponseHeader(\"Accept-Ranges\"))&&\"bytes\"===n)){m=j}var p=new f(m,j);p.ob((function(a){var b=a*p.ma,d=(a+1)*p.ma-1,d=Math.min(d,j-1);if(\"undefined\"===typeof p.F[a]){var e=p.F;b>d&&Ub(Error(\"invalid range (\"+b+\", \"+d+\") or no bytes requested!\"));d>j-1&&Ub(Error(\"only \"+j+\" bytes available! programmer error!\"));var f=new XMLHttpRequest;f.open(\"GET\",c,ee);j!==m&&f.setRequestHeader(\"Range\",\"bytes=\"+b+\"-\"+d);\"undefined\"!=typeof Uint8Array&&(f.responseType=\"arraybuffer\");f.overrideMimeType&&f.overrideMimeType(\"text/plain; charset=x-user-defined\");f.send(xc);200<=f.status&&300>f.status||304===f.status||Ub(Error(\"Couldn't load \"+c+\". Status: \"+f.status));b=f.response!==cc?new Uint8Array(f.response||[]):Nd(f.responseText||\"\",wc);e[a]=b}\"undefined\"===typeof p.F[a]&&Ub(Error(\"doXHR failed!\"));return p.F[a]}));f={d:ee,b:p}}else{f={d:ee,url:c}}return Q.G(a,b,f,d,e)}),Ra:(function(a,b,c,d,e,f,h,j){function n(c){function i(c){j||Q.S(a,b,c,d,e);f&&f();rn(\"cp \"+m)}var n=ee;K.preloadPlugins.forEach((function(a){!n&&a.canHandle(m)&&(a.handle(c,m,i,(function(){h&&h();rn(\"cp \"+m)})),n=wc)}));n||i(c)}Tb.Ua();var m=Q.bb([a,b],wc);pr(\"cp \"+m);\"string\"==typeof c?Tb.Ka(c,(function(a){n(a)}),h):n(c)}),Qa:(function(a,b,c,d,e){return Q.G(a,b,{d:ee,link:c},d,e)}),s:(function(a,b,c,d){!c&&!d&&Ub(Error(\"A device must have at least one callback defined.\"));return Q.G(a,b,{d:wc,input:c,l:d},Boolean(c),Boolean(d))}),ua:(function(a){if(a.d||a.e||a.link||a.b){return wc}var b=wc;\"undefined\"!==typeof XMLHttpRequest&&Ub(Error(\"Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.\"));if(K.read){try{a.b=Nd(K.read(a.url),wc)}catch(c){b=ee}}else{Ub(Error(\"Cannot load without read() or XMLHttpRequest.\"))}b||Ea(va.r);return b}),ra:(function(){Q.root||(Q.root={v:wc,write:wc,e:wc,d:ee,timestamp:Date.now(),$:1,b:{}})}),t:(function(a,b,e){function f(a){a===xc||10===a?(b.M(b.buffer.join(\"\")),b.buffer=[]):b.buffer.push(k.wa(a))}Ae(!Q.t.Z,\"FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)\");Q.t.Z=wc;Q.ra();var a=a||K.stdin,b=b||K.stdout,e=e||K.stderr,h=wc,j=wc,l=wc;a||(h=ee,a=(function(){if(!a.R||!a.R.length){var b;\"undefined\"!=typeof window&&\"function\"==typeof window.prompt?(b=window.prompt(\"Input: \"),b===xc&&(b=String.fromCharCode(0))):\"function\"==typeof readline&&(b=readline());b||(b=\"\");a.R=Nd(b+\"\\n\",wc)}return a.R.shift()}));var k=new Qa.Q;b||(j=ee,b=f);b.M||(b.M=K.print);b.buffer||(b.buffer=[]);e||(l=ee,e=f);e.M||(e.M=K.print);e.buffer||(e.buffer=[]);try{Q.H(\"/\",\"tmp\",wc,wc)}catch(n){}var m=Q.H(\"/\",\"dev\",wc,wc),p=Q.s(m,\"stdin\",a),s=Q.s(m,\"stdout\",xc,b),e=Q.s(m,\"stderr\",xc,e);Q.s(m,\"tty\",a,b);Q.a[1]={path:\"/dev/stdin\",object:p,position:0,p:wc,q:ee,K:ee,aa:!h,error:ee,f:ee,g:[]};Q.a[2]={path:\"/dev/stdout\",object:s,position:0,p:ee,q:wc,K:ee,aa:!j,error:ee,f:ee,g:[]};Q.a[3]={path:\"/dev/stderr\",object:e,position:0,p:ee,q:wc,K:ee,aa:!l,error:ee,f:ee,g:[]};Om=d([1],\"void*\",$g);zg=d([2],\"void*\",$g);oa=d([3],\"void*\",$g);Q.pa(\"/\",\"dev/shm/tmp\",wc,wc);for(h=Q.a.length;h>e-6&63,e=e-6,a=a+\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"[k]}}2==e?(a+=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"[(d&3)<<4],a+=\"==\"):4==e&&(a+=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"[(d&15)<<2],a+=\"=\");r.src=\"data:audio/x-\"+c.substr(-3)+\";base64,\"+a;f(r)}});r.src=m;setTimeout((function(){f(r)}),1e4)}else{return h()}})})}}),Yc:(function(a,b,c){try{var d=a.getContext(b?\"experimental-webgl\":\"2d\");d||Ub(\":(\")}catch(e){return K.print(\"Could not create canvas - \"+e),xc}b&&(a.style.backgroundColor=\"black\",a.addEventListener(\"webglcontextlost\",(function(){alert(\"WebGL context lost. You will need to reload the page.\")}),ee));c&&(K.Zc=d,K.Ad=b,Tb.eb.forEach((function(a){a()})));return d}),ca:(function(){function a(){var b=ee;if((document.webkitFullScreenElement||document.webkitFullscreenElement||document.mozFullScreenElement||document.mozFullscreenElement||document.fullScreenElement||document.fullscreenElement)===c){c.kb=c.requestPointerLock||c.mozRequestPointerLock||c.webkitRequestPointerLock,c.kb(),b=wc}if(K.onFullScreen){K.onFullScreen(b)}}function b(){Tb.hb=document.pointerLockElement===c||document.mozPointerLockElement===c||document.webkitPointerLockElement===c}var c=K.canvas;document.addEventListener(\"fullscreenchange\",a,ee);document.addEventListener(\"mozfullscreenchange\",a,ee);document.addEventListener(\"webkitfullscreenchange\",a,ee);document.addEventListener(\"pointerlockchange\",b,ee);document.addEventListener(\"mozpointerlockchange\",b,ee);document.addEventListener(\"webkitpointerlockchange\",b,ee);c.ca=c.requestFullScreen||c.mozRequestFullScreen||(c.webkitRequestFullScreen?(function(){c.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT)}):xc);c.ca()}),requestAnimationFrame:(function(a){window.requestAnimationFrame||(window.requestAnimationFrame=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame||window.oRequestAnimationFrame||window.setTimeout);window.requestAnimationFrame(a)}),ed:(function(a){return a.movementX||a.mozMovementX||a.webkitMovementX||0}),fd:(function(a){return a.movementY||a.mozMovementY||a.webkitMovementY||0}),Db:(function(a,b,c){var d=new XMLHttpRequest;d.open(\"GET\",a,wc);d.responseType=\"arraybuffer\";d.onload=(function(){200==d.status?b(d.response):c()});d.onerror=c;d.send(xc)}),Ka:(function(a,b,c){Tb.Db(a,(function(c){Ae(c,'Loading data file \"'+a+'\" failed (no arrayBuffer).');b(new Uint8Array(c));rn(\"al \"+a)}),(function(){c?c():Ub('Loading data file \"'+a+'\" failed.')}));pr(\"al \"+a)}),lb:[],Cb:(function(){var a=K.canvas;Tb.lb.forEach((function(b){b(a.width,a.height)}))}),od:(function(a,b,c){var d=K.canvas;d.width=a;d.height=b;c||Tb.Cb()})};IU.unshift({U:(function(){!K.noFSInit&&!Q.t.Z&&Q.t()})});CU.push({U:(function(){Q.va=ee})});tr.push({U:(function(){Q.jb()})});K.FS_createFolder=Q.H;K.FS_createPath=Q.pa;K.FS_createDataFile=Q.S;K.FS_createPreloadedFile=Q.Ra;K.FS_createLazyFile=Q.Pa;K.FS_createLink=Q.Qa;K.FS_createDevice=Q.s;Ea(0);ni.c=d([0],\"i8\",c);kk.c=d([0],\"i8\",c);mC(Ee);pC.La=d(12,\"void*\",c);K.requestFullScreen=(function(){Tb.ca()});K.requestAnimationFrame=(function(a){Tb.requestAnimationFrame(a)});K.pauseMainLoop=(function(){Tb.k.pause()});K.resumeMainLoop=(function(){Tb.k.mb()});qC.X=1;K._vizRenderFromString=rC;Kc.X=1;pk.X=1;zC.X=1;Er.X=1;FC.X=1;Cn.X=1;Nc.X=1;GC.X=1;Ag.X=1;yC.X=1;JC.X=1;tk.X=1;Cg.X=1;Hn.X=1;In.X=1;LC.X=1;hh.X=1;MC.X=1;NC.X=1;OC.X=1;PC.X=1;QC.X=1;RC.X=1;Ei.X=1;Or.X=1;Kn.X=1;xk.X=1;Rr.X=1;eD.X=1;gD.X=1;hD.X=1;Ck.X=1;oh.X=1;Ji.X=1;nh.X=1;Br.X=1;HD.X=1;QD.X=1;RD.X=1;JD.X=1;GD.X=1;fs.X=1;wD.X=1;xD.X=1;yD.X=1;Qi.X=1;ms.X=1;LE.X=1;IE.X=1;yE.X=1;OE.X=1;ps.X=1;xE.X=1;UE.X=1;Gi.X=1;vD.X=1;kD.X=1;Ur.X=1;lD.X=1;Tk.X=1;Ld.X=1;Uk.X=1;NE.X=1;hF.X=1;kF.X=1;Qs.X=1;$s.X=1;at.X=1;bt.X=1;qF.X=1;rF.X=1;ct.X=1;yF.X=1;Zs.X=1;ht.X=1;so.X=1;ag.X=1;jt.X=1;Fg.X=1;nc.X=1;uF.X=1;Bo.X=1;OF.X=1;aG.X=1;nG.X=1;qt.X=1;rt.X=1;RF.X=1;AG.X=1;sG.X=1;gl.X=1;rG.X=1;hl.X=1;Oo.X=1;DG.X=1;EG.X=1;HG.X=1;RG.X=1;GG.X=1;KG.X=1;LG.X=1;fg.X=1;IG.X=1;yt.X=1;jl.X=1;Wo.X=1;Vo.X=1;SG.X=1;Xo.X=1;At.X=1;zt.X=1;Bt.X=1;Et.X=1;Dt.X=1;UG.X=1;XG.X=1;Ft.X=1;ff.X=1;Jg.X=1;cH.X=1;Ii.X=1;lH.X=1;tH.X=1;vH.X=1;xH.X=1;Yi.X=1;AH.X=1;CH.X=1;FH.X=1;GH.X=1;HH.X=1;gu.X=1;hu.X=1;LH.X=1;MH.X=1;NH.X=1;OH.X=1;dp.X=1;Wt.X=1;Xt.X=1;Yt.X=1;vk.X=1;jp.X=1;Au.X=1;Wd.X=1;QH.X=1;vl.X=1;Bu.X=1;xl.X=1;UH.X=1;YH.X=1;Du.X=1;XH.X=1;WH.X=1;Eu.X=1;$H.X=1;cI.X=1;iI.X=1;np.X=1;Pu.X=1;Qu.X=1;uI.X=1;VI.X=1;Tu.X=1;Vu.X=1;av.X=1;ev.X=1;kv.X=1;mv.X=1;An.X=1;OD.X=1;Bp.X=1;bK.X=1;YJ.X=1;fK.X=1;kK.X=1;mK.X=1;nK.X=1;rK.X=1;lK.X=1;vv.X=1;tK.X=1;sK.X=1;aK.X=1;eh.X=1;nv.X=1;Re.X=1;Bn.X=1;Ke.X=1;ut.X=1;MK.X=1;OK.X=1;PK.X=1;SK.X=1;Dv.X=1;Gp.X=1;dL.X=1;eL.X=1;gL.X=1;hL.X=1;iL.X=1;Gt.X=1;Mv.X=1;jL.X=1;Ov.X=1;mL.X=1;Hp.X=1;zL.X=1;AL.X=1;Ht.X=1;DL.X=1;CL.X=1;ng.X=1;Pl.X=1;Pk.X=1;Wv.X=1;uh.X=1;Np.X=1;Op.X=1;Sf.X=1;OL.X=1;Rl.X=1;fw.X=1;hw.X=1;iw.X=1;mw.X=1;Vl.X=1;aM.X=1;cw.X=1;pw.X=1;cM.X=1;eM.X=1;hM.X=1;uw.X=1;uM.X=1;Zv.X=1;Pp.X=1;$v.X=1;zr.X=1;Dw.X=1;Ew.X=1;UM.X=1;VM.X=1;WM.X=1;cq.X=1;ZM.X=1;cN.X=1;dN.X=1;wj.X=1;Lw.X=1;hN.X=1;hm.X=1;nN.X=1;vN.X=1;wN.X=1;Jw.X=1;gN.X=1;AN.X=1;Uw.X=1;Pw.X=1;uN.X=1;DN.X=1;jm.X=1;Ww.X=1;ON.X=1;NN.X=1;SN.X=1;Zw.X=1;Yw.X=1;jq.X=1;Cj.X=1;$w.X=1;YN.X=1;ZN.X=1;lq.X=1;bx.X=1;jO.X=1;gO.X=1;hO.X=1;dx.X=1;nO.X=1;Sg.X=1;bi.X=1;ex.X=1;hx.X=1;ix.X=1;qO.X=1;tO.X=1;fx.X=1;gx.X=1;kO.X=1;lO.X=1;cx.X=1;mO.X=1;mm.X=1;ux.X=1;uO.X=1;vO.X=1;xx.X=1;pq.X=1;gm.X=1;OO.X=1;RO.X=1;SO.X=1;zx.X=1;WO.X=1;XO.X=1;Bx.X=1;lx.X=1;tq.X=1;aP.X=1;Gx.X=1;qq.X=1;Nx.X=1;Dx.X=1;Ix.X=1;Sw.X=1;uq.X=1;nP.X=1;Rw.X=1;Px.X=1;vq.X=1;vm.X=1;wm.X=1;oP.X=1;sq.X=1;wq.X=1;Jx.X=1;Kx.X=1;hP.X=1;yP.X=1;zP.X=1;gP.X=1;fP.X=1;Ux.X=1;Tx.X=1;Xx.X=1;JP.X=1;MP.X=1;NP.X=1;YP.X=1;CP.X=1;Yx.X=1;SP.X=1;UP.X=1;hQ.X=1;iQ.X=1;cy.X=1;dy.X=1;DP.X=1;EP.X=1;FP.X=1;ay.X=1;Zx.X=1;$x.X=1;nQ.X=1;oQ.X=1;pQ.X=1;qQ.X=1;yQ.X=1;zQ.X=1;tQ.X=1;mx.X=1;iy.X=1;CQ.X=1;FQ.X=1;GQ.X=1;HQ.X=1;zm.X=1;PQ.X=1;SQ.X=1;XQ.X=1;YQ.X=1;aR.X=1;dR.X=1;fR.X=1;ry.X=1;ZQ.X=1;mR.X=1;Cq.X=1;HR.X=1;TR.X=1;eS.X=1;gS.X=1;mS.X=1;Fm.X=1;NS.X=1;VS.X=1;XS.X=1;Mj.X=1;Of.X=1;eT.X=1;MT.X=1;jU.X=1;mU.X=1;pU.X=1;Gb.X=1;uU.X=1;wU.X=1;vU.X=1;AU.X=1;G.X=1;BU.X=1;yU.X=1;zU.X=1;var hk=xc;K.Na=(function(a){function b(){for(var a=0;3>a;a++){f.push(0)}}var e=a.length+1,f=[d(Nd(\"/bin/this.program\"),\"i8\",c)];b();for(var h=0;h>2],b[1]=a[d+4>>2],f[0]);d=(c+340|0)>>2;e=(b[0]=a[d],b[1]=a[d+1],f[0])-e;f[0]=e;a[d]=b[0];a[d+1]=b[1];m[c+529|0]=1;return 0}),0,PC,0,(function(b,c){var d=$M(a[b>>2]),e=$M(a[c>>2]);return((e|0)<(d|0)&1)-((e|0)>(d|0)&1)|0}),0,(function(b){var c;y(b,qba|0);c=(b+12|0)>>2;y(b,a[a[a[c]>>2]>>2]);y(b,lr|0);y(b,a[a[a[c]>>2]+4>>2]);y(b,kr|0);y(b,a[a[a[c]>>2]+8>>2]);y(b,oA|0)}),0,(function(b,c){return a[b>>2]-a[c>>2]|0}),0,(function(a,b,c,d){y(a,tz|0);0!=(b|0)&&0!=m[b]<<24>>24&&(y(a,ysa|0),y(a,Bo(b)),y(a,ue|0));0!=(c|0)&&0!=m[c]<<24>>24&&(y(a,nsa|0),y(a,nc(c)),y(a,ue|0));0!=(d|0)&&0!=m[d]<<24>>24&&(y(a,vy|0),y(a,nc(d)),y(a,ue|0));y(a,ar|0)}),0,(function(b,c,d){var e;0==(c|0)&&(a[Pm>>2]=d);for(var c=c-1|0,d=0,f=b;;){if((d|0)>=(c|0)){var h=b,j=f;break}var k=a[Pm>>2],n=m[k];if(0==n<<24>>24){e=339;break}a[Pm>>2]=k+1|0;k=f+1|0;m[f]=n;if(10==n<<24>>24){h=b;j=k;break}else{d=d+1|0,f=k}}339==e&&(0==(d|0)?(h=a[Pm>>2]=0,j=f):(m[f]=10,h=b,j=f+1|0));m[j]=0;return h}),0,(function(a){cc(a|0)}),0,(function(a){y(a,Xj|0)}),0,fR,0,MH,0,(function(b){var c,d=h;c=(b+12|0)>>2;0!=(a[a[c]+20>>2]|0)&&(y(b,vva|0),np(b,0,a[a[c]+20>>2]+4|0));y(b,hva|0);y(b,Vua|0);N(b,Iua|0,(j=h,h+=4,a[j>>2]=a[a[c]+28>>2],j));h=d}),0,CH,0,(function(c){var d;m[c+528|0]=0;d=(c+348|0)>>2;var e=1.1*(b[0]=a[d],b[1]=a[d+1],f[0]);f[0]=e;a[d]=b[0];a[d+1]=b[1];m[c+529|0]=1;return 0}),0,NC,0,jU,0,(function(){return Vca|0}),0,(function(b){var c=a[b+16>>2];y(b,Wj|0);y(b,nc(a[c+152>>2]));y(b,Wpa|0);y(b,kn|0);y(b,nc(a[a[c+8>>2]+12>>2]));y(b,hn|0)}),0,HR,0,(function(c,d,e,g,j,m,l,k){m=h;h+=32;l=.5*$c(g*g+j*j);d=.5*g+d;g=m|0;f[0]=d-l;a[g>>2]=b[0];a[g+4>>2]=b[1];e=.5*j+e;j=m+8|0;f[0]=e-l;a[j>>2]=b[0];a[j+4>>2]=b[1];j=m+16|0;f[0]=d+l;a[j>>2]=b[0];a[j+4>>2]=b[1];j=m+24|0;f[0]=e+l;a[j>>2]=b[0];a[j+4>>2]=b[1];Qk(c,m|0,(k>>>3&1^1)&255);h=m}),0,(function(a){y(a,gr|0)}),0,(function(c,d,e){var g=h;h+=16;c=a[c>>2];Ui(g,d,e,90*(a[a[c+20>>2]+152>>2]&3)|0);var d=g|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),e=g+8|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),j=c+96|0,j=.5*(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]);if(e<-j|e>j){return h=g,0}e=c+104|0;if(d<-(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])){return h=g,0}c=c+112|0;c=d<=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0]);h=g;return c&1}),0,vH,0,tH,0,(function(b,c){var d=a[c+8>>2];0!=(d|0)&&G(d);d=a[c+24>>2];0!=(d|0)&&Ho(d);d=c;G(d)}),0,nC,0,(function(c){var d=a[a[c+16>>2]+8>>2],c=d|0,e=a[a[a[Kb>>2]>>2]+8>>2],g=a[U+20>>2];g>>>0>2]>>>0||(na(U+16|0,1),g=a[U+20>>2]);m[g]=0;g=a[U+16>>2];a[U+20>>2]=g;qc(c,e,g);0!=(a[d+48>>2]|0)&&(d=a[a[a[Kb>>2]+4>>2]+8>>2],e=a[U+84>>2],e>>>0>2]>>>0||(na(U+80|0,1),e=a[U+84>>2]),m[e]=0,e=a[U+80>>2],a[U+84>>2]=e,qc(c,d,e));f[0]=1;a[kc+8>>2]=b[0];a[kc+12>>2]=b[1];f[0]=1;a[kc+40>>2]=b[0];a[kc+44>>2]=b[1]}),0,(function(b){var c=a[a[b>>2]+128>>2],d=b+572|0;0==(a[d>>2]|0)&&(a[d>>2]=c|0,d=c+148|0,m[d]|=2,vv(b,c));Rr(b,c);m[b+532|0]=1}),0,mS,0,(function(c,d,e,g,m,D){g=h;y(c,cr|0);var m=a[Ug>>2],l=a[$d>>2];N(c,eA|0,(j=h,h+=8,a[j>>2]=m,a[j+4>>2]=l,j));Oy(c,D);y(c,br|0);Mq(c);y(c,bA|0);D=h;if(0<(e|0)){m=0;for(l=$z|0;;){var k=(m<<4)+d|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),n=(m<<4)+d+8|0,n=(a[$d>>2]>>>0)-(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]);N(c,X7|0,(j=h,h+=20,a[j>>2]=l,f[0]=k,a[j+4>>2]=b[0],a[j+8>>2]=b[1],f[0]=n,a[j+12>>2]=b[0],a[j+16>>2]=b[1],j));l=0==(m|0)?ly|0:Y|0;m=m+1|0;if((m|0)==(e|0)){break}}}y(c,ue|0);h=D;y(c,B8|0);h=g}),0,(function(b,c){var d=h,e=a[a[c+16>>2]+12>>2],f=a[a[c+12>>2]+12>>2];la(3,Zea|0,(j=h,h+=12,a[j>>2]=c,a[j+4>>2]=e,a[j+8>>2]=f,j));h=d;return 0}),0,(function(c,d,e){var g=h;y(c,ECa|0);Fm(c,0);y(c,VA|0);if(0<(e|0)){for(var m=0;;){var D=(m<<4)+d|0,D=(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0]),l=(m<<4)+d+8|0,l=-(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);N(c,UA|0,(j=h,h+=16,f[0]=D,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=l,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));m=m+1|0;if((m|0)==(e|0)){break}}}y(c,Dm|0);h=g}),0,(function(a,b){y(a,Yha|0);y(a,b);y(a,wd|0)}),0,(function(b){var c;y(b,Qma|0);c=V(a[a[b>>2]+128>>2]|0,Ema|0);0!=(c|0)&&0!=m[c]<<24>>24&&(y(b,tma|0),y(b,c),y(b,jma|0));y(b,Yla|0);y(b,Nla|0);y(b,Bla|0);c=(b+12|0)>>2;y(b,nc(a[a[a[c]>>2]>>2]));y(b,lr|0);y(b,nc(a[a[a[c]>>2]+4>>2]));y(b,kr|0);y(b,nc(a[a[a[c]>>2]+8>>2]));y(b,oA|0);y(b,dr|0)}),0,(function(b,c,d){var e,f=h;e=d>>2;d=h;h+=32;a[d>>2]=a[e];a[d+4>>2]=a[e+1];a[d+8>>2]=a[e+2];a[d+12>>2]=a[e+3];a[d+16>>2]=a[e+4];a[d+20>>2]=a[e+5];a[d+24>>2]=a[e+6];a[d+28>>2]=a[e+7];0==(b|0)&&sa(pd|0,114,$j|0,Nj|0);d=b+16|0;0==(a[d>>2]|0)&&sa(pd|0,116,$j|0,Nr|0);0==(c|0)&&sa(pd|0,117,$j|0,Oh|0);c=c+8|0;0==(a[c>>2]|0)&&sa(pd|0,118,$j|0,Ph|0);0==(a[a[d>>2]+8>>2]|0)&&sa(pd|0,121,$j|0,$t|0);N(b,Z3|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j));N(b,K3|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j));N(b,u3|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j));N(b,e3|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j));N(b,R2|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j));N(b,C2|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j));N(b,l2|0,(j=h,h+=4,a[j>>2]=a[c>>2],j));N(b,Y1|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j));N(b,Sp|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j));h=f}),0,(function(){a[tg>>2]=-1}),0,(function(b){b=a[b+28>>2];0!=(b|0)&&G(b)}),0,(function(a){y(a,NZ|0);y(a,$m|0);y(a,kZ|0);y(a,WY|0);y(a,$m|0);y(a,IY|0);y(a,uY|0);y(a,fY|0);y(a,$m|0);y(a,RX|0);y(a,DX|0);y(a,$m|0);y(a,oX|0)}),0,QC,0,(function(){a[rf>>2]=0}),0,(function(){return 0}),0,(function(b){var c=h;y(b,Aqa|0);var d=b+64|0,e=b+12|0;2!=(a[d>>2]|0)&&N(b,nqa|0,(j=h,h+=4,a[j>>2]=a[a[e>>2]+28>>2],j));if(0==(a[a[e>>2]+20>>2]|0)&&2!=(a[d>>2]|0)){var d=a[b+468>>2],e=a[b+472>>2],f=a[b+476>>2];N(b,wy|0,(j=h,h+=16,a[j>>2]=a[b+464>>2],a[j+4>>2]=d,a[j+8>>2]=e,a[j+12>>2]=f,j))}y(b,Zpa|0);y(b,Kpa|0);h=c}),0,wM,0,(function(c){var d;m[c+528|0]=0;d=(c+348|0)>>2;var e=(b[0]=a[d],b[1]=a[d+1],f[0])/1.1;f[0]=e;a[d]=b[0];a[d+1]=b[1];m[c+529|0]=1;return 0}),0,eS,0,(function(b){G(a[b+52>>2])}),0,(function(c,d,e){var g,m=h;g=e>>2;e=h;h+=32;a[e>>2]=a[g];a[e+4>>2]=a[g+1];a[e+8>>2]=a[g+2];a[e+12>>2]=a[g+3];a[e+16>>2]=a[g+4];a[e+20>>2]=a[g+5];a[e+24>>2]=a[g+6];a[e+28>>2]=a[g+7];g=c+228|0;var D=c+212|0;g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])-(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0])&-1;var d=a[d+8>>2],D=e+16|0,D=(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0]),l=e|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),k=e+24|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),e=e+8|0,e=k-(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);N(c,k8|0,(j=h,h+=36,a[j>>2]=d,f[0]=D-l,a[j+4>>2]=b[0],a[j+8>>2]=b[1],f[0]=e,a[j+12>>2]=b[0],a[j+16>>2]=b[1],f[0]=l,a[j+20>>2]=b[0],a[j+24>>2]=b[1],f[0]=(g>>>0)-k,a[j+28>>2]=b[0],a[j+32>>2]=b[1],j));y(c,V7|0);h=m}),0,(function(a,b,c,d){return jg(d,a,c)}),0,gS,0,(function(b){var c;y(b,sGa|0);y(b,dGa|0);c=(b+12|0)>>2;y(b,Of(a[a[a[c]>>2]>>2]));y(b,lr|0);y(b,Of(a[a[a[c]>>2]+4>>2]));y(b,kr|0);y(b,Of(a[a[a[c]>>2]+8>>2]));y(b,pFa|0)}),0,(function(b,c){return ka(a[b>>2],a[c>>2])}),0,(function(c){var d;m[c+528|0]=0;var e=c+348|0;d=(c+340|0)>>2;e=10/(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])+(b[0]=a[d],b[1]=a[d+1],f[0]);f[0]=e;a[d]=b[0];a[d+1]=b[1];m[c+529|0]=1;return 0}),0,(function(c,d,e){var g,m=h;h+=1024;g=e>>2;e=h;h+=32;a[e>>2]=a[g];a[e+4>>2]=a[g+1];a[e+8>>2]=a[g+2];a[e+12>>2]=a[g+3];a[e+16>>2]=a[g+4];a[e+20>>2]=a[g+5];a[e+24>>2]=a[g+6];a[e+28>>2]=a[g+7];c=c+16|0;g=a[Bd+(a[a[c>>2]+12>>2]<<2)>>2];db(g,cIa|0);var y=e|0,l=(b[0]=a[y>>2],b[1]=a[y+4>>2],f[0]),y=e+8|0,k=(b[0]=a[y>>2],b[1]=a[y+4>>2],f[0]);Ap(g,l,k);var y=m|0,n=e+16|0,l=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0])-l,e=e+24|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])-k;Ma(y,Hl|0,(j=h,h+=8,a[j>>2]=(0>l?l-.5:l+.5)&-1,a[j+4>>2]=(0>e?e-.5:e+.5)&-1,j));db(g,y);fi(a[a[c>>2]+12>>2],Y|0,a[d+8>>2]);h=m}),0,eT,0,(function(b){var c=a[b+16>>2];y(b,Wj|0);y(b,nc(a[c+152>>2]));y(b,lqa|0);y(b,kn|0);y(b,nc(a[a[c+8>>2]+12>>2]));y(b,hn|0)}),0,(function(b,c){return a[a[b>>2]+20>>2]-a[a[c>>2]+20>>2]|0}),0,(function(c,d,e){var g=h;y(c,f5|0);var m=d+16|0,D=(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0]),m=d|0,m=(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0]),D=D-m,l=d+24|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),d=d+8|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),l=l-d,d=(a[$d>>2]>>>0)-(d+l);N(c,Ky|0,(j=h,h+=16,f[0]=m-D,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=d,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));N(c,Ly|0,(j=h,h+=16,f[0]=2*D,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=2*l,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));Oy(c,e);y(c,br|0);Mq(c);y(c,n4|0);h=g}),0,(function(c,d,e){var g=h;y(c,cr|0);var m=a[Ug>>2],D=a[$d>>2];N(c,vea|0,(j=h,h+=8,a[j>>2]=m,a[j+4>>2]=D,j));y(c,Yda|0);if(0<(e|0)){for(var m=e-1|0,D=d|0,l=d+8|0,k=0;;){if(0==(k|0)){y(c,Fda|0);var n=(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0]),z=(a[$d>>2]>>>0)-(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);N(c,yA|0,(j=h,h+=16,f[0]=n,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=z,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));y(c,Rca|0)}else{n=(k<<4)+d|0,n=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),z=(k<<4)+d+8|0,z=(a[$d>>2]>>>0)-(b[0]=a[z>>2],b[1]=a[z+4>>2],f[0]),N(c,yA|0,(j=h,h+=16,f[0]=n,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=z,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j))}(k|0)==(m|0)&&y(c,uca|0);k=k+1|0;if((k|0)==(e|0)){break}}}y(c,cca|0);Mq(c);y(c,tA|0);h=g}),0,kK,0,(function(b,c){return ka(a[b>>2],a[c>>2])}),0,(function(c,d,e,g){var m=h;y(c,Mxa|0);Fm(c,g);y(c,VA|0);g=0<(e|0);a:do{if(g){for(var D=0;;){var l=(D<<4)+d|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),k=(D<<4)+d+8|0,k=-(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);N(c,UA|0,(j=h,h+=16,f[0]=l,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=k,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));D=D+1|0;if((D|0)==(e|0)){break a}}}}while(0);e=d|0;e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);d=d+8|0;d=-(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]);N(c,yxa|0,(j=h,h+=16,f[0]=e,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=d,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));y(c,Dm|0);h=m}),0,(function(b,c,d){var b=a[c>>2],e=a[d>>2];b>>>0>>0?d=-1:b>>>0>e>>>0?d=1:(c=a[c+4>>2],d=a[d+4>>2],d=c>>>0>>0?-1:c>>>0>d>>>0&1);return d}),0,(function(c,d,e){var g,j,m=h;h+=32;var l=m|0;j=m>>2;g=d>>2;a[j]=a[g];a[j+1]=a[g+1];a[j+2]=a[g+2];a[j+3]=a[g+3];g=d+16|0;j=d|0;g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])-(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]);j=m+16|0;f[0]=g;a[j>>2]=b[0];a[j+4>>2]=b[1];g=d+24|0;d=d+8|0;d=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])-(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]);g=m+24|0;f[0]=d;a[g>>2]=b[0];a[g+4>>2]=b[1];d=(c+16|0)>>2;if(0!=(e|0)&&(e=a[d],g=e+76|0,.5<(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]))){Nf(c,e+52|0),kf(c,l,2),y(c,pya|0)}e=a[d]+40|0;if(.5<(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])){Em(c),Nf(c,a[d]+16|0),kf(c,l,2),y(c,aya|0)}h=m}),0,(function(c,d,e){var g=c+16|0,h=a[g>>2]+40|0;if(.5<(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0])){Em(c);Nf(c,a[g>>2]+16|0);y(c,Jj|0);g=d|0;h=d+8|0;Hd(c,(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0]));y(c,Kj|0);g=1<(e|0);a:do{if(g){for(h=1;;){var j=(h<<4)+d|0,l=(h<<4)+d+8|0;Hd(c,(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]),(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]));y(c,Hq|0);h=h+1|0;if((h|0)==(e|0)){break a}}}}while(0);y(c,zy|0)}}),0,(function(b,c,d){var e,f=a[b+16>>2];e=(f+88|0)>>2;0!=(a[e]|0)&&(Gm(b),y(b,jA|0),kf(b,c,d),y(b,Lj|0),gi(b,f+16|0),c=a[e],1==(c|0)?(y(b,Hm|0),e=a[e]):e=c,2==(e|0)&&y(b,Im|0),Mj(b),y(b,wd|0))}),0,(function(c){var d=c>>2,e=h,g=a[d+4];y(c,Goa|0);g=g+8|0;0!=m[a[a[g>>2]+12>>2]]<<24>>24&&(y(c,vA|0),y(c,nc(a[a[g>>2]+12>>2])));N(c,hoa|0,(j=h,h+=4,a[j>>2]=a[d+41]*a[d+40]|0,j));g=a[d+111];N(c,Vna|0,(j=h,h+=8,a[j>>2]=a[d+110],a[j+4>>2]=g,j));var d=c+376|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),g=c+384|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),q=c+392|0,q=(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]),D=c+400|0,D=(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0]);N(c,Hna|0,(j=h,h+=32,f[0]=d,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=g,a[j+8>>2]=b[0],a[j+12>>2]=b[1],f[0]=q,a[j+16>>2]=b[0],a[j+20>>2]=b[1],f[0]=D,a[j+24>>2]=b[0],a[j+28>>2]=b[1],j));y(c,sna|0);y(c,dna|0);y(c,ar|0);h=e}),0,(function(c){var d=h;h+=36;JF(c,8);var e;var g=d>>2,q=h;h+=12;e=q>>2;var y=q+8;a[y>>2]=5;var l=V(c|0,tla|0);0!=(l|0)&&1<=(Cd(l,kda|0,(j=h,h+=8,a[j>>2]=q,a[j+4>>2]=y,j))|0)?(l=(b[0]=a[e],b[1]=a[e+1],f[0]),1>l?(f[0]=1,a[e]=b[0],a[e+1]=b[1],e=1):20>2]=b[0],a[l+4>>2]=b[1],a[g+6]=-1,a[g+7]=a[y>>2],a[g+8]=0,0!=m[ld]<<24>>24&&Va(a[oa>>2],D$|0,(j=h,h+=8,f[0]=e,a[j>>2]=b[0],a[j+4>>2]=b[1],j)),g=d):(a[g+6]=0,g=a[g+8]=0);h=q;e=g;XN(c);q=d+32|0;g=d+28|0;for(y=d+24|0;!(kx(c,e),0!=(a[q>>2]|0)&&(la(0,bka|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),e=a[y>>2]=0),lx(c,0!=(e|0)&1),zO(c,e),l=a[g>>2]-1|0,a[g>>2]=l,0==(a[y>>2]|0)|0==(l|0));){}mx(c);bx(c,1);if(0!=jo(V(c|0,HJa|0))<<24>>24&&(q=ra(c),0!=(q|0))){for(;;){g=Ib(c,q);y=0==(g|0);a:do{if(!y){for(e=g;;){if(DN(c,e),e=yb(c,e),0==(e|0)){break a}}}}while(0);q=ba(c,q);if(0==(q|0)){break}}}Bt(c,1);h=d}),0,(function(a,b,c){a=0==(c|0);0==(b|0)?b=a?0:Gb(c):a?(G(b),b=0):b=mc(b,c);return b}),0,mU,0,OC,0,(function(c){var d=h,e=a[c+16>>2];y(c,Wj|0);y(c,nc(a[e+152>>2]));y(c,Hpa|0);var g=c+480|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),q=c+488|0,q=(b[0]=a[q>>2],b[1]=a[q+4>>2],f[0]),D=-a[c+356>>2]|0,l=c+496|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),k=c+504|0,k=-(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);N(c,tpa|0,(j=h,h+=36,f[0]=g,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=q,a[j+8>>2]=b[0],a[j+12>>2]=b[1],a[j+16>>2]=D,f[0]=l,a[j+20>>2]=b[0],a[j+24>>2]=b[1],f[0]=k,a[j+28>>2]=b[0],a[j+32>>2]=b[1],j));e=e+8|0;0!=m[a[a[e>>2]+12>>2]]<<24>>24&&(y(c,kn|0),y(c,nc(a[a[e>>2]+12>>2])),y(c,hn|0));h=d}),0,(function(b,c,d){zm(b);Am(b);Bm(a[a[b+16>>2]+12>>2],76,c,d)}),0,(function(a){y(a,Xj|0)}),0,(function(b){var c;for(c=b>>2;!(b=a[c+32],0==(b|0));){c=b>>2}var b=a[c+3],d=a[b+236>>2];c=a[c+4];var e=a[c+236>>2];return(d|0)>(e|0)?0:(d|0)<(e|0)?1:b=(a[b+240>>2]|0)<(a[c+240>>2]|0)&1}),0,bk(),0,(function(a){y(a,Mm|0)}),0,TR,0,(function(b,c){var d,e=fa(24);d=e>>2;a[d+2]=a[c+8>>2];a[d+3]=a[c+12>>2];a[d+4]=a[c+16>>2];a[d+5]=a[c+20>>2];return e}),0,(function(c,d,e,g){var q=h,D=a[c+16>>2],l=D+40|0;if(.5<=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])){Nf(c,D+16|0),D=g+24|0,gj(c,(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0])),N(c,Nxa|0,(j=h,h+=4,a[j>>2]=a[g+20>>2],j)),D=Qu(a[g>>2],a[Gq>>2]),l=m[g+72|0]<<24>>24,114==(l|0)?(l=g+56|0,d-=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])):108==(l|0)?l=g+56|0:(l=g+56|0,d-=.5*(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])),g=g+48|0,Hd(c,d,(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0])+e),y(c,zxa|0),gj(c,(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])),N(c,lxa|0,(j=h,h+=4,a[j>>2]=D,j))}h=q}),0,(function(a){y(a,gr|0)}),0,(function(b){return 0!=(a[a[b>>2]+116>>2]|0)||0!=(a[b+40>>2]|0)?0:b=nC(a[b+36>>2])}),0,(function(c,d,e,g){var m=h;y(c,cr|0);var D=a[Ug>>2],l=a[$d>>2];N(c,eA|0,(j=h,h+=8,a[j>>2]=D,a[j+4>>2]=l,j));Oy(c,g);y(c,br|0);Mq(c);y(c,bA|0);if(0<(e|0)){g=e-1|0;for(D=0;;){var l=(D<<4)+d|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),k=(D<<4)+d+8|0,k=(a[$d>>2]>>>0)-(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);0==(D|0)?(y(c,$z|0),N(c,Qz|0,(j=h,h+=16,f[0]=l,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=k,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j)),y(c,M5|0)):N(c,Qz|0,(j=h,h+=16,f[0]=l,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=k,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));(D|0)==(g|0)&&y(c,w5|0);D=D+1|0;if((D|0)==(e|0)){break}}}y(c,tA|0);h=m}),0,(function(c,d,e){var g,h=a[c+16>>2];g=(h+88|0)>>2;0!=(a[g]|0)&&(Gm(c),y(c,jA|0),kf(c,d,e),y(c,Lj|0),gi(c,h+16|0),y(c,Jq|0),d=h+96|0,gj(c,(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0])),d=a[g],1==(d|0)?(y(c,Hm|0),g=a[g]):g=d,2==(g|0)&&y(c,Im|0),y(c,Sna|0),Mj(c),y(c,wd|0))}),0,(function(b,c){var d=h;if(0!=(c|0)){var e=a[b+16>>2]+212|0;0!=(a[e>>2]|0)&&(y(b,$wa|0),kf(b,a[e>>2],2),y(b,Jwa|0),e=Qu(c,a[Gq>>2]),N(b,vwa|0,(j=h,h+=4,a[j>>2]=e,j)))}h=d}),0,YQ,0,(function(){return EEa|0}),0,Lc,0,(function(b){b=a[b+28>>2];0!=(b|0)&&(G(a[b+40>>2]),G(b))}),0,(function(b){var c=a[a[b+16>>2]+8>>2];UL(104,170);var d=a[b+64>>2];2==(d|0)?av(b,c,b,0):3==(d|0)?av(b,c,b,1):0==(d|0)||1==(d|0)?0==(a[b+148>>2]&134217728|0)&&iw(c,b):4==(d|0)&&(PQ(c),0==(a[b+148>>2]&134217728|0)&&iw(c,b));UL(0,0)}),0,(function(a){y(a,vz|0)}),0,pU,0,(function(){a[rf>>2]=2}),0,(function(c){var d;d=c+528|0;var e=0==m[d]<<24>>24;m[d]=e&1;if(!e){return 0}d=a[c+440>>2];e=a[c+444>>2];d=(d>>>0)/(d|0);var e=(e>>>0)/(e|0),g=c+348|0;f[0]=d>2]=b[0];a[g+4>>2]=b[1];d=(c+332|0)>>2;a[d]=0;a[d+1]=0;a[d+2]=0;a[d+3]=0;m[c+529|0]=1;return 0}),0,ax,0,mR,0,(function(b){var c,d=h;y(b,vpa|0);2==(a[b+64>>2]|0)?y(b,kpa|0):y(b,wd|0);c=a[a[b+12>>2]>>2]>>2;var e=a[c+1],f=a[c+2];N(b,Xoa|0,(j=h,h+=12,a[j>>2]=a[c],a[j+4>>2]=e,a[j+8>>2]=f,j));h=d}),0,Mp,0,(function(b,c){var d=h;N(b,JOa|0,(j=h,h+=4,a[j>>2]=c,j));h=d}),0,(function(b){var c=h,d=a[b+16>>2];y(b,wca|0);d=d+8|0;0!=m[a[a[d>>2]+12>>2]]<<24>>24&&(y(b,vA|0),y(b,a[a[d>>2]+12>>2]));N(b,Kba|0,(j=h,h+=4,a[j>>2]=a[b+164>>2]*a[b+160>>2]|0,j));h=c}),0,(function(b,c){Kc(a[c+8>>2]);G(c)}),0,(function(c,d,e,g){var m,y=h;m=e>>2;e=h;h+=32;a[e>>2]=a[m];a[e+4>>2]=a[m+1];a[e+8>>2]=a[m+2];a[e+12>>2]=a[m+3];a[e+16>>2]=a[m+4];a[e+20>>2]=a[m+5];a[e+24>>2]=a[m+6];a[e+28>>2]=a[m+7];0==(c|0)&&sa(pd|0,205,mr|0,Nj|0);0==(d|0)&&sa(pd|0,206,mr|0,Oh|0);m=(d+8|0)>>2;0==(a[m]|0)&&sa(pd|0,207,mr|0,Ph|0);if(0!=(a[d+52>>2]|0)){var d=e|0,d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]),l=e+8|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),k=e+16|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]),e=e+24|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);0!=g<<24>>24&&(N(c,an|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j)),N(c,sf|0,(j=h,h+=16,f[0]=d,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=l,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j)),N(c,sf|0,(j=h,h+=16,f[0]=d,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=e,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j)),N(c,sf|0,(j=h,h+=16,f[0]=k,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=e,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j)),N(c,sf|0,(j=h,h+=16,f[0]=k,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=l,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j)),N(c,sf|0,(j=h,h+=16,f[0]=d,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=l,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j)),g=a[m],N(c,$5|0,(j=h,h+=8,a[j>>2]=4,a[j+4>>2]=g,j)));N(c,an|0,(j=h,h+=1,h=h+3>>2<<2,a[j>>2]=0,j));N(c,sf|0,(j=h,h+=16,f[0]=d,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=l,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));N(c,sf|0,(j=h,h+=16,f[0]=d,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=e,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));N(c,sf|0,(j=h,h+=16,f[0]=k,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=e,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));N(c,sf|0,(j=h,h+=16,f[0]=k,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=l,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));N(c,sf|0,(j=h,h+=16,f[0]=d,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=l,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));g=a[m];N(c,K5|0,(j=h,h+=8,a[j>>2]=4,a[j+4>>2]=g,j))}h=y}),0,xi,0,(function(){a[XB>>2]=0==(a[XB>>2]|0)&1}),0,(function(c,d,e,g,m){var D,l=h;D=(c+16|0)>>2;if(0!=(m|0)){var m=a[D],k=m+76|0;if(.5<(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])){Nf(c,m+52|0),y(c,an|0),kf(c,e,g),y(c,gc|0),m=e|0,k=e+8|0,Hd(c,(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0]),(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])),N(c,sDa|0,(j=h,h+=8,a[j>>2]=g,a[j+4>>2]=d,j))}}m=a[D]+40|0;if(.5<(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0])){Em(c),Nf(c,a[D]+16|0),y(c,an|0),kf(c,e,g),y(c,gc|0),D=e|0,e=e+8|0,Hd(c,(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0]),(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])),N(c,fDa|0,(j=h,h+=8,a[j>>2]=g,a[j+4>>2]=d,j))}h=l}),0,(function(b){var c=h;N(b,Jva|0,(j=h,h+=4,a[j>>2]=a[a[a[b+16>>2]+8>>2]+12>>2],j));y(b,gr|0);h=c}),0,(function(a){y(a,Voa|0)}),0,(function(a){y(a,vha|0)}),0,(function(b){ju(a[b+28>>2])}),0,(function(b,c,d,e){var f=h;y(b,tz|0);0!=(c|0)&&0!=m[c]<<24>>24&&(c=Of(c),N(b,U_|0,(j=h,h+=4,a[j>>2]=c,j)));0!=(d|0)&&0!=m[d]<<24>>24&&(d=Of(d),N(b,E_|0,(j=h,h+=4,a[j>>2]=d,j)));0!=(e|0)&&0!=m[e]<<24>>24&&(e=Of(e),N(b,q_|0,(j=h,h+=4,a[j>>2]=e,j)));y(b,ar|0);h=f}),0,(function(c,d,e,g,m,D){y(c,Cya|0);Fm(c,D);y(c,oya|0);g=h;if(0<(e|0)){m=0;for(D=77;;){var l=(m<<4)+d|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]),k=(m<<4)+d+8|0,k=-(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0]);N(c,$xa|0,(j=h,h+=20,a[j>>2]=D,f[0]=l,a[j+4>>2]=b[0],a[j+8>>2]=b[1],f[0]=k,a[j+12>>2]=b[0],a[j+16>>2]=b[1],j));D=0==(m|0)?67:32;m=m+1|0;if((m|0)==(e|0)){break}}}h=g;y(c,Dm|0)}),0,(function(b,c){var d=a[Ul>>2];J[a[d>>2]](d,c,1);return 0}),0,(function(a,b){y(a,Wj|0);y(a,nc(b));y(a,ipa|0)}),0,(function(b,c,d){c=h;b=d>>2;d=h;h+=32;a[d>>2]=a[b];a[d+4>>2]=a[b+1];a[d+8>>2]=a[b+2];a[d+12>>2]=a[b+3];a[d+16>>2]=a[b+4];a[d+20>>2]=a[b+5];a[d+24>>2]=a[b+6];a[d+28>>2]=a[b+7];h=c}),0,AH,0,NS,0,MC,0,(function(b,c,d){b=a[b>>2];var e,f=a[a[b+128>>2]+32>>2];wC(b,c);c=a[b+124>>2];e=c>>2;a[e+14]=Bn(c,a[e+13]);if(0==(a[f+44>>2]|0)&&0==(a[e+37]&67108864|0)){Lc(Ar|0,20,1,a[oa>>2])}else{e=b+120|0;var h=a[e>>2];0==(h|0)?(h=fa(624),a[e>>2]=h,a[b+124>>2]=h,e=a[Lh>>2]=h):(e=a[Lh>>2],0==(e|0)?e=a[Lh>>2]=h:(e=a[e+4>>2],0==(e|0)&&(e=fa(624),a[a[Lh>>2]+4>>2]=e),a[Lh>>2]=e));a[e+32>>2]=d;a[e>>2]=b;Br(b,f);Cr(c);iK(c);Dr(b)}}),0,(function(b,c,d){return a[c>>2]-a[d>>2]|0}),0,(function(b,c){G(a[c+52>>2])}),0,zC,0,(function(b,c){var d=a[c+20>>2];0!=(d|0)&&ri(d);if(0!=(a[c+52>>2]|0)&&(d=a[c+60>>2],0!=(d|0))){J[d](c)}G(c)}),0,(function(a,b){G(b)}),0,(function(b,c){var d=a[c+8>>2],e=d+80|0,f=m[d+84|0];1==f<<24>>24?(e=a[e>>2],Kc(a[e+76>>2]),Io(e|0),G(e)):2==f<<24>>24&&rt(a[e>>2]);Io(d|0);G(d);G(c)}),0,(function(b,c,d,e){zm(b);Am(b);0==(e|0)?Bm(a[a[b+16>>2]+12>>2],112,c,d):(jy(b),Bm(a[a[b+16>>2]+12>>2],80,c,d))}),0,(function(c,d,e){var g,m=h;y(c,kxa|0);Fm(c,e);g=(d|0)>>2;var D=(b[0]=a[g],b[1]=a[g+1],f[0]),e=(d+8|0)>>2,l=-(b[0]=a[e],b[1]=a[e+1],f[0]);N(c,Wwa|0,(j=h,h+=16,f[0]=D,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=l,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));D=d+16|0;g=(b[0]=a[D>>2],b[1]=a[D+4>>2],f[0])-(b[0]=a[g],b[1]=a[g+1],f[0]);d=d+24|0;d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0])-(b[0]=a[e],b[1]=a[e+1],f[0]);N(c,Iwa|0,(j=h,h+=16,f[0]=g,a[j>>2]=b[0],a[j+4>>2]=b[1],f[0]=d,a[j+8>>2]=b[0],a[j+12>>2]=b[1],j));y(c,Py|0);h=m}),0,(function(){return 1}),0,HQ,0,NH,0,(function(b){7==(a[b>>2]|0)&&ZF(a[b+76>>2],1)}),0,(function(b,c){var d=c+12|0,e=D[d>>1];if(0!=e<<16>>16){var f=c+8|0,e=0>16;a:do{if(e){for(var h=a[f>>2],j=0;;){var k=a[h>>2];0!=(k|0)&&G(k);k=a[h+16>>2];0!=(k|0)&&Ho(k);j=j+1|0;if((j|0)<(D[d>>1]<<16>>16|0)){h=h+76|0}else{break a}}}}while(0);G(a[f>>2])}d=c;G(d)}),0,lH,0,(function(a){y(a,Mm|0)}),0,dR,0,nm,0,FQ,0,(function(a){y(a,Xj|0)}),0,(function(a){y(a,Xj|0)}),0,(function(b){var c=h;h+=8;var d=c+4,e=a[a[b+16>>2]+8>>2],b=a[b+64>>2];4==(b|0)?(ev(e,d,c),SQ(e,a[d>>2],a[c>>2])):1==(b|0)?0!=(D[e+164>>1]&1)<<16>>16&&oF(e):0==(b|0)&&(d=h,h+=8,ev(e,d+4,d),h=d);h=c}),0,Th,0,OH,0,(function(b,c,d){var b=a[c+16>>2],b=0==(b|0)?-1:a[b+16>>2],e=a[c+12>>2],e=0==(e|0)?-1:a[e+16>>2],f=a[d+16>>2],f=0==(f|0)?-1:a[f+16>>2],h=a[d+12>>2],h=0==(h|0)?-1:a[h+16>>2];return(e|0)!=(h|0)?e-h|0:c=(b|0)==(f|0)?JL(a[c+4>>2],d):b-f|0}),0,(function(b,c,d,e,f){var h;h=a[b+16>>2]>>2;Cq(b,a[h+51],a[h+53],a[h+52],c,d,e,f)}),0,(function(a){cc(a|0);0!=(a|0)&&G(a)}),0,(function(b){var c,d=h,e=a[b+16>>2];y(b,xga|0);c=a[a[b+12>>2]>>2]>>2;var f=a[c+1],m=a[c+2];N(b,Ufa|0,(j=h,h+=12,a[j>>2]=a[c],a[j+4>>2]=f,a[j+8>>2]=m,j));N(b,rfa|0,(j=h,h+=4,a[j>>2]=a[a[e+8>>2]+12>>2],j));N(b,Lea|0,(j=h,h+=4,a[j>>2]=a[b+164>>2]*a[b+160>>2]|0,j));y(b,pea|0);y(b,Tda|0);y(b,Ada|0);y(b,hda|0);y(b,Lca|0);y(b,zca|0);y(b,fca|0);y(b,Nba|0);y(b,tba|0);h=d}),0,(function(a){cc(a|0);0!=(a|0)&&G(a)}),0,MT,0,(function(a){m[a+530|0]=0;m[a+533|0]=0}),0,(function(c,d,e){var g=h;h+=12;var m=g+4,y=a[c+16>>2],l=y+96|0,l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0])&-1,k=a[y+16>>2],n=a[rf>>2];Aq(a[y+88>>2],g,m);y=a[g>>2];m=(b[0]=a[m>>2],b[1]=a[m+4>>2],f[0]);N(c,CB|0,(j=h,h+=68,a[j>>2]=2,a[j+4>>2]=1,a[j+8>>2]=y,a[j+12>>2]=l,a[j+16>>2]=k,a[j+20>>2]=0,a[j+24>>2]=n,a[j+28>>2]=0,a[j+32>>2]=0,f[0]=m,a[j+36>>2]=b[0],a[j+40>>2]=b[1],a[j+44>>2]=0,a[j+48>>2]=0,a[j+52>>2]=0,a[j+56>>2]=0,a[j+60>>2]=0,a[j+64>>2]=e,j));ry(c,d,e,0);h=g}),0,GQ,0,(function(a,b){dc(b)}),0,(function(b,c){return Lb(a[b>>2],a[c>>2])}),0,bk(),0,(function(b,c){return Lb(a[b>>2],a[c>>2])}),0,(function(a,b){y(a,xAa|0);y(a,b);y(a,wd|0)}),0,XS,0,VS,0,jO,0,(function(c,d,e,g){var q,y=h;h+=1024;q=(c+16|0)>>2;var l=a[a[q]+12>>2],k=y|0,n=g+24|0;Ma(k,Qla|0,(j=h,h+=8,f[0]=(b[0]=a[n>>2],b[1]=a[n+4>>2],f[0]),a[j>>2]=b[0],a[j+4>>2]=b[1],j));l=a[Bd+(l<<2)>>2];db(l,k);fi(a[a[q]+12>>2],Y|0,a[g+20>>2]);Am(c);c=m[g+72|0]<<24>>24;c=114==(c|0)?1:108==(c|0)?-1:0;db(l,Zja|0);Ap(l,d,e);d=g+56|0;d=(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0])&-1;Ma(k,Hl|0,(j=h,h+=8,a[j>>2]=c,a[j+4>>2]=d,j));db(l,k);fi(a[a[q]+12>>2],Y|0,a[g>>2]);h=y}),0,FH,0,(function(b,c,d){var b=a[c+16>>2],b=0==(b|0)?-1:a[b+16>>2],e=a[c+12>>2],e=0==(e|0)?-1:a[e+16>>2],f=a[d+16>>2],f=0==(f|0)?-1:a[f+16>>2],h=a[d+12>>2],h=0==(h|0)?-1:a[h+16>>2];return(b|0)!=(f|0)?b-f|0:c=(e|0)==(h|0)?JL(a[c+4>>2],d):e-h|0}),0,(function(){a[rf>>2]=1}),0,(function(c){var d;m[c+528|0]=0;var e=c+348|0;d=(c+332|0)>>2;e=10/(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0])+(b[0]=a[d],b[1]=a[d+1],f[0]);f[0]=e;a[d]=b[0];a[d+1]=b[1];m[c+529|0]=1;return 0}),0,(function(c,d){var e=a[Id>>2],g=e+36*a[d>>2]+20|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),e=e+36*a[c>>2]+20|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]);return(g>e&1)-(g>2],h=g|0,d=d-(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0]),g=g+8|0,e=e-(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]),c=a[c+4>>2];return d*d+e*e<=(b[0]=a[c>>2],b[1]=a[c+4>>2],f[0])&1}),0,(function(b){var c=a[b+16>>2];y(b,Wj|0);y(b,nc(a[c+152>>2]));y(b,jra|0);y(b,kn|0);c=ec(cg|0,a[c+8>>2]|0);y(b,nc(c));G(c);y(b,hn|0)}),0,GH,0,(function(a){y(a,vz|0)}),0,(function(){a[tg>>2]=1}),0,(function(b,c,d,e,f,h){zm(b);Am(b);0==(h|0)?Bm(a[a[b+16>>2]+12>>2],66,c,d):(jy(b),Bm(a[a[b+16>>2]+12>>2],98,c,d))}),0,(function(){a[rf>>2]=2}),0,nK,0,(function(b){var c;c=a[b+16>>2]>>2;var d=a[b+64>>2];3==(d|0)?(Cq(b,a[c+51],a[c+53],a[c+52],a[c+37],a[c+42],a[c+46],a[c+38]),y(b,Mda|0)):2==(d|0)&&Cq(b,a[c+51],a[c+53],a[c+52],a[c+37],a[c+42],a[c+46],a[c+38])}),0,(function(c){var d,e,g=h;e=Uk(V(c|0,Xk|0));if(0==(e|0)){la(0,wJa|0,(j=h,h+=4,a[j>>2]=a[c+12>>2],j))}else{var m=iI(e);e=m>>2;if(0!=(m|0)){var m=a[e+10],y=a[e+11];d=c+48|0;f[0]=(m|0)/72;a[d>>2]=b[0];a[d+4>>2]=b[1];d=c+56|0;f[0]=(y|0)/72;a[d>>2]=b[0];a[d+4>>2]=b[1];var l=fa(12);d=l>>2;a[c+28>>2]=l;a[d]=a[e+3];a[d+1]=((m|0)/-2&-1)-a[e+8]|0;a[d+2]=((y|0)/-2&-1)-a[e+9]|0}}h=g}),0,HH,0,GC,0,(function(a,b){y(a,eDa|0);y(a,nc(b));y(a,dr|0)}),0,(function(c,d){var e=a[a[c>>2]+108>>2],g=a[a[d>>2]+108>>2],h=0!=(g|0);if(0==(e|0)){return h&1}if(!h){return-1}var h=e+24|0,h=(b[0]=a[h>>2],b[1]=a[h+4>>2],f[0]),e=e+32|0,e=(b[0]=a[e>>2],b[1]=a[e+4>>2],f[0]),j=g+24|0,j=(b[0]=a[j>>2],b[1]=a[j+4>>2],f[0]),g=g+32|0,g=(b[0]=a[g>>2],b[1]=a[g+4>>2],f[0]);return h>j?-1:hg?-1:e>2]=d,a[j+4>>2]=e,j));h=c}),0,JC,0,(function(a){cc(a|0)}),0,(function(a){y(a,Xj|0)}),0,(function(){a[rf>>2]=2}),0,(function(b,c){return a[a[b>>2]+240>>2]-a[a[c>>2]+240>>2]|0}),0,(function(b,c){var d=a[b>>2];An(d,a[d+128>>2],c)}),0,(function(c,d,e,g){var m,y=h;h+=12;var l=y+4,k=a[c+16>>2];m=k>>2;var k=k+96|0,k=(b[0]=a[k>>2],b[1]=a[k+4>>2],f[0])&-1,n=a[m+4],z=a[m+13],p=a[rf>>2];Aq(a[m+22],y,l);m=a[y>>2];l=(b[0]=a[l>>2],b[1]=a[l+4>>2],f[0]);N(c,CB|0,(j=h,h+=68,a[j>>2]=2,a[j+4>>2]=3,a[j+8>>2]=m,a[j+12>>2]=k,a[j+16>>2]=n,a[j+20>>2]=z,a[j+24>>2]=p,a[j+28>>2]=0,a[j+32>>2]=0!=(g|0)?20:-1,f[0]=l,a[j+36>>2]=b[0],a[j+40>>2]=b[1],a[j+44>>2]=0,a[j+48>>2]=0,a[j+52>>2]=0,a[j+56>>2]=0,a[j+60>>2]=0,a[j+64>>2]=e+1|0,j));ry(c,d,e,1);h=y}),0,oi,0,(function(c){var d;m[c+528|0]=0;d=c+348|0;var e=10/(b[0]=a[d>>2],b[1]=a[d+4>>2],f[0]);d=(c+332|0)>>2;e=(b[0]=a[d],b[1]=a[d+1],f[0])-e;f[0]=e;a[d]=b[0];a[d+1]=b[1];m[c+529|0]=1;return 0}),0,mK,0,(function(b,c,d,e){var f=h;h+=40;if(0==m[d]<<24>>24){c=bu>>2}else{var e=0==(e|0)?cu|0:e,y=a[c+28>>2],l=KH(y,d);0==(l|0)?0!=(Yi(c,y+16|0,f,d,15,0)|0)&&BH(a[c+12>>2],d):0!=(Yi(c,l+16|0,f,e,m[l+65|0]&255,0)|0)&&la(0,du|0,(j=h,h+=12,a[j>>2]=a[c+12>>2],a[j+4>>2]=d,a[j+8>>2]=e,j));c=f>>2}b>>=2;for(d=c+10;c

"; + } + *_os << '\n'; + } + +private: + bool _use_html; + std::ostream* _os; + +}; + +void VLogService::default_method(::google::protobuf::RpcController* cntl_base, + const ::brpc::VLogRequest*, + ::brpc::VLogResponse*, + ::google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + Controller *cntl = static_cast(cntl_base); + const bool use_html = UseHTML(cntl->http_request()); + butil::IOBufBuilder os; + + cntl->http_response().set_content_type( + use_html ? "text/html" : "text/plain"); + if (use_html) { + os << "" << gridtable_style() + << "" + "
" : " | "); + if (_use_html) { + *_os << "
"; + } + *_os << site.full_module << ":" << site.line_no << bar + << site.current_verbose_level << bar << site.required_verbose_level + << bar; + if (site.current_verbose_level >= site.required_verbose_level) { + if (_use_html) { + *_os << "" + << "enabled"; + } else { + *_os << "enabled"; + } + } else { + *_os << "disabled"; + } + if (_use_html) { + *_os << "
" + "" + "\n"; + } else { + os << "Module | Current | Required | Status\n"; + } + VLogPrinter printer(use_html, os); + print_vlog_sites(&printer); + if (use_html) { + os << "
ModuleCurrentRequiredStatus
\n"; + } + + if (use_html) { + os << "\n"; + } + os.move_to(cntl->response_attachment()); +} + +} // namespace brpc + +#endif + diff --git a/src/brpc/builtin/vlog_service.h b/src/brpc/builtin/vlog_service.h new file mode 100644 index 0000000..ce717df --- /dev/null +++ b/src/brpc/builtin/vlog_service.h @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_VLOG_SERVICE_H +#define BRPC_VLOG_SERVICE_H + +#if !BRPC_WITH_GLOG + +#include +#include "brpc/builtin_service.pb.h" + +namespace brpc { + +class VLogService : public vlog { +public: + void default_method(::google::protobuf::RpcController* controller, + const ::brpc::VLogRequest* request, + ::brpc::VLogResponse* response, + ::google::protobuf::Closure* done); + +}; + +} // namespace brpc + +#endif // BRPC_WITH_GLOG + +#endif //BRPC_VLOG_SERVICE_H diff --git a/src/brpc/builtin_service.proto b/src/brpc/builtin_service.proto new file mode 100644 index 0000000..bb28f44 --- /dev/null +++ b/src/brpc/builtin_service.proto @@ -0,0 +1,179 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +import "google/protobuf/descriptor.proto"; + +package brpc; + +option cc_generic_services = true; +option java_generic_services = true; +option java_package = "com.brpc"; +option java_outer_classname = "BuiltinService"; + +message IndexRequest {} +message IndexResponse {} +message FlagsRequest {} +message FlagsResponse {} +message VersionRequest {} +message VersionResponse {} +message HealthRequest {} +message HealthResponse {} +message StatusRequest {} +message StatusResponse {} +message ProtobufsRequest {} +message ProtobufsResponse {} +message ConnectionsRequest {} +message ConnectionsResponse {} +message ListRequest {} +message ListResponse { + repeated google.protobuf.ServiceDescriptorProto service = 1; +} +message VarsRequest {} +message VarsResponse {} +message BthreadsRequest {} +message BthreadsResponse {} +message IdsRequest {} +message IdsResponse{} +message SocketsRequest {} +message SocketsResponse {} +message RpczRequest {} +message RpczResponse {} +message ThreadsRequest {} +message ThreadsResponse {} +message DirRequest {} +message DirResponse {} +message VLogRequest {} +message VLogResponse {} +message MetricsRequest {} +message MetricsResponse {} +message MemoryRequest {} +message MemoryResponse {} +message BadMethodRequest { + required string service_name = 1; +} +message BadMethodResponse {} + +service index { + rpc default_method(IndexRequest) returns (IndexResponse); +} + +service version { + rpc default_method(VersionRequest) returns (VersionResponse); +} + +service health { + rpc default_method(HealthRequest) returns (HealthResponse); +} + +service status { + rpc default_method(StatusRequest) returns (StatusResponse); +} + +service protobufs { + rpc default_method(ProtobufsRequest) returns (ProtobufsResponse); +} + +service connections { + rpc default_method(ConnectionsRequest) returns (ConnectionsResponse); +} + +service list { + rpc default_method(ListRequest) returns (ListResponse); +} + +service threads { + rpc default_method(ThreadsRequest) returns (ThreadsResponse); +} + +service vlog { + rpc default_method(VLogRequest) returns (VLogResponse); +} + +service bthreads { + rpc default_method(BthreadsRequest) returns (BthreadsResponse); +} + +service ids { + rpc default_method(IdsRequest) returns (IdsResponse); +} + +service sockets { + rpc default_method(SocketsRequest) returns (SocketsResponse); +} + +service brpc_metrics { + rpc default_method(MetricsRequest) returns (MetricsResponse); +} + +service badmethod { + rpc no_method(BadMethodRequest) returns (BadMethodResponse); +} + + +message ProfileRequest {} +message ProfileResponse {} + +service pprof { + rpc profile(ProfileRequest) returns (ProfileResponse); + rpc contention(ProfileRequest) returns (ProfileResponse); + rpc heap(ProfileRequest) returns (ProfileResponse); + rpc symbol(ProfileRequest) returns (ProfileResponse); + rpc cmdline(ProfileRequest) returns (ProfileResponse); + rpc growth(ProfileRequest) returns (ProfileResponse); +} + +message HotspotsRequest {} +message HotspotsResponse {} + +service hotspots { + rpc cpu(HotspotsRequest) returns (HotspotsResponse); + rpc cpu_non_responsive(HotspotsRequest) returns (HotspotsResponse); + rpc heap(HotspotsRequest) returns (HotspotsResponse); + rpc heap_non_responsive(HotspotsRequest) returns (HotspotsResponse); + rpc growth(HotspotsRequest) returns (HotspotsResponse); + rpc growth_non_responsive(HotspotsRequest) returns (HotspotsResponse); + rpc contention(HotspotsRequest) returns (HotspotsResponse); + rpc contention_non_responsive(HotspotsRequest) returns (HotspotsResponse); + rpc iobuf(HotspotsRequest) returns (HotspotsResponse); + rpc iobuf_non_responsive(HotspotsRequest) returns (HotspotsResponse); +} + +service flags { + rpc default_method(FlagsRequest) returns (FlagsResponse); +} + +service vars { + rpc default_method(VarsRequest) returns (VarsResponse); +} + +service rpcz { + rpc enable(RpczRequest) returns (RpczResponse); + rpc disable(RpczRequest) returns (RpczResponse); + rpc stats(RpczRequest) returns (RpczResponse); + rpc hex_log_id(RpczRequest) returns (RpczResponse); + rpc dec_log_id(RpczRequest) returns (RpczResponse); + rpc default_method(RpczRequest) returns (RpczResponse); +} + +service dir { + rpc default_method(DirRequest) returns (DirResponse); +} + +service memory { + rpc default_method(MemoryRequest) returns (MemoryResponse); +} diff --git a/src/brpc/callback.h b/src/brpc/callback.h new file mode 100644 index 0000000..739278e --- /dev/null +++ b/src/brpc/callback.h @@ -0,0 +1,1097 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: kenton@google.com (Kenton Varda) and others +// +// Contains basic types and utilities used by the rest of the library. + +// The code in this file is modified from google/protobuf/stubs/common.h +// in protobuf-2.4, mainly for creating closures. We need to separate +// the code because protobuf 3.0 moves NewCallback into internal namespace. +// Another reason is that we add more overloads to the function which is +// probably widely used throughout baidu. When user's callback creation +// code breaks in protobuf 3.0, they can simply replace +// google::protobuf::NewCallback with brpc::NewCallback. + +#ifndef BRPC_CALLBACK_H +#define BRPC_CALLBACK_H + +#include // Closure +#if GOOGLE_PROTOBUF_VERSION >= 3007000 +// After protobuf 3.7.0, callback.h is removed from common.h, we need to explicitly +// include this file. +#include +#endif + +namespace brpc { + +// Abstract interface for a callback. When calling an RPC, you must provide +// a Closure to call when the procedure completes. See the Service interface +// in service.h. +// +// To automatically construct a Closure which calls a particular function or +// method with a particular set of parameters, use the NewCallback() function. +// Example: +// void FooDone(const FooResponse* response) { +// ... +// } +// +// void CallFoo() { +// ... +// // When done, call FooDone() and pass it a pointer to the response. +// Closure* callback = NewCallback(&FooDone, response); +// // Make the call. +// service->Foo(controller, request, response, callback); +// } +// +// Example that calls a method: +// class Handler { +// public: +// ... +// +// void FooDone(const FooResponse* response) { +// ... +// } +// +// void CallFoo() { +// ... +// // When done, call FooDone() and pass it a pointer to the response. +// Closure* callback = NewCallback(this, &Handler::FooDone, response); +// // Make the call. +// service->Foo(controller, request, response, callback); +// } +// }; +// +// Currently NewCallback() supports binding zero, one, or two arguments. +// +// Callbacks created with NewCallback() automatically delete themselves when +// executed. They should be used when a callback is to be called exactly +// once (usually the case with RPC callbacks). If a callback may be called +// a different number of times (including zero), create it with +// NewPermanentCallback() instead. You are then responsible for deleting the +// callback (using the "delete" keyword as normal). +// +// Note that NewCallback() is a bit touchy regarding argument types. Generally, +// the values you provide for the parameter bindings must exactly match the +// types accepted by the callback function. For example: +// void Foo(string s); +// NewCallback(&Foo, "foo"); // WON'T WORK: const char* != string +// NewCallback(&Foo, string("foo")); // WORKS +// Also note that the arguments cannot be references: +// void Foo(const string& s); +// string my_str; +// NewCallback(&Foo, my_str); // WON'T WORK: Can't use referecnes. +// However, correctly-typed pointers will work just fine. + +namespace internal { + +template +inline T* get_pointer(T* p) { + return p; +} + +class FunctionClosure0 : public ::google::protobuf::Closure { + public: + typedef void (*FunctionType)(); + + FunctionClosure0(FunctionType function, bool self_deleting) + : function_(function), self_deleting_(self_deleting) {} + ~FunctionClosure0() {} + + void Run() override { + bool needs_delete = self_deleting_; // read in case callback deletes + function_(); + if (needs_delete) delete this; + } + + private: + FunctionType function_; + bool self_deleting_; +}; + +template +class MethodClosure0 : public ::google::protobuf::Closure { + public: + typedef void (Class::*MethodType)(); + + MethodClosure0(const Pointer& object, MethodType method, bool self_deleting) + : object_(object), method_(method), self_deleting_(self_deleting) {} + ~MethodClosure0() {} + + void Run() { + bool needs_delete = self_deleting_; // read in case callback deletes + (get_pointer(object_)->*method_)(); + if (needs_delete) delete this; + } + + private: + Pointer object_; + MethodType method_; + bool self_deleting_; +}; + +template +class FunctionClosure1 : public ::google::protobuf::Closure { + public: + typedef void (*FunctionType)(Arg1 arg1); + + FunctionClosure1(FunctionType function, bool self_deleting, + Arg1 arg1) + : function_(function), self_deleting_(self_deleting), + arg1_(arg1) {} + ~FunctionClosure1() {} + + void Run() { + bool needs_delete = self_deleting_; // read in case callback deletes + function_(arg1_); + if (needs_delete) delete this; + } + + private: + FunctionType function_; + bool self_deleting_; + Arg1 arg1_; +}; + +template +class MethodClosure1 : public ::google::protobuf::Closure { + public: + typedef void (Class::*MethodType)(Arg1 arg1); + + MethodClosure1(const Pointer& object, MethodType method, bool self_deleting, + Arg1 arg1) + : object_(object), method_(method), self_deleting_(self_deleting), + arg1_(arg1) {} + ~MethodClosure1() {} + + void Run() { + bool needs_delete = self_deleting_; // read in case callback deletes + (get_pointer(object_)->*method_)(arg1_); + if (needs_delete) delete this; + } + + private: + Pointer object_; + MethodType method_; + bool self_deleting_; + Arg1 arg1_; +}; + +template +class FunctionClosure2 : public ::google::protobuf::Closure { + public: + typedef void (*FunctionType)(Arg1 arg1, Arg2 arg2); + + FunctionClosure2(FunctionType function, bool self_deleting, + Arg1 arg1, Arg2 arg2) + : function_(function), self_deleting_(self_deleting), + arg1_(arg1), arg2_(arg2) {} + ~FunctionClosure2() {} + + void Run() { + bool needs_delete = self_deleting_; // read in case callback deletes + function_(arg1_, arg2_); + if (needs_delete) delete this; + } + + private: + FunctionType function_; + bool self_deleting_; + Arg1 arg1_; + Arg2 arg2_; +}; + +template +class MethodClosure2 : public ::google::protobuf::Closure { + public: + typedef void (Class::*MethodType)(Arg1 arg1, Arg2 arg2); + + MethodClosure2(const Pointer& object, MethodType method, bool self_deleting, + Arg1 arg1, Arg2 arg2) + : object_(object), method_(method), self_deleting_(self_deleting), + arg1_(arg1), arg2_(arg2) {} + ~MethodClosure2() {} + + void Run() { + bool needs_delete = self_deleting_; // read in case callback deletes + (get_pointer(object_)->*method_)(arg1_, arg2_); + if (needs_delete) delete this; + } + + private: + Pointer object_; + MethodType method_; + bool self_deleting_; + Arg1 arg1_; + Arg2 arg2_; +}; + +template +class FunctionClosure3 : public ::google::protobuf::Closure { + public: + typedef void (*FunctionType)(Arg1 arg1, Arg2 arg2, Arg3 arg3); + + FunctionClosure3(FunctionType function, bool self_deleting, + Arg1 arg1, Arg2 arg2, Arg3 arg3) + : function_(function), self_deleting_(self_deleting), + arg1_(arg1), arg2_(arg2), arg3_(arg3) {} + ~FunctionClosure3() {} + + void Run() { + bool needs_delete = self_deleting_; // read in case callback deletes + function_(arg1_, arg2_, arg3_); + if (needs_delete) delete this; + } + + private: + FunctionType function_; + bool self_deleting_; + Arg1 arg1_; + Arg2 arg2_; + Arg3 arg3_; +}; + +template +class MethodClosure3 : public ::google::protobuf::Closure { + public: + typedef void (Class::*MethodType)(Arg1 arg1, Arg2 arg2, Arg3 arg3); + + MethodClosure3(const Pointer& object, MethodType method, bool self_deleting, + Arg1 arg1, Arg2 arg2, Arg3 arg3) + : object_(object), method_(method), self_deleting_(self_deleting), + arg1_(arg1), arg2_(arg2),arg3_(arg3) {} + ~MethodClosure3() {} + + void Run() { + bool needs_delete = self_deleting_; // read in case callback deletes + (get_pointer(object_)->*method_)(arg1_, arg2_,arg3_); + if (needs_delete) delete this; + } + + private: + Pointer object_; + MethodType method_; + bool self_deleting_; + Arg1 arg1_; + Arg2 arg2_; + Arg3 arg3_; +}; + +template +class FunctionClosure4 : public ::google::protobuf::Closure { + public: + typedef void (*FunctionType)(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4); + + FunctionClosure4(FunctionType function, bool self_deleting, + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4) + : function_(function), self_deleting_(self_deleting), + arg1_(arg1), arg2_(arg2), arg3_(arg3), arg4_(arg4) {} + ~FunctionClosure4() {} + + void Run() { + bool needs_delete = self_deleting_; // read in case callback deletes + function_(arg1_, arg2_, arg3_, arg4_); + if (needs_delete) delete this; + } + + private: + FunctionType function_; + bool self_deleting_; + Arg1 arg1_; + Arg2 arg2_; + Arg3 arg3_; + Arg4 arg4_; +}; + +template +class MethodClosure4 : public ::google::protobuf::Closure { + public: + typedef void (Class::*MethodType)(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4); + + MethodClosure4(const Pointer& object, MethodType method, bool self_deleting, + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4) + : object_(object), method_(method), self_deleting_(self_deleting), + arg1_(arg1), arg2_(arg2),arg3_(arg3),arg4_(arg4) {} + ~MethodClosure4() {} + + void Run() { + bool needs_delete = self_deleting_; // read in case callback deletes + (get_pointer(object_)->*method_)(arg1_, arg2_,arg3_,arg4_); + if (needs_delete) delete this; + } + + private: + Pointer object_; + MethodType method_; + bool self_deleting_; + Arg1 arg1_; + Arg2 arg2_; + Arg3 arg3_; + Arg4 arg4_; +}; + +template +class FunctionClosure5 : public ::google::protobuf::Closure { + public: + typedef void (*FunctionType)(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5); + + FunctionClosure5(FunctionType function, bool self_deleting, + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5) + : function_(function), self_deleting_(self_deleting), + arg1_(arg1), arg2_(arg2), arg3_(arg3), arg4_(arg4), arg5_(arg5) {} + ~FunctionClosure5() {} + + void Run() { + bool needs_delete = self_deleting_; // read in case callback deletes + function_(arg1_, arg2_, arg3_, arg4_, arg5_); + if (needs_delete) delete this; + } + + private: + FunctionType function_; + bool self_deleting_; + Arg1 arg1_; + Arg2 arg2_; + Arg3 arg3_; + Arg4 arg4_; + Arg5 arg5_; +}; + +template +class MethodClosure5 : public ::google::protobuf::Closure { + public: + typedef void (Class::*MethodType)(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5); + + MethodClosure5(const Pointer& object, MethodType method, bool self_deleting, + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5) + : object_(object), method_(method), self_deleting_(self_deleting), + arg1_(arg1), arg2_(arg2),arg3_(arg3),arg4_(arg4),arg5_(arg5) {} + ~MethodClosure5() {} + + void Run() { + bool needs_delete = self_deleting_; // read in case callback deletes + (get_pointer(object_)->*method_)(arg1_, arg2_,arg3_,arg4_,arg5_); + if (needs_delete) delete this; + } + + private: + Pointer object_; + MethodType method_; + bool self_deleting_; + Arg1 arg1_; + Arg2 arg2_; + Arg3 arg3_; + Arg4 arg4_; + Arg5 arg5_; +}; + +template +class FunctionClosure6 : public ::google::protobuf::Closure { + public: + typedef void (*FunctionType)(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6); + + FunctionClosure6(FunctionType function, bool self_deleting, + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6) + : function_(function), self_deleting_(self_deleting), + arg1_(arg1), arg2_(arg2), arg3_(arg3), arg4_(arg4), arg5_(arg5), arg6_(arg6) {} + ~FunctionClosure6() {} + + void Run() { + bool needs_delete = self_deleting_; // read in case callback deletes + function_(arg1_, arg2_, arg3_, arg4_, arg5_, arg6_); + if (needs_delete) delete this; + } + + private: + FunctionType function_; + bool self_deleting_; + Arg1 arg1_; + Arg2 arg2_; + Arg3 arg3_; + Arg4 arg4_; + Arg5 arg5_; + Arg6 arg6_; +}; + +template +class MethodClosure6 : public ::google::protobuf::Closure { + public: + typedef void (Class::*MethodType)(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6); + + MethodClosure6(const Pointer& object, MethodType method, bool self_deleting, + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6) + : object_(object), method_(method), self_deleting_(self_deleting), + arg1_(arg1), arg2_(arg2),arg3_(arg3),arg4_(arg4),arg5_(arg5),arg6_(arg6) {} + ~MethodClosure6() {} + + void Run() { + bool needs_delete = self_deleting_; // read in case callback deletes + (get_pointer(object_)->*method_)(arg1_, arg2_,arg3_,arg4_,arg5_,arg6_); + if (needs_delete) delete this; + } + + private: + Pointer object_; + MethodType method_; + bool self_deleting_; + Arg1 arg1_; + Arg2 arg2_; + Arg3 arg3_; + Arg4 arg4_; + Arg5 arg5_; + Arg6 arg6_; +}; + +template +class FunctionClosure7 : public ::google::protobuf::Closure { + public: + typedef void (*FunctionType)(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7); + + FunctionClosure7(FunctionType function, bool self_deleting, + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7) + : function_(function), self_deleting_(self_deleting), + arg1_(arg1), arg2_(arg2), arg3_(arg3), arg4_(arg4), arg5_(arg5), arg6_(arg6), arg7_(arg7) {} + ~FunctionClosure7() {} + + void Run() { + bool needs_delete = self_deleting_; // read in case callback deletes + function_(arg1_, arg2_, arg3_, arg4_, arg5_, arg6_, arg7_); + if (needs_delete) delete this; + } + + private: + FunctionType function_; + bool self_deleting_; + Arg1 arg1_; + Arg2 arg2_; + Arg3 arg3_; + Arg4 arg4_; + Arg5 arg5_; + Arg6 arg6_; + Arg7 arg7_; +}; + +template +class MethodClosure7 : public ::google::protobuf::Closure { + public: + typedef void (Class::*MethodType)(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7); + + MethodClosure7(const Pointer& object, MethodType method, bool self_deleting, + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7) + : object_(object), method_(method), self_deleting_(self_deleting), + arg1_(arg1), arg2_(arg2),arg3_(arg3),arg4_(arg4),arg5_(arg5),arg6_(arg6),arg7_(arg7) {} + ~MethodClosure7() {} + + void Run() { + bool needs_delete = self_deleting_; // read in case callback deletes + (get_pointer(object_)->*method_)(arg1_, arg2_,arg3_,arg4_,arg5_,arg6_,arg7_); + if (needs_delete) delete this; + } + + private: + Pointer object_; + MethodType method_; + bool self_deleting_; + Arg1 arg1_; + Arg2 arg2_; + Arg3 arg3_; + Arg4 arg4_; + Arg5 arg5_; + Arg6 arg6_; + Arg7 arg7_; +}; + + +template +class FunctionClosure8 : public ::google::protobuf::Closure { + public: + typedef void (*FunctionType)(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8); + + FunctionClosure8(FunctionType function, bool self_deleting, + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8) + : function_(function), self_deleting_(self_deleting), + arg1_(arg1), arg2_(arg2), arg3_(arg3), arg4_(arg4), arg5_(arg5), arg6_(arg6), arg7_(arg7), arg8_(arg8) {} + ~FunctionClosure8() {} + + void Run() { + bool needs_delete = self_deleting_; // read in case callback deletes + function_(arg1_, arg2_, arg3_, arg4_, arg5_, arg6_, arg7_,arg8_); + if (needs_delete) delete this; + } + + private: + FunctionType function_; + bool self_deleting_; + Arg1 arg1_; + Arg2 arg2_; + Arg3 arg3_; + Arg4 arg4_; + Arg5 arg5_; + Arg6 arg6_; + Arg7 arg7_; + Arg8 arg8_; +}; + +template +class MethodClosure8 : public ::google::protobuf::Closure { + public: + typedef void (Class::*MethodType)(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8); + + MethodClosure8(const Pointer& object, MethodType method, bool self_deleting, + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8) + : object_(object), method_(method), self_deleting_(self_deleting), + arg1_(arg1), arg2_(arg2),arg3_(arg3),arg4_(arg4),arg5_(arg5),arg6_(arg6),arg7_(arg7),arg8_(arg8) {} + ~MethodClosure8() {} + + void Run() { + bool needs_delete = self_deleting_; // read in case callback deletes + (get_pointer(object_)->*method_)(arg1_, arg2_,arg3_,arg4_,arg5_,arg6_,arg7_,arg8_); + if (needs_delete) delete this; + } + + private: + Pointer object_; + MethodType method_; + bool self_deleting_; + Arg1 arg1_; + Arg2 arg2_; + Arg3 arg3_; + Arg4 arg4_; + Arg5 arg5_; + Arg6 arg6_; + Arg7 arg7_; + Arg8 arg8_; +}; + + +template +class FunctionClosure9 : public ::google::protobuf::Closure { + public: + typedef void (*FunctionType)(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8, Arg9 arg9); + + FunctionClosure9(FunctionType function, bool self_deleting, + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8, Arg9 arg9) + : function_(function), self_deleting_(self_deleting), + arg1_(arg1), arg2_(arg2), arg3_(arg3), arg4_(arg4), arg5_(arg5), arg6_(arg6), arg7_(arg7), arg8_(arg8), arg9_(arg9) {} + ~FunctionClosure9() {} + + void Run() { + bool needs_delete = self_deleting_; // read in case callback deletes + function_(arg1_, arg2_, arg3_, arg4_, arg5_, arg6_, arg7_,arg8_,arg9_); + if (needs_delete) delete this; + } + + private: + FunctionType function_; + bool self_deleting_; + Arg1 arg1_; + Arg2 arg2_; + Arg3 arg3_; + Arg4 arg4_; + Arg5 arg5_; + Arg6 arg6_; + Arg7 arg7_; + Arg8 arg8_; + Arg9 arg9_; +}; + +template +class MethodClosure9 : public ::google::protobuf::Closure { + public: + typedef void (Class::*MethodType)(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8, Arg9 arg9); + + MethodClosure9(const Pointer& object, MethodType method, bool self_deleting, + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8, Arg9 arg9) + : object_(object), method_(method), self_deleting_(self_deleting), + arg1_(arg1), arg2_(arg2),arg3_(arg3),arg4_(arg4),arg5_(arg5),arg6_(arg6),arg7_(arg7),arg8_(arg8),arg9_(arg9) {} + ~MethodClosure9() {} + + void Run() { + bool needs_delete = self_deleting_; // read in case callback deletes + (get_pointer(object_)->*method_)(arg1_, arg2_,arg3_,arg4_,arg5_,arg6_,arg7_,arg8_,arg9_); + if (needs_delete) delete this; + } + + private: + Pointer object_; + MethodType method_; + bool self_deleting_; + Arg1 arg1_; + Arg2 arg2_; + Arg3 arg3_; + Arg4 arg4_; + Arg5 arg5_; + Arg6 arg6_; + Arg7 arg7_; + Arg8 arg8_; + Arg9 arg9_; +}; + +template +class FunctionClosure10 : public ::google::protobuf::Closure { + public: + typedef void (*FunctionType)(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8, Arg9 arg9, Arg10 arg10); + + FunctionClosure10(FunctionType function, bool self_deleting, + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8, Arg9 arg9, Arg10 arg10) + : function_(function), self_deleting_(self_deleting), + arg1_(arg1), arg2_(arg2), arg3_(arg3), arg4_(arg4), arg5_(arg5), arg6_(arg6), arg7_(arg7), arg8_(arg8), arg9_(arg9), arg10_(arg10) {} + ~FunctionClosure10() {} + + void Run() { + bool needs_delete = self_deleting_; // read in case callback deletes + function_(arg1_, arg2_, arg3_, arg4_, arg5_, arg6_, arg7_,arg8_,arg9_,arg10_); + if (needs_delete) delete this; + } + + private: + FunctionType function_; + bool self_deleting_; + Arg1 arg1_; + Arg2 arg2_; + Arg3 arg3_; + Arg4 arg4_; + Arg5 arg5_; + Arg6 arg6_; + Arg7 arg7_; + Arg8 arg8_; + Arg9 arg9_; + Arg10 arg10_; +}; + +template +class MethodClosure10 : public ::google::protobuf::Closure { + public: + typedef void (Class::*MethodType)(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8, Arg9 arg9, Arg10 arg10); + + MethodClosure10(const Pointer& object, MethodType method, bool self_deleting, + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8, Arg9 arg9, Arg10 arg10) + : object_(object), method_(method), self_deleting_(self_deleting), + arg1_(arg1), arg2_(arg2),arg3_(arg3),arg4_(arg4),arg5_(arg5),arg6_(arg6),arg7_(arg7),arg8_(arg8),arg9_(arg9),arg10_(arg10) {} + ~MethodClosure10() {} + + void Run() { + bool needs_delete = self_deleting_; // read in case callback deletes + (get_pointer(object_)->*method_)(arg1_, arg2_,arg3_,arg4_,arg5_,arg6_,arg7_,arg8_,arg9_,arg10_); + if (needs_delete) delete this; + } + + private: + Pointer object_; + MethodType method_; + bool self_deleting_; + Arg1 arg1_; + Arg2 arg2_; + Arg3 arg3_; + Arg4 arg4_; + Arg5 arg5_; + Arg6 arg6_; + Arg7 arg7_; + Arg8 arg8_; + Arg9 arg9_; + Arg10 arg10_; +}; + +} // namespace internal + +// See Closure. +inline ::google::protobuf::Closure* NewCallback(void (*function)()) { + return new internal::FunctionClosure0(function, true); +} + +// See Closure. +inline ::google::protobuf::Closure* NewPermanentCallback(void (*function)()) { + return new internal::FunctionClosure0(function, false); +} + +// See Closure. +template +inline ::google::protobuf::Closure* NewCallback(const Pointer& object, void (Class::*method)()) { + return new internal::MethodClosure0(object, method, true); +} + +// See Closure. +template +inline ::google::protobuf::Closure* NewPermanentCallback(const Pointer& object, void (Class::*method)()) { + return new internal::MethodClosure0(object, method, false); +} + +// See Closure. +template +inline ::google::protobuf::Closure* NewCallback(void (*function)(Arg1), + Arg1 arg1) { + return new internal::FunctionClosure1(function, true, arg1); +} + +// See Closure. +template +inline ::google::protobuf::Closure* NewPermanentCallback(void (*function)(Arg1), + Arg1 arg1) { + return new internal::FunctionClosure1(function, false, arg1); +} + +// See Closure. +template +inline ::google::protobuf::Closure* NewCallback(const Pointer& object, void (Class::*method)(Arg1), + Arg1 arg1) { + return new internal::MethodClosure1(object, method, true, arg1); +} + +// See Closure. +template +inline ::google::protobuf::Closure* NewPermanentCallback(const Pointer& object, void (Class::*method)(Arg1), + Arg1 arg1) { + return new internal::MethodClosure1(object, method, false, arg1); +} + +// See Closure. +template +inline ::google::protobuf::Closure* NewCallback(void (*function)(Arg1, Arg2), + Arg1 arg1, Arg2 arg2) { + return new internal::FunctionClosure2( + function, true, arg1, arg2); +} + +// See Closure. +template +inline ::google::protobuf::Closure* NewPermanentCallback(void (*function)(Arg1, Arg2), + Arg1 arg1, Arg2 arg2) { + return new internal::FunctionClosure2( + function, false, arg1, arg2); +} + +// See Closure. +template +inline ::google::protobuf::Closure* NewCallback(const Pointer& object, void (Class::*method)(Arg1, Arg2), + Arg1 arg1, Arg2 arg2) { + return new internal::MethodClosure2( + object, method, true, arg1, arg2); +} + +// See Closure. +template +inline ::google::protobuf::Closure* NewPermanentCallback( + const Pointer& object, void (Class::*method)(Arg1, Arg2), + Arg1 arg1, Arg2 arg2) { + return new internal::MethodClosure2( + object, method, false, arg1, arg2); +} + +// See Closure. +template +inline ::google::protobuf::Closure* NewCallback(void (*function)(Arg1, Arg2, Arg3), + Arg1 arg1, Arg2 arg2, Arg3 arg3) { + return new internal::FunctionClosure3( + function, true, arg1, arg2, arg3); +} + +// See Closure. +template + inline ::google::protobuf::Closure* NewPermanentCallback(void (*function)(Arg1, Arg2, Arg3), + Arg1 arg1, Arg2 arg2, Arg3 arg3) { + return new internal::FunctionClosure3( + function, false, arg1, arg2, arg3); +} + +// See Closure +template +inline ::google::protobuf::Closure* NewCallback( + const Pointer& object, void (Class::*method)(Arg1, Arg2, Arg3), + Arg1 arg1, Arg2 arg2, Arg3 arg3) { + return new internal::MethodClosure3( + object, method, true, arg1, arg2, arg3); +} + +// See Closure +template + inline ::google::protobuf::Closure* NewPermanentCallback( + const Pointer& object, void (Class::*method)(Arg1, Arg2, Arg3), + Arg1 arg1, Arg2 arg2, Arg3 arg3) { + return new internal::MethodClosure3( + object, method, false, arg1, arg2, arg3); +} + +// See Closure. +template +inline ::google::protobuf::Closure* NewCallback(void (*function)(Arg1, Arg2, Arg3, Arg4), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4) { + return new internal::FunctionClosure4( + function, true, arg1, arg2, arg3, arg4); +} + +// See Closure. +template +inline ::google::protobuf::Closure* NewPermanentCallback(void (*function)(Arg1, Arg2, Arg3, Arg4), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4) { + return new internal::FunctionClosure4( + function, false, arg1, arg2, arg3, arg4); +} + +// See Closure. +template +inline ::google::protobuf::Closure* NewCallback( + const Pointer& object, void (Class::*method)(Arg1, Arg2, Arg3, Arg4), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4) { + return new internal::MethodClosure4( + object, method, true, arg1, arg2, arg3, arg4); +} + +// See Closure. +template +inline ::google::protobuf::Closure* NewPermanentCallback( + const Pointer& object, void (Class::*method)(Arg1, Arg2, Arg3, Arg4), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4) { + return new internal::MethodClosure4( + object, method, false, arg1, arg2, arg3, arg4); +} + +// See Closure. +template +inline ::google::protobuf::Closure* NewCallback(void (*function)(Arg1, Arg2, Arg3, Arg4, Arg5), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5) { + return new internal::FunctionClosure5( + function, true, arg1, arg2, arg3, arg4, arg5); +} + +// See Closure. +template +inline ::google::protobuf::Closure* NewPermanentCallback(void (*function)(Arg1, Arg2, Arg3, Arg4, Arg5), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5) { + return new internal::FunctionClosure5( + function, false, arg1, arg2, arg3, arg4, arg5); +} + +// See Closure +template +inline ::google::protobuf::Closure* NewCallback( + const Pointer& object, void (Class::*method)(Arg1, Arg2, Arg3, Arg4, Arg5), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5) { + return new internal::MethodClosure5( + object, method, true, arg1, arg2, arg3, arg4, arg5); +} + +// See Closure +template +inline ::google::protobuf::Closure* NewPermanentCallback( + const Pointer& object, void (Class::*method)(Arg1, Arg2, Arg3, Arg4, Arg5), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5) { + return new internal::MethodClosure5( + object, method, false, arg1, arg2, arg3, arg4, arg5); +} + +// See Closure. +template +inline ::google::protobuf::Closure* NewCallback(void (*function)(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6) { + return new internal::FunctionClosure6( + function, true, arg1, arg2, arg3, arg4, arg5, arg6); +} + +// See Closure. +template +inline ::google::protobuf::Closure* NewPermanentCallback(void (*function)(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6) { + return new internal::FunctionClosure6( + function, false, arg1, arg2, arg3, arg4, arg5, arg6); +} + +// See Closure +template +inline ::google::protobuf::Closure* NewCallback( + const Pointer& object, void (Class::*method)(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6) { + return new internal::MethodClosure6( + object, method, true, arg1, arg2, arg3, arg4, arg5, arg6); +} + +// See Closure +template +inline ::google::protobuf::Closure* NewPermanentCallback( + const Pointer& object, void (Class::*method)(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6) { + return new internal::MethodClosure6( + object, method, false, arg1, arg2, arg3, arg4, arg5, arg6); +} + +// See Closure +template +inline ::google::protobuf::Closure* NewCallback(void (*function)(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7) { + return new internal::FunctionClosure7( + function, true, arg1, arg2, arg3, arg4, arg5, arg6, arg7); +} + +// See Closure. +template +inline ::google::protobuf::Closure* NewPermanentCallback(void (*function)(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7) { + return new internal::FunctionClosure7( + function, false, arg1, arg2, arg3, arg4, arg5, arg6, arg7); +} + + +// See Closure +template +inline ::google::protobuf::Closure* NewCallback( + const Pointer& object, void (Class::*method)(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7) { + return new internal::MethodClosure7( + object, method, true, arg1, arg2, arg3, arg4, arg5, arg6, arg7); +} + + +// See Closure +template +inline ::google::protobuf::Closure* NewPermanentCallback( + const Pointer& object, void (Class::*method)(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7) { + return new internal::MethodClosure7( + object, method, false, arg1, arg2, arg3, arg4, arg5, arg6, arg7); +} + + + +// See Closure. +template +inline ::google::protobuf::Closure* NewCallback(void (*function)(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8) { + return new internal::FunctionClosure8( + function, true, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); +} + + +// See Closure. +template +inline ::google::protobuf::Closure* NewPermanentCallback(void (*function)(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8) { + return new internal::FunctionClosure8( + function, false, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); +} + + + +// See Closure +template +inline ::google::protobuf::Closure* NewCallback( + const Pointer& object, void (Class::*method)(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8) { + return new internal::MethodClosure8( + object, method, true, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); +} + + +// See Closure +template +inline ::google::protobuf::Closure* NewPermanentCallback( + const Pointer& object, void (Class::*method)(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8) { + return new internal::MethodClosure8( + object, method, false, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); +} + + +// See Closure. +template +inline ::google::protobuf::Closure* NewCallback(void (*function)(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8, Arg9 arg9) { + return new internal::FunctionClosure9( + function, true, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); +} + + +// See Closure. +template +inline ::google::protobuf::Closure* NewPermanentCallback(void (*function)(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8, Arg9 arg9) { + return new internal::FunctionClosure9( + function, false, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); +} + + +// See Closure +template +inline ::google::protobuf::Closure* NewCallback( + const Pointer& object, void (Class::*method)(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8, Arg9 arg9) { + return new internal::MethodClosure9( + object, method, true, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); +} + + +// See Closure +template +inline ::google::protobuf::Closure* NewPermanentCallback( + const Pointer& object, void (Class::*method)(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8, Arg9 arg9) { + return new internal::MethodClosure9( + object, method, false, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); +} + +// See Closure. +template +inline ::google::protobuf::Closure* NewCallback(void (*function)(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8, Arg9 arg9, Arg10 arg10) { + return new internal::FunctionClosure10( + function, true, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); +} + + +// See Closure. +template +inline ::google::protobuf::Closure* NewPermanentCallback(void (*function)(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8, Arg9 arg9, Arg10 arg10) { + return new internal::FunctionClosure10( + function, false, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); +} + + +// See Closure +template +inline ::google::protobuf::Closure* NewCallback( + const Pointer& object, void (Class::*method)(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8, Arg9 arg9, Arg10 arg10) { + return new internal::MethodClosure10( + object, method, true, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); +} + + +// See Closure +template +inline ::google::protobuf::Closure* NewPermanentCallback( + const Pointer& object, void (Class::*method)(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10), + Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8, Arg9 arg9, Arg10 arg10) { + return new internal::MethodClosure10( + object, method, false, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); +} + +} // namespace brpc + + +#endif // BRPC_CALLBACK_H diff --git a/src/brpc/channel.cpp b/src/brpc/channel.cpp new file mode 100644 index 0000000..a8caeaf --- /dev/null +++ b/src/brpc/channel.cpp @@ -0,0 +1,661 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include "butil/time.h" // milliseconds_from_now +#include "butil/logging.h" +#include "butil/third_party/murmurhash3/murmurhash3.h" +#include "butil/strings/string_util.h" +#include "bthread/unstable.h" // bthread_timer_add +#include "brpc/socket_map.h" // SocketMapInsert +#include "brpc/compress.h" +#include "brpc/global.h" +#include "brpc/span.h" +#include "brpc/details/load_balancer_with_naming.h" +#include "brpc/controller.h" +#include "brpc/channel.h" +#include "brpc/serialized_request.h" +#include "brpc/serialized_response.h" +#include "brpc/details/usercode_backup_pool.h" // TooManyUserCode +#include "brpc/rdma/rdma_helper.h" +#include "brpc/policy/esp_authenticator.h" +#include "brpc/transport_factory.h" +#include "brpc/details/controller_private_accessor.h" + +namespace brpc { + +DECLARE_bool(enable_rpcz); +DECLARE_bool(usercode_in_pthread); +DEFINE_string(health_check_path, "", "Http path of health check call." + "By default health check succeeds if the server is connectable." + "If this flag is set, health check is not completed until a http " + "call to the path succeeds within -health_check_timeout_ms(to make " + "sure the server functions well)."); +DEFINE_int32(health_check_timeout_ms, 500, "The timeout for both establishing " + "the connection and the http call to -health_check_path over the connection"); + +ChannelOptions::ChannelOptions() + : connect_timeout_ms(200) + , timeout_ms(500) + , backup_request_ms(-1) + , max_retry(3) + , enable_circuit_breaker(false) + , protocol(PROTOCOL_BAIDU_STD) + , connection_type(CONNECTION_TYPE_UNKNOWN) + , succeed_without_server(true) + , log_succeed_without_server(true) + , socket_mode(SOCKET_MODE_TCP) + , auth(NULL) + , backup_request_policy(NULL) + , retry_policy(NULL) + , ns_filter(NULL) +{} + +ChannelSSLOptions* ChannelOptions::mutable_ssl_options() { + if (!_ssl_options) { + _ssl_options.reset(new ChannelSSLOptions); + } + return _ssl_options.get(); +} + +static ChannelSignature ComputeChannelSignature(const ChannelOptions& opt) { + if (opt.auth == NULL && + !opt.has_ssl_options() && + opt.client_host.empty() && + opt.device_name.empty() && + opt.connection_group.empty() && + opt.hc_option.health_check_path.empty()) { + // Returning zeroized result by default is more intuitive for users. + return ChannelSignature(); + } + uint32_t seed = 0; + std::string buf; + buf.reserve(1024); + butil::MurmurHash3_x64_128_Context mm_ctx; + do { + buf.clear(); + butil::MurmurHash3_x64_128_Init(&mm_ctx, seed); + + if (!opt.connection_group.empty()) { + buf.append("|conng="); + buf.append(opt.connection_group); + } + if (!opt.client_host.empty()) { + buf.append("|clih="); + buf.append(opt.client_host); + } + if (!opt.device_name.empty()) { + buf.append("|devn="); + buf.append(opt.device_name); + } + if (opt.auth) { + buf.append("|auth="); + buf.append((char*)&opt.auth, sizeof(opt.auth)); + } + if (!opt.hc_option.health_check_path.empty()) { + buf.append("|health_check_path="); + buf.append(opt.hc_option.health_check_path); + buf.append("|health_check_timeout_ms="); + buf.append(std::to_string(opt.hc_option.health_check_timeout_ms)); + } + if (opt.has_ssl_options()) { + const ChannelSSLOptions& ssl = opt.ssl_options(); + buf.push_back('|'); + buf.append(ssl.ciphers); + buf.push_back('|'); + buf.append(ssl.protocols); + buf.push_back('|'); + buf.append(ssl.sni_name); + const VerifyOptions& verify = ssl.verify; + buf.push_back('|'); + buf.append((char*)&verify.verify_depth, sizeof(verify.verify_depth)); + buf.push_back('|'); + buf.append(verify.ca_file_path); + } else { + // All disabled ChannelSSLOptions are the same + } + if (opt.socket_mode == SOCKET_MODE_RDMA) { + buf.append("|rdma"); + } + butil::MurmurHash3_x64_128_Update(&mm_ctx, buf.data(), buf.size()); + buf.clear(); + + if (opt.has_ssl_options()) { + const CertInfo& cert = opt.ssl_options().client_cert; + if (!cert.certificate.empty()) { + // Certificate may be too long (PEM string) to fit into `buf' + butil::MurmurHash3_x64_128_Update( + &mm_ctx, cert.certificate.data(), cert.certificate.size()); + butil::MurmurHash3_x64_128_Update( + &mm_ctx, cert.private_key.data(), cert.private_key.size()); + } + } + // sni_filters has no effect in ChannelSSLOptions + ChannelSignature result; + butil::MurmurHash3_x64_128_Final(result.data, &mm_ctx); + if (result != ChannelSignature()) { + // the empty result is reserved for default case and cannot + // be used, increment the seed and retry. + return result; + } + ++seed; + } while (true); +} + +Channel::Channel(ProfilerLinker) + : _server_id(INVALID_SOCKET_ID) + , _serialize_request(NULL) + , _pack_request(NULL) + , _get_method_name(NULL) + , _preferred_index(-1) { +} + +Channel::~Channel() { + if (_server_id != INVALID_SOCKET_ID) { + const ChannelSignature sig = ComputeChannelSignature(_options); + SocketMapRemove(SocketMapKey(_server_address, sig)); + } +} + + +int Channel::InitChannelOptions(const ChannelOptions* options) { + if (options) { // Override default options if user provided one. + _options = *options; + } + const Protocol* protocol = FindProtocol(_options.protocol); + if (NULL == protocol || !protocol->support_client()) { + LOG(ERROR) << "Channel does not support the protocol"; + return -1; + } + if (_options.hc_option.health_check_path.empty()) { + _options.hc_option.health_check_path = FLAGS_health_check_path; + _options.hc_option.health_check_timeout_ms = FLAGS_health_check_timeout_ms; + } + auto ret = TransportFactory::ContextInitOrDie(_options.socket_mode, false, &_options); + if (ret != 0) { + LOG(ERROR) << "Fail to initialize transport context for channel, ret=" << ret; + return -1; + } + + _serialize_request = protocol->serialize_request; + _pack_request = protocol->pack_request; + _get_method_name = protocol->get_method_name; + + // Check connection_type + if (_options.connection_type == CONNECTION_TYPE_UNKNOWN) { + // Save has_error which will be overriden in later assignments to + // connection_type. + const bool has_error = _options.connection_type.has_error(); + + if (protocol->supported_connection_type & CONNECTION_TYPE_SINGLE) { + _options.connection_type = CONNECTION_TYPE_SINGLE; + } else if (protocol->supported_connection_type & CONNECTION_TYPE_POOLED) { + _options.connection_type = CONNECTION_TYPE_POOLED; + } else { + _options.connection_type = CONNECTION_TYPE_SHORT; + } + if (has_error) { + LOG(ERROR) << "Channel=" << this << " chose connection_type=" + << _options.connection_type.name() << " for protocol=" + << _options.protocol.name(); + } + } else { + if (!(_options.connection_type & protocol->supported_connection_type)) { + LOG(ERROR) << protocol->name << " does not support connection_type=" + << ConnectionTypeToString(_options.connection_type); + return -1; + } + } + + _preferred_index = get_client_side_messenger()->FindProtocolIndex(_options.protocol); + if (_preferred_index < 0) { + LOG(ERROR) << "Fail to get index for protocol=" + << _options.protocol.name(); + return -1; + } + + if (_options.protocol == PROTOCOL_ESP) { + if (_options.auth == NULL) { + _options.auth = policy::global_esp_authenticator(); + } + } + + // Normalize connection_group + std::string& cg = _options.connection_group; + if (!cg.empty() && (::isspace(cg.front()) || ::isspace(cg.back()))) { + butil::TrimWhitespace(cg, butil::TRIM_ALL, &cg); + } + + return 0; +} + +int Channel::Init(const char* server_addr_and_port, + const ChannelOptions* options) { + GlobalInitializeOrDie(); + butil::EndPoint point; + const AdaptiveProtocolType& ptype = (options ? options->protocol : _options.protocol); + const Protocol* protocol = FindProtocol(ptype); + if (protocol == NULL || !protocol->support_client()) { + LOG(ERROR) << "Channel does not support the protocol"; + return -1; + } + if (protocol->parse_server_address != NULL) { + if (!protocol->parse_server_address(&point, server_addr_and_port)) { + LOG(ERROR) << "Fail to parse address=`" << server_addr_and_port << '\''; + return -1; + } + } else { + if (str2endpoint(server_addr_and_port, &point) != 0 && + hostname2endpoint(server_addr_and_port, &point) != 0) { + // Many users called the wrong Init(). Print some log to save + // our troubleshooting time. + if (strstr(server_addr_and_port, "://")) { + LOG(ERROR) << "Invalid address=`" << server_addr_and_port + << "'. Use Init(naming_service_name, " + "load_balancer_name, options) instead."; + } else { + LOG(ERROR) << "Invalid address=`" << server_addr_and_port << '\''; + } + return -1; + } + } + return InitSingle(point, server_addr_and_port, options); +} + +int Channel::Init(const char* server_addr, int port, + const ChannelOptions* options) { + GlobalInitializeOrDie(); + butil::EndPoint point; + const AdaptiveProtocolType& ptype = (options ? options->protocol : _options.protocol); + const Protocol* protocol = FindProtocol(ptype); + if (protocol == NULL || !protocol->support_client()) { + LOG(ERROR) << "Channel does not support the protocol"; + return -1; + } + if (protocol->parse_server_address != NULL) { + if (!protocol->parse_server_address(&point, server_addr)) { + LOG(ERROR) << "Fail to parse address=`" << server_addr << '\''; + return -1; + } + point.port = port; + } else { + if (str2endpoint(server_addr, port, &point) != 0 && + hostname2endpoint(server_addr, port, &point) != 0) { + LOG(ERROR) << "Invalid address=`" << server_addr << '\''; + return -1; + } + } + return InitSingle(point, server_addr, options, port); +} + +static int CreateSocketSSLContext(const ChannelOptions& options, + std::shared_ptr* ssl_ctx) { + if (options.has_ssl_options()) { + SSL_CTX* raw_ctx = CreateClientSSLContext(options.ssl_options()); + if (!raw_ctx) { + LOG(ERROR) << "Fail to CreateClientSSLContext"; + return -1; + } + *ssl_ctx = std::make_shared(); + (*ssl_ctx)->raw_ctx = raw_ctx; + (*ssl_ctx)->sni_name = options.ssl_options().sni_name; + (*ssl_ctx)->alpn_protocols = options.ssl_options().alpn_protocols; + } else { + (*ssl_ctx) = NULL; + } + return 0; +} + +int Channel::Init(butil::EndPoint server_addr_and_port, + const ChannelOptions* options) { + return InitSingle(server_addr_and_port, "", options); +} + +int Channel::InitSingle(const butil::EndPoint& server_addr_and_port, + const char* raw_server_address, + const ChannelOptions* options, + int raw_port) { + GlobalInitializeOrDie(); + if (InitChannelOptions(options) != 0) { + return -1; + } + int* port_out = raw_port == -1 ? &raw_port: NULL; + ParseURL(raw_server_address, &_scheme, &_service_name, port_out); + if (raw_port != -1) { + _service_name.append(":").append(std::to_string(raw_port)); + } + if (_options.protocol == brpc::PROTOCOL_HTTP && _scheme == "https") { + if (_options.mutable_ssl_options()->sni_name.empty()) { + _options.mutable_ssl_options()->sni_name = _service_name; + } + } + const int port = server_addr_and_port.port; + if (port < 0) { + LOG(ERROR) << "Invalid port=" << port; + return -1; + } + butil::EndPoint client_endpoint; + if (!_options.client_host.empty() && + butil::str2ip(_options.client_host.c_str(), &client_endpoint.ip) != 0 && + butil::hostname2ip(_options.client_host.c_str(), &client_endpoint.ip) != 0) { + LOG(ERROR) << "Invalid client host=`" << _options.client_host << '\''; + return -1; + } + _server_address = server_addr_and_port; + const ChannelSignature sig = ComputeChannelSignature(_options); + std::shared_ptr ssl_ctx; + if (CreateSocketSSLContext(_options, &ssl_ctx) != 0) { + return -1; + } + SocketOptions opt; + opt.local_side = client_endpoint; + opt.initial_ssl_ctx = ssl_ctx; + opt.socket_mode = _options.socket_mode; + opt.hc_option = _options.hc_option; + opt.device_name = _options.device_name; + if (SocketMapInsert(SocketMapKey(server_addr_and_port, sig), + &_server_id, opt) != 0) { + LOG(ERROR) << "Fail to insert into SocketMap"; + return -1; + } + return 0; +} + +int Channel::Init(const char* ns_url, + const char* lb_name, + const ChannelOptions* options) { + if (lb_name == NULL || *lb_name == '\0') { + // Treat ns_url as server_addr_and_port + return Init(ns_url, options); + } + GlobalInitializeOrDie(); + if (InitChannelOptions(options) != 0) { + return -1; + } + int raw_port = -1; + ParseURL(ns_url, &_scheme, &_service_name, &raw_port); + if (raw_port != -1) { + _service_name.append(":").append(std::to_string(raw_port)); + } + if (_options.protocol == brpc::PROTOCOL_HTTP && _scheme == "https") { + if (_options.mutable_ssl_options()->sni_name.empty()) { + _options.mutable_ssl_options()->sni_name = _service_name; + } + } + butil::EndPoint client_endpoint; + if (!_options.client_host.empty() && + butil::str2ip(_options.client_host.c_str(), &client_endpoint.ip) != 0 && + butil::hostname2ip(_options.client_host.c_str(), &client_endpoint.ip) != 0) { + LOG(ERROR) << "Invalid client host=`" << _options.client_host << '\''; + return -1; + } + std::unique_ptr lb(new (std::nothrow) + LoadBalancerWithNaming); + if (NULL == lb) { + LOG(FATAL) << "Fail to new LoadBalancerWithNaming"; + return -1; + } + GetNamingServiceThreadOptions ns_opt; + ns_opt.succeed_without_server = _options.succeed_without_server; + ns_opt.log_succeed_without_server = _options.log_succeed_without_server; + ns_opt.socket_option.socket_mode = _options.socket_mode; + ns_opt.channel_signature = ComputeChannelSignature(_options); + ns_opt.socket_option.hc_option = _options.hc_option; + ns_opt.socket_option.local_side = client_endpoint; + ns_opt.socket_option.device_name = _options.device_name; + if (CreateSocketSSLContext(_options, + &ns_opt.socket_option.initial_ssl_ctx) != 0) { + return -1; + } + if (lb->Init(ns_url, lb_name, _options.ns_filter, &ns_opt) != 0) { + LOG(ERROR) << "Fail to initialize LoadBalancerWithNaming"; + return -1; + } + _lb.reset(lb.release()); + return 0; +} + +static void HandleTimeout(void* arg) { + bthread_id_t correlation_id = { (uint64_t)arg }; + bthread_id_error(correlation_id, ERPCTIMEDOUT); +} + +static void HandleBackupRequest(void* arg) { + bthread_id_t correlation_id = { (uint64_t)arg }; + bthread_id_error(correlation_id, EBACKUPREQUEST); +} + +void Channel::CallMethod(const google::protobuf::MethodDescriptor* method, + google::protobuf::RpcController* controller_base, + const google::protobuf::Message* request, + google::protobuf::Message* response, + google::protobuf::Closure* done) { + const int64_t start_send_real_us = butil::gettimeofday_us(); + Controller* cntl = static_cast(controller_base); + cntl->OnRPCBegin(start_send_real_us); + // Override max_retry first to reset the range of correlation_id + if (cntl->max_retry() == UNSET_MAGIC_NUM) { + cntl->set_max_retry(_options.max_retry); + } + if (cntl->max_retry() < 0) { + // this is important because #max_retry decides #versions allocated + // in correlation_id. negative max_retry causes undefined behavior. + cntl->set_max_retry(0); + } + // HTTP needs this field to be set before any SetFailed() + cntl->_request_protocol = _options.protocol; + if (_options.protocol.has_param()) { + CHECK(cntl->protocol_param().empty()); + cntl->protocol_param() = _options.protocol.param(); + } + if (_options.protocol == brpc::PROTOCOL_HTTP && (_scheme == "https" || _scheme == "http")) { + URI& uri = cntl->http_request().uri(); + if (uri.host().empty() && !_service_name.empty()) { + uri.SetHostAndPort(_service_name); + } + } + cntl->_preferred_index = _preferred_index; + cntl->_retry_policy = _options.retry_policy; + if (_options.enable_circuit_breaker) { + cntl->add_flag(Controller::FLAGS_ENABLED_CIRCUIT_BREAKER); + } + const CallId correlation_id = cntl->call_id(); + const int rc = bthread_id_lock_and_reset_range( + correlation_id, NULL, 2 + cntl->max_retry()); + if (rc != 0) { + CHECK_EQ(EINVAL, rc); + if (!cntl->FailedInline()) { + cntl->SetFailed(EINVAL, "Fail to lock call_id=%" PRId64, + correlation_id.value); + } + LOG_IF(ERROR, cntl->is_used_by_rpc()) + << "Controller=" << cntl << " was used by another RPC before. " + "Did you forget to Reset() it before reuse?"; + // Have to run done in-place. If the done runs in another thread, + // Join() on this RPC is no-op and probably ends earlier than running + // the callback and releases resources used in the callback. + // Since this branch is only entered by wrongly-used RPC, the + // potentially introduced deadlock(caused by locking RPC and done with + // the same non-recursive lock) is acceptable and removable by fixing + // user's code. + if (done) { + done->Run(); + } + return; + } + cntl->set_used_by_rpc(); + + if (cntl->_sender == NULL && IsTraceable(Span::tls_parent().get())) { + const int64_t start_send_us = butil::cpuwide_time_us(); + std::string method_name; + if (_get_method_name) { + method_name = butil::EnsureString(_get_method_name(method, cntl)); + } else if (method) { + method_name = butil::EnsureString(method->full_name()); + } else { + const static std::string NULL_METHOD_STR = "null-method"; + method_name = NULL_METHOD_STR; + } + std::shared_ptr span = Span::CreateClientSpan( + method_name, start_send_real_us - start_send_us); + if (span) { + ControllerPrivateAccessor accessor(cntl); + span->set_log_id(cntl->log_id()); + span->set_base_cid(correlation_id); + span->set_protocol(_options.protocol); + span->set_start_send_us(start_send_us); + accessor.set_span(span); + } + } + // Override some options if they haven't been set by Controller + if (cntl->timeout_ms() == UNSET_MAGIC_NUM) { + cntl->set_timeout_ms(_options.timeout_ms); + } + // Since connection is shared extensively amongst channels and RPC, + // overriding connect_timeout_ms does not make sense, just use the + // one in ChannelOptions + cntl->_connect_timeout_ms = _options.connect_timeout_ms; + if (cntl->backup_request_ms() == UNSET_MAGIC_NUM && + NULL == cntl->_backup_request_policy) { + cntl->set_backup_request_ms(_options.backup_request_ms); + cntl->_backup_request_policy = _options.backup_request_policy; + } + if (cntl->connection_type() == CONNECTION_TYPE_UNKNOWN) { + cntl->set_connection_type(_options.connection_type); + } + cntl->_response = response; + cntl->_done = done; + cntl->_pack_request = _pack_request; + cntl->_method = method; + cntl->_auth = _options.auth; + + if (SingleServer()) { + cntl->_single_server_id = _server_id; + cntl->_remote_side = _server_address; + } + + // Share the lb with controller. + cntl->_lb = _lb; + + // Ensure that serialize_request is done before pack_request in all + // possible executions, including: + // HandleSendFailed => OnVersionedRPCReturned => IssueRPC(pack_request) + _serialize_request(&cntl->_request_buf, cntl, request); + if (cntl->FailedInline()) { + // Handle failures caused by serialize_request, and these error_codes + // should be excluded from the retry_policy. + return cntl->HandleSendFailed(); + } + if (FLAGS_usercode_in_pthread && + done != NULL && + TooManyUserCode()) { + cntl->SetFailed(ELIMIT, "Too many user code to run when " + "-usercode_in_pthread is on"); + return cntl->HandleSendFailed(); + } + + if (!cntl->_request_streams.empty()) { + // Currently we cannot handle retry and backup request correctly + cntl->set_max_retry(0); + cntl->set_backup_request_ms(-1); + cntl->_backup_request_policy = NULL; + } + + if (cntl->backup_request_ms() >= 0 && + (cntl->backup_request_ms() < cntl->timeout_ms() || + cntl->timeout_ms() < 0)) { + // Setup timer for backup request. When it occurs, we'll setup a + // timer of timeout_ms before sending backup request. + + // _deadline_us is for truncating _connect_timeout_ms and resetting + // timer when EBACKUPREQUEST occurs. + if (cntl->timeout_ms() < 0) { + cntl->_deadline_us = -1; + } else { + cntl->_deadline_us = cntl->timeout_ms() * 1000L + start_send_real_us; + } + const int rc = bthread_timer_add( + &cntl->_timeout_id, + butil::microseconds_to_timespec( + cntl->backup_request_ms() * 1000L + start_send_real_us), + HandleBackupRequest, (void*)correlation_id.value); + if (BAIDU_UNLIKELY(rc != 0)) { + cntl->SetFailed(rc, "Fail to add timer for backup request"); + return cntl->HandleSendFailed(); + } + } else if (cntl->timeout_ms() >= 0) { + // Setup timer for RPC timetout + + // _deadline_us is for truncating _connect_timeout_ms + cntl->_deadline_us = cntl->timeout_ms() * 1000L + start_send_real_us; + const int rc = bthread_timer_add( + &cntl->_timeout_id, + butil::microseconds_to_timespec(cntl->_deadline_us), + HandleTimeout, (void*)correlation_id.value); + if (BAIDU_UNLIKELY(rc != 0)) { + cntl->SetFailed(rc, "Fail to add timer for timeout"); + return cntl->HandleSendFailed(); + } + } else { + cntl->_deadline_us = -1; + } + + cntl->IssueRPC(start_send_real_us); + if (done == NULL) { + // MUST wait for response when sending synchronous RPC. It will + // be woken up by callback when RPC finishes (succeeds or still + // fails after retry) + Join(correlation_id); + cntl->SubmitSpan(); + cntl->OnRPCEnd(butil::gettimeofday_us()); + } +} + +void Channel::Describe(std::ostream& os, const DescribeOptions& opt) const { + os << "Channel["; + if (SingleServer()) { + os << _server_address; + } else { + _lb->Describe(os, opt); + } + os << "]"; +} + +int Channel::Weight() { + return (_lb ? _lb->Weight() : 0); +} + +int Channel::CheckHealth() { + if (_lb == NULL) { + SocketUniquePtr ptr; + if (Socket::Address(_server_id, &ptr) == 0 && ptr->IsAvailable()) { + return 0; + } + return -1; + } else { + SocketUniquePtr tmp_sock; + LoadBalancer::SelectIn sel_in = { 0, false, true, 0, NULL }; + LoadBalancer::SelectOut sel_out(&tmp_sock); + return _lb->SelectServer(sel_in, &sel_out); + } +} + +} // namespace brpc diff --git a/src/brpc/channel.h b/src/brpc/channel.h new file mode 100644 index 0000000..28a17ac --- /dev/null +++ b/src/brpc/channel.h @@ -0,0 +1,277 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_CHANNEL_H +#define BRPC_CHANNEL_H + +// To brpc developers: This is a header included by user, don't depend +// on internal structures, use opaque pointers instead. + +#include // std::ostream +#include "bthread/errno.h" // Redefine errno +#include "butil/intrusive_ptr.hpp" // butil::intrusive_ptr +#include "butil/ptr_container.h" +#include "brpc/ssl_options.h" // ChannelSSLOptions +#include "brpc/channel_base.h" // ChannelBase +#include "brpc/adaptive_protocol_type.h" // AdaptiveProtocolType +#include "brpc/adaptive_connection_type.h" // AdaptiveConnectionType +#include "brpc/socket_id.h" // SocketId +#include "brpc/controller.h" // brpc::Controller +#include "brpc/details/profiler_linker.h" +#include "brpc/retry_policy.h" +#include "brpc/backup_request_policy.h" +#include "brpc/naming_service_filter.h" +#include "brpc/health_check_option.h" +#include "brpc/socket_mode.h" + +namespace brpc { + +struct ChannelOptions { + // Constructed with default options. + ChannelOptions(); + + // Issue error when a connection is not established after so many + // milliseconds. -1 means wait indefinitely. + // Default: 200 (milliseconds) + // Maximum: 0x7fffffff (roughly 30 days) + int32_t connect_timeout_ms; + + // Max duration of RPC over this Channel. -1 means wait indefinitely. + // Overridable by Controller.set_timeout_ms(). + // Default: 500 (milliseconds) + // Maximum: 0x7fffffff (roughly 30 days) + int32_t timeout_ms; + + // Send another request if RPC does not finish after so many milliseconds. + // Overridable by Controller.set_backup_request_ms() or + // Controller.set_backup_request_policy(). + // The request will be sent to a different server by best effort. + // If timeout_ms is set and backup_request_ms >= timeout_ms, backup request + // will never be sent. + // backup request does NOT imply server-side cancellation. + // Default: -1 (disabled) + // Maximum: 0x7fffffff (roughly 30 days) + int32_t backup_request_ms; + + // Retry limit for RPC over this Channel. <=0 means no retry. + // Overridable by Controller.set_max_retry(). + // Default: 3 + // Maximum: INT_MAX + int max_retry; + + // When the error rate of a server node is too high, isolate the node. + // Note that this isolation is GLOBAL, the node will become unavailable + // for all channels running in this process during the isolation. + // Default: false + bool enable_circuit_breaker; + + // Serialization protocol, defined in src/brpc/options.proto + // NOTE: You can assign name of the protocol to this field as well, for + // Example: options.protocol = "baidu_std"; + AdaptiveProtocolType protocol; + + // Type of connection to server. If unset, use the default connection type + // of the protocol. + // NOTE: You can assign name of the type to this field as well, for + // Example: options.connection_type = "single"; + // Possible values: "single", "pooled", "short". + AdaptiveConnectionType connection_type; + + // Channel.Init() succeeds even if there's no server in the NamingService. + // E.g. the BNS directory is empty. All RPC over the channel will fail before + // new nodes being added to the NamingService. + // Default: true (false before r32470) + bool succeed_without_server; + // Print a log when above situation happens. + // Default: true. + bool log_succeed_without_server; + + // SSL related options. Refer to `ChannelSSLOptions' for details + bool has_ssl_options() const { return _ssl_options != NULL; } + const ChannelSSLOptions& ssl_options() const { return *_ssl_options; } + ChannelSSLOptions* mutable_ssl_options(); + + // Let this channel Choose to use a certain socket: 0 SOCKET_MODE_TCP, 1 SOCKET_MODE_RDMA. + // Default: SOCKET_MODE_TCP + SocketMode socket_mode; + + // Turn on authentication for this channel if `auth' is not NULL. + // Note `auth' will not be deleted by channel and must remain valid when + // the channel is being used. + // Default: NULL + const Authenticator* auth; + + // Customize the backup request time and whether to send backup request. + // Priority: `backup_request_policy' > `backup_request_ms'. + // Overridable per-RPC by Controller.set_backup_request_ms() or + // Controller.set_backup_request_policy(). + // This object is NOT owned by channel and should remain valid during + // channel's lifetime. + // Default: NULL + BackupRequestPolicy* backup_request_policy; + + // Customize the error code that should be retried. The interface is + // defined in src/brpc/retry_policy.h + // This object is NOT owned by channel and should remain valid when + // channel is used. + // Default: NULL + const RetryPolicy* retry_policy; + + // Filter ServerNodes (i.e. based on `tag' field of `ServerNode') + // which are generated by NamingService. The interface is defined + // in src/brpc/naming_service_filter.h + // This object is NOT owned by channel and should remain valid when + // channel is used. + // Default: NULL + const NamingServiceFilter* ns_filter; + + // Channels with same connection_group share connections. + // In other words, set to a different value to stop sharing connections. + // Case-sensitive, leading and trailing spaces are ignored. + // Default: "" + std::string connection_group; + + // Set the health check param according to the channel granularity. + // Its priority is higher than FLAGS_health_check_path and FLAGS_health_check_timeout_ms. + // When it is not set, FLAGS_health_check_path and FLAGS_health_check_timeout_ms will take effect. + HealthCheckOption hc_option; + + // IP address or host name of the client. + // if the client_host is "", the client IP address is determined by the OS. + // Default: "" + std::string client_host; + + // The device name of the client's network adapter. + // if the device_name is "", the flow control is determined by the OS. + // Default: "" + std::string device_name; +private: + // SSLOptions is large and not often used, allocate it on heap to + // prevent ChannelOptions from being bloated in most cases. + butil::PtrContainer _ssl_options; +}; + +// A Channel represents a communication line to one server or multiple servers +// which can be used to call that Server's services. Servers may be running +// on another machines. Normally, you should not call a Channel directly, but +// instead construct a stub Service wrapping it. +// Example: +// brpc::Channel channel; +// channel.Init("bns://rdev.matrix.all", "rr", NULL/*default options*/); +// MyService_Stub stub(&channel); +// stub.MyMethod(&controller, &request, &response, NULL); +class Channel : public ChannelBase { +friend class Controller; +friend class SelectiveChannel; +public: + Channel(ProfilerLinker = ProfilerLinker()); + virtual ~Channel(); + + DISALLOW_COPY_AND_ASSIGN(Channel); + + // Connect this channel to a single server whose address is given by the + // first parameter. Use default options if `options' is NULL. + int Init(butil::EndPoint server_addr_and_port, const ChannelOptions* options); + int Init(const char* server_addr_and_port, const ChannelOptions* options); + int Init(const char* server_addr, int port, const ChannelOptions* options); + + // Connect this channel to a group of servers whose addresses can be + // accessed via `naming_service_url' according to its protocol. Use the + // method specified by `load_balancer_name' to distribute traffic to + // servers. Use default options if `options' is NULL. + // Supported naming service("protocol://service_name"): + // bns:// # Baidu Naming Service + // file:// # load addresses from the file + // list://addr1,addr2,... # use the addresses separated by comma + // http:// # Domain Naming Service, aka DNS. + // Supported load balancer: + // rr # round robin, choose next server + // random # randomly choose a server + // wr # weighted random + // wrr # weighted round robin + // la # locality aware + // c_murmurhash/c_md5 # consistent hashing with murmurhash3/md5 + // "" or NULL # treat `naming_service_url' as `server_addr_and_port' + // # Init(xxx, "", options) and Init(xxx, NULL, options) + // # are exactly same with Init(xxx, options) + int Init(const char* naming_service_url, + const char* load_balancer_name, + const ChannelOptions* options); + + // Call `method' of the remote service with `request' as input, and + // `response' as output. `controller' contains options and extra data. + // If `done' is not NULL, this method returns after request was sent + // and `done->Run()' will be called when the call finishes, otherwise + // caller blocks until the call finishes. + void CallMethod(const google::protobuf::MethodDescriptor* method, + google::protobuf::RpcController* controller, + const google::protobuf::Message* request, + google::protobuf::Message* response, + google::protobuf::Closure* done); + + // Get current options. + const ChannelOptions& options() const { return _options; } + + void Describe(std::ostream&, const DescribeOptions&) const; + + // Sum of weights of servers that this channel connects to. + int Weight(); + + int CheckHealth(); + +protected: + bool SingleServer() const { return _lb.get() == NULL; } + + // Pick a server using `lb' and then send RPC. Wait for response when + // sending synchronous RPC. + // NOTE: DO NOT directly use `controller' after this call when + // sending asynchronous RPC (controller->_done != NULL) since + // user callback `done' could be called when it returns and + // therefore destroy the `controller' inside `done' + static void CallMethodImpl(Controller* controller, SharedLoadBalancer* lb); + + int InitChannelOptions(const ChannelOptions* options); + int InitSingle(const butil::EndPoint& server_addr_and_port, + const char* raw_server_address, + const ChannelOptions* options, + int raw_port = -1); + + std::string _service_name; + std::string _scheme; + butil::EndPoint _server_address; + SocketId _server_id; + Protocol::SerializeRequest _serialize_request; + Protocol::PackRequest _pack_request; + Protocol::GetMethodName _get_method_name; + // This will be shared between channel and controllers that + // are in the middle of RPC procedure using this channel. + // It will be destroyed after channel's destruction and all + // the RPC above has finished + butil::intrusive_ptr _lb; + ChannelOptions _options; + int _preferred_index; +}; + +enum ChannelOwnership { + OWNS_CHANNEL, + DOESNT_OWN_CHANNEL +}; + +} // namespace brpc + +#endif // BRPC_CHANNEL_H diff --git a/src/brpc/channel_base.h b/src/brpc/channel_base.h new file mode 100644 index 0000000..ed6ff24 --- /dev/null +++ b/src/brpc/channel_base.h @@ -0,0 +1,49 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_CHANNEL_BASE_H +#define BRPC_CHANNEL_BASE_H + +#include +#include +#include "butil/logging.h" +#include // google::protobuf::RpcChannel +#include "brpc/describable.h" + +// To brpc developers: This is a header included by user, don't depend +// on internal structures, use opaque pointers instead. + + +namespace brpc { + +// Base of all brpc channels. +class ChannelBase : public google::protobuf::RpcChannel/*non-copyable*/, + public Describable { +public: + virtual int Weight() { + CHECK(false) << "Not implemented"; + abort(); + }; + + virtual int CheckHealth() = 0; +}; + +} // namespace brpc + + +#endif // BRPC_CHANNEL_BASE_H diff --git a/src/brpc/checksum.cpp b/src/brpc/checksum.cpp new file mode 100644 index 0000000..85d04d8 --- /dev/null +++ b/src/brpc/checksum.cpp @@ -0,0 +1,100 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/checksum.h" + +#include "brpc/protocol.h" +#include "butil/logging.h" + +namespace brpc { + +static const int MAX_HANDLER_SIZE = 1024; +static ChecksumHandler s_handler_map[MAX_HANDLER_SIZE] = {{NULL, NULL, NULL}}; + +int RegisterChecksumHandler(ChecksumType type, ChecksumHandler handler) { + if (NULL == handler.Compute) { + LOG(FATAL) << "Invalid parameter: handler function is NULL"; + return -1; + } + int index = type; + if (index < 0 || index >= MAX_HANDLER_SIZE) { + LOG(FATAL) << "ChecksumType=" << type << " is out of range"; + return -1; + } + if (s_handler_map[index].Compute != NULL) { + LOG(FATAL) << "ChecksumType=" << type << " was registered"; + return -1; + } + s_handler_map[index] = handler; + return 0; +} + +// Find ChecksumHandler by type. +// Returns NULL if not found +inline const ChecksumHandler* FindChecksumHandler(ChecksumType type) { + int index = type; + if (index < 0 || index >= MAX_HANDLER_SIZE) { + LOG(ERROR) << "ChecksumType=" << type << " is out of range"; + return NULL; + } + if (NULL == s_handler_map[index].Compute) { + return NULL; + } + return &s_handler_map[index]; +} + +const char* ChecksumTypeToCStr(ChecksumType type) { + if (type == CHECKSUM_TYPE_NONE) { + return "none"; + } + const ChecksumHandler* handler = FindChecksumHandler(type); + return (handler != NULL ? handler->name : "unknown"); +} + +void ListChecksumHandler(std::vector* vec) { + vec->clear(); + for (int i = 0; i < MAX_HANDLER_SIZE; ++i) { + if (s_handler_map[i].Compute != NULL) { + vec->push_back(s_handler_map[i]); + } + } +} + +// Compute `data' checksum +void ComputeDataChecksum(const ChecksumIn& in, ChecksumType checksum_type) { + if (checksum_type == CHECKSUM_TYPE_NONE) { + return; + } + const ChecksumHandler* handler = FindChecksumHandler(checksum_type); + if (NULL != handler) { + handler->Compute(in); + } +} + +// Verify `data' checksum Returns true on success, false otherwise +bool VerifyDataChecksum(const ChecksumIn& in, ChecksumType checksum_type) { + if (checksum_type == CHECKSUM_TYPE_NONE) { + return true; + } + const ChecksumHandler* handler = FindChecksumHandler(checksum_type); + if (NULL != handler) { + return handler->Verify(in); + } + return true; +} + +} // namespace brpc diff --git a/src/brpc/checksum.h b/src/brpc/checksum.h new file mode 100644 index 0000000..ff0a98a --- /dev/null +++ b/src/brpc/checksum.h @@ -0,0 +1,63 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_CHECKSUM_H +#define BRPC_CHECKSUM_H + +#include "brpc/controller.h" +#include "brpc/options.pb.h" // ChecksumType +#include "butil/iobuf.h" // butil::IOBuf + +namespace brpc { + +struct ChecksumIn { + const butil::IOBuf* buf; + Controller* cntl; +}; + +struct ChecksumHandler { + // checksum `buf'. + // Returns checksum value + void (*Compute)(const ChecksumIn& in); + + // verify buf checksum + // Rerturn true on success, false otherwise + bool (*Verify)(const ChecksumIn& in); + + // Name of the checksum algorithm, must be string constant. + const char* name; +}; + +// [NOT thread-safe] Register `handler' using key=`type' +// Returns 0 on success, -1 otherwise +int RegisterChecksumHandler(ChecksumType type, ChecksumHandler handler); + +// Returns the `name' of the checksumType if registered +const char* ChecksumTypeToCStr(ChecksumType type); + +// Put all registered handlers into `vec'. +void ListChecksumHandler(std::vector* vec); + +// Compute `data' checksum and set to controller +void ComputeDataChecksum(const ChecksumIn& in, ChecksumType checksum_type); + +// Verify `data' checksum Returns true on success, false otherwise +bool VerifyDataChecksum(const ChecksumIn& in, ChecksumType checksum_type); + +} // namespace brpc + +#endif // BRPC_CHECKSUM_H diff --git a/src/brpc/circuit_breaker.cpp b/src/brpc/circuit_breaker.cpp new file mode 100644 index 0000000..785ec77 --- /dev/null +++ b/src/brpc/circuit_breaker.cpp @@ -0,0 +1,256 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/circuit_breaker.h" + +#include +#include + +#include "brpc/errno.pb.h" +#include "brpc/reloadable_flags.h" +#include "butil/time.h" + +namespace brpc { + +DEFINE_int32(circuit_breaker_short_window_size, 1500, + "Short window sample size."); +DEFINE_int32(circuit_breaker_long_window_size, 3000, + "Long window sample size."); +DEFINE_int32(circuit_breaker_short_window_error_percent, 10, + "The maximum error rate allowed by the short window, ranging from 0-99."); +DEFINE_int32(circuit_breaker_long_window_error_percent, 5, + "The maximum error rate allowed by the long window, ranging from 0-99."); +DEFINE_int32(circuit_breaker_min_error_cost_us, 500, + "The minimum error_cost, when the ema of error cost is less than this " + "value, it will be set to zero."); +DEFINE_int32(circuit_breaker_max_failed_latency_mutiple, 2, + "The maximum multiple of the latency of the failed request relative to " + "the average latency of the success requests."); +DEFINE_int32(circuit_breaker_min_isolation_duration_ms, 100, + "Minimum isolation duration in milliseconds"); +DEFINE_int32(circuit_breaker_max_isolation_duration_ms, 30000, + "Maximum isolation duration in milliseconds"); +DEFINE_double(circuit_breaker_epsilon_value, 0.02, + "ema_alpha = 1 - std::pow(epsilon, 1.0 / window_size)"); +DEFINE_int32(circuit_breaker_half_open_window_size, 0, + "The limited number of requests allowed to pass through by the half-open " + "window. Only if all of them are successful, the circuit breaker will " + "go to the closed state. Otherwise, it goes back to the open state. " + "Values == 0 disables this feature"); +BRPC_VALIDATE_GFLAG(circuit_breaker_half_open_window_size, NonNegativeInteger); + +namespace { +// EPSILON is used to generate the smoothing coefficient when calculating EMA. +// The larger the EPSILON, the larger the smoothing coefficient, which means +// that the proportion of early data is larger. +// smooth = pow(EPSILON, 1 / window_size), +// eg: when window_size = 100, +// EPSILON = 0.1, smooth = 0.9772 +// EPSILON = 0.3, smooth = 0.9880 +// when window_size = 1000, +// EPSILON = 0.1, smooth = 0.9977 +// EPSILON = 0.3, smooth = 0.9987 + +#define EPSILON (FLAGS_circuit_breaker_epsilon_value) + +} // namespace + +CircuitBreaker::EmaErrorRecorder::EmaErrorRecorder(int window_size, + int max_error_percent) + : _window_size(window_size) + , _max_error_percent(max_error_percent) + , _smooth(std::pow(EPSILON, 1.0/window_size)) + , _sample_count_when_initializing(0) + , _error_count_when_initializing(0) + , _ema_error_cost(0) + , _ema_latency(0) { +} + +bool CircuitBreaker::EmaErrorRecorder::OnCallEnd(int error_code, + int64_t latency) { + int64_t ema_latency = 0; + bool healthy = false; + if (error_code == 0) { + ema_latency = UpdateLatency(latency); + healthy = UpdateErrorCost(0, ema_latency); + } else { + ema_latency = _ema_latency.load(butil::memory_order_relaxed); + healthy = UpdateErrorCost(latency, ema_latency); + } + + // When the window is initializing, use error_rate to determine + // if it needs to be isolated. + if (_sample_count_when_initializing.load(butil::memory_order_relaxed) < _window_size && + _sample_count_when_initializing.fetch_add(1, butil::memory_order_relaxed) < _window_size) { + if (error_code != 0) { + const int32_t error_count = + _error_count_when_initializing.fetch_add(1, butil::memory_order_relaxed); + return error_count < _window_size * _max_error_percent / 100; + } + // Because once OnCallEnd returned false, the node will be ioslated soon, + // so when error_code=0, we no longer check the error count. + return true; + } + + return healthy; +} + +void CircuitBreaker::EmaErrorRecorder::Reset() { + if (_sample_count_when_initializing.load(butil::memory_order_relaxed) < _window_size) { + _sample_count_when_initializing.store(0, butil::memory_order_relaxed); + _error_count_when_initializing.store(0, butil::memory_order_relaxed); + _ema_latency.store(0, butil::memory_order_relaxed); + } + _ema_error_cost.store(0, butil::memory_order_relaxed); +} + +int64_t CircuitBreaker::EmaErrorRecorder::UpdateLatency(int64_t latency) { + int64_t ema_latency = _ema_latency.load(butil::memory_order_relaxed); + do { + int64_t next_ema_latency = 0; + if (0 == ema_latency) { + next_ema_latency = latency; + } else { + next_ema_latency = ema_latency * _smooth + latency * (1 - _smooth); + } + if (_ema_latency.compare_exchange_weak(ema_latency, next_ema_latency)) { + return next_ema_latency; + } + } while(true); +} + +bool CircuitBreaker::EmaErrorRecorder::UpdateErrorCost(int64_t error_cost, + int64_t ema_latency) { + const int max_mutiple = FLAGS_circuit_breaker_max_failed_latency_mutiple; + if (ema_latency != 0) { + error_cost = std::min(ema_latency * max_mutiple, error_cost); + } + // Errorous response + if (error_cost != 0) { + int64_t ema_error_cost = + _ema_error_cost.fetch_add(error_cost, butil::memory_order_relaxed); + ema_error_cost += error_cost; + const int64_t max_error_cost = + ema_latency * _window_size * (_max_error_percent / 100.0) * (1.0 + EPSILON); + return ema_error_cost <= max_error_cost; + } + + // Ordinary response + int64_t ema_error_cost = _ema_error_cost.load(butil::memory_order_relaxed); + do { + if (ema_error_cost == 0) { + break; + } else if (ema_error_cost < FLAGS_circuit_breaker_min_error_cost_us) { + if (_ema_error_cost.compare_exchange_weak( + ema_error_cost, 0, butil::memory_order_relaxed)) { + break; + } + } else { + int64_t next_ema_error_cost = ema_error_cost * _smooth; + if (_ema_error_cost.compare_exchange_weak( + ema_error_cost, next_ema_error_cost)) { + break; + } + } + } while (true); + return true; +} + +CircuitBreaker::CircuitBreaker() + : _long_window(FLAGS_circuit_breaker_long_window_size, + FLAGS_circuit_breaker_long_window_error_percent) + , _short_window(FLAGS_circuit_breaker_short_window_size, + FLAGS_circuit_breaker_short_window_error_percent) + , _last_reset_time_ms(0) + , _isolation_duration_ms(FLAGS_circuit_breaker_min_isolation_duration_ms) + , _isolated_times(0) + , _broken(false) + , _half_open(false) + , _half_open_success_count(0) { +} + +bool CircuitBreaker::OnCallEnd(int error_code, int64_t latency) { + // If the server has reached its maximum concurrency, it will return + // ELIMIT directly when a new request arrives. This usually means that + // the entire downstream cluster is overloaded. If we isolate nodes at + // this time, may increase the pressure on downstream. On the other hand, + // since the latency corresponding to ELIMIT is usually very small, we + // cannot handle it as a successful request. Here we simply ignore the requests + // that returned ELIMIT. + if (error_code == ELIMIT) { + return true; + } + if (_broken.load(butil::memory_order_relaxed)) { + return false; + } + if (FLAGS_circuit_breaker_half_open_window_size > 0 + && _half_open.load(butil::memory_order_relaxed)) { + if (error_code != 0) { + MarkAsBroken(); + return false; + } + if (_half_open_success_count.fetch_add(1, butil::memory_order_relaxed) + + 1 == FLAGS_circuit_breaker_half_open_window_size) { + _half_open.store(false, butil::memory_order_relaxed); + _half_open_success_count.store(0, butil::memory_order_relaxed); + } + } + + if (_long_window.OnCallEnd(error_code, latency) && + _short_window.OnCallEnd(error_code, latency)) { + return true; + } + MarkAsBroken(); + return false; +} + +void CircuitBreaker::Reset() { + _long_window.Reset(); + _short_window.Reset(); + _last_reset_time_ms = butil::cpuwide_time_ms(); + _broken.store(false, butil::memory_order_release); + if (FLAGS_circuit_breaker_half_open_window_size > 0) { + _half_open.store(true, butil::memory_order_relaxed); + _half_open_success_count.store(0, butil::memory_order_relaxed); + } +} + +void CircuitBreaker::MarkAsBroken() { + if (!_broken.exchange(true, butil::memory_order_acquire)) { + _isolated_times.fetch_add(1, butil::memory_order_relaxed); + UpdateIsolationDuration(); + } +} + +void CircuitBreaker::UpdateIsolationDuration() { + int64_t now_time_ms = butil::cpuwide_time_ms(); + int isolation_duration_ms = _isolation_duration_ms.load(butil::memory_order_relaxed); + const int max_isolation_duration_ms = + FLAGS_circuit_breaker_max_isolation_duration_ms; + const int min_isolation_duration_ms = + FLAGS_circuit_breaker_min_isolation_duration_ms; + if (now_time_ms - _last_reset_time_ms < max_isolation_duration_ms) { + isolation_duration_ms = + std::min(isolation_duration_ms * 2, max_isolation_duration_ms); + } else { + isolation_duration_ms = min_isolation_duration_ms; + } + _isolation_duration_ms.store(isolation_duration_ms, butil::memory_order_relaxed); +} + + +} // namespace brpc diff --git a/src/brpc/circuit_breaker.h b/src/brpc/circuit_breaker.h new file mode 100644 index 0000000..b16a429 --- /dev/null +++ b/src/brpc/circuit_breaker.h @@ -0,0 +1,96 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_CIRCUIT_BREAKER_H +#define BRPC_CIRCUIT_BREAKER_H + +#include "butil/atomicops.h" + +namespace brpc { + +class CircuitBreaker { +public: + CircuitBreaker(); + + ~CircuitBreaker() {} + + // Sampling the current rpc. Returns false if a node needs to + // be isolated. Otherwise return true. + // error_code: Error_code of this call, 0 means success. + // latency: Time cost of this call. + // Note: Once OnCallEnd() determined that a node needs to be isolated, + // it will always return false until you call Reset(). Usually Reset() + // will be called in the health check thread. + bool OnCallEnd(int error_code, int64_t latency); + + // Reset CircuitBreaker and clear history data. will erase the historical + // data and start sampling again. Before you call this method, you need to + // ensure that no one else is accessing CircuitBreaker. + void Reset(); + + // Mark the Socket as broken. Call this method when you want to isolate a + // node in advance. When this method is called multiple times in succession, + // only the first call will take effect. + void MarkAsBroken(); + + // Number of times marked as broken + int isolated_times() const { + return _isolated_times.load(butil::memory_order_relaxed); + } + + // The duration that should be isolated when the socket fails in milliseconds. + // The higher the frequency of socket errors, the longer the duration. + int isolation_duration_ms() const { + return _isolation_duration_ms.load(butil::memory_order_relaxed); + } + +private: + void UpdateIsolationDuration(); + + class EmaErrorRecorder { + public: + EmaErrorRecorder(int windows_size, int max_error_percent); + bool OnCallEnd(int error_code, int64_t latency); + void Reset(); + + private: + int64_t UpdateLatency(int64_t latency); + bool UpdateErrorCost(int64_t latency, int64_t ema_latency); + + const int _window_size; + const int _max_error_percent; + const double _smooth; + + butil::atomic _sample_count_when_initializing; + butil::atomic _error_count_when_initializing; + butil::atomic _ema_error_cost; + butil::atomic _ema_latency; + }; + + EmaErrorRecorder _long_window; + EmaErrorRecorder _short_window; + int64_t _last_reset_time_ms; + butil::atomic _isolation_duration_ms; + butil::atomic _isolated_times; + butil::atomic _broken; + butil::atomic _half_open; + butil::atomic _half_open_success_count; +}; + +} // namespace brpc + +#endif // BRPC_CIRCUIT_BREAKER_H_ diff --git a/src/brpc/closure_guard.h b/src/brpc/closure_guard.h new file mode 100644 index 0000000..c1405b5 --- /dev/null +++ b/src/brpc/closure_guard.h @@ -0,0 +1,74 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_CLOSURE_GUARD_H +#define BRPC_CLOSURE_GUARD_H + +#include +#include "butil/macros.h" + + +namespace brpc { + +// RAII: Call Run() of the closure on destruction. +class ClosureGuard { +public: + ClosureGuard() : _done(NULL) {} + + // Constructed with a closure which will be Run() inside dtor. + explicit ClosureGuard(google::protobuf::Closure* done) : _done(done) {} + + // Run internal closure if it's not NULL. + ~ClosureGuard() { + if (_done) { + _done->Run(); + } + } + + // Run internal closure if it's not NULL and set it to `done'. + void reset(google::protobuf::Closure* done) { + if (_done) { + _done->Run(); + } + _done = done; + } + + // Return and set internal closure to NULL. + google::protobuf::Closure* release() { + google::protobuf::Closure* const prev_done = _done; + _done = NULL; + return prev_done; + } + + // True if no closure inside. + bool empty() const { return _done == NULL; } + + // Exchange closure with another guard. + void swap(ClosureGuard& other) { std::swap(_done, other._done); } + +private: + // Copying this object makes no sense. + DISALLOW_COPY_AND_ASSIGN(ClosureGuard); + + google::protobuf::Closure* _done; +}; + +} // namespace brpc + + +#endif // BRPC_CLOSURE_GUARD_H diff --git a/src/brpc/cluster_recover_policy.cpp b/src/brpc/cluster_recover_policy.cpp new file mode 100644 index 0000000..bb59a23 --- /dev/null +++ b/src/brpc/cluster_recover_policy.cpp @@ -0,0 +1,149 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include "brpc/cluster_recover_policy.h" +#include "butil/scoped_lock.h" +#include "butil/synchronization/lock.h" +#include "brpc/server_id.h" +#include "brpc/socket.h" +#include "butil/fast_rand.h" +#include "butil/time.h" +#include "butil/string_splitter.h" + +namespace brpc { + +DEFINE_int64(detect_available_server_interval_ms, 10, "The interval " + "to detect available server count in DefaultClusterRecoverPolicy"); + +DefaultClusterRecoverPolicy::DefaultClusterRecoverPolicy( + int64_t min_working_instances, int64_t hold_seconds) + : _recovering(false) + , _min_working_instances(min_working_instances) + , _last_usable(0) + , _last_usable_change_time_ms(0) + , _hold_seconds(hold_seconds) + , _usable_cache(0) + , _usable_cache_time_ms(0) { } + +void DefaultClusterRecoverPolicy::StartRecover() { + std::unique_lock mu(_mutex); + _recovering = true; +} + +bool DefaultClusterRecoverPolicy::StopRecoverIfNecessary() { + if (!_recovering) { + return false; + } + int64_t now_ms = butil::cpuwide_time_ms(); + std::unique_lock mu(_mutex); + if (_last_usable_change_time_ms != 0 && _last_usable != 0 && + (now_ms - _last_usable_change_time_ms > _hold_seconds * 1000)) { + _recovering = false; + _last_usable = 0; + _last_usable_change_time_ms = 0; + mu.unlock(); + return false; + } + mu.unlock(); + return true; +} + +uint64_t DefaultClusterRecoverPolicy::GetUsableServerCount( + int64_t now_ms, const std::vector& server_list) { + if (now_ms - _usable_cache_time_ms < FLAGS_detect_available_server_interval_ms) { + return _usable_cache; + } + uint64_t usable = 0; + size_t n = server_list.size(); + SocketUniquePtr ptr; + for (size_t i = 0; i < n; ++i) { + if (Socket::Address(server_list[i].id, &ptr) == 0 + && ptr->IsAvailable()) { + usable++; + } + } + { + std::unique_lock mu(_mutex); + _usable_cache = usable; + _usable_cache_time_ms = now_ms; + } + return _usable_cache; +} + + +bool DefaultClusterRecoverPolicy::DoReject(const std::vector& server_list) { + if (!_recovering) { + return false; + } + int64_t now_ms = butil::cpuwide_time_ms(); + uint64_t usable = GetUsableServerCount(now_ms, server_list); + if (_last_usable != usable) { + std::unique_lock mu(_mutex); + if (_last_usable != usable) { + _last_usable = usable; + _last_usable_change_time_ms = now_ms; + } + } + if (butil::fast_rand_less_than(_min_working_instances) >= usable) { + return true; + } + return false; +} + +bool GetRecoverPolicyByParams(const butil::StringPiece& params, + std::shared_ptr* ptr_out) { + int64_t min_working_instances = -1; + int64_t hold_seconds = -1; + bool has_meet_params = false; + for (butil::KeyValuePairsSplitter sp(params.begin(), params.end(), ' ', '='); + sp; ++sp) { + if (sp.value().empty()) { + LOG(ERROR) << "Empty value for " << sp.key() << " in lb parameter"; + return false; + } + if (sp.key() == "min_working_instances") { + if (!butil::StringToInt64(sp.value(), &min_working_instances)) { + return false; + } + has_meet_params = true; + continue; + } else if (sp.key() == "hold_seconds") { + if (!butil::StringToInt64(sp.value(), &hold_seconds)) { + return false; + } + has_meet_params = true; + continue; + } + LOG(ERROR) << "Failed to set this unknown parameters " << sp.key_and_value(); + return false; + } + if (min_working_instances > 0 && hold_seconds > 0) { + ptr_out->reset( + new DefaultClusterRecoverPolicy(min_working_instances, hold_seconds)); + } else if (has_meet_params) { + // In this case, user set some params but not in the right way, just return + // false to let user take care of this situation. + LOG(ERROR) << "Invalid params=`" << params << "'"; + return false; + } + return true; +} + +} // namespace brpc diff --git a/src/brpc/cluster_recover_policy.h b/src/brpc/cluster_recover_policy.h new file mode 100644 index 0000000..fe492aa --- /dev/null +++ b/src/brpc/cluster_recover_policy.h @@ -0,0 +1,89 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_CLUSTER_RECOVER_POLICY +#define BRPC_CLUSTER_RECOVER_POLICY + +#include +#include +#include "butil/synchronization/lock.h" +#include "butil/strings/string_piece.h" +#include "butil/strings/string_number_conversions.h" + +namespace brpc { + +struct ServerId; + +// After all servers are down and health check happens, servers are +// online one by one. Once one server is up, all the request that should +// be sent to all servers, would be sent to one server, which may be a +// disastrous behaviour. In the worst case it would cause the server being down +// again if circuit breaker is enabled and the cluster would never recover. +// This class controls the amount of requests that sent to the revived +// servers when recovering from all servers are down. +class ClusterRecoverPolicy { +public: + virtual ~ClusterRecoverPolicy() {} + + // Indicate that recover from all server being down is happening. + virtual void StartRecover() = 0; + + // Return true if some customized policies are satisfied. + virtual bool DoReject(const std::vector& server_list) = 0; + + // Stop recover state and do not reject the request if some condition is + // satisfied. Return true if the current state is still in recovering. + virtual bool StopRecoverIfNecessary() = 0; +}; + +// The default cluster recover policy. Once no servers are available, recover is start. +// If in recover state, the probability that a request is accepted is q/n, in +// which q is the number of current available server, n is the number of minimum +// working instances setting by user. If q is not changed during a given time, +// hold_seconds, then the cluster is considered recovered and all the request +// would be sent to the current available servers. +class DefaultClusterRecoverPolicy : public ClusterRecoverPolicy { +public: + DefaultClusterRecoverPolicy(int64_t min_working_instances, int64_t hold_seconds); + + void StartRecover() override; + bool DoReject(const std::vector& server_list) override; + bool StopRecoverIfNecessary() override; + +private: + uint64_t GetUsableServerCount(int64_t now_ms, const std::vector& server_list); + +private: + bool _recovering; + int64_t _min_working_instances; + butil::Mutex _mutex; + uint64_t _last_usable; + int64_t _last_usable_change_time_ms; + int64_t _hold_seconds; + uint64_t _usable_cache; + int64_t _usable_cache_time_ms; +}; + +// Return a DefaultClusterRecoverPolicy object by params. +bool GetRecoverPolicyByParams(const butil::StringPiece& params, + std::shared_ptr* ptr_out); + +} // namespace brpc + +#endif + diff --git a/src/brpc/compress.cpp b/src/brpc/compress.cpp new file mode 100644 index 0000000..36e55c7 --- /dev/null +++ b/src/brpc/compress.cpp @@ -0,0 +1,128 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "butil/logging.h" +#include "json2pb/json_to_pb.h" +#include "brpc/compress.h" +#include "brpc/protocol.h" +#include "brpc/proto_base.pb.h" + +namespace brpc { + +static const int MAX_HANDLER_SIZE = 1024; +static CompressHandler s_handler_map[MAX_HANDLER_SIZE] = { { NULL, NULL, NULL } }; + +int RegisterCompressHandler(CompressType type, + CompressHandler handler) { + if (NULL == handler.Compress || NULL == handler.Decompress) { + LOG(FATAL) << "Invalid parameter: handler function is NULL"; + return -1; + } + int index = type; + if (index < 0 || index >= MAX_HANDLER_SIZE) { + LOG(FATAL) << "CompressType=" << type << " is out of range"; + return -1; + } + if (s_handler_map[index].Compress != NULL) { + LOG(FATAL) << "CompressType=" << type << " was registered"; + return -1; + } + s_handler_map[index] = handler; + return 0; +} + +// Find CompressHandler by type. +// Returns NULL if not found +const CompressHandler* FindCompressHandler(CompressType type) { + int index = type; + if (index < 0 || index >= MAX_HANDLER_SIZE) { + LOG(ERROR) << "CompressType=" << type << " is out of range"; + return NULL; + } + if (NULL == s_handler_map[index].Compress) { + return NULL; + } + return &s_handler_map[index]; +} + +const char* CompressTypeToCStr(CompressType type) { + if (type == COMPRESS_TYPE_NONE) { + return "none"; + } + const CompressHandler* handler = FindCompressHandler(type); + return (handler != NULL ? handler->name : "unknown"); +} + +void ListCompressHandler(std::vector* vec) { + vec->clear(); + for (int i = 0; i < MAX_HANDLER_SIZE; ++i) { + if (s_handler_map[i].Compress != NULL) { + vec->push_back(s_handler_map[i]); + } + } +} + +bool ParseFromCompressedData(const butil::IOBuf& data, + google::protobuf::Message* msg, + CompressType compress_type) { + if (compress_type == COMPRESS_TYPE_NONE) { + return ParsePbFromIOBuf(msg, data); + } + const CompressHandler* handler = FindCompressHandler(compress_type); + if (NULL == handler) { + return false; + } + + Deserializer deserializer([msg](google::protobuf::io::ZeroCopyInputStream* input) { + return msg->ParseFromZeroCopyStream(input); + }); + return handler->Decompress(data, &deserializer); +} + +bool SerializeAsCompressedData(const google::protobuf::Message& msg, + butil::IOBuf* buf, CompressType compress_type) { + if (compress_type == COMPRESS_TYPE_NONE) { + butil::IOBufAsZeroCopyOutputStream wrapper(buf); + return msg.SerializeToZeroCopyStream(&wrapper); + } + const CompressHandler* handler = FindCompressHandler(compress_type); + if (NULL == handler) { + return false; + } + + Serializer serializer([&msg](google::protobuf::io::ZeroCopyOutputStream* output) { + return msg.SerializeToZeroCopyStream(output); + }); + return handler->Compress(serializer, buf); +} + +::google::protobuf::Metadata Serializer::GetMetadata() const { + ::google::protobuf::Metadata metadata{}; + metadata.descriptor = SerializerBase::descriptor(); + metadata.reflection = nullptr; + return metadata; +} + +::google::protobuf::Metadata Deserializer::GetMetadata() const { + ::google::protobuf::Metadata metadata{}; + metadata.descriptor = DeserializerBase::descriptor(); + metadata.reflection = nullptr; + return metadata; +} + +} // namespace brpc diff --git a/src/brpc/compress.h b/src/brpc/compress.h new file mode 100644 index 0000000..4529959 --- /dev/null +++ b/src/brpc/compress.h @@ -0,0 +1,202 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_COMPRESS_H +#define BRPC_COMPRESS_H + +#include // Message +#include "butil/iobuf.h" // butil::IOBuf +#include "butil/logging.h" +#include "brpc/options.pb.h" // CompressType +#include "brpc/nonreflectable_message.h" + +namespace brpc { + +// Serializer can be used to implement custom serialization +// before compression with user callback. +class Serializer : public NonreflectableMessage { +public: + using Callback = std::function; + + Serializer() :Serializer(NULL) {} + + explicit Serializer(Callback callback) + :_callback(std::move(callback)) { + SharedCtor(); + } + + ~Serializer() override { + SharedDtor(); + } + + Serializer(const Serializer& from) + : NonreflectableMessage(from) { + SharedCtor(); + MergeFrom(from); + } + + Serializer& operator=(const Serializer& from) { + CopyFrom(from); + return *this; + } + + void Swap(Serializer* other) { + if (other != this) { + } + } + + void MergeFrom(const Serializer& from) override { + CHECK_NE(&from, this); + } + + // implements Message ---------------------------------------------- + void Clear() override { + _callback = nullptr; + } + size_t ByteSizeLong() const override { return 0; } + int GetCachedSize() const PB_425_OVERRIDE { return ByteSize(); } + + ::google::protobuf::Metadata GetMetadata() const PB_527_OVERRIDE; + + // Converts the data into `output' for later compression. + bool SerializeTo(google::protobuf::io::ZeroCopyOutputStream* output) const { + if (!_callback) { + LOG(WARNING) << "Serializer::SerializeTo() called without callback"; + return false; + } + return _callback(output); + } + + void SetCallback(Callback callback) { + _callback = std::move(callback); + } + +private: + void SharedCtor() {} + void SharedDtor() {} + + Callback _callback; +}; + +// Deserializer can be used to implement custom deserialization +// after decompression with user callback. +class Deserializer : public NonreflectableMessage { +public: +public: + using Callback = std::function; + + Deserializer() :Deserializer(NULL) {} + + explicit Deserializer(Callback callback) : _callback(std::move(callback)) { + SharedCtor(); + } + + ~Deserializer() override { + SharedDtor(); + } + + Deserializer(const Deserializer& from) + : NonreflectableMessage(from) { + SharedCtor(); + MergeFrom(from); + } + + Deserializer& operator=(const Deserializer& from) { + CopyFrom(from); + return *this; + } + + void Swap(Deserializer* other) { + if (other != this) { + _callback.swap(other->_callback); + } + } + + void MergeFrom(const Deserializer& from) override { + CHECK_NE(&from, this); + _callback = from._callback; + } + + // implements Message ---------------------------------------------- + void Clear() override { _callback = nullptr; } + size_t ByteSizeLong() const override { return 0; } + int GetCachedSize() const PB_425_OVERRIDE { return ByteSize(); } + + ::google::protobuf::Metadata GetMetadata() const PB_527_OVERRIDE; + + // Converts the decompressed `input'. + bool DeserializeFrom(google::protobuf::io::ZeroCopyInputStream* intput) const { + if (!_callback) { + LOG(WARNING) << "Deserializer::DeserializeFrom() called without callback"; + return false; + } + return _callback(intput); + } + void SetCallback(Callback callback) { + _callback = std::move(callback); + } + +private: + void SharedCtor() {} + void SharedDtor() {} + + Callback _callback; +}; + +struct CompressHandler { + // Compress serialized `msg' into `buf'. + // Returns true on success, false otherwise + bool (*Compress)(const google::protobuf::Message& msg, butil::IOBuf* buf); + + // Parse decompressed `data' as `msg'. + // Returns true on success, false otherwise + bool (*Decompress)(const butil::IOBuf& data, google::protobuf::Message* msg); + + // Name of the compression algorithm, must be string constant. + const char* name; +}; + +// [NOT thread-safe] Register `handler' using key=`type' +// Returns 0 on success, -1 otherwise +int RegisterCompressHandler(CompressType type, CompressHandler handler); + +// Returns CompressHandler pointer of `type' if registered, NULL otherwise. +const CompressHandler* FindCompressHandler(CompressType type); + +// Returns the `name' of the CompressType if registered +const char* CompressTypeToCStr(CompressType type); + +// Put all registered handlers into `vec'. +void ListCompressHandler(std::vector* vec); + +// Parse decompressed `data' as `msg' using registered `compress_type'. +// Returns true on success, false otherwise +bool ParseFromCompressedData(const butil::IOBuf& data, + google::protobuf::Message* msg, + CompressType compress_type); + +// Compress serialized `msg' into `buf' using registered `compress_type'. +// Returns true on success, false otherwise +bool SerializeAsCompressedData(const google::protobuf::Message& msg, + butil::IOBuf* buf, + CompressType compress_type); + +} // namespace brpc + + +#endif // BRPC_COMPRESS_H diff --git a/src/brpc/concurrency_limiter.h b/src/brpc/concurrency_limiter.h new file mode 100644 index 0000000..351dd0d --- /dev/null +++ b/src/brpc/concurrency_limiter.h @@ -0,0 +1,65 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_CONCURRENCY_LIMITER_H +#define BRPC_CONCURRENCY_LIMITER_H + +#include "brpc/describable.h" +#include "brpc/destroyable.h" +#include "brpc/extension.h" // Extension +#include "brpc/adaptive_max_concurrency.h" // AdaptiveMaxConcurrency +#include "brpc/controller.h" + +namespace brpc { + +class ConcurrencyLimiter { +public: + virtual ~ConcurrencyLimiter() {} + + // This method should be called each time a request comes in. It returns + // false when the concurrency reaches the upper limit, otherwise it + // returns true. Normally, when OnRequested returns false, you should + // return an ELIMIT error directly. + virtual bool OnRequested(int current_concurrency, Controller* cntl) = 0; + + // Each request should call this method before responding. + // `error_code' : Error code obtained from the controller, 0 means success. + // `latency' : Microseconds taken by RPC. + // NOTE: Even if OnRequested returns false, after sending ELIMIT, you + // still need to call OnResponded. + virtual void OnResponded(int error_code, int64_t latency_us) = 0; + + // Returns the latest max_concurrency. + // The return value is only for logging. + virtual int MaxConcurrency() = 0; + + // Reset max_concurrency + virtual int ResetMaxConcurrency(const AdaptiveMaxConcurrency& amc) = 0; + + // Create an instance from the amc + // Caller is responsible for delete the instance after usage. + virtual ConcurrencyLimiter* New(const AdaptiveMaxConcurrency& amc) const = 0; +}; + +inline Extension* ConcurrencyLimiterExtension() { + return Extension::instance(); +} + +} // namespace brpc + + +#endif // BRPC_CONCURRENCY_LIMITER_H diff --git a/src/brpc/controller.cpp b/src/brpc/controller.cpp new file mode 100644 index 0000000..bf1fecf --- /dev/null +++ b/src/brpc/controller.cpp @@ -0,0 +1,1806 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include "bthread/bthread.h" +#include "butil/build_config.h" // OS_MACOSX +#include "butil/string_printf.h" +#include "butil/logging.h" +#include "butil/time.h" +#include "bthread/bthread.h" +#include "bthread/unstable.h" +#include "bvar/bvar.h" +#include "brpc/socket.h" +#include "brpc/socket_map.h" +#include "brpc/channel.h" +#include "brpc/load_balancer.h" +#include "brpc/closure_guard.h" +#include "brpc/details/controller_private_accessor.h" +#include "brpc/controller.h" +#include "brpc/span.h" +#include "brpc/server.h" // Server::_session_local_data_pool +#include "brpc/simple_data_pool.h" +#include "brpc/retry_policy.h" +#include "brpc/stream_impl.h" +#include "brpc/policy/streaming_rpc_protocol.h" // FIXME +#include "brpc/rpc_dump.h" +#include "brpc/details/usercode_backup_pool.h" // RunUserCode +#include "brpc/mongo_service_adaptor.h" + +// Force linking the .o in UT (which analysis deps by inclusions) +#include "brpc/parallel_channel.h" +#include "brpc/selective_channel.h" +#include "bthread/task_group.h" + +namespace bthread { +extern BAIDU_THREAD_LOCAL TaskGroup* tls_task_group; +} + +// This is the only place that both client/server must link, so we put +// registrations of errno here. +BAIDU_REGISTER_ERRNO(brpc::ENOSERVICE, "No such service"); +BAIDU_REGISTER_ERRNO(brpc::ENOMETHOD, "No such method"); +BAIDU_REGISTER_ERRNO(brpc::EREQUEST, "Bad request"); +BAIDU_REGISTER_ERRNO(brpc::ERPCAUTH, "Authentication failed"); +BAIDU_REGISTER_ERRNO(brpc::ETOOMANYFAILS, "Too many sub channels failed"); +BAIDU_REGISTER_ERRNO(brpc::EPCHANFINISH, "ParallelChannel finished"); +BAIDU_REGISTER_ERRNO(brpc::EBACKUPREQUEST, "Sending backup request"); +BAIDU_REGISTER_ERRNO(brpc::ERPCTIMEDOUT, "RPC call is timed out"); +BAIDU_REGISTER_ERRNO(brpc::EFAILEDSOCKET, "Broken socket"); +BAIDU_REGISTER_ERRNO(brpc::EHTTP, "Bad http call"); +BAIDU_REGISTER_ERRNO(brpc::EOVERCROWDED, "The server is overcrowded"); +BAIDU_REGISTER_ERRNO(brpc::ERTMPPUBLISHABLE, "RtmpRetryingClientStream is publishable"); +BAIDU_REGISTER_ERRNO(brpc::ERTMPCREATESTREAM, "createStream was rejected by the RTMP server"); +BAIDU_REGISTER_ERRNO(brpc::EEOF, "Got EOF"); +BAIDU_REGISTER_ERRNO(brpc::EUNUSED, "The socket was not needed"); +BAIDU_REGISTER_ERRNO(brpc::ESSL, "SSL related operation failed"); +BAIDU_REGISTER_ERRNO(brpc::EH2RUNOUTSTREAMS, "The H2 socket was run out of streams"); + +BAIDU_REGISTER_ERRNO(brpc::EINTERNAL, "General internal error"); +BAIDU_REGISTER_ERRNO(brpc::ERESPONSE, "Bad response"); +BAIDU_REGISTER_ERRNO(brpc::ELOGOFF, "Server is stopping"); +BAIDU_REGISTER_ERRNO(brpc::ELIMIT, "Reached server's max_concurrency"); +BAIDU_REGISTER_ERRNO(brpc::ECLOSE, "Close socket initiatively"); +BAIDU_REGISTER_ERRNO(brpc::EITP, "Bad Itp response"); +BAIDU_REGISTER_ERRNO(brpc::ESHUTDOWNWRITE, "Shutdown write of socket"); + +#if BRPC_WITH_RDMA +BAIDU_REGISTER_ERRNO(brpc::ERDMA, "RDMA verbs error"); +BAIDU_REGISTER_ERRNO(brpc::ERDMAMEM, "Memory not registered for RDMA"); +#endif + +DECLARE_bool(log_as_json); + +namespace brpc { + +DEFINE_bool(graceful_quit_on_sigterm, false, + "Register SIGTERM handle func to quit graceful"); +DEFINE_bool(graceful_quit_on_sighup, false, + "Register SIGHUP handle func to quit graceful"); + +const IdlNames idl_single_req_single_res = { "req", "res" }; +const IdlNames idl_single_req_multi_res = { "req", "" }; +const IdlNames idl_multi_req_single_res = { "", "res" }; +const IdlNames idl_multi_req_multi_res = { "", "" }; + +extern const int64_t IDL_VOID_RESULT = 12345678987654321LL; + +// For definitely false branch in src/brpc/profiler_link.h +int PROFILER_LINKER_DUMMY = 0; + +static void PrintRevision(std::ostream& os, void*) { +#if defined(BRPC_REVISION) + os << BRPC_REVISION; +#else + os << "undefined"; +#endif +} +static bvar::PassiveStatus s_rpc_revision( + "rpc_revision", PrintRevision, NULL); + +static const int RETRY_AVOIDANCE = 8; + +// Defined in parallel_channel.cpp +void DestroyParallelChannelDone(google::protobuf::Closure* c); +const Controller* GetSubControllerOfParallelChannel( + const google::protobuf::Closure* done, int index); +const Controller* GetSubControllerOfSelectiveChannel( + const RPCSender* sender, int index); + +DECLARE_bool(usercode_in_pthread); +DECLARE_bool(usercode_in_coroutine); +static const int MAX_RETRY_COUNT = 1000; +static bvar::Adder* g_ncontroller = NULL; + +static pthread_once_t s_create_vars_once = PTHREAD_ONCE_INIT; + +static void CreateVars() { + g_ncontroller = new bvar::Adder("rpc_controller_count"); +} + +Controller::Controller() { + CHECK_EQ(0, pthread_once(&s_create_vars_once, CreateVars)); + *g_ncontroller << 1; + ResetPods(); +} + +Controller::Controller(const Inheritable& parent_ctx) { + CHECK_EQ(0, pthread_once(&s_create_vars_once, CreateVars)); + *g_ncontroller << 1; + ResetPods(); + _inheritable = parent_ctx; +} + +struct SessionKVFlusher { + Controller* cntl; +}; +static std::ostream& operator<<(std::ostream& os, const SessionKVFlusher& f) { + f.cntl->FlushSessionKV(os); + return os; +} + +Controller::~Controller() { + *g_ncontroller << -1; + if (_session_kv != nullptr && _session_kv->Count() != 0) { + LOG(INFO) << SessionKVFlusher{ this }; + } + ResetNonPods(); +} + +class IgnoreAllRead : public ProgressiveReader { +public: + // @ProgressiveReader + butil::Status OnReadOnePart(const void* /*data*/, size_t /*length*/) { + return butil::Status::OK(); + } + void OnEndOfMessage(const butil::Status&) {} +}; + +static IgnoreAllRead* s_ignore_all_read = NULL; +static pthread_once_t s_ignore_all_read_once = PTHREAD_ONCE_INIT; +static void CreateIgnoreAllRead() { s_ignore_all_read = new IgnoreAllRead; } + +// If resource needs to be destroyed or memory needs to be deleted (both +// directly and indirectly referenced), do them in this method. Notice that +// you don't have to set the fields to initial state after deletion since +// they'll be set uniformly after this method is called. +void Controller::ResetNonPods() { + if (auto span = _span.lock()) { + Span::Submit(span, butil::cpuwide_time_us()); + } + _span.reset(); + _error_text.clear(); + _remote_side = butil::EndPoint(); + _local_side = butil::EndPoint(); + if (_session_local_data) { + _server->_session_local_data_pool->Return(_session_local_data); + } + _mongo_session_data.reset(); + delete _sampled_request; + + if (!is_used_by_rpc() && _correlation_id != INVALID_BTHREAD_ID) { + CHECK_NE(EPERM, bthread_id_cancel(_correlation_id)); + } + if (_oncancel_id != INVALID_BTHREAD_ID) { + bthread_id_error(_oncancel_id, 0); + } + if (_pchan_sub_count > 0) { + DestroyParallelChannelDone(_done); + } + delete _sender; + _lb.reset(NULL); + _current_call.Reset(); + ExcludedServers::Destroy(_accessed); + _request_buf.clear(); + delete _http_request; + delete _http_response; + delete _request_user_fields; + delete _response_user_fields; + _request_attachment.clear(); + _response_attachment.clear(); + if (_wpa) { + _wpa->MarkRPCAsDone(Failed()); + _wpa.reset(NULL); + } + if (_rpa != NULL) { + if (!has_progressive_reader()) { + // Never called ReadProgressiveAttachmentBy (successfully), the data + // is probably being buffered and a full buffer may block parse + // handler of the protocol. We need to set a reader to consume + // the buffer. + pthread_once(&s_ignore_all_read_once, CreateIgnoreAllRead); + _rpa->ReadProgressiveAttachmentBy(s_ignore_all_read); + } + _rpa.reset(NULL); + } + delete _remote_stream_settings; + _bind_sock.reset(); + _thrift_method_name.clear(); + _after_rpc_resp_fn = nullptr; + + CHECK(_unfinished_call == NULL); +} + +void Controller::ResetPods() { + // NOTE: Make the sequence of assignments same with the order that they're + // defined in header. Better for cpu cache and faster for lookup. + _flags = 0; +#ifndef BAIDU_INTERNAL + set_pb_bytes_to_base64(true); +#endif + _error_code = 0; + _session_local_data = NULL; + _server = NULL; + _oncancel_id = INVALID_BTHREAD_ID; + _auth_context = NULL; + _sampled_request = NULL; + _request_protocol = PROTOCOL_UNKNOWN; + _max_retry = UNSET_MAGIC_NUM; + _retry_policy = NULL; + _correlation_id = INVALID_BTHREAD_ID; + _connection_type = CONNECTION_TYPE_UNKNOWN; + _timeout_ms = UNSET_MAGIC_NUM; + _backup_request_ms = UNSET_MAGIC_NUM; + _backup_request_policy = NULL; + _connect_timeout_ms = UNSET_MAGIC_NUM; + _real_timeout_ms = UNSET_MAGIC_NUM; + _deadline_us = -1; + _timeout_id = 0; + _begin_time_us = 0; + _end_time_us = 0; + _tos = 0; + _preferred_index = -1; + _request_compress_type = COMPRESS_TYPE_NONE; + _response_compress_type = COMPRESS_TYPE_NONE; + _request_checksum_type = CHECKSUM_TYPE_NONE; + _response_checksum_type = CHECKSUM_TYPE_NONE; + _fail_limit = UNSET_MAGIC_NUM; + _pipelined_count = 0; + _inheritable.Reset(); + _pchan_sub_count = 0; + _response = NULL; + _done = NULL; + _sender = NULL; + _request_code = 0; + _single_server_id = INVALID_SOCKET_ID; + _unfinished_call = NULL; + _stream_creator = NULL; + _accessed = NULL; + _pack_request = NULL; + _method = NULL; + _auth = NULL; + _idl_names = idl_single_req_single_res; + _idl_result = IDL_VOID_RESULT; + _http_request = NULL; + _http_response = NULL; + _request_user_fields = NULL; + _response_user_fields = NULL; + _request_content_type = CONTENT_TYPE_PB; + _response_content_type = CONTENT_TYPE_PB; + _request_streams.clear(); + _response_streams.clear(); + _remote_stream_settings = NULL; + _session_data = NULL; + _auth_flags = 0; + _rpc_received_us = 0; +} + +Controller::Call::Call(Controller::Call* rhs) + : nretry(rhs->nretry) + , need_feedback(rhs->need_feedback) + , enable_circuit_breaker(rhs->enable_circuit_breaker) + , peer_id(rhs->peer_id) + , begin_time_us(rhs->begin_time_us) + , sending_sock(rhs->sending_sock.release()) + // A backup/retry call never inherits the source call's bind-sock affinity. + , bind_sock_action(BIND_SOCK_NONE) + , stream_user_data(rhs->stream_user_data) { + // NOTE: fields in rhs should be reset because RPC could fail before + // setting all the fields to next call and _current_call.OnComplete + // will behave incorrectly. + rhs->need_feedback = false; + rhs->peer_id = INVALID_SOCKET_ID; + rhs->stream_user_data = NULL; +} + +Controller::Call::~Call() { + CHECK(sending_sock.get() == NULL); +} + +void Controller::Call::Reset() { + nretry = 0; + need_feedback = false; + enable_circuit_breaker = false; + peer_id = INVALID_SOCKET_ID; + begin_time_us = 0; + sending_sock.reset(NULL); + bind_sock_action = BIND_SOCK_NONE; + stream_user_data = NULL; +} + +void Controller::set_timeout_ms(int64_t timeout_ms) { + if (timeout_ms <= 0x7fffffff) { + _timeout_ms = timeout_ms; + _real_timeout_ms = timeout_ms; + } else { + _timeout_ms = 0x7fffffff; + LOG(WARNING) << "timeout_ms is limited to 0x7fffffff (roughly 24 days)"; + } +} + +void Controller::set_backup_request_ms(int64_t timeout_ms) { + if (timeout_ms <= 0x7fffffff) { + _backup_request_ms = timeout_ms; + } else { + _backup_request_ms = 0x7fffffff; + LOG(WARNING) << "backup_request_ms is limited to 0x7fffffff (roughly 24 days)"; + } +} + +int64_t Controller::backup_request_ms() const { + int timeout_ms = _backup_request_ms; + if (NULL != _backup_request_policy) { + const int32_t policy_ms = _backup_request_policy->GetBackupRequestMs(this); + // -1 is the designated sentinel: the policy defers to the channel-level + // backup_request_ms (set from ChannelOptions). Any other negative value + // disables backup for this RPC. Values >= 0 override directly. + if (policy_ms != -1) { + timeout_ms = policy_ms; + } + } + if (timeout_ms > 0x7fffffff) { + timeout_ms = 0x7fffffff; + LOG(WARNING) << "backup_request_ms is limited to 0x7fffffff (roughly 24 days)"; + } + return timeout_ms; +} + +void Controller::set_max_retry(int max_retry) { + if (max_retry > MAX_RETRY_COUNT) { + LOG(WARNING) << "Retry count can't be larger than " + << MAX_RETRY_COUNT << ", round it to " + << MAX_RETRY_COUNT; + _max_retry = MAX_RETRY_COUNT; + } else { + _max_retry = max_retry; + } +} + +void Controller::set_log_id(uint64_t log_id) { + add_flag(FLAGS_LOG_ID); + _inheritable.log_id = log_id; +} + + +bool Controller::Failed() const { + return FailedInline(); +} + +std::string Controller::ErrorText() const { + return _error_text; +} + +void StartCancel(CallId id) { + bthread_id_error(id, ECANCELED); +} + +void Controller::StartCancel() { + LOG(FATAL) << "You must call brpc::StartCancel(id) instead!" + " because this function is racing with ~Controller() in " + " asynchronous calls."; +} + +static const char HEX_ALPHA[] = "0123456789ABCDEF"; +void Controller::AppendServerIdentiy() { + if (_server == NULL) { + return; + } + if (is_security_mode()) { + _error_text.reserve(_error_text.size() + MD5_DIGEST_LENGTH * 2 + 2); + _error_text.push_back('['); + char ipbuf[64]; + int len = snprintf(ipbuf, sizeof(ipbuf), "%s:%d", + butil::my_ip_cstr(), _server->listen_address().port); + unsigned char digest[MD5_DIGEST_LENGTH]; + MD5((const unsigned char*)ipbuf, len, digest); + for (size_t i = 0; i < sizeof(digest); ++i) { + _error_text.push_back(HEX_ALPHA[digest[i] & 0xF]); + _error_text.push_back(HEX_ALPHA[digest[i] >> 4]); + } + _error_text.push_back(']'); + } else { + butil::string_appendf(&_error_text, "[%s:%d]", + butil::my_ip_cstr(), _server->listen_address().port); + } +} + +inline void UpdateResponseHeader(Controller* cntl) { + DCHECK(cntl->Failed()); + if (cntl->request_protocol() == PROTOCOL_HTTP || + cntl->request_protocol() == PROTOCOL_H2) { + if (cntl->ErrorCode() != EHTTP) { + // Set the related status code + cntl->http_response().set_status_code( + ErrorCodeToStatusCode(cntl->ErrorCode())); + } // else assume that status code is already set along with EHTTP. + if (cntl->server() != NULL) { + // Override HTTP body at server-side to conduct error text + // to the client. + // The client-side should preserve body which may be a piece + // of useable data rather than error text. + cntl->response_attachment().clear(); + cntl->response_attachment().append(cntl->ErrorText()); + } + } +} + +void Controller::SetFailed(const std::string& reason) { + _error_code = -1; + if (!_error_text.empty()) { + _error_text.push_back(' '); + } + if (_current_call.nretry != 0) { + butil::string_appendf(&_error_text, "[R%d]", _current_call.nretry); + } else { + AppendServerIdentiy(); + } + _error_text.append(reason); + if (auto span = _span.lock()) { + span->set_error_code(_error_code); + span->Annotate(reason); + } + UpdateResponseHeader(this); +} + +void Controller::SetFailed(int error_code, const char* reason_fmt, ...) { + if (error_code == 0) { + CHECK(false) << "error_code is 0"; + error_code = -1; + } + _error_code = error_code; + if (!_error_text.empty()) { + _error_text.push_back(' '); + } + if (_current_call.nretry != 0) { + butil::string_appendf(&_error_text, "[R%d]", _current_call.nretry); + } else { + AppendServerIdentiy(); + } + const size_t old_size = _error_text.size(); + if (_error_code != -1) { + butil::string_appendf(&_error_text, "[E%d]", _error_code); + } + va_list ap; + va_start(ap, reason_fmt); + butil::string_vappendf(&_error_text, reason_fmt, ap); + va_end(ap); + if (auto span = _span.lock()) { + span->set_error_code(_error_code); + span->AnnotateCStr(_error_text.c_str() + old_size, 0); + } + UpdateResponseHeader(this); +} + +void Controller::CloseConnection(const char* reason_fmt, ...) { + if (_error_code == 0) { + _error_code = ECLOSE; + } + add_flag(FLAGS_CLOSE_CONNECTION); + if (!_error_text.empty()) { + _error_text.push_back(' '); + } + if (_current_call.nretry != 0) { + butil::string_appendf(&_error_text, "[R%d]", _current_call.nretry); + } else { + AppendServerIdentiy(); + } + const size_t old_size = _error_text.size(); + if (_error_code != -1) { + butil::string_appendf(&_error_text, "[E%d]", _error_code); + } + va_list ap; + va_start(ap, reason_fmt); + butil::string_vappendf(&_error_text, reason_fmt, ap); + va_end(ap); + if (auto span = _span.lock()) { + span->set_error_code(_error_code); + span->AnnotateCStr(_error_text.c_str() + old_size, 0); + } + UpdateResponseHeader(this); +} + +bool Controller::IsCanceled() const { + SocketUniquePtr sock; + return (Socket::Address(_current_call.peer_id, &sock) != 0); +} + +class RunOnCancelThread { +public: + RunOnCancelThread(google::protobuf::Closure* cb, bthread_id_t id) + : _cb(cb), _id(id) {} + + static void* RunThis(void* arg) { + ((RunOnCancelThread*)arg)->Run(); + return NULL; + } + + void Run() { + _cb->Run(); + CHECK_EQ(0, bthread_id_unlock_and_destroy(_id)); + delete this; + } + +private: + google::protobuf::Closure* _cb; + bthread_id_t _id; +}; + +int Controller::RunOnCancel(bthread_id_t id, void* data, int error_code) { + if (error_code == 0) { + // Called from Controller::ResetNonPods upon Controller's Reset or + // destruction, we just call the callback in-place. + static_cast(data)->Run(); + CHECK_EQ(0, bthread_id_unlock_and_destroy(id)); + return 0; + } + // Called from Socket::SetFailed, should be infrequent. + // To make sure Socket::SetFailed is never blocked, we run the callback + // in a new thread. + RunOnCancelThread* arg = new RunOnCancelThread( + static_cast(data), id); + bthread_t th; + CHECK_EQ(0, bthread_start_urgent(&th, NULL, RunOnCancelThread::RunThis, arg)); + return 0; +} + +void Controller::NotifyOnCancel(google::protobuf::Closure* callback) { + if (NULL == callback) { + LOG(WARNING) << "Parameter `callback' is NLLL"; + return; + } + + ClosureGuard guard(callback); + if (_oncancel_id != INVALID_BTHREAD_ID) { + LOG(FATAL) << "NotifyCancel a single call more than once!"; + return; + } + SocketUniquePtr sock; + if (Socket::Address(_current_call.peer_id, &sock) != 0) { + // Connection already broken + return; + } + if (bthread_id_create(&_oncancel_id, callback, RunOnCancel) != 0) { + PLOG(FATAL) << "Fail to create bthread_id"; + return; + } + sock->NotifyOnFailed(_oncancel_id); // Always succeed + guard.release(); +} + +void Join(CallId id) { + bthread_id_join(id); +} + +void JoinResponse(CallId id) { + bthread_id_join(id); +} + +static void HandleTimeout(void* arg) { + bthread_id_t correlation_id = { (uint64_t)arg }; + bthread_id_error(correlation_id, ERPCTIMEDOUT); +} + +void Controller::OnVersionedRPCReturned(const CompletionInfo& info, + bool new_bthread, int saved_error) { + // TODO(gejun): Simplify call-ending code. + // Intercept previous calls + while (info.id != _correlation_id && info.id != current_id()) { + if (_unfinished_call && get_id(_unfinished_call->nretry) == info.id) { + if (!FailedInline()) { + // Continue with successful backup request. + break; + } + // Complete failed backup request. + _unfinished_call->OnComplete(this, _error_code, info.responded, false); + delete _unfinished_call; + _unfinished_call = NULL; + } + // Ignore all non-backup requests and failed backup requests. + _error_code = saved_error; + response_attachment().clear(); + CHECK_EQ(0, bthread_id_unlock(info.id)); + return; + } + + if (is_ending_rpc()) { + // SelectiveChannel may still deliver late SubDone callbacks after the + // main RPC has entered EndRPC(). Ignore those callbacks instead of + // letting them re-enter retry/backup on partially torn-down state. + _error_code = saved_error; + response_attachment().clear(); + CHECK_EQ(0, bthread_id_unlock(info.id)); + return; + } + + if ((!_error_code && _retry_policy == NULL) || + _current_call.nretry >= _max_retry) { + goto END_OF_RPC; + } + if (_error_code == EBACKUPREQUEST) { + if (NULL != _backup_request_policy && !_backup_request_policy->DoBackup(this)) { + // No need to do backup request. + _error_code = saved_error; + CHECK_EQ(0, bthread_id_unlock(info.id)); + return; + } + + // Reset timeout if needed + int rc = 0; + if (timeout_ms() >= 0) { + rc = bthread_timer_add( + &_timeout_id, + butil::microseconds_to_timespec(_deadline_us), + HandleTimeout, (void*)_correlation_id.value); + } + if (rc != 0) { + SetFailed(rc, "Fail to add timer"); + goto END_OF_RPC; + } + if (!SingleServer()) { + if (_accessed == NULL) { + _accessed = ExcludedServers::Create( + std::min(_max_retry, RETRY_AVOIDANCE)); + if (NULL == _accessed) { + SetFailed(ENOMEM, "Fail to create ExcludedServers"); + goto END_OF_RPC; + } + } + _accessed->Add(_current_call.peer_id); + } + // _current_call does not end yet. + CHECK(_unfinished_call == NULL); // only one backup request now. + _unfinished_call = new (std::nothrow) Call(&_current_call); + if (_unfinished_call == NULL) { + SetFailed(ENOMEM, "Fail to new Call"); + goto END_OF_RPC; + } + ++_current_call.nretry; + add_flag(FLAGS_BACKUP_REQUEST); + return IssueRPC(butil::gettimeofday_us()); + } else { + auto retry_policy = _retry_policy ? _retry_policy : DefaultRetryPolicy(); + if (retry_policy->DoRetry(this)) { + // The error must come from _current_call because: + // * we intercepted error from _unfinished_call in OnVersionedRPCReturned + // * ERPCTIMEDOUT/ECANCELED are not retrying error by default. + CHECK_EQ(current_id(), info.id) << "error_code=" << _error_code; + if (!SingleServer()) { + if (_accessed == NULL) { + _accessed = ExcludedServers::Create( + std::min(_max_retry, RETRY_AVOIDANCE)); + if (NULL == _accessed) { + SetFailed(ENOMEM, "Fail to create ExcludedServers"); + goto END_OF_RPC; + } + } + _accessed->Add(_current_call.peer_id); + } + _current_call.OnComplete(this, _error_code, info.responded, false); + ++_current_call.nretry; + // Clear http responses before retrying, otherwise the response may + // be mixed with older (and undefined) stuff. This is actually not + // done before r32008. + if (_http_response) { + _http_response->Clear(); + } + response_attachment().clear(); + + // Retry backoff. + bthread::TaskGroup* g = bthread::tls_task_group; + int64_t backoff_time_us = retry_policy->GetBackoffTimeMs(this) * 1000L; + if (backoff_time_us > 0 && + backoff_time_us < _deadline_us - butil::gettimeofday_us()) { + // No need to do retry backoff when the backoff time is longer than the remaining rpc time. + if (retry_policy->CanRetryBackoffInPthread() || + (g && !g->is_current_pthread_task())) { + bthread_usleep(backoff_time_us); + } else { + LOG(WARNING) << "`CanRetryBackoffInPthread()' returns false, " + "skip retry backoff in pthread."; + } + } + return IssueRPC(butil::gettimeofday_us()); + } + } + +END_OF_RPC: + if (new_bthread && !FLAGS_usercode_in_coroutine) { + // [ Essential for -usercode_in_pthread=true ] + // When -usercode_in_pthread is on, the reserved threads (set by + // -usercode_backup_threads) may all block on bthread_id_lock in + // ProcessXXXResponse(), until the id is unlocked or destroyed which + // is run in a new thread when new_bthread is true. However since all + // workers are blocked, the created bthread will never be scheduled + // and result in deadlock. + // Make the id unlockable before creating the bthread fixes the issue. + // When -usercode_in_pthread is false, this also removes some useless + // waiting of the bthreads processing responses. + + // Note[_done]: callid is destroyed after _done which possibly takes + // a lot of time, stop useless locking + + // Note[cid]: When the callid needs to be destroyed in done->Run(), + // it does not mean that it will be destroyed directly in done->Run(), + // conversely the callid may still be locked/unlocked for many times + // before destroying. E.g. in slective channel, the callid is referenced + // by multiple sub-done and only destroyed by the last one. Calling + // bthread_id_about_to_destroy right here which makes the id unlockable + // anymore, is wrong. On the other hand, the combo channles setting + // FLAGS_DESTROY_CID_IN_DONE to true must be aware of + // -usercode_in_pthread and avoid deadlock by their own (TBR) + + if ((FLAGS_usercode_in_pthread || _done != NULL/*Note[_done]*/) && + !has_flag(FLAGS_DESTROY_CID_IN_DONE)/*Note[cid]*/) { + bthread_id_about_to_destroy(info.id); + } + // No need to join this bthread since RPC caller won't wake up + // (or user's done won't be called) until this bthread finishes + bthread_t bt; + bthread_attr_t attr = (FLAGS_usercode_in_pthread ? + BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL); + bthread_attr_set_name(&attr, "RunEndRPC"); + _tmp_completion_info = info; + if (bthread_start_background(&bt, &attr, RunEndRPC, this) != 0) { + LOG(FATAL) << "Fail to start bthread"; + EndRPC(info); + } + } else { + if (_done != NULL/*Note[_done]*/ && + !has_flag(FLAGS_DESTROY_CID_IN_DONE)/*Note[cid]*/) { + bthread_id_about_to_destroy(info.id); + } + EndRPC(info); + } +} + +void* Controller::RunEndRPC(void* arg) { + Controller* c = static_cast(arg); + c->EndRPC(c->_tmp_completion_info); + return NULL; +} + +inline bool does_error_affect_main_socket(int error_code) { + // Errors tested in this function are reported by pooled connections + // and very likely to indicate that the server-side is down and the socket + // should be health-checked. + return error_code == ECONNREFUSED || + error_code == ENETUNREACH || + error_code == EHOSTUNREACH || + error_code == EINVAL/*returned by connect "0.0.0.1"*/; +} + +//Note: A RPC call is probably consisted by several individual Calls such as +// retries and backup requests. This method simply cares about the error of +// this very Call (specified by |error_code|) rather than the error of the +// entire RPC (specified by c->FailedInline()). +void Controller::Call::OnComplete( + Controller* c, int error_code/*note*/, bool responded, bool end_of_rpc) { + if (stream_user_data) { + stream_user_data->DestroyStreamUserData(sending_sock, c, error_code, end_of_rpc); + stream_user_data = NULL; + } + + if (sending_sock != NULL) { + if (error_code != 0) { + sending_sock->AddRecentError(); + } + + if (enable_circuit_breaker) { + sending_sock->FeedbackCircuitBreaker(error_code, + butil::gettimeofday_us() - begin_time_us); + } + } + + switch (c->connection_type()) { + case CONNECTION_TYPE_UNKNOWN: + break; + case CONNECTION_TYPE_SINGLE: + // Set main socket to be failed for connection refusal of streams. + // "single" streams are often maintained in a separate SocketMap and + // different from the main socket as well. + if (c->_stream_creator != NULL && + does_error_affect_main_socket(error_code) && + (sending_sock == NULL || sending_sock->id() != peer_id)) { + Socket::SetFailed(peer_id); + } + break; + case CONNECTION_TYPE_POOLED: + // NOTE: Not reuse pooled connection if this call fails and no response + // has been received through this connection + // Otherwise in-flight responses may come back in future and break the + // assumption that one pooled connection cannot have more than one + // message at the same time. + if (sending_sock != NULL && (error_code == 0 || responded)) { + if (bind_sock_action == BIND_SOCK_RESERVE) { + // Reserve this socket on the controller for a following RPC + // (used by mysql transactions for connection affinity). + c->_bind_sock.reset(sending_sock.release()); + } else if (bind_sock_action == BIND_SOCK_USE) { + // Socket is owned by the binder; do not return it to the pool. + } else if (!sending_sock->is_read_progressive()) { + // Normally-read socket which will not be used after RPC ends, + // safe to return. Notice that Socket::is_read_progressive may + // differ from Controller::is_response_read_progressively() + // because RPC possibly ends before setting up the socket. + sending_sock->ReturnToPool(); + } else { + // Progressively-read socket. Should be returned when the read + // ends. The method handles the details. + sending_sock->OnProgressiveReadCompleted(); + } + break; + } + // fall through + case CONNECTION_TYPE_SHORT: + if (sending_sock != NULL) { + // Check the comment in CONNECTION_TYPE_POOLED branch. + if (bind_sock_action == BIND_SOCK_RESERVE) { + c->_bind_sock.reset(sending_sock.release()); + } else if (bind_sock_action == BIND_SOCK_USE) { + // Socket is owned by the binder; do not fail it. + } else if (!sending_sock->is_read_progressive()) { + if (c->_stream_creator == NULL) { + sending_sock->SetFailed(); + } + } else { + sending_sock->OnProgressiveReadCompleted(); + } + } + if (does_error_affect_main_socket(error_code)) { + // main socket should die as well. + // NOTE: main socket may be wrongly set failed (provided that + // short/pooled socket does not hold a ref of the main socket). + // E.g. an in-parallel RPC sets the peer_id to be failed + // -> this RPC meets ECONNREFUSED + // -> main socket gets revived from HC + // -> this RPC sets main socket to be failed again. + Socket::SetFailed(peer_id); + } + break; + } + + if (ELOGOFF == error_code) { + SocketUniquePtr sock; + if (Socket::Address(peer_id, &sock) == 0) { + // Block this `Socket' while not closing the fd + sock->SetLogOff(); + } + } + + if (need_feedback && c->_lb) { + const LoadBalancer::CallInfo info = + { begin_time_us, peer_id, error_code, c }; + c->_lb->Feedback(info); + } + + // Release the `Socket' we used to send/receive data + sending_sock.reset(NULL); +} + +void Controller::EndRPC(const CompletionInfo& info) { + add_flag(FLAGS_ENDING_RPC); + + if (_timeout_id != 0) { + bthread_timer_del(_timeout_id); + _timeout_id = 0; + } + + // End _current_call and _unfinished_call. + if (info.id == current_id() || info.id == _correlation_id) { + if (_current_call.sending_sock != NULL) { + _remote_side = _current_call.sending_sock->remote_side(); + _local_side = _current_call.sending_sock->local_side(); + } + + if (_unfinished_call != NULL) { + // When _current_call is successful, mark _unfinished_call as + // EBACKUPREQUEST, we can't use 0 because the server possibly + // never respond, we can't use ERPCTIMEDOUT because _current_call + // is sent after _unfinished_call which is not necessarily timedout + // When _current_call is error, mark _unfinished_call with the + // same error. This is not accurate as well, but we have to end + // _unfinished_call with some sort of error anyway. + const int err = (_error_code == 0 ? EBACKUPREQUEST : _error_code); + _unfinished_call->OnComplete(this, err, false, false); + delete _unfinished_call; + _unfinished_call = NULL; + } + // TODO: Replace this with stream_creator. + HandleStreamConnection(_current_call.sending_sock.get()); + // Propagate the reserve action; OnComplete only actually reserves the + // socket when the RPC succeeded (its error_code==0 || responded guard). + _current_call.bind_sock_action = bind_sock_action(); + _current_call.OnComplete(this, _error_code, info.responded, true); + } else { + // Even if _unfinished_call succeeded, we don't use EBACKUPREQUEST + // (which gets punished in LALB) for _current_call because _current_call + // is sent after _unfinished_call, it's just normal that _current_call + // does not respond before _unfinished_call. + if (_unfinished_call == NULL) { + CHECK(false) << "A previous non-backup request responded, cid=" + << info.id << " current_cid=" << current_id() + << " initial_cid=" << _correlation_id + << " stream_user_data=" << _current_call.stream_user_data + << " sending_sock=" << _current_call.sending_sock.get(); + } + _current_call.OnComplete(this, ECANCELED, false, false); + if (_unfinished_call != NULL) { + if (_unfinished_call->sending_sock != NULL) { + _remote_side = _unfinished_call->sending_sock->remote_side(); + _local_side = _unfinished_call->sending_sock->local_side(); + } + // TODO: Replace this with stream_creator. + HandleStreamConnection(_unfinished_call->sending_sock.get()); + if (get_id(_unfinished_call->nretry) == info.id) { + _unfinished_call->OnComplete( + this, _error_code, info.responded, true); + } else { + CHECK(false) << "A previous non-backup request responded"; + _unfinished_call->OnComplete(this, ECANCELED, false, true); + } + + delete _unfinished_call; + _unfinished_call = NULL; + } + } + if (_stream_creator) { + _stream_creator->DestroyStreamCreator(this); + _stream_creator = NULL; + } + // Clear _error_text when the call succeeded, otherwise a successful + // call with non-empty ErrorText may confuse user. + if (!_error_code) { + _error_text.clear(); + } + // RPC finished, now it's safe to release `LoadBalancerWithNaming' + _lb.reset(); + if (auto span = _span.lock()) { + span->set_ending_cid(info.id); + span->set_async(_done); + // Submit the span if we're in async RPC. For sync RPC, the span + // is submitted after Join() to get a more accurate resuming timestamp. + if (_done) { + SubmitSpan(); + } + } + + // No need to retry or can't retry, just call user's `done'. + const CallId saved_cid = _correlation_id; + if (_done) { + if (!FLAGS_usercode_in_pthread || _done == DoNothing()/*Note*/) { + // Note: no need to run DoNothing in backup thread when pthread + // mode is on. Otherwise there's a tricky deadlock: + // void SomeService::CallMethod(...) { // -usercode_in_pthread=true + // ... + // channel.CallMethod(...., brpc::DoNothing()); + // brpc::Join(cntl.call_id()); + // ... + // } + // Join is not signalled when the done does not Run() and the done + // can't Run() because all backup threads are blocked by Join(). + + OnRPCEnd(butil::gettimeofday_us()); + const bool destroy_cid_in_done = has_flag(FLAGS_DESTROY_CID_IN_DONE); + _done->Run(); + // NOTE: Don't touch this Controller anymore, because it's likely to be + // deleted by done. + if (!destroy_cid_in_done) { + // Make this thread not scheduling itself when launching new + // bthreads, saving signalings. + // FIXME: We're assuming the calling thread is about to quit. + bthread_about_to_quit(); + CHECK_EQ(0, bthread_id_unlock_and_destroy(saved_cid)); + } + } else { + RunUserCode(RunDoneInBackupThread, this); + } + } else { + // OnRPCEnd for sync RPC is called in Channel::CallMethod to count in + // latency of the context-switch. + + // Check comments in above branch on bthread_about_to_quit. + bthread_about_to_quit(); + CHECK_EQ(0, bthread_id_unlock_and_destroy(saved_cid)); + } +} + +void Controller::OnRPCEnd(int64_t end_time_us) { + _end_time_us = end_time_us; + if (NULL != _backup_request_policy) { + _backup_request_policy->OnRPCEnd(this); + } +} + +void Controller::RunDoneInBackupThread(void* arg) { + static_cast(arg)->DoneInBackupThread(); +} + +void Controller::DoneInBackupThread() { + // OnRPCEnd for sync RPC is called in Channel::CallMethod to count in + // latency of the context-switch. + OnRPCEnd(butil::gettimeofday_us()); + const CallId saved_cid = _correlation_id; + const bool destroy_cid_in_done = has_flag(FLAGS_DESTROY_CID_IN_DONE); + _done->Run(); + // NOTE: Don't touch fields of controller anymore, it may be deleted. + if (!destroy_cid_in_done) { + CHECK_EQ(0, bthread_id_unlock_and_destroy(saved_cid)); + } +} + +void Controller::SubmitSpan() { + const int64_t now = butil::cpuwide_time_us(); + if (auto span = _span.lock()) { + span->set_start_callback_us(now); + if (auto parent_span = span->local_parent().lock()) { + if (parent_span->is_active()) { + parent_span->AsParent(); + } + } + Span::Submit(span, now); + _span.reset(); + } +} + +void Controller::HandleSendFailed() { + if (!FailedInline()) { + SetFailed("Must be SetFailed() before calling HandleSendFailed()"); + LOG(FATAL) << ErrorText(); + } + const CompletionInfo info = { current_id(), false }; + // NOTE: Launch new thread to run the callback in an asynchronous call + // (and done is not allowed to run in-place) + // Users may hold a lock before asynchronus CallMethod returns and + // grab the same lock inside done->Run(). If done->Run() is called in the + // same stack of CallMethod, the code is deadlocked. + // We don't need to run the callback in new thread in a sync call since + // the created thread needs to be joined anyway before end of CallMethod. + const bool new_bthread = (_done != NULL && !is_done_allowed_to_run_in_place()); + OnVersionedRPCReturned(info, new_bthread, _error_code); +} + +void Controller::IssueRPC(int64_t start_realtime_us) { + _current_call.begin_time_us = start_realtime_us; + + // If has retry/backup request,we will recalculate the timeout, + if (_real_timeout_ms > 0) { + _real_timeout_ms -= (start_realtime_us - _begin_time_us) / 1000; + } + + // Clear last error, Don't clear _error_text because we append to it. + _error_code = 0; + + // Make versioned correlation_id. + // call_id : unversioned, mainly for ECANCELED and ERPCTIMEDOUT + // call_id + 1 : first try. + // call_id + 2 : retry 1 + // ... + // call_id + N + 1 : retry N + // All ids except call_id are versioned. Say if we've sent retry 1 and + // a failed response of first try comes back, it will be ignored. + const CallId cid = current_id(); + + // Intercept IssueRPC when _sender is set. Currently _sender is only set + // by SelectiveChannel. + if (_sender) { + if (_sender->IssueRPC(start_realtime_us) != 0) { + return HandleSendFailed(); + } + CHECK_EQ(0, bthread_id_unlock(cid)); + return; + } + + // Pick a target server for sending RPC + _current_call.need_feedback = false; + _current_call.enable_circuit_breaker = has_enabled_circuit_breaker(); + SocketUniquePtr tmp_sock; + if ((_connection_type & CONNECTION_TYPE_POOLED_AND_SHORT) && + bind_sock_action() == BIND_SOCK_USE) { + // Reuse the socket reserved by a previous RPC (mysql transaction affinity). + tmp_sock.reset(_bind_sock.release()); + if (!tmp_sock || (!is_health_check_call() && !tmp_sock->IsAvailable())) { + SetFailed(EHOSTDOWN, "Not connected to bind socket yet, server_id=%" PRIu64, + tmp_sock ? tmp_sock->id() : (SocketId)0); + tmp_sock.reset(); // Release ref ASAP + return HandleSendFailed(); + } + _current_call.peer_id = tmp_sock->id(); + } else if (SingleServer()) { + // Don't use _current_call.peer_id which is set to -1 after construction + // of the backup call. + const int rc = Socket::Address(_single_server_id, &tmp_sock); + if (rc != 0 || (!is_health_check_call() && !tmp_sock->IsAvailable())) { + SetFailed(EHOSTDOWN, "Not connected to %s yet, server_id=%" PRIu64, + endpoint2str(_remote_side).c_str(), _single_server_id); + tmp_sock.reset(); // Release ref ASAP + return HandleSendFailed(); + } + _current_call.peer_id = _single_server_id; + } else { + LoadBalancer::SelectIn sel_in = + { start_realtime_us, true, + has_request_code(), _request_code, _accessed }; + LoadBalancer::SelectOut sel_out(&tmp_sock); + const int rc = _lb->SelectServer(sel_in, &sel_out); + if (rc != 0) { + std::ostringstream os; + DescribeOptions opt; + opt.verbose = false; + _lb->Describe(os, opt); + SetFailed(rc, "Fail to select server from %s", os.str().c_str()); + return HandleSendFailed(); + } + _current_call.need_feedback = sel_out.need_feedback; + _current_call.peer_id = tmp_sock->id(); + // NOTE: _remote_side must be set here because _pack_request below + // may need it (e.g. http may set "Host" to _remote_side) + // Don't set _local_side here because tmp_sock may be not connected + // here. + _remote_side = tmp_sock->remote_side(); + } + if (_stream_creator) { + _current_call.stream_user_data = + _stream_creator->OnCreatingStream(&tmp_sock, this); + if (FailedInline()) { + return HandleSendFailed(); + } + // remote_side can't be changed. + CHECK_EQ(_remote_side, tmp_sock->remote_side()); + } + + if (auto span = _span.lock()) { + if (_current_call.nretry == 0) { + span->set_remote_side(_remote_side); + } else { + span->Annotate("Retrying %s", + endpoint2str(_remote_side).c_str()); + } + } + // Handle connection type + if (_connection_type == CONNECTION_TYPE_SINGLE || + _stream_creator != NULL) { // let user decides the sending_sock + // in the callback(according to connection_type) directly + _current_call.sending_sock.reset(tmp_sock.release()); + // TODO(gejun): Setting preferred index of single-connected socket + // has two issues: + // 1. race conditions. If a set perferred_index is overwritten by + // another thread, the response back has to check protocols one + // by one. This is a performance issue, correctness is unaffected. + // 2. thrashing between different protocols. Also a performance issue. + _current_call.sending_sock->set_preferred_index(_preferred_index); + } else { + int rc = 0; + if (bind_sock_action() == BIND_SOCK_USE) { + // Already holding the reserved socket; use it directly. + _current_call.sending_sock.reset(tmp_sock.release()); + } else if (_connection_type == CONNECTION_TYPE_POOLED) { + rc = tmp_sock->GetPooledSocket(&_current_call.sending_sock); + } else if (_connection_type == CONNECTION_TYPE_SHORT) { + rc = tmp_sock->GetShortSocket(&_current_call.sending_sock); + } else { + tmp_sock.reset(); + SetFailed(EINVAL, "Invalid connection_type=%d", (int)_connection_type); + return HandleSendFailed(); + } + if (rc) { + tmp_sock.reset(); + SetFailed(rc, "Fail to get %s connection", + ConnectionTypeToString(_connection_type)); + return HandleSendFailed(); + } + // Remember the preferred protocol for non-single connection. When + // the response comes back, InputMessenger calls the right handler + // w/o trying other protocols. This is a must for (many) protocols that + // can't be distinguished from other protocols w/o ambiguity. + _current_call.sending_sock->set_preferred_index(_preferred_index); + // Set preferred_index of main_socket as well to make it easier to + // debug and observe from /connections. + // tmp_sock is NULL on the BIND_SOCK_USE path. + if (tmp_sock && tmp_sock->preferred_index() < 0) { + tmp_sock->set_preferred_index(_preferred_index); + } + tmp_sock.reset(); + } + if (_tos > 0) { + _current_call.sending_sock->set_type_of_service(_tos); + } + if (is_response_read_progressively()) { + // Tag the socket so that when the response comes back, the parser will + // stop before reading all body. + _current_call.sending_sock->read_will_be_progressive(_connection_type); + } + + // Handle authentication + const Authenticator* using_auth = NULL; + if (_auth != NULL) { + // Only one thread will be the winner and get the right to pack + // authentication information, others wait until the request + // is sent. + int auth_error = 0; + if (_current_call.sending_sock->FightAuthentication(&auth_error) == 0) { + using_auth = _auth; + } else if (auth_error != 0) { + SetFailed(auth_error, "Fail to authenticate, %s", + berror(auth_error)); + return HandleSendFailed(); + } + } + // Make request + butil::IOBuf packet; + SocketMessage* user_packet = NULL; + _pack_request(&packet, &user_packet, cid.value, _method, this, + _request_buf, using_auth); + // TODO: PackRequest may accept SocketMessagePtr<>? + SocketMessagePtr<> user_packet_guard(user_packet); + if (FailedInline()) { + // controller should already be SetFailed. + if (using_auth) { + // Don't forget to signal waiters on authentication + _current_call.sending_sock->SetAuthentication(ErrorCode()); + } + return HandleSendFailed(); + } + + timespec connect_abstime; + timespec* pabstime = NULL; + if (_connect_timeout_ms > 0) { + if (_deadline_us >= 0) { + connect_abstime = butil::microseconds_to_timespec( + std::min(_connect_timeout_ms * 1000L + start_realtime_us, + _deadline_us)); + } else { + connect_abstime = butil::microseconds_to_timespec( + _connect_timeout_ms * 1000L + start_realtime_us); + } + pabstime = &connect_abstime; + } + Socket::WriteOptions wopt; + wopt.id_wait = cid; + wopt.abstime = pabstime; + wopt.pipelined_count = _pipelined_count; + wopt.auth_flags = _auth_flags; + wopt.ignore_eovercrowded = has_flag(FLAGS_IGNORE_EOVERCROWDED); + wopt.write_in_background = write_to_socket_in_background(); + int rc; + size_t packet_size = 0; + if (user_packet_guard) { + if (auto span = _span.lock()) { + packet_size = user_packet_guard->EstimatedByteSize(); + } + rc = _current_call.sending_sock->Write(user_packet_guard, &wopt); + } else { + packet_size = packet.size(); + rc = _current_call.sending_sock->Write(&packet, &wopt); + } + if (auto span = _span.lock()) { + if (_current_call.nretry == 0) { + span->set_sent_us(butil::cpuwide_time_us()); + span->set_request_size(packet_size); + } else { + span->Annotate("Requested(%lld) [%d]", + (long long)packet_size, _current_call.nretry + 1); + } + } + if (using_auth) { + // For performance concern, we set authentication to immediately + // after the first `Write' returns instead of waiting for server + // to confirm the credential data + _current_call.sending_sock->SetAuthentication(rc); + } + CHECK_EQ(0, bthread_id_unlock(cid)); +} + +void Controller::set_auth_context(const AuthContext* ctx) { + if (_auth_context != NULL) { + LOG(FATAL) << "Impossible! This function is supposed to be called " + "only once when verification succeeds in server side"; + return; + } + // Ownership is belong to `Socket' instead of `Controller' + _auth_context = ctx; +} + +int Controller::HandleSocketFailed(bthread_id_t id, void* data, int error_code, + const std::string& error_text) { + Controller* cntl = static_cast(data); + if (!cntl->is_used_by_rpc()) { + // Cannot destroy the call_id before RPC otherwise an async RPC + // using the controller cannot be joined and related resources may be + // destroyed before done->Run() running in another bthread. + // The error set will be detected in Channel::CallMethod and fail + // the RPC. + cntl->SetFailed(error_code, "Cancel call_id=%" PRId64 + " before CallMethod()", id.value); + return bthread_id_unlock(id); + } + const int saved_error = cntl->ErrorCode(); + if (error_code == ERPCTIMEDOUT) { + cntl->SetFailed(error_code, "Reached timeout=%" PRId64 "ms @%s", + cntl->timeout_ms(), + butil::endpoint2str(cntl->remote_side()).c_str()); + } else if (error_code == EBACKUPREQUEST) { + cntl->SetFailed(error_code, "Reached backup timeout=%" PRId64 "ms @%s", + cntl->backup_request_ms(), + butil::endpoint2str(cntl->remote_side()).c_str()); + } else if (!error_text.empty()) { + cntl->SetFailed(error_code, "%s", error_text.c_str()); + } else { + cntl->SetFailed(error_code, "%s @%s", berror(error_code), + butil::endpoint2str(cntl->remote_side()).c_str()); + } + + struct OnVersionedRPCReturnedArgs { + bthread_id_t id; + Controller* cntl; + int error; + }; + auto func = [](void* p) -> void* { + std::unique_ptr args(static_cast(p)); + CompletionInfo info = { args->id, false }; + args->cntl->OnVersionedRPCReturned(info, true, args->error); + return NULL; + }; + + auto* args = new OnVersionedRPCReturnedArgs{ id, cntl, saved_error }; + bthread_t tid; + // RetryPolicy may block current bthread, so start a new bthread to run OnVersionedRPCReturned + if (!cntl->_retry_policy || bthread_start_background(&tid, NULL, func, args) != 0) { + func(args); + } + return 0; +} + +CallId Controller::call_id() { + butil::atomic* target = + (butil::atomic*)&_correlation_id.value; + uint64_t loaded = target->load(butil::memory_order_relaxed); + if (loaded) { + const CallId id = { loaded }; + return id; + } + // Optimistic locking. + CallId cid = { 0 }; + // The range of this id will be reset in Channel::CallMethod + CHECK_EQ(0, bthread_id_create2(&cid, this, HandleSocketFailed)); + if (!target->compare_exchange_strong(loaded, cid.value, + butil::memory_order_relaxed)) { + bthread_id_cancel(cid); + cid.value = loaded; + } + return cid; +} + +void Controller::SaveClientSettings(ClientSettings* s) const { + s->timeout_ms = _timeout_ms; + s->backup_request_ms = _backup_request_ms; + s->backup_request_policy = _backup_request_policy; + s->max_retry = _max_retry; + s->tos = _tos; + s->connection_type = _connection_type; + s->request_compress_type = _request_compress_type; + s->request_checksum_type = _request_checksum_type; + s->log_id = log_id(); + s->has_request_code = has_request_code(); + s->request_code = _request_code; +} + +void Controller::ApplyClientSettings(const ClientSettings& s) { + set_timeout_ms(s.timeout_ms); + set_backup_request_ms(s.backup_request_ms); + set_backup_request_policy(s.backup_request_policy); + set_max_retry(s.max_retry); + set_type_of_service(s.tos); + set_connection_type(s.connection_type); + set_request_compress_type(s.request_compress_type); + set_request_checksum_type(s.request_checksum_type); + set_log_id(s.log_id); + set_flag(FLAGS_REQUEST_CODE, s.has_request_code); + _request_code = s.request_code; +} + +int Controller::sub_count() const { + int n = _pchan_sub_count; + if (_sender) { + n += !!GetSubControllerOfSelectiveChannel(_sender, 0); + } + return n; +} + +const Controller* Controller::sub(int index) const { + if (_pchan_sub_count > 0 && _done != NULL) { + return GetSubControllerOfParallelChannel(_done, index); + } + if (_sender != NULL) { + return GetSubControllerOfSelectiveChannel(_sender, index); + } + return NULL; +} + +uint64_t Controller::trace_id() const { + if (auto span = _span.lock()) { + return span->trace_id(); + } + return 0; +} + +uint64_t Controller::span_id() const { + if (auto span = _span.lock()) { + return span->span_id(); + } + return 0; +} + +void* Controller::session_local_data() { + if (_session_local_data) { + return _session_local_data; + } + if (_server) { + SimpleDataPool* pool = _server->_session_local_data_pool; + if (pool) { + _session_local_data = pool->Borrow(); + return _session_local_data; + } + } + return NULL; +} + +void Controller::HandleStreamConnection(Socket *host_socket) { + if (_request_streams.empty()) { + CHECK(!has_remote_stream()); + return; + } + size_t stream_num = _request_streams.size(); + std::vector ptrs(stream_num); + if (!FailedInline()) { + if (_remote_stream_settings == NULL) { + if (!FailedInline()) { + SetFailed(EREQUEST, "The server didn't accept the stream"); + } + } else { + for (size_t i = 0; i < stream_num; ++i) { + if (Socket::Address(_request_streams[i], &ptrs[i]) != 0) { + if (!FailedInline()) { + SetFailed(EREQUEST, "Request stream=%" PRIu64 " was closed before responded", + _request_streams[i]); + break; + } + } + } + } + } + if (FailedInline()) { + Stream::SetFailed(_request_streams, _error_code, + "%s", _error_text.c_str()); + if (_remote_stream_settings != NULL) { + policy::SendStreamRst(host_socket, + _remote_stream_settings->stream_id()); + for (int i = 0; i < _remote_stream_settings->extra_stream_ids().size(); ++i) { + policy::SendStreamRst(host_socket, + _remote_stream_settings->extra_stream_ids()[i]); + } + } + return; + } + Stream* s = (Stream*)ptrs[0]->conn(); + s->SetConnected(_remote_stream_settings); + if (stream_num > 1) { + auto extra_stream_ids = std::move(*_remote_stream_settings->mutable_extra_stream_ids()); + _remote_stream_settings->clear_extra_stream_ids(); + for (size_t i = 1; i < stream_num; ++i) { + if(!ptrs[i]) continue; + Stream* extra_stream = (Stream *) ptrs[i]->conn(); + _remote_stream_settings->set_stream_id(extra_stream_ids[i - 1]); + s->SetHostSocket(host_socket); + extra_stream->SetConnected(_remote_stream_settings); + } + } +} + +// TODO: Need more security advices from professionals. +// TODO: Is percent encoding better? +void WebEscape(const std::string& source, std::string* output) { + output->reserve(source.length() + 10); + for (size_t pos = 0; pos != source.size(); ++pos) { + switch (source[pos]) { + case '&': output->append("&"); break; + case '\"': output->append("""); break; + case '\'': output->append("'"); break; + case '<': output->append("<"); break; + case '>': output->append(">"); break; + default: output->push_back(source[pos]); break; + } + } +} + +std::string WebEscape(const std::string& source) { + std::string output; + WebEscape(source, &output); + return output; +} + +void Controller::reset_sampled_request(SampledRequest* req) { + delete _sampled_request; + _sampled_request = req; +} + +SampledRequest* Controller::release_sampled_request() { + SampledRequest* saved_sampled_request = _sampled_request; + _sampled_request = NULL; + return saved_sampled_request; +} + +void Controller::set_stream_creator(StreamCreator* sc) { + if (_stream_creator) { + LOG(FATAL) << "A StreamCreator has been set previously"; + return; + } + _stream_creator = sc; +} + +butil::intrusive_ptr +Controller::CreateProgressiveAttachment(StopStyle stop_style) { + if (has_progressive_writer()) { + LOG(ERROR) << "One controller can only have one ProgressiveAttachment"; + return NULL; + } + if (_request_protocol != PROTOCOL_HTTP) { + LOG(ERROR) << "Only http supports ProgressiveAttachment now"; + return NULL; + } + if (_current_call.sending_sock == NULL) { + LOG(ERROR) << "sending_sock is NULL"; + return NULL; + } + SocketUniquePtr httpsock; + _current_call.sending_sock->ReAddress(&httpsock); + + if (stop_style == FORCE_STOP) { + httpsock->fail_me_at_server_stop(); + } + _wpa.reset(new ProgressiveAttachment( + httpsock, http_request().before_http_1_1())); + return _wpa; +} + +void Controller::ReadProgressiveAttachmentBy(ProgressiveReader* r) { + if (r == NULL) { + LOG(FATAL) << "Param[r] is NULL"; + return; + } + if (!is_response_read_progressively()) { + return r->OnEndOfMessage( + butil::Status(EINVAL, "Can't read progressive attachment from a " + "controller without calling " + "response_will_be_read_progressively() before")); + } + if (_rpa == NULL) { + return r->OnEndOfMessage( + butil::Status(EINVAL, "ReadableProgressiveAttachment is NULL")); + } + if (has_progressive_reader()) { + return r->OnEndOfMessage( + butil::Status(EPERM, "%s can't be called more than once", + __FUNCTION__)); + } + add_flag(FLAGS_PROGRESSIVE_READER); + return _rpa->ReadProgressiveAttachmentBy(r); +} + +void Controller::set_mongo_session_data(MongoContext* data) { + _mongo_session_data = data; +} + +bool Controller::is_ssl() const { + Socket* s = _current_call.sending_sock.get(); + return s != NULL && s->is_ssl(); +} + +x509_st* Controller::get_peer_certificate() const { + Socket* s = _current_call.sending_sock.get(); + return s ? s->GetPeerCertificate() : NULL; +} + +int Controller::GetSockOption(int level, int optname, void* optval, socklen_t* optlen) { + Socket* s = _current_call.sending_sock.get(); + if (s) { + return getsockopt(s->fd(), level, optname, optval, optlen); + } else { + errno = EBADF; + return -1; + } +} + +void Controller::CallAfterRpcResp(const google::protobuf::Message* req, const google::protobuf::Message* res) { + if (_after_rpc_resp_fn) { + _after_rpc_resp_fn(this, req, res); + _after_rpc_resp_fn = nullptr; + } +} + +#if defined(OS_MACOSX) +typedef sig_t SignalHandler; +#else +typedef sighandler_t SignalHandler; +#endif + +static volatile bool s_signal_quit = false; +static SignalHandler s_prev_sigint_handler = NULL; +static SignalHandler s_prev_sigterm_handler = NULL; +static SignalHandler s_prev_sighup_handler = NULL; + +static void quit_handler(int signo) { + s_signal_quit = true; + if (SIGINT == signo && s_prev_sigint_handler) { + s_prev_sigint_handler(signo); + } + if (SIGTERM == signo && s_prev_sigterm_handler) { + s_prev_sigterm_handler(signo); + } + if (SIGHUP == signo && s_prev_sighup_handler) { + s_prev_sighup_handler(signo); + } +} + +static pthread_once_t register_quit_signal_once = PTHREAD_ONCE_INIT; + +static void RegisterQuitSignalOrDie() { + // Not thread-safe. + SignalHandler prev = signal(SIGINT, quit_handler); + if (prev != SIG_DFL && + prev != SIG_IGN) { // shell may install SIGINT of background jobs with SIG_IGN + RELEASE_ASSERT_VERBOSE(prev != SIG_ERR, + "Fail to register SIGINT, abort"); + s_prev_sigint_handler = prev; + LOG(WARNING) << "SIGINT was installed with " << prev; + } + + if (FLAGS_graceful_quit_on_sigterm) { + prev = signal(SIGTERM, quit_handler); + if (prev != SIG_DFL && + prev != SIG_IGN) { // shell may install SIGTERM of background jobs with SIG_IGN + RELEASE_ASSERT_VERBOSE(prev != SIG_ERR, + "Fail to register SIGTERM, abort"); + s_prev_sigterm_handler = prev; + LOG(WARNING) << "SIGTERM was installed with " << prev; + } + } + + if (FLAGS_graceful_quit_on_sighup) { + prev = signal(SIGHUP, quit_handler); + if (prev != SIG_DFL && + prev != SIG_IGN) { // shell may install SIGHUP of background jobs with SIG_IGN + RELEASE_ASSERT_VERBOSE(prev != SIG_ERR, + "Fail to register SIGHUP, abort"); + s_prev_sighup_handler = prev; + LOG(WARNING) << "SIGHUP was installed with " << prev; + } + } +} + +bool IsAskedToQuit() { + pthread_once(®ister_quit_signal_once, RegisterQuitSignalOrDie); + return s_signal_quit; +} + +void AskToQuit() { + raise(SIGINT); +} + +class DoNothingClosure : public google::protobuf::Closure { + void Run() { } +}; +google::protobuf::Closure* DoNothing() { + return butil::get_leaky_singleton(); +} + +KVMap& Controller::SessionKV() { + if (_session_kv == nullptr) { + _session_kv.reset(new KVMap); + } + return *_session_kv.get(); +} + +#define BRPC_SESSION_END_MSG "Session ends." +#define BRPC_REQ_ID "@rid" +#define BRPC_KV_SEP "=" + +void Controller::FlushSessionKV(std::ostream& os) { + if (_session_kv == nullptr || _session_kv->Count() == 0) { + return; + } + + const std::string* pRID = nullptr; + if (!request_id().empty()) { + pRID = &request_id(); + } + + if (FLAGS_log_as_json) { + if (pRID) { + os << "\"" BRPC_REQ_ID "\":\"" << *pRID << "\","; + } + os << "\"M\":\"" BRPC_SESSION_END_MSG "\""; + for (auto it = _session_kv->Begin(); it != _session_kv->End(); ++it) { + os << ",\"" << it->first << "\":\"" << it->second << '"'; + } + } else { + if (pRID) { + os << BRPC_REQ_ID BRPC_KV_SEP << *pRID << " "; + } + os << BRPC_SESSION_END_MSG; + for (auto it = _session_kv->Begin(); it != _session_kv->End(); ++it) { + os << ' ' << it->first << BRPC_KV_SEP << it->second; + } + } +} + +std::ostream& operator<<(std::ostream& os, const Controller::LogPrefixDummy& p) { + p.DoPrintLogPrefix(os); + return os; +} + +void Controller::DoPrintLogPrefix(std::ostream& os) const { + const std::string* pRID = nullptr; + if (!request_id().empty()) { + pRID = &request_id(); + if (pRID) { + if (FLAGS_log_as_json) { + os << BRPC_REQ_ID "\":\"" << *pRID << "\","; + } else { + os << BRPC_REQ_ID BRPC_KV_SEP << *pRID << " "; + } + } + } + if (FLAGS_log_as_json) { + os << "\"M\":\""; + } +} + + +ControllerPrivateAccessor& ControllerPrivateAccessor::set_span( + const std::shared_ptr& span) { + _cntl->_span = span; + return *this; +} + +ControllerPrivateAccessor& ControllerPrivateAccessor::set_span(Span* span) { + if (span) { + _cntl->_span = span->shared_from_this(); + } else { + _cntl->_span.reset(); + } + return *this; +} + +std::shared_ptr ControllerPrivateAccessor::span() const { + return _cntl->_span.lock(); +} + +} // namespace brpc diff --git a/src/brpc/controller.h b/src/brpc/controller.h new file mode 100644 index 0000000..cb51870 --- /dev/null +++ b/src/brpc/controller.h @@ -0,0 +1,1031 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_CONTROLLER_H +#define BRPC_CONTROLLER_H + +// To brpc developers: This is a header included by user, don't depend +// on internal structures, use opaque pointers instead. + +#include // std::function +#include // Users often need gflags +#include +#include +#include "butil/intrusive_ptr.hpp" // butil::intrusive_ptr +#include "bthread/errno.h" // Redefine errno +#include "butil/endpoint.h" // butil::EndPoint +#include "butil/iobuf.h" // butil::IOBuf +#include "bthread/types.h" // bthread_id_t +#include "brpc/options.pb.h" // CompressType +#include "brpc/errno.pb.h" // error code +#include "brpc/http_header.h" // HttpHeader +#include "brpc/authenticator.h" // AuthContext +#include "brpc/socket_id.h" // SocketId +#include "brpc/stream.h" // StreamId +#include "brpc/stream_creator.h" // StreamCreator +#include "brpc/protocol.h" // Protocol +#include "brpc/traceprintf.h" +#include "brpc/reloadable_flags.h" +#include "brpc/closure_guard.h" // User often needs this +#include "brpc/callback.h" +#include "brpc/progressive_attachment.h" // ProgressiveAttachment +#include "brpc/progressive_reader.h" // ProgressiveReader +#include "brpc/grpc.h" +#include "brpc/kvmap.h" +#include "brpc/rpc_dump.h" + +// EAUTH is defined in MAC +#ifndef EAUTH +#define EAUTH ERPCAUTH +#endif + +extern "C" { +#ifndef USE_MESALINK +struct x509_st; +#else +#include +#define x509_st X509 +#endif +} + +namespace brpc { +class Span; +class Server; +class SharedLoadBalancer; +class ExcludedServers; +class RPCSender; +class StreamSettings; +class MongoContext; +class RetryPolicy; +class BackupRequestPolicy; +class InputMessageBase; +class ThriftStub; +namespace policy { +class OnServerStreamCreated; +void ProcessMongoRequest(InputMessageBase*); +void ProcessThriftRequest(InputMessageBase*); +} +namespace schan { +class Sender; +class SubDone; +} + +// For serializing/parsing from idl services. +struct IdlNames { + const char* request_name; // must be string-constant + const char* response_name; // must be string-constant +}; + +extern const IdlNames idl_single_req_single_res; +extern const IdlNames idl_single_req_multi_res; +extern const IdlNames idl_multi_req_single_res; +extern const IdlNames idl_multi_req_multi_res; + +// The identifier to be associated with a RPC call. +typedef bthread_id_t CallId; + +// Styles for stopping progressive attachment. +enum StopStyle { + FORCE_STOP, + WAIT_FOR_STOP, +}; + +const int32_t UNSET_MAGIC_NUM = -123456789; + +// If a controller wants to reserve the sending socket after the RPC (used by +// mysql transactions for connection affinity), set BIND_SOCK_RESERVE; later RPCs +// reuse it via BIND_SOCK_USE. +enum BindSockAction { + BIND_SOCK_RESERVE, + BIND_SOCK_USE, + BIND_SOCK_NONE, +}; + +typedef butil::FlatMap UserFieldsMap; + +// A Controller mediates a single method call. The primary purpose of +// the controller is to provide a way to manipulate settings per RPC-call +// and to find out about RPC-level errors. +class Controller : public google::protobuf::RpcController/*non-copyable*/ { +friend class Channel; +friend class ParallelChannel; +friend class ParallelChannelDone; +friend class ControllerPrivateAccessor; +friend class ServerPrivateAccessor; +friend class SelectiveChannel; +friend class ThriftStub; +friend class schan::Sender; +friend class schan::SubDone; +friend class policy::OnServerStreamCreated; +friend int StreamCreate(StreamId*, Controller&, const StreamOptions*); +friend int StreamCreate(StreamIds&, int, Controller&, const StreamOptions*); +friend int StreamAccept(StreamId*, Controller&, const StreamOptions*); +friend int StreamAccept(StreamIds&, Controller&, const StreamOptions*); +friend void policy::ProcessMongoRequest(InputMessageBase*); +friend void policy::ProcessThriftRequest(InputMessageBase*); + // << Flags >> + static const uint32_t FLAGS_IGNORE_EOVERCROWDED = 1; + static const uint32_t FLAGS_SECURITY_MODE = (1 << 1); + static const uint32_t FLAGS_ADDED_CONCURRENCY = (1 << 2); + static const uint32_t FLAGS_READ_PROGRESSIVELY = (1 << 3); + static const uint32_t FLAGS_PROGRESSIVE_READER = (1 << 4); + static const uint32_t FLAGS_BACKUP_REQUEST = (1 << 5); + // Let _done delete the correlation_id, used by combo channels to + // make lifetime of the correlation_id more flexible. + static const uint32_t FLAGS_DESTROY_CID_IN_DONE = (1 << 7); + static const uint32_t FLAGS_CLOSE_CONNECTION = (1 << 8); + static const uint32_t FLAGS_LOG_ID = (1 << 9); // log_id is set + static const uint32_t FLAGS_REQUEST_CODE = (1 << 10); + static const uint32_t FLAGS_PB_BYTES_TO_BASE64 = (1 << 11); + static const uint32_t FLAGS_ALLOW_DONE_TO_RUN_IN_PLACE = (1 << 12); + static const uint32_t FLAGS_USED_BY_RPC = (1 << 13); + // Reserve/reuse the sending socket after the RPC (mysql transactions). + // The two bits encode BindSockAction: neither=BIND_SOCK_NONE, + // RESERVE bit=BIND_SOCK_RESERVE, USE bit=BIND_SOCK_USE. + static const uint32_t FLAGS_BIND_SOCK_RESERVE = (1 << 14); + static const uint32_t FLAGS_BIND_SOCK_USE = (1 << 15); + static const uint32_t FLAGS_PB_JSONIFY_EMPTY_ARRAY = (1 << 16); + static const uint32_t FLAGS_ENABLED_CIRCUIT_BREAKER = (1 << 17); + static const uint32_t FLAGS_ALWAYS_PRINT_PRIMITIVE_FIELDS = (1 << 18); + static const uint32_t FLAGS_HEALTH_CHECK_CALL = (1 << 19); + static const uint32_t FLAGS_PB_SINGLE_REPEATED_TO_ARRAY = (1 << 20); + static const uint32_t FLAGS_MANAGE_HTTP_BODY_ON_ERROR = (1 << 21); + static const uint32_t FLAGS_WRITE_TO_SOCKET_IN_BACKGROUND = (1 << 22); + static const uint32_t FLAGS_ENDING_RPC = (1 << 23); + +public: + struct Inheritable { + Inheritable() : log_id(0) {} + void Reset() { + log_id = 0; + request_id.clear(); + } + + uint64_t log_id; + std::string request_id; + }; + +public: + Controller(); + Controller(const Inheritable& parent_ctx); + ~Controller(); + + // ------------------------------------------------------------------ + // Client-side methods + // These calls shall be made from the client side only. Their results + // are undefined on the server side (may crash). + // ------------------------------------------------------------------ + + // Set/get timeout in milliseconds for the RPC call. Use + // ChannelOptions.timeout_ms on unset. + void set_timeout_ms(int64_t timeout_ms); + int64_t timeout_ms() const { return _timeout_ms; } + + // Set/get the delay to send backup request in milliseconds. Use + // ChannelOptions.backup_request_ms on unset. + void set_backup_request_ms(int64_t timeout_ms); + void set_backup_request_policy(BackupRequestPolicy* policy) { + _backup_request_policy = policy; + } + int64_t backup_request_ms() const; + + // Set/get maximum times of retrying. Use ChannelOptions.max_retry on unset. + // <=0 means no retry. + // Conditions of retrying: + // * The connection is broken. No retry if the connection is still on. + // Use backup_request if you want to issue another request after some + // time. + // * Not timed out. + // * retried_count() < max_retry(). + // * Retry may work for the error. E.g. No retry when the request is + // incorrect (EREQUEST), retrying is pointless. + void set_max_retry(int max_retry); + int max_retry() const { return _max_retry; } + + // Get number of retries. + int retried_count() const { return _current_call.nretry; } + + // True if a backup request was sent during the RPC. + bool has_backup_request() const { return has_flag(FLAGS_BACKUP_REQUEST); } + + // This function has different meanings in client and server side. + // In client side it gets latency of the RPC call. While in server side, + // it gets queue time before server processes the RPC call. + int64_t latency_us() const { + if (_end_time_us == UNSET_MAGIC_NUM) { + return butil::cpuwide_time_us() - _begin_time_us; + } + return _end_time_us - _begin_time_us; + } + + // Response of the RPC call (passed to CallMethod) + google::protobuf::Message* response() const { return _response; } + + // An identifier to send to server along with request. This is widely used + // throughout baidu's servers to tag a searching session (a series of + // queries following the topology of servers) with a same log_id. + void set_log_id(uint64_t log_id); + + void set_request_id(std::string request_id) { _inheritable.request_id = request_id; } + + // Set type of service: http://en.wikipedia.org/wiki/Type_of_service + // Current implementation has limits: If the connection is already + // established, this setting has no effect until the connection is broken + // and re-connected. And because of connection sharing, setting different + // tos to a single connection is undefined. + void set_type_of_service(short tos) { _tos = tos; } + + // Set type of connections for sending RPC. + // Use ChannelOptions.connection_type on unset. + void set_connection_type(ConnectionType type) { _connection_type = type; } + + // Set compression method for request. + void set_request_compress_type(CompressType t) { _request_compress_type = t; } + + // Set checksum type for request. + void set_request_checksum_type(ChecksumType t) { _request_checksum_type = t; } + + // Required by some load balancers. + void set_request_code(uint64_t request_code) { + add_flag(FLAGS_REQUEST_CODE); + _request_code = request_code; + } + bool has_request_code() const { return has_flag(FLAGS_REQUEST_CODE); } + uint64_t request_code() const { return _request_code; } + + // Mutable header of http request. + HttpHeader& http_request() { + if (_http_request == NULL) { + _http_request = new HttpHeader; + } + return *_http_request; + } + bool has_http_request() const { return _http_request; } + HttpHeader* release_http_request() { + HttpHeader* const tmp = _http_request; + _http_request = NULL; + return tmp; + } + + UserFieldsMap* request_user_fields() { + if (!_request_user_fields) { + _request_user_fields = new UserFieldsMap; + } + return _request_user_fields; + } + + bool has_request_user_fields() const { return _request_user_fields; } + + UserFieldsMap* response_user_fields() { + if (!_response_user_fields) { + _response_user_fields = new UserFieldsMap; + } + return _response_user_fields; + } + + bool has_response_user_fields() const { return _response_user_fields; } + + // User attached data or body of http request, which is wired to network + // directly instead of being serialized into protobuf messages. + butil::IOBuf& request_attachment() { return _request_attachment; } + + ConnectionType connection_type() const { return _connection_type; } + // Get the called method. May-be NULL for non-pb services. + const google::protobuf::MethodDescriptor* method() const { return _method; } + + // Get the controllers for accessing sub channels in combo channels. + // Ordinary channel: + // sub_count() is 0 and sub() is always NULL. + // ParallelChannel/PartitionChannel: + // sub_count() is #sub-channels and sub(i) is the controller for + // accessing i-th sub channel inside ParallelChannel, if i is outside + // [0, sub_count() - 1], sub(i) is NULL. + // NOTE: You must test sub() against NULL, ALWAYS. Even if i is inside + // range, sub(i) can still be NULL: + // * the rpc call may fail and terminate before accessing the sub channel + // * the sub channel was skipped + // SelectiveChannel/DynamicPartitionChannel: + // sub_count() is always 1 and sub(0) is the controller of successful + // or last call to sub channels. + int sub_count() const; + const Controller* sub(int index) const; + + // Get/own SampledRequest for sending dumped requests. + // Deleted along with controller. + void reset_sampled_request(SampledRequest* req); + const SampledRequest* sampled_request() const { return _sampled_request; } + SampledRequest* release_sampled_request(); + + + // Attach a StreamCreator to this RPC. Notice that the ownership of sc has + // been transferred to cntl, and sc->DestroyStreamCreator() would be called + // only once to destroy sc. + void set_stream_creator(StreamCreator* sc); + + // Make the RPC end when the HTTP response has complete headers and let + // user read the remaining body by using ReadProgressiveAttachmentBy(). + void response_will_be_read_progressively() { add_flag(FLAGS_READ_PROGRESSIVELY); } + // Make the RPC end when the HTTP request has complete headers and let + // user read the remaining body by using ReadProgressiveAttachmentBy(). + void request_will_be_read_progressively() { add_flag(FLAGS_READ_PROGRESSIVELY); } + // True if response_will_be_read_progressively() was called. + bool is_response_read_progressively() const { return has_flag(FLAGS_READ_PROGRESSIVELY); } + + // Read the remaining body after RPC: + // - This function can only be called once. + // - If user called response_will_be_read_progressively() but + // ReadProgressiveAttachmentBy(), controller will set a reader ignoring + // all bytes read before self's Reset() or dtor. + // - If user did not call response_will_be_read_progressively() and calls + // ReadProgressiveAttachmentBy(), the reader is Destroyed() immediately. + // - Any error occurred will destroy the reader by calling r->Destroy(). + // - r->Destroy() is guaranteed to be called once and only once. + void ReadProgressiveAttachmentBy(ProgressiveReader* r); + + // True if ReadProgressiveAttachmentBy() was ever called successfully. + bool has_progressive_reader() const { return has_flag(FLAGS_PROGRESSIVE_READER); } + + // RPC may fail with EOVERCROWDED if the socket to write is too full + // (limited by -socket_max_unwritten_bytes). In some scenarios, user + // may wish to suppress the error completely. To do this, call this + // method before doing the RPC. + void ignore_eovercrowded() { add_flag(FLAGS_IGNORE_EOVERCROWDED); } + + // Set if the field of bytes in protobuf message should be encoded + // to base64 string in HTTP request. + void set_pb_bytes_to_base64(bool f) { set_flag(FLAGS_PB_BYTES_TO_BASE64, f); } + bool has_pb_bytes_to_base64() const { return has_flag(FLAGS_PB_BYTES_TO_BASE64); } + + // Set if the single repeated field in protobuf message should be encoded + // as array when serialize/deserialize to/from json. + void set_pb_single_repeated_to_array(bool f) { set_flag(FLAGS_PB_SINGLE_REPEATED_TO_ARRAY, f); } + bool has_pb_single_repeated_to_array() const { return has_flag(FLAGS_PB_SINGLE_REPEATED_TO_ARRAY); } + + // Set if convert the repeated field that has no entry to a empty array + // of json in HTTP response. + void set_pb_jsonify_empty_array(bool f) { set_flag(FLAGS_PB_JSONIFY_EMPTY_ARRAY, f); } + bool has_pb_jsonify_empty_array() const { return has_flag(FLAGS_PB_JSONIFY_EMPTY_ARRAY); } + + // Whether to always print primitive fields. By default proto3 primitive + // fields with default values will be omitted in JSON output. For example, an + // int32 field set to 0 will be omitted. Set this flag to true will override + // the default behavior and print primitive fields regardless of their values. + void set_always_print_primitive_fields(bool f) { set_flag(FLAGS_ALWAYS_PRINT_PRIMITIVE_FIELDS, f); } + bool has_always_print_primitive_fields() const { return has_flag(FLAGS_ALWAYS_PRINT_PRIMITIVE_FIELDS); } + + + // Tell RPC that done of the RPC can be run in the same thread where + // the RPC is issued, otherwise done is always run in a different thread. + // In current implementation, this option only affects RPC that fails + // before sending the request. + // This option is *rarely* needed by ordinary users. Don't set this option + // if you don't know the consequences. Read implementions in channel.cpp + // and controller.cpp to know more. + void allow_done_to_run_in_place() + { add_flag(FLAGS_ALLOW_DONE_TO_RUN_IN_PLACE); } + // True iff above method was called. + bool is_done_allowed_to_run_in_place() const + { return has_flag(FLAGS_ALLOW_DONE_TO_RUN_IN_PLACE); } + + // Create a background KEEPWRITE bthread to write to socket when issuing + // RPCs, instead of trying to write to socket once in calling thread (see + // `Socket::StartWrite` in socket.cpp). + // The socket write could take some time (several microseconds maybe), if + // you cares about it and don't want the calling thread to be blocked, you + // can set this flag. + // Should provides better batch effect in situations like when you are + // continually issuing lots of async RPC calls in only one thread. + void set_write_to_socket_in_background(bool f) { set_flag(FLAGS_WRITE_TO_SOCKET_IN_BACKGROUND, f); } + bool write_to_socket_in_background() const { return has_flag(FLAGS_WRITE_TO_SOCKET_IN_BACKGROUND); } + + // ------------------------------------------------------------------------ + // Server-side methods. + // These calls shall be made from the server side only. Their results are + // undefined on the client side (may crash). + // ------------------------------------------------------------------------ + + // Returns true if the client canceled the RPC or the connection has broken, + // so the server may as well give up on replying to it. The server should still + // call the final "done" callback. + // Note: Reaching deadline of the RPC would not affect this function, which means + // even if deadline has been reached, this function may still return false. + bool IsCanceled() const override; + + // Asks that the given callback be called when the RPC is canceled or the + // connection has broken. The callback will always be called exactly once. + // If the RPC completes without being canceled/broken connection, the callback + // will be called after completion. If the RPC has already been canceled/broken + // when NotifyOnCancel() is called, the callback will be called immediately. + // + // NotifyOnCancel() must be called no more than once per request. + void NotifyOnCancel(google::protobuf::Closure* callback) override; + + // Returns the authenticated result. NULL if there is no authentication + const AuthContext* auth_context() const { return _auth_context; } + + // Whether the underlying channel is using SSL + bool is_ssl() const; + + // Get the peer certificate, which can be printed by ostream + x509_st* get_peer_certificate() const; + + // Mutable header of http response. + HttpHeader& http_response() { + if (_http_response == NULL) { + _http_response = new HttpHeader; + } + return *_http_response; + } + bool has_http_response() const { return _http_response; } + HttpHeader* release_http_response() { + HttpHeader* const tmp = _http_response; + _http_response = NULL; + return tmp; + } + + // User attached data or body of http response, which is wired to network + // directly instead of being serialized into protobuf messages. + butil::IOBuf& response_attachment() { return _response_attachment; } + + // Response Body of a failed HTTP call is set to be ErrorText() by default, + // even if response_attachment() is non-empty. + // If this flag is true, the http body of a failed HTTP call will not be + // replaced by ErrorText() and should be managed by user self. + void manage_http_body_on_error(bool manage_or_not) + { set_flag(FLAGS_MANAGE_HTTP_BODY_ON_ERROR, manage_or_not); } + + bool does_manage_http_body_on_error() const + { return has_flag(FLAGS_MANAGE_HTTP_BODY_ON_ERROR); } + + // Create a ProgressiveAttachment to write (often after RPC). + // If `stop_style' is FORCE_STOP, the underlying socket will be failed + // immediately when the socket becomes idle or server is stopped. + // Default value of `stop_style' is WAIT_FOR_STOP. + butil::intrusive_ptr + CreateProgressiveAttachment(StopStyle stop_style = WAIT_FOR_STOP); + + bool has_progressive_writer() const { return _wpa != NULL; } + + // Set compression method for response. + void set_response_compress_type(CompressType t) { _response_compress_type = t; } + + // Set checksum type for response. + void set_response_checksum_type(ChecksumType t) { _response_checksum_type = t; } + + // Non-zero when this RPC call is traced (by rpcz or rig). + // NOTE: Only valid at server-side, always zero at client-side. + uint64_t trace_id() const; + uint64_t span_id() const; + + // Tell RPC to close the connection instead of sending back response. + // If this controller was not SetFailed() before, ErrorCode() will be + // set to ECLOSE. + // NOTE: the underlying connection is not closed immediately. + void CloseConnection(const char* reason_fmt, ...); + + // True if CloseConnection() was called. + bool IsCloseConnection() const { return has_flag(FLAGS_CLOSE_CONNECTION); } + + // ServerOptions.security_mode is turned on, and the RPC is from + // connections accepted from port (rather than internal_port) + bool is_security_mode() const { return has_flag(FLAGS_SECURITY_MODE); } + + // The server running this RPC session. + // Always NULL at client-side. + const Server* server() const { return _server; } + + // Get the data attached to current RPC session. The data is created by + // ServerOptions.session_local_data_factory and reused between different + // RPC. If factory is NULL, this method returns NULL. + void* session_local_data(); + + // Get the data attached to a mongo session(practically a socket). + MongoContext* mongo_session_data() { return _mongo_session_data.get(); } + + // ------------------------------------------------------------------- + // Both-side methods. + // Following methods can be called from both client and server. But they + // may have different or opposite semantics. + // ------------------------------------------------------------------- + + // Client-side: successful or last server called. Accessible from + // PackXXXRequest() in protocols. + // Server-side: returns the client sending the request + butil::EndPoint remote_side() const { return _remote_side; } + + // Client-side: the local address for talking with server, undefined until + // this RPC succeeds (because the connection may not be established + // before RPC). + // Server-side: the address that clients access. + butil::EndPoint local_side() const { return _local_side; } + + // Protocol of the request sent by client or received by server. + ProtocolType request_protocol() const { return _request_protocol; } + + // Resets the Controller to its initial state so that it may be reused in + // a new call. Must NOT be called while an RPC is in progress. + void Reset() override { + ResetNonPods(); + ResetPods(); + } + + // Causes Failed() to return true on the client side. "reason" will be + // incorporated into the message returned by ErrorText(). + // NOTE: Change http_response().status_code() according to `error_code' + // as well if the protocol is HTTP. If you want to overwrite the + // status_code, call http_response().set_status_code() after SetFailed() + // (rather than before SetFailed) + void SetFailed(const std::string& reason) override; + void SetFailed(int error_code, const char* reason_fmt, ...) + __attribute__ ((__format__ (__printf__, 3, 4))); + + // After a call has finished, returns true if the RPC call failed. + // The response to Channel is undefined when Failed() is true. + // Calling Failed() before a call has finished is undefined. + bool Failed() const override; + + // If Failed() is true, return description of the errors. + // NOTE: ErrorText() != berror(ErrorCode()). + std::string ErrorText() const override; + + // Last error code. Equals 0 iff Failed() is false. + // If there's retry, latter code overwrites former one. + int ErrorCode() const { return _error_code; } + + // Getters: + const Inheritable& inheritable() { return _inheritable; } + bool has_log_id() const { return has_flag(FLAGS_LOG_ID); } + uint64_t log_id() const { return _inheritable.log_id; } + const std::string& request_id() const { return _inheritable.request_id; } + CompressType request_compress_type() const { return _request_compress_type; } + CompressType response_compress_type() const { return _response_compress_type; } + ChecksumType request_checksum_type() const { return _request_checksum_type; } + ChecksumType response_checksum_type() const { return _response_checksum_type; } + const HttpHeader& http_request() const + { return _http_request != NULL ? *_http_request : DefaultHttpHeader(); } + + const HttpHeader& http_response() const + { return _http_response != NULL ? *_http_response : DefaultHttpHeader(); } + + const butil::IOBuf& request_attachment() const { return _request_attachment; } + const butil::IOBuf& response_attachment() const { return _response_attachment; } + + // Get the object to write key/value which will be flushed into + // LOG(INFO) when this controller is deleted. + KVMap& SessionKV(); + + // Flush SessionKV() into `os' + void FlushSessionKV(std::ostream& os); + + // Contextual prefixes for LOGD/LOGI/LOGW/LOGE/LOGF macros + class LogPrefixDummy { + public: + LogPrefixDummy(const Controller* cntl) : _cntl(cntl) {} + void DoPrintLogPrefix(std::ostream& os) const { _cntl->DoPrintLogPrefix(os); } + private: + const Controller* _cntl; + }; + friend class LogPrefixDummy; + LogPrefixDummy LogPrefix() const { return LogPrefixDummy(this); } + + // Return true if the remote side creates a stream. + bool has_remote_stream() { return _remote_stream_settings != NULL; } + + // The id to cancel RPC call or join response. + CallId call_id(); + + // Get/set idl names. Notice that the names must be string-constant. + // int32_t Echo(EchoRequest req, EchoResponse res); + // ^ ^ + // request_name response_name + void set_idl_names(const IdlNames& names) { _idl_names = names; } + IdlNames idl_names() const { return _idl_names; } + + // Get/set idl result. The type is limited to be integral. + // int32_t Echo(EchoRequest req, EchoResponse res); + // ^ + // result + void set_idl_result(int64_t result) { _idl_result = result; } + int64_t idl_result() const { return _idl_result; } + + const std::string& thrift_method_name() { return _thrift_method_name; } + + // Get sock option. .e.g get vip info through ttm kernel module hook, + int GetSockOption(int level, int optname, void* optval, socklen_t* optlen); + + // Get deadline of this RPC (since the Epoch in microseconds). + // -1 means no deadline. + int64_t deadline_us() const { return _deadline_us; } + + using AfterRpcRespFnType = std::function; + + void set_after_rpc_resp_fn(AfterRpcRespFnType&& fn) { _after_rpc_resp_fn = fn; } + + void CallAfterRpcResp(const google::protobuf::Message* req, const google::protobuf::Message* res); + + void set_request_content_type(ContentType type) { + _request_content_type = type; + } + ContentType request_content_type() const { + return _request_content_type; + } + + void set_response_content_type(ContentType type) { + _response_content_type = type; + } + ContentType response_content_type() const { + return _response_content_type; + } + + // If brpc acts as a server, this interface exposes the time when the RPC was received from the + // socket. This function can be used in scenarios where the user code needs to understand the RPC + // reception time, such as for precise control of timeouts. Users will require timing to start + // from the receipt of the RPC. When the user processing function starts to handle the RPC, if + // it is found that the RPC has timed out, it will be directly discarded + void set_rpc_received_us(int64_t received_us) { _rpc_received_us = received_us; } + + // Get the received time of RPC (in microseconds), if the returned value is 0, it means that + // the received time of RPC is not recorded in the controller. + int64_t get_rpc_received_us() const { return _rpc_received_us; } + +private: + struct CompletionInfo { + CallId id; // call_id of the corresponding request + bool responded; // triggered by a response rather than other errors + }; + + // Call this method when receiving response/failure. If RPC failed, + // it will try to retry this RPC. Otherwise, it calls user `done' + // if it exists and destroys the correlation_id. Note that + // the correlation_id MUST have been locked before this call. + // Parameter `new_bthread': + // false - Run this function in the current bthread/pthread. Note that + // it could last for a long time or even block the caller (as + // it contains user's `done') + // true - Creates a new bthread to run this function and returns to + // the caller immediately + // Parameter `id': + // It will be used to checked against `_correlation_id' and + // `_current_call.nretry'. If not matched, nothing will happen, + // which means this event has been processed before + // Parameter `saved_error': + // If the above check failed, `_error_code' will be reverted to this + void OnVersionedRPCReturned(const CompletionInfo&, + bool new_bthread, int saved_error); + + static void* RunEndRPC(void* arg); + void EndRPC(const CompletionInfo&); + + static int HandleSocketFailed(bthread_id_t, void* data, int error_code, + const std::string& error_text); + void HandleSendFailed(); + + static int RunOnCancel(bthread_id_t, void* data, int error_code); + + void set_auth_context(const AuthContext* ctx); + + // MongoContext is created by ParseMongoRequest when the first msg comes + // over a socket, then stored in MongoContextMessage of the socket. cntl + // gets a shared reference of the data in PocessMongoRequest. When socket + // is recycled, the container, AKA MongoContextMessage is destroyed, which + // has no infuluence on the cntl(s) who already gets the shared reference + // of the MongoContext. The MongoContext will not be recycled until both + // the container(MongoContextMessage) and all related cntl(s) are recycled. + void set_mongo_session_data(MongoContext* data); + + // Reset POD/non-POD fields. + void ResetPods(); + void ResetNonPods(); + + void StartCancel() override; + + // Using fixed start_realtime_us (microseconds since the Epoch) gives + // more accurate deadline. + void IssueRPC(int64_t start_realtime_us); + + struct ClientSettings { + int32_t timeout_ms; + int32_t backup_request_ms; + BackupRequestPolicy* backup_request_policy; + int max_retry; + int32_t tos; + ConnectionType connection_type; + CompressType request_compress_type; + ChecksumType request_checksum_type; + uint64_t log_id; + bool has_request_code; + int64_t request_code; + }; + + void SaveClientSettings(ClientSettings*) const; + void ApplyClientSettings(const ClientSettings&); + + bool FailedInline() const { return _error_code; } + + CallId get_id(int nretry) const { + CallId id = { _correlation_id.value + nretry + 1 }; + return id; + } + + // Tell RPC that this particular call is used to do health check. + bool is_health_check_call() const { return has_flag(FLAGS_HEALTH_CHECK_CALL); } + +public: + CallId current_id() const { + CallId id = { _correlation_id.value + _current_call.nretry + 1 }; + return id; + } +private: + + // Append server information to `_error_text' + void AppendServerIdentiy(); + + // Contexts for tracking and ending a sent request. + // One RPC to a channel may send several requests due to retrying. + struct Call { + Call() { Reset(); } + Call(Call*); //move semantics + ~Call(); + void Reset(); + void OnComplete(Controller* c, int error_code, bool responded, bool end_of_rpc); + + int nretry; // sent in nretry-th retry. + bool need_feedback; // The LB needs feedback. + bool enable_circuit_breaker; // The channel enabled circuit_breaker + bool touched_by_stream_creator; + SocketId peer_id; // main server id + int64_t begin_time_us; // sent real time. + // The actual `Socket' for sending RPC. It's socket id will be + // exactly the same as `peer_id' if `_connection_type' is + // CONNECTION_TYPE_SINGLE. Otherwise, it may be a temporary + // socket fetched from socket pool + SocketUniquePtr sending_sock; + // How sending_sock is treated when this call completes, must be set + // in every constructor and Reset(). See BindSockAction. + BindSockAction bind_sock_action; + StreamUserData* stream_user_data; + }; + + void HandleStreamConnection(Socket *host_socket); + + bool SingleServer() const { return _single_server_id != INVALID_SOCKET_ID; } + + void SubmitSpan(); + + void OnRPCBegin(int64_t begin_time_us) { + _begin_time_us = begin_time_us; + // make latency_us() return 0 when RPC is not over + _end_time_us = begin_time_us; + } + + void OnRPCEnd(int64_t end_time_us); + + static void RunDoneInBackupThread(void*); + void DoneInBackupThread(); + + // Utilities for manipulating _flags + inline void add_flag(uint32_t f) { _flags |= f; } + inline void clear_flag(uint32_t f) { _flags &= ~f; } + inline void set_flag(uint32_t f, bool t) + { return t ? add_flag(f) : clear_flag(f); } + inline bool has_flag(uint32_t f) const { return _flags & f; } + + // BindSockAction stored in the FLAGS_BIND_SOCK_* bits of _flags instead + // of a dedicated member. + void set_bind_sock_action(BindSockAction action) { + clear_flag(FLAGS_BIND_SOCK_RESERVE | FLAGS_BIND_SOCK_USE); + if (action == BIND_SOCK_RESERVE) { + add_flag(FLAGS_BIND_SOCK_RESERVE); + } else if (action == BIND_SOCK_USE) { + add_flag(FLAGS_BIND_SOCK_USE); + } + } + BindSockAction bind_sock_action() const { + if (has_flag(FLAGS_BIND_SOCK_RESERVE)) { + return BIND_SOCK_RESERVE; + } + if (has_flag(FLAGS_BIND_SOCK_USE)) { + return BIND_SOCK_USE; + } + return BIND_SOCK_NONE; + } + + void set_used_by_rpc() { add_flag(FLAGS_USED_BY_RPC); } + bool is_used_by_rpc() const { return has_flag(FLAGS_USED_BY_RPC); } + + bool has_enabled_circuit_breaker() const { + return has_flag(FLAGS_ENABLED_CIRCUIT_BREAKER); + } + + bool is_ending_rpc() const { return has_flag(FLAGS_ENDING_RPC); } + + std::string& protocol_param() { return _thrift_method_name; } + const std::string& protocol_param() const { return _thrift_method_name; } + + void DoPrintLogPrefix(std::ostream& os) const; + +private: + // NOTE: align and group fields to make Controller as compact as possible. + + std::weak_ptr _span; + uint32_t _flags; // all boolean fields inside Controller + int32_t _error_code; + std::string _error_text; + butil::EndPoint _remote_side; + butil::EndPoint _local_side; + + void* _session_local_data; + const Server* _server; + bthread_id_t _oncancel_id; + const AuthContext* _auth_context; // Authentication result + butil::intrusive_ptr _mongo_session_data; + SampledRequest* _sampled_request; + + ProtocolType _request_protocol; + // Some of them are copied from `Channel' which might be destroyed + // after CallMethod. + int _max_retry; + const RetryPolicy* _retry_policy; + // Synchronization object for one RPC call. It remains unchanged even + // when retry happens. Synchronous RPC will wait on this id. + CallId _correlation_id; + + ConnectionType _connection_type; + + // Used by ParallelChannel + int _fail_limit; + + uint32_t _pipelined_count; + + // [Timeout related] + int32_t _timeout_ms; + int32_t _connect_timeout_ms; + int32_t _backup_request_ms; + // Priority: `_backup_request_policy' > `_backup_request_ms'. + BackupRequestPolicy* _backup_request_policy; + // If this rpc call has retry/backup request,this var save the real timeout for current call + int64_t _real_timeout_ms; + // Deadline of this RPC (since the Epoch in microseconds). + int64_t _deadline_us; + // Timer registered to trigger RPC timeout event + bthread_timer_t _timeout_id; + + // Begin/End time of a single RPC call (since Epoch in microseconds) + int64_t _begin_time_us; + int64_t _end_time_us; + short _tos; // Type of service. + // The index of parse function which `InputMessenger' will use + int _preferred_index; + CompressType _request_compress_type; + CompressType _response_compress_type; + ChecksumType _request_checksum_type; + ChecksumType _response_checksum_type; + std::string _checksum_value; + Inheritable _inheritable; + int _pchan_sub_count; + google::protobuf::Message* _response; + google::protobuf::Closure* _done; + RPCSender* _sender; + uint64_t _request_code; + SocketId _single_server_id; + butil::intrusive_ptr _lb; + + // for passing parameters to created bthread, don't modify it otherwhere. + CompletionInfo _tmp_completion_info; + + Call _current_call; + Call* _unfinished_call; + ExcludedServers* _accessed; + + StreamCreator* _stream_creator; + + // Fields will be used when making requests + Protocol::PackRequest _pack_request; + const google::protobuf::MethodDescriptor* _method; + const Authenticator* _auth; + butil::IOBuf _request_buf; + IdlNames _idl_names; + int64_t _idl_result; + + HttpHeader* _http_request; + HttpHeader* _http_response; + + // User fields of baidu_std protocol. + UserFieldsMap* _request_user_fields; + UserFieldsMap* _response_user_fields; + + std::unique_ptr _session_kv; + + // Fields with large size but low access frequency + butil::IOBuf _request_attachment; + butil::IOBuf _response_attachment; + + // Only SerializedRequest supports `_request_content_type'. + ContentType _request_content_type; + // Only SerializedResponse supports `_response_content_type'. + ContentType _response_content_type; + + // Writable progressive attachment + butil::intrusive_ptr _wpa; + // Readable progressive attachment + butil::intrusive_ptr _rpa; + + // TODO: Replace following fields with StreamCreator + // Defined at client side + StreamIds _request_streams; + // Defined at server side + StreamIds _response_streams; + // Defined at both sides + StreamSettings *_remote_stream_settings; + + // Whether/how to reserve the sending socket after the RPC (mysql + // transactions) is stored in the FLAGS_BIND_SOCK_* bits of _flags; see + // set_bind_sock_action()/bind_sock_action(). The socket reserved by a + // previous RPC and reused when the action is BIND_SOCK_USE: + SocketUniquePtr _bind_sock; + // Opaque per-RPC slot a protocol codec may use to carry typed state from + // serialize_request to pack_request/parse (e.g. the mysql prepared-statement + // stub). Not owned by Controller. + void* _session_data; + + // Thrift method name, only used when thrift protocol enabled + std::string _thrift_method_name; + + uint32_t _auth_flags; + + AfterRpcRespFnType _after_rpc_resp_fn; + + // The point in time when the rpc is read from the socket + int64_t _rpc_received_us; +}; + +// Advises the RPC system that the caller desires that the RPC call be +// canceled. If the call is canceled, the "done" callback will still be +// called and the Controller will indicate that the call failed at that +// time. +void StartCancel(CallId id); + +// Suspend until the RPC finishes. +void Join(CallId id); + +// Get a global closure for doing nothing. Used in semi-synchronous +// RPC calls. Example: +// stub1.method1(&cntl1, &request1, &response1, brpc::DoNothing()); +// stub2.method2(&cntl2, &request2, &response2, brpc::DoNothing()); +// ... +// brpc::Join(cntl1.call_id()); +// brpc::Join(cntl2.call_id()); +google::protobuf::Closure* DoNothing(); + +// Convert non-web symbols to web equivalence. +void WebEscape(const std::string& source, std::string* output); +std::string WebEscape(const std::string& source); + +// True if Ctrl-C is ever pressed. +bool IsAskedToQuit(); + +// Send Ctrl-C to current process. +void AskToQuit(); + +std::ostream& operator<<(std::ostream& os, const Controller::LogPrefixDummy& p); + +} // namespace brpc + +// Print contextual logs prefixed with "@rid=REQUEST_ID" which marks a session +// and eases debugging. The REQUEST_ID is carried in http/rpc request or +// inherited from another controller. +// As a server: +// Call CLOG*(cntl) << ... to log instead of LOG(*) << .. +// As a client: +// Inside a service: +// Use Controller(service_cntl->inheritable()) to create controllers which +// inherit session info from the service's requests +// Standalone brpc client: +// Set cntl->set_request_id(REQUEST_ID); +// Standalone http client: +// Set header 'X-REQUEST-ID' +#define CLOGD(cntl) LOG(DEBUG) << (cntl)->LogPrefix() +#define CLOGI(cntl) LOG(INFO) << (cntl)->LogPrefix() +#define CLOGW(cntl) LOG(WARNING) << (cntl)->LogPrefix() +#define CLOGE(cntl) LOG(ERROR) << (cntl)->LogPrefix() +#define CLOGF(cntl) LOG(FATAL) << (cntl)->LogPrefix() +#define CVLOG(v, cntl) VLOG(v) << (cntl)->LogPrefix() + +#endif // BRPC_CONTROLLER_H diff --git a/src/brpc/coroutine.h b/src/brpc/coroutine.h new file mode 100644 index 0000000..513f402 --- /dev/null +++ b/src/brpc/coroutine.h @@ -0,0 +1,148 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_COROUTINE_H +#define BRPC_COROUTINE_H + +#if __cplusplus >= 202002L + +#define BRPC_ENABLE_COROUTINE 1 + +#include +#include +#include +#include "brpc/callback.h" + +namespace brpc { +namespace experimental { + +namespace detail { +class AwaitablePromiseBase; +template +class AwaitablePromise; +} + +class AwaitableDone; +class Coroutine; + +// WARN:The bRPC coroutine feature is experimental, DO NOT use in production environment! + +// Awaitable is used as coroutine return type, for example: +// Awaitable func1() { +// co_return 42; +// } +// Awaitable func2() { +// int ret = co_await func1(); +// co_return std::to_string(ret); +// } +template +class Awaitable { +public: + using promise_type = detail::AwaitablePromise; + + ~Awaitable() {} + + // NOTE: compiler will generate calls to these functions automatically, + // DO NOT call them manually + bool await_ready(); + template + void await_suspend(std::coroutine_handle > awaiting); + T await_resume(); + +private: +friend class detail::AwaitablePromise; +friend class AwaitableDone; +friend class Coroutine; + + Awaitable() = delete; + Awaitable(promise_type* p) : _promise(p) {} + + promise_type* promise() { + return _promise; + } + + promise_type* _promise; +}; + +// Utility for a coroutine to wait for RPC call. Usage: +// AwaitableDone done; +// stub.CallMethod(&cntl, &req, &resp, &done); +// co_await done.awaitable(); +// +class AwaitableDone : public google::protobuf::Closure { +public: + AwaitableDone(); + + void Run() override; + + Awaitable& awaitable() { + return _awaitable; + } +private: + Awaitable _awaitable; +}; + +// Class for management of coroutine +// 1. To create a new coroutine and wait it finish: +// Awaitable func(double val); +// +// int main() { +// Coroutine coro(func(1.0)); +// coro.join(); +// } +// 2. To wait a coroutine in another coroutine: +// Awaitable another_func() { +// Coroutine coro(func(1.0)); +// co_await coro.awaitable(); +// } +// 3. To create a detached coroutine without waiting: +// Coroutine coro(func(1.0), true); +// 4. To sleep in a coroutine: +// co_await Coroutine::usleep(100); +// +// NOTE: Inside coroutine function, DO NOT call pthread-blocking or +// bthread-blocking functions (eg. bthread_join(), bthread_usleep(), syncronized RPC), +// otherwise may cause dead lock or long latency. +class Coroutine { +public: + template + Coroutine(Awaitable&& aw, bool detach = false); + + ~Coroutine(); + + template + T join(); + + template + Awaitable awaitable(); + + static Awaitable usleep(int sleep_us); + +private: + detail::AwaitablePromiseBase* _promise{nullptr}; + bool _waited{false}; + std::atomic* _butex{nullptr}; +}; + +} // namespace experimental +} // namespace brpc + +#include "brpc/coroutine_inl.h" + +#endif // __cplusplus >= 202002L + +#endif // BRPC_COROUTINE_H \ No newline at end of file diff --git a/src/brpc/coroutine_inl.h b/src/brpc/coroutine_inl.h new file mode 100644 index 0000000..1ff4005 --- /dev/null +++ b/src/brpc/coroutine_inl.h @@ -0,0 +1,312 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_COROUTINE_INL_H +#define BRPC_COROUTINE_INL_H + +#include "bthread/unstable.h" // bthread_timer_add +#include "bthread/butex.h" // butex_wake/butex_wait + +namespace brpc { +namespace experimental { + +namespace detail { + +class AwaitablePromiseBase { +public: + AwaitablePromiseBase() { + } + + virtual ~AwaitablePromiseBase() { + delete _suspended_or_done; + } + + virtual void resume() = 0; + virtual void destroy() = 0; + + bool needs_suspend() { + return _suspended_or_done != nullptr; + } + + void set_needs_suspend() { + _suspended_or_done = new std::atomic(); + _suspended_or_done->store(false); + } + + // For a Coroutine's leaf function + // Its caller will be suspended, waiting for its done. + // But the suspend and done are always in different threads. + // It may suspend before done, or done before suspend. + // So we use an atomic, after first suspend_or_done() it will become true. + // Then the second suspend_or_done(), exchange(true) will returns true. + // Then we can safely delete this. + void suspend_or_done() { + if (_suspended_or_done->exchange(true)) { + // Already suspend AND done + if (_caller) { + // The leaf function has finished, resume its caller. + _caller->resume(); + } + delete this; + } + } + + void on_suspend() { suspend_or_done(); } + void on_done() { suspend_or_done(); } + + void set_callback(std::function cb) { + _callback = cb; + } + + void set_caller(AwaitablePromiseBase* caller) { + _caller = caller; + } + + // When the coroutine function begins, initial_suspend() will be called + auto initial_suspend() { + // Always suspend the function, later resume() will make it start to run + return std::suspend_always{}; + } + + // When the coroutine function throws unhandled exception, unhandled_exception() will be called + void unhandled_exception() { + LOG(ERROR) << "Coroutine throws unhandled exception!"; + std::exit(1); + } + + // When the coroutine function ends, final_suspend() will be called + auto final_suspend() noexcept { + if (_caller) { + // The caller is waiting for this function to return + // Now it can be resumed + _caller->resume(); + } + if (_callback) { + _callback(); + _callback = nullptr; + } + // Returns suspend_never{} so that the coroutine will be destroyed and the AwaitablePromise be deleted. + // DO NOT call destroy() here, which will cause double destruct of RAII objects. + // DO NOT call delete this here, which will cause malloc and free not match. + return std::suspend_never{}; + } + +private: + // For a Coroutine's root function, it needs a callback to notify its waiter + std::function _callback; + // For a Coroutine's leaf function, it is always resumed from another thread. + // It needs an atomic variable to keep thread safety. + // Non-leaf function does't need this, so we defined it as an optional pointer. + std::atomic* _suspended_or_done{nullptr}; + // For a Coroutine's non-root function, it needs to resume its caller when it finished. + AwaitablePromiseBase* _caller{nullptr}; +}; + +template +class AwaitablePromise : public AwaitablePromiseBase { +public: + T value() { + return _value; + } + + void set_value(T value) { + _value = value; + } + + void resume() override { + _coro.resume(); + } + + void destroy() override { + _coro.destroy(); + } + + // When we call a coroutine function, an AwaitablePromise will be created. + // Then call its get_return_object() to return an Awaitable. + auto get_return_object() { + _coro = std::coroutine_handle::from_promise(*this); + return Awaitable(this); + } + + // When we call co_return in a function, return_value() will be called. + auto return_value(T v) { + _value = v; + return std::suspend_never{}; + } + +private: + T _value; + std::coroutine_handle _coro; +}; + +template <> +class AwaitablePromise : public AwaitablePromiseBase { +public: + void resume() override { + _coro.resume(); + } + + void destroy() override { + _coro.destroy(); + } + + // When we call a coroutine function, an AwaitablePromise will be created. + // Then call its get_return_object() to return an Awaitable. + auto get_return_object() { + _coro = std::coroutine_handle::from_promise(*this); + return Awaitable(this); + } + + // When we call return in a coroutine function, return_value() will be called. + auto return_value() { + return std::suspend_never{}; + } + +private: + std::coroutine_handle _coro; +}; + +} // namespace detail + +// When co_await an Awaitable, await_ready() will be called automatically. +template +inline bool Awaitable::await_ready() { + // Always returns false so that the caller will be suspended at the co_await point. + return false; +} + +// If await_ready returns false, await_suspend() will be called automatically. +template +template +inline void Awaitable::await_suspend(std::coroutine_handle > awaiting) { + _promise->set_caller(&awaiting.promise()); + if (_promise->needs_suspend()) { + _promise->on_suspend(); + return; + } + _promise->resume(); +} + +// When the caller resumes from co_await, await_resume() will be called to get return value +template +inline T Awaitable::await_resume() { + if constexpr (!std::is_same::value) { + return _promise->value(); + } +} + +inline AwaitableDone::AwaitableDone() + : _awaitable(new detail::AwaitablePromise) { + _awaitable.promise()->set_needs_suspend(); +} + +inline void AwaitableDone::Run() { + _awaitable.promise()->on_done(); +} + +template +inline Coroutine::Coroutine(Awaitable&& aw, bool detach) { + detail::AwaitablePromise* origin_promise = aw.promise(); + CHECK(origin_promise); + + if (!detach) { + // Create butex for join() + _butex = bthread::butex_create_checked >(); + _butex->store(0); + + // Create AwaitablePromise for awaitable() + _promise = new detail::AwaitablePromise(); + _promise->set_needs_suspend(); + + auto cb = [this, origin_promise]() { + if constexpr (!std::is_same::value) { + dynamic_cast*>(_promise)->set_value(origin_promise->value()); + } + // wakeup join() + _butex->store(1); + bthread::butex_wake(_butex); + + // wakeup co_await on awaitable() + _promise->on_done(); + }; + origin_promise->set_callback(cb); + } + + // Start to run the coroutine + origin_promise->resume(); +} + +inline Coroutine::~Coroutine() { + if (_promise != nullptr && !_waited) { + join(); + } + if (_butex) { + bthread::butex_destroy(_butex); + _butex = nullptr; + } +} + +template +inline T Coroutine::join() { + CHECK(_promise != nullptr) << "join() can not be called to detached coroutine!"; + CHECK(_waited == false) << "awaitable() or join() can only be called once!"; + _waited = true; + bthread::butex_wait(_butex, 0, nullptr); + if constexpr (!std::is_same::value) { + auto promise = dynamic_cast*>(_promise); + CHECK(promise != nullptr) << "join type not match"; + T ret = promise->value(); + _promise->on_suspend(); + return ret; + } else { + _promise->on_suspend(); + } +} + +template +inline Awaitable Coroutine::awaitable() { + CHECK(_promise != nullptr) << "awaitable() can not be called to detached coroutine!"; + CHECK(_waited == false) << "awaitable() or join() can only be called once!"; + auto promise = dynamic_cast*>(_promise); + CHECK(promise != nullptr) << "awaitable type not match"; + _waited = true; + return Awaitable(promise); +} + +// NOTE: the caller will be resumed on bthread timer thread, +// bthread only have one timer thread, this may be performance bottle-neck +inline Awaitable Coroutine::usleep(int sleep_us) { + auto promise = new detail::AwaitablePromise(); + promise->set_needs_suspend(); + bthread_timer_t timer; + auto abstime = butil::microseconds_from_now(sleep_us); + auto cb = [](void* p) { + auto promise = static_cast*>(p); + promise->set_value(0); + promise->on_done(); + }; + if (bthread_timer_add(&timer, abstime, cb, promise) != 0) { + promise->set_value(-1); + promise->on_done(); + } + return Awaitable(promise); +} + +} // namespace experimental +} // namespace brpc + +#endif // BRPC_COROUTINE_INL_H \ No newline at end of file diff --git a/src/brpc/couchbase.cpp b/src/brpc/couchbase.cpp new file mode 100644 index 0000000..52e16dc --- /dev/null +++ b/src/brpc/couchbase.cpp @@ -0,0 +1,2634 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/couchbase.h" + +#include //for crc32 Vbucket_id + +// Debug flag for enabling debug statements +static bool DBUG = false; // Set to true to enable debug logs + +// Debug print macro +#define DEBUG_PRINT(msg) \ + do { \ + if (DBUG) { \ + std::cout << "[DEBUG] " << msg << std::endl; \ + } \ + } while (0) + +#include + +#include "brpc/policy/couchbase_protocol.h" +#include "brpc/proto_base.pb.h" +#include "butil/logging.h" +#include "butil/macros.h" +#include "butil/string_printf.h" +#include "butil/sys_byteorder.h" +#include "butil/third_party/rapidjson/document.h" +#include "butil/third_party/rapidjson/rapidjson.h" + +namespace brpc { + +// Couchbase protocol constants +namespace { +[[maybe_unused]] constexpr uint32_t APPLE_VBUCKET_COUNT = 64; +constexpr uint32_t DEFAULT_VBUCKET_COUNT = 1024; +constexpr int CONNECTION_ID_SIZE = 33; +constexpr size_t RANDOM_ID_HEX_SIZE = 67; // 33 bytes * 2 + null terminator +} // namespace + +// Static member definitions +CouchbaseManifestManager* + CouchbaseOperations::CouchbaseRequest::metadata_tracking = + &common_metadata_tracking; + +bool brpc::CouchbaseManifestManager::setBucketToCollectionManifest( + std::string server, std::string bucket, + CouchbaseManifestManager::CollectionManifest manifest) { + // Then update the collection manifest with proper locking + { + UniqueLock write_lock(rw_bucket_to_collection_manifest_mutex_); + bucket_to_collection_manifest_[server][bucket] = manifest; + } + + return true; +} + +bool brpc::CouchbaseManifestManager::getBucketToCollectionManifest( + std::string server, std::string bucket, + CouchbaseManifestManager::CollectionManifest* manifest) { + SharedLock read_lock(rw_bucket_to_collection_manifest_mutex_); + auto it1 = bucket_to_collection_manifest_.find(server); + if (it1 == bucket_to_collection_manifest_.end()) { + return false; + } + auto it2 = it1->second.find(bucket); + if (it2 == it1->second.end()) { + return false; + } + *manifest = it2->second; + return true; +} + +bool brpc::CouchbaseManifestManager::getManifestToCollectionId( + CouchbaseManifestManager::CollectionManifest* manifest, std::string scope, + std::string collection, uint8_t* collection_id) { + if (manifest == nullptr || collection_id == nullptr) { + DEBUG_PRINT("Invalid input: manifest or collection_id is null"); + return false; + } + auto it1 = manifest->scope_to_collection_id_map.find(scope); + if (it1 == manifest->scope_to_collection_id_map.end()) { + DEBUG_PRINT("Scope: " << scope << " not found in manifest"); + return false; + } + auto it2 = it1->second.find(collection); + if (it2 == it1->second.end()) { + DEBUG_PRINT("Collection: " << collection + << " not found in scope: " << scope); + return false; + } + *collection_id = it2->second; + return true; +} + +bool CouchbaseManifestManager::jsonToCollectionManifest( + const std::string& json, + CouchbaseManifestManager::CollectionManifest* manifest) { + if (manifest == nullptr) { + DEBUG_PRINT("Invalid input: manifest is null"); + return false; + } + + // Clear existing data + manifest->uid.clear(); + manifest->scope_to_collection_id_map.clear(); + + if (json.empty()) { + DEBUG_PRINT("JSON std::string is empty"); + return false; + } + + // Parse JSON using RapidJSON + BUTIL_RAPIDJSON_NAMESPACE::Document document; + document.Parse(json.c_str()); + + if (document.HasParseError()) { + DEBUG_PRINT("Failed to parse JSON: " << document.GetParseError()); + return false; + } + + if (!document.IsObject()) { + DEBUG_PRINT("JSON root is not an object"); + return false; + } + + // Extract uid + if (document.HasMember("uid") && document["uid"].IsString()) { + manifest->uid = document["uid"].GetString(); + } else { + DEBUG_PRINT("Missing or invalid 'uid' field in JSON"); + return false; + } + + // Extract scopes + if (!document.HasMember("scopes") || !document["scopes"].IsArray()) { + DEBUG_PRINT("Missing or invalid 'scopes' field in JSON"); + return false; + } + + const BUTIL_RAPIDJSON_NAMESPACE::Value& scopes = document["scopes"]; + for (BUTIL_RAPIDJSON_NAMESPACE::SizeType i = 0; i < scopes.Size(); ++i) { + const BUTIL_RAPIDJSON_NAMESPACE::Value& scope = scopes[i]; + + if (!scope.IsObject()) { + DEBUG_PRINT("Scope at index " << i << " is not an object"); + return false; + } + + // Extract scope name + if (!scope.HasMember("name") || !scope["name"].IsString()) { + DEBUG_PRINT("Missing or invalid 'name' field in scope at index " << i); + return false; + } + std::string scope_name = scope["name"].GetString(); + + // Extract collections + if (!scope.HasMember("collections") || !scope["collections"].IsArray()) { + DEBUG_PRINT("Missing or invalid 'collections' field in scope '" + << scope_name << "'"); + return false; + } + + const BUTIL_RAPIDJSON_NAMESPACE::Value& collections = scope["collections"]; + std:: unordered_map collection_map; + + for (BUTIL_RAPIDJSON_NAMESPACE::SizeType j = 0; j < collections.Size(); + ++j) { + const BUTIL_RAPIDJSON_NAMESPACE::Value& collection = collections[j]; + + if (!collection.IsObject()) { + DEBUG_PRINT("Collection at index " << j << " in scope '" << scope_name + << "' is not an object"); + return false; + } + + // Extract collection name + if (!collection.HasMember("name") || !collection["name"].IsString()) { + DEBUG_PRINT("Missing or invalid 'name' field in collection at index " + << j << " in scope '" << scope_name << "'"); + return false; + } + std::string collection_name = collection["name"].GetString(); + + // Extract collection uid (hex std::string) + if (!collection.HasMember("uid") || !collection["uid"].IsString()) { + DEBUG_PRINT("Missing or invalid 'uid' field in collection '" + << collection_name << "' in scope '" << scope_name << "'"); + return false; + } + std::string collection_uid_str = collection["uid"].GetString(); + + // Convert hex std::string to uint8_t + uint8_t collection_id = 0; + try { + // Convert hex std::string to integer + unsigned long uid_val = std::stoul(collection_uid_str, nullptr, 16); + if (uid_val > 255) { + DEBUG_PRINT( + "Collection uid '" + << collection_uid_str << "' exceeds uint8_t range in collection '" + << collection_name << "' in scope '" << scope_name << "'"); + return false; + } + collection_id = static_cast(uid_val); + } catch (const std::exception& e) { + DEBUG_PRINT("Failed to parse collection uid '" + << collection_uid_str << "' as hex in collection '" + << collection_name << "' in scope '" << scope_name << ": " + << e.what()); + return false; + } + + // Add to collection map + collection_map[collection_name] = collection_id; + } + + // Add scope and its collections to manifest + manifest->scope_to_collection_id_map[scope_name] = + std::move(collection_map); + } + + return true; +} + +bool CouchbaseManifestManager::refreshCollectionManifest( + brpc::Channel* channel, const std::string& server, const std::string& bucket, + std:: unordered_map* + local_collection_manifest_cache) { + // first fetch the manifest + // then compare the UID with the cached one + if (channel == nullptr) { + DEBUG_PRINT("No channel found, make sure to call Authenticate() first"); + return false; + } + if (server.empty()) { + DEBUG_PRINT("Server is empty, make sure to call Authenticate() first"); + return false; + } + if (bucket.empty()) { + DEBUG_PRINT("No bucket selected, make sure to call SelectBucket() first"); + return false; + } + CouchbaseOperations::CouchbaseRequest temp_get_manifest_request; + CouchbaseOperations::CouchbaseResponse temp_get_manifest_response; + brpc::Controller temp_cntl; + temp_get_manifest_request.getCollectionManifest(); + channel->CallMethod(NULL, &temp_cntl, &temp_get_manifest_request, + &temp_get_manifest_response, NULL); + if (temp_cntl.Failed()) { + DEBUG_PRINT("Failed to get collection manifest: bRPC controller error " + << temp_cntl.ErrorText()); + return false; + } + std::string manifest_json; + if (!temp_get_manifest_response.popManifest(&manifest_json)) { + DEBUG_PRINT("Failed to parse response for refreshing collection Manifest: " + << temp_get_manifest_response.lastError()); + return false; + } + brpc::CouchbaseManifestManager::CollectionManifest manifest; + if (!common_metadata_tracking.jsonToCollectionManifest(manifest_json, + &manifest)) { + DEBUG_PRINT("Failed to parse collection manifest JSON"); + return false; + } + brpc::CouchbaseManifestManager::CollectionManifest cached_manifest; + if (!common_metadata_tracking.getBucketToCollectionManifest( + server, bucket, &cached_manifest)) { + // No cached manifest found, set the new one + if (!common_metadata_tracking.setBucketToCollectionManifest(server, bucket, + manifest)) { + DEBUG_PRINT("Failed to cache collection manifest for bucket " + << bucket << " on server " << server); + return false; + } + DEBUG_PRINT("Cached collection manifest for bucket " + << bucket << " on server " << server); + // also update the local cache + if (local_collection_manifest_cache != nullptr) { + (*local_collection_manifest_cache)[bucket] = manifest; + } + return true; + } + // Compare the UID with the cached one + // If they are different, refresh the cache + else if (manifest.uid != cached_manifest.uid) { + DEBUG_PRINT("Collection manifest has changed for bucket " + << bucket << " on server " << server); + if (!common_metadata_tracking.setBucketToCollectionManifest(server, bucket, + manifest)) { + DEBUG_PRINT("Failed to update cached collection manifest for bucket " + << bucket << " on server " << server); + return false; + } + DEBUG_PRINT("Updated cached collection manifest for bucket " + << bucket << " on server " << server); + // update the local cache as well + if (local_collection_manifest_cache != nullptr) { + (*local_collection_manifest_cache)[bucket] = manifest; + DEBUG_PRINT("Added to local collection manifest cache for bucket " + << bucket << " on server " << server); + } + return true; + } else { + DEBUG_PRINT("Collection manifest is already up-to-date for bucket " + << bucket << " on server " << server); + if (local_collection_manifest_cache != nullptr) { + if (local_collection_manifest_cache->find(bucket) != + local_collection_manifest_cache->end()) { + // if the bucket already exists in the local cache, check the UID + if ((*local_collection_manifest_cache)[bucket].uid != manifest.uid) { + // if the UID is different, update the local cache + (*local_collection_manifest_cache)[bucket] = manifest; + DEBUG_PRINT("Updated local collection manifest cache for bucket " + << bucket << " on server " << server); + } + } else { + // if the bucket does not exist in the local cache, add it + (*local_collection_manifest_cache)[bucket] = manifest; + DEBUG_PRINT("Added to local collection manifest cache for bucket " + << bucket << " on server " << server); + } + } + return false; + } +} + +uint32_t CouchbaseOperations::CouchbaseRequest::hashCrc32(const char* key, + size_t key_length) { + static const uint32_t crc32tab[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, + }; + + uint64_t x; + uint32_t crc = UINT32_MAX; + + for (x = 0; x < key_length; x++) + crc = (crc >> 8) ^ crc32tab[(crc ^ (uint64_t)key[x]) & 0xff]; + +#ifdef __APPLE__ + return ((~crc) >> 16) % APPLE_VBUCKET_COUNT; +#else + return ((~crc) >> 16) % DEFAULT_VBUCKET_COUNT; +#endif +} + +void CouchbaseOperations::CouchbaseRequest::sharedCtor() { + _pipelined_count = 0; + _cached_size_ = 0; +} + +void CouchbaseOperations::CouchbaseRequest::sharedDtor() {} + +void CouchbaseOperations::CouchbaseRequest::setCachedSize(int size) const { + _cached_size_ = size; +} + +void CouchbaseOperations::CouchbaseRequest::Clear() { + _buf.clear(); + _pipelined_count = 0; +} + +// Support for scope level collections will be added in future. +// Get the Scope ID for a given scope name +// bool CouchbaseOperations::CouchbaseRequest::GetScopeId(const +// butil::StringPiece& scope_name) { +// if (scope_name.empty()) { +// DEBUG_PRINT("Empty scope name"); +// return false; +// } +// // Opcode 0xBC for Get Scope ID (see Collections.md) +// const policy::CouchbaseRequestHeader header = { +// policy::CB_MAGIC_REQUEST, +// policy::CB_GET_SCOPE_ID, +// butil::HostToNet16(scope_name.size()), +// 0, // no extras +// policy::CB_BINARY_RAW_BYTES, +// 0, // no vbucket +// butil::HostToNet32(scope_name.size()), +// 0, // opaque +// 0 // no CAS +// }; +// if (_buf.append(&header, sizeof(header))) { +// return false; +// } +// if (_buf.append(scope_name.data(), scope_name.size())) { +// return false; +// } +// ++_pipelined_count; +// return true; +// } + +bool CouchbaseOperations::CouchbaseRequest::selectBucketRequest( + const butil::StringPiece& bucket_name) { + if (bucket_name.empty()) { + DEBUG_PRINT("Empty bucket name"); + return false; + } + // construct the request header + const policy::CouchbaseRequestHeader header = { + policy::CB_MAGIC_REQUEST, + policy::CB_SELECT_BUCKET, + butil::HostToNet16(bucket_name.size()), + 0, + policy::CB_BINARY_RAW_BYTES, + 0, + butil::HostToNet32(bucket_name.size()), + 0, + 0}; + if (_buf.append(&header, sizeof(header))) { + DEBUG_PRINT("Failed to append header to buffer"); + return false; + } + if (_buf.append(bucket_name.data(), bucket_name.size())) { + DEBUG_PRINT("Failed to append bucket name to buffer"); + return false; + } + ++_pipelined_count; + return true; +} + +// HelloRequest sends a Hello request to the Couchbase server, which specifies +// the client features and capabilities. +// This is typically the first request sent after connecting to the server. +// It includes the agent name and a randomly generated connection ID in JSON +// format. +bool CouchbaseOperations::CouchbaseRequest::helloRequest() { + std::string agent = "brpc/1.0.0 ("; +#ifdef __APPLE__ + agent += "Darwin/"; +#elif defined(__linux__) + agent += "Linux/"; +#else + agent += "UnknownOS/"; +#endif +#if defined(__x86_64__) + agent += "x86_64"; +#elif defined(__aarch64__) + agent += "arm64"; +#else + agent += "unknown"; +#endif + agent += ";bssl/0x1010107f)"; + + // Generate a random connection ID as hex std::string + unsigned char raw_id[CONNECTION_ID_SIZE]; + FILE* urandom = fopen("/dev/urandom", "rb"); + if (!urandom || + fread(raw_id, 1, CONNECTION_ID_SIZE, urandom) != CONNECTION_ID_SIZE) { + if (urandom) fclose(urandom); + DEBUG_PRINT("Failed to generate random connection id"); + return false; + } + fclose(urandom); + char hex_id[RANDOM_ID_HEX_SIZE] = {0}; + for (int i = 0; i < CONNECTION_ID_SIZE; ++i) { + sprintf(hex_id + i * 2, "%02x", raw_id[i]); + } + + // Format key as JSON: {"a":"agent","i":"hex_id"} + std::string key = + std::string("{\"a\":\"") + agent + "\",\"i\":\"" + hex_id + "\"}"; + + const uint16_t key_len = key.size(); + uint16_t features[] = { + butil::HostToNet16(0x0001), // Datatype + butil::HostToNet16(0x0006), // XError + butil::HostToNet16(0x0007), // SelectBucket + butil::HostToNet16(0x000b), // Snappy + butil::HostToNet16(0x0012) // Collections + }; + + const uint32_t value_len = sizeof(features); + const uint32_t total_body_len = key_len + value_len; + + const policy::CouchbaseRequestHeader header = { + policy::CB_MAGIC_REQUEST, + policy::CB_HELLO_SELECT_FEATURES, + butil::HostToNet16(key_len), // key length + 0, // extras length + policy::CB_BINARY_RAW_BYTES, // data type + 0, // vbucket id + butil::HostToNet32(total_body_len), // total body length + 0, // opaque + 0 // cas value + }; + + if (_buf.append(&header, sizeof(header))) { + DEBUG_PRINT("Failed to append Hello header to buffer"); + return false; + } + if (_buf.append(key.data(), key_len)) { + DEBUG_PRINT("Failed to append Hello JSON key to buffer"); + return false; + } + if (_buf.append(reinterpret_cast(features), value_len)) { + DEBUG_PRINT("Failed to append Hello features to buffer"); + return false; + } + ++_pipelined_count; + return true; +} + +bool CouchbaseOperations::CouchbaseRequest::authenticateRequest( + const butil::StringPiece& username, const butil::StringPiece& password) { + if (username.empty() || password.empty()) { + DEBUG_PRINT("Empty username or password"); + return false; + } + // insert the features to get enabled, calling function helloRequest() will do + // this. + if (!helloRequest()) { + DEBUG_PRINT("Failed to send helloRequest for authentication"); + return false; + } + // Construct the request header + constexpr char kPlainAuthCommand[] = "PLAIN"; + constexpr char kPadding[1] = {'\0'}; + const brpc::policy::CouchbaseRequestHeader header = { + brpc::policy::CB_MAGIC_REQUEST, + brpc::policy::CB_BINARY_SASL_AUTH, + butil::HostToNet16(sizeof(kPlainAuthCommand) - 1), + 0, + 0, + 0, + butil::HostToNet32(sizeof(kPlainAuthCommand) + 1 + username.length() * 2 + + password.length()), + 0, + 0}; + std::string auth_str; + auth_str.reserve(sizeof(header) + sizeof(kPlainAuthCommand) - 1 + + username.size() * 2 + password.size() + 2); + auth_str.append(reinterpret_cast(&header), sizeof(header)); + auth_str.append(kPlainAuthCommand, sizeof(kPlainAuthCommand) - 1); + auth_str.append(username.data(), username.size()); + auth_str.append(kPadding, sizeof(kPadding)); + auth_str.append(username.data(), username.size()); + auth_str.append(kPadding, sizeof(kPadding)); + auth_str.append(password.data(), password.size()); + if (_buf.append(auth_str.data(), auth_str.size())) { + DEBUG_PRINT("Failed to append auth std::string to buffer"); + return false; + } + ++_pipelined_count; + return true; +} + +void CouchbaseOperations::CouchbaseRequest::MergeFrom( + const CouchbaseRequest& from) { + CHECK_NE(&from, this); + _buf.append(from._buf); + _pipelined_count += from._pipelined_count; +} + +bool CouchbaseOperations::CouchbaseRequest::IsInitialized() const { + return _pipelined_count != 0; +} + +void CouchbaseOperations::CouchbaseRequest::Swap(CouchbaseRequest* other) { + if (other != this) { + _buf.swap(other->_buf); + std::swap(_pipelined_count, other->_pipelined_count); + std::swap(_cached_size_, other->_cached_size_); + } +} + +void CouchbaseOperations::CouchbaseResponse::sharedCtor() { _cached_size_ = 0; } + +void CouchbaseOperations::CouchbaseResponse::sharedDtor() {} + +void CouchbaseOperations::CouchbaseResponse::setCachedSize(int size) const { + _cached_size_ = size; +} + +void CouchbaseOperations::CouchbaseResponse::Clear() {} + +void CouchbaseOperations::CouchbaseResponse::MergeFrom( + const CouchbaseResponse& from) { + CHECK_NE(&from, this); + _err = from._err; + _buf.append(from._buf); +} + +bool CouchbaseOperations::CouchbaseResponse::IsInitialized() const { + return !_buf.empty(); +} + +void CouchbaseOperations::CouchbaseResponse::swap(CouchbaseResponse* other) { + if (other != this) { + _buf.swap(other->_buf); + std::swap(_cached_size_, other->_cached_size_); + } +} + +// =================================================================== + +const char* CouchbaseOperations::CouchbaseResponse::statusStr(Status st) { + switch (st) { + case STATUS_SUCCESS: + return "SUCCESS"; + case STATUS_KEY_ENOENT: + return "Key not found"; + case STATUS_KEY_EEXISTS: + return "Key already exists"; + case STATUS_E2BIG: + return "Value too large"; + case STATUS_EINVAL: + return "Invalid arguments"; + case STATUS_NOT_STORED: + return "Item not stored"; + case STATUS_DELTA_BADVAL: + return "Invalid delta value for increment/decrement"; + case STATUS_VBUCKET_BELONGS_TO_ANOTHER_SERVER: + return "VBucket belongs to another server"; + case STATUS_AUTH_ERROR: + return "Authentication failed"; + case STATUS_AUTH_CONTINUE: + return "Authentication continue"; + case STATUS_ERANGE: + return "Range error"; + case STATUS_ROLLBACK: + return "Rollback required"; + case STATUS_EACCESS: + return "Access denied"; + case STATUS_NOT_INITIALIZED: + return "Not initialized"; + case STATUS_UNKNOWN_COMMAND: + return "Unknown command"; + case STATUS_ENOMEM: + return "Out of memory"; + case STATUS_NOT_SUPPORTED: + return "Operation not supported"; + case STATUS_EINTERNAL: + return "Internal server error"; + case STATUS_EBUSY: + return "Server busy"; + case STATUS_ETMPFAIL: + return "Temporary failure"; + case STATUS_UNKNOWN_COLLECTION: + return "Unknown collection"; + case STATUS_NO_COLLECTIONS_MANIFEST: + return "No collections manifest"; + case STATUS_CANNOT_APPLY_COLLECTIONS_MANIFEST: + return "Cannot apply collections manifest"; + case STATUS_COLLECTIONS_MANIFEST_IS_AHEAD: + return "Collections manifest is ahead"; + case STATUS_UNKNOWN_SCOPE: + return "Unknown scope"; + case STATUS_DCP_STREAM_ID_INVALID: + return "Invalid DCP stream ID"; + case STATUS_DURABILITY_INVALID_LEVEL: + return "Invalid durability level"; + case STATUS_DURABILITY_IMPOSSIBLE: + return "Durability requirements impossible"; + case STATUS_SYNC_WRITE_IN_PROGRESS: + return "Synchronous write in progress"; + case STATUS_SYNC_WRITE_AMBIGUOUS: + return "Synchronous write result ambiguous"; + case STATUS_SYNC_WRITE_RE_COMMIT_IN_PROGRESS: + return "Synchronous write re-commit in progress"; + case STATUS_SUBDOC_PATH_NOT_FOUND: + return "Sub-document path not found"; + case STATUS_SUBDOC_PATH_MISMATCH: + return "Sub-document path mismatch"; + case STATUS_SUBDOC_PATH_EINVAL: + return "Invalid sub-document path"; + case STATUS_SUBDOC_PATH_E2BIG: + return "Sub-document path too deep"; + case STATUS_SUBDOC_DOC_E2DEEP: + return "Sub-document too deep"; + case STATUS_SUBDOC_VALUE_CANTINSERT: + return "Cannot insert sub-document value"; + case STATUS_SUBDOC_DOC_NOT_JSON: + return "Document is not JSON"; + case STATUS_SUBDOC_NUM_E2BIG: + return "Sub-document number too large"; + case STATUS_SUBDOC_DELTA_E2BIG: + return "Sub-document delta too large"; + case STATUS_SUBDOC_PATH_EEXISTS: + return "Sub-document path already exists"; + case STATUS_SUBDOC_VALUE_E2DEEP: + return "Sub-document value too deep"; + case STATUS_SUBDOC_INVALID_COMBO: + return "Invalid sub-document operation combination"; + case STATUS_SUBDOC_MULTI_PATH_FAILURE: + return "Sub-document multi-path operation failed"; + case STATUS_SUBDOC_SUCCESS_DELETED: + return "Sub-document operation succeeded on deleted document"; + case STATUS_SUBDOC_XATTR_INVALID_FLAG_COMBO: + return "Invalid extended attribute flag combination"; + case STATUS_SUBDOC_XATTR_INVALID_KEY_COMBO: + return "Invalid extended attribute key combination"; + case STATUS_SUBDOC_XATTR_UNKNOWN_MACRO: + return "Unknown extended attribute macro"; + case STATUS_SUBDOC_XATTR_UNKNOWN_VATTR: + return "Unknown virtual extended attribute"; + case STATUS_SUBDOC_XATTR_CANT_MODIFY_VATTR: + return "Cannot modify virtual extended attribute"; + case STATUS_SUBDOC_MULTI_PATH_FAILURE_DELETED: + return "Sub-document multi-path operation failed on deleted document"; + case STATUS_SUBDOC_INVALID_XATTR_ORDER: + return "Invalid extended attribute order"; + case STATUS_SUBDOC_XATTR_UNKNOWN_VATTR_MACRO: + return "Unknown virtual extended attribute macro"; + case STATUS_SUBDOC_CAN_ONLY_REVIVE_DELETED_DOCUMENTS: + return "Can only revive deleted documents"; + case STATUS_SUBDOC_DELETED_DOCUMENT_CANT_HAVE_VALUE: + return "Deleted document cannot have a value"; + case STATUS_XATTR_EINVAL: + return "Invalid extended attributes"; + } + return "Unknown status"; +} + +// Helper method to format error messages with status codes +std::string CouchbaseOperations::CouchbaseResponse::formatErrorMessage( + uint16_t status_code, const std::string& operation, + const std::string& error_msg) { + if (error_msg.empty()) { + return butil::string_printf("%s failed with status 0x%02x (%s)", + operation.c_str(), status_code, + statusStr((Status)status_code)); + } else { + return butil::string_printf( + "%s failed with status 0x%02x (%s): %s", operation.c_str(), status_code, + statusStr((Status)status_code), error_msg.c_str()); + } +} + +// MUST NOT have extras. +// MUST have key. +// MUST NOT have value. +bool CouchbaseOperations::CouchbaseRequest::getOrDelete( + uint8_t command, const butil::StringPiece& key, uint8_t coll_id) { + // Collection ID + uint8_t collection_id = coll_id; + uint16_t VBucket_id = hashCrc32(key.data(), key.size()); + const policy::CouchbaseRequestHeader header = { + policy::CB_MAGIC_REQUEST, command, + butil::HostToNet16(key.size() + 1), // Key + 0, // extras length + policy::CB_BINARY_RAW_BYTES, // data type + butil::HostToNet16(VBucket_id), + butil::HostToNet32(key.size() + + sizeof(collection_id)), // total body length includes + // key and collection id + 0, 0}; + if (_buf.append(&header, sizeof(header))) { + return false; + } + if (_buf.append(&collection_id, sizeof(collection_id))) { + return false; + } + if (_buf.append(key.data(), key.size())) { + return false; + } + ++_pipelined_count; + return true; +} + +// collectionID fetching either from the metadata cache or if doesn't exist then +// fetch from the server. +bool CouchbaseOperations::CouchbaseRequest::getCachedOrFetchCollectionId( + std::string collection_name, uint8_t* coll_id, + brpc::CouchbaseManifestManager* metadata_tracking, brpc::Channel* channel, + const std::string& server, const std::string& selected_bucket, + std:: unordered_map* + local_cache) { + if (collection_name.empty()) { + DEBUG_PRINT("Empty collection name"); + return false; + } + if (channel == nullptr) { + DEBUG_PRINT("No channel found, make sure to call Authenticate() first"); + return false; + } + if (server.empty()) { + DEBUG_PRINT("Server is empty, make sure to call Authenticate() first"); + return false; + } + if (selected_bucket.empty()) { + DEBUG_PRINT("No bucket selected, make sure to call SelectBucket() first"); + return false; + } + + brpc::CouchbaseManifestManager::CollectionManifest manifest; + // check if the server/bucket exists in the cached collection manifest + if (!metadata_tracking->getBucketToCollectionManifest(server, selected_bucket, + &manifest)) { + DEBUG_PRINT("No cached collection manifest found for bucket " + << selected_bucket << " on server " << server + << ", fetching from server"); + // No cached manifest found, fetch from server + if (!metadata_tracking->refreshCollectionManifest( + channel, server, selected_bucket, local_cache)) { + return false; + } + // local cache will also be updated in refreshCollectionManifest + // get the reference to collectionID from local cache + if (!getLocalCachedCollectionId(selected_bucket, "_default", + collection_name, coll_id)) { + // collectionID not found in the latest manifest fetched from server + return false; + } + // collectionID has been found in the latest manifest fetched from server + // and is stored in coll_id + return true; + } else { + // check if collection name to id mapping exists. + if (!metadata_tracking->getManifestToCollectionId( + &manifest, "_default", collection_name, coll_id)) { + // Just to verify that the collectionID does not exist in the manifest + // refresh manifest from server and try again + if (!metadata_tracking->refreshCollectionManifest( + channel, server, selected_bucket, local_cache)) { + return false; + } + // local cache will also be updated in refreshCollectionManifest + // get the reference to collectionID from local cache + if (!getLocalCachedCollectionId(selected_bucket, "_default", + collection_name, coll_id)) { + // collectionID not found in the latest manifest fetched from server + return false; + } + // collectionID has been found in the latest manifest fetched from server + // and is stored in coll_id + return true; + } + // update the local cache with the manifest in global cache + (*local_collection_manifest_cache)[selected_bucket] = manifest; + // collectionID found in the cached manifest + return true; + } +} + +bool CouchbaseOperations::CouchbaseRequest::getRequest( + const butil::StringPiece& key, std::string collection_name, + brpc::Channel* channel, const std::string& server, const std::string& bucket) { + DEBUG_PRINT("getRequest called with key: " + << key << ", collection_name: " << collection_name + << ", server: " << server << ", bucket: " << bucket); + uint8_t coll_id = 0; // default collection ID + if (collection_name != "_default") { + // check if the local cache is empty or not. + if (local_collection_manifest_cache->empty()) { + DEBUG_PRINT("Local collection manifest cache is empty in getRequest"); + // if local cache is empty, goto global cache or fetch from server + if (!getCachedOrFetchCollectionId( + collection_name, &coll_id, metadata_tracking, channel, server, + bucket, local_collection_manifest_cache)) { + DEBUG_PRINT( + "Failed to get collection id from global cache or server in " + "getRequest"); + return false; + } + } + // check if the collection id is available in the local cache + else if (!getLocalCachedCollectionId(bucket, "_default", collection_name, + &coll_id)) { + DEBUG_PRINT("Collection id not found in local cache in getRequest"); + // if not check in the global cache or fetch from server + if (!getCachedOrFetchCollectionId( + collection_name, &coll_id, metadata_tracking, channel, server, + bucket, local_collection_manifest_cache)) { + DEBUG_PRINT( + "Failed to get collection id from global cache or server in " + "getRequest"); + return false; + } + } + } + DEBUG_PRINT("getRequest using coll_id: " << (int)coll_id); + return getOrDelete(policy::CB_BINARY_GET, key, coll_id); +} + +bool CouchbaseOperations::CouchbaseRequest::deleteRequest( + const butil::StringPiece& key, std::string collection_name, + brpc::Channel* channel, const std::string& server, const std::string& bucket) { + DEBUG_PRINT("deleteRequest called with key: " + << key << ", collection_name: " << collection_name + << ", server: " << server << ", bucket: " << bucket); + uint8_t coll_id = 0; // default collection ID + if (collection_name != "_default") { + // check if the local cache is empty or not. + if (local_collection_manifest_cache->empty()) { + DEBUG_PRINT("Local collection manifest cache is empty in deleteRequest"); + // if local cache is empty, goto global cache or fetch from server + if (!getCachedOrFetchCollectionId( + collection_name, &coll_id, metadata_tracking, channel, server, + bucket, local_collection_manifest_cache)) { + DEBUG_PRINT( + "Failed to get collection id from global cache or server in " + "deleteRequest"); + return false; + } + } + // check if the collection id is available in the local cache + else if (!getLocalCachedCollectionId(bucket, "_default", collection_name, + &coll_id)) { + DEBUG_PRINT("Collection id not found in local cache in deleteRequest"); + // if not check in the global cache or fetch from server + if (!getCachedOrFetchCollectionId( + collection_name, &coll_id, metadata_tracking, channel, server, + bucket, local_collection_manifest_cache)) { + DEBUG_PRINT( + "Failed to get collection id from global cache or server in " + "deleteRequest"); + return false; + } + } + } + DEBUG_PRINT("deleteRequest using coll_id: " << (int)coll_id); + return getOrDelete(policy::CB_BINARY_DELETE, key, coll_id); +} + +struct FlushHeaderWithExtras { + policy::CouchbaseRequestHeader header; + uint32_t exptime; +} __attribute__((packed)); +BAIDU_CASSERT(sizeof(FlushHeaderWithExtras) == 28, must_match); + +bool CouchbaseOperations::CouchbaseResponse::popGet(butil::IOBuf* value, + uint32_t* flags, + uint64_t* cas_value) { + const size_t n = _buf.size(); + policy::CouchbaseResponseHeader header; + if (n < sizeof(header)) { + butil::string_printf(&_err, "buffer is too small to contain a header"); + return false; + } + _buf.copy_to(&header, sizeof(header)); + if (header.command != (uint8_t)policy::CB_BINARY_GET) { + butil::string_printf(&_err, "not a GET response"); + return false; + } + if (n < sizeof(header) + header.total_body_length) { + butil::string_printf(&_err, "response=%u < header=%u + body=%u", + (unsigned)n, (unsigned)sizeof(header), + header.total_body_length); + return false; + } + if (header.status != (uint16_t)STATUS_SUCCESS) { + if (DBUG && header.extras_length != 0) { + DEBUG_PRINT("GET response must not have flags"); + } + if (DBUG && header.key_length != 0) { + DEBUG_PRINT("GET response must not have key"); + } + const int value_size = (int)header.total_body_length - + (int)header.extras_length - (int)header.key_length; + _status_code = header.status; + if (value_size < 0) { + butil::string_printf(&_err, "value_size=%d is non-negative", value_size); + return false; + } + _buf.pop_front(sizeof(header) + header.extras_length + header.key_length); + if (value_size > 0) { + std::string error_msg; + _buf.cutn(&error_msg, value_size); + _err = formatErrorMessage(header.status, "GET operation", error_msg); + } else { + _err = formatErrorMessage(header.status, "GET operation"); + } + return false; + } + if (header.extras_length != 4u) { + butil::string_printf( + &_err, "GET response must have flags as extras, actual length=%u", + header.extras_length); + return false; + } + if (header.key_length != 0) { + butil::string_printf(&_err, "GET response must not have key"); + return false; + } + const int value_size = (int)header.total_body_length - + (int)header.extras_length - (int)header.key_length; + if (value_size < 0) { + butil::string_printf(&_err, "value_size=%d is non-negative", value_size); + return false; + } + _buf.pop_front(sizeof(header)); + uint32_t raw_flags = 0; + _buf.cutn(&raw_flags, sizeof(raw_flags)); + if (flags) { + *flags = butil::NetToHost32(raw_flags); + } + if (value) { + value->clear(); + _buf.cutn(value, value_size); + } + if (cas_value) { + *cas_value = header.cas_value; + } + _err.clear(); + return true; +} + +bool CouchbaseOperations::CouchbaseResponse::popGet(std::string* value, + uint32_t* flags, + uint64_t* cas_value) { + butil::IOBuf tmp; + if (popGet(&tmp, flags, cas_value)) { + tmp.copy_to(value); + return true; + } + return false; +} + +// MUST NOT have extras +// MUST NOT have key +// MUST NOT have value +bool CouchbaseOperations::CouchbaseResponse::popDelete() { + return popStore(policy::CB_BINARY_DELETE, NULL); +} + +struct StoreHeaderWithExtras { + policy::CouchbaseRequestHeader header; + uint32_t flags; + uint32_t exptime; +} __attribute__((packed)); +BAIDU_CASSERT(sizeof(StoreHeaderWithExtras) == 32, must_match); +const size_t STORE_EXTRAS = + sizeof(StoreHeaderWithExtras) - sizeof(policy::CouchbaseRequestHeader); +// MUST have extras. +// MUST have key. +// MAY have value. +// Extra data for set/add/replace: +// Byte/ 0 | 1 | 2 | 3 | +// / | | | | +// |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +// +---------------+---------------+---------------+---------------+ +// 0| Flags | +// +---------------+---------------+---------------+---------------+ +// 4| Expiration | +// +---------------+---------------+---------------+---------------+ +// Total 8 bytes +bool CouchbaseOperations::CouchbaseRequest::store( + uint8_t command, const butil::StringPiece& key, + const butil::StringPiece& value, uint32_t flags, uint32_t exptime, + uint64_t cas_value, uint8_t coll_id) { + // add collection id + // uint16_t collection_id = 0x00; + uint8_t collection_id = coll_id; + uint16_t vBucket_id = hashCrc32(key.data(), key.size()); + StoreHeaderWithExtras header_with_extras = { + {policy::CB_MAGIC_REQUEST, command, + butil::HostToNet16(key.size() + + 1), // collection id is not included in part of key, + // so not including it in key length. + STORE_EXTRAS, policy::CB_JSON, butil::HostToNet16(vBucket_id), + butil::HostToNet32(STORE_EXTRAS + sizeof(collection_id) + key.size() + + value.size()), // total body length + 0, butil::HostToNet64(cas_value)}, + butil::HostToNet32(flags), + butil::HostToNet32(exptime)}; + if (_buf.append(&header_with_extras, sizeof(header_with_extras))) { + return false; + } + if (_buf.append(&collection_id, sizeof(collection_id))) { + return false; + } + if (_buf.append(key.data(), key.size())) { + return false; + } + if (_buf.append(value.data(), value.size())) { + return false; + } + ++_pipelined_count; + return true; +} + +// MUST have CAS +// MUST NOT have extras +// MUST NOT have key +// MUST NOT have value +bool CouchbaseOperations::CouchbaseResponse::popStore(uint8_t command, + uint64_t* cas_value) { + const size_t n = _buf.size(); + policy::CouchbaseResponseHeader header; + if (n < sizeof(header)) { + butil::string_printf(&_err, "buffer is too small to contain a header"); + return false; + } + _buf.copy_to(&header, sizeof(header)); + if (header.command != command) { + butil::string_printf(&_err, "Not a STORE response"); + return false; + } + if (n < sizeof(header) + header.total_body_length) { + butil::string_printf(&_err, "Not enough data"); + return false; + } + if (DBUG && header.extras_length != 0) { + DEBUG_PRINT("STORE response must not have flags"); + } + if (DBUG && header.key_length != 0) { + DEBUG_PRINT("STORE response must not have key"); + } + int value_size = (int)header.total_body_length - (int)header.extras_length - + (int)header.key_length; + if (header.status != (uint16_t)STATUS_SUCCESS) { + _buf.pop_front(sizeof(header) + header.extras_length + header.key_length); + _status_code = header.status; + if (value_size > 0) { + std::string error_msg; + _buf.cutn(&error_msg, value_size); + _err = formatErrorMessage( + header.status, couchbaseBinaryCommandToString(command), error_msg); + } else { + _err = formatErrorMessage(header.status, + couchbaseBinaryCommandToString(command)); + } + return false; + } + if (DBUG && value_size != 0) { + DEBUG_PRINT("STORE response must not have value, actually=" << value_size); + } + _buf.pop_front(sizeof(header) + header.total_body_length); + if (cas_value) { + *cas_value = header.cas_value; + } + _err.clear(); + return true; +} + +const char* +CouchbaseOperations::CouchbaseResponse::couchbaseBinaryCommandToString( + uint8_t cmd) { + switch (cmd) { + case 0x1f: + return "CB_HELLO_SELECT_FEATURES"; + case 0x89: + return "CB_SELECT_BUCKET"; + case 0xBC: + return "CB_GET_SCOPE_ID"; + case 0x00: + return "CB_BINARY_GET"; + case 0x01: + return "CB_BINARY_SET"; + case 0x02: + return "CB_BINARY_ADD"; + case 0x03: + return "CB_BINARY_REPLACE"; + case 0x04: + return "CB_BINARY_DELETE"; + case 0x05: + return "CB_BINARY_INCREMENT"; + case 0x06: + return "CB_BINARY_DECREMENT"; + case 0x07: + return "CB_BINARY_QUIT"; + case 0x08: + return "CB_BINARY_FLUSH"; + case 0x09: + return "CB_BINARY_GETQ"; + case 0x0a: + return "CB_BINARY_NOOP"; + case 0x0b: + return "CB_BINARY_VERSION"; + case 0x0c: + return "CB_BINARY_GETK"; + case 0x0d: + return "CB_BINARY_GETKQ"; + case 0x0e: + return "CB_BINARY_APPEND"; + case 0x0f: + return "CB_BINARY_PREPEND"; + case 0x10: + return "CB_BINARY_STAT"; + case 0x11: + return "CB_BINARY_SETQ"; + case 0x12: + return "CB_BINARY_ADDQ"; + case 0x13: + return "CB_BINARY_REPLACEQ"; + case 0x14: + return "CB_BINARY_DELETEQ"; + case 0x15: + return "CB_BINARY_INCREMENTQ"; + case 0x16: + return "CB_BINARY_DECREMENTQ"; + case 0x17: + return "CB_BINARY_QUITQ"; + case 0x18: + return "CB_BINARY_FLUSHQ"; + case 0x19: + return "CB_BINARY_APPENDQ"; + case 0x1a: + return "CB_BINARY_PREPENDQ"; + case 0x1c: + return "CB_BINARY_TOUCH"; + case 0x1d: + return "CB_BINARY_GAT"; + case 0x1e: + return "CB_BINARY_GATQ"; + case 0x23: + return "CB_BINARY_GATK"; + case 0x24: + return "CB_BINARY_GATKQ"; + case 0x20: + return "CB_BINARY_SASL_LIST_MECHS"; + case 0x21: + return "CB_BINARY_SASL_AUTH"; + case 0x22: + return "CB_BINARY_SASL_STEP"; + case 0xb5: + return "CB_GET_CLUSTER_CONFIG"; + case 0xba: + return "CB_GET_COLLECTIONS_MANIFEST"; + case 0xbb: + return "CB_COLLECTIONS_GET_CID"; + default: + return "UNKNOWN_COMMAND"; + } +} + +bool CouchbaseOperations::CouchbaseRequest::upsertRequest( + const butil::StringPiece& key, const butil::StringPiece& value, + uint32_t flags, uint32_t exptime, uint64_t cas_value, + std::string collection_name, brpc::Channel* channel, const std::string& server, + const std::string& bucket) { + DEBUG_PRINT("upsertRequest called with key: " + << key << ", value: " << value + << ", collection_name: " << collection_name + << ", server: " << server << ", bucket: " << bucket); + uint8_t coll_id = 0; // default collection ID + if (collection_name != "_default") { + // check if the local cache is empty or not. + if (local_collection_manifest_cache->empty()) { + DEBUG_PRINT("Local collection manifest cache is empty in upsertRequest"); + // if local cache is empty, goto global cache or fetch from server + if (!getCachedOrFetchCollectionId( + collection_name, &coll_id, metadata_tracking, channel, server, + bucket, local_collection_manifest_cache)) { + DEBUG_PRINT( + "Failed to get collection id from global cache or server in " + "upsertRequest"); + return false; + } + } + // check if the collection id is available in the local cache + else if (!getLocalCachedCollectionId(bucket, "_default", collection_name, + &coll_id)) { + DEBUG_PRINT("Collection id not found in local cache in upsertRequest"); + // if not check in the global cache or fetch from server + if (!getCachedOrFetchCollectionId( + collection_name, &coll_id, metadata_tracking, channel, server, + bucket, local_collection_manifest_cache)) { + DEBUG_PRINT( + "Failed to get collection id from global cache or server in " + "upsertRequest"); + return false; + } + } + } + DEBUG_PRINT("upsertRequest using coll_id: " << (int)coll_id); + return store(policy::CB_BINARY_SET, key, value, flags, exptime, cas_value, + coll_id); +} + +bool CouchbaseOperations::CouchbaseRequest::getCollectionManifest() { + const policy::CouchbaseRequestHeader header = { + policy::CB_MAGIC_REQUEST, + policy::CB_GET_COLLECTIONS_MANIFEST, + 0, // no key + 0, // no extras + policy::CB_BINARY_RAW_BYTES, + 0, // no vbucket + 0, // no body (no key, no extras, no value) + 0, // opaque + 0 // no CAS + }; + if (_buf.append(&header, sizeof(header))) { + return false; + } + ++_pipelined_count; + return true; +} + +bool CouchbaseOperations::CouchbaseRequest::addRequest( + const butil::StringPiece& key, const butil::StringPiece& value, + uint32_t flags, uint32_t exptime, uint64_t cas_value, + std::string collection_name, brpc::Channel* channel, const std::string& server, + const std::string& bucket) { + DEBUG_PRINT("addRequest called with key: " + << key << ", value: " << value + << ", collection_name: " << collection_name + << ", server: " << server << ", bucket: " << bucket); + uint8_t coll_id = 0; // default collection ID + if (collection_name != "_default") { + // check if the local cache is empty or not. + if (local_collection_manifest_cache->empty()) { + DEBUG_PRINT("Local collection manifest cache is empty in addRequest"); + // if local cache is empty, goto global cache or fetch from server + if (!getCachedOrFetchCollectionId( + collection_name, &coll_id, metadata_tracking, channel, server, + bucket, local_collection_manifest_cache)) { + DEBUG_PRINT( + "Failed to get collection id from global cache or server in " + "addRequest"); + return false; + } + } + // check if the collection id is available in the local cache + else if (!getLocalCachedCollectionId(bucket, "_default", collection_name, + &coll_id)) { + DEBUG_PRINT("Collection id not found in local cache in addRequest"); + // if not check in the global cache or fetch from server + if (!getCachedOrFetchCollectionId( + collection_name, &coll_id, metadata_tracking, channel, server, + bucket, local_collection_manifest_cache)) { + DEBUG_PRINT( + "Failed to get collection id from global cache or server in " + "addRequest"); + return false; + } + } + } + DEBUG_PRINT("addRequest using coll_id: " << (int)coll_id); + return store(policy::CB_BINARY_ADD, key, value, flags, exptime, cas_value, + coll_id); +} + +bool CouchbaseOperations::CouchbaseRequest::appendRequest( + const butil::StringPiece& key, const butil::StringPiece& value, + uint32_t flags, uint32_t exptime, uint64_t cas_value, + std::string collection_name, brpc::Channel* channel, const std::string& server, + const std::string& bucket) { + if (value.empty()) { + DEBUG_PRINT("value to append must be non-empty"); + return false; + } + uint8_t coll_id = 0; // default collection ID + if (collection_name != "_default") { + // check if the local cache is empty or not. + if (local_collection_manifest_cache->empty()) { + // if local cache is empty, goto global cache or fetch from server + if (!getCachedOrFetchCollectionId( + collection_name, &coll_id, metadata_tracking, channel, server, + bucket, local_collection_manifest_cache)) { + return false; + } + } + // check if the collection id is available in the local cache + else if (!getLocalCachedCollectionId(bucket, "_default", collection_name, + &coll_id)) { + // if not check in the global cache or fetch from server + if (!getCachedOrFetchCollectionId( + collection_name, &coll_id, metadata_tracking, channel, server, + bucket, local_collection_manifest_cache)) { + return false; + } + } + } + return store(policy::CB_BINARY_APPEND, key, value, flags, exptime, cas_value, + coll_id); +} + +bool CouchbaseOperations::CouchbaseRequest::prependRequest( + const butil::StringPiece& key, const butil::StringPiece& value, + uint32_t flags, uint32_t exptime, uint64_t cas_value, + std::string collection_name, brpc::Channel* channel, const std::string& server, + const std::string& bucket) { + if (value.empty()) { + DEBUG_PRINT("value to prepend must be non-empty"); + return false; + } + uint8_t coll_id = 0; // default collection ID + if (collection_name != "_default") { + // check if the local cache is empty or not. + if (local_collection_manifest_cache->empty()) { + // if local cache is empty, goto global cache or fetch from server + if (!getCachedOrFetchCollectionId( + collection_name, &coll_id, metadata_tracking, channel, server, + bucket, local_collection_manifest_cache)) { + return false; + } + } + // check if the collection id is available in the local cache + else if (!getLocalCachedCollectionId(bucket, "_default", collection_name, + &coll_id)) { + // if not check in the global cache or fetch from server + if (!getCachedOrFetchCollectionId( + collection_name, &coll_id, metadata_tracking, channel, server, + bucket, local_collection_manifest_cache)) { + return false; + } + } + } + return store(policy::CB_BINARY_PREPEND, key, value, flags, exptime, cas_value, + coll_id); +} + +bool CouchbaseOperations::CouchbaseResponse::popAuthenticate( + uint64_t* cas_value) { + return popStore(policy::CB_BINARY_SASL_AUTH, cas_value); +} +bool CouchbaseOperations::CouchbaseResponse::popHello(uint64_t* cas_value) { + return popStore(policy::CB_HELLO_SELECT_FEATURES, cas_value); +} +bool CouchbaseOperations::CouchbaseResponse::popUpsert(uint64_t* cas_value) { + return popStore(policy::CB_BINARY_SET, cas_value); +} +bool CouchbaseOperations::CouchbaseResponse::popAdd(uint64_t* cas_value) { + return popStore(policy::CB_BINARY_ADD, cas_value); +} +// Warning: Not tested +// bool CouchbaseOperations::CouchbaseResponse::PopReplace(uint64_t* cas_value) +// { +// return popStore(policy::CB_BINARY_REPLACE, cas_value); +// } +bool CouchbaseOperations::CouchbaseResponse::popAppend(uint64_t* cas_value) { + return popStore(policy::CB_BINARY_APPEND, cas_value); +} +bool CouchbaseOperations::CouchbaseResponse::popPrepend(uint64_t* cas_value) { + return popStore(policy::CB_BINARY_PREPEND, cas_value); +} +bool CouchbaseOperations::CouchbaseResponse::popSelectBucket( + uint64_t* cas_value) { + if (popStore(policy::CB_SELECT_BUCKET, cas_value) == false) { + DEBUG_PRINT("Failed to select bucket: " << _err); + return false; + } + // Note: Bucket tracking is now handled at CouchbaseOperations level, not + // per-thread + return true; +} +// Collection-related response method +bool CouchbaseOperations::CouchbaseResponse::popCollectionId( + uint8_t* collection_id) { + const size_t n = _buf.size(); + policy::CouchbaseResponseHeader header; + if (n < sizeof(header)) { + butil::string_printf(&_err, "buffer is too small to contain a header"); + return false; + } + _buf.copy_to(&header, sizeof(header)); + + if (header.command != policy::CB_COLLECTIONS_GET_CID) { + butil::string_printf(&_err, "Not a collection ID response"); + return false; + } + + // Making sure buffer has the whole body (extras + key + value) + if (n < sizeof(header) + header.total_body_length) { + butil::string_printf(&_err, "Not enough data"); + return false; + } + + if (header.status != 0) { + // handle error case + _buf.pop_front(sizeof(header) + header.extras_length + header.key_length); + // Possibly read error message from value if present + size_t value_size = + header.total_body_length - header.extras_length - header.key_length; + if (value_size > 0) { + std::string err_msg; + _buf.cutn(&err_msg, value_size); + _err = + formatErrorMessage(header.status, "Collection ID request", err_msg); + } else { + _err = formatErrorMessage(header.status, "Collection ID request"); + } + return false; + } + + // Success case: we expect extras_length >= 12 (8 bytes manifest + 4 bytes + // collection id) + if (header.extras_length < 12) { + butil::string_printf(&_err, "Extras too small to contain collection ID"); + // remove the response from buffer so you don't re‐process + _buf.pop_front(sizeof(header) + header.total_body_length); + return false; + } + + // Skip header + _buf.pop_front(sizeof(header)); + + // return true; + uint64_t manifest_id_net = 0; + _buf.copy_to(reinterpret_cast(&manifest_id_net), + sizeof(manifest_id_net)); + // You may convert this if needed: + uint64_t manifest_id = butil::NetToHost64(manifest_id_net); + DEBUG_PRINT("Manifest ID: " << manifest_id); + _buf.pop_front(sizeof(manifest_id_net)); + + // Next 1 bytes → collection ID (u8) + uint32_t cid_net = 0; + _buf.copy_to(reinterpret_cast(&cid_net), sizeof(cid_net)); + uint8_t cid_host = butil::NetToHost32(cid_net); + *collection_id = static_cast(cid_host); + _buf.pop_front(sizeof(cid_net)); + + _buf.pop_front(header.total_body_length); + _err.clear(); + return true; +} + +bool CouchbaseOperations::CouchbaseResponse::popManifest( + std::string* manifest_json) { + const size_t n = _buf.size(); + policy::CouchbaseResponseHeader header; + if (n < sizeof(header)) { + butil::string_printf(&_err, "buffer is too small to contain a header"); + return false; + } + _buf.copy_to(&header, sizeof(header)); + + if (header.command != policy::CB_GET_COLLECTIONS_MANIFEST) { + butil::string_printf(&_err, "Not a get collections manifest response"); + return false; + } + + // Making sure buffer has the whole body (extras + key + value) + if (n < sizeof(header) + header.total_body_length) { + butil::string_printf(&_err, "Not enough data"); + return false; + } + + if (header.status != 0) { + // handle error case + if (header.extras_length != 0) { + DEBUG_PRINT("Get Collections Manifest response must not have extras"); + } + if (header.key_length != 0) { + DEBUG_PRINT("Get Collections Manifest response must not have key"); + } + _buf.pop_front(sizeof(header) + header.extras_length + header.key_length); + // Possibly read error message from value if present + size_t value_size = + header.total_body_length - header.extras_length - header.key_length; + if (value_size > 0) { + std::string err_msg; + _buf.cutn(&err_msg, value_size); + _err = formatErrorMessage(header.status, "Get Collections Manifest", + err_msg); + } else { + _err = formatErrorMessage(header.status, "Get Collections Manifest"); + } + return false; + } + + // Success case: the manifest should be in the value section + size_t value_size = + header.total_body_length - header.extras_length - header.key_length; + if (value_size == 0) { + butil::string_printf(&_err, "No manifest data in response"); + _buf.pop_front(sizeof(header) + header.total_body_length); + return false; + } + + // Skip header and any extras/key + _buf.pop_front(sizeof(header) + header.extras_length + header.key_length); + + // Read the manifest JSON from the value section + _buf.cutn(manifest_json, value_size); + + _err.clear(); + return true; +} + +struct IncrHeaderWithExtras { + policy::CouchbaseRequestHeader header; + uint64_t delta; + uint64_t initial_value; + uint32_t exptime; +} __attribute__((packed)); +BAIDU_CASSERT(sizeof(IncrHeaderWithExtras) == 44, must_match); + +const size_t INCR_EXTRAS = + sizeof(IncrHeaderWithExtras) - sizeof(policy::CouchbaseRequestHeader); + +// MUST have extras. +// MUST have key. +// MUST NOT have value. +// Extra data for incr/decr: +// Byte/ 0 | 1 | 2 | 3 | +// / | | | | +// |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +// +---------------+---------------+---------------+---------------+ +// 0| Delta to add / subtract | +// | | +// +---------------+---------------+---------------+---------------+ +// 8| Initial value | +// | | +// +---------------+---------------+---------------+---------------+ +// 16| Expiration | +// +---------------+---------------+---------------+---------------+ +// Total 20 bytes +bool CouchbaseOperations::CouchbaseRequest::counter( + uint8_t command, const butil::StringPiece& key, uint64_t delta, + uint64_t initial_value, uint32_t exptime) { + IncrHeaderWithExtras header_with_extras = { + {policy::CB_MAGIC_REQUEST, command, butil::HostToNet16(key.size()), + INCR_EXTRAS, policy::CB_BINARY_RAW_BYTES, 0, + butil::HostToNet32(INCR_EXTRAS + key.size()), 0, 0}, + butil::HostToNet64(delta), + butil::HostToNet64(initial_value), + butil::HostToNet32(exptime)}; + if (_buf.append(&header_with_extras, sizeof(header_with_extras))) { + return false; + } + if (_buf.append(key.data(), key.size())) { + return false; + } + ++_pipelined_count; + return true; +} + +// MUST NOT have extras. +// MUST NOT have key. +// MUST have value. +// Byte/ 0 | 1 | 2 | 3 | +// / | | | | +// |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +// +---------------+---------------+---------------+---------------+ +// 0| 64-bit unsigned response. | +// | | +// +---------------+---------------+---------------+---------------+ +// Total 8 bytes +bool CouchbaseOperations::CouchbaseResponse::popCounter(uint8_t command, + uint64_t* new_value, + uint64_t* cas_value) { + const size_t n = _buf.size(); + policy::CouchbaseResponseHeader header; + if (n < sizeof(header)) { + butil::string_printf(&_err, "buffer is too small to contain a header"); + return false; + } + _buf.copy_to(&header, sizeof(header)); + if (header.command != command) { + butil::string_printf(&_err, "not a INCR/DECR response"); + return false; + } + if (n < sizeof(header) + header.total_body_length) { + butil::string_printf(&_err, "response=%u < header=%u + body=%u", + (unsigned)n, (unsigned)sizeof(header), + header.total_body_length); + return false; + } + if (DBUG && header.extras_length != 0) { + DEBUG_PRINT("INCR/DECR response must not have flags"); + } + if (DBUG && header.key_length != 0) { + DEBUG_PRINT("INCR/DECR response must not have key"); + } + const int value_size = (int)header.total_body_length - + (int)header.extras_length - (int)header.key_length; + _buf.pop_front(sizeof(header) + header.extras_length + header.key_length); + + if (header.status != (uint16_t)STATUS_SUCCESS) { + if (value_size < 0) { + butil::string_printf(&_err, "value_size=%d is negative", value_size); + } else { + if (value_size > 0) { + std::string error_msg; + _buf.cutn(&error_msg, value_size); + _err = + formatErrorMessage(header.status, "Counter operation", error_msg); + } else { + _err = formatErrorMessage(header.status, "Counter operation"); + } + } + return false; + } + if (value_size != 8) { + butil::string_printf(&_err, "value_size=%d is not 8", value_size); + return false; + } + uint64_t raw_value = 0; + _buf.cutn(&raw_value, sizeof(raw_value)); + *new_value = butil::NetToHost64(raw_value); + if (cas_value) { + *cas_value = header.cas_value; + } + _err.clear(); + return true; +} + +// MUST NOT have extras. +// MUST NOT have key. +// MUST NOT have value. +bool CouchbaseOperations::CouchbaseRequest::versionRequest() { + const policy::CouchbaseRequestHeader header = {policy::CB_MAGIC_REQUEST, + policy::CB_BINARY_VERSION, + 0, + 0, + policy::CB_BINARY_RAW_BYTES, + 0, + 0, + 0, + 0}; + if (_buf.append(&header, sizeof(header))) { + return false; + } + ++_pipelined_count; + return true; +} + +bool CouchbaseOperations::CouchbaseResponse::popVersion(std::string* version) { + const size_t n = _buf.size(); + policy::CouchbaseResponseHeader header; + if (n < sizeof(header)) { + butil::string_printf(&_err, "buffer is too small to contain a header"); + return false; + } + _buf.copy_to(&header, sizeof(header)); + if (header.command != policy::CB_BINARY_VERSION) { + butil::string_printf(&_err, "not a VERSION response"); + return false; + } + if (n < sizeof(header) + header.total_body_length) { + butil::string_printf(&_err, "response=%u < header=%u + body=%u", + (unsigned)n, (unsigned)sizeof(header), + header.total_body_length); + return false; + } + if (DBUG && header.extras_length != 0) { + DEBUG_PRINT("VERSION response must not have flags"); + } + if (DBUG && header.key_length != 0) { + DEBUG_PRINT("VERSION response must not have key"); + } + const int value_size = (int)header.total_body_length - + (int)header.extras_length - (int)header.key_length; + _buf.pop_front(sizeof(header) + header.extras_length + header.key_length); + if (value_size < 0) { + butil::string_printf(&_err, "value_size=%d is negative", value_size); + return false; + } + if (header.status != (uint16_t)STATUS_SUCCESS) { + if (value_size > 0) { + std::string error_msg; + _buf.cutn(&error_msg, value_size); + _err = formatErrorMessage(header.status, "Version request", error_msg); + } else { + _err = formatErrorMessage(header.status, "Version request"); + } + return false; + } + if (version) { + version->clear(); + _buf.cutn(version, value_size); + } + _err.clear(); + return true; +} + +bool sendRequest(CouchbaseOperations::operation_type op_type, const std::string& key, + const std::string& value, std::string collection_name, + CouchbaseOperations::Result* result, brpc::Channel* channel, + const std::string& server, const std::string& bucket, + CouchbaseOperations::CouchbaseRequest* request, + CouchbaseOperations::CouchbaseResponse* response) { + if (channel == nullptr) { + DEBUG_PRINT("No channel found, make sure to call Authenticate() first"); + result->error_message = + "No channel found, make sure to call Authenticate() first"; + return false; + } + if (server.empty()) { + DEBUG_PRINT("Server is empty, make sure to call Authenticate() first"); + result->error_message = + "Server is empty, make sure to call Authenticate() first"; + return false; + } + if (bucket.empty()) { + DEBUG_PRINT("No bucket selected, make sure to call SelectBucket() first"); + result->error_message = + "No bucket selected, make sure to call SelectBucket() first"; + return false; + } + brpc::Controller cntl; + bool request_created = false; + switch (op_type) { + case CouchbaseOperations::GET: + request_created = + request->getRequest(key, collection_name, channel, server, bucket); + break; + case CouchbaseOperations::UPSERT: + request_created = request->upsertRequest( + key, value, 0, 0, 0, collection_name, channel, server, bucket); + break; + case CouchbaseOperations::ADD: + request_created = request->addRequest( + key, value, 0, 0, 0, collection_name, channel, server, bucket); + break; + case CouchbaseOperations::APPEND: + request_created = request->appendRequest( + key, value, 0, 0, 0, collection_name, channel, server, bucket); + break; + case CouchbaseOperations::PREPEND: + request_created = request->prependRequest( + key, value, 0, 0, 0, collection_name, channel, server, bucket); + break; + case CouchbaseOperations::DELETE: + request_created = + request->deleteRequest(key, collection_name, channel, server, bucket); + break; + default: + DEBUG_PRINT("Unsupported operation type"); + result->success = false; + result->value = ""; + result->error_message = "Unsupported operation type"; + return false; + } + if (!request_created) { + DEBUG_PRINT("CollectionID does not exist." << op_type); + result->success = false; + result->value = ""; + result->error_message = + "CollectionID does not exist." + std::to_string(op_type); + result->status_code = 0x88; // using 0x88 as the only possible failure code + // that indicates the collectionID is not found + return false; + } + channel->CallMethod(NULL, &cntl, request, response, NULL); + if (cntl.Failed()) { + DEBUG_PRINT("Failed to perform operation on key: " + << key << " to Couchbase: " << cntl.ErrorText()); + result->success = false; + result->value = ""; + result->error_message = cntl.ErrorText(); + return false; + } + if (op_type == CouchbaseOperations::GET) { + std::string value; + uint32_t flags = 0; + uint64_t cas = 0; + if (response->popGet(&value, &flags, &cas) == false) { + result->success = false; + result->value = ""; + result->error_message = response->lastError(); + result->status_code = response->_status_code; + if (result->status_code == 0x88) { + DEBUG_PRINT( + "CollectionID does not exist on server, need to refresh collection " + "manifest from server"); + // could have called sendRequest recursively, + // but if somehow the collectionID keeps on chaning, it would lead to + // infinite recursion and stack overflow in the end. so we retry once + // here instead and return failure if it still fails. + + // (0x88) unknown collection, this means that the collection_manifest + // has been updated on the server side. The collectionID present in the + // local cache/global cache is no longer valid. This can happen if a + // collection is deleted and recreated with the same name. + if (!request->metadata_tracking->refreshCollectionManifest( + channel, server, bucket, + request->local_collection_manifest_cache)) { + DEBUG_PRINT("Failed to refresh collection manifest"); + result->error_message = "Failed to refresh collection manifest"; + } else { + DEBUG_PRINT("Successfully refreshed collection manifest"); + // retry the request; + request->Clear(); + response->Clear(); + cntl.Reset(); + if (!request->getRequest(key, collection_name, channel, server, + bucket)) { + DEBUG_PRINT("CollectionID does not exist."); + result->success = false; + result->value = ""; + result->error_message = "CollectionID does not exist."; + result->status_code = + 0x88; // using 0x88 as the only possible failure code that + // indicates the collectionID is not found + return false; + } + channel->CallMethod(NULL, &cntl, request, response, NULL); + if (cntl.Failed()) { + DEBUG_PRINT("Failed to perform operation on key: " + << key << " to Couchbase: " << cntl.ErrorText()); + result->success = false; + result->value = ""; + result->error_message = cntl.ErrorText(); + return false; // return on failure + } + if (response->popGet(&value, &flags, &cas) == false) { + result->success = false; + result->value = ""; + result->error_message = response->lastError(); + result->status_code = response->_status_code; + return false; // return on failure + } + // Successfully got the value after retry + result->success = true; + result->value = value; + result->status_code = 0; + return true; + } + } + return false; + } + // Successfully got the value + result->success = true; + result->value = value; + result->status_code = 0; + return true; + } else { + uint64_t cas_value = 0; + // pop response on the basis of operation type + bool pop_success = false; + switch (op_type) { + case CouchbaseOperations::UPSERT: + pop_success = response->popUpsert(&cas_value); + break; + case CouchbaseOperations::ADD: + pop_success = response->popAdd(&cas_value); + break; + case CouchbaseOperations::APPEND: + pop_success = response->popAppend(&cas_value); + break; + case CouchbaseOperations::PREPEND: + pop_success = response->popPrepend(&cas_value); + break; + case CouchbaseOperations::DELETE: + pop_success = response->popDelete(); + break; + default: + DEBUG_PRINT("Unsupported operation type in response pop"); + result->success = false; + result->value = ""; + result->error_message = "Unsupported operation type in response pop"; + return false; + } + if (!pop_success) { + result->success = false; + result->value = ""; + result->error_message = response->lastError(); + result->status_code = response->_status_code; + if (result->status_code == 0x88) { + // (0x88) unknown collection, this typically means that the + // collection_manifest has been updated on the server side. and the + // client have a stale copy of collection manifest. In this case, we + // need to refresh the collection manifest and retry the operation. + if (!request->metadata_tracking->refreshCollectionManifest( + channel, server, bucket, + request->local_collection_manifest_cache)) { + DEBUG_PRINT("Failed to refresh collection manifest"); + result->error_message = "Failed to refresh collection manifest"; + return false; + } + // could have called sendRequest recursively, + // but if somehow the collectionID keeps on chaning, it would lead to + // infinite recursion and stack overflow in the end. so we retry once + // here instead and return failure if it still fails. + DEBUG_PRINT("Successfully refreshed collection manifest"); + // retry the request; + request->Clear(); + response->Clear(); + switch (op_type) { + case CouchbaseOperations::UPSERT: + request->upsertRequest(key, value, 0, 0, 0, collection_name, + channel, server, bucket); + break; + case CouchbaseOperations::ADD: + request->addRequest(key, value, 0, 0, 0, collection_name, channel, + server, bucket); + break; + case CouchbaseOperations::APPEND: + request->appendRequest(key, value, 0, 0, 0, collection_name, + channel, server, bucket); + break; + case CouchbaseOperations::PREPEND: + request->prependRequest(key, value, 0, 0, 0, collection_name, + channel, server, bucket); + break; + case CouchbaseOperations::DELETE: + request->deleteRequest(key, collection_name, channel, server, + bucket); + break; + default: + DEBUG_PRINT("Unsupported operation type in response pop"); + result->success = false; + result->value = ""; + result->error_message = + "Unsupported operation type in response pop"; + return false; + } + channel->CallMethod(NULL, &cntl, request, response, NULL); + if (cntl.Failed()) { + DEBUG_PRINT("Failed to perform operation on key: " + << key << " to Couchbase: " << cntl.ErrorText()); + result->success = false; + result->value = ""; + result->error_message = cntl.ErrorText(); + return false; // return on failure + } + pop_success = false; + switch (op_type) { + case CouchbaseOperations::UPSERT: + pop_success = response->popUpsert(&cas_value); + break; + case CouchbaseOperations::ADD: + pop_success = response->popAdd(&cas_value); + break; + case CouchbaseOperations::APPEND: + pop_success = response->popAppend(&cas_value); + break; + case CouchbaseOperations::PREPEND: + pop_success = response->popPrepend(&cas_value); + break; + case CouchbaseOperations::DELETE: + pop_success = response->popDelete(); + break; + default: + DEBUG_PRINT("Unsupported operation type in response pop"); + result->success = false; + result->value = ""; + result->error_message = + "Unsupported operation type in response pop"; + return false; + } + if (!pop_success) { + result->success = false; + result->value = ""; + result->error_message = response->lastError(); + result->status_code = response->_status_code; + return false; // return on failure + } + // Successfully performed the operation after retry + result->success = true; + result->value = ""; + result->status_code = 0; + return true; + } + return false; + } + // Successfully performed the operation + // Note: For operations other than GET, we don't have a value to return + // so we return empty std::string for value. + result->success = true; + result->value = ""; + result->status_code = 0; + return true; + } +} +CouchbaseOperations::Result CouchbaseOperations::get(const std::string& key, + std::string collection_name) { + // create CouchbaseRequest and CouchbaseResponse objects and then using the + // channel which is created for this thread in authenticate() use it to call() + CouchbaseRequest request(&local_bucket_to_collection_manifest_); + CouchbaseResponse response; + brpc::Controller cntl; + CouchbaseOperations::Result result; + sendRequest(CouchbaseOperations::GET, key, "", collection_name, &result, + channel_, server_address_, selected_bucket_, &request, &response); + return result; +} + +bool CouchbaseOperations::CouchbaseRequest::getLocalCachedCollectionId( + const std::string& bucket, const std::string& scope, const std::string& collection, + uint8_t* collection_id) { + if (bucket.empty() || scope.empty() || collection.empty()) { + DEBUG_PRINT("Bucket, scope, and collection names must be non-empty"); + return false; + } + auto it = local_collection_manifest_cache->find(bucket); + if (it != local_collection_manifest_cache->end()) { + CouchbaseManifestManager::CollectionManifest& manifest = it->second; + if (manifest.scope_to_collection_id_map.find(scope) != + manifest.scope_to_collection_id_map.end()) { + auto& collection_map = manifest.scope_to_collection_id_map[scope]; + if (collection_map.find(collection) != collection_map.end()) { + *collection_id = collection_map[collection]; + return true; + } else { + DEBUG_PRINT("Collection name not found in local cache: " << collection); + return false; + } + } else { + DEBUG_PRINT("Scope name not found in local cache: " << scope); + return false; + } + } else { + DEBUG_PRINT("Bucket name not found in local cache: " << bucket); + return false; + } +} + +CouchbaseOperations::Result CouchbaseOperations::upsert( + const std::string& key, const std::string& value, std::string collection_name) { + CouchbaseRequest request(&local_bucket_to_collection_manifest_); + CouchbaseResponse response; + brpc::Controller cntl; + CouchbaseOperations::Result result; + sendRequest(CouchbaseOperations::UPSERT, key, value, collection_name, &result, + channel_, server_address_, selected_bucket_, &request, &response); + return result; +} + +CouchbaseOperations::Result CouchbaseOperations::delete_( + const std::string& key, std::string collection_name) { + CouchbaseRequest request(&local_bucket_to_collection_manifest_); + CouchbaseResponse response; + brpc::Controller cntl; + CouchbaseOperations::Result result; + if (!sendRequest(CouchbaseOperations::DELETE, key, "", collection_name, + &result, channel_, server_address_, selected_bucket_, + &request, &response)) { + return result; + } + return result; +} + +CouchbaseOperations::Result CouchbaseOperations::add(const std::string& key, + const std::string& value, + std::string collection_name) { + CouchbaseRequest request(&local_bucket_to_collection_manifest_); + CouchbaseResponse response; + brpc::Controller cntl; + CouchbaseOperations::Result result; + sendRequest(CouchbaseOperations::ADD, key, value, collection_name, &result, + channel_, server_address_, selected_bucket_, &request, &response); + return result; +} + +CouchbaseOperations::Result CouchbaseOperations::authenticate( + const std::string& username, const std::string& password, + const std::string& server_address, const std::string& bucket_name) { + return authenticateAll(username, password, server_address, bucket_name, false, + ""); +} + +CouchbaseOperations::Result CouchbaseOperations::authenticateSSL( + const std::string& username, const std::string& password, + const std::string& server_address, const std::string& bucket_name, + std::string path_to_cert) { + return authenticateAll(username, password, server_address, bucket_name, true, + path_to_cert); +} + +CouchbaseOperations::Result CouchbaseOperations::authenticateAll( + const std::string& username, const std::string& password, + const std::string& server_address, const std::string& bucket_name, bool enable_ssl, + std::string path_to_cert) { + // Create a channel to the Couchbase server + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_COUCHBASE; + options.connection_type = "single"; + options.timeout_ms = 1000; // 1 second + options.max_retry = 3; + + // CRITICAL: Set unique connection_group to prevent connection sharing + // Each CouchbaseOperations instance connected to same bucket gets its own + // connection group + options.connection_group = server_address + bucket_name; + + // enable_ssl + if (enable_ssl) { + brpc::ChannelSSLOptions* ssl_options = options.mutable_ssl_options(); + ssl_options->sni_name = server_address; + ssl_options->verify.verify_depth = + 1; // Enable certificate verification, to disable SSL set it to 0 + ssl_options->verify.ca_file_path = + path_to_cert; // Path to your downloaded TLS certificate + } + CouchbaseOperations::Result result; + brpc::Channel* new_channel = new brpc::Channel(); + if (new_channel->Init(server_address.c_str(), &options) != 0) { + DEBUG_PRINT("Failed to initialize Couchbase channel to " << server_address); + delete new_channel; + result.success = false; + result.value = ""; + result.error_message = "Failed to initialize Couchbase channel"; + return result; + } + // Create a CouchbaseRequest and CouchbaseResponse for authentication + CouchbaseRequest request; + CouchbaseResponse response; + brpc::Controller cntl; + if (request.authenticateRequest(username.c_str(), password.c_str()) == + false) { + DEBUG_PRINT("Failed to create Authenticate request for user: " << username); + delete new_channel; + result.success = false; + return result; + } + new_channel->CallMethod(NULL, &cntl, &request, &response, NULL); + if (cntl.Failed()) { + DEBUG_PRINT("Failed to access Couchbase: " << cntl.ErrorText()); + delete new_channel; + result.success = false; + result.value = ""; + result.error_message = cntl.ErrorText(); + return result; + } + uint64_t cas; + if (response.popHello(&cas) == false) { + DEBUG_PRINT("Failed to receive HELO response from Couchbase: " + << response.lastError()); + delete new_channel; + result.success = false; + result.value = ""; + result.error_message = response.lastError(); + result.status_code = response._status_code; + return result; + } + if (response.popAuthenticate(&cas) == false) { + DEBUG_PRINT("Failed to authenticate user: " << username << " to Couchbase: " + << response.lastError()); + result.success = false; + result.value = ""; + result.error_message = response.lastError(); + result.status_code = response._status_code; + return result; + } + // Successfully authenticated + channel_ = new_channel; + this->server_address_ = server_address; + result.success = true; + result.status_code = 0; + + DEBUG_PRINT("Instance " << reinterpret_cast(this) + << " authenticated with unique connection_group:" + << server_address_ + bucket_name); + + // select the bucket + result = selectBucket(bucket_name); + return result; +} + +CouchbaseOperations::Result CouchbaseOperations::selectBucket( + const std::string& bucket_name) { + CouchbaseRequest request(&local_bucket_to_collection_manifest_); + CouchbaseResponse response; + brpc::Controller cntl; + CouchbaseOperations::Result result; + if (request.selectBucketRequest(bucket_name.c_str()) == false) { + DEBUG_PRINT( + "Failed to create Select Bucket request for bucket: " << bucket_name); + result.success = false; + result.value = ""; + return result; + } + channel_->CallMethod(NULL, &cntl, &request, &response, NULL); + if (cntl.Failed()) { + DEBUG_PRINT("Failed to select bucket: " + << bucket_name << " from Couchbase: " << cntl.ErrorText()); + result.success = false; + result.value = ""; + result.error_message = cntl.ErrorText(); + return result; + } + if (response.popSelectBucket(NULL) == false) { + result.success = false; + result.value = ""; + result.error_message = response.lastError(); + result.status_code = response._status_code; + return result; + } + // Successfully selected the bucket + selected_bucket_ = bucket_name; + result.success = true; + result.value = ""; + result.status_code = 0; + + // fetch the collection manifest for this bucket and store it in local cache + if (request.local_collection_manifest_cache->find(bucket_name) == + request.local_collection_manifest_cache->end()) { + // only fetch if not already present in the local cache + CouchbaseManifestManager::CollectionManifest manifest; + if (!common_metadata_tracking.getBucketToCollectionManifest( + server_address_, bucket_name, &manifest)) { + DEBUG_PRINT("Collection manifest for bucket: " + << bucket_name + << " not found in global cache, the local cache"); + + // manifest for this bucket/server is not cached yet, will fetch it from + // server now. refresh will also update the local cache with the fetched + // manifest + request.metadata_tracking->refreshCollectionManifest( + channel_, server_address_, bucket_name, + request.local_collection_manifest_cache); + // We simply try once to prefetch the manifest, before any collection + // operation. If it fails, it will be lazily updated when a collection + // operation is performed. + } else { + // update the local cache with the cache manifest from global + // cache(common_metadata_tracking) + DEBUG_PRINT("Updated local cache collection manifest for bucket: " + << bucket_name); + (*request.local_collection_manifest_cache)[bucket_name] = manifest; + } + } else { + DEBUG_PRINT("Collection manifest for bucket: " + << bucket_name << " already present in local cache"); + } + DEBUG_PRINT("Bucket selected successfully " << bucket_name); + return result; +} + +CouchbaseOperations::Result CouchbaseOperations::append( + const std::string& key, const std::string& value, std::string collection_name) { + CouchbaseRequest request(&local_bucket_to_collection_manifest_); + CouchbaseResponse response; + brpc::Controller cntl; + CouchbaseOperations::Result result; + sendRequest(CouchbaseOperations::APPEND, key, value, collection_name, &result, + channel_, server_address_, selected_bucket_, &request, &response); + return result; +} + +CouchbaseOperations::Result CouchbaseOperations::prepend( + const std::string& key, const std::string& value, std::string collection_name) { + CouchbaseRequest request(&local_bucket_to_collection_manifest_); + CouchbaseResponse response; + brpc::Controller cntl; + CouchbaseOperations::Result result; + sendRequest(CouchbaseOperations::PREPEND, key, value, collection_name, + &result, channel_, server_address_, selected_bucket_, &request, + &response); + return result; +} + +CouchbaseOperations::Result CouchbaseOperations::version() { + CouchbaseRequest request; + CouchbaseResponse response; + brpc::Controller cntl; + CouchbaseOperations::Result result; + if (request.versionRequest() == false) { + DEBUG_PRINT("Failed to create Version request"); + result.success = false; + result.value = ""; + return result; + } + channel_->CallMethod(NULL, &cntl, &request, &response, NULL); + if (cntl.Failed()) { + DEBUG_PRINT("Failed to get version from Couchbase: " << cntl.ErrorText()); + result.success = false; + result.value = ""; + result.error_message = cntl.ErrorText(); + return result; + } + std::string version; + if (response.popVersion(&version) == false) { + result.success = false; + result.value = ""; + result.error_message = response.lastError(); + result.status_code = response._status_code; + return result; + } + // Successfully got version + result.success = true; + result.value = version; + result.status_code = 0; + return result; +} + +bool CouchbaseOperations::beginPipeline() { + if (pipeline_active) { + DEBUG_PRINT("Pipeline already active. Call clearPipeline() first."); + return false; + } + + // Clear any previous state + while (!pipeline_operations_queue.empty()) { + pipeline_operations_queue.pop(); + } + pipeline_request_couchbase_req.Clear(); + + pipeline_active = true; + return true; +} + +bool CouchbaseOperations::pipelineRequest(operation_type op_type, + const std::string& key, + const std::string& value, + std::string collection_name) { + if (!pipeline_active) { + DEBUG_PRINT("Pipeline not active. Call beginPipeline() first."); + return false; + } + + switch (op_type) { + case GET: + if (pipeline_request_couchbase_req.getRequest( + key, collection_name, channel_, server_address_, + selected_bucket_) == false) { + return false; + } + pipeline_operations_queue.push(GET); + break; + case UPSERT: + if (pipeline_request_couchbase_req.upsertRequest( + key, value, 0, 0, 0, collection_name, channel_, server_address_, + selected_bucket_) == false) { + return false; + } + pipeline_operations_queue.push(UPSERT); + break; + case ADD: + if (pipeline_request_couchbase_req.addRequest( + key, value, 0, 0, 0, collection_name, channel_, server_address_, + selected_bucket_) == false) { + return false; + } + pipeline_operations_queue.push(ADD); + break; + case APPEND: + if (pipeline_request_couchbase_req.appendRequest( + key, value, 0, 0, 0, collection_name, channel_, server_address_, + selected_bucket_) == false) { + return false; + } + pipeline_operations_queue.push(APPEND); + break; + case PREPEND: + if (pipeline_request_couchbase_req.prependRequest( + key, value, 0, 0, 0, collection_name, channel_, server_address_, + selected_bucket_) == false) { + return false; + } + pipeline_operations_queue.push(PREPEND); + break; + case DELETE: + if (pipeline_request_couchbase_req.deleteRequest( + key, collection_name, channel_, server_address_, + selected_bucket_) == false) { + return false; + } + pipeline_operations_queue.push(DELETE); + break; + default: + DEBUG_PRINT("Invalid operation type for pipelining"); + return false; + } + return true; +} +std::vector CouchbaseOperations::executePipeline() { + std::vector results; + + if (!pipeline_active || pipeline_operations_queue.empty()) { + DEBUG_PRINT("No pipeline active or no operations queued"); + return results; + } + + brpc::Controller cntl; + channel_->CallMethod(NULL, &cntl, &pipeline_request_couchbase_req, + &pipeline_response_couchbase_resp, NULL); + + if (cntl.Failed()) { + DEBUG_PRINT("Pipeline execution failed: " << cntl.ErrorText()); + // Create failure results for all operations + size_t op_count = pipeline_operations_queue.size(); + results.reserve(op_count); + + CouchbaseOperations::Result failure_result; + failure_result.success = false; + failure_result.error_message = cntl.ErrorText(); + + for (size_t i = 0; i < op_count; ++i) { + results.push_back(failure_result); + } + + clearPipeline(); + return results; + } + + // Process each operation in the order they were added + CouchbaseOperations::CouchbaseResponse* response = + &pipeline_response_couchbase_resp; + while (!pipeline_operations_queue.empty()) { + CouchbaseOperations::Result result; + operation_type op_type = pipeline_operations_queue.front(); + pipeline_operations_queue.pop(); + switch (op_type) { + case GET: { + std::string value; + uint32_t flags = 0; + uint64_t cas = 0; + if (response->popGet(&value, &flags, &cas) == false) { + result.success = false; + result.value = ""; + result.error_message = response->lastError(); + result.status_code = response->_status_code; + } else { + result.success = true; + result.value = value; + } + results.push_back(result); + break; + } + case UPSERT: { + if (response->popUpsert(NULL) == false) { + result.success = false; + result.value = ""; + result.error_message = response->lastError(); + result.status_code = response->_status_code; + } else { + result.success = true; + result.value = ""; + } + results.push_back(result); + break; + } + case ADD: { + if (response->popAdd(NULL) == false) { + result.success = false; + result.value = ""; + result.error_message = response->lastError(); + result.status_code = response->_status_code; + } else { + result.success = true; + result.value = ""; + } + results.push_back(result); + break; + } + case APPEND: { + uint64_t cas_value; + if (response->popAppend(&cas_value) == false) { + result.success = false; + result.value = ""; + result.error_message = response->lastError(); + result.status_code = response->_status_code; + } else { + result.success = true; + result.value = ""; + } + results.push_back(result); + break; + } + case PREPEND: { + uint64_t cas_value; + if (response->popPrepend(&cas_value) == false) { + result.success = false; + result.value = ""; + result.error_message = response->lastError(); + result.status_code = response->_status_code; + } else { + result.success = true; + result.value = ""; + } + results.push_back(result); + break; + } + case DELETE: { + if (response->popDelete() == false) { + result.success = false; + result.value = ""; + result.error_message = response->lastError(); + result.status_code = response->_status_code; + } else { + result.success = true; + result.value = ""; + } + results.push_back(result); + break; + } + default: + DEBUG_PRINT("Invalid operation type in pipeline response processing"); + result.success = false; + result.value = ""; + result.error_message = "Invalid operation type"; + results.push_back(result); + break; + } + } + + pipeline_active = false; + pipeline_request_couchbase_req.Clear(); + + return results; +} + +bool CouchbaseOperations::clearPipeline() { + while (!pipeline_operations_queue.empty()) { + pipeline_operations_queue.pop(); + } + pipeline_request_couchbase_req.Clear(); + pipeline_active = false; + return true; +} +} // namespace brpc \ No newline at end of file diff --git a/src/brpc/couchbase.h b/src/brpc/couchbase.h new file mode 100644 index 0000000..3e52f60 --- /dev/null +++ b/src/brpc/couchbase.h @@ -0,0 +1,517 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_COUCHBASE_H +#define BRPC_COUCHBASE_H + +#endif + +#include + +#include +#include +#include +#include +#include +#include + +#include "brpc/nonreflectable_message.h" +#include "brpc/pb_compat.h" +#include "butil/iobuf.h" +#include "butil/strings/string_piece.h" + +namespace brpc { + +// Forward declarations for friend functions +class InputMessageBase; +class Controller; +namespace policy { +void ProcessCouchbaseResponse(InputMessageBase* msg); +void SerializeCouchbaseRequest(butil::IOBuf* buf, Controller* cntl, + const google::protobuf::Message* request); +} // namespace policy + +// Simple C++11 compatible reader-writer lock +class ReaderWriterLock { + private: + std::mutex mutex_; + std::condition_variable reader_cv_; + std::condition_variable writer_cv_; + std::atomic reader_count_; + std::atomic writer_active_; + std::atomic waiting_writers_; + + public: + ReaderWriterLock() + : reader_count_(0), writer_active_(false), waiting_writers_(0) {} + + void lock_shared() { + std::unique_lock lock(mutex_); + reader_cv_.wait(lock, [this] { + return !writer_active_.load() && waiting_writers_.load() == 0; + }); + reader_count_.fetch_add(1); + } + + void unlock_shared() { + reader_count_.fetch_sub(1); + if (reader_count_.load() == 0) { + std::lock_guard lock(mutex_); + writer_cv_.notify_one(); + } + } + + void lock() { + std::unique_lock lock(mutex_); + waiting_writers_.fetch_add(1); + writer_cv_.wait(lock, [this] { + return !writer_active_.load() && reader_count_.load() == 0; + }); + waiting_writers_.fetch_sub(1); + writer_active_.store(true); + } + + void unlock() { + writer_active_.store(false); + std::lock_guard lock(mutex_); + writer_cv_.notify_one(); + reader_cv_.notify_all(); + } +}; + +// RAII helper classes +class SharedLock { + private: + ReaderWriterLock& lock_; + + public: + explicit SharedLock(ReaderWriterLock& lock) : lock_(lock) { + lock_.lock_shared(); + } + ~SharedLock() { lock_.unlock_shared(); } +}; + +class UniqueLock { + private: + ReaderWriterLock& lock_; + + public: + explicit UniqueLock(ReaderWriterLock& lock) : lock_(lock) { lock_.lock(); } + ~UniqueLock() { lock_.unlock(); } +}; + +// manager +class CouchbaseManifestManager { + public: + struct CollectionManifest { + std::string uid; // uid of the manifest, it can be used to track if the manifest + // is updated + std::unordered_map> + scope_to_collection_id_map; // scope -> (collection -> collection_id) + }; + + private: + std::unordered_map> + bucket_to_collection_manifest_; + ReaderWriterLock rw_bucket_to_collection_manifest_mutex_; + + public: + CouchbaseManifestManager() {} + ~CouchbaseManifestManager() { bucket_to_collection_manifest_.clear(); } + bool setBucketToCollectionManifest(std::string server, std::string bucket, + CollectionManifest manifest); + + bool getBucketToCollectionManifest(std::string server, std::string bucket, + CollectionManifest* manifest); + bool getManifestToCollectionId(CollectionManifest* manifest, std::string scope, + std::string collection, uint8_t* collection_id); + + bool jsonToCollectionManifest(const std::string& json, + CollectionManifest* manifest); + bool refreshCollectionManifest( + brpc::Channel* channel, const std::string& server, const std::string& bucket, + std::unordered_map* local_cache = nullptr); +} static common_metadata_tracking; +class CouchbaseOperations { + public: + enum operation_type { + GET = 1, + UPSERT = 2, + ADD = 3, + REPLACE = 4, + APPEND = 5, + PREPEND = 6, + DELETE = 7 + }; + struct Result { + bool success; + std::string error_message; + std::string value; + uint16_t status_code; // 0x00 if success + }; + Result get(const std::string& key, std::string collection_name = "_default"); + Result upsert(const std::string& key, const std::string& value, + std::string collection_name = "_default"); + Result add(const std::string& key, const std::string& value, + std::string collection_name = "_default"); + // Warning: Not tested + // Result replace(const std::string& key, const std::string& value, std::string + // collection_name = "_default"); + Result append(const std::string& key, const std::string& value, + std::string collection_name = "_default"); + Result prepend(const std::string& key, const std::string& value, + std::string collection_name = "_default"); + Result delete_(const std::string& key, std::string collection_name = "_default"); + // Warning: Not tested + // Result Increment(const string& key, uint64_t delta, uint64_t initial_value, + // uint32_t exptime, string collection_name = "_default"); Result + // Decrement(const string& key, uint64_t delta, uint64_t initial_value, + // uint32_t exptime, string collection_name = "_default"); Result Touch(const + // string& key, uint32_t exptime, string collection_name = "_default"); Result + // Flush(uint32_t timeout = 0); + Result version(); + Result authenticateSSL(const std::string& username, const std::string& password, + const std::string& server_address, + const std::string& bucket_name, std::string path_to_cert = ""); + Result authenticate(const std::string& username, const std::string& password, + const std::string& server_address, const std::string& bucket_name); + Result selectBucket(const std::string& bucket_name); + + // Pipeline management + bool beginPipeline(); + bool pipelineRequest(operation_type op_type, const std::string& key, + const std::string& value = "", + std::string collection_name = "_default"); + std::vector executePipeline(); // Return by value instead of pointer + bool clearPipeline(); + + // Pipeline status + bool isPipelineActive() const { return pipeline_active; } + size_t getPipelineSize() const { return pipeline_operations_queue.size(); } + + CouchbaseOperations() + : pipeline_request_couchbase_req(&local_bucket_to_collection_manifest_), + pipeline_active(false) {} + ~CouchbaseOperations() {} + bool getLocalCachedCollectionId(const std::string& bucket, const std::string& scope, + const std::string& collection, uint8_t* coll_id); + + private: + CouchbaseOperations::Result authenticateAll(const std::string& username, + const std::string& password, + const std::string& server_address, + const std::string& bucket_name, + bool enable_ssl, + std::string path_to_cert); + friend void policy::ProcessCouchbaseResponse(InputMessageBase* msg); + friend void policy::SerializeCouchbaseRequest( + butil::IOBuf* buf, Controller* cntl, + const google::protobuf::Message* request); + brpc::Channel* channel_; + std::string server_address_; + std::string selected_bucket_; + + std::unordered_map + local_bucket_to_collection_manifest_; + + public: + // these classes have been made public so that normal user can also create + // advanced bRPC programs as per their requirements. + class CouchbaseRequest : public NonreflectableMessage { + public: + static brpc::CouchbaseManifestManager* metadata_tracking; + int _pipelined_count; + butil::IOBuf _buf; + mutable int _cached_size_; + void sharedCtor(); + void sharedDtor(); + void setCachedSize(int size) const; + bool getOrDelete(uint8_t command, const butil::StringPiece& key, + uint8_t coll_id = 0); + bool counter(uint8_t command, const butil::StringPiece& key, uint64_t delta, + uint64_t initial_value, uint32_t exptime); + + bool store(uint8_t command, const butil::StringPiece& key, + const butil::StringPiece& value, uint32_t flags, + uint32_t exptime, uint64_t cas_value, uint8_t coll_id = 0); + uint32_t hashCrc32(const char* key, size_t key_length); + + public: + std::unordered_map* + local_collection_manifest_cache; + + CouchbaseRequest( + std::unordered_map* + local_cache_reference) + : NonreflectableMessage() { + metadata_tracking = &common_metadata_tracking; + local_collection_manifest_cache = local_cache_reference; + sharedCtor(); + } + CouchbaseRequest() : NonreflectableMessage() { + metadata_tracking = &common_metadata_tracking; + sharedCtor(); + } + ~CouchbaseRequest() { sharedDtor(); } + CouchbaseRequest(const CouchbaseRequest& from) + : NonreflectableMessage() { + metadata_tracking = &common_metadata_tracking; + sharedCtor(); + MergeFrom(from); + } + + inline CouchbaseRequest& operator=(const CouchbaseRequest& from) { + if (this != &from) { + MergeFrom(from); + } + return *this; + } + + bool selectBucketRequest(const butil::StringPiece& bucket_name); + bool authenticateRequest(const butil::StringPiece& username, + const butil::StringPiece& password); + bool helloRequest(); + + // Using GetCollectionManifest instead of fetching collection ID directly + // bool GetCollectionId(const butil::StringPiece& scope_name, + // const butil::StringPiece& collection_name); + + bool getScopeId(const butil::StringPiece& scope_name); + + bool getCollectionManifest(); + + bool getLocalCachedCollectionId(const std::string& bucket, const std::string& scope, + const std::string& collection, uint8_t* coll_id); + + bool getCachedOrFetchCollectionId( + std::string collection_name, uint8_t* coll_id, + brpc::CouchbaseManifestManager* metadata_tracking, + brpc::Channel* channel, const std::string& server, + const std::string& selected_bucket, + std::unordered_map* + local_cache); + + // Collection-aware document operations + bool getRequest(const butil::StringPiece& key, + std::string collection_name = "_default", + brpc::Channel* channel = nullptr, const std::string& server = "", + const std::string& bucket = ""); + + bool upsertRequest(const butil::StringPiece& key, + const butil::StringPiece& value, uint32_t flags, + uint32_t exptime, uint64_t cas_value, + std::string collection_name = "_default", + brpc::Channel* channel = nullptr, + const std::string& server = "", const std::string& bucket = ""); + + bool addRequest(const butil::StringPiece& key, + const butil::StringPiece& value, uint32_t flags, + uint32_t exptime, uint64_t cas_value, + std::string collection_name = "_default", + brpc::Channel* channel = nullptr, const std::string& server = "", + const std::string& bucket = ""); + + bool appendRequest(const butil::StringPiece& key, + const butil::StringPiece& value, uint32_t flags, + uint32_t exptime, uint64_t cas_value, + std::string collection_name = "_default", + brpc::Channel* channel = nullptr, + const std::string& server = "", const std::string& bucket = ""); + + bool prependRequest(const butil::StringPiece& key, + const butil::StringPiece& value, uint32_t flags, + uint32_t exptime, uint64_t cas_value, + std::string collection_name = "_default", + brpc::Channel* channel = nullptr, + const std::string& server = "", const std::string& bucket = ""); + + bool deleteRequest(const butil::StringPiece& key, + std::string collection_name = "_default", + brpc::Channel* channel = nullptr, + const std::string& server = "", const std::string& bucket = ""); + + bool versionRequest(); + + int pipelinedCount() const { return _pipelined_count; } + + butil::IOBuf& rawBuffer() { return _buf; } + const butil::IOBuf& rawBuffer() const { + return _buf; + } // used in couchbase_protocol serialization. + void Swap(CouchbaseRequest* other); + void MergeFrom(const CouchbaseRequest& from) override; + void Clear() override; + bool IsInitialized() const PB_527_OVERRIDE; + }; + + class CouchbaseResponse : public NonreflectableMessage { + public: + static brpc::CouchbaseManifestManager* metadata_tracking; + + private: + std::string _err; + butil::IOBuf _buf; + mutable int _cached_size_; + bool popCounter(uint8_t command, uint64_t* new_value, uint64_t* cas_value); + bool popStore(uint8_t command, uint64_t* cas_value); + + void sharedCtor(); + void sharedDtor(); + void setCachedSize(int size) const; + + public: + uint16_t _status_code; + + CouchbaseResponse() : NonreflectableMessage() { + sharedCtor(); + } + ~CouchbaseResponse() { sharedDtor(); } + CouchbaseResponse(const CouchbaseResponse& from) + : NonreflectableMessage() { + metadata_tracking = &common_metadata_tracking; + sharedCtor(); + MergeFrom(from); + } + inline CouchbaseResponse& operator=(const CouchbaseResponse& from) { + if (this != &from) { + MergeFrom(from); + } + return *this; + } + + // the status codes are from Couchbase Binary Protocol documentation, + // for original reference of status codes visit + // https://github.com/couchbase/kv_engine/blob/master/include/mcbp/protocol/status.h + enum Status { + STATUS_SUCCESS = 0x00, + STATUS_KEY_ENOENT = 0x01, + STATUS_KEY_EEXISTS = 0x02, + STATUS_E2BIG = 0x03, + STATUS_EINVAL = 0x04, + STATUS_NOT_STORED = 0x05, + STATUS_DELTA_BADVAL = 0x06, + STATUS_VBUCKET_BELONGS_TO_ANOTHER_SERVER = 0x07, + STATUS_AUTH_ERROR = 0x20, + STATUS_AUTH_CONTINUE = 0x21, + STATUS_ERANGE = 0x22, + STATUS_ROLLBACK = 0x23, + STATUS_EACCESS = 0x24, + STATUS_NOT_INITIALIZED = 0x25, + STATUS_UNKNOWN_COMMAND = 0x81, + STATUS_ENOMEM = 0x82, + STATUS_NOT_SUPPORTED = 0x83, + STATUS_EINTERNAL = 0x84, + STATUS_EBUSY = 0x85, + STATUS_ETMPFAIL = 0x86, + STATUS_UNKNOWN_COLLECTION = 0x88, + STATUS_NO_COLLECTIONS_MANIFEST = 0x89, + STATUS_CANNOT_APPLY_COLLECTIONS_MANIFEST = 0x8a, + STATUS_COLLECTIONS_MANIFEST_IS_AHEAD = 0x8b, + STATUS_UNKNOWN_SCOPE = 0x8c, + STATUS_DCP_STREAM_ID_INVALID = 0x8d, + STATUS_DURABILITY_INVALID_LEVEL = 0xa0, + STATUS_DURABILITY_IMPOSSIBLE = 0xa1, + STATUS_SYNC_WRITE_IN_PROGRESS = 0xa2, + STATUS_SYNC_WRITE_AMBIGUOUS = 0xa3, + STATUS_SYNC_WRITE_RE_COMMIT_IN_PROGRESS = 0xa4, + STATUS_SUBDOC_PATH_NOT_FOUND = 0xc0, + STATUS_SUBDOC_PATH_MISMATCH = 0xc1, + STATUS_SUBDOC_PATH_EINVAL = 0xc2, + STATUS_SUBDOC_PATH_E2BIG = 0xc3, + STATUS_SUBDOC_DOC_E2DEEP = 0xc4, + STATUS_SUBDOC_VALUE_CANTINSERT = 0xc5, + STATUS_SUBDOC_DOC_NOT_JSON = 0xc6, + STATUS_SUBDOC_NUM_E2BIG = 0xc7, + STATUS_SUBDOC_DELTA_E2BIG = 0xc8, + STATUS_SUBDOC_PATH_EEXISTS = 0xc9, + STATUS_SUBDOC_VALUE_E2DEEP = 0xca, + STATUS_SUBDOC_INVALID_COMBO = 0xcb, + STATUS_SUBDOC_MULTI_PATH_FAILURE = 0xcc, + STATUS_SUBDOC_SUCCESS_DELETED = 0xcd, + STATUS_SUBDOC_XATTR_INVALID_FLAG_COMBO = 0xce, + STATUS_SUBDOC_XATTR_INVALID_KEY_COMBO = 0xcf, + STATUS_SUBDOC_XATTR_UNKNOWN_MACRO = 0xd0, + STATUS_SUBDOC_XATTR_UNKNOWN_VATTR = 0xd1, + STATUS_SUBDOC_XATTR_CANT_MODIFY_VATTR = 0xd2, + STATUS_SUBDOC_MULTI_PATH_FAILURE_DELETED = 0xd3, + STATUS_SUBDOC_INVALID_XATTR_ORDER = 0xd4, + STATUS_SUBDOC_XATTR_UNKNOWN_VATTR_MACRO = 0xd5, + STATUS_SUBDOC_CAN_ONLY_REVIVE_DELETED_DOCUMENTS = 0xd6, + STATUS_SUBDOC_DELETED_DOCUMENT_CANT_HAVE_VALUE = 0xd7, + STATUS_XATTR_EINVAL = 0xe0 + }; + const char* couchbaseBinaryCommandToString(uint8_t cmd); + void MergeFrom(const CouchbaseResponse& from) override; + void Clear() override; + bool IsInitialized() const PB_527_OVERRIDE; + + butil::IOBuf& rawBuffer() { return _buf; } + static const char* statusStr(Status); + + // Helper method to format error messages with status codes + static std::string formatErrorMessage(uint16_t status_code, + const std::string& operation, + const std::string& error_msg = ""); + + // Add methods to handle response parsing + void swap(CouchbaseResponse* other); + bool popGet(butil::IOBuf* value, uint32_t* flags, uint64_t* cas_value); + bool popGet(std::string* value, uint32_t* flags, uint64_t* cas_value); + const std::string& lastError() const { return _err; } + bool popUpsert(uint64_t* cas_value); + bool popAdd(uint64_t* cas_value); + // Warning: Not tested + // bool popReplace(uint64_t* cas_value); + bool popAppend(uint64_t* cas_value); + bool popPrepend(uint64_t* cas_value); + bool popSelectBucket(uint64_t* cas_value); + bool popAuthenticate(uint64_t* cas_value); + bool popHello(uint64_t* cas_value); + + // Collection-related response methods + bool popCollectionId(uint8_t* collection_id); + + bool popManifest(std::string* manifest_json); + + bool popDelete(); + // Warning: Not tested + // bool popFlush(); + // bool popIncrement(uint64_t* new_value, uint64_t* cas_value); + // bool popDecrement(uint64_t* new_value, uint64_t* cas_value); + // bool popTouch(); + bool popVersion(std::string* version); + }; + + friend bool sendRequest(CouchbaseOperations::operation_type op_type, + const std::string& key, const std::string& value, + std::string collection_name, + CouchbaseOperations::Result* result, + brpc::Channel* channel, const std::string& server, + const std::string& bucket, CouchbaseRequest* request, + CouchbaseResponse* response); + + // Pipeline management - per instance + std::queue pipeline_operations_queue; + CouchbaseRequest pipeline_request_couchbase_req; + CouchbaseResponse pipeline_response_couchbase_resp; + bool pipeline_active; +}; + +} // namespace brpc \ No newline at end of file diff --git a/src/brpc/data_factory.h b/src/brpc/data_factory.h new file mode 100644 index 0000000..5733073 --- /dev/null +++ b/src/brpc/data_factory.h @@ -0,0 +1,50 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_DATA_FACTORY_H +#define BRPC_DATA_FACTORY_H + +// To brpc developers: This is a header included by user, don't depend +// on internal structures, use opaque pointers instead. + +namespace brpc { + +// ---- thread safety ---- +// Method implementations of this interface should be thread-safe +class DataFactory { +public: + virtual ~DataFactory() {} + + // Implement this method to create a piece of data + // Returns the data, NULL on error. + virtual void* CreateData() const = 0; + + // Implement this method to destroy data created by Create(). + virtual void DestroyData(void*) const = 0; + + // Overwrite this method to reset the data before reuse. Nothing done by default. + // Returns + // true: the data can be kept for future reuse + // false: the data is improper to be reused and should be sent to + // DestroyData() immediately after calling this method + virtual bool ResetData(void*) const { return true; } +}; + +} // namespace brpc + +#endif // BRPC_DATA_FACTORY_H diff --git a/src/brpc/describable.h b/src/brpc/describable.h new file mode 100644 index 0000000..9cb7b75 --- /dev/null +++ b/src/brpc/describable.h @@ -0,0 +1,114 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_DESCRIBABLE_H +#define BRPC_DESCRIBABLE_H + +#include +#include "butil/macros.h" +#include "butil/class_name.h" + +namespace brpc { + +struct DescribeOptions { + DescribeOptions() + : verbose(true) + , use_html(false) + {} + + bool verbose; + bool use_html; +}; + +class Describable { +public: + virtual ~Describable() {} + virtual void Describe(std::ostream& os, const DescribeOptions&) const { + os << butil::class_name_str(*this); + } +}; + +class NonConstDescribable { +public: + virtual ~NonConstDescribable() {} + virtual void Describe(std::ostream& os, const DescribeOptions&) { + os << butil::class_name_str(*this); + } +}; + +inline std::ostream& operator<<(std::ostream& os, const Describable& obj) { + DescribeOptions options; + options.verbose = false; + obj.Describe(os, options); + return os; +} + +inline std::ostream& operator<<(std::ostream& os, + NonConstDescribable& obj) { + DescribeOptions options; + options.verbose = false; + obj.Describe(os, options); + return os; +} + +// Append `indent' spaces after each newline. +// Example: +// IndentingOStream os1(std::cout, 2); +// IndentingOStream os2(os1, 2); +// std::cout << "begin1\nhello" << std::endl << "world\nend1" << std::endl; +// os1 << "begin2\nhello" << std::endl << "world\nend2" << std::endl; +// os2 << "begin3\nhello" << std::endl << "world\nend3" << std::endl; +// Output: +// begin1 +// hello +// world +// end1 +// begin2 +// hello +// world +// end2 +// begin3 +// hello +// world +// end3 +class IndentingOStream : virtual private std::streambuf, public std::ostream { +public: + IndentingOStream(std::ostream& dest, int indent) + : std::ostream(this) + , _dest(dest.rdbuf()) + , _is_at_start_of_line(false) + , _indent(indent, ' ') + {} +protected: + int overflow(int ch) override { + if (_is_at_start_of_line && ch != '\n' ) { + _dest->sputn(_indent.data(), _indent.size()); + } + _is_at_start_of_line = (ch == '\n'); + return _dest->sputc(ch); + } +private: + DISALLOW_COPY_AND_ASSIGN(IndentingOStream); + std::streambuf* _dest; + bool _is_at_start_of_line; + std::string _indent; +}; + +} // namespace brpc + +#endif // BRPC_DESCRIBABLE_H diff --git a/src/brpc/destroyable.h b/src/brpc/destroyable.h new file mode 100644 index 0000000..d37f45d --- /dev/null +++ b/src/brpc/destroyable.h @@ -0,0 +1,49 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_DESTROYABLE_H +#define BRPC_DESTROYABLE_H + +#include "butil/unique_ptr.h" // std::unique_ptr + + +namespace brpc { + +class Destroyable { +public: + virtual ~Destroyable() {} + virtual void Destroy() = 0; +}; + +namespace detail { +template struct Destroyer { + void operator()(T* obj) const { if (obj) { obj->Destroy(); } } +}; +} + +// A special unique_ptr that calls "obj->Destroy()" instead of "delete obj". +template +struct DestroyingPtr : public std::unique_ptr > { + DestroyingPtr() {} + DestroyingPtr(T* p) : std::unique_ptr >(p) {} +}; + +} // namespace brpc + + +#endif // BRPC_DESTROYABLE_H diff --git a/src/brpc/details/controller_private_accessor.h b/src/brpc/details/controller_private_accessor.h new file mode 100644 index 0000000..07a071b --- /dev/null +++ b/src/brpc/details/controller_private_accessor.h @@ -0,0 +1,200 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_CONTROLLER_PRIVATE_ACCESSOR_H +#define BRPC_CONTROLLER_PRIVATE_ACCESSOR_H + +// This is an rpc-internal file. + +#include "brpc/socket.h" +#include "brpc/controller.h" +#include "brpc/stream.h" + +namespace google { +namespace protobuf { +class Message; +} +} + +namespace brpc { + +class Span; + +class AuthContext; + +// A wrapper to access some private methods/fields of `Controller' +// This is supposed to be used by internal RPC protocols ONLY +class ControllerPrivateAccessor { +public: + explicit ControllerPrivateAccessor(Controller* cntl) { + _cntl = cntl; + } + + void OnResponse(CallId id, int saved_error) { + const Controller::CompletionInfo info = { id, true }; + _cntl->OnVersionedRPCReturned(info, false, saved_error); + } + + ControllerPrivateAccessor &set_peer_id(SocketId peer_id) { + _cntl->_current_call.peer_id = peer_id; + return *this; + } + + Socket* get_sending_socket() { + return _cntl->_current_call.sending_sock.get(); + } + + int64_t real_timeout_ms() { + return _cntl->_real_timeout_ms; + } + + void move_in_server_receiving_sock(SocketUniquePtr& ptr) { + CHECK(_cntl->_current_call.sending_sock == NULL); + _cntl->_current_call.sending_sock.reset(ptr.release()); + } + + StreamUserData* get_stream_user_data() { + return _cntl->_current_call.stream_user_data; + } + + ControllerPrivateAccessor &set_security_mode(bool security_mode) { + _cntl->set_flag(Controller::FLAGS_SECURITY_MODE, security_mode); + return *this; + } + + ControllerPrivateAccessor &set_remote_side(const butil::EndPoint& pt) { + _cntl->_remote_side = pt; + return *this; + } + + ControllerPrivateAccessor &set_local_side(const butil::EndPoint& pt) { + _cntl->_local_side = pt; + return *this; + } + + ControllerPrivateAccessor &set_auth_context(const AuthContext* ctx) { + _cntl->set_auth_context(ctx); + return *this; + } + + // Overloaded set_span methods to support both shared_ptr and raw pointer + ControllerPrivateAccessor &set_span(const std::shared_ptr& span); + ControllerPrivateAccessor &set_span(Span* span); + + ControllerPrivateAccessor &set_request_protocol(ProtocolType protocol) { + _cntl->_request_protocol = protocol; + return *this; + } + + std::shared_ptr span() const; + + uint32_t pipelined_count() const { return _cntl->_pipelined_count; } + void set_pipelined_count(uint32_t count) { _cntl->_pipelined_count = count; } + + // The mysql protocol stores its statement type (MYSQL_NORMAL_STATEMENT / + // MYSQL_PREPARED_STATEMENT) in the pipelined_count slot. + void set_mysql_statement_type(uint32_t type) { set_pipelined_count(type); } + + ControllerPrivateAccessor& set_server(const Server* server) { + _cntl->_server = server; + return *this; + } + + // Pass the owership of |settings| to _cntl, while is going to be + // destroyed in Controller::Reset() + void set_remote_stream_settings(StreamSettings *settings) { + _cntl->_remote_stream_settings = settings; + } + StreamSettings* remote_stream_settings() { + return _cntl->_remote_stream_settings; + } + + StreamIds request_streams() { return _cntl->_request_streams; } + StreamIds response_streams() { return _cntl->_response_streams; } + + void set_method(const google::protobuf::MethodDescriptor* method) + { _cntl->_method = method; } + + void set_readable_progressive_attachment(ReadableProgressiveAttachment* s) + { _cntl->_rpa.reset(s); } + + void set_auth_flags(uint32_t auth_flags) { + _cntl->_auth_flags = auth_flags; + } + + void clear_auth_flags() { _cntl->_auth_flags = 0; } + + // Set how the sending socket is reserved after the RPC (mysql transactions). + void set_bind_sock_action(BindSockAction action) { _cntl->set_bind_sock_action(action); } + // Transfer ownership of the reserved socket to `ptr`. + void get_bind_sock(SocketUniquePtr* ptr) { + if (_cntl->_bind_sock) { + _cntl->_bind_sock->ReAddress(ptr); + } + } + // Reuse an externally-reserved socket for the next RPC. + void use_bind_sock(SocketId sock_id) { + _cntl->set_bind_sock_action(BIND_SOCK_USE); + Socket::Address(sock_id, &_cntl->_bind_sock); + } + void set_session_data(void* d) { _cntl->_session_data = d; } + void* session_data() const { return _cntl->_session_data; } + + std::string& protocol_param() { return _cntl->protocol_param(); } + const std::string& protocol_param() const { return _cntl->protocol_param(); } + + // Note: This function can only be called in server side. The deadline of client + // side is properly set in the RPC sending path. + void set_deadline_us(int64_t deadline_us) { _cntl->_deadline_us = deadline_us; } + + ControllerPrivateAccessor& set_begin_time_us(int64_t begin_time_us) { + _cntl->_begin_time_us = begin_time_us; + _cntl->_end_time_us = UNSET_MAGIC_NUM; + return *this; + } + + ControllerPrivateAccessor& set_health_check_call() { + _cntl->add_flag(Controller::FLAGS_HEALTH_CHECK_CALL); + return *this; + } + + void set_checksum_value(const char* c, size_t size) { + _cntl->_checksum_value.assign(c, size); + } + + void set_checksum_value(const std::string& c) { + _cntl->_checksum_value = c; + } + + const std::string& checksum_value() const { return _cntl->_checksum_value; } + +private: + Controller* _cntl; +}; + +// Inherit this class to intercept Controller::IssueRPC. This is an internal +// utility only useable by brpc developers. +class RPCSender { +public: + virtual ~RPCSender() {} + virtual int IssueRPC(int64_t start_realtime_us) = 0; +}; + +} // namespace brpc + + +#endif // BRPC_CONTROLLER_PRIVATE_ACCESSOR_H diff --git a/src/brpc/details/has_epollrdhup.cpp b/src/brpc/details/has_epollrdhup.cpp new file mode 100644 index 0000000..dd085ad --- /dev/null +++ b/src/brpc/details/has_epollrdhup.cpp @@ -0,0 +1,71 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "butil/build_config.h" + +#if defined(OS_LINUX) + +#include // epoll_create +#include // socketpair +#include // ^ +#include "butil/fd_guard.h" // fd_guard +#include "brpc/details/has_epollrdhup.h" + +#ifndef EPOLLRDHUP +#define EPOLLRDHUP 0x2000 +#endif + +namespace brpc { + +static unsigned int check_epollrdhup() { + butil::fd_guard epfd(epoll_create(16)); + if (epfd < 0) { + return 0; + } + butil::fd_guard fds[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, (int*)fds) < 0) { + return 0; + } + epoll_event evt = { static_cast(EPOLLIN | EPOLLRDHUP | EPOLLET), + { NULL }}; + if (epoll_ctl(epfd, EPOLL_CTL_ADD, fds[0], &evt) < 0) { + return 0; + } + if (close(fds[1].release()) < 0) { + return 0; + } + epoll_event e; + int n; + while ((n = epoll_wait(epfd, &e, 1, -1)) == 0); + if (n < 0) { + return 0; + } + return (e.events & EPOLLRDHUP) ? EPOLLRDHUP : static_cast(0); +} + +extern const unsigned int has_epollrdhup = check_epollrdhup(); + +} // namespace brpc + +#else + +namespace brpc { +extern const unsigned int has_epollrdhup = false; +} + +#endif // defined(OS_LINUX) diff --git a/src/brpc/details/has_epollrdhup.h b/src/brpc/details/has_epollrdhup.h new file mode 100644 index 0000000..059acd4 --- /dev/null +++ b/src/brpc/details/has_epollrdhup.h @@ -0,0 +1,34 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_HAS_EPOLLRDHUP_H +#define BRPC_HAS_EPOLLRDHUP_H + + +namespace brpc { + +// Check if the kernel supports EPOLLRDHUP which is added in Linux 2.6.17 +// This flag is useful in Edge Triggered mode. Without the flag user has +// to call an additional read() even if return value(positive) is less +// than given `count', otherwise return value=0(indicating EOF) may be lost. +extern const unsigned int has_epollrdhup; + +} // namespace brpc + + +#endif // BRPC_HAS_EPOLLRDHUP_H diff --git a/src/brpc/details/health_check.cpp b/src/brpc/details/health_check.cpp new file mode 100644 index 0000000..7cf4e32 --- /dev/null +++ b/src/brpc/details/health_check.cpp @@ -0,0 +1,235 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/details/health_check.h" +#include "brpc/socket.h" +#include "brpc/channel.h" +#include "brpc/controller.h" +#include "brpc/details/controller_private_accessor.h" +#include "brpc/global.h" +#include "brpc/log.h" +#include "bthread/unstable.h" +#include "bthread/bthread.h" + +namespace brpc { + +// Declared at socket.cpp +extern SocketVarsCollector* g_vars; + +class HealthCheckChannel : public brpc::Channel { +public: + HealthCheckChannel() {} + ~HealthCheckChannel() {} + + int Init(SocketId id, const ChannelOptions* options); +}; + +int HealthCheckChannel::Init(SocketId id, const ChannelOptions* options) { + brpc::GlobalInitializeOrDie(); + if (InitChannelOptions(options) != 0) { + return -1; + } + _server_id = id; + return 0; +} + +class OnAppHealthCheckDone : public google::protobuf::Closure { +public: + virtual void Run(); + + HealthCheckChannel channel; + brpc::Controller cntl; + SocketId id; + int64_t interval_s; + int64_t last_check_time_ms; + HealthCheckOption hc_option; +}; + +class HealthCheckManager { +public: + static void StartCheck(SocketId id, int64_t check_interval_s); + static void* AppCheck(void* arg); +}; + +void HealthCheckManager::StartCheck(SocketId id, int64_t check_interval_s) { + SocketUniquePtr ptr; + const int rc = Socket::AddressFailedAsWell(id, &ptr); + if (rc < 0) { + RPC_VLOG << "SocketId=" << id + << " was abandoned during health checking"; + return; + } + LOG(INFO) << "Checking path=" << ptr->remote_side() << ptr->health_check_path(); + OnAppHealthCheckDone* done = new OnAppHealthCheckDone; + done->id = id; + done->interval_s = check_interval_s; + done->hc_option = ptr->_hc_option; + brpc::ChannelOptions options; + options.protocol = PROTOCOL_HTTP; + options.max_retry = 0; + options.timeout_ms = + std::min((int64_t)(done->hc_option.health_check_timeout_ms), check_interval_s * 1000); + if (done->channel.Init(id, &options) != 0) { + LOG(WARNING) << "Fail to init health check channel to SocketId=" << id; + ptr->_ninflight_app_health_check.fetch_sub( + 1, butil::memory_order_relaxed); + delete done; + return; + } + AppCheck(done); +} + +void* HealthCheckManager::AppCheck(void* arg) { + OnAppHealthCheckDone* done = static_cast(arg); + done->cntl.Reset(); + done->cntl.http_request().uri() = done->hc_option.health_check_path; + ControllerPrivateAccessor(&done->cntl).set_health_check_call(); + done->last_check_time_ms = butil::cpuwide_time_ms(); + done->channel.CallMethod(NULL, &done->cntl, NULL, NULL, done); + return NULL; +} + +void OnAppHealthCheckDone::Run() { + std::unique_ptr self_guard(this); + SocketUniquePtr ptr; + const int rc = Socket::AddressFailedAsWell(id, &ptr); + if (rc < 0) { + RPC_VLOG << "SocketId=" << id + << " was abandoned during health checking"; + return; + } + if (!cntl.Failed() || ptr->Failed()) { + LOG_IF(INFO, !cntl.Failed()) << "Succeeded to call " + << ptr->remote_side() << hc_option.health_check_path; + // if ptr->Failed(), previous SetFailed would trigger next round + // of hc, just return here. + ptr->_ninflight_app_health_check.fetch_sub( + 1, butil::memory_order_relaxed); + return; + } + RPC_VLOG << "Fail to check path=" << hc_option.health_check_path + << ", " << cntl.ErrorText(); + + int64_t sleep_time_ms = + last_check_time_ms + interval_s * 1000 - butil::cpuwide_time_ms(); + if (sleep_time_ms > 0) { + // TODO(zhujiashun): we need to handle the case when timer fails + // and bthread_usleep returns immediately. In most situations, + // the possibility of this case is quite small, so currently we + // just keep sending the hc call. + bthread_usleep(sleep_time_ms * 1000); + } + HealthCheckManager::AppCheck(self_guard.release()); +} + +class HealthCheckTask : public PeriodicTask { +public: + explicit HealthCheckTask(SocketId id); + bool OnTriggeringTask(timespec* next_abstime) override; + void OnDestroyingTask() override; + +private: + SocketId _id; + bool _first_time; +}; + +HealthCheckTask::HealthCheckTask(SocketId id) + : _id(id) + , _first_time(true) {} + +bool HealthCheckTask::OnTriggeringTask(timespec* next_abstime) { + SocketUniquePtr ptr; + const int rc = Socket::AddressFailedAsWell(_id, &ptr); + CHECK(rc != 0); + if (rc < 0) { + RPC_VLOG << "SocketId=" << _id + << " was abandoned before health checking"; + return false; + } + // Note: Making a Socket re-addressable is hard. An alternative is + // creating another Socket with selected internal fields to replace + // failed Socket. Although it avoids concurrent issues with in-place + // revive, it changes SocketId: many code need to watch SocketId + // and update on change, which is impractical. Another issue with + // this method is that it has to move "selected internal fields" + // which may be accessed in parallel, not trivial to be moved. + // Finally we choose a simple-enough solution: wait until the + // reference count hits `expected_nref', which basically means no + // one is addressing the Socket(except here). Because the Socket + // is not addressable, the reference count will not increase + // again. This solution is not perfect because the `expected_nref' + // is implementation specific. In our case, one reference comes + // from someone who holds a reference related to health checking, + // e.g. SocketMapInsert(socket_map.cpp) or ChannelBalancer::AddChannel + // (selective_channel.cpp), one reference is here. Although WaitAndReset() + // could hang when someone is addressing the failed Socket forever + // (also indicating bug), this is not an issue in current code. + if (_first_time) { // Only check at first time. + _first_time = false; + if (ptr->WaitAndReset(2/*note*/) != 0) { + LOG(INFO) << "Cancel checking " << *ptr; + return false; + } + } + + // g_vars must not be NULL because it is newed at the creation of + // first Socket. When g_vars is used, the socket is at health-checking + // state, which means the socket must be created and then g_vars can + // not be NULL. + g_vars->nhealthcheck << 1; + int hc = 0; + if (ptr->_user) { + hc = ptr->_user->CheckHealth(ptr.get()); + } else { + hc = ptr->CheckHealth(); + } + if (hc == 0) { + if (!ptr->health_check_path().empty()) { + ptr->_ninflight_app_health_check.fetch_add( + 1, butil::memory_order_relaxed); + } + // See comments above. + ptr->Revive(2/*note*/); + ptr->_hc_count = 0; + if (!ptr->health_check_path().empty()) { + HealthCheckManager::StartCheck(_id, ptr->_health_check_interval_s); + } + return false; + } else if (hc == ESTOP) { + LOG(INFO) << "Cancel checking " << *ptr; + return false; + } else { + RPC_VLOG << "Fail to check " << *ptr + << ", error code=" << errno + << ": " << berror(); + } + ++ ptr->_hc_count; + *next_abstime = butil::seconds_from_now(ptr->_health_check_interval_s); + return true; +} + +void HealthCheckTask::OnDestroyingTask() { + delete this; +} + +void StartHealthCheck(SocketId id, int64_t delay_ms) { + PeriodicTaskManager::StartTaskAt(new HealthCheckTask(id), + butil::milliseconds_from_now(delay_ms)); +} + +} // namespace brpc diff --git a/src/brpc/details/health_check.h b/src/brpc/details/health_check.h new file mode 100644 index 0000000..5a20756 --- /dev/null +++ b/src/brpc/details/health_check.h @@ -0,0 +1,36 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef _HEALTH_CHECK_H +#define _HEALTH_CHECK_H + +#include "brpc/socket_id.h" +#include "brpc/periodic_task.h" +#include "bvar/bvar.h" +#include "brpc/socket.h" + +namespace brpc { + +// Start health check for socket id after delay_ms. +// If delay_ms <= 0, HealthCheck would be started +// immediately. +void StartHealthCheck(SocketId id, int64_t delay_ms); + +} // namespace brpc + +#endif diff --git a/src/brpc/details/hpack-static-table.h b/src/brpc/details/hpack-static-table.h new file mode 100644 index 0000000..08abc3d --- /dev/null +++ b/src/brpc/details/hpack-static-table.h @@ -0,0 +1,366 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Intentionally no header guard + + + +namespace brpc { + +struct HeaderCstr { + const char* name; + const char* value; +}; + +static const HeaderCstr s_static_headers[] = { + {":authority", ""}, + {":method", "GET"}, + {":method", "POST"}, + {":path", "/"}, + {":path", "/index.html"}, + {":scheme", "http"}, + {":scheme", "https"}, + {":status", "200"}, + {":status", "204"}, + {":status", "206"}, + {":status", "304"}, + {":status", "400"}, + {":status", "404"}, + {":status", "500"}, + {"accept-charset", ""}, + {"accept-encoding", "gzip, deflate"}, + {"accept-language", ""}, + {"accept-ranges", ""}, + {"accept", ""}, + {"access-control-allow-origin", ""}, + {"age", ""}, + {"allow", ""}, + {"authorization", ""}, + {"cache-control", ""}, + {"content-disposition", ""}, + {"content-encoding", ""}, + {"content-language", ""}, + {"content-length", ""}, + {"content-location", ""}, + {"content-range", ""}, + {"content-type", ""}, + {"cookie", ""}, + {"date", ""}, + {"etag", ""}, + {"expect", ""}, + {"expires", ""}, + {"from", ""}, + {"host", ""}, + {"if-match", ""}, + {"if-modified-since", ""}, + {"if-none-match", ""}, + {"if-range", ""}, + {"if-unmodified-since", ""}, + {"last-modified", ""}, + {"link", ""}, + {"location", ""}, + {"max-forwards", ""}, + {"proxy-authenticate", ""}, + {"proxy-authorization", ""}, + {"range", ""}, + {"referer", ""}, + {"refresh", ""}, + {"retry-after", ""}, + {"server", ""}, + {"set-cookie", ""}, + {"strict-transport-security", ""}, + {"transfer-encoding", ""}, + {"user-agent", ""}, + {"vary", ""}, + {"via", ""}, + {"www-authenticate", ""} +}; + +BAIDU_CASSERT(ARRAY_SIZE(s_static_headers) == 61u, + number_of_entries_in_static_table_must_be_61); + +struct HuffmanCode { + uint32_t code; + uint32_t bit_len; +}; + +static const HuffmanCode s_huffman_table[] = { + {0x1ff8, 13}, + {0x7fffd8, 23}, + {0xfffffe2, 28}, + {0xfffffe3, 28}, + {0xfffffe4, 28}, + {0xfffffe5, 28}, + {0xfffffe6, 28}, + {0xfffffe7, 28}, + {0xfffffe8, 28}, + {0xffffea, 24}, + {0x3ffffffc, 30}, + {0xfffffe9, 28}, + {0xfffffea, 28}, + {0x3ffffffd, 30}, + {0xfffffeb, 28}, + {0xfffffec, 28}, + {0xfffffed, 28}, + {0xfffffee, 28}, + {0xfffffef, 28}, + {0xffffff0, 28}, + {0xffffff1, 28}, + {0xffffff2, 28}, + {0x3ffffffe, 30}, + {0xffffff3, 28}, + {0xffffff4, 28}, + {0xffffff5, 28}, + {0xffffff6, 28}, + {0xffffff7, 28}, + {0xffffff8, 28}, + {0xffffff9, 28}, + {0xffffffa, 28}, + {0xffffffb, 28}, + {0x14, 6}, + {0x3f8, 10}, + {0x3f9, 10}, + {0xffa, 12}, + {0x1ff9, 13}, + {0x15, 6}, + {0xf8, 8}, + {0x7fa, 11}, + {0x3fa, 10}, + {0x3fb, 10}, + {0xf9, 8}, + {0x7fb, 11}, + {0xfa, 8}, + {0x16, 6}, + {0x17, 6}, + {0x18, 6}, + {0x0, 5}, + {0x1, 5}, + {0x2, 5}, + {0x19, 6}, + {0x1a, 6}, + {0x1b, 6}, + {0x1c, 6}, + {0x1d, 6}, + {0x1e, 6}, + {0x1f, 6}, + {0x5c, 7}, + {0xfb, 8}, + {0x7ffc, 15}, + {0x20, 6}, + {0xffb, 12}, + {0x3fc, 10}, + {0x1ffa, 13}, + {0x21, 6}, + {0x5d, 7}, + {0x5e, 7}, + {0x5f, 7}, + {0x60, 7}, + {0x61, 7}, + {0x62, 7}, + {0x63, 7}, + {0x64, 7}, + {0x65, 7}, + {0x66, 7}, + {0x67, 7}, + {0x68, 7}, + {0x69, 7}, + {0x6a, 7}, + {0x6b, 7}, + {0x6c, 7}, + {0x6d, 7}, + {0x6e, 7}, + {0x6f, 7}, + {0x70, 7}, + {0x71, 7}, + {0x72, 7}, + {0xfc, 8}, + {0x73, 7}, + {0xfd, 8}, + {0x1ffb, 13}, + {0x7fff0, 19}, + {0x1ffc, 13}, + {0x3ffc, 14}, + {0x22, 6}, + {0x7ffd, 15}, + {0x3, 5}, + {0x23, 6}, + {0x4, 5}, + {0x24, 6}, + {0x5, 5}, + {0x25, 6}, + {0x26, 6}, + {0x27, 6}, + {0x6, 5}, + {0x74, 7}, + {0x75, 7}, + {0x28, 6}, + {0x29, 6}, + {0x2a, 6}, + {0x7, 5}, + {0x2b, 6}, + {0x76, 7}, + {0x2c, 6}, + {0x8, 5}, + {0x9, 5}, + {0x2d, 6}, + {0x77, 7}, + {0x78, 7}, + {0x79, 7}, + {0x7a, 7}, + {0x7b, 7}, + {0x7ffe, 15}, + {0x7fc, 11}, + {0x3ffd, 14}, + {0x1ffd, 13}, + {0xffffffc, 28}, + {0xfffe6, 20}, + {0x3fffd2, 22}, + {0xfffe7, 20}, + {0xfffe8, 20}, + {0x3fffd3, 22}, + {0x3fffd4, 22}, + {0x3fffd5, 22}, + {0x7fffd9, 23}, + {0x3fffd6, 22}, + {0x7fffda, 23}, + {0x7fffdb, 23}, + {0x7fffdc, 23}, + {0x7fffdd, 23}, + {0x7fffde, 23}, + {0xffffeb, 24}, + {0x7fffdf, 23}, + {0xffffec, 24}, + {0xffffed, 24}, + {0x3fffd7, 22}, + {0x7fffe0, 23}, + {0xffffee, 24}, + {0x7fffe1, 23}, + {0x7fffe2, 23}, + {0x7fffe3, 23}, + {0x7fffe4, 23}, + {0x1fffdc, 21}, + {0x3fffd8, 22}, + {0x7fffe5, 23}, + {0x3fffd9, 22}, + {0x7fffe6, 23}, + {0x7fffe7, 23}, + {0xffffef, 24}, + {0x3fffda, 22}, + {0x1fffdd, 21}, + {0xfffe9, 20}, + {0x3fffdb, 22}, + {0x3fffdc, 22}, + {0x7fffe8, 23}, + {0x7fffe9, 23}, + {0x1fffde, 21}, + {0x7fffea, 23}, + {0x3fffdd, 22}, + {0x3fffde, 22}, + {0xfffff0, 24}, + {0x1fffdf, 21}, + {0x3fffdf, 22}, + {0x7fffeb, 23}, + {0x7fffec, 23}, + {0x1fffe0, 21}, + {0x1fffe1, 21}, + {0x3fffe0, 22}, + {0x1fffe2, 21}, + {0x7fffed, 23}, + {0x3fffe1, 22}, + {0x7fffee, 23}, + {0x7fffef, 23}, + {0xfffea, 20}, + {0x3fffe2, 22}, + {0x3fffe3, 22}, + {0x3fffe4, 22}, + {0x7ffff0, 23}, + {0x3fffe5, 22}, + {0x3fffe6, 22}, + {0x7ffff1, 23}, + {0x3ffffe0, 26}, + {0x3ffffe1, 26}, + {0xfffeb, 20}, + {0x7fff1, 19}, + {0x3fffe7, 22}, + {0x7ffff2, 23}, + {0x3fffe8, 22}, + {0x1ffffec, 25}, + {0x3ffffe2, 26}, + {0x3ffffe3, 26}, + {0x3ffffe4, 26}, + {0x7ffffde, 27}, + {0x7ffffdf, 27}, + {0x3ffffe5, 26}, + {0xfffff1, 24}, + {0x1ffffed, 25}, + {0x7fff2, 19}, + {0x1fffe3, 21}, + {0x3ffffe6, 26}, + {0x7ffffe0, 27}, + {0x7ffffe1, 27}, + {0x3ffffe7, 26}, + {0x7ffffe2, 27}, + {0xfffff2, 24}, + {0x1fffe4, 21}, + {0x1fffe5, 21}, + {0x3ffffe8, 26}, + {0x3ffffe9, 26}, + {0xffffffd, 28}, + {0x7ffffe3, 27}, + {0x7ffffe4, 27}, + {0x7ffffe5, 27}, + {0xfffec, 20}, + {0xfffff3, 24}, + {0xfffed, 20}, + {0x1fffe6, 21}, + {0x3fffe9, 22}, + {0x1fffe7, 21}, + {0x1fffe8, 21}, + {0x7ffff3, 23}, + {0x3fffea, 22}, + {0x3fffeb, 22}, + {0x1ffffee, 25}, + {0x1ffffef, 25}, + {0xfffff4, 24}, + {0xfffff5, 24}, + {0x3ffffea, 26}, + {0x7ffff4, 23}, + {0x3ffffeb, 26}, + {0x7ffffe6, 27}, + {0x3ffffec, 26}, + {0x3ffffed, 26}, + {0x7ffffe7, 27}, + {0x7ffffe8, 27}, + {0x7ffffe9, 27}, + {0x7ffffea, 27}, + {0x7ffffeb, 27}, + {0xffffffe, 28}, + {0x7ffffec, 27}, + {0x7ffffed, 27}, + {0x7ffffee, 27}, + {0x7ffffef, 27}, + {0x7fffff0, 27}, + {0x3ffffee, 26}, + {0x3fffffff, 30}, +}; + +BAIDU_CASSERT(ARRAY_SIZE(s_huffman_table) == 257u, + sizeof_s_huffman_table_must_be_257); + +const static int32_t HPACK_HUFFMAN_EOS = 256; + +} // namespace brpc diff --git a/src/brpc/details/hpack.cpp b/src/brpc/details/hpack.cpp new file mode 100644 index 0000000..e4e7890 --- /dev/null +++ b/src/brpc/details/hpack.cpp @@ -0,0 +1,887 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/details/hpack.h" + +#include // std::numeric_limits +#include +#include "butil/containers/bounded_queue.h" // butil::BoundedQueue +#include "butil/containers/flat_map.h" // butil::FlatMap +#include "butil/containers/case_ignored_flat_map.h" // butil::FlatMap +#include "brpc/details/hpack-static-table.h" // s_static_headers + + +namespace brpc { + +// Options to initialize a IndexTable +struct IndexTableOptions { + size_t max_size; + uint64_t start_index; + const HeaderCstr* static_table; + size_t static_table_size; + bool need_indexes; + IndexTableOptions() + : max_size(0) + , start_index(0) + , static_table(NULL) + , static_table_size(0) + , need_indexes(false) + {} +}; + +struct HeaderAndHashCode { + size_t hash_code; + const HPacker::Header* header; +}; + +struct HeaderHasher { + size_t operator()(const HPacker::Header& h) const { + return butil::CaseIgnoredHasher()(h.name) + * 101 + butil::DefaultHasher()(h.value); + } + size_t operator()(const HeaderAndHashCode& h) const { + return h.hash_code; + } +}; + +struct HeaderEqualTo { + bool operator()(const HPacker::Header& h1, const HPacker::Header& h2) const { + return butil::CaseIgnoredEqual()(h1.name, h2.name) + && butil::DefaultEqualTo()(h1.value, h2.value); + } + bool operator()(const HPacker::Header& h1, const HeaderAndHashCode& h2) const { + return operator()(h1, *h2.header); + } +}; + +class BAIDU_CACHELINE_ALIGNMENT IndexTable { +DISALLOW_COPY_AND_ASSIGN(IndexTable); + typedef HPacker::Header Header; +public: + IndexTable() + : _start_index(0) + , _add_times(0) + , _size(0) + {} + ~IndexTable() {} + int Init(const IndexTableOptions& options); + + const Header* HeaderAt(int index) const { + if (BAIDU_UNLIKELY(index < _start_index)) { + return NULL; + } + return _header_queue.bottom(index - _start_index); + }; + + int GetIndexOfHeader(const HeaderAndHashCode& h) { + DCHECK(_need_indexes); + const uint64_t* v = _header_index.seek(h); + if (!v) { + return 0; + } + DCHECK_LE(_add_times - *v, _header_queue.size()); + // The latest added entry has the smallest index + return _start_index + (_add_times - *v) - 1; + } + + int GetIndexOfName(const std::string& name) { + DCHECK(_need_indexes); + const uint64_t* v = _name_index.seek(name); + if (!v) { + return 0; + } + DCHECK_LE(_add_times - *v, _header_queue.size()); + // The latest added entry has the smallest index + return _start_index + (_add_times - *v) - 1; + } + + bool empty() const { return _size == 0; } + int start_index() const { return _start_index; } + int end_index() const { return start_index() + _header_queue.size(); } + + static inline size_t HeaderSize(const Header& h) { + // https://tools.ietf.org/html/rfc7541#section-4.1 + return h.name.size() + h.value.size() + 32; + } + + void PopHeader() { + DCHECK(!empty()); + const Header* h = _header_queue.top(); + const size_t entry_size = HeaderSize(*h); + DCHECK_LE(entry_size, _size); + const uint64_t id = _add_times - _header_queue.size(); + if (_need_indexes) { + RemoveHeaderFromIndexes(*h, id); + } + _size -= entry_size; + _header_queue.pop(); + } + + void RemoveHeaderFromIndexes(const Header& h, uint64_t expected_id) { + if (!h.value.empty()) { + const uint64_t* v = _header_index.seek(h); + DCHECK(v); + if (*v == expected_id) { + _header_index.erase(h); + } + } + const uint64_t* v = _name_index.seek(h.name); + DCHECK(v); + if (*v == expected_id) { + _name_index.erase(h.name); + } + } + + void AddHeader(const Header& h) { + CHECK(!h.name.empty()); + const size_t entry_size = HeaderSize(h); + + while (!empty() && (_size + entry_size) > _max_size) { + PopHeader(); + } + + if (entry_size > _max_size) { + // https://tools.ietf.org/html/rfc7541#section-4.1 + // If this header is larger than the max size, clear the table only. + DCHECK(empty()); + return; + } + + _size += entry_size; + CHECK(!_header_queue.full()); + _header_queue.push(h); + + const int id = _add_times++; + if (_need_indexes) { + // Overwrite existance value. + if (!h.value.empty()) { + _header_index[h] = id; + } + _name_index[h.name] = id; + } + } + + void ResetMaxSize(size_t new_max_size) { + LOG(INFO) << this << ".size=" << _size << " new_max_size=" << new_max_size + << " max_size=" << _max_size; + if (new_max_size > _max_size) { + //LOG(ERROR) << "Invalid new_max_size=" << new_max_size; + //return -1; + _max_size = new_max_size; + return; + } + if (new_max_size < _max_size) { + _max_size = new_max_size; + while (_size > _max_size) { + PopHeader(); + } + } + return; + } + + void Print(std::ostream& os) const; + +private: + + int _start_index; + bool _need_indexes; + uint64_t _add_times; // Increase when adding a new entry. + size_t _max_size; + size_t _size; + butil::BoundedQueue
_header_queue; + + // ----------------------- Encoder only ---------------------------- + // Indexes that map entry to the latest time it was added. + // Note that duplicated entries are allowed in index table, which indicates + // that the same header is possibly added/removed for multiple times, + // requiring a costly multimap to index all the header entries. + // Since the encoder just cares whether this header is in the index table + // rather than which the index number is, only the latest entry of the same + // header is indexed here, which is definitely the last one to be removed. + butil::FlatMap _header_index; + butil::CaseIgnoredFlatMap _name_index; +}; + +int IndexTable::Init(const IndexTableOptions& options) { + size_t num_headers = 0; + if (options.static_table_size > 0) { + num_headers = options.static_table_size; + _max_size = UINT_MAX; + } else { + num_headers = options.max_size / (32 + 2); + // ^ + // name and value both have at least one byte in. + _max_size = options.max_size; + } + void *header_queue_storage = malloc(num_headers * sizeof(Header)); + if (!header_queue_storage) { + LOG(ERROR) << "Fail to malloc space for " << num_headers << " headers"; + return -1; + } + butil::BoundedQueue
tmp( + header_queue_storage, num_headers * sizeof(Header), + butil::OWNS_STORAGE); + _header_queue.swap(tmp); + _start_index = options.start_index; + _need_indexes = options.need_indexes; + if (_need_indexes) { + if (_name_index.init(num_headers * 2) != 0) { + LOG(WARNING) << "Fail to init _name_index"; + } + if (_header_index.init(num_headers * 2) != 0) { + LOG(WARNING) << "Fail to init _name_index"; + } + } + if (options.static_table_size > 0) { + // Add header in the reverse order + for (int i = options.static_table_size - 1; i >= 0; --i) { + Header h; + h.name = options.static_table[i].name; + h.value = options.static_table[i].value; + AddHeader(h); + } + } + return 0; +} + +void IndexTable::Print(std::ostream& os) const { + os << "{start_index=" << _start_index + << " need_indexes=" << _need_indexes + << " add_times=" << _add_times + << " max_size=" << _max_size + << " size=" << _size + << " header_queue.size=" << _header_queue.size() + << " header_index.size=" << _header_index.size() + << " name_index.size=" << _name_index.size() + << '}'; +} + +struct HuffmanNode { + uint16_t left_child; + uint16_t right_child; + int32_t value; +}; + +class BAIDU_CACHELINE_ALIGNMENT HuffmanTree { +DISALLOW_COPY_AND_ASSIGN(HuffmanTree); +public: + typedef uint16_t NodeId; + enum ConstValue { + NULL_NODE = 0, + ROOT_NODE = 1, + INVALID_VALUE = INT_MAX + }; + + HuffmanTree() { + // Allocate memory for root + HuffmanNode root = { NULL_NODE, NULL_NODE, INVALID_VALUE }; + _node_memory.push_back(root); + } + + void AddLeafNode(int32_t value, const HuffmanCode& code) { + NodeId cur = ROOT_NODE; + for (int i = code.bit_len; i > 0; i--) { + CHECK_EQ(node(cur).value, INVALID_VALUE) << "value=" << value << "cur=" << cur; + if (code.code & (1u << (i - 1))) { + if (node(cur).right_child == NULL_NODE) { + NodeId new_id = AllocNode(); + node(cur).right_child = new_id; + } + cur = node(cur).right_child; + } else { + if (node(cur).left_child == NULL_NODE) { + NodeId new_id = AllocNode(); + node(cur).left_child = new_id; + } + cur = node(cur).left_child; + } + } + CHECK_EQ(INVALID_VALUE, node(cur).value) << "value=" << value << " cur=" << cur; + CHECK_EQ(NULL_NODE, node(cur).left_child); + CHECK_EQ(NULL_NODE, node(cur).right_child); + node(cur).value = value; + } + + const HuffmanNode* node(NodeId id) const { + if (id == 0u) { + return NULL; + } + if (id > _node_memory.size()) { + return NULL; + } + return &_node_memory[id - 1]; + } + +private: + + HuffmanNode& node(NodeId id) { + return _node_memory[id - 1]; + } + + NodeId AllocNode() { + const NodeId id = _node_memory.size() + 1; + HuffmanNode node = { NULL_NODE, NULL_NODE, INVALID_VALUE }; + _node_memory.push_back(node); + return id; + } + std::vector _node_memory; + +}; + +class HuffmanEncoder { +DISALLOW_COPY_AND_ASSIGN(HuffmanEncoder); +public: + HuffmanEncoder(butil::IOBufAppender* out, const HuffmanCode* table) + : _out(out) + , _table(table) + , _partial_byte(0) + , _remain_bit(8) + , _out_bytes(0) + {} + + void Encode(unsigned char byte) { + const HuffmanCode code = _table[byte]; + uint16_t bits_left = code.bit_len; + while (bits_left) { + const uint16_t adding_bits_len = std::min(_remain_bit, bits_left); + const uint8_t adding_bits = static_cast( + (code.code & ((1u << bits_left) - 1)) // clear leading bits + >> (bits_left - adding_bits_len)); // align to LSB + _partial_byte |= adding_bits << (_remain_bit - adding_bits_len); + _remain_bit -= adding_bits_len; + bits_left -= adding_bits_len; + if (!_remain_bit) { + ++_out_bytes; + _out->push_back(_partial_byte); + _remain_bit = 8; + _partial_byte = 0; + } + } + } + + void EndStream() { + if (_remain_bit == 8u) { + return; + } + DCHECK_LT(_remain_bit, 8u); + // Add padding `1's to lsb to make _out aligned + _partial_byte |= (1 << _remain_bit) - 1; + // TODO: push_back is probably costly since it acquires tls everytime it + // is invoked. + _out->push_back(_partial_byte); + _partial_byte = 0; + _remain_bit = 0; + _out = NULL; + ++_out_bytes; + } + + uint32_t out_bytes() const { return _out_bytes; } + +private: + butil::IOBufAppender* _out; + const HuffmanCode* _table; + uint8_t _partial_byte; + uint16_t _remain_bit; + uint32_t _out_bytes; +}; + +class HuffmanDecoder { +DISALLOW_COPY_AND_ASSIGN(HuffmanDecoder); +public: + HuffmanDecoder(std::string* out, const HuffmanTree* tree) + // FIXME: resizing of out is costly + : _out(out) + , _tree(tree) + , _cur_node(tree->node(HuffmanTree::ROOT_NODE)) + , _cur_depth(0) // Depth of root node is 0 + , _padding(true) + {} + int Decode(uint8_t byte) { + for (int i = 7; i >= 0; --i) { + if (byte & (1u << i)) { + _cur_node = _tree->node(_cur_node->right_child); + if (BAIDU_UNLIKELY(!_cur_node)) { + LOG(ERROR) << "Decoder stream reaches NULL_NODE"; + return -1; + } + if (_cur_node->value != HuffmanTree::INVALID_VALUE) { + if (BAIDU_UNLIKELY(_cur_node->value == HPACK_HUFFMAN_EOS)) { + LOG(ERROR) << "Decoder stream reaches EOS"; + return -1; + } + _out->push_back(static_cast(_cur_node->value)); + _cur_node = _tree->node(HuffmanTree::ROOT_NODE); + _cur_depth = 0; + _padding = true; + continue; + } + _padding &= 1; + } else { + _cur_node = _tree->node(_cur_node->left_child); + if (BAIDU_UNLIKELY(!_cur_node)) { + LOG(ERROR) << "Decoder stream reaches NULL_NODE"; + return -1; + } + if (_cur_node->value != HuffmanTree::INVALID_VALUE) { + if (BAIDU_UNLIKELY(_cur_node->value == HPACK_HUFFMAN_EOS)) { + LOG(ERROR) << "Decoder stream reaches EOS"; + return -1; + } + _out->push_back(static_cast(_cur_node->value)); + _cur_node = _tree->node(HuffmanTree::ROOT_NODE); + _cur_depth = 0; + _padding = true; + continue; + } + _padding &= 0; + } + ++_cur_depth; + } + return 0; + } + int EndStream() { + if (_cur_depth == 0) { + return 0; + } + if (_cur_depth <= 7 && _padding) { + return 0; + } + // Invalid stream, the padding is not corresponding to MSB of EOS + // https://tools.ietf.org/html/rfc7541#section-5.2 + return -1; + } +private: + std::string* _out; + const HuffmanTree* _tree; + const HuffmanNode* _cur_node; + uint16_t _cur_depth; + bool _padding; +}; + +// Primitive Type Representations + +// Encode variant intger and return the size +inline void EncodeInteger(butil::IOBufAppender* out, uint8_t msb, + uint8_t prefix_size, uint32_t value) { + uint8_t max_prefix_value = (1 << prefix_size) - 1; + if (value < max_prefix_value) { + msb |= value; + out->push_back(msb); + return; + } + value -= max_prefix_value; + msb |= max_prefix_value; + out->push_back(msb); + for (; value >= 128; ) { + const uint8_t c = (value & 0x7f) | 0x80; + value >>= 7; + out->push_back(c); + } + out->push_back(static_cast(value)); +} + +// Static variables +static HuffmanTree* s_huffman_tree = NULL; +static IndexTable* s_static_table = NULL; +static pthread_once_t s_create_once = PTHREAD_ONCE_INIT; + +static void CreateStaticTableOrDie() { + s_huffman_tree = new HuffmanTree; + for (size_t i = 0; i < ARRAY_SIZE(s_huffman_table); ++i) { + s_huffman_tree->AddLeafNode(i, s_huffman_table[i]); + } + IndexTableOptions options; + options.max_size = UINT_MAX; + options.static_table = s_static_headers; + options.static_table_size = ARRAY_SIZE(s_static_headers); + options.start_index = 1; + options.need_indexes = true; + s_static_table = new IndexTable; + if (s_static_table->Init(options) != 0) { + LOG(ERROR) << "Fail to init static table"; + exit(1); + } +} + +static void CreateStaticTableOnceOrDie() { + if (pthread_once(&s_create_once, CreateStaticTableOrDie) != 0) { + PLOG(ERROR) << "Fail to pthread_once"; + exit(1); + } +} + +// Assume that no header would be larger than 10MB +static const size_t MAX_HPACK_INTEGER = 10 * 1024 * 1024ul; + +inline ssize_t DecodeInteger(butil::IOBufBytesIterator& iter, + uint8_t prefix_size, uint32_t* value) { + if (iter == NULL) { + return 0; // No enough data + } + uint8_t first_byte = *iter; + uint64_t tmp = first_byte & ((1 << prefix_size) - 1); + ++iter; + if (tmp < ((1u << prefix_size) - 1)) { + *value = static_cast(tmp); + return 1; + } + uint8_t cur_byte = 0; + int m = 0; + ssize_t in_bytes = 1; + do { + if (!iter) { + return 0; + } + cur_byte = *iter; + in_bytes++; + tmp += static_cast(cur_byte & 0x7f) << m; + m += 7; + ++iter; + } while ((cur_byte & 0x80) && (tmp < MAX_HPACK_INTEGER)); + + if (tmp >= MAX_HPACK_INTEGER) { + LOG(ERROR) << "Source stream is likely malformed"; + return -1; + } + + *value = static_cast(tmp); + + return in_bytes; +} + +template // use template to remove dead branches. +inline void EncodeString(butil::IOBufAppender* out, const std::string& s, + bool huffman_encoding) { + if (!huffman_encoding) { + EncodeInteger(out, 0x00, 7, s.size()); + if (LOWERCASE) { + for (size_t i = 0; i < s.size(); ++i) { + out->push_back(butil::ascii_tolower(s[i])); + } + } else { + out->append(s); + } + return; + } + // Calculate length of encoded string + uint32_t bit_len = 0; + if (LOWERCASE) { + for (size_t i = 0; i < s.size(); ++i) { + bit_len += s_huffman_table[(uint8_t)butil::ascii_tolower(s[i])].bit_len; + } + } else { + for (size_t i = 0; i < s.size(); ++i) { + bit_len += s_huffman_table[(uint8_t)s[i]].bit_len; + } + } + EncodeInteger(out, 0x80, 7, (bit_len >> 3) + !!(bit_len & 7)); + HuffmanEncoder e(out, s_huffman_table); + if (LOWERCASE) { + for (size_t i = 0; i < s.size(); ++i) { + e.Encode(butil::ascii_tolower(s[i])); + } + } else { + for (size_t i = 0; i < s.size(); ++i) { + e.Encode(s[i]); + } + } + e.EndStream(); +} + +inline ssize_t DecodeString(butil::IOBufBytesIterator& iter, std::string* out) { + if (iter == NULL) { + return 0; + } + const bool huffman = *iter & 0x80; + uint32_t length = 0; + ssize_t in_bytes = DecodeInteger(iter, 7, &length); + if (in_bytes <= 0) { + return -1; + } + if (length > iter.bytes_left()) { + return 0; + } + in_bytes += length; + out->clear(); + if (!huffman) { + iter.copy_and_forward(out, length); + return in_bytes; + } + HuffmanDecoder d(out, s_huffman_tree); + for (; iter != NULL && length; ++iter, --length) { + if (d.Decode(*iter) != 0) { + return -1; + } + } + if (d.EndStream() != 0) { + return -1; + } + return in_bytes; +} + +HPacker::HPacker() + : _encode_table(NULL) + , _decode_table(NULL) { + CreateStaticTableOnceOrDie(); +} + +HPacker::~HPacker() { + if (_encode_table) { + delete _encode_table; + _encode_table = NULL; + } + if (_decode_table) { + delete _decode_table; + _decode_table = NULL; + } +} + +int HPacker::Init(size_t max_table_size) { + CHECK(!_encode_table); + CHECK(!_decode_table); + IndexTableOptions encode_table_options; + encode_table_options.max_size = max_table_size; + encode_table_options.start_index = s_static_table->end_index(); + encode_table_options.need_indexes = true; + _encode_table = new IndexTable; + if (_encode_table->Init(encode_table_options) != 0) { + LOG(ERROR) << "Fail to init encode table"; + return -1; + } + IndexTableOptions decode_table_options; + decode_table_options.max_size = max_table_size; + decode_table_options.start_index = s_static_table->end_index(); + decode_table_options.need_indexes = false; + _decode_table = new IndexTable; + if (_decode_table->Init(decode_table_options) != 0) { + LOG(ERROR) << "Fail to init decode table"; + return -1; + } + return 0; +} + +inline int HPacker::FindHeaderFromIndexTable(const Header& h) const { + // saves a hash (which is a hotspot) for ones missing s_static_table + const HeaderAndHashCode hhc = { HeaderHasher()(h), &h }; + int index = s_static_table->GetIndexOfHeader(hhc); + if (index > 0) { + return index; + } + return _encode_table->GetIndexOfHeader(hhc); +} + +inline int HPacker::FindNameFromIndexTable(const std::string& name) const { + int index = s_static_table->GetIndexOfName(name); + if (index > 0) { + return index; + } + return _encode_table->GetIndexOfName(name); +} + +void HPacker::Encode(butil::IOBufAppender* out, const Header& header, + const HPackOptions& options) { + if (options.index_policy != HPACK_NEVER_INDEX_HEADER) { + const int index = FindHeaderFromIndexTable(header); + if (index > 0) { + // This header is already in the index table + return EncodeInteger(out, 0x80, 7, index); + } + } // The header can't be indexed or the header wasn't in the index table + + const int name_index = FindNameFromIndexTable(header.name); + if (options.index_policy == HPACK_INDEX_HEADER) { + // TODO: Add Options that indexes name independently + _encode_table->AddHeader(header); + } + switch (options.index_policy) { + case HPACK_INDEX_HEADER: + EncodeInteger(out, 0x40, 6, name_index); + break; + case HPACK_NOT_INDEX_HEADER: + EncodeInteger(out, 0x00, 4, name_index); + break; + case HPACK_NEVER_INDEX_HEADER: + EncodeInteger(out, 0x10, 4, name_index); + break; + } + if (name_index == 0) { + EncodeString(out, header.name, options.encode_name); + } + EncodeString(out, header.value, options.encode_value); +} + +inline const HPacker::Header* HPacker::HeaderAt(int index) const { + return (index >= _decode_table->start_index()) + ? _decode_table->HeaderAt(index) : s_static_table->HeaderAt(index); +} + +inline ssize_t HPacker::DecodeWithKnownPrefix( + butil::IOBufBytesIterator& iter, Header* h, uint8_t prefix_size) const { + int index = 0; + ssize_t index_bytes = DecodeInteger(iter, prefix_size, (uint32_t*)&index); + ssize_t name_bytes = 0; + if (index_bytes <= 0) { + LOG(ERROR) << "Fail to decode index"; + return -1; + } + if (index != 0) { + const Header* indexed_header = HeaderAt(index); + if (indexed_header == NULL) { + LOG(ERROR) << "No header at index=" << index; + return -1; + } + h->name = indexed_header->name; + } else { + name_bytes = DecodeString(iter, &h->name); + if (name_bytes <= 0) { + LOG(ERROR) << "Fail to decode name"; + return -1; + } + tolower(&h->name); + } + ssize_t value_bytes = DecodeString(iter, &h->value); + if (value_bytes <= 0) { + LOG(ERROR) << "Fail to decode value"; + return -1; + } + return index_bytes + name_bytes + value_bytes; +} + +ssize_t HPacker::Decode(butil::IOBufBytesIterator& iter, Header* h) { + ssize_t skipped_bytes = 0; +decode_next: + if (iter == NULL) { + return 0; + } + const uint8_t first_byte = *iter; + // Check the leading 4 bits to determin the entry type + switch (first_byte >> 4) { + case 15: + case 14: + case 13: + case 12: + case 11: + case 10: + case 9: + case 8: + // (1xxx) Indexed Header Field Representation + // https://tools.ietf.org/html/rfc7541#section-6.1 + { + int index = 0; + ssize_t index_bytes = DecodeInteger(iter, 7, (uint32_t*)&index); + if (index_bytes <= 0) { + return index_bytes; + } + const Header* indexed_header = HeaderAt(index); + if (indexed_header == NULL) { + LOG(ERROR) << "No header at index=" << index; + return -1; + } + *h = *indexed_header; + return skipped_bytes + index_bytes; + } + break; + case 7: + case 5: + case 6: + case 4: + // (01xx) Literal Header Field with Incremental Indexing + // https://tools.ietf.org/html/rfc7541#section-6.2.1 + { + const ssize_t bytes_consumed = DecodeWithKnownPrefix(iter, h, 6); + if (bytes_consumed <= 0) { + return -1; + } + _decode_table->AddHeader(*h); + return skipped_bytes + bytes_consumed; + } + break; + case 3: + case 2: + // (001x) Dynamic Table Size Update + // https://tools.ietf.org/html/rfc7541#section-6.3 + { + uint32_t max_size = 0; + ssize_t read_bytes = DecodeInteger(iter, 5, &max_size); + if (read_bytes <= 0) { + return read_bytes; + } + if (max_size > H2Settings::DEFAULT_HEADER_TABLE_SIZE) { + LOG(ERROR) << "Invalid max_size=" << max_size; + return -1; + } + _decode_table->ResetMaxSize(max_size); + skipped_bytes += read_bytes; + goto decode_next; + } + case 1: + // (0001) Literal Header Field Never Indexed + // https://tools.ietf.org/html/rfc7541#section-6.2.3 + { + const ssize_t bytes_consumed = DecodeWithKnownPrefix(iter, h, 4); + return bytes_consumed > 0 ? skipped_bytes + bytes_consumed : bytes_consumed; + } + // TODO: Expose NeverIndex to the caller. + case 0: + // (0000) Literal Header Field without Indexing + // https://tools.ietf.org/html/rfc7541#section-6.2.1 + { + const ssize_t bytes_consumed = DecodeWithKnownPrefix(iter, h, 4); + return bytes_consumed > 0 ? skipped_bytes + bytes_consumed : bytes_consumed; + } + // TODO: Expose NeverIndex to the caller. + default: + CHECK(false) << "Can't reach here"; + return -1; + } +} + +void HPacker::Describe(std::ostream& os, const DescribeOptions& opt) const { + if (opt.verbose) { + os << '\n'; + } + const char sep = (opt.verbose ? '\n' : ' '); + os << "encode_table="; + if (_encode_table) { + _encode_table->Print(os); + } else { + os << "null"; + } + os << sep << "decode_table="; + if (_decode_table) { + _decode_table->Print(os); + } else { + os << "null"; + } + if (opt.verbose) { + os << '\n'; + } +} + +void tolower(std::string* s) { + const char* d = s->c_str(); + for (size_t i = 0; i < s->size(); ++i) { + const char c = d[i]; + const char c2 = butil::ascii_tolower(c); + if (c2 != c) { + (*s)[i] = c2; + } + } +} + +} // namespace brpc diff --git a/src/brpc/details/hpack.h b/src/brpc/details/hpack.h new file mode 100644 index 0000000..05a5f30 --- /dev/null +++ b/src/brpc/details/hpack.h @@ -0,0 +1,152 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_HPACK_H +#define BRPC_HPACK_H + +#include "butil/iobuf.h" // butil::IOBuf +#include "butil/strings/string_piece.h" // butil::StringPiece +#include "brpc/http2.h" +#include "brpc/describable.h" + +namespace brpc { + +enum HeaderIndexPolicy { + // Append this header, alerting the decoder dynamic table + // - If the given header matches one of the indexed header, this header + // is replaced by the index. + // - If not, append this header into the decoder dynamic table + HPACK_INDEX_HEADER = 0, + + // Append this header, without alerting the decoder dynamic table + // - If the given header matches one of the indexed header, this header + // is replaced by the index. + // - If not, append this header directly *WITHOUT* any modification on the + // decoder dynamic table + HPACK_NOT_INDEX_HEADER = 1, + + // Append this header which will never replaced by a index + HPACK_NEVER_INDEX_HEADER = 2, +}; + +// Options to encode a header +struct HPackOptions { + + // How to index this header field. + // Default: HPACK_INDEX_HEADER + HeaderIndexPolicy index_policy; + + // If true, the name string would be encoded with huffman encoding + // Default: false + bool encode_name; + + // If true, the value string would be encoded with huffman encoding + // Default: false + bool encode_value; + + // Construct default options + HPackOptions(); +}; + +inline HPackOptions::HPackOptions() + : index_policy(HPACK_INDEX_HEADER) + , encode_name(false) + , encode_value(false) +{} + +class IndexTable; + +// HPACK - Header compression algorithm for http2 (rfc7541) +// http://httpwg.org/specs/rfc7541.html +// Note: Name of header is assumed to be in *lowercase* acoording to +// https://tools.ietf.org/html/rfc7540#section-8.1.2 +// Just as in HTTP/1.x, header field names are strings of ASCII +// characters that are compared in a case-insensitive fashion. However, +// header field names *MUST* be converted to lowercase prior to their +// encoding in HTTP/2. A request or response containing uppercase +// header field names MUST be treated as malformed +// Not supported methods: +// - Resize dynamic table. +class HPacker : public Describable { +public: + struct Header { + std::string name; + std::string value; + + Header() {} + explicit Header(const std::string& name2) : name(name2) {} + Header(const std::string& name2, const std::string& value2) + : name(name2), value(value2) {} + }; + + HPacker(); + ~HPacker(); + + // Initialize the instance. + // Returns 0 on success, -1 otherwise. + int Init(size_t max_table_size = H2Settings::DEFAULT_HEADER_TABLE_SIZE); + + // Encode header and append the encoded buffer to |out| + // Returns true on success. + void Encode(butil::IOBufAppender* out, const Header& header, + const HPackOptions& options); + void Encode(butil::IOBufAppender* out, const Header& header) + { return Encode(out, header, HPackOptions()); } + + // Try to decode at most one Header from source and erase corresponding + // buffer. + // Returns: + // * $size of decoded buffer when a header is succesfully decoded + // * 0 when the source is incompleted + // * -1 when the source is malformed + ssize_t Decode(butil::IOBuf* source, Header* h); + + // Like the previous function, except that the source is from + // IOBufBytesIterator. + ssize_t Decode(butil::IOBufBytesIterator& source, Header* h); + + void Describe(std::ostream& os, const DescribeOptions&) const; + +private: + DISALLOW_COPY_AND_ASSIGN(HPacker); + int FindHeaderFromIndexTable(const Header& h) const; + int FindNameFromIndexTable(const std::string& name) const; + const Header* HeaderAt(int index) const; + ssize_t DecodeWithKnownPrefix( + butil::IOBufBytesIterator& iter, Header* h, uint8_t prefix_size) const; + + IndexTable* _encode_table; + IndexTable* _decode_table; +}; + +// Lowercase the input string, a fast implementation. +void tolower(std::string* s); + +inline ssize_t HPacker::Decode(butil::IOBuf* source, Header* h) { + butil::IOBufBytesIterator iter(*source); + const ssize_t nc = Decode(iter, h); + if (nc > 0) { + source->pop_front(nc); + } + return nc; +} + +} // namespace brpc + + +#endif //BRPC_HPACK_H diff --git a/src/brpc/details/http_message.cpp b/src/brpc/details/http_message.cpp new file mode 100644 index 0000000..003bafa --- /dev/null +++ b/src/brpc/details/http_message.cpp @@ -0,0 +1,757 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // std::string +#include +#include +#include "butil/macros.h" +#include "butil/logging.h" // LOG +#include "butil/scoped_lock.h" +#include "butil/endpoint.h" +#include "butil/base64.h" +#include "bthread/bthread.h" // bthread_usleep +#include "brpc/log.h" +#include "brpc/reloadable_flags.h" +#include "brpc/details/http_message.h" + +namespace brpc { + +DEFINE_bool(allow_chunked_length, false, + "Allow both Transfer-Encoding and Content-Length headers are present."); +DEFINE_bool(allow_http_1_1_request_without_host, true, + "Allow HTTP/1.1 request without host which violates the HTTP/1.1 specification."); +DEFINE_bool(http_verbose, false, + "[DEBUG] Print EVERY http request/response"); +DEFINE_int32(http_verbose_max_body_length, 512, + "[DEBUG] Max body length printed when -http_verbose is on"); +DECLARE_int64(socket_max_unwritten_bytes); +DECLARE_uint64(max_body_size); + +// Implement callbacks for http parser + +int HttpMessage::on_message_begin(http_parser *parser) { + HttpMessage *http_message = (HttpMessage *)parser->data; + http_message->_stage = HTTP_ON_MESSAGE_BEGIN; + return 0; +} + +// For request +int HttpMessage::on_url(http_parser *parser, + const char *at, const size_t length) { + HttpMessage *http_message = (HttpMessage *)parser->data; + http_message->_stage = HTTP_ON_URL; + http_message->_url.append(at, length); + return 0; +} + +// For response +int HttpMessage::on_status(http_parser *parser, const char *, const size_t) { + HttpMessage *http_message = (HttpMessage *)parser->data; + http_message->_stage = HTTP_ON_STATUS; + // According to https://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html + // Client is not required to examine or display the Reason-Phrase + return 0; +} + +// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 +// Multiple message-header fields with the same field-name MAY be present in a +// message if and only if the entire field-value for that header field is +// defined as a comma-separated list [i.e., #(values)]. It MUST be possible to +// combine the multiple header fields into one "field-name: field-value" pair, +// without changing the semantics of the message, by appending each subsequent +// field-value to the first, each separated by a comma. The order in which +// header fields with the same field-name are received is therefore significant +// to the interpretation of the combined field value, and thus a proxy MUST NOT +// change the order of these field values when a message is forwarded. +int HttpMessage::on_header_field(http_parser *parser, + const char *at, const size_t length) { + HttpMessage *http_message = (HttpMessage *)parser->data; + if (http_message->_stage != HTTP_ON_HEADER_FIELD) { + http_message->_stage = HTTP_ON_HEADER_FIELD; + http_message->_cur_header.clear(); + } + http_message->_cur_header.append(at, length); + return 0; +} + +int HttpMessage::on_header_value(http_parser *parser, + const char *at, const size_t length) { + HttpMessage *http_message = (HttpMessage *)parser->data; + bool first_entry = false; + if (http_message->_stage != HTTP_ON_HEADER_VALUE) { + http_message->_stage = HTTP_ON_HEADER_VALUE; + first_entry = true; + if (http_message->_cur_header.empty()) { + LOG(ERROR) << "Header name is empty"; + return -1; + } + HttpHeader& header = http_message->header(); + if (header.CanFoldedInLine(http_message->_cur_header)) { + http_message->_cur_value = + &header.GetOrAddHeader(http_message->_cur_header); + } else { + http_message->_cur_value = + &header.AddHeader(http_message->_cur_header); + } + if (http_message->_cur_value && !http_message->_cur_value->empty()) { + http_message->_cur_value->append( + header.HeaderValueDelimiter(http_message->_cur_header)); + } + } + if (http_message->_cur_value) { + http_message->_cur_value->append(at, length); + } + if (FLAGS_http_verbose) { + butil::IOBufBuilder* vs = http_message->_vmsgbuilder.get(); + if (vs == NULL) { + vs = new butil::IOBufBuilder; + http_message->_vmsgbuilder.reset(vs); + if (parser->type == HTTP_REQUEST) { + *vs << "[ HTTP REQUEST @" << butil::my_ip() << " ]\n< " + << HttpMethod2Str((HttpMethod)parser->method) << ' ' + << http_message->_url << " HTTP/" << parser->http_major + << '.' << parser->http_minor; + } else { + // NOTE: http_message->header().status_code() may not be set yet. + *vs << "[ HTTP RESPONSE @" << butil::my_ip() << " ]\n< HTTP/" + << parser->http_major + << '.' << parser->http_minor << ' ' << parser->status_code + << ' ' << HttpReasonPhrase(parser->status_code); + } + } + if (first_entry) { + *vs << "\n< " << http_message->_cur_header << ": "; + } + vs->write(at, length); + } + return 0; +} + +int HttpMessage::on_headers_complete(http_parser *parser) { + HttpMessage* http_message = (HttpMessage *)parser->data; + http_message->_stage = HTTP_ON_HEADERS_COMPLETE; + if (parser->http_major > 1) { + // NOTE: this checking is a MUST because ProcessHttpResponse relies + // on it to cast InputMessageBase* into different types. + LOG(WARNING) << "Invalid major_version=" << parser->http_major; + parser->http_major = 1; + } + HttpHeader& headers = http_message->header(); + headers.set_version(parser->http_major, parser->http_minor); + // Only for response + // http_parser may set status_code to 0 when the field is not needed, + // e.g. in a request. In principle status_code is undefined in a request, + // but to be consistent and not surprise users, we set it to OK as well. + headers.set_status_code( + !parser->status_code ? HTTP_STATUS_OK : parser->status_code); + // Only for request + // method is 0(which is DELETE) for response as well. Since users are + // unlikely to check method of a response, we don't do anything. + headers.set_method(static_cast(parser->method)); + bool is_http_request = parser->type == HTTP_REQUEST; + if (is_http_request && headers.uri().SetHttpURL(http_message->_url) != 0) { + LOG(ERROR) << "Fail to parse url=`" << http_message->_url << '\''; + return -1; + } + // https://datatracker.ietf.org/doc/html/rfc2616#section-5.2 + //1. If Request-URI is an absoluteURI, the host is part of the Request-URI. + //Any Host header field value in the request MUST be ignored. + //2. If the Request-URI is not an absoluteURI, and the request includes a + //Host header field, the host is determined by the Host header field value. + //3. If the host as determined by rule 1 or 2 is not a valid host on the + //server, the responce MUST be a 400 (Bad Request) error messsage. + URI& uri = headers.uri(); + if (uri._host.empty()) { + const std::string* host_header = headers.GetHeader("host"); + if (host_header != NULL) { + uri.SetHostAndPort(*host_header); + } + } + + // https://datatracker.ietf.org/doc/html/rfc2616#section-14.23 + // All Internet-based HTTP/1.1 servers MUST respond with a 400 (Bad Request) + // status code to any HTTP/1.1 request message which lacks a Host header field. + if (uri.host().empty() && is_http_request && + !headers.before_http_1_1() && + !FLAGS_allow_http_1_1_request_without_host) { + LOG(ERROR) << "HTTP protocol error: missing host header"; + return -1; + } + + // https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.3 + // If a message is received with both a Transfer-Encoding and a + // Content-Length header field, the Transfer-Encoding overrides the + // Content-Length. Such a message might indicate an attempt to + // perform request smuggling (Section 9.5) or response splitting + // (Section 9.4) and ought to be handled as an error. A sender MUST + // remove the received Content-Length field prior to forwarding such + // a message. + + // Reject message if both Transfer-Encoding and Content-Length headers + // are present or if allowed by gflag and 'Transfer-Encoding' + // is chunked - remove Content-Length and serve request. + if (parser->uses_transfer_encoding && parser->flags & F_CONTENTLENGTH) { + if (parser->flags & F_CHUNKED && FLAGS_allow_chunked_length) { + headers.RemoveHeader("Content-Length"); + } else { + LOG(ERROR) << "HTTP protocol error: both Content-Length " + << "and Transfer-Encoding are set."; + return -1; + } + } + + // If server receives a response to a HEAD request, returns 1 and then + // the parser will interpret that as saying that this message has no body. + if (parser->type == HTTP_RESPONSE && + http_message->request_method() == HTTP_METHOD_HEAD) { + return 1; + } + return 0; +} + +int HttpMessage::UnlockAndFlushToBodyReader(std::unique_lock& mu) { + if (_body.empty()) { + mu.unlock(); + return 0; + } + butil::IOBuf body_seen = _body.movable(); + ProgressiveReader* r = _body_reader; + mu.unlock(); + for (size_t i = 0; i < body_seen.backing_block_num(); ++i) { + butil::StringPiece blk = body_seen.backing_block(i); + butil::Status st = r->OnReadOnePart(blk.data(), blk.size()); + if (!st.ok()) { + mu.lock(); + _body_reader = NULL; + mu.unlock(); + r->OnEndOfMessage(st); + return -1; + } + } + return 0; +} + +int HttpMessage::on_body_cb(http_parser *parser, + const char *at, const size_t length) { + return static_cast(parser->data)->OnBody(at, length); +} + +int HttpMessage::on_message_complete_cb(http_parser *parser) { + return static_cast(parser->data)->OnMessageComplete(); +} + +int HttpMessage::OnBody(const char *at, const size_t length) { + if (!_read_body_progressively) { + if (length > FLAGS_max_body_size || + _body_size > FLAGS_max_body_size - length) { + _body_too_large = true; + return -1; + } + _body_size += length; + } + if (_vmsgbuilder) { + if (_stage != HTTP_ON_BODY) { + // only add prefix at first entry. + *_vmsgbuilder << "\n<\n"; + } + if (_read_body_progressively && + // If the status_code is non-OK, the body is likely to be the error + // description which is very helpful for debugging. Otherwise + // the body is probably streaming data which is too long to print. + header().status_code() == HTTP_STATUS_OK) { + LOG(INFO) << '\n' << _vmsgbuilder->buf(); + _vmsgbuilder.reset(NULL); + } else { + if (_vbodylen < (size_t)FLAGS_http_verbose_max_body_length) { + int plen = std::min(length, (size_t)FLAGS_http_verbose_max_body_length + - _vbodylen); + std::string str = butil::ToPrintableString( + at, plen, std::numeric_limits::max()); + _vmsgbuilder->write(str.data(), str.size()); + } + _vbodylen += length; + } + } + if (_stage != HTTP_ON_BODY) { + _stage = HTTP_ON_BODY; + } + if (!_read_body_progressively) { + // Normal read. + if (NULL != _current_source_iobuf) { + _current_source_iobuf->append_to( + &_body, length, _parsed_block_size + (at - _current_block_base)); + } else { + _body.append(at, length); + } + return 0; + } + // Progressive read. + std::unique_lock mu(_body_mutex); + ProgressiveReader* r = _body_reader; + while (r == NULL) { + // When _body is full, the sleep-waiting may block parse handler + // of the protocol. A more efficient solution is to remove the + // socket from epoll and add it back when the _body is not full, + // which requires a set of complicated "pause" and "unpause" + // asynchronous API. We just leave the job to bthread right now + // to make everything work. + if ((int64_t)_body.size() <= FLAGS_socket_max_unwritten_bytes) { + _body.append(at, length); + return 0; + } + mu.unlock(); + bthread_usleep(10000/*10ms*/); + mu.lock(); + r = _body_reader; + } + // Safe to operate _body_reader outside lock because OnBody() is + // guaranteed to be called by only one thread. + if (UnlockAndFlushToBodyReader(mu) != 0) { + return -1; + } + butil::Status st = r->OnReadOnePart(at, length); + if (st.ok()) { + return 0; + } + mu.lock(); + _body_reader = NULL; + mu.unlock(); + r->OnEndOfMessage(st); + return -1; +} + +int HttpMessage::OnMessageComplete() { + if (_vmsgbuilder) { + if (_vbodylen > (size_t)FLAGS_http_verbose_max_body_length) { + *_vmsgbuilder << "\n"; + } + LOG(INFO) << '\n' << _vmsgbuilder->buf(); + _vmsgbuilder.reset(NULL); + } + _cur_header.clear(); + _cur_value = NULL; + if (!_read_body_progressively) { + // Normal read. + _stage = HTTP_ON_MESSAGE_COMPLETE; + return 0; + } + // Progressive read. + std::unique_lock mu(_body_mutex); + _stage = HTTP_ON_MESSAGE_COMPLETE; + if (_body_reader != NULL) { + // Solve the case: SetBodyReader quit at ntry=MAX_TRY with non-empty + // _body and the remaining _body is just the last part. + // Make sure _body is emptied. + if (UnlockAndFlushToBodyReader(mu) != 0) { + return -1; + } + mu.lock(); + ProgressiveReader* r = _body_reader; + _body_reader = NULL; + mu.unlock(); + r->OnEndOfMessage(butil::Status()); + } + return 0; +} + +class FailAllRead : public ProgressiveReader { +public: + // @ProgressiveReader + butil::Status OnReadOnePart(const void* /*data*/, size_t /*length*/) { + return butil::Status(-1, "Trigger by FailAllRead at %s:%d", + __FILE__, __LINE__); + } + void OnEndOfMessage(const butil::Status&) {} +}; + +static FailAllRead* s_fail_all_read = NULL; +static pthread_once_t s_fail_all_read_once = PTHREAD_ONCE_INIT; +static void CreateFailAllRead() { s_fail_all_read = new FailAllRead; } + +void HttpMessage::SetBodyReader(ProgressiveReader* r) { + if (!_read_body_progressively) { + return r->OnEndOfMessage( + butil::Status(EPERM, "Call SetBodyReader on HttpMessage with" + " read_body_progressively=false")); + } + const int MAX_TRY = 3; + int ntry = 0; + do { + std::unique_lock mu(_body_mutex); + if (_body_reader != NULL) { + mu.unlock(); + return r->OnEndOfMessage( + butil::Status(EPERM, "SetBodyReader is called more than once")); + } + if (_body.empty()) { + if (_stage <= HTTP_ON_BODY) { + _body_reader = r; + return; + } else { // The body is complete and successfully consumed. + mu.unlock(); + return r->OnEndOfMessage(butil::Status()); + } + } else if (_stage <= HTTP_ON_BODY && ++ntry >= MAX_TRY) { + // Stop making _body empty after we've tried several times. + // If _stage is greater than HTTP_ON_BODY, neither OnBody() nor + // OnMessageComplete() will be called in future, we have to spin + // another time to empty _body. + _body_reader = r; + return; + } + butil::IOBuf body_seen = _body.movable(); + mu.unlock(); + for (size_t i = 0; i < body_seen.backing_block_num(); ++i) { + butil::StringPiece blk = body_seen.backing_block(i); + butil::Status st = r->OnReadOnePart(blk.data(), blk.size()); + if (!st.ok()) { + r->OnEndOfMessage(st); + // Make OnBody() or OnMessageComplete() fail on next call to + // close the socket. If the message was already complete, the + // socket will not be closed. + pthread_once(&s_fail_all_read_once, CreateFailAllRead); + r = s_fail_all_read; + ntry = MAX_TRY; + break; + } + } + } while (true); +} + +const http_parser_settings g_parser_settings = { + &HttpMessage::on_message_begin, + &HttpMessage::on_url, + &HttpMessage::on_status, + &HttpMessage::on_header_field, + &HttpMessage::on_header_value, + &HttpMessage::on_headers_complete, + &HttpMessage::on_body_cb, + &HttpMessage::on_message_complete_cb +}; + +HttpMessage::HttpMessage(bool read_body_progressively, + HttpMethod request_method) + : _request_method(request_method) + , _read_body_progressively(read_body_progressively) { + http_parser_init(&_parser, HTTP_BOTH); + _parser.allow_chunked_length = 1; + _parser.data = this; +} + +HttpMessage::~HttpMessage() { + if (_body_reader) { + ProgressiveReader* saved_body_reader = _body_reader; + _body_reader = NULL; + // Successfully ended message is ended in OnMessageComplete() or + // SetBodyReader() and _body_reader should be null-ed. Non-null + // _body_reader here just means the socket is broken before completion + // of the message. + saved_body_reader->OnEndOfMessage( + butil::Status(ECONNRESET, "The socket was broken")); + } +} + +ssize_t HttpMessage::ParseFromArray(const char *data, const size_t length) { + if (Completed()) { + if (length == 0) { + return 0; + } + LOG(ERROR) << "Append data(len=" << length + << ") to already-completed message"; + return -1; + } + const size_t nprocessed = + http_parser_execute(&_parser, &g_parser_settings, data, length); + if (_parser.http_errno != 0) { + // May try HTTP on other formats, failure is norm. + RPC_VLOG << "Fail to parse http message, parser=" << _parser + << ", buf=`" << butil::StringPiece(data, length) << '\''; + return -1; + } + _parsed_length += nprocessed; + return nprocessed; +} + +ssize_t HttpMessage::ParseFromIOBuf(const butil::IOBuf &buf) { + if (Completed()) { + if (buf.empty()) { + return 0; + } + LOG(ERROR) << "Append data(len=" << buf.size() + << ") to already-completed message"; + return -1; + } + _parsed_block_size = 0; + _current_source_iobuf = &buf; + BRPC_SCOPE_EXIT { + _current_source_iobuf = NULL; + }; + size_t nprocessed = 0; + for (size_t i = 0; i < buf.backing_block_num(); ++i) { + butil::StringPiece blk = buf.backing_block(i); + if (blk.empty()) { + // length=0 will be treated as EOF by http_parser, must skip. + continue; + } + _current_block_base = blk.data(); + size_t n = http_parser_execute( + &_parser, &g_parser_settings, blk.data(), blk.size()); + nprocessed += n; + _parsed_block_size += n; + if (_parser.http_errno != 0) { + // May try HTTP on other formats, failure is norm. + RPC_VLOG << "Fail to parse http message, parser=" << _parser + << ", buf=" << butil::ToPrintable(buf); + return -1; + } + if (Completed()) { + break; + } + } + _parsed_length += nprocessed; + return (ssize_t)nprocessed; +} + +static void DescribeHttpParserFlags(std::ostream& os, unsigned int flags) { + if (flags & F_CHUNKED) { + os << "F_CHUNKED|"; + } + if (flags & F_CONNECTION_KEEP_ALIVE) { + os << "F_CONNECTION_KEEP_ALIVE|"; + } + if (flags & F_CONNECTION_CLOSE) { + os << "F_CONNECTION_CLOSE|"; + } + if (flags & F_TRAILING) { + os << "F_TRAILING|"; + } + if (flags & F_UPGRADE) { + os << "F_UPGRADE|"; + } + if (flags & F_SKIPBODY) { + os << "F_SKIPBODY|"; + } + if (flags & F_CONTENTLENGTH) { + os << "F_CONTENTLENGTH|"; + } +} + +std::ostream& operator<<(std::ostream& os, const http_parser& parser) { + os << "{type=" << http_parser_type_name((http_parser_type)parser.type) + << " flags=`"; + DescribeHttpParserFlags(os, parser.flags); + os << "' state=" << http_parser_state_name(parser.state) + << " header_state=" << http_parser_header_state_name( + parser.header_state) + << " http_errno=`" << http_errno_description( + (http_errno)parser.http_errno) + << "' index=" << parser.index + << " nread=" << parser.nread + << " content_length=" << parser.content_length + << " http_major=" << parser.http_major + << " http_minor=" << parser.http_minor; + if (parser.type == HTTP_RESPONSE || parser.type == HTTP_BOTH) { + os << " status_code=" << parser.status_code; + } + if (parser.type == HTTP_REQUEST || parser.type == HTTP_BOTH) { + os << " method=" << HttpMethod2Str((HttpMethod)parser.method); + } + os << " data=" << parser.data + << '}'; + return os; +} + +#define BRPC_CRLF "\r\n" + +// Request format +// Request = Request-Line ; Section 5.1 +// *(( general-header ; Section 4.5 +// | request-header ; Section 5.3 +// | entity-header ) CRLF) ; Section 7.1 +// CRLF +// [ message-body ] ; Section 4.3 +// Request-Line = Method SP Request-URI SP HTTP-Version CRLF +// Method = "OPTIONS" ; Section 9.2 +// | "GET" ; Section 9.3 +// | "HEAD" ; Section 9.4 +// | "POST" ; Section 9.5 +// | "PUT" ; Section 9.6 +// | "DELETE" ; Section 9.7 +// | "TRACE" ; Section 9.8 +// | "CONNECT" ; Section 9.9 +// | extension-method +// extension-method = token +void MakeRawHttpRequest(butil::IOBuf* request, + HttpHeader* h, + const butil::EndPoint& remote_side, + const butil::IOBuf* content) { + butil::IOBufBuilder os; + os << HttpMethod2Str(h->method()) << ' '; + const URI& uri = h->uri(); + uri.PrintWithoutHost(os); // host is sent by "Host" header. + os << " HTTP/" << h->major_version() << '.' + << h->minor_version() << BRPC_CRLF; + // Never use "Content-Length" set by user. + h->RemoveHeader("Content-Length"); + const std::string* transfer_encoding = h->GetHeader("Transfer-Encoding"); + if (h->method() == HTTP_METHOD_GET) { + h->RemoveHeader("Transfer-Encoding"); + } else if (!transfer_encoding) { + // https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2 + // A sender MUST NOT send a Content-Length header field in any message + // that contains a Transfer-Encoding header field. + os << "Content-Length: " << (content ? content->length() : 0) + << BRPC_CRLF; + } + // `Expect: 100-continue' is not supported, remove it. + const std::string* expect = h->GetHeader("Expect"); + if (expect && *expect == "100-continue") { + h->RemoveHeader("Expect"); + } + //rfc 7230#section-5.4: + //A client MUST send a Host header field in all HTTP/1.1 request + //messages. If the authority component is missing or undefined for + //the target URI, then a client MUST send a Host header field with an + //empty field-value. + //rfc 7231#sec4.3: + //the request-target consists of only the host name and port number of + //the tunnel destination, separated by a colon. For example, + //Host: server.example.com:80 + if (h->GetHeader("host") == NULL) { + os << "Host: "; + if (!uri.host().empty()) { + os << uri.host(); + if (uri.port() >= 0) { + os << ':' << uri.port(); + } + } else if (remote_side.port != 0) { + os << remote_side; + } + os << BRPC_CRLF; + } + if (!h->content_type().empty()) { + os << "Content-Type: " << h->content_type() + << BRPC_CRLF; + } + for (HttpHeader::HeaderIterator it = h->HeaderBegin(); + it != h->HeaderEnd(); ++it) { + os << it->first << ": " << it->second << BRPC_CRLF; + } + if (h->GetHeader("Accept") == NULL) { + os << "Accept: */*" BRPC_CRLF; + } + // The fake "curl" user-agent may let servers return plain-text results. + if (h->GetHeader("User-Agent") == NULL) { + os << "User-Agent: brpc/1.0 curl/7.0" BRPC_CRLF; + } + const std::string& user_info = h->uri().user_info(); + if (!user_info.empty() && h->GetHeader("Authorization") == NULL) { + // NOTE: just assume user_info is well formatted, namely + // ":". Users are very unlikely to add extra + // characters in this part and even if users did, most of them are + // invalid and rejected by http_parser_parse_url(). + std::string encoded_user_info; + butil::Base64Encode(user_info, &encoded_user_info); + os << "Authorization: Basic " << encoded_user_info << BRPC_CRLF; + } + os << BRPC_CRLF; // CRLF before content + os.move_to(*request); + if (h->method() != HTTP_METHOD_GET && content) { + request->append(*content); + } +} + +// Response format +// Response = Status-Line ; Section 6.1 +// *(( general-header ; Section 4.5 +// | response-header ; Section 6.2 +// | entity-header ) CRLF) ; Section 7.1 +// CRLF +// [ message-body ] ; Section 7.2 +// Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF +void MakeRawHttpResponse(butil::IOBuf* response, + HttpHeader* h, + butil::IOBuf* content) { + butil::IOBufBuilder os; + os << "HTTP/" << h->major_version() << '.' + << h->minor_version() << ' ' << h->status_code() + << ' ' << h->reason_phrase() << BRPC_CRLF; + bool is_invalid_content = h->status_code() < HTTP_STATUS_OK || + h->status_code() == HTTP_STATUS_NO_CONTENT; + bool is_head_req = h->method() == HTTP_METHOD_HEAD; + if (is_invalid_content) { + // https://www.rfc-editor.org/rfc/rfc7230#section-3.3.1 + // A server MUST NOT send a Transfer-Encoding header field in any + // response with a status code of 1xx (Informational) or 204 (No + // Content). + h->RemoveHeader("Transfer-Encoding"); + // https://www.rfc-editor.org/rfc/rfc7230#section-3.3.2 + // A server MUST NOT send a Content-Length header field in any response + // with a status code of 1xx (Informational) or 204 (No Content). + h->RemoveHeader("Content-Length"); + } else { + const std::string* transfer_encoding = h->GetHeader("Transfer-Encoding"); + // https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2 + // A sender MUST NOT send a Content-Length header field in any message + // that contains a Transfer-Encoding header field. + if (transfer_encoding) { + h->RemoveHeader("Content-Length"); + } + if (content) { + const std::string* content_length = h->GetHeader("Content-Length"); + if (is_head_req) { + if (!content_length && !transfer_encoding) { + // Prioritize "Content-Length" set by user. + // If "Content-Length" is not set, set it to the length of content. + os << "Content-Length: " << content->length() << BRPC_CRLF; + } + } else { + if (!transfer_encoding) { + if (content_length) { + h->RemoveHeader("Content-Length"); + } + // Never use "Content-Length" set by user. + // Always set Content-Length since lighttpd requires the header to be + // set to 0 for empty content. + os << "Content-Length: " << content->length() << BRPC_CRLF; + } + } + } + } + if (!is_invalid_content && !h->content_type().empty()) { + os << "Content-Type: " << h->content_type() + << BRPC_CRLF; + } + for (HttpHeader::HeaderIterator it = h->HeaderBegin(); + it != h->HeaderEnd(); ++it) { + os << it->first << ": " << it->second << BRPC_CRLF; + } + os << BRPC_CRLF; // CRLF before content + os.move_to(*response); + + // https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.2 + // The HEAD method is identical to GET except that the server MUST NOT + // send a message body in the response (i.e., the response terminates at + // the end of the header section). + if (!is_invalid_content && !is_head_req && content) { + response->append(butil::IOBuf::Movable(*content)); + } +} +#undef BRPC_CRLF + +} // namespace brpc diff --git a/src/brpc/details/http_message.h b/src/brpc/details/http_message.h new file mode 100644 index 0000000..ae4a016 --- /dev/null +++ b/src/brpc/details/http_message.h @@ -0,0 +1,159 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_HTTP_MESSAGE_H +#define BRPC_HTTP_MESSAGE_H + +#include // std::unique_ptr +#include // std::string +#include "butil/macros.h" +#include "butil/iobuf.h" // butil::IOBuf +#include "butil/scoped_lock.h" // butil::unique_lock +#include "butil/endpoint.h" +#include "brpc/details/http_parser.h" // http_parser +#include "brpc/http_header.h" // HttpHeader +#include "brpc/progressive_reader.h" // ProgressiveReader + + +namespace brpc { + +enum HttpParserStage { + HTTP_ON_MESSAGE_BEGIN, + HTTP_ON_URL, + HTTP_ON_STATUS, + HTTP_ON_HEADER_FIELD, + HTTP_ON_HEADER_VALUE, + HTTP_ON_HEADERS_COMPLETE, + HTTP_ON_BODY, + HTTP_ON_MESSAGE_COMPLETE +}; + +class HttpMessage { +public: + // If read_body_progressively is true, the body will be read progressively + // by using SetBodyReader(). + explicit HttpMessage(bool read_body_progressively = false, + HttpMethod request_method = HTTP_METHOD_GET); + ~HttpMessage(); + + const butil::IOBuf &body() const { return _body; } + butil::IOBuf &body() { return _body; } + + // Parse from array, length=0 is treated as EOF. + // Returns bytes parsed, -1 on failure. + ssize_t ParseFromArray(const char *data, const size_t length); + + // Parse from butil::IOBuf. + // Emtpy `buf' is sliently ignored, which is different from ParseFromArray. + // Returns bytes parsed, -1 on failure. + ssize_t ParseFromIOBuf(const butil::IOBuf &buf); + + bool Completed() const { return _stage == HTTP_ON_MESSAGE_COMPLETE; } + HttpParserStage stage() const { return _stage; } + + HttpMethod request_method() const { return _request_method; } + + HttpHeader& header() { return _header; } + const HttpHeader& header() const { return _header; } + size_t parsed_length() const { return _parsed_length; } + bool body_too_large() const { return _body_too_large; } + + // Http parser callback functions + static int on_message_begin(http_parser *); + static int on_url(http_parser *, const char *, const size_t); + static int on_status(http_parser*, const char *, const size_t); + static int on_header_field(http_parser *, const char *, const size_t); + static int on_header_value(http_parser *, const char *, const size_t); + // Returns -1 on error, 0 on success, 1 on success and skip body. + static int on_headers_complete(http_parser *); + static int on_body_cb(http_parser*, const char *, const size_t); + static int on_message_complete_cb(http_parser *); + + const http_parser& parser() const { return _parser; } + + bool read_body_progressively() const { return _read_body_progressively; } + + void set_read_body_progressively(bool read_body_progressively) { + _read_body_progressively = read_body_progressively; + } + + // Send new parts of the body to the reader. If the body already has some + // data, feed them to the reader immediately. + // Any error during the setting will destroy the reader. + void SetBodyReader(ProgressiveReader* r); + +protected: + int OnBody(const char* data, size_t size); + int OnMessageComplete(); + size_t _parsed_length{0}; + +private: + DISALLOW_COPY_AND_ASSIGN(HttpMessage); + int UnlockAndFlushToBodyReader(std::unique_lock& locked); + + HttpParserStage _stage{HTTP_ON_MESSAGE_BEGIN}; + std::string _url; + HttpMethod _request_method{HTTP_METHOD_GET}; + HttpHeader _header; + bool _read_body_progressively{false}; + // For mutual exclusion between on_body and SetBodyReader. + butil::Mutex _body_mutex; + // Read body progressively + ProgressiveReader* _body_reader{NULL}; + butil::IOBuf _body; + size_t _body_size{0}; + bool _body_too_large{false}; + + // Store the IOBuf information in `ParseFromIOBuf' + // for later zero-copy usage in `OnBody'. + const butil::IOBuf* _current_source_iobuf{NULL}; + const char* _current_block_base{NULL}; + size_t _parsed_block_size{0}; + + // Parser related members + struct http_parser _parser; + std::string _cur_header; + std::string *_cur_value{NULL}; + +protected: + // Only valid when -http_verbose is on + std::unique_ptr _vmsgbuilder; + size_t _vbodylen{0}; +}; + +std::ostream& operator<<(std::ostream& os, const http_parser& parser); + +// Serialize a http request. +// header: may be modified in some cases +// remote_side: used when "Host" is absent +// content: could be NULL. +void MakeRawHttpRequest(butil::IOBuf* request, + HttpHeader* header, + const butil::EndPoint& remote_side, + const butil::IOBuf* content); + +// Serialize a http response. +// header: may be modified in some cases +// content: cleared after usage. could be NULL. +void MakeRawHttpResponse(butil::IOBuf* response, + HttpHeader* header, + butil::IOBuf* content); + +} // namespace brpc + +#endif // BRPC_HTTP_MESSAGE_H diff --git a/src/brpc/details/http_parser.cpp b/src/brpc/details/http_parser.cpp new file mode 100644 index 0000000..a4aa4d1 --- /dev/null +++ b/src/brpc/details/http_parser.cpp @@ -0,0 +1,2466 @@ +/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev + * + * Additional changes are licensed under the same terms as NGINX and + * copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include "brpc/details/http_parser.h" +#include +#include +#include +#include +#include +#include + +#ifndef ULLONG_MAX +# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */ +#endif + +#ifndef MIN +# define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef ARRAY_SIZE +# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#endif + +#ifndef BIT_AT +# define BIT_AT(a, i) \ + (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ + (1 << ((unsigned int) (i) & 7)))) +#endif + +#ifndef ELEM_AT +# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v)) +#endif + +#define SET_ERRNO(e) \ +do { \ + parser->http_errno = (e); \ +} while(0) + + +/* Run the notify callback FOR, returning ER if it fails */ +#define CALLBACK_NOTIFY_(FOR, ER) \ +do { \ + assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ + \ + if (settings->on_##FOR) { \ + if (0 != settings->on_##FOR(parser)) { \ + SET_ERRNO(HPE_CB_##FOR); \ + } \ + \ + /* We either errored above or got paused; get out */ \ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \ + return (ER); \ + } \ + } \ +} while (0) + +/* Run the notify callback FOR and consume the current byte */ +#define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1) + +/* Run the notify callback FOR and don't consume the current byte */ +#define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data) + +/* Run data callback FOR with LEN bytes, returning ER if it fails */ +#define CALLBACK_DATA_(FOR, LEN, ER) \ +do { \ + assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ + \ + if (FOR##_mark) { \ + if (settings->on_##FOR) { \ + if (0 != settings->on_##FOR(parser, FOR##_mark, (LEN))) { \ + SET_ERRNO(HPE_CB_##FOR); \ + } \ + \ + /* We either errored above or got paused; get out */ \ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \ + return (ER); \ + } \ + } \ + FOR##_mark = NULL; \ + } \ +} while (0) + +/* Run the data callback FOR and consume the current byte */ +#define CALLBACK_DATA(FOR) \ + CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1) + +/* Run the data callback FOR and don't consume the current byte */ +#define CALLBACK_DATA_NOADVANCE(FOR) \ + CALLBACK_DATA_(FOR, p - FOR##_mark, p - data) + +/* Set the mark FOR; non-destructive if mark is already set */ +#define MARK(FOR) \ +do { \ + if (!FOR##_mark) { \ + FOR##_mark = p; \ + } \ +} while (0) + + +#define PROXY_CONNECTION "proxy-connection" +#define CONNECTION "connection" +#define CONTENT_LENGTH "content-length" +#define TRANSFER_ENCODING "transfer-encoding" +#define UPGRADE "upgrade" +#define CHUNKED "chunked" +#define KEEP_ALIVE "keep-alive" +#define CLOSE "close" + + +namespace brpc { + +static const char *method_strings[] = + { +#define XX(num, name, string) #string, + HTTP_METHOD_MAP(XX) +#undef XX + }; + + +/* Tokens as defined by rfc 2616. Also lowercases them. + * token = 1* + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + */ +static const char tokens[256] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0, '!', 0, '#', '$', '%', '&', '\'', +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 0, 0, '*', '+', 0, '-', '.', 0, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + '0', '1', '2', '3', '4', '5', '6', '7', +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + '8', '9', 0, 0, 0, 0, 0, 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 'x', 'y', 'z', 0, 0, 0, '^', '_', +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 'x', 'y', 'z', 0, '|', 0, '~', 0 }; + + +static const int8_t unhex[256] = + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + }; + + +#if HTTP_PARSER_STRICT +# define T(v) 0 +#else +# define T(v) v +#endif + + +static const uint8_t normal_url_char[32] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, }; + +#undef T + +enum state + { s_dead = 1 /* important that this is > 0 */ + + , s_start_req_or_res + , s_res_or_resp_H + , s_start_res + , s_res_H + , s_res_HT + , s_res_HTT + , s_res_HTTP + , s_res_first_http_major + , s_res_http_major + , s_res_first_http_minor + , s_res_http_minor + , s_res_first_status_code + , s_res_status_code + , s_res_status_start + , s_res_status + , s_res_line_almost_done + + , s_start_req + + , s_req_method + , s_req_spaces_before_url + , s_req_scheme + , s_req_scheme_slash + , s_req_scheme_slash_slash + , s_req_server_start + , s_req_server + , s_req_server_with_at + , s_req_path + , s_req_query_string_start + , s_req_query_string + , s_req_fragment_start + , s_req_fragment + , s_req_http_start + , s_req_http_H + , s_req_http_HT + , s_req_http_HTT + , s_req_http_HTTP + , s_req_first_http_major + , s_req_http_major + , s_req_first_http_minor + , s_req_http_minor + , s_req_line_almost_done + + , s_header_field_start + , s_header_field + , s_header_value_discard_ws + , s_header_value_discard_ws_almost_done + , s_header_value_discard_lws + , s_header_value_start + , s_header_value + , s_header_value_lws + + , s_header_almost_done + + , s_chunk_size_start + , s_chunk_size + , s_chunk_parameters + , s_chunk_size_almost_done + + , s_headers_almost_done + , s_headers_done + + /* Important: 's_headers_done' must be the last 'header' state. All + * states beyond this must be 'body' states. It is used for overflow + * checking. See the PARSING_HEADER() macro. + */ + + , s_chunk_data + , s_chunk_data_almost_done + , s_chunk_data_done + + , s_body_identity + , s_body_identity_eof + + , s_message_done + }; + +int is_failed_after_queries(const http_parser* parser) { + return parser->state >= s_req_query_string_start; +} +int is_failed_after_http_version(const http_parser* parser) { + return parser->state > s_req_http_major; +} + +const char* http_parser_state_name(unsigned int state) { + switch (state) { + case s_dead: return "s_dead"; + case s_start_req_or_res: return "s_start_req_or_res"; + case s_res_or_resp_H: return "s_res_or_resp_H"; + case s_start_res: return "s_start_res"; + case s_res_H: return "s_res_H"; + case s_res_HT: return "s_res_HT"; + case s_res_HTT: return "s_res_HTT"; + case s_res_HTTP: return "s_res_HTTP"; + case s_res_first_http_major: return "s_res_first_http_major"; + case s_res_http_major: return "s_res_http_major"; + case s_res_first_http_minor: return "s_res_first_http_minor"; + case s_res_http_minor: return "s_res_http_minor"; + case s_res_first_status_code: return "s_res_first_status_code"; + case s_res_status_code: return "s_res_status_code"; + case s_res_status_start: return "s_res_status_start"; + case s_res_status: return "s_res_status"; + case s_res_line_almost_done: return "s_res_line_almost_done"; + case s_start_req: return "s_start_req"; + case s_req_method: return "s_req_method"; + case s_req_spaces_before_url: return "s_req_spaces_before_url"; + case s_req_scheme: return "s_req_scheme"; + case s_req_scheme_slash: return "s_req_scheme_slash"; + case s_req_scheme_slash_slash: return "s_req_scheme_slash_slash"; + case s_req_server_start: return "s_req_server_start"; + case s_req_server: return "s_req_server"; + case s_req_server_with_at: return "s_req_server_with_at"; + case s_req_path: return "s_req_path"; + case s_req_query_string_start: return "s_req_query_string_start"; + case s_req_query_string: return "s_req_query_string"; + case s_req_fragment_start: return "s_req_fragment_start"; + case s_req_fragment: return "s_req_fragment"; + case s_req_http_start: return "s_req_http_start"; + case s_req_http_H: return "s_req_http_H"; + case s_req_http_HT: return "s_req_http_HT"; + case s_req_http_HTT: return "s_req_http_HTT"; + case s_req_http_HTTP: return "s_req_http_HTTP"; + case s_req_first_http_major: return "s_req_first_http_major"; + case s_req_http_major: return "s_req_http_major"; + case s_req_first_http_minor: return "s_req_first_http_minor"; + case s_req_http_minor: return "s_req_http_minor"; + case s_req_line_almost_done: return "s_req_line_almost_done"; + case s_header_field_start: return "s_header_field_start"; + case s_header_field: return "s_header_field"; + case s_header_value_discard_ws: return "s_header_value_discard_ws"; + case s_header_value_discard_ws_almost_done: + return "s_header_value_discard_ws_almost_done"; + case s_header_value_discard_lws: return "s_header_value_discard_lws"; + case s_header_value_start: return "s_header_value_start"; + case s_header_value: return "s_header_value"; + case s_header_value_lws: return "s_header_value_lws"; + case s_header_almost_done: return "s_header_almost_done"; + case s_chunk_size_start: return "s_chunk_size_start"; + case s_chunk_size: return "s_chunk_size"; + case s_chunk_parameters: return "s_chunk_parameters"; + case s_chunk_size_almost_done: return "s_chunk_size_almost_done"; + case s_headers_almost_done: return "s_headers_almost_done"; + case s_headers_done: return "s_headers_done"; + case s_chunk_data: return "s_chunk_data"; + case s_chunk_data_almost_done: return "s_chunk_data_almost_done"; + case s_chunk_data_done: return "s_chunk_data_done"; + case s_body_identity: return "s_body_identity"; + case s_body_identity_eof: return "s_body_identity_eof"; + case s_message_done: return "s_message_done"; + } + return "s_unknown"; +} + +#define PARSING_HEADER(state) (state <= s_headers_done) + + +enum header_states + { h_general = 0 + , h_C + , h_CO + , h_CON + + , h_matching_connection + , h_matching_proxy_connection + , h_matching_content_length + , h_matching_transfer_encoding + , h_matching_upgrade + + , h_connection + , h_content_length + , h_transfer_encoding + , h_upgrade + + , h_matching_transfer_encoding_token_start + , h_matching_transfer_encoding_chunked + , h_matching_transfer_encoding_token + + , h_matching_connection_keep_alive + , h_matching_connection_close + + , h_transfer_encoding_chunked + , h_connection_keep_alive + , h_connection_close + }; + +const char* http_parser_header_state_name(unsigned int header_state) { + switch (header_state) { + case h_general: return "h_general"; + case h_C: return "h_C"; + case h_CO: return "h_CO"; + case h_CON: return "h_CON"; + case h_matching_connection: return "h_matching_connection"; + case h_matching_proxy_connection: return "h_matching_proxy_connection"; + case h_matching_content_length: return "h_matching_content_length"; + case h_matching_transfer_encoding: return "h_matching_transfer_encoding"; + case h_matching_upgrade: return "h_matching_upgrade"; + case h_connection: return "h_connection"; + case h_content_length: return "h_content_length"; + case h_transfer_encoding: return "h_transfer_encoding"; + case h_upgrade: return "h_upgrade"; + case h_matching_transfer_encoding_chunked: return "h_matching_transfer_encoding_chunked"; + case h_matching_connection_keep_alive: return "h_matching_connection_keep_alive"; + case h_matching_connection_close: return "h_matching_connection_close"; + case h_transfer_encoding_chunked: return "h_transfer_encoding_chunked"; + case h_connection_keep_alive: return "h_connection_keep_alive"; + case h_connection_close: return "h_connection_close"; + } + return "h_unknown"; +} + +const char* http_parser_type_name(enum http_parser_type type) { + switch (type) { + case HTTP_REQUEST: return "HTTP_REQUEST"; + case HTTP_RESPONSE: return "HTTP_RESPONSE"; + case HTTP_BOTH: return "HTTP_BOTH"; + } + return "UNKNOWN_TYPE"; +} + +enum http_host_state + { + s_http_host_dead = 1 + , s_http_userinfo_start + , s_http_userinfo + , s_http_host_start + , s_http_host_v6_start + , s_http_host + , s_http_host_v6 + , s_http_host_v6_end + , s_http_host_port_start + , s_http_host_port +}; + +/* Macros for character classes; depends on strict-mode */ +#define CR '\r' +#define LF '\n' +#define LOWER(c) (unsigned char)(c | 0x20) +#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') +#define IS_NUM(c) ((c) >= '0' && (c) <= '9') +#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) +#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) +#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ + (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ + (c) == ')') +#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \ + (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ + (c) == '$' || (c) == ',') + +/* NOTE(gejun): Not use strict mode for these macros since the additional + * characeters seem to be OK. + */ +#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) +#define IS_HOST_CHAR(c) \ + (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') +/* NOTE(gejun): Enable utf8 which is compatible with ASCII. Services in baidu + * are likely to pass chinese characters in url. + */ +#define IS_URL_CHAR(c) \ + (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80)) + +// Called by ParseRestfulPath() in restful.cpp +bool is_url_char(char c) { return IS_URL_CHAR(c); } + +#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res) + +/** + * Verify that a char is a valid visible (printable) US-ASCII + * character or %x80-FF + **/ +#define IS_HEADER_CHAR(ch) \ + (ch == CR || ch == LF || ch == 9 || ((unsigned char)ch > 31 && ch != 127)) + +#if BRPC_HTTP_PARSER_STRICT +# define STRICT_CHECK(cond) \ +do { \ + if (cond) { \ + SET_ERRNO(HPE_STRICT); \ + goto error; \ + } \ +} while (0) +# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) +#else +# define STRICT_CHECK(cond) +# define NEW_MESSAGE() start_state +#endif + + +/* Map errno values to strings for human-readable output */ +#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s }, +static struct { + const char *name; + const char *description; +} http_strerror_tab[] = { + HTTP_ERRNO_MAP(HTTP_STRERROR_GEN) +}; +#undef HTTP_STRERROR_GEN + +int http_message_needs_eof(const http_parser *parser); + +/* Our URL parser. + * + * This is designed to be shared by http_parser_execute() for URL validation, + * hence it has a state transition + byte-for-byte interface. In addition, it + * is meant to be embedded in http_parser_parse_url(), which does the dirty + * work of turning state transitions URL components for its API. + * + * This function should only be invoked with non-space characters. It is + * assumed that the caller cares about (and can detect) the transition between + * URL and non-URL states by looking for these. + */ +static enum state +parse_url_char(enum state s, const char ch) +{ + if (ch == ' ' || ch == '\r' || ch == '\n') { + return s_dead; + } + +#if BRPC_HTTP_PARSER_STRICT + if (ch == '\t' || ch == '\f') { + return s_dead; + } +#endif + + switch (s) { + case s_req_spaces_before_url: + /* Proxied requests are followed by scheme of an absolute URI (alpha). + * All methods except CONNECT are followed by '/' or '*'. + */ + + if (ch == '/' || ch == '*') { + return s_req_path; + } + + if (IS_ALPHA(ch)) { + return s_req_scheme; + } + + break; + + case s_req_scheme: + if (IS_ALPHA(ch)) { + return s; + } + + if (ch == ':') { + return s_req_scheme_slash; + } + + break; + + case s_req_scheme_slash: + if (ch == '/') { + return s_req_scheme_slash_slash; + } + + break; + + case s_req_scheme_slash_slash: + if (ch == '/') { + return s_req_server_start; + } + + break; + + case s_req_server_with_at: + if (ch == '@') { + return s_dead; + } + + /* FALLTHROUGH */ + case s_req_server_start: + case s_req_server: + if (ch == '/') { + return s_req_path; + } + + if (ch == '?') { + return s_req_query_string_start; + } + + if (ch == '@') { + return s_req_server_with_at; + } + + if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { + return s_req_server; + } + + break; + + case s_req_path: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + return s_req_query_string_start; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_query_string_start: + case s_req_query_string: + if (IS_URL_CHAR(ch)) { + return s_req_query_string; + } + switch (ch) { + case '?': + /* allow extra '?' in query string */ + return s_req_query_string; + case '#': + return s_req_fragment_start; + } + break; + + case s_req_fragment_start: + if (IS_URL_CHAR(ch)) { + return s_req_fragment; + } + switch (ch) { + case '?': + return s_req_fragment; + case '#': + return s; + } + break; + + case s_req_fragment: + if (IS_URL_CHAR(ch)) { + return s; + } + switch (ch) { + case '?': + case '#': + return s; + } + break; + + default: + break; + } + + /* We should never fall out of the switch above unless there's an error */ + return s_dead; +} + +size_t http_parser_execute (http_parser *parser, + const http_parser_settings *settings, + const char *data, + size_t len) +{ + char c, ch; + int8_t unhex_val; + const char *p = data; + const char *header_field_mark = 0; + const char *header_value_mark = 0; + const char *url_mark = 0; + const char *body_mark = 0; + const char *status_mark = 0; + const unsigned int lenient = parser->lenient_http_headers; + const unsigned int allow_chunked_length = parser->allow_chunked_length; + + /* We're in an error state. Don't bother doing anything. */ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + return 0; + } + + if (len == 0) { + switch (parser->state) { + case s_body_identity_eof: + /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if + * we got paused. + */ + CALLBACK_NOTIFY_NOADVANCE(message_complete); + return 0; + + case s_dead: + case s_start_req_or_res: + case s_start_res: + case s_start_req: + return 0; + + default: + SET_ERRNO(HPE_INVALID_EOF_STATE); + return 1; + } + } + + + if (parser->state == s_header_field) + header_field_mark = data; + if (parser->state == s_header_value) + header_value_mark = data; + switch (parser->state) { + case s_req_path: + case s_req_scheme: + case s_req_scheme_slash: + case s_req_scheme_slash_slash: + case s_req_server_start: + case s_req_server: + case s_req_server_with_at: + case s_req_query_string_start: + case s_req_query_string: + case s_req_fragment_start: + case s_req_fragment: + url_mark = data; + break; + case s_res_status: + status_mark = data; + break; + } + + for (p=data; p != data + len; p++) { + ch = *p; + + if (PARSING_HEADER(parser->state)) { + ++parser->nread; + /* Don't allow the total size of the HTTP headers (including the status + * line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect + * embedders against denial-of-service attacks where the attacker feeds + * us a never-ending header that the embedder keeps buffering. + * + * This check is arguably the responsibility of embedders but we're doing + * it on the embedder's behalf because most won't bother and this way we + * make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger + * than any reasonable request or response so this should never affect + * day-to-day operation. + */ + if (parser->nread > (BRPC_HTTP_MAX_HEADER_SIZE)) { + SET_ERRNO(HPE_HEADER_OVERFLOW); + goto error; + } + } + + reexecute_byte: + switch (parser->state) { + + case s_dead: + /* this state is used after a 'Connection: close' message + * the parser will error out if it reads another message + */ + if (ch == CR || ch == LF) + break; + + SET_ERRNO(HPE_CLOSED_CONNECTION); + goto error; + + case s_start_req_or_res: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->uses_transfer_encoding = 0; + parser->content_length = ULLONG_MAX; + + if (ch == 'H') { + parser->state = s_res_or_resp_H; + + CALLBACK_NOTIFY(message_begin); + } else { + parser->type = HTTP_REQUEST; + parser->state = s_start_req; + goto reexecute_byte; + } + + break; + } + + case s_res_or_resp_H: + if (ch == 'T') { + parser->type = HTTP_RESPONSE; + parser->state = s_res_HT; + } else { + if (ch != 'E') { + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + parser->type = HTTP_REQUEST; + parser->method = HTTP_HEAD; + parser->index = 2; + parser->state = s_req_method; + } + break; + + case s_start_res: + { + parser->flags = 0; + parser->uses_transfer_encoding = 0; + parser->content_length = ULLONG_MAX; + + switch (ch) { + case 'H': + parser->state = s_res_H; + break; + + case CR: + case LF: + break; + + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + CALLBACK_NOTIFY(message_begin); + break; + } + + case s_res_H: + STRICT_CHECK(ch != 'T'); + parser->state = s_res_HT; + break; + + case s_res_HT: + STRICT_CHECK(ch != 'T'); + parser->state = s_res_HTT; + break; + + case s_res_HTT: + STRICT_CHECK(ch != 'P'); + parser->state = s_res_HTTP; + break; + + case s_res_HTTP: + STRICT_CHECK(ch != '/'); + parser->state = s_res_first_http_major; + break; + + case s_res_first_http_major: + if (ch < '0' || ch > '9') { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + parser->state = s_res_http_major; + break; + + /* major HTTP version or dot */ + case s_res_http_major: + { + if (ch == '.') { + parser->state = s_res_first_http_minor; + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (parser->http_major > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_res_first_http_minor: + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + parser->state = s_res_http_minor; + break; + + /* minor HTTP version or end of request line */ + case s_res_http_minor: + { + if (ch == ' ') { + parser->state = s_res_first_status_code; + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (parser->http_minor > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + case s_res_first_status_code: + { + if (!IS_NUM(ch)) { + if (ch == ' ') { + break; + } + + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + parser->status_code = ch - '0'; + parser->state = s_res_status_code; + break; + } + + case s_res_status_code: + { + if (!IS_NUM(ch)) { + switch (ch) { + case ' ': + parser->state = s_res_status_start; + break; + case CR: + parser->state = s_res_line_almost_done; + break; + case LF: + parser->state = s_header_field_start; + break; + default: + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + break; + } + + parser->status_code *= 10; + parser->status_code += ch - '0'; + + if (parser->status_code > 999) { + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + + break; + } + + case s_res_status_start: + { + if (ch == CR) { + parser->state = s_res_line_almost_done; + break; + } + + if (ch == LF) { + parser->state = s_header_field_start; + break; + } + + MARK(status); + parser->state = s_res_status; + parser->index = 0; + break; + } + + case s_res_status: + if (ch == CR) { + parser->state = s_res_line_almost_done; + CALLBACK_DATA(status); + break; + } + + if (ch == LF) { + parser->state = s_header_field_start; + CALLBACK_DATA(status); + break; + } + + break; + + case s_res_line_almost_done: + STRICT_CHECK(ch != LF); + parser->state = s_header_field_start; + break; + + case s_start_req: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->uses_transfer_encoding = 0; + parser->content_length = ULLONG_MAX; + + if (!IS_ALPHA(ch)) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + parser->method = (enum http_method) 0; + parser->index = 1; + switch (ch) { + case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break; + case 'D': parser->method = HTTP_DELETE; break; + case 'G': parser->method = HTTP_GET; break; + case 'H': parser->method = HTTP_HEAD; break; + case 'L': parser->method = HTTP_LOCK; break; + case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break; + case 'N': parser->method = HTTP_NOTIFY; break; + case 'O': parser->method = HTTP_OPTIONS; break; + case 'P': parser->method = HTTP_POST; + /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ + break; + case 'R': parser->method = HTTP_REPORT; break; + case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break; + case 'T': parser->method = HTTP_TRACE; break; + case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break; + default: + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + parser->state = s_req_method; + + CALLBACK_NOTIFY(message_begin); + + break; + } + + case s_req_method: + { + const char *matcher; + if (ch == '\0') { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + matcher = method_strings[parser->method]; + if (ch == ' ' && matcher[parser->index] == '\0') { + parser->state = s_req_spaces_before_url; + } else if (ch == matcher[parser->index]) { + ; /* nada */ + } else if (parser->method == HTTP_CONNECT) { + if (parser->index == 1 && ch == 'H') { + parser->method = HTTP_CHECKOUT; + } else if (parser->index == 2 && ch == 'P') { + parser->method = HTTP_COPY; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + } else if (parser->method == HTTP_MKCOL) { + if (parser->index == 1 && ch == 'O') { + parser->method = HTTP_MOVE; + } else if (parser->index == 1 && ch == 'E') { + parser->method = HTTP_MERGE; + } else if (parser->index == 1 && ch == '-') { + parser->method = HTTP_MSEARCH; + } else if (parser->index == 2 && ch == 'A') { + parser->method = HTTP_MKACTIVITY; + } else if (parser->index == 3 && ch == 'A') { + parser->method = HTTP_MKCALENDAR; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + } else if (parser->method == HTTP_SUBSCRIBE) { + if (parser->index == 1 && ch == 'E') { + parser->method = HTTP_SEARCH; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + } else if (parser->index == 1 && parser->method == HTTP_POST) { + if (ch == 'R') { + parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */ + } else if (ch == 'U') { + parser->method = HTTP_PUT; /* or HTTP_PURGE */ + } else if (ch == 'A') { + parser->method = HTTP_PATCH; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + } else if (parser->index == 2) { + if (parser->method == HTTP_PUT) { + if (ch == 'R') { + parser->method = HTTP_PURGE; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + } else if (parser->method == HTTP_UNLOCK) { + if (ch == 'S') { + parser->method = HTTP_UNSUBSCRIBE; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') { + parser->method = HTTP_PROPPATCH; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + ++parser->index; + break; + } + + case s_req_spaces_before_url: + { + if (ch == ' ') break; + + MARK(url); + if (parser->method == HTTP_CONNECT) { + parser->state = s_req_server_start; + } + + /* Don't change parser->state when the new state is s_dead otherwise + parser->state is always s_dead after failure which is not + informational for debugging. The parser is known to be failed by + checking http_errno. + */ + enum state new_state = parse_url_char((enum state)parser->state, ch); + if (new_state == s_dead) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + parser->state = new_state; + + break; + } + + case s_req_scheme: + case s_req_scheme_slash: + case s_req_scheme_slash_slash: + case s_req_server_start: + { + enum state new_state; + switch (ch) { + /* No whitespace allowed here */ + case ' ': + case CR: + case LF: + SET_ERRNO(HPE_INVALID_URL); + goto error; + default: + new_state = parse_url_char((enum state)parser->state, ch); + if (new_state == s_dead) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + parser->state = new_state; + } + + break; + } + + case s_req_server: + case s_req_server_with_at: + case s_req_path: + case s_req_query_string_start: + case s_req_query_string: + case s_req_fragment_start: + case s_req_fragment: + { + enum state new_state; + switch (ch) { + case CR: + case LF: + parser->http_major = 0; + parser->http_minor = 9; + parser->state = (ch == CR) ? + s_req_line_almost_done : + s_header_field_start; + CALLBACK_DATA(url); + break; + case ' ': + parser->state = s_req_http_start; + CALLBACK_DATA(url); + break; + default: + new_state = parse_url_char((enum state)parser->state, ch); + if (new_state == s_dead) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + parser->state = new_state; + } + break; + } + + case s_req_http_start: + switch (ch) { + case 'H': + parser->state = s_req_http_H; + break; + case ' ': + break; + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + break; + + case s_req_http_H: + STRICT_CHECK(ch != 'T'); + parser->state = s_req_http_HT; + break; + + case s_req_http_HT: + STRICT_CHECK(ch != 'T'); + parser->state = s_req_http_HTT; + break; + + case s_req_http_HTT: + STRICT_CHECK(ch != 'P'); + parser->state = s_req_http_HTTP; + break; + + case s_req_http_HTTP: + STRICT_CHECK(ch != '/'); + parser->state = s_req_first_http_major; + break; + + /* first digit of major HTTP version */ + case s_req_first_http_major: + if (ch < '1' || ch > '9') { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + parser->state = s_req_http_major; + break; + + /* major HTTP version or dot */ + case s_req_http_major: + { + if (ch == '.') { + parser->state = s_req_first_http_minor; + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (parser->http_major > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_req_first_http_minor: + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + parser->state = s_req_http_minor; + break; + + /* minor HTTP version or end of request line */ + case s_req_http_minor: + { + if (ch == CR) { + parser->state = s_req_line_almost_done; + break; + } + + if (ch == LF) { + parser->state = s_header_field_start; + break; + } + + /* XXX allow spaces after digit? */ + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (parser->http_minor > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* end of request line */ + case s_req_line_almost_done: + { + if (ch != LF) { + SET_ERRNO(HPE_LF_EXPECTED); + goto error; + } + + parser->state = s_header_field_start; + break; + } + + case s_header_field_start: + { + if (ch == CR) { + parser->state = s_headers_almost_done; + break; + } + + if (ch == LF) { + /* they might be just sending \n instead of \r\n so this would be + * the second \n to denote the end of headers*/ + parser->state = s_headers_almost_done; + goto reexecute_byte; + } + + c = TOKEN(ch); + + if (!c) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + MARK(header_field); + + parser->index = 0; + parser->state = s_header_field; + + switch (c) { + case 'c': + parser->header_state = h_C; + break; + + case 'p': + parser->header_state = h_matching_proxy_connection; + break; + + case 't': + parser->header_state = h_matching_transfer_encoding; + break; + + case 'u': + parser->header_state = h_matching_upgrade; + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_field: + { + c = TOKEN(ch); + + if (c) { + switch (parser->header_state) { + case h_general: + break; + + case h_C: + parser->index++; + parser->header_state = (c == 'o' ? h_CO : h_general); + break; + + case h_CO: + parser->index++; + parser->header_state = (c == 'n' ? h_CON : h_general); + break; + + case h_CON: + parser->index++; + switch (c) { + case 'n': + parser->header_state = h_matching_connection; + break; + case 't': + parser->header_state = h_matching_content_length; + break; + default: + parser->header_state = h_general; + break; + } + break; + + /* connection */ + + case h_matching_connection: + parser->index++; + if (parser->index > sizeof(CONNECTION)-1 + || c != CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONNECTION)-2) { + parser->header_state = h_connection; + } + break; + + /* proxy-connection */ + + case h_matching_proxy_connection: + parser->index++; + if (parser->index > sizeof(PROXY_CONNECTION)-1 + || c != PROXY_CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(PROXY_CONNECTION)-2) { + parser->header_state = h_connection; + } + break; + + /* content-length */ + + case h_matching_content_length: + parser->index++; + if (parser->index > sizeof(CONTENT_LENGTH)-1 + || c != CONTENT_LENGTH[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONTENT_LENGTH)-2) { + parser->header_state = h_content_length; + } + break; + + /* transfer-encoding */ + + case h_matching_transfer_encoding: + parser->index++; + if (parser->index > sizeof(TRANSFER_ENCODING)-1 + || c != TRANSFER_ENCODING[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) { + parser->header_state = h_transfer_encoding; + parser->uses_transfer_encoding = 1; + } + break; + + /* upgrade */ + + case h_matching_upgrade: + parser->index++; + if (parser->index > sizeof(UPGRADE)-1 + || c != UPGRADE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(UPGRADE)-2) { + parser->header_state = h_upgrade; + } + break; + + case h_connection: + case h_content_length: + case h_transfer_encoding: + case h_upgrade: + if (ch != ' ') parser->header_state = h_general; + break; + + default: + assert(0 && "Unknown header_state"); + break; + } + break; + } + + if (ch == ':') { + parser->state = s_header_value_discard_ws; + CALLBACK_DATA(header_field); + break; + } + + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + case s_header_value_discard_ws: + if (ch == ' ' || ch == '\t') break; + + if (ch == CR) { + parser->state = s_header_value_discard_ws_almost_done; + break; + } + + if (ch == LF) { + parser->state = s_header_value_discard_lws; + break; + } + + /* FALLTHROUGH */ + + case s_header_value_start: + { + MARK(header_value); + + parser->state = s_header_value; + parser->index = 0; + + c = LOWER(ch); + + switch (parser->header_state) { + case h_upgrade: + parser->flags |= F_UPGRADE; + parser->header_state = h_general; + break; + + case h_transfer_encoding: + /* looking for 'Transfer-Encoding: chunked' */ + if ('c' == c) { + parser->header_state = h_matching_transfer_encoding_chunked; + } else { + parser->header_state = h_matching_transfer_encoding_token; + } + break; + + /* Multi-value `Transfer-Encoding` header */ + case h_matching_transfer_encoding_token_start: + break; + + case h_content_length: + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + if (parser->flags & F_CONTENTLENGTH) { + SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); + goto error; + } + + parser->flags |= F_CONTENTLENGTH; + parser->content_length = ch - '0'; + break; + + case h_connection: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + parser->header_state = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + parser->header_state = h_matching_connection_close; + } else { + parser->header_state = h_general; + } + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_value: + { + + if (ch == CR) { + parser->state = s_header_almost_done; + CALLBACK_DATA(header_value); + break; + } + + if (ch == LF) { + parser->state = s_header_almost_done; + CALLBACK_DATA_NOADVANCE(header_value); + goto reexecute_byte; + } + + if (!lenient && !IS_HEADER_CHAR(ch)) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + c = LOWER(ch); + + switch (parser->header_state) { + case h_general: + break; + + case h_connection: + case h_transfer_encoding: + assert(0 && "Shouldn't get here."); + break; + + case h_content_length: + { + uint64_t t; + + if (ch == ' ') break; + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + t = parser->content_length; + t *= 10; + t += ch - '0'; + + /* Overflow? Test against a conservative limit for simplicity. */ + if ((ULLONG_MAX - 10) / 10 < parser->content_length) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = t; + break; + } + + /* Transfer-Encoding: chunked */ + case h_matching_transfer_encoding_token_start: + /* looking for 'Transfer-Encoding: chunked' */ + if ('c' == c) { + parser->header_state = h_matching_transfer_encoding_chunked; + } else if (TOKEN(c)) { + /* NOTE(gejun): Not use strict mode for these macros since the additional + * characeters seem to be OK. + */ + + /* TODO(indutny): similar code below does this, but why? + * At the very least it seems to be inconsistent given that + * h_matching_transfer_encoding_token does not check for + * `STRICT_TOKEN` + */ + parser->header_state = h_matching_transfer_encoding_token; + } else if (c == ' ' || c == '\t') { + /* Skip lws */ + } else { + parser->header_state = h_general; + } + break; + + /* Transfer-Encoding: chunked */ + case h_matching_transfer_encoding_chunked: + parser->index++; + if (parser->index > sizeof(CHUNKED)-1 + || c != CHUNKED[parser->index]) { + parser->header_state = h_matching_transfer_encoding_token; + } else if (parser->index == sizeof(CHUNKED)-2) { + parser->header_state = h_transfer_encoding_chunked; + } + break; + + case h_matching_transfer_encoding_token: + if (ch == ',') { + parser->header_state = h_matching_transfer_encoding_token_start; + parser->index = 0; + } + break; + + /* looking for 'Connection: keep-alive' */ + case h_matching_connection_keep_alive: + parser->index++; + if (parser->index > sizeof(KEEP_ALIVE)-1 + || c != KEEP_ALIVE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(KEEP_ALIVE)-2) { + parser->header_state = h_connection_keep_alive; + } + break; + + /* looking for 'Connection: close' */ + case h_matching_connection_close: + parser->index++; + if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CLOSE)-2) { + parser->header_state = h_connection_close; + } + break; + + case h_transfer_encoding_chunked: + if (ch != ' ') parser->header_state = h_matching_transfer_encoding_token; + break; + + case h_connection_keep_alive: + case h_connection_close: + if (ch != ' ') parser->header_state = h_general; + break; + + default: + parser->state = s_header_value; + parser->header_state = h_general; + break; + } + break; + } + + case s_header_almost_done: + { + STRICT_CHECK(ch != LF); + + parser->state = s_header_value_lws; + break; + } + + case s_header_value_lws: + { + if (ch == ' ' || ch == '\t') { + parser->state = s_header_value_start; + goto reexecute_byte; + } + + /* finished the header */ + switch (parser->header_state) { + case h_connection_keep_alive: + parser->flags |= F_CONNECTION_KEEP_ALIVE; + break; + case h_connection_close: + parser->flags |= F_CONNECTION_CLOSE; + break; + case h_transfer_encoding_chunked: + parser->flags |= F_CHUNKED; + break; + default: + break; + } + + parser->state = s_header_field_start; + goto reexecute_byte; + } + + case s_header_value_discard_ws_almost_done: + { + STRICT_CHECK(ch != LF); + parser->state = s_header_value_discard_lws; + break; + } + + case s_header_value_discard_lws: + { + if (ch == ' ' || ch == '\t') { + parser->state = s_header_value_discard_ws; + break; + } else { + /* header value was empty */ + MARK(header_value); + parser->state = s_header_field_start; + CALLBACK_DATA_NOADVANCE(header_value); + goto reexecute_byte; + } + } + + case s_headers_almost_done: + { + STRICT_CHECK(ch != LF); + + if (parser->flags & F_TRAILING) { + /* End of a chunked request */ + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + break; + } + + /* Cannot use transfer-encoding and a content-length header together + per the HTTP specification. (RFC 7230 Section 3.3.3) */ + if ((parser->uses_transfer_encoding == 1) && + (parser->flags & F_CONTENTLENGTH)) { + /* Allow it for lenient parsing as long as `Transfer-Encoding` is + * not `chunked` or allow_length_with_encoding is set + */ + if (parser->flags & F_CHUNKED) { + if (!allow_chunked_length) { + SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); + goto error; + } + } else if (!lenient) { + SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); + goto error; + } + } + + parser->state = s_headers_done; + + /* Here we call the headers_complete callback. This is somewhat + * different than other callbacks because if the user returns 1, we + * will interpret that as saying that this message has no body. This + * is needed for the annoying case of recieving a response to a HEAD + * request. + * + * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so + * we have to simulate it by handling a change in errno below. + */ + if (settings->on_headers_complete) { + switch (settings->on_headers_complete(parser)) { + case 0: + break; + + case 1: + parser->flags |= F_SKIPBODY; + break; + + default: + SET_ERRNO(HPE_CB_headers_complete); + return p - data; /* Error */ + } + } + + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + return p - data; + } + + goto reexecute_byte; + } + + case s_headers_done: + { + STRICT_CHECK(ch != LF); + + parser->nread = 0; + + /* Exit, the rest of the connect is in a different protocol. */ + if (parser->method == HTTP_CONNECT) { + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + return (p - data) + 1; + } + + if (parser->flags & F_SKIPBODY) { + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + } else if (parser->flags & F_CHUNKED) { + /* chunked encoding - ignore Content-Length header */ + parser->state = s_chunk_size_start; + } else if (parser->uses_transfer_encoding == 1) { + if (parser->type == HTTP_REQUEST && !lenient) { + /* RFC 7230 3.3.3 */ + /* If a Transfer-Encoding header field + * is present in a request and the chunked transfer coding is not + * the final encoding, the message body length cannot be determined + * reliably; the server MUST respond with the 400 (Bad Request) + * status code and then close the connection. + */ + SET_ERRNO(HPE_INVALID_TRANSFER_ENCODING); + return (p - data); /* Error */ + } else { + /* RFC 7230 3.3.3 */ + /* If a Transfer-Encoding header field is present in a response and + * the chunked transfer coding is not the final encoding, the + * message body length is determined by reading the connection until + * it is closed by the server. + */ + parser->state = s_body_identity_eof; + } + } else { + if (parser->content_length == 0) { + /* Content-Length header given but zero: Content-Length: 0\r\n */ + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + } else if (parser->content_length != ULLONG_MAX) { + /* Content-Length header given and non-zero */ + parser->state = s_body_identity; + } else { + if (parser->type == HTTP_REQUEST || + !http_message_needs_eof(parser)) { + /* Assume content-length 0 - read the next */ + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + } else { + /* Read body until EOF */ + parser->state = s_body_identity_eof; + } + } + } + + break; + } + + case s_body_identity: + { + uint64_t to_read = MIN(parser->content_length, + (uint64_t) ((data + len) - p)); + + assert(parser->content_length != 0 + && parser->content_length != ULLONG_MAX); + + /* The difference between advancing content_length and p is because + * the latter will automaticaly advance on the next loop iteration. + * Further, if content_length ends up at 0, we want to see the last + * byte again for our message complete callback. + */ + MARK(body); + parser->content_length -= to_read; + p += to_read - 1; + + if (parser->content_length == 0) { + parser->state = s_message_done; + + /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. + * + * The alternative to doing this is to wait for the next byte to + * trigger the data callback, just as in every other case. The + * problem with this is that this makes it difficult for the test + * harness to distinguish between complete-on-EOF and + * complete-on-length. It's not clear that this distinction is + * important for applications, but let's keep it for now. + */ + CALLBACK_DATA_(body, p - body_mark + 1, p - data); + goto reexecute_byte; + } + + break; + } + + /* read until EOF */ + case s_body_identity_eof: + MARK(body); + p = data + len - 1; + + break; + + case s_message_done: + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + break; + + case s_chunk_size_start: + { + assert(parser->nread == 1); + assert(parser->flags & F_CHUNKED); + + unhex_val = unhex[(unsigned char)ch]; + if (unhex_val == -1) { + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + parser->content_length = unhex_val; + parser->state = s_chunk_size; + break; + } + + case s_chunk_size: + { + uint64_t t; + + assert(parser->flags & F_CHUNKED); + + if (ch == CR) { + parser->state = s_chunk_size_almost_done; + break; + } + + unhex_val = unhex[(unsigned char)ch]; + + if (unhex_val == -1) { + if (ch == ';' || ch == ' ') { + parser->state = s_chunk_parameters; + break; + } + + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + t = parser->content_length; + t *= 16; + t += unhex_val; + + /* Overflow? Test against a conservative limit for simplicity. */ + if ((ULLONG_MAX - 16) / 16 < parser->content_length) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = t; + break; + } + + case s_chunk_parameters: + { + assert(parser->flags & F_CHUNKED); + /* just ignore this shit. TODO check for overflow */ + if (ch == CR) { + parser->state = s_chunk_size_almost_done; + break; + } + break; + } + + case s_chunk_size_almost_done: + { + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + + parser->nread = 0; + + if (parser->content_length == 0) { + parser->flags |= F_TRAILING; + parser->state = s_header_field_start; + } else { + parser->state = s_chunk_data; + } + break; + } + + case s_chunk_data: + { + uint64_t to_read = MIN(parser->content_length, + (uint64_t) ((data + len) - p)); + + assert(parser->flags & F_CHUNKED); + assert(parser->content_length != 0 + && parser->content_length != ULLONG_MAX); + + /* See the explanation in s_body_identity for why the content + * length and data pointers are managed this way. + */ + MARK(body); + parser->content_length -= to_read; + p += to_read - 1; + + if (parser->content_length == 0) { + parser->state = s_chunk_data_almost_done; + } + + break; + } + + case s_chunk_data_almost_done: + assert(parser->flags & F_CHUNKED); + assert(parser->content_length == 0); + STRICT_CHECK(ch != CR); + parser->state = s_chunk_data_done; + CALLBACK_DATA(body); + break; + + case s_chunk_data_done: + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + parser->nread = 0; + parser->state = s_chunk_size_start; + break; + + default: + assert(0 && "unhandled state"); + SET_ERRNO(HPE_INVALID_INTERNAL_STATE); + goto error; + } + } + + /* Run callbacks for any marks that we have leftover after we ran our of + * bytes. There should be at most one of these set, so it's OK to invoke + * them in series (unset marks will not result in callbacks). + * + * We use the NOADVANCE() variety of callbacks here because 'p' has already + * overflowed 'data' and this allows us to correct for the off-by-one that + * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p' + * value that's in-bounds). + */ + + assert(((header_field_mark ? 1 : 0) + + (header_value_mark ? 1 : 0) + + (url_mark ? 1 : 0) + + (body_mark ? 1 : 0) + + (status_mark ? 1 : 0)) <= 1); + + CALLBACK_DATA_NOADVANCE(header_field); + CALLBACK_DATA_NOADVANCE(header_value); + CALLBACK_DATA_NOADVANCE(url); + CALLBACK_DATA_NOADVANCE(body); + CALLBACK_DATA_NOADVANCE(status); + + return len; + +error: + if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { + SET_ERRNO(HPE_UNKNOWN); + } + + return (p - data); +} + + +/* Does the parser need to see an EOF to find the end of the message? */ +int +http_message_needs_eof (const http_parser *parser) +{ + if (parser->type == HTTP_REQUEST) { + return 0; + } + + /* See RFC 2616 section 4.4 */ + if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ + parser->status_code == 204 || /* No Content */ + parser->status_code == 304 || /* Not Modified */ + parser->flags & F_SKIPBODY) { /* response to a HEAD request */ + return 0; + } + + /* RFC 7230 3.3.3, see `s_headers_almost_done` */ + if ((parser->uses_transfer_encoding == 1) && + (parser->flags & F_CHUNKED) == 0) { + return 1; + } + + if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) { + return 0; + } + + return 1; +} + + +int +http_should_keep_alive (const http_parser *parser) +{ + if (parser->http_major > 0 && parser->http_minor > 0) { + /* HTTP/1.1 */ + if (parser->flags & F_CONNECTION_CLOSE) { + return 0; + } + } else { + /* HTTP/1.0 or earlier */ + if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { + return 0; + } + } + + return !http_message_needs_eof(parser); +} + + +const char * +http_method_str (enum http_method m) +{ + return ELEM_AT(method_strings, m, ""); +} + + +void +http_parser_init (http_parser *parser, enum http_parser_type t) +{ + void *data = parser->data; /* preserve application data */ + memset(parser, 0, sizeof(*parser)); + parser->data = data; + parser->type = t; + parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); + parser->http_errno = HPE_OK; +} + +const char * +http_errno_name(enum http_errno err) { + assert(err < (http_errno)(sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); + return http_strerror_tab[err].name; +} + +const char * +http_errno_description(enum http_errno err) { + assert(err < (http_errno)(sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); + return http_strerror_tab[err].description; +} + +static enum http_host_state +http_parse_host_char(enum http_host_state s, const char ch) { + switch(s) { + case s_http_userinfo: + case s_http_userinfo_start: + if (ch == '@') { + return s_http_host_start; + } + + if (IS_USERINFO_CHAR(ch)) { + return s_http_userinfo; + } + break; + + case s_http_host_start: + if (ch == '[') { + return s_http_host_v6_start; + } + + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + break; + + case s_http_host: + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + /* FALLTHROUGH */ + case s_http_host_v6_end: + if (ch == ':') { + return s_http_host_port_start; + } + + break; + + case s_http_host_v6: + if (ch == ']') { + return s_http_host_v6_end; + } + + /* FALLTHROUGH */ + case s_http_host_v6_start: + if (IS_HEX(ch) || ch == ':' || ch == '.') { + return s_http_host_v6; + } + + break; + + case s_http_host_port: + case s_http_host_port_start: + if (IS_NUM(ch)) { + return s_http_host_port; + } + + break; + + default: + break; + } + return s_http_host_dead; +} + +static int +http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { + enum http_host_state s; + + const char *p; + size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len; + + u->field_data[UF_HOST].len = 0; + + s = found_at ? s_http_userinfo_start : s_http_host_start; + + for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) { + enum http_host_state new_s = http_parse_host_char(s, *p); + + if (new_s == s_http_host_dead) { + return 1; + } + + switch(new_s) { + case s_http_host: + if (s != s_http_host) { + u->field_data[UF_HOST].off = p - buf; + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_v6: + if (s != s_http_host_v6) { + u->field_data[UF_HOST].off = p - buf; + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_port: + if (s != s_http_host_port) { + u->field_data[UF_PORT].off = p - buf; + u->field_data[UF_PORT].len = 0; + u->field_set |= (1 << UF_PORT); + } + u->field_data[UF_PORT].len++; + break; + + case s_http_userinfo: + if (s != s_http_userinfo) { + u->field_data[UF_USERINFO].off = p - buf ; + u->field_data[UF_USERINFO].len = 0; + u->field_set |= (1 << UF_USERINFO); + } + u->field_data[UF_USERINFO].len++; + break; + + default: + break; + } + s = new_s; + } + + /* Make sure we don't end somewhere unexpected */ + switch (s) { + case s_http_host_start: + case s_http_host_v6_start: + case s_http_host_v6: + case s_http_host_port_start: + case s_http_userinfo: + case s_http_userinfo_start: + return 1; + default: + break; + } + + return 0; +} + +int +http_parser_parse_url(const char *buf, size_t buflen, int is_connect, + struct http_parser_url *u) +{ + enum state s; + const char *p; + enum http_parser_url_fields uf, old_uf; + int found_at = 0; + + u->port = u->field_set = 0; + s = is_connect ? s_req_server_start : s_req_spaces_before_url; + old_uf = UF_MAX; + + for (p = buf; p < buf + buflen; p++) { + s = parse_url_char(s, *p); + + /* Figure out the next field that we're operating on */ + switch (s) { + case s_dead: + return 1; + + /* Skip delimeters */ + case s_req_scheme_slash: + case s_req_scheme_slash_slash: + case s_req_server_start: + case s_req_query_string_start: + case s_req_fragment_start: + continue; + + case s_req_scheme: + uf = UF_SCHEME; + break; + + case s_req_server_with_at: + found_at = 1; + // fall through + case s_req_server: + uf = UF_HOST; + break; + + case s_req_path: + uf = UF_PATH; + break; + + case s_req_query_string: + uf = UF_QUERY; + break; + + case s_req_fragment: + uf = UF_FRAGMENT; + break; + + default: + assert(!"Unexpected state"); + return 1; + } + + /* Nothing's changed; soldier on */ + if (uf == old_uf) { + u->field_data[uf].len++; + continue; + } + + u->field_data[uf].off = p - buf; + u->field_data[uf].len = 1; + + u->field_set |= (1 << uf); + old_uf = uf; + } + + /* host must be present if there is a scheme */ + /* parsing http:///toto will fail */ + if ((u->field_set & ((1 << UF_SCHEME) | (1 << UF_HOST))) != 0) { + if (http_parse_host(buf, u, found_at) != 0) { + return 1; + } + } + + /* CONNECT requests can only contain "hostname:port" */ + if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { + return 1; + } + + if (u->field_set & (1 << UF_PORT)) { + /* Don't bother with endp; we've already validated the string */ + unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10); + + /* Ports have a max value of 2^16 */ + if (v > 0xffff) { + return 1; + } + + u->port = (uint16_t) v; + } + + return 0; +} + +void +http_parser_pause(http_parser *parser, int paused) { + /* Users should only be pausing/unpausing a parser that is not in an error + * state. In non-debug builds, there's not much that we can do about this + * other than ignore it. + */ + if (HTTP_PARSER_ERRNO(parser) == HPE_OK || + HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) { + SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK); + } else { + assert(0 && "Attempting to pause parser in error state"); + } +} + +int +http_body_is_final(const struct http_parser *parser) { + return parser->state == s_message_done; +} + +unsigned long +http_parser_version(void) { + return HTTP_PARSER_VERSION_MAJOR * 0x10000 | + HTTP_PARSER_VERSION_MINOR * 0x00100 | + HTTP_PARSER_VERSION_PATCH * 0x00001; +} + +} // namespace brpc diff --git a/src/brpc/details/http_parser.h b/src/brpc/details/http_parser.h new file mode 100644 index 0000000..0ea4efd --- /dev/null +++ b/src/brpc/details/http_parser.h @@ -0,0 +1,341 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef BRPC_HTTP_PARSER_H +#define BRPC_HTTP_PARSER_H + +/* Also update SONAME in the Makefile whenever you change these. */ +#define HTTP_PARSER_VERSION_MAJOR 2 +#define HTTP_PARSER_VERSION_MINOR 3 +#define HTTP_PARSER_VERSION_PATCH 0 + +#include +#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600) +#include +#include +typedef __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#else +#include +#endif + +/* Compile with -DBRPC_HTTP_PARSER_STRICT=0 to make less checks, but run + * faster + */ +#ifndef BRPC_HTTP_PARSER_STRICT +# define BRPC_HTTP_PARSER_STRICT 1 +#endif + +/* Maximium header size allowed. If the macro is not defined + * before including this header then the default is used. To + * change the maximum header size, define the macro in the build + * environment (e.g. -DBRPC_HTTP_MAX_HEADER_SIZE=). To remove + * the effective limit on the size of the header, define the macro + * to a very large number (e.g. -DBRPC_HTTP_MAX_HEADER_SIZE=0x7fffffff) + */ +#ifndef BRPC_HTTP_MAX_HEADER_SIZE +# define BRPC_HTTP_MAX_HEADER_SIZE (80*1024) +#endif + + +namespace brpc { + +struct http_parser; + +/* Callbacks should return non-zero to indicate an error. The parser will + * then halt execution. + * + * The one exception is on_headers_complete. In a HTTP_RESPONSE parser + * returning '1' from on_headers_complete will tell the parser that it + * should not expect a body. This is used when receiving a response to a + * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: + * chunked' headers that indicate the presence of a body. + * + * http_data_cb does not return data chunks. It will be called arbitrarily + * many times for each string. E.G. you might get 10 callbacks for "on_url" + * each providing just a few characters more data. + */ +typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); +typedef int (*http_cb) (http_parser*); + + +/* Request Methods */ +#define HTTP_METHOD_MAP(XX) \ + XX(0, DELETE, DELETE) \ + XX(1, GET, GET) \ + XX(2, HEAD, HEAD) \ + XX(3, POST, POST) \ + XX(4, PUT, PUT) \ + /* pathological */ \ + XX(5, CONNECT, CONNECT) \ + XX(6, OPTIONS, OPTIONS) \ + XX(7, TRACE, TRACE) \ + /* webdav */ \ + XX(8, COPY, COPY) \ + XX(9, LOCK, LOCK) \ + XX(10, MKCOL, MKCOL) \ + XX(11, MOVE, MOVE) \ + XX(12, PROPFIND, PROPFIND) \ + XX(13, PROPPATCH, PROPPATCH) \ + XX(14, SEARCH, SEARCH) \ + XX(15, UNLOCK, UNLOCK) \ + /* subversion */ \ + XX(16, REPORT, REPORT) \ + XX(17, MKACTIVITY, MKACTIVITY) \ + XX(18, CHECKOUT, CHECKOUT) \ + XX(19, MERGE, MERGE) \ + /* upnp */ \ + XX(20, MSEARCH, M-SEARCH) \ + XX(21, NOTIFY, NOTIFY) \ + XX(22, SUBSCRIBE, SUBSCRIBE) \ + XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \ + /* RFC-5789 */ \ + XX(24, PATCH, PATCH) \ + XX(25, PURGE, PURGE) \ + /* CalDAV */ \ + XX(26, MKCALENDAR, MKCALENDAR) \ + +enum http_method + { +#define XX(num, name, string) HTTP_##name = num, + HTTP_METHOD_MAP(XX) +#undef XX + }; + + +enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; + + +/* Flag values for http_parser.flags field */ +enum http_parser_flags + { F_CHUNKED = 1 << 0 + , F_CONNECTION_KEEP_ALIVE = 1 << 1 + , F_CONNECTION_CLOSE = 1 << 2 + , F_TRAILING = 1 << 3 + , F_UPGRADE = 1 << 4 + , F_SKIPBODY = 1 << 5 + , F_CONTENTLENGTH = 1 << 6 + }; + + +/* Map for errno-related constants + * + * The provided argument should be a macro that takes 2 arguments. + */ +#define HTTP_ERRNO_MAP(XX) \ + /* No error */ \ + XX(OK, "success") \ + \ + /* Callback-related errors */ \ + XX(CB_message_begin, "the on_message_begin callback failed") \ + XX(CB_url, "the on_url callback failed") \ + XX(CB_header_field, "the on_header_field callback failed") \ + XX(CB_header_value, "the on_header_value callback failed") \ + XX(CB_headers_complete, "the on_headers_complete callback failed") \ + XX(CB_body, "the on_body callback failed") \ + XX(CB_message_complete, "the on_message_complete callback failed") \ + XX(CB_status, "the on_status callback failed") \ + \ + /* Parsing-related errors */ \ + XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ + XX(HEADER_OVERFLOW, \ + "too many header bytes seen; overflow detected") \ + XX(CLOSED_CONNECTION, \ + "data received after completed connection: close message") \ + XX(INVALID_VERSION, "invalid HTTP version") \ + XX(INVALID_STATUS, "invalid HTTP status code") \ + XX(INVALID_METHOD, "invalid HTTP method") \ + XX(INVALID_URL, "invalid URL") \ + XX(INVALID_HOST, "invalid host") \ + XX(INVALID_PORT, "invalid port") \ + XX(INVALID_PATH, "invalid path") \ + XX(INVALID_QUERY_STRING, "invalid query string") \ + XX(INVALID_FRAGMENT, "invalid fragment") \ + XX(LF_EXPECTED, "LF character expected") \ + XX(INVALID_HEADER_TOKEN, "invalid character in header") \ + XX(INVALID_CONTENT_LENGTH, \ + "invalid character in content-length header") \ + XX(UNEXPECTED_CONTENT_LENGTH, \ + "unexpected content-length header") \ + XX(INVALID_CHUNK_SIZE, \ + "invalid character in chunk size header") \ + XX(INVALID_CONSTANT, "invalid constant string") \ + XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ + XX(STRICT, "strict mode assertion failed") \ + XX(PAUSED, "parser is paused") \ + XX(UNKNOWN, "an unknown error occurred") \ + XX(INVALID_TRANSFER_ENCODING, \ + "request has invalid transfer-encoding") + + +/* Define HPE_* values for each errno value above */ +#define HTTP_ERRNO_GEN(n, s) HPE_##n, +enum http_errno { + HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) +}; +#undef HTTP_ERRNO_GEN + + +/* Get an http_errno value from an http_parser */ +#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) + + +struct http_parser { + /** PRIVATE **/ + unsigned int type : 2; /* enum http_parser_type */ + unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */ + unsigned int state : 7; /* enum state from http_parser.c */ + unsigned int header_state : 7; /* enum header_state from http_parser.c */ + unsigned int index : 5; /* index into current matcher */ + unsigned int uses_transfer_encoding : 1; /* Transfer-Encoding header is present */ + unsigned int allow_chunked_length : 1; /* Allow headers with both + * `Content-Length` and + * `Transfer-Encoding: chunked` set */ + unsigned int lenient_http_headers : 1; + + uint32_t nread; /* # bytes read in various scenarios */ + uint64_t content_length; /* # bytes in body. `(uint64_t) -1` (all bits one) + * if no Content-Length header. + */ + + /** READ-ONLY **/ + unsigned short http_major; + unsigned short http_minor; + unsigned int status_code : 16; /* responses only */ + unsigned int method : 8; /* requests only */ + unsigned int http_errno : 7; + unsigned int dummy : 1; + + /** PUBLIC **/ + void *data; /* A pointer to get hook to the "connection" or "socket" object */ +}; + + +struct http_parser_settings { + http_cb on_message_begin; + http_data_cb on_url; + http_data_cb on_status; + http_data_cb on_header_field; + http_data_cb on_header_value; + http_cb on_headers_complete; + http_data_cb on_body; + http_cb on_message_complete; +}; + + +enum http_parser_url_fields + { UF_SCHEME = 0 + , UF_HOST = 1 + , UF_PORT = 2 + , UF_PATH = 3 + , UF_QUERY = 4 + , UF_FRAGMENT = 5 + , UF_USERINFO = 6 + , UF_MAX = 7 + }; + + +/* Result structure for http_parser_parse_url(). + * + * Callers should index into field_data[] with UF_* values iff field_set + * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and + * because we probably have padding left over), we convert any port to + * a uint16_t. + */ +struct http_parser_url { + uint16_t field_set; /* Bitmask of (1 << UF_*) values */ + uint16_t port; /* Converted UF_PORT string */ + + struct { + uint16_t off; /* Offset into buffer in which field starts */ + uint16_t len; /* Length of run in buffer */ + } field_data[UF_MAX]; +}; + + +/* Returns the library version. Bits 16-23 contain the major version number, + * bits 8-15 the minor version number and bits 0-7 the patch level. + * Usage example: + * + * unsigned long version = http_parser_version(); + * unsigned major = (version >> 16) & 255; + * unsigned minor = (version >> 8) & 255; + * unsigned patch = version & 255; + * printf("http_parser v%u.%u.%u\n", major, minor, patch); + */ +unsigned long http_parser_version(void); + +void http_parser_init(http_parser *parser, enum http_parser_type type); + + +/* Executes the parser. Returns number of parsed bytes. Sets + * `parser->http_errno` on error. */ +size_t http_parser_execute(http_parser *parser, + const http_parser_settings *settings, + const char *data, + size_t len); + + +/* If http_should_keep_alive() in the on_headers_complete or + * on_message_complete callback returns 0, then this should be + * the last message on the connection. + * If you are the server, respond with the "Connection: close" header. + * If you are the client, close the connection. + */ +int http_should_keep_alive(const http_parser *parser); + +/* Returns a string version of the HTTP method. */ +const char *http_method_str(enum http_method m); + +/* Return a string name of the given error */ +const char *http_errno_name(enum http_errno err); + +/* Return a string description of the given error */ +const char *http_errno_description(enum http_errno err); + +/* Parse a URL; return nonzero on failure */ +int http_parser_parse_url(const char *buf, size_t buflen, + int is_connect, + struct http_parser_url *u); + +/* Pause or un-pause the parser; a nonzero value pauses */ +void http_parser_pause(http_parser *parser, int paused); + +/* Checks if this is the final chunk of the body. */ +int http_body_is_final(const http_parser *parser); + +/* Return a string name of the given type */ +const char* http_parser_type_name(enum http_parser_type type); + +/* Return a string name of the given state */ +const char* http_parser_state_name(unsigned int state); +const char* http_parser_header_state_name(unsigned int header_state); + +} // namespace brpc + + +#endif // BRPC_HTTP_PARSER_H diff --git a/src/brpc/details/jemalloc_profiler.cpp b/src/brpc/details/jemalloc_profiler.cpp new file mode 100644 index 0000000..fdd06fb --- /dev/null +++ b/src/brpc/details/jemalloc_profiler.cpp @@ -0,0 +1,358 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/controller.h" +#include "brpc/http_header.h" +#include "brpc/reloadable_flags.h" +#include "brpc/uri.h" +#include "butil/files/file_path.h" +#include "butil/iobuf.h" +#include "butil/popen.h" +#include "gflags/gflags.h" +#include "gflags/gflags_declare.h" +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +// weak symbol: resolved at runtime by the linker if we are using jemalloc, nullptr otherwise +int BAIDU_WEAK mallctl(const char*, void*, size_t*, void*, size_t); +void BAIDU_WEAK malloc_stats_print(void (*write_cb)(void *, const char *), void *cbopaque, const char *opts); +} + +namespace brpc { + +// https://jemalloc.net/jemalloc.3.html +DEFINE_bool(je_prof_active, false, "control jemalloc prof.active, jemalloc profiling enabled but inactive," + "it toggle profiling at any time during process running"); +DEFINE_int32(je_prof_dump, 0, "control jemalloc prof.dump, change this only dump profile"); +DEFINE_int32(je_prof_reset, 19, "control jemalloc prof.reset, reset all memory profile statistics, " + "and optionally update the sample rate, default 2^19 B"); + +DECLARE_int32(max_flame_graph_width); + +// define in src/brpc/builtin/pprof_service.cpp +extern int MakeProfName(ProfilingType type, char* buf, size_t buf_len); + +static bool HasInit(const std::string& fn) { + static std::set fns; + if (fns.count(fn) > 0) { + return true; + } + fns.insert(fn); + return false; +} + +bool HasJemalloc() { + return mallctl != nullptr; +} + +// env need MALLOC_CONF="prof:true" before process start +static bool HasEnableJemallocProfile() { + bool prof = false; + size_t len = sizeof(prof); + int ret = mallctl("opt.prof", &prof, &len, nullptr, 0); + if (ret != 0) { + LOG(WARNING) << "mallctl get opt.prof err, ret:" << ret; + return false; + } + return prof; +} + +static void WriteCb(void *opaque, const char *str) { + // maybe call n times WriteCb by single malloc_stats_print + static_cast(opaque)->append(str); +} + +std::string StatsPrint(const std::string& opts) { + if (malloc_stats_print == nullptr) { + return "your jemalloc no support malloc_stats_print"; + } + + std::string stat_str; + malloc_stats_print(WriteCb, &stat_str, opts.c_str()); + return stat_str; +} + +static int JeProfileActive(bool active) { + if (!HasJemalloc()) { + LOG(WARNING) << "no jemalloc"; + return -1; + } + + if (!HasEnableJemallocProfile()) { + LOG(WARNING) << "jemalloc have not set opt.prof before start"; + return -1; + } + + int ret = mallctl("prof.active", nullptr, nullptr, &active, sizeof(active)); + if (ret != 0) { + LOG(WARNING) << "mallctl set prof.active:" << active << " err, ret:" << ret; + return ret; + } + LOG(INFO) << "mallctl set prof.active:" << active << " succ"; + return 0; +} + +static std::string JeProfileDump() { + if (!HasJemalloc()) { + LOG(WARNING) << "no jemalloc"; + return ""; + } + + if (!HasEnableJemallocProfile()) { + LOG(WARNING) << "jemalloc have not set opt.prof before start"; + return ""; + } + + char prof_name[256]; + if (MakeProfName(PROFILING_HEAP, prof_name, sizeof(prof_name)) != 0) { + LOG(WARNING) << "Fail to create .prof file, " << berror(); + return ""; + } + butil::File::Error error; + const butil::FilePath dir = butil::FilePath(prof_name).DirName(); + if (!butil::CreateDirectoryAndGetError(dir, &error)) { + LOG(WARNING) << "Fail to create directory= " << dir.value(); + return ""; + } + + const char* p_prof_name = prof_name; + int ret = mallctl("prof.dump", NULL, NULL, (void*)&p_prof_name, sizeof(p_prof_name)); + if (ret != 0) { + LOG(WARNING) << "mallctl set prof.dump:" << p_prof_name << " err, ret:" << ret; + return ""; + } + LOG(INFO) << "heap profile dump:" << prof_name << " succ"; + return prof_name; +} + +static int JeProfileReset(size_t lg_sample) { + if (!HasJemalloc()) { + LOG(WARNING) << "no jemalloc"; + return -1; + } + + if (!HasEnableJemallocProfile()) { + LOG(WARNING) << "jemalloc have not set opt.prof before start"; + return -1; + } + + int ret = mallctl("prof.reset", nullptr, nullptr, &lg_sample, sizeof(lg_sample)); + if (ret != 0) { + LOG(WARNING) << "mallctl set prof.reset:" << lg_sample << " err, ret:" << ret; + return ret; + } + LOG(INFO) << "mallctl set prof.reset:" << lg_sample << " succ"; + return 0; +} + +// https://github.com/jemalloc/jemalloc/blob/5.3.0/bin/jeprof.in#L211-L222 +static const std::unordered_set g_extra_options_set = { + "inuse_space", "inuse_objects", "alloc_space", + "alloc_objects", "show_bytes", "drop_negative", + "total_delay", "contentions", "mean_delay" +}; + +std::string GlobalExtraOptionsString() { + std::string result; + result.reserve(64); + for (const auto& option : g_extra_options_set) { + result.append(option); + result.append(" "); + } + return result; +} + +void JeControlProfile(Controller* cntl) { + const brpc::URI& uri = cntl->http_request().uri(); + // http:ip:port/pprof/heap?display=(text|svg|stats|flamegraph)&extra_options=(inuse_space|inuse_objects..) + const std::string* uri_display = uri.GetQuery("display"); + + butil::IOBuf& buf = cntl->response_attachment(); + cntl->http_response().set_content_type("text/plain"); + + // support ip:port/pprof/heap?display=stats + if (uri_display != nullptr && *uri_display == "stats") { + const std::string* uri_opts = uri.GetQuery("opts"); + std::string opts = !uri_opts || uri_opts->empty() ? "Ja" : *uri_opts; + buf.append(StatsPrint(opts)); + return; + } + + if (!HasEnableJemallocProfile()) { + cntl->SetFailed(ENOMETHOD, "Heap profiler is not enabled, (no MALLOC_CONF=prof:true in env)"); + return; + } + + // only dump profile + const std::string prof_name = JeProfileDump(); + if (prof_name.empty()) { + cntl->SetFailed(-1, "Fail to dump profile"); + buf.append("\nFail to dump profile"); + return; + } + + // support jeprof ip:port/pprof/heap + if (uri_display == nullptr || uri_display->empty()) { + std::string content; + if (!butil::ReadFileToString(butil::FilePath(prof_name), &content)) { + LOG(WARNING) << "read " << prof_name << " fail"; + return; + } + buf.append(content); + return; + } + + // support curl/browser + buf.append(prof_name); + + std::string jeprof; + const char* f_jeprof = std::getenv("JEPROF_FILE"); + if (f_jeprof != nullptr && butil::PathExists(butil::FilePath(f_jeprof))) { + jeprof = f_jeprof; + } else { + LOG(WARNING) << "env JEPROF_FILE invalid"; + buf.append("\nenv JEPROF_FILE invalid"); + jeprof = "jeprof "; // use PATH + } + + char process_path[500] = {}; + ssize_t len = butil::GetProcessAbsolutePath(process_path, sizeof(process_path)); + if (len == -1) { + LOG(WARNING) << "GetProcessAbsolutePath of process err"; + buf.append("\nGetProcessAbsolutePath of process err"); + return; + } + const std::string process_file(process_path, len); + + std::string cmd_str = butil::string_printf( + "%s %s %s", jeprof.c_str(), process_file.c_str(), prof_name.c_str()); + + // https://github.com/jemalloc/jemalloc/blob/5.3.0/bin/jeprof.in#L211-L222 + // e.g: inuse_space, contentions + const std::string* uri_extra_options = uri.GetQuery("extra_options"); + if (uri_extra_options != nullptr && !uri_extra_options->empty()) { + if (g_extra_options_set.count(uri_extra_options->c_str()) > 0) { + butil::string_appendf(&cmd_str, " --%s", uri_extra_options->c_str()); + } else { + static std::string g_options_str = GlobalExtraOptionsString(); + LOG(WARNING) << "Unsupported jemalloc options=" << *uri_extra_options + << ", only support [" << g_options_str << "]"; + } + } + + bool display_img = false; + if (*uri_display == "svg") { + cmd_str += " --svg "; + display_img = true; + } else if (*uri_display == "flamegraph") { + const char* flamegraph_tool = getenv("FLAMEGRAPH_PL_PATH"); + if (!flamegraph_tool) { + LOG(WARNING) << " display: " << *uri_display + << " invalid, env FLAMEGRAPH_PL_PATH invalid"; + buf.append("\ndisplay:" + *uri_display + " invalid, env FLAMEGRAPH_PL_PATH invalid"); + return; + } + const int width_size = FLAGS_max_flame_graph_width > 0 ? FLAGS_max_flame_graph_width : 1200; + butil::string_appendf(&cmd_str, " --collapsed | %s --colors mem --width %d", + flamegraph_tool, width_size); + display_img = true; + } else if (*uri_display == "text") { + cmd_str += " --text "; + } else { + LOG(WARNING) << " display: " << *uri_display << " invalid"; + buf.append("\ndisplay:" + *uri_display + " invalid"); + return; + } + butil::IOBufBuilder builder; + if (butil::read_command_output(builder, cmd_str.c_str()) != 0) { + buf.append("\nread_command_output <" + cmd_str + "> fail"); + LOG(WARNING) << "read_command_output <" + cmd_str + "> fail"; + return; + } + + if (display_img) { + buf.swap(builder.buf()); + cntl->http_response().set_content_type("image/svg+xml"); + } else { + buf.append("\ncmd: " + cmd_str + "\n"); + buf.append(builder.buf()); + } +} + +static bool validate_je_prof_active(const char*, bool enable) { + if (!HasJemalloc()) { + return true; + } + + if (!HasInit(__func__)) { + return true; + } + + if (JeProfileActive(enable) != 0) { + LOG(WARNING) << "JeControlSample err"; + return false; + } + + return true; +} + +static bool validate_je_prof_dump(const char*, int32_t val) { + if (!HasJemalloc()) { + return true; + } + if (!HasInit(__func__)) { + return true; + } + + const std::string prof_name = JeProfileDump(); + if (prof_name.empty()) { + LOG(WARNING) << "Fail to dump profile"; + return false; + } + return true; +} + +static bool validate_je_prof_reset(const char*, int32_t val) { + if (!HasJemalloc()) { + return true; + } + if (!HasInit(__func__)) { + return true; + } + + if (JeProfileReset(val) != 0) { + LOG(WARNING) << "JeProfileReset err"; + return false; + } + + return true; +} + +// e.g: curl ip:port/flags/je_prof_active?setvalue=true or update flags in web +BRPC_VALIDATE_GFLAG(je_prof_active, validate_je_prof_active); +BRPC_VALIDATE_GFLAG(je_prof_dump, validate_je_prof_dump); +BRPC_VALIDATE_GFLAG(je_prof_reset, validate_je_prof_reset); + +} + diff --git a/src/brpc/details/jemalloc_profiler.h b/src/brpc/details/jemalloc_profiler.h new file mode 100644 index 0000000..8468065 --- /dev/null +++ b/src/brpc/details/jemalloc_profiler.h @@ -0,0 +1,34 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + + +#pragma once + +#include + +namespace brpc { + +bool HasJemalloc(); + +// opts: Ja +// more see ref: https://github.com/jemalloc/jemalloc/blob/dev/include/jemalloc/internal/stats.h#L9 +std::string StatsPrint(const std::string& opts); + +void JeControlProfile(Controller* cntl); + +} + diff --git a/src/brpc/details/load_balancer_with_naming.cpp b/src/brpc/details/load_balancer_with_naming.cpp new file mode 100644 index 0000000..7370a1f --- /dev/null +++ b/src/brpc/details/load_balancer_with_naming.cpp @@ -0,0 +1,68 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/details/load_balancer_with_naming.h" + + +namespace brpc { + +LoadBalancerWithNaming::~LoadBalancerWithNaming() { + if (_nsthread_ptr.get()) { + _nsthread_ptr->RemoveWatcher(this); + } +} + +int LoadBalancerWithNaming::Init(const char* ns_url, const char* lb_name, + const NamingServiceFilter* filter, + const GetNamingServiceThreadOptions* options) { + if (SharedLoadBalancer::Init(lb_name) != 0) { + return -1; + } + if (GetNamingServiceThread(&_nsthread_ptr, ns_url, options) != 0) { + LOG(ERROR) << "Fail to get NamingServiceThread"; + return -1; + } + if (_nsthread_ptr->AddWatcher(this, filter) != 0) { + LOG(ERROR) << "Fail to add watcher into _server_list"; + return -1; + } + return 0; +} + +void LoadBalancerWithNaming::OnAddedServers( + const std::vector& servers) { + AddServersInBatch(servers); +} + +void LoadBalancerWithNaming::OnRemovedServers( + const std::vector& servers) { + RemoveServersInBatch(servers); +} + +void LoadBalancerWithNaming::Describe(std::ostream& os, + const DescribeOptions& options) { + if (_nsthread_ptr) { + _nsthread_ptr->Describe(os, options); + } else { + os << "NULL"; + } + os << " lb="; + SharedLoadBalancer::Describe(os, options); +} + +} // namespace brpc diff --git a/src/brpc/details/load_balancer_with_naming.h b/src/brpc/details/load_balancer_with_naming.h new file mode 100644 index 0000000..9364d84 --- /dev/null +++ b/src/brpc/details/load_balancer_with_naming.h @@ -0,0 +1,51 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_LOAD_BALANCER_WITH_NAMING_H +#define BRPC_LOAD_BALANCER_WITH_NAMING_H + +#include "butil/intrusive_ptr.hpp" +#include "brpc/load_balancer.h" +#include "brpc/details/naming_service_thread.h" // NamingServiceWatcher + + +namespace brpc { + +class LoadBalancerWithNaming : public SharedLoadBalancer, + public NamingServiceWatcher { +public: + LoadBalancerWithNaming() {} + ~LoadBalancerWithNaming(); + + int Init(const char* ns_url, const char* lb_name, + const NamingServiceFilter* filter, + const GetNamingServiceThreadOptions* options); + + void OnAddedServers(const std::vector& servers); + void OnRemovedServers(const std::vector& servers); + + void Describe(std::ostream& os, const DescribeOptions& options); + +private: + butil::intrusive_ptr _nsthread_ptr; +}; + +} // namespace brpc + + +#endif // BRPC_LOAD_BALANCER_WITH_NAMING_H diff --git a/src/brpc/details/mesalink_ssl_helper.cpp b/src/brpc/details/mesalink_ssl_helper.cpp new file mode 100644 index 0000000..afc3861 --- /dev/null +++ b/src/brpc/details/mesalink_ssl_helper.cpp @@ -0,0 +1,402 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + + +#ifdef USE_MESALINK + +#include // recv +#include +#include +#include +#include +#include +#include +#include "butil/unique_ptr.h" +#include "butil/logging.h" +#include "butil/string_splitter.h" +#include "brpc/socket.h" +#include "brpc/ssl_options.h" +#include "brpc/details/ssl_helper.h" + +namespace brpc { + +static const char* const PEM_START = "-----BEGIN"; + +static bool IsPemString(const std::string& input) { + for (const char* s = input.c_str(); *s != '\0'; ++s) { + if (*s != '\n') { + return strncmp(s, PEM_START, strlen(PEM_START)) == 0; + } + } + return false; +} + +const char* SSLStateToString(SSLState s) { + switch (s) { + case SSL_UNKNOWN: + return "SSL_UNKNOWN"; + case SSL_OFF: + return "SSL_OFF"; + case SSL_CONNECTING: + return "SSL_CONNECTING"; + case SSL_CONNECTED: + return "SSL_CONNECTED"; + } + return "Bad SSLState"; +} + +static int ParseSSLProtocols(const std::string& str_protocol) { + int protocol_flag = 0; + butil::StringSplitter sp(str_protocol.data(), + str_protocol.data() + str_protocol.size(), ','); + for (; sp; ++sp) { + butil::StringPiece protocol(sp.field(), sp.length()); + protocol.trim_spaces(); + if (strncasecmp(protocol.data(), "SSLv3", protocol.size()) == 0 + || strncasecmp(protocol.data(), "TLSv1", protocol.size()) == 0 + || strncasecmp(protocol.data(), "TLSv1.1", protocol.size()) == 0) { + LOG(WARNING) << "Ignored insecure SSL/TLS protocol=" << protocol; + } else if (strncasecmp(protocol.data(), "TLSv1.2", protocol.size()) == 0) { + protocol_flag |= TLSv1_2; + } else { + LOG(ERROR) << "Unknown SSL protocol=" << protocol; + return -1; + } + } + return protocol_flag; +} + +std::ostream& operator<<(std::ostream& os, const SSLError& ssl) { + char buf[128]; // Should be enough + ERR_error_string_n(ssl.error, buf, sizeof(buf)); + return os << buf; +} + +std::ostream& operator<<(std::ostream& os, const CertInfo& cert) { + os << "certificate["; + if (IsPemString(cert.certificate)) { + size_t pos = cert.certificate.find('\n'); + if (pos == std::string::npos) { + pos = 0; + } else { + pos++; + } + os << cert.certificate.substr(pos, 16) << "..."; + } else { + os << cert.certificate; + } + + os << "] private-key["; + if (IsPemString(cert.private_key)) { + size_t pos = cert.private_key.find('\n'); + if (pos == std::string::npos) { + pos = 0; + } else { + pos++; + } + os << cert.private_key.substr(pos, 16) << "..."; + } else { + os << cert.private_key; + } + os << "]"; + return os; +} + +void ExtractHostnames(X509* x, std::vector* hostnames) { + STACK_OF(X509_NAME)* names = (STACK_OF(X509_NAME)*) + X509_get_alt_subject_names(x); + if (names) { + for (int i = 0; i < sk_X509_NAME_num(names); i++) { + char buf[255] = {0}; + X509_NAME* name = sk_X509_NAME_value(names, i); + if (X509_NAME_oneline(name, buf, 255)) { + std::string hostname(buf); + hostnames->push_back(hostname); + } + } + sk_X509_NAME_free(names); + } +} + +struct FreeSSL { + inline void operator()(SSL* ssl) const { + if (ssl != NULL) { + SSL_free(ssl); + } + } +}; + +struct FreeBIO { + inline void operator()(BIO* io) const { + if (io != NULL) { + BIO_free(io); + } + } +}; + +struct FreeX509 { + inline void operator()(X509* x) const { + if (x != NULL) { + X509_free(x); + } + } +}; + +struct FreeEVPKEY { + inline void operator()(EVP_PKEY* k) const { + if (k != NULL) { + EVP_PKEY_free(k); + } + } +}; + +static int LoadCertificate(SSL_CTX* ctx, + const std::string& certificate, + const std::string& private_key, + std::vector* hostnames) { + // Load the private key + if (IsPemString(private_key)) { + std::unique_ptr kbio( + BIO_new_mem_buf((void*)private_key.c_str(), -1)); + std::unique_ptr key( + PEM_read_bio_PrivateKey(kbio.get(), NULL, 0, NULL)); + if (SSL_CTX_use_PrivateKey(ctx, key.get()) != 1) { + LOG(ERROR) << "Fail to load " << private_key << ": " + << SSLError(ERR_get_error()); + return -1; + } + } else { + if (SSL_CTX_use_PrivateKey_file( + ctx, private_key.c_str(), SSL_FILETYPE_PEM) != 1) { + LOG(ERROR) << "Fail to load " << private_key << ": " + << SSLError(ERR_get_error()); + return -1; + } + } + + // Open & Read certificate + std::unique_ptr cbio; + if (IsPemString(certificate)) { + cbio.reset(BIO_new_mem_buf((void*)certificate.c_str(), -1)); + } else { + cbio.reset(BIO_new(BIO_s_file())); + if (BIO_read_filename(cbio.get(), certificate.c_str()) <= 0) { + LOG(ERROR) << "Fail to read " << certificate << ": " + << SSLError(ERR_get_error()); + return -1; + } + } + std::unique_ptr x( + PEM_read_bio_X509(cbio.get(), NULL, 0, NULL)); + if (!x) { + LOG(ERROR) << "Fail to parse " << certificate << ": " + << SSLError(ERR_get_error()); + return -1; + } + + // Load the main certificate + if (SSL_CTX_use_certificate(ctx, x.get()) != 1) { + LOG(ERROR) << "Fail to load " << certificate << ": " + << SSLError(ERR_get_error()); + return -1; + } + + // Load the certificate chain + //SSL_CTX_clear_chain_certs(ctx); + X509* ca = NULL; + while ((ca = PEM_read_bio_X509(cbio.get(), NULL, 0, NULL))) { + if (SSL_CTX_add_extra_chain_cert(ctx, ca) != 1) { + LOG(ERROR) << "Fail to load chain certificate in " + << certificate << ": " << SSLError(ERR_get_error()); + X509_free(ca); + return -1; + } + } + ERR_clear_error(); + + // Validate certificate and private key + if (SSL_CTX_check_private_key(ctx) != 1) { + LOG(ERROR) << "Fail to verify " << private_key << ": " + << SSLError(ERR_get_error()); + return -1; + } + + return 0; +} + +static int SetSSLOptions(SSL_CTX* ctx, const std::string& ciphers, + int protocols, const VerifyOptions& verify) { + if (verify.verify_depth > 0) { + std::string cafile = verify.ca_file_path; + if (!cafile.empty()) { + if (SSL_CTX_load_verify_locations(ctx, cafile.c_str(), NULL) == 0) { + LOG(ERROR) << "Fail to load CA file " << cafile + << ": " << SSLError(ERR_get_error()); + return -1; + } + } + SSL_CTX_set_verify(ctx, (SSL_VERIFY_PEER + | SSL_VERIFY_FAIL_IF_NO_PEER_CERT), NULL); + } else { + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); + } + + return 0; +} + +SSL_CTX* CreateClientSSLContext(const ChannelSSLOptions& options) { + std::unique_ptr ssl_ctx( + SSL_CTX_new(TLSv1_2_client_method())); + if (!ssl_ctx) { + LOG(ERROR) << "Fail to new SSL_CTX: " << SSLError(ERR_get_error()); + return NULL; + } + + if (!options.client_cert.certificate.empty() + && LoadCertificate(ssl_ctx.get(), + options.client_cert.certificate, + options.client_cert.private_key, NULL) != 0) { + return NULL; + } + + int protocols = ParseSSLProtocols(options.protocols); + if (protocols < 0 + || SetSSLOptions(ssl_ctx.get(), options.ciphers, + protocols, options.verify) != 0) { + return NULL; + } + + SSL_CTX_set_session_cache_mode(ssl_ctx.get(), SSL_SESS_CACHE_CLIENT); + return ssl_ctx.release(); +} + +SSL_CTX* CreateServerSSLContext(const std::string& certificate, + const std::string& private_key, + const ServerSSLOptions& options, + std::vector* hostnames) { + std::unique_ptr ssl_ctx( + SSL_CTX_new(TLSv1_2_server_method())); + if (!ssl_ctx) { + LOG(ERROR) << "Fail to new SSL_CTX: " << SSLError(ERR_get_error()); + return NULL; + } + + if (LoadCertificate(ssl_ctx.get(), certificate, + private_key, hostnames) != 0) { + return NULL; + } + + int protocols = TLSv1 | TLSv1_1 | TLSv1_2; + if (!options.disable_ssl3) { + protocols |= SSLv3; + } + if (SetSSLOptions(ssl_ctx.get(), options.ciphers, + protocols, options.verify) != 0) { + return NULL; + } + + /* SSL_CTX_set_timeout(ssl_ctx.get(), options.session_lifetime_s); */ + SSL_CTX_sess_set_cache_size(ssl_ctx.get(), options.session_cache_size); + + return ssl_ctx.release(); +} + +SSL* CreateSSLSession(SSL_CTX* ctx, SocketId id, int fd, bool server_mode) { + if (ctx == NULL) { + LOG(WARNING) << "Lack SSL_ctx to create an SSL session"; + return NULL; + } + SSL* ssl = SSL_new(ctx); + if (ssl == NULL) { + LOG(ERROR) << "Fail to SSL_new: " << SSLError(ERR_get_error()); + return NULL; + } + if (SSL_set_fd(ssl, fd) != 1) { + LOG(ERROR) << "Fail to SSL_set_fd: " << SSLError(ERR_get_error()); + SSL_free(ssl); + return NULL; + } + + if (server_mode) { + SSL_set_accept_state(ssl); + } else { + SSL_set_connect_state(ssl); + } + + return ssl; +} + +void AddBIOBuffer(SSL* ssl, int fd, int bufsize) { + // MesaLink uses buffered IO internally +} + +SSLState DetectSSLState(int fd, int* error_code) { + // Peek the first few bytes inside socket to detect whether + // it's an SSL connection. If it is, create an SSL session + // which will be used to read/write after + + // Header format of SSLv2 + // +-----------+------+----- + // | 2B header | 0x01 | etc. + // +-----------+------+----- + // The first bit of header is always 1, with the following + // 15 bits are the length of data + + // Header format of SSLv3 or TLSv1.0, 1.1, 1.2 + // +------+------------+-----------+------+----- + // | 0x16 | 2B version | 2B length | 0x01 | etc. + // +------+------------+-----------+------+----- + char header[6]; + const ssize_t nr = recv(fd, header, sizeof(header), MSG_PEEK); + if (nr < (ssize_t)sizeof(header)) { + if (nr < 0) { + if (errno == ENOTSOCK) { + return SSL_OFF; + } + *error_code = errno; // Including EAGAIN and EINTR + } else if (nr == 0) { // EOF + *error_code = 0; + } else { // Not enough data, need retry + *error_code = EAGAIN; + } + return SSL_UNKNOWN; + } + + if ((header[0] == 0x16 && header[5] == 0x01) // SSLv3 or TLSv1.0, 1.1, 1.2 + || ((header[0] & 0x80) == 0x80 && header[2] == 0x01)) { // SSLv2 + return SSL_CONNECTING; + } else { + return SSL_OFF; + } +} + +int SSLThreadInit() { + return 0; +} + +int SSLDHInit() { + return 0; +} + +void Print(std::ostream& os, SSL* ssl, const char* sep) { + os << "cipher=" << SSL_get_cipher_name(ssl) << sep + << "protocol=" << SSL_get_version(ssl) << sep; +} + +} // namespace brpc + +#endif // USE_MESALINK diff --git a/src/brpc/details/method_status.cpp b/src/brpc/details/method_status.cpp new file mode 100644 index 0000000..3bed6bf --- /dev/null +++ b/src/brpc/details/method_status.cpp @@ -0,0 +1,167 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/macros.h" +#include "brpc/controller.h" +#include "brpc/details/server_private_accessor.h" +#include "brpc/details/method_status.h" + +namespace brpc { + +static int cast_int(void* arg) { + return *(int*)arg; +} + +static int cast_cl(void* arg) { + auto cl = static_cast*>(arg)->get(); + if (cl) { + return cl->MaxConcurrency(); + } + return 0; +} + +MethodStatus::MethodStatus() + : _nconcurrency(0) + , _nconcurrency_bvar(cast_int, &_nconcurrency) + , _eps_bvar(&_nerror_bvar) + , _max_concurrency_bvar(cast_cl, &_cl) +{ +} + +MethodStatus::~MethodStatus() { +} + +int MethodStatus::Expose(const butil::StringPiece& prefix) { + if (_nconcurrency_bvar.expose_as(prefix, "concurrency") != 0) { + return -1; + } + if (_nerror_bvar.expose_as(prefix, "error") != 0) { + return -1; + } + if (_eps_bvar.expose_as(prefix, "eps") != 0) { + return -1; + } + if (_latency_rec.expose(prefix) != 0) { + return -1; + } + if (_cl) { + if (_max_concurrency_bvar.expose_as(prefix, "max_concurrency") != 0) { + return -1; + } + } + return 0; +} + +template +void OutputTextValue(std::ostream& os, + const char* prefix, + const T& value) { + os << prefix << value << "\n"; +} + +template +void OutputValue(std::ostream& os, + const char* prefix, + const std::string& bvar_name, + const T& value, + const DescribeOptions& options, + bool expand) { + if (options.use_html) { + os << "

" << prefix << "" + << value + << "

\n"; + } else { + return OutputTextValue(os, prefix, value); + } +} + +void MethodStatus::Describe( + std::ostream &os, const DescribeOptions& options) const { + // success requests + OutputValue(os, "count: ", _latency_rec.count_name(), _latency_rec.count(), + options, false); + const int64_t qps = _latency_rec.qps(); + const bool expand = (qps != 0); + OutputValue(os, "qps: ", _latency_rec.qps_name(), _latency_rec.qps(), + options, expand); + + // errorous requests + OutputValue(os, "error: ", _nerror_bvar.name(), _nerror_bvar.get_value(), + options, false); + OutputValue(os, "eps: ", _eps_bvar.name(), + _eps_bvar.get_value(1), options, false); + + // latencies + OutputValue(os, "latency: ", _latency_rec.latency_name(), + _latency_rec.latency(), options, false); + if (options.use_html) { + OutputValue(os, "latency_percentiles: ", + _latency_rec.latency_percentiles_name(), + _latency_rec.latency_percentiles(), options, false); + OutputValue(os, "latency_cdf: ", _latency_rec.latency_cdf_name(), + "click to view", options, expand); + } else { + OutputTextValue(os, "latency_50: ", + _latency_rec.latency_percentile(0.5)); + OutputTextValue(os, "latency_90: ", + _latency_rec.latency_percentile(0.9)); + OutputTextValue(os, "latency_99: ", + _latency_rec.latency_percentile(0.99)); + OutputTextValue(os, "latency_999: ", + _latency_rec.latency_percentile(0.999)); + OutputTextValue(os, "latency_9999: ", + _latency_rec.latency_percentile(0.9999)); + } + OutputValue(os, "max_latency: ", _latency_rec.max_latency_name(), + _latency_rec.max_latency(), options, false); + + // Concurrency + OutputValue(os, "concurrency: ", _nconcurrency_bvar.name(), + _nconcurrency, options, false); + if (_cl) { + OutputValue(os, "max_concurrency: ", _max_concurrency_bvar.name(), + MaxConcurrency(), options, false); + } +} + +void MethodStatus::SetConcurrencyLimiter(ConcurrencyLimiter* cl) { + _cl.reset(cl); +} + +int HandleResponseWritten(bthread_id_t id, void* data, int /*error_code*/) { + auto args = static_cast(data); + args->sent_us = butil::cpuwide_time_us(); + CHECK_EQ(0, bthread_id_unlock_and_destroy(id)); + return 0; +} + +ConcurrencyRemover::~ConcurrencyRemover() { + if (_status) { + _status->OnResponded(_c->ErrorCode(), butil::cpuwide_time_us() - _received_us); + _status = NULL; + } + ServerPrivateAccessor(_c->server()).RemoveConcurrency(_c); +} + +} // namespace brpc diff --git a/src/brpc/details/method_status.h b/src/brpc/details/method_status.h new file mode 100644 index 0000000..9b7f070 --- /dev/null +++ b/src/brpc/details/method_status.h @@ -0,0 +1,122 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_METHOD_STATUS_H +#define BRPC_METHOD_STATUS_H + +#include "butil/macros.h" // DISALLOW_COPY_AND_ASSIGN +#include "bvar/bvar.h" // vars +#include "brpc/describable.h" +#include "brpc/concurrency_limiter.h" + + +namespace brpc { + +class Controller; +class Server; +// Record accessing stats of a method. +class MethodStatus : public Describable { +public: + MethodStatus(); + ~MethodStatus(); + + // Call this function when the method is about to be called. + // Returns false when the method is overloaded. If rejected_cc is not + // NULL, it's set with the rejected concurrency. + bool OnRequested(int* rejected_cc = NULL, Controller* cntl = NULL); + + // Call this when the method just finished. + // `error_code' : The error code obtained from the controller. Equal to + // 0 when the call is successful. + // `latency_us' : microseconds taken by a successful call. Latency can + // be measured in this utility class as well, but the callsite often + // did the time keeping and the cost is better saved. + void OnResponded(int error_code, int64_t latency_us); + + // Expose internal vars. + // Return 0 on success, -1 otherwise. + int Expose(const butil::StringPiece& prefix); + + // Describe internal vars, used by /status + void Describe(std::ostream &os, const DescribeOptions&) const override; + + // Current max_concurrency of the method. + int MaxConcurrency() const { return _cl ? _cl->MaxConcurrency() : 0; } + +private: +friend class Server; + DISALLOW_COPY_AND_ASSIGN(MethodStatus); + + // Note: SetConcurrencyLimiter() is not thread safe and can only be called + // before the server is started. + void SetConcurrencyLimiter(ConcurrencyLimiter* cl); + + std::unique_ptr _cl; + butil::atomic _nconcurrency; + bvar::Adder _nerror_bvar; + bvar::LatencyRecorder _latency_rec; + bvar::PassiveStatus _nconcurrency_bvar; + bvar::PerSecond> _eps_bvar; + bvar::PassiveStatus _max_concurrency_bvar; +}; + +struct ResponseWriteInfo { + int64_t sent_us{0}; +}; + +int HandleResponseWritten(bthread_id_t id, void* data, int error_code); + +class ConcurrencyRemover { +public: + ConcurrencyRemover(MethodStatus* status, Controller* c, int64_t received_us) + : _status(status) , _c(c) , _received_us(received_us) {} + ~ConcurrencyRemover(); + +private: + DISALLOW_COPY_AND_ASSIGN(ConcurrencyRemover); + MethodStatus* _status; + Controller* _c; + int64_t _received_us; +}; + +inline bool MethodStatus::OnRequested(int* rejected_cc, Controller* cntl) { + const int cc = _nconcurrency.fetch_add(1, butil::memory_order_relaxed) + 1; + if (NULL == _cl || _cl->OnRequested(cc, cntl)) { + return true; + } + if (rejected_cc) { + *rejected_cc = cc; + } + return false; +} + +inline void MethodStatus::OnResponded(int error_code, int64_t latency) { + _nconcurrency.fetch_sub(1, butil::memory_order_relaxed); + if (0 == error_code) { + _latency_rec << latency; + } else { + _nerror_bvar << 1; + } + if (NULL != _cl) { + _cl->OnResponded(error_code, latency); + } +} + +} // namespace brpc + +#endif //BRPC_METHOD_STATUS_H diff --git a/src/brpc/details/naming_service_thread.cpp b/src/brpc/details/naming_service_thread.cpp new file mode 100644 index 0000000..7eb005e --- /dev/null +++ b/src/brpc/details/naming_service_thread.cpp @@ -0,0 +1,507 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include "bthread/butex.h" +#include "butil/scoped_lock.h" +#include "butil/logging.h" +#include "brpc/log.h" +#include "brpc/socket_map.h" +#include "brpc/details/naming_service_thread.h" + + +namespace brpc { + +struct NSKey { + std::string protocol; + std::string service_name; + ChannelSignature channel_signature; + + NSKey(const std::string& prot_in, + const std::string& service_in, + const ChannelSignature& sig) + : protocol(prot_in), service_name(service_in), channel_signature(sig) { + } +}; +struct NSKeyHasher { + size_t operator()(const NSKey& nskey) const { + size_t h = butil::DefaultHasher()(nskey.protocol); + h = h * 101 + butil::DefaultHasher()(nskey.service_name); + h = h * 101 + nskey.channel_signature.data[1]; + return h; + } +}; +inline bool operator==(const NSKey& k1, const NSKey& k2) { + return k1.protocol == k2.protocol && + k1.service_name == k2.service_name && + k1.channel_signature == k2.channel_signature; +} + +typedef butil::FlatMap NamingServiceMap; +// Construct on demand to make the code work before main() +static NamingServiceMap* g_nsthread_map = NULL; +static pthread_mutex_t g_nsthread_map_mutex = PTHREAD_MUTEX_INITIALIZER; + +NamingServiceThread::Actions::Actions(NamingServiceThread* owner) + : _owner(owner) + , _wait_id(INVALID_BTHREAD_ID) + , _has_wait_error(false) + , _wait_error(0) { + CHECK_EQ(0, bthread_id_create(&_wait_id, NULL, NULL)); +} + +NamingServiceThread::Actions::~Actions() { + // Remove all sockets from SocketMap + for (std::vector::const_iterator it = _last_servers.begin(); + it != _last_servers.end(); ++it) { + const SocketMapKey key(*it, _owner->_options.channel_signature); + SocketMapRemove(key); + } + EndWait(0); +} + +void NamingServiceThread::Actions::AddServers( + const std::vector&) { + // FIXME(gejun) + RELEASE_ASSERT_VERBOSE(false, "Not implemented"); +} + +void NamingServiceThread::Actions::RemoveServers( + const std::vector&) { + // FIXME(gejun) + RELEASE_ASSERT_VERBOSE(false, "Not implemented"); +} + +void NamingServiceThread::Actions::ResetServers( + const std::vector& servers) { + _servers.assign(servers.begin(), servers.end()); + + // Diff servers with _last_servers by comparing sorted vectors. + // Notice that _last_servers is always sorted. + std::sort(_servers.begin(), _servers.end()); + const size_t dedup_size = std::unique(_servers.begin(), _servers.end()) + - _servers.begin(); + if (dedup_size != _servers.size()) { + LOG(WARNING) << "Removed " << _servers.size() - dedup_size + << " duplicated servers"; + _servers.resize(dedup_size); + } + _added.resize(_servers.size()); + std::vector::iterator _added_end = + std::set_difference(_servers.begin(), _servers.end(), + _last_servers.begin(), _last_servers.end(), + _added.begin()); + _added.resize(_added_end - _added.begin()); + + _removed.resize(_last_servers.size()); + std::vector::iterator _removed_end = + std::set_difference(_last_servers.begin(), _last_servers.end(), + _servers.begin(), _servers.end(), + _removed.begin()); + _removed.resize(_removed_end - _removed.begin()); + + _added_sockets.clear(); + for (size_t i = 0; i < _added.size(); ++i) { + ServerNodeWithId tagged_id; + tagged_id.node = _added[i]; + // TODO: For each unique SocketMapKey (i.e. SSL settings), insert a new + // Socket. SocketMapKey may be passed through AddWatcher. Make sure + // to pick those Sockets with the right settings during OnAddedServers + const SocketMapKey key(_added[i], _owner->_options.channel_signature); + CHECK_EQ(0, SocketMapInsert(key, &tagged_id.id, + _owner->_options.socket_option)); + _added_sockets.push_back(tagged_id); + } + + _removed_sockets.clear(); + for (size_t i = 0; i < _removed.size(); ++i) { + ServerNodeWithId tagged_id; + tagged_id.node = _removed[i]; + const SocketMapKey key(_removed[i], _owner->_options.channel_signature); + CHECK_EQ(0, SocketMapFind(key, &tagged_id.id)); + _removed_sockets.push_back(tagged_id); + } + + // Refresh sockets + if (_removed_sockets.empty()) { + _sockets = _owner->_last_sockets; + } else { + std::sort(_removed_sockets.begin(), _removed_sockets.end()); + _sockets.resize(_owner->_last_sockets.size()); + std::vector::iterator _sockets_end = + std::set_difference( + _owner->_last_sockets.begin(), _owner->_last_sockets.end(), + _removed_sockets.begin(), _removed_sockets.end(), + _sockets.begin()); + _sockets.resize(_sockets_end - _sockets.begin()); + } + if (!_added_sockets.empty()) { + const size_t before_added = _sockets.size(); + std::sort(_added_sockets.begin(), _added_sockets.end()); + _sockets.insert(_sockets.end(), + _added_sockets.begin(), _added_sockets.end()); + std::inplace_merge(_sockets.begin(), _sockets.begin() + before_added, + _sockets.end()); + } + std::vector removed_ids; + ServerNodeWithId2ServerId(_removed_sockets, &removed_ids, NULL); + + { + BAIDU_SCOPED_LOCK(_owner->_mutex); + _last_servers.swap(_servers); + _owner->_last_sockets.swap(_sockets); + for (std::map::iterator + it = _owner->_watchers.begin(); + it != _owner->_watchers.end(); ++it) { + if (!_removed_sockets.empty()) { + it->first->OnRemovedServers(removed_ids); + } + + std::vector added_ids; + ServerNodeWithId2ServerId(_added_sockets, &added_ids, it->second); + if (!_added_sockets.empty()) { + it->first->OnAddedServers(added_ids); + } + } + } + + for (size_t i = 0; i < _removed.size(); ++i) { + // TODO: Remove all Sockets that have the same address in SocketMapKey.peer + // We may need another data structure to avoid linear cost + const SocketMapKey key(_removed[i], _owner->_options.channel_signature); + SocketMapRemove(key); + } + + if (!_removed.empty() || !_added.empty()) { + std::ostringstream info; + info << butil::class_name_str(*_owner->_ns) << "(\"" + << _owner->_service_name << "\"):"; + if (!_added.empty()) { + info << " added "<< _added.size(); + } + if (!_removed.empty()) { + info << " removed " << _removed.size(); + } + LOG(INFO) << info.str(); + } + + EndWait(servers.empty() ? ENODATA : 0); +} + +void NamingServiceThread::Actions::EndWait(int error_code) { + if (bthread_id_trylock(_wait_id, NULL) == 0) { + _wait_error = error_code; + _has_wait_error.store(true, butil::memory_order_release); + bthread_id_unlock_and_destroy(_wait_id); + } +} + +int NamingServiceThread::Actions::WaitForFirstBatchOfServers() { + // Wait can happen before signal in which case it returns non-zero, + // so we ignore return value here and use `_wait_error' instead + if (!_has_wait_error.load(butil::memory_order_acquire)) { + bthread_id_join(_wait_id); + } + return _wait_error; +} + +NamingServiceThread::NamingServiceThread() + : _tid(0) + , _ns(NULL) + , _actions(this) { +} + +NamingServiceThread::~NamingServiceThread() { + RPC_VLOG << "~NamingServiceThread(" << *this << ')'; + // Remove from g_nsthread_map first + if (!_protocol.empty()) { + const NSKey key(_protocol, _service_name, _options.channel_signature); + std::unique_lock mu(g_nsthread_map_mutex); + if (g_nsthread_map != NULL) { + NamingServiceThread** ptr = g_nsthread_map->seek(key); + if (ptr != NULL && *ptr == this) { + g_nsthread_map->erase(key); + } + } + } + if (_tid) { + bthread_stop(_tid); + bthread_join(_tid, NULL); + _tid = 0; + } + { + BAIDU_SCOPED_LOCK(_mutex); + std::vector to_be_removed; + ServerNodeWithId2ServerId(_last_sockets, &to_be_removed, NULL); + if (!_last_sockets.empty()) { + for (std::map::iterator + it = _watchers.begin(); it != _watchers.end(); ++it) { + it->first->OnRemovedServers(to_be_removed); + } + } + _watchers.clear(); + } + + if (_ns) { + _ns->Destroy(); + _ns = NULL; + } +} + +void* NamingServiceThread::RunThis(void* arg) { + static_cast(arg)->Run(); + return NULL; +} + +int NamingServiceThread::Start(NamingService* naming_service, + const std::string& protocol, + const std::string& service_name, + const GetNamingServiceThreadOptions* opt_in) { + if (naming_service == NULL) { + LOG(ERROR) << "Param[naming_service] is NULL"; + return -1; + } + _ns = naming_service; + _protocol = protocol; + _service_name = service_name; + if (opt_in) { + _options = *opt_in; + } + _last_sockets.clear(); + if (_ns->RunNamingServiceReturnsQuickly()) { + RunThis(this); + } else { + int rc = bthread_start_urgent(&_tid, NULL, RunThis, this); + if (rc) { + LOG(ERROR) << "Fail to create bthread: " << berror(rc); + return rc; + } + } + return WaitForFirstBatchOfServers(); +} + +int NamingServiceThread::WaitForFirstBatchOfServers() { + int rc = _actions.WaitForFirstBatchOfServers(); + if (rc == ENODATA && _options.succeed_without_server) { + if (_options.log_succeed_without_server) { + LOG(WARNING) << '`' << *this << "' is empty! RPC over the channel" + " will fail until servers appear"; + } + rc = 0; + } + if (rc) { + LOG(ERROR) << "Fail to WaitForFirstBatchOfServers: " << berror(rc); + return -1; + } + return 0; +} + +void NamingServiceThread::EndWait(int error_code) { + _actions.EndWait(error_code); +} + +void NamingServiceThread::ServerNodeWithId2ServerId( + const std::vector& src, + std::vector* dst, const NamingServiceFilter* filter) { + dst->reserve(src.size()); + for (std::vector::const_iterator + it = src.begin(); it != src.end(); ++it) { + if (filter && !filter->Accept(it->node)) { + continue; + } + ServerId socket; + socket.id = it->id; + socket.tag = it->node.tag; + dst->push_back(socket); + } +} + +int NamingServiceThread::AddWatcher(NamingServiceWatcher* watcher, + const NamingServiceFilter* filter) { + if (watcher == NULL) { + LOG(ERROR) << "Param[watcher] is NULL"; + return -1; + } + BAIDU_SCOPED_LOCK(_mutex); + if (_watchers.emplace(watcher, filter).second) { + if (!_last_sockets.empty()) { + std::vector added_ids; + ServerNodeWithId2ServerId(_last_sockets, &added_ids, filter); + watcher->OnAddedServers(added_ids); + } + return 0; + } + return -1; +} + +int NamingServiceThread::RemoveWatcher(NamingServiceWatcher* watcher) { + if (watcher == NULL) { + LOG(ERROR) << "Param[watcher] is NULL"; + return -1; + } + BAIDU_SCOPED_LOCK(_mutex); + if (_watchers.erase(watcher)) { + // Not call OnRemovedServers of the watcher because watcher can + // remove the sockets by itself and in most cases, removing + // sockets is useless. + return 0; + } + return -1; +} + +void NamingServiceThread::Run() { + int rc = _ns->RunNamingService(_service_name.c_str(), &_actions); + if (rc != 0) { + LOG(WARNING) << "Fail to run naming service: " << berror(rc); + if (rc == ENODATA) { + LOG(ERROR) << "RunNamingService should not return ENODATA, " + "change it to ESTOP"; + rc = ESTOP; + } + _actions.EndWait(rc); + } + + // Don't remove servers here which may still be used by watchers: + // A stop-updating naming service does not mean that it's not needed + // anymore. Remove servers inside dtor. +} + +static const size_t MAX_PROTOCOL_LEN = 31; + +static const char* ParseNamingServiceUrl(const char* url, char* protocol) { + // Accepting "[^:]{1,MAX_PROTOCOL_LEN}://*.*" + // ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ + // protocol service_name + if (__builtin_expect(url != NULL, 1)) { + const char* p1 = url; + while (*p1 != ':') { + if (p1 < url + MAX_PROTOCOL_LEN && *p1) { + protocol[p1 - url] = *p1; + ++p1; + } else { + return NULL; + } + } + if (p1 <= url + MAX_PROTOCOL_LEN) { + protocol[p1 - url] = '\0'; + const char* p2 = p1; + if (*++p2 == '/' && *++p2 == '/') { + return p2 + 1; + } + } + } + return NULL; +} + +int GetNamingServiceThread( + butil::intrusive_ptr* nsthread_out, + const char* url, + const GetNamingServiceThreadOptions* options) { + char protocol[MAX_PROTOCOL_LEN + 1]; + const char* const service_name = ParseNamingServiceUrl(url, protocol); + if (service_name == NULL) { + LOG(ERROR) << "Invalid naming service url=" << url; + return -1; + } + const NamingService* source_ns = NamingServiceExtension()->Find(protocol); + if (source_ns == NULL) { + LOG(ERROR) << "Unknown protocol=" << protocol; + return -1; + } + const NSKey key(protocol, service_name, + (options ? options->channel_signature : ChannelSignature())); + bool new_thread = false; + butil::intrusive_ptr nsthread; + { + std::unique_lock mu(g_nsthread_map_mutex); + if (g_nsthread_map == NULL) { + g_nsthread_map = new (std::nothrow) NamingServiceMap; + if (NULL == g_nsthread_map) { + mu.unlock(); + LOG(ERROR) << "Fail to new g_nsthread_map"; + return -1; + } + if (g_nsthread_map->init(64) != 0) { + LOG(WARNING) << "Fail to init g_nsthread_map"; + } + } + NamingServiceThread*& ptr = (*g_nsthread_map)[key]; + if (ptr != NULL) { + if (ptr->AddRefManually() == 0) { + // The ns thread's last intrusive_ptr was just destructed and + // the removal-from-global-map-code in ptr->~NamingServiceThread() + // is about to run or already running, need to create another ns + // thread. + // Notice that we don't need to remove the reference because + // the object is already destructing. + ptr = NULL; + } else { + nsthread.reset(ptr, false); + } + } + if (ptr == NULL) { + NamingServiceThread* thr = new (std::nothrow) NamingServiceThread; + if (thr == NULL) { + mu.unlock(); + LOG(ERROR) << "Fail to new NamingServiceThread"; + return -1; + } + ptr = thr; + nsthread.reset(ptr); + new_thread = true; + } + } + if (new_thread) { + int rc = nsthread->Start(source_ns->New(), key.protocol, key.service_name, options); + if (rc != 0) { + LOG(ERROR) << "Fail to start NamingServiceThread"; + // Wake up those waiting for first batch of servers. + nsthread->EndWait(rc); + std::unique_lock mu(g_nsthread_map_mutex); + g_nsthread_map->erase(key); + return -1; + } + } else { + if (nsthread->WaitForFirstBatchOfServers() != 0) { + return -1; + } + } + nsthread_out->swap(nsthread); + return 0; +} + +void NamingServiceThread::Describe(std::ostream& os, + const DescribeOptions& options) const { + if (_ns == NULL) { + os << "null"; + } else { + _ns->Describe(os, options); + } + os << "://" << _service_name; +} + +std::ostream& operator<<(std::ostream& os, const NamingServiceThread& nsthr) { + nsthr.Describe(os, DescribeOptions()); + return os; +} + +} // namespace brpc diff --git a/src/brpc/details/naming_service_thread.h b/src/brpc/details/naming_service_thread.h new file mode 100644 index 0000000..f01fbea --- /dev/null +++ b/src/brpc/details/naming_service_thread.h @@ -0,0 +1,144 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_NAMING_SERVICE_THREAD_H +#define BRPC_NAMING_SERVICE_THREAD_H + +#include +#include "butil/intrusive_ptr.hpp" // butil::intrusive_ptr +#include "bthread/bthread.h" // bthread_t +#include "brpc/server_id.h" // ServerId +#include "brpc/shared_object.h" // SharedObject +#include "brpc/naming_service.h" // NamingService +#include "brpc/naming_service_filter.h" // NamingServiceFilter +#include "brpc/socket_map.h" +#include "brpc/socket_mode.h" + +namespace brpc { + +// Inherit this class to observer NamingService changes. +// NOTE: Same SocketId with different tags are treated as different entries. +// When you change tag of a server, the server with the old tag will appear +// in OnRemovedServers first, then in OnAddedServers with the new tag. +class NamingServiceWatcher { +public: + virtual ~NamingServiceWatcher() {} + virtual void OnAddedServers(const std::vector& servers) = 0; + virtual void OnRemovedServers(const std::vector& servers) = 0; +}; + +struct GetNamingServiceThreadOptions { + GetNamingServiceThreadOptions() + : succeed_without_server(false) + , log_succeed_without_server(true) { + socket_option.socket_mode = SOCKET_MODE_TCP; +} + + bool succeed_without_server; + bool log_succeed_without_server; + ChannelSignature channel_signature; + SocketOptions socket_option; +}; + +// A dedicated thread to map a name to ServerIds +class NamingServiceThread : public SharedObject, public Describable { + struct ServerNodeWithId { + ServerNode node; + SocketId id; + + inline bool operator<(const ServerNodeWithId& rhs) const { + return id != rhs.id ? (id < rhs.id) : (node < rhs.node); + } + }; + class Actions : public NamingServiceActions { + public: + explicit Actions(NamingServiceThread* owner); + ~Actions() override; + void AddServers(const std::vector& servers) override; + void RemoveServers(const std::vector& servers) override; + void ResetServers(const std::vector& servers) override; + int WaitForFirstBatchOfServers(); + void EndWait(int error_code); + + private: + NamingServiceThread* _owner; + bthread_id_t _wait_id; + butil::atomic _has_wait_error; + int _wait_error; + std::vector _last_servers; + std::vector _servers; + std::vector _added; + std::vector _removed; + std::vector _sockets; + std::vector _added_sockets; + std::vector _removed_sockets; + }; + +public: + NamingServiceThread(); + ~NamingServiceThread() override; + + int Start(NamingService* ns, + const std::string& protocol, + const std::string& service_name, + const GetNamingServiceThreadOptions* options); + int WaitForFirstBatchOfServers(); + void EndWait(int error_code); + + int AddWatcher(NamingServiceWatcher* w, const NamingServiceFilter* f); + int AddWatcher(NamingServiceWatcher* w) { return AddWatcher(w, NULL); } + int RemoveWatcher(NamingServiceWatcher* w); + + void Describe(std::ostream& os, const DescribeOptions&) const override; + +private: + void Run(); + static void* RunThis(void*); + + static void ServerNodeWithId2ServerId( + const std::vector& src, + std::vector* dst, const NamingServiceFilter* filter); + + butil::Mutex _mutex; + bthread_t _tid; + NamingService* _ns; + std::string _protocol; + std::string _service_name; + GetNamingServiceThreadOptions _options; + std::vector _last_sockets; + Actions _actions; + std::map _watchers; +}; + +std::ostream& operator<<(std::ostream& os, const NamingServiceThread&); + +// Get the decicated thread associated with `url' and put the thread into +// `ns_thread'. Calling with same `url' shares and returns the same thread. +// If the url is not accessed before, this function blocks until the +// NamingService returns the first batch of servers. If no servers are +// available, unless `options->succeed_without_server' is on, this function +// returns -1. +// Returns 0 on success, -1 otherwise. +int GetNamingServiceThread(butil::intrusive_ptr* ns_thread, + const char* url, + const GetNamingServiceThreadOptions* options); + +} // namespace brpc + + +#endif // BRPC_NAMING_SERVICE_THREAD_H diff --git a/src/brpc/details/profiler_linker.h b/src/brpc/details/profiler_linker.h new file mode 100644 index 0000000..8df0197 --- /dev/null +++ b/src/brpc/details/profiler_linker.h @@ -0,0 +1,55 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_PROFILER_LINKER_H +#define BRPC_PROFILER_LINKER_H + +#if defined(BRPC_ENABLE_CPU_PROFILER) || defined(BAIDU_RPC_ENABLE_CPU_PROFILER) +#include "butil/gperftools_profiler.h" +#endif + +namespace brpc { + +// defined in src/brpc/builtin/index_service.cpp +extern bool cpu_profiler_enabled; + +// defined in src/brpc/controller.cpp +extern int PROFILER_LINKER_DUMMY; + +struct ProfilerLinker { + // [ Must be inlined ] + // This function is included by user's compilation unit to force + // linking of ProfilerStart()/ProfilerStop() + // etc when corresponding macros are defined. + inline ProfilerLinker() { + +#if defined(BRPC_ENABLE_CPU_PROFILER) || defined(BAIDU_RPC_ENABLE_CPU_PROFILER) + cpu_profiler_enabled = true; + // compiler has no way to tell if PROFILER_LINKER_DUMMY is 0 or not, + // so it has to link the function inside the branch. + if (PROFILER_LINKER_DUMMY != 0/*must be false*/) { + ProfilerStart("this_function_should_never_run"); + } +#endif + } +}; + +} // namespace brpc + + +#endif // BRPC_PROFILER_LINKER_H diff --git a/src/brpc/details/rtmp_utils.cpp b/src/brpc/details/rtmp_utils.cpp new file mode 100644 index 0000000..396dd94 --- /dev/null +++ b/src/brpc/details/rtmp_utils.cpp @@ -0,0 +1,63 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/details/rtmp_utils.h" + + +namespace brpc { + +int avc_nalu_read_uev(BitStream* stream, int32_t* v) { + if (stream->empty()) { + return -1; + } + // ue(v) in 9.1 Parsing process for Exp-Golomb codes + // H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 227. + // Syntax elements coded as ue(v), me(v), or se(v) are Exp-Golomb-coded. + // leadingZeroBits = -1; + // for( b = 0; !b; leadingZeroBits++ ) + // b = read_bits( 1 ) + // The variable codeNum is then assigned as follows: + // codeNum = (2<empty(); leadingZeroBits++) { + b = stream->read_bit(); + } + if (leadingZeroBits >= 31) { + return -1; + } + int32_t result = (1 << leadingZeroBits) - 1; + for (int i = 0; i < leadingZeroBits; i++) { + if (stream->empty()) { + return -1; + } + int32_t b = stream->read_bit(); + result += b << (leadingZeroBits - 1); + } + *v = result; + return 0; +} + +int avc_nalu_read_bit(BitStream* stream, int8_t* v) { + if (stream->empty()) { + return -1; + } + *v = stream->read_bit(); + return 0; +} + +} // namespace brpc diff --git a/src/brpc/details/rtmp_utils.h b/src/brpc/details/rtmp_utils.h new file mode 100644 index 0000000..f1d46fc --- /dev/null +++ b/src/brpc/details/rtmp_utils.h @@ -0,0 +1,63 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_DETAILS_RTMP_UTILS_H +#define BRPC_DETAILS_RTMP_UTILS_H + +#include // int32_t +#include // size_t + +namespace brpc { + +// Extract bits one by one from the bytes. +class BitStream { +public: + BitStream(const void* data, size_t len) + : _data(data), _data_end((const char*)data + len), _shift(7) {} + ~BitStream() {} + + // True if no bits any more. + bool empty() { return _data == _data_end; } + + // Read one bit from the data. empty() must be checked before calling + // this function, otherwise the behavior is undefined. + int8_t read_bit() { + const int8_t* p = (const int8_t*)_data; + int8_t result = (*p >> _shift) & 0x1; + if (_shift == 0) { + _shift = 7; + _data = p + 1; + } else { + --_shift; + } + return result; + } + +private: + const void* _data; + const void* const _data_end; + int _shift; +}; + +int avc_nalu_read_uev(BitStream* stream, int32_t* v); +int avc_nalu_read_bit(BitStream* stream, int8_t* v); + +} // namespace brpc + + +#endif // BRPC_DETAILS_RTMP_UTILS_H diff --git a/src/brpc/details/server_private_accessor.h b/src/brpc/details/server_private_accessor.h new file mode 100644 index 0000000..aacf283 --- /dev/null +++ b/src/brpc/details/server_private_accessor.h @@ -0,0 +1,130 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_SERVER_PRIVATE_ACCESSOR_H +#define BRPC_SERVER_PRIVATE_ACCESSOR_H + +#include +#include "brpc/server.h" +#include "brpc/acceptor.h" +#include "brpc/details/method_status.h" +#include "brpc/builtin/bad_method_service.h" +#include "brpc/restful.h" + +namespace brpc { + +// A wrapper to access some private methods/fields of `Server' +// This is supposed to be used by internal RPC protocols ONLY +class ServerPrivateAccessor { +public: + explicit ServerPrivateAccessor(const Server* svr) { + CHECK(svr); + _server = svr; + } + + void AddError() { + _server->_nerror_bvar << 1; + } + + // Returns true if the `max_concurrency' limit is not reached. + bool AddConcurrency(Controller* c) { + if (_server->options().max_concurrency <= 0) { + return true; + } + c->add_flag(Controller::FLAGS_ADDED_CONCURRENCY); + return (butil::subtle::NoBarrier_AtomicIncrement(&_server->_concurrency, 1) + <= _server->options().max_concurrency); + } + + void RemoveConcurrency(const Controller* c) { + if (c->has_flag(Controller::FLAGS_ADDED_CONCURRENCY)) { + butil::subtle::NoBarrier_AtomicIncrement(&_server->_concurrency, -1); + } + } + + // Find by MethodDescriptor::full_name + const Server::MethodProperty* + FindMethodPropertyByFullName(const butil::StringPiece &fullname) { + return _server->FindMethodPropertyByFullName(fullname); + } + const Server::MethodProperty* + FindMethodPropertyByFullName(const butil::StringPiece& fullname) const { + return _server->FindMethodPropertyByFullName(fullname); + } + const Server::MethodProperty* + FindMethodPropertyByFullName(const butil::StringPiece& full_service_name, + const butil::StringPiece& method_name) const { + return _server->FindMethodPropertyByFullName( + full_service_name, method_name); + } + const Server::MethodProperty* FindMethodPropertyByNameAndIndex( + const butil::StringPiece& service_name, int method_index) const { + return _server->FindMethodPropertyByNameAndIndex(service_name, method_index); + } + + const Server::ServiceProperty* + FindServicePropertyByFullName(const butil::StringPiece& fullname) const { + return _server->FindServicePropertyByFullName(fullname); + } + + const Server::ServiceProperty* + FindServicePropertyByName(const butil::StringPiece& name) const { + return _server->FindServicePropertyByName(name); + } + + const Server::ServiceProperty* + FindServicePropertyAdaptively(const butil::StringPiece& service_name) const { + if (service_name.find('.') == butil::StringPiece::npos) { + return _server->FindServicePropertyByName(service_name); + } else { + return _server->FindServicePropertyByFullName(service_name); + } + } + + Acceptor* acceptor() const { return _server->_am; } + + RestfulMap* global_restful_map() const + { return _server->_global_restful_map; } + +private: + const Server* _server; +}; + +// Count one error if release() is not called before destruction of this object. +class ScopedNonServiceError { +public: + ScopedNonServiceError(const Server* server) : _server(server) {} + ~ScopedNonServiceError() { + if (_server) { + ServerPrivateAccessor(_server).AddError(); + _server = NULL; + } + } + const Server* release() { + const Server* tmp = _server; + _server = NULL; + return tmp; + } +private: + DISALLOW_COPY_AND_ASSIGN(ScopedNonServiceError); + const Server* _server; +}; + +} // namespace brpc + + +#endif // BRPC_SERVER_PRIVATE_ACCESSOR_H diff --git a/src/brpc/details/sparse_minute_counter.h b/src/brpc/details/sparse_minute_counter.h new file mode 100644 index 0000000..3834afa --- /dev/null +++ b/src/brpc/details/sparse_minute_counter.h @@ -0,0 +1,168 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_SPARSE_MINUTE_COUNTER_H +#define BRPC_SPARSE_MINUTE_COUNTER_H + + +#include "butil/containers/bounded_queue.h" + +namespace brpc { + +// An utility to add up per-second value into per-minute value. +// About "sparse": +// This utility assumes that when a lot of instances exist, many of them do +// not need to update every second. This is true for stats of connections. +// When a lot of connections(>100K) connected to one server, most of them +// are idle since the overall actitivities of all connections are limited +// by throughput of the server. To make use of the fact, this utility stores +// per-second values in a sparse array tagged with timestamps. The array +// is resized on-demand to save memory. +template class SparseMinuteCounter { + struct Item { + int64_t timestamp_ms; + T value; + Item() : timestamp_ms(0) {} + Item(int64_t ts, const T& v) : timestamp_ms(ts), value(v) {} + }; +public: + SparseMinuteCounter() : _q(NULL) {} + ~SparseMinuteCounter() { DestroyQueue(_q); } + + // Add `value' into this counter at timestamp `now_ms' + // Returns true when old value is popped and set into *popped. + bool Add(int64_t now_ms, const T& value, T* popped); + + // Try to pop value before one minute. + // Returns true when old value is popped and set into *popped. + bool TryPop(int64_t now_ms, T* popped); + +private: + DISALLOW_COPY_AND_ASSIGN(SparseMinuteCounter); + typedef butil::BoundedQueue Q; + static Q* CreateQueue(uint32_t cap); + static void DestroyQueue(Q* q); + void Resize(); + + Q* _q; + Item _first_item; +}; + +template +bool SparseMinuteCounter::Add(int64_t now_ms, const T& val, T* popped) { + if (_q) { // more common + Item new_item(now_ms, val); + if (_q->full()) { + const Item* const oldest = _q->top(); + if (now_ms < oldest->timestamp_ms + 60000 && + _q->capacity() < 60) { + Resize(); + _q->push(new_item); + return false; + } else { + *popped = oldest->value; + _q->pop(); + _q->push(new_item); + return true; + } + } else { + _q->push(new_item); + return false; + } + } else { + // first-time storing is different. If Add() is rarely called, + // This strategy may not allocate _q at all. + if (_first_item.timestamp_ms == 0) { + _first_item.timestamp_ms = std::max(now_ms, (int64_t)1); + _first_item.value = val; + return false; + } + const int64_t delta = now_ms - _first_item.timestamp_ms; + if (delta >= 60000) { + *popped = _first_item.value; + _first_item.timestamp_ms = std::max(now_ms, (int64_t)1); + _first_item.value = val; + return true; + } + // Predict initial capacity of _q according to interval between + // now_ms and last timestamp. + int64_t initial_cap = (delta <= 1000 ? 30 : (60000 + delta - 1) / delta); + if (initial_cap < 4) { + initial_cap = 4; + } + _q = CreateQueue(initial_cap); + _q->push(_first_item); + _q->push(Item(now_ms, val)); + return false; + } +} + +template +typename SparseMinuteCounter::Q* +SparseMinuteCounter::CreateQueue(uint32_t cap) { + const size_t memsize = + sizeof(Q) + sizeof(Item) * cap; + char* mem = (char*)malloc(memsize); // intended crash on ENOMEM + return new (mem) Q(mem + sizeof(Q), sizeof(Item) * cap, butil::NOT_OWN_STORAGE); +} + +template +void SparseMinuteCounter::DestroyQueue(Q* q) { + if (q) { + q->~Q(); + free(q); + } +} + +template +void SparseMinuteCounter::Resize() { + CHECK_LT(_q->capacity(), (size_t)60); + uint32_t new_cap = std::min(2 * (uint32_t)_q->capacity(), 60u); + Q* new_q = CreateQueue(new_cap); + for (size_t i = 0; i < _q->size(); ++i) { + new_q->push(*_q->top(i)); + } + DestroyQueue(_q); + _q = new_q; +} + +template +bool SparseMinuteCounter::TryPop(int64_t now_ms, T* popped) { + if (_q) { + const Item* const oldest = _q->top(); + if (oldest == NULL || now_ms < oldest->timestamp_ms + 60000) { + return false; + } + *popped = oldest->value; + _q->pop(); + return true; + } else { + if (_first_item.timestamp_ms == 0 || + now_ms < _first_item.timestamp_ms + 60000) { + return false; + } + _first_item.timestamp_ms = 0; + *popped = _first_item.value; + return true; + } +} + +} // namespace brpc + + +#endif // BRPC_SPARSE_MINUTE_COUNTER_H diff --git a/src/brpc/details/ssl_helper.cpp b/src/brpc/details/ssl_helper.cpp new file mode 100644 index 0000000..0e31f14 --- /dev/null +++ b/src/brpc/details/ssl_helper.cpp @@ -0,0 +1,1005 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/ssl_options.h" +#include "butil/files/scoped_file.h" +#include +#ifndef USE_MESALINK + +#include // recv +#include // pthread_once +#include // fopen +#include // getenv +#include +#include +#include +#include +#include "butil/unique_ptr.h" +#include "butil/logging.h" +#include "butil/ssl_compat.h" +#include "butil/string_splitter.h" +#include "brpc/socket.h" +#include "brpc/details/ssl_helper.h" + +namespace brpc { + +#ifndef OPENSSL_NO_DH +static DH* g_dh_1024 = NULL; +static DH* g_dh_2048 = NULL; +static DH* g_dh_4096 = NULL; +static DH* g_dh_8192 = NULL; +#endif // OPENSSL_NO_DH + +static const char* const PEM_START = "-----BEGIN"; + +static bool IsPemString(const std::string& input) { + for (const char* s = input.c_str(); *s != '\0'; ++s) { + if (*s != '\n') { + return strncmp(s, PEM_START, strlen(PEM_START)) == 0; + } + } + return false; +} + +const char* SSLStateToString(SSLState s) { + switch (s) { + case SSL_UNKNOWN: + return "SSL_UNKNOWN"; + case SSL_OFF: + return "SSL_OFF"; + case SSL_CONNECTING: + return "SSL_CONNECTING"; + case SSL_CONNECTED: + return "SSL_CONNECTED"; + } + return "Bad SSLState"; +} + +static int ParseSSLProtocols(const std::string& str_protocol) { + int protocol_flag = 0; + butil::StringSplitter sp(str_protocol.data(), + str_protocol.data() + str_protocol.size(), ','); + for (; sp; ++sp) { + butil::StringPiece protocol(sp.field(), sp.length()); + protocol.trim_spaces(); + if (strncasecmp(protocol.data(), "SSLv3", protocol.size()) == 0) { + protocol_flag |= SSLv3; + } else if (strncasecmp(protocol.data(), "TLSv1", protocol.size()) == 0) { + protocol_flag |= TLSv1; + } else if (strncasecmp(protocol.data(), "TLSv1.1", protocol.size()) == 0) { + protocol_flag |= TLSv1_1; + } else if (strncasecmp(protocol.data(), "TLSv1.2", protocol.size()) == 0) { + protocol_flag |= TLSv1_2; + } else if (strncasecmp(protocol.data(), "TLSv1.3", protocol.size()) == 0) { + protocol_flag |= TLSv1_3; + } else { + LOG(ERROR) << "Unknown SSL protocol=" << protocol; + return -1; + } + } + return protocol_flag; +} + +std::ostream& operator<<(std::ostream& os, const SSLError& ssl) { + char buf[128]; // Should be enough + ERR_error_string_n(ssl.error, buf, sizeof(buf)); + return os << buf; +} + +std::ostream& operator<<(std::ostream& os, const CertInfo& cert) { + os << "certificate["; + if (IsPemString(cert.certificate)) { + size_t pos = cert.certificate.find('\n'); + if (pos == std::string::npos) { + pos = 0; + } else { + pos++; + } + os << cert.certificate.substr(pos, 16) << "..."; + } else { + os << cert.certificate; + } + + os << "] private-key["; + if (IsPemString(cert.private_key)) { + size_t pos = cert.private_key.find('\n'); + if (pos == std::string::npos) { + pos = 0; + } else { + pos++; + } + os << cert.private_key.substr(pos, 16) << "..."; + } else { + os << cert.private_key; + } + os << "]"; + return os; +} + +static void SSLInfoCallback(const SSL* ssl, int where, int ret) { + (void)ret; + SocketUniquePtr s; + SocketId id = (SocketId)SSL_get_app_data((SSL*)ssl); + if (Socket::Address(id, &s) != 0) { + // Already failed + return; + } + + if (where & SSL_CB_HANDSHAKE_START) { + if (s->ssl_state() == SSL_CONNECTED) { + // Disable renegotiation (CVE-2009-3555) + LOG(ERROR) << "Close " << *s << " due to insecure " + << "renegotiation detected (CVE-2009-3555)"; + s->SetFailed(); + } + } +} + +static void SSLMessageCallback(int write_p, int version, int content_type, + const void* buf, size_t len, SSL* ssl, void* arg) { + (void)version; + (void)arg; +#ifdef TLS1_RT_HEARTBEAT + // Test heartbeat received (write_p is set to 0 for a received record) + if ((content_type == TLS1_RT_HEARTBEAT) && (write_p == 0)) { + const unsigned char* p = (const unsigned char*)buf; + + // Check if this is a CVE-2014-0160 exploitation attempt. + if (*p != TLS1_HB_REQUEST) { + return; + } + + // 1 type + 2 size + 0 payload + 16 padding + if (len >= 1 + 2 + 16) { + unsigned int payload = (p[1] * 256) + p[2]; + if (3 + payload + 16 <= len) { + return; // OK no problem + } + } + + // We have a clear heartbleed attack (CVE-2014-0160), the + // advertised payload is larger than the advertised packet + // length, so we have garbage in the buffer between the + // payload and the end of the buffer (p+len). We can't know + // if the SSL stack is patched, and we don't know if we can + // safely wipe out the area between p+3+len and payload. + // So instead, we prevent the response from being sent by + // setting the max_send_fragment to 0 and we report an SSL + // error, which will kill this connection. It will be reported + // above as SSL_ERROR_SSL while an other handshake failure with + // a heartbeat message will be reported as SSL_ERROR_SYSCALL. + ssl->max_send_fragment = 0; + SSLerr(SSL_F_TLS1_HEARTBEAT, SSL_R_SSL_HANDSHAKE_FAILURE); + return; + } +#endif // TLS1_RT_HEARTBEAT +} + +#if defined(OPENSSL_IS_BORINGSSL) || (OPENSSL_VERSION_NUMBER >= 0x10101000L) +static pthread_once_t g_ssl_keylog_once = PTHREAD_ONCE_INIT; +static FILE* g_ssl_keylog_file = NULL; + +static void InitSSLKeyLogFile() { + const char* path = getenv("SSLKEYLOGFILE"); + if (path == NULL || path[0] == '\0') { + return; + } + g_ssl_keylog_file = fopen(path, "ae"); + if (g_ssl_keylog_file == NULL) { + PLOG(WARNING) << "Fail to open SSLKEYLOGFILE=" << path; + } else { + setvbuf(g_ssl_keylog_file, NULL, _IOLBF, 0); + LOG(WARNING) << "SSLKEYLOGFILE is enabled (path: " << path << "). " + << "Sensitive TLS session keys will be written to this file. " + << "This feature is intended for debugging only and should NOT be used in production environments."; + } +} + +static void SSLKeyLogCallback(const SSL* ssl, const char* line) { + (void)ssl; + if (line == NULL || g_ssl_keylog_file == NULL) { + return; + } + // Write the full key log line with newline in one call to keep output atomic. + fprintf(g_ssl_keylog_file, "%s\n", line); +} + +static void MaybeSetKeyLogCallback(SSL_CTX* ctx) { + pthread_once(&g_ssl_keylog_once, InitSSLKeyLogFile); + if (ctx != NULL && g_ssl_keylog_file != NULL) { + SSL_CTX_set_keylog_callback(ctx, SSLKeyLogCallback); + } +} +#else +static void MaybeSetKeyLogCallback(SSL_CTX* ctx) { + (void)ctx; +} +#endif + +#ifndef OPENSSL_NO_DH +static DH* SSLGetDHCallback(SSL* ssl, int exp, int keylen) { + (void)exp; + EVP_PKEY* pkey = SSL_get_privatekey(ssl); + int type = pkey ? EVP_PKEY_base_id(pkey) : EVP_PKEY_NONE; + + // The keylen supplied by OpenSSL can only be 512 or 1024. + // See ssl3_send_server_key_exchange() in ssl/s3_srvr.c + if (type == EVP_PKEY_RSA || type == EVP_PKEY_DSA) { + keylen = EVP_PKEY_bits(pkey); + } + + if (keylen >= 8192) { + return g_dh_8192; + } else if (keylen >= 4096) { + return g_dh_4096; + } else if (keylen >= 2048) { + return g_dh_2048; + } else { + return g_dh_1024; + } +} +#endif // OPENSSL_NO_DH + +void ExtractHostnames(X509* x, std::vector* hostnames) { +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + STACK_OF(GENERAL_NAME)* names = (STACK_OF(GENERAL_NAME)*) + X509_get_ext_d2i(x, NID_subject_alt_name, NULL, NULL); + if (names) { + for (size_t i = 0; i < static_cast(sk_GENERAL_NAME_num(names)); i++) { + char* str = NULL; + GENERAL_NAME* name = sk_GENERAL_NAME_value(names, i); + if (name->type == GEN_DNS) { + if (ASN1_STRING_to_UTF8((unsigned char**)&str, + name->d.dNSName) >= 0) { + std::string hostname(str); + hostnames->push_back(hostname); + OPENSSL_free(str); + } + } + } + sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); + } +#endif // SSL_CTRL_SET_TLSEXT_HOSTNAME + + int i = -1; + X509_NAME* xname = X509_get_subject_name(x); + while ((i = X509_NAME_get_index_by_NID(xname, NID_commonName, i)) != -1) { + char* str = NULL; + X509_NAME_ENTRY* entry = X509_NAME_get_entry(xname, i); + const int len = ASN1_STRING_to_UTF8((unsigned char**)&str, + X509_NAME_ENTRY_get_data(entry)); + if (len >= 0) { + std::string hostname(str, len); + hostnames->push_back(hostname); + OPENSSL_free(str); + } + } +} + +struct FreeSSL { + inline void operator()(SSL* ssl) const { + if (ssl != NULL) { + SSL_free(ssl); + } + } +}; + +struct FreeBIO { + inline void operator()(BIO* io) const { + if (io != NULL) { + BIO_free(io); + } + } +}; + +struct FreeX509 { + inline void operator()(X509* x) const { + if (x != NULL) { + X509_free(x); + } + } +}; + +struct FreeEVPKEY { + inline void operator()(EVP_PKEY* k) const { + if (k != NULL) { + EVP_PKEY_free(k); + } + } +}; + +static int LoadCertificate(SSL_CTX* ctx, + const std::string& certificate, + const std::string& private_key, + std::vector* hostnames) { + // Load the private key + if (IsPemString(private_key)) { + std::unique_ptr kbio( + BIO_new_mem_buf((void*)private_key.c_str(), -1)); + std::unique_ptr key( + PEM_read_bio_PrivateKey(kbio.get(), NULL, 0, NULL)); + if (SSL_CTX_use_PrivateKey(ctx, key.get()) != 1) { + LOG(ERROR) << "Fail to load " << private_key << ": " + << SSLError(ERR_get_error()); + return -1; + } + + } else { + if (SSL_CTX_use_PrivateKey_file( + ctx, private_key.c_str(), SSL_FILETYPE_PEM) != 1) { + LOG(ERROR) << "Fail to load " << private_key << ": " + << SSLError(ERR_get_error()); + return -1; + } + } + + // Open & Read certificate + std::unique_ptr cbio; + if (IsPemString(certificate)) { + cbio.reset(BIO_new_mem_buf((void*)certificate.c_str(), -1)); + } else { + cbio.reset(BIO_new(BIO_s_file())); + if (BIO_read_filename(cbio.get(), certificate.c_str()) <= 0) { + LOG(ERROR) << "Fail to read " << certificate << ": " + << SSLError(ERR_get_error()); + return -1; + } + } + std::unique_ptr x( + PEM_read_bio_X509_AUX(cbio.get(), NULL, 0, NULL)); + if (!x) { + LOG(ERROR) << "Fail to parse " << certificate << ": " + << SSLError(ERR_get_error()); + return -1; + } + + // Load the main certificate + if (SSL_CTX_use_certificate(ctx, x.get()) != 1) { + LOG(ERROR) << "Fail to load " << certificate << ": " + << SSLError(ERR_get_error()); + return -1; + } + + // Load the certificate chain +#if (OPENSSL_VERSION_NUMBER >= 0x10002000L) + SSL_CTX_clear_chain_certs(ctx); +#else + if (ctx->extra_certs != NULL) { + sk_X509_pop_free(ctx->extra_certs, X509_free); + ctx->extra_certs = NULL; + } +#endif + X509* ca = NULL; + while ((ca = PEM_read_bio_X509(cbio.get(), NULL, 0, NULL))) { + if (SSL_CTX_add_extra_chain_cert(ctx, ca) != 1) { + LOG(ERROR) << "Fail to load chain certificate in " + << certificate << ": " << SSLError(ERR_get_error()); + X509_free(ca); + return -1; + } + } + + int err = ERR_get_error(); + if (err != 0 && (ERR_GET_LIB(err) != ERR_LIB_PEM + || ERR_GET_REASON(err) != PEM_R_NO_START_LINE)) { + LOG(ERROR) << "Fail to read chain certificate in " + << certificate << ": " << SSLError(err); + return -1; + } + ERR_clear_error(); + + // Validate certificate and private key + if (SSL_CTX_check_private_key(ctx) != 1) { + LOG(ERROR) << "Fail to verify " << private_key << ": " + << SSLError(ERR_get_error()); + return -1; + } + + if (hostnames != NULL) { + ExtractHostnames(x.get(), hostnames); + } + return 0; +} + +static int SetSSLOptions(SSL_CTX* ctx, const std::string& ciphers, + int protocols, const VerifyOptions& verify) { + long ssloptions = SSL_OP_ALL // All known workarounds for bugs + | SSL_OP_NO_SSLv2 +#ifdef SSL_OP_NO_COMPRESSION + | SSL_OP_NO_COMPRESSION +#endif // SSL_OP_NO_COMPRESSION + | SSL_OP_CIPHER_SERVER_PREFERENCE + | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION; + + if (!(protocols & SSLv3)) { + ssloptions |= SSL_OP_NO_SSLv3; + } + if (!(protocols & TLSv1)) { + ssloptions |= SSL_OP_NO_TLSv1; + } + +#ifdef SSL_OP_NO_TLSv1_1 + if (!(protocols & TLSv1_1)) { + ssloptions |= SSL_OP_NO_TLSv1_1; + } +#endif // SSL_OP_NO_TLSv1_1 + +#ifdef SSL_OP_NO_TLSv1_2 + if (!(protocols & TLSv1_2)) { + ssloptions |= SSL_OP_NO_TLSv1_2; + } +#endif // SSL_OP_NO_TLSv1_2 + +#ifdef SSL_OP_NO_TLSv1_3 + if (!(protocols & TLSv1_3)) { + ssloptions |= SSL_OP_NO_TLSv1_3; + } +#endif // SSL_OP_NO_TLSv1_3 + SSL_CTX_set_options(ctx, ssloptions); + + long sslmode = SSL_MODE_ENABLE_PARTIAL_WRITE + | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER; + SSL_CTX_set_mode(ctx, sslmode); + + if (!ciphers.empty() && + SSL_CTX_set_cipher_list(ctx, ciphers.c_str()) != 1) { + LOG(ERROR) << "Fail to set cipher list to " << ciphers + << ": " << SSLError(ERR_get_error()); + return -1; + } + + // TODO: Verify the CNAME in certificate matches the requesting host + if (verify.verify_depth > 0) { + if (verify.verify_mode == VerifyMode::VERIFY_FAIL_IF_NO_PEER_CERT) { + SSL_CTX_set_verify(ctx, (SSL_VERIFY_PEER + | SSL_VERIFY_FAIL_IF_NO_PEER_CERT), NULL); + } else if (verify.verify_mode == VerifyMode::VERIFY_PEER) { + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); + } else if (verify.verify_mode == VerifyMode::VERIFY_NONE) { + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); + } else { + // for forward compatibility + SSL_CTX_set_verify(ctx, (SSL_VERIFY_PEER + | SSL_VERIFY_FAIL_IF_NO_PEER_CERT), NULL); + } + SSL_CTX_set_verify_depth(ctx, verify.verify_depth); + std::string cafile = verify.ca_file_path; + if (cafile.empty()) { + cafile = X509_get_default_cert_area() + std::string("/cert.pem"); + } + if (SSL_CTX_load_verify_locations(ctx, cafile.c_str(), NULL) == 0) { + if (verify.ca_file_path.empty()) { + LOG(WARNING) << "Fail to load default CA file " << cafile + << ": " << SSLError(ERR_get_error()); + } else { + LOG(ERROR) << "Fail to load CA file " << cafile + << ": " << SSLError(ERR_get_error()); + return -1; + } + } + } else { + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); + } + + SSL_CTX_set_info_callback(ctx, SSLInfoCallback); +#if OPENSSL_VERSION_NUMBER >= 0x00907000L + // To detect and protect from heartbleed attack + SSL_CTX_set_msg_callback(ctx, SSLMessageCallback); +#endif + + return 0; +} + +static int ServerALPNCallback( + SSL* ssl, const unsigned char** out, unsigned char* outlen, + const unsigned char* in, unsigned int inlen, void* arg) { + const std::string* alpns = static_cast(arg); + if (alpns == nullptr) { + return SSL_TLSEXT_ERR_NOACK; + } + + // Use OpenSSL standard select API. + int select_result = SSL_select_next_proto( + const_cast(out), outlen, + reinterpret_cast(alpns->data()), alpns->size(), + in, inlen); + return (select_result == OPENSSL_NPN_NEGOTIATED) + ? SSL_TLSEXT_ERR_OK : SSL_TLSEXT_ERR_NOACK; +} + +static int SetServerALPNCallback(SSL_CTX* ssl_ctx, const std::string* alpns) { + if (ssl_ctx == nullptr) { + LOG(ERROR) << "Fail to set server ALPN callback, ssl_ctx is nullptr."; + return -1; + } + + // Server set alpn callback when openssl version is more than 1.0.2 +#if (OPENSSL_VERSION_NUMBER >= SSL_VERSION_NUMBER(1, 0, 2)) + SSL_CTX_set_alpn_select_cb(ssl_ctx, ServerALPNCallback, + const_cast(alpns)); +#else + LOG(WARNING) << "OpenSSL version=" << OPENSSL_VERSION_STR + << " is lower than 1.0.2, ignore server alpn."; +#endif + return 0; +} + +SSL_CTX* CreateClientSSLContext(const ChannelSSLOptions& options) { + std::unique_ptr ssl_ctx( + SSL_CTX_new(SSLv23_client_method())); + if (!ssl_ctx) { + LOG(ERROR) << "Fail to new SSL_CTX: " << SSLError(ERR_get_error()); + return NULL; + } + MaybeSetKeyLogCallback(ssl_ctx.get()); + + if (!options.client_cert.certificate.empty() + && LoadCertificate(ssl_ctx.get(), + options.client_cert.certificate, + options.client_cert.private_key, NULL) != 0) { + return NULL; + } + + int protocols = ParseSSLProtocols(options.protocols); + if (protocols < 0 + || SetSSLOptions(ssl_ctx.get(), options.ciphers, + protocols, options.verify) != 0) { + return NULL; + } + + if (!options.alpn_protocols.empty()) { + std::vector alpn_list; + if (!BuildALPNProtocolList(options.alpn_protocols, alpn_list)) { + return NULL; + } + SSL_CTX_set_alpn_protos(ssl_ctx.get(), alpn_list.data(), alpn_list.size()); + } + + SSL_CTX_set_session_cache_mode(ssl_ctx.get(), SSL_SESS_CACHE_CLIENT); + return ssl_ctx.release(); +} + +SSL_CTX* CreateServerSSLContext(const std::string& certificate, + const std::string& private_key, + const ServerSSLOptions& options, + const std::string* alpns, + std::vector* hostnames) { + std::unique_ptr ssl_ctx( + SSL_CTX_new(SSLv23_server_method())); + if (!ssl_ctx) { + LOG(ERROR) << "Fail to new SSL_CTX: " << SSLError(ERR_get_error()); + return NULL; + } + MaybeSetKeyLogCallback(ssl_ctx.get()); + + if (LoadCertificate(ssl_ctx.get(), certificate, + private_key, hostnames) != 0) { + return NULL; + } + + int protocols = TLSv1 | TLSv1_1 | TLSv1_2 | TLSv1_3; + if (!options.disable_ssl3) { + protocols |= SSLv3; + } + if (SetSSLOptions(ssl_ctx.get(), options.ciphers, + protocols, options.verify) != 0) { + return NULL; + } + +#ifdef SSL_MODE_RELEASE_BUFFERS + if (options.release_buffer) { + long sslmode = SSL_CTX_get_mode(ssl_ctx.get()); + sslmode |= SSL_MODE_RELEASE_BUFFERS; + SSL_CTX_set_mode(ssl_ctx.get(), sslmode); + } +#endif // SSL_MODE_RELEASE_BUFFERS + + SSL_CTX_set_timeout(ssl_ctx.get(), options.session_lifetime_s); + SSL_CTX_sess_set_cache_size(ssl_ctx.get(), options.session_cache_size); + +#ifndef OPENSSL_NO_DH + SSL_CTX_set_tmp_dh_callback(ssl_ctx.get(), SSLGetDHCallback); + +#if !defined(OPENSSL_NO_ECDH) && defined(SSL_CTX_set_tmp_ecdh) + EC_KEY* ecdh = NULL; + int i = OBJ_sn2nid(options.ecdhe_curve_name.c_str()); + if (!i || ((ecdh = EC_KEY_new_by_curve_name(i)) == NULL)) { + LOG(ERROR) << "Fail to find ECDHE named curve=" + << options.ecdhe_curve_name + << ": " << SSLError(ERR_get_error()); + return NULL; + } + SSL_CTX_set_tmp_ecdh(ssl_ctx.get(), ecdh); + EC_KEY_free(ecdh); +#endif + +#endif // OPENSSL_NO_DH + + // Set ALPN callback to choose application protocol when alpns is not empty. + if (alpns != nullptr && !alpns->empty()) { + if (SetServerALPNCallback(ssl_ctx.get(), alpns) != 0) { + return NULL; + } + } + return ssl_ctx.release(); +} + +SSL* CreateSSLSession(SSL_CTX* ctx, SocketId id, int fd, bool server_mode) { + if (ctx == NULL) { + LOG(WARNING) << "Lack SSL_ctx to create an SSL session"; + return NULL; + } + SSL* ssl = SSL_new(ctx); + if (ssl == NULL) { + LOG(ERROR) << "Fail to SSL_new: " << SSLError(ERR_get_error()); + return NULL; + } + if (SSL_set_fd(ssl, fd) != 1) { + LOG(ERROR) << "Fail to SSL_set_fd: " << SSLError(ERR_get_error()); + SSL_free(ssl); + return NULL; + } + + if (server_mode) { + SSL_set_accept_state(ssl); + } else { + SSL_set_connect_state(ssl); + } + SSL_set_app_data(ssl, id); + return ssl; +} + +void AddBIOBuffer(SSL* ssl, int fd, int bufsize) { +#if defined(OPENSSL_IS_BORINGSSL) + BIO *rbio = BIO_new(BIO_s_mem()); + BIO *wbio = BIO_new(BIO_s_mem()); +#else + BIO *rbio = BIO_new(BIO_f_buffer()); + BIO_set_buffer_size(rbio, bufsize); + BIO *wbio = BIO_new(BIO_f_buffer()); + BIO_set_buffer_size(wbio, bufsize); +#endif + BIO* rfd = BIO_new(BIO_s_fd()); + BIO_set_fd(rfd, fd, 0); + rbio = BIO_push(rbio, rfd); + BIO* wfd = BIO_new(BIO_s_fd()); + BIO_set_fd(wfd, fd, 0); + wbio = BIO_push(wbio, wfd); + SSL_set_bio(ssl, rbio, wbio); +} + +SSLState DetectSSLState(int fd, int* error_code) { + // Peek the first few bytes inside socket to detect whether + // it's an SSL connection. If it is, create an SSL session + // which will be used to read/write after + + // Header format of SSLv2 + // +-----------+------+----- + // | 2B header | 0x01 | etc. + // +-----------+------+----- + // The first bit of header is always 1, with the following + // 15 bits are the length of data + + // Header format of SSLv3 or TLSv1.0, 1.1, 1.2 + // +------+------------+-----------+------+----- + // | 0x16 | 2B version | 2B length | 0x01 | etc. + // +------+------------+-----------+------+----- + char header[6]; + const ssize_t nr = recv(fd, header, sizeof(header), MSG_PEEK); + if (nr < (ssize_t)sizeof(header)) { + if (nr < 0) { + if (errno == ENOTSOCK) { + return SSL_OFF; + } + *error_code = errno; // Including EAGAIN and EINTR + } else if (nr == 0) { // EOF + *error_code = 0; + } else { // Not enough data, need retry + *error_code = EAGAIN; + } + return SSL_UNKNOWN; + } + + if ((header[0] == 0x16 && header[5] == 0x01) // SSLv3 or TLSv1.0, 1.1, 1.2 + || ((header[0] & 0x80) == 0x80 && header[2] == 0x01)) { // SSLv2 + return SSL_CONNECTING; + } else { + return SSL_OFF; + } +} + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + +// NOTE: Can't find a macro for CRYPTO_THREADID +// Fallback to use CRYPTO_LOCK_ECDH as flag +#ifdef CRYPTO_LOCK_ECDH +static void SSLGetThreadId(CRYPTO_THREADID* tid) { + CRYPTO_THREADID_set_numeric(tid, (unsigned long)pthread_self()); +} +#else +static unsigned long SSLGetThreadId() { + return pthread_self(); +} +#endif // CRYPTO_LOCK_ECDH + +// Locks for SSL library +// NOTE: If we replace this with bthread_mutex_t, SSL routines +// may crash probably due to some TLS data used inside OpenSSL +// Also according to performance test, there is little difference +// between pthread mutex and bthread mutex +static butil::Mutex* g_ssl_mutexs = NULL; + +static void SSLLockCallback(int mode, int n, const char* file, int line) { + (void)file; + (void)line; + // Following log is too anonying even for verbose logs. + // RPC_VLOG << "[" << file << ':' << line << "] SSL" + // << (mode & CRYPTO_LOCK ? "locks" : "unlocks") + // << " thread=" << CRYPTO_thread_id(); + if (mode & CRYPTO_LOCK) { + g_ssl_mutexs[n].lock(); + } else { + g_ssl_mutexs[n].unlock(); + } +} +#endif // OPENSSL_VERSION_NUMBER < 0x10100000L + +int SSLThreadInit() { +#if OPENSSL_VERSION_NUMBER < 0x10100000L + g_ssl_mutexs = new butil::Mutex[CRYPTO_num_locks()]; + CRYPTO_set_locking_callback(SSLLockCallback); +# ifdef CRYPTO_LOCK_ECDH + CRYPTO_THREADID_set_callback(SSLGetThreadId); +# else + CRYPTO_set_id_callback(SSLGetThreadId); +# endif // CRYPTO_LOCK_ECDH +#endif // OPENSSL_VERSION_NUMBER < 0x10100000L + return 0; +} + +#ifndef OPENSSL_NO_DH + +static DH* SSLGetDH1024() { + BIGNUM* p = get_rfc2409_prime_1024(NULL); + if (!p) { + return NULL; + } + // See RFC 2409, Section 6 "Oakley Groups" + // for the reason why 2 is used as generator. + BIGNUM* g = NULL; + BN_dec2bn(&g, "2"); + if (!g) { + BN_free(p); + return NULL; + } + DH *dh = DH_new(); + if (!dh) { + BN_free(p); + BN_free(g); + return NULL; + } + DH_set0_pqg(dh, p, NULL, g); + return dh; +} + +static DH* SSLGetDH2048() { + BIGNUM* p = get_rfc3526_prime_2048(NULL); + if (!p) { + return NULL; + } + // See RFC 3526, Section 3 "2048-bit MODP Group" + // for the reason why 2 is used as generator. + BIGNUM* g = NULL; + BN_dec2bn(&g, "2"); + if (!g) { + BN_free(p); + return NULL; + } + DH* dh = DH_new(); + if (!dh) { + BN_free(p); + BN_free(g); + return NULL; + } + DH_set0_pqg(dh, p, NULL, g); + return dh; +} + +static DH* SSLGetDH4096() { + BIGNUM* p = get_rfc3526_prime_4096(NULL); + if (!p) { + return NULL; + } + // See RFC 3526, Section 5 "4096-bit MODP Group" + // for the reason why 2 is used as generator. + BIGNUM* g = NULL; + BN_dec2bn(&g, "2"); + if (!g) { + BN_free(p); + return NULL; + } + DH *dh = DH_new(); + if (!dh) { + BN_free(p); + BN_free(g); + return NULL; + } + DH_set0_pqg(dh, p, NULL, g); + return dh; +} + +static DH* SSLGetDH8192() { + BIGNUM* p = get_rfc3526_prime_8192(NULL); + if (!p) { + return NULL; + } + // See RFC 3526, Section 7 "8192-bit MODP Group" + // for the reason why 2 is used as generator. + BIGNUM* g = NULL; + BN_dec2bn(&g, "2"); + if (!g) { + BN_free(g); + return NULL; + } + DH *dh = DH_new(); + if (!dh) { + BN_free(p); + BN_free(g); + return NULL; + } + DH_set0_pqg(dh, p, NULL, g); + return dh; +} + +#endif // OPENSSL_NO_DH + +int SSLDHInit() { +#ifndef OPENSSL_NO_DH + if ((g_dh_1024 = SSLGetDH1024()) == NULL) { + LOG(ERROR) << "Fail to initialize DH-1024"; + return -1; + } + if ((g_dh_2048 = SSLGetDH2048()) == NULL) { + LOG(ERROR) << "Fail to initialize DH-2048"; + return -1; + } + if ((g_dh_4096 = SSLGetDH4096()) == NULL) { + LOG(ERROR) << "Fail to initialize DH-4096"; + return -1; + } + if ((g_dh_8192 = SSLGetDH8192()) == NULL) { + LOG(ERROR) << "Fail to initialize DH-8192"; + return -1; + } +#endif // OPENSSL_NO_DH + return 0; +} + +static std::string GetNextLevelSeparator(const char* sep) { + if (sep[0] != '\n') { + return sep; + } + const size_t left_len = strlen(sep + 1); + if (left_len == 0) { + return "\n "; + } + std::string new_sep; + new_sep.reserve(left_len * 2 + 1); + new_sep.append(sep, left_len + 1); + new_sep.append(sep + 1, left_len); + return new_sep; +} + +void Print(std::ostream& os, SSL* ssl, const char* sep) { + os << "cipher=" << SSL_get_cipher(ssl) << sep + << "protocol=" << SSL_get_version(ssl) << sep + << "verify=" << (SSL_get_verify_mode(ssl) & SSL_VERIFY_PEER + ? "success" : "none"); + X509* cert = SSL_get_peer_certificate(ssl); + if (cert) { + os << sep << "peer_certificate={"; + const std::string new_sep = GetNextLevelSeparator(sep); + if (sep[0] == '\n') { + os << new_sep; + } + Print(os, cert, new_sep.c_str()); + if (sep[0] == '\n') { + os << sep; + } + os << '}'; + } +} + +void Print(std::ostream& os, X509* cert, const char* sep) { + BIO* buf = BIO_new(BIO_s_mem()); + if (buf == NULL) { + return; + } + BIO_printf(buf, "subject="); + X509_NAME_print(buf, X509_get_subject_name(cert), 0); + BIO_printf(buf, "%sstart_date=", sep); + ASN1_TIME_print(buf, X509_get_notBefore(cert)); + BIO_printf(buf, "%sexpire_date=", sep); + ASN1_TIME_print(buf, X509_get_notAfter(cert)); + + BIO_printf(buf, "%scommon_name=", sep); + std::vector hostnames; + brpc::ExtractHostnames(cert, &hostnames); + for (size_t i = 0; i < hostnames.size(); ++i) { + BIO_printf(buf, "%s;", hostnames[i].c_str()); + } + + BIO_printf(buf, "%sissuer=", sep); + X509_NAME_print(buf, X509_get_issuer_name(cert), 0); + + char* bufp = NULL; + int len = BIO_get_mem_data(buf, &bufp); + os << butil::StringPiece(bufp, len); +} + +std::string ALPNProtocolToString(const AdaptiveProtocolType& protocol) { + butil::StringPiece name = protocol.name(); + // Default use http 1.1 version + if (name.starts_with("http")) { + name.set("http/1.1"); + } + + // ALPN extension uses 1 byte to record the protocol length + // and it's maximum length is 255. + if (name.size() > CHAR_MAX) { + name = name.substr(0, CHAR_MAX); + } + + char length = static_cast(name.size()); + return std::string(&length, 1) + name.data(); +} + +bool BuildALPNProtocolList( + const std::vector& alpn_protocols, + std::vector& result +) { + size_t alpn_list_length = 0; + for (const auto& alpn_protocol : alpn_protocols) { + if (alpn_protocol.size() > UCHAR_MAX) { + LOG(ERROR) << "Fail to build ALPN procotol list: " + << "protocol name length " << alpn_protocol.size() << " too long, " + << "max 255 supported."; + return false; + } + alpn_list_length += alpn_protocol.size() + 1; + } + + result.resize(alpn_list_length); + for (size_t curr = 0, i = 0; i < alpn_protocols.size(); i++) { + result[curr++] = static_cast( + alpn_protocols[i].size() + ); + std::copy( + alpn_protocols[i].begin(), + alpn_protocols[i].end(), + result.begin() + curr + ); + curr += alpn_protocols[i].size(); + } + return true; +} + +} // namespace brpc + +#endif // USE_MESALINK diff --git a/src/brpc/details/ssl_helper.h b/src/brpc/details/ssl_helper.h new file mode 100644 index 0000000..a9b8736 --- /dev/null +++ b/src/brpc/details/ssl_helper.h @@ -0,0 +1,126 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_SSL_HELPER_H +#define BRPC_SSL_HELPER_H + +#include +#ifndef USE_MESALINK +#include +// For some versions of openssl, SSL_* are defined inside this header +#include +#include +#else +#include +#include +#include +#endif +#include "brpc/socket_id.h" // SocketId +#include "brpc/ssl_options.h" // ServerSSLOptions +#include "brpc/adaptive_protocol_type.h" // AdaptiveProtocolType + +namespace brpc { + +// The calculation method is the same as OPENSSL_VERSION_NUMBER in the openssl/crypto.h file. +// SSL_VERSION_NUMBER can pass parameter calculation instead of using fixed macro. +#define SSL_VERSION_NUMBER(major, minor, patch) \ + ( (major << 28) | (minor << 20) | (patch << 4) ) + +enum SSLState { + SSL_UNKNOWN = 0, + SSL_OFF = 1, // Not an SSL connection + SSL_CONNECTING = 2, // During SSL handshake + SSL_CONNECTED = 3, // SSL handshake completed +}; + +enum SSLProtocol { + SSLv3 = 1 << 0, + TLSv1 = 1 << 1, + TLSv1_1 = 1 << 2, + TLSv1_2 = 1 << 3, + TLSv1_3 = 1 << 4, +}; + +struct FreeSSLCTX { + inline void operator()(SSL_CTX* ctx) const { + if (ctx != NULL) { + SSL_CTX_free(ctx); + } + } +}; + +struct SSLError { + explicit SSLError(unsigned long e) : error(e) { } + unsigned long error; +}; +std::ostream& operator<<(std::ostream& os, const SSLError&); +std::ostream& operator<<(std::ostream& os, const CertInfo&); + +const char* SSLStateToString(SSLState s); + +// Initialize locks and callbacks to make SSL work under multi-threaded +// environment. Return 0 on success, -1 otherwise +int SSLThreadInit(); + +// Initialize global Diffie-Hellman parameters used for DH key exchanges +// Return 0 on success, -1 otherwise +int SSLDHInit(); + +// Create a new SSL_CTX in client mode and +// set the right options according `options' +SSL_CTX* CreateClientSSLContext(const ChannelSSLOptions& options); + +// Create a new SSL_CTX in server mode using `certificate_file' +// and `private_key_file' and then set the right options and alpn +// onto it according `options'.Finally, extract hostnames from CN/subject +// fields into `hostnames' +// Attention: ensure that the life cycle of function return is greater than alpns param. +SSL_CTX* CreateServerSSLContext(const std::string& certificate_file, + const std::string& private_key_file, + const ServerSSLOptions& options, + const std::string* alpns, + std::vector* hostnames); + +// Create a new SSL (per connection object) using configurations in `ctx'. +// Set the required `fd' and mode. `id' will be set into SSL as app data. +SSL* CreateSSLSession(SSL_CTX* ctx, SocketId id, int fd, bool server_mode); + +// Add a buffer layer of BIO in front of the socket fd layer, +// which can reduce the total number of calls to system read/write +void AddBIOBuffer(SSL* ssl, int fd, int bufsize); + +// Judge whether the underlying channel of `fd' is using SSL +// If the return value is SSL_UNKNOWN, `error_code' will be +// set to indicate the reason (0 for EOF) +SSLState DetectSSLState(int fd, int* error_code); + +void Print(std::ostream& os, SSL* ssl, const char* sep); +void Print(std::ostream& os, X509* cert, const char* sep); + +std::string ALPNProtocolToString(const AdaptiveProtocolType& protocol); + +// Build a binary formatted ALPN protocol list that OpenSSL's +// `SSL_CTX_set_alpn_protos` accepts from a C++ string vector. +bool BuildALPNProtocolList( + const std::vector& alpn_protocols, + std::vector& result +); + +} // namespace brpc + +#endif // BRPC_SSL_HELPER_H diff --git a/src/brpc/details/tcmalloc_extension.cpp b/src/brpc/details/tcmalloc_extension.cpp new file mode 100644 index 0000000..6f0c9e4 --- /dev/null +++ b/src/brpc/details/tcmalloc_extension.cpp @@ -0,0 +1,70 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include // dlsym +#include // getenv +#include "butil/compiler_specific.h" +#include "brpc/details/tcmalloc_extension.h" + +namespace { +typedef MallocExtension* (*GetInstanceFn)(); +static pthread_once_t g_get_instance_fn_once = PTHREAD_ONCE_INIT; +static GetInstanceFn g_get_instance_fn = NULL; +static void InitGetInstanceFn() { + g_get_instance_fn = (GetInstanceFn)dlsym( + RTLD_NEXT, "_ZN15MallocExtension8instanceEv"); +} +} // namespace + +MallocExtension* BAIDU_WEAK MallocExtension::instance() { + // On fedora 26, this weak function is NOT overriden by the one in tcmalloc + // which is dynamically linked.The same issue can't be re-produced in + // Ubuntu and the exact cause is unknown yet. Using dlsym to get the + // function works around the issue right now. Note that we can't use dlsym + // to fully replace the weak-function mechanism since our code are generally + // not compiled with -rdynamic which writes symbols to the table that + // dlsym reads. + pthread_once(&g_get_instance_fn_once, InitGetInstanceFn); + if (g_get_instance_fn) { + return g_get_instance_fn(); + } + return NULL; +} + +bool IsHeapProfilerEnabled() { + return MallocExtension::instance() != NULL; +} + +bool IsTCMallocEnabled() { + return IsHeapProfilerEnabled(); +} + +static bool check_TCMALLOC_SAMPLE_PARAMETER() { + char* str = getenv("TCMALLOC_SAMPLE_PARAMETER"); + if (str == NULL) { + return false; + } + char* endptr; + int val = strtol(str, &endptr, 10); + return (*endptr == '\0' && val > 0); +} + +bool has_TCMALLOC_SAMPLE_PARAMETER() { + static bool val = check_TCMALLOC_SAMPLE_PARAMETER(); + return val; +} diff --git a/src/brpc/details/tcmalloc_extension.h b/src/brpc/details/tcmalloc_extension.h new file mode 100644 index 0000000..037393e --- /dev/null +++ b/src/brpc/details/tcmalloc_extension.h @@ -0,0 +1,321 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// --- +// Author: Sanjay Ghemawat +// +// Extra extensions exported by some malloc implementations. These +// extensions are accessed through a virtual base class so an +// application can link against a malloc that does not implement these +// extensions, and it will get default versions that do nothing. +// +// NOTE FOR C USERS: If you wish to use this functionality from within +// a C program, see malloc_extension_c.h. + +#include +// I can't #include config.h in this public API file, but I should +// really use configure (and make malloc_extension.h a .in file) to +// figure out if the system has stdint.h or not. But I'm lazy, so +// for now I'm assuming it's a problem only with MSVC. +#ifndef _MSC_VER +#include +#endif +#include +#include + +// Annoying stuff for windows -- makes sure clients can import these functions +#ifndef PERFTOOLS_DLL_DECL +# ifdef _WIN32 +# define PERFTOOLS_DLL_DECL __declspec(dllimport) +# else +# define PERFTOOLS_DLL_DECL +# endif +#endif + +static const int kMallocHistogramSize = 64; + +typedef std::string MallocExtensionWriter; + +namespace base { +struct MallocRange; +} + +// The default implementations of the following routines do nothing. +// All implementations should be thread-safe; the current one +// (TCMallocImplementation) is. +class PERFTOOLS_DLL_DECL MallocExtension { + public: + virtual ~MallocExtension(); + + // Call this very early in the program execution -- say, in a global + // constructor -- to set up parameters and state needed by all + // instrumented malloc implemenatations. One example: this routine + // sets environemnt variables to tell STL to use libc's malloc() + // instead of doing its own memory management. This is safe to call + // multiple times, as long as each time is before threads start up. + static void Initialize(); + + // See "verify_memory.h" to see what these routines do + virtual bool VerifyAllMemory(); + virtual bool VerifyNewMemory(void* p); + virtual bool VerifyArrayNewMemory(void* p); + virtual bool VerifyMallocMemory(void* p); + virtual bool MallocMemoryStats(int* blocks, size_t* total, + int histogram[kMallocHistogramSize]); + + // Get a human readable description of the current state of the malloc + // data structures. The state is stored as a null-terminated string + // in a prefix of "buffer[0,buffer_length-1]". + // REQUIRES: buffer_length > 0. + virtual void GetStats(char* buffer, int buffer_length); + + // Outputs to "writer" a sample of live objects and the stack traces + // that allocated these objects. The format of the returned output + // is equivalent to the output of the heap profiler and can + // therefore be passed to "pprof". + // NOTE: by default, tcmalloc does not do any heap sampling, and this + // function will always return an empty sample. To get useful + // data from GetHeapSample, you must also set the environment + // variable TCMALLOC_SAMPLE_PARAMETER to a value such as 524288. + virtual void GetHeapSample(MallocExtensionWriter* writer); + + // Outputs to "writer" the stack traces that caused growth in the + // address space size. The format of the returned output is + // equivalent to the output of the heap profiler and can therefore + // be passed to "pprof". (This does not depend on, or require, + // TCMALLOC_SAMPLE_PARAMETER.) + virtual void GetHeapGrowthStacks(MallocExtensionWriter* writer); + + // Invokes func(arg, range) for every controlled memory + // range. *range is filled in with information about the range. + // + // This is a best-effort interface useful only for performance + // analysis. The implementation may not call func at all. + typedef void (RangeFunction)(void*, const base::MallocRange*); + virtual void Ranges(void* arg, RangeFunction func); + + // ------------------------------------------------------------------- + // Control operations for getting and setting malloc implementation + // specific parameters. Some currently useful properties: + // + // generic + // ------- + // "generic.current_allocated_bytes" + // Number of bytes currently allocated by application + // This property is not writable. + // + // "generic.heap_size" + // Number of bytes in the heap == + // current_allocated_bytes + + // fragmentation + + // freed memory regions + // This property is not writable. + // + // tcmalloc + // -------- + // "tcmalloc.max_total_thread_cache_bytes" + // Upper limit on total number of bytes stored across all + // per-thread caches. Default: 16MB. + // + // "tcmalloc.current_total_thread_cache_bytes" + // Number of bytes used across all thread caches. + // This property is not writable. + // + // "tcmalloc.pageheap_free_bytes" + // Number of bytes in free, mapped pages in page heap. These + // bytes can be used to fulfill allocation requests. They + // always count towards virtual memory usage, and unless the + // underlying memory is swapped out by the OS, they also count + // towards physical memory usage. This property is not writable. + // + // "tcmalloc.pageheap_unmapped_bytes" + // Number of bytes in free, unmapped pages in page heap. + // These are bytes that have been released back to the OS, + // possibly by one of the MallocExtension "Release" calls. + // They can be used to fulfill allocation requests, but + // typically incur a page fault. They always count towards + // virtual memory usage, and depending on the OS, typically + // do not count towards physical memory usage. This property + // is not writable. + // ------------------------------------------------------------------- + + // Get the named "property"'s value. Returns true if the property + // is known. Returns false if the property is not a valid property + // name for the current malloc implementation. + // REQUIRES: property != NULL; value != NULL + virtual bool GetNumericProperty(const char* property, size_t* value); + + // Set the named "property"'s value. Returns true if the property + // is known and writable. Returns false if the property is not a + // valid property name for the current malloc implementation, or + // is not writable. + // REQUIRES: property != NULL + virtual bool SetNumericProperty(const char* property, size_t value); + + // Mark the current thread as "idle". This routine may optionally + // be called by threads as a hint to the malloc implementation that + // any thread-specific resources should be released. Note: this may + // be an expensive routine, so it should not be called too often. + // + // Also, if the code that calls this routine will go to sleep for + // a while, it should take care to not allocate anything between + // the call to this routine and the beginning of the sleep. + // + // Most malloc implementations ignore this routine. + virtual void MarkThreadIdle(); + + // Mark the current thread as "busy". This routine should be + // called after MarkThreadIdle() if the thread will now do more + // work. If this method is not called, performance may suffer. + // + // Most malloc implementations ignore this routine. + virtual void MarkThreadBusy(); + + // Try to release num_bytes of free memory back to the operating + // system for reuse. Use this extension with caution -- to get this + // memory back may require faulting pages back in by the OS, and + // that may be slow. (Currently only implemented in tcmalloc.) + virtual void ReleaseToSystem(size_t num_bytes); + + // Same as ReleaseToSystem() but release as much memory as possible. + virtual void ReleaseFreeMemory(); + + // Sets the rate at which we release unused memory to the system. + // Zero means we never release memory back to the system. Increase + // this flag to return memory faster; decrease it to return memory + // slower. Reasonable rates are in the range [0,10]. (Currently + // only implemented in tcmalloc). + virtual void SetMemoryReleaseRate(double rate); + + // Gets the release rate. Returns a value < 0 if unknown. + virtual double GetMemoryReleaseRate(); + + // Returns the estimated number of bytes that will be allocated for + // a request of "size" bytes. This is an estimate: an allocation of + // SIZE bytes may reserve more bytes, but will never reserve less. + // (Currently only implemented in tcmalloc, other implementations + // always return SIZE.) + // This is equivalent to malloc_good_size() in OS X. + virtual size_t GetEstimatedAllocatedSize(size_t size); + + // Returns the actual number N of bytes reserved by tcmalloc for the + // pointer p. The client is allowed to use the range of bytes + // [p, p+N) in any way it wishes (i.e. N is the "usable size" of this + // allocation). This number may be equal to or greater than the number + // of bytes requested when p was allocated. + // p must have been allocated by this malloc implementation, + // must not be an interior pointer -- that is, must be exactly + // the pointer returned to by malloc() et al., not some offset + // from that -- and should not have been freed yet. p may be NULL. + // (Currently only implemented in tcmalloc; other implementations + // will return 0.) + // This is equivalent to malloc_size() in OS X, malloc_usable_size() + // in glibc, and _msize() for windows. + virtual size_t GetAllocatedSize(void* p); + + // The current malloc implementation. Always non-NULL. + static MallocExtension* instance(); + + // Change the malloc implementation. Typically called by the + // malloc implementation during initialization. + static void Register(MallocExtension* implementation); + + // Returns detailed information about malloc's freelists. For each list, + // return a FreeListInfo: + struct FreeListInfo { + size_t min_object_size; + size_t max_object_size; + size_t total_bytes_free; + const char* type; + }; + // Each item in the vector refers to a different freelist. The lists + // are identified by the range of allocations that objects in the + // list can satisfy ([min_object_size, max_object_size]) and the + // type of freelist (see below). The current size of the list is + // returned in total_bytes_free (which count against a processes + // resident and virtual size). + // + // Currently supported types are: + // + // "tcmalloc.page{_unmapped}" - tcmalloc's page heap. An entry for each size + // class in the page heap is returned. Bytes in "page_unmapped" + // are no longer backed by physical memory and do not count against + // the resident size of a process. + // + // "tcmalloc.large{_unmapped}" - tcmalloc's list of objects larger + // than the largest page heap size class. Only one "large" + // entry is returned. There is no upper-bound on the size + // of objects in the large free list; this call returns + // kint64max for max_object_size. Bytes in + // "large_unmapped" are no longer backed by physical memory + // and do not count against the resident size of a process. + // + // "tcmalloc.central" - tcmalloc's central free-list. One entry per + // size-class is returned. Never unmapped. + // + // "debug.free_queue" - free objects queued by the debug allocator + // and not returned to tcmalloc. + // + // "tcmalloc.thread" - tcmalloc's per-thread caches. Never unmapped. + virtual void GetFreeListSizes(std::vector* v); + + protected: + // Get a list of stack traces of sampled allocation points. Returns + // a pointer to a "new[]-ed" result array, and stores the sample + // period in "sample_period". + // + // The state is stored as a sequence of adjacent entries + // in the returned array. Each entry has the following form: + // uintptr_t count; // Number of objects with following trace + // uintptr_t size; // Total size of objects with following trace + // uintptr_t depth; // Number of PC values in stack trace + // void* stack[depth]; // PC values that form the stack trace + // + // The list of entries is terminated by a "count" of 0. + // + // It is the responsibility of the caller to "delete[]" the returned array. + // + // May return NULL to indicate no results. + // + // This is an internal extension. Callers should use the more + // convenient "GetHeapSample(string*)" method defined above. + virtual void** ReadStackTraces(int* sample_period); + + // Like ReadStackTraces(), but returns stack traces that caused growth + // in the address space size. + virtual void** ReadHeapGrowthStackTraces(); +}; + +// True iff heap profiler is enabled. +bool IsHeapProfilerEnabled(); + +bool IsTCMallocEnabled(); + +// True iff TCMALLOC_SAMPLE_PARAMETER is set in environment. +bool has_TCMALLOC_SAMPLE_PARAMETER(); diff --git a/src/brpc/details/usercode_backup_pool.cpp b/src/brpc/details/usercode_backup_pool.cpp new file mode 100644 index 0000000..338038a --- /dev/null +++ b/src/brpc/details/usercode_backup_pool.cpp @@ -0,0 +1,186 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include "butil/scoped_lock.h" +#include "butil/threading/platform_thread.h" +#ifdef BAIDU_INTERNAL +#include "butil/comlog_sink.h" +#endif +#include "brpc/details/usercode_backup_pool.h" + +namespace bthread { +// Defined in bthread/task_control.cpp +void run_worker_startfn(); +} + + +namespace brpc { + +DEFINE_int32(usercode_backup_threads, 5, "# of backup threads to run user code" + " when too many pthread worker of bthreads are used"); +DEFINE_int32(max_pending_in_each_backup_thread, 10, + "Max number of un-run user code in each backup thread, requests" + " still coming in will be failed"); + +// Store pending user code. +struct UserCode { + void (*fn)(void*); + void* arg; +}; +struct UserCodeBackupPool { + // Run user code when parallelism of user code reaches the threshold + std::deque queue; + bvar::PassiveStatus inplace_var; + bvar::PassiveStatus queue_size_var; + bvar::Adder inpool_count; + bvar::PerSecond > inpool_per_second; + // NOTE: we don't use Adder directly which does not compile in gcc 3.4 + bvar::Adder inpool_elapse_us; + bvar::PassiveStatus inpool_elapse_s; + bvar::PerSecond > pool_usage; + + UserCodeBackupPool(); + int Init(); + void UserCodeRunningLoop(); +}; + +static pthread_mutex_t s_usercode_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t s_usercode_cond = PTHREAD_COND_INITIALIZER; +static pthread_once_t s_usercode_init = PTHREAD_ONCE_INIT; +butil::static_atomic g_usercode_inplace = BUTIL_STATIC_ATOMIC_INIT(0); +bool g_too_many_usercode = false; +static UserCodeBackupPool* s_usercode_pool = NULL; + +static int GetUserCodeInPlace(void*) { + return g_usercode_inplace.load(butil::memory_order_relaxed); +} + +static size_t GetUserCodeQueueSize(void*) { + BAIDU_SCOPED_LOCK(s_usercode_mutex); + return (s_usercode_pool != NULL ? s_usercode_pool->queue.size() : 0); +} + +static double GetInPoolElapseInSecond(void* arg) { + return static_cast*>(arg)->get_value() / 1000000.0; +} + +UserCodeBackupPool::UserCodeBackupPool() + : inplace_var("rpc_usercode_inplace", GetUserCodeInPlace, NULL) + , queue_size_var("rpc_usercode_queue_size", GetUserCodeQueueSize, NULL) + , inpool_count("rpc_usercode_backup_count") + , inpool_per_second("rpc_usercode_backup_second", &inpool_count) + , inpool_elapse_s(GetInPoolElapseInSecond, &inpool_elapse_us) + , pool_usage("rpc_usercode_backup_usage", &inpool_elapse_s, 1) { +} + +static void* UserCodeRunner(void* args) { + butil::PlatformThread::SetNameSimple("brpc_user_code_runner"); + static_cast(args)->UserCodeRunningLoop(); + return NULL; +} + +int UserCodeBackupPool::Init() { + // Like bthread workers, these threads never quit (to avoid potential hang + // during termination of program). + for (int i = 0; i < FLAGS_usercode_backup_threads; ++i) { + pthread_t th; + if (pthread_create(&th, NULL, UserCodeRunner, this) != 0) { + LOG(ERROR) << "Fail to create UserCodeRunner"; + return -1; + } + } + return 0; +} + +// Entry of backup thread for running user code. +void UserCodeBackupPool::UserCodeRunningLoop() { + bthread::run_worker_startfn(); +#ifdef BAIDU_INTERNAL + logging::ComlogInitializer comlog_initializer; +#endif + + int64_t last_time = butil::cpuwide_time_us(); + while (true) { + bool blocked = false; + UserCode usercode = { NULL, NULL }; + { + BAIDU_SCOPED_LOCK(s_usercode_mutex); + while (queue.empty()) { + pthread_cond_wait(&s_usercode_cond, &s_usercode_mutex); + blocked = true; + } + usercode = queue.front(); + queue.pop_front(); + if (g_too_many_usercode && + (int)queue.size() <= FLAGS_usercode_backup_threads) { + g_too_many_usercode = false; + } + } + const int64_t begin_time = (blocked ? butil::cpuwide_time_us() : last_time); + usercode.fn(usercode.arg); + const int64_t end_time = butil::cpuwide_time_us(); + inpool_count << 1; + inpool_elapse_us << (end_time - begin_time); + last_time = end_time; + } +} + +static void InitUserCodeBackupPool() { + s_usercode_pool = new UserCodeBackupPool; + if (s_usercode_pool->Init() != 0) { + LOG(ERROR) << "Fail to init UserCodeBackupPool"; + // rare and critical, often happen when the program just started since + // this function is called from GlobalInitializeOrDieImpl() as well, + // quiting is the best choice. + exit(1); + } +} + +void InitUserCodeBackupPoolOnceOrDie() { + pthread_once(&s_usercode_init, InitUserCodeBackupPool); +} + +void EndRunningUserCodeInPool(void (*fn)(void*), void* arg) { + InitUserCodeBackupPoolOnceOrDie(); + + g_usercode_inplace.fetch_sub(1, butil::memory_order_relaxed); + + // Not enough idle workers, run the code in backup threads to prevent + // all workers from being blocked and no responses will be processed + // anymore (deadlocked). + const UserCode usercode = { fn, arg }; + pthread_mutex_lock(&s_usercode_mutex); + s_usercode_pool->queue.push_back(usercode); + // If the queue has too many items, we can't drop the user code + // directly which often must be run, for example: client-side done. + // The solution is that we set a mark which is not cleared before + // queue becomes short again. RPC code checks the mark before + // submitting tasks that may generate more user code. + if ((int)s_usercode_pool->queue.size() >= + (FLAGS_usercode_backup_threads * + FLAGS_max_pending_in_each_backup_thread)) { + g_too_many_usercode = true; + } + pthread_mutex_unlock(&s_usercode_mutex); + pthread_cond_signal(&s_usercode_cond); +} + +} // namespace brpc diff --git a/src/brpc/details/usercode_backup_pool.h b/src/brpc/details/usercode_backup_pool.h new file mode 100644 index 0000000..e53752f --- /dev/null +++ b/src/brpc/details/usercode_backup_pool.h @@ -0,0 +1,88 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_USERCODE_BACKUP_POOL_H +#define BRPC_USERCODE_BACKUP_POOL_H + +#include "butil/atomicops.h" +#include "bthread/bthread.h" +#include + + +namespace brpc { + +DECLARE_bool(usercode_in_pthread); +DECLARE_int32(usercode_backup_threads); + +// "user code backup pool" is a set of pthreads to run user code when #pthread +// workers of bthreads reaches a threshold, avoiding potential deadlock when +// -usercode_in_pthread is on. These threads are NOT supposed to be active +// frequently, if they're, user should configure more num_threads for the +// server or set -bthread_concurrency to a larger value. + +// Run the user code in-place or in backup threads. The place depends on +// busy-ness of bthread workers. +// NOTE: To avoid memory allocation(for `arg') in the case of in-place running, +// check out the inline impl. of this function just below. +void RunUserCode(void (*fn)(void*), void* arg); + +// RPC code should check this function before submitting operations that have +// user code to run laterly. +inline bool TooManyUserCode() { + extern bool g_too_many_usercode; + return g_too_many_usercode; +} + +// If this function returns true, the user code is suggested to be run in-place +// and user should call EndRunningUserCodeInPlace() after running the code. +// Otherwise, user should call EndRunningUserCodeInPool() to run the user code +// in backup threads. +// Check RunUserCode() below to see the usage pattern. +inline bool BeginRunningUserCode() { + extern butil::static_atomic g_usercode_inplace; + return (g_usercode_inplace.fetch_add(1, butil::memory_order_relaxed) + + FLAGS_usercode_backup_threads) < bthread_getconcurrency(); +} + +inline void EndRunningUserCodeInPlace() { + extern butil::static_atomic g_usercode_inplace; + g_usercode_inplace.fetch_sub(1, butil::memory_order_relaxed); +} + +void EndRunningUserCodeInPool(void (*fn)(void*), void* arg); + +// Incorporate functions above together. However `arg' to this function often +// has to be new-ed even for in-place cases. If performance is critical, use +// the BeginXXX/EndXXX pattern. +inline void RunUserCode(void (*fn)(void*), void* arg) { + if (BeginRunningUserCode()) { + fn(arg); + EndRunningUserCodeInPlace(); + } else { + EndRunningUserCodeInPool(fn, arg); + } +} + +// [Optional] initialize the pool of backup threads. If this function is not +// called, it will be called in EndRunningUserCodeInPool +void InitUserCodeBackupPoolOnceOrDie(); + +} // namespace brpc + + +#endif //BRPC_USERCODE_BACKUP_POOL_H diff --git a/src/brpc/errno.proto b/src/brpc/errno.proto new file mode 100644 index 0000000..26ffadc --- /dev/null +++ b/src/brpc/errno.proto @@ -0,0 +1,57 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package brpc; +option java_package="com.brpc"; +option java_outer_classname="BaiduRpcErrno"; + +enum Errno { + // Errno caused by client + ENOSERVICE = 1001; // Service not found + ENOMETHOD = 1002; // Method not found + EREQUEST = 1003; // Bad Request + ERPCAUTH = 1004; // Unauthorized, can't be called EAUTH + // directly which is defined in MACOSX + ETOOMANYFAILS = 1005; // Too many sub calls failed + EPCHANFINISH = 1006; // [Internal] ParallelChannel finished + EBACKUPREQUEST = 1007; // Sending backup request + ERPCTIMEDOUT = 1008; // RPC call is timed out + EFAILEDSOCKET = 1009; // Broken socket + EHTTP = 1010; // Bad http call + EOVERCROWDED = 1011; // The server is overcrowded + ERTMPPUBLISHABLE = 1012; // RtmpRetryingClientStream is publishable + ERTMPCREATESTREAM = 1013; // createStream was rejected by the RTMP server + EEOF = 1014; // Got EOF + EUNUSED = 1015; // The socket was not needed + ESSL = 1016; // SSL related error + EH2RUNOUTSTREAMS = 1017; // The H2 socket was run out of streams + EREJECT = 1018; // The Request is rejected + + // Errno caused by server + EINTERNAL = 2001; // Internal Server Error + ERESPONSE = 2002; // Bad Response + ELOGOFF = 2003; // Server is stopping + ELIMIT = 2004; // Reached server's limit on resources + ECLOSE = 2005; // Close socket initiatively + EITP = 2006; // Failed Itp response + ESHUTDOWNWRITE = 2007; // Shutdown write of socket + + // Errno related to RDMA (may happen at both sides) + ERDMA = 3001; // RDMA verbs error + ERDMAMEM = 3002; // Memory not registered for RDMA +} diff --git a/src/brpc/esp_head.h b/src/brpc/esp_head.h new file mode 100644 index 0000000..c5d9cc1 --- /dev/null +++ b/src/brpc/esp_head.h @@ -0,0 +1,46 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_ESP_HEAD_H +#define BRPC_ESP_HEAD_H + +#include + +namespace brpc { + +#pragma pack(push, r1, 1) +union EspAddress { + uint64_t addr; + struct { + uint16_t stub; + uint16_t port; + uint32_t ip; + }; +}; + +struct EspHead { + EspAddress from; + EspAddress to; + uint32_t msg; + uint64_t msg_id; + int body_len; +}; +#pragma pack(pop, r1) + +} // namespace brpc + +#endif // BRPC_ESP_HEAD_H diff --git a/src/brpc/esp_message.cpp b/src/brpc/esp_message.cpp new file mode 100644 index 0000000..affebd2 --- /dev/null +++ b/src/brpc/esp_message.cpp @@ -0,0 +1,72 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "esp_message.h" + +#include "brpc/proto_base.pb.h" +#include "butil/logging.h" + +namespace brpc { + +EspMessage::EspMessage() + : NonreflectableMessage() { + SharedCtor(); +} + +void EspMessage::SharedCtor() { + memset(&head, 0, sizeof(head)); +} + +EspMessage::~EspMessage() { + SharedDtor(); +} + +void EspMessage::SharedDtor() { +} + +void EspMessage::Clear() { + head.body_len = 0; + body.clear(); +} + +size_t EspMessage::ByteSizeLong() const { + return sizeof(head) + body.size(); +} + +void EspMessage::MergeFrom(const EspMessage& from) { + CHECK_NE(&from, this); + head = from.head; + body = from.body; +} + +void EspMessage::Swap(EspMessage* other) { + if (other != this) { + const EspHead tmp = other->head; + other->head = head; + head = tmp; + body.swap(other->body); + } +} + +::google::protobuf::Metadata EspMessage::GetMetadata() const { + ::google::protobuf::Metadata metadata{}; + metadata.descriptor = EspMessageBase::descriptor(); + metadata.reflection = nullptr; + return metadata; +} + +} // namespace brpc diff --git a/src/brpc/esp_message.h b/src/brpc/esp_message.h new file mode 100644 index 0000000..4178b82 --- /dev/null +++ b/src/brpc/esp_message.h @@ -0,0 +1,55 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_ESP_MESSAGE_H +#define BRPC_ESP_MESSAGE_H + +#include "brpc/esp_head.h" +#include "brpc/nonreflectable_message.h" +#include "butil/iobuf.h" + +namespace brpc { + +class EspMessage : public NonreflectableMessage { +public: + EspHead head; + butil::IOBuf body; + +public: + EspMessage(); + ~EspMessage() override; + + void Swap(EspMessage* other); + + // implements Message ---------------------------------------------- + + void MergeFrom(const EspMessage& from) override; + void Clear() override; + + size_t ByteSizeLong() const override; + int GetCachedSize() const PB_425_OVERRIDE { return ByteSize(); } + + ::google::protobuf::Metadata GetMetadata() const PB_527_OVERRIDE; + +private: + void SharedCtor(); + void SharedDtor(); +}; + +} // namespace brpc + +#endif // BRPC_ESP_MESSAGE_H diff --git a/src/brpc/event_dispatcher.cpp b/src/brpc/event_dispatcher.cpp new file mode 100644 index 0000000..15152fc --- /dev/null +++ b/src/brpc/event_dispatcher.cpp @@ -0,0 +1,114 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // DEFINE_int32 +#include "butil/compat.h" +#include "butil/fd_utility.h" // make_close_on_exec +#include "butil/logging.h" // LOG +#include "butil/third_party/murmurhash3/murmurhash3.h"// fmix32 +#include "bvar/latency_recorder.h" // bvar::LatencyRecorder +#include "bthread/bthread.h" // bthread_start_background +#include "bthread/task_group.h" // TaskGroup::address_meta +#include "brpc/event_dispatcher.h" + +DECLARE_int32(task_group_ntags); + +DECLARE_int32(event_dispatcher_num); + +namespace brpc { +DEFINE_bool(event_dispatcher_edisp_unsched, false, + "Disable event dispatcher schedule"); + +DEFINE_bool(usercode_in_pthread, false, + "Call user's callback in pthreads, use bthreads otherwise"); +DEFINE_bool(usercode_in_coroutine, false, + "User's callback are run in coroutine, no bthread or pthread blocking call"); + +static EventDispatcher* g_edisp = NULL; +static bvar::LatencyRecorder* g_edisp_read_lantency = NULL; +static bvar::LatencyRecorder* g_edisp_write_lantency = NULL; +static pthread_once_t g_edisp_once = PTHREAD_ONCE_INIT; + +bool EventDispatcherUnsched() { + return FLAGS_event_dispatcher_edisp_unsched; +} + +static void StopAndJoinGlobalDispatchers() { + for (int i = 0; i < FLAGS_task_group_ntags; ++i) { + for (int j = 0; j < FLAGS_event_dispatcher_num; ++j) { + g_edisp[i * FLAGS_event_dispatcher_num + j].Stop(); + g_edisp[i * FLAGS_event_dispatcher_num + j].Join(); + } + } +} + +void InitializeGlobalDispatchers() { + g_edisp_read_lantency = new bvar::LatencyRecorder("event_dispatcher_read"); + g_edisp_write_lantency = new bvar::LatencyRecorder("event_dispatcher_write"); + + g_edisp = new EventDispatcher[FLAGS_task_group_ntags * FLAGS_event_dispatcher_num]; + for (int i = 0; i < FLAGS_task_group_ntags; ++i) { + for (int j = 0; j < FLAGS_event_dispatcher_num; ++j) { + bthread_attr_t attr = + FLAGS_usercode_in_pthread ? BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL; + attr.tag = (BTHREAD_TAG_DEFAULT + i) % FLAGS_task_group_ntags; + g_edisp[i * FLAGS_event_dispatcher_num + j].set_priority_index(j); + CHECK_EQ(0, g_edisp[i * FLAGS_event_dispatcher_num + j].Start(&attr)); + } + } + // This atexit is will be run before g_task_control.stop() because above + // Start() initializes g_task_control by creating bthread (to run epoll/kqueue). + CHECK_EQ(0, atexit(StopAndJoinGlobalDispatchers)); +} + +EventDispatcher& GetGlobalEventDispatcher(int fd, bthread_tag_t tag) { + pthread_once(&g_edisp_once, InitializeGlobalDispatchers); + if (FLAGS_task_group_ntags == 1 && FLAGS_event_dispatcher_num == 1) { + return g_edisp[0]; + } + int index = butil::fmix32(fd) % FLAGS_event_dispatcher_num; + return g_edisp[tag * FLAGS_event_dispatcher_num + index]; +} + +int IOEventData::OnCreated(const IOEventDataOptions& options) { + if (!options.input_cb) { + LOG(ERROR) << "Invalid input_cb=NULL"; + return -1; + } + if (!options.output_cb) { + LOG(ERROR) << "Invalid output_cb=NULL"; + return -1; + } + + _options = options; + return 0; +} + +void IOEventData::BeforeRecycled() { + _options = { NULL, NULL, NULL }; +} + +} // namespace brpc + +#if defined(OS_LINUX) + #include "brpc/event_dispatcher_epoll.cpp" +#elif defined(OS_MACOSX) + #include "brpc/event_dispatcher_kqueue.cpp" +#else + #error Not implemented +#endif diff --git a/src/brpc/event_dispatcher.h b/src/brpc/event_dispatcher.h new file mode 100644 index 0000000..d95243a --- /dev/null +++ b/src/brpc/event_dispatcher.h @@ -0,0 +1,315 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_EVENT_DISPATCHER_H +#define BRPC_EVENT_DISPATCHER_H + +#include // DECLARE_bool +#include "butil/macros.h" // DISALLOW_COPY_AND_ASSIGN +#include "bthread/types.h" // bthread_t, bthread_attr_t +#include "brpc/versioned_ref_with_id.h" + + +namespace brpc { + +DECLARE_bool(event_dispatcher_edisp_unsched); + +// Unique identifier of a IOEventData. +// Users shall store EventDataId instead of EventData and call EventData::Address() +// to convert the identifier to an unique_ptr at each access. Whenever a +// unique_ptr is not destructed, the enclosed EventData will not be recycled. +typedef VRefId IOEventDataId; + +const VRefId INVALID_IO_EVENT_DATA_ID = INVALID_VREF_ID; + +class IOEventData; + +typedef VersionedRefWithIdUniquePtr EventDataUniquePtr; + +// User callback type of input event and output event. +typedef int (*InputEventCallback) (void* id, uint32_t events, + const bthread_attr_t& thread_attr); +typedef InputEventCallback OutputEventCallback; + +struct IOEventDataOptions { + // Callback for input event. + InputEventCallback input_cb; + // Callback for output event. + OutputEventCallback output_cb; + // User data. + void* user_data; +}; + +// EventDispatcher finds IOEventData by IOEventDataId which is +// stored in epoll/kqueue data, and calls input/output event callback, +// so EventDispatcher supports various IO types, such as socket, +// pipe, eventfd, timerfd, etc. +class IOEventData : public VersionedRefWithId { +public: + explicit IOEventData(Forbidden f) + : VersionedRefWithId(f) + , _options{ NULL, NULL, NULL } {} + + DISALLOW_COPY_AND_ASSIGN(IOEventData); + + int CallInputEventCallback(uint32_t events, + const bthread_attr_t& thread_attr) { + return _options.input_cb(_options.user_data, events, thread_attr); + } + + int CallOutputEventCallback(uint32_t events, + const bthread_attr_t& thread_attr) { + return _options.output_cb(_options.user_data, events, thread_attr); + } + +private: +friend class VersionedRefWithId; + + int OnCreated(const IOEventDataOptions& options); + void BeforeRecycled(); + + IOEventDataOptions _options; +}; + +namespace rdma { +class RdmaEndpoint; +} + +// Dispatch edge-triggered events of file descriptors to consumers. +// By default callbacks run in spawned bthreads; when usercode-in-coroutine is +// enabled, the callback may run inline in the current coroutine. +class EventDispatcher { +friend class Socket; +friend class rdma::RdmaEndpoint; +template friend class IOEvent; +public: + EventDispatcher(); + + virtual ~EventDispatcher(); + + // Start this dispatcher in a bthread. + // Use |*thread_attr| (if it's not NULL) as the attribute to + // create bthreads running user callbacks. + // Returns 0 on success, -1 otherwise. + virtual int Start(const bthread_attr_t* thread_attr); + + // True iff this dispatcher is running in a bthread + bool Running() const; + + // Stop bthread of this dispatcher. + void Stop(); + + void set_priority_index(int idx) { _priority_index = idx; } + + // Suspend calling thread until bthread of this dispatcher stops. + void Join(); + + // When edge-triggered events happen on `fd', call + // `on_edge_triggered_events' of `socket_id'. + // Notice that this function also transfers ownership of `socket_id', + // When the file descriptor is removed from internal epoll, the Socket + // will be dereferenced once additionally. + // Returns 0 on success, -1 otherwise. + int AddConsumer(IOEventDataId event_data_id, int fd); + + // Watch EPOLLOUT event on `fd' into epoll device. If `pollin' is + // true, EPOLLIN event will also be included and EPOLL_CTL_MOD will + // be used instead of EPOLL_CTL_ADD. When event arrives, + // `Socket::HandleEpollOut' will be called with `socket_id' + // Returns 0 on success, -1 otherwise and errno is set + int RegisterEvent(IOEventDataId event_data_id, int fd, bool pollin); + + // Remove EPOLLOUT event on `fd'. If `pollin' is true, EPOLLIN event + // will be kept and EPOLL_CTL_MOD will be used instead of EPOLL_CTL_DEL + // Returns 0 on success, -1 otherwise and errno is set + int UnregisterEvent(IOEventDataId event_data_id, int fd, bool pollin); + +private: + DISALLOW_COPY_AND_ASSIGN(EventDispatcher); + + // Calls Run() + static void* RunThis(void* arg); + + // Thread entry. + void Run(); + + // Remove the file descriptor `fd' from epoll. + int RemoveConsumer(int fd); + + // Call user callback of input event and output event. + template + static int OnEvent(IOEventDataId event_data_id, uint32_t events, + const bthread_attr_t& thread_attr) { + EventDataUniquePtr data; + if (IOEventData::Address(event_data_id, &data) != 0) { + return -1; + } + return IsInputEvent ? + data->CallInputEventCallback(events, thread_attr) : + data->CallOutputEventCallback(events, thread_attr); + } + + static int CallInputEventCallback(IOEventDataId event_data_id, + uint32_t events, + const bthread_attr_t& thread_attr) { + return OnEvent(event_data_id, events, thread_attr); + } + + static int CallOutputEventCallback(IOEventDataId event_data_id, + uint32_t events, + const bthread_attr_t& thread_attr) { + return OnEvent(event_data_id, events, thread_attr); + } + + // The epoll/kqueue fd to watch events. + int _event_dispatcher_fd; + + // false unless Stop() is called. + volatile bool _stop; + + // identifier of hosting bthread + bthread_t _tid; + + // The attribute of bthreads calling user callbacks. + bthread_attr_t _thread_attr; + + // Pipe fds to wakeup EventDispatcher from `epoll_wait' in order to quit + int _wakeup_fds[2]; + + int _priority_index{-1}; +}; + +EventDispatcher& GetGlobalEventDispatcher(int fd, bthread_tag_t tag); + +// Unified unsched switch for transport layer. +// false -> urgent start (foreground scheduling before caller continues), +// true -> background start (allowing schedule away). +bool EventDispatcherUnsched(); + +// IOEvent class manages the IO events of a file descriptor conveniently. +template +class IOEvent { +public: + IOEvent() + : _init(false) + , _event_data_id(INVALID_IO_EVENT_DATA_ID) + , _bthread_tag(bthread_self_tag()) {} + + ~IOEvent() { Reset(); } + + DISALLOW_COPY_AND_ASSIGN(IOEvent); + + int Init(void* user_data) { + if (_init) { + LOG(WARNING) << "IOEvent has been initialized"; + return 0; + } + IOEventDataOptions options{ OnInputEvent, OnOutputEvent, user_data }; + if (IOEventData::Create(&_event_data_id, options) != 0) { + LOG(ERROR) << "Fail to create EventData"; + return -1; + } + _init = true; + return 0; + } + + void Reset() { + if (_init) { + IOEventData::SetFailedById(_event_data_id); + _init = false; + } + } + + // See comments of `EventDispatcher::AddConsumer'. + int AddConsumer(int fd) { + if (!_init) { + LOG(ERROR) << "IOEvent has not been initialized"; + return -1; + } + return GetGlobalEventDispatcher(fd, _bthread_tag) + .AddConsumer(_event_data_id, fd); + } + + // See comments of `EventDispatcher::RemoveConsumer'. + int RemoveConsumer(int fd) { + if (!_init) { + LOG(ERROR) << "IOEvent has not been initialized"; + return -1; + } + return GetGlobalEventDispatcher(fd, _bthread_tag).RemoveConsumer(fd); + } + + // See comments of `EventDispatcher::RegisterEvent'. + int RegisterEvent(int fd, bool pollin) { + if (!_init) { + LOG(ERROR) << "IOEvent has not been initialized"; + return -1; + } + return GetGlobalEventDispatcher(fd, _bthread_tag) + .RegisterEvent(_event_data_id, fd, pollin); + } + + // See comments of `EventDispatcher::UnregisterEvent'. + int UnregisterEvent(int fd, bool pollin) { + if (!_init) { + LOG(ERROR) << "IOEvent has not been initialized"; + return -1; + } + return GetGlobalEventDispatcher(fd, _bthread_tag) + .UnregisterEvent(_event_data_id, fd, pollin); + } + + void set_bthread_tag(bthread_tag_t bthread_tag) { + _bthread_tag = bthread_tag; + } + bthread_tag_t bthread_tag() const { + return _bthread_tag; + } + +private: + // Generic callback to handle input event. + static int OnInputEvent(void* user_data, uint32_t events, + const bthread_attr_t& thread_attr) { + static_assert( + butil::is_result_int::value, + "T::OnInputEvent signature mismatch"); + return T::OnInputEvent(user_data, events, thread_attr); + } + + // Generic callback to handle output event. + static int OnOutputEvent(void* user_data, uint32_t events, + const bthread_attr_t& thread_attr) { + static_assert( + butil::is_result_int::value, + "T::OnInputEvent signature mismatch"); + return T::OnOutputEvent(user_data, events, thread_attr); + } + + bool _init; + IOEventDataId _event_data_id; + bthread_tag_t _bthread_tag; +}; + +} // namespace brpc + + +#endif // BRPC_EVENT_DISPATCHER_H diff --git a/src/brpc/event_dispatcher_epoll.cpp b/src/brpc/event_dispatcher_epoll.cpp new file mode 100644 index 0000000..31f60aa --- /dev/null +++ b/src/brpc/event_dispatcher_epoll.cpp @@ -0,0 +1,252 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + + +#ifdef BRPC_SOCKET_HAS_EOF +#include "brpc/details/has_epollrdhup.h" +#endif + +namespace brpc { + +EventDispatcher::EventDispatcher() + : _event_dispatcher_fd(-1) + , _stop(false) + , _tid(0) + , _thread_attr(BTHREAD_ATTR_NORMAL) { + _event_dispatcher_fd = epoll_create(1024 * 1024); + if (_event_dispatcher_fd < 0) { + PLOG(FATAL) << "Fail to create epoll"; + return; + } + CHECK_EQ(0, butil::make_close_on_exec(_event_dispatcher_fd)); + + _wakeup_fds[0] = -1; + _wakeup_fds[1] = -1; + if (pipe(_wakeup_fds) != 0) { + PLOG(FATAL) << "Fail to create pipe"; + return; + } +} + +EventDispatcher::~EventDispatcher() { + Stop(); + Join(); + if (_event_dispatcher_fd >= 0) { + close(_event_dispatcher_fd); + _event_dispatcher_fd = -1; + } + if (_wakeup_fds[0] > 0) { + close(_wakeup_fds[0]); + close(_wakeup_fds[1]); + } +} + +int EventDispatcher::Start(const bthread_attr_t* thread_attr) { + if (_event_dispatcher_fd < 0) { + LOG(FATAL) << "epoll was not created"; + return -1; + } + + if (_tid != 0) { + LOG(FATAL) << "Already started this dispatcher(" << this + << ") in bthread=" << _tid; + return -1; + } + + // Set _thread_attr before creating epoll thread to make sure + // everyting seems sane to the thread. + if (thread_attr) { + _thread_attr = *thread_attr; + } + + //_thread_attr is used in StartInputEvent(), assign flag NEVER_QUIT to it will cause new bthread + // that created by epoll_wait() never to quit. + // Only event dispatcher thread has flag BTHREAD_GLOBAL_PRIORITY. + bthread_attr_t epoll_thread_attr = + _thread_attr | BTHREAD_NEVER_QUIT | BTHREAD_GLOBAL_PRIORITY; + bthread_attr_set_name(&epoll_thread_attr, "EventDispatcher::RunThis"); + + // Polling thread uses the same attr for consumer threads (NORMAL right + // now). Previously, we used small stack (32KB) which may be overflowed + // when the older comlog (e.g. 3.1.85) calls com_openlog_r(). Since this + // is also a potential issue for consumer threads, using the same attr + // should be a reasonable solution. + int rc = bthread_start_background(&_tid, &epoll_thread_attr, RunThis, this); + if (rc) { + LOG(FATAL) << "Fail to create epoll thread: " << berror(rc); + return -1; + } + return 0; +} + +bool EventDispatcher::Running() const { + return !_stop && _event_dispatcher_fd >= 0 && _tid != 0; +} + +void EventDispatcher::Stop() { + _stop = true; + + if (_event_dispatcher_fd >= 0) { + epoll_event evt = { EPOLLOUT, { NULL } }; + epoll_ctl(_event_dispatcher_fd, EPOLL_CTL_ADD, _wakeup_fds[1], &evt); + } +} + +void EventDispatcher::Join() { + if (_tid) { + bthread_join(_tid, NULL); + _tid = 0; + } +} + +int EventDispatcher::RegisterEvent(IOEventDataId event_data_id, + int fd, bool pollin) { + if (_event_dispatcher_fd < 0) { + errno = EINVAL; + return -1; + } + + epoll_event evt; + evt.data.u64 = event_data_id; + evt.events = EPOLLOUT | EPOLLET; +#ifdef BRPC_SOCKET_HAS_EOF + evt.events |= has_epollrdhup; +#endif + if (pollin) { + evt.events |= EPOLLIN; + if (epoll_ctl(_event_dispatcher_fd, EPOLL_CTL_MOD, fd, &evt) < 0) { + // This fd has been removed from epoll via `RemoveConsumer', + // in which case errno will be ENOENT + return -1; + } + } else { + if (epoll_ctl(_event_dispatcher_fd, EPOLL_CTL_ADD, fd, &evt) < 0) { + return -1; + } + } + return 0; +} + +int EventDispatcher::UnregisterEvent(IOEventDataId event_data_id, + int fd, bool pollin) { + if (pollin) { + epoll_event evt; + evt.data.u64 = event_data_id; + evt.events = EPOLLIN | EPOLLET; +#ifdef BRPC_SOCKET_HAS_EOF + evt.events |= has_epollrdhup; +#endif + return epoll_ctl(_event_dispatcher_fd, EPOLL_CTL_MOD, fd, &evt); + } else { + return epoll_ctl(_event_dispatcher_fd, EPOLL_CTL_DEL, fd, NULL); + } + return -1; +} + +int EventDispatcher::AddConsumer(IOEventDataId event_data_id, int fd) { + if (_event_dispatcher_fd < 0) { + errno = EINVAL; + return -1; + } + epoll_event evt; + evt.data.u64 = event_data_id; + evt.events = EPOLLIN | EPOLLET; +#ifdef BRPC_SOCKET_HAS_EOF + evt.events |= has_epollrdhup; +#endif + return epoll_ctl(_event_dispatcher_fd, EPOLL_CTL_ADD, fd, &evt); +} + +int EventDispatcher::RemoveConsumer(int fd) { + if (fd < 0) { + return -1; + } + // Removing the consumer from dispatcher before closing the fd because + // if process was forked and the fd is not marked as close-on-exec, + // closing does not set reference count of the fd to 0, thus does not + // remove the fd from epoll. More badly, the fd will not be removable + // from epoll again! If the fd was level-triggered and there's data left, + // epoll_wait will keep returning events of the fd continuously, making + // program abnormal. + if (epoll_ctl(_event_dispatcher_fd, EPOLL_CTL_DEL, fd, NULL) < 0) { + PLOG(WARNING) << "Fail to remove fd=" << fd << " from epfd=" << _event_dispatcher_fd; + return -1; + } + return 0; +} + +void* EventDispatcher::RunThis(void* arg) { + EventDispatcher* ed = (EventDispatcher*)arg; + if (ed->_priority_index >= 0) { + bthread::TaskMeta* meta = + bthread::TaskGroup::address_meta(bthread_self()); + meta->priority_index = ed->_priority_index; + } + ed->Run(); + return NULL; +} + +void EventDispatcher::Run() { + while (!_stop) { + epoll_event e[32]; +#ifdef BRPC_ADDITIONAL_EPOLL + // Performance downgrades in examples. + int n = epoll_wait(_event_dispatcher_fd, e, ARRAY_SIZE(e), 0); + if (n == 0) { + n = epoll_wait(_event_dispatcher_fd, e, ARRAY_SIZE(e), -1); + } +#else + const int n = epoll_wait(_event_dispatcher_fd, e, ARRAY_SIZE(e), -1); +#endif + if (_stop) { + // epoll_ctl/epoll_wait should have some sort of memory fencing + // guaranteeing that we(after epoll_wait) see _stop set before + // epoll_ctl. + break; + } + if (n < 0) { + if (EINTR == errno) { + // We've checked _stop, no wake-up will be missed. + continue; + } + PLOG(FATAL) << "Fail to epoll_wait epfd=" << _event_dispatcher_fd; + break; + } + for (int i = 0; i < n; ++i) { + if (e[i].events & (EPOLLIN | EPOLLERR | EPOLLHUP) +#ifdef BRPC_SOCKET_HAS_EOF + || (e[i].events & has_epollrdhup) +#endif + ) { + int64_t start_ns = butil::cpuwide_time_ns(); + // We don't care about the return value. + CallInputEventCallback(e[i].data.u64, e[i].events, _thread_attr); + (*g_edisp_read_lantency) << (butil::cpuwide_time_ns() - start_ns); + } + } + for (int i = 0; i < n; ++i) { + if (e[i].events & (EPOLLOUT | EPOLLERR | EPOLLHUP)) { + int64_t start_ns = butil::cpuwide_time_ns(); + // We don't care about the return value. + CallOutputEventCallback(e[i].data.u64, e[i].events, _thread_attr); + (*g_edisp_write_lantency) << (butil::cpuwide_time_ns() - start_ns); + } + } + } +} + +} // namespace brpc diff --git a/src/brpc/event_dispatcher_kqueue.cpp b/src/brpc/event_dispatcher_kqueue.cpp new file mode 100644 index 0000000..f73e620 --- /dev/null +++ b/src/brpc/event_dispatcher_kqueue.cpp @@ -0,0 +1,232 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include + +namespace brpc { + +EventDispatcher::EventDispatcher() + : _event_dispatcher_fd(-1) + , _stop(false) + , _tid(0) + , _thread_attr(BTHREAD_ATTR_NORMAL) { + _event_dispatcher_fd = kqueue(); + if (_event_dispatcher_fd < 0) { + PLOG(FATAL) << "Fail to create kqueue"; + return; + } + CHECK_EQ(0, butil::make_close_on_exec(_event_dispatcher_fd)); + + _wakeup_fds[0] = -1; + _wakeup_fds[1] = -1; + if (pipe(_wakeup_fds) != 0) { + PLOG(FATAL) << "Fail to create pipe"; + return; + } +} + +EventDispatcher::~EventDispatcher() { + Stop(); + Join(); + if (_event_dispatcher_fd >= 0) { + close(_event_dispatcher_fd); + _event_dispatcher_fd = -1; + } + if (_wakeup_fds[0] > 0) { + close(_wakeup_fds[0]); + close(_wakeup_fds[1]); + } +} + +int EventDispatcher::Start(const bthread_attr_t* thread_attr) { + if (_event_dispatcher_fd < 0) { + LOG(FATAL) << "kqueue was not created"; + return -1; + } + + if (_tid != 0) { + LOG(FATAL) << "Already started this dispatcher(" << this + << ") in bthread=" << _tid; + return -1; + } + + // Set _thread_attr before creating kqueue thread to make sure + // everyting seems sane to the thread. + if (thread_attr) { + _thread_attr = *thread_attr; + } + + //_thread_attr is used in StartInputEvent(), assign flag NEVER_QUIT to it will cause new bthread + // that created by kevent() never to quit. + // Only event dispatcher thread has flag BTHREAD_GLOBAL_PRIORITY. + bthread_attr_t kqueue_thread_attr = + _thread_attr | BTHREAD_NEVER_QUIT | BTHREAD_GLOBAL_PRIORITY; + bthread_attr_set_name(&kqueue_thread_attr, "EventDispatcher::RunThis"); + + // Polling thread uses the same attr for consumer threads (NORMAL right + // now). Previously, we used small stack (32KB) which may be overflowed + // when the older comlog (e.g. 3.1.85) calls com_openlog_r(). Since this + // is also a potential issue for consumer threads, using the same attr + // should be a reasonable solution. + int rc = bthread_start_background( + &_tid, &kqueue_thread_attr, RunThis, this); + if (rc) { + LOG(FATAL) << "Fail to create kqueue thread: " << berror(rc); + return -1; + } + return 0; +} + +bool EventDispatcher::Running() const { + return !_stop && _event_dispatcher_fd >= 0 && _tid != 0; +} + +void EventDispatcher::Stop() { + _stop = true; + + if (_event_dispatcher_fd >= 0) { + struct kevent kqueue_event; + EV_SET(&kqueue_event, _wakeup_fds[1], EVFILT_WRITE, EV_ADD | EV_ENABLE, + 0, 0, NULL); + kevent(_event_dispatcher_fd, &kqueue_event, 1, NULL, 0, NULL); + } +} + +void EventDispatcher::Join() { + if (_tid) { + bthread_join(_tid, NULL); + _tid = 0; + } +} + +int EventDispatcher::RegisterEvent(IOEventDataId event_data_id, + int fd, bool pollin) { + if (_event_dispatcher_fd < 0) { + errno = EINVAL; + return -1; + } + + struct kevent evt; + //TODO(zhujiashun): add EV_EOF + EV_SET(&evt, fd, EVFILT_WRITE, EV_ADD | EV_ENABLE | EV_CLEAR, + 0, 0, (void*)event_data_id); + if (kevent(_event_dispatcher_fd, &evt, 1, NULL, 0, NULL) < 0) { + return -1; + } + if (pollin) { + EV_SET(&evt, fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR, + 0, 0, (void*)event_data_id); + if (kevent(_event_dispatcher_fd, &evt, 1, NULL, 0, NULL) < 0) { + return -1; + } + } + return 0; +} + +int EventDispatcher::UnregisterEvent(IOEventDataId event_data_id, + int fd, bool pollin) { + struct kevent evt; + EV_SET(&evt, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); + if (kevent(_event_dispatcher_fd, &evt, 1, NULL, 0, NULL) < 0) { + return -1; + } + if (pollin) { + EV_SET(&evt, fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR, + 0, 0, (void*)event_data_id); + return kevent(_event_dispatcher_fd, &evt, 1, NULL, 0, NULL); + } + return 0; +} + +int EventDispatcher::AddConsumer(IOEventDataId event_data_id, int fd) { + if (_event_dispatcher_fd < 0) { + errno = EINVAL; + return -1; + } + struct kevent evt; + EV_SET(&evt, fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR, + 0, 0, (void*)event_data_id); + return kevent(_event_dispatcher_fd, &evt, 1, NULL, 0, NULL); +} + +int EventDispatcher::RemoveConsumer(int fd) { + if (fd < 0) { + return -1; + } + // Removing the consumer from dispatcher before closing the fd because + // if process was forked and the fd is not marked as close-on-exec, + // closing does not set reference count of the fd to 0, thus does not + // remove the fd from kqueue More badly, the fd will not be removable + // from kqueue again! If the fd was level-triggered and there's data left, + // kevent will keep returning events of the fd continuously, making + // program abnormal. + struct kevent evt; + EV_SET(&evt, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); + kevent(_event_dispatcher_fd, &evt, 1, NULL, 0, NULL); + EV_SET(&evt, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); + kevent(_event_dispatcher_fd, &evt, 1, NULL, 0, NULL); + return 0; +} + +void* EventDispatcher::RunThis(void* arg) { + ((EventDispatcher*)arg)->Run(); + return NULL; +} + +void EventDispatcher::Run() { + while (!_stop) { + struct kevent e[32]; + int n = kevent(_event_dispatcher_fd, NULL, 0, e, ARRAY_SIZE(e), NULL); + if (_stop) { + // EV_SET/kevent should have some sort of memory fencing + // guaranteeing that we(after kevent) see _stop set before + // EV_SET + break; + } + if (n < 0) { + if (EINTR == errno) { + // We've checked _stop, no wake-up will be missed. + continue; + } + PLOG(FATAL) << "Fail to kqueue epfd=" << _event_dispatcher_fd; + break; + } + for (int i = 0; i < n; ++i) { + if ((e[i].flags & EV_ERROR) || e[i].filter == EVFILT_READ) { + int64_t start_ns = butil::cpuwide_time_ns(); + // We don't care about the return value. + CallInputEventCallback((IOEventDataId)e[i].udata, + e[i].filter, _thread_attr); + (*g_edisp_read_lantency) << (butil::cpuwide_time_ns() - start_ns); + } + } + for (int i = 0; i < n; ++i) { + if ((e[i].flags & EV_ERROR) || e[i].filter == EVFILT_WRITE) { + int64_t start_ns = butil::cpuwide_time_ns(); + // We don't care about the return value. + CallOutputEventCallback((IOEventDataId)e[i].udata, + e[i].filter, _thread_attr); + (*g_edisp_write_lantency) << (butil::cpuwide_time_ns() - start_ns); + } + } + } +} + +} // namespace brpc diff --git a/src/brpc/excluded_servers.h b/src/brpc/excluded_servers.h new file mode 100644 index 0000000..ad095f4 --- /dev/null +++ b/src/brpc/excluded_servers.h @@ -0,0 +1,102 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_EXCLUDED_SERVERS_H +#define BRPC_EXCLUDED_SERVERS_H + +#include "butil/scoped_lock.h" +#include "butil/containers/bounded_queue.h" +#include "brpc/socket_id.h" // SocketId + + +namespace brpc { + +// Remember servers that should be avoided in selection. These servers +// are often selected in previous tries inside a RPC. +class ExcludedServers { +public: + // Create a instance with at most `cap' servers. + static ExcludedServers* Create(int cap); + + // Destroy the instance + static void Destroy(ExcludedServers* ptr); + + // Add a server. If the internal queue is full, pop one from the queue first. + void Add(SocketId id); + + // True if the server shall be excluded. + bool IsExcluded(SocketId id) const; + static bool IsExcluded(const ExcludedServers* s, SocketId id) { + return s != NULL && s->IsExcluded(id); + } + + // #servers inside. + size_t size() const { return _l.size(); } + +private: + ExcludedServers(int cap) + : _l(_space, sizeof(SocketId)* cap, butil::NOT_OWN_STORAGE) {} + ~ExcludedServers() {} + // Controller::_accessed may be shared by sub channels in schan, protect + // all mutable methods with this mutex. In ordinary channels, this mutex + // is never contended. + mutable butil::Mutex _mutex; + butil::BoundedQueue _l; + SocketId _space[0]; +}; + +// =================================================== + +inline ExcludedServers* ExcludedServers::Create(int cap) { + void *space = malloc( + offsetof(ExcludedServers, _space) + sizeof(SocketId) * cap); + if (NULL == space) { + return NULL; + } + return new (space) ExcludedServers(cap); +} + +inline void ExcludedServers::Destroy(ExcludedServers* ptr) { + if (ptr) { + ptr->~ExcludedServers(); + free(ptr); + } +} + +inline void ExcludedServers::Add(SocketId id) { + BAIDU_SCOPED_LOCK(_mutex); + const SocketId* last_id = _l.bottom(); + if (last_id == NULL || *last_id != id) { + _l.elim_push(id); + } +} + +inline bool ExcludedServers::IsExcluded(SocketId id) const { + BAIDU_SCOPED_LOCK(_mutex); + for (size_t i = 0; i < _l.size(); ++i) { + if (*_l.bottom(i) == id) { + return true; + } + } + return false; +} + +} // namespace brpc + + +#endif // BRPC_EXCLUDED_SERVERS_H diff --git a/src/brpc/extension.h b/src/brpc/extension.h new file mode 100644 index 0000000..7854362 --- /dev/null +++ b/src/brpc/extension.h @@ -0,0 +1,62 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_EXTENSION_H +#define BRPC_EXTENSION_H + +#include +#include "butil/scoped_lock.h" +#include "butil/logging.h" +#include "butil/containers/case_ignored_flat_map.h" +#include "butil/memory/singleton_on_pthread_once.h" + +namespace butil { +template class GetLeakySingleton; +} + + +namespace brpc { + +// A global map from string to user-extended instances (typed T). +// It's used by NamingService and LoadBalancer to maintain globally +// available instances. +// All names are case-insensitive. Names are printed in lowercases. + +template +class Extension { +public: + static Extension* instance(); + + int Register(const std::string& name, T* instance); + int RegisterOrDie(const std::string& name, T* instance); + T* Find(const char* name); + void List(std::ostream& os, char separator); + +private: +template friend U* butil::create_leaky_singleton_obj(); + Extension() = default; + butil::CaseIgnoredFlatMap _instance_map; + butil::Mutex _map_mutex; +}; + +} // namespace brpc + + +#include "brpc/extension_inl.h" + +#endif // BRPC_EXTENSION_H diff --git a/src/brpc/extension_inl.h b/src/brpc/extension_inl.h new file mode 100644 index 0000000..8a081c7 --- /dev/null +++ b/src/brpc/extension_inl.h @@ -0,0 +1,87 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_EXTENSION_INL_H +#define BRPC_EXTENSION_INL_H + + +namespace brpc { + +template +Extension* Extension::instance() { + // NOTE: We don't delete extensions because in principle they can be + // accessed during exiting, e.g. create a channel to send rpc at exit. + return butil::get_leaky_singleton >(); +} + +template +int Extension::Register(const std::string& name, T* instance) { + if (NULL == instance) { + LOG(ERROR) << "instance to \"" << name << "\" is NULL"; + return -1; + } + BAIDU_SCOPED_LOCK(_map_mutex); + if (_instance_map.seek(name) != NULL) { + LOG(ERROR) << "\"" << name << "\" was registered"; + return -1; + } + _instance_map[name] = instance; + return 0; +} + +template +int Extension::RegisterOrDie(const std::string& name, T* instance) { + if (Register(name, instance) == 0) { + return 0; + } + exit(1); +} + +template +T* Extension::Find(const char* name) { + if (NULL == name) { + return NULL; + } + BAIDU_SCOPED_LOCK(_map_mutex); + T** p = _instance_map.seek(name); + if (p) { + return *p; + } + return NULL; +} + +template +void Extension::List(std::ostream& os, char separator) { + BAIDU_SCOPED_LOCK(_map_mutex); + for (typename butil::CaseIgnoredFlatMap::iterator + it = _instance_map.begin(); it != _instance_map.end(); ++it) { + // private extensions which is not intended to be seen by users starts + // with underscore. + if (it->first.data()[0] != '_') { + if (it != _instance_map.begin()) { + os << separator; + } + os << it->first; + } + } +} + +} // namespace brpc + + +#endif // BRPC_EXTENSION_INL_H diff --git a/src/brpc/get_favicon.proto b/src/brpc/get_favicon.proto new file mode 100644 index 0000000..8ec06c0 --- /dev/null +++ b/src/brpc/get_favicon.proto @@ -0,0 +1,32 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package brpc; + +option cc_generic_services = true; +option java_generic_services = true; +option java_package = "com.brpc"; +option java_outer_classname = "GetFavicon"; + +message GetFaviconRequest {} +message GetFaviconResponse {} + +// Hack for accessing /favicon.ico +service ico { + rpc default_method(GetFaviconRequest) returns (GetFaviconResponse); +} diff --git a/src/brpc/get_js.proto b/src/brpc/get_js.proto new file mode 100644 index 0000000..d60cac0 --- /dev/null +++ b/src/brpc/get_js.proto @@ -0,0 +1,31 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package brpc; + +option cc_generic_services = true; + +message GetJsRequest {} +message GetJsResponse {} + +service js { + rpc sorttable(GetJsRequest) returns (GetJsResponse); + rpc jquery_min(GetJsRequest) returns (GetJsResponse); + rpc flot_min(GetJsRequest) returns (GetJsResponse); + rpc viz_min(GetJsRequest) returns (GetJsResponse); +} diff --git a/src/brpc/global.cpp b/src/brpc/global.cpp new file mode 100644 index 0000000..e940251 --- /dev/null +++ b/src/brpc/global.cpp @@ -0,0 +1,700 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef USE_MESALINK +#include +#include +#else +#include +#endif + +#include +#include // O_RDONLY +#include + +#include "butil/build_config.h" // OS_LINUX +#include "butil/debug/leak_annotations.h" +// Naming services +#ifdef BAIDU_INTERNAL +#include "brpc/policy/baidu_naming_service.h" +#endif +#include "brpc/policy/file_naming_service.h" +#include "brpc/policy/list_naming_service.h" +#include "brpc/policy/domain_naming_service.h" +#include "brpc/policy/remote_file_naming_service.h" +#include "brpc/policy/consul_naming_service.h" +#include "brpc/policy/discovery_naming_service.h" +#include "brpc/policy/nacos_naming_service.h" + +// Load Balancers +#include "brpc/policy/round_robin_load_balancer.h" +#include "brpc/policy/weighted_round_robin_load_balancer.h" +#include "brpc/policy/randomized_load_balancer.h" +#include "brpc/policy/weighted_randomized_load_balancer.h" +#include "brpc/policy/locality_aware_load_balancer.h" +#include "brpc/policy/p2c_ewma_load_balancer.h" +#include "brpc/policy/consistent_hashing_load_balancer.h" +#include "brpc/policy/hasher.h" +#include "brpc/policy/dynpart_load_balancer.h" + + +// Span +#include "brpc/span.h" +#include "bthread/unstable.h" +#include "bthread/bthread.h" + +// Compress handlers +#include "brpc/compress.h" +#include "brpc/policy/gzip_compress.h" +#include "brpc/policy/snappy_compress.h" + +// Checksum handlers +#include "brpc/checksum.h" +#include "brpc/policy/crc32c_checksum.h" + +// Protocols +#include "brpc/protocol.h" +#include "brpc/policy/baidu_rpc_protocol.h" +#include "brpc/policy/http_rpc_protocol.h" +#include "brpc/policy/http2_rpc_protocol.h" +#include "brpc/policy/hulu_pbrpc_protocol.h" +#include "brpc/policy/nova_pbrpc_protocol.h" +#include "brpc/policy/public_pbrpc_protocol.h" +#include "brpc/policy/ubrpc2pb_protocol.h" +#include "brpc/policy/sofa_pbrpc_protocol.h" +#include "brpc/policy/memcache_binary_protocol.h" +#include "brpc/policy/couchbase_protocol.h" +#include "brpc/policy/streaming_rpc_protocol.h" +#include "brpc/policy/mongo_protocol.h" +#include "brpc/policy/redis_protocol.h" +#include "brpc/policy/nshead_mcpack_protocol.h" +#include "brpc/policy/rtmp_protocol.h" +#include "brpc/policy/esp_protocol.h" +#include "brpc/policy/mysql/mysql_protocol.h" +#ifdef ENABLE_THRIFT_FRAMED_PROTOCOL +# include "brpc/policy/thrift_protocol.h" +#endif + +// Concurrency Limiters +#include "brpc/concurrency_limiter.h" +#include "brpc/policy/auto_concurrency_limiter.h" +#include "brpc/policy/constant_concurrency_limiter.h" +#include "brpc/policy/timeout_concurrency_limiter.h" + +#include "brpc/input_messenger.h" // get_or_new_client_side_messenger +#include "brpc/socket_map.h" // SocketMapList +#include "brpc/server.h" +#include "brpc/trackme.h" // TrackMe +#include "brpc/details/usercode_backup_pool.h" +#if defined(OS_LINUX) +#include // malloc_trim +#endif +#include "butil/fd_guard.h" +#include "butil/files/file_watcher.h" + +extern "C" { +// defined in gperftools/malloc_extension_c.h +void BAIDU_WEAK MallocExtension_ReleaseFreeMemory(void); +} + +namespace brpc { + +DECLARE_bool(usercode_in_pthread); + +DEFINE_int32(free_memory_to_system_interval, 0, + "Try to return free memory to system every so many seconds, " + "values <= 0 disables this feature"); +BRPC_VALIDATE_GFLAG(free_memory_to_system_interval, PassValidate); + +namespace policy { +// Defined in http_rpc_protocol.cpp +void InitCommonStrings(); +} + +using namespace policy; + +const char* const DUMMY_SERVER_PORT_FILE = "dummy_server.port"; + +struct GlobalExtensions { + GlobalExtensions() + : dns(80) + , dns_with_ssl(443) + , ch_mh_lb(CONS_HASH_LB_MURMUR3) + , ch_md5_lb(CONS_HASH_LB_MD5) + , ch_ketama_lb(CONS_HASH_LB_KETAMA) + , constant_cl(0) { + } + +#ifdef BAIDU_INTERNAL + BaiduNamingService bns; +#endif + FileNamingService fns; + ListNamingService lns; + DomainListNamingService dlns; + DomainNamingService dns; + DomainNamingService dns_with_ssl; + RemoteFileNamingService rfns; + ConsulNamingService cns; + DiscoveryNamingService dcns; + NacosNamingService nns; + + RoundRobinLoadBalancer rr_lb; + WeightedRoundRobinLoadBalancer wrr_lb; + RandomizedLoadBalancer randomized_lb; + WeightedRandomizedLoadBalancer wr_lb; + LocalityAwareLoadBalancer la_lb; + P2CEwmaLoadBalancer p2c_ewma_lb; + ConsistentHashingLoadBalancer ch_mh_lb; + ConsistentHashingLoadBalancer ch_md5_lb; + ConsistentHashingLoadBalancer ch_ketama_lb; + DynPartLoadBalancer dynpart_lb; + + AutoConcurrencyLimiter auto_cl; + ConstantConcurrencyLimiter constant_cl; + TimeoutConcurrencyLimiter timeout_cl; +}; + +static pthread_once_t register_extensions_once = PTHREAD_ONCE_INIT; +static GlobalExtensions* g_ext = NULL; + +static long ReadPortOfDummyServer(const char* filename) { + butil::fd_guard fd(open(filename, O_RDONLY)); + if (fd < 0) { + LOG(ERROR) << "Fail to open `" << DUMMY_SERVER_PORT_FILE << "'"; + return -1; + } + char port_str[32]; + const ssize_t nr = read(fd, port_str, sizeof(port_str)); + if (nr <= 0) { + LOG(ERROR) << "Fail to read `" << DUMMY_SERVER_PORT_FILE << "': " + << (nr == 0 ? "nothing to read" : berror()); + return -1; + } + port_str[std::min((size_t)nr, sizeof(port_str)-1)] = '\0'; + const char* p = port_str; + for (; isspace(*p); ++p) {} + char* endptr = NULL; + const long port = strtol(p, &endptr, 10); + for (; isspace(*endptr); ++endptr) {} + if (*endptr != '\0') { + LOG(ERROR) << "Invalid port=`" << port_str << "'"; + return -1; + } + return port; +} + +// Expose counters of butil::IOBuf +static int64_t GetIOBufBlockCount(void*) { + return butil::IOBuf::block_count(); +} +static int64_t GetIOBufBlockCountHitTLSThreshold(void*) { + return butil::IOBuf::block_count_hit_tls_threshold(); +} +static int64_t GetIOBufNewBigViewCount(void*) { + return butil::IOBuf::new_bigview_count(); +} +static int64_t GetIOBufBlockMemory(void*) { + return butil::IOBuf::block_memory(); +} + +// Defined in server.cpp +extern butil::static_atomic g_running_server_count; +static int GetRunningServerCount(void*) { + return g_running_server_count.load(butil::memory_order_relaxed); +} + +// Update global stuff periodically. +static void* GlobalUpdate(void*) { + // This bthread runs for the whole process lifetime and never returns, so + // the local objects below live until the process exits and their + // destructors never run. They are reachable from this bthread's stack, so + // the objects themselves are not reported as leaks, but the heap buffers + // they allocate while exposing themselves (variable names, watched path) + // would be. Disable leak detection only around their construction and + // re-enable it right after. + ANNOTATE_MEMORY_LEAK_DISABLE(); + // Expose variables. + bvar::PassiveStatus var_iobuf_block_count( + "iobuf_block_count", GetIOBufBlockCount, NULL); + bvar::PassiveStatus var_iobuf_block_count_hit_tls_threshold( + "iobuf_block_count_hit_tls_threshold", + GetIOBufBlockCountHitTLSThreshold, NULL); + bvar::PassiveStatus var_iobuf_new_bigview_count( + GetIOBufNewBigViewCount, NULL); + bvar::PerSecond > var_iobuf_new_bigview_second( + "iobuf_newbigview_second", &var_iobuf_new_bigview_count); + bvar::PassiveStatus var_iobuf_block_memory( + "iobuf_block_memory", GetIOBufBlockMemory, NULL); + bvar::PassiveStatus var_running_server_count( + "rpc_server_count", GetRunningServerCount, NULL); + + butil::FileWatcher fw; + const int fw_rc = fw.init_from_not_exist(DUMMY_SERVER_PORT_FILE); + ANNOTATE_MEMORY_LEAK_ENABLE(); + if (fw_rc < 0) { + LOG(FATAL) << "Fail to init FileWatcher on `" << DUMMY_SERVER_PORT_FILE << "'"; + return NULL; + } + + std::vector conns; + const int64_t start_time_us = butil::cpuwide_time_us(); + const int WARN_NOSLEEP_THRESHOLD = 2; + int64_t last_time_us = start_time_us; + int consecutive_nosleep = 0; + int64_t last_return_free_memory_time = start_time_us; + while (1) { + const int64_t sleep_us = 1000000L + last_time_us - butil::cpuwide_time_us(); + if (sleep_us > 0) { + if (bthread_usleep(sleep_us) < 0) { + PLOG_IF(FATAL, errno != ESTOP) << "Fail to sleep"; + break; + } + consecutive_nosleep = 0; + } else { + if (++consecutive_nosleep >= WARN_NOSLEEP_THRESHOLD) { + consecutive_nosleep = 0; + LOG(WARNING) << __FUNCTION__ << " is too busy!"; + } + } + last_time_us = butil::cpuwide_time_us(); + + TrackMe(); + + if (!IsDummyServerRunning() + && g_running_server_count.load(butil::memory_order_relaxed) == 0 + && fw.check_and_consume() > 0) { + long port = ReadPortOfDummyServer(DUMMY_SERVER_PORT_FILE); + if (port >= 0) { + StartDummyServerAt(port); + } + } + + { + // See detail above. + ANNOTATE_SCOPED_MEMORY_LEAK; + SocketMapList(&conns); + } + const int64_t now_ms = butil::cpuwide_time_ms(); + for (size_t i = 0; i < conns.size(); ++i) { + SocketUniquePtr ptr; + if (Socket::Address(conns[i], &ptr) == 0) { + ptr->UpdateStatsEverySecond(now_ms); + } + } + + const int return_mem_interval = + FLAGS_free_memory_to_system_interval/*reloadable*/; + if (return_mem_interval > 0 && + last_time_us >= last_return_free_memory_time + + return_mem_interval * 1000000L) { + last_return_free_memory_time = last_time_us; + // TODO: Calling MallocExtension::instance()->ReleaseFreeMemory may + // crash the program in later calls to malloc, verified on tcmalloc + // 1.7 and 2.5, which means making the static member function weak + // in details/tcmalloc_extension.cpp is probably not correct, however + // it does work for heap profilers. + if (MallocExtension_ReleaseFreeMemory != NULL) { + MallocExtension_ReleaseFreeMemory(); + } else { +#if defined(OS_LINUX) + // GNU specific. + malloc_trim(10 * 1024 * 1024/*leave 10M pad*/); +#endif + } + } + } + return NULL; +} + +#if GOOGLE_PROTOBUF_VERSION < 3022000 +static void BaiduStreamingLogHandler(google::protobuf::LogLevel level, + const char* filename, int line, + const std::string& message) { + switch (level) { + case google::protobuf::LOGLEVEL_INFO: + LOG(INFO) << filename << ':' << line << ' ' << message; + return; + case google::protobuf::LOGLEVEL_WARNING: + LOG(WARNING) << filename << ':' << line << ' ' << message; + return; + case google::protobuf::LOGLEVEL_ERROR: + LOG(ERROR) << filename << ':' << line << ' ' << message; + return; + case google::protobuf::LOGLEVEL_FATAL: + LOG(FATAL) << filename << ':' << line << ' ' << message; + return; + } + CHECK(false) << filename << ':' << line << ' ' << message; +} +#endif + +static void GlobalInitializeOrDieImpl() { + ////////////////////////////////////////////////////////////////// + // Be careful about usages of gflags inside this function which // + // may be called before main() only seeing gflags with default // + // values even if the gflags will be set after main(). // + ////////////////////////////////////////////////////////////////// + + // Ignore SIGPIPE. + struct sigaction oldact; + if (sigaction(SIGPIPE, NULL, &oldact) != 0 || + (oldact.sa_handler == NULL && oldact.sa_sigaction == NULL)) { + CHECK(SIG_ERR != signal(SIGPIPE, SIG_IGN)); + } + +#if GOOGLE_PROTOBUF_VERSION < 3022000 + // Make GOOGLE_LOG print to comlog device + SetLogHandler(&BaiduStreamingLogHandler); +#endif + + if (bthread_set_span_funcs(CreateBthreadSpanAsVoid, + DestroyRpczParentSpan, + EndBthreadSpan) != 0) { + LOG(FATAL) << "Failed to register span callbacks to bthread"; + } + + // Setting the variable here does not work, the profiler probably check + // the variable before main() for only once. + // setenv("TCMALLOC_SAMPLE_PARAMETER", "524288", 0); + + // Initialize openssl library + SSL_library_init(); + // RPC doesn't require openssl.cnf, users can load it by themselves if needed + SSL_load_error_strings(); + if (SSLThreadInit() != 0 || SSLDHInit() != 0) { + exit(1); + } + + // Defined in http_rpc_protocol.cpp + InitCommonStrings(); + + // Leave memory of these extensions to process's clean up. + g_ext = new(std::nothrow) GlobalExtensions(); + if (NULL == g_ext) { + exit(1); + } + // Naming Services +#ifdef BAIDU_INTERNAL + NamingServiceExtension()->RegisterOrDie("bns", &g_ext->bns); +#endif + NamingServiceExtension()->RegisterOrDie("file", &g_ext->fns); + NamingServiceExtension()->RegisterOrDie("list", &g_ext->lns); + NamingServiceExtension()->RegisterOrDie("dlist", &g_ext->dlns); + NamingServiceExtension()->RegisterOrDie("http", &g_ext->dns); + NamingServiceExtension()->RegisterOrDie("https", &g_ext->dns_with_ssl); + NamingServiceExtension()->RegisterOrDie("redis", &g_ext->dns); + NamingServiceExtension()->RegisterOrDie("remotefile", &g_ext->rfns); + NamingServiceExtension()->RegisterOrDie("consul", &g_ext->cns); + NamingServiceExtension()->RegisterOrDie("discovery", &g_ext->dcns); + NamingServiceExtension()->RegisterOrDie("nacos", &g_ext->nns); + + // Load Balancers + LoadBalancerExtension()->RegisterOrDie("rr", &g_ext->rr_lb); + LoadBalancerExtension()->RegisterOrDie("wrr", &g_ext->wrr_lb); + LoadBalancerExtension()->RegisterOrDie("random", &g_ext->randomized_lb); + LoadBalancerExtension()->RegisterOrDie("wr", &g_ext->wr_lb); + LoadBalancerExtension()->RegisterOrDie("la", &g_ext->la_lb); + LoadBalancerExtension()->RegisterOrDie("p2c", &g_ext->p2c_ewma_lb); + LoadBalancerExtension()->RegisterOrDie("c_murmurhash", &g_ext->ch_mh_lb); + LoadBalancerExtension()->RegisterOrDie("c_md5", &g_ext->ch_md5_lb); + LoadBalancerExtension()->RegisterOrDie("c_ketama", &g_ext->ch_ketama_lb); + LoadBalancerExtension()->RegisterOrDie("_dynpart", &g_ext->dynpart_lb); + + // Compress Handlers + CompressHandler gzip_compress = { GzipCompress, GzipDecompress, "gzip" }; + if (RegisterCompressHandler(COMPRESS_TYPE_GZIP, gzip_compress) != 0) { + exit(1); + } + CompressHandler zlib_compress = { ZlibCompress, ZlibDecompress, "zlib" }; + if (RegisterCompressHandler(COMPRESS_TYPE_ZLIB, zlib_compress) != 0) { + exit(1); + } + CompressHandler snappy_compress = { SnappyCompress, SnappyDecompress, "snappy" }; + if (RegisterCompressHandler(COMPRESS_TYPE_SNAPPY, snappy_compress) != 0) { + exit(1); + } + + // Checksum Handlers + const ChecksumHandler crc32c_checksum = {Crc32cCompute, Crc32cVerify, + "crc32c"}; + if (RegisterChecksumHandler(CHECKSUM_TYPE_CRC32C, crc32c_checksum) != 0) { + exit(1); + } + + // Protocols + Protocol baidu_protocol = { ParseRpcMessage, + SerializeRpcRequest, PackRpcRequest, + ProcessRpcRequest, ProcessRpcResponse, + VerifyRpcRequest, NULL, NULL, + CONNECTION_TYPE_ALL, "baidu_std" }; + if (RegisterProtocol(PROTOCOL_BAIDU_STD, baidu_protocol) != 0) { + exit(1); + } + + Protocol streaming_protocol = { ParseStreamingMessage, + NULL, NULL, ProcessStreamingMessage, + ProcessStreamingMessage, + NULL, NULL, NULL, + CONNECTION_TYPE_SINGLE, "streaming_rpc" }; + + if (RegisterProtocol(PROTOCOL_STREAMING_RPC, streaming_protocol) != 0) { + exit(1); + } + + Protocol http_protocol = { ParseHttpMessage, + SerializeHttpRequest, PackHttpRequest, + ProcessHttpRequest, ProcessHttpResponse, + VerifyHttpRequest, ParseHttpServerAddress, + GetHttpMethodName, + CONNECTION_TYPE_POOLED_AND_SHORT, + "http" }; + if (RegisterProtocol(PROTOCOL_HTTP, http_protocol) != 0) { + exit(1); + } + + Protocol http2_protocol = { ParseH2Message, + SerializeHttpRequest, PackH2Request, + ProcessHttpRequest, ProcessHttpResponse, + VerifyHttpRequest, ParseHttpServerAddress, + GetHttpMethodName, + CONNECTION_TYPE_SINGLE, + "h2" }; + if (RegisterProtocol(PROTOCOL_H2, http2_protocol) != 0) { + exit(1); + } + + Protocol hulu_protocol = { ParseHuluMessage, + SerializeRequestDefault, PackHuluRequest, + ProcessHuluRequest, ProcessHuluResponse, + VerifyHuluRequest, NULL, NULL, + CONNECTION_TYPE_ALL, "hulu_pbrpc" }; + if (RegisterProtocol(PROTOCOL_HULU_PBRPC, hulu_protocol) != 0) { + exit(1); + } + + // Only valid at client side + Protocol nova_protocol = { ParseNsheadMessage, + SerializeNovaRequest, PackNovaRequest, + NULL, ProcessNovaResponse, + NULL, NULL, NULL, + CONNECTION_TYPE_POOLED_AND_SHORT, "nova_pbrpc" }; + if (RegisterProtocol(PROTOCOL_NOVA_PBRPC, nova_protocol) != 0) { + exit(1); + } + + // Only valid at client side + Protocol public_pbrpc_protocol = { ParseNsheadMessage, + SerializePublicPbrpcRequest, + PackPublicPbrpcRequest, + NULL, ProcessPublicPbrpcResponse, + NULL, NULL, NULL, + // public_pbrpc server implementation + // doesn't support full duplex + CONNECTION_TYPE_POOLED_AND_SHORT, + "public_pbrpc" }; + if (RegisterProtocol(PROTOCOL_PUBLIC_PBRPC, public_pbrpc_protocol) != 0) { + exit(1); + } + + Protocol sofa_protocol = { ParseSofaMessage, + SerializeRequestDefault, PackSofaRequest, + ProcessSofaRequest, ProcessSofaResponse, + VerifySofaRequest, NULL, NULL, + CONNECTION_TYPE_ALL, "sofa_pbrpc" }; + if (RegisterProtocol(PROTOCOL_SOFA_PBRPC, sofa_protocol) != 0) { + exit(1); + } + + // Only valid at server side. We generalize all the protocols that + // prefixes with nshead as `nshead_protocol' and specify the content + // parsing after nshead by ServerOptions.nshead_service. + Protocol nshead_protocol = { ParseNsheadMessage, + SerializeNsheadRequest, PackNsheadRequest, + ProcessNsheadRequest, ProcessNsheadResponse, + VerifyNsheadRequest, NULL, NULL, + CONNECTION_TYPE_POOLED_AND_SHORT, "nshead" }; + if (RegisterProtocol(PROTOCOL_NSHEAD, nshead_protocol) != 0) { + exit(1); + } + + Protocol mc_binary_protocol = { ParseMemcacheMessage, + SerializeMemcacheRequest, + PackMemcacheRequest, + NULL, ProcessMemcacheResponse, + NULL, NULL, GetMemcacheMethodName, + CONNECTION_TYPE_ALL, "memcache" }; + if (RegisterProtocol(PROTOCOL_MEMCACHE, mc_binary_protocol) != 0) { + exit(1); + } + + Protocol couchbase_protocol = { ParseCouchbaseMessage, + SerializeCouchbaseRequest, + PackCouchbaseRequest, + NULL, ProcessCouchbaseResponse, + NULL, NULL, GetCouchbaseMethodName, + CONNECTION_TYPE_ALL, "couchbase" }; + if (RegisterProtocol(PROTOCOL_COUCHBASE, couchbase_protocol) != 0) { + exit(1); + } + + Protocol redis_protocol = { ParseRedisMessage, + SerializeRedisRequest, + PackRedisRequest, + ProcessRedisRequest, ProcessRedisResponse, + NULL, NULL, GetRedisMethodName, + CONNECTION_TYPE_ALL, "redis" }; + if (RegisterProtocol(PROTOCOL_REDIS, redis_protocol) != 0) { + exit(1); + } + + Protocol mongo_protocol = { ParseMongoMessage, + NULL, NULL, + ProcessMongoRequest, NULL, + NULL, NULL, NULL, + CONNECTION_TYPE_POOLED, "mongo" }; + if (RegisterProtocol(PROTOCOL_MONGO, mongo_protocol) != 0) { + exit(1); + } + +// Use Macro is more straight forward than weak link technology(becasue of static link issue) +#ifdef ENABLE_THRIFT_FRAMED_PROTOCOL + Protocol thrift_binary_protocol = { + policy::ParseThriftMessage, + policy::SerializeThriftRequest, policy::PackThriftRequest, + policy::ProcessThriftRequest, policy::ProcessThriftResponse, + policy::VerifyThriftRequest, NULL, NULL, + CONNECTION_TYPE_POOLED_AND_SHORT, "thrift" }; + if (RegisterProtocol(PROTOCOL_THRIFT, thrift_binary_protocol) != 0) { + exit(1); + } +#endif + + // Only valid at client side + Protocol ubrpc_compack_protocol = { + ParseNsheadMessage, + SerializeUbrpcCompackRequest, PackUbrpcRequest, + NULL, ProcessUbrpcResponse, + NULL, NULL, NULL, + CONNECTION_TYPE_POOLED_AND_SHORT, "ubrpc_compack" }; + if (RegisterProtocol(PROTOCOL_UBRPC_COMPACK, ubrpc_compack_protocol) != 0) { + exit(1); + } + Protocol ubrpc_mcpack2_protocol = { + ParseNsheadMessage, + SerializeUbrpcMcpack2Request, PackUbrpcRequest, + NULL, ProcessUbrpcResponse, + NULL, NULL, NULL, + CONNECTION_TYPE_POOLED_AND_SHORT, "ubrpc_mcpack2" }; + if (RegisterProtocol(PROTOCOL_UBRPC_MCPACK2, ubrpc_mcpack2_protocol) != 0) { + exit(1); + } + + // Only valid at client side + Protocol nshead_mcpack_protocol = { + ParseNsheadMessage, + SerializeNsheadMcpackRequest, PackNsheadMcpackRequest, + NULL, ProcessNsheadMcpackResponse, + NULL, NULL, NULL, + CONNECTION_TYPE_POOLED_AND_SHORT, "nshead_mcpack" }; + if (RegisterProtocol(PROTOCOL_NSHEAD_MCPACK, nshead_mcpack_protocol) != 0) { + exit(1); + } + + Protocol rtmp_protocol = { + ParseRtmpMessage, + SerializeRtmpRequest, PackRtmpRequest, + ProcessRtmpMessage, ProcessRtmpMessage, + NULL, NULL, NULL, + (ConnectionType)(CONNECTION_TYPE_SINGLE|CONNECTION_TYPE_SHORT), + "rtmp" }; + if (RegisterProtocol(PROTOCOL_RTMP, rtmp_protocol) != 0) { + exit(1); + } + + Protocol esp_protocol = { + ParseEspMessage, + SerializeEspRequest, PackEspRequest, + NULL, ProcessEspResponse, + NULL, NULL, NULL, + CONNECTION_TYPE_POOLED_AND_SHORT, "esp"}; + if (RegisterProtocol(PROTOCOL_ESP, esp_protocol) != 0) { + exit(1); + } + + Protocol mysql_protocol = {ParseMysqlMessage, + SerializeMysqlRequest, + PackMysqlRequest, + NULL, + ProcessMysqlResponse, + NULL, + NULL, + GetMysqlMethodName, + CONNECTION_TYPE_POOLED_AND_SHORT, + "mysql"}; + if (RegisterProtocol(PROTOCOL_MYSQL, mysql_protocol) != 0) { + exit(1); + } + + std::vector protocols; + ListProtocols(&protocols); + for (size_t i = 0; i < protocols.size(); ++i) { + if (protocols[i].process_response) { + InputMessageHandler handler; + // `process_response' is required at client side + handler.parse = protocols[i].parse; + handler.process = protocols[i].process_response; + // No need to verify at client side + handler.verify = NULL; + handler.arg = NULL; + handler.name = protocols[i].name; + if (get_or_new_client_side_messenger()->AddHandler(handler) != 0) { + exit(1); + } + } + } + + // Concurrency Limiters + ConcurrencyLimiterExtension()->RegisterOrDie("auto", &g_ext->auto_cl); + ConcurrencyLimiterExtension()->RegisterOrDie("constant", &g_ext->constant_cl); + ConcurrencyLimiterExtension()->RegisterOrDie("timeout", &g_ext->timeout_cl); + + if (FLAGS_usercode_in_pthread) { + // Optional. If channel/server are initialized before main(), this + // flag may be false at here even if it will be set to true after + // main(). In which case, the usercode pool will not be initialized + // until the pool is used. + InitUserCodeBackupPoolOnceOrDie(); + } + + // We never join GlobalUpdate, let it quit with the process. + bthread_t th; + bthread_attr_t attr = BTHREAD_ATTR_NORMAL; + bthread_attr_set_name(&attr, "GlobalUpdate"); + CHECK(bthread_start_background(&th, &attr, GlobalUpdate, NULL) == 0) + << "Fail to start GlobalUpdate"; +} + +void GlobalInitializeOrDie() { + if (pthread_once(®ister_extensions_once, + GlobalInitializeOrDieImpl) != 0) { + LOG(FATAL) << "Fail to pthread_once"; + exit(1); + } +} + +} // namespace brpc diff --git a/src/brpc/global.h b/src/brpc/global.h new file mode 100644 index 0000000..06f1d69 --- /dev/null +++ b/src/brpc/global.h @@ -0,0 +1,32 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_GLOBAL_H +#define BRPC_GLOBAL_H + + +namespace brpc { + +// Register all naming service, load balancers, compress handlers inside +// `brpc/policy/' directory +void GlobalInitializeOrDie(); + +} // namespace brpc + + +#endif // BRPC_GLOBAL_H diff --git a/src/brpc/grpc.cpp b/src/brpc/grpc.cpp new file mode 100644 index 0000000..87f98c0 --- /dev/null +++ b/src/brpc/grpc.cpp @@ -0,0 +1,209 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // int64_t +#include // std::stringstream +#include // std::setw +#include "brpc/grpc.h" +#include "brpc/errno.pb.h" +#include "brpc/http_status_code.h" +#include "butil/logging.h" + +namespace brpc { + +const char* GrpcStatusToString(GrpcStatus s) { + switch (s) { + case GRPC_OK: return "GRPC_OK"; + case GRPC_CANCELED: return "GRPC_CANCELED"; + case GRPC_UNKNOWN: return "GRPC_UNKNOWN"; + case GRPC_INVALIDARGUMENT: return "GRPC_INVALIDARGUMENT"; + case GRPC_DEADLINEEXCEEDED: return "GRPC_DEADLINEEXCEEDED"; + case GRPC_NOTFOUND: return "GRPC_NOTFOUND"; + case GRPC_ALREADYEXISTS: return "GRPC_ALREADYEXISTS"; + case GRPC_PERMISSIONDENIED: return "GRPC_PERMISSIONDENIED"; + case GRPC_RESOURCEEXHAUSTED: return "GRPC_RESOURCEEXHAUSTED"; + case GRPC_FAILEDPRECONDITION: return "GRPC_FAILEDPRECONDITION"; + case GRPC_ABORTED: return "GRPC_ABORTED"; + case GRPC_OUTOFRANGE: return "GRPC_OUTOFRANGE"; + case GRPC_UNIMPLEMENTED: return "GRPC_UNIMPLEMENTED"; + case GRPC_INTERNAL: return "GRPC_INTERNAL"; + case GRPC_UNAVAILABLE: return "GRPC_UNAVAILABLE"; + case GRPC_DATALOSS: return "GRPC_DATALOSS"; + case GRPC_UNAUTHENTICATED: return "GRPC_UNAUTHENTICATED"; + case GRPC_MAX: return "GRPC_MAX"; + } + return "Unknown-GrpcStatus"; +} + +GrpcStatus ErrorCodeToGrpcStatus(int error_code) { + switch (error_code) { + case 0: + return GRPC_OK; + case ENOSERVICE: + case ENOMETHOD: + return GRPC_UNIMPLEMENTED; + case ERPCAUTH: + return GRPC_UNAUTHENTICATED; + case EREQUEST: + case EINVAL: + return GRPC_INVALIDARGUMENT; + case ELIMIT: + return GRPC_RESOURCEEXHAUSTED; + case ELOGOFF: + return GRPC_UNAVAILABLE; + case EPERM: + return GRPC_PERMISSIONDENIED; + case ERPCTIMEDOUT: + return GRPC_DEADLINEEXCEEDED; + case ETIMEDOUT: + return GRPC_INTERNAL; + case ECANCELED: + return GRPC_CANCELED; + default: + return GRPC_INTERNAL; + } +} + +int GrpcStatusToErrorCode(GrpcStatus grpc_status) { + switch (grpc_status) { + case GRPC_OK: + return 0; + case GRPC_CANCELED: + return ECANCELED; + case GRPC_UNKNOWN: + return EINTERNAL; + case GRPC_INVALIDARGUMENT: + return EINVAL; + case GRPC_DEADLINEEXCEEDED: + return ERPCTIMEDOUT; + case GRPC_NOTFOUND: + return EINTERNAL; + case GRPC_ALREADYEXISTS: + return EEXIST; + case GRPC_PERMISSIONDENIED: + return EPERM; + case GRPC_RESOURCEEXHAUSTED: + return ELIMIT; + case GRPC_FAILEDPRECONDITION: + case GRPC_ABORTED: + case GRPC_OUTOFRANGE: + return EINTERNAL; + case GRPC_UNIMPLEMENTED: + return ENOMETHOD; + case GRPC_INTERNAL: + case GRPC_UNAVAILABLE: + return EINTERNAL; + case GRPC_DATALOSS: + return EINTERNAL; + case GRPC_UNAUTHENTICATED: + return ERPCAUTH; + default: + return EINTERNAL; + } +} + +void PercentEncode(const std::string& str, std::string* str_out) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + for (std::string::const_iterator it = str.begin(); + it != str.end(); ++it) { + const std::string::value_type& c = *it; + // Unreserved Characters are referred from + // https://en.wikipedia.org/wiki/Percent-encoding + if ((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + c == '-' || c == '_' || c == '.' || c == '~') { + escaped << c; + continue; + } + escaped << '%' << std::setw(2) << int((unsigned char) c); + } + if (str_out) { + *str_out = escaped.str(); + } +} + +static int hex_to_int(char c) { + if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } else if (c >= '0' && c <= '9') { + return c - '0'; + } + return 0; +} + +void PercentDecode(const std::string& str, std::string* str_out) { + std::ostringstream unescaped; + for (std::string::const_iterator it = str.begin(); + it != str.end(); ++it) { + const std::string::value_type& c = *it; + if (c == '%' && it + 2 < str.end()) { + int i1 = hex_to_int(*++it); + int i2 = hex_to_int(*++it); + unescaped << (char)(i1 * 16 + i2); + } else { + unescaped << c; + } + } + if (str_out) { + *str_out = unescaped.str(); + } +} + +int64_t ConvertGrpcTimeoutToUS(const std::string* grpc_timeout) { + if (!grpc_timeout || grpc_timeout->empty()) { + return -1; + } + char* endptr = NULL; + int64_t timeout_value = (int64_t)strtol(grpc_timeout->data(), &endptr, 10); + // Only the format that the digit number is equal to (timeout header size - 1) + // is valid. Otherwise the format is not valid and is treated as no deadline. + // For example: + // "1H", "2993S", "82m" is valid. + // "30A" is also valid, but the following switch would fall into default + // case and return -1 since 'A' is not a valid time unit. + // "123ASH" is not vaid since the digit number is 3, while the size is 6. + // "HHH" is not valid since the dight number is 0, while the size is 3. + if ((size_t)(endptr - grpc_timeout->data()) != grpc_timeout->size() - 1) { + return -1; + } + switch (*endptr) { + case 'H': + return timeout_value * 3600 * 1000000; + case 'M': + return timeout_value * 60 * 1000000; + case 'S': + return timeout_value * 1000000; + case 'm': + return timeout_value * 1000; + case 'u': + return timeout_value; + case 'n': + timeout_value = (timeout_value + 500) / 1000; + return (timeout_value == 0) ? 1 : timeout_value; + default: + return -1; + } + CHECK(false) << "Impossible"; +} + +} // namespace brpc diff --git a/src/brpc/grpc.h b/src/brpc/grpc.h new file mode 100644 index 0000000..7bddabc --- /dev/null +++ b/src/brpc/grpc.h @@ -0,0 +1,161 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_GRPC_H +#define BRPC_GRPC_H + +#include +#include + +namespace brpc { + +enum GrpcStatus { + // OK is returned on success. + GRPC_OK = 0, + + // CANCELED indicates the operation was canceled (typically by the caller). + GRPC_CANCELED, + + // Unknown error. An example of where this error may be returned is + // if a Status value received from another address space belongs to + // an error-space that is not known in this address space. Also + // errors raised by APIs that do not return enough error information + // may be converted to this error. + GRPC_UNKNOWN, + + // INVALIDARGUMENT Indicates client specified an invalid argument. + // Note that this differs from FAILEDPRECONDITION. It indicates arguments + // that are problematic regardless of the state of the system + // (e.g., a malformed file name). + GRPC_INVALIDARGUMENT, + + // DEADLINEEXCEEDED Means operation expired before completion. + // For operations that change the state of the system, this error may be + // returned even if the operation has completed successfully. For + // example, a successful response from a server could have been delayed + // long enough for the deadline to expire. + GRPC_DEADLINEEXCEEDED, + + // NOTFOUND Means some requested entity (e.g., file or directory) was + // not found. + GRPC_NOTFOUND, + + // ALREADYEXISTS Means an attempt to create an entity failed because one + // already exists. + GRPC_ALREADYEXISTS, + + // PERMISSIONDENIED Indicates the caller does not have permission to + // execute the specified operation. It must not be used for rejections + // caused by exhausting some resource (use ResourceExhausted + // instead for those errors). It must not be + // used if the caller cannot be identified (use UNAUTHENTICATED + // instead for those errors). + GRPC_PERMISSIONDENIED, + + // RESOURCEEXHAUSTED Indicates some resource has been exhausted, perhaps + // a per-user quota, or perhaps the entire file system is out of space. + GRPC_RESOURCEEXHAUSTED, + + // FAILEDPRECONDITION indicates operation was rejected because the + // system is not in a state required for the operation's execution. + // For example, directory to be deleted may be non-empty, an rmdir + // operation is applied to a non-directory, etc. + // + // A litmus test that may help a service implementor in deciding + // between FAILEDPRECONDITION, Aborted, and Unavailable: + // (a) Use Unavailable if the client can retry just the failing call. + // (b) Use Aborted if the client should retry at a higher-level + // (e.g., restarting a read-modify-write sequence). + // (c) Use FAILEDPRECONDITION if the client should not retry until + // the system state has been explicitly fixed. E.g., if an "rmdir" + // fails because the directory is non-empty, FAILEDPRECONDITION + // should be returned since the client should not retry unless + // they have first fixed up the directory by deleting files from it. + // (d) Use FAILEDPRECONDITION if the client performs conditional + // REST Get/Update/Delete on a resource and the resource on the + // server does not match the condition. E.g., conflicting + // read-modify-write on the same resource. + GRPC_FAILEDPRECONDITION, + + // ABORTED indicates the operation was aborted, typically due to a + // concurrency issue like sequencer check failures, transaction aborts, + // etc. + // + // See litmus test above for deciding between FAILEDPRECONDITION, + // Aborted, and Unavailable. + GRPC_ABORTED, + + // OUTOFRANGE means operation was attempted past the valid range. + // E.g., seeking or reading past end of file. + // + // Unlike INVALIDARGUMENT, this error indicates a problem that may + // be fixed if the system state changes. For example, a 32-bit file + // system will generate INVALIDARGUMENT if asked to read at an + // offset that is not in the range [0,2^32-1], but it will generate + // OUTOFRANGE if asked to read from an offset past the current + // file size. + // + // There is a fair bit of overlap between FAILEDPRECONDITION and + // OUTOFRANGE. We recommend using OUTOFRANGE (the more specific + // error) when it applies so that callers who are iterating through + // a space can easily look for an OUTOFRANGE error to detect when + // they are done. + GRPC_OUTOFRANGE, + + // UNIMPLEMENTED indicates operation is not implemented or not + // supported/enabled in this service. + GRPC_UNIMPLEMENTED, + + // INTERNAL errors. Means some invariants expected by underlying + // system has been broken. If you see one of these errors, + // something is very broken. + GRPC_INTERNAL, + + // UNAVAILABLE indicates the service is currently unavailable. + // This is a most likely a transient condition and may be corrected + // by retrying with a backoff. + // + // See litmus test above for deciding between FAILEDPRECONDITION, + // ABORTED, and UNAVAILABLE. + GRPC_UNAVAILABLE, + + // DATALOSS indicates unrecoverable data loss or corruption. + GRPC_DATALOSS, + + // UNAUTHENTICATED indicates the request does not have valid + // authentication credentials for the operation. + GRPC_UNAUTHENTICATED, + + GRPC_MAX, +}; + +// Get description of the error. +const char* GrpcStatusToString(GrpcStatus); + +// Convert between error code and grpc status with similar semantics +GrpcStatus ErrorCodeToGrpcStatus(int error_code); +int GrpcStatusToErrorCode(GrpcStatus grpc_status); + +void PercentEncode(const std::string& str, std::string* str_out); + +void PercentDecode(const std::string& str, std::string* str_out); + + +} // namespace brpc + +#endif // BRPC_GRPC_H diff --git a/src/brpc/grpc_health_check.proto b/src/brpc/grpc_health_check.proto new file mode 100644 index 0000000..273840b --- /dev/null +++ b/src/brpc/grpc_health_check.proto @@ -0,0 +1,41 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax = "proto2"; +package grpc.health.v1; +option cc_generic_services = true; + +message HealthCheckRequest { + optional string service = 1; +} + +message HealthCheckResponse { + enum ServingStatus { + UNKNOWN = 0; + SERVING = 1; + NOT_SERVING = 2; + SERVICE_UNKNOWN = 3; // Used only by the Watch method. + } + optional ServingStatus status = 1; +} + +service Health { + rpc Check(HealthCheckRequest) returns (HealthCheckResponse); + + rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse); +} + diff --git a/src/brpc/health_check_option.h b/src/brpc/health_check_option.h new file mode 100644 index 0000000..2fe432a --- /dev/null +++ b/src/brpc/health_check_option.h @@ -0,0 +1,34 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_HEALTH_CHECK_OPTION_H +#define BRPC_HEALTH_CHECK_OPTION_H + +#include + +namespace brpc { + +struct HealthCheckOption { + // Http path of health check call + std::string health_check_path; + // The timeout for both establishing the connection and the http call to health_check_path over the connection + int32_t health_check_timeout_ms{500}; +}; + +} // namespace brpc + +#endif // BRPC_HEALTH_CHECK_OPTION_H \ No newline at end of file diff --git a/src/brpc/health_reporter.h b/src/brpc/health_reporter.h new file mode 100644 index 0000000..85a600a --- /dev/null +++ b/src/brpc/health_reporter.h @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_HEALTH_REPORTER_H +#define BRPC_HEALTH_REPORTER_H + +#include "brpc/controller.h" + + +namespace brpc { + +// For customizing /health page. +// Inherit this class and assign an instance to ServerOptions.health_reporter. +class HealthReporter { +public: + virtual ~HealthReporter() {} + + // Get the http request from cntl->http_request() / cntl->request_attachment() + // and put the response in cntl->http_response() / cntl->response_attachment() + // Don't forget to call done->Run() at the end. + virtual void GenerateReport(Controller* cntl, google::protobuf::Closure* done) = 0; +}; + +} // namespace brpc + + +#endif // BRPC_HEALTH_REPORTER_H diff --git a/src/brpc/http2.cpp b/src/brpc/http2.cpp new file mode 100644 index 0000000..59099c2 --- /dev/null +++ b/src/brpc/http2.cpp @@ -0,0 +1,115 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/logging.h" +#include "brpc/details/hpack.h" +#include "brpc/errno.pb.h" +#include "brpc/http2.h" + +namespace brpc { + +H2Settings::H2Settings() + : header_table_size(DEFAULT_HEADER_TABLE_SIZE) + , enable_push(false) + , max_concurrent_streams(std::numeric_limits::max()) + , stream_window_size(256 * 1024) + , connection_window_size(1024 * 1024) + , max_frame_size(DEFAULT_MAX_FRAME_SIZE) + , max_header_list_size(std::numeric_limits::max()) { +} + +bool H2Settings::IsValid(bool log_error) const { + if (stream_window_size > MAX_WINDOW_SIZE) { + LOG_IF(ERROR, log_error) << "Invalid stream_window_size=" << stream_window_size; + return false; + } + if (connection_window_size < DEFAULT_INITIAL_WINDOW_SIZE || + connection_window_size > MAX_WINDOW_SIZE) { + LOG_IF(ERROR, log_error) << "Invalid connection_window_size=" << connection_window_size; + return false; + } + if (max_frame_size < DEFAULT_MAX_FRAME_SIZE || + max_frame_size > MAX_OF_MAX_FRAME_SIZE) { + LOG_IF(ERROR, log_error) << "Invalid max_frame_size=" << max_frame_size; + return false; + } + return true; +} + +std::ostream& operator<<(std::ostream& os, const H2Settings& s) { + os << "{header_table_size=" << s.header_table_size + << " enable_push=" << s.enable_push + << " max_concurrent_streams=" << s.max_concurrent_streams + << " stream_window_size=" << s.stream_window_size; + if (s.connection_window_size > 0) { + os << " conn_window_size=" << s.connection_window_size; + } + os << " max_frame_size=" << s.max_frame_size + << " max_header_list_size=" << s.max_header_list_size + << '}'; + return os; +} + +const char* H2ErrorToString(H2Error e) { + switch (e) { + case H2_NO_ERROR: return "NO_ERROR"; + case H2_PROTOCOL_ERROR: return "PROTOCOL_ERROR"; + case H2_INTERNAL_ERROR: return "INTERNAL_ERROR"; + case H2_FLOW_CONTROL_ERROR: return "FLOW_CONTROL_ERROR"; + case H2_SETTINGS_TIMEOUT: return "SETTINGS_TIMEOUT"; + case H2_STREAM_CLOSED_ERROR: return "STREAM_CLOSED"; + case H2_FRAME_SIZE_ERROR: return "FRAME_SIZE_ERROR"; + case H2_REFUSED_STREAM: return "REFUSED_STREAM"; + case H2_CANCEL: return "CANCEL"; + case H2_COMPRESSION_ERROR: return "COMPRESSION_ERROR"; + case H2_CONNECT_ERROR: return "CONNECT_ERROR"; + case H2_ENHANCE_YOUR_CALM: return "ENHANCE_YOUR_CALM"; + case H2_INADEQUATE_SECURITY: return "INADEQUATE_SECURITY"; + case H2_HTTP_1_1_REQUIRED: return "HTTP_1_1_REQUIRED"; + } + return "Unknown-H2Error"; +} + +int H2ErrorToStatusCode(H2Error e) { + switch (e) { + case H2_NO_ERROR: + return HTTP_STATUS_OK; + case H2_SETTINGS_TIMEOUT: + return HTTP_STATUS_GATEWAY_TIMEOUT; + case H2_STREAM_CLOSED_ERROR: + return HTTP_STATUS_BAD_REQUEST; + case H2_REFUSED_STREAM: + case H2_CANCEL: + case H2_ENHANCE_YOUR_CALM: + return HTTP_STATUS_SERVICE_UNAVAILABLE; + case H2_INADEQUATE_SECURITY: + return HTTP_STATUS_UNAUTHORIZED; + case H2_HTTP_1_1_REQUIRED: + return HTTP_STATUS_VERSION_NOT_SUPPORTED; + case H2_PROTOCOL_ERROR: + case H2_FLOW_CONTROL_ERROR: + case H2_FRAME_SIZE_ERROR: + case H2_COMPRESSION_ERROR: + case H2_CONNECT_ERROR: + case H2_INTERNAL_ERROR: + return HTTP_STATUS_INTERNAL_SERVER_ERROR; + } + return HTTP_STATUS_INTERNAL_SERVER_ERROR; +} + +} // namespace brpc diff --git a/src/brpc/http2.h b/src/brpc/http2.h new file mode 100644 index 0000000..69d3087 --- /dev/null +++ b/src/brpc/http2.h @@ -0,0 +1,126 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BAIDU_RPC_HTTP2_H +#define BAIDU_RPC_HTTP2_H + +#include +#include "brpc/http_status_code.h" + +// To baidu-rpc developers: This is a header included by user, don't depend +// on internal structures, use opaque pointers instead. + +namespace brpc { + +struct H2Settings { + // Construct with default values. + H2Settings(); + + // Returns true iff all options are valid. + bool IsValid(bool log_error = false) const; + + // Allows the sender to inform the remote endpoint of the maximum size of + // the header compression table used to decode header blocks, in octets. + // The encoder can select any size equal to or less than this value by + // using signaling specific to the header compression format inside a + // header block (see [COMPRESSION]). + // Default: 4096 + static const uint32_t DEFAULT_HEADER_TABLE_SIZE = 4096; + uint32_t header_table_size; + + // Enable server push or not (Section 8.2). + // An endpoint MUST NOT send a PUSH_PROMISE frame if it receives this + // parameter set to a value of 0. An endpoint that has both set this + // parameter to 0 and had it acknowledged MUST treat the receipt of a + // PUSH_PROMISE frame as a connection error (Section 5.4.1) of type + // PROTOCOL_ERROR. + // Default: false (server push is disabled) + static const bool DEFAULT_ENABLE_PUSH = true; + bool enable_push; + + // The maximum number of concurrent streams that the sender will allow. + // This limit is directional: it applies to the number of streams that the + // sender permits the receiver to create. It is recommended that this value + // be no smaller than 100, so as to not unnecessarily limit parallelism. + // 0 prevents the creation of new streams. However, this can also happen + // for any limit that is exhausted with active streams. Servers SHOULD only + // set a zero value for short durations; if a server does not wish to + // accept requests, closing the connection is more appropriate. + // Default: unlimited + uint32_t max_concurrent_streams; + + // Sender's initial window size (in octets) for stream-level flow control. + // This setting affects the window size of all streams (see Section 6.9.2). + // Values above the maximum flow-control window size of 2^31-1 are treated + // as a connection error (Section 5.4.1) of type FLOW_CONTROL_ERROR + // Default: 256 * 1024 + static const uint32_t DEFAULT_INITIAL_WINDOW_SIZE = 65535; + static const uint32_t MAX_WINDOW_SIZE = (1u << 31) - 1; + uint32_t stream_window_size; + + // Initial window size for connection-level flow control. + // Default: 1024 * 1024 + // Setting to zero stops printing this field. + uint32_t connection_window_size; + + // Size of the largest frame payload that the sender is willing to receive, + // in octets. The value advertised by an endpoint MUST be between 16384 and + // 16777215, inclusive. Values outside this range are treated as a + // connection error(Section 5.4.1) of type PROTOCOL_ERROR. + // Default: 16384 + static const uint32_t DEFAULT_MAX_FRAME_SIZE = 16384; + static const uint32_t MAX_OF_MAX_FRAME_SIZE = 16777215; + uint32_t max_frame_size; + + // This advisory setting informs a peer of the maximum size of header list + // that the sender is prepared to accept, in octets. The value is based on + // the uncompressed size of header fields, including the length of the name + // and value in octets plus an overhead of 32 octets for each header field. + // For any given request, a lower limit than what is advertised MAY be + // enforced. + // Default: unlimited. + uint32_t max_header_list_size; +}; + +std::ostream& operator<<(std::ostream& os, const H2Settings& s); + +enum H2Error { + H2_NO_ERROR = 0x0, // Graceful shutdown + H2_PROTOCOL_ERROR = 0x1, // Protocol error detected + H2_INTERNAL_ERROR = 0x2, // Implementation fault + H2_FLOW_CONTROL_ERROR = 0x3, // Flow-control limits exceeded + H2_SETTINGS_TIMEOUT = 0x4, // Settings not acknowledged + H2_STREAM_CLOSED_ERROR = 0x5, // Frame received for closed stream + H2_FRAME_SIZE_ERROR = 0x6, // Frame size incorrect + H2_REFUSED_STREAM = 0x7, // Stream not processed + H2_CANCEL = 0x8, // Stream cancelled + H2_COMPRESSION_ERROR = 0x9, // Compression state not updated + H2_CONNECT_ERROR = 0xa, // TCP connection error for CONNECT method + H2_ENHANCE_YOUR_CALM = 0xb, // Processing capacity exceeded + H2_INADEQUATE_SECURITY = 0xc, // Negotiated TLS parameters not acceptable + H2_HTTP_1_1_REQUIRED = 0xd, // Use HTTP/1.1 for the request +}; + +// Get description of the error. +const char* H2ErrorToString(H2Error e); + +// Convert the error to status code with similar semantics +int H2ErrorToStatusCode(H2Error e); + +} // namespace brpc + +#endif // BAIDU_RPC_HTTP2_H diff --git a/src/brpc/http_header.cpp b/src/brpc/http_header.cpp new file mode 100644 index 0000000..9cc82b7 --- /dev/null +++ b/src/brpc/http_header.cpp @@ -0,0 +1,160 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/http_status_code.h" // HTTP_STATUS_* +#include "brpc/http_header.h" + + +namespace brpc { + +const char* HttpHeader::SET_COOKIE = "set-cookie"; +const char* HttpHeader::COOKIE = "cookie"; +const char* HttpHeader::CONTENT_TYPE = "content-type"; + +HttpHeader::HttpHeader() + : _status_code(HTTP_STATUS_OK) + , _method(HTTP_METHOD_GET) + , _version(1, 1) + , _first_set_cookie(NULL) { + // NOTE: don't forget to clear the field in Clear() as well. +} + +void HttpHeader::Swap(HttpHeader &rhs) { + _headers.swap(rhs._headers); + _uri.Swap(rhs._uri); + std::swap(_status_code, rhs._status_code); + std::swap(_method, rhs._method); + _content_type.swap(rhs._content_type); + _unresolved_path.swap(rhs._unresolved_path); + std::swap(_version, rhs._version); +} + +void HttpHeader::Clear() { + _headers.clear(); + _uri.Clear(); + _status_code = HTTP_STATUS_OK; + _method = HTTP_METHOD_GET; + _content_type.clear(); + _unresolved_path.clear(); + _version = std::make_pair(1, 1); +} + +const std::string* HttpHeader::GetHeader(const char* key) const { + return GetHeader(std::string(key)); +} + +const std::string* HttpHeader::GetHeader(const std::string& key) const { + if (IsSetCookie(key)) { + return _first_set_cookie; + } + std::string* val = _headers.seek(key); + return val; +} + +std::vector HttpHeader::GetAllSetCookieHeader() const { + return GetMultiLineHeaders(SET_COOKIE); +} + +std::vector +HttpHeader::GetMultiLineHeaders(const std::string& key) const { + std::vector headers; + for (const auto& iter : _headers) { + if (_header_key_equal(iter.first, key)) { + headers.push_back(&iter.second); + } + } + return headers; +} + +void HttpHeader::SetHeader(const std::string& key, + const std::string& value) { + GetOrAddHeader(key) = value; +} + +void HttpHeader::RemoveHeader(const char* key) { + if (IsContentType(key)) { + _content_type.clear(); + } else { + _headers.erase(key); + if (IsSetCookie(key)) { + _first_set_cookie = NULL; + } + } +} + +void HttpHeader::AppendHeader(const std::string& key, + const butil::StringPiece& value) { + if (!CanFoldedInLine(key)) { + // Add a new Set-Cookie header field. + std::string& slot = AddHeader(key); + slot.assign(value.data(), value.size()); + } else { + std::string& slot = GetOrAddHeader(key); + if (slot.empty()) { + slot.assign(value.data(), value.size()); + } else { + slot.reserve(slot.size() + 1 + value.size()); + slot.append(HeaderValueDelimiter(key)); + slot.append(value.data(), value.size()); + } + } +} + +const char* HttpHeader::reason_phrase() const { + return HttpReasonPhrase(_status_code); +} + +void HttpHeader::set_status_code(int status_code) { + _status_code = status_code; +} + +std::string& HttpHeader::GetOrAddHeader(const std::string& key) { + if (IsContentType(key)) { + return _content_type; + } + + bool is_set_cookie = IsSetCookie(key); + // Only returns the first Set-Cookie header field for compatibility. + if (is_set_cookie && NULL != _first_set_cookie) { + return *_first_set_cookie; + } + + std::string* val = _headers.seek(key); + if (NULL == val) { + val = _headers.insert({ key, "" }); + if (is_set_cookie) { + _first_set_cookie = val; + } + } + return *val; +} + +std::string& HttpHeader::AddHeader(const std::string& key) { + std::string* val = _headers.insert({ key, "" }); + if (IsSetCookie(key) && NULL == _first_set_cookie) { + _first_set_cookie = val; + } + return *val; +} + +const HttpHeader& DefaultHttpHeader() { + static HttpHeader h; + return h; +} + +} // namespace brpc diff --git a/src/brpc/http_header.h b/src/brpc/http_header.h new file mode 100644 index 0000000..39b2fa9 --- /dev/null +++ b/src/brpc/http_header.h @@ -0,0 +1,213 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_HTTP_HEADER_H +#define BRPC_HTTP_HEADER_H + +#include +#include "butil/strings/string_piece.h" // StringPiece +#include "butil/containers/case_ignored_flat_map.h" +#include "brpc/uri.h" // URI +#include "brpc/http_method.h" // HttpMethod +#include "brpc/http_status_code.h" +#include "brpc/http2.h" + +// To rpc developers: DON'T put impl. details here, use opaque pointers instead. + + +namespace brpc { +class InputMessageBase; +namespace policy { +void ProcessHttpRequest(InputMessageBase *msg); +class H2StreamContext; +} + +// Non-body part of a HTTP message. +class HttpHeader { +public: + typedef butil::CaseIgnoredMultiFlatMap HeaderMap; + typedef HeaderMap::const_iterator HeaderIterator; + typedef HeaderMap::key_equal HeaderKeyEqual; + + HttpHeader(); + + // Exchange internal fields with another HttpHeader. + void Swap(HttpHeader &rhs); + + // Reset internal fields as if they're just default-constructed. + void Clear(); + + // Get http version, 1.1 by default. + int major_version() const { return _version.first; } + int minor_version() const { return _version.second; } + // Change the http version + void set_version(int http_major, int http_minor) + { _version = std::make_pair(http_major, http_minor); } + + // True if version of http is earlier than 1.1 + bool before_http_1_1() const + { return (major_version() * 10000 + minor_version()) <= 10000; } + + // True if the message is from HTTP2. + bool is_http2() const { return major_version() == 2; } + + // Get/set "Content-Type". + // possible values: "text/plain", "application/json" ... + // NOTE: Equal to `GetHeader("Content-Type")', ·SetHeader("Content-Type")‘ (case-insensitive). + const std::string& content_type() const { return _content_type; } + void set_content_type(const std::string& type) { _content_type = type; } + void set_content_type(const char* type) { _content_type = type; } + std::string& mutable_content_type() { return _content_type; } + + // Get value of a header which is case-insensitive according to: + // https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 + // Namely, GetHeader("log-id"), GetHeader("Log-Id"), GetHeader("LOG-ID") + // point to the same value. + // Return pointer to the value, NULL on not found. + // NOTE: If the key is "Content-Type", `GetHeader("Content-Type")' + // (case-insensitive) is equal to `content_type()'. + const std::string* GetHeader(const char* key) const; + const std::string* GetHeader(const std::string& key) const; + + std::vector GetAllSetCookieHeader() const; + + // Set value of a header. + // NOTE: If the key is "Content-Type", `SetHeader("Content-Type", ...)' + // (case-insensitive) is equal to `set_content_type(...)'. + void SetHeader(const std::string& key, const std::string& value); + + // Remove all headers of key. + void RemoveHeader(const char* key); + void RemoveHeader(const std::string& key) { RemoveHeader(key.c_str()); } + + // Append value to a header. If the header already exists, separate + // old value and new value with comma(,), two-byte delimiter of "; " + // or into a new header field, according to: + // + // https://datatracker.ietf.org/doc/html/rfc2616#section-4.2 + // Multiple message-header fields with the same field-name MAY be + // present in a message if and only if the entire field-value for that + // header field is defined as a comma-separated list [i.e., #(values)]. + // + // https://datatracker.ietf.org/doc/html/rfc9114#section-4.2.1 + // If a decompressed field section contains multiple cookie field lines, + // these MUST be concatenated into a single byte string using the two-byte + // delimiter of "; " (ASCII 0x3b, 0x20) before being passed into a context + // other than HTTP/2 or HTTP/3, such as an HTTP/1.1 connection, or a generic + // HTTP server application. + // + // https://datatracker.ietf.org/doc/html/rfc6265#section-3 + // Origin servers SHOULD NOT fold multiple Set-Cookie header + // fields into a single header field. + void AppendHeader(const std::string& key, const butil::StringPiece& value); + + // Get header iterators which are invalidated after calling AppendHeader() + HeaderIterator HeaderBegin() const { return _headers.begin(); } + HeaderIterator HeaderEnd() const { return _headers.end(); } + // #headers + size_t HeaderCount() const { return _headers.size(); } + + // Get the URI object, check src/brpc/uri.h for details. + const URI& uri() const { return _uri; } + URI& uri() { return _uri; } + + // Get/set http method. + HttpMethod method() const { return _method; } + void set_method(const HttpMethod method) { _method = method; } + + // Get/set status-code and reason-phrase. Notice that the const char* + // returned by reason_phrase() will be invalidated after next call to + // set_status_code(). + int status_code() const { return _status_code; } + const char* reason_phrase() const; + void set_status_code(int status_code); + + // The URL path removed with matched prefix. + // NOTE: always normalized and NOT started with /. + // + // Accessing HttpService.Echo + // [URL] [unresolved_path] + // "/HttpService/Echo" "" + // "/HttpService/Echo/Foo" "Foo" + // "/HttpService/Echo/Foo/Bar" "Foo/Bar" + // "/HttpService//Echo///Foo//" "Foo" + // + // Accessing FileService.default_method: + // [URL] [unresolved_path] + // "/FileService" "" + // "/FileService/123.txt" "123.txt" + // "/FileService/mydir/123.txt" "mydir/123.txt" + // "/FileService//mydir///123.txt//" "mydir/123.txt" + const std::string& unresolved_path() const { return _unresolved_path; } + +private: +friend class HttpMessage; +friend class HttpMessageSerializer; +friend class policy::H2StreamContext; +friend void policy::ProcessHttpRequest(InputMessageBase *msg); + + static const char* SET_COOKIE; + static const char* COOKIE; + static const char* CONTENT_TYPE; + + std::vector GetMultiLineHeaders(const std::string& key) const; + + std::string& GetOrAddHeader(const std::string& key); + + std::string& AddHeader(const std::string& key); + + bool IsSetCookie(const std::string& key) const { + return _header_key_equal(key, SET_COOKIE); + } + + bool IsCookie(const std::string& key) const { + return _header_key_equal(key, COOKIE); + } + + bool IsContentType(const std::string& key) const { + return _header_key_equal(key, CONTENT_TYPE); + } + + // Return true if the header can be folded in line, + // otherwise, returns false, i.e., Set-Cookie header. + // See comments of `AppendHeader'. + bool CanFoldedInLine(const std::string& key) { + return !IsSetCookie(key); + } + + const char* HeaderValueDelimiter(const std::string& key) { + return IsCookie(key) ? "; " : ","; + } + + HeaderKeyEqual _header_key_equal; + HeaderMap _headers; + URI _uri; + int _status_code; + HttpMethod _method; + std::string _content_type; + std::string _unresolved_path; + std::pair _version; + std::string* _first_set_cookie; +}; + +const HttpHeader& DefaultHttpHeader(); + +} // namespace brpc + + +#endif //BRPC_HTTP_HEADER_H diff --git a/src/brpc/http_method.cpp b/src/brpc/http_method.cpp new file mode 100644 index 0000000..c66ed33 --- /dev/null +++ b/src/brpc/http_method.cpp @@ -0,0 +1,142 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "butil/macros.h" +#include "butil/logging.h" +#include +#include +#include "brpc/http_method.h" + +namespace brpc { + +struct HttpMethodPair { + HttpMethod method; + const char *str; +}; + +static HttpMethodPair g_method_pairs[] = { + { HTTP_METHOD_DELETE , "DELETE" }, + { HTTP_METHOD_GET , "GET" }, + { HTTP_METHOD_HEAD , "HEAD" }, + { HTTP_METHOD_POST , "POST" }, + { HTTP_METHOD_PUT , "PUT" }, + { HTTP_METHOD_CONNECT , "CONNECT" }, + { HTTP_METHOD_OPTIONS , "OPTIONS" }, + { HTTP_METHOD_TRACE , "TRACE" }, + { HTTP_METHOD_COPY , "COPY" }, + { HTTP_METHOD_LOCK , "LOCK" }, + { HTTP_METHOD_MKCOL , "MKCOL" }, + { HTTP_METHOD_MOVE , "MOVE" }, + { HTTP_METHOD_PROPFIND , "PROPFIND" }, + { HTTP_METHOD_PROPPATCH , "PROPPATCH" }, + { HTTP_METHOD_SEARCH , "SEARCH" }, + { HTTP_METHOD_UNLOCK , "UNLOCK" }, + { HTTP_METHOD_REPORT , "REPORT" }, + { HTTP_METHOD_MKACTIVITY , "MKACTIVITY" }, + { HTTP_METHOD_CHECKOUT , "CHECKOUT" }, + { HTTP_METHOD_MERGE , "MERGE" }, + { HTTP_METHOD_MSEARCH , "M-SEARCH" }, + { HTTP_METHOD_NOTIFY , "NOTIFY" }, + { HTTP_METHOD_SUBSCRIBE , "SUBSCRIBE" }, + { HTTP_METHOD_UNSUBSCRIBE , "UNSUBSCRIBE" }, + { HTTP_METHOD_PATCH , "PATCH" }, + { HTTP_METHOD_PURGE , "PURGE" }, + { HTTP_METHOD_MKCALENDAR , "MKCALENDAR" }, +}; + +static const char* g_method2str_map[64] = { NULL }; +static pthread_once_t g_init_maps_once = PTHREAD_ONCE_INIT; +static uint8_t g_first_char_index[26] = { 0 }; + +struct LessThanByName { + bool operator()(const HttpMethodPair& p1, const HttpMethodPair& p2) const { + return strcasecmp(p1.str, p2.str) < 0; + } +}; + +static void BuildHttpMethodMaps() { + for (size_t i = 0; i < ARRAY_SIZE(g_method_pairs); ++i) { + const int method = (int)g_method_pairs[i].method; + RELEASE_ASSERT(method >= 0 && + method <= (int)ARRAY_SIZE(g_method2str_map)); + g_method2str_map[method] = g_method_pairs[i].str; + } + std::sort(g_method_pairs, g_method_pairs + ARRAY_SIZE(g_method_pairs), + LessThanByName()); + char last_fc = '\0'; + for (size_t i = 0; i < ARRAY_SIZE(g_method_pairs); ++i) { + char fc = g_method_pairs[i].str[0]; + RELEASE_ASSERT_VERBOSE(fc >= 'A' && fc <= 'Z', + "Invalid method_name=%s", + g_method_pairs[i].str); + if (fc != last_fc) { + last_fc = fc; + g_first_char_index[fc - 'A'] = (uint8_t)(i + 1); + } + } +} + +const char *HttpMethod2Str(HttpMethod method) { + pthread_once(&g_init_maps_once, BuildHttpMethodMaps); + if ((int)method < 0 || + (int)method >= (int)ARRAY_SIZE(g_method2str_map)) { + return "UNKNOWN"; + } + const char* s = g_method2str_map[method]; + return s ? s : "UNKNOWN"; +} + +bool Str2HttpMethod(const char* method_str, HttpMethod* method) { + const char fc = ::toupper(static_cast(*method_str)); + if (fc == 'G') { + if (strcasecmp(method_str + 1, /*G*/"ET") == 0) { + *method = HTTP_METHOD_GET; + return true; + } + } else if (fc == 'P') { + if (strcasecmp(method_str + 1, /*P*/"OST") == 0) { + *method = HTTP_METHOD_POST; + return true; + } else if (strcasecmp(method_str + 1, /*P*/"UT") == 0) { + *method = HTTP_METHOD_PUT; + return true; + } + } + pthread_once(&g_init_maps_once, BuildHttpMethodMaps); + if (fc < 'A' || fc > 'Z') { + return false; + } + size_t index = g_first_char_index[fc - 'A']; + if (index == 0) { + return false; + } + --index; + for (; index < ARRAY_SIZE(g_method_pairs); ++index) { + const HttpMethodPair& p = g_method_pairs[index]; + if (strcasecmp(method_str, p.str) == 0) { + *method = p.method; + return true; + } + if (p.str[0] != fc) { + return false; + } + } + return false; +} + +} // namespace brpc diff --git a/src/brpc/http_method.h b/src/brpc/http_method.h new file mode 100644 index 0000000..5bf0f91 --- /dev/null +++ b/src/brpc/http_method.h @@ -0,0 +1,63 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_HTTP_METHOD_H +#define BRPC_HTTP_METHOD_H + +namespace brpc { + +enum HttpMethod { + HTTP_METHOD_DELETE = 0, + HTTP_METHOD_GET = 1, + HTTP_METHOD_HEAD = 2, + HTTP_METHOD_POST = 3, + HTTP_METHOD_PUT = 4, + HTTP_METHOD_CONNECT = 5, + HTTP_METHOD_OPTIONS = 6, + HTTP_METHOD_TRACE = 7, + HTTP_METHOD_COPY = 8, + HTTP_METHOD_LOCK = 9, + HTTP_METHOD_MKCOL = 10, + HTTP_METHOD_MOVE = 11, + HTTP_METHOD_PROPFIND = 12, + HTTP_METHOD_PROPPATCH = 13, + HTTP_METHOD_SEARCH = 14, + HTTP_METHOD_UNLOCK = 15, + HTTP_METHOD_REPORT = 16, + HTTP_METHOD_MKACTIVITY = 17, + HTTP_METHOD_CHECKOUT = 18, + HTTP_METHOD_MERGE = 19, + HTTP_METHOD_MSEARCH = 20, // M-SEARCH + HTTP_METHOD_NOTIFY = 21, + HTTP_METHOD_SUBSCRIBE = 22, + HTTP_METHOD_UNSUBSCRIBE = 23, + HTTP_METHOD_PATCH = 24, + HTTP_METHOD_PURGE = 25, + HTTP_METHOD_MKCALENDAR = 26 +}; + +// Returns literal description of `http_method'. "UNKNOWN" on not found. +const char *HttpMethod2Str(HttpMethod http_method); + +// Convert case-insensitive `method_str' to enum HttpMethod. +// Returns true on success. +bool Str2HttpMethod(const char* method_str, HttpMethod* method); + +} // namespace brpc + +#endif //BRPC_HTTP_METHOD_H diff --git a/src/brpc/http_status_code.cpp b/src/brpc/http_status_code.cpp new file mode 100644 index 0000000..cd3a7c2 --- /dev/null +++ b/src/brpc/http_status_code.cpp @@ -0,0 +1,144 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // snprintf + +#include "butil/logging.h" // BAIDU_* +#include "butil/macros.h" // ARRAY_SIZE +#include "butil/thread_local.h" // thread_local +#include "brpc/errno.pb.h" +#include "brpc/http_status_code.h" + + +namespace brpc { + +static struct status_pair{ + int status_code; + const char *reason_phrase; +} status_pairs[] = { + // Informational 1xx + { HTTP_STATUS_CONTINUE, "Continue" }, + { HTTP_STATUS_SWITCHING_PROTOCOLS, "Switching Protocols" }, + + // Successful 2xx + { HTTP_STATUS_OK, "OK" }, + { HTTP_STATUS_CREATED, "Created" }, + { HTTP_STATUS_ACCEPTED, "Accepted" }, + { HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION, "Non-Authoritative Informational" }, + { HTTP_STATUS_NO_CONTENT, "No Content" }, + { HTTP_STATUS_RESET_CONTENT, "Reset Content" }, + { HTTP_STATUS_PARTIAL_CONTENT, "Partial Content" }, + + // Redirection 3xx + { HTTP_STATUS_MULTIPLE_CHOICES, "Multiple Choices" }, + { HTTP_STATUS_MOVE_PERMANENTLY, "Move Permanently" }, + { HTTP_STATUS_FOUND, "Found" }, + { HTTP_STATUS_SEE_OTHER, "See Other" }, + { HTTP_STATUS_NOT_MODIFIED, "Not Modified" }, + { HTTP_STATUS_USE_PROXY, "Use Proxy" }, + { HTTP_STATUS_TEMPORARY_REDIRECT, "Temporary Redirect" }, + + // Client Error 4xx + { HTTP_STATUS_BAD_REQUEST, "Bad Request" }, + { HTTP_STATUS_UNAUTHORIZED, "Unauthorized" }, + { HTTP_STATUS_PAYMENT_REQUIRED, "Payment Required" }, + { HTTP_STATUS_FORBIDDEN, "Forbidden" }, + { HTTP_STATUS_NOT_FOUND, "Not Found" }, + { HTTP_STATUS_METHOD_NOT_ALLOWED, "Method Not Allowed" }, + { HTTP_STATUS_NOT_ACCEPTABLE, "Not Acceptable" }, + { HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED, "Proxy Authentication Required" }, + { HTTP_STATUS_REQUEST_TIMEOUT, "Request Timeout" }, + { HTTP_STATUS_CONFLICT, "Conflict" }, + { HTTP_STATUS_GONE, "Gone" }, + { HTTP_STATUS_LENGTH_REQUIRED, "Length Required" }, + { HTTP_STATUS_PRECONDITION_FAILED, "Precondition Failed" }, + { HTTP_STATUS_REQUEST_ENTITY_TOO_LARGE, "Request Entity Too Large" }, + { HTTP_STATUS_REQUEST_URI_TOO_LARG, "Request-URI Too Long" }, + { HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type"}, + { HTTP_STATUS_REQUEST_RANGE_NOT_SATISFIABLE, "Requested Range Not Satisfiable" }, + { HTTP_STATUS_EXPECTATION_FAILED, "Expectation Failed" }, + + // Server Error 5xx + { HTTP_STATUS_INTERNAL_SERVER_ERROR, "Internal Server Error" }, + { HTTP_STATUS_NOT_IMPLEMENTED, "Not Implemented" }, + { HTTP_STATUS_BAD_GATEWAY, "Bad Gateway" }, + { HTTP_STATUS_SERVICE_UNAVAILABLE, "Service Unavailable" }, + { HTTP_STATUS_GATEWAY_TIMEOUT, "Gateway Timeout" }, + { HTTP_STATUS_VERSION_NOT_SUPPORTED, "HTTP Version Not Supported" }, +}; + +static const char *phrases[1024]; +static pthread_once_t init_reason_phrases_once = PTHREAD_ONCE_INIT; + +static void InitReasonPhrases() { + memset(phrases, 0, sizeof(phrases)); + for (size_t i = 0; i < ARRAY_SIZE(status_pairs); ++i) { + if (status_pairs[i].status_code >= 0 && + status_pairs[i].status_code < (int)ARRAY_SIZE(phrases)) { + phrases[status_pairs[i].status_code] = status_pairs[i].reason_phrase; + } else { + LOG(FATAL) << "The status_pairs[" << i << "] is invalid" + << " status_code=" << status_pairs[i].status_code + << " reason_phrase=`" << status_pairs[i].reason_phrase + << '\''; + } + } +} + +static BAIDU_THREAD_LOCAL char tls_phrase_cache[64]; + +const char *HttpReasonPhrase(int status_code) { + pthread_once(&init_reason_phrases_once, InitReasonPhrases); + const char* desc = NULL; + if (status_code >= 0 && + status_code < (int)ARRAY_SIZE(phrases) && + (desc = phrases[status_code])) { + return desc; + } + snprintf(tls_phrase_cache, sizeof(tls_phrase_cache), + "Unknown status code (%d)", status_code); + return tls_phrase_cache; +} + +int ErrorCodeToStatusCode(int error_code) { + if (error_code == 0) { + return HTTP_STATUS_OK; + } + switch (error_code) { + case ENOSERVICE: + case ENOMETHOD: + return HTTP_STATUS_NOT_FOUND; + case ERPCAUTH: + return HTTP_STATUS_UNAUTHORIZED; + case EREQUEST: + case EINVAL: + return HTTP_STATUS_BAD_REQUEST; + case ELIMIT: + case ELOGOFF: + return HTTP_STATUS_SERVICE_UNAVAILABLE; + case EPERM: + return HTTP_STATUS_FORBIDDEN; + case ERPCTIMEDOUT: + case ETIMEDOUT: + return HTTP_STATUS_GATEWAY_TIMEOUT; + default: + return HTTP_STATUS_INTERNAL_SERVER_ERROR; + } +} + +} // namespace brpc diff --git a/src/brpc/http_status_code.h b/src/brpc/http_status_code.h new file mode 100644 index 0000000..3dedfee --- /dev/null +++ b/src/brpc/http_status_code.h @@ -0,0 +1,683 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_HTTP_STATUS_CODE_H +#define BRPC_HTTP_STATUS_CODE_H + + +namespace brpc { + +// Read the description of a status code carefully before using it + +// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html +// Status-Code = +// "100" ; Section 10.1.1: Continue +// | "101" ; Section 10.1.2: Switching Protocols +// | "200" ; Section 10.2.1: OK +// | "201" ; Section 10.2.2: Created +// | "202" ; Section 10.2.3: Accepted +// | "203" ; Section 10.2.4: Non-Authoritative Information +// | "204" ; Section 10.2.5: No Content +// | "205" ; Section 10.2.6: Reset Content +// | "206" ; Section 10.2.7: Partial Content +// | "300" ; Section 10.3.1: Multiple Choices +// | "301" ; Section 10.3.2: Moved Permanently +// | "302" ; Section 10.3.3: Found +// | "303" ; Section 10.3.4: See Other +// | "304" ; Section 10.3.5: Not Modified +// | "305" ; Section 10.3.6: Use Proxy +// | "307" ; Section 10.3.8: Temporary Redirect +// | "400" ; Section 10.4.1: Bad Request +// | "401" ; Section 10.4.2: Unauthorized +// | "402" ; Section 10.4.3: Payment Required +// | "403" ; Section 10.4.4: Forbidden +// | "404" ; Section 10.4.5: Not Found +// | "405" ; Section 10.4.6: Method Not Allowed +// | "406" ; Section 10.4.7: Not Acceptable +// | "407" ; Section 10.4.8: Proxy Authentication Required +// | "408" ; Section 10.4.9: Request Time-out +// | "409" ; Section 10.4.10: Conflict +// | "410" ; Section 10.4.11: Gone +// | "411" ; Section 10.4.12: Length Required +// | "412" ; Section 10.4.13: Precondition Failed +// | "413" ; Section 10.4.14: Request Entity Too Large +// | "414" ; Section 10.4.15: Request-URI Too Large +// | "415" ; Section 10.4.16: Unsupported Media Type +// | "416" ; Section 10.4.17: Requested range not satisfiable +// | "417" ; Section 10.4.18: Expectation Failed +// | "500" ; Section 10.5.1: Internal Server Error +// | "501" ; Section 10.5.2: Not Implemented +// | "502" ; Section 10.5.3: Bad Gateway +// | "503" ; Section 10.5.4: Service Unavailable +// | "504" ; Section 10.5.5: Gateway Time-out +// | "505" ; Section 10.5.6: HTTP Version not supported +// | extension-code + +// Return the reason phrase of a given status_code. +// "Unknown status code (|status_code|)" will be returned if the status_code is +// unknown +// This function is thread-safe and NULL is never supposed to be returned +// +// NOTICE: the memory referenced by the pointer returned before might be reused +// when this function is called again, so please DON'T try to cache the return +// value into a container. Directly copy the memory instead. +const char *HttpReasonPhrase(int status_code); + +// Convert brpc error code to related status code. +int ErrorCodeToStatusCode(int error_code); + +// Informational 1xx +// This class of status code indicates a provisional response, consisting +// only of the Status-Line and optional headers, and is terminated by an +// empty line. There are no required headers for this class of status code. +// Since HTTP/1.0 did not define any 1xx status codes, servers MUST NOT +// send a 1xx response to an HTTP/1.0 client except under experimental +// conditions. +// +// A client MUST be prepared to accept one or more 1xx status responses +// prior to a regular response, even if the client does not expect a 100 +// (Continue) status message. Unexpected 1xx status responses MAY be +// ignored by a user agent. +// +// Proxies MUST forward 1xx responses, unless the connection between the +// proxy and its client has been closed, or unless the proxy itself +// requested the generation of the 1xx response. (For example, if a +// +// proxy adds a "Expect: 100-continue" field when it forwards a request; +// then it need not forward the corresponding 100 (Continue) response(s).) + +// 100 Continue +// +// The client SHOULD continue with its request. This interim response is +// used to inform the client that the initial part of the request has been +// received and has not yet been rejected by the server. The client SHOULD +// continue by sending the remainder of the request or, if the request has +// already been completed, ignore this response. The server MUST send a +// final response after the request has been completed. +// +// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.2.3 for +// detailed discussion of the use and handling of this status code. +static const int HTTP_STATUS_CONTINUE = 100; + +// 101 Switching Protocols +// +// The server understands and is willing to comply with the client's +// request, via the Upgrade message header field (section 14.42), for a +// change in the application protocol being used on this connection. The +// server will switch protocols to those defined by the response's Upgrade +// header field immediately after the empty line which terminates the 101 +// response. +// +// The protocol SHOULD be switched only when it is advantageous to do so. +// For example, switching to a newer version of HTTP is advantageous over +// older versions, and switching to a real-time, synchronous protocol might +// be advantageous when delivering resources that use such features. +static const int HTTP_STATUS_SWITCHING_PROTOCOLS = 101; + +// Successful 2xx +// This class of status code indicates that the client's request was +// successfully received, understood, and accepted. + +// 200 OK +// +// The request has succeeded. The information returned with the response is +// dependent on the method used in the request, for example: +// - GET an entity corresponding to the requested resource is sent in the +// response. +// - HEAD the entity-header fields corresponding to the requested resource +// are sent in the response without any message-body. +// - POST an entity describing or containing the result of the action; +// - TRACE an entity containing the request message as received by the end +// server. +static const int HTTP_STATUS_OK = 200; + +// 201 Created +// +// The request has been fulfilled and resulted in a new resource being +// created. The newly created resource can be referenced by the URI(s) +// returned in the entity of the response, with the most specific URI for +// the resource given by a Location header field. The response SHOULD +// include an entity containing a list of resource characteristics and +// location(s) from which the user or user agent can choose the one most +// appropriate. The entity format is specified by the media type given in +// the Content-Type header field. The origin server MUST create the resource +// before returning the 201 status code. If the action cannot be carried out +// immediately, the server SHOULD respond with 202 (Accepted) response +// instead. +// +// A 201 response MAY contain an ETag response header field indicating the +// current value of the entity tag for the requested variant just created; +// see section 14.19 +// (http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19) +static const int HTTP_STATUS_CREATED = 201; + +// 202 Accepted +// +// The request has been accepted for processing, but the processing has not +// been completed. The request might or might not eventually be acted upon; +// as it might be disallowed when processing actually takes place. There is +// no facility for re-sending a status code from an asynchronous operation +// such as this. +// +// The 202 response is intentionally non-committal. Its purpose is to allow +// a server to accept a request for some other process (perhaps a +// batch-oriented process that is only run once per day) without requiring +// that the user agent's connection to the server persist until the process +// is completed. The entity returned with this response SHOULD include an +// indication of the request's current status and either a pointer to a +// status monitor or some estimate of when the user can expect the request +// to be fulfilled. +static const int HTTP_STATUS_ACCEPTED = 202; + +// 203 Non-Authoritative Information +// +// The returned metainformation in the entity-header is not the definitive +// set as available from the origin server, but is gathered from a local or +// a third-party copy. The set presented MAY be a subset or superset of the +// original version. For example, including local annotation information +// about the resource might result in a superset of the metainformation +// known by the origin server. Use of this response code is not required and +// is only appropriate when the response would otherwise be 200 (OK). +static const int HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION = 203; + +// 204 No Content +// +// The server has fulfilled the request but does not need to return an +// entity-body, and might want to return updated metainformation. The +// response MAY include new or updated metainformation in the form of +// entity-headers, which if present SHOULD be associated with the requested +// variant. +// +// If the client is a user agent, it SHOULD NOT change its document view +// from that which caused the request to be sent. This response is primarily +// intended to allow input for actions to take place without causing a +// change to the user agent's active document view, although any new or +// updated metainformation SHOULD be applied to the document currently in +// the user agent's active view. +// +// The 204 response MUST NOT include a message-body, and thus is always +// terminated by the first empty line after the header fields. +static const int HTTP_STATUS_NO_CONTENT = 204; + +// 205 Reset Content +// +// The server has fulfilled the request and the user agent SHOULD reset the +// document view which caused the request to be sent. This response is +// primarily intended to allow input for actions to take place via user +// input, followed by a clearing of the form in which the input is given so +// that the user can easily initiate another input action. The response +// MUST NOT include an entity. +static const int HTTP_STATUS_RESET_CONTENT = 205; + +// 206 Partial Content +// +// The server has fulfilled the partial GET request for the resource. The +// request MUST have included a Range header field (section 14.35 +// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35) +// indicating the desired range, and MAY have included an If-Range header +// field (section 14.27) to make the request conditional. +// +// The response MUST include the following header fields: +// - Either a Content-Range header field (section 14.16 +// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.16) +// indicating the range included with this response, or a +// multipart/byteranges Content-Type including Content-Range fields for +// each part. If a Content-Length header field is present in the +// response, its value MUST match the actual number of OCTETs +// transmitted in the message-body. +// - Date +// - ETag and/or Content-Location, if the header would have been sent +// in a 200 response to the same request +// - Expires, Cache-Control, and/or Vary, if the field-value might +// differ from that sent in any previous response for the same +// variant +// +// If the 206 response is the result of an If-Range request that used a +// strong cache validator (see section 13.3.3 +// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.3), +// the response SHOULD NOT include other entity-headers. If the response is +// the result of an If-Range request that used a weak validator, the +// response MUST NOT include other entity-headers; this prevents +// inconsistencies between cached entity-bodies and updated headers. +// Otherwise, the response MUST include all of the entity-headers that would +// have been returned with a 200 (OK) response to the same request. +// +// A cache MUST NOT combine a 206 response with other previously cached +// content if the ETag or Last-Modified headers do not match exactly, see +// 13.5.4 (http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.4) +// +// A cache that does not support the Range and Content-Range headers MUST +// NOT cache 206 (Partial) responses. +static const int HTTP_STATUS_PARTIAL_CONTENT = 206; + +// Redirection 3xx +// This class of status code indicates that further action needs to be +// taken by the user agent in order to fulfill the request. The action +// required MAY be carried out by the user agent without interaction with +// the user if and only if the method used in the second request is GET or +// HEAD. A client SHOULD detect infinite redirection loops, since such +// loops generate network traffic for each redirection. + +// 300 Multiple Choices +// The requested resource corresponds to any one of a set of +// representations, each with its own specific location, and agent-driven +// negotiation information +// (http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.2) +// is being provided so that the user (or user agent) can select a preferred +// representation and redirect its request to that location. +// +// Unless it was a HEAD request, the response SHOULD include an entity +// containing a list of resource characteristics and location(s) from which +// the user or user agent can choose the one most appropriate. The entity +// format is specified by the media type given in the Content- Type header +// field. Depending upon the format and the capabilities of +// +// the user agent, selection of the most appropriate choice MAY be performed +// automatically. However, this specification does not define any standard +// for such automatic selection. +// +// If the server has a preferred choice of representation, it SHOULD include +// the specific URI for that representation in the Location field; user +// agents MAY use the Location field value for automatic redirection. This +// response is cacheable unless indicated otherwise. +static const int HTTP_STATUS_MULTIPLE_CHOICES = 300; + +// 301 Moved Permanently +// +// The requested resource has been assigned a new permanent URI and any +// future references to this resource SHOULD use one of the returned URIs. +// Clients with link editing capabilities ought to automatically re-link +// references to the Request-URI to one or more of the new references +// returned by the server, where possible. This response is cacheable +// unless indicated otherwise. +// +// The new permanent URI SHOULD be given by the Location field in the +// response. Unless the request method was HEAD, the entity of the response +// SHOULD contain a short hypertext note with a hyperlink to the new +// URI(s). +// +// If the 301 status code is received in response to a request other than +// GET or HEAD, the user agent MUST NOT automatically redirect the request +// unless it can be confirmed by the user, since this might change the +// conditions under which the request was issued. +static const int HTTP_STATUS_MOVE_PERMANENTLY = 301; + +// 302 Found +// +// The requested resource resides temporarily under a different URI. Since +// the redirection might be altered on occasion, the client SHOULD continue +// to use the Request-URI for future requests. This response is only +// cacheable if indicated by a Cache-Control or Expires header field. +// +// The temporary URI SHOULD be given by the Location field in the response. +// Unless the request method was HEAD, the entity of the response SHOULD +// contain a short hypertext note with a hyperlink to the new URI(s). +// +// If the 302 status code is received in response to a request other than +// GET or HEAD, the user agent MUST NOT automatically redirect the request +// unless it can be confirmed by the user, since this might change the +// conditions under which the request was issued. +static const int HTTP_STATUS_FOUND = 302; + +// 303 See Other +// +// The response to the request can be found under a different URI and +// SHOULD be retrieved using a GET method on that resource. This method +// exists primarily to allow the output of a POST-activated script to +// redirect the user agent to a selected resource. The new URI is not a +// substitute reference for the originally requested resource. The 303 +// response MUST NOT be cached, but the response to the second (redirected) +// request might be cacheable. +// +// The different URI SHOULD be given by the Location field in the response. +// Unless the request method was HEAD, the entity of the response SHOULD +// contain a short hypertext note with a hyperlink to the new URI(s). +static const int HTTP_STATUS_SEE_OTHER = 303; + +// 304 Not Modified +// +// If the client has performed a conditional GET request and access is +// allowed, but the document has not been modified, the server SHOULD +// respond with this status code. The 304 response MUST NOT contain a +// message-body, and thus is always terminated by the first empty line +// after the header fields. +// +// The response MUST include the following header fields: +// - Date, unless its omission is required by section 14.18.1 +// (http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.18.1) +// If a clockless origin server obeys these rules, and proxies and clients +// add their own Date to any response received without one (as already +// specified by [RFC 2068], section 14.19 +// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19), caches +// will operate correctly. +static const int HTTP_STATUS_NOT_MODIFIED = 304; + +// 305 Use Proxy +// +// The requested resource MUST be accessed through the proxy given by the +// Location field. The Location field gives the URI of the proxy. The +// recipient is expected to repeat this single request via the proxy. 305 +// responses MUST only be generated by origin servers. +// +// Note: RFC 2068 was not clear that 305 was intended to redirect a +// single request, and to be generated by origin servers only. +// Not observing these limitations has significant security +// consequences. +static const int HTTP_STATUS_USE_PROXY = 305; + +// 307 Temporary Redirect +// +// The requested resource resides temporarily under a different URI. Since +// the redirection MAY be altered on occasion, the client SHOULD continue to +// use the Request-URI for future requests. This response is only cacheable +// if indicated by a Cache-Control or Expires header field. +// +// The temporary URI SHOULD be given by the Location field in the response. +// Unless the request method was HEAD, the entity of the response SHOULD +// contain a short hypertext note with a hyperlink to the new URI(s) , since +// many pre-HTTP/1.1 user agents do not understand the 307 status. +// Therefore, the note SHOULD contain the information necessary for a user +// to repeat the original request on the new URI. +// +// If the 307 status code is received in response to a request other than +// GET or HEAD, the user agent MUST NOT automatically redirect the request +// unless it can be confirmed by the user, since this might change the +// conditions under which the request was issued. +static const int HTTP_STATUS_TEMPORARY_REDIRECT = 307; + +// Client Error 4xx +// The 4xx class of status code is intended for cases in which the client +// seems to have erred. Except when responding to a HEAD request, the +// server SHOULD include an entity containing an explanation of the error +// situation, and whether it is a temporary or permanent condition. These +// status codes are applicable to any request method. User agents SHOULD +// display any included entity to the user. +// +// If the client is sending data, a server implementation using TCP SHOULD +// be careful to ensure that the client acknowledges receipt of the +// packet(s) containing the response, before the server closes the input +// connection. If the client continues sending data to the server after the +// close, the server's TCP stack will send a reset packet to the client; +// which may erase the client's unacknowledged input buffers before they +// can be read and interpreted by the HTTP application. + +// 400 Bad Request +// The request could not be understood by the server due to malformed +// syntax. The client SHOULD NOT repeat the request without modifications. +static const int HTTP_STATUS_BAD_REQUEST = 400; + +// 401 Unauthorized +// +// The request requires user authentication. The response MUST include a +// WWW-Authenticate header field containing a challenge +// applicable to the requested resource. The client MAY repeat the request +// with a suitable Authorization header field. If the request +// already included Authorization credentials, then the 401 response +// indicates that authorization has been refused for those credentials. If +// the 401 response contains the same challenge as the prior response, and +// the user agent has already attempted authentication at least once, then +// the user SHOULD be presented the entity that was given in the response; +// since that entity might include relevant diagnostic information. HTTP +// access authentication is explained in "HTTP Authentication: Basic and +// Digest Access Authentication" (http://www.ietf.org/rfc/rfc2617.txt) +static const int HTTP_STATUS_UNAUTHORIZED = 401; + +// 402 Payment Required +// +// This code is reserved for future use. +static const int HTTP_STATUS_PAYMENT_REQUIRED = 402; + +// 403 Forbidden +// +// The server understood the request, but is refusing to fulfill it. +// Authorization will not help and the request SHOULD NOT be repeated. If +// the request method was not HEAD and the server wishes to make public why +// the request has not been fulfilled, it SHOULD describe the reason for +// the refusal in the entity. If the server does not wish to make this +// information available to the client, the status code 404 (Not Found) can +// be used instead. +static const int HTTP_STATUS_FORBIDDEN = 403; + +// 404 Not Found +// +// The server has not found anything matching the Request-URI. No indication +// is given of whether the condition is temporary or permanent. The 410 +// (Gone) status code SHOULD be used if the server knows, through some +// internally configurable mechanism, that an old resource is permanently +// unavailable and has no forwarding address. This status code is commonly +// used when the server does not wish to reveal exactly why the request has +// been refused, or when no other response is applicable. +static const int HTTP_STATUS_NOT_FOUND = 404; + +// 405 Method Not Allowed +// +// The method specified in the Request-Line is not allowed for the resource +// identified by the Request-URI. The response MUST include an Allow header +// containing a list of valid methods for the requested resource. +static const int HTTP_STATUS_METHOD_NOT_ALLOWED = 405; + +// 406 Not Acceptable +// +// The resource identified by the request is only capable of generating +// response entities which have content characteristics not acceptable +// according to the accept headers sent in the request. +// +// Unless it was a HEAD request, the response SHOULD include an entity +// containing a list of available entity characteristics and location(s) +// from which the user or user agent can choose the one most appropriate. +// The entity format is specified by the media type given in the +// Content-Type header field. Depending upon the format and the +// capabilities of the user agent, selection of the most appropriate choice +// MAY be performed automatically. However, this specification does not +// define any standard for such automatic selection. +static const int HTTP_STATUS_NOT_ACCEPTABLE = 406; + +// 407 Proxy Authentication Required +// This code is similar to 401 (Unauthorized), but indicates that the client +// must first authenticate itself with the proxy. The proxy MUST return a +// Proxy-Authenticate header field containing a challenge +// applicable to the proxy for the requested resource. The client MAY repeat +// the request with a suitable Proxy-Authorization header field. +// HTTP access authentication is explained in "HTTP Authentication: +// Basic and Digest Access Authentication" +static const int HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED = 407; + +// 408 Request Timeout +// +// The client did not produce a request within the time that the server was +// prepared to wait. The client MAY repeat the request without modifications +// at any later time. +static const int HTTP_STATUS_REQUEST_TIMEOUT = 408; + +// 409 Conflict +// +// The request could not be completed due to a conflict with the current +// state of the resource. This code is only allowed in situations where it +// is expected that the user might be able to resolve the conflict and +// resubmit the request. The response body SHOULD include enough information +// for the user to recognize the source of the conflict. Ideally, the +// response entity would include enough information for the user or user +// agent to fix the problem; however, that might not be possible and is not +// required. +// +// Conflicts are most likely to occur in response to a PUT request. For +// example, if versioning were being used and the entity being PUT included +// changes to a resource which conflict with those made by an earlier +// (third-party) request, the server might use the 409 response to indicate +// that it can't complete the request. In this case, the response entity +// would likely contain a list of the differences between the two versions +// in a format defined by the response Content-Type. +static const int HTTP_STATUS_CONFLICT = 409; + +// 410 Gone +// +// The requested resource is no longer available at the server and no +// forwarding address is known. This condition is expected to be considered +// permanent. Clients with link editing capabilities SHOULD delete +// references to the Request-URI after user approval. If the server does not +// know, or has no facility to determine, whether or not the condition is +// permanent, the status code 404 (Not Found) SHOULD be used instead. This +// response is cacheable unless indicated otherwise. +// +// The 410 response is primarily intended to assist the task of web +// maintenance by notifying the recipient that the resource is intentionally +// unavailable and that the server owners desire that remote links to that +// resource be removed. Such an event is common for limited-time; +// promotional services and for resources belonging to individuals no longer +// working at the server's site. It is not necessary to mark all permanently +// unavailable resources as "gone" or to keep the mark for any length of +// time -- that is left to the discretion of the server owner. +static const int HTTP_STATUS_GONE = 410; + +// 411 Length Required +// +// The server refuses to accept the request without a defined +// Content-Length. The client MAY repeat the request if it adds a valid +// Content-Length header field containing the length of the message-body in +// the request message. +static const int HTTP_STATUS_LENGTH_REQUIRED = 411; + +// 412 Precondition Failed +// +// The precondition given in one or more of the request-header fields +// evaluated to false when it was tested on the server. This response code +// allows the client to place preconditions on the current resource +// metainformation (header field data) and thus prevent the requested method +// from being applied to a resource other than the one intended. +static const int HTTP_STATUS_PRECONDITION_FAILED = 412; + +// 413 Request Entity Too Large +// +// The server is refusing to process a request because the request entity is +// larger than the server is willing or able to process. The server MAY +// close the connection to prevent the client from continuing the request. +// +// If the condition is temporary, the server SHOULD include a Retry-After +// header field to indicate that it is temporary and after what time the +// client MAY try again. +static const int HTTP_STATUS_REQUEST_ENTITY_TOO_LARGE = 413; + +// 414 Request-URI Too Long +// +// The server is refusing to service the request because the Request-URI is +// longer than the server is willing to interpret. This rare condition is +// only likely to occur when a client has improperly converted a POST +// request to a GET request with long query information, when the client has +// descended into a URI "black hole" of redirection (e.g., a redirected URI +// prefix that points to a suffix of itself), or when the server is under +// attack by a client attempting to exploit security holes present in some +// servers using fixed-length buffers for reading or manipulating the +// Request-URI. +static const int HTTP_STATUS_REQUEST_URI_TOO_LARG = 414; + +// 415 Unsupported Media Type +// +// The server is refusing to service the request because the entity of the +// request is in a format not supported by the requested resource for the +// requested method. +static const int HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE = 415; + +// 416 Requested Range Not Satisfiable +// +// A server SHOULD return a response with this status code if a request +// included a Range request-header field, and none of the +// range-specifier values in this field overlap the current extent of the +// selected resource, and the request did not include an If-Range +// request-header field. (For byte-ranges, this means that the +// first-byte-pos of all of the byte-range-spec values were greater than the +// current length of the selected resource.) +// +// When this status code is returned for a byte-range request, the response +// SHOULD include a Content-Range entity-header field specifying the current +// length of the selected resource. This response MUST +// NOT use the multipart/byteranges content-type. +static const int HTTP_STATUS_REQUEST_RANGE_NOT_SATISFIABLE = 416; + +// 417 Expectation Failed +// +// The expectation given in an Expect request-header field (see section +// 14.20 http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.20) +// could not be met by this server, or, if the server is a proxy, the +// server has unambiguous evidence that the request could not be met by the +// next-hop server. +static const int HTTP_STATUS_EXPECTATION_FAILED = 417; + +// Server Error 5xx +// +// Response status codes beginning with the digit "5" indicate cases in +// which the server is aware that it has erred or is incapable of performing +// the request. Except when responding to a HEAD request, the server SHOULD +// include an entity containing an explanation of the error situation, and +// whether it is a temporary or permanent condition. User agents SHOULD +// display any included entity to the user. These response codes are +// applicable to any request method. + +// 500 Internal Server Error +// +// The server encountered an unexpected condition which prevented it from +// fulfilling the request. +static const int HTTP_STATUS_INTERNAL_SERVER_ERROR = 500; + +// 501 Not Implemented +// +// The server does not support the functionality required to fulfill the +// request. This is the appropriate response when the server does not +// recognize the request method and is not capable of supporting it for any +// resource. +static const int HTTP_STATUS_NOT_IMPLEMENTED = 501; + +// 502 Bad Gateway +// +// The server, while acting as a gateway or proxy, received an invalid +// response from the upstream server it accessed in attempting to fulfill +// the request. +static const int HTTP_STATUS_BAD_GATEWAY = 502; + +// 503 Service Unavailable +// +// The server is currently unable to handle the request due to a temporary +// overloading or maintenance of the server. The implication is that this is +// a temporary condition which will be alleviated after some delay. If +// known, the length of the delay MAY be indicated in a Retry-After header. +// If no Retry-After is given, the client SHOULD handle the response as it +// would for a 500 response. +static const int HTTP_STATUS_SERVICE_UNAVAILABLE = 503; + +// 504 Gateway Timeout +// +// The server, while acting as a gateway or proxy, did not receive a timely +// response from the upstream server specified by the URI (e.g. HTTP, FTP; +// LDAP) or some other auxiliary server (e.g. DNS) it needed to access in +// attempting to complete the request. +static const int HTTP_STATUS_GATEWAY_TIMEOUT = 504; + +// 505 HTTP Version Not Supported +// +// The server does not support, or refuses to support, the HTTP protocol +// version that was used in the request message. The server is indicating +// that it is unable or unwilling to complete the request using the same +// major version as the client, as described in section 3.1, other than with +// this error message. The response SHOULD contain an entity describing why +// that version is not supported and what other protocols are supported by +// that server. +static const int HTTP_STATUS_VERSION_NOT_SUPPORTED = 505; + +} // namespace brpc + + +#endif //BRPC_HTTP_STATUS_CODE_H diff --git a/src/brpc/input_message_base.h b/src/brpc/input_message_base.h new file mode 100644 index 0000000..b117eb9 --- /dev/null +++ b/src/brpc/input_message_base.h @@ -0,0 +1,69 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_INPUT_MESSAGE_BASE_H +#define BRPC_INPUT_MESSAGE_BASE_H + +#include "brpc/socket_id.h" // SocketId +#include "brpc/destroyable.h" // DestroyingPtr + + +namespace brpc { + +// Messages returned by Parse handlers must extend this class +class InputMessageBase : public Destroyable { +protected: + // Implement this method to customize deletion of this message. + virtual void DestroyImpl() = 0; + +public: + // Called to release the memory of this message instead of "delete" + void Destroy(); + + // Own the socket where this message is from. + Socket* ReleaseSocket(); + + // Get the socket where this message is from. + Socket* socket() const { return _socket.get(); } + + // Arg of the InputMessageHandler which parses this message successfully. + const void* arg() const { return _arg; } + + // [Internal] + int64_t received_us() const { return _received_us; } + int64_t base_real_us() const { return _base_real_us; } + +protected: + virtual ~InputMessageBase(); + +private: +friend class InputMessenger; +friend void* ProcessInputMessage(void*); +friend class Stream; +friend class Transport; + int64_t _received_us; + int64_t _base_real_us; + SocketUniquePtr _socket; + void (*_process)(InputMessageBase* msg); + const void* _arg; +}; + +} // namespace brpc + + +#endif // BRPC_INPUT_MESSAGE_BASE_H diff --git a/src/brpc/input_messenger.cpp b/src/brpc/input_messenger.cpp new file mode 100644 index 0000000..c249cca --- /dev/null +++ b/src/brpc/input_messenger.cpp @@ -0,0 +1,587 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/fd_guard.h" // fd_guard +#include "butil/logging.h" // CHECK +#include "butil/time.h" // cpuwide_time_us +#include "butil/fd_utility.h" // make_non_blocking +#include "bthread/bthread.h" // bthread_start_background +#include "bthread/unstable.h" // bthread_flush +#include "bvar/bvar.h" // bvar::Adder +#include "brpc/options.pb.h" // ProtocolType +#include "brpc/reloadable_flags.h" // BRPC_VALIDATE_GFLAG +#include "brpc/protocol.h" // ListProtocols +#include "brpc/rdma/rdma_endpoint.h" +#include "brpc/input_messenger.h" +#include "brpc/transport_factory.h" + +namespace brpc { + +InputMessenger* g_messenger = NULL; +static pthread_once_t g_messenger_init = PTHREAD_ONCE_INIT; +static void InitClientSideMessenger() { + g_messenger = new InputMessenger; +} +InputMessenger* get_or_new_client_side_messenger() { + pthread_once(&g_messenger_init, InitClientSideMessenger); + return g_messenger; +} + +static ProtocolType FindProtocolOfHandler(const InputMessageHandler& h); + +// NOTE: This flag was true by default before r31206. But since we have +// /connections to view all the active connections, logging closing does not +// help much to locate problems, and crashings are unlikely to be associated +// with an EOF. +DEFINE_bool(log_connection_close, false, + "Print log when remote side closes the connection"); +BRPC_VALIDATE_GFLAG(log_connection_close, PassValidate); + +DEFINE_bool(socket_keepalive, false, + "Enable keepalive of sockets if this value is true"); + +DEFINE_int32(socket_keepalive_idle_s, -1, + "Set idle time for socket keepalive in seconds if this value is positive"); + +DEFINE_int32(socket_keepalive_interval_s, -1, + "Set interval between keepalives in seconds if this value is positive"); + +DEFINE_int32(socket_keepalive_count, -1, + "Set number of keepalives before death if this value is positive"); + +DEFINE_int32(socket_tcp_user_timeout_ms, -1, + "If this value is positive, set number of milliseconds that transmitted " + "data may remain unacknowledged, or bufferred data may remain untransmitted " + "(due to zero window size) before TCP will forcibly close the corresponding " + "connection and return ETIMEDOUT to the application. Only linux supports " + "TCP_USER_TIMEOUT."); + +DECLARE_bool(usercode_in_pthread); +DECLARE_bool(usercode_in_coroutine); +DECLARE_uint64(max_body_size); + +const size_t MSG_SIZE_WINDOW = 10; // Take last so many message into stat. +const size_t MIN_ONCE_READ = 4096; +const size_t MAX_ONCE_READ = 524288; +const size_t PROTO_DUMMY_LEN = 4; + +ParseResult InputMessenger::CutInputMessage( + Socket* m, size_t* index, bool read_eof) { + const int preferred = m->preferred_index(); + const int max_index = (int)_max_index.load(butil::memory_order_acquire); + // Try preferred handler first. The preferred_index is set on last + // selection or by client. + if (preferred >= 0 && preferred <= max_index + && _handlers[preferred].parse != NULL) { + int cur_index = preferred; + do { + ParseResult result = + _handlers[cur_index].parse(&m->_read_buf, m, read_eof, _handlers[cur_index].arg); + if (result.is_ok() || + result.error() == PARSE_ERROR_NOT_ENOUGH_DATA) { + m->set_preferred_index(cur_index); + *index = cur_index; + return result; + } else if (result.error() != PARSE_ERROR_TRY_OTHERS) { + // Critical error, return directly. + LOG_IF(ERROR, result.error() == PARSE_ERROR_TOO_BIG_DATA) + << "A message from " << m->remote_side() + << "(protocol=" << _handlers[cur_index].name + << ") is bigger than " << FLAGS_max_body_size + << " bytes, the connection will be closed." + " Set max_body_size to allow bigger messages"; + return result; + } else { + if (m->_read_buf.size() >= 4) { + // The length of `data' must be PROTO_DUMMY_LEN + 1 to store extra ending char '\0' + char data[PROTO_DUMMY_LEN + 1]; + m->_read_buf.copy_to_cstr(data, PROTO_DUMMY_LEN); + if (strncmp(data, "RDMA", PROTO_DUMMY_LEN) == 0) { + // To avoid timeout when client uses RDMA but server uses TCP + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + } + } + + if (m->CreatedByConnect()) { + if((ProtocolType)cur_index == PROTOCOL_BAIDU_STD && cur_index == preferred) { + // baidu_std may fall to streaming_rpc. + cur_index = (int)PROTOCOL_STREAMING_RPC; + continue; + } else if((ProtocolType)cur_index == PROTOCOL_STREAMING_RPC && cur_index == preferred) { + // streaming_rpc may fall to baidu_std. + cur_index = (int)PROTOCOL_BAIDU_STD; + continue; + } else { + // The protocol is fixed at client-side, no need to try others. + LOG(ERROR) << "Fail to parse response from " << m->remote_side() + << " by " << _handlers[preferred].name + << " at client-side"; + return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG); + } + } else { + // Try other protocols. + break; + } + } while (true); + // Clear context before trying next protocol which probably has + // an incompatible context with the current one. + if (m->parsing_context()) { + m->reset_parsing_context(NULL); + } + m->set_preferred_index(-1); + } + for (int i = 0; i <= max_index; ++i) { + if (i == preferred || _handlers[i].parse == NULL) { + // Don't try preferred handler(already tried) or invalid handler + continue; + } + ParseResult result = _handlers[i].parse(&m->_read_buf, m, read_eof, _handlers[i].arg); + if (result.is_ok() || + result.error() == PARSE_ERROR_NOT_ENOUGH_DATA) { + m->set_preferred_index(i); + *index = i; + return result; + } else if (result.error() != PARSE_ERROR_TRY_OTHERS) { + // Critical error, return directly. + LOG_IF(ERROR, result.error() == PARSE_ERROR_TOO_BIG_DATA) + << "A message from " << m->remote_side() + << "(protocol=" << _handlers[i].name + << ") is bigger than " << FLAGS_max_body_size + << " bytes, the connection will be closed." + " Set max_body_size to allow bigger messages"; + return result; + } + // Clear context before trying next protocol which definitely has + // an incompatible context with the current one. + if (m->parsing_context()) { + m->reset_parsing_context(NULL); + } + // Try other protocols. + } + return MakeParseError(PARSE_ERROR_TRY_OTHERS); +} + +void* ProcessInputMessage(void* void_arg) { + InputMessageBase* msg = static_cast(void_arg); + msg->_process(msg); + return NULL; +} + +struct RunLastMessage { + inline void operator()(InputMessageBase* last_msg) { + ProcessInputMessage(last_msg); + } +}; + +InputMessageClosure::~InputMessageClosure() noexcept(false) { + if (_msg) { + ProcessInputMessage(_msg); + } +} + +void InputMessageClosure::reset(InputMessageBase* m) { + if (_msg) { + ProcessInputMessage(_msg); + } + _msg = m; +} + +int InputMessenger::ProcessNewMessage( + Socket* m, ssize_t bytes, bool read_eof, + const uint64_t received_us, const uint64_t base_realtime, + InputMessageClosure& last_msg) { + m->AddInputBytes(bytes); + + // Avoid this socket to be closed due to idle_timeout_s + m->_last_readtime_us.store(received_us, butil::memory_order_relaxed); + + size_t last_size = m->_read_buf.length(); + int num_bthread_created = 0; + while (1) { + size_t index = 8888; + ParseResult pr = CutInputMessage(m, &index, read_eof); + if (!pr.is_ok()) { + if (pr.error() == PARSE_ERROR_NOT_ENOUGH_DATA) { + // incomplete message, re-read. + // However, some buffer may have been consumed + // under protocols like HTTP. Record this size + m->_last_msg_size += (last_size - m->_read_buf.length()); + break; + } else if (pr.error() == PARSE_ERROR_TRY_OTHERS) { + LOG(WARNING) + << "Close " << *m << " due to unknown message: " + << butil::ToPrintable(m->_read_buf); + m->SetFailed(EINVAL, "Close %s due to unknown message", + m->description().c_str()); + return -1; + } else { + LOG(WARNING) << "Close " << *m << ": " << pr.error_str(); + m->SetFailed(EINVAL, "Close %s: %s", + m->description().c_str(), pr.error_str()); + return -1; + } + } + + m->AddInputMessages(1); + // Calculate average size of messages + const size_t cur_size = m->_read_buf.length(); + if (cur_size == 0) { + // _read_buf is consumed, it's good timing to return blocks + // cached internally back to TLS, otherwise the memory is not + // reused until next message arrives which is quite uncertain + // in situations that most connections are idle. + m->_read_buf.return_cached_blocks(); + } + m->_last_msg_size += (last_size - cur_size); + last_size = cur_size; + const size_t old_avg = m->_avg_msg_size; + if (old_avg != 0) { + m->_avg_msg_size = (old_avg * (MSG_SIZE_WINDOW - 1) + m->_last_msg_size) + / MSG_SIZE_WINDOW; + } else { + m->_avg_msg_size = m->_last_msg_size; + } + m->_last_msg_size = 0; + + if (pr.message() == NULL) { // the Process() step can be skipped. + continue; + } + pr.message()->_received_us = received_us; + pr.message()->_base_real_us = base_realtime; + + // This unique_ptr prevents msg to be lost before transfering + // ownership to last_msg + DestroyingPtr msg(pr.message()); + m->_transport->QueueMessage(last_msg, &num_bthread_created, false); + if (_handlers[index].process == NULL) { + LOG(ERROR) << "process of index=" << index << " is NULL"; + continue; + } + m->ReAddress(&msg->_socket); + m->PostponeEOF(); + msg->_process = _handlers[index].process; + msg->_arg = _handlers[index].arg; + + if (_handlers[index].verify != NULL) { + int auth_error = 0; + if (0 == m->FightAuthentication(&auth_error)) { + // Get the right to authenticate + if (_handlers[index].verify(msg.get())) { + m->SetAuthentication(0); + } else { + m->SetAuthentication(ERPCAUTH); + LOG(WARNING) << "Fail to authenticate " << *m; + m->SetFailed(ERPCAUTH, "Fail to authenticate %s", + m->description().c_str()); + return -1; + } + } else { + LOG_IF(FATAL, auth_error != 0) << + "Impossible! Socket should have been " + "destroyed when authentication failed"; + } + } + if (!m->is_read_progressive()) { + // Transfer ownership to last_msg + last_msg.reset(msg.release()); + } else { + last_msg.reset(msg.release()); + m->_transport->QueueMessage(last_msg, &num_bthread_created, false); + bthread_flush(); + num_bthread_created = 0; + } + } + // In RDMA polling mode, all messages must be executed in a new bthread and + // not in the bthread where the polling bthread is located, because the + // method for processing messages may call synchronization primitives, + // causing the polling bthread to be scheduled out. + if (m->_socket_mode == SOCKET_MODE_RDMA) { + m->_transport->QueueMessage(last_msg, &num_bthread_created, true); + } + if (num_bthread_created) { + bthread_flush(); + } + return 0; +} + +void InputMessenger::OnNewMessages(Socket* m) { + // Notes: + // - If the socket has only one message, the message will be parsed and + // processed in this bthread. nova-pbrpc and http works in this way. + // - If the socket has several messages, all messages will be parsed ( + // meaning cutting from butil::IOBuf. serializing from protobuf is part of + // "process") in this bthread. All messages except the last one will be + // processed in separate bthreads. To minimize the overhead, scheduling + // is batched(notice the BTHREAD_NOSIGNAL and bthread_flush). + // - Verify will always be called in this bthread at most once and before + // any process. + InputMessenger* messenger = static_cast(m->user()); + int progress = Socket::PROGRESS_INIT; + + // Notice that all *return* no matter successful or not will run last + // message, even if the socket is about to be closed. This should be + // OK in most cases. + InputMessageClosure last_msg; + bool read_eof = false; + while (!read_eof) { + const int64_t received_us = butil::cpuwide_time_us(); + const int64_t base_realtime = butil::gettimeofday_us() - received_us; + + // Calculate bytes to be read. + size_t once_read = m->_avg_msg_size * 16; + if (once_read < MIN_ONCE_READ) { + once_read = MIN_ONCE_READ; + } else if (once_read > MAX_ONCE_READ) { + once_read = MAX_ONCE_READ; + } + + // Read. + const ssize_t nr = m->DoRead(once_read); + if (nr <= 0) { + if (0 == nr) { + // Set `read_eof' flag and proceed to feed EOF into `Protocol' + // (implied by m->_read_buf.empty), which may produce a new + // `InputMessageBase' under some protocols such as HTTP + LOG_IF(WARNING, FLAGS_log_connection_close) << *m << " was closed by remote side"; + read_eof = true; + } else if (errno != EAGAIN) { + if (errno == EINTR) { + continue; // just retry + } + const int saved_errno = errno; + PLOG(WARNING) << "Fail to read from " << *m; + m->SetFailed(saved_errno, "Fail to read from %s: %s", + m->description().c_str(), berror(saved_errno)); + return; + } else if (!m->MoreReadEvents(&progress)) { + return; + } else { // new events during processing + continue; + } + } + + if (messenger->ProcessNewMessage(m, nr, read_eof, received_us, + base_realtime, last_msg) < 0) { + return; + } + } + + if (read_eof) { + m->SetEOF(); + } +} + +InputMessenger::InputMessenger(size_t capacity) + : _handlers(NULL) + , _max_index(-1) + , _non_protocol(false) + , _capacity(capacity) { +} + +InputMessenger::~InputMessenger() { + delete[] _handlers; + _handlers = NULL; + _max_index.store(-1, butil::memory_order_relaxed); + _capacity = 0; +} + +int InputMessenger::AddHandler(const InputMessageHandler& handler) { + if (handler.parse == NULL || handler.process == NULL + || handler.name == NULL) { + CHECK(false) << "Invalid argument"; + return -1; + } + BAIDU_SCOPED_LOCK(_add_handler_mutex); + if (NULL == _handlers) { + _handlers = new (std::nothrow) InputMessageHandler[_capacity]; + if (NULL == _handlers) { + LOG(FATAL) << "Fail to new array of InputMessageHandler"; + return -1; + } + memset(_handlers, 0, sizeof(*_handlers) * _capacity); + _non_protocol = false; + } + if (_non_protocol) { + CHECK(false) << "AddNonProtocolHandler was invoked"; + return -1; + } + ProtocolType type = FindProtocolOfHandler(handler); + if (type == PROTOCOL_UNKNOWN) { + CHECK(false) << "Adding a handler which doesn't belong to any protocol"; + return -1; + } + const int index = type; + if (index >= (int)_capacity) { + LOG(FATAL) << "Can't add more handlers than " << _capacity; + return -1; + } + if (_handlers[index].parse == NULL) { + // The same protocol might be added more than twice + _handlers[index] = handler; + } else if (_handlers[index].parse != handler.parse + || _handlers[index].process != handler.process) { + CHECK(_handlers[index].parse == handler.parse); + CHECK(_handlers[index].process == handler.process); + return -1; + } + if (index > _max_index.load(butil::memory_order_relaxed)) { + _max_index.store(index, butil::memory_order_release); + } + return 0; +} + +int InputMessenger::AddNonProtocolHandler(const InputMessageHandler& handler) { + if (handler.parse == NULL || handler.process == NULL + || handler.name == NULL) { + CHECK(false) << "Invalid argument"; + return -1; + } + BAIDU_SCOPED_LOCK(_add_handler_mutex); + if (NULL == _handlers) { + _handlers = new (std::nothrow) InputMessageHandler[_capacity]; + if (NULL == _handlers) { + LOG(FATAL) << "Fail to new array of InputMessageHandler"; + return -1; + } + memset(_handlers, 0, sizeof(*_handlers) * _capacity); + _non_protocol = true; + } + if (!_non_protocol) { + CHECK(false) << "AddHandler was invoked"; + return -1; + } + const int index = _max_index.load(butil::memory_order_relaxed) + 1; + _handlers[index] = handler; + _max_index.store(index, butil::memory_order_release); + return 0; +} + +int InputMessenger::Create(const butil::EndPoint& remote_side, + time_t health_check_interval_s, + SocketId* id) { + SocketOptions options; + options.remote_side = remote_side; + options.user = this; + options.on_edge_triggered_events = OnNewMessages; + options.health_check_interval_s = health_check_interval_s; + if (FLAGS_socket_keepalive) { + options.keepalive_options = std::make_shared(); + options.keepalive_options->keepalive_idle_s + = FLAGS_socket_keepalive_idle_s; + options.keepalive_options->keepalive_interval_s + = FLAGS_socket_keepalive_interval_s; + options.keepalive_options->keepalive_count + = FLAGS_socket_keepalive_count; + } + options.tcp_user_timeout_ms = FLAGS_socket_tcp_user_timeout_ms; + return Socket::Create(options, id); +} + +int InputMessenger::Create(SocketOptions options, SocketId* id) { + options.user = this; + options.need_on_edge_trigger = true; + // Enable keepalive by options or Gflag. + // Priority: options > Gflag. + if (options.keepalive_options || FLAGS_socket_keepalive) { + if (!options.keepalive_options) { + options.keepalive_options = std::make_shared(); + } + if (options.keepalive_options->keepalive_idle_s <= 0) { + options.keepalive_options->keepalive_idle_s + = FLAGS_socket_keepalive_idle_s; + } + if (options.keepalive_options->keepalive_interval_s <= 0) { + options.keepalive_options->keepalive_interval_s + = FLAGS_socket_keepalive_interval_s; + } + if (options.keepalive_options->keepalive_count <= 0) { + options.keepalive_options->keepalive_count + = FLAGS_socket_keepalive_count; + } + } + if (options.tcp_user_timeout_ms <= 0) { + options.tcp_user_timeout_ms = FLAGS_socket_tcp_user_timeout_ms; + } + return Socket::Create(options, id); +} + +int InputMessenger::FindProtocolIndex(const char* name) const { + for (size_t i = 0; i < _capacity; ++i) { + if (_handlers[i].parse != NULL + && strcmp(name, _handlers[i].name) == 0) { + return i; + } + } + return -1; +} + +int InputMessenger::FindProtocolIndex(ProtocolType type) const { + const Protocol* proto = FindProtocol(type); + if (NULL == proto) { + return -1; + } + return FindProtocolIndex(proto->name); + +} + +const char* InputMessenger::NameOfProtocol(int n) const { + if (n < 0 || (size_t)n >= _capacity || _handlers[n].parse == NULL) { + return "unknown"; // use lowercase to be consistent with valid names. + } + return _handlers[n].name; +} + +static ProtocolType FindProtocolOfHandler(const InputMessageHandler& h) { + std::vector > vec; + ListProtocols(&vec); + for (size_t i = 0; i < vec.size(); ++i) { + if (vec[i].second.parse == h.parse && + ((vec[i].second.process_request == h.process) + // ^^ server side + || (vec[i].second.process_response == h.process)) + // ^^ client side + && strcmp(vec[i].second.name, h.name) == 0) { + return vec[i].first; + } + } + return PROTOCOL_UNKNOWN; +} + +void InputMessageBase::Destroy() { + // Release base-specific resources. + if (_socket) { + _socket->CheckEOF(); + _socket.reset(); + } + DestroyImpl(); + // This object may be destroyed, don't touch fields anymore. +} + +Socket* InputMessageBase::ReleaseSocket() { + Socket* s = _socket.release(); + if (s) { + s->CheckEOF(); + } + return s; +} + +InputMessageBase::~InputMessageBase() {} + +} // namespace brpc diff --git a/src/brpc/input_messenger.h b/src/brpc/input_messenger.h new file mode 100644 index 0000000..8482c3f --- /dev/null +++ b/src/brpc/input_messenger.h @@ -0,0 +1,167 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_INPUT_MESSENGER_H +#define BRPC_INPUT_MESSENGER_H + +#include "butil/iobuf.h" // butil::IOBuf +#include "brpc/socket.h" // SocketId, SocketUser +#include "brpc/parse_result.h" // ParseResult +#include "brpc/input_message_base.h" // InputMessageBase + + +namespace brpc { +namespace rdma { +class RdmaEndpoint; +} +class TcpTransport; +struct InputMessageHandler { + // The callback to cut a message from `source'. + // Returned message will be passed to process_request or process_response + // later and Destroy()-ed by them. + // Returns: + // MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA): + // `source' does not form a complete message yet. + // MakeParseError(PARSE_ERROR_TRY_OTHERS). + // `source' does not fit the protocol, the data should be tried by + // other protocols. If the data is definitely corrupted (e.g. magic + // header matches but other fields are wrong), pop corrupted part + // from `source' before returning. + // MakeMessage(InputMessageBase*): + // The message is parsed successfully and cut from `source'. + typedef ParseResult (*Parse)(butil::IOBuf* source, Socket *socket, + bool read_eof, const void *arg); + Parse parse; + + // The callback to handle `msg' created by a successful parse(). + // `msg' must be Destroy()-ed when the processing is done. To make sure + // Destroy() is always called, consider using DestroyingPtr<> defined in + // destroyable.h + // May be called in a different thread from parse(). + typedef void (*Process)(InputMessageBase* msg); + Process process; + + // The callback to verify authentication of this socket. Only called + // on the first message that a socket receives. Can be NULL when + // authentication is not needed or this is the client side. + // Returns true on successful authentication. + typedef bool (*Verify)(const InputMessageBase* msg); + Verify verify; + + // An argument associated with the handler. + const void* arg; + + // Name of this handler, must be string constant. + const char* name; +}; + +class InputMessageClosure { +public: + InputMessageClosure() : _msg(NULL) { } + ~InputMessageClosure() noexcept(false); + + InputMessageBase* release() { + InputMessageBase* m = _msg; + _msg = NULL; + return m; + } + + void reset(InputMessageBase* m); + +private: + InputMessageBase* _msg; +}; + +// Process messages from connections. +// `Message' corresponds to a client's request or a server's response. +class InputMessenger : public SocketUser { +friend class Socket; +friend class TcpTransport; +friend class rdma::RdmaEndpoint; +public: + explicit InputMessenger(size_t capacity = 128); + ~InputMessenger(); + + // [thread-safe] Must be called at least once before Start(). + // `handler' contains user-supplied callbacks to cut off and + // process messages from connections. + // Returns 0 on success, -1 otherwise. + int AddHandler(const InputMessageHandler& handler); + + // [thread-safe] Create a socket to process input messages. + int Create(const butil::EndPoint& remote_side, + time_t health_check_interval_s, + SocketId* id); + // Overwrite necessary fields in `base_options' and create a socket with + // the modified options. + int Create(SocketOptions base_options, SocketId* id); + + // Returns the internal index of `InputMessageHandler' whose name=`name' + // Returns -1 when not found + int FindProtocolIndex(const char* name) const; + int FindProtocolIndex(ProtocolType type) const; + + // Get name of the n-th handler + const char* NameOfProtocol(int n) const; + + // Add a handler which doesn't belong to any registered protocol. + // Note: Invoking this method indicates that you are using Socket without + // Channel nor Server. + int AddNonProtocolHandler(const InputMessageHandler& handler); + +protected: + // Load data from m->fd() into m->read_buf, cut off new messages and + // call callbacks. + static void OnNewMessages(Socket* m); + +private: + + // Find a valid scissor from `handlers' to cut off `header' and `payload' + // from m->read_buf, save index of the scissor into `index'. + ParseResult CutInputMessage(Socket* m, size_t* index, bool read_eof); + + // Process a new message just received in OnNewMessages + // Return value >= 0 means success + int ProcessNewMessage( + Socket* m, ssize_t bytes, bool read_eof, + const uint64_t received_us, const uint64_t base_realtime, + InputMessageClosure& last_msg); + + // User-supplied scissors and handlers. + // the index of handler is exactly the same as the protocol + InputMessageHandler* _handlers; + // Max added protocol type + butil::atomic _max_index; + bool _non_protocol; + size_t _capacity; + + butil::Mutex _add_handler_mutex; +}; + +// Get the global InputMessenger at client-side. +BUTIL_FORCE_INLINE InputMessenger* get_client_side_messenger() { + extern InputMessenger* g_messenger; + return g_messenger; +} + +InputMessenger* get_or_new_client_side_messenger(); + +} // namespace brpc + + +#endif // BRPC_INPUT_MESSENGER_H diff --git a/src/brpc/interceptor.h b/src/brpc/interceptor.h new file mode 100644 index 0000000..dbd59b3 --- /dev/null +++ b/src/brpc/interceptor.h @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_INTERCEPTOR_H +#define BRPC_INTERCEPTOR_H + +#include "brpc/controller.h" + + +namespace brpc { + +class Interceptor { +public: + virtual ~Interceptor() = default; + + // Returns true if accept request, reject request otherwise. + // When server rejects request, You can fill `error_code' + // and `error_txt' which will send to client. + virtual bool Accept(const brpc::Controller* controller, + int& error_code, + std::string& error_txt) const = 0; + +}; + +} // namespace brpc + + +#endif //BRPC_INTERCEPTOR_H diff --git a/src/brpc/kvmap.h b/src/brpc/kvmap.h new file mode 100644 index 0000000..489ed2e --- /dev/null +++ b/src/brpc/kvmap.h @@ -0,0 +1,75 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_KVMAP_H +#define BRPC_KVMAP_H + +#include "butil/containers/flat_map.h" + +namespace brpc { + +// Remember Key/Values in string +class KVMap { +public: + typedef butil::FlatMap Map; + typedef Map::const_iterator Iterator; + + KVMap() {} + + // Exchange internal fields with another KVMap. + void Swap(KVMap &rhs) { _entries.swap(rhs._entries); } + + // Reset internal fields as if they're just default-constructed. + void Clear() { _entries.clear(); } + + // Get value of a key(case-sensitive) + // Return pointer to the value, NULL on not found. + const std::string* Get(const char* key) const { return _entries.seek(key); } + const std::string* Get(const std::string& key) const { + return _entries.seek(key); + } + + // Set value of a key + void Set(const std::string& key, const std::string& value) { + _entries[key] = value; + } + void Set(const std::string& key, const char* value) { _entries[key] = value; } + // Convert other types to string as well + template + void Set(const std::string& key, const T& value) { + _entries[key] = std::to_string(value); + } + + // Remove a key + void Remove(const char* key) { _entries.erase(key); } + void Remove(const std::string& key) { _entries.erase(key); } + + // Get iterators to iterate key/value + Iterator Begin() const { return _entries.begin(); } + Iterator End() const { return _entries.end(); } + + // number of key/values + size_t Count() const { return _entries.size(); } + +private: + + Map _entries; +}; + +} // namespace brpc + +#endif // BRPC_KVMAP_H diff --git a/src/brpc/load_balancer.cpp b/src/brpc/load_balancer.cpp new file mode 100644 index 0000000..2532d9e --- /dev/null +++ b/src/brpc/load_balancer.cpp @@ -0,0 +1,136 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "brpc/reloadable_flags.h" +#include "brpc/load_balancer.h" +#include "brpc/socket.h" + + +namespace brpc { + +DEFINE_bool(show_lb_in_vars, false, "Describe LoadBalancers in vars"); +DEFINE_int32(default_weight_of_wlb, 0, "Default weight value of Weighted LoadBalancer(wlb). " + "wlb policy degradation is enabled when default_weight_of_wlb > 0 to avoid some " + "problems when user is using wlb but forgot to set the weights of some of their " + "downstream instances. Then these instances will be set default_weight_of_wlb as " + "their weights. wlb policy degradation is not enabled by default."); +BRPC_VALIDATE_GFLAG(show_lb_in_vars, PassValidate); + +// For assigning unique names for lb. +static butil::static_atomic g_lb_counter = BUTIL_STATIC_ATOMIC_INIT(0); + +bool LoadBalancer::IsServerAvailable(SocketId id, SocketUniquePtr* out) { + SocketUniquePtr ptr; + bool res = Socket::Address(id, &ptr) == 0 && ptr->IsAvailable(); + if (res) { + *out = std::move(ptr); + } + return res; +} + +void SharedLoadBalancer::DescribeLB(std::ostream& os, void* arg) { + (static_cast(arg))->Describe(os, DescribeOptions()); +} + +void SharedLoadBalancer::ExposeLB() { + bool changed = false; + _st_mutex.lock(); + if (!_exposed) { + _exposed = true; + changed = true; + } + _st_mutex.unlock(); + if (changed) { + char name[32]; + snprintf(name, sizeof(name), "_load_balancer_%d", g_lb_counter.fetch_add( + 1, butil::memory_order_relaxed)); + _st.expose(name); + } +} + +SharedLoadBalancer::SharedLoadBalancer() + : _lb(NULL) + , _weight_sum(0) + , _exposed(false) + , _st(DescribeLB, this) { +} + +SharedLoadBalancer::~SharedLoadBalancer() { + _st.hide(); + if (_lb) { + _lb->Destroy(); + _lb = NULL; + } +} + +int SharedLoadBalancer::Init(const char* lb_protocol) { + std::string lb_name; + butil::StringPiece lb_params; + if (!ParseParameters(lb_protocol, &lb_name, &lb_params)) { + LOG(FATAL) << "Fail to parse this load balancer protocol '" << lb_protocol << '\''; + return -1; + } + const LoadBalancer* lb = LoadBalancerExtension()->Find(lb_name.c_str()); + if (lb == NULL) { + LOG(FATAL) << "Fail to find LoadBalancer by `" << lb_name << "'"; + return -1; + } + _lb = lb->New(lb_params); + if (_lb == NULL) { + LOG(FATAL) << "Fail to new LoadBalancer"; + return -1; + } + if (FLAGS_show_lb_in_vars && !_exposed) { + ExposeLB(); + } + return 0; +} + +void SharedLoadBalancer::Describe(std::ostream& os, + const DescribeOptions& options) { + if (_lb == NULL) { + os << "lb=NULL"; + } else { + _lb->Describe(os, options); + } +} + +bool SharedLoadBalancer::ParseParameters(const butil::StringPiece& lb_protocol, + std::string* lb_name, + butil::StringPiece* lb_params) { + lb_name->clear(); + lb_params->clear(); + if (lb_protocol.empty()) { + return false; + } + const char separator = ':'; + size_t pos = lb_protocol.find(separator); + if (pos == std::string::npos) { + lb_name->append(lb_protocol.data(), lb_protocol.size()); + } else { + lb_name->append(lb_protocol.data(), pos); + if (pos < lb_protocol.size() - sizeof(separator)) { + *lb_params = lb_protocol.substr(pos + sizeof(separator)); + } + } + + return true; +} + +} // namespace brpc diff --git a/src/brpc/load_balancer.h b/src/brpc/load_balancer.h new file mode 100644 index 0000000..2a76fa4 --- /dev/null +++ b/src/brpc/load_balancer.h @@ -0,0 +1,194 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_LOAD_BALANCER_H +#define BRPC_LOAD_BALANCER_H + +#include "bvar/passive_status.h" +#include "brpc/describable.h" +#include "brpc/destroyable.h" +#include "brpc/excluded_servers.h" // ExcludedServers +#include "brpc/shared_object.h" // SharedObject +#include "brpc/server_id.h" // ServerId +#include "brpc/extension.h" // Extension + +namespace brpc { + +class Controller; + +// Select a server from a set of servers (in form of ServerId). +class LoadBalancer : public NonConstDescribable, public Destroyable { +public: + struct SelectIn { + int64_t begin_time_us; + // Weight of different nodes could be changed. + bool changable_weights; + bool has_request_code; + uint64_t request_code; + const ExcludedServers* excluded; + }; + + struct SelectOut { + explicit SelectOut(SocketUniquePtr* ptr_in) + : ptr(ptr_in), need_feedback(false) {} + SocketUniquePtr* ptr; + bool need_feedback; + }; + + struct CallInfo { + // Exactly same with SelectIn.begin_time_us, may be different from + // controller->_begin_time_us which is beginning of the RPC. + int64_t begin_time_us; + // Remote side of the call. + SocketId server_id; + // A RPC may have multiple calls, this error may be different from + // controller->ErrorCode(); + int error_code; + // The controller for the RPC. Should NOT be saved in Feedback() + // and used after the function. + const Controller* controller; + }; + + LoadBalancer() { } + + // ==================================================================== + // All methods must be thread-safe! + // Take a look at policy/round_robin_load_balancer.cpp to see how to + // make SelectServer() low contended by using DoublyBufferedData<> + // ===================================================================== + + // Add `server' into this balancer. + // Returns true on added. + virtual bool AddServer(const ServerId& server) = 0; + + // Remove `server' from this balancer. + // Returns true iff the server was removed. + virtual bool RemoveServer(const ServerId& server) = 0; + + // Add a list of `servers' into this balancer. + // Returns number of servers added. + virtual size_t AddServersInBatch(const std::vector& servers) = 0; + + // Remove a list of `servers' from this balancer. + // Returns number of servers removed. + virtual size_t RemoveServersInBatch(const std::vector& servers) = 0; + + // Select a server and address it into `out->ptr'. + // If Feedback() should be called when the RPC is done, set + // out->need_feedback to true. + // Returns 0 on success, errno otherwise. + virtual int SelectServer(const SelectIn& in, SelectOut* out) = 0; + + // Feedback this balancer with CallInfo gathered before RPC finishes. + // This function is only called when corresponding SelectServer was + // successful and out->need_feedback was set to true. + virtual void Feedback(const CallInfo& /*info*/) { } + + // Create/destroy an instance. + // Caller is responsible for Destroy() the instance after usage. + virtual LoadBalancer* New(const butil::StringPiece& params) const = 0; + +protected: + virtual ~LoadBalancer() { } + + // Returns true and set `out' if the server is available (not failed, not logoff). + // Otherwise, returns false. + static bool IsServerAvailable(SocketId id, SocketUniquePtr* out); +}; + +DECLARE_bool(show_lb_in_vars); +DECLARE_int32(default_weight_of_wlb); + +// A intrusively shareable load balancer created from name. +class SharedLoadBalancer : public SharedObject, public NonConstDescribable { +public: + SharedLoadBalancer(); + ~SharedLoadBalancer(); + + int Init(const char* lb_name); + + int SelectServer(const LoadBalancer::SelectIn& in, + LoadBalancer::SelectOut* out) { + if (FLAGS_show_lb_in_vars && !_exposed) { + ExposeLB(); + } + return _lb->SelectServer(in, out); + } + + void Feedback(const LoadBalancer::CallInfo& info) { _lb->Feedback(info); } + + bool AddServer(const ServerId& server) { + if (_lb->AddServer(server)) { + _weight_sum.fetch_add(1, butil::memory_order_relaxed); + return true; + } + return false; + } + bool RemoveServer(const ServerId& server) { + if (_lb->RemoveServer(server)) { + _weight_sum.fetch_sub(1, butil::memory_order_relaxed); + return true; + } + return false; + } + + size_t AddServersInBatch(const std::vector& servers) { + size_t n = _lb->AddServersInBatch(servers); + if (n) { + _weight_sum.fetch_add(n, butil::memory_order_relaxed); + } + return n; + } + + size_t RemoveServersInBatch(const std::vector& servers) { + size_t n = _lb->RemoveServersInBatch(servers); + if (n) { + _weight_sum.fetch_sub(n, butil::memory_order_relaxed); + } + return n; + } + + virtual void Describe(std::ostream& os, const DescribeOptions&); + + virtual int Weight() { + return _weight_sum.load(butil::memory_order_relaxed); + } + +private: + static bool ParseParameters(const butil::StringPiece& lb_protocol, + std::string* lb_name, + butil::StringPiece* lb_params); + static void DescribeLB(std::ostream& os, void* arg); + void ExposeLB(); + + LoadBalancer* _lb; + butil::atomic _weight_sum; + volatile bool _exposed; + butil::Mutex _st_mutex; + bvar::PassiveStatus _st; +}; + +// For registering global instances. +inline Extension* LoadBalancerExtension() { + return Extension::instance(); +} + +} // namespace brpc + + +#endif // BRPC_LOAD_BALANCER_H diff --git a/src/brpc/log.h b/src/brpc/log.h new file mode 100644 index 0000000..9cd180d --- /dev/null +++ b/src/brpc/log.h @@ -0,0 +1,31 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_LOG_H +#define BRPC_LOG_H + +#include // PRId64 PRIu64 +#include "bthread/errno.h" + +#define RPC_VLOG_LEVEL 99 +#define RPC_VLOG_IS_ON VLOG_IS_ON(RPC_VLOG_LEVEL) +#define RPC_VLOG VLOG(RPC_VLOG_LEVEL) +#define RPC_VPLOG VPLOG(RPC_VLOG_LEVEL) +#define RPC_VLOG_IF(cond) VLOG_IF(RPC_VLOG_LEVEL, (cond)) +#define RPC_VPLOG_IF(cond) VPLOG_IF(RPC_VLOG_LEVEL, (cond)) + +#endif // BRPC_LOG_H diff --git a/src/brpc/memcache.cpp b/src/brpc/memcache.cpp new file mode 100644 index 0000000..c7d6f83 --- /dev/null +++ b/src/brpc/memcache.cpp @@ -0,0 +1,807 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/memcache.h" + +#include +#include "brpc/policy/memcache_binary_header.h" +#include "brpc/proto_base.pb.h" +#include "butil/logging.h" +#include "butil/macros.h" +#include "butil/string_printf.h" +#include "butil/sys_byteorder.h" + +namespace brpc { + +MemcacheRequest::MemcacheRequest() + : NonreflectableMessage() { + SharedCtor(); +} + +MemcacheRequest::MemcacheRequest(const MemcacheRequest& from) + : NonreflectableMessage(from) { + SharedCtor(); + MergeFrom(from); +} + +void MemcacheRequest::SharedCtor() { + _pipelined_count = 0; + _cached_size_ = 0; +} + +MemcacheRequest::~MemcacheRequest() { + SharedDtor(); +} + +void MemcacheRequest::SharedDtor() { +} + +void MemcacheRequest::SetCachedSize(int size) const { + _cached_size_ = size; +} + +void MemcacheRequest::Clear() { + _buf.clear(); + _pipelined_count = 0; +} + +bool MemcacheRequest::MergePartialFromCodedStream( + ::google::protobuf::io::CodedInputStream* input) { + LOG(WARNING) << "You're not supposed to parse a MemcacheRequest"; + + // simple approach just making it work. + butil::IOBuf tmp; + const void* data = NULL; + int size = 0; + while (input->GetDirectBufferPointer(&data, &size)) { + tmp.append(data, size); + input->Skip(size); + } + const butil::IOBuf saved = tmp; + int count = 0; + for (; !tmp.empty(); ++count) { + char aux_buf[sizeof(policy::MemcacheRequestHeader)]; + const policy::MemcacheRequestHeader* header = + (const policy::MemcacheRequestHeader*)tmp.fetch(aux_buf, sizeof(aux_buf)); + if (header == NULL) { + return false; + } + if (header->magic != (uint8_t)policy::MC_MAGIC_REQUEST) { + return false; + } + uint32_t total_body_length = butil::NetToHost32(header->total_body_length); + if (tmp.size() < sizeof(*header) + total_body_length) { + return false; + } + tmp.pop_front(sizeof(*header) + total_body_length); + } + _buf.append(saved); + _pipelined_count += count; + return true; +} + +void MemcacheRequest::SerializeWithCachedSizes( + ::google::protobuf::io::CodedOutputStream* output) const { + LOG(WARNING) << "You're not supposed to serialize a MemcacheRequest"; + + // simple approach just making it work. + butil::IOBufAsZeroCopyInputStream wrapper(_buf); + const void* data = NULL; + int size = 0; + while (wrapper.Next(&data, &size)) { + output->WriteRaw(data, size); + } +} + +size_t MemcacheRequest::ByteSizeLong() const { + int total_size = static_cast(_buf.size()); + _cached_size_ = total_size; + return total_size; +} + +void MemcacheRequest::MergeFrom(const MemcacheRequest& from) { + CHECK_NE(&from, this); + _buf.append(from._buf); + _pipelined_count += from._pipelined_count; +} + +bool MemcacheRequest::IsInitialized() const { + return _pipelined_count != 0; +} + +void MemcacheRequest::Swap(MemcacheRequest* other) { + if (other != this) { + _buf.swap(other->_buf); + std::swap(_pipelined_count, other->_pipelined_count); + std::swap(_cached_size_, other->_cached_size_); + } +} + +::google::protobuf::Metadata MemcacheRequest::GetMetadata() const { + ::google::protobuf::Metadata metadata{}; + metadata.descriptor = MemcacheRequestBase::descriptor(); + metadata.reflection = nullptr; + return metadata; +} + +MemcacheResponse::MemcacheResponse() + : NonreflectableMessage() { + SharedCtor(); +} + +MemcacheResponse::MemcacheResponse(const MemcacheResponse& from) + : NonreflectableMessage(from) { + SharedCtor(); + MergeFrom(from); +} + +void MemcacheResponse::SharedCtor() { + _cached_size_ = 0; +} + +MemcacheResponse::~MemcacheResponse() { + SharedDtor(); +} + +void MemcacheResponse::SharedDtor() { +} + +void MemcacheResponse::SetCachedSize(int size) const { + _cached_size_ = size; +} + +void MemcacheResponse::Clear() { +} + +bool MemcacheResponse::MergePartialFromCodedStream( + ::google::protobuf::io::CodedInputStream* input) { + LOG(WARNING) << "You're not supposed to parse a MemcacheResponse"; + + // simple approach just making it work. + const void* data = NULL; + int size = 0; + while (input->GetDirectBufferPointer(&data, &size)) { + _buf.append(data, size); + input->Skip(size); + } + return true; +} + +void MemcacheResponse::SerializeWithCachedSizes( + ::google::protobuf::io::CodedOutputStream* output) const { + LOG(WARNING) << "You're not supposed to serialize a MemcacheResponse"; + + // simple approach just making it work. + butil::IOBufAsZeroCopyInputStream wrapper(_buf); + const void* data = NULL; + int size = 0; + while (wrapper.Next(&data, &size)) { + output->WriteRaw(data, size); + } +} + +size_t MemcacheResponse::ByteSizeLong() const { + int total_size = static_cast(_buf.size()); + _cached_size_ = total_size; + return total_size; +} + +void MemcacheResponse::MergeFrom(const MemcacheResponse& from) { + CHECK_NE(&from, this); + _err = from._err; + // responses of memcached according to their binary layout, should be + // directly concatenatible. + _buf.append(from._buf); +} + +bool MemcacheResponse::IsInitialized() const { + return !_buf.empty(); +} + +void MemcacheResponse::Swap(MemcacheResponse* other) { + if (other != this) { + _buf.swap(other->_buf); + std::swap(_cached_size_, other->_cached_size_); + } +} + +::google::protobuf::Metadata MemcacheResponse::GetMetadata() const { + ::google::protobuf::Metadata metadata{}; + metadata.descriptor = MemcacheResponseBase::descriptor(); + metadata.reflection = nullptr; + return metadata; +} + +// =================================================================== + +const char* MemcacheResponse::status_str(Status st) { + switch (st) { + case STATUS_SUCCESS: + return "SUCCESS"; + case STATUS_KEY_ENOENT: + return "The key does not exist"; + case STATUS_KEY_EEXISTS: + return "The key exists"; + case STATUS_E2BIG: + return "Arg list is too long"; + case STATUS_EINVAL: + return "Invalid argument"; + case STATUS_NOT_STORED: + return "Not stored"; + case STATUS_DELTA_BADVAL: + return "Bad delta"; + case STATUS_AUTH_ERROR: + return "authentication error"; + case STATUS_AUTH_CONTINUE: + return "authentication continue"; + case STATUS_UNKNOWN_COMMAND: + return "Unknown command"; + case STATUS_ENOMEM: + return "Out of memory"; + } + return "Unknown status"; +} + +// MUST NOT have extras. +// MUST have key. +// MUST NOT have value. +bool MemcacheRequest::GetOrDelete(uint8_t command, const butil::StringPiece& key) { + const policy::MemcacheRequestHeader header = { + policy::MC_MAGIC_REQUEST, + command, + butil::HostToNet16(key.size()), + 0, + policy::MC_BINARY_RAW_BYTES, + 0, + butil::HostToNet32(key.size()), + 0, + 0 + }; + if (_buf.append(&header, sizeof(header))) { + return false; + } + if (_buf.append(key.data(), key.size())) { + return false; + } + ++_pipelined_count; + return true; +} + +bool MemcacheRequest::Get(const butil::StringPiece& key) { + return GetOrDelete(policy::MC_BINARY_GET, key); +} + +bool MemcacheRequest::Delete(const butil::StringPiece& key) { + return GetOrDelete(policy::MC_BINARY_DELETE, key); +} + +struct FlushHeaderWithExtras { + policy::MemcacheRequestHeader header; + uint32_t exptime; +} __attribute__((packed)); +BAIDU_CASSERT(sizeof(FlushHeaderWithExtras) == 28, must_match); + +// MAY have extras. +// MUST NOT have key. +// MUST NOT have value. +// Extra data for flush: +// Byte/ 0 | 1 | 2 | 3 | +// / | | | | +// |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +// +---------------+---------------+---------------+---------------+ +// 0| Expiration | +// +---------------+---------------+---------------+---------------+ +// Total 4 bytes +bool MemcacheRequest::Flush(uint32_t timeout) { + const uint8_t FLUSH_EXTRAS = (timeout == 0 ? 0 : 4); + FlushHeaderWithExtras header_with_extras = {{ + policy::MC_MAGIC_REQUEST, + policy::MC_BINARY_FLUSH, + 0, + FLUSH_EXTRAS, + policy::MC_BINARY_RAW_BYTES, + 0, + butil::HostToNet32(FLUSH_EXTRAS), + 0, + 0 }, butil::HostToNet32(timeout) }; + if (FLUSH_EXTRAS == 0) { + if (_buf.append(&header_with_extras.header, + sizeof(policy::MemcacheRequestHeader))) { + return false; + } + } else { + if (_buf.append(&header_with_extras, sizeof(header_with_extras))) { + return false; + } + } + ++_pipelined_count; + return true; +} + +// (if found): +// MUST have extras. +// MAY have key. +// MAY have value. +// Extra data for the get commands: +// Byte/ 0 | 1 | 2 | 3 | +// / | | | | +// |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +// +---------------+---------------+---------------+---------------+ +// 0| Flags | +// +---------------+---------------+---------------+---------------+ +// Total 4 bytes +bool MemcacheResponse::PopGet( + butil::IOBuf* value, uint32_t* flags, uint64_t* cas_value) { + const size_t n = _buf.size(); + policy::MemcacheResponseHeader header; + if (n < sizeof(header)) { + butil::string_printf(&_err, "buffer is too small to contain a header"); + return false; + } + _buf.copy_to(&header, sizeof(header)); + if (header.command != (uint8_t)policy::MC_BINARY_GET) { + butil::string_printf(&_err, "not a GET response"); + return false; + } + if (n < sizeof(header) + header.total_body_length) { + butil::string_printf(&_err, "response=%u < header=%u + body=%u", + (unsigned)n, (unsigned)sizeof(header), header.total_body_length); + return false; + } + if (header.status != (uint16_t)STATUS_SUCCESS) { + LOG_IF(ERROR, header.extras_length != 0) << "GET response must not have flags"; + LOG_IF(ERROR, header.key_length != 0) << "GET response must not have key"; + const int value_size = (int)header.total_body_length - (int)header.extras_length + - (int)header.key_length; + if (value_size < 0) { + butil::string_printf(&_err, "value_size=%d is non-negative", value_size); + return false; + } + _buf.pop_front(sizeof(header) + header.extras_length + + header.key_length); + _err.clear(); + _buf.cutn(&_err, value_size); + return false; + } + if (header.extras_length != 4u) { + butil::string_printf(&_err, "GET response must have flags as extras, actual length=%u", + header.extras_length); + return false; + } + if (header.key_length != 0) { + butil::string_printf(&_err, "GET response must not have key"); + return false; + } + const int value_size = (int)header.total_body_length - (int)header.extras_length + - (int)header.key_length; + if (value_size < 0) { + butil::string_printf(&_err, "value_size=%d is non-negative", value_size); + return false; + } + _buf.pop_front(sizeof(header)); + uint32_t raw_flags = 0; + _buf.cutn(&raw_flags, sizeof(raw_flags)); + if (flags) { + *flags = butil::NetToHost32(raw_flags); + } + if (value) { + value->clear(); + _buf.cutn(value, value_size); + } + if (cas_value) { + *cas_value = header.cas_value; + } + _err.clear(); + return true; +} + +bool MemcacheResponse::PopGet( + std::string* value, uint32_t* flags, uint64_t* cas_value) { + butil::IOBuf tmp; + if (PopGet(&tmp, flags, cas_value)) { + tmp.copy_to(value); + return true; + } + return false; +} + +// MUST NOT have extras +// MUST NOT have key +// MUST NOT have value +bool MemcacheResponse::PopDelete() { + return PopStore(policy::MC_BINARY_DELETE, NULL); +} +bool MemcacheResponse::PopFlush() { + return PopStore(policy::MC_BINARY_FLUSH, NULL); +} + +struct StoreHeaderWithExtras { + policy::MemcacheRequestHeader header; + uint32_t flags; + uint32_t exptime; +} __attribute__((packed)); +BAIDU_CASSERT(sizeof(StoreHeaderWithExtras) == 32, must_match); +const size_t STORE_EXTRAS = sizeof(StoreHeaderWithExtras) - + sizeof(policy::MemcacheRequestHeader); +// MUST have extras. +// MUST have key. +// MAY have value. +// Extra data for set/add/replace: +// Byte/ 0 | 1 | 2 | 3 | +// / | | | | +// |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +// +---------------+---------------+---------------+---------------+ +// 0| Flags | +// +---------------+---------------+---------------+---------------+ +// 4| Expiration | +// +---------------+---------------+---------------+---------------+ +// Total 8 bytes +bool MemcacheRequest::Store( + uint8_t command, const butil::StringPiece& key, const butil::StringPiece& value, + uint32_t flags, uint32_t exptime, uint64_t cas_value) { + StoreHeaderWithExtras header_with_extras = {{ + policy::MC_MAGIC_REQUEST, + command, + butil::HostToNet16(key.size()), + STORE_EXTRAS, + policy::MC_BINARY_RAW_BYTES, + 0, + butil::HostToNet32(STORE_EXTRAS + key.size() + value.size()), + 0, + butil::HostToNet64(cas_value) + }, butil::HostToNet32(flags), butil::HostToNet32(exptime)}; + if (_buf.append(&header_with_extras, sizeof(header_with_extras))) { + return false; + } + if (_buf.append(key.data(), key.size())) { + return false; + } + if (_buf.append(value.data(), value.size())) { + return false; + } + ++_pipelined_count; + return true; +} + +// MUST have CAS +// MUST NOT have extras +// MUST NOT have key +// MUST NOT have value +bool MemcacheResponse::PopStore(uint8_t command, uint64_t* cas_value) { + const size_t n = _buf.size(); + policy::MemcacheResponseHeader header; + if (n < sizeof(header)) { + butil::string_printf(&_err, "buffer is too small to contain a header"); + return false; + } + _buf.copy_to(&header, sizeof(header)); + if (header.command != command) { + butil::string_printf(&_err, "Not a STORE response"); + return false; + } + if (n < sizeof(header) + header.total_body_length) { + butil::string_printf(&_err, "Not enough data"); + return false; + } + LOG_IF(ERROR, header.extras_length != 0) << "STORE response must not have flags"; + LOG_IF(ERROR, header.key_length != 0) << "STORE response must not have key"; + int value_size = (int)header.total_body_length - (int)header.extras_length + - (int)header.key_length; + if (header.status != (uint16_t)STATUS_SUCCESS) { + _buf.pop_front(sizeof(header) + header.extras_length + header.key_length); + _err.clear(); + _buf.cutn(&_err, value_size); + return false; + } + LOG_IF(ERROR, value_size != 0) << "STORE response must not have value, actually=" + << value_size; + _buf.pop_front(sizeof(header) + header.total_body_length); + if (cas_value) { + CHECK(header.cas_value); + *cas_value = header.cas_value; + } + _err.clear(); + return true; +} + +bool MemcacheRequest::Set( + const butil::StringPiece& key, const butil::StringPiece& value, + uint32_t flags, uint32_t exptime, uint64_t cas_value) { + return Store(policy::MC_BINARY_SET, key, value, flags, exptime, cas_value); +} + +bool MemcacheRequest::Add( + const butil::StringPiece& key, const butil::StringPiece& value, + uint32_t flags, uint32_t exptime, uint64_t cas_value) { + return Store(policy::MC_BINARY_ADD, key, value, flags, exptime, cas_value); +} + +bool MemcacheRequest::Replace( + const butil::StringPiece& key, const butil::StringPiece& value, + uint32_t flags, uint32_t exptime, uint64_t cas_value) { + return Store(policy::MC_BINARY_REPLACE, key, value, flags, exptime, cas_value); +} + +bool MemcacheRequest::Append( + const butil::StringPiece& key, const butil::StringPiece& value, + uint32_t flags, uint32_t exptime, uint64_t cas_value) { + if (value.empty()) { + LOG(ERROR) << "value to append must be non-empty"; + return false; + } + return Store(policy::MC_BINARY_APPEND, key, value, flags, exptime, cas_value); +} + +bool MemcacheRequest::Prepend( + const butil::StringPiece& key, const butil::StringPiece& value, + uint32_t flags, uint32_t exptime, uint64_t cas_value) { + if (value.empty()) { + LOG(ERROR) << "value to prepend must be non-empty"; + return false; + } + return Store(policy::MC_BINARY_PREPEND, key, value, flags, exptime, cas_value); +} + +bool MemcacheResponse::PopSet(uint64_t* cas_value) { + return PopStore(policy::MC_BINARY_SET, cas_value); +} +bool MemcacheResponse::PopAdd(uint64_t* cas_value) { + return PopStore(policy::MC_BINARY_ADD, cas_value); +} +bool MemcacheResponse::PopReplace(uint64_t* cas_value) { + return PopStore(policy::MC_BINARY_REPLACE, cas_value); +} +bool MemcacheResponse::PopAppend(uint64_t* cas_value) { + return PopStore(policy::MC_BINARY_APPEND, cas_value); +} +bool MemcacheResponse::PopPrepend(uint64_t* cas_value) { + return PopStore(policy::MC_BINARY_PREPEND, cas_value); +} + +struct IncrHeaderWithExtras { + policy::MemcacheRequestHeader header; + uint64_t delta; + uint64_t initial_value; + uint32_t exptime; +} __attribute__((packed)); +BAIDU_CASSERT(sizeof(IncrHeaderWithExtras) == 44, must_match); + +const size_t INCR_EXTRAS = sizeof(IncrHeaderWithExtras) - + sizeof(policy::MemcacheRequestHeader); + +// MUST have extras. +// MUST have key. +// MUST NOT have value. +// Extra data for incr/decr: +// Byte/ 0 | 1 | 2 | 3 | +// / | | | | +// |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +// +---------------+---------------+---------------+---------------+ +// 0| Delta to add / subtract | +// | | +// +---------------+---------------+---------------+---------------+ +// 8| Initial value | +// | | +// +---------------+---------------+---------------+---------------+ +// 16| Expiration | +// +---------------+---------------+---------------+---------------+ +// Total 20 bytes +bool MemcacheRequest::Counter( + uint8_t command, const butil::StringPiece& key, uint64_t delta, + uint64_t initial_value, uint32_t exptime) { + IncrHeaderWithExtras header_with_extras = {{ + policy::MC_MAGIC_REQUEST, + command, + butil::HostToNet16(key.size()), + INCR_EXTRAS, + policy::MC_BINARY_RAW_BYTES, + 0, + butil::HostToNet32(INCR_EXTRAS + key.size()), + 0, + 0 }, butil::HostToNet64(delta), butil::HostToNet64(initial_value), butil::HostToNet32(exptime) }; + if (_buf.append(&header_with_extras, sizeof(header_with_extras))) { + return false; + } + if (_buf.append(key.data(), key.size())) { + return false; + } + ++_pipelined_count; + return true; +} + +bool MemcacheRequest::Increment(const butil::StringPiece& key, uint64_t delta, + uint64_t initial_value, uint32_t exptime) { + return Counter(policy::MC_BINARY_INCREMENT, key, delta, initial_value, exptime); +} + +bool MemcacheRequest::Decrement(const butil::StringPiece& key, uint64_t delta, + uint64_t initial_value, uint32_t exptime) { + return Counter(policy::MC_BINARY_DECREMENT, key, delta, initial_value, exptime); +} + +// MUST NOT have extras. +// MUST NOT have key. +// MUST have value. +// Byte/ 0 | 1 | 2 | 3 | +// / | | | | +// |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +// +---------------+---------------+---------------+---------------+ +// 0| 64-bit unsigned response. | +// | | +// +---------------+---------------+---------------+---------------+ +// Total 8 bytes +bool MemcacheResponse::PopCounter( + uint8_t command, uint64_t* new_value, uint64_t* cas_value) { + const size_t n = _buf.size(); + policy::MemcacheResponseHeader header; + if (n < sizeof(header)) { + butil::string_printf(&_err, "buffer is too small to contain a header"); + return false; + } + _buf.copy_to(&header, sizeof(header)); + if (header.command != command) { + butil::string_printf(&_err, "not a INCR/DECR response"); + return false; + } + if (n < sizeof(header) + header.total_body_length) { + butil::string_printf(&_err, "response=%u < header=%u + body=%u", + (unsigned)n, (unsigned)sizeof(header), header.total_body_length); + return false; + } + LOG_IF(ERROR, header.extras_length != 0) << "INCR/DECR response must not have flags"; + LOG_IF(ERROR, header.key_length != 0) << "INCR/DECR response must not have key"; + const int value_size = (int)header.total_body_length - (int)header.extras_length + - (int)header.key_length; + _buf.pop_front(sizeof(header) + header.extras_length + header.key_length); + + if (header.status != (uint16_t)STATUS_SUCCESS) { + if (value_size < 0) { + butil::string_printf(&_err, "value_size=%d is negative", value_size); + } else { + _err.clear(); + _buf.cutn(&_err, value_size); + } + return false; + } + if (value_size != 8) { + butil::string_printf(&_err, "value_size=%d is not 8", value_size); + return false; + } + uint64_t raw_value = 0; + _buf.cutn(&raw_value, sizeof(raw_value)); + *new_value = butil::NetToHost64(raw_value); + if (cas_value) { + *cas_value = header.cas_value; + } + _err.clear(); + return true; +} + +bool MemcacheResponse::PopIncrement(uint64_t* new_value, uint64_t* cas_value) { + return PopCounter(policy::MC_BINARY_INCREMENT, new_value, cas_value); +} +bool MemcacheResponse::PopDecrement(uint64_t* new_value, uint64_t* cas_value) { + return PopCounter(policy::MC_BINARY_DECREMENT, new_value, cas_value); +} + +// MUST have extras. +// MUST have key. +// MUST NOT have value. +struct TouchHeaderWithExtras { + policy::MemcacheRequestHeader header; + uint32_t exptime; +} __attribute__((packed)); +BAIDU_CASSERT(sizeof(TouchHeaderWithExtras) == 28, must_match); +const size_t TOUCH_EXTRAS = sizeof(TouchHeaderWithExtras) - sizeof(policy::MemcacheRequestHeader); + +// MAY have extras. +// MUST NOT have key. +// MUST NOT have value. +// Extra data for touch: +// Byte/ 0 | 1 | 2 | 3 | +// / | | | | +// |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| +// +---------------+---------------+---------------+---------------+ +// 0| Expiration | +// +---------------+---------------+---------------+---------------+ +// Total 4 bytes +bool MemcacheRequest::Touch(const butil::StringPiece& key, uint32_t exptime) { + TouchHeaderWithExtras header_with_extras = {{ + policy::MC_MAGIC_REQUEST, + policy::MC_BINARY_TOUCH, + butil::HostToNet16(key.size()), + TOUCH_EXTRAS, + policy::MC_BINARY_RAW_BYTES, + 0, + butil::HostToNet32(TOUCH_EXTRAS + key.size()), + 0, + 0 }, butil::HostToNet32(exptime) }; + if (_buf.append(&header_with_extras, sizeof(header_with_extras))) { + return false; + } + if (_buf.append(key.data(), key.size())) { + return false; + } + ++_pipelined_count; + return true; +} + +// MUST NOT have extras. +// MUST NOT have key. +// MUST NOT have value. +bool MemcacheRequest::Version() { + const policy::MemcacheRequestHeader header = { + policy::MC_MAGIC_REQUEST, + policy::MC_BINARY_VERSION, + 0, + 0, + policy::MC_BINARY_RAW_BYTES, + 0, + 0, + 0, + 0 + }; + if (_buf.append(&header, sizeof(header))) { + return false; + } + ++_pipelined_count; + return true; +} + +// MUST NOT have extras. +// MUST NOT have key. +// MUST have value. +bool MemcacheResponse::PopVersion(std::string* version) { + const size_t n = _buf.size(); + policy::MemcacheResponseHeader header; + if (n < sizeof(header)) { + butil::string_printf(&_err, "buffer is too small to contain a header"); + return false; + } + _buf.copy_to(&header, sizeof(header)); + if (header.command != policy::MC_BINARY_VERSION) { + butil::string_printf(&_err, "not a VERSION response"); + return false; + } + if (n < sizeof(header) + header.total_body_length) { + butil::string_printf(&_err, "response=%u < header=%u + body=%u", + (unsigned)n, (unsigned)sizeof(header), header.total_body_length); + return false; + } + LOG_IF(ERROR, header.extras_length != 0) << "VERSION response must not have flags"; + LOG_IF(ERROR, header.key_length != 0) << "VERSION response must not have key"; + const int value_size = (int)header.total_body_length - (int)header.extras_length + - (int)header.key_length; + _buf.pop_front(sizeof(header) + header.extras_length + header.key_length); + if (value_size < 0) { + butil::string_printf(&_err, "value_size=%d is negative", value_size); + return false; + } + if (header.status != (uint16_t)STATUS_SUCCESS) { + _err.clear(); + _buf.cutn(&_err, value_size); + return false; + } + if (version) { + version->clear(); + _buf.cutn(version, value_size); + } + _err.clear(); + return true; +} + +} // namespace brpc diff --git a/src/brpc/memcache.h b/src/brpc/memcache.h new file mode 100644 index 0000000..daedd49 --- /dev/null +++ b/src/brpc/memcache.h @@ -0,0 +1,223 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_MEMCACHE_H +#define BRPC_MEMCACHE_H + +#include + +#include "butil/iobuf.h" +#include "butil/strings/string_piece.h" +#include "brpc/nonreflectable_message.h" +#include "brpc/pb_compat.h" + +namespace brpc { + +// Request to memcache. +// Notice that you can pipeline multiple operations in one request and sent +// them to memcached server together. +// Example: +// MemcacheRequest request; +// request.get("my_key1"); +// request.get("my_key2"); +// request.set("my_key3", "some_value", 0, 10); +// ... +// MemcacheResponse response; +// // 2 GET and 1 SET are sent to the server together. +// channel.CallMethod(&controller, &request, &response, NULL/*done*/); +class MemcacheRequest : public NonreflectableMessage { +public: + MemcacheRequest(); + ~MemcacheRequest() override; + MemcacheRequest(const MemcacheRequest& from); + inline MemcacheRequest& operator=(const MemcacheRequest& from) { + CopyFrom(from); + return *this; + } + void Swap(MemcacheRequest* other); + + bool Get(const butil::StringPiece& key); + + // If the cas_value(Data Version Check) is non-zero, the requested operation + // MUST only succeed if the item exists and has a cas_value identical to the + // provided value. + bool Set(const butil::StringPiece& key, const butil::StringPiece& value, + uint32_t flags, uint32_t exptime, uint64_t cas_value); + + bool Add(const butil::StringPiece& key, const butil::StringPiece& value, + uint32_t flags, uint32_t exptime, uint64_t cas_value); + + bool Replace(const butil::StringPiece& key, const butil::StringPiece& value, + uint32_t flags, uint32_t exptime, uint64_t cas_value); + + bool Append(const butil::StringPiece& key, const butil::StringPiece& value, + uint32_t flags, uint32_t exptime, uint64_t cas_value); + + bool Prepend(const butil::StringPiece& key, const butil::StringPiece& value, + uint32_t flags, uint32_t exptime, uint64_t cas_value); + + bool Delete(const butil::StringPiece& key); + bool Flush(uint32_t timeout); + + bool Increment(const butil::StringPiece& key, uint64_t delta, + uint64_t initial_value, uint32_t exptime); + bool Decrement(const butil::StringPiece& key, uint64_t delta, + uint64_t initial_value, uint32_t exptime); + + bool Touch(const butil::StringPiece& key, uint32_t exptime); + + bool Version(); + + int pipelined_count() const { return _pipelined_count; } + + butil::IOBuf& raw_buffer() { return _buf; } + const butil::IOBuf& raw_buffer() const { return _buf; } + +public: + // Protobuf methods. + void MergeFrom(const MemcacheRequest& from) override; + void Clear() override; + bool IsInitialized() const PB_527_OVERRIDE; + + size_t ByteSizeLong() const override; + bool MergePartialFromCodedStream( + ::google::protobuf::io::CodedInputStream* input) PB_310_OVERRIDE; + void SerializeWithCachedSizes( + ::google::protobuf::io::CodedOutputStream* output) const PB_310_OVERRIDE; + int GetCachedSize() const PB_425_OVERRIDE { return _cached_size_; } + + ::google::protobuf::Metadata GetMetadata() const PB_527_OVERRIDE; + +private: + bool GetOrDelete(uint8_t command, const butil::StringPiece& key); + bool Counter(uint8_t command, const butil::StringPiece& key, uint64_t delta, + uint64_t initial_value, uint32_t exptime); + + bool Store(uint8_t command, const butil::StringPiece& key, + const butil::StringPiece& value, + uint32_t flags, uint32_t exptime, uint64_t cas_value); + + void SharedCtor(); + void SharedDtor(); + void SetCachedSize(int size) const PB_425_OVERRIDE; + + int _pipelined_count; + butil::IOBuf _buf; + mutable int _cached_size_; +}; + +// Response from Memcache. +// Notice that a MemcacheResponse instance may contain multiple operations +// due to pipelining. You can call pop_xxx according to your calling sequence +// of operations in corresponding MemcacheRequest. +// Example: +// MemcacheResponse response; +// channel.CallMethod(&controller, &request, &response, NULL/*done*/); +// ... +// if (!response.PopGet(&my_value1, &flags1, &cas1)) { +// LOG(FATAL) << "Fail to pop GET: " << response.LastError(); +// } else { +// // Use my_value1, flags1, cas1 +// } +// if (!response.PopGet(&my_value2, &flags2, &cas2)) { +// LOG(FATAL) << "Fail to pop GET: " << response.LastError(); +// } else { +// // Use my_value2, flags2, cas2 +// } +// if (!response.PopSet(&cas3)) { +// LOG(FATAL) << "Fail to pop SET: " << response.LastError(); +// } else { +// // the SET was successful. +// } +class MemcacheResponse : public NonreflectableMessage { +public: + // Definition of the valid response status numbers. + // See section 3.2 Response Status + enum Status { + STATUS_SUCCESS = 0x00, + STATUS_KEY_ENOENT = 0x01, + STATUS_KEY_EEXISTS = 0x02, + STATUS_E2BIG = 0x03, + STATUS_EINVAL = 0x04, + STATUS_NOT_STORED = 0x05, + STATUS_DELTA_BADVAL = 0x06, + STATUS_AUTH_ERROR = 0x20, + STATUS_AUTH_CONTINUE = 0x21, + STATUS_UNKNOWN_COMMAND = 0x81, + STATUS_ENOMEM = 0x82 + }; + + MemcacheResponse(); + ~MemcacheResponse() override; + MemcacheResponse(const MemcacheResponse& from); + inline MemcacheResponse& operator=(const MemcacheResponse& from) { + CopyFrom(from); + return *this; + } + void Swap(MemcacheResponse* other); + + const std::string& LastError() const { return _err; } + + bool PopGet(butil::IOBuf* value, uint32_t* flags, uint64_t* cas_value); + bool PopGet(std::string* value, uint32_t* flags, uint64_t* cas_value); + bool PopSet(uint64_t* cas_value); + bool PopAdd(uint64_t* cas_value); + bool PopReplace(uint64_t* cas_value); + bool PopAppend(uint64_t* cas_value); + bool PopPrepend(uint64_t* cas_value); + bool PopDelete(); + bool PopFlush(); + bool PopIncrement(uint64_t* new_value, uint64_t* cas_value); + bool PopDecrement(uint64_t* new_value, uint64_t* cas_value); + bool PopTouch(); + bool PopVersion(std::string* version); + butil::IOBuf& raw_buffer() { return _buf; } + const butil::IOBuf& raw_buffer() const { return _buf; } + static const char* status_str(Status); + +public: + // implements Message ---------------------------------------------- + void MergeFrom(const MemcacheResponse& from) override; + void Clear() override; + bool IsInitialized() const PB_527_OVERRIDE; + + size_t ByteSizeLong() const override; + bool MergePartialFromCodedStream( + ::google::protobuf::io::CodedInputStream* input) PB_310_OVERRIDE; + void SerializeWithCachedSizes( + ::google::protobuf::io::CodedOutputStream* output) const PB_310_OVERRIDE; + int GetCachedSize() const PB_425_OVERRIDE { return _cached_size_; } + + ::google::protobuf::Metadata GetMetadata() const PB_527_OVERRIDE; + +private: + bool PopCounter(uint8_t command, uint64_t* new_value, uint64_t* cas_value); + bool PopStore(uint8_t command, uint64_t* cas_value); + + void SharedCtor(); + void SharedDtor(); + void SetCachedSize(int size) const PB_425_OVERRIDE; + + std::string _err; + butil::IOBuf _buf; + mutable int _cached_size_; +}; + +} // namespace brpc + +#endif // BRPC_MEMCACHE_H diff --git a/src/brpc/mongo_head.h b/src/brpc/mongo_head.h new file mode 100644 index 0000000..b9da017 --- /dev/null +++ b/src/brpc/mongo_head.h @@ -0,0 +1,78 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_MONGO_HEAD_H +#define BRPC_MONGO_HEAD_H + +#include "butil/sys_byteorder.h" + + +namespace brpc { + +// Sync with +// https://github.com/mongodb/mongo-c-driver/blob/master/src/mongoc/mongoc-opcode.h +// https://docs.mongodb.org/manual/reference/mongodb-wire-protocol/#request-opcodes +enum MongoOpCode { + MONGO_OPCODE_REPLY = 1, + MONGO_OPCODE_MSG = 1000, + MONGO_OPCODE_UPDATE = 2001, + MONGO_OPCODE_INSERT = 2002, + MONGO_OPCODE_QUERY = 2004, + MONGO_OPCODE_GET_MORE = 2005, + MONGO_OPCODE_DELETE = 2006, + MONGO_OPCODE_KILL_CURSORS = 2007, +}; + +inline bool is_mongo_opcode(int32_t op_code) { + switch (op_code) { + case MONGO_OPCODE_REPLY: return true; + case MONGO_OPCODE_MSG: return true; + case MONGO_OPCODE_UPDATE: return true; + case MONGO_OPCODE_INSERT: return true; + case MONGO_OPCODE_QUERY: return true; + case MONGO_OPCODE_GET_MORE: return true; + case MONGO_OPCODE_DELETE: return true; + case MONGO_OPCODE_KILL_CURSORS : return true; + } + return false; +} + +// All data of mongo protocol is little-endian. +// https://docs.mongodb.org/manual/reference/mongodb-wire-protocol/#byte-ordering +#pragma pack(1) +struct mongo_head_t { + int32_t message_length; // total message size, including this + int32_t request_id; // identifier for this message + int32_t response_to; // requestID from the original request + // (used in responses from db) + int32_t op_code; // request type, see MongoOpCode. + + void make_host_endian() { + if (!ARCH_CPU_LITTLE_ENDIAN) { + message_length = butil::ByteSwap((uint32_t)message_length); + request_id = butil::ByteSwap((uint32_t)request_id); + response_to = butil::ByteSwap((uint32_t)response_to); + op_code = butil::ByteSwap((uint32_t)op_code); + } + } +}; +#pragma pack() + +} // namespace brpc + + +#endif // BRPC_MONGO_HEAD_H diff --git a/src/brpc/mongo_service_adaptor.h b/src/brpc/mongo_service_adaptor.h new file mode 100644 index 0000000..d050fb6 --- /dev/null +++ b/src/brpc/mongo_service_adaptor.h @@ -0,0 +1,63 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_MONGO_SERVICE_ADAPTOR_H +#define BRPC_MONGO_SERVICE_ADAPTOR_H + +#include "butil/iobuf.h" +#include "brpc/input_message_base.h" +#include "brpc/shared_object.h" + + +namespace brpc { + +// custom mongo context. derive this and implement your own functionalities. +class MongoContext : public SharedObject { +public: + virtual ~MongoContext() {} +}; + +// a container of custom mongo context. created by ParseMongoRequest when the first msg comes over +// a socket. it lives as long as the socket. +class MongoContextMessage : public InputMessageBase { +public: + MongoContextMessage(MongoContext *context) : _context(context) {} + // @InputMessageBase + void DestroyImpl() { delete this; } + MongoContext* context() { return _context.get(); } + +private: + butil::intrusive_ptr _context; +}; + +class MongoServiceAdaptor { +public: + virtual ~MongoServiceAdaptor() = default; + + // Make an error msg when the cntl fails. If cntl fails, we must send mongo client a msg not + // only to indicate the error, but also to finish the round trip. + virtual void SerializeError(int response_to, butil::IOBuf* out_buf) const = 0; + + // Create a custom context which is attached to socket. This func is called only when the first + // msg from the socket comes. The context will be destroyed when the socket is closed. + virtual MongoContext* CreateSocketContext() const = 0; +}; + +} // namespace brpc + + +#endif diff --git a/src/brpc/naming_service.h b/src/brpc/naming_service.h new file mode 100644 index 0000000..1298bc9 --- /dev/null +++ b/src/brpc/naming_service.h @@ -0,0 +1,77 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_NAMING_SERVICE_H +#define BRPC_NAMING_SERVICE_H + +#include // std::vector +#include // std::string +#include // std::ostream +#include "butil/endpoint.h" // butil::EndPoint +#include "butil/macros.h" // BAIDU_CONCAT +#include "brpc/describable.h" +#include "brpc/destroyable.h" +#include "brpc/extension.h" // Extension +#include "brpc/server_node.h" // ServerNode + +namespace brpc { + +// Continuing actions to added/removed servers. +// NOTE: You don't have to implement this class. +class NamingServiceActions { +public: + virtual ~NamingServiceActions() {} + virtual void AddServers(const std::vector& servers) = 0; + virtual void RemoveServers(const std::vector& servers) = 0; + virtual void ResetServers(const std::vector& servers) = 0; +}; + +// Mapping a name to ServerNodes. +class NamingService : public Describable, public Destroyable { +public: + // Implement this method to get servers associated with `service_name' + // in periodic or event-driven manner, call methods of `actions' to + // tell RPC system about server changes. This method will be run in + // a dedicated bthread without access from other threads, thus the + // implementation does NOT need to be thread-safe. + // Return 0 on success, error code otherwise. + virtual int RunNamingService(const char* service_name, + NamingServiceActions* actions) = 0; + + // If this method returns true, RunNamingService will be called without + // a dedicated bthread. As the name implies, this is suitable for static + // and simple impl, saving the cost of creating a bthread. However most + // impl of RunNamingService never quit, thread is a must to prevent the + // method from blocking the caller. + virtual bool RunNamingServiceReturnsQuickly() { return false; } + + // Create/destroy an instance. + // Caller is responsible for Destroy() the instance after usage. + virtual NamingService* New() const = 0; + +protected: + virtual ~NamingService() {} +}; + +inline Extension* NamingServiceExtension() { + return Extension::instance(); +} + +} // namespace brpc + +#endif // BRPC_NAMING_SERVICE_H diff --git a/src/brpc/naming_service_filter.h b/src/brpc/naming_service_filter.h new file mode 100644 index 0000000..37f6801 --- /dev/null +++ b/src/brpc/naming_service_filter.h @@ -0,0 +1,40 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_NAMING_SERVICE_FILTER_H +#define BRPC_NAMING_SERVICE_FILTER_H + +#include "brpc/naming_service.h" // ServerNode + + +namespace brpc { + +class NamingServiceFilter { +public: + virtual ~NamingServiceFilter() {} + + // Return true to take this `server' as a candidate to issue RPC + // Return false to filter it out + virtual bool Accept(const ServerNode& server) const = 0; +}; + +} // namespace brpc + + + +#endif // BRPC_NAMING_SERVICE_FILTER_H diff --git a/src/brpc/nonreflectable_message.h b/src/brpc/nonreflectable_message.h new file mode 100644 index 0000000..089b239 --- /dev/null +++ b/src/brpc/nonreflectable_message.h @@ -0,0 +1,310 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_NONREFLECTABLE_MESSAGE_H +#define BRPC_NONREFLECTABLE_MESSAGE_H + +#include +#include + +#include "brpc/pb_compat.h" + +namespace brpc { + +// +// In bRPC, some non-Protobuf based protocol messages are also designed to implement +// Protobuf Message interfaces, to provide a unified protocol message. +// The API of Protobuf Message changes frequently, and these non-Protobuf based protocol +// messages do not rely on the reflection functionality of Protobuf. +// +// NonreflectableMessage is designed to isolate upstream API changes and +// provides basic implementations to simplify the adaptation process. +// +// Function implementations are kept order with the upstream, +// and use only #if version_check #endif, to make maintenance easier. +// +template +class NonreflectableMessage : public ::google::protobuf::Message { +public: + inline NonreflectableMessage() = default; + inline NonreflectableMessage(const NonreflectableMessage&) : NonreflectableMessage() {} + inline NonreflectableMessage& operator=(const NonreflectableMessage& other) { + CopyFrom(other); + return *this; + } + inline NonreflectableMessage& operator=(const T& other) { + CopyFrom(other); + return *this; + } + +#if GOOGLE_PROTOBUF_VERSION >= 5029000 + using ClassData = ::google::protobuf::internal::ClassData; + using ClassDataFull = ::google::protobuf::internal::ClassDataFull; +#elif GOOGLE_PROTOBUF_VERSION >= 5026000 + using ClassData = ::google::protobuf::Message::ClassData; + using ClassDataFull = ::google::protobuf::Message::ClassDataFull; +#endif + +#if GOOGLE_PROTOBUF_VERSION >= 5026000 + const ClassData* GetClassData() const override { +# if GOOGLE_PROTOBUF_VERSION >= 5027000 + return _class_data_.base(); +# else + return nullptr; +# endif + } +#endif + +#if GOOGLE_PROTOBUF_VERSION < 3019000 + Message* New() const override { + return new T(); + } +#endif + +#if GOOGLE_PROTOBUF_VERSION >= 3000000 && GOOGLE_PROTOBUF_VERSION < 5029000 + Message* New(::google::protobuf::Arena* arena) const override { + return ::google::protobuf::Arena::Create(arena); + } +#endif + + void CopyFrom(const ::google::protobuf::Message& other) PB_321_OVERRIDE { + if (&other == this) { + return; + } + Clear(); + MergeFrom(other); + } + + inline void CopyFrom(const NonreflectableMessage& other) { + if (&other == this) { + return; + } + Clear(); + MergeFrom(other); + } + + void MergeFrom(const ::google::protobuf::Message& other) PB_526_OVERRIDE { + if (&other == this) { + return; + } + + // Cross-type merging is meaningless, call implementation of subclass +#if GOOGLE_PROTOBUF_VERSION >= 3007000 + const T* same_type_other = ::google::protobuf::DynamicCastToGenerated(&other); +#elif GOOGLE_PROTOBUF_VERSION >= 3000000 + const T* same_type_other = ::google::protobuf::internal::DynamicCastToGenerated(&other); +#endif // GOOGLE_PROTOBUF_VERSION + if (same_type_other != nullptr) { + MergeFrom(*same_type_other); + } else { + Message::MergeFrom(other); + } + } + + virtual void MergeFrom(const T&) = 0; + +#if GOOGLE_PROTOBUF_VERSION > 3019000 && GOOGLE_PROTOBUF_VERSION < 5026000 + // Unsupported by default. + std::string InitializationErrorString() const override { + return "unknown error"; + } +#endif + +#if GOOGLE_PROTOBUF_VERSION < 3019000 + // Unsupported by default. + void DiscardUnknownFields() override {} +#endif + +#if GOOGLE_PROTOBUF_VERSION >= 3004000 && GOOGLE_PROTOBUF_VERSION < 5026000 + // Unsupported by default. + size_t SpaceUsedLong() const override { + return 0; + } +#endif + + // Unsupported by default. + ::std::string GetTypeName() const PB_526_OVERRIDE { + return {}; + } + + void Clear() override {} + +#if GOOGLE_PROTOBUF_VERSION < 3010000 + bool MergePartialFromCodedStream(::google::protobuf::io::CodedInputStream*) override { + return true; + } +#endif + + // Quickly check if all required fields have values set. + // Unsupported by default. + bool IsInitialized() const PB_527_OVERRIDE { + return true; + } + +#if GOOGLE_PROTOBUF_VERSION >= 3010000 && GOOGLE_PROTOBUF_VERSION <= 5026000 + const char* _InternalParse( + const char* ptr, ::google::protobuf::internal::ParseContext*) override { + return ptr; + } +#endif + + // Size of bytes after serialization. +#if GOOGLE_PROTOBUF_VERSION < 3004000 + virtual size_t ByteSizeLong() const { + return 0; + } + + int ByteSize() const override { + return static_cast(ByteSizeLong()); + } +#else + size_t ByteSizeLong() const override { + return 0; + } +#endif + +#if GOOGLE_PROTOBUF_VERSION >= 3007000 && GOOGLE_PROTOBUF_VERSION < 3010000 + void SerializeWithCachedSizes(::google::protobuf::io::CodedOutputStream*) const override {} +#endif + +#if GOOGLE_PROTOBUF_VERSION >= 3010000 && GOOGLE_PROTOBUF_VERSION < 3011000 + uint8_t* InternalSerializeWithCachedSizesToArray( + uint8_t* ptr, ::google::protobuf::io::EpsCopyOutputStream*) const override { + return ptr; + } +#endif + +#if GOOGLE_PROTOBUF_VERSION >= 3011000 + uint8_t* _InternalSerialize( + uint8_t* ptr, ::google::protobuf::io::EpsCopyOutputStream*) const override { + return ptr; + } +#endif + +#if GOOGLE_PROTOBUF_VERSION < 4025000 + // Unnecessary for Nonreflectable message. + int GetCachedSize() const override { + return 0; + } +#endif + +#if GOOGLE_PROTOBUF_VERSION < 4025000 + // Unnecessary for Nonreflectable message. + void SetCachedSize(int) const override {} +#endif + +public: + // Only can be used to determine whether the Types are the same. + ::google::protobuf::Metadata GetMetadata() const PB_527_OVERRIDE { + ::google::protobuf::Metadata metadata{}; + // can only be used to + metadata.descriptor = reinterpret_cast(&_instance); + metadata.reflection = reinterpret_cast(&_instance); + return metadata; + } + + // Only can be used to determine whether the Types are the same. + inline static const ::google::protobuf::Descriptor* descriptor() noexcept { + return default_instance().GetMetadata().descriptor; + } + + inline static const T& default_instance() noexcept { + return _instance; + } + +private: + static T _instance; + +#if GOOGLE_PROTOBUF_VERSION >= 5027000 + struct NonreflectableMessageClassData : ClassDataFull { + constexpr NonreflectableMessageClassData() + : ClassDataFull( +# if GOOGLE_PROTOBUF_VERSION >= 7034000 + ClassData{ + &_instance, // prototype + nullptr, // tc_table + nullptr, // is_initialized + nullptr, // merge_to_from + ::google::protobuf::internal::MessageCreator(), // message_creator + 0, // cached_size_offset + false, // is_lite + }, + nullptr, // descriptor_methods + nullptr, // descriptor_table + nullptr // get_metadata_tracker +# elif GOOGLE_PROTOBUF_VERSION >= 5029000 + ClassData{ + &_instance, // prototype + nullptr, // tc_table + nullptr, // on_demand_register_arena_dtor + nullptr, // is_initialized + nullptr, // merge_to_from + ::google::protobuf::internal::MessageCreator(), // message_creator + 0, // cached_size_offset + false, // is_lite + }, + nullptr, // descriptor_methods + nullptr, // descriptor_table + nullptr // get_metadata_tracker +# elif GOOGLE_PROTOBUF_VERSION >= 5028000 + ClassData{ + &_instance, // prototype + nullptr, // tc_table + nullptr, // on_demand_register_arena_dtor + nullptr, // is_initialized + nullptr, // merge_to_from + 0, // cached_size_offset + false, // is_lite + }, + nullptr, // descriptor_methods + nullptr, // descriptor_table + nullptr // get_metadata_tracker +# else + ClassData{ + nullptr, // tc_table + nullptr, // on_demand_register_arena_dtor + nullptr, // is_initialized + 0, // cached_size_offset + false, // is_lite + }, + nullptr, // merge_to_from + nullptr, // descriptor_methods + nullptr, // descriptor_table + nullptr // get_metadata_tracker +# endif + ) { + // Only can be used to determine whether the Types are the same. + descriptor = default_instance().GetMetadata().descriptor; + reflection = default_instance().GetMetadata().reflection; + } + }; + + static const NonreflectableMessageClassData _class_data_; +#endif +}; + +#if GOOGLE_PROTOBUF_VERSION >= 5027000 +template +const typename NonreflectableMessage::NonreflectableMessageClassData NonreflectableMessage::_class_data_; +#endif + +template +T NonreflectableMessage::_instance; + +} // namespace brpc + +#endif // BRPC_NONREFLECTABLE_MESSAGE_H diff --git a/src/brpc/nshead.h b/src/brpc/nshead.h new file mode 100644 index 0000000..4ea7268 --- /dev/null +++ b/src/brpc/nshead.h @@ -0,0 +1,41 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_NSHEAD_H +#define BRPC_NSHEAD_H + + +namespace brpc { + +// Copied from public/nshead/nshead.h which is essentially unchangable. (Or +// even if it's changed, servers accepting new formats should also accept +// older formats). +static const unsigned int NSHEAD_MAGICNUM = 0xfb709394; +struct nshead_t { + unsigned short id; + unsigned short version; + unsigned int log_id; + char provider[16]; + unsigned int magic_num; + unsigned int reserved; + unsigned int body_len; +}; + +} // namespace brpc + + +#endif // BRPC_NSHEAD_H diff --git a/src/brpc/nshead_message.cpp b/src/brpc/nshead_message.cpp new file mode 100644 index 0000000..46081c7 --- /dev/null +++ b/src/brpc/nshead_message.cpp @@ -0,0 +1,90 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/nshead_message.h" + +#include "brpc/proto_base.pb.h" +#include "butil/logging.h" + +namespace brpc { + +NsheadMessage::NsheadMessage() + : NonreflectableMessage() { + SharedCtor(); +} + +NsheadMessage::NsheadMessage(const NsheadMessage& from) + : NonreflectableMessage(from) { + SharedCtor(); + MergeFrom(from); +} + +void NsheadMessage::SharedCtor() { + memset(&head, 0, sizeof(head)); +} + +NsheadMessage::~NsheadMessage() { + SharedDtor(); +} + +void NsheadMessage::SharedDtor() { +} + +void NsheadMessage::Clear() { + memset(&head, 0, sizeof(head)); + body.clear(); +} + +size_t NsheadMessage::ByteSizeLong() const { + return sizeof(nshead_t) + body.size(); +} + +void NsheadMessage::MergeFrom(const ::google::protobuf::Message& from) { + CHECK_NE(&from, this); + const NsheadMessage* source = dynamic_cast(&from); + if (source == NULL) { + LOG(ERROR) << "Can only merge from NsheadMessage"; + return; + } else { + MergeFrom(*source); + } +} + +void NsheadMessage::MergeFrom(const NsheadMessage& from) { + CHECK_NE(&from, this); + // No way to merge two nshead messages, just overwrite. + head = from.head; + body = from.body; +} + +void NsheadMessage::Swap(NsheadMessage* other) { + if (other != this) { + const nshead_t tmp = other->head; + other->head = head; + head = tmp; + body.swap(other->body); + } +} + +::google::protobuf::Metadata NsheadMessage::GetMetadata() const { + ::google::protobuf::Metadata metadata{}; + metadata.descriptor = NsheadMessageBase::descriptor(); + metadata.reflection = nullptr; + return metadata; +} + +} // namespace brpc diff --git a/src/brpc/nshead_message.h b/src/brpc/nshead_message.h new file mode 100644 index 0000000..6b48c7a --- /dev/null +++ b/src/brpc/nshead_message.h @@ -0,0 +1,66 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_NSHEAD_MESSAGE_H +#define BRPC_NSHEAD_MESSAGE_H + +#include "brpc/nonreflectable_message.h" +#include "brpc/nshead.h" // nshead_t +#include "brpc/pb_compat.h" +#include "butil/iobuf.h" // IOBuf + +namespace brpc { + +// Representing a nshead request or response. +class NsheadMessage : public NonreflectableMessage { +public: + nshead_t head; + butil::IOBuf body; + +public: + NsheadMessage(); + ~NsheadMessage() override; + + NsheadMessage(const NsheadMessage& from); + + inline NsheadMessage& operator=(const NsheadMessage& from) { + CopyFrom(from); + return *this; + } + + void Swap(NsheadMessage* other); + + // implements Message ---------------------------------------------- + void MergeFrom(const ::google::protobuf::Message& from) PB_526_OVERRIDE; + void MergeFrom(const NsheadMessage& from) override; + void Clear() override; + + size_t ByteSizeLong() const override; + int GetCachedSize() const PB_425_OVERRIDE { return ByteSize(); } + + ::google::protobuf::Metadata GetMetadata() const PB_527_OVERRIDE; + +private: + void SharedCtor(); + void SharedDtor(); +}; + +} // namespace brpc + + +#endif // BRPC_NSHEAD_MESSAGE_H diff --git a/src/brpc/nshead_meta.proto b/src/brpc/nshead_meta.proto new file mode 100644 index 0000000..03d44ca --- /dev/null +++ b/src/brpc/nshead_meta.proto @@ -0,0 +1,41 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +import "brpc/options.proto"; + +package brpc; + +option java_package="com.brpc"; +option java_outer_classname="NsheadProto"; + +message NsheadMeta { + // Returned by `MethodDescriptor::full_name' + // Something likd "test.EchoService.Echo" + required string full_method_name = 1; + + optional int64 correlation_id = 2; + optional int64 log_id = 3; + optional int32 attachment_size = 4; + optional CompressType compress_type = 5; + + optional int64 trace_id = 6; + optional int64 span_id = 7; + optional int64 parent_span_id = 8; + + optional bytes user_string = 9; +} diff --git a/src/brpc/nshead_pb_service_adaptor.cpp b/src/brpc/nshead_pb_service_adaptor.cpp new file mode 100644 index 0000000..889f957 --- /dev/null +++ b/src/brpc/nshead_pb_service_adaptor.cpp @@ -0,0 +1,148 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // MethodDescriptor +#include // Message + +#include "brpc/nshead_pb_service_adaptor.h" + +#include "brpc/controller.h" // Controller +#include "brpc/socket.h" // Socket +#include "brpc/server.h" // Server +#include "brpc/span.h" +#include "brpc/details/server_private_accessor.h" +#include "brpc/details/controller_private_accessor.h" +#include "brpc/policy/most_common_message.h" + +#include "butil/iobuf.h" // butil::IOBuf +#include "butil/strings/string_util.h" +#include "butil/time.h" + + +namespace brpc { + +struct SendNsheadPbResponse : public google::protobuf::Closure { + SendNsheadPbResponse(const NsheadPbServiceAdaptor* adaptor, + Controller* cntl, + NsheadMessage* ns_res, + NsheadClosure* done); + ~SendNsheadPbResponse() {} + + void Run(); + + NsheadMeta meta; + const NsheadPbServiceAdaptor* adaptor; + Controller* cntl; + std::unique_ptr pbreq; + std::unique_ptr pbres; + NsheadMessage* ns_res; + NsheadClosure* done; + MethodStatus* status; +}; + +extern const size_t SendNsheadPbResponseSize = sizeof(SendNsheadPbResponse); + +SendNsheadPbResponse::SendNsheadPbResponse( + const NsheadPbServiceAdaptor* adaptor2, + Controller* cntl2, + NsheadMessage* ns_res2, + NsheadClosure* done2) + : adaptor(adaptor2) + , cntl(cntl2) + , ns_res(ns_res2) + , done(done2) + , status(NULL) { +} + +void SendNsheadPbResponse::Run() { + MethodStatus* saved_status = status; + const int64_t received_us = done->received_us(); + if (!cntl->IsCloseConnection()) { + adaptor->SerializeResponseToIOBuf(meta, cntl, pbres.get(), ns_res); + } + + const bool saved_failed = cntl->Failed(); + NsheadClosure* saved_done = done; + // The space is allocated by NsheadClosure, don't delete. + this->~SendNsheadPbResponse(); + + // NOTE: *this was destructed, don't touch anything. + + // FIXME(gejun): We can't put this after saved_done->Run() where the + // service containing saved_status may be destructed (upon server's + // quiting). Thus the latency does not include the time of sending + // back response. + if (saved_status) { + saved_status->OnResponded( + !saved_failed, butil::cpuwide_time_us() - received_us); + } + saved_done->Run(); +} + +void NsheadPbServiceAdaptor::ProcessNsheadRequest( + const Server& server, Controller* controller, + const NsheadMessage& request, NsheadMessage* response, + NsheadClosure* done) { + SendNsheadPbResponse* pbdone = new (done->additional_space()) + SendNsheadPbResponse(this, controller, response, done); + NsheadMeta* meta = &pbdone->meta; + do { + if (controller->Failed()) { + break; + } + ParseNsheadMeta(server, request, controller, meta); + if (controller->Failed()) { + break; + } + if (meta->has_log_id()) { + controller->set_log_id(meta->log_id()); + } + + ServerPrivateAccessor server_accessor(&server); + const Server::MethodProperty *sp = server_accessor + .FindMethodPropertyByFullName(meta->full_method_name()); + if (NULL == sp || + sp->service->GetDescriptor() == BadMethodService::descriptor()) { + controller->SetFailed(ENOMETHOD, "Fail to find method=%s", + meta->full_method_name().c_str()); + break; + } + pbdone->status = sp->status; + sp->status->OnRequested(); + + google::protobuf::Service* svc = sp->service; + const google::protobuf::MethodDescriptor* method = sp->method; + ControllerPrivateAccessor(controller).set_method(method); + done->SetMethodName(butil::EnsureString(method->full_name())); + pbdone->pbreq.reset(svc->GetRequestPrototype(method).New()); + pbdone->pbres.reset(svc->GetResponsePrototype(method).New()); + + ParseRequestFromIOBuf(*meta, request, controller, pbdone->pbreq.get()); + if (controller->Failed()) { + break; + } + + // `meta', `req' and `res' will be deleted inside `pbdone' + return svc->CallMethod(method, controller, pbdone->pbreq.get(), + pbdone->pbres.get(), pbdone); + } while (false); + + pbdone->Run(); +} + +} // namespace brpc diff --git a/src/brpc/nshead_pb_service_adaptor.h b/src/brpc/nshead_pb_service_adaptor.h new file mode 100644 index 0000000..8fa1e82 --- /dev/null +++ b/src/brpc/nshead_pb_service_adaptor.h @@ -0,0 +1,95 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_NSHEAD_PB_SERVICE_ADAPTOR_H +#define BRPC_NSHEAD_PB_SERVICE_ADAPTOR_H + +#include "brpc/nshead_service.h" // NsheadService +#include "brpc/nshead_meta.pb.h" // NsheadMeta + +namespace brpc { + +class NsheadPbServiceAdaptor; +extern const size_t SendNsheadPbResponseSize; + +// Adapt nshead requests to use protobuf-based service. +// What RPC does: +// * Call ParseNsheadMeta() to understand the nshead header, user must +// tell RPC which pb method to call in the callback. +// * Call ParseRequestFromIOBuf() to convert the body after nshead header +// to pb request, then call the pb method. +// * When user calls server's done to end the RPC, SerializeResponseToIOBuf() +// is called to convert pb response to binary data that will be appended +// after nshead header and sent back to client. + +class NsheadPbServiceAdaptor : public NsheadService { +public: + NsheadPbServiceAdaptor() : NsheadService( + NsheadServiceOptions(false, SendNsheadPbResponseSize)) {} + virtual ~NsheadPbServiceAdaptor() {} + + // Fetch meta from `nshead_req' into `meta'. + // Params: + // server: where the RPC runs. + // nshead_req: the nshead request that server received. + // controller: If something goes wrong, call controller->SetFailed() + // meta: Set meta information into this structure. `full_method_name' + // must be set if controller is not SetFailed()-ed + // FIXME: server is not needed anymore, controller->server() is same + virtual void ParseNsheadMeta(const Server& server, + const NsheadMessage& nshead_req, + Controller* controller, + NsheadMeta* meta) const = 0; + + // Transform `nshead_req' to `pb_req'. + // Params: + // meta: was set by ParseNsheadMeta() + // nshead_req: the nshead request that server received. + // controller: you can set attachment into the controller. If something + // goes wrong, call controller->SetFailed() + // pb_req: the pb request should be set by your implementation. + virtual void ParseRequestFromIOBuf(const NsheadMeta& meta, + const NsheadMessage& nshead_req, + Controller* controller, + google::protobuf::Message* pb_req) const = 0; + + // Transform `pb_res' (and controller) to `nshead_res'. + // Params: + // meta: was set by ParseNsheadMeta() + // controller: If something goes wrong, call controller->SetFailed() + // pb_res: the pb response that returned by pb method. [NOTE] `pb_res' + // can be NULL or uninitialized when RPC failed (indicated by + // Controller::Failed()), in which case you may put error + // information into `nshead_res'. + // nshead_res: the nshead response that will be sent back to client. + virtual void SerializeResponseToIOBuf(const NsheadMeta& meta, + Controller* controller, + const google::protobuf::Message* pb_res, + NsheadMessage* nshead_res) const = 0; + +private: + void ProcessNsheadRequest( + const Server& server, Controller* controller, + const NsheadMessage& request, NsheadMessage* response, + NsheadClosure* done); +}; + +} // namespace brpc + + +#endif // BRPC_NSHEAD_PB_SERVICE_ADAPTOR_H diff --git a/src/brpc/nshead_service.cpp b/src/brpc/nshead_service.cpp new file mode 100644 index 0000000..0371baa --- /dev/null +++ b/src/brpc/nshead_service.cpp @@ -0,0 +1,61 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "butil/class_name.h" +#include "brpc/nshead_service.h" +#include "brpc/details/method_status.h" + + +namespace brpc { + +BAIDU_CASSERT(sizeof(nshead_t) == 36, sizeof_nshead_must_be_36); + +NsheadService::NsheadService() : _additional_space(0) { + _status = new MethodStatus; +} + +NsheadService::NsheadService(const NsheadServiceOptions& options) + : _status(NULL), _additional_space(options.additional_space) { + if (options.generate_status) { + _status = new MethodStatus; + } +} + +NsheadService::~NsheadService() { + delete _status; + _status = NULL; +} + +void NsheadService::Describe(std::ostream &os, const DescribeOptions&) const { + os << butil::class_name_str(*this); +} + +void NsheadService::Expose(const butil::StringPiece& prefix) { + _cached_name = butil::class_name_str(*this); + if (_status == NULL) { + return; + } + std::string s; + s.reserve(prefix.size() + 1 + _cached_name.size()); + s.append(prefix.data(), prefix.size()); + s.push_back('_'); + s.append(_cached_name); + _status->Expose(s); +} + +} // namespace brpc diff --git a/src/brpc/nshead_service.h b/src/brpc/nshead_service.h new file mode 100644 index 0000000..49ff9d7 --- /dev/null +++ b/src/brpc/nshead_service.h @@ -0,0 +1,129 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_NSHEAD_SERVICE_H +#define BRPC_NSHEAD_SERVICE_H + +#include "brpc/controller.h" // Controller +#include "brpc/nshead_message.h" // NsheadMessage +#include "brpc/describable.h" +#include "brpc/adaptive_max_concurrency.h" + +namespace brpc { + +class Server; +class MethodStatus; +class StatusService; +namespace policy { +void ProcessNsheadRequest(InputMessageBase* msg_base); +} + +// The continuation of request processing. Namely send response back to client. +// NOTE: you DON'T need to inherit this class or create instance of this class. +class NsheadClosure : public google::protobuf::Closure { +public: + explicit NsheadClosure(void* additional_space); + + // [Required] Call this to send response back to the client. + void Run() override; + + // [Optional] Set the full method name. If unset, use name of the service. + void SetMethodName(const std::string& full_method_name); + + // The space required by subclass at NsheadServiceOptions. subclass may + // utilizes this feature to save the cost of allocating closure separately. + // If subclass does not require space, this return value is NULL. + void* additional_space() { return _additional_space; } + + int64_t received_us() const { return _received_us; } + + // Don't send response back, used by MIMO. + void DoNotRespond(); + +private: +friend void policy::ProcessNsheadRequest(InputMessageBase* msg_base); +friend class DeleteNsheadClosure; + // Only callable by Run(). + ~NsheadClosure() override; + + const Server* _server; + int64_t _received_us; + NsheadMessage _request; + NsheadMessage _response; + bool _do_respond; + void* _additional_space; + Controller _controller; +}; + +struct NsheadServiceOptions { + NsheadServiceOptions() : generate_status(true), additional_space(0) {} + NsheadServiceOptions(bool generate_status2, size_t additional_space2) + : generate_status(generate_status2) + , additional_space(additional_space2) {} + + bool generate_status; + size_t additional_space; +}; + +// Inherit this class to let brpc server understands nshead requests. +class NsheadService : public Describable { +public: + NsheadService(); + explicit NsheadService(const NsheadServiceOptions&); + ~NsheadService() override; + + // Implement this method to handle nshead requests. Notice that this + // method can be called with a failed Controller(something wrong with the + // request before calling this method), in which case the implemenetation + // shall send specific response with error information back to client. + // Parameters: + // server The server receiving the request. + // controller per-rpc settings. + // request The nshead request received. + // response The nshead response that you should fill in. + // done You must call done->Run() to end the processing. + virtual void ProcessNsheadRequest(const Server& server, + Controller* controller, + const NsheadMessage& request, + NsheadMessage* response, + NsheadClosure* done) = 0; + + // Put descriptions into the stream. + void Describe(std::ostream &os, const DescribeOptions&) const override; + +private: +DISALLOW_COPY_AND_ASSIGN(NsheadService); +friend class NsheadClosure; +friend void policy::ProcessNsheadRequest(InputMessageBase* msg_base); +friend class StatusService; +friend class Server; + +private: + void Expose(const butil::StringPiece& prefix); + + // Tracking status of non NsheadPbService + MethodStatus* _status; + AdaptiveMaxConcurrency _max_concurrency; + size_t _additional_space; + std::string _cached_name; +}; + +} // namespace brpc + + +#endif // BRPC_NSHEAD_SERVICE_H diff --git a/src/brpc/options.proto b/src/brpc/options.proto new file mode 100644 index 0000000..935caaa --- /dev/null +++ b/src/brpc/options.proto @@ -0,0 +1,112 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +import "google/protobuf/descriptor.proto"; + +package brpc; +option java_package="com.brpc"; +option java_outer_classname="Options"; + +enum TalkType { + TALK_TYPE_NORMAL = 0; + TALK_TYPE_ONEWAY = 1; +} + +enum ConnectionType { + // bit-exclusive values since we may OR them to represent supported types. + CONNECTION_TYPE_UNKNOWN = 0; + CONNECTION_TYPE_SINGLE = 1; + CONNECTION_TYPE_POOLED = 2; + CONNECTION_TYPE_SHORT = 4; +} + +enum ProtocolType { + PROTOCOL_UNKNOWN = 0; + PROTOCOL_BAIDU_STD = 1; + PROTOCOL_STREAMING_RPC = 2; + PROTOCOL_HULU_PBRPC = 3; + PROTOCOL_SOFA_PBRPC = 4; + PROTOCOL_RTMP = 5; + PROTOCOL_THRIFT = 6; + PROTOCOL_HTTP = 7; + PROTOCOL_PUBLIC_PBRPC = 8; + PROTOCOL_NOVA_PBRPC = 9; + PROTOCOL_REDIS = 10; + PROTOCOL_NSHEAD_CLIENT = 11; // implemented in baidu-rpc-ub + PROTOCOL_NSHEAD = 12; + PROTOCOL_HADOOP_RPC = 13; + PROTOCOL_HADOOP_SERVER_RPC = 14; + PROTOCOL_MONGO = 15; // server side only + PROTOCOL_UBRPC_COMPACK = 16; + PROTOCOL_DIDX_CLIENT = 17; // Client side only + PROTOCOL_MEMCACHE = 18; // Client side only + PROTOCOL_ITP = 19; + PROTOCOL_NSHEAD_MCPACK = 20; + PROTOCOL_DISP_IDL = 21; // Client side only + PROTOCOL_ERSDA_CLIENT = 22; // Client side only + PROTOCOL_UBRPC_MCPACK2 = 23; // Client side only + // Reserve special protocol for cds-agent, which depends on FIFO right now + PROTOCOL_CDS_AGENT = 24; // Client side only + PROTOCOL_ESP = 25; // Client side only + PROTOCOL_H2 = 26; + PROTOCOL_COUCHBASE = 27; + PROTOCOL_MYSQL = 28; // Client side only +} + +enum CompressType { + COMPRESS_TYPE_NONE = 0; + COMPRESS_TYPE_SNAPPY = 1; + COMPRESS_TYPE_GZIP = 2; + COMPRESS_TYPE_ZLIB = 3; + COMPRESS_TYPE_LZ4 = 4; +} + +enum ChecksumType { + CHECKSUM_TYPE_NONE = 0; + CHECKSUM_TYPE_CRC32C = 1; +} + +enum ContentType { + CONTENT_TYPE_PB = 0; + CONTENT_TYPE_JSON = 1; + CONTENT_TYPE_PROTO_JSON = 2; + CONTENT_TYPE_PROTO_TEXT = 3; +} + +message ChunkInfo { + required int64 stream_id = 1; + required int64 chunk_id = 2; +} + +extend google.protobuf.ServiceOptions { + // Timeout in milliseconds, at service level. + optional int64 service_timeout = 90000 [default = 10000]; +} + +extend google.protobuf.MethodOptions { + // Talk type. + optional TalkType request_talk_type = 90001 [default = TALK_TYPE_NORMAL]; + optional TalkType response_talk_type = 90002 [default = TALK_TYPE_NORMAL]; + + // If set, override service_timeout. + optional int64 method_timeout = 90003; + + // Compression for request/response. + optional CompressType request_compression = 90004 [default = COMPRESS_TYPE_NONE]; + optional CompressType response_compression = 90005 [default = COMPRESS_TYPE_NONE]; +} diff --git a/src/brpc/parallel_channel.cpp b/src/brpc/parallel_channel.cpp new file mode 100644 index 0000000..de2b86f --- /dev/null +++ b/src/brpc/parallel_channel.cpp @@ -0,0 +1,835 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "bthread/bthread.h" // bthread_id_xx +#include "bthread/unstable.h" // bthread_timer_add +#include "butil/atomicops.h" +#include "butil/time.h" +#include "butil/macros.h" +#include "brpc/details/controller_private_accessor.h" +#include "brpc/parallel_channel.h" + +namespace brpc { + +DECLARE_bool(usercode_in_pthread); + +// Not see difference when memory is cached. +#ifdef BRPC_CACHE_PCHAN_MEM +struct Memory { + int size; + void* ptr; +}; +static __thread Memory tls_cached_pchan_mem = { 0, NULL }; +#endif + +class ParallelChannelDone : public google::protobuf::Closure { +private: + ParallelChannelDone(int fail_limit, int success_limit, + int ndone, int nchan, int memsize, + Controller* cntl, google::protobuf::Closure* user_done) + : _fail_limit(fail_limit) + , _success_limit(success_limit) + , _ndone(ndone) + , _nchan(nchan) + , _memsize(memsize) + , _current_success(0) + , _current_fail(0) + , _current_done(0) + , _cntl(cntl) + , _user_done(user_done) + , _callmethod_bthread(INVALID_BTHREAD) + , _callmethod_pthread(0) { + } + +public: + class SubDone : public google::protobuf::Closure { + public: + SubDone() : shared_data(NULL) { + } + + ~SubDone() override { + // Can't delete request/response in ~SubCall because the + // object is copyable. + if (ap.flags & DELETE_REQUEST) { + delete ap.request; + } + if (ap.flags & DELETE_RESPONSE) { + delete ap.response; + } + } + + void Run() override { + shared_data->OnSubDoneRun(this); + } + + ParallelChannelDone* shared_data; + butil::intrusive_ptr merger; + SubCall ap; + Controller cntl; + }; + + static ParallelChannelDone* Create( + int fail_limit, int success_limit, + int ndone, const SubCall* aps, int nchan, + Controller* cntl, google::protobuf::Closure* user_done) { + // We need to create the object in this way because _sub_done is + // dynamically allocated. + // The memory layout: + // ParallelChannelDone + // SubDone1 ` + // SubDone2 - ndone + // ... / + // SubDoneIndex1 ` + // SubDoneIndex2 - nchan, existing when nchan != ndone + // ... / + size_t req_size = offsetof(ParallelChannelDone, _sub_done) + + sizeof(SubDone) * ndone; + if (ndone != nchan) { + req_size += sizeof(int) * nchan; + } + void* mem = NULL; + int memsize = 0; +#ifdef BRPC_CACHE_PCHAN_MEM + Memory pchan_mem = tls_cached_pchan_mem; + if (pchan_mem.size >= req_size) { // use tls if it's big enough + mem = pchan_mem.ptr; + memsize = pchan_mem.size; + pchan_mem.size = 0; + pchan_mem.ptr = NULL; + tls_cached_pchan_mem = pchan_mem; + } else { + mem = malloc(req_size); + memsize = req_size; + if (BAIDU_UNLIKELY(NULL == mem)) { + return NULL; + } + } +#else + mem = malloc(req_size); + memsize = req_size; + if (BAIDU_UNLIKELY(NULL == mem)) { + return NULL; + } +#endif + auto d = new (mem) ParallelChannelDone( + fail_limit, success_limit, ndone, nchan, memsize, cntl, user_done); + + // Apply client settings of _cntl to controllers of sub calls, except + // timeout. If we let sub channel do their timeout separately, when + // timeout happens, we get ETOOMANYFAILS rather than ERPCTIMEDOUT. + Controller::ClientSettings settings; + cntl->SaveClientSettings(&settings); + settings.timeout_ms = -1; + for (int i = 0; i < ndone; ++i) { + new (d->sub_done(i)) SubDone; + d->sub_done(i)->cntl.ApplyClientSettings(settings); + d->sub_done(i)->cntl.allow_done_to_run_in_place(); + } + // Setup the map for finding sub_done of i-th sub_channel + if (ndone != nchan) { + int done_index = 0; + for (int i = 0; i < nchan; ++i) { + if (aps[i].is_skip()) { + d->sub_done_map(i) = -1; + } else { + d->sub_done_map(i) = done_index++; + } + } + CHECK_EQ(ndone, done_index); + } + return d; + } + + static void Destroy(ParallelChannelDone* d) { + if (d != NULL) { + for (int i = 0; i < d->_ndone; ++i) { + d->sub_done(i)->~SubDone(); + } +#ifdef BRPC_CACHE_PCHAN_MEM + Memory pchan_mem = tls_cached_pchan_mem; + if (pchan_mem.size != 0) { + // free the memory if tls already has sth. + d->~ParallelChannelDone(); + free(d); + } else { + pchan_mem.size = d->_memsize; + pchan_mem.ptr = d; + d->~ParallelChannelDone(); + tls_cached_pchan_mem = pchan_mem; + } +#else + d->~ParallelChannelDone(); + free(d); +#endif + } + } + + void Run() override { + const int ec = _cntl->ErrorCode(); + if (ec == EPCHANFINISH) { + // all sub calls finished. Clear the error and we'll set + // successfulness of _cntl in OnSubDoneRun(). + _cntl->_error_code = 0; + _cntl->_error_text.clear(); + } else { + CHECK(ECANCELED == ec || ERPCTIMEDOUT == ec) << "ec=" << ec; + } + OnSubDoneRun(NULL); + } + + static void* RunOnComplete(void* arg) { + static_cast(arg)->OnComplete(); + return NULL; + } + + // For otherwhere to know if they're in the same thread. + void SaveThreadInfoOfCallsite() { + _callmethod_bthread = bthread_self(); + if (_callmethod_bthread == INVALID_BTHREAD) { + _callmethod_pthread = pthread_self(); + } + } + + bool IsSameThreadAsCallMethod() const { + if (_callmethod_bthread != INVALID_BTHREAD) { + return bthread_self() == _callmethod_bthread; + } + return pthread_self() == _callmethod_pthread; + } + + void OnSubDoneRun(SubDone* fin) { + if (fin != NULL) { + // [ called from SubDone::Run() ] + + int error_code = fin->cntl.ErrorCode(); + // EPCHANFINISH is not an error of sub calls. + bool fail = 0 != error_code && EPCHANFINISH != error_code; + bool cancel = + // Count failed sub calls, if `fail_limit' is reached, cancel others. + (fail && _current_fail.fetch_add(1, butil::memory_order_relaxed) + 1 + == _fail_limit) || + // Count successful sub calls, if `success_limit' is reached, cancel others. + (0 == error_code && + _current_success.fetch_add(1, butil::memory_order_relaxed) + 1 + == _success_limit); + + if (cancel) { + // Only cancel once by `fail_limit' or `success_limit'. + for (int i = 0; i < _ndone; ++i) { + SubDone* sd = sub_done(i); + if (fin != sd) { + bthread_id_error( + sd->cntl.call_id(), fail ? ECANCELED : EPCHANFINISH); + } + } + } + // NOTE: Don't access any member after the fetch_add because + // another thread may already go down and Destroy()-ed this object. + const uint32_t saved_ndone = _ndone; + const CallId saved_cid = _cntl->_correlation_id; + // Add 1 to finished sub calls. + // The release fence is matched with acquire fence below to + // guarantee visibilities of all other variables. + const uint32_t val = + _current_done.fetch_add(1, butil::memory_order_release); + // Lower 31 bits are number of finished sub calls. If caller is not + // the last call that finishes, return. + if ((val & 0x7fffffff) + 1 != saved_ndone) { + return; + } + // If _cntl->call_id() is still there, stop it by sending a special + // error(which will be cleared) and return. + if (!(val & 0x80000000)) { + bthread_id_error(saved_cid, EPCHANFINISH); + return; + } + } else { + // [ Called from this->Run() ] + + // We may cancel sub calls even if all sub calls finish because + // of reading the value relaxly (and CPU cache is not sync yet). + // It's OK and we have to, because sub_done can't be accessed + // after fetch_or. + uint32_t val = _current_done.load(butil::memory_order_relaxed); + // Lower 31 bits are number of finished sub calls. Cancel sub calls + // if not all of them finish. + if ((val & 0x7fffffff) != (uint32_t)_ndone) { + for (int i = 0; i < _ndone; ++i) { + bthread_id_error(sub_done(i)->cntl.call_id(), ECANCELED); + } + } + // NOTE: Don't access any member after the fetch_or because + // another thread may already go down and Destroy()-ed this object. + const int saved_ndone = _ndone; + // Modify MSB to mark that this->Run() run. + // The release fence is matched with acquire fence below to + // guarantee visibilities of all other variables. + val = _current_done.fetch_or(0x80000000, butil::memory_order_release); + // If not all sub calls finish, return. + if ((val & 0x7fffffff) != (uint32_t)saved_ndone) { + return; + } + } + butil::atomic_thread_fence(butil::memory_order_acquire); + + if (fin != NULL && + !_cntl->is_done_allowed_to_run_in_place() && + IsSameThreadAsCallMethod()) { + // A sub channel's CallMethod calls a subdone directly, create a + // thread to run OnComplete. + bthread_t bh; + bthread_attr_t attr = (FLAGS_usercode_in_pthread ? + BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL); + if (bthread_start_background(&bh, &attr, RunOnComplete, this) != 0) { + LOG(FATAL) << "Fail to start bthread"; + OnComplete(); + } + } else { + OnComplete(); + } + } + + void OnComplete() { + // [ Rendezvous point ] + // One and only one thread arrives here. + // all call_id of sub calls are destroyed and call_id of _cntl is + // still locked (because FLAGS_DESTROY_CID_IN_DONE is true); + + // Merge responses of successful calls if fail_limit is not reached. + // nfailed may be increased during the merging. + // NOTE: Don't forget to set "nfailed = _ndone" when the _cntl is set + // to be failed since the RPC is still considered to be successful if + // nfailed is less than fail_limit + int nfailed = _current_fail.load(butil::memory_order_relaxed); + if (nfailed < _fail_limit) { + for (int i = 0; i < _ndone; ++i) { + SubDone* sd = sub_done(i); + google::protobuf::Message* sub_res = sd->cntl._response; + if (!sd->cntl.FailedInline()) { // successful calls only. + if (sd->merger == NULL) { + try { + _cntl->_response->MergeFrom(*sub_res); + } catch (const std::exception& e) { + nfailed = _ndone; + _cntl->SetFailed(ERESPONSE, "%s", e.what()); + break; + } + } else { + ResponseMerger::Result res = + sd->merger->Merge(_cntl->_response, sub_res); + switch (res) { + case ResponseMerger::MERGED: + break; + case ResponseMerger::FAIL: + ++nfailed; + break; + case ResponseMerger::FAIL_ALL: + nfailed = _ndone; + _cntl->SetFailed( + ERESPONSE, + "Fail to merge response of channel[%d]", i); + break; + } + } + } + } + } + + // Note: 1 <= _fail_limit <= _ndone. + if (nfailed >= _fail_limit) { + // If controller was already failed, don't change it. + if (!_cntl->FailedInline()) { + char buf[16]; + int unified_ec = ECANCELED; + for (int i = 0; i < _ndone; ++i) { + Controller* sub_cntl = &sub_done(i)->cntl; + const int ec = sub_cntl->ErrorCode(); + if (ec != 0 && ec != ECANCELED) { + if (unified_ec == ECANCELED) { + unified_ec = ec; + } else if (unified_ec != ec) { + unified_ec = ETOOMANYFAILS; + break; + } + } + } + _cntl->SetFailed(unified_ec, "%d/%d channels failed, fail_limit=%d", + nfailed, _ndone, _fail_limit); + for (int i = 0; i < _ndone; ++i) { + Controller* sub_cntl = &sub_done(i)->cntl; + if (sub_cntl->FailedInline()) { + const int len = snprintf(buf, sizeof(buf), " [C%d]", i); + _cntl->_error_text.append(buf, len); + _cntl->_error_text.append(sub_cntl->_error_text); + } + } + } + } else { + // Failed sub channels does not reach the limit, the RPC is + // considered to be successful. For example, a RPC to a + // ParallelChannel is canceled by user however enough sub calls + // (> _ndone - fail_limit) already succeed before the canceling, + // the RPC is still successful rather than ECANCELED. + _cntl->_error_code = 0; + _cntl->_error_text.clear(); + } + google::protobuf::Closure* user_done = _user_done; + const CallId saved_cid = _cntl->call_id(); + // NOTE: we don't destroy self here, controller destroys this done in + // Reset() so that user can access sub controllers before Reset(). + if (user_done) { + _cntl->OnRPCEnd(butil::gettimeofday_us()); + user_done->Run(); + } + CHECK_EQ(0, bthread_id_unlock_and_destroy(saved_cid)); + } + + int sub_done_size() const { return _ndone; } + SubDone* sub_done(int i) { return &_sub_done[i]; } + const SubDone* sub_done(int i) const { return &_sub_done[i]; } + + + int& sub_done_map(int i) { + return reinterpret_cast((_sub_done + _ndone))[i]; + } + + int sub_done_map(int i) const { + return reinterpret_cast((_sub_done + _ndone))[i]; + } + + int sub_channel_size() const { return _nchan; } + // Different from sub_done(), sub_channel_controller returns NULL for + // invalid accesses and never crashes. + const Controller* sub_channel_controller(int i) const { + if (i >= 0 && i < _nchan) { + if (_nchan == _ndone) { + return &sub_done(i)->cntl; + } + const int offset = sub_done_map(i); + if (offset >= 0) { + return &sub_done(offset)->cntl; + } + } + return NULL; + } + +private: + int _fail_limit; + int _success_limit; + int _ndone; + int _nchan; +#if defined(__clang__) + int ALLOW_UNUSED _memsize; +#else + int _memsize; +#endif + butil::atomic _current_success; + butil::atomic _current_fail; + butil::atomic _current_done; + Controller* _cntl; + google::protobuf::Closure* _user_done; + bthread_t _callmethod_bthread; + pthread_t _callmethod_pthread; + SubDone _sub_done[0]; +}; + +// Used in controller.cpp +void DestroyParallelChannelDone(google::protobuf::Closure* c) { + ParallelChannelDone::Destroy(static_cast(c)); +} + +const Controller* GetSubControllerOfParallelChannel( + const google::protobuf::Closure* c, int i) { + const ParallelChannelDone* d = static_cast(c); + return d->sub_channel_controller(i); +} + +int ParallelChannel::Init(const ParallelChannelOptions* options) { + if (options != NULL) { + _options = *options; + } + return 0; +} + +int ParallelChannel::AddChannel(ChannelBase* sub_channel, + ChannelOwnership ownership, + CallMapper* call_mapper, + ResponseMerger* merger) { + if (NULL == sub_channel) { + LOG(ERROR) << "Param[sub_channel] is NULL"; + return -1; + } + if (_chans.capacity() == 0) { + _chans.reserve(32); + } + SubChan sc; + sc.chan = sub_channel; + sc.ownership = ownership; + sc.call_mapper = call_mapper; + sc.merger = merger; + _chans.push_back(sc); + return 0; +} + +int ParallelChannel::AddChannel(ChannelBase* sub_channel, + ChannelOwnership ownership, + const butil::intrusive_ptr& call_mapper, + const butil::intrusive_ptr& merger) { + if (NULL == sub_channel) { + LOG(ERROR) << "Param[sub_channel] is NULL"; + return -1; + } + if (_chans.capacity() == 0) { + _chans.reserve(32); + } + SubChan sc; + sc.chan = sub_channel; + sc.ownership = ownership; + sc.call_mapper = call_mapper; + sc.merger = merger; + _chans.push_back(sc); + return 0; +} + +struct SortByChannelPtr { + bool operator()(const ParallelChannel::SubChan& c1, + const ParallelChannel::SubChan& c2) const { + return c1.chan < c2.chan; + } +}; + +struct EqualChannelPtr { + bool operator()(const ParallelChannel::SubChan& c1, + const ParallelChannel::SubChan& c2) const { + return c1.chan == c2.chan; + } +}; + +void ParallelChannel::Reset() { + // Removal of channels are a little complex because a channel may be + // added multiple times. + + // Dereference call_mapper and mergers first. + for (size_t i = 0; i < _chans.size(); ++i) { + _chans[i].call_mapper.reset(); + _chans[i].merger.reset(); + } + + // Remove not own-ed channels. + for (size_t i = 0; i < _chans.size();) { + if (_chans[i].ownership != OWNS_CHANNEL) { + _chans[i] = _chans.back(); + _chans.pop_back(); + } else { + ++i; + } + } + + if (_chans.empty()) { + return; + } + + // Sort own-ed channels so that we can deduplicate them more efficiently. + std::sort(_chans.begin(), _chans.end(), SortByChannelPtr()); + const size_t uniq_size = + std::unique(_chans.begin(), _chans.end(), EqualChannelPtr()) + - _chans.begin(); + for (size_t i = 0; i < uniq_size; ++i) { + CHECK_EQ(_chans[i].ownership, OWNS_CHANNEL); + delete _chans[i].chan; + } + _chans.clear(); +} + +ParallelChannel::~ParallelChannel() { + Reset(); +} + +static void HandleTimeout(void* arg) { + bthread_id_t correlation_id = { (uint64_t)arg }; + bthread_id_error(correlation_id, ERPCTIMEDOUT); +} + +void* ParallelChannel::RunDoneAndDestroy(void* arg) { + Controller* c = static_cast(arg); + // Move done out from the controller. + google::protobuf::Closure* done = c->_done; + c->_done = NULL; + // Save call_id from the controller which may be deleted after Run(). + const bthread_id_t cid = c->call_id(); + done->Run(); + CHECK_EQ(0, bthread_id_unlock_and_destroy(cid)); + return NULL; +} + +void ParallelChannel::CallMethod( + const google::protobuf::MethodDescriptor* method, + google::protobuf::RpcController* cntl_base, + const google::protobuf::Message* request, + google::protobuf::Message* response, + google::protobuf::Closure* done) { + Controller* cntl = static_cast(cntl_base); + cntl->OnRPCBegin(butil::gettimeofday_us()); + // Make sure cntl->sub_count() always equal #sub-channels + const int nchan = _chans.size(); + cntl->_pchan_sub_count = nchan; + + const CallId cid = cntl->call_id(); + const int rc = bthread_id_lock(cid, NULL); + if (rc != 0) { + CHECK_EQ(EINVAL, rc); + if (!cntl->FailedInline()) { + cntl->SetFailed(EINVAL, "Fail to lock call_id=%" PRId64, cid.value); + } + LOG_IF(ERROR, cntl->is_used_by_rpc()) + << "Controller=" << cntl << " was used by another RPC before. " + "Did you forget to Reset() it before reuse?"; + // Have to run done in-place. + // Read comment in CallMethod() in channel.cpp for details. + if (done) { + done->Run(); + } + return; + } + cntl->set_used_by_rpc(); + + ParallelChannelDone* d = NULL; + int ndone = nchan; + int fail_limit = 1; + int success_limit = 1; + Controller::ClientSettings settings{}; + DEFINE_SMALL_ARRAY(SubCall, aps, nchan, 64); + + if (cntl->FailedInline()) { + // The call_id is cancelled before RPC. + goto FAIL; + } + // we don't support http whose response is NULL. + if (response == NULL) { + cntl->SetFailed(EINVAL, "response must be non-NULL"); + goto FAIL; + } + if (nchan == 0) { + cntl->SetFailed(EPERM, "No channels added"); + goto FAIL; + } + + for (int i = 0; i < nchan; ++i) { + SubChan& sub_chan = _chans[i]; + if (sub_chan.call_mapper != NULL) { + aps[i] = sub_chan.call_mapper->Map(i, nchan, method, request, response); + // Test is_skip first because it implies is_bad. + if (aps[i].is_skip()) { + --ndone; + } else if (aps[i].is_bad()) { + cntl->SetFailed( + EREQUEST, "CallMapper of channel[%d] returns Bad()", i); + goto FAIL; + } + } else { + google::protobuf::Message* cur_res = response->New(); + if (cur_res == NULL) { + cntl->SetFailed(ENOMEM, "Fail to new response"); + goto FAIL; + } + aps[i] = SubCall(method, request, cur_res, DELETE_RESPONSE); + } + } + if (ndone <= 0) { + cntl->SetFailed(ECANCELED, "Skipped all channels(%d)", nchan); + goto FAIL; + } + + if (_options.fail_limit < 0) { + // Both Controller and ParallelChannel haven't set `fail_limit' + fail_limit = ndone; + } else { + fail_limit = _options.fail_limit; + if (fail_limit < 1) { + fail_limit = 1; + } else if (fail_limit > ndone) { + fail_limit = ndone; + } + } + + // `success_limit' is only valid when `fail_limit' is not set. + if (_options.fail_limit >= 0 || _options.success_limit < 0) { + success_limit = ndone; + } else { + success_limit = _options.success_limit; + if (success_limit < 1) { + success_limit = 1; + } else if (success_limit > ndone) { + success_limit = ndone; + } + } + + d = ParallelChannelDone::Create( + fail_limit, success_limit, ndone, aps, nchan, cntl, done); + if (NULL == d) { + cntl->SetFailed(ENOMEM, "Fail to new ParallelChannelDone"); + goto FAIL; + } + + for (int i = 0, j = 0; i < nchan; ++i) { + SubChan& sub_chan = _chans[i]; + if (!aps[i].is_skip()) { + ParallelChannelDone::SubDone* sd = d->sub_done(j++); + sd->ap = aps[i]; + sd->shared_data = d; + sd->merger = sub_chan.merger; + } + } + cntl->_response = response; + cntl->_done = d; + cntl->add_flag(Controller::FLAGS_DESTROY_CID_IN_DONE); + + if (cntl->timeout_ms() == UNSET_MAGIC_NUM) { + cntl->set_timeout_ms(_options.timeout_ms); + } + if (cntl->timeout_ms() >= 0) { + cntl->_deadline_us = cntl->timeout_ms() * 1000L + cntl->_begin_time_us; + // Setup timer for RPC timetout + const int rc = bthread_timer_add( + &cntl->_timeout_id, + butil::microseconds_to_timespec(cntl->_deadline_us), + HandleTimeout, (void*)cid.value); + if (rc != 0) { + cntl->SetFailed(rc, "Fail to add timer"); + goto FAIL; + } + } else { + cntl->_deadline_us = -1; + } + d->SaveThreadInfoOfCallsite(); + CHECK_EQ(0, bthread_id_unlock(cid)); + // Don't touch `cntl' and `d' again (for async RPC) + + // Apply client settings of _cntl to controllers of sub calls, except + // timeout. If we let sub channel do their timeout separately, when + // timeout happens, we get ETOOMANYFAILS rather than ERPCTIMEDOUT. + cntl->SaveClientSettings(&settings); + settings.timeout_ms = -1; + for (int i = 0, j = 0; i < nchan; ++i) { + if (!aps[i].is_skip()) { + ParallelChannelDone::SubDone* sd = d->sub_done(j++); + if (NULL != _chans[i].call_mapper) { + _chans[i].call_mapper->MapController(i, nchan, cntl, &sd->cntl); + } else { + // Forward the attachment to each sub call. + sd->cntl.request_attachment().append(cntl->request_attachment()); + } + sd->cntl.ApplyClientSettings(settings); + sd->cntl.allow_done_to_run_in_place(); + } + } + for (int i = 0, j = 0; i < nchan; ++i) { + if (!aps[i].is_skip()) { + ParallelChannelDone::SubDone* sd = d->sub_done(j++); + _chans[i].chan->CallMethod(sd->ap.method, &sd->cntl, + sd->ap.request, sd->ap.response, sd); + } + // Although we can delete request (if delete_request is true) after + // starting sub call, we leave it in ~SubCall(called when d is + // Destroy()-ed) because we may need to check requests for debugging + // purposes. + } + if (done == NULL) { + Join(cid); + cntl->OnRPCEnd(butil::gettimeofday_us()); + } + return; + +FAIL: + // The RPC was failed after locking call_id and before calling sub channels. + if (d) { + // Set the _done to NULL to make sure cntl->sub(any_index) is NULL. + cntl->_done = NULL; + ParallelChannelDone::Destroy(d); + } + if (done) { + if (!cntl->is_done_allowed_to_run_in_place()) { + bthread_t bh; + bthread_attr_t attr = (FLAGS_usercode_in_pthread ? + BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL); + // Hack: save done in cntl->_done to remove a malloc of args. + cntl->_done = done; + if (bthread_start_background(&bh, &attr, RunDoneAndDestroy, cntl) == 0) { + return; + } + cntl->_done = NULL; + LOG(FATAL) << "Fail to start bthread"; + } + done->Run(); + } + CHECK_EQ(0, bthread_id_unlock_and_destroy(cid)); +} + +int ParallelChannel::Weight() { + if (_chans.empty()) { + return 0; + } + int w = _chans[0].chan->Weight(); + for (size_t i = 1; i < _chans.size(); ++i) { + const int w2 = _chans[i].chan->Weight(); + if (w2 < w) { + w = w2; + } + } + return w; +} + +int ParallelChannel::CheckHealth() { + if (_chans.empty()) { + return -1; + } + int threshold = (int)_chans.size(); + if (_options.fail_limit > 0) { + threshold -= _options.fail_limit; + ++threshold; + } + if (threshold <= 0) { + return 0; + } + int nhealthy = 0; + for (size_t i = 0; i < _chans.size(); ++i) { + nhealthy += (_chans[i].chan->CheckHealth() == 0); + if (nhealthy >= threshold) { + return 0; + } + } + return -1; +} + +void ParallelChannel::Describe( + std::ostream& os, const DescribeOptions& options) const { + os << "ParallelChannel["; + if (!options.verbose) { + os << _chans.size(); + } else { + for (size_t i = 0; i < _chans.size(); ++i) { + if (i != 0) { + os << ' '; + } + os << *_chans[i].chan; + } + } + os << "]"; +} + +} // namespace brpc diff --git a/src/brpc/parallel_channel.h b/src/brpc/parallel_channel.h new file mode 100644 index 0000000..292213c --- /dev/null +++ b/src/brpc/parallel_channel.h @@ -0,0 +1,291 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_PARALLEL_CHANNEL_H +#define BRPC_PARALLEL_CHANNEL_H + +// To brpc developers: This is a header included by user, don't depend +// on internal structures, use opaque pointers instead. + +#include +#include "brpc/shared_object.h" +#include "brpc/channel.h" + + +namespace brpc { + +// Possible values of SubCall.flag, MUST be bitwise exclusive. +static const int DELETE_REQUEST = 1; +static const int DELETE_RESPONSE = 2; +static const int SKIP_SUB_CHANNEL = 4; + +// Return value of CallMapper +struct SubCall { + SubCall(const google::protobuf::MethodDescriptor* method2, + const google::protobuf::Message* request2, + google::protobuf::Message* response2, + int flags2) + : method(method2) + , request(request2) + , response(response2) + , flags(flags2) + { } + + SubCall() : method(NULL), request(NULL), response(NULL), flags(0) { } + + // Returning this makes the call to ParallelChannel fail immediately. + static SubCall Bad() { return SubCall(); } + + // Returning this makes the channel being skipped. + static SubCall Skip() { + SubCall sc; + sc.flags = SKIP_SUB_CHANNEL; + return sc; + } + + // True if this object is constructed by Bad(). + bool is_bad() const { + return request == NULL || response == NULL; + } + + // True if this object is constructed by Skip(). + // true is_skip() implies true is_bad(). + bool is_skip() const { return flags & SKIP_SUB_CHANNEL; } + + const google::protobuf::MethodDescriptor* method; + const google::protobuf::Message* request; + google::protobuf::Message* response; + int flags; +}; + +// Map calls to ParallelChannel to sub channels, which can have different +// requests and responses. +// Examples: +// 1. broadcast request and merge responses: +// return SubCall(method, request, response->New(), DELETE_RESPONSE); +// +// 2. change sth. of the request and merge responses: +// FooRequest* copied_req = brpc::Clone(request); +// copied_req->set_xxx(...); +// return SubCall(method, copied_req, response->New(), +// DELETE_REQUEST | DELETE_RESPONSE); +// +// 3. Use sub_requests in request and put sub_responses in response. +// if (channel_index >= request->sub_request_size()) { +// return SubCall::Bad(); +// } +// return SubCall(sub_method, request->sub_request(channel_index), +// response->add_sub_response(), 0); +// MapController calls to ParallelChannel to sub channels, which can have +// different controllers. +// Note: +// Modifying ClientSettings configurations (such as timeout, retries, etc.) +// is ineffective because all sub-controllers use the main controller's +// ClientSettings configuration. +// Examples: +// sub_cntl->http_request().SetHeader(...); +class CallMapper : public SharedObject { +public: + virtual SubCall Map(int channel_index/*starting from 0*/, + int channel_count, + const google::protobuf::MethodDescriptor* method, + const google::protobuf::Message* request, + google::protobuf::Message* response) { + return Map(channel_index, method, request, response); + } + + virtual void MapController(int channel_index/*starting from 0*/, int channel_count, + const Controller* main_cntl, Controller* sub_cntl) { + // Forward the attachment to each sub call by default. + sub_cntl->request_attachment().append(main_cntl->request_attachment()); + } + +protected: + // TODO: Remove this backward compatibility method. + // This method is deprecated. You should override public Map function. + virtual SubCall Map(int channel_index/*starting from 0*/, + const google::protobuf::MethodDescriptor* method, + const google::protobuf::Message* request, + google::protobuf::Message* response) { + return SubCall::Bad(); + } + + // Only callable by subclasses and butil::intrusive_ptr + ~CallMapper() override = default; +}; + +// Clone req_base typed `Req'. +template Req* Clone(const google::protobuf::Message* req_base) { + const Req* req = dynamic_cast(req_base); + Req* copied_req = req->New(); + copied_req->MergeFrom(*req); + return copied_req; +} + +// Merge sub_response into response. +class ResponseMerger : public SharedObject { +public: + enum Result { + // the response was merged successfully + MERGED, + + // the sub_response was not merged and will be counted as one failure. + // e.g. 10 sub channels & fail_limit=4, 3 failed before merging, 1 + // failed after merging, the rpc call will be treated as 4 sub + // failures which reaches fail_limit, thus the call is failed. + FAIL, + + // make the call to ParallelChannel fail. + FAIL_ALL + }; + + virtual Result Merge(google::protobuf::Message* response, + const google::protobuf::Message* sub_response) = 0; +protected: + // Only callable by subclasses and butil::intrusive_ptr + ~ResponseMerger() override = default; +}; + +struct ParallelChannelOptions { + // [NOTE] timeout of sub channels are disabled in ParallelChannel. Control + // deadlines of RPC by this timeout_ms or Controller.set_timeout_ms() + // + // Max duration of RPC over this Channel. -1 means wait indefinitely. + // Overridable by Controller.set_timeout_ms(). + // Default: 500 (milliseconds) + // Maximum: 0x7fffffff (roughly 30 days) + int32_t timeout_ms{500}; + + // The RPC is considered to be successful if number of failed sub RPC + // does not reach this limit. Even if the RPC is timedout or canceled, + // as long as number of failed sub RPC does not reach this limit, the + // RPC is still successful. RPC will stop soon (rather than waiting for + // the timeout) when the limit is reached. + // Default: number of sub channels, meaning that the RPC to ParallChannel + // does not fail unless all sub RPC failed. + int fail_limit{-1}; + + // The RPC is considered to be successful when number of successful sub + // RPC reach this limit. + // Default: number of sub channels, meaning that the RPC to ParallChannel + // does not return unless all sub RPC succeed. + // Note: `success_limit' is only valid when `fail_limit' is not set. + int success_limit{ -1}; +}; + +// ParallelChannel(aka "pchan") accesses all sub channels simultaneously with +// optionally modified requests (by CallMapper) and merges responses (by +// ResponseMerger) when they come back. The main purpose of pchan is to make +// parallel accesses to different partitions or sub systems much easier. +// ParallelChannel is a fully functional Channel: +// * synchronous and asynchronous RPC. +// * deletable immediately after an asynchronous call. +// * cancelable call_id (cancels all sub calls). +// * timeout. +// There's no separate retrying inside ParallelChannel. To retry, enable +// retrying of sub channels. +class ParallelChannel : public ChannelBase { +friend class Controller; +public: + ~ParallelChannel() override; + + // Initialize ParallelChannel with `options'. + // NOTE: Currently this function always returns 0. + int Init(const ParallelChannelOptions* options); + + // Add a sub channel, which can be a ParallelChannel as well. + // `sub_channel' will be deleted in dtor of ParallelChannel when ownership + // is OWNS_CHANNEL. + // A sub channel can be added multiple times. If it's added with + // brpc::OWNS_CHANNEL, it will be deleted for only once. + // If call_mapper is NULL: + // - Every sub_channel will get the same `request' to ParallelChannel + // - responses of sub channels are New()-ed from the `response' to + // ParallelChannel. + // If response_merger is NULL: + // - responses of sub channels will be merged to the `response' to + // ParalleChannel by google::protobuf::Message::MergeFrom(). + // `call_mapper' and `response_merger' are always deleted in dtor. + // NOTE: + // `call_mapper' and `response_merger' are intrusively shared, namely + // they have referential counters to record how many sub channels are + // using them. As a result, one call_mapper or response_merger can be + // associated to different sub channels without any problem. + // CAUTION: + // AddChannel() during CallMethod() is thread-unsafe! + // Returns 0 on success, -1 otherwise. + int AddChannel(ChannelBase* sub_channel, + ChannelOwnership ownership, + CallMapper* call_mapper, + ResponseMerger* response_merger); + + // same as AddChannel(... CallMapper* call_mapper, ResponseMerger* response_merger) + // use intrusive_ptr to avoid potential memory leak + int AddChannel(ChannelBase* sub_channel, + ChannelOwnership ownership, + const butil::intrusive_ptr& call_mapper, + const butil::intrusive_ptr& response_merger); + + // Call `method' of the remote service with `request' as input, and + // `response' as output. `controller' contains options and extra data. + // If `done' is not NULL, this method returns after request was sent + // and `done->Run()' will be called when the call finishes, otherwise + // caller blocks until the call finishes. + void CallMethod(const google::protobuf::MethodDescriptor* method, + google::protobuf::RpcController* controller, + const google::protobuf::Message* request, + google::protobuf::Message* response, + google::protobuf::Closure* done) override; + + // Number of sub channels. + size_t channel_count() const { return _chans.size(); } + + // Reset to the state that this channel was just constructed. + // NOTE: fail_limit is kept. + void Reset(); + + // Minimum weight of sub channels. + // FIXME(gejun): be minimum of top(nchan-fail_limit) + int Weight() override; + + // Put description into `os'. + void Describe(std::ostream& os, const DescribeOptions&) const override; + +public: + struct SubChan { + ChannelBase* chan; + ChannelOwnership ownership; + butil::intrusive_ptr call_mapper; + // ParallelChannel may be dtor before async RPC call finishes and + // merger is shared with SubDone. + butil::intrusive_ptr merger; + }; + typedef std::vector ChannelList; + +protected: + static void* RunDoneAndDestroy(void* arg); + int CheckHealth() override; + + ParallelChannelOptions _options; + ChannelList _chans; +}; + +} // namespace brpc + + +#endif // BRPC_PARALLEL_CHANNEL_H diff --git a/src/brpc/parse_result.h b/src/brpc/parse_result.h new file mode 100644 index 0000000..e84f80e --- /dev/null +++ b/src/brpc/parse_result.h @@ -0,0 +1,92 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_PARSE_RESULT_H +#define BRPC_PARSE_RESULT_H + + +namespace brpc { + +enum ParseError { + PARSE_OK = 0, + PARSE_ERROR_TRY_OTHERS, + PARSE_ERROR_NOT_ENOUGH_DATA, + PARSE_ERROR_TOO_BIG_DATA, + PARSE_ERROR_NO_RESOURCE, + PARSE_ERROR_ABSOLUTELY_WRONG, +}; + +inline const char* ParseErrorToString(ParseError e) { + switch (e) { + case PARSE_OK: return "ok"; + case PARSE_ERROR_TRY_OTHERS: return "try other protocols"; + case PARSE_ERROR_NOT_ENOUGH_DATA: return "not enough data"; + case PARSE_ERROR_TOO_BIG_DATA: return "too big data"; + case PARSE_ERROR_NO_RESOURCE: return "no resource for the message"; + case PARSE_ERROR_ABSOLUTELY_WRONG: return "absolutely wrong message"; + } + return "unknown ParseError"; +} + +class InputMessageBase; + +// A specialized Maybe<> type to represent a parsing result. +class ParseResult { +public: + // Create a failed parsing result. + explicit ParseResult(ParseError err) + : _msg(NULL), _err(err), _user_desc(NULL) {} + // The `user_desc' must be string constant or always valid. + explicit ParseResult(ParseError err, const char* user_desc) + : _msg(NULL), _err(err), _user_desc(user_desc) {} + // Create a successful parsing result. + explicit ParseResult(InputMessageBase* msg) + : _msg(msg), _err(PARSE_OK), _user_desc(NULL) {} + + // Return PARSE_OK when the result is successful. + ParseError error() const { return _err; } + const char* error_str() const + { return _user_desc ? _user_desc : ParseErrorToString(_err); } + bool is_ok() const { return error() == PARSE_OK; } + + // definitely NULL when result is failed. + InputMessageBase* message() const { return _msg; } + +private: + InputMessageBase* _msg; + ParseError _err; + const char* _user_desc; +}; + +// Wrap ParseError/message into ParseResult. +// You can also call ctor of ParseError directly. +inline ParseResult MakeParseError(ParseError err) { + return ParseResult(err); +} +// The `user_desc' must be string constant or always valid. +inline ParseResult MakeParseError(ParseError err, const char* user_desc) { + return ParseResult(err, user_desc); +} +inline ParseResult MakeMessage(InputMessageBase* msg) { + return ParseResult(msg); +} + +} // namespace brpc + + +#endif // BRPC_PARSE_RESULT_H diff --git a/src/brpc/partition_channel.cpp b/src/brpc/partition_channel.cpp new file mode 100644 index 0000000..f6a37bc --- /dev/null +++ b/src/brpc/partition_channel.cpp @@ -0,0 +1,491 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "butil/containers/flat_map.h" +#include "brpc/log.h" +#include "brpc/load_balancer.h" +#include "brpc/details/naming_service_thread.h" +#include "brpc/partition_channel.h" +#include "brpc/global.h" + + +namespace brpc { + +// ================= PartitionChannelBase ==================== + +// Base of PartitionChannel and DynamicPartitionChannel. +class PartitionChannelBase : public ParallelChannel, + public NamingServiceWatcher { +public: + PartitionChannelBase(); + ~PartitionChannelBase(); + + int Init(int num_partition_kinds, + PartitionParser* partition_parser, + const char* load_balancer_name, + const PartitionChannelOptions* options); + + int partition_count() const { return channel_count(); } + + size_t AddServersInBatch(const std::vector& servers); + size_t RemoveServersInBatch(const std::vector& servers); + +private: + bool initialized() const { return _parser != NULL; } + void PartitionServersIntoTemps(const std::vector& servers); + void OnAddedServers(const std::vector& servers); + void OnRemovedServers(const std::vector& servers); + + struct SubChannel : public Channel { + SharedLoadBalancer* lb() { return _lb.get(); } + std::vector tmp; + }; + + SubChannel* _subs; + PartitionParser* _parser; +}; + +PartitionChannelBase::PartitionChannelBase() + : _subs(NULL) + , _parser(NULL) { +} + +PartitionChannelBase::~PartitionChannelBase() { + delete [] _subs; + _subs = NULL; +} + +int PartitionChannelBase::Init(int num_partition_kinds, + PartitionParser* partition_parser, + const char* load_balancer_name, + const PartitionChannelOptions* options_in) { + if (num_partition_kinds <= 0) { + LOG(ERROR) << "Parameter[num_partition_kinds] must be positive"; + return -1; + } + if (NULL == partition_parser) { + LOG(ERROR) << "Parameter[partition_parser] must be non-NULL"; + return -1; + } + PartitionChannelOptions options; + if (options_in) { + options = *options_in; + } + options.succeed_without_server = true; + options.log_succeed_without_server = false; + _subs = new (std::nothrow) SubChannel[num_partition_kinds]; + if (NULL == _subs) { + LOG(ERROR) << "Fail to new Channels[" << num_partition_kinds << "]"; + return -1; + } + for (int i = 0; i < num_partition_kinds; ++i) { + if (_subs[i].Init("list://", load_balancer_name, &options) != 0) { + LOG(ERROR) << "Fail to init sub channel[" << i << "]"; + return -1; + } + } + for (int i = 0; i < num_partition_kinds; ++i) { + if (AddChannel(&_subs[i], DOESNT_OWN_CHANNEL, + options.call_mapper.get(), + options.response_merger.get()) != 0) { + LOG(ERROR) << "Fail to add sub channel[" << i << "]"; + return -1; + } + } + ParallelChannelOptions pchan_options; + pchan_options.timeout_ms = options.timeout_ms; + pchan_options.fail_limit = options.fail_limit; + if (ParallelChannel::Init(&pchan_options) != 0) { + LOG(ERROR) << "Fail to init PartitionChannel as ParallelChannel"; + return -1; + } + // Must be last one because it's the marker of initialized(). + _parser = partition_parser; + return 0; +} + +void PartitionChannelBase::PartitionServersIntoTemps( + const std::vector& servers) { + for (int i = 0; i < partition_count(); ++i) { + _subs[i].tmp.clear(); + } + for (size_t i = 0; i < servers.size(); ++i) { + Partition part; + if (!_parser->ParseFromTag(servers[i].tag, &part)) { + LOG(ERROR) << "Fail to parse " << servers[i].tag; + continue; + } + if (part.num_partition_kinds != partition_count()) { + // Not belonging to this channel. + continue; + } + if (part.index < 0 || part.index >= partition_count()) { + LOG(ERROR) << "Invalid index=" << part.index << " in tag=`" + << servers[i].tag << "'"; + continue; + } + if (_subs[part.index].tmp.capacity() == 0) { + _subs[part.index].tmp.reserve(16); + } + _subs[part.index].tmp.push_back(servers[i]); + } +} + +size_t PartitionChannelBase::AddServersInBatch( + const std::vector& servers) { + PartitionServersIntoTemps(servers); + size_t ntotal = 0; + for (int i = 0; i < partition_count(); ++i) { + if (!_subs[i].tmp.empty()) { + size_t n = _subs[i].lb()->AddServersInBatch(_subs[i].tmp); + ntotal += n; + RPC_VLOG << "Added " << n << " servers to channel[" << i << "]"; + } + } + return ntotal; +} + +size_t PartitionChannelBase::RemoveServersInBatch( + const std::vector& servers) { + PartitionServersIntoTemps(servers); + size_t ntotal = 0; + for (int i = 0; i < partition_count(); ++i) { + if (!_subs[i].tmp.empty()) { + size_t n = _subs[i].lb()->RemoveServersInBatch(_subs[i].tmp); + ntotal += n; + RPC_VLOG << "Removed " << n << " servers from channel[" << i << "]"; + } + } + return ntotal; +} + +void PartitionChannelBase::OnAddedServers( + const std::vector& servers) { + AddServersInBatch(servers); +} + +void PartitionChannelBase::OnRemovedServers( + const std::vector& servers) { + RemoveServersInBatch(servers); +} + +// ================= PartitionChannel ==================== + +PartitionChannelOptions::PartitionChannelOptions() + : ChannelOptions(), fail_limit(-1) { +} + +PartitionChannel::PartitionChannel() + : _pchan(NULL) + , _parser(NULL) { +} + +PartitionChannel::~PartitionChannel() { + if (_nsthread_ptr) { + if (_pchan) { + _nsthread_ptr->RemoveWatcher(_pchan); + } + _nsthread_ptr.reset(); + } + delete _pchan; + _pchan = NULL; + delete _parser; + _parser = NULL; +} + +int PartitionChannel::Init(int num_partition_kinds, + PartitionParser* partition_parser, + const char* ns_url, + const char* load_balancer_name, + const PartitionChannelOptions* options_in) { + // Force naming services to register. + GlobalInitializeOrDie(); + if (num_partition_kinds == 0) { + LOG(ERROR) << "Parameter[num_partition_kinds] must be positive"; + return -1; + } + if (NULL == partition_parser) { + LOG(ERROR) << "Parameter[partition_parser] must be non-NULL"; + return -1; + } + GetNamingServiceThreadOptions ns_opt; + if (options_in) { + ns_opt.succeed_without_server = options_in->succeed_without_server; + } + if (GetNamingServiceThread(&_nsthread_ptr, ns_url, &ns_opt) != 0) { + LOG(ERROR) << "Fail to get NamingServiceThread"; + return -1; + } + _pchan = new (std::nothrow) PartitionChannelBase; + if (NULL == _pchan) { + LOG(ERROR) << "Fail to new PartitionChannelBase"; + return -1; + } + if (_pchan->Init(num_partition_kinds, partition_parser, + load_balancer_name, options_in) != 0) { + LOG(ERROR) << "Fail to init PartitionChannelBase"; + return -1; + } + if (_nsthread_ptr->AddWatcher( + _pchan, (options_in ? options_in->ns_filter : NULL)) != 0) { + LOG(ERROR) << "Fail to add PartitionChannelBase as watcher"; + return -1; + } + // Must be last one because it's the marker of initialized(). + _parser = partition_parser; + return 0; +} + +void PartitionChannel::CallMethod( + const google::protobuf::MethodDescriptor* method, + google::protobuf::RpcController* controller, + const google::protobuf::Message* request, + google::protobuf::Message* response, + google::protobuf::Closure* done) { + if (_pchan != NULL) { + _pchan->CallMethod(method, controller, request, response, done); + } else { + Controller* cntl = static_cast(controller); + cntl->SetFailed(EINVAL, "PartitionChannel=%p is not initialized yet", + this); + // This is a branch only entered by wrongly-used RPC, just call done + // in-place. See comments in channel.cpp on deadlock concerns. + if (done) { + done->Run(); + } + } +} + +int PartitionChannel::partition_count() const { + return _pchan ? _pchan->partition_count() : 0; +} + +int PartitionChannel::CheckHealth() { + if (_pchan == NULL) { + return -1; + } + return static_cast(_pchan)->CheckHealth(); +} + +// ================= DynamicPartitionChannel ==================== + +class DynamicPartitionChannel::Partitioner : public NamingServiceWatcher { +public: + void PartitionServersIntoTemps(const std::vector& servers) { + for (PartChanMap::const_iterator it = _part_chan_map.begin(); + it != _part_chan_map.end(); ++it) { + it->second->tmp.clear(); + } + for (size_t i = 0; i < servers.size(); ++i) { + Partition part; + if (!_parser->ParseFromTag(servers[i].tag, &part)) { + LOG(ERROR) << "Fail to parse " << servers[i].tag; + continue; + } + if (part.num_partition_kinds <= 0) { + LOG(ERROR) << "Invalid num_partition_kinds=" << part.num_partition_kinds + << " in tag=`" << servers[i].tag << "'"; + continue; + } + if (part.index < 0 || part.index >= part.num_partition_kinds) { + LOG(ERROR) << "Invalid index=" << part.index << " in tag=`" + << servers[i].tag << "'"; + continue; + } + SubPartitionChannel** ppchan = _part_chan_map.seek(part.num_partition_kinds); + SubPartitionChannel* pchan = NULL; + if (ppchan == NULL) { + pchan = new (std::nothrow) SubPartitionChannel; + if (pchan == NULL) { + LOG(ERROR) << "Fail to new SubPartitionChannel"; + continue; + } + if (pchan->Init(part.num_partition_kinds, _parser, + _load_balancer_name.c_str(), &_options) != 0) { + LOG(ERROR) << "Fail to init SubPartitionChannel=#" + << part.num_partition_kinds; + delete pchan; + continue; + } + if (_schan->AddChannel(pchan, &pchan->handle) != 0) { + LOG(ERROR) << "Fail to add SubPartitionChannel=#" + << part.num_partition_kinds; + delete pchan; + continue; + } + _part_chan_map[part.num_partition_kinds] = pchan; + RPC_VLOG << "Added partition=" << part.num_partition_kinds; + } else { + pchan = *ppchan; + CHECK_EQ(part.num_partition_kinds, pchan->partition_count()); + } + + if (pchan->tmp.capacity() == 0) { + pchan->tmp.reserve(16); + } + pchan->tmp.push_back(servers[i]); + } + } + + void OnAddedServers(const std::vector& servers) { + PartitionServersIntoTemps(servers); + for (PartChanMap::const_iterator it = _part_chan_map.begin(); + it != _part_chan_map.end(); ++it) { + if (!it->second->tmp.empty()) { + size_t n = it->second->AddServersInBatch(it->second->tmp); + it->second->num_servers += n; + RPC_VLOG << "Added " << n << " servers to partition=" + << it->first; + } + } + } + + void OnRemovedServers(const std::vector& servers) { + PartitionServersIntoTemps(servers); + std::vector erased_parts; + for (PartChanMap::const_iterator it = _part_chan_map.begin(); + it != _part_chan_map.end(); ++it) { + SubPartitionChannel* partchan = it->second; + if (!partchan->tmp.empty()) { + size_t n = partchan->RemoveServersInBatch(partchan->tmp); + partchan->num_servers -= n; + RPC_VLOG << "Removed " << n << " servers from partition=" + << it->first; + if (partchan->num_servers <= 0) { + CHECK_EQ(0, partchan->num_servers); + const int npart = partchan->partition_count(); + _schan->RemoveAndDestroyChannel(partchan->handle); + // NOTE: Don't touch partchan again! + RPC_VLOG << "Removed partition=" << npart; + erased_parts.push_back(it->first); + } + } + } + for (size_t i = 0; i < erased_parts.size(); ++i) { + CHECK_EQ(1UL, _part_chan_map.erase(erased_parts[i])); + } + } + + Partitioner() + : _schan(NULL) + , _parser(NULL) + {} + + ~Partitioner() { + // Do nothing. _schan deletes all sub channels. + } + + int Init(SelectiveChannel* schan, + PartitionParser* parser, + const char* load_balancer_name, + const PartitionChannelOptions* options) { + _schan = schan; + _parser = parser; + _load_balancer_name = load_balancer_name; + if (options) { + _options = *options; + } + return 0; + } + +private: + struct SubPartitionChannel : public PartitionChannelBase { + SubPartitionChannel() : num_servers(0) {} + int num_servers; + SelectiveChannel::ChannelHandle handle; // uninitialized + std::vector tmp; + }; + typedef butil::FlatMap PartChanMap; + + PartChanMap _part_chan_map; + SelectiveChannel* _schan; + PartitionParser* _parser; + std::string _load_balancer_name; + PartitionChannelOptions _options; +}; + +DynamicPartitionChannel::DynamicPartitionChannel() + : _partitioner(NULL) + , _parser(NULL) { +} + +DynamicPartitionChannel::~DynamicPartitionChannel() { + if (_nsthread_ptr) { + if (_partitioner) { + _nsthread_ptr->RemoveWatcher(_partitioner); + } + _nsthread_ptr.reset(); + } + delete _partitioner; + _partitioner = NULL; + delete _parser; + _parser = NULL; +} + +int DynamicPartitionChannel::Init( + PartitionParser* partition_parser, + const char* ns_url, + const char* load_balancer_name, + const PartitionChannelOptions* options_in) { + GlobalInitializeOrDie(); + if (NULL == partition_parser) { + LOG(ERROR) << "Parameter[partition_parser] must be non-NULL"; + return -1; + } + GetNamingServiceThreadOptions ns_opt; + if (options_in) { + ns_opt.succeed_without_server = options_in->succeed_without_server; + } + if (GetNamingServiceThread(&_nsthread_ptr, ns_url, &ns_opt) != 0) { + LOG(ERROR) << "Fail to get NamingServiceThread"; + return -1; + } + if (_schan.Init("_dynpart", options_in) != 0) { + LOG(ERROR) << "Fail to init _schan"; + return -1; + } + _partitioner = new (std::nothrow) Partitioner; + if (NULL == _partitioner) { + LOG(ERROR) << "Fail to new Partitioner"; + return -1; + } + if (_partitioner->Init(&_schan, partition_parser, + load_balancer_name, options_in) != 0) { + LOG(ERROR) << "Fail to init Partitioner"; + return -1; + } + if (_nsthread_ptr->AddWatcher( + _partitioner, (options_in ? options_in->ns_filter : NULL)) != 0) { + LOG(ERROR) << "Fail to add Partitioner as watcher"; + return -1; + } + // Must be last one because it's the marker of initialized(). + _parser = partition_parser; + return 0; +} + +void DynamicPartitionChannel::CallMethod( + const google::protobuf::MethodDescriptor* method, + google::protobuf::RpcController* controller, + const google::protobuf::Message* request, + google::protobuf::Message* response, + google::protobuf::Closure* done) { + _schan.CallMethod(method, controller, request, response, done); +} + +} // namespace brpc diff --git a/src/brpc/partition_channel.h b/src/brpc/partition_channel.h new file mode 100644 index 0000000..f2421b8 --- /dev/null +++ b/src/brpc/partition_channel.h @@ -0,0 +1,174 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_PARTITION_CHANNEL_H +#define BRPC_PARTITION_CHANNEL_H + +// To brpc developers: This is a header included by user, don't depend +// on internal structures, use opaque pointers instead. + +#include "brpc/parallel_channel.h" +#include "brpc/selective_channel.h" // For DynamicPartitionChannel + + +namespace brpc { + +class NamingServiceThread; +class PartitionChannelBase; + +// Representing a partition kind. +struct Partition { + // Index of the partition kind, counting from 0. + int index; + + // Number of partition kinds, a partition kind may have more than one + // instances. + int num_partition_kinds; +}; + +// Parse partition from a string tag which is often associated with servers +// in NamingServices. +class PartitionParser { +public: + virtual ~PartitionParser() {} + + // Implement this method to get partition `out' from string `tag'. + // Returns true on success. + virtual bool ParseFromTag(const std::string& tag, Partition* out) = 0; +}; + +// For customizing PartitionChannel. +struct PartitionChannelOptions : public ChannelOptions { + // Constructed with default values. + PartitionChannelOptions(); + + // Make RPC call stop soon (without waiting for the timeout) when failed + // sub calls reached this number. + // Default: number of sub channels, which means the RPC to ParallChannel + // will not be canceled until all sub calls failed. + int fail_limit; + + // Check comments on ParallelChannel.AddChannel in parallel_channel.h + // Sub channels in PartitionChannel share the same mapper and merger. + butil::intrusive_ptr call_mapper; + butil::intrusive_ptr response_merger; +}; + +// PartitionChannel is a specialized ParallelChannel whose sub channels are +// built from a NamingService which specifies partitioning information in +// tags. This channel eases access to partitioned servers. +class PartitionChannel : public ChannelBase { +public: + PartitionChannel(); + ~PartitionChannel(); + + // Initialize this PartitionChannel with `num_partition_kinds' sub channels + // sending requests to different partitions listed in `naming_service_url'. + // `partition_parser' parses partition from tags associated with servers. + // When this method succeeds, `partition_parser' is owned by this channel, + // otherwise `partition_parser' is unmodified and can be used for other + // usages. + // For example: + // num_partition_kinds = 3 + // partition_parser = parse N/M as Partition{index=N, num_partition_kinds=M} + // naming_service = s1(tag=1/3) s2(tag=2/3) s3(tag=0/3) s4(tag=1/4) s5(tag=2/3) + // load_balancer = rr + // 3 sub channels(c0,c1,c2) will be created: + // - c0 sends requests to s3 because the tag=0/3 means s3 is the first + // partition kind in 3 kinds. + // - c1 sends requests to s1 because the tag=1/3 means s1 is the second + // partition kind in 3 kinds. s4(tag=1/4) is ignored because number of + // partition kinds does not match. + // - c2 sends requests to s2 and s5 because the tags=2/3 means they're + // both the third partition kind in 3 kinds. s2 and s5 will be load- + // balanced with "rr" algorithm. + // / c0 -> s3 (rr) + // request -> PartitionChannel -- c1 -> s1 (rr) + // \ c2 -> s2, s5 (rr) + int Init(int num_partition_kinds, + PartitionParser* partition_parser, + const char* naming_service_url, + const char* load_balancer_name, + const PartitionChannelOptions* options); + + // Access sub channels corresponding to partitions in parallel. + void CallMethod(const google::protobuf::MethodDescriptor* method, + google::protobuf::RpcController* controller, + const google::protobuf::Message* request, + google::protobuf::Message* response, + google::protobuf::Closure* done); + + int partition_count() const; + +private: + bool initialized() const { return _parser != NULL; } + + int CheckHealth(); + + PartitionChannelBase* _pchan; + butil::intrusive_ptr _nsthread_ptr; + PartitionParser* _parser; +}; + +// As the name implies, this combo channel discovers differently partitioned +// servers and builds sub PartitionChannels on-the-fly for different groups +// of servers. When multiple partitioning methods co-exist, traffic is +// splitted based on capacities, namely # of servers in groups. The main +// purpose of this channel is to transit from one partitioning method to +// another smoothly. For example, with proper deployment, servers can be +// changed from M-partitions to N-partitions losslessly without changing the +// client code. +class DynamicPartitionChannel : public ChannelBase { +public: + DynamicPartitionChannel(); + ~DynamicPartitionChannel(); + + // Unlike PartitionChannel, DynamicPartitionChannel does not need + // `num_partition_kinds'. It discovers and groups differently partitioned + // servers automatically. + int Init(PartitionParser* partition_parser, + const char* naming_service_url, + const char* load_balancer_name, + const PartitionChannelOptions* options); + + // Access partitions according to their capacities. + void CallMethod(const google::protobuf::MethodDescriptor* method, + google::protobuf::RpcController* controller, + const google::protobuf::Message* request, + google::protobuf::Message* response, + google::protobuf::Closure* done); + +private: + bool initialized() const { return _parser != NULL; } + + int CheckHealth() { + return static_cast(&_schan)->CheckHealth(); + } + + class Partitioner; + + SelectiveChannel _schan; + Partitioner* _partitioner; + butil::intrusive_ptr _nsthread_ptr; + PartitionParser* _parser; +}; + +} // namespace brpc + + +#endif // BRPC_PARTITION_CHANNEL_H diff --git a/src/brpc/pb_compat.h b/src/brpc/pb_compat.h new file mode 100644 index 0000000..68874c1 --- /dev/null +++ b/src/brpc/pb_compat.h @@ -0,0 +1,52 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_PB_COMPAT_H +#define BRPC_PB_COMPAT_H + +#if GOOGLE_PROTOBUF_VERSION < 5027000 +# define PB_527_OVERRIDE override +#else +# define PB_527_OVERRIDE +#endif + +#if GOOGLE_PROTOBUF_VERSION < 5026000 +# define PB_526_OVERRIDE override +#else +# define PB_526_OVERRIDE +#endif + +#if GOOGLE_PROTOBUF_VERSION < 4025000 +# define PB_425_OVERRIDE override +#else +# define PB_425_OVERRIDE +#endif + +#if GOOGLE_PROTOBUF_VERSION < 3021000 +# define PB_321_OVERRIDE override +#else +# define PB_321_OVERRIDE +#endif + +#if GOOGLE_PROTOBUF_VERSION < 3010000 +# define PB_310_OVERRIDE override +#else +# define PB_310_OVERRIDE +#endif + +#endif // BRPC_PB_COMPAT_H diff --git a/src/brpc/periodic_naming_service.cpp b/src/brpc/periodic_naming_service.cpp new file mode 100644 index 0000000..69d403c --- /dev/null +++ b/src/brpc/periodic_naming_service.cpp @@ -0,0 +1,75 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/logging.h" +#include "bthread/bthread.h" +#include "brpc/log.h" +#include "brpc/reloadable_flags.h" +#include "brpc/periodic_naming_service.h" + +namespace brpc { + +DEFINE_int32(ns_access_interval, 5, + "Wait so many seconds before next access to naming service"); +BRPC_VALIDATE_GFLAG(ns_access_interval, PositiveInteger); + +int PeriodicNamingService::GetNamingServiceAccessIntervalMs() const { + return std::max(FLAGS_ns_access_interval, 1) * 1000; +} + +int PeriodicNamingService::RunNamingService( + const char* service_name, NamingServiceActions* actions) { + std::vector servers; + bool ever_reset = false; + while (true) { + servers.clear(); + const int rc = GetServers(service_name, &servers); + if (rc == 0) { + ever_reset = true; + actions->ResetServers(servers); + } else if (!ever_reset) { + // ResetServers must be called at first time even if GetServers + // failed, to wake up callers to `WaitForFirstBatchOfServers' + ever_reset = true; + servers.clear(); + actions->ResetServers(servers); + } + + // If `bthread_stop' is called to stop the ns bthread when `brpc::Join‘ is called + // in `GetServers' to wait for a rpc to complete. The bthread will be woken up, + // reset `TaskMeta::interrupted' and continue to join the rpc. After the rpc is complete, + // `bthread_usleep' will not sense the interrupt signal and sleep successfully. + // Finally, the ns bthread will never exit. So need to check the stop status of + // the bthread here and exit the bthread in time. + if (bthread_stopped(bthread_self())) { + RPC_VLOG << "Quit NamingServiceThread=" << bthread_self(); + return 0; + } + if (bthread_usleep(GetNamingServiceAccessIntervalMs() * 1000UL) < 0) { + if (errno == ESTOP) { + RPC_VLOG << "Quit NamingServiceThread=" << bthread_self(); + return 0; + } + PLOG(FATAL) << "Fail to sleep"; + return -1; + } + } +} + +} // namespace brpc diff --git a/src/brpc/periodic_naming_service.h b/src/brpc/periodic_naming_service.h new file mode 100644 index 0000000..7e51114 --- /dev/null +++ b/src/brpc/periodic_naming_service.h @@ -0,0 +1,41 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_PERIODIC_NAMING_SERVICE_H +#define BRPC_PERIODIC_NAMING_SERVICE_H + +#include "brpc/naming_service.h" + + +namespace brpc { + +class PeriodicNamingService : public NamingService { +protected: + virtual int GetServers(const char *service_name, + std::vector* servers) = 0; + + virtual int GetNamingServiceAccessIntervalMs() const; + + int RunNamingService(const char* service_name, + NamingServiceActions* actions) override; +}; + +} // namespace brpc + + +#endif // BRPC_PERIODIC_NAMING_SERVICE_H diff --git a/src/brpc/periodic_task.cpp b/src/brpc/periodic_task.cpp new file mode 100644 index 0000000..3ba9c0e --- /dev/null +++ b/src/brpc/periodic_task.cpp @@ -0,0 +1,67 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include "brpc/periodic_task.h" + +namespace brpc { + +PeriodicTask::~PeriodicTask() { +} + +static void* PeriodicTaskThread(void* arg) { + PeriodicTask* task = static_cast(arg); + timespec abstime; + if (!task->OnTriggeringTask(&abstime)) { // end + task->OnDestroyingTask(); + return NULL; + } + PeriodicTaskManager::StartTaskAt(task, abstime); + return NULL; +} + +static void RunPeriodicTaskThread(void* arg) { + bthread_t th = 0; + bthread_attr_t attr = BTHREAD_ATTR_NORMAL; + bthread_attr_set_name(&attr, "PeriodicTaskThread"); + int rc = bthread_start_background( + &th, &attr, PeriodicTaskThread, arg); + if (rc != 0) { + LOG(ERROR) << "Fail to start PeriodicTaskThread"; + static_cast(arg)->OnDestroyingTask(); + return; + } +} + +void PeriodicTaskManager::StartTaskAt(PeriodicTask* task, const timespec& abstime) { + if (task == NULL) { + LOG(ERROR) << "Param[task] is NULL"; + return; + } + bthread_timer_t timer_id; + const int rc = bthread_timer_add( + &timer_id, abstime, RunPeriodicTaskThread, task); + if (rc != 0) { + LOG(ERROR) << "Fail to add timer for RunPerodicTaskThread"; + task->OnDestroyingTask(); + return; + } +} + +} // namespace brpc diff --git a/src/brpc/periodic_task.h b/src/brpc/periodic_task.h new file mode 100644 index 0000000..88dc739 --- /dev/null +++ b/src/brpc/periodic_task.h @@ -0,0 +1,50 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_PERIODIC_TASK_H +#define BRPC_PERIODIC_TASK_H + +#include + +namespace brpc { + +// Override OnTriggeringTask() with code that needs to be periodically run. If +// the task is completed, the method should return false; Otherwise the method +// should return true and set `next_abstime' to the time that the task should +// be run next time. +// Each call to OnTriggeringTask() is run in a separated bthread which can be +// suspended. To preserve states between different calls, put the states as +// fields of (subclass of) PeriodicTask. +// If any error occurs or OnTriggeringTask() returns false, the task is called +// with OnDestroyingTask() and will not be scheduled anymore. +class PeriodicTask { +public: + virtual ~PeriodicTask(); + virtual bool OnTriggeringTask(timespec* next_abstime) = 0; + virtual void OnDestroyingTask() = 0; +}; + +class PeriodicTaskManager { +public: + static void StartTaskAt(PeriodicTask* task, const timespec& abstime); +}; + + +} // namespace brpc + +#endif // BRPC_PERIODIC_TASK_H diff --git a/src/brpc/policy/auto_concurrency_limiter.cpp b/src/brpc/policy/auto_concurrency_limiter.cpp new file mode 100644 index 0000000..e9cce0f --- /dev/null +++ b/src/brpc/policy/auto_concurrency_limiter.cpp @@ -0,0 +1,300 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include "brpc/errno.pb.h" +#include "brpc/policy/auto_concurrency_limiter.h" + +namespace bthread { + +DECLARE_int32(bthread_concurrency); + +} // namespace bthread + +namespace brpc { +namespace policy { + +DEFINE_int32(auto_cl_sample_window_size_ms, 1000, "Duration of the sampling window."); +DEFINE_int32(auto_cl_min_sample_count, 100, + "During the duration of the sampling window, if the number of " + "requests collected is less than this value, the sampling window " + "will be discarded."); +DEFINE_int32(auto_cl_max_sample_count, 200, + "During the duration of the sampling window, once the number of " + "requests collected is greater than this value, even if the " + "duration of the window has not ended, the max_concurrency will " + "be updated and a new sampling window will be started."); +DEFINE_double(auto_cl_sampling_interval_ms, 0.1, + "Interval for sampling request in auto concurrency limiter"); +DEFINE_int32(auto_cl_initial_max_concurrency, 40, + "Initial max concurrency for gradient concurrency limiter"); +DEFINE_int32(auto_cl_noload_latency_remeasure_interval_ms, 50000, + "Interval for remeasurement of noload_latency. In the period of " + "remeasurement of noload_latency will halve max_concurrency."); +DEFINE_double(auto_cl_alpha_factor_for_ema, 0.1, + "The smoothing coefficient used in the calculation of ema, " + "the value range is 0-1. The smaller the value, the smaller " + "the effect of a single sample_window on max_concurrency."); +DEFINE_bool(auto_cl_enable_error_punish, true, + "Whether to consider failed requests when calculating maximum concurrency"); +DEFINE_double(auto_cl_fail_punish_ratio, 1.0, + "Use the failed requests to punish normal requests. The larger " + "the configuration item, the more aggressive the penalty strategy."); +DEFINE_double(auto_cl_max_explore_ratio, 0.3, + "The larger the value, the higher the tolerance of the server to " + "the fluctuation of latency at low load, and the the greater the " + "maximum growth rate of qps. Correspondingly, the server will have " + "a higher latency for a short period of time after the overload."); +DEFINE_double(auto_cl_min_explore_ratio, 0.06, + "Auto concurrency limiter will perform fault tolerance based on " + "this parameter when judging the load situation of the server. " + "It should be a positive value close to 0, the larger it is, " + "the higher the latency of the server at full load."); +DEFINE_double(auto_cl_change_rate_of_explore_ratio, 0.02, + "The speed of change of auto_cl_max_explore_ratio when the " + "load situation of the server changes, The value range is " + "(0 - `max_explore_ratio')"); +DEFINE_double(auto_cl_reduce_ratio_while_remeasure, 0.9, + "This value affects the reduction ratio to mc during retesting " + "noload_latency. The value range is (0-1)"); +DEFINE_int32(auto_cl_latency_fluctuation_correction_factor, 1, + "Affect the judgement of the server's load situation. The larger " + "the value, the higher the tolerance for the fluctuation of the " + "latency. If the value is too large, the latency will be higher " + "when the server is overloaded."); +DEFINE_double(auto_cl_error_rate_punish_threshold, 0, + "Threshold for error-rate-based punishment attenuation. " + "Valid range: [0, 1). 0 (default) disables the feature. " + "Values >= 1 are ignored and treated as 0. " + "e.g. 0.1: error rates below 10%% produce zero punishment; " + "above it the punishment scales linearly from 0 to full strength. " + "Only effective when auto_cl_enable_error_punish is true."); + +AutoConcurrencyLimiter::AutoConcurrencyLimiter() + : _max_concurrency(FLAGS_auto_cl_initial_max_concurrency) + , _remeasure_start_us(NextResetTime(butil::cpuwide_time_us())) + , _reset_latency_us(0) + , _min_latency_us(-1) + , _ema_max_qps(-1) + , _explore_ratio(FLAGS_auto_cl_max_explore_ratio) + , _last_sampling_time_us(0) + , _total_succ_req(0) { +} + +AutoConcurrencyLimiter* AutoConcurrencyLimiter::New(const AdaptiveMaxConcurrency&) const { + return new (std::nothrow) AutoConcurrencyLimiter; +} + +bool AutoConcurrencyLimiter::OnRequested(int current_concurrency, Controller*) { + return current_concurrency <= _max_concurrency; +} + +void AutoConcurrencyLimiter::OnResponded(int error_code, int64_t latency_us) { + if (0 == error_code) { + _total_succ_req.fetch_add(1, butil::memory_order_relaxed); + } else if (ELIMIT == error_code) { + return; + } + + const int64_t now_time_us = butil::cpuwide_time_us(); + int64_t last_sampling_time_us = + _last_sampling_time_us.load(butil::memory_order_relaxed); + + if (last_sampling_time_us == 0 || + now_time_us - last_sampling_time_us >= + FLAGS_auto_cl_sampling_interval_ms * 1000) { + bool sample_this_call = _last_sampling_time_us.compare_exchange_strong( + last_sampling_time_us, now_time_us, butil::memory_order_relaxed); + if (sample_this_call) { + bool sample_window_submitted = AddSample(error_code, latency_us, + now_time_us); + if (sample_window_submitted) { + // The following log prints has data-race in extreme cases, + // unless you are in debug, you should not open it. + VLOG(1) + << "Sample window submitted, current max_concurrency:" + << _max_concurrency + << ", min_latency_us:" << _min_latency_us + << ", ema_max_qps:" << _ema_max_qps + << ", explore_ratio:" << _explore_ratio; + } + } + } +} + +int AutoConcurrencyLimiter::MaxConcurrency() { + return _max_concurrency; +} + +int AutoConcurrencyLimiter::ResetMaxConcurrency(const AdaptiveMaxConcurrency&) { + return -1; +} + +int64_t AutoConcurrencyLimiter::NextResetTime(int64_t sampling_time_us) { + int64_t reset_start_us = sampling_time_us + + (FLAGS_auto_cl_noload_latency_remeasure_interval_ms / 2 + + butil::fast_rand_less_than(FLAGS_auto_cl_noload_latency_remeasure_interval_ms / 2)) * 1000; + return reset_start_us; +} + +bool AutoConcurrencyLimiter::AddSample(int error_code, + int64_t latency_us, + int64_t sampling_time_us) { + std::unique_lock lock_guard(_sw_mutex); + if (_reset_latency_us != 0) { + // min_latency is about to be reset soon. + if (_reset_latency_us > sampling_time_us) { + // ignoring samples during waiting for the deadline. + return false; + } + // Remeasure min_latency when concurrency has dropped to low load + _min_latency_us = -1; + _reset_latency_us = 0; + _remeasure_start_us = NextResetTime(sampling_time_us); + ResetSampleWindow(sampling_time_us); + } + + if (_sw.start_time_us == 0) { + _sw.start_time_us = sampling_time_us; + } + + if (error_code != 0 && FLAGS_auto_cl_enable_error_punish) { + ++_sw.failed_count; + _sw.total_failed_us += latency_us; + } else if (error_code == 0) { + ++_sw.succ_count; + _sw.total_succ_us += latency_us; + } + + if (_sw.succ_count + _sw.failed_count < FLAGS_auto_cl_min_sample_count) { + if (sampling_time_us - _sw.start_time_us >= + FLAGS_auto_cl_sample_window_size_ms * 1000) { + // If the sample size is insufficient at the end of the sampling + // window, discard the entire sampling window + ResetSampleWindow(sampling_time_us); + } + return false; + } + if (sampling_time_us - _sw.start_time_us < + FLAGS_auto_cl_sample_window_size_ms * 1000 && + _sw.succ_count + _sw.failed_count < FLAGS_auto_cl_max_sample_count) { + return false; + } + + if(_sw.succ_count > 0) { + UpdateMaxConcurrency(sampling_time_us); + } else { + // All request failed + AdjustMaxConcurrency(_max_concurrency / 2); + } + ResetSampleWindow(sampling_time_us); + return true; +} + +void AutoConcurrencyLimiter::ResetSampleWindow(int64_t sampling_time_us) { + _total_succ_req.exchange(0, butil::memory_order_relaxed); + _sw.start_time_us = sampling_time_us; + _sw.succ_count = 0; + _sw.failed_count = 0; + _sw.total_failed_us = 0; + _sw.total_succ_us = 0; +} + +void AutoConcurrencyLimiter::UpdateMinLatency(int64_t latency_us) { + const double ema_factor = FLAGS_auto_cl_alpha_factor_for_ema; + if (_min_latency_us <= 0) { + _min_latency_us = latency_us; + } else if (latency_us < _min_latency_us) { + _min_latency_us = latency_us * ema_factor + _min_latency_us * (1 - ema_factor); + } +} + +void AutoConcurrencyLimiter::UpdateQps(double qps) { + const double ema_factor = FLAGS_auto_cl_alpha_factor_for_ema / 10; + if (qps >= _ema_max_qps) { + _ema_max_qps = qps; + } else { + _ema_max_qps = qps * ema_factor + _ema_max_qps * (1 - ema_factor); + } +} + +void AutoConcurrencyLimiter::AdjustMaxConcurrency(int next_max_concurrency) { + next_max_concurrency = std::max(bthread::FLAGS_bthread_concurrency, next_max_concurrency); + if (next_max_concurrency != _max_concurrency) { + _max_concurrency = next_max_concurrency; + } +} + +void AutoConcurrencyLimiter::UpdateMaxConcurrency(int64_t sampling_time_us) { + int32_t total_succ_req = _total_succ_req.load(butil::memory_order_relaxed); + double failed_punish = _sw.total_failed_us * FLAGS_auto_cl_fail_punish_ratio; + + // Threshold-based attenuation: when 0 < threshold < 1, attenuate punishment + // based on error rate. Inspired by Sentinel's threshold-based circuit breaker: + // low error rates should not inflate avg_latency. Above threshold, punishment + // scales linearly from 0 to full strength. + // Invalid values (<=0 or >=1) skip this block entirely, preserving original behavior. + if (FLAGS_auto_cl_error_rate_punish_threshold > 0 && + FLAGS_auto_cl_error_rate_punish_threshold < 1.0 && + _sw.failed_count > 0) { + double threshold = FLAGS_auto_cl_error_rate_punish_threshold; + double error_rate = static_cast(_sw.failed_count) / + (_sw.succ_count + _sw.failed_count); + if (error_rate <= threshold) { + // Error rate within dead zone, cancel punishment. + failed_punish = 0; + } else { + // Linear ramp: 0 at threshold, 1.0 at 100% error rate. + double punish_factor = (error_rate - threshold) / (1.0 - threshold); + failed_punish *= punish_factor; + } + } + + int64_t avg_latency = + std::ceil((failed_punish + _sw.total_succ_us) / _sw.succ_count); + double qps = 1000000.0 * total_succ_req / (sampling_time_us - _sw.start_time_us); + UpdateMinLatency(avg_latency); + UpdateQps(qps); + + int next_max_concurrency = 0; + // Remeasure min_latency at regular intervals + if (_remeasure_start_us <= sampling_time_us) { + const double reduce_ratio = FLAGS_auto_cl_reduce_ratio_while_remeasure; + _reset_latency_us = sampling_time_us + avg_latency * 2; + next_max_concurrency = + std::ceil(_ema_max_qps * _min_latency_us / 1000000 * reduce_ratio); + } else { + const double change_step = FLAGS_auto_cl_change_rate_of_explore_ratio; + const double max_explore_ratio = FLAGS_auto_cl_max_explore_ratio; + const double min_explore_ratio = FLAGS_auto_cl_min_explore_ratio; + const double correction_factor = FLAGS_auto_cl_latency_fluctuation_correction_factor; + if (avg_latency <= _min_latency_us * (1.0 + min_explore_ratio * correction_factor) || + qps <= _ema_max_qps / (1.0 + min_explore_ratio)) { + _explore_ratio = std::min(max_explore_ratio, _explore_ratio + change_step); + } else { + _explore_ratio = std::max(min_explore_ratio, _explore_ratio - change_step); + } + next_max_concurrency = + _min_latency_us * _ema_max_qps / 1000000 * (1 + _explore_ratio); + } + + AdjustMaxConcurrency(next_max_concurrency); +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/auto_concurrency_limiter.h b/src/brpc/policy/auto_concurrency_limiter.h new file mode 100644 index 0000000..d221f73 --- /dev/null +++ b/src/brpc/policy/auto_concurrency_limiter.h @@ -0,0 +1,90 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_POLICY_AUTO_CONCURRENCY_LIMITER_H +#define BRPC_POLICY_AUTO_CONCURRENCY_LIMITER_H + +#include "bvar/bvar.h" +#include "butil/containers/bounded_queue.h" +#include "brpc/concurrency_limiter.h" + +namespace brpc { +namespace policy { + +class AutoConcurrencyLimiter : public ConcurrencyLimiter { +public: + AutoConcurrencyLimiter(); + + bool OnRequested(int current_concurrency, Controller*) override; + + void OnResponded(int error_code, int64_t latency_us) override; + + int MaxConcurrency() override; + + int ResetMaxConcurrency(const AdaptiveMaxConcurrency&) override; + + AutoConcurrencyLimiter* New(const AdaptiveMaxConcurrency&) const override; + +private: + struct SampleWindow { + SampleWindow() + : start_time_us(0) + , succ_count(0) + , failed_count(0) + , total_failed_us(0) + , total_succ_us(0) {} + int64_t start_time_us; + int32_t succ_count; + int32_t failed_count; + int64_t total_failed_us; + int64_t total_succ_us; + }; + + bool AddSample(int error_code, int64_t latency_us, int64_t sampling_time_us); + int64_t NextResetTime(int64_t sampling_time_us); + + // The following methods are not thread safe and can only be called + // in AppSample() + void UpdateMaxConcurrency(int64_t sampling_time_us); + void ResetSampleWindow(int64_t sampling_time_us); + void UpdateMinLatency(int64_t latency_us); + void UpdateQps(double qps); + + void AdjustMaxConcurrency(int next_max_concurrency); + + // modified per sample-window or more + int _max_concurrency; + int64_t _remeasure_start_us; + int64_t _reset_latency_us; + int64_t _min_latency_us; + double _ema_max_qps; + double _explore_ratio; + + // modified per sample. + BAIDU_CACHELINE_ALIGNMENT butil::atomic _last_sampling_time_us; + butil::Mutex _sw_mutex; + SampleWindow _sw; + + // modified per request. + BAIDU_CACHELINE_ALIGNMENT butil::atomic _total_succ_req; +}; + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_AUTO_CONCURRENCY_LIMITER_H diff --git a/src/brpc/policy/baidu_naming_service.cpp b/src/brpc/policy/baidu_naming_service.cpp new file mode 100644 index 0000000..1dc6865 --- /dev/null +++ b/src/brpc/policy/baidu_naming_service.cpp @@ -0,0 +1,80 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +#ifdef BAIDU_INTERNAL + +#include //webfoot::* +#include //BnsInput BnsOutput +#include "butil/logging.h" // CHECK +#include "brpc/policy/baidu_naming_service.h" + +namespace brpc { +namespace policy { + +int BaiduNamingService::GetServers(const char *service_name, + std::vector* servers) { + servers->clear(); + BnsInput input; + input.set_service_name(service_name); + BnsOutput output; + const int rc = webfoot::get_instance_by_service(input, &output); + if (rc != webfoot::WEBFOOT_RET_SUCCESS) { + if (rc != webfoot::WEBFOOT_SERVICE_BEYOND_THRSHOLD) { + LOG(WARNING) << "Fail to get servers of `" << service_name + << "', " << webfoot::error_to_string(rc); + return -1; + } else { + // NOTE: output is valid for this error, just print a warning. + LOG(WARNING) << webfoot::error_to_string(rc); + } + } + const int instance_number = output.instance_size(); + if (instance_number == 0) { + LOG(WARNING) << "No server attached to `" << service_name << "'"; + return 0; + } + for (int i = 0; i < instance_number; i++) { + const BnsInstance& instance = output.instance(i); + if (instance.status() == 0) { + butil::ip_t ip; + if (butil::str2ip(instance.host_ip().c_str(), &ip) != 0) { + LOG(WARNING) << "Invalid ip=" << instance.host_ip(); + continue; + } + servers->push_back(ServerNode(ip, instance.port(), instance.tag())); + } + } + return 0; +} + +void BaiduNamingService::Describe( + std::ostream& os, const DescribeOptions&) const { + os << "bns"; + return; +} + +NamingService* BaiduNamingService::New() const { + return new BaiduNamingService; +} + +void BaiduNamingService::Destroy() { + delete this; +} + +} // namespace policy +} // namespace brpc +#endif // BAIDU_INTERNAL diff --git a/src/brpc/policy/baidu_naming_service.h b/src/brpc/policy/baidu_naming_service.h new file mode 100644 index 0000000..6992425 --- /dev/null +++ b/src/brpc/policy/baidu_naming_service.h @@ -0,0 +1,48 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +#ifdef BAIDU_INTERNAL + +#ifndef BRPC_POLICY_BAIDU_NAMING_SERVICE_H +#define BRPC_POLICY_BAIDU_NAMING_SERVICE_H + +#include "brpc/periodic_naming_service.h" + +namespace brpc { +namespace policy { + +// Acquire server list from Baidu-Naming-Service, aka BNS +class BaiduNamingService : public PeriodicNamingService { +public: + // You can specify port by appending port selector: + // e.g.: bns://DPOP-inner-API-inner-API.jpaas.hosts:main + // ^^^^^ + int GetServers(const char *service_name, + std::vector* servers); + + void Describe(std::ostream& os, const DescribeOptions&) const; + + NamingService* New() const; + + void Destroy(); +}; + +} // namespace policy +} // namespace brpc + +#endif //BRPC_POLICY_BAIDU_NAMING_SERVICE_H +#endif // BAIDU_INTERNAL diff --git a/src/brpc/policy/baidu_rpc_meta.proto b/src/brpc/policy/baidu_rpc_meta.proto new file mode 100644 index 0000000..5591c5d --- /dev/null +++ b/src/brpc/policy/baidu_rpc_meta.proto @@ -0,0 +1,55 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +import "brpc/options.proto"; +import "brpc/streaming_rpc_meta.proto"; + +package brpc.policy; +option java_package="com.brpc.policy"; +option java_outer_classname="BaiduRpcProto"; + +message RpcMeta { + optional RpcRequestMeta request = 1; + optional RpcResponseMeta response = 2; + optional int32 compress_type = 3; + optional int64 correlation_id = 4; + optional int32 attachment_size = 5; + optional ChunkInfo chunk_info = 6; + optional bytes authentication_data = 7; + optional StreamSettings stream_settings = 8; + map user_fields = 9; + optional ContentType content_type = 10; + optional int32 checksum_type = 11; + optional bytes checksum_value = 12; +} + +message RpcRequestMeta { + required string service_name = 1; + required string method_name = 2; + optional int64 log_id = 3; + optional int64 trace_id = 4; + optional int64 span_id = 5; + optional int64 parent_span_id = 6; + optional string request_id = 7; // correspond to x-request-id in http header + optional int32 timeout_ms = 8; // client's timeout setting for current call +} + +message RpcResponseMeta { + optional int32 error_code = 1; + optional string error_text = 2; +} diff --git a/src/brpc/policy/baidu_rpc_protocol.cpp b/src/brpc/policy/baidu_rpc_protocol.cpp new file mode 100644 index 0000000..2c5a7e7 --- /dev/null +++ b/src/brpc/policy/baidu_rpc_protocol.cpp @@ -0,0 +1,1151 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // MethodDescriptor +#include // Message +#include +#include +#include + +#include "butil/iobuf.h" // butil::IOBuf +#include "butil/logging.h" // LOG() +#include "butil/memory/scope_guard.h" +#include "butil/raw_pack.h" // RawPacker RawUnpacker +#include "butil/strings/string_util.h" + +#include "json2pb/json_to_pb.h" +#include "json2pb/pb_to_json.h" +#include "brpc/controller.h" // Controller +#include "brpc/socket.h" // Socket +#include "brpc/server.h" // Server +#include "brpc/span.h" +#include "brpc/compress.h" // ParseFromCompressedData +#include "brpc/checksum.h" +#include "brpc/stream_impl.h" +#include "brpc/rpc_dump.h" // SampledRequest +#include "brpc/rpc_pb_message_factory.h" +#include "brpc/policy/baidu_rpc_meta.pb.h" // RpcRequestMeta +#include "brpc/policy/baidu_rpc_protocol.h" +#include "brpc/policy/most_common_message.h" +#include "brpc/policy/streaming_rpc_protocol.h" +#include "brpc/details/usercode_backup_pool.h" +#include "brpc/details/controller_private_accessor.h" +#include "brpc/details/server_private_accessor.h" + +extern "C" { +void bthread_assign_data(void* data); +} + + +namespace brpc { +namespace policy { + +DEFINE_bool(baidu_protocol_use_fullname, true, + "If this flag is true, baidu_std puts service.full_name in requests" + ", otherwise puts service.name (required by jprotobuf)."); + +DEFINE_bool(baidu_std_protocol_deliver_timeout_ms, false, + "If this flag is true, baidu_std puts timeout_ms in requests."); + +DECLARE_bool(pb_enum_as_number); + +// Notes: +// 1. 12-byte header [PRPC][body_size][meta_size] +// 2. body_size and meta_size are in network byte order +// 3. Use service->full_name() + method_name to specify the method to call +// 4. `attachment_size' is set iff request/response has attachment +// 5. Not supported: chunk_info + +// Pack header into `buf' +inline void PackRpcHeader(char* rpc_header, uint32_t meta_size, int payload_size) { + uint32_t* dummy = (uint32_t*)rpc_header; // suppress strict-alias warning + *dummy = *(uint32_t*)"PRPC"; + butil::RawPacker(rpc_header + 4) + .pack32(meta_size + payload_size) + .pack32(meta_size); +} + +static void SerializeRpcHeaderAndMeta( + butil::IOBuf* out, const RpcMeta& meta, int payload_size) { + const uint32_t meta_size = GetProtobufByteSize(meta); + if (meta_size <= 244) { // most common cases + char header_and_meta[12 + meta_size]; + PackRpcHeader(header_and_meta, meta_size, payload_size); + ::google::protobuf::io::ArrayOutputStream arr_out(header_and_meta + 12, meta_size); + ::google::protobuf::io::CodedOutputStream coded_out(&arr_out); + meta.SerializeWithCachedSizes(&coded_out); // not calling ByteSize again + CHECK(!coded_out.HadError()); + CHECK_EQ(0, out->append(header_and_meta, sizeof(header_and_meta))); + } else { + char header[12]; + PackRpcHeader(header, meta_size, payload_size); + CHECK_EQ(0, out->append(header, sizeof(header))); + butil::IOBufAsZeroCopyOutputStream buf_stream(out); + ::google::protobuf::io::CodedOutputStream coded_out(&buf_stream); + meta.SerializeWithCachedSizes(&coded_out); + CHECK(!coded_out.HadError()); + } +} + +ParseResult ParseRpcMessage(butil::IOBuf* source, Socket* socket, + bool /*read_eof*/, const void*) { + char header_buf[12]; + const size_t n = source->copy_to(header_buf, sizeof(header_buf)); + if (n >= 4) { + void* dummy = header_buf; + if (*(const uint32_t*)dummy != *(const uint32_t*)"PRPC") { + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + } else { + if (memcmp(header_buf, "PRPC", n) != 0) { + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + } + if (n < sizeof(header_buf)) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + uint32_t body_size; + uint32_t meta_size; + butil::RawUnpacker(header_buf + 4).unpack32(body_size).unpack32(meta_size); + if (body_size > FLAGS_max_body_size) { + // We need this log to report the body_size to give users some clues + // which is not printed in InputMessenger. + LOG(ERROR) << "body_size=" << body_size << " from " + << socket->remote_side() << " is too large"; + return MakeParseError(PARSE_ERROR_TOO_BIG_DATA); + } else if (source->length() < sizeof(header_buf) + body_size) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + if (meta_size > body_size) { + LOG(ERROR) << "meta_size=" << meta_size << " is bigger than body_size=" + << body_size; + // Pop the message + source->pop_front(sizeof(header_buf) + body_size); + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + source->pop_front(sizeof(header_buf)); + MostCommonMessage* msg = MostCommonMessage::Get(); + source->cutn(&msg->meta, meta_size); + source->cutn(&msg->payload, body_size - meta_size); + return MakeMessage(msg); +} + +bool SerializeRpcMessage(const google::protobuf::Message& message, + Controller& cntl, ContentType content_type, + CompressType compress_type, ChecksumType checksum_type, + butil::IOBuf* buf) { + auto serialize = [&](Serializer& serializer) -> bool { + bool ok; + if (COMPRESS_TYPE_NONE == compress_type) { + butil::IOBufAsZeroCopyOutputStream stream(buf); + ok = serializer.SerializeTo(&stream); + } else { + const CompressHandler* handler = FindCompressHandler(compress_type); + if (NULL == handler) { + return false; + } + ok = handler->Compress(serializer, buf); + } + ChecksumIn checksum_in{buf, &cntl}; + ComputeDataChecksum(checksum_in, checksum_type); + return ok; + }; + + if (CONTENT_TYPE_PB == content_type) { + Serializer serializer([&message](google::protobuf::io::ZeroCopyOutputStream* output) -> bool { + return message.SerializeToZeroCopyStream(output); + }); + return serialize(serializer); + } else if (CONTENT_TYPE_JSON == content_type) { + Serializer serializer([&message, &cntl](google::protobuf::io::ZeroCopyOutputStream* output) -> bool { + json2pb::Pb2JsonOptions options; + options.bytes_to_base64 = cntl.has_pb_bytes_to_base64(); + options.jsonify_empty_array = cntl.has_pb_jsonify_empty_array(); + options.always_print_primitive_fields = cntl.has_always_print_primitive_fields(); + options.single_repeated_to_array = cntl.has_pb_single_repeated_to_array(); + options.enum_option = FLAGS_pb_enum_as_number + ? json2pb::OUTPUT_ENUM_BY_NUMBER + : json2pb::OUTPUT_ENUM_BY_NAME; + std::string error; + bool ok = json2pb::ProtoMessageToJson(message, output, options, &error); + if (!ok) { + LOG(INFO) << "Fail to serialize message=" + << message.GetDescriptor()->full_name() + << " to json :" << error; + } + return ok; + }); + return serialize(serializer); + } else if (CONTENT_TYPE_PROTO_JSON == content_type) { + Serializer serializer([&message, &cntl](google::protobuf::io::ZeroCopyOutputStream* output) -> bool { + json2pb::Pb2ProtoJsonOptions options; + options.always_print_enums_as_ints = FLAGS_pb_enum_as_number; + AlwaysPrintPrimitiveFields(options) = cntl.has_always_print_primitive_fields(); + std::string error; + bool ok = json2pb::ProtoMessageToProtoJson(message, output, options, &error); + if (!ok) { + LOG(INFO) << "Fail to serialize message=" + << message.GetDescriptor()->full_name() + << " to proto-json :" << error; + } + return ok; + }); + return serialize(serializer); + } else if (CONTENT_TYPE_PROTO_TEXT == content_type) { + Serializer serializer([&message](google::protobuf::io::ZeroCopyOutputStream* output) -> bool { + return google::protobuf::TextFormat::Print(message, output); + }); + return serialize(serializer); + } + return false; +} + +static bool SerializeResponse(const google::protobuf::Message& res, + Controller& cntl, butil::IOBuf& buf) { + if (res.GetDescriptor() == SerializedResponse::descriptor()) { + buf.swap(((SerializedResponse&)res).serialized_data()); + return true; + } + + if (!res.IsInitialized()) { + cntl.SetFailed(ERESPONSE, "Missing required fields in response: %s", + res.InitializationErrorString().c_str()); + return false; + } + + ContentType content_type = cntl.response_content_type(); + CompressType compress_type = cntl.response_compress_type(); + ChecksumType checksum_type = cntl.response_checksum_type(); + if (!SerializeRpcMessage(res, cntl, content_type, compress_type, + checksum_type, &buf)) { + cntl.SetFailed(ERESPONSE, + "Fail to serialize response=%s, " + "ContentType=%s, CompressType=%s, ChecksumType=%s", + butil::EnsureString(res.GetDescriptor()->full_name()).c_str(), + ContentTypeToCStr(content_type), + CompressTypeToCStr(compress_type), + ChecksumTypeToCStr(checksum_type)); + return false; + } + return true; +} + +namespace { +struct BaiduProxyPBMessages : public RpcPBMessages { + static BaiduProxyPBMessages* Get() { + return butil::get_object(); + } + + static void Return(BaiduProxyPBMessages* messages) { + messages->Clear(); + butil::return_object(messages); + } + + void Clear() { + request.Clear(); + response.Clear(); + } + + ::google::protobuf::Message* Request() override { return &request; } + ::google::protobuf::Message* Response() override { return &response; } + + SerializedRequest request; + SerializedResponse response; +}; +} + +// Used by UT, can't be static. +void SendRpcResponse(int64_t correlation_id, Controller* cntl, + RpcPBMessages* messages, const Server* server, + MethodStatus* method_status, int64_t received_us, + std::shared_ptr span) { + ControllerPrivateAccessor accessor(cntl); + if (span) { + span->set_start_send_us(butil::cpuwide_time_us()); + } + Socket* sock = accessor.get_sending_socket(); + + const google::protobuf::Message* req = NULL == messages ? NULL : messages->Request(); + const google::protobuf::Message* res = NULL == messages ? NULL : messages->Response(); + + // Recycle resources at the end of this function. + BRPC_SCOPE_EXIT { + { + // Remove concurrency and record latency at first. + ConcurrencyRemover concurrency_remover(method_status, cntl, received_us); + } + + std::unique_ptr recycle_cntl(cntl); + + if (NULL == messages) { + return; + } + + cntl->CallAfterRpcResp(req, res); + if (NULL == server->options().baidu_master_service) { + server->options().rpc_pb_message_factory->Return(messages); + } else { + BaiduProxyPBMessages::Return(static_cast(messages)); + } + }; + + StreamIds response_stream_ids = accessor.response_streams(); + + if (cntl->IsCloseConnection()) { + for(size_t i = 0; i < response_stream_ids.size(); ++i) { + StreamClose(response_stream_ids[i]); + } + sock->SetFailed(); + return; + } + bool append_body = false; + butil::IOBuf res_body; + // `res' can be NULL here, in which case we don't serialize it + // If user calls `SetFailed' on Controller, we don't serialize + // response either + if (res != NULL && !cntl->Failed()) { + append_body = SerializeResponse(*res, *cntl, res_body); + } + + // Don't use res->ByteSize() since it may be compressed + size_t res_size = 0; + size_t attached_size = 0; + if (append_body) { + res_size = res_body.length(); + attached_size = cntl->response_attachment().length(); + } + + int error_code = cntl->ErrorCode(); + if (error_code == -1) { + // replace general error (-1) with INTERNAL_SERVER_ERROR to make a + // distinction between server error and client error + error_code = EINTERNAL; + } + RpcMeta meta; + RpcResponseMeta* response_meta = meta.mutable_response(); + response_meta->set_error_code(error_code); + if (!cntl->ErrorText().empty()) { + // Only set error_text when it's not empty since protobuf Message + // always new the string no matter if it's empty or not. + response_meta->set_error_text(cntl->ErrorText()); + } + meta.set_correlation_id(correlation_id); + meta.set_compress_type(cntl->response_compress_type()); + meta.set_content_type(cntl->response_content_type()); + meta.set_checksum_type(cntl->response_checksum_type()); + meta.set_checksum_value(accessor.checksum_value()); + if (attached_size > 0) { + meta.set_attachment_size(attached_size); + } + StreamId response_stream_id = INVALID_STREAM_ID; + SocketUniquePtr stream_ptr; + if (!response_stream_ids.empty()) { + response_stream_id = response_stream_ids[0]; + if (Socket::Address(response_stream_id, &stream_ptr) == 0) { + Stream* s = (Stream *) stream_ptr->conn(); + StreamSettings *stream_settings = meta.mutable_stream_settings(); + s->FillSettings(stream_settings); + s->SetHostSocket(sock); + for (size_t i = 1; i < response_stream_ids.size(); ++i) { + stream_settings->mutable_extra_stream_ids()->Add(response_stream_ids[i]); + } + } else { + LOG(WARNING) << "Stream=" << response_stream_id + << " was closed before sending response"; + } + } + + if (cntl->has_response_user_fields() && + !cntl->response_user_fields()->empty()) { + ::google::protobuf::Map& user_fields + = *meta.mutable_user_fields(); + user_fields.insert(cntl->response_user_fields()->begin(), + cntl->response_user_fields()->end()); + + } + + butil::IOBuf res_buf; + SerializeRpcHeaderAndMeta(&res_buf, meta, res_size + attached_size); + if (append_body) { + res_buf.append(res_body.movable()); + if (attached_size > 0) { + res_buf.append(cntl->response_attachment().movable()); + } + } + + ResponseWriteInfo args; + bthread_id_t response_id = INVALID_BTHREAD_ID; + if (span) { + span->set_response_size(res_buf.size()); + CHECK_EQ(0, bthread_id_create(&response_id, &args, HandleResponseWritten)); + } + + // Send rpc response over stream even if server side failed to create + // stream for some reason. + if (cntl->has_remote_stream()) { + // Send the response over stream to notify that this stream connection + // is successfully built. + // Response_stream can be INVALID_STREAM_ID when error occurs. + if (SendStreamData(sock, &res_buf, + accessor.remote_stream_settings()->stream_id(), + response_stream_id, response_id) != 0) { + error_code = errno; + PLOG_IF(WARNING, error_code != EPIPE) + << "Fail to write into " << sock->description(); + cntl->SetFailed(error_code, "Fail to write into %s", + sock->description().c_str()); + Stream::SetFailed(response_stream_ids, error_code, + "Fail to write into %s", + sock->description().c_str()); + return; + } + + // Now it's ok the mark these server-side streams as connected as all the + // written user data would follower the RPC response. + // Reuse stream_ptr to avoid address first stream id again + if (stream_ptr) { + ((Stream*)stream_ptr->conn())->SetConnected(); + } + for (size_t i = 1; i < response_stream_ids.size(); ++i) { + StreamId extra_stream_id = response_stream_ids[i]; + SocketUniquePtr extra_stream_ptr; + if (Socket::Address(extra_stream_id, &extra_stream_ptr) == 0) { + Stream* extra_stream = (Stream *) extra_stream_ptr->conn(); + extra_stream->SetHostSocket(sock); + extra_stream->SetConnected(); + } else { + LOG(WARNING) << "Stream=" << extra_stream_id + << " was closed before sending response"; + } + } + } else{ + // Have the risk of unlimited pending responses, in which case, tell + // users to set max_concurrency. + Socket::WriteOptions wopt; + wopt.ignore_eovercrowded = true; + if (INVALID_BTHREAD_ID != response_id) { + wopt.id_wait = response_id; + wopt.notify_on_success = true; + } + if (sock->Write(&res_buf, &wopt) != 0) { + const int errcode = errno; + PLOG_IF(WARNING, errcode != EPIPE) << "Fail to write into " << *sock; + cntl->SetFailed(errcode, "Fail to write into %s", + sock->description().c_str()); + return; + } + } + + if (span) { + bthread_id_join(response_id); + // Do not care about the result of background writing. + // TODO: this is not sent + span->set_sent_us(args.sent_us); + } +} + +namespace { +struct CallMethodInBackupThreadArgs { + ::google::protobuf::Service* service; + const ::google::protobuf::MethodDescriptor* method; + ::google::protobuf::RpcController* controller; + const ::google::protobuf::Message* request; + ::google::protobuf::Message* response; + ::google::protobuf::Closure* done; +}; +} + +static void CallMethodInBackupThread(void* void_args) { + CallMethodInBackupThreadArgs* args = (CallMethodInBackupThreadArgs*)void_args; + args->service->CallMethod(args->method, args->controller, args->request, + args->response, args->done); + delete args; +} + +// Used by other protocols as well. +void EndRunningCallMethodInPool( + ::google::protobuf::Service* service, + const ::google::protobuf::MethodDescriptor* method, + ::google::protobuf::RpcController* controller, + const ::google::protobuf::Message* request, + ::google::protobuf::Message* response, + ::google::protobuf::Closure* done) { + CallMethodInBackupThreadArgs* args = new CallMethodInBackupThreadArgs; + args->service = service; + args->method = method; + args->controller = controller; + args->request = request; + args->response = response; + args->done = done; + return EndRunningUserCodeInPool(CallMethodInBackupThread, args); +}; + +bool DeserializeRpcMessage(const butil::IOBuf& data, Controller& cntl, + ContentType content_type, CompressType compress_type, + ChecksumType checksum_type, + google::protobuf::Message* message) { + auto deserialize = [&](Deserializer& deserializer) -> bool { + ChecksumIn checksum_in{&data, &cntl}; + bool ok = VerifyDataChecksum(checksum_in, checksum_type); + if (!ok) { + return ok; + } + if (COMPRESS_TYPE_NONE == compress_type) { + butil::IOBufAsZeroCopyInputStream stream(data); + ok = deserializer.DeserializeFrom(&stream); + } else { + const CompressHandler* handler = FindCompressHandler(compress_type); + if (NULL == handler) { + return false; + } + ok = handler->Decompress(data, &deserializer); + } + return ok; + }; + + if (CONTENT_TYPE_PB == content_type) { + Deserializer deserializer([message]( + google::protobuf::io::ZeroCopyInputStream* input) -> bool { + return message->ParseFromZeroCopyStream(input); + }); + return deserialize(deserializer); + } else if (CONTENT_TYPE_JSON == content_type) { + Deserializer deserializer([message, &cntl]( + google::protobuf::io::ZeroCopyInputStream* input) -> bool { + json2pb::Json2PbOptions options; + options.base64_to_bytes = cntl.has_pb_bytes_to_base64(); + options.array_to_single_repeated = cntl.has_pb_single_repeated_to_array(); + std::string error; + bool ok = json2pb::JsonToProtoMessage(input, message, options, &error); + if (!ok) { + LOG(INFO) << "Fail to parse json to " + << message->GetDescriptor()->full_name() + << ": "<< error; + } + return ok; + }); + return deserialize(deserializer); + } else if (CONTENT_TYPE_PROTO_JSON == content_type) { + Deserializer deserializer([message]( + google::protobuf::io::ZeroCopyInputStream* input) -> bool { + json2pb::ProtoJson2PbOptions options; + options.ignore_unknown_fields = true; + std::string error; + bool ok = json2pb::ProtoJsonToProtoMessage(input, message, options, &error); + if (!ok) { + LOG(INFO) << "Fail to parse proto-json to " + << message->GetDescriptor()->full_name() + << ": "<< error; + } + return ok; + }); + return deserialize(deserializer); + } else if (CONTENT_TYPE_PROTO_TEXT == content_type) { + Deserializer deserializer([message]( + google::protobuf::io::ZeroCopyInputStream* input) -> bool { + return google::protobuf::TextFormat::Parse(input, message); + }); + return deserialize(deserializer); + } + return false; +} + +void ProcessRpcRequest(InputMessageBase* msg_base) { + const int64_t start_parse_us = butil::cpuwide_time_us(); + DestroyingPtr msg(static_cast(msg_base)); + SocketUniquePtr socket_guard(msg->ReleaseSocket()); + Socket* socket = socket_guard.get(); + const Server* server = static_cast(msg_base->arg()); + ScopedNonServiceError non_service_error(server); + + RpcMeta meta; + if (!ParsePbFromIOBuf(&meta, msg->meta)) { + LOG(WARNING) << "Fail to parse RpcMeta from " << *socket; + socket->SetFailed(EREQUEST, "Fail to parse RpcMeta from %s", + socket->description().c_str()); + return; + } + const RpcRequestMeta &request_meta = meta.request(); + + SampledRequest* sample = AskToBeSampled(); + if (sample) { + sample->meta.set_service_name(request_meta.service_name()); + sample->meta.set_method_name(request_meta.method_name()); + sample->meta.set_compress_type((CompressType)meta.compress_type()); + sample->meta.set_protocol_type(PROTOCOL_BAIDU_STD); + sample->meta.set_attachment_size(meta.attachment_size()); + sample->meta.set_authentication_data(meta.authentication_data()); + sample->request = msg->payload; + sample->submit(start_parse_us); + } + + std::unique_ptr cntl(new (std::nothrow) Controller); + if (NULL == cntl.get()) { + LOG(WARNING) << "Fail to new Controller"; + return; + } + + RpcPBMessages* messages = NULL; + + ServerPrivateAccessor server_accessor(server); + ControllerPrivateAccessor accessor(cntl.get()); + const bool security_mode = server->options().security_mode() && + socket->user() == server_accessor.acceptor(); + if (request_meta.has_log_id()) { + cntl->set_log_id(request_meta.log_id()); + } + if (request_meta.has_request_id()) { + cntl->set_request_id(request_meta.request_id()); + } + if (request_meta.has_timeout_ms()) { + cntl->set_timeout_ms(request_meta.timeout_ms()); + } + cntl->set_request_content_type(meta.content_type()); + cntl->set_request_compress_type((CompressType)meta.compress_type()); + cntl->set_request_checksum_type((ChecksumType)meta.checksum_type()); + cntl->set_rpc_received_us(msg->received_us()); + accessor.set_checksum_value(meta.checksum_value()); + accessor.set_server(server) + .set_security_mode(security_mode) + .set_peer_id(socket->id()) + .set_remote_side(socket->remote_side()) + .set_local_side(socket->local_side()) + .set_auth_context(socket->auth_context()) + .set_request_protocol(PROTOCOL_BAIDU_STD) + .set_begin_time_us(msg->received_us()) + .move_in_server_receiving_sock(socket_guard); + + if (meta.has_stream_settings()) { + accessor.set_remote_stream_settings(meta.release_stream_settings()); + } + + if (!meta.user_fields().empty()) { + for (const auto& it : meta.user_fields()) { + (*cntl->request_user_fields())[it.first] = it.second; + } + } + + // Tag the bthread with this server's key for thread_local_data(). + if (server->thread_local_options().thread_local_data_factory) { + bthread_assign_data((void*)&server->thread_local_options()); + } + + std::shared_ptr span; + if (IsTraceable(request_meta.has_trace_id())) { + span = Span::CreateServerSpan( + request_meta.trace_id(), request_meta.span_id(), + request_meta.parent_span_id(), msg->base_real_us()); + accessor.set_span(span); + span->set_log_id(request_meta.log_id()); + span->set_remote_side(cntl->remote_side()); + span->set_protocol(PROTOCOL_BAIDU_STD); + span->set_received_us(msg->received_us()); + span->set_start_parse_us(start_parse_us); + span->set_request_size(msg->payload.size() + msg->meta.size() + 12); + } + + MethodStatus* method_status = NULL; + do { + if (!server->IsRunning()) { + cntl->SetFailed(ELOGOFF, "Server is stopping"); + break; + } + + if (!server_accessor.AddConcurrency(cntl.get())) { + cntl->SetFailed( + ELIMIT, "Reached server's max_concurrency=%d", + server->options().max_concurrency); + break; + } + + if (FLAGS_usercode_in_pthread && TooManyUserCode()) { + cntl->SetFailed(ELIMIT, "Too many user code to run when" + " -usercode_in_pthread is on"); + break; + } + + const int req_size = static_cast(msg->payload.size()); + if (meta.has_attachment_size()) { + if (req_size < meta.attachment_size()) { + cntl->SetFailed(EREQUEST, + "attachment_size=%d is larger than request_size=%d", + meta.attachment_size(), req_size); + break; + } + } + + google::protobuf::Service* svc = NULL; + google::protobuf::MethodDescriptor* method = NULL; + if (NULL != server->options().baidu_master_service) { + if (socket->is_overcrowded() && + !server->options().ignore_eovercrowded && + !server->options().baidu_master_service->ignore_eovercrowded()) { + cntl->SetFailed(EOVERCROWDED, "Connection to %s is overcrowded", + butil::endpoint2str(socket->remote_side()).c_str()); + break; + } + svc = server->options().baidu_master_service; + auto sampled_request = new (std::nothrow) SampledRequest; + if (NULL == sampled_request) { + cntl->SetFailed(ENOMEM, "Fail to get sampled_request"); + break; + } + sampled_request->meta.set_service_name(request_meta.service_name()); + sampled_request->meta.set_method_name(request_meta.method_name()); + cntl->reset_sampled_request(sampled_request); + // Switch to service-specific error. + non_service_error.release(); + method_status = server->options().baidu_master_service->_status; + if (method_status) { + int rejected_cc = 0; + if (!method_status->OnRequested(&rejected_cc, cntl.get())) { + cntl->SetFailed( + ELIMIT, + "Rejected by %s's ConcurrencyLimiter, concurrency=%d", + butil::class_name(), rejected_cc); + break; + } + } + if (span) { + span->ResetServerSpanName(sampled_request->meta.method_name()); + } + + messages = BaiduProxyPBMessages::Get(); + msg->payload.cutn( + &((SerializedRequest*)messages->Request())->serialized_data(), + req_size - meta.attachment_size()); + if (!msg->payload.empty()) { + cntl->request_attachment().swap(msg->payload); + } + } else { + // NOTE(gejun): jprotobuf sends service names without packages. So the + // name should be changed to full when it's not. + butil::StringPiece svc_name(request_meta.service_name()); + if (svc_name.find('.') == butil::StringPiece::npos) { + const Server::ServiceProperty* sp = + server_accessor.FindServicePropertyByName(svc_name); + if (NULL == sp) { + cntl->SetFailed(ENOSERVICE, "Fail to find service=%s", + request_meta.service_name().c_str()); + break; + } + svc_name = sp->service->GetDescriptor()->full_name(); + } + const Server::MethodProperty* mp = + server_accessor.FindMethodPropertyByFullName( + svc_name, request_meta.method_name()); + if (NULL == mp) { + cntl->SetFailed(ENOMETHOD, "Fail to find method=%s/%s", + request_meta.service_name().c_str(), + request_meta.method_name().c_str()); + break; + } else if (mp->service->GetDescriptor() == BadMethodService::descriptor()) { + BadMethodRequest breq; + BadMethodResponse bres; + breq.set_service_name(request_meta.service_name()); + mp->service->CallMethod(mp->method, cntl.get(), &breq, &bres, NULL); + break; + } + if (socket->is_overcrowded() && + !server->options().ignore_eovercrowded && + !mp->ignore_eovercrowded) { + cntl->SetFailed( + EOVERCROWDED, "Connection to %s is overcrowded", + butil::endpoint2str(socket->remote_side()).c_str()); + break; + } + // Switch to service-specific error. + non_service_error.release(); + method_status = mp->status; + if (method_status) { + int rejected_cc = 0; + if (!method_status->OnRequested(&rejected_cc, cntl.get())) { + cntl->SetFailed( + ELIMIT, + "Rejected by %s's ConcurrencyLimiter, concurrency=%d", + butil::EnsureString(mp->method->full_name()).c_str(), rejected_cc); + break; + } + } + svc = mp->service; + method = const_cast(mp->method); + accessor.set_method(method); + + if (span) { + span->ResetServerSpanName(butil::EnsureString(method->full_name())); + } + + if (!server->AcceptRequest(cntl.get())) { + break; + } + + butil::IOBuf req_buf; + int body_without_attachment_size = req_size - meta.attachment_size(); + msg->payload.cutn(&req_buf, body_without_attachment_size); + if (meta.attachment_size() > 0) { + cntl->request_attachment().swap(msg->payload); + } + + ContentType content_type = meta.content_type(); + auto compress_type = + static_cast(meta.compress_type()); + auto checksum_type = + static_cast(meta.checksum_type()); + messages = + server->options().rpc_pb_message_factory->Get(*svc, *method); + if (!DeserializeRpcMessage(req_buf, *cntl, content_type, + compress_type, checksum_type, + messages->Request())) { + cntl->SetFailed( + EREQUEST, + "Fail to parse request=%s, ContentType=%s, " + "CompressType=%s, ChecksumType=%s, request_size=%d", + butil::EnsureString(messages->Request()->GetDescriptor()->full_name()).c_str(), + ContentTypeToCStr(content_type), + CompressTypeToCStr(compress_type), + ChecksumTypeToCStr(checksum_type), req_size); + break; + } + req_buf.clear(); + } + + // `socket' will be held until response has been sent + google::protobuf::Closure* done = ::brpc::NewCallback< + int64_t, Controller*, RpcPBMessages*, + const Server*, MethodStatus*, int64_t, std::shared_ptr>( + &SendRpcResponse, meta.correlation_id(),cntl.get(), + messages, server, method_status, msg->received_us(), span); + + // optional, just release resource ASAP + msg.reset(); + + if (span) { + span->set_start_callback_us(butil::cpuwide_time_us()); + span->AsParent(); + } + if (!FLAGS_usercode_in_pthread) { + return svc->CallMethod(method, cntl.release(), + messages->Request(), + messages->Response(), done); + } + if (BeginRunningUserCode()) { + svc->CallMethod(method, cntl.release(), + messages->Request(), + messages->Response(), done); + return EndRunningUserCodeInPlace(); + } else { + return EndRunningCallMethodInPool( + svc, method, cntl.release(), + messages->Request(), + messages->Response(), done); + } + } while (false); + + // `cntl', `req' and `res' will be deleted inside `SendRpcResponse' + // `socket' will be held until response has been sent + + SendRpcResponse(meta.correlation_id(), + cntl.release(), messages, + server, method_status, + msg->received_us(), span); +} + +bool VerifyRpcRequest(const InputMessageBase* msg_base) { + const MostCommonMessage* msg = + static_cast(msg_base); + const Server* server = static_cast(msg->arg()); + Socket* socket = msg->socket(); + + RpcMeta request_meta; + if (!ParsePbFromIOBuf(&request_meta, msg->meta)) { + LOG(WARNING) << "Fail to parse RpcRequestMeta"; + return false; + } + const Authenticator* auth = server->options().auth; + if (NULL == auth) { + // Fast pass (no authentication) + return true; + } + if (auth->VerifyCredential(request_meta.authentication_data(), + socket->remote_side(), + socket->mutable_auth_context()) == 0) { + return true; + } + + // Send `ERPCAUTH' to client. + RpcMeta response_meta; + response_meta.set_correlation_id(request_meta.correlation_id()); + response_meta.mutable_response()->set_error_code(ERPCAUTH); + response_meta.mutable_response()->set_error_text("Fail to authenticate"); + std::string user_error_text = auth->GetUnauthorizedErrorText(); + if (!user_error_text.empty()) { + response_meta.mutable_response()->mutable_error_text()->append(": "); + response_meta.mutable_response()->mutable_error_text()->append(user_error_text); + } + butil::IOBuf res_buf; + SerializeRpcHeaderAndMeta(&res_buf, response_meta, 0); + Socket::WriteOptions opt; + opt.ignore_eovercrowded = true; + if (socket->Write(&res_buf, &opt) != 0) { + PLOG_IF(WARNING, errno != EPIPE) << "Fail to write into " << *socket; + } + + return false; +} + +void ProcessRpcResponse(InputMessageBase* msg_base) { + const int64_t start_parse_us = butil::cpuwide_time_us(); + DestroyingPtr msg(static_cast(msg_base)); + RpcMeta meta; + if (!ParsePbFromIOBuf(&meta, msg->meta)) { + LOG(WARNING) << "Fail to parse from response meta"; + return; + } + + const bthread_id_t cid = { static_cast(meta.correlation_id()) }; + Controller* cntl = NULL; + + StreamId remote_stream_id = meta.has_stream_settings() ? meta.stream_settings().stream_id(): INVALID_STREAM_ID; + + const int rc = bthread_id_lock(cid, (void**)&cntl); + if (rc != 0) { + LOG_IF(ERROR, rc != EINVAL && rc != EPERM) + << "Fail to lock correlation_id=" << cid << ": " << berror(rc); + if (remote_stream_id != INVALID_STREAM_ID) { + SendStreamRst(msg->socket(), remote_stream_id); + const auto & extra_stream_ids = meta.stream_settings().extra_stream_ids(); + for (int i = 0; i < extra_stream_ids.size(); ++i) { + policy::SendStreamRst(msg->socket(), extra_stream_ids[i]); + } + } + return; + } + + ControllerPrivateAccessor accessor(cntl); + if (remote_stream_id != INVALID_STREAM_ID) { + accessor.set_remote_stream_settings( + new StreamSettings(meta.stream_settings())); + } + + if (!meta.user_fields().empty()) { + for (const auto& it : meta.user_fields()) { + (*cntl->response_user_fields())[it.first] = it.second; + } + } + + cntl->set_rpc_received_us(msg->received_us()); + if (auto span = accessor.span()) { + span->set_base_real_us(msg->base_real_us()); + span->set_received_us(msg->received_us()); + span->set_response_size(msg->meta.size() + msg->payload.size() + 12); + span->set_start_parse_us(start_parse_us); + } + const RpcResponseMeta &response_meta = meta.response(); + const int saved_error = cntl->ErrorCode(); + do { + if (response_meta.error_code() != 0) { + // If error_code is unset, default is 0 = success. + cntl->SetFailed(response_meta.error_code(), + "%s", response_meta.error_text().c_str()); + break; + } + // Parse response message iff error code from meta is 0 + butil::IOBuf res_buf; + const int res_size = msg->payload.length(); + butil::IOBuf* res_buf_ptr = &msg->payload; + if (meta.has_attachment_size()) { + if (meta.attachment_size() > res_size) { + cntl->SetFailed( + ERESPONSE, "attachment_size=%d is larger than response_size=%d", + meta.attachment_size(), res_size); + break; + } + int body_without_attachment_size = res_size - meta.attachment_size(); + msg->payload.cutn(&res_buf, body_without_attachment_size); + res_buf_ptr = &res_buf; + cntl->response_attachment().swap(msg->payload); + } + + ContentType content_type = meta.content_type(); + auto compress_type = (CompressType)meta.compress_type(); + auto checksum_type = (ChecksumType)meta.checksum_type(); + cntl->set_response_content_type(content_type); + cntl->set_response_compress_type(compress_type); + cntl->set_response_checksum_type(checksum_type); + accessor.set_checksum_value(meta.checksum_value()); + if (cntl->response()) { + if (cntl->response()->GetDescriptor() == SerializedResponse::descriptor()) { + ((SerializedResponse*)cntl->response())-> + serialized_data().append(*res_buf_ptr); + } else if (!DeserializeRpcMessage(*res_buf_ptr, *cntl, content_type, + compress_type, checksum_type, + cntl->response())) { + cntl->SetFailed( + EREQUEST, + "Fail to parse response=%s, ContentType=%s, " + "CompressType=%s, ChecksumType=%s, request_size=%d", + butil::EnsureString(cntl->response()->GetDescriptor()->full_name()).c_str(), + ContentTypeToCStr(content_type), + CompressTypeToCStr(compress_type), + ChecksumTypeToCStr(checksum_type), res_size); + } + } // else silently ignore the response. + } while (0); + // Unlocks correlation_id inside. Revert controller's + // error code if it version check of `cid' fails + msg.reset(); // optional, just release resource ASAP + accessor.OnResponse(cid, saved_error); +} + +void SerializeRpcRequest(butil::IOBuf* request_buf, Controller* cntl, + const google::protobuf::Message* request) { + // Check sanity of request. + if (NULL == request) { + return cntl->SetFailed(EREQUEST, "`request' is NULL"); + } + if (request->GetDescriptor() == SerializedRequest::descriptor()) { + request_buf->append(((SerializedRequest*)request)->serialized_data()); + return; + } + if (!request->IsInitialized()) { + return cntl->SetFailed(EREQUEST, "Missing required fields in request: %s", + request->InitializationErrorString().c_str()); + } + + ContentType content_type = cntl->request_content_type(); + CompressType compress_type = cntl->request_compress_type(); + ChecksumType checksum_type = cntl->request_checksum_type(); + if (!SerializeRpcMessage(*request, *cntl, content_type, compress_type, + checksum_type, request_buf)) { + return cntl->SetFailed( + EREQUEST, + "Fail to compress request=%s, " + "ContentType=%s, CompressType=%s, ChecksumType=%s", + butil::EnsureString(request->GetDescriptor()->full_name()).c_str(), + ContentTypeToCStr(content_type), CompressTypeToCStr(compress_type), + ChecksumTypeToCStr(checksum_type)); + } +} + +void PackRpcRequest(butil::IOBuf* req_buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* cntl, + const butil::IOBuf& request_body, + const Authenticator* auth) { + RpcMeta meta; + if (auth && auth->GenerateCredential( + meta.mutable_authentication_data()) != 0) { + return cntl->SetFailed(EREQUEST, "Fail to generate credential"); + } + + ControllerPrivateAccessor accessor(cntl); + RpcRequestMeta* request_meta = meta.mutable_request(); + if (method) { + request_meta->set_service_name(FLAGS_baidu_protocol_use_fullname ? + method->service()->full_name() : + method->service()->name()); + request_meta->set_method_name(method->name()); + meta.set_compress_type(cntl->request_compress_type()); + meta.set_checksum_type(cntl->request_checksum_type()); + meta.set_checksum_value(accessor.checksum_value()); + } else if (NULL != cntl->sampled_request()) { + // Replaying. Keep service-name as the one seen by server. + request_meta->set_service_name(cntl->sampled_request()->meta.service_name()); + request_meta->set_method_name(cntl->sampled_request()->meta.method_name()); + meta.set_compress_type(cntl->sampled_request()->meta.has_compress_type() ? + cntl->sampled_request()->meta.compress_type() : + cntl->request_compress_type()); + } else { + return cntl->SetFailed(ENOMETHOD, "%s.method is NULL", __func__ ); + } + if (cntl->has_log_id()) { + request_meta->set_log_id(cntl->log_id()); + } + if (!cntl->request_id().empty()) { + request_meta->set_request_id(cntl->request_id()); + } + meta.set_correlation_id(correlation_id); + StreamIds request_stream_ids = accessor.request_streams(); + if (!request_stream_ids.empty()) { + StreamSettings* stream_settings = meta.mutable_stream_settings(); + StreamId request_stream_id = request_stream_ids[0]; + SocketUniquePtr ptr; + if (Socket::Address(request_stream_id, &ptr) != 0) { + return cntl->SetFailed(EREQUEST, "Stream=%" PRIu64 " was closed", + request_stream_id); + } + Stream* s = (Stream*) ptr->conn(); + s->FillSettings(stream_settings); + for (size_t i = 1; i < request_stream_ids.size(); ++i) { + stream_settings->mutable_extra_stream_ids()->Add(request_stream_ids[i]); + } + } + + if (cntl->has_request_user_fields() && !cntl->request_user_fields()->empty()) { + ::google::protobuf::Map& user_fields + = *meta.mutable_user_fields(); + user_fields.insert(cntl->request_user_fields()->begin(), + cntl->request_user_fields()->end()); + } + + // Don't use res->ByteSize() since it may be compressed + const size_t req_size = request_body.length(); + const size_t attached_size = cntl->request_attachment().length(); + if (attached_size) { + meta.set_attachment_size(attached_size); + } + + if (FLAGS_baidu_std_protocol_deliver_timeout_ms) { + if (accessor.real_timeout_ms() > 0) { + request_meta->set_timeout_ms(accessor.real_timeout_ms()); + } + } + meta.set_content_type(cntl->request_content_type()); + + if (auto span = accessor.span()) { + request_meta->set_trace_id(span->trace_id()); + request_meta->set_span_id(span->span_id()); + request_meta->set_parent_span_id(span->parent_span_id()); + } + + SerializeRpcHeaderAndMeta(req_buf, meta, req_size + attached_size); + req_buf->append(request_body); + if (attached_size) { + req_buf->append(cntl->request_attachment()); + } +} + +const char* ContentTypeToCStr(ContentType content_type) { + switch (content_type) { + case CONTENT_TYPE_PB: + return "pb"; + case CONTENT_TYPE_JSON: + return "json"; + case CONTENT_TYPE_PROTO_JSON: + return "proto-json"; + case CONTENT_TYPE_PROTO_TEXT: + return "proto-text"; + default: + return "unknown"; + } +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/baidu_rpc_protocol.h b/src/brpc/policy/baidu_rpc_protocol.h new file mode 100644 index 0000000..77ecc78 --- /dev/null +++ b/src/brpc/policy/baidu_rpc_protocol.h @@ -0,0 +1,59 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_BRPC_PROTOCOL_H +#define BRPC_POLICY_BRPC_PROTOCOL_H + +#include "brpc/protocol.h" + +namespace brpc { +namespace policy { + +// Parse binary format of baidu_std +ParseResult ParseRpcMessage(butil::IOBuf* source, Socket *socket, bool read_eof, + const void *arg); + +// Actions to a (client) request in baidu_std format +void ProcessRpcRequest(InputMessageBase* msg); + +// Actions to a (server) response in baidu_std format. +void ProcessRpcResponse(InputMessageBase* msg); + +// Verify authentication information in baidu_std format +bool VerifyRpcRequest(const InputMessageBase* msg); + +// Serialize `request' into `buf'. +void SerializeRpcRequest(butil::IOBuf* request_buf, Controller* cntl, + const google::protobuf::Message* request); + +// Pack `request' to `method' into `buf'. +void PackRpcRequest(butil::IOBuf* buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* controller, + const butil::IOBuf& request, + const Authenticator* auth); + +// Returns the `name' of the 'content_type'. +const char* ContentTypeToCStr(ContentType content_type); + +} // namespace policy +} // namespace brpc + +#endif // BRPC_POLICY_BRPC_PROTOCOL_H diff --git a/src/brpc/policy/consistent_hashing_load_balancer.cpp b/src/brpc/policy/consistent_hashing_load_balancer.cpp new file mode 100644 index 0000000..d29ad55 --- /dev/null +++ b/src/brpc/policy/consistent_hashing_load_balancer.cpp @@ -0,0 +1,411 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // std::set_union +#include +#include +#include +#include "butil/containers/flat_map.h" +#include "butil/errno.h" +#include "butil/strings/string_number_conversions.h" +#include "brpc/socket.h" +#include "brpc/policy/consistent_hashing_load_balancer.h" +#include "brpc/policy/hasher.h" + +namespace brpc { +namespace policy { + +// TODO: or 160? +DEFINE_int32(chash_num_replicas, 100, + "default number of replicas per server in chash"); +DEFINE_bool(consistent_hashing_enable_server_tag, false, + "if consistent hashing enable server with tag"); + +// Defined in hasher.cpp. +const char* GetHashName(HashFunc hasher); + +class ReplicaPolicy { +public: + virtual ~ReplicaPolicy() = default; + + virtual bool Build(ServerId server, + size_t num_replicas, + std::vector* replicas) const = 0; + virtual const char* name() const = 0; +}; + +class DefaultReplicaPolicy : public ReplicaPolicy { +public: + DefaultReplicaPolicy(HashFunc hash) : _hash_func(hash) {} + + virtual bool Build(ServerId server, + size_t num_replicas, + std::vector* replicas) const; + + virtual const char* name() const { return GetHashName(_hash_func); } + +private: + HashFunc _hash_func; +}; + +bool DefaultReplicaPolicy::Build(ServerId server, + size_t num_replicas, + std::vector* replicas) const { + SocketUniquePtr ptr; + if (Socket::AddressFailedAsWell(server.id, &ptr) == -1) { + return false; + } + replicas->clear(); + for (size_t i = 0; i < num_replicas; ++i) { + char host[256]; + int len = 0; + if (!FLAGS_consistent_hashing_enable_server_tag) { + len = snprintf(host, sizeof(host), "%s-%lu", + endpoint2str(ptr->remote_side()).c_str(), i); + } else { + len = snprintf(host, sizeof(host), "%s-%lu-%s", + endpoint2str(ptr->remote_side()).c_str(), i, server.tag.c_str()); + } + ConsistentHashingLoadBalancer::Node node; + node.hash = _hash_func(host, len); + node.server_sock = server; + node.server_addr = ptr->remote_side(); + replicas->push_back(node); + } + return true; +} + +class KetamaReplicaPolicy : public ReplicaPolicy { +public: + virtual bool Build(ServerId server, + size_t num_replicas, + std::vector* replicas) const; + + virtual const char* name() const { return "ketama"; } +}; + +bool KetamaReplicaPolicy::Build(ServerId server, + size_t num_replicas, + std::vector* replicas) const { + SocketUniquePtr ptr; + if (Socket::AddressFailedAsWell(server.id, &ptr) == -1) { + return false; + } + replicas->clear(); + const size_t points_per_hash = 4; + CHECK(num_replicas % points_per_hash == 0) + << "Ketam hash replicas number(" << num_replicas << ") should be n*4"; + for (size_t i = 0; i < num_replicas / points_per_hash; ++i) { + char host[256]; + int len = 0; + if (!FLAGS_consistent_hashing_enable_server_tag) { + len = snprintf(host, sizeof(host), "%s-%lu", + endpoint2str(ptr->remote_side()).c_str(), i); + } else { + len = snprintf(host, sizeof(host), "%s-%lu-%s", + endpoint2str(ptr->remote_side()).c_str(), i, server.tag.c_str()); + } + unsigned char digest[MD5_DIGEST_LENGTH]; + MD5HashSignature(host, len, digest); + for (size_t j = 0; j < points_per_hash; ++j) { + ConsistentHashingLoadBalancer::Node node; + node.server_sock = server; + node.server_addr = ptr->remote_side(); + node.hash = ((uint32_t) (digest[3 + j * 4] & 0xFF) << 24) + | ((uint32_t) (digest[2 + j * 4] & 0xFF) << 16) + | ((uint32_t) (digest[1 + j * 4] & 0xFF) << 8) + | (digest[0 + j * 4] & 0xFF); + replicas->push_back(node); + } + } + return true; +} + +namespace { + +pthread_once_t s_replica_policy_once = PTHREAD_ONCE_INIT; +const std::array* g_replica_policy = nullptr; + +void InitReplicaPolicy() { + g_replica_policy = new std::array({ + new DefaultReplicaPolicy(MurmurHash32), + new DefaultReplicaPolicy(MD5Hash32), + new KetamaReplicaPolicy + }); +} + +inline const ReplicaPolicy* GetReplicaPolicy(ConsistentHashingLoadBalancerType type) { + pthread_once(&s_replica_policy_once, InitReplicaPolicy); + return g_replica_policy->at(type); +} + +} // namespace + +ConsistentHashingLoadBalancer::ConsistentHashingLoadBalancer( + ConsistentHashingLoadBalancerType type) + : _num_replicas(FLAGS_chash_num_replicas), _type(type) { + CHECK(GetReplicaPolicy(_type)) + << "Fail to find replica policy for consistency lb type: '" << _type << '\''; +} + +size_t ConsistentHashingLoadBalancer::AddBatch( + std::vector &bg, const std::vector &fg, + const std::vector &servers, bool *executed) { + if (*executed) { + // Hack DBD + return fg.size() - bg.size(); + } + *executed = true; + bg.resize(fg.size() + servers.size()); + bg.resize(std::set_union(fg.begin(), fg.end(), + servers.begin(), servers.end(), bg.begin()) + - bg.begin()); + return bg.size() - fg.size(); +} + +size_t ConsistentHashingLoadBalancer::RemoveBatch( + std::vector &bg, const std::vector &fg, + const std::vector &servers, bool *executed) { + if (*executed) { + return bg.size() - fg.size(); + } + *executed = true; + if (servers.empty()) { + bg = fg; + return 0; + } + butil::FlatSet id_set; + bool use_set = true; + if (id_set.init(servers.size() * 2) == 0) { + for (size_t i = 0; i < servers.size(); ++i) { + if (id_set.insert(servers[i]) == NULL) { + use_set = false; + break; + } + } + } else { + use_set = false; + } + CHECK(use_set) << "Fail to construct id_set, " << berror(); + bg.clear(); + for (size_t i = 0; i < fg.size(); ++i) { + const bool removed = + use_set ? (id_set.seek(fg[i].server_sock) != NULL) + : (std::find(servers.begin(), servers.end(), + fg[i].server_sock) != servers.end()); + if (!removed) { + bg.push_back(fg[i]); + } + } + return fg.size() - bg.size(); +} + +size_t ConsistentHashingLoadBalancer::Remove( + std::vector &bg, const std::vector &fg, + const ServerId& server, bool *executed) { + if (*executed) { + return bg.size() - fg.size(); + } + *executed = true; + bg.clear(); + for (size_t i = 0; i < fg.size(); ++i) { + if (fg[i].server_sock != server) { + bg.push_back(fg[i]); + } + } + return fg.size() - bg.size(); +} + +bool ConsistentHashingLoadBalancer::AddServer(const ServerId& server) { + std::vector add_nodes; + add_nodes.reserve(_num_replicas); + if (!GetReplicaPolicy(_type)->Build(server, _num_replicas, &add_nodes)) { + return false; + } + std::sort(add_nodes.begin(), add_nodes.end()); + bool executed = false; + const size_t ret = _db_hash_ring.ModifyWithForeground( + AddBatch, add_nodes, &executed); + CHECK(ret == 0 || ret == _num_replicas) << ret; + return ret != 0; +} + +size_t ConsistentHashingLoadBalancer::AddServersInBatch( + const std::vector &servers) { + std::vector add_nodes; + add_nodes.reserve(servers.size() * _num_replicas); + std::vector replicas; + replicas.reserve(_num_replicas); + for (size_t i = 0; i < servers.size(); ++i) { + replicas.clear(); + if (GetReplicaPolicy(_type)->Build(servers[i], _num_replicas, &replicas)) { + add_nodes.insert(add_nodes.end(), replicas.begin(), replicas.end()); + } + } + std::sort(add_nodes.begin(), add_nodes.end()); + bool executed = false; + const size_t ret = _db_hash_ring.ModifyWithForeground(AddBatch, add_nodes, &executed); + CHECK(ret % _num_replicas == 0); + const size_t n = ret / _num_replicas; + LOG_IF(ERROR, n != servers.size()) + << "Fail to AddServersInBatch, expected " << servers.size() + << " actually " << n; + return n; +} + +bool ConsistentHashingLoadBalancer::RemoveServer(const ServerId& server) { + bool executed = false; + const size_t ret = _db_hash_ring.ModifyWithForeground(Remove, server, &executed); + CHECK(ret == 0 || ret == _num_replicas); + return ret != 0; +} + +size_t ConsistentHashingLoadBalancer::RemoveServersInBatch( + const std::vector &servers) { + bool executed = false; + const size_t ret = _db_hash_ring.ModifyWithForeground(RemoveBatch, servers, &executed); + CHECK(ret % _num_replicas == 0); + const size_t n = ret / _num_replicas; + return n; +} + +LoadBalancer *ConsistentHashingLoadBalancer::New(const butil::StringPiece& params) const { + ConsistentHashingLoadBalancer* lb = + new (std::nothrow) ConsistentHashingLoadBalancer(_type); + if (lb && !lb->SetParameters(params)) { + delete lb; + lb = nullptr; + } + return lb; +} + +void ConsistentHashingLoadBalancer::Destroy() { + delete this; +} + +int ConsistentHashingLoadBalancer::SelectServer( + const SelectIn &in, SelectOut *out) { + if (!in.has_request_code) { + LOG(ERROR) << "Controller.set_request_code() is required"; + return EINVAL; + } + if (in.request_code > UINT_MAX) { + LOG(ERROR) << "request_code must be 32-bit currently"; + return EINVAL; + } + butil::DoublyBufferedData >::ScopedPtr s; + if (_db_hash_ring.Read(&s) != 0) { + return ENOMEM; + } + if (s->empty()) { + return ENODATA; + } + std::vector::const_iterator choice = + std::lower_bound(s->begin(), s->end(), (uint32_t)in.request_code); + if (choice == s->end()) { + choice = s->begin(); + } + for (size_t i = 0; i < s->size(); ++i) { + if (((i + 1) == s->size() // always take last chance + || !ExcludedServers::IsExcluded(in.excluded, choice->server_sock.id)) + && IsServerAvailable(choice->server_sock.id, out->ptr)) { + return 0; + } else { + if (++choice == s->end()) { + choice = s->begin(); + } + } + } + return EHOSTDOWN; +} + +void ConsistentHashingLoadBalancer::Describe( + std::ostream &os, const DescribeOptions& options) { + if (!options.verbose) { + os << "c_hash"; + return; + } + os << "ConsistentHashingLoadBalancer {\n" + << " hash function: " << GetReplicaPolicy(_type)->name() << '\n' + << " replica per host: " << _num_replicas << '\n'; + std::map load_map; + GetLoads(&load_map); + os << " number of hosts: " << load_map.size() << '\n'; + os << " load of hosts: {\n"; + double expected_load_per_server = 1.0 / load_map.size(); + double load_sum = 0; + double load_sqr_sum = 0; + for (std::map::iterator + it = load_map.begin(); it!= load_map.end(); ++it) { + os << " " << it->first << ": " << it->second << '\n'; + double normalized_load = it->second / expected_load_per_server; + load_sum += normalized_load; + load_sqr_sum += normalized_load * normalized_load; + } + os << " }\n"; + os << "deviation: " + << sqrt(load_sqr_sum * load_map.size() - load_sum * load_sum) + / load_map.size(); + os << "}\n"; +} + +void ConsistentHashingLoadBalancer::GetLoads( + std::map *load_map) { + load_map->clear(); + std::map count_map; + do { + butil::DoublyBufferedData >::ScopedPtr s; + if (_db_hash_ring.Read(&s) != 0) { + break; + } + if (s->empty()) { + break; + } + count_map[s->begin()->server_addr] += + s->begin()->hash + (UINT_MAX - (s->end() - 1)->hash); + for (size_t i = 1; i < s->size(); ++i) { + count_map[(*s.get())[i].server_addr] += + (*s.get())[i].hash - (*s.get())[i - 1].hash; + } + } while (0); + for (std::map::iterator + it = count_map.begin(); it!= count_map.end(); ++it) { + (*load_map)[it->first] = (double)it->second / UINT_MAX; + } +} + +bool ConsistentHashingLoadBalancer::SetParameters(const butil::StringPiece& params) { + for (butil::KeyValuePairsSplitter sp(params.begin(), params.end(), ' ', '='); + sp; ++sp) { + if (sp.value().empty()) { + LOG(ERROR) << "Empty value for " << sp.key() << " in lb parameter"; + return false; + } + if (sp.key() == "replicas") { + if (!butil::StringToSizeT(sp.value(), &_num_replicas)) { + return false; + } + continue; + } + LOG(ERROR) << "Failed to set this unknown parameters " << sp.key_and_value(); + } + return true; +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/consistent_hashing_load_balancer.h b/src/brpc/policy/consistent_hashing_load_balancer.h new file mode 100644 index 0000000..a4808c1 --- /dev/null +++ b/src/brpc/policy/consistent_hashing_load_balancer.h @@ -0,0 +1,90 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_CONSISTENT_HASHING_LOAD_BALANCER_H +#define BRPC_CONSISTENT_HASHING_LOAD_BALANCER_H + +#include // uint32_t +#include +#include // std::vector +#include "butil/endpoint.h" // butil::EndPoint +#include "butil/containers/doubly_buffered_data.h" +#include "brpc/load_balancer.h" + + +namespace brpc { +namespace policy { + +class ReplicaPolicy; + +enum ConsistentHashingLoadBalancerType { + CONS_HASH_LB_MURMUR3 = 0, + CONS_HASH_LB_MD5 = 1, + CONS_HASH_LB_KETAMA = 2, + + // Identify the last one. + CONS_HASH_LB_LAST = 3 +}; + +class ConsistentHashingLoadBalancer : public LoadBalancer { +public: + struct Node { + uint32_t hash; + ServerId server_sock; + butil::EndPoint server_addr; // To make sorting stable among all clients + bool operator<(const Node &rhs) const { + if (hash < rhs.hash) { return true; } + if (hash > rhs.hash) { return false; } + if (server_addr < rhs.server_addr) { return true; } + if (server_addr > rhs.server_addr) { return false; } + // compare by tag if has the same ip-port + return server_sock.tag < rhs.server_sock.tag; + } + bool operator<(const uint32_t code) const { + return hash < code; + } + }; + explicit ConsistentHashingLoadBalancer(ConsistentHashingLoadBalancerType type); + bool AddServer(const ServerId& server); + bool RemoveServer(const ServerId& server); + size_t AddServersInBatch(const std::vector &servers); + size_t RemoveServersInBatch(const std::vector &servers); + LoadBalancer *New(const butil::StringPiece& params) const; + void Destroy(); + int SelectServer(const SelectIn &in, SelectOut *out); + void Describe(std::ostream &os, const DescribeOptions& options); + +private: + bool SetParameters(const butil::StringPiece& params); + void GetLoads(std::map *load_map); + static size_t AddBatch(std::vector &bg, const std::vector &fg, + const std::vector &servers, bool *executed); + static size_t RemoveBatch(std::vector &bg, const std::vector &fg, + const std::vector &servers, bool *executed); + static size_t Remove(std::vector &bg, const std::vector &fg, + const ServerId& server, bool *executed); + size_t _num_replicas; + ConsistentHashingLoadBalancerType _type; + butil::DoublyBufferedData > _db_hash_ring; +}; + +} // namespace policy +} // namespace brpc + + +#endif //BRPC_CONSISTENT_HASHING_LOAD_BALANCER_H diff --git a/src/brpc/policy/constant_concurrency_limiter.cpp b/src/brpc/policy/constant_concurrency_limiter.cpp new file mode 100644 index 0000000..7d73e2e --- /dev/null +++ b/src/brpc/policy/constant_concurrency_limiter.cpp @@ -0,0 +1,51 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/constant_concurrency_limiter.h" + +namespace brpc { +namespace policy { + +ConstantConcurrencyLimiter::ConstantConcurrencyLimiter(int max_concurrency) + : _max_concurrency(max_concurrency) { +} + +bool ConstantConcurrencyLimiter::OnRequested(int current_concurrency, Controller*) { + return current_concurrency <= _max_concurrency; +} + +void ConstantConcurrencyLimiter::OnResponded(int error_code, int64_t latency) { +} + +int ConstantConcurrencyLimiter::MaxConcurrency() { + return _max_concurrency.load(butil::memory_order_relaxed); +} + +int ConstantConcurrencyLimiter::ResetMaxConcurrency( + const AdaptiveMaxConcurrency& amc) { + _max_concurrency.store(static_cast(amc), butil::memory_order_relaxed); + return 0; +} + +ConstantConcurrencyLimiter* +ConstantConcurrencyLimiter::New(const AdaptiveMaxConcurrency& amc) const { + CHECK_EQ(amc.type(), AdaptiveMaxConcurrency::CONSTANT()); + return new ConstantConcurrencyLimiter(static_cast(amc)); +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/constant_concurrency_limiter.h b/src/brpc/policy/constant_concurrency_limiter.h new file mode 100644 index 0000000..9bae939 --- /dev/null +++ b/src/brpc/policy/constant_concurrency_limiter.h @@ -0,0 +1,48 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_POLICY_CONSTANT_CONCURRENCY_LIMITER_H +#define BRPC_POLICY_CONSTANT_CONCURRENCY_LIMITER_H + +#include "brpc/concurrency_limiter.h" + +namespace brpc { +namespace policy { + +class ConstantConcurrencyLimiter : public ConcurrencyLimiter { +public: + explicit ConstantConcurrencyLimiter(int max_concurrency); + + bool OnRequested(int current_concurrency, Controller*) override; + + void OnResponded(int error_code, int64_t latency_us) override; + + int MaxConcurrency() override; + + int ResetMaxConcurrency(const AdaptiveMaxConcurrency&) override; + + ConstantConcurrencyLimiter* New(const AdaptiveMaxConcurrency&) const override; + +private: + butil::atomic _max_concurrency; +}; + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_CONSTANT_CONCURRENCY_LIMITER_H diff --git a/src/brpc/policy/consul_naming_service.cpp b/src/brpc/policy/consul_naming_service.cpp new file mode 100644 index 0000000..70e0a46 --- /dev/null +++ b/src/brpc/policy/consul_naming_service.cpp @@ -0,0 +1,269 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include // std::string +#include // std::set +#include "butil/string_printf.h" +#include "butil/third_party/rapidjson/document.h" +#include "butil/third_party/rapidjson/stringbuffer.h" +#include "butil/third_party/rapidjson/prettywriter.h" +#include "butil/time/time.h" +#include "bthread/bthread.h" +#include "brpc/log.h" +#include "brpc/channel.h" +#include "brpc/policy/file_naming_service.h" +#include "brpc/policy/consul_naming_service.h" + + +namespace brpc { +namespace policy { + +DEFINE_string(consul_agent_addr, "http://127.0.0.1:8500", + "The query string of request consul for discovering service."); +DEFINE_string(consul_service_discovery_url, + "/v1/health/service/", + "The url of consul for discovering service."); +DEFINE_string(consul_url_parameter, "?stale&passing", + "The query string of request consul for discovering service."); +DEFINE_int32(consul_connect_timeout_ms, 200, + "Timeout for creating connections to consul in milliseconds"); +DEFINE_int32(consul_blocking_query_wait_secs, 60, + "Maximum duration for the blocking request in secs."); +DEFINE_bool(consul_enable_degrade_to_file_naming_service, false, + "Use local backup file when consul cannot connect"); +DEFINE_string(consul_file_naming_service_dir, "", + "When it degraded to file naming service, the file with name of the " + "service name will be searched in this dir to use."); +DEFINE_int32(consul_retry_interval_ms, 500, + "Wait so many milliseconds before retry when error happens"); + +constexpr char kConsulIndex[] = "X-Consul-Index"; + +std::string RapidjsonValueToString(const BUTIL_RAPIDJSON_NAMESPACE::Value& value) { + BUTIL_RAPIDJSON_NAMESPACE::StringBuffer buffer; + BUTIL_RAPIDJSON_NAMESPACE::PrettyWriter writer(buffer); + value.Accept(writer); + return buffer.GetString(); +} + +int ConsulNamingService::DegradeToOtherServiceIfNeeded(const char* service_name, + std::vector* servers) { + if (FLAGS_consul_enable_degrade_to_file_naming_service && !_backup_file_loaded) { + _backup_file_loaded = true; + const std::string file(FLAGS_consul_file_naming_service_dir + service_name); + LOG(INFO) << "Load server list from " << file; + FileNamingService fns; + return fns.GetServers(file.c_str(), servers); + } + return -1; +} + +int ConsulNamingService::GetServers(const char* service_name, + std::vector* servers) { + if (!_consul_connected) { + ChannelOptions opt; + opt.protocol = PROTOCOL_HTTP; + opt.connect_timeout_ms = FLAGS_consul_connect_timeout_ms; + opt.timeout_ms = (FLAGS_consul_blocking_query_wait_secs + 10) * butil::Time::kMillisecondsPerSecond; + if (_channel.Init(FLAGS_consul_agent_addr.c_str(), "rr", &opt) != 0) { + LOG(ERROR) << "Fail to init channel to consul at " << FLAGS_consul_agent_addr; + return DegradeToOtherServiceIfNeeded(service_name, servers); + } + _consul_connected = true; + } + + if (_consul_url.empty()) { + _consul_url.append(FLAGS_consul_service_discovery_url); + _consul_url.append(service_name); + _consul_url.append(FLAGS_consul_url_parameter); + } + + servers->clear(); + std::string consul_url(_consul_url); + if (!_consul_index.empty()) { + butil::string_appendf(&consul_url, "&index=%s&wait=%ds", _consul_index.c_str(), + FLAGS_consul_blocking_query_wait_secs); + } + + Controller cntl; + cntl.http_request().uri() = consul_url; + _channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + if (cntl.Failed()) { + LOG(ERROR) << "Fail to access " << consul_url << ": " + << cntl.ErrorText(); + return DegradeToOtherServiceIfNeeded(service_name, servers); + } + + const std::string* index = cntl.http_response().GetHeader(kConsulIndex); + if (index != nullptr) { + if (*index == _consul_index) { + LOG_EVERY_N(INFO, 100) << "There is no service changed for the list of " + << service_name + << ", consul_index: " << _consul_index; + return -1; + } + } else { + LOG(ERROR) << "Failed to parse consul index of " << service_name << "."; + return -1; + } + + // Sort/unique the inserted vector is faster, but may have a different order + // of addresses from the file. To make assertions in tests easier, we use + // set to de-duplicate and keep the order. + std::set presence; + + BUTIL_RAPIDJSON_NAMESPACE::Document services; + services.Parse(cntl.response_attachment().to_string().c_str()); + if (!services.IsArray()) { + LOG(ERROR) << "The consul's response for " + << service_name << " is not a json array"; + return -1; + } + + for (BUTIL_RAPIDJSON_NAMESPACE::SizeType i = 0; i < services.Size(); ++i) { + auto itr_service = services[i].FindMember("Service"); + if (itr_service == services[i].MemberEnd()) { + LOG(ERROR) << "No service info in node: " + << RapidjsonValueToString(services[i]); + continue; + } + + const BUTIL_RAPIDJSON_NAMESPACE::Value& service = itr_service->value; + auto itr_address = service.FindMember("Address"); + auto itr_port = service.FindMember("Port"); + if (itr_address == service.MemberEnd() || + !itr_address->value.IsString() || + itr_port == service.MemberEnd() || + !itr_port->value.IsUint()) { + LOG(ERROR) << "Service with no valid address or port: " + << RapidjsonValueToString(service); + continue; + } + + butil::EndPoint end_point; + if (str2endpoint(service["Address"].GetString(), + service["Port"].GetUint(), + &end_point) != 0) { + LOG(ERROR) << "Service with illegal address or port: " + << RapidjsonValueToString(service); + continue; + } + + ServerNode node; + node.addr = end_point; + auto itr_tags = service.FindMember("Tags"); + if (itr_tags != service.MemberEnd()) { + if (itr_tags->value.IsArray()) { + if (itr_tags->value.Size() > 0) { + // Tags in consul is an array, here we only use the first one. + const BUTIL_RAPIDJSON_NAMESPACE::Value& tag = itr_tags->value[0]; + if (tag.IsString()) { + node.tag = tag.GetString(); + } else { + LOG(ERROR) << "First tag returned by consul is not string, service: " + << RapidjsonValueToString(service); + continue; + } + } + } else { + LOG(ERROR) << "Service tags returned by consul is not json array, service: " + << RapidjsonValueToString(service); + continue; + } + } + + if (presence.insert(node).second) { + servers->push_back(node); + } else { + RPC_VLOG << "Duplicated server=" << node; + } + } + + _consul_index = *index; + + if (servers->empty() && !services.Empty()) { + LOG(ERROR) << "All service about " << service_name + << " from consul is invalid, refuse to update servers"; + return -1; + } + + RPC_VLOG << "Got " << servers->size() + << (servers->size() > 1 ? " servers" : " server") + << " from " << service_name; + return 0; +} + +int ConsulNamingService::RunNamingService(const char* service_name, + NamingServiceActions* actions) { + std::vector servers; + bool ever_reset = false; + for (;;) { + servers.clear(); + const int rc = GetServers(service_name, &servers); + // If `bthread_stop' is called to stop the ns bthread when `brpc::Join‘ is called + // in `GetServers' to wait for a rpc to complete. The bthread will be woken up, + // reset `TaskMeta::interrupted' and continue to join the rpc. After the rpc is complete, + // `bthread_usleep' will not sense the interrupt signal and sleep successfully. + // Finally, the ns bthread will never exit. So need to check the stop status of + // the bthread here and exit the bthread in time. + if (bthread_stopped(bthread_self())) { + RPC_VLOG << "Quit NamingServiceThread=" << bthread_self(); + return 0; + } + if (rc == 0) { + ever_reset = true; + actions->ResetServers(servers); + } else { + if (!ever_reset) { + // ResetServers must be called at first time even if GetServers + // failed, to wake up callers to `WaitForFirstBatchOfServers' + ever_reset = true; + servers.clear(); + actions->ResetServers(servers); + } + if (bthread_usleep(std::max(FLAGS_consul_retry_interval_ms, 1) * butil::Time::kMicrosecondsPerMillisecond) < 0) { + if (errno == ESTOP) { + RPC_VLOG << "Quit NamingServiceThread=" << bthread_self(); + return 0; + } + PLOG(FATAL) << "Fail to sleep"; + return -1; + } + } + } + CHECK(false); + return -1; +} + + +void ConsulNamingService::Describe(std::ostream& os, + const DescribeOptions&) const { + os << "consul"; +} + +NamingService* ConsulNamingService::New() const { + return new ConsulNamingService; +} + +void ConsulNamingService::Destroy() { + delete this; +} + +} // namespace policy +} // namespace brpc \ No newline at end of file diff --git a/src/brpc/policy/consul_naming_service.h b/src/brpc/policy/consul_naming_service.h new file mode 100644 index 0000000..062fed2 --- /dev/null +++ b/src/brpc/policy/consul_naming_service.h @@ -0,0 +1,59 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_CONSUL_NAMING_SERVICE +#define BRPC_POLICY_CONSUL_NAMING_SERVICE + +#include "brpc/naming_service.h" +#include "brpc/channel.h" + + +namespace brpc { +class Channel; +namespace policy { + +class ConsulNamingService : public NamingService { +private: + int RunNamingService(const char* service_name, + NamingServiceActions* actions) override; + + int GetServers(const char* service_name, + std::vector* servers); + + void Describe(std::ostream& os, const DescribeOptions&) const override; + + NamingService* New() const override; + + int DegradeToOtherServiceIfNeeded(const char* service_name, + std::vector* servers); + + void Destroy() override; + +private: + Channel _channel; + std::string _consul_index; + std::string _consul_url; + bool _backup_file_loaded = false; + bool _consul_connected = false; +}; + +} // namespace policy +} // namespace brpc + + +#endif //BRPC_POLICY_CONSUL_NAMING_SERVICE \ No newline at end of file diff --git a/src/brpc/policy/couchbase_authenticator.cpp b/src/brpc/policy/couchbase_authenticator.cpp new file mode 100644 index 0000000..40670f0 --- /dev/null +++ b/src/brpc/policy/couchbase_authenticator.cpp @@ -0,0 +1,57 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/couchbase_authenticator.h" + +#include "butil/base64.h" +#include "butil/iobuf.h" +#include "butil/string_printf.h" +#include "butil/sys_byteorder.h" +#include "brpc/policy/memcache_binary_header.h" + +namespace brpc { +namespace policy { + +namespace { + +constexpr char kPlainAuthCommand[] = "PLAIN"; +constexpr char kPadding[1] = {'\0'}; + +} // namespace + +// To get the couchbase authentication protocol, see +// https://developer.couchbase.com/documentation/server/3.x/developer/dev-guide-3.0/sasl.html +int CouchbaseAuthenticator::GenerateCredential(std::string* auth_str) const { + const brpc::policy::MemcacheRequestHeader header = { + brpc::policy::MC_MAGIC_REQUEST, brpc::policy::MC_BINARY_SASL_AUTH, + butil::HostToNet16(sizeof(kPlainAuthCommand) - 1), 0, 0, 0, + butil::HostToNet32(sizeof(kPlainAuthCommand) + 1 + + bucket_name_.length() * 2 + bucket_password_.length()), + 0, 0}; + auth_str->clear(); + auth_str->append(reinterpret_cast(&header), sizeof(header)); + auth_str->append(kPlainAuthCommand, sizeof(kPlainAuthCommand) - 1); + auth_str->append(bucket_name_); + auth_str->append(kPadding, sizeof(kPadding)); + auth_str->append(bucket_name_); + auth_str->append(kPadding, sizeof(kPadding)); + auth_str->append(bucket_password_); + return 0; +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/couchbase_authenticator.h b/src/brpc/policy/couchbase_authenticator.h new file mode 100644 index 0000000..f974237 --- /dev/null +++ b/src/brpc/policy/couchbase_authenticator.h @@ -0,0 +1,51 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_POLICY_COUCHBASE_AUTHENTICATOR_H +#define BRPC_POLICY_COUCHBASE_AUTHENTICATOR_H + +#include "brpc/authenticator.h" + +namespace brpc { +namespace policy { + +// Request to couchbase for authentication. +// Notice that authentication for couchbase in special SASLAuthProtocol. +// Couchbase Server 2.2 provide CRAM-MD5 support for SASL authentication, +// but Couchbase Server prior to 2.2 using PLAIN SASL authentication. +class CouchbaseAuthenticator : public Authenticator { + public: + CouchbaseAuthenticator(const std::string& bucket_name, + const std::string& bucket_password) + : bucket_name_(bucket_name), bucket_password_(bucket_password) {} + + int GenerateCredential(std::string* auth_str) const; + + int VerifyCredential(const std::string&, const butil::EndPoint&, + brpc::AuthContext*) const { + return 0; + } + + private: + const std::string bucket_name_; + const std::string bucket_password_; +}; + +} // namespace policy +} // namespace brpc + +#endif // BRPC_POLICY_COUCHBASE_AUTHENTICATOR_H diff --git a/src/brpc/policy/couchbase_protocol.cpp b/src/brpc/policy/couchbase_protocol.cpp new file mode 100644 index 0000000..0ab79bf --- /dev/null +++ b/src/brpc/policy/couchbase_protocol.cpp @@ -0,0 +1,239 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/couchbase_protocol.h" + +#include +#include // MethodDescriptor +#include // Message + +#include "brpc/compress.h" // ParseFromCompressedData +#include "brpc/controller.h" // Controller +#include "brpc/couchbase.h" // CouchbaseRequest, CouchbaseResponse +#include "brpc/details/controller_private_accessor.h" +#include "brpc/details/server_private_accessor.h" +#include "brpc/policy/most_common_message.h" +#include "brpc/server.h" // Server +#include "brpc/socket.h" // Socket +#include "brpc/span.h" +#include "butil/containers/flat_map.h" +#include "butil/iobuf.h" // butil::IOBuf +#include "butil/logging.h" // LOG() +#include "butil/sys_byteorder.h" +#include "butil/time.h" + +namespace brpc { + +DECLARE_bool(enable_rpcz); +DECLARE_uint64(max_body_size); + +namespace policy { + +BAIDU_CASSERT(sizeof(CouchbaseRequestHeader) == 24, must_match); +BAIDU_CASSERT(sizeof(CouchbaseResponseHeader) == 24, must_match); + +static uint64_t supported_cmd_map[8]; +static pthread_once_t supported_cmd_map_once = PTHREAD_ONCE_INIT; + +static void InitSupportedCommandMap() { + butil::bit_array_clear(supported_cmd_map, 256); + butil::bit_array_set(supported_cmd_map, CB_BINARY_GET); + butil::bit_array_set(supported_cmd_map, CB_HELLO_SELECT_FEATURES); + butil::bit_array_set(supported_cmd_map, CB_SELECT_BUCKET); + butil::bit_array_set(supported_cmd_map, CB_GET_SCOPE_ID); + butil::bit_array_set(supported_cmd_map, CB_BINARY_SET); + butil::bit_array_set(supported_cmd_map, CB_BINARY_ADD); + butil::bit_array_set(supported_cmd_map, CB_BINARY_REPLACE); + butil::bit_array_set(supported_cmd_map, CB_BINARY_DELETE); + butil::bit_array_set(supported_cmd_map, CB_BINARY_INCREMENT); + butil::bit_array_set(supported_cmd_map, CB_BINARY_DECREMENT); + butil::bit_array_set(supported_cmd_map, CB_BINARY_FLUSH); + butil::bit_array_set(supported_cmd_map, CB_BINARY_VERSION); + butil::bit_array_set(supported_cmd_map, CB_BINARY_NOOP); + butil::bit_array_set(supported_cmd_map, CB_BINARY_APPEND); + butil::bit_array_set(supported_cmd_map, CB_BINARY_PREPEND); + butil::bit_array_set(supported_cmd_map, CB_BINARY_STAT); + butil::bit_array_set(supported_cmd_map, CB_BINARY_TOUCH); + butil::bit_array_set(supported_cmd_map, CB_BINARY_SASL_AUTH); + // Collection management commands + butil::bit_array_set(supported_cmd_map, CB_GET_COLLECTIONS_MANIFEST); + butil::bit_array_set(supported_cmd_map, CB_COLLECTIONS_GET_CID); + butil::bit_array_set(supported_cmd_map, CB_COLLECTIONS_GET_SCOPE_ID); +} + +inline bool IsSupportedCommand(uint8_t command) { + pthread_once(&supported_cmd_map_once, InitSupportedCommandMap); + return butil::bit_array_get(supported_cmd_map, command); +} + +ParseResult ParseCouchbaseMessage(butil::IOBuf* source, Socket* socket, + bool /*read_eof*/, const void* /*arg*/) { + while (1) { + const uint8_t* p_cbmagic = (const uint8_t*)source->fetch1(); + if (NULL == p_cbmagic) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + if (*p_cbmagic != (uint8_t)CB_MAGIC_RESPONSE) { + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + char buf[24]; + const uint8_t* p = (const uint8_t*)source->fetch(buf, sizeof(buf)); + if (NULL == p) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + const CouchbaseResponseHeader* header = (const CouchbaseResponseHeader*)p; + uint32_t total_body_length = butil::NetToHost32(header->total_body_length); + if (total_body_length > FLAGS_max_body_size) { + return MakeParseError(PARSE_ERROR_TOO_BIG_DATA); + } + if (source->size() < sizeof(*header) + total_body_length) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + + if (!IsSupportedCommand(header->command)) { + LOG(WARNING) << "Not support command=" << header->command; + source->pop_front(sizeof(*header) + total_body_length); + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + + PipelinedInfo pi; + if (!socket->PopPipelinedInfo(&pi)) { + LOG(WARNING) << "No corresponding PipelinedInfo in socket, drop"; + source->pop_front(sizeof(*header) + total_body_length); + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + MostCommonMessage* msg = + static_cast(socket->parsing_context()); + if (msg == NULL) { + msg = MostCommonMessage::Get(); + socket->reset_parsing_context(msg); + } + + // endianness conversions. + const CouchbaseResponseHeader local_header = { + header->magic, + header->command, + butil::NetToHost16(header->key_length), + header->extras_length, + header->data_type, + butil::NetToHost16(header->status), + total_body_length, + butil::NetToHost32(header->opaque), + butil::NetToHost64(header->cas_value), + }; + msg->meta.append(&local_header, sizeof(local_header)); + source->pop_front(sizeof(*header)); + source->cutn(&msg->meta, total_body_length); + if (++msg->pi.count >= pi.count) { + CHECK_EQ(msg->pi.count, pi.count); + msg = static_cast(socket->release_parsing_context()); + msg->pi = pi; + return MakeMessage(msg); + } else { + socket->GivebackPipelinedInfo(pi); + } + } +} + +void ProcessCouchbaseResponse(InputMessageBase* msg_base) { + const int64_t start_parse_us = butil::cpuwide_time_us(); + DestroyingPtr msg( + static_cast(msg_base)); + + const bthread_id_t cid = msg->pi.id_wait; + Controller* cntl = NULL; + const int rc = bthread_id_lock(cid, (void**)&cntl); + if (rc != 0) { + LOG_IF(ERROR, rc != EINVAL && rc != EPERM) + << "Fail to lock correlation_id=" << cid << ": " << berror(rc); + return; + } + + ControllerPrivateAccessor accessor(cntl); + if (auto span = accessor.span()) { + span->set_base_real_us(msg->base_real_us()); + span->set_received_us(msg->received_us()); + span->set_response_size(msg->meta.length()); + span->set_start_parse_us(start_parse_us); + } + const int saved_error = cntl->ErrorCode(); + if (cntl->response() == NULL) { + cntl->SetFailed(ERESPONSE, "response is NULL!"); + } else if (cntl->response()->GetDescriptor() != + CouchbaseOperations::CouchbaseResponse::descriptor()) { + cntl->SetFailed(ERESPONSE, "Must be CouchbaseResponse"); + } else { + // We work around ParseFrom of pb which is just a placeholder. + ((CouchbaseOperations::CouchbaseResponse*)cntl->response())->rawBuffer() = + msg->meta.movable(); + if (msg->pi.count != accessor.pipelined_count()) { + cntl->SetFailed(ERESPONSE, + "pipelined_count=%d of response does " + "not equal request's=%d", + msg->pi.count, accessor.pipelined_count()); + } + } + // Unlocks correlation_id inside. Revert controller's + // error code if it version check of `cid' fails + msg.reset(); // optional, just release resource ASAP + accessor.OnResponse(cid, saved_error); +} + +void SerializeCouchbaseRequest(butil::IOBuf* buf, Controller* cntl, + const google::protobuf::Message* request) { + if (request == NULL) { + return cntl->SetFailed(EREQUEST, "request is NULL"); + } + if (request->GetDescriptor() != + CouchbaseOperations::CouchbaseRequest::descriptor()) { + return cntl->SetFailed(EREQUEST, "Must be CouchbaseRequest"); + } + const CouchbaseOperations::CouchbaseRequest* mr = + (const CouchbaseOperations::CouchbaseRequest*)request; + // We work around SerializeTo of pb which is just a placeholder. + *buf = mr->rawBuffer(); + ControllerPrivateAccessor(cntl).set_pipelined_count(mr->pipelinedCount()); +} + +void PackCouchbaseRequest(butil::IOBuf* buf, SocketMessage**, + uint64_t /*correlation_id*/, + const google::protobuf::MethodDescriptor*, + Controller* cntl, const butil::IOBuf& request, + const Authenticator* auth) { + if (auth) { + std::string auth_str; + if (auth->GenerateCredential(&auth_str) != 0) { + return cntl->SetFailed(EREQUEST, "Fail to generate credential"); + } + if (auth_str.empty()) { + return cntl->SetFailed(EREQUEST, "Empty auth_str"); + } + buf->append(auth_str); + // pipelined_count(); + } else { + buf->append(request); + } +} + +const std::string& GetCouchbaseMethodName( + const google::protobuf::MethodDescriptor*, const Controller*) { + const static std::string CouchbaseD_STR = "Couchbase"; + return CouchbaseD_STR; +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/couchbase_protocol.h b/src/brpc/policy/couchbase_protocol.h new file mode 100644 index 0000000..15367de --- /dev/null +++ b/src/brpc/policy/couchbase_protocol.h @@ -0,0 +1,173 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_POLICY_COUCHBASE_BINARY_PROTOCOL_H +#define BRPC_POLICY_COUCHBASE_BINARY_PROTOCOL_H + +#include "brpc/protocol.h" + +namespace brpc { +namespace policy { + +enum CouchbaseMagic { CB_MAGIC_REQUEST = 0x80, CB_MAGIC_RESPONSE = 0x81 }; + +// Definition of the data types in the packet +// https://github.com/couchbase/kv_engine/blob/master/docs/BinaryProtocol.md +enum CouchbaseBinaryDataType { CB_BINARY_RAW_BYTES = 0x00 }; + +enum CouchbaseJsonDataType { CB_JSON = 0x01 }; + +// Definition of the different command opcodes. +// https://github.com/couchbase/kv_engine/blob/master/docs/BinaryProtocol.md +enum CouchbaseBinaryCommand { + CB_HELLO_SELECT_FEATURES = 0x1f, + CB_SELECT_BUCKET = 0x89, + CB_GET_SCOPE_ID = 0xBC, + CB_BINARY_GET = 0x00, + CB_BINARY_SET = 0x01, + CB_BINARY_ADD = 0x02, + CB_BINARY_REPLACE = 0x03, + CB_BINARY_DELETE = 0x04, + CB_BINARY_INCREMENT = 0x05, + CB_BINARY_DECREMENT = 0x06, + CB_BINARY_QUIT = 0x07, + CB_BINARY_FLUSH = 0x08, + CB_BINARY_GETQ = 0x09, + CB_BINARY_NOOP = 0x0a, + CB_BINARY_VERSION = 0x0b, + CB_BINARY_GETK = 0x0c, + CB_BINARY_GETKQ = 0x0d, + CB_BINARY_APPEND = 0x0e, + CB_BINARY_PREPEND = 0x0f, + CB_BINARY_STAT = 0x10, + CB_BINARY_SETQ = 0x11, + CB_BINARY_ADDQ = 0x12, + CB_BINARY_REPLACEQ = 0x13, + CB_BINARY_DELETEQ = 0x14, + CB_BINARY_INCREMENTQ = 0x15, + CB_BINARY_DECREMENTQ = 0x16, + CB_BINARY_QUITQ = 0x17, + CB_BINARY_FLUSHQ = 0x18, + CB_BINARY_APPENDQ = 0x19, + CB_BINARY_PREPENDQ = 0x1a, + CB_BINARY_TOUCH = 0x1c, + CB_BINARY_GAT = 0x1d, + CB_BINARY_GATQ = 0x1e, + CB_BINARY_GATK = 0x23, + CB_BINARY_GATKQ = 0x24, + + CB_BINARY_SASL_LIST_MECHS = 0x20, + CB_BINARY_SASL_AUTH = 0x21, + CB_BINARY_SASL_STEP = 0x22, + + // Collection Management Commands (Couchbase 7.0+) + CB_GET_CLUSTER_CONFIG = 0xb5, + CB_GET_COLLECTIONS_MANIFEST = 0xba, + CB_COLLECTIONS_GET_CID = 0xbb, + CB_COLLECTIONS_GET_SCOPE_ID = 0xbc, + +}; + +struct CouchbaseRequestHeader { + // Magic number identifying the package (See Couchbase Binary + // Protocol#Magic_Byte) + uint8_t magic; + + // Command code (See Couchbase Binary Protocol#Command_opcodes) + uint8_t command; + + // Length in bytes of the text key that follows the command extras + uint16_t key_length; + + // Length in bytes of the command extras + uint8_t extras_length; + + // Reserved for future use (See Couchbase Binary Protocol#Data_Type) + uint8_t data_type; + + // The virtual bucket for this command + uint16_t vbucket_id; + + // Length in bytes of extra + key + value + uint32_t total_body_length; + + // Will be copied back to you in the response + uint32_t opaque; + + // Data version check + uint64_t cas_value; +}; + +struct CouchbaseResponseHeader { + // Magic number identifying the package (See Couchbase Binary + // Protocol#Magic_Byte) + uint8_t magic; + + // Command code (See Couchbase Binary Protocol#Command_opcodes) + uint8_t command; + + // Length in bytes of the text key that follows the command extras + uint16_t key_length; + + // Length in bytes of the command extras + uint8_t extras_length; + + // Reserved for future use (See Couchbase Binary Protocol#Data_Type) + uint8_t data_type; + + // Status of the response (non-zero on error) + uint16_t status; + + // Length in bytes of extra + key + value + uint32_t total_body_length; + + // Will be copied back to you in the response + uint32_t opaque; + + // Data version check + uint64_t cas_value; +}; + +// Parse couchbase messages. +ParseResult ParseCouchbaseMessage(butil::IOBuf* source, Socket* socket, + bool read_eof, const void* arg); + +// Actions to a couchbase response. +void ProcessCouchbaseResponse(InputMessageBase* msg); + +// Serialize a couchbase request. +void SerializeCouchbaseRequest(butil::IOBuf* buf, Controller* cntl, + const google::protobuf::Message* request); + +// Pack `request' to `method' into `buf'. +void PackCouchbaseRequest(butil::IOBuf* buf, SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* controller, const butil::IOBuf& request, + const Authenticator* auth); + +// process couchbase request. +// since, there is no server side instance running, this function is not +// implemented. void ProcessCouchbaseRequest(InputMessageBase* msg); + +const std::string& GetCouchbaseMethodName( + const google::protobuf::MethodDescriptor*, const Controller*); + +} // namespace policy +} // namespace brpc + +#endif // BRPC_POLICY_COUCHBASE_BINARY_PROTOCOL_H diff --git a/src/brpc/policy/crc32c_checksum.cpp b/src/brpc/policy/crc32c_checksum.cpp new file mode 100644 index 0000000..28b6fab --- /dev/null +++ b/src/brpc/policy/crc32c_checksum.cpp @@ -0,0 +1,64 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/crc32c_checksum.h" + +#include "brpc/details/controller_private_accessor.h" +#include "brpc/log.h" +#include "butil/crc32c.h" +#include "butil/sys_byteorder.h" + +namespace brpc { +namespace policy { + +void Crc32cCompute(const ChecksumIn& in) { + auto buf = in.buf; + auto cntl = in.cntl; + butil::IOBufAsZeroCopyInputStream wrapper(*buf); + const void* data; + int size; + uint32_t crc = 0; + while (wrapper.Next(&data, &size)) { + crc = butil::crc32c::Extend(crc, static_cast(data), size); + } + RPC_VLOG << "Crc32cCompute crc=" << crc; + crc = butil::HostToNet32(butil::crc32c::Mask(crc)); + ControllerPrivateAccessor(cntl).set_checksum_value( + reinterpret_cast(&crc), sizeof(crc)); +} + +bool Crc32cVerify(const ChecksumIn& in) { + auto buf = in.buf; + auto cntl = in.cntl; + butil::IOBufAsZeroCopyInputStream wrapper(*buf); + const void* data; + int size; + uint32_t crc = 0; + while (wrapper.Next(&data, &size)) { + crc = butil::crc32c::Extend(crc, static_cast(data), size); + } + auto& val = ControllerPrivateAccessor(const_cast(cntl)) + .checksum_value(); + CHECK_EQ(val.size(), sizeof(crc)); + auto expected = *reinterpret_cast(val.data()); + expected = butil::crc32c::Unmask(butil::NetToHost32(expected)); + RPC_VLOG << "Crc32cVerify crc=" << crc << " expected=" << expected; + return crc == expected; +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/crc32c_checksum.h b/src/brpc/policy/crc32c_checksum.h new file mode 100644 index 0000000..cfdd724 --- /dev/null +++ b/src/brpc/policy/crc32c_checksum.h @@ -0,0 +1,37 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_POLICY_CRC32C_CHECKSUM_H +#define BRPC_POLICY_CRC32C_CHECKSUM_H + +#include "brpc/checksum.h" +#include "brpc/controller.h" +#include "butil/iobuf.h" // butil::IOBuf + +namespace brpc { +namespace policy { + +// Compute checksum +void Crc32cCompute(const ChecksumIn& in); + +// Verify checksum +bool Crc32cVerify(const ChecksumIn& in); + +} // namespace policy +} // namespace brpc + +#endif // BRPC_POLICY_CRC32C_CHECKSUM_H diff --git a/src/brpc/policy/dh.cpp b/src/brpc/policy/dh.cpp new file mode 100644 index 0000000..e56cf19 --- /dev/null +++ b/src/brpc/policy/dh.cpp @@ -0,0 +1,123 @@ +// [ Modified from code in SRS2 (src/protocol/srs_rtmp_handshake.cpp:140) ] +// The MIT License (MIT) +// Copyright (c) 2013-2015 SRS(ossrs) +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "butil/logging.h" +#include "butil/ssl_compat.h" +#include "brpc/log.h" +#include "brpc/policy/dh.h" + +namespace brpc { +namespace policy { + +void DHWrapper::clear() { + if (_pdh != NULL) { + DH_free(_pdh); + _pdh = NULL; + } +} + +int DHWrapper::initialize(bool ensure_128bytes_public_key) { + for (;;) { + if (do_initialize() != 0) { + return -1; + } + if (ensure_128bytes_public_key) { + const BIGNUM* pub_key = NULL; + DH_get0_key(_pdh, &pub_key, NULL); + int key_size = BN_num_bytes(pub_key); + if (key_size != 128) { + RPC_VLOG << "regenerate 128B key, current=" << key_size; + clear(); + continue; + } + } + break; + } + return 0; +} + +int DHWrapper::copy_public_key(char* pkey, int* pkey_size) const { + const BIGNUM* pub_key = NULL; + DH_get0_key(_pdh, &pub_key, NULL); + // copy public key to bytes. + // sometimes, the key_size is 127, seems ok. + int key_size = BN_num_bytes(pub_key); + CHECK_GT(key_size, 0); + + // maybe the key_size is 127, but dh will write all 128bytes pkey, + // no need to set/initialize the pkey. + // @see https://github.com/ossrs/srs/issues/165 + key_size = BN_bn2bin(pub_key, (unsigned char*)pkey); + CHECK_GT(key_size, 0); + + // output the size of public key. + // @see https://github.com/ossrs/srs/issues/165 + CHECK_LE(key_size, *pkey_size); + *pkey_size = key_size; + return 0; +} + +int DHWrapper::copy_shared_key(const void* ppkey, int ppkey_size, + void* skey, int* skey_size) const { + BIGNUM* ppk = BN_bin2bn((const unsigned char*)ppkey, ppkey_size, 0); + if (ppk == NULL) { + LOG(ERROR) << "Fail to BN_bin2bn"; + return -1; + } + // @see https://github.com/ossrs/srs/issues/165 + int key_size = DH_compute_key((unsigned char*)skey, ppk, _pdh); + if (key_size < 0 || key_size > *skey_size) { + LOG(ERROR) << "Fail to compute shared key"; + BN_free(ppk); + return -1; + } + *skey_size = key_size; + return 0; +} + +int DHWrapper::do_initialize() { + BIGNUM* p = get_rfc2409_prime_1024(NULL); + if (!p) { + return -1; + } + // See RFC 2409, Section 6 "Oakley Groups" + // for the reason why 2 is used as generator. + BIGNUM* g = NULL; + BN_dec2bn(&g, "2"); + if (!g) { + BN_free(p); + return -1; + } + _pdh = DH_new(); + if (!_pdh) { + BN_free(p); + BN_free(g); + return -1; + } + DH_set0_pqg(_pdh, p, NULL, g); + + // Generate private and public key + if (!DH_generate_key(_pdh)) { + LOG(ERROR) << "Fail to DH_generate_key"; + return -1; + } + return 0; +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/dh.h b/src/brpc/policy/dh.h new file mode 100644 index 0000000..8b888ba --- /dev/null +++ b/src/brpc/policy/dh.h @@ -0,0 +1,67 @@ +// [ Modified from code in SRS2 (src/protocol/srs_rtmp_handshake.cpp:140) ] +// The MIT License (MIT) +// Copyright (c) 2013-2015 SRS(ossrs) +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef BRPC_POLICY_DH_H +#define BRPC_POLICY_DH_H + +#include + + +namespace brpc { +namespace policy { + +// Diffie-Hellman key exchange +class DHWrapper { +public: + DHWrapper() : _pdh(NULL) {} + ~DHWrapper() { clear(); } + + // initialize dh, generate the public and private key. + // set `ensure_128bytes_public_key' to true to ensure public key is 128bytes + int initialize(bool ensure_128bytes_public_key = false); + + // copy the public key. + // @param pkey the bytes to copy the public key. + // @param pkey_size the max public key size, output the actual public key size. + // user should never ignore this size. + // @remark, when ensure_128bytes_public_key is true, the size always 128. + int copy_public_key(char* pkey, int* pkey_size) const; + + // generate and copy the shared key. + // generate the shared key with peer public key. + // @param ppkey peer public key. + // @param ppkey_size the size of ppkey. + // @param skey the computed shared key. + // @param skey_size the max shared key size, output the actual shared key size. + // user should never ignore this size. + int copy_shared_key(const void* ppkey, int ppkey_size, + void* skey, int* skey_size) const; + +private: + int do_initialize(); + void clear(); + +private: + DH* _pdh; +}; + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_DH_H diff --git a/src/brpc/policy/discovery_naming_service.cpp b/src/brpc/policy/discovery_naming_service.cpp new file mode 100644 index 0000000..b935bcf --- /dev/null +++ b/src/brpc/policy/discovery_naming_service.cpp @@ -0,0 +1,469 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/third_party/rapidjson/document.h" +#include "butil/third_party/rapidjson/memorybuffer.h" +#include "butil/third_party/rapidjson/writer.h" +#include "butil/string_printf.h" +#include "butil/strings/string_split.h" +#include "butil/fast_rand.h" +#include "bthread/bthread.h" +#include "brpc/channel.h" +#include "brpc/controller.h" +#include "brpc/policy/discovery_naming_service.h" + +namespace brpc { +namespace policy { + +#ifdef BILIBILI_INTERNAL +# define DEFAULT_DISCOVERY_API_ADDR "http://api.bilibili.co/discovery/nodes" +#else +# define DEFAULT_DISCOVERY_API_ADDR "" +#endif + +DEFINE_string(discovery_api_addr, DEFAULT_DISCOVERY_API_ADDR, "The address of discovery api"); +DEFINE_int32(discovery_timeout_ms, 3000, "Timeout for discovery requests"); +DEFINE_string(discovery_env, "prod", "Environment of services"); +DEFINE_string(discovery_status, "1", "Status of services. 1 for ready, 2 for not ready, 3 for all"); +DEFINE_string(discovery_zone, "", "Zone of services"); +DEFINE_int32(discovery_renew_interval_s, 30, "The interval between two consecutive renews"); +DEFINE_int32(discovery_reregister_threshold, 3, "The renew error threshold beyond" + " which Register would be called again"); + +static pthread_once_t s_init_discovery_channel_once = PTHREAD_ONCE_INIT; +static Channel* s_discovery_channel = NULL; + +static int ListDiscoveryNodes(const char* discovery_api_addr, std::string* servers) { + Channel api_channel; + ChannelOptions channel_options; + channel_options.protocol = PROTOCOL_HTTP; + channel_options.timeout_ms = FLAGS_discovery_timeout_ms; + channel_options.connect_timeout_ms = FLAGS_discovery_timeout_ms / 3; + if (api_channel.Init(discovery_api_addr, "", &channel_options) != 0) { + LOG(FATAL) << "Fail to init channel to " << discovery_api_addr; + return -1; + } + Controller cntl; + cntl.http_request().uri() = discovery_api_addr; + api_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + if (cntl.Failed()) { + LOG(FATAL) << "Fail to access " << cntl.http_request().uri() + << ": " << cntl.ErrorText(); + return -1; + } + + servers->assign("list://"); + + const std::string response = cntl.response_attachment().to_string(); + BUTIL_RAPIDJSON_NAMESPACE::Document d; + d.Parse(response.c_str()); + if (!d.IsObject()) { + LOG(ERROR) << "Fail to parse " << response << " as json object"; + return -1; + } + auto itr = d.FindMember("data"); + if (itr == d.MemberEnd()) { + LOG(ERROR) << "No data field in discovery nodes response"; + return -1; + } + const BUTIL_RAPIDJSON_NAMESPACE::Value& data = itr->value; + if (!data.IsArray()) { + LOG(ERROR) << "data field is not an array"; + return -1; + } + for (BUTIL_RAPIDJSON_NAMESPACE::SizeType i = 0; i < data.Size(); ++i) { + const BUTIL_RAPIDJSON_NAMESPACE::Value& addr_item = data[i]; + auto itr_addr = addr_item.FindMember("addr"); + auto itr_status = addr_item.FindMember("status"); + if (itr_addr == addr_item.MemberEnd() || + !itr_addr->value.IsString() || + itr_status == addr_item.MemberEnd() || + !itr_status->value.IsUint() || + itr_status->value.GetUint() != 0) { + continue; + } + servers->push_back(','); + servers->append(itr_addr->value.GetString(), + itr_addr->value.GetStringLength()); + } + return 0; +} + +static void NewDiscoveryChannel() { + // NOTE: Newly added discovery server is NOT detected until this server + // is restarted. The reasons for this design is that NS cluster rarely + // changes. Although we could detect new discovery servers by implmenenting + // a NamingService, however which is too heavy for solving such a rare case. + std::string discovery_servers; + if (ListDiscoveryNodes(FLAGS_discovery_api_addr.c_str(), &discovery_servers) != 0) { + LOG(ERROR) << "Fail to get discovery nodes from " << FLAGS_discovery_api_addr; + return; + } + ChannelOptions channel_options; + channel_options.protocol = PROTOCOL_HTTP; + channel_options.timeout_ms = FLAGS_discovery_timeout_ms; + channel_options.connect_timeout_ms = FLAGS_discovery_timeout_ms / 3; + s_discovery_channel = new Channel; + if (s_discovery_channel->Init(discovery_servers.c_str(), "rr", &channel_options) != 0) { + LOG(ERROR) << "Fail to init channel to " << discovery_servers; + return; + } +} + +inline Channel* GetOrNewDiscoveryChannel() { + pthread_once(&s_init_discovery_channel_once, NewDiscoveryChannel); + return s_discovery_channel; +} + +bool DiscoveryRegisterParam::IsValid() const { + return !appid.empty() && !hostname.empty() && !addrs.empty() && + !env.empty() && !zone.empty() && !version.empty(); +} + +DiscoveryClient::DiscoveryClient() + : _th(INVALID_BTHREAD) + , _registered(false) {} + +DiscoveryClient::~DiscoveryClient() { + if (_registered.load(butil::memory_order_acquire)) { + bthread_stop(_th); + bthread_join(_th, NULL); + DoCancel(); + } +} + +static int ParseCommonResult(const butil::IOBuf& buf, std::string* error_text) { + const std::string s = buf.to_string(); + BUTIL_RAPIDJSON_NAMESPACE::Document d; + d.Parse(s.c_str()); + if (!d.IsObject()) { + LOG(ERROR) << "Fail to parse " << buf << " as json object"; + return -1; + } + auto itr_code = d.FindMember("code"); + if (itr_code == d.MemberEnd() || !itr_code->value.IsInt()) { + LOG(ERROR) << "Invalid `code' field in " << buf; + return -1; + } + int code = itr_code->value.GetInt(); + auto itr_message = d.FindMember("message"); + if (itr_message != d.MemberEnd() && itr_message->value.IsString() && error_text) { + error_text->assign(itr_message->value.GetString(), + itr_message->value.GetStringLength()); + } + return code; +} + +int DiscoveryClient::DoRenew() const { + // May create short connections which are OK. + ChannelOptions channel_options; + channel_options.protocol = PROTOCOL_HTTP; + channel_options.timeout_ms = FLAGS_discovery_timeout_ms; + channel_options.connect_timeout_ms = FLAGS_discovery_timeout_ms / 3; + Channel chan; + if (chan.Init(_current_discovery_server, &channel_options) != 0) { + LOG(FATAL) << "Fail to init channel to " << _current_discovery_server; + return -1; + } + + Controller cntl; + cntl.http_request().set_method(HTTP_METHOD_POST); + cntl.http_request().uri() = "/discovery/renew"; + cntl.http_request().set_content_type("application/x-www-form-urlencoded"); + butil::IOBufBuilder os; + os << "appid=" << _params.appid + << "&hostname=" << _params.hostname + << "&env=" << _params.env + << "®ion=" << _params.region + << "&zone=" << _params.zone; + os.move_to(cntl.request_attachment()); + chan.CallMethod(NULL, &cntl, NULL, NULL, NULL); + if (cntl.Failed()) { + LOG(ERROR) << "Fail to post /discovery/renew: " << cntl.ErrorText(); + return -1; + } + std::string error_text; + if (ParseCommonResult(cntl.response_attachment(), &error_text) != 0) { + LOG(ERROR) << "Fail to renew " << _params.hostname << " to " << _params.appid + << ": " << error_text; + return -1; + } + return 0; +} + +void* DiscoveryClient::PeriodicRenew(void* arg) { + DiscoveryClient* d = static_cast(arg); + int consecutive_renew_error = 0; + int64_t init_sleep_s = FLAGS_discovery_renew_interval_s / 2 + + butil::fast_rand_less_than(FLAGS_discovery_renew_interval_s / 2); + if (bthread_usleep(init_sleep_s * 1000000) != 0) { + if (errno == ESTOP) { + return NULL; + } + } + + while (!bthread_stopped(bthread_self())) { + if (consecutive_renew_error == FLAGS_discovery_reregister_threshold) { + LOG(WARNING) << "Re-register since discovery renew error threshold reached"; + // Do register until succeed or Cancel is called + while (!bthread_stopped(bthread_self())) { + if (d->DoRegister() == 0) { + break; + } + bthread_usleep(FLAGS_discovery_renew_interval_s * 1000000); + } + consecutive_renew_error = 0; + } + if (d->DoRenew() != 0) { + consecutive_renew_error++; + continue; + } + consecutive_renew_error = 0; + bthread_usleep(FLAGS_discovery_renew_interval_s * 1000000); + } + return NULL; +} + +int DiscoveryClient::Register(const DiscoveryRegisterParam& params) { + if (_registered.load(butil::memory_order_relaxed) || + _registered.exchange(true, butil::memory_order_release)) { + return 0; + } + if (!params.IsValid()) { + return -1; + } + _params = params; + + if (DoRegister() != 0) { + return -1; + } + if (bthread_start_background(&_th, NULL, PeriodicRenew, this) != 0) { + LOG(ERROR) << "Fail to start background PeriodicRenew"; + return -1; + } + return 0; +} + +int DiscoveryClient::DoRegister() { + Channel* chan = GetOrNewDiscoveryChannel(); + if (NULL == chan) { + LOG(ERROR) << "Fail to create discovery channel"; + return -1; + } + Controller cntl; + cntl.http_request().set_method(HTTP_METHOD_POST); + cntl.http_request().uri() = "/discovery/register"; + cntl.http_request().set_content_type("application/x-www-form-urlencoded"); + butil::IOBufBuilder os; + os << "appid=" << _params.appid + << "&hostname=" << _params.hostname; + + std::vector addrs; + butil::SplitString(_params.addrs, ',', &addrs); + for (size_t i = 0; i < addrs.size(); ++i) { + if (!addrs[i].empty()) { + os << "&addrs=" << addrs[i]; + } + } + + os << "&env=" << _params.env + << "&zone=" << _params.zone + << "®ion=" << _params.region + << "&status=" << _params.status + << "&version=" << _params.version + << "&metadata=" << _params.metadata; + os.move_to(cntl.request_attachment()); + chan->CallMethod(NULL, &cntl, NULL, NULL, NULL); + if (cntl.Failed()) { + LOG(ERROR) << "Fail to register " << _params.appid << ": " << cntl.ErrorText(); + return -1; + } + std::string error_text; + if (ParseCommonResult(cntl.response_attachment(), &error_text) != 0) { + LOG(ERROR) << "Fail to register " << _params.hostname << " to " << _params.appid + << ": " << error_text; + return -1; + } + _current_discovery_server = cntl.remote_side(); + return 0; +} + +int DiscoveryClient::DoCancel() const { + // May create short connections which are OK. + ChannelOptions channel_options; + channel_options.protocol = PROTOCOL_HTTP; + channel_options.timeout_ms = FLAGS_discovery_timeout_ms; + channel_options.connect_timeout_ms = FLAGS_discovery_timeout_ms / 3; + Channel chan; + if (chan.Init(_current_discovery_server, &channel_options) != 0) { + LOG(FATAL) << "Fail to init channel to " << _current_discovery_server; + return -1; + } + + Controller cntl; + cntl.http_request().set_method(HTTP_METHOD_POST); + cntl.http_request().uri() = "/discovery/cancel"; + cntl.http_request().set_content_type("application/x-www-form-urlencoded"); + butil::IOBufBuilder os; + os << "appid=" << _params.appid + << "&hostname=" << _params.hostname + << "&env=" << _params.env + << "®ion=" << _params.region + << "&zone=" << _params.zone; + os.move_to(cntl.request_attachment()); + chan.CallMethod(NULL, &cntl, NULL, NULL, NULL); + if (cntl.Failed()) { + LOG(ERROR) << "Fail to post /discovery/cancel: " << cntl.ErrorText(); + return -1; + } + std::string error_text; + if (ParseCommonResult(cntl.response_attachment(), &error_text) != 0) { + LOG(ERROR) << "Fail to cancel " << _params.hostname << " in " << _params.appid + << ": " << error_text; + return -1; + } + return 0; +} + +// ========== DiscoveryNamingService ============= + +int DiscoveryNamingService::GetServers(const char* service_name, + std::vector* servers) { + if (service_name == NULL || *service_name == '\0' || + FLAGS_discovery_env.empty() || + FLAGS_discovery_status.empty()) { + LOG_ONCE(ERROR) << "Invalid parameters"; + return -1; + } + Channel* chan = GetOrNewDiscoveryChannel(); + if (NULL == chan) { + LOG(ERROR) << "Fail to create discovery channel"; + return -1; + } + servers->clear(); + Controller cntl; + std::string uri_str = butil::string_printf( + "/discovery/fetchs?appid=%s&env=%s&status=%s", service_name, + FLAGS_discovery_env.c_str(), FLAGS_discovery_status.c_str()); + if (!FLAGS_discovery_zone.empty()) { + uri_str.append("&zone="); + uri_str.append(FLAGS_discovery_zone); + } + cntl.http_request().uri() = uri_str; + chan->CallMethod(NULL, &cntl, NULL, NULL, NULL); + if (cntl.Failed()) { + LOG(ERROR) << "Fail to get /discovery/fetchs: " << cntl.ErrorText(); + return -1; + } + + const std::string response = cntl.response_attachment().to_string(); + BUTIL_RAPIDJSON_NAMESPACE::Document d; + d.Parse(response.c_str()); + if (!d.IsObject()) { + LOG(ERROR) << "Fail to parse " << response << " as json object"; + return -1; + } + auto itr_data = d.FindMember("data"); + if (itr_data == d.MemberEnd()) { + LOG(ERROR) << "No data field in discovery/fetchs response"; + return -1; + } + const BUTIL_RAPIDJSON_NAMESPACE::Value& data = itr_data->value; + auto itr_service = data.FindMember(service_name); + if (itr_service == data.MemberEnd()) { + LOG(ERROR) << "No " << service_name << " field in discovery response"; + return -1; + } + const BUTIL_RAPIDJSON_NAMESPACE::Value& services = itr_service->value; + auto itr_instances = services.FindMember("instances"); + if (itr_instances == services.MemberEnd()) { + LOG(ERROR) << "Fail to find instances"; + return -1; + } + const BUTIL_RAPIDJSON_NAMESPACE::Value& instances = itr_instances->value; + if (!instances.IsArray()) { + LOG(ERROR) << "Fail to parse instances as an array"; + return -1; + } + + for (BUTIL_RAPIDJSON_NAMESPACE::SizeType i = 0; i < instances.Size(); ++i) { + std::string metadata; + // convert metadata in object to string + auto itr_metadata = instances[i].FindMember("metadata"); + if (itr_metadata != instances[i].MemberEnd()) { + BUTIL_RAPIDJSON_NAMESPACE::MemoryBuffer buffer; + BUTIL_RAPIDJSON_NAMESPACE::Writer writer(buffer); + itr_metadata->value.Accept(writer); + metadata.assign(buffer.GetBuffer(), buffer.GetSize()); + } + + auto itr = instances[i].FindMember("addrs"); + if (itr == instances[i].MemberEnd() || !itr->value.IsArray()) { + LOG(ERROR) << "Fail to find addrs or addrs is not an array"; + return -1; + } + const BUTIL_RAPIDJSON_NAMESPACE::Value& addrs = itr->value; + for (BUTIL_RAPIDJSON_NAMESPACE::SizeType j = 0; j < addrs.Size(); ++j) { + if (!addrs[j].IsString()) { + continue; + } + // The result returned by discovery include protocol prefix, such as + // http://172.22.35.68:6686, which should be removed. + butil::StringPiece addr(addrs[j].GetString(), addrs[j].GetStringLength()); + butil::StringPiece::size_type pos = addr.find("://"); + if (pos != butil::StringPiece::npos) { + if (pos != 4 /* sizeof("grpc") */ || + strncmp("grpc", addr.data(), 4) != 0) { + // Skip server that has prefix but not start with "grpc" + continue; + } + addr.remove_prefix(pos + 3); + } + ServerNode node; + node.tag = metadata; + // Variable addr contains data from addrs[j].GetString(), it is a + // null-terminated string, so it is safe to pass addr.data() as the + // first parameter to str2endpoint. + if (str2endpoint(addr.data(), &node.addr) != 0) { + LOG(ERROR) << "Invalid address=`" << addr << '\''; + continue; + } + servers->push_back(node); + } + } + return 0; +} + +void DiscoveryNamingService::Describe(std::ostream& os, + const DescribeOptions&) const { + os << "discovery"; + return; +} + +NamingService* DiscoveryNamingService::New() const { + return new DiscoveryNamingService; +} + +void DiscoveryNamingService::Destroy() { + delete this; +} + + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/discovery_naming_service.h b/src/brpc/policy/discovery_naming_service.h new file mode 100644 index 0000000..8a092a8 --- /dev/null +++ b/src/brpc/policy/discovery_naming_service.h @@ -0,0 +1,89 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_DISCOVERY_NAMING_SERVICE_H +#define BRPC_POLICY_DISCOVERY_NAMING_SERVICE_H + +#include "brpc/periodic_naming_service.h" +#include "brpc/channel.h" +#include "butil/synchronization/lock.h" + +namespace brpc { +namespace policy { + +struct DiscoveryRegisterParam { + std::string appid; + std::string hostname; + std::string env; + std::string zone; + std::string region; + std::string addrs; // splitted by ',' + int status; + std::string version; + std::string metadata; + + bool IsValid() const; +}; + +// ONE DiscoveryClient corresponds to ONE service instance. +// If your program has multiple service instances to register, +// you need multiple DiscoveryClient. +// Note: Cancel to the server is automatically called in dtor. +class DiscoveryClient { +public: + DiscoveryClient(); + ~DiscoveryClient(); + + // Initialize this client. + // Returns 0 on success. + // NOTE: Calling more than once does nothing and returns 0. + int Register(const DiscoveryRegisterParam& req); + +private: + static void* PeriodicRenew(void* arg); + int DoCancel() const; + int DoRegister(); + int DoRenew() const; + +private: + bthread_t _th; + butil::atomic _registered; + DiscoveryRegisterParam _params; + butil::EndPoint _current_discovery_server; +}; + +class DiscoveryNamingService : public PeriodicNamingService { +private: + int GetServers(const char* service_name, + std::vector* servers) override; + + void Describe(std::ostream& os, const DescribeOptions&) const override; + + NamingService* New() const override; + + void Destroy() override; + +private: + DiscoveryClient _client; +}; + + +} // namespace policy +} // namespace brpc + +#endif // BRPC_POLICY_DISCOVERY_NAMING_SERVICE_H diff --git a/src/brpc/policy/domain_naming_service.cpp b/src/brpc/policy/domain_naming_service.cpp new file mode 100644 index 0000000..d93d799 --- /dev/null +++ b/src/brpc/policy/domain_naming_service.cpp @@ -0,0 +1,184 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/build_config.h" // OS_MACOSX +#include // gethostbyname_r +#include // strtol +#include // std::string +#include "bthread/bthread.h" +#include "brpc/log.h" +#include "brpc/policy/domain_naming_service.h" + +DEFINE_bool(dns_support_ipv6, false, "Resolve DNS by IPV6 address first"); + +namespace brpc { +namespace policy { + +DomainNamingService::DomainNamingService(int default_port) + : _aux_buf_len(0) + , _default_port(default_port) {} + +int DomainNamingService::GetServers(const char* dns_name, + std::vector* servers) { + servers->clear(); + if (!dns_name) { + LOG(ERROR) << "dns_name is NULL"; + return -1; + } + + // Should be enough to hold host name + char buf[256]; + size_t i = 0; + for (; i < sizeof(buf) - 1 && dns_name[i] != '\0' + && dns_name[i] != ':' && dns_name[i] != '/'; ++i) { + buf[i] = dns_name[i]; + } + if (i == sizeof(buf) - 1) { + LOG(ERROR) << "dns_name=`" << dns_name << "' is too long"; + return -1; + } + + buf[i] = '\0'; + int port = _default_port; + if (dns_name[i] == ':') { + ++i; + char* end = NULL; + port = strtol(dns_name + i, &end, 10); + if (end == dns_name + i) { + LOG(ERROR) << "No port after colon in `" << dns_name << '\''; + return -1; + } else if (*end != '\0') { + if (*end != '/') { + LOG(ERROR) << "Invalid content=`" << end << "' after port=" + << port << " in `" << dns_name << '\''; + return -1; + } + // Drop path and other stuff. + RPC_VLOG << "Drop content=`" << end << "' after port=" << port + << " in `" << dns_name << '\''; + // NOTE: Don't ever change *end which is const. + } + } + if (port < 0 || port > 65535) { + LOG(ERROR) << "Invalid port=" << port << " in `" << dns_name << '\''; + return -1; + } + + if (FLAGS_dns_support_ipv6) { + struct addrinfo hints; + struct addrinfo* addrResult; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_DGRAM; + char portBuf[16]; + snprintf(portBuf, arraysize(portBuf), "%d", port); + auto ret = getaddrinfo(buf, portBuf, &hints, &addrResult); + if (!ret) { + for(auto rp = addrResult; rp != NULL; rp = rp->ai_next) { + butil::EndPoint point; + auto ret = butil::sockaddr2endpoint((struct sockaddr_storage*)rp->ai_addr, rp->ai_addrlen, &point); + if(!ret) { + servers->push_back(ServerNode(point, std::string())); + } + } + + freeaddrinfo(addrResult); + return 0; + } else { + LOG(WARNING) << "Can't resolve `" << buf << "for ipv6, fallback to ipv4"; + // fallback to ipv4 + } + + } + +#if defined(OS_MACOSX) + _aux_buf_len = 0; // suppress unused warning + // gethostbyname on MAC is thread-safe (with current usage) since the + // returned hostent is TLS. Check following link for the ref: + // https://lists.apple.com/archives/darwin-dev/2006/May/msg00008.html + struct hostent* result = gethostbyname(buf); + if (result == NULL) { + LOG(WARNING) << "result of gethostbyname is NULL"; + return -1; + } +#else + if (_aux_buf == NULL) { + _aux_buf_len = 1024; + _aux_buf.reset(new char[_aux_buf_len]); + } + int ret = 0; + int error = 0; + struct hostent ent; + struct hostent* result = NULL; + do { + result = NULL; + error = 0; + ret = gethostbyname_r(buf, &ent, _aux_buf.get(), _aux_buf_len, + &result, &error); + if (ret != ERANGE) { // _aux_buf is not long enough + break; + } + _aux_buf_len *= 2; + _aux_buf.reset(new char[_aux_buf_len]); + RPC_VLOG << "Resized _aux_buf to " << _aux_buf_len + << ", dns_name=" << dns_name; + } while (1); + if (ret != 0) { + // `hstrerror' is thread safe under linux + LOG(WARNING) << "Can't resolve `" << buf << "', return=`" << berror(ret) + << "' herror=`" << hstrerror(error) << '\''; + return -1; + } + if (result == NULL) { + LOG(WARNING) << "result of gethostbyname_r is NULL"; + return -1; + } +#endif + + //TODO add protocols other than IPv4 supports + butil::EndPoint point; + point.port = port; + for (int i = 0; result->h_addr_list[i] != NULL; ++i) { + if (result->h_addrtype == AF_INET) { + // Only fetch IPv4 addresses + bcopy(result->h_addr_list[i], &point.ip, result->h_length); + servers->push_back(ServerNode(point, std::string())); + } else { + LOG(WARNING) << "Found address of unsupported protocol=" + << result->h_addrtype; + } + } + return 0; +} + +void DomainNamingService::Describe( + std::ostream& os, const DescribeOptions&) const { + os << "http"; + return; +} + +NamingService* DomainNamingService::New() const { + return new DomainNamingService(_default_port); +} + +void DomainNamingService::Destroy() { + delete this; +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/domain_naming_service.h b/src/brpc/policy/domain_naming_service.h new file mode 100644 index 0000000..f8874f6 --- /dev/null +++ b/src/brpc/policy/domain_naming_service.h @@ -0,0 +1,54 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_DOMAIN_NAMING_SERVICE_H +#define BRPC_POLICY_DOMAIN_NAMING_SERVICE_H + +#include "brpc/periodic_naming_service.h" +#include "butil/unique_ptr.h" + + +namespace brpc { +namespace policy { + +class DomainNamingService : public PeriodicNamingService { +public: + DomainNamingService(int default_port); + DomainNamingService() : DomainNamingService(80) {} + +private: + int GetServers(const char *service_name, + std::vector* servers) override; + + void Describe(std::ostream& os, const DescribeOptions&) const override; + + NamingService* New() const override; + + void Destroy() override; + +private: + std::unique_ptr _aux_buf; + size_t _aux_buf_len; + int _default_port; +}; + +} // namespace policy +} // namespace brpc + + +#endif //BRPC_POLICY_DOMAIN_NAMING_SERVICE_H diff --git a/src/brpc/policy/dynpart_load_balancer.cpp b/src/brpc/policy/dynpart_load_balancer.cpp new file mode 100644 index 0000000..ad3cbbc --- /dev/null +++ b/src/brpc/policy/dynpart_load_balancer.cpp @@ -0,0 +1,185 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "butil/macros.h" +#include "butil/fast_rand.h" +#include "brpc/socket.h" +#include "brpc/policy/dynpart_load_balancer.h" + + +namespace brpc { + +namespace schan { +// defined in brpc/selective_channel.cpp +int GetSubChannelWeight(SocketUser* u); +} + +namespace policy { + +bool DynPartLoadBalancer::Add(Servers& bg, const ServerId& id) { + if (bg.server_list.capacity() < 128) { + bg.server_list.reserve(128); + } + std::map::iterator it = bg.server_map.find(id); + if (it != bg.server_map.end()) { + return false; + } + bg.server_map[id] = bg.server_list.size(); + bg.server_list.push_back(id); + return true; +} + +bool DynPartLoadBalancer::Remove(Servers& bg, const ServerId& id) { + std::map::iterator it = bg.server_map.find(id); + if (it != bg.server_map.end()) { + size_t index = it->second; + bg.server_list[index] = bg.server_list.back(); + bg.server_map[bg.server_list[index]] = index; + bg.server_list.pop_back(); + bg.server_map.erase(it); + return true; + } + return false; +} + +size_t DynPartLoadBalancer::BatchAdd( + Servers& bg, const std::vector& servers) { + size_t count = 0; + for (size_t i = 0; i < servers.size(); ++i) { + count += !!Add(bg, servers[i]); + } + return count; +} + +size_t DynPartLoadBalancer::BatchRemove( + Servers& bg, const std::vector& servers) { + size_t count = 0; + for (size_t i = 0; i < servers.size(); ++i) { + count += !!Remove(bg, servers[i]); + } + return count; +} + +bool DynPartLoadBalancer::AddServer(const ServerId& id) { + return _db_servers.Modify(Add, id); +} + +bool DynPartLoadBalancer::RemoveServer(const ServerId& id) { + return _db_servers.Modify(Remove, id); +} + +size_t DynPartLoadBalancer::AddServersInBatch( + const std::vector& servers) { + const size_t n = _db_servers.Modify(BatchAdd, servers); + LOG_IF(ERROR, n != servers.size()) + << "Fail to AddServersInBatch, expected " << servers.size() + << " actually " << n; + return n; +} + +size_t DynPartLoadBalancer::RemoveServersInBatch( + const std::vector& servers) { + const size_t n = _db_servers.Modify(BatchRemove, servers); + return n; +} + +int DynPartLoadBalancer::SelectServer(const SelectIn& in, SelectOut* out) { + butil::DoublyBufferedData::ScopedPtr s; + if (_db_servers.Read(&s) != 0) { + return ENOMEM; + } + const size_t n = s->server_list.size(); + if (n == 0) { + return ENODATA; + } + if (n == 1) { + if (Socket::Address(s->server_list[0].id, out->ptr) == 0) { + return 0; + } + return EHOSTDOWN; + } + int64_t total_weight = 0; + std::pair ptrs[8]; + int nptr = 0; + bool exclusion = true; + do { + for (size_t i = 0; i < n; ++i) { + const SocketId id = s->server_list[i].id; + if ((!exclusion || !ExcludedServers::IsExcluded(in.excluded, id)) + && Socket::Address(id, &ptrs[nptr].first) == 0) { + int w = schan::GetSubChannelWeight(ptrs[nptr].first->user()); + total_weight += w; + RELEASE_ASSERT_VERBOSE(nptr < 8, "Not supported yet"); + ptrs[nptr].second = total_weight; + ++nptr; + } + } + if (nptr != 0) { + break; + } + if (!exclusion) { + return EHOSTDOWN; + } + exclusion = false; + CHECK_EQ(0, total_weight); + total_weight = 0; + } while (1); + + if (nptr == 1) { + out->ptr->reset(ptrs[0].first.release()); + return 0; + } + uint32_t r = butil::fast_rand_less_than(total_weight); + for (int i = 0; i < nptr; ++i) { + if (ptrs[i].second > r) { + out->ptr->reset(ptrs[i].first.release()); + return 0; + } + } + return EHOSTDOWN; +} + +DynPartLoadBalancer* DynPartLoadBalancer::New(const butil::StringPiece&) const { + return new (std::nothrow) DynPartLoadBalancer; +} + +void DynPartLoadBalancer::Destroy() { + delete this; +} + +void DynPartLoadBalancer::Describe( + std::ostream &os, const DescribeOptions& options) { + if (!options.verbose) { + os << "dynpart"; + return; + } + os << "DynPart{"; + butil::DoublyBufferedData::ScopedPtr s; + if (_db_servers.Read(&s) != 0) { + os << "fail to read _db_servers"; + } else { + os << "n=" << s->server_list.size() << ':'; + for (size_t i = 0; i < s->server_list.size(); ++i) { + os << ' ' << s->server_list[i]; + } + } + os << '}'; +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/dynpart_load_balancer.h b/src/brpc/policy/dynpart_load_balancer.h new file mode 100644 index 0000000..4e8833b --- /dev/null +++ b/src/brpc/policy/dynpart_load_balancer.h @@ -0,0 +1,62 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_DYNPART_LOAD_BALANCER_H +#define BRPC_POLICY_DYNPART_LOAD_BALANCER_H + +#include // std::vector +#include // std::map +#include "butil/containers/doubly_buffered_data.h" +#include "brpc/load_balancer.h" + + +namespace brpc { +namespace policy { + +// CAUTION: This is just a quick/hacking impl. for loading balancing between +// partchans in a DynamicPartitionChannel. Any details are subject to change. + +class DynPartLoadBalancer : public LoadBalancer { +public: + bool AddServer(const ServerId& id) override; + bool RemoveServer(const ServerId& id) override; + size_t AddServersInBatch(const std::vector& servers) override; + size_t RemoveServersInBatch(const std::vector& servers) override; + int SelectServer(const SelectIn& in, SelectOut* out) override; + DynPartLoadBalancer* New(const butil::StringPiece&) const override; + void Destroy() override; + void Describe(std::ostream&, const DescribeOptions& options) override; + +private: + struct Servers { + std::vector server_list; + std::map server_map; + }; + static bool Add(Servers& bg, const ServerId& id); + static bool Remove(Servers& bg, const ServerId& id); + static size_t BatchAdd(Servers& bg, const std::vector& servers); + static size_t BatchRemove(Servers& bg, const std::vector& servers); + + butil::DoublyBufferedData _db_servers; +}; + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_DYNPART_LOAD_BALANCER_H diff --git a/src/brpc/policy/esp_authenticator.cpp b/src/brpc/policy/esp_authenticator.cpp new file mode 100644 index 0000000..737ff18 --- /dev/null +++ b/src/brpc/policy/esp_authenticator.cpp @@ -0,0 +1,49 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "butil/logging.h" +#include "butil/memory/singleton_on_pthread_once.h" +#include "brpc/policy/esp_authenticator.h" + + +namespace brpc { +namespace policy { + +const char* MAGICNUM = "\0ESP\x01\x02"; +const int MAGICNUM_LEN = 6; + +int EspAuthenticator::GenerateCredential(std::string* auth_str) const { + auth_str->assign(MAGICNUM, MAGICNUM_LEN); + uint16_t local_port = 0; + auth_str->append((char *)&local_port, sizeof(local_port)); + return 0; +} + +int EspAuthenticator::VerifyCredential( + const std::string& /*auth_str*/, + const butil::EndPoint& /*client_addr*/, + AuthContext* /*out_ctx*/) const { + //nothing to do + return 0; +} + +const Authenticator* global_esp_authenticator() { + return butil::get_leaky_singleton(); +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/esp_authenticator.h b/src/brpc/policy/esp_authenticator.h new file mode 100644 index 0000000..a0f83b9 --- /dev/null +++ b/src/brpc/policy/esp_authenticator.h @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_POLICY_ESP_AUTHENTICATOR_H +#define BRPC_POLICY_ESP_AUTHENTICATOR_H + +#include "brpc/authenticator.h" + + +namespace brpc { +namespace policy { + +class EspAuthenticator: public Authenticator { +public: + int GenerateCredential(std::string* auth_str) const; + + int VerifyCredential(const std::string& auth_str, + const butil::EndPoint& client_addr, + AuthContext* out_ctx) const; +}; + +const Authenticator* global_esp_authenticator(); + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_GIANO_AUTHENTICATOR_H diff --git a/src/brpc/policy/esp_protocol.cpp b/src/brpc/policy/esp_protocol.cpp new file mode 100644 index 0000000..ee8464b --- /dev/null +++ b/src/brpc/policy/esp_protocol.cpp @@ -0,0 +1,160 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // MethodDescriptor +#include // Message +#include + +#include "butil/time.h" +#include "butil/iobuf.h" // butil::IOBuf + +#include "brpc/controller.h" // Controller +#include "brpc/socket.h" // Socket +#include "brpc/server.h" // Server +#include "brpc/span.h" +#include "brpc/details/server_private_accessor.h" +#include "brpc/details/controller_private_accessor.h" +#include "brpc/policy/most_common_message.h" +#include "brpc/details/usercode_backup_pool.h" +#include "brpc/policy/esp_protocol.h" +#include "brpc/esp_message.h" + + +namespace brpc { +namespace policy { + +ParseResult ParseEspMessage( + butil::IOBuf* source, + Socket*, + bool /*read_eof*/, + const void* /*arg*/) { + + EspHead head; + const size_t n = source->copy_to((char *)&head, sizeof(head)); + if (n < sizeof(head)) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + + uint32_t body_len = head.body_len; + if (body_len > FLAGS_max_body_size) { + return MakeParseError(PARSE_ERROR_TOO_BIG_DATA); + } else if (source->length() < sizeof(head) + body_len) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + + policy::MostCommonMessage* msg = policy::MostCommonMessage::Get(); + source->cutn(&msg->meta, sizeof(head)); + source->cutn(&msg->payload, body_len); + return MakeMessage(msg); +} + +void SerializeEspRequest( + butil::IOBuf* request_buf, + Controller* cntl, + const google::protobuf::Message* req_base) { + + if (req_base == NULL) { + return cntl->SetFailed(EREQUEST, "request is NULL"); + } + ControllerPrivateAccessor accessor(cntl); + if (req_base->GetDescriptor() != EspMessage::descriptor()) { + return cntl->SetFailed(EINVAL, "Type of request must be EspMessage"); + } + if (cntl->response() != NULL && + cntl->response()->GetDescriptor() != EspMessage::descriptor()) { + return cntl->SetFailed(EINVAL, "Type of response must be EspMessage"); + } + const EspMessage* req = (const EspMessage*)req_base; + + EspHead head = req->head; + head.body_len = req->body.size(); + request_buf->append(&head, sizeof(head)); + request_buf->append(req->body); +} + +void PackEspRequest(butil::IOBuf* packet_buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor*, + Controller* cntl, + const butil::IOBuf& request, + const Authenticator* auth) { + + ControllerPrivateAccessor accessor(cntl); + if (cntl->connection_type() == CONNECTION_TYPE_SINGLE) { + return cntl->SetFailed( + EINVAL, "esp protocol can't work with CONNECTION_TYPE_SINGLE"); + } + + accessor.get_sending_socket()->set_correlation_id(correlation_id); + if (auto span = accessor.span()) { + span->set_request_size(request.length()); + } + + if (auth != NULL) { + std::string auth_str; + auth->GenerateCredential(&auth_str); + //means first request in this connect, need to special head + packet_buf->append(auth_str); + } + + packet_buf->append(request); +} + +void ProcessEspResponse(InputMessageBase* msg_base) { + const int64_t start_parse_us = butil::cpuwide_time_us(); + DestroyingPtr msg(static_cast(msg_base)); + + // Fetch correlation id that we saved before in `PackEspRequest' + const CallId cid = { static_cast(msg->socket()->correlation_id()) }; + Controller* cntl = NULL; + const int rc = bthread_id_lock(cid, (void**)&cntl); + if (rc != 0) { + LOG_IF(ERROR, rc != EINVAL && rc != EPERM) + << "Fail to lock correlation_id=" << cid << ", " << berror(rc); + return; + } + + ControllerPrivateAccessor accessor(cntl); + if (auto span = accessor.span()) { + span->set_base_real_us(msg->base_real_us()); + span->set_received_us(msg->received_us()); + span->set_response_size(msg->payload.length()); + span->set_start_parse_us(start_parse_us); + } + // MUST be EspMessage (checked in SerializeEspRequest) + EspMessage* response = (EspMessage*)cntl->response(); + const int saved_error = cntl->ErrorCode(); + + if (response != NULL) { + msg->meta.copy_to(&response->head, sizeof(EspHead)); + msg->payload.swap(response->body); + if (response->head.msg != 0) { + cntl->SetFailed(ENOENT, "esp response head msg != 0"); + LOG(WARNING) << "Server " << msg->socket()->remote_side() + << " doesn't contain the right data"; + } + } // else just ignore the response. + + // Unlocks correlation_id inside. Revert controller's + // error code if it version check of `cid' fails + msg.reset(); // optional, just release resource ASAP + accessor.OnResponse(cid, saved_error); +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/esp_protocol.h b/src/brpc/policy/esp_protocol.h new file mode 100644 index 0000000..7fb58c3 --- /dev/null +++ b/src/brpc/policy/esp_protocol.h @@ -0,0 +1,55 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_POLICY_ESP_PROTOCOL_H +#define BRPC_POLICY_ESP_PROTOCOL_H + +#include +#include + +#include "brpc/protocol.h" + + +namespace brpc { +namespace policy { + +ParseResult ParseEspMessage( + butil::IOBuf* source, + Socket* socket, + bool read_eof, + const void *arg); + +void SerializeEspRequest( + butil::IOBuf* request_buf, + Controller* controller, + const google::protobuf::Message* request); + +void PackEspRequest(butil::IOBuf* packet_buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor*, + Controller* controller, + const butil::IOBuf&, + const Authenticator*); + +void ProcessEspResponse(InputMessageBase* msg); + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_ESP_PROTOCOL_H diff --git a/src/brpc/policy/file_naming_service.cpp b/src/brpc/policy/file_naming_service.cpp new file mode 100644 index 0000000..df49673 --- /dev/null +++ b/src/brpc/policy/file_naming_service.cpp @@ -0,0 +1,168 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // getline +#include // std::string +#include // std::set +#include "butil/files/file_watcher.h" // FileWatcher +#include "butil/files/scoped_file.h" // ScopedFILE +#include "bthread/bthread.h" // bthread_usleep +#include "brpc/log.h" +#include "brpc/policy/file_naming_service.h" + + +namespace brpc { +namespace policy { + +bool SplitIntoServerAndTag(const butil::StringPiece& line, + butil::StringPiece* server_addr, + butil::StringPiece* tag) { + size_t i = 0; + for (; i < line.size() && isspace(line[i]); ++i) {} + if (i == line.size() || line[i] == '#') { // blank line or comments + return false; + } + const char* const addr_start = line.data() + i; + const char* tag_start = NULL; + ssize_t tag_size = 0; + for (; i < line.size() && !isspace(line[i]); ++i) {} + if (server_addr) { + server_addr->set(addr_start, line.data() + i - addr_start); + } + if (i != line.size()) { + for (++i; i < line.size() && isspace(line[i]); ++i) {} + if (i < line.size()) { + tag_start = line.data() + i; + tag_size = 1; + // find start of comments. + for (++i; i < line.size() && line[i] != '#'; ++i, ++tag_size) {} + // trim ending blanks + for (; tag_size > 0 && isspace(tag_start[tag_size - 1]); + --tag_size) {} + } + if (tag) { + if (tag_size) { + tag->set(tag_start, tag_size); + } else { + tag->clear(); + } + } + } + return true; +} + +int FileNamingService::GetServers(const char *service_name, + std::vector* servers) { + servers->clear(); + char* line = NULL; + size_t line_len = 0; + ssize_t nr = 0; + // Sort/unique the inserted vector is faster, but may have a different order + // of addresses from the file. To make assertions in tests easier, we use + // set to de-duplicate and keep the order. + std::set presence; + + butil::ScopedFILE fp(fopen(service_name, "r")); + if (!fp) { + PLOG(ERROR) << "Fail to open `" << service_name << "'"; + return errno; + } + while ((nr = getline(&line, &line_len, fp.get())) != -1) { + if (line[nr - 1] == '\n') { // remove ending newline + --nr; + } + butil::StringPiece addr; + butil::StringPiece tag; + if (!SplitIntoServerAndTag(butil::StringPiece(line, nr), + &addr, &tag)) { + continue; + } + const_cast(addr.data())[addr.size()] = '\0'; // safe + butil::EndPoint point; + if (str2endpoint(addr.data(), &point) != 0 && + hostname2endpoint(addr.data(), &point) != 0) { + LOG(ERROR) << "Invalid address=`" << addr << '\''; + continue; + } + ServerNode node; + node.addr = point; + tag.CopyToString(&node.tag); + if (presence.insert(node).second) { + servers->push_back(node); + } else { + RPC_VLOG << "Duplicated server=" << node; + } + } + RPC_VLOG << "Got " << servers->size() + << (servers->size() > 1 ? " servers" : " server"); + free(line); + return 0; +} + +int FileNamingService::RunNamingService(const char* service_name, + NamingServiceActions* actions) { + std::vector servers; + butil::FileWatcher fw; + if (fw.init(service_name) < 0) { + LOG(ERROR) << "Fail to init FileWatcher on `" << service_name << "'"; + return -1; + } + for (;;) { + const int rc = GetServers(service_name, &servers); + if (rc != 0) { + return rc; + } + actions->ResetServers(servers); + + for (;;) { + butil::FileWatcher::Change change = fw.check_and_consume(); + if (change > 0) { + break; + } + if (change < 0) { + LOG(ERROR) << "`" << service_name << "' was deleted"; + } + if (bthread_usleep(100000L/*100ms*/) < 0) { + if (errno == ESTOP) { + return 0; + } + PLOG(ERROR) << "Fail to sleep"; + return -1; + } + } + } + CHECK(false); + return -1; +} + +void FileNamingService::Describe(std::ostream& os, + const DescribeOptions&) const { + os << "file"; + return; +} + +NamingService* FileNamingService::New() const { + return new FileNamingService; +} + +void FileNamingService::Destroy() { + delete this; +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/file_naming_service.h b/src/brpc/policy/file_naming_service.h new file mode 100644 index 0000000..c9d6cbb --- /dev/null +++ b/src/brpc/policy/file_naming_service.h @@ -0,0 +1,48 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_FILE_NAMING_SERVICE +#define BRPC_POLICY_FILE_NAMING_SERVICE + +#include "brpc/naming_service.h" + + +namespace brpc { +namespace policy { + +class FileNamingService : public NamingService { +friend class ConsulNamingService; +private: + int RunNamingService(const char* service_name, + NamingServiceActions* actions) override; + + int GetServers(const char *service_name, + std::vector* servers); + + void Describe(std::ostream& os, const DescribeOptions&) const override; + + NamingService* New() const override; + + void Destroy() override; +}; + +} // namespace policy +} // namespace brpc + + +#endif //BRPC_POLICY_FILE_NAMING_SERVICE diff --git a/src/brpc/policy/giano_authenticator.cpp b/src/brpc/policy/giano_authenticator.cpp new file mode 100644 index 0000000..d74f106 --- /dev/null +++ b/src/brpc/policy/giano_authenticator.cpp @@ -0,0 +1,90 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +#ifdef BAIDU_INTERNAL + + +#include "butil/logging.h" +#include "brpc/policy/giano_authenticator.h" + +namespace brpc { +namespace policy { + +GianoAuthenticator::GianoAuthenticator(const baas::CredentialGenerator* gen, + const baas::CredentialVerifier* ver) { + if (gen) { + _generator = new(std::nothrow) baas::CredentialGenerator(*gen); + CHECK(_generator); + } else { + _generator = NULL; + } + if (ver) { + _verifier = new(std::nothrow) baas::CredentialVerifier(*ver); + CHECK(_verifier); + } else { + _verifier = NULL; + } +} + +GianoAuthenticator::~GianoAuthenticator() { + delete _generator; + _generator = NULL; + + delete _verifier; + _verifier = NULL; +} + +int GianoAuthenticator::GenerateCredential(std::string* auth_str) const { + if (NULL == _generator) { + LOG(FATAL) << "CredentialGenerator is NULL"; + return -1; + } + + return (baas::sdk::BAAS_OK == + _generator->GenerateCredential(auth_str) ? 0 : -1); +} + +int GianoAuthenticator::VerifyCredential( + const std::string& auth_str, + const butil::EndPoint& client_addr, + AuthContext* out_ctx) const { + if (NULL == _verifier) { + LOG(FATAL) << "CredentialVerifier is NULL"; + return -1; + } + + baas::CredentialContext ctx; + int rc = _verifier->Verify( + auth_str, endpoint2str(client_addr).c_str(), &ctx); + if (rc != baas::sdk::BAAS_OK) { + LOG(WARNING) << "Giano fails to verify credentical, " + << baas::sdk::GetReturnCodeMessage(rc); + return -1; + } + if (out_ctx != NULL) { + out_ctx->set_user(ctx.user()); + out_ctx->set_group(ctx.group()); + out_ctx->set_roles(ctx.roles()); + out_ctx->set_starter(ctx.starter()); + out_ctx->set_is_service(ctx.IsService()); + } + return 0; +} + +} // namespace policy +} // namespace brpc +#endif // BAIDU_INTERNAL diff --git a/src/brpc/policy/giano_authenticator.h b/src/brpc/policy/giano_authenticator.h new file mode 100644 index 0000000..d2b9acb --- /dev/null +++ b/src/brpc/policy/giano_authenticator.h @@ -0,0 +1,56 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +#ifdef BAIDU_INTERNAL + + +#ifndef BRPC_POLICY_GIANO_AUTHENTICATOR_H +#define BRPC_POLICY_GIANO_AUTHENTICATOR_H + +#include // Giano stuff +#include "brpc/authenticator.h" + +namespace brpc { +namespace policy { + +class GianoAuthenticator: public Authenticator { +public: + // Either `gen' or `ver' can be NULL (but not at the same time), + // in which case it can only verify/generate credential data + explicit GianoAuthenticator(const baas::CredentialGenerator* gen, + const baas::CredentialVerifier* ver); + + ~GianoAuthenticator(); + + int GenerateCredential(std::string* auth_str) const; + + int VerifyCredential(const std::string& auth_str, + const butil::EndPoint& client_addr, + AuthContext* out_ctx) const; + +private: + baas::CredentialGenerator* _generator; + baas::CredentialVerifier* _verifier; +}; + + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_GIANO_AUTHENTICATOR_H +#endif // BAIDU_INTERNAL diff --git a/src/brpc/policy/gzip_compress.cpp b/src/brpc/policy/gzip_compress.cpp new file mode 100644 index 0000000..e8c77a5 --- /dev/null +++ b/src/brpc/policy/gzip_compress.cpp @@ -0,0 +1,198 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // GzipXXXStream +#include +#include "butil/logging.h" +#include "brpc/policy/gzip_compress.h" +#include "brpc/protocol.h" +#include "brpc/compress.h" + +namespace brpc { +namespace policy { + +const char* Format2CStr(google::protobuf::io::GzipOutputStream::Format format) { + switch (format) { + case google::protobuf::io::GzipOutputStream::GZIP: + return "gzip"; + case google::protobuf::io::GzipOutputStream::ZLIB: + return "zlib"; + default: + return "unknown"; + } +} + +const char* Format2CStr(google::protobuf::io::GzipInputStream::Format format) { + switch (format) { + case google::protobuf::io::GzipInputStream::GZIP: + return "gzip"; + case google::protobuf::io::GzipInputStream::ZLIB: + return "zlib"; + default: + return "unknown"; + } +} + +static bool Compress(const google::protobuf::Message& msg, butil::IOBuf* buf, + google::protobuf::io::GzipOutputStream::Format format) { + butil::IOBufAsZeroCopyOutputStream wrapper(buf); + GzipCompressOptions options; + options.format = format; + google::protobuf::io::GzipOutputStream gzip(&wrapper, options); + bool ok; + if (msg.GetDescriptor() == Serializer::descriptor()) { + ok = ((const Serializer&)msg).SerializeTo(&gzip); + } else { + ok = msg.SerializeToZeroCopyStream(&gzip); + } + if (!ok) { + LOG(WARNING) << "Fail to serialize input message=" + << msg.GetDescriptor()->full_name() + << ", format=" << Format2CStr(format) << " : " + << (NULL == gzip.ZlibErrorMessage() ? "" : gzip.ZlibErrorMessage()); + } + return ok && gzip.Close(); +} + +static bool Decompress(const butil::IOBuf& data, google::protobuf::Message* msg, + google::protobuf::io::GzipInputStream::Format format) { + butil::IOBufAsZeroCopyInputStream wrapper(data); + google::protobuf::io::GzipInputStream gzip(&wrapper, format); + bool ok; + if (msg->GetDescriptor() == Deserializer::descriptor()) { + ok = ((Deserializer*)msg)->DeserializeFrom(&gzip); + } else { + ok = msg->ParseFromZeroCopyStream(&gzip); + } + if (!ok) { + LOG(WARNING) << "Fail to deserialize input message=" + << msg->GetDescriptor()->full_name() + << ", format=" << Format2CStr(format) << " : " + << (NULL == gzip.ZlibErrorMessage() ? "" : gzip.ZlibErrorMessage()); + } + return ok; +} + +bool GzipCompress(const google::protobuf::Message& msg, butil::IOBuf* buf) { + return Compress(msg, buf, google::protobuf::io::GzipOutputStream::GZIP); +} + +bool GzipDecompress(const butil::IOBuf& data, google::protobuf::Message* msg) { + return Decompress(data, msg, google::protobuf::io::GzipInputStream::GZIP); +} + +bool GzipCompress(const butil::IOBuf& msg, butil::IOBuf* buf, + const GzipCompressOptions* options_in) { + butil::IOBufAsZeroCopyOutputStream wrapper(buf); + GzipCompressOptions gzip_opt; + if (options_in) { + gzip_opt = *options_in; + } + google::protobuf::io::GzipOutputStream out(&wrapper, gzip_opt); + butil::IOBufAsZeroCopyInputStream in(msg); + const void* data_in = NULL; + int size_in = 0; + void* data_out = NULL; + int size_out = 0; + while (1) { + if (size_out == 0 && !out.Next(&data_out, &size_out)) { + break; + } + if (size_in == 0 && !in.Next(&data_in, &size_in)) { + break; + } + const int size_cp = std::min(size_in, size_out); + memcpy(data_out, data_in, size_cp); + size_in -= size_cp; + data_in = (char*)data_in + size_cp; + size_out -= size_cp; + data_out = (char*)data_out + size_cp; + } + if (size_in != 0 || (size_t)in.ByteCount() != msg.size()) { + // If any stage is not fully consumed, something went wrong. + LOG(WARNING) << "Fail to compress, format=" << Format2CStr(gzip_opt.format) + << " : " << out.ZlibErrorMessage(); + return false; + } + if (size_out != 0) { + out.BackUp(size_out); + } + return out.Close(); +} + +inline bool GzipDecompressBase( + const butil::IOBuf& data, butil::IOBuf* msg, + google::protobuf::io::GzipInputStream::Format format) { + butil::IOBufAsZeroCopyInputStream wrapper(data); + google::protobuf::io::GzipInputStream in(&wrapper, format); + butil::IOBufAsZeroCopyOutputStream out(msg); + const void* data_in = NULL; + int size_in = 0; + void* data_out = NULL; + int size_out = 0; + while (1) { + if (size_out == 0 && !out.Next(&data_out, &size_out)) { + break; + } + if (size_in == 0 && !in.Next(&data_in, &size_in)) { + break; + } + const int size_cp = std::min(size_in, size_out); + memcpy(data_out, data_in, size_cp); + size_in -= size_cp; + data_in = (char*)data_in + size_cp; + size_out -= size_cp; + data_out = (char*)data_out + size_cp; + } + if (size_in != 0 || + (size_t)wrapper.ByteCount() != data.size() || + in.Next(&data_in, &size_in)) { + // If any stage is not fully consumed, something went wrong. + // Here we call in.Next addtitionally to make sure that the gzip + // "blackbox" does not have buffer left. + LOG(WARNING) << "Fail to decompress, format=" << Format2CStr(format) + << " : " << in.ZlibErrorMessage(); + return false; + } + if (size_out != 0) { + out.BackUp(size_out); + } + return true; +} + +bool ZlibCompress(const google::protobuf::Message& msg, butil::IOBuf* buf) { + return Compress(msg, buf, google::protobuf::io::GzipOutputStream::ZLIB); +} + +bool ZlibDecompress(const butil::IOBuf& data, + google::protobuf::Message* msg) { + return Decompress(data, msg, google::protobuf::io::GzipInputStream::ZLIB); +} + +bool GzipDecompress(const butil::IOBuf& data, butil::IOBuf* msg) { + return GzipDecompressBase( + data, msg, google::protobuf::io::GzipInputStream::GZIP); +} + +bool ZlibDecompress(const butil::IOBuf& data, butil::IOBuf* msg) { + return GzipDecompressBase( + data, msg, google::protobuf::io::GzipInputStream::ZLIB); +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/gzip_compress.h b/src/brpc/policy/gzip_compress.h new file mode 100644 index 0000000..426aaf5 --- /dev/null +++ b/src/brpc/policy/gzip_compress.h @@ -0,0 +1,52 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_GZIP_COMPRESS_H +#define BRPC_POLICY_GZIP_COMPRESS_H + +#include // Message +#include +#include "butil/iobuf.h" // butil::IOBuf + + +namespace brpc { +namespace policy { + +typedef google::protobuf::io::GzipOutputStream::Options GzipCompressOptions; + +// Compress serialized `msg' into `buf'. +bool GzipCompress(const google::protobuf::Message& msg, butil::IOBuf* buf); +bool ZlibCompress(const google::protobuf::Message& msg, butil::IOBuf* buf); + +// Parse `msg' from decompressed `buf'. +bool GzipDecompress(const butil::IOBuf& buf, google::protobuf::Message* msg); +bool ZlibDecompress(const butil::IOBuf& buf, google::protobuf::Message* msg); + +// Put compressed `in' into `out'. +bool GzipCompress(const butil::IOBuf& in, butil::IOBuf* out, + const GzipCompressOptions*); + +// Put decompressed `in' into `out'. +bool GzipDecompress(const butil::IOBuf& in, butil::IOBuf* out); +bool ZlibDecompress(const butil::IOBuf& in, butil::IOBuf* out); + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_GZIP_COMPRESS_H diff --git a/src/brpc/policy/hasher.cpp b/src/brpc/policy/hasher.cpp new file mode 100644 index 0000000..ba33eb5 --- /dev/null +++ b/src/brpc/policy/hasher.cpp @@ -0,0 +1,171 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include "butil/third_party/murmurhash3/murmurhash3.h" +#include "brpc/policy/hasher.h" + + +namespace brpc { +namespace policy { + +void MD5HashSignature(const void* key, size_t len, unsigned char* results) { + MD5_CTX my_md5; + MD5_Init(&my_md5); + MD5_Update(&my_md5, key, len); + MD5_Final(results, &my_md5); +} + +uint32_t MD5Hash32(const void* key, size_t len) { + unsigned char results[MD5_DIGEST_LENGTH]; + MD5HashSignature(key, len, results); + return ((uint32_t) (results[3] & 0xFF) << 24) + | ((uint32_t) (results[2] & 0xFF) << 16) + | ((uint32_t) (results[1] & 0xFF) << 8) + | (results[0] & 0xFF); +} + +uint32_t MD5Hash32V(const butil::StringPiece* keys, size_t num_keys) { + MD5_CTX ctx; + MD5_Init(&ctx); + for (size_t i = 0; i < num_keys; ++i) { + MD5_Update(&ctx, (const unsigned char *)keys[i].data(), + keys[i].size()); + } + unsigned char results[MD5_DIGEST_LENGTH]; + MD5_Final(results, &ctx); + return ((uint32_t) (results[3] & 0xFF) << 24) + | ((uint32_t) (results[2] & 0xFF) << 16) + | ((uint32_t) (results[1] & 0xFF) << 8) + | (results[0] & 0xFF); +} + +uint32_t MurmurHash32(const void* key, size_t len) { + uint32_t hash; + butil::MurmurHash3_x86_32(key, (int)len, 0, &hash); + return hash; +} + +uint32_t MurmurHash32V(const butil::StringPiece* keys, size_t num_keys) { + butil::MurmurHash3_x86_32_Context ctx; + butil::MurmurHash3_x86_32_Init(&ctx, 0); + for (size_t i = 0; i < num_keys; ++i) { + butil::MurmurHash3_x86_32_Update(&ctx, keys[i].data(), keys[i].size()); + } + uint32_t hash; + butil::MurmurHash3_x86_32_Final(&hash, &ctx); + return hash; +} + +/* The crc32 functions and data was originally written by Spencer + * Garrett and was gleaned from the PostgreSQL source + * tree via the files contrib/ltree/crc32.[ch] and from FreeBSD at + * src/usr.bin/cksum/crc32.c. + */ +static const uint32_t crc32tab[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, + 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, + 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, + 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, + 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, + 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, + 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, + 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, + 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, + 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, + 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, + 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, + 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, + 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, + 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, + 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, +}; + +uint32_t CRCHash32(const void* key, size_t len) { + uint32_t crc = UINT_MAX; + for (size_t x = 0; x < len; x++) { + crc = (crc >> 8) ^ crc32tab[(crc ^ (uint32_t)((const char*)key)[x]) & 0xff]; + } + return ((~crc) >> 16) & 0x7fff; +} + +const char *GetHashName(HashFunc hasher) { + if (hasher == MurmurHash32) { + return "murmurhash3"; + } + if (hasher == MD5Hash32) { + return "md5"; + } + if (hasher == CRCHash32) { + return "crc32"; + } + + return "user_defined"; +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/hasher.h b/src/brpc/policy/hasher.h new file mode 100644 index 0000000..cb660ca --- /dev/null +++ b/src/brpc/policy/hasher.h @@ -0,0 +1,43 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_HASHER_H +#define BRPC_HASHER_H + +#include +#include +#include "butil/strings/string_piece.h" + + +namespace brpc { +namespace policy { + +using HashFunc = uint32_t(*)(const void*, size_t); + +void MD5HashSignature(const void* key, size_t len, unsigned char* results); +uint32_t MD5Hash32(const void* key, size_t len); +uint32_t MD5Hash32V(const butil::StringPiece* keys, size_t num_keys); + +uint32_t MurmurHash32(const void* key, size_t len); +uint32_t MurmurHash32V(const butil::StringPiece* keys, size_t num_keys); + +} // namespace policy +} // namespace brpc + + +#endif //BRPC_HASHER_H diff --git a/src/brpc/policy/http2_rpc_protocol.cpp b/src/brpc/policy/http2_rpc_protocol.cpp new file mode 100644 index 0000000..b2de496 --- /dev/null +++ b/src/brpc/policy/http2_rpc_protocol.cpp @@ -0,0 +1,1851 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/http2_rpc_protocol.h" +#include "brpc/details/controller_private_accessor.h" +#include "brpc/server.h" +#include "butil/base64.h" +#include "brpc/log.h" + +namespace brpc { + +DECLARE_bool(http_verbose); +DECLARE_int32(http_verbose_max_body_length); +DECLARE_int32(health_check_interval); +DECLARE_bool(usercode_in_pthread); + +namespace policy { + +DEFINE_int32(h2_client_header_table_size, + H2Settings::DEFAULT_HEADER_TABLE_SIZE, + "maximum size of compression tables for decoding headers"); +DEFINE_int32(h2_client_stream_window_size, 256 * 1024, + "Initial window size for stream-level flow control"); +DEFINE_int32(h2_client_connection_window_size, 1024 * 1024, + "Initial window size for connection-level flow control"); +DEFINE_int32(h2_client_max_frame_size, + H2Settings::DEFAULT_MAX_FRAME_SIZE, + "Size of the largest frame payload that client is willing to receive"); + +DEFINE_bool(h2_hpack_encode_name, false, + "Encode name in HTTP2 headers with huffman encoding"); +DEFINE_bool(h2_hpack_encode_value, false, + "Encode value in HTTP2 headers with huffman encoding"); + +static bool CheckStreamWindowSize(const char*, int32_t val) { + return val >= 0; +} +BRPC_VALIDATE_GFLAG(h2_client_stream_window_size, CheckStreamWindowSize); + +static bool CheckConnWindowSize(const char*, int32_t val) { + return val >= (int32_t)H2Settings::DEFAULT_INITIAL_WINDOW_SIZE; +} +BRPC_VALIDATE_GFLAG(h2_client_connection_window_size, CheckConnWindowSize); + +const char* H2StreamState2Str(H2StreamState s) { + switch (s) { + case H2_STREAM_IDLE: return "idle"; + case H2_STREAM_RESERVED_LOCAL: return "reserved(local)"; + case H2_STREAM_RESERVED_REMOTE: return "reserved(remote)"; + case H2_STREAM_OPEN: return "open"; + case H2_STREAM_HALF_CLOSED_LOCAL: return "half-closed(local)"; + case H2_STREAM_HALF_CLOSED_REMOTE: return "half-closed(remote)"; + case H2_STREAM_CLOSED: return "closed"; + } + return "unknown(H2StreamState)"; +} + +static const char* H2ConnectionState2Str(H2ConnectionState s) { + switch (s) { + case H2_CONNECTION_UNINITIALIZED: return "UNINITIALIZED"; + case H2_CONNECTION_READY: return "READY"; + case H2_CONNECTION_GOAWAY: return "GOAWAY"; + } + return "UNKNOWN(H2ConnectionState)"; +} + +// A series of utilities to load numbers from http2 streams. +inline uint8_t LoadUint8(butil::IOBufBytesIterator& it) { + uint8_t v = *it; + ++it; + return v; +} +inline uint16_t LoadUint16(butil::IOBufBytesIterator& it) { + uint16_t v = *it; ++it; + v = ((v << 8) | *it); ++it; + return v; +} +inline uint32_t LoadUint32(butil::IOBufBytesIterator& it) { + uint32_t v = *it; ++it; + v = ((v << 8) | *it); ++it; + v = ((v << 8) | *it); ++it; + v = ((v << 8) | *it); ++it; + return v; +} +inline void SaveUint16(void* out, uint16_t v) { + uint8_t* p = (uint8_t*)out; + p[0] = (v >> 8) & 0xFF; + p[1] = v & 0xFF; +} +inline void SaveUint32(void* out, uint32_t v) { + uint8_t* p = (uint8_t*)out; + p[0] = (v >> 24) & 0xFF; + p[1] = (v >> 16) & 0xFF; + p[2] = (v >> 8) & 0xFF; + p[3] = v & 0xFF; +} + +const uint8_t H2_FLAGS_END_STREAM = 0x1; +const uint8_t H2_FLAGS_ACK = 0x1; +const uint8_t H2_FLAGS_END_HEADERS = 0x4; +const uint8_t H2_FLAGS_PADDED = 0x8; +const uint8_t H2_FLAGS_PRIORITY = 0x20; + +#define H2_CONNECTION_PREFACE_PREFIX "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" +const size_t H2_CONNECTION_PREFACE_PREFIX_SIZE = 24; + +void SerializeFrameHead(void* out_buf, + uint32_t payload_size, H2FrameType type, + uint8_t flags, uint32_t stream_id) { + uint8_t* p = (uint8_t*)out_buf; + *p++ = (payload_size >> 16) & 0xFF; + *p++ = (payload_size >> 8) & 0xFF; + *p++ = payload_size & 0xFF; + *p++ = (uint8_t)type; + *p++ = flags; + *p++ = (stream_id >> 24) & 0xFF; + *p++ = (stream_id >> 16) & 0xFF; + *p++ = (stream_id >> 8) & 0xFF; + *p++ = stream_id & 0xFF; +} + +inline void SerializeFrameHead(void* out_buf, const H2FrameHead& h) { + return SerializeFrameHead(out_buf, h.payload_size, h.type, + h.flags, h.stream_id); +} + +static int WriteAck(Socket* s, const void* data, size_t n) { + butil::IOBuf sendbuf; + sendbuf.append(data, n); + Socket::WriteOptions wopt; + wopt.ignore_eovercrowded = true; + return s->Write(&sendbuf, &wopt); +} + +// [ https://tools.ietf.org/html/rfc7540#section-6.5.1 ] + +enum H2SettingsIdentifier { + H2_SETTINGS_HEADER_TABLE_SIZE = 0x1, + H2_SETTINGS_ENABLE_PUSH = 0x2, + H2_SETTINGS_MAX_CONCURRENT_STREAMS = 0x3, + H2_SETTINGS_STREAM_WINDOW_SIZE = 0x4, + H2_SETTINGS_MAX_FRAME_SIZE = 0x5, + H2_SETTINGS_MAX_HEADER_LIST_SIZE = 0x6 +}; + +// Parse from n bytes from the iterator. +// Returns true on success. +bool ParseH2Settings(H2Settings* out, butil::IOBufBytesIterator& it, size_t n) { + const uint32_t npairs = n / 6; + if (npairs * 6 != n) { + LOG(ERROR) << "Invalid payload_size=" << n; + return false; + } + for (uint32_t i = 0; i < npairs; ++i) { + uint16_t id = LoadUint16(it); + uint32_t value = LoadUint32(it); + switch (static_cast(id)) { + case H2_SETTINGS_HEADER_TABLE_SIZE: + out->header_table_size = value; + break; + case H2_SETTINGS_ENABLE_PUSH: + if (value > 1) { + LOG(ERROR) << "Invalid value=" << value << " for ENABLE_PUSH"; + return false; + } + out->enable_push = value; + break; + case H2_SETTINGS_MAX_CONCURRENT_STREAMS: + out->max_concurrent_streams = value; + break; + case H2_SETTINGS_STREAM_WINDOW_SIZE: + if (value > H2Settings::MAX_WINDOW_SIZE) { + LOG(ERROR) << "Invalid stream_window_size=" << value; + return false; + } + out->stream_window_size = value; + break; + case H2_SETTINGS_MAX_FRAME_SIZE: + if (value > H2Settings::MAX_OF_MAX_FRAME_SIZE || + value < H2Settings::DEFAULT_MAX_FRAME_SIZE) { + LOG(ERROR) << "Invalid max_frame_size=" << value; + return false; + } + out->max_frame_size = value; + break; + case H2_SETTINGS_MAX_HEADER_LIST_SIZE: + out->max_header_list_size = value; + break; + default: + // An endpoint that receives a SETTINGS frame with any unknown or + // unsupported identifier MUST ignore that setting (section 6.5.2) + break; + } + } + return true; +} + +// Maximum value that may be returned by SerializeH2Settings +static const size_t H2_SETTINGS_MAX_BYTE_SIZE = 36; + +// Serialize to `out' which is at least ByteSize() bytes long. +// Returns bytes written. +size_t SerializeH2Settings(const H2Settings& in, void* out) { + uint8_t* p = (uint8_t*)out; + if (in.header_table_size != H2Settings::DEFAULT_HEADER_TABLE_SIZE) { + SaveUint16(p, H2_SETTINGS_HEADER_TABLE_SIZE); + SaveUint32(p + 2, in.header_table_size); + p += 6; + } + if (in.enable_push != H2Settings::DEFAULT_ENABLE_PUSH) { + SaveUint16(p, H2_SETTINGS_ENABLE_PUSH); + SaveUint32(p + 2, in.enable_push); + p += 6; + } + if (in.max_concurrent_streams != std::numeric_limits::max()) { + SaveUint16(p, H2_SETTINGS_MAX_CONCURRENT_STREAMS); + SaveUint32(p + 2, in.max_concurrent_streams); + p += 6; + } + if (in.stream_window_size != H2Settings::DEFAULT_INITIAL_WINDOW_SIZE) { + SaveUint16(p, H2_SETTINGS_STREAM_WINDOW_SIZE); + SaveUint32(p + 2, in.stream_window_size); + p += 6; + } + if (in.max_frame_size != H2Settings::DEFAULT_MAX_FRAME_SIZE) { + SaveUint16(p, H2_SETTINGS_MAX_FRAME_SIZE); + SaveUint32(p + 2, in.max_frame_size); + p += 6; + } + if (in.max_header_list_size != std::numeric_limits::max()) { + SaveUint16(p, H2_SETTINGS_MAX_HEADER_LIST_SIZE); + SaveUint32(p + 2, in.max_header_list_size); + p += 6; + } + return static_cast(p - (uint8_t*)out); +} + +static size_t SerializeH2SettingsFrameAndWU(const H2Settings& in, void* out) { + uint8_t* p = (uint8_t*)out; + size_t nb = SerializeH2Settings(in, p + FRAME_HEAD_SIZE); + SerializeFrameHead(p, nb, H2_FRAME_SETTINGS, 0, 0); + p += FRAME_HEAD_SIZE + nb; + if (in.connection_window_size > H2Settings::DEFAULT_INITIAL_WINDOW_SIZE) { + SerializeFrameHead(p, 4, H2_FRAME_WINDOW_UPDATE, 0, 0); + SaveUint32(p + FRAME_HEAD_SIZE, + in.connection_window_size - H2Settings::DEFAULT_INITIAL_WINDOW_SIZE); + p += FRAME_HEAD_SIZE + 4; + } + return static_cast(p - (uint8_t*)out); +} + +inline bool AddWindowSize(butil::atomic* window_size, int64_t diff) { + // A sender MUST NOT allow a flow-control window to exceed 2^31 - 1. + // If a sender receives a WINDOW_UPDATE that causes a flow-control window + // to exceed this maximum, it MUST terminate either the stream or the connection, + // as appropriate. + int64_t before_add = window_size->fetch_add(diff, butil::memory_order_relaxed); + if ((((before_add | diff) >> 31) & 1) == 0) { + // two positive int64_t, check positive overflow + if ((before_add + diff) & (1 << 31)) { + return false; + } + } + if ((((before_add & diff) >> 31) & 1) == 1) { + // two negative int64_t, check negaitive overflow + if (((before_add + diff) & (1 << 31)) == 0) { + return false; + } + } + // window_size being negative is OK + return true; +} + +inline bool MinusWindowSize(butil::atomic* window_size, int64_t size) { + if (window_size->load(butil::memory_order_relaxed) < size) { + // false negative is OK. + return false; + } + int64_t before_sub = window_size->fetch_sub(size, butil::memory_order_relaxed); + if (before_sub < size) { + window_size->fetch_add(size, butil::memory_order_relaxed); + return false; + } + return true; +} + +static H2Context::FrameHandler s_frame_handlers[H2_FRAME_TYPE_MAX + 1]; +static pthread_once_t s_frame_handlers_init_once = PTHREAD_ONCE_INIT; +void InitFrameHandlers() { + s_frame_handlers[H2_FRAME_DATA] = &H2Context::OnData; + s_frame_handlers[H2_FRAME_HEADERS] = &H2Context::OnHeaders; + s_frame_handlers[H2_FRAME_PRIORITY] = &H2Context::OnPriority; + s_frame_handlers[H2_FRAME_RST_STREAM] = &H2Context::OnResetStream; + s_frame_handlers[H2_FRAME_SETTINGS] = &H2Context::OnSettings; + s_frame_handlers[H2_FRAME_PUSH_PROMISE] = &H2Context::OnPushPromise; + s_frame_handlers[H2_FRAME_PING] = &H2Context::OnPing; + s_frame_handlers[H2_FRAME_GOAWAY] = &H2Context::OnGoAway; + s_frame_handlers[H2_FRAME_WINDOW_UPDATE] = &H2Context::OnWindowUpdate; + s_frame_handlers[H2_FRAME_CONTINUATION] = &H2Context::OnContinuation; +} +inline H2Context::FrameHandler FindFrameHandler(H2FrameType type) { + pthread_once(&s_frame_handlers_init_once, InitFrameHandlers); + if (type < 0 || type > H2_FRAME_TYPE_MAX) { + return NULL; + } + return s_frame_handlers[type]; +} + +H2Context::H2Context(Socket* socket, const Server* server) + : _socket(socket) + // Maximize the window size to make sending big request possible before + // receving the remote settings. + , _remote_window_left(H2Settings::MAX_WINDOW_SIZE) + , _conn_state(H2_CONNECTION_UNINITIALIZED) + , _last_received_stream_id(-1) + , _last_sent_stream_id(1) + , _goaway_stream_id(-1) + , _remote_settings_received(false) + , _deferred_window_update(0) { + // Stop printing the field which is useless for remote settings. + _remote_settings.connection_window_size = 0; + // Maximize the window size to make sending big request possible before + // receving the remote settings. + _remote_settings.stream_window_size = H2Settings::MAX_WINDOW_SIZE; + if (server) { + _unack_local_settings = server->options().h2_settings; + } else { + _unack_local_settings.header_table_size = FLAGS_h2_client_header_table_size; + _unack_local_settings.stream_window_size = FLAGS_h2_client_stream_window_size; + _unack_local_settings.max_frame_size = FLAGS_h2_client_max_frame_size; + _unack_local_settings.connection_window_size = FLAGS_h2_client_connection_window_size; + } +#if defined(UNIT_TEST) + // In ut, we hope _last_sent_stream_id run out quickly to test the correctness + // of creating new h2 socket. This value is 10,000 less than 0x7FFFFFFF. + _last_sent_stream_id = 0x7fffd8ef; +#endif +} + +H2Context::~H2Context() { + for (StreamMap::iterator it = _pending_streams.begin(); + it != _pending_streams.end(); ++it) { + delete it->second; + } + _pending_streams.clear(); +} + +int H2Context::Init() { + if (_pending_streams.init(64, 70) != 0) { + LOG(WARNING) << "Fail to init _pending_streams"; + } + if (_hpacker.Init(_unack_local_settings.header_table_size) != 0) { + LOG(WARNING) << "Fail to init _hpacker"; + } + return 0; +} + +H2StreamContext* H2Context::RemoveStreamAndDeferWU(int stream_id) { + H2StreamContext* sctx = NULL; + { + std::unique_lock mu(_stream_mutex); + if (!_pending_streams.erase(stream_id, &sctx)) { + return NULL; + } + } + // The remote stream will not send any more data, sending back the + // stream-level WINDOW_UPDATE is pointless, just move the value into + // the connection. + DeferWindowUpdate(sctx->ReleaseDeferredWindowUpdate()); + return sctx; +} + +void H2Context::RemoveGoAwayStreams( + int goaway_stream_id, std::vector* out_streams) { + out_streams->clear(); + if (goaway_stream_id == 0) { // quick path + StreamMap tmp; + { + std::unique_lock mu(_stream_mutex); + _goaway_stream_id = goaway_stream_id; + _pending_streams.swap(tmp); + } + for (StreamMap::const_iterator it = tmp.begin(); it != tmp.end(); ++it) { + out_streams->push_back(it->second); + } + } else { + std::unique_lock mu(_stream_mutex); + _goaway_stream_id = goaway_stream_id; + for (StreamMap::const_iterator it = _pending_streams.begin(); + it != _pending_streams.end(); ++it) { + if (it->first > goaway_stream_id) { + out_streams->push_back(it->second); + } + } + for (size_t i = 0; i < out_streams->size(); ++i) { + _pending_streams.erase((*out_streams)[i]->stream_id()); + } + } +} + +H2StreamContext* H2Context::FindStream(int stream_id) { + std::unique_lock mu(_stream_mutex); + H2StreamContext** psctx = _pending_streams.seek(stream_id); + if (psctx) { + return *psctx; + } + return NULL; +} + +int H2Context::TryToInsertStream(int stream_id, H2StreamContext* ctx) { + std::unique_lock mu(_stream_mutex); + if (_goaway_stream_id >= 0 && stream_id > _goaway_stream_id) { + return 1; + } + H2StreamContext*& sctx = _pending_streams[stream_id]; + if (sctx == NULL) { + sctx = ctx; + return 0; + } + return -1; +} + +ParseResult H2Context::ConsumeFrameHead( + butil::IOBufBytesIterator& it, H2FrameHead* frame_head) { + uint8_t length_buf[3]; + size_t n = it.copy_and_forward(length_buf, sizeof(length_buf)); + if (n < 3) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + const uint32_t length = ((uint32_t)length_buf[0] << 16) + | ((uint32_t)length_buf[1] << 8) | length_buf[2]; + if (length > _local_settings.max_frame_size) { + LOG(ERROR) << "Too large frame length=" << length << " max=" + << _local_settings.max_frame_size; + return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG); + } + if (it.bytes_left() < FRAME_HEAD_SIZE - 3 + length) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + frame_head->payload_size = length; + frame_head->type = (H2FrameType)LoadUint8(it); + frame_head->flags = LoadUint8(it); + const uint32_t stream_id = LoadUint32(it); + if (stream_id & 0x80000000) { + LOG(ERROR) << "Invalid stream_id=" << stream_id; + return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG); + } + frame_head->stream_id = static_cast(stream_id); + return MakeMessage(NULL); +} + +ParseResult H2Context::Consume( + butil::IOBufBytesIterator& it, Socket* socket) { + if (_conn_state == H2_CONNECTION_UNINITIALIZED) { + if (is_server_side()) { + // Wait for the client connection preface prefix + char preface[H2_CONNECTION_PREFACE_PREFIX_SIZE]; + const size_t n = it.copy_and_forward(preface, sizeof(preface)); + if (memcmp(preface, H2_CONNECTION_PREFACE_PREFIX, n) != 0) { + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + if (n < sizeof(preface)) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + _conn_state = H2_CONNECTION_READY; + + char settingsbuf[FRAME_HEAD_SIZE + H2_SETTINGS_MAX_BYTE_SIZE + + FRAME_HEAD_SIZE + 4/*for WU*/]; + const size_t nb = SerializeH2SettingsFrameAndWU(_unack_local_settings, settingsbuf); + if (WriteAck(socket, settingsbuf, nb) != 0) { + LOG(WARNING) << "Fail to respond http2-client with settings to " << *socket; + return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG); + } + } else { + _conn_state = H2_CONNECTION_READY; + } + return MakeMessage(NULL); + } else if (_conn_state == H2_CONNECTION_READY) { + H2FrameHead frame_head; + ParseResult res = ConsumeFrameHead(it, &frame_head); + if (!res.is_ok()) { + return res; + } + H2Context::FrameHandler handler = FindFrameHandler(frame_head.type); + if (handler == NULL) { + LOG(ERROR) << "Invalid frame type=" << (int)frame_head.type; + return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG); + } + H2ParseResult h2_res = (this->*handler)(it, frame_head); + if (h2_res.is_ok()) { + return MakeMessage(h2_res.message()); + } + if (h2_res.stream_id()) { // send RST_STREAM + char rstbuf[FRAME_HEAD_SIZE + 4]; + SerializeFrameHead(rstbuf, 4, H2_FRAME_RST_STREAM, + 0, h2_res.stream_id()); + SaveUint32(rstbuf + FRAME_HEAD_SIZE, h2_res.error()); + if (WriteAck(_socket, rstbuf, sizeof(rstbuf)) != 0) { + LOG(WARNING) << "Fail to send RST_STREAM to " << *_socket; + return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG); + } + H2StreamContext* sctx = RemoveStreamAndDeferWU(h2_res.stream_id()); + if (sctx) { + if (is_server_side()) { + delete sctx; + return MakeMessage(NULL); + } else { + sctx->header().set_status_code( + H2ErrorToStatusCode(h2_res.error())); + return MakeMessage(sctx); + } + } + return MakeMessage(NULL); + } else { // send GOAWAY + char goawaybuf[FRAME_HEAD_SIZE + 8]; + SerializeFrameHead(goawaybuf, 8, H2_FRAME_GOAWAY, 0, 0); + SaveUint32(goawaybuf + FRAME_HEAD_SIZE, _last_received_stream_id); + SaveUint32(goawaybuf + FRAME_HEAD_SIZE + 4, h2_res.error()); + if (WriteAck(_socket, goawaybuf, sizeof(goawaybuf)) != 0) { + LOG(WARNING) << "Fail to send GOAWAY to " << *_socket; + return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG); + } + return MakeMessage(NULL); + } + } else { + return MakeParseError(PARSE_ERROR_NO_RESOURCE); + } +} + +H2ParseResult H2Context::OnHeaders( + butil::IOBufBytesIterator& it, const H2FrameHead& frame_head) { + // HEADERS frames MUST be associated with a stream. If a HEADERS frame + // is received whose stream identifier field is 0x0, the recipient MUST + // respond with a connection error (Section 5.4.1) of type PROTOCOL_ERROR. + if (frame_head.stream_id == 0) { + LOG(ERROR) << "Invalid stream_id=" << frame_head.stream_id; + return MakeH2Error(H2_PROTOCOL_ERROR); + } + const bool has_padding = (frame_head.flags & H2_FLAGS_PADDED); + const bool has_priority = (frame_head.flags & H2_FLAGS_PRIORITY); + if (frame_head.payload_size < + (size_t)(has_priority ? 5 : 0) + (size_t)has_padding) { + LOG(ERROR) << "Invalid payload_size=" << frame_head.payload_size; + return MakeH2Error(H2_FRAME_SIZE_ERROR); + } + uint32_t frag_size = frame_head.payload_size; + uint8_t pad_length = 0; + if (has_padding) { + pad_length = LoadUint8(it); + --frag_size; + } + if (has_priority) { + const uint32_t ALLOW_UNUSED stream_dep = LoadUint32(it); + const uint32_t ALLOW_UNUSED weight = LoadUint8(it); + frag_size -= 5; + } + if (frag_size < pad_length) { + LOG(ERROR) << "Invalid payload_size=" << frame_head.payload_size; + return MakeH2Error(H2_FRAME_SIZE_ERROR); + } + frag_size -= pad_length; + H2StreamContext* sctx = NULL; + if (is_server_side() && + frame_head.stream_id > _last_received_stream_id) { // new stream + if ((frame_head.stream_id & 1) == 0) { + LOG(ERROR) << "stream_id=" << frame_head.stream_id + << " created by client is not odd"; + return MakeH2Error(H2_PROTOCOL_ERROR); + } + _last_received_stream_id = frame_head.stream_id; + sctx = new H2StreamContext(_socket->is_read_progressive()); + sctx->Init(this, frame_head.stream_id); + const int rc = TryToInsertStream(frame_head.stream_id, sctx); + if (rc < 0) { + delete sctx; + LOG(ERROR) << "Fail to insert existing stream_id=" << frame_head.stream_id; + return MakeH2Error(H2_PROTOCOL_ERROR); + } else if (rc > 0) { + delete sctx; + return MakeH2Error(H2_REFUSED_STREAM); + } + } else { + sctx = FindStream(frame_head.stream_id); + if (sctx == NULL) { + if (is_client_side()) { + RPC_VLOG << "Fail to find stream_id=" << frame_head.stream_id; + // Ignore the message without closing the socket. + H2StreamContext tmp_sctx(false); + tmp_sctx.Init(this, frame_head.stream_id); + tmp_sctx.OnHeaders(it, frame_head, frag_size, pad_length); + return MakeH2Message(NULL); + } else { + LOG(ERROR) << "Fail to find stream_id=" << frame_head.stream_id; + return MakeH2Error(H2_PROTOCOL_ERROR); + } + } + } + return sctx->OnHeaders(it, frame_head, frag_size, pad_length); +} + +H2ParseResult H2StreamContext::OnHeaders( + butil::IOBufBytesIterator& it, const H2FrameHead& frame_head, + uint32_t frag_size, uint8_t pad_length) { + _parsed_length += FRAME_HEAD_SIZE + frame_head.payload_size; +#if defined(BRPC_H2_STREAM_STATE) + SetState(H2_STREAM_OPEN); +#endif + butil::IOBufBytesIterator it2(it, frag_size); + if (ConsumeHeaders(it2) < 0) { + LOG(ERROR) << "Invalid header, frag_size=" << frag_size + << ", stream_id=" << frame_head.stream_id; + return MakeH2Error(H2_PROTOCOL_ERROR); + } + const size_t nskip = frag_size - it2.bytes_left(); + CHECK_EQ(nskip, it.forward(nskip)); + if (it2.bytes_left()) { + it.append_and_forward(&_remaining_header_fragment, + it2.bytes_left()); + } + it.forward(pad_length); + if (frame_head.flags & H2_FLAGS_END_HEADERS) { + if (it2.bytes_left() != 0) { + LOG(ERROR) << "Incomplete header: payload_size=" << frame_head.payload_size + << ", stream_id=" << frame_head.stream_id; + return MakeH2Error(H2_PROTOCOL_ERROR); + } + if (frame_head.flags & H2_FLAGS_END_STREAM) { + return OnEndStream(); + } + return MakeH2Message(NULL); + } else { + if (frame_head.flags & H2_FLAGS_END_STREAM) { + // Delay calling OnEndStream() in OnContinuation() + _stream_ended = true; + } + return MakeH2Message(NULL); + } +} + +H2ParseResult H2Context::OnContinuation( + butil::IOBufBytesIterator& it, const H2FrameHead& frame_head) { + H2StreamContext* sctx = FindStream(frame_head.stream_id); + if (sctx == NULL) { + if (is_client_side()) { + RPC_VLOG << "Fail to find stream_id=" << frame_head.stream_id; + // Ignore the message without closing the socket. + H2StreamContext tmp_sctx(false); + tmp_sctx.Init(this, frame_head.stream_id); + tmp_sctx.OnContinuation(it, frame_head); + return MakeH2Message(NULL); + } else { + LOG(ERROR) << "Fail to find stream_id=" << frame_head.stream_id; + return MakeH2Error(H2_PROTOCOL_ERROR); + } + } + return sctx->OnContinuation(it, frame_head); +} + +H2ParseResult H2StreamContext::OnContinuation( + butil::IOBufBytesIterator& it, const H2FrameHead& frame_head) { + _parsed_length += FRAME_HEAD_SIZE + frame_head.payload_size; + it.append_and_forward(&_remaining_header_fragment, frame_head.payload_size); + const size_t size = _remaining_header_fragment.size(); + butil::IOBufBytesIterator it2(_remaining_header_fragment); + if (ConsumeHeaders(it2) < 0) { + LOG(ERROR) << "Invalid header: payload_size=" << frame_head.payload_size + << ", stream_id=" << frame_head.stream_id; + return MakeH2Error(H2_PROTOCOL_ERROR); + } + _remaining_header_fragment.pop_front(size - it2.bytes_left()); + if (frame_head.flags & H2_FLAGS_END_HEADERS) { + if (it2.bytes_left() != 0) { + LOG(ERROR) << "Incomplete header: payload_size=" << frame_head.payload_size + << ", stream_id=" << frame_head.stream_id; + return MakeH2Error(H2_PROTOCOL_ERROR); + } + if (_stream_ended) { + return OnEndStream(); + } + } + return MakeH2Message(NULL); +} + +H2ParseResult H2Context::OnData( + butil::IOBufBytesIterator& it, const H2FrameHead& frame_head) { + uint32_t frag_size = frame_head.payload_size; + uint8_t pad_length = 0; + if (frame_head.flags & H2_FLAGS_PADDED) { + if (frag_size == 0) { + LOG(ERROR) << "Invalid payload_size=" << frame_head.payload_size; + return MakeH2Error(H2_FRAME_SIZE_ERROR); + } + --frag_size; + pad_length = LoadUint8(it); + } + if (frag_size < pad_length) { + LOG(ERROR) << "Invalid payload_size=" << frame_head.payload_size; + return MakeH2Error(H2_FRAME_SIZE_ERROR); + } + frag_size -= pad_length; + H2StreamContext* sctx = FindStream(frame_head.stream_id); + if (sctx == NULL) { + // If a DATA frame is received whose stream is not in "open" or "half-closed (local)" state, + // the recipient MUST respond with a stream error (Section 5.4.2) of type STREAM_CLOSED. + // Ignore the message without closing the socket. + H2StreamContext tmp_sctx(false); + tmp_sctx.Init(this, frame_head.stream_id); + tmp_sctx.OnData(it, frame_head, frag_size, pad_length); + DeferWindowUpdate(tmp_sctx.ReleaseDeferredWindowUpdate()); + + LOG(ERROR) << "Fail to find stream_id=" << frame_head.stream_id; + return MakeH2Error(H2_STREAM_CLOSED_ERROR, frame_head.stream_id); + } + return sctx->OnData(it, frame_head, frag_size, pad_length); +} + +H2ParseResult H2StreamContext::OnData( + butil::IOBufBytesIterator& it, const H2FrameHead& frame_head, + uint32_t frag_size, uint8_t pad_length) { + _parsed_length += FRAME_HEAD_SIZE + frame_head.payload_size; + butil::IOBuf data; + it.append_and_forward(&data, frag_size); + it.forward(pad_length); + for (size_t i = 0; i < data.backing_block_num(); ++i) { + const butil::StringPiece blk = data.backing_block(i); + if (OnBody(blk.data(), blk.size()) != 0) { + if (body_too_large()) { + return MakeH2Error(H2_ENHANCE_YOUR_CALM, stream_id()); + } + LOG(ERROR) << "Fail to parse data"; + return MakeH2Error(H2_PROTOCOL_ERROR); + } + } + + int64_t acc = frag_size + + _deferred_window_update.fetch_add(frag_size, butil::memory_order_relaxed); + int64_t quota = static_cast( + _conn_ctx->local_settings().stream_window_size / + (_conn_ctx->VolatilePendingStreamSize() + 1)); + // Allocate the quota of the window to each stream. + if (acc >= quota) { + if (acc > _conn_ctx->local_settings().stream_window_size) { + LOG(ERROR) << "Fail to satisfy the stream-level flow control policy"; + return MakeH2Error(H2_FLOW_CONTROL_ERROR, frame_head.stream_id); + } + // Rarely happen for small messages. + const int64_t stream_wu = + _deferred_window_update.exchange(0, butil::memory_order_relaxed); + + if (stream_wu > 0) { + char winbuf[(FRAME_HEAD_SIZE + 4) * 2]; + char* p = winbuf; + + SerializeFrameHead(p, 4, H2_FRAME_WINDOW_UPDATE, 0, stream_id()); + SaveUint32(p + FRAME_HEAD_SIZE, stream_wu); + p += FRAME_HEAD_SIZE + 4; + + const int64_t conn_wu = stream_wu + _conn_ctx->ReleaseDeferredWindowUpdate(); + SerializeFrameHead(p, 4, H2_FRAME_WINDOW_UPDATE, 0, 0); + SaveUint32(p + FRAME_HEAD_SIZE, conn_wu); + if (WriteAck(_conn_ctx->_socket, winbuf, sizeof(winbuf)) != 0) { + LOG(WARNING) << "Fail to send WINDOW_UPDATE to " << *_conn_ctx->_socket; + return MakeH2Error(H2_INTERNAL_ERROR); + } + } + } + if (frame_head.flags & H2_FLAGS_END_STREAM) { + return OnEndStream(); + } + return MakeH2Message(NULL); +} + +H2ParseResult H2Context::OnResetStream( + butil::IOBufBytesIterator& it, const H2FrameHead& frame_head) { + if (frame_head.payload_size != 4) { + LOG(ERROR) << "Invalid payload_size=" << frame_head.payload_size; + return MakeH2Error(H2_FRAME_SIZE_ERROR); + } + const H2Error h2_error = static_cast(LoadUint32(it)); + H2StreamContext* sctx = FindStream(frame_head.stream_id); + if (sctx == NULL) { + RPC_VLOG << "Fail to find stream_id=" << frame_head.stream_id; + return MakeH2Message(NULL); + } + return sctx->OnResetStream(h2_error, frame_head); +} + +H2ParseResult H2StreamContext::OnResetStream( + H2Error h2_error, const H2FrameHead& frame_head) { + _parsed_length += FRAME_HEAD_SIZE + frame_head.payload_size; +#if defined(BRPC_H2_STREAM_STATE) + if (state() == H2_STREAM_OPEN) { + SetState(H2_STREAM_HALF_CLOSED_REMOTE); + } else if (state() == H2_STREAM_HALF_CLOSED_LOCAL) { + SetState(H2_STREAM_CLOSED); + } else { + LOG(ERROR) << "Invalid state=" << H2StreamState2Str(_state) + << " in stream_id=" << stream_id(); + return MakeH2Error(H2_PROTOCOL_ERROR); + } +#endif + H2StreamContext* sctx = _conn_ctx->RemoveStreamAndDeferWU(stream_id()); + if (sctx == NULL) { + LOG(ERROR) << "Fail to find stream_id=" << stream_id(); + return MakeH2Error(H2_PROTOCOL_ERROR); + } + if (_conn_ctx->is_client_side()) { + sctx->header().set_status_code(H2ErrorToStatusCode(h2_error)); + return MakeH2Message(sctx); + } else { + // No need to process the request. + delete sctx; + return MakeH2Message(NULL); + } +} + +H2ParseResult H2StreamContext::OnEndStream() { +#if defined(BRPC_H2_STREAM_STATE) + if (state() == H2_STREAM_OPEN) { + SetState(H2_STREAM_HALF_CLOSED_REMOTE); + } else if (state() == H2_STREAM_HALF_CLOSED_LOCAL) { + SetState(H2_STREAM_CLOSED); + } else { + LOG(ERROR) << "Invalid state=" << H2StreamState2Str(_state) + << " in stream_id=" << stream_id(); + return MakeH2Error(H2_PROTOCOL_ERROR); + } +#endif + H2StreamContext* sctx = _conn_ctx->RemoveStreamAndDeferWU(stream_id()); + if (sctx == NULL) { + RPC_VLOG << "Fail to find stream_id=" << stream_id(); + return MakeH2Message(NULL); + } + CHECK_EQ(sctx, this); + + OnMessageComplete(); + return MakeH2Message(sctx); +} + +H2ParseResult H2Context::OnSettings( + butil::IOBufBytesIterator& it, const H2FrameHead& frame_head) { + // SETTINGS frames always apply to a connection, never a single stream. + // The stream identifier for a SETTINGS frame MUST be zero (0x0). If an + // endpoint receives a SETTINGS frame whose stream identifier field is + // anything other than 0x0, the endpoint MUST respond with a connection + // error (Section 5.4.1) of type PROTOCOL_ERROR. + if (frame_head.stream_id != 0) { + LOG(ERROR) << "Invalid stream_id=" << frame_head.stream_id; + return MakeH2Error(H2_PROTOCOL_ERROR); + } + if (frame_head.flags & H2_FLAGS_ACK) { + if (frame_head.payload_size != 0) { + LOG(ERROR) << "Non-zero payload_size=" << frame_head.payload_size + << " for settings-ACK"; + return MakeH2Error(H2_PROTOCOL_ERROR); + } + _local_settings = _unack_local_settings; + return MakeH2Message(NULL); + } + const int64_t old_stream_window_size = _remote_settings.stream_window_size; + if (!_remote_settings_received) { + // To solve the problem that sender can't send large request before receving + // remote setting, the initial window size of stream/connection is set to + // MAX_WINDOW_SIZE(see constructor of H2Context). + // As a result, in the view of remote side, window size is 65535 by default so + // it may not send its stream size to sender, making stream size still be + // MAX_WINDOW_SIZE. In this case we need to revert this value to default. + H2Settings tmp_settings; + if (!ParseH2Settings(&tmp_settings, it, frame_head.payload_size)) { + LOG(ERROR) << "Fail to parse from SETTINGS"; + return MakeH2Error(H2_PROTOCOL_ERROR); + } + _remote_settings = tmp_settings; + _remote_window_left.fetch_sub( + H2Settings::MAX_WINDOW_SIZE - H2Settings::DEFAULT_INITIAL_WINDOW_SIZE, + butil::memory_order_relaxed); + _remote_settings_received = true; + } else { + if (!ParseH2Settings(&_remote_settings, it, frame_head.payload_size)) { + LOG(ERROR) << "Fail to parse from SETTINGS"; + return MakeH2Error(H2_PROTOCOL_ERROR); + } + } + const int64_t window_diff = + static_cast(_remote_settings.stream_window_size) + - old_stream_window_size; + if (window_diff) { + // Do not update the connection flow-control window here, which can only + // be changed using WINDOW_UPDATE frames. + // https://tools.ietf.org/html/rfc7540#section-6.9.2 + // TODO(gejun): Has race conditions with AppendAndDestroySelf + std::unique_lock mu(_stream_mutex); + for (StreamMap::const_iterator it = _pending_streams.begin(); + it != _pending_streams.end(); ++it) { + if (!AddWindowSize(&it->second->_remote_window_left, window_diff)) { + return MakeH2Error(H2_FLOW_CONTROL_ERROR); + } + } + } + // Respond with ack + char headbuf[FRAME_HEAD_SIZE]; + SerializeFrameHead(headbuf, 0, H2_FRAME_SETTINGS, H2_FLAGS_ACK, 0); + if (WriteAck(_socket, headbuf, sizeof(headbuf)) != 0) { + LOG(WARNING) << "Fail to respond settings with ack to " << *_socket; + return MakeH2Error(H2_PROTOCOL_ERROR); + } + return MakeH2Message(NULL); +} + +H2ParseResult H2Context::OnPriority( + butil::IOBufBytesIterator&, const H2FrameHead&) { + LOG(ERROR) << "Not support PRIORITY frame yet"; + return MakeH2Error(H2_PROTOCOL_ERROR); +} + +H2ParseResult H2Context::OnPushPromise( + butil::IOBufBytesIterator&, const H2FrameHead&) { + LOG(ERROR) << "Not support PUSH_PROMISE frame yet"; + return MakeH2Error(H2_PROTOCOL_ERROR); +} + +H2ParseResult H2Context::OnPing( + butil::IOBufBytesIterator& it, const H2FrameHead& frame_head) { + if (frame_head.payload_size != 8) { + LOG(ERROR) << "Invalid payload_size=" << frame_head.payload_size; + return MakeH2Error(H2_FRAME_SIZE_ERROR); + } + if (frame_head.stream_id != 0) { + LOG(ERROR) << "Invalid stream_id=" << frame_head.stream_id; + return MakeH2Error(H2_PROTOCOL_ERROR); + } + if (frame_head.flags & H2_FLAGS_ACK) { + return MakeH2Message(NULL); + } + + char pongbuf[FRAME_HEAD_SIZE + 8]; + SerializeFrameHead(pongbuf, 8, H2_FRAME_PING, H2_FLAGS_ACK, 0); + it.copy_and_forward(pongbuf + FRAME_HEAD_SIZE, 8); + if (WriteAck(_socket, pongbuf, sizeof(pongbuf)) != 0) { + LOG(WARNING) << "Fail to send ack of PING to " << *_socket; + return MakeH2Error(H2_PROTOCOL_ERROR); + } + return MakeH2Message(NULL); +} + +static void* ProcessHttpResponseWrapper(void* void_arg) { + ProcessHttpResponse(static_cast(void_arg)); + return NULL; +} + +H2ParseResult H2Context::OnGoAway( + butil::IOBufBytesIterator& it, const H2FrameHead& h) { + if (h.payload_size < 8) { + LOG(ERROR) << "Invalid payload_size=" << h.payload_size; + return MakeH2Error(H2_FRAME_SIZE_ERROR); + } + if (h.stream_id != 0) { + LOG(ERROR) << "Invalid stream_id=" << h.stream_id; + return MakeH2Error(H2_PROTOCOL_ERROR); + } + if (h.flags) { + LOG(ERROR) << "Invalid flags=" << h.flags; + return MakeH2Error(H2_PROTOCOL_ERROR); + } + // Skip Additional Debug Data + it.forward(h.payload_size - 8); + const int last_stream_id = static_cast(LoadUint32(it)); + const H2Error ALLOW_UNUSED h2_error = static_cast(LoadUint32(it)); + // TODO(zhujiashun): client and server should unify the code. + // Server Push is not supported so it works fine now. + if (is_client_side()) { + // The socket will not be selected for further requests. + _socket->SetLogOff(); + + std::vector goaway_streams; + RemoveGoAwayStreams(last_stream_id, &goaway_streams); + if (goaway_streams.empty()) { + return MakeH2Message(NULL); + } + for (size_t i = 0; i < goaway_streams.size(); ++i) { + H2StreamContext* sctx = goaway_streams[i]; + sctx->header().set_status_code(HTTP_STATUS_SERVICE_UNAVAILABLE); + } + for (size_t i = 1; i < goaway_streams.size(); ++i) { + bthread_t th; + bthread_attr_t tmp = (FLAGS_usercode_in_pthread ? + BTHREAD_ATTR_PTHREAD : + BTHREAD_ATTR_NORMAL); + tmp.keytable_pool = _socket->keytable_pool(); + CHECK_EQ(0, bthread_start_background(&th, &tmp, ProcessHttpResponseWrapper, + static_cast(goaway_streams[i]))); + } + return MakeH2Message(goaway_streams[0]); + } else { + // server serves requests on-demand, ignoring GOAWAY is OK. + return MakeH2Message(NULL); + } +} + +H2ParseResult H2Context::OnWindowUpdate( + butil::IOBufBytesIterator& it, const H2FrameHead& frame_head) { + if (frame_head.payload_size != 4) { + LOG(ERROR) << "Invalid payload_size=" << frame_head.payload_size; + return MakeH2Error(H2_FRAME_SIZE_ERROR); + } + const uint32_t inc = LoadUint32(it); + if ((inc & 0x80000000) || (inc == 0)) { + LOG(ERROR) << "Invalid window_size_increment=" << inc; + return MakeH2Error(H2_PROTOCOL_ERROR); + } + if (frame_head.stream_id == 0) { + if (!AddWindowSize(&_remote_window_left, inc)) { + LOG(ERROR) << "Invalid connection-level window_size_increment=" << inc; + return MakeH2Error(H2_FLOW_CONTROL_ERROR); + } + return MakeH2Message(NULL); + } else { + H2StreamContext* sctx = FindStream(frame_head.stream_id); + if (sctx == NULL) { + RPC_VLOG << "Fail to find stream_id=" << frame_head.stream_id; + return MakeH2Message(NULL); + } + if (!AddWindowSize(&sctx->_remote_window_left, inc)) { + LOG(ERROR) << "Invalid stream-level window_size_increment=" << inc + << " to remote_window_left=" << sctx->_remote_window_left.load(butil::memory_order_relaxed); + return MakeH2Error(H2_FLOW_CONTROL_ERROR); + } + return MakeH2Message(NULL); + } +} + +void H2Context::Describe(std::ostream& os, const DescribeOptions& opt) const { + if (opt.verbose) { + os << '\n'; + } + const char sep = (opt.verbose ? '\n' : ' '); + os << "conn_state=" << H2ConnectionState2Str(_conn_state); + os << sep << "last_received_stream_id=" << _last_received_stream_id + << sep << "last_sent_stream_id=" << _last_sent_stream_id; + os << sep << "deferred_window_update=" + << _deferred_window_update.load(butil::memory_order_relaxed) + << sep << "remote_conn_window_left=" + << _remote_window_left.load(butil::memory_order_relaxed) + << sep << "remote_settings=" << _remote_settings + << sep << "remote_settings_received=" << _remote_settings_received + << sep << "local_settings=" << _local_settings + << sep << "hpacker={"; + IndentingOStream os2(os, 2); + _hpacker.Describe(os2, opt); + os << '}'; + size_t abandoned_size = 0; + { + BAIDU_SCOPED_LOCK(_abandoned_streams_mutex); + abandoned_size = _abandoned_streams.size(); + } + os << sep << "abandoned_streams=" << abandoned_size + << sep << "pending_streams=" << VolatilePendingStreamSize(); + if (opt.verbose) { + os << '\n'; + } +} + +inline int64_t H2Context::ReleaseDeferredWindowUpdate() { + if (_deferred_window_update.load(butil::memory_order_relaxed) == 0) { + return 0; + } + return _deferred_window_update.exchange(0, butil::memory_order_relaxed); +} + +void H2Context::DeferWindowUpdate(int64_t size) { + if (size <= 0) { + return; + } + const int64_t acc = _deferred_window_update.fetch_add(size, butil::memory_order_relaxed) + size; + if (acc >= local_settings().stream_window_size / 2) { + // Rarely happen for small messages. + const int64_t conn_wu = _deferred_window_update.exchange(0, butil::memory_order_relaxed); + if (conn_wu > 0) { + char winbuf[FRAME_HEAD_SIZE + 4]; + SerializeFrameHead(winbuf, 4, H2_FRAME_WINDOW_UPDATE, 0, 0); + SaveUint32(winbuf + FRAME_HEAD_SIZE, conn_wu); + if (WriteAck(_socket, winbuf, sizeof(winbuf)) != 0) { + LOG(WARNING) << "Fail to send WINDOW_UPDATE"; + } + } + } +} + +#if defined(BRPC_PROFILE_H2) +bvar::Adder g_parse_time; +bvar::PerSecond > g_parse_time_per_second( + "h2_parse_second", &g_parse_time); +#endif + +ParseResult ParseH2Message(butil::IOBuf *source, Socket *socket, + bool read_eof, const void *arg) { +#if defined(BRPC_PROFILE_H2) + bvar::ScopedTimer > tm(g_parse_time); +#endif + H2Context* ctx = static_cast(socket->parsing_context()); + if (ctx == NULL) { + if (read_eof || source->empty()) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + const Server* server = static_cast(arg); + ctx = new H2Context(socket, server); + if (ctx->Init() != 0) { + delete ctx; + LOG(ERROR) << "Fail to init H2Context"; + return MakeParseError(PARSE_ERROR_NO_RESOURCE); + } + socket->initialize_parsing_context(&ctx); + } + butil::IOBufBytesIterator it(*source); + size_t last_bytes_left = it.bytes_left(); + CHECK_EQ(last_bytes_left, source->size()); + while (true) { + ParseResult res = ctx->Consume(it, socket); + if (res.is_ok()) { + last_bytes_left = it.bytes_left(); + if (res.message() == NULL) { + // no message to process, continue parsing. + continue; + } + } + source->pop_front(source->size() - last_bytes_left); + ctx->ClearAbandonedStreams(); + return res; + } +} + +void H2Context::AddAbandonedStream(uint32_t stream_id) { + std::unique_lock mu(_abandoned_streams_mutex); + _abandoned_streams.push_back(stream_id); +} + +inline void H2Context::ClearAbandonedStreams() { + std::unique_lock mu(_abandoned_streams_mutex); + while (!_abandoned_streams.empty()) { + const uint32_t stream_id = _abandoned_streams.back(); + _abandoned_streams.pop_back(); + mu.unlock(); + H2StreamContext* sctx = RemoveStreamAndDeferWU(stream_id); + if (sctx != NULL) { + delete sctx; + } + mu.lock(); + } +} + +H2StreamContext::H2StreamContext(bool read_body_progressively) + : HttpContext(read_body_progressively) + , _conn_ctx(NULL) +#if defined(BRPC_H2_STREAM_STATE) + , _state(H2_STREAM_IDLE) +#endif + , _stream_id(0) + , _stream_ended(false) + , _remote_window_left(0) + , _deferred_window_update(0) + , _correlation_id(INVALID_BTHREAD_ID.value) { + header().set_version(2, 0); +#ifndef NDEBUG + get_h2_bvars()->h2_stream_context_count << 1; +#endif +} + +void H2StreamContext::Init(H2Context* conn_ctx, int stream_id) { + _conn_ctx = conn_ctx; + _stream_id = stream_id; + _remote_window_left.store(conn_ctx->remote_settings().stream_window_size, + butil::memory_order_relaxed); +} + +H2StreamContext::~H2StreamContext() { +#ifndef NDEBUG + get_h2_bvars()->h2_stream_context_count << -1; +#endif +} + +#if defined(BRPC_H2_STREAM_STATE) +void H2StreamContext::SetState(H2StreamState state) { + const H2StreamState old_state = _state; + _state = state; + RPC_VLOG << "stream_id=" << stream_id() << " changed from " + << H2StreamState2Str(old_state) << " to " + << H2StreamState2Str(state); +} +#endif + +bool H2StreamContext::ConsumeWindowSize(int64_t size) { + // This method is guaranteed to be called in AppendAndDestroySelf() which + // is run sequentially. As a result, _remote_window_left of this stream + // context will not be decremented (may be incremented) because following + // AppendAndDestroySelf() are not run yet. + // This fact is important to make window_size changes to stream and + // connection contexts transactionally. + if (_remote_window_left.load(butil::memory_order_relaxed) < size) { + return false; + } + if (!MinusWindowSize(&_conn_ctx->_remote_window_left, size)) { + return false; + } + int64_t after_sub = _remote_window_left.fetch_sub(size, butil::memory_order_relaxed) - size; + if (after_sub < 0) { + LOG(FATAL) << "Impossible, the http2 impl is buggy"; + _remote_window_left.fetch_add(size, butil::memory_order_relaxed); + return false; + } + return true; +} + +int H2StreamContext::ConsumeHeaders(butil::IOBufBytesIterator& it) { + HPacker& hpacker = _conn_ctx->hpacker(); + HttpHeader& h = header(); + while (it) { + HPacker::Header pair; + const int rc = hpacker.Decode(it, &pair); + if (rc < 0) { + return -1; + } + if (rc == 0) { + break; + } + const char* const name = pair.name.c_str(); + bool matched = false; + if (name[0] == ':') { // reserved names + switch (name[1]) { + case 'a': + if (strcmp(name + 2, /*a*/"uthority") == 0) { + matched = true; + h.uri().SetHostAndPort(pair.value); + } + break; + case 'm': + if (strcmp(name + 2, /*m*/"ethod") == 0) { + matched = true; + HttpMethod method; + if (!Str2HttpMethod(pair.value.c_str(), &method)) { + LOG(ERROR) << "Invalid method=" << pair.value; + return -1; + } + h.set_method(method); + } + break; + case 'p': + if (strcmp(name + 2, /*p*/"ath") == 0) { + matched = true; + // Including path/query/fragment + h.uri().SetH2Path(pair.value); + } + break; + case 's': + if (strcmp(name + 2, /*:s*/"cheme") == 0) { + matched = true; + h.uri().set_scheme(pair.value); + } else if (strcmp(name + 2, /*:s*/"tatus") == 0) { + matched = true; + char* endptr = NULL; + const int sc = strtol(pair.value.c_str(), &endptr, 10); + if (*endptr != '\0') { + LOG(ERROR) << "Invalid status=" << pair.value; + return -1; + } + h.set_status_code(sc); + } + break; + default: + break; + } + if (!matched) { + LOG(ERROR) << "Unknown name=`" << name << '\''; + return -1; + } + } else if (name[0] == 'c' && + strcmp(name + 1, /*c*/"ontent-type") == 0) { + h.set_content_type(pair.value); + } else { + h.AppendHeader(pair.name, pair.value); + } + + if (FLAGS_http_verbose) { + butil::IOBufBuilder* vs = this->_vmsgbuilder.get(); + if (vs == NULL) { + vs = new butil::IOBufBuilder; + this->_vmsgbuilder.reset(vs); + if (_conn_ctx->is_server_side()) { + *vs << "[ H2 REQUEST @" << butil::my_ip() << " ]"; + } else { + *vs << "[ H2 RESPONSE @" << butil::my_ip() << " ]"; + } + } + // print \n first to be consistent with code in http_message.cpp + *vs << "\n< " << pair.name << " = " << pair.value; + } + } + return 0; +} + +const CommonStrings* get_common_strings(); + +static void PackH2Message(butil::IOBuf* out, + butil::IOBuf& headers, + butil::IOBuf& trailer_headers, + const butil::IOBuf& data, + int stream_id, + H2Context* conn_ctx) { + const H2Settings& remote_settings = conn_ctx->remote_settings(); + char headbuf[FRAME_HEAD_SIZE]; + H2FrameHead headers_head = { + (uint32_t)headers.size(), H2_FRAME_HEADERS, 0, stream_id}; + if (data.empty() && trailer_headers.empty()) { + headers_head.flags |= H2_FLAGS_END_STREAM; + } + if (headers_head.payload_size <= remote_settings.max_frame_size) { + headers_head.flags |= H2_FLAGS_END_HEADERS; + SerializeFrameHead(headbuf, headers_head); + out->append(headbuf, sizeof(headbuf)); + out->append(butil::IOBuf::Movable(headers)); + } else { + headers_head.payload_size = remote_settings.max_frame_size; + SerializeFrameHead(headbuf, headers_head); + out->append(headbuf, sizeof(headbuf)); + headers.cutn(out, headers_head.payload_size); + + H2FrameHead cont_head = {0, H2_FRAME_CONTINUATION, 0, stream_id}; + while (!headers.empty()) { + if (headers.size() <= remote_settings.max_frame_size) { + cont_head.flags |= H2_FLAGS_END_HEADERS; + cont_head.payload_size = headers.size(); + } else { + cont_head.payload_size = remote_settings.max_frame_size; + } + SerializeFrameHead(headbuf, cont_head); + out->append(headbuf, FRAME_HEAD_SIZE); + headers.cutn(out, cont_head.payload_size); + } + } + if (!data.empty()) { + H2FrameHead data_head = {0, H2_FRAME_DATA, 0, stream_id}; + butil::IOBufBytesIterator it(data); + while (it.bytes_left()) { + if (it.bytes_left() <= remote_settings.max_frame_size) { + data_head.payload_size = it.bytes_left(); + if (trailer_headers.empty()) { + data_head.flags |= H2_FLAGS_END_STREAM; + } + } else { + data_head.payload_size = remote_settings.max_frame_size; + } + SerializeFrameHead(headbuf, data_head); + out->append(headbuf, FRAME_HEAD_SIZE); + it.append_and_forward(out, data_head.payload_size); + } + } + if (!trailer_headers.empty()) { + H2FrameHead headers_head = { + (uint32_t)trailer_headers.size(), H2_FRAME_HEADERS, 0, stream_id}; + headers_head.flags |= H2_FLAGS_END_STREAM; + headers_head.flags |= H2_FLAGS_END_HEADERS; + SerializeFrameHead(headbuf, headers_head); + out->append(headbuf, sizeof(headbuf)); + out->append(butil::IOBuf::Movable(trailer_headers)); + } + const int64_t conn_wu = conn_ctx->ReleaseDeferredWindowUpdate(); + if (conn_wu > 0) { + char winbuf[FRAME_HEAD_SIZE + 4]; + SerializeFrameHead(winbuf, 4, H2_FRAME_WINDOW_UPDATE, 0, 0); + SaveUint32(winbuf + FRAME_HEAD_SIZE, conn_wu); + out->append(winbuf, sizeof(winbuf)); + } +} + +H2UnsentRequest* H2UnsentRequest::New(Controller* c) { + const HttpHeader& h = c->http_request(); + const CommonStrings* const common = get_common_strings(); + const bool need_content_type = !h.content_type().empty(); + const bool need_accept = !h.GetHeader(common->ACCEPT); + const bool need_user_agent = !h.GetHeader(common->USER_AGENT); + const std::string& user_info = h.uri().user_info(); + const bool need_authorization = + (!user_info.empty() && !h.GetHeader("Authorization")); + const size_t maxsize = h.HeaderCount() + 4 + + (size_t)need_content_type + + (size_t)need_accept + + (size_t)need_user_agent + + (size_t)need_authorization; + const size_t memsize = offsetof(H2UnsentRequest, _list) + + sizeof(HPacker::Header) * maxsize; + H2UnsentRequest* msg = new (malloc(memsize)) H2UnsentRequest(c); + // :method + if (h.method() == HTTP_METHOD_GET) { + msg->push(common->H2_METHOD, common->METHOD_GET); + } else if (h.method() == HTTP_METHOD_POST) { + msg->push(common->H2_METHOD, common->METHOD_POST); + } else { + msg->push(common->H2_METHOD) = HttpMethod2Str(h.method()); + } + // :scheme + const std::string* scheme = &h.uri().scheme(); + if (scheme->empty()) { + scheme = (c->is_ssl() ? &common->H2_SCHEME_HTTPS : + &common->H2_SCHEME_HTTP); + } + msg->push(common->H2_SCHEME, *scheme); + // :path + h.uri().GenerateH2Path(&msg->push(common->H2_PATH)); + // :authority + const std::string* phost = h.GetHeader("host"); + if (phost) { + msg->push(common->H2_AUTHORITY) = *phost; + } else { + const URI& uri = h.uri(); + std::string* val = &msg->push(common->H2_AUTHORITY); + if (!uri.host().empty()) { + if (uri.port() < 0) { + *val = uri.host(); + } else { + butil::string_printf(val, "%s:%d", uri.host().c_str(), uri.port()); + } + } else if (c->remote_side().port != 0) { + *val = butil::endpoint2str(c->remote_side()).c_str(); + } + } + if (need_content_type) { + msg->push(common->CONTENT_TYPE, h.content_type()); + } + if (need_accept) { + msg->push(common->ACCEPT, common->DEFAULT_ACCEPT); + } + if (need_user_agent) { + msg->push(common->USER_AGENT, common->DEFAULT_USER_AGENT); + } + if (need_authorization) { + // NOTE: just assume user_info is well formatted, namely + // ":". Users are very unlikely to add extra + // characters in this part and even if users did, most of them are + // invalid and rejected by http_parser_parse_url(). + std::string encoded_user_info; + butil::Base64Encode(user_info, &encoded_user_info); + std::string* val = &msg->push(common->AUTHORIZATION); + val->reserve(6 + encoded_user_info.size()); + val->append("Basic "); + val->append(encoded_user_info); + } + msg->_sctx.reset(new H2StreamContext(c->is_response_read_progressively())); + return msg; +} + +void H2UnsentRequest::Destroy() { + for (size_t i = 0; i < _size; ++i) { + _list[i].~Header(); + } + this->~H2UnsentRequest(); + free(this); +} + +struct RemoveRefOnQuit { + RemoveRefOnQuit(H2UnsentRequest* msg) : _msg(msg) {} + ~RemoveRefOnQuit() { _msg->RemoveRefManually(); } +private: + DISALLOW_COPY_AND_ASSIGN(RemoveRefOnQuit); + H2UnsentRequest* _msg; +}; + +void H2UnsentRequest::DestroyStreamUserData(SocketUniquePtr& sending_sock, + Controller* cntl, + int error_code, + bool /*end_of_rpc*/) { + RemoveRefOnQuit deref_self(this); + if (sending_sock != NULL && error_code != 0) { + CHECK_EQ(cntl, _cntl); + std::unique_lock mu(_mutex); + _cntl = NULL; + if (_stream_id != 0) { + H2Context* ctx = static_cast(sending_sock->parsing_context()); + ctx->AddAbandonedStream(_stream_id); + } + } +} + +#if defined(BRPC_PROFILE_H2) +bvar::Adder g_append_request_time; +bvar::PerSecond > g_append_request_time_per_second( + "h2_append_request_second", &g_append_request_time); +#endif + +butil::Status +H2UnsentRequest::AppendAndDestroySelf(butil::IOBuf* out, Socket* socket) { +#if defined(BRPC_PROFILE_H2) + bvar::ScopedTimer > tm(g_append_request_time); +#endif + RemoveRefOnQuit deref_self(this); + if (socket == NULL) { + return butil::Status::OK(); + } + H2Context* ctx = static_cast(socket->parsing_context()); + + // Create a http2 stream and store correlation_id in. + if (ctx == NULL) { + CHECK(socket->CreatedByConnect()); + ctx = new H2Context(socket, NULL); + if (ctx->Init() != 0) { + delete ctx; + return butil::Status(EINTERNAL, "Fail to init H2Context"); + } + socket->initialize_parsing_context(&ctx); + + // Append client connection preface + out->append(H2_CONNECTION_PREFACE_PREFIX, + H2_CONNECTION_PREFACE_PREFIX_SIZE); + + char settingsbuf[FRAME_HEAD_SIZE + H2_SETTINGS_MAX_BYTE_SIZE + + FRAME_HEAD_SIZE + 4/*for WU*/]; + const size_t nb = SerializeH2SettingsFrameAndWU( + ctx->_unack_local_settings, settingsbuf); + out->append(settingsbuf, nb); + } + + // TODO(zhujiashun): also check this in server push + if (ctx->VolatilePendingStreamSize() > ctx->remote_settings().max_concurrent_streams) { + return butil::Status(ELIMIT, "Pending Stream count exceeds max concurrent stream"); + } + + // Although the critical section looks huge, it should rarely be contended + // since timeout of RPC is much larger than the delay of sending. + std::unique_lock mu(_mutex); + if (_cntl == NULL) { + return butil::Status(ECANCELED, "The RPC was already failed"); + } + + const int id = ctx->AllocateClientStreamId(); + if (id < 0) { + // The RPC should be failed and retried. + // Note that the socket should not be SetFailed() which may affect + // other RPC successfully sent requests and waiting for responses. + RPC_VLOG << "Fail to allocate stream_id on " << *socket + << " h2req=" << (StreamUserData*)this; + return butil::Status(EH2RUNOUTSTREAMS, "Fail to allocate stream_id"); + } + + _sctx->Init(ctx, id); + // check flow control restriction + if (!_cntl->request_attachment().empty()) { + const int64_t data_size = _cntl->request_attachment().size(); + if (!_sctx->ConsumeWindowSize(data_size)) { + return butil::Status(ELIMIT, "remote_window_left is not enough, data_size=%" PRId64, data_size); + } + } + + const int rc = ctx->TryToInsertStream(id, _sctx.get()); + if (rc < 0) { + return butil::Status(EINTERNAL, "Fail to insert existing stream_id"); + } else if (rc > 0) { + return butil::Status(ELOGOFF, "the connection just issued GOAWAY"); + } + _stream_id = _sctx->stream_id(); + // After calling TryToInsertStream, the ownership of _sctx is transferred to ctx + _sctx.release(); + + HPacker& hpacker = ctx->hpacker(); + butil::IOBufAppender appender; + HPackOptions options; + options.encode_name = FLAGS_h2_hpack_encode_name; + options.encode_value = FLAGS_h2_hpack_encode_value; + if (ctx->remote_settings().header_table_size == 0) { + options.index_policy = HPACK_NEVER_INDEX_HEADER; + } + + for (size_t i = 0; i < _size; ++i) { + hpacker.Encode(&appender, _list[i], options); + } + if (_cntl->has_http_request()) { + const HttpHeader& h = _cntl->http_request(); + for (HttpHeader::HeaderIterator it = h.HeaderBegin(); + it != h.HeaderEnd(); ++it) { + HPacker::Header header(it->first, it->second); + hpacker.Encode(&appender, header, options); + } + } + butil::IOBuf frag; + appender.move_to(frag); + butil::IOBuf dummy_buf; + PackH2Message(out, frag, dummy_buf, _cntl->request_attachment(), _stream_id, ctx); + return butil::Status::OK(); +} + +size_t H2UnsentRequest::EstimatedByteSize() { + size_t sz = 0; + for (size_t i = 0; i < _size; ++i) { + sz += _list[i].name.size() + _list[i].value.size() + 1; + } + std::unique_lock mu(_mutex); + if (_cntl == NULL) { + return 0; + } + if (_cntl->has_http_request()) { + const HttpHeader& h = _cntl->http_request(); + for (HttpHeader::HeaderIterator it = h.HeaderBegin(); + it != h.HeaderEnd(); ++it) { + sz += it->first.size() + it->second.size() + 1; + } + } + sz += _cntl->request_attachment().size(); + return sz; +} + +void H2UnsentRequest::Print(std::ostream& os) const { + os << "[ H2 REQUEST @" << butil::my_ip() << " ]\n"; + for (size_t i = 0; i < _size; ++i) { + os << "> " << _list[i].name << " = " << _list[i].value << '\n'; + } + std::unique_lock mu(_mutex); + if (_cntl == NULL) { + return; + } + if (_cntl->has_http_request()) { + const HttpHeader& h = _cntl->http_request(); + for (HttpHeader::HeaderIterator it = h.HeaderBegin(); + it != h.HeaderEnd(); ++it) { + os << "> " << it->first << " = " << it->second << '\n'; + } + } + const butil::IOBuf* body = &_cntl->request_attachment(); + if (!body->empty()) { + os << "> \n"; + } + os << butil::ToPrintable(*body, FLAGS_http_verbose_max_body_length); + +} + +H2UnsentResponse::H2UnsentResponse(Controller* c, int stream_id, bool is_grpc) + : _size(0) + , _stream_id(stream_id) + , _http_response(c->release_http_response()) + , _is_grpc(is_grpc) { + _data.swap(c->response_attachment()); + if (is_grpc) { + _grpc_status = ErrorCodeToGrpcStatus(c->ErrorCode()); + PercentEncode(c->ErrorText(), &_grpc_message); + } +} + +H2UnsentResponse* H2UnsentResponse::New(Controller* c, int stream_id, bool is_grpc) { + const HttpHeader* const h = &c->http_response(); + const CommonStrings* const common = get_common_strings(); + const bool need_content_type = !h->content_type().empty(); + const size_t maxsize = 1 + + (size_t)need_content_type; + const size_t memsize = offsetof(H2UnsentResponse, _list) + + sizeof(HPacker::Header) * maxsize; + H2UnsentResponse* msg = new (malloc(memsize)) H2UnsentResponse(c, stream_id, is_grpc); + // :status + if (h->status_code() == 200) { + msg->push(common->H2_STATUS, common->STATUS_200); + } else { + butil::string_printf(&msg->push(common->H2_STATUS), + "%d", h->status_code()); + } + if (need_content_type) { + msg->push(common->CONTENT_TYPE, h->content_type()); + } + return msg; +} + +void H2UnsentResponse::Destroy() { + for (size_t i = 0; i < _size; ++i) { + _list[i].~Header(); + } + this->~H2UnsentResponse(); + free(this); +} + +#if defined(BRPC_PROFILE_H2) +bvar::Adder g_append_response_time; +bvar::PerSecond > g_append_response_time_per_second( + "h2_append_response_second", &g_append_response_time); +#endif + +butil::Status +H2UnsentResponse::AppendAndDestroySelf(butil::IOBuf* out, Socket* socket) { +#if defined(BRPC_PROFILE_H2) + bvar::ScopedTimer > tm(g_append_response_time); +#endif + DestroyingPtr destroy_self(this); + if (socket == NULL) { + return butil::Status::OK(); + } + H2Context* ctx = static_cast(socket->parsing_context()); + + // flow control + // NOTE: Currently the stream context is definitely removed and updating + // window size is useless, however it's not true when progressive request + // is supported. + // TODO(zhujiashun): Instead of just returning error to client, a better + // solution to handle not enough window size is to wait until WINDOW_UPDATE + // is received, and then retry those failed response again. + if (!MinusWindowSize(&ctx->_remote_window_left, _data.size())) { + char rstbuf[FRAME_HEAD_SIZE + 4]; + SerializeFrameHead(rstbuf, 4, H2_FRAME_RST_STREAM, 0, _stream_id); + SaveUint32(rstbuf + FRAME_HEAD_SIZE, H2_FLOW_CONTROL_ERROR); + out->append(rstbuf, sizeof(rstbuf)); + return butil::Status::OK(); + } + + HPacker& hpacker = ctx->hpacker(); + butil::IOBufAppender appender; + HPackOptions options; + options.encode_name = FLAGS_h2_hpack_encode_name; + options.encode_value = FLAGS_h2_hpack_encode_value; + if (ctx->remote_settings().header_table_size == 0) { + options.index_policy = HPACK_NEVER_INDEX_HEADER; + } + + for (size_t i = 0; i < _size; ++i) { + hpacker.Encode(&appender, _list[i], options); + } + if (_http_response) { + for (HttpHeader::HeaderIterator it = _http_response->HeaderBegin(); + it != _http_response->HeaderEnd(); ++it) { + HPacker::Header header(it->first, it->second); + hpacker.Encode(&appender, header, options); + } + } + butil::IOBuf frag; + appender.move_to(frag); + + butil::IOBuf trailer_frag; + if (_is_grpc) { + HPacker::Header status_header("grpc-status", + butil::string_printf("%d", _grpc_status)); + hpacker.Encode(&appender, status_header, options); + if (!_grpc_message.empty()) { + HPacker::Header msg_header("grpc-message", _grpc_message); + hpacker.Encode(&appender, msg_header, options); + } + appender.move_to(trailer_frag); + } + + PackH2Message(out, frag, trailer_frag, _data, _stream_id, ctx); + return butil::Status::OK(); +} + +size_t H2UnsentResponse::EstimatedByteSize() { + size_t sz = 0; + for (size_t i = 0; i < _size; ++i) { + sz += _list[i].name.size() + _list[i].value.size() + 1; + } + if (_http_response) { + for (HttpHeader::HeaderIterator it = _http_response->HeaderBegin(); + it != _http_response->HeaderEnd(); ++it) { + sz += it->first.size() + it->second.size() + 1; + } + } + sz += _data.size(); + return sz; +} + +void H2UnsentResponse::Print(std::ostream& os) const { + os << "[ H2 RESPONSE @" << butil::my_ip() << " ]\n"; + for (size_t i = 0; i < _size; ++i) { + os << "> " << _list[i].name << " = " << _list[i].value << '\n'; + } + if (_http_response) { + for (HttpHeader::HeaderIterator it = _http_response->HeaderBegin(); + it != _http_response->HeaderEnd(); ++it) { + os << "> " << it->first << " = " << it->second << '\n'; + } + } + if (!_data.empty()) { + os << "> \n"; + } + os << butil::ToPrintable(_data, FLAGS_http_verbose_max_body_length); +} + +void PackH2Request(butil::IOBuf*, + SocketMessage** user_message, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor*, + Controller* cntl, + const butil::IOBuf&, + const Authenticator* auth) { + ControllerPrivateAccessor accessor(cntl); + + HttpHeader* header = &cntl->http_request(); + if (auth != NULL && header->GetHeader("Authorization") == NULL) { + std::string auth_data; + if (auth->GenerateCredential(&auth_data) != 0) { + return cntl->SetFailed(EREQUEST, "Fail to GenerateCredential"); + } + header->SetHeader("Authorization", auth_data); + } + + H2UnsentRequest* h2_req = dynamic_cast(accessor.get_stream_user_data()); + CHECK(h2_req); + h2_req->AddRefManually(); // add ref for AppendAndDestroySelf + h2_req->_sctx->set_correlation_id(correlation_id); + *user_message = h2_req; + + if (FLAGS_http_verbose) { + LOG(INFO) << '\n' << *h2_req; + } +} + +static bool IsH2SocketValid(Socket* s) { + H2Context* c = static_cast(s->parsing_context()); + return (c == NULL || !c->RunOutStreams()); +} + +StreamUserData* H2GlobalStreamCreator::OnCreatingStream( + SocketUniquePtr* inout, Controller* cntl) { + if ((*inout)->GetAgentSocket(inout, IsH2SocketValid) != 0) { + cntl->SetFailed(EINTERNAL, "Fail to create agent socket"); + return NULL; + } + + H2UnsentRequest* h2_req = H2UnsentRequest::New(cntl); + if (!h2_req) { + cntl->SetFailed(ENOMEM, "Fail to create H2UnsentRequest"); + return NULL; + } + return h2_req; +} + +void H2GlobalStreamCreator::DestroyStreamCreator(Controller* cntl) { + // H2GlobalStreamCreator is a global singleton value. + // Don't delete it in this function. +} + +StreamCreator* get_h2_global_stream_creator() { + return butil::get_leaky_singleton(); +} + +} // namespace policy + +} // namespace brpc diff --git a/src/brpc/policy/http2_rpc_protocol.h b/src/brpc/policy/http2_rpc_protocol.h new file mode 100644 index 0000000..b4422ee --- /dev/null +++ b/src/brpc/policy/http2_rpc_protocol.h @@ -0,0 +1,426 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BAIDU_RPC_POLICY_HTTP2_RPC_PROTOCOL_H +#define BAIDU_RPC_POLICY_HTTP2_RPC_PROTOCOL_H + +#include "brpc/policy/http_rpc_protocol.h" // HttpContext +#include "brpc/input_message_base.h" +#include "brpc/protocol.h" +#include "brpc/details/hpack.h" +#include "brpc/stream_creator.h" +#include "brpc/controller.h" + +#ifndef NDEBUG +#include "bvar/bvar.h" +#endif + +namespace brpc { +namespace policy { + +class H2StreamContext; + +class H2ParseResult { +public: + explicit H2ParseResult(H2Error err, int stream_id) + : _msg(NULL), _err(err), _stream_id(stream_id) {} + explicit H2ParseResult(H2StreamContext* msg) + : _msg(msg), _err(H2_NO_ERROR), _stream_id(0) {} + + // Return H2_NO_ERROR when the result is successful. + H2Error error() const { return _err; } + const char* error_str() const { return H2ErrorToString(_err); } + bool is_ok() const { return error() == H2_NO_ERROR; } + int stream_id() const { return _stream_id; } + + // definitely NULL when result is failed. + H2StreamContext* message() const { return _msg; } + +private: + H2StreamContext* _msg; + H2Error _err; + int _stream_id; +}; + +inline H2ParseResult MakeH2Error(H2Error err, int stream_id) +{ return H2ParseResult(err, stream_id); } +inline H2ParseResult MakeH2Error(H2Error err) +{ return H2ParseResult(err, 0); } +inline H2ParseResult MakeH2Message(H2StreamContext* msg) +{ return H2ParseResult(msg); } + +class H2Context; + +enum H2FrameType { + H2_FRAME_DATA = 0x0, + H2_FRAME_HEADERS = 0x1, + H2_FRAME_PRIORITY = 0x2, + H2_FRAME_RST_STREAM = 0x3, + H2_FRAME_SETTINGS = 0x4, + H2_FRAME_PUSH_PROMISE = 0x5, + H2_FRAME_PING = 0x6, + H2_FRAME_GOAWAY = 0x7, + H2_FRAME_WINDOW_UPDATE = 0x8, + H2_FRAME_CONTINUATION = 0x9, + // ============================ + H2_FRAME_TYPE_MAX = 0x9 +}; + +// https://tools.ietf.org/html/rfc7540#section-4.1 +struct H2FrameHead { + // The length of the frame payload expressed as an unsigned 24-bit integer. + // Values greater than H2Settings.max_frame_size MUST NOT be sent + uint32_t payload_size; + + // The 8-bit type of the frame. The frame type determines the format and + // semantics of the frame. Implementations MUST ignore and discard any + // frame that has a type that is unknown. + H2FrameType type; + + // An 8-bit field reserved for boolean flags specific to the frame type. + // Flags are assigned semantics specific to the indicated frame type. + // Flags that have no defined semantics for a particular frame type + // MUST be ignored and MUST be left unset (0x0) when sending. + uint8_t flags; + + // A stream identifier (see Section 5.1.1) expressed as an unsigned 31-bit + // integer. The value 0x0 is reserved for frames that are associated with + // the connection as a whole as opposed to an individual stream. + int stream_id; +}; + +enum H2StreamState { + H2_STREAM_IDLE = 0, + H2_STREAM_RESERVED_LOCAL, + H2_STREAM_RESERVED_REMOTE, + H2_STREAM_OPEN, + H2_STREAM_HALF_CLOSED_LOCAL, + H2_STREAM_HALF_CLOSED_REMOTE, + H2_STREAM_CLOSED, +}; +const char* H2StreamState2Str(H2StreamState); + +#ifndef NDEBUG +struct H2Bvars { + bvar::Adder h2_unsent_request_count; + bvar::Adder h2_stream_context_count; + + H2Bvars() + : h2_unsent_request_count("h2_unsent_request_count") + , h2_stream_context_count("h2_stream_context_count") { + } +}; +inline H2Bvars* get_h2_bvars() { + return butil::get_leaky_singleton(); +} +#endif + +class H2UnsentRequest : public SocketMessage, public StreamUserData { +friend void PackH2Request(butil::IOBuf*, SocketMessage**, + uint64_t, const google::protobuf::MethodDescriptor*, + Controller*, const butil::IOBuf&, const Authenticator*); +public: + static H2UnsentRequest* New(Controller* c); + void Print(std::ostream& os) const; + + int AddRefManually() + { return _nref.fetch_add(1, butil::memory_order_relaxed); } + + void RemoveRefManually() { + if (_nref.fetch_sub(1, butil::memory_order_release) == 1) { + butil::atomic_thread_fence(butil::memory_order_acquire); + Destroy(); + } + } + + // @SocketMessage + butil::Status AppendAndDestroySelf(butil::IOBuf* out, Socket*) override; + size_t EstimatedByteSize() override; + + // @StreamUserData + void DestroyStreamUserData(SocketUniquePtr& sending_sock, + Controller* cntl, + int error_code, + bool end_of_rpc) override; + +private: + std::string& push(const std::string& name) + { return (new (&_list[_size++]) HPacker::Header(name))->value; } + + void push(const std::string& name, const std::string& value) + { new (&_list[_size++]) HPacker::Header(name, value); } + + H2UnsentRequest(Controller* c) + : _nref(1) + , _size(0) + , _stream_id(0) + , _cntl(c) { +#ifndef NDEBUG + get_h2_bvars()->h2_unsent_request_count << 1; +#endif + } + ~H2UnsentRequest() { +#ifndef NDEBUG + get_h2_bvars()->h2_unsent_request_count << -1; +#endif + } + H2UnsentRequest(const H2UnsentRequest&); + void operator=(const H2UnsentRequest&); + void Destroy(); + +private: + butil::atomic _nref; + uint32_t _size; + int _stream_id; + mutable butil::Mutex _mutex; + Controller* _cntl; + std::unique_ptr _sctx; + HPacker::Header _list[0]; +}; + +class H2UnsentResponse : public SocketMessage { +public: + static H2UnsentResponse* New(Controller* c, int stream_id, bool is_grpc); + void Destroy(); + void Print(std::ostream& os) const; + // @SocketMessage + butil::Status AppendAndDestroySelf(butil::IOBuf* out, Socket*) override; + size_t EstimatedByteSize() override; + +private: + std::string& push(const std::string& name) + { return (new (&_list[_size++]) HPacker::Header(name))->value; } + + void push(const std::string& name, const std::string& value) + { new (&_list[_size++]) HPacker::Header(name, value); } + + H2UnsentResponse(Controller* c, int stream_id, bool is_grpc); + ~H2UnsentResponse() {} + H2UnsentResponse(const H2UnsentResponse&); + void operator=(const H2UnsentResponse&); + +private: + uint32_t _size; + uint32_t _stream_id; + std::unique_ptr _http_response; + butil::IOBuf _data; + bool _is_grpc; + GrpcStatus _grpc_status; + std::string _grpc_message; + HPacker::Header _list[0]; +}; + +// Used in http_rpc_protocol.cpp +class H2StreamContext : public HttpContext { +public: + H2StreamContext(bool read_body_progressively); + ~H2StreamContext(); + void Init(H2Context* conn_ctx, int stream_id); + + // Decode headers in HPACK from *it and set into this->header(). The input + // does not need to complete. + // Returns 0 on success, -1 otherwise. + int ConsumeHeaders(butil::IOBufBytesIterator& it); + H2ParseResult OnEndStream(); + + H2ParseResult OnData(butil::IOBufBytesIterator&, const H2FrameHead&, + uint32_t frag_size, uint8_t pad_length); + H2ParseResult OnHeaders(butil::IOBufBytesIterator&, const H2FrameHead&, + uint32_t frag_size, uint8_t pad_length); + H2ParseResult OnContinuation(butil::IOBufBytesIterator&, const H2FrameHead&); + H2ParseResult OnResetStream(H2Error h2_error, const H2FrameHead&); + + uint64_t correlation_id() const { return _correlation_id; } + void set_correlation_id(uint64_t cid) { _correlation_id = cid; } + + size_t parsed_length() const { return this->_parsed_length; } + int stream_id() const { return _stream_id; } + + int64_t ReleaseDeferredWindowUpdate() { + if (_deferred_window_update.load(butil::memory_order_relaxed) == 0) { + return 0; + } + return _deferred_window_update.exchange(0, butil::memory_order_relaxed); + } + + bool ConsumeWindowSize(int64_t size); + +#if defined(BRPC_H2_STREAM_STATE) + H2StreamState state() const { return _state; } + void SetState(H2StreamState state); +#endif + +friend class H2Context; + H2Context* _conn_ctx; +#if defined(BRPC_H2_STREAM_STATE) + H2StreamState _state; +#endif + int _stream_id; + bool _stream_ended; + butil::atomic _remote_window_left; + butil::atomic _deferred_window_update; + uint64_t _correlation_id; + butil::IOBuf _remaining_header_fragment; +}; + +StreamCreator* get_h2_global_stream_creator(); + +ParseResult ParseH2Message(butil::IOBuf *source, Socket *socket, + bool read_eof, const void *arg); +void PackH2Request(butil::IOBuf* buf, + SocketMessage** user_message_out, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* controller, + const butil::IOBuf& request, + const Authenticator* auth); + +class H2GlobalStreamCreator : public StreamCreator { +protected: + StreamUserData* OnCreatingStream(SocketUniquePtr* inout, Controller* cntl) override; + void DestroyStreamCreator(Controller* cntl) override; +}; + +enum H2ConnectionState { + H2_CONNECTION_UNINITIALIZED, + H2_CONNECTION_READY, + H2_CONNECTION_GOAWAY, +}; + +void SerializeFrameHead(void* out_buf, + uint32_t payload_size, H2FrameType type, + uint8_t flags, uint32_t stream_id); + +size_t SerializeH2Settings(const H2Settings& in, void* out); + +const size_t FRAME_HEAD_SIZE = 9; + +// Contexts of a http2 connection +class H2Context : public Destroyable, public Describable { +public: + typedef H2ParseResult (H2Context::*FrameHandler)( + butil::IOBufBytesIterator&, const H2FrameHead&); + + // main_socket: the socket owns this object as parsing_context + // server: NULL means client-side + H2Context(Socket* main_socket, const Server* server); + ~H2Context(); + // Must be called before usage. + int Init(); + + H2ConnectionState state() const { return _conn_state; } + ParseResult Consume(butil::IOBufBytesIterator& it, Socket*); + + void ClearAbandonedStreams(); + void AddAbandonedStream(uint32_t stream_id); + + //@Destroyable + void Destroy() override { delete this; } + + int AllocateClientStreamId(); + bool RunOutStreams() const; + // Try to map stream_id to ctx if stream_id does not exist before + // Returns 0 on success, -1 on exist, 1 on goaway. + int TryToInsertStream(int stream_id, H2StreamContext* ctx); + size_t VolatilePendingStreamSize() const { return _pending_streams.size(); } + + HPacker& hpacker() { return _hpacker; } + const H2Settings& remote_settings() const { return _remote_settings; } + const H2Settings& local_settings() const { return _local_settings; } + + bool is_client_side() const { return _socket->CreatedByConnect(); } + bool is_server_side() const { return !is_client_side(); } + + void Describe(std::ostream& os, const DescribeOptions&) const override; + + void DeferWindowUpdate(int64_t); + int64_t ReleaseDeferredWindowUpdate(); + +private: +friend class H2StreamContext; +friend class H2UnsentRequest; +friend class H2UnsentResponse; +friend void InitFrameHandlers(); + + ParseResult ConsumeFrameHead(butil::IOBufBytesIterator&, H2FrameHead*); + + H2ParseResult OnData(butil::IOBufBytesIterator&, const H2FrameHead&); + H2ParseResult OnHeaders(butil::IOBufBytesIterator&, const H2FrameHead&); + H2ParseResult OnPriority(butil::IOBufBytesIterator&, const H2FrameHead&); + H2ParseResult OnResetStream(butil::IOBufBytesIterator&, const H2FrameHead&); + H2ParseResult OnSettings(butil::IOBufBytesIterator&, const H2FrameHead&); + H2ParseResult OnPushPromise(butil::IOBufBytesIterator&, const H2FrameHead&); + H2ParseResult OnPing(butil::IOBufBytesIterator&, const H2FrameHead&); + H2ParseResult OnGoAway(butil::IOBufBytesIterator&, const H2FrameHead&); + H2ParseResult OnWindowUpdate(butil::IOBufBytesIterator&, const H2FrameHead&); + H2ParseResult OnContinuation(butil::IOBufBytesIterator&, const H2FrameHead&); + + H2StreamContext* RemoveStreamAndDeferWU(int stream_id); + void RemoveGoAwayStreams(int goaway_stream_id, std::vector* out_streams); + + H2StreamContext* FindStream(int stream_id); + + // True if the connection is established by client, otherwise it's + // accepted by server. + Socket* _socket; + butil::atomic _remote_window_left; + H2ConnectionState _conn_state; + int _last_received_stream_id; + uint32_t _last_sent_stream_id; + int _goaway_stream_id; + H2Settings _remote_settings; + bool _remote_settings_received; + H2Settings _local_settings; + H2Settings _unack_local_settings; + HPacker _hpacker; + mutable butil::Mutex _abandoned_streams_mutex; + std::vector _abandoned_streams; + typedef butil::FlatMap StreamMap; + mutable butil::Mutex _stream_mutex; + StreamMap _pending_streams; + butil::atomic _deferred_window_update; +}; + +inline int H2Context::AllocateClientStreamId() { + if (RunOutStreams()) { + LOG(WARNING) << "Fail to allocate new client stream, _last_sent_stream_id=" + << _last_sent_stream_id; + return -1; + } + const int id = _last_sent_stream_id; + _last_sent_stream_id += 2; + return id; +} + +inline bool H2Context::RunOutStreams() const { + return (_last_sent_stream_id > 0x7FFFFFFF); +} + +inline std::ostream& operator<<(std::ostream& os, const H2UnsentRequest& req) { + req.Print(os); + return os; +} +inline std::ostream& operator<<(std::ostream& os, const H2UnsentResponse& res) { + res.Print(os); + return os; +} + +} // namespace policy +} // namespace brpc + +#endif // BAIDU_RPC_POLICY_HTTP2_RPC_PROTOCOL_H diff --git a/src/brpc/policy/http_rpc_protocol.cpp b/src/brpc/policy/http_rpc_protocol.cpp new file mode 100644 index 0000000..8cbe069 --- /dev/null +++ b/src/brpc/policy/http_rpc_protocol.cpp @@ -0,0 +1,1818 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // MethodDescriptor +#include +#include +#include + +#include "brpc/policy/http_rpc_protocol.h" + +#include "butil/string_printf.h" +#include "butil/string_splitter.h" // StringMultiSplitter +#include "butil/strings/string_util.h" +#include "butil/sys_byteorder.h" +#include "butil/time.h" +#include "butil/unique_ptr.h" // std::unique_ptr + +#include "json2pb/pb_to_json.h" // ProtoMessageToJson +#include "json2pb/json_to_pb.h" // JsonToProtoMessage +#include "brpc/compress.h" +#include "brpc/errno.pb.h" // ENOSERVICE, ENOMETHOD +#include "brpc/controller.h" // Controller +#include "brpc/server.h" // Server +#include "brpc/details/server_private_accessor.h" +#include "brpc/span.h" +#include "brpc/socket.h" // Socket +#include "brpc/rpc_dump.h" // SampledRequest +#include "brpc/http_status_code.h" // HTTP_STATUS_* +#include "brpc/details/controller_private_accessor.h" +#include "brpc/builtin/index_service.h" // IndexService +#include "brpc/policy/gzip_compress.h" +#include "brpc/policy/http2_rpc_protocol.h" +#include "brpc/details/usercode_backup_pool.h" +#include "brpc/grpc.h" + +extern "C" { +void bthread_assign_data(void* data); +} + +namespace brpc { + +int is_failed_after_queries(const http_parser* parser); +int is_failed_after_http_version(const http_parser* parser); +DECLARE_bool(http_verbose); +DECLARE_int32(http_verbose_max_body_length); +// Defined in grpc.cpp +int64_t ConvertGrpcTimeoutToUS(const std::string* grpc_timeout); + +namespace policy { + +DEFINE_int32(http_max_error_length, 2048, "Max printed length of a http error"); + +DEFINE_int32(http_body_compress_threshold, 512, "Not compress http body when " + "it's less than so many bytes."); + +DEFINE_string(http_header_of_user_ip, "", "http requests sent by proxies may " + "set the client ip in http headers. When this flag is non-empty, " + "brpc will read ip:port from the specified header for " + "authorization and set Controller::remote_side(). Currently, " + "support IPv4 address only."); + +DEFINE_bool(pb_enum_as_number, false, + "[Not recommended] Convert enums in " + "protobuf to json as numbers, affecting both client-side and " + "server-side"); + +DEFINE_string(request_id_header, "x-request-id", "The http header to mark a session"); + +DEFINE_bool(use_http_error_code, false, "Whether set the x-bd-error-code header " + "of http response to brpc error code"); + +// Read user address from the header specified by -http_header_of_user_ip +static bool GetUserAddressFromHeaderImpl(const HttpHeader& headers, + butil::EndPoint* user_addr) { + const std::string* user_addr_str = + headers.GetHeader(FLAGS_http_header_of_user_ip); + if (user_addr_str == NULL) { + return false; + } + //TODO add protocols other than IPv4 supports. + if (user_addr_str->find(':') == std::string::npos) { + if (butil::str2ip(user_addr_str->c_str(), &user_addr->ip) != 0) { + LOG(WARNING) << "Fail to parse ip from " << *user_addr_str; + return false; + } + user_addr->port = 0; + } else { + if (butil::str2endpoint(user_addr_str->c_str(), user_addr) != 0) { + LOG(WARNING) << "Fail to parse ip:port from " << *user_addr_str; + return false; + } + } + return true; +} + +inline bool GetUserAddressFromHeader(const HttpHeader& headers, + butil::EndPoint* user_addr) { + if (FLAGS_http_header_of_user_ip.empty()) { + return false; + } + return GetUserAddressFromHeaderImpl(headers, user_addr); +} + +CommonStrings::CommonStrings() + : ACCEPT("accept") + , DEFAULT_ACCEPT("*/*") + , USER_AGENT("user-agent") + , DEFAULT_USER_AGENT("brpc/1.0 curl/7.0") + , CONTENT_TYPE("content-type") + , CONTENT_TYPE_TEXT("text/plain") + , CONTENT_TYPE_JSON("application/json") + , CONTENT_TYPE_PROTO("application/proto") + , CONTENT_TYPE_SPRING_PROTO("application/x-protobuf") + , ERROR_CODE("x-bd-error-code") + , AUTHORIZATION("authorization") + , ACCEPT_ENCODING("accept-encoding") + , CONTENT_ENCODING("content-encoding") + , CONTENT_LENGTH("content_length") + , EXPECT("expect") + , CONTINUE_100("100-continue") + , GZIP("gzip") + , CONNECTION("connection") + , KEEP_ALIVE("keep-alive") + , CLOSE("close") + , LOG_ID("log-id") + , DEFAULT_METHOD("default_method") + , NO_METHOD("no_method") + , H2_SCHEME(":scheme") + , H2_SCHEME_HTTP("http") + , H2_SCHEME_HTTPS("https") + , H2_AUTHORITY(":authority") + , H2_PATH(":path") + , H2_STATUS(":status") + , STATUS_200("200") + , H2_METHOD(":method") + , METHOD_GET("GET") + , METHOD_POST("POST") + , TE("te") + , TRAILERS("trailers") + , GRPC_ENCODING("grpc-encoding") + , GRPC_ACCEPT_ENCODING("grpc-accept-encoding") + , GRPC_ACCEPT_ENCODING_VALUE("identity,gzip") + , GRPC_STATUS("grpc-status") + , GRPC_MESSAGE("grpc-message") + , GRPC_TIMEOUT("grpc-timeout") + , DEFAULT_PATH("/") +{} + +static CommonStrings* common = NULL; +static pthread_once_t g_common_strings_once = PTHREAD_ONCE_INIT; +static void CreateCommonStrings() { + common = new CommonStrings; +} +// Called in global.cpp +int InitCommonStrings() { + return pthread_once(&g_common_strings_once, CreateCommonStrings); +} +static const int ALLOW_UNUSED force_creation_of_common = InitCommonStrings(); +const CommonStrings* get_common_strings() { return common; } + +HttpContentType ParseContentType(butil::StringPiece ct, bool* is_grpc_ct) { + // According to http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7 + // media-type = type "/" subtype *( ";" parameter ) + // type = token + // subtype = token + + const butil::StringPiece prefix = "application/"; + if (!ct.starts_with(prefix)) { + return HTTP_CONTENT_OTHERS; + } + ct.remove_prefix(prefix.size()); + + if (ct.starts_with("grpc")) { + if (ct.size() == (size_t)4 || ct[4] == ';') { + if (is_grpc_ct) { + *is_grpc_ct = true; + } + // assume that the default content type for grpc is proto. + return HTTP_CONTENT_PROTO; + } else if (ct[4] == '+') { + ct.remove_prefix(5); + if (is_grpc_ct) { + *is_grpc_ct = true; + } + } + // else don't change ct. Note that "grpcfoo" is a valid but non-grpc + // content-type in the sense of format. + } + + HttpContentType type = HTTP_CONTENT_OTHERS; + if (ct.starts_with("json")) { + type = HTTP_CONTENT_JSON; + ct.remove_prefix(4); + } else if (ct.starts_with("proto-json")) { + type = HTTP_CONTENT_PROTO_JSON; + ct.remove_prefix(10); + } else if (ct.starts_with("proto-text")) { + type = HTTP_CONTENT_PROTO_TEXT; + ct.remove_prefix(10); + } else if (ct.starts_with("proto")) { + type = HTTP_CONTENT_PROTO; + ct.remove_prefix(5); + } else if (ct.starts_with("x-protobuf")) { + type = HTTP_CONTENT_PROTO; + ct.remove_prefix(10); + } else { + return HTTP_CONTENT_OTHERS; + } + return (ct.empty() || ct.front() == ';') ? type : HTTP_CONTENT_OTHERS; +} + +static void PrintMessage(const butil::IOBuf& inbuf, + bool request_or_response, + bool has_content) { + butil::IOBuf buf1 = inbuf; + butil::IOBuf buf2; + char str[48]; + if (request_or_response) { + snprintf(str, sizeof(str), "[ HTTP REQUEST @%s ]", butil::my_ip_cstr()); + } else { + snprintf(str, sizeof(str), "[ HTTP RESPONSE @%s ]", butil::my_ip_cstr()); + } + buf2.append(str); + size_t last_size; + do { + buf2.append("\r\n> "); + last_size = buf2.size(); + } while (buf1.cut_until(&buf2, "\r\n") == 0); + if (buf2.size() == last_size) { + buf2.pop_back(2); // remove "> " + } + if (!has_content) { + LOG(INFO) << '\n' << buf2 << buf1; + } else { + LOG(INFO) << '\n' << buf2 << butil::ToPrintableString(buf1, FLAGS_http_verbose_max_body_length); + } +} + +static void AddGrpcPrefix(butil::IOBuf* body, bool compressed) { + char buf[5]; + buf[0] = (compressed ? 1 : 0); + *(uint32_t*)(buf + 1) = butil::HostToNet32(body->size()); + butil::IOBuf tmp_buf; + tmp_buf.append(buf, sizeof(buf)); + tmp_buf.append(butil::IOBuf::Movable(*body)); + body->swap(tmp_buf); +} + +static bool RemoveGrpcPrefix(butil::IOBuf* body, bool* compressed) { + if (body->empty()) { + *compressed = false; + return true; + } + const size_t sz = body->size(); + if (sz < (size_t)5) { + return false; + } + char buf[5]; + body->cutn(buf, sizeof(buf)); + *compressed = buf[0]; + const size_t message_length = butil::NetToHost32(*(uint32_t*)(buf + 1)); + return (message_length + 5 == sz); +} + +static bool JsonToProtoMessage(const butil::IOBuf& body, + google::protobuf::Message* message, + Controller* cntl, int error_code) { + butil::IOBufAsZeroCopyInputStream wrapper(body); + json2pb::Json2PbOptions options; + options.base64_to_bytes = cntl->has_pb_bytes_to_base64(); + options.array_to_single_repeated = cntl->has_pb_single_repeated_to_array(); + std::string error; + bool ok = json2pb::JsonToProtoMessage(&wrapper, message, options, &error); + if (!ok) { + cntl->SetFailed(error_code, "Fail to parse http json body as %s: %s", + butil::EnsureString(message->GetDescriptor()->full_name()).c_str(), + error.c_str()); + } + return ok; +} + +static bool ProtoMessageToJson(const google::protobuf::Message& message, + butil::IOBufAsZeroCopyOutputStream* wrapper, + Controller* cntl, int error_code) { + json2pb::Pb2JsonOptions options; + options.bytes_to_base64 = cntl->has_pb_bytes_to_base64(); + options.jsonify_empty_array = cntl->has_pb_jsonify_empty_array(); + options.always_print_primitive_fields = cntl->has_always_print_primitive_fields(); + options.single_repeated_to_array = cntl->has_pb_single_repeated_to_array(); + options.enum_option = FLAGS_pb_enum_as_number + ? json2pb::OUTPUT_ENUM_BY_NUMBER + : json2pb::OUTPUT_ENUM_BY_NAME; + std::string error; + bool ok = json2pb::ProtoMessageToJson(message, wrapper, options, &error); + if (!ok) { + cntl->SetFailed(error_code, "Fail to convert %s to json: %s", + butil::EnsureString(message.GetDescriptor()->full_name()).c_str(), + error.c_str()); + } + return ok; +} + +static bool ProtoJsonToProtoMessage(const butil::IOBuf& body, + google::protobuf::Message* message, + Controller* cntl, int error_code) { + json2pb::ProtoJson2PbOptions options; + options.ignore_unknown_fields = true; + butil::IOBufAsZeroCopyInputStream wrapper(body); + std::string error; + bool ok = json2pb::ProtoJsonToProtoMessage(&wrapper, message, options, &error); + if (!ok) { + cntl->SetFailed(error_code, "Fail to parse http proto-json body as %s: %s", + butil::EnsureString(message->GetDescriptor()->full_name()).c_str(), + error.c_str()); + } + return ok; +} + +static bool ProtoMessageToProtoJson(const google::protobuf::Message& message, + butil::IOBufAsZeroCopyOutputStream* wrapper, + Controller* cntl, int error_code) { + json2pb::Pb2ProtoJsonOptions options; + AlwaysPrintPrimitiveFields(options) = cntl->has_always_print_primitive_fields(); + options.always_print_enums_as_ints = FLAGS_pb_enum_as_number; + std::string error; + bool ok = json2pb::ProtoMessageToProtoJson(message, wrapper, options, &error); + if (!ok) { + cntl->SetFailed(error_code, "Fail to convert %s to proto-json: %s", + butil::EnsureString(message.GetDescriptor()->full_name()).c_str(), error.c_str()); + } + return ok; +} + +void ProcessHttpResponse(InputMessageBase* msg) { + const int64_t start_parse_us = butil::cpuwide_time_us(); + DestroyingPtr imsg_guard(static_cast(msg)); + Socket* socket = imsg_guard->socket(); + uint64_t cid_value; + const bool is_http2 = imsg_guard->header().is_http2(); + if (is_http2) { + H2StreamContext* h2_sctx = static_cast(msg); + cid_value = h2_sctx->correlation_id(); + } else { + cid_value = socket->correlation_id(); + } + if (cid_value == 0) { + LOG(WARNING) << "Fail to find correlation_id from " << *socket; + return; + } + const bthread_id_t cid = { cid_value }; + Controller* cntl = NULL; + const int rc = bthread_id_lock(cid, (void**)&cntl); + if (rc != 0) { + LOG_IF(ERROR, rc != EINVAL && rc != EPERM) + << "Fail to lock correlation_id=" << cid << ": " << berror(rc); + return; + } + + ControllerPrivateAccessor accessor(cntl); + + if (auto span = accessor.span()) { + span->set_base_real_us(msg->base_real_us()); + span->set_received_us(msg->received_us()); + // TODO: changing when imsg_guard->read_body_progressively() is true + span->set_response_size(imsg_guard->parsed_length()); + span->set_start_parse_us(start_parse_us); + } + + HttpHeader* res_header = &cntl->http_response(); + res_header->Swap(imsg_guard->header()); + butil::IOBuf& res_body = imsg_guard->body(); + CHECK(cntl->response_attachment().empty()); + const int saved_error = cntl->ErrorCode(); + + bool is_grpc_ct = false; + const HttpContentType content_type = + ParseContentType(res_header->content_type(), &is_grpc_ct); + const bool is_grpc = (is_http2 && is_grpc_ct); + bool grpc_compressed = false; // only valid when is_grpc is true. + + do { + if (!is_http2) { + // If header has "Connection: close", close the connection. + const std::string* conn_cmd = res_header->GetHeader(common->CONNECTION); + if (conn_cmd != NULL && 0 == strcasecmp(conn_cmd->c_str(), "close")) { + // Server asked to close the connection. + if (imsg_guard->read_body_progressively()) { + // Close the socket when reading completes. + socket->read_will_be_progressive(CONNECTION_TYPE_SHORT); + } else { + socket->SetFailed(); + } + } + } else if (is_grpc) { + if (!RemoveGrpcPrefix(&res_body, &grpc_compressed)) { + cntl->SetFailed(ERESPONSE, "Invalid gRPC response"); + break; + } + const std::string* grpc_status = res_header->GetHeader(common->GRPC_STATUS); + if (grpc_status) { + // TODO: More strict parsing + GrpcStatus status = (GrpcStatus)strtol(grpc_status->data(), NULL, 10); + if (status != GRPC_OK) { + const std::string* grpc_message = + res_header->GetHeader(common->GRPC_MESSAGE); + if (grpc_message) { + std::string message_decoded; + PercentDecode(*grpc_message, &message_decoded); + cntl->SetFailed(GrpcStatusToErrorCode(status), "%s", + message_decoded.c_str()); + } else { + cntl->SetFailed(GrpcStatusToErrorCode(status), "%s", + GrpcStatusToString(status)); + } + break; + } + } + } + + if (imsg_guard->read_body_progressively()) { + // Set RPA if needed + accessor.set_readable_progressive_attachment(imsg_guard.get()); + const int sc = res_header->status_code(); + if (sc < 200 || sc >= 300) { + // Even if the body is for streaming purpose, a non-OK status + // code indicates that the body is probably the error text + // which is helpful for debugging. + // content may be binary data, so the size limit is a must. + std::string body_str; + res_body.copy_to( + &body_str, std::min((int)res_body.size(), + FLAGS_http_max_error_length)); + cntl->SetFailed(EHTTP, "HTTP/%d.%d %d %s: %.*s", + res_header->major_version(), + res_header->minor_version(), + static_cast(res_header->status_code()), + res_header->reason_phrase(), + (int)body_str.size(), body_str.c_str()); + } else if (cntl->response() != NULL && + cntl->response()->GetDescriptor()->field_count() != 0) { + cntl->SetFailed(ERESPONSE, "A protobuf response can't be parsed" + " from progressively-read HTTP body"); + } + break; + } + + // Fail RPC if status code is an error in http sense. + // ErrorCode of RPC is unified to EHTTP. + const int sc = res_header->status_code(); + if (sc < 200 || sc >= 300) { + std::string err = butil::string_printf( + "HTTP/%d.%d %d %s", + res_header->major_version(), + res_header->minor_version(), + static_cast(res_header->status_code()), + res_header->reason_phrase()); + if (!res_body.empty()) { + // Use content as error text if it's present. Notice that + // content may be binary data, so the size limit is a must. + err.append(": "); + res_body.append_to( + &err, std::min((int)res_body.size(), + FLAGS_http_max_error_length)); + } + // If server return brpc error code by x-bd-error-code, + // set the returned error code to controller. Otherwise, + // set EHTTP to controller uniformly. + const std::string* error_code_ptr = res_header->GetHeader(common->ERROR_CODE); + int error_code = error_code_ptr ? strtol(error_code_ptr->data(), NULL, 10) : 0; + if (FLAGS_use_http_error_code && error_code != 0) { + cntl->SetFailed(error_code, "%s", err.c_str()); + } else { + cntl->SetFailed(EHTTP, "%s", err.c_str()); + } + if (cntl->response() == NULL || + cntl->response()->GetDescriptor()->field_count() == 0) { + // A http call. Http users may need the body(containing a html, + // json etc) even if the http call was failed. This is different + // from protobuf services where responses are undefined when RPC + // was failed. + cntl->response_attachment().swap(res_body); + } + break; + } + if (cntl->response() == NULL || + cntl->response()->GetDescriptor()->field_count() == 0) { + // a http call, content is the "real response". + cntl->response_attachment().swap(res_body); + break; + } + + const std::string* encoding = NULL; + if (is_grpc) { + if (grpc_compressed) { + encoding = res_header->GetHeader(common->GRPC_ENCODING); + if (encoding == NULL) { + cntl->SetFailed(ERESPONSE, "Fail to find header `grpc-encoding' " + "in compressed gRPC response"); + break; + } + } + } else { + encoding = res_header->GetHeader(common->CONTENT_ENCODING); + } + if (encoding != NULL && *encoding == common->GZIP) { + TRACEPRINTF("Decompressing response=%lu", + (unsigned long)res_body.size()); + butil::IOBuf uncompressed; + if (!policy::GzipDecompress(res_body, &uncompressed)) { + cntl->SetFailed(ERESPONSE, "Fail to un-gzip response body"); + break; + } + res_body.swap(uncompressed); + } + if (content_type == HTTP_CONTENT_PROTO) { + if (!ParsePbFromIOBuf(cntl->response(), res_body)) { + cntl->SetFailed(ERESPONSE, "Fail to parse content as %s", + butil::EnsureString(cntl->response()->GetDescriptor()->full_name()).c_str()); + break; + } + } else if (content_type == HTTP_CONTENT_PROTO_TEXT) { + if (!ParsePbTextFromIOBuf(cntl->response(), res_body)) { + cntl->SetFailed(ERESPONSE, "Fail to parse proto-text content as %s", + butil::EnsureString(cntl->response()->GetDescriptor()->full_name()).c_str()); + break; + } + } else if (content_type == HTTP_CONTENT_JSON) { + // Message body is json. + if (!JsonToProtoMessage(res_body, cntl->response(), cntl, ERESPONSE)) { + break; + } + } else if (content_type == HTTP_CONTENT_PROTO_JSON) { + // Message body is json. + if (!ProtoJsonToProtoMessage(res_body, cntl->response(), cntl, ERESPONSE)) { + break; + } + } else { + cntl->SetFailed(ERESPONSE, + "Unknown content-type=%s when response is not NULL", + res_header->content_type().c_str()); + break; + } + } while (0); + + // Unlocks correlation_id inside. Revert controller's + // error code if it version check of `cid' fails + imsg_guard.reset(); + accessor.OnResponse(cid, saved_error); +} + +void SerializeHttpRequest(butil::IOBuf* /*not used*/, + Controller* cntl, + const google::protobuf::Message* pbreq) { + HttpHeader& hreq = cntl->http_request(); + const bool is_http2 = (cntl->request_protocol() == PROTOCOL_H2); + bool is_grpc = false; + ControllerPrivateAccessor accessor(cntl); + if (!accessor.protocol_param().empty() && hreq.content_type().empty()) { + const std::string& param = accessor.protocol_param(); + if (param.find('/') == std::string::npos) { + std::string& s = hreq.mutable_content_type(); + s.reserve(12 + param.size()); + s.append("application/"); + s.append(param); + } else { + hreq.set_content_type(param); + } + } + if (pbreq != NULL) { + // If request is not NULL, message body will be serialized proto/json, + if (!pbreq->IsInitialized()) { + return cntl->SetFailed( + EREQUEST, "Missing required fields in request: %s", + pbreq->InitializationErrorString().c_str()); + } + if (!cntl->request_attachment().empty()) { + return cntl->SetFailed(EREQUEST, "request_attachment must be empty " + "when request is not NULL"); + } + HttpContentType content_type = HTTP_CONTENT_OTHERS; + if (hreq.content_type().empty()) { + // Set content-type if user did not. + // Note that http1.x defaults to json and h2 defaults to pb. + if (is_http2) { + content_type = HTTP_CONTENT_PROTO; + hreq.set_content_type(common->CONTENT_TYPE_PROTO); + } else { + content_type = HTTP_CONTENT_JSON; + hreq.set_content_type(common->CONTENT_TYPE_JSON); + } + } else { + bool is_grpc_ct = false; + content_type = ParseContentType(hreq.content_type(), &is_grpc_ct); + is_grpc = (is_http2 && is_grpc_ct); + } + + butil::IOBufAsZeroCopyOutputStream wrapper(&cntl->request_attachment()); + if (content_type == HTTP_CONTENT_PROTO) { + // Serialize content as protobuf + if (!pbreq->SerializeToZeroCopyStream(&wrapper)) { + cntl->request_attachment().clear(); + return cntl->SetFailed(EREQUEST, "Fail to serialize %s", + butil::EnsureString(pbreq->GetTypeName()).c_str()); + } + } else if (content_type == HTTP_CONTENT_PROTO_TEXT) { + if (!google::protobuf::TextFormat::Print(*pbreq, &wrapper)) { + cntl->request_attachment().clear(); + return cntl->SetFailed(EREQUEST, "Fail to print %s as proto-text", + butil::EnsureString(pbreq->GetTypeName()).c_str()); + } + } else if (content_type == HTTP_CONTENT_PROTO_JSON) { + if (!ProtoMessageToProtoJson(*pbreq, &wrapper, cntl, EREQUEST)) { + cntl->request_attachment().clear(); + return; + } + } else if (content_type == HTTP_CONTENT_JSON) { + if (!ProtoMessageToJson(*pbreq, &wrapper, cntl, EREQUEST)) { + cntl->request_attachment().clear(); + return; + } + } else { + return cntl->SetFailed( + EREQUEST, "Cannot serialize pb request according to content_type=%s", + hreq.content_type().c_str()); + } + } else { + // Use request_attachment. + // TODO: Checking required fields of http header. + } + // Make RPC fail if uri() is not OK (previous SetHttpURL/operator= failed) + if (!hreq.uri().status().ok()) { + return cntl->SetFailed(EREQUEST, "%s", + hreq.uri().status().error_cstr()); + } + bool grpc_compressed = false; + if (cntl->request_compress_type() != COMPRESS_TYPE_NONE) { + if (cntl->request_compress_type() != COMPRESS_TYPE_GZIP) { + return cntl->SetFailed(EREQUEST, "http does not support %s", + CompressTypeToCStr(cntl->request_compress_type())); + } + const size_t request_size = cntl->request_attachment().size(); + if (request_size >= (size_t)FLAGS_http_body_compress_threshold) { + TRACEPRINTF("Compressing request=%lu", (unsigned long)request_size); + butil::IOBuf compressed; + if (GzipCompress(cntl->request_attachment(), &compressed, NULL)) { + cntl->request_attachment().swap(compressed); + if (is_grpc) { + grpc_compressed = true; + hreq.SetHeader(common->GRPC_ENCODING, common->GZIP); + } else { + hreq.SetHeader(common->CONTENT_ENCODING, common->GZIP); + } + } else { + cntl->SetFailed("Fail to gzip the request body, skip compressing"); + } + } + } + + // Fill log-id if user set it. + if (cntl->has_log_id()) { + hreq.SetHeader(common->LOG_ID, + butil::string_printf("%llu", (unsigned long long)cntl->log_id())); + } + if (!cntl->request_id().empty()) { + hreq.SetHeader(FLAGS_request_id_header, cntl->request_id()); + } + + if (!is_http2) { + // HTTP before 1.1 needs to set keep-alive explicitly. + if (hreq.before_http_1_1() && + cntl->connection_type() != CONNECTION_TYPE_SHORT && + hreq.GetHeader(common->CONNECTION) == NULL) { + hreq.SetHeader(common->CONNECTION, common->KEEP_ALIVE); + } + } else { + cntl->set_stream_creator(get_h2_global_stream_creator()); + if (is_grpc) { + /* + hreq.SetHeader(common->GRPC_ACCEPT_ENCODING, + common->GRPC_ACCEPT_ENCODING_VALUE); + */ + // TODO: do we need this? + hreq.SetHeader(common->TE, common->TRAILERS); + if (cntl->timeout_ms() >= 0) { + hreq.SetHeader(common->GRPC_TIMEOUT, + butil::string_printf("%" PRId64 "m", cntl->timeout_ms())); + } + // Append compressed and length before body + AddGrpcPrefix(&cntl->request_attachment(), grpc_compressed); + } + } + + // Set url to /ServiceName/MethodName when we're about to call protobuf + // services (indicated by non-NULL method). + const google::protobuf::MethodDescriptor* method = cntl->method(); + if (method != NULL) { + hreq.set_method(HTTP_METHOD_POST); + std::string path; + path.reserve(2 + method->service()->full_name().size() + + method->name().size()); + path.push_back('/'); + path.append(method->service()->full_name()); + path.push_back('/'); + path.append(method->name()); + hreq.uri().set_path(path); + } + + if (auto span = accessor.span()) { + hreq.SetHeader("x-bd-trace-id", butil::string_printf( + "%llu", (unsigned long long)span->trace_id())); + hreq.SetHeader("x-bd-span-id", butil::string_printf( + "%llu", (unsigned long long)span->span_id())); + hreq.SetHeader("x-bd-parent-span-id", butil::string_printf( + "%llu", (unsigned long long)span->parent_span_id())); + } +} + +void PackHttpRequest(butil::IOBuf* buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor*, + Controller* cntl, + const butil::IOBuf& /*unused*/, + const Authenticator* auth) { + if (cntl->connection_type() == CONNECTION_TYPE_SINGLE) { + return cntl->SetFailed(EREQUEST, "http can't work with CONNECTION_TYPE_SINGLE"); + } + ControllerPrivateAccessor accessor(cntl); + HttpHeader* header = &cntl->http_request(); + if (auth != NULL && header->GetHeader(common->AUTHORIZATION) == NULL) { + std::string auth_data; + if (auth->GenerateCredential(&auth_data) != 0) { + return cntl->SetFailed(EREQUEST, "Fail to GenerateCredential"); + } + header->SetHeader(common->AUTHORIZATION, auth_data); + } + + // Store `correlation_id' into Socket since http server + // may not echo back this field. But we send it anyway. + accessor.get_sending_socket()->set_correlation_id(correlation_id); + + // Store http request method into Socket since http response parser needs it, + // and skips response body if request method is HEAD. + accessor.get_sending_socket()->set_http_request_method(header->method()); + + MakeRawHttpRequest(buf, header, cntl->remote_side(), + &cntl->request_attachment()); + if (FLAGS_http_verbose) { + PrintMessage(*buf, true, true); + } +} + +inline bool SupportGzip(Controller* cntl) { + const std::string* encodings = + cntl->http_request().GetHeader(common->ACCEPT_ENCODING); + return (encodings && encodings->find(common->GZIP) != std::string::npos); +} + +class HttpResponseSender { +friend class HttpResponseSenderAsDone; +public: + HttpResponseSender() + : HttpResponseSender(NULL) {} + explicit HttpResponseSender(Controller* cntl/*own*/) + : _cntl(cntl) + , _messages(NULL) + , _method_status(NULL) + , _received_us(0) + , _h2_stream_id(-1) {} + + HttpResponseSender(HttpResponseSender&& s) noexcept + : _cntl(std::move(s._cntl)) + , _messages(s._messages) + , _method_status(s._method_status) + , _received_us(s._received_us) + , _h2_stream_id(s._h2_stream_id) { + s._messages = NULL; + s._method_status = NULL; + s._received_us = 0; + s._h2_stream_id = -1; + } + ~HttpResponseSender(); + + void set_messages(RpcPBMessages* messages) { _messages = messages; } + void set_method_status(MethodStatus* ms) { _method_status = ms; } + void set_received_us(int64_t t) { _received_us = t; } + void set_h2_stream_id(int id) { _h2_stream_id = id; } + +private: + std::unique_ptr _cntl; + RpcPBMessages* _messages; + MethodStatus* _method_status; + int64_t _received_us; + int _h2_stream_id; +}; + +class HttpResponseSenderAsDone : public google::protobuf::Closure { +public: + explicit HttpResponseSenderAsDone(HttpResponseSender* s) : _sender(std::move(*s)) {} + void Run() override { + if (NULL != _sender._messages) { + _sender._cntl->CallAfterRpcResp(_sender._messages->Request(), + _sender._messages->Response()); + } + delete this; + } + +private: + HttpResponseSender _sender; +}; + +HttpResponseSender::~HttpResponseSender() { + // Return messages to factory at the end. + BRPC_SCOPE_EXIT { + if (NULL != _messages) { + _cntl->server()->options().rpc_pb_message_factory->Return(_messages); + } + }; + Controller* cntl = _cntl.get(); + if (cntl == NULL) { + return; + } + ControllerPrivateAccessor accessor(cntl); + auto span = accessor.span(); + if (span) { + span->set_start_send_us(butil::cpuwide_time_us()); + } + ConcurrencyRemover concurrency_remover(_method_status, cntl, _received_us); + Socket* socket = accessor.get_sending_socket(); + const google::protobuf::Message* res = NULL != _messages ? _messages->Response() : NULL; + + if (cntl->IsCloseConnection()) { + socket->SetFailed(); + return; + } + + const HttpHeader* req_header = &cntl->http_request(); + HttpHeader* res_header = &cntl->http_response(); + res_header->set_version(req_header->major_version(), + req_header->minor_version()); + + const std::string* content_type_str = &res_header->content_type(); + if (content_type_str->empty()) { + // Use request's content_type if response's is not set. + content_type_str = &req_header->content_type(); + res_header->set_content_type(*content_type_str); + } + // Notice that HTTP1 can have a header named `grpc-encoding' as well + // which should be treated as an user-defined header and ignored by + // the framework. + bool is_grpc_ct = false; + const HttpContentType content_type = ParseContentType(*content_type_str, &is_grpc_ct); + const bool is_http2 = req_header->is_http2(); + const bool is_grpc = (is_http2 && is_grpc_ct); + + // Convert response to json/proto if needed. + // Notice: Not check res->IsInitialized() which should be checked in the + // conversion function. + if (res != NULL && + cntl->response_attachment().empty() && + // ^ user did not fill the body yet. + res->GetDescriptor()->field_count() > 0 && + // ^ a pb service + !cntl->Failed()) { + // ^ pb response in failed RPC is undefined, no need to convert. + + butil::IOBufAsZeroCopyOutputStream wrapper(&cntl->response_attachment()); + if (content_type == HTTP_CONTENT_PROTO) { + if (!res->SerializeToZeroCopyStream(&wrapper)) { + cntl->SetFailed(ERESPONSE, "Fail to serialize %s", + butil::EnsureString(res->GetTypeName()).c_str()); + } + } else if (content_type == HTTP_CONTENT_PROTO_TEXT) { + if (!google::protobuf::TextFormat::Print(*res, &wrapper)) { + cntl->SetFailed(ERESPONSE, "Fail to print %s as proto-text", + butil::EnsureString(res->GetTypeName()).c_str()); + } + } else if (content_type == HTTP_CONTENT_PROTO_JSON) { + ProtoMessageToProtoJson(*res, &wrapper, cntl, ERESPONSE); + } else { + ProtoMessageToJson(*res, &wrapper, cntl, ERESPONSE); + } + } + + // In HTTP 0.9, the server always closes the connection after sending the + // response. The client must close its end of the connection after + // receiving the response. + // In HTTP 1.0, the server always closes the connection after sending the + // response UNLESS the client sent a Connection: keep-alive request header + // and the server sent a Connection: keep-alive response header. If no + // such response header exists, the client must close its end of the + // connection after receiving the response. + // In HTTP 1.1, the server does not close the connection after sending + // the response UNLESS the client sent a Connection: close request header, + // or the server sent a Connection: close response header. If such a + // response header exists, the client must close its end of the connection + // after receiving the response. + if (!is_http2) { + const std::string* res_conn = res_header->GetHeader(common->CONNECTION); + if (res_conn == NULL || strcasecmp(res_conn->c_str(), "close") != 0) { + const std::string* req_conn = + req_header->GetHeader(common->CONNECTION); + if (req_header->before_http_1_1()) { + if (req_conn != NULL && + strcasecmp(req_conn->c_str(), "keep-alive") == 0) { + res_header->SetHeader(common->CONNECTION, common->KEEP_ALIVE); + } + } else { + if (req_conn != NULL && + strcasecmp(req_conn->c_str(), "close") == 0) { + res_header->SetHeader(common->CONNECTION, common->CLOSE); + } + } + } // else user explicitly set Connection:close, clients of + // HTTP 1.1/1.0/0.9 should all close the connection. + } else if (is_grpc) { + // status code is always 200 according to grpc protocol + res_header->set_status_code(HTTP_STATUS_OK); + } + + bool grpc_compressed = false; + if (cntl->Failed()) { + if (!cntl->does_manage_http_body_on_error()) { + cntl->response_attachment().clear(); + } + if (!is_grpc) { + // Set status-code with default value(converted from error code) + // if user did not set it. + if (res_header->status_code() == HTTP_STATUS_OK) { + res_header->set_status_code(ErrorCodeToStatusCode(cntl->ErrorCode())); + } + // Fill ErrorCode into header + res_header->SetHeader(common->ERROR_CODE, + butil::string_printf("%d", cntl->ErrorCode())); + + if (!cntl->does_manage_http_body_on_error()) { + // Fill body with ErrorText. + // user may compress the output and change content-encoding. However + // body is error-text right now, remove the header. + res_header->RemoveHeader(common->CONTENT_ENCODING); + res_header->set_content_type(common->CONTENT_TYPE_TEXT); + cntl->response_attachment().append(cntl->ErrorText()); + } + } + } else if (cntl->has_progressive_writer()) { + // Transfer-Encoding is supported since HTTP/1.1 + if (res_header->major_version() < 2 && !res_header->before_http_1_1()) { + res_header->SetHeader("Transfer-Encoding", "chunked"); + } + if (!cntl->response_attachment().empty()) { + LOG(ERROR) << "response_attachment(size=" + << cntl->response_attachment().size() << ") will be" + " ignored when CreateProgressiveAttachment() was called"; + } + // not set_content to enable chunked mode. + } else if (cntl->response_compress_type() == COMPRESS_TYPE_GZIP) { + const size_t response_size = cntl->response_attachment().size(); + if (response_size >= (size_t)FLAGS_http_body_compress_threshold + && (is_http2 || SupportGzip(cntl))) { + TRACEPRINTF("Compressing response=%lu", (unsigned long)response_size); + butil::IOBuf tmpbuf; + if (GzipCompress(cntl->response_attachment(), &tmpbuf, NULL)) { + cntl->response_attachment().swap(tmpbuf); + if (is_grpc) { + grpc_compressed = true; + res_header->SetHeader(common->GRPC_ENCODING, common->GZIP); + } else { + res_header->SetHeader(common->CONTENT_ENCODING, common->GZIP); + } + } else { + LOG(ERROR) << "Fail to gzip the http response, skip compression."; + } + } + } else { + // TODO(gejun): Support snappy (grpc) + LOG_IF(ERROR, cntl->response_compress_type() != COMPRESS_TYPE_NONE) + << "Unknown compress_type=" << cntl->response_compress_type() + << ", skip compression."; + } + + int rc = -1; + // Have the risk of unlimited pending responses, in which case, tell + // users to set max_concurrency. + ResponseWriteInfo args; + Socket::WriteOptions wopt; + wopt.ignore_eovercrowded = true; + bthread_id_t response_id = INVALID_BTHREAD_ID; + if (span) { + CHECK_EQ(0, bthread_id_create(&response_id, &args, HandleResponseWritten)); + wopt.id_wait = response_id; + wopt.notify_on_success = true; + } + if (is_http2) { + if (is_grpc) { + // Append compressed and length before body + AddGrpcPrefix(&cntl->response_attachment(), grpc_compressed); + } + SocketMessagePtr h2_response( + H2UnsentResponse::New(cntl, _h2_stream_id, is_grpc)); + if (h2_response == NULL) { + LOG(ERROR) << "Fail to make http2 response"; + errno = EINVAL; + rc = -1; + } else { + if (FLAGS_http_verbose) { + LOG(INFO) << '\n' << *h2_response; + } + if (span) { + span->set_response_size(h2_response->EstimatedByteSize()); + } + rc = socket->Write(h2_response, &wopt); + } + } else { + butil::IOBuf* content = NULL; + if (cntl->Failed() || !cntl->has_progressive_writer()) { + content = &cntl->response_attachment(); + } + res_header->set_method(req_header->method()); + butil::IOBuf res_buf; + MakeRawHttpResponse(&res_buf, res_header, content); + if (FLAGS_http_verbose) { + PrintMessage(res_buf, false, !!content); + } + if (span) { + span->set_response_size(res_buf.size()); + } + rc = socket->Write(&res_buf, &wopt); + } + + if (rc != 0) { + // EPIPE is common in pooled connections + backup requests. + const int errcode = errno; + PLOG_IF(WARNING, errcode != EPIPE) << "Fail to write into " << *socket; + cntl->SetFailed(errcode, "Fail to write into %s", socket->description().c_str()); + return; + } + + if (span) { + bthread_id_join(response_id); + // Do not care about the result of background writing. + // TODO: this is not sent + span->set_sent_us(args.sent_us); + } +} + +// Normalize the sub string of `uri_path' covered by `splitter' and +// put it into `unresolved_path' +static void FillUnresolvedPath(std::string* unresolved_path, + const std::string& uri_path, + butil::StringSplitter& splitter) { + if (unresolved_path == NULL) { + return; + } + if (!splitter) { + unresolved_path->clear(); + return; + } + // Normalize unresolve_path. + const size_t path_len = + uri_path.c_str() + uri_path.size() - splitter.field(); + unresolved_path->reserve(path_len); + unresolved_path->clear(); + for (butil::StringSplitter slash_sp( + splitter.field(), splitter.field() + path_len, '/'); + slash_sp != NULL; ++slash_sp) { + if (!unresolved_path->empty()) { + unresolved_path->push_back('/'); + } + unresolved_path->append(slash_sp.field(), slash_sp.length()); + } +} + +inline const Server::MethodProperty* +FindMethodPropertyByURIImpl(const std::string& uri_path, const Server* server, + std::string* unresolved_path) { + ServerPrivateAccessor wrapper(server); + butil::StringSplitter splitter(uri_path.c_str(), '/'); + // Show index page for empty URI + if (NULL == splitter) { + return wrapper.FindMethodPropertyByFullName( + IndexService::descriptor()->full_name(), common->DEFAULT_METHOD); + } + butil::StringPiece service_name(splitter.field(), splitter.length()); + const bool full_service_name = + (service_name.find('.') != butil::StringPiece::npos); + const Server::ServiceProperty* const sp = + (full_service_name ? + wrapper.FindServicePropertyByFullName(service_name) : + wrapper.FindServicePropertyByName(service_name)); + if (NULL == sp) { + // normal for urls matching _global_restful_map + return NULL; + } + // Find restful methods by uri. + if (sp->restful_map) { + ++splitter; + butil::StringPiece left_path; + if (splitter) { + // The -1 is for including /, always safe because of ++splitter + left_path.set(splitter.field() - 1, uri_path.c_str() + + uri_path.size() - splitter.field() + 1); + } + return sp->restful_map->FindMethodProperty(left_path, unresolved_path); + } + if (!full_service_name) { + // Change to service's fullname. + service_name = sp->service->GetDescriptor()->full_name(); + } + + // Regard URI as [service_name]/[method_name] + const Server::MethodProperty* mp = NULL; + butil::StringPiece method_name; + if (++splitter != NULL) { + method_name.set(splitter.field(), splitter.length()); + // Copy splitter rather than modifying it directly since it's used + // in later branches. + mp = wrapper.FindMethodPropertyByFullName(service_name, method_name); + if (mp) { + ++splitter; // skip method name + FillUnresolvedPath(unresolved_path, uri_path, splitter); + return mp; + } + } + + // Try [service_name]/default_method + mp = wrapper.FindMethodPropertyByFullName(service_name, common->DEFAULT_METHOD); + if (mp) { + FillUnresolvedPath(unresolved_path, uri_path, splitter); + return mp; + } + + // Call BadMethodService::no_method for service_name-only URL. + if (method_name.empty()) { + return wrapper.FindMethodPropertyByFullName( + BadMethodService::descriptor()->full_name(), common->NO_METHOD); + } + + // Called an existing service w/o default_method with an unknown method. + return NULL; +} + +// Used in UT, don't be static +const Server::MethodProperty* +FindMethodPropertyByURI(const std::string& uri_path, const Server* server, + std::string* unresolved_path) { + const Server::MethodProperty* mp = + FindMethodPropertyByURIImpl(uri_path, server, unresolved_path); + if (mp != NULL) { + if (mp->http_url != NULL && !mp->params.allow_default_url) { + // the restful method is accessed from its + // default url (SERVICE/METHOD) which should be rejected. + return NULL; + } + return mp; + } + // uri_path cannot match any methods with exact service_name. Match + // the fuzzy patterns in global restful map which often matches + // extension names. Say "*.txt => get_text_file, *.mp4 => download_mp4". + ServerPrivateAccessor accessor(server); + if (accessor.global_restful_map()) { + return accessor.global_restful_map()->FindMethodProperty( + uri_path, unresolved_path); + } + return NULL; +} + +ParseResult ParseHttpMessage(butil::IOBuf *source, Socket *socket, + bool read_eof, const void* arg) { + HttpContext* http_imsg = + static_cast(socket->parsing_context()); + if (http_imsg == NULL) { + if (read_eof || source->empty()) { + // 1. read_eof: Read EOF after intact HTTP messages, a common case. + // Notice that errors except NOT_ENOUGH_DATA can't be returned + // otherwise the Socket will be SetFailed() and messages just + // in ProcessHttpXXX() may be dropped. + // 2. source->empty(): also common, InputMessage tries parse + // handlers until error is met. If a message was consumed, + // source is likely to be empty. + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + http_imsg = new (std::nothrow) HttpContext( + socket->is_read_progressive(), + socket->http_request_method()); + if (http_imsg == NULL) { + LOG(FATAL) << "Fail to new HttpContext"; + return MakeParseError(PARSE_ERROR_NO_RESOURCE); + } + // Parsing http is costly, parsing an incomplete http message from the + // beginning repeatedly should be avoided, otherwise the cost may reach + // O(n^2) in the worst case. Save incomplete http messages in sockets + // to prevent re-parsing. The message will be released when it is + // completed or destroyed along with the socket. + socket->reset_parsing_context(http_imsg); + } + ssize_t rc = 0; + if (read_eof) { + // Send EOF to HttpContext, check comments in http_message.h + rc = http_imsg->ParseFromArray(NULL, 0); + } else { + // Empty `source' is sliently ignored and 0 is returned, check + // comments in http_message.h + rc = http_imsg->ParseFromIOBuf(*source); + } + if (rc < 0 && http_imsg->body_too_large()) { + if (socket->CreatedByConnect()) { + return MakeParseError(PARSE_ERROR_TOO_BIG_DATA); + } + const int release_rc = socket->ReleaseAdditionalReference(); + if (release_rc == 0) { + butil::IOBuf resp; + HttpHeader header; + header.set_status_code(HTTP_STATUS_REQUEST_ENTITY_TOO_LARGE); + header.SetHeader("Connection", "close"); + MakeRawHttpResponse(&resp, &header, NULL); + Socket::WriteOptions wopt; + wopt.ignore_eovercrowded = true; + socket->Write(&resp, &wopt); + } else if (release_rc > 0) { + LOG(ERROR) << "Impossible: Recycled!"; + } + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + if (http_imsg->is_stage2()) { + // The header part is already parsed as an intact HTTP message + // to the ProcessHttpXXX. Here parses the body part. + if (rc >= 0) { + source->pop_front(rc); + if (http_imsg->Completed()) { + // Already returned the message before, don't return again. + CHECK_EQ(http_imsg, socket->release_parsing_context()); + // NOTE: calling http_imsg->Destroy() is wrong which can only + // be called from ProcessHttpXXX + http_imsg->RemoveOneRefForStage2(); + socket->OnProgressiveReadCompleted(); + return MakeMessage(NULL); + } else { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + } else { + // Fail to parse the body. Since headers were parsed successfully, + // the message is assumed to be HTTP, stop trying other protocols. + const char* err = http_errno_description( + HTTP_PARSER_ERRNO(&http_imsg->parser())); + return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG, err); + } + } else if (rc >= 0) { + // Normal or stage1 of progressive-read http message. + source->pop_front(rc); + if (http_imsg->Completed()) { + CHECK_EQ(http_imsg, socket->release_parsing_context()); + const ParseResult result = MakeMessage(http_imsg); + http_imsg->CheckProgressiveRead(arg, socket); + if (socket->is_read_progressive()) { + socket->OnProgressiveReadCompleted(); + } + return result; + } else if (http_imsg->stage() >= HTTP_ON_HEADERS_COMPLETE) { + // https://datatracker.ietf.org/doc/html/rfc7231#section-5.1.1 + // A server that receives a 100-continue expectation in an HTTP/1.0 + // request MUST ignore that expectation. + // + // A server MAY omit sending a 100 (Continue) response if it has + // already received some or all of the message body for the + // corresponding request, or if the framing indicates that there is + // no message body. + if (http_imsg->parser().type == HTTP_REQUEST && + !http_imsg->header().before_http_1_1()) { + const std::string* expect = http_imsg->header().GetHeader(common->EXPECT); + if (expect && *expect == common->CONTINUE_100) { + // Send 100-continue response back. + butil::IOBuf resp; + HttpHeader header; + header.set_status_code(HTTP_STATUS_CONTINUE); + MakeRawHttpResponse(&resp, &header, NULL); + Socket::WriteOptions wopt; + wopt.ignore_eovercrowded = true; + socket->Write(&resp, &wopt); + } + } + + http_imsg->CheckProgressiveRead(arg, socket); + if (socket->is_read_progressive()) { + // header part of a progressively-read http message is complete, + // go on to ProcessHttpXXX w/o waiting for full body. + http_imsg->AddOneRefForStage2(); // released when body is fully read + return MakeMessage(http_imsg); + } + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } else { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + } else if (!socket->CreatedByConnect()) { + // Note: If the parser fails at query-string/fragment/the-following + // -"HTTP/x.y", the message is very likely to be in http format (not + // other protocols registered after http). We send 400 back to client + // which is more informational than just closing the connection (may + // cause OP's alarms if the remote side is baidu's nginx). To do this, + // We make InputMessenger do nothing by cheating it with + // PARSE_ERROR_NOT_ENOUGH_DATA and remove the addtitional ref of the + // socket so that it will be recycled when the response is written. + // We can't use SetFailed which interrupts the writing. + // Tricky: Socket::ReleaseAdditionalReference() does not remove the + // internal fd from epoll thus we can still get EPOLLIN and read + // in more data. If the second read happens, parsing_context() + // should return the same InputMessage that we see now because we + // don't reset_parsing_context(NULL) in this branch, and following + // ParseFromXXX should return -1 immediately because of the non-zero + // parser.http_errno, and ReleaseAdditionalReference() here should + // return -1 to prevent us from sending another 400. + if (is_failed_after_queries(&http_imsg->parser())) { + int rc = socket->ReleaseAdditionalReference(); + if (rc < 0) { + // Already released, leave the socket to be recycled + // by itself. + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } else if (rc > 0) { + LOG(ERROR) << "Impossible: Recycled!"; + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + // Send 400 back. + butil::IOBuf resp; + HttpHeader header; + header.set_status_code(HTTP_STATUS_BAD_REQUEST); + MakeRawHttpResponse(&resp, &header, NULL); + Socket::WriteOptions wopt; + wopt.ignore_eovercrowded = true; + socket->Write(&resp, &wopt); + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } else { + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + } else { + if (is_failed_after_http_version(&http_imsg->parser())) { + return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG, + "invalid http response"); + } + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } +} + +static void SendUnauthorizedResponse(const std::string& user_error_text, Socket* socket, const InputMessageBase* msg) { + HttpContext* http_request = (HttpContext*)msg; + const bool is_http2 = http_request->header().is_http2(); + if (is_http2) { + // for grpc client + const H2StreamContext* h2_sctx = static_cast(msg); + brpc::Controller cntl; + cntl.http_response().set_status_code(200); + cntl.http_response().set_content_type("application/grpc"); + cntl.SetFailed(ERPCAUTH, "%s", user_error_text.empty() ? "Fail to authenticate" : user_error_text.c_str()); + + SocketMessagePtr h2_response( + H2UnsentResponse::New(&cntl, h2_sctx->stream_id(), true)); + brpc::Socket::WriteOptions opt; + opt.ignore_eovercrowded = true; + socket->Write(h2_response, &opt); + return; + } + + // Send 403(forbidden) to client. + HttpHeader header; + header.set_status_code(HTTP_STATUS_FORBIDDEN); + butil::IOBuf content; + content.append(butil::string_printf("[%d]", ERPCAUTH)); + content.append("Fail to authenticate"); + if (!user_error_text.empty()) { + content.append(": "); + content.append(user_error_text); + } + butil::IOBuf res_buf; + MakeRawHttpResponse(&res_buf, &header, &content); + Socket::WriteOptions opt; + opt.ignore_eovercrowded = true; + if (socket->Write(&res_buf, &opt) != 0) { + PLOG_IF(WARNING, errno != EPIPE) << "Fail to write into " << *socket; + } +} + +bool VerifyHttpRequest(const InputMessageBase* msg) { + Server* server = (Server*)msg->arg(); + Socket* socket = msg->socket(); + + HttpContext* http_request = (HttpContext*)msg; + const Authenticator* auth = server->options().auth; + if (NULL == auth) { + // Fast pass + return true; + } + const Server::MethodProperty* mp = FindMethodPropertyByURI( + http_request->header().uri().path(), server, NULL); + if (mp != NULL && mp->is_builtin_service && + mp->service->GetDescriptor() != BadMethodService::descriptor()) { + // Builtin services on internal_port doesn't need authentication + // Builtin services on the public listener must pass authentication + if (server->options().internal_port >= 0 && + socket->local_side().port == server->options().internal_port) { + return true; + } + } + + const std::string *authorization + = http_request->header().GetHeader(common->AUTHORIZATION); + if (authorization == NULL) { + SendUnauthorizedResponse(auth->GetUnauthorizedErrorText(), socket, msg); + return false; + } + butil::EndPoint user_addr; + if (!GetUserAddressFromHeader(http_request->header(), &user_addr)) { + user_addr = socket->remote_side(); + } + if (auth->VerifyCredential(*authorization, user_addr, + socket->mutable_auth_context()) != 0) { + SendUnauthorizedResponse(auth->GetUnauthorizedErrorText(), socket, msg); + return false; + } + + return true; +} + + +// Defined in baidu_rpc_protocol.cpp +void EndRunningCallMethodInPool( + ::google::protobuf::Service* service, + const ::google::protobuf::MethodDescriptor* method, + ::google::protobuf::RpcController* controller, + const ::google::protobuf::Message* request, + ::google::protobuf::Message* response, + ::google::protobuf::Closure* done); + +void ProcessHttpRequest(InputMessageBase *msg) { + const int64_t start_parse_us = butil::cpuwide_time_us(); + DestroyingPtr imsg_guard(static_cast(msg)); + SocketUniquePtr socket_guard(imsg_guard->ReleaseSocket()); + Socket* socket = socket_guard.get(); + const Server* server = static_cast(msg->arg()); + ScopedNonServiceError non_service_error(server); + + Controller* cntl = new (std::nothrow) Controller; + if (NULL == cntl) { + LOG(FATAL) << "Fail to new Controller"; + return; + } + HttpResponseSender resp_sender(cntl); + resp_sender.set_received_us(msg->received_us()); + + const bool is_http2 = imsg_guard->header().is_http2(); + if (is_http2) { + H2StreamContext* h2_sctx = static_cast(msg); + resp_sender.set_h2_stream_id(h2_sctx->stream_id()); + } + + ControllerPrivateAccessor accessor(cntl); + HttpHeader& req_header = cntl->http_request(); + imsg_guard->header().Swap(req_header); + butil::IOBuf& req_body = imsg_guard->body(); + butil::EndPoint user_addr; + if (!GetUserAddressFromHeader(req_header, &user_addr)) { + user_addr = socket->remote_side(); + } + ServerPrivateAccessor server_accessor(server); + const bool security_mode = server->options().security_mode() && + socket->user() == server_accessor.acceptor(); + accessor.set_server(server) + .set_security_mode(security_mode) + .set_peer_id(socket->id()) + .set_remote_side(user_addr) + .set_local_side(socket->local_side()) + .set_auth_context(socket->auth_context()) + .set_request_protocol(is_http2 ? PROTOCOL_H2 : PROTOCOL_HTTP) + .set_begin_time_us(msg->received_us()) + .move_in_server_receiving_sock(socket_guard); + + // Read log-id. errno may be set when input to strtoull overflows. + // atoi/atol/atoll don't support 64-bit integer and can't be used. + const std::string* log_id_str = req_header.GetHeader(common->LOG_ID); + if (log_id_str) { + char* logid_end = NULL; + errno = 0; + uint64_t logid = strtoull(log_id_str->c_str(), &logid_end, 10); + if (*logid_end || errno) { + LOG(ERROR) << "Invalid " << common->LOG_ID << '=' + << *log_id_str << " in http request"; + } else { + cntl->set_log_id(logid); + } + } + + const std::string* request_id = req_header.GetHeader(FLAGS_request_id_header); + if (request_id) { + cntl->set_request_id(*request_id); + } + + // Tag the bthread with this server's key for + // thread_local_data(). + if (server->thread_local_options().thread_local_data_factory) { + bthread_assign_data((void*)&server->thread_local_options()); + } + + std::shared_ptr span; + const std::string& path = req_header.uri().path(); + const std::string* trace_id_str = req_header.GetHeader("x-bd-trace-id"); + if (IsTraceable(trace_id_str)) { + uint64_t trace_id = 0; + if (trace_id_str) { + trace_id = strtoull(trace_id_str->c_str(), NULL, 10); + } + uint64_t span_id = 0; + const std::string* span_id_str = req_header.GetHeader("x-bd-span-id"); + if (span_id_str) { + span_id = strtoull(span_id_str->c_str(), NULL, 10); + } + uint64_t parent_span_id = 0; + const std::string* parent_span_id_str = + req_header.GetHeader("x-bd-parent-span-id"); + if (parent_span_id_str) { + parent_span_id = strtoull(parent_span_id_str->c_str(), NULL, 10); + } + span = Span::CreateServerSpan( + path, trace_id, span_id, parent_span_id, msg->base_real_us()); + accessor.set_span(span); + span->set_log_id(cntl->log_id()); + span->set_remote_side(user_addr); + span->set_received_us(msg->received_us()); + span->set_start_parse_us(start_parse_us); + span->set_protocol(is_http2 ? PROTOCOL_H2 : PROTOCOL_HTTP); + span->set_request_size(imsg_guard->parsed_length()); + } + + if (!server->IsRunning()) { + cntl->SetFailed(ELOGOFF, "Server is stopping"); + return; + } + + if (server->options().http_master_service) { + // If http_master_service is on, just call it. + google::protobuf::Service* svc = server->options().http_master_service; + const google::protobuf::MethodDescriptor* md = + svc->GetDescriptor()->FindMethodByName(common->DEFAULT_METHOD); + if (md == NULL) { + cntl->SetFailed(ENOMETHOD, "No default_method in http_master_service"); + return; + } + accessor.set_method(md); + cntl->request_attachment().swap(req_body); + google::protobuf::Closure* done = new HttpResponseSenderAsDone(&resp_sender); + if (span) { + span->ResetServerSpanName(butil::EnsureString(md->full_name())); + span->set_start_callback_us(butil::cpuwide_time_us()); + span->AsParent(); + } + // `cntl', `req' and `res' will be deleted inside `done' + return svc->CallMethod(md, cntl, NULL, NULL, done); + } + + const Server::MethodProperty* const mp = + FindMethodPropertyByURI(path, server, &req_header._unresolved_path); + if (NULL == mp) { + if (security_mode) { + std::string escape_path; + WebEscape(path, &escape_path); + cntl->SetFailed(ENOMETHOD, "Fail to find method on `%s'", escape_path.c_str()); + } else { + cntl->SetFailed(ENOMETHOD, "Fail to find method on `%s'", path.c_str()); + } + return; + } else if (mp->service->GetDescriptor() == BadMethodService::descriptor()) { + BadMethodRequest breq; + BadMethodResponse bres; + butil::StringSplitter split(path.c_str(), '/'); + breq.set_service_name(std::string(split.field(), split.length())); + mp->service->CallMethod(mp->method, cntl, &breq, &bres, NULL); + return; + } + // Switch to service-specific error. + non_service_error.release(); + MethodStatus* method_status = mp->status; + const std::string method_full_name = butil::EnsureString(mp->method->full_name()); + resp_sender.set_method_status(method_status); + if (method_status) { + int rejected_cc = 0; + if (!method_status->OnRequested(&rejected_cc)) { + cntl->SetFailed(ELIMIT, "Rejected by %s's ConcurrencyLimiter, concurrency=%d", + method_full_name.c_str(), rejected_cc); + return; + } + } + + if (span) { + span->ResetServerSpanName(method_full_name); + } + // NOTE: accesses to builtin services are not counted as part of + // concurrency, therefore are not limited by ServerOptions.max_concurrency. + if (!mp->is_builtin_service && !mp->params.is_tabbed) { + if (socket->is_overcrowded() && + !server->options().ignore_eovercrowded && + !mp->ignore_eovercrowded) { + cntl->SetFailed(EOVERCROWDED, "Connection to %s is overcrowded", + butil::endpoint2str(socket->remote_side()).c_str()); + return; + } + if (!server_accessor.AddConcurrency(cntl)) { + cntl->SetFailed(ELIMIT, "Reached server's max_concurrency=%d", + server->options().max_concurrency); + return; + } + if (FLAGS_usercode_in_pthread && TooManyUserCode()) { + cntl->SetFailed(ELIMIT, "Too many user code to run when" + " -usercode_in_pthread is on"); + return; + } + if (!server->AcceptRequest(cntl)) { + return; + } + } else if (security_mode) { + cntl->SetFailed(EPERM, "Not allowed to access builtin services, try " + "ServerOptions.internal_port=%d instead if you're in" + " internal network", server->options().internal_port); + return; + } + + google::protobuf::Service* svc = mp->service; + const google::protobuf::MethodDescriptor* method = mp->method; + accessor.set_method(method); + RpcPBMessages* messages = server->options().rpc_pb_message_factory->Get(*svc, *method);; + resp_sender.set_messages(messages); + google::protobuf::Message* req = messages->Request(); + google::protobuf::Message* res = messages->Response(); + + const std::string request_full_name = butil::EnsureString(req->GetDescriptor()->full_name()); + + if (__builtin_expect(!req || !res, 0)) { + PLOG(FATAL) << "Fail to new req or res"; + cntl->SetFailed("Fail to new req or res"); + return; + } + if (mp->params.allow_http_body_to_pb && + method->input_type()->field_count() > 0) { + // A protobuf service. No matter if Content-type is set to + // applcation/json or body is empty, we have to treat body as a json + // and try to convert it to pb, which guarantees that a protobuf + // service is always accessed with valid requests. + if (req_body.empty()) { + // Treat empty body specially since parsing it results in error + if (!req->IsInitialized()) { + cntl->SetFailed(EREQUEST, "%s needs to be created from a" + " non-empty json, it has required fields.", + request_full_name.c_str()); + return; + } // else all fields of the request are optional. + } else { + bool is_grpc_ct = false; + const HttpContentType content_type = + ParseContentType(req_header.content_type(), &is_grpc_ct); + const std::string* encoding = NULL; + if (is_http2 && is_grpc_ct) { + bool grpc_compressed = false; + if (!RemoveGrpcPrefix(&req_body, &grpc_compressed)) { + cntl->SetFailed(EREQUEST, "Invalid gRPC request"); + return; + } + if (grpc_compressed) { + encoding = req_header.GetHeader(common->GRPC_ENCODING); + if (encoding == NULL) { + cntl->SetFailed( + EREQUEST, "Fail to find header `grpc-encoding'" + " in compressed gRPC request"); + return; + } + } + int64_t timeout_value_us = + ConvertGrpcTimeoutToUS(req_header.GetHeader(common->GRPC_TIMEOUT)); + if (timeout_value_us >= 0) { + accessor.set_deadline_us( + butil::gettimeofday_us() + timeout_value_us); + } + } else { // http or h2 but not grpc + encoding = req_header.GetHeader(common->CONTENT_ENCODING); + } + if (encoding != NULL && *encoding == common->GZIP) { + TRACEPRINTF("Decompressing request=%lu", + (unsigned long)req_body.size()); + butil::IOBuf uncompressed; + if (!policy::GzipDecompress(req_body, &uncompressed)) { + cntl->SetFailed(EREQUEST, "Fail to un-gzip request body"); + return; + } + req_body.swap(uncompressed); + } + if (content_type == HTTP_CONTENT_PROTO) { + if (!ParsePbFromIOBuf(req, req_body)) { + cntl->SetFailed(EREQUEST, "Fail to parse http body as %s", + request_full_name.c_str()); + return; + } + } else if (content_type == HTTP_CONTENT_PROTO_TEXT) { + if (!ParsePbTextFromIOBuf(req, req_body)) { + cntl->SetFailed(EREQUEST, "Fail to parse http proto-text body as %s", + request_full_name.c_str()); + return; + } + } else if (content_type == HTTP_CONTENT_PROTO_JSON) { + if (!ProtoJsonToProtoMessage(req_body, req, cntl, EREQUEST)) { + return; + } + } else { + cntl->set_pb_bytes_to_base64(mp->params.pb_bytes_to_base64); + cntl->set_pb_single_repeated_to_array(mp->params.pb_single_repeated_to_array); + if (!JsonToProtoMessage(req_body, req, cntl, EREQUEST)) { + return; + } + } + } + if (!is_http2) { + SampledRequest* sample = AskToBeSampled(); + if (sample) { + sample->meta.set_compress_type(COMPRESS_TYPE_NONE); + sample->meta.set_protocol_type(PROTOCOL_HTTP); + sample->meta.set_attachment_size(req_body.size()); + + butil::EndPoint ep; + MakeRawHttpRequest(&sample->request, &req_header, ep, &req_body); + sample->submit(start_parse_us); + } + } + } else { + if (imsg_guard->read_body_progressively()) { + accessor.set_readable_progressive_attachment(imsg_guard.get()); + } else { + // A http server, just keep content as it is. + cntl->request_attachment().swap(req_body); + } + } + + google::protobuf::Closure* done = new HttpResponseSenderAsDone(&resp_sender); + imsg_guard.reset(); // optional, just release resource ASAP + + if (span) { + span->set_start_callback_us(butil::cpuwide_time_us()); + span->AsParent(); + } + if (!FLAGS_usercode_in_pthread) { + return svc->CallMethod(method, cntl, req, res, done); + } + if (BeginRunningUserCode()) { + svc->CallMethod(method, cntl, req, res, done); + return EndRunningUserCodeInPlace(); + } else { + return EndRunningCallMethodInPool(svc, method, cntl, req, res, done); + } +} + +bool ParseHttpServerAddress(butil::EndPoint* point, const char* server_addr_and_port) { + std::string scheme; + std::string host; + int port = -1; + if (ParseURL(server_addr_and_port, &scheme, &host, &port) != 0) { + LOG(ERROR) << "Invalid address=`" << server_addr_and_port << '\''; + return false; + } + if (scheme.empty() || scheme == "http") { + if (port < 0) { + port = 80; + } + } else if (scheme == "https") { + if (port < 0) { + port = 443; + } + } else { + LOG(ERROR) << "Invalid scheme=`" << scheme << '\''; + return false; + } + if (str2endpoint(host.c_str(), port, point) != 0 && + hostname2endpoint(host.c_str(), port, point) != 0) { + LOG(ERROR) << "Invalid host=" << host << " port=" << port; + return false; + } + return true; +} + +const std::string& GetHttpMethodName( + const google::protobuf::MethodDescriptor*, + const Controller* cntl) { + const std::string& path = cntl->http_request().uri().path(); + return !path.empty() ? path : common->DEFAULT_PATH; +} + +void HttpContext::CheckProgressiveRead(const void* arg, Socket *socket) { + if (arg == NULL || !((Server *)arg)->has_progressive_read_method()) { + // arg == NULL indicates not in server-end + return; + } + const Server::MethodProperty *const sp = FindMethodPropertyByURI( + header().uri().path(), (Server *)arg, + const_cast(&header().unresolved_path())); + if (sp != NULL && sp->params.enable_progressive_read) { + set_read_body_progressively(true); + socket->read_will_be_progressive(CONNECTION_TYPE_SHORT); + } +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/http_rpc_protocol.h b/src/brpc/policy/http_rpc_protocol.h new file mode 100644 index 0000000..bc8bd06 --- /dev/null +++ b/src/brpc/policy/http_rpc_protocol.h @@ -0,0 +1,163 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_HTTP_RPC_PROTOCOL_H +#define BRPC_POLICY_HTTP_RPC_PROTOCOL_H + +#include "brpc/details/http_message.h" // HttpMessage +#include "brpc/input_messenger.h" // InputMessenger +#include "brpc/protocol.h" + +namespace brpc { +namespace policy { + +// Put commonly used std::strings (or other constants that need memory +// allocations) in this struct to avoid memory allocations for each request. +struct CommonStrings { + std::string ACCEPT; + std::string DEFAULT_ACCEPT; + std::string USER_AGENT; + std::string DEFAULT_USER_AGENT; + std::string CONTENT_TYPE; + std::string CONTENT_TYPE_TEXT; + std::string CONTENT_TYPE_JSON; + std::string CONTENT_TYPE_PROTO; + std::string CONTENT_TYPE_SPRING_PROTO; + std::string ERROR_CODE; + std::string AUTHORIZATION; + std::string ACCEPT_ENCODING; + std::string CONTENT_ENCODING; + std::string CONTENT_LENGTH; + std::string EXPECT; + std::string CONTINUE_100; + std::string GZIP; + std::string CONNECTION; + std::string KEEP_ALIVE; + std::string CLOSE; + // Many users already GetHeader("log-id") in their code, it's difficult to + // rename this to `x-bd-log-id'. + // NOTE: Keep in mind that this name also appears inside `http_message.cpp' + std::string LOG_ID; + std::string DEFAULT_METHOD; + std::string NO_METHOD; + std::string H2_SCHEME; + std::string H2_SCHEME_HTTP; + std::string H2_SCHEME_HTTPS; + std::string H2_AUTHORITY; + std::string H2_PATH; + std::string H2_STATUS; + std::string STATUS_200; + std::string H2_METHOD; + std::string METHOD_GET; + std::string METHOD_POST; + + // GRPC-related headers + std::string CONTENT_TYPE_GRPC; + std::string TE; + std::string TRAILERS; + std::string GRPC_ENCODING; + std::string GRPC_ACCEPT_ENCODING; + std::string GRPC_ACCEPT_ENCODING_VALUE; + std::string GRPC_STATUS; + std::string GRPC_MESSAGE; + std::string GRPC_TIMEOUT; + + std::string DEFAULT_PATH; + + CommonStrings(); +}; + +// Used in UT. +class HttpContext : public ReadableProgressiveAttachment + , public InputMessageBase + , public HttpMessage { +public: + explicit HttpContext(bool read_body_progressively, + HttpMethod request_method = HTTP_METHOD_GET) + : InputMessageBase() + , HttpMessage(read_body_progressively, request_method) + , _is_stage2(false) { + // add one ref for Destroy + butil::intrusive_ptr(this).detach(); + } + + void AddOneRefForStage2() { + butil::intrusive_ptr(this).detach(); + _is_stage2 = true; + } + + void RemoveOneRefForStage2() { + butil::intrusive_ptr(this, false); + } + + // True if AddOneRefForStage2() was ever called. + bool is_stage2() const { return _is_stage2; } + + // @InputMessageBase + void DestroyImpl() override { + RemoveOneRefForStage2(); + } + + // @ReadableProgressiveAttachment + void ReadProgressiveAttachmentBy(ProgressiveReader* r) override { + return SetBodyReader(r); + } + + void CheckProgressiveRead(const void* arg, Socket *socket); + +private: + bool _is_stage2; +}; + +// Implement functions required in protocol.h +ParseResult ParseHttpMessage(butil::IOBuf *source, Socket *socket, + bool read_eof, const void *arg); +void ProcessHttpRequest(InputMessageBase *msg); +void ProcessHttpResponse(InputMessageBase* msg); +bool VerifyHttpRequest(const InputMessageBase* msg); +void SerializeHttpRequest(butil::IOBuf* request_buf, + Controller* cntl, + const google::protobuf::Message* msg); +void PackHttpRequest(butil::IOBuf* buf, + SocketMessage** user_message_out, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* controller, + const butil::IOBuf& request, + const Authenticator* auth); +bool ParseHttpServerAddress(butil::EndPoint* out, const char* server_addr_and_port); +const std::string& GetHttpMethodName(const google::protobuf::MethodDescriptor*, + const Controller*); + +enum HttpContentType { + HTTP_CONTENT_OTHERS = 0, + HTTP_CONTENT_JSON = 1, + HTTP_CONTENT_PROTO = 2, + HTTP_CONTENT_PROTO_TEXT = 3, + HTTP_CONTENT_PROTO_JSON = 4, +}; + +// Parse from the textual content type. One type may have more than one literals. +// Returns a numerical type. *is_grpc_ct is set to true if the content-type is +// set by gRPC. +HttpContentType ParseContentType(butil::StringPiece content_type, bool* is_grpc_ct); + +} // namespace policy +} // namespace brpc + +#endif // BRPC_POLICY_HTTP_RPC_PROTOCOL_H diff --git a/src/brpc/policy/hulu_pbrpc_controller.h b/src/brpc/policy/hulu_pbrpc_controller.h new file mode 100644 index 0000000..751c874 --- /dev/null +++ b/src/brpc/policy/hulu_pbrpc_controller.h @@ -0,0 +1,92 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_HULU_PBRPC_CONTROLLER_H +#define BRPC_HULU_PBRPC_CONTROLLER_H + +#include // int64_t +#include // std::string +#include "brpc/controller.h" // Controller + +namespace brpc { +namespace policy { + +// Special Controller that can be filled with hulu-pbrpc specific meta fields. +class HuluController : public Controller { +public: + HuluController() + : _request_source_addr(0) + , _response_source_addr(0) + {} + + void Reset() { + _request_source_addr = 0; + _response_source_addr = 0; + _request_user_data.clear(); + _response_user_data.clear(); + Controller::Reset(); + } + + + // ------------------------------------------------------------------ + // Client-side methods + // These calls shall be made from the client side only. Their results + // are undefined on the server side (may crash). + // ------------------------------------------------------------------ + + // Send the address that the client listens as a server to the remote + // side. + int64_t request_source_addr() const { return _request_source_addr; } + void set_request_source_addr(int64_t request_source_addr) + { _request_source_addr = request_source_addr; } + + // Send a raw data along with the Hulu rpc meta instead of carrying it with + // the request. + const std::string& request_user_data() const { return _request_user_data; } + void set_request_user_data(const std::string& request_user_data) + { _request_user_data = request_user_data; } + + // ------------------------------------------------------------------------ + // Server-side methods. + // These calls shall be made from the server side only. Their results are + // undefined on the client side (may crash). + // ------------------------------------------------------------------------ + + // Send the address that the server listens to the remote side. + int64_t response_source_addr() const { return _response_source_addr; } + void set_response_source_addr(int64_t response_source_addr) + { _response_source_addr = response_source_addr; } + + // Send a raw data along with the Hulu rpc meta instead of carrying it with + // the response. + const std::string& response_user_data() const { return _response_user_data; } + void set_response_user_data(const std::string& response_user_data) + { _response_user_data = response_user_data; } + + +private: + int64_t _request_source_addr; + int64_t _response_source_addr; + std::string _request_user_data; + std::string _response_user_data; +}; + +} // namespace policy +} // namespace brpc + +#endif //BRPC_HULU_PBRPC_CONTROLLER_H diff --git a/src/brpc/policy/hulu_pbrpc_meta.proto b/src/brpc/policy/hulu_pbrpc_meta.proto new file mode 100644 index 0000000..945e881 --- /dev/null +++ b/src/brpc/policy/hulu_pbrpc_meta.proto @@ -0,0 +1,56 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +import "brpc/options.proto"; // For TalkType + +package brpc.policy; +option java_package="com.brpc.policy"; +option java_outer_classname="HuluRpcProto"; + +message HuluRpcRequestMeta { + required string service_name = 1; + required int32 method_index = 2; + optional int32 compress_type = 3; + optional int64 correlation_id = 4; + optional int64 log_id = 5; + optional ChunkInfo chuck_info = 6; + + optional int64 trace_id = 7; + optional int64 parent_span_id = 8; + optional int64 span_id = 9; + + optional TalkType request_talk_type = 10; + optional bytes user_data = 11; + + optional int32 user_message_size = 12; + optional int64 user_defined_source_addr = 13; + optional string method_name = 14; + optional bytes credential_data = 15; +} + +message HuluRpcResponseMeta { + optional int32 error_code = 1; + optional string error_text = 2; + optional sint64 correlation_id = 3; + optional int32 compress_type = 4; + optional ChunkInfo chuck_info = 5; + optional TalkType response_talk_type = 6; + optional bytes user_data = 7; + optional int32 user_message_size = 8; + optional int64 user_defined_source_addr = 9; +} diff --git a/src/brpc/policy/hulu_pbrpc_protocol.cpp b/src/brpc/policy/hulu_pbrpc_protocol.cpp new file mode 100644 index 0000000..f698048 --- /dev/null +++ b/src/brpc/policy/hulu_pbrpc_protocol.cpp @@ -0,0 +1,733 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // MethodDescriptor +#include // Message +#include +#include + +#include "butil/strings/string_util.h" +#include "butil/time.h" + +#include "brpc/controller.h" // Controller +#include "brpc/socket.h" // Socket +#include "brpc/server.h" // Server +#include "brpc/details/server_private_accessor.h" +#include "brpc/span.h" +#include "brpc/compress.h" // ParseFromCompressedData +#include "brpc/details/controller_private_accessor.h" +#include "brpc/rpc_dump.h" +#include "brpc/policy/hulu_pbrpc_meta.pb.h" // HuluRpcRequestMeta +#include "brpc/policy/hulu_pbrpc_protocol.h" +#include "brpc/policy/most_common_message.h" +#include "brpc/policy/hulu_pbrpc_controller.h" // HuluController +#include "brpc/details/usercode_backup_pool.h" + +extern "C" { +void bthread_assign_data(void* data); +} + + +namespace brpc { +namespace policy { + +// Notes: +// 1. 12-byte header [HULU][body_size][meta_size] +// 2. Fields in header are NOT in network byte order (which may cause +// problems on machines with different byte order) +// 3. Use service->name() (rather than service->full_name()) + method_index +// to locate method defined in .proto file +// 4. 'user_message_size' is the size of protobuf request, +// and should be set if request/response has attachment +// 5. Not supported: +// chunk_info - hulu doesn't support either +// TalkType - nobody has use this so far in hulu + +enum HuluCompressType { + HULU_COMPRESS_TYPE_NONE = 0, + HULU_COMPRESS_TYPE_SNAPPY = 1, + HULU_COMPRESS_TYPE_GZIP = 2, + HULU_COMPRESS_TYPE_ZLIB = 3, +}; + +CompressType Hulu2CompressType(HuluCompressType type) { + switch (type) { + case HULU_COMPRESS_TYPE_NONE: + return COMPRESS_TYPE_NONE; + case HULU_COMPRESS_TYPE_SNAPPY: + return COMPRESS_TYPE_SNAPPY; + case HULU_COMPRESS_TYPE_GZIP: + return COMPRESS_TYPE_GZIP; + case HULU_COMPRESS_TYPE_ZLIB: + return COMPRESS_TYPE_ZLIB; + default: + LOG(ERROR) << "Unknown HuluCompressType=" << type; + return COMPRESS_TYPE_NONE; + } +} + +HuluCompressType CompressType2Hulu(CompressType type) { + switch (type) { + case COMPRESS_TYPE_NONE: + return HULU_COMPRESS_TYPE_NONE; + case COMPRESS_TYPE_SNAPPY: + return HULU_COMPRESS_TYPE_SNAPPY; + case COMPRESS_TYPE_GZIP: + return HULU_COMPRESS_TYPE_GZIP; + case COMPRESS_TYPE_ZLIB: + return HULU_COMPRESS_TYPE_ZLIB; + case COMPRESS_TYPE_LZ4: + LOG(ERROR) << "Hulu doesn't support LZ4"; + return HULU_COMPRESS_TYPE_NONE; + default: + LOG(ERROR) << "Unknown CompressType=" << type; + return HULU_COMPRESS_TYPE_NONE; + } +} + +// Can't use RawPacker/RawUnpacker because HULU does not use network byte order! +class HuluRawPacker { +public: + explicit HuluRawPacker(void* stream) : _stream((char*)stream) {} + + HuluRawPacker& pack32(uint32_t hostvalue) { + *(uint32_t*)_stream = hostvalue; + _stream += 4; + return *this; + } + + HuluRawPacker& pack64(uint64_t hostvalue) { + uint32_t *p = (uint32_t*)_stream; + *p = (hostvalue & 0xFFFFFFFF); + *(p + 1) = (hostvalue >> 32); + _stream += 8; + return *this; + } + +private: + char* _stream; +}; + +class HuluRawUnpacker { +public: + explicit HuluRawUnpacker(const void* stream) + : _stream((const char*)stream) {} + + HuluRawUnpacker& unpack32(uint32_t & hostvalue) { + hostvalue = *(const uint32_t*)_stream; + _stream += 4; + return *this; + } + + HuluRawUnpacker& unpack64(uint64_t & hostvalue) { + const uint32_t *p = (const uint32_t*)_stream; + hostvalue = ((uint64_t)*(p + 1) << 32) | *p; + _stream += 8; + return *this; + } + +private: + const char* _stream; +}; + +inline void PackHuluHeader(char* hulu_header, uint32_t meta_size, int body_size) { + uint32_t* dummy = reinterpret_cast(hulu_header); // suppress strict-alias warning + *dummy = *reinterpret_cast("HULU"); + HuluRawPacker rp(hulu_header + 4); + rp.pack32(meta_size + body_size).pack32(meta_size); +} + +template +static void SerializeHuluHeaderAndMeta( + butil::IOBuf* out, const Meta& meta, int payload_size) { + const uint32_t meta_size = GetProtobufByteSize(meta); + if (meta_size <= 244) { // most common cases + char header_and_meta[12 + meta_size]; + PackHuluHeader(header_and_meta, meta_size, payload_size); + ::google::protobuf::io::ArrayOutputStream arr_out(header_and_meta + 12, meta_size); + ::google::protobuf::io::CodedOutputStream coded_out(&arr_out); + meta.SerializeWithCachedSizes(&coded_out); // not calling ByteSize again + CHECK(!coded_out.HadError()); + out->append(header_and_meta, sizeof(header_and_meta)); + } else { + char header[12]; + PackHuluHeader(header, meta_size, payload_size); + out->append(header, sizeof(header)); + butil::IOBufAsZeroCopyOutputStream buf_stream(out); + ::google::protobuf::io::CodedOutputStream coded_out(&buf_stream); + meta.SerializeWithCachedSizes(&coded_out); + CHECK(!coded_out.HadError()); + } +} + +ParseResult ParseHuluMessage(butil::IOBuf* source, Socket* socket, + bool /*read_eof*/, const void* /*arg*/) { + char header_buf[12]; + const size_t n = source->copy_to(header_buf, sizeof(header_buf)); + if (n >= 4) { + void* dummy = header_buf; + if (*(const uint32_t*)dummy != *(const uint32_t*)"HULU") { + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + } else { + if (memcmp(header_buf, "HULU", n) != 0) { + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + } + if (n < sizeof(header_buf)) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + uint32_t body_size; + HuluRawUnpacker ru(header_buf + 4); + ru.unpack32(body_size); + if (body_size > FLAGS_max_body_size) { + // We need this log to report the body_size to give users some clues + // which is not printed in InputMessenger. + LOG(ERROR) << "body_size=" << body_size << " from " + << socket->remote_side() << " is too large"; + return MakeParseError(PARSE_ERROR_TOO_BIG_DATA); + } else if (source->length() < sizeof(header_buf) + body_size) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + uint32_t meta_size; + ru.unpack32(meta_size); + if (__builtin_expect(meta_size > body_size, 0)) { + LOG(ERROR) << "meta_size=" << meta_size + << " is bigger than body_size=" << body_size; + // Pop the message + source->pop_front(sizeof(header_buf) + body_size); + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + source->pop_front(sizeof(header_buf)); + MostCommonMessage* msg = MostCommonMessage::Get(); + source->cutn(&msg->meta, meta_size); + source->cutn(&msg->payload, body_size - meta_size); + return MakeMessage(msg); +} + +// Assemble response packet using `correlation_id', `controller', +// `res', and then write this packet to `sock' +static void SendHuluResponse(int64_t correlation_id, + HuluController* cntl, + const google::protobuf::Message* req, + const google::protobuf::Message* res, + const Server* server, + MethodStatus* method_status, + int64_t received_us) { + ControllerPrivateAccessor accessor(cntl); + auto span = accessor.span(); + if (span) { + span->set_start_send_us(butil::cpuwide_time_us()); + } + Socket* sock = accessor.get_sending_socket(); + std::unique_ptr recycle_cntl(cntl); + ConcurrencyRemover concurrency_remover(method_status, cntl, received_us); + std::unique_ptr recycle_req(req); + std::unique_ptr recycle_res(res); + + if (cntl->IsCloseConnection()) { + sock->SetFailed(); + return; + } + + bool append_body = false; + butil::IOBuf res_body_buf; + // `res' can be NULL here, in which case we don't serialize it + // If user calls `SetFailed' on Controller, we don't serialize + // response either + CompressType type = cntl->response_compress_type(); + if (res != NULL && !cntl->Failed()) { + if (!res->IsInitialized()) { + cntl->SetFailed( + ERESPONSE, "Missing required fields in response: %s", + res->InitializationErrorString().c_str()); + } else if (!SerializeAsCompressedData(*res, &res_body_buf, type)) { + cntl->SetFailed(ERESPONSE, "Fail to serialize response, " + "CompressType=%s", CompressTypeToCStr(type)); + } else { + append_body = true; + } + } + + // Don't use res->ByteSize() since it may be compressed + size_t res_size = 0; + size_t attached_size = 0; + if (append_body) { + res_size = res_body_buf.length(); + attached_size = cntl->response_attachment().length(); + } + + HuluRpcResponseMeta meta; + const int error_code = cntl->ErrorCode(); + meta.set_error_code(error_code); + if (!cntl->ErrorText().empty()) { + // Only set error_text when it's not empty since protobuf Message + // always new the string no matter if it's empty or not. + meta.set_error_text(cntl->ErrorText()); + } + meta.set_correlation_id(correlation_id); + meta.set_compress_type( + CompressType2Hulu(cntl->response_compress_type())); + if (attached_size > 0) { + meta.set_user_message_size(res_size); + } + if (cntl->response_source_addr() != 0) { + meta.set_user_defined_source_addr(cntl->response_source_addr()); + } + if (!cntl->response_user_data().empty()) { + meta.set_user_data(cntl->response_user_data()); + } + + butil::IOBuf res_buf; + SerializeHuluHeaderAndMeta(&res_buf, meta, res_size + attached_size); + if (append_body) { + res_buf.append(res_body_buf.movable()); + if (attached_size) { + res_buf.append(cntl->response_attachment().movable()); + } + } + if (span) { + span->set_response_size(res_buf.size()); + } + + // Have the risk of unlimited pending responses, in which case, tell + // users to set max_concurrency. + ResponseWriteInfo args; + Socket::WriteOptions wopt; + wopt.ignore_eovercrowded = true; + bthread_id_t response_id = INVALID_BTHREAD_ID; + if (span) { + CHECK_EQ(0, bthread_id_create(&response_id, &args, HandleResponseWritten)); + wopt.id_wait = response_id; + wopt.notify_on_success = true; + } + if (sock->Write(&res_buf, &wopt) != 0) { + const int errcode = errno; + PLOG_IF(WARNING, errcode != EPIPE) << "Fail to write into " << *sock; + cntl->SetFailed(errcode, "Fail to write into %s", + sock->description().c_str()); + return; + } + + if (span) { + bthread_id_join(response_id); + // Do not care about the result of background writing. + // TODO: this is not sent + span->set_sent_us(args.sent_us); + } +} + +// Defined in baidu_rpc_protocol.cpp +void EndRunningCallMethodInPool( + ::google::protobuf::Service* service, + const ::google::protobuf::MethodDescriptor* method, + ::google::protobuf::RpcController* controller, + const ::google::protobuf::Message* request, + ::google::protobuf::Message* response, + ::google::protobuf::Closure* done); + +void ProcessHuluRequest(InputMessageBase* msg_base) { + const int64_t start_parse_us = butil::cpuwide_time_us(); + DestroyingPtr msg(static_cast(msg_base)); + SocketUniquePtr socket_guard(msg->ReleaseSocket()); + Socket* socket = socket_guard.get(); + const Server* server = static_cast(msg_base->arg()); + ScopedNonServiceError non_service_error(server); + + HuluRpcRequestMeta meta; + if (!ParsePbFromIOBuf(&meta, msg->meta)) { + LOG(WARNING) << "Fail to parse HuluRpcRequestMeta, close the connection"; + socket->SetFailed(); + return; + } + + const CompressType req_cmp_type = Hulu2CompressType((HuluCompressType)meta.compress_type()); + SampledRequest* sample = AskToBeSampled(); + if (sample) { + sample->meta.set_service_name(meta.service_name()); + sample->meta.set_method_index(meta.method_index()); + sample->meta.set_compress_type(req_cmp_type); + sample->meta.set_protocol_type(PROTOCOL_HULU_PBRPC); + sample->meta.set_user_data(meta.user_data()); + if (meta.has_user_message_size() + && static_cast(meta.user_message_size()) < msg->payload.size()) { + size_t attachment_size = msg->payload.size() - meta.user_message_size(); + sample->meta.set_attachment_size(attachment_size); + } + sample->request = msg->payload; + sample->submit(start_parse_us); + } + + std::unique_ptr cntl(new (std::nothrow) HuluController()); + if (NULL == cntl.get()) { + LOG(WARNING) << "Fail to new Controller"; + return; + } + std::unique_ptr req; + std::unique_ptr res; + + ServerPrivateAccessor server_accessor(server); + ControllerPrivateAccessor accessor(cntl.get()); + int64_t correlation_id = meta.correlation_id(); + const bool security_mode = server->options().security_mode() && + socket->user() == server_accessor.acceptor(); + if (meta.has_log_id()) { + cntl->set_log_id(meta.log_id()); + } + cntl->set_request_compress_type(req_cmp_type); + accessor.set_server(server) + .set_security_mode(security_mode) + .set_peer_id(socket->id()) + .set_remote_side(socket->remote_side()) + .set_local_side(socket->local_side()) + .set_auth_context(socket->auth_context()) + .set_request_protocol(PROTOCOL_HULU_PBRPC) + .set_begin_time_us(msg->received_us()) + .move_in_server_receiving_sock(socket_guard); + + if (meta.has_user_data()) { + cntl->set_request_user_data(meta.user_data()); + } + + if (meta.has_user_defined_source_addr()) { + cntl->set_request_source_addr(meta.user_defined_source_addr()); + } + + + // Tag the bthread with this server's key for thread_local_data(). + if (server->thread_local_options().thread_local_data_factory) { + bthread_assign_data((void*)&server->thread_local_options()); + } + + std::shared_ptr span; + if (IsTraceable(meta.has_trace_id())) { + span = Span::CreateServerSpan( + meta.trace_id(), meta.span_id(), meta.parent_span_id(), + msg->base_real_us()); + accessor.set_span(span); + span->set_log_id(meta.log_id()); + span->set_remote_side(cntl->remote_side()); + span->set_protocol(PROTOCOL_HULU_PBRPC); + span->set_received_us(msg->received_us()); + span->set_start_parse_us(start_parse_us); + span->set_request_size(msg->payload.size() + msg->meta.size() + 12); + } + + MethodStatus* method_status = NULL; + do { + if (!server->IsRunning()) { + cntl->SetFailed(ELOGOFF, "Server is stopping"); + break; + } + + if (!server_accessor.AddConcurrency(cntl.get())) { + cntl->SetFailed(ELIMIT, "Reached server's max_concurrency=%d", + server->options().max_concurrency); + break; + } + if (FLAGS_usercode_in_pthread && TooManyUserCode()) { + cntl->SetFailed(ELIMIT, "Too many user code to run when" + " -usercode_in_pthread is on"); + break; + } + + const Server::MethodProperty *sp = + server_accessor.FindMethodPropertyByNameAndIndex( + meta.service_name(), meta.method_index()); + if (NULL == sp) { + cntl->SetFailed(ENOMETHOD, "Fail to find method=%d of service=%s", + meta.method_index(), meta.service_name().c_str()); + break; + } else if (sp->service->GetDescriptor() + == BadMethodService::descriptor()) { + BadMethodRequest breq; + BadMethodResponse bres; + breq.set_service_name(meta.service_name()); + sp->service->CallMethod(sp->method, cntl.get(), &breq, &bres, NULL); + break; + } + if (socket->is_overcrowded() && + !server->options().ignore_eovercrowded && + !sp->ignore_eovercrowded) { + cntl->SetFailed(EOVERCROWDED, "Connection to %s is overcrowded", + butil::endpoint2str(socket->remote_side()).c_str()); + break; + } + + // Switch to service-specific error. + non_service_error.release(); + method_status = sp->status; + const google::protobuf::MethodDescriptor* method = sp->method; + const std::string method_full_name = butil::EnsureString(method->full_name()); + if (method_status) { + int rejected_cc = 0; + if (!method_status->OnRequested(&rejected_cc)) { + cntl->SetFailed(ELIMIT, "Rejected by %s's ConcurrencyLimiter, concurrency=%d", + method_full_name.c_str(), rejected_cc); + break; + } + } + + google::protobuf::Service* svc = sp->service; + accessor.set_method(method); + + if (!server->AcceptRequest(cntl.get())) { + break; + } + + if (span) { + span->ResetServerSpanName(method_full_name); + } + const int reqsize = msg->payload.length(); + butil::IOBuf req_buf; + butil::IOBuf* req_buf_ptr = &msg->payload; + if (meta.has_user_message_size()) { + msg->payload.cutn(&req_buf, meta.user_message_size()); + req_buf_ptr = &req_buf; + cntl->request_attachment().swap(msg->payload); + } + + req.reset(svc->GetRequestPrototype(method).New()); + if (!ParseFromCompressedData(*req_buf_ptr, req.get(), req_cmp_type)) { + cntl->SetFailed(EREQUEST, "Fail to parse request message, " + "CompressType=%s, request_size=%d", + CompressTypeToCStr(req_cmp_type), reqsize); + break; + } + + res.reset(svc->GetResponsePrototype(method).New()); + // `socket' will be held until response has been sent + google::protobuf::Closure* done = ::brpc::NewCallback< + int64_t, HuluController*, const google::protobuf::Message*, + const google::protobuf::Message*, const Server*, + MethodStatus *, int64_t>( + &SendHuluResponse, correlation_id, cntl.get(), + req.get(), res.get(), server, + method_status, msg->received_us()); + + // optional, just release resource ASAP + msg.reset(); + req_buf.clear(); + + if (span) { + span->set_start_callback_us(butil::cpuwide_time_us()); + span->AsParent(); + } + if (!FLAGS_usercode_in_pthread) { + return svc->CallMethod(method, cntl.release(), + req.release(), res.release(), done); + } + if (BeginRunningUserCode()) { + svc->CallMethod(method, cntl.release(), + req.release(), res.release(), done); + return EndRunningUserCodeInPlace(); + } else { + return EndRunningCallMethodInPool( + svc, method, cntl.release(), + req.release(), res.release(), done); + } + } while (false); + + // `cntl', `req' and `res' will be deleted inside `SendHuluResponse' + // `socket' will be held until response has been sent + SendHuluResponse(correlation_id, cntl.release(), + req.release(), res.release(), server, + method_status, msg->received_us()); +} + +bool VerifyHuluRequest(const InputMessageBase* msg_base) { + const MostCommonMessage* msg = + static_cast(msg_base); + Socket* socket = msg->socket(); + const Server* server = static_cast(msg->arg()); + + HuluRpcRequestMeta request_meta; + if (!ParsePbFromIOBuf(&request_meta, msg->meta)) { + LOG(WARNING) << "Fail to parse HuluRpcRequestMeta"; + return false; + } + const Authenticator* auth = server->options().auth; + if (NULL == auth) { + // Fast pass (no authentication) + return true; + } + if (auth->VerifyCredential(request_meta.credential_data(), + socket->remote_side(), + socket->mutable_auth_context()) == 0) { + return true; + } + + // Send `ERPCAUTH' to client. + HuluRpcResponseMeta response_meta; + response_meta.set_correlation_id(request_meta.correlation_id()); + response_meta.set_error_code(ERPCAUTH); + std::string user_error_text = auth->GetUnauthorizedErrorText(); + response_meta.set_error_text("Fail to authenticate"); + if (!user_error_text.empty()) { + response_meta.mutable_error_text()->append(": "); + response_meta.mutable_error_text()->append(user_error_text); + } + butil::IOBuf res_buf; + SerializeHuluHeaderAndMeta(&res_buf, request_meta, 0); + Socket::WriteOptions opt; + opt.ignore_eovercrowded = true; + if (socket->Write(&res_buf, &opt) != 0) { + PLOG_IF(WARNING, errno != EPIPE) << "Fail to write into " << *socket; + } + + return false; +} + +void ProcessHuluResponse(InputMessageBase* msg_base) { + const int64_t start_parse_us = butil::cpuwide_time_us(); + DestroyingPtr msg(static_cast(msg_base)); + HuluRpcResponseMeta meta; + if (!ParsePbFromIOBuf(&meta, msg->meta)) { + LOG(WARNING) << "Fail to parse from response meta"; + return; + } + + const bthread_id_t cid = { static_cast(meta.correlation_id()) }; + Controller* cntl = NULL; + const int rc = bthread_id_lock(cid, (void**)&cntl); + if (rc != 0) { + LOG_IF(ERROR, rc != EINVAL && rc != EPERM) + << "Fail to lock correlation_id=" << cid << ": " << berror(rc); + return; + } + + ControllerPrivateAccessor accessor(cntl); + if (auto span = accessor.span()) { + span->set_base_real_us(msg->base_real_us()); + span->set_received_us(msg->received_us()); + span->set_response_size(msg->meta.size() + msg->payload.size() + 12); + span->set_start_parse_us(start_parse_us); + } + const int saved_error = cntl->ErrorCode(); + if (meta.error_code() != 0) { + // If error_code is unset, default is 0 = success. + cntl->SetFailed(meta.error_code(), + "%s", meta.error_text().c_str()); + } else { + // Parse response message iff error code from meta is 0 + butil::IOBuf res_buf; + butil::IOBuf* res_buf_ptr = &msg->payload; + if (meta.has_user_message_size()) { + msg->payload.cutn(&res_buf, meta.user_message_size()); + res_buf_ptr = &res_buf; + cntl->response_attachment().swap(msg->payload); + } + + CompressType res_cmp_type = Hulu2CompressType((HuluCompressType)meta.compress_type()); + cntl->set_response_compress_type(res_cmp_type); + if (cntl->response()) { + if (!ParseFromCompressedData( + *res_buf_ptr, cntl->response(), res_cmp_type)) { + cntl->SetFailed( + ERESPONSE, "Fail to parse response message, " + "CompressType=%s, response_size=%" PRIu64, + CompressTypeToCStr(res_cmp_type), + (uint64_t)msg->payload.length()); + } + } // else silently ignore the response. + HuluController* hulu_controller = dynamic_cast(cntl); + if (hulu_controller) { + if (meta.has_user_defined_source_addr()) { + hulu_controller->set_response_source_addr( + meta.user_defined_source_addr()); + } + if (meta.has_user_data()) { + hulu_controller->set_response_user_data(meta.user_data()); + } + } + } + // Unlocks correlation_id inside. Revert controller's + // error code if it version check of `cid' fails + msg.reset(); // optional, just release resource ASAP + accessor.OnResponse(cid, saved_error); +} + +void PackHuluRequest(butil::IOBuf* req_buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* cntl, + const butil::IOBuf& req_body, + const Authenticator* auth) { + HuluRpcRequestMeta meta; + if (auth != NULL && auth->GenerateCredential( + meta.mutable_credential_data()) != 0) { + return cntl->SetFailed(EREQUEST, "Fail to generate credential"); + } + + if (method) { + meta.set_service_name(method->service()->name()); + meta.set_method_index(method->index()); + meta.set_compress_type(CompressType2Hulu(cntl->request_compress_type())); + } else if (cntl->sampled_request()) { + // Replaying. Keep service-name as the one seen by server. + meta.set_service_name(cntl->sampled_request()->meta.service_name()); + meta.set_method_index(cntl->sampled_request()->meta.method_index()); + meta.set_compress_type( + CompressType2Hulu(cntl->sampled_request()->meta.compress_type())); + meta.set_user_data(cntl->sampled_request()->meta.user_data()); + } else { + return cntl->SetFailed(ENOMETHOD, "method is NULL"); + } + + HuluController* hulu_controller = dynamic_cast(cntl); + if (hulu_controller != NULL) { + if (hulu_controller->request_source_addr() != 0) { + meta.set_user_defined_source_addr( + hulu_controller->request_source_addr()); + } + if (!hulu_controller->request_user_data().empty()) { + meta.set_user_data(hulu_controller->request_user_data()); + } + } + + meta.set_correlation_id(correlation_id); + if (cntl->has_log_id()) { + meta.set_log_id(cntl->log_id()); + } + + // Don't use res->ByteSize() since it may be compressed + const size_t req_size = req_body.size(); + const size_t attached_size = cntl->request_attachment().length(); + if (attached_size) { + meta.set_user_message_size(req_size); + } // else don't set user_mesage_size when there's no attachment, otherwise + // existing hulu-pbrpc server may complain about empty attachment. + + auto span = ControllerPrivateAccessor(cntl).span(); + if (span) { + meta.set_trace_id(span->trace_id()); + meta.set_span_id(span->span_id()); + meta.set_parent_span_id(span->parent_span_id()); + } + + SerializeHuluHeaderAndMeta(req_buf, meta, req_size + attached_size); + req_buf->append(req_body); + if (attached_size) { + req_buf->append(cntl->request_attachment()); + } +} + +} // namespace policy +} // namespace brpc + diff --git a/src/brpc/policy/hulu_pbrpc_protocol.h b/src/brpc/policy/hulu_pbrpc_protocol.h new file mode 100644 index 0000000..d62f8a6 --- /dev/null +++ b/src/brpc/policy/hulu_pbrpc_protocol.h @@ -0,0 +1,53 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_HULU_PBRPC_PROTOCOL_H +#define BRPC_POLICY_HULU_PBRPC_PROTOCOL_H + +#include "brpc/policy/hulu_pbrpc_meta.pb.h" +#include "brpc/protocol.h" + +namespace brpc { +namespace policy { + +// Parse binary format of hulu-pbrpc. +ParseResult ParseHuluMessage(butil::IOBuf* source, Socket *socket, bool read_eof, const void *arg); + +// Actions to a (client) request in hulu-pbrpc format. +void ProcessHuluRequest(InputMessageBase* msg); + +// Actions to a (server) response in hulu-pbrpc format. +void ProcessHuluResponse(InputMessageBase* msg); + +// Verify authentication information in hulu-pbrpc format +bool VerifyHuluRequest(const InputMessageBase* msg); + +// Pack `request' to `method' into `buf'. +void PackHuluRequest(butil::IOBuf* buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* controller, + const butil::IOBuf& request, + const Authenticator* auth); + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_HULU_PBRPC_PROTOCOL_H diff --git a/src/brpc/policy/list_naming_service.cpp b/src/brpc/policy/list_naming_service.cpp new file mode 100644 index 0000000..3a8ba45 --- /dev/null +++ b/src/brpc/policy/list_naming_service.cpp @@ -0,0 +1,124 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // strtol +#include // std::string +#include // std::set +#include "butil/string_splitter.h" // StringSplitter +#include "brpc/log.h" +#include "brpc/policy/list_naming_service.h" + + +namespace brpc { +namespace policy { + +// Defined in file_naming_service.cpp +bool SplitIntoServerAndTag(const butil::StringPiece& line, + butil::StringPiece* server_addr, + butil::StringPiece* tag); + +int ParseServerList(const char* service_name, + std::vector* servers) { + servers->clear(); + // Sort/unique the inserted vector is faster, but may have a different order + // of addresses from the file. To make assertions in tests easier, we use + // set to de-duplicate and keep the order. + std::set presence; + std::string line; + + if (!service_name) { + LOG(FATAL) << "Param[service_name] is NULL"; + return -1; + } + for (butil::StringSplitter sp(service_name, ','); sp != NULL; ++sp) { + line.assign(sp.field(), sp.length()); + butil::StringPiece addr; + butil::StringPiece tag; + if (!SplitIntoServerAndTag(line, &addr, &tag)) { + continue; + } + const_cast(addr.data())[addr.size()] = '\0'; // safe + butil::EndPoint point; + if (str2endpoint(addr.data(), &point) != 0 && + hostname2endpoint(addr.data(), &point) != 0) { + LOG(ERROR) << "Invalid address=`" << addr << '\''; + continue; + } + ServerNode node; + node.addr = point; + tag.CopyToString(&node.tag); + if (presence.insert(node).second) { + servers->push_back(node); + } else { + RPC_VLOG << "Duplicated server=" << node; + } + } + RPC_VLOG << "Got " << servers->size() + << (servers->size() > 1 ? " servers" : " server"); + return 0; +} + +int ListNamingService::GetServers(const char *service_name, + std::vector* servers) { + return ParseServerList(service_name, servers); +} + +int ListNamingService::RunNamingService(const char* service_name, + NamingServiceActions* actions) { + std::vector servers; + const int rc = GetServers(service_name, &servers); + if (rc != 0) { + servers.clear(); + } + actions->ResetServers(servers); + return 0; +} + +void ListNamingService::Describe( + std::ostream& os, const DescribeOptions&) const { + os << "list"; + return; +} + +NamingService* ListNamingService::New() const { + return new ListNamingService; +} + +void ListNamingService::Destroy() { + delete this; +} + +int DomainListNamingService::GetServers(const char* service_name, + std::vector* servers) { + return ParseServerList(service_name, servers); +} + +void DomainListNamingService::Describe(std::ostream& os, + const DescribeOptions&) const { + os << "dlist"; + return; +} + +NamingService* DomainListNamingService::New() const { + return new DomainListNamingService; +} + +void DomainListNamingService::Destroy() { delete this; } + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/list_naming_service.h b/src/brpc/policy/list_naming_service.h new file mode 100644 index 0000000..0a69dee --- /dev/null +++ b/src/brpc/policy/list_naming_service.h @@ -0,0 +1,63 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_LIST_NAMING_SERVICE +#define BRPC_POLICY_LIST_NAMING_SERVICE + +#include "brpc/naming_service.h" +#include "brpc/periodic_naming_service.h" + + +namespace brpc { +namespace policy { + +class ListNamingService : public NamingService { +private: + int RunNamingService(const char* service_name, + NamingServiceActions* actions) override; + + // We don't need a dedicated bthread to run this static NS. + bool RunNamingServiceReturnsQuickly() override { return true; } + + int GetServers(const char *service_name, + std::vector* servers); + + void Describe(std::ostream& os, const DescribeOptions& options) const override; + + NamingService* New() const override; + + void Destroy() override; +}; + +class DomainListNamingService : public PeriodicNamingService { +private: + int GetServers(const char* service_name, + std::vector* servers) override; + void Describe(std::ostream& os, + const DescribeOptions& options) const override; + + NamingService* New() const override; + + void Destroy() override; +}; + +} // namespace policy +} // namespace brpc + + +#endif //BRPC_POLICY_LIST_NAMING_SERVICE diff --git a/src/brpc/policy/locality_aware_load_balancer.cpp b/src/brpc/policy/locality_aware_load_balancer.cpp new file mode 100644 index 0000000..beea516 --- /dev/null +++ b/src/brpc/policy/locality_aware_load_balancer.cpp @@ -0,0 +1,592 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // numeric_limits +#include +#include "butil/time.h" // gettimeofday_us +#include "butil/fast_rand.h" +#include "brpc/log.h" +#include "brpc/socket.h" +#include "brpc/reloadable_flags.h" +#include "brpc/policy/locality_aware_load_balancer.h" + +namespace brpc { +namespace policy { + +DEFINE_int64(min_weight, 1000, "Minimum weight of a node in LALB"); +DEFINE_double(punish_inflight_ratio, 1.5, "Decrease weight proportionally if " + "average latency of the inflight requests exeeds average " + "latency of the node times this ratio"); +DEFINE_double(punish_error_ratio, 1.2, + "Multiply latencies caused by errors with this ratio"); + +static const int64_t DEFAULT_QPS = 1; +static const size_t INITIAL_WEIGHT_TREE_SIZE = 128; +// 1008680231 +static const int64_t WEIGHT_SCALE = + std::numeric_limits::max() / 72000000 / (INITIAL_WEIGHT_TREE_SIZE - 1); + +LocalityAwareLoadBalancer::LocalityAwareLoadBalancer() + : _total(0) { +} + +LocalityAwareLoadBalancer::~LocalityAwareLoadBalancer() { + _db_servers.ModifyWithForeground(RemoveAll); +} + +bool LocalityAwareLoadBalancer::Add(Servers& bg, const Servers& fg, + SocketId id, + LocalityAwareLoadBalancer* lb) { + if (bg.weight_tree.capacity() < INITIAL_WEIGHT_TREE_SIZE) { + bg.weight_tree.reserve(INITIAL_WEIGHT_TREE_SIZE); + } + if (bg.server_map.seek(id) != NULL) { + // The id duplicates. + return false; + } + const size_t* pindex = fg.server_map.seek(id); + if (pindex == NULL) { + // Both fg and bg do not have the id. We create and insert a new Weight + // structure. Later when we modify the other buffer(current fg), just + // copy the pointer. + + const size_t index = bg.weight_tree.size(); + // If there exists node, set initial weight to be average of existing + // weights, set to WEIGHT_SCALE otherwise. If we insert from empty + // and no update during the insertions, all initial weights will be + // WEIGHT_SCALE, which feels good. + int64_t initial_weight = WEIGHT_SCALE; + if (!bg.weight_tree.empty()) { + initial_weight = lb->_total.load(butil::memory_order_relaxed) + / bg.weight_tree.size(); + } + + // Maintain the mapping from id to offset in weight_tree. This mapping + // is just for fast testing of existence of id. + bg.server_map[id] = index; + + // Push the weight structure into the tree. Notice that we also need + // a left_weight entry to store weight sum of all left nodes so that + // the load balancing by weights can be done in O(logN) complexity. + ServerInfo info = { id, lb->PushLeft(), new Weight(initial_weight) }; + bg.weight_tree.push_back(info); + + // The weight structure may already have initial weight. Add the weight + // to left_weight entries of all parent nodes and _total. The time + // complexity is strictly O(logN) because the tree is complete. + const int64_t diff = info.weight->volatile_value(); + if (diff) { + bg.UpdateParentWeights(diff, index); + lb->_total.fetch_add(diff, butil::memory_order_relaxed); + } + } else { + // We already modified the other buffer, just sync. Two buffers are + // always synced in this algorithm. + bg.server_map[id] = bg.weight_tree.size(); + bg.weight_tree.push_back(fg.weight_tree[*pindex]); + } + return true; +} + +bool LocalityAwareLoadBalancer::Remove( + Servers& bg, SocketId id, LocalityAwareLoadBalancer* lb) { + size_t* pindex = bg.server_map.seek(id); + if (NULL == pindex) { + // The id does not exist. + return false; + } + // Save the index and remove mapping from id to the index. + const size_t index = *pindex; + bg.server_map.erase(id); + + Weight* w = bg.weight_tree[index].weight; + // Set the weight to 0. Before we change weight of the parent nodes, + // SelectServer may still go to the node, but when it sees a zero weight, + // it retries, as if this range of weight is removed. + const int64_t rm_weight = w->Disable(); + if (index + 1 == bg.weight_tree.size()) { + // last node. Removing is easier. + bg.weight_tree.pop_back(); + if (rm_weight) { + // The first buffer. Remove the weight from parents to disable + // traffic going to this node. We can't remove the left_weight + // entry because the foreground buffer does not pop last node yet + // and still needs the left_weight (which should be same size with + // the tree). We can't delete the weight structure for same reason. + int64_t diff = -rm_weight; + bg.UpdateParentWeights(diff, index); + lb->_total.fetch_add(diff, butil::memory_order_relaxed); + } else { + // the second buffer. clean left stuff. + delete w; + lb->PopLeft(); + } + } else { + // Move last node to position `index' to fill the space. + bg.weight_tree[index].server_id = bg.weight_tree.back().server_id; + bg.weight_tree[index].weight = bg.weight_tree.back().weight; + bg.server_map[bg.weight_tree[index].server_id] = index; + bg.weight_tree.pop_back(); + + Weight* w2 = bg.weight_tree[index].weight; // previously back() + if (rm_weight) { + // First buffer. + // We need to remove the weight of last node from its parent + // nodes and add the weight to parent nodes of node `index'. + // However this process is not atomic. The foreground buffer still + // sees w2 as last node and it may change the weight during the + // process. To solve this problem, we atomically reset the weight + // and remember the previous index (back()) in _old_index. Later + // change to weight will add the diff to _old_diff_sum if _old_index + // matches the index which SelectServer is from. In this way we + // know the weight diff from foreground before we later modify it. + const int64_t add_weight = w2->MarkOld(bg.weight_tree.size()); + + // Add the weight diff to parent nodes of node `index'. Notice + // that we don't touch parent nodes of last node here because + // foreground is still sending traffic to last node. + const int64_t diff = add_weight - rm_weight; + if (diff) { + bg.UpdateParentWeights(diff, index); + lb->_total.fetch_add(diff, butil::memory_order_relaxed); + } + // At this point, the foreground distributes traffic to nodes + // correctly except node `index' because weight of the node is 0. + } else { + // Second buffer. + // Reset _old_* fields and get the weight change by SelectServer() + // after MarkOld(). + const std::pair p = w2->ClearOld(); + // Add the diff to parent nodes of node `index' + const int64_t diff = p.second; + if (diff) { + bg.UpdateParentWeights(diff, index); + } + // Remove weight from parent nodes of last node. + int64_t old_weight = - p.first - p.second; + if (old_weight) { + bg.UpdateParentWeights(old_weight, bg.weight_tree.size()); + } + lb->_total.fetch_add(- p.first, butil::memory_order_relaxed); + // Clear resources. + delete w; + lb->PopLeft(); + } + } + return true; +} + +bool LocalityAwareLoadBalancer::RemoveAll(Servers& bg, const Servers& fg) { + bg.server_map.clear(); + if (!fg.weight_tree.empty()) { + for (size_t i = 0; i < bg.weight_tree.size(); ++i) { + delete bg.weight_tree[i].weight; + } + } + bg.weight_tree.clear(); + return true; +} + +size_t LocalityAwareLoadBalancer::BatchAdd( + Servers& bg, const Servers& fg, const std::vector& servers, + LocalityAwareLoadBalancer* lb) { + size_t count = 0; + for (size_t i = 0; i < servers.size(); ++i) { + count += !!Add(bg, fg, servers[i], lb); + } + return count; +} + +// FIXME(gejun): not work +size_t LocalityAwareLoadBalancer::BatchRemove( + Servers& bg, const std::vector& servers, + LocalityAwareLoadBalancer* lb) { + size_t count = 0; + for (size_t i = 0; i < servers.size(); ++i) { + count += !!Remove(bg, servers[i], lb); + } + return count; +} + +bool LocalityAwareLoadBalancer::AddServer(const ServerId& id) { + if (_id_mapper.AddServer(id)) { + RPC_VLOG << "LALB: added " << id; + return _db_servers.ModifyWithForeground(Add, id.id, this); + } else { + return true; + } +} + +bool LocalityAwareLoadBalancer::RemoveServer(const ServerId& id) { + if (_id_mapper.RemoveServer(id)) { + RPC_VLOG << "LALB: removed " << id; + return _db_servers.Modify(Remove, id.id, this); + } else { + return true; + } +} + +size_t LocalityAwareLoadBalancer::AddServersInBatch( + const std::vector& servers) { + std::vector & ids = _id_mapper.AddServers(servers); + RPC_VLOG << "LALB: added " << ids.size(); + _db_servers.ModifyWithForeground(BatchAdd, ids, this); + return servers.size(); +} + +size_t LocalityAwareLoadBalancer::RemoveServersInBatch( + const std::vector& servers) { + std::vector & ids = _id_mapper.RemoveServers(servers); + RPC_VLOG << "LALB: removed " << ids.size(); + size_t count = 0; + for (size_t i = 0; i < ids.size(); ++i) { + count += _db_servers.Modify(Remove, ids[i], this); + } + return count; + // FIXME(gejun): Batch removing is buggy + // return _db_servers.Modify(BatchRemove, servers, this); +} + +int LocalityAwareLoadBalancer::SelectServer(const SelectIn& in, SelectOut* out) { + butil::DoublyBufferedData::ScopedPtr s; + if (_db_servers.Read(&s) != 0) { + return ENOMEM; + } + const size_t n = s->weight_tree.size(); + if (n == 0) { + return ENODATA; + } + size_t ntry = 0; + size_t nloop = 0; + int64_t total = _total.load(butil::memory_order_relaxed); + int64_t dice = butil::fast_rand_less_than(total); + size_t index = 0; + int64_t self = 0; + while (total > 0) { + // FIXME(gejun): This is the final protection from the selection code + // falls into infinite loop. This branch should never be entered in + // production servers. If it does, there must be a bug. + if (++nloop > 10000) { + LOG(ERROR) << "A selection runs too long!"; + return EHOSTDOWN; + } + + // Locate a weight range in the tree. This is obviously not atomic and + // left-weights / total / weight-of-the-node may not be consistent. But + // this is what we have to pay to gain more parallelism. + const ServerInfo & info = s->weight_tree[index]; + const int64_t left = info.left->load(butil::memory_order_relaxed); + if (dice < left) { + index = index * 2 + 1; + if (index < n) { + continue; + } + } else if (dice >= left + (self = info.weight->volatile_value())) { + dice -= left + self; + index = index * 2 + 2; + if (index < n) { + continue; + } + } else if (IsServerAvailable(info.server_id, out->ptr)) { + if ((ntry + 1) == n // Instead of fail with EHOSTDOWN, we prefer + // choosing the server again. + || !ExcludedServers::IsExcluded(in.excluded, info.server_id)) { + if (!in.changable_weights) { + return 0; + } + const Weight::AddInflightResult r = + info.weight->AddInflight(in, index, dice - left); + if (r.weight_diff) { + s->UpdateParentWeights(r.weight_diff, index); + _total.fetch_add(r.weight_diff, butil::memory_order_relaxed); + } + if (r.chosen) { + out->need_feedback = true; + return 0; + } + } + if (++ntry >= n) { + break; + } + } else if (in.changable_weights) { + const int64_t diff = + info.weight->MarkFailed(index, total / n); + if (diff) { + s->UpdateParentWeights(diff, index); + _total.fetch_add(diff, butil::memory_order_relaxed); + } + if (dice >= left + self + diff) { + dice -= left + self + diff; + index = index * 2 + 2; + } else { + // left child may contain available nodes + dice = butil::fast_rand_less_than(left); + index = index * 2 + 1; + } + if (index < n) { + continue; + } + if (++ntry >= n) { + break; + } + } else { + if (++ntry >= n) { + break; + } + } + total = _total.load(butil::memory_order_relaxed); + dice = butil::fast_rand_less_than(total); + index = 0; + } + return EHOSTDOWN; +} + +void LocalityAwareLoadBalancer::Feedback(const CallInfo& info) { + butil::DoublyBufferedData::ScopedPtr s; + if (_db_servers.Read(&s) != 0) { + return; + } + const size_t* pindex = s->server_map.seek(info.server_id); + if (NULL == pindex) { + return; + } + const size_t index = *pindex; + Weight* w = s->weight_tree[index].weight; + const int64_t diff = w->Update(info, index); + if (diff != 0) { + s->UpdateParentWeights(diff, index); + _total.fetch_add(diff, butil::memory_order_relaxed); + } +} + +int64_t LocalityAwareLoadBalancer::Weight::Update( + const CallInfo& ci, size_t index) { + const int64_t end_time_us = butil::gettimeofday_us(); + const int64_t latency = end_time_us - ci.begin_time_us; + BAIDU_SCOPED_LOCK(_mutex); + if (Disabled()) { + // The weight was disabled and will be removed soon, do nothing + // and the diff is 0. + return 0; + } + + _begin_time_sum -= ci.begin_time_us; + --_begin_time_count; + + if (latency <= 0) { + // time skews, ignore the sample. + return 0; + } + if (ci.error_code == 0) { + // Add a new entry + TimeInfo tm_info = { latency, end_time_us }; + if (!_time_q.empty()) { + tm_info.latency_sum += _time_q.bottom()->latency_sum; + } + _time_q.elim_push(tm_info); + } else { + // Accumulate into the last entry so that errors always decrease + // the overall QPS and latency. + // Note that the latency used is linearly mixed from the real latency + // (of an erroneous call) and the timeout, so that errors that are more + // unlikely to be solved by later retries are punished more. + // Examples: + // max_retry=0: always use timeout + // max_retry=1, retried=0: latency + // max_retry=1, retried=1: timeout + // max_retry=2, retried=0: latency + // max_retry=2, retried=1: (latency + timeout) / 2 + // max_retry=2, retried=2: timeout + // ... + int ndone = 1; + int nleft = 0; + if (ci.controller->max_retry() > 0) { + ndone = ci.controller->retried_count(); + nleft = ci.controller->max_retry() - ndone; + } + const int64_t err_latency = + (nleft * (int64_t)(latency * FLAGS_punish_error_ratio) + + ndone * ci.controller->timeout_ms() * 1000L) / (ndone + nleft); + + if (!_time_q.empty()) { + TimeInfo* ti = _time_q.bottom(); + ti->latency_sum += err_latency; + ti->end_time_us = end_time_us; + } else { + // If the first response is error, enlarge the latency as timedout + // since we know nothing about the normal latency yet. + const TimeInfo tm_info = { + std::max(err_latency, ci.controller->timeout_ms() * 1000L), + end_time_us + }; + _time_q.push(tm_info); + } + } + + const int64_t top_time_us = _time_q.top()->end_time_us; + const size_t n = _time_q.size(); + int64_t scaled_qps = DEFAULT_QPS * WEIGHT_SCALE; + if (end_time_us > top_time_us) { + // Only calculate scaled_qps when the queue is full or the elapse + // between bottom and top is reasonably large(so that error of the + // calculated QPS is probably smaller). + if (n == _time_q.capacity() || + end_time_us >= top_time_us + 1000000L/*1s*/) { + // will not overflow. + scaled_qps = (n - 1) * 1000000L * WEIGHT_SCALE / (end_time_us - top_time_us); + if (scaled_qps < WEIGHT_SCALE) { + scaled_qps = WEIGHT_SCALE; + } + } + _avg_latency = (_time_q.bottom()->latency_sum - + _time_q.top()->latency_sum) / (n - 1); + } else if (n == 1) { + _avg_latency = _time_q.bottom()->latency_sum; + } else { + // end_time_us <= top_time_us && n > 1: the QPS is so high that + // the time elapse between top and bottom is 0(possible in examples), + // or time skews, we don't update the weight for safety. + return 0; + } + if (_avg_latency == 0) { + return 0; + } + _base_weight = scaled_qps / _avg_latency; + return ResetWeight(index, end_time_us); +} + +LocalityAwareLoadBalancer* LocalityAwareLoadBalancer::New( + const butil::StringPiece&) const { + return new (std::nothrow) LocalityAwareLoadBalancer; +} + +void LocalityAwareLoadBalancer::Destroy() { + delete this; +} + +void LocalityAwareLoadBalancer::Weight::Describe(std::ostream& os, int64_t now) { + std::unique_lock mu(_mutex); + int64_t begin_time_sum = _begin_time_sum; + int begin_time_count = _begin_time_count; + int64_t weight = _weight; + int64_t base_weight = _base_weight; + size_t n = _time_q.size(); + double qps = 0; + int64_t avg_latency = _avg_latency; + if (n <= 1UL) { + qps = 0; + } else { + if (n == _time_q.capacity()) { + --n; + } + qps = n * 1000000 / (double)(now - _time_q.top()->end_time_us); + } + mu.unlock(); + + os << "weight=" << weight; + if (base_weight != weight) { + os << "(base=" << base_weight << ')'; + } + if (begin_time_count != 0) { + os << " inflight_delay=" << now - begin_time_sum / begin_time_count + << "(count=" << begin_time_count << ')'; + } else { + os << " inflight_delay=0"; + } + os << " avg_latency=" << avg_latency + << " expected_qps=" << qps; +} + +void LocalityAwareLoadBalancer::Describe( + std::ostream& os, const DescribeOptions& options) { + if (!options.verbose) { + os << "la"; + return; + } + os << "LocalityAware{total=" + << _total.load(butil::memory_order_relaxed) << ' '; + butil::DoublyBufferedData::ScopedPtr s; + if (_db_servers.Read(&s) != 0) { + os << "fail to read _db_servers"; + } else { + const int64_t now = butil::gettimeofday_us(); + const size_t n = s->weight_tree.size(); + os << '['; + for (size_t i = 0; i < n; ++i) { + const ServerInfo & info = s->weight_tree[i]; + os << "\n{id=" << info.server_id; + { + SocketUniquePtr tmp_ptr; + if (Socket::Address(info.server_id, &tmp_ptr) != 0) { + os << "(broken)"; + } + } + os << " left=" + << info.left->load(butil::memory_order_relaxed) << ' '; + info.weight->Describe(os, now); + os << '}'; + } + os << ']'; + } + os << '}'; +} + +LocalityAwareLoadBalancer::Weight::Weight(int64_t initial_weight) + : _weight(initial_weight) + , _base_weight(initial_weight) + , _begin_time_sum(0) + , _begin_time_count(0) + , _old_diff_sum(0) + , _old_index((size_t)-1L) + , _old_weight(0) + , _avg_latency(0) + , _time_q(_time_q_items, sizeof(_time_q_items), butil::NOT_OWN_STORAGE) { +} + +LocalityAwareLoadBalancer::Weight::~Weight() { +} + +int64_t LocalityAwareLoadBalancer::Weight::Disable() { + BAIDU_SCOPED_LOCK(_mutex); + const int64_t saved = _weight; + _base_weight = -1; + _weight = 0; + return saved; +} + +int64_t LocalityAwareLoadBalancer::Weight::MarkOld(size_t index) { + BAIDU_SCOPED_LOCK(_mutex); + const int64_t saved = _weight; + _old_weight = saved; + _old_diff_sum = 0; + _old_index = index; + return saved; +} + +std::pair LocalityAwareLoadBalancer::Weight::ClearOld() { + BAIDU_SCOPED_LOCK(_mutex); + const int64_t old_weight = _old_weight; + const int64_t diff = _old_diff_sum; + _old_diff_sum = 0; + _old_index = (size_t)-1; + _old_weight = 0; + return std::make_pair(old_weight, diff); +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/locality_aware_load_balancer.h b/src/brpc/policy/locality_aware_load_balancer.h new file mode 100644 index 0000000..82373a3 --- /dev/null +++ b/src/brpc/policy/locality_aware_load_balancer.h @@ -0,0 +1,225 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_LOCALITY_AWARE_LOAD_BALANCER_H +#define BRPC_POLICY_LOCALITY_AWARE_LOAD_BALANCER_H + +#include // std::vector +#include // std::deque +#include // std::map +#include "butil/containers/flat_map.h" // FlatMap +#include "butil/containers/doubly_buffered_data.h" // DoublyBufferedData +#include "butil/containers/bounded_queue.h" // BoundedQueue +#include "brpc/load_balancer.h" +#include "brpc/controller.h" + + +namespace brpc { +namespace policy { + +DECLARE_int64(min_weight); +DECLARE_double(punish_inflight_ratio); + +// Locality-aware is an iterative algorithm to send requests to servers which +// have lowest expected latencies. Read docs/cn/lalb.md to get a peek at the +// algorithm. The implementation is complex. +class LocalityAwareLoadBalancer : public LoadBalancer { +public: + LocalityAwareLoadBalancer(); + ~LocalityAwareLoadBalancer() override; + bool AddServer(const ServerId& id) override; + bool RemoveServer(const ServerId& id) override; + size_t AddServersInBatch(const std::vector& servers) override; + size_t RemoveServersInBatch(const std::vector& servers) override; + LocalityAwareLoadBalancer* New(const butil::StringPiece&) const override; + void Destroy() override; + int SelectServer(const SelectIn& in, SelectOut* out) override; + void Feedback(const CallInfo& info) override; + void Describe(std::ostream& os, const DescribeOptions& options) override; + +private: + struct TimeInfo { + int64_t latency_sum; // microseconds + int64_t end_time_us; + }; + + class Servers; + class Weight { + friend class Servers; + public: + static const int RECV_QUEUE_SIZE = 128; + + explicit Weight(int64_t initial_weight); + ~Weight(); + + // Called in Feedback() to recalculate _weight. + // Returns diff of _weight. + int64_t Update(const CallInfo&, size_t index); + + // Weight of self. Notice that this value may change at any time. + int64_t volatile_value() const { return _weight; } + + struct AddInflightResult { + bool chosen; + int64_t weight_diff; + }; + AddInflightResult AddInflight( + const SelectIn& in, size_t index, int64_t dice); + int64_t MarkFailed(size_t index, int64_t avg_weight); + + void Describe(std::ostream& os, int64_t now); + + int64_t Disable(); + bool Disabled() const { return _base_weight < 0; } + int64_t MarkOld(size_t index); + std::pair ClearOld(); + + int64_t ResetWeight(size_t index, int64_t now_us); + + private: + int64_t _weight; + int64_t _base_weight; + butil::Mutex _mutex; + int64_t _begin_time_sum; + int _begin_time_count; + int64_t _old_diff_sum; + size_t _old_index; + int64_t _old_weight; + int64_t _avg_latency; + butil::BoundedQueue _time_q; + // content of _time_q + TimeInfo _time_q_items[RECV_QUEUE_SIZE]; + }; + + struct ServerInfo { + SocketId server_id; + butil::atomic* left; + Weight* weight; + }; + + class Servers { + public: + std::vector weight_tree; + butil::FlatMap server_map; + + Servers() { + if (server_map.init(1024, 70) != 0) { + LOG(WARNING) << "Fail to init server_map"; + } + } + + // Add diff to left_weight of all parent nodes of node `index'. + // Not require position `index' to exist. + void UpdateParentWeights(int64_t diff, size_t index) const; + }; + static bool Add(Servers& bg, const Servers& fg, + SocketId id, LocalityAwareLoadBalancer*); + static bool Remove(Servers& bg, SocketId id, + LocalityAwareLoadBalancer*); + static size_t BatchAdd(Servers& bg, const Servers& fg, + const std::vector& servers, + LocalityAwareLoadBalancer*); + static size_t BatchRemove(Servers& bg, + const std::vector& servers, + LocalityAwareLoadBalancer*); + static bool RemoveAll(Servers& bg, const Servers& fg); + + // Add a entry to _left_weights. + butil::atomic* PushLeft() { + _left_weights.push_back(0); + return (butil::atomic*)&_left_weights.back(); + } + void PopLeft() { _left_weights.pop_back(); } + + butil::atomic _total; + butil::DoublyBufferedData _db_servers; + std::deque _left_weights; + ServerId2SocketIdMapper _id_mapper; +}; + +inline void LocalityAwareLoadBalancer::Servers::UpdateParentWeights( + int64_t diff, size_t index) const { + while (index != 0) { + const size_t parent_index = (index - 1) >> 1; + if ((parent_index << 1) + 1 == index) { // left child + weight_tree[parent_index].left->fetch_add( + diff, butil::memory_order_relaxed); + } + index = parent_index; + } +} + +inline int64_t LocalityAwareLoadBalancer::Weight::ResetWeight( + size_t index, int64_t now_us) { + int64_t new_weight = _base_weight; + if (_begin_time_count > 0) { + const int64_t inflight_delay = + now_us - _begin_time_sum / _begin_time_count; + const int64_t punish_latency = + (int64_t)(_avg_latency * FLAGS_punish_inflight_ratio); + if (inflight_delay >= punish_latency && _avg_latency > 0) { + new_weight = new_weight * punish_latency / inflight_delay; + } + } + if (new_weight < FLAGS_min_weight) { + new_weight = FLAGS_min_weight; + } + const int64_t old_weight = _weight; + _weight = new_weight; + const int64_t diff = new_weight - old_weight; + if (_old_index == index && diff != 0) { + _old_diff_sum += diff; + } + return diff; +} + +inline LocalityAwareLoadBalancer::Weight::AddInflightResult +LocalityAwareLoadBalancer::Weight::AddInflight( + const SelectIn& in, size_t index, int64_t dice) { + BAIDU_SCOPED_LOCK(_mutex); + if (Disabled()) { + AddInflightResult r = { false, 0 }; + return r; + } + const int64_t diff = ResetWeight(index, in.begin_time_us); + if (_weight < dice) { + // inflight delay makes the weight too small to choose. + AddInflightResult r = { false, diff }; + return r; + } + _begin_time_sum += in.begin_time_us; + ++_begin_time_count; + AddInflightResult r = { true, diff }; + return r; +} + +inline int64_t LocalityAwareLoadBalancer::Weight::MarkFailed( + size_t index, int64_t avg_weight) { + BAIDU_SCOPED_LOCK(_mutex); + if (_base_weight <= avg_weight) { + return 0; + } + _base_weight = avg_weight; + return ResetWeight(index, 0); +} + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_LOCALITY_AWARE_LOAD_BALANCER_H diff --git a/src/brpc/policy/memcache_binary_header.h b/src/brpc/policy/memcache_binary_header.h new file mode 100644 index 0000000..93ba879 --- /dev/null +++ b/src/brpc/policy/memcache_binary_header.h @@ -0,0 +1,162 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_MEMCACHE_BINARY_HEADER_H +#define BRPC_MEMCACHE_BINARY_HEADER_H + + +namespace brpc { +namespace policy { + +// https://code.google.com/p/memcached/wiki/BinaryProtocolRevamped + +// Definition of the legal "magic" values used in a packet. +// See section 3.1 Magic byte +enum MemcacheMagic { + MC_MAGIC_REQUEST = 0x80, + MC_MAGIC_RESPONSE = 0x81 +}; + +// Definition of the data types in the packet +// See section 3.4 Data Types +enum MemcacheBinaryDataType { + MC_BINARY_RAW_BYTES = 0x00 +}; + +// Definition of the different command opcodes. +// See section 3.3 Command Opcodes +enum MemcacheBinaryCommand { + MC_BINARY_GET = 0x00, + MC_BINARY_SET = 0x01, + MC_BINARY_ADD = 0x02, + MC_BINARY_REPLACE = 0x03, + MC_BINARY_DELETE = 0x04, + MC_BINARY_INCREMENT = 0x05, + MC_BINARY_DECREMENT = 0x06, + MC_BINARY_QUIT = 0x07, + MC_BINARY_FLUSH = 0x08, + MC_BINARY_GETQ = 0x09, + MC_BINARY_NOOP = 0x0a, + MC_BINARY_VERSION = 0x0b, + MC_BINARY_GETK = 0x0c, + MC_BINARY_GETKQ = 0x0d, + MC_BINARY_APPEND = 0x0e, + MC_BINARY_PREPEND = 0x0f, + MC_BINARY_STAT = 0x10, + MC_BINARY_SETQ = 0x11, + MC_BINARY_ADDQ = 0x12, + MC_BINARY_REPLACEQ = 0x13, + MC_BINARY_DELETEQ = 0x14, + MC_BINARY_INCREMENTQ = 0x15, + MC_BINARY_DECREMENTQ = 0x16, + MC_BINARY_QUITQ = 0x17, + MC_BINARY_FLUSHQ = 0x18, + MC_BINARY_APPENDQ = 0x19, + MC_BINARY_PREPENDQ = 0x1a, + MC_BINARY_TOUCH = 0x1c, + MC_BINARY_GAT = 0x1d, + MC_BINARY_GATQ = 0x1e, + MC_BINARY_GATK = 0x23, + MC_BINARY_GATKQ = 0x24, + + MC_BINARY_SASL_LIST_MECHS = 0x20, + MC_BINARY_SASL_AUTH = 0x21, + MC_BINARY_SASL_STEP = 0x22, + + // These commands are used for range operations and exist within + // this header for use in other projects. Range operations are + // not expected to be implemented in the memcached server itself. + MC_BINARY_RGET = 0x30, + MC_BINARY_RSET = 0x31, + MC_BINARY_RSETQ = 0x32, + MC_BINARY_RAPPEND = 0x33, + MC_BINARY_RAPPENDQ = 0x34, + MC_BINARY_RPREPEND = 0x35, + MC_BINARY_RPREPENDQ = 0x36, + MC_BINARY_RDELETE = 0x37, + MC_BINARY_RDELETEQ = 0x38, + MC_BINARY_RINCR = 0x39, + MC_BINARY_RINCRQ = 0x3a, + MC_BINARY_RDECR = 0x3b, + MC_BINARY_RDECRQ = 0x3c + // End Range operations +}; + +struct MemcacheRequestHeader { + // Magic number identifying the package (See BinaryProtocolRevamped#Magic_Byte) + uint8_t magic; + + // Command code (See BinaryProtocolRevamped#Command_opcodes) + uint8_t command; + + // Length in bytes of the text key that follows the command extras + uint16_t key_length; + + // Length in bytes of the command extras + uint8_t extras_length; + + // Reserved for future use (See BinaryProtocolRevamped#Data_Type) + uint8_t data_type; + + // The virtual bucket for this command + uint16_t vbucket_id; + + // Length in bytes of extra + key + value + uint32_t total_body_length; + + // Will be copied back to you in the response + uint32_t opaque; + + // Data version check + uint64_t cas_value; +}; + +struct MemcacheResponseHeader { + // Magic number identifying the package (See BinaryProtocolRevamped#Magic_Byte) + uint8_t magic; + + // Command code (See BinaryProtocolRevamped#Command_opcodes) + uint8_t command; + + // Length in bytes of the text key that follows the command extras + uint16_t key_length; + + // Length in bytes of the command extras + uint8_t extras_length; + + // Reserved for future use (See BinaryProtocolRevamped#Data_Type) + uint8_t data_type; + + // Status of the response (non-zero on error) + uint16_t status; + + // Length in bytes of extra + key + value + uint32_t total_body_length; + + // Will be copied back to you in the response + uint32_t opaque; + + // Data version check + uint64_t cas_value; +}; + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_MEMCACHE_BINARY_HEADER_H diff --git a/src/brpc/policy/memcache_binary_protocol.cpp b/src/brpc/policy/memcache_binary_protocol.cpp new file mode 100644 index 0000000..e3174be --- /dev/null +++ b/src/brpc/policy/memcache_binary_protocol.cpp @@ -0,0 +1,237 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // MethodDescriptor +#include // Message +#include +#include "butil/logging.h" // LOG() +#include "butil/time.h" +#include "butil/iobuf.h" // butil::IOBuf +#include "butil/sys_byteorder.h" +#include "brpc/controller.h" // Controller +#include "brpc/details/controller_private_accessor.h" +#include "brpc/socket.h" // Socket +#include "brpc/server.h" // Server +#include "brpc/details/server_private_accessor.h" +#include "brpc/span.h" +#include "brpc/compress.h" // ParseFromCompressedData +#include "brpc/policy/memcache_binary_protocol.h" +#include "brpc/policy/memcache_binary_header.h" +#include "brpc/memcache.h" +#include "brpc/policy/most_common_message.h" +#include "butil/containers/flat_map.h" + + +namespace brpc { + +DECLARE_bool(enable_rpcz); +DECLARE_uint64(max_body_size); + +namespace policy { + +BAIDU_CASSERT(sizeof(MemcacheRequestHeader) == 24, must_match); +BAIDU_CASSERT(sizeof(MemcacheResponseHeader) == 24, must_match); + +static uint64_t supported_cmd_map[8]; +static pthread_once_t supported_cmd_map_once = PTHREAD_ONCE_INIT; + +static void InitSupportedCommandMap() { + butil::bit_array_clear(supported_cmd_map, 256); + butil::bit_array_set(supported_cmd_map, MC_BINARY_GET); + butil::bit_array_set(supported_cmd_map, MC_BINARY_SET); + butil::bit_array_set(supported_cmd_map, MC_BINARY_ADD); + butil::bit_array_set(supported_cmd_map, MC_BINARY_REPLACE); + butil::bit_array_set(supported_cmd_map, MC_BINARY_DELETE); + butil::bit_array_set(supported_cmd_map, MC_BINARY_INCREMENT); + butil::bit_array_set(supported_cmd_map, MC_BINARY_DECREMENT); + butil::bit_array_set(supported_cmd_map, MC_BINARY_FLUSH); + butil::bit_array_set(supported_cmd_map, MC_BINARY_VERSION); + butil::bit_array_set(supported_cmd_map, MC_BINARY_NOOP); + butil::bit_array_set(supported_cmd_map, MC_BINARY_APPEND); + butil::bit_array_set(supported_cmd_map, MC_BINARY_PREPEND); + butil::bit_array_set(supported_cmd_map, MC_BINARY_STAT); + butil::bit_array_set(supported_cmd_map, MC_BINARY_TOUCH); + butil::bit_array_set(supported_cmd_map, MC_BINARY_SASL_AUTH); +} + +inline bool IsSupportedCommand(uint8_t command) { + pthread_once(&supported_cmd_map_once, InitSupportedCommandMap); + return butil::bit_array_get(supported_cmd_map, command); +} + +ParseResult ParseMemcacheMessage(butil::IOBuf* source, + Socket* socket, bool /*read_eof*/, const void */*arg*/) { + while (1) { + const uint8_t* p_mcmagic = (const uint8_t*)source->fetch1(); + if (NULL == p_mcmagic) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + if (*p_mcmagic != (uint8_t)MC_MAGIC_RESPONSE) { + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + char buf[24]; + const uint8_t* p = (const uint8_t*)source->fetch(buf, sizeof(buf)); + if (NULL == p) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + const MemcacheResponseHeader* header = (const MemcacheResponseHeader*)p; + uint32_t total_body_length = butil::NetToHost32(header->total_body_length); + if (total_body_length > FLAGS_max_body_size) { + return MakeParseError(PARSE_ERROR_TOO_BIG_DATA); + } + if (source->size() < sizeof(*header) + total_body_length) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + + if (!IsSupportedCommand(header->command)) { + LOG(WARNING) << "Not support command=" << header->command; + source->pop_front(sizeof(*header) + total_body_length); + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + + PipelinedInfo pi; + if (!socket->PopPipelinedInfo(&pi)) { + LOG(WARNING) << "No corresponding PipelinedInfo in socket, drop"; + source->pop_front(sizeof(*header) + total_body_length); + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + MostCommonMessage* msg = + static_cast(socket->parsing_context()); + if (msg == NULL) { + msg = MostCommonMessage::Get(); + socket->reset_parsing_context(msg); + } + + // endianness conversions. + const MemcacheResponseHeader local_header = { + header->magic, + header->command, + butil::NetToHost16(header->key_length), + header->extras_length, + header->data_type, + butil::NetToHost16(header->status), + total_body_length, + butil::NetToHost32(header->opaque), + butil::NetToHost64(header->cas_value), + }; + msg->meta.append(&local_header, sizeof(local_header)); + source->pop_front(sizeof(*header)); + source->cutn(&msg->meta, total_body_length); + if (header->command == MC_BINARY_SASL_AUTH) { + if (header->status != 0) { + LOG(ERROR) << "Failed to authenticate the couchbase bucket."; + return MakeParseError(PARSE_ERROR_NO_RESOURCE, + "Fail to authenticate with the couchbase bucket"); + } + DestroyingPtr auth_msg( + static_cast(socket->release_parsing_context())); + socket->GivebackPipelinedInfo(pi); + } else { + if (++msg->pi.count >= pi.count) { + CHECK_EQ(msg->pi.count, pi.count); + msg = static_cast(socket->release_parsing_context()); + msg->pi = pi; + return MakeMessage(msg); + } else { + socket->GivebackPipelinedInfo(pi); + } + } + } +} + +void ProcessMemcacheResponse(InputMessageBase* msg_base) { + const int64_t start_parse_us = butil::cpuwide_time_us(); + DestroyingPtr msg(static_cast(msg_base)); + + const bthread_id_t cid = msg->pi.id_wait; + Controller* cntl = NULL; + const int rc = bthread_id_lock(cid, (void**)&cntl); + if (rc != 0) { + LOG_IF(ERROR, rc != EINVAL && rc != EPERM) + << "Fail to lock correlation_id=" << cid << ": " << berror(rc); + return; + } + + ControllerPrivateAccessor accessor(cntl); + if (auto span = accessor.span()) { + span->set_base_real_us(msg->base_real_us()); + span->set_received_us(msg->received_us()); + span->set_response_size(msg->meta.length()); + span->set_start_parse_us(start_parse_us); + } + const int saved_error = cntl->ErrorCode(); + if (cntl->response() == NULL) { + cntl->SetFailed(ERESPONSE, "response is NULL!"); + } else if (cntl->response()->GetDescriptor() != MemcacheResponse::descriptor()) { + cntl->SetFailed(ERESPONSE, "Must be MemcacheResponse"); + } else { + // We work around ParseFrom of pb which is just a placeholder. + ((MemcacheResponse*)cntl->response())->raw_buffer() = msg->meta.movable(); + if (msg->pi.count != accessor.pipelined_count()) { + cntl->SetFailed(ERESPONSE, "pipelined_count=%d of response does " + "not equal request's=%d", + msg->pi.count, accessor.pipelined_count()); + } + } + // Unlocks correlation_id inside. Revert controller's + // error code if it version check of `cid' fails + msg.reset(); // optional, just release resource ASAP + accessor.OnResponse(cid, saved_error); +} + +void SerializeMemcacheRequest(butil::IOBuf* buf, + Controller* cntl, + const google::protobuf::Message* request) { + if (request == NULL) { + return cntl->SetFailed(EREQUEST, "request is NULL"); + } + if (request->GetDescriptor() != MemcacheRequest::descriptor()) { + return cntl->SetFailed(EREQUEST, "Must be MemcacheRequest"); + } + const MemcacheRequest* mr = (const MemcacheRequest*)request; + // We work around SerializeTo of pb which is just a placeholder. + *buf = mr->raw_buffer(); + ControllerPrivateAccessor(cntl).set_pipelined_count(mr->pipelined_count()); +} + +void PackMemcacheRequest(butil::IOBuf* buf, + SocketMessage**, + uint64_t /*correlation_id*/, + const google::protobuf::MethodDescriptor*, + Controller* cntl, + const butil::IOBuf& request, + const Authenticator* auth) { + if (auth) { + std::string auth_str; + if (auth->GenerateCredential(&auth_str) != 0) { + return cntl->SetFailed(EREQUEST, "Fail to generate credential"); + } + buf->append(auth_str); + } + buf->append(request); +} + +const std::string& GetMemcacheMethodName( + const google::protobuf::MethodDescriptor*, + const Controller*) { + const static std::string MEMCACHED_STR = "memcached"; + return MEMCACHED_STR; +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/memcache_binary_protocol.h b/src/brpc/policy/memcache_binary_protocol.h new file mode 100644 index 0000000..f51b800 --- /dev/null +++ b/src/brpc/policy/memcache_binary_protocol.h @@ -0,0 +1,57 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_MEMCACHE_BINARY_PROTOCOL_H +#define BRPC_POLICY_MEMCACHE_BINARY_PROTOCOL_H + +#include "brpc/protocol.h" + + +namespace brpc { +namespace policy { + +// Parse memcache messages. +ParseResult ParseMemcacheMessage(butil::IOBuf* source, Socket *socket, bool read_eof, + const void *arg); + +// Actions to a memcache response. +void ProcessMemcacheResponse(InputMessageBase* msg); + +// Serialize a memcache request. +void SerializeMemcacheRequest(butil::IOBuf* buf, + Controller* cntl, + const google::protobuf::Message* request); + +// Pack `request' to `method' into `buf'. +void PackMemcacheRequest(butil::IOBuf* buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* controller, + const butil::IOBuf& request, + const Authenticator* auth); + +const std::string& GetMemcacheMethodName( + const google::protobuf::MethodDescriptor*, + const Controller*); + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_MEMCACHE_BINARY_PROTOCOL_H diff --git a/src/brpc/policy/mongo.proto b/src/brpc/policy/mongo.proto new file mode 100644 index 0000000..87b839b --- /dev/null +++ b/src/brpc/policy/mongo.proto @@ -0,0 +1,63 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; + +package brpc.policy; + +option cc_generic_services = true; +option java_generic_services = true; +option java_package="com.brpc.policy"; +option java_outer_classname="MongoProto"; + +enum MongoOp { + OPREPLY = 1; + DBMSG = 1000; + DB_UPDATE = 2001; + DB_INSERT = 2002; + DB_QUERY = 2004; + DB_GETMORE = 2005; + DB_DELETE = 2006; + DB_KILLCURSORS = 2007; + DB_COMMAND = 2008; + DB_COMMANDREPLY = 2009; +} + +message MongoHeader { + required int32 message_length = 1; + required int32 request_id = 2; + required int32 response_to = 3; + required MongoOp op_code = 4; +} + +message MongoRequest { + required MongoHeader header = 1; + required string message = 2; +} + +message MongoResponse { + required MongoHeader header = 1; + required int32 response_flags = 2; + required int64 cursor_id = 3; + required int32 starting_from = 4; + required int32 number_returned = 5; + required string message = 6; +} + +service MongoService { + rpc default_method(MongoRequest) returns (MongoResponse); +} diff --git a/src/brpc/policy/mongo_protocol.cpp b/src/brpc/policy/mongo_protocol.cpp new file mode 100644 index 0000000..ee41642 --- /dev/null +++ b/src/brpc/policy/mongo_protocol.cpp @@ -0,0 +1,308 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // MethodDescriptor +#include // Message +#include + +#include "butil/iobuf.h" // butil::IOBuf +#include "butil/strings/string_util.h" +#include "butil/time.h" + +#include "brpc/controller.h" // Controller +#include "brpc/socket.h" // Socket +#include "brpc/server.h" // Server +#include "brpc/span.h" +#include "brpc/mongo_head.h" +#include "brpc/details/server_private_accessor.h" +#include "brpc/details/controller_private_accessor.h" +#include "brpc/mongo_service_adaptor.h" +#include "brpc/policy/most_common_message.h" +#include "brpc/policy/nshead_protocol.h" +#include "brpc/policy/mongo.pb.h" +#include "brpc/details/usercode_backup_pool.h" + +extern "C" { +void bthread_assign_data(void* data); +} + + +namespace brpc { +namespace policy { + +struct SendMongoResponse : public google::protobuf::Closure { + SendMongoResponse(const Server *server) : + status(NULL), + received_us(0L), + server(server) {} + ~SendMongoResponse(); + void Run(); + + MethodStatus* status; + int64_t received_us; + const Server *server; + Controller cntl; + MongoRequest req; + MongoResponse res; +}; + +SendMongoResponse::~SendMongoResponse() { + LogErrorTextAndDelete(false)(&cntl); +} + +void SendMongoResponse::Run() { + std::unique_ptr delete_self(this); + ConcurrencyRemover concurrency_remover(status, &cntl, received_us); + Socket* socket = ControllerPrivateAccessor(&cntl).get_sending_socket(); + + if (cntl.IsCloseConnection()) { + socket->SetFailed(); + return; + } + + const MongoServiceAdaptor* adaptor = + server->options().mongo_service_adaptor; + butil::IOBuf res_buf; + if (cntl.Failed()) { + adaptor->SerializeError(res.header().response_to(), &res_buf); + } else if (res.has_message()) { + mongo_head_t header = { + res.header().message_length(), + res.header().request_id(), + res.header().response_to(), + res.header().op_code() + }; + res_buf.append(static_cast(&header), sizeof(mongo_head_t)); + int32_t response_flags = res.response_flags(); + int64_t cursor_id = res.cursor_id(); + int32_t starting_from = res.starting_from(); + int32_t number_returned = res.number_returned(); + res_buf.append(&response_flags, sizeof(response_flags)); + res_buf.append(&cursor_id, sizeof(cursor_id)); + res_buf.append(&starting_from, sizeof(starting_from)); + res_buf.append(&number_returned, sizeof(number_returned)); + res_buf.append(res.message()); + } + + if (!res_buf.empty()) { + // Have the risk of unlimited pending responses, in which case, tell + // users to set max_concurrency. + Socket::WriteOptions wopt; + wopt.ignore_eovercrowded = true; + if (socket->Write(&res_buf, &wopt) != 0) { + PLOG(WARNING) << "Fail to write into " << *socket; + return; + } + } +} + +ParseResult ParseMongoMessage(butil::IOBuf* source, + Socket* socket, bool /*read_eof*/, const void *arg) { + const Server* server = static_cast(arg); + // arg may be NULL when the parser is invoked outside of a full Server + // context (e.g. during protocol probing or fuzz testing). Without this + // guard, server->options() dereferences a null pointer and crashes. + if (NULL == server) { + LOG(FATAL) << "Failed creating server"; + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + const MongoServiceAdaptor* adaptor = server->options().mongo_service_adaptor; + if (NULL == adaptor) { + // The server does not enable mongo adaptor. + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + + char buf[sizeof(mongo_head_t)]; + const char *p = (const char *)source->fetch(buf, sizeof(buf)); + if (NULL == p) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + mongo_head_t header = *(const mongo_head_t*)p; + header.make_host_endian(); + if (!is_mongo_opcode(header.op_code)) { + // The op_code plays the role of "magic number" here. + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + if (header.message_length < (int32_t)sizeof(mongo_head_t)) { + // definitely not a valid mongo packet. + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + uint32_t body_len = static_cast(header.message_length); + if (body_len > FLAGS_max_body_size) { + return MakeParseError(PARSE_ERROR_TOO_BIG_DATA); + } else if (source->length() < body_len) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + // Mongo protocol is a protocol with state. Each connection has its own + // mongo context. (e.g. last error occured on the connection, the cursor + // created by the last Query). The context is stored in + // socket::_input_message, and created at the first time when msg + // comes over the socket. + Destroyable *socket_context_msg = socket->parsing_context(); + if (NULL == socket_context_msg) { + MongoContext *context = adaptor->CreateSocketContext(); + if (NULL == context) { + return MakeParseError(PARSE_ERROR_NO_RESOURCE); + } + socket_context_msg = new MongoContextMessage(context); + socket->reset_parsing_context(socket_context_msg); + } + policy::MostCommonMessage* msg = policy::MostCommonMessage::Get(); + source->cutn(&msg->meta, sizeof(buf)); + size_t act_body_len = source->cutn(&msg->payload, body_len - sizeof(buf)); + if (act_body_len != body_len - sizeof(buf)) { + CHECK(false); // Very unlikely, unless memory is corrupted. + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + return MakeMessage(msg); +} + +// Defined in baidu_rpc_protocol.cpp +void EndRunningCallMethodInPool( + ::google::protobuf::Service* service, + const ::google::protobuf::MethodDescriptor* method, + ::google::protobuf::RpcController* controller, + const ::google::protobuf::Message* request, + ::google::protobuf::Message* response, + ::google::protobuf::Closure* done); + +void ProcessMongoRequest(InputMessageBase* msg_base) { + DestroyingPtr msg(static_cast(msg_base)); + SocketUniquePtr socket_guard(msg->ReleaseSocket()); + Socket* socket = socket_guard.get(); + const Server* server = static_cast(msg_base->arg()); + ScopedNonServiceError non_service_error(server); + + char buf[sizeof(mongo_head_t)]; + const char *p = (const char *)msg->meta.fetch(buf, sizeof(buf)); + const mongo_head_t *header = (const mongo_head_t*)p; + + const google::protobuf::ServiceDescriptor* srv_des = MongoService::descriptor(); + if (1 != srv_des->method_count()) { + LOG(WARNING) << "method count:" << srv_des->method_count() + << " of MongoService should be equal to 1!"; + } + + const Server::MethodProperty *mp = + ServerPrivateAccessor(server) + .FindMethodPropertyByFullName(srv_des->method(0)->full_name()); + + MongoContextMessage *context_msg = + dynamic_cast(socket->parsing_context()); + if (NULL == context_msg) { + LOG(WARNING) << "socket context wasn't set correctly"; + return; + } + + SendMongoResponse* mongo_done = new SendMongoResponse(server); + mongo_done->cntl.set_mongo_session_data(context_msg->context()); + + ControllerPrivateAccessor accessor(&(mongo_done->cntl)); + accessor.set_server(server) + .set_security_mode(server->options().security_mode()) + .set_peer_id(socket->id()) + .set_remote_side(socket->remote_side()) + .set_local_side(socket->local_side()) + .set_auth_context(socket->auth_context()) + .set_request_protocol(PROTOCOL_MONGO) + .set_begin_time_us(msg->received_us()) + .move_in_server_receiving_sock(socket_guard); + + // Tag the bthread with this server's key for + // thread_local_data(). + if (server->thread_local_options().thread_local_data_factory) { + bthread_assign_data((void*)&server->thread_local_options()); + } + do { + if (!server->IsRunning()) { + mongo_done->cntl.SetFailed(ELOGOFF, "Server is stopping"); + break; + } + + if (!ServerPrivateAccessor(server).AddConcurrency(&(mongo_done->cntl))) { + mongo_done->cntl.SetFailed( + ELIMIT, "Reached server's max_concurrency=%d", + server->options().max_concurrency); + break; + } + if (FLAGS_usercode_in_pthread && TooManyUserCode()) { + mongo_done->cntl.SetFailed(ELIMIT, "Too many user code to run when" + " -usercode_in_pthread is on"); + break; + } + + if (NULL == mp || + mp->service->GetDescriptor() == BadMethodService::descriptor()) { + mongo_done->cntl.SetFailed(ENOMETHOD, "Fail to find default_method"); + break; + } + // Switch to service-specific error. + non_service_error.release(); + MethodStatus* method_status = mp->status; + mongo_done->status = method_status; + if (method_status) { + int rejected_cc = 0; + if (!method_status->OnRequested(&rejected_cc)) { + mongo_done->cntl.SetFailed( + ELIMIT, "Rejected by %s's ConcurrencyLimiter, concurrency=%d", + butil::EnsureString(mp->method->full_name()).c_str(), rejected_cc); + break; + } + } + + if (!MongoOp_IsValid(header->op_code)) { + mongo_done->cntl.SetFailed(EREQUEST, "Unknown op_code:%d", header->op_code); + break; + } + + mongo_done->cntl.set_log_id(header->request_id); + const std::string &body_str = msg->payload.to_string(); + mongo_done->req.set_message(body_str.c_str(), body_str.size()); + mongo_done->req.mutable_header()->set_message_length(header->message_length); + mongo_done->req.mutable_header()->set_request_id(header->request_id); + mongo_done->req.mutable_header()->set_response_to(header->response_to); + mongo_done->req.mutable_header()->set_op_code( + static_cast(header->op_code)); + mongo_done->res.mutable_header()->set_response_to(header->request_id); + mongo_done->received_us = msg->received_us(); + + google::protobuf::Service* svc = mp->service; + const google::protobuf::MethodDescriptor* method = mp->method; + accessor.set_method(method); + + if (!FLAGS_usercode_in_pthread) { + return svc->CallMethod( + method, &(mongo_done->cntl), &(mongo_done->req), + &(mongo_done->res), mongo_done); + } + if (BeginRunningUserCode()) { + return svc->CallMethod( + method, &(mongo_done->cntl), &(mongo_done->req), + &(mongo_done->res), mongo_done); + return EndRunningUserCodeInPlace(); + } else { + return EndRunningCallMethodInPool( + svc, method, &(mongo_done->cntl), &(mongo_done->req), + &(mongo_done->res), mongo_done); + } + } while (false); + + mongo_done->Run(); +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/mongo_protocol.h b/src/brpc/policy/mongo_protocol.h new file mode 100644 index 0000000..3b8e6c4 --- /dev/null +++ b/src/brpc/policy/mongo_protocol.h @@ -0,0 +1,38 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_POLICY_MONGO_PROTOCOL_H +#define BRPC_POLICY_MONGO_PROTOCOL_H + +#include "brpc/protocol.h" +#include "brpc/input_messenger.h" + + +namespace brpc { +namespace policy { + +// Parse binary format of mongo +ParseResult ParseMongoMessage(butil::IOBuf* source, Socket* socket, bool read_eof, const void *arg); + +// Actions to a (client) request in mongo format +void ProcessMongoRequest(InputMessageBase* msg); + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_MONGO_PROTOCOL_H diff --git a/src/brpc/policy/most_common_message.h b/src/brpc/policy/most_common_message.h new file mode 100644 index 0000000..ef682a7 --- /dev/null +++ b/src/brpc/policy/most_common_message.h @@ -0,0 +1,55 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_MOST_COMMON_MESSAGE_H +#define BRPC_POLICY_MOST_COMMON_MESSAGE_H + +#include "butil/object_pool.h" +#include "brpc/input_messenger.h" + + +namespace brpc { +namespace policy { + +// Try to use this message as the intermediate message between Parse() and +// Process() to maximize usage of ObjectPool, otherwise +// you have to new the messages or use a separate ObjectPool (which is likely +// to waste more memory) +struct BAIDU_CACHELINE_ALIGNMENT MostCommonMessage : public InputMessageBase { + butil::IOBuf meta; + butil::IOBuf payload; + PipelinedInfo pi; + + inline static MostCommonMessage* Get() { + return butil::get_object(); + } + + // @InputMessageBase + void DestroyImpl() { + meta.clear(); + payload.clear(); + pi.reset(); + butil::return_object(this); + } +}; + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_MOST_COMMON_MESSAGE_H diff --git a/src/brpc/policy/mysql/mysql.cpp b/src/brpc/policy/mysql/mysql.cpp new file mode 100644 index 0000000..154f739 --- /dev/null +++ b/src/brpc/policy/mysql/mysql.cpp @@ -0,0 +1,524 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Authors: Yang,Liming (yangliming01@baidu.com) + +#define INTERNAL_SUPPRESS_PROTOBUF_FIELD_DEPRECATION +#include +#include +#include "butil/string_printf.h" +#include "butil/macros.h" +#include "butil/logging.h" +#include "brpc/controller.h" +#include "brpc/policy/mysql/mysql.h" +#include "brpc/policy/mysql/mysql_common.h" + +namespace brpc { + +DEFINE_int32(mysql_multi_replies_size, 10, "multi replies size in one MysqlResponse"); + +butil::Status MysqlStatementStub::PackExecuteCommand(butil::IOBuf* outbuf, uint32_t stmt_id) { + butil::Status st; + // long data + for (const auto& i : _long_data) { + st = MysqlMakeLongDataPacket(outbuf, stmt_id, i.param_id, i.long_data); + if (!st.ok()) { + LOG(ERROR) << "make long data header error " << st; + return st; + } + } + _long_data.clear(); + // execute data + st = MysqlMakeExecutePacket(outbuf, stmt_id, _execute_data); + if (!st.ok()) { + LOG(ERROR) << "make execute header error " << st; + return st; + } + _execute_data.clear(); + _null_mask.mask.clear(); + _null_mask.area = butil::IOBuf::INVALID_AREA; + _param_types.types.clear(); + _param_types.area = butil::IOBuf::INVALID_AREA; + + return st; +} + +MysqlRequest::MysqlRequest() + : NonreflectableMessage() { + SharedCtor(); +} + +MysqlRequest::MysqlRequest(const MysqlTransaction* tx) + : NonreflectableMessage() { + SharedCtor(); + _tx = tx; +} + +MysqlRequest::MysqlRequest(MysqlStatement* stmt) + : NonreflectableMessage() { + SharedCtor(); + _stmt = new MysqlStatementStub(stmt); +} + +MysqlRequest::MysqlRequest(const MysqlTransaction* tx, MysqlStatement* stmt) + : NonreflectableMessage() { + SharedCtor(); + _tx = tx; + _stmt = new MysqlStatementStub(stmt); +} + +MysqlRequest::MysqlRequest(const MysqlRequest& from) + : NonreflectableMessage(from) { + SharedCtor(); + MergeFrom(from); +} + +void MysqlRequest::SharedCtor() { + _has_error = false; + _cached_size_ = 0; + _has_command = false; + _tx = NULL; + _stmt = NULL; + _param_index = 0; +} + +MysqlRequest::~MysqlRequest() { + SharedDtor(); + if (_stmt != NULL) { + delete _stmt; + } + _stmt = NULL; +} + +void MysqlRequest::SharedDtor() { +} + +void MysqlRequest::SetCachedSize(int size) const { + _cached_size_ = size; +} + +void MysqlRequest::Clear() { + _has_error = false; + _buf.clear(); + _has_command = false; + _tx = NULL; + if (_stmt) { + delete _stmt; + _stmt = NULL; + } + _param_index = 0; +} + +size_t MysqlRequest::ByteSizeLong() const { + int total_size = _buf.size(); + _cached_size_ = total_size; + return total_size; +} + +void MysqlRequest::MergeFrom(const MysqlRequest& from) { + if (&from == this) { + return; + } + _has_command = from._has_command; + _has_error = from._has_error; + _buf = from._buf; + _cached_size_ = from._cached_size_; + _param_index = from._param_index; + // _tx is a non-owning pointer (never deleted by MysqlRequest): shallow copy. + _tx = from._tx; + // _stmt is owned (deleted in the dtor): deep-copy to avoid double free. + if (_stmt != NULL) { + delete _stmt; + _stmt = NULL; + } + if (from._stmt != NULL) { + _stmt = new MysqlStatementStub(*from._stmt); + } +} + +void MysqlRequest::Swap(MysqlRequest* other) { + if (other != this) { + _buf.swap(other->_buf); + std::swap(_has_error, other->_has_error); + std::swap(_cached_size_, other->_cached_size_); + std::swap(_has_command, other->_has_command); + std::swap(_tx, other->_tx); + std::swap(_stmt, other->_stmt); + std::swap(_param_index, other->_param_index); + } +} + +bool MysqlRequest::SerializeTo(butil::IOBuf* buf) const { + if (_has_error) { + LOG(ERROR) << "Reject serialization due to error in CommandXXX[V]"; + return false; + } + *buf = _buf; + return true; +} + +bool MysqlRequest::Query(const butil::StringPiece& command) { + if (_has_error) { + return false; + } + + if (_has_command) { + LOG(WARNING) << "MysqlRequest::Query: a command was already set on this request"; + return false; + } + + const butil::Status st = MysqlMakeCommand(&_buf, MYSQL_COM_QUERY, command); + if (st.ok()) { + _has_command = true; + return true; + } else { + CHECK(st.ok()) << st; + _has_error = true; + return false; + } +} + +bool MysqlRequest::AddParam(int8_t p) { + if (_has_error) { + return false; + } + if (_stmt == NULL || _stmt->stmt() == NULL) { + LOG(WARNING) << "MysqlRequest::AddParam(int8_t): no prepared statement bound to request"; + _has_error = true; + return false; + } + const butil::Status st = MysqlMakeExecuteData(_stmt, _param_index, &p, MYSQL_FIELD_TYPE_TINY); + if (st.ok()) { + ++_param_index; + return true; + } else { + CHECK(st.ok()) << st; + _has_error = true; + return false; + } +} +bool MysqlRequest::AddParam(uint8_t p) { + if (_stmt == NULL || _stmt->stmt() == NULL) { + LOG(WARNING) << "MysqlRequest::AddParam(uint8_t): no prepared statement bound to request"; + _has_error = true; + return false; + } + const butil::Status st = + MysqlMakeExecuteData(_stmt, _param_index, &p, MYSQL_FIELD_TYPE_TINY, true); + if (st.ok()) { + ++_param_index; + return true; + } else { + CHECK(st.ok()) << st; + _has_error = true; + return false; + } +} +bool MysqlRequest::AddParam(int16_t p) { + if (_stmt == NULL || _stmt->stmt() == NULL) { + LOG(WARNING) << "MysqlRequest::AddParam(int16_t): no prepared statement bound to request"; + _has_error = true; + return false; + } + const butil::Status st = MysqlMakeExecuteData(_stmt, _param_index, &p, MYSQL_FIELD_TYPE_SHORT); + if (st.ok()) { + ++_param_index; + return true; + } else { + CHECK(st.ok()) << st; + _has_error = true; + return false; + } +} +bool MysqlRequest::AddParam(uint16_t p) { + if (_stmt == NULL || _stmt->stmt() == NULL) { + LOG(WARNING) << "MysqlRequest::AddParam(uint16_t): no prepared statement bound to request"; + _has_error = true; + return false; + } + const butil::Status st = + MysqlMakeExecuteData(_stmt, _param_index, &p, MYSQL_FIELD_TYPE_SHORT, true); + if (st.ok()) { + ++_param_index; + return true; + } else { + CHECK(st.ok()) << st; + _has_error = true; + return false; + } +} +bool MysqlRequest::AddParam(int32_t p) { + if (_stmt == NULL || _stmt->stmt() == NULL) { + LOG(WARNING) << "MysqlRequest::AddParam(int32_t): no prepared statement bound to request"; + _has_error = true; + return false; + } + const butil::Status st = MysqlMakeExecuteData(_stmt, _param_index, &p, MYSQL_FIELD_TYPE_LONG); + if (st.ok()) { + ++_param_index; + return true; + } else { + CHECK(st.ok()) << st; + _has_error = true; + return false; + } +} +bool MysqlRequest::AddParam(uint32_t p) { + if (_stmt == NULL || _stmt->stmt() == NULL) { + LOG(WARNING) << "MysqlRequest::AddParam(uint32_t): no prepared statement bound to request"; + _has_error = true; + return false; + } + const butil::Status st = + MysqlMakeExecuteData(_stmt, _param_index, &p, MYSQL_FIELD_TYPE_LONG, true); + if (st.ok()) { + ++_param_index; + return true; + } else { + CHECK(st.ok()) << st; + _has_error = true; + return false; + } +} +bool MysqlRequest::AddParam(int64_t p) { + if (_stmt == NULL || _stmt->stmt() == NULL) { + LOG(WARNING) << "MysqlRequest::AddParam(int64_t): no prepared statement bound to request"; + _has_error = true; + return false; + } + const butil::Status st = + MysqlMakeExecuteData(_stmt, _param_index, &p, MYSQL_FIELD_TYPE_LONGLONG); + if (st.ok()) { + ++_param_index; + return true; + } else { + CHECK(st.ok()) << st; + _has_error = true; + return false; + } +} +bool MysqlRequest::AddParam(uint64_t p) { + if (_stmt == NULL || _stmt->stmt() == NULL) { + LOG(WARNING) << "MysqlRequest::AddParam(uint64_t): no prepared statement bound to request"; + _has_error = true; + return false; + } + const butil::Status st = + MysqlMakeExecuteData(_stmt, _param_index, &p, MYSQL_FIELD_TYPE_LONGLONG, true); + if (st.ok()) { + ++_param_index; + return true; + } else { + CHECK(st.ok()) << st; + _has_error = true; + return false; + } +} +bool MysqlRequest::AddParam(float p) { + if (_stmt == NULL || _stmt->stmt() == NULL) { + LOG(WARNING) << "MysqlRequest::AddParam(float): no prepared statement bound to request"; + _has_error = true; + return false; + } + const butil::Status st = MysqlMakeExecuteData(_stmt, _param_index, &p, MYSQL_FIELD_TYPE_FLOAT); + if (st.ok()) { + ++_param_index; + return true; + } else { + CHECK(st.ok()) << st; + _has_error = true; + return false; + } +} +bool MysqlRequest::AddParam(double p) { + if (_stmt == NULL || _stmt->stmt() == NULL) { + LOG(WARNING) << "MysqlRequest::AddParam(double): no prepared statement bound to request"; + _has_error = true; + return false; + } + const butil::Status st = MysqlMakeExecuteData(_stmt, _param_index, &p, MYSQL_FIELD_TYPE_DOUBLE); + if (st.ok()) { + ++_param_index; + return true; + } else { + CHECK(st.ok()) << st; + _has_error = true; + return false; + } +} +bool MysqlRequest::AddParam(const butil::StringPiece& p) { + if (_stmt == NULL || _stmt->stmt() == NULL) { + LOG(WARNING) << "MysqlRequest::AddParam(StringPiece): no prepared statement bound to request"; + _has_error = true; + return false; + } + const butil::Status st = MysqlMakeExecuteData(_stmt, _param_index, &p, MYSQL_FIELD_TYPE_STRING); + if (st.ok()) { + ++_param_index; + return true; + } else { + CHECK(st.ok()) << st; + _has_error = true; + return false; + } +} + +void MysqlRequest::Print(std::ostream& os) const { + butil::IOBuf cp = _buf; + { + uint8_t buf[3]; + cp.cutn(buf, 3); + os << "size:" << mysql_uint3korr(buf) << ","; + } + { + uint8_t buf; + cp.cut1((char*)&buf); + os << "sequence:" << (unsigned)buf << ","; + } + os << "payload(hex):"; + while (!cp.empty()) { + uint8_t buf; + cp.cut1((char*)&buf); + os << std::hex << (unsigned)buf; + } +} + +std::ostream& operator<<(std::ostream& os, const MysqlRequest& r) { + r.Print(os); + return os; +} + +MysqlResponse::MysqlResponse() + : NonreflectableMessage() { + SharedCtor(); +} + +void MysqlResponse::SharedCtor() { + _nreply = 0; + _cached_size_ = 0; +} + +MysqlResponse::~MysqlResponse() { + SharedDtor(); +} + +void MysqlResponse::SharedDtor() { +} + +void MysqlResponse::SetCachedSize(int size) const { + _cached_size_ = size; +} + +void MysqlResponse::Clear() { + MysqlReply empty_reply; + _first_reply.Swap(empty_reply); + _other_replies.clear(); + _arena.clear(); + _nreply = 0; + _cached_size_ = 0; +} + +size_t MysqlResponse::ByteSizeLong() const { + return _cached_size_; +} + +void MysqlResponse::MergeFrom(const MysqlResponse&) { + CHECK(false) << "MysqlResponse does not support MergeFrom/CopyFrom"; +} + +bool MysqlResponse::IsInitialized() const { + return true; +} + +void MysqlResponse::Swap(MysqlResponse* other) { + if (other != this) { + _first_reply.Swap(other->_first_reply); + std::swap(_other_replies, other->_other_replies); + _arena.swap(other->_arena); + std::swap(_nreply, other->_nreply); + std::swap(_cached_size_, other->_cached_size_); + } +} + +ParseError MysqlResponse::ConsumePartialIOBuf(butil::IOBuf& buf, + bool is_auth, + MysqlStmtType stmt_type) { + bool more_results = true; + size_t oldsize = 0; + while (more_results) { + oldsize = buf.size(); + if (reply_size() == 0) { + ParseError err = + _first_reply.ConsumePartialIOBuf(buf, &_arena, is_auth, stmt_type, &more_results); + if (err != PARSE_OK) { + return err; + } + } else { + const int32_t replies_size = + FLAGS_mysql_multi_replies_size > 1 ? FLAGS_mysql_multi_replies_size : 10; + if (_other_replies.size() < reply_size()) { + MysqlReply* replies = + (MysqlReply*)_arena.allocate(sizeof(MysqlReply) * (replies_size - 1)); + if (replies == NULL) { + LOG(ERROR) << "Fail to allocate MysqlReply[" << replies_size - 1 << "]"; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + _other_replies.reserve(replies_size - 1); + for (int i = 0; i < replies_size - 1; ++i) { + new (&replies[i]) MysqlReply; + _other_replies.push_back(&replies[i]); + } + } + ParseError err = _other_replies[_nreply - 1]->ConsumePartialIOBuf( + buf, &_arena, is_auth, stmt_type, &more_results); + if (err != PARSE_OK) { + return err; + } + } + + const size_t newsize = buf.size(); + _cached_size_ += oldsize - newsize; + oldsize = newsize; + ++_nreply; + } + + if (oldsize == 0) { + return PARSE_OK; + } else { + LOG(ERROR) << "Parse protocol finished, but IOBuf has more data"; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } +} + +std::ostream& operator<<(std::ostream& os, const MysqlResponse& response) { + os << "\n-----MYSQL REPLY BEGIN-----\n"; + if (response.reply_size() == 0) { + os << ""; + } else if (response.reply_size() == 1) { + os << response.reply(0); + } else { + for (size_t i = 0; i < response.reply_size(); ++i) { + os << "\nreply(" << i << ")----------"; + os << response.reply(i); + } + } + os << "\n-----MYSQL REPLY END-----\n"; + + return os; +} + +} // namespace brpc diff --git a/src/brpc/policy/mysql/mysql.h b/src/brpc/policy/mysql/mysql.h new file mode 100644 index 0000000..d55086e --- /dev/null +++ b/src/brpc/policy/mysql/mysql.h @@ -0,0 +1,244 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Authors: Yang,Liming (yangliming01@baidu.com) + +#ifndef BRPC_MYSQL_H +#define BRPC_MYSQL_H + +#include +#include + +#include "brpc/nonreflectable_message.h" +#include "brpc/pb_compat.h" +#include "butil/iobuf.h" +#include "butil/strings/string_piece.h" +#include "butil/arena.h" +#include "brpc/parse_result.h" +#include "brpc/policy/mysql/mysql_command.h" +#include "brpc/policy/mysql/mysql_reply.h" +#include "brpc/policy/mysql/mysql_transaction.h" +#include "brpc/policy/mysql/mysql_statement.h" + +namespace brpc { +// Request to mysql. +// Notice that you can pipeline multiple commands in one request and sent +// them to ONE mysql-server together. +// Example: +// MysqlRequest request; +// request.Query("select * from table"); +// MysqlResponse response; +// channel.CallMethod(NULL, &controller, &request, &response, NULL/*done*/); +// if (!cntl.Failed()) { +// LOG(INFO) << response.reply(0); +// } + +class MysqlStatementStub { +public: + MysqlStatementStub(MysqlStatement* stmt); + MysqlStatement* stmt(); + butil::IOBuf& execute_data(); + butil::Status PackExecuteCommand(butil::IOBuf* outbuf, uint32_t stmt_id); + // prepare statement null mask + struct NullMask { + NullMask() : area(butil::IOBuf::INVALID_AREA) {} + std::vector mask; + butil::IOBuf::Area area; + }; + // prepare statement param types + struct ParamTypes { + ParamTypes() : area(butil::IOBuf::INVALID_AREA) {} + std::vector types; + butil::IOBuf::Area area; + }; + // null mask and param types + NullMask& null_mask(); + ParamTypes& param_types(); + // save long data + void save_long_data(uint16_t param_id, const butil::StringPiece& value); + +private: + MysqlStatement* _stmt; + butil::IOBuf _execute_data; + NullMask _null_mask; + ParamTypes _param_types; + // long data + struct LongData { + uint16_t param_id; + butil::IOBuf long_data; + }; + std::vector _long_data; +}; + +inline MysqlStatementStub::MysqlStatementStub(MysqlStatement* stmt) : _stmt(stmt) {} + +inline MysqlStatement* MysqlStatementStub::stmt() { + return _stmt; +} + +inline butil::IOBuf& MysqlStatementStub::execute_data() { + return _execute_data; +} + +inline MysqlStatementStub::NullMask& MysqlStatementStub::null_mask() { + return _null_mask; +} + +inline MysqlStatementStub::ParamTypes& MysqlStatementStub::param_types() { + return _param_types; +} + +inline void MysqlStatementStub::save_long_data(uint16_t param_id, const butil::StringPiece& value) { + LongData d; + d.param_id = param_id; + d.long_data.append(value.data(), value.size()); + _long_data.push_back(d); +} + +class MysqlRequest : public NonreflectableMessage { +public: + MysqlRequest(); + MysqlRequest(const MysqlTransaction* tx); + MysqlRequest(MysqlStatement* stmt); + MysqlRequest(const MysqlTransaction* tx, MysqlStatement* stmt); + ~MysqlRequest() override; + MysqlRequest(const MysqlRequest& from); + inline MysqlRequest& operator=(const MysqlRequest& from) { + CopyFrom(from); + return *this; + } + void Swap(MysqlRequest* other); + + // Serialize the request into `buf'. Return true on success. + bool SerializeTo(butil::IOBuf* buf) const; + + // Protobuf methods. + void MergeFrom(const MysqlRequest& from) override; + void Clear() override; + + size_t ByteSizeLong() const override; + int GetCachedSize() const PB_425_OVERRIDE { + return _cached_size_; + } + + // call query command + bool Query(const butil::StringPiece& command); + // add statement params + bool AddParam(int8_t p); + bool AddParam(uint8_t p); + bool AddParam(int16_t p); + bool AddParam(uint16_t p); + bool AddParam(int32_t p); + bool AddParam(uint32_t p); + bool AddParam(int64_t p); + bool AddParam(uint64_t p); + bool AddParam(float p); + bool AddParam(double p); + bool AddParam(const butil::StringPiece& p); + + // True if previous command failed. + bool has_error() const { + return _has_error; + } + + const MysqlTransaction* tx() const { + return _tx; + } + + MysqlStatementStub* stmt() const { + return _stmt; + } + + void Print(std::ostream&) const; + +private: + void SharedCtor(); + void SharedDtor(); + void SetCachedSize(int size) const PB_425_OVERRIDE; + + bool _has_command; // request has command + bool _has_error; // previous AddCommand had error + butil::IOBuf _buf; // the serialized request. + mutable int _cached_size_; // ByteSize + const MysqlTransaction* _tx; // transaction + MysqlStatementStub* _stmt; // statement + uint16_t _param_index; // statement param index +}; + +// Response from Mysql. +// Notice that a MysqlResponse instance may contain multiple replies +// due to pipelining. +class MysqlResponse : public NonreflectableMessage { +public: + MysqlResponse(); + ~MysqlResponse() override; + // MysqlResponse does not support deep copy: MergeFrom() is a CHECK-fail + // (see mysql.cpp), so CopyFrom()/copy-construction would silently drop all + // parsed replies. Make the class explicitly non-copyable. + MysqlResponse(const MysqlResponse& from) = delete; + MysqlResponse& operator=(const MysqlResponse& from) = delete; + void Swap(MysqlResponse* other); + // Parse and consume intact replies from the buf, actual reply size may less then max_count, if + // some command execute failed + // Returns PARSE_OK on success. + // Returns PARSE_ERROR_NOT_ENOUGH_DATA if data in `buf' is not enough to parse. + // Returns PARSE_ERROR_ABSOLUTELY_WRONG if the parsing + // failed. + ParseError ConsumePartialIOBuf(butil::IOBuf& buf, bool is_auth, MysqlStmtType stmt_type); + + // Number of replies in this response. + // (May have more than one reply due to pipeline) + size_t reply_size() const { + return _nreply; + } + + const MysqlReply& reply(size_t index) const { + if (index < reply_size()) { + return (index == 0 ? _first_reply : *_other_replies[index - 1]); + } + static MysqlReply mysql_nil; + return mysql_nil; + } + // implements Message ---------------------------------------------- + + void MergeFrom(const MysqlResponse& from) override; + void Clear() override; + bool IsInitialized() const PB_527_OVERRIDE; + + size_t ByteSizeLong() const override; + int GetCachedSize() const PB_425_OVERRIDE { + return 0; + } + +private: + void SharedCtor(); + void SharedDtor(); + void SetCachedSize(int size) const PB_425_OVERRIDE; + + MysqlReply _first_reply; + std::vector _other_replies; + butil::Arena _arena; + size_t _nreply; + mutable int _cached_size_; +}; + +std::ostream& operator<<(std::ostream& os, const MysqlRequest&); +std::ostream& operator<<(std::ostream& os, const MysqlResponse&); + +} // namespace brpc + +#endif // BRPC_MYSQL_H diff --git a/src/brpc/policy/mysql/mysql_auth_handshake.cpp b/src/brpc/policy/mysql/mysql_auth_handshake.cpp new file mode 100644 index 0000000..1b73e87 --- /dev/null +++ b/src/brpc/policy/mysql/mysql_auth_handshake.cpp @@ -0,0 +1,305 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/mysql/mysql_auth_handshake.h" + +#include + +#include "brpc/policy/mysql/mysql_auth_packet.h" +#include "brpc/policy/mysql/mysql_auth_scramble.h" +#include "butil/logging.h" + +namespace brpc { +namespace policy { +namespace mysql { + +namespace { + +// MySQL HandshakeV10 fixed-size pieces and constants. +const size_t kAuthPluginDataPart1Len = 8; +const size_t kReservedAfterCapsLen = 10; +const size_t kFillerAfterPart1Len = 1; +const size_t kReservedInResponseLen = 23; + +// Reads N little-endian bytes from |buf| at |off| into |out|. +template +bool ReadLE(const butil::StringPiece& buf, size_t off, size_t n, T* out) { + if (off + n > buf.size()) return false; + T v = 0; + for (size_t i = 0; i < n; ++i) { + v |= static_cast(static_cast(buf[off + i])) << (8 * i); + } + *out = v; + return true; +} + +template +void WriteLE(T value, size_t n, std::string* out) { + for (size_t i = 0; i < n; ++i) { + out->push_back(static_cast((value >> (8 * i)) & 0xff)); + } +} + +} // namespace + +bool ParseHandshakeV10(const butil::StringPiece& payload, HandshakeV10* out) { + if (payload.empty()) { + LOG(ERROR) << "ParseHandshakeV10: empty payload"; + return false; + } + + size_t off = 0; + out->protocol_version = static_cast(payload[off++]); + if (out->protocol_version != kHandshakeV10Tag) { + LOG(ERROR) << "ParseHandshakeV10: unexpected protocol_version=" + << static_cast(out->protocol_version) << ", expected " + << static_cast(kHandshakeV10Tag); + return false; + } + + // server_version: NUL-terminated string + std::string version; + { + const butil::StringPiece rest(payload.data() + off, + payload.size() - off); + const size_t consumed = DecodeNullTerminatedString(rest, &version); + if (consumed == 0) { + LOG(ERROR) << "ParseHandshakeV10: unterminated server_version string"; + return false; + } + off += consumed; + } + out->server_version = std::move(version); + + // connection_id: 4 LE bytes + if (!ReadLE(payload, off, 4, &out->connection_id)) { + LOG(ERROR) << "ParseHandshakeV10: truncated before connection_id"; + return false; + } + off += 4; + + // auth-plugin-data-part-1: 8 bytes + if (off + kAuthPluginDataPart1Len > payload.size()) { + LOG(ERROR) << "ParseHandshakeV10: truncated before " + "auth-plugin-data-part-1"; + return false; + } + std::string salt(payload.data() + off, kAuthPluginDataPart1Len); + off += kAuthPluginDataPart1Len; + + // filler 0x00 + if (off + kFillerAfterPart1Len > payload.size()) { + LOG(ERROR) << "ParseHandshakeV10: truncated before filler after " + "auth-plugin-data-part-1"; + return false; + } + off += kFillerAfterPart1Len; + + // capability flags (lower 2 bytes) + uint16_t caps_lo = 0; + if (!ReadLE(payload, off, 2, &caps_lo)) { + LOG(ERROR) << "ParseHandshakeV10: truncated before capability flags " + "(lower 2 bytes)"; + return false; + } + off += 2; + out->capability_flags = caps_lo; + + if (off == payload.size()) { + // Pre-4.1 server. We don't support these — bail. + LOG(ERROR) << "ParseHandshakeV10: pre-4.1 server not supported"; + return false; + } + + // character_set + if (off >= payload.size()) { + LOG(ERROR) << "ParseHandshakeV10: truncated before character_set"; + return false; + } + out->character_set = static_cast(payload[off++]); + + // status_flags + if (!ReadLE(payload, off, 2, &out->status_flags)) { + LOG(ERROR) << "ParseHandshakeV10: truncated before status_flags"; + return false; + } + off += 2; + + // capability flags upper 2 bytes + uint16_t caps_hi = 0; + if (!ReadLE(payload, off, 2, &caps_hi)) { + LOG(ERROR) << "ParseHandshakeV10: truncated before capability flags " + "(upper 2 bytes)"; + return false; + } + off += 2; + out->capability_flags |= static_cast(caps_hi) << 16; + + // length of auth-plugin-data (or 0x00 when CLIENT_PLUGIN_AUTH is absent) + if (off >= payload.size()) { + LOG(ERROR) << "ParseHandshakeV10: truncated before " + "auth-plugin-data length"; + return false; + } + const uint8_t apd_total_len = static_cast(payload[off++]); + + // 10 reserved bytes (all 0x00) + if (off + kReservedAfterCapsLen > payload.size()) { + LOG(ERROR) << "ParseHandshakeV10: truncated before 10 reserved bytes"; + return false; + } + off += kReservedAfterCapsLen; + + if (out->capability_flags & CLIENT_SECURE_CONNECTION) { + // auth-plugin-data-part-2: max(13, apd_total_len - 8) bytes. Modern + // servers send 13 (12 salt bytes + 1 NUL filler). + const size_t part2_len = apd_total_len > kAuthPluginDataPart1Len + ? static_cast(apd_total_len) - kAuthPluginDataPart1Len + : static_cast(13); + const size_t want = part2_len < 13 ? 13 : part2_len; + if (off + want > payload.size()) { + LOG(ERROR) << "ParseHandshakeV10: truncated auth-plugin-data-part-2," + " want " << want << " bytes, have " + << (payload.size() - off); + return false; + } + // Concat salt parts; trim trailing NUL filler so callers see the + // raw 20-byte salt. + salt.append(payload.data() + off, want); + off += want; + if (!salt.empty() && salt.back() == '\0') { + salt.pop_back(); + } + } + if (salt.size() != kSaltLen) { + LOG(ERROR) << "ParseHandshakeV10: auth-plugin-data length mismatch, got " + << salt.size() << " expected " << kSaltLen; + return false; + } + out->auth_plugin_data = std::move(salt); + + if (out->capability_flags & CLIENT_PLUGIN_AUTH) { + std::string name; + const butil::StringPiece rest(payload.data() + off, + payload.size() - off); + const size_t consumed = DecodeNullTerminatedString(rest, &name); + // Some servers omit the trailing NUL; tolerate by treating the + // remainder of the payload as the plugin name. + if (consumed == 0) { + out->auth_plugin_name.assign(rest.data(), rest.size()); + } else { + out->auth_plugin_name = std::move(name); + } + } + + return true; +} + +bool BuildHandshakeResponse41(const HandshakeResponse41& req, std::string* out) { + // The CLIENT_SECURE_CONNECTION encoding prefixes auth_response with a + // single length byte, so it cannot represent a payload larger than 255 + // bytes. Validate this FIRST and fail hard rather than silently + // truncating: a truncated auth_response is invalid and would + // desynchronize the packet stream. Larger payloads (e.g. RSA + // ciphertext) require the caller to negotiate + // CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA, which has no such limit. + const bool lenenc_client_data = + req.capability_flags & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA; + if (!lenenc_client_data && + (req.capability_flags & CLIENT_SECURE_CONNECTION) && + req.auth_response.size() > 0xff) { + LOG(ERROR) << "Cannot build HandshakeResponse41: auth_response is " + << req.auth_response.size() << " bytes, exceeding the " + "255-byte CLIENT_SECURE_CONNECTION length prefix; " + "negotiate CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA for " + "larger payloads"; + return false; + } + + WriteLE(req.capability_flags, 4, out); + WriteLE(req.max_packet_size, 4, out); + out->push_back(static_cast(req.character_set)); + out->append(kReservedInResponseLen, '\0'); + out->append(req.username); + out->push_back('\0'); + + if (lenenc_client_data) { + EncodeLengthEncodedString(req.auth_response, out); + } else if (req.capability_flags & CLIENT_SECURE_CONNECTION) { + // Length validated above to fit in a single byte. + const uint8_t len = static_cast(req.auth_response.size()); + out->push_back(static_cast(len)); + out->append(req.auth_response.data(), req.auth_response.size()); + } else { + out->append(req.auth_response); + out->push_back('\0'); + } + + if (req.capability_flags & CLIENT_CONNECT_WITH_DB) { + out->append(req.database); + out->push_back('\0'); + } + + if (req.capability_flags & CLIENT_PLUGIN_AUTH) { + out->append(req.auth_plugin_name); + out->push_back('\0'); + } + return true; +} + +bool ParseAuthSwitchRequest(const butil::StringPiece& payload, + AuthSwitchRequest* out) { + if (payload.empty() || + static_cast(payload[0]) != kAuthSwitchRequestTag) { + LOG(ERROR) << "ParseAuthSwitchRequest: empty payload or missing " + "AuthSwitchRequest tag"; + return false; + } + size_t off = 1; + std::string name; + const butil::StringPiece rest(payload.data() + off, payload.size() - off); + const size_t consumed = DecodeNullTerminatedString(rest, &name); + if (consumed == 0) { + LOG(ERROR) << "ParseAuthSwitchRequest: unterminated auth_plugin_name " + "string"; + return false; + } + off += consumed; + out->auth_plugin_name = std::move(name); + + // Remainder is auth-plugin-data; trim a single trailing NUL filler. + out->auth_plugin_data.assign(payload.data() + off, payload.size() - off); + if (!out->auth_plugin_data.empty() && out->auth_plugin_data.back() == '\0') { + out->auth_plugin_data.pop_back(); + } + return true; +} + +bool ParseAuthMoreData(const butil::StringPiece& payload, AuthMoreData* out) { + if (payload.empty() || + static_cast(payload[0]) != kAuthMoreDataTag) { + LOG(ERROR) << "ParseAuthMoreData: empty payload or missing " + "AuthMoreData tag"; + return false; + } + out->data.assign(payload.data() + 1, payload.size() - 1); + return true; +} + +} // namespace mysql +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/mysql/mysql_auth_handshake.h b/src/brpc/policy/mysql/mysql_auth_handshake.h new file mode 100644 index 0000000..98232ab --- /dev/null +++ b/src/brpc/policy/mysql/mysql_auth_handshake.h @@ -0,0 +1,137 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Codec for the four MySQL connection-phase packets the client touches +// during authentication. All functions operate on raw packet payloads +// (without the 4-byte packet header); the caller is responsible for +// framing. Specifications: +// HandshakeV10: +// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake_v10.html +// HandshakeResponse41: +// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake_response.html +// AuthSwitchRequest / AuthMoreData: +// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_switch_request.html +// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_more_data.html + +#ifndef BRPC_POLICY_MYSQL_MYSQL_AUTH_HANDSHAKE_H +#define BRPC_POLICY_MYSQL_MYSQL_AUTH_HANDSHAKE_H + +#include + +#include + +#include "butil/strings/string_piece.h" + +namespace brpc { +namespace policy { +namespace mysql { + +// Subset of MySQL capability flags we recognize. +enum CapabilityFlag : uint32_t { + CLIENT_LONG_PASSWORD = 0x00000001, + CLIENT_LONG_FLAG = 0x00000004, + CLIENT_CONNECT_WITH_DB = 0x00000008, + CLIENT_PROTOCOL_41 = 0x00000200, + CLIENT_TRANSACTIONS = 0x00002000, + CLIENT_SECURE_CONNECTION = 0x00008000, + CLIENT_PLUGIN_AUTH = 0x00080000, + CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA = 0x00200000, + CLIENT_DEPRECATE_EOF = 0x01000000, +}; + +// The leading status byte of an authentication-related packet. Used +// by callers to dispatch a packet payload to the right parser before +// invoking any of the functions below. +enum PacketTag : uint8_t { + kHandshakeV10Tag = 0x0a, + kAuthSwitchRequestTag = 0xfe, + kAuthMoreDataTag = 0x01, + kOkPacketTag = 0x00, + kErrPacketTag = 0xff, +}; + +// Parsed HandshakeV10 (server greeting). +struct HandshakeV10 { + uint8_t protocol_version; // always 10 + std::string server_version; // human-readable, NUL-terminated on wire + uint32_t connection_id; + std::string auth_plugin_data; // 20-byte salt (parts 1 + 2 concatenated) + uint32_t capability_flags; // upper 16 bits OR'd in when present + uint8_t character_set; + uint16_t status_flags; + std::string auth_plugin_name; // e.g., "mysql_native_password" +}; + +// Parses |payload| (a packet body without the 4-byte header) as a +// HandshakeV10. Returns true on success. Rejects packets whose +// protocol_version is not 10 or whose salt is not 20 bytes long. +bool ParseHandshakeV10(const butil::StringPiece& payload, HandshakeV10* out); + +// Inputs for building a HandshakeResponse41 payload. The caller is +// expected to have already negotiated capability_flags against the +// server's advertised flags and computed the scrambled auth_response. +struct HandshakeResponse41 { + uint32_t capability_flags; + uint32_t max_packet_size; + uint8_t character_set; + std::string username; + std::string auth_response; // bytes from NativePasswordScramble, + // CachingSha2PasswordScramble, etc. + std::string database; // omitted when CLIENT_CONNECT_WITH_DB + // is not in capability_flags + std::string auth_plugin_name; // included when CLIENT_PLUGIN_AUTH + // is in capability_flags +}; + +// Appends a HandshakeResponse41 payload (no header) to |out| and returns +// true. auth_response encoding obeys capability_flags: +// - CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA -> length-encoded string +// - CLIENT_SECURE_CONNECTION -> 1-byte length + data +// - neither -> NUL-terminated +// The 1-byte-length scheme cannot represent an auth_response longer than +// 255 bytes. Rather than silently truncating it (which produces an +// invalid response and desynchronizes the packet stream), the function +// logs an error and returns false WITHOUT writing to |out|. Callers with +// larger payloads (e.g. RSA ciphertext) must negotiate +// CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA. +bool BuildHandshakeResponse41(const HandshakeResponse41& req, std::string* out); + +// Parsed AuthSwitchRequest (server asks client to switch plugins). +struct AuthSwitchRequest { + std::string auth_plugin_name; + std::string auth_plugin_data; // 20-byte salt; trailing NUL stripped +}; + +// Parses an AuthSwitchRequest payload. Returns true on success. The +// caller must have already verified payload[0] == kAuthSwitchRequestTag. +bool ParseAuthSwitchRequest(const butil::StringPiece& payload, + AuthSwitchRequest* out); + +// Parsed AuthMoreData (server sends RSA pubkey or fast-auth status). +struct AuthMoreData { + std::string data; // 0x03=fast-auth-ok, 0x04=request-pubkey, or PEM +}; + +// Parses an AuthMoreData payload. Returns true on success. The +// caller must have already verified payload[0] == kAuthMoreDataTag. +bool ParseAuthMoreData(const butil::StringPiece& payload, AuthMoreData* out); + +} // namespace mysql +} // namespace policy +} // namespace brpc + +#endif // BRPC_POLICY_MYSQL_MYSQL_AUTH_HANDSHAKE_H diff --git a/src/brpc/policy/mysql/mysql_auth_packet.cpp b/src/brpc/policy/mysql/mysql_auth_packet.cpp new file mode 100644 index 0000000..1e1395a --- /dev/null +++ b/src/brpc/policy/mysql/mysql_auth_packet.cpp @@ -0,0 +1,186 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/mysql/mysql_auth_packet.h" + +#include + +#include "butil/logging.h" + +namespace brpc { +namespace policy { +namespace mysql { + +size_t DecodeLengthEncodedInt(const butil::StringPiece& buf, uint64_t* out, + bool* is_null) { + // Define *out and *is_null on every path so a caller that forgets to + // check the return value can never read an uninitialized result. + *out = 0; + if (is_null != nullptr) { + *is_null = false; + } + if (buf.empty()) { + LOG(WARNING) << "DecodeLengthEncodedInt: empty buffer"; + return 0; + } + const unsigned char first = static_cast(buf[0]); + if (first < 0xfb) { + *out = first; + return 1; + } + if (first == 0xfb) { + // 0xFB is the lenenc NULL marker, not a length prefix. Report NULL + // (one byte consumed) instead of folding it into the failure path. + if (is_null != nullptr) { + *is_null = true; + } + return 1; + } + if (first == 0xfc) { + if (buf.size() < 3) { + LOG(WARNING) << "DecodeLengthEncodedInt: truncated 0xFC value, need 3 bytes"; + return 0; + } + *out = static_cast(buf[1]) + | (static_cast(static_cast(buf[2])) << 8); + return 3; + } + if (first == 0xfd) { + if (buf.size() < 4) { + LOG(WARNING) << "DecodeLengthEncodedInt: truncated 0xFD value, need 4 bytes"; + return 0; + } + *out = static_cast(buf[1]) + | (static_cast(static_cast(buf[2])) << 8) + | (static_cast(static_cast(buf[3])) << 16); + return 4; + } + if (first == 0xfe) { + if (buf.size() < 9) { + LOG(WARNING) << "DecodeLengthEncodedInt: truncated 0xFE value, need 9 bytes"; + return 0; + } + uint64_t v = 0; + for (int i = 0; i < 8; ++i) { + v |= static_cast(static_cast(buf[1 + i])) + << (8 * i); + } + *out = v; + return 9; + } + // 0xff is reserved for error packet marker; not a valid lenenc-int. + LOG(WARNING) << "DecodeLengthEncodedInt: reserved 0xFF marker"; + return 0; +} + +void EncodeLengthEncodedInt(uint64_t value, std::string* out) { + if (value < 0xfb) { + out->push_back(static_cast(value)); + return; + } + if (value < 0x10000ULL) { + out->push_back(static_cast(0xfc)); + out->push_back(static_cast(value & 0xff)); + out->push_back(static_cast((value >> 8) & 0xff)); + return; + } + if (value < 0x1000000ULL) { + out->push_back(static_cast(0xfd)); + out->push_back(static_cast(value & 0xff)); + out->push_back(static_cast((value >> 8) & 0xff)); + out->push_back(static_cast((value >> 16) & 0xff)); + return; + } + out->push_back(static_cast(0xfe)); + for (int i = 0; i < 8; ++i) { + out->push_back(static_cast((value >> (8 * i)) & 0xff)); + } +} + +size_t DecodeLengthEncodedString(const butil::StringPiece& buf, + std::string* out_value, + bool* is_null) { + out_value->clear(); + if (is_null != nullptr) { + *is_null = false; + } + uint64_t len = 0; + bool len_is_null = false; + const size_t prefix = DecodeLengthEncodedInt(buf, &len, &len_is_null); + if (prefix == 0) { + LOG(WARNING) << "DecodeLengthEncodedString: failed to decode length prefix"; + return 0; + } + if (len_is_null) { + // Leading 0xFB: the string itself is NULL. Only the marker byte is + // consumed; there is no payload to read. + if (is_null != nullptr) { + *is_null = true; + } + return prefix; + } + if (prefix > buf.size() || len > buf.size() - prefix) { + LOG(WARNING) << "DecodeLengthEncodedString: declared length " << len + << " exceeds buffer"; + return 0; + } + out_value->assign(buf.data() + prefix, len); + return prefix + len; +} + +void EncodeLengthEncodedString(const butil::StringPiece& value, + std::string* out) { + EncodeLengthEncodedInt(value.size(), out); + out->append(value.data(), value.size()); +} + +bool DecodePacketHeader(const butil::StringPiece& buf, PacketHeader* out) { + if (buf.size() < kPacketHeaderLen) { + LOG(WARNING) << "DecodePacketHeader: buffer smaller than packet header"; + return false; + } + out->payload_len = + static_cast(buf[0]) + | (static_cast(static_cast(buf[1])) << 8) + | (static_cast(static_cast(buf[2])) << 16); + out->seq = static_cast(buf[3]); + return true; +} + +void EncodePacketHeader(const PacketHeader& header, std::string* out) { + out->push_back(static_cast(header.payload_len & 0xff)); + out->push_back(static_cast((header.payload_len >> 8) & 0xff)); + out->push_back(static_cast((header.payload_len >> 16) & 0xff)); + out->push_back(static_cast(header.seq)); +} + +size_t DecodeNullTerminatedString(const butil::StringPiece& buf, + std::string* out_value) { + const char* nul = static_cast( + memchr(buf.data(), '\0', buf.size())); + if (nul == nullptr) { + LOG(WARNING) << "DecodeNullTerminatedString: no NUL terminator found"; + return 0; + } + const size_t len = static_cast(nul - buf.data()); + out_value->assign(buf.data(), len); + return len + 1; +} + +} // namespace mysql +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/mysql/mysql_auth_packet.h b/src/brpc/policy/mysql/mysql_auth_packet.h new file mode 100644 index 0000000..dcefa3c --- /dev/null +++ b/src/brpc/policy/mysql/mysql_auth_packet.h @@ -0,0 +1,104 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Wire-format helpers for the MySQL client protocol (length-encoded +// integers, length-encoded strings, packet headers) used by the +// authentication-handshake layer. Specification: +// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_integers.html +// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_strings.html +// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_packets.html + +#ifndef BRPC_POLICY_MYSQL_MYSQL_AUTH_PACKET_H +#define BRPC_POLICY_MYSQL_MYSQL_AUTH_PACKET_H + +#include + +#include + +#include "butil/strings/string_piece.h" + +namespace brpc { +namespace policy { +namespace mysql { + +// MySQL packet header: 3-byte little-endian payload length + 1-byte +// sequence id. +struct PacketHeader { + uint32_t payload_len; // 0 .. (1 << 24) - 1 + uint8_t seq; +}; +static const size_t kPacketHeaderLen = 4; + +// Maximum payload length representable in a single MySQL packet +// (24-bit length field; larger payloads are split across packets). +static const uint32_t kMaxPayloadLen = (1u << 24) - 1; + +// Decodes a length-encoded integer (lenenc-int) from |buf|. +// +// On success stores the value in *out and returns the number of bytes +// consumed (1, 3, 4, or 9). +// +// 0xFB is the protocol's NULL marker (a NULL column value in a result +// row), NOT an ordinary integer: when |buf| begins with 0xFB the value is +// NULL, *out is set to 0, *is_null (when non-NULL) is set to true, and 1 +// (the single byte consumed) is returned. For every non-NULL result +// *is_null is set to false. +// +// Returns 0 on failure: an empty buffer, a truncated multi-byte value, or +// the reserved 0xFF marker. On failure *out is set to 0 and *is_null +// (when non-NULL) to false, so a caller that forgets to check the return +// value never reads an uninitialized result. |is_null| may be NULL when +// the caller does not need to distinguish NULL from 0. +size_t DecodeLengthEncodedInt(const butil::StringPiece& buf, uint64_t* out, + bool* is_null = nullptr); + +// Appends a length-encoded integer encoding of |value| to |out|. +void EncodeLengthEncodedInt(uint64_t value, std::string* out); + +// Decodes a length-encoded string into |out_value| and returns the +// number of bytes consumed. A leading 0xFB encodes the protocol NULL +// value: when present *out_value is cleared, *is_null (when non-NULL) is +// set to true, and 1 (the marker byte) is returned. For a non-NULL +// string *is_null is set to false. Returns 0 if the leading lenenc-int +// is invalid or the declared payload is truncated. |is_null| may be NULL. +size_t DecodeLengthEncodedString(const butil::StringPiece& buf, + std::string* out_value, + bool* is_null = nullptr); + +// Appends a length-encoded string encoding of |value| to |out|. +void EncodeLengthEncodedString(const butil::StringPiece& value, + std::string* out); + +// Decodes a packet header from the first kPacketHeaderLen bytes of +// |buf|. Returns true on success. +bool DecodePacketHeader(const butil::StringPiece& buf, PacketHeader* out); + +// Appends an encoded packet header to |out|. Caller must guarantee +// header.payload_len <= kMaxPayloadLen. +void EncodePacketHeader(const PacketHeader& header, std::string* out); + +// Decodes a NUL-terminated string starting at |buf[0]|. Stores the +// string (without the NUL) in *out_value and returns bytes consumed +// (string length + 1). Returns 0 if no NUL is found within |buf|. +size_t DecodeNullTerminatedString(const butil::StringPiece& buf, + std::string* out_value); + +} // namespace mysql +} // namespace policy +} // namespace brpc + +#endif // BRPC_POLICY_MYSQL_MYSQL_AUTH_PACKET_H diff --git a/src/brpc/policy/mysql/mysql_auth_scramble.cpp b/src/brpc/policy/mysql/mysql_auth_scramble.cpp new file mode 100644 index 0000000..64ab3d3 --- /dev/null +++ b/src/brpc/policy/mysql/mysql_auth_scramble.cpp @@ -0,0 +1,204 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/mysql/mysql_auth_scramble.h" + +#include + +#include +#include +#include +#include + +#include "butil/sha1.h" + +namespace brpc { +namespace policy { +namespace mysql { + +namespace { + +bool Sha256Bytes(const unsigned char* data, size_t len, unsigned char out[32]) { + unsigned int digest_len = 0; + return EVP_Digest(data, len, out, &digest_len, EVP_sha256(), nullptr) == 1 + && digest_len == 32; +} + +} // namespace + +std::string NativePasswordScramble(const butil::StringPiece& salt, + const butil::StringPiece& password) { + if (password.empty()) { + return std::string(); + } + if (salt.size() != kSaltLen) { + return std::string(); + } + + const size_t kHashLen = butil::kSHA1Length; + + unsigned char sha_pw[kHashLen]; + butil::SHA1HashBytes( + reinterpret_cast(password.data()), + password.size(), sha_pw); + + unsigned char sha_sha_pw[kHashLen]; + butil::SHA1HashBytes(sha_pw, kHashLen, sha_sha_pw); + + unsigned char joined[kHashLen * 2]; + memcpy(joined, salt.data(), kHashLen); + memcpy(joined + kHashLen, sha_sha_pw, kHashLen); + + unsigned char salted_hash[kHashLen]; + butil::SHA1HashBytes(joined, sizeof(joined), salted_hash); + + std::string out(kHashLen, '\0'); + for (size_t i = 0; i < kHashLen; ++i) { + out[i] = static_cast(sha_pw[i] ^ salted_hash[i]); + } + return out; +} + +std::string CachingSha2PasswordScramble(const butil::StringPiece& salt, + const butil::StringPiece& password) { + if (password.empty()) { + return std::string(); + } + if (salt.size() != kSaltLen) { + return std::string(); + } + + const size_t kHashLen = 32; + + unsigned char sha_pw[kHashLen]; + if (!Sha256Bytes(reinterpret_cast(password.data()), + password.size(), sha_pw)) { + return std::string(); + } + + unsigned char sha_sha_pw[kHashLen]; + if (!Sha256Bytes(sha_pw, kHashLen, sha_sha_pw)) { + return std::string(); + } + + unsigned char joined[kHashLen + kSaltLen]; + memcpy(joined, sha_sha_pw, kHashLen); + memcpy(joined + kHashLen, salt.data(), kSaltLen); + + unsigned char salted_hash[kHashLen]; + if (!Sha256Bytes(joined, sizeof(joined), salted_hash)) { + return std::string(); + } + + std::string out(kHashLen, '\0'); + for (size_t i = 0; i < kHashLen; ++i) { + out[i] = static_cast(sha_pw[i] ^ salted_hash[i]); + } + return out; +} + +std::string CachingSha2PasswordRsaEncrypt( + const butil::StringPiece& server_pubkey_pem, + const butil::StringPiece& salt, + const butil::StringPiece& password) { + if (salt.size() != kSaltLen) { + return std::string(); + } + if (server_pubkey_pem.empty()) { + return std::string(); + } + + std::string plaintext; + plaintext.resize(password.size() + 1); + for (size_t i = 0; i < password.size(); ++i) { + plaintext[i] = static_cast( + password.data()[i] ^ salt.data()[i % kSaltLen]); + } + plaintext[password.size()] = static_cast( + '\0' ^ salt.data()[password.size() % kSaltLen]); + + BIO* bio = BIO_new_mem_buf(server_pubkey_pem.data(), + static_cast(server_pubkey_pem.size())); + if (bio == nullptr) { + return std::string(); + } + EVP_PKEY* pkey = PEM_read_bio_PUBKEY(bio, nullptr, nullptr, nullptr); + BIO_free(bio); + if (pkey == nullptr) { + return std::string(); + } + + EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, nullptr); + if (ctx == nullptr) { + EVP_PKEY_free(pkey); + return std::string(); + } + + std::string out; + do { + if (EVP_PKEY_encrypt_init(ctx) <= 0) break; + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) break; + + size_t out_len = 0; + if (EVP_PKEY_encrypt( + ctx, nullptr, &out_len, + reinterpret_cast(plaintext.data()), + plaintext.size()) <= 0) { + break; + } + out.resize(out_len); + if (EVP_PKEY_encrypt( + ctx, + reinterpret_cast(&out[0]), &out_len, + reinterpret_cast(plaintext.data()), + plaintext.size()) <= 0) { + out.clear(); + break; + } + out.resize(out_len); + } while (false); + + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(pkey); + return out; +} + +std::string CachingSha2PasswordCleartext(const butil::StringPiece& password) { + if (password.empty()) { + return std::string(); + } + std::string out; + out.reserve(password.size() + 1); + out.append(password.data(), password.size()); + out.push_back('\0'); + return out; +} + +std::string CachingSha2PasswordSlowPath( + const butil::StringPiece& password, + const butil::StringPiece& salt, + const butil::StringPiece& server_pubkey_pem, + bool is_ssl) { + if (is_ssl) { + return CachingSha2PasswordCleartext(password); + } + return CachingSha2PasswordRsaEncrypt(server_pubkey_pem, salt, password); +} + +} // namespace mysql +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/mysql/mysql_auth_scramble.h b/src/brpc/policy/mysql/mysql_auth_scramble.h new file mode 100644 index 0000000..4eebe5f --- /dev/null +++ b/src/brpc/policy/mysql/mysql_auth_scramble.h @@ -0,0 +1,119 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Clean-room implementation of the three MySQL client authentication +// scrambles, written from MySQL's public protocol documentation and +// not derived from any GPL-licensed source. +// +// Specifications: +// mysql_native_password: +// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_authentication_methods_native_password_authentication.html +// caching_sha2_password (fast path + RSA path): +// https://dev.mysql.com/doc/dev/mysql-server/latest/page_caching_sha2_authentication_exchanges.html + +#ifndef BRPC_POLICY_MYSQL_MYSQL_AUTH_SCRAMBLE_H +#define BRPC_POLICY_MYSQL_MYSQL_AUTH_SCRAMBLE_H + +#include + +#include "butil/strings/string_piece.h" + +namespace brpc { +namespace policy { +namespace mysql { + +// Salt length in HandshakeV10's auth-plugin-data field. Both +// mysql_native_password and caching_sha2_password use a 20-byte salt. +static const size_t kSaltLen = 20; + +// mysql_native_password produces a 20-byte (SHA-1-sized) response. +static const size_t kNativePasswordResponseLen = 20; + +// caching_sha2_password fast path produces a 32-byte (SHA-256-sized) +// response. +static const size_t kCachingSha2PasswordResponseLen = 32; + +// Computes the mysql_native_password scramble. +// scramble = SHA1(p) XOR SHA1( salt || SHA1( SHA1(p) ) ) +// +// Returns 20 raw bytes on success. Returns an empty string when the +// password is empty (per spec: zero-byte wire response) or when |salt| +// is not exactly kSaltLen bytes. +std::string NativePasswordScramble(const butil::StringPiece& salt, + const butil::StringPiece& password); + +// Computes the caching_sha2_password fast-path scramble. +// scramble = SHA256(p) XOR SHA256( SHA256( SHA256(p) ) || salt ) +// +// Returns 32 raw bytes on success. Returns an empty string when the +// password is empty or when |salt| is not exactly kSaltLen bytes. +std::string CachingSha2PasswordScramble(const butil::StringPiece& salt, + const butil::StringPiece& password); + +// Computes the caching_sha2_password slow-path payload using RSA-OAEP +// encryption against the server's PEM-encoded RSA public key. +// +// obfuscated = (password || '\0') XOR repeat(salt, len) +// ciphertext = RSA-OAEP-SHA1-encrypt(obfuscated, server_pubkey) +// +// Returns the raw ciphertext (RSA modulus size in bytes) on success. +// Returns an empty string when |salt| is not kSaltLen, when the PEM +// blob does not parse as an RSA public key, or when the password plus +// terminator does not fit the OAEP plaintext budget for the key. +std::string CachingSha2PasswordRsaEncrypt( + const butil::StringPiece& server_pubkey_pem, + const butil::StringPiece& salt, + const butil::StringPiece& password); + +// Computes the caching_sha2_password "secure transport" payload: the +// raw password bytes followed by a single NUL terminator. Safe to +// send only when the underlying channel is already protected +// (SSL-wrapped, unix domain socket, or shared memory) -- the bytes +// travel in the clear at this layer. +// +// Mirrors what the official mysql client sends from +// sql-common/client_authentication.cc:871 +// when is_secure_transport() returns true. +// +// Returns "\0" on success. Returns an empty string when +// |password| is empty (matches the wire convention for blank +// passwords). +std::string CachingSha2PasswordCleartext(const butil::StringPiece& password); + +// Dispatches the caching_sha2_password slow-path response computation. +// +// is_ssl=true -> CachingSha2PasswordCleartext(password) +// |salt| and |server_pubkey_pem| are ignored. +// is_ssl=false -> CachingSha2PasswordRsaEncrypt( +// server_pubkey_pem, salt, password) +// +// |is_ssl| is intentionally NOT defaulted: every caller must state +// whether the underlying channel is secure (SSL/unix-socket/shared-mem), +// making the cleartext-vs-RSA decision explicit at the call site. Pass +// is_ssl=true on a secure channel to send the password in the clear (one +// round trip); pass is_ssl=false on plain TCP to use RSA-OAEP. +std::string CachingSha2PasswordSlowPath( + const butil::StringPiece& password, + const butil::StringPiece& salt, + const butil::StringPiece& server_pubkey_pem, + bool is_ssl); + +} // namespace mysql +} // namespace policy +} // namespace brpc + +#endif // BRPC_POLICY_MYSQL_MYSQL_AUTH_SCRAMBLE_H diff --git a/src/brpc/policy/mysql/mysql_authenticator.cpp b/src/brpc/policy/mysql/mysql_authenticator.cpp new file mode 100644 index 0000000..d9823b0 --- /dev/null +++ b/src/brpc/policy/mysql/mysql_authenticator.cpp @@ -0,0 +1,221 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. +// +// Author(s): Yang,Liming + +#include +#include "brpc/policy/mysql/mysql_authenticator.h" +#include "brpc/policy/mysql/mysql_auth_scramble.h" +#include "brpc/policy/mysql/mysql_command.h" +#include "brpc/policy/mysql/mysql_reply.h" +#include "brpc/policy/mysql/mysql_common.h" +#include "butil/base64.h" +#include "butil/iobuf.h" +#include "butil/logging.h" // LOG() +#include "butil/sys_byteorder.h" + +namespace brpc { +namespace policy { + +namespace { +const butil::StringPiece mysql_native_password("mysql_native_password"); +const butil::StringPiece caching_sha2_password("caching_sha2_password"); +const char* auth_param_delim = "\t"; +bool MysqlHandleParams(const butil::StringPiece& params, std::string* param_cmd) { + if (params.empty()) { + return true; + } + const char* delim1 = "&"; + std::vector idx; + for (size_t p = params.find(delim1); p != butil::StringPiece::npos; + p = params.find(delim1, p + 1)) { + idx.push_back(p); + } + + const char* delim2 = "="; + std::stringstream ss; + for (size_t i = 0; i < idx.size() + 1; ++i) { + size_t pos = (i > 0) ? idx[i - 1] + 1 : 0; + size_t len = (i < idx.size()) ? idx[i] - pos : params.size() - pos; + butil::StringPiece raw(params.data() + pos, len); + const size_t p = raw.find(delim2); + if (p != butil::StringPiece::npos) { + butil::StringPiece k(raw.data(), p); + butil::StringPiece v(raw.data() + p + 1, raw.size() - p - 1); + if (k == "charset") { + ss << "SET NAMES " << v << ";"; + } else { + ss << "SET " << k << "=" << v << ";"; + } + } + } + *param_cmd = ss.str(); + return true; +} +}; // namespace + +// user + "\t" + password + "\t" + schema + "\t" + collation + "\t" + param +bool MysqlAuthenticator::SerializeToString(std::string* str) const { + std::stringstream ss; + ss << _user << auth_param_delim; + ss << _passwd << auth_param_delim; + ss << _schema << auth_param_delim; + ss << _collation << auth_param_delim; + std::string param_cmd; + if (MysqlHandleParams(_params, ¶m_cmd)) { + ss << param_cmd; + } else { + LOG(ERROR) << "handle mysql authentication params failed, ignore it"; + return false; + } + *str = ss.str(); + return true; +} + +void MysqlParseAuthenticator(const butil::StringPiece& raw, + std::string* user, + std::string* password, + std::string* schema, + std::string* collation) { + std::vector idx; + idx.reserve(4); + for (size_t p = raw.find(auth_param_delim); p != butil::StringPiece::npos; + p = raw.find(auth_param_delim, p + 1)) { + idx.push_back(p); + } + if (idx.size() < 4) { + LOG(ERROR) << "malformed mysql authentication string, expected at least 4 '\\t' " + "delimiters but found " << idx.size(); + user->clear(); + password->clear(); + schema->clear(); + collation->clear(); + return; + } + user->assign(raw.data(), 0, idx[0]); + password->assign(raw.data(), idx[0] + 1, idx[1] - idx[0] - 1); + schema->assign(raw.data(), idx[1] + 1, idx[2] - idx[1] - 1); + collation->assign(raw.data(), idx[2] + 1, idx[3] - idx[2] - 1); +} + +void MysqlParseParams(const butil::StringPiece& raw, std::string* params) { + size_t idx = raw.rfind(auth_param_delim); + params->assign(raw.data(), idx + 1, raw.size() - idx - 1); +} + +int MysqlPackAuthenticator(const MysqlReply::Auth& auth, + const butil::StringPiece& user, + const butil::StringPiece& password, + const butil::StringPiece& schema, + const butil::StringPiece& collation, + std::string* auth_cmd) { + const uint16_t capability = + butil::ByteSwapToLE16((schema == "" ? 0x8285 : 0x828d) & auth.capability()); + const uint16_t extended_capability = butil::ByteSwapToLE16(0x000b & auth.extended_capability()); + butil::IOBuf salt; + salt.append(auth.salt().data(), auth.salt().size()); + salt.append(auth.salt2().data(), auth.salt2().size()); + if (auth.auth_plugin() == mysql_native_password) { + // Clean-room mysql_native_password scramble: + // SHA1(p) XOR SHA1( salt || SHA1(SHA1(p)) ) + // Produces the same 20 wire bytes as the original GPL helper, but is + // derived from MySQL's public protocol docs. Returns empty for a + // blank password (the wire convention) and empty on a bad salt length. + const std::string scramble = + mysql::NativePasswordScramble(salt.to_string(), password); + if (!password.empty() && scramble.empty()) { + LOG(ERROR) << "failed to build mysql_native_password scramble, salt size=" + << salt.size() << " (expected " << mysql::kSaltLen << ")"; + return 1; + } + salt.clear(); + salt.append(scramble); + } else if (auth.auth_plugin() == caching_sha2_password) { + // Clean-room caching_sha2_password fast-path scramble (32 bytes): + // SHA256(p) XOR SHA256( SHA256( SHA256(p) ) || salt ) + // The server replies with an AuthMoreData status byte after this; + // mysql_protocol.cpp's HandleAuthentication drives the follow-up + // (fast-auth-success / full-auth RSA exchange). Returns empty for a + // blank password (the wire convention) and empty on a bad salt length. + const std::string scramble = + mysql::CachingSha2PasswordScramble(salt.to_string(), password); + if (!password.empty() && scramble.empty()) { + LOG(ERROR) << "failed to build caching_sha2_password scramble, salt size=" + << salt.size() << " (expected " << mysql::kSaltLen << ")"; + return 1; + } + salt.clear(); + salt.append(scramble); + } else { + LOG(ERROR) << "no support auth plugin [" << auth.auth_plugin() << "]"; + return 1; + } + + butil::IOBuf payload; + payload.append(&capability, 2); + payload.append(&extended_capability, 2); + payload.push_back(0x00); + payload.push_back(0x00); + payload.push_back(0x00); + payload.push_back(0x00); + auto iter = MysqlCollations.find(std::string(collation.data(), collation.size())); + if (iter == MysqlCollations.end()) { + LOG(ERROR) << "wrong collation [" << collation << "]"; + return 1; + } + payload.append(&iter->second, 1); + const std::string stuff(23, '\0'); + payload.append(stuff); + payload.append(user.data(), user.size()); + payload.push_back('\0'); + payload.append(pack_encode_length(salt.size())); + payload.append(salt); + if (schema != "") { + payload.append(schema.data(), schema.size()); + payload.push_back('\0'); + } + if (auth.auth_plugin() == mysql_native_password) { + payload.append(mysql_native_password.data(), mysql_native_password.size()); + payload.push_back('\0'); + } else if (auth.auth_plugin() == caching_sha2_password) { + payload.append(caching_sha2_password.data(), caching_sha2_password.size()); + payload.push_back('\0'); + } + butil::IOBuf message; + const uint32_t payload_size = butil::ByteSwapToLE32(payload.size()); + // header + message.append(&payload_size, 3); + message.push_back(0x01); + // payload + message.append(payload); + *auth_cmd = message.to_string(); + return 0; +} + +int MysqlPackParams(const butil::StringPiece& params, std::string* param_cmd) { + if (!params.empty()) { + butil::IOBuf buf; + MysqlMakeCommand(&buf, MYSQL_COM_QUERY, params); + buf.copy_to(param_cmd); + return 0; + } + LOG(ERROR) << "empty connection params"; + return 1; +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/mysql/mysql_authenticator.h b/src/brpc/policy/mysql/mysql_authenticator.h new file mode 100644 index 0000000..0838225 --- /dev/null +++ b/src/brpc/policy/mysql/mysql_authenticator.h @@ -0,0 +1,91 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. +// +// Author(s): Yang,Liming + +#ifndef BRPC_POLICY_MYSQL_AUTHENTICATOR_H +#define BRPC_POLICY_MYSQL_AUTHENTICATOR_H + +#include "butil/iobuf.h" +#include "brpc/authenticator.h" +#include "brpc/policy/mysql/mysql_reply.h" + +namespace brpc { +namespace policy { +// Request to mysql for authentication. +class MysqlAuthenticator : public Authenticator { +public: + MysqlAuthenticator(const butil::StringPiece& user, + const butil::StringPiece& passwd, + const butil::StringPiece& schema, + const butil::StringPiece& params = "", + const butil::StringPiece& collation = MysqlDefaultCollation) + : _user(user.data(), user.size()), + _passwd(passwd.data(), passwd.size()), + _schema(schema.data(), schema.size()), + _params(params.data(), params.size()), + _collation(collation.data(), collation.size()) {} + + int GenerateCredential(std::string* auth_str) const { + return 0; + } + + int VerifyCredential(const std::string&, const butil::EndPoint&, brpc::AuthContext*) const { + return 0; + } + + const butil::StringPiece user() const; + const butil::StringPiece passwd() const; + const butil::StringPiece schema() const; + const butil::StringPiece params() const; + const butil::StringPiece collation() const; + bool SerializeToString(std::string* str) const; + +private: + DISALLOW_COPY_AND_ASSIGN(MysqlAuthenticator); + + const std::string _user; + const std::string _passwd; + const std::string _schema; + const std::string _params; + const std::string _collation; +}; + +inline const butil::StringPiece MysqlAuthenticator::user() const { + return _user; +} + +inline const butil::StringPiece MysqlAuthenticator::passwd() const { + return _passwd; +} + +inline const butil::StringPiece MysqlAuthenticator::schema() const { + return _schema; +} + +inline const butil::StringPiece MysqlAuthenticator::params() const { + return _params; +} + +inline const butil::StringPiece MysqlAuthenticator::collation() const { + return _collation; +} + +} // namespace policy +} // namespace brpc + +#endif // BRPC_POLICY_MYSQL_AUTHENTICATOR_H diff --git a/src/brpc/policy/mysql/mysql_command.cpp b/src/brpc/policy/mysql/mysql_command.cpp new file mode 100644 index 0000000..a4ecf9d --- /dev/null +++ b/src/brpc/policy/mysql/mysql_command.cpp @@ -0,0 +1,272 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Authors: Yang,Liming (yangliming01@baidu.com) + +#include "butil/sys_byteorder.h" +#include "butil/logging.h" // LOG() +#include "brpc/policy/mysql/mysql_command.h" +#include "brpc/policy/mysql/mysql_common.h" +#include "brpc/policy/mysql/mysql.h" + +namespace brpc { + +namespace { +const uint32_t max_allowed_packet = 67108864; +const uint32_t max_packet_size = 16777215; + +template +butil::Status MakePacket(butil::IOBuf* outbuf, const H& head, const F& func, const D& data) { + int64_t pkg_len = (int64_t)head.size() + (int64_t)data.size(); + if (pkg_len > max_allowed_packet) { + return butil::Status( + EINVAL, + "[MakePacket] statement size is too big, maxAllowedPacket = %d, pkg_len = %lld", + max_allowed_packet, + (long long)pkg_len); + } + uint32_t size, header; + uint8_t seq = 0; + size_t offset = 0; + // When the payload length is an exact multiple of max_packet_size, the + // MySQL multi-packet protocol requires a trailing 0-length packet to mark + // the end. Loop while pkg_len > 0, plus one extra pass emitting an empty + // packet when the previous chunk exactly filled max_packet_size. + bool need_trailing = false; + for (; pkg_len > 0 || need_trailing; pkg_len -= max_packet_size, ++seq) { + if (pkg_len > max_packet_size) { + size = max_packet_size; + } else { + size = pkg_len; + } + need_trailing = (size == max_packet_size); + header = butil::ByteSwapToLE32(size); + ((uint8_t*)&header)[3] = seq; + outbuf->append(&header, 4); + if (seq == 0) { + const uint32_t old_size = outbuf->size(); + outbuf->append(head); + size -= outbuf->size() - old_size; + } + func(outbuf, data, size, offset); + offset += size; + } + + return butil::Status::OK(); +} + +} // namespace + +butil::Status MysqlMakeCommand(butil::IOBuf* outbuf, + const MysqlCommandType type, + const butil::StringPiece& command) { + if (outbuf == NULL || command.size() == 0) { + return butil::Status(EINVAL, "[MysqlMakeCommand] Param[outbuf] or [stmt] is NULL"); + } + auto func = + [](butil::IOBuf* outbuf, const butil::StringPiece& command, size_t size, size_t offset) { + outbuf->append(command.data() + offset, size); + }; + butil::IOBuf head; + head.push_back(type); + return MakePacket(outbuf, head, func, command); +} + +butil::Status MysqlMakeExecutePacket(butil::IOBuf* outbuf, + uint32_t stmt_id, + const butil::IOBuf& edata) { + butil::IOBuf head; // cmd_type + stmt_id + flag + reserved + body_size + head.push_back(MYSQL_COM_STMT_EXECUTE); + const uint32_t si = butil::ByteSwapToLE32(stmt_id); + head.append(&si, 4); + head.push_back('\0'); + head.push_back((char)0x01); + head.push_back('\0'); + head.push_back('\0'); + head.push_back('\0'); + auto func = [](butil::IOBuf* outbuf, const butil::IOBuf& data, size_t size, size_t offset) { + data.append_to(outbuf, size, offset); + }; + return MakePacket(outbuf, head, func, edata); +} + +butil::Status MysqlMakeExecuteData(MysqlStatementStub* stmt, + uint16_t index, + const void* value, + MysqlFieldType type, + bool is_unsigned) { + const uint16_t n = stmt->stmt()->param_count(); + uint32_t long_data_size = max_allowed_packet / (n + 1); + if (long_data_size < 64) { + long_data_size = 64; + } + // if param count is zero finished. + if (n == 0) { + return butil::Status::OK(); + } + butil::IOBuf& buf = stmt->execute_data(); + MysqlStatementStub::NullMask& null_mask = stmt->null_mask(); + MysqlStatementStub::ParamTypes& param_types = stmt->param_types(); + // else param number larger than zero. + if (index >= n) { + LOG(ERROR) << "too many params"; + return butil::Status(EINVAL, "[MysqlMakeExecuteData] too many params"); + } + // reserve null mask and param types packing at first param + if (index == 0) { + const size_t mask_len = (n + 7) / 8; + const size_t types_len = 2 * n; + null_mask.mask.resize(mask_len, 0); + null_mask.area = buf.reserve(mask_len); + buf.push_back((char)0x01); + param_types.types.resize(types_len, 0); + param_types.area = buf.reserve(types_len); + } + // pack param value + switch (type) { + case MYSQL_FIELD_TYPE_TINY: + if (is_unsigned) { + param_types.types[index + index] = MYSQL_FIELD_TYPE_TINY; + param_types.types[index + index + 1] = 0x80; + } else { + param_types.types[index + index] = MYSQL_FIELD_TYPE_TINY; + param_types.types[index + index + 1] = 0x00; + } + buf.append(value, 1); + break; + case MYSQL_FIELD_TYPE_SHORT: + if (is_unsigned) { + param_types.types[index + index] = MYSQL_FIELD_TYPE_SHORT; + param_types.types[index + index + 1] = 0x80; + } else { + param_types.types[index + index] = MYSQL_FIELD_TYPE_SHORT; + param_types.types[index + index + 1] = 0x00; + } + { + uint16_t v = butil::ByteSwapToLE16(*(uint16_t*)value); + buf.append(&v, 2); + } + break; + case MYSQL_FIELD_TYPE_LONG: + if (is_unsigned) { + param_types.types[index + index] = MYSQL_FIELD_TYPE_LONG; + param_types.types[index + index + 1] = 0x80; + + } else { + param_types.types[index + index] = MYSQL_FIELD_TYPE_LONG; + param_types.types[index + index + 1] = 0x00; + } + { + uint32_t v = butil::ByteSwapToLE32(*(uint32_t*)value); + buf.append(&v, 4); + } + break; + case MYSQL_FIELD_TYPE_LONGLONG: + if (is_unsigned) { + param_types.types[index + index] = MYSQL_FIELD_TYPE_LONGLONG; + param_types.types[index + index + 1] = 0x80; + } else { + param_types.types[index + index] = MYSQL_FIELD_TYPE_LONGLONG; + param_types.types[index + index + 1] = 0x00; + } + { + uint64_t v = butil::ByteSwapToLE64(*(uint64_t*)value); + buf.append(&v, 8); + } + break; + case MYSQL_FIELD_TYPE_FLOAT: + param_types.types[index + index] = MYSQL_FIELD_TYPE_FLOAT; + param_types.types[index + index + 1] = 0x00; + buf.append(value, 4); + break; + case MYSQL_FIELD_TYPE_DOUBLE: + param_types.types[index + index] = MYSQL_FIELD_TYPE_DOUBLE; + param_types.types[index + index + 1] = 0x00; + buf.append(value, 8); + break; + case MYSQL_FIELD_TYPE_STRING: { + const butil::StringPiece* p = (butil::StringPiece*)value; + if (p == NULL || p->data() == NULL) { + param_types.types[index + index] = MYSQL_FIELD_TYPE_NULL; + param_types.types[index + index + 1] = 0x00; + null_mask.mask[index / 8] |= 1 << (index & 7); + } else { + param_types.types[index + index] = MYSQL_FIELD_TYPE_STRING; + param_types.types[index + index + 1] = 0x00; + if (p->size() < long_data_size) { + std::string len = pack_encode_length(p->size()); + buf.append(len); + buf.append(p->data(), p->size()); + } else { + stmt->save_long_data(index, *p); + } + } + } break; + case MYSQL_FIELD_TYPE_NULL: { + param_types.types[index + index] = MYSQL_FIELD_TYPE_NULL; + param_types.types[index + index + 1] = 0x00; + null_mask.mask[index / 8] |= 1 << (index & 7); + } break; + default: + LOG(ERROR) << "wrong param type"; + return butil::Status(EINVAL, "[MysqlMakeExecuteData] wrong param type"); + } + + // all args have been building + if (index + 1 == n) { + buf.unsafe_assign(null_mask.area, null_mask.mask.data()); + buf.unsafe_assign(param_types.area, param_types.types.data()); + } + + return butil::Status::OK(); +} + +butil::Status MysqlMakeLongDataPacket(butil::IOBuf* outbuf, + uint32_t stmt_id, + uint16_t param_id, + const butil::IOBuf& ldata) { + butil::IOBuf head; + head.push_back(MYSQL_COM_STMT_SEND_LONG_DATA); + const uint32_t si = butil::ByteSwapToLE32(stmt_id); + head.append(&si, 4); + const uint16_t pi = butil::ByteSwapToLE16(param_id); + head.append(&pi, 2); + // Cap each chunk so that head.size() + len never exceeds max_allowed_packet, + // otherwise MakePacket rejects (EINVAL) an exact-limit-multiple payload. + const size_t max_chunk = max_allowed_packet - head.size(); + size_t len, pos = 0; + for (size_t pkg_len = ldata.size(); pkg_len > 0; pkg_len -= len) { + if (pkg_len < max_chunk) { + len = pkg_len; + } else { + len = max_chunk; + } + butil::IOBuf data; + ldata.append_to(&data, len, pos); + pos += len; + auto func = [](butil::IOBuf* outbuf, const butil::IOBuf& data, size_t size, size_t offset) { + data.append_to(outbuf, size, offset); + }; + auto rc = MakePacket(outbuf, head, func, data); + if (!rc.ok()) { + return rc; + } + } + return butil::Status::OK(); +} + +} // namespace brpc diff --git a/src/brpc/policy/mysql/mysql_command.h b/src/brpc/policy/mysql/mysql_command.h new file mode 100644 index 0000000..f826aba --- /dev/null +++ b/src/brpc/policy/mysql/mysql_command.h @@ -0,0 +1,94 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Authors: Yang,Liming (yangliming01@baidu.com) + +#ifndef BRPC_MYSQL_COMMAND_H +#define BRPC_MYSQL_COMMAND_H + +#include +#include "butil/iobuf.h" +#include "butil/status.h" +#include "brpc/policy/mysql/mysql_common.h" + +namespace brpc { +// mysql command types +enum MysqlCommandType : unsigned char { + MYSQL_COM_SLEEP, + MYSQL_COM_QUIT, + MYSQL_COM_INIT_DB, + MYSQL_COM_QUERY, + MYSQL_COM_FIELD_LIST, + MYSQL_COM_CREATE_DB, + MYSQL_COM_DROP_DB, + MYSQL_COM_REFRESH, + MYSQL_COM_SHUTDOWN, + MYSQL_COM_STATISTICS, + MYSQL_COM_PROCESS_INFO, + MYSQL_COM_CONNECT, + MYSQL_COM_PROCESS_KILL, + MYSQL_COM_DEBUG, + MYSQL_COM_PING, + MYSQL_COM_TIME, + MYSQL_COM_DELAYED_INSERT, + MYSQL_COM_CHANGE_USER, + MYSQL_COM_BINLOG_DUMP, + MYSQL_COM_TABLE_DUMP, + MYSQL_COM_CONNECT_OUT, + MYSQL_COM_REGISTER_SLAVE, + MYSQL_COM_STMT_PREPARE, + MYSQL_COM_STMT_EXECUTE, + MYSQL_COM_STMT_SEND_LONG_DATA, + MYSQL_COM_STMT_CLOSE, + MYSQL_COM_STMT_RESET, + MYSQL_COM_SET_OPTION, + MYSQL_COM_STMT_FETCH, + MYSQL_COM_DAEMON, + MYSQL_COM_BINLOG_DUMP_GTID, + MYSQL_COM_RESET_CONNECTION, +}; + +butil::Status MysqlMakeCommand(butil::IOBuf* outbuf, + const MysqlCommandType type, + const butil::StringPiece& stmt); + +// Prepared Statement Protocol +// an prepared statement has a unique statement id in one connection (in brpc SocketId), an prepared +// statement can be executed in many connections, so ever connection has a different statement id. +// In bprc, we can only get a connection in the stage of PackXXXRequest which is behind our building +// mysql protocol stage, but building prepared statement need the statement id of a connection, so +// we will need to building this fragment at PackXXXRequest stage. + +class MysqlStatementStub; +// prepared statement execute command header, will be called at PackXXXRequest stage. +butil::Status MysqlMakeExecutePacket(butil::IOBuf* outbuf, + uint32_t stmt_id, + const butil::IOBuf& body); +// prepared statement execute command body, will be called at building mysql protocol stage. +butil::Status MysqlMakeExecuteData(MysqlStatementStub* stmt, + uint16_t index, + const void* value, + MysqlFieldType type, + bool is_unsigned = false); +// prepared statement long data header +butil::Status MysqlMakeLongDataPacket(butil::IOBuf* outbuf, + uint32_t stmt_id, + uint16_t param_id, + const butil::IOBuf& body); + +} // namespace brpc +#endif diff --git a/src/brpc/policy/mysql/mysql_common.cpp b/src/brpc/policy/mysql/mysql_common.cpp new file mode 100644 index 0000000..4fcabb9 --- /dev/null +++ b/src/brpc/policy/mysql/mysql_common.cpp @@ -0,0 +1,237 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Authors: Yang,Liming (yangliming01@baidu.com) + +#include "brpc/policy/mysql/mysql_common.h" + +namespace brpc { + +// Definition lives here (single TU) to avoid a per-include copy of the map. +// utf16/ucs2/utf32 collations are not supported. +const std::map MysqlCollations = { + {"big5_chinese_ci", 1}, + {"latin2_czech_cs", 2}, + {"dec8_swedish_ci", 3}, + {"cp850_general_ci", 4}, + {"latin1_german1_ci", 5}, + {"hp8_english_ci", 6}, + {"koi8r_general_ci", 7}, + {"latin1_swedish_ci", 8}, + {"latin2_general_ci", 9}, + {"swe7_swedish_ci", 10}, + {"ascii_general_ci", 11}, + {"ujis_japanese_ci", 12}, + {"sjis_japanese_ci", 13}, + {"cp1251_bulgarian_ci", 14}, + {"latin1_danish_ci", 15}, + {"hebrew_general_ci", 16}, + {"tis620_thai_ci", 18}, + {"euckr_korean_ci", 19}, + {"latin7_estonian_cs", 20}, + {"latin2_hungarian_ci", 21}, + {"koi8u_general_ci", 22}, + {"cp1251_ukrainian_ci", 23}, + {"gb2312_chinese_ci", 24}, + {"greek_general_ci", 25}, + {"cp1250_general_ci", 26}, + {"latin2_croatian_ci", 27}, + {"gbk_chinese_ci", 28}, + {"cp1257_lithuanian_ci", 29}, + {"latin5_turkish_ci", 30}, + {"latin1_german2_ci", 31}, + {"armscii8_general_ci", 32}, + {"utf8_general_ci", 33}, + {"cp1250_czech_cs", 34}, + {"cp866_general_ci", 36}, + {"keybcs2_general_ci", 37}, + {"macce_general_ci", 38}, + {"macroman_general_ci", 39}, + {"cp852_general_ci", 40}, + {"latin7_general_ci", 41}, + {"latin7_general_cs", 42}, + {"macce_bin", 43}, + {"cp1250_croatian_ci", 44}, + {"utf8mb4_general_ci", 45}, + {"utf8mb4_bin", 46}, + {"latin1_bin", 47}, + {"latin1_general_ci", 48}, + {"latin1_general_cs", 49}, + {"cp1251_bin", 50}, + {"cp1251_general_ci", 51}, + {"cp1251_general_cs", 52}, + {"macroman_bin", 53}, + {"cp1256_general_ci", 57}, + {"cp1257_bin", 58}, + {"cp1257_general_ci", 59}, + {"binary", 63}, + {"armscii8_bin", 64}, + {"ascii_bin", 65}, + {"cp1250_bin", 66}, + {"cp1256_bin", 67}, + {"cp866_bin", 68}, + {"dec8_bin", 69}, + {"greek_bin", 70}, + {"hebrew_bin", 71}, + {"hp8_bin", 72}, + {"keybcs2_bin", 73}, + {"koi8r_bin", 74}, + {"koi8u_bin", 75}, + {"utf8_tolower_ci", 76}, + {"latin2_bin", 77}, + {"latin5_bin", 78}, + {"latin7_bin", 79}, + {"cp850_bin", 80}, + {"cp852_bin", 81}, + {"swe7_bin", 82}, + {"utf8_bin", 83}, + {"big5_bin", 84}, + {"euckr_bin", 85}, + {"gb2312_bin", 86}, + {"gbk_bin", 87}, + {"sjis_bin", 88}, + {"tis620_bin", 89}, + {"ujis_bin", 91}, + {"geostd8_general_ci", 92}, + {"geostd8_bin", 93}, + {"latin1_spanish_ci", 94}, + {"cp932_japanese_ci", 95}, + {"cp932_bin", 96}, + {"eucjpms_japanese_ci", 97}, + {"eucjpms_bin", 98}, + {"cp1250_polish_ci", 99}, + {"utf8_unicode_ci", 192}, + {"utf8_icelandic_ci", 193}, + {"utf8_latvian_ci", 194}, + {"utf8_romanian_ci", 195}, + {"utf8_slovenian_ci", 196}, + {"utf8_polish_ci", 197}, + {"utf8_estonian_ci", 198}, + {"utf8_spanish_ci", 199}, + {"utf8_swedish_ci", 200}, + {"utf8_turkish_ci", 201}, + {"utf8_czech_ci", 202}, + {"utf8_danish_ci", 203}, + {"utf8_lithuanian_ci", 204}, + {"utf8_slovak_ci", 205}, + {"utf8_spanish2_ci", 206}, + {"utf8_roman_ci", 207}, + {"utf8_persian_ci", 208}, + {"utf8_esperanto_ci", 209}, + {"utf8_hungarian_ci", 210}, + {"utf8_sinhala_ci", 211}, + {"utf8_german2_ci", 212}, + {"utf8_croatian_ci", 213}, + {"utf8_unicode_520_ci", 214}, + {"utf8_vietnamese_ci", 215}, + {"utf8_general_mysql500_ci", 223}, + {"utf8mb4_unicode_ci", 224}, + {"utf8mb4_icelandic_ci", 225}, + {"utf8mb4_latvian_ci", 226}, + {"utf8mb4_romanian_ci", 227}, + {"utf8mb4_slovenian_ci", 228}, + {"utf8mb4_polish_ci", 229}, + {"utf8mb4_estonian_ci", 230}, + {"utf8mb4_spanish_ci", 231}, + {"utf8mb4_swedish_ci", 232}, + {"utf8mb4_turkish_ci", 233}, + {"utf8mb4_czech_ci", 234}, + {"utf8mb4_danish_ci", 235}, + {"utf8mb4_lithuanian_ci", 236}, + {"utf8mb4_slovak_ci", 237}, + {"utf8mb4_spanish2_ci", 238}, + {"utf8mb4_roman_ci", 239}, + {"utf8mb4_persian_ci", 240}, + {"utf8mb4_esperanto_ci", 241}, + {"utf8mb4_hungarian_ci", 242}, + {"utf8mb4_sinhala_ci", 243}, + {"utf8mb4_german2_ci", 244}, + {"utf8mb4_croatian_ci", 245}, + {"utf8mb4_unicode_520_ci", 246}, + {"utf8mb4_vietnamese_ci", 247}, + {"gb18030_chinese_ci", 248}, + {"gb18030_bin", 249}, + {"gb18030_unicode_520_ci", 250}, + {"utf8mb4_0900_ai_ci", 255}, +}; + + +const char* MysqlDefaultCollation = "utf8mb4_general_ci"; + +const char* MysqlFieldTypeToString(MysqlFieldType type) { + switch (type) { + case MYSQL_FIELD_TYPE_DECIMAL: + case MYSQL_FIELD_TYPE_TINY: + return "tiny"; + case MYSQL_FIELD_TYPE_SHORT: + return "short"; + case MYSQL_FIELD_TYPE_LONG: + return "long"; + case MYSQL_FIELD_TYPE_FLOAT: + return "float"; + case MYSQL_FIELD_TYPE_DOUBLE: + return "double"; + case MYSQL_FIELD_TYPE_NULL: + return "null"; + case MYSQL_FIELD_TYPE_TIMESTAMP: + return "timestamp"; + case MYSQL_FIELD_TYPE_LONGLONG: + return "longlong"; + case MYSQL_FIELD_TYPE_INT24: + return "int24"; + case MYSQL_FIELD_TYPE_DATE: + return "date"; + case MYSQL_FIELD_TYPE_TIME: + return "time"; + case MYSQL_FIELD_TYPE_DATETIME: + return "datetime"; + case MYSQL_FIELD_TYPE_YEAR: + return "year"; + case MYSQL_FIELD_TYPE_NEWDATE: + return "new date"; + case MYSQL_FIELD_TYPE_VARCHAR: + return "varchar"; + case MYSQL_FIELD_TYPE_BIT: + return "bit"; + case MYSQL_FIELD_TYPE_JSON: + return "json"; + case MYSQL_FIELD_TYPE_NEWDECIMAL: + return "new decimal"; + case MYSQL_FIELD_TYPE_ENUM: + return "enum"; + case MYSQL_FIELD_TYPE_SET: + return "set"; + case MYSQL_FIELD_TYPE_TINY_BLOB: + return "tiny blob"; + case MYSQL_FIELD_TYPE_MEDIUM_BLOB: + return "blob"; + case MYSQL_FIELD_TYPE_LONG_BLOB: + return "long blob"; + case MYSQL_FIELD_TYPE_BLOB: + return "blob"; + case MYSQL_FIELD_TYPE_VAR_STRING: + return "var string"; + case MYSQL_FIELD_TYPE_STRING: + return "string"; + case MYSQL_FIELD_TYPE_GEOMETRY: + return "geometry"; + default: + return "Unknown Field Type"; + } +} + +} // namespace brpc diff --git a/src/brpc/policy/mysql/mysql_common.h b/src/brpc/policy/mysql/mysql_common.h new file mode 100644 index 0000000..e471ac2 --- /dev/null +++ b/src/brpc/policy/mysql/mysql_common.h @@ -0,0 +1,196 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Authors: Yang,Liming (yangliming01@baidu.com) + +#ifndef BRPC_MYSQL_COMMON_H +#define BRPC_MYSQL_COMMON_H + +#include +#include +#include "butil/logging.h" // LOG() + +namespace brpc { +// Mysql collations +extern const char* MysqlDefaultCollation; +extern const std::map MysqlCollations; + +enum MysqlFieldType : uint8_t { + MYSQL_FIELD_TYPE_DECIMAL = 0x00, + MYSQL_FIELD_TYPE_TINY = 0x01, + MYSQL_FIELD_TYPE_SHORT = 0x02, + MYSQL_FIELD_TYPE_LONG = 0x03, + MYSQL_FIELD_TYPE_FLOAT = 0x04, + MYSQL_FIELD_TYPE_DOUBLE = 0x05, + MYSQL_FIELD_TYPE_NULL = 0x06, + MYSQL_FIELD_TYPE_TIMESTAMP = 0x07, + MYSQL_FIELD_TYPE_LONGLONG = 0x08, + MYSQL_FIELD_TYPE_INT24 = 0x09, + MYSQL_FIELD_TYPE_DATE = 0x0A, + MYSQL_FIELD_TYPE_TIME = 0x0B, + MYSQL_FIELD_TYPE_DATETIME = 0x0C, + MYSQL_FIELD_TYPE_YEAR = 0x0D, + MYSQL_FIELD_TYPE_NEWDATE = 0x0E, + MYSQL_FIELD_TYPE_VARCHAR = 0x0F, + MYSQL_FIELD_TYPE_BIT = 0x10, + MYSQL_FIELD_TYPE_JSON = 0xF5, + MYSQL_FIELD_TYPE_NEWDECIMAL = 0xF6, + MYSQL_FIELD_TYPE_ENUM = 0xF7, + MYSQL_FIELD_TYPE_SET = 0xF8, + MYSQL_FIELD_TYPE_TINY_BLOB = 0xF9, + MYSQL_FIELD_TYPE_MEDIUM_BLOB = 0xFA, + MYSQL_FIELD_TYPE_LONG_BLOB = 0xFB, + MYSQL_FIELD_TYPE_BLOB = 0xFC, + MYSQL_FIELD_TYPE_VAR_STRING = 0xFD, + MYSQL_FIELD_TYPE_STRING = 0xFE, + MYSQL_FIELD_TYPE_GEOMETRY = 0xFF, +}; + +enum MysqlFieldFlag : uint16_t { + MYSQL_NOT_NULL_FLAG = 0x0001, + MYSQL_PRI_KEY_FLAG = 0x0002, + MYSQL_UNIQUE_KEY_FLAG = 0x0004, + MYSQL_MULTIPLE_KEY_FLAG = 0x0008, + MYSQL_BLOB_FLAG = 0x0010, + MYSQL_UNSIGNED_FLAG = 0x0020, + MYSQL_ZEROFILL_FLAG = 0x0040, + MYSQL_BINARY_FLAG = 0x0080, + MYSQL_ENUM_FLAG = 0x0100, + MYSQL_AUTO_INCREMENT_FLAG = 0x0200, + MYSQL_TIMESTAMP_FLAG = 0x0400, + MYSQL_SET_FLAG = 0x0800, +}; + +enum MysqlServerStatus : uint16_t { + MYSQL_SERVER_STATUS_IN_TRANS = 1, + MYSQL_SERVER_STATUS_AUTOCOMMIT = 2, /* Server in auto_commit mode */ + MYSQL_SERVER_MORE_RESULTS_EXISTS = 8, /* Multi query - next query exists */ + MYSQL_SERVER_QUERY_NO_GOOD_INDEX_USED = 16, + MYSQL_SERVER_QUERY_NO_INDEX_USED = 32, + /** + The server was able to fulfill the clients request and opened a + read-only non-scrollable cursor for a query. This flag comes + in reply to COM_STMT_EXECUTE and COM_STMT_FETCH commands. + */ + MYSQL_SERVER_STATUS_CURSOR_EXISTS = 64, + /** + This flag is sent when a read-only cursor is exhausted, in reply to + COM_STMT_FETCH command. + */ + MYSQL_SERVER_STATUS_LAST_ROW_SENT = 128, + MYSQL_SERVER_STATUS_DB_DROPPED = 256, /* A database was dropped */ + MYSQL_SERVER_STATUS_NO_BACKSLASH_ESCAPES = 512, + /** + Sent to the client if after a prepared statement reprepare + we discovered that the new statement returns a different + number of result set columns. + */ + MYSQL_SERVER_STATUS_METADATA_CHANGED = 1024, + MYSQL_SERVER_QUERY_WAS_SLOW = 2048, + + /** + To mark ResultSet containing output parameter values. + */ + MYSQL_SERVER_PS_OUT_PARAMS = 4096, + + /** + Set at the same time as MYSQL_SERVER_STATUS_IN_TRANS if the started + multi-statement transaction is a read-only transaction. Cleared + when the transaction commits or aborts. Since this flag is sent + to clients in OK and EOF packets, the flag indicates the + transaction status at the end of command execution. + */ + MYSQL_SERVER_STATUS_IN_TRANS_READONLY = 8192, + MYSQL_SERVER_SESSION_STATE_CHANGED = 1UL << 14, +}; + +// 1. normal statement 2. prepared statement 3. need prepare statement +enum MysqlStmtType : uint32_t { + MYSQL_NORMAL_STATEMENT = 1, + MYSQL_PREPARED_STATEMENT = 2, + MYSQL_NEED_PREPARE = 3, +}; + +const char* MysqlFieldTypeToString(MysqlFieldType); + +inline std::string pack_encode_length(const uint64_t value) { + std::stringstream ss; + if (value <= 250) { + ss.put((char)value); + } else if (value <= 0xffff) { + ss.put((char)0xfc).put((char)value).put((char)(value >> 8)); + } else if (value <= 0xffffff) { + ss.put((char)0xfd).put((char)value).put((char)(value >> 8)).put((char)(value >> 16)); + } else { + ss.put((char)0xfe) + .put((char)value) + .put((char)(value >> 8)) + .put((char)(value >> 16)) + .put((char)(value >> 24)) + .put((char)(value >> 32)) + .put((char)(value >> 40)) + .put((char)(value >> 48)) + .put((char)(value >> 56)); + } + return ss.str(); +} + +// little endian order to host order +#if !defined(ARCH_CPU_LITTLE_ENDIAN) + +inline uint16_t mysql_uint2korr(const uint8_t* A) { + return (uint16_t)(((uint16_t)(A[0])) + ((uint16_t)(A[1]) << 8)); +} + +inline uint32_t mysql_uint3korr(const uint8_t* A) { + return (uint32_t)(((uint32_t)(A[0])) + (((uint32_t)(A[1])) << 8) + (((uint32_t)(A[2])) << 16)); +} + +inline uint32_t mysql_uint4korr(const uint8_t* A) { + return (uint32_t)(((uint32_t)(A[0])) + (((uint32_t)(A[1])) << 8) + (((uint32_t)(A[2])) << 16) + + (((uint32_t)(A[3])) << 24)); +} + +inline uint64_t mysql_uint8korr(const uint8_t* A) { + return (uint64_t)(((uint64_t)(A[0])) + (((uint64_t)(A[1])) << 8) + (((uint64_t)(A[2])) << 16) + + (((uint64_t)(A[3])) << 24) + (((uint64_t)(A[4])) << 32) + + (((uint64_t)(A[5])) << 40) + (((uint64_t)(A[6])) << 48) + + (((uint64_t)(A[7])) << 56)); +} + +#else + +inline uint16_t mysql_uint2korr(const uint8_t* A) { + return *(uint16_t*)A; +} + +inline uint32_t mysql_uint3korr(const uint8_t* A) { + return (uint32_t)(((uint32_t)(A[0])) + (((uint32_t)(A[1])) << 8) + (((uint32_t)(A[2])) << 16)); +} + +inline uint32_t mysql_uint4korr(const uint8_t* A) { + return *(uint32_t*)A; +} + +inline uint64_t mysql_uint8korr(const uint8_t* A) { + return *(uint64_t*)A; +} + +#endif + +} // namespace brpc +#endif diff --git a/src/brpc/policy/mysql/mysql_protocol.cpp b/src/brpc/policy/mysql/mysql_protocol.cpp new file mode 100644 index 0000000..c82f1a2 --- /dev/null +++ b/src/brpc/policy/mysql/mysql_protocol.cpp @@ -0,0 +1,540 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Authors: Yang,Liming (yangliming01@baidu.com) + +#include // MethodDescriptor +#include // Message +#include +#include +#include "butil/logging.h" // LOG() +#include "butil/time.h" +#include "butil/iobuf.h" // butil::IOBuf +#include "butil/sys_byteorder.h" +#include "brpc/controller.h" // Controller +#include "brpc/details/controller_private_accessor.h" +#include "brpc/socket.h" // Socket +#include "brpc/server.h" // Server +#include "brpc/details/server_private_accessor.h" +#include "brpc/span.h" +#include "brpc/policy/mysql/mysql.h" +#include "brpc/policy/mysql/mysql_authenticator.h" +#include "brpc/policy/mysql/mysql_protocol.h" +#include "brpc/policy/mysql/mysql_auth_scramble.h" + +namespace brpc { + +DECLARE_bool(enable_rpcz); + +namespace policy { + +DEFINE_bool(mysql_verbose, false, "Print all mysql requests and responses"); + +void MysqlParseAuthenticator(const butil::StringPiece& raw, + std::string* user, + std::string* password, + std::string* schema, + std::string* collation); +void MysqlParseParams(const butil::StringPiece& raw, std::string* params); +// pack mysql authentication_data +int MysqlPackAuthenticator(const MysqlReply::Auth& auth, + const butil::StringPiece& user, + const butil::StringPiece& password, + const butil::StringPiece& schema, + const butil::StringPiece& collation, + std::string* auth_cmd); +int MysqlPackParams(const butil::StringPiece& params, std::string* param_cmd); + +namespace { +// The connection-phase handshake spans several packets, so it needs per-connection +// (not per-RPC) scratch state. Rather than add a field to the shared Controller, we +// reuse the per-connection AuthContext: group() tracks the auth step, and (for +// caching_sha2_password below) roles() stashes the salt across the RSA round trip. +const char* auth_step[] = {"AUTH_OK", "PARAMS_OK"}; + +// Extra AuthContext group/state markers for the caching_sha2_password +// multi-round-trip exchange. After the client sends the 32-byte fast +// scramble in its HandshakeResponse41 (group still default/empty), the +// server may answer with an AuthMoreData status byte. These markers track +// where we are in that follow-up handshake so re-entries pick the right +// branch: +// CACHE_SHA2_SENT : sent the fast scramble; awaiting the server's +// AuthMoreData (0x03 fast-auth / 0x04 full-auth) or OK. +// CACHE_SHA2_PUBKEY : on plain TCP full-auth, we requested the RSA public +// key (sent 0x02); awaiting the AuthMoreData carrying +// the PEM, after which we send the RSA-encrypted pw. +const char* kCacheSha2Sent = "CACHE_SHA2_SENT"; +const char* kCacheSha2Pubkey = "CACHE_SHA2_PUBKEY"; + +// Frames |payload| as a single MySQL packet: 3-byte little-endian payload +// length + 1-byte sequence id, then the payload, and writes it to |fd|. +// |seq| is the sequence id the packet must carry (the previous server +// packet's seq + 1, per the MySQL packet-sequence rule). +static void WriteMysqlAuthPacket(int fd, const std::string& payload, uint8_t seq) { + butil::IOBuf buf; + const uint32_t len = butil::ByteSwapToLE32((uint32_t)payload.size()); + buf.append(&len, 3); + buf.push_back((char)seq); + buf.append(payload); + buf.cut_into_file_descriptor(fd); +} + +struct InputResponse : public InputMessageBase { + bthread_id_t id_wait; + MysqlResponse response; + + // @InputMessageBase + void DestroyImpl() { + delete this; + } +}; + +bool PackRequest(butil::IOBuf* buf, + ControllerPrivateAccessor& accessor, + const butil::IOBuf& request) { + if (accessor.pipelined_count() == MYSQL_PREPARED_STATEMENT) { + Socket* sock = accessor.get_sending_socket(); + if (sock == NULL) { + LOG(ERROR) << "[MYSQL PACK] get sending socket with NULL"; + return false; + } + auto stub = static_cast(accessor.session_data()); + if (stub == NULL) { + LOG(ERROR) << "[MYSQL PACK] get prepare statement with NULL"; + return false; + } + uint32_t stmt_id; + // if can't found stmt_id in this socket, create prepared statement on it, store user + // request. + if ((stmt_id = stub->stmt()->StatementId(sock->id())) == 0) { + butil::IOBuf b; + butil::Status st = MysqlMakeCommand(&b, MYSQL_COM_STMT_PREPARE, stub->stmt()->str()); + if (!st.ok()) { + LOG(ERROR) << "[MYSQL PACK] make prepare statement error " << st; + return false; + } + accessor.set_mysql_statement_type(MYSQL_NEED_PREPARE); + buf->append(b); + return true; + } + // else pack execute header with stmt_id + butil::Status st = stub->PackExecuteCommand(buf, stmt_id); + if (!st.ok()) { + LOG(ERROR) << "write execute data error " << st; + return false; + } + return true; + } + buf->append(request); + return true; +} + +ParseError HandleAuthentication(const InputResponse* msg, const Socket* socket, PipelinedInfo* pi) { + const bthread_id_t cid = pi->id_wait; + Controller* cntl = NULL; + if (bthread_id_lock(cid, (void**)&cntl) != 0) { + LOG(ERROR) << "[MYSQL PARSE] fail to lock controller"; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + + ParseError parseCode = PARSE_OK; + const AuthContext* ctx = socket->auth_context(); + if (ctx == NULL) { + parseCode = PARSE_ERROR_ABSOLUTELY_WRONG; + LOG(ERROR) << "[MYSQL PARSE] auth context is null"; + goto END_OF_AUTH; + } + if (msg->response.reply(0).is_auth()) { + std::string user, password, schema, collation, auth_cmd; + const MysqlReply& reply = msg->response.reply(0); + MysqlParseAuthenticator(ctx->user(), &user, &password, &schema, &collation); + if (MysqlPackAuthenticator(reply.auth(), user, password, schema, collation, &auth_cmd) == + 0) { + butil::IOBuf buf; + buf.append(auth_cmd); + const ssize_t nw = buf.cut_into_file_descriptor(socket->fd()); + if (nw < 0 || !buf.empty()) { + LOG(WARNING) << "[MYSQL PARSE] failed to write auth command to fd=" + << socket->fd() << ", nw=" << nw + << ", remaining=" << buf.size(); + } + const bool is_caching_sha2 = (reply.auth().auth_plugin() == "caching_sha2_password"); + if (is_caching_sha2) { + // caching_sha2_password is a multi-round-trip exchange: stash + // the 20-byte salt (greeting salt + salt2) for a later RSA + // full-auth round, and mark that the fast scramble was sent. + // _roles is otherwise unused on the mysql path. + std::string salt; + salt.append(reply.auth().salt().data(), reply.auth().salt().size()); + salt.append(reply.auth().salt2().data(), reply.auth().salt2().size()); + const_cast(ctx)->set_roles(salt); + const_cast(ctx)->set_group(kCacheSha2Sent); + } else { + const_cast(ctx)->set_group(auth_step[0]); + } + } else { + parseCode = PARSE_ERROR_ABSOLUTELY_WRONG; + LOG(ERROR) << "[MYSQL PARSE] wrong pack authentication data"; + } + } else if (msg->response.reply(0).is_auth_more_data()) { + // caching_sha2_password follow-up packet (server -> client). The + // first data byte after the 0x01 tag is a status marker, except when + // we are awaiting the RSA public key (CACHE_SHA2_PUBKEY), in which + // case the whole payload is the PEM public key. + std::string user, password, schema, collation; + MysqlParseAuthenticator(ctx->user(), &user, &password, &schema, &collation); + const MysqlReply::AuthMoreData& amd = msg->response.reply(0).auth_more_data(); + const butil::StringPiece data = amd.data(); + const uint8_t next_seq = (uint8_t)(amd.seq() + 1); + if (ctx->group() == kCacheSha2Pubkey) { + // The payload is the server's PEM RSA public key. Encrypt the + // password with it (plain-TCP full-auth) and send the ciphertext. + const std::string rsa = mysql::CachingSha2PasswordRsaEncrypt( + data, ctx->roles(), password); + if (rsa.empty()) { + parseCode = PARSE_ERROR_ABSOLUTELY_WRONG; + LOG(ERROR) << "[MYSQL PARSE] failed to RSA-encrypt caching_sha2 password"; + goto END_OF_AUTH; + } + WriteMysqlAuthPacket(socket->fd(), rsa, next_seq); + // Stay in CACHE_SHA2_SENT-equivalent: the server replies OK next. + const_cast(ctx)->set_group(kCacheSha2Sent); + } else if (!data.empty() && (uint8_t)data[0] == 0x03) { + // fast_auth_success: server will send OK next; send nothing. + const_cast(ctx)->set_group(kCacheSha2Sent); + } else if (!data.empty() && (uint8_t)data[0] == 0x04) { + // perform_full_authentication. + if (socket->is_ssl()) { + // Secure channel: send the cleartext password (one round trip). + const std::string clear = mysql::CachingSha2PasswordCleartext(password); + WriteMysqlAuthPacket(socket->fd(), clear, next_seq); + const_cast(ctx)->set_group(kCacheSha2Sent); + } else { + // Plain TCP: request the server's RSA public key (0x02), then + // wait for the AuthMoreData carrying the PEM. + WriteMysqlAuthPacket(socket->fd(), std::string(1, (char)0x02), next_seq); + const_cast(ctx)->set_group(kCacheSha2Pubkey); + } + } else { + parseCode = PARSE_ERROR_ABSOLUTELY_WRONG; + LOG(ERROR) << "[MYSQL PARSE] unexpected caching_sha2 AuthMoreData marker"; + } + } else if (msg->response.reply_size() > 0) { + for (size_t i = 0; i < msg->response.reply_size(); ++i) { + if (!msg->response.reply(i).is_ok()) { + LOG(ERROR) << "[MYSQL PARSE] auth failed " << msg->response; + parseCode = PARSE_ERROR_NO_RESOURCE; + goto END_OF_AUTH; + } + } + std::string params, params_cmd; + MysqlParseParams(ctx->user(), ¶ms); + // Auth just completed (either native's single round trip, group + // AUTH_OK, or caching_sha2's multi round-trip, group CACHE_SHA2_SENT) + // and connection params have not been sent yet: send them now. + const bool auth_just_done = + (ctx->group() == auth_step[0] || ctx->group() == kCacheSha2Sent); + if (auth_just_done && !params.empty()) { + if (MysqlPackParams(params, ¶ms_cmd) == 0) { + butil::IOBuf buf; + buf.append(params_cmd); + buf.cut_into_file_descriptor(socket->fd()); + const_cast(ctx)->set_group(auth_step[1]); + } else { + parseCode = PARSE_ERROR_ABSOLUTELY_WRONG; + LOG(ERROR) << "[MYSQL PARSE] wrong pack params data"; + } + } else { + butil::IOBuf raw_req; + raw_req.append(ctx->starter()); + raw_req.cut_into_file_descriptor(socket->fd()); + pi->auth_flags = 0; + } + } else { + parseCode = PARSE_ERROR_ABSOLUTELY_WRONG; + LOG(ERROR) << "[MYSQL PARSE] wrong authentication step"; + } + +END_OF_AUTH: + if (bthread_id_unlock(cid) != 0) { + parseCode = PARSE_ERROR_ABSOLUTELY_WRONG; + LOG(ERROR) << "[MYSQL PARSE] fail to unlock controller"; + } + return parseCode; +} + +ParseError HandlePrepareStatement(const InputResponse* msg, + const Socket* socket, + PipelinedInfo* pi) { + if (!msg->response.reply(0).is_prepare_ok()) { + LOG(ERROR) << "[MYSQL PARSE] response is not prepare ok, " << msg->response; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + const MysqlReply::PrepareOk& ok = msg->response.reply(0).prepare_ok(); + const bthread_id_t cid = pi->id_wait; + Controller* cntl = NULL; + if (bthread_id_lock(cid, (void**)&cntl) != 0) { + LOG(ERROR) << "[MYSQL PARSE] fail to lock controller"; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + ParseError parseCode = PARSE_OK; + butil::IOBuf buf; + butil::Status st; + MysqlStatementStub* stub = NULL; + MysqlStatement* stmt = NULL; + stub = static_cast(ControllerPrivateAccessor(cntl).session_data()); + if (stub == NULL) { + LOG(ERROR) << "[MYSQL PACK] get prepare statement with NULL"; + parseCode = PARSE_ERROR_ABSOLUTELY_WRONG; + goto END_OF_PREPARE; + } + stmt = stub->stmt(); + if (stmt == NULL || stmt->param_count() != ok.param_count()) { + LOG(ERROR) << "[MYSQL PACK] stmt can't be NULL"; + parseCode = PARSE_ERROR_ABSOLUTELY_WRONG; + goto END_OF_PREPARE; + } + if (stmt->param_count() != ok.param_count()) { + LOG(ERROR) << "[MYSQL PACK] stmt param number " << stmt->param_count() + << " not equal to prepareOk.param_number " << ok.param_count(); + parseCode = PARSE_ERROR_ABSOLUTELY_WRONG; + goto END_OF_PREPARE; + } + stmt->SetStatementId(socket->id(), ok.stmt_id()); + st = stub->PackExecuteCommand(&buf, ok.stmt_id()); + if (!st.ok()) { + LOG(ERROR) << "[MYSQL PACK] make execute header error " << st; + parseCode = PARSE_ERROR_ABSOLUTELY_WRONG; + goto END_OF_PREPARE; + } + { + const ssize_t nw = buf.cut_into_file_descriptor(socket->fd()); + if (nw < 0 || !buf.empty()) { + LOG(WARNING) << "[MYSQL PARSE] failed to write execute command to fd=" + << socket->fd() << ", nw=" << nw + << ", remaining=" << buf.size(); + } + } + pi->count = MYSQL_PREPARED_STATEMENT; +END_OF_PREPARE: + if (bthread_id_unlock(cid) != 0) { + parseCode = PARSE_ERROR_ABSOLUTELY_WRONG; + LOG(ERROR) << "[MYSQL PARSE] fail to unlock controller"; + } + return parseCode; +} + +} // namespace + +// "Message" = "Response" as we only implement the client for mysql. +ParseResult ParseMysqlMessage(butil::IOBuf* source, + Socket* socket, + bool /*read_eof*/, + const void* /*arg*/) { + if (source->empty()) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + + PipelinedInfo pi; + if (!socket->PopPipelinedInfo(&pi)) { + LOG(WARNING) << "No corresponding PipelinedInfo in socket"; + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + + InputResponse* msg = static_cast(socket->parsing_context()); + if (msg == NULL) { + msg = new InputResponse; + socket->reset_parsing_context(msg); + } + + MysqlStmtType stmt_type = static_cast(pi.count); + ParseError err = msg->response.ConsumePartialIOBuf(*source, pi.auth_flags != 0, stmt_type); + if (FLAGS_mysql_verbose) { + LOG(INFO) << "[MYSQL PARSE] " << msg->response; + } + if (err != PARSE_OK) { + if (err == PARSE_ERROR_NOT_ENOUGH_DATA) { + socket->GivebackPipelinedInfo(pi); + } + return MakeParseError(err); + } + if (pi.auth_flags) { + ParseError err = HandleAuthentication(msg, socket, &pi); + if (err != PARSE_OK) { + return MakeParseError(err, "Fail to authenticate with Mysql"); + } + DestroyingPtr auth_msg = + static_cast(socket->release_parsing_context()); + socket->GivebackPipelinedInfo(pi); + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + if (stmt_type == MYSQL_NEED_PREPARE) { + // A failed PREPARE (e.g. ER_PARSE_ERROR 1064) comes back as a normal + // ERR packet. Deliver it to the caller like any other error response + // and keep the connection open -- matching the command path and other + // protocols (redis, baidu_std). Only a successful prepare proceeds to + // pack and send the COM_STMT_EXECUTE. + if (!msg->response.reply(0).is_prepare_ok()) { + msg->id_wait = pi.id_wait; + socket->release_parsing_context(); + return MakeMessage(msg); + } + // store stmt_id, make execute header. + ParseError err = HandlePrepareStatement(msg, socket, &pi); + if (err != PARSE_OK) { + return MakeParseError(err, "Fail to make parepared statement with Mysql"); + } + DestroyingPtr prepare_msg = + static_cast(socket->release_parsing_context()); + socket->GivebackPipelinedInfo(pi); + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + msg->id_wait = pi.id_wait; + socket->release_parsing_context(); + return MakeMessage(msg); +} + +void ProcessMysqlResponse(InputMessageBase* msg_base) { + const int64_t start_parse_us = butil::cpuwide_time_us(); + DestroyingPtr msg(static_cast(msg_base)); + + const bthread_id_t cid = msg->id_wait; + Controller* cntl = NULL; + const int rc = bthread_id_lock(cid, (void**)&cntl); + if (rc != 0) { + LOG_IF(ERROR, rc != EINVAL && rc != EPERM) + << "Fail to lock correlation_id=" << cid << ": " << berror(rc); + return; + } + + ControllerPrivateAccessor accessor(cntl); + // Controller::span() returns a std::shared_ptr in current master + // (was a raw Span* when #2093 was written). + if (auto span = accessor.span()) { + span->set_base_real_us(msg->base_real_us()); + span->set_received_us(msg->received_us()); + span->set_response_size(msg->response.ByteSize()); + span->set_start_parse_us(start_parse_us); + } + const int saved_error = cntl->ErrorCode(); + if (cntl->response() != NULL) { + if (cntl->response()->GetDescriptor() != MysqlResponse::descriptor()) { + LOG(ERROR) << "[MYSQL PROCESS] response message is not a MysqlResponse"; + cntl->SetFailed(ERESPONSE, "Must be MysqlResponse"); + } else { + // We work around ParseFrom of pb which is just a placeholder. + ((MysqlResponse*)cntl->response())->Swap(&msg->response); + } + } // silently ignore the response. + + // Unlocks correlation_id inside. Revert controller's + // error code if it version check of `cid' fails + msg.reset(); // optional, just release resourse ASAP + accessor.OnResponse(cid, saved_error); +} + +void SerializeMysqlRequest(butil::IOBuf* buf, + Controller* cntl, + const google::protobuf::Message* request) { + if (request == NULL) { + LOG(ERROR) << "[MYSQL SERIALIZE] request is NULL"; + return cntl->SetFailed(EREQUEST, "request is NULL"); + } + if (request->GetDescriptor() != MysqlRequest::descriptor()) { + LOG(ERROR) << "[MYSQL SERIALIZE] request message is not a MysqlRequest"; + return cntl->SetFailed(EREQUEST, "The request is not a MysqlRequest"); + } + const MysqlRequest* rr = (const MysqlRequest*)request; + // We work around SerializeTo of pb which is just a placeholder. + if (!rr->SerializeTo(buf)) { + LOG(ERROR) << "[MYSQL SERIALIZE] failed to serialize MysqlRequest to IOBuf"; + return cntl->SetFailed(EREQUEST, "Fail to serialize MysqlRequest"); + } + // mysql doesn't use pipelined_count to verify the end of a response; instead we + // reuse it as a MysqlStmtType tag so the parse function knows which reply shape + // to expect (OK and PrepareOk are otherwise indistinguishable). Default to + // MYSQL_NORMAL_STATEMENT (1); it is upgraded to MYSQL_PREPARED_STATEMENT (2) + // below when the request carries a prepared statement. + ControllerPrivateAccessor accessor(cntl); + accessor.set_mysql_statement_type(MYSQL_NORMAL_STATEMENT); + + auto tx = rr->tx(); + if (tx != NULL) { + accessor.use_bind_sock(tx->GetSocketId()); + } + auto st = rr->stmt(); + if (st != NULL) { + accessor.set_session_data(rr->stmt()); + accessor.set_mysql_statement_type(MYSQL_PREPARED_STATEMENT); + } + if (FLAGS_mysql_verbose) { + LOG(INFO) << "\n[MYSQL REQUEST] " << *rr; + } +} + +void PackMysqlRequest(butil::IOBuf* buf, + SocketMessage**, + uint64_t /*correlation_id*/, + const google::protobuf::MethodDescriptor*, + Controller* cntl, + const butil::IOBuf& request, + const Authenticator* auth) { + ControllerPrivateAccessor accessor(cntl); + if (auth) { + const MysqlAuthenticator* my_auth(dynamic_cast(auth)); + if (my_auth == NULL) { + LOG(ERROR) << "[MYSQL PACK] there is not MysqlAuthenticator"; + return; + } + Socket* sock = accessor.get_sending_socket(); + if (sock == NULL) { + LOG(ERROR) << "[MYSQL PACK] get sending socket with NULL"; + return; + } + AuthContext* ctx = sock->mutable_auth_context(); + std::string str; + if (!my_auth->SerializeToString(&str)) { + LOG(ERROR) << "[MYSQL PACK] auth param serialize to string failed"; + return; + } + ctx->set_user(str); + butil::IOBuf b; + if (!PackRequest(&b, accessor, request)) { + LOG(ERROR) << "[MYSQL PACK] pack request error"; + return; + } + ctx->set_starter(b.to_string()); + // Mark this as an auth write so the connection-phase handshake is run + // and the (empty) data buffer is allowed through Socket::Write. Mirrors + // redis's set_auth_flags(); 1 == "this pipelined slot is the auth reply". + accessor.set_auth_flags(1); + } else { + if (!PackRequest(buf, accessor, request)) { + LOG(ERROR) << "[MYSQL PACK] pack request error"; + return; + } + } +} + +const std::string& GetMysqlMethodName(const google::protobuf::MethodDescriptor*, + const Controller*) { + const static std::string MYSQL_SERVER_STR = "mysql-server"; + return MYSQL_SERVER_STR; +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/mysql/mysql_protocol.h b/src/brpc/policy/mysql/mysql_protocol.h new file mode 100644 index 0000000..816bd5c --- /dev/null +++ b/src/brpc/policy/mysql/mysql_protocol.h @@ -0,0 +1,55 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Authors: Yang,Liming (yangliming01@baidu.com) + +#ifndef BRPC_POLICY_MYSQL_PROTOCOL_H +#define BRPC_POLICY_MYSQL_PROTOCOL_H + +#include "brpc/protocol.h" + + +namespace brpc { +namespace policy { + +// Parse mysql response. +ParseResult ParseMysqlMessage(butil::IOBuf* source, Socket* socket, bool read_eof, const void* arg); + +// Actions to a mysql response. +void ProcessMysqlResponse(InputMessageBase* msg); + +// Serialize a mysql request. +void SerializeMysqlRequest(butil::IOBuf* buf, + Controller* cntl, + const google::protobuf::Message* request); + +// Pack `request' to `method' into `buf'. +void PackMysqlRequest(butil::IOBuf* buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* controller, + const butil::IOBuf& request, + const Authenticator* auth); + +const std::string& GetMysqlMethodName(const google::protobuf::MethodDescriptor*, const Controller*); + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_MYSQL_PROTOCOL_H diff --git a/src/brpc/policy/mysql/mysql_reply.cpp b/src/brpc/policy/mysql/mysql_reply.cpp new file mode 100644 index 0000000..0a29964 --- /dev/null +++ b/src/brpc/policy/mysql/mysql_reply.cpp @@ -0,0 +1,1467 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Authors: Yang,Liming (yangliming01@baidu.com) + +#include "brpc/policy/mysql/mysql_common.h" +#include "brpc/policy/mysql/mysql_reply.h" +#include "butil/logging.h" // LOG() + +namespace brpc { + +#define MY_ALLOC_CHECK(expr) \ + do { \ + if ((expr) == false) { \ + return PARSE_ERROR_ABSOLUTELY_WRONG; \ + } \ + } while (0) + +#define MY_PARSE_CHECK(expr) \ + do { \ + ParseError rc = (expr); \ + if (rc != PARSE_OK) { \ + return rc; \ + } \ + } while (0) + +template +inline bool my_alloc_check(butil::Arena* arena, const size_t n, Type*& pointer) { + if (pointer == NULL) { + pointer = (Type*)arena->allocate(sizeof(Type) * n); + if (pointer == NULL) { + LOG(ERROR) << "my_alloc_check: arena failed to allocate " << (sizeof(Type) * n) + << " bytes (n=" << n << ")"; + return false; + } + for (size_t i = 0; i < n; ++i) { + new (pointer + i) Type; + } + } + return true; +} + +template <> +inline bool my_alloc_check(butil::Arena* arena, const size_t n, char*& pointer) { + if (pointer == NULL) { + pointer = (char*)arena->allocate(sizeof(char) * n); + if (pointer == NULL) { + LOG(ERROR) << "my_alloc_check: arena failed to allocate " << n << " char bytes"; + return false; + } + } + return true; +} + +namespace { +struct MysqlHeader { + uint32_t payload_size; + uint32_t seq; +}; +const char* digits01 = + "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123" + "456789"; +const char* digits10 = + "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999" + "999999"; + +// Emit a zero fractional-second part ".000..." for a column that declares +// `decimal` digits but whose binary value carries no microsecond bytes on the +// wire (e.g. DATETIME(3) with a zero fraction is sent with len==7, TIME(3) +// with len==8). Keeps the formatted string length consistent with dstlen. +inline void write_zero_microsecs(uint8_t decimal, char* d) { + if (decimal == 0 || decimal == 0x1f) { + return; + } + uint8_t n = decimal > 6 ? 6 : decimal; + size_t i = 0; + d[i++] = '.'; + for (uint8_t k = 0; k < n; ++k) { + d[i++] = '0'; + } +} +} // namespace + +const char* MysqlRspTypeToString(MysqlRspType type) { + switch (type) { + case MYSQL_RSP_OK: + return "ok"; + case MYSQL_RSP_ERROR: + return "error"; + case MYSQL_RSP_RESULTSET: + return "resultset"; + case MYSQL_RSP_EOF: + return "eof"; + case MYSQL_RSP_AUTH: + return "auth"; + case MYSQL_RSP_AUTH_MORE_DATA: + return "auth_more_data"; + case MYSQL_RSP_PREPARE_OK: + return "prepare_ok"; + default: + return "Unknown Response Type"; + } +} + +// check if the buf is contain a full package +inline bool is_full_package(const butil::IOBuf& buf) { + uint8_t header[4]; + const uint8_t* p = (const uint8_t*)buf.fetch(header, sizeof(header)); + if (p == NULL) { + return false; + } + uint32_t payload_size = mysql_uint3korr(p); + if (buf.size() < payload_size + 4) { + return false; + } + return true; +} +// if is eof package +inline bool is_an_eof(const butil::IOBuf& buf) { + uint8_t tmp[5]; + const uint8_t* p = (const uint8_t*)buf.fetch(tmp, sizeof(tmp)); + if (p == NULL) { + return false; + } + uint8_t type = p[4]; + if (type == MYSQL_RSP_EOF) { + return true; + } else { + return false; + } +} +// parse header +inline bool parse_header(butil::IOBuf& buf, MysqlHeader* value) { + if (!is_full_package(buf)) { + return false; + } + { + uint8_t tmp[3]; + buf.cutn(tmp, sizeof(tmp)); + value->payload_size = mysql_uint3korr(tmp); + } + { + uint8_t tmp; + buf.cut1((char*)&tmp); + value->seq = tmp; + } + return true; +} +// use this carefully, we depending on parse_header for checking IOBuf contain full package +inline uint64_t parse_encode_length(butil::IOBuf& buf) { + if (buf.size() == 0) { + return 0; + } + + uint64_t value = 0; + uint8_t f = 0; + buf.cut1((char*)&f); + if (f <= 250) { + value = f; + } else if (f == 251) { + value = 0; + } else if (f == 252) { + uint8_t tmp[2]; + buf.cutn(tmp, sizeof(tmp)); + value = mysql_uint2korr(tmp); + } else if (f == 253) { + uint8_t tmp[3]; + buf.cutn(tmp, sizeof(tmp)); + value = mysql_uint3korr(tmp); + } else if (f == 254) { + uint8_t tmp[8]; + buf.cutn(tmp, sizeof(tmp)); + value = mysql_uint8korr(tmp); + } + return value; +} + +ParseError MysqlReply::ConsumePartialIOBuf(butil::IOBuf& buf, + butil::Arena* arena, + bool is_auth, + MysqlStmtType stmt_type, + bool* more_results) { + *more_results = false; + if (!is_full_package(buf)) { + return PARSE_ERROR_NOT_ENOUGH_DATA; + } + uint8_t header[4 + 1]; // use the extra byte to judge message type + const uint8_t* p = (const uint8_t*)buf.fetch(header, sizeof(header)); + uint8_t type = (_type == MYSQL_RSP_UNKNOWN) ? p[4] : (uint8_t)_type; + // During the connection (auth) phase the server may send an AuthMoreData + // packet (first byte 0x01) as part of the caching_sha2_password exchange + // -- a fast-auth/full-auth status byte or the RSA public key. It must be + // recognized here BEFORE the greeting branch, because the greeting + // (HandshakeV10, first byte 0x0a) and AuthMoreData (0x01) are otherwise + // both non-OK/non-error auth packets. Outside the auth phase, a first + // byte of 0x01 is a normal resultset column-count, handled below. + if (is_auth && type == 0x01) { + // Peek the status byte after the 4-byte header + 0x01 tag. A + // fast-auth-success marker (0x03) is immediately followed by a + // terminal OK packet, and the server typically ships both in one TCP + // segment. The response wrapper parses exactly one reply per pass + // and rejects trailing bytes, so when the OK is already buffered we + // skip the 0x03 packet here and let the OK become this reply (the + // auth state machine then proceeds to send the first real query). + // When the OK has not arrived yet, we expose the AuthMoreData so the + // state machine can wait for it. A full-auth marker (0x04) and the + // RSA-pubkey payload always require a client response, so they are + // never coalesced. + uint8_t status[4 + 2]; + const uint8_t* sp = (const uint8_t*)buf.fetch(status, sizeof(status)); + const bool fast_auth_success = (sp != NULL && sp[5] == 0x03); + if (fast_auth_success) { + // Determine, WITHOUT consuming anything, whether the OK packet + // that follows the fast-auth marker is also fully buffered. + const uint32_t amd_total = 4 + mysql_uint3korr(sp); + butil::IOBuf rest; + // Non-destructively copy the bytes that follow the 0x01 packet. + buf.append_to(&rest, buf.size(), amd_total); + if (!is_full_package(rest)) { + // OK not arrived yet: expose the fast-auth marker untouched + // and let the state machine wait for the next packet. + _type = MYSQL_RSP_AUTH_MORE_DATA; + MY_ALLOC_CHECK(my_alloc_check(arena, 1, _data.auth_more_data)); + MY_PARSE_CHECK(_data.auth_more_data->Parse(buf, arena)); + return PARSE_OK; + } + // Both packets buffered: drop the 0x01 packet from |buf| and + // parse the following OK/ERR as this reply. + butil::IOBuf discard; + buf.cutn(&discard, amd_total); + const uint8_t* p2 = (const uint8_t*)buf.fetch(header, sizeof(header)); + type = p2[4]; + } else { + _type = MYSQL_RSP_AUTH_MORE_DATA; + MY_ALLOC_CHECK(my_alloc_check(arena, 1, _data.auth_more_data)); + MY_PARSE_CHECK(_data.auth_more_data->Parse(buf, arena)); + return PARSE_OK; + } + } + if (is_auth && type != 0x00 && type != 0xFF) { + _type = MYSQL_RSP_AUTH; + MY_ALLOC_CHECK(my_alloc_check(arena, 1, _data.auth)); + MY_PARSE_CHECK(_data.auth->Parse(buf, arena)); + return PARSE_OK; + } + if (type == 0x00 && (is_auth || stmt_type != MYSQL_NEED_PREPARE)) { + _type = MYSQL_RSP_OK; + MY_ALLOC_CHECK(my_alloc_check(arena, 1, _data.ok)); + MY_PARSE_CHECK(_data.ok->Parse(buf, arena)); + *more_results = _data.ok->status() & MYSQL_SERVER_MORE_RESULTS_EXISTS; + } else if ((type == 0x00 && stmt_type == MYSQL_NEED_PREPARE) || type == MYSQL_RSP_PREPARE_OK) { + _type = MYSQL_RSP_PREPARE_OK; + MY_ALLOC_CHECK(my_alloc_check(arena, 1, _data.prepare_ok)); + MY_PARSE_CHECK(_data.prepare_ok->Parse(buf, arena)); + } else if (type == 0xFF) { + _type = MYSQL_RSP_ERROR; + MY_ALLOC_CHECK(my_alloc_check(arena, 1, _data.error)); + MY_PARSE_CHECK(_data.error->Parse(buf, arena)); + } else if (type == 0xFE) { + _type = MYSQL_RSP_EOF; + MY_ALLOC_CHECK(my_alloc_check(arena, 1, _data.eof)); + MY_PARSE_CHECK(_data.eof->Parse(buf)); + *more_results = _data.eof->status() & MYSQL_SERVER_MORE_RESULTS_EXISTS; + } else if (type >= 0x01 && type <= 0xFA) { + _type = MYSQL_RSP_RESULTSET; + MY_ALLOC_CHECK(my_alloc_check(arena, 1, _data.result_set)); + MY_PARSE_CHECK(_data.result_set->Parse(buf, arena, !(stmt_type == MYSQL_NORMAL_STATEMENT))); + *more_results = _data.result_set->_eof2.status() & MYSQL_SERVER_MORE_RESULTS_EXISTS; + } else { + LOG(ERROR) << "Unknown Response Type " + << "type=" << unsigned(type) << " buf_size=" << buf.size(); + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + return PARSE_OK; +} + +void MysqlReply::Print(std::ostream& os) const { + if (_type == MYSQL_RSP_AUTH) { + const Auth& auth = *_data.auth; + os << "\nprotocol:" << (unsigned)auth._protocol << "\nversion:" << auth._version + << "\nthread_id:" << auth._thread_id << "\nsalt:" << auth._salt + << "\ncapacity:" << auth._capability << "\nlanguage:" << (unsigned)auth._collation + << "\nstatus:" << auth._status << "\nextended_capacity:" << auth._extended_capability + << "\nauth_plugin_length:" << auth._auth_plugin_length << "\nsalt2:" << auth._salt2 + << "\nauth_plugin:" << auth._auth_plugin; + } else if (_type == MYSQL_RSP_AUTH_MORE_DATA) { + const AuthMoreData& amd = *_data.auth_more_data; + os << "\nauth_more_data.size:" << amd._data.size(); + } else if (_type == MYSQL_RSP_OK) { + const Ok& ok = *_data.ok; + os << "\naffect_row:" << ok._affect_row << "\nindex:" << ok._index + << "\nstatus:" << ok._status << "\nwarning:" << ok._warning << "\nmessage:" << ok._msg; + } else if (_type == MYSQL_RSP_ERROR) { + const Error& err = *_data.error; + os << "\nerrcode:" << err._errcode << "\nstatus:" << err._status + << "\nmessage:" << err._msg; + } else if (_type == MYSQL_RSP_RESULTSET) { + const ResultSet& r = *_data.result_set; + os << "\nheader.column_count:" << r._header._column_count; + for (uint64_t i = 0; i < r._header._column_count; ++i) { + os << "\ncolumn[" << i << "].catalog:" << r._columns[i]._catalog << "\ncolumn[" << i + << "].database:" << r._columns[i]._database << "\ncolumn[" << i + << "].table:" << r._columns[i]._table << "\ncolumn[" << i + << "].origin_table:" << r._columns[i]._origin_table << "\ncolumn[" << i + << "].name:" << r._columns[i]._name << "\ncolumn[" << i + << "].origin_name:" << r._columns[i]._origin_name << "\ncolumn[" << i + << "].charset:" << (uint16_t)r._columns[i]._charset << "\ncolumn[" << i + << "].length:" << r._columns[i]._length << "\ncolumn[" << i + << "].type:" << (unsigned)r._columns[i]._type << "\ncolumn[" << i + << "].flag:" << (unsigned)r._columns[i]._flag << "\ncolumn[" << i + << "].decimal:" << (unsigned)r._columns[i]._decimal; + } + os << "\neof1.warning:" << r._eof1._warning; + os << "\neof1.status:" << r._eof1._status; + int n = 0; + for (const Row* row = r._first->_next; row != r._last->_next; row = row->_next) { + os << "\nrow(" << n++ << "):"; + for (uint64_t j = 0; j < r._header._column_count; ++j) { + if (row->field(j).is_nil()) { + os << "NULL\t"; + continue; + } + switch (row->field(j)._type) { + case MYSQL_FIELD_TYPE_NULL: + os << "NULL"; + break; + case MYSQL_FIELD_TYPE_TINY: + if (r._columns[j]._flag & MYSQL_UNSIGNED_FLAG) { + os << unsigned(row->field(j).tiny()); + } else { + os << signed(row->field(j).stiny()); + } + break; + case MYSQL_FIELD_TYPE_SHORT: + case MYSQL_FIELD_TYPE_YEAR: + if (r._columns[j]._flag & MYSQL_UNSIGNED_FLAG) { + os << unsigned(row->field(j).small()); + } else { + os << signed(row->field(j).ssmall()); + } + break; + case MYSQL_FIELD_TYPE_INT24: + case MYSQL_FIELD_TYPE_LONG: + if (r._columns[j]._flag & MYSQL_UNSIGNED_FLAG) { + os << row->field(j).integer(); + } else { + os << row->field(j).sinteger(); + } + break; + case MYSQL_FIELD_TYPE_LONGLONG: + if (r._columns[j]._flag & MYSQL_UNSIGNED_FLAG) { + os << row->field(j).bigint(); + } else { + os << row->field(j).sbigint(); + } + break; + case MYSQL_FIELD_TYPE_FLOAT: + os << row->field(j).float32(); + break; + case MYSQL_FIELD_TYPE_DOUBLE: + os << row->field(j).float64(); + break; + case MYSQL_FIELD_TYPE_DECIMAL: + case MYSQL_FIELD_TYPE_NEWDECIMAL: + case MYSQL_FIELD_TYPE_VARCHAR: + case MYSQL_FIELD_TYPE_BIT: + case MYSQL_FIELD_TYPE_ENUM: + case MYSQL_FIELD_TYPE_SET: + case MYSQL_FIELD_TYPE_TINY_BLOB: + case MYSQL_FIELD_TYPE_MEDIUM_BLOB: + case MYSQL_FIELD_TYPE_LONG_BLOB: + case MYSQL_FIELD_TYPE_BLOB: + case MYSQL_FIELD_TYPE_VAR_STRING: + case MYSQL_FIELD_TYPE_STRING: + case MYSQL_FIELD_TYPE_GEOMETRY: + case MYSQL_FIELD_TYPE_JSON: + case MYSQL_FIELD_TYPE_TIME: + case MYSQL_FIELD_TYPE_DATE: + case MYSQL_FIELD_TYPE_NEWDATE: + case MYSQL_FIELD_TYPE_TIMESTAMP: + case MYSQL_FIELD_TYPE_DATETIME: + os << row->field(j).string(); + break; + default: + os << "Unknown field type"; + } + os << "\t"; + } + } + os << "\neof2.warning:" << r._eof2._warning; + os << "\neof2.status:" << r._eof2._status; + } else if (_type == MYSQL_RSP_EOF) { + const Eof& e = *_data.eof; + os << "\nwarning:" << e._warning << "\nstatus:" << e._status; + } else if (_type == MYSQL_RSP_PREPARE_OK) { + const PrepareOk& prep = *_data.prepare_ok; + os << "\nstmt_id:" << prep._header._stmt_id + << "\ncolumn_count:" << prep._header._column_count + << "\nparam_count:" << prep._header._param_count; + for (uint16_t i = 0; i < prep._header._param_count; ++i) { + os << "\nparam[" << i << "].catalog:" << prep._params[i]._catalog << "\nparam[" << i + << "].database:" << prep._params[i]._database << "\nparam[" << i + << "].table:" << prep._params[i]._table << "\nparam[" << i + << "].origin_table:" << prep._params[i]._origin_table << "\nparam[" << i + << "].name:" << prep._params[i]._name << "\nparam[" << i + << "].origin_name:" << prep._params[i]._origin_name << "\nparam[" << i + << "].charset:" << (uint16_t)prep._params[i]._charset << "\nparam[" << i + << "].length:" << prep._params[i]._length << "\nparam[" << i + << "].type:" << (unsigned)prep._params[i]._type << "\nparam[" << i + << "].flag:" << (unsigned)prep._params[i]._flag << "\nparam[" << i + << "].decimal:" << (unsigned)prep._params[i]._decimal; + } + for (uint16_t i = 0; i < prep._header._column_count; ++i) { + os << "\ncolumn[" << i << "].catalog:" << prep._columns[i]._catalog << "\ncolumn[" << i + << "].database:" << prep._columns[i]._database << "\ncolumn[" << i + << "].table:" << prep._columns[i]._table << "\ncolumn[" << i + << "].origin_table:" << prep._columns[i]._origin_table << "\ncolumn[" << i + << "].name:" << prep._columns[i]._name << "\ncolumn[" << i + << "].origin_name:" << prep._columns[i]._origin_name << "\ncolumn[" << i + << "].charset:" << (uint16_t)prep._columns[i]._charset << "\ncolumn[" << i + << "].length:" << prep._columns[i]._length << "\ncolumn[" << i + << "].type:" << (unsigned)prep._columns[i]._type << "\ncolumn[" << i + << "].flag:" << (unsigned)prep._columns[i]._flag << "\ncolumn[" << i + << "].decimal:" << (unsigned)prep._columns[i]._decimal; + } + } else { + os << "Unknown response type"; + } +} + +ParseError MysqlReply::Auth::Parse(butil::IOBuf& buf, butil::Arena* arena) { + if (is_parsed()) { + return PARSE_OK; + } + const std::string delim(1, 0x00); + MysqlHeader header; + if (!parse_header(buf, &header)) { + return PARSE_ERROR_NOT_ENOUGH_DATA; + } + buf.cut1((char*)&_protocol); + { + butil::IOBuf version; + buf.cut_until(&version, delim); + char* d = NULL; + MY_ALLOC_CHECK(my_alloc_check(arena, version.size(), d)); + version.copy_to(d); + _version.set(d, version.size()); + } + { + uint8_t tmp[4]; + buf.cutn(tmp, sizeof(tmp)); + _thread_id = mysql_uint4korr(tmp); + } + { + butil::IOBuf salt; + buf.cut_until(&salt, delim); + char* d = NULL; + MY_ALLOC_CHECK(my_alloc_check(arena, salt.size(), d)); + salt.copy_to(d); + _salt.set(d, salt.size()); + } + { + uint8_t tmp[2]; + buf.cutn(&tmp, sizeof(tmp)); + _capability = mysql_uint2korr(tmp); + } + buf.cut1((char*)&_collation); + { + uint8_t tmp[2]; + buf.cutn(tmp, sizeof(tmp)); + _status = mysql_uint2korr(tmp); + } + { + uint8_t tmp[2]; + buf.cutn(tmp, sizeof(tmp)); + _extended_capability = mysql_uint2korr(tmp); + } + buf.cut1((char*)&_auth_plugin_length); + buf.pop_front(10); + { + butil::IOBuf salt2; + buf.cut_until(&salt2, delim); + char* d = NULL; + MY_ALLOC_CHECK(my_alloc_check(arena, salt2.size(), d)); + salt2.copy_to(d); + _salt2.set(d, salt2.size()); + } + { + if (_auth_plugin_length > buf.size()) { + LOG(ERROR) << "MysqlReply::Auth::Parse: auth_plugin length " << _auth_plugin_length + << " exceeds remaining buffer size " << buf.size(); + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + char* d = NULL; + MY_ALLOC_CHECK(my_alloc_check(arena, _auth_plugin_length, d)); + buf.cutn(d, _auth_plugin_length); + _auth_plugin.set(d, _auth_plugin_length); + } + buf.clear(); // consume all buf + set_parsed(); + return PARSE_OK; +} + +ParseError MysqlReply::AuthMoreData::Parse(butil::IOBuf& buf, butil::Arena* arena) { + if (is_parsed()) { + return PARSE_OK; + } + MysqlHeader header; + if (!parse_header(buf, &header)) { + return PARSE_ERROR_NOT_ENOUGH_DATA; + } + _seq = (uint8_t)header.seq; + // Drop the 0x01 AuthMoreData tag; expose only the bytes after it (a + // single status byte 0x03/0x04, or the PEM-encoded RSA public key). + buf.pop_front(1); + const int64_t len = (int64_t)header.payload_size - 1; + if (len > 0) { + char* d = NULL; + MY_ALLOC_CHECK(my_alloc_check(arena, len, d)); + buf.cutn(d, len); + _data.set(d, len); + } else { + _data.set(NULL, 0); + } + set_parsed(); + return PARSE_OK; +} + +ParseError MysqlReply::ResultSetHeader::Parse(butil::IOBuf& buf) { + if (is_parsed()) { + return PARSE_OK; + } + MysqlHeader header; + if (!parse_header(buf, &header)) { + return PARSE_ERROR_NOT_ENOUGH_DATA; + } + uint64_t old_size, new_size; + old_size = buf.size(); + _column_count = parse_encode_length(buf); + // Guard against an absurd/malicious column count driving unbounded + // allocations downstream (per-column arrays and the row NULL-bitmap). + // MySQL's hard limit is 4096 columns per table; 65535 is a generous cap + // that no legitimate result set exceeds. + if (_column_count > 65535) { + LOG(ERROR) << "illegal column count " << _column_count; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + new_size = buf.size(); + if (old_size - new_size < header.payload_size) { + _extra_msg = parse_encode_length(buf); + } else { + _extra_msg = 0; + } + set_parsed(); + return PARSE_OK; +} + +ParseError MysqlReply::Column::Parse(butil::IOBuf& buf, butil::Arena* arena) { + if (is_parsed()) { + return PARSE_OK; + } + MysqlHeader header; + if (!parse_header(buf, &header)) { + return PARSE_ERROR_NOT_ENOUGH_DATA; + } + + // Each length-encoded string must fit within the remaining buffer; an + // oversized length would otherwise drive my_alloc_check/cutn/.set past the + // packet (mirrors the hardened auth_plugin path above). + uint64_t len = parse_encode_length(buf); + if (len > buf.size()) { + LOG(WARNING) << "MysqlReply::Column::Parse: catalog length " << len + << " exceeds remaining buffer size " << buf.size(); + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + char* catalog = NULL; + MY_ALLOC_CHECK(my_alloc_check(arena, len, catalog)); + buf.cutn(catalog, len); + _catalog.set(catalog, len); + + len = parse_encode_length(buf); + if (len > buf.size()) { + LOG(WARNING) << "MysqlReply::Column::Parse: database length " << len + << " exceeds remaining buffer size " << buf.size(); + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + char* database = NULL; + MY_ALLOC_CHECK(my_alloc_check(arena, len, database)); + buf.cutn(database, len); + _database.set(database, len); + + len = parse_encode_length(buf); + if (len > buf.size()) { + LOG(WARNING) << "MysqlReply::Column::Parse: table length " << len + << " exceeds remaining buffer size " << buf.size(); + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + char* table = NULL; + MY_ALLOC_CHECK(my_alloc_check(arena, len, table)); + buf.cutn(table, len); + _table.set(table, len); + + len = parse_encode_length(buf); + if (len > buf.size()) { + LOG(WARNING) << "MysqlReply::Column::Parse: origin_table length " << len + << " exceeds remaining buffer size " << buf.size(); + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + char* origin_table = NULL; + MY_ALLOC_CHECK(my_alloc_check(arena, len, origin_table)); + buf.cutn(origin_table, len); + _origin_table.set(origin_table, len); + + len = parse_encode_length(buf); + if (len > buf.size()) { + LOG(WARNING) << "MysqlReply::Column::Parse: name length " << len + << " exceeds remaining buffer size " << buf.size(); + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + char* name = NULL; + MY_ALLOC_CHECK(my_alloc_check(arena, len, name)); + buf.cutn(name, len); + _name.set(name, len); + + len = parse_encode_length(buf); + if (len > buf.size()) { + LOG(WARNING) << "MysqlReply::Column::Parse: origin_name length " << len + << " exceeds remaining buffer size " << buf.size(); + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + char* origin_name = NULL; + MY_ALLOC_CHECK(my_alloc_check(arena, len, origin_name)); + buf.cutn(origin_name, len); + _origin_name.set(origin_name, len); + buf.pop_front(1); + { + uint8_t tmp[2]; + buf.cutn(tmp, sizeof(tmp)); + _charset = mysql_uint2korr(tmp); + } + { + uint8_t tmp[4]; + buf.cutn(tmp, sizeof(tmp)); + _length = mysql_uint4korr(tmp); + } + buf.cut1((char*)&_type); + { + uint8_t tmp[2]; + buf.cutn(tmp, sizeof(tmp)); + _flag = (MysqlFieldFlag)mysql_uint2korr(tmp); + } + buf.cut1((char*)&_decimal); + buf.pop_front(2); + set_parsed(); + return PARSE_OK; +} + +ParseError MysqlReply::Ok::Parse(butil::IOBuf& buf, butil::Arena* arena) { + if (is_parsed()) { + return PARSE_OK; + } + MysqlHeader header; + if (!parse_header(buf, &header)) { + return PARSE_ERROR_NOT_ENOUGH_DATA; + } + + uint64_t old_size, new_size; + old_size = buf.size(); + buf.pop_front(1); + + _affect_row = parse_encode_length(buf); + _index = parse_encode_length(buf); + { + uint8_t tmp[2]; + buf.cutn(tmp, sizeof(tmp)); + _status = mysql_uint2korr(tmp); + } + { + uint8_t tmp[2]; + buf.cutn(tmp, sizeof(tmp)); + _warning = mysql_uint2korr(tmp); + } + + new_size = buf.size(); + if (old_size - new_size < header.payload_size) { + const int64_t len = header.payload_size - (old_size - new_size); + char* msg = NULL; + MY_ALLOC_CHECK(my_alloc_check(arena, len, msg)); + buf.cutn(msg, len); + _msg.set(msg, len); + } + set_parsed(); + return PARSE_OK; +} + +ParseError MysqlReply::Eof::Parse(butil::IOBuf& buf) { + if (is_parsed()) { + return PARSE_OK; + } + MysqlHeader header; + if (!parse_header(buf, &header)) { + return PARSE_ERROR_NOT_ENOUGH_DATA; + } + buf.pop_front(1); + { + uint8_t tmp[2]; + buf.cutn(tmp, sizeof(tmp)); + _warning = mysql_uint2korr(tmp); + } + { + uint8_t tmp[2]; + buf.cutn(tmp, sizeof(tmp)); + _status = mysql_uint2korr(tmp); + } + set_parsed(); + return PARSE_OK; +} + +ParseError MysqlReply::Error::Parse(butil::IOBuf& buf, butil::Arena* arena) { + if (is_parsed()) { + return PARSE_OK; + } + MysqlHeader header; + if (!parse_header(buf, &header)) { + return PARSE_ERROR_NOT_ENOUGH_DATA; + } + buf.pop_front(1); // 0xFF + { + uint8_t tmp[2]; + buf.cutn(tmp, sizeof(tmp)); + _errcode = mysql_uint2korr(tmp); + } + buf.pop_front(1); // '#' + // 5 byte server status + char* status = NULL; + MY_ALLOC_CHECK(my_alloc_check(arena, 5, status)); + buf.cutn(status, 5); + _status.set(status, 5); + // error message, Null-Terminated string. + // payload layout consumed so far: 0xFF(1) + errcode(2) + '#'(1) + + // sql_state(5) = 9 bytes; guard against a malformed short packet to avoid + // an unsigned underflow producing a huge length. + if (header.payload_size < 9) { + LOG(WARNING) << "MysqlReply::Error::Parse: truncated ERR packet, payload_size " + << header.payload_size << " < 9 (0xFF+errcode+'#'+sql_state)"; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + uint64_t len = header.payload_size - 9; + char* msg = NULL; + MY_ALLOC_CHECK(my_alloc_check(arena, len, msg)); + buf.cutn(msg, len); + _msg.set(msg, len); + set_parsed(); + return PARSE_OK; +} + +ParseError MysqlReply::Row::Parse(butil::IOBuf& buf, + const MysqlReply::Column* columns, + uint64_t column_count, + MysqlReply::Field* fields, + bool binary, + butil::Arena* arena) { + if (is_parsed()) { + return PARSE_OK; + } + MysqlHeader header; + if (!parse_header(buf, &header)) { + return PARSE_ERROR_NOT_ENOUGH_DATA; + } + if (!binary) { // mysql text protocol + for (uint64_t i = 0; i < column_count; ++i) { + MY_PARSE_CHECK(fields[i].Parse(buf, columns + i, arena)); + } + } else { // mysql binary protocol + uint8_t hdr = 0; + buf.cut1((char*)&hdr); + if (hdr != 0x00) { + LOG(WARNING) << "MysqlReply::Row::Parse: binary row packet header byte is " + << unsigned(hdr) << ", expected 0x00"; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + // NULL-bitmap, [(column-count + 7 + 2) / 8 bytes]. Allocate from the + // arena instead of a stack VLA: column_count is attacker-controlled + // (length-encoded in the result-set header), so a large value would + // otherwise be an unbounded stack allocation / stack overflow. + const uint64_t size = ((column_count + 7 + 2) >> 3); + uint8_t* null_mask = NULL; + MY_ALLOC_CHECK(my_alloc_check(arena, (size_t)size, null_mask)); + for (uint64_t i = 0; i < size; ++i) { + null_mask[i] = 0; + } + buf.cutn(null_mask, size); + for (uint64_t i = 0; i < column_count; ++i) { + MY_PARSE_CHECK(fields[i].Parse(buf, columns + i, i, column_count, null_mask, arena)); + } + } + set_parsed(); + return PARSE_OK; +} + +ParseError MysqlReply::Field::Parse(butil::IOBuf& buf, + const MysqlReply::Column* column, + butil::Arena* arena) { + if (is_parsed()) { + return PARSE_OK; + } + // field type + _type = column->_type; + // is unsigned flag set + _unsigned = column->_flag & MYSQL_UNSIGNED_FLAG; + // parse encode length + const uint64_t len = parse_encode_length(buf); + // is it null? + if (len == 0 && !(column->_flag & MYSQL_NOT_NULL_FLAG)) { + _is_nil = true; + set_parsed(); + return PARSE_OK; + } + // field is not null + butil::IOBuf str; + buf.cutn(&str, len); + switch (_type) { + case MYSQL_FIELD_TYPE_NULL: + _is_nil = true; + break; + case MYSQL_FIELD_TYPE_TINY: + if (column->_flag & MYSQL_UNSIGNED_FLAG) { + _data.tiny = strtoul(str.to_string().c_str(), NULL, 10); + } else { + _data.stiny = strtol(str.to_string().c_str(), NULL, 10); + } + break; + case MYSQL_FIELD_TYPE_SHORT: + case MYSQL_FIELD_TYPE_YEAR: + if (column->_flag & MYSQL_UNSIGNED_FLAG) { + _data.small = strtoul(str.to_string().c_str(), NULL, 10); + } else { + _data.ssmall = strtol(str.to_string().c_str(), NULL, 10); + } + break; + case MYSQL_FIELD_TYPE_INT24: + case MYSQL_FIELD_TYPE_LONG: + if (column->_flag & MYSQL_UNSIGNED_FLAG) { + _data.integer = strtoul(str.to_string().c_str(), NULL, 10); + } else { + _data.sinteger = strtol(str.to_string().c_str(), NULL, 10); + } + break; + case MYSQL_FIELD_TYPE_LONGLONG: + if (column->_flag & MYSQL_UNSIGNED_FLAG) { + _data.bigint = strtoul(str.to_string().c_str(), NULL, 10); + } else { + _data.sbigint = strtol(str.to_string().c_str(), NULL, 10); + } + break; + case MYSQL_FIELD_TYPE_FLOAT: + _data.float32 = strtof(str.to_string().c_str(), NULL); + break; + case MYSQL_FIELD_TYPE_DOUBLE: + _data.float64 = strtod(str.to_string().c_str(), NULL); + break; + case MYSQL_FIELD_TYPE_DECIMAL: + case MYSQL_FIELD_TYPE_NEWDECIMAL: + case MYSQL_FIELD_TYPE_VARCHAR: + case MYSQL_FIELD_TYPE_BIT: + case MYSQL_FIELD_TYPE_ENUM: + case MYSQL_FIELD_TYPE_SET: + case MYSQL_FIELD_TYPE_TINY_BLOB: + case MYSQL_FIELD_TYPE_MEDIUM_BLOB: + case MYSQL_FIELD_TYPE_LONG_BLOB: + case MYSQL_FIELD_TYPE_BLOB: + case MYSQL_FIELD_TYPE_VAR_STRING: + case MYSQL_FIELD_TYPE_STRING: + case MYSQL_FIELD_TYPE_GEOMETRY: + case MYSQL_FIELD_TYPE_JSON: + case MYSQL_FIELD_TYPE_TIME: + case MYSQL_FIELD_TYPE_DATE: + case MYSQL_FIELD_TYPE_NEWDATE: + case MYSQL_FIELD_TYPE_TIMESTAMP: + case MYSQL_FIELD_TYPE_DATETIME: { + char* d = NULL; + MY_ALLOC_CHECK(my_alloc_check(arena, len, d)); + str.copy_to(d); + _data.str.set(d, len); + } break; + default: + LOG(ERROR) << "Unknown field type"; + set_parsed(); + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + set_parsed(); + return PARSE_OK; +} + +ParseError MysqlReply::Field::Parse(butil::IOBuf& buf, + const MysqlReply::Column* column, + uint64_t column_index, + uint64_t column_count, + const uint8_t* null_mask, + butil::Arena* arena) { + if (is_parsed()) { + return PARSE_OK; + } + // field type + _type = column->_type; + // is unsigned flag set + _unsigned = column->_flag & MYSQL_UNSIGNED_FLAG; + // (byte >> bit-pos) % 2 == 1 + if (((null_mask[(column_index + 2) >> 3] >> ((column_index + 2) & 7)) & 1) == 1) { + _is_nil = true; + set_parsed(); + return PARSE_OK; + } + + switch (_type) { + case MYSQL_FIELD_TYPE_NULL: + _is_nil = true; + break; + case MYSQL_FIELD_TYPE_TINY: + if (column->_flag & MYSQL_UNSIGNED_FLAG) { + buf.cut1((char*)&_data.tiny); + } else { + buf.cut1((char*)&_data.stiny); + } + break; + case MYSQL_FIELD_TYPE_SHORT: + case MYSQL_FIELD_TYPE_YEAR: + if (column->_flag & MYSQL_UNSIGNED_FLAG) { + uint8_t* p = (uint8_t*)&_data.small; + buf.cutn(p, 2); + _data.small = mysql_uint2korr(p); + } else { + uint8_t* p = (uint8_t*)&_data.ssmall; + buf.cutn(p, 2); + _data.ssmall = (int16_t)mysql_uint2korr(p); + } + break; + case MYSQL_FIELD_TYPE_INT24: + case MYSQL_FIELD_TYPE_LONG: + if (column->_flag & MYSQL_UNSIGNED_FLAG) { + uint8_t* p = (uint8_t*)&_data.integer; + buf.cutn(p, 4); + _data.integer = mysql_uint4korr(p); + } else { + uint8_t* p = (uint8_t*)&_data.sinteger; + buf.cutn(p, 4); + _data.sinteger = (int32_t)mysql_uint4korr(p); + } + break; + case MYSQL_FIELD_TYPE_LONGLONG: + if (column->_flag & MYSQL_UNSIGNED_FLAG) { + uint8_t* p = (uint8_t*)&_data.bigint; + buf.cutn(p, 8); + _data.bigint = mysql_uint8korr(p); + } else { + uint8_t* p = (uint8_t*)&_data.sbigint; + buf.cutn(p, 8); + _data.sbigint = (int64_t)mysql_uint8korr(p); + } + break; + case MYSQL_FIELD_TYPE_FLOAT: { + uint8_t* p = (uint8_t*)&_data.float32; + buf.cutn(p, 4); + } break; + case MYSQL_FIELD_TYPE_DOUBLE: { + uint8_t* p = (uint8_t*)&_data.float64; + buf.cutn(p, 8); + } break; + case MYSQL_FIELD_TYPE_DECIMAL: + case MYSQL_FIELD_TYPE_NEWDECIMAL: + case MYSQL_FIELD_TYPE_VARCHAR: + case MYSQL_FIELD_TYPE_BIT: + case MYSQL_FIELD_TYPE_ENUM: + case MYSQL_FIELD_TYPE_SET: + case MYSQL_FIELD_TYPE_TINY_BLOB: + case MYSQL_FIELD_TYPE_MEDIUM_BLOB: + case MYSQL_FIELD_TYPE_LONG_BLOB: + case MYSQL_FIELD_TYPE_BLOB: + case MYSQL_FIELD_TYPE_VAR_STRING: + case MYSQL_FIELD_TYPE_STRING: + case MYSQL_FIELD_TYPE_GEOMETRY: + case MYSQL_FIELD_TYPE_JSON: { + const uint64_t len = parse_encode_length(buf); + // is it null? + if (len == 0 && !(column->_flag & MYSQL_NOT_NULL_FLAG)) { + _is_nil = true; + set_parsed(); + return PARSE_OK; + } + // field is not null + if (len > buf.size()) { + LOG(WARNING) << "MysqlReply::Field::Parse (binary): string field length " << len + << " exceeds remaining buffer size " << buf.size(); + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + char* d = NULL; + MY_ALLOC_CHECK(my_alloc_check(arena, len, d)); + buf.cutn(d, len); + _data.str.set(d, len); + } break; + case MYSQL_FIELD_TYPE_NEWDATE: // Date YYYY-MM-DD + case MYSQL_FIELD_TYPE_DATE: // Date YYYY-MM-DD + case MYSQL_FIELD_TYPE_DATETIME: // Timestamp YYYY-MM-DD HH:MM:SS[.fractal] + case MYSQL_FIELD_TYPE_TIMESTAMP: { // Timestamp YYYY-MM-DD HH:MM:SS[.fractal] + ParseError rc = ParseBinaryDataTime(buf, column, _data.str, arena); + if (rc != PARSE_OK) { + return rc; + } + } break; + case MYSQL_FIELD_TYPE_TIME: { // Time [-][H]HH:MM:SS[.fractal] + ParseError rc = ParseBinaryTime(buf, column, _data.str, arena); + if (rc != PARSE_OK) { + return rc; + } + } break; + default: + LOG(ERROR) << "Unknown field type"; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + set_parsed(); + return PARSE_OK; +} + +ParseError MysqlReply::Field::ParseBinaryTime(butil::IOBuf& buf, + const MysqlReply::Column* column, + butil::StringPiece& str, + butil::Arena* arena) { + + const uint64_t len = parse_encode_length(buf); + // A length of 0, 8 or 12 are the only legal binary TIME encodings. Anything + // else is a malformed packet -- reject it rather than reading past the value. + // NOTE: len == 0 is NOT a NULL value (NULL is signalled by the row + // NULL-bitmap, handled by the caller before we are reached); it is the zero + // TIME value "00:00:00" with no field bytes on the wire. + if (len != 0 && len != 8 && len != 12) { + LOG(ERROR) << "invalid TIME packet length " << len; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + // Never read more value bytes than the packet actually carries. + if (len > buf.size()) { + LOG(ERROR) << "TIME value length " << len << " exceeds buffer size " << buf.size(); + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + + // Base "HH:MM:SS" is 8 bytes, but MySQL binary TIME spans up to 838 hours + // and may be negative, so reserve 2 extra bytes for a leading sign and a + // possible 3rd hour digit ("-838:59:59[.ffffff]"). + uint8_t dstlen; + switch (column->_decimal) { + case 0x00: + case 0x1f: + dstlen = 8 + 2; + break; + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + dstlen = 8 + 2 + 1 + column->_decimal; + break; + default: + LOG(ERROR) << "protocol error, illegal decimals value " << column->_decimal; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + + size_t i = 0; + char* d = NULL; + MY_ALLOC_CHECK(my_alloc_check(arena, dstlen + 2, d)); + d[dstlen] = '\0'; + d[dstlen + 1] = '\0'; + // Read only the fields that are present for this `len`; absent fields are 0. + // len == 0 -> no bytes: "00:00:00". + // len == 8 -> is_negative(1) days(4 LE) hour(1) min(1) sec(1), no micros. + // len == 12 -> + micros(4 LE). + uint32_t day = 0; + uint8_t neg = 0, hour = 0, min = 0, sec = 0; + + if (len >= 8) { + buf.cut1((char*)&neg); + buf.cutn(&day, 4); + day = mysql_uint4korr((uint8_t*)&day); + buf.cut1((char*)&hour); + buf.cut1((char*)&min); + buf.cut1((char*)&sec); + } + + // Validate field ranges so the formatted output cannot overflow the buffer + // and so we never index past digits01/digits10. MySQL caps TIME at 838 + // hours and 59 min/sec; total_hour is at most 3 digits, which dstlen sizes + // for. A larger total_hour would emit >3 hour digits and overrun `d`. + if (neg > 1 || min > 59 || sec > 59) { + LOG(ERROR) << "invalid TIME field value"; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + // MySQL binary TIME spans up to 838 hours, so the total can exceed 255 and + // must be accumulated in a wider type than the 1-byte wire field. + uint32_t total_hour = (uint32_t)hour + day * 24; + if (total_hour > 838) { + LOG(ERROR) << "TIME total hours " << total_hour << " exceeds MySQL max 838"; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + + if (neg == 1) { + d[i++] = '-'; + } + if (total_hour >= 100) { + // total_hour is in [100, 838]: exactly 3 digits, which dstlen reserves + // space for. Emit hundreds/tens/units directly; the digits01/digits10 + // lookup tables only cover 0..99 so they cannot be indexed by the full + // value here. + d[i++] = (char)('0' + total_hour / 100); + const uint32_t rem = total_hour % 100; + d[i++] = digits10[rem]; + d[i++] = digits01[rem]; + } else { + d[i++] = digits10[total_hour]; + d[i++] = digits01[total_hour]; + } + + d[i++] = ':'; + d[i++] = digits10[min]; + d[i++] = digits01[min]; + d[i++] = ':'; + d[i++] = digits10[sec]; + d[i++] = digits01[sec]; + + // Microseconds are only present on the wire when len == 12; for len == 0 or + // len == 8 there are no microsecond bytes even if the column declares + // decimals. + ParseError rc; + if (len == 12) { + rc = ParseMicrosecs(buf, column->_decimal, d + i); + } else { + write_zero_microsecs(column->_decimal, d + i); + rc = PARSE_OK; + } + if (rc == PARSE_OK) { + // TIME is variable-width (optional sign, 2- or 3+-digit hour), so report + // the EXACT bytes actually written: i (through ":SS") plus the + // fractional part -- '.' + decimal digits when decimal is 1..6, else + // nothing (decimal 0 or 0x1f writes no fractional bytes). + const size_t micros_len = + (column->_decimal >= 1 && column->_decimal <= 6) ? (size_t)column->_decimal + 1 : 0; + str.set(d, i + micros_len); + } + return rc; +} + +ParseError MysqlReply::Field::ParseBinaryDataTime(butil::IOBuf& buf, + const MysqlReply::Column* column, + butil::StringPiece& str, + butil::Arena* arena) { + const uint64_t len = parse_encode_length(buf); + // A length of 0, 4, 7 or 11 are the only legal binary DATE/DATETIME/ + // TIMESTAMP encodings. Reject anything else rather than over-reading. + // NOTE: len == 0 is NOT a NULL value (NULL is signalled by the row + // NULL-bitmap, handled by the caller before we are reached); it is the zero + // value "0000-00-00 00:00:00" (or "0000-00-00" for DATE) with no field + // bytes on the wire. + if (len != 0 && len != 4 && len != 7 && len != 11) { + LOG(ERROR) << "illegal date time length " << len; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + // Never read more value bytes than the packet actually carries. + if (len > buf.size()) { + LOG(ERROR) << "DATETIME value length " << len << " exceeds buffer size " << buf.size(); + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + // A DATE column carries only the date part; a time-of-day part on the wire + // would not fit its 10-byte output buffer, so reject those packets. + const bool is_date = (column->_type == MYSQL_FIELD_TYPE_DATE || + column->_type == MYSQL_FIELD_TYPE_NEWDATE); + if (is_date && len != 0 && len != 4) { + LOG(ERROR) << "illegal DATE length " << len; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + + uint8_t dstlen; + if (is_date) { + dstlen = 10; + } else { + switch (column->_decimal) { + case 0x00: + case 0x1f: + dstlen = 19; + break; + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + dstlen = 19 + 1 + column->_decimal; + break; + default: + LOG(ERROR) << "protocol error, illegal decimal value " << column->_decimal; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + } + + size_t i = 0; + char* d = NULL; + MY_ALLOC_CHECK(my_alloc_check(arena, dstlen, d)); + // Read only the fields present for this `len`; absent fields are 0. + // len == 0 -> no bytes (all-zero value). + // len == 4 -> year(2 LE) month(1) day(1) only -> "YYYY-MM-DD". + // len == 7 -> + hour(1) min(1) sec(1) -> "YYYY-MM-DD HH:MM:SS". + // len == 11 -> + micros(4 LE). + uint16_t year = 0; + uint8_t month = 0, day = 0, hour = 0, min = 0, sec = 0; + if (len >= 4) { + buf.cutn(&year, 2); + year = mysql_uint2korr((uint8_t*)&year); + buf.cut1((char*)&month); + buf.cut1((char*)&day); + } + if (len >= 7) { + buf.cut1((char*)&hour); + buf.cut1((char*)&min); + buf.cut1((char*)&sec); + } + + // Validate field ranges: year < 10000 keeps the 4-digit year within bounds + // and keeps every two-digit component inside the digits01/digits10 tables + // (which only cover 0..99), preventing both buffer overrun and OOB reads. + if (year > 9999 || month > 99 || day > 99 || hour > 99 || min > 59 || sec > 59) { + LOG(ERROR) << "invalid DATE/DATETIME field value"; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + + const uint8_t pt = year / 100; + const uint8_t p1 = year - (100 * pt); + d[i++] = digits10[pt]; + d[i++] = digits01[pt]; + d[i++] = digits10[p1]; + d[i++] = digits01[p1]; + d[i++] = '-'; + d[i++] = digits10[month]; + d[i++] = digits01[month]; + d[i++] = '-'; + d[i++] = digits10[day]; + d[i++] = digits01[day]; + + if (is_date) { + // DATE column: only "YYYY-MM-DD" (10 bytes) is meaningful. + str.set(d, i); + return PARSE_OK; + } + + // DATETIME/TIMESTAMP column: always emit the full "YYYY-MM-DD HH:MM:SS" + // form. When len == 4 the time-of-day fields were absent on the wire and + // default to zero ("00:00:00"); we still write those bytes here so the + // reported length matches what was actually written. + d[i++] = ' '; + d[i++] = digits10[hour]; + d[i++] = digits01[hour]; + d[i++] = ':'; + d[i++] = digits10[min]; + d[i++] = digits01[min]; + d[i++] = ':'; + d[i++] = digits10[sec]; + d[i++] = digits01[sec]; + + // Microseconds are only present on the wire when len == 11; for len == 7 + // there are no microsecond bytes even if the column declares decimals. + ParseError rc; + if (len == 11) { + rc = ParseMicrosecs(buf, column->_decimal, d + i); + } else { + write_zero_microsecs(column->_decimal, d + i); + rc = PARSE_OK; + } + if (rc == PARSE_OK) { + // Report the EXACT bytes written: "YYYY-MM-DD HH:MM:SS" (i == 19) plus + // the fractional part -- '.' + decimal digits when decimal is 1..6, else + // nothing. + const size_t micros_len = + (column->_decimal >= 1 && column->_decimal <= 6) ? (size_t)column->_decimal + 1 : 0; + str.set(d, i + micros_len); + } + return rc; +} + +ParseError MysqlReply::Field::ParseMicrosecs(butil::IOBuf& buf, uint8_t decimal, char* d) { + size_t i = 0; + uint32_t microsecs; + uint8_t p1, p2, p3; + // Always consume the 4 microsecond bytes present on the wire (the caller + // only invokes this when the value length includes them); format them only + // when the column declares 1..6 fractional digits (0 / 0x1f == no fraction). + buf.cutn((char*)µsecs, 4); + if (decimal == 0 || decimal > 6) { + return PARSE_OK; + } + microsecs = mysql_uint4korr((uint8_t*)µsecs); + p1 = microsecs / 10000; + microsecs -= 10000 * p1; + p2 = microsecs / 100; + microsecs -= 100 * p2; + p3 = microsecs; + + switch (decimal) { + case 1: + d[i++] = '.'; + d[i++] = digits10[p1]; + break; + case 2: + d[i++] = '.'; + d[i++] = digits10[p1]; + d[i++] = digits01[p1]; + break; + case 3: + d[i++] = '.'; + d[i++] = digits10[p1]; + d[i++] = digits01[p1]; + d[i++] = digits10[p2]; + break; + case 4: + d[i++] = '.'; + d[i++] = digits10[p1]; + d[i++] = digits01[p1]; + d[i++] = digits10[p2]; + d[i++] = digits01[p2]; + break; + case 5: + d[i++] = '.'; + d[i++] = digits10[p1]; + d[i++] = digits01[p1]; + d[i++] = digits10[p2]; + d[i++] = digits01[p2]; + d[i++] = digits10[p3]; + break; + default: + d[i++] = '.'; + d[i++] = digits10[p1]; + d[i++] = digits01[p1]; + d[i++] = digits10[p2]; + d[i++] = digits01[p2]; + d[i++] = digits10[p3]; + d[i++] = digits01[p3]; + } + return PARSE_OK; +} + +ParseError MysqlReply::ResultSet::Parse(butil::IOBuf& buf, butil::Arena* arena, bool binary) { + if (is_parsed()) { + return PARSE_OK; + } + // parse header + MY_PARSE_CHECK(_header.Parse(buf)); + // parse colunms + MY_ALLOC_CHECK(my_alloc_check(arena, _header._column_count, _columns)); + for (uint64_t i = 0; i < _header._column_count; ++i) { + MY_PARSE_CHECK(_columns[i].Parse(buf, arena)); + } + // parse eof1 + MY_PARSE_CHECK(_eof1.Parse(buf)); + // parse row + std::vector rows; + for (;;) { + // if not full package reread + if (!is_full_package(buf)) { + return PARSE_ERROR_NOT_ENOUGH_DATA; + } + // if eof break loops for row + if (is_an_eof(buf)) { + break; + } + // allocate memory for row and fields + Row* row = NULL; + Field* fields = NULL; + MY_ALLOC_CHECK(my_alloc_check(arena, 1, row)); + MY_ALLOC_CHECK(my_alloc_check(arena, _header._column_count, fields)); + row->_fields = fields; + row->_field_count = _header._column_count; + _last->_next = row; + _last = row; + // parse row and fields + MY_PARSE_CHECK(row->Parse(buf, _columns, _header._column_count, fields, binary, arena)); + // add row count + ++_row_count; + } + // parse eof2 + MY_PARSE_CHECK(_eof2.Parse(buf)); + set_parsed(); + return PARSE_OK; +} + +ParseError MysqlReply::PrepareOk::Parse(butil::IOBuf& buf, butil::Arena* arena) { + if (is_parsed()) { + return PARSE_OK; + } + + MY_PARSE_CHECK(_header.Parse(buf)); + + if (_header._param_count > 0) { + MY_ALLOC_CHECK(my_alloc_check(arena, _header._param_count, _params)); + for (uint16_t i = 0; i < _header._param_count; ++i) { + MY_PARSE_CHECK(_params[i].Parse(buf, arena)); + } + MY_PARSE_CHECK(_eof1.Parse(buf)); + } + + if (_header._column_count > 0) { + MY_ALLOC_CHECK(my_alloc_check(arena, _header._column_count, _columns)); + for (uint16_t i = 0; i < _header._column_count; ++i) { + MY_PARSE_CHECK(_columns[i].Parse(buf, arena)); + } + MY_PARSE_CHECK(_eof2.Parse(buf)); + } + set_parsed(); + return PARSE_OK; +} + +ParseError MysqlReply::PrepareOk::Header::Parse(butil::IOBuf& buf) { + if (is_parsed()) { + return PARSE_OK; + } + + MysqlHeader header; + if (!parse_header(buf, &header)) { + return PARSE_ERROR_NOT_ENOUGH_DATA; + } + + buf.pop_front(1); + { + uint8_t tmp[4]; + buf.cutn(tmp, sizeof(tmp)); + _stmt_id = mysql_uint4korr(tmp); + } + { + uint8_t tmp[2]; + buf.cutn(tmp, sizeof(tmp)); + _column_count = mysql_uint2korr(tmp); + } + { + uint8_t tmp[2]; + buf.cutn(tmp, sizeof(tmp)); + _param_count = mysql_uint2korr(tmp); + } + buf.pop_front(1); + { + uint8_t tmp[2]; + buf.cutn(tmp, sizeof(tmp)); + _warning = mysql_uint2korr(tmp); + } + + set_parsed(); + return PARSE_OK; +} + +} // namespace brpc diff --git a/src/brpc/policy/mysql/mysql_reply.h b/src/brpc/policy/mysql/mysql_reply.h new file mode 100644 index 0000000..2cb9052 --- /dev/null +++ b/src/brpc/policy/mysql/mysql_reply.h @@ -0,0 +1,850 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Authors: Yang,Liming (yangliming01@baidu.com) + +#ifndef BRPC_MYSQL_REPLY_H +#define BRPC_MYSQL_REPLY_H + +#include "butil/iobuf.h" // butil::IOBuf +#include "butil/arena.h" +#include "butil/sys_byteorder.h" +#include "butil/logging.h" // LOG() +#include "brpc/parse_result.h" +#include "brpc/policy/mysql/mysql_common.h" + +namespace brpc { + +class CheckParsed { +public: + CheckParsed() : _is_parsed(false) {} + bool is_parsed() const { + return _is_parsed; + } + void set_parsed() { + _is_parsed = true; + } + +private: + bool _is_parsed; +}; + +enum MysqlRspType : uint8_t { + MYSQL_RSP_OK = 0x00, + MYSQL_RSP_ERROR = 0xFF, + MYSQL_RSP_RESULTSET = 0x01, + MYSQL_RSP_EOF = 0xFE, + MYSQL_RSP_AUTH = 0xFB, // add for mysql auth + MYSQL_RSP_PREPARE_OK = 0xFC, // add for prepared statement + MYSQL_RSP_UNKNOWN = 0xFD, // add for other case + MYSQL_RSP_AUTH_MORE_DATA = 0xFA, // add for caching_sha2_password auth +}; + +const char* MysqlRspTypeToString(MysqlRspType); + +class MysqlReply { +public: + // Mysql Auth package + class Auth : private CheckParsed { + public: + Auth(); + uint8_t protocol() const; + butil::StringPiece version() const; + uint32_t thread_id() const; + butil::StringPiece salt() const; + uint16_t capability() const; + uint8_t collation() const; + uint16_t status() const; + uint16_t extended_capability() const; + uint8_t auth_plugin_length() const; + butil::StringPiece salt2() const; + butil::StringPiece auth_plugin() const; + + private: + ParseError Parse(butil::IOBuf& buf, butil::Arena* arena); + + DISALLOW_COPY_AND_ASSIGN(Auth); + friend class MysqlReply; + + uint8_t _protocol; + butil::StringPiece _version; + uint32_t _thread_id; + butil::StringPiece _salt; + uint16_t _capability; + uint8_t _collation; + uint16_t _status; + uint16_t _extended_capability; + uint8_t _auth_plugin_length; + butil::StringPiece _salt2; + butil::StringPiece _auth_plugin; + }; + // Mysql AuthMoreData package (0x01) sent during caching_sha2_password + // authentication. Exposes the raw bytes that follow the 0x01 tag, e.g. + // a single status byte (0x03 fast-auth-success / 0x04 full-auth-required) + // or the server's PEM-encoded RSA public key. + class AuthMoreData : private CheckParsed { + public: + AuthMoreData(); + // Bytes after the 0x01 tag (status byte or PEM public key). + butil::StringPiece data() const; + // Sequence id of this packet's header. The client's follow-up + // packet must carry seq+1 (MySQL packet sequence rule). + uint8_t seq() const; + + private: + ParseError Parse(butil::IOBuf& buf, butil::Arena* arena); + + DISALLOW_COPY_AND_ASSIGN(AuthMoreData); + friend class MysqlReply; + + butil::StringPiece _data; + uint8_t _seq; + }; + // Mysql Prepared Statement Ok + class Column; + // Mysql Eof package + class Eof : private CheckParsed { + public: + Eof(); + uint16_t warning() const; + uint16_t status() const; + + private: + ParseError Parse(butil::IOBuf& buf); + + DISALLOW_COPY_AND_ASSIGN(Eof); + friend class MysqlReply; + + uint16_t _warning; + uint16_t _status; + }; + // Mysql PrepareOk package + class PrepareOk : private CheckParsed { + public: + PrepareOk(); + uint32_t stmt_id() const; + uint16_t column_count() const; + uint16_t param_count() const; + uint16_t warning() const; + const Column& param(uint16_t index) const; + const Column& column(uint16_t index) const; + + private: + ParseError Parse(butil::IOBuf& buf, butil::Arena* arena); + + DISALLOW_COPY_AND_ASSIGN(PrepareOk); + friend class MysqlReply; + + class Header : private CheckParsed { + public: + Header() : _stmt_id(0), _column_count(0), _param_count(0), _warning(0) {} + uint32_t _stmt_id; + uint16_t _column_count; + uint16_t _param_count; + uint16_t _warning; + ParseError Parse(butil::IOBuf& buf); + }; + Header _header; + Column* _params; + Eof _eof1; + Column* _columns; + Eof _eof2; + }; + // Mysql Ok package + class Ok : private CheckParsed { + public: + Ok(); + uint64_t affect_row() const; + uint64_t index() const; + uint16_t status() const; + uint16_t warning() const; + butil::StringPiece msg() const; + + private: + ParseError Parse(butil::IOBuf& buf, butil::Arena* arena); + + DISALLOW_COPY_AND_ASSIGN(Ok); + friend class MysqlReply; + + uint64_t _affect_row; + uint64_t _index; + uint16_t _status; + uint16_t _warning; + butil::StringPiece _msg; + }; + // Mysql Error package + class Error : private CheckParsed { + public: + Error(); + uint16_t errcode() const; + butil::StringPiece status() const; + butil::StringPiece msg() const; + + private: + ParseError Parse(butil::IOBuf& buf, butil::Arena* arena); + + DISALLOW_COPY_AND_ASSIGN(Error); + friend class MysqlReply; + + uint16_t _errcode; + butil::StringPiece _status; + butil::StringPiece _msg; + }; + // Mysql Column + class Column : private CheckParsed { + public: + Column(); + butil::StringPiece catalog() const; + butil::StringPiece database() const; + butil::StringPiece table() const; + butil::StringPiece origin_table() const; + butil::StringPiece name() const; + butil::StringPiece origin_name() const; + uint16_t charset() const; + uint32_t length() const; + MysqlFieldType type() const; + MysqlFieldFlag flag() const; + uint8_t decimal() const; + + private: + ParseError Parse(butil::IOBuf& buf, butil::Arena* arena); + + DISALLOW_COPY_AND_ASSIGN(Column); + friend class MysqlReply; + + butil::StringPiece _catalog; + butil::StringPiece _database; + butil::StringPiece _table; + butil::StringPiece _origin_table; + butil::StringPiece _name; + butil::StringPiece _origin_name; + uint16_t _charset; + uint32_t _length; + MysqlFieldType _type; + MysqlFieldFlag _flag; + uint8_t _decimal; + }; + // Mysql Field + class Field : private CheckParsed { + public: + Field(); + int8_t stiny() const; + uint8_t tiny() const; + int16_t ssmall() const; + uint16_t small() const; + int32_t sinteger() const; + uint32_t integer() const; + int64_t sbigint() const; + uint64_t bigint() const; + float float32() const; + double float64() const; + butil::StringPiece string() const; + bool is_stiny() const; + bool is_tiny() const; + bool is_ssmall() const; + bool is_small() const; + bool is_sinteger() const; + bool is_integer() const; + bool is_sbigint() const; + bool is_bigint() const; + bool is_float32() const; + bool is_float64() const; + bool is_string() const; + bool is_nil() const; + + private: + ParseError Parse(butil::IOBuf& buf, const MysqlReply::Column* column, butil::Arena* arena); + ParseError Parse(butil::IOBuf& buf, + const MysqlReply::Column* column, + uint64_t column_index, + uint64_t column_number, + const uint8_t* null_mask, + butil::Arena* arena); + ParseError ParseBinaryTime(butil::IOBuf& buf, + const MysqlReply::Column* column, + butil::StringPiece& str, + butil::Arena* arena); + ParseError ParseBinaryDataTime(butil::IOBuf& buf, + const MysqlReply::Column* column, + butil::StringPiece& str, + butil::Arena* arena); + ParseError ParseMicrosecs(butil::IOBuf& buf, uint8_t decimal, char* d); + DISALLOW_COPY_AND_ASSIGN(Field); + friend class MysqlReply; + + union { + int8_t stiny; + uint8_t tiny; + int16_t ssmall; + uint16_t small; + int32_t sinteger; + uint32_t integer; + int64_t sbigint; + uint64_t bigint; + float float32; + double float64; + butil::StringPiece str; + } _data = {.str = NULL}; + MysqlFieldType _type; + bool _unsigned; + bool _is_nil; + }; + // Mysql Row + class Row : private CheckParsed { + public: + Row(); + uint64_t field_count() const; + const Field& field(const uint64_t index) const; + + private: + ParseError Parse(butil::IOBuf& buf, + const Column* columns, + uint64_t column_number, + Field* fields, + bool binary, + butil::Arena* arena); + + DISALLOW_COPY_AND_ASSIGN(Row); + friend class MysqlReply; + + Field* _fields; + uint64_t _field_count; + Row* _next; + }; + +public: + MysqlReply(); + ParseError ConsumePartialIOBuf(butil::IOBuf& buf, + butil::Arena* arena, + bool is_auth, + MysqlStmtType stmt_type, + bool* more_results); + void Swap(MysqlReply& other); + void Print(std::ostream& os) const; + // response type + MysqlRspType type() const; + // get auth + const Auth& auth() const; + // get auth-more-data (caching_sha2_password) + const AuthMoreData& auth_more_data() const; + const Ok& ok() const; + const PrepareOk& prepare_ok() const; + const Error& error() const; + const Eof& eof() const; + // get column number + uint64_t column_count() const; + // get one column + const Column& column(const uint64_t index) const; + // get row number + uint64_t row_count() const; + // get one row + const Row& next() const; + bool is_auth() const; + bool is_auth_more_data() const; + bool is_ok() const; + bool is_prepare_ok() const; + bool is_error() const; + bool is_eof() const; + bool is_resultset() const; + +private: + // Mysql result set header + struct ResultSetHeader : private CheckParsed { + ResultSetHeader() : _column_count(0), _extra_msg(0) {} + ParseError Parse(butil::IOBuf& buf); + uint64_t _column_count; + uint64_t _extra_msg; + + private: + DISALLOW_COPY_AND_ASSIGN(ResultSetHeader); + }; + // Mysql result set + struct ResultSet : private CheckParsed { + ResultSet() : _columns(NULL), _row_count(0) { + _cur = _first = _last = &_dummy; + } + ParseError Parse(butil::IOBuf& buf, butil::Arena* arena, bool binary); + ResultSetHeader _header; + Column* _columns; + Eof _eof1; + // row list begin + Row* _first; + Row* _last; + Row* _cur; + uint64_t _row_count; + // row list end + Eof _eof2; + + private: + DISALLOW_COPY_AND_ASSIGN(ResultSet); + Row _dummy; + }; + // member values + MysqlRspType _type; + union { + Auth* auth; + AuthMoreData* auth_more_data; + ResultSet* result_set; + Ok* ok; + PrepareOk* prepare_ok; + Error* error; + Eof* eof; + uint64_t padding; // For swapping, must cover all bytes. + } _data; + + DISALLOW_COPY_AND_ASSIGN(MysqlReply); +}; + +// mysql reply +inline MysqlReply::MysqlReply() { + _type = MYSQL_RSP_UNKNOWN; + _data.padding = 0; +} +inline void MysqlReply::Swap(MysqlReply& other) { + std::swap(_type, other._type); + std::swap(_data.padding, other._data.padding); +} +inline std::ostream& operator<<(std::ostream& os, const MysqlReply& r) { + r.Print(os); + return os; +} +inline MysqlRspType MysqlReply::type() const { + return _type; +} +inline const MysqlReply::Auth& MysqlReply::auth() const { + if (is_auth()) { + return *_data.auth; + } + CHECK(false) << "The reply is " << MysqlRspTypeToString(_type) << ", not an auth"; + static Auth auth_nil; + return auth_nil; +} +inline const MysqlReply::AuthMoreData& MysqlReply::auth_more_data() const { + if (is_auth_more_data()) { + return *_data.auth_more_data; + } + CHECK(false) << "The reply is " << MysqlRspTypeToString(_type) << ", not an auth_more_data"; + static AuthMoreData auth_more_data_nil; + return auth_more_data_nil; +} +inline const MysqlReply::PrepareOk& MysqlReply::prepare_ok() const { + if (is_prepare_ok()) { + return *_data.prepare_ok; + } + CHECK(false) << "The reply is " << MysqlRspTypeToString(_type) << ", not an ok"; + static PrepareOk prepare_ok_nil; + return prepare_ok_nil; +} +inline const MysqlReply::Ok& MysqlReply::ok() const { + if (is_ok()) { + return *_data.ok; + } + CHECK(false) << "The reply is " << MysqlRspTypeToString(_type) << ", not an ok"; + static Ok ok_nil; + return ok_nil; +} +inline const MysqlReply::Error& MysqlReply::error() const { + if (is_error()) { + return *_data.error; + } + CHECK(false) << "The reply is " << MysqlRspTypeToString(_type) << ", not an error"; + static Error error_nil; + return error_nil; +} +inline const MysqlReply::Eof& MysqlReply::eof() const { + if (is_eof()) { + return *_data.eof; + } + CHECK(false) << "The reply is " << MysqlRspTypeToString(_type) << ", not an eof"; + static Eof eof_nil; + return eof_nil; +} +inline uint64_t MysqlReply::column_count() const { + if (is_resultset()) { + return _data.result_set->_header._column_count; + } + CHECK(false) << "The reply is " << MysqlRspTypeToString(_type) << ", not an resultset"; + return 0; +} +inline const MysqlReply::Column& MysqlReply::column(const uint64_t index) const { + static Column column_nil; + if (is_resultset()) { + if (index < _data.result_set->_header._column_count) { + return _data.result_set->_columns[index]; + } + CHECK(false) << "index " << index << " out of bound [0," + << _data.result_set->_header._column_count << ")"; + return column_nil; + } + CHECK(false) << "The reply is " << MysqlRspTypeToString(_type) << ", not an resultset"; + return column_nil; +} +inline uint64_t MysqlReply::row_count() const { + if (is_resultset()) { + return _data.result_set->_row_count; + } + CHECK(false) << "The reply is " << MysqlRspTypeToString(_type) << ", not an resultset"; + return 0; +} +inline const MysqlReply::Row& MysqlReply::next() const { + static Row row_nil; + if (is_resultset()) { + if (_data.result_set->_row_count == 0) { + CHECK(false) << "there are 0 rows returned"; + return row_nil; + } + if (_data.result_set->_cur == _data.result_set->_last->_next) { + _data.result_set->_cur = _data.result_set->_first->_next; + } else { + _data.result_set->_cur = _data.result_set->_cur->_next; + } + return *_data.result_set->_cur; + } + CHECK(false) << "The reply is " << MysqlRspTypeToString(_type) << ", not an resultset"; + return row_nil; +} +inline bool MysqlReply::is_auth() const { + return _type == MYSQL_RSP_AUTH; +} +inline bool MysqlReply::is_auth_more_data() const { + return _type == MYSQL_RSP_AUTH_MORE_DATA; +} +inline bool MysqlReply::is_prepare_ok() const { + return _type == MYSQL_RSP_PREPARE_OK; +} +inline bool MysqlReply::is_ok() const { + return _type == MYSQL_RSP_OK; +} +inline bool MysqlReply::is_error() const { + return _type == MYSQL_RSP_ERROR; +} +inline bool MysqlReply::is_eof() const { + return _type == MYSQL_RSP_EOF; +} +inline bool MysqlReply::is_resultset() const { + return _type == MYSQL_RSP_RESULTSET; +} +// mysql auth +inline MysqlReply::Auth::Auth() + : _protocol(0), + _thread_id(0), + _capability(0), + _collation(0), + _status(0), + _extended_capability(0), + _auth_plugin_length(0) {} +inline uint8_t MysqlReply::Auth::protocol() const { + return _protocol; +} +inline butil::StringPiece MysqlReply::Auth::version() const { + return _version; +} +inline uint32_t MysqlReply::Auth::thread_id() const { + return _thread_id; +} +inline butil::StringPiece MysqlReply::Auth::salt() const { + return _salt; +} +inline uint16_t MysqlReply::Auth::capability() const { + return _capability; +} +inline uint8_t MysqlReply::Auth::collation() const { + return _collation; +} +inline uint16_t MysqlReply::Auth::status() const { + return _status; +} +inline uint16_t MysqlReply::Auth::extended_capability() const { + return _extended_capability; +} +inline uint8_t MysqlReply::Auth::auth_plugin_length() const { + return _auth_plugin_length; +} +inline butil::StringPiece MysqlReply::Auth::salt2() const { + return _salt2; +} +inline butil::StringPiece MysqlReply::Auth::auth_plugin() const { + return _auth_plugin; +} +// mysql auth-more-data +inline MysqlReply::AuthMoreData::AuthMoreData() : _seq(0) {} +inline butil::StringPiece MysqlReply::AuthMoreData::data() const { + return _data; +} +inline uint8_t MysqlReply::AuthMoreData::seq() const { + return _seq; +} +// mysql prepared statement ok +inline MysqlReply::PrepareOk::PrepareOk() : _params(NULL), _columns(NULL) {} +inline uint32_t MysqlReply::PrepareOk::stmt_id() const { + CHECK(_header._stmt_id > 0) << "stmt id is wrong"; + return _header._stmt_id; +} +inline uint16_t MysqlReply::PrepareOk::column_count() const { + return _header._column_count; +} +inline uint16_t MysqlReply::PrepareOk::param_count() const { + return _header._param_count; +} +inline uint16_t MysqlReply::PrepareOk::warning() const { + return _header._warning; +} +inline const MysqlReply::Column& MysqlReply::PrepareOk::param(uint16_t index) const { + if (index < _header._param_count) { + return _params[index]; + } + static Column column_nil; + CHECK(false) << "index " << index << " out of bound [0," << _header._param_count << ")"; + return column_nil; +} +inline const MysqlReply::Column& MysqlReply::PrepareOk::column(uint16_t index) const { + if (index < _header._column_count) { + return _columns[index]; + } + CHECK(false) << "index " << index << " out of bound [0," << _header._column_count << ")"; + static Column column_nil; + return column_nil; +} +// mysql reply ok +inline MysqlReply::Ok::Ok() : _affect_row(0), _index(0), _status(0), _warning(0) {} +inline uint64_t MysqlReply::Ok::affect_row() const { + return _affect_row; +} +inline uint64_t MysqlReply::Ok::index() const { + return _index; +} +inline uint16_t MysqlReply::Ok::status() const { + return _status; +} +inline uint16_t MysqlReply::Ok::warning() const { + return _warning; +} +inline butil::StringPiece MysqlReply::Ok::msg() const { + return _msg; +} +// mysql reply error +inline MysqlReply::Error::Error() : _errcode(0) {} +inline uint16_t MysqlReply::Error::errcode() const { + return _errcode; +} +inline butil::StringPiece MysqlReply::Error::status() const { + return _status; +} +inline butil::StringPiece MysqlReply::Error::msg() const { + return _msg; +} +// mysql reply eof +inline MysqlReply::Eof::Eof() : _warning(0), _status(0) {} +inline uint16_t MysqlReply::Eof::warning() const { + return _warning; +} +inline uint16_t MysqlReply::Eof::status() const { + return _status; +} +// mysql reply column +inline MysqlReply::Column::Column() : _length(0), _type(MYSQL_FIELD_TYPE_NULL), _decimal(0) {} +inline butil::StringPiece MysqlReply::Column::catalog() const { + return _catalog; +} +inline butil::StringPiece MysqlReply::Column::database() const { + return _database; +} +inline butil::StringPiece MysqlReply::Column::table() const { + return _table; +} +inline butil::StringPiece MysqlReply::Column::origin_table() const { + return _origin_table; +} +inline butil::StringPiece MysqlReply::Column::name() const { + return _name; +} +inline butil::StringPiece MysqlReply::Column::origin_name() const { + return _origin_name; +} +inline uint16_t MysqlReply::Column::charset() const { + return _charset; +} +inline uint32_t MysqlReply::Column::length() const { + return _length; +} +inline MysqlFieldType MysqlReply::Column::type() const { + return _type; +} +inline MysqlFieldFlag MysqlReply::Column::flag() const { + return _flag; +} +inline uint8_t MysqlReply::Column::decimal() const { + return _decimal; +} +// mysql reply row +inline MysqlReply::Row::Row() : _fields(NULL), _field_count(0), _next(NULL) {} +inline uint64_t MysqlReply::Row::field_count() const { + return _field_count; +} +inline const MysqlReply::Field& MysqlReply::Row::field(const uint64_t index) const { + if (index < _field_count) { + return _fields[index]; + } + CHECK(false) << "index " << index << " out of bound [0," << _field_count << ")"; + static Field field_nil; + return field_nil; +} +// mysql reply field +inline MysqlReply::Field::Field() + : _type(MYSQL_FIELD_TYPE_NULL), _unsigned(false), _is_nil(false) {} +inline int8_t MysqlReply::Field::stiny() const { + if (is_stiny()) { + return _data.stiny; + } + CHECK(false) << "The reply is " << MysqlFieldTypeToString(_type) << " and " + << (_is_nil ? "NULL" : "NOT NULL") << ", not an stiny"; + return 0; +} +inline uint8_t MysqlReply::Field::tiny() const { + if (is_tiny()) { + return _data.tiny; + } + CHECK(false) << "The reply is " << MysqlFieldTypeToString(_type) << " and " + << (_is_nil ? "NULL" : "NOT NULL") << ", not an tiny"; + return 0; +} +inline int16_t MysqlReply::Field::ssmall() const { + if (is_ssmall()) { + return _data.ssmall; + } + CHECK(false) << "The reply is " << MysqlFieldTypeToString(_type) << " and " + << (_is_nil ? "NULL" : "NOT NULL") << ", not an ssmall"; + return 0; +} +inline uint16_t MysqlReply::Field::small() const { + if (is_small()) { + return _data.small; + } + CHECK(false) << "The reply is " << MysqlFieldTypeToString(_type) << " and " + << (_is_nil ? "NULL" : "NOT NULL") << ", not an small"; + return 0; +} +inline int32_t MysqlReply::Field::sinteger() const { + if (is_sinteger()) { + return _data.sinteger; + } + CHECK(false) << "The reply is " << MysqlFieldTypeToString(_type) << " and " + << (_is_nil ? "NULL" : "NOT NULL") << ", not an sinteger"; + return 0; +} +inline uint32_t MysqlReply::Field::integer() const { + if (is_integer()) { + return _data.integer; + } + CHECK(false) << "The reply is " << MysqlFieldTypeToString(_type) << " and " + << (_is_nil ? "NULL" : "NOT NULL") << ", not an integer"; + return 0; +} +inline int64_t MysqlReply::Field::sbigint() const { + if (is_sbigint()) { + return _data.sbigint; + } + CHECK(false) << "The reply is " << MysqlFieldTypeToString(_type) << " and " + << (_is_nil ? "NULL" : "NOT NULL") << ", not an sbigint"; + return 0; +} +inline uint64_t MysqlReply::Field::bigint() const { + if (is_bigint()) { + return _data.bigint; + } + CHECK(false) << "The reply is " << MysqlFieldTypeToString(_type) << " and " + << (_is_nil ? "NULL" : "NOT NULL") << ", not an bigint"; + return 0; +} +inline float MysqlReply::Field::float32() const { + if (is_float32()) { + return _data.float32; + } + CHECK(false) << "The reply is " << MysqlFieldTypeToString(_type) << " and " + << (_is_nil ? "NULL" : "NOT NULL") << ", not an float32"; + return 0; +} +inline double MysqlReply::Field::float64() const { + if (is_float64()) { + return _data.float64; + } + CHECK(false) << "The reply is " << MysqlFieldTypeToString(_type) << " and " + << (_is_nil ? "NULL" : "NOT NULL") << ", not an float64"; + return 0; +} +inline butil::StringPiece MysqlReply::Field::string() const { + if (is_string()) { + return _data.str; + } + CHECK(false) << "The reply is " << MysqlFieldTypeToString(_type) << " and " + << (_is_nil ? "NULL" : "NOT NULL") << ", not an string"; + return butil::StringPiece(); +} +inline bool MysqlReply::Field::is_stiny() const { + return _type == MYSQL_FIELD_TYPE_TINY && !_unsigned && !_is_nil; +} +inline bool MysqlReply::Field::is_tiny() const { + return _type == MYSQL_FIELD_TYPE_TINY && _unsigned && !_is_nil; +} +inline bool MysqlReply::Field::is_ssmall() const { + return (_type == MYSQL_FIELD_TYPE_SHORT || _type == MYSQL_FIELD_TYPE_YEAR) && !_unsigned && + !_is_nil; +} +inline bool MysqlReply::Field::is_small() const { + return (_type == MYSQL_FIELD_TYPE_SHORT || _type == MYSQL_FIELD_TYPE_YEAR) && _unsigned && + !_is_nil; +} +inline bool MysqlReply::Field::is_sinteger() const { + return (_type == MYSQL_FIELD_TYPE_INT24 || _type == MYSQL_FIELD_TYPE_LONG) && !_unsigned && + !_is_nil; +} +inline bool MysqlReply::Field::is_integer() const { + return (_type == MYSQL_FIELD_TYPE_INT24 || _type == MYSQL_FIELD_TYPE_LONG) && _unsigned && + !_is_nil; +} +inline bool MysqlReply::Field::is_sbigint() const { + return _type == MYSQL_FIELD_TYPE_LONGLONG && !_unsigned && !_is_nil; +} +inline bool MysqlReply::Field::is_bigint() const { + return _type == MYSQL_FIELD_TYPE_LONGLONG && _unsigned && !_is_nil; +} +inline bool MysqlReply::Field::is_float32() const { + return _type == MYSQL_FIELD_TYPE_FLOAT && !_is_nil; +} +inline bool MysqlReply::Field::is_float64() const { + return _type == MYSQL_FIELD_TYPE_DOUBLE && !_is_nil; +} +inline bool MysqlReply::Field::is_string() const { + return (_type == MYSQL_FIELD_TYPE_DECIMAL || _type == MYSQL_FIELD_TYPE_NEWDECIMAL || + _type == MYSQL_FIELD_TYPE_VARCHAR || _type == MYSQL_FIELD_TYPE_BIT || + _type == MYSQL_FIELD_TYPE_ENUM || _type == MYSQL_FIELD_TYPE_SET || + _type == MYSQL_FIELD_TYPE_TINY_BLOB || _type == MYSQL_FIELD_TYPE_MEDIUM_BLOB || + _type == MYSQL_FIELD_TYPE_LONG_BLOB || _type == MYSQL_FIELD_TYPE_BLOB || + _type == MYSQL_FIELD_TYPE_VAR_STRING || _type == MYSQL_FIELD_TYPE_STRING || + _type == MYSQL_FIELD_TYPE_GEOMETRY || _type == MYSQL_FIELD_TYPE_JSON || + _type == MYSQL_FIELD_TYPE_TIME || _type == MYSQL_FIELD_TYPE_DATE || + _type == MYSQL_FIELD_TYPE_NEWDATE || _type == MYSQL_FIELD_TYPE_TIMESTAMP || + _type == MYSQL_FIELD_TYPE_DATETIME) && + !_is_nil; +} +inline bool MysqlReply::Field::is_nil() const { + return _is_nil; +} + +} // namespace brpc + +#endif // BRPC_MYSQL_REPLY_H diff --git a/src/brpc/policy/mysql/mysql_statement.cpp b/src/brpc/policy/mysql/mysql_statement.cpp new file mode 100644 index 0000000..5f41088 --- /dev/null +++ b/src/brpc/policy/mysql/mysql_statement.cpp @@ -0,0 +1,163 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Authors: Yang,Liming (yangliming01@baidu.com) + +#include +#include +#include "butil/logging.h" +#include "brpc/socket.h" +#include "brpc/policy/mysql/mysql_statement.h" + +namespace brpc { +DEFINE_int32(mysql_statement_map_size, + 100, + "Mysql statement map size, usually equal to max bthread number"); + +MysqlStatementUniquePtr NewMysqlStatement(const Channel& channel, const butil::StringPiece& str) { + MysqlStatementUniquePtr ptr(new MysqlStatement(channel, str)); + return ptr; +} + +uint32_t MysqlStatement::StatementId(SocketId socket_id) const { + if (_connection_type == CONNECTION_TYPE_SHORT) { + return 0; + } + { + MysqlStatementDBD::ScopedPtr ptr; + if (_id_map.Read(&ptr) != 0) { + LOG(WARNING) << "MysqlStatement::StatementId: failed to read the " + "statement-id map (DoublyBufferedData::Read failed) " + "for socket_id=" << socket_id << ", returning stmt_id=0"; + return 0; + } + const MysqlStatementId* p = ptr->seek(socket_id); + if (p == NULL) { + LOG(WARNING) << "MysqlStatement::StatementId: no prepared statement id " + "cached for socket_id=" << socket_id + << " (statement not found / not prepared on this " + "connection), returning stmt_id=0"; + return 0; + } + SocketUniquePtr socket; + if (Socket::Address(socket_id, &socket) == 0) { + uint64_t fd_version = socket->fd_version(); + if (fd_version == p->version) { + return p->stmt_id; + } + } + } + // The socket was closed/recycled (version mismatch or address failed): + // the cached stmt_id is stale and the server has dropped the prepared + // statement. Erase the entry so it doesn't accumulate for the process + // lifetime; a fresh prepare will re-insert via SetStatementId. + // + // NOTE: the read ScopedPtr above is released (closing scope) BEFORE this + // Modify(), since DoublyBufferedData::Modify() blocks until all live + // Read() references are gone -- holding `ptr` here would deadlock. + _id_map.Modify(my_delete_k, socket_id); + LOG(WARNING) << "MysqlStatement::StatementId: cached statement id for " + "socket_id=" << socket_id << " is stale (socket closed/recycled " + "-- fd_version mismatch or Socket::Address failed); erased the " + "entry and returning stmt_id=0 to force a re-prepare"; + return 0; +} + +void MysqlStatement::SetStatementId(SocketId socket_id, uint32_t stmt_id) { + if (_connection_type == CONNECTION_TYPE_SHORT) { + return; + } + SocketUniquePtr socket; + if (Socket::Address(socket_id, &socket) == 0) { + uint64_t fd_version = socket->fd_version(); + MysqlStatementId value{stmt_id, fd_version}; + _id_map.Modify(my_update_kv, socket_id, value); + } +} + +namespace { +// Count only top-level placeholder '?' in a SQL statement, skipping any '?' +// that appears inside a single-quoted / double-quoted / backtick-quoted +// literal, or inside a -- , # , or /* */ comment. This mirrors how a SQL +// lexer treats quoting so a valid statement containing a literal '?' +// (e.g. WHERE name = '?') is not miscounted and wrongly rejected on prepare. +uint16_t CountPlaceholders(const std::string& s) { + uint16_t count = 0; + const size_t n = s.size(); + for (size_t i = 0; i < n; ++i) { + const char c = s[i]; + if (c == '\'' || c == '"' || c == '`') { + // Skip the quoted span. Handles backslash escapes and the SQL + // doubled-quote escape ('' inside '...'). + const char quote = c; + ++i; + while (i < n) { + const char d = s[i]; + if (d == '\\' && quote != '`') { + ++i; // skip escaped char + } else if (d == quote) { + if (i + 1 < n && s[i + 1] == quote) { + ++i; // doubled quote -> literal quote, stay in string + } else { + break; // closing quote + } + } + ++i; + } + } else if (c == '-' && i + 1 < n && s[i + 1] == '-') { + // line comment until end of line + i += 2; + while (i < n && s[i] != '\n') { + ++i; + } + } else if (c == '#') { + // line comment until end of line + ++i; + while (i < n && s[i] != '\n') { + ++i; + } + } else if (c == '/' && i + 1 < n && s[i + 1] == '*') { + // block comment until */ + i += 2; + while (i + 1 < n && !(s[i] == '*' && s[i + 1] == '/')) { + ++i; + } + ++i; // land on '/' (loop ++i moves past it) + } else if (c == '?') { + ++count; + } + } + return count; +} +} // namespace + +void MysqlStatement::Init(const Channel& channel) { + _param_count = CountPlaceholders(_str); + ChannelOptions opts = channel.options(); + _connection_type = ConnectionType(opts.connection_type); + if (_connection_type != CONNECTION_TYPE_SHORT) { + _id_map.Modify(my_init_kv); + } else { + LOG_EVERY_SECOND(WARNING) + << "Prepared statement on a 'short' connection re-prepares on every " + "execute (a new TCP connection per request cannot cache the " + "server stmt_id); use connection_type='pooled' for prepared " + "statements."; + } +} + +} // namespace brpc diff --git a/src/brpc/policy/mysql/mysql_statement.h b/src/brpc/policy/mysql/mysql_statement.h new file mode 100644 index 0000000..8e924a6 --- /dev/null +++ b/src/brpc/policy/mysql/mysql_statement.h @@ -0,0 +1,69 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Authors: Yang,Liming (yangliming01@baidu.com) + +#ifndef BRPC_MYSQL_STATEMENT_H +#define BRPC_MYSQL_STATEMENT_H +#include +#include +#include "brpc/channel.h" +#include "brpc/policy/mysql/mysql_statement_inl.h" + +namespace brpc { +// mysql prepared statement Unique Ptr +class MysqlStatement; +typedef std::unique_ptr MysqlStatementUniquePtr; +// mysql prepared statement +class MysqlStatement { +public: + const butil::StringPiece str() const; + uint16_t param_count() const; + uint32_t StatementId(SocketId sock_id) const; + void SetStatementId(SocketId sock_id, uint32_t stmt_id); + +private: + MysqlStatement(const Channel& channel, const butil::StringPiece& str); + void Init(const Channel& channel); + DISALLOW_COPY_AND_ASSIGN(MysqlStatement); + + friend MysqlStatementUniquePtr NewMysqlStatement(const Channel& channel, + const butil::StringPiece& str); + + const std::string _str; // prepare statement string + uint16_t _param_count; + mutable MysqlStatementDBD _id_map; // SocketId and statement id + ConnectionType _connection_type; +}; + +inline MysqlStatement::MysqlStatement(const Channel& channel, const butil::StringPiece& str) + : _str(str.data(), str.size()), _param_count(0) { + Init(channel); +} + +inline const butil::StringPiece MysqlStatement::str() const { + return butil::StringPiece(_str); +} + +inline uint16_t MysqlStatement::param_count() const { + return _param_count; +} + +MysqlStatementUniquePtr NewMysqlStatement(const Channel& channel, const butil::StringPiece& str); + +} // namespace brpc +#endif diff --git a/src/brpc/policy/mysql/mysql_statement_inl.h b/src/brpc/policy/mysql/mysql_statement_inl.h new file mode 100644 index 0000000..3e1323c --- /dev/null +++ b/src/brpc/policy/mysql/mysql_statement_inl.h @@ -0,0 +1,61 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Authors: Yang,Liming (yangliming01@baidu.com) + +#ifndef BRPC_MYSQL_STATEMENT_INL_H +#define BRPC_MYSQL_STATEMENT_INL_H +#include +#include "butil/containers/flat_map.h" // FlatMap +#include "butil/containers/doubly_buffered_data.h" +#include "brpc/socket_id.h" + +namespace brpc { +DECLARE_int32(mysql_statement_map_size); + +struct MysqlStatementId { + uint32_t stmt_id; // statement id + uint64_t version; // socket's fd version +}; + +typedef butil::FlatMap MysqlStatementKVMap; +typedef butil::DoublyBufferedData MysqlStatementDBD; + +inline size_t my_init_kv(MysqlStatementKVMap& m) { + if (FLAGS_mysql_statement_map_size < 100) { + FLAGS_mysql_statement_map_size = 100; + } + m.init(FLAGS_mysql_statement_map_size); + return 1; +} + +inline size_t my_update_kv(MysqlStatementKVMap& m, SocketId key, MysqlStatementId value) { + MysqlStatementId* p = m.seek(key); + if (p == NULL) { + m.insert(key, value); + } else { + *p = value; + } + return 1; +} + +inline size_t my_delete_k(MysqlStatementKVMap& m, SocketId key) { + return m.erase(key); +} + +} // namespace brpc +#endif diff --git a/src/brpc/policy/mysql/mysql_transaction.cpp b/src/brpc/policy/mysql/mysql_transaction.cpp new file mode 100644 index 0000000..58871dd --- /dev/null +++ b/src/brpc/policy/mysql/mysql_transaction.cpp @@ -0,0 +1,125 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Authors: Yang,Liming (yangliming01@baidu.com) + +#include +#include "butil/logging.h" // LOG() +#include "brpc/policy/mysql/mysql_transaction.h" +#include "brpc/policy/mysql/mysql.h" +#include "brpc/socket.h" +#include "brpc/details/controller_private_accessor.h" + +namespace brpc { +// mysql transaction isolation level string +const char* mysql_isolation_level[] = { + "REPEATABLE READ", "READ COMMITTED", "READ UNCOMMITTED", "SERIALIZABLE"}; + +SocketId MysqlTransaction::GetSocketId() const { + return _socket->id(); +} + +bool MysqlTransaction::DoneTransaction(const char* command) { + bool rc = false; + MysqlRequest request(this); + if (_socket == NULL) { // must already commit or rollback, return true. + return true; + } else if (!request.Query(command)) { + LOG(ERROR) << "Fail to query command" << command; + } else { + MysqlResponse response; + Controller cntl; + _channel.CallMethod(NULL, &cntl, &request, &response, NULL); + if (!cntl.Failed()) { + if (response.reply(0).is_ok()) { + rc = true; + } else { + LOG(ERROR) << "Fail " << command << " transaction, " << response; + } + } else { + LOG(ERROR) << "Fail " << command << " transaction, " << cntl.ErrorText(); + } + } + if (rc && _connection_type == CONNECTION_TYPE_POOLED) { + _socket->ReturnToPool(); + } + _socket.reset(); + return rc; +} + +MysqlTransactionUniquePtr NewMysqlTransaction(Channel& channel, + const MysqlTransactionOptions& opts) { + const char* command[2] = {"START TRANSACTION READ ONLY", "START TRANSACTION"}; + + if (channel.options().connection_type == CONNECTION_TYPE_SINGLE) { + LOG(ERROR) << "mysql transaction can't use connection type 'single'"; + return NULL; + } + std::stringstream ss; + // repeatable read is mysql default isolation level, so ignore it. + if (opts.isolation_level != MysqlIsoRepeatableRead) { + ss << "SET TRANSACTION ISOLATION LEVEL " << mysql_isolation_level[opts.isolation_level] + << ";"; + } + + if (opts.readonly) { + ss << command[0]; + } else { + ss << command[1]; + } + + MysqlRequest request; + if (!request.Query(ss.str())) { + LOG(ERROR) << "Fail to query command" << ss.str(); + return NULL; + } + + MysqlTransactionUniquePtr tx; + MysqlResponse response; + Controller cntl; + ControllerPrivateAccessor(&cntl).set_bind_sock_action(BIND_SOCK_RESERVE); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + if (!cntl.Failed()) { + // repeatable read isolation send one reply, other isolation has two reply + if ((opts.isolation_level == MysqlIsoRepeatableRead && response.reply(0).is_ok()) || + (response.reply(0).is_ok() && response.reply(1).is_ok())) { + SocketUniquePtr socket; + ControllerPrivateAccessor(&cntl).get_bind_sock(&socket); + if (socket == NULL) { + LOG(ERROR) << "Fail create mysql transaction, get bind socket failed"; + } else { + tx.reset(new MysqlTransaction(channel, socket, cntl.connection_type())); + } + } else { + // The RPC itself succeeded so a socket was reserved on the + // controller; the transaction did not start though, so return the + // reserved pooled socket instead of letting ~Controller drop its + // ref (which would leak the pooled connection). + SocketUniquePtr socket; + ControllerPrivateAccessor(&cntl).get_bind_sock(&socket); + if (socket != NULL && cntl.connection_type() == CONNECTION_TYPE_POOLED) { + socket->ReturnToPool(); + } + LOG(ERROR) << "Fail create mysql transaction, " << response; + } + } else { + LOG(ERROR) << "Fail create mysql transaction, " << cntl.ErrorText(); + } + return tx; +} + +} // namespace brpc diff --git a/src/brpc/policy/mysql/mysql_transaction.h b/src/brpc/policy/mysql/mysql_transaction.h new file mode 100644 index 0000000..6472529 --- /dev/null +++ b/src/brpc/policy/mysql/mysql_transaction.h @@ -0,0 +1,92 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Authors: Yang,Liming (yangliming01@baidu.com) + +#ifndef BRPC_MYSQL_TRANSACTION_H +#define BRPC_MYSQL_TRANSACTION_H + +#include "brpc/socket_id.h" +#include "brpc/channel.h" + +namespace brpc { +// mysql isolation level enum +enum MysqlIsolationLevel { + MysqlIsoRepeatableRead = 0, + MysqlIsoReadCommitted = 1, + MysqlIsoReadUnCommitted = 2, + MysqlIsoSerializable = 3, +}; +// mysql transaction options +struct MysqlTransactionOptions { + // if is readonly transaction + MysqlTransactionOptions() : readonly(false), isolation_level(MysqlIsoRepeatableRead) {} + bool readonly; + MysqlIsolationLevel isolation_level; +}; +// MysqlTransaction Unique Ptr +class MysqlTransaction; +typedef std::unique_ptr MysqlTransactionUniquePtr; +// mysql transaction type +class MysqlTransaction { +public: + ~MysqlTransaction(); + SocketId GetSocketId() const; + // commit transaction + bool commit(); + // rollback transaction + bool rollback(); + +private: + MysqlTransaction(Channel& channel, SocketUniquePtr& socket, ConnectionType connection_type); + bool DoneTransaction(const char* command); + DISALLOW_COPY_AND_ASSIGN(MysqlTransaction); + + friend MysqlTransactionUniquePtr NewMysqlTransaction(Channel& channel, + const MysqlTransactionOptions& opts); + +private: + Channel& _channel; + SocketUniquePtr _socket; + ConnectionType _connection_type; +}; + +inline MysqlTransaction::MysqlTransaction(Channel& channel, + SocketUniquePtr& socket, + ConnectionType connection_type) + : _channel(channel), _connection_type(connection_type) { + _socket.reset(socket.release()); +} + +inline MysqlTransaction::~MysqlTransaction() { + CHECK(rollback()) << "rollback failed"; +} + +inline bool MysqlTransaction::commit() { + return DoneTransaction("COMMIT"); +} + +inline bool MysqlTransaction::rollback() { + return DoneTransaction("ROLLBACK"); +} + +MysqlTransactionUniquePtr NewMysqlTransaction( + Channel& channel, const MysqlTransactionOptions& opts = MysqlTransactionOptions()); + +} // namespace brpc + +#endif diff --git a/src/brpc/policy/nacos_naming_service.cpp b/src/brpc/policy/nacos_naming_service.cpp new file mode 100644 index 0000000..c4cc46b --- /dev/null +++ b/src/brpc/policy/nacos_naming_service.cpp @@ -0,0 +1,289 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "nacos_naming_service.h" + +#include + +#include + +#include "brpc/http_status_code.h" +#include "brpc/log.h" +#include "butil/iobuf.h" +#include "butil/logging.h" +#include "butil/third_party/rapidjson/document.h" + +namespace brpc { +namespace policy { + +DEFINE_string(nacos_address, "", + "The query string of request nacos for discovering service."); +DEFINE_string(nacos_service_discovery_path, "/nacos/v1/ns/instance/list", + "The url path for discovering service."); +DEFINE_string(nacos_service_auth_path, "/nacos/v1/auth/login", + "The url path for authentiction."); +DEFINE_int32(nacos_connect_timeout_ms, 200, + "Timeout for creating connections to nacos in milliseconds"); +DEFINE_string(nacos_username, "", "nacos username"); +DEFINE_string(nacos_password, "", "nacos password"); +DEFINE_string(nacos_load_balancer, "rr", "nacos load balancer name"); + +int NacosNamingService::Connect() { + ChannelOptions opt; + opt.protocol = PROTOCOL_HTTP; + opt.connect_timeout_ms = FLAGS_nacos_connect_timeout_ms; + const int ret = _channel.Init(FLAGS_nacos_address.c_str(), + FLAGS_nacos_load_balancer.c_str(), &opt); + if (ret != 0) { + LOG(ERROR) << "Fail to init channel to nacos at " + << FLAGS_nacos_address; + } + return ret; +} + +int NacosNamingService::RefreshAccessToken(const char *service_name) { + Controller cntl; + cntl.http_request().uri() = FLAGS_nacos_service_auth_path; + cntl.http_request().set_method(brpc::HttpMethod::HTTP_METHOD_POST); + cntl.http_request().set_content_type("application/x-www-form-urlencoded"); + + auto &buf = cntl.request_attachment(); + buf.append("username="); + buf.append(FLAGS_nacos_username); + buf.append("&password="); + buf.append(FLAGS_nacos_password); + + _channel.CallMethod(nullptr, &cntl, nullptr, nullptr, nullptr); + if (cntl.Failed()) { + LOG(ERROR) << "Fail to access " << FLAGS_nacos_service_auth_path << ": " + << cntl.ErrorText(); + return -1; + } + + BUTIL_RAPIDJSON_NAMESPACE::Document doc; + if (doc.Parse(cntl.response_attachment().to_string().c_str()) + .HasParseError()) { + LOG(ERROR) << "Failed to parse nacos auth response"; + return -1; + } + if (!doc.IsObject()) { + LOG(ERROR) << "The nacos's auth response for " << service_name + << " is not a json object"; + return -1; + } + + auto iter = doc.FindMember("accessToken"); + if (iter != doc.MemberEnd() && iter->value.IsString()) { + _access_token = iter->value.GetString(); + } else { + LOG(ERROR) << "The nacos's auth response for " << service_name + << " has no accessToken field"; + return -1; + } + + auto iter_ttl = doc.FindMember("tokenTtl"); + if (iter_ttl != doc.MemberEnd() && iter_ttl->value.IsInt()) { + _token_expire_time = time(NULL) + iter_ttl->value.GetInt() - 10; + } else { + _token_expire_time = 0; + } + + return 0; +} + +int NacosNamingService::GetServerNodes(const char *service_name, + bool token_changed, + std::vector *nodes) { + if (_nacos_url.empty() || token_changed) { + _nacos_url = FLAGS_nacos_service_discovery_path; + _nacos_url += "?"; + if (!_access_token.empty()) { + _nacos_url += "accessToken=" + _access_token; + _nacos_url += "&"; + } + _nacos_url += service_name; + } + + Controller cntl; + cntl.http_request().uri() = _nacos_url; + _channel.CallMethod(nullptr, &cntl, nullptr, nullptr, nullptr); + if (cntl.Failed()) { + LOG(ERROR) << "Fail to access " << _nacos_url << ": " + << cntl.ErrorText(); + return -1; + } + if (cntl.http_response().status_code() != HTTP_STATUS_OK) { + LOG(ERROR) << "Failed to request nacos, http status code: " + << cntl.http_response().status_code(); + return -1; + } + + BUTIL_RAPIDJSON_NAMESPACE::Document doc; + if (doc.Parse(cntl.response_attachment().to_string().c_str()) + .HasParseError()) { + LOG(ERROR) << "Failed to parse nacos response"; + return -1; + } + if (!doc.IsObject()) { + LOG(ERROR) << "The nacos's response for " << service_name + << " is not a json object"; + return -1; + } + + auto it_hosts = doc.FindMember("hosts"); + if (it_hosts == doc.MemberEnd()) { + LOG(ERROR) << "The nacos's response for " << service_name + << " has no hosts member"; + return -1; + } + auto &hosts = it_hosts->value; + if (!hosts.IsArray()) { + LOG(ERROR) << "hosts member in nacos response is not an array"; + return -1; + } + + std::set presence; + for (auto it = hosts.Begin(); it != hosts.End(); ++it) { + auto &host = *it; + if (!host.IsObject()) { + LOG(ERROR) << "host member in nacos response is not an object"; + continue; + } + + auto it_ip = host.FindMember("ip"); + if (it_ip == host.MemberEnd() || !it_ip->value.IsString()) { + LOG(ERROR) << "host in nacos response has not ip"; + continue; + } + auto &ip = it_ip->value; + + auto it_port = host.FindMember("port"); + if (it_port == host.MemberEnd() || !it_port->value.IsInt()) { + LOG(ERROR) << "host in nacos response has not port"; + continue; + } + auto &port = it_port->value; + + auto it_enabled = host.FindMember("enabled"); + if (it_enabled == host.MemberEnd() || !(it_enabled->value.IsBool()) || + !(it_enabled->value.GetBool())) { + LOG(INFO) << "nacos " << ip.GetString() << ":" << port.GetInt() + << " is not enabled"; + continue; + } + + auto it_healthy = host.FindMember("healthy"); + if (it_healthy == host.MemberEnd() || !(it_healthy->value.IsBool()) || + !(it_healthy->value.GetBool())) { + LOG(INFO) << "nacos " << ip.GetString() << ":" << port.GetInt() + << " is not healthy"; + continue; + } + + butil::EndPoint end_point; + if (str2endpoint(ip.GetString(), port.GetUint(), &end_point) != 0) { + LOG(ERROR) << "ncos service with illegal address or port: " + << ip.GetString() << ":" << port.GetUint(); + continue; + } + + ServerNode node(end_point); + auto it_weight = host.FindMember("weight"); + if (it_weight != host.MemberEnd() && it_weight->value.IsNumber()) { + node.tag = + std::to_string(static_cast(it_weight->value.GetDouble())); + } + + presence.insert(node); + } + + nodes->reserve(presence.size()); + nodes->assign(presence.begin(), presence.end()); + + if (nodes->empty() && hosts.Size() != 0) { + LOG(ERROR) << "All service about " << service_name + << " from nacos is invalid, refuse to update servers"; + return -1; + } + + RPC_VLOG << "Got " << nodes->size() + << (nodes->size() > 1 ? " servers" : " server") << " from " + << service_name; + + auto it_cache = doc.FindMember("cacheMillis"); + if (it_cache != doc.MemberEnd() && it_cache->value.IsInt64()) { + _cache_ms = it_cache->value.GetInt64(); + } + + return 0; +} + +NacosNamingService::NacosNamingService() + : _nacos_connected(false), _cache_ms(-1), _token_expire_time(0) {} + +int NacosNamingService::GetNamingServiceAccessIntervalMs() const { + if (0 < _cache_ms) { + return _cache_ms; + } + return PeriodicNamingService::GetNamingServiceAccessIntervalMs(); +} + +int NacosNamingService::GetServers(const char *service_name, + std::vector *servers) { + if (!_nacos_connected) { + const int ret = Connect(); + if (0 == ret) { + _nacos_connected = true; + } else { + return ret; + } + } + + const bool authentiction_enabled = + !FLAGS_nacos_username.empty() && !FLAGS_nacos_password.empty(); + const bool has_invalid_access_token = + _access_token.empty() || + (0 < _token_expire_time && _token_expire_time <= time(NULL)); + bool token_changed = false; + + if (authentiction_enabled && has_invalid_access_token) { + const int ret = RefreshAccessToken(service_name); + if (ret == 0) { + token_changed = true; + } else { + return ret; + } + } + + servers->clear(); + return GetServerNodes(service_name, token_changed, servers); +} + +void NacosNamingService::Describe(std::ostream &os, + const DescribeOptions &) const { + os << "nacos"; + return; +} + +NamingService *NacosNamingService::New() const { + return new NacosNamingService; +} + +void NacosNamingService::Destroy() { delete this; } + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/nacos_naming_service.h b/src/brpc/policy/nacos_naming_service.h new file mode 100644 index 0000000..dcd7713 --- /dev/null +++ b/src/brpc/policy/nacos_naming_service.h @@ -0,0 +1,67 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_POLICY_NACOS_NAMING_SERVICE_H +#define BRPC_POLICY_NACOS_NAMING_SERVICE_H + +#include + +#include +#include + +#include "brpc/channel.h" +#include "brpc/periodic_naming_service.h" +#include "brpc/server_node.h" + +namespace brpc { +namespace policy { + +// Acquire server list from nacos +class NacosNamingService : public PeriodicNamingService { +public: + NacosNamingService(); + + int GetServers(const char* service_name, + std::vector* servers) override; + + int GetNamingServiceAccessIntervalMs() const override; + + void Describe(std::ostream& os, const DescribeOptions&) const override; + + NamingService* New() const override; + + void Destroy() override; + +private: + int Connect(); + int RefreshAccessToken(const char* service_name); + int GetServerNodes(const char* service_name, bool token_changed, + std::vector* nodes); + +private: + brpc::Channel _channel; + std::string _nacos_url; + std::string _access_token; + bool _nacos_connected; + long _cache_ms; + time_t _token_expire_time; +}; + +} // namespace policy +} // namespace brpc + +#endif // BRPC_POLICY_NACOS_NAMING_SERVICE_H diff --git a/src/brpc/policy/nova_pbrpc_protocol.cpp b/src/brpc/policy/nova_pbrpc_protocol.cpp new file mode 100644 index 0000000..a1d88f2 --- /dev/null +++ b/src/brpc/policy/nova_pbrpc_protocol.cpp @@ -0,0 +1,202 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // MethodDescriptor +#include // Message +#include + +#include "butil/time.h" +#include "butil/iobuf.h" // butil::IOBuf + +#include "brpc/controller.h" // Controller +#include "brpc/socket.h" // Socket +#include "brpc/server.h" // Server +#include "brpc/details/server_private_accessor.h" +#include "brpc/span.h" +#include "brpc/errno.pb.h" // EREQUEST, ERESPONSE +#include "brpc/details/controller_private_accessor.h" +#include "brpc/policy/most_common_message.h" +#include "brpc/policy/nova_pbrpc_protocol.h" +#include "brpc/compress.h" + + +namespace brpc { +namespace policy { + +// Protocol of NOVA PBRPC: +// - It doesn't support authentication, attachment and compression +// - The head is nshead, followed by the request body, without meta field, +// fields in nshead are NOT in network order. +// - Can not send feedback on failure. +// - There should be only one user service in server because there's no service +// information in the request, |reserved| field in nshead is the method index +// - |id|, |version| and |provider| in head are undefined, |magic_num| should be +// NSHEAD_MAGIC in both request and response +static const unsigned short NOVA_SNAPPY_COMPRESS_FLAG = 0x1u; + +void NovaServiceAdaptor::ParseNsheadMeta( + const Server& svr, const NsheadMessage& request, Controller* cntl, + NsheadMeta* out_meta) const { + google::protobuf::Service* service = svr.first_service(); + if (!service) { + cntl->SetFailed(ENOSERVICE, "No first_service in this server"); + return; + } + const int method_index = request.head.reserved; + const google::protobuf::ServiceDescriptor* sd = service->GetDescriptor(); + if (method_index < 0 || method_index >= sd->method_count()) { + cntl->SetFailed(ENOMETHOD, "Fail to find method by index=%d", method_index); + return; + } + const google::protobuf::MethodDescriptor* method = sd->method(method_index); + out_meta->set_full_method_name(method->full_name()); + if (request.head.version & NOVA_SNAPPY_COMPRESS_FLAG) { + out_meta->set_compress_type(COMPRESS_TYPE_SNAPPY); + } +} + +void NovaServiceAdaptor::ParseRequestFromIOBuf( + const NsheadMeta& meta, const NsheadMessage& raw_req, + Controller* cntl, google::protobuf::Message* pb_req) const { + CompressType type = meta.compress_type(); + if (!ParseFromCompressedData(raw_req.body, pb_req, type)) { + cntl->SetFailed(EREQUEST, "Fail to parse request message, " + "CompressType=%s, request_size=%" PRIu64, + CompressTypeToCStr(type), + (uint64_t)raw_req.body.length()); + } else { + cntl->set_request_compress_type(type); + } +} + +void NovaServiceAdaptor::SerializeResponseToIOBuf( + const NsheadMeta&, Controller* cntl, + const google::protobuf::Message* pb_res, NsheadMessage* raw_res) const { + if (cntl->Failed()) { + cntl->CloseConnection("Close connection due to previous error"); + return; + } + CompressType type = cntl->response_compress_type(); + if (type == COMPRESS_TYPE_SNAPPY) { + raw_res->head.version = NOVA_SNAPPY_COMPRESS_FLAG; + } else if (type != COMPRESS_TYPE_NONE) { + LOG(WARNING) << "nova_pbrpc protocol doesn't support " + << "compress_type=" << type; + type = COMPRESS_TYPE_NONE; + } + if (!SerializeAsCompressedData(*pb_res, &raw_res->body, type)) { + cntl->CloseConnection("Close connection due to failure of serialization"); + return; + } +} + +void ProcessNovaResponse(InputMessageBase* msg_base) { + const int64_t start_parse_us = butil::cpuwide_time_us(); + DestroyingPtr msg(static_cast(msg_base)); + Socket* socket = msg->socket(); + + // Fetch correlation id that we saved before in `PackNovaRequest' + const bthread_id_t cid = { static_cast(socket->correlation_id()) }; + Controller* cntl = NULL; + const int rc = bthread_id_lock(cid, (void**)&cntl); + if (rc != 0) { + LOG_IF(ERROR, rc != EINVAL && rc != EPERM) + << "Fail to lock correlation_id=" << cid << ": " << berror(rc); + return; + } + + ControllerPrivateAccessor accessor(cntl); + if (auto span = accessor.span()) { + span->set_base_real_us(msg->base_real_us()); + span->set_received_us(msg->received_us()); + span->set_response_size(msg->meta.size() + msg->payload.size()); + span->set_start_parse_us(start_parse_us); + } + const int saved_error = cntl->ErrorCode(); + // Fetch compress flag from nshead + char buf[sizeof(nshead_t)]; + const char *p = (const char *)msg->meta.fetch(buf, sizeof(buf)); + if (NULL == p) { + LOG(WARNING) << "Fail to fetch nshead from client=" + << socket->remote_side(); + return; + } + const nshead_t *nshead = (const nshead_t *)p; + CompressType type = (nshead->version & NOVA_SNAPPY_COMPRESS_FLAG ? + COMPRESS_TYPE_SNAPPY : COMPRESS_TYPE_NONE); + if (!ParseFromCompressedData(msg->payload, cntl->response(), type)) { + cntl->SetFailed(ERESPONSE, "Fail to parse response message"); + } else { + cntl->set_response_compress_type(type); + } + // Unlocks correlation_id inside. Revert controller's + // error code if it version check of `cid' fails + msg.reset(); // optional, just release resource ASAP + accessor.OnResponse(cid, saved_error); +} + +void SerializeNovaRequest(butil::IOBuf* buf, Controller* cntl, + const google::protobuf::Message* request) { + CompressType type = cntl->request_compress_type(); + if (type != COMPRESS_TYPE_NONE && type != COMPRESS_TYPE_SNAPPY) { + cntl->SetFailed(EREQUEST, "nova_pbrpc protocol doesn't support " + "compress_type=%d", type); + return; + } + return SerializeRequestDefault(buf, cntl, request); +} + +void PackNovaRequest(butil::IOBuf* buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* controller, + const butil::IOBuf& request, + const Authenticator* /*not supported*/) { + ControllerPrivateAccessor accessor(controller); + if (controller->connection_type() == CONNECTION_TYPE_SINGLE) { + return controller->SetFailed( + EINVAL, "nova_pbrpc can't work with CONNECTION_TYPE_SINGLE"); + } + // Store `correlation_id' into Socket since nova_pbrpc protocol + // doesn't contain this field + accessor.get_sending_socket()->set_correlation_id(correlation_id); + + nshead_t nshead; + memset(&nshead, 0, sizeof(nshead_t)); + nshead.log_id = controller->log_id(); + nshead.magic_num = NSHEAD_MAGICNUM; + nshead.reserved = method->index(); + nshead.body_len = request.size(); + // Set compress flag + if (controller->request_compress_type() == COMPRESS_TYPE_SNAPPY) { + nshead.version = NOVA_SNAPPY_COMPRESS_FLAG; + } + buf->append(&nshead, sizeof(nshead)); + + // Span* span = accessor.span(); + // if (span) { + // request_meta->set_trace_id(span->trace_id()); + // request_meta->set_span_id(span->span_id()); + // request_meta->set_parent_span_id(span->parent_span_id()); + // } + buf->append(request); +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/nova_pbrpc_protocol.h b/src/brpc/policy/nova_pbrpc_protocol.h new file mode 100644 index 0000000..448c3db --- /dev/null +++ b/src/brpc/policy/nova_pbrpc_protocol.h @@ -0,0 +1,66 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_NOVA_PBRPC_PROTOCOL_H +#define BRPC_POLICY_NOVA_PBRPC_PROTOCOL_H + +#include "brpc/nshead_pb_service_adaptor.h" +#include "brpc/policy/nshead_protocol.h" + + +namespace brpc { +namespace policy { + +// Actions to a (server) response in nova_pbrpc format. +void ProcessNovaResponse(InputMessageBase* msg); + +void SerializeNovaRequest(butil::IOBuf* buf, Controller* cntl, + const google::protobuf::Message* request); + +// Pack `request' to `method' into `buf'. +void PackNovaRequest(butil::IOBuf* buf, + SocketMessage** user_message_out, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* controller, + const butil::IOBuf& request, + const Authenticator* auth); + +class NovaServiceAdaptor : public NsheadPbServiceAdaptor { +public: + void ParseNsheadMeta(const Server& svr, + const NsheadMessage& request, + Controller*, + NsheadMeta* out_meta) const; + + void ParseRequestFromIOBuf( + const NsheadMeta& meta, const NsheadMessage& ns_req, + Controller* controller, google::protobuf::Message* pb_req) const; + + void SerializeResponseToIOBuf( + const NsheadMeta& meta, + Controller* controller, + const google::protobuf::Message* pb_res, + NsheadMessage* ns_res) const; +}; + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_NOVA_PBRPC_PROTOCOL_H diff --git a/src/brpc/policy/nshead_mcpack_protocol.cpp b/src/brpc/policy/nshead_mcpack_protocol.cpp new file mode 100644 index 0000000..8ba49f9 --- /dev/null +++ b/src/brpc/policy/nshead_mcpack_protocol.cpp @@ -0,0 +1,187 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // MethodDescriptor +#include // Message +#include + +#include "butil/iobuf.h" // butil::IOBuf +#include "butil/strings/string_util.h" +#include "butil/time.h" + +#include "brpc/controller.h" // Controller +#include "brpc/socket.h" // Socket +#include "brpc/server.h" // Server +#include "brpc/details/server_private_accessor.h" +#include "brpc/span.h" +#include "brpc/errno.pb.h" // EREQUEST, ERESPONSE +#include "brpc/details/controller_private_accessor.h" +#include "brpc/policy/most_common_message.h" +#include "brpc/policy/nshead_mcpack_protocol.h" +#include "mcpack2pb/mcpack2pb.h" + + +namespace brpc { +namespace policy { + +void NsheadMcpackAdaptor::ParseNsheadMeta( + const Server& svr, const NsheadMessage& /*request*/, Controller* cntl, + NsheadMeta* out_meta) const { + google::protobuf::Service* service = svr.first_service(); + if (!service) { + cntl->SetFailed(ENOSERVICE, "No first_service in this server"); + return; + } + const google::protobuf::ServiceDescriptor* sd = service->GetDescriptor(); + if (sd->method_count() == 0) { + cntl->SetFailed(ENOMETHOD, "No method in service=%s", + butil::EnsureString(sd->full_name()).c_str()); + return; + } + const google::protobuf::MethodDescriptor* method = sd->method(0); + out_meta->set_full_method_name(method->full_name()); +} + +void NsheadMcpackAdaptor::ParseRequestFromIOBuf( + const NsheadMeta&, const NsheadMessage& raw_req, + Controller* cntl, google::protobuf::Message* pb_req) const { + const std::string msg_name = butil::EnsureString(pb_req->GetDescriptor()->full_name()); + mcpack2pb::MessageHandler handler = mcpack2pb::find_message_handler(msg_name); + if (!handler.parse_from_iobuf(pb_req, raw_req.body)) { + cntl->SetFailed(EREQUEST, "Fail to parse request message, " + "request_size=%" PRIu64, (uint64_t)raw_req.body.length()); + return; + } +} + +void NsheadMcpackAdaptor::SerializeResponseToIOBuf( + const NsheadMeta&, Controller* cntl, + const google::protobuf::Message* pb_res, NsheadMessage* raw_res) const { + if (cntl->Failed()) { + cntl->CloseConnection("Close connection due to previous error"); + return; + } + CompressType type = cntl->response_compress_type(); + if (type != COMPRESS_TYPE_NONE) { + LOG(WARNING) << "nshead_mcpack protocol doesn't support compression"; + type = COMPRESS_TYPE_NONE; + } + + if (pb_res == NULL) { + cntl->CloseConnection("response was not created yet"); + return; + } + + const std::string msg_name = butil::EnsureString(pb_res->GetDescriptor()->full_name()); + mcpack2pb::MessageHandler handler = mcpack2pb::find_message_handler(msg_name); + if (!handler.serialize_to_iobuf(*pb_res, &raw_res->body, + ::mcpack2pb::FORMAT_MCPACK_V2)) { + cntl->CloseConnection("Fail to serialize %s", msg_name.c_str()); + return; + } +} + +void ProcessNsheadMcpackResponse(InputMessageBase* msg_base) { + const int64_t start_parse_us = butil::cpuwide_time_us(); + DestroyingPtr msg(static_cast(msg_base)); + const Socket* socket = msg->socket(); + + // Fetch correlation id that we saved before in `PackNsheadMcpackRequest' + const bthread_id_t cid = { static_cast(socket->correlation_id()) }; + Controller* cntl = NULL; + const int rc = bthread_id_lock(cid, (void**)&cntl); + if (rc != 0) { + LOG_IF(ERROR, rc != EINVAL && rc != EPERM) + << "Fail to lock correlation_id=" << cid << ": " << berror(rc); + return; + } + + ControllerPrivateAccessor accessor(cntl); + if (auto span = accessor.span()) { + span->set_base_real_us(msg->base_real_us()); + span->set_received_us(msg->received_us()); + span->set_response_size(msg->meta.size() + msg->payload.size()); + span->set_start_parse_us(start_parse_us); + } + const int saved_error = cntl->ErrorCode(); + google::protobuf::Message* res = cntl->response(); + if (res == NULL) { + // silently ignore response. + return; + } + const std::string msg_name = butil::EnsureString(res->GetDescriptor()->full_name()); + mcpack2pb::MessageHandler handler = mcpack2pb::find_message_handler(msg_name); + if (!handler.parse_from_iobuf(res, msg->payload)) { + return cntl->CloseConnection("Fail to parse response message"); + } + // Unlocks correlation_id inside. Revert controller's + // error code if it version check of `cid' fails + msg.reset(); // optional, just release resource ASAP + accessor.OnResponse(cid, saved_error); +} + +void SerializeNsheadMcpackRequest(butil::IOBuf* buf, Controller* cntl, + const google::protobuf::Message* pb_req) { + CompressType type = cntl->request_compress_type(); + if (type != COMPRESS_TYPE_NONE) { + cntl->SetFailed(EREQUEST, + "nshead_mcpack protocol doesn't support compression"); + return; + } + const std::string msg_name = butil::EnsureString(pb_req->GetDescriptor()->full_name()); + mcpack2pb::MessageHandler handler = mcpack2pb::find_message_handler(msg_name); + if (!handler.serialize_to_iobuf(*pb_req, buf, ::mcpack2pb::FORMAT_MCPACK_V2)) { + cntl->SetFailed(EREQUEST, "Fail to serialize %s", msg_name.c_str()); + return; + } +} + +void PackNsheadMcpackRequest(butil::IOBuf* buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor*, + Controller* controller, + const butil::IOBuf& request, + const Authenticator* /*not supported*/) { + ControllerPrivateAccessor accessor(controller); + if (controller->connection_type() == CONNECTION_TYPE_SINGLE) { + return controller->SetFailed( + EINVAL, "nshead_mcpack can't work with CONNECTION_TYPE_SINGLE"); + } + // Store `correlation_id' into Socket since nshead_mcpack protocol + // doesn't contain this field + accessor.get_sending_socket()->set_correlation_id(correlation_id); + + nshead_t nshead; + memset(&nshead, 0, sizeof(nshead_t)); + nshead.log_id = controller->log_id(); + nshead.magic_num = NSHEAD_MAGICNUM; + nshead.body_len = request.size(); + buf->append(&nshead, sizeof(nshead)); + + // Span* span = accessor.span(); + // if (span) { + // request_meta->set_trace_id(span->trace_id()); + // request_meta->set_span_id(span->span_id()); + // request_meta->set_parent_span_id(span->parent_span_id()); + // } + buf->append(request); +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/nshead_mcpack_protocol.h b/src/brpc/policy/nshead_mcpack_protocol.h new file mode 100644 index 0000000..bda4031 --- /dev/null +++ b/src/brpc/policy/nshead_mcpack_protocol.h @@ -0,0 +1,66 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_NSHEAD_MCPACK_PROTOCOL_H +#define BRPC_POLICY_NSHEAD_MCPACK_PROTOCOL_H + +#include "brpc/nshead_pb_service_adaptor.h" +#include "brpc/policy/nshead_protocol.h" + + +namespace brpc { +namespace policy { + +// Actions to a (server) response in nshead+mcpack format. +void ProcessNsheadMcpackResponse(InputMessageBase* msg); + +void SerializeNsheadMcpackRequest(butil::IOBuf* buf, Controller* cntl, + const google::protobuf::Message* request); + +// Pack `request' to `method' into `buf'. +void PackNsheadMcpackRequest(butil::IOBuf* buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* controller, + const butil::IOBuf& request, + const Authenticator* auth); + +class NsheadMcpackAdaptor : public NsheadPbServiceAdaptor { +public: + void ParseNsheadMeta(const Server& svr, + const NsheadMessage& request, + Controller*, + NsheadMeta* out_meta) const; + + void ParseRequestFromIOBuf( + const NsheadMeta& meta, const NsheadMessage& ns_req, + Controller* controller, google::protobuf::Message* pb_req) const; + + void SerializeResponseToIOBuf( + const NsheadMeta& meta, + Controller* controller, + const google::protobuf::Message* pb_res, + NsheadMessage* ns_res) const; +}; + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_NSHEAD_MCPACK_PROTOCOL_H diff --git a/src/brpc/policy/nshead_protocol.cpp b/src/brpc/policy/nshead_protocol.cpp new file mode 100644 index 0000000..82f696e --- /dev/null +++ b/src/brpc/policy/nshead_protocol.cpp @@ -0,0 +1,451 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // MethodDescriptor +#include // Message +#include +#include "butil/time.h" +#include "butil/iobuf.h" // butil::IOBuf +#include "brpc/log.h" +#include "brpc/controller.h" // Controller +#include "brpc/socket.h" // Socket +#include "brpc/server.h" // Server +#include "brpc/span.h" +#include "brpc/rpc_dump.h" +#include "brpc/details/server_private_accessor.h" +#include "brpc/details/controller_private_accessor.h" +#include "brpc/nshead_service.h" +#include "brpc/policy/most_common_message.h" +#include "brpc/policy/nshead_protocol.h" +#include "brpc/details/usercode_backup_pool.h" + +extern "C" { +void bthread_assign_data(void* data); +} + + +namespace brpc { + +NsheadClosure::NsheadClosure(void* additional_space) + : _server(NULL) + , _received_us(0) + , _do_respond(true) + , _additional_space(additional_space) { +} + +NsheadClosure::~NsheadClosure() { + LogErrorTextAndDelete(false)(&_controller); +} + +void NsheadClosure::DoNotRespond() { + _do_respond = false; +} + +class DeleteNsheadClosure { +public: + void operator()(NsheadClosure* done) const { + done->~NsheadClosure(); + free(done); + } +}; + +void NsheadClosure::Run() { + // Recycle itself after `Run' + std::unique_ptr recycle_ctx(this); + + ControllerPrivateAccessor accessor(&_controller); + auto span = accessor.span(); + if (span) { + span->set_start_send_us(butil::cpuwide_time_us()); + } + Socket* sock = accessor.get_sending_socket(); + MethodStatus* method_status = _server->options().nshead_service->_status; + ConcurrencyRemover concurrency_remover(method_status, &_controller, _received_us); + if (!method_status) { + // Judge errors belongings. + // may not be accurate, but it does not matter too much. + const int error_code = _controller.ErrorCode(); + if (error_code == ENOSERVICE || + error_code == ENOMETHOD || + error_code == EREQUEST || + error_code == ECLOSE || + error_code == ELOGOFF || + error_code == ELIMIT) { + ServerPrivateAccessor(_server).AddError(); + } + } + + if (_controller.IsCloseConnection()) { + sock->SetFailed(); + return; + } + + int64_t sent_us = 0; + if (_do_respond) { + // response uses request's head as default. + // Notice that the response use request.head.log_id directly rather + // than _controller.log_id() which may be different and packed in + // the meta or user messages. + _response.head = _request.head; + _response.head.magic_num = NSHEAD_MAGICNUM; + _response.head.body_len = _response.body.length(); + if (span) { + int response_size = sizeof(nshead_t) + _response.head.body_len; + span->set_response_size(response_size); + } + butil::IOBuf write_buf; + write_buf.append(&_response.head, sizeof(nshead_t)); + write_buf.append(_response.body.movable()); + // Have the risk of unlimited pending responses, in which case, tell + // users to set max_concurrency. + ResponseWriteInfo args; + Socket::WriteOptions wopt; + wopt.ignore_eovercrowded = true; + bthread_id_t response_id = INVALID_BTHREAD_ID; + if (span) { + CHECK_EQ(0, bthread_id_create(&response_id, &args, HandleResponseWritten)); + wopt.id_wait = response_id; + wopt.notify_on_success = true; + } + if (sock->Write(&write_buf, &wopt) != 0) { + const int errcode = errno; + PLOG_IF(WARNING, errcode != EPIPE) << "Fail to write into " << *sock; + _controller.SetFailed(errcode, "Fail to write into %s", + sock->description().c_str()); + return; + } + + if (span) { + bthread_id_join(response_id); + // Do not care about the result of background writing. + sent_us = args.sent_us; + } + } + if (span) { + // TODO: this is not sent + span->set_sent_us(0 == sent_us ? butil::cpuwide_time_us() : sent_us); + } +} + +void NsheadClosure::SetMethodName(const std::string& full_method_name) { + ControllerPrivateAccessor accessor(&_controller); + if (auto span = accessor.span()) { + span->ResetServerSpanName(full_method_name); + } +} + +namespace policy { + +ParseResult ParseNsheadMessage(butil::IOBuf* source, + Socket*, bool /*read_eof*/, const void* /*arg*/) { + char header_buf[sizeof(nshead_t)]; + const size_t n = source->copy_to(header_buf, sizeof(header_buf)); + if (n < offsetof(nshead_t, magic_num) + 4) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + const void* dummy = header_buf + offsetof(nshead_t, magic_num); + const unsigned int magic_num = *(unsigned int*)dummy; + if (magic_num != NSHEAD_MAGICNUM) { + RPC_VLOG << "magic_num=" << magic_num + << " doesn't match NSHEAD_MAGICNUM=" << NSHEAD_MAGICNUM; + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + if (n < sizeof(nshead_t)) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + const nshead_t* nshead = (const nshead_t *)header_buf; + uint32_t body_len = nshead->body_len; + if (body_len > FLAGS_max_body_size) { + return MakeParseError(PARSE_ERROR_TOO_BIG_DATA); + } else if (source->length() < sizeof(header_buf) + body_len) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + + policy::MostCommonMessage* msg = policy::MostCommonMessage::Get(); + source->cutn(&msg->meta, sizeof(header_buf)); + source->cutn(&msg->payload, body_len); + return MakeMessage(msg); +} + +namespace { +struct CallMethodInBackupThreadArgs { + NsheadService* service; + const Server* server; + Controller* controller; + const NsheadMessage* request; + NsheadMessage* response; + NsheadClosure* done; +}; +} + +static void CallMethodInBackupThread(void* void_args) { + CallMethodInBackupThreadArgs* args = (CallMethodInBackupThreadArgs*)void_args; + args->service->ProcessNsheadRequest(*args->server, args->controller, + *args->request, args->response, + args->done); + delete args; +} + +static void EndRunningCallMethodInPool(NsheadService* service, + const Server& server, + Controller* controller, + const NsheadMessage& request, + NsheadMessage* response, + NsheadClosure* done) { + CallMethodInBackupThreadArgs* args = new CallMethodInBackupThreadArgs; + args->service = service; + args->server = &server; + args->controller = controller; + args->request = &request; + args->response = response; + args->done = done; + return EndRunningUserCodeInPool(CallMethodInBackupThread, args); +}; + +void ProcessNsheadRequest(InputMessageBase* msg_base) { + const int64_t start_parse_us = butil::cpuwide_time_us(); + + DestroyingPtr msg(static_cast(msg_base)); + SocketUniquePtr socket_guard(msg->ReleaseSocket()); + Socket* socket = socket_guard.get(); + const Server* server = static_cast(msg_base->arg()); + ScopedNonServiceError non_service_error(server); + + char buf[sizeof(nshead_t)]; + const char *p = (const char *)msg->meta.fetch(buf, sizeof(buf)); + const nshead_t *req_head = (const nshead_t *)p; + + NsheadService* service = server->options().nshead_service; + if (service == NULL) { + LOG_EVERY_SECOND(WARNING) + << "Received nshead request however the server does not set" + " ServerOptions.nshead_service, close the connection."; + socket->SetFailed(); + return; + } + void* space = malloc(sizeof(NsheadClosure) + service->_additional_space); + if (!space) { + LOG(FATAL) << "Fail to new NsheadClosure"; + socket->SetFailed(); + return; + } + + // for nshead sample request + SampledRequest* sample = AskToBeSampled(); + if (sample) { + sample->meta.set_protocol_type(PROTOCOL_NSHEAD); + sample->meta.set_nshead(p, sizeof(nshead_t)); // nshead + sample->request = msg->payload; + sample->submit(start_parse_us); + } + + // Switch to service-specific error. + non_service_error.release(); + MethodStatus* method_status = service->_status; + if (method_status) { + CHECK(method_status->OnRequested()); + } + + void* sub_space = NULL; + if (service->_additional_space) { + sub_space = (char*)space + sizeof(NsheadClosure); + } + NsheadClosure* nshead_done = new (space) NsheadClosure(sub_space); + Controller* cntl = &(nshead_done->_controller); + NsheadMessage* req = &(nshead_done->_request); + NsheadMessage* res = &(nshead_done->_response); + + req->head = *req_head; + msg->payload.swap(req->body); + nshead_done->_received_us = msg->received_us(); + nshead_done->_server = server; + + ServerPrivateAccessor server_accessor(server); + ControllerPrivateAccessor accessor(cntl); + const bool security_mode = server->options().security_mode() && + socket->user() == server_accessor.acceptor(); + // Initialize log_id with the log_id in nshead. Notice that the protocols + // on top of NsheadService may pack log_id in meta or user messages and + // overwrite the value. + cntl->set_log_id(req_head->log_id); + accessor.set_server(server) + .set_security_mode(security_mode) + .set_peer_id(socket->id()) + .set_remote_side(socket->remote_side()) + .set_local_side(socket->local_side()) + .set_request_protocol(PROTOCOL_NSHEAD) + .set_begin_time_us(msg->received_us()) + .move_in_server_receiving_sock(socket_guard); + + // Tag the bthread with this server's key for thread_local_data(). + if (server->thread_local_options().thread_local_data_factory) { + bthread_assign_data((void*)&server->thread_local_options()); + } + + std::shared_ptr span; + if (IsTraceable(false)) { + span = Span::CreateServerSpan(0, 0, 0, msg->base_real_us()); + accessor.set_span(span); + span->set_log_id(req_head->log_id); + span->set_remote_side(cntl->remote_side()); + span->set_protocol(PROTOCOL_NSHEAD); + span->set_received_us(msg->received_us()); + span->set_start_parse_us(start_parse_us); + span->set_request_size(sizeof(nshead_t) + req_head->body_len); + } + + do { + if (!server->IsRunning()) { + cntl->SetFailed(ELOGOFF, "Server is stopping"); + break; + } + if (socket->is_overcrowded() && !server->options().ignore_eovercrowded) { + cntl->SetFailed(EOVERCROWDED, "Connection to %s is overcrowded", + butil::endpoint2str(socket->remote_side()).c_str()); + break; + } + if (!server_accessor.AddConcurrency(cntl)) { + cntl->SetFailed( + ELIMIT, "Reached server's max_concurrency=%d", + server->options().max_concurrency); + break; + } + if (FLAGS_usercode_in_pthread && TooManyUserCode()) { + cntl->SetFailed(ELIMIT, "Too many user code to run when" + " -usercode_in_pthread is on"); + break; + } + if (!server->AcceptRequest(cntl)) { + break; + } + } while (false); + + msg.reset(); // optional, just release resource ASAP + if (span) { + span->ResetServerSpanName(service->_cached_name); + span->set_start_callback_us(butil::cpuwide_time_us()); + span->AsParent(); + } + if (!FLAGS_usercode_in_pthread) { + return service->ProcessNsheadRequest(*server, cntl, *req, res, nshead_done); + } + if (BeginRunningUserCode()) { + service->ProcessNsheadRequest(*server, cntl, *req, res, nshead_done); + return EndRunningUserCodeInPlace(); + } else { + return EndRunningCallMethodInPool( + service, *server, cntl, *req, res, nshead_done); + } +} + +void ProcessNsheadResponse(InputMessageBase* msg_base) { + const int64_t start_parse_us = butil::cpuwide_time_us(); + DestroyingPtr msg(static_cast(msg_base)); + + // Fetch correlation id that we saved before in `PackNsheadRequest' + const CallId cid = { static_cast(msg->socket()->correlation_id()) }; + Controller* cntl = NULL; + const int rc = bthread_id_lock(cid, (void**)&cntl); + if (rc != 0) { + LOG_IF(ERROR, rc != EINVAL && rc != EPERM) + << "Fail to lock correlation_id=" << cid << ": " << berror(rc); + return; + } + + ControllerPrivateAccessor accessor(cntl); + if (auto span = accessor.span()) { + span->set_base_real_us(msg->base_real_us()); + span->set_received_us(msg->received_us()); + span->set_response_size(msg->payload.length()); + span->set_start_parse_us(start_parse_us); + } + // MUST be NsheadMessage (checked in SerializeNsheadRequest) + NsheadMessage* response = (NsheadMessage*)cntl->response(); + const int saved_error = cntl->ErrorCode(); + if (response != NULL) { + msg->meta.copy_to(&response->head, sizeof(nshead_t)); + msg->payload.swap(response->body); + } // else just ignore the response. + + // Unlocks correlation_id inside. Revert controller's + // error code if it version check of `cid' fails + msg.reset(); // optional, just release resource ASAP + accessor.OnResponse(cid, saved_error); +} + +bool VerifyNsheadRequest(const InputMessageBase* msg_base) { + Server* server = (Server*)msg_base->arg(); + if (server->options().auth) { + LOG(WARNING) << "nshead does not support authentication"; + return false; + } + return true; +} + +void SerializeNsheadRequest(butil::IOBuf* request_buf, Controller* cntl, + const google::protobuf::Message* req_base) { + if (req_base == NULL) { + return cntl->SetFailed(EREQUEST, "request is NULL"); + } + if (req_base->GetDescriptor() != NsheadMessage::descriptor()) { + return cntl->SetFailed(EINVAL, "Type of request must be NsheadMessage"); + } + if (cntl->response() != NULL && + cntl->response()->GetDescriptor() != NsheadMessage::descriptor()) { + return cntl->SetFailed(EINVAL, "Type of response must be NsheadMessage"); + } + const NsheadMessage* req = (const NsheadMessage*)req_base; + nshead_t nshead = req->head; + if (cntl->has_log_id()) { + nshead.log_id = cntl->log_id(); + } + nshead.magic_num = NSHEAD_MAGICNUM; + nshead.body_len = req->body.size(); + request_buf->append(&nshead, sizeof(nshead)); + request_buf->append(req->body); +} + +void PackNsheadRequest( + butil::IOBuf* packet_buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor*, + Controller* cntl, + const butil::IOBuf& request, + const Authenticator*) { + ControllerPrivateAccessor accessor(cntl); + if (cntl->connection_type() == CONNECTION_TYPE_SINGLE) { + return cntl->SetFailed( + EINVAL, "nshead protocol can't work with CONNECTION_TYPE_SINGLE"); + } + // Store `correlation_id' into the socket since nshead protocol can't + // pack the field. + accessor.get_sending_socket()->set_correlation_id(correlation_id); + + if (auto span = accessor.span()) { + span->set_request_size(request.length()); + // TODO: Nowhere to set tracing ids. + // request_meta->set_trace_id(span->trace_id()); + // request_meta->set_span_id(span->span_id()); + // request_meta->set_parent_span_id(span->parent_span_id()); + } + packet_buf->append(request); +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/nshead_protocol.h b/src/brpc/policy/nshead_protocol.h new file mode 100644 index 0000000..41e2710 --- /dev/null +++ b/src/brpc/policy/nshead_protocol.h @@ -0,0 +1,56 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_NSHEAD_PROTOCOL_H +#define BRPC_POLICY_NSHEAD_PROTOCOL_H + +#include "brpc/protocol.h" + + +namespace brpc { +namespace policy { + +// Parse binary format of nshead +ParseResult ParseNsheadMessage(butil::IOBuf* source, Socket* socket, bool read_eof, const void *arg); + +// Actions to a (client) request in nshead format +void ProcessNsheadRequest(InputMessageBase* msg); + +// Actions to a (server) response in nshead format +void ProcessNsheadResponse(InputMessageBase* msg); + +void SerializeNsheadRequest(butil::IOBuf* request_buf, Controller* controller, + const google::protobuf::Message* request); + +void PackNsheadRequest( + butil::IOBuf* packet_buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor*, + Controller* controller, + const butil::IOBuf&, + const Authenticator*); + +// Verify authentication information in nshead format +bool VerifyNsheadRequest(const InputMessageBase *msg); + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_NSHEAD_PROTOCOL_H diff --git a/src/brpc/policy/p2c_ewma_load_balancer.cpp b/src/brpc/policy/p2c_ewma_load_balancer.cpp new file mode 100644 index 0000000..2b3f5bb --- /dev/null +++ b/src/brpc/policy/p2c_ewma_load_balancer.cpp @@ -0,0 +1,389 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // std::exp +#include +#include "butil/fast_rand.h" // fast_rand_less_than +#include "butil/time.h" // gettimeofday_us +#include "butil/string_splitter.h" // KeyValuePairsSplitter +#include "butil/strings/string_number_conversions.h" // StringToUint +#include "brpc/socket.h" +#include "brpc/controller.h" +#include "brpc/policy/p2c_ewma_load_balancer.h" + +namespace brpc { +namespace policy { + +DEFINE_uint32(p2c_default_choices, 2, + "Default number of servers sampled per selection in p2c, " + "overridable per channel with the `choices' parameter"); +DEFINE_int64(p2c_default_tau_ms, 10000, + "Default decay time(ms) of the peak-EWMA latency in p2c " + "(Finagle's PeakEwma default), overridable per channel with " + "the `tau_ms' parameter"); +DEFINE_int64(p2c_max_punish_ms, 30000, + "Cap(ms) on the punished latency recorded for a failed call in " + "p2c, bounding how long a persistently failing server takes to " + "recover after it turns healthy. 0 means no cap"); + +namespace { +// Upper bound of the `choices' parameter. Compile-time because it sizes the +// sampling array on SelectServer's stack. +const uint32_t MAX_CHOICES = 64; +// Floor of the latency term so that in-flight counts break ties between +// servers that have no latency observation yet. +const double MIN_LATENCY_TERM_US = 1.0; + +uint32_t WeightOfTag(const std::string& tag) { + if (tag.empty()) { + return 1; + } + uint32_t weight = 0; + if (!butil::StringToUint(tag, &weight) || weight == 0) { + LOG(WARNING) << "Invalid weight tag=`" << tag << "', use weight=1"; + return 1; + } + return weight; +} +} // namespace + +P2CEwmaLoadBalancer::P2CEwmaLoadBalancer() + // Clamp so that broken flag values can not disable comparison or decay. + : _choices(std::min(std::max(FLAGS_p2c_default_choices, 2u), MAX_CHOICES)) + , _tau_us(std::max(FLAGS_p2c_default_tau_ms, 1) * 1000L) {} + +bool P2CEwmaLoadBalancer::Add(Servers& bg, const Servers& fg, + const ServerId& id) { + if (bg.server_list.capacity() < 128) { + bg.server_list.reserve(128); + } + if (bg.server_map.seek(id.id) != NULL) { + return false; + } + ServerInfo info = { id.id, WeightOfTag(id.tag), NULL }; + const size_t* pindex = fg.server_map.seek(id.id); + if (pindex == NULL) { + // Both buffers do not have the server. Create the stat structure + // which will be shared by both buffers. + info.stat = std::make_shared(); + } else { + // Already added to the other buffer, share its stat. + info.stat = fg.server_list[*pindex].stat; + } + bg.server_map[id.id] = bg.server_list.size(); + bg.server_list.push_back(info); + return true; +} + +bool P2CEwmaLoadBalancer::Remove(Servers& bg, const ServerId& id) { + size_t* pindex = bg.server_map.seek(id.id); + if (pindex == NULL) { + return false; + } + const size_t index = *pindex; + bg.server_list[index] = bg.server_list.back(); + bg.server_map[bg.server_list[index].id] = index; + bg.server_list.pop_back(); + bg.server_map.erase(id.id); + return true; +} + +size_t P2CEwmaLoadBalancer::BatchAdd( + Servers& bg, const Servers& fg, const std::vector& servers) { + size_t count = 0; + for (size_t i = 0; i < servers.size(); ++i) { + count += !!Add(bg, fg, servers[i]); + } + return count; +} + +size_t P2CEwmaLoadBalancer::BatchRemove( + Servers& bg, const std::vector& servers) { + size_t count = 0; + for (size_t i = 0; i < servers.size(); ++i) { + count += !!Remove(bg, servers[i]); + } + return count; +} + +bool P2CEwmaLoadBalancer::AddServer(const ServerId& id) { + return _db_servers.ModifyWithForeground(Add, id); +} + +bool P2CEwmaLoadBalancer::RemoveServer(const ServerId& id) { + return _db_servers.Modify(Remove, id); +} + +size_t P2CEwmaLoadBalancer::AddServersInBatch( + const std::vector& servers) { + const size_t n = _db_servers.ModifyWithForeground(BatchAdd, servers); + LOG_IF(ERROR, n != servers.size()) + << "Fail to AddServersInBatch, expected " << servers.size() + << " actually " << n; + return n; +} + +size_t P2CEwmaLoadBalancer::RemoveServersInBatch( + const std::vector& servers) { + return _db_servers.Modify(BatchRemove, servers); +} + +double P2CEwmaLoadBalancer::Score( + const ServerInfo& info, int64_t now_us) const { + const int64_t ewma_us = + info.stat->ewma_us.load(butil::memory_order_relaxed); + const int32_t inflight = + info.stat->inflight.load(butil::memory_order_relaxed); + double latency_term = MIN_LATENCY_TERM_US; + if (ewma_us > 0) { + // Decay the (possibly stale) EWMA at read time so that a server + // penalized long ago regains traffic and gets re-observed. + const int64_t stamp_us = + info.stat->stamp_us.load(butil::memory_order_relaxed); + const int64_t elapsed_us = now_us - stamp_us; + double decayed = (double)ewma_us; + if (elapsed_us > 0) { + decayed *= std::exp(-(double)elapsed_us / (double)_tau_us); + } + latency_term = std::max(decayed, MIN_LATENCY_TERM_US); + } + // Clamp so that a transiently negative counter can not invert routing. + const int32_t load = std::max(inflight + 1, 1); + return latency_term * (double)load / (double)info.weight; +} + +int P2CEwmaLoadBalancer::SelectServer(const SelectIn& in, SelectOut* out) { + butil::DoublyBufferedData::ScopedPtr s; + if (_db_servers.Read(&s) != 0) { + return ENOMEM; + } + const size_t n = s->server_list.size(); + if (n == 0) { + return ENODATA; + } + // Reuse the caller-provided timestamp to avoid an extra clock read per + // selection; only Channel::CheckHealth passes 0. + const int64_t now_us = in.begin_time_us > 0 + ? in.begin_time_us : butil::gettimeofday_us(); + + const ServerInfo* best = NULL; + double best_score = 0; + SocketUniquePtr best_ptr; + // Score the server at `index' and keep it if it beats the current best. + // Excluded and unavailable servers are skipped. + auto consider = [&](size_t index) { + const ServerInfo& info = s->server_list[index]; + if (ExcludedServers::IsExcluded(in.excluded, info.id)) { + return; + } + SocketUniquePtr ptr; + if (!IsServerAvailable(info.id, &ptr)) { + return; + } + const double score = Score(info, now_us); + if (best == NULL || score < best_score) { + best = &info; + best_score = score; + best_ptr.swap(ptr); + } + }; + + const size_t choices = std::min((size_t)_choices, n); + if (choices >= n) { + // Scan from a random offset so that equal scores do not herd all + // clients onto the lowest-indexed server. + const size_t start = butil::fast_rand_less_than(n); + for (size_t i = 0; i < n; ++i) { + consider((start + i) % n); + } + } else { + // Sample `choices' distinct random servers. Attempts are bounded so + // that duplicated draws never loop for long. + size_t chosen[MAX_CHOICES]; + size_t nchosen = 0; + const size_t max_attempts = 4 * choices + 8; + for (size_t attempt = 0; + attempt < max_attempts && nchosen < choices; ++attempt) { + const size_t index = butil::fast_rand_less_than(n); + bool duplicated = false; + for (size_t i = 0; i < nchosen; ++i) { + if (chosen[i] == index) { + duplicated = true; + break; + } + } + if (duplicated) { + continue; + } + chosen[nchosen++] = index; + consider(index); + } + if (best == NULL) { + // All sampled servers were excluded or unavailable, fall back + // to scoring the whole list before violating exclusion below. + for (size_t i = 0; i < n; ++i) { + consider(i); + } + } + } + + if (best == NULL) { + // Always take last chance: all servers are excluded, send to any + // available one as rr/random do. + for (size_t i = 0; i < n; ++i) { + if (IsServerAvailable(s->server_list[i].id, &best_ptr)) { + best = &s->server_list[i]; + break; + } + } + if (best == NULL) { + return EHOSTDOWN; + } + } + if (in.changable_weights) { + best->stat->inflight.fetch_add(1, butil::memory_order_relaxed); + out->need_feedback = true; + } + out->ptr->swap(best_ptr); + return 0; +} + +void P2CEwmaLoadBalancer::Feedback(const CallInfo& info) { + butil::DoublyBufferedData::ScopedPtr s; + if (_db_servers.Read(&s) != 0) { + return; + } + const size_t* pindex = s->server_map.seek(info.server_id); + if (pindex == NULL) { + // The server was removed after selection, its stat is gone with it. + return; + } + NodeStat* stat = s->server_list[*pindex].stat.get(); + stat->inflight.fetch_sub(1, butil::memory_order_relaxed); + + const int64_t now_us = butil::gettimeofday_us(); + int64_t latency_us = now_us - info.begin_time_us; + if (latency_us <= 0) { + // time skews, ignore the sample. + return; + } + if (info.error_code != 0) { + // Punish failures with at least the timeout(if any) and twice the + // current average, so that a failing server keeps losing comparisons + // even when it fails fast. + const int64_t timeout_us = info.controller->timeout_ms() * 1000L; + latency_us = std::max(latency_us, timeout_us); + latency_us = std::max( + latency_us, 2 * stat->ewma_us.load(butil::memory_order_relaxed)); + // Cap the punishment: consecutive failures double the EWMA each + // time, which otherwise grows without bound and delays recovery + // after the server turns healthy. + const int64_t max_punish_us = FLAGS_p2c_max_punish_ms * 1000L; + if (max_punish_us > 0 && latency_us > max_punish_us) { + latency_us = max_punish_us; + } + } + + BAIDU_SCOPED_LOCK(stat->update_mutex); + const int64_t ewma_us = stat->ewma_us.load(butil::memory_order_relaxed); + int64_t new_ewma_us = latency_us; + if (ewma_us > 0 && latency_us < ewma_us) { + // Downward samples decay the average while upward spikes(handled by + // the branch above) replace it immediately. + const int64_t elapsed_us = + now_us - stat->stamp_us.load(butil::memory_order_relaxed); + const double w = elapsed_us > 0 + ? std::exp(-(double)elapsed_us / (double)_tau_us) : 1.0; + new_ewma_us = (int64_t)(ewma_us * w + latency_us * (1.0 - w)); + } + stat->ewma_us.store(new_ewma_us, butil::memory_order_relaxed); + stat->stamp_us.store(now_us, butil::memory_order_relaxed); +} + +P2CEwmaLoadBalancer* P2CEwmaLoadBalancer::New( + const butil::StringPiece& params) const { + P2CEwmaLoadBalancer* lb = new (std::nothrow) P2CEwmaLoadBalancer; + if (lb != NULL && !lb->SetParameters(params)) { + delete lb; + lb = NULL; + } + return lb; +} + +bool P2CEwmaLoadBalancer::SetParameters(const butil::StringPiece& params) { + for (butil::KeyValuePairsSplitter sp(params.begin(), params.end(), ' ', '='); + sp; ++sp) { + if (sp.value().empty()) { + LOG(ERROR) << "Empty value for " << sp.key() << " in lb parameter"; + return false; + } + if (sp.key() == "choices") { + unsigned choices = 0; + if (!butil::StringToUint(sp.value().as_string(), &choices) || + choices < 2 || choices > MAX_CHOICES) { + LOG(ERROR) << "Invalid choices=`" << sp.value() << "'"; + return false; + } + _choices = choices; + } else if (sp.key() == "tau_ms") { + int64_t tau_ms = 0; + if (!butil::StringToInt64(sp.value(), &tau_ms) || tau_ms <= 0) { + LOG(ERROR) << "Invalid tau_ms=`" << sp.value() << "'"; + return false; + } + _tau_us = tau_ms * 1000L; + } else { + LOG(ERROR) << "Unknown parameter " << sp.key_and_value(); + return false; + } + } + return true; +} + +void P2CEwmaLoadBalancer::Destroy() { + delete this; +} + +void P2CEwmaLoadBalancer::Describe( + std::ostream& os, const DescribeOptions& options) { + if (!options.verbose) { + os << "p2c"; + return; + } + os << "P2CEwma{choices=" << _choices << " tau_ms=" << _tau_us / 1000; + butil::DoublyBufferedData::ScopedPtr s; + if (_db_servers.Read(&s) != 0) { + os << " fail to read _db_servers"; + } else { + const int64_t now_us = butil::gettimeofday_us(); + os << " n=" << s->server_list.size() << ':'; + for (size_t i = 0; i < s->server_list.size(); ++i) { + const ServerInfo& info = s->server_list[i]; + os << ' ' << info.id << '(' << "w=" << info.weight + << " ewma_us=" + << info.stat->ewma_us.load(butil::memory_order_relaxed) + << " inflight=" + << info.stat->inflight.load(butil::memory_order_relaxed) + << " score=" << Score(info, now_us) << ')'; + } + } + os << '}'; +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/p2c_ewma_load_balancer.h b/src/brpc/policy/p2c_ewma_load_balancer.h new file mode 100644 index 0000000..1594f15 --- /dev/null +++ b/src/brpc/policy/p2c_ewma_load_balancer.h @@ -0,0 +1,94 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_P2C_EWMA_LOAD_BALANCER_H +#define BRPC_POLICY_P2C_EWMA_LOAD_BALANCER_H + +#include // std::shared_ptr +#include // std::vector +#include "butil/containers/flat_map.h" // FlatMap +#include "butil/containers/doubly_buffered_data.h" // DoublyBufferedData +#include "brpc/load_balancer.h" + +namespace brpc { +namespace policy { + +// Power-of-Two-Choices with Peak-EWMA latency scoring ("p2c"). +// Each selection samples `choices'(default 2) distinct servers and routes +// to the one with the lower load score: +// score = peak_ewma_latency_us * (inflight + 1) / weight +// The latency EWMA is peak-sensitive: an upward spike replaces the average +// immediately while recovery decays with time constant `tau_ms'(default 10s), +// so a degraded server is shed within one observation. Selection is O(1) +// regardless of fleet size. Weight is got from tag of ServerId(default 1). +class P2CEwmaLoadBalancer : public LoadBalancer { +public: + P2CEwmaLoadBalancer(); + bool AddServer(const ServerId& id) override; + bool RemoveServer(const ServerId& id) override; + size_t AddServersInBatch(const std::vector& servers) override; + size_t RemoveServersInBatch(const std::vector& servers) override; + int SelectServer(const SelectIn& in, SelectOut* out) override; + void Feedback(const CallInfo& info) override; + P2CEwmaLoadBalancer* New(const butil::StringPiece& params) const override; + void Destroy() override; + void Describe(std::ostream& os, const DescribeOptions&) override; + +private: + // Mutable per-server load state. Allocated once when the server is first + // added and shared by both buffers of _db_servers, so a stable pointer + // can be used from SelectServer()/Feedback() without copying. + struct NodeStat { + NodeStat() : inflight(0), ewma_us(0), stamp_us(0) {} + butil::atomic inflight; + // Peak-sensitive EWMA of latency in us. 0 means no observation yet. + butil::atomic ewma_us; + // Time of the last EWMA update. + butil::atomic stamp_us; + butil::Mutex update_mutex; + }; + struct ServerInfo { + SocketId id; + uint32_t weight; + std::shared_ptr stat; + }; + struct Servers { + std::vector server_list; + // Maps SocketId to index in server_list. + butil::FlatMap server_map; + }; + + static bool Add(Servers& bg, const Servers& fg, const ServerId& id); + static bool Remove(Servers& bg, const ServerId& id); + static size_t BatchAdd(Servers& bg, const Servers& fg, + const std::vector& servers); + static size_t BatchRemove(Servers& bg, const std::vector& servers); + + bool SetParameters(const butil::StringPiece& params); + // Load score of the server at `now_us'. Smaller is better. + double Score(const ServerInfo& info, int64_t now_us) const; + + butil::DoublyBufferedData _db_servers; + uint32_t _choices; + int64_t _tau_us; +}; + +} // namespace policy +} // namespace brpc + +#endif // BRPC_POLICY_P2C_EWMA_LOAD_BALANCER_H diff --git a/src/brpc/policy/public_pbrpc_meta.proto b/src/brpc/policy/public_pbrpc_meta.proto new file mode 100644 index 0000000..f8222a1 --- /dev/null +++ b/src/brpc/policy/public_pbrpc_meta.proto @@ -0,0 +1,61 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; + +package brpc.policy; +option java_package = "com.brpc.policy"; +option java_outer_classname = "PublicPbrpcProto"; + +message PublicPbrpcRequest{ + optional RequestHead requestHead = 1; + repeated RequestBody requestBody = 2; +} +message RequestHead{ + optional string from_host = 1; + optional uint32 content_type = 2; + optional bool connection = 3; + optional string charset = 4; + optional string accept_charset = 5; + optional string create_time = 6; + optional uint64 log_id = 7; + optional uint32 compress_type = 8; +} +message RequestBody{ + optional string version = 1; + optional string charset = 2; + required string service = 3; + required uint32 method_id = 4; + required uint64 id = 5; + optional bytes serialized_request = 6; +} +message PublicPbrpcResponse { + optional ResponseHead responseHead = 1; + repeated ResponseBody responseBody = 2; +} +message ResponseHead{ + required sint32 code = 1; + optional string text = 2; + optional string from_host = 3; + optional uint32 compress_type = 4; +} +message ResponseBody{ + optional bytes serialized_response = 1; + optional string version = 2; + optional int32 error = 3; + required uint64 id = 4; +} diff --git a/src/brpc/policy/public_pbrpc_protocol.cpp b/src/brpc/policy/public_pbrpc_protocol.cpp new file mode 100644 index 0000000..a4298a1 --- /dev/null +++ b/src/brpc/policy/public_pbrpc_protocol.cpp @@ -0,0 +1,287 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // MethodDescriptor +#include // Message +#include +#include "butil/third_party/snappy/snappy.h" // snappy::Compress +#include "butil/time.h" +#include "brpc/controller.h" // Controller +#include "brpc/socket.h" // Socket +#include "brpc/server.h" // Server +#include "brpc/details/server_private_accessor.h" +#include "brpc/span.h" +#include "brpc/compress.h" // ParseFromCompressedData +#include "brpc/details/controller_private_accessor.h" +#include "brpc/policy/public_pbrpc_meta.pb.h" // PublicRpcRequestMeta +#include "brpc/policy/public_pbrpc_protocol.h" +#include "brpc/policy/most_common_message.h" + + +namespace brpc { +namespace policy { + +// Notes on public_pbrpc Protocol: +// 1 - It's based on nshead whose request has special `version' and +// `provider' field. However, these fields are not checked at +// server side +// 2 - The whole nshead body is a single protobuf `PublicPbrpcRequest', +// which contains `RequestHead' and `RequestBody' (no more than 1 +// `RequestBody' although it's repeated). The user protobuf request +// is jammed in `RequestBody::serialized_request'. These rules are +// the same for `PublicPbrpcResponse' +// 3 - Contains unique `id' field in each request/response +// 4 - Lots of fields such as `charset' seem to be useless +// 5 - Only support snappy compression. No authentication + +static const std::string VERSION = "pbrpc=1.0"; +static const std::string CHARSET = "utf-8"; +static const std::string SUCCESS_TEXT = "success"; +static const char* TIME_FORMAT = "%Y%m%d%H%M%S"; +static const char* PROVIDER = "__pbrpc__"; +static const uint32_t CONTENT_TYPE = 1; +static const uint32_t COMPRESS_TYPE = 1; +static const uint32_t NSHEAD_VERSION = 1000; + +void PublicPbrpcServiceAdaptor::ParseNsheadMeta( + const Server& svr, const NsheadMessage& request, Controller* cntl, + NsheadMeta* out_meta) const { + PublicPbrpcRequest pbreq; + if (!ParsePbFromIOBuf(&pbreq, request.body)) { + cntl->CloseConnection("Fail to parse from PublicPbrpcRequest"); + return; + } + if (pbreq.requestbody_size() == 0) { + cntl->CloseConnection("Missing request body inside PublicPbrpcRequest"); + return; + } + const RequestHead& head = pbreq.requesthead(); + const RequestBody& body = pbreq.requestbody(0); + const Server::MethodProperty *sp = ServerPrivateAccessor(&svr) + .FindMethodPropertyByNameAndIndex(body.service(), body.method_id()); + if (NULL == sp) { + cntl->SetFailed(ENOMETHOD, "Fail to find method by service=%s method_id=%u", + body.service().c_str(), body.method_id()); + return; + } + out_meta->set_full_method_name(sp->method->full_name()); + out_meta->set_correlation_id(body.id()); + if (head.has_log_id()) { + out_meta->set_log_id(head.log_id()); + } + if (head.compress_type() == COMPRESS_TYPE) { + out_meta->set_compress_type(COMPRESS_TYPE_SNAPPY); + } + // Store `RequestBody::version' field into `NsheadMeta::user_string' + out_meta->set_user_string(body.version()); + + // HACK! Clear `request.body' and append only protobuf request data into it, + // which will be passed into `ParseRequestFromIOBuf'. As a result, we can + // avoid parsing `PublicPbrpcRequest' twice + NsheadMessage& mutable_req = const_cast(request); + mutable_req.body.clear(); + mutable_req.body.append(body.serialized_request()); +} + +void PublicPbrpcServiceAdaptor::ParseRequestFromIOBuf( + const NsheadMeta& meta, const NsheadMessage& raw_req, + Controller* cntl, google::protobuf::Message* pb_req) const { + CompressType type = meta.compress_type(); + if (!ParseFromCompressedData(raw_req.body, pb_req, type)) { + cntl->SetFailed(EREQUEST, "Fail to parse request message, " + "CompressType=%s, request_size=%" PRIu64, + CompressTypeToCStr(type), + (uint64_t)raw_req.body.length()); + } else { + cntl->set_request_compress_type(type); + } +} + +void PublicPbrpcServiceAdaptor::SerializeResponseToIOBuf( + const NsheadMeta& meta, Controller* cntl, + const google::protobuf::Message* pb_res, NsheadMessage* raw_res) const { + PublicPbrpcResponse whole_res; + ResponseHead* head = whole_res.mutable_responsehead(); + ResponseBody* body = whole_res.add_responsebody(); + + head->set_from_host(butil::ip2str(butil::my_ip()).c_str()); + body->set_version(meta.user_string()); + body->set_id(meta.correlation_id()); + if (cntl->Failed()) { + head->set_code(cntl->ErrorCode()); + head->set_text(cntl->ErrorText()); + } else { + head->set_code(0); + head->set_text(SUCCESS_TEXT); + std::string* response_str = body->mutable_serialized_response(); + if (!pb_res->SerializeToString(response_str)) { + cntl->CloseConnection("Close connection due to failure of " + "serializing user's response"); + return; + } + if (cntl->response_compress_type() == COMPRESS_TYPE_SNAPPY) { + std::string tmp; + butil::snappy::Compress(response_str->data(), response_str->size(), &tmp); + response_str->swap(tmp); + head->set_compress_type(COMPRESS_TYPE); + } + } + butil::IOBufAsZeroCopyOutputStream wrapper(&raw_res->body); + if (!whole_res.SerializeToZeroCopyStream(&wrapper)) { + cntl->CloseConnection("Close connection due to failure of " + "serializing the whole response"); + return; + } +} + +void ProcessPublicPbrpcResponse(InputMessageBase* msg_base) { + const int64_t start_parse_us = butil::cpuwide_time_us(); + DestroyingPtr msg(static_cast(msg_base)); + + PublicPbrpcResponse pbres; + if (!ParsePbFromIOBuf(&pbres, msg->payload)) { + LOG(WARNING) << "Fail to parse from PublicPbrpcResponse"; + return; + } + if (pbres.responsebody_size() == 0) { + LOG(WARNING) << "Missing response body inside PublicPbrpcResponse"; + return; + } + const ResponseHead& head = pbres.responsehead(); + const ResponseBody& body = pbres.responsebody(0); + const bthread_id_t cid = { static_cast(body.id()) }; + Controller* cntl = NULL; + const int rc = bthread_id_lock(cid, (void**)&cntl); + if (rc != 0) { + LOG_IF(ERROR, rc != EINVAL && rc != EPERM) + << "Fail to lock correlation_id=" << cid << ": " << berror(rc); + return; + } + + ControllerPrivateAccessor accessor(cntl); + if (auto span = accessor.span()) { + span->set_base_real_us(msg->base_real_us()); + span->set_received_us(msg->received_us()); + span->set_response_size(msg->meta.size() + msg->payload.size()); + span->set_start_parse_us(start_parse_us); + } + const int saved_error = cntl->ErrorCode(); + if (head.code() != 0) { + // If error_code is unset, default is 0 = success. + cntl->SetFailed(head.code(), "%s", head.text().c_str()); + } else { + // Parse response message iff error code from meta is 0 + const std::string& res_data = body.serialized_response(); + CompressType type = (head.compress_type() == COMPRESS_TYPE ? + COMPRESS_TYPE_SNAPPY : COMPRESS_TYPE_NONE); + bool parse_result = false; + if (type == COMPRESS_TYPE_SNAPPY) { + butil::IOBuf tmp; + tmp.append(res_data); + parse_result = ParseFromCompressedData(tmp, cntl->response(), type); + } else { + parse_result = ParsePbFromString(cntl->response(), res_data); + } + if (!parse_result) { + cntl->SetFailed(ERESPONSE, "Fail to parse response message, " + "CompressType=%s, response_size=%" PRIu64, + CompressTypeToCStr(type), + (uint64_t)res_data.length()); + } else { + cntl->set_response_compress_type(type); + } + } + // Unlocks correlation_id inside. Revert controller's + // error code if it version check of `cid' fails + msg.reset(); // optional, just release resource ASAP + accessor.OnResponse(cid, saved_error); +} + +void SerializePublicPbrpcRequest(butil::IOBuf* buf, Controller* cntl, + const google::protobuf::Message* request) { + CompressType type = cntl->request_compress_type(); + if (type != COMPRESS_TYPE_NONE && type != COMPRESS_TYPE_SNAPPY) { + cntl->SetFailed(EREQUEST, "public_pbrpc doesn't support " + "compress type=%d", type); + return; + } + return SerializeRequestDefault(buf, cntl, request); +} + +void PackPublicPbrpcRequest(butil::IOBuf* buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* controller, + const butil::IOBuf& request, + const Authenticator* /*not supported*/) { + PublicPbrpcRequest pbreq; + RequestHead* head = pbreq.mutable_requesthead(); + RequestBody* body = pbreq.add_requestbody(); + butil::IOBufAsZeroCopyOutputStream request_stream(buf); + + head->set_from_host(butil::ip2str(butil::my_ip()).c_str()); + head->set_content_type(CONTENT_TYPE); + bool short_connection = (controller->connection_type() == CONNECTION_TYPE_SHORT); + head->set_connection(!short_connection); + head->set_charset(CHARSET); + char time_buf[128]; + time_t now = time(NULL); + strftime(time_buf, sizeof(time_buf), TIME_FORMAT, localtime(&now)); + head->set_create_time(time_buf); + if (controller->has_log_id()) { + head->set_log_id(controller->log_id()); + } + if (controller->request_compress_type() == COMPRESS_TYPE_SNAPPY) { + head->set_compress_type(COMPRESS_TYPE); + } + + body->set_version(VERSION); + body->set_charset(CHARSET); + body->set_service(method->service()->name()); + body->set_method_id(method->index()); + body->set_id(correlation_id); + std::string* request_str = body->mutable_serialized_request(); + request.copy_to(request_str); + + nshead_t nshead; + memset(&nshead, 0, sizeof(nshead_t)); + nshead.log_id = controller->log_id(); + nshead.magic_num = NSHEAD_MAGICNUM; + snprintf(nshead.provider, sizeof(nshead.provider), "%s", PROVIDER); + nshead.version = NSHEAD_VERSION; + nshead.body_len = GetProtobufByteSize(pbreq); + buf->append(&nshead, sizeof(nshead)); + + auto span = ControllerPrivateAccessor(controller).span(); + if (span) { + // TODO: Nowhere to set tracing ids. + // request_meta->set_trace_id(span->trace_id()); + // request_meta->set_span_id(span->span_id()); + // request_meta->set_parent_span_id(span->parent_span_id()); + } + + if (!pbreq.SerializeToZeroCopyStream(&request_stream)) { + controller->SetFailed(EREQUEST, "Fail to serialize PublicPbrpcRequest"); + return; + } +} + +} // namespace policy +} // namespace brpc + diff --git a/src/brpc/policy/public_pbrpc_protocol.h b/src/brpc/policy/public_pbrpc_protocol.h new file mode 100644 index 0000000..3e630a5 --- /dev/null +++ b/src/brpc/policy/public_pbrpc_protocol.h @@ -0,0 +1,64 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_PUBLIC_PBRPC_PROTOCOL_H +#define BRPC_POLICY_PUBLIC_PBRPC_PROTOCOL_H + +#include "brpc/nshead_pb_service_adaptor.h" +#include "brpc/policy/nshead_protocol.h" + + +namespace brpc { +namespace policy { + +// Actions to a (server) response in public-pbrpc format. +void ProcessPublicPbrpcResponse(InputMessageBase* msg); + +void SerializePublicPbrpcRequest(butil::IOBuf* buf, Controller* cntl, + const google::protobuf::Message* request); + +// Pack `request' to `method' into `buf'. +void PackPublicPbrpcRequest(butil::IOBuf* buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* controller, + const butil::IOBuf& request, + const Authenticator* auth); + +class PublicPbrpcServiceAdaptor : public NsheadPbServiceAdaptor { +public: + void ParseNsheadMeta( + const Server& svr, const NsheadMessage& request, Controller*, + NsheadMeta* out_meta) const; + + void ParseRequestFromIOBuf( + const NsheadMeta& meta, const NsheadMessage& raw_req, + Controller* controller, google::protobuf::Message* pb_req) const; + + void SerializeResponseToIOBuf( + const NsheadMeta& meta, Controller* controller, + const google::protobuf::Message* pb_res, + NsheadMessage* raw_res) const; +}; + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_PUBLIC_PBRPC_PROTOCOL_H diff --git a/src/brpc/policy/randomized_load_balancer.cpp b/src/brpc/policy/randomized_load_balancer.cpp new file mode 100644 index 0000000..4ff43d7 --- /dev/null +++ b/src/brpc/policy/randomized_load_balancer.cpp @@ -0,0 +1,173 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "butil/macros.h" +#include "butil/fast_rand.h" +#include "bthread/prime_offset.h" +#include "brpc/socket.h" +#include "brpc/policy/randomized_load_balancer.h" +#include "butil/strings/string_number_conversions.h" + +namespace brpc { +namespace policy { + +bool RandomizedLoadBalancer::Add(Servers& bg, const ServerId& id) { + if (bg.server_list.capacity() < 128) { + bg.server_list.reserve(128); + } + std::map::iterator it = bg.server_map.find(id); + if (it != bg.server_map.end()) { + return false; + } + bg.server_map[id] = bg.server_list.size(); + bg.server_list.push_back(id); + return true; +} + +bool RandomizedLoadBalancer::Remove(Servers& bg, const ServerId& id) { + std::map::iterator it = bg.server_map.find(id); + if (it != bg.server_map.end()) { + size_t index = it->second; + bg.server_list[index] = bg.server_list.back(); + bg.server_map[bg.server_list[index]] = index; + bg.server_list.pop_back(); + bg.server_map.erase(it); + return true; + } + return false; +} + +size_t RandomizedLoadBalancer::BatchAdd( + Servers& bg, const std::vector& servers) { + size_t count = 0; + for (size_t i = 0; i < servers.size(); ++i) { + count += !!Add(bg, servers[i]); + } + return count; +} + +size_t RandomizedLoadBalancer::BatchRemove( + Servers& bg, const std::vector& servers) { + size_t count = 0; + for (size_t i = 0; i < servers.size(); ++i) { + count += !!Remove(bg, servers[i]); + } + return count; +} + +bool RandomizedLoadBalancer::AddServer(const ServerId& id) { + return _db_servers.Modify(Add, id); +} + +bool RandomizedLoadBalancer::RemoveServer(const ServerId& id) { + return _db_servers.Modify(Remove, id); +} + +size_t RandomizedLoadBalancer::AddServersInBatch( + const std::vector& servers) { + const size_t n = _db_servers.Modify(BatchAdd, servers); + LOG_IF(ERROR, n != servers.size()) + << "Fail to AddServersInBatch, expected " << servers.size() + << " actually " << n; + return n; +} + +size_t RandomizedLoadBalancer::RemoveServersInBatch( + const std::vector& servers) { + const size_t n = _db_servers.Modify(BatchRemove, servers); + return n; +} + +int RandomizedLoadBalancer::SelectServer(const SelectIn& in, SelectOut* out) { + butil::DoublyBufferedData::ScopedPtr s; + if (_db_servers.Read(&s) != 0) { + return ENOMEM; + } + size_t n = s->server_list.size(); + if (n == 0) { + return ENODATA; + } + if (_cluster_recover_policy && _cluster_recover_policy->StopRecoverIfNecessary()) { + if (_cluster_recover_policy->DoReject(s->server_list)) { + return EREJECT; + } + } + uint32_t stride = 0; + size_t offset = butil::fast_rand_less_than(n); + for (size_t i = 0; i < n; ++i) { + const SocketId id = s->server_list[offset].id; + if (((i + 1) == n // always take last chance + || !ExcludedServers::IsExcluded(in.excluded, id)) + && IsServerAvailable(id, out->ptr)) { + // We found an available server + return 0; + } + if (stride == 0) { + stride = bthread::prime_offset(); + } + // If `Address' failed, use `offset+stride' to retry so that + // this failed server won't be visited again inside for + offset = (offset + stride) % n; + } + if (_cluster_recover_policy) { + _cluster_recover_policy->StartRecover(); + } + // After we traversed the whole server list, there is still no + // available server + return EHOSTDOWN; +} + +RandomizedLoadBalancer* RandomizedLoadBalancer::New( + const butil::StringPiece& params) const { + RandomizedLoadBalancer* lb = new (std::nothrow) RandomizedLoadBalancer; + if (lb && !lb->SetParameters(params)) { + delete lb; + lb = NULL; + } + return lb; +} + +void RandomizedLoadBalancer::Destroy() { + delete this; +} + +void RandomizedLoadBalancer::Describe( + std::ostream &os, const DescribeOptions& options) { + if (!options.verbose) { + os << "random"; + return; + } + os << "Randomized{"; + butil::DoublyBufferedData::ScopedPtr s; + if (_db_servers.Read(&s) != 0) { + os << "fail to read _db_servers"; + } else { + os << "n=" << s->server_list.size() << ':'; + for (size_t i = 0; i < s->server_list.size(); ++i) { + os << ' ' << s->server_list[i]; + } + } + os << '}'; +} + +bool RandomizedLoadBalancer::SetParameters(const butil::StringPiece& params) { + return GetRecoverPolicyByParams(params, &_cluster_recover_policy); +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/randomized_load_balancer.h b/src/brpc/policy/randomized_load_balancer.h new file mode 100644 index 0000000..3787e45 --- /dev/null +++ b/src/brpc/policy/randomized_load_balancer.h @@ -0,0 +1,64 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_RANDOMIZED_LOAD_BALANCER_H +#define BRPC_POLICY_RANDOMIZED_LOAD_BALANCER_H + +#include // std::vector +#include // std::map +#include "butil/containers/doubly_buffered_data.h" +#include "brpc/load_balancer.h" +#include "brpc/cluster_recover_policy.h" + +namespace brpc { +namespace policy { + +// This LoadBalancer selects servers randomly using a thread-specific random +// number. Selected numbers of servers(added at the same time) are less close +// than RoundRobinLoadBalancer. +class RandomizedLoadBalancer : public LoadBalancer { +public: + bool AddServer(const ServerId& id) override; + bool RemoveServer(const ServerId& id) override; + size_t AddServersInBatch(const std::vector& servers) override; + size_t RemoveServersInBatch(const std::vector& servers) override; + int SelectServer(const SelectIn& in, SelectOut* out) override; + RandomizedLoadBalancer* New(const butil::StringPiece&) const override; + void Destroy() override; + void Describe(std::ostream& os, const DescribeOptions&) override; + +private: + struct Servers { + std::vector server_list; + std::map server_map; + }; + bool SetParameters(const butil::StringPiece& params); + static bool Add(Servers& bg, const ServerId& id); + static bool Remove(Servers& bg, const ServerId& id); + static size_t BatchAdd(Servers& bg, const std::vector& servers); + static size_t BatchRemove(Servers& bg, const std::vector& servers); + + butil::DoublyBufferedData _db_servers; + std::shared_ptr _cluster_recover_policy; +}; + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_RANDOMIZED_LOAD_BALANCER_H diff --git a/src/brpc/policy/redis_authenticator.cpp b/src/brpc/policy/redis_authenticator.cpp new file mode 100644 index 0000000..152d983 --- /dev/null +++ b/src/brpc/policy/redis_authenticator.cpp @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/redis_authenticator.h" + +#include "butil/base64.h" +#include "butil/iobuf.h" +#include "butil/string_printf.h" +#include "butil/sys_byteorder.h" +#include "brpc/redis_command.h" + +namespace brpc { +namespace policy { + +int RedisAuthenticator::GenerateCredential(std::string* auth_str) const { + butil::IOBuf buf; + if (!passwd_.empty()) { + brpc::RedisCommandFormat(&buf, "AUTH %s", passwd_.c_str()); + } + if (db_ >= 0) { + brpc::RedisCommandFormat(&buf, "SELECT %d", db_); + } + *auth_str = buf.to_string(); + return 0; +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/redis_authenticator.h b/src/brpc/policy/redis_authenticator.h new file mode 100644 index 0000000..8359811 --- /dev/null +++ b/src/brpc/policy/redis_authenticator.h @@ -0,0 +1,59 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_POLICY_REDIS_AUTHENTICATOR_H +#define BRPC_POLICY_REDIS_AUTHENTICATOR_H + +#include "brpc/authenticator.h" + +namespace brpc { +namespace policy { + +// Request to redis for authentication. +class RedisAuthenticator : public Authenticator { +public: + RedisAuthenticator(const std::string& passwd, int db = -1) + : passwd_(passwd), db_(db) {} + + int GenerateCredential(std::string* auth_str) const; + + int VerifyCredential(const std::string&, const butil::EndPoint&, + brpc::AuthContext*) const { + return 0; + } + + uint32_t GetAuthFlags() const { + uint32_t n = 0; + if (!passwd_.empty()) { + ++n; + } + if (db_ >= 0) { + ++n; + } + return n; + } + +private: + const std::string passwd_; + + int db_; +}; + +} // namespace policy +} // namespace brpc + +#endif // BRPC_POLICY_COUCHBASE_AUTHENTICATOR_H diff --git a/src/brpc/policy/redis_protocol.cpp b/src/brpc/policy/redis_protocol.cpp new file mode 100644 index 0000000..7dc5b5b --- /dev/null +++ b/src/brpc/policy/redis_protocol.cpp @@ -0,0 +1,333 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Jiashun Zhu(zhujiashun2010@gmail.com) + +#include // MethodDescriptor +#include // Message +#include +#include "brpc/policy/redis_authenticator.h" +#include "butil/logging.h" // LOG() +#include "butil/time.h" +#include "butil/iobuf.h" // butil::IOBuf +#include "brpc/controller.h" // Controller +#include "brpc/details/controller_private_accessor.h" +#include "brpc/socket.h" // Socket +#include "brpc/server.h" // Server +#include "brpc/details/server_private_accessor.h" +#include "brpc/span.h" +#include "brpc/redis.h" +#include "brpc/redis_command.h" +#include "brpc/policy/redis_protocol.h" + +namespace brpc { + +DECLARE_bool(enable_rpcz); +DECLARE_bool(usercode_in_pthread); + +namespace policy { + +DEFINE_bool(redis_verbose, false, + "[DEBUG] Print EVERY redis request/response"); + +struct InputResponse : public InputMessageBase { + bthread_id_t id_wait; + RedisResponse response; + + // @InputMessageBase + void DestroyImpl() { + delete this; + } +}; + + +int ConsumeCommand(RedisConnContext* ctx, + const std::vector& args, + bool flush_batched, + butil::IOBufAppender* appender) { + RedisReply output(&ctx->arena); + RedisCommandHandlerResult result = REDIS_CMD_HANDLED; + if (ctx->transaction_handler) { + result = ctx->transaction_handler->Run(ctx, args, &output, flush_batched); + if (result == REDIS_CMD_HANDLED) { + ctx->transaction_handler.reset(NULL); + } else if (result == REDIS_CMD_BATCHED) { + LOG(ERROR) << "BATCHED should not be returned by a transaction handler."; + return -1; + } + } else { + RedisCommandHandler* ch = ctx->redis_service->FindCommandHandler(args[0]); + if (!ch) { + char buf[64]; + snprintf(buf, sizeof(buf), "ERR unknown command `%s`", args[0].as_string().c_str()); + output.SetError(buf); + } else { + result = ch->Run(ctx, args, &output, flush_batched); + if (result == REDIS_CMD_CONTINUE) { + if (ctx->batched_size != 0) { + LOG(ERROR) << "CONTINUE should not be returned in a batched process."; + return -1; + } + ctx->transaction_handler.reset(ch->NewTransactionHandler()); + } else if (result == REDIS_CMD_BATCHED) { + ctx->batched_size++; + } + } + } + if (result == REDIS_CMD_HANDLED) { + if (ctx->batched_size) { + if ((int)output.size() != (ctx->batched_size + 1)) { + LOG(ERROR) << "reply array size can't be matched with batched size, " + << " expected=" << ctx->batched_size + 1 << " actual=" << output.size(); + return -1; + } + for (int i = 0; i < (int)output.size(); ++i) { + output[i].SerializeTo(appender); + } + ctx->batched_size = 0; + } else { + output.SerializeTo(appender); + } + } else if (result == REDIS_CMD_CONTINUE) { + output.SerializeTo(appender); + } else if (result == REDIS_CMD_BATCHED) { + // just do nothing and wait handler to return OK. + } else { + LOG(ERROR) << "unknown status=" << result; + return -1; + } + return 0; +} + + +ParseResult ParseRedisMessage(butil::IOBuf* source, Socket* socket, + bool read_eof, const void* arg) { + if (read_eof || source->empty()) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + const Server* server = static_cast(arg); + if (server) { + const RedisService* const rs = server->options().redis_service; + if (!rs) { + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + RedisConnContext* ctx = static_cast(socket->parsing_context()); + if (ctx == NULL) { + ctx = new RedisConnContext(rs); + socket->reset_parsing_context(ctx); + } + std::vector current_args; + butil::IOBufAppender appender; + ParseError err = PARSE_OK; + + err = ctx->parser.Consume(*source, ¤t_args, &ctx->arena); + if (err != PARSE_OK) { + return MakeParseError(err); + } + while (true) { + std::vector next_args; + err = ctx->parser.Consume(*source, &next_args, &ctx->arena); + if (err != PARSE_OK) { + break; + } + if (ConsumeCommand(ctx, current_args, false, &appender) != 0) { + return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG); + } + current_args.swap(next_args); + } + if (ConsumeCommand(ctx, current_args, + true /*must be the last message*/, &appender) != 0) { + return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG); + } + butil::IOBuf sendbuf; + appender.move_to(sendbuf); + CHECK(!sendbuf.empty()); + Socket::WriteOptions wopt; + wopt.ignore_eovercrowded = true; + LOG_IF(WARNING, socket->Write(&sendbuf, &wopt) != 0) + << "Fail to send redis reply"; + if(ctx->parser.ParsedArgsSize() == 0) { + ctx->arena.clear(); + } + return MakeParseError(err); + } else { + // NOTE(gejun): PopPipelinedInfo() is actually more contended than what + // I thought before. The Socket._pipeline_q is a SPSC queue pushed before + // sending and popped when response comes back, being protected by a + // mutex. Previously the mutex is shared with Socket._id_wait_list. When + // 200 bthreads access one redis-server, ~1.5s in total is spent on + // contention in 10-second duration. If the mutex is separated, the time + // drops to ~0.25s. I further replaced PeekPipelinedInfo() with + // GivebackPipelinedInfo() to lock only once(when receiving response) + // in most cases, and the time decreases to ~0.14s. + PipelinedInfo pi; + if (!socket->PopPipelinedInfo(&pi)) { + LOG(WARNING) << "No corresponding PipelinedInfo in socket"; + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + + do { + InputResponse* msg = static_cast(socket->parsing_context()); + if (msg == NULL) { + msg = new InputResponse; + socket->reset_parsing_context(msg); + } + + const int consume_count = (pi.auth_flags ? pi.auth_flags : pi.count); + + ParseError err = msg->response.ConsumePartialIOBuf(*source, consume_count); + if (err != PARSE_OK) { + socket->GivebackPipelinedInfo(pi); + return MakeParseError(err); + } + + if (pi.auth_flags) { + for (int i = 0; i < (int)pi.auth_flags; ++i) { + if (i >= msg->response.reply_size() || + !(msg->response.reply(i).type() == + brpc::REDIS_REPLY_STATUS && + msg->response.reply(i).data().compare("OK") == 0)) { + LOG(ERROR) << "Redis Auth failed: " << msg->response; + return MakeParseError(PARSE_ERROR_NO_RESOURCE, + "Fail to authenticate with Redis"); + } + } + + DestroyingPtr auth_msg( + static_cast(socket->release_parsing_context())); + pi.auth_flags = 0; + continue; + } + + CHECK_EQ((uint32_t)msg->response.reply_size(), pi.count); + msg->id_wait = pi.id_wait; + socket->release_parsing_context(); + return MakeMessage(msg); + } while(true); + } + + return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG); +} + +void ProcessRedisResponse(InputMessageBase* msg_base) { + const int64_t start_parse_us = butil::cpuwide_time_us(); + DestroyingPtr msg(static_cast(msg_base)); + + const bthread_id_t cid = msg->id_wait; + Controller* cntl = NULL; + const int rc = bthread_id_lock(cid, (void**)&cntl); + if (rc != 0) { + LOG_IF(ERROR, rc != EINVAL && rc != EPERM) + << "Fail to lock correlation_id=" << cid << ": " << berror(rc); + return; + } + + ControllerPrivateAccessor accessor(cntl); + if (auto span = accessor.span()) { + span->set_base_real_us(msg->base_real_us()); + span->set_received_us(msg->received_us()); + span->set_response_size(msg->response.ByteSize()); + span->set_start_parse_us(start_parse_us); + } + const int saved_error = cntl->ErrorCode(); + if (cntl->response() != NULL) { + if (cntl->response()->GetDescriptor() != RedisResponse::descriptor()) { + cntl->SetFailed(ERESPONSE, "Must be RedisResponse"); + } else { + // We work around ParseFrom of pb which is just a placeholder. + if (msg->response.reply_size() != (int)accessor.pipelined_count()) { + cntl->SetFailed(ERESPONSE, "pipelined_count=%d of response does " + "not equal request's=%d", + msg->response.reply_size(), accessor.pipelined_count()); + } + ((RedisResponse*)cntl->response())->Swap(&msg->response); + if (FLAGS_redis_verbose) { + LOG(INFO) << "\n[REDIS RESPONSE] " + << *((RedisResponse*)cntl->response()); + } + } + } // silently ignore the response. + + // Unlocks correlation_id inside. Revert controller's + // error code if it version check of `cid' fails + msg.reset(); // optional, just release resource ASAP + accessor.OnResponse(cid, saved_error); +} + +void ProcessRedisRequest(InputMessageBase* msg_base) { } + +void SerializeRedisRequest(butil::IOBuf* buf, + Controller* cntl, + const google::protobuf::Message* request) { + if (request == NULL) { + return cntl->SetFailed(EREQUEST, "request is NULL"); + } + if (request->GetDescriptor() != RedisRequest::descriptor()) { + return cntl->SetFailed(EREQUEST, "The request is not a RedisRequest"); + } + const RedisRequest* rr = (const RedisRequest*)request; + // If redis byte size is zero, brpc call will fail with E22. Continuous E22 may cause E112 in the end. + // So set failed and return useful error message + if (GetProtobufByteSize(*rr) == 0) { + return cntl->SetFailed(EREQUEST, "request byte size is empty"); + } + // We work around SerializeTo of pb which is just a placeholder. + if (!rr->SerializeTo(buf)) { + return cntl->SetFailed(EREQUEST, "Fail to serialize RedisRequest"); + } + ControllerPrivateAccessor(cntl).set_pipelined_count(rr->command_size()); + if (FLAGS_redis_verbose) { + LOG(INFO) << "\n[REDIS REQUEST] " << *rr; + } +} + +void PackRedisRequest(butil::IOBuf* buf, + SocketMessage**, + uint64_t /*correlation_id*/, + const google::protobuf::MethodDescriptor*, + Controller* cntl, + const butil::IOBuf& request, + const Authenticator* auth) { + if (auth) { + std::string auth_str; + if (auth->GenerateCredential(&auth_str) != 0) { + return cntl->SetFailed(EREQUEST, "Fail to generate credential"); + } + buf->append(auth_str); + const RedisAuthenticator* redis_auth = + dynamic_cast(auth); + if (redis_auth == NULL) { + return cntl->SetFailed(EREQUEST, "Fail to generate credential"); + } + ControllerPrivateAccessor(cntl).set_auth_flags( + redis_auth->GetAuthFlags()); + } else { + ControllerPrivateAccessor(cntl).clear_auth_flags(); + } + + buf->append(request); +} + +const std::string& GetRedisMethodName( + const google::protobuf::MethodDescriptor*, + const Controller*) { + const static std::string REDIS_SERVER_STR = "redis-server"; + return REDIS_SERVER_STR; +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/redis_protocol.h b/src/brpc/policy/redis_protocol.h new file mode 100644 index 0000000..b3f7184 --- /dev/null +++ b/src/brpc/policy/redis_protocol.h @@ -0,0 +1,64 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_REDIS_PROTOCOL_H +#define BRPC_POLICY_REDIS_PROTOCOL_H + +#include "brpc/protocol.h" + + +namespace brpc { +namespace policy { + +// Parse redis response. +ParseResult ParseRedisMessage(butil::IOBuf* source, Socket *socket, bool read_eof, + const void *arg); + +// Actions to a redis response. +void ProcessRedisResponse(InputMessageBase* msg); + +// Actions to a redis request, which is left unimplemented. +// All requests are processed in execution queue pushed in +// the parsing process. This function must be declared since +// server only enables redis as a server-side protocol when +// this function is declared. +void ProcessRedisRequest(InputMessageBase* msg); + +// Serialize a redis request. +void SerializeRedisRequest(butil::IOBuf* buf, + Controller* cntl, + const google::protobuf::Message* request); + +// Pack `request' to `method' into `buf'. +void PackRedisRequest(butil::IOBuf* buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* controller, + const butil::IOBuf& request, + const Authenticator* auth); + +const std::string& GetRedisMethodName( + const google::protobuf::MethodDescriptor*, + const Controller*); + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_REDIS_PROTOCOL_H diff --git a/src/brpc/policy/remote_file_naming_service.cpp b/src/brpc/policy/remote_file_naming_service.cpp new file mode 100644 index 0000000..c5aeac9 --- /dev/null +++ b/src/brpc/policy/remote_file_naming_service.cpp @@ -0,0 +1,163 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include // getline +#include // std::string +#include // std::set +#include "bthread/bthread.h" // bthread_usleep +#include "butil/iobuf.h" +#include "brpc/log.h" +#include "brpc/channel.h" +#include "brpc/policy/remote_file_naming_service.h" + + +namespace brpc { +namespace policy { + +DEFINE_int32(remote_file_connect_timeout_ms, -1, + "Timeout for creating connections to fetch remote server lists," + " set to remote_file_timeout_ms/3 by default (-1)"); +DEFINE_int32(remote_file_timeout_ms, 1000, + "Timeout for fetching remote server lists"); + +// Defined in file_naming_service.cpp +bool SplitIntoServerAndTag(const butil::StringPiece& line, + butil::StringPiece* server_addr, + butil::StringPiece* tag); + +static bool CutLineFromIOBuf(butil::IOBuf* source, std::string* line_out) { + if (source->empty()) { + return false; + } + butil::IOBuf line_data; + if (source->cut_until(&line_data, "\n") != 0) { + source->cutn(line_out, source->size()); + return true; + } + line_data.copy_to(line_out); + if (!line_out->empty() && butil::back_char(*line_out) == '\r') { + line_out->resize(line_out->size() - 1); + } + return true; +} + +int RemoteFileNamingService::GetServers(const char *service_name_cstr, + std::vector* servers) { + servers->clear(); + + if (_channel == NULL) { + butil::StringPiece tmpname(service_name_cstr); + size_t pos = tmpname.find("://"); + butil::StringPiece proto; + if (pos != butil::StringPiece::npos) { + proto = tmpname.substr(0, pos); + for (pos += 3; tmpname[pos] == '/'; ++pos) {} + tmpname.remove_prefix(pos); + } else { + proto = "http"; + } + if (proto != "bns" && proto != "http") { + LOG(ERROR) << "Invalid protocol=`" << proto + << "\' in service_name=" << service_name_cstr; + return -1; + } + size_t slash_pos = tmpname.find('/'); + butil::StringPiece server_addr_piece; + if (slash_pos == butil::StringPiece::npos) { + server_addr_piece = tmpname; + _path = "/"; + } else { + server_addr_piece = tmpname.substr(0, slash_pos); + _path = tmpname.substr(slash_pos).as_string(); + } + _server_addr.reserve(proto.size() + 3 + server_addr_piece.size()); + _server_addr.append(proto.data(), proto.size()); + _server_addr.append("://"); + _server_addr.append(server_addr_piece.data(), server_addr_piece.size()); + ChannelOptions opt; + opt.protocol = PROTOCOL_HTTP; + opt.connect_timeout_ms = FLAGS_remote_file_connect_timeout_ms > 0 ? + FLAGS_remote_file_connect_timeout_ms : FLAGS_remote_file_timeout_ms / 3; + opt.timeout_ms = FLAGS_remote_file_timeout_ms; + std::unique_ptr chan(new Channel); + if (chan->Init(_server_addr.c_str(), "rr", &opt) != 0) { + LOG(ERROR) << "Fail to init channel to " << _server_addr; + return -1; + } + _channel.swap(chan); + } + + Controller cntl; + cntl.http_request().uri() = _path; + _channel->CallMethod(NULL, &cntl, NULL, NULL, NULL); + if (cntl.Failed()) { + LOG(WARNING) << "Fail to access " << _server_addr << _path << ": " + << cntl.ErrorText(); + return -1; + } + std::string line; + // Sort/unique the inserted vector is faster, but may have a different order + // of addresses from the file. To make assertions in tests easier, we use + // set to de-duplicate and keep the order. + std::set presence; + + while (CutLineFromIOBuf(&cntl.response_attachment(), &line)) { + butil::StringPiece addr; + butil::StringPiece tag; + if (!SplitIntoServerAndTag(line, &addr, &tag)) { + continue; + } + const_cast(addr.data())[addr.size()] = '\0'; // safe + butil::EndPoint point; + if (str2endpoint(addr.data(), &point) != 0 && + hostname2endpoint(addr.data(), &point) != 0) { + LOG(ERROR) << "Invalid address=`" << addr << '\''; + continue; + } + ServerNode node; + node.addr = point; + tag.CopyToString(&node.tag); + if (presence.insert(node).second) { + servers->push_back(node); + } else { + RPC_VLOG << "Duplicated server=" << node; + } + } + RPC_VLOG << "Got " << servers->size() + << (servers->size() > 1 ? " servers" : " server") + << " from " << service_name_cstr; + return 0; +} + +void RemoteFileNamingService::Describe(std::ostream& os, + const DescribeOptions&) const { + os << "remotefile"; + return; +} + +NamingService* RemoteFileNamingService::New() const { + return new RemoteFileNamingService; +} + +void RemoteFileNamingService::Destroy() { + delete this; +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/remote_file_naming_service.h b/src/brpc/policy/remote_file_naming_service.h new file mode 100644 index 0000000..ab4bed9 --- /dev/null +++ b/src/brpc/policy/remote_file_naming_service.h @@ -0,0 +1,52 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_HTTP_FILE_NAMING_SERVICE +#define BRPC_POLICY_HTTP_FILE_NAMING_SERVICE + +#include "brpc/periodic_naming_service.h" +#include "brpc/channel.h" +#include "butil/unique_ptr.h" + + +namespace brpc { +class Channel; +namespace policy { + +class RemoteFileNamingService : public PeriodicNamingService { +private: + int GetServers(const char* service_name, + std::vector* servers) override; + + void Describe(std::ostream& os, const DescribeOptions&) const override; + + NamingService* New() const override; + + void Destroy() override; + +private: + std::unique_ptr _channel; + std::string _server_addr; + std::string _path; +}; + +} // namespace policy +} // namespace brpc + + +#endif //BRPC_POLICY_HTTP_FILE_NAMING_SERVICE diff --git a/src/brpc/policy/round_robin_load_balancer.cpp b/src/brpc/policy/round_robin_load_balancer.cpp new file mode 100644 index 0000000..cf67624 --- /dev/null +++ b/src/brpc/policy/round_robin_load_balancer.cpp @@ -0,0 +1,173 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "butil/macros.h" +#include "butil/fast_rand.h" +#include "bthread/prime_offset.h" +#include "brpc/socket.h" +#include "brpc/policy/round_robin_load_balancer.h" + + +namespace brpc { +namespace policy { + +bool RoundRobinLoadBalancer::Add(Servers& bg, const ServerId& id) { + if (bg.server_list.capacity() < 128) { + bg.server_list.reserve(128); + } + std::map::iterator it = bg.server_map.find(id); + if (it != bg.server_map.end()) { + return false; + } + bg.server_map[id] = bg.server_list.size(); + bg.server_list.push_back(id); + return true; +} + +bool RoundRobinLoadBalancer::Remove(Servers& bg, const ServerId& id) { + std::map::iterator it = bg.server_map.find(id); + if (it != bg.server_map.end()) { + const size_t index = it->second; + bg.server_list[index] = bg.server_list.back(); + bg.server_map[bg.server_list[index]] = index; + bg.server_list.pop_back(); + bg.server_map.erase(it); + return true; + } + return false; +} + +size_t RoundRobinLoadBalancer::BatchAdd( + Servers& bg, const std::vector& servers) { + size_t count = 0; + for (size_t i = 0; i < servers.size(); ++i) { + count += !!Add(bg, servers[i]); + } + return count; +} + +size_t RoundRobinLoadBalancer::BatchRemove( + Servers& bg, const std::vector& servers) { + size_t count = 0; + for (size_t i = 0; i < servers.size(); ++i) { + count += !!Remove(bg, servers[i]); + } + return count; +} + +bool RoundRobinLoadBalancer::AddServer(const ServerId& id) { + return _db_servers.Modify(Add, id); +} + +bool RoundRobinLoadBalancer::RemoveServer(const ServerId& id) { + return _db_servers.Modify(Remove, id); +} + +size_t RoundRobinLoadBalancer::AddServersInBatch( + const std::vector& servers) { + const size_t n = _db_servers.Modify(BatchAdd, servers); + LOG_IF(ERROR, n != servers.size()) + << "Fail to AddServersInBatch, expected " << servers.size() + << " actually " << n; + return n; +} + +size_t RoundRobinLoadBalancer::RemoveServersInBatch( + const std::vector& servers) { + const size_t n = _db_servers.Modify(BatchRemove, servers); + return n; +} + +int RoundRobinLoadBalancer::SelectServer(const SelectIn& in, SelectOut* out) { + butil::DoublyBufferedData::ScopedPtr s; + if (_db_servers.Read(&s) != 0) { + return ENOMEM; + } + const size_t n = s->server_list.size(); + if (n == 0) { + return ENODATA; + } + if (_cluster_recover_policy && _cluster_recover_policy->StopRecoverIfNecessary()) { + if (_cluster_recover_policy->DoReject(s->server_list)) { + return EREJECT; + } + } + TLS tls = s.tls(); + if (tls.stride == 0) { + tls.stride = bthread::prime_offset(); + // use random at first time, for the case of + // use rr lb every time in new thread + tls.offset = butil::fast_rand_less_than(n); + } + + for (size_t i = 0; i < n; ++i) { + tls.offset = (tls.offset + tls.stride) % n; + const SocketId id = s->server_list[tls.offset].id; + if (((i + 1) == n // always take last chance + || !ExcludedServers::IsExcluded(in.excluded, id)) + && IsServerAvailable(id, out->ptr)) { + s.tls() = tls; + return 0; + } + } + if (_cluster_recover_policy) { + _cluster_recover_policy->StartRecover(); + } + s.tls() = tls; + return EHOSTDOWN; +} + +RoundRobinLoadBalancer* RoundRobinLoadBalancer::New( + const butil::StringPiece& params) const { + RoundRobinLoadBalancer* lb = new (std::nothrow) RoundRobinLoadBalancer; + if (lb && !lb->SetParameters(params)) { + delete lb; + lb = NULL; + } + return lb; +} + +void RoundRobinLoadBalancer::Destroy() { + delete this; +} + +void RoundRobinLoadBalancer::Describe( + std::ostream &os, const DescribeOptions& options) { + if (!options.verbose) { + os << "rr"; + return; + } + os << "RoundRobin{"; + butil::DoublyBufferedData::ScopedPtr s; + if (_db_servers.Read(&s) != 0) { + os << "fail to read _db_servers"; + } else { + os << "n=" << s->server_list.size() << ':'; + for (size_t i = 0; i < s->server_list.size(); ++i) { + os << ' ' << s->server_list[i]; + } + } + os << '}'; +} + +bool RoundRobinLoadBalancer::SetParameters(const butil::StringPiece& params) { + return GetRecoverPolicyByParams(params, &_cluster_recover_policy); +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/round_robin_load_balancer.h b/src/brpc/policy/round_robin_load_balancer.h new file mode 100644 index 0000000..f087dcd --- /dev/null +++ b/src/brpc/policy/round_robin_load_balancer.h @@ -0,0 +1,68 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_ROUND_ROBIN_LOAD_BALANCER_H +#define BRPC_POLICY_ROUND_ROBIN_LOAD_BALANCER_H + +#include // std::vector +#include // std::map +#include "butil/containers/doubly_buffered_data.h" +#include "brpc/load_balancer.h" +#include "brpc/cluster_recover_policy.h" + +namespace brpc { +namespace policy { + +// This LoadBalancer selects server evenly. Selected numbers of servers(added +// at the same time) are very close. +class RoundRobinLoadBalancer : public LoadBalancer { +public: + bool AddServer(const ServerId& id) override; + bool RemoveServer(const ServerId& id) override; + size_t AddServersInBatch(const std::vector& servers) override; + size_t RemoveServersInBatch(const std::vector& servers) override; + int SelectServer(const SelectIn& in, SelectOut* out) override; + RoundRobinLoadBalancer* New(const butil::StringPiece&) const override; + void Destroy() override; + void Describe(std::ostream&, const DescribeOptions& options) override; + +private: + struct Servers { + std::vector server_list; + std::map server_map; + }; + struct TLS { + TLS() : stride(0), offset(0) { } + uint32_t stride; + uint32_t offset; + }; + bool SetParameters(const butil::StringPiece& params); + static bool Add(Servers& bg, const ServerId& id); + static bool Remove(Servers& bg, const ServerId& id); + static size_t BatchAdd(Servers& bg, const std::vector& servers); + static size_t BatchRemove(Servers& bg, const std::vector& servers); + + butil::DoublyBufferedData _db_servers; + std::shared_ptr _cluster_recover_policy; +}; + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_ROUND_ROBIN_LOAD_BALANCER_H diff --git a/src/brpc/policy/rtmp_protocol.cpp b/src/brpc/policy/rtmp_protocol.cpp new file mode 100644 index 0000000..6232201 --- /dev/null +++ b/src/brpc/policy/rtmp_protocol.cpp @@ -0,0 +1,3682 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // HMAC_CTX_init +#include +#include "butil/scoped_lock.h" +#include "butil/fast_rand.h" +#include "butil/sys_byteorder.h" +#include "brpc/log.h" +#include "brpc/server.h" +#include "brpc/details/controller_private_accessor.h" +#include "brpc/details/server_private_accessor.h" +#include "brpc/span.h" +#include "brpc/policy/dh.h" +#include "brpc/policy/rtmp_protocol.h" + +// For printing logs with useful prefixes. +#define RTMP_LOG(level, socket, mh) \ + LOG(level) << (socket)->remote_side() << '[' << (mh).stream_id << "] " +#define RTMP_ERROR(socket, mh) RTMP_LOG(ERROR, (socket), (mh)) +#define RTMP_WARNING(socket, mh) RTMP_LOG(WARNING, (socket), (mh)) + +// Older openssl does not have EVP_sha256. To make the code always compile, +// we mark the symbol as weak. If the runtime does not have the function, +// handshaking will fallback to the simple one. +extern "C" { +const EVP_MD* BAIDU_WEAK EVP_sha256(void); +} + + +namespace brpc { + +DECLARE_int64(socket_max_unwritten_bytes); +DECLARE_bool(use_normal_stack_for_keepwrite); + +DEFINE_int32(rtmp_server_chunk_size, 60000, + "Value of SetChunkSize sent to client before responding connect."); +DEFINE_int32(rtmp_server_window_ack_size, 2500000, + "Value of WindowAckSize sent to client before responding connect."); + +DEFINE_bool(rtmp_client_use_simple_handshake, true, + "Use simple handshaking(the one in RTMP spec) to create client " + "connections, false to use adobe proprietary handshake which " + "consumes more CPU"); +DEFINE_string(user_defined_data_message, "", + "extra name that user can specify in Data Message of RTMP, handled by OnMetaData"); + +namespace policy { + +// Used in rtmp.cpp, don't be static +int WriteWithoutOvercrowded(Socket* s, SocketMessagePtr<>& msg) { + Socket::WriteOptions wopt; + wopt.ignore_eovercrowded = true; + return s->Write(msg, &wopt); +} + +const char* messagetype2str(RtmpMessageType t) { + switch (t) { + case RTMP_MESSAGE_SET_CHUNK_SIZE: return "SetChunkSize"; + case RTMP_MESSAGE_ABORT: return "AbortMessage"; + case RTMP_MESSAGE_ACK: return "Ack"; + case RTMP_MESSAGE_USER_CONTROL: return "UserControlMessage"; + case RTMP_MESSAGE_WINDOW_ACK_SIZE: return "WindowAckSize"; + case RTMP_MESSAGE_SET_PEER_BANDWIDTH: return "SetPeerBandwidth"; + case RTMP_MESSAGE_AUDIO: return "AudioMessage"; + case RTMP_MESSAGE_VIDEO: return "VideoMessage"; + case RTMP_MESSAGE_DATA_AMF3: return "DataMessage_AMF3"; + case RTMP_MESSAGE_SHARED_OBJECT_AMF3: return "SharedObjectMessage_AMF3"; + case RTMP_MESSAGE_COMMAND_AMF3: return "CommandMessage_AMF3"; + case RTMP_MESSAGE_DATA_AMF0: return "DataMessage_AMF0"; + case RTMP_MESSAGE_SHARED_OBJECT_AMF0: return "SharedObjectMessage_AMF0"; + case RTMP_MESSAGE_COMMAND_AMF0: return "CommandMessage_AMF0"; + case RTMP_MESSAGE_AGGREGATE: return "AggregateMessage"; + } + return "Unknown RtmpMessageType"; +} + +const char* messagetype2str(uint8_t t) { + return messagetype2str((RtmpMessageType)t); +} + +// Unchangable constants required by RTMP +static const uint32_t RTMP_INITIAL_CHUNK_SIZE = 128; +static const uint8_t RTMP_DEFAULT_VERSION = 3; +static const size_t RTMP_HANDSHAKE_SIZE0 = 1; +static const size_t RTMP_HANDSHAKE_SIZE1 = 1536; +static const size_t RTMP_HANDSHAKE_SIZE2 = RTMP_HANDSHAKE_SIZE1; +static const char* const SIMPLIFIED_RTMP_MAGIC_NUMBER = "BDMS"; +static const size_t MAGIC_NUMBER_SIZE = 4; /* magic number */ + +// ========== The handshaking described in RTMP spec ========== + +// The random data for handshaking +static butil::IOBuf* s_rtmp_handshake_server_random = NULL; +static pthread_once_t s_sr_once = PTHREAD_ONCE_INIT; +static void InitRtmpHandshakeServerRandom() { + char buf[1528]; + for (int i = 0; i < 191; ++i) { + ((uint64_t*)buf)[i] = butil::fast_rand(); + } + s_rtmp_handshake_server_random = new butil::IOBuf; + s_rtmp_handshake_server_random->append(buf, sizeof(buf)); +} +static const butil::IOBuf& GetRtmpHandshakeServerRandom() { + pthread_once(&s_sr_once, InitRtmpHandshakeServerRandom); + return *s_rtmp_handshake_server_random; +} + +static butil::IOBuf* s_rtmp_handshake_client_random = NULL; +static pthread_once_t s_cr_once = PTHREAD_ONCE_INIT; +static void InitRtmpHandshakeClientRandom() { + char buf[1528]; + for (int i = 0; i < 191; ++i) { + ((uint64_t*)buf)[i] = butil::fast_rand(); + } + s_rtmp_handshake_client_random = new butil::IOBuf; + s_rtmp_handshake_client_random->append(buf, sizeof(buf)); +} +static const butil::IOBuf& GetRtmpHandshakeClientRandom() { + pthread_once(&s_cr_once, InitRtmpHandshakeClientRandom); + return *s_rtmp_handshake_client_random; +} + +// For timestamps in simple handshaking. +static uint32_t GetRtmpTimestamp() { + return 0; +} + +// ========== Proprietary handshaking used by Adobe ========= +namespace adobe_hs { + +// Modified from code in SRS2 (src/protocol/srs_rtmp_handshake.cpp:94) +int openssl_HMACsha256(const void* key, int key_size, + const void* data, int data_size, void* digest) { + if (NULL == EVP_sha256) { + LOG_ONCE(ERROR) << "Fail to find EVP_sha256, fall back to simple handshaking"; + return -1; + } + unsigned int digest_size = 0; + unsigned char* temp_digest = (unsigned char*)digest; + if (key == NULL) { + // NOTE: first parameter of EVP_Digest in older openssl is void*. + if (EVP_Digest(const_cast(data), data_size, temp_digest, + &digest_size, EVP_sha256(), NULL) < 0) { + LOG(ERROR) << "Fail to EVP_Digest"; + return -1; + } + } else { + // Note: following code uses HMAC_CTX previously which is ABI + // inconsistent in different version of openssl. + if (HMAC(EVP_sha256(), key, key_size, + (const unsigned char*) data, data_size, + temp_digest, &digest_size) == NULL) { + LOG(ERROR) << "Fail to HMAC"; + return -1; + } + } + if (digest_size != 32) { + LOG(ERROR) << "digest_size=" << digest_size << " of sha256 is not 32"; + return -1; + } + return 0; +} + +// 68bytes FMS key for signing the sever packets. +static const uint8_t GenuineFMSKey[] = { + 0x47, 0x65, 0x6e, 0x75, 0x69, 0x6e, 0x65, 0x20, + 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x46, 0x6c, + 0x61, 0x73, 0x68, 0x20, 0x4d, 0x65, 0x64, 0x69, + 0x61, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Media Server 001 + 0xf0, 0xee, 0xc2, 0x4a, 0x80, 0x68, 0xbe, 0xe8, + 0x2e, 0x00, 0xd0, 0xd1, 0x02, 0x9e, 0x7e, 0x57, + 0x6e, 0xec, 0x5d, 0x2d, 0x29, 0x80, 0x6f, 0xab, + 0x93, 0xb8, 0xe6, 0x36, 0xcf, 0xeb, 0x31, 0xae +}; // 68 + +// 62bytes FlashPlayer key for signing the client packets. +static const uint8_t GenuineFPKey[] = { + 0x47, 0x65, 0x6E, 0x75, 0x69, 0x6E, 0x65, 0x20, + 0x41, 0x64, 0x6F, 0x62, 0x65, 0x20, 0x46, 0x6C, + 0x61, 0x73, 0x68, 0x20, 0x50, 0x6C, 0x61, 0x79, + 0x65, 0x72, 0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Player 001 + 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, + 0x2E, 0x00, 0xD0, 0xD1, 0x02, 0x9E, 0x7E, 0x57, + 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, + 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE +}; // 62 + +static const uint32_t FP_VERSION = 0x80000702; +static const uint32_t FMS_VERSION = 0x01000504; + +// A structure inside C1 or S1 +class KeyBlock { +public: + static const int SIZE = 764; + static const int KEY_SIZE = 128; + + void Generate(); + void Load(const void* buf); // `buf' must be at least SIZE bytes + void Save(void* buf) const; // ^ + + const void* random0() const { return _buf; } + int random0_size() const { return _offset; } + const void* random1() const { return _buf + _offset + KEY_SIZE; } + int random1_size() const { return SIZE - KEY_SIZE - 4 - _offset; } + const void* key() const { return _buf + _offset; } + void* key() { return _buf + _offset; } + +private: + uint32_t _offset; + uint32_t _offset_data; + char _buf[SIZE - 4]; +}; + +// A structure inside C1 or S1 +class DigestBlock { +public: + static const int SIZE = 764; + static const int DIGEST_SIZE = 32; + + void Generate(); + void Load(const void* buf); // `buf' must be at least SIZE bytes + void Save(void* buf) const; // ^ + // `buf' must be at least SIZE - DIGEST_SIZE bytes + void SaveWithoutDigest(void* buf) const; + + const void* random0() const { return _buf; } + int random0_size() const { return _offset; } + const void* random1() const { return _buf + _offset + DIGEST_SIZE; } + int random1_size() const { return SIZE - DIGEST_SIZE - 4 - _offset; } + const void* digest() const { return _buf + _offset; } + void* digest() { return _buf + _offset; } + +private: + uint32_t _offset; + uint32_t _offset_data; + char _buf[SIZE - 4]; +}; + +enum C1S1Schema { INVALID_SCHEMA, SCHEMA0, SCHEMA1 }; + +// Common part of C1 and S1. +class C1S1Base { +public: + static const int SIZE = 1536; + C1S1Base() : _schema(INVALID_SCHEMA) {} + bool Save(void* buf) const; + C1S1Schema schema() const { return _schema; } +protected: + bool ComputeDigestBase(const void* key, int key_size, void* digest) const; + C1S1Schema _schema; +public: + uint32_t time; + uint32_t version; + KeyBlock key_blk; + DigestBlock digest_blk; +}; + +class C1 : public C1S1Base { +public: + bool Generate(C1S1Schema schema); + bool Load(const void* buf); +protected: + bool ComputeDigest(void* digest) const + { return ComputeDigestBase(GenuineFPKey, 30, digest); } +}; + +class S1 : public C1S1Base { +public: + bool Generate(const C1&); + bool Load(const void* buf, C1S1Schema schema); +protected: + bool ComputeDigest(void* digest) const + { return ComputeDigestBase(GenuineFMSKey, 36, digest); } +}; + +// The common part of C2 and S2. +class C2S2Base { +public: + static const int SIZE = 1536; + static const int DIGEST_SIZE = 32; + bool Generate(const void* key, int key_size, const void* c1s1_digest); + bool Load(const void* key, int key_size, + const void* c1s1_digest, const void* buf); + void Save(void* buf) const; +private: + bool ComputeDigest(const void* key, int key_size, + const void* c1s1_digest, void* digest) const; +public: + char random[SIZE - DIGEST_SIZE]; + char digest[DIGEST_SIZE]; +}; + +class C2 : public C2S2Base { +public: + bool Generate(const void* s1_digest) + { return C2S2Base::Generate(GenuineFPKey, 62, s1_digest); } + bool Load(const void* s1_digest, const void* buf) + { return C2S2Base::Load(GenuineFPKey, 62, s1_digest, buf); } +}; + +class S2 : public C2S2Base { +public: + bool Generate(const void* c1_digest) + { return C2S2Base::Generate(GenuineFMSKey, 68, c1_digest); } + bool Load(const void* c1_digest, const void* buf) + { return C2S2Base::Load(GenuineFMSKey, 68, c1_digest, buf); } +}; + +// ===== impl. ===== +void KeyBlock::Generate() { + _offset_data = butil::fast_rand() & 0xFFFFFFFF; + const uint8_t* p = (const uint8_t*)&_offset_data; + _offset = ((uint32_t)p[0] + p[1] + p[2] + p[3]) % (SIZE - KEY_SIZE - 4); + for (size_t i = 0; i < sizeof(_buf) / 8; ++i) { + ((uint64_t*)_buf)[i] = butil::fast_rand(); + } +} + +void KeyBlock::Load(const void* buf) { + // Layout of key (764 bytes) + // random-data: `offset' bytes + // key-data: 128 bytes + // random-data: 764 - `offset' - 128 - 4 bytes + // offset: 4 bytes + _offset_data = ReadBigEndian4Bytes((const char*)buf + SIZE - 4); + const uint8_t* p = (const uint8_t*)&_offset_data; + _offset = ((uint32_t)p[0] + p[1] + p[2] + p[3]) % (SIZE - KEY_SIZE - 4); + memcpy(_buf, buf, SIZE - 4); +} + +void KeyBlock::Save(void* buf) const { + memcpy(buf, _buf, SIZE - 4); + char* p = static_cast(buf) + SIZE - 4; + WriteBigEndian4Bytes(&p, _offset_data); +} + +void DigestBlock::Generate() { + _offset_data = butil::fast_rand() & 0xFFFFFFFF; + const uint8_t* p = (const uint8_t*)&_offset_data; + _offset = ((uint32_t)p[0] + p[1] + p[2] + p[3]) % (SIZE - DIGEST_SIZE - 4); + for (size_t i = 0; i < sizeof(_buf) / 8; ++i) { + ((uint64_t*)_buf)[i] = butil::fast_rand(); + } +} + +void DigestBlock::Load(const void* buf) { + // Layout of digest (764 bytes) + // offset: 4 bytes + // random-data: `offset' bytes + // digest-data: 32 bytes + // random-data: 764 - 4 - `offset' - 32 bytes + _offset_data = ReadBigEndian4Bytes(buf); + const uint8_t* p = (const uint8_t*)&_offset_data; + _offset = ((uint32_t)p[0] + p[1] + p[2] + p[3]) % (SIZE - DIGEST_SIZE - 4); + memcpy(_buf, (const char*)buf + 4, SIZE - 4); +} + +void DigestBlock::Save(void* buf) const { + char* p = static_cast(buf); + WriteBigEndian4Bytes(&p, _offset_data); + memcpy(p, _buf, SIZE - 4); +} + +void DigestBlock::SaveWithoutDigest(void* buf) const { + char* p = static_cast(buf); + WriteBigEndian4Bytes(&p, _offset_data); + memcpy(p, random0(), random0_size()); + p += random0_size(); + memcpy(p, random1(), random1_size()); +} + +bool C1S1Base::Save(void* buf) const { + char* p = static_cast(buf); + WriteBigEndian4Bytes(&p, time); + WriteBigEndian4Bytes(&p, version); + if (_schema == SCHEMA0) { + key_blk.Save(p); + digest_blk.Save(p + KeyBlock::SIZE); + return true; + } else if (_schema == SCHEMA1) { + digest_blk.Save(p); + key_blk.Save(p + DigestBlock::SIZE); + return true; + } else { + CHECK(false) << "Invalid schema=" << (int)_schema; + return false; + } +} + +bool C1S1Base::ComputeDigestBase(const void* key, int key_size, + void* digest) const { + char buf[SIZE - DigestBlock::DIGEST_SIZE]; + char* p = buf; + WriteBigEndian4Bytes(&p, time); + WriteBigEndian4Bytes(&p, version); + if (_schema == SCHEMA0) { + key_blk.Save(p); + digest_blk.SaveWithoutDigest(p + KeyBlock::SIZE); + } else if (_schema == SCHEMA1) { + digest_blk.SaveWithoutDigest(p); + key_blk.Save(p + DigestBlock::SIZE - DigestBlock::DIGEST_SIZE); + } else { + LOG(ERROR) << "Invalid schema=" << (int)_schema; + return false; + } + char tmp_digest[EVP_MAX_MD_SIZE]; + if (openssl_HMACsha256(key, key_size, buf, sizeof(buf), tmp_digest) != 0) { + LOG(WARNING) << "Fail to compute digest of C1/S1"; + return false; + } + memcpy(digest, tmp_digest, DigestBlock::DIGEST_SIZE); + return true; +} + +bool C1::Generate(C1S1Schema schema) { + _schema = schema; + time = ::time(NULL); + version = FP_VERSION; + key_blk.Generate(); + digest_blk.Generate(); + return ComputeDigest(digest_blk.digest()); +} + +bool C1::Load(const void* buf) { + const uint8_t* const p = static_cast(buf); + time = ReadBigEndian4Bytes(p); + version = ReadBigEndian4Bytes(p + 4); + // Try schema0 (key before digest) first + _schema = SCHEMA0; + key_blk.Load(p + 8); + digest_blk.Load(p + 8 + KeyBlock::SIZE); + char tmp_digest[DigestBlock::DIGEST_SIZE]; + if (!ComputeDigest(tmp_digest)) { + LOG(WARNING) << "Fail to compute digest of C1 (schema0)"; + return false; + } + if (memcmp(tmp_digest, digest_blk.digest(), + DigestBlock::DIGEST_SIZE) == 0) { + return true; + } + // Try schema1 (digest before key) + _schema = SCHEMA1; + digest_blk.Load(p + 8); + key_blk.Load(p + 8 + DigestBlock::SIZE); + if (!ComputeDigest(tmp_digest)) { + LOG(WARNING) << "Fail to compute digest of C1 (schema1)"; + return false; + } + if (memcmp(tmp_digest, digest_blk.digest(), + DigestBlock::DIGEST_SIZE) == 0) { + return true; + } + _schema = INVALID_SCHEMA; + return false; +} + +bool S1::Generate(const C1& c1) { + _schema = c1.schema(); + time = ::time(NULL); + version = FMS_VERSION; + key_blk.Generate(); + digest_blk.Generate(); + + DHWrapper dh; + if (dh.initialize(true) != 0) { // tool ~1.1ms + return false; + } + int pkey_size = 128; + if (dh.copy_shared_key(c1.key_blk.key(), 128, + key_blk.key(), &pkey_size) != 0) { // took ~0.9ms + LOG(ERROR) << "Fail to compute key of S1"; + return false; + } + return ComputeDigest(digest_blk.digest()); +} + +bool S1::Load(const void* buf, C1S1Schema schema) { + const uint8_t* const p = static_cast(buf); + _schema = schema; + time = ReadBigEndian4Bytes(p); + version = ReadBigEndian4Bytes(p + 4); + if (_schema == SCHEMA0) { + key_blk.Load(p + 8); + digest_blk.Load(p + 8 + KeyBlock::SIZE); + } else if (_schema == SCHEMA1) { + digest_blk.Load(p + 8); + key_blk.Load(p + 8 + DigestBlock::SIZE); + } + char tmp_digest[DigestBlock::DIGEST_SIZE]; + if (!ComputeDigest(tmp_digest)) { + LOG(WARNING) << "Fail to compute digest of S1"; + return false; + } + if (memcmp(tmp_digest, digest_blk.digest(), + DigestBlock::DIGEST_SIZE) != 0) { + return false; + } + return true; +} + +bool C2S2Base::ComputeDigest(const void* key, int key_size, + const void* c1s1_digest, void* digest) const { + char temp_key[EVP_MAX_MD_SIZE]; + if (openssl_HMACsha256(key, key_size, c1s1_digest, DigestBlock::DIGEST_SIZE, + temp_key) != 0) { + LOG(WARNING) << "Fail to create temp key"; + return false; + } + char tmp_digest[EVP_MAX_MD_SIZE]; + if (openssl_HMACsha256(temp_key, 32, random, SIZE - DIGEST_SIZE, + tmp_digest) != 0) { + LOG(WARNING) << "Fail to create temp digest"; + return false; + } + memcpy(digest, tmp_digest, 32); + return true; +} + +bool C2S2Base::Generate(const void* key, int key_size, + const void* c1s1_digest) { + for (int i = 0; i < (SIZE - DIGEST_SIZE) / 8; ++i) { + ((uint64_t*)random)[i] = butil::fast_rand(); + } + return ComputeDigest(key, key_size, c1s1_digest, digest); +} + +bool C2S2Base::Load(const void* key, int key_size, const void* c1s1_digest, + const void* buf) { + memcpy(random, buf, SIZE - DIGEST_SIZE); + memcpy(digest, (const char*)buf + SIZE - DIGEST_SIZE, DIGEST_SIZE); + char tmp_digest[DIGEST_SIZE]; + if (!ComputeDigest(key, key_size, c1s1_digest, tmp_digest)) { + LOG(WARNING) << "Fail to compute digest of C2/S2"; + return false; + } + return memcmp(tmp_digest, digest, DIGEST_SIZE) == 0; +} + +void C2S2Base::Save(void* buf) const { + memcpy(buf, random, SIZE - DIGEST_SIZE); + memcpy(static_cast(buf) + SIZE - DIGEST_SIZE, digest, DIGEST_SIZE); +} + +} // namespace adobe_hs + +// Get size of the basic header according to chunk stream id. +inline size_t GetBasicHeaderLength(uint32_t cs_id) { + if (cs_id < 2) { + return 0; + } else if (cs_id <= 63) { + return 1; + } else if (cs_id <= 319) { + return 2; + } else if (cs_id <= RTMP_MAX_CHUNK_STREAM_ID) { + return 3; + } else { + return 0; + } +} + +// Write a basic header into buf and forward the buf. +static void +WriteBasicHeader(char** buf, RtmpChunkType chunk_type, uint32_t cs_id) { + char* out = *buf; + if (cs_id < 2) { + CHECK(false) << "Reserved chunk_stream_id=" << cs_id; + } else if (cs_id <= 63) { + *out++ = (((uint32_t)chunk_type << 6) | cs_id); + } else if (cs_id <= 319) { + *out++ = ((uint32_t)chunk_type << 6); + *out++ = cs_id - 64; + } else if (cs_id <= RTMP_MAX_CHUNK_STREAM_ID) { + *out++ = (((uint32_t)chunk_type << 6) | 1); + *out++ = (cs_id - 64) & 0xFF; + *out++ = ((cs_id - 64) >> 8); + } else { + CHECK(false) << "Invalid chunk_stream_id=" << cs_id; + } + *buf = out; +} + +// Write all data in *buf into fd. +// Returns 0 on success, -1 otherwise. +// Writing *all* is possible because we only call this fn during handshaking +// and connecting, the data in total is generally less than socket buffer. +static int WriteAll(int fd, butil::IOBuf* buf) { + while (!buf->empty()) { + ssize_t nw = buf->cut_into_file_descriptor(fd); + if (nw < 0) { + if (errno == EINTR) { + continue; + } + if (errno == EAGAIN) { + // Almost impossible because we only call this fn during + // RTMP handshaking/connecting, the total size that we may + // write is less than buffer size of the fd. If the + // impossible really happens, just spin until the fd becomes + // writable. + LOG_EVERY_SECOND(ERROR) << "Impossible: meet EAGAIN!"; + bthread_usleep(1000); + continue; + } + return -1; + } + } + return 0; +} + +// Send C0 C1 to the socket. +// Used in rtmp.cpp +int SendC0C1(int fd, bool* is_simple_handshake) { + bool done_adobe_hs = false; + butil::IOBuf tmp; + if (!FLAGS_rtmp_client_use_simple_handshake) { + adobe_hs::C1 c1; + if (c1.Generate(adobe_hs::SCHEMA1)) { + char buf[RTMP_HANDSHAKE_SIZE0 + RTMP_HANDSHAKE_SIZE1]; + buf[0] = RTMP_DEFAULT_VERSION; + c1.Save(buf + RTMP_HANDSHAKE_SIZE0); + tmp.append(buf, sizeof(buf)); + done_adobe_hs = true; + } else { + LOG(WARNING) << "Fail to generate C1, use simple handshaking"; + } + } + if (is_simple_handshake) { + *is_simple_handshake = !done_adobe_hs; + } + if (!done_adobe_hs) { + char buf[9]; + char* p = buf; + *p++ = RTMP_DEFAULT_VERSION; // c0 (version) + WriteBigEndian4Bytes(&p, GetRtmpTimestamp()); // c1.time + WriteBigEndian4Bytes(&p, 0); // c1.zero + tmp.append(buf, sizeof(buf)); + tmp.append(GetRtmpHandshakeClientRandom()); + } + return WriteAll(fd, &tmp); +} + +const char* RtmpContext::state2str(State s) { + switch (s) { + case STATE_UNINITIALIZED: return "STATE_UNINITIALIZED"; + case STATE_RECEIVED_S0S1: return "STATE_RECEIVED_S0S1"; + case STATE_RECEIVED_S2: return "STATE_RECEIVED_S2"; + case STATE_RECEIVED_C0C1: return "STATE_RECEIVED_C0C1"; + case STATE_RECEIVED_C2: return "STATE_RECEIVED_C2"; + } + return "Unknown state"; +} + +void RtmpContext::SetState(const butil::EndPoint& remote_side, State new_state) { + const State old_state = _state; + _state = new_state; + RPC_VLOG << remote_side << ": " << state2str(old_state) + << " -> " << state2str(new_state); +} + +RtmpUnsentMessage* MakeUnsentControlMessage( + uint8_t message_type, uint32_t chunk_stream_id, + const void* data, size_t n) { + RtmpUnsentMessage* msg = new RtmpUnsentMessage; + msg->header.message_length = n; + msg->header.message_type = message_type; + msg->header.stream_id = RTMP_CONTROL_MESSAGE_STREAM_ID; + msg->chunk_stream_id = chunk_stream_id; + msg->body.append(data, n); + return msg; +} + +RtmpUnsentMessage* MakeUnsentControlMessage( + uint8_t message_type, const void* data, size_t n) { + return MakeUnsentControlMessage( + message_type, RTMP_CONTROL_CHUNK_STREAM_ID, data, n); +} + +RtmpUnsentMessage* MakeUnsentControlMessage( + uint8_t message_type, uint32_t chunk_stream_id, const butil::IOBuf& body) { + RtmpUnsentMessage* msg = new RtmpUnsentMessage; + msg->header.message_length = body.size(); + msg->header.message_type = message_type; + msg->header.stream_id = RTMP_CONTROL_MESSAGE_STREAM_ID; + msg->chunk_stream_id = chunk_stream_id; + msg->body = body; + return msg; +} + +RtmpUnsentMessage* MakeUnsentControlMessage( + uint8_t message_type, const butil::IOBuf& body) { + return MakeUnsentControlMessage( + message_type, RTMP_CONTROL_CHUNK_STREAM_ID, body); +} + +RtmpContext::RtmpContext(const RtmpClientOptions* copt, const Server* server) + : _state(RtmpContext::STATE_UNINITIALIZED) + , _s1_digest(NULL) + , _chunk_size_out(RTMP_INITIAL_CHUNK_SIZE) + , _chunk_size_in(RTMP_INITIAL_CHUNK_SIZE) + , _window_ack_size(RTMP_DEFAULT_WINDOW_ACK_SIZE) + , _nonack_bytes(0) + , _received_bytes(0) + , _cs_id_allocator(RTMP_CONTROL_CHUNK_STREAM_ID + 1) + , _ms_id_allocator(RTMP_CONTROL_MESSAGE_STREAM_ID + 1) + , _client_options(copt) + , _on_connect(NULL) + , _on_connect_arg(NULL) + , _only_check_simple_s0s1(false) + , _create_stream_with_play_or_publish(false) + , _server(server) + , _service(NULL) + , _trans_id_allocator(2) + , _simplified_rtmp(false) { + if (server) { + _service = server->options().rtmp_service; + } + _free_ms_ids.reserve(32); + if (_mstream_map.init(1024, 70) != 0) { + LOG(FATAL) << "Fail to initialize _mstream_map"; + } + if (_trans_map.init(1024, 70) != 0) { + LOG(FATAL) << "Fail to initialize _trans_map"; + } + memset(static_cast(_cstream_ctx), 0, sizeof(_cstream_ctx)); +} + +RtmpContext::~RtmpContext() { + if (!_mstream_map.empty()) { + size_t ncstream = 0; + size_t nsstream = 0; + for (butil::FlatMap::iterator + it = _mstream_map.begin(); it != _mstream_map.end(); ++it) { + if (it->second.stream->is_server_stream()) { + ++nsstream; + } else { + ++ncstream; + } + } + _mstream_map.clear(); + LOG(FATAL) << "RtmpContext=" << this << " is deallocated" + " before all streams(" << ncstream << " client, " << nsstream + << "server) on the connection quit"; + } + + // cancel incomplete transactions + for (butil::FlatMap::iterator + it = _trans_map.begin(); it != _trans_map.end(); ++it) { + if (it->second) { + it->second->Cancel(); + } + } + _trans_map.clear(); + + // Delete chunk streams + for (size_t i = 0; i < RTMP_CHUNK_ARRAY_1ST_SIZE; ++i) { + SubChunkArray* p = _cstream_ctx[i].load(butil::memory_order_relaxed); + if (p) { + _cstream_ctx[i].store(NULL, butil::memory_order_relaxed); + delete p; + } + } + + free(_s1_digest); + _s1_digest = NULL; +} + +void RtmpContext::Destroy() { + delete this; +} + +butil::Status +RtmpUnsentMessage::AppendAndDestroySelf(butil::IOBuf* out, Socket* s) { + std::unique_ptr destroy_self(this); + if (s == NULL) { // abandoned + RPC_VLOG << "Socket=NULL"; + return butil::Status::OK(); + } + RtmpContext* ctx = static_cast(s->parsing_context()); + RtmpChunkStream* cstream = ctx->GetChunkStream(chunk_stream_id); + if (cstream == NULL) { + s->SetFailed(EINVAL, "Invalid chunk_stream_id=%u", chunk_stream_id); + return butil::Status(EINVAL, "Invalid chunk_stream_id=%u", chunk_stream_id); + } + if (cstream->SerializeMessage(out, header, &body) != 0) { + s->SetFailed(EINVAL, "Fail to serialize message"); + return butil::Status(EINVAL, "Fail to serialize message"); + } + if (new_chunk_size) { + ctx->_chunk_size_out = new_chunk_size; + } + if (!next) { + return butil::Status::OK(); + } + RtmpUnsentMessage* p = next.release(); + destroy_self.reset(); + return p->AppendAndDestroySelf(out, s); +} + +RtmpContext::SubChunkArray::SubChunkArray() { + memset(static_cast(ptrs), 0, sizeof(ptrs)); +} + +RtmpContext::SubChunkArray::~SubChunkArray() { + for (size_t i = 0; i < RTMP_CHUNK_ARRAY_2ND_SIZE; ++i) { + RtmpChunkStream* stream = ptrs[i].load(butil::memory_order_relaxed); + if (stream) { + ptrs[i].store(NULL, butil::memory_order_relaxed); + delete stream; + } + } +} + +RtmpChunkStream* RtmpContext::GetChunkStream(uint32_t cs_id) { + if (cs_id > RTMP_MAX_CHUNK_STREAM_ID) { + LOG(ERROR) << "Invalid chunk_stream_id=" << cs_id; + return NULL; + } + const uint32_t index1 = cs_id / RTMP_CHUNK_ARRAY_2ND_SIZE; + SubChunkArray* sub_array = + _cstream_ctx[index1].load(butil::memory_order_consume); + if (sub_array == NULL) { + // Optimistic creation. + sub_array = new SubChunkArray; + SubChunkArray* expected = NULL; + if (!_cstream_ctx[index1].compare_exchange_strong( + expected, sub_array, butil::memory_order_acq_rel)) { + delete sub_array; + sub_array = expected; + } + } + const uint32_t index2 = cs_id - index1 * RTMP_CHUNK_ARRAY_2ND_SIZE; + RtmpChunkStream* cstream = + sub_array->ptrs[index2].load(butil::memory_order_consume); + if (cstream == NULL) { + // Optimistic creation. + cstream = new RtmpChunkStream(this, cs_id); + RtmpChunkStream* expected = NULL; + if (!sub_array->ptrs[index2].compare_exchange_strong( + expected, cstream, butil::memory_order_acq_rel)) { + delete cstream; + cstream = expected; + } + } + return cstream; +} + +void RtmpContext::ClearChunkStream(uint32_t cs_id) { + if (cs_id > RTMP_MAX_CHUNK_STREAM_ID) { + LOG(ERROR) << "Invalid chunk_stream_id=" << cs_id; + return; + } + const uint32_t index1 = cs_id / RTMP_CHUNK_ARRAY_2ND_SIZE; + SubChunkArray* sub_array = + _cstream_ctx[index1].load(butil::memory_order_consume); + if (sub_array == NULL) { + LOG(ERROR) << "chunk_stream_id=" << cs_id << " does not exist"; + return; + } + const uint32_t index2 = cs_id - index1 * RTMP_CHUNK_ARRAY_2ND_SIZE; + RtmpChunkStream* cstream = + sub_array->ptrs[index2].load(butil::memory_order_consume); + if (cstream == NULL) { + LOG(ERROR) << "chunk_stream_id=" << cs_id << " does not exist"; + return; + } + delete sub_array->ptrs[index2].exchange( + NULL, butil::memory_order_acquire); +} + +void RtmpContext::AllocateChunkStreamId(uint32_t* chunk_stream_id) { + if (!_free_cs_ids.empty()) { + *chunk_stream_id = _free_cs_ids.back(); + _free_cs_ids.pop_back(); + return; + } + *chunk_stream_id = _cs_id_allocator++; + if (_cs_id_allocator > RTMP_MAX_CHUNK_STREAM_ID) { + _cs_id_allocator = RTMP_CONTROL_CHUNK_STREAM_ID + 1; + } +} + +void RtmpContext::DeallocateChunkStreamId(uint32_t chunk_stream_id) { + // NOTE: duplicated id may be pushed into _free_cs_ids, not affecting correctness. + _free_cs_ids.push_back(chunk_stream_id); +} + +bool RtmpContext::AllocateMessageStreamId(uint32_t* stream_id) { + if (!_free_ms_ids.empty()) { + *stream_id = _free_ms_ids.back(); + _free_ms_ids.pop_back(); + return true; + } + if (_ms_id_allocator != std::numeric_limits::max()) { + *stream_id = _ms_id_allocator++; + return true; + } + return false; +} + +void RtmpContext::DeallocateMessageStreamId(uint32_t stream_id) { + _free_ms_ids.push_back(stream_id); +} + +bool RtmpContext::FindMessageStream( + uint32_t stream_id, butil::intrusive_ptr* stream) { + BAIDU_SCOPED_LOCK(_stream_mutex); + MessageStreamInfo* info = _mstream_map.seek(stream_id); + if (info == NULL || info->stream == NULL) { + return false; + } + *stream = info->stream; + return true; +} + +bool RtmpContext::AddClientStream(RtmpStreamBase* stream) { + const uint32_t stream_id = stream->stream_id(); + if (stream_id == RTMP_CONTROL_MESSAGE_STREAM_ID) { + LOG(ERROR) << "stream_id=" << RTMP_CONTROL_MESSAGE_STREAM_ID + << " is reserved for control stream"; + return false; + } + uint32_t chunk_stream_id = 0; + { + std::unique_lock mu(_stream_mutex); + MessageStreamInfo& info = _mstream_map[stream_id]; + if (info.stream != NULL) { + mu.unlock(); + LOG(ERROR) << "stream_id=" << stream_id << " is already used"; + return false; + } + AllocateChunkStreamId(&chunk_stream_id); + info.stream.reset(stream); + } + stream->_chunk_stream_id = chunk_stream_id; + return true; +} + +bool RtmpContext::AddServerStream(RtmpStreamBase* stream) { + uint32_t stream_id = 0; + { + std::unique_lock mu(_stream_mutex); + if (!AllocateMessageStreamId(&stream_id)) { + return false; + } + MessageStreamInfo& info = _mstream_map[stream_id]; + if (info.stream != NULL) { + mu.unlock(); + LOG(ERROR) << "stream_id=" << stream_id << " is already used"; + return false; + } + info.stream.reset(stream); + } + stream->_message_stream_id = stream_id; + stream->_chunk_stream_id = 0; + return true; +} + +bool RtmpContext::RemoveMessageStream(RtmpStreamBase* stream) { + if (stream == NULL) { + LOG(FATAL) << "Param[stream] is NULL"; + return false; + } + const uint32_t stream_id = stream->stream_id(); + if (stream_id == RTMP_CONTROL_MESSAGE_STREAM_ID) { + LOG(FATAL) << "stream_id=" << RTMP_CONTROL_MESSAGE_STREAM_ID + << " is reserved for control stream"; + return false; + } + // for deref the stream outside _stream_mutex. + butil::intrusive_ptr deref_ptr; + { + std::unique_lock mu(_stream_mutex); + MessageStreamInfo* info = _mstream_map.seek(stream_id); + if (info == NULL) { + mu.unlock(); + return false; + } + if (stream != info->stream) { + mu.unlock(); + LOG(FATAL) << "Unmatched " + << (stream->is_client_stream() ? "client" : "server") + << " stream of stream_id=" << stream_id; + return false; + } + if (stream->is_client_stream()) { + DeallocateChunkStreamId(stream->_chunk_stream_id); + } else { // server-side + DeallocateMessageStreamId(stream_id); + } + deref_ptr.swap(info->stream); + _mstream_map.erase(stream_id); + } + return true; +} + +// The transactionID is for createStream and RPC-call in RTMP which is +// infrequent in most cases, thus we just use a map protected with a mutex +// to allocate the id. To lower the possibility of ABA issues, we increase +// transaction id on each allocation. To jump over crowded area fastly, we +// double the increment of `_trans_id_allocator' on each try. +bool RtmpContext::AddTransaction(uint32_t* out_transaction_id, + RtmpTransactionHandler* handler) { + uint32_t transaction_id = 0; + uint32_t step = 1; + BAIDU_SCOPED_LOCK(_trans_mutex); + for (int i = 0; i < 10; ++i) { + transaction_id = _trans_id_allocator; + _trans_id_allocator += step; + if (transaction_id < 2) { // 0 and 1 are reserved by rtmp spec. + continue; + } + step *= 2; // 1,2,4,8,16,32,64,128,256,512,1024 + if (_trans_map.seek(transaction_id) == NULL) { + _trans_map[transaction_id] = handler; + *out_transaction_id = transaction_id; + return true; + } + } + return false; +} + +RtmpTransactionHandler* +RtmpContext::RemoveTransaction(uint32_t transaction_id) { + RtmpTransactionHandler* handler = NULL; + { + BAIDU_SCOPED_LOCK(_trans_mutex); + RtmpTransactionHandler** phandler = _trans_map.seek(transaction_id); + if (phandler != NULL) { + handler = *phandler; + _trans_map.erase(transaction_id); + } + } + return handler; +} + +int RtmpContext::SendConnectRequest(const butil::EndPoint& remote_side, int fd, bool simplified_rtmp) { + butil::IOBuf req_buf; + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + WriteAMFString(RTMP_AMF0_COMMAND_CONNECT, &ostream); + WriteAMFUint32(1, &ostream); + RtmpConnectRequest req; + if (_client_options->app.empty()) { + LOG(ERROR) << "RtmpClientOptions.app must be set"; + return -1; + } + req.set_app(_client_options->app); + if (!_client_options->flashVer.empty()) { + req.set_flashver(_client_options->flashVer); + } + if (!_client_options->swfUrl.empty()) { + req.set_swfurl(_client_options->swfUrl); + } + // formulate tcUrl + if (_client_options->tcUrl.empty()) { + std::string* const tcurl = req.mutable_tcurl(); + tcurl->reserve(32 + _client_options->app.size()); + tcurl->append("rtmp://"); + tcurl->append(butil::endpoint2str(remote_side).c_str()); + tcurl->push_back('/'); + tcurl->append(_client_options->app); + } else { + req.set_tcurl(_client_options->tcUrl); + } + req.set_fpad(_client_options->fpad); + req.set_capabilities(239); // Copy from SRS + req.set_audiocodecs(_client_options->audioCodecs); + req.set_videocodecs(_client_options->videoCodecs); + req.set_videofunction(_client_options->videoFunction); + if (!_client_options->pageUrl.empty()) { + req.set_pageurl(_client_options->pageUrl); + } + req.set_objectencoding(RTMP_AMF0); + req.set_stream_multiplexing(true); + WriteAMFObject(req, &ostream); + if (!ostream.good()) { + LOG(ERROR) << "Fail to serialize connect request"; + return -1; + } + } + RtmpMessageHeader header; + header.message_length = req_buf.size(); + header.message_type = RTMP_MESSAGE_COMMAND_AMF0; + header.stream_id = RTMP_CONTROL_MESSAGE_STREAM_ID; + + butil::IOBuf msg_buf; + if (simplified_rtmp) { + char buf[5]; + char* p = buf; + *p++ = RTMP_DEFAULT_VERSION; + memcpy(p, SIMPLIFIED_RTMP_MAGIC_NUMBER, MAGIC_NUMBER_SIZE); + msg_buf.append(buf, sizeof(buf)); + } + RtmpChunkStream* cstream = GetChunkStream(RTMP_CONTROL_CHUNK_STREAM_ID); + if (cstream->SerializeMessage(&msg_buf, header, &req_buf) != 0) { + LOG(ERROR) << "Fail to serialize connect message"; + return -1; + } + + // WindowAckSize + { + char cntl_buf[4]; + char* p = cntl_buf; + WriteBigEndian4Bytes(&p, _client_options->window_ack_size); + RtmpMessageHeader header2; + header2.message_length = sizeof(cntl_buf); + header2.message_type = RTMP_MESSAGE_WINDOW_ACK_SIZE; + header2.stream_id = RTMP_CONTROL_MESSAGE_STREAM_ID; + butil::IOBuf tmp; + tmp.append(cntl_buf, sizeof(cntl_buf)); + if (cstream->SerializeMessage(&msg_buf, header2, &tmp) != 0) { + LOG(ERROR) << "Fail to serialize WindowAckSize message"; + return -1; + } + } + + // SetChunkSize + { + char cntl_buf[4]; + char* p = cntl_buf; + WriteBigEndian4Bytes(&p, _client_options->chunk_size); + RtmpMessageHeader header3; + header3.message_length = sizeof(cntl_buf); + header3.message_type = RTMP_MESSAGE_SET_CHUNK_SIZE; + header3.stream_id = RTMP_CONTROL_MESSAGE_STREAM_ID; + butil::IOBuf tmp; + tmp.append(cntl_buf, sizeof(cntl_buf)); + if (cstream->SerializeMessage(&msg_buf, header3, &tmp) != 0) { + LOG(ERROR) << "Fail to serialize SetChunkSize message"; + return -1; + } + _chunk_size_out = _client_options->chunk_size; + } + + return WriteAll(fd, &msg_buf); +} + +ParseResult RtmpContext::Feed(butil::IOBuf* source, Socket* socket) { + switch (_state) { + case STATE_UNINITIALIZED: + if (socket->CreatedByConnect()) { + return WaitForS0S1(source, socket); + } else { + return WaitForC0C1orSimpleRtmp(source, socket); + } + case STATE_RECEIVED_S0S1: + return WaitForS2(source, socket); + case STATE_RECEIVED_C0C1: + return WaitForC2(source, socket); + case STATE_RECEIVED_S2: + case STATE_RECEIVED_C2: + return OnChunks(source, socket); + } + CHECK(false) << "Never here!"; + return MakeParseError(PARSE_ERROR_NO_RESOURCE); +} + +ParseResult RtmpContext::WaitForC0C1orSimpleRtmp(butil::IOBuf* source, Socket* socket) { + if (source->length() < RTMP_HANDSHAKE_SIZE0 + MAGIC_NUMBER_SIZE) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + char buf[RTMP_HANDSHAKE_SIZE0 + MAGIC_NUMBER_SIZE]; + const char* p = (const char*)source->fetch(buf, sizeof(buf)); + if (memcmp(p + RTMP_HANDSHAKE_SIZE0, SIMPLIFIED_RTMP_MAGIC_NUMBER, MAGIC_NUMBER_SIZE) == 0) { + source->pop_front(RTMP_HANDSHAKE_SIZE0 + MAGIC_NUMBER_SIZE); + SetState(socket->remote_side(), STATE_RECEIVED_C2); + _simplified_rtmp = true; + return OnChunks(source, socket); + } + + if (source->length() < RTMP_HANDSHAKE_SIZE0 + RTMP_HANDSHAKE_SIZE1) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + // Consume C0C1 and change the state. + char c0c1_buf[RTMP_HANDSHAKE_SIZE0 + RTMP_HANDSHAKE_SIZE1]; + source->cutn(c0c1_buf, sizeof(c0c1_buf)); + SetState(socket->remote_side(), STATE_RECEIVED_C0C1); + + butil::IOBuf tmp; + adobe_hs::C1 c1; + if (c1.Load(c0c1_buf + RTMP_HANDSHAKE_SIZE0)) { + RPC_VLOG << socket->remote_side() << ": Loaded C1 with schema" + << (c1.schema() == adobe_hs::SCHEMA0 ? "0" : "1"); + tmp.push_back(RTMP_DEFAULT_VERSION); + { + adobe_hs::S1 s1; + if (!s1.Generate(c1)) { + LOG(WARNING) << socket->remote_side() << ": Fail to generate s1"; + return MakeParseError(PARSE_ERROR_NO_RESOURCE); + } + char buf[RTMP_HANDSHAKE_SIZE1]; + s1.Save(buf); + tmp.append(buf, RTMP_HANDSHAKE_SIZE1); + _s1_digest = malloc(adobe_hs::DigestBlock::DIGEST_SIZE); + if (_s1_digest == NULL) { + LOG(ERROR) << "Fail to malloc"; + return MakeParseError(PARSE_ERROR_NO_RESOURCE); + } + memcpy(_s1_digest, s1.digest_blk.digest(), + adobe_hs::DigestBlock::DIGEST_SIZE); + } + { + adobe_hs::S2 s2; + if (!s2.Generate(c1.digest_blk.digest())) { + LOG(ERROR) << socket->remote_side() << ": Fail to generate s2"; + return MakeParseError(PARSE_ERROR_NO_RESOURCE); + } + char buf[RTMP_HANDSHAKE_SIZE2]; + s2.Save(buf); + tmp.append(buf, RTMP_HANDSHAKE_SIZE2); + } + } else { + RPC_VLOG << socket->remote_side() << ": Fallback to simple handshaking"; + // Send back S0 S1 S2 + char buf[9]; + char* q = buf; + *q++ = RTMP_DEFAULT_VERSION; // s0 (version) + WriteBigEndian4Bytes(&q, GetRtmpTimestamp()); // s1.time + WriteBigEndian4Bytes(&q, 0); // s1.zero + tmp.append(buf, sizeof(buf)); + tmp.append(GetRtmpHandshakeServerRandom()); + + char* const s2 = c0c1_buf + RTMP_HANDSHAKE_SIZE0; + q = s2 + 4; + const uint32_t s2_time2 = GetRtmpTimestamp(); + WriteBigEndian4Bytes(&q, s2_time2); + tmp.append(s2, RTMP_HANDSHAKE_SIZE2); + } + if (WriteAll(socket->fd(), &tmp) != 0) { + LOG(WARNING) << socket->remote_side() << ": Fail to write S0 S1 S2"; + return MakeParseError(PARSE_ERROR_NO_RESOURCE); + } + return WaitForC2(source, socket); +} + +ParseResult RtmpContext::WaitForC2(butil::IOBuf* source, Socket* socket) { + if (source->length() < RTMP_HANDSHAKE_SIZE2) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + // Consume C2 and change the state. + char c2_buf[RTMP_HANDSHAKE_SIZE2]; + source->cutn(c2_buf, sizeof(c2_buf)); + // C2 loading is so common to be failed (for players based on flash) + // Don't load C2 right now. + // if (_s1_digest) { + // adobe_hs::C2 c2; + // if (!c2.Load(_s1_digest, c2_buf)) { + // LOG(WARNING) << socket->remote_side() << ": Fail to load C2"; + // } + // } + SetState(socket->remote_side(), STATE_RECEIVED_C2); + return OnChunks(source, socket); +} + +ParseResult RtmpContext::WaitForS0S1(butil::IOBuf* source, Socket* socket) { + if (source->length() < RTMP_HANDSHAKE_SIZE0 + RTMP_HANDSHAKE_SIZE1) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + // Consume S0 S1 and change the state. + char s0s1_buf[RTMP_HANDSHAKE_SIZE0 + RTMP_HANDSHAKE_SIZE1]; + source->cutn(s0s1_buf, sizeof(s0s1_buf)); + SetState(socket->remote_side(), STATE_RECEIVED_S0S1); + + butil::IOBuf tmp; + bool done_adobe_hs = false; + if (!_only_check_simple_s0s1) { + adobe_hs::S1 s1; + if (s1.Load(s0s1_buf + RTMP_HANDSHAKE_SIZE0, adobe_hs::SCHEMA1)) { + RPC_VLOG << socket->remote_side() << ": Loaded S1 with schema1"; + adobe_hs::C2 c2; + if (!c2.Generate(s1.digest_blk.digest())) { + LOG(ERROR) << socket->remote_side() << ": Fail to generate c2"; + return MakeParseError(PARSE_ERROR_NO_RESOURCE); + } + c2.Save(s0s1_buf + RTMP_HANDSHAKE_SIZE0); + tmp.append(s0s1_buf + RTMP_HANDSHAKE_SIZE0, RTMP_HANDSHAKE_SIZE1); + done_adobe_hs = true; + } else { + RPC_VLOG << socket->remote_side() << ": Fallback to simple handshaking"; + } + } + if (!done_adobe_hs) { + // Send back C2 + char* const c2 = s0s1_buf + RTMP_HANDSHAKE_SIZE0; + char* q = c2 + 4; // skip time + const uint32_t c2_time2 = GetRtmpTimestamp(); + WriteBigEndian4Bytes(&q, c2_time2); // time2 + tmp.append(c2, RTMP_HANDSHAKE_SIZE2); + } + if (WriteAll(socket->fd(), &tmp) != 0) { + LOG(WARNING) << socket->remote_side() << ": Fail to write C2"; + return MakeParseError(PARSE_ERROR_NO_RESOURCE); + } + return WaitForS2(source, socket); +} + +ParseResult RtmpContext::WaitForS2(butil::IOBuf* source, Socket* socket) { + if (source->length() < RTMP_HANDSHAKE_SIZE2) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + // Consume S2 and change the state. + source->pop_front(RTMP_HANDSHAKE_SIZE2); + SetState(socket->remote_side(), STATE_RECEIVED_S2); + + if (SendConnectRequest(socket->remote_side(), socket->fd(), false) != 0) { + LOG(ERROR) << "Fail to send connect request to " << socket->remote_side(); + return MakeParseError(PARSE_ERROR_NO_RESOURCE); + } + return OnChunks(source, socket); +} + +ParseResult RtmpContext::OnChunks(butil::IOBuf* source, Socket* socket) { + // Parse basic header. + const char* p = (const char*)source->fetch1(); + if (NULL == p) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + const uint8_t first_byte = *p; + // 2 bits, deciding type of following chunk message header. + const RtmpChunkType fmt = (RtmpChunkType)(first_byte >> 6); + // cs_id is short for "chunk stream id" + uint32_t cs_id = (first_byte & 0x3F); + uint8_t basic_header_len = 1u; + if (cs_id == 0) { // 2-byte basic header + if (source->size() < 2u) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + char basic_header_buf[2]; + const uint8_t* p = (const uint8_t*)source->fetch(basic_header_buf, 2); + cs_id = ((uint32_t)p[1]) + 64; + basic_header_len = 2u; + } else if (cs_id == 1) { // 3-byte basic header + if (source->size() < 3u) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + char basic_header_buf[3]; + const uint8_t* p = (const uint8_t*)source->fetch(basic_header_buf, 3); + cs_id = ((uint32_t)p[2]) * 256 + ((uint32_t)p[1]) + 64; + basic_header_len = 3u; + } // else 1-byte basic header, keep cs_id as it is. + RtmpBasicHeader bh = { cs_id, fmt, basic_header_len }; + RtmpChunkStream* cstream = GetChunkStream(cs_id); + if (cstream == NULL) { + LOG(ERROR) << "Invalid chunk_stream_id=" << cs_id; + return MakeParseError(PARSE_ERROR_NO_RESOURCE); + } + return cstream->Feed(bh, source, socket); +} + +static int SendAck(Socket* socket, uint64_t received_bytes) { + const uint32_t data = butil::HostToNet32(received_bytes); + SocketMessagePtr msg( + MakeUnsentControlMessage(RTMP_MESSAGE_ACK, &data, sizeof(data))); + return WriteWithoutOvercrowded(socket, msg); +} + +void RtmpContext::AddReceivedBytes(Socket* socket, uint32_t sz) { + _received_bytes += sz; + _nonack_bytes += sz; + if (_nonack_bytes > _window_ack_size) { + _nonack_bytes -= _window_ack_size; + PLOG_IF(WARNING, SendAck(socket, _received_bytes) != 0) + << socket->remote_side() << ": Fail to send ack"; + } +} + +// ============ RtmpChunkStream ============== + +RtmpChunkStream::RtmpChunkStream(RtmpContext* conn_ctx, uint32_t cs_id) + : _conn_ctx(conn_ctx) + , _cs_id(cs_id) { +} + +RtmpChunkStream::ReadParams::ReadParams() + : last_has_extended_ts(false) + , first_chunk_of_message(true) + , last_timestamp_delta(0) + , left_message_length(0) { +} + +RtmpChunkStream::WriteParams::WriteParams() + : last_has_extended_ts(false) + , last_timestamp_delta(0) { +} + +MethodStatus* g_client_msg_status = NULL; +static pthread_once_t g_client_msg_status_once = PTHREAD_ONCE_INIT; +static void InitClientMessageStatus() { + g_client_msg_status = new MethodStatus; + g_client_msg_status->Expose("rtmp_client_in"); +} + +MethodStatus* g_server_msg_status = NULL; +static pthread_once_t g_server_msg_status_once = PTHREAD_ONCE_INIT; +static void InitServerMessageStatus() { + g_server_msg_status = new MethodStatus; + g_server_msg_status->Expose("rtmp_server_in"); +} + +struct ChunkStatus { + bvar::Adder count; + bvar::PerSecond > second; + ChunkStatus() : second("rtmp_chunk_in_second", &count) {} +}; +inline void AddChunk() { + butil::get_leaky_singleton()->count << 1; +} + +ParseResult RtmpChunkStream::Feed(const RtmpBasicHeader& bh, + butil::IOBuf* source, Socket* socket) { + // Parse message header. Notice that basic header is still in source. + uint32_t header_len = bh.header_length; + bool has_extended_ts = false; + uint32_t timestamp_delta = 0; + RtmpMessageHeader mh; + uint32_t cur_chunk_size = 0; + RtmpContext* ctx = connection_context(); + const uint32_t chunk_size_in = ctx->_chunk_size_in; + + switch (bh.fmt) { + case RTMP_CHUNK_TYPE0: { + // Type 0 chunk headers are 11 bytes long. This type MUST be + // used at the start of a chunk stream, and whenever the stream + // timestamp goes backward (e.g., because of a backward seek). + const uint32_t MSG_HEADER_LEN = 11u; + header_len += MSG_HEADER_LEN; + if (source->size() < header_len) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + char buf[header_len + 4/*extended ts*/]; + const char* p = (const char*)source->fetch(buf, header_len) + + bh.header_length; + mh.timestamp = ReadBigEndian3Bytes(p); + if (mh.timestamp == 0xFFFFFFu) { + header_len += 4u; + if (source->size() < header_len) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + p = (const char*)source->fetch(buf, header_len) + + bh.header_length; // fetch again. + mh.timestamp = ReadBigEndian4Bytes(p + MSG_HEADER_LEN); + has_extended_ts = true; + } + timestamp_delta = mh.timestamp; + mh.message_length = ReadBigEndian3Bytes(p + 3); + _r.left_message_length = mh.message_length; + cur_chunk_size = std::min(chunk_size_in, _r.left_message_length); + if (source->size() < header_len + cur_chunk_size) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + _r.left_message_length -= cur_chunk_size; + mh.message_type = p[6]; + + // NOTE: stream_id is little endian. + char* pmsid = (char*)&mh.stream_id; + pmsid[0] = p[7]; + pmsid[1] = p[8]; + pmsid[2] = p[9]; + pmsid[3] = p[10]; + } break; + case RTMP_CHUNK_TYPE1: { + // Type 1 chunk headers are 7 bytes long. The message stream ID is + // not included; this chunk takes the same stream ID as the + // preceding chunk. Streams with variable-sized messages + // (for example, many video formats) SHOULD use this format for + // the first chunk of each new message after the first. + const uint32_t MSG_HEADER_LEN = 7u; + header_len += MSG_HEADER_LEN; + if (source->size() < header_len) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + char buf[header_len + 4/*extended ts*/]; + const char* p = (const char*)source->fetch(buf, header_len) + + bh.header_length; + timestamp_delta = ReadBigEndian3Bytes(p); + if (timestamp_delta == 0xFFFFFFu) { + header_len += 4u; + if (source->size() < header_len) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + p = (const char*)source->fetch(buf, header_len) + + bh.header_length; // fetch again. + timestamp_delta = ReadBigEndian4Bytes(p + MSG_HEADER_LEN); + has_extended_ts = true; + } + if (!_r.last_msg_header.is_valid()) { + LOG(ERROR) << "No last message in chunk_stream=" << _cs_id + << " for ChunkType1"; + return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG); + } + mh.timestamp = _r.last_msg_header.timestamp + timestamp_delta; + mh.message_length = ReadBigEndian3Bytes(p + 3); + _r.left_message_length = mh.message_length; + cur_chunk_size = std::min(chunk_size_in, _r.left_message_length); + if (source->size() < header_len + cur_chunk_size) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + _r.left_message_length -= cur_chunk_size; + mh.message_type = p[6]; + mh.stream_id = _r.last_msg_header.stream_id; + } break; + case RTMP_CHUNK_TYPE2: { + // Type 2 chunk headers are 3 bytes long. Neither the stream ID + // nor the message length is included; this chunk has the same + // stream ID and message length as the preceding chunk. Streams + // with constant-sized messages (for example, some audio and data + // formats) SHOULD use this format for the first chunk of each + // message after the first. + const uint32_t MSG_HEADER_LEN = 3u; + header_len += MSG_HEADER_LEN; + if (source->size() < header_len) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + char buf[header_len + 4/*extended ts*/]; + const char* p = (const char*)source->fetch(buf, header_len) + + bh.header_length; + timestamp_delta = ReadBigEndian3Bytes(p); + if (timestamp_delta == 0xFFFFFFu) { + header_len += 4u; + if (source->size() < header_len) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + p = (const char*)source->fetch(buf, header_len) + + bh.header_length; // fetch again. + timestamp_delta = ReadBigEndian4Bytes(p + MSG_HEADER_LEN); + has_extended_ts = true; + } + if (!_r.last_msg_header.is_valid()) { + LOG(ERROR) << "No last message in chunk_stream=" << _cs_id + << " for ChunkType2"; + return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG); + } + mh.timestamp = _r.last_msg_header.timestamp + timestamp_delta; + mh.message_length = _r.last_msg_header.message_length; + cur_chunk_size = std::min(chunk_size_in, _r.left_message_length); + if (source->size() < header_len + cur_chunk_size) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + _r.left_message_length -= cur_chunk_size; + mh.message_type = _r.last_msg_header.message_type; + mh.stream_id = _r.last_msg_header.stream_id; + } break; + case RTMP_CHUNK_TYPE3: { + // Type 3 chunks have no message header, everything inherits from + // previous chunks. + if (!_r.last_has_extended_ts) { + timestamp_delta = _r.last_timestamp_delta; + } else { + header_len += 4u; + if (source->size() < header_len) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + char buf[header_len]; + const char* p = (const char*)source->fetch(buf, header_len) + + bh.header_length; + timestamp_delta = ReadBigEndian4Bytes(p); + has_extended_ts = true; + // librtmp may not set extended timestamp for non-first type-3 + // chunks of a message. Assume that the extended timestamp exists, + // if the timestamp does not match the ones in previous chunks + // (of the message), rewind the parsing cursor. + if (!_r.first_chunk_of_message && + timestamp_delta > 0 && + timestamp_delta != _r.last_timestamp_delta) { + header_len -= 4; + timestamp_delta = _r.last_timestamp_delta; + } + } + if (!_r.last_msg_header.is_valid()) { + LOG(ERROR) << "No last message in chunk_stream=" << _cs_id + << " for ChunkType3"; + return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG); + } + mh.timestamp = _r.last_msg_header.timestamp; + if (_r.first_chunk_of_message) { + // Only the first type-3 chunk adds the delta. + mh.timestamp += timestamp_delta; + } + mh.message_length = _r.last_msg_header.message_length; + cur_chunk_size = std::min(chunk_size_in, _r.left_message_length); + if (source->size() < header_len + cur_chunk_size) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + _r.left_message_length -= cur_chunk_size; + mh.message_type = _r.last_msg_header.message_type; + mh.stream_id = _r.last_msg_header.stream_id; + } break; + } // switch(fmt) + + source->pop_front(header_len); + source->cutn(&_r.msg_body, cur_chunk_size); + ctx->AddReceivedBytes(socket, header_len + cur_chunk_size); + const int vlvl = RPC_VLOG_LEVEL + 2; + VLOG(vlvl) << socket->remote_side() + << ": Chunk{chunk_stream_id=" << bh.chunk_stream_id + << " fmt=" << bh.fmt + << " body_size=" << cur_chunk_size << '}'; + _r.last_has_extended_ts = has_extended_ts; + _r.last_timestamp_delta = timestamp_delta; + _r.last_msg_header = mh; + + AddChunk(); + + if (_r.left_message_length == 0) { + MethodStatus* st = NULL; + if (ctx->service() != NULL) { + pthread_once(&g_server_msg_status_once, InitServerMessageStatus); + st = g_server_msg_status; + } else { + pthread_once(&g_client_msg_status_once, InitClientMessageStatus); + st = g_client_msg_status; + } + if (st) { + butil::Timer tm; + tm.start(); + CHECK(st->OnRequested()); + const bool ret = OnMessage(bh, mh, &_r.msg_body, socket); + tm.stop(); + st->OnResponded(ret, tm.u_elapsed()); + } else { + (void)OnMessage(bh, mh, &_r.msg_body, socket); + } + _r.msg_body.clear(); + _r.left_message_length = mh.message_length; + _r.first_chunk_of_message = true; + } else { + _r.first_chunk_of_message = false; + } + return MakeMessage(NULL); +} + +int RtmpChunkStream::SerializeMessage(butil::IOBuf* buf, + const RtmpMessageHeader& mh, + butil::IOBuf* body) { + const size_t bh_size = GetBasicHeaderLength(_cs_id); + if (bh_size == 0) { + CHECK(false) << "Invalid chunk_stream_id=" << _cs_id; + return -1; + } + // NOTE: body->size() may be longer than mh.message_length; + uint32_t left_size = mh.message_length; + CHECK_LE((size_t)left_size, body->size()); + bool has_extended_ts = false; + uint32_t timestamp_delta = 0; + const uint32_t chunk_size_out = connection_context()->_chunk_size_out; + if (left_size > 0) { + const uint32_t cur_chunk_size = std::min(chunk_size_out, left_size); + left_size -= cur_chunk_size; + char header_buf[32]; // enough + char* p = header_buf + bh_size; + RtmpChunkType chunk_type = RTMP_CHUNK_TYPE0; + if (!_w.last_msg_header.is_valid() || + mh.stream_id != _w.last_msg_header.stream_id || + mh.timestamp < _w.last_msg_header.timestamp) { // backward seek + chunk_type = RTMP_CHUNK_TYPE0; + timestamp_delta = mh.timestamp; + uint32_t packed_ts = mh.timestamp; + if (packed_ts >= 0xFFFFFFu) { + has_extended_ts = true; + packed_ts = 0xFFFFFFu; + } + WriteBigEndian3Bytes(&p, packed_ts); + WriteBigEndian3Bytes(&p, mh.message_length); + *p++ = mh.message_type; + WriteLittleEndian4Bytes(&p, mh.stream_id); + } else if (mh.message_length != _w.last_msg_header.message_length || + mh.message_type != _w.last_msg_header.message_type) { + chunk_type = RTMP_CHUNK_TYPE1; + timestamp_delta = mh.timestamp - _w.last_msg_header.timestamp; + uint32_t packed_ts = timestamp_delta; + if (packed_ts >= 0xFFFFFFu) { + has_extended_ts = true; + packed_ts = 0xFFFFFFu; + } + WriteBigEndian3Bytes(&p, packed_ts); + WriteBigEndian3Bytes(&p, mh.message_length); + *p++ = mh.message_type; + } else { + timestamp_delta = mh.timestamp - _w.last_msg_header.timestamp; + if (timestamp_delta != _w.last_timestamp_delta) { + chunk_type = RTMP_CHUNK_TYPE2; + uint32_t packed_ts = timestamp_delta; + if (packed_ts >= 0xFFFFFFu) { + has_extended_ts = true; + packed_ts = 0xFFFFFFu; + } + WriteBigEndian3Bytes(&p, packed_ts); + } else { + chunk_type = RTMP_CHUNK_TYPE3; + has_extended_ts = _w.last_has_extended_ts; + } + } + if (has_extended_ts) { + WriteBigEndian4Bytes(&p, timestamp_delta); + } + // Overwrite the basic header again (with correct chunk_type). + char* dummy_ptr = header_buf; + WriteBasicHeader(&dummy_ptr, chunk_type, _cs_id); + buf->append(header_buf, p - header_buf); + body->cutn(buf, cur_chunk_size); + + _w.last_has_extended_ts = has_extended_ts; + _w.last_timestamp_delta = timestamp_delta; + _w.last_msg_header = mh; + } + // Send left data as type-3 chunks + while (left_size > 0) { + const uint32_t cur_chunk_size = std::min(chunk_size_out, left_size); + left_size -= cur_chunk_size; + has_extended_ts = _w.last_has_extended_ts; + char header_buf[8]; // enough (3+4 bytes at maximum) + char* p = header_buf; + WriteBasicHeader(&p, RTMP_CHUNK_TYPE3, _cs_id); + if (has_extended_ts) { + // Add extended timestamp in non-first type-3 chunks to be + // consistent with flash/FMLE/FMS. + WriteBigEndian4Bytes(&p, timestamp_delta); + } + buf->append(header_buf, p - header_buf); + body->cutn(buf, cur_chunk_size); + } + return 0; +} + +static const RtmpChunkStream::MessageHandler s_msg_handlers[] = { + &RtmpChunkStream::OnSetChunkSize, // 1 + &RtmpChunkStream::OnAbortMessage, // 2 + &RtmpChunkStream::OnAck, // 3 + &RtmpChunkStream::OnUserControlMessage, // 4 + &RtmpChunkStream::OnWindowAckSize,// 5 + &RtmpChunkStream::OnSetPeerBandwidth, // 6 + NULL, //7 + &RtmpChunkStream::OnAudioMessage, // 8 + &RtmpChunkStream::OnVideoMessage, // 9 + NULL, // 10 + NULL, // 11 + NULL, // 12 + NULL, // 13 + NULL, // 14 + &RtmpChunkStream::OnDataMessageAMF3, // 15 + &RtmpChunkStream::OnSharedObjectMessageAMF3, // 16 + &RtmpChunkStream::OnCommandMessageAMF3, // 17 + &RtmpChunkStream::OnDataMessageAMF0, // 18 + &RtmpChunkStream::OnSharedObjectMessageAMF0, // 19 + &RtmpChunkStream::OnCommandMessageAMF0, // 20 + NULL, // 21 + &RtmpChunkStream::OnAggregateMessage, // 22 +}; + +typedef butil::FlatMap CommandHandlerMap; +static CommandHandlerMap* s_cmd_handlers = NULL; +static pthread_once_t s_cmd_handlers_init_once = PTHREAD_ONCE_INIT; +static void InitCommandHandlers() { + // Dispatch commands based on "Command Name". + s_cmd_handlers = new CommandHandlerMap; + if (s_cmd_handlers->init(64, 70) != 0) { + LOG(WARNING) << "Fail to init s_cmd_handlers"; + } + (*s_cmd_handlers)[RTMP_AMF0_COMMAND_CONNECT] = &RtmpChunkStream::OnConnect; + (*s_cmd_handlers)[RTMP_AMF0_COMMAND_ON_BW_DONE] = &RtmpChunkStream::OnBWDone; + (*s_cmd_handlers)[RTMP_AMF0_COMMAND_RESULT] = &RtmpChunkStream::OnResult; + (*s_cmd_handlers)[RTMP_AMF0_COMMAND_ERROR] = &RtmpChunkStream::OnError; + (*s_cmd_handlers)[RTMP_AMF0_COMMAND_PLAY] = &RtmpChunkStream::OnPlay; + (*s_cmd_handlers)[RTMP_AMF0_COMMAND_PLAY2] = &RtmpChunkStream::OnPlay2; + (*s_cmd_handlers)[RTMP_AMF0_COMMAND_CREATE_STREAM] = &RtmpChunkStream::OnCreateStream; + (*s_cmd_handlers)[RTMP_AMF0_COMMAND_DELETE_STREAM] = &RtmpChunkStream::OnDeleteStream; + (*s_cmd_handlers)[RTMP_AMF0_COMMAND_CLOSE_STREAM] = &RtmpChunkStream::OnCloseStream; + (*s_cmd_handlers)[RTMP_AMF0_COMMAND_PUBLISH] = &RtmpChunkStream::OnPublish; + (*s_cmd_handlers)[RTMP_AMF0_COMMAND_SEEK] = &RtmpChunkStream::OnSeek; + (*s_cmd_handlers)[RTMP_AMF0_COMMAND_PAUSE] = &RtmpChunkStream::OnPause; + (*s_cmd_handlers)[RTMP_AMF0_COMMAND_ON_STATUS] = &RtmpChunkStream::OnStatus; + (*s_cmd_handlers)[RTMP_AMF0_COMMAND_RELEASE_STREAM] = &RtmpChunkStream::OnReleaseStream; + (*s_cmd_handlers)[RTMP_AMF0_COMMAND_FC_PUBLISH] = &RtmpChunkStream::OnFCPublish; + (*s_cmd_handlers)[RTMP_AMF0_COMMAND_FC_UNPUBLISH] = &RtmpChunkStream::OnFCUnpublish; + (*s_cmd_handlers)[RTMP_AMF0_COMMAND_GET_STREAM_LENGTH] = &RtmpChunkStream::OnGetStreamLength; + (*s_cmd_handlers)[RTMP_AMF0_COMMAND_CHECK_BW] = &RtmpChunkStream::OnCheckBW; +} + +bool RtmpChunkStream::OnMessage(const RtmpBasicHeader& bh, + const RtmpMessageHeader& mh, + butil::IOBuf* msg_body, + Socket* socket) { + // Make sure msg_body is consistent with the header. Previous code + // forgot to clear msg_body before appending new message. + CHECK_EQ((size_t)mh.message_length, msg_body->size()); + + if (mh.message_type >= 1 && mh.message_type <= 6) { + // protocol/user control messages MUST/SHOULD have message stream ID 0 + // (known as the control stream) and be sent in chunk stream ID 2. + // control messages take effect as soon as they are received; their + // timestamps are ignored. + if (mh.stream_id != 0 || bh.chunk_stream_id != 2) { + RTMP_ERROR(socket, mh) << "Control messages should be sent on " + "stream_id=0 chunk_stream_id=2"; + } + } + const uint32_t index = mh.message_type - 1; + if (index >= arraysize(s_msg_handlers)) { + RTMP_ERROR(socket, mh) << "Unknown message_type=" << (int)mh.message_type; + return false; + } + MessageHandler handler = s_msg_handlers[index]; + if (handler == NULL) { + RTMP_ERROR(socket, mh) << "Unknown message_type=" << (int)mh.message_type; + return false; + } + // audio/video/ack are more verbose than other messages. + const int vlvl = ((mh.message_type != RTMP_MESSAGE_AUDIO && + mh.message_type != RTMP_MESSAGE_VIDEO && + mh.message_type != RTMP_MESSAGE_ACK) ? + (RPC_VLOG_LEVEL + 1) : (RPC_VLOG_LEVEL + 2)); + VLOG(vlvl) << socket->remote_side() << "[" << mh.stream_id + << "] Message{timestamp=" << mh.timestamp + << " type=" << messagetype2str(mh.message_type) + << " body_size=" << mh.message_length << '}'; + return (this->*handler)(mh, msg_body, socket); +} + +bool RtmpChunkStream::OnSetChunkSize( + const RtmpMessageHeader& mh, butil::IOBuf* msg_body, Socket* socket) { + if (mh.message_length != 4u) { + RTMP_ERROR(socket, mh) << "Expected message_length=4, actually " + << mh.message_length; + return false; + } + char buf[4]; + msg_body->cutn(buf, sizeof(buf)); + const uint32_t new_size = ReadBigEndian4Bytes(buf); + if (new_size > 0x7FFFFFFF) { + RTMP_ERROR(socket, mh) << "MSB of chunk_size=" << new_size + << " is not zero"; + return false; + } + const uint32_t old_size = connection_context()->_chunk_size_in; + connection_context()->_chunk_size_in = new_size; + RPC_VLOG << socket->remote_side() << "[" << mh.stream_id + << "] SetChunkSize: " << old_size << " -> " << new_size; + return true; +} + +bool RtmpChunkStream::OnAbortMessage( + const RtmpMessageHeader& mh, butil::IOBuf* msg_body, Socket* socket) { + if (mh.message_length != 4u) { + RTMP_ERROR(socket, mh) << "Expected message_length=4, actually " + << mh.message_length; + return false; + } + char buf[4]; + msg_body->cutn(buf, sizeof(buf)); + uint32_t cs_id = ReadBigEndian4Bytes(buf); + if (cs_id > RTMP_MAX_CHUNK_STREAM_ID) { + RTMP_ERROR(socket, mh) << "Invalid chunk_stream_id=" << cs_id; + return false; + } + connection_context()->ClearChunkStream(cs_id); + return true; +} + +bool RtmpChunkStream::OnAck( + const RtmpMessageHeader& mh, butil::IOBuf* msg_body, Socket* socket) { + if (mh.message_length != 4u) { + RTMP_ERROR(socket, mh) << "Expected message_length=4, actually " + << mh.message_length; + return false; + } + char buf[4]; + msg_body->cutn(buf, sizeof(buf)); + uint32_t bytes_received = ReadBigEndian4Bytes(buf); + // TODO(gejun): No usage right now. + (void)bytes_received; + //RPC_VLOG << socket->remote_side() << ": Ack=" << bytes_received; + return true; +} + +bool RtmpChunkStream::OnWindowAckSize( + const RtmpMessageHeader& mh, butil::IOBuf* msg_body, Socket* socket) { + if (mh.message_length != 4u) { + RTMP_ERROR(socket, mh) << "Expected message_length=4, actually " + << mh.message_length; + return false; + } + char buf[4]; + msg_body->cutn(buf, sizeof(buf)); + const uint32_t old_size = connection_context()->_window_ack_size; + const uint32_t new_size = ReadBigEndian4Bytes(buf); + connection_context()->_window_ack_size = new_size; + RPC_VLOG << socket->remote_side() << "[" << mh.stream_id + << "] WindowAckSize: " << old_size << " -> " << new_size; + return true; +} + +bool RtmpChunkStream::OnSetPeerBandwidth( + const RtmpMessageHeader& mh, butil::IOBuf* msg_body, Socket* socket) { + if (mh.message_length != 5u) { + RTMP_ERROR(socket, mh) << "Expected message_length=5, actually " + << mh.message_length; + return false; + } + char buf[5]; + msg_body->cutn(buf, sizeof(buf)); + const uint32_t new_size = ReadBigEndian4Bytes(buf); + const RtmpLimitType limit_type = (RtmpLimitType)buf[4]; + RPC_VLOG << socket->remote_side() << "[" << mh.stream_id + << "] SetPeerBandwidth=" << new_size + << " limit_type=" << limit_type; + return true; +} + +bool RtmpChunkStream::OnUserControlMessage( + const RtmpMessageHeader& mh, butil::IOBuf* msg_body, Socket* socket) { + if (mh.message_length < 2u || mh.message_length > 32u) { + RTMP_ERROR(socket, mh) << "Invalid user control message length=" + << mh.message_length << " bytes"; + return false; + } + char buf[mh.message_length]; // safe to put on stack. + msg_body->cutn(buf, mh.message_length); + const uint16_t event_type = ReadBigEndian2Bytes(buf); + butil::StringPiece event_data(buf + 2, mh.message_length - 2); + switch ((RtmpUserControlEventType)event_type) { + case RTMP_USER_CONTROL_EVENT_STREAM_BEGIN: + return OnStreamBegin(mh, event_data, socket); + case RTMP_USER_CONTROL_EVENT_STREAM_EOF: + return OnStreamEOF(mh, event_data, socket); + case RTMP_USER_CONTROL_EVENT_STREAM_DRY: + return OnStreamDry(mh, event_data, socket); + case RTMP_USER_CONTROL_EVENT_SET_BUFFER_LENGTH: + return OnSetBufferLength(mh, event_data, socket); + case RTMP_USER_CONTROL_EVENT_STREAM_IS_RECORDED: + return OnStreamIsRecorded(mh, event_data, socket); + case RTMP_USER_CONTROL_EVENT_PING_REQUEST: + return OnPingRequest(mh, event_data, socket); + case RTMP_USER_CONTROL_EVENT_PING_RESPONSE: + return OnPingResponse(mh, event_data, socket); + case RTMP_USER_CONTROL_EVENT_BUFFER_EMPTY: + return OnBufferEmpty(mh, event_data, socket); + case RTMP_USER_CONTROL_EVENT_BUFFER_READY: + return OnBufferReady(mh, event_data, socket); + } // switch(event_type) + RTMP_ERROR(socket, mh) << "Unknown event_type=" << event_type; + return false; +} + +bool RtmpChunkStream::OnStreamBegin(const RtmpMessageHeader& mh, + const butil::StringPiece& event_data, + Socket* socket) { + RtmpService* service = connection_context()->service(); + if (service != NULL) { + RTMP_ERROR(socket, mh) << "Server should not receive `StreamBegin'"; + return false; + } + if (event_data.size() != 4) { + RTMP_ERROR(socket, mh) << "Invalid StreamBegin.event_data.size=" + << event_data.size(); + return false; + } + // Ignore StreamBegin + return true; +} + +bool RtmpChunkStream::OnStreamEOF(const RtmpMessageHeader& mh, + const butil::StringPiece& event_data, + Socket* socket) { + RtmpService* service = connection_context()->service(); + if (service != NULL) { + RTMP_ERROR(socket, mh) << "Server should not receive `StreamEOF'"; + return false; + } + if (event_data.size() != 4) { + RTMP_ERROR(socket, mh) << "Invalid StreamEOF.event_data.size=" + << event_data.size(); + return false; + } + // Ignore StreamEOF + return true; +} + +bool RtmpChunkStream::OnStreamDry(const RtmpMessageHeader& mh, + const butil::StringPiece& event_data, + Socket* socket) { + RtmpService* service = connection_context()->service(); + if (service != NULL) { + RTMP_ERROR(socket, mh) << "Server should not receive `StreamDry'"; + return false; + } + if (event_data.size() != 4) { + RTMP_ERROR(socket, mh) << "Invalid StreamDry.event_data.size=" + << event_data.size(); + return false; + } + // Ignore StreamDry + return true; +} + +bool RtmpChunkStream::OnStreamIsRecorded(const RtmpMessageHeader& mh, + const butil::StringPiece& event_data, + Socket* socket) { + RtmpService* service = connection_context()->service(); + if (service != NULL) { + RTMP_ERROR(socket, mh) << "Server should not receive `StreamIsRecorded'"; + return false; + } + if (event_data.size() != 4) { + RTMP_ERROR(socket, mh) << "Invalid StreamIsRecorded.event_data.size=" + << event_data.size(); + return false; + } + // Ignore StreamIsRecorded + return true; +} + +bool RtmpChunkStream::OnSetBufferLength(const RtmpMessageHeader& mh, + const butil::StringPiece& event_data, + Socket* socket) { + RtmpService* service = connection_context()->service(); + if (service == NULL) { + RTMP_ERROR(socket, mh) << "Client should not receive `SetBufferLength'"; + return false; + } + if (event_data.size() != 8) { + RTMP_ERROR(socket, mh) << "Invalid SetBufferLength.event_data.size=" + << event_data.size(); + return false; + } + const uint32_t stream_id = ReadBigEndian4Bytes(event_data.data()); + const uint32_t bl_ms = ReadBigEndian4Bytes(event_data.data() + 4); + RPC_VLOG << socket->remote_side() << "[" << mh.stream_id + << "] SetBufferLength{stream_id=" << stream_id + << " buffer_length_ms=" << bl_ms << '}'; + if (stream_id == RTMP_CONTROL_MESSAGE_STREAM_ID) { + // Ignore SetBufferSize for control stream. + return true; + } + butil::intrusive_ptr stream; + if (!connection_context()->FindMessageStream(stream_id, &stream)) { + RTMP_WARNING(socket, mh) << "Fail to find stream_id=" << stream_id; + return false; + } + static_cast(stream.get())->OnSetBufferLength(bl_ms); + return true; +} + +bool RtmpChunkStream::OnPingRequest(const RtmpMessageHeader& mh, + const butil::StringPiece& event_data, + Socket* socket) { + RtmpService* service = connection_context()->service(); + if (service != NULL) { + RTMP_ERROR(socket, mh) << "Server should not receive `PingRequest'"; + return false; + } + if (event_data.size() != 4) { + RTMP_ERROR(socket, mh) << "Invalid PingRequest.event_data.size=" + << event_data.size(); + return false; + } + const uint32_t timestamp = ReadBigEndian4Bytes(event_data.data()); + char data[6]; + char* p = data; + WriteBigEndian2Bytes(&p, RTMP_USER_CONTROL_EVENT_PING_RESPONSE); + WriteBigEndian4Bytes(&p, timestamp); + SocketMessagePtr msg( + MakeUnsentControlMessage(RTMP_MESSAGE_USER_CONTROL, data, sizeof(data))); + if (socket->Write(msg) != 0) { + PLOG(WARNING) << "Fail to send back PingResponse"; + return false; + } + return true; +} + +bool RtmpChunkStream::OnPingResponse(const RtmpMessageHeader& mh, + const butil::StringPiece& event_data, + Socket* socket) { + RtmpService* service = connection_context()->service(); + if (service == NULL) { + RTMP_ERROR(socket, mh) << "Client should not receive `PingResponse'"; + return false; + } + if (event_data.size() != 4) { + RTMP_ERROR(socket, mh) << "Invalid PingResponse.event_data.size=" + << event_data.size(); + return false; + } + const uint32_t timestamp = ReadBigEndian4Bytes(event_data.data()); + service->OnPingResponse(socket->remote_side(), timestamp); + return true; +} + +bool RtmpChunkStream::OnBufferEmpty(const RtmpMessageHeader& mh, + const butil::StringPiece& event_data, + Socket* socket) { + // Ignore right now. + // NOTE: If we need to fetch the data from FMS as fast as possible, + // we may consider the pause/unpause trick used in http://repo.or.cz/w/rtmpdump.git/blob/8880d1456b282ee79979adbe7b6a6eb8ad371081:/librtmp/rtmp.c#l2787 + if (event_data.size() != 4) { + RTMP_ERROR(socket, mh) << "Invalid BufferEmpty.event_data.size=" + << event_data.size(); + return false; + } + const uint32_t tmp = ReadBigEndian4Bytes(event_data.data()); + const int vlvl = RPC_VLOG_LEVEL + 1; + VLOG(vlvl) << socket->remote_side() << "[" << mh.stream_id + << "] BufferEmpty(" << tmp << ')'; + return true; +} + +bool RtmpChunkStream::OnBufferReady(const RtmpMessageHeader& mh, + const butil::StringPiece& event_data, + Socket* socket) { + if (event_data.size() != 4) { + RTMP_ERROR(socket, mh) << "Invalid BufferReady.event_data.size=" + << event_data.size(); + return false; + } + const uint32_t tmp = ReadBigEndian4Bytes(event_data.data()); + const int vlvl = RPC_VLOG_LEVEL + 1; + VLOG(vlvl) << socket->remote_side() << "[" << mh.stream_id + << "] BufferReady(" << tmp << ')'; + return true; +} + +bool RtmpChunkStream::OnAudioMessage( + const RtmpMessageHeader& mh, butil::IOBuf* msg_body, Socket* socket) { + char first_byte = 0; + if (!msg_body->cut1(&first_byte)) { + // pretty common, don't print logs. + return false; + } + // TODO: execq? + RtmpAudioMessage msg; + msg.timestamp = mh.timestamp; + msg.codec = (FlvAudioCodec)((first_byte >> 4) & 0xF); + msg.rate = (FlvSoundRate)((first_byte >> 2) & 0x3); + msg.bits = (FlvSoundBits)((first_byte >> 1) & 0x1); + msg.type = (FlvSoundType)(first_byte & 0x1); + msg_body->swap(msg.data); + + const int vlvl = RPC_VLOG_LEVEL + 1; + VLOG(vlvl) << socket->remote_side() << "[" << mh.stream_id << "] " << msg; + butil::intrusive_ptr stream; + if (!connection_context()->FindMessageStream(mh.stream_id, &stream)) { + LOG_EVERY_SECOND(WARNING) << socket->remote_side() + << ": Fail to find stream_id=" << mh.stream_id; + return false; + } + stream->CallOnAudioMessage(&msg); + return true; +} + +bool RtmpChunkStream::OnVideoMessage( + const RtmpMessageHeader& mh, butil::IOBuf* msg_body, Socket* socket) { + char first_byte = 0; + if (!msg_body->cut1(&first_byte)) { + // pretty common, don't print logs. + return false; + } + // TODO: execq? + RtmpVideoMessage msg; + msg.timestamp = mh.timestamp; + msg.frame_type = (FlvVideoFrameType)((first_byte >> 4) & 0xF); + msg.codec = (FlvVideoCodec)(first_byte & 0xF); + if (!is_video_frame_type_valid(msg.frame_type)) { + RTMP_WARNING(socket, mh) << "Invalid frame_type=" << (int)msg.frame_type; + } + if (!is_video_codec_valid(msg.codec)) { + RTMP_WARNING(socket, mh) << "Invalid codec=" << (int)msg.codec; + } + msg_body->swap(msg.data); + + const int vlvl = RPC_VLOG_LEVEL + 1; + VLOG(vlvl) << socket->remote_side() << "[" << mh.stream_id << "] " << msg; + butil::intrusive_ptr stream; + if (!connection_context()->FindMessageStream(mh.stream_id, &stream)) { + LOG_EVERY_SECOND(WARNING) << socket->remote_side() + << ": Fail to find stream_id=" << mh.stream_id; + return false; + } + stream->CallOnVideoMessage(&msg); + return true; +} + +bool RtmpChunkStream::OnDataMessageAMF0( + const RtmpMessageHeader& mh, butil::IOBuf* msg_body, Socket* socket) { + butil::IOBufAsZeroCopyInputStream zc_stream(*msg_body); + AMFInputStream istream(&zc_stream); + std::string name; + if (!ReadAMFString(&name, &istream)) { + RTMP_ERROR(socket, mh) << "Fail to read name of DataMessage"; + return false; + } + if (name == RTMP_AMF0_SET_DATAFRAME) { + if (!ReadAMFString(&name, &istream)) { + RTMP_ERROR(socket, mh) << "Fail to read name of DataMessage"; + return false; + } + } + RPC_VLOG << socket->remote_side() << "[" << mh.stream_id + << "] DataMessage{timestamp=" << mh.timestamp + << " name=" << name << '}'; + + if (name == RTMP_AMF0_ON_META_DATA || name == FLAGS_user_defined_data_message) { + if (istream.check_emptiness()) { + // Ignore empty metadata (seen in pulling streams from quanmin) + return false; + } + RtmpMetaData metadata; + metadata.timestamp = mh.timestamp; + if (!ReadAMFObject(&metadata.data, &istream)) { + RTMP_ERROR(socket, mh) << "Fail to read metadata"; + return false; + } + // TODO: execq? + butil::intrusive_ptr stream; + if (!connection_context()->FindMessageStream(mh.stream_id, &stream)) { + LOG_EVERY_SECOND(WARNING) << socket->remote_side() + << ": Fail to find stream_id=" << mh.stream_id; + return false; + } + stream->CallOnMetaData(&metadata, name); + return true; + } else if (name == RTMP_AMF0_ON_CUE_POINT) { + if (istream.check_emptiness()) { + return false; + } + RtmpCuePoint cuepoint; + cuepoint.timestamp = mh.timestamp; + if (!ReadAMFObject(&cuepoint.data, &istream)) { + RTMP_ERROR(socket, mh) << "Fail to read cuepoint"; + return false; + } + // TODO: execq? + butil::intrusive_ptr stream; + if (!connection_context()->FindMessageStream(mh.stream_id, &stream)) { + LOG_EVERY_SECOND(WARNING) << socket->remote_side() + << ": Fail to find stream_id=" << mh.stream_id; + return false; + } + stream->CallOnCuePoint(&cuepoint); + return true; + } else if (name == RTMP_AMF0_DATA_SAMPLE_ACCESS) { + return true; + } else if (name == RTMP_AMF0_COMMAND_ON_STATUS) { + return true; + } + return false; +} + +bool RtmpChunkStream::OnSharedObjectMessageAMF0( + const RtmpMessageHeader&, butil::IOBuf*, Socket* socket) { + LOG_EVERY_SECOND(ERROR) << socket->remote_side() << ": Not implemented"; + return false; +} + +bool RtmpChunkStream::OnCommandMessageAMF0( + const RtmpMessageHeader& mh, butil::IOBuf* msg_body, Socket* socket) { + butil::IOBufAsZeroCopyInputStream zc_stream(*msg_body); + AMFInputStream istream(&zc_stream); + std::string command_name; + if (!ReadAMFString(&command_name, &istream)) { + RTMP_ERROR(socket, mh) << "Fail to read commandName"; + return false; + } + RPC_VLOG << socket->remote_side() << "[" << mh.stream_id + << "] Command{timestamp=" << mh.timestamp + << " name=" << command_name << '}'; + pthread_once(&s_cmd_handlers_init_once, InitCommandHandlers); + RtmpChunkStream::CommandHandler* phandler = + s_cmd_handlers->seek(command_name); + if (phandler == NULL) { + RTMP_ERROR(socket, mh) << "Unknown command_name=" << command_name; + return false; + } + return (this->**phandler)(mh, &istream, socket); +} + +bool RtmpChunkStream::OnDataMessageAMF3( + const RtmpMessageHeader& mh, butil::IOBuf* msg_body, Socket* socket) { + msg_body->pop_front(1); + return OnDataMessageAMF0(mh, msg_body, socket); +} + +bool RtmpChunkStream::OnSharedObjectMessageAMF3( + const RtmpMessageHeader&, butil::IOBuf*, Socket*) { + LOG(ERROR) << "Not implemented"; + return false; +} + +bool RtmpChunkStream::OnCommandMessageAMF3( + const RtmpMessageHeader& mh, butil::IOBuf* msg_body, Socket* socket) { + msg_body->pop_front(1); + return OnCommandMessageAMF0(mh, msg_body, socket); +} + +bool RtmpChunkStream::OnAggregateMessage( + const RtmpMessageHeader&, butil::IOBuf*, Socket*) { + LOG(ERROR) << "Not implemented"; + return false; +} + +// // Setup necessary fields in the controller. +// static void SetupRtmpServerController(Controller* cntl, +// const Server* server, +// const Socket* socket) { +// ControllerPrivateAccessor accessor(cntl); +// ServerPrivateAccessor server_accessor(server); +// const bool security_mode = server->options().security_mode() && +// socket->user() == server_accessor.acceptor(); +// accessor.set_server(server) +// .set_security_mode(security_mode) +// .set_peer_id(socket->id()) +// .set_remote_side(socket->remote_side()) +// .set_local_side(socket->local_side()) +// .set_auth_context(socket->auth_context()) +// .set_request_protocol(PROTOCOL_RTMP); +// } + +bool RtmpChunkStream::OnConnect(const RtmpMessageHeader& mh, + AMFInputStream* istream, + Socket* socket) { + if (!connection_context()->is_server_side()) { + RTMP_ERROR(socket, mh) << "Client should not receive `connect'"; + return false; + } + uint32_t transaction_id = 0; + if (!ReadAMFUint32(&transaction_id, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read connect.TransactionId"; + return false; + } + RtmpConnectRequest* req = &connection_context()->_connect_req; + if (!ReadAMFObject(req, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read connect.CommandObjects"; + return false; + } + RPC_VLOG << socket->remote_side() << "[" << mh.stream_id + << "] connect{" << req->ShortDebugString() << '}'; + + TemporaryArrayBuilder, 5> msgs; + char* p = NULL; + // WindowAckSize + // TODO(gejun): seems not effective to ffplay. + char wasbuf[4]; + p = wasbuf; + WriteBigEndian4Bytes(&p, FLAGS_rtmp_server_window_ack_size); + msgs.push().reset(MakeUnsentControlMessage( + RTMP_MESSAGE_WINDOW_ACK_SIZE, wasbuf, sizeof(wasbuf))); + + // SetPeerBandwidth + char spbbuf[5]; + p = spbbuf; + WriteBigEndian4Bytes(&p, FLAGS_rtmp_server_window_ack_size); + *p++ = RTMP_LIMIT_DYNAMIC; + msgs.push().reset(MakeUnsentControlMessage( + RTMP_MESSAGE_SET_PEER_BANDWIDTH, spbbuf, sizeof(spbbuf))); + + // SetChunkSize. + // @SRS + // set chunk size to larger. + // set the chunk size before any larger response greater than 128, + // to make OBS happy, @see https://github.com/ossrs/srs/issues/454 + // NOTE(gejun): This also makes ffmpeg/ffplay *much* faster for publishing + // and playing http://code.bj.bcebos.com/bbb_1080p.flv at 2k bitrate. + char csbuf[4]; + p = csbuf; + WriteBigEndian4Bytes(&p, FLAGS_rtmp_server_chunk_size); + RtmpUnsentMessage* scs_msg = MakeUnsentControlMessage( + RTMP_MESSAGE_SET_CHUNK_SIZE, csbuf, sizeof(csbuf)); + scs_msg->new_chunk_size = FLAGS_rtmp_server_chunk_size; + msgs.push().reset(scs_msg); + + // _result + butil::IOBuf req_buf; + RtmpInfo info; + RtmpConnectResponse response; + // TODO: Set this field. + std::string error_text; + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + WriteAMFString((!error_text.empty() ? RTMP_AMF0_COMMAND_ERROR : + RTMP_AMF0_COMMAND_RESULT), &ostream); + WriteAMFUint32(1, &ostream); + if (!response.has_fmsver()) { + response.set_fmsver("FMS/" RTMP_SIG_FMS_VER); + } + if (!response.has_capabilities()) { + response.set_capabilities(127); + } + if (!response.has_mode()) { + response.set_mode(1); + } + response.set_create_stream_with_play_or_publish(true); + WriteAMFObject(response, &ostream); + // Set info + if (error_text.empty()) { + info.set_code(RTMP_STATUS_CODE_CONNECT_SUCCESS); + info.set_level(RTMP_INFO_LEVEL_STATUS); + info.set_description("Connection succeeded"); + info.set_objectencoding(req->objectencoding()); + } else { + info.set_code(RTMP_STATUS_CODE_CONNECT_REJECTED); + info.set_level(RTMP_INFO_LEVEL_ERROR); + info.set_description(error_text); + } + WriteAMFObject(info, &ostream); + CHECK(ostream.good()); + } + msgs.push().reset(MakeUnsentControlMessage( + RTMP_MESSAGE_COMMAND_AMF0, chunk_stream_id(), req_buf)); + + // onBWDone + req_buf.clear(); + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + WriteAMFString(RTMP_AMF0_COMMAND_ON_BW_DONE, &ostream); + WriteAMFUint32(0, &ostream); + WriteAMFNull(&ostream); + CHECK(ostream.good()); + } + // chunk_stream_id is same with _result to connect, confirmed in SRS. + msgs.push().reset(MakeUnsentControlMessage( + RTMP_MESSAGE_COMMAND_AMF0, chunk_stream_id(), req_buf)); + + for (size_t i = msgs.size(); i > 1; --i) { + msgs[i-2]->next.reset(msgs[i-1].release()); + } + if (socket->Write(msgs[0]) != 0) { + PLOG(WARNING) << socket->remote_side() << ": Fail to respond connect"; + socket->SetFailed(EFAILEDSOCKET, "Fail to respond connect"); + return false; + } + RPC_VLOG << socket->remote_side() << ": respond connect, props={" + << response.ShortDebugString() + << "} info={" << info.ShortDebugString() << '}'; + return true; +} + +bool RtmpChunkStream::OnBWDone(const RtmpMessageHeader& mh, + AMFInputStream*, + Socket* socket) { + RPC_VLOG << socket->remote_side() << "[" << mh.stream_id + << "] ignore onBWDone"; + return true; +} + +void RtmpContext::OnConnected(int error_code) { + if (_on_connect != NULL) { + void (*saved_on_connect)(int, void*) = _on_connect; + void* saved_arg = _on_connect_arg; + _on_connect = NULL; + saved_on_connect(error_code, saved_arg); + } +} + +bool RtmpChunkStream::OnResult(const RtmpMessageHeader& mh, + AMFInputStream* istream, Socket* socket) { + uint32_t transaction_id = 0; + if (!ReadAMFUint32(&transaction_id, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read _result.TransactionId"; + return false; + } + if (transaction_id < 2) { + if (transaction_id == 1) { + RtmpConnectResponse connect_res; + if (!ReadAMFObject(&connect_res, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read _result.Properties"; + return false; + } + if (!_conn_ctx->_simplified_rtmp) { + // In simplified rtmp case, connection_context()->_create_stream_with_play_or_publish + // is set and OnConnected is called in RtmpConnect::StartConnect, so we don't need + // to do these operations again here. + if (connect_res.create_stream_with_play_or_publish()) { + connection_context()->_create_stream_with_play_or_publish = true; + } + connection_context()->OnConnected(0); + } else { + CHECK(connect_res.create_stream_with_play_or_publish()); + } + } // else nothing to do with transaction_id=0 + return true; + } + if (connection_context()->unconnected()) { + RTMP_ERROR(socket, mh) << "Received _result.TransactionId=" + << transaction_id << " before connected"; + } + RtmpContext* ctx = static_cast(socket->parsing_context()); + RtmpTransactionHandler* handler = ctx->RemoveTransaction(transaction_id); + if (handler == NULL) { + RTMP_WARNING(socket, mh) << "Unknown _result.TransactionId=" + << transaction_id; + return false; + } + // TODO: Should Run return bool? + handler->Run(false, mh, istream, socket); + return true; +} + +bool RtmpChunkStream::OnError(const RtmpMessageHeader& mh, + AMFInputStream* istream, + Socket* socket) { + uint32_t transaction_id = 0; + if (!ReadAMFUint32(&transaction_id, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read _error.TransactionId"; + return false; + } + if (transaction_id < 2) { + if (transaction_id == 1) { + connection_context()->OnConnected(-1); + } // else nothing to do with transaction_id=0 + return true; + } + if (connection_context()->unconnected()) { + RTMP_ERROR(socket, mh) << "Received _error.TransactionId=" + << transaction_id << " before connected"; + } + RtmpContext* ctx = static_cast(socket->parsing_context()); + RtmpTransactionHandler* handler = ctx->RemoveTransaction(transaction_id); + if (handler == NULL) { + RTMP_WARNING(socket, mh) << "Unknown _error.TransactionId=" + << transaction_id; + return false; + } + handler->Run(true, mh, istream, socket); + return true; +} + +bool RtmpChunkStream::OnStatus(const RtmpMessageHeader& mh, + AMFInputStream* istream, + Socket* socket) { + if (!connection_context()->is_client_side()) { + RTMP_ERROR(socket, mh) << "Server-side should not receive `onStatus'"; + return false; + } + uint32_t transaction_id = 0; + if (!ReadAMFUint32(&transaction_id, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read onStatus.TransactionId"; + return false; + } + if (!ReadAMFNull(istream)) { // command object + RTMP_ERROR(socket, mh) << "Fail to read onStatus.CommandObject"; + return false; + } + RtmpInfo info; + if (!ReadAMFObject(&info, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read onStatus.InfoObject"; + return false; + } + butil::intrusive_ptr stream; + if (!connection_context()->FindMessageStream(mh.stream_id, &stream)) { + RTMP_WARNING(socket, mh) << "Fail to find stream_id=" << mh.stream_id; + return false; + } + RPC_VLOG << socket->remote_side() << "[" << mh.stream_id + << "] onStatus{" << info.ShortDebugString() << '}'; + static_cast(stream.get())->OnStatus(info); + return true; +} + +bool RtmpChunkStream::OnCreateStream(const RtmpMessageHeader& mh, + AMFInputStream* istream, + Socket* socket) { + RtmpService* service = connection_context()->service(); + if (service == NULL) { + RTMP_ERROR(socket, mh) << "Client should not receive `createStream'"; + return false; + } + double transaction_id = 0; + if (!ReadAMFNumber(&transaction_id, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read createStream.TransactionId"; + return false; + } + bool is_publish = false; + RtmpPublishType publish_type = RTMP_PUBLISH_LIVE; + std::string stream_name; + AMFObject cmd_obj; + if (!ReadAMFObject(&cmd_obj, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read createStream.CommandObject"; + return false; + } + const AMFField* cmd_name_field = cmd_obj.Find("CommandName"); + if (cmd_name_field != NULL && cmd_name_field->IsString()) { + is_publish = (cmd_name_field->AsString() == "publish"); + } + const AMFField* stream_name_field = cmd_obj.Find("StreamName"); + if (stream_name_field != NULL && stream_name_field->IsString()) { + stream_name_field->AsString().CopyToString(&stream_name); + } + if (is_publish) { + const AMFField* publish_type_field = cmd_obj.Find("PublishType"); + if (publish_type_field != NULL && publish_type_field->IsString()) { + Str2RtmpPublishType(publish_type_field->AsString(), &publish_type); + } + } + RPC_VLOG << socket->remote_side() << "[" << mh.stream_id + << "] createStream{transaction_id=" << transaction_id << '}'; + std::string error_text; + butil::intrusive_ptr stream( + service->NewStream(connection_context()->_connect_req)); + if (connection_context()->_connect_req.stream_multiplexing() && + stream != NULL) { + stream->_client_supports_stream_multiplexing = true; + } + if (NULL == stream) { + error_text = "Fail to create stream"; + LOG(ERROR) << error_text; + } else { + socket->ReAddress(&stream->_rtmpsock); + if (!connection_context()->AddServerStream(stream.get())) { + error_text = "Fail to add stream"; + LOG(ERROR) << error_text; + } else { + const int rc = bthread_id_create(&stream->_onfail_id, stream.get(), + RtmpServerStream::RunOnFailed); + if (rc) { + LOG(ERROR) << "Fail to create RtmpServerStream._onfail_id: " + << berror(rc); + stream->OnStopInternal(); + return false; + } + // Add a ref for RunOnFailed. + butil::intrusive_ptr(stream).detach(); + socket->fail_me_at_server_stop(); + socket->NotifyOnFailed(stream->_onfail_id); + } + } + // Respond createStream + butil::IOBuf req_buf; + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + WriteAMFString((!error_text.empty() ? RTMP_AMF0_COMMAND_ERROR : + RTMP_AMF0_COMMAND_RESULT), &ostream); + WriteAMFNumber(transaction_id, &ostream); + if (error_text.empty()) { + if (!stream_name.empty()) { + AMFObject cmd_obj; + cmd_obj.SetBool("PlayOrPublishAccepted", true); + WriteAMFObject(cmd_obj, &ostream); + } else { + WriteAMFNull(&ostream); + } + WriteAMFUint32(stream->stream_id(), &ostream); + } else { + WriteAMFNull(&ostream); + RtmpInfo info; + info.set_level(RTMP_INFO_LEVEL_ERROR); + // TODO(gejun): Not sure about the code. + info.set_code("NetConnection.CreateStream.Rejected"); + info.set_description(error_text); + WriteAMFObject(info, &ostream); + } + CHECK(ostream.good()); + } + SocketMessagePtr msg( + MakeUnsentControlMessage( + RTMP_MESSAGE_COMMAND_AMF0, chunk_stream_id(), req_buf)); + if (WriteWithoutOvercrowded(socket, msg) != 0) { + PLOG(WARNING) << socket->remote_side() << '[' << mh.stream_id + << "] Fail to respond createStream"; + // End the stream at server-side. + const bthread_id_t id = stream->_onfail_id; + if (id != INVALID_BTHREAD_ID) { + bthread_id_error(id, 0); + } + return false; + } + if (!error_text.empty()) { + return false; + } + if (stream_name.empty()) { + return true; + } + butil::IOBuf cmd_buf; + { + butil::IOBufAsZeroCopyOutputStream zc_ostream(&cmd_buf); + AMFOutputStream ostream(&zc_ostream); + WriteAMFUint32(0, &ostream); // TransactionId + WriteAMFNull(&ostream); // CommandObject + WriteAMFString(stream_name, &ostream); // StreamName + if (is_publish) { + WriteAMFString(RtmpPublishType2Str(publish_type), &ostream); + } + } + butil::IOBufAsZeroCopyInputStream zc_istream(cmd_buf); + AMFInputStream cmd_istream(&zc_istream); + RtmpMessageHeader header; + header.timestamp = mh.timestamp; + header.message_length = cmd_buf.size(); + header.message_type = RTMP_MESSAGE_COMMAND_AMF0; + header.stream_id = stream->stream_id(); + if (is_publish) { + return OnPublish(header, &cmd_istream, socket); + } else { + return OnPlay(header, &cmd_istream, socket); + } +} + +class OnPlayContinuation : public google::protobuf::Closure { +public: + void Run(); +public: + butil::Status status; + butil::intrusive_ptr player_stream; +}; + +void OnPlayContinuation::Run() { + std::unique_ptr delete_self(this); + + if (status.ok()) { + // nothing to do, we already sent NetStream.Play.Reset/Start stuff in + // RtmpContext::OnPlay(). Notice that we can't send those stuff here + // because user may write to players inside RtmpStreamBase::OnPlay() + // (with cached headers/metadata) before calling this Run(), which + // causes flashplayer to not work(black screen). + return; + } + + if (player_stream->SendStopMessage(status.error_cstr()) != 0) { + PLOG(WARNING) << "Fail to send StreamNotFound to " + << player_stream->remote_side(); + } + if (FLAGS_log_error_text) { + LOG(WARNING) << "Error to " << player_stream->remote_side() << '[' + << player_stream->stream_id() << "]: " << status; + } +} + +bool RtmpChunkStream::OnPlay(const RtmpMessageHeader& mh, + AMFInputStream* istream, + Socket* socket) { + if (!connection_context()->is_server_side()) { + RTMP_ERROR(socket, mh) << "Client should not receive `play'"; + return false; + } + uint32_t transaction_id = 0; + if (!ReadAMFUint32(&transaction_id, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read play.TransactionId"; + return false; + } + // ffmpeg send non-zero transaction_id for play. + + if (!ReadAMFNull(istream)) { + RTMP_ERROR(socket, mh) << "Fail to read play.CommandObject"; + return false; + } + RtmpPlayOptions play_opt; // inner fields are initialized with defaults. + if (!ReadAMFString(&play_opt.stream_name, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read play.StreamName"; + return false; + } + // start/duration/reset are optional, check emptiness of the stream before + // calling ReadAMFXXX which prints log for failed branches. + if (!istream->check_emptiness()) { + if (!ReadAMFNumber(&play_opt.start, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read play.Start"; + return false; + } + } + if (!istream->check_emptiness()) { + if (!ReadAMFNumber(&play_opt.duration, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read play.Duration"; + return false; + } + } + if (!istream->check_emptiness()) { + if (!ReadAMFBool(&play_opt.reset, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read play.Reset"; + return false; + } + } + RPC_VLOG << socket->remote_side() << "[" << mh.stream_id + << "] play{transaction_id=" << transaction_id + << " stream_name=" << play_opt.stream_name + << " start=" << play_opt.start + << " duration=" << play_opt.duration + << " reset=" << play_opt.reset << '}'; + + butil::IOBuf req_buf; + TemporaryArrayBuilder, 5> msgs; + + // TODO(gejun): RTMP spec sends StreamIsRecorded before StreamBegin + // however SRS does not. + // StreamBegin + { + char cntl_buf[6]; + char* p = cntl_buf; + WriteBigEndian2Bytes(&p, RTMP_USER_CONTROL_EVENT_STREAM_BEGIN); + WriteBigEndian4Bytes(&p, mh.stream_id); + msgs.push().reset(MakeUnsentControlMessage( + RTMP_MESSAGE_USER_CONTROL, cntl_buf, sizeof(cntl_buf))); + } + // Play.Reset + if (play_opt.reset) { + // According to RTMP spec: NetStream.Play.Reset is sent only if the + // play command sent by the client has set the reset flag. + req_buf.clear(); + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + WriteAMFString(RTMP_AMF0_COMMAND_ON_STATUS, &ostream); + WriteAMFUint32(0, &ostream); + WriteAMFNull(&ostream); + RtmpInfo info; + info.set_code(RTMP_STATUS_CODE_PLAY_RESET); + info.set_level(RTMP_INFO_LEVEL_STATUS); + info.set_description("Reset " + play_opt.stream_name); + WriteAMFObject(info, &ostream); + } + RtmpUnsentMessage* msg = new RtmpUnsentMessage; + msg->header.message_length = req_buf.size(); + msg->header.message_type = RTMP_MESSAGE_COMMAND_AMF0; + msg->header.stream_id = mh.stream_id; + msg->chunk_stream_id = chunk_stream_id(); + msg->body = req_buf; + msgs.push().reset(msg); + } + + // Play.Start + req_buf.clear(); + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + WriteAMFString(RTMP_AMF0_COMMAND_ON_STATUS, &ostream); + WriteAMFUint32(0, &ostream); + WriteAMFNull(&ostream); + RtmpInfo info; + info.set_code(RTMP_STATUS_CODE_PLAY_START); + info.set_level(RTMP_INFO_LEVEL_STATUS); + info.set_description("Start playing " + play_opt.stream_name); + WriteAMFObject(info, &ostream); + } + RtmpUnsentMessage* msg2 = new RtmpUnsentMessage; + msg2->header.message_length = req_buf.size(); + msg2->header.message_type = RTMP_MESSAGE_COMMAND_AMF0; + msg2->header.stream_id = mh.stream_id; + msg2->chunk_stream_id = chunk_stream_id(); + msg2->body = req_buf; + msgs.push().reset(msg2); + + // |RtmpSampleAccess(true, true) + req_buf.clear(); + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + WriteAMFString(RTMP_AMF0_SAMPLE_ACCESS, &ostream); + WriteAMFBool(true, &ostream); + WriteAMFBool(true, &ostream); + } + RtmpUnsentMessage* msg3 = new RtmpUnsentMessage; + msg3->header.message_length = req_buf.size(); + msg3->header.message_type = RTMP_MESSAGE_DATA_AMF0; + msg3->header.stream_id = mh.stream_id; + msg3->chunk_stream_id = chunk_stream_id(); + msg3->body = req_buf; + msgs.push().reset(msg3); + + // onStatus(NetStream.Data.Start) + req_buf.clear(); + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + WriteAMFString(RTMP_AMF0_COMMAND_ON_STATUS, &ostream); + RtmpInfo info; + info.set_code(RTMP_STATUS_CODE_DATA_START); + WriteAMFObject(info, &ostream); + } + RtmpUnsentMessage* msg4 = new RtmpUnsentMessage; + msg4->header.message_length = req_buf.size(); + msg4->header.message_type = RTMP_MESSAGE_DATA_AMF0; + msg4->header.stream_id = mh.stream_id; + msg4->chunk_stream_id = chunk_stream_id(); + msg4->body = req_buf; + msgs.push().reset(msg4); + + butil::intrusive_ptr stream_guard; + if (!connection_context()->FindMessageStream(mh.stream_id, &stream_guard)) { + RTMP_WARNING(socket, mh) << "Fail to find stream_id=" << mh.stream_id; + return false; + } + // Change the chunk_stream_id of the server stream to be same with play, + // so that laterly user can call SendXXXMessage successfully. + stream_guard->_chunk_stream_id = chunk_stream_id(); + RtmpServerStream* stream = static_cast(stream_guard.get()); + + for (size_t i = msgs.size(); i > 1; --i) { + msgs[i-2]->next.reset(msgs[i-1].release()); + } + if (WriteWithoutOvercrowded(socket, msgs[0]) != 0) { + PLOG(WARNING) << socket->remote_side() << '[' << mh.stream_id + << "] Fail to respond play"; + return false; + } + // cyberplayer sends play instead of unpause (and send closeStream instead + // of pause). play automatically unpauses + if (stream->_paused) { + stream->_paused = false; + RPC_VLOG << "Trigger unpause"; + stream->OnPause(false, 0); + } + // Call user's callback. + OnPlayContinuation* done = new OnPlayContinuation; + done->player_stream.reset(stream, false/*don't add ref*/); + stream_guard.detach(); + done->player_stream->OnPlay(play_opt, &done->status, done); + return true; +} + +bool RtmpChunkStream::OnPlay2(const RtmpMessageHeader& mh, + AMFInputStream* istream, + Socket* socket) { + if (!connection_context()->is_server_side()) { + RTMP_ERROR(socket, mh) << "Client should not receive `play2'"; + return false; + } + uint32_t transaction_id = 0; + if (!ReadAMFUint32(&transaction_id, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read play2.TransactionId"; + return false; + } + if (!ReadAMFNull(istream)) { + RTMP_ERROR(socket, mh) << "Fail to read play2.CommandObject"; + return false; + } + RtmpPlay2Options play2_options; + if (!ReadAMFObject(&play2_options, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read play2.Parameters"; + return false; + } + butil::intrusive_ptr stream; + if (!connection_context()->FindMessageStream(mh.stream_id, &stream)) { + RTMP_WARNING(socket, mh) << "Fail to find stream_id=" << mh.stream_id; + return false; + } + static_cast(stream.get())->OnPlay2(play2_options); + return true; +} + +bool RtmpChunkStream::OnDeleteStream(const RtmpMessageHeader& mh, + AMFInputStream* istream, + Socket* socket) { + if (!connection_context()->is_server_side()) { + RTMP_ERROR(socket, mh) << "Client should not receive `deleteStream'"; + return false; + } + uint32_t transaction_id = 0; + if (!ReadAMFUint32(&transaction_id, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read deleteStream.TransactionId"; + return false; + } + if (!ReadAMFNull(istream)) { // command object + RTMP_ERROR(socket, mh) << "Fail to read deleteStream.CommandObject"; + return false; + } + uint32_t stream_id = 0; + if (!ReadAMFUint32(&stream_id, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read deleteStream.StreamId"; + return false; + } + butil::intrusive_ptr stream; + if (!connection_context()->FindMessageStream(stream_id, &stream)) { + // TODO: frequent, commented now + //RTMP_WARNING(socket, mh) << "Fail to find stream_id=" << stream_id; + return false; + } + bthread_id_t id = static_cast(stream.get())->_onfail_id; + if (id != INVALID_BTHREAD_ID) { + bthread_id_error(id, 0); + } + return true; +} + +bool RtmpChunkStream::OnCloseStream(const RtmpMessageHeader& mh, + AMFInputStream* istream, + Socket* socket) { + if (!connection_context()->is_server_side()) { + RTMP_ERROR(socket, mh) << "Client should not receive `closeStream'"; + return false; + } + uint32_t transaction_id = 0; + if (!ReadAMFUint32(&transaction_id, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read closeStream.TransactionId"; + return false; + } + if (!ReadAMFNull(istream)) { // command object + RTMP_ERROR(socket, mh) << "Fail to read closeStream.CommandObject"; + return false; + } + butil::intrusive_ptr stream; + if (!connection_context()->FindMessageStream(mh.stream_id, &stream)) { + // TODO: frequent, commented now + //RTMP_WARNING(socket, mh) << "Fail to find stream_id=" << mh.stream_id; + return false; + } + if (!stream->_paused) { + stream->_paused = true; + // TODO(gejun): Run in execq. + static_cast(stream.get())->OnPause(true, 0); + } + return true; +} + +class OnPublishContinuation : public google::protobuf::Closure { +public: + void Run(); +public: + butil::Status status; + std::string publish_name; + butil::intrusive_ptr publish_stream; +}; + +void OnPublishContinuation::Run() { + std::unique_ptr delete_self(this); + if (!status.ok()) { + if (publish_stream->SendStopMessage(status.error_cstr()) != 0) { + PLOG(WARNING) << "Fail to send StreamNotFound to " + << publish_stream->remote_side(); + } + if (FLAGS_log_error_text) { + LOG(WARNING) << "Error to " << publish_stream->remote_side() + << '[' << publish_stream->stream_id() << "]: " + << status; + } + return; + } + butil::IOBuf req_buf; + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + WriteAMFString(RTMP_AMF0_COMMAND_ON_STATUS, &ostream); + WriteAMFUint32(0, &ostream); + WriteAMFNull(&ostream); + RtmpInfo info; + info.set_code(RTMP_STATUS_CODE_PUBLISH_START); + info.set_level(RTMP_INFO_LEVEL_STATUS); + info.set_description("Started publishing " + publish_name); + WriteAMFObject(info, &ostream); + CHECK(ostream.good()); + } + SocketMessagePtr msg(new RtmpUnsentMessage); + msg->header.message_length = req_buf.size(); + msg->header.message_type = RTMP_MESSAGE_COMMAND_AMF0; + msg->header.stream_id = publish_stream->stream_id(); + msg->chunk_stream_id = publish_stream->chunk_stream_id(); + msg->body = req_buf; + + if (WriteWithoutOvercrowded(publish_stream->socket(), msg) != 0) { + PLOG(WARNING) << publish_stream->remote_side() << '[' + << publish_stream->stream_id() << "] Fail to respond publish"; + } +} + +bool RtmpChunkStream::OnPublish(const RtmpMessageHeader& mh, + AMFInputStream* istream, + Socket* socket) { + if (!connection_context()->is_server_side()) { + RTMP_ERROR(socket, mh) << "Client should not receive `publish'"; + return false; + } + uint32_t transaction_id = 0; + if (!ReadAMFUint32(&transaction_id, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read publish.TransactionId"; + return false; + } + if (!ReadAMFNull(istream)) { // command object + RTMP_ERROR(socket, mh) << "Fail to read publish.CommandObject"; + return false; + } + std::string publish_name; + if (!ReadAMFString(&publish_name, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read publish.PublishName"; + return false; + } + std::string publish_type_str; + if (!ReadAMFString(&publish_type_str, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read publish.PublishType"; + return false; + } + RtmpPublishType publish_type; + if (!Str2RtmpPublishType(publish_type_str, &publish_type)) { + RTMP_ERROR(socket, mh) << "Invalid publish_type=" << publish_type_str; + return false; + } + + RPC_VLOG << socket->remote_side() << "[" << mh.stream_id + << "] publish{transaction_id=" << transaction_id + << " stream_name=" << publish_name + << " type=" << RtmpPublishType2Str(publish_type) << '}'; + + butil::intrusive_ptr stream_guard; + if (!connection_context()->FindMessageStream(mh.stream_id, &stream_guard)) { + RTMP_WARNING(socket, mh) << "Fail to find stream_id=" << mh.stream_id; + return false; + } + // Change the chunk_stream_id of the server stream to be same with publish, + // so that laterly user can call SendStopMessage successfully. + stream_guard->_chunk_stream_id = chunk_stream_id(); + RtmpServerStream* stream = static_cast(stream_guard.get()); + stream->_is_publish = true; + OnPublishContinuation* done = new OnPublishContinuation; + done->publish_name = publish_name; + done->publish_stream.reset(stream, false/*not add ref*/); + stream_guard.detach(); + stream->OnPublish(publish_name, publish_type, &done->status, done); + return true; +} + +static bool SendFMLEStartResponse(Socket* sock, double transaction_id) { + butil::IOBuf req_buf; + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + WriteAMFString(RTMP_AMF0_COMMAND_RESULT, &ostream); + WriteAMFNumber(transaction_id, &ostream); + WriteAMFNull(&ostream); + WriteAMFUndefined(&ostream); + CHECK(ostream.good()); + } + SocketMessagePtr msg( + MakeUnsentControlMessage(RTMP_MESSAGE_COMMAND_AMF0, req_buf)); + if (sock->Write(msg) != 0) { + PLOG(WARNING) << sock->remote_side() << ": Fail to respond FMLEStart"; + return false; + } + return true; +} + +bool RtmpChunkStream::OnReleaseStream( + const RtmpMessageHeader& mh, AMFInputStream* istream, Socket* socket) { + if (!connection_context()->is_server_side()) { + RTMP_ERROR(socket, mh) << "Client should not receive `releaseStream'"; + return false; + } + double transaction_id = 0; + if (!ReadAMFNumber(&transaction_id, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read releaseStream.TransactionId"; + return false; + } + if (!ReadAMFNull(istream)) { // command object + RTMP_ERROR(socket, mh) << "Fail to read releaseStream.CommandObject"; + return false; + } + std::string stream_name; + if (!ReadAMFString(&stream_name, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read releaseStream.StreamName"; + return false; + } + RTMP_WARNING(socket, mh) << "Ignored releaseStream(" << stream_name << ')'; + return SendFMLEStartResponse(socket, transaction_id); +} + +bool RtmpChunkStream::OnFCPublish( + const RtmpMessageHeader& mh, AMFInputStream* istream, Socket* socket) { + if (!connection_context()->is_server_side()) { + RTMP_ERROR(socket, mh) << "Client should not receive `FCPublish'"; + return false; + } + double transaction_id = 0; + if (!ReadAMFNumber(&transaction_id, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read FCPublish.TransactionId"; + return false; + } + if (!ReadAMFNull(istream)) { // command object + RTMP_ERROR(socket, mh) << "Fail to read FCPublish.CommandObject"; + return false; + } + std::string stream_name; + if (!ReadAMFString(&stream_name, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read FCPublish.StreamName"; + return false; + } + RTMP_WARNING(socket, mh) << "Ignored FCPublish(" << stream_name << ')'; + return SendFMLEStartResponse(socket, transaction_id); +} + +bool RtmpChunkStream::OnFCUnpublish( + const RtmpMessageHeader& mh, AMFInputStream* istream, Socket* socket) { + if (!connection_context()->is_server_side()) { + RTMP_ERROR(socket, mh) << "Client should not receive `FCUnpublish'"; + return false; + } + double transaction_id = 0; + if (!ReadAMFNumber(&transaction_id, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read FCUnpublish.TransactionId"; + return false; + } + if (!ReadAMFNull(istream)) { // command object + RTMP_ERROR(socket, mh) << "Fail to read FCUnpublish.CommandObject"; + return false; + } + std::string stream_name; + if (!ReadAMFString(&stream_name, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read FCUnpublish.StreamName"; + return false; + } + RTMP_WARNING(socket, mh) << "Ignored FCUnpublish(" << stream_name << ')'; + return SendFMLEStartResponse(socket, transaction_id); +} + +bool RtmpChunkStream::OnGetStreamLength( + const RtmpMessageHeader&, AMFInputStream*, Socket*) { + // In (non-live) streams with no metadata, the duration of a stream can + // be retrieved by calling the RTMP function getStreamLength with the + // playpath. The server will return a positive duration upon the request if + // the duration is known, otherwise either no response or a duration of 0 + // will be returned. + + // Just ignore the command. + return true; +} + +bool RtmpChunkStream::OnCheckBW( + const RtmpMessageHeader&, AMFInputStream*, Socket*) { + return true; +} + +bool RtmpChunkStream::OnSeek(const RtmpMessageHeader& mh, + AMFInputStream* istream, + Socket* socket) { + if (!connection_context()->is_server_side()) { + RTMP_ERROR(socket, mh) << "Client should not receive `seek'"; + return false; + } + uint32_t transaction_id = 0; + if (!ReadAMFUint32(&transaction_id, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read seek.TransactionId"; + return false; + } + if (!ReadAMFNull(istream)) { // command object + RTMP_ERROR(socket, mh) << "Fail to read seek.CommandObject"; + return false; + } + double milliseconds = 0; + if (!ReadAMFNumber(&milliseconds, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read seek.milliSeconds"; + return false; + } + + butil::intrusive_ptr stream; + if (!connection_context()->FindMessageStream(mh.stream_id, &stream)) { + RTMP_WARNING(socket, mh) << "Fail to find stream_id=" << mh.stream_id; + return false; + } + // TODO(gejun): Run in execq. + int rc = static_cast(stream.get())->OnSeek(milliseconds); + butil::IOBuf req_buf; + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + if (rc == 0) { + WriteAMFString(RTMP_AMF0_COMMAND_ON_STATUS, &ostream); + WriteAMFUint32(0, &ostream); + WriteAMFNull(&ostream); + RtmpInfo info; + info.set_code(RTMP_STATUS_CODE_STREAM_SEEK); + info.set_level(RTMP_INFO_LEVEL_STATUS); + info.set_description("Seek successfully."); + WriteAMFObject(info, &ostream); + CHECK(ostream.good()); + } else { + WriteAMFString(RTMP_AMF0_COMMAND_ERROR, &ostream); + WriteAMFNumber(0, &ostream); + WriteAMFNull(&ostream); + RtmpInfo info; + info.set_level(RTMP_INFO_LEVEL_ERROR); + info.set_code(RTMP_STATUS_CODE_STREAM_SEEK); // TODO + info.set_description("Fail to seek"); + WriteAMFObject(info, &ostream); + CHECK(ostream.good()); + } + } + SocketMessagePtr msg(new RtmpUnsentMessage); + msg->header.message_length = req_buf.size(); + msg->header.message_type = RTMP_MESSAGE_COMMAND_AMF0; + msg->header.stream_id = mh.stream_id; + msg->chunk_stream_id = chunk_stream_id(); + msg->body = req_buf; + + if (socket->Write(msg) != 0) { + PLOG(WARNING) << socket->remote_side() << ": Fail to respond seek"; + return false; + } + return (rc == 0); +} + +bool RtmpChunkStream::OnPause(const RtmpMessageHeader& mh, + AMFInputStream* istream, + Socket* socket) { + if (!connection_context()->is_server_side()) { + RTMP_ERROR(socket, mh) << "Client should not receive `pause'"; + return false; + } + uint32_t transaction_id = 0; + if (!ReadAMFUint32(&transaction_id, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read pause.TransactionId"; + return false; + } + if (!ReadAMFNull(istream)) { // command object + RTMP_ERROR(socket, mh) << "Fail to read pause.CommandObject"; + return false; + } + bool pause_or_unpause = true; + if (!ReadAMFBool(&pause_or_unpause, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read pause/unpause flag"; + return false; + } + double milliseconds = 0; + if (!ReadAMFNumber(&milliseconds, istream)) { + RTMP_ERROR(socket, mh) << "Fail to read pause.milliSeconds"; + return false; + } + + butil::intrusive_ptr stream; + if (!connection_context()->FindMessageStream(mh.stream_id, &stream)) { + RTMP_WARNING(socket, mh) << "Fail to find stream_id=" << mh.stream_id; + return false; + } + if (stream->_paused == pause_or_unpause) { + if (pause_or_unpause) { + RTMP_ERROR(socket, mh) << "Pause an already paused stream"; + } else { + RTMP_ERROR(socket, mh) << "Unpause an already unpaused stream"; + } + return false; + } + int rc = static_cast(stream.get())->OnPause( + pause_or_unpause, milliseconds); + + // Send back status. + butil::IOBuf req_buf; + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + if (rc == 0) { + WriteAMFString(RTMP_AMF0_COMMAND_ON_STATUS, &ostream); + WriteAMFUint32(0, &ostream); + WriteAMFNull(&ostream); + RtmpInfo info; + if (pause_or_unpause) { + info.set_code(RTMP_STATUS_CODE_STREAM_PAUSE); + } else { + info.set_code(RTMP_STATUS_CODE_STREAM_UNPAUSE); + } + info.set_level(RTMP_INFO_LEVEL_STATUS); + info.set_description("Paused stream."); + WriteAMFObject(info, &ostream); + CHECK(ostream.good()); + } else { + WriteAMFString(RTMP_AMF0_COMMAND_ERROR, &ostream); + WriteAMFNumber(0, &ostream); + WriteAMFNull(&ostream); + RtmpInfo info; + if (pause_or_unpause) { + info.set_code(RTMP_STATUS_CODE_STREAM_PAUSE); + } else { + info.set_code(RTMP_STATUS_CODE_STREAM_UNPAUSE); + } + info.set_level(RTMP_INFO_LEVEL_ERROR); + info.set_description(pause_or_unpause ? "Fail to pause" : + "Fail to unpause"); + WriteAMFObject(info, &ostream); + CHECK(ostream.good()); + } + } + SocketMessagePtr msg1(new RtmpUnsentMessage); + msg1->header.message_length = req_buf.size(); + msg1->header.message_type = RTMP_MESSAGE_COMMAND_AMF0; + msg1->header.stream_id = mh.stream_id; + msg1->chunk_stream_id = chunk_stream_id(); + msg1->body = req_buf; + + // StreamEOF(pause) or StreamBegin(unpause) + char cntl_buf[6]; + char* p = cntl_buf; + if (pause_or_unpause) { + WriteBigEndian2Bytes(&p, RTMP_USER_CONTROL_EVENT_STREAM_EOF); + } else { + WriteBigEndian2Bytes(&p, RTMP_USER_CONTROL_EVENT_STREAM_BEGIN); + } + WriteBigEndian4Bytes(&p, mh.stream_id); + RtmpUnsentMessage* msg2 = MakeUnsentControlMessage( + RTMP_MESSAGE_USER_CONTROL, cntl_buf, sizeof(cntl_buf)); + msg1->next.reset(msg2); + + if (WriteWithoutOvercrowded(socket, msg1) != 0) { + PLOG(WARNING) << socket->remote_side() << '[' << mh.stream_id + << "] Fail to respond " << (pause_or_unpause ? "pause" : "unpause"); + return false; + } + if (rc == 0) { + stream->_paused = pause_or_unpause; + return true; + } + return false; +} + +// ============== protocol handlers ============= + +inline ParseResult IsPossiblyRtmp(const butil::IOBuf* source) { + const char* p = (const char*)source->fetch1(); + if (p == NULL) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + if (*p != RTMP_DEFAULT_VERSION) { + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + return MakeMessage(NULL); +} + +ParseResult ParseRtmpMessage(butil::IOBuf* source, Socket *socket, bool read_eof, + const void* arg) { + RtmpContext* rtmp_ctx = static_cast(socket->parsing_context()); + if (rtmp_ctx == NULL) { + if (arg == NULL) { + // We are probably parsing another client-side protocol. + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + const Server* server = static_cast(arg); + RtmpService* service = server->options().rtmp_service; + if (service == NULL) { + // Validating RTMP protocol only checks the first byte, which + // is very easy to be confused with other protocols. Currently + // if rtmp_service is not set, the protocol is skipped w/o any + // warning. We'll register the protocol on demand in later CI + // to avoid the confusion from root. + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + if (read_eof) { + // No former RtmpContext when we read an EOF + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + const ParseResult r = IsPossiblyRtmp(source); + if (!r.is_ok()) { + return r; + } + rtmp_ctx = new (std::nothrow) RtmpContext(NULL, server); + if (rtmp_ctx == NULL) { + LOG(FATAL) << "Fail to new RtmpContext"; + return MakeParseError(PARSE_ERROR_NO_RESOURCE); + } + socket->reset_parsing_context(rtmp_ctx); + // We don't need to customize app_connect at server-side. + } + return rtmp_ctx->Feed(source, socket); +} + +void ProcessRtmpMessage(InputMessageBase*) { + CHECK(false) << "Should never be called"; +} + +class OnServerStreamCreated : public RtmpTransactionHandler { +public: + OnServerStreamCreated(RtmpClientStream* stream, CallId call_id); + void Run(bool error, const RtmpMessageHeader& mh, + AMFInputStream* istream, Socket* socket); + void Cancel(); +private: + butil::intrusive_ptr _stream; + CallId _call_id; +}; + +OnServerStreamCreated::OnServerStreamCreated( + RtmpClientStream* stream, CallId call_id) + : _stream(stream), _call_id(call_id) {} + +void OnServerStreamCreated::Run(bool error, + const RtmpMessageHeader&, + AMFInputStream* istream, + Socket* socket) { + std::unique_ptr delete_self(this); + // End the createStream call. + RtmpContext* ctx = static_cast(socket->parsing_context()); + if (ctx == NULL) { + LOG(FATAL) << "RtmpContext must be created"; + return; + } + const int64_t start_parse_us = butil::cpuwide_time_us(); + // TODO(gejun): Don't have received time right now. + const int64_t received_us = start_parse_us; + const int64_t base_realtime = butil::gettimeofday_us() - received_us; + const bthread_id_t cid = _call_id; + Controller* cntl = NULL; + const int rc = bthread_id_lock(cid, (void**)&cntl); + if (rc != 0) { + LOG_IF(ERROR, rc != EINVAL && rc != EPERM) + << "Fail to lock correlation_id=" << cid << ": " << berror(rc); + return; + } + + ControllerPrivateAccessor accessor(cntl); + const int saved_error = cntl->ErrorCode(); + do { + AMFObject cmd_obj; + if (!ReadAMFObject(&cmd_obj, istream)) { + cntl->SetFailed(ERESPONSE, "Fail to read the command object"); + break; + } + const AMFField* field = cmd_obj.Find("PlayOrPublishAccepted"); + if (field != NULL && field->IsBool() && field->AsBool()) { + _stream->_created_stream_with_play_or_publish = true; + } + if (error) { + RtmpInfo info; + if (!ReadAMFObject(&info, istream)) { + cntl->SetFailed(ERESPONSE, "Fail to read the info object"); + break; + } + cntl->SetFailed(ERTMPCREATESTREAM, "%s: %s", info.code().c_str(), + info.description().c_str()); + break; + } + uint32_t stream_id = 0; + if (!ReadAMFUint32(&stream_id, istream)) { + cntl->SetFailed(ERESPONSE, "Fail to read stream_id"); + break; + } + _stream->_message_stream_id = stream_id; + // client stream needs to be added here rather than OnDestroyingStream + // to avoid the race between OnDestroyingStream and a failed OnStatus, + // because the former function runs in another bthread and may run later + // than OnStatus which needs to see the stream. + if (!ctx->AddClientStream(_stream.get())) { + cntl->SetFailed(EINVAL, "Fail to add client stream_id=%u", stream_id); + break; + } + } while (0); + if (auto span = accessor.span()) { + span->set_base_real_us(base_realtime); + span->set_received_us(received_us); + span->set_response_size(istream->popped_bytes()); + span->set_start_parse_us(start_parse_us); + } + // Unlocks correlation_id inside. Revert controller's + // error code if it version check of `cid' fails. + // Can't call accessor.OnResponse which does not new bthread. + const Controller::CompletionInfo info = { cid, true }; + cntl->OnVersionedRPCReturned(info, true, saved_error); +} + +void OnServerStreamCreated::Cancel() { + delete this; +} + +butil::Status +RtmpCreateStreamMessage::AppendAndDestroySelf(butil::IOBuf* out, Socket* s) { + std::unique_ptr destroy_self(this); + if (s == NULL) { // abandoned + return butil::Status::OK(); + } + // Serialize createStream command + RtmpContext* ctx = static_cast(socket->parsing_context()); + if (ctx == NULL) { + return butil::Status(EINVAL, "RtmpContext of %s is not created", + socket->description().c_str()); + } + butil::IOBuf req_buf; + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + WriteAMFString(RTMP_AMF0_COMMAND_CREATE_STREAM, &ostream); + WriteAMFUint32(transaction_id, &ostream); + // Notice that we can't directly pack play/publish command as the + // command object, because SRS at source site(for publishing) only + // accepts null command objects, although non-null command objects + // are allowed in RTMP spec. + // One limitation of current implementation is that even if the server + // accepts play/publish along with createStream, the first createStream + // command will not carry play/publish information, because connection + // is established lazily, when first createStream is being packed, + // connect command is not sent yet and capability of the server is + // unknown. + if (ctx->can_stream_be_created_with_play_or_publish()) { + AMFObject cmd_obj; + if (!options.publish_name.empty()) { + cmd_obj.SetString("CommandName", "publish"); + cmd_obj.SetString("StreamName", options.publish_name); + cmd_obj.SetString("PublishType", + RtmpPublishType2Str(options.publish_type)); + WriteAMFObject(cmd_obj, &ostream); + } else if (!options.play_name.empty()) { + cmd_obj.SetString("CommandName", "play"); + cmd_obj.SetString("StreamName", options.play_name); + WriteAMFObject(cmd_obj, &ostream); + } else { + WriteAMFNull(&ostream); + } + } else { + WriteAMFNull(&ostream); + } + CHECK(ostream.good()); + } + RtmpChunkStream* cstream = ctx->GetChunkStream(RTMP_CONTROL_CHUNK_STREAM_ID); + if (cstream == NULL) { + socket->SetFailed(EINVAL, "Invalid chunk_stream_id=%u", + RTMP_CONTROL_CHUNK_STREAM_ID); + return butil::Status(EINVAL, "Invalid chunk_stream_id=%u", + RTMP_CONTROL_CHUNK_STREAM_ID); + } + RtmpMessageHeader header; + header.message_length = req_buf.size(); + header.message_type = RTMP_MESSAGE_COMMAND_AMF0; + header.stream_id = RTMP_CONTROL_MESSAGE_STREAM_ID; + if (cstream->SerializeMessage(out, header, &req_buf) != 0) { + socket->SetFailed(EINVAL, "Fail to serialize message"); + return butil::Status(EINVAL, "Fail to serialize message"); + } + return butil::Status::OK(); +} + +void PackRtmpRequest(butil::IOBuf* /*buf*/, + SocketMessage** user_message, + uint64_t /*correlation_id*/, + const google::protobuf::MethodDescriptor* /*NULL*/, + Controller* cntl, + const butil::IOBuf& /*request*/, + const Authenticator*) { + // Send createStream command + ControllerPrivateAccessor accessor(cntl); + Socket* s = accessor.get_sending_socket(); + RtmpContext* ctx = static_cast(s->parsing_context()); + if (ctx == NULL) { + cntl->SetFailed(EINVAL, "RtmpContext of %s is not created", + s->description().c_str()); + return; + } + // Hack: we pass stream as response in RtmpClientStream::Create + RtmpClientStream* stream = (RtmpClientStream*)cntl->response(); + + // Hack: save last transaction_id into log_id(useless here) so that we + // can get it back and cancel the transaction before creating new one + // (for retrying). + CHECK_LT(cntl->log_id(), (uint64_t)std::numeric_limits::max()); + uint32_t transaction_id = cntl->log_id(); + if (transaction_id != 0) { + RtmpTransactionHandler* last_handler = + ctx->RemoveTransaction(transaction_id); + if (last_handler) { + last_handler->Cancel(); + } + } + OnServerStreamCreated* cb = new OnServerStreamCreated(stream, cntl->call_id()); + if (!ctx->AddTransaction(&transaction_id, cb)) { + cntl->SetFailed(EINVAL, "Fail to add transaction"); + delete cb; + return; + } + cntl->set_log_id(transaction_id); + RtmpCreateStreamMessage* msg = new RtmpCreateStreamMessage; + s->ReAddress(&msg->socket); + msg->transaction_id = transaction_id; + msg->options = stream->options(); + *user_message = msg; +} + +void SerializeRtmpRequest(butil::IOBuf* /*buf*/, + Controller* /*cntl*/, + const google::protobuf::Message* /*NULL*/) { +} + +} // namespace policy +} // namespace brpc + + +#undef RTMP_LOG +#undef RTMP_ERROR +#undef RTMP_WARNING diff --git a/src/brpc/policy/rtmp_protocol.h b/src/brpc/policy/rtmp_protocol.h new file mode 100644 index 0000000..b5572c2 --- /dev/null +++ b/src/brpc/policy/rtmp_protocol.h @@ -0,0 +1,641 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_RTMP_PROTOCOL_H +#define BRPC_POLICY_RTMP_PROTOCOL_H + +#include "butil/containers/flat_map.h" +#include "brpc/protocol.h" +#include "brpc/rtmp.h" +#include "brpc/amf.h" +#include "brpc/socket.h" + + +namespace brpc { + +class Server; +class RtmpService; +class RtmpStreamBase; + +namespace policy { + +const uint32_t RTMP_DEFAULT_CHUNK_SIZE = 60000; // Copy from SRS +const uint32_t RTMP_DEFAULT_WINDOW_ACK_SIZE = 2500000; // Copy from SRS +const uint32_t RTMP_MAX_CHUNK_STREAM_ID = 65599; +const uint32_t RTMP_CHUNK_ARRAY_2ND_SIZE = 256; +const uint32_t RTMP_CHUNK_ARRAY_1ST_SIZE = + (RTMP_MAX_CHUNK_STREAM_ID + RTMP_CHUNK_ARRAY_2ND_SIZE) + / RTMP_CHUNK_ARRAY_2ND_SIZE; +const uint32_t RTMP_CONTROL_CHUNK_STREAM_ID = 2; +const uint32_t RTMP_CONTROL_MESSAGE_STREAM_ID = 0; + +enum RtmpMessageType { + RTMP_MESSAGE_SET_CHUNK_SIZE = 1, + RTMP_MESSAGE_ABORT = 2, + RTMP_MESSAGE_ACK = 3, + RTMP_MESSAGE_USER_CONTROL = 4, + RTMP_MESSAGE_WINDOW_ACK_SIZE = 5, + RTMP_MESSAGE_SET_PEER_BANDWIDTH = 6, + RTMP_MESSAGE_AUDIO = 8, + RTMP_MESSAGE_VIDEO = 9, + RTMP_MESSAGE_DATA_AMF3 = 15, + RTMP_MESSAGE_SHARED_OBJECT_AMF3 = 16, + RTMP_MESSAGE_COMMAND_AMF3 = 17, + RTMP_MESSAGE_DATA_AMF0 = 18, + RTMP_MESSAGE_SHARED_OBJECT_AMF0 = 19, + RTMP_MESSAGE_COMMAND_AMF0 = 20, + RTMP_MESSAGE_AGGREGATE = 22, +}; + +inline bool is_video_frame_type_valid(FlvVideoFrameType t) { + return (t >= FLV_VIDEO_FRAME_KEYFRAME && t <= FLV_VIDEO_FRAME_INFOFRAME); +} + +inline bool is_video_codec_valid(FlvVideoCodec id) { + return (id >= FLV_VIDEO_JPEG && id <= FLV_VIDEO_HEVC); +} + +// Get literal form of the message type. +const char* messagetype2str(RtmpMessageType); +const char* messagetype2str(uint8_t); + +// Define string constants as macros rather than consts because macros +// are concatenatable. +#define RTMP_SIG_FMS_VER "3,5,3,888" +#define RTMP_SIG_CLIENT_ID "ASAICiss" + +#define RTMP_STATUS_CODE_CONNECT_SUCCESS "NetConnection.Connect.Success" +#define RTMP_STATUS_CODE_CONNECT_REJECTED "NetConnection.Connect.Rejected" +#define RTMP_STATUS_CODE_PLAY_RESET "NetStream.Play.Reset" +#define RTMP_STATUS_CODE_PLAY_START "NetStream.Play.Start" +#define RTMP_STATUS_CODE_STREAM_NOT_FOUND "NetStream.Play.StreamNotFound" +#define RTMP_STATUS_CODE_STREAM_PAUSE "NetStream.Pause.Notify" +#define RTMP_STATUS_CODE_STREAM_UNPAUSE "NetStream.Unpause.Notify" +#define RTMP_STATUS_CODE_PUBLISH_START "NetStream.Publish.Start" +#define RTMP_STATUS_CODE_DATA_START "NetStream.Data.Start" +#define RTMP_STATUS_CODE_UNPUBLISH_SUCCESS "NetStream.Unpublish.Success" +#define RTMP_STATUS_CODE_STREAM_SEEK "NetStream.Seek.Notify" + +#define RTMP_AMF0_COMMAND_CONNECT "connect" +#define RTMP_AMF0_COMMAND_CREATE_STREAM "createStream" +#define RTMP_AMF0_COMMAND_CLOSE_STREAM "closeStream" // SRS has this. +#define RTMP_AMF0_COMMAND_DELETE_STREAM "deleteStream" +#define RTMP_AMF0_COMMAND_PLAY "play" +#define RTMP_AMF0_COMMAND_PLAY2 "play2" +#define RTMP_AMF0_COMMAND_SEEK "seek" +#define RTMP_AMF0_COMMAND_PAUSE "pause" +#define RTMP_AMF0_COMMAND_ON_BW_DONE "onBWDone" +#define RTMP_AMF0_COMMAND_ON_STATUS "onStatus" +#define RTMP_AMF0_COMMAND_RESULT "_result" +#define RTMP_AMF0_COMMAND_ERROR "_error" +#define RTMP_AMF0_COMMAND_RELEASE_STREAM "releaseStream" +#define RTMP_AMF0_COMMAND_FC_PUBLISH "FCPublish" +#define RTMP_AMF0_COMMAND_FC_UNPUBLISH "FCUnpublish" +#define RTMP_AMF0_COMMAND_GET_STREAM_LENGTH "getStreamLength" +#define RTMP_AMF0_COMMAND_CHECK_BW "_checkbw" +#define RTMP_AMF0_COMMAND_PUBLISH "publish" +#define RTMP_AMF0_DATA_SAMPLE_ACCESS "|RtmpSampleAccess" +#define RTMP_AMF0_COMMAND_CALL "call" +#define RTMP_AMF0_SET_DATAFRAME "@setDataFrame" +#define RTMP_AMF0_ON_META_DATA "onMetaData" +#define RTMP_AMF0_ON_CUE_POINT "onCuePoint" +#define RTMP_AMF0_SAMPLE_ACCESS "|RtmpSampleAccess" + +#define RTMP_INFO_LEVEL_STATUS "status" +#define RTMP_INFO_LEVEL_ERROR "error" +#define RTMP_INFO_LEVEL_WARNING "warning" + +enum RtmpUserControlEventType { + RTMP_USER_CONTROL_EVENT_STREAM_BEGIN = 0, + RTMP_USER_CONTROL_EVENT_STREAM_EOF = 1, + RTMP_USER_CONTROL_EVENT_STREAM_DRY = 2, + RTMP_USER_CONTROL_EVENT_SET_BUFFER_LENGTH = 3, + RTMP_USER_CONTROL_EVENT_STREAM_IS_RECORDED = 4, + RTMP_USER_CONTROL_EVENT_PING_REQUEST = 6, + RTMP_USER_CONTROL_EVENT_PING_RESPONSE = 7, + + // Not specified in any official documentation, but is sent by Flash Media + // Server 3.5. Reference: http://repo.or.cz/w/rtmpdump.git/blob/8880d1456b282ee79979adbe7b6a6eb8ad371081:/librtmp/rtmp.c#l2787 + // After the server has sent a complete buffer, and sends a Buffer Empty + // message, it will wait until the play duration of that buffer has passed + // before sending a new buffer. The Buffer Ready message will be sent when + // the new buffer starts. (There is no BufferReady message for the very + // first buffer; presumably the Stream Begin message is sufficient for + // that purpose.) + RTMP_USER_CONTROL_EVENT_BUFFER_EMPTY = 31, + RTMP_USER_CONTROL_EVENT_BUFFER_READY = 32, +}; + +// Header part of a RTMP message. +struct RtmpMessageHeader { + uint32_t timestamp; + uint32_t message_length; + uint8_t message_type; + uint32_t stream_id; + + RtmpMessageHeader() + : timestamp(0) + , message_length(0) + , message_type(0) + , stream_id(RTMP_CONTROL_MESSAGE_STREAM_ID) { + } + + bool is_valid() const { return message_type != 0; } +}; + +class RtmpContext; + +// The intermediate header passing to CutMessageIntoFileDescriptor. +class RtmpUnsentMessage : public SocketMessage { +public: + RtmpMessageHeader header; + uint32_t chunk_stream_id; + // Set RtmpContext::_chunk_size_out to this in AppendAndDestroySelf() + // if this field is non-zero. + uint32_t new_chunk_size; + butil::IOBuf body; + // If next is not NULL, next->AppendAndDestroySelf() will be called + // recursively. For implementing batched messages. + SocketMessagePtr next; +public: + RtmpUnsentMessage() + : chunk_stream_id(0) , new_chunk_size(0), next(NULL) {} + // @SocketMessage + butil::Status AppendAndDestroySelf(butil::IOBuf* out, Socket*); +}; + +// Notice that we can't directly pack CreateStream command in PackRtmpRequest, because +// we need to pack an AMFObject according to ctx->can_stream_be_created_with_play_or_publish(), +// which is in the response of Connect command(sent in RtmpConnect::StartConnect). +struct RtmpCreateStreamMessage : public SocketMessage { +public: + SocketUniquePtr socket; + uint32_t transaction_id; + RtmpClientStreamOptions options; +public: + explicit RtmpCreateStreamMessage() {} + // @SocketMessage + butil::Status AppendAndDestroySelf(butil::IOBuf* out, Socket*); +}; + +enum RtmpChunkType { + RTMP_CHUNK_TYPE0 = 0, + RTMP_CHUNK_TYPE1 = 1, + RTMP_CHUNK_TYPE2 = 2, + RTMP_CHUNK_TYPE3 = 3 +}; + +// header part of a chunk. +struct RtmpBasicHeader { + uint32_t chunk_stream_id; + RtmpChunkType fmt; + uint8_t header_length; +}; + +// Read big-endian values from buf. +uint8_t Read1Byte(const void* buf); +uint16_t ReadBigEndian2Bytes(const void* buf); +uint32_t ReadBigEndian3Bytes(const void* buf); +uint32_t ReadBigEndian4Bytes(const void* buf); +// Write values in big-endian into *buf and forward *buf. +void Write1Byte(char** buf, uint8_t val); +void WriteBigEndian2Bytes(char** buf, uint16_t val); +void WriteBigEndian3Bytes(char** buf, uint32_t val); +void WriteBigEndian4Bytes(char** buf, uint32_t val); +void WriteLittleEndian4Bytes(char** buf, uint32_t val); + +// Append the control message into `msg_buf' which is writable to Socket. +RtmpUnsentMessage* MakeUnsentControlMessage( + uint8_t message_type, const void* body, size_t size); +RtmpUnsentMessage* MakeUnsentControlMessage( + uint8_t message_type, const butil::IOBuf& body); + +// The callback associated with a transaction_id. +// If the transaction is successfully done, Run() will be called, otherwise +// Cancel() will be called. +class RtmpTransactionHandler { +public: + virtual ~RtmpTransactionHandler() {} + virtual void Run(bool error, const RtmpMessageHeader& mh, + AMFInputStream*, Socket* socket) = 0; + virtual void Cancel() = 0; +}; + +class RtmpChunkStream; + +// Associated with a RTMP connection. +class RtmpContext : public Destroyable { +friend class RtmpChunkStream; +friend class RtmpUnsentMessage; +public: + // States during handshake. + enum State { + STATE_UNINITIALIZED, + STATE_RECEIVED_S0S1, + STATE_RECEIVED_S2, + STATE_RECEIVED_C0C1, + STATE_RECEIVED_C2, + }; + + // Get literal form of the state. + static const char* state2str(State); + + // One of copt/service must be NULL, indicating this context belongs + // to a server-side or client-side socket. + RtmpContext(const RtmpClientOptions* copt, const Server* server); + ~RtmpContext(); + + // @Destroyable + void Destroy(); + + // Parse `source' from `socket'. + // This method is only called from Protocol.Parse thus does not need + // to be thread-safe. + ParseResult Feed(butil::IOBuf* source, Socket* socket); + + const RtmpClientOptions* client_options() const { return _client_options; } + const Server* server() const { return _server; } + RtmpService* service() const { return _service; } + + bool is_server_side() const { return service() != NULL; } + bool is_client_side() const { return service() == NULL; } + + // XXXMessageStream may be called from multiple threads(currently not), + // so they're protected by _stream_mutex + + // Find the stream by its id and reference the stream with intrusive_ptr. + // Returns true on success. + bool FindMessageStream(uint32_t stream_id, + butil::intrusive_ptr* stream); + + // Called in client-side to map the id to stream. + bool AddClientStream(RtmpStreamBase* stream); + + // Called in server-side to allocate an id and map the id to stream. + bool AddServerStream(RtmpStreamBase* stream); + + // Remove the stream from mapping. + // Returns true on success. + bool RemoveMessageStream(RtmpStreamBase* stream); + + // Allocate id for a transaction. + // Returns true on success. + // This method is called in pack_request(for createStream) where is + // accessible by multiple threads. However creating streams is unlikely to + // be very frequent, so this method is simply synchronized by _trans_mutex. + bool AddTransaction(uint32_t* transaction_id, + RtmpTransactionHandler* handler); + // Remove the transaction associated with the id. + // Return the transaction handler. + RtmpTransactionHandler* RemoveTransaction(uint32_t transaction_id); + + // Get the chunk stream by its id. The stream is created by need. + RtmpChunkStream* GetChunkStream(uint32_t cs_id); + // Reset the chunk stream associated with the id. + void ClearChunkStream(uint32_t cs_id); + + // Allocate/deallocate id for a chunk stream. + void AllocateChunkStreamId(uint32_t* chunk_stream_id); + void DeallocateChunkStreamId(uint32_t chunk_stream_id); + + // Allocate/deallocate id for a message stream. + bool AllocateMessageStreamId(uint32_t* message_stream_id); + void DeallocateMessageStreamId(uint32_t message_stream_id); + + // Set the callback to be called in OnConnected(). This method should + // be called before initiating the RTMP handshake (sending C0 and C1) + void SetConnectCallback(void (*app_connect_done)(int, void*), void* data) { + _on_connect = app_connect_done; + _on_connect_arg = data; + } + // Called when the RTMP connection is established. + void OnConnected(int error_code); + bool unconnected() const { return _on_connect != NULL; } + + void only_check_simple_s0s1() { _only_check_simple_s0s1 = true; } + bool can_stream_be_created_with_play_or_publish() const + { return _create_stream_with_play_or_publish; } + + // Call this fn to change _state. + void SetState(const butil::EndPoint& remote_side, State new_state); + + void set_create_stream_with_play_or_publish(bool create_stream_with_play_or_publish) + { _create_stream_with_play_or_publish = create_stream_with_play_or_publish; } + + void set_simplified_rtmp(bool simplified_rtmp) + { _simplified_rtmp = simplified_rtmp; } + + int SendConnectRequest(const butil::EndPoint& remote_side, int fd, bool simplified_rtmp); + +private: + ParseResult WaitForC0C1orSimpleRtmp(butil::IOBuf* source, Socket* socket); + ParseResult WaitForC2(butil::IOBuf* source, Socket* socket); + ParseResult WaitForS0S1(butil::IOBuf* source, Socket* socket); + ParseResult WaitForS2(butil::IOBuf* source, Socket* socket); + ParseResult OnChunks(butil::IOBuf* source, Socket* socket); + + // Count received bytes and send ack back if needed. + void AddReceivedBytes(Socket* socket, uint32_t size); + +private: + State _state; + void* _s1_digest; + // Outbound chunksize (inbound chunksize of peer), modifiable by self. + uint32_t _chunk_size_out; + // Inbound chunksize(outbound chunksize of peer), modifiale by peer. + uint32_t _chunk_size_in; + uint32_t _window_ack_size; + uint32_t _nonack_bytes; + uint64_t _received_bytes; + uint32_t _cs_id_allocator; + std::vector _free_cs_ids; + uint32_t _ms_id_allocator; + std::vector _free_ms_ids; + // Client-side options. + const RtmpClientOptions* _client_options; + // Callbacks to be called in OnConnected(). + void (*_on_connect)(int, void*); + void* _on_connect_arg; + bool _only_check_simple_s0s1; + bool _create_stream_with_play_or_publish; + + // Server and service. + const Server* _server; + RtmpService* _service; + + // Mapping message_stream_id to message streams. + butil::Mutex _stream_mutex; + struct MessageStreamInfo { + butil::intrusive_ptr stream; + }; + butil::FlatMap _mstream_map; + + // Mapping transaction id to handlers. + butil::Mutex _trans_mutex; + uint32_t _trans_id_allocator; + butil::FlatMap _trans_map; + + RtmpConnectRequest _connect_req; + + // Map chunk_stream_id to chunk streams. + // The array is 2-level to reduce memory for most connections. + struct SubChunkArray { + butil::atomic ptrs[RTMP_CHUNK_ARRAY_2ND_SIZE]; + SubChunkArray(); + ~SubChunkArray(); + }; + butil::atomic _cstream_ctx[RTMP_CHUNK_ARRAY_1ST_SIZE]; + + bool _simplified_rtmp; +}; + +class RtmpChunkStream { +public: + typedef bool (RtmpChunkStream::*MessageHandler)( + const RtmpMessageHeader& mh, butil::IOBuf* msg_body, Socket* socket); + + typedef bool (RtmpChunkStream::*CommandHandler)( + const RtmpMessageHeader& mh, AMFInputStream*, Socket* socket); + +public: + RtmpChunkStream(RtmpContext* conn_ctx, uint32_t cs_id); + + ParseResult Feed(const RtmpBasicHeader& bh, + butil::IOBuf* source, Socket* socket); + + RtmpContext* connection_context() const { return _conn_ctx; } + + uint32_t chunk_stream_id() const { return _cs_id; } + + int SerializeMessage(butil::IOBuf* buf, const RtmpMessageHeader& mh, + butil::IOBuf* body); + + bool OnMessage( + const RtmpBasicHeader& bh, const RtmpMessageHeader& mh, + butil::IOBuf* msg_body, Socket* socket); + + bool OnSetChunkSize(const RtmpMessageHeader& mh, + butil::IOBuf* msg_body, Socket* socket); + bool OnAbortMessage(const RtmpMessageHeader& mh, + butil::IOBuf* msg_body, Socket* socket); + bool OnAck(const RtmpMessageHeader& mh, + butil::IOBuf* msg_body, Socket* socket); + bool OnUserControlMessage(const RtmpMessageHeader& mh, + butil::IOBuf* msg_body, Socket* socket); + bool OnStreamBegin(const RtmpMessageHeader&, + const butil::StringPiece& event_data, Socket* socket); + bool OnStreamEOF(const RtmpMessageHeader&, + const butil::StringPiece& event_data, Socket* socket); + bool OnStreamDry(const RtmpMessageHeader&, + const butil::StringPiece& event_data, Socket* socket); + bool OnSetBufferLength(const RtmpMessageHeader&, + const butil::StringPiece& event_data, Socket* socket); + bool OnStreamIsRecorded(const RtmpMessageHeader&, + const butil::StringPiece& event_data, Socket* socket); + bool OnPingRequest(const RtmpMessageHeader&, + const butil::StringPiece& event_data, Socket* socket); + bool OnPingResponse(const RtmpMessageHeader&, + const butil::StringPiece& event_data, Socket* socket); + bool OnBufferEmpty(const RtmpMessageHeader&, + const butil::StringPiece& event_data, Socket* socket); + bool OnBufferReady(const RtmpMessageHeader&, + const butil::StringPiece& event_data, Socket* socket); + + bool OnWindowAckSize(const RtmpMessageHeader& mh, + butil::IOBuf* msg_body, Socket* socket); + bool OnSetPeerBandwidth(const RtmpMessageHeader& mh, + butil::IOBuf* msg_body, Socket* socket); + + bool OnAudioMessage(const RtmpMessageHeader& mh, + butil::IOBuf* msg_body, Socket* socket); + bool OnVideoMessage(const RtmpMessageHeader& mh, + butil::IOBuf* msg_body, Socket* socket); + bool OnDataMessageAMF0(const RtmpMessageHeader& mh, + butil::IOBuf* msg_body, Socket* socket); + bool OnDataMessageAMF3(const RtmpMessageHeader& mh, + butil::IOBuf* msg_body, Socket* socket); + bool OnSharedObjectMessageAMF0(const RtmpMessageHeader& mh, + butil::IOBuf* msg_body, Socket* socket); + bool OnSharedObjectMessageAMF3(const RtmpMessageHeader& mh, + butil::IOBuf* msg_body, Socket* socket); + bool OnCommandMessageAMF0(const RtmpMessageHeader& mh, + butil::IOBuf* msg_body, Socket* socket); + bool OnCommandMessageAMF3(const RtmpMessageHeader& mh, + butil::IOBuf* msg_body, Socket* socket); + bool OnAggregateMessage(const RtmpMessageHeader& mh, + butil::IOBuf* msg_body, Socket* socket); + + bool OnStatus(const RtmpMessageHeader& mh, AMFInputStream* istream, + Socket* socket); + bool OnConnect(const RtmpMessageHeader& mh, AMFInputStream* istream, + Socket* socket); + bool OnBWDone(const RtmpMessageHeader& mh, AMFInputStream* istream, + Socket* socket); + bool OnResult(const RtmpMessageHeader& mh, AMFInputStream* istream, + Socket* socket); + bool OnError(const RtmpMessageHeader& mh, AMFInputStream* istream, + Socket* socket); + bool OnPlay(const RtmpMessageHeader& mh, AMFInputStream* istream, + Socket* socket); + bool OnPlay2(const RtmpMessageHeader& mh, AMFInputStream* istream, + Socket* socket); + bool OnCreateStream(const RtmpMessageHeader& mh, AMFInputStream* istream, + Socket* socket); + bool OnDeleteStream(const RtmpMessageHeader& mh, AMFInputStream* istream, + Socket* socket); + bool OnCloseStream(const RtmpMessageHeader& mh, AMFInputStream* istream, + Socket* socket); + bool OnPublish(const RtmpMessageHeader& mh, AMFInputStream* istream, + Socket* socket); + bool OnReleaseStream(const RtmpMessageHeader& mh, AMFInputStream* istream, + Socket* socket); + bool OnFCPublish(const RtmpMessageHeader& mh, AMFInputStream* istream, + Socket* socket); + bool OnFCUnpublish(const RtmpMessageHeader& mh, AMFInputStream* istream, + Socket* socket); + bool OnGetStreamLength(const RtmpMessageHeader& mh, AMFInputStream* istream, + Socket* socket); + bool OnCheckBW(const RtmpMessageHeader& mh, AMFInputStream* istream, + Socket* socket); + bool OnSeek(const RtmpMessageHeader& mh, AMFInputStream* istream, + Socket* socket); + bool OnPause(const RtmpMessageHeader& mh, AMFInputStream* istream, + Socket* socket); + +private: + struct ReadParams { + ReadParams(); + bool last_has_extended_ts; + bool first_chunk_of_message; + uint32_t last_timestamp_delta; + uint32_t left_message_length; + RtmpMessageHeader last_msg_header; + butil::IOBuf msg_body; + }; + struct WriteParams { + WriteParams(); + bool last_has_extended_ts; + uint32_t last_timestamp_delta; + RtmpMessageHeader last_msg_header; + }; + + RtmpContext* _conn_ctx; + uint32_t _cs_id; + ReadParams _r; + WriteParams _w; +}; + +// Parse binary format of rmtp. +ParseResult ParseRtmpMessage(butil::IOBuf* source, Socket *socket, bool read_eof, + const void *arg); + +// no-op placeholder, never be called. +void ProcessRtmpMessage(InputMessageBase* msg); + +// Pack createStream message +void PackRtmpRequest(butil::IOBuf* buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* controller, + const butil::IOBuf& request, + const Authenticator* auth); + +// Serialize createStream message +void SerializeRtmpRequest(butil::IOBuf* buf, + Controller* cntl, + const google::protobuf::Message* request); + +// ============== inline impl. ================= +// TODO(gejun): impl. do not work for big-endian machines. +inline uint8_t Read1Byte(const void* void_buf) { + return *(const char*)void_buf; +} +inline uint16_t ReadBigEndian2Bytes(const void* void_buf) { + uint16_t ret = 0; + char* p = (char*)&ret; + const char* buf = (const char*)void_buf; + p[1] = buf[0]; + p[0] = buf[1]; + return ret; +} +inline uint32_t ReadBigEndian3Bytes(const void* void_buf) { + uint32_t ret = 0; + char* p = (char*)&ret; + const char* buf = (const char*)void_buf; + p[3] = 0; + p[2] = buf[0]; + p[1] = buf[1]; + p[0] = buf[2]; + return ret; +} +inline uint32_t ReadBigEndian4Bytes(const void* void_buf) { + uint32_t ret = 0; + char* p = (char*)&ret; + const char* buf = (const char*)void_buf; + p[3] = buf[0]; + p[2] = buf[1]; + p[1] = buf[2]; + p[0] = buf[3]; + return ret; +} +inline void Write1Byte(char** buf, uint8_t val) { + char* out = *buf; + *out= val; + *buf = out + 1; +} +inline void WriteBigEndian2Bytes(char** buf, uint16_t val) { + const char* p = (const char*)&val; + char* out = *buf; + out[0] = p[1]; + out[1] = p[0]; + *buf = out + 2; +} +inline void WriteBigEndian3Bytes(char** buf, uint32_t val) { + const char* p = (const char*)&val; + CHECK_EQ(p[3], 0); + char* out = *buf; + out[0] = p[2]; + out[1] = p[1]; + out[2] = p[0]; + *buf = out + 3; +} +inline void WriteBigEndian4Bytes(char** buf, uint32_t val) { + const char* p = (const char*)&val; + char* out = *buf; + out[0] = p[3]; + out[1] = p[2]; + out[2] = p[1]; + out[3] = p[0]; + *buf = out + 4; +} +inline void WriteLittleEndian4Bytes(char** buf, uint32_t val) { + const char* p = (const char*)&val; + char* out = *buf; + out[0] = p[0]; + out[1] = p[1]; + out[2] = p[2]; + out[3] = p[3]; + *buf = out + 4; +} + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_RTMP_PROTOCOL_H diff --git a/src/brpc/policy/snappy_compress.cpp b/src/brpc/policy/snappy_compress.cpp new file mode 100644 index 0000000..8019b97 --- /dev/null +++ b/src/brpc/policy/snappy_compress.cpp @@ -0,0 +1,85 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "butil/logging.h" +#include "butil/third_party/snappy/snappy.h" +#include "brpc/policy/snappy_compress.h" +#include "brpc/protocol.h" +#include "brpc/compress.h" + +namespace brpc { +namespace policy { + +bool SnappyCompress(const google::protobuf::Message& msg, butil::IOBuf* buf) { + butil::IOBuf serialized_pb; + butil::IOBufAsZeroCopyOutputStream wrapper(&serialized_pb); + bool ok; + if (msg.GetDescriptor() == Serializer::descriptor()) { + ok = ((const Serializer&)msg).SerializeTo(&wrapper); + } else { + ok = msg.SerializeToZeroCopyStream(&wrapper); + } + if (!ok) { + LOG(WARNING) << "Fail to serialize input pb=" + << msg.GetDescriptor()->full_name(); + return false; + } + + ok = SnappyCompress(serialized_pb, buf); + if (!ok) { + LOG(WARNING) << "Fail to snappy::Compress, size=" + << serialized_pb.size(); + } + return ok; +} + +bool SnappyDecompress(const butil::IOBuf& data, google::protobuf::Message* msg) { + butil::IOBuf binary_pb; + if (!SnappyDecompress(data, &binary_pb)) { + LOG(WARNING) << "Fail to snappy::Uncompress, size=" << data.size(); + return false; + } + + bool ok; + butil::IOBufAsZeroCopyInputStream stream(binary_pb); + if (msg->GetDescriptor() == Deserializer::descriptor()) { + ok = ((Deserializer*)msg)->DeserializeFrom(&stream); + } else { + ok = msg->ParseFromZeroCopyStream(&stream); + } + if (!ok) { + LOG(WARNING) << "Fail to eserialize input message=" + << msg->GetDescriptor()->full_name(); + } + return ok; +} + +bool SnappyCompress(const butil::IOBuf& in, butil::IOBuf* out) { + butil::IOBufAsSnappySource source(in); + butil::IOBufAsSnappySink sink(*out); + return butil::snappy::Compress(&source, &sink); +} + +bool SnappyDecompress(const butil::IOBuf& in, butil::IOBuf* out) { + butil::IOBufAsSnappySource source(in); + butil::IOBufAsSnappySink sink(*out); + return butil::snappy::Uncompress(&source, &sink); +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/snappy_compress.h b/src/brpc/policy/snappy_compress.h new file mode 100644 index 0000000..082aba9 --- /dev/null +++ b/src/brpc/policy/snappy_compress.h @@ -0,0 +1,45 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_SNAPPY_COMPRESS_H +#define BRPC_POLICY_SNAPPY_COMPRESS_H + +#include // Message +#include "butil/iobuf.h" // IOBuf + + +namespace brpc { +namespace policy { + +// Compress serialized `msg' into `buf'. +bool SnappyCompress(const google::protobuf::Message& msg, butil::IOBuf* buf); + +// Parse `msg' from decompressed `buf' +bool SnappyDecompress(const butil::IOBuf& data, google::protobuf::Message* msg); + +// Put compressed `in' into `out'. +bool SnappyCompress(const butil::IOBuf& in, butil::IOBuf* out); + +// Put decompressed `in' into `out'. +bool SnappyDecompress(const butil::IOBuf& in, butil::IOBuf* out); + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_SNAPPY_COMPRESS_H diff --git a/src/brpc/policy/sofa_pbrpc_meta.proto b/src/brpc/policy/sofa_pbrpc_meta.proto new file mode 100644 index 0000000..cab7440 --- /dev/null +++ b/src/brpc/policy/sofa_pbrpc_meta.proto @@ -0,0 +1,75 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +// Meta of sofa-pbrpc https://github.com/baidu/sofa-pbrpc + +package brpc.policy; +option java_package = "com.brpc.policy"; +option java_outer_classname = "SofaRpcProto"; + +enum SofaCompressType { + SOFA_COMPRESS_TYPE_NONE = 0; + SOFA_COMPRESS_TYPE_GZIP = 1; + SOFA_COMPRESS_TYPE_ZLIB = 2; + SOFA_COMPRESS_TYPE_SNAPPY = 3; + SOFA_COMPRESS_TYPE_LZ4 = 4; +} + +message SofaRpcMeta { + + ///////////////////////////////////////////////////// + // The following fields are used both for request and response. + + // Message type. + enum Type { + REQUEST = 0; + RESPONSE = 1; + }; + required Type type = 1; + + // Message sequence id. + required uint64 sequence_id = 2; + + ///////////////////////////////////////////////////// + // The following fields are used only for request. + + // Method full name. + // For example: "test.HelloService.GreetMethod" + optional string method = 100; + + ///////////////////////////////////////////////////// + // The following fields are used only for response. + + // Set as true if the call is failed. + optional bool failed = 200; + + // The error code if the call is failed. + optional int32 error_code = 201; + + // The error reason if the call is failed. + optional string reason = 202; + + ///////////////////////////////////////////////////// + // Compression related fields. + + // Set the request/response compress type. + optional SofaCompressType compress_type = 300; + + // Set the response compress type of user expected. + optional SofaCompressType expected_response_compress_type = 301; +} diff --git a/src/brpc/policy/sofa_pbrpc_protocol.cpp b/src/brpc/policy/sofa_pbrpc_protocol.cpp new file mode 100644 index 0000000..01b2185 --- /dev/null +++ b/src/brpc/policy/sofa_pbrpc_protocol.cpp @@ -0,0 +1,582 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // MethodDescriptor +#include // Message +#include +#include + +#include "butil/time.h" +#include "butil/strings/string_util.h" + +#include "brpc/controller.h" // Controller +#include "brpc/socket.h" // Socket +#include "brpc/server.h" // Server +#include "brpc/details/server_private_accessor.h" +#include "brpc/span.h" +#include "brpc/compress.h" // ParseFromCompressedData +#include "brpc/rpc_dump.h" +#include "brpc/details/controller_private_accessor.h" +#include "brpc/policy/most_common_message.h" +#include "brpc/policy/sofa_pbrpc_meta.pb.h" // SofaRpcMeta +#include "brpc/policy/sofa_pbrpc_protocol.h" +#include "brpc/details/usercode_backup_pool.h" + +extern "C" { +void bthread_assign_data(void* data); +} + + +namespace brpc { +namespace policy { + +// Notes: +// 1. 24-byte header [SOFA][meta_size][body_size(64)][message_size(64)], +// 2. Fields in header are NOT in network byte order (which may cause +// problems on machines with different byte order) +// 3. meta of request and response are same, distinguished by SofaRpcMeta::type +// 4. sofa-pbrpc does not support log_id, attachment, tracing + +CompressType Sofa2CompressType(SofaCompressType type) { + switch (type) { + case SOFA_COMPRESS_TYPE_NONE: + return COMPRESS_TYPE_NONE; + case SOFA_COMPRESS_TYPE_SNAPPY: + return COMPRESS_TYPE_SNAPPY; + case SOFA_COMPRESS_TYPE_GZIP: + return COMPRESS_TYPE_GZIP; + case SOFA_COMPRESS_TYPE_ZLIB: + return COMPRESS_TYPE_ZLIB; + default: + LOG(ERROR) << "Unknown SofaCompressType=" << type; + return COMPRESS_TYPE_NONE; + } +} + +SofaCompressType CompressType2Sofa(CompressType type) { + switch (type) { + case COMPRESS_TYPE_NONE: + return SOFA_COMPRESS_TYPE_NONE; + case COMPRESS_TYPE_SNAPPY: + return SOFA_COMPRESS_TYPE_SNAPPY; + case COMPRESS_TYPE_GZIP: + return SOFA_COMPRESS_TYPE_GZIP; + case COMPRESS_TYPE_ZLIB: + return SOFA_COMPRESS_TYPE_ZLIB; + case COMPRESS_TYPE_LZ4: + LOG(ERROR) << "sofa-pbrpc does not support LZ4"; + return SOFA_COMPRESS_TYPE_NONE; + default: + LOG(ERROR) << "Unknown SofaCompressType=" << type; + return SOFA_COMPRESS_TYPE_NONE; + } +} + +// Can't use RawPacker/RawUnpacker because SOFA does not use network byte order! +class SofaRawPacker { +public: + explicit SofaRawPacker(void* stream) : _stream((char*)stream) {} + + SofaRawPacker& pack32(uint32_t hostvalue) { + *(uint32_t*)_stream = hostvalue; + _stream += 4; + return *this; + } + + SofaRawPacker& pack64(uint64_t hostvalue) { + uint32_t *p = (uint32_t*)_stream; + *p = (hostvalue & 0xFFFFFFFF); + *(p + 1) = (hostvalue >> 32); + _stream += 8; + return *this; + } + +private: + char* _stream; +}; + +class SofaRawUnpacker { +public: + explicit SofaRawUnpacker(const void* stream) + : _stream((const char*)stream) {} + + SofaRawUnpacker& unpack32(uint32_t & hostvalue) { + hostvalue = *(const uint32_t*)_stream; + _stream += 4; + return *this; + } + + SofaRawUnpacker& unpack64(uint64_t & hostvalue) { + const uint32_t *p = (const uint32_t*)_stream; + hostvalue = ((uint64_t)*(p + 1) << 32) | *p; + _stream += 8; + return *this; + } + +private: + const char* _stream; +}; + +inline void PackSofaHeader(char* sofa_header, uint32_t meta_size, int body_size) { + uint32_t* dummy = reinterpret_cast(sofa_header); // suppress strict-alias warning + *dummy = *reinterpret_cast("SOFA"); + + SofaRawPacker rp(sofa_header + 4); + rp.pack32(meta_size).pack64(body_size).pack64(meta_size + body_size); +} + +static void SerializeSofaHeaderAndMeta( + butil::IOBuf* out, const SofaRpcMeta& meta, int payload_size) { + const uint32_t meta_size = GetProtobufByteSize(meta); + if (meta_size <= 232) { // most common cases + char header_and_meta[24 + meta_size]; + PackSofaHeader(header_and_meta, meta_size, payload_size); + ::google::protobuf::io::ArrayOutputStream arr_out(header_and_meta + 24, meta_size); + ::google::protobuf::io::CodedOutputStream coded_out(&arr_out); + meta.SerializeWithCachedSizes(&coded_out); // not calling ByteSize again + CHECK(!coded_out.HadError()); + out->append(header_and_meta, sizeof(header_and_meta)); + } else { + char header[24]; + PackSofaHeader(header, meta_size, payload_size); + out->append(header, sizeof(header)); + butil::IOBufAsZeroCopyOutputStream buf_stream(out); + ::google::protobuf::io::CodedOutputStream coded_out(&buf_stream); + meta.SerializeWithCachedSizes(&coded_out); + CHECK(!coded_out.HadError()); + } +} + +ParseResult ParseSofaMessage(butil::IOBuf* source, Socket* socket, + bool /*read_eof*/, const void* /*arg*/) { + char header_buf[24]; + const size_t n = source->copy_to(header_buf, sizeof(header_buf)); + if (n >= 4) { + void* dummy = header_buf; + if (*(const uint32_t*)dummy != *(const uint32_t*)"SOFA") { + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + } else { + if (memcmp(header_buf, "SOFA", n) != 0) { + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + } + if (n < sizeof(header_buf)) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + uint32_t meta_size; + uint64_t body_size; + uint64_t msg_size; + SofaRawUnpacker ru(header_buf + 4); + ru.unpack32(meta_size).unpack64(body_size).unpack64(msg_size); + if (msg_size != meta_size + body_size) { + LOG(ERROR) << "msg_size=" << msg_size << " != meta_size=" << meta_size + << " + body_size=" << body_size; + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + if (body_size > FLAGS_max_body_size) { + // We need this log to report the body_size to give users some clues + // which is not printed in InputMessenger. + LOG(ERROR) << "body_size=" << body_size << " from " + << socket->remote_side() << " is too large"; + return MakeParseError(PARSE_ERROR_TOO_BIG_DATA); + } else if (source->length() < sizeof(header_buf) + msg_size) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + source->pop_front(sizeof(header_buf)); + MostCommonMessage* msg = MostCommonMessage::Get(); + source->cutn(&msg->meta, meta_size); + source->cutn(&msg->payload, body_size); + return MakeMessage(msg); +} + +// Assemble response packet using `correlation_id', `controller', +// `res', and then write this packet to `sock' +static void SendSofaResponse(int64_t correlation_id, + Controller* cntl, + const google::protobuf::Message* req, + const google::protobuf::Message* res, + const Server* server, + MethodStatus* method_status, + int64_t received_us) { + ControllerPrivateAccessor accessor(cntl); + auto span = accessor.span(); + if (span) { + span->set_start_send_us(butil::cpuwide_time_us()); + } + Socket* sock = accessor.get_sending_socket(); + std::unique_ptr recycle_cntl(cntl); + ConcurrencyRemover concurrency_remover(method_status, cntl, received_us); + std::unique_ptr recycle_req(req); + std::unique_ptr recycle_res(res); + + if (cntl->IsCloseConnection()) { + sock->SetFailed(); + return; + } + + LOG_IF(WARNING, !cntl->response_attachment().empty()) + << "sofa-pbrpc does not support attachment, " + "your response_attachment will not be sent"; + + bool append_body = false; + butil::IOBuf res_body; + // `res' can be NULL here, in which case we don't serialize it + // If user calls `SetFailed' on Controller, we don't serialize + // response either + CompressType type = cntl->response_compress_type(); + if (res != NULL && !cntl->Failed()) { + if (!res->IsInitialized()) { + cntl->SetFailed( + ERESPONSE, "Missing required fields in response: %s", + res->InitializationErrorString().c_str()); + } else if (!SerializeAsCompressedData(*res, &res_body, type)) { + cntl->SetFailed(ERESPONSE, "Fail to serialize response, " + "CompressType=%s", CompressTypeToCStr(type)); + } else { + append_body = true; + } + } + + // Don't use res->ByteSize() since it may be compressed + size_t res_size = 0; + if (append_body) { + res_size = res_body.length(); + } + + SofaRpcMeta meta; + meta.set_type(SofaRpcMeta::RESPONSE); + // sofa-pbrpc client needs `failed' to be set currently(1.0.1.28195). + const int error_code = cntl->ErrorCode(); + meta.set_failed(error_code != 0); + meta.set_error_code(error_code); + if (!cntl->ErrorText().empty()) { + // Only set error_text when it's not empty since protobuf Message + // always new the string no matter if it's empty or not. + meta.set_reason(cntl->ErrorText()); + } + meta.set_sequence_id(correlation_id); + meta.set_compress_type( + CompressType2Sofa(cntl->response_compress_type())); + + butil::IOBuf res_buf; + SerializeSofaHeaderAndMeta(&res_buf, meta, res_size); + if (append_body) { + res_buf.append(res_body.movable()); + } + if (span) { + span->set_response_size(res_buf.size()); + } + // Have the risk of unlimited pending responses, in which case, tell + // users to set max_concurrency. + ResponseWriteInfo args; + Socket::WriteOptions wopt; + wopt.ignore_eovercrowded = true; + bthread_id_t response_id = INVALID_BTHREAD_ID; + if (span) { + CHECK_EQ(0, bthread_id_create(&response_id, &args, HandleResponseWritten)); + wopt.id_wait = response_id; + wopt.notify_on_success = true; + } + if (sock->Write(&res_buf, &wopt) != 0) { + const int errcode = errno; + PLOG_IF(WARNING, errcode != EPIPE) << "Fail to write into " << *sock; + cntl->SetFailed(errcode, "Fail to write into %s", + sock->description().c_str()); + return; + } + + if (span) { + bthread_id_join(response_id); + // Do not care about the result of background writing. + // TODO: this is not sent + span->set_sent_us(args.sent_us); + } +} + +// Defined in baidu_rpc_protocol.cpp +void EndRunningCallMethodInPool( + ::google::protobuf::Service* service, + const ::google::protobuf::MethodDescriptor* method, + ::google::protobuf::RpcController* controller, + const ::google::protobuf::Message* request, + ::google::protobuf::Message* response, + ::google::protobuf::Closure* done); + +void ProcessSofaRequest(InputMessageBase* msg_base) { + const int64_t start_parse_us = butil::cpuwide_time_us(); + DestroyingPtr msg(static_cast(msg_base)); + SocketUniquePtr socket_guard(msg->ReleaseSocket()); + Socket* socket = socket_guard.get(); + const Server* server = static_cast(msg_base->arg()); + ScopedNonServiceError non_service_error(server); + + SofaRpcMeta meta; + if (!ParsePbFromIOBuf(&meta, msg->meta)) { + LOG(WARNING) << "Fail to parse SofaRpcMeta from " << *socket; + socket->SetFailed(EREQUEST, "Fail to parse SofaRpcMeta from %s", + socket->description().c_str()); + return; + } + const CompressType req_cmp_type = Sofa2CompressType(meta.compress_type()); + + SampledRequest* sample = AskToBeSampled(); + if (sample) { + sample->meta.set_method_name(meta.method()); + sample->meta.set_compress_type(req_cmp_type); + sample->meta.set_protocol_type(PROTOCOL_SOFA_PBRPC); + sample->request = msg->payload; + sample->submit(start_parse_us); + } + + std::unique_ptr cntl(new (std::nothrow) Controller); + if (NULL == cntl.get()) { + LOG(WARNING) << "Fail to new Controller"; + return; + } + std::unique_ptr req; + std::unique_ptr res; + + ControllerPrivateAccessor accessor(cntl.get()); + ServerPrivateAccessor server_accessor(server); + const int64_t correlation_id = meta.sequence_id(); + const bool security_mode = server->options().security_mode() && + socket->user() == server_accessor.acceptor(); + + cntl->set_request_compress_type(req_cmp_type); + accessor.set_server(server) + .set_security_mode(security_mode) + .set_peer_id(socket->id()) + .set_remote_side(socket->remote_side()) + .set_local_side(socket->local_side()) + .set_auth_context(socket->auth_context()) + .set_request_protocol(PROTOCOL_SOFA_PBRPC) + .set_begin_time_us(msg->received_us()) + .move_in_server_receiving_sock(socket_guard); + + // Tag the bthread with this server's key for thread_local_data(). + if (server->thread_local_options().thread_local_data_factory) { + bthread_assign_data((void*)&server->thread_local_options()); + } + + std::shared_ptr span; + if (IsTraceable(false)) { + span = Span::CreateServerSpan( + 0/*meta.trace_id()*/, 0/*meta.span_id()*/, + 0/*meta.parent_span_id()*/, msg->base_real_us()); + accessor.set_span(span); + span->set_remote_side(cntl->remote_side()); + span->set_protocol(PROTOCOL_SOFA_PBRPC); + span->set_received_us(msg->received_us()); + span->set_start_parse_us(start_parse_us); + span->set_request_size(msg->meta.size() + msg->payload.size() + 24); + } + + MethodStatus* method_status = NULL; + do { + if (!server->IsRunning()) { + cntl->SetFailed(ELOGOFF, "Server is stopping"); + break; + } + + if (!server_accessor.AddConcurrency(cntl.get())) { + cntl->SetFailed( + ELIMIT, "Reached server's max_concurrency=%d", + server->options().max_concurrency); + break; + } + if (FLAGS_usercode_in_pthread && TooManyUserCode()) { + cntl->SetFailed(ELIMIT, "Too many user code to run when" + " -usercode_in_pthread is on"); + break; + } + + const Server::MethodProperty *sp = + server_accessor.FindMethodPropertyByFullName(meta.method()); + if (NULL == sp) { + cntl->SetFailed(ENOMETHOD, "Fail to find method=%s", + meta.method().c_str()); + break; + } + if (socket->is_overcrowded() && + !server->options().ignore_eovercrowded && + !sp->ignore_eovercrowded) { + cntl->SetFailed(EOVERCROWDED, "Connection to %s is overcrowded", + butil::endpoint2str(socket->remote_side()).c_str()); + break; + } + // Switch to service-specific error. + non_service_error.release(); + method_status = sp->status; + if (method_status) { + int rejected_cc = 0; + if (!method_status->OnRequested(&rejected_cc)) { + cntl->SetFailed(ELIMIT, "Rejected by %s's ConcurrencyLimiter, concurrency=%d", + butil::EnsureString(sp->method->full_name()).c_str(), rejected_cc); + break; + } + } + google::protobuf::Service* svc = sp->service; + const google::protobuf::MethodDescriptor* method = sp->method; + accessor.set_method(method); + + if (!server->AcceptRequest(cntl.get())) { + break; + } + + if (span) { + span->ResetServerSpanName(butil::EnsureString(method->full_name())); + } + req.reset(svc->GetRequestPrototype(method).New()); + if (!ParseFromCompressedData(msg->payload, req.get(), req_cmp_type)) { + cntl->SetFailed(EREQUEST, "Fail to parse request message, " + "CompressType=%d, size=%d", + req_cmp_type, (int)msg->payload.size()); + break; + } + + res.reset(svc->GetResponsePrototype(method).New()); + // `socket' will be held until response has been sent + google::protobuf::Closure* done = ::brpc::NewCallback< + int64_t, Controller*, const google::protobuf::Message*, + const google::protobuf::Message*, const Server*, + MethodStatus *, int64_t>( + &SendSofaResponse, correlation_id, cntl.get(), + req.get(), res.get(), server, + method_status, msg->received_us()); + + msg.reset(); // optional, just release resource ASAP + + // `cntl', `req' and `res' will be deleted inside `done' + if (span) { + span->set_start_callback_us(butil::cpuwide_time_us()); + span->AsParent(); + } + if (!FLAGS_usercode_in_pthread) { + return svc->CallMethod(method, cntl.release(), + req.release(), res.release(), done); + } + if (BeginRunningUserCode()) { + svc->CallMethod(method, cntl.release(), + req.release(), res.release(), done); + return EndRunningUserCodeInPlace(); + } else { + return EndRunningCallMethodInPool( + svc, method, cntl.release(), + req.release(), res.release(), done); + } + } while (false); + + // `cntl', `req' and `res' will be deleted inside `SendSofaResponse' + // `socket' will be held until response has been sent + SendSofaResponse(correlation_id, cntl.release(), + req.release(), res.release(), server, + method_status, msg->received_us()); +} + +bool VerifySofaRequest(const InputMessageBase* msg_base) { + const Server* server = static_cast(msg_base->arg()); + if (server->options().auth) { + LOG(WARNING) << "sofa-pbrpc does not support authentication"; + return false; + } + return true; +} + +void ProcessSofaResponse(InputMessageBase* msg_base) { + const int64_t start_parse_us = butil::cpuwide_time_us(); + DestroyingPtr msg(static_cast(msg_base)); + SofaRpcMeta meta; + if (!ParsePbFromIOBuf(&meta, msg->meta)) { + LOG(WARNING) << "Fail to parse response meta"; + return; + } + + const bthread_id_t cid = { static_cast(meta.sequence_id()) }; + Controller* cntl = NULL; + const int rc = bthread_id_lock(cid, (void**)&cntl); + if (rc != 0) { + LOG_IF(ERROR, rc != EINVAL && rc != EPERM) + << "Fail to lock correlation_id=" << cid << ": " << berror(rc); + return; + } + + ControllerPrivateAccessor accessor(cntl); + if (auto span = accessor.span()) { + span->set_base_real_us(msg->base_real_us()); + span->set_received_us(msg->received_us()); + span->set_response_size(msg->meta.size() + msg->payload.size() + 24); + span->set_start_parse_us(start_parse_us); + } + const int saved_error = cntl->ErrorCode(); + if (meta.error_code() != 0) { + // If error_code is unset, default is 0 = success. + cntl->SetFailed(meta.error_code(), + "%s", meta.reason().c_str()); + } else if (cntl->response()) { + // Parse response message iff error code from meta is 0 + CompressType res_cmp_type = Sofa2CompressType(meta.compress_type()); + if (!ParseFromCompressedData( + msg->payload, cntl->response(), res_cmp_type)) { + cntl->SetFailed( + ERESPONSE, "Fail to parse response message, " + "CompressType=%d, response_size=%" PRIu64, + res_cmp_type, (uint64_t)msg->payload.length()); + } else { + cntl->set_response_compress_type(res_cmp_type); + } + } // else silently ignore the response. + + // Unlocks correlation_id inside. Revert controller's + // error code if it version check of `cid' fails + msg.reset(); // optional, just release resource ASAP + accessor.OnResponse(cid, saved_error); +} + +void PackSofaRequest(butil::IOBuf* req_buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* cntl, + const butil::IOBuf& req_body, + const Authenticator* /*not supported*/) { + if (!cntl->request_attachment().empty()) { + LOG(WARNING) << "sofa-pbrpc does not support attachment, " + "your request_attachment will not be sent"; + } + SofaRpcMeta meta; + meta.set_type(SofaRpcMeta::REQUEST); + meta.set_sequence_id(correlation_id); + if (method) { + meta.set_method(method->full_name()); + meta.set_compress_type(CompressType2Sofa(cntl->request_compress_type())); + } else if (cntl->sampled_request()) { + // Replaying. + meta.set_method(cntl->sampled_request()->meta.method_name()); + meta.set_compress_type( + CompressType2Sofa(cntl->sampled_request()->meta.compress_type())); + } else { + return cntl->SetFailed(ENOMETHOD, "method is NULL"); + } + + SerializeSofaHeaderAndMeta(req_buf, meta, req_body.size()); + req_buf->append(req_body); +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/sofa_pbrpc_protocol.h b/src/brpc/policy/sofa_pbrpc_protocol.h new file mode 100644 index 0000000..715949e --- /dev/null +++ b/src/brpc/policy/sofa_pbrpc_protocol.h @@ -0,0 +1,54 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_SOFA_PBRPC_PROTOCOL_H +#define BRPC_POLICY_SOFA_PBRPC_PROTOCOL_H + +#include "brpc/policy/sofa_pbrpc_meta.pb.h" +#include "brpc/protocol.h" + + +namespace brpc { +namespace policy { + +// Parse binary format of sofa-pbrpc. +ParseResult ParseSofaMessage(butil::IOBuf* source, Socket *socket, bool read_eof, const void *arg); + +// Actions to a (client) request in sofa-pbrpc format. +void ProcessSofaRequest(InputMessageBase* msg); + +// Actions to a (server) response in sofa-pbrpc format. +void ProcessSofaResponse(InputMessageBase* msg); + +// Verify authentication information in sofa-pbrpc format +bool VerifySofaRequest(const InputMessageBase* msg); + +// Pack `request' to `method' into `buf'. +void PackSofaRequest(butil::IOBuf* buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* controller, + const butil::IOBuf& request, + const Authenticator* auth); + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_SOFA_PBRPC_PROTOCOL_H diff --git a/src/brpc/policy/streaming_rpc_protocol.cpp b/src/brpc/policy/streaming_rpc_protocol.cpp new file mode 100644 index 0000000..b741acf --- /dev/null +++ b/src/brpc/policy/streaming_rpc_protocol.cpp @@ -0,0 +1,186 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/streaming_rpc_protocol.h" + +#include // MethodDescriptor +#include // Message +#include +#include "butil/macros.h" +#include "butil/logging.h" // LOG() +#include "butil/time.h" +#include "butil/iobuf.h" // butil::IOBuf +#include "butil/raw_pack.h" // RawPacker RawUnpacker +#include "brpc/log.h" +#include "brpc/socket.h" // Socket +#include "brpc/streaming_rpc_meta.pb.h" // StreamFrameMeta +#include "brpc/policy/most_common_message.h" +#include "brpc/stream_impl.h" + + +namespace brpc { +namespace policy { + +// Notes on Streaming RPC Protocol: +// 1 - Header format is [STRM][body_size][meta_size], 12 bytes in total +// 2 - body_size and meta_size are in network byte order +void PackStreamMessage(butil::IOBuf* out, + const StreamFrameMeta &fm, + const butil::IOBuf *data) { + const uint32_t data_length = data ? data->length() : 0; + const uint32_t meta_length = GetProtobufByteSize(fm); + char head[12]; + uint32_t* dummy = (uint32_t*)head; // suppresses strict-alias warning + *(uint32_t*)dummy = *(const uint32_t*)"STRM"; + butil::RawPacker(head + 4) + .pack32(data_length + meta_length) + .pack32(meta_length); + out->append(head, ARRAY_SIZE(head)); + butil::IOBufAsZeroCopyOutputStream wrapper(out); + CHECK(fm.SerializeToZeroCopyStream(&wrapper)); + if (data != NULL) { + out->append(*data); + } +} + +ParseResult ParseStreamingMessage(butil::IOBuf* source, + Socket* socket, bool /*read_eof*/, const void* /*arg*/) { + char header_buf[12]; + const size_t n = source->copy_to(header_buf, sizeof(header_buf)); + if (n >= 4) { + void* dummy = header_buf; + if (*(const uint32_t*)dummy != *(const uint32_t*)"STRM") { + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + } else { + if (memcmp(header_buf, "STRM", n) != 0) { + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + } + if (n < sizeof(header_buf)) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + uint32_t body_size; + uint32_t meta_size; + butil::RawUnpacker(header_buf + 4).unpack32(body_size).unpack32(meta_size); + if (body_size > FLAGS_max_body_size) { + return MakeParseError(PARSE_ERROR_TOO_BIG_DATA); + } else if (source->length() < sizeof(header_buf) + body_size) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + if (BAIDU_UNLIKELY(meta_size > body_size)) { + LOG(ERROR) << "meta_size=" << meta_size << " is bigger than body_size=" + << body_size; + // Pop the message + source->pop_front(sizeof(header_buf) + body_size); + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + source->pop_front(sizeof(header_buf)); + butil::IOBuf meta_buf; + source->cutn(&meta_buf, meta_size); + butil::IOBuf payload; + source->cutn(&payload, body_size - meta_size); + + do { + StreamFrameMeta fm; + if (!ParsePbFromIOBuf(&fm, meta_buf)) { + LOG(WARNING) << "Fail to Parse StreamFrameMeta from " << *socket; + break; + } + SocketUniquePtr ptr; + if (Socket::Address((SocketId)fm.stream_id(), &ptr) != 0) { + RPC_VLOG_IF(fm.frame_type() != FRAME_TYPE_RST + && fm.frame_type() != FRAME_TYPE_CLOSE + && fm.frame_type() != FRAME_TYPE_FEEDBACK) + << "Fail to find stream=" << fm.stream_id(); + // It's normal that the stream is closed before receiving feedback frames from peer. + // In this case, RST frame should not be sent to peer, otherwise on-fly data can be lost. + if (fm.has_source_stream_id() && fm.frame_type() != FRAME_TYPE_FEEDBACK) { + SendStreamRst(socket, fm.source_stream_id()); + } + break; + } + meta_buf.clear(); // to reduce memory resident + // ptr->conn() returns the connection-level context attached to the + // socket. It may be NULL when the socket was found by ID but has no + // Stream object associated (e.g. during protocol probing or fuzz + // testing). Calling OnReceived on a null pointer would crash. + Stream* stream_conn = (Stream*)ptr->conn(); + if (stream_conn == NULL) { + LOG(FATAL) << "No stream object found"; + break; + } + stream_conn->OnReceived(fm, &payload, socket); + } while (0); + + // Hack input messenger + return MakeMessage(NULL); +} + +void ProcessStreamingMessage(InputMessageBase* /*msg*/) { + CHECK(false) << "Should never be called"; +} + +void SendStreamRst(Socket *sock, int64_t remote_stream_id) { + CHECK(sock != NULL); + StreamFrameMeta fm; + fm.set_stream_id(remote_stream_id); + fm.set_frame_type(FRAME_TYPE_RST); + butil::IOBuf out; + PackStreamMessage(&out, fm, NULL); + Socket::WriteOptions wopt; + wopt.ignore_eovercrowded = true; + sock->Write(&out, &wopt); +} + +void SendStreamClose(Socket *sock, int64_t remote_stream_id, + int64_t source_stream_id) { + CHECK(sock != NULL); + StreamFrameMeta fm; + fm.set_stream_id(remote_stream_id); + fm.set_source_stream_id(source_stream_id); + fm.set_frame_type(FRAME_TYPE_CLOSE); + butil::IOBuf out; + PackStreamMessage(&out, fm, NULL); + Socket::WriteOptions wopt; + wopt.ignore_eovercrowded = true; + sock->Write(&out, &wopt); +} + +int SendStreamData(Socket* sock, const butil::IOBuf* data, + int64_t remote_stream_id, int64_t source_stream_id, + bthread_id_t response_id) { + CHECK(sock != NULL); + StreamFrameMeta fm; + fm.set_stream_id(remote_stream_id); + fm.set_source_stream_id(source_stream_id); + fm.set_frame_type(FRAME_TYPE_DATA); + fm.set_has_continuation(false); + butil::IOBuf out; + PackStreamMessage(&out, fm, data); + Socket::WriteOptions wopt; + if (INVALID_BTHREAD_ID != response_id) { + wopt.id_wait = response_id; + wopt.notify_on_success = true; + } + wopt.ignore_eovercrowded = true; + return sock->Write(&out, &wopt); +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/streaming_rpc_protocol.h b/src/brpc/policy/streaming_rpc_protocol.h new file mode 100644 index 0000000..22023e7 --- /dev/null +++ b/src/brpc/policy/streaming_rpc_protocol.h @@ -0,0 +1,51 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_STREAMING_RPC_PROTOCOL_H +#define BRPC_STREAMING_RPC_PROTOCOL_H + +#include "bthread/types.h" +#include "brpc/protocol.h" +#include "brpc/streaming_rpc_meta.pb.h" + +namespace brpc { +namespace policy { + +void PackStreamMessage(butil::IOBuf* out, + const StreamFrameMeta &fm, + const butil::IOBuf *data); + +ParseResult ParseStreamingMessage(butil::IOBuf* source, Socket* socket, + bool read_eof, const void* arg); + +void ProcessStreamingMessage(InputMessageBase* msg); + +void SendStreamRst(Socket* sock, int64_t remote_stream_id); + +void SendStreamClose(Socket *sock, int64_t remote_stream_id, + int64_t source_stream_id); + +int SendStreamData(Socket* sock, const butil::IOBuf* data, + int64_t remote_stream_id, int64_t source_stream_id, + bthread_id_t); + +} // namespace policy +} // namespace brpc + + +#endif //BRPC_STREAMING_RPC_PROTOCOL_H diff --git a/src/brpc/policy/thrift_protocol.cpp b/src/brpc/policy/thrift_protocol.cpp new file mode 100755 index 0000000..2b5739e --- /dev/null +++ b/src/brpc/policy/thrift_protocol.cpp @@ -0,0 +1,765 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // MethodDescriptor +#include // Message +#include + +#include "butil/time.h" +#include "butil/iobuf.h" // butil::IOBuf +#include "brpc/log.h" +#include "brpc/controller.h" // Controller +#include "brpc/socket.h" // Socket +#include "brpc/server.h" // Server +#include "brpc/span.h" +#include "brpc/details/server_private_accessor.h" +#include "brpc/details/controller_private_accessor.h" +#include "brpc/thrift_service.h" +#include "brpc/policy/most_common_message.h" +#include "brpc/policy/thrift_protocol.h" +#include "brpc/details/usercode_backup_pool.h" + +#include +#include +#include +#include + +// _THRIFT_STDCXX_H_ is defined by thrift/stdcxx.h which was added since thrift 0.11.0 +// but deprecated after thrift 0.13.0 +#include // to include stdcxx.h if present +#ifndef THRIFT_STDCXX + #if defined(_THRIFT_STDCXX_H_) + # define THRIFT_STDCXX apache::thrift::stdcxx + #elif defined(_THRIFT_VERSION_LOWER_THAN_0_11_0_) + # define THRIFT_STDCXX boost + # include + #else + # define THRIFT_STDCXX std + #endif +#endif + +extern "C" { +void bthread_assign_data(void* data); +} + +namespace brpc { +namespace policy { + +static const uint32_t MAX_THRIFT_METHOD_NAME_LENGTH = 256; // reasonably large +static const uint32_t THRIFT_HEAD_VERSION_MASK = (uint32_t)0xffffff00; +static const uint32_t THRIFT_HEAD_VERSION_1 = (uint32_t)0x80010000; +struct thrift_head_t { + uint32_t body_len; +}; + +// A faster implementation of TProtocol::readMessageBegin without depending +// on thrift stuff. +static butil::Status +ReadThriftMessageBegin(butil::IOBuf* body, + std::string* method_name, + ::apache::thrift::protocol::TMessageType* mtype, + uint32_t* seq_id) { + // Thrift protocol format: + // Version + Message type + Length + Method + Sequence Id + // | | | | | + // 3 + 1 + 4 + >0 + 4 + uint32_t version_and_len_buf[2]; + size_t k = body->copy_to(version_and_len_buf, sizeof(version_and_len_buf)); + if (k != sizeof(version_and_len_buf) ) { + return butil::Status(-1, "Fail to copy %zu bytes from body", + sizeof(version_and_len_buf)); + } + *mtype = (apache::thrift::protocol::TMessageType) + (ntohl(version_and_len_buf[0]) & 0x000000FF); + const uint32_t method_name_length = ntohl(version_and_len_buf[1]); + if (method_name_length > MAX_THRIFT_METHOD_NAME_LENGTH) { + return butil::Status(-1, "method_name_length=%u is too long", + method_name_length); + } + + char buf[sizeof(version_and_len_buf) + method_name_length + 4]; + k = body->cutn(buf, sizeof(buf)); + if (k != sizeof(buf)) { + return butil::Status(-1, "Fail to cut %zu bytes", sizeof(buf)); + } + method_name->assign(buf + sizeof(version_and_len_buf), method_name_length); + // suppress strict-aliasing warning + uint32_t* p_seq_id = (uint32_t*)(buf + sizeof(version_and_len_buf) + method_name_length); + *seq_id = ntohl(*p_seq_id); + return butil::Status::OK(); +} + +inline size_t ThriftMessageBeginSize(const std::string& method_name) { + return 12 + method_name.size(); +} + +static void +WriteThriftMessageBegin(char* buf, + const std::string& method_name, + ::apache::thrift::protocol::TMessageType mtype, + uint32_t seq_id) { + char* p = buf; + *(uint32_t*)p = htonl(THRIFT_HEAD_VERSION_1 | (((uint32_t)mtype) & 0x000000FF)); + p += 4; + *(uint32_t*)p = htonl(method_name.size()); + p += 4; + memcpy(p, method_name.data(), method_name.size()); + p += method_name.size(); + *(uint32_t*)p = htonl(seq_id); +} + +bool ReadThriftStruct(const butil::IOBuf& body, + ThriftMessageBase* raw_msg, + int16_t expected_fid) { + const size_t body_len = body.size(); + uint8_t* thrift_buffer = (uint8_t*)malloc(body_len); + body.copy_to(thrift_buffer, body_len); + auto in_buffer = + THRIFT_STDCXX::make_shared( + thrift_buffer, body_len, + ::apache::thrift::transport::TMemoryBuffer::TAKE_OWNERSHIP); + apache::thrift::protocol::TBinaryProtocolT iprot(in_buffer); + + bool success = false; + try { + // The following code was taken from thrift auto generate code + std::string fname; + + uint32_t xfer = 0; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + xfer += iprot.readStructBegin(fname); + while (true) { + xfer += iprot.readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + if (fid == expected_fid) { + if (ftype == ::apache::thrift::protocol::T_STRUCT) { + xfer += raw_msg->Read(&iprot); + success = true; + } else { + xfer += iprot.skip(ftype); + } + } else { + xfer += iprot.skip(ftype); + } + xfer += iprot.readFieldEnd(); + } + + xfer += iprot.readStructEnd(); + (void)xfer; + iprot.getTransport()->readEnd(); + } catch (std::exception& e) { + LOG(WARNING) << "Catched thrift exception: " << e.what(); + } catch (...) { + LOG(WARNING) << "Catched unknown thrift exception"; + } + return success; +} + +void ReadThriftException(const butil::IOBuf& body, + ::apache::thrift::TApplicationException* x) { + size_t body_len = body.size(); + uint8_t* thrift_buffer = (uint8_t*)malloc(body_len); + body.copy_to(thrift_buffer, body_len); + auto in_buffer = + THRIFT_STDCXX::make_shared( + thrift_buffer, body_len, + ::apache::thrift::transport::TMemoryBuffer::TAKE_OWNERSHIP); + apache::thrift::protocol::TBinaryProtocolT iprot(in_buffer); + + x->read(&iprot); + iprot.readMessageEnd(); + iprot.getTransport()->readEnd(); +} + +// The continuation of request processing. Namely send response back to client. +class ThriftClosure : public google::protobuf::Closure { +public: + explicit ThriftClosure(); + ~ThriftClosure(); + + // [Required] Call this to send response back to the client. + void Run() override; + + // Suspend/resume calling DoRun(). + void SuspendRunning(); + void ResumeRunning(); + +private: + void DoRun(); +friend void ProcessThriftRequest(InputMessageBase* msg_base); + + butil::atomic _run_counter; + int64_t _received_us; + ThriftFramedMessage _request; + ThriftFramedMessage _response; + Controller _controller; +}; + +inline ThriftClosure::ThriftClosure() + : _run_counter(1), _received_us(0) { +} + +ThriftClosure::~ThriftClosure() { + LogErrorTextAndDelete(false)(&_controller); +} + +inline void ThriftClosure::SuspendRunning() { + _run_counter.fetch_add(1, butil::memory_order_relaxed); +} + +inline void ThriftClosure::ResumeRunning() { + if (_run_counter.fetch_sub(1, butil::memory_order_relaxed) == 1) { + DoRun(); + } +} + +void ThriftClosure::Run() { + if (_run_counter.fetch_sub(1, butil::memory_order_relaxed) == 1) { + DoRun(); + } +} + +void ThriftClosure::DoRun() { + // Recycle itself after `Run' + std::unique_ptr recycle_ctx(this); + const Server* server = _controller.server(); + + ControllerPrivateAccessor accessor(&_controller); + auto span = accessor.span(); + if (span) { + span->set_start_send_us(butil::cpuwide_time_us()); + } + Socket* sock = accessor.get_sending_socket(); + MethodStatus* method_status = (server->options().thrift_service ? + server->options().thrift_service->_status : NULL); + ConcurrencyRemover concurrency_remover(method_status, &_controller, _received_us); + if (!method_status) { + // Judge errors belongings. + // may not be accurate, but it does not matter too much. + const int error_code = _controller.ErrorCode(); + if (error_code == ENOSERVICE || + error_code == ENOMETHOD || + error_code == EREQUEST || + error_code == ECLOSE || + error_code == ELOGOFF || + error_code == ELIMIT) { + ServerPrivateAccessor(server).AddError(); + } + } + + if (_controller.IsCloseConnection() || + // seq_id is not read yet, no valid response can be sent back + !_controller.has_log_id()) { + sock->SetFailed(); + return; + } + + const std::string& method_name = _controller.thrift_method_name(); + if (method_name.empty() || method_name[0] == ' ') { + _controller.SetFailed(ENOMETHOD, "Invalid thrift_method_name!"); + } + if (method_name.size() > MAX_THRIFT_METHOD_NAME_LENGTH) { + _controller.SetFailed(ENOMETHOD, "thrift_method_name is too long"); + } + if (_controller.log_id() > (uint64_t)0xffffffff) { + _controller.SetFailed(ERESPONSE, "Invalid thrift seq_id=%" PRIu64, + _controller.log_id()); + } + const uint32_t seq_id = (uint32_t)_controller.log_id(); + + butil::IOBuf write_buf; + + // The following code was taken and modified from thrift auto generated code + if (_controller.Failed()) { + auto out_buffer = + THRIFT_STDCXX::make_shared(); + apache::thrift::protocol::TBinaryProtocolT oprot(out_buffer); + ::apache::thrift::TApplicationException x(_controller.ErrorText()); + oprot.writeMessageBegin( + method_name, ::apache::thrift::protocol::T_EXCEPTION, seq_id); + x.write(&oprot); + oprot.writeMessageEnd(); + oprot.getTransport()->writeEnd(); + oprot.getTransport()->flush(); + + uint8_t* buf; + uint32_t sz; + out_buffer->getBuffer(&buf, &sz); + const thrift_head_t head = { htonl(sz) }; + write_buf.append(&head, sizeof(head)); + write_buf.append(buf, sz); + } else if (_response.raw_instance()) { + auto out_buffer = + THRIFT_STDCXX::make_shared(); + apache::thrift::protocol::TBinaryProtocolT oprot(out_buffer); + oprot.writeMessageBegin( + method_name, ::apache::thrift::protocol::T_REPLY, seq_id); + + uint32_t xfer = 0; + xfer += oprot.writeStructBegin("rpc_result"); // can be any valid name + xfer += oprot.writeFieldBegin("success", + ::apache::thrift::protocol::T_STRUCT, + THRIFT_RESPONSE_FID); + xfer += _response.raw_instance()->Write(&oprot); + xfer += oprot.writeFieldEnd(); + xfer += oprot.writeFieldStop(); + xfer += oprot.writeStructEnd(); + (void)xfer; + + oprot.writeMessageEnd(); + oprot.getTransport()->writeEnd(); + oprot.getTransport()->flush(); + + uint8_t* buf; + uint32_t sz; + out_buffer->getBuffer(&buf, &sz); + const thrift_head_t head = { htonl(sz) }; + write_buf.append(&head, sizeof(head)); + write_buf.append(buf, sz); + } else { + const size_t mb_size = ThriftMessageBeginSize(method_name); + char buf[sizeof(thrift_head_t) + mb_size]; + // suppress strict-aliasing warning + thrift_head_t* head = (thrift_head_t*)buf; + head->body_len = htonl(mb_size + _response.body.size()); + WriteThriftMessageBegin(buf + sizeof(thrift_head_t), method_name, + ::apache::thrift::protocol::T_REPLY, seq_id); + write_buf.append(buf, sizeof(buf)); + write_buf.append(_response.body.movable()); + } + + if (span) { + span->set_response_size(write_buf.size()); + } + // Have the risk of unlimited pending responses, in which case, tell + // users to set max_concurrency. + Socket::WriteOptions wopt; + wopt.ignore_eovercrowded = true; + if (sock->Write(&write_buf, &wopt) != 0) { + const int errcode = errno; + PLOG_IF(WARNING, errcode != EPIPE) << "Fail to write into " << *sock; + _controller.SetFailed(errcode, "Fail to write into %s", + sock->description().c_str()); + return; + } + + if (span) { + // TODO: this is not sent + span->set_sent_us(butil::cpuwide_time_us()); + } +} + +ParseResult ParseThriftMessage(butil::IOBuf* source, + Socket*, bool /*read_eof*/, const void* /*arg*/) { + char header_buf[sizeof(thrift_head_t) + 4]; + const size_t n = source->copy_to(header_buf, sizeof(header_buf)); + if (n < sizeof(header_buf)) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + + const uint32_t sz = ntohl(*(uint32_t*)(header_buf + sizeof(thrift_head_t))); + uint32_t version = sz & THRIFT_HEAD_VERSION_MASK; + if (version != THRIFT_HEAD_VERSION_1) { + RPC_VLOG << "version=" << version + << " doesn't match THRIFT_VERSION=" << THRIFT_HEAD_VERSION_1; + return MakeParseError(PARSE_ERROR_TRY_OTHERS); + } + // suppress strict-aliasing warning + thrift_head_t* head = (thrift_head_t*)header_buf; + const uint32_t body_len = ntohl(head->body_len); + if (body_len > FLAGS_max_body_size) { + return MakeParseError(PARSE_ERROR_TOO_BIG_DATA); + } else if (source->length() < sizeof(thrift_head_t) + body_len) { + return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); + } + + MostCommonMessage* msg = MostCommonMessage::Get(); + source->pop_front(sizeof(thrift_head_t)); + source->cutn(&msg->payload, body_len); + return MakeMessage(msg); +} + +inline void ProcessThriftFramedRequestNoExcept(ThriftService* service, Controller* cntl, + ThriftFramedMessage* req, ThriftFramedMessage* res, + ThriftClosure* done) { + // NOTE: done is not actually run before ResumeRunning() is called so that + // we can still set `cntl' in the catch branch. + done->SuspendRunning(); + try { + service->ProcessThriftFramedRequest(cntl, req, res, done); + } catch (std::exception& e) { + cntl->SetFailed(EINTERNAL, "Catched exception: %s", e.what()); + } catch (std::string& e) { + cntl->SetFailed(EINTERNAL, "Catched std::string: %s", e.c_str()); + } catch (const char* e) { + cntl->SetFailed(EINTERNAL, "Catched const char*: %s", e); + } catch (...) { + cntl->SetFailed(EINTERNAL, "Catched unknown exception"); + } + done->ResumeRunning(); +} + +namespace { +struct CallMethodInBackupThreadArgs { + ThriftService* service; + Controller* controller; + ThriftFramedMessage* request; + ThriftFramedMessage* response; + ThriftClosure* done; +}; +} + +static void CallMethodInBackupThread(void* void_args) { + CallMethodInBackupThreadArgs* args = (CallMethodInBackupThreadArgs*)void_args; + ProcessThriftFramedRequestNoExcept(args->service, + args->controller, + args->request, + args->response, + args->done); + delete args; +} + +static void EndRunningCallMethodInPool(ThriftService* service, + Controller* controller, + ThriftFramedMessage* request, + ThriftFramedMessage* response, + ThriftClosure* done) { + CallMethodInBackupThreadArgs* args = new CallMethodInBackupThreadArgs; + args->service = service; + args->controller = controller; + args->request = request; + args->response = response; + args->done = done; + return EndRunningUserCodeInPool(CallMethodInBackupThread, args); +}; + +void ProcessThriftRequest(InputMessageBase* msg_base) { + const int64_t start_parse_us = butil::cpuwide_time_us(); + + DestroyingPtr msg(static_cast(msg_base)); + SocketUniquePtr socket_guard(msg->ReleaseSocket()); + Socket* socket = socket_guard.get(); + const Server* server = static_cast(msg_base->arg()); + ScopedNonServiceError non_service_error(server); + + ThriftClosure* thrift_done = new ThriftClosure; + ClosureGuard done_guard(thrift_done); + Controller* cntl = &(thrift_done->_controller); + ThriftFramedMessage* req = &(thrift_done->_request); + ThriftFramedMessage* res = &(thrift_done->_response); + thrift_done->_received_us = msg->received_us(); + + ServerPrivateAccessor server_accessor(server); + const bool security_mode = server->options().security_mode() && + socket->user() == server_accessor.acceptor(); + ControllerPrivateAccessor accessor(cntl); + accessor.set_server(server) + .set_security_mode(security_mode) + .set_peer_id(socket->id()) + .set_remote_side(socket->remote_side()) + .set_local_side(socket->local_side()) + .set_request_protocol(PROTOCOL_THRIFT) + .set_begin_time_us(msg_base->received_us()) + .move_in_server_receiving_sock(socket_guard); + + uint32_t seq_id; + ::apache::thrift::protocol::TMessageType mtype; + butil::Status st = ReadThriftMessageBegin( + &msg->payload, &cntl->_thrift_method_name, &mtype, &seq_id); + if (!st.ok()) { + return cntl->SetFailed(EREQUEST, "%s", st.error_cstr()); + } + msg->payload.swap(req->body); + req->field_id = THRIFT_REQUEST_FID; + cntl->set_log_id(seq_id); // Pass seq_id by log_id + + ThriftService* service = server->options().thrift_service; + if (service == NULL) { + LOG_EVERY_SECOND(ERROR) + << "Received thrift request however the server does not set" + " ServerOptions.thrift_service, close the connection."; + return cntl->SetFailed(EINTERNAL, "ServerOptions.thrift_service is NULL"); + } + + // Switch to service-specific error. + non_service_error.release(); + MethodStatus* method_status = service->_status; + if (method_status) { + if (!method_status->OnRequested()) { + return cntl->SetFailed(ELIMIT, "Reached %s's max_concurrency=%d", + cntl->thrift_method_name().c_str(), + method_status->MaxConcurrency()); + } + } + + // Tag the bthread with this server's key for thread_local_data(). + if (server->thread_local_options().thread_local_data_factory) { + bthread_assign_data((void*)&server->thread_local_options()); + } + + std::shared_ptr span; + if (IsTraceable(false)) { + span = Span::CreateServerSpan(0, 0, 0, msg->base_real_us()); + accessor.set_span(span); + span->set_log_id(seq_id); + span->set_remote_side(cntl->remote_side()); + span->set_protocol(PROTOCOL_THRIFT); + span->set_received_us(msg->received_us()); + span->set_start_parse_us(start_parse_us); + span->set_request_size(sizeof(thrift_head_t) + req->body.size()); + } + + if (!server->IsRunning()) { + return cntl->SetFailed(ELOGOFF, "Server is stopping"); + } + if (socket->is_overcrowded() && !server->options().ignore_eovercrowded) { + return cntl->SetFailed(EOVERCROWDED, "Connection to %s is overcrowded", + butil::endpoint2str(socket->remote_side()).c_str()); + } + if (!server_accessor.AddConcurrency(cntl)) { + return cntl->SetFailed(ELIMIT, "Reached server's max_concurrency=%d", + server->options().max_concurrency); + } + if (FLAGS_usercode_in_pthread && TooManyUserCode()) { + return cntl->SetFailed(ELIMIT, "Too many user code to run when" + " -usercode_in_pthread is on"); + } + + if (!server->AcceptRequest(cntl)) { + return; + } + + msg.reset(); // optional, just release resource ASAP + + if (span) { + span->ResetServerSpanName(cntl->thrift_method_name()); + span->set_start_callback_us(butil::cpuwide_time_us()); + span->AsParent(); + } + + done_guard.release(); + + if (!FLAGS_usercode_in_pthread) { + return ProcessThriftFramedRequestNoExcept(service, cntl, req, res, thrift_done); + } + + if (BeginRunningUserCode()) { + ProcessThriftFramedRequestNoExcept(service, cntl, req, res, thrift_done); + return EndRunningUserCodeInPlace(); + } else { + return EndRunningCallMethodInPool(service, cntl, req, res, thrift_done); + } +} + +void ProcessThriftResponse(InputMessageBase* msg_base) { + const int64_t start_parse_us = butil::cpuwide_time_us(); + DestroyingPtr msg(static_cast(msg_base)); + + // Fetch correlation id that we saved before in `PacThriftRequest' + const CallId cid = { static_cast(msg->socket()->correlation_id()) }; + Controller* cntl = NULL; + const int rc = bthread_id_lock(cid, (void**)&cntl); + if (rc != 0) { + LOG_IF(ERROR, rc != EINVAL && rc != EPERM) + << "Fail to lock correlation_id=" << cid << ": " << berror(rc); + return; + } + + ControllerPrivateAccessor accessor(cntl); + if (auto span = accessor.span()) { + span->set_base_real_us(msg->base_real_us()); + span->set_received_us(msg->received_us()); + span->set_response_size(msg->payload.length()); + span->set_start_parse_us(start_parse_us); + } + + const int saved_error = cntl->ErrorCode(); + do { + // The following code was taken from thrift auto generate code + std::string fname; + ::apache::thrift::protocol::TMessageType mtype; + uint32_t seq_id = 0; // unchecked + + butil::Status st = ReadThriftMessageBegin(&msg->payload, &fname, &mtype, &seq_id); + if (!st.ok()) { + cntl->SetFailed(ERESPONSE, "%s", st.error_cstr()); + break; + } + if (mtype == ::apache::thrift::protocol::T_EXCEPTION) { + ::apache::thrift::TApplicationException x; + ReadThriftException(msg->payload, &x); + // TODO: Convert exception type to brpc errors. + cntl->SetFailed(x.what()); + break; + } + if (mtype != ::apache::thrift::protocol::T_REPLY) { + cntl->SetFailed(ERESPONSE, "message_type is not T_REPLY"); + break; + } + if (fname != cntl->thrift_method_name()) { + cntl->SetFailed(ERESPONSE, + "response.method_name=%s does not match request.method_name=%s", + fname.c_str(), cntl->thrift_method_name().c_str()); + break; + } + + // Read presult + + // MUST be ThriftFramedMessage (checked in SerializeThriftRequest) + ThriftFramedMessage* response = (ThriftFramedMessage*)cntl->response(); + if (response) { + if (response->raw_instance()) { + if (!ReadThriftStruct(msg->payload, response->raw_instance(), + THRIFT_RESPONSE_FID)) { + cntl->SetFailed(ERESPONSE, "Fail to read presult"); + break; + } + } else { + msg->payload.swap(response->body); + response->field_id = THRIFT_RESPONSE_FID; + } + } // else just ignore the response. + } while (false); + + // Unlocks correlation_id inside. Revert controller's + // error code if it version check of `cid' fails + msg.reset(); // optional, just release resource ASAP + accessor.OnResponse(cid, saved_error); +} + +bool VerifyThriftRequest(const InputMessageBase* msg_base) { + Server* server = (Server*)msg_base->arg(); + if (server->options().auth) { + LOG(WARNING) << "thrift does not support authentication"; + return false; + } + return true; +} + +void SerializeThriftRequest(butil::IOBuf* request_buf, Controller* cntl, + const google::protobuf::Message* req_base) { + if (req_base == NULL) { + return cntl->SetFailed(EREQUEST, "request is NULL"); + } + if (req_base->GetDescriptor() != ThriftFramedMessage::descriptor()) { + return cntl->SetFailed(EINVAL, "Type of request must be ThriftFramedMessage"); + } + if (cntl->response() != NULL && + cntl->response()->GetDescriptor() != ThriftFramedMessage::descriptor()) { + return cntl->SetFailed(EINVAL, "Type of response must be ThriftFramedMessage"); + } + + const std::string& method_name = cntl->thrift_method_name(); + // we should do more check on the thrift method name, but since it is rare when + // the method_name is just some white space or something else + if (method_name.empty() || method_name[0] == ' ') { + return cntl->SetFailed(ENOMETHOD, "Invalid thrift_method_name!"); + } + if (method_name.size() > MAX_THRIFT_METHOD_NAME_LENGTH) { + return cntl->SetFailed(ENOMETHOD, "thrift_method_name is too long"); + } + + const ThriftFramedMessage* req = (const ThriftFramedMessage*)req_base; + + // xxx_pargs write + if (req->raw_instance()) { + auto out_buffer = + THRIFT_STDCXX::make_shared(); + apache::thrift::protocol::TBinaryProtocolT oprot(out_buffer); + + oprot.writeMessageBegin( + method_name, ::apache::thrift::protocol::T_CALL, 0/*seq_id*/); + + uint32_t xfer = 0; + char struct_begin_str[32 + method_name.size()]; + char* p = struct_begin_str; + memcpy(p, "ThriftService_", 14); + p += 14; + memcpy(p, method_name.data(), method_name.size()); + p += method_name.size(); + memcpy(p, "_pargs", 6); + p += 6; + *p = '\0'; + xfer += oprot.writeStructBegin(struct_begin_str); + xfer += oprot.writeFieldBegin("request", ::apache::thrift::protocol::T_STRUCT, + THRIFT_REQUEST_FID); + + // request's write + xfer += req->raw_instance()->Write(&oprot); + + xfer += oprot.writeFieldEnd(); + xfer += oprot.writeFieldStop(); + xfer += oprot.writeStructEnd(); + (void)xfer; + + oprot.writeMessageEnd(); + oprot.getTransport()->writeEnd(); + oprot.getTransport()->flush(); + + uint8_t* buf; + uint32_t sz; + out_buffer->getBuffer(&buf, &sz); + + const thrift_head_t head = { htonl(sz) }; + request_buf->append(&head, sizeof(head)); + request_buf->append(buf, sz); + } else { + const size_t mb_size = ThriftMessageBeginSize(method_name); + char buf[sizeof(thrift_head_t) + mb_size]; + // suppress strict-aliasing warning + thrift_head_t* head = (thrift_head_t*)buf; + head->body_len = htonl(mb_size + req->body.size()); + WriteThriftMessageBegin(buf + sizeof(thrift_head_t), method_name, + ::apache::thrift::protocol::T_CALL, 0/*seq_id*/); + request_buf->append(buf, sizeof(buf)); + request_buf->append(req->body); + } +} + +void PackThriftRequest( + butil::IOBuf* packet_buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor*, + Controller* cntl, + const butil::IOBuf& request, + const Authenticator*) { + ControllerPrivateAccessor accessor(cntl); + if (cntl->connection_type() == CONNECTION_TYPE_SINGLE) { + return cntl->SetFailed( + EINVAL, "thrift protocol can't work with CONNECTION_TYPE_SINGLE"); + } + // Store `correlation_id' into the socket since thrift protocol can't + // pack the field. + accessor.get_sending_socket()->set_correlation_id(correlation_id); + + if (auto span = accessor.span()) { + span->set_request_size(request.length()); + // TODO: Nowhere to set tracing ids. + // request_meta->set_trace_id(span->trace_id()); + // request_meta->set_span_id(span->span_id()); + // request_meta->set_parent_span_id(span->parent_span_id()); + } + packet_buf->append(request); +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/thrift_protocol.h b/src/brpc/policy/thrift_protocol.h new file mode 100755 index 0000000..223e1f1 --- /dev/null +++ b/src/brpc/policy/thrift_protocol.h @@ -0,0 +1,54 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_THRIFT_PROTOCOL_H +#define BRPC_POLICY_THRIFT_PROTOCOL_H + +#include "brpc/protocol.h" + +namespace brpc { +namespace policy { + +// Parse binary protocol format of thrift framed +ParseResult ParseThriftMessage(butil::IOBuf* source, Socket* socket, bool read_eof, const void *arg); + +// Actions to a (client) request in thrift binary framed format +void ProcessThriftRequest(InputMessageBase* msg); + +// Actions to a (server) response in thrift binary framed format +void ProcessThriftResponse(InputMessageBase* msg); + +void SerializeThriftRequest(butil::IOBuf* request_buf, Controller* controller, + const google::protobuf::Message* request); + +void PackThriftRequest( + butil::IOBuf* packet_buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor*, + Controller* controller, + const butil::IOBuf&, + const Authenticator*); + +// Verify authentication information in thrift binary format +bool VerifyThriftRequest(const InputMessageBase *msg); + +} // namespace policy +} // namespace brpc + +#endif // BRPC_POLICY_THRIFT_PROTOCOL_H diff --git a/src/brpc/policy/timeout_concurrency_limiter.cpp b/src/brpc/policy/timeout_concurrency_limiter.cpp new file mode 100644 index 0000000..21aad33 --- /dev/null +++ b/src/brpc/policy/timeout_concurrency_limiter.cpp @@ -0,0 +1,186 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/timeout_concurrency_limiter.h" +#include "brpc/controller.h" +#include "brpc/errno.pb.h" +#include +#include + +namespace brpc { +namespace policy { + +DEFINE_int32(timeout_cl_sample_window_size_ms, 1000, + "Duration of the sampling window."); +DEFINE_int32(timeout_cl_min_sample_count, 100, + "During the duration of the sampling window, if the number of " + "requests collected is less than this value, the sampling window " + "will be discarded."); +DEFINE_int32(timeout_cl_max_sample_count, 200, + "During the duration of the sampling window, once the number of " + "requests collected is greater than this value, even if the " + "duration of the window has not ended, the max_concurrency will " + "be updated and a new sampling window will be started."); +DEFINE_double(timeout_cl_sampling_interval_ms, 0.1, + "Interval for sampling request in auto concurrency limiter"); +DEFINE_int32(timeout_cl_initial_avg_latency_us, 500, + "Initial max concurrency for gradient concurrency limiter"); +DEFINE_bool( + timeout_cl_enable_error_punish, true, + "Whether to consider failed requests when calculating maximum concurrency"); +DEFINE_double( + timeout_cl_fail_punish_ratio, 1.0, + "Use the failed requests to punish normal requests. The larger " + "the configuration item, the more aggressive the penalty strategy."); +DEFINE_int32(timeout_cl_default_timeout_ms, 500, + "Default timeout for rpc request"); +DEFINE_int32(timeout_cl_max_concurrency, 100, + "When average latency statistics not refresh, this flag can keep " + "requests not exceed this max concurrency"); + +TimeoutConcurrencyLimiter::TimeoutConcurrencyLimiter() + : _avg_latency_us(FLAGS_timeout_cl_initial_avg_latency_us), + _last_sampling_time_us(0), + _timeout_ms(FLAGS_timeout_cl_default_timeout_ms), + _max_concurrency(FLAGS_timeout_cl_max_concurrency) {} + +TimeoutConcurrencyLimiter::TimeoutConcurrencyLimiter( + const TimeoutConcurrencyConf &conf) + : _avg_latency_us(FLAGS_timeout_cl_initial_avg_latency_us), + _last_sampling_time_us(0), + _timeout_ms(conf.timeout_ms), + _max_concurrency(conf.max_concurrency) {} + +TimeoutConcurrencyLimiter *TimeoutConcurrencyLimiter::New( + const AdaptiveMaxConcurrency &amc) const { + return new (std::nothrow) + TimeoutConcurrencyLimiter(static_cast(amc)); +} + +bool TimeoutConcurrencyLimiter::OnRequested(int current_concurrency, + Controller *cntl) { + auto timeout_ms = _timeout_ms; + if (cntl != nullptr && cntl->timeout_ms() != UNSET_MAGIC_NUM) { + timeout_ms = cntl->timeout_ms(); + } + // In extreme cases, the average latency may be greater than requested + // timeout, allow currency_concurrency is 1 ensures the average latency can + // be obtained renew. + return current_concurrency == 1 || + (current_concurrency <= _max_concurrency && + _avg_latency_us < timeout_ms * 1000); +} + +void TimeoutConcurrencyLimiter::OnResponded(int error_code, + int64_t latency_us) { + if (ELIMIT == error_code) { + return; + } + + const int64_t now_time_us = butil::cpuwide_time_us(); + int64_t last_sampling_time_us = + _last_sampling_time_us.load(butil::memory_order_relaxed); + + if (last_sampling_time_us == 0 || + now_time_us - last_sampling_time_us >= + FLAGS_timeout_cl_sampling_interval_ms * 1000) { + bool sample_this_call = _last_sampling_time_us.compare_exchange_strong( + last_sampling_time_us, now_time_us, butil::memory_order_relaxed); + if (sample_this_call) { + bool sample_window_submitted = + AddSample(error_code, latency_us, now_time_us); + if (sample_window_submitted) { + // The following log prints has data-race in extreme cases, + // unless you are in debug, you should not open it. + VLOG(1) << "Sample window submitted, current avg_latency_us:" + << _avg_latency_us; + } + } + } +} + +int TimeoutConcurrencyLimiter::MaxConcurrency() { + return FLAGS_timeout_cl_max_concurrency; +} + +int TimeoutConcurrencyLimiter::ResetMaxConcurrency( + const AdaptiveMaxConcurrency &) { + return -1; +} + +bool TimeoutConcurrencyLimiter::AddSample(int error_code, int64_t latency_us, + int64_t sampling_time_us) { + std::unique_lock lock_guard(_sw_mutex); + if (_sw.start_time_us == 0) { + _sw.start_time_us = sampling_time_us; + } + + if (error_code != 0 && FLAGS_timeout_cl_enable_error_punish) { + ++_sw.failed_count; + _sw.total_failed_us += latency_us; + } else if (error_code == 0) { + ++_sw.succ_count; + _sw.total_succ_us += latency_us; + } + + if (_sw.succ_count + _sw.failed_count < FLAGS_timeout_cl_min_sample_count) { + if (sampling_time_us - _sw.start_time_us >= + FLAGS_timeout_cl_sample_window_size_ms * 1000) { + // If the sample size is insufficient at the end of the sampling + // window, discard the entire sampling window + ResetSampleWindow(sampling_time_us); + } + return false; + } + if (sampling_time_us - _sw.start_time_us < + FLAGS_timeout_cl_sample_window_size_ms * 1000 && + _sw.succ_count + _sw.failed_count < FLAGS_timeout_cl_max_sample_count) { + return false; + } + + if (_sw.succ_count > 0) { + UpdateAvgLatency(); + } else { + // All request failed + AdjustAvgLatency(_avg_latency_us * 2); + } + ResetSampleWindow(sampling_time_us); + return true; +} + +void TimeoutConcurrencyLimiter::ResetSampleWindow(int64_t sampling_time_us) { + _sw.start_time_us = sampling_time_us; + _sw.succ_count = 0; + _sw.failed_count = 0; + _sw.total_failed_us = 0; + _sw.total_succ_us = 0; +} + +void TimeoutConcurrencyLimiter::AdjustAvgLatency(int64_t avg_latency_us) { + _avg_latency_us = avg_latency_us; +} + +void TimeoutConcurrencyLimiter::UpdateAvgLatency() { + double failed_punish = + _sw.total_failed_us * FLAGS_timeout_cl_fail_punish_ratio; + auto avg_latency_us = + std::ceil((failed_punish + _sw.total_succ_us) / _sw.succ_count); + AdjustAvgLatency(avg_latency_us); +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/timeout_concurrency_limiter.h b/src/brpc/policy/timeout_concurrency_limiter.h new file mode 100644 index 0000000..f7e4dde --- /dev/null +++ b/src/brpc/policy/timeout_concurrency_limiter.h @@ -0,0 +1,79 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_POLICY_TIMEOUT_CONCURRENCY_LIMITER_H +#define BRPC_POLICY_TIMEOUT_CONCURRENCY_LIMITER_H + +#include "brpc/concurrency_limiter.h" + +namespace brpc { +namespace policy { + +class TimeoutConcurrencyLimiter : public ConcurrencyLimiter { + public: + TimeoutConcurrencyLimiter(); + explicit TimeoutConcurrencyLimiter(const TimeoutConcurrencyConf& conf); + + bool OnRequested(int current_concurrency, Controller* cntl) override; + + void OnResponded(int error_code, int64_t latency_us) override; + + int MaxConcurrency() override; + + int ResetMaxConcurrency(const AdaptiveMaxConcurrency&) override; + + TimeoutConcurrencyLimiter* New( + const AdaptiveMaxConcurrency&) const override; + + private: + struct SampleWindow { + SampleWindow() + : start_time_us(0), + succ_count(0), + failed_count(0), + total_failed_us(0), + total_succ_us(0) {} + int64_t start_time_us; + int32_t succ_count; + int32_t failed_count; + int64_t total_failed_us; + int64_t total_succ_us; + }; + + bool AddSample(int error_code, int64_t latency_us, + int64_t sampling_time_us); + + // The following methods are not thread safe and can only be called + // in AppSample() + void ResetSampleWindow(int64_t sampling_time_us); + void UpdateAvgLatency(); + void AdjustAvgLatency(int64_t avg_latency_us); + + // modified per sample-window or more + int64_t _avg_latency_us; + // modified per sample. + BAIDU_CACHELINE_ALIGNMENT butil::atomic _last_sampling_time_us; + butil::Mutex _sw_mutex; + SampleWindow _sw; + int64_t _timeout_ms; + int _max_concurrency; +}; + +} // namespace policy +} // namespace brpc + +#endif // BRPC_POLICY_TIMEOUT_CONCURRENCY_LIMITER_H diff --git a/src/brpc/policy/ubrpc2pb_protocol.cpp b/src/brpc/policy/ubrpc2pb_protocol.cpp new file mode 100644 index 0000000..2f5194c --- /dev/null +++ b/src/brpc/policy/ubrpc2pb_protocol.cpp @@ -0,0 +1,573 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // MethodDescriptor +#include // Message +#include + +#include "butil/iobuf.h" // butil::IOBuf +#include "butil/strings/string_util.h" +#include "butil/time.h" + +#include "brpc/controller.h" // Controller +#include "brpc/socket.h" // Socket +#include "brpc/server.h" // Server +#include "brpc/details/server_private_accessor.h" +#include "brpc/span.h" +#include "brpc/errno.pb.h" // EREQUEST, ERESPONSE +#include "brpc/details/controller_private_accessor.h" +#include "brpc/policy/most_common_message.h" +#include "brpc/policy/ubrpc2pb_protocol.h" +#include "brpc/compress.h" + + +namespace brpc { + +extern const int64_t IDL_VOID_RESULT; + +namespace policy { + +static const unsigned int UBRPC_NSHEAD_VERSION = 1000; + +void UbrpcAdaptor::ParseNsheadMeta( + const Server&, const NsheadMessage& request, Controller* cntl, + NsheadMeta* out_meta) const { + butil::IOBufAsZeroCopyInputStream zc_stream(request.body); + mcpack2pb::InputStream stream(&zc_stream); + if (!::mcpack2pb::unbox(&stream)) { + cntl->SetFailed(EREQUEST, "Request is not a compack/mcpack2 object"); + return; + } + mcpack2pb::ObjectIterator it1(&stream, request.body.size() - stream.popped_bytes()); + bool found_content = false; + for (; it1 != NULL; ++it1) { + if (it1->name == "content") { + found_content = true; + break; + } + } + if (!found_content) { + cntl->SetFailed(EREQUEST, "Fail to find request.content"); + return; + } + if (it1->value.type() != mcpack2pb::FIELD_ARRAY) { + cntl->SetFailed(EREQUEST, "Expect request.content to be array, " + "actually %s", mcpack2pb::type2str(it1->value.type())); + return; + } + + mcpack2pb::ArrayIterator it2(it1->value); + if (it2 == NULL) { + cntl->SetFailed(EREQUEST, "Fail to parse request.content as array"); + return; + } + std::string service_name; + std::string method_name; + bool has_params = false; + size_t user_req_offset = 0; + size_t user_req_size = 0; + for (mcpack2pb::ObjectIterator it3(*it2); it3 != NULL; ++it3) { + if (it3->name == "service_name") { + if (it3->value.type() != mcpack2pb::FIELD_STRING) { + cntl->SetFailed(EREQUEST, "Expect request.content[0].service_name" + " to be string, actually %s", + mcpack2pb::type2str(it3->value.type())); + return; + } + it3->value.as_string(&service_name, "request.content[0].service_name"); + } else if (it3->name == "method") { + if (it3->value.type() != mcpack2pb::FIELD_STRING) { + cntl->SetFailed(EREQUEST, "Expect request.content[0].method" + " to be string, actually %s", + mcpack2pb::type2str(it3->value.type())); + return; + } + it3->value.as_string(&method_name, "request.content[0].method"); + } else if (it3->name == "id") { + if (!mcpack2pb::is_primitive(it3->value.type()) || + !mcpack2pb::is_integral((mcpack2pb::PrimitiveFieldType)it3->value.type())) { + cntl->SetFailed(ERESPONSE, "request.content[0].id must be " + "integer, actually %s", + mcpack2pb::type2str(it3->value.type())); + return; + } + out_meta->set_correlation_id( + it3->value.as_int64("request.content[0].id")); + } else if (it3->name == "params") { + if (it3->value.type() != mcpack2pb::FIELD_OBJECT) { + cntl->SetFailed(EREQUEST, "Expect request.content[0].params " + "to be object, actually %s", + mcpack2pb::type2str(it3->value.type())); + return; + } + has_params = true; + user_req_offset = stream.popped_bytes(); + user_req_size = it3->value.size(); + const size_t stream_end = stream.popped_bytes() + it3->value.size(); + mcpack2pb::ObjectIterator it4(it3->value); + if (it4 == NULL || it4.field_count() == 0) { + cntl->SetFailed(EREQUEST, "Nothing in request.content[0].params"); + return; + } + if (it4.field_count() == 1) { + user_req_offset = stream.popped_bytes(); + user_req_size = it4->value.size(); + } + // Pop left bytes, otherwise ++it3 may complain about + // "not fully consumed". + if (stream_end > stream.popped_bytes()) { + stream.popn(stream_end - stream.popped_bytes()); + } + } + } + if (service_name.empty()) { + cntl->SetFailed(EREQUEST, "Fail to find request.content[0].service_name"); + return; + } + if (method_name.empty()) { + cntl->SetFailed(EREQUEST, "Fail to find request.content[0].method"); + return; + } + if (!has_params) { + cntl->SetFailed(EREQUEST, "Fail to find request.content[0].params"); + return; + } + + // Change request.body with the user's request. + butil::IOBuf& buf = const_cast(request.body); + buf.pop_front(user_req_offset); + if (buf.size() != user_req_size) { + if (buf.size() < user_req_size) { + cntl->SetFailed(EREQUEST, "request_size=%" PRIu64 " is shorter than" + "specified=%" PRIu64, (uint64_t)buf.size(), + (uint64_t)user_req_size); + return; + } + buf.pop_back(buf.size() - user_req_size); + } + std::string full_method_name; + full_method_name.reserve(service_name.size() + 1 + method_name.size()); + full_method_name.append(service_name); + full_method_name.push_back('.'); + full_method_name.append(method_name); + out_meta->set_full_method_name(full_method_name); +} + +void UbrpcAdaptor::ParseRequestFromIOBuf( + const NsheadMeta&, const NsheadMessage& raw_req, + Controller* cntl, google::protobuf::Message* pb_req) const { + const std::string msg_name = butil::EnsureString(pb_req->GetDescriptor()->full_name()); + mcpack2pb::MessageHandler handler = mcpack2pb::find_message_handler(msg_name); + if (handler.parse_body == NULL) { + return cntl->SetFailed(EREQUEST, "Fail to find parser of %s", + msg_name.c_str()); + } + butil::IOBufAsZeroCopyInputStream bodystream(raw_req.body); + if (!handler.parse_body(pb_req, &bodystream, raw_req.body.size())) { + cntl->SetFailed(EREQUEST, "Fail to parse %s", msg_name.c_str()); + return; + } +} + +static void AppendError(const NsheadMeta& meta, + Controller* cntl, butil::IOBuf& buf) { + butil::IOBufAsZeroCopyOutputStream wrapper(&buf); + mcpack2pb::OutputStream ostream(&wrapper); + mcpack2pb::Serializer sr(&ostream); + sr.begin_object(); + { + sr.begin_mcpack_array("content", mcpack2pb::FIELD_OBJECT); + sr.begin_object(); + { + sr.add_int64("id", meta.correlation_id()); + sr.begin_object("error"); + sr.add_int32("code", cntl->ErrorCode()); + sr.add_string("message", cntl->ErrorText()); + sr.end_object(); + } + sr.end_object(); + sr.end_array(); + } + sr.end_object(); + ostream.done(); +} + +void UbrpcAdaptor::SerializeResponseToIOBuf( + const NsheadMeta& meta, Controller* cntl, + const google::protobuf::Message* pb_res, NsheadMessage* raw_res) const { + CompressType type = cntl->response_compress_type(); + if (type != COMPRESS_TYPE_NONE) { + LOG(WARNING) << "ubrpc protocol doesn't support compression"; + type = COMPRESS_TYPE_NONE; + } + + if (pb_res == NULL || cntl->Failed()) { + if (!cntl->Failed()) { + cntl->SetFailed(ERESPONSE, "response was not created yet"); + } + return AppendError(meta, cntl, raw_res->body); + } + // TODO: This is optional since serializing already checks. + // if (!pb_res->IsInitialized()) { + // cntl->SetFailed(ERESPONSE, "Missing required fields in response: %s", + // pb_res->InitializationErrorString().c_str()); + // return AppendError(meta, cntl, raw_res->body); + // } + + const std::string msg_name = butil::EnsureString(pb_res->GetDescriptor()->full_name()); + mcpack2pb::MessageHandler handler = mcpack2pb::find_message_handler(msg_name); + if (handler.serialize_body == NULL) { + cntl->SetFailed(ERESPONSE, "Fail to find serializer of %s", + msg_name.c_str()); + return AppendError(meta, cntl, raw_res->body); + } + + butil::IOBufAsZeroCopyOutputStream owrapper(&raw_res->body); + mcpack2pb::OutputStream ostream(&owrapper); + mcpack2pb::Serializer sr(&ostream); + sr.begin_object(); + { + sr.begin_mcpack_array("content", mcpack2pb::FIELD_OBJECT); + sr.begin_object(); + { + sr.add_int64("id", meta.correlation_id()); + if (cntl->idl_result() != IDL_VOID_RESULT) { + // Set `result' only when idl_result is (probably) changed. + // There's definitely false negative, but it should be OK in + // most cases. + sr.add_int64("result", cntl->idl_result()); + } + sr.begin_object("result_params"); + const char* const response_name = cntl->idl_names().response_name; + if (response_name != NULL && *response_name) { + sr.begin_object(response_name); + handler.serialize_body(*pb_res, sr, _format); + sr.end_object(); + } else { + handler.serialize_body(*pb_res, sr, _format); + } + sr.end_object(); + } + sr.end_object(); + sr.end_array(); + } + sr.end_object(); + ostream.done(); + if (!sr.good()) { + cntl->SetFailed(ERESPONSE, "Fail to serialize %s", msg_name.c_str()); + raw_res->body.clear(); + return AppendError(meta, cntl, raw_res->body); + } +} + +static void ParseResponse(Controller* cntl, butil::IOBuf& buf, + google::protobuf::Message* res) { + if (res == NULL) { + // silently ignore response. + return; + } + const std::string msg_name = butil::EnsureString(res->GetDescriptor()->full_name()); + mcpack2pb::MessageHandler handler = mcpack2pb::find_message_handler(msg_name); + if (handler.parse_body == NULL) { + return cntl->SetFailed(ERESPONSE, "Fail to find parser of %s", + msg_name.c_str()); + } + butil::IOBufAsZeroCopyInputStream zc_stream(buf); + mcpack2pb::InputStream stream(&zc_stream); + if (!::mcpack2pb::unbox(&stream)) { + cntl->SetFailed(ERESPONSE, "Response is not a compack/mcpack2 object"); + return; + } + mcpack2pb::ObjectIterator it1(&stream, buf.size() - stream.popped_bytes()); + bool found_content = false; + for (; it1 != NULL; ++it1) { + if (it1->name == "content") { + found_content = true; + break; + } + } + if (!found_content) { + cntl->SetFailed(ERESPONSE, "Fail to find response.content"); + return; + } + if (it1->value.type() != mcpack2pb::FIELD_ARRAY) { + cntl->SetFailed(ERESPONSE, "Expect response.content to be array," + " actually %s", mcpack2pb::type2str(it1->value.type())); + return; + } + mcpack2pb::ArrayIterator it2(it1->value); + if (it2 == NULL) { + cntl->SetFailed("Fail to parse response.content as array"); + return; + } + bool has_result_params = false; + size_t user_res_offset = 0; + size_t user_res_size = 0; + const char* response_name = "result_params"; + for (mcpack2pb::ObjectIterator it3(*it2); it3 != NULL; ++it3) { + if (it3->name == "error") { + if (it3->value.type() != mcpack2pb::FIELD_OBJECT) { + cntl->SetFailed(ERESPONSE, "Expect response.content[0].error" + " to be object, actually %s", + mcpack2pb::type2str(it3->value.type())); + return; + } + int32_t code = 0; + std::string msg; + for (mcpack2pb::ObjectIterator it4(it3->value); it4 != NULL; ++it4) { + if (it4->name == "code") { + if (!mcpack2pb::is_primitive(it4->value.type()) || + !mcpack2pb::is_integral( + (mcpack2pb::PrimitiveFieldType)it4->value.type())) { + cntl->SetFailed( + ERESPONSE, "Expect response.content[0].error.code " + "to be integer, actually %s", + mcpack2pb::type2str(it4->value.type())); + return; + } + code = it4->value.as_int32("response.content[0].error.code"); + if (code == 0) { + cntl->SetFailed(ERESPONSE, + "response.content[0].error.code is 0"); + return; + } + } else if (it4->name == "message") { + if (it4->value.type() != mcpack2pb::FIELD_STRING) { + cntl->SetFailed( + ERESPONSE, "Expect response.content[0].error." + "message to be string, actually %s", + mcpack2pb::type2str(it4->value.type())); + return; + } + it4->value.as_string(&msg, "response.content[0].error.message"); + } // else field "data" (probably non-ASCII) is ignored. + } + if (code == 0) { + cntl->SetFailed(ERESPONSE, + "Fail to find response.content[0].error.code"); + return; + } + if (msg.empty()) { + cntl->SetFailed(ERESPONSE, + "Fail to find response.content[0].error.message"); + return; + } + cntl->SetFailed(code, "%s", msg.c_str()); + return; // no need to parse left fields. + } else if (it3->name == "result") { + if (!mcpack2pb::is_primitive(it3->value.type()) || + !mcpack2pb::is_integral((mcpack2pb::PrimitiveFieldType)it3->value.type())) { + cntl->SetFailed(ERESPONSE, "Expect response.content[0].result" + " to be integer, actually %s", + mcpack2pb::type2str(it3->value.type())); + return; + } + cntl->set_idl_result(it3->value.as_int64("response.content[0].result")); + } else if (it3->name == "result_params") { + if (it3->value.type() != mcpack2pb::FIELD_OBJECT) { + cntl->SetFailed(ERESPONSE, "Expect response.content[0].result_params" + " to be object, actually %s", + mcpack2pb::type2str(it3->value.type())); + return; + } + has_result_params = true; + user_res_offset = stream.popped_bytes(); + user_res_size = it3->value.size(); + const size_t stream_end = stream.popped_bytes() + it3->value.size(); + const char* const expname = cntl->idl_names().response_name; + if (expname != NULL && *expname) { + mcpack2pb::ObjectIterator it4(it3->value); + bool found_response_name = false; + for (; it4 != NULL; ++it4) { + if (it4->name == expname) { + found_response_name = true; + break; + } + } + if (!found_response_name) { + cntl->SetFailed(ERESPONSE, "Fail to find response." + "content[0].result_params.%s", expname); + return; + } + response_name = expname; + user_res_offset = stream.popped_bytes(); + user_res_size = it4->value.size(); + } + // Pop left bytes, otherwise ++it3 may complain about + // "not fully consumed". + if (stream_end > stream.popped_bytes()) { + stream.popn(stream_end - stream.popped_bytes()); + } + } + } + if (!has_result_params) { + cntl->SetFailed(ERESPONSE, + "Fail to find response.content[0].result_params"); + return; + } + + buf.pop_front(user_res_offset); + if (buf.size() != user_res_size) { + if (buf.size() < user_res_size) { + cntl->SetFailed(ERESPONSE, "response_size=%" PRIu64 " is shorter " + "than specified=%" PRIu64, (uint64_t)buf.size(), + (uint64_t)user_res_size); + return; + } + buf.pop_back(buf.size() - user_res_size); + } + butil::IOBufAsZeroCopyInputStream bufstream(buf); + if (!handler.parse_body(res, &bufstream, buf.size())) { + cntl->SetFailed(ERESPONSE, "Fail to parse %s from response.content[0]." + "result_params.%s", msg_name.c_str(), response_name); + return; + } +} + +void ProcessUbrpcResponse(InputMessageBase* msg_base) { + const int64_t start_parse_us = butil::cpuwide_time_us(); + DestroyingPtr msg(static_cast(msg_base)); + Socket* socket = msg->socket(); + + // Fetch correlation id that we saved before in `PackUbrpcRequest' + const bthread_id_t cid = { static_cast(socket->correlation_id()) }; + Controller* cntl = NULL; + const int rc = bthread_id_lock(cid, (void**)&cntl); + if (rc != 0) { + LOG_IF(ERROR, rc != EINVAL && rc != EPERM) + << "Fail to lock correlation_id=" << cid << ": " << berror(rc); + return; + } + + ControllerPrivateAccessor accessor(cntl); + if (auto span = accessor.span()) { + span->set_base_real_us(msg->base_real_us()); + span->set_received_us(msg->received_us()); + span->set_response_size(msg->meta.size() + msg->payload.size()); + span->set_start_parse_us(start_parse_us); + } + const int saved_error = cntl->ErrorCode(); + ParseResponse(cntl, msg->payload, cntl->response()); + + // Unlocks correlation_id inside. Revert controller's + // error code if it version check of `cid' fails + msg.reset(); // optional, just release resource ASAP + accessor.OnResponse(cid, saved_error); +} + +static void SerializeUbrpcRequest(butil::IOBuf* buf, Controller* cntl, + const google::protobuf::Message* request, + mcpack2pb::SerializationFormat format) { + CompressType type = cntl->request_compress_type(); + if (type != COMPRESS_TYPE_NONE) { + return cntl->SetFailed( + EREQUEST, "ubrpc protocol doesn't support compression"); + } + if (cntl->method() == NULL) { + return cntl->SetFailed(ENOMETHOD, "method is NULL"); + } + const std::string msg_name = butil::EnsureString(request->GetDescriptor()->full_name()); + mcpack2pb::MessageHandler handler = mcpack2pb::find_message_handler(msg_name); + if (handler.serialize_body == NULL) { + return cntl->SetFailed(EREQUEST, "Fail to find serializer of %s", + msg_name.c_str()); + } + + butil::IOBufAsZeroCopyOutputStream owrapper(buf); + mcpack2pb::OutputStream ostream(&owrapper); + mcpack2pb::Serializer sr(&ostream); + sr.begin_object(); + { + sr.begin_object("header"); + sr.add_bool("connection", + cntl->connection_type() == CONNECTION_TYPE_POOLED); + sr.end_object(); + + sr.begin_mcpack_array("content", mcpack2pb::FIELD_OBJECT); + sr.begin_object(); + { + sr.add_string("service_name", butil::EnsureString(cntl->method()->service()->name())); + sr.add_int64("id", cntl->call_id().value); + sr.add_string("method", butil::EnsureString(cntl->method()->name())); + sr.begin_object("params"); + const char* const request_name = cntl->idl_names().request_name; + if (request_name != NULL && *request_name) { + sr.begin_object(request_name); + handler.serialize_body(*request, sr, format); + sr.end_object(); + } else { + handler.serialize_body(*request, sr, format); + } + sr.end_object(); + } + sr.end_object(); + sr.end_array(); + } + sr.end_object(); + ostream.done(); + if (!sr.good()) { + return cntl->SetFailed(EREQUEST, "Fail to serialize %s", + msg_name.c_str()); + } +} + +void SerializeUbrpcCompackRequest(butil::IOBuf* buf, Controller* cntl, + const google::protobuf::Message* request) { + return SerializeUbrpcRequest(buf, cntl, request, mcpack2pb::FORMAT_COMPACK); +} + +void SerializeUbrpcMcpack2Request(butil::IOBuf* buf, Controller* cntl, + const google::protobuf::Message* request) { + return SerializeUbrpcRequest(buf, cntl, request, mcpack2pb::FORMAT_MCPACK_V2); +} + +void PackUbrpcRequest(butil::IOBuf* buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor*, + Controller* controller, + const butil::IOBuf& request, + const Authenticator* /*not supported*/) { + ControllerPrivateAccessor accessor(controller); + if (controller->connection_type() == CONNECTION_TYPE_SINGLE) { + return controller->SetFailed( + EINVAL, "ubrpc protocol can't work with CONNECTION_TYPE_SINGLE"); + } + // Store `correlation_id' into Socket since ubrpc protocol doesn't + // contain this field + accessor.get_sending_socket()->set_correlation_id(correlation_id); + + nshead_t nshead; + memset(&nshead, 0, sizeof(nshead_t)); + nshead.log_id = controller->log_id(); + nshead.magic_num = NSHEAD_MAGICNUM; + nshead.body_len = request.size(); + nshead.version = UBRPC_NSHEAD_VERSION; + buf->append(&nshead, sizeof(nshead)); + + // Span* span = accessor.span(); + // if (span) { + // request_meta->set_trace_id(span->trace_id()); + // request_meta->set_span_id(span->span_id()); + // request_meta->set_parent_span_id(span->parent_span_id()); + // } + buf->append(request); +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/ubrpc2pb_protocol.h b/src/brpc/policy/ubrpc2pb_protocol.h new file mode 100644 index 0000000..669222d --- /dev/null +++ b/src/brpc/policy/ubrpc2pb_protocol.h @@ -0,0 +1,83 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_UBRPC2PB_PROTOCOL_H +#define BRPC_POLICY_UBRPC2PB_PROTOCOL_H + +#include "mcpack2pb/mcpack2pb.h" +#include "brpc/nshead_pb_service_adaptor.h" +#include "brpc/policy/nshead_protocol.h" + + +namespace brpc { +namespace policy { + +void ProcessUbrpcResponse(InputMessageBase* msg); + +void SerializeUbrpcCompackRequest(butil::IOBuf* buf, Controller* cntl, + const google::protobuf::Message* request); +void SerializeUbrpcMcpack2Request(butil::IOBuf* buf, Controller* cntl, + const google::protobuf::Message* request); + +void PackUbrpcRequest(butil::IOBuf* buf, + SocketMessage**, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* controller, + const butil::IOBuf& request, + const Authenticator* auth); + +class UbrpcAdaptor : public NsheadPbServiceAdaptor { +public: + explicit UbrpcAdaptor(mcpack2pb::SerializationFormat format) + : _format(format) {} + + void ParseNsheadMeta(const Server& svr, + const NsheadMessage& request, + Controller*, + NsheadMeta* out_meta) const; + + void ParseRequestFromIOBuf( + const NsheadMeta& meta, const NsheadMessage& ns_req, + Controller* controller, google::protobuf::Message* pb_req) const; + + void SerializeResponseToIOBuf( + const NsheadMeta& meta, + Controller* controller, + const google::protobuf::Message* pb_res, + NsheadMessage* ns_res) const; + +private: + mcpack2pb::SerializationFormat _format; +}; + +class UbrpcCompackAdaptor : public UbrpcAdaptor { +public: + UbrpcCompackAdaptor() : UbrpcAdaptor(mcpack2pb::FORMAT_COMPACK) {} +}; + +class UbrpcMcpack2Adaptor : public UbrpcAdaptor { +public: + UbrpcMcpack2Adaptor() : UbrpcAdaptor(mcpack2pb::FORMAT_MCPACK_V2) {} +}; + +} // namespace policy +} // namespace brpc + + +#endif // BRPC_POLICY_UBRPC2PB_PROTOCOL_H diff --git a/src/brpc/policy/weighted_randomized_load_balancer.cpp b/src/brpc/policy/weighted_randomized_load_balancer.cpp new file mode 100644 index 0000000..46923ac --- /dev/null +++ b/src/brpc/policy/weighted_randomized_load_balancer.cpp @@ -0,0 +1,203 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 + +#include "butil/fast_rand.h" +#include "bthread/prime_offset.h" +#include "brpc/socket.h" +#include "brpc/policy/weighted_randomized_load_balancer.h" +#include "butil/strings/string_number_conversions.h" + +namespace brpc { +namespace policy { + +static bool server_compare(const WeightedRandomizedLoadBalancer::Server& lhs, + const WeightedRandomizedLoadBalancer::Server& rhs) { + return lhs.current_weight_sum < rhs.current_weight_sum; +} + +bool WeightedRandomizedLoadBalancer::Add(Servers& bg, const ServerId& id) { + if (bg.server_list.capacity() < 128) { + bg.server_list.reserve(128); + } + uint32_t weight = 0; + if (!butil::StringToUint(id.tag, &weight) || weight <= 0) { + if (FLAGS_default_weight_of_wlb > 0) { + LOG(WARNING) << "Invalid weight is set: " << id.tag + << ". Now, 'weight' has been set to " + "FLAGS_default_weight_of_wlb by default."; + weight = FLAGS_default_weight_of_wlb; + } else { + LOG(ERROR) << "Invalid weight is set: " << id.tag; + return false; + } + } + bool insert_server = + bg.server_map.emplace(id.id, bg.server_list.size()).second; + if (insert_server) { + uint64_t current_weight_sum = bg.weight_sum + weight; + bg.server_list.emplace_back(id.id, weight, current_weight_sum); + bg.weight_sum = current_weight_sum; + return true; + } + return false; +} + +bool WeightedRandomizedLoadBalancer::Remove(Servers& bg, const ServerId& id) { + typedef std::map::iterator MapIter_t; + MapIter_t iter = bg.server_map.find(id.id); + if (iter != bg.server_map.end()) { + size_t index = iter->second; + Server remove_server = bg.server_list[index]; + int32_t weight_diff = bg.server_list.back().weight - remove_server.weight; + bg.weight_sum -= remove_server.weight; + bg.server_list[index] = bg.server_list.back(); + bg.server_list[index].current_weight_sum = remove_server.current_weight_sum + weight_diff; + bg.server_map[bg.server_list[index].id] = index; + bg.server_list.pop_back(); + bg.server_map.erase(iter); + size_t n = bg.server_list.size(); + for (index++; index < n; index++) { + bg.server_list[index].current_weight_sum += weight_diff; + } + return true; + } + return false; +} + +size_t WeightedRandomizedLoadBalancer::BatchAdd( + Servers& bg, const std::vector& servers) { + size_t count = 0; + for (size_t i = 0; i < servers.size(); ++i) { + count += !!Add(bg, servers[i]); + } + return count; +} + +size_t WeightedRandomizedLoadBalancer::BatchRemove( + Servers& bg, const std::vector& servers) { + size_t count = 0; + for (size_t i = 0; i < servers.size(); ++i) { + count += !!Remove(bg, servers[i]); + } + return count; +} + +bool WeightedRandomizedLoadBalancer::AddServer(const ServerId& id) { + return _db_servers.Modify(Add, id); +} + +bool WeightedRandomizedLoadBalancer::RemoveServer(const ServerId& id) { + return _db_servers.Modify(Remove, id); +} + +size_t WeightedRandomizedLoadBalancer::AddServersInBatch( + const std::vector& servers) { + return _db_servers.Modify(BatchAdd, servers); +} + +size_t WeightedRandomizedLoadBalancer::RemoveServersInBatch( + const std::vector& servers) { + return _db_servers.Modify(BatchRemove, servers); +} + +int WeightedRandomizedLoadBalancer::SelectServer(const SelectIn& in, SelectOut* out) { + butil::DoublyBufferedData::ScopedPtr s; + if (_db_servers.Read(&s) != 0) { + return ENOMEM; + } + size_t n = s->server_list.size(); + if (n == 0) { + return ENODATA; + } + + butil::FlatSet random_traversed; + uint64_t weight_sum = s->weight_sum; + for (size_t i = 0; i < n; ++i) { + uint64_t random_weight = butil::fast_rand_less_than(weight_sum); + const Server random_server(0, 0, random_weight); + const auto& server = + std::lower_bound(s->server_list.begin(), s->server_list.end(), + random_server, server_compare); + const SocketId id = server->id; + if (ExcludedServers::IsExcluded(in.excluded, id)) { + continue; + } + random_traversed.insert(id); + if (IsServerAvailable(id, out->ptr)) { + // An available server is found. + return 0; + } + } + + if (random_traversed.size() < n) { + // Try to traverse the remaining servers to find an available server. + uint32_t offset = butil::fast_rand_less_than(n); + uint32_t stride = bthread::prime_offset(); + for (size_t i = 0; i < n; ++i) { + offset = (offset + stride) % n; + SocketId id = s->server_list[offset].id; + if (NULL != random_traversed.seek(id)) { + continue; + } + if (IsServerAvailable(id, out->ptr)) { + if (!ExcludedServers::IsExcluded(in.excluded, id)) { + // Prioritize servers that are not excluded. + return 0; + } + } + } + } + + // Returns EHOSTDOWN, if no available server is found + // after traversing the whole server list. + // Otherwise, returns 0 with a available excluded server. + return NULL == out->ptr ? EHOSTDOWN : 0; +} + +LoadBalancer* WeightedRandomizedLoadBalancer::New( + const butil::StringPiece&) const { + return new (std::nothrow) WeightedRandomizedLoadBalancer; +} + +void WeightedRandomizedLoadBalancer::Destroy() { + delete this; +} + +void WeightedRandomizedLoadBalancer::Describe( + std::ostream &os, const DescribeOptions& options) { + if (!options.verbose) { + os << "wr"; + return; + } + os << "WeightedRandomized{"; + butil::DoublyBufferedData::ScopedPtr s; + if (_db_servers.Read(&s) != 0) { + os << "fail to read _db_servers"; + } else { + os << "n=" << s->server_list.size() << ':'; + for (const auto& server : s->server_list) { + os << ' ' << server.id << '(' << server.weight << ')'; + } + } + os << '}'; +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/weighted_randomized_load_balancer.h b/src/brpc/policy/weighted_randomized_load_balancer.h new file mode 100644 index 0000000..9d7a705 --- /dev/null +++ b/src/brpc/policy/weighted_randomized_load_balancer.h @@ -0,0 +1,71 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_WEIGHTED_RANDOMIZED_LOAD_BALANCER_H +#define BRPC_POLICY_WEIGHTED_RANDOMIZED_LOAD_BALANCER_H + +#include +#include +#include "butil/containers/doubly_buffered_data.h" +#include "brpc/load_balancer.h" + +namespace brpc { +namespace policy { + +// This LoadBalancer selects server as the assigned weight. +// Weight is got from tag of ServerId. +class WeightedRandomizedLoadBalancer : public LoadBalancer { +public: + bool AddServer(const ServerId& id) override; + bool RemoveServer(const ServerId& id) override; + size_t AddServersInBatch(const std::vector& servers) override; + size_t RemoveServersInBatch(const std::vector& servers) override; + int SelectServer(const SelectIn& in, SelectOut* out) override; + LoadBalancer* New(const butil::StringPiece&) const override; + void Destroy() override; + void Describe(std::ostream& os, const DescribeOptions&) override; + + struct Server { + explicit Server(SocketId s_id = 0, uint32_t s_w = 0, uint64_t s_c_w_s = 0) + : id(s_id), weight(s_w), current_weight_sum(s_c_w_s) {} + SocketId id; + uint32_t weight; + uint64_t current_weight_sum; + }; + struct Servers { + // The value is configured weight and weight_sum for each server. + std::vector server_list; + // The value is the index of the server in "server_list". + std::map server_map; + uint64_t weight_sum; + Servers() : weight_sum(0) {} + }; + +private: + static bool Add(Servers& bg, const ServerId& id); + static bool Remove(Servers& bg, const ServerId& id); + static size_t BatchAdd(Servers& bg, const std::vector& servers); + static size_t BatchRemove(Servers& bg, const std::vector& servers); + + butil::DoublyBufferedData _db_servers; +}; + +} // namespace policy +} // namespace brpc + +#endif // BRPC_POLICY_WEIGHTED_RANDOMIZED_LOAD_BALANCER_H diff --git a/src/brpc/policy/weighted_round_robin_load_balancer.cpp b/src/brpc/policy/weighted_round_robin_load_balancer.cpp new file mode 100644 index 0000000..44d8a95 --- /dev/null +++ b/src/brpc/policy/weighted_round_robin_load_balancer.cpp @@ -0,0 +1,276 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 + +#include "butil/fast_rand.h" +#include "brpc/socket.h" +#include "brpc/policy/weighted_round_robin_load_balancer.h" +#include "butil/strings/string_number_conversions.h" + +namespace { + +const std::vector prime_stride = { +2,3,5,11,17,29,47,71,107,137,163,251,307,379,569,683,857,1289,1543,1949,2617, +2927,3407,4391,6599,9901,14867,22303,33457,50207,75323,112997,169501,254257, +381389,572087,849083,1273637,1910471,2865727,4298629,6447943,9671923,14507903, +21761863,32642861,48964297,73446469,110169743,165254623,247881989,371822987, +557734537,836601847,1254902827,1882354259,2823531397,4235297173,6352945771, +9529418671}; + +bool IsCoprime(uint64_t num1, uint64_t num2) { + uint64_t temp; + if (num1 < num2) { + temp = num1; + num1 = num2; + num2 = temp; + } + while (true) { + temp = num1 % num2; + if (temp == 0) { + break; + } else { + num1 = num2; + num2 = temp; + } + } + return num2 == 1; +} + +// Get a reasonable stride according to weights configured of servers. +uint64_t GetStride(const uint64_t weight_sum, const size_t num) { + if (weight_sum == 1) { + return 1; + } + uint32_t average_weight = weight_sum / num; + auto iter = std::lower_bound( + prime_stride.begin(), prime_stride.end(), average_weight); + while (iter != prime_stride.end() + && !IsCoprime(weight_sum, *iter)) { + ++iter; + } + CHECK(iter != prime_stride.end()) << "Failed to get stride"; + return *iter > weight_sum ? *iter % weight_sum : *iter; +} + +} // namespace + +namespace brpc { +namespace policy { + +bool WeightedRoundRobinLoadBalancer::Add(Servers& bg, const ServerId& id) { + if (bg.server_list.capacity() < 128) { + bg.server_list.reserve(128); + } + uint32_t weight = 0; + if (!butil::StringToUint(id.tag, &weight) || weight <= 0) { + if (FLAGS_default_weight_of_wlb > 0) { + LOG(WARNING) << "Invalid weight is set: " << id.tag + << ". Now, 'weight' has been set to 'FLAGS_default_weight_of_wlb' by default."; + weight = FLAGS_default_weight_of_wlb; + } else { + LOG(ERROR) << "Invalid weight is set: " << id.tag; + return false; + } + } + bool insert_server = + bg.server_map.emplace(id.id, bg.server_list.size()).second; + if (insert_server) { + bg.server_list.emplace_back(id.id, weight); + bg.weight_sum += weight; + return true; + } + return false; +} + +bool WeightedRoundRobinLoadBalancer::Remove(Servers& bg, const ServerId& id) { + auto iter = bg.server_map.find(id.id); + if (iter != bg.server_map.end()) { + const size_t index = iter->second; + bg.weight_sum -= bg.server_list[index].weight; + bg.server_list[index] = bg.server_list.back(); + bg.server_map[bg.server_list[index].id] = index; + bg.server_list.pop_back(); + bg.server_map.erase(iter); + return true; + } + return false; +} + +size_t WeightedRoundRobinLoadBalancer::BatchAdd( + Servers& bg, const std::vector& servers) { + size_t count = 0; + for (size_t i = 0; i < servers.size(); ++i) { + count += !!Add(bg, servers[i]); + } + return count; +} + +size_t WeightedRoundRobinLoadBalancer::BatchRemove( + Servers& bg, const std::vector& servers) { + size_t count = 0; + for (size_t i = 0; i < servers.size(); ++i) { + count += !!Remove(bg, servers[i]); + } + return count; +} + +bool WeightedRoundRobinLoadBalancer::AddServer(const ServerId& id) { + return _db_servers.Modify(Add, id); +} + +bool WeightedRoundRobinLoadBalancer::RemoveServer(const ServerId& id) { + return _db_servers.Modify(Remove, id); +} + +size_t WeightedRoundRobinLoadBalancer::AddServersInBatch( + const std::vector& servers) { + const size_t n = _db_servers.Modify(BatchAdd, servers); + LOG_IF(ERROR, n != servers.size()) + << "Fail to AddServersInBatch, expected " << servers.size() + << " actually " << n; + return n; +} + +size_t WeightedRoundRobinLoadBalancer::RemoveServersInBatch( + const std::vector& servers) { + const size_t n = _db_servers.Modify(BatchRemove, servers); + return n; +} + +int WeightedRoundRobinLoadBalancer::SelectServer(const SelectIn& in, SelectOut* out) { + butil::DoublyBufferedData::ScopedPtr s; + if (_db_servers.Read(&s) != 0) { + return ENOMEM; + } + if (s->server_list.empty()) { + return ENODATA; + } + TLS& tls = s.tls(); + if (tls.IsNeededCalculateNewStride(s->weight_sum, s->server_list.size())) { + if (tls.stride == 0) { + tls.position = butil::fast_rand_less_than(s->server_list.size()); + } + tls.stride = GetStride(s->weight_sum, s->server_list.size()); + } + // If server list changed, the position may be out of range. + tls.position %= s->server_list.size(); + // Check whether remain server was removed from server list. + if (tls.remain_server.weight > 0 && + tls.remain_server.id != s->server_list[tls.position].id) { + tls.remain_server.weight = 0; + } + // The servers that can not be chosen. + std::unordered_set filter; + TLS tls_temp = tls; + uint64_t remain_weight = s->weight_sum; + size_t remain_servers = s->server_list.size(); + while (remain_servers > 0) { + SocketId server_id = GetServerInNextStride(s->server_list, filter, tls_temp); + if ((remain_servers == 1 // always take last chance + || !ExcludedServers::IsExcluded(in.excluded, server_id)) + && Socket::Address(server_id, out->ptr) == 0 + && (*out->ptr)->IsAvailable()) { + // update tls. + tls.remain_server = tls_temp.remain_server; + tls.position = tls_temp.position; + return 0; + } else { + // Skip this invalid server. We need calculate a new stride for server selection. + if (--remain_servers == 0) { + break; + } + filter.emplace(server_id); + remain_weight -= (s->server_list[s->server_map.at(server_id)]).weight; + // Select from beginning status. + tls_temp.stride = GetStride(remain_weight, remain_servers); + tls_temp.position = tls.position; + tls_temp.remain_server = tls.remain_server; + } + } + return EHOSTDOWN; +} + +SocketId WeightedRoundRobinLoadBalancer::GetServerInNextStride( + const std::vector& server_list, + const std::unordered_set& filter, + TLS& tls) { + SocketId final_server = INVALID_SOCKET_ID; + uint64_t stride = tls.stride; + Server& remain = tls.remain_server; + if (remain.weight > 0) { + if (filter.count(remain.id) == 0) { + final_server = remain.id; + if (remain.weight > stride) { + remain.weight -= stride; + return final_server; + } else { + stride -= remain.weight; + } + } + remain.weight = 0; + ++tls.position; + tls.position %= server_list.size(); + } + while (stride > 0) { + final_server = server_list[tls.position].id; + if (filter.count(final_server) == 0) { + uint32_t configured_weight = server_list[tls.position].weight; + if (configured_weight > stride) { + remain.id = final_server; + remain.weight = configured_weight - stride; + return final_server; + } + stride -= configured_weight; + } + ++tls.position; + tls.position %= server_list.size(); + } + return final_server; +} + +LoadBalancer* WeightedRoundRobinLoadBalancer::New( + const butil::StringPiece&) const { + return new (std::nothrow) WeightedRoundRobinLoadBalancer; +} + +void WeightedRoundRobinLoadBalancer::Destroy() { + delete this; +} + +void WeightedRoundRobinLoadBalancer::Describe( + std::ostream &os, const DescribeOptions& options) { + if (!options.verbose) { + os << "wrr"; + return; + } + os << "WeightedRoundRobin{"; + butil::DoublyBufferedData::ScopedPtr s; + if (_db_servers.Read(&s) != 0) { + os << "fail to read _db_servers"; + } else { + os << "n=" << s->server_list.size() << ':'; + for (const auto& server : s->server_list) { + os << ' ' << server.id << '(' << server.weight << ')'; + } + } + os << '}'; +} + +} // namespace policy +} // namespace brpc diff --git a/src/brpc/policy/weighted_round_robin_load_balancer.h b/src/brpc/policy/weighted_round_robin_load_balancer.h new file mode 100644 index 0000000..828de65 --- /dev/null +++ b/src/brpc/policy/weighted_round_robin_load_balancer.h @@ -0,0 +1,90 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_POLICY_WEIGHTED_ROUND_ROBIN_LOAD_BALANCER_H +#define BRPC_POLICY_WEIGHTED_ROUND_ROBIN_LOAD_BALANCER_H + +#include +#include +#include +#include "butil/containers/doubly_buffered_data.h" +#include "brpc/load_balancer.h" + +namespace brpc { +namespace policy { + +// This LoadBalancer selects server as the assigned weight. +// Weight is got from tag of ServerId. +class WeightedRoundRobinLoadBalancer : public LoadBalancer { +public: + bool AddServer(const ServerId& id) override; + bool RemoveServer(const ServerId& id) override; + size_t AddServersInBatch(const std::vector& servers) override; + size_t RemoveServersInBatch(const std::vector& servers) override; + int SelectServer(const SelectIn& in, SelectOut* out) override; + LoadBalancer* New(const butil::StringPiece&) const override; + void Destroy() override; + void Describe(std::ostream&, const DescribeOptions& options) override; + +private: + struct Server { + Server(SocketId s_id = 0, uint32_t s_w = 0): id(s_id), weight(s_w) {} + SocketId id; + uint32_t weight; + }; + struct Servers { + // The value is configured weight for each server. + std::vector server_list; + // The value is the index of the server in "server_list". + std::map server_map; + uint64_t weight_sum = 0; + }; + struct TLS { + size_t position = 0; + uint64_t stride = 0; + Server remain_server; + // If server list changed, we need calculate a new stride. + bool IsNeededCalculateNewStride(const uint64_t curr_weight_sum, + const size_t curr_servers_num) { + if (curr_weight_sum != weight_sum + || curr_servers_num != servers_num) { + weight_sum = curr_weight_sum; + servers_num = curr_servers_num; + return true; + } + return false; + } + private: + uint64_t weight_sum = 0; + size_t servers_num = 0; + }; + static bool Add(Servers& bg, const ServerId& id); + static bool Remove(Servers& bg, const ServerId& id); + static size_t BatchAdd(Servers& bg, const std::vector& servers); + static size_t BatchRemove(Servers& bg, const std::vector& servers); + static SocketId GetServerInNextStride(const std::vector& server_list, + const std::unordered_set& filter, + TLS& tls); + + butil::DoublyBufferedData _db_servers; +}; + +} // namespace policy +} // namespace brpc + +#endif // BRPC_POLICY_WEIGHTED_ROUND_ROBIN_LOAD_BALANCER_H diff --git a/src/brpc/progressive_attachment.cpp b/src/brpc/progressive_attachment.cpp new file mode 100644 index 0000000..0f8a3a3 --- /dev/null +++ b/src/brpc/progressive_attachment.cpp @@ -0,0 +1,261 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "butil/logging.h" +#include "bthread/bthread.h" // INVALID_BTHREAD_ID before bthread r32748 +#include "brpc/progressive_attachment.h" +#include "brpc/socket.h" +#include "brpc/errno.pb.h" + + +namespace brpc { + +// defined in socket.cpp +DECLARE_int64(socket_max_unwritten_bytes); + +const int ProgressiveAttachment::RPC_RUNNING = 0; +const int ProgressiveAttachment::RPC_SUCCEED = 1; +const int ProgressiveAttachment::RPC_FAILED = 2; + +ProgressiveAttachment::ProgressiveAttachment(SocketUniquePtr& movable_httpsock, + bool before_http_1_1) + : _before_http_1_1(before_http_1_1) + , _pause_from_mark_rpc_as_done(false) + , _rpc_state(RPC_RUNNING) + , _notify_id(INVALID_BTHREAD_ID) { + _httpsock.swap(movable_httpsock); +} + +ProgressiveAttachment::~ProgressiveAttachment() { + if (_httpsock) { + CHECK(_rpc_state.load(butil::memory_order_relaxed) != RPC_RUNNING); + CHECK(_saved_buf.empty()); + if (!_before_http_1_1) { + // note: _httpsock may already be failed. + if (_rpc_state.load(butil::memory_order_relaxed) == RPC_SUCCEED) { + butil::IOBuf tmpbuf; + tmpbuf.append("0\r\n\r\n", 5); + Socket::WriteOptions wopt; + wopt.ignore_eovercrowded = true; + _httpsock->Write(&tmpbuf, &wopt); + } + } else { + // Close _httpsock to notify the client that all the content has + // been transferred. + // Note: invoke ReleaseAdditionalReference instead of SetFailed to + // make sure that all the data has been written before the fd is + // closed. + _httpsock->ReleaseAdditionalReference(); + } + } + if (_notify_id != INVALID_BTHREAD_ID) { + bthread_id_error(_notify_id, 0); + } +} + +static char s_hex_map[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', + '9', 'A', 'B', 'C', 'D', 'E', 'F' }; +inline char ToHex(uint32_t size/*0-15*/) { return s_hex_map[size]; } + +inline void AppendChunkHead(butil::IOBuf* buf, uint32_t size) { + char tmp[32]; + int i = (int)sizeof(tmp); + tmp[--i] = '\n'; + tmp[--i] = '\r'; + if (size == 0) { + tmp[--i] = '0'; + } else { + for (--i; i >= 0; --i) { + const uint32_t new_size = (size >> 4); + tmp[i] = ToHex(size - (new_size << 4)); + size = new_size; + if (size == 0) { + --i; + break; + } + } + } + buf->append(tmp + i + 1, sizeof(tmp) - i - 1); +} + +inline void AppendAsChunk(butil::IOBuf* chunk_buf, const butil::IOBuf& data, + bool before_http_1_1) { + if (!before_http_1_1) { + AppendChunkHead(chunk_buf, data.size()); + chunk_buf->append(data); + chunk_buf->append("\r\n", 2); + } else { + chunk_buf->append(data); + } +} + +inline void AppendAsChunk(butil::IOBuf* chunk_buf, const void* data, + size_t length, bool before_http_1_1) { + if (!before_http_1_1) { + AppendChunkHead(chunk_buf, length); + chunk_buf->append(data, length); + chunk_buf->append("\r\n", 2); + } else { + chunk_buf->append(data, length); + } +} + +int ProgressiveAttachment::Write(const butil::IOBuf& data) { + if (data.empty()) { + LOG_EVERY_SECOND(WARNING) + << "Write an empty chunk. To suppress this warning, check emptiness" + " of the chunk before calling ProgressiveAttachment.Write()"; + return 0; + } + + int rpc_state = _rpc_state.load(butil::memory_order_acquire); + if (rpc_state == RPC_RUNNING) { + std::unique_lock mu(_mutex); + rpc_state = _rpc_state.load(butil::memory_order_acquire); + if (rpc_state == RPC_RUNNING) { + if (_saved_buf.size() >= (size_t)FLAGS_socket_max_unwritten_bytes || + _pause_from_mark_rpc_as_done) { + errno = EOVERCROWDED; + return -1; + } + AppendAsChunk(&_saved_buf, data, _before_http_1_1); + return 0; + } + } + // The RPC is already done (http headers were written into the socket) + // write into the socket directly. + if (rpc_state == RPC_SUCCEED) { + butil::IOBuf tmpbuf; + AppendAsChunk(&tmpbuf, data, _before_http_1_1); + return _httpsock->Write(&tmpbuf); + } else { + errno = ECANCELED; + return -1; + } +} + +int ProgressiveAttachment::Write(const void* data, size_t n) { + if (data == NULL || n == 0) { + LOG_EVERY_SECOND(WARNING) + << "Write an empty chunk. To suppress this warning, check emptiness" + " of the chunk before calling ProgressiveAttachment.Write()"; + return 0; + } + int rpc_state = _rpc_state.load(butil::memory_order_acquire); + if (rpc_state == RPC_RUNNING) { + std::unique_lock mu(_mutex); + rpc_state = _rpc_state.load(butil::memory_order_relaxed); + if (rpc_state == RPC_RUNNING) { + if (_saved_buf.size() >= (size_t)FLAGS_socket_max_unwritten_bytes || + _pause_from_mark_rpc_as_done) { + errno = EOVERCROWDED; + return -1; + } + AppendAsChunk(&_saved_buf, data, n, _before_http_1_1); + return 0; + } + } + // The RPC is already done (http headers were written into the socket) + // write into the socket directly. + if (rpc_state == RPC_SUCCEED) { + butil::IOBuf tmpbuf; + AppendAsChunk(&tmpbuf, data, n, _before_http_1_1); + return _httpsock->Write(&tmpbuf); + } else { + errno = ECANCELED; + return -1; + } +} + +void ProgressiveAttachment::MarkRPCAsDone(bool rpc_failed) { + // Notes: + // * Writing here is more timely than being flushed in next Write(), in + // some extreme situations, the delay before next Write() may be + // significant. + // * Write() should be outside lock because a failed write triggers + // SetFailed() which runs the closure to NotifyOnStopped() which may + // call methods requesting for the lock again. Another solution is to + // use recursive lock. + // * _saved_buf can't be much longer than FLAGS_socket_max_unwritten_bytes, + // ignoring EOVERCROWDED simplifies the error handling. + // * If the iteration repeats too many times, _pause_from_mark_rpc_as_done + // is set to true to make Write() fails with EOVERCROWDED temporarily to + // stop _saved_buf from growing. + const int MAX_TRY = 3; + int ntry = 0; + bool permanent_error = false; + do { + std::unique_lock mu(_mutex); + if (_saved_buf.empty() || permanent_error || rpc_failed) { + butil::IOBuf tmp; + tmp.swap(_saved_buf); // Clear _saved_buf outside lock. + _pause_from_mark_rpc_as_done = false; + _rpc_state.store((rpc_failed? RPC_FAILED: RPC_SUCCEED), + butil::memory_order_release); + mu.unlock(); + return; + } + if (++ntry > MAX_TRY) { + _pause_from_mark_rpc_as_done = true; + } + butil::IOBuf copied; + copied.swap(_saved_buf); + mu.unlock(); + Socket::WriteOptions wopt; + wopt.ignore_eovercrowded = true; + if (_httpsock->Write(&copied, &wopt) != 0) { + permanent_error = true; + } + } while (true); +} + +butil::EndPoint ProgressiveAttachment::remote_side() const { + return _httpsock ? _httpsock->remote_side() : butil::EndPoint(); +} + +butil::EndPoint ProgressiveAttachment::local_side() const { + return _httpsock ? _httpsock->local_side() : butil::EndPoint(); +} + +static int RunOnFailed(bthread_id_t id, void* data, int) { + bthread_id_unlock_and_destroy(id); + static_cast(data)->Run(); + return 0; +} + +void ProgressiveAttachment::NotifyOnStopped(google::protobuf::Closure* done) { + if (done == NULL) { + LOG(ERROR) << "Param[done] is NULL"; + return; + } + if (_notify_id != INVALID_BTHREAD_ID) { + LOG(ERROR) << "NotifyOnStopped() can only be called once"; + return done->Run(); + } + if (_httpsock == NULL) { + return done->Run(); + } + const int rc = bthread_id_create(&_notify_id, done, RunOnFailed); + if (rc) { + LOG(ERROR) << "Fail to create _notify_id: " << berror(rc); + return done->Run(); + } + _httpsock->NotifyOnFailed(_notify_id); +} + +} // namespace brpc diff --git a/src/brpc/progressive_attachment.h b/src/brpc/progressive_attachment.h new file mode 100644 index 0000000..31477f8 --- /dev/null +++ b/src/brpc/progressive_attachment.h @@ -0,0 +1,83 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_PROGRESSIVE_ATTACHMENT_H +#define BRPC_PROGRESSIVE_ATTACHMENT_H + +#include "brpc/callback.h" +#include "butil/atomicops.h" +#include "butil/iobuf.h" +#include "butil/endpoint.h" // butil::EndPoint +#include "bthread/types.h" // bthread_id_t +#include "brpc/socket_id.h" // SocketUniquePtr +#include "brpc/shared_object.h" // SharedObject + +namespace brpc { + +class ProgressiveAttachment : public SharedObject { +friend class Controller; +public: + // [Thread-safe] + // Write `data' as one HTTP chunk to peer ASAP. + // Returns 0 on success, -1 otherwise and errno is set. + // Errnos are same as what Socket.Write may set. + int Write(const butil::IOBuf& data); + int Write(const void* data, size_t n); + + // Get ip/port of peer/self. + butil::EndPoint remote_side() const; + butil::EndPoint local_side() const; + + // [Not thread-safe and can only be called once] + // Run the callback when the underlying connection is broken (thus + // transmission of the attachment is permanently stopped), or when + // this attachment is destructed. In another word, the callback will + // always be run. + void NotifyOnStopped(google::protobuf::Closure* callback); + +protected: + // Transfer-Encoding is added since HTTP/1.1. If the protocol of the + // response is before_http_1_1, we will write the data directly to the + // socket without any futher modification and close the socket after all the + // data has been written (so the client would receive EOF). Otherwise we + // will encode each piece of data in the format of chunked-encoding. + ProgressiveAttachment(SocketUniquePtr& movable_httpsock, + bool before_http_1_1); + ~ProgressiveAttachment(); + + // Called by controller only. + void MarkRPCAsDone(bool rpc_failed); + + bool _before_http_1_1; + bool _pause_from_mark_rpc_as_done; + butil::atomic _rpc_state; + butil::Mutex _mutex; + SocketUniquePtr _httpsock; + butil::IOBuf _saved_buf; + bthread_id_t _notify_id; + +private: + static const int RPC_RUNNING; + static const int RPC_SUCCEED; + static const int RPC_FAILED; +}; + +} // namespace brpc + + +#endif // BRPC_PROGRESSIVE_ATTACHMENT_H diff --git a/src/brpc/progressive_reader.h b/src/brpc/progressive_reader.h new file mode 100644 index 0000000..6f54ae6 --- /dev/null +++ b/src/brpc/progressive_reader.h @@ -0,0 +1,92 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_PROGRESSIVE_READER_H +#define BRPC_PROGRESSIVE_READER_H + +#include "brpc/shared_object.h" + + +namespace brpc { + +// [Implement by user] +// To read a very long or infinitely long response progressively. +// Client-side usage: +// cntl.response_will_be_read_progressively(); // before RPC +// ... +// channel.CallMethod(NULL, &cntl, NULL, NULL, NULL/*done*/); +// ... +// cntl.ReadProgressiveAttachmentBy(new MyProgressiveReader); // after RPC +// ... +class ProgressiveReader { +public: + // Called when one part was read. + // Error returned is treated as *permenant* and the socket where the + // data was read will be closed. + // A temporary error may be handled by blocking this function, which + // may block the HTTP parsing on the socket. + virtual butil::Status OnReadOnePart(const void* data, size_t length) = 0; + + // Called when there's nothing to read anymore. The `status' is a hint for + // why this method is called. + // - status.ok(): the message is complete and successfully consumed. + // - otherwise: socket was broken or OnReadOnePart() failed. + // This method will be called once and only once. No other methods will + // be called after. User can release the memory of this object inside. + virtual void OnEndOfMessage(const butil::Status& status) = 0; + +protected: + virtual ~ProgressiveReader() {} +}; + +// [Implement by protocol handlers] +// Share ProgressiveReader between protocol handlers and controllers. +// Take chunked HTTP response as an example: +// 1. The protocol handler parses headers and goes to ProcessHttpResponse +// before reading all body. +// 2. ProcessHttpResponse sets controller's RPA which is just the HttpContext +// in this case. The RPC ends at the end of ProcessHttpResponse. +// 3. When the RPC ends, user may call Controller.ReadProgressiveAttachmentBy() +// to read the body. If user does not set a reader, controller sets one +// ignoring all bytes read before self's destruction. +// The call chain: +// Controller.ReadProgressiveAttachmentBy() +// -> ReadableProgressiveAttachment.ReadProgressiveAttachmentBy() +// -> HttpMesage.SetBodyReader() +// -> ProgressiveReader.OnReadOnePart() +// Already-read body will be fed immediately and the reader is remembered. +// 4. The protocol handler also sets a reference to the RPA in the socket. +// When new part arrives, HttpMessage.on_body is called, which calls +// ProgressiveReader.OnReadOnePart() when reader is set. +// 5. When all body is read, the socket releases the reference to the RPA. +// If controller is deleted after all body is read, the RPA should be +// destroyed at controller's deletion. If controller is deleted before +// all body is read, the RPA should be destroyed when all body is read +// or the socket is destroyed. +class ReadableProgressiveAttachment : public SharedObject { +public: + // Read the constantly-appending attachment by a ProgressiveReader. + // Any error occurred should destroy the reader by calling r->Destroy(). + // r->Destroy() should be guaranteed to be called once and only once. + virtual void ReadProgressiveAttachmentBy(ProgressiveReader* r) = 0; +}; + +} // namespace brpc + + +#endif // BRPC_PROGRESSIVE_READER_H diff --git a/src/brpc/proto_base.proto b/src/brpc/proto_base.proto new file mode 100644 index 0000000..3fcdda0 --- /dev/null +++ b/src/brpc/proto_base.proto @@ -0,0 +1,44 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +option cc_generic_services = true; + +package brpc; + +message RedisRequestBase {} +message RedisResponseBase {} + +message EspMessageBase {} + +message CouchbaseRequestBase {} +message CouchbaseResponseBase {} + +message MemcacheRequestBase {} +message MemcacheResponseBase {} + +message NsheadMessageBase {} + +message SerializedRequestBase {} +message SerializedResponseBase {} + +message SerializerBase {} +message DeserializerBase {} + +message ThriftFramedMessageBase {} + +service BaiduMasterServiceBase {} diff --git a/src/brpc/protocol.cpp b/src/brpc/protocol.cpp new file mode 100644 index 0000000..9bb1fde --- /dev/null +++ b/src/brpc/protocol.cpp @@ -0,0 +1,270 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + + +// Since kDefaultTotalBytesLimit is private, we need some hacks to get the limit. +// Works for pb 2.4, 2.6, 3.0 +#include "google/protobuf/stubs/common.h" +#if GOOGLE_PROTOBUF_VERSION < 4022000 +#define private public +#include +const int PB_TOTAL_BYETS_LIMITS_RAW = + google::protobuf::io::CodedInputStream::kDefaultTotalBytesLimit; +const uint64_t PB_TOTAL_BYETS_LIMITS = + PB_TOTAL_BYETS_LIMITS_RAW < 0 ? (uint64_t)-1LL : PB_TOTAL_BYETS_LIMITS_RAW; +#undef private +#else +#include +const int PB_TOTAL_BYETS_LIMITS_RAW = INT_MAX; +const uint64_t PB_TOTAL_BYETS_LIMITS = + PB_TOTAL_BYETS_LIMITS_RAW < 0 ? (uint64_t)-1LL : PB_TOTAL_BYETS_LIMITS_RAW; +#endif + +#include +#include +#include +#include "butil/logging.h" +#include "butil/memory/singleton_on_pthread_once.h" +#include "brpc/protocol.h" +#include "brpc/controller.h" +#include "brpc/compress.h" +#include "brpc/global.h" +#include "brpc/serialized_request.h" +#include "brpc/input_messenger.h" + + +namespace brpc { + +DEFINE_uint64(max_body_size, 64 * 1024 * 1024, + "Maximum size of a single message body in all protocols"); + +DEFINE_bool(log_error_text, false, + "Print Controller.ErrorText() when server is about to" + " respond a failed RPC"); +BRPC_VALIDATE_GFLAG(log_error_text, PassValidate); + +// Not using ProtocolType_MAX as the boundary because others may define new +// protocols outside brpc. +const size_t MAX_PROTOCOL_SIZE = 128; +struct ProtocolEntry { + butil::atomic valid; + Protocol protocol; + + ProtocolEntry() : valid(false) {} +}; +struct ProtocolMap { + ProtocolEntry entries[MAX_PROTOCOL_SIZE]; +}; +inline ProtocolEntry* get_protocol_map() { + return butil::get_leaky_singleton()->entries; +} +static pthread_mutex_t s_protocol_map_mutex = PTHREAD_MUTEX_INITIALIZER; + +int RegisterProtocol(ProtocolType type, const Protocol& protocol) { + const size_t index = type; + if (index >= MAX_PROTOCOL_SIZE) { + LOG(ERROR) << "ProtocolType=" << type << " is out of range"; + return -1; + } + if (!protocol.support_client() && !protocol.support_server()) { + LOG(ERROR) << "ProtocolType=" << type + << " neither supports client nor server"; + return -1; + } + ProtocolEntry* const protocol_map = get_protocol_map(); + BAIDU_SCOPED_LOCK(s_protocol_map_mutex); + if (protocol_map[index].valid.load(butil::memory_order_relaxed)) { + LOG(ERROR) << "ProtocolType=" << type << " was registered"; + return -1; + } + protocol_map[index].protocol = protocol; + protocol_map[index].valid.store(true, butil::memory_order_release); + return 0; +} + +// Called frequently, must be fast. +const Protocol* FindProtocol(ProtocolType type) { + const size_t index = type; + if (index >= MAX_PROTOCOL_SIZE) { + LOG(ERROR) << "ProtocolType=" << type << " is out of range"; + return NULL; + } + ProtocolEntry* const protocol_map = get_protocol_map(); + if (protocol_map[index].valid.load(butil::memory_order_acquire)) { + return &protocol_map[index].protocol; + } + return NULL; +} + +void ListProtocols(std::vector* vec) { + vec->clear(); + ProtocolEntry* const protocol_map = get_protocol_map(); + for (size_t i = 0; i < MAX_PROTOCOL_SIZE; ++i) { + if (protocol_map[i].valid.load(butil::memory_order_acquire)) { + vec->push_back(protocol_map[i].protocol); + } + } +} + +void ListProtocols(std::vector >* vec) { + vec->clear(); + ProtocolEntry* const protocol_map = get_protocol_map(); + for (size_t i = 0; i < MAX_PROTOCOL_SIZE; ++i) { + if (protocol_map[i].valid.load(butil::memory_order_acquire)) { + vec->emplace_back((ProtocolType)i, protocol_map[i].protocol); + } + } +} + +void SerializeRequestDefault(butil::IOBuf* buf, Controller* cntl, + const google::protobuf::Message* request) { + // Check sanity of request. + if (!request) { + return cntl->SetFailed(EREQUEST, "`request' is NULL"); + } + if (!request->IsInitialized()) { + return cntl->SetFailed( + EREQUEST, "Missing required fields in request: %s", + request->InitializationErrorString().c_str()); + } + if (!SerializeAsCompressedData(*request, buf, cntl->request_compress_type())) { + return cntl->SetFailed( + EREQUEST, "Fail to compress request, compress_type=%d", + (int)cntl->request_compress_type()); + } +} + +// ====================================================== + +inline bool CompareStringPieceWithoutCase( + const butil::StringPiece& s1, const char* s2) { + if (strlen(s2) != s1.size()) { + return false; + } + return strncasecmp(s1.data(), s2, s1.size()) == 0; +} + +ProtocolType StringToProtocolType(const butil::StringPiece& name, + bool print_log_on_unknown) { + // Force init of s_protocol_name. + GlobalInitializeOrDie(); + + ProtocolEntry* const protocol_map = get_protocol_map(); + for (size_t i = 0; i < MAX_PROTOCOL_SIZE; ++i) { + if (protocol_map[i].valid.load(butil::memory_order_acquire) && + CompareStringPieceWithoutCase(name, protocol_map[i].protocol.name)) { + return static_cast(i); + } + } + // We need to print a log here otherwise the return value cannot reflect + // the original input, which makes later initializations of other classes + // fail with vague logs which is not informational to user, like this: + // "channel doesn't support protocol=unknown" + // Some callsite may not need this log, so we keep a flag. + if (print_log_on_unknown) { + std::ostringstream err; + err << "Unknown protocol `" << name << "', supported protocols:"; + for (size_t i = 0; i < MAX_PROTOCOL_SIZE; ++i) { + if (protocol_map[i].valid.load(butil::memory_order_acquire)) { + err << ' ' << protocol_map[i].protocol.name; + } + } + LOG(ERROR) << err.str(); + } + return PROTOCOL_UNKNOWN; +} + +const char* ProtocolTypeToString(ProtocolType type) { + // Force init of s_protocol_name. + GlobalInitializeOrDie(); + + const Protocol* p = FindProtocol(type); + if (p != NULL) { + return p->name; + } + return "unknown"; +} + +BUTIL_FORCE_INLINE bool ParsePbFromZeroCopyStreamInlined( + google::protobuf::Message* msg, + google::protobuf::io::ZeroCopyInputStream* input) { + google::protobuf::io::CodedInputStream decoder(input); + // Remove the limit inside pb so that it never conflicts with -max_body_size + // According to source code of pb, SetTotalBytesLimit is not a simple set, + // avoid calling the function when the limit is definitely unreached. + if (PB_TOTAL_BYETS_LIMITS < FLAGS_max_body_size) { +#if GOOGLE_PROTOBUF_VERSION >= 3006000 + decoder.SetTotalBytesLimit(INT_MAX); +#else + decoder.SetTotalBytesLimit(INT_MAX, -1); +#endif + } + return msg->ParseFromCodedStream(&decoder) && decoder.ConsumedEntireMessage(); +} + +BUTIL_FORCE_INLINE bool ParsePbTextFromZeroCopyStreamInlined( + google::protobuf::Message* msg, + google::protobuf::io::ZeroCopyInputStream* input) { + return google::protobuf::TextFormat::Parse(input, msg); +} + +bool ParsePbFromZeroCopyStream( + google::protobuf::Message* msg, + google::protobuf::io::ZeroCopyInputStream* input) { + return ParsePbFromZeroCopyStreamInlined(msg, input); +} + +bool ParsePbTextFromIOBuf(google::protobuf::Message* msg, const butil::IOBuf& buf) { + butil::IOBufAsZeroCopyInputStream stream(buf); + return ParsePbTextFromZeroCopyStreamInlined(msg, &stream); +} + +bool ParsePbFromIOBuf(google::protobuf::Message* msg, const butil::IOBuf& buf) { + butil::IOBufAsZeroCopyInputStream stream(buf); + return ParsePbFromZeroCopyStreamInlined(msg, &stream); +} + +bool ParsePbFromArray(google::protobuf::Message* msg, + const void* data, size_t size) { + google::protobuf::io::ArrayInputStream stream(data, size); + return ParsePbFromZeroCopyStreamInlined(msg, &stream); +} + +bool ParsePbFromString(google::protobuf::Message* msg, const std::string& str) { + google::protobuf::io::ArrayInputStream stream(str.data(), str.size()); + return ParsePbFromZeroCopyStreamInlined(msg, &stream); +} + +void LogErrorTextAndDelete::operator()(Controller* c) const { + if (!c) { + return; + } + if (FLAGS_log_error_text && c->ErrorCode()) { + if (c->ErrorCode() == ECLOSE) { + LOG(WARNING) << "Close connection to " << c->remote_side() + << ": " << c->ErrorText(); + } else { + LOG(WARNING) << "Error to " << c->remote_side() + << ": " << c->ErrorText(); + } + } + if (_delete_cntl) { + delete c; + } +} + +} // namespace brpc diff --git a/src/brpc/protocol.h b/src/brpc/protocol.h new file mode 100644 index 0000000..0492d0b --- /dev/null +++ b/src/brpc/protocol.h @@ -0,0 +1,253 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_PROTOCOL_H +#define BRPC_PROTOCOL_H + +// To brpc developers: This is a header included by user, don't depend +// on internal structures, use opaque pointers instead. + +#include // std::vector +#include // uint64_t +#include // DECLARE_xxx +#include "butil/endpoint.h" // butil::EndPoint +#include "butil/iobuf.h" +#include "butil/logging.h" +#include "brpc/options.pb.h" // ProtocolType +#include "brpc/socket_id.h" // SocketId +#include "brpc/parse_result.h" // ParseResult +#include "brpc/adaptive_connection_type.h" +#include "brpc/adaptive_protocol_type.h" + +namespace google { +namespace protobuf { +class Message; +class MethodDescriptor; +} // namespace protobuf +} // namespace google + +namespace butil { +class IOBuf; +} + + +namespace brpc { +class Socket; +class SocketMessage; +class Controller; +class Authenticator; +class InputMessageBase; + +DECLARE_uint64(max_body_size); +DECLARE_bool(log_error_text); + +// Get the serialized byte size of the protobuf message, +// different versions of protobuf have different methods +// use template to avoid include `google/protobuf/message.h` +template +inline uint32_t GetProtobufByteSize(const T& message) { +#if GOOGLE_PROTOBUF_VERSION >= 3010000 + return message.ByteSizeLong(); +#else + return static_cast(message.ByteSize()); +#endif +} + +// 3 steps to add a new Protocol: +// Step1: Add a new ProtocolType in src/brpc/options.proto +// as identifier of the Protocol. +// Step2: Implement callbacks of struct `Protocol' in policy/ directory. +// Step3: Register the protocol in global.cpp using `RegisterProtocol' + +struct Protocol { + // [Required by both client and server] + // The callback to cut a message from `source'. + // Returned message will be passed to process_request and process_response + // later and Destroy()-ed by InputMessenger. + // Returns: + // MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA): + // `source' does not form a complete message yet. + // MakeParseError(PARSE_ERROR_TRY_OTHERS). + // `source' does not fit the protocol, the data should be tried by + // other protocols. If the data is definitely corrupted (e.g. magic + // header matches but other fields are wrong), pop corrupted part + // from `source' before returning. + // MakeMessage(InputMessageBase*): + // The message is parsed successfully and cut from `source'. + typedef ParseResult (*Parse)(butil::IOBuf* source, Socket *socket, + bool read_eof, const void *arg); + Parse parse; + + // [Required by client] + // The callback to serialize `request' into `request_buf' which will be + // packed into message by pack_request later. Called once for each RPC. + // `cntl' provides additional data needed by some protocol (say HTTP). + // Call cntl->SetFailed() on error. + typedef void (*SerializeRequest)( + butil::IOBuf* request_buf, + Controller* cntl, + const google::protobuf::Message* request); + SerializeRequest serialize_request; + + // [Required by client] + // The callback to pack `request_buf' into `iobuf_out' or `user_message_out' + // Called before sending each request (including retries). + // Remember to pack authentication information when `auth' is not NULL. + // Call cntl->SetFailed() on error. + typedef void (*PackRequest)( + butil::IOBuf* iobuf_out, + SocketMessage** user_message_out, + uint64_t correlation_id, + const google::protobuf::MethodDescriptor* method, + Controller* controller, + const butil::IOBuf& request_buf, + const Authenticator* auth); + PackRequest pack_request; + + // [Required by server] + // The callback to handle request `msg' created by a successful parse(). + // `msg' must be Destroy()-ed when the processing is done. To make sure + // Destroy() is always called, consider using DestroyingPtr<> defined in + // destroyable.h + // May be called in a different thread from parse(). + typedef void (*ProcessRequest)(InputMessageBase* msg); + ProcessRequest process_request; + + // [Required by client] + // The callback to handle response `msg' created by a successful parse(). + // `msg' must be Destroy()-ed when the processing is done. To make sure + // Destroy() is always called, consider using DestroyingPtr<> defined in + // destroyable.h + // May be called in a different thread from parse(). + typedef void (*ProcessResponse)(InputMessageBase* msg); + ProcessResponse process_response; + + // [Required by authenticating server] + // The callback to verify authentication of this socket. Only called + // on the first message that a socket receives. Can be NULL when + // authentication is not needed or this is the client side. + // Returns true on successful authentication. + typedef bool (*Verify)(const InputMessageBase* msg); + Verify verify; + + // [Optional] + // Convert `server_addr_and_port'(a parameter to Channel) to butil::EndPoint. + typedef bool (*ParseServerAddress)(butil::EndPoint* out, + const char* server_addr_and_port); + ParseServerAddress parse_server_address; + + // [Optional] Customize method name. + typedef const std::string& (*GetMethodName)( + const google::protobuf::MethodDescriptor* method, + const Controller*); + GetMethodName get_method_name; + + // Bitwise-or of supported ConnectionType + ConnectionType supported_connection_type; + + // Name of this protocol, must be string constant. + const char* name; + + // True if this protocol is supported at client-side. + bool support_client() const { + return serialize_request && pack_request && process_response; + } + // True if this protocol is supported at server-side. + bool support_server() const { return process_request; } +}; + +const ConnectionType CONNECTION_TYPE_POOLED_AND_SHORT = + (ConnectionType)((int)CONNECTION_TYPE_POOLED | + (int)CONNECTION_TYPE_SHORT); + +const ConnectionType CONNECTION_TYPE_ALL = + (ConnectionType)((int)CONNECTION_TYPE_SINGLE | + (int)CONNECTION_TYPE_POOLED | + (int)CONNECTION_TYPE_SHORT); + +// [thread-safe] +// Register `protocol' using key=`type'. +// Returns 0 on success, -1 otherwise +int RegisterProtocol(ProtocolType type, const Protocol& protocol); + +// [thread-safe] +// Find the protocol registered with key=`type'. +// Returns NULL on not found. +const Protocol* FindProtocol(ProtocolType type); + +// [thread-safe] +// List all registered protocols into `vec'. +void ListProtocols(std::vector* vec); +void ListProtocols(std::vector >* vec); + +// The common serialize_request implementation used by many protocols. +void SerializeRequestDefault(butil::IOBuf* buf, + Controller* cntl, + const google::protobuf::Message* request); + +// Replacements for msg->ParseFromXXX() to make the bytes limit in pb +// consistent with -max_body_size +bool ParsePbFromZeroCopyStream(google::protobuf::Message* msg, + google::protobuf::io::ZeroCopyInputStream* input); +bool ParsePbFromIOBuf(google::protobuf::Message* msg, const butil::IOBuf& buf); +bool ParsePbTextFromIOBuf(google::protobuf::Message* msg, const butil::IOBuf& buf); +bool ParsePbFromArray(google::protobuf::Message* msg, const void* data, size_t size); +bool ParsePbFromString(google::protobuf::Message* msg, const std::string& str); + +// Deleter for unique_ptr to print error_text of the controller when +// -log_error_text is on, then delete the controller if `delete_cntl' is true +class LogErrorTextAndDelete { +public: + explicit LogErrorTextAndDelete(bool delete_cntl = true) + : _delete_cntl(delete_cntl) {} + void operator()(Controller* c) const; +private: + bool _delete_cntl; +}; + +// Utility to build a temporary array. +// Example: +// TemporaryArrayBuilder b; +// b.push() = Foo1; +// b.push() = Foo2; +// UseArray(b.raw_array(), b.size()); +template +class TemporaryArrayBuilder { +public: + TemporaryArrayBuilder() : _size(0) {} + T& push() { + if (_size < N) { + return _arr[_size++]; + } else { + CHECK(false) << "push to a full array, cap=" << N; + static T dummy; + return dummy; + } + } + T& operator[](size_t i) { return _arr[i]; } + size_t size() const { return _size; } + T* raw_array() { return _arr; } +private: + size_t _size; + T _arr[N]; +}; + +} // namespace brpc + + +#endif // BRPC_PROTOCOL_H diff --git a/src/brpc/rdma/block_pool.cpp b/src/brpc/rdma/block_pool.cpp new file mode 100644 index 0000000..d8dbb8a --- /dev/null +++ b/src/brpc/rdma/block_pool.cpp @@ -0,0 +1,670 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +#if BRPC_WITH_RDMA + +#include +#include +#include +#include +#include "butil/fast_rand.h" +#include "butil/iobuf.h" +#include "butil/object_pool.h" +#include "butil/thread_local.h" +#include "butil/memory/scope_guard.h" +#include "brpc/rdma/block_pool.h" + +namespace brpc { +namespace rdma { + +DEFINE_int32(rdma_memory_pool_initial_size_mb, 1024, + "Initial size of memory pool for RDMA (MB)"); +DEFINE_int32(rdma_memory_pool_increase_size_mb, 1024, + "Increased size of memory pool for RDMA (MB)"); +DEFINE_int32(rdma_memory_pool_max_regions, 3, "Max number of regions"); +DEFINE_int32(rdma_memory_pool_buckets, 4, "Number of buckets to reduce race"); +DEFINE_int32(rdma_memory_pool_tls_cache_num, 128, "Number of cached block in tls"); +DEFINE_bool(rdma_memory_pool_user_specified_memory, false, + "If true, the user must call UserExtendBlockPool() to extend " + "memory. bRPC will not handle memory extension."); +DEFINE_string(rdma_recv_block_type, "default", "Default size type for recv WR: " + "default(8KB - 32B)/large(64KB - 32B)/huge(2MB - 32B)"); + +static RegisterCallback g_cb = NULL; + +// Number of bytes in 1MB +static const size_t BYTES_IN_MB = 1048576; + +static const int BLOCK_DEFAULT = 0; // 8KB +static const int BLOCK_LARGE = 1; // 64KB +static const int BLOCK_HUGE = 2; // 2MB +static const int BLOCK_SIZE_COUNT = 3; +static size_t g_block_size[BLOCK_SIZE_COUNT] = { 8192, 65536, 2 * BYTES_IN_MB }; + +struct IdleNode { + void* start; + size_t len; + IdleNode* next; +}; + +struct Region { + Region() { start = 0; } + uintptr_t start; + size_t size; + uint32_t block_type; + uint32_t id; // lkey +}; + +static const int32_t RDMA_MEMORY_POOL_MIN_REGIONS = 1; +static const int32_t RDMA_MEMORY_POOL_MAX_REGIONS = 16; +static Region g_regions[RDMA_MEMORY_POOL_MAX_REGIONS]; +static int g_region_num = 0; + +static const int32_t RDMA_MEMORY_POOL_MIN_SIZE = 32; // 16MB +static const int32_t RDMA_MEMORY_POOL_MAX_SIZE = 1048576; // 1TB + +static const int32_t RDMA_MEMORY_POOL_MIN_BUCKETS = 1; +static const int32_t RDMA_MEMORY_POOL_MAX_BUCKETS = 16; +static size_t g_buckets = 1; + +static bool g_dump_enable = false; +static butil::Mutex* g_dump_mutex = NULL; + +// Only for default block size +static __thread IdleNode* tls_idle_list = NULL; +static __thread size_t tls_idle_num = 0; +static __thread bool tls_inited = false; +static butil::Mutex* g_tls_info_mutex = NULL; +static size_t g_tls_info_cnt = 0; +static size_t* g_tls_info[1024]; + +// For each block size, there are some buckets of idle list to reduce race. +struct GlobalInfo { + std::vector idle_list[BLOCK_SIZE_COUNT]; + std::vector lock[BLOCK_SIZE_COUNT]; + std::vector idle_size[BLOCK_SIZE_COUNT]; + int region_num[BLOCK_SIZE_COUNT]; + butil::Mutex extend_lock; + std::vector expansion_list[BLOCK_SIZE_COUNT]; + std::vector expansion_size[BLOCK_SIZE_COUNT]; +}; +static GlobalInfo* g_info = NULL; + +static inline Region* GetRegion(const void* buf) { + if (!buf) { + errno = EINVAL; + return NULL; + } + Region* r = NULL; + uintptr_t addr = (uintptr_t)buf; + for (int i = 0; i < FLAGS_rdma_memory_pool_max_regions; ++i) { + if (g_regions[i].start == 0) { + break; + } + if (addr >= g_regions[i].start && + addr < g_regions[i].start + g_regions[i].size) { + r = &g_regions[i]; + break; + } + } + return r; +} + +uint32_t GetRegionId(const void* buf) { + Region* r = GetRegion(buf); + if (!r) { + return 0; + } + return r->id; +} + +static void* ExtendBlockPoolImpl(void* region_base, size_t region_size, int block_type) { + auto region_base_guard = butil::MakeScopeGuard([region_base]() { + free(region_base); + }); + + if (g_region_num == FLAGS_rdma_memory_pool_max_regions) { + LOG_EVERY_SECOND(ERROR) << "Memory pool reaches max regions"; + errno = ENOMEM; + return NULL; + } + + uint32_t id = g_cb(region_base, region_size); + if (id == 0) { + errno = EINVAL; + return NULL; + } + + IdleNode* node[g_buckets]; + for (size_t i = 0; i < g_buckets; ++i) { + node[i] = butil::get_object(); + if (!node[i]) { + PLOG_EVERY_SECOND(ERROR) << "Memory not enough"; + for (size_t j = 0; j < i; ++j) { + butil::return_object(node[j]); + } + errno = ENOMEM; + return NULL; + } + } + + Region* region = &g_regions[g_region_num++]; + region->start = (uintptr_t)region_base; + region->size = region_size; + region->id = id; + region->block_type = block_type; + + for (size_t i = 0; i < g_buckets; ++i) { + node[i]->start = (void*)(region->start + i * (region_size / g_buckets)); + node[i]->len = region_size / g_buckets; + node[i]->next = g_info->expansion_list[block_type][i]; + g_info->expansion_list[block_type][i] = node[i]; + g_info->expansion_size[block_type][i] += node[i]->len; + } + g_info->region_num[block_type]++; + + // `region_base' is inuse, cannot be freed. + region_base_guard.dismiss(); + + return region_base; +} + +// Extend the block pool with a new region (with different region ID) +static void* ExtendBlockPool(size_t region_size, int block_type) { + if (region_size < 1 || block_type < 0) { + errno = EINVAL; + return NULL; + } + + if (FLAGS_rdma_memory_pool_user_specified_memory) { + LOG_EVERY_SECOND(ERROR) << "Fail to extend new region, " + "rdma_memory_pool_user_specified_memory is " + "true, ExtendBlockPool is disabled"; + return NULL; + } + + // Regularize region size + region_size = region_size * BYTES_IN_MB / g_block_size[block_type] / g_buckets; + region_size *= g_block_size[block_type] * g_buckets; + + LOG(INFO) << "Start extend rdma memory " << region_size / BYTES_IN_MB << "MB"; + + void* region_base = NULL; + if (posix_memalign(®ion_base, 4096, region_size) != 0) { + PLOG_EVERY_SECOND(ERROR) << "Memory not enough"; + return NULL; + } + + return ExtendBlockPoolImpl(region_base, region_size, block_type); +} + +void* ExtendBlockPoolByUser(void* region_base, size_t region_size, int block_type) { + auto region_base_guard = butil::MakeScopeGuard([region_base]() { + free(region_base); + }); + + if (!FLAGS_rdma_memory_pool_user_specified_memory) { + LOG_EVERY_SECOND(ERROR) << "User extend memory is disabled"; + return NULL; + } + if (reinterpret_cast(region_base) % 4096 != 0) { + LOG_EVERY_SECOND(ERROR) << "region_base must be 4096 aligned"; + errno = EINVAL; + return NULL; + } + + region_size = + region_size * BYTES_IN_MB / g_block_size[block_type] / g_buckets; + region_size *= g_block_size[block_type] * g_buckets; + + region_base_guard.dismiss(); + BAIDU_SCOPED_LOCK(g_info->extend_lock); + return ExtendBlockPoolImpl(region_base, region_size, block_type); +} + +bool InitBlockPool(RegisterCallback cb) { + if (!cb) { + errno = EINVAL; + return false; + } + if (g_cb) { + LOG(WARNING) << "Do not initialize block pool repeatedly"; + errno = EINVAL; + return false; + } + g_cb = cb; + if (FLAGS_rdma_memory_pool_max_regions < RDMA_MEMORY_POOL_MIN_REGIONS || + FLAGS_rdma_memory_pool_max_regions > RDMA_MEMORY_POOL_MAX_REGIONS) { + LOG(WARNING) << "rdma_memory_pool_max_regions(" + << FLAGS_rdma_memory_pool_max_regions << ") not in [" + << RDMA_MEMORY_POOL_MIN_REGIONS << "," + << RDMA_MEMORY_POOL_MAX_REGIONS << "]!"; + errno = EINVAL; + return false; + } + if (FLAGS_rdma_memory_pool_initial_size_mb < RDMA_MEMORY_POOL_MIN_SIZE || + FLAGS_rdma_memory_pool_initial_size_mb > RDMA_MEMORY_POOL_MAX_SIZE) { + LOG(WARNING) << "rdma_memory_pool_initial_size_mb(" + << FLAGS_rdma_memory_pool_initial_size_mb << ") not in [" + << RDMA_MEMORY_POOL_MIN_SIZE << "," + << RDMA_MEMORY_POOL_MAX_SIZE << "]!"; + errno = EINVAL; + return false; + } + if (FLAGS_rdma_memory_pool_increase_size_mb < RDMA_MEMORY_POOL_MIN_SIZE || + FLAGS_rdma_memory_pool_increase_size_mb > RDMA_MEMORY_POOL_MAX_SIZE) { + LOG(WARNING) << "rdma_memory_pool_increase_size_mb(" + << FLAGS_rdma_memory_pool_increase_size_mb << ") not in [" + << RDMA_MEMORY_POOL_MIN_SIZE << "," + << RDMA_MEMORY_POOL_MAX_SIZE << "]!"; + errno = EINVAL; + return false; + } + if (FLAGS_rdma_memory_pool_buckets < RDMA_MEMORY_POOL_MIN_BUCKETS || + FLAGS_rdma_memory_pool_buckets > RDMA_MEMORY_POOL_MAX_BUCKETS) { + LOG(WARNING) << "rdma_memory_pool_buckets(" + << FLAGS_rdma_memory_pool_buckets << ") not in [" + << RDMA_MEMORY_POOL_MIN_BUCKETS << "," + << RDMA_MEMORY_POOL_MAX_BUCKETS << "]!"; + errno = EINVAL; + return false; + } + g_buckets = FLAGS_rdma_memory_pool_buckets; + g_info = new (std::nothrow) GlobalInfo; + if (!g_info) { + return false; + } + + for (int i = 0; i < BLOCK_SIZE_COUNT; ++i) { + g_info->idle_list[i].resize(g_buckets, NULL); + if (g_info->idle_list[i].size() != g_buckets) { + return false; + } + g_info->lock[i].resize(g_buckets, NULL); + if (g_info->lock[i].size() != g_buckets) { + return false; + } + g_info->idle_size[i].resize(g_buckets, 0); + if (g_info->idle_size[i].size() != g_buckets) { + return false; + } + g_info->region_num[i] = 0; + for (size_t j = 0; j < g_buckets; ++j) { + g_info->lock[i][j] = new (std::nothrow) butil::Mutex; + if (!g_info->lock[i][j]) { + return false; + } + } + g_info->expansion_list[i].resize(g_buckets, NULL); + if (g_info->expansion_list[i].size() != g_buckets) { + return false; + } + g_info->expansion_size[i].resize(g_buckets, 0); + if (g_info->expansion_size[i].size() != g_buckets) { + return false; + } + } + + g_dump_mutex = new butil::Mutex; + g_tls_info_mutex = new butil::Mutex; + + if (FLAGS_rdma_memory_pool_user_specified_memory) { + return true; + } + + if (ExtendBlockPool(FLAGS_rdma_memory_pool_initial_size_mb, + GetRdmaBlockType()) != NULL) { + return true; + } + return false; +} + +static void MoveExpansionList2EmptyIdleList(int block_type, size_t index) { + CHECK(NULL == g_info->idle_list[block_type][index]); + + g_info->idle_list[block_type][index] = g_info->expansion_list[block_type][index]; + g_info->idle_size[block_type][index] += g_info->expansion_size[block_type][index]; + g_info->expansion_list[block_type][index] = NULL; + g_info->expansion_size[block_type][index] = 0; +} + +static void* AllocBlockFrom(int block_type) { + bool locked = false; + if (BAIDU_UNLIKELY(g_dump_enable)) { + g_dump_mutex->lock(); + locked = true; + } + BUTIL_SCOPE_EXIT { + if (locked) { + g_dump_mutex->unlock(); + } + }; + + void* ptr = NULL; + if (0 == block_type && NULL != tls_idle_list) { + CHECK(tls_idle_num > 0); + IdleNode* n = tls_idle_list; + tls_idle_list = n->next; + ptr = n->start; + butil::return_object(n); + tls_idle_num--; + return ptr; + } + + size_t index = butil::fast_rand() % g_buckets; + BAIDU_SCOPED_LOCK(*g_info->lock[block_type][index]); + IdleNode* node = g_info->idle_list[block_type][index]; + if (NULL == node) { + BAIDU_SCOPED_LOCK(g_info->extend_lock); + node = g_info->idle_list[block_type][index]; + if (NULL == node && NULL != g_info->expansion_list[block_type][index]) { + MoveExpansionList2EmptyIdleList(block_type, index); + node = g_info->idle_list[block_type][index]; + } + if (NULL == node) { + // There is no block left, extend a new region. + if (!ExtendBlockPool(FLAGS_rdma_memory_pool_increase_size_mb, block_type)) { + LOG_EVERY_SECOND(ERROR) << "Fail to extend new region. " + << "You can set the size of memory pool larger. " + << "Refer to the help message of these flags: " + << "rdma_memory_pool_initial_size_mb, " + << "rdma_memory_pool_increase_size_mb, " + << "rdma_memory_pool_max_regions."; + return NULL; + } + MoveExpansionList2EmptyIdleList(block_type, index); + node = g_info->idle_list[block_type][index]; + } + } + CHECK(NULL != node); + + ptr = node->start; + if (node->len > g_block_size[block_type]) { + node->start = (char*)node->start + g_block_size[block_type]; + node->len -= g_block_size[block_type]; + } else { + g_info->idle_list[block_type][index] = node->next; + butil::return_object(node); + } + g_info->idle_size[block_type][index] -= g_block_size[block_type]; + + // Move more blocks from global list to tls list + if (block_type == 0) { + node = g_info->idle_list[0][index]; + tls_idle_list = node; + IdleNode* last_node = NULL; + while (node) { + if (tls_idle_num > (uint32_t)FLAGS_rdma_memory_pool_tls_cache_num / 2 + || node->len > g_block_size[0]) { + break; + } + tls_idle_num++; + last_node = node; + node = node->next; + } + if (tls_idle_num == 0) { + tls_idle_list = NULL; + } else { + g_info->idle_list[0][index] = node; + } + if (last_node) { + last_node->next = NULL; + } + } + + return ptr; +} + +void* AllocBlock(size_t size) { + if (size == 0 || size > g_block_size[BLOCK_SIZE_COUNT - 1]) { + errno = EINVAL; + return NULL; + } + for (int i = 0; i < BLOCK_SIZE_COUNT; ++i) { + if (size <= g_block_size[i]) { + return AllocBlockFrom(i);; + } + } + return NULL; +} + +void RecycleAll() { + // Only block_type == 0 needs recycle + while (tls_idle_list) { + IdleNode* node = tls_idle_list; + tls_idle_list = node->next; + Region* r = GetRegion(node->start); + if (!r) { + continue; + } + uint64_t index = ((uintptr_t)node->start - r->start) * g_buckets / r->size; + BAIDU_SCOPED_LOCK(*g_info->lock[0][index]); + node->next = g_info->idle_list[0][index]; + g_info->idle_list[0][index] = node; + } + tls_idle_num = 0; +} + +int DeallocBlock(void* buf) { + if (!buf) { + errno = EINVAL; + return -1; + } + + Region* r = GetRegion(buf); + if (!r) { + errno = ERANGE; + return -1; + } + + IdleNode* node = butil::get_object(); + if (!node) { + PLOG_EVERY_SECOND(ERROR) << "Memory not enough"; + // May lead to block leak, but do not return -1 + return 0; + } + + uint32_t block_type = r->block_type; + size_t block_size = g_block_size[block_type]; + node->start = buf; + node->len = block_size; + + bool locked = false; + if (BAIDU_UNLIKELY(g_dump_enable)) { + g_dump_mutex->lock(); + locked = true; + } + BUTIL_SCOPE_EXIT { + if (locked) { + g_dump_mutex->unlock(); + } + }; + + if (block_type == 0 && tls_idle_num < (uint32_t)FLAGS_rdma_memory_pool_tls_cache_num) { + if (!tls_inited) { + tls_inited = true; + butil::thread_atexit(RecycleAll); + BAIDU_SCOPED_LOCK(*g_tls_info_mutex); + if (g_tls_info_cnt < 1024) { + g_tls_info[g_tls_info_cnt++] = &tls_idle_num; + } + } + tls_idle_num++; + node->next = tls_idle_list; + tls_idle_list = node; + return 0; + } + + uint64_t index = ((uintptr_t)buf - r->start) * g_buckets / r->size; + if (block_type == 0) { + size_t len = 0; + // Recycle half the cached blocks in tls for default block size + int num = FLAGS_rdma_memory_pool_tls_cache_num / 2; + IdleNode* new_head = tls_idle_list; + IdleNode* recycle_tail = NULL; + for (int i = 0; i < num; ++i) { + recycle_tail = new_head; + len += recycle_tail->len; + new_head = new_head->next; + } + if (recycle_tail) { + BAIDU_SCOPED_LOCK(*g_info->lock[0][index]); + recycle_tail->next = node; + node->next = g_info->idle_list[0][index]; + g_info->idle_list[0][index] = tls_idle_list; + g_info->idle_size[0][index] += len; + } + tls_idle_list = new_head; + tls_idle_num -= num; + } else { + BAIDU_SCOPED_LOCK(*g_info->lock[block_type][index]); + node->next = g_info->idle_list[block_type][index]; + g_info->idle_list[block_type][index] = node; + g_info->idle_size[block_type][index] += node->len; + } + return 0; +} + +size_t GetBlockSize(int type) { + return g_block_size[type]; +} + +size_t GetRdmaBlockSize() { + if (FLAGS_rdma_recv_block_type == "default") { + return GetBlockSize(0); + } else if (FLAGS_rdma_recv_block_type == "large") { + return GetBlockSize(1); + } else if (FLAGS_rdma_recv_block_type == "huge") { + return GetBlockSize(2); + } else { + LOG(ERROR) << "rdma_recv_block_type incorrect " + << "(valid value: default/large/huge)"; + return 0; + } +} + +int GetRdmaBlockType() { + if (FLAGS_rdma_recv_block_type == "default") { + return BLOCK_DEFAULT; + } else if (FLAGS_rdma_recv_block_type == "large") { + return BLOCK_LARGE; + } else if (FLAGS_rdma_recv_block_type == "huge") { + return BLOCK_HUGE; + } else { + LOG(ERROR) << "rdma_recv_block_type incorrect " + << "(valid value: default/large/huge)"; + return -1; + } +} + +void DumpMemoryPoolInfo(std::ostream& os) { + if (!g_dump_mutex) { + return; + } + g_dump_enable = true; + usleep(1000); // wait until all the threads read new g_dump_enable + BAIDU_SCOPED_LOCK(*g_dump_mutex); + os << "********************* Memory Pool Info Dump **********************\n"; + os << "Region Info:\n"; + for (int i = 0; i < g_region_num; ++i) { + os << "\tRegion " << i << ":\n" + << "\t\tBase Addr: " << g_regions[i].start << "\n" + << "\t\tSize: " << g_regions[i].size << "\n" + << "\t\tBlock Type: " << g_regions[i].block_type << "\n" + << "\t\tId: " << g_regions[i].id << "\n"; + } + os << "Idle List Info:\n"; + for (int i = 0; i < BLOCK_SIZE_COUNT; ++i) { + os << "\tFor block size " << GetBlockSize(i) << ":\n"; + for (size_t j = 0; j < g_buckets; ++j) { + os << "\t\tBucket " << j << ": {" << g_info->idle_size[i][j] + << ", " << g_info->expansion_size[i][j] << "}\n"; + } + } + os << "Thread Local Cache Info:\n"; + for (size_t i = 0; i < g_tls_info_cnt; ++i) { + os << "\tThread " << i << ": " << *g_tls_info[i] * 8192 << "\n"; + } + os << "******************************************************************\n"; + g_dump_enable = false; +} + +// Just for UT +void DestroyBlockPool() { + RecycleAll(); + for (int i = 0; i < BLOCK_SIZE_COUNT; ++i) { + for (size_t j = 0; j < g_buckets; ++j) { + IdleNode* node = g_info->idle_list[i][j]; + while (node) { + IdleNode* tmp = node->next; + butil::return_object(node); + node = tmp; + } + g_info->idle_list[i][j] = NULL; + // Release the per-bucket mutexes allocated in InitBlockPool. + delete g_info->lock[i][j]; + g_info->lock[i][j] = NULL; + } + } + delete g_info; + g_info = NULL; + delete g_dump_mutex; + g_dump_mutex = NULL; + delete g_tls_info_mutex; + g_tls_info_mutex = NULL; + for (int i = 0; i < g_region_num; ++i) { + if (g_regions[i].start == 0) { + break; + } + free((void*)g_regions[i].start); + g_regions[i].start = 0; + } + g_region_num = 0; + g_cb = NULL; +} + +// Just for UT +int GetBlockType(void* buf) { + Region* r = GetRegion(buf); + if (!r) { + return -1; + } + return r->block_type; +} + +// Just for UT +size_t GetGlobalLen(int block_type) { + size_t len = 0; + for (size_t i = 0; i < g_buckets; ++i) { + IdleNode* node = g_info->idle_list[block_type][i]; + while (node) { + len += node->len; + node = node->next; + } + } + return len; +} + +// Just for UT +size_t GetRegionNum() { + return g_region_num; +} + +} // namespace rdma +} // namespace brpc + +#endif // if BRPC_WITH_RDMA diff --git a/src/brpc/rdma/block_pool.h b/src/brpc/rdma/block_pool.h new file mode 100644 index 0000000..c9589fb --- /dev/null +++ b/src/brpc/rdma/block_pool.h @@ -0,0 +1,115 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_RDMA_BLOCK_POOL_H +#define BRPC_RDMA_BLOCK_POOL_H + +#if BRPC_WITH_RDMA + +namespace brpc { +namespace rdma { + +// This is used as a memory pool for RDMA. The reason why we use memory +// pool is that RDMA transmission requires data in a "registered" space. +// We first get a large bulk of memory by tcmalloc (or other memory +// allocator). Then we call ibv_reg_mr to register this bulk. Every time we +// want to use a piece of memory to send/recv with RDMA, we allocate it from +// memory pool instead of tcmalloc. +// +// It is called block_pool due to the unit of memory allocation is block, +// not byte. That means that when the caller wants to get a piece of +// memory smaller than the block size, the pool will still return a whole +// block for it. Apparently, this mechanism may introduce waste of memory. +// However, since in brpc the memory pool is only used by IOBuf, which +// always requires block allocation, we believe that block_pool is a better +// design than the byte-based memory pool. +// +// Because the initial size of block_pool (default: 1GB) may not enough, we +// hope that the pool is scalable, which means that it can be enlarged when +// there is no more memory in the pool. Therefore, we introduce the concept +// of region. Every bulk of memory got from tcmalloc is called a region. +// And the region is the unit of RDMA registration. The caller must be able +// to get the LKey of the region from the pool, which we call it region ID. +// +// Since IOBuf supports different block size, the block_pool also supports +// several block sizes: 8KB(default), 64KB and 2MB. The block allocated to +// the caller is the block with minimum size which is larger than the +// applied size. For example, if the caller needs a buffer with a size of +// 9KB, block_pool will allocate a 64KB-block for it. Please remember that +// different-size blocks are in different regions. +// +// Currently, the block_pool supports 16 regions at most. If there is more than +// one region, the complexity of finding which region an address belongs to +// is O(n). Here n is the number of regions. In order to avoid race conditions +// among threads, we do not use more efficient search data structure. +// Therefore, DO NOT rely on the scalable feature of block_pool too much. The +// developper should estimate the consumption of memory used for RDMA in +// advance as possible as she/he can. Besides, if it is possible, please use +// one size of blocks only. +// +// The block_pool is thread-safe, so that the caller can call it in different +// threads. However, before calling allocation and deallocation, the caller +// must call initialization of the block_pool. Otherwise the behavior is +// undefined. + +typedef uint32_t (*RegisterCallback)(void*, size_t); + +// Initialize the block pool +// The argument is a callback called when the pool is enlarged with a new +// region. It should be the memory registration in brpc. However, +// in block_pool, we just abstract it into a function to get region id. +// Return the first region's address, NULL if failed and errno is set. +bool InitBlockPool(RegisterCallback cb); + +// In scenarios where users need to manually specify memory regions (e.g., using +// hugepages or custom memory pools), when +// FLAGS_rdma_memory_pool_user_specified_memory is true, user is responsibility +// of extending memory blocks , this ensuring flexibility for advanced use +// cases. +void* ExtendBlockPoolByUser(void* region_base, size_t region_size, int block_type); + +// Allocate a buf with length at least @a size (require: size>0) +// Return the address allocated, NULL if failed and errno is set. +void* AllocBlock(size_t size); + +// Deallocate the buf (require: buf!=NULL) +// Return 0 if success, -1 if failed and errno is set. +// If the given buf is not in any region, the errno is ERANGE. +int DeallocBlock(void* buf); + +// Get the region ID of the given buf +uint32_t GetRegionId(const void* buf); + +// Return the block size of given block type +// type=0: BLOCK_DEFAULT(8KB) +// type=1: BLOCK_LARGE(64KB) +// type=2: BLOCK_HUGE(2MB) +size_t GetBlockSize(int type); + +size_t GetRdmaBlockSize(); + +int GetRdmaBlockType(); + +// Dump memory pool information +void DumpMemoryPoolInfo(std::ostream& os); + +} // namespace rdma +} // namespace brpc + +#endif // if BRPC_WITH_RDMA + +#endif // BRPC_RDMA_BLOCK_POOL_H diff --git a/src/brpc/rdma/rdma_endpoint.cpp b/src/brpc/rdma/rdma_endpoint.cpp new file mode 100644 index 0000000..a5016dc --- /dev/null +++ b/src/brpc/rdma/rdma_endpoint.cpp @@ -0,0 +1,1792 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +#if BRPC_WITH_RDMA + +#include +#include "butil/fd_utility.h" +#include "butil/logging.h" // CHECK, LOG +#include "butil/sys_byteorder.h" // HostToNet,NetToHost +#include "bthread/bthread.h" +#include "brpc/errno.pb.h" +#include "brpc/event_dispatcher.h" +#include "brpc/input_messenger.h" +#include "brpc/socket.h" +#include "brpc/reloadable_flags.h" +#include "brpc/rdma/block_pool.h" +#include "brpc/rdma/rdma_helper.h" +#include "brpc/rdma/rdma_endpoint.h" +#include "brpc/rdma_transport.h" +#include "brpc/rdma/rdma_handshake.h" + +DECLARE_int32(task_group_ntags); + +namespace brpc { +namespace rdma { + +extern ibv_cq* (*IbvCreateCq)(ibv_context*, int, void*, ibv_comp_channel*, int); +extern int (*IbvDestroyCq)(ibv_cq*); +extern ibv_comp_channel* (*IbvCreateCompChannel)(ibv_context*); +extern int (*IbvDestroyCompChannel)(ibv_comp_channel*); +extern int (*IbvGetCqEvent)(ibv_comp_channel*, ibv_cq**, void**); +extern void (*IbvAckCqEvents)(ibv_cq*, unsigned int); +extern ibv_qp* (*IbvCreateQp)(ibv_pd*, ibv_qp_init_attr*); +extern int (*IbvModifyQp)(ibv_qp*, ibv_qp_attr*, ibv_qp_attr_mask); +extern int (*IbvQueryQp)(ibv_qp*, ibv_qp_attr*, ibv_qp_attr_mask, ibv_qp_init_attr*); +extern int (*IbvDestroyQp)(ibv_qp*); +extern int (*IbvQueryEce)(ibv_qp*, ibv_ece*); +extern int (*IbvSetEce)(ibv_qp*, ibv_ece*); +extern bool g_skip_rdma_init; + +DEFINE_int32(rdma_sq_size, 128, "SQ size for RDMA"); +DEFINE_int32(rdma_rq_size, 128, "RQ size for RDMA"); +DEFINE_bool(rdma_recv_zerocopy, true, "Enable zerocopy for receive side"); +DEFINE_int32(rdma_zerocopy_min_size, 512, "The minimal size for receive zerocopy"); +DEFINE_int32(rdma_cqe_poll_once, 32, "The maximum of cqe number polled once."); +DEFINE_int32(rdma_prepared_qp_size, 128, "SQ and RQ size for prepared QP."); +DEFINE_int32(rdma_prepared_qp_cnt, 1024, "Initial count of prepared QP."); +DEFINE_bool(rdma_trace_verbose, false, "Print log message verbosely"); +BRPC_VALIDATE_GFLAG(rdma_trace_verbose, brpc::PassValidate); +DEFINE_bool(rdma_use_polling, false, "Use polling mode for RDMA."); +DEFINE_int32(rdma_poller_num, 1, "Poller number in RDMA polling mode."); +DEFINE_bool(rdma_poller_yield, false, "Yield thread in RDMA polling mode."); +DEFINE_bool(rdma_disable_bthread, false, "Disable bthread in RDMA"); +DEFINE_bool(rdma_ece, false, "Open ece in RDMA, should use this feature when rdma nics are from the same merchant."); + +static const size_t IOBUF_BLOCK_HEADER_LEN = 32; // implementation-dependent + +// DO NOT change this value unless you know the safe value!!! +// This is the number of reserved WRs in SQ/RQ for pure ACK. +extern const size_t RESERVED_WR_NUM = 3; + +// The local recv block size, set during GlobalInitialize. +uint32_t g_rdma_recv_block_size = 0; + +// static const uint32_t MAX_INLINE_DATA = 64; +static const uint8_t MAX_HOP_LIMIT = 16; +static const uint8_t TIMEOUT = 14; +static const uint8_t RETRY_CNT = 7; +extern const uint16_t MIN_QP_SIZE = 16; +static const uint16_t MAX_QP_SIZE = 4096; +extern const uint16_t MIN_BLOCK_SIZE = 1024; + +// ACK message wire format (shared by all protocol versions): a single +// 4B big-endian flags word; bit 0 (HELLO_ACK_RDMA_OK) indicates the +// sender wants to use RDMA. The state machines in +// ProcessHandshakeAt{Client,Server} inline the corresponding 4B +// send/recv directly using ReadFromFd / WriteToFd. +static const size_t HELLO_ACK_LEN = 4; +static const uint32_t HELLO_ACK_RDMA_OK = 0x1; + +static butil::Mutex* g_rdma_resource_mutex = NULL; +static RdmaResource* g_rdma_resource_list = NULL; + +RdmaResource::~RdmaResource() { + if (NULL != qp) { + IbvDestroyQp(qp); + } + if (NULL != polling_cq) { + IbvDestroyCq(polling_cq); + } + if (NULL != send_cq) { + IbvDestroyCq(send_cq); + } + if (NULL != recv_cq) { + IbvDestroyCq(recv_cq); + } + if (NULL != comp_channel) { + IbvDestroyCompChannel(comp_channel); + } +} + +RdmaEndpoint::RdmaEndpoint(Socket* s) + : _socket(s) + , _state(UNINIT) + , _handshake_version(0) + , _resource(NULL) + , _send_cq_events(0) + , _recv_cq_events(0) + , _cq_sid(INVALID_SOCKET_ID) + , _sq_size(FLAGS_rdma_sq_size) + , _rq_size(FLAGS_rdma_rq_size) + , _remote_recv_block_size(0) + , _accumulated_ack(0) + , _unsolicited(0) + , _unsolicited_bytes(0) + , _sq_current(0) + , _sq_unsignaled(0) + , _sq_sent(0) + , _rq_received(0) + , _local_window_capacity(0) + , _remote_window_capacity(0) + , _sq_imm_window_size(0) + , _remote_rq_window_size(0) + , _sq_window_size(0) + , _new_rq_wrs(0) +{ + if (_sq_size < MIN_QP_SIZE) { + _sq_size = MIN_QP_SIZE; + } + if (_sq_size > MAX_QP_SIZE) { + _sq_size = MAX_QP_SIZE; + } + if (_rq_size < MIN_QP_SIZE) { + _rq_size = MIN_QP_SIZE; + } + if (_rq_size > MAX_QP_SIZE) { + _rq_size = MAX_QP_SIZE; + } + _read_butex = bthread::butex_create_checked >(); +} + +RdmaEndpoint::~RdmaEndpoint() { + Reset(); + bthread::butex_destroy(_read_butex); +} + +void RdmaEndpoint::Reset() { + DeallocateResources(); + + _state.store(UNINIT, butil::memory_order_relaxed); + _resource = NULL; + _send_cq_events = 0; + _recv_cq_events = 0; + _cq_sid = INVALID_SOCKET_ID; + _sbuf.clear(); + _rbuf.clear(); + _rbuf_data.clear(); + _remote_recv_block_size = 0; + _accumulated_ack = 0; + _unsolicited = 0; + _unsolicited_bytes = 0; + _sq_current = 0; + _sq_unsignaled = 0; + _sq_sent = 0; + _rq_received = 0; + _local_window_capacity = 0; + _remote_window_capacity = 0; + _sq_imm_window_size = 0; + _remote_rq_window_size.store(0, butil::memory_order_relaxed); + _sq_window_size.store(0, butil::memory_order_relaxed); + _new_rq_wrs.store(0, butil::memory_order_relaxed); +} + +void RdmaConnect::StartConnect(const Socket* socket, + void (*done)(int err, void* data), + void* data) { + auto* rdma_transport = static_cast(socket->_transport.get()); + CHECK(rdma_transport->_rdma_ep != NULL); + SocketUniquePtr s; + if (Socket::Address(socket->id(), &s) != 0) { + return; + } + if (!IsRdmaAvailable()) { + rdma_transport->_rdma_ep->_state.store(RdmaEndpoint::FALLBACK_TCP, + butil::memory_order_relaxed); + rdma_transport->_rdma_state = RdmaTransport::RDMA_OFF; + done(0, data); + return; + } + _done = done; + _data = data; + bthread_t tid; + bthread_attr_t attr = BTHREAD_ATTR_NORMAL; + bthread_attr_set_name(&attr, "RdmaProcessHandshakeAtClient"); + if (bthread_start_background(&tid, &attr, + RdmaEndpoint::ProcessHandshakeAtClient, + rdma_transport->_rdma_ep) < 0) { + LOG(FATAL) << "Fail to start handshake bthread"; + Run(); + } else { + s.release(); + } +} + +void RdmaConnect::StopConnect(Socket* socket) { } + +void RdmaConnect::Run() { + _done(errno, _data); +} + +static void TryReadOnTcpDuringRdmaEst(Socket* s) { + int progress = Socket::PROGRESS_INIT; + while (true) { + uint8_t tmp; + ssize_t nr = read(s->fd(), &tmp, 1); + if (nr < 0) { + if (errno != EAGAIN) { + const int saved_errno = errno; + PLOG(WARNING) << "Fail to read from " << s; + s->SetFailed(saved_errno, "Fail to read from %s: %s", + s->description().c_str(), berror(saved_errno)); + return; + } + if (!s->MoreReadEvents(&progress)) { + break; + } + } else if (nr == 0) { + s->SetEOF(); + return; + } else { + LOG(WARNING) << "Read unexpected data from " << s; + s->SetFailed(EPROTO, "Read unexpected data from %s", + s->description().c_str()); + return; + } + } +} + +void RdmaEndpoint::OnNewDataFromTcp(Socket* m) { + auto* rdma_transport = static_cast(m->_transport.get()); + RdmaEndpoint* ep = rdma_transport->GetRdmaEp(); + CHECK(ep != NULL); + + int progress = Socket::PROGRESS_INIT; + while (true) { + State state = ep->_state.load(butil::memory_order_acquire); + if (state == UNINIT) { + if (!m->CreatedByConnect()) { + if (!IsRdmaAvailable()) { + rdma_transport->_rdma_state = RdmaTransport::RDMA_OFF; + ep->_state.store(FALLBACK_TCP, butil::memory_order_relaxed); + continue; + } + bthread_t tid; + ep->_state.store(S_HELLO_WAIT, butil::memory_order_relaxed); + SocketUniquePtr s; + m->ReAddress(&s); + bthread_attr_t attr = BTHREAD_ATTR_NORMAL; + bthread_attr_set_name(&attr, "RdmaProcessHandshakeAtServer"); + if (bthread_start_background(&tid, &attr, ProcessHandshakeAtServer, ep) < 0) { + ep->_state.store(UNINIT, butil::memory_order_relaxed); + LOG(FATAL) << "Fail to start handshake bthread"; + } else { + s.release(); + } + } else { + // The connection may be closed or reset before the client + // starts handshake. This will be handled by client handshake. + // Ignore the exception here. + } + } else if (state < ESTABLISHED) { // during handshake + ep->_read_butex->fetch_add(1, butil::memory_order_release); + bthread::butex_wake(ep->_read_butex); + } else if (state == FALLBACK_TCP){ // handshake finishes + InputMessenger::OnNewMessages(m); + return; + } else if (state == ESTABLISHED) { + TryReadOnTcpDuringRdmaEst(ep->_socket); + return; + } + if (!m->MoreReadEvents(&progress)) { + break; + } + } +} + +static const int WAIT_TIMEOUT_MS = 50; + +// Drive an EAGAIN-aware read loop to completion (exactly `len` bytes). +// `read_once(offset, remaining)` performs ONE underlying read attempt: +// - returns > 0 : number of bytes consumed (added to running total); +// - returns = 0 : end-of-stream (the loop fails with EEOF); +// - returns < 0 : errno set; EAGAIN is handled here via butex_wait, +// any other errno bubbles up. +// `offset` is bytes already received in THIS call (initially 0); the +// callable uses it to choose the next write target (e.g. `(char*)buf +// + offset`). Callables that don't need offset (e.g. IOPortal append) +// can ignore it. +// +// Centralizes the EAGAIN/butex/EOF loop so the two ReadFromFd +// overloads below stay one-liners; any future read source (memory- +// mapped, scatter-vector, etc.) can plug in by passing its own +// `read_once`. +template +static int ReadFromFdLoop(butil::atomic* read_butex, + size_t len, ReadOnce&& read_once) { + size_t received = 0; + while (received < len) { + const int expected_val = read_butex->load(butil::memory_order_acquire); + const timespec duetime = butil::milliseconds_from_now(WAIT_TIMEOUT_MS); + ssize_t nr = read_once(received, len - received); + if (nr < 0) { + if (errno == EAGAIN) { + if (bthread::butex_wait(read_butex, expected_val, &duetime) < 0) { + if (errno != EWOULDBLOCK && errno != ETIMEDOUT) { + return -1; + } + } + } else { + return -1; + } + } else if (nr == 0) { // Got EOF + errno = EEOF; + return -1; + } else { + received += nr; + } + } + return 0; +} + +int RdmaEndpoint::ReadFromFd(void* data, size_t len) { + CHECK(data != NULL); + const int fd = _socket->fd(); + return ReadFromFdLoop(_read_butex, len, + [data, fd](size_t offset, size_t remaining) { + return read(fd, (uint8_t*)data + offset, remaining); + }); +} + +int RdmaEndpoint::ReadFromFd(butil::IOPortal* data, size_t len) { + CHECK(data != NULL); + const int fd = _socket->fd(); + return ReadFromFdLoop(_read_butex, len, + [data, fd](size_t /*offset*/, size_t remaining) { + return data->append_from_file_descriptor(fd, remaining); + }); +} + +// Drive an EAGAIN-aware write loop to completion (exactly `len` bytes). +// +// `write_once(offset, remaining)` performs ONE underlying write attempt: +// - returns >= 0 : number of bytes consumed (added to running total); +// - returns < 0 : errno set; EAGAIN triggers `wait_writable(duetime)`, +// any other errno bubbles up. +// `offset` is bytes already written in THIS call (initially 0); the +// callable uses it to choose the next read source (e.g. `(char*)buf +// + offset`). Callables that drain a self-tracking sink (e.g. +// IOBuf::cut_into_file_descriptor) can ignore both args. +// +// `wait_writable(duetime)` is invoked on EAGAIN to park until the fd +// becomes writable again. It returns 0 on wake-up (or ETIMEDOUT), +// non-zero on hard failure. +template +static int WriteToFdLoop(size_t len, WriteOnce&& write_once, WaitWritable&& wait_writable) { + size_t written = 0; + while (written < len) { + const timespec duetime = butil::milliseconds_from_now(WAIT_TIMEOUT_MS); + ssize_t nw = write_once(written, len - written); + if (nw >= 0) { + written += nw; + continue; + } + + if (errno != EAGAIN) { + return -1; + } + if (!wait_writable(&duetime)) { + return -1; + } + } + return 0; +} + +int RdmaEndpoint::WriteToFd(void* data, size_t len) { + CHECK(data != NULL); + Socket* s = _socket; + const int fd = s->fd(); + return WriteToFdLoop(len, + [data, fd](size_t offset, size_t remaining) { + return write(fd, (uint8_t*)data + offset, remaining); + }, + [s, fd](const timespec* duetime) { + return s->WaitEpollOut(fd, true, duetime) == 0 || errno == ETIMEDOUT; + }); +} + +int RdmaEndpoint::WriteToFd(butil::IOBuf* data) { + CHECK(data != NULL); + Socket* s = _socket; + const int fd = s->fd(); + return WriteToFdLoop(data->size(), + [data, fd](size_t /*offset*/, size_t /*remaining*/) { + return data->cut_into_file_descriptor(fd); + }, + [s, fd](const timespec* duetime) { + return s->WaitEpollOut(fd, true, duetime) == 0 || errno == ETIMEDOUT; + }); +} + +inline void RdmaEndpoint::TryReadOnTcp() { + if (_socket->_nevent.fetch_add(1, butil::memory_order_acq_rel) == 0) { + State state = _state.load(butil::memory_order_acquire); + if (state == FALLBACK_TCP) { + InputMessenger::OnNewMessages(_socket); + } else if (state == ESTABLISHED) { + TryReadOnTcpDuringRdmaEst(_socket); + } + } +} + +void RdmaEndpoint::ApplyRemoteHello(const ParsedHello& remote) { + _remote_recv_block_size = remote.block_size; + _local_window_capacity = + std::min(_sq_size, remote.rq_size) - RESERVED_WR_NUM; + _remote_window_capacity = + std::min(_rq_size, remote.sq_size) - RESERVED_WR_NUM; + _sq_imm_window_size = RESERVED_WR_NUM; + _remote_rq_window_size.store( + _local_window_capacity, butil::memory_order_relaxed); + _sq_window_size.store( + _local_window_capacity, butil::memory_order_relaxed); +} + +// Client-side handshake entry: the state machine. +// +// C_ALLOC_QPCQ +// | +// v +// C_HELLO_SEND (hs->SendLocalHello) +// | +// v +// C_HELLO_WAIT (hs->ReceiveAndParseRemoteHello) +// | +// v +// [negotiation: ApplyRemoteHello + C_BRINGUP_QP] +// | +// v +// C_ACK_SEND +// | +// v +// ESTABLISHED / FALLBACK_TCP +void* RdmaEndpoint::ProcessHandshakeAtClient(void* arg) { + auto ep = static_cast(arg); + SocketUniquePtr s(ep->_socket); + RdmaConnect::RunGuard rg((RdmaConnect*)s->_app_connect.get()); + auto rdma_transport = static_cast(s->_transport.get()); + + LOG_IF(INFO, FLAGS_rdma_trace_verbose) + << "Start handshake on " << s->description(); + + std::unique_ptr handshake = CreateClientHandshake(ep); + CHECK(handshake != NULL); + ep->_handshake_version = handshake->ProtocolVersion(); + + // First initialize CQ and QP resources. + ep->_state.store(C_ALLOC_QPCQ, butil::memory_order_relaxed); + if (ep->AllocateResources() < 0) { + LOG(WARNING) << "Fallback to tcp:" << s->description(); + rdma_transport->_rdma_state = RdmaTransport::RDMA_OFF; + ep->_state.store(FALLBACK_TCP, butil::memory_order_release); + return NULL; + } + + // Send hello message to server + ep->_state.store(C_HELLO_SEND, butil::memory_order_relaxed); + if (handshake->SendLocalHello() < 0) { + int saved_errno = errno; + PLOG(WARNING) << "Fail to send hello message to server:" + << s->description(); + s->SetFailed(saved_errno, "Fail to complete rdma handshake from %s: %s", + s->description().c_str(), berror(saved_errno)); + ep->_state.store(FAILED, butil::memory_order_relaxed); + return NULL; + } + + // Receive and parse remote hello. + ep->_state.store(C_HELLO_WAIT, butil::memory_order_relaxed); + ParsedHello remote{}; + bool negotiated = false; + if (handshake->ReceiveAndParseRemoteHello(&remote, &negotiated) < 0) { + int saved_errno = errno; + PLOG(WARNING) << "Fail to receive hello from server:" + << s->description(); + s->SetFailed(saved_errno, "Fail to complete rdma handshake from %s: %s", + s->description().c_str(), berror(saved_errno)); + ep->_state.store(FAILED, butil::memory_order_relaxed); + return NULL; + } + + if (!negotiated) { + LOG(WARNING) << "Fail to negotiate with server, fallback to tcp:" + << s->description(); + rdma_transport->_rdma_state = RdmaTransport::RDMA_OFF; + } else { + ep->ApplyRemoteHello(remote); + ep->_state.store(C_BRINGUP_QP, butil::memory_order_relaxed); + if (ep->BringUpQp(remote.lid, remote.gid, remote.qp_num) < 0) { + LOG(WARNING) << "Fail to bringup QP, fallback to tcp:" + << s->description(); + rdma_transport->_rdma_state = RdmaTransport::RDMA_OFF; + } else { + rdma_transport->_rdma_state = RdmaTransport::RDMA_ON; + } + } + + // Send ACK message to server + ep->_state.store(C_ACK_SEND, butil::memory_order_relaxed); + bool rdma_on = rdma_transport->_rdma_state == RdmaTransport::RDMA_ON; + uint32_t flags = rdma_on ? HELLO_ACK_RDMA_OK : 0; + uint32_t flags_be = butil::HostToNet32(flags); + if (ep->WriteToFd(&flags_be, HELLO_ACK_LEN) < 0) { + int saved_errno = errno; + PLOG(WARNING) << "Fail to send Ack Message to server:" + << s->description(); + s->SetFailed(saved_errno, "Fail to complete rdma handshake from %s: %s", + s->description().c_str(), berror(saved_errno)); + ep->_state.store(FAILED, butil::memory_order_relaxed); + return NULL; + } + + if (rdma_transport->_rdma_state == RdmaTransport::RDMA_ON) { + ep->_state.store(ESTABLISHED, butil::memory_order_release); + LOG_IF(INFO, FLAGS_rdma_trace_verbose) + << "Client handshake ends (use rdma v" << ep->_handshake_version + << ") on " << s->description(); + } else { + ep->_state.store(FALLBACK_TCP, butil::memory_order_release); + LOG_IF(INFO, FLAGS_rdma_trace_verbose) + << "Client handshake ends (use tcp) on " << s->description(); + } + + errno = 0; + + return NULL; +} + +// Server-side handshake entry: the state machine. +// +// S_HELLO_WAIT (read magic + dispatch + hs->ReceiveAndParseRemoteHello) +// | +// v +// [negotiation: ApplyRemoteHello + S_ALLOC_QPCQ + S_BRINGUP_QP] +// | +// v +// S_HELLO_SEND (hs->SendLocalHello) +// | +// v +// S_ACK_WAIT +// | +// v +// ESTABLISHED / FALLBACK_TCP +void* RdmaEndpoint::ProcessHandshakeAtServer(void* arg) { + auto ep = static_cast(arg); + SocketUniquePtr s(ep->_socket); + auto rdma_transport = static_cast(s->_transport.get()); + + LOG_IF(INFO, FLAGS_rdma_trace_verbose) + << "Start handshake on " << s->description(); + + ep->_state.store(S_HELLO_WAIT, butil::memory_order_relaxed); + uint8_t magic[MAGIC_STR_LEN]; + if (ep->ReadFromFd(magic, MAGIC_STR_LEN) < 0) { + int saved_errno = errno; + PLOG(WARNING) << "Fail to read Hello Message from client:" + << s->description() << " " << s->_remote_side; + s->SetFailed(saved_errno, "Fail to complete rdma handshake from %s: %s", + s->description().c_str(), berror(saved_errno)); + ep->_state.store(FAILED, butil::memory_order_relaxed); + return NULL; + } + + // Dispatch on magic, or fall back to TCP + std::unique_ptr handshake = CreateServerHandshakeByMagic(ep, magic); + if (!handshake) { + LOG_IF(INFO, FLAGS_rdma_trace_verbose) + << "It seems that the client does not use RDMA, fallback to TCP:" + << s->description(); + // We need to copy data read back to _socket->_read_buf. + s->_read_buf.append(magic, MAGIC_STR_LEN); + rdma_transport->_rdma_state = RdmaTransport::RDMA_OFF; + // Use release memory order to publish the magic bytes appended + // above to whoever reads `_state == FALLBACK_TCP` (the event + // thread in OnNewDataFromTcp). + ep->_state.store(FALLBACK_TCP, butil::memory_order_release); + ep->TryReadOnTcp(); + return NULL; + } + ep->_handshake_version = handshake->ProtocolVersion(); + + // Magic was already consumed above; the subclass MUST NOT re-read it. + ParsedHello remote{}; + bool negotiated = false; + if (handshake->ReceiveAndParseRemoteHello(&remote, &negotiated) < 0) { + int saved_errno = errno; + PLOG(WARNING) << "Fail to receive hello from client:" + << s->description(); + s->SetFailed(saved_errno, "Fail to complete rdma handshake from %s: %s", + s->description().c_str(), berror(saved_errno)); + ep->_state.store(FAILED, butil::memory_order_relaxed); + return NULL; + } + + if (!negotiated) { + LOG(WARNING) << "Fail to negotiate with client, fallback to tcp:" + << s->description(); + rdma_transport->_rdma_state = RdmaTransport::RDMA_OFF; + } else { + ep->ApplyRemoteHello(remote); + ep->_state.store(S_ALLOC_QPCQ, butil::memory_order_relaxed); + if (ep->AllocateResources() < 0) { + LOG(WARNING) << "Fail to allocate rdma resources, fallback to tcp:" + << s->description(); + rdma_transport->_rdma_state = RdmaTransport::RDMA_OFF; + } else { + ep->_state.store(S_BRINGUP_QP, butil::memory_order_relaxed); + if (ep->BringUpQp(remote.lid, remote.gid, remote.qp_num) < 0) { + LOG(WARNING) << "Fail to bringup QP, fallback to tcp:" + << s->description(); + rdma_transport->_rdma_state = RdmaTransport::RDMA_OFF; + } + } + } + + ep->_state.store(S_HELLO_SEND, butil::memory_order_relaxed); + if (handshake->SendLocalHello() < 0) { + int saved_errno = errno; + PLOG(WARNING) << "Fail to send Hello Message to client:" + << s->description(); + s->SetFailed(saved_errno, "Fail to complete rdma handshake from %s: %s", + s->description().c_str(), berror(saved_errno)); + ep->_state.store(FAILED, butil::memory_order_relaxed); + return NULL; + } + + ep->_state.store(S_ACK_WAIT, butil::memory_order_relaxed); + uint32_t flags_be = 0; + if (ep->ReadFromFd(&flags_be, HELLO_ACK_LEN) < 0) { + int saved_errno = errno; + PLOG(WARNING) << "Fail to read ack message from client:" + << s->description(); + s->SetFailed(saved_errno, "Fail to complete rdma handshake from %s: %s", + s->description().c_str(), berror(saved_errno)); + ep->_state.store(FAILED, butil::memory_order_relaxed); + return NULL; + } + uint32_t flags = butil::NetToHost32(flags_be); + bool client_ack_ok = (flags & HELLO_ACK_RDMA_OK) != 0; + if (client_ack_ok) { + if (rdma_transport->_rdma_state == RdmaTransport::RDMA_OFF) { + // Client asked for RDMA but we are falling back: protocol + // breakdown, abort the connection so the client sees a + // clean error rather than a half-up RDMA channel. + LOG(WARNING) << "Client wants RDMA in ACK but server is in " + << "RDMA_OFF state: " << s->description(); + s->SetFailed(EPROTO, "Fail to complete rdma handshake from %s: %s", + s->description().c_str(), berror(EPROTO)); + ep->_state.store(FAILED, butil::memory_order_relaxed); + return NULL; + } + rdma_transport->_rdma_state = RdmaTransport::RDMA_ON; + ep->_state.store(ESTABLISHED, butil::memory_order_release); + LOG_IF(INFO, FLAGS_rdma_trace_verbose) + << "Server handshake ends (use rdma v" << ep->_handshake_version + << ") on " << s->description(); + } else { + rdma_transport->_rdma_state = RdmaTransport::RDMA_OFF; + ep->_state.store(FALLBACK_TCP, butil::memory_order_release); + LOG_IF(INFO, FLAGS_rdma_trace_verbose) + << "Server handshake ends (use tcp) on " << s->description(); + } + + ep->TryReadOnTcp(); + + return NULL; +} + +bool RdmaEndpoint::IsWritable() const { + if (BAIDU_UNLIKELY(g_skip_rdma_init)) { + // Just for UT + return false; + } + + return _remote_rq_window_size.load(butil::memory_order_relaxed) > 0 && + _sq_window_size.load(butil::memory_order_relaxed) > 0; +} + +// RdmaIOBuf inherits from IOBuf to provide a new function. +// The reason is that we need to use some protected member function of IOBuf. +class RdmaIOBuf : public butil::IOBuf { +friend class RdmaEndpoint; +private: + // Cut the current IOBuf to ibv_sge list and `to' for at most first max_sge + // blocks or first max_len bytes. + // Return: the bytes included in the sglist, or -1 if failed + ssize_t cut_into_sglist_and_iobuf(ibv_sge* sglist, size_t* sge_index, + butil::IOBuf* to, size_t max_sge, + size_t max_len) { + size_t len = 0; + while (*sge_index < max_sge) { + if (len == max_len || _ref_num() == 0) { + break; + } + butil::IOBuf::BlockRef const& r = _ref_at(0); + CHECK(r.length > 0); + const void* start = fetch1(); + uint32_t lkey = GetRegionId(start); + if (lkey == 0) { // get lkey for user registered memory + uint64_t meta = get_first_data_meta(); + if (meta <= UINT_MAX) { + lkey = (uint32_t)meta; + } + } + if (BAIDU_UNLIKELY(lkey == 0)) { // only happens when meta is not specified + lkey = GetLKey((char*)start - r.offset); + } + if (lkey == 0) { + LOG(WARNING) << "Memory not registered for rdma. " + << "Is this iobuf allocated before calling " + << "GlobalRdmaInitializeOrDie? Or just forget to " + << "call RegisterMemoryForRdma for your own buffer?"; + errno = ERDMAMEM; + return -1; + } + size_t i = *sge_index; + if (len + r.length > max_len) { + // Split the block to comply with size for receiving + sglist[i].length = max_len - len; + len = max_len; + } else { + sglist[i].length = r.length; + len += r.length; + } + sglist[i].addr = (uint64_t)start; + sglist[i].lkey = lkey; + cutn(to, sglist[i].length); + (*sge_index)++; + } + return len; + } +}; + +// Note this function is coupled with the implementation of IOBuf +ssize_t RdmaEndpoint::CutFromIOBufList(butil::IOBuf** from, size_t ndata) { + if (BAIDU_UNLIKELY(g_skip_rdma_init)) { + // Just for UT + errno = EAGAIN; + return -1; + } + + CHECK(from != NULL); + CHECK(ndata > 0); + + size_t total_len = 0; + size_t current = 0; + uint32_t remote_rq_window_size = + _remote_rq_window_size.load(butil::memory_order_relaxed); + uint32_t sq_window_size = + _sq_window_size.load(butil::memory_order_relaxed); + ibv_send_wr wr; + int max_sge = GetRdmaMaxSge(); + ibv_sge sglist[max_sge]; + while (current < ndata) { + if (remote_rq_window_size == 0 || sq_window_size == 0) { + // There is no space left in SQ or remote RQ. + if (total_len > 0) { + break; + } else { + errno = EAGAIN; + return -1; + } + } + butil::IOBuf* to = &_sbuf[_sq_current]; + size_t this_len = 0; + + memset(&wr, 0, sizeof(wr)); + wr.sg_list = sglist; + wr.opcode = IBV_WR_SEND_WITH_IMM; + + RdmaIOBuf* data = (RdmaIOBuf*)from[current]; + size_t sge_index = 0; + while (sge_index < (uint32_t)max_sge && + this_len < _remote_recv_block_size) { + if (data->empty()) { + // The current IOBuf is empty, find next one + ++current; + if (current == ndata) { + break; + } + data = (RdmaIOBuf*)from[current]; + continue; + } + + ssize_t len = data->cut_into_sglist_and_iobuf( + sglist, &sge_index, to, max_sge, _remote_recv_block_size - this_len); + if (len < 0) { + return -1; + } + CHECK(len > 0); + this_len += len; + total_len += len; + } + if (this_len == 0) { + continue; + } + + wr.num_sge = sge_index; + + uint32_t imm = _new_rq_wrs.exchange(0, butil::memory_order_relaxed); + wr.imm_data = butil::HostToNet32(imm); + // Avoid too much recv completion event to reduce the cpu overhead + bool solicited = false; + if (remote_rq_window_size == 1 || sq_window_size == 1 || current + 1 >= ndata) { + // Only last message in the write queue or last message in the + // current window will be flagged as solicited. + solicited = true; + } else { + if (_unsolicited > _local_window_capacity / 4) { + // Make sure the recv side can be signaled to return ack + solicited = true; + } else if (_accumulated_ack > _remote_window_capacity / 4) { + // Make sure the recv side can be signaled to handle ack + solicited = true; + } else if (_unsolicited_bytes > 1048576) { + // Make sure the recv side can be signaled when it receives enough data + solicited = true; + } else { + ++_unsolicited; + _unsolicited_bytes += this_len; + _accumulated_ack += imm; + } + } + if (solicited) { + wr.send_flags |= IBV_SEND_SOLICITED; + _unsolicited = 0; + _unsolicited_bytes = 0; + _accumulated_ack = 0; + } + + // Avoid too much send completion event to reduce the CPU overhead + ++_sq_unsignaled; + if (_sq_unsignaled >= _local_window_capacity / 4) { + // Refer to: + // http::www.rdmamojo.com/2014/06/30/working-unsignaled-completions/ + wr.send_flags |= IBV_SEND_SIGNALED; + wr.wr_id = _sq_unsignaled; + _sq_unsignaled = 0; + } + + ibv_send_wr* bad = NULL; + int err = ibv_post_send(_resource->qp, &wr, &bad); + if (err != 0) { + // We use other way to guarantee the Send Queue is not full. + // So we just consider this error as an unrecoverable error. + std::ostringstream oss; + DebugInfo(oss, ", "); + LOG(WARNING) << "Fail to ibv_post_send: " << berror(err) << " " << oss.str(); + errno = err; + return -1; + } + + ++_sq_current; + if (_sq_current == _sq_size - RESERVED_WR_NUM) { + _sq_current = 0; + } + + // Update `_remote_rq_window_size' and `_sq_window_size'. Note that + // `_remote_rq_window_size' and `_sq_window_size' will never be negative. + // Because there is at most one thread can enter this function for each + // Socket, and the other thread of HandleCompletion can only add these + // counters. + remote_rq_window_size = + _remote_rq_window_size.fetch_sub(1, butil::memory_order_relaxed) - 1; + sq_window_size = _sq_window_size.fetch_sub(1, butil::memory_order_relaxed) - 1; + } + + return total_len; +} + +int RdmaEndpoint::SendAck(int num) { + if (_new_rq_wrs.fetch_add(num, butil::memory_order_relaxed) > _remote_window_capacity / 2 && + _sq_imm_window_size > 0) { + return SendImm(_new_rq_wrs.exchange(0, butil::memory_order_relaxed)); + } + return 0; +} + +int RdmaEndpoint::SendImm(uint32_t imm) { + if (imm == 0) { + return 0; + } + + ibv_send_wr wr; + memset(&wr, 0, sizeof(wr)); + wr.opcode = IBV_WR_SEND_WITH_IMM; + wr.imm_data = butil::HostToNet32(imm); + wr.send_flags |= IBV_SEND_SOLICITED | IBV_SEND_SIGNALED; + wr.wr_id = 0; + + ibv_send_wr* bad = NULL; + int err = ibv_post_send(_resource->qp, &wr, &bad); + if (err != 0) { + std::ostringstream oss; + DebugInfo(oss, ", "); + // We use other way to guarantee the Send Queue is not full. + // So we just consider this error as an unrecoverable error. + LOG(WARNING) << "Fail to ibv_post_send: " << berror(err) << " " << oss.str(); + return -1; + } + + // `_sq_imm_window_size' will never be negative. + // Because IMM can only be sent if + // `_sq_imm_window_size` is greater than 0. + _sq_imm_window_size -= 1; + return 0; +} + +ssize_t RdmaEndpoint::HandleCompletion(ibv_wc& wc) { + bool zerocopy = FLAGS_rdma_recv_zerocopy; + switch (wc.opcode) { + case IBV_WC_SEND: { // send completion + if (0 == wc.wr_id) { + _sq_imm_window_size += 1; + // If there are any unacknowledged recvs, send an ack. + SendAck(0); + return 0; + } + // Update SQ window. + uint16_t wnd_to_update = wc.wr_id; + for (uint16_t i = 0; i < wnd_to_update; ++i) { + _sbuf[_sq_sent++].clear(); + if (_sq_sent == _sq_size - RESERVED_WR_NUM) { + _sq_sent = 0; + } + } + butil::subtle::MemoryBarrier(); + + _sq_window_size.fetch_add(wnd_to_update, butil::memory_order_relaxed); + if (_remote_rq_window_size.load(butil::memory_order_relaxed) >= + _local_window_capacity / 8) { + // Do not wake up writing thread right after polling IBV_WC_SEND. + // Otherwise the writing thread may switch to background too quickly. + _socket->WakeAsEpollOut(); + } + return 0; + } + case IBV_WC_RECV: { // recv completion + // Please note that only the first wc.byte_len bytes is valid + if (wc.byte_len > 0) { + if (wc.byte_len < (uint32_t)FLAGS_rdma_zerocopy_min_size) { + zerocopy = false; + } + CHECK_NE(_state.load(butil::memory_order_acquire), FALLBACK_TCP); + if (zerocopy) { + _rbuf[_rq_received].cutn(&_socket->_read_buf, wc.byte_len); + } else { + // Copy data when the receive data is really small + _socket->_read_buf.append(_rbuf_data[_rq_received], wc.byte_len); + } + } + if (0 != (wc.wc_flags & IBV_WC_WITH_IMM) && wc.imm_data > 0) { + // Update window + uint32_t acks = butil::NetToHost32(wc.imm_data); + uint32_t wnd_thresh = _local_window_capacity / 8; + uint32_t remote_rq_window_size = + _remote_rq_window_size.fetch_add(acks, butil::memory_order_relaxed); + if (_sq_window_size.load(butil::memory_order_relaxed) > 0 && + (remote_rq_window_size >= wnd_thresh || acks >= wnd_thresh)) { + // Do not wake up writing thread right after _remote_rq_window_size > 0. + // Otherwise the writing thread may switch to background too quickly. + _socket->WakeAsEpollOut(); + } + } + // We must re-post recv WR + if (PostRecv(1, zerocopy) < 0) { + return -1; + } + if (wc.byte_len > 0) { + SendAck(1); + } + return wc.byte_len; + } + default: + // Some driver bugs may lead to unexpected completion opcode. + // If this happens, please update your driver. + CHECK(false) << "This should not happen. Got a completion with opcode=" + << wc.opcode; + return -1; + } + return 0; +} + +int RdmaEndpoint::DoPostRecv(void* block, size_t block_size) { + ibv_recv_wr wr; + memset(&wr, 0, sizeof(wr)); + ibv_sge sge; + sge.addr = (uint64_t)block; + sge.length = block_size; + sge.lkey = GetRegionId(block); + wr.num_sge = 1; + wr.sg_list = &sge; + + ibv_recv_wr* bad = NULL; + int err = ibv_post_recv(_resource->qp, &wr, &bad); + if (err != 0) { + LOG(WARNING) << "Fail to ibv_post_recv: " << berror(err); + return -1; + } + return 0; +} + +int RdmaEndpoint::PostRecv(uint32_t num, bool zerocopy) { + // We do the post repeatedly from the _rbuf[_rq_received]. + while (num > 0) { + if (zerocopy) { + _rbuf[_rq_received].clear(); + butil::IOBufAsZeroCopyOutputStream os(&_rbuf[_rq_received], + g_rdma_recv_block_size + IOBUF_BLOCK_HEADER_LEN); + int size = 0; + if (!os.Next(&_rbuf_data[_rq_received], &size)) { + // Memory is not enough for preparing a block + PLOG(WARNING) << "Fail to allocate rbuf"; + return -1; + } else { + CHECK_EQ(static_cast(size), g_rdma_recv_block_size); + } + } + if (DoPostRecv(_rbuf_data[_rq_received], g_rdma_recv_block_size) < 0) { + _rbuf[_rq_received].clear(); + return -1; + } + --num; + ++_rq_received; + if (_rq_received == _rq_size) { + _rq_received = 0; + } + }; + return 0; +} + +static ibv_qp* AllocateQp(ibv_cq* send_cq, ibv_cq* recv_cq, uint32_t sq_size, uint32_t rq_size) { + ibv_qp_init_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.send_cq = send_cq; + attr.recv_cq = recv_cq; + attr.cap.max_send_wr = sq_size; + attr.cap.max_recv_wr = rq_size; + attr.cap.max_send_sge = GetRdmaMaxSge(); + attr.cap.max_recv_sge = 1; + attr.qp_type = IBV_QPT_RC; + return IbvCreateQp(GetRdmaPd(), &attr); +} + +static RdmaResource* AllocateQpCq(uint16_t sq_size, uint16_t rq_size) { + std::unique_ptr resource(new RdmaResource); + if (!FLAGS_rdma_use_polling) { + resource->comp_channel = IbvCreateCompChannel(GetRdmaContext()); + if (NULL == resource->comp_channel) { + PLOG(WARNING) << "Fail to create comp channel for CQ"; + return NULL; + } + + if (butil::make_close_on_exec(resource->comp_channel->fd) < 0) { + PLOG(WARNING) << "Fail to set comp channel close-on-exec"; + return NULL; + } + if (butil::make_non_blocking(resource->comp_channel->fd) < 0) { + PLOG(WARNING) << "Fail to set comp channel nonblocking"; + return NULL; + } + + resource->send_cq = IbvCreateCq(GetRdmaContext(), FLAGS_rdma_prepared_qp_size, + NULL, resource->comp_channel, GetRdmaCompVector()); + if (NULL == resource->send_cq) { + PLOG(WARNING) << "Fail to create send CQ"; + return NULL; + } + + resource->recv_cq = IbvCreateCq(GetRdmaContext(), FLAGS_rdma_prepared_qp_size, + NULL, resource->comp_channel, GetRdmaCompVector()); + if (NULL == resource->recv_cq) { + PLOG(WARNING) << "Fail to create recv CQ"; + return NULL; + } + + resource->qp = AllocateQp(resource->send_cq, resource->recv_cq, sq_size, rq_size); + if (NULL == resource->qp) { + PLOG(WARNING) << "Fail to create QP"; + return NULL; + } + } else { + resource->polling_cq = + IbvCreateCq(GetRdmaContext(), 2 * FLAGS_rdma_prepared_qp_size, NULL, NULL, 0); + if (NULL == resource->polling_cq) { + PLOG(WARNING) << "Fail to create polling CQ"; + return NULL; + } + resource->qp = AllocateQp(resource->polling_cq, + resource->polling_cq, + sq_size, rq_size); + if (NULL == resource->qp) { + PLOG(WARNING) << "Fail to create QP"; + return NULL; + } + } + + return resource.release(); +} + +int RdmaEndpoint::AllocateResources() { + if (BAIDU_UNLIKELY(g_skip_rdma_init)) { + // For UT + return 0; + } + + CHECK(_resource == NULL); + + if (_sq_size <= FLAGS_rdma_prepared_qp_size && + _rq_size <= FLAGS_rdma_prepared_qp_size) { + BAIDU_SCOPED_LOCK(*g_rdma_resource_mutex); + if (g_rdma_resource_list) { + _resource = g_rdma_resource_list; + g_rdma_resource_list = g_rdma_resource_list->next; + } + } + if (!_resource) { + _resource = AllocateQpCq(_sq_size, _rq_size); + } else { + _resource->next = NULL; + } + if (!_resource) { + return -1; + } + + if (!FLAGS_rdma_use_polling) { + if (0 != ReqNotifyCq(true)) { + return -1; + } + if (0 != ReqNotifyCq(false)) { + return -1; + } + + SocketOptions options; + options.user = this; + options.keytable_pool = _socket->_keytable_pool; + options.fd = _resource->comp_channel->fd; + options.on_edge_triggered_events = PollCq; + if (Socket::Create(options, &_cq_sid) < 0) { + PLOG(WARNING) << "Fail to create socket for cq"; + return -1; + } + } else { + SocketOptions options; + options.user = this; + options.keytable_pool = _socket->_keytable_pool; + if (Socket::Create(options, &_cq_sid) < 0) { + PLOG(WARNING) << "Fail to create socket for cq"; + return -1; + } + PollerAddCqSid(); + } + + _sbuf.resize(_sq_size - RESERVED_WR_NUM); + if (_sbuf.size() != _sq_size - RESERVED_WR_NUM) { + return -1; + } + _rbuf.resize(_rq_size); + if (_rbuf.size() != _rq_size) { + return -1; + } + _rbuf_data.resize(_rq_size, NULL); + if (_rbuf_data.size() != _rq_size) { + return -1; + } + + return 0; +} + +int RdmaEndpoint::BringUpQp(uint16_t lid, ibv_gid gid, uint32_t qp_num) { + if (BAIDU_UNLIKELY(g_skip_rdma_init)) { + // For UT + return 0; + } + + ibv_qp_attr attr; + + attr.qp_state = IBV_QPS_INIT; + attr.pkey_index = 0; // TODO: support more pkey use in future + attr.port_num = GetRdmaPortNum(); + attr.qp_access_flags = IBV_ACCESS_REMOTE_WRITE; + int err = IbvModifyQp(_resource->qp, &attr, (ibv_qp_attr_mask)( + IBV_QP_STATE | + IBV_QP_PKEY_INDEX | + IBV_QP_PORT | + IBV_QP_ACCESS_FLAGS)); + if (err != 0) { + LOG(WARNING) << "Fail to modify QP from RESET to INIT: " << berror(err); + return -1; + } + + if (FLAGS_rdma_ece) { + struct ibv_ece ece; + int err = IbvQueryEce(_resource->qp, &ece); + if (err != 0) { + LOG(WARNING) << "Fail to IbvQueryEce: " << berror(err); + return -1; + } + // ToDo: should check if remote qp support ece + err = IbvSetEce(_resource->qp, &ece); + if (err != 0) { + LOG(WARNING) << "Fail to IbvSetEce: " << berror(err); + return -1; + } + } + + if (PostRecv(_rq_size, true) < 0) { + PLOG(WARNING) << "Fail to post recv wr"; + return -1; + } + + attr.qp_state = IBV_QPS_RTR; + attr.path_mtu = IBV_MTU_1024; // TODO: support more mtu in future + attr.ah_attr.grh.dgid = gid; + attr.ah_attr.grh.flow_label = 0; + attr.ah_attr.grh.sgid_index = GetRdmaGidIndex(); + attr.ah_attr.grh.hop_limit = MAX_HOP_LIMIT; + attr.ah_attr.grh.traffic_class = 0; + attr.ah_attr.dlid = lid; + attr.ah_attr.sl = 0; + attr.ah_attr.src_path_bits = 0; + attr.ah_attr.static_rate = 0; + attr.ah_attr.is_global = 1; + attr.ah_attr.port_num = GetRdmaPortNum(); + attr.dest_qp_num = qp_num; + attr.rq_psn = 0; + attr.max_dest_rd_atomic = 0; + attr.min_rnr_timer = 0; // We do not allow rnr error + err = IbvModifyQp(_resource->qp, &attr, (ibv_qp_attr_mask)( + IBV_QP_STATE | + IBV_QP_PATH_MTU | + IBV_QP_MIN_RNR_TIMER | + IBV_QP_AV | + IBV_QP_MAX_DEST_RD_ATOMIC | + IBV_QP_DEST_QPN | + IBV_QP_RQ_PSN)); + if (err != 0) { + LOG(WARNING) << "Fail to modify QP from INIT to RTR: " << berror(err); + return -1; + } + + attr.qp_state = IBV_QPS_RTS; + attr.timeout = TIMEOUT; + attr.retry_cnt = RETRY_CNT; + attr.rnr_retry = 0; // We do not allow rnr error + attr.sq_psn = 0; + attr.max_rd_atomic = 0; + err = IbvModifyQp(_resource->qp, &attr, (ibv_qp_attr_mask)( + IBV_QP_STATE | + IBV_QP_RNR_RETRY | + IBV_QP_RETRY_CNT | + IBV_QP_TIMEOUT | + IBV_QP_SQ_PSN | + IBV_QP_MAX_QP_RD_ATOMIC)); + if (err != 0) { + LOG(WARNING) << "Fail to modify QP from RTR to RTS: " << berror(err); + return -1; + } + + return 0; +} + +static void DeallocateCq(ibv_cq* cq) { + if (NULL == cq) { + return; + } + + int err = IbvDestroyCq(cq); + LOG_IF(WARNING, 0 != err) << "Fail to destroy CQ: " << berror(err); +} + +static int DrainCq(ibv_cq* cq) { + if (NULL == cq) { + return 0; + } + + ibv_wc wc; + int ret; + do { + ret = ibv_poll_cq(cq, 1, &wc); + } while (ret > 0); + + LOG_IF(ERROR, ret < 0) << "drain CQ failed: " << ret; + return ret; +} + +void RdmaEndpoint::DeallocateResources() { + if (!_resource) { + return; + } + if (FLAGS_rdma_use_polling) { + PollerRemoveCqSid(); + } + bool move_to_rdma_resource_list = false; + if (_sq_size <= FLAGS_rdma_prepared_qp_size && + _rq_size <= FLAGS_rdma_prepared_qp_size && + FLAGS_rdma_prepared_qp_cnt > 0) { + ibv_qp_attr attr; + attr.qp_state = IBV_QPS_RESET; + if (IbvModifyQp(_resource->qp, &attr, IBV_QP_STATE) == 0) { + move_to_rdma_resource_list = true; + } + } + + if (NULL != _resource->send_cq) { + IbvAckCqEvents(_resource->send_cq, _send_cq_events); + } + if (NULL != _resource->recv_cq) { + IbvAckCqEvents(_resource->recv_cq, _recv_cq_events); + } + + bool remove_consumer = true; +_reclaim: + if (!move_to_rdma_resource_list) { + if (NULL != _resource->qp) { + int err = IbvDestroyQp(_resource->qp); + LOG_IF(WARNING, 0 != err) << "Fail to destroy QP: " << berror(err); + _resource->qp = NULL; + } + + DeallocateCq(_resource->polling_cq); + DeallocateCq(_resource->send_cq); + DeallocateCq(_resource->recv_cq); + + if (NULL != _resource->comp_channel) { + // Destroy send_comp_channel will destroy this fd, + // so that we should remove it from epoll fd first + int fd = _resource->comp_channel->fd; + GetGlobalEventDispatcher(fd, _socket->_io_event.bthread_tag()).RemoveConsumer(fd); + remove_consumer = false; + int err = IbvDestroyCompChannel(_resource->comp_channel); + LOG_IF(WARNING, 0 != err) << "Fail to destroy CQ channel: " << berror(err); + + } + + _resource->polling_cq = NULL; + _resource->send_cq = NULL; + _resource->recv_cq = NULL; + _resource->comp_channel = NULL; + delete _resource; + _resource = NULL; + } + + if (INVALID_SOCKET_ID != _cq_sid) { + SocketUniquePtr s; + if (Socket::Address(_cq_sid, &s) == 0) { + if (remove_consumer) { + s->_io_event.RemoveConsumer(s->_fd); + } + s->_user = NULL; // Do not release user (this RdmaEndpoint). + s->_fd = -1; // Already remove fd from epoll fd. + s->SetFailed(); + } + } + + if (move_to_rdma_resource_list) { + // When a QP is moved to the RESET state, all associated send and + // receive queues are flushed, meaning any outstanding WRs are effectively + // abandoned by the hardware. + // + // However, the CQ associated with that QP is *not* cleared automatically, + // meaning that it will still contain entries for WRs that completed before + // the reset. + // + // The application should finish polling the CQ to remove these obsolete + // entries before reusing the QP. + int ret = DrainCq(_resource->polling_cq); + ret += DrainCq(_resource->send_cq); + ret += DrainCq(_resource->recv_cq); + if (ret < 0) { + move_to_rdma_resource_list = false; + goto _reclaim; + } + + BAIDU_SCOPED_LOCK(*g_rdma_resource_mutex); + _resource->next = g_rdma_resource_list; + g_rdma_resource_list = _resource; + } +} + +static const int MAX_CQ_EVENTS = 128; + +int RdmaEndpoint::GetAndAckEvents(SocketUniquePtr& s) { + void* context = NULL; + ibv_cq* cq = NULL; + while (true) { + if (IbvGetCqEvent(_resource->comp_channel, &cq, &context) != 0) { + if (errno != EAGAIN) { + const int saved_errno = errno; + PLOG(ERROR) << "Fail to get cq event from " << s->description(); + s->SetFailed(saved_errno, "Fail to get cq event from %s: %s", + s->description().c_str(), berror(saved_errno)); + return -1; + } + break; + } + if (cq == _resource->send_cq) { + ++_send_cq_events; + } else if (cq == _resource->recv_cq) { + ++_recv_cq_events; + } else { + // Unexpected CQ event that does not belong to + // this endpoint's send/recv CQs. + LOG(WARNING) << "Unexpected CQ event from cq=" << cq + << " of " << s->description(); + // Acknowledge this single event immediately + // to avoid leaking unacknowledged events. + IbvAckCqEvents(cq, 1); + } + } + if (_send_cq_events >= MAX_CQ_EVENTS) { + IbvAckCqEvents(_resource->send_cq, _send_cq_events); + _send_cq_events = 0; + } + if (_recv_cq_events >= MAX_CQ_EVENTS) { + IbvAckCqEvents(_resource->recv_cq, _recv_cq_events); + _recv_cq_events = 0; + } + return 0; +} + +int RdmaEndpoint::ReqNotifyCq(bool send_cq) { + errno = ibv_req_notify_cq( + send_cq ? _resource->send_cq : _resource->recv_cq, + send_cq ? 0 : 1); + if (0 != errno) { + const int saved_errno = errno; + PLOG(WARNING) << "Fail to arm " << (send_cq ? "send" : "recv") + << " CQ comp channel from " << _socket->description(); + _socket->SetFailed(saved_errno, "Fail to arm %s CQ channel from %s: %s", + send_cq ? "send" : "recv", _socket->description().c_str(), + berror(saved_errno)); + return -1; + } + + return 0; +} + +void RdmaEndpoint::PollCq(Socket* m) { + RdmaEndpoint* ep = static_cast(m->user()); + if (!ep) { + return; + } + + SocketUniquePtr s; + if (Socket::Address(ep->_socket->id(), &s) < 0) { + return; + } + auto* rdma_transport = static_cast(s->_transport.get()); + CHECK(ep == rdma_transport->_rdma_ep); + + bool send = false; + ibv_cq* cq = ep->_resource->recv_cq; + + if (!FLAGS_rdma_use_polling) { + if (ep->GetAndAckEvents(s) < 0) { + return; + } + } else { + // Polling is considered as non-send, so no need to change `send'. + // Only need to poll polling_cq. + cq = ep->_resource->polling_cq; + } + + int progress = Socket::PROGRESS_INIT; + bool notified = false; + InputMessageClosure last_msg; + ibv_wc wc[FLAGS_rdma_cqe_poll_once]; + while (true) { + int cnt = ibv_poll_cq(cq, FLAGS_rdma_cqe_poll_once, wc); + if (cnt < 0) { + const int saved_errno = errno; + PLOG(WARNING) << "Fail to poll cq: " << s->description(); + s->SetFailed(saved_errno, "Fail to poll cq from %s: %s", + s->description().c_str(), berror(saved_errno)); + return; + } + if (cnt == 0) { + if (FLAGS_rdma_use_polling) { + return; + } + + if (!send) { + // It's send cq's turn. + send = true; + cq = ep->_resource->send_cq; + continue; + } + // `recv_cq' and `send_cq' have been polled. + if (!notified) { + // Since RDMA only provides one shot event, we have to call the + // notify function every time. Because there is a possibility + // that the event arrives after the poll but before the notify, + // we should re-poll the CQ once after the notify to check if + // there is an available CQE. + if (0 != ep->ReqNotifyCq(true)) { + return; + } + if (0 != ep->ReqNotifyCq(false)) { + return; + } + notified = true; + continue; + } + if (!m->MoreReadEvents(&progress)) { + break; + } + + if (0 != ep->GetAndAckEvents(s)) { + return; + } + + // Restart polling from `recv_cq'. + send = false; + cq = ep->_resource->recv_cq; + notified = false; + continue; + } + notified = false; + + ssize_t bytes = 0; + for (int i = 0; i < cnt; ++i) { + if (s->Failed()) { + return; + } + + if (wc[i].status != IBV_WC_SUCCESS) { + PLOG(WARNING) << "Fail to handle RDMA completion, error status(" + << wc[i].status << "): " << s->description(); + s->SetFailed(ERDMA, "RDMA completion error(%d) from %s: %s", + wc[i].status, s->description().c_str(), berror(ERDMA)); + continue; + } + + ssize_t nr = ep->HandleCompletion(wc[i]); + if (nr < 0) { + const int saved_errno = errno; + PLOG(WARNING) << "Fail to handle RDMA completion: " << s->description(); + s->SetFailed(saved_errno, "Fail to handle rdma completion from %s: %s", + s->description().c_str(), berror(saved_errno)); + } else if (nr > 0) { + bytes += nr; + } + } + // Send CQE has no messages to process. + if (send) { + continue; + } + + // Just call PrcessNewMessage once for all of these CQEs. + // Otherwise it may call too many bthread_flush to affect performance. + const int64_t received_us = butil::cpuwide_time_us(); + const int64_t base_realtime = butil::gettimeofday_us() - received_us; + InputMessenger* messenger = static_cast(s->user()); + if (messenger->ProcessNewMessage( + s.get(), bytes, false, received_us, base_realtime, last_msg) < 0) { + return; + } + } +} + +std::string RdmaEndpoint::GetStateStr() const { + switch (_state.load(butil::memory_order_relaxed)) { + case UNINIT: return "UNINIT"; + case C_ALLOC_QPCQ: return "C_ALLOC_QPCQ"; + case C_HELLO_SEND: return "C_HELLO_SEND"; + case C_HELLO_WAIT: return "C_HELLO_WAIT"; + case C_BRINGUP_QP: return "C_BRINGUP_QP"; + case C_ACK_SEND: return "C_ACK_SEND"; + case S_HELLO_WAIT: return "S_HELLO_WAIT"; + case S_ALLOC_QPCQ: return "S_ALLOC_QPCQ"; + case S_BRINGUP_QP: return "S_BRINGUP_QP"; + case S_HELLO_SEND: return "S_HELLO_SEND"; + case S_ACK_WAIT: return "S_ACK_WAIT"; + case ESTABLISHED: return "ESTABLISHED"; + case FALLBACK_TCP: return "FALLBACK_TCP"; + case FAILED: return "FAILED"; + default: return "UNKNOWN"; + } +} + +void RdmaEndpoint::DebugInfo(std::ostream& os, butil::StringPiece connector) const { + os << "rdma_state=ON" + << connector << "handshake_state=" << GetStateStr() + << connector << "handshake_version=" << static_cast(_handshake_version) + << connector << "rdma_sq_imm_window_size=" << _sq_imm_window_size + << connector << "rdma_remote_rq_window_size=" << _remote_rq_window_size.load(butil::memory_order_relaxed) + << connector << "rdma_sq_window_size=" << _sq_window_size.load(butil::memory_order_relaxed) + << connector << "rdma_local_window_capacity=" << _local_window_capacity + << connector << "rdma_remote_window_capacity=" << _remote_window_capacity + << connector << "rdma_sbuf_head=" << _sq_current + << connector << "rdma_sbuf_tail=" << _sq_sent + << connector << "rdma_rbuf_head=" << _rq_received + << connector << "rdma_unacked_rq_wr=" << _new_rq_wrs.load(butil::memory_order_relaxed) + << connector << "rdma_received_ack=" << _accumulated_ack + << connector << "rdma_unsolicited_sent=" << _unsolicited + << connector << "rdma_unsignaled_sq_wr=" << _sq_unsignaled; +} + +int RdmaEndpoint::GlobalInitialize() { + g_rdma_recv_block_size = GetRdmaBlockSize() - IOBUF_BLOCK_HEADER_LEN; + if (g_rdma_recv_block_size <= 0) { + LOG(ERROR) << "rdma_recv_block_type incorrect " + << "(valid value: default/large/huge)"; + errno = EINVAL; + return -1; + } + + g_rdma_resource_mutex = new butil::Mutex; + for (int i = 0; i < FLAGS_rdma_prepared_qp_cnt; ++i) { + RdmaResource* res = AllocateQpCq(FLAGS_rdma_prepared_qp_size, + FLAGS_rdma_prepared_qp_size); + if (!res) { + return -1; + } + res->next = g_rdma_resource_list; + g_rdma_resource_list = res; + } + + if (FLAGS_rdma_use_polling) { + _poller_groups = std::vector(FLAGS_task_group_ntags); + } + + return 0; +} + +void RdmaEndpoint::GlobalRelease() { + if (g_rdma_resource_mutex) { + BAIDU_SCOPED_LOCK(*g_rdma_resource_mutex); + while (g_rdma_resource_list) { + RdmaResource* res = g_rdma_resource_list; + g_rdma_resource_list = g_rdma_resource_list->next; + delete res; + } + } + // release polling mode at exit or call RdmaEndpoint::PollingModeRelease + // explicitly + if (FLAGS_rdma_use_polling) { + for (int i = 0; i < FLAGS_task_group_ntags; ++i) { + PollingModeRelease(i); + } + } +} + +std::vector RdmaEndpoint::_poller_groups; + +int RdmaEndpoint::PollingModeInitialize(bthread_tag_t tag, + std::function callback, + std::function init_fn, + std::function release_fn) { + if (!FLAGS_rdma_use_polling) { + return 0; + } + auto& group = _poller_groups[tag]; + auto& pollers = group.pollers; + auto& running = group.running; + bool expected = false; + if (!running.compare_exchange_strong(expected, true)) { + return 0; + } + struct FnArgs { + Poller* poller; + std::atomic* running; + }; + auto fn = [](void* p) -> void* { + std::unique_ptr args(static_cast(p)); + auto poller = args->poller; + auto running = args->running; + std::unordered_set cq_sids; + CqSidOp op; + + if (poller->init_fn) { + poller->init_fn(); + } + + while (running->load(std::memory_order_relaxed)) { + while (poller->op_queue.Dequeue(op)) { + if (op.type == CqSidOp::ADD) { + cq_sids.emplace(op.sid); + } else if (op.type == CqSidOp::REMOVE) { + cq_sids.erase(op.sid); + } + } + for (auto sid : cq_sids) { + SocketUniquePtr s; + if (Socket::Address(sid, &s) < 0) { + continue; + } + PollCq(s.get()); + } + if (poller->callback) { + poller->callback(); + } + if (FLAGS_rdma_poller_yield) { + bthread_yield(); + } + } + + if (poller->release_fn) { + poller->release_fn(); + } + + return nullptr; + }; + for (int i = 0; i < FLAGS_rdma_poller_num; ++i) { + auto args = new FnArgs{&pollers[i], &running}; + auto attr = FLAGS_rdma_disable_bthread ? BTHREAD_ATTR_PTHREAD + : BTHREAD_ATTR_NORMAL; + attr.tag = tag; + bthread_attr_set_name(&attr, "RdmaPolling"); + pollers[i].callback = callback; + pollers[i].init_fn = init_fn; + pollers[i].release_fn = release_fn; + auto rc = bthread_start_background(&pollers[i].tid, &attr, fn, args); + if (rc != 0) { + LOG(ERROR) << "Fail to start rdma polling bthread"; + return -1; + } + } + return 0; +} + +void RdmaEndpoint::PollingModeRelease(bthread_tag_t tag) { + if (!FLAGS_rdma_use_polling) { + return; + } + auto& group = _poller_groups[tag]; + auto& pollers = group.pollers; + auto& running = group.running; + running.store(false, std::memory_order_relaxed); + for (int i = 0; i < FLAGS_rdma_poller_num; ++i) { + bthread_join(pollers[i].tid, NULL); + } +} + +void RdmaEndpoint::PollerAddCqSid() { + auto index = butil::fmix32(_cq_sid) % FLAGS_rdma_poller_num; + auto& group = _poller_groups[bthread_self_tag()]; + auto& pollers = group.pollers; + auto& poller = pollers[index]; + if (INVALID_SOCKET_ID != _cq_sid) { + poller.op_queue.Enqueue(CqSidOp{_cq_sid, CqSidOp::ADD}); + } +} + +void RdmaEndpoint::PollerRemoveCqSid() { + auto index = butil::fmix32(_cq_sid) % FLAGS_rdma_poller_num; + auto& group = _poller_groups[bthread_self_tag()]; + auto& pollers = group.pollers; + auto& poller = pollers[index]; + if (INVALID_SOCKET_ID != _cq_sid) { + poller.op_queue.Enqueue(CqSidOp{_cq_sid, CqSidOp::REMOVE}); + } +} + +} // namespace rdma +} // namespace brpc + +#endif // if BRPC_WITH_RDMA diff --git a/src/brpc/rdma/rdma_endpoint.h b/src/brpc/rdma/rdma_endpoint.h new file mode 100644 index 0000000..41c3382 --- /dev/null +++ b/src/brpc/rdma/rdma_endpoint.h @@ -0,0 +1,365 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_RDMA_ENDPOINT_H +#define BRPC_RDMA_ENDPOINT_H + +#if BRPC_WITH_RDMA +#include +#include +#include +#include +#include +#include +#include "butil/atomicops.h" +#include "butil/iobuf.h" +#include "butil/macros.h" +#include "butil/containers/mpsc_queue.h" +#include "brpc/socket.h" + + +namespace brpc { +class Socket; +namespace rdma { + +DECLARE_bool(rdma_use_polling); +DECLARE_int32(rdma_poller_num); +DECLARE_bool(rdma_disable_bthread); + +class RdmaHandshakeClientV2; +class RdmaHandshakeServerV2; +class RdmaHandshakeClientV3; +class RdmaHandshakeServerV3; +struct ParsedHello; +class RdmaHello; +class RdmaEndpoint; +namespace v2_wire { + int ReadBodyAndNegotiate(RdmaEndpoint* ep, ParsedHello* remote, bool* negotiated); + int DrainBytes(RdmaEndpoint* ep, size_t n); +} // namespace v2_wire + +namespace v3_wire { + void FillLocalRdmaHello(const RdmaEndpoint* ep, RdmaHello* msg); + int ReadAndParseV3Hello(RdmaEndpoint* ep, RdmaHello* out); + int WriteV3Hello(RdmaEndpoint* ep, const RdmaHello& msg); +} // namespace v3_wire + +class RdmaConnect : public AppConnect { +public: + void StartConnect(const Socket* socket, + void (*done)(int err, void* data), void* data) override; + void StopConnect(Socket*) override; + struct RunGuard { + RunGuard(RdmaConnect* rc) { this_rc = rc; } + ~RunGuard() { if (this_rc) this_rc->Run(); } + RdmaConnect* this_rc; + }; + +private: + void Run(); + void (*_done)(int, void*){NULL}; + void* _data{NULL}; +}; + +struct RdmaResource { + RdmaResource* next{NULL}; + ibv_qp* qp{NULL}; + // For polling mode. + ibv_cq* polling_cq{NULL}; + // For event mode. + ibv_cq* send_cq{NULL}; + ibv_cq* recv_cq{NULL}; + ibv_comp_channel* comp_channel{NULL}; + RdmaResource() = default; + ~RdmaResource(); + DISALLOW_COPY_AND_ASSIGN(RdmaResource); +}; + +class BAIDU_CACHELINE_ALIGNMENT RdmaEndpoint : public SocketUser { +friend class RdmaConnect; +friend class Socket; +friend class RdmaHandshakeClientV2; +friend class RdmaHandshakeServerV2; +friend class RdmaHandshakeClientV3; +friend class RdmaHandshakeServerV3; +friend int v2_wire::ReadBodyAndNegotiate(RdmaEndpoint*, ParsedHello*, bool*); +friend int v2_wire::DrainBytes(RdmaEndpoint*, size_t); +friend void v3_wire::FillLocalRdmaHello(const RdmaEndpoint*, RdmaHello*); +friend int v3_wire::ReadAndParseV3Hello(RdmaEndpoint*, RdmaHello*); +friend int v3_wire::WriteV3Hello(RdmaEndpoint*, const RdmaHello&); +public: + explicit RdmaEndpoint(Socket* s); + ~RdmaEndpoint() override; + + // Global initialization + // Return 0 if success, -1 if failed and errno set + static int GlobalInitialize(); + + static void GlobalRelease(); + + // Reset the endpoint (for next use) + void Reset(); + + // Cut data from the given IOBuf list and use RDMA to send + // Return bytes cut if success, -1 if failed and errno set + ssize_t CutFromIOBufList(butil::IOBuf** data, size_t ndata); + + // Whether the endpoint can send more data + bool IsWritable() const; + + // For debug + void DebugInfo(std::ostream& os, + butil::StringPiece connector = "\n") const; + + // Callback when there is new epollin event on TCP fd + static void OnNewDataFromTcp(Socket* m); + + // Initialize polling mode + static int PollingModeInitialize(bthread_tag_t tag, + std::function callback, + std::function init_fn, + std::function release_fn); + + static void PollingModeRelease(bthread_tag_t tag); + +private: + enum State { + UNINIT = 0x0, + C_ALLOC_QPCQ = 0x1, + C_HELLO_SEND = 0x2, + C_HELLO_WAIT = 0x3, + C_BRINGUP_QP = 0x4, + C_ACK_SEND = 0x5, + S_HELLO_WAIT = 0x11, + S_ALLOC_QPCQ = 0x12, + S_BRINGUP_QP = 0x13, + S_HELLO_SEND = 0x14, + S_ACK_WAIT = 0x15, + ESTABLISHED = 0x100, + FALLBACK_TCP = 0x200, + FAILED = 0x300 + }; + + // Process handshake at the client + static void* ProcessHandshakeAtClient(void* arg); + + // Process handshake at the server + static void* ProcessHandshakeAtServer(void* arg); + + // Allocate resources + // Return 0 if success, -1 if failed and errno set + int AllocateResources(); + + // Release resources + void DeallocateResources(); + + // Send Imm data to the remote side + // Arguments: + // imm: imm data in the WR + // Return: + // 0: success + // -1: failed, errno set + int SendImm(uint32_t imm); + + // Try to send pure ACK to the remote side + // Arguments: + // num: the number of rq entry received + // Return: + // 0: success + // -1: failed, errno set + int SendAck(int num); + + // Handle CQE + // If wc is not RDMA RECV event: + // return 0 if success, -1 if failed and errno set + // If wc is RDMA RECV event: + // return bytes appended if success, -1 if failed and errno set + ssize_t HandleCompletion(ibv_wc& wc); + + // Post a given number of WRs to Recv Queue + // If zerocopy is true, reallocate block. + // Return 0 if success, -1 if failed and errno set + int PostRecv(uint32_t num, bool zerocopy); + + // Post a WR pointing to the block to the local Recv Queue + // Arguments: + // block: the addr to receive data (ibv_sge.addr) + // block_size: the maximum length can be received (ibv_sge.length) + // Return: + // 0: success + // -1: failed, errno set + int DoPostRecv(void* block, size_t block_size); + + // Read at most len bytes from fd in _socket to data + // wait for _read_butex if encounter EAGAIN + // return -1 if encounter other errno (including EOF) + int ReadFromFd(void* data, size_t len); + int ReadFromFd(butil::IOPortal* data, size_t len); + + + // Write at most len bytes from data to fd in _socket + // wait for _epollout_butex if encounter EAGAIN + // return -1 if encounter other errno + int WriteToFd(void* data, size_t len); + + // Write data to fd in _socket. + // wait for _epollout_butex if encounter EAGAIN. + // return -1 if encounter other errno. + int WriteToFd(butil::IOBuf* data); + + // Copy negotiated remote parameters into the endpoint and compute + // the SQ/RQ window capacities. Called by both + // ProcessHandshakeAtClient and ProcessHandshakeAtServer after the + // peer's hello has been validated. + void ApplyRemoteHello(const ParsedHello& remote); + + // Bringup the QP from RESET state to RTS state + // Arguments: + // lid: remote LID + // gid: remote GID + // qp_num: remote QP number + // Return: + // 0: success + // -1: failed, errno set + int BringUpQp(uint16_t lid, ibv_gid gid, uint32_t qp_num); + + // Get event from comp channel and ack the events + int GetAndAckEvents(SocketUniquePtr& s); + + // Request completion notification on a send/recv CQ. + int ReqNotifyCq(bool send_cq); + + // Poll CQ and get the work completion + static void PollCq(Socket* m); + + // Get the description of current handshake state + std::string GetStateStr() const; + + // Try to read data on TCP fd in _socket + void TryReadOnTcp(); + + // Add cq socket id to poller + void PollerAddCqSid(); + + // Remove cq socket id to poller + void PollerRemoveCqSid(); + + // Not owner + Socket* _socket; + + // State of Handshake + butil::atomic _state; + + // Wire-level handshake protocol version (set by dispatch in + // ProcessHandshakeAtClient/Server). Aligned with the protocol code: + // 0 = unnegotiated + // 2 = v2 "RDMA" + // 3 = v3 "RDM3" + int _handshake_version; + + // rdma resource + RdmaResource* _resource; + + // The number of events requiring ack. + unsigned int _send_cq_events; + unsigned int _recv_cq_events; + + // The SocketId which wrap the comp channel of CQ. + SocketId _cq_sid; + + // Capacity of local Send Queue and local Recv Queue + uint16_t _sq_size; + uint16_t _rq_size; + + // Act as sendbuf and recvbuf, but requires no memcpy + std::vector _sbuf; + std::vector _rbuf; + // Data address of _rbuf + std::vector _rbuf_data; + // Remote block size for receiving + uint32_t _remote_recv_block_size; + + // The number of new recv WRs acked to the remote side + uint16_t _accumulated_ack; + // The number of WRs sent without solicited flag + uint16_t _unsolicited; + // The bytes sent without solicited flag + uint32_t _unsolicited_bytes; + // The current index should be used for sending + uint16_t _sq_current; + // The number of send WRs not signaled + uint16_t _sq_unsignaled; + // The just completed send WR's index + uint16_t _sq_sent; + // The just completed recv WR's index + uint16_t _rq_received; + // The capacity of local window: min(local SQ, remote RQ) + uint16_t _local_window_capacity; + // The capacity of remote window: min(local RQ, remote SQ) + uint16_t _remote_window_capacity; + // The number of IMM WRs we can post to the local Send Queue. + uint16_t _sq_imm_window_size; + // The number of WRs we can send to remote side. + butil::atomic _remote_rq_window_size; + // The number of WRs we can post to the local Send Queue + butil::atomic _sq_window_size; + // The number of new WRs posted in the local Recv Queue + butil::atomic _new_rq_wrs; + + // butex for inform read events on TCP fd during handshake + butil::atomic *_read_butex; + + DISALLOW_COPY_AND_ASSIGN(RdmaEndpoint); + + // Cq socket id operation type + struct CqSidOp { + enum OpType { + ADD, + REMOVE, + }; + SocketId sid; + OpType type; + }; + // Poller instance + struct BAIDU_CACHELINE_ALIGNMENT Poller { + bthread_t tid{INVALID_BTHREAD}; + butil::MPSCQueue> op_queue; + // Callback used for io_uring/spdk etc + std::function callback; + // Init and Destroy function + std::function init_fn; + std::function release_fn; + }; + // Poller group + struct BAIDU_CACHELINE_ALIGNMENT PollerGroup { + PollerGroup() : pollers(FLAGS_rdma_poller_num), running(false) {} + std::vector pollers; + std::atomic running; + }; + static std::vector _poller_groups; +}; + +} // namespace rdma +} // namespace brpc + +#else // if BRPC_WITH_RDMA + +class RdmaEndpoint { }; + +#endif // ifdef USE_RD
+#include // std::min +#include +#include +#include +#include "butil/iobuf.h" // IOBuf, IOPortal, IOBufAsZeroCopy*Stream +#include "butil/sys_byteorder.h" +#include "brpc/socket.h" +#include "brpc/rdma/rdma_endpoint.h" +#include "brpc/rdma/rdma_helper.h" +#include "brpc/rdma_transport.h" +#include "brpc/rdma/rdma_handshake.pb.h" + +namespace brpc { +namespace rdma { + +DEFINE_int32(rdma_client_handshake_version, 2, + "RDMA handshake protocol version used by client. " + "2 = legacy 'RDMA' magic (default, compatible with all servers); " + "3 = new 'RDM3' protobuf-based handshake " + "(MUST only be enabled after target servers support v3)."); + +extern const uint16_t MIN_QP_SIZE; +extern const uint16_t MIN_BLOCK_SIZE; +extern uint32_t g_rdma_recv_block_size; +extern bool g_skip_rdma_init; + +// Wire-level constants for the v2 handshake. +static const char* MAGIC_STR = "RDMA"; +static constexpr uint16_t RDMA_HELLO_V2_MSG_LEN = 40; // In Byte +extern const uint16_t RDMA_HELLO_V2_VERSION = 2; +extern const uint16_t RDMA_IMPL_V2_VERSION = 1; + +// Wire-level constants for the v3 handshake. +static const char* MAGIC_STR_V3 = "RDM3"; +static const size_t RDMA_HELLO_V3_PB_SIZE_LEN = 4; +static const size_t RDMA_HELLO_V3_MAX_PB_SIZE = 4096; + +namespace v2_wire { + +void HelloMessage::Serialize(void* data) const { + uint16_t* current_pos = (uint16_t*)data; + *(current_pos++) = butil::HostToNet16(msg_len); + *(current_pos++) = butil::HostToNet16(hello_ver); + *(current_pos++) = butil::HostToNet16(impl_ver); + uint32_t* block_size_pos = (uint32_t*)current_pos; + *block_size_pos = butil::HostToNet32(block_size); + current_pos += 2; // move forward 4 Bytes + *(current_pos++) = butil::HostToNet16(sq_size); + *(current_pos++) = butil::HostToNet16(rq_size); + *(current_pos++) = butil::HostToNet16(lid); + fast_memcpy(current_pos, gid.raw, 16); + uint32_t* qp_num_pos = (uint32_t*)((char*)current_pos + 16); + *qp_num_pos = butil::HostToNet32(qp_num); +} + +void HelloMessage::Deserialize(void* data) { + uint16_t* current_pos = (uint16_t*)data; + msg_len = butil::NetToHost16(*current_pos++); + hello_ver = butil::NetToHost16(*current_pos++); + impl_ver = butil::NetToHost16(*current_pos++); + block_size = butil::NetToHost32(*(uint32_t*)current_pos); + current_pos += 2; // move forward 4 Bytes + sq_size = butil::NetToHost16(*current_pos++); + rq_size = butil::NetToHost16(*current_pos++); + lid = butil::NetToHost16(*current_pos++); + fast_memcpy(gid.raw, current_pos, 16); + qp_num = butil::NetToHost32(*(uint32_t*)((char*)current_pos + 16)); +} + +static bool ValidHelloMessage(const HelloMessage& msg) { + return msg.hello_ver == RDMA_HELLO_V2_VERSION && + msg.impl_ver == RDMA_IMPL_V2_VERSION && + msg.block_size >= MIN_BLOCK_SIZE && + msg.sq_size >= MIN_QP_SIZE && + msg.rq_size >= MIN_QP_SIZE; +} + +static void TranslateV2Hello(const HelloMessage& msg, ParsedHello* out) { + out->block_size = msg.block_size; + out->sq_size = msg.sq_size; + out->rq_size = msg.rq_size; + out->lid = msg.lid; + out->gid = msg.gid; + out->qp_num = msg.qp_num; +} + +int ReadBodyAndNegotiate(RdmaEndpoint* ep, ParsedHello* remote, bool* negotiated) { + uint8_t data[HELLO_MSG_LEN_MIN]; + if (ep->ReadFromFd(data, HELLO_MSG_LEN_MIN - MAGIC_STR_LEN) < 0) { + return -1; + } + HelloMessage remote_msg{}; + remote_msg.Deserialize(data); + if (remote_msg.msg_len < HELLO_MSG_LEN_MIN || + remote_msg.msg_len > HELLO_MSG_LEN_MAX) { + errno = EPROTO; + return -1; + } + if (remote_msg.msg_len > HELLO_MSG_LEN_MIN) { + // Drain unknown trailing bytes so they don't pollute subsequent + // reads (e.g. the upcoming ACK message). v2 base fields already + // carry enough information for negotiation; unknown trailing + // bytes are treated as optional hints that v2 safely ignores. + size_t ext_len = remote_msg.msg_len - HELLO_MSG_LEN_MIN; + if (DrainBytes(ep, ext_len) < 0) { + return -1; + } + } + if (!ValidHelloMessage(remote_msg)) { + *negotiated = false; + return 0; + } + *negotiated = true; + TranslateV2Hello(remote_msg, remote); + return 0; +} + +int DrainBytes(RdmaEndpoint* ep, size_t n) { + uint8_t scratch[64]; + while (n > 0) { + size_t chunk = std::min(n, sizeof(scratch)); + if (ep->ReadFromFd(scratch, chunk) < 0) { + return -1; + } + n -= chunk; + } + return 0; +} + +} // namespace v2_wire + +int RdmaHandshakeClientV2::SendLocalHello() { + RdmaEndpoint* ep = _ep; + uint8_t data[RDMA_HELLO_V2_MSG_LEN]; + + v2_wire::HelloMessage local_msg{}; + local_msg.msg_len = RDMA_HELLO_V2_MSG_LEN; + local_msg.hello_ver = RDMA_HELLO_V2_VERSION; + local_msg.impl_ver = RDMA_IMPL_V2_VERSION; + local_msg.block_size = g_rdma_recv_block_size; + local_msg.sq_size = ep->_sq_size; + local_msg.rq_size = ep->_rq_size; + local_msg.lid = GetRdmaLid(); + local_msg.gid = GetRdmaGid(); + if (BAIDU_LIKELY(ep->_resource)) { + local_msg.qp_num = ep->_resource->qp->qp_num; + } else { + // Only happens in UT + local_msg.qp_num = 0; + } + fast_memcpy(data, MAGIC_STR, 4); + local_msg.Serialize((char*)data + 4); + return ep->WriteToFd(data, RDMA_HELLO_V2_MSG_LEN); +} + +int RdmaHandshakeClientV2::ReceiveAndParseRemoteHello(ParsedHello* remote, + bool* negotiated) { + RdmaEndpoint* ep = _ep; + + // Read and verify magic (the endpoint did NOT pre-read magic on the client side). + uint8_t magic[MAGIC_STR_LEN]; + if (ep->ReadFromFd(magic, MAGIC_STR_LEN) < 0) { + return -1; + } + if (memcmp(magic, MAGIC_STR, MAGIC_STR_LEN) != 0) { + errno = EPROTO; + return -1; + } + return v2_wire::ReadBodyAndNegotiate(ep, remote, negotiated); +} + +int RdmaHandshakeServerV2::ReceiveAndParseRemoteHello(ParsedHello* remote, bool* negotiated) { + // Magic already consumed by ProcessHandshakeAtServer. + return v2_wire::ReadBodyAndNegotiate(_ep, remote, negotiated); +} + +int RdmaHandshakeServerV2::SendLocalHello() { + uint8_t data[RDMA_HELLO_V2_MSG_LEN]; + v2_wire::HelloMessage local_msg{}; + local_msg.msg_len = RDMA_HELLO_V2_MSG_LEN; + auto rdma_transport = static_cast(_ep->_socket->_transport.get()); + if (rdma_transport->_rdma_state == RdmaTransport::RDMA_OFF) { + local_msg.hello_ver = 0; + local_msg.impl_ver = 0; + local_msg.block_size = 0; + local_msg.sq_size = 0; + local_msg.rq_size = 0; + local_msg.lid = 0; + memset(local_msg.gid.raw, 0, sizeof(local_msg.gid.raw)); + local_msg.qp_num = 0; + } else { + local_msg.hello_ver = RDMA_HELLO_V2_VERSION; + local_msg.impl_ver = RDMA_IMPL_V2_VERSION; + local_msg.block_size = g_rdma_recv_block_size; + local_msg.sq_size = _ep->_sq_size; + local_msg.rq_size = _ep->_rq_size; + local_msg.lid = GetRdmaLid(); + local_msg.gid = GetRdmaGid(); + if (BAIDU_LIKELY(_ep->_resource)) { + local_msg.qp_num = _ep->_resource->qp->qp_num; + } else { + // Only happens in UT + local_msg.qp_num = 0; + } + } + fast_memcpy(data, MAGIC_STR, 4); + local_msg.Serialize((char*)data + 4); + return _ep->WriteToFd(data, RDMA_HELLO_V2_MSG_LEN); +} + +namespace v3_wire { + +bool ValidRdmaHello(const RdmaHello& msg) { + if (msg.gid().size() != sizeof(ibv_gid)) { + return false; + } + // ParsedHello stores these as uint16_t; reject values that would truncate. + constexpr uint16_t MAX_UINT16 = std::numeric_limits::max(); + if (msg.sq_size() > MAX_UINT16 || msg.rq_size() > MAX_UINT16 || msg.lid() > MAX_UINT16) { + return false; + } + if (msg.block_size() < MIN_BLOCK_SIZE) { + return false; + } + if (msg.sq_size() < MIN_QP_SIZE) { + return false; + } + if (msg.rq_size() < MIN_QP_SIZE) { + return false; + } + // qp_num == 0 only happens in UT (no real QP allocated). + if (msg.qp_num() == 0 && !g_skip_rdma_init) { + return false; + } + return true; +} + +void FillLocalRdmaHello(const RdmaEndpoint* ep, RdmaHello* msg) { + msg->set_block_size(g_rdma_recv_block_size); + msg->set_sq_size(ep->_sq_size); + msg->set_rq_size(ep->_rq_size); + msg->set_lid(GetRdmaLid()); + ibv_gid gid = GetRdmaGid(); + msg->set_gid(std::string(reinterpret_cast(gid.raw), + sizeof(gid.raw))); + if (BAIDU_LIKELY(ep->_resource)) { + msg->set_qp_num(ep->_resource->qp->qp_num); + } else { + // Only happens in UT + msg->set_qp_num(0); + } +} + +int ReadAndParseV3Hello(RdmaEndpoint* ep, RdmaHello* out) { + uint8_t size_buf[RDMA_HELLO_V3_PB_SIZE_LEN]; + if (ep->ReadFromFd(size_buf, RDMA_HELLO_V3_PB_SIZE_LEN) < 0) { + return -1; + } + uint32_t pb_size = butil::NetToHost32( + *reinterpret_cast(size_buf)); + if (pb_size == 0 || pb_size > RDMA_HELLO_V3_MAX_PB_SIZE) { + errno = EPROTO; + return -1; + } + butil::IOPortal body; + if (ep->ReadFromFd(&body, pb_size) < 0) { + return -1; + } + + butil::IOBufAsZeroCopyInputStream input(body); + if (!out->ParseFromZeroCopyStream(&input)) { + LOG(ERROR) << "Failed to parse RdmaHello"; + errno = EPROTO; + return -1; + } + return 0; +} + +int WriteV3Hello(RdmaEndpoint* ep, const RdmaHello& msg) { + uint32_t pb_size = static_cast(msg.ByteSizeLong()); + if (pb_size > RDMA_HELLO_V3_MAX_PB_SIZE) { + errno = EPROTO; + return -1; + } + + // [ "RDM3" 4B ][ pb_size 4B (big-endian) ][ RdmaHello protobuf bytes ] + butil::IOBuf packet; + packet.append(MAGIC_STR_V3, MAGIC_STR_LEN); + uint32_t pb_size_be = butil::HostToNet32(pb_size); + packet.append(&pb_size_be, RDMA_HELLO_V3_PB_SIZE_LEN); + butil::IOBufAsZeroCopyOutputStream output(&packet); + if (!msg.SerializeToZeroCopyStream(&output)) { + LOG(ERROR) << "Failed to serialize RdmaHello"; + errno = EPROTO; + return -1; + } + return ep->WriteToFd(&packet); +} + +void TranslateHello(const RdmaHello& msg, ParsedHello* out) { + out->block_size = msg.block_size(); + out->sq_size = static_cast(msg.sq_size()); + out->rq_size = static_cast(msg.rq_size()); + out->lid = static_cast(msg.lid()); + fast_memcpy(out->gid.raw, msg.gid().data(), sizeof(out->gid.raw)); + out->qp_num = msg.qp_num(); +} + +} // namespace v3_wire + +int RdmaHandshakeClientV3::SendLocalHello() { + RdmaHello local_msg{}; + v3_wire::FillLocalRdmaHello(_ep, &local_msg); + return v3_wire::WriteV3Hello(_ep, local_msg); +} + +int RdmaHandshakeClientV3::ReceiveAndParseRemoteHello(ParsedHello* remote, + bool* negotiated) { + uint8_t magic[MAGIC_STR_LEN]; + if (_ep->ReadFromFd(magic, MAGIC_STR_LEN) < 0) { + return -1; + } + if (memcmp(magic, MAGIC_STR_V3, MAGIC_STR_LEN) != 0) { + errno = EPROTO; + return -1; + } + + RdmaHello remote_msg{}; + if (v3_wire::ReadAndParseV3Hello(_ep, &remote_msg) < 0) { + return -1; + } + if (!v3_wire::ValidRdmaHello(remote_msg)) { + *negotiated = false; + return 0; + } + *negotiated = true; + v3_wire::TranslateHello(remote_msg, remote); + return 0; +} + +int RdmaHandshakeServerV3::ReceiveAndParseRemoteHello(ParsedHello* remote, bool* negotiated) { + // Magic already consumed by ProcessHandshakeAtServer. + RdmaHello remote_msg{}; + if (v3_wire::ReadAndParseV3Hello(_ep, &remote_msg) < 0) { + return -1; + } + if (!v3_wire::ValidRdmaHello(remote_msg)) { + *negotiated = false; + return 0; + } + *negotiated = true; + v3_wire::TranslateHello(remote_msg, remote); + return 0; +} + +int RdmaHandshakeServerV3::SendLocalHello() { + RdmaHello local_msg{}; + v3_wire::FillLocalRdmaHello(_ep, &local_msg); + return v3_wire::WriteV3Hello(_ep, local_msg); +} + +std::unique_ptr CreateClientHandshake(RdmaEndpoint* ep) { + switch (FLAGS_rdma_client_handshake_version) { + case 3: + return std::unique_ptr(new RdmaHandshakeClientV3(ep)); + case 2: + default: + return std::unique_ptr(new RdmaHandshakeClientV2(ep)); + } +} + +std::unique_ptr CreateServerHandshakeByMagic( + RdmaEndpoint* ep, const uint8_t magic[MAGIC_STR_LEN]) { + if (memcmp(magic, MAGIC_STR, MAGIC_STR_LEN) == 0) { + return std::unique_ptr(new RdmaHandshakeServerV2(ep)); + } + if (memcmp(magic, MAGIC_STR_V3, MAGIC_STR_LEN) == 0) { + return std::unique_ptr(new RdmaHandshakeServerV3(ep)); + } + return nullptr; +} + +} // namespace rdma +} // namespace brpc + +#endif // BRPC_WITH_RDMA diff --git a/src/brpc/rdma/rdma_handshake.h b/src/brpc/rdma/rdma_handshake.h new file mode 100644 index 0000000..5f36a9e --- /dev/null +++ b/src/brpc/rdma/rdma_handshake.h @@ -0,0 +1,192 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_RDMA_HANDSHAKE_H +#define BRPC_RDMA_HANDSHAKE_H + +#if BRPC_WITH_RDMA + +#include +#include +#include +#include +#include "butil/macros.h" + +namespace brpc { +namespace rdma { + +class RdmaEndpoint; + +// Length of the RDMA handshake magic string (e.g. "RDMA", "RDM3"). +static const size_t MAGIC_STR_LEN = 4; + +// Wire-format-agnostic representation of a peer's hello message. +// Each protocol version (v2 binary, v3 protobuf) translates its own +// wire format into this struct so the state-machine driver in +// RdmaEndpoint::ProcessHandshakeAt{Client,Server} stays free of any +// wire-format details. +struct ParsedHello { + uint32_t block_size; + uint16_t sq_size; + uint16_t rq_size; + uint16_t lid; + ibv_gid gid; + uint32_t qp_num; +}; + +namespace v2_wire { + +// Wire constants for the v2 hello. +// +// HELLO_MSG_LEN_MIN: total length of the base v2 hello (4B magic + +// 36B HelloMessage). Anything shorter than this is malformed. +// HELLO_MSG_LEN_MAX: upper bound for the entire v2 hello message +// length declared by HelloMessage::msg_len. Anything beyond this is +// treated as a protocol error and the connection is closed without +// attempting to drain. +static constexpr size_t HELLO_MSG_LEN_MIN = 40; +static constexpr size_t HELLO_MSG_LEN_MAX = 4096; + +// v2 binary HelloMessage. +struct HelloMessage { + void Serialize(void* data) const; + void Deserialize(void* data); + + uint16_t msg_len; + uint16_t hello_ver; + uint16_t impl_ver; + uint32_t block_size; + uint16_t sq_size; + uint16_t rq_size; + uint16_t lid; + ibv_gid gid; + uint32_t qp_num; +}; + +} // namespace v2_wire + +// Abstract base class of an RDMA handshake. +// +// Acts as the protocol-version dispatch point for the state machine +// driven by RdmaEndpoint::ProcessHandshakeAt{Client,Server}. +class RdmaHandshake { +public: + explicit RdmaHandshake(RdmaEndpoint* ep) : _ep(ep) {} + virtual ~RdmaHandshake() = default; + + DISALLOW_COPY_AND_ASSIGN(RdmaHandshake); + + // Wire-level protocol version (2 for "RDMA", 3 for "RDM3"). + virtual int ProtocolVersion() const = 0; + + // Build and send the local hello (including the protocol magic). + // Returns 0 on success, -1 on IO error (errno set). + // + // For a server in fallback state, implementations MUST still + // produce a sendable message; each version uses its own wire + // convention to signal "I am falling back" to the peer: + // - v2: zero hello_ver/impl_ver so the peer's HelloNegotiationValid + // rejects it; + // - v3: qp_num==0 so the peer's ValidRdmaHello rejects it. + virtual int SendLocalHello() = 0; + + // Read the peer's hello, validate it, and translate into ParsedHello. + // + // Role-specific semantics: + // - Client subclasses: read & verify the 4B magic first, then the + // body. (The endpoint did NOT pre-read the magic on the client + // side.) + // - Server subclasses: read ONLY the body. The 4B magic was + // already consumed by ProcessHandshakeAtServer and was used to + // pick `this` from CreateServerHandshakeByMagic; re-reading + // would deadlock. + // + // Outputs: + // *negotiated -- true if the remote hello is structurally valid + // AND passes per-protocol negotiation checks; + // false means the peer asked for fallback or sent + // something we can't honor. + // Returns: + // 0 -- IO/parsing layer OK; check *negotiated and *remote. + // -1 -- IO error or unrecoverable protocol error (errno set). + virtual int ReceiveAndParseRemoteHello(ParsedHello* remote, bool* negotiated) = 0; + +protected: + RdmaEndpoint* _ep; +}; + +// v2 handshake (legacy "RDMA" magic, 36B binary HelloMessage). +class RdmaHandshakeClientV2 : public RdmaHandshake { +public: + using RdmaHandshake::RdmaHandshake; + int ProtocolVersion() const override { return 2; } + + int SendLocalHello() override; + int ReceiveAndParseRemoteHello(ParsedHello* remote, bool* negotiated) override; +}; + +class RdmaHandshakeServerV2 : public RdmaHandshake { +public: + using RdmaHandshake::RdmaHandshake; + int ProtocolVersion() const override { return 2; } + + int SendLocalHello() override; + int ReceiveAndParseRemoteHello(ParsedHello* remote, bool* negotiated) override; +}; + +// v3 handshake (new "RDM3" magic, protobuf RdmaHello). +// [ "RDM3" 4B ][ pb_size 4B (big-endian) ][ RdmaHello protobuf bytes ] +class RdmaHandshakeClientV3 : public RdmaHandshake { +public: + using RdmaHandshake::RdmaHandshake; + int ProtocolVersion() const override { return 3; } + + int SendLocalHello() override; + int ReceiveAndParseRemoteHello(ParsedHello* remote, bool* negotiated) override; +}; + +class RdmaHandshakeServerV3 : public RdmaHandshake { +public: + using RdmaHandshake::RdmaHandshake; + int ProtocolVersion() const override { return 3; } + + int SendLocalHello() override; + int ReceiveAndParseRemoteHello(ParsedHello* remote, bool* negotiated) override; +}; + +// Factory methods +// +// Pick the client-side handshake based on +// FLAGS_rdma_client_handshake_version: +// 2 (default) -> RdmaHandshakeClientV2 +// 3 -> RdmaHandshakeClientV3 +// Other values fall back to V2. +std::unique_ptr CreateClientHandshake(RdmaEndpoint* ep); + +// Pick the server-side handshake based on the 4B magic already read. +// Returns NULL if `magic` is not a recognized RDMA magic +// (the caller should then fallback to TCP). +// "RDMA" -> RdmaHandshakeServerV2 +// "RDM3" -> RdmaHandshakeServerV3 +std::unique_ptr CreateServerHandshakeByMagic( + RdmaEndpoint* ep, const uint8_t magic[MAGIC_STR_LEN]); + +} // namespace rdma +} // namespace brpc + +#endif // BRPC_WITH_RDMA +#endif // BRPC_RDMA_HANDSHAKE_H diff --git a/src/brpc/rdma/rdma_handshake.proto b/src/brpc/rdma/rdma_handshake.proto new file mode 100644 index 0000000..c180b58 --- /dev/null +++ b/src/brpc/rdma/rdma_handshake.proto @@ -0,0 +1,46 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax = "proto2"; + +package brpc.rdma; + +option cc_generic_services = false; + +// RDMA handshake v3 message. +// Carried in the body of every "RDM3" handshake packet: +// +// [ "RDM3" 4B ][ pb_size 4B ][ RdmaHello protobuf bytes ] +message RdmaHello { + // ---- v2-parity base fields (required) ---- + // Listed first and in the same logical order as the v2 binary + // HelloMessage (minus hello_ver / impl_ver, which are subsumed by + // the wrapper magic "RDM3"). Keeping the same ordering simplifies + // side-by-side reasoning when debugging mixed v2/v3 traffic. + // + // Marked `required` because the handshake cannot proceed without + // any of these; ParseFromArray() will reject a missing field at + // the protobuf layer, so we don't need an extra has_xxx() check + // in RdmaHelloValid() for presence. + required uint32 block_size = 1; + required uint32 sq_size = 2; + required uint32 rq_size = 3; + required uint32 lid = 4; + // Must be exactly 16 bytes (sizeof(ibv_gid)). + required bytes gid = 5; + required uint32 qp_num = 6; +} diff --git a/src/brpc/rdma/rdma_helper.cpp b/src/brpc/rdma/rdma_helper.cpp new file mode 100644 index 0000000..9634890 --- /dev/null +++ b/src/brpc/rdma/rdma_helper.cpp @@ -0,0 +1,746 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +#if BRPC_WITH_RDMA + +#include // dlopen +#include +#include +#include +#include +#include "butil/containers/flat_map.h" // butil::FlatMap +#include "butil/fd_guard.h" +#include "butil/fd_utility.h" // butil::make_non_blocking +#include "butil/logging.h" +#include "brpc/socket.h" +#include "brpc/rdma/block_pool.h" +#include "brpc/rdma/rdma_endpoint.h" +#include "brpc/rdma/rdma_helper.h" + + +namespace butil { +namespace iobuf { +// declared in iobuf.cpp +extern void* (*blockmem_allocate)(size_t); +extern void (*blockmem_deallocate)(void*); +} +} + +namespace brpc { +namespace rdma { + +void* g_handle_ibverbs = NULL; +bool g_skip_rdma_init = false; + +ibv_device** (*IbvGetDeviceList)(int*) = NULL; +void (*IbvFreeDeviceList)(ibv_device**) = NULL; +ibv_context* (*IbvOpenDevice)(ibv_device*) = NULL; +int (*IbvCloseDevice)(ibv_context*) = NULL; +const char* (*IbvGetDeviceName)(ibv_device*) = NULL; +int (*IbvForkInit)(void) = NULL; +int (*IbvQueryDevice)(ibv_context*, ibv_device_attr*) = NULL; +int (*IbvQueryPort)(ibv_context*, uint8_t, ibv_port_attr*) = NULL; +int (*IbvQueryGid)(ibv_context*, uint8_t, int, ibv_gid*) = NULL; +ibv_pd* (*IbvAllocPd)(ibv_context*) = NULL; +int (*IbvDeallocPd)(ibv_pd*) = NULL; +ibv_cq* (*IbvCreateCq)(ibv_context*, int, void*, ibv_comp_channel*, int) = NULL; +int (*IbvDestroyCq)(ibv_cq*) = NULL; +ibv_qp* (*IbvCreateQp)(ibv_pd*, ibv_qp_init_attr*) = NULL; +int (*IbvModifyQp)(ibv_qp*, ibv_qp_attr*, ibv_qp_attr_mask) = NULL; +int (*IbvQueryQp)(ibv_qp*, ibv_qp_attr*, ibv_qp_attr_mask, ibv_qp_init_attr*) = NULL; +int (*IbvDestroyQp)(ibv_qp*) = NULL; +ibv_comp_channel* (*IbvCreateCompChannel)(ibv_context*) = NULL; +int (*IbvDestroyCompChannel)(ibv_comp_channel*) = NULL; +ibv_mr* (*IbvRegMr)(ibv_pd*, void*, size_t, int) = NULL; +int (*IbvDeregMr)(ibv_mr*) = NULL; +int (*IbvGetCqEvent)(ibv_comp_channel*, ibv_cq**, void**) = NULL; +void (*IbvAckCqEvents)(ibv_cq*, unsigned int) = NULL; +int (*IbvGetAsyncEvent)(ibv_context*, ibv_async_event*) = NULL; +void (*IbvAckAsyncEvent)(ibv_async_event*) = NULL; +const char* (*IbvEventTypeStr)(ibv_event_type) = NULL; +int (*IbvQueryEce)(ibv_qp*, ibv_ece*) = NULL; +int (*IbvSetEce)(ibv_qp*, ibv_ece*) = NULL; + +// NOTE: +// ibv_post_send, ibv_post_recv, ibv_poll_cq, ibv_req_notify_cq are all inline function +// defined in infiniband/verbs.h. + +static int g_gid_tbl_len = 0; +static uint8_t g_gid_index = 0; +static ibv_gid g_gid; +static uint16_t g_lid; +static int g_max_sge = 0; +static uint8_t g_port_num = 1; + +static int g_comp_vector_index = 0; + +butil::atomic g_rdma_available(false); + +DEFINE_int32(rdma_max_sge, 0, "Max SGE num in a WR"); +DEFINE_string(rdma_device, "", "The name of the HCA device used " + "(Empty means using the first active device)"); +DEFINE_int32(rdma_port, 1, "The port number to use. For RoCE, it is always 1."); +DEFINE_int32(rdma_gid_index, -1, "The GID index to use. -1 means using the last one."); + +// static const size_t SYSFS_SIZE = 4096; +static ibv_device** g_devices = NULL; +static ibv_context* g_context = NULL; +static SocketId g_async_socket; +static ibv_pd* g_pd = NULL; +static std::vector* g_mrs = NULL; // mr registered by brpc + +static butil::FlatMap* g_user_mrs; // mr registered by user +static butil::Mutex* g_user_mrs_lock = NULL; + +// Store the original IOBuf memalloc and memdealloc functions +static void* (*g_mem_alloc)(size_t) = NULL; +static void (*g_mem_dealloc)(void*) = NULL; + +namespace { +struct IbvDeviceDeleter { + void operator()(ibv_device** device_list) { + IbvFreeDeviceList(device_list); + } +}; + +struct IbvContextDeleter { + void operator() (ibv_context* context) { + IbvCloseDevice(context); + } +}; +} // namespace + +static void GlobalRelease() { + g_rdma_available.store(false, butil::memory_order_release); + usleep(100000); // to avoid unload library too early + + // We do not set `g_async_socket' to failed explicitly to avoid + // close async_fd twice. + + RdmaEndpoint::GlobalRelease(); + + if (g_user_mrs_lock) { + BAIDU_SCOPED_LOCK(*g_user_mrs_lock); + for (butil::FlatMap::iterator it = g_user_mrs->begin(); + it != g_user_mrs->end(); ++it) { + IbvDeregMr(it->second); + } + g_user_mrs->clear(); + delete g_user_mrs; + g_user_mrs = NULL; + } + delete g_user_mrs_lock; + g_user_mrs_lock = NULL; + + if (g_mrs) { + for (size_t i = 0; i < g_mrs->size(); ++i) { + IbvDeregMr((*g_mrs)[i]); + } + delete g_mrs; + g_mrs = NULL; + } + + if (g_pd) { + IbvDeallocPd(g_pd); + g_pd = NULL; + } + + if (g_context) { + IbvCloseDevice(g_context); + g_context = NULL; + } + + if (g_devices) { + IbvFreeDeviceList(g_devices); + g_devices = NULL; + } +} + +void* UserExtendBlockPool(void* region_base, size_t region_size, + int block_type) { + return ExtendBlockPoolByUser(region_base, region_size, block_type); +} + +uint32_t RdmaRegisterMemory(void* buf, size_t size) { + // Register the memory as callback in block_pool + // The thread-safety should be guaranteed by the caller + ibv_mr* mr = IbvRegMr(g_pd, buf, size, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_RELAXED_ORDERING); + if (!mr) { + PLOG(WARNING) << "Do not support IBV_ACCESS_RELAXED_ORDERING for RDMA!!!"; + mr = IbvRegMr(g_pd, buf, size, IBV_ACCESS_LOCAL_WRITE); + if (!mr) { + PLOG(ERROR) << "Fail to register memory"; + return 0; + } + } + g_mrs->push_back(mr); + return mr->lkey; +} + +static void* BlockAllocate(size_t len) { + if (len == 0) { + errno = EINVAL; + return NULL; + } + void* ptr = AllocBlock(len); + if (!ptr) { + LOG(ERROR) << "Fail to get block from memory pool"; + } + + return ptr; +} + +void BlockDeallocate(void* buf) { + if (!buf) { + errno = EINVAL; + return; + } + DeallocBlock(buf); +} + +static void FindRdmaLid() { + ibv_port_attr attr; + if (IbvQueryPort(g_context, g_port_num, &attr) != 0) { + return; + } + g_lid = attr.lid; + LOG(INFO) << "RDMA LID changes to: " << g_lid; + return; +} + +static bool FindRdmaGid(ibv_context* context) { + bool found = false; + for (int i = g_gid_tbl_len - 1; i >= 0; --i) { + ibv_gid gid; + if (IbvQueryGid(context, g_port_num, i, &gid) != 0) { + continue; + } + if (gid.global.interface_id == 0) { + continue; + } + if (FLAGS_rdma_gid_index == i) { + g_gid = gid; + g_gid_index = i; + return true; + } + // For infiniband, there is only one GID for each port. + // For RoCE, there are 2 GIDs for each MAC and 2 GIDs for each IP. + // Generally, the last GID is a RoCEv2-type GID generated by IP. + if (!found) { + g_gid = gid; + g_gid_index = i; + found = true; + } + } + if (FLAGS_rdma_gid_index >= 0 && g_gid_index != FLAGS_rdma_gid_index) { + found = false; + } + return found; +} + +static void OnRdmaAsyncEvent(Socket* m) { + int progress = Socket::PROGRESS_INIT; + do { + ibv_async_event event; + if (IbvGetAsyncEvent(g_context, &event) != 0) { + break; + } + LOG(WARNING) << "rdma async event: " << IbvEventTypeStr(event.event_type); + switch (event.event_type) { + case IBV_EVENT_QP_REQ_ERR: + case IBV_EVENT_QP_ACCESS_ERR: + case IBV_EVENT_QP_FATAL: { + SocketId sid = (SocketId)event.element.qp->qp_context; + SocketUniquePtr s; + if (Socket::Address(sid, &s) == 0) { + s->SetFailed(ERDMA, "QP fatal error"); + LOG(WARNING) << "Receive a QP fatal error on " << s->description(); + } + // NOTE: + // We must ack the async event here, before `s' is recycled. + // Otherwise there will be an deadlock. + // Please check the use of ibv_ack_async_event at: + // http://www.rdmamojo.com/2012/08/16/ibv_ack_async_event/ + IbvAckAsyncEvent(&event); + break; + } + case IBV_EVENT_CQ_ERR: { + LOG(WARNING) << "CQ overruns, the connection will be stopped."; + IbvAckAsyncEvent(&event); + break; + } + case IBV_EVENT_COMM_EST: + case IBV_EVENT_SQ_DRAINED: + case IBV_EVENT_QP_LAST_WQE_REACHED: { + // just ignore the event + IbvAckAsyncEvent(&event); + break; + } + case IBV_EVENT_SRQ_ERR: + case IBV_EVENT_SRQ_LIMIT_REACHED: { + // SRQ not used, should not happen + IbvAckAsyncEvent(&event); + break; + } + case IBV_EVENT_LID_CHANGE: { + FindRdmaLid(); + IbvAckAsyncEvent(&event); + break; + } + case IBV_EVENT_PATH_MIG: + case IBV_EVENT_PATH_MIG_ERR: + case IBV_EVENT_PKEY_CHANGE: + case IBV_EVENT_SM_CHANGE: + case IBV_EVENT_CLIENT_REREGISTER: { + // for IB only, we haven't test these events carefully + IbvAckAsyncEvent(&event); + break; + } + case IBV_EVENT_PORT_ACTIVE: + case IBV_EVENT_PORT_ERR: { + // Port up/down will lead these two events. + // The port error is recoverable. + IbvAckAsyncEvent(&event); + break; + } + case IBV_EVENT_GID_CHANGE: { + FindRdmaGid(g_context); + IbvAckAsyncEvent(&event); + break; + } + case IBV_EVENT_DEVICE_FATAL: { + // because the memory resources are related to rdma device + // we view this error unrecoverable + GlobalDisableRdma(); + IbvAckAsyncEvent(&event); + break; + } + default: + // should not happen + IbvAckAsyncEvent(&event); + break; + } + if (!m->MoreReadEvents(&progress)) { + break; + } + } while (true); +} + +#define LoadSymbol(handle, func, symbol) \ + *(void**)(&func) = dlsym(handle, symbol); \ + if (!func) { \ + LOG(ERROR) << "Fail to find symbol: " << symbol; \ + return -1; \ + } + +static int ReadRdmaDynamicLib() { + const static char* const kRdmaLibs[] = { + "libibverbs.so", + "libibverbs.so.1" + }; + for (const char* lib : kRdmaLibs) { + dlerror(); // Clear existing error + g_handle_ibverbs = dlopen(lib, RTLD_LAZY); + if (g_handle_ibverbs) { + LOG(INFO) << "Successfully loaded " << lib; + break; + } + LOG(WARNING) << "Failed to load " << lib << ": " << dlerror(); + } + if (!g_handle_ibverbs) { + LOG(ERROR) << "Failed to load any of the RDMA libraries"; + return -1; + } + + LoadSymbol(g_handle_ibverbs, IbvGetDeviceList, "ibv_get_device_list"); + LoadSymbol(g_handle_ibverbs, IbvFreeDeviceList, "ibv_free_device_list"); + LoadSymbol(g_handle_ibverbs, IbvOpenDevice, "ibv_open_device"); + LoadSymbol(g_handle_ibverbs, IbvCloseDevice, "ibv_close_device"); + LoadSymbol(g_handle_ibverbs, IbvGetDeviceName, "ibv_get_device_name"); + LoadSymbol(g_handle_ibverbs, IbvForkInit, "ibv_fork_init"); + LoadSymbol(g_handle_ibverbs, IbvQueryDevice, "ibv_query_device"); + LoadSymbol(g_handle_ibverbs, IbvQueryPort, "ibv_query_port"); + LoadSymbol(g_handle_ibverbs, IbvQueryGid, "ibv_query_gid"); + LoadSymbol(g_handle_ibverbs, IbvAllocPd, "ibv_alloc_pd"); + LoadSymbol(g_handle_ibverbs, IbvDeallocPd, "ibv_dealloc_pd"); + LoadSymbol(g_handle_ibverbs, IbvCreateCq, "ibv_create_cq"); + LoadSymbol(g_handle_ibverbs, IbvDestroyCq, "ibv_destroy_cq"); + LoadSymbol(g_handle_ibverbs, IbvCreateQp, "ibv_create_qp"); + LoadSymbol(g_handle_ibverbs, IbvModifyQp, "ibv_modify_qp"); + LoadSymbol(g_handle_ibverbs, IbvQueryQp, "ibv_query_qp"); + LoadSymbol(g_handle_ibverbs, IbvDestroyQp, "ibv_destroy_qp"); + LoadSymbol(g_handle_ibverbs, IbvCreateCompChannel, "ibv_create_comp_channel"); + LoadSymbol(g_handle_ibverbs, IbvDestroyCompChannel, "ibv_destroy_comp_channel"); + LoadSymbol(g_handle_ibverbs, IbvRegMr, "ibv_reg_mr"); + LoadSymbol(g_handle_ibverbs, IbvDeregMr, "ibv_dereg_mr"); + LoadSymbol(g_handle_ibverbs, IbvGetCqEvent, "ibv_get_cq_event"); + LoadSymbol(g_handle_ibverbs, IbvAckCqEvents, "ibv_ack_cq_events"); + LoadSymbol(g_handle_ibverbs, IbvGetAsyncEvent, "ibv_get_async_event"); + LoadSymbol(g_handle_ibverbs, IbvAckAsyncEvent, "ibv_ack_async_event"); + LoadSymbol(g_handle_ibverbs, IbvEventTypeStr, "ibv_event_type_str"); + LoadSymbol(g_handle_ibverbs, IbvQueryEce, "ibv_query_ece"); + LoadSymbol(g_handle_ibverbs, IbvSetEce, "ibv_set_ece"); + + return 0; +} + +static inline void ExitWithError() { + GlobalRelease(); + exit(1); +} + +/** + * @brief Open the RDMA device specified by FLAGS_rdma_device or the first + * available device if FLAGS_rdma_device is empty. Also, number of available + * devices are written to `*num_available_devices` + * + * @param num_total Total number returned by ibv_open_devices + * @param num_available_devices Location to write num available + * @return ibv_context* nullptr if no device available or device_name not match + */ +static ibv_context* OpenDevice(int num_total, int* num_available_devices) { + *num_available_devices = 0; + ibv_context* ret_context = nullptr; + for (int i = 0; i < num_total; ++i) { + std::unique_ptr context{ + IbvOpenDevice(g_devices[i]), IbvContextDeleter()}; + const char* dev_name = IbvGetDeviceName(g_devices[i]); + if (!context) { + PLOG(ERROR) << "Fail to open rdma device " << dev_name; + continue; + } + ibv_port_attr attr; + errno = IbvQueryPort(context.get(), uint8_t(FLAGS_rdma_port), &attr); + if (errno != 0) { + PLOG(WARNING) << "Fail to query port " << FLAGS_rdma_port << " on " + << dev_name; + continue; + } + if (attr.state != IBV_PORT_ACTIVE) { + LOG(WARNING) << "Device " << dev_name << " port not active"; + continue; + } + + ++*num_available_devices; + if (ret_context) { + continue; + } + if (!FLAGS_rdma_device.empty()) { + // Use provided device_name + if (FLAGS_rdma_device == dev_name) { + ret_context = context.release(); + g_gid_tbl_len = attr.gid_tbl_len; + g_lid = attr.lid; + } else { + LOG(INFO) << "Device name not match: " << context->device->name + << " vs " << FLAGS_rdma_device; + } + } else { + // Fallback to first available device + ret_context = context.release(); + g_gid_tbl_len = attr.gid_tbl_len; + g_lid = attr.lid; + } + } + return ret_context; +} + +static void GlobalRdmaInitializeOrDieImpl() { + if (BAIDU_UNLIKELY(g_skip_rdma_init)) { + // Just for UT + return; + } + + if (ReadRdmaDynamicLib() < 0) { + LOG(ERROR) << "Fail to load rdma dynamic lib"; + ExitWithError(); + } + + // ibv_fork_init is very important. If we don't call this API, + // we may get some very, very strange problems if the program + // calls fork(). + if (IbvForkInit()) { + PLOG(ERROR) << "Fail to ibv_fork_init"; + ExitWithError(); + } + + int num = 0; + g_devices = IbvGetDeviceList(&num); + if (num == 0) { + LOG(ERROR) << "Fail to find rdma device"; + ExitWithError(); + } + + // Find the first active port + g_port_num = FLAGS_rdma_port; + int available_devices; + g_context = OpenDevice(num, &available_devices); + + if (!g_context) { + LOG(ERROR) << "Fail to find available RDMA device " << FLAGS_rdma_device; + ExitWithError(); + } + if (available_devices > 1 && FLAGS_rdma_device.size() == 0) { + LOG(INFO) << "This server has more than one available RDMA device. Only " + << "the first one (" << g_context->device->name + << ") will be used. If you want to use other device, please " + << "specify it with --rdma_device."; + } else { + LOG(INFO) << "RDMA device: " << g_context->device->name; + } + LOG(INFO) << "RDMA LID: " << g_lid; + if (!FindRdmaGid(g_context)) { + LOG(ERROR) << "Fail to find available RDMA GID"; + ExitWithError(); + } else { + LOG(INFO) << "RDMA GID Index: " << (int)g_gid_index; + } + + // Create protection domain + g_pd = IbvAllocPd(g_context); + if (!g_pd) { + PLOG(ERROR) << "Fail to allocate protection domain"; + ExitWithError(); + } + + g_user_mrs_lock = new (std::nothrow) butil::Mutex; + if (!g_user_mrs_lock) { + PLOG(WARNING) << "Fail to construct g_user_mrs_lock"; + ExitWithError(); + } + + g_user_mrs = new (std::nothrow) butil::FlatMap(); + if (!g_user_mrs) { + PLOG(WARNING) << "Fail to construct g_user_mrs"; + ExitWithError(); + } + + if (g_user_mrs->init(65536) < 0) { + PLOG(WARNING) << "Fail to initialize g_user_mrs"; + ExitWithError(); + } + + g_mrs = new (std::nothrow) std::vector; + if (!g_mrs) { + PLOG(ERROR) << "Fail to allocate a RDMA MR list"; + ExitWithError(); + } + + ibv_device_attr attr; + if (IbvQueryDevice(g_context, &attr) != 0) { + PLOG(ERROR) << "Fail to get the device information"; + ExitWithError(); + } + // Too large sge consumes too much memory for QP + if (FLAGS_rdma_max_sge > 0) { + g_max_sge = attr.max_sge < FLAGS_rdma_max_sge ? + attr.max_sge : FLAGS_rdma_max_sge; + } else { + g_max_sge = attr.max_sge; + } + + // Initialize RDMA memory pool (block_pool) + butil::SetDefaultBlockSize(GetRdmaBlockSize()); + if (!InitBlockPool(RdmaRegisterMemory)) { + PLOG(ERROR) << "Fail to initialize RDMA memory pool"; + ExitWithError(); + } + + if (RdmaEndpoint::GlobalInitialize() < 0) { + LOG(ERROR) << "rdma_recv_block_type incorrect " + << "(valid value: default/large/huge)"; + ExitWithError(); + } + + atexit(GlobalRelease); + + SocketOptions opt; + opt.fd = g_context->async_fd; + butil::make_close_on_exec(opt.fd); + if (butil::make_non_blocking(opt.fd) < 0) { + PLOG(WARNING) << "Fail to set async_fd to nonblocking"; + ExitWithError(); + } + opt.on_edge_triggered_events = OnRdmaAsyncEvent; + if (Socket::Create(opt, &g_async_socket) < 0) { + LOG(WARNING) << "Fail to create socket to get async event of RDMA"; + ExitWithError(); + } + + g_mem_alloc = butil::iobuf::blockmem_allocate; + g_mem_dealloc = butil::iobuf::blockmem_deallocate; + butil::iobuf::blockmem_allocate = BlockAllocate; + butil::iobuf::blockmem_deallocate = BlockDeallocate; + g_rdma_available.store(true, butil::memory_order_relaxed); +} + +static pthread_once_t initialize_rdma_once = PTHREAD_ONCE_INIT; + +void GlobalRdmaInitializeOrDie() { + if (pthread_once(&initialize_rdma_once, + GlobalRdmaInitializeOrDieImpl) != 0) { + LOG(FATAL) << "Fail to pthread_once GlobalRdmaInitializeOrDie"; + exit(1); + } +} + +uint32_t RegisterMemoryForRdma(void* buf, size_t len) { + ibv_mr* mr = IbvRegMr(g_pd, buf, len, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_RELAXED_ORDERING); + if (!mr) { + PLOG(WARNING) << "Do not support IBV_ACCESS_RELAXED_ORDERING for RDMA!!!"; + mr = IbvRegMr(g_pd, buf, len, IBV_ACCESS_LOCAL_WRITE); + if (!mr) { + PLOG(ERROR) << "Fail to register memory"; + return 0; + } + } + { + BAIDU_SCOPED_LOCK(*g_user_mrs_lock); + if (!g_user_mrs->insert(buf, mr)) { + LOG(WARNING) << "Fail to insert to user mr maps (now there are " + << g_user_mrs->size() << " mrs already"; + } else { + return mr->lkey; + } + } + if(IbvDeregMr(mr)) { + PLOG(ERROR) << "Failed to deregister memory"; + } + return 0; +} + +void DeregisterMemoryForRdma(void* buf) { + ibv_mr* mr = NULL; + { + BAIDU_SCOPED_LOCK(*g_user_mrs_lock); + ibv_mr** mr_ptr = g_user_mrs->seek(buf); + if (mr_ptr) { + mr = *mr_ptr; + g_user_mrs->erase(buf); + } + } + if (mr) { + if (IbvDeregMr(mr)) { + PLOG(ERROR) << "Failed to deregister memory at: " << mr->addr; + } + } else { + LOG(WARNING) << "Try to deregister a buffer which is not registered"; + } +} + +int GetRdmaMaxSge() { + return g_max_sge; +} + +int GetRdmaCompVector() { + if (!g_context) { + return 0; + } + // g_comp_vector_index is not an atomic variable. If more than + // one CQ is created at the same time, some CQs will share the + // same index. However, this vector is only used to assign an + // event queue for the CQ. Sharing the same event queue is not + // a problem. + return (g_comp_vector_index++) % g_context->num_comp_vectors; +} + +ibv_context* GetRdmaContext() { + return g_context; +} + +ibv_pd* GetRdmaPd() { + return g_pd; +} + +uint32_t GetLKey(void* buf) { + BAIDU_SCOPED_LOCK(*g_user_mrs_lock); + ibv_mr** mr_ptr = g_user_mrs->seek(buf); + if (mr_ptr) { + return (*mr_ptr)->lkey; + } + return 0; +} + +ibv_gid GetRdmaGid() { + return g_gid; +} + +uint16_t GetRdmaLid() { + return g_lid; +} + +uint8_t GetRdmaGidIndex() { + return g_gid_index; +} + +uint8_t GetRdmaPortNum() { + return g_port_num; +} + +bool IsRdmaAvailable() { + return g_rdma_available.load(butil::memory_order_acquire); +} + +void GlobalDisableRdma() { + if (g_rdma_available.exchange(false, butil::memory_order_acquire)) { + LOG(FATAL) << "RDMA is disabled due to some unrecoverable problem"; + } +} + +bool SupportedByRdma(std::string protocol) { + if (protocol.compare("baidu_std") == 0) { + // Since rdma is used for high performance scenario, + // we consider baidu_std for the only protocol to support. + return true; + } + return false; +} + +bool InitPollingModeWithTag(bthread_tag_t tag, + std::function callback, + std::function init_fn, + std::function release_fn) { + if (RdmaEndpoint::PollingModeInitialize(tag, callback, init_fn, + release_fn) == 0) { + return true; + } + return false; +} + +void ReleasePollingModeWithTag(bthread_tag_t tag) { + RdmaEndpoint::PollingModeRelease(tag); +} + +} // namespace rdma +} // namespace brpc + +#else + +#include +#include "butil/logging.h" + +namespace brpc { +namespace rdma { +void GlobalRdmaInitializeOrDie() { + LOG(ERROR) << "brpc is not compiled with rdma. To enable it, please refer to " + << "https://github.com/apache/brpc/blob/master/docs/en/rdma.md"; + exit(1); +} +} +} + +#endif // if BRPC_WITH_RDMA diff --git a/src/brpc/rdma/rdma_helper.h b/src/brpc/rdma/rdma_helper.h new file mode 100644 index 0000000..0527633 --- /dev/null +++ b/src/brpc/rdma/rdma_helper.h @@ -0,0 +1,106 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_RDMA_HELPER_H +#define BRPC_RDMA_HELPER_H + +#if BRPC_WITH_RDMA + +#include +#include +#include +#include "bthread/types.h" + + +namespace brpc { +namespace rdma { + +// Initialize RDMA environment +// Exit if failed +void GlobalRdmaInitializeOrDie(); + +// Initialize RDMA polling mode with tag +bool InitPollingModeWithTag(bthread_tag_t tag, + std::function callback = nullptr, + std::function init_fn = nullptr, + std::function release_fn = nullptr); + +void ReleasePollingModeWithTag(bthread_tag_t tag); + +// Register the given memory +// Return the memory lkey for the given memory, Return 0 when fails +// To use the memory in IOBuf, append_user_data_with_meta must be called +// and take the lkey as the data meta +uint32_t RegisterMemoryForRdma(void* buf, size_t len); + +// Deregister the given memory +void DeregisterMemoryForRdma(void* buf); + +// Get global RDMA context +ibv_context* GetRdmaContext(); + +// Get global RDMA protection domain +ibv_pd* GetRdmaPd(); + +// Return lkey of the given address +uint32_t GetLKey(void* buf); + +// Return GID Index +uint8_t GetRdmaGidIndex(); + +// Return Global GID +ibv_gid GetRdmaGid(); + +// Return Global LID +uint16_t GetRdmaLid(); + +// Return suggested comp vector for CQ +int GetRdmaCompVector(); + +// Return current port number used +uint8_t GetRdmaPortNum(); + +// Get max_sge supported by the device +int GetRdmaMaxSge(); + +// Get suggested comp_vector for a new CQ +int GetCompVector(); + +// If the RDMA environment is available +bool IsRdmaAvailable(); + +// Disable RDMA in the remaining lifetime of the process +void GlobalDisableRdma(); + +// If the given protocol supported by RDMA +bool SupportedByRdma(std::string protocol); + +} // namespace rdma +} // namespace brpc +#else +namespace brpc { +namespace rdma { + +// Initialize RDMA environment +// Exit if failed +void GlobalRdmaInitializeOrDie(); + +} // namespace rdma +} // namespace brpc +#endif // if BRPC_WITH_RDMA + +#endif // BRPC_RDMA_HELPER_H diff --git a/src/brpc/rdma_transport.cpp b/src/brpc/rdma_transport.cpp new file mode 100644 index 0000000..88d89a7 --- /dev/null +++ b/src/brpc/rdma_transport.cpp @@ -0,0 +1,239 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +#if BRPC_WITH_RDMA + +#include "brpc/rdma_transport.h" +#include "brpc/event_dispatcher.h" +#include "brpc/tcp_transport.h" +#include "brpc/rdma/rdma_endpoint.h" +#include "brpc/rdma/rdma_helper.h" + +namespace brpc { +DECLARE_bool(usercode_in_coroutine); +DECLARE_bool(usercode_in_pthread); + +extern SocketVarsCollector *g_vars; + +void RdmaTransport::Init(Socket *socket, const SocketOptions &options) { + CHECK(_rdma_ep == NULL); + if (options.socket_mode == SOCKET_MODE_RDMA) { + _rdma_ep = new(std::nothrow)rdma::RdmaEndpoint(socket); + if (!_rdma_ep) { + const int saved_errno = errno; + PLOG(ERROR) << "Fail to create RdmaEndpoint"; + socket->SetFailed( + saved_errno, "Fail to create RdmaEndpoint: %s", berror(saved_errno)); + } + _rdma_state = RDMA_UNKNOWN; + } else { + _rdma_state = RDMA_OFF; + socket->_socket_mode = SOCKET_MODE_TCP; + } + _socket = socket; + _default_connect = options.app_connect; + _on_edge_trigger = options.on_edge_triggered_events; + if (options.need_on_edge_trigger && _on_edge_trigger == NULL) { + _on_edge_trigger = rdma::RdmaEndpoint::OnNewDataFromTcp; + } + _tcp_transport = std::make_shared(); + _tcp_transport->Init(socket, options); +} + +void RdmaTransport::Release() { + if (_rdma_ep) { + delete _rdma_ep; + _rdma_ep = NULL; + _rdma_state = RDMA_UNKNOWN; + } +} + +int RdmaTransport::Reset(int32_t expected_nref) { + if (_rdma_ep) { + _rdma_ep->Reset(); + _rdma_state = RDMA_UNKNOWN; + } + return 0; +} + +std::shared_ptr RdmaTransport::Connect() { + if (_default_connect == nullptr) { + return std::make_shared(); + } + return _default_connect; +} + +int RdmaTransport::CutFromIOBuf(butil::IOBuf *buf) { + if (_rdma_ep && _rdma_state != RDMA_OFF) { + butil::IOBuf *data_arr[1] = {buf}; + return _rdma_ep->CutFromIOBufList(data_arr, 1); + } else { + return _tcp_transport->CutFromIOBuf(buf); + } +} + +ssize_t RdmaTransport::CutFromIOBufList(butil::IOBuf **buf, size_t ndata) { + if (_rdma_ep && _rdma_state != RDMA_OFF) { + return _rdma_ep->CutFromIOBufList(buf, ndata); + } + return _tcp_transport->CutFromIOBufList(buf, ndata); +} + +int RdmaTransport::WaitEpollOut(butil::atomic *_epollout_butex, + bool pollin, const timespec duetime) { + if (_rdma_state == RDMA_ON) { + const int expected_val = _epollout_butex->load(butil::memory_order_acquire); + CHECK(_rdma_ep != NULL); + if (!_rdma_ep->IsWritable()) { + g_vars->nwaitepollout << 1; + if (bthread::butex_wait(_epollout_butex, expected_val, &duetime) < 0) { + if (errno != EAGAIN && errno != ETIMEDOUT) { + const int saved_errno = errno; + PLOG(WARNING) << "Fail to wait rdma window of " << _socket; + _socket->SetFailed(saved_errno, + "Fail to wait rdma window of %s: %s", + _socket->description().c_str(), + berror(saved_errno)); + } + if (_socket->Failed()) { + // NOTE: + // Different from TCP, we cannot find the RDMA channel + // failed by writing to it. Thus we must check if it + // is already failed here. + return 1; + } + } + } + } else { + return _tcp_transport->WaitEpollOut(_epollout_butex, pollin, duetime); + } + return 0; +} + +void RdmaTransport::ProcessEvent(bthread_attr_t attr) { + bthread_t tid; + if (FLAGS_usercode_in_coroutine) { + OnEdge(_socket); + } else if (!EventDispatcherUnsched()) { + auto rc = bthread_start_urgent(&tid, &attr, OnEdge, _socket); + if (rc != 0) { + LOG(FATAL) << "Fail to start ProcessEvent"; + OnEdge(_socket); + } + } else if (bthread_start_background(&tid, &attr, OnEdge, _socket) != 0) { + LOG(FATAL) << "Fail to start ProcessEvent"; + OnEdge(_socket); + } +} + +void RdmaTransport::QueueMessage(InputMessageClosure& input_msg, + int* num_bthread_created, bool last_msg) { + if (last_msg && !rdma::FLAGS_rdma_use_polling) { + return; + } + InputMessageBase* to_run_msg = input_msg.release(); + if (!to_run_msg) { + return; + } + + if (rdma::FLAGS_rdma_disable_bthread) { + ProcessInputMessage(to_run_msg); + return; + } + // Create bthread for last_msg. The bthread is not scheduled + // until bthread_flush() is called (in the worse case). + + // TODO(gejun): Join threads. + bthread_t th; + bthread_attr_t tmp = (FLAGS_usercode_in_pthread ? + BTHREAD_ATTR_PTHREAD : + BTHREAD_ATTR_NORMAL) | BTHREAD_NOSIGNAL; + tmp.keytable_pool = _socket->keytable_pool(); + tmp.tag = bthread_self_tag(); + bthread_attr_set_name(&tmp, "ProcessInputMessage"); + + if (!FLAGS_usercode_in_coroutine && bthread_start_background( + &th, &tmp, ProcessInputMessage, to_run_msg) == 0) { + ++*num_bthread_created; + } else { + ProcessInputMessage(to_run_msg); + } +} + +void RdmaTransport::Debug(std::ostream &os) { + if (_rdma_state == RDMA_ON && _rdma_ep) { + _rdma_ep->DebugInfo(os); + } +} + +int RdmaTransport::ContextInitOrDie(bool serverOrNot, const void* _options) { + if (serverOrNot) { + if (!OptionsAvailableOverRdma(static_cast(_options))) { + return -1; + } + rdma::GlobalRdmaInitializeOrDie(); + if (!rdma::InitPollingModeWithTag(static_cast(_options)->bthread_tag)) { + return -1; + } + } else { + if (!OptionsAvailableForRdma(static_cast(_options))) { + return -1; + } + rdma::GlobalRdmaInitializeOrDie(); + if (!rdma::InitPollingModeWithTag(bthread_self_tag())) { + return -1; + } + return 0; + } + + return 0; +} + +bool RdmaTransport::OptionsAvailableForRdma(const ChannelOptions* opt) { + if (opt->has_ssl_options()) { + LOG(WARNING) << "Cannot use SSL and RDMA at the same time"; + return false; + } + if (!rdma::SupportedByRdma(opt->protocol.name())) { + LOG(WARNING) << "Cannot use " << opt->protocol.name() + << " over RDMA"; + return false; + } + return true; +} + +bool RdmaTransport::OptionsAvailableOverRdma(const ServerOptions* opt) { + if (opt->rtmp_service) { + LOG(WARNING) << "RTMP is not supported by RDMA"; + return false; + } + if (opt->has_ssl_options()) { + LOG(WARNING) << "SSL is not supported by RDMA"; + return false; + } + if (opt->nshead_service) { + LOG(WARNING) << "NSHEAD is not supported by RDMA"; + return false; + } + if (opt->mongo_service_adaptor) { + LOG(WARNING) << "MONGO is not supported by RDMA"; + return false; + } + return true; +} +} // namespace brpc +#endif diff --git a/src/brpc/rdma_transport.h b/src/brpc/rdma_transport.h new file mode 100644 index 0000000..d8520b1 --- /dev/null +++ b/src/brpc/rdma_transport.h @@ -0,0 +1,66 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_RDMA_TRANSPORT_H +#define BRPC_RDMA_TRANSPORT_H + +#if BRPC_WITH_RDMA +#include "brpc/socket.h" +#include "brpc/channel.h" +#include "brpc/transport.h" + +namespace brpc { +class RdmaTransport : public Transport { +friend class TransportFactory; +friend class rdma::RdmaEndpoint; +friend class rdma::RdmaConnect; +friend class rdma::RdmaHandshakeServerV2; +public: + void Init(Socket* socket, const SocketOptions& options) override; + void Release() override; + int Reset(int32_t expected_nref) override; + std::shared_ptr Connect() override; + int CutFromIOBuf(butil::IOBuf* buf) override; + ssize_t CutFromIOBufList(butil::IOBuf** buf, size_t ndata) override; + int WaitEpollOut(butil::atomic* _epollout_butex, bool pollin, const timespec duetime) override; + void ProcessEvent(bthread_attr_t attr) override; + void QueueMessage(InputMessageClosure& inputMsg, int* num_bthread_created, bool last_msg) override; + void Debug(std::ostream &os) override; + rdma::RdmaEndpoint* GetRdmaEp() { + CHECK(_rdma_ep != NULL); + return _rdma_ep; + } + static int ContextInitOrDie(bool serverOrNot, const void* _options); +private: + static bool OptionsAvailableForRdma(const ChannelOptions* opt); + static bool OptionsAvailableOverRdma(const ServerOptions* opt); + + // The on/off state of RDMA + enum RdmaState { + RDMA_ON, + RDMA_OFF, + RDMA_UNKNOWN + }; + // The RdmaEndpoint + rdma::RdmaEndpoint* _rdma_ep = NULL; + // Should use RDMA or not + RdmaState _rdma_state; + std::shared_ptr _tcp_transport; +}; +} // namespace brpc +#endif // BRPC_WITH_RDMA +#endif //BRPC_RDMA_TRANSPORT_H \ No newline at end of file diff --git a/src/brpc/redis.cpp b/src/brpc/redis.cpp new file mode 100644 index 0000000..b9c5363 --- /dev/null +++ b/src/brpc/redis.cpp @@ -0,0 +1,395 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/redis.h" + +#include +#include // ReflectionOps::Merge + +#include "brpc/redis_command.h" +#include "brpc/proto_base.pb.h" +#include "butil/status.h" +#include "butil/strings/string_util.h" // StringToLowerASCII + +namespace brpc { + +DEFINE_bool(redis_verbose_crlf2space, false, "[DEBUG] Show \\r\\n as a space"); + +RedisRequest::RedisRequest() + : NonreflectableMessage() { + SharedCtor(); +} + +RedisRequest::RedisRequest(const RedisRequest& from) + : NonreflectableMessage(from) { + SharedCtor(); + MergeFrom(from); +} + +void RedisRequest::SharedCtor() { + _ncommand = 0; + _has_error = false; + _cached_size_ = 0; +} + +RedisRequest::~RedisRequest() { + SharedDtor(); +} + +void RedisRequest::SharedDtor() { +} + +void RedisRequest::SetCachedSize(int size) const { + _cached_size_ = size; +} + +void RedisRequest::Clear() { + _ncommand = 0; + _has_error = false; + _buf.clear(); +} + +size_t RedisRequest::ByteSizeLong() const { + int total_size = static_cast(_buf.size()); + _cached_size_ = total_size; + return total_size; +} + +void RedisRequest::MergeFrom(const RedisRequest& from) { + CHECK_NE(&from, this); + _has_error = _has_error || from._has_error; + _buf.append(from._buf); + _ncommand += from._ncommand; +} + +bool RedisRequest::IsInitialized() const { + return _ncommand != 0; +} + +void RedisRequest::Swap(RedisRequest* other) { + if (other != this) { + _buf.swap(other->_buf); + std::swap(_ncommand, other->_ncommand); + std::swap(_has_error, other->_has_error); + std::swap(_cached_size_, other->_cached_size_); + } +} + +bool RedisRequest::AddCommand(const butil::StringPiece& command) { + if (_has_error) { + return false; + } + const butil::Status st = RedisCommandNoFormat(&_buf, command); + if (st.ok()) { + ++_ncommand; + return true; + } else { + CHECK(st.ok()) << st; + _has_error = true; + return false; + } +} + +bool RedisRequest::AddCommandByComponents(const butil::StringPiece* components, + size_t n) { + if (_has_error) { + return false; + } + const butil::Status st = RedisCommandByComponents(&_buf, components, n); + if (st.ok()) { + ++_ncommand; + return true; + } else { + CHECK(st.ok()) << st; + _has_error = true; + return false; + } +} + +bool RedisRequest::AddCommandWithArgs(const char* fmt, ...) { + if (_has_error) { + return false; + } + va_list ap; + va_start(ap, fmt); + const butil::Status st = RedisCommandFormatV(&_buf, fmt, ap); + va_end(ap); + if (st.ok()) { + ++_ncommand; + return true; + } else { + CHECK(st.ok()) << st; + _has_error = true; + return false; + } +} + +bool RedisRequest::AddCommandV(const char* fmt, va_list ap) { + if (_has_error) { + return false; + } + const butil::Status st = RedisCommandFormatV(&_buf, fmt, ap); + if (st.ok()) { + ++_ncommand; + return true; + } else { + CHECK(st.ok()) << st; + _has_error = true; + return false; + } +} + +bool RedisRequest::SerializeTo(butil::IOBuf* buf) const { + if (_has_error) { + LOG(ERROR) << "Reject serialization due to error in AddCommand[V]"; + return false; + } + *buf = _buf; + return true; +} + +::google::protobuf::Metadata RedisRequest::GetMetadata() const { + ::google::protobuf::Metadata metadata{}; + metadata.descriptor = RedisRequestBase::descriptor(); + metadata.reflection = nullptr; + return metadata; +} + +void RedisRequest::Print(std::ostream& os) const { + butil::IOBuf cp = _buf; + butil::IOBuf seg; + while (cp.cut_until(&seg, "\r\n") == 0) { + os << seg; + if (FLAGS_redis_verbose_crlf2space) { + os << ' '; + } else { + os << "\\r\\n"; + } + seg.clear(); + } + if (!cp.empty()) { + os << cp; + } + if (_has_error) { + os << "[ERROR]"; + } +} + +std::ostream& operator<<(std::ostream& os, const RedisRequest& r) { + r.Print(os); + return os; +} + +RedisResponse::RedisResponse() + : NonreflectableMessage() + , _first_reply(&_arena) { + SharedCtor(); +} +RedisResponse::RedisResponse(const RedisResponse& from) + : NonreflectableMessage(from) + , _first_reply(&_arena) { + SharedCtor(); + MergeFrom(from); +} + +void RedisResponse::SharedCtor() { + _other_replies = NULL; + _cached_size_ = 0; + _nreply = 0; +} + +RedisResponse::~RedisResponse() { + SharedDtor(); +} + +void RedisResponse::SharedDtor() { +} + +void RedisResponse::SetCachedSize(int size) const { + _cached_size_ = size; +} + +void RedisResponse::Clear() { + _first_reply.Reset(); + _other_replies = NULL; + _arena.clear(); + _nreply = 0; + _cached_size_ = 0; +} + +size_t RedisResponse::ByteSizeLong() const { + return _cached_size_; +} + +void RedisResponse::MergeFrom(const RedisResponse& from) { + CHECK_NE(&from, this); + if (from._nreply == 0) { + return; + } + _cached_size_ += from._cached_size_; + if (_nreply == 0) { + _first_reply.CopyFromDifferentArena(from._first_reply); + } + const int new_nreply = _nreply + from._nreply; + if (new_nreply == 1) { + _nreply = new_nreply; + return; + } + RedisReply* new_others = + (RedisReply*)_arena.allocate(sizeof(RedisReply) * (new_nreply - 1)); + for (int i = 0; i < new_nreply - 1; ++i) { + new (new_others + i) RedisReply(&_arena); + } + int new_other_index = 0; + for (int i = 1; i < _nreply; ++i) { + new_others[new_other_index++].CopyFromSameArena( + _other_replies[i - 1]); + } + for (int i = !_nreply; i < from._nreply; ++i) { + new_others[new_other_index++].CopyFromDifferentArena(from.reply(i)); + } + DCHECK_EQ(new_nreply - 1, new_other_index); + _other_replies = new_others; + _nreply = new_nreply; +} + +bool RedisResponse::IsInitialized() const { + return reply_size() > 0; +} + +void RedisResponse::Swap(RedisResponse* other) { + if (other != this) { + _first_reply.Swap(other->_first_reply); + std::swap(_other_replies, other->_other_replies); + _arena.swap(other->_arena); + std::swap(_nreply, other->_nreply); + std::swap(_cached_size_, other->_cached_size_); + } +} + +::google::protobuf::Metadata RedisResponse::GetMetadata() const { + ::google::protobuf::Metadata metadata{}; + metadata.descriptor = RedisResponseBase::descriptor(); + metadata.reflection = nullptr; + return metadata; +} + +// =================================================================== + +ParseError RedisResponse::ConsumePartialIOBuf(butil::IOBuf& buf, int reply_count) { + size_t oldsize = buf.size(); + if (reply_size() == 0) { + ParseError err = _first_reply.ConsumePartialIOBuf(buf); + if (err != PARSE_OK) { + return err; + } + const size_t newsize = buf.size(); + _cached_size_ += oldsize - newsize; + oldsize = newsize; + ++_nreply; + } + if (reply_count > 1) { + if (_other_replies == NULL) { + _other_replies = (RedisReply*)_arena.allocate( + sizeof(RedisReply) * (reply_count - 1)); + if (_other_replies == NULL) { + LOG(ERROR) << "Fail to allocate RedisReply[" << reply_count -1 << "]"; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + for (int i = 0; i < reply_count - 1; ++i) { + new (&_other_replies[i]) RedisReply(&_arena); + } + } + for (int i = reply_size(); i < reply_count; ++i) { + ParseError err = _other_replies[i - 1].ConsumePartialIOBuf(buf); + if (err != PARSE_OK) { + return err; + } + const size_t newsize = buf.size(); + _cached_size_ += oldsize - newsize; + oldsize = newsize; + ++_nreply; + } + } + return PARSE_OK; +} + +std::ostream& operator<<(std::ostream& os, const RedisResponse& response) { + if (response.reply_size() == 0) { + return os << ""; + } else if (response.reply_size() == 1) { + return os << response.reply(0); + } else { + os << '['; + for (int i = 0; i < response.reply_size(); ++i) { + if (i) { + os << ", "; + } + os << response.reply(i); + } + os << ']'; + } + return os; +} + +bool RedisService::AddCommandHandler(const std::string& name, RedisCommandHandler* handler) { + std::string lcname = StringToLowerASCII(name); + auto it = _command_map.find(lcname); + if (it != _command_map.end()) { + LOG(ERROR) << "redis command name=" << name << " exist"; + return false; + } + _command_map[lcname] = handler; + return true; +} + +RedisCommandHandler* RedisService::FindCommandHandler(const butil::StringPiece& name) const { + auto it = _command_map.find(name.as_string()); + if (it != _command_map.end()) { + return it->second; + } + return NULL; +} + +RedisCommandHandler* RedisCommandHandler::NewTransactionHandler() { + LOG(ERROR) << "NewTransactionHandler is not implemented"; + return NULL; +} + +// ========== impl of RedisConnContext ========== +RedisConnContext::RedisConnContext(const RedisService* rs) + : redis_service(rs) + , batched_size(0) + , session(nullptr) {} + +RedisConnContext::~RedisConnContext() { } + +void RedisConnContext::Destroy() { + if (session) { + session->Destroy(); + } + delete this; +} + +void RedisConnContext::reset_session(Destroyable* s){ + if (session) { + session->Destroy(); + } + session = s; +} + +} // namespace brpc diff --git a/src/brpc/redis.h b/src/brpc/redis.h new file mode 100644 index 0000000..c140baf --- /dev/null +++ b/src/brpc/redis.h @@ -0,0 +1,298 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_REDIS_H +#define BRPC_REDIS_H + +#include + +#include "brpc/destroyable.h" +#include "brpc/nonreflectable_message.h" +#include "brpc/parse_result.h" +#include "brpc/redis_command.h" +#include "brpc/pb_compat.h" +#include "brpc/redis_reply.h" +#include "butil/arena.h" +#include "butil/iobuf.h" +#include "butil/strings/string_piece.h" + +namespace brpc { + +// Request to redis. +// Notice that you can pipeline multiple commands in one request and sent +// them to ONE redis-server together. +// Example: +// RedisRequest request; +// request.AddCommand("PING"); +// RedisResponse response; +// channel.CallMethod(&controller, &request, &response, NULL/*done*/); +// if (!cntl.Failed()) { +// LOG(INFO) << response.reply(0); +// } +class RedisRequest : public NonreflectableMessage { +public: + RedisRequest(); + ~RedisRequest() override; + RedisRequest(const RedisRequest& from); + inline RedisRequest& operator=(const RedisRequest& from) { + CopyFrom(from); + return *this; + } + void Swap(RedisRequest* other); + + // Add a command with a va_list to this request. The conversion + // specifiers are compatible with the ones used by hiredis, namely except + // that %b stands for binary data, other specifiers are similar with printf. + bool AddCommandV(const char* fmt, va_list args); + + // Concatenate components into a redis command, similarly with + // redisCommandArgv() in hiredis. + // Example: + // butil::StringPiece components[] = { "set", "key", "value" }; + // request.AddCommandByComponents(components, arraysize(components)); + bool AddCommandByComponents(const butil::StringPiece* components, size_t n); + + // Add a command with variadic args to this request. + // The reason that adding so many overloads rather than using ... is that + // it's the only way to dispatch the AddCommand w/o args differently. + bool AddCommand(const butil::StringPiece& command); + + template + bool AddCommand(const char* format, A1 a1) + { return AddCommandWithArgs(format, a1); } + + template + bool AddCommand(const char* format, A1 a1, A2 a2) + { return AddCommandWithArgs(format, a1, a2); } + + template + bool AddCommand(const char* format, A1 a1, A2 a2, A3 a3) + { return AddCommandWithArgs(format, a1, a2, a3); } + + template + bool AddCommand(const char* format, A1 a1, A2 a2, A3 a3, A4 a4) + { return AddCommandWithArgs(format, a1, a2, a3, a4); } + + template + bool AddCommand(const char* format, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) + { return AddCommandWithArgs(format, a1, a2, a3, a4, a5); } + + template + bool AddCommand(const char* format, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) + { return AddCommandWithArgs(format, a1, a2, a3, a4, a5, a6); } + + // Number of successfully added commands + int command_size() const { return _ncommand; } + + // True if previous AddCommand[V] failed. + bool has_error() const { return _has_error; } + + // Serialize the request into `buf'. Return true on success. + bool SerializeTo(butil::IOBuf* buf) const; + + // Protobuf methods. + void MergeFrom(const RedisRequest& from) override; + void Clear() override; + bool IsInitialized() const PB_527_OVERRIDE; + + size_t ByteSizeLong() const override; + int GetCachedSize() const PB_425_OVERRIDE { return _cached_size_; } + + void Print(std::ostream&) const; + + ::google::protobuf::Metadata GetMetadata() const PB_527_OVERRIDE; + +private: + void SharedCtor(); + void SharedDtor(); + void SetCachedSize(int size) const PB_425_OVERRIDE; + bool AddCommandWithArgs(const char* fmt, ...); + + int _ncommand; // # of valid commands + bool _has_error; // previous AddCommand had error + butil::IOBuf _buf; // the serialized request. + mutable int _cached_size_; // ByteSize +}; + +// Response from Redis. +// Notice that a RedisResponse instance may contain multiple replies +// due to pipelining. +class RedisResponse : public NonreflectableMessage { +public: + RedisResponse(); + ~RedisResponse() override; + RedisResponse(const RedisResponse& from); + inline RedisResponse& operator=(const RedisResponse& from) { + CopyFrom(from); + return *this; + } + void Swap(RedisResponse* other); + + // Number of replies in this response. + // (May have more than one reply due to pipeline) + int reply_size() const { return _nreply; } + + // Get index-th reply. If index is out-of-bound, nil reply is returned. + const RedisReply& reply(int index) const { + if (index < reply_size()) { + return (index == 0 ? _first_reply : _other_replies[index - 1]); + } + static RedisReply redis_nil(NULL); + return redis_nil; + } + + // Parse and consume intact replies from the buf. + // Returns PARSE_OK on success. + // Returns PARSE_ERROR_NOT_ENOUGH_DATA if data in `buf' is not enough to parse. + // Returns PARSE_ERROR_ABSOLUTELY_WRONG if the parsing failed. + ParseError ConsumePartialIOBuf(butil::IOBuf& buf, int reply_count); + + // implements Message ---------------------------------------------- + void MergeFrom(const RedisResponse& from) override; + void Clear() override; + bool IsInitialized() const PB_527_OVERRIDE; + + size_t ByteSizeLong() const override; + int GetCachedSize() const PB_425_OVERRIDE { return _cached_size_; } + + ::google::protobuf::Metadata GetMetadata() const PB_527_OVERRIDE; + +private: + void SharedCtor(); + void SharedDtor(); + void SetCachedSize(int size) const PB_425_OVERRIDE; + + RedisReply _first_reply; + RedisReply* _other_replies; + butil::Arena _arena; + int _nreply; + mutable int _cached_size_; +}; + +std::ostream& operator<<(std::ostream& os, const RedisRequest&); +std::ostream& operator<<(std::ostream& os, const RedisResponse&); + +class RedisCommandHandler; + +// Container of CommandHandlers. +// Assign an instance to ServerOption.redis_service to enable redis support. +class RedisService { +public: + virtual ~RedisService() {} + + // Call this function to register `handler` that can handle command `name`. + bool AddCommandHandler(const std::string& name, RedisCommandHandler* handler); + + // This function should not be touched by user and used by brpc deverloper only. + RedisCommandHandler* FindCommandHandler(const butil::StringPiece& name) const; + +private: + typedef std::unordered_map CommandMap; + CommandMap _command_map; +}; + +enum RedisCommandHandlerResult { + REDIS_CMD_HANDLED = 0, + REDIS_CMD_CONTINUE = 1, + REDIS_CMD_BATCHED = 2, +}; + +class RedisCommandParser; + +// This class is as parsing_context in socket. +class RedisConnContext : public Destroyable { +public: + explicit RedisConnContext(const RedisService* rs); + + ~RedisConnContext(); + // @Destroyable + void Destroy() override; + void reset_session(Destroyable* s); + + Destroyable* get_session() { return session; } + + const RedisService* redis_service; + // If user starts a transaction, transaction_handler indicates the + // handler pointer that runs the transaction command. + std::unique_ptr transaction_handler; + // >0 if command handler is run in batched mode. + int batched_size; + + RedisCommandParser parser; + butil::Arena arena; + +private: + // If user is authenticated, session is set. + // Keep auth session info in RedisConnContext to distinguish diffrent users( or diffrent db). + Destroyable* session; +}; + +// The Command handler for a redis request. User should impletement Run(). +class RedisCommandHandler { +public: + virtual ~RedisCommandHandler() {} + + // Once Server receives commands, it will first find the corresponding handlers and + // call them sequentially(one by one) according to the order that requests arrive, + // just like what redis-server does. + // `args' is the array of request command. For example, "set somekey somevalue" + // corresponds to args[0]=="set", args[1]=="somekey" and args[2]=="somevalue". + // `output', which should be filled by user, is the content that sent to client side. + // Read brpc/src/redis_reply.h for more usage. + // `flush_batched' indicates whether the user should flush all the results of + // batched commands. If user want to do some batch processing, user should buffer + // the commands and return REDIS_CMD_BATCHED. Once `flush_batched' is true, + // run all the commands, set `output' to be an array in which every element is the + // result of batched commands and return REDIS_CMD_HANDLED. + // + // The return value should be REDIS_CMD_HANDLED for normal cases. If you want + // to implement transaction, return REDIS_CMD_CONTINUE once server receives + // an start marker and brpc will call MultiTransactionHandler() to new a transaction + // handler that all the following commands are sent to this tranction handler until + // it returns REDIS_CMD_HANDLED. Read the comment below. + virtual RedisCommandHandlerResult Run(const std::vector& args, + brpc::RedisReply* output, + bool flush_batched) { + return REDIS_CMD_HANDLED; + }; + virtual RedisCommandHandlerResult Run(RedisConnContext* ctx, + const std::vector& args, + brpc::RedisReply* output, + bool flush_batched) { + return Run(args, output, flush_batched); + } + // The Run() returns CONTINUE for "multi", which makes brpc call this method to + // create a transaction_handler to process following commands until transaction_handler + // returns OK. For example, for command "multi; set k1 v1; set k2 v2; set k3 v3; + // exec": + // 1) First command is "multi" and Run() should return REDIS_CMD_CONTINUE, + // then brpc calls NewTransactionHandler() to new a transaction_handler. + // 2) brpc calls transaction_handler.Run() with command "set k1 v1", + // which should return CONTINUE. + // 3) brpc calls transaction_handler.Run() with command "set k2 v2", + // which should return CONTINUE. + // 4) brpc calls transaction_handler.Run() with command "set k3 v3", + // which should return CONTINUE. + // 5) An ending marker(exec) is found in transaction_handler.Run(), user exeuctes all + // the commands and return OK. This Transation is done. + virtual RedisCommandHandler* NewTransactionHandler(); +}; + +} // namespace brpc + +#endif // BRPC_REDIS_H diff --git a/src/brpc/redis_cluster.cpp b/src/brpc/redis_cluster.cpp new file mode 100644 index 0000000..f2531c9 --- /dev/null +++ b/src/brpc/redis_cluster.cpp @@ -0,0 +1,1219 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/redis_cluster.h" + +#include +#include +#include + +#include +#include +#include + +#include "butil/endpoint.h" +#include "butil/logging.h" +#include "brpc/controller.h" +#include "brpc/redis_command.h" + +namespace brpc { +namespace { + +static const size_t kRedisClusterSlotCount = 16384; + +static const uint16_t kCrc16Table[256] = { + 0x0000,0x1021,0x2042,0x3063,0x4084,0x50A5,0x60C6,0x70E7, + 0x8108,0x9129,0xA14A,0xB16B,0xC18C,0xD1AD,0xE1CE,0xF1EF, + 0x1231,0x0210,0x3273,0x2252,0x52B5,0x4294,0x72F7,0x62D6, + 0x9339,0x8318,0xB37B,0xA35A,0xD3BD,0xC39C,0xF3FF,0xE3DE, + 0x2462,0x3443,0x0420,0x1401,0x64E6,0x74C7,0x44A4,0x5485, + 0xA56A,0xB54B,0x8528,0x9509,0xE5EE,0xF5CF,0xC5AC,0xD58D, + 0x3653,0x2672,0x1611,0x0630,0x76D7,0x66F6,0x5695,0x46B4, + 0xB75B,0xA77A,0x9719,0x8738,0xF7DF,0xE7FE,0xD79D,0xC7BC, + 0x48C4,0x58E5,0x6886,0x78A7,0x0840,0x1861,0x2802,0x3823, + 0xC9CC,0xD9ED,0xE98E,0xF9AF,0x8948,0x9969,0xA90A,0xB92B, + 0x5AF5,0x4AD4,0x7AB7,0x6A96,0x1A71,0x0A50,0x3A33,0x2A12, + 0xDBFD,0xCBDC,0xFBBF,0xEB9E,0x9B79,0x8B58,0xBB3B,0xAB1A, + 0x6CA6,0x7C87,0x4CE4,0x5CC5,0x2C22,0x3C03,0x0C60,0x1C41, + 0xEDAE,0xFD8F,0xCDEC,0xDDCD,0xAD2A,0xBD0B,0x8D68,0x9D49, + 0x7E97,0x6EB6,0x5ED5,0x4EF4,0x3E13,0x2E32,0x1E51,0x0E70, + 0xFF9F,0xEFBE,0xDFDD,0xCFFC,0xBF1B,0xAF3A,0x9F59,0x8F78, + 0x9188,0x81A9,0xB1CA,0xA1EB,0xD10C,0xC12D,0xF14E,0xE16F, + 0x1080,0x00A1,0x30C2,0x20E3,0x5004,0x4025,0x7046,0x6067, + 0x83B9,0x9398,0xA3FB,0xB3DA,0xC33D,0xD31C,0xE37F,0xF35E, + 0x02B1,0x1290,0x22F3,0x32D2,0x4235,0x5214,0x6277,0x7256, + 0xB5EA,0xA5CB,0x95A8,0x8589,0xF56E,0xE54F,0xD52C,0xC50D, + 0x34E2,0x24C3,0x14A0,0x0481,0x7466,0x6447,0x5424,0x4405, + 0xA7DB,0xB7FA,0x8799,0x97B8,0xE75F,0xF77E,0xC71D,0xD73C, + 0x26D3,0x36F2,0x0691,0x16B0,0x6657,0x7676,0x4615,0x5634, + 0xD94C,0xC96D,0xF90E,0xE92F,0x99C8,0x89E9,0xB98A,0xA9AB, + 0x5844,0x4865,0x7806,0x6827,0x18C0,0x08E1,0x3882,0x28A3, + 0xCB7D,0xDB5C,0xEB3F,0xFB1E,0x8BF9,0x9BD8,0xABBB,0xBB9A, + 0x4A75,0x5A54,0x6A37,0x7A16,0x0AF1,0x1AD0,0x2AB3,0x3A92, + 0xFD2E,0xED0F,0xDD6C,0xCD4D,0xBDAA,0xAD8B,0x9DE8,0x8DC9, + 0x7C26,0x6C07,0x5C64,0x4C45,0x3CA2,0x2C83,0x1CE0,0x0CC1, + 0xEF1F,0xFF3E,0xCF5D,0xDF7C,0xAF9B,0xBFBA,0x8FD9,0x9FF8, + 0x6E17,0x7E36,0x4E55,0x5E74,0x2E93,0x3EB2,0x0ED1,0x1EF0 +}; + +static std::string Trim(const std::string& in) { + size_t begin = 0; + while (begin < in.size() && isspace(static_cast(in[begin]))) { + ++begin; + } + size_t end = in.size(); + while (end > begin && isspace(static_cast(in[end - 1]))) { + --end; + } + return in.substr(begin, end - begin); +} + +static std::vector SplitByChar(const std::string& text, char delim) { + std::vector out; + std::string current; + std::stringstream ss(text); + while (std::getline(ss, current, delim)) { + current = Trim(current); + if (!current.empty()) { + out.push_back(current); + } + } + return out; +} + +static std::vector SplitByWhitespace(const std::string& text) { + std::vector out; + std::stringstream ss(text); + std::string token; + while (ss >> token) { + out.push_back(token); + } + return out; +} + +static std::string EndpointHost(const std::string& endpoint) { + std::string ep = endpoint; + if (!ep.empty() && ep[0] == '[') { + const size_t right = ep.find(']'); + if (right != std::string::npos) { + return ep.substr(1, right - 1); + } + return ep; + } + const size_t p = ep.rfind(':'); + if (p == std::string::npos) { + return ep; + } + return ep.substr(0, p); +} + +static bool EncodeReply(const RedisReply& reply, butil::IOBuf* out) { + butil::IOBufAppender appender; + // RedisReply::SerializeTo does not support REDIS_REPLY_NIL directly. + // Encode nil as a null bulk string so response parsing can consume it. + if (reply.type() == REDIS_REPLY_NIL) { + appender.append("$-1\r\n", 5); + appender.move_to(*out); + return true; + } + if (!const_cast(reply).SerializeTo(&appender)) { + return false; + } + appender.move_to(*out); + return true; +} + +} // namespace + +RedisClusterChannelOptions::RedisClusterChannelOptions() + : max_redirect(5) + , refresh_interval_s(30) + , enable_periodic_refresh(true) + , topology_refresh_timeout_ms(1000) { + channel_options.protocol = brpc::PROTOCOL_REDIS; +} + +RedisClusterChannel::SingleCommandResult::SingleCommandResult() + : ok(false) + , is_status_ok(false) + , integer_value(0) + , is_error(false) { +} + +struct RedisClusterChannel::AsyncCall { + RedisClusterChannel* self; + Controller* cntl; + const RedisRequest* request; + RedisResponse* response; + google::protobuf::Closure* done; +}; + +RedisClusterChannel::RedisClusterChannel() + : _stop_refresh(false) + , _refresh_started(false) + , _refresh_tid(0) { + _db_slot_to_endpoint.Modify([](std::vector& bg) -> size_t { + bg.assign(kRedisClusterSlotCount, std::string()); + return 1; + }); +} + +RedisClusterChannel::~RedisClusterChannel() { + _stop_refresh.store(true); + if (_refresh_started) { + bthread_join(_refresh_tid, NULL); + } +} + +int RedisClusterChannel::Init(const std::string& seed_nodes, + const RedisClusterChannelOptions* options) { + if (seed_nodes.empty()) { + LOG(ERROR) << "seed_nodes is empty"; + return -1; + } + + RedisClusterChannelOptions resolved; + if (options) { + resolved = *options; + } + resolved.channel_options.protocol = brpc::PROTOCOL_REDIS; + _options = resolved; + + const std::vector seeds = SplitByChar(seed_nodes, ','); + if (seeds.empty()) { + LOG(ERROR) << "No valid seed endpoint in " << seed_nodes; + return -1; + } + + { + BAIDU_SCOPED_LOCK(_mutex); + _seed_endpoints = seeds; + } + + for (size_t i = 0; i < seeds.size(); ++i) { + if (GetOrCreateChannel(seeds[i]) == NULL) { + LOG(WARNING) << "Fail to init seed channel=" << seeds[i]; + } + } + + if (!RefreshTopology()) { + LOG(ERROR) << "Fail to fetch redis cluster topology from seeds"; + return -1; + } + + if (_options.enable_periodic_refresh && _options.refresh_interval_s > 0) { + _stop_refresh.store(false); + if (bthread_start_background(&_refresh_tid, NULL, + RedisClusterChannel::RunPeriodicRefresh, + this) == 0) { + _refresh_started = true; + } else { + LOG(WARNING) << "Fail to start periodic refresh bthread"; + } + } + return 0; +} + +void RedisClusterChannel::CallMethod( + const google::protobuf::MethodDescriptor* /*method*/, + google::protobuf::RpcController* controller_base, + const google::protobuf::Message* request_base, + google::protobuf::Message* response_base, + google::protobuf::Closure* done) { + Controller* cntl = static_cast(controller_base); + if (cntl == NULL) { + LOG(ERROR) << "controller is NULL"; + if (done) { + done->Run(); + } + return; + } + + if (request_base == NULL || + request_base->GetDescriptor() != RedisRequest::descriptor()) { + cntl->SetFailed(EREQUEST, "request must be RedisRequest"); + if (done) { + done->Run(); + } + return; + } + if (response_base == NULL || + response_base->GetDescriptor() != RedisResponse::descriptor()) { + cntl->SetFailed(ERESPONSE, "response must be RedisResponse"); + if (done) { + done->Run(); + } + return; + } + + const RedisRequest* request = static_cast(request_base); + RedisResponse* response = static_cast(response_base); + + if (done == NULL) { + CallMethodImpl(cntl, *request, response); + return; + } + + AsyncCall* ac = new (std::nothrow) AsyncCall; + if (ac == NULL) { + cntl->SetFailed(ENOMEM, "Fail to allocate async context"); + done->Run(); + return; + } + ac->self = this; + ac->cntl = cntl; + ac->request = request; + ac->response = response; + ac->done = done; + + bthread_t tid; + if (bthread_start_background(&tid, NULL, RedisClusterChannel::RunAsyncCall, ac) != 0) { + delete ac; + CallMethodImpl(cntl, *request, response); + done->Run(); + } +} + +bool RedisClusterChannel::CallMethodImpl(Controller* cntl, + const RedisRequest& request, + RedisResponse* response) { + std::vector commands; + if (!ParseRequest(request, &commands, cntl)) { + return false; + } + if (commands.empty()) { + cntl->SetFailed(EREQUEST, "request has no redis command"); + return false; + } + + std::vector replies(commands.size()); + for (size_t i = 0; i < commands.size(); ++i) { + if (!ExecuteCommand(commands[i], &replies[i], cntl)) { + return false; + } + } + + butil::IOBuf merged; + for (size_t i = 0; i < replies.size(); ++i) { + merged.append(replies[i]); + } + + response->Clear(); + ParseError err = response->ConsumePartialIOBuf(merged, static_cast(commands.size())); + if (err != PARSE_OK || !merged.empty()) { + cntl->SetFailed(ERESPONSE, "Fail to parse merged redis response"); + return false; + } + return true; +} + +bool RedisClusterChannel::ParseRequest(const RedisRequest& request, + std::vector* commands, + Controller* cntl) const { + commands->clear(); + + butil::IOBuf serialized; + if (!request.SerializeTo(&serialized)) { + cntl->SetFailed(EREQUEST, "Fail to serialize redis request"); + return false; + } + + RedisCommandParser parser; + butil::Arena arena; + + while (!serialized.empty()) { + std::vector args; + ParseError err = parser.Consume(serialized, &args, &arena); + if (err != PARSE_OK) { + cntl->SetFailed(EREQUEST, "Fail to parse redis request (err=%d)", err); + return false; + } + ParsedCommand cmd; + cmd.args.reserve(args.size()); + for (size_t i = 0; i < args.size(); ++i) { + cmd.args.push_back(args[i].as_string()); + } + commands->push_back(cmd); + } + return true; +} + +bool RedisClusterChannel::ExecuteCommand(const ParsedCommand& cmd, + butil::IOBuf* encoded_reply, + Controller* cntl) { + if (cmd.args.empty()) { + cntl->SetFailed(EREQUEST, "Empty redis command"); + return false; + } + const std::string& name = cmd.args[0]; + + if (name == "multi" || name == "exec") { + AppendErrorReply(encoded_reply, + "ERR MULTI/EXEC is not supported by RedisClusterChannel"); + return true; + } + if (name == "mget") { + return ExecuteMGet(cmd, encoded_reply, cntl); + } + if (name == "mset") { + return ExecuteMSet(cmd, encoded_reply, cntl); + } + if (name == "del" || name == "exists" || name == "unlink") { + return ExecuteIntegerAggregate(cmd, encoded_reply, cntl); + } + if (name == "eval" || name == "evalsha") { + return ExecuteEvalLike(cmd, encoded_reply, cntl); + } + + SingleCommandResult result; + if (!ExecuteSingleCommand(cmd.args, NULL, &result, cntl)) { + return false; + } + encoded_reply->swap(result.encoded_reply); + return true; +} + +bool RedisClusterChannel::ExecuteSingleCommand(const std::vector& args, + const std::string* forced_endpoint, + SingleCommandResult* result, + Controller* cntl) { + if (args.empty()) { + cntl->SetFailed(EREQUEST, "Empty redis command"); + return false; + } + + std::string endpoint; + int key_slot = -1; + if (forced_endpoint != NULL) { + endpoint = *forced_endpoint; + } else if (!IsNoKeyCommand(args[0]) && args.size() >= 2) { + if (!PickEndpointForKey(args[1], &endpoint, &key_slot)) { + RefreshTopology(); + if (!PickEndpointForKey(args[1], &endpoint, &key_slot)) { + cntl->SetFailed(EHOSTDOWN, "No endpoint found for key"); + return false; + } + } + } else { + if (!PickAnyEndpoint(&endpoint)) { + RefreshTopology(); + if (!PickAnyEndpoint(&endpoint)) { + cntl->SetFailed(EHOSTDOWN, "No endpoint available in redis cluster"); + return false; + } + } + } + + bool asking = false; + std::string next_endpoint = endpoint; + const int max_redirect = std::max(_options.max_redirect, 0); + for (int i = 0; i <= max_redirect; ++i) { + RedirectInfo redirect; + if (!SendToEndpoint(next_endpoint, args, asking, result, &redirect, cntl)) { + return false; + } + if (!redirect.valid) { + return true; + } + + if (!redirect.endpoint.empty()) { + next_endpoint = redirect.endpoint; + GetOrCreateChannel(next_endpoint); + } + // ASK is a temporary redirection during slot migration and should not + // overwrite the stable slot map. Only persist MOVED target. + if (!redirect.asking && + redirect.slot >= 0 && + redirect.slot < static_cast(kRedisClusterSlotCount) && + !redirect.endpoint.empty()) { + _db_slot_to_endpoint.Modify( + [](std::vector& bg, int slot, + const std::string& endpoint) -> size_t { + if (bg[slot] == endpoint) { + return 0; + } + bg[slot] = endpoint; + return 1; + }, + redirect.slot, redirect.endpoint); + } + + if (!redirect.asking) { + RefreshTopology(); + } + asking = redirect.asking; + } + + cntl->SetFailed(ERESPONSE, "Too many redis cluster redirects"); + return false; +} + +bool RedisClusterChannel::ExecuteMGet(const ParsedCommand& cmd, + butil::IOBuf* encoded_reply, + Controller* cntl) { + if (cmd.args.size() < 2) { + AppendErrorReply(encoded_reply, + "ERR wrong number of arguments for 'mget' command"); + return true; + } + + std::vector values; + values.reserve(cmd.args.size() - 1); + for (size_t i = 1; i < cmd.args.size(); ++i) { + std::vector sub_args; + sub_args.push_back("get"); + sub_args.push_back(cmd.args[i]); + SingleCommandResult sub_result; + if (!ExecuteSingleCommand(sub_args, NULL, &sub_result, cntl)) { + return false; + } + values.push_back(sub_result.encoded_reply); + } + + AppendArrayHeader(encoded_reply, values.size()); + for (size_t i = 0; i < values.size(); ++i) { + encoded_reply->append(values[i]); + } + return true; +} + +bool RedisClusterChannel::ExecuteMSet(const ParsedCommand& cmd, + butil::IOBuf* encoded_reply, + Controller* cntl) { + if (cmd.args.size() < 3 || ((cmd.args.size() - 1) % 2 != 0)) { + AppendErrorReply(encoded_reply, + "ERR wrong number of arguments for 'mset' command"); + return true; + } + + for (size_t i = 1; i + 1 < cmd.args.size(); i += 2) { + std::vector sub_args; + sub_args.push_back("set"); + sub_args.push_back(cmd.args[i]); + sub_args.push_back(cmd.args[i + 1]); + + SingleCommandResult sub_result; + if (!ExecuteSingleCommand(sub_args, NULL, &sub_result, cntl)) { + return false; + } + if (sub_result.is_error || !sub_result.is_status_ok) { + encoded_reply->swap(sub_result.encoded_reply); + return true; + } + } + + AppendStatusReply(encoded_reply, "OK"); + return true; +} + +bool RedisClusterChannel::ExecuteIntegerAggregate(const ParsedCommand& cmd, + butil::IOBuf* encoded_reply, + Controller* cntl) { + if (cmd.args.size() < 2) { + AppendErrorReply(encoded_reply, + "ERR wrong number of arguments"); + return true; + } + + int64_t total = 0; + for (size_t i = 1; i < cmd.args.size(); ++i) { + std::vector sub_args; + sub_args.push_back(cmd.args[0]); + sub_args.push_back(cmd.args[i]); + + SingleCommandResult sub_result; + if (!ExecuteSingleCommand(sub_args, NULL, &sub_result, cntl)) { + return false; + } + if (sub_result.is_error) { + encoded_reply->swap(sub_result.encoded_reply); + return true; + } + total += sub_result.integer_value; + } + + AppendIntegerReply(encoded_reply, total); + return true; +} + +bool RedisClusterChannel::ExecuteEvalLike(const ParsedCommand& cmd, + butil::IOBuf* encoded_reply, + Controller* cntl) { + if (cmd.args.size() < 3) { + AppendErrorReply(encoded_reply, + "ERR wrong number of arguments for eval/evalsha"); + return true; + } + + int64_t numkeys = 0; + if (!ParseInt(cmd.args[2], &numkeys) || numkeys < 0) { + AppendErrorReply(encoded_reply, + "ERR invalid numkeys for eval/evalsha"); + return true; + } + + if (cmd.args.size() < static_cast(3 + numkeys)) { + AppendErrorReply(encoded_reply, + "ERR not enough keys for eval/evalsha"); + return true; + } + + std::string forced_endpoint; + if (numkeys > 0) { + const std::string tag_key = cmd.args[3]; + const int first_slot = HashSlot(tag_key); + for (int64_t i = 1; i < numkeys; ++i) { + if (HashSlot(cmd.args[3 + i]) != first_slot) { + AppendErrorReply(encoded_reply, + "CROSSSLOT Keys in request don't hash to the same slot"); + return true; + } + } + + int slot = -1; + if (!PickEndpointForKey(tag_key, &forced_endpoint, &slot)) { + RefreshTopology(); + if (!PickEndpointForKey(tag_key, &forced_endpoint, &slot)) { + cntl->SetFailed(EHOSTDOWN, "No endpoint found for eval/evalsha"); + return false; + } + } + } + + SingleCommandResult result; + if (!ExecuteSingleCommand(cmd.args, + numkeys > 0 ? &forced_endpoint : NULL, + &result, + cntl)) { + return false; + } + encoded_reply->swap(result.encoded_reply); + return true; +} + +bool RedisClusterChannel::PickEndpointForKey(const std::string& key, + std::string* endpoint, + int* slot) const { + const int key_slot = HashSlot(key); + if (key_slot < 0 || key_slot >= static_cast(kRedisClusterSlotCount)) { + return false; + } + butil::DoublyBufferedData >::ScopedPtr s; + if (_db_slot_to_endpoint.Read(&s) != 0 || + static_cast(key_slot) >= s->size()) { + return false; + } + const std::string& mapped = (*s)[key_slot]; + if (mapped.empty()) { + return false; + } + *endpoint = mapped; + if (slot != NULL) { + *slot = key_slot; + } + return true; +} + +bool RedisClusterChannel::PickAnyEndpoint(std::string* endpoint) const { + BAIDU_SCOPED_LOCK(_mutex); + for (std::unordered_map >::const_iterator + it = _channels.begin(); it != _channels.end(); ++it) { + *endpoint = it->first; + return true; + } + if (!_seed_endpoints.empty()) { + *endpoint = _seed_endpoints.front(); + return true; + } + return false; +} + +bool RedisClusterChannel::SendToEndpoint(const std::string& endpoint, + const std::vector& args, + bool asking, + SingleCommandResult* result, + RedirectInfo* redirect, + Controller* cntl) { + result->ok = false; + redirect->valid = false; + redirect->asking = false; + redirect->slot = -1; + redirect->endpoint.clear(); + + Channel* channel = GetOrCreateChannel(endpoint); + if (channel == NULL) { + cntl->SetFailed(EHOSTDOWN, "Fail to get channel for %s", endpoint.c_str()); + return false; + } + + RedisRequest request; + if (asking) { + if (!request.AddCommand("asking")) { + cntl->SetFailed(EREQUEST, "Fail to build ASKING command"); + return false; + } + std::vector components; + components.reserve(args.size()); + for (size_t i = 0; i < args.size(); ++i) { + components.push_back(args[i]); + } + if (!request.AddCommandByComponents(&components[0], components.size())) { + cntl->SetFailed(EREQUEST, "Fail to build redis command"); + return false; + } + } else { + if (!BuildRedisRequest(args, &request)) { + cntl->SetFailed(EREQUEST, "Fail to build redis command"); + return false; + } + } + + RedisResponse response; + Controller sub_cntl; + if (cntl->timeout_ms() > 0) { + sub_cntl.set_timeout_ms(cntl->timeout_ms()); + } + channel->CallMethod(NULL, &sub_cntl, &request, &response, NULL); + if (sub_cntl.Failed()) { + cntl->SetFailed(sub_cntl.ErrorCode(), + "Redis cluster sub-request to %s failed: %s", + endpoint.c_str(), + sub_cntl.ErrorText().c_str()); + return false; + } + + const int expected = asking ? 2 : 1; + if (response.reply_size() != expected) { + cntl->SetFailed(ERESPONSE, + "Unexpected redis response size=%d expected=%d", + response.reply_size(), + expected); + return false; + } + + const RedisReply& selected = response.reply(asking ? 1 : 0); + if (!EncodeReply(selected, &result->encoded_reply)) { + cntl->SetFailed(ERESPONSE, "Fail to encode redis reply"); + return false; + } + + result->ok = true; + result->is_error = selected.is_error(); + result->is_status_ok = (selected.type() == REDIS_REPLY_STATUS && + selected.data() == "OK"); + result->integer_value = selected.is_integer() ? selected.integer() : 0; + if (result->is_error) { + result->error_text = selected.error_message(); + } else { + result->error_text.clear(); + } + + ParseRedirectReply(*result, redirect); + return true; +} + +bool RedisClusterChannel::RefreshTopology() { + std::vector candidates; + { + BAIDU_SCOPED_LOCK(_mutex); + candidates = _seed_endpoints; + for (std::unordered_map >::const_iterator + it = _channels.begin(); it != _channels.end(); ++it) { + candidates.push_back(it->first); + } + } + + std::sort(candidates.begin(), candidates.end()); + candidates.erase(std::unique(candidates.begin(), candidates.end()), candidates.end()); + + for (size_t i = 0; i < candidates.size(); ++i) { + if (RefreshTopologyFromEndpoint(candidates[i])) { + return true; + } + } + return false; +} + +bool RedisClusterChannel::RefreshTopologyFromEndpoint(const std::string& endpoint) { + Channel* channel = GetOrCreateChannel(endpoint); + if (channel == NULL) { + return false; + } + + std::vector slot_to_endpoint(kRedisClusterSlotCount); + std::vector discovered; + if (FetchAndParseClusterSlots(channel, endpoint, + &slot_to_endpoint, &discovered)) { + ApplyTopology(slot_to_endpoint, discovered); + return true; + } + + slot_to_endpoint.assign(kRedisClusterSlotCount, std::string()); + discovered.clear(); + if (FetchAndParseClusterNodes(channel, &slot_to_endpoint, &discovered)) { + ApplyTopology(slot_to_endpoint, discovered); + return true; + } + return false; +} + +bool RedisClusterChannel::FetchAndParseClusterSlots( + Channel* channel, + const std::string& endpoint, + std::vector* slot_to_endpoint, + std::vector* discovered_endpoints) { + RedisRequest request; + if (!request.AddCommand("cluster slots")) { + return false; + } + + RedisResponse response; + Controller cntl; + cntl.set_timeout_ms(_options.topology_refresh_timeout_ms); + channel->CallMethod(NULL, &cntl, &request, &response, NULL); + if (cntl.Failed()) { + return false; + } + + if (response.reply_size() != 1 || !response.reply(0).is_array()) { + return false; + } + + const RedisReply& root = response.reply(0); + const std::string fallback_host = EndpointHost(endpoint); + bool has_slot = false; + for (size_t i = 0; i < root.size(); ++i) { + const RedisReply& item = root[i]; + if (!item.is_array() || item.size() < 3) { + continue; + } + if (!item[0].is_integer() || !item[1].is_integer()) { + continue; + } + + int64_t start = item[0].integer(); + int64_t end = item[1].integer(); + if (start < 0 || end < start) { + continue; + } + + if (!item[2].is_array() || item[2].size() < 2 || !item[2][1].is_integer()) { + continue; + } + std::string host; + if (item[2][0].is_string()) { + host = item[2][0].data().as_string(); + } + if (host.empty()) { + host = fallback_host; + } + + const int64_t port = item[2][1].integer(); + if (port <= 0 || port > 65535) { + continue; + } + + std::ostringstream oss; + oss << host << ":" << port; + const std::string master_endpoint = oss.str(); + discovered_endpoints->push_back(master_endpoint); + + start = std::max(start, 0); + end = std::min(end, static_cast(kRedisClusterSlotCount - 1)); + for (int64_t slot = start; slot <= end; ++slot) { + (*slot_to_endpoint)[slot] = master_endpoint; + has_slot = true; + } + } + return has_slot; +} + +bool RedisClusterChannel::FetchAndParseClusterNodes( + Channel* channel, + std::vector* slot_to_endpoint, + std::vector* discovered_endpoints) { + RedisRequest request; + if (!request.AddCommand("cluster nodes")) { + return false; + } + + RedisResponse response; + Controller cntl; + cntl.set_timeout_ms(_options.topology_refresh_timeout_ms); + channel->CallMethod(NULL, &cntl, &request, &response, NULL); + if (cntl.Failed()) { + return false; + } + + if (response.reply_size() != 1 || !response.reply(0).is_string()) { + return false; + } + + bool has_slot = false; + const std::string payload = response.reply(0).data().as_string(); + const std::vector lines = SplitByChar(payload, '\n'); + for (size_t i = 0; i < lines.size(); ++i) { + std::string line = lines[i]; + if (!line.empty() && line[line.size() - 1] == '\r') { + line.resize(line.size() - 1); + } + const std::vector fields = SplitByWhitespace(line); + if (fields.size() < 8) { + continue; + } + + const std::string& flags = fields[2]; + if (flags.find("master") == std::string::npos) { + continue; + } + if (flags.find("fail") != std::string::npos || + flags.find("handshake") != std::string::npos || + flags.find("noaddr") != std::string::npos) { + continue; + } + + std::string endpoint; + if (!ParseRedisNodeAddress(fields[1], &endpoint)) { + continue; + } + discovered_endpoints->push_back(endpoint); + + for (size_t j = 8; j < fields.size(); ++j) { + const std::string& slot_token = fields[j]; + if (slot_token.empty() || + slot_token[0] == '[' || + slot_token.find("->") != std::string::npos || + slot_token.find("<-") != std::string::npos) { + continue; + } + + size_t dash = slot_token.find('-'); + int64_t start = 0; + int64_t end = 0; + if (dash == std::string::npos) { + if (!ParseInt(slot_token, &start)) { + continue; + } + end = start; + } else { + if (!ParseInt(slot_token.substr(0, dash), &start) || + !ParseInt(slot_token.substr(dash + 1), &end)) { + continue; + } + } + + if (start < 0 || end < start) { + continue; + } + start = std::max(start, 0); + end = std::min(end, static_cast(kRedisClusterSlotCount - 1)); + for (int64_t slot = start; slot <= end; ++slot) { + (*slot_to_endpoint)[slot] = endpoint; + has_slot = true; + } + } + } + return has_slot; +} + +void RedisClusterChannel::ApplyTopology( + const std::vector& slot_to_endpoint, + const std::vector& discovered_endpoints) { + std::set unique_eps; + for (size_t i = 0; i < discovered_endpoints.size(); ++i) { + if (!discovered_endpoints[i].empty()) { + unique_eps.insert(discovered_endpoints[i]); + } + } + for (size_t i = 0; i < slot_to_endpoint.size(); ++i) { + if (!slot_to_endpoint[i].empty()) { + unique_eps.insert(slot_to_endpoint[i]); + } + } + + for (std::set::const_iterator it = unique_eps.begin(); + it != unique_eps.end(); ++it) { + GetOrCreateChannel(*it); + } + + _db_slot_to_endpoint.Modify( + [](std::vector& bg, + const std::vector& src) -> size_t { + bg = src; + return 1; + }, + slot_to_endpoint); + + BAIDU_SCOPED_LOCK(_mutex); + for (std::set::const_iterator it = unique_eps.begin(); + it != unique_eps.end(); ++it) { + if (std::find(_seed_endpoints.begin(), _seed_endpoints.end(), *it) == + _seed_endpoints.end()) { + _seed_endpoints.push_back(*it); + } + } +} + +Channel* RedisClusterChannel::GetOrCreateChannel(const std::string& endpoint) { + { + BAIDU_SCOPED_LOCK(_mutex); + std::unordered_map >::iterator it = + _channels.find(endpoint); + if (it != _channels.end()) { + return it->second.get(); + } + } + + std::unique_ptr new_channel(new (std::nothrow) Channel); + if (!new_channel) { + return NULL; + } + ChannelOptions options = _options.channel_options; + options.protocol = brpc::PROTOCOL_REDIS; + if (new_channel->Init(endpoint.c_str(), &options) != 0) { + return NULL; + } + + BAIDU_SCOPED_LOCK(_mutex); + std::unordered_map >::iterator it = + _channels.find(endpoint); + if (it != _channels.end()) { + return it->second.get(); + } + Channel* ptr = new_channel.get(); + _channels[endpoint] = std::move(new_channel); + return ptr; +} + +uint16_t RedisClusterChannel::HashSlot(const std::string& key) { + const std::string hashed = ExtractHashtag(key); + uint16_t crc = 0; + for (size_t i = 0; i < hashed.size(); ++i) { + const uint8_t idx = static_cast((crc >> 8) ^ + static_cast(hashed[i])); + crc = static_cast((crc << 8) ^ kCrc16Table[idx]); + } + return crc & (kRedisClusterSlotCount - 1); +} + +std::string RedisClusterChannel::ExtractHashtag(const std::string& key) { + const size_t begin = key.find('{'); + if (begin == std::string::npos) { + return key; + } + const size_t end = key.find('}', begin + 1); + if (end == std::string::npos || end == begin + 1) { + return key; + } + return key.substr(begin + 1, end - begin - 1); +} + +bool RedisClusterChannel::ParseRedirectReply(const SingleCommandResult& result, + RedirectInfo* redirect) { + redirect->valid = false; + redirect->asking = false; + redirect->slot = -1; + redirect->endpoint.clear(); + + if (!result.is_error || result.error_text.empty()) { + return false; + } + + std::vector fields = SplitByWhitespace(result.error_text); + if (fields.size() < 3) { + return false; + } + + if (fields[0] == "MOVED") { + redirect->asking = false; + } else if (fields[0] == "ASK") { + redirect->asking = true; + } else { + return false; + } + + int64_t slot = -1; + if (!ParseInt(fields[1], &slot)) { + return false; + } + std::string endpoint; + if (!ParseRedisNodeAddress(fields[2], &endpoint)) { + return false; + } + + redirect->valid = true; + redirect->slot = static_cast(slot); + redirect->endpoint = endpoint; + return true; +} + +bool RedisClusterChannel::BuildRedisRequest(const std::vector& args, + RedisRequest* request) { + request->Clear(); + if (args.empty()) { + return false; + } + std::vector components; + components.reserve(args.size()); + for (size_t i = 0; i < args.size(); ++i) { + components.push_back(args[i]); + } + return request->AddCommandByComponents(&components[0], components.size()); +} + +void RedisClusterChannel::AppendIntegerReply(butil::IOBuf* buf, int64_t value) { + std::ostringstream oss; + oss << ':' << value << "\r\n"; + buf->append(oss.str()); +} + +void RedisClusterChannel::AppendStatusReply(butil::IOBuf* buf, + const std::string& value) { + buf->push_back('+'); + buf->append(value); + buf->append("\r\n"); +} + +void RedisClusterChannel::AppendErrorReply(butil::IOBuf* buf, + const std::string& value) { + buf->push_back('-'); + buf->append(value); + buf->append("\r\n"); +} + +void RedisClusterChannel::AppendArrayHeader(butil::IOBuf* buf, size_t size) { + std::ostringstream oss; + oss << '*' << size << "\r\n"; + buf->append(oss.str()); +} + +bool RedisClusterChannel::ParseRedisNodeAddress(const std::string& token, + std::string* endpoint) { + if (token.empty()) { + return false; + } + std::string t = token; + + const size_t comma = t.find(','); + if (comma != std::string::npos) { + t.resize(comma); + } + const size_t at = t.find('@'); + if (at != std::string::npos) { + t.resize(at); + } + + std::string host; + int64_t port = 0; + if (!t.empty() && t[0] == '[') { + const size_t r = t.find(']'); + if (r == std::string::npos || r + 2 > t.size() || t[r + 1] != ':') { + return false; + } + host = t.substr(1, r - 1); + if (!ParseInt(t.substr(r + 2), &port)) { + return false; + } + std::ostringstream oss; + oss << '[' << host << "]:" << port; + *endpoint = oss.str(); + return true; + } + + const size_t pos = t.rfind(':'); + if (pos == std::string::npos) { + return false; + } + host = t.substr(0, pos); + if (!ParseInt(t.substr(pos + 1), &port)) { + return false; + } + if (host.empty() || port <= 0 || port > 65535) { + return false; + } + + std::ostringstream oss; + oss << host << ':' << port; + *endpoint = oss.str(); + return true; +} + +bool RedisClusterChannel::ParseInt(const std::string& s, int64_t* out) { + if (s.empty()) { + return false; + } + char* end = NULL; + errno = 0; + const long long value = strtoll(s.c_str(), &end, 10); + if (errno != 0 || end != s.c_str() + s.size()) { + return false; + } + *out = value; + return true; +} + +bool RedisClusterChannel::IsNoKeyCommand(const std::string& cmd) { + return (cmd == "ping" || cmd == "info" || cmd == "auth" || + cmd == "select" || cmd == "echo" || cmd == "time" || + cmd == "dbsize" || cmd == "cluster"); +} + +void* RedisClusterChannel::RunPeriodicRefresh(void* arg) { + RedisClusterChannel* self = static_cast(arg); + while (!self->_stop_refresh.load()) { + const int interval_s = std::max(self->_options.refresh_interval_s, 1); + int64_t remain_us = interval_s * 1000000L; + while (remain_us > 0 && !self->_stop_refresh.load()) { + const int64_t step_us = std::min(remain_us, 100000L); + bthread_usleep(step_us); + remain_us -= step_us; + } + if (self->_stop_refresh.load()) { + break; + } + self->RefreshTopology(); + } + return NULL; +} + +void* RedisClusterChannel::RunAsyncCall(void* arg) { + std::unique_ptr ac(static_cast(arg)); + ac->self->CallMethodImpl(ac->cntl, *ac->request, ac->response); + ac->done->Run(); + return NULL; +} + +int RedisClusterChannel::CheckHealth() { + std::string endpoint; + return PickAnyEndpoint(&endpoint) ? 0 : -1; +} + +int RedisClusterChannel::Weight() { + std::set unique; + butil::DoublyBufferedData >::ScopedPtr s; + if (_db_slot_to_endpoint.Read(&s) != 0) { + return 0; + } + for (size_t i = 0; i < s->size(); ++i) { + if (!(*s)[i].empty()) { + unique.insert((*s)[i]); + } + } + return static_cast(unique.size()); +} + +} // namespace brpc diff --git a/src/brpc/redis_cluster.h b/src/brpc/redis_cluster.h new file mode 100644 index 0000000..1250a8e --- /dev/null +++ b/src/brpc/redis_cluster.h @@ -0,0 +1,184 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_REDIS_CLUSTER_H +#define BRPC_REDIS_CLUSTER_H + +#include + +#include +#include +#include +#include +#include + +#include "bthread/bthread.h" +#include "butil/containers/doubly_buffered_data.h" +#include "butil/synchronization/lock.h" +#include "brpc/channel.h" +#include "brpc/channel_base.h" +#include "brpc/redis.h" + +namespace brpc { + +struct RedisClusterChannelOptions { + RedisClusterChannelOptions(); + + ChannelOptions channel_options; + int max_redirect; + int refresh_interval_s; + bool enable_periodic_refresh; + int topology_refresh_timeout_ms; +}; + +// Channel implementation for Redis Cluster. +class RedisClusterChannel : public ChannelBase { +public: + RedisClusterChannel(); + ~RedisClusterChannel() override; + + DISALLOW_COPY_AND_ASSIGN(RedisClusterChannel); + + int Init(const std::string& seed_nodes, + const RedisClusterChannelOptions* options = NULL); + + void CallMethod(const google::protobuf::MethodDescriptor* method, + google::protobuf::RpcController* controller, + const google::protobuf::Message* request, + google::protobuf::Message* response, + google::protobuf::Closure* done) override; + + int CheckHealth() override; + int Weight() override; + +private: + struct ParsedCommand { + std::vector args; + }; + + struct RedirectInfo { + bool valid; + bool asking; + int slot; + std::string endpoint; + }; + + struct SingleCommandResult { + bool ok; + butil::IOBuf encoded_reply; + bool is_status_ok; + int64_t integer_value; + bool is_error; + std::string error_text; + SingleCommandResult(); + }; + + struct AsyncCall; + + static void* RunPeriodicRefresh(void* arg); + static void* RunAsyncCall(void* arg); + + bool CallMethodImpl(Controller* cntl, + const RedisRequest& request, + RedisResponse* response); + + bool ParseRequest(const RedisRequest& request, + std::vector* commands, + Controller* cntl) const; + + bool ExecuteCommand(const ParsedCommand& cmd, + butil::IOBuf* encoded_reply, + Controller* cntl); + + bool ExecuteSingleCommand(const std::vector& args, + const std::string* forced_endpoint, + SingleCommandResult* result, + Controller* cntl); + + bool ExecuteMGet(const ParsedCommand& cmd, + butil::IOBuf* encoded_reply, + Controller* cntl); + bool ExecuteMSet(const ParsedCommand& cmd, + butil::IOBuf* encoded_reply, + Controller* cntl); + bool ExecuteIntegerAggregate(const ParsedCommand& cmd, + butil::IOBuf* encoded_reply, + Controller* cntl); + bool ExecuteEvalLike(const ParsedCommand& cmd, + butil::IOBuf* encoded_reply, + Controller* cntl); + + bool PickEndpointForKey(const std::string& key, std::string* endpoint, int* slot) const; + bool PickAnyEndpoint(std::string* endpoint) const; + + bool SendToEndpoint(const std::string& endpoint, + const std::vector& args, + bool asking, + SingleCommandResult* result, + RedirectInfo* redirect, + Controller* cntl); + + bool RefreshTopology(); + bool RefreshTopologyFromEndpoint(const std::string& endpoint); + bool FetchAndParseClusterSlots(Channel* channel, + const std::string& endpoint, + std::vector* slot_to_endpoint, + std::vector* discovered_endpoints); + bool FetchAndParseClusterNodes(Channel* channel, + std::vector* slot_to_endpoint, + std::vector* discovered_endpoints); + + void ApplyTopology(const std::vector& slot_to_endpoint, + const std::vector& discovered_endpoints); + + Channel* GetOrCreateChannel(const std::string& endpoint); + + static uint16_t HashSlot(const std::string& key); + static std::string ExtractHashtag(const std::string& key); + static bool ParseRedirectReply(const SingleCommandResult& result, + RedirectInfo* redirect); + + static bool BuildRedisRequest(const std::vector& args, + RedisRequest* request); + + static void AppendIntegerReply(butil::IOBuf* buf, int64_t value); + static void AppendStatusReply(butil::IOBuf* buf, const std::string& value); + static void AppendErrorReply(butil::IOBuf* buf, const std::string& value); + static void AppendArrayHeader(butil::IOBuf* buf, size_t size); + + static bool ParseRedisNodeAddress(const std::string& token, + std::string* endpoint); + static bool ParseInt(const std::string& s, int64_t* out); + + static bool IsNoKeyCommand(const std::string& cmd); + +private: + RedisClusterChannelOptions _options; + + mutable butil::Mutex _mutex; + mutable butil::DoublyBufferedData > _db_slot_to_endpoint; + std::vector _seed_endpoints; + std::unordered_map > _channels; + + std::atomic _stop_refresh; + bool _refresh_started; + bthread_t _refresh_tid; +}; + +} // namespace brpc + +#endif // BRPC_REDIS_CLUSTER_H diff --git a/src/brpc/redis_command.cpp b/src/brpc/redis_command.cpp new file mode 100644 index 0000000..ccc6461 --- /dev/null +++ b/src/brpc/redis_command.cpp @@ -0,0 +1,629 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include + +#include "butil/logging.h" +#include "brpc/log.h" +#include "brpc/redis_command.h" +#include "gflags/gflags.h" + +namespace { + +const size_t CTX_WIDTH = 5; +const size_t CRLF_NOT_FOUND = (size_t)-1; + +size_t FindCRLF(const butil::IOBuf& buf, size_t max_scan_size) { + butil::IOBufBytesIterator it(buf); + bool prev_cr = false; + size_t pos = 0; + while (it && pos < max_scan_size) { + const char c = static_cast(*it); + if (prev_cr && c == '\n') { + return pos - 1; + } + prev_cr = (c == '\r'); + ++it; + ++pos; + } + return CRLF_NOT_FOUND; +} + +} // namespace + +namespace brpc { + +DECLARE_int32(redis_max_allocation_size); + +// Much faster than snprintf(..., "%lu", d); +inline size_t AppendDecimal(char* outbuf, unsigned long d) { + char buf[24]; // enough for decimal 64-bit integers + size_t n = sizeof(buf); + do { + const unsigned long q = d / 10; + buf[--n] = d - q * 10 + '0'; + d = q; + } while (d); + fast_memcpy(outbuf, buf + n, sizeof(buf) - n); + return sizeof(buf) - n; +} + +// This function is the hotspot of RedisCommandFormatV() when format is +// short or does not have many %. In a 100K-time call to formating of +// "GET key1", the time spent on RedisRequest.AddCommand() are ~700ns +// vs. ~400ns while using snprintf() vs. AppendDecimal() respectively. +inline void AppendHeader(std::string& buf, char fc, unsigned long value) { + char header[32]; + header[0] = fc; + size_t len = AppendDecimal(header + 1, value); + header[len + 1] = '\r'; + header[len + 2] = '\n'; + buf.append(header, len + 3); +} +inline void AppendHeader(butil::IOBuf& buf, char fc, unsigned long value) { + char header[32]; + header[0] = fc; + size_t len = AppendDecimal(header + 1, value); + header[len + 1] = '\r'; + header[len + 2] = '\n'; + buf.append(header, len + 3); +} + +static void FlushComponent(std::string* out, std::string* compbuf, int* ncomp) { + AppendHeader(*out, '$', compbuf->size()); + out->append(*compbuf); + out->append("\r\n", 2); + compbuf->clear(); + ++*ncomp; +} + +// Support hiredis-style format, namely everything is same with printf except +// that %b corresponds to binary-data + length. Notice that we can't use +// %.*s (printf built-in) which ends scaning at \0 and is not binary-safe. +// Some code is copied or modified from redisvFormatCommand() in +// https://github.com/redis/hiredis/blob/master/hiredis.c to keep close +// compatibility with hiredis. +butil::Status +RedisCommandFormatV(butil::IOBuf* outbuf, const char* fmt, va_list ap) { + if (outbuf == NULL || fmt == NULL) { + return butil::Status(EINVAL, "Param[outbuf] or [fmt] is NULL"); + } + const size_t fmt_len = strlen(fmt); + std::string nocount_buf; + nocount_buf.reserve(fmt_len * 3 / 2 + 16); + std::string compbuf; // A component + compbuf.reserve(fmt_len + 16); + const char* c = fmt; + int ncomponent = 0; + char quote_char = 0; + const char* quote_pos = fmt; + int nargs = 0; + for (; *c; ++c) { + if (*c != '%' || c[1] == '\0') { + if (*c == ' ') { + if (quote_char) { + compbuf.push_back(*c); + } else if (!compbuf.empty()) { + FlushComponent(&nocount_buf, &compbuf, &ncomponent); + } + } else if (*c == '"' || *c == '\'') { // Check quotation. + if (!quote_char) { // begin quote + quote_char = *c; + quote_pos = c; + if (!compbuf.empty()) { + FlushComponent(&nocount_buf, &compbuf, &ncomponent); + } + } else if (quote_char == *c) { + const char last_char = (compbuf.empty() ? 0 : compbuf.back()); + if (last_char == '\\') { + // Even if the preceding chars are two consecutive backslashes + // (\\), still do the escaping, which is the behavior of + // official redis-cli. + compbuf.pop_back(); + compbuf.push_back(*c); + } else { // end quote + quote_char = 0; + FlushComponent(&nocount_buf, &compbuf, &ncomponent); + } + } else { + compbuf.push_back(*c); + } + } else { + compbuf.push_back(*c); + } + } else { + char *arg; + size_t size; + + switch(c[1]) { + case 's': + arg = va_arg(ap, char*); + size = strlen(arg); + if (size > 0) { + compbuf.append(arg, size); + } + ++nargs; + break; + case 'b': + arg = va_arg(ap, char*); + size = va_arg(ap, size_t); + if (size > 0) { + compbuf.append(arg, size); + } + ++nargs; + break; + case '%': + compbuf.push_back('%'); + break; + default: { + /* Try to detect printf format */ + static const char intfmts[] = "diouxX"; + static const char flags[] = "#0-+ "; + char _format[24]; + char _printed[40]; + const char *_p = c+1; + size_t _l = 0; + va_list _cpy; + + /* Flags */ + while (*_p != '\0' && strchr(flags,*_p) != NULL) _p++; + + /* Field width */ + while (*_p != '\0' && isdigit(*_p)) _p++; + + /* Precision */ + if (*_p == '.') { + _p++; + while (*_p != '\0' && isdigit(*_p)) _p++; + } + + /* Copy va_list before consuming with va_arg */ + va_copy(_cpy, ap); + + /* Integer conversion (without modifiers) */ + if (strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); + goto fmt_valid; + } + + /* Double conversion (without modifiers) */ + if (strchr("eEfFgGaA",*_p) != NULL) { + va_arg(ap,double); + goto fmt_valid; + } + + /* Size: char */ + if (_p[0] == 'h' && _p[1] == 'h') { + _p += 2; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); /* char gets promoted to int */ + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: short */ + if (_p[0] == 'h') { + _p += 1; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); /* short gets promoted to int */ + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: long long */ + if (_p[0] == 'l' && _p[1] == 'l') { + _p += 2; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,long long); + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: long */ + if (_p[0] == 'l') { + _p += 1; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,long); + goto fmt_valid; + } + goto fmt_invalid; + } + + fmt_invalid: + va_end(_cpy); + return butil::Status(EINVAL, "Invalid format"); + + fmt_valid: + ++nargs; + _l = _p + 1 - c; + if (_l < sizeof(_format)-2) { + memcpy(_format, c, _l); + _format[_l] = '\0'; + int plen = vsnprintf(_printed, sizeof(_printed), _format, _cpy); + if (plen > 0) { + compbuf.append(_printed, plen); + } + /* Update current position (note: outer blocks + * increment c twice so compensate here) */ + c = _p - 1; + } + va_end(_cpy); + break; + } // end default + } // end switch + + ++c; + } + } + if (quote_char) { + const char* ctx_begin = + quote_pos - std::min((size_t)(quote_pos - fmt), CTX_WIDTH); + size_t ctx_size = + std::min((size_t)(fmt + fmt_len - ctx_begin), CTX_WIDTH * 2 + 1); + return butil::Status(EINVAL, "Unmatched quote: ...%.*s... (offset=%lu)", + (int)ctx_size, ctx_begin, quote_pos - fmt); + } + + if (!compbuf.empty()) { + FlushComponent(&nocount_buf, &compbuf, &ncomponent); + } + + LOG_IF(ERROR, nargs == 0) << "You must call RedisCommandNoFormat() " + "to replace RedisCommandFormatV without any args (to avoid potential " + "formatting of conversion specifiers)"; + + AppendHeader(*outbuf, '*', ncomponent); + outbuf->append(nocount_buf); + return butil::Status::OK(); +} + +butil::Status RedisCommandFormat(butil::IOBuf* buf, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + const butil::Status st = RedisCommandFormatV(buf, fmt, ap); + va_end(ap); + return st; +} + +butil::Status +RedisCommandNoFormat(butil::IOBuf* outbuf, const butil::StringPiece& cmd) { + if (outbuf == NULL || cmd == NULL) { + return butil::Status(EINVAL, "Param[outbuf] or [cmd] is NULL"); + } + const size_t cmd_len = cmd.size(); + std::string nocount_buf; + nocount_buf.reserve(cmd_len * 3 / 2 + 16); + std::string compbuf; // A component + compbuf.reserve(cmd_len + 16); + int ncomponent = 0; + char quote_char = 0; + const char* quote_pos = cmd.data(); + for (const char* c = cmd.data(); c != cmd.data() + cmd.size(); ++c) { + if (*c == ' ') { + if (quote_char) { + compbuf.push_back(*c); + } else if (!compbuf.empty()) { + FlushComponent(&nocount_buf, &compbuf, &ncomponent); + } + } else if (*c == '"' || *c == '\'') { // Check quotation. + if (!quote_char) { // begin quote + quote_char = *c; + quote_pos = c; + if (!compbuf.empty()) { + FlushComponent(&nocount_buf, &compbuf, &ncomponent); + } + } else if (quote_char == *c) { + const char last_char = (compbuf.empty() ? 0 : compbuf.back()); + if (last_char == '\\') { + // Even if the preceding chars are two consecutive backslashes + // (\\), still do the escaping, which is the behavior of + // official redis-cli. + compbuf.pop_back(); + compbuf.push_back(*c); + } else { // end quote + quote_char = 0; + FlushComponent(&nocount_buf, &compbuf, &ncomponent); + } + } else { + compbuf.push_back(*c); + } + } else { + compbuf.push_back(*c); + } + } + if (quote_char) { + const char* ctx_begin = + quote_pos - std::min((size_t)(quote_pos - cmd.data()), CTX_WIDTH); + size_t ctx_size = + std::min((size_t)(cmd.data() + cmd.size() - ctx_begin), CTX_WIDTH * 2 + 1); + return butil::Status(EINVAL, "Unmatched quote: ...%.*s... (offset=%lu)", + (int)ctx_size, ctx_begin, quote_pos - cmd.data()); + } + + if (!compbuf.empty()) { + FlushComponent(&nocount_buf, &compbuf, &ncomponent); + } + + AppendHeader(*outbuf, '*', ncomponent); + outbuf->append(nocount_buf); + return butil::Status::OK(); +} + +butil::Status RedisCommandByComponents(butil::IOBuf* output, + const butil::StringPiece* components, + size_t ncomponents) { + if (output == NULL) { + return butil::Status(EINVAL, "Param[output] is NULL"); + } + AppendHeader(*output, '*', ncomponents); + for (size_t i = 0; i < ncomponents; ++i) { + AppendHeader(*output, '$', components[i].size()); + output->append(components[i].data(), components[i].size()); + output->append("\r\n", 2); + } + return butil::Status::OK(); +} + +RedisCommandParser::RedisCommandParser() + : _parsing_array(false) + , _length(0) + , _index(0) {} + +size_t RedisCommandParser::ParsedArgsSize() { + return _args.size(); +} + +ParseError RedisCommandParser::Consume(butil::IOBuf& buf, + std::vector* args, + butil::Arena* arena) { + ParseError err = PARSE_OK; + do { + RedisCommandConsumeState state = ConsumeImpl(buf, arena, &err); + if (state == CONSUME_STATE_CONTINUE) { + continue; + } else if (state == CONSUME_STATE_DONE) { + break; + } else { + return err; + } + } while (true); + + args->swap(_args); + Reset(); + return PARSE_OK; +} + +RedisCommandConsumeState RedisCommandParser::ConsumeImpl(butil::IOBuf& buf, + butil::Arena* arena, + ParseError* err) { + const auto pfc = static_cast(buf.fetch1()); + if (pfc == NULL) { + *err = PARSE_ERROR_NOT_ENOUGH_DATA; + return CONSUME_STATE_ERROR; + } + // '*' stands for array "*\r\n..." + if (!_parsing_array && *pfc != '*') { + if (!std::isalpha(static_cast(*pfc))) { + *err = PARSE_ERROR_TRY_OTHERS; + return CONSUME_STATE_ERROR; + } + // The HTTP/2 connection preface ("PRI * HTTP/2.0\r\n...") is alpha-leading + // and would otherwise be consumed as an inline redis command (first token + // "PRI"), preventing protocol auto-detection from falling through to + // HTTP/2 (e.g. gRPC clients). Defer to other protocols when the input + // matches the preface, either fully or as a not-yet-complete prefix. No + // valid redis command begins with these bytes, so this is unambiguous. + // See issue #3109. + static const char h2_preface[] = "PRI * HTTP/2.0\r\n"; + const size_t h2_preface_len = sizeof(h2_preface) - 1; + if (*pfc == h2_preface[0]) { + char head[h2_preface_len]; + const size_t n = buf.copy_to(head, h2_preface_len); + if (memcmp(head, h2_preface, n) == 0) { + *err = PARSE_ERROR_TRY_OTHERS; + return CONSUME_STATE_ERROR; + } + } + const size_t max_inline_size = + FLAGS_redis_max_allocation_size > 0 ? + static_cast(FLAGS_redis_max_allocation_size) : 0; + const size_t crlf_pos = FindCRLF(buf, max_inline_size + 2); + if (crlf_pos == CRLF_NOT_FOUND) { + if (buf.size() <= max_inline_size) { + *err = PARSE_ERROR_NOT_ENOUGH_DATA; + return CONSUME_STATE_ERROR; + } + if (buf.size() == max_inline_size + 1) { + char last_char = '\0'; + buf.copy_to(&last_char, 1, max_inline_size); + if (last_char == '\r') { + *err = PARSE_ERROR_NOT_ENOUGH_DATA; + return CONSUME_STATE_ERROR; + } + } + LOG(ERROR) << "inline command exceeds max allocation size! max=" + << FLAGS_redis_max_allocation_size << ", actually=" << buf.size(); + *err = PARSE_ERROR_ABSOLUTELY_WRONG; + return CONSUME_STATE_ERROR; + } + if (crlf_pos > max_inline_size) { + LOG(ERROR) << "inline command exceeds max allocation size! max=" + << FLAGS_redis_max_allocation_size << ", actually=" << crlf_pos; + *err = PARSE_ERROR_ABSOLUTELY_WRONG; + return CONSUME_STATE_ERROR; + } + const size_t buf_size = crlf_pos; + const auto copy_str = static_cast(arena->allocate(buf_size + 1)); + // arena->allocate() may return NULL on allocation failure + if (copy_str == NULL) { + LOG(FATAL) << "Arena failed allocation"; + *err = PARSE_ERROR_ABSOLUTELY_WRONG; + return CONSUME_STATE_ERROR; + } + buf.copy_to(copy_str, buf_size); + if (*copy_str == ' ') { + *err = PARSE_ERROR_ABSOLUTELY_WRONG; + return CONSUME_STATE_ERROR; + } + copy_str[buf_size] = '\0'; + size_t offset = 0; + while (offset < crlf_pos && copy_str[offset] != ' ') { + ++offset; + } + const auto first_arg = static_cast(arena->allocate(offset)); + memcpy(first_arg, copy_str, offset); + for (size_t i = 0; i < offset; ++i) { + first_arg[i] = tolower(static_cast(first_arg[i])); + } + _args.push_back(butil::StringPiece(first_arg, offset)); + if (offset == crlf_pos) { + // only one argument, directly return + buf.pop_front(crlf_pos + 2); + return CONSUME_STATE_DONE; + } + size_t arg_start_pos = ++offset; + + for (; offset < crlf_pos; ++offset) { + if (copy_str[offset] != ' ') { + continue; + } + const auto arg_length = offset - arg_start_pos; + const auto arg = static_cast(arena->allocate(arg_length)); + memcpy(arg, copy_str + arg_start_pos, arg_length); + _args.push_back(butil::StringPiece(arg, arg_length)); + arg_start_pos = ++offset; + } + + if (arg_start_pos < crlf_pos) { + // process the last argument + const auto arg_length = crlf_pos - arg_start_pos; + const auto arg = static_cast(arena->allocate(arg_length)); + memcpy(arg, copy_str + arg_start_pos, arg_length); + _args.push_back(butil::StringPiece(arg, arg_length)); + } + + buf.pop_front(crlf_pos + 2); + return CONSUME_STATE_DONE; + } + // '$' stands for bulk string "$\r\n\r\n" + if (_parsing_array && *pfc != '$') { + *err = PARSE_ERROR_ABSOLUTELY_WRONG; + return CONSUME_STATE_ERROR; + } + char intbuf[32]; // enough for fc + 64-bit decimal + \r\n + const size_t ncopied = buf.copy_to(intbuf, sizeof(intbuf) - 1); + intbuf[ncopied] = '\0'; + const size_t crlf_pos = butil::StringPiece(intbuf, ncopied).find("\r\n"); + if (crlf_pos == butil::StringPiece::npos) { // not enough data + *err = PARSE_ERROR_NOT_ENOUGH_DATA; + return CONSUME_STATE_ERROR; + } + char* endptr = NULL; + int64_t value = strtoll(intbuf + 1/*skip fc*/, &endptr, 10); + if (endptr != intbuf + crlf_pos) { + LOG(ERROR) << '`' << intbuf + 1 << "' is not a valid 64-bit decimal"; + *err = PARSE_ERROR_ABSOLUTELY_WRONG; + return CONSUME_STATE_ERROR; + } + if (value < 0) { + LOG(ERROR) << "Invalid len=" << value << " in redis command"; + *err = PARSE_ERROR_ABSOLUTELY_WRONG; + return CONSUME_STATE_ERROR; + } + if (!_parsing_array) { + if (value > (int64_t)(FLAGS_redis_max_allocation_size / sizeof(butil::StringPiece))) { + LOG(ERROR) << "command array size exceeds limit! max=" + << (FLAGS_redis_max_allocation_size / sizeof(butil::StringPiece)) + << ", actually=" << value; + *err = PARSE_ERROR_ABSOLUTELY_WRONG; + return CONSUME_STATE_ERROR; + } + buf.pop_front(crlf_pos + 2/*CRLF*/); + if (value == 0) { + LOG(ERROR) << "Empty redis command array"; + *err = PARSE_ERROR_ABSOLUTELY_WRONG; + return CONSUME_STATE_ERROR; + } + _parsing_array = true; + _length = value; + _index = 0; + _args.resize(value); + return CONSUME_STATE_CONTINUE; + } + if (_index >= _length) { + LOG(ERROR) << "Too many bulk strings in redis command"; + *err = PARSE_ERROR_ABSOLUTELY_WRONG; + return CONSUME_STATE_ERROR; + } + const int64_t len = value; // `value' is length of the string + if (len < 0) { + LOG(ERROR) << "string in command is nil!"; + *err = PARSE_ERROR_ABSOLUTELY_WRONG; + return CONSUME_STATE_ERROR; + } + if (len > FLAGS_redis_max_allocation_size) { + LOG(ERROR) << "command string exceeds max allocation size! max=" + << FLAGS_redis_max_allocation_size << ", actually=" << len; + *err = PARSE_ERROR_ABSOLUTELY_WRONG; + return CONSUME_STATE_ERROR; + } + if (buf.size() < crlf_pos + 2 + (size_t)len + 2/*CRLF*/) { + *err = PARSE_ERROR_NOT_ENOUGH_DATA; + return CONSUME_STATE_ERROR; + } + buf.pop_front(crlf_pos + 2/*CRLF*/); + char* d = (char*)arena->allocate((len/8 + 1) * 8); + // Guard against allocation failure + if (d == NULL) { + LOG(FATAL) << "Arena failed allocation"; + *err = PARSE_ERROR_ABSOLUTELY_WRONG; + return CONSUME_STATE_ERROR; + } + buf.cutn(d, len); + d[len] = '\0'; + _args[_index].set(d, len); + if (_index == 0) { + // convert it to lowercase when it is command name + for (int i = 0; i < len; ++i) { + d[i] = ::tolower(static_cast(d[i])); + } + } + char crlf[2]; + buf.cutn(crlf, sizeof(crlf)); + if (crlf[0] != '\r' || crlf[1] != '\n') { + LOG(ERROR) << "string in command is not ended with CRLF"; + *err = PARSE_ERROR_ABSOLUTELY_WRONG; + return CONSUME_STATE_ERROR; + } + if (++_index == _length) { + return CONSUME_STATE_DONE; + } + return CONSUME_STATE_CONTINUE; +} + +void RedisCommandParser::Reset() { + _parsing_array = false; + _length = 0; + _index = 0; + _args.clear(); +} + +} // namespace brpc diff --git a/src/brpc/redis_command.h b/src/brpc/redis_command.h new file mode 100644 index 0000000..25bd885 --- /dev/null +++ b/src/brpc/redis_command.h @@ -0,0 +1,85 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_REDIS_COMMAND_H +#define BRPC_REDIS_COMMAND_H + +#include +#include // std::unique_ptr +#include +#include "butil/iobuf.h" +#include "butil/status.h" +#include "butil/arena.h" +#include "brpc/parse_result.h" + +namespace brpc { + +// Format a redis command and append it to `buf'. +// Returns butil::Status::OK() on success. +butil::Status RedisCommandFormat(butil::IOBuf* buf, const char* fmt, ...); +butil::Status RedisCommandFormatV(butil::IOBuf* buf, const char* fmt, va_list args); + +// Just convert the command to the text format of redis without processing the +// specifiers(%) inside. +butil::Status RedisCommandNoFormat(butil::IOBuf* buf, const butil::StringPiece& command); + +// Concatenate components to form a redis command. +butil::Status RedisCommandByComponents(butil::IOBuf* buf, + const butil::StringPiece* components, + size_t num_components); + +enum RedisCommandConsumeState { + CONSUME_STATE_CONTINUE, + CONSUME_STATE_DONE, + CONSUME_STATE_ERROR, +}; + +// A parser used to parse redis raw command. +class RedisCommandParser { +public: + RedisCommandParser(); + + // Parse raw message from `buf'. Return PARSE_OK and set the parsed command + // to `args' and length to `len' if successful. Memory of args are allocated + // in `arena'. + ParseError Consume(butil::IOBuf& buf, std::vector* args, + butil::Arena* arena); + size_t ParsedArgsSize(); + +private: + // Reset parser to the initial state. + void Reset(); + + // Consume one arg from `buf'. + // Return CONSUME_STATE_CONTINUE if the parser needs more data. + // Return CONSUME_STATE_DONE if the parser has parsed a complete command. + // Return CONSUME_STATE_ERROR if the parser meets an error. + RedisCommandConsumeState ConsumeImpl(butil::IOBuf& buf, + butil::Arena* arena, + ParseError* err); + + bool _parsing_array; // if the parser has met array indicator '*' + int _length; // array length + int _index; // current parsing array index + std::vector _args; // parsed command string +}; + +} // namespace brpc + + +#endif // BRPC_REDIS_COMMAND_H diff --git a/src/brpc/redis_reply.cpp b/src/brpc/redis_reply.cpp new file mode 100644 index 0000000..14c76f4 --- /dev/null +++ b/src/brpc/redis_reply.cpp @@ -0,0 +1,492 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/logging.h" +#include "butil/string_printf.h" +#include "brpc/redis_reply.h" +#include "gflags/gflags.h" + +namespace brpc { + +DEFINE_int32(redis_max_allocation_size, 64 * 1024 * 1024, + "Maximum memory allocation size in bytes for a single redis request or reply (64MB by default)"); +DEFINE_int32(redis_max_reply_depth, 128, + "Maximum nesting depth for redis array replies"); + +//BAIDU_CASSERT(sizeof(RedisReply) == 24, size_match); +const int RedisReply::npos = -1; + +const char* RedisReplyTypeToString(RedisReplyType type) { + switch (type) { + case REDIS_REPLY_STRING: return "string"; + case REDIS_REPLY_ARRAY: return "array"; + case REDIS_REPLY_INTEGER: return "integer"; + case REDIS_REPLY_NIL: return "nil"; + case REDIS_REPLY_STATUS: return "status"; + case REDIS_REPLY_ERROR: return "error"; + default: return "unknown redis type"; + } +} + +bool RedisReply::SerializeTo(butil::IOBufAppender* appender) { + switch (_type) { + case REDIS_REPLY_ERROR: + // fall through + case REDIS_REPLY_STATUS: + appender->push_back((_type == REDIS_REPLY_ERROR)? '-' : '+'); + if (_length < (int)sizeof(_data.short_str)) { + appender->append(_data.short_str, _length); + } else { + appender->append(_data.long_str, _length); + } + appender->append("\r\n", 2); + return true; + case REDIS_REPLY_INTEGER: + appender->push_back(':'); + appender->append_decimal(_data.integer); + appender->append("\r\n", 2); + return true; + case REDIS_REPLY_STRING: + appender->push_back('$'); + appender->append_decimal(_length); + appender->append("\r\n", 2); + if (_length != npos) { + if (_length < (int)sizeof(_data.short_str)) { + appender->append(_data.short_str, _length); + } else { + appender->append(_data.long_str, _length); + } + appender->append("\r\n", 2); + } + return true; + case REDIS_REPLY_ARRAY: + appender->push_back('*'); + appender->append_decimal(_length); + appender->append("\r\n", 2); + if (_length != npos) { + for (int i = 0; i < _length; ++i) { + if (!_data.array.replies[i].SerializeTo(appender)) { + return false; + } + } + } + return true; + case REDIS_REPLY_NIL: + LOG(ERROR) << "Do you forget to call SetXXX()?"; + return false; + } + CHECK(false) << "unknown redis type=" << _type; + return false; +} + +ParseError RedisReply::ConsumePartialIOBuf(butil::IOBuf& buf) { + return ConsumePartialIOBuf(buf, 0); +} + +ParseError RedisReply::ConsumePartialIOBuf(butil::IOBuf& buf, int depth) { + if (depth > FLAGS_redis_max_reply_depth) { + LOG(ERROR) << "redis reply exceeds max depth! max=" + << FLAGS_redis_max_reply_depth << ", actually=" << depth; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + if (_type == REDIS_REPLY_ARRAY && _data.array.last_index >= 0) { + // The parsing was suspended while parsing sub replies, + // continue the parsing. + RedisReply* subs = (RedisReply*)_data.array.replies; + for (int i = _data.array.last_index; i < _length; ++i) { + ParseError err = subs[i].ConsumePartialIOBuf(buf, depth + 1); + if (err != PARSE_OK) { + return err; + } + ++_data.array.last_index; + } + // We've got an intact reply. reset the index. + _data.array.last_index = -1; + return PARSE_OK; + } + + // Notice that all branches returning PARSE_ERROR_NOT_ENOUGH_DATA must not change `buf'. + const char* pfc = (const char*)buf.fetch1(); + if (pfc == NULL) { + return PARSE_ERROR_NOT_ENOUGH_DATA; + } + const char fc = *pfc; // first character + switch (fc) { + case '-': // Error "-\r\n" + case '+': { // Simple String "+\r\n" + butil::IOBuf str; + if (buf.cut_until(&str, "\r\n") != 0) { + const size_t len = buf.size(); + if (len > std::numeric_limits::max()) { + LOG(ERROR) << "simple string is too long! max length=2^32-1," + " actually=" << len; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + return PARSE_ERROR_NOT_ENOUGH_DATA; + } + const size_t len = str.size() - 1; + if (len < sizeof(_data.short_str)) { + // SSO short strings, including empty string. + _type = (fc == '-' ? REDIS_REPLY_ERROR : REDIS_REPLY_STATUS); + _length = len; + str.copy_to_cstr(_data.short_str, (size_t)-1L, 1/*skip fc*/); + return PARSE_OK; + } + char* d = (char*)_arena->allocate((len/8 + 1)*8); + if (d == NULL) { + LOG(FATAL) << "Fail to allocate string[" << len << "]"; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + CHECK_EQ(len, str.copy_to_cstr(d, (size_t)-1L, 1/*skip fc*/)); + _type = (fc == '-' ? REDIS_REPLY_ERROR : REDIS_REPLY_STATUS); + _length = len; + _data.long_str = d; + return PARSE_OK; + } + case '$': // Bulk String "$\r\n\r\n" + case '*': // Array "*\r\n..." + case ':': { // Integer ":\r\n" + char intbuf[32]; // enough for fc + 64-bit decimal + \r\n + const size_t ncopied = buf.copy_to(intbuf, sizeof(intbuf) - 1); + intbuf[ncopied] = '\0'; + const size_t crlf_pos = butil::StringPiece(intbuf, ncopied).find("\r\n"); + if (crlf_pos == butil::StringPiece::npos) { // not enough data + return PARSE_ERROR_NOT_ENOUGH_DATA; + } + char* endptr = NULL; + int64_t value = strtoll(intbuf + 1/*skip fc*/, &endptr, 10); + if (endptr != intbuf + crlf_pos) { + LOG(ERROR) << '`' << intbuf + 1 << "' is not a valid 64-bit decimal"; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + if (fc == ':') { + buf.pop_front(crlf_pos + 2/*CRLF*/); + _type = REDIS_REPLY_INTEGER; + _length = 0; + _data.integer = value; + return PARSE_OK; + } else if (fc == '$') { + const int64_t len = value; // `value' is length of the string + if (len < 0) { // redis nil + buf.pop_front(crlf_pos + 2/*CRLF*/); + _type = REDIS_REPLY_NIL; + _length = 0; + _data.integer = 0; + return PARSE_OK; + } + if (len > FLAGS_redis_max_allocation_size) { + LOG(ERROR) << "bulk string exceeds max allocation size! max=" + << FLAGS_redis_max_allocation_size << ", actually=" << len; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + // We provide c_str(), thus even if bulk string is started with + // length, we have to end it with \0. + if (buf.size() < crlf_pos + 2 + (size_t)len + 2/*CRLF*/) { + return PARSE_ERROR_NOT_ENOUGH_DATA; + } + if ((size_t)len < sizeof(_data.short_str)) { + // SSO short strings, including empty string. + _type = REDIS_REPLY_STRING; + _length = len; + buf.pop_front(crlf_pos + 2); + buf.cutn(_data.short_str, len); + _data.short_str[len] = '\0'; + } else { + char* d = (char*)_arena->allocate((len/8 + 1)*8); + if (d == NULL) { + LOG(FATAL) << "Fail to allocate string[" << len << "]"; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + buf.pop_front(crlf_pos + 2/*CRLF*/); + buf.cutn(d, len); + d[len] = '\0'; + _type = REDIS_REPLY_STRING; + _length = len; + _data.long_str = d; + } + char crlf[2]; + buf.cutn(crlf, sizeof(crlf)); + if (crlf[0] != '\r' || crlf[1] != '\n') { + LOG(ERROR) << "Bulk string is not ended with CRLF"; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + return PARSE_OK; + } else { + const int64_t count = value; // `value' is count of sub replies + if (count < 0) { // redis nil + buf.pop_front(crlf_pos + 2/*CRLF*/); + _type = REDIS_REPLY_NIL; + _length = 0; + _data.integer = 0; + return PARSE_OK; + } + if (count == 0) { // empty array + buf.pop_front(crlf_pos + 2/*CRLF*/); + _type = REDIS_REPLY_ARRAY; + _length = 0; + _data.array.last_index = -1; + _data.array.replies = NULL; + return PARSE_OK; + } + int64_t max_count = FLAGS_redis_max_allocation_size / sizeof(RedisReply); + if (count > max_count) { + LOG(ERROR) << "array allocation exceeds max allocation size! max=" + << max_count << ", actually=" << count; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + // FIXME(gejun): Call allocate_aligned instead. + RedisReply* subs = (RedisReply*)_arena->allocate(sizeof(RedisReply) * count); + if (subs == NULL) { + LOG(FATAL) << "Fail to allocate RedisReply[" << count << "]"; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + for (int64_t i = 0; i < count; ++i) { + new (&subs[i]) RedisReply(_arena); + } + buf.pop_front(crlf_pos + 2/*CRLF*/); + _type = REDIS_REPLY_ARRAY; + _length = count; + _data.array.replies = subs; + + // Recursively parse sub replies. If any of them fails, it will + // be continued in next calls by tracking _data.array.last_index. + _data.array.last_index = 0; + for (int64_t i = 0; i < count; ++i) { + ParseError err = subs[i].ConsumePartialIOBuf(buf, depth + 1); + if (err != PARSE_OK) { + return err; + } + ++_data.array.last_index; + } + _data.array.last_index = -1; + return PARSE_OK; + } + } + default: + LOG(ERROR) << "Invalid first character=" << (int)fc; + return PARSE_ERROR_ABSOLUTELY_WRONG; + } + return PARSE_ERROR_ABSOLUTELY_WRONG; +} + +class RedisStringPrinter { +public: + RedisStringPrinter(const char* str, size_t length) + : _str(str, length) {} + void Print(std::ostream& os) const; +private: + butil::StringPiece _str; +}; + +static std::ostream& +operator<<(std::ostream& os, const RedisStringPrinter& printer) { + printer.Print(os); + return os; +} + +void RedisStringPrinter::Print(std::ostream& os) const { + size_t flush_start = 0; + for (size_t i = 0; i < _str.size(); ++i) { + const char c = _str[i]; + if (c <= 0) { // unprintable chars + if (i != flush_start) { + os << butil::StringPiece(_str.data() + flush_start, i - flush_start); + } + char buf[8] = "\\u0000"; + uint8_t d1 = ((uint8_t)c) & 0xF; + uint8_t d2 = ((uint8_t)c) >> 4; + buf[4] = (d1 < 10 ? d1 + '0' : (d1 - 10) + 'A'); + buf[5] = (d2 < 10 ? d2 + '0' : (d2 - 10) + 'A'); + os << butil::StringPiece(buf, 6); + flush_start = i + 1; + } else if (c == '"' || c == '\\') { // need to escape + if (i != flush_start) { + os << butil::StringPiece(_str.data() + flush_start, i - flush_start); + } + os << '\\' << c; + flush_start = i + 1; + } + } + if (flush_start != _str.size()) { + os << butil::StringPiece(_str.data() + flush_start, _str.size() - flush_start); + } +} + +// Mimic how official redis-cli prints. +void RedisReply::Print(std::ostream& os) const { + switch (_type) { + case REDIS_REPLY_STRING: + os << '"'; + if (_length < (int)sizeof(_data.short_str)) { + os << RedisStringPrinter(_data.short_str, _length); + } else { + os << RedisStringPrinter(_data.long_str, _length); + } + os << '"'; + break; + case REDIS_REPLY_ARRAY: + os << '['; + for (int i = 0; i < _length; ++i) { + if (i != 0) { + os << ", "; + } + _data.array.replies[i].Print(os); + } + os << ']'; + break; + case REDIS_REPLY_INTEGER: + os << "(integer) " << _data.integer; + break; + case REDIS_REPLY_NIL: + os << "(nil)"; + break; + case REDIS_REPLY_ERROR: + os << "(error) "; + // fall through + case REDIS_REPLY_STATUS: + if (_length < (int)sizeof(_data.short_str)) { + os << RedisStringPrinter(_data.short_str, _length); + } else { + os << RedisStringPrinter(_data.long_str, _length); + } + break; + default: + os << "UnknownType=" << _type; + break; + } +} + +void RedisReply::CopyFromDifferentArena(const RedisReply& other) { + _type = other._type; + _length = other._length; + switch (_type) { + case REDIS_REPLY_ARRAY: { + RedisReply* subs = (RedisReply*)_arena->allocate(sizeof(RedisReply) * _length); + if (subs == NULL) { + LOG(FATAL) << "Fail to allocate RedisReply[" << _length << "]"; + return; + } + for (int i = 0; i < _length; ++i) { + new (&subs[i]) RedisReply(_arena); + } + _data.array.last_index = other._data.array.last_index; + if (_data.array.last_index > 0) { + // incomplete state + for (int i = 0; i < _data.array.last_index; ++i) { + subs[i].CopyFromDifferentArena(other._data.array.replies[i]); + } + } else { + for (int i = 0; i < _length; ++i) { + subs[i].CopyFromDifferentArena(other._data.array.replies[i]); + } + } + _data.array.replies = subs; + } + break; + case REDIS_REPLY_INTEGER: + _data.integer = other._data.integer; + break; + case REDIS_REPLY_NIL: + break; + case REDIS_REPLY_STRING: + // fall through + case REDIS_REPLY_ERROR: + // fall through + case REDIS_REPLY_STATUS: + if (_length < (int)sizeof(_data.short_str)) { + memcpy(_data.short_str, other._data.short_str, _length + 1); + } else { + char* d = (char*)_arena->allocate((_length/8 + 1)*8); + if (d == NULL) { + LOG(FATAL) << "Fail to allocate string[" << _length << "]"; + return; + } + memcpy(d, other._data.long_str, _length + 1); + _data.long_str = d; + } + break; + } +} + +void RedisReply::SetArray(int size) { + if (_type != REDIS_REPLY_NIL) { + Reset(); + } + _type = REDIS_REPLY_ARRAY; + if (size < 0) { + LOG(ERROR) << "negative size=" << size << " when calling SetArray"; + return; + } else if (size == 0) { + _length = 0; + return; + } + RedisReply* subs = (RedisReply*)_arena->allocate(sizeof(RedisReply) * size); + if (!subs) { + LOG(FATAL) << "Fail to allocate RedisReply[" << size << "]"; + return; + } + for (int i = 0; i < size; ++i) { + new (&subs[i]) RedisReply(_arena); + } + _length = size; + _data.array.replies = subs; +} + +void RedisReply::SetStringImpl(const butil::StringPiece& str, RedisReplyType type) { + if (_type != REDIS_REPLY_NIL) { + Reset(); + } + const size_t size = str.size(); + if (size < sizeof(_data.short_str)) { + memcpy(_data.short_str, str.data(), size); + _data.short_str[size] = '\0'; + } else { + char* d = (char*)_arena->allocate((size/8 + 1) * 8); + if (!d) { + LOG(FATAL) << "Fail to allocate string[" << size << "]"; + return; + } + memcpy(d, str.data(), size); + d[size] = '\0'; + _data.long_str = d; + } + _type = type; + _length = size; +} + +void RedisReply::FormatStringImpl(const char* fmt, va_list args, RedisReplyType type) { + va_list copied_args; + va_copy(copied_args, args); + char buf[64]; + int ret = vsnprintf(buf, sizeof(buf), fmt, copied_args); + va_end(copied_args); + if (ret < 0) { + LOG(FATAL) << "Fail to vsnprintf into buf=" << (void*)buf << " size=" << sizeof(buf); + return; + } else if (ret < (int)sizeof(buf)) { + return SetStringImpl(buf, type); + } else { + std::string str; + str.reserve(ret + 1); + butil::string_vappendf(&str, fmt, args); + return SetStringImpl(str, type); + } +} + +} // namespace brpc diff --git a/src/brpc/redis_reply.h b/src/brpc/redis_reply.h new file mode 100644 index 0000000..13114be --- /dev/null +++ b/src/brpc/redis_reply.h @@ -0,0 +1,336 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_REDIS_REPLY_H +#define BRPC_REDIS_REPLY_H + +#include "butil/iobuf.h" // butil::IOBuf +#include "butil/strings/string_piece.h" // butil::StringPiece +#include "butil/arena.h" // butil::Arena +#include "butil/logging.h" // CHECK +#include "parse_result.h" // ParseError + + +namespace brpc { + +// Different types of replies. +enum RedisReplyType { + REDIS_REPLY_STRING = 1, // Bulk String + REDIS_REPLY_ARRAY = 2, + REDIS_REPLY_INTEGER = 3, + REDIS_REPLY_NIL = 4, + REDIS_REPLY_STATUS = 5, // Simple String + REDIS_REPLY_ERROR = 6 +}; + +const char* RedisReplyTypeToString(RedisReplyType); + +// A reply from redis-server. +class RedisReply { +public: + // The initial value for a reply is a nil. + // All needed memory is allocated on `arena'. + RedisReply(butil::Arena* arena); + + // Type of the reply. + RedisReplyType type() const { return _type; } + + bool is_nil() const; // True if the reply is a (redis) nil. + bool is_integer() const; // True if the reply is an integer. + bool is_error() const; // True if the reply is an error. + bool is_string() const; // True if the reply is a string. + bool is_array() const; // True if the reply is an array. + + // Set the reply to the null string. + void SetNullString(); + + // Set the reply to the null array. + void SetNullArray(); + + // Set the reply to the array with `size' elements. After calling + // SetArray, use operator[] to visit sub replies and set their + // value. + void SetArray(int size); + + // Set the reply to a status. + void SetStatus(const butil::StringPiece& str); + void FormatStatus(const char* fmt, ...); + + // Set the reply to an error. + void SetError(const butil::StringPiece& str); + void FormatError(const char* fmt, ...); + + // Set this reply to integer `value'. + void SetInteger(int64_t value); + + // Set this reply to a (bulk) string. + void SetString(const butil::StringPiece& str); + void FormatString(const char* fmt, ...); + + // Convert the reply into a signed 64-bit integer(according to + // http://redis.io/topics/protocol). If the reply is not an integer, + // call stacks are logged and 0 is returned. + int64_t integer() const; + + // Convert the reply to an error message. If the reply is not an error + // message, call stacks are logged and "" is returned. + const char* error_message() const; + + // Convert the reply to a (c-style) string. If the reply is not a string, + // call stacks are logged and "" is returned. Notice that a + // string containing \0 is not printed fully, use data() instead. + const char* c_str() const; + // Convert the reply to a StringPiece. If the reply is not a string, + // call stacks are logged and "" is returned. + // If you need a std::string, call .data().as_string() (which allocates mem) + butil::StringPiece data() const; + + // Return number of sub replies in the array if this reply is an array, or + // return the length of string if this reply is a string, otherwise 0 is + // returned (call stacks are not logged). + size_t size() const; + // Get the index-th sub reply. If this reply is not an array or index is out of + // range, a nil reply is returned (call stacks are not logged) + const RedisReply& operator[](size_t index) const; + RedisReply& operator[](size_t index); + + // Parse from `buf' which may be incomplete. + // Returns PARSE_OK when an intact reply is parsed and cut off from `buf'. + // Returns PARSE_ERROR_NOT_ENOUGH_DATA if data in `buf' is not enough to parse, + // and `buf' is guaranteed to be UNCHANGED so that you can call this + // function on a RedisReply object with the same buf again and again until + // the function returns PARSE_OK. This property makes sure the parsing of + // RedisReply in the worst case is O(N) where N is size of the on-wire + // reply. As a contrast, if the parsing needs `buf' to be intact, + // the complexity in worst case may be O(N^2). + // Returns PARSE_ERROR_ABSOLUTELY_WRONG if the parsing failed. + ParseError ConsumePartialIOBuf(butil::IOBuf& buf); + + // Serialize to iobuf appender using redis protocol + bool SerializeTo(butil::IOBufAppender* appender); + + // Swap internal fields with another reply. + void Swap(RedisReply& other); + + // Reset to the state that this reply was just constructed. + void Reset(); + + // Print fields into ostream + void Print(std::ostream& os) const; + + // Copy from another reply allocating on `_arena', which is a deep copy. + void CopyFromDifferentArena(const RedisReply& other); + + // Copy from another reply allocating on a same Arena, which is a shallow copy. + void CopyFromSameArena(const RedisReply& other); + +private: + static const int npos; + + // RedisReply does not own the memory of fields, copying must be done + // by calling CopyFrom[Different|Same]Arena. + DISALLOW_COPY_AND_ASSIGN(RedisReply); + + ParseError ConsumePartialIOBuf(butil::IOBuf& buf, int depth); + void FormatStringImpl(const char* fmt, va_list args, RedisReplyType type); + void SetStringImpl(const butil::StringPiece& str, RedisReplyType type); + + RedisReplyType _type; + int _length; // length of short_str/long_str, count of replies + union { + int64_t integer; + char short_str[16]; + const char* long_str; + struct { + int32_t last_index; // >= 0 if previous parsing suspends on replies. + RedisReply* replies; + } array; + uint64_t padding[2]; // For swapping, must cover all bytes. + } _data; + butil::Arena* _arena; +}; + +// =========== inline impl. ============== + +inline std::ostream& operator<<(std::ostream& os, const RedisReply& r) { + r.Print(os); + return os; +} + +inline void RedisReply::Reset() { + _type = REDIS_REPLY_NIL; + _length = 0; + _data.array.last_index = -1; + _data.array.replies = NULL; + // _arena should not be reset because further memory allocation needs it. +} + +inline RedisReply::RedisReply(butil::Arena* arena) + : _arena(arena) { + Reset(); +} + +inline bool RedisReply::is_nil() const { + return (_type == REDIS_REPLY_NIL || _length == npos); +} +inline bool RedisReply::is_error() const { return _type == REDIS_REPLY_ERROR; } +inline bool RedisReply::is_integer() const { return _type == REDIS_REPLY_INTEGER; } +inline bool RedisReply::is_string() const +{ return _type == REDIS_REPLY_STRING || _type == REDIS_REPLY_STATUS; } +inline bool RedisReply::is_array() const { return _type == REDIS_REPLY_ARRAY; } + +inline int64_t RedisReply::integer() const { + if (is_integer()) { + return _data.integer; + } + CHECK(false) << "The reply is " << RedisReplyTypeToString(_type) + << ", not an integer"; + return 0; +} + +inline void RedisReply::SetNullArray() { + if (_type != REDIS_REPLY_NIL) { + Reset(); + } + _type = REDIS_REPLY_ARRAY; + _length = npos; +} + +inline void RedisReply::SetNullString() { + if (_type != REDIS_REPLY_NIL) { + Reset(); + } + _type = REDIS_REPLY_STRING; + _length = npos; +} + +inline void RedisReply::SetStatus(const butil::StringPiece& str) { + return SetStringImpl(str, REDIS_REPLY_STATUS); +} +inline void RedisReply::FormatStatus(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + FormatStringImpl(fmt, ap, REDIS_REPLY_STATUS); + va_end(ap); +} + +inline void RedisReply::SetError(const butil::StringPiece& str) { + return SetStringImpl(str, REDIS_REPLY_ERROR); +} +inline void RedisReply::FormatError(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + FormatStringImpl(fmt, ap, REDIS_REPLY_ERROR); + va_end(ap); +} + +inline void RedisReply::SetInteger(int64_t value) { + if (_type != REDIS_REPLY_NIL) { + Reset(); + } + _type = REDIS_REPLY_INTEGER; + _length = 0; + _data.integer = value; +} + +inline void RedisReply::SetString(const butil::StringPiece& str) { + return SetStringImpl(str, REDIS_REPLY_STRING); +} +inline void RedisReply::FormatString(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + FormatStringImpl(fmt, ap, REDIS_REPLY_STRING); + va_end(ap); +} + +inline const char* RedisReply::c_str() const { + if (is_string()) { + if (_length < (int)sizeof(_data.short_str)) { // SSO + return _data.short_str; + } else { + return _data.long_str; + } + } + CHECK(false) << "The reply is " << RedisReplyTypeToString(_type) + << ", not a string"; + return ""; +} + +inline butil::StringPiece RedisReply::data() const { + if (is_string()) { + if (_length < (int)sizeof(_data.short_str)) { // SSO + return butil::StringPiece(_data.short_str, _length); + } else { + return butil::StringPiece(_data.long_str, _length); + } + } + CHECK(false) << "The reply is " << RedisReplyTypeToString(_type) + << ", not a string"; + return butil::StringPiece(); +} + +inline const char* RedisReply::error_message() const { + if (is_error()) { + if (_length < (int)sizeof(_data.short_str)) { // SSO + return _data.short_str; + } else { + return _data.long_str; + } + } + CHECK(false) << "The reply is " << RedisReplyTypeToString(_type) + << ", not an error"; + return ""; +} + +inline size_t RedisReply::size() const { + return _length; +} + +inline RedisReply& RedisReply::operator[](size_t index) { + return const_cast( + const_cast(this)->operator[](index)); +} + +inline const RedisReply& RedisReply::operator[](size_t index) const { + if (is_array() && index < (size_t)_length) { + return _data.array.replies[index]; + } + static RedisReply redis_nil(NULL); + return redis_nil; +} + +inline void RedisReply::Swap(RedisReply& other) { + std::swap(_type, other._type); + std::swap(_length, other._length); + std::swap(_data.padding[0], other._data.padding[0]); + std::swap(_data.padding[1], other._data.padding[1]); + // reply _arena should not be swapped because _arena point to address in redisresponse. + // std::swap(_arena, other._arena); +} + +inline void RedisReply::CopyFromSameArena(const RedisReply& other) { + _type = other._type; + _length = other._length; + _data.padding[0] = other._data.padding[0]; + _data.padding[1] = other._data.padding[1]; + _arena = other._arena; +} + +} // namespace brpc + +#endif // BRPC_REDIS_H diff --git a/src/brpc/reloadable_flags.cpp b/src/brpc/reloadable_flags.cpp new file mode 100644 index 0000000..69fb4ad --- /dev/null +++ b/src/brpc/reloadable_flags.cpp @@ -0,0 +1,86 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/reloadable_flags.h" + +namespace brpc { + +bool PassValidate(const char*, bool) { + return true; +} +bool PassValidate(const char*, int32_t) { + return true; +} +bool PassValidate(const char*, uint32_t) { + return true; +} +bool PassValidate(const char*, int64_t) { + return true; +} +bool PassValidate(const char*, uint64_t) { + return true; +} +bool PassValidate(const char*, double) { + return true; +} + +bool PositiveInteger(const char*, int32_t val) { + return val > 0; +} +bool PositiveInteger(const char*, uint32_t val) { + return val > 0; +} +bool PositiveInteger(const char*, int64_t val) { + return val > 0; +} +bool PositiveInteger(const char*, uint64_t val) { + return val > 0; +} + +bool NonNegativeInteger(const char*, int32_t val) { + return val >= 0; +} +bool NonNegativeInteger(const char*, int64_t val) { + return val >= 0; +} + +bool RegisterFlagValidatorOrDie(const bool* flag, + bool (*validate_fn)(const char*, bool)) { + return butil::RegisterFlagValidatorOrDieImpl(flag, validate_fn); +} +bool RegisterFlagValidatorOrDie(const int32_t* flag, + bool (*validate_fn)(const char*, int32_t)) { + return butil::RegisterFlagValidatorOrDieImpl(flag, validate_fn); +} +bool RegisterFlagValidatorOrDie(const uint32_t* flag, + bool (*validate_fn)(const char*, uint32_t)) { + return butil::RegisterFlagValidatorOrDieImpl(flag, validate_fn); +} +bool RegisterFlagValidatorOrDie(const int64_t* flag, + bool (*validate_fn)(const char*, int64_t)) { + return butil::RegisterFlagValidatorOrDieImpl(flag, validate_fn); +} +bool RegisterFlagValidatorOrDie(const uint64_t* flag, + bool (*validate_fn)(const char*, uint64_t)) { + return butil::RegisterFlagValidatorOrDieImpl(flag, validate_fn); +} +bool RegisterFlagValidatorOrDie(const double* flag, + bool (*validate_fn)(const char*, double)) { + return butil::RegisterFlagValidatorOrDieImpl(flag, validate_fn); +} + +} // namespace brpc diff --git a/src/brpc/reloadable_flags.h b/src/brpc/reloadable_flags.h new file mode 100644 index 0000000..60b7851 --- /dev/null +++ b/src/brpc/reloadable_flags.h @@ -0,0 +1,58 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_RELOADABLE_FLAGS_H +#define BRPC_RELOADABLE_FLAGS_H + +#include "butil/reloadable_flags.h" + +#define BRPC_VALIDATE_GFLAG(flag, validate_fn) BUTIL_VALIDATE_GFLAG(flag, validate_fn) + +namespace brpc { + +extern bool PassValidate(const char*, bool); +extern bool PassValidate(const char*, int32_t); +extern bool PassValidate(const char*, uint32_t); +extern bool PassValidate(const char*, int64_t); +extern bool PassValidate(const char*, uint64_t); +extern bool PassValidate(const char*, double); + +extern bool PositiveInteger(const char*, int32_t); +extern bool PositiveInteger(const char*, uint32_t); +extern bool PositiveInteger(const char*, int64_t); +extern bool PositiveInteger(const char*, uint64_t); + +extern bool NonNegativeInteger(const char*, int32_t); +extern bool NonNegativeInteger(const char*, int64_t); + +extern bool RegisterFlagValidatorOrDie( + const bool* flag, bool (*validate_fn)(const char*, bool)); +extern bool RegisterFlagValidatorOrDie( + const int32_t* flag, bool (*validate_fn)(const char*, int32_t)); +extern bool RegisterFlagValidatorOrDie( + const uint32_t* flag, bool (*validate_fn)(const char*, uint32_t)); +extern bool RegisterFlagValidatorOrDie( + const int64_t* flag, bool (*validate_fn)(const char*, int64_t)); +extern bool RegisterFlagValidatorOrDie( + const uint64_t* flag, bool (*validate_fn)(const char*, uint64_t)); +extern bool RegisterFlagValidatorOrDie( + const double* flag, bool (*validate_fn)(const char*, double)); +} // namespace brpc + + +#endif // BRPC_RELOADABLE_FLAGS_H diff --git a/src/brpc/restful.cpp b/src/brpc/restful.cpp new file mode 100644 index 0000000..36b31e9 --- /dev/null +++ b/src/brpc/restful.cpp @@ -0,0 +1,506 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "brpc/log.h" +#include "brpc/restful.h" +#include "brpc/details/method_status.h" + + +namespace brpc { + +// Define in http_parser.cpp +extern bool is_url_char(char c); + +inline butil::StringPiece remove_last_char(butil::StringPiece s) { + if (!s.empty()) { + s.remove_suffix(1); + } + return s; +} + +std::ostream& operator<<(std::ostream& os, const RestfulMethodPath& p) { + if (!p.service_name.empty()) { + os << '/' << p.service_name; + } + if (p.has_wildcard) { + os << p.prefix << '*' << remove_last_char(p.postfix); + } else { + os << remove_last_char(p.prefix); + } + return os; +} + +std::string RestfulMethodPath::to_string() const { + std::string s; + s.reserve(service_name.size() + prefix.size() + 2 + postfix.size()); + if (!service_name.empty()) { + s.push_back('/'); + s.append(service_name); + } + if (has_wildcard) { + s.append(prefix); + s.push_back('*'); + butil::StringPiece tmp = remove_last_char(postfix); + s.append(tmp.data(), tmp.size()); + } else { + butil::StringPiece tmp = remove_last_char(prefix); + s.append(tmp.data(), tmp.size()); + } + return s; +} + +struct DebugPrinter { + explicit DebugPrinter(const RestfulMethodPath& p) : path(&p) {} + const RestfulMethodPath* path; +}; + +std::ostream& operator<<(std::ostream& os, const DebugPrinter& p) { + os << "{service=" << p.path->service_name + << " prefix=" << p.path->prefix + << " postfix=" << p.path->postfix + << " wildcard=" << p.path->has_wildcard + << '}'; + return os; +} + +bool ParseRestfulPath(butil::StringPiece path, + RestfulMethodPath* path_out) { + path.trim_spaces(); + if (path.empty()) { + LOG(ERROR) << "Parameter[path] is empty"; + return false; + } + // Check validity of the path. + // TODO(gejun): Probably too strict. + int star_index = -1; + for (const char* p = path.data(); p != path.data() + path.size(); ++p) { + if (*p == '*') { + if (star_index < 0) { + star_index = (int)(p - path.data()); + } else { + LOG(ERROR) << "More than one wildcard in restful_path=`" + << path << '\''; + return false; + } + } else if (!is_url_char(*p)) { + LOG(ERROR) << "Invalid character=`" << *p << "' (index=" + << p - path.data() << ") in path=`" << path << '\''; + return false; + } + } + path_out->has_wildcard = (star_index >= 0); + + butil::StringPiece first_part; + butil::StringPiece second_part; + if (star_index < 0) { + first_part = path; + } else { + first_part = path.substr(0, star_index); + second_part = path.substr(star_index + 1); + } + + // Extract service_name and prefix from first_part + // The prefix is normalized as: + // / - "*B => M" + // /A - "/A*B => M" (disabled for performance) + // /A/ - "/A/*B => M" + path_out->service_name.clear(); + path_out->prefix.clear(); + { + // remove heading slashes. + size_t i = 0; + for (; i < first_part.size() && first_part[i] == '/'; ++i) {} + first_part.remove_prefix(i); + } + const size_t slash_pos = first_part.find('/'); + if (slash_pos != butil::StringPiece::npos) { + path_out->service_name.assign(first_part.data(), slash_pos); + butil::StringPiece prefix_raw = first_part.substr(slash_pos + 1); + butil::StringSplitter sp(prefix_raw.data(), + prefix_raw.data() + prefix_raw.size(), '/'); + for (; sp; ++sp) { + // Put first component into service_name and others into prefix. + if (path_out->prefix.empty()) { + path_out->prefix.reserve(prefix_raw.size() + 2); + } + path_out->prefix.push_back('/'); + path_out->prefix.append(sp.field(), sp.length()); + } + if (!path_out->has_wildcard || + prefix_raw.empty() || + prefix_raw.back() == '/') { + path_out->prefix.push_back('/'); + } else { + LOG(ERROR) << "Pattern A* (A is not ended with /) in path=`" + << path << "' is disallowed for performance concerns"; + return false; + } + } else if (!path_out->has_wildcard) { + // no slashes, no wildcard. Example: abc => Method + path_out->service_name.assign(first_part.data(), first_part.size()); + path_out->prefix.push_back('/'); + } else { // no slashes, has wildcard. Example: abc* => Method + if (!first_part.empty()) { + LOG(ERROR) << "Pattern A* (A is not ended with /) in path=`" + << path << "' is disallowed for performance concerns"; + return false; + } + path_out->prefix.push_back('/'); + path_out->prefix.append(first_part.data(), first_part.size()); + } + + // Normalize second_part as postfix: + // / - "A* => M" or "A => M" + // B/ - "A*B => M" + // /B/ - "A*/B => M" + path_out->postfix.clear(); + if (path_out->has_wildcard) { + if (second_part.empty() || second_part[0] == '/') { + path_out->postfix.push_back('/'); + } + butil::StringSplitter sp2(second_part.data(), + second_part.data() + second_part.size(), '/'); + for (; sp2; ++sp2) { + if (path_out->postfix.empty()) { + path_out->postfix.reserve(second_part.size() + 2); + } + path_out->postfix.append(sp2.field(), sp2.length()); + path_out->postfix.push_back('/'); + } + } else { + path_out->postfix.push_back('/'); + } + VLOG(RPC_VLOG_LEVEL + 1) << "orig_path=" << path + << " first_part=" << first_part + << " second_part=" << second_part + << " path=" << DebugPrinter(*path_out); + return true; +} + +bool ParseRestfulMappings(const butil::StringPiece& mappings, + std::vector* list) { + if (list == NULL) { + LOG(ERROR) << "Param[list] is NULL"; + return false; + } + list->clear(); + list->reserve(8); + butil::StringSplitter sp( + mappings.data(), mappings.data() + mappings.size(), ','); + int nmappings = 0; + for (; sp; ++sp) { + ++nmappings; + size_t i = 0; + const char* p = sp.field(); + const size_t n = sp.length(); + bool added_sth = false; + for (; i < n; ++i) { + // find = + if (p[i] != '=') { + continue; + } + const size_t equal_sign_pos = i; + for (; i < n && p[i] == '='; ++i) {} // skip repeated = + // If the = ends with >, it's the arrow that we're finding. + // otherwise just skip and keep searching. + if (i < n && p[i] == '>') { + RestfulMapping m; + // Parse left part of the arrow as url path. + butil::StringPiece path(sp.field(), equal_sign_pos); + if (!ParseRestfulPath(path, &m.path)) { + LOG(ERROR) << "Fail to parse path=`" << path << '\''; + return false; + } + // Treat right part of the arrow as method_name. + butil::StringPiece method_name_piece(p + i + 1, n - (i + 1)); + method_name_piece.trim_spaces(); + if (method_name_piece.empty()) { + LOG(ERROR) << "No method name in " << nmappings + << "-th mapping"; + return false; + } + m.method_name.assign(method_name_piece.data(), + method_name_piece.size()); + list->push_back(m); + added_sth = true; + break; + } + } + // If we don't get a valid mapping from the string, issue error. + if (!added_sth) { + LOG(ERROR) << "Invalid mapping: " + << butil::StringPiece(sp.field(), sp.length()); + return false; + } + } + return true; +} + +RestfulMap::~RestfulMap() { + ClearMethods(); +} + +// This function inserts a mapping into _dedup_map. +bool RestfulMap::AddMethod(const RestfulMethodPath& path, + google::protobuf::Service* service, + const Server::MethodProperty::OpaqueParams& params, + const std::string& method_name, + MethodStatus* status) { + if (service == NULL) { + LOG(ERROR) << "Param[service] is NULL"; + return false; + } + const google::protobuf::MethodDescriptor* md = + service->GetDescriptor()->FindMethodByName(method_name); + if (md == NULL) { + LOG(ERROR) << service->GetDescriptor()->full_name() + << " has no method called `" << method_name << '\''; + return false; + } + if (path.service_name != _service_name) { + LOG(ERROR) << "Impossible: path.service_name does not match name" + " of this RestfulMap"; + return false; + } + // Use the string-form of path as key is a MUST to implement + // RemoveByPathString which is used in Server.RemoveMethodsOf + std::string dedup_key = path.to_string(); + DedupMap::const_iterator it = _dedup_map.find(dedup_key); + if (it != _dedup_map.end()) { + LOG(ERROR) << "Already mapped `" << it->second.path + << "' to `" << it->second.method->full_name() << '\''; + return false; + } + RestfulMethodProperty& info = _dedup_map[dedup_key]; + info.is_builtin_service = false; + info.own_method_status = false; + info.params = params; + info.service = service; + info.method = md; + info.status = status; + info.path = path; + info.ownership = SERVER_DOESNT_OWN_SERVICE; + RPC_VLOG << "Mapped `" << path << "' to `" << md->full_name() << '\''; + return true; +} + +void RestfulMap::ClearMethods() { + _sorted_paths.clear(); + for (DedupMap::iterator it = _dedup_map.begin(); + it != _dedup_map.end(); ++it) { + if (it->second.own_method_status) { + delete it->second.status; + } + } + _dedup_map.clear(); +} + +struct CompareItemInPathList { + bool operator()(const RestfulMethodProperty* e1, + const RestfulMethodProperty* e2) const { + const RestfulMethodPath& path1 = e1->path; + const RestfulMethodPath& path2 = e2->path; + const int rc1 = path1.prefix.compare(path2.prefix); + if (rc1 != 0) { + return rc1 < 0; + } + // /A/*/B is put before /A/B so that we try exact patterns first + // (the matching is in reversed order) + if (path1.has_wildcard != path2.has_wildcard) { + return path1.has_wildcard > path2.has_wildcard; + } + // Compare postfix from back to front. + const bool postfix_result = std::lexicographical_compare( + path1.postfix.rbegin(), path1.postfix.rend(), + path2.postfix.rbegin(), path2.postfix.rend()); + return postfix_result; + } +}; + +void RestfulMap::PrepareForFinding() { + _sorted_paths.clear(); + _sorted_paths.reserve(_dedup_map.size()); + for (DedupMap::iterator it = _dedup_map.begin(); it != _dedup_map.end(); + ++it) { + _sorted_paths.push_back(&it->second); + } + std::sort(_sorted_paths.begin(), _sorted_paths.end(), + CompareItemInPathList()); + if (VLOG_IS_ON(RPC_VLOG_LEVEL + 1)) { + std::ostringstream os; + os << "_sorted_paths(" << _service_name << "):"; + for (PathList::const_iterator it = _sorted_paths.begin(); + it != _sorted_paths.end(); ++it) { + os << ' ' << (*it)->path; + } + VLOG(RPC_VLOG_LEVEL + 1) << os.str(); + } +} + +// Remove last component from the (normalized) path: +// Say /A/B/C/ -> /A/B/ +// Notice that /A/ is modified to / and returns true. +static bool RemoveLastComponent(butil::StringPiece* path) { + if (path->empty()) { + return false; + } + if (path->back() == '/') { + path->remove_suffix(1); + } + size_t slash_pos = path->rfind('/'); + if (slash_pos == std::string::npos) { + return false; + } + path->remove_suffix(path->size() - slash_pos - 1); // keep the slash + return true; +} + +// Normalized as /A/B/C/ +static std::string NormalizeSlashes(const butil::StringPiece& path) { + std::string out_path; + out_path.reserve(path.size() + 2); + butil::StringSplitter sp(path.data(), path.data() + path.size(), '/'); + for (; sp; ++sp) { + out_path.push_back('/'); + out_path.append(sp.field(), sp.length()); + } + out_path.push_back('/'); + return out_path; +} + +size_t RestfulMap::RemoveByPathString(const std::string& path) { + // removal only happens when server stops, clear _sorted_paths to make + // sure wild pointers do not exist. + if (!_sorted_paths.empty()) { + _sorted_paths.clear(); + } + return _dedup_map.erase(path); +} + +struct PrefixLess { + bool operator()(const butil::StringPiece& path, + const RestfulMethodProperty* p) const { + return path < p->path.prefix; + } +}; + +const Server::MethodProperty* +RestfulMap::FindMethodProperty(const butil::StringPiece& method_path, + std::string* unresolved_path) const { + if (_sorted_paths.empty()) { + LOG(ERROR) << "_sorted_paths is empty, method_path=" << method_path; + return NULL; + } + const std::string full_path = NormalizeSlashes(method_path); + butil::StringPiece sub_path = full_path; + PathList::const_iterator last_find_pos = _sorted_paths.end(); + do { + if (last_find_pos == _sorted_paths.begin()) { + return NULL; + } + // Note: stop trying places that we already visited or skipped. + PathList::const_iterator it = + std::upper_bound(_sorted_paths.begin(), last_find_pos/*note*/, + sub_path, PrefixLess()); + if (it != _sorted_paths.begin()) { + --it; + } + + bool matched = false; + bool remove_heading_slash_from_unresolved = false; + butil::StringPiece left; + do { + const RestfulMethodPath& rpath = (*it)->path; + if (!sub_path.starts_with(rpath.prefix)) { + VLOG(RPC_VLOG_LEVEL + 1) + << "sub_path=" << sub_path << " does not match prefix=" + << rpath.prefix << " full_path=" << full_path + << " candidate=" << DebugPrinter(rpath); + // NOTE: We can stop trying patterns before *it because pattern + // "/A*B => M" is disabled which makes prefixes of all restful + // paths end with /. If `full_path' matches with a prefix, the + // prefix must be a sub path of the full_path, which makes + // prefix matching runs at most #components-of-path times. + // Otherwise we have to match all "/A*B" patterns before *it, + // which is more complicated but rarely needed by users. + break; + } + left = full_path; + // Remove matched prefix from `left'. + if (!rpath.prefix.empty()) { + // make sure `left' is still starting with / + size_t removal = rpath.prefix.size(); + if (rpath.prefix[removal - 1] == '/') { + --removal; + remove_heading_slash_from_unresolved = true; + } + left.remove_prefix(removal); + } + // Match postfix. + if (left.ends_with(rpath.postfix)) { + left.remove_suffix(rpath.postfix.size()); + if (!left.empty() && !rpath.has_wildcard) { + VLOG(RPC_VLOG_LEVEL + 1) + << "Unmatched extra=" << left + << " sub_path=" << sub_path + << " full_path=" << full_path + << " candidate=" << DebugPrinter(rpath); + } else { + matched = true; + VLOG(RPC_VLOG_LEVEL + 1) + << "Matched sub_path=" << sub_path + << " full_path=" << full_path + << " with restful_path=" << DebugPrinter(rpath); + break; + } + } + if (it == _sorted_paths.begin()) { + VLOG(RPC_VLOG_LEVEL + 1) + << "Hit beginning, sub_path=" << sub_path + << " full_path=" << full_path + << " candidate=" << DebugPrinter(rpath); + return NULL; + } + // Matched with prefix but postfix or wildcard, moving forward + --it; + } while (true); + last_find_pos = it; + + if (!matched) { + continue; + } + if (unresolved_path) { + if (!left.empty()) { + if (remove_heading_slash_from_unresolved && left[0] == '/') { + unresolved_path->assign(left.data() + 1, left.size() - 1); + } else { + unresolved_path->assign(left.data(), left.size()); + } + } else { + unresolved_path->clear(); + } + } + return *it; + } while (RemoveLastComponent(&sub_path)); + // ^^^^^^^^ + // sub_path can be / to match patterns like "*.flv => M" whose prefix is / + return NULL; +} + +} // namespace brpc diff --git a/src/brpc/restful.h b/src/brpc/restful.h new file mode 100644 index 0000000..ceab491 --- /dev/null +++ b/src/brpc/restful.h @@ -0,0 +1,114 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_RESTFUL_H +#define BRPC_RESTFUL_H + +#include +#include "butil/strings/string_piece.h" +#include "brpc/server.h" + + +namespace brpc { + +struct RestfulMethodPath { + std::string service_name; + std::string prefix; + std::string postfix; + bool has_wildcard; + + std::string to_string() const; +}; +struct RestfulMapping { + RestfulMethodPath path; + std::string method_name; +}; + +// Split components of `path_in' into `path_out'. +// * path_out->service_name does not have /. +// * path_out->prefix is normalized as +// prefix := "/COMPONENT" prefix | "" (no dot in COMPONENT) +// Returns true on success. +bool ParseRestfulPath(butil::StringPiece path_in, RestfulMethodPath* path_out); + +// Parse "PATH1 => NAME1, PATH2 => NAME2 ..." where: +// * PATHs are acceptible by ParseRestfulPath. +// * NAMEs are valid as method names in protobuf. +// Returns true on success. +bool ParseRestfulMappings(const butil::StringPiece& mappings, + std::vector* list); + +struct RestfulMethodProperty : public Server::MethodProperty { + RestfulMethodPath path; + ServiceOwnership ownership; +}; + +// Store paths under a same toplevel name. +class RestfulMap { +public: + typedef std::map DedupMap; + typedef std::vector PathList; + + explicit RestfulMap(const std::string& service_name) + : _service_name(service_name) {} + virtual ~RestfulMap(); + + // Map `path' to the method denoted by `method_name' in `service'. + // Returns MethodStatus of the method on success, NULL otherwise. + bool AddMethod(const RestfulMethodPath& path, + google::protobuf::Service* service, + const Server::MethodProperty::OpaqueParams& params, + const std::string& method_name, + MethodStatus* status); + + // Remove by RestfulMethodPath::to_string() of the path to AddMethod() + // Returns number of methods removed (should be 1 or 0 currently) + size_t RemoveByPathString(const std::string& path); + + // Remove all methods. + void ClearMethods(); + + // Called after by Server at starting moment, to refresh _sorted_paths + void PrepareForFinding(); + + // Find the method by path. + // Time complexity in worst-case is #slashes-in-input * log(#paths-stored) + const Server::MethodProperty* + FindMethodProperty(const butil::StringPiece& method_path, + std::string* unresolved_path) const; + + const std::string& service_name() const { return _service_name; } + + // Number of methods in this map. Only for UT right now. + size_t size() const { return _dedup_map.size(); } + +private: + DISALLOW_COPY_AND_ASSIGN(RestfulMap); + + std::string _service_name; + // refreshed each time + PathList _sorted_paths; + DedupMap _dedup_map; +}; + +std::ostream& operator<<(std::ostream& os, const RestfulMethodPath&); + +} // namespace brpc + + +#endif // BRPC_RESTFUL_H diff --git a/src/brpc/retry_policy.cpp b/src/brpc/retry_policy.cpp new file mode 100644 index 0000000..3120b3c --- /dev/null +++ b/src/brpc/retry_policy.cpp @@ -0,0 +1,78 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/retry_policy.h" +#include "butil/fast_rand.h" + + +namespace brpc { + +bool RpcRetryPolicy::DoRetry(const Controller* controller) const { + const int error_code = controller->ErrorCode(); + if (!error_code) { + return false; + } + return (EFAILEDSOCKET == error_code + || EEOF == error_code + || EHOSTDOWN == error_code + || ELOGOFF == error_code + || ETIMEDOUT == error_code // This is not timeout of RPC. + || ELIMIT == error_code + || ENOENT == error_code + || EPIPE == error_code + || ECONNREFUSED == error_code + || ECONNRESET == error_code + || ENODATA == error_code + || EOVERCROWDED == error_code + || EH2RUNOUTSTREAMS == error_code); +} + +// NOTE(gejun): g_default_policy can't be deleted on process's exit because +// client-side may still retry and use the policy at exit +static pthread_once_t g_default_policy_once = PTHREAD_ONCE_INIT; +static RpcRetryPolicy* g_default_policy = NULL; +static void init_default_policy() { + g_default_policy = new RpcRetryPolicy; +} +const RetryPolicy* DefaultRetryPolicy() { + pthread_once(&g_default_policy_once, init_default_policy); + return g_default_policy; +} + +int32_t RpcRetryPolicyWithFixedBackoff::GetBackoffTimeMs( + const Controller* controller) const { + int64_t remaining_rpc_time_ms = + (controller->deadline_us() - butil::gettimeofday_us()) / 1000; + if (remaining_rpc_time_ms < _no_backoff_remaining_rpc_time_ms) { + return 0; + } + return _backoff_time_ms; +} + +int32_t RpcRetryPolicyWithJitteredBackoff::GetBackoffTimeMs( + const Controller* controller) const { + int64_t remaining_rpc_time_ms = + (controller->deadline_us() - butil::gettimeofday_us()) / 1000; + if (remaining_rpc_time_ms < _no_backoff_remaining_rpc_time_ms) { + return 0; + } + return butil::fast_rand_in(_min_backoff_time_ms, + _max_backoff_time_ms); +} + +} // namespace brpc diff --git a/src/brpc/retry_policy.h b/src/brpc/retry_policy.h new file mode 100644 index 0000000..bfe20d8 --- /dev/null +++ b/src/brpc/retry_policy.h @@ -0,0 +1,139 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_RETRY_POLICY_H +#define BRPC_RETRY_POLICY_H + +#include "brpc/controller.h" + + +namespace brpc { + +// Inherit this class to customize when the RPC should be retried. +class RetryPolicy { +public: + virtual ~RetryPolicy() = default; + + // Returns true if the RPC represented by `controller' should be retried. + // [Example] + // By default, HTTP errors are not retried, but you need to retry + // HTTP_STATUS_FORBIDDEN in your app. You can implement the RetryPolicy + // as follows: + // + // class MyRetryPolicy : public brpc::RetryPolicy { + // public: + // bool DoRetry(const brpc::Controller* cntl) const { + // if (cntl->ErrorCode() == 0) { // don't retry successful RPC + // return false; + // } + // if (cntl->ErrorCode() == brpc::EHTTP && // http errors + // cntl->http_response().status_code() == brpc::HTTP_STATUS_FORBIDDEN) { + // return true; + // } + // // Leave other cases to default. + // return brpc::DefaultRetryPolicy()->DoRetry(cntl); + // } + // }; + // + // You can retry unqualified responses even if the RPC was successful + // class MyRetryPolicy : public brpc::RetryPolicy { + // public: + // bool DoRetry(const brpc::Controller* cntl) const { + // if (cntl->ErrorCode() == 0) { // successful RPC + // if (!is_qualified(cntl->response())) { + // cntl->response()->Clear(); // reset the response + // return true; + // } + // return false; + // } + // // Leave other cases to default. + // return brpc::DefaultRetryPolicy()->DoRetry(cntl); + // } + // }; + virtual bool DoRetry(const Controller* controller) const = 0; + // ^ + // don't forget the const modifier + + // Returns the backoff time in milliseconds before every retry. + virtual int32_t GetBackoffTimeMs(const Controller* controller) const { return 0; } + // ^ + // don't forget the const modifier + + // Returns true if enable retry backoff in pthread, otherwise returns false. + virtual bool CanRetryBackoffInPthread() const { return false; } + // ^ + // don't forget the const modifier +}; + +// Get the RetryPolicy used by brpc. +const RetryPolicy* DefaultRetryPolicy(); + +class RpcRetryPolicy : public RetryPolicy { +public: + bool DoRetry(const Controller* controller) const override; +}; + +class RpcRetryPolicyWithFixedBackoff : public RpcRetryPolicy { +public: + RpcRetryPolicyWithFixedBackoff(int32_t backoff_time_ms, + int32_t no_backoff_remaining_rpc_time_ms, + bool retry_backoff_in_pthread) + : _backoff_time_ms(backoff_time_ms) + , _no_backoff_remaining_rpc_time_ms(no_backoff_remaining_rpc_time_ms) + , _retry_backoff_in_pthread(retry_backoff_in_pthread) {} + + int32_t GetBackoffTimeMs(const Controller* controller) const override; + + bool CanRetryBackoffInPthread() const override { return _retry_backoff_in_pthread; } + + +private: + int32_t _backoff_time_ms; + // If remaining rpc time is less than `_no_backoff_remaining_rpc_time', no backoff. + int32_t _no_backoff_remaining_rpc_time_ms; + bool _retry_backoff_in_pthread; +}; + +class RpcRetryPolicyWithJitteredBackoff : public RpcRetryPolicy { +public: + RpcRetryPolicyWithJitteredBackoff(int32_t min_backoff_time_ms, + int32_t max_backoff_time_ms, + int32_t no_backoff_remaining_rpc_time_ms, + bool retry_backoff_in_pthread) + : _min_backoff_time_ms(min_backoff_time_ms) + , _max_backoff_time_ms(max_backoff_time_ms) + , _no_backoff_remaining_rpc_time_ms(no_backoff_remaining_rpc_time_ms) + , _retry_backoff_in_pthread(retry_backoff_in_pthread) {} + + int32_t GetBackoffTimeMs(const Controller* controller) const override; + + bool CanRetryBackoffInPthread() const override { return _retry_backoff_in_pthread; } + +private: + // Generate jittered backoff time between [_min_backoff_ms, _max_backoff_ms]. + int32_t _min_backoff_time_ms; + int32_t _max_backoff_time_ms; + // If remaining rpc time is less than `_no_backoff_remaining_rpc_time', no backoff. + int32_t _no_backoff_remaining_rpc_time_ms; + bool _retry_backoff_in_pthread; +}; + +} // namespace brpc + + +#endif // BRPC_RETRY_POLICY_H diff --git a/src/brpc/rpc_dump.cpp b/src/brpc/rpc_dump.cpp new file mode 100644 index 0000000..4686713 --- /dev/null +++ b/src/brpc/rpc_dump.cpp @@ -0,0 +1,364 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include // O_CREAT +#include "butil/file_util.h" +#include "butil/raw_pack.h" +#include "butil/unique_ptr.h" +#include "butil/fast_rand.h" +#include "butil/files/file_enumerator.h" +#include "bvar/bvar.h" +#include "brpc/log.h" +#include "brpc/reloadable_flags.h" +#include "brpc/rpc_dump.h" +#include "brpc/protocol.h" + +namespace bvar { +std::string read_command_name(); +} + +namespace brpc { + +DECLARE_uint64(max_body_size); + +#define DUMPED_FILE_PREFIX "requests" + +// Layout of dumped files: +// /.yyyymmdd_hhmmss_uuuuus +// /.yyyymmdd_hhmmss_uuuuus +// ... +// /.yyyymmdd_hhmmss_uuuuus + +DEFINE_bool(rpc_dump, false, + "Dump requests into files so that they can replayed " + "laterly. Flags prefixed with \"rpc_dump_\" are not effective " + "until this flag is true"); +DEFINE_string(rpc_dump_dir, "./rpc_data/rpc_dump/", + "The directory of dumped files, will be cleaned " + "if it exists when this process starts"); +DEFINE_int32(rpc_dump_max_files, 32, + "Max number of dumped files in a directory. " + "If new file is needed, oldest file is removed."); +DEFINE_int32(rpc_dump_max_requests_in_one_file, 1000, + "Max number of requests in one dumped file"); + +BRPC_VALIDATE_GFLAG(rpc_dump, PassValidate); +BRPC_VALIDATE_GFLAG(rpc_dump_max_requests_in_one_file, PositiveInteger); +BRPC_VALIDATE_GFLAG(rpc_dump_max_files, PositiveInteger); + +static const size_t UNWRITTEN_BUFSIZE = 1024 * 1024; +static const int64_t FLUSH_TIMEOUT = 2000000L; // 2s + +class RpcDumpContext { +public: + void SaveFlags(); + + void SetRound(size_t round); + + void Dump(size_t round, SampledRequest*); + + static bool Serialize(butil::IOBuf& buf, SampledRequest* sample); + + RpcDumpContext() + : _cur_req_count(0) + , _cur_fd(-1) + , _last_round(0) + , _max_requests_in_one_file(0) + , _max_files(0) + , _sched_write_time(butil::gettimeofday_us() + FLUSH_TIMEOUT) + , _last_file_time(0) + { + _command_name = bvar::read_command_name(); + SaveFlags(); + // Clean the directory at fist time. + butil::DeleteFile(_dir, true); + } + ~RpcDumpContext() { + if (_cur_fd >= 0) { + close(_cur_fd); + _cur_fd = -1; + } + } + +private: + std::string _command_name; + int _cur_req_count; // written #req in current file + int _cur_fd; // fd of current file + size_t _last_round; + // save gflags which could be reloaded at anytime. + int _max_requests_in_one_file; + int _max_files; + int64_t _sched_write_time; // duetime of last write + int64_t _last_file_time; // time for the postfix of last file + // the queue for remembering oldest file to remove. + std::deque _filenames; + butil::FilePath _dir; + // current filename, being here just to reuse memory. + std::string _cur_filename; + // buffering output to file so they can be written in batch. + butil::IOBuf _unwritten_buf; +}; + +bvar::CollectorSpeedLimit g_rpc_dump_sl = BVAR_COLLECTOR_SPEED_LIMIT_INITIALIZER; +static RpcDumpContext* g_rpc_dump_ctx = NULL; + +void SampledRequest::dump_and_destroy(size_t round) { + static bvar::DisplaySamplingRatio sampling_ratio_var( + "rpc_dump_sampling_ratio", &g_rpc_dump_sl); + + // Safe to modify g_rpc_dump_ctx w/o locking. + RpcDumpContext* rpc_dump_ctx = g_rpc_dump_ctx; + if (rpc_dump_ctx == NULL) { + rpc_dump_ctx = new RpcDumpContext; + g_rpc_dump_ctx = rpc_dump_ctx; + } + rpc_dump_ctx->Dump(round, this); + destroy(); +} + +void SampledRequest::destroy() { + delete this; +} + +// Save gflags which could be reloaded at anytime. +void RpcDumpContext::SaveFlags() { + std::string dir; + CHECK(GFLAGS_NAMESPACE::GetCommandLineOption("rpc_dump_dir", &dir)); + + const size_t pos = dir.find(""); + if (pos != std::string::npos) { + dir.replace(pos, 5/**/, _command_name); + } + _dir = butil::FilePath(dir); + + _max_requests_in_one_file = FLAGS_rpc_dump_max_requests_in_one_file; + _max_files = FLAGS_rpc_dump_max_files; +} + +// Dump a request. +void RpcDumpContext::Dump(size_t round, SampledRequest* sample) { + if (_last_round != round) { + _last_round = round; + SaveFlags(); + } + + if (!Serialize(_unwritten_buf, sample)) { + return; + } + ++_cur_req_count; + if (_cur_req_count >= _max_requests_in_one_file) { + // Reach the limit of #request in a file. + RPC_VLOG << "Write because _cur_req_count=" << _cur_req_count; + } else if (_unwritten_buf.size() >= UNWRITTEN_BUFSIZE) { + // Too much unwritten data + RPC_VLOG << "Write because _unwritten_buf=" << _unwritten_buf.size(); + } else if (butil::gettimeofday_us() >= _sched_write_time) { + // Not write for a while. + RPC_VLOG << "Write because timeout"; + } else { + return; + } + + // Open file if needed. + if (_cur_fd < 0) { + // Make sure the dir exists. + butil::File::Error error; + if (!butil::CreateDirectoryAndGetError(_dir, &error)) { + LOG(ERROR) << "Fail to create directory=`" << _dir.value() + << "', " << error; + return; + } + // Remove oldest files. + while ((int)_filenames.size() >= _max_files && !_filenames.empty()) { + butil::DeleteFile(butil::FilePath(_filenames.front()), false); + _filenames.pop_front(); + } + // Make current time as postfix. + int64_t cur_file_time = butil::gettimeofday_us(); + // Make postfix monotonic. + if (cur_file_time <= _last_file_time) { + cur_file_time = _last_file_time + 1; + } + time_t rawtime = cur_file_time / 1000000L; + struct tm* timeinfo = localtime(&rawtime); + char ts_buf[64]; + strftime(ts_buf, sizeof(ts_buf), "%Y%m%d_%H%M%S", timeinfo); + butil::string_printf(&_cur_filename, "%s/" DUMPED_FILE_PREFIX ".%s_%06u", + _dir.value().c_str(), ts_buf, + (unsigned)(cur_file_time - rawtime * 1000000L)); + _cur_fd = open(_cur_filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666); + if (_cur_fd < 0) { + PLOG(ERROR) << "Fail to open " << _cur_filename; + return; + } + _last_file_time = cur_file_time; + _filenames.push_back(_cur_filename); + } + // Write all data in _unwritten_buf. This is different from writing + // into a socket: local file should always be writable unless error occurs + bool fail_to_write = false; + while (!_unwritten_buf.empty()) { + if (_unwritten_buf.cut_into_file_descriptor(_cur_fd) < 0) { + if (errno != EINTR && errno != EAGAIN) { + PLOG(ERROR) << "Fail to write into " << _cur_filename; + fail_to_write = true; + break; + } + } + } + _unwritten_buf.clear(); + _sched_write_time = butil::gettimeofday_us() + FLUSH_TIMEOUT; + if (fail_to_write || _cur_req_count >= _max_requests_in_one_file) { + // clean up + if (_cur_fd >= 0) { + close(_cur_fd); + _cur_fd = -1; + } + _cur_req_count = 0; + } +} + +bool RpcDumpContext::Serialize(butil::IOBuf& buf, SampledRequest* sample) { + // Use the header of baidu_std. + char rpc_header[12]; + butil::IOBuf::Area header_area = buf.reserve(sizeof(rpc_header)); + + const size_t starting_size = buf.size(); + butil::IOBufAsZeroCopyOutputStream buf_stream(&buf); + if (!sample->meta.SerializeToZeroCopyStream(&buf_stream)) { + LOG(ERROR) << "Fail to serialize"; + return false; + } + const size_t meta_size = buf.size() - starting_size; + buf.append(sample->request); + + uint32_t* dummy = (uint32_t*)rpc_header; // suppress strict-alias warning + *dummy = *(uint32_t*)"PRPC"; + butil::RawPacker(rpc_header + 4) + .pack32(meta_size + sample->request.size()) + .pack32(meta_size); + CHECK_EQ(0, buf.unsafe_assign(header_area, rpc_header)); + return true; +} + +SampleIterator::SampleIterator(const butil::StringPiece& dir) + : _cur_fd(-1) + , _enum(NULL) + , _dir(std::string(dir.data(), dir.size())) { +} + +SampleIterator::~SampleIterator() { + if (_cur_fd) { + ::close(_cur_fd); + _cur_fd = -1; + } + delete _enum; + _enum = NULL; +} + +SampledRequest* SampleIterator::Next() { + if (!_cur_buf.empty()) { + bool error = false; + SampledRequest* r = Pop(_cur_buf, &error); + if (r) { + return r; + } + if (error) { + _cur_buf.clear(); + if (_cur_fd >= 0) { + ::close(_cur_fd); + _cur_fd = -1; + } + } + } + while (1) { + while (_cur_fd >= 0) { + ssize_t nr = _cur_buf.append_from_file_descriptor(_cur_fd, 524288); + if (nr < 0) { + if (errno != EAGAIN && errno != EINTR) { + PLOG(ERROR) << "Fail to read fd=" << _cur_fd; + break; + } + } else if (nr == 0) { // EOF + break; + } else { + return Next(); // tailr + } + } + _cur_buf.clear(); + if (_cur_fd >= 0) { + ::close(_cur_fd); + _cur_fd = -1; + } + + if (_enum == NULL) { + _enum = new butil::FileEnumerator( + _dir, false, butil::FileEnumerator::FILES); + } + butil::FilePath filename = _enum->Next(); + if (filename.empty()) { + return NULL; + } + _cur_fd = open(filename.value().c_str(), O_RDONLY); + } +} + +SampledRequest* SampleIterator::Pop(butil::IOBuf& buf, bool* format_error) { + char backing_buf[12]; + const char* p = (const char*)buf.fetch(backing_buf, sizeof(backing_buf)); + if (NULL == p) { // buf.length() < sizeof(backing_buf) + return NULL; + } + if (*(const uint32_t*)p != *(const uint32_t*)"PRPC") { + LOG(ERROR) << "Unmatched magic string"; + *format_error = true; + return NULL; + } + uint32_t body_size; + uint32_t meta_size; + butil::RawUnpacker(p + 4).unpack32(body_size).unpack32(meta_size); + if (body_size > FLAGS_max_body_size) { + LOG(ERROR) << "Too big body=" << body_size; + *format_error = true; + return NULL; + } else if (buf.length() < sizeof(backing_buf) + body_size) { + return NULL; + } + if (meta_size > body_size) { + LOG(ERROR) << "meta_size=" << meta_size << " is bigger than body_size=" + << body_size; + *format_error = true; + return NULL; + } + buf.pop_front(sizeof(backing_buf)); + butil::IOBuf meta_buf; + buf.cutn(&meta_buf, meta_size); + std::unique_ptr req(new SampledRequest); + if (!ParsePbFromIOBuf(&req->meta, meta_buf)) { + LOG(ERROR) << "Fail to parse RpcDumpMeta"; + *format_error = true; + return NULL; + } + buf.cutn(&req->request, body_size - meta_size); + return req.release(); +} + +#undef DUMPED_FILE_PREFIX + +} // namespace brpc diff --git a/src/brpc/rpc_dump.h b/src/brpc/rpc_dump.h new file mode 100644 index 0000000..09bb6f1 --- /dev/null +++ b/src/brpc/rpc_dump.h @@ -0,0 +1,106 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_RPC_DUMP_H +#define BRPC_RPC_DUMP_H + +#include +#include "butil/iobuf.h" // IOBuf +#include "butil/files/file_path.h" // FilePath +#include "bvar/collector.h" +#include "brpc/rpc_dump.pb.h" // RpcDumpMeta + +namespace butil { +class FileEnumerator; +} + +namespace brpc { + +DECLARE_bool(rpc_dump); + +// Randomly take samples of all requests and write into a file in batch in +// a background thread. + +// Example: +// SampledRequest* sample = AskToBeSampled(); +// If (sample) { +// sample->xxx = yyy; +// sample->request = ...; +// sample->Submit(); +// } +// +// In practice, sampled requests are just small fraction of all requests. +// The overhead of sampling should be negligible for overall performance. + +class SampledRequest : public bvar::Collected { +public: + butil::IOBuf request; + RpcDumpMeta meta; + + // Implement methods of Sampled. + void dump_and_destroy(size_t round) override; + void destroy() override; + bvar::CollectorSpeedLimit* speed_limit() override { + extern bvar::CollectorSpeedLimit g_rpc_dump_sl; + return &g_rpc_dump_sl; + } +}; + +// If this function returns non-NULL, the caller must fill the returned +// object and submit it for later dumping by calling SubmitSample(). If +// the caller ignores non-NULL return value, the object is leaked. +inline SampledRequest* AskToBeSampled() { + extern bvar::CollectorSpeedLimit g_rpc_dump_sl; + if (!FLAGS_rpc_dump || !bvar::is_collectable(&g_rpc_dump_sl)) { + return NULL; + } + return new (std::nothrow) SampledRequest; +} + +// Read samples from dumped files in a directory. +// Example: +// SampleIterator it("./rpc_dump_echo_server"); +// for (SampledRequest* req = it->Next(); req != NULL; req = it->Next()) { +// ... +// } +class SampleIterator { +public: + explicit SampleIterator(const butil::StringPiece& dir); + ~SampleIterator(); + + // Read a sample. Order of samples are not guaranteed to be same with + // the order that they're stored in dumped files. + // Returns the sample which should be deleted by caller. NULL means + // all dumped files are read. + SampledRequest* Next(); + +private: + // Parse on request from the buf. Set `format_error' to true when + // the buf does not match the format. + static SampledRequest* Pop(butil::IOBuf& buf, bool* format_error); + + butil::IOPortal _cur_buf; + int _cur_fd; + butil::FileEnumerator* _enum; + butil::FilePath _dir; +}; + +} // namespace brpc + + +#endif // BRPC_RPC_DUMP_H diff --git a/src/brpc/rpc_dump.proto b/src/brpc/rpc_dump.proto new file mode 100644 index 0000000..e3c8aab --- /dev/null +++ b/src/brpc/rpc_dump.proto @@ -0,0 +1,48 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +import "brpc/options.proto"; + +package brpc; + +message RpcDumpMeta { + // baidu_std, hulu_pbrpc + optional string service_name = 1; + + // baidu_std, sofa_pbrpc(full_method_name) + optional string method_name = 2; + + // hulu_pbrpc + optional int32 method_index = 3; + + // baidu_std, hulu_pbrpc, sofa_pbrpc + optional CompressType compress_type = 4; + optional ProtocolType protocol_type = 5; + + // baidu_std, hulu_pbrpc + optional int32 attachment_size = 6; + + // baidu_std + optional bytes authentication_data = 7; + + // hulu_pbrpc + optional bytes user_data = 8; + + // nshead + optional bytes nshead = 9; +} diff --git a/src/brpc/rpc_pb_message_factory.cpp b/src/brpc/rpc_pb_message_factory.cpp new file mode 100644 index 0000000..27e2457 --- /dev/null +++ b/src/brpc/rpc_pb_message_factory.cpp @@ -0,0 +1,50 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/rpc_pb_message_factory.h" + +namespace brpc { + +struct DefaultRpcPBMessages : public RpcPBMessages { + DefaultRpcPBMessages() : request(NULL), response(NULL) {} + ::google::protobuf::Message* Request() override { return request; } + ::google::protobuf::Message* Response() override { return response; } + + ::google::protobuf::Message* request; + ::google::protobuf::Message* response; +}; + + +RpcPBMessages* DefaultRpcPBMessageFactory::Get( + const ::google::protobuf::Service& service, + const ::google::protobuf::MethodDescriptor& method) { + auto messages = butil::get_object(); + messages->request = service.GetRequestPrototype(&method).New(); + messages->response = service.GetResponsePrototype(&method).New(); + return messages; +} + +void DefaultRpcPBMessageFactory::Return(RpcPBMessages* messages) { + auto default_messages = static_cast(messages); + delete default_messages->request; + delete default_messages->response; + default_messages->request = NULL; + default_messages->response = NULL; + butil::return_object(default_messages); +} + +} // namespace brpc \ No newline at end of file diff --git a/src/brpc/rpc_pb_message_factory.h b/src/brpc/rpc_pb_message_factory.h new file mode 100644 index 0000000..20d27a0 --- /dev/null +++ b/src/brpc/rpc_pb_message_factory.h @@ -0,0 +1,138 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_RPC_PB_MESSAGE_FACTORY_H +#define BRPC_RPC_PB_MESSAGE_FACTORY_H + +#include +#include +#include +#include +#include "butil/object_pool.h" + +namespace brpc { + +// Inherit this class to customize rpc protobuf messages, +// include request and response. +class RpcPBMessages { +public: + virtual ~RpcPBMessages() = default; + // Get protobuf request message. + virtual google::protobuf::Message* Request() = 0; + // Get protobuf response message. + virtual google::protobuf::Message* Response() = 0; +}; + +// Factory to manage `RpcPBMessages'. +class RpcPBMessageFactory { +public: + virtual ~RpcPBMessageFactory() = default; + // Get `RpcPBMessages' according to `service' and `method'. + // Common practice to create protobuf message: + // service.GetRequestPrototype(&method).New() -> request; + // service.GetResponsePrototype(&method).New() -> response. + virtual RpcPBMessages* Get(const ::google::protobuf::Service& service, + const ::google::protobuf::MethodDescriptor& method) = 0; + // Return `RpcPBMessages' to factory. + virtual void Return(RpcPBMessages* messages) = 0; +}; + +class DefaultRpcPBMessageFactory : public RpcPBMessageFactory { +public: + RpcPBMessages* Get(const ::google::protobuf::Service& service, + const ::google::protobuf::MethodDescriptor& method) override; + void Return(RpcPBMessages* messages) override; +}; + +namespace internal { + +// Allocate protobuf message from arena. +// The arena is created with `StartBlockSize' and `MaxBlockSize' options. +// For more details, see `google::protobuf::ArenaOptions'. +template +struct ArenaRpcPBMessages : public RpcPBMessages { + class ArenaOptionsWrapper { + public: + ArenaOptionsWrapper() { + options.start_block_size = StartBlockSize; + options.max_block_size = MaxBlockSize; + } + + private: + friend struct ArenaRpcPBMessages; + ::google::protobuf::ArenaOptions options; + }; + + explicit ArenaRpcPBMessages(ArenaOptionsWrapper options_wrapper) + : arena(options_wrapper.options) + , request(NULL) + , response(NULL) {} + + ::google::protobuf::Message* Request() override { return request; } + ::google::protobuf::Message* Response() override { return response; } + + ::google::protobuf::Arena arena; + ::google::protobuf::Message* request; + ::google::protobuf::Message* response; +}; + +template +class ArenaRpcPBMessageFactory : public RpcPBMessageFactory { + typedef ::brpc::internal::ArenaRpcPBMessages + ArenaRpcPBMessages; +public: + ArenaRpcPBMessageFactory() { + _arena_options.start_block_size = StartBlockSize; + _arena_options.max_block_size = MaxBlockSize; + } + + RpcPBMessages* Get(const ::google::protobuf::Service& service, + const ::google::protobuf::MethodDescriptor& method) override { + typename ArenaRpcPBMessages::ArenaOptionsWrapper options_wrapper; + auto messages = butil::get_object(options_wrapper); + messages->request = service.GetRequestPrototype(&method).New(&messages->arena); + messages->response = service.GetResponsePrototype(&method).New(&messages->arena); + return messages; + } + + void Return(RpcPBMessages* messages) override { + auto arena_messages = static_cast(messages); + arena_messages->request = NULL; + arena_messages->response = NULL; + arena_messages->arena.Reset(); + butil::return_object(arena_messages); + } + +private: + ::google::protobuf::ArenaOptions _arena_options; +}; + +} + +template +RpcPBMessageFactory* GetArenaRpcPBMessageFactory() { + return new ::brpc::internal::ArenaRpcPBMessageFactory(); +} + +BUTIL_FORCE_INLINE RpcPBMessageFactory* GetArenaRpcPBMessageFactory() { + // Default arena options, same as `google::protobuf::ArenaOptions'. + return GetArenaRpcPBMessageFactory<256, 8192>(); +} + +} // namespace brpc + +#endif // BRPC_RPC_PB_MESSAGE_FACTORY_H diff --git a/src/brpc/rtmp.cpp b/src/brpc/rtmp.cpp new file mode 100644 index 0000000..3168123 --- /dev/null +++ b/src/brpc/rtmp.cpp @@ -0,0 +1,2897 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include // StringOutputStream +#include "bthread/bthread.h" // bthread_id_xx +#include "bthread/unstable.h" // bthread_timer_del +#include "brpc/log.h" +#include "brpc/callback.h" // Closure +#include "brpc/channel.h" // Channel +#include "brpc/socket_map.h" // SocketMap +#include "brpc/socket.h" // Socket +#include "brpc/policy/rtmp_protocol.h" // policy::* +#include "brpc/rtmp.h" +#include "brpc/details/rtmp_utils.h" + + +namespace brpc { + +DEFINE_bool(rtmp_server_close_connection_on_error, true, + "Close the client connection on play/publish errors, clients setting" + " RtmpConnectRequest.stream_multiplexing to true are not affected" + " by this flag"); + +struct RtmpBvars { + bvar::Adder client_count; + bvar::Adder client_stream_count; + bvar::Adder retrying_client_stream_count; + bvar::Adder server_stream_count; + + RtmpBvars() + : client_count("rtmp_client_count") + , client_stream_count("rtmp_client_stream_count") + , retrying_client_stream_count("rtmp_retrying_client_stream_count") + , server_stream_count("rtmp_server_stream_count") { + } +}; +inline RtmpBvars* get_rtmp_bvars() { + return butil::get_leaky_singleton(); +} + +namespace policy { +int SendC0C1(int fd, bool* is_simple_handshake); +int WriteWithoutOvercrowded(Socket*, SocketMessagePtr<>& msg); +} + +FlvWriter::FlvWriter(butil::IOBuf* buf) + : _write_header(false), _buf(buf), _options() { +} + +FlvWriter::FlvWriter(butil::IOBuf* buf, const FlvWriterOptions& options) + : _write_header(false), _buf(buf), _options(options) { +} + +butil::Status FlvWriter::Write(const RtmpVideoMessage& msg) { + char buf[32]; + char* p = buf; + if (!_write_header) { + _write_header = true; + const char flags_bit = static_cast(_options.flv_content_type); + const char header[9] = { 'F', 'L', 'V', 0x01, flags_bit, 0, 0, 0, 0x09 }; + memcpy(p, header, sizeof(header)); + p += sizeof(header); + policy::WriteBigEndian4Bytes(&p, 0); // PreviousTagSize0 + } + // FLV tag + *p++ = FLV_TAG_VIDEO; + policy::WriteBigEndian3Bytes(&p, msg.size()); + policy::WriteBigEndian3Bytes(&p, (msg.timestamp & 0xFFFFFF)); + *p++ = (msg.timestamp >> 24) & 0xFF; + policy::WriteBigEndian3Bytes(&p, 0); // StreamID + // header of VIDEODATA + *p++ = ((msg.frame_type & 0xF) << 4) | (msg.codec & 0xF); + _buf->append(buf, p - buf); + _buf->append(msg.data); + // PreviousTagSize + p = buf; + policy::WriteBigEndian4Bytes(&p, 11 + msg.size()); + _buf->append(buf, p - buf); + return butil::Status::OK(); +} + +butil::Status FlvWriter::Write(const RtmpAudioMessage& msg) { + char buf[32]; + char* p = buf; + if (!_write_header) { + _write_header = true; + const char flags_bit = static_cast(_options.flv_content_type); + const char header[9] = { 'F', 'L', 'V', 0x01, flags_bit, 0, 0, 0, 0x09 }; + memcpy(p, header, sizeof(header)); + p += sizeof(header); + policy::WriteBigEndian4Bytes(&p, 0); // PreviousTagSize0 + } + // FLV tag + *p++ = FLV_TAG_AUDIO; + policy::WriteBigEndian3Bytes(&p, msg.size()); + policy::WriteBigEndian3Bytes(&p, (msg.timestamp & 0xFFFFFF)); + *p++ = (msg.timestamp >> 24) & 0xFF; + policy::WriteBigEndian3Bytes(&p, 0); // StreamID + // header of AUDIODATA + *p++ = ((msg.codec & 0xF) << 4) + | ((msg.rate & 0x3) << 2) + | ((msg.bits & 0x1) << 1) + | (msg.type & 0x1); + _buf->append(buf, p - buf); + _buf->append(msg.data); + // PreviousTagSize + p = buf; + policy::WriteBigEndian4Bytes(&p, 11 + msg.size()); + _buf->append(buf, p - buf); + return butil::Status::OK(); +} + +butil::Status FlvWriter::WriteScriptData(const butil::IOBuf& req_buf, uint32_t timestamp) { + char buf[32]; + char* p = buf; + if (!_write_header) { + _write_header = true; + const char flags_bit = static_cast(_options.flv_content_type); + const char header[9] = { 'F', 'L', 'V', 0x01, flags_bit, 0, 0, 0, 0x09 }; + memcpy(p, header, sizeof(header)); + p += sizeof(header); + policy::WriteBigEndian4Bytes(&p, 0); // PreviousTagSize0 + } + // FLV tag + *p++ = FLV_TAG_SCRIPT_DATA; + policy::WriteBigEndian3Bytes(&p, req_buf.size()); + policy::WriteBigEndian3Bytes(&p, (timestamp & 0xFFFFFF)); + *p++ = (timestamp >> 24) & 0xFF; + policy::WriteBigEndian3Bytes(&p, 0); // StreamID + _buf->append(buf, p - buf); + _buf->append(req_buf); + // PreviousTagSize + p = buf; + policy::WriteBigEndian4Bytes(&p, 11 + req_buf.size()); + _buf->append(buf, p - buf); + return butil::Status::OK(); +} + +butil::Status FlvWriter::Write(const RtmpCuePoint& cuepoint) { + butil::IOBuf req_buf; + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + WriteAMFString(RTMP_AMF0_SET_DATAFRAME, &ostream); + WriteAMFString(RTMP_AMF0_ON_CUE_POINT, &ostream); + WriteAMFObject(cuepoint.data, &ostream); + if (!ostream.good()) { + return butil::Status(EINVAL, "Fail to serialize cuepoint"); + } + } + return WriteScriptData(req_buf, cuepoint.timestamp); +} + +butil::Status FlvWriter::Write(const RtmpMetaData& metadata) { + butil::IOBuf req_buf; + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + WriteAMFString(RTMP_AMF0_ON_META_DATA, &ostream); + WriteAMFObject(metadata.data, &ostream); + if (!ostream.good()) { + return butil::Status(EINVAL, "Fail to serialize metadata"); + } + } + return WriteScriptData(req_buf, metadata.timestamp); +} + +FlvReader::FlvReader(butil::IOBuf* buf) + : _read_header(false), _buf(buf) { +} + +butil::Status FlvReader::ReadHeader() { + if (!_read_header) { + // 9 is the size of FlvHeader, which is usually composed of + // { 'F', 'L', 'V', 0x01, 0x05, 0, 0, 0, 0x09 }. + char header_buf[9 + 4/* PreviousTagSize0 */]; + const char* p = (const char*)_buf->fetch(header_buf, sizeof(header_buf)); + if (p == NULL) { + return butil::Status(EAGAIN, "Fail to read, not enough data"); + } + const char flv_header_signature[3] = { 'F', 'L', 'V' }; + if (memcmp(p, flv_header_signature, sizeof(flv_header_signature)) != 0) { + LOG(FATAL) << "Fail to parse FLV header"; + return butil::Status(EINVAL, "Fail to parse FLV header"); + } + _buf->pop_front(sizeof(header_buf)); + _read_header = true; + } + return butil::Status::OK(); +} + +butil::Status FlvReader::PeekMessageType(FlvTagType* type_out) { + butil::Status st = ReadHeader(); + if (!st.ok()) { + return st; + } + const char* p = (const char*)_buf->fetch1(); + if (p == NULL) { + return butil::Status(EAGAIN, "Fail to read, not enough data"); + } + FlvTagType type = (FlvTagType)*p; + if (type != FLV_TAG_AUDIO && type != FLV_TAG_VIDEO && + type != FLV_TAG_SCRIPT_DATA) { + return butil::Status(EINVAL, "Fail to parse FLV tag"); + } + if (type_out) { + *type_out = type; + } + return butil::Status::OK(); +} + +butil::Status FlvReader::Read(RtmpVideoMessage* msg) { + char tags[11]; + const unsigned char* p = (const unsigned char*)_buf->fetch(tags, sizeof(tags)); + if (p == NULL) { + return butil::Status(EAGAIN, "Fail to read, not enough data"); + } + if (*p != FLV_TAG_VIDEO) { + return butil::Status(EINVAL, "Fail to parse RtmpVideoMessage"); + } + uint32_t msg_size = policy::ReadBigEndian3Bytes(p + 1); + uint32_t timestamp = policy::ReadBigEndian3Bytes(p + 4); + timestamp |= (*(p + 7) << 24); + // The tag body carries at least the 1-byte VideoTagHeader consumed below. + // A DataSize of 0 makes `msg_size - 1' wrap to 0xFFFFFFFF and the cutn() + // then swallows the whole remaining buffer as one message. + if (msg_size < 1) { + return butil::Status(EINVAL, "Invalid FLV video tag with DataSize=0"); + } + if (_buf->length() < 11 + msg_size + 4/*PreviousTagSize*/) { + return butil::Status(EAGAIN, "Fail to read, not enough data"); + } + _buf->pop_front(11); + char first_byte = 0; + CHECK(_buf->cut1(&first_byte)); + msg->timestamp = timestamp; + msg->frame_type = (FlvVideoFrameType)((first_byte >> 4) & 0xF); + msg->codec = (FlvVideoCodec)(first_byte & 0xF); + // TODO(zhujiashun): check the validation of frame_type and codec + _buf->cutn(&msg->data, msg_size - 1); + _buf->pop_front(4/* PreviousTagSize0 */); + + return butil::Status::OK(); +} + +butil::Status FlvReader::Read(RtmpAudioMessage* msg) { + char tags[11]; + const unsigned char* p = (const unsigned char*)_buf->fetch(tags, sizeof(tags)); + if (p == NULL) { + return butil::Status(EAGAIN, "Fail to read, not enough data"); + } + if (*p != FLV_TAG_AUDIO) { + return butil::Status(EINVAL, "Fail to parse RtmpAudioMessage"); + } + uint32_t msg_size = policy::ReadBigEndian3Bytes(p + 1); + uint32_t timestamp = policy::ReadBigEndian3Bytes(p + 4); + timestamp |= (*(p + 7) << 24); + // The tag body carries at least the 1-byte AudioTagHeader consumed below. + // A DataSize of 0 makes `msg_size - 1' wrap to 0xFFFFFFFF and the cutn() + // then swallows the whole remaining buffer as one message. + if (msg_size < 1) { + return butil::Status(EINVAL, "Invalid FLV audio tag with DataSize=0"); + } + if (_buf->length() < 11 + msg_size + 4/*PreviousTagSize*/) { + return butil::Status(EAGAIN, "Fail to read, not enough data"); + } + _buf->pop_front(11); + char first_byte = 0; + CHECK(_buf->cut1(&first_byte)); + msg->timestamp = timestamp; + msg->codec = (FlvAudioCodec)((first_byte >> 4) & 0xF); + msg->rate = (FlvSoundRate)((first_byte >> 2) & 0x3); + msg->bits = (FlvSoundBits)((first_byte >> 1) & 0x1); + msg->type = (FlvSoundType)(first_byte & 0x1); + _buf->cutn(&msg->data, msg_size - 1); + _buf->pop_front(4/* PreviousTagSize0 */); + + return butil::Status::OK(); +} + +butil::Status FlvReader::Read(RtmpMetaData* msg, std::string* name) { + char tags[11]; + const unsigned char* p = (const unsigned char*)_buf->fetch(tags, sizeof(tags)); + if (p == NULL) { + return butil::Status(EAGAIN, "Fail to read, not enough data"); + } + if (*p != FLV_TAG_SCRIPT_DATA) { + return butil::Status(EINVAL, "Fail to parse RtmpScriptMessage"); + } + uint32_t msg_size = policy::ReadBigEndian3Bytes(p + 1); + uint32_t timestamp = policy::ReadBigEndian3Bytes(p + 4); + timestamp |= (*(p + 7) << 24); + if (_buf->length() < 11 + msg_size + 4/*PreviousTagSize*/) { + return butil::Status(EAGAIN, "Fail to read, not enough data"); + } + _buf->pop_front(11); + butil::IOBuf req_buf; + _buf->cutn(&req_buf, msg_size); + _buf->pop_front(4/* PreviousTagSize0 */); + { + butil::IOBufAsZeroCopyInputStream zc_stream(req_buf); + AMFInputStream istream(&zc_stream); + if (!ReadAMFString(name, &istream)) { + return butil::Status(EINVAL, "Fail to read AMF string"); + } + if (!ReadAMFObject(&msg->data, &istream)) { + return butil::Status(EINVAL, "Fail to read AMF object"); + } + } + msg->timestamp = timestamp; + return butil::Status::OK(); +} + +const char* FlvVideoFrameType2Str(FlvVideoFrameType t) { + switch (t) { + case FLV_VIDEO_FRAME_KEYFRAME: return "keyframe"; + case FLV_VIDEO_FRAME_INTERFRAME: return "interframe"; + case FLV_VIDEO_FRAME_DISPOSABLE_INTERFRAME: return "disposable interframe"; + case FLV_VIDEO_FRAME_GENERATED_KEYFRAME: return "generated keyframe"; + case FLV_VIDEO_FRAME_INFOFRAME: return "info/command frame"; + } + return "Unknown FlvVideoFrameType"; +} + +const char* FlvVideoCodec2Str(FlvVideoCodec id) { + switch (id) { + case FLV_VIDEO_JPEG: return "JPEG"; + case FLV_VIDEO_SORENSON_H263: return "Sorenson H.263"; + case FLV_VIDEO_SCREEN_VIDEO: return "Screen video"; + case FLV_VIDEO_ON2_VP6: return "On2 VP6"; + case FLV_VIDEO_ON2_VP6_WITH_ALPHA_CHANNEL: + return "On2 VP6 with alpha channel"; + case FLV_VIDEO_SCREEN_VIDEO_V2: return "Screen video version 2"; + case FLV_VIDEO_AVC: return "AVC"; + case FLV_VIDEO_HEVC: return "H.265"; + } + return "Unknown FlvVideoCodec"; +} + +const char* FlvAudioCodec2Str(FlvAudioCodec codec) { + switch (codec) { + case FLV_AUDIO_LINEAR_PCM_PLATFORM_ENDIAN: + return "Linear PCM, platform endian"; + case FLV_AUDIO_ADPCM: return "ADPCM"; + case FLV_AUDIO_MP3: return "MP3"; + case FLV_AUDIO_LINEAR_PCM_LITTLE_ENDIAN: + return "Linear PCM, little endian"; + case FLV_AUDIO_NELLYMOSER_16KHZ_MONO: + return "Nellymoser 16-kHz mono"; + case FLV_AUDIO_NELLYMOSER_8KHZ_MONO: + return "Nellymoser 8-kHz mono"; + case FLV_AUDIO_NELLYMOSER: + return "Nellymoser"; + case FLV_AUDIO_G711_ALAW_LOGARITHMIC_PCM: + return "G.711 A-law logarithmic PCM"; + case FLV_AUDIO_G711_MULAW_LOGARITHMIC_PCM: + return "G.711 mu-law logarithmic PCM"; + case FLV_AUDIO_RESERVED: + return "reserved"; + case FLV_AUDIO_AAC: return "AAC"; + case FLV_AUDIO_SPEEX: return "Speex"; + case FLV_AUDIO_MP3_8KHZ: return "MP3 8-Khz"; + case FLV_AUDIO_DEVICE_SPECIFIC_SOUND: + return "Device-specific sound"; + } + return "Unknown FlvAudioCodec"; +} + +const char* FlvSoundRate2Str(FlvSoundRate rate) { + switch (rate) { + case FLV_SOUND_RATE_5512HZ: return "5512"; + case FLV_SOUND_RATE_11025HZ: return "11025"; + case FLV_SOUND_RATE_22050HZ: return "22050"; + case FLV_SOUND_RATE_44100HZ: return "44100"; + } + return "Unknown FlvSoundRate"; +} + +const char* FlvSoundBits2Str(FlvSoundBits size) { + switch (size) { + case FLV_SOUND_8BIT: return "8"; + case FLV_SOUND_16BIT: return "16"; + } + return "Unknown FlvSoundBits"; +} + +const char* FlvSoundType2Str(FlvSoundType t) { + switch (t) { + case FLV_SOUND_MONO: return "mono"; + case FLV_SOUND_STEREO: return "stereo"; + } + return "Unknown FlvSoundType"; +} + +std::ostream& operator<<(std::ostream& os, const RtmpAudioMessage& msg) { + return os << "AudioMessage{timestamp=" << msg.timestamp + << " codec=" << FlvAudioCodec2Str(msg.codec) + << " rate=" << FlvSoundRate2Str(msg.rate) + << " bits=" << FlvSoundBits2Str(msg.bits) + << " type=" << FlvSoundType2Str(msg.type) + << " data=" << butil::ToPrintable(msg.data) << '}'; +} + +std::ostream& operator<<(std::ostream& os, const RtmpVideoMessage& msg) { + return os << "VideoMessage{timestamp=" << msg.timestamp + << " type=" << FlvVideoFrameType2Str(msg.frame_type) + << " codec=" << FlvVideoCodec2Str(msg.codec) + << " data=" << butil::ToPrintable(msg.data) << '}'; +} + +butil::Status RtmpAACMessage::Create(const RtmpAudioMessage& msg) { + if (msg.codec != FLV_AUDIO_AAC) { + return butil::Status(EINVAL, "codec=%s is not AAC", + FlvAudioCodec2Str(msg.codec)); + } + const uint8_t* p = (const uint8_t*)msg.data.fetch1(); + if (p == NULL) { + return butil::Status(EINVAL, "Not enough data in AudioMessage"); + } + if (*p > FLV_AAC_PACKET_RAW) { + return butil::Status(EINVAL, "Invalid AAC packet_type=%d", (int)*p); + } + this->timestamp = msg.timestamp; + this->rate = msg.rate; + this->bits = msg.bits; + this->type = msg.type; + this->packet_type = (FlvAACPacketType)*p; + msg.data.append_to(&data, msg.data.size() - 1, 1); + return butil::Status::OK(); +} + +AudioSpecificConfig::AudioSpecificConfig() + : aac_object(AAC_OBJECT_UNKNOWN) + , aac_sample_rate(0) + , aac_channels(0) { +} + +butil::Status AudioSpecificConfig::Create(const butil::IOBuf& buf) { + if (buf.size() < 2u) { + return butil::Status(EINVAL, "data_size=%" PRIu64 " is too short", + (uint64_t)buf.size()); + } + char tmpbuf[2]; + buf.copy_to(tmpbuf, arraysize(tmpbuf)); + return Create(tmpbuf, arraysize(tmpbuf)); +} + +butil::Status AudioSpecificConfig::Create(const void* data, size_t len) { + if (len < 2u) { + return butil::Status(EINVAL, "data_size=%" PRIu64 " is too short", (uint64_t)len); + } + uint8_t profile_ObjectType = ((const char*)data)[0]; + uint8_t samplingFrequencyIndex = ((const char*)data)[1]; + aac_channels = (samplingFrequencyIndex >> 3) & 0x0f; + aac_sample_rate = ((profile_ObjectType << 1) & 0x0e) | ((samplingFrequencyIndex >> 7) & 0x01); + aac_object = (AACObjectType)((profile_ObjectType >> 3) & 0x1f); + if (aac_object == AAC_OBJECT_UNKNOWN) { + return butil::Status(EINVAL, "Invalid object type"); + } + return butil::Status::OK(); +} + +bool RtmpAudioMessage::IsAACSequenceHeader() const { + if (codec != FLV_AUDIO_AAC) { + return false; + } + const uint8_t* p = (const uint8_t*)data.fetch1(); + if (p == NULL) { + return false; + } + return *p == FLV_AAC_PACKET_SEQUENCE_HEADER; +} + +butil::Status RtmpAVCMessage::Create(const RtmpVideoMessage& msg) { + if (msg.codec != FLV_VIDEO_AVC) { + return butil::Status(EINVAL, "codec=%s is not AVC", + FlvVideoCodec2Str(msg.codec)); + } + uint8_t buf[4]; + const uint8_t* p = (const uint8_t*)msg.data.fetch(buf, sizeof(buf)); + if (p == NULL) { + return butil::Status(EINVAL, "Not enough data in VideoMessage"); + } + if (*p > FLV_AVC_PACKET_END_OF_SEQUENCE) { + return butil::Status(EINVAL, "Invalid AVC packet_type=%d", (int)*p); + } + this->timestamp = msg.timestamp; + this->frame_type = msg.frame_type; + this->packet_type = (FlvAVCPacketType)*p; + this->composition_time = policy::ReadBigEndian3Bytes(p + 1); + msg.data.append_to(&data, msg.data.size() - 4, 4); + return butil::Status::OK(); +} + +bool RtmpVideoMessage::IsAVCSequenceHeader() const { + if (codec != FLV_VIDEO_AVC || frame_type != FLV_VIDEO_FRAME_KEYFRAME) { + return false; + } + const uint8_t* p = (const uint8_t*)data.fetch1(); + if (p == NULL) { + return false; + } + return *p == FLV_AVC_PACKET_SEQUENCE_HEADER; +} + +bool RtmpVideoMessage::IsHEVCSequenceHeader() const { + if (codec != FLV_VIDEO_HEVC || frame_type != FLV_VIDEO_FRAME_KEYFRAME) { + return false; + } + const uint8_t* p = (const uint8_t*)data.fetch1(); + if (p == NULL) { + return false; + } + return *p == FLV_AVC_PACKET_SEQUENCE_HEADER; +} + +const char* AVCProfile2Str(AVCProfile p) { + switch (p) { + case AVC_PROFILE_BASELINE: return "Baseline"; + case AVC_PROFILE_CONSTRAINED_BASELINE: return "ConstrainedBaseline"; + case AVC_PROFILE_MAIN: return "Main"; + case AVC_PROFILE_EXTENDED: return "Extended"; + case AVC_PROFILE_HIGH: return "High"; + case AVC_PROFILE_HIGH10: return "High10"; + case AVC_PROFILE_HIGH10_INTRA: return "High10Intra"; + case AVC_PROFILE_HIGH422: return "High422"; + case AVC_PROFILE_HIGH422_INTRA: return "High422Intra"; + case AVC_PROFILE_HIGH444: return "High444"; + case AVC_PROFILE_HIGH444_PREDICTIVE: return "High444Predictive"; + case AVC_PROFILE_HIGH444_INTRA: return "High444Intra"; + } + return "Unknown"; +} + +AVCDecoderConfigurationRecord::AVCDecoderConfigurationRecord() + : width(0) + , height(0) + , avc_profile((AVCProfile)0) + , avc_level((AVCLevel)0) + , length_size_minus1(-1) { +} + +std::ostream& operator<<(std::ostream& os, + const AVCDecoderConfigurationRecord& r) { + os << "{profile=" << AVCProfile2Str(r.avc_profile) + << " level=" << (int)r.avc_level + << " length_size_minus1=" << (int)r.length_size_minus1 + << " width=" << r.width + << " height=" << r.height + << " sps=["; + for (size_t i = 0; i < r.sps_list.size(); ++i) { + if (i) { + os << ' '; + } + os << r.sps_list[i].size(); + } + os << "] pps=["; + for (size_t i = 0; i < r.pps_list.size(); ++i) { + if (i) { + os << ' '; + } + os << r.pps_list[i].size(); + } + os << "]}"; + return os; +} + +butil::Status AVCDecoderConfigurationRecord::Create(const butil::IOBuf& buf) { + // the buf should be short generally, copy it out to continuous memory + // to simplify parsing. + DEFINE_SMALL_ARRAY(char, cont_buf, buf.size(), 64); + buf.copy_to(cont_buf, buf.size()); + return Create(cont_buf, buf.size()); +} + +butil::Status AVCDecoderConfigurationRecord::Create(const void* data, size_t len) { + butil::StringPiece buf((const char*)data, len); + if (buf.size() < 6) { + return butil::Status(EINVAL, "Length=%lu is not long enough", + (unsigned long)buf.size()); + } + // skip configurationVersion at buf[0] + avc_profile = (AVCProfile)buf[1]; + // skip profile_compatibility at buf[2] + avc_level = (AVCLevel)buf[3]; + + // 5.3.4.2.1 Syntax, H.264-AVC-ISO_IEC_14496-15.pdf, page 16 + // 5.2.4.1 AVC decoder configuration record + // 5.2.4.1.2 Semantics + // The value of this field shall be one of 0, 1, or 3 corresponding to a + // length encoded with 1, 2, or 4 bytes, respectively. + length_size_minus1 = buf[4] & 0x03; + if (length_size_minus1 == 2) { + return butil::Status(EINVAL, "lengthSizeMinusOne should never be 2"); + } + + // Parsing SPS + const int num_sps = (int)(buf[5] & 0x1f); + buf.remove_prefix(6); + sps_list.clear(); + sps_list.reserve(num_sps); + for (int i = 0; i < num_sps; ++i) { + if (buf.size() < 2) { + return butil::Status(EINVAL, "Not enough data to decode SPS-length"); + } + const uint16_t sps_length = policy::ReadBigEndian2Bytes(buf.data()); + if (buf.size() < 2u + sps_length) { + return butil::Status(EINVAL, "Not enough data to decode SPS"); + } + if (sps_length > 0) { + butil::Status st = ParseSPS(buf.substr(2, sps_length), sps_length); + if (!st.ok()) { + return st; + } + sps_list.push_back(buf.substr(2, sps_length).as_string()); + } + buf.remove_prefix(2 + sps_length); + } + // Parsing PPS + pps_list.clear(); + if (buf.empty()) { + return butil::Status(EINVAL, "Not enough data to decode PPS"); + } + const int num_pps = (int)buf[0]; + buf.remove_prefix(1); + for (int i = 0; i < num_pps; ++i) { + if (buf.size() < 2) { + return butil::Status(EINVAL, "Not enough data to decode PPS-length"); + } + const uint16_t pps_length = policy::ReadBigEndian2Bytes(buf.data()); + if (buf.size() < 2u + pps_length) { + return butil::Status(EINVAL, "Not enough data to decode PPS"); + } + if (pps_length > 0) { + pps_list.push_back(buf.substr(2, pps_length).as_string()); + } + buf.remove_prefix(2 + pps_length); + } + return butil::Status::OK(); +} + +butil::Status AVCDecoderConfigurationRecord::ParseSPS( + const butil::StringPiece& buf, size_t sps_length) { + // for NALU, 7.3.1 NAL unit syntax + // H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 61. + if (buf.empty()) { + return butil::Status(EINVAL, "SPS is empty"); + } + const int8_t nutv = buf[0]; + const int8_t forbidden_zero_bit = (nutv >> 7) & 0x01; + if (forbidden_zero_bit) { + return butil::Status(EINVAL, "forbidden_zero_bit shall equal 0"); + } + // nal_ref_idc not equal to 0 specifies that the content of the NAL unit + // contains: + // a sequence parameter set + // or a picture parameter set + // or a slice of a reference picture + // or a slice data partition of a reference picture. + int8_t nal_ref_idc = (nutv >> 5) & 0x03; + if (!nal_ref_idc) { + return butil::Status(EINVAL, "nal_ref_idc is 0"); + } + // 7.4.1 NAL unit semantics + // H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 61. + // nal_unit_type specifies the type of RBSP data structure contained in + // the NAL unit as specified in Table 7-1. + const AVCNaluType nal_unit_type = (AVCNaluType)(nutv & 0x1f); + if (nal_unit_type != AVC_NALU_SPS) { + return butil::Status(EINVAL, "nal_unit_type is not %d", (int)AVC_NALU_SPS); + } + // Extract the rbsp from sps. + DEFINE_SMALL_ARRAY(char, rbsp, sps_length - 1, 64); + buf.copy(rbsp, sps_length - 1, 1); + size_t rbsp_len = 0; + for (size_t i = 1; i < sps_length; ++i) { + // XX 00 00 03 XX, the 03 byte should be dropped. + if (!(i >= 3 && buf[i - 2] == 0 && buf[i - 1] == 0 && buf[i] == 3)) { + rbsp[rbsp_len++] = buf[i]; + } + } + // for SPS, 7.3.2.1.1 Sequence parameter set data syntax + // H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 62. + if (rbsp_len < 3) { + return butil::Status(EINVAL, "rbsp must be at least 3 bytes"); + } + // Decode rbsp. + const char* p = rbsp; + uint8_t profile_idc = *p++; + if (!profile_idc) { + return butil::Status(EINVAL, "profile_idc is 0"); + } + int8_t flags = *p++; + if (flags & 0x03) { + return butil::Status(EINVAL, "Invalid flags=%d", (int)flags); + } + uint8_t level_idc = *p++; + if (!level_idc) { + return butil::Status(EINVAL, "level_idc is 0"); + } + BitStream bs(p, rbsp + rbsp_len - p); + int32_t seq_parameter_set_id = -1; + if (avc_nalu_read_uev(&bs, &seq_parameter_set_id) != 0) { + return butil::Status(EINVAL, "Fail to read seq_parameter_set_id"); + } + if (seq_parameter_set_id < 0) { + return butil::Status(EINVAL, "Invalid seq_parameter_set_id=%d", + (int)seq_parameter_set_id); + } + int32_t chroma_format_idc = -1; + if (profile_idc == 100 || profile_idc == 110 || profile_idc == 122 || + profile_idc == 244 || profile_idc == 44 || profile_idc == 83 || + profile_idc == 86 || profile_idc == 118 || profile_idc == 128) { + if (avc_nalu_read_uev(&bs, &chroma_format_idc) != 0) { + return butil::Status(EINVAL, "Fail to read chroma_format_idc"); + } + if (chroma_format_idc == 3) { + int8_t separate_colour_plane_flag = -1; + if (avc_nalu_read_bit(&bs, &separate_colour_plane_flag) != 0) { + return butil::Status(EINVAL, "Fail to read separate_colour_plane_flag"); + } + } + int32_t bit_depth_luma_minus8 = -1; + if (avc_nalu_read_uev(&bs, &bit_depth_luma_minus8) != 0) { + return butil::Status(EINVAL, "Fail to read bit_depth_luma_minus8"); + } + int32_t bit_depth_chroma_minus8 = -1; + if (avc_nalu_read_uev(&bs, &bit_depth_chroma_minus8) != 0) { + return butil::Status(EINVAL, "Fail to read bit_depth_chroma_minus8"); + } + int8_t qpprime_y_zero_transform_bypass_flag = -1; + if (avc_nalu_read_bit(&bs, &qpprime_y_zero_transform_bypass_flag) != 0) { + return butil::Status(EINVAL, "Fail to read qpprime_y_zero_transform_bypass_flag"); + } + int8_t seq_scaling_matrix_present_flag = -1; + if (avc_nalu_read_bit(&bs, &seq_scaling_matrix_present_flag) != 0) { + return butil::Status(EINVAL, "Fail to read seq_scaling_matrix_present_flag"); + } + if (seq_scaling_matrix_present_flag) { + int nb_scmpfs = (chroma_format_idc != 3 ? 8 : 12); + for (int i = 0; i < nb_scmpfs; i++) { + int8_t seq_scaling_matrix_present_flag_i = -1; + if (avc_nalu_read_bit(&bs, &seq_scaling_matrix_present_flag_i)) { + return butil::Status(EINVAL, "Fail to read seq_scaling_" + "matrix_present_flag[%d]", i); + } + if (seq_scaling_matrix_present_flag_i) { + return butil::Status(EINVAL, "Invalid seq_scaling_matrix_" + "present_flag[%d]=%d nb_scmpfs=%d", + i, (int)seq_scaling_matrix_present_flag_i, + nb_scmpfs); + } + } + } + } + int32_t log2_max_frame_num_minus4 = -1; + if (avc_nalu_read_uev(&bs, &log2_max_frame_num_minus4) != 0) { + return butil::Status(EINVAL, "Fail to read log2_max_frame_num_minus4"); + } + int32_t pic_order_cnt_type = -1; + if (avc_nalu_read_uev(&bs, &pic_order_cnt_type) != 0) { + return butil::Status(EINVAL, "Fail to read pic_order_cnt_type"); + } + if (pic_order_cnt_type == 0) { + int32_t log2_max_pic_order_cnt_lsb_minus4 = -1; + if (avc_nalu_read_uev(&bs, &log2_max_pic_order_cnt_lsb_minus4) != 0) { + return butil::Status(EINVAL, "Fail to read log2_max_pic_order_cnt_lsb_minus4"); + } + } else if (pic_order_cnt_type == 1) { + int8_t delta_pic_order_always_zero_flag = -1; + if (avc_nalu_read_bit(&bs, &delta_pic_order_always_zero_flag) != 0) { + return butil::Status(EINVAL, "Fail to read delta_pic_order_always_zero_flag"); + } + int32_t offset_for_non_ref_pic = -1; + if (avc_nalu_read_uev(&bs, &offset_for_non_ref_pic) != 0) { + return butil::Status(EINVAL, "Fail to read offset_for_non_ref_pic"); + } + int32_t offset_for_top_to_bottom_field = -1; + if (avc_nalu_read_uev(&bs, &offset_for_top_to_bottom_field) != 0) { + return butil::Status(EINVAL, "Fail to read offset_for_top_to_bottom_field"); + } + int32_t num_ref_frames_in_pic_order_cnt_cycle = -1; + if (avc_nalu_read_uev(&bs, &num_ref_frames_in_pic_order_cnt_cycle) != 0) { + return butil::Status(EINVAL, "Fail to read num_ref_frames_in_pic_order_cnt_cycle"); + } + if (num_ref_frames_in_pic_order_cnt_cycle) { + return butil::Status(EINVAL, "Invalid num_ref_frames_in_pic_order_cnt_cycle=%d", + num_ref_frames_in_pic_order_cnt_cycle); + } + } + int32_t max_num_ref_frames = -1; + if (avc_nalu_read_uev(&bs, &max_num_ref_frames) != 0) { + return butil::Status(EINVAL, "Fail to read max_num_ref_frames"); + } + int8_t gaps_in_frame_num_value_allowed_flag = -1; + if (avc_nalu_read_bit(&bs, &gaps_in_frame_num_value_allowed_flag) != 0) { + return butil::Status(EINVAL, "Fail to read gaps_in_frame_num_value_allowed_flag"); + } + int32_t pic_width_in_mbs_minus1 = -1; + if (avc_nalu_read_uev(&bs, &pic_width_in_mbs_minus1) != 0) { + return butil::Status(EINVAL, "Fail to read pic_width_in_mbs_minus1"); + } + int32_t pic_height_in_map_units_minus1 = -1; + if (avc_nalu_read_uev(&bs, &pic_height_in_map_units_minus1) != 0) { + return butil::Status(EINVAL, "Fail to read pic_height_in_map_units_minus1"); + } + width = (int)(pic_width_in_mbs_minus1 + 1) * 16; + height = (int)(pic_height_in_map_units_minus1 + 1) * 16; + return butil::Status::OK(); +} + +static bool find_avc_annexb_nalu_start_code(const butil::IOBuf& buf, + size_t* start_code_length) { + size_t consecutive_zero_count = 0; + for (butil::IOBufBytesIterator it(buf); it != NULL; ++it) { + char c = *it; + if (c == 0) { + ++consecutive_zero_count; + } else if (c == 1) { + if (consecutive_zero_count >= 2) { + if (start_code_length) { + *start_code_length = consecutive_zero_count + 1; + } + return true; + } + return false; + } else { + return false; + } + } + return false; +} + +static void find_avc_annexb_nalu_stop_code(const butil::IOBuf& buf, + size_t* nalu_length_out, + size_t* stop_code_length) { + size_t nalu_length = 0; + size_t consecutive_zero_count = 0; + for (butil::IOBufBytesIterator it(buf); it != NULL; ++it) { + unsigned char c = (unsigned char)*it; + if (c > 1) { // most frequent + ++nalu_length; + consecutive_zero_count = 0; + continue; + } + if (c == 0) { + ++consecutive_zero_count; + } else { // c == 1 + if (consecutive_zero_count >= 2) { + if (nalu_length_out) { + *nalu_length_out = nalu_length; + } + if (stop_code_length) { + *stop_code_length = consecutive_zero_count + 1; + } + return; + } + ++nalu_length; + consecutive_zero_count = 0; + } + } + if (nalu_length_out) { + *nalu_length_out = nalu_length + consecutive_zero_count; + } + if (stop_code_length) { + *stop_code_length = 0; + } +} + +AVCNaluIterator::AVCNaluIterator(butil::IOBuf* data, uint32_t length_size_minus1, + AVCNaluFormat* format) + : _data(data) + , _format(format) + , _length_size_minus1(length_size_minus1) + , _nalu_type(AVC_NALU_EMPTY) { + if (_data) { + ++*this; + } +} + +AVCNaluIterator::~AVCNaluIterator() { +} + +void AVCNaluIterator::operator++() { + if (*_format == AVC_NALU_FORMAT_ANNEXB) { + if (!next_as_annexb()) { + return set_end(); + } + } else if (*_format == AVC_NALU_FORMAT_IBMF) { + if (!next_as_ibmf()) { + return set_end(); + } + } else { + size_t start_code_length = 0; + if (find_avc_annexb_nalu_start_code(*_data, &start_code_length) && + _data->size() > start_code_length) { + if (start_code_length > 0) { + _data->pop_front(start_code_length); + } + *_format = AVC_NALU_FORMAT_ANNEXB; + if (!next_as_annexb()) { + return set_end(); + } + } else if (next_as_ibmf()) { + *_format = AVC_NALU_FORMAT_IBMF; + } else { + set_end(); + } + } +} + +bool AVCNaluIterator::next_as_annexb() { + if (_data->empty()) { + return false; + } + size_t nalu_length = 0; + size_t stop_code_length = 0; + find_avc_annexb_nalu_stop_code(*_data, &nalu_length, &stop_code_length); + _cur_nalu.clear(); + _nalu_type = AVC_NALU_EMPTY; + if (nalu_length) { + _data->cutn(&_cur_nalu, nalu_length); + const uint8_t byte0 = *(const uint8_t*)_cur_nalu.fetch1(); + _nalu_type = (AVCNaluType)(byte0 & 0x1f); + } + if (stop_code_length) { + _data->pop_front(stop_code_length); + } + return true; +} + +bool AVCNaluIterator::next_as_ibmf() { + // The value of this field shall be one of 0, 1, or 3 corresponding to a + // length encoded with 1, 2, or 4 bytes, respectively. + CHECK_NE(_length_size_minus1, 2u); + + if (_data->empty()) { + return false; + } + if (_data->size() < _length_size_minus1 + 1) { + LOG(ERROR) << "Not enough data to decode length of NALU"; + return false; + } + int32_t nalu_length = 0; + char buf[4]; + if (_length_size_minus1 == 3) { + _data->copy_to(buf, 4); + nalu_length = policy::ReadBigEndian4Bytes(buf); + } else if (_length_size_minus1 == 1) { + _data->copy_to(buf, 2); + nalu_length = policy::ReadBigEndian2Bytes(buf); + } else { + _data->copy_to(buf, 1); + nalu_length = *buf; + } + // maybe stream is invalid format. + // see: https://github.com/ossrs/srs/issues/183 + if (nalu_length < 0) { + LOG(ERROR) << "Invalid nalu_length=" << nalu_length; + return false; + } + if (_data->size() < _length_size_minus1 + 1 + nalu_length) { + LOG(ERROR) << "Not enough data to decode NALU"; + return false; + } + _data->pop_front(_length_size_minus1 + 1); + _cur_nalu.clear(); + _nalu_type = AVC_NALU_EMPTY; + if (nalu_length) { + _data->cutn(&_cur_nalu, nalu_length); + const uint8_t byte0 = *(const uint8_t*)_cur_nalu.fetch1(); + _nalu_type = (AVCNaluType)(byte0 & 0x1f); + } + return true; +} + +RtmpClientOptions::RtmpClientOptions() + : fpad(false) + , audioCodecs((RtmpAudioCodec)3575) // Copy from SRS + , videoCodecs((RtmpVideoCodec)252) // Copy from SRS + , videoFunction(RTMP_VIDEO_FUNCTION_CLIENT_SEEK) + , timeout_ms(1000) + , connect_timeout_ms(500) + , buffer_length_ms(1000) + , chunk_size(policy::RTMP_DEFAULT_CHUNK_SIZE) + , window_ack_size(policy::RTMP_DEFAULT_WINDOW_ACK_SIZE) + , simplified_rtmp(false) { +} + +// Shared by RtmpClient and RtmpClientStream(s) +class RtmpClientImpl : public SharedObject { +friend class RtmpClientStream; +public: + RtmpClientImpl() { + get_rtmp_bvars()->client_count << 1; + } + ~RtmpClientImpl() { + get_rtmp_bvars()->client_count << -1; + RPC_VLOG << "Destroying RtmpClientImpl=" << this; + } + + // Specify the servers to connect. + int Init(butil::EndPoint server_addr_and_port, + const RtmpClientOptions& options); + int Init(const char* server_addr_and_port, + const RtmpClientOptions& options); + int Init(const char* server_addr, int port, + const RtmpClientOptions& options); + int Init(const char* naming_service_url, + const char* load_balancer_name, + const RtmpClientOptions& options); + + const RtmpClientOptions& options() const { return _connect_options; } + SocketMap& socket_map() { return _socket_map; } + + int CreateSocket(const butil::EndPoint& pt, SocketId* id); + +private: + DISALLOW_COPY_AND_ASSIGN(RtmpClientImpl); + int CommonInit(const RtmpClientOptions& options); + + Channel _chan; + RtmpClientOptions _connect_options; + SocketMap _socket_map; +}; + +class RtmpConnect : public AppConnect { +public: + // @AppConnect + void StartConnect(const Socket* s, void (*done)(int, void*), void* data) override; + void StopConnect(Socket* s) override; +}; + +void RtmpConnect::StartConnect( + const Socket* s, void (*done)(int, void*), void* data) { + RPC_VLOG << "Establish rtmp-level connection on " << *s; + policy::RtmpContext* ctx = + static_cast(s->parsing_context()); + if (ctx == NULL) { + LOG(FATAL) << "RtmpContext of " << *s << " is NULL"; + return done(EINVAL, data); + } + + const RtmpClientOptions* _client_options = ctx->client_options(); + if (_client_options && _client_options->simplified_rtmp) { + ctx->set_simplified_rtmp(true); + if (ctx->SendConnectRequest(s->remote_side(), s->fd(), true) != 0) { + LOG(ERROR) << s->remote_side() << ": Fail to send simple connect"; + return done(EINVAL, data); + } + ctx->SetState(s->remote_side(), policy::RtmpContext::STATE_RECEIVED_S2); + ctx->set_create_stream_with_play_or_publish(true); + return done(0, data); + } + + // Save to callback to call when RTMP connect is done. + ctx->SetConnectCallback(done, data); + + // Initiate the rtmp handshake. + bool is_simple_handshake = false; + if (policy::SendC0C1(s->fd(), &is_simple_handshake) != 0) { + LOG(ERROR) << s->remote_side() << ": Fail to send C0 C1"; + return done(EINVAL, data); + } + if (is_simple_handshake) { + ctx->only_check_simple_s0s1(); + } +} + +void RtmpConnect::StopConnect(Socket* s) { + policy::RtmpContext* ctx = + static_cast(s->parsing_context()); + if (ctx == NULL) { + LOG(FATAL) << "RtmpContext of " << *s << " is NULL"; + } else { + ctx->OnConnected(EFAILEDSOCKET); + } +} + +class RtmpSocketCreator : public SocketCreator { +public: + RtmpSocketCreator(const RtmpClientOptions& connect_options) + : _connect_options(connect_options) { + } + + int CreateSocket(const SocketOptions& opt, SocketId* id) override { + SocketOptions sock_opt = opt; + sock_opt.app_connect = std::make_shared(); + sock_opt.initial_parsing_context = new policy::RtmpContext(&_connect_options, NULL); + return get_client_side_messenger()->Create(sock_opt, id); + } + +private: + RtmpClientOptions _connect_options; +}; + +int RtmpClientImpl::CreateSocket(const butil::EndPoint& pt, SocketId* id) { + SocketOptions sock_opt; + sock_opt.remote_side = pt; + sock_opt.app_connect = std::make_shared(); + sock_opt.initial_parsing_context = new policy::RtmpContext(&_connect_options, NULL); + return get_client_side_messenger()->Create(sock_opt, id); +} + +int RtmpClientImpl::CommonInit(const RtmpClientOptions& options) { + _connect_options = options; + SocketMapOptions sm_options; + sm_options.socket_creator = new RtmpSocketCreator(_connect_options); + if (_socket_map.Init(sm_options) != 0) { + LOG(ERROR) << "Fail to init _socket_map"; + return -1; + } + return 0; +} + +int RtmpClientImpl::Init(butil::EndPoint server_addr_and_port, + const RtmpClientOptions& options) { + if (CommonInit(options) != 0) { + return -1; + } + ChannelOptions copts; + copts.connect_timeout_ms = options.connect_timeout_ms; + copts.timeout_ms = options.timeout_ms; + copts.protocol = PROTOCOL_RTMP; + return _chan.Init(server_addr_and_port, &copts); +} +int RtmpClientImpl::Init(const char* server_addr_and_port, + const RtmpClientOptions& options) { + if (CommonInit(options) != 0) { + return -1; + } + ChannelOptions copts; + copts.connect_timeout_ms = options.connect_timeout_ms; + copts.timeout_ms = options.timeout_ms; + copts.protocol = PROTOCOL_RTMP; + return _chan.Init(server_addr_and_port, &copts); +} +int RtmpClientImpl::Init(const char* server_addr, int port, + const RtmpClientOptions& options) { + if (CommonInit(options) != 0) { + return -1; + } + ChannelOptions copts; + copts.connect_timeout_ms = options.connect_timeout_ms; + copts.timeout_ms = options.timeout_ms; + copts.protocol = PROTOCOL_RTMP; + return _chan.Init(server_addr, port, &copts); +} +int RtmpClientImpl::Init(const char* naming_service_url, + const char* load_balancer_name, + const RtmpClientOptions& options) { + if (CommonInit(options) != 0) { + return -1; + } + ChannelOptions copts; + copts.connect_timeout_ms = options.connect_timeout_ms; + copts.timeout_ms = options.timeout_ms; + copts.protocol = PROTOCOL_RTMP; + return _chan.Init(naming_service_url, load_balancer_name, &copts); +} + +RtmpClient::RtmpClient() {} +RtmpClient::~RtmpClient() {} +RtmpClient::RtmpClient(const RtmpClient& rhs) : _impl(rhs._impl) {} + +RtmpClient& RtmpClient::operator=(const RtmpClient& rhs) { + _impl = rhs._impl; + return *this; +} + +const RtmpClientOptions& RtmpClient::options() const { + if (_impl) { + return _impl->options(); + } else { + static RtmpClientOptions dft_opt; + return dft_opt; + } +} + +int RtmpClient::Init(butil::EndPoint server_addr_and_port, + const RtmpClientOptions& options) { + butil::intrusive_ptr tmp(new (std::nothrow) RtmpClientImpl); + if (tmp == NULL) { + LOG(FATAL) << "Fail to new RtmpClientImpl"; + return -1; + } + if (tmp->Init(server_addr_and_port, options) != 0) { + return -1; + } + tmp.swap(_impl); + return 0; +} + +int RtmpClient::Init(const char* server_addr_and_port, + const RtmpClientOptions& options) { + butil::intrusive_ptr tmp(new (std::nothrow) RtmpClientImpl); + if (tmp == NULL) { + LOG(FATAL) << "Fail to new RtmpClientImpl"; + return -1; + } + if (tmp->Init(server_addr_and_port, options) != 0) { + return -1; + } + tmp.swap(_impl); + return 0; +} + +int RtmpClient::Init(const char* server_addr, int port, + const RtmpClientOptions& options) { + butil::intrusive_ptr tmp(new (std::nothrow) RtmpClientImpl); + if (tmp == NULL) { + LOG(FATAL) << "Fail to new RtmpClientImpl"; + return -1; + } + if (tmp->Init(server_addr, port, options) != 0) { + return -1; + } + tmp.swap(_impl); + return 0; +} + +int RtmpClient::Init(const char* naming_service_url, + const char* load_balancer_name, + const RtmpClientOptions& options) { + butil::intrusive_ptr tmp(new (std::nothrow) RtmpClientImpl); + if (tmp == NULL) { + LOG(FATAL) << "Fail to new RtmpClientImpl"; + return -1; + } + if (tmp->Init(naming_service_url, load_balancer_name, options) != 0) { + return -1; + } + tmp.swap(_impl); + return 0; +} + +bool RtmpClient::initialized() const { return _impl != NULL; } + +RtmpStreamBase::RtmpStreamBase(bool is_client) + : _is_client(is_client) + , _paused(false) + , _stopped(false) + , _processing_msg(false) + , _has_data_ever(false) + , _message_stream_id(0) + , _chunk_stream_id(0) + , _create_realtime_us(butil::gettimeofday_us()) + , _is_server_accepted(false) { +} + +RtmpStreamBase::~RtmpStreamBase() { +} + +void RtmpStreamBase::Destroy() { + return; +} + +int RtmpStreamBase::SendMessage(uint32_t timestamp, + uint8_t message_type, + const butil::IOBuf& body) { + if (_rtmpsock == NULL) { + errno = EPERM; + return -1; + } + if (_chunk_stream_id == 0) { + LOG(ERROR) << "SendXXXMessage can't be called before play() is received"; + errno = EPERM; + return -1; + } + SocketMessagePtr msg(new policy::RtmpUnsentMessage); + msg->header.timestamp = timestamp; + msg->header.message_length = body.size(); + msg->header.message_type = message_type; + msg->header.stream_id = _message_stream_id; + msg->chunk_stream_id = _chunk_stream_id; + msg->body = body; + return _rtmpsock->Write(msg); +} + +int RtmpStreamBase::SendControlMessage( + uint8_t message_type, const void* body, size_t size) { + if (_rtmpsock == NULL) { + errno = EPERM; + return -1; + } + SocketMessagePtr msg( + policy::MakeUnsentControlMessage(message_type, body, size)); + return _rtmpsock->Write(msg); +} + +int RtmpStreamBase::SendCuePoint(const RtmpCuePoint& cuepoint) { + butil::IOBuf req_buf; + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + WriteAMFString(RTMP_AMF0_SET_DATAFRAME, &ostream); + WriteAMFString(RTMP_AMF0_ON_CUE_POINT, &ostream); + WriteAMFObject(cuepoint.data, &ostream); + if (!ostream.good()) { + LOG(ERROR) << "Fail to serialize cuepoint"; + return -1; + } + } + return SendMessage(cuepoint.timestamp, policy::RTMP_MESSAGE_DATA_AMF0, req_buf); +} + +int RtmpStreamBase::SendMetaData(const RtmpMetaData& metadata, + const butil::StringPiece& name) { + butil::IOBuf req_buf; + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + WriteAMFString(name, &ostream); + WriteAMFObject(metadata.data, &ostream); + if (!ostream.good()) { + LOG(ERROR) << "Fail to serialize metadata"; + return -1; + } + } + return SendMessage(metadata.timestamp, policy::RTMP_MESSAGE_DATA_AMF0, req_buf); +} + +int RtmpStreamBase::SendSharedObjectMessage(const RtmpSharedObjectMessage&) { + CHECK(false) << "Not supported yet"; + return -1; +} + +int RtmpStreamBase::SendAudioMessage(const RtmpAudioMessage& msg) { + if (_rtmpsock == NULL) { + errno = EPERM; + return -1; + } + if (_chunk_stream_id == 0) { + LOG(ERROR) << __FUNCTION__ << " can't be called before play() is received"; + errno = EPERM; + return -1; + } + if (_paused) { + errno = EPERM; + return -1; + } + SocketMessagePtr msg2(new policy::RtmpUnsentMessage); + msg2->header.timestamp = msg.timestamp; + msg2->header.message_length = msg.size(); + msg2->header.message_type = policy::RTMP_MESSAGE_AUDIO; + msg2->header.stream_id = _message_stream_id; + msg2->chunk_stream_id = _chunk_stream_id; + // Make audio header. + const char audio_head = + ((msg.codec & 0xF) << 4) + | ((msg.rate & 0x3) << 2) + | ((msg.bits & 0x1) << 1) + | (msg.type & 0x1); + msg2->body.push_back(audio_head); + msg2->body.append(msg.data); + return _rtmpsock->Write(msg2); +} + +int RtmpStreamBase::SendAACMessage(const RtmpAACMessage& msg) { + if (_rtmpsock == NULL) { + errno = EPERM; + return -1; + } + if (_chunk_stream_id == 0) { + LOG(ERROR) << __FUNCTION__ << " can't be called before play() is received"; + errno = EPERM; + return -1; + } + if (_paused) { + errno = EPERM; + return -1; + } + SocketMessagePtr msg2(new policy::RtmpUnsentMessage); + msg2->header.timestamp = msg.timestamp; + msg2->header.message_length = msg.size(); + msg2->header.message_type = policy::RTMP_MESSAGE_AUDIO; + msg2->header.stream_id = _message_stream_id; + msg2->chunk_stream_id = _chunk_stream_id; + // Make audio header. + char aac_head[2]; + aac_head[0] = ((FLV_AUDIO_AAC & 0xF) << 4) + | ((msg.rate & 0x3) << 2) + | ((msg.bits & 0x1) << 1) + | (msg.type & 0x1); + aac_head[1] = (FlvAACPacketType)msg.packet_type; + msg2->body.append(aac_head, sizeof(aac_head)); + msg2->body.append(msg.data); + return _rtmpsock->Write(msg2); +} + +int RtmpStreamBase::SendUserMessage(void*) { + CHECK(false) << "You should implement your own SendUserMessage"; + return 0; +} + +int RtmpStreamBase::SendVideoMessage(const RtmpVideoMessage& msg) { + if (_rtmpsock == NULL) { + errno = EPERM; + return -1; + } + if (_chunk_stream_id == 0) { + LOG(ERROR) << __FUNCTION__ << " can't be called before play() is received"; + errno = EPERM; + return -1; + } + if (!policy::is_video_frame_type_valid(msg.frame_type)) { + LOG(WARNING) << "Invalid frame_type=" << (int)msg.frame_type; + } + if (!policy::is_video_codec_valid(msg.codec)) { + LOG(WARNING) << "Invalid codec=" << (int)msg.codec; + } + if (_paused) { + errno = EPERM; + return -1; + } + SocketMessagePtr msg2(new policy::RtmpUnsentMessage); + msg2->header.timestamp = msg.timestamp; + msg2->header.message_length = msg.size(); + msg2->header.message_type = policy::RTMP_MESSAGE_VIDEO; + msg2->header.stream_id = _message_stream_id; + msg2->chunk_stream_id = _chunk_stream_id; + // Make video header + const char video_head = ((msg.frame_type & 0xF) << 4) | (msg.codec & 0xF); + msg2->body.push_back(video_head); + msg2->body.append(msg.data); + return _rtmpsock->Write(msg2); +} + +int RtmpStreamBase::SendAVCMessage(const RtmpAVCMessage& msg) { + if (_rtmpsock == NULL) { + errno = EPERM; + return -1; + } + if (_chunk_stream_id == 0) { + LOG(ERROR) << __FUNCTION__ << " can't be called before play() is received"; + errno = EPERM; + return -1; + } + if (!policy::is_video_frame_type_valid(msg.frame_type)) { + LOG(WARNING) << "Invalid frame_type=" << (int)msg.frame_type; + } + if (_paused) { + errno = EPERM; + return -1; + } + SocketMessagePtr msg2(new policy::RtmpUnsentMessage); + msg2->header.timestamp = msg.timestamp; + msg2->header.message_length = msg.size(); + msg2->header.message_type = policy::RTMP_MESSAGE_VIDEO; + msg2->header.stream_id = _message_stream_id; + msg2->chunk_stream_id = _chunk_stream_id; + // Make video header + char avc_head[5]; + char* p = avc_head; + *p++ = ((msg.frame_type & 0xF) << 4) | (FLV_VIDEO_AVC & 0xF); + *p++ = (FlvAVCPacketType)msg.packet_type; + policy::WriteBigEndian3Bytes(&p, msg.composition_time); + msg2->body.append(avc_head, sizeof(avc_head)); + msg2->body.append(msg.data); + return _rtmpsock->Write(msg2); +} + +int RtmpStreamBase::SendStopMessage(const butil::StringPiece&) { + return -1; +} + +const char* RtmpObjectEncoding2Str(RtmpObjectEncoding e) { + switch (e) { + case RTMP_AMF0: return "AMF0"; + case RTMP_AMF3: return "AMF3"; + } + return "Unknown RtmpObjectEncoding"; +} + +void RtmpStreamBase::SignalError() { + return; +} + +void RtmpStreamBase::OnFirstMessage() {} + +void RtmpStreamBase::OnUserData(void*) { + LOG(INFO) << remote_side() << '[' << stream_id() + << "] ignored UserData{}"; +} + +void RtmpStreamBase::OnCuePoint(RtmpCuePoint* cuepoint) { + LOG(INFO) << remote_side() << '[' << stream_id() + << "] ignored CuePoint{" << cuepoint->data << '}'; +} + +void RtmpStreamBase::OnMetaData(RtmpMetaData* metadata, const butil::StringPiece& name) { + LOG(INFO) << remote_side() << '[' << stream_id() + << "] ignored MetaData{" << metadata->data << '}' + << " name{" << name << '}'; +} + +void RtmpStreamBase::OnSharedObjectMessage(RtmpSharedObjectMessage*) { + LOG(ERROR) << remote_side() << '[' << stream_id() + << "] ignored SharedObjectMessage{}"; +} + +void RtmpStreamBase::OnAudioMessage(RtmpAudioMessage* msg) { + LOG(ERROR) << remote_side() << '[' << stream_id() << "] ignored " << *msg; +} + +void RtmpStreamBase::OnVideoMessage(RtmpVideoMessage* msg) { + LOG(ERROR) << remote_side() << '[' << stream_id() << "] ignored " << *msg; +} + +void RtmpStreamBase::OnStop() { + // do nothing by default +} + +bool RtmpStreamBase::BeginProcessingMessage(const char* fun_name) { + std::unique_lock mu(_call_mutex); + if (_stopped) { + mu.unlock(); + LOG(ERROR) << fun_name << " is called after OnStop()"; + return false; + } + if (_processing_msg) { + mu.unlock(); + LOG(ERROR) << "Impossible: Another OnXXXMessage is being called!"; + return false; + } + _processing_msg = true; + if (!_has_data_ever) { + _has_data_ever = true; + OnFirstMessage(); + } + return true; +} + +void RtmpStreamBase::EndProcessingMessage() { + std::unique_lock mu(_call_mutex); + _processing_msg = false; + if (_stopped) { + mu.unlock(); + return OnStop(); + } +} + +void RtmpStreamBase::CallOnUserData(void* data) { + if (BeginProcessingMessage("OnUserData()")) { + OnUserData(data); + EndProcessingMessage(); + } +} + +void RtmpStreamBase::CallOnCuePoint(RtmpCuePoint* obj) { + if (BeginProcessingMessage("OnCuePoint()")) { + OnCuePoint(obj); + EndProcessingMessage(); + } +} + +void RtmpStreamBase::CallOnMetaData(RtmpMetaData* obj, const butil::StringPiece& name) { + if (BeginProcessingMessage("OnMetaData()")) { + OnMetaData(obj, name); + EndProcessingMessage(); + } +} + +void RtmpStreamBase::CallOnSharedObjectMessage(RtmpSharedObjectMessage* msg) { + if (BeginProcessingMessage("OnSharedObjectMessage()")) { + OnSharedObjectMessage(msg); + EndProcessingMessage(); + } +} + +void RtmpStreamBase::CallOnAudioMessage(RtmpAudioMessage* msg) { + if (BeginProcessingMessage("OnAudioMessage()")) { + OnAudioMessage(msg); + EndProcessingMessage(); + } +} + +void RtmpStreamBase::CallOnVideoMessage(RtmpVideoMessage* msg) { + if (BeginProcessingMessage("OnVideoMessage()")) { + OnVideoMessage(msg); + EndProcessingMessage(); + } +} + +void RtmpStreamBase::CallOnStop() { + { + std::unique_lock mu(_call_mutex); + if (_stopped) { + mu.unlock(); + LOG(ERROR) << "OnStop() was called more than once"; + return; + } + _stopped = true; + if (_processing_msg) { + // EndProcessingMessage() will call OnStop(); + return; + } + } + OnStop(); +} + +butil::EndPoint RtmpStreamBase::remote_side() const +{ return _rtmpsock ? _rtmpsock->remote_side() : butil::EndPoint(); } + +butil::EndPoint RtmpStreamBase::local_side() const +{ return _rtmpsock ? _rtmpsock->local_side() : butil::EndPoint(); } + +// ============ RtmpClientStream ============= + +RtmpClientStream::RtmpClientStream() + : RtmpStreamBase(true) + , _onfail_id(INVALID_BTHREAD_ID) + , _create_stream_rpc_id(INVALID_BTHREAD_ID) + , _from_socketmap(true) + , _created_stream_with_play_or_publish(false) + , _state(STATE_UNINITIALIZED) { + get_rtmp_bvars()->client_stream_count << 1; + _self_ref.reset(this); +} + +RtmpClientStream::~RtmpClientStream() { + get_rtmp_bvars()->client_stream_count << -1; +} + +void RtmpClientStream::Destroy() { + bthread_id_t onfail_id = INVALID_BTHREAD_ID; + CallId create_stream_rpc_id = INVALID_BTHREAD_ID; + butil::intrusive_ptr self_ref; + + std::unique_lock mu(_state_mutex); + switch (_state) { + case STATE_UNINITIALIZED: + _state = STATE_DESTROYING; + mu.unlock(); + OnStopInternal(); + _self_ref.swap(self_ref); + return; + case STATE_CREATING: + _state = STATE_DESTROYING; + create_stream_rpc_id = _create_stream_rpc_id; + mu.unlock(); + _self_ref.swap(self_ref); + StartCancel(create_stream_rpc_id); + return; + case STATE_CREATED: + _state = STATE_DESTROYING; + onfail_id = _onfail_id; + mu.unlock(); + _self_ref.swap(self_ref); + bthread_id_error(onfail_id, 0); + return; + case STATE_ERROR: + _state = STATE_DESTROYING; + mu.unlock(); + _self_ref.swap(self_ref); + return; + case STATE_DESTROYING: + // Destroy() was already called. + return; + } +} + +void RtmpClientStream::SignalError() { + bthread_id_t onfail_id = INVALID_BTHREAD_ID; + std::unique_lock mu(_state_mutex); + switch (_state) { + case STATE_UNINITIALIZED: + _state = STATE_ERROR; + mu.unlock(); + OnStopInternal(); + return; + case STATE_CREATING: + _state = STATE_ERROR; + mu.unlock(); + return; + case STATE_CREATED: + _state = STATE_ERROR; + onfail_id = _onfail_id; + mu.unlock(); + bthread_id_error(onfail_id, 0); + return; + case STATE_ERROR: + case STATE_DESTROYING: + // SignalError() or Destroy() was already called. + return; + } +} + +StreamUserData* RtmpClientStream::OnCreatingStream( + SocketUniquePtr* inout, Controller* cntl) { + { + std::unique_lock mu(_state_mutex); + if (_state == STATE_ERROR || _state == STATE_DESTROYING) { + cntl->SetFailed(EINVAL, "Fail to replace socket for stream, _state is error or destroying"); + return NULL; + } + } + SocketId esid; + if (cntl->connection_type() == CONNECTION_TYPE_SHORT) { + if (_client_impl->CreateSocket((*inout)->remote_side(), &esid) != 0) { + cntl->SetFailed(EINVAL, "Fail to create RTMP socket"); + return NULL; + } + } else { + if (_client_impl->socket_map().Insert( + SocketMapKey((*inout)->remote_side()), &esid) != 0) { + cntl->SetFailed(EINVAL, "Fail to get the RTMP socket"); + return NULL; + } + } + SocketUniquePtr tmp_ptr; + if (Socket::Address(esid, &tmp_ptr) != 0) { + cntl->SetFailed(EFAILEDSOCKET, "Fail to address RTMP SocketId=%" PRIu64 + " from SocketMap of RtmpClient=%p", + esid, _client_impl.get()); + return NULL; + } + RPC_VLOG << "Replace Socket For Stream, RTMP socketId=" << esid + << ", main socketId=" << (*inout)->id(); + tmp_ptr->ShareStats(inout->get()); + inout->reset(tmp_ptr.release()); + return this; +} + +int RtmpClientStream::RunOnFailed(bthread_id_t id, void* data, int) { + butil::intrusive_ptr stream( + static_cast(data), false); + CHECK(stream->_rtmpsock); + // Must happen after NotifyOnFailed which is after all other callsites + // to OnStopInternal(). + stream->OnStopInternal(); + bthread_id_unlock_and_destroy(id); + return 0; +} + +void RtmpClientStream::OnFailedToCreateStream() { + { + std::unique_lock mu(_state_mutex); + switch (_state) { + case STATE_CREATING: + _state = STATE_ERROR; + break; + case STATE_UNINITIALIZED: + case STATE_CREATED: + _state = STATE_ERROR; + mu.unlock(); + CHECK(false) << "Impossible"; + break; + case STATE_ERROR: + case STATE_DESTROYING: + break; + } + } + return OnStopInternal(); +} + +void RtmpClientStream::DestroyStreamUserData(SocketUniquePtr& sending_sock, + Controller* cntl, + int /*error_code*/, + bool end_of_rpc) { + if (!end_of_rpc) { + if (sending_sock) { + if (_from_socketmap) { + _client_impl->socket_map().Remove(SocketMapKey(sending_sock->remote_side()), + sending_sock->id()); + } else { + sending_sock->SetFailed(); // not necessary, already failed. + } + } + } else { + // Always move sending_sock into _rtmpsock at the end of rpc. + // - If the RPC is successful, moving sending_sock prevents it from + // setfailed in Controller after calling this method. + // - If the RPC is failed, OnStopInternal() can clean up the socket_map + // inserted in OnCreatingStream(). + _rtmpsock.swap(sending_sock); + } +} + + +void RtmpClientStream::DestroyStreamCreator(Controller* cntl) { + if (cntl->Failed()) { + if (_rtmpsock != NULL && + // ^ If sending_sock is NULL, the RPC fails before _pack_request + // which calls AddTransaction, in another word, RemoveTransaction + // is not needed. + cntl->ErrorCode() != ERTMPCREATESTREAM) { + // ^ ERTMPCREATESTREAM is triggered by receiving "_error" command, + // RemoveTransaction should already be called. + CHECK_LT(cntl->log_id(), (uint64_t)std::numeric_limits::max()); + const uint32_t transaction_id = cntl->log_id(); + policy::RtmpContext* rtmp_ctx = + static_cast(_rtmpsock->parsing_context()); + if (rtmp_ctx == NULL) { + LOG(FATAL) << "RtmpContext must be created"; + } else { + policy::RtmpTransactionHandler* handler = + rtmp_ctx->RemoveTransaction(transaction_id); + if (handler) { + handler->Cancel(); + } + } + } + return OnFailedToCreateStream(); + } + + int rc = 0; + bthread_id_t onfail_id = INVALID_BTHREAD_ID; + { + std::unique_lock mu(_state_mutex); + switch (_state) { + case STATE_CREATING: + CHECK(_rtmpsock); + rc = bthread_id_create(&onfail_id, this, RunOnFailed); + if (rc) { + cntl->SetFailed(ENOMEM, "Fail to create _onfail_id: %s", berror(rc)); + mu.unlock(); + return OnFailedToCreateStream(); + } + // Add a ref for RunOnFailed. + butil::intrusive_ptr(this).detach(); + _state = STATE_CREATED; + _onfail_id = onfail_id; + break; + case STATE_UNINITIALIZED: + case STATE_CREATED: + _state = STATE_ERROR; + mu.unlock(); + CHECK(false) << "Impossible"; + return OnStopInternal(); + case STATE_ERROR: + case STATE_DESTROYING: + mu.unlock(); + return OnStopInternal(); + } + } + if (onfail_id != INVALID_BTHREAD_ID) { + _rtmpsock->NotifyOnFailed(onfail_id); + } +} + +void RtmpClientStream::OnStopInternal() { + if (_rtmpsock == NULL) { + return CallOnStop(); + } + + if (!_rtmpsock->Failed() && _chunk_stream_id != 0) { + // SRS requires closeStream which is sent over this stream. + butil::IOBuf req_buf1; + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf1); + AMFOutputStream ostream(&zc_stream); + WriteAMFString(RTMP_AMF0_COMMAND_CLOSE_STREAM, &ostream); + WriteAMFUint32(0, &ostream); + WriteAMFNull(&ostream); + CHECK(ostream.good()); + } + SocketMessagePtr msg1(new policy::RtmpUnsentMessage); + msg1->header.message_length = req_buf1.size(); + msg1->header.message_type = policy::RTMP_MESSAGE_COMMAND_AMF0; + msg1->header.stream_id = _message_stream_id; + msg1->chunk_stream_id = _chunk_stream_id; + msg1->body = req_buf1; + + // Send deleteStream over the control stream. + butil::IOBuf req_buf2; + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf2); + AMFOutputStream ostream(&zc_stream); + WriteAMFString(RTMP_AMF0_COMMAND_DELETE_STREAM, &ostream); + WriteAMFUint32(0, &ostream); + WriteAMFNull(&ostream); + WriteAMFUint32(_message_stream_id, &ostream); + CHECK(ostream.good()); + } + policy::RtmpUnsentMessage* msg2 = policy::MakeUnsentControlMessage( + policy::RTMP_MESSAGE_COMMAND_AMF0, req_buf2); + msg1->next.reset(msg2); + + if (policy::WriteWithoutOvercrowded(_rtmpsock.get(), msg1) != 0) { + if (errno != EFAILEDSOCKET) { + PLOG(WARNING) << "Fail to send closeStream/deleteStream to " + << _rtmpsock->remote_side() << "[" + << _message_stream_id << "]"; + // Close the connection to make sure the server-side knows the + // closing event, however this may terminate other streams over + // the connection as well. + _rtmpsock->SetFailed(EFAILEDSOCKET, "Fail to send closeStream/deleteStream"); + } + } + } + policy::RtmpContext* ctx = + static_cast(_rtmpsock->parsing_context()); + if (ctx != NULL) { + if (!ctx->RemoveMessageStream(this)) { + // The stream is not registered yet. Is this normal? + LOG(ERROR) << "Fail to remove stream_id=" << _message_stream_id; + } + } else { + LOG(FATAL) << "RtmpContext of " << *_rtmpsock << " is NULL"; + } + if (_from_socketmap) { + _client_impl->socket_map().Remove(SocketMapKey(_rtmpsock->remote_side()), + _rtmpsock->id()); + } else { + _rtmpsock->ReleaseAdditionalReference(); + } + CallOnStop(); +} + +RtmpPlayOptions::RtmpPlayOptions() + : start(-2) + , duration(-1) + , reset(true) { +} + +int RtmpClientStream::Play(const RtmpPlayOptions& opt) { + if (_rtmpsock == NULL) { + errno = EPERM; + return -1; + } + if (opt.stream_name.empty()) { + LOG(ERROR) << "Empty stream_name"; + errno = EINVAL; + return -1; + } + if (_client_impl == NULL) { + LOG(ERROR) << "The client stream is not created yet"; + errno = EPERM; + return -1; + } + butil::IOBuf req_buf; + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + WriteAMFString(RTMP_AMF0_COMMAND_PLAY, &ostream); + WriteAMFUint32(0, &ostream); + WriteAMFNull(&ostream); + WriteAMFString(opt.stream_name, &ostream); + WriteAMFNumber(opt.start, &ostream); + WriteAMFNumber(opt.duration, &ostream); + WriteAMFBool(opt.reset, &ostream); + CHECK(ostream.good()); + } + SocketMessagePtr msg1(new policy::RtmpUnsentMessage); + msg1->header.message_length = req_buf.size(); + msg1->header.message_type = policy::RTMP_MESSAGE_COMMAND_AMF0; + msg1->header.stream_id = _message_stream_id; + msg1->chunk_stream_id = _chunk_stream_id; + msg1->body = req_buf; + + if (_client_impl->options().buffer_length_ms > 0) { + char data[10]; + char* p = data; + policy::WriteBigEndian2Bytes( + &p, policy::RTMP_USER_CONTROL_EVENT_SET_BUFFER_LENGTH); + policy::WriteBigEndian4Bytes(&p, stream_id()); + policy::WriteBigEndian4Bytes(&p, _client_impl->options().buffer_length_ms); + policy::RtmpUnsentMessage* msg2 = policy::MakeUnsentControlMessage( + policy::RTMP_MESSAGE_USER_CONTROL, data, sizeof(data)); + msg1->next.reset(msg2); + } + // FIXME(gejun): Do we need to SetChunkSize for play? + // if (_client_impl->options().chunk_size > policy::RTMP_INITIAL_CHUNK_SIZE) { + // if (SetChunkSize(_client_impl->options().chunk_size) != 0) { + // return -1; + // } + // } + return _rtmpsock->Write(msg1); +} + +int RtmpClientStream::Play2(const RtmpPlay2Options& opt) { + butil::IOBuf req_buf; + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + WriteAMFString(RTMP_AMF0_COMMAND_PLAY2, &ostream); + WriteAMFUint32(0, &ostream); + WriteAMFNull(&ostream); + WriteAMFObject(opt, &ostream); + if (!ostream.good()) { + LOG(ERROR) << "Fail to serialize play2 request"; + errno = EINVAL; + return -1; + } + } + return SendMessage(0, policy::RTMP_MESSAGE_COMMAND_AMF0, req_buf); +} + +const char* RtmpPublishType2Str(RtmpPublishType type) { + switch (type) { + case RTMP_PUBLISH_RECORD: return "record"; + case RTMP_PUBLISH_APPEND: return "append"; + case RTMP_PUBLISH_LIVE: return "live"; + } + return "Unknown RtmpPublishType"; +} + +bool Str2RtmpPublishType(const butil::StringPiece& str, RtmpPublishType* type) { + if (str == "record") { + *type = RTMP_PUBLISH_RECORD; + return true; + } else if (str == "append") { + *type = RTMP_PUBLISH_APPEND; + return true; + } else if (str == "live") { + *type = RTMP_PUBLISH_LIVE; + return true; + } + return false; +} + +int RtmpClientStream::Publish(const butil::StringPiece& name, + RtmpPublishType type) { + butil::IOBuf req_buf; + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + WriteAMFString(RTMP_AMF0_COMMAND_PUBLISH, &ostream); + WriteAMFUint32(0, &ostream); + WriteAMFNull(&ostream); + WriteAMFString(name, &ostream); + WriteAMFString(RtmpPublishType2Str(type), &ostream); + CHECK(ostream.good()); + } + return SendMessage(0, policy::RTMP_MESSAGE_COMMAND_AMF0, req_buf); +} + +int RtmpClientStream::Seek(double offset_ms) { + butil::IOBuf req_buf; + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + WriteAMFString(RTMP_AMF0_COMMAND_SEEK, &ostream); + WriteAMFUint32(0, &ostream); + WriteAMFNull(&ostream); + WriteAMFNumber(offset_ms, &ostream); + CHECK(ostream.good()); + } + return SendMessage(0, policy::RTMP_MESSAGE_COMMAND_AMF0, req_buf); +} + +int RtmpClientStream::Pause(bool pause_or_unpause, double offset_ms) { + butil::IOBuf req_buf; + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + WriteAMFString(RTMP_AMF0_COMMAND_PAUSE, &ostream); + WriteAMFUint32(0, &ostream); + WriteAMFNull(&ostream); + WriteAMFBool(pause_or_unpause, &ostream); + WriteAMFNumber(offset_ms, &ostream); + CHECK(ostream.good()); + } + return SendMessage(0, policy::RTMP_MESSAGE_COMMAND_AMF0, req_buf); +} + +void RtmpClientStream::OnStatus(const RtmpInfo& info) { + if (info.level() == RTMP_INFO_LEVEL_ERROR) { + LOG(WARNING) << remote_side() << '[' << stream_id() + << "] " << info.code() << ": " << info.description(); + return SignalError(); + } else if (info.level() == RTMP_INFO_LEVEL_STATUS) { + if ((!_options.play_name.empty() && + info.code() == RTMP_STATUS_CODE_PLAY_START) || + (!_options.publish_name.empty() && + info.code() == RTMP_STATUS_CODE_PUBLISH_START)) { + // the memory fence makes sure that if _is_server_accepted is true, + // publish request must be sent (so that SendXXX functions can + // be enabled) + _is_server_accepted.store(true, butil::memory_order_release); + } + } +} + +RtmpClientStreamOptions::RtmpClientStreamOptions() + : share_connection(true) + , wait_until_play_or_publish_is_sent(false) + , create_stream_max_retry(3) + , publish_type(RTMP_PUBLISH_LIVE) { +} + +class OnClientStreamCreated : public google::protobuf::Closure { +public: + void Run(); // @Closure + void CancelBeforeCallMethod() { delete this; } + +public: + Controller cntl; + // Hold a reference of stream to prevent it from destructing during an + // async Create(). + butil::intrusive_ptr stream; +}; + +void OnClientStreamCreated::Run() { + std::unique_ptr delete_self(this); + if (cntl.Failed()) { + LOG(WARNING) << "Fail to create stream=" << stream->rtmp_url() + << ": " << cntl.ErrorText(); + return; + } + if (stream->_created_stream_with_play_or_publish) { + // the server accepted the play/publish command packed in createStream + return; + } + const RtmpClientStreamOptions& options = stream->options(); + bool do_nothing = true; + if (!options.play_name.empty()) { + do_nothing = false; + RtmpPlayOptions play_opt; + play_opt.stream_name = options.play_name; + if (stream->Play(play_opt) != 0) { + LOG(WARNING) << "Fail to play " << options.play_name; + return stream->SignalError(); + } + } + if (!options.publish_name.empty()) { + do_nothing = false; + if (stream->Publish(options.publish_name, options.publish_type) != 0) { + LOG(WARNING) << "Fail to publish " << stream->rtmp_url(); + return stream->SignalError(); + } + } + if (do_nothing) { + LOG(ERROR) << "play_name and publish_name are both empty"; + return stream->SignalError(); + } +} + +void RtmpClientStream::Init(const RtmpClient* client, + const RtmpClientStreamOptions& options) { + if (client->_impl == NULL) { + LOG(FATAL) << "RtmpClient is not initialized"; + return OnStopInternal(); + } + { + std::unique_lock mu(_state_mutex); + if (_state == STATE_DESTROYING || _state == STATE_ERROR) { + // already Destroy()-ed or SignalError()-ed + LOG(WARNING) << "RtmpClientStream=" << this << " was already " + "Destroy()-ed, stop Init()"; + return; + } + } + _client_impl = client->_impl; + _options = options; + OnClientStreamCreated* done = new OnClientStreamCreated; + done->stream.reset(this); + done->cntl.set_stream_creator(this); + done->cntl.set_connection_type(_options.share_connection ? + CONNECTION_TYPE_SINGLE : + CONNECTION_TYPE_SHORT); + _from_socketmap = (done->cntl.connection_type() == CONNECTION_TYPE_SINGLE); + done->cntl.set_max_retry(_options.create_stream_max_retry); + if (_options.hash_code.has_been_set()) { + done->cntl.set_request_code(_options.hash_code); + } + + // Hack: we pass stream as response so that PackRtmpRequest can get + // the stream from controller. + google::protobuf::Message* res = (google::protobuf::Message*)this; + const CallId call_id = done->cntl.call_id(); + { + std::unique_lock mu(_state_mutex); + switch (_state) { + case STATE_UNINITIALIZED: + _state = STATE_CREATING; + _create_stream_rpc_id = call_id; + break; + case STATE_CREATING: + case STATE_CREATED: + mu.unlock(); + LOG(ERROR) << "RtmpClientStream::Init() is called by multiple " + "threads simultaneously"; + return done->CancelBeforeCallMethod(); + case STATE_ERROR: + case STATE_DESTROYING: + mu.unlock(); + return done->CancelBeforeCallMethod(); + } + } + _client_impl->_chan.CallMethod(NULL, &done->cntl, NULL, res, done); + if (options.wait_until_play_or_publish_is_sent) { + Join(call_id); + } +} + +std::string RtmpClientStream::rtmp_url() const { + if (_client_impl == NULL) { + return std::string(); + } + butil::StringPiece tcurl = _client_impl->options().tcUrl; + butil::StringPiece stream_name = _options.stream_name(); + std::string result; + result.reserve(tcurl.size() + 1 + stream_name.size()); + result.append(tcurl.data(), tcurl.size()); + result.push_back('/'); + result.append(stream_name.data(), stream_name.size()); + return result; +} + +// ========= RtmpRetryingClientStream ============ + +RtmpRetryingClientStreamOptions::RtmpRetryingClientStreamOptions() + : retry_interval_ms(1000) + , max_retry_duration_ms(-1) + , fast_retry_count(2) + , quit_when_no_data_ever(true) { +} + +RtmpRetryingClientStream::RtmpRetryingClientStream() + : RtmpStreamBase(true) + , _destroying(false) + , _called_on_stop(false) + , _changed_stream(false) + , _has_timer_ever(false) + , _is_server_accepted_ever(false) + , _num_fast_retries(0) + , _last_creation_time_us(0) + , _last_retry_start_time_us(0) + , _create_timer_id(0) + , _sub_stream_creator(NULL) { + get_rtmp_bvars()->retrying_client_stream_count << 1; + _self_ref.reset(this); +} + +RtmpRetryingClientStream::~RtmpRetryingClientStream() { + delete _sub_stream_creator; + _sub_stream_creator = NULL; + get_rtmp_bvars()->retrying_client_stream_count << -1; +} + +void RtmpRetryingClientStream::CallOnStopIfNeeded() { + // CallOnStop uses locks, we don't need memory fence on _called_on_stop, + // atomic ops is enough. + if (!_called_on_stop.load(butil::memory_order_relaxed) && + !_called_on_stop.exchange(true, butil::memory_order_relaxed)) { + CallOnStop(); + } +} + +void RtmpRetryingClientStream::Destroy() { + if (_destroying.exchange(true, butil::memory_order_relaxed)) { + // Destroy() was already called. + return; + } + + // Make sure _self_ref is released before quiting this function. + // Notice that _self_ref.reset(NULL) is wrong because it may destructs + // this object immediately. + butil::intrusive_ptr self_ref; + _self_ref.swap(self_ref); + + butil::intrusive_ptr old_sub_stream; + { + BAIDU_SCOPED_LOCK(_stream_mutex); + // swap instead of reset(NULL) to make the stream destructed + // outside _stream_mutex. + _using_sub_stream.swap(old_sub_stream); + } + if (old_sub_stream) { + old_sub_stream->Destroy(); + } + + if (_has_timer_ever) { + if (bthread_timer_del(_create_timer_id) == 0) { + // The callback is not run yet. Remove the additional ref added + // before creating the timer. + butil::intrusive_ptr deref(this, false); + } + } + return CallOnStopIfNeeded(); +} + +void RtmpRetryingClientStream::Init( + SubStreamCreator* sub_stream_creator, + const RtmpRetryingClientStreamOptions& options) { + if (sub_stream_creator == NULL) { + LOG(ERROR) << "sub_stream_creator is NULL"; + return CallOnStopIfNeeded(); + } + _sub_stream_creator = sub_stream_creator; + if (_destroying.load(butil::memory_order_relaxed)) { + LOG(WARNING) << "RtmpRetryingClientStream=" << this << " was already " + "Destroy()-ed, stop Init()"; + return; + } + _options = options; + // retrying stream does not support this option. + _options.wait_until_play_or_publish_is_sent = false; + _last_retry_start_time_us = butil::gettimeofday_us(); + Recreate(); +} + +void RetryingClientMessageHandler::OnPlayable() { + _parent->OnPlayable(); +} + +void RetryingClientMessageHandler::OnUserData(void* msg) { + _parent->CallOnUserData(msg); +} + +void RetryingClientMessageHandler::OnCuePoint(brpc::RtmpCuePoint* cuepoint) { + _parent->CallOnCuePoint(cuepoint); +} + +void RetryingClientMessageHandler::OnMetaData(brpc::RtmpMetaData* metadata, const butil::StringPiece& name) { + _parent->CallOnMetaData(metadata, name); +} + +void RetryingClientMessageHandler::OnAudioMessage(brpc::RtmpAudioMessage* msg) { + _parent->CallOnAudioMessage(msg); +} + +void RetryingClientMessageHandler::OnVideoMessage(brpc::RtmpVideoMessage* msg) { + _parent->CallOnVideoMessage(msg); +} + +void RetryingClientMessageHandler::OnSharedObjectMessage(RtmpSharedObjectMessage* msg) { + _parent->CallOnSharedObjectMessage(msg); +} + +void RetryingClientMessageHandler::OnSubStreamStop(RtmpStreamBase* sub_stream) { + _parent->OnSubStreamStop(sub_stream); +} + +RetryingClientMessageHandler::RetryingClientMessageHandler(RtmpRetryingClientStream* parent) + : _parent(parent) {} + +void RtmpRetryingClientStream::Recreate() { + butil::intrusive_ptr sub_stream; + _sub_stream_creator->NewSubStream(new RetryingClientMessageHandler(this), &sub_stream); + butil::intrusive_ptr old_sub_stream; + bool destroying = false; + { + BAIDU_SCOPED_LOCK(_stream_mutex); + // Need to check _destroying to avoid setting the new sub_stream to a + // destroying retrying stream. + // Note: the load of _destroying and the setting of _using_sub_stream + // must be in the same lock, otherwise current bthread may be scheduled + // and Destroy() may be called, making new sub_stream leaked. + destroying = _destroying.load(butil::memory_order_relaxed); + if (!destroying) { + _using_sub_stream.swap(old_sub_stream); + _using_sub_stream = sub_stream; + _changed_stream = true; + } + } + if (old_sub_stream) { + old_sub_stream->Destroy(); + } + if (destroying) { + sub_stream->Destroy(); + return; + } + _last_creation_time_us = butil::gettimeofday_us(); + // If Init() of sub_stream is called before setting _using_sub_stream, + // OnStop() may happen before _using_sub_stream is set and the stopped + // stream is wrongly left in the variable. + + _sub_stream_creator->LaunchSubStream(sub_stream.get(), &_options); +} + +void RtmpRetryingClientStream::OnRecreateTimer(void* arg) { + // Hold the referenced stream. + butil::intrusive_ptr ptr( + static_cast(arg), false/*not add ref*/); + ptr->Recreate(); +} + +void RtmpRetryingClientStream::OnSubStreamStop(RtmpStreamBase* sub_stream) { + // Make sure the sub_stream is destroyed after this function. + DestroyingPtr sub_stream_guard(sub_stream); + + butil::intrusive_ptr removed_sub_stream; + { + BAIDU_SCOPED_LOCK(_stream_mutex); + if (sub_stream == _using_sub_stream) { + _using_sub_stream.swap(removed_sub_stream); + } + } + if (removed_sub_stream == NULL || + _destroying.load(butil::memory_order_relaxed) || + _called_on_stop.load(butil::memory_order_relaxed)) { + return; + } + // Update _is_server_accepted_ever + if (sub_stream->is_server_accepted()) { + _is_server_accepted_ever = true; + } + + if (_options.max_retry_duration_ms == 0) { + return CallOnStopIfNeeded(); + } + // If the sub_stream has data ever, count this retry as the beginning + // of RtmpRetryingClientStreamOptions.max_retry_duration_ms. + if ((!_options.play_name.empty() && sub_stream->has_data_ever()) || + (!_options.publish_name.empty() && sub_stream->is_server_accepted())) { + const int64_t now = butil::gettimeofday_us(); + if (now >= _last_retry_start_time_us + + 3 * _options.retry_interval_ms * 1000L) { + // re-enable fast retries when the interval is long enough. + // `3' is just a randomly-chosen (small) number. + _num_fast_retries = 0; + } + _last_retry_start_time_us = now; + } + // Check max duration. Notice that this branch cannot be moved forward + // above branch which may update _last_retry_start_time_us + if (_options.max_retry_duration_ms > 0 && + butil::gettimeofday_us() > + (_last_retry_start_time_us + _options.max_retry_duration_ms * 1000L)) { + // exceed the duration, stop retrying. + return CallOnStopIfNeeded(); + } + if (_num_fast_retries < _options.fast_retry_count) { + ++_num_fast_retries; + // Retry immediately for several times. Works for scenarios like: + // restarting servers, occasional connection lost etc... + return Recreate(); + } + if (_options.quit_when_no_data_ever && + ((!_options.play_name.empty() && !has_data_ever()) || + (!_options.publish_name.empty() && !_is_server_accepted_ever))) { + // Stop retrying when created playing streams never have data or + // publishing streams were never accepted. It's very likely that + // continuing retrying does not make sense. + return CallOnStopIfNeeded(); + } + const int64_t wait_us = _last_creation_time_us + + _options.retry_interval_ms * 1000L - butil::gettimeofday_us(); + if (wait_us > 0) { + // retry is too frequent, schedule the retry. + // Add a ref for OnRecreateTimer which does deref. + butil::intrusive_ptr(this).detach(); + if (bthread_timer_add(&_create_timer_id, + butil::microseconds_from_now(wait_us), + OnRecreateTimer, this) != 0) { + LOG(ERROR) << "Fail to create timer"; + return CallOnStopIfNeeded(); + } + _has_timer_ever = true; + } else { + Recreate(); + } +} + +int RtmpRetryingClientStream::AcquireStreamToSend( + butil::intrusive_ptr* ptr) { + BAIDU_SCOPED_LOCK(_stream_mutex); + if (!_using_sub_stream) { + errno = EPERM; + return -1; + } + if (!_using_sub_stream->is_server_accepted()) { + // not published yet. + errno = EPERM; + return -1; + } + if (_changed_stream) { + _changed_stream = false; + errno = ERTMPPUBLISHABLE; + return -1; + } + *ptr = _using_sub_stream; + return 0; +} + +int RtmpRetryingClientStream::SendCuePoint(const RtmpCuePoint& obj) { + butil::intrusive_ptr ptr; + if (AcquireStreamToSend(&ptr) != 0) { + return -1; + } + return ptr->SendCuePoint(obj); +} + +int RtmpRetryingClientStream::SendMetaData(const RtmpMetaData& obj, const butil::StringPiece& name) { + butil::intrusive_ptr ptr; + if (AcquireStreamToSend(&ptr) != 0) { + return -1; + } + return ptr->SendMetaData(obj, name); +} + +int RtmpRetryingClientStream::SendSharedObjectMessage( + const RtmpSharedObjectMessage& msg) { + butil::intrusive_ptr ptr; + if (AcquireStreamToSend(&ptr) != 0) { + return -1; + } + return ptr->SendSharedObjectMessage(msg); +} + +int RtmpRetryingClientStream::SendAudioMessage(const RtmpAudioMessage& msg) { + butil::intrusive_ptr ptr; + if (AcquireStreamToSend(&ptr) != 0) { + return -1; + } + return ptr->SendAudioMessage(msg); +} + +int RtmpRetryingClientStream::SendAACMessage(const RtmpAACMessage& msg) { + butil::intrusive_ptr ptr; + if (AcquireStreamToSend(&ptr) != 0) { + return -1; + } + return ptr->SendAACMessage(msg); +} + +int RtmpRetryingClientStream::SendVideoMessage(const RtmpVideoMessage& msg) { + butil::intrusive_ptr ptr; + if (AcquireStreamToSend(&ptr) != 0) { + return -1; + } + return ptr->SendVideoMessage(msg); +} + +int RtmpRetryingClientStream::SendAVCMessage(const RtmpAVCMessage& msg) { + butil::intrusive_ptr ptr; + if (AcquireStreamToSend(&ptr) != 0) { + return -1; + } + return ptr->SendAVCMessage(msg); +} + +void RtmpRetryingClientStream::StopCurrentStream() { + butil::intrusive_ptr sub_stream; + { + BAIDU_SCOPED_LOCK(_stream_mutex); + sub_stream = _using_sub_stream; + } + if (sub_stream) { + sub_stream->SignalError(); + } +} + +void RtmpRetryingClientStream::OnPlayable() {} + +butil::EndPoint RtmpRetryingClientStream::remote_side() const { + { + BAIDU_SCOPED_LOCK(_stream_mutex); + if (_using_sub_stream) { + return _using_sub_stream->remote_side(); + } + } + return butil::EndPoint(); +} + +butil::EndPoint RtmpRetryingClientStream::local_side() const { + { + BAIDU_SCOPED_LOCK(_stream_mutex); + if (_using_sub_stream) { + return _using_sub_stream->local_side(); + } + } + return butil::EndPoint(); +} + +// =========== RtmpService =============== +void RtmpService::OnPingResponse(const butil::EndPoint&, uint32_t) { + // TODO: put into some bvars? +} + +RtmpServerStream::RtmpServerStream() + : RtmpStreamBase(false) + , _client_supports_stream_multiplexing(false) + , _is_publish(false) + , _onfail_id(INVALID_BTHREAD_ID) { + get_rtmp_bvars()->server_stream_count << 1; +} + +RtmpServerStream::~RtmpServerStream() { + get_rtmp_bvars()->server_stream_count << -1; +} + +void RtmpServerStream::Destroy() { + CHECK(false) << "You're not supposed to call Destroy() for server-side streams"; +} + +void RtmpServerStream::OnPlay(const RtmpPlayOptions& opt, + butil::Status* status, + google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + status->set_error(EPERM, "%s[%u] ignored play{stream_name=%s start=%f" + " duration=%f reset=%d}", + butil::endpoint2str(remote_side()).c_str(), stream_id(), + opt.stream_name.c_str(), opt.start, opt.duration, + (int)opt.reset); +} + +void RtmpServerStream::OnPlay2(const RtmpPlay2Options& opt) { + LOG(ERROR) << remote_side() << '[' << stream_id() + << "] ignored play2{" << opt.ShortDebugString() << '}'; +} + +void RtmpServerStream::OnPublish(const std::string& name, + RtmpPublishType type, + butil::Status* status, + google::protobuf::Closure* done) { + ClosureGuard done_guard(done); + status->set_error(EPERM, "%s[%u] ignored publish{stream_name=%s type=%s}", + butil::endpoint2str(remote_side()).c_str(), stream_id(), + name.c_str(), RtmpPublishType2Str(type)); +} + +int RtmpServerStream::OnSeek(double offset_ms) { + LOG(ERROR) << remote_side() << '[' << stream_id() << "] ignored seek(" + << offset_ms << ")"; + return -1; +} + +int RtmpServerStream::OnPause(bool pause, double offset_ms) { + LOG(ERROR) << remote_side() << '[' << stream_id() << "] ignored " + << (pause ? "pause" : "unpause") + << "(offset_ms=" << offset_ms << ")"; + return -1; +} + +void RtmpServerStream::OnSetBufferLength(uint32_t /*buffer_length_ms*/) {} + +int RtmpServerStream::SendStopMessage(const butil::StringPiece& error_desc) { + if (_rtmpsock == NULL) { + errno = EINVAL; + return -1; + } + if (FLAGS_rtmp_server_close_connection_on_error && + !_client_supports_stream_multiplexing) { + _rtmpsock->SetFailed(EFAILEDSOCKET, "Close connection because %.*s", + (int)error_desc.size(), error_desc.data()); + // The purpose is to close the connection, no matter what SetFailed() + // returns, the operation should be done. + LOG_IF(WARNING, FLAGS_log_error_text) + << "Close connection because " << error_desc; + return 0; + } + + // Send StreamNotFound error to make the client close connections. + // Works for flashplayer and ffplay(not started playing), not work for SRS + // and ffplay(started playing) + butil::IOBuf req_buf; + RtmpInfo info; + { + butil::IOBufAsZeroCopyOutputStream zc_stream(&req_buf); + AMFOutputStream ostream(&zc_stream); + WriteAMFString(RTMP_AMF0_COMMAND_ON_STATUS, &ostream); + WriteAMFUint32(0, &ostream); + WriteAMFNull(&ostream); + if (_is_publish) { + // NetStream.Publish.Rejected does not work for ffmpeg, works for OBS. + // NetStream.Publish.BadName does not work for OBS. + // NetStream.Play.StreamNotFound is not accurate but works for both + // ffmpeg and OBS. + info.set_code(RTMP_STATUS_CODE_STREAM_NOT_FOUND); + } else { + info.set_code(RTMP_STATUS_CODE_STREAM_NOT_FOUND); + } + info.set_level(RTMP_INFO_LEVEL_ERROR); + if (!error_desc.empty()) { + info.set_description(error_desc.as_string()); + } + WriteAMFObject(info, &ostream); + } + SocketMessagePtr msg(new policy::RtmpUnsentMessage); + msg->header.message_length = req_buf.size(); + msg->header.message_type = policy::RTMP_MESSAGE_COMMAND_AMF0; + msg->header.stream_id = _message_stream_id; + msg->chunk_stream_id = _chunk_stream_id; + msg->body = req_buf; + + if (policy::WriteWithoutOvercrowded(_rtmpsock.get(), msg) != 0) { + PLOG_IF(WARNING, errno != EFAILEDSOCKET) + << _rtmpsock->remote_side() << '[' << _message_stream_id + << "]: Fail to send " << info.code() << ": " << error_desc; + return -1; + } + LOG_IF(WARNING, FLAGS_log_error_text) + << _rtmpsock->remote_side() << '[' << _message_stream_id << "]: Sent " + << info.code() << ' ' << error_desc; + return 0; +} + +// Call this method to send StreamDry to the client. +// Returns 0 on success, -1 otherwise. +int RtmpServerStream::SendStreamDry() { + char data[6]; + char* p = data; + policy::WriteBigEndian2Bytes(&p, policy::RTMP_USER_CONTROL_EVENT_STREAM_DRY); + policy::WriteBigEndian4Bytes(&p, stream_id()); + return SendControlMessage(policy::RTMP_MESSAGE_USER_CONTROL, data, sizeof(data)); +} + +int RtmpServerStream::RunOnFailed(bthread_id_t id, void* data, int) { + butil::intrusive_ptr stream( + static_cast(data), false); + CHECK(stream->_rtmpsock); + stream->OnStopInternal(); + bthread_id_unlock_and_destroy(id); + return 0; +} + +void RtmpServerStream::OnStopInternal() { + if (_rtmpsock == NULL) { + return CallOnStop(); + } + policy::RtmpContext* ctx = + static_cast(_rtmpsock->parsing_context()); + if (ctx == NULL) { + LOG(FATAL) << _rtmpsock->remote_side() << ": RtmpContext of " + << *_rtmpsock << " is NULL"; + return CallOnStop(); + } + if (ctx->RemoveMessageStream(this)) { + return CallOnStop(); + } +} + +butil::StringPiece RemoveRtmpPrefix(const butil::StringPiece& url_in) { + if (!url_in.starts_with("rtmp://")) { + return url_in; + } + butil::StringPiece url = url_in; + size_t i = 7; + for (; i < url.size() && url[i] == '/'; ++i); + url.remove_prefix(i); + return url; +} + +butil::StringPiece RemoveProtocolPrefix(const butil::StringPiece& url_in) { + size_t proto_pos = url_in.find("://"); + if (proto_pos == butil::StringPiece::npos) { + return url_in; + } + butil::StringPiece url = url_in; + size_t i = proto_pos + 3; + for (; i < url.size() && url[i] == '/'; ++i); + url.remove_prefix(i); + return url; +} + +void ParseRtmpHostAndPort(const butil::StringPiece& host_and_port, + butil::StringPiece* host, + butil::StringPiece* port) { + size_t colon_pos = host_and_port.find(':'); + if (colon_pos == butil::StringPiece::npos) { + if (host) { + *host = host_and_port; + } + if (port) { + *port = "1935"; + } + } else { + if (host) { + *host = host_and_port.substr(0, colon_pos); + } + if (port) { + *port = host_and_port.substr(colon_pos + 1); + } + } +} + +butil::StringPiece RemoveQueryStrings(const butil::StringPiece& stream_name_in, + butil::StringPiece* query_strings) { + const size_t qm_pos = stream_name_in.find('?'); + if (qm_pos == butil::StringPiece::npos) { + if (query_strings) { + query_strings->clear(); + } + return stream_name_in; + } else { + if (query_strings) { + *query_strings = stream_name_in.substr(qm_pos + 1); + } + return stream_name_in.substr(0, qm_pos); + } +} + +// Split vhost from *app in forms of "APP?vhost=..." and overwrite *host. +static void SplitVHostFromApp(const butil::StringPiece& app_and_vhost, + butil::StringPiece* app, + butil::StringPiece* vhost) { + const size_t q_pos = app_and_vhost.find('?'); + if (q_pos == butil::StringPiece::npos) { + if (app) { + *app = app_and_vhost; + } + if (vhost) { + vhost->clear(); + } + return; + } + + if (app) { + *app = app_and_vhost.substr(0, q_pos); + } + if (vhost) { + butil::StringPiece qstr = app_and_vhost.substr(q_pos + 1); + butil::StringSplitter sp(qstr.data(), qstr.data() + qstr.size(), '&'); + for (; sp; ++sp) { + butil::StringPiece field(sp.field(), sp.length()); + if (field.starts_with("vhost=")) { + *vhost = field.substr(6); + // vhost cannot have port. + const size_t colon_pos = vhost->find_last_of(':'); + if (colon_pos != butil::StringPiece::npos) { + vhost->remove_suffix(vhost->size() - colon_pos); + } + return; + } + } + vhost->clear(); + } +} + +void ParseRtmpURL(const butil::StringPiece& rtmp_url_in, + butil::StringPiece* host, + butil::StringPiece* vhost, + butil::StringPiece* port, + butil::StringPiece* app, + butil::StringPiece* stream_name) { + if (stream_name) { + stream_name->clear(); + } + butil::StringPiece rtmp_url = RemoveRtmpPrefix(rtmp_url_in); + size_t slash1_pos = rtmp_url.find_first_of('/'); + if (slash1_pos == butil::StringPiece::npos) { + if (host || port) { + ParseRtmpHostAndPort(rtmp_url, host, port); + } + if (app) { + app->clear(); + } + return; + } + if (host || port) { + ParseRtmpHostAndPort(rtmp_url.substr(0, slash1_pos), host, port); + } + // Remove duplicated slashes. + for (++slash1_pos; slash1_pos < rtmp_url.size() && + rtmp_url[slash1_pos] == '/'; ++slash1_pos); + rtmp_url.remove_prefix(slash1_pos); + size_t slash2_pos = rtmp_url.find_first_of('/'); + if (slash2_pos == butil::StringPiece::npos) { + return SplitVHostFromApp(rtmp_url, app, vhost); + } + SplitVHostFromApp(rtmp_url.substr(0, slash2_pos), app, vhost); + if (stream_name != NULL) { + // Remove duplicated slashes. + for (++slash2_pos; slash2_pos < rtmp_url.size() && + rtmp_url[slash2_pos] == '/'; ++slash2_pos); + rtmp_url.remove_prefix(slash2_pos); + *stream_name = rtmp_url; + } +} + +std::string MakeRtmpURL(const butil::StringPiece& host, + const butil::StringPiece& port, + const butil::StringPiece& app, + const butil::StringPiece& stream_name) { + std::string result; + result.reserve(15 + host.size() + app.size() + stream_name.size()); + result.append("rtmp://"); + result.append(host.data(), host.size()); + if (!port.empty()) { + result.push_back(':'); + result.append(port.data(), port.size()); + } + if (!app.empty()) { + result.push_back('/'); + result.append(app.data(), app.size()); + } + if (!stream_name.empty()) { + if (app.empty()) { // extra / to notify user that app is empty. + result.push_back('/'); + } + result.push_back('/'); + result.append(stream_name.data(), stream_name.size()); + } + return result; +} + +} // namespace brpc diff --git a/src/brpc/rtmp.h b/src/brpc/rtmp.h new file mode 100644 index 0000000..eb1b418 --- /dev/null +++ b/src/brpc/rtmp.h @@ -0,0 +1,1130 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_RTMP_H +#define BRPC_RTMP_H + +#include "butil/strings/string_piece.h" // butil::StringPiece +#include "butil/endpoint.h" // butil::EndPoint +#include "brpc/shared_object.h" // SharedObject, intrusive_ptr +#include "brpc/socket_id.h" // SocketUniquePtr +#include "brpc/controller.h" // Controller, IOBuf +#include "brpc/rtmp.pb.h" // RtmpConnectRequest +#include "brpc/amf.h" // AMFObject +#include "brpc/destroyable.h" // DestroyingPtr + + +namespace brpc { +namespace policy { +class RtmpContext; +class RtmpChunkStream; +class OnServerStreamCreated; +} +class RtmpClientImpl; +class RtmpClientStream; +class RtmpServerStream; +class StatusService; + +// ======= Audio ======= + +enum RtmpAudioCodec { + RTMP_AUDIO_NONE = 0x0001, // Raw sound, no compression + RTMP_AUDIO_ADPCM = 0x0002, // ADPCM compression + RTMP_AUDIO_MP3 = 0x0004, // mp3 compression + RTMP_AUDIO_INTEL = 0x0008, // Not used + RTMP_AUDIO_UNUSED = 0x0010, // Not used + RTMP_AUDIO_NELLY8 = 0x0020, // NellyMoser at 8-kHz compression + RTMP_AUDIO_NELLY = 0x0040, // NellyMoser compression (5, 11, 22, and 44 kHz) + RTMP_AUDIO_G711A = 0x0080, // G711A sound compression (Flash Media Server only) + RTMP_AUDIO_G711U = 0x0100, // G711U sound compression (Flash Media Server only) + RTMP_AUDIO_NELLY16 = 0x0200, // NellyMouser at 16-kHz compression + RTMP_AUDIO_AAC = 0x0400, // Advanced audio coding (AAC) codec + RTMP_AUDIO_SPEEX = 0x0800, // Speex Audio + RTMP_AUDIO_ALL = 0x0FFF, // All RTMP-supported audio codecs +}; +static const RtmpAudioCodec RTMP_AUDIO_UNKNOWN = (RtmpAudioCodec)0; + +enum FlvAudioCodec { + FLV_AUDIO_LINEAR_PCM_PLATFORM_ENDIAN = 0, + FLV_AUDIO_ADPCM = 1, + FLV_AUDIO_MP3 = 2, + FLV_AUDIO_LINEAR_PCM_LITTLE_ENDIAN = 3, + FLV_AUDIO_NELLYMOSER_16KHZ_MONO = 4, + FLV_AUDIO_NELLYMOSER_8KHZ_MONO = 5, + FLV_AUDIO_NELLYMOSER = 6, + FLV_AUDIO_G711_ALAW_LOGARITHMIC_PCM = 7, + FLV_AUDIO_G711_MULAW_LOGARITHMIC_PCM = 8, + FLV_AUDIO_RESERVED = 9, + FLV_AUDIO_AAC = 10, + FLV_AUDIO_SPEEX = 11, + FLV_AUDIO_MP3_8KHZ = 14, + FLV_AUDIO_DEVICE_SPECIFIC_SOUND = 15, +}; +// note: 16 is always safe because SoundFormat in flv spec is only 4 bits. +static const FlvAudioCodec FLV_AUDIO_UNKNOWN = (FlvAudioCodec)16/*note*/; + +const char* FlvAudioCodec2Str(FlvAudioCodec); + +enum FlvSoundRate { + FLV_SOUND_RATE_5512HZ = 0, + FLV_SOUND_RATE_11025HZ = 1, + FLV_SOUND_RATE_22050HZ = 2, + FLV_SOUND_RATE_44100HZ = 3, +}; +const char* FlvSoundRate2Str(FlvSoundRate); + +// Only pertains to uncompressed formats. Compressed formats always decode +// to 16 bits internally. +enum FlvSoundBits { + FLV_SOUND_8BIT = 0, + FLV_SOUND_16BIT = 1, +}; +const char* FlvSoundBits2Str(FlvSoundBits); + +// For Nellymoser: always 0. For AAC: always 1. +enum FlvSoundType { + FLV_SOUND_MONO = 0, + FLV_SOUND_STEREO = 1, +}; +const char* FlvSoundType2Str(FlvSoundType); + +// The Audio Message in RTMP. +struct RtmpAudioMessage { + uint32_t timestamp; + FlvAudioCodec codec; + FlvSoundRate rate; + FlvSoundBits bits; + FlvSoundType type; + butil::IOBuf data; + + bool IsAACSequenceHeader() const; + size_t size() const { return data.size() + 1; } +}; +std::ostream& operator<<(std::ostream&, const RtmpAudioMessage&); + +enum FlvAACPacketType { + FLV_AAC_PACKET_SEQUENCE_HEADER = 0, + FLV_AAC_PACKET_RAW = 1, +}; + +// The Audio Message when format == FLV_AUDIO_AAC +struct RtmpAACMessage { + uint32_t timestamp; + FlvSoundRate rate; + FlvSoundBits bits; + FlvSoundType type; + FlvAACPacketType packet_type; + + // For sequence header: AudioSpecificConfig + // For raw: Raw AAC frame data + butil::IOBuf data; + + // Create AAC message from audio message. + butil::Status Create(const RtmpAudioMessage& msg); + + // Size of serialized message. + size_t size() const { return data.size() + 2; } +}; + +// the aac object type, for RTMP sequence header +// aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 23 +enum AACObjectType { + AAC_OBJECT_MAIN = 1, + AAC_OBJECT_LC = 2, + AAC_OBJECT_SSR = 3, + AAC_OBJECT_HE = 5, // AAC HE = LC+SBR + AAC_OBJECT_HEV2 = 29, // AAC HEv2 = LC+SBR+PS +}; +static const AACObjectType AAC_OBJECT_UNKNOWN = (AACObjectType)0; + +struct AudioSpecificConfig { + AudioSpecificConfig(); + butil::Status Create(const butil::IOBuf& buf); + butil::Status Create(const void* data, size_t len); + + AACObjectType aac_object; + uint8_t aac_sample_rate; + uint8_t aac_channels; +}; + +// ======= Video ======= + +enum RtmpVideoCodec { + RTMP_VIDEO_UNUSED = 0x0001, // Obsolete value + RTMP_VIDEO_JPEG = 0x0002, // Obsolete value + RTMP_VIDEO_SORENSON = 0x0004, // Sorenson Flash video + RTMP_VIDEO_HOMEBREW = 0x0008, // V1 screen sharing + RTMP_VIDEO_VP6 = 0x0010, // On2 video (Flash 8+) + RTMP_VIDEO_VP6ALPHA = 0x0020, // On2 video with alpha + RTMP_VIDEO_HOMEBREWV = 0x0040, // Screen sharing version 2 (Flash 8+) + RTMP_VIDEO_H264 = 0x0080, // H264 video + RTMP_VIDEO_ALL = 0x00FF, // All RTMP-supported video +}; +static const RtmpVideoCodec RTMP_VIDEO_UNKNOWN = (RtmpVideoCodec)0; + +enum RtmpVideoFunction { + // Indicates that the client can perform frame-accurate seeks. + RTMP_VIDEO_FUNCTION_CLIENT_SEEK = 1, +}; + +enum FlvVideoFrameType { + FLV_VIDEO_FRAME_KEYFRAME = 1, // for AVC, a seekable frame + FLV_VIDEO_FRAME_INTERFRAME = 2, // for AVC, a non-seekable frame + FLV_VIDEO_FRAME_DISPOSABLE_INTERFRAME = 3, // H.263 only + FLV_VIDEO_FRAME_GENERATED_KEYFRAME = 4, // reserved for server use only + FLV_VIDEO_FRAME_INFOFRAME = 5 +}; +const char* FlvVideoFrameType2Str(FlvVideoFrameType); + +enum FlvVideoCodec { + FLV_VIDEO_JPEG = 1, // currently unused + FLV_VIDEO_SORENSON_H263 = 2, + FLV_VIDEO_SCREEN_VIDEO = 3, + FLV_VIDEO_ON2_VP6 = 4, + FLV_VIDEO_ON2_VP6_WITH_ALPHA_CHANNEL = 5, + FLV_VIDEO_SCREEN_VIDEO_V2 = 6, + FLV_VIDEO_AVC = 7, + FLV_VIDEO_HEVC = 12 +}; +static const FlvVideoCodec FLV_VIDEO_UNKNOWN = (FlvVideoCodec)0; + +const char* FlvVideoCodec2Str(FlvVideoCodec); + +// The Video Message in RTMP. +struct RtmpVideoMessage { + uint32_t timestamp; + FlvVideoFrameType frame_type; + FlvVideoCodec codec; + butil::IOBuf data; + + // True iff this message is a sequence header of AVC codec. + bool IsAVCSequenceHeader() const; + + // True iff this message is a sequence header of HEVC(H.265) codec. + bool IsHEVCSequenceHeader() const; + + // Size of serialized message + size_t size() const { return data.size() + 1; } +}; +std::ostream& operator<<(std::ostream&, const RtmpVideoMessage&); + +enum FlvAVCPacketType { + FLV_AVC_PACKET_SEQUENCE_HEADER = 0, + FLV_AVC_PACKET_NALU = 1, + // lower level NALU sequence ender is not required or supported + FLV_AVC_PACKET_END_OF_SEQUENCE = 2, +}; + +// The Video Message when codec == FLV_VIDEO_AVC +struct RtmpAVCMessage { + uint32_t timestamp; + FlvVideoFrameType frame_type; + FlvAVCPacketType packet_type; + int32_t composition_time; + + // For sequence header: AVCDecoderConfigurationRecord + // For NALU: One or more NALUs + // For end of sequence: empty + butil::IOBuf data; + + // Create a AVC message from a video message. + butil::Status Create(const RtmpVideoMessage&); + + // Size of serialized message. + size_t size() const { return data.size() + 5; } +}; + +// the profile for avc/h.264. +// @see Annex A Profiles and levels, H.264-AVC-ISO_IEC_14496-10.pdf, page 205. +enum AVCProfile { + // @see ffmpeg, libavcodec/avcodec.h:2713 + AVC_PROFILE_BASELINE = 66, + AVC_PROFILE_CONSTRAINED_BASELINE = 578, + AVC_PROFILE_MAIN = 77, + AVC_PROFILE_EXTENDED = 88, + AVC_PROFILE_HIGH = 100, + AVC_PROFILE_HIGH10 = 110, + AVC_PROFILE_HIGH10_INTRA = 2158, + AVC_PROFILE_HIGH422 = 122, + AVC_PROFILE_HIGH422_INTRA = 2170, + AVC_PROFILE_HIGH444 = 144, + AVC_PROFILE_HIGH444_PREDICTIVE = 244, + AVC_PROFILE_HIGH444_INTRA = 2192, +}; +const char* AVCProfile2Str(AVCProfile); + +// the level for avc/h.264. +// @see Annex A Profiles and levels, H.264-AVC-ISO_IEC_14496-10.pdf, page 207. +enum AVCLevel { + AVC_LEVEL_1 = 10, + AVC_LEVEL_11 = 11, + AVC_LEVEL_12 = 12, + AVC_LEVEL_13 = 13, + AVC_LEVEL_2 = 20, + AVC_LEVEL_21 = 21, + AVC_LEVEL_22 = 22, + AVC_LEVEL_3 = 30, + AVC_LEVEL_31 = 31, + AVC_LEVEL_32 = 32, + AVC_LEVEL_4 = 40, + AVC_LEVEL_41 = 41, + AVC_LEVEL_5 = 50, + AVC_LEVEL_51 = 51, +}; + +// Table 7-1 - NAL unit type codes, syntax element categories, and NAL unit type classes +// H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 83. +enum AVCNaluType { + AVC_NALU_EMPTY = 0, + AVC_NALU_NONIDR = 1, + AVC_NALU_DATAPARTITIONA = 2, + AVC_NALU_DATAPARTITIONB = 3, + AVC_NALU_DATAPARTITIONC = 4, + AVC_NALU_IDR = 5, + AVC_NALU_SEI = 6, + AVC_NALU_SPS = 7, + AVC_NALU_PPS = 8, + AVC_NALU_ACCESSUNITDELIMITER = 9, + AVC_NALU_EOSEQUENCE = 10, + AVC_NALU_EOSTREAM = 11, + AVC_NALU_FILTERDATA = 12, + AVC_NALU_SPSEXT = 13, + AVC_NALU_PREFIXNALU = 14, + AVC_NALU_SUBSETSPS = 15, + AVC_NALU_LAYERWITHOUTPARTITION = 19, + AVC_NALU_CODEDSLICEEXT = 20, +}; + +struct AVCDecoderConfigurationRecord { + AVCDecoderConfigurationRecord(); + + butil::Status Create(const butil::IOBuf& buf); + butil::Status Create(const void* data, size_t len); + + int width; + int height; + AVCProfile avc_profile; + AVCLevel avc_level; + int8_t length_size_minus1; + std::vector sps_list; + std::vector pps_list; + +private: + butil::Status ParseSPS(const butil::StringPiece& buf, size_t sps_length); +}; +std::ostream& operator<<(std::ostream&, const AVCDecoderConfigurationRecord&); + +enum AVCNaluFormat { + AVC_NALU_FORMAT_UNKNOWN = 0, + AVC_NALU_FORMAT_ANNEXB, + AVC_NALU_FORMAT_IBMF, +}; + +// Iterate NALUs inside RtmpAVCMessage.data +class AVCNaluIterator { +public: + AVCNaluIterator(butil::IOBuf* data, uint32_t length_size_minus1, + AVCNaluFormat* format_inout); + ~AVCNaluIterator(); + void operator++(); + operator void*() const { return _data; } + butil::IOBuf& operator*() { return _cur_nalu; } + butil::IOBuf* operator->() { return &_cur_nalu; } + AVCNaluType nalu_type() const { return _nalu_type; } +private: + // `data' is mutable, improper to be copied. + DISALLOW_COPY_AND_ASSIGN(AVCNaluIterator); + bool next_as_annexb(); + bool next_as_ibmf(); + void set_end() { _data = NULL; } + butil::IOBuf* _data; + butil::IOBuf _cur_nalu; + AVCNaluFormat* _format; + uint32_t _length_size_minus1; + AVCNaluType _nalu_type; +}; + +// ==== Meta data ==== +enum RtmpObjectEncoding { + RTMP_AMF0 = 0, // AMF0 object encoding supported by Flash 6 and later + RTMP_AMF3 = 3, // AMF3 encoding from Flash 9 (AS3) +}; +const char* RtmpObjectEncoding2Str(RtmpObjectEncoding); + +struct RtmpMetaData { + uint32_t timestamp; + AMFObject data; +}; + +struct RtmpCuePoint { + uint32_t timestamp; + AMFObject data; +}; + +enum class FlvHeaderFlags : uint8_t { + VIDEO = 0x01, + AUDIO = 0x04, + AUDIO_AND_VIDEO = 0x05, +}; + +struct FlvWriterOptions { + FlvWriterOptions() = default; + + FlvHeaderFlags flv_content_type = FlvHeaderFlags::AUDIO_AND_VIDEO; +}; + +struct RtmpSharedObjectMessage { + // Not implemented yet. +}; + +enum FlvTagType { + FLV_TAG_AUDIO = 8, + FLV_TAG_VIDEO = 9, + FLV_TAG_SCRIPT_DATA = 18, +}; + +class FlvWriter { +public: + // Start appending FLV tags into the buffer + explicit FlvWriter(butil::IOBuf* buf); + explicit FlvWriter(butil::IOBuf* buf, const FlvWriterOptions& options); + + // Append a video/audio/metadata/cuepoint message into the output buffer. + butil::Status Write(const RtmpVideoMessage&); + butil::Status Write(const RtmpAudioMessage&); + butil::Status Write(const RtmpMetaData&); + butil::Status Write(const RtmpCuePoint&); + +private: + butil::Status WriteScriptData(const butil::IOBuf& req_buf, uint32_t timestamp); + +private: + bool _write_header; + butil::IOBuf* _buf; + FlvWriterOptions _options; +}; + +class FlvReader { +public: + // Start reading FLV tags from the buffer. The data read by the following + // Read functions would be removed from *buf. + explicit FlvReader(butil::IOBuf* buf); + + // Get the next message type. + // If it is a valid flv tag, butil::Status::OK() is returned and the + // type is written to *type. Otherwise an error would be returned, + // leaving *type unchanged. + // Note: If error_code of the return value is EAGAIN, the caller + // should wait more data and try call PeekMessageType again. + butil::Status PeekMessageType(FlvTagType* type); + + // Read a video/audio/metadata message from the input buffer. + // Caller should use the result of function PeekMessageType to select an + // appropriate function, e.g., if *type is set to FLV_TAG_AUDIO in + // PeekMessageType, caller should call Read(RtmpAudioMessage*) subsequently. + butil::Status Read(RtmpVideoMessage* msg); + butil::Status Read(RtmpAudioMessage* msg); + butil::Status Read(RtmpMetaData* object, std::string* object_name); + +private: + butil::Status ReadHeader(); + +private: + bool _read_header; + butil::IOBuf* _buf; +}; + +struct RtmpPlayOptions { + // [Required] Name of the stream to play. + // * video (FLV) files: specify the name without a file extension, + // example: "sample". + // * MP3 or ID3 tags: precede the name with mp3, + // example: "mp3:sample". + // * H.264/AAC files: precede the name with mp4 and specify file extension. + // example: "mp4:sample.m4v" + std::string stream_name; + + // Specifies the start time in seconds. + // * The default value -2 means the subscriber first tries to play the live + // stream specified in `stream_name'. If alive stream of that name is not + // found, it plays the recorded stream of the same name. If there is no + // recorded stream with that name, the subscriber waits for a new live + // stream with that name and plays it when available. + // * -1: only the live stream specified in `stream_name' is played. + // * 0 or a positive number: a recorded stream specified by `stream_name' + // is played beginning from the time specified by this field. If no + // recorded stream is found, the next item in the playlist is played. + double start; + + // Specifies the duration of playback in seconds. + // * The default value -1 means a live stream is played until it is no + // longer available or a recorded stream is played until it ends. + // * A negative number other than -1: interpreted as -1. + // * 0: plays the single frame since the time specified in `start' + // from the beginning of a recorded stream. The value of `start' is + // assumed to be equal to or greater than 0. + // * A positive number: plays a live stream for the time period specified + // by this field. After that it becomes available or plays a recorded + // stream for the time specified by this field. If a stream ends before + // the time specified by `duration', playback ends when the stream ends. + double duration; + + // Specifies whether to flush any previous playlist. + bool reset; + + RtmpPlayOptions(); +}; + +enum RtmpPublishType { + // The stream is published and the data is recorded to a new file. The file + // is stored on the server in a subdirectory within the directory that + // contains the server application. If the file already exists, it is + // overwritten. + RTMP_PUBLISH_RECORD = 1, + + // The stream is published and the data is appended to a file. If no file + // is found, it is created. + RTMP_PUBLISH_APPEND, + + // Live data is published without recording it in a file. + RTMP_PUBLISH_LIVE, +}; +const char* RtmpPublishType2Str(RtmpPublishType); +bool Str2RtmpPublishType(const butil::StringPiece&, RtmpPublishType*); + +// For SetPeerBandwidth +enum RtmpLimitType { + RTMP_LIMIT_HARD = 0, + RTMP_LIMIT_SOFT = 1, + RTMP_LIMIT_DYNAMIC = 2 +}; + +// The common part of RtmpClientStream and RtmpServerStream. +class RtmpStreamBase : public SharedObject + , public Destroyable { +public: + explicit RtmpStreamBase(bool is_client); + + // @Destroyable + // For ClientStream, this function must be called to end this stream no matter + // Init() is called or not. Use DestroyingPtr<> which is a specialized unique_ptr + // to call Destroy() automatically. + // If this stream is enclosed in intrusive_ptr<>, this method can be called + // before/during Init(), or multiple times, because the stream is not + // destructed yet after calling Destroy(), otherwise the behavior is + // undefined. + virtual void Destroy(); + + // Process media messages from the peer. + // Following methods and OnStop() on the same stream are never called + // simultaneously. + // NOTE: Inputs can be modified and consumed. + virtual void OnUserData(void* msg); + virtual void OnCuePoint(RtmpCuePoint*); + virtual void OnMetaData(RtmpMetaData*, const butil::StringPiece&); + virtual void OnSharedObjectMessage(RtmpSharedObjectMessage* msg); + virtual void OnAudioMessage(RtmpAudioMessage* msg); + virtual void OnVideoMessage(RtmpVideoMessage* msg); + + // Will be called in the same thread before any OnMetaData/OnCuePoint + // OnSharedObjectMessage/OnAudioMessage/OnVideoMessage are called. + virtual void OnFirstMessage(); + + // Called when this stream is about to be destroyed or the underlying + // connection is broken. This method and above methods(OnXXX) on the + // same stream are never called simultaneously. + virtual void OnStop(); + + // Send media messages to the peer. + // Returns 0 on success, -1 otherwise. + virtual int SendCuePoint(const RtmpCuePoint&); + virtual int SendMetaData(const RtmpMetaData&, + const butil::StringPiece& name = "onMetaData"); + virtual int SendSharedObjectMessage(const RtmpSharedObjectMessage& msg); + virtual int SendAudioMessage(const RtmpAudioMessage& msg); + virtual int SendAACMessage(const RtmpAACMessage& msg); + virtual int SendVideoMessage(const RtmpVideoMessage& msg); + virtual int SendAVCMessage(const RtmpAVCMessage& msg); + // msg is owned by the caller of this function + virtual int SendUserMessage(void* msg); + + // Send a message to the peer to make it stop. The concrete message depends + // on implementation of the stream. + virtual int SendStopMessage(const butil::StringPiece& error_description); + + // // Call user's procedure at server-side. + // // request == NULL : send AMF null as the parameter. + // // response == NULL : response is not needed. + // // done == NULL : synchronous call, asynchronous otherwise. + // void Call(Controller* cntl, + // const butil::StringPiece& procedure_name, + // const google::protobuf::Message* request, + // google::protobuf::Message* response, + // google::protobuf::Closure* done); + + // Get id of the message stream. + uint32_t stream_id() const { return _message_stream_id; } + + // Get id of the chunk stream. + uint32_t chunk_stream_id() const { return _chunk_stream_id; } + + // Get ip/port of peer/self + virtual butil::EndPoint remote_side() const; + virtual butil::EndPoint local_side() const; + + bool is_client_stream() const { return _is_client; } + bool is_server_stream() const { return !_is_client; } + + // True iff OnStop() was called. + bool is_stopped() const { return _stopped; } + + // When this stream is created, got from butil::gettimeofday_us(). + int64_t create_realtime_us() const { return _create_realtime_us; } + + bool is_paused() const { return _paused; } + + // True if OnMetaData/OnCuePoint/OnXXXMessage() was ever called. + bool has_data_ever() const { return _has_data_ever; } + + // The underlying socket for reading/writing. + Socket* socket() { return _rtmpsock.get(); } + const Socket* socket() const { return _rtmpsock.get(); } + + // Returns true when the server accepted play or publish command. + // The acquire fence makes sure the callsite seeing true must be after + // sending play or publish command (possibly in another thread). + bool is_server_accepted() const + { return _is_server_accepted.load(butil::memory_order_acquire); } + + // Explicitly notify error to current stream + virtual void SignalError(); + +protected: +friend class policy::RtmpContext; +friend class policy::RtmpChunkStream; +friend class policy::OnServerStreamCreated; + + virtual ~RtmpStreamBase(); + + int SendMessage(uint32_t timestamp, uint8_t message_type, + const butil::IOBuf& body); + int SendControlMessage(uint8_t message_type, const void* body, size_t); + + // OnStop is mutually exclusive with OnXXXMessage, following methods + // implement the exclusion. + bool BeginProcessingMessage(const char* fun_name); + void EndProcessingMessage(); + void CallOnUserData(void* data); + void CallOnCuePoint(RtmpCuePoint*); + void CallOnMetaData(RtmpMetaData*, const butil::StringPiece&); + void CallOnSharedObjectMessage(RtmpSharedObjectMessage* msg); + void CallOnAudioMessage(RtmpAudioMessage* msg); + void CallOnVideoMessage(RtmpVideoMessage* msg); + void CallOnStop(); + + bool _is_client; + bool _paused; // Only used by RtmpServerStream + bool _stopped; // True when OnStop() was called. + bool _processing_msg; // True when OnXXXMessage/OnMetaData/OnCuePoint are called. + bool _has_data_ever; + uint32_t _message_stream_id; + uint32_t _chunk_stream_id; + int64_t _create_realtime_us; + SocketUniquePtr _rtmpsock; + butil::Mutex _call_mutex; + butil::atomic _is_server_accepted; +}; + +struct RtmpClientOptions { + // Constructed with default options. + RtmpClientOptions(); + + // The Server application name the client is connected to. + std::string app; + + // Flash Player version. It is the same string as returned by the + // ApplicationScript getversion () function. + std::string flashVer; + + // URL of the source SWF file making the connection. + std::string swfUrl; + + // URL of the Server. It has the following format: + // protocol://servername:port/appName/appInstance + std::string tcUrl; + + // True if proxy is being used. + bool fpad; + + // Indicates what audio codecs the client supports. + RtmpAudioCodec audioCodecs; + + // Indicates what video codecs are supported. + RtmpVideoCodec videoCodecs; + + // Indicates what special video functions are supported. + RtmpVideoFunction videoFunction; + + // URL of the web page from where the SWF file was loaded. + std::string pageUrl; + + // ======================================================= + // Following fields are not part of on-wire RTMP data. + + // Timeout(in milliseconds) for creating a stream. + // Default: 1000 + int32_t timeout_ms; + + // Timeout(in milliseconds) for creating a stream. + // Default: 500 + int32_t connect_timeout_ms; + + // Value of SetBufferLength sent after Play. + // Default: 1000 + uint32_t buffer_length_ms; + + // Value of SetChunkSize sent after Play. + // Default: 60000 + uint32_t chunk_size; + + // Value of WindowAckSize sent after connect message. + // Default: 2500000 + uint32_t window_ack_size; + + // Indicates whether to use simplified rtmp protocol or not. + // The process of handshaking and connection will be reduced to 0 + // RTT by client directly sending a magic number, Connect command + // and CreateStream command to server. Server receiving this magic + // number should recognize it as the beginning of simplified rtmp + // protocol, skip regular handshaking process and change its state + // as if the handshaking has already completed. + // Default: false; + bool simplified_rtmp; +}; + +// Represent the communication line to one or multiple RTMP servers. +// Notice this does NOT correspond to the "NetConnection" in AS which +// only stands for one server. +class RtmpClient { +public: + RtmpClient(); + ~RtmpClient(); + RtmpClient(const RtmpClient&); + RtmpClient& operator=(const RtmpClient&); + + // Specify the servers to connect. + int Init(butil::EndPoint server_addr_and_port, + const RtmpClientOptions& options); + int Init(const char* server_addr_and_port, + const RtmpClientOptions& options); + int Init(const char* server_addr, int port, + const RtmpClientOptions& options); + int Init(const char* naming_service_url, + const char* load_balancer_name, + const RtmpClientOptions& options); + + // True if Init() was successfully called. + bool initialized() const; + + const RtmpClientOptions& options() const; + + void swap(RtmpClient& other) { _impl.swap(other._impl); } + +private: +friend class RtmpClientStream; + butil::intrusive_ptr _impl; +}; + +struct RtmpHashCode { + RtmpHashCode() : _has_hash_code(false), _hash_code(0) {} + void operator=(uint32_t hash_code) { + _has_hash_code = true; + _hash_code = hash_code; + } + operator uint32_t() const { return _hash_code; } + bool has_been_set() const { return _has_hash_code; } +private: + bool _has_hash_code; + uint32_t _hash_code; +}; + +struct RtmpClientStreamOptions { + // Reuse the same RTMP connection if possible. + // Default: true; + bool share_connection; + + // Init() blocks until play or publish is sent. + // Default: false + bool wait_until_play_or_publish_is_sent; + + // Max #retries for creating the stream. + // Default: 3 + int create_stream_max_retry; + + // stream name for play command. + std::string play_name; + + // stream name and type for publish command. + std::string publish_name; + RtmpPublishType publish_type; // default: RTMP_PUBLISH_LIVE + + // The hash code for consistent hashing load balancer. + RtmpHashCode hash_code; + + RtmpClientStreamOptions(); + + const std::string& stream_name() const + { return !publish_name.empty() ? publish_name : play_name; } +}; + +// Represent a "NetStream" in AS. Multiple streams can be multiplexed +// into one TCP connection. +class RtmpClientStream : public RtmpStreamBase + , public StreamCreator + , public StreamUserData { +public: + RtmpClientStream(); + + void Destroy() override; + + // Create this stream on `client' according to `options'. + // If any error occurred during initialization, OnStop() will be called. + // If this stream is enclosed in intrusive_ptr<> and: + // - Destroy() was called before, Init() will return immediately. + // - Destroy() is called during creation of the stream, the process will + // be cancelled and OnStop() will be called soon. + void Init(const RtmpClient* client, const RtmpClientStreamOptions& options); + + // Change bitrate. + int Play2(const RtmpPlay2Options&); + + // Seek the offset (in milliseconds) within a media file or playlist. + int Seek(double offset_ms); + + int Pause(bool pause_or_unpause, double offset_ms); + + // The options passed to Init() + const RtmpClientStreamOptions& options() const { return _options; } + + // In form of "rtmp://HOST/APP/STREAM_NAME" + std::string rtmp_url() const; + +protected: + virtual ~RtmpClientStream(); + +private: +friend class policy::RtmpChunkStream; +friend class policy::OnServerStreamCreated; +friend class OnClientStreamCreated; +friend class RtmpRetryingClientStream; + + int Play(const RtmpPlayOptions& opt); + int Publish(const butil::StringPiece& name, RtmpPublishType type); + + // @StreamCreator + StreamUserData* OnCreatingStream(SocketUniquePtr* inout, Controller* cntl) override; + void DestroyStreamCreator(Controller* cntl) override; + + // @StreamUserData + void DestroyStreamUserData(SocketUniquePtr& sending_sock, + Controller* cntl, + int error_code, + bool end_of_rpc) override; + + void OnFailedToCreateStream(); + + static int RunOnFailed(bthread_id_t id, void* data, int); + void OnStopInternal(); + + // Called when the stream received a status message. Server may send status + // messages back to client for publish/seek/pause etc commands. + void OnStatus(const RtmpInfo& info); + + // The Destroy() w/o dereference _self_ref, to be called internally by + // client stream self. + void SignalError() override; + + butil::intrusive_ptr _client_impl; + butil::intrusive_ptr _self_ref; + bthread_id_t _onfail_id; + CallId _create_stream_rpc_id; + bool _from_socketmap; + bool _created_stream_with_play_or_publish; + enum State { + STATE_UNINITIALIZED, + STATE_CREATING, + STATE_CREATED, + STATE_ERROR, + STATE_DESTROYING, + }; + State _state; + butil::Mutex _state_mutex; + RtmpClientStreamOptions _options; +}; + +struct RtmpRetryingClientStreamOptions : public RtmpClientStreamOptions { + // Wait for at least so many milliseconds before next retry. + // Default: 1000 + int retry_interval_ms; + + // >0: Retry for so many milliseconds approximately. + // 0: Never retry. + // -1: Infinite retries. + // Default: -1 + int max_retry_duration_ms; + + // Retry so many times without any delay between consecutive retries. + // (controlled by retry_interval_ms) + // Default: 2 + int fast_retry_count; + + // Stop retrying when ALL created streams fail before playing or + // publishing any data. "ALL" = max(fast_retry_count, 1) + // In most scenarios, this option should be true which may stop + // pointless retries. + // Default: true + bool quit_when_no_data_ever; + + RtmpRetryingClientStreamOptions(); +}; + +// Base class for handling the messages received by a SubStream +class RtmpMessageHandler { +public: + virtual void OnPlayable() = 0; + virtual void OnUserData(void*) = 0; + virtual void OnCuePoint(brpc::RtmpCuePoint* cuepoint) = 0; + virtual void OnMetaData(brpc::RtmpMetaData* metadata, const butil::StringPiece& name) = 0; + virtual void OnAudioMessage(brpc::RtmpAudioMessage* msg) = 0; + virtual void OnVideoMessage(brpc::RtmpVideoMessage* msg) = 0; + virtual void OnSharedObjectMessage(RtmpSharedObjectMessage* msg) = 0; + virtual void OnSubStreamStop(RtmpStreamBase* sub_stream) = 0; + virtual ~RtmpMessageHandler() {} +}; + +class RtmpRetryingClientStream; +// RtmpMessageHandler for RtmpRetryingClientStream +class RetryingClientMessageHandler : public RtmpMessageHandler { +public: + RetryingClientMessageHandler(RtmpRetryingClientStream* parent); + ~RetryingClientMessageHandler() {} + + void OnPlayable(); + void OnUserData(void*); + void OnCuePoint(brpc::RtmpCuePoint* cuepoint); + void OnMetaData(brpc::RtmpMetaData* metadata, const butil::StringPiece& name); + void OnAudioMessage(brpc::RtmpAudioMessage* msg); + void OnVideoMessage(brpc::RtmpVideoMessage* msg); + void OnSharedObjectMessage(RtmpSharedObjectMessage* msg); + void OnSubStreamStop(RtmpStreamBase* sub_stream); + +private: + butil::intrusive_ptr _parent; +}; + +class SubStreamCreator { +public: + // Create a new SubStream and use *message_handler to handle messages from + // the current SubStream. *sub_stream is set iff the creation is successful. + // Note: message_handler is OWNED by this creator and deleted by the creator. + virtual void NewSubStream(RtmpMessageHandler* message_handler, + butil::intrusive_ptr* sub_stream) = 0; + + // Do the Initialization of sub_stream. If an error happens, sub_stream->Destroy() + // would be called. + // Note: sub_stream is not OWNED by the creator. + virtual void LaunchSubStream(RtmpStreamBase* sub_stream, + RtmpRetryingClientStreamOptions* options) = 0; + virtual ~SubStreamCreator() {} +}; + +class RtmpRetryingClientStream : public RtmpStreamBase { +public: + RtmpRetryingClientStream(); + + // Must be called to end this stream no matter Init() is called or not. + void Destroy(); + + // Initialize this stream with the given sub_stream_creator which may create a + // different sub stream each time. + // NOTE: sub_stream_creator is OWNED by this stream and deleted by this stream. + void Init(SubStreamCreator* sub_stream_creator, + const RtmpRetryingClientStreamOptions& options); + + // @RtmpStreamBase + // If the stream is recreated, following methods may return -1 and set + // errno to ERTMPPUBLISHABLE for once. (so that users can be notified to + // resend metadata or header messages). + int SendCuePoint(const RtmpCuePoint&); + int SendMetaData(const RtmpMetaData&, + const butil::StringPiece& name = "onMetaData"); + int SendSharedObjectMessage(const RtmpSharedObjectMessage& msg); + int SendAudioMessage(const RtmpAudioMessage& msg); + int SendAACMessage(const RtmpAACMessage& msg); + int SendVideoMessage(const RtmpVideoMessage& msg); + int SendAVCMessage(const RtmpAVCMessage& msg); + butil::EndPoint remote_side() const; + butil::EndPoint local_side() const; + + // Call this function to stop current stream. New sub stream will be + // tried to be created later. + void StopCurrentStream(); + + // If a sub stream was created, this method will be called in the same + // thread before any OnMetaData/OnCuePoint/OnSharedObjectMessage/OnAudioMessage/ + // OnVideoMessage are called. + virtual void OnPlayable(); + + const RtmpRetryingClientStreamOptions& options() const { return _options; } + +protected: + ~RtmpRetryingClientStream(); + +private: +friend class RetryingClientMessageHandler; + + void OnSubStreamStop(RtmpStreamBase* sub_stream); + int AcquireStreamToSend(butil::intrusive_ptr*); + static void OnRecreateTimer(void* arg); + void Recreate(); + void CallOnStopIfNeeded(); + + butil::intrusive_ptr _using_sub_stream; + butil::intrusive_ptr _self_ref; + mutable butil::Mutex _stream_mutex; + RtmpRetryingClientStreamOptions _options; + butil::atomic _destroying; + butil::atomic _called_on_stop; + bool _changed_stream; + bool _has_timer_ever; + bool _is_server_accepted_ever; + int _num_fast_retries; + int64_t _last_creation_time_us; + int64_t _last_retry_start_time_us; + bthread_timer_t _create_timer_id; + // Note: RtmpClient can be efficiently copied. + RtmpClient _client_copy; + SubStreamCreator* _sub_stream_creator; +}; + +// Utility function to get components from rtmp_url which could be in forms of: +// rtmp://HOST/APP/STREAM_NAME +// rtmp://HOST/APP (empty stream_name) +// rtmp://HOST (empty app and stream_name) +// rtmp://HOST/APP?vhost=.../STREAM_NAME (This is how SRS put vhost in URL) +// "rtmp://" can be ignored. +// NOTE: query strings after stream_name is not removed and returned as part +// of stream_name. +void ParseRtmpURL(const butil::StringPiece& rtmp_url, + butil::StringPiece* host, + butil::StringPiece* vhost_after_app, + butil::StringPiece* port, + butil::StringPiece* app, + butil::StringPiece* stream_name); +void ParseRtmpHostAndPort(const butil::StringPiece& host_and_port, + butil::StringPiece* host, + butil::StringPiece* port); +butil::StringPiece RemoveQueryStrings(const butil::StringPiece& stream_name, + butil::StringPiece* query_strings); +// Returns "rtmp://HOST/APP/STREAM_NAME" +std::string MakeRtmpURL(const butil::StringPiece& host, + const butil::StringPiece& port, + const butil::StringPiece& app, + const butil::StringPiece& stream_name); +// Returns url removed with beginning "rtmp://". +butil::StringPiece RemoveRtmpPrefix(const butil::StringPiece& url); +// Returns url removed with beginning "xxx://" +butil::StringPiece RemoveProtocolPrefix(const butil::StringPiece& url); + +// Implement this class and assign an instance to ServerOption.rtmp_service +// to enable RTMP support. +class RtmpService { +public: + virtual ~RtmpService() {} + + // Called when receiving a Pong response from `remote_side'. + virtual void OnPingResponse(const butil::EndPoint& remote_side, + uint32_t ping_timestamp); + + // Called to create a server-side stream. + virtual RtmpServerStream* NewStream(const RtmpConnectRequest&) = 0; + +private: +friend class StatusService; +friend class policy::RtmpChunkStream; +}; + +// Represent the "NetStream" on server-side. +class RtmpServerStream : public RtmpStreamBase { +public: + RtmpServerStream(); + ~RtmpServerStream(); + + // Called when receiving a play request. + // Call status->set_error() when the play request is rejected. + // Call done->Run() when the play request is processed (either accepted + // or rejected) + virtual void OnPlay(const RtmpPlayOptions&, + butil::Status* status, + google::protobuf::Closure* done); + + // Called when receiving a publish request. + // Call status->set_error() when the publish request is rejected. + // Call done->Run() when the publish request is processed (either accepted + // Returns 0 on success, -1 otherwise. + virtual void OnPublish(const std::string& stream_name, + RtmpPublishType publish_type, + butil::Status* status, + google::protobuf::Closure* done); + + // Called when receiving a play2 request. + virtual void OnPlay2(const RtmpPlay2Options&); + + // Called when receiving a seek request. + // Returns 0 on success, -1 otherwise. + virtual int OnSeek(double offset_ms); + + // Called when receiving a pause/unpause request. + // Returns 0 on success, -1 otherwise. + virtual int OnPause(bool pause_or_unpause, double offset_ms); + + // Called when receiving information from Rtmp client on buffer size (in + // milliseconds) that is used to buffer any data coming over a stream. + // This event is sent before the server starts processing the stream. + virtual void OnSetBufferLength(uint32_t buffer_length_ms); + + // @RtmpStreamBase, sending StreamNotFound + int SendStopMessage(const butil::StringPiece& error_description); + void Destroy(); + +private: +friend class policy::RtmpContext; +friend class policy::RtmpChunkStream; + int SendStreamDry(); + static int RunOnFailed(bthread_id_t id, void* data, int); + void OnStopInternal(); + // Indicating the client supports multiple streams over one connection. + bool _client_supports_stream_multiplexing; + bool _is_publish; + bthread_id_t _onfail_id; +}; + +} // namespace brpc + + +#endif // BRPC_RTMP_H diff --git a/src/brpc/rtmp.proto b/src/brpc/rtmp.proto new file mode 100644 index 0000000..a97a5c8 --- /dev/null +++ b/src/brpc/rtmp.proto @@ -0,0 +1,85 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; + +package brpc; + +message RtmpConnectRequest { + // The Server application name the client is connected to. + optional string app = 1; + + // Flash Player version. It is the same string as returned by the + // ApplicationScript getversion () function. + optional string flashVer = 2; + + // URL of the source SWF file making the connection. + optional string swfUrl = 3; + + // URL of the Server. It has the following format: + // protocol://servername:port/appName/appInstance + optional string tcUrl = 4; + + // True if proxy is being used. + optional bool fpad = 5; + + // Unknown, copy from SRS. + optional double capabilities = 6; + + // Indicates what audio codecs the client supports. + optional double audioCodecs = 7; + + // Indicates what video codecs are supported. + optional double videoCodecs = 8; + + // Indicates what special video functions are supported. + optional double videoFunction = 9; + + // URL of the web page from where the SWF file was loaded. + optional string pageUrl = 10; + + // AMF encoding method. + optional double objectEncoding = 11; + + // True if the client supports multiple streams over one connection. + optional bool stream_multiplexing = 12; +} + +message RtmpConnectResponse { + optional string fmsVer = 1; + optional double capabilities = 2; + optional double mode = 3; + optional bool create_stream_with_play_or_publish = 4; +} + +message RtmpPlay2Options { + optional double len = 1; + optional double offset = 2; + optional string oldStreamName = 3; + optional double start = 4; + optional string streamName = 5; + optional string transition = 6; +} + +message RtmpInfo { + optional string code = 1; + optional string level = 2; + optional string description = 3; + optional double objectEncoding = 4; // used in connect response +} + +message RtmpEmptyObject {} diff --git a/src/brpc/selective_channel.cpp b/src/brpc/selective_channel.cpp new file mode 100644 index 0000000..287b0a1 --- /dev/null +++ b/src/brpc/selective_channel.cpp @@ -0,0 +1,627 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include "bthread/bthread.h" // bthread_id_xx +#include "brpc/socket.h" // SocketUser +#include "brpc/load_balancer.h" // LoadBalancer +#include "brpc/details/controller_private_accessor.h" // RPCSender +#include "brpc/selective_channel.h" +#include "brpc/global.h" + + +namespace brpc { + +DEFINE_int32(channel_check_interval, 1, + "seconds between consecutive health-checking of unaccessible" + " sub channels inside SelectiveChannel"); + +namespace schan { + +// This map is generally very small, std::map may be good enough. +typedef std::map ChannelToIdMap; + +// Representing a sub channel. +class SubChannel : public SocketUser { +public: + ChannelBase* chan; + ChannelOwnership ownership; + + // internal channel is deleted after the fake Socket is SetFailed + void BeforeRecycle(Socket*) { + if (ownership == OWNS_CHANNEL) { + delete chan; + } + delete this; + } + + int CheckHealth(Socket* ptr) { + if (ptr->health_check_count() == 0) { + LOG(INFO) << "Checking " << *chan << " chan=0x" << (void*)chan + << " Fake" << *ptr; + } + return chan->CheckHealth(); + } + + void AfterRevived(Socket* ptr) { + LOG(INFO) << "Revived " << *chan << " chan=0x" << (void*)chan + << " Fake" << *ptr << " (Connectable)"; + } +}; + +int GetSubChannelWeight(SocketUser* u) { + return static_cast(u)->chan->Weight(); +} + +// Load balance between fake sockets whose SocketUsers are sub channels. +class ChannelBalancer : public SharedLoadBalancer { +public: + struct SelectOut { + SelectOut() : need_feedback(false) {} + + ChannelBase* channel() { + return static_cast(fake_sock->user())->chan; + } + + SocketUniquePtr fake_sock; + bool need_feedback; + }; + + ChannelBalancer() {} + ~ChannelBalancer(); + int Init(const char* lb_name); + int AddChannel(ChannelBase* sub_channel, + const SelectiveChannel::SubChannelOptions& subopt, + SelectiveChannel::ChannelHandle* handle); + void RemoveAndDestroyChannel(const SelectiveChannel::ChannelHandle& handle); + int SelectChannel(const LoadBalancer::SelectIn& in, SelectOut* out); + int CheckHealth(); + void Describe(std::ostream& os, const DescribeOptions&); + +private: + butil::Mutex _mutex; + // Find out duplicated sub channels. + ChannelToIdMap _chan_map; +}; + +class SubDone; +class Sender; + +struct Resource { + Resource() : response(NULL), sub_done(NULL) {} + + google::protobuf::Message* response; + SubDone* sub_done; +}; + +// The done to sub channels. +class SubDone : public google::protobuf::Closure { +public: + explicit SubDone(Sender* owner) + : _owner(owner) + , _cid(INVALID_BTHREAD_ID) + , _peer_id(INVALID_SOCKET_ID) { + } + ~SubDone() {} + void Run(); + + Sender* _owner; + CallId _cid; + SocketId _peer_id; + Controller _cntl; +}; + +// The sender to intercept Controller::IssueRPC +class Sender : public RPCSender, + public google::protobuf::Closure { +friend class SubDone; +public: + Sender(Controller* cntl, + const google::protobuf::Message* request, + google::protobuf::Message* response, + const butil::intrusive_ptr& lb, + google::protobuf::Closure* user_done); + ~Sender() { Clear(); } + int IssueRPC(int64_t start_realtime_us); + Resource PopFree(); + bool PushFree(const Resource& r); + const Controller* SubController(int index) const; + void Run(); + void Clear(); + +private: + Controller* _main_cntl; + const google::protobuf::Message* _request; + google::protobuf::Message* _response; + butil::intrusive_ptr _lb; + google::protobuf::Closure* _user_done; + short _nfree; + short _nalloc; + bool _finished; + Resource _free_resources[2]; + Resource _alloc_resources[2]; + SubDone _sub_done0; +}; + +// =============================================== + +ChannelBalancer::~ChannelBalancer() { + for (ChannelToIdMap::iterator + it = _chan_map.begin(); it != _chan_map.end(); ++it) { + it->second->ReleaseAdditionalReference(); + it->second->ReleaseHCRelatedReference(); + } + _chan_map.clear(); +} + +int ChannelBalancer::Init(const char* lb_name) { + return SharedLoadBalancer::Init(lb_name); +} + +int ChannelBalancer::AddChannel(ChannelBase* sub_channel, + const SelectiveChannel::SubChannelOptions& subopt, + SelectiveChannel::ChannelHandle* handle) { + if (NULL == sub_channel) { + LOG(ERROR) << "Parameter[sub_channel] is NULL"; + return -1; + } + BAIDU_SCOPED_LOCK(_mutex); + if (_chan_map.find(sub_channel) != _chan_map.end()) { + LOG(ERROR) << "Duplicated sub_channel=" << sub_channel; + return -1; + } + SubChannel* sub_chan = new (std::nothrow) SubChannel; + if (sub_chan == NULL) { + LOG(FATAL) << "Fail to to new SubChannel"; + return -1; + } + sub_chan->chan = sub_channel; + sub_chan->ownership = subopt.ownership; + SocketId sock_id; + SocketOptions options; + options.user = sub_chan; + options.health_check_interval_s = FLAGS_channel_check_interval; + + if (Socket::Create(options, &sock_id) != 0) { + delete sub_chan; + LOG(ERROR) << "Fail to create fake socket for sub channel"; + return -1; + } + SocketUniquePtr ptr; + int rc = Socket::AddressFailedAsWell(sock_id, &ptr); + if (rc < 0) { + LOG(ERROR) << "Fail to address SocketId=" << sock_id; + return -1; + } + if (rc > 0 && !ptr->HCEnabled()) { + LOG(ERROR) << "Health check of SocketId=" + << sock_id << " is disabled"; + return -1; + } + if (!AddServer(ServerId(sock_id, subopt.tag))) { + LOG(ERROR) << "Duplicated sub_channel=" << sub_channel; + // sub_chan will be deleted when the socket is recycled. + ptr->SetFailed(); + // Cancel health checking. + ptr->ReleaseHCRelatedReference(); + return -1; + } + // The health-check-related reference has been held on created. + _chan_map[sub_channel] = ptr.get(); + if (handle) { + handle->id = sock_id; + handle->tag = subopt.tag; + } + return 0; +} + +void ChannelBalancer::RemoveAndDestroyChannel(const SelectiveChannel::ChannelHandle& handle) { + if (!RemoveServer(ServerId(handle.id, handle.tag))) { + return; + } + SocketUniquePtr ptr; + const int rc = Socket::AddressFailedAsWell(handle.id, &ptr); + if (rc >= 0) { + SubChannel* sub = static_cast(ptr->user()); + { + BAIDU_SCOPED_LOCK(_mutex); + CHECK_EQ(1UL, _chan_map.erase(sub->chan)); + } + if (rc == 0) { + ptr->ReleaseAdditionalReference(); + } + // Cancel health checking. + ptr->ReleaseHCRelatedReference(); + } +} + +inline int ChannelBalancer::SelectChannel(const LoadBalancer::SelectIn& in, + SelectOut* out) { + LoadBalancer::SelectOut sel_out(&out->fake_sock); + const int rc = SelectServer(in, &sel_out); + if (rc != 0) { + return rc; + } + out->need_feedback = sel_out.need_feedback; + return 0; +} + +int ChannelBalancer::CheckHealth() { + BAIDU_SCOPED_LOCK(_mutex); + for (ChannelToIdMap::const_iterator it = _chan_map.begin(); + it != _chan_map.end(); ++it) { + if (!it->second->Failed() && + it->first->CheckHealth() == 0) { + return 0; + } + } + return -1; +} + +void ChannelBalancer::Describe(std::ostream& os, + const DescribeOptions& options) { + BAIDU_SCOPED_LOCK(_mutex); + if (!options.verbose) { + os << _chan_map.size(); + return; + } + for (ChannelToIdMap::const_iterator it = _chan_map.begin(); + it != _chan_map.end(); ++it) { + if (it != _chan_map.begin()) { + os << ' '; + } + it->first->Describe(os, options); + } +} + +// =================================== + +Sender::Sender(Controller* cntl, + const google::protobuf::Message* request, + google::protobuf::Message* response, + const butil::intrusive_ptr& lb, + google::protobuf::Closure* user_done) + : _main_cntl(cntl) + , _request(request) + , _response(response) + , _lb(lb) + , _user_done(user_done) + , _nfree(0) + , _nalloc(0) + , _finished(false) + , _sub_done0(this) { +} + +int Sender::IssueRPC(int64_t start_realtime_us) { + _main_cntl->_current_call.need_feedback = false; + ChannelBalancer* balancer = + static_cast(_lb.get()); + if (balancer == NULL) { + _main_cntl->SetFailed(ECANCELED, + "SelectiveChannel balancer is unavailable"); + return -1; + } + LoadBalancer::SelectIn sel_in = { start_realtime_us, + true, + _main_cntl->has_request_code(), + _main_cntl->_request_code, + _main_cntl->_accessed }; + ChannelBalancer::SelectOut sel_out; + const int rc = balancer->SelectChannel(sel_in, &sel_out); + if (rc != 0) { + _main_cntl->SetFailed(rc, "Fail to select channel, %s", berror(rc)); + return -1; + } + _main_cntl->_current_call.need_feedback = sel_out.need_feedback; + _main_cntl->_current_call.peer_id = sel_out.fake_sock->id(); + + Resource r = PopFree(); + if (r.sub_done == NULL) { + CHECK(false) << "Impossible!"; + _main_cntl->SetFailed("Impossible happens"); + return -1; + } + r.sub_done->_cid = _main_cntl->current_id(); + r.sub_done->_peer_id = sel_out.fake_sock->id(); + Controller* sub_cntl = &r.sub_done->_cntl; + // No need to count timeout. We already managed timeout in schan. If + // timeout occurs, sub calls are canceled with ERPCTIMEDOUT. + sub_cntl->_timeout_ms = -1; + sub_cntl->_real_timeout_ms = _main_cntl->timeout_ms(); + + // Inherit following fields of _main_cntl. + // TODO(gejun): figure out a better way to maintain these fields. + sub_cntl->set_connection_type(_main_cntl->connection_type()); + sub_cntl->set_type_of_service(_main_cntl->_tos); + sub_cntl->set_request_compress_type(_main_cntl->request_compress_type()); + sub_cntl->set_log_id(_main_cntl->log_id()); + sub_cntl->set_request_code(_main_cntl->request_code()); + // Forward request attachment to the subcall + sub_cntl->request_attachment().append(_main_cntl->request_attachment()); + ProtocolType protocol = _main_cntl->request_protocol(); + if (PROTOCOL_HTTP == protocol || PROTOCOL_H2 == protocol) { + sub_cntl->http_request() = _main_cntl->http_request(); + } + + sel_out.channel()->CallMethod(_main_cntl->_method, &r.sub_done->_cntl, + _request, r.response, r.sub_done); + return 0; +} + +void SubDone::Run() { + Controller* main_cntl = NULL; + const int rc = bthread_id_lock(_cid, (void**)&main_cntl); + if (rc != 0) { + // _cid must be valid because schan does not dtor before cancelling + // all sub calls. + LOG(ERROR) << "Fail to lock correlation_id=" + << _cid.value << ": " << berror(rc); + return; + } + Resource r; + r.response = _cntl._response; + r.sub_done = this; + if (!_owner->PushFree(r)) { + return; + } + const int saved_error = main_cntl->ErrorCode(); + + // NOTE: Copying gettable-but-settable fields which are generally set + // during the RPC to reflect details. + main_cntl->_remote_side = _cntl._remote_side; + // connection_type may be changed during CallMethod. + main_cntl->set_connection_type(_cntl.connection_type()); + main_cntl->response_attachment().swap(_cntl.response_attachment()); + + if (_cntl.Failed()) { + if (_cntl.ErrorCode() == ENODATA || _cntl.ErrorCode() == EHOSTDOWN) { + // LB could not find a server. + Socket::SetFailed(_peer_id); // trigger HC. + } + main_cntl->SetFailed(_cntl._error_text); + main_cntl->_error_code = _cntl._error_code; + } else { + if (_cntl._response != main_cntl->_response) { + main_cntl->_response->GetReflection()->Swap( + main_cntl->_response, _cntl._response); + } + } + const Controller::CompletionInfo info = { _cid, true }; + main_cntl->OnVersionedRPCReturned(info, false, saved_error); +} + +void Sender::Run() { + _finished = true; + if (_nfree != _nalloc) { + const int saved_nalloc = _nalloc; + int error = (_main_cntl->ErrorCode() == ERPCTIMEDOUT ? ERPCTIMEDOUT : ECANCELED); + CallId ids[_nalloc]; + for (int i = 0; i < _nalloc; ++i) { + ids[i] = _alloc_resources[i].sub_done->_cntl.call_id(); + } + CallId cid = _main_cntl->call_id(); + CHECK_EQ(0, bthread_id_unlock(cid)); + for (int i = 0; i < saved_nalloc; ++i) { + bthread_id_error(ids[i], error); + } + } else { + Clear(); + } +} + +void Sender::Clear() { + if (_main_cntl == NULL) { + return; + } + for (int i = 0; i < _nalloc; ++i) { + delete _alloc_resources[i].response; + if (_alloc_resources[i].sub_done != &_sub_done0) { + delete _alloc_resources[i].sub_done; + } + _alloc_resources[i] = Resource(); + } + const CallId cid = _main_cntl->call_id(); + _main_cntl = NULL; + _lb.reset(NULL); + if (_user_done) { + _user_done->Run(); + } + bthread_id_unlock_and_destroy(cid); +} + +inline Resource Sender::PopFree() { + if (_nfree == 0) { + if (_nalloc == 0) { + Resource r; + r.response = _response->New(); + r.sub_done = &_sub_done0; + _alloc_resources[_nalloc++] = r; + return r; + } else if (_nalloc == 1) { + Resource r; + r.response = _response->New(); + r.sub_done = new SubDone(this); + _alloc_resources[_nalloc++] = r; + return r; + } else { + CHECK(false) << "nalloc=" << _nalloc; + return Resource(); + } + } else { + Resource r = _free_resources[--_nfree]; + r.response->Clear(); + Controller& sub_cntl = r.sub_done->_cntl; + ExcludedServers* saved_accessed = sub_cntl._accessed; + sub_cntl._accessed = NULL; + sub_cntl.Reset(); + sub_cntl._accessed = saved_accessed; + return r; + } +} + +inline bool Sender::PushFree(const Resource& r) { + if (_nfree < 2) { + _free_resources[_nfree++] = r; + if (_finished && _nfree == _nalloc) { + Clear(); + return false; + } + return true; + } else { + CHECK(false) << "Impossible!"; + return false; + } +} + +inline const Controller* Sender::SubController(int index) const { + if (index != 0) { + return NULL; + } + for (int i = 0; i < _nfree; ++i) { + if (!_free_resources[i].sub_done->_cntl.Failed()) { + return &_free_resources[i].sub_done->_cntl; + } + } + if (_nfree != 0) { + return &_free_resources[_nfree - 1].sub_done->_cntl; + } + return NULL; +} + +} // namespace schan + +const Controller* GetSubControllerOfSelectiveChannel( + const RPCSender* sender, int index) { + return static_cast(sender)->SubController(index); +} + +static void PassSerializeRequest(butil::IOBuf*, Controller*, + const google::protobuf::Message*) { +} + +SelectiveChannel::SelectiveChannel() {} + +SelectiveChannel::~SelectiveChannel() {} + +int SelectiveChannel::Init(const char* lb_name, const ChannelOptions* options) { + // Force naming services to register. + GlobalInitializeOrDie(); + if (initialized()) { + LOG(ERROR) << "Already initialized"; + return -1; + } + schan::ChannelBalancer* lb = new (std::nothrow) schan::ChannelBalancer; + if (NULL == lb) { + LOG(FATAL) << "Fail to new ChannelBalancer"; + return -1; + } + if (lb->Init(lb_name) != 0) { + LOG(ERROR) << "Fail to init lb"; + delete lb; + return -1; + } + _chan._lb.reset(lb); + _chan._serialize_request = PassSerializeRequest; + if (options) { + _chan._options = *options; + // Modify some fields to be consistent with behavior of schan. + _chan._options.connection_type = CONNECTION_TYPE_UNKNOWN; + _chan._options.succeed_without_server = true; + _chan._options.auth = NULL; + } + _chan._options.protocol = PROTOCOL_UNKNOWN; + return 0; +} + +bool SelectiveChannel::initialized() const { + return _chan._lb != NULL; +} + +int SelectiveChannel::AddChannel(ChannelBase* sub_channel, + const SubChannelOptions& option, + ChannelHandle* handle) { + schan::ChannelBalancer* lb = + static_cast(_chan._lb.get()); + if (lb == NULL) { + LOG(ERROR) << "You must call Init() to initialize a SelectiveChannel"; + return -1; + } + return lb->AddChannel(sub_channel, option, handle); +} + +void SelectiveChannel::RemoveAndDestroyChannel(const ChannelHandle& handle) { + schan::ChannelBalancer* lb = + static_cast(_chan._lb.get()); + if (lb == NULL) { + LOG(ERROR) << "You must call Init() to initialize a SelectiveChannel"; + return; + } + lb->RemoveAndDestroyChannel(handle); +} + +void SelectiveChannel::CallMethod( + const google::protobuf::MethodDescriptor* method, + google::protobuf::RpcController* controller_base, + const google::protobuf::Message* request, + google::protobuf::Message* response, + google::protobuf::Closure* user_done) { + Controller* cntl = static_cast(controller_base); + if (!initialized()) { + cntl->SetFailed(EINVAL, "SelectiveChannel=%p is not initialized yet", + this); + // This is a branch only entered by wrongly-used RPC, just call done + // in-place. See comments in channel.cpp on deadlock concerns. + if (user_done) { + user_done->Run(); + } + return; + } + schan::Sender* sndr = + new schan::Sender(cntl, request, response, _chan._lb, user_done); + cntl->_sender = sndr; + cntl->add_flag(Controller::FLAGS_DESTROY_CID_IN_DONE); + const CallId cid = cntl->call_id(); + _chan.CallMethod(method, cntl, request, response, sndr); + if (user_done == NULL) { + Join(cid); + cntl->OnRPCEnd(butil::gettimeofday_us()); + } +} + +int SelectiveChannel::CheckHealth() { + schan::ChannelBalancer* lb = + static_cast(_chan._lb.get()); + if (lb) { + return lb->CheckHealth(); + } + return -1; +} + +void SelectiveChannel::Describe( + std::ostream& os, const DescribeOptions& options) const { + os << "SelectiveChannel["; + if (_chan._lb != NULL) { + _chan._lb->Describe(os, options); + } else { + os << "uninitialized"; + } + os << ']'; +} + +} // namespace brpc diff --git a/src/brpc/selective_channel.h b/src/brpc/selective_channel.h new file mode 100644 index 0000000..fd8fb9c --- /dev/null +++ b/src/brpc/selective_channel.h @@ -0,0 +1,113 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_SELECTIVE_CHANNEL_H +#define BRPC_SELECTIVE_CHANNEL_H + +// To brpc developers: This is a header included by user, don't depend +// on internal structures, use opaque pointers instead. + +#include "brpc/socket_id.h" +#include "brpc/channel.h" + + +namespace brpc { + +// A combo channel to split traffic to sub channels, aka "schan". The main +// purpose of schan is to load balance between groups of servers. +// SelectiveChannel is a fully functional Channel: +// * synchronous and asynchronous RPC. +// * deletable immediately after an asynchronous call. +// * cancelable call_id (cancels all sub calls). +// * timeout. +// Due to its designing goal, schan has a separate layer of retrying and +// backup request. Namely when schan fails to access a sub channel, it may +// retry another channel. sub channels inside a schan share the information +// of accessed servers and avoid retrying accessed servers by best efforts. +// When a schan would send a backup request, it calls a sub channel with +// the request. Since a sub channel can be a combo channel as well, the +// "backup request" may be "backup requests". +// ^ +// CAUTION: +// ======= +// Currently SelectiveChannel requires `request' to CallMethod be +// valid before the RPC ends. Other channels do not. If you're doing async +// calls with SelectiveChannel, make sure that `request' is owned and deleted +// in `done'. +class SelectiveChannel : public ChannelBase/*non-copyable*/ { +public: + struct ChannelHandle { + SocketId id; + std::string tag; + }; + + struct SubChannelOptions { + std::string tag; + ChannelOwnership ownership = OWNS_CHANNEL; + }; + + SelectiveChannel(); + ~SelectiveChannel(); + + // You MUST initialize a schan before using it. `load_balancer_name' is the + // name of load balancing algorithm which is listed in brpc/channel.h + // if `options' is NULL, use default options. + int Init(const char* load_balancer_name, const ChannelOptions* options); + + // Add a sub channel, which will be deleted along with schan or explicitly + // by RemoveAndDestroyChannel. + // On success, handle is set with the key for removal. + // NOTE: Different from pchan, schan can add channels at any time. + // Returns 0 on success, -1 otherwise. + int AddChannel(ChannelBase* sub_channel, ChannelHandle* handle) { + return AddChannel(sub_channel, SubChannelOptions(), handle); + } + int AddChannel(ChannelBase* sub_channel, const std::string& tag, ChannelHandle* handle) { + SubChannelOptions option; + option.tag = tag; + return AddChannel(sub_channel, option, handle); + } + int AddChannel(ChannelBase* sub_channel, const SubChannelOptions& option, + ChannelHandle* handle); + + // Remove and destroy the sub_channel associated with `handle'. + void RemoveAndDestroyChannel(const ChannelHandle& handle); + + // Send request by a sub channel. schan may retry another sub channel + // according to retrying/backup-request settings. + void CallMethod(const google::protobuf::MethodDescriptor* method, + google::protobuf::RpcController* controller, + const google::protobuf::Message* request, + google::protobuf::Message* response, + google::protobuf::Closure* done); + + // True iff Init() was successful. + bool initialized() const; + + void Describe(std::ostream& os, const DescribeOptions& options) const; + +private: + int CheckHealth(); + + Channel _chan; +}; + +} // namespace brpc + + +#endif // BRPC_SELECTIVE_CHANNEL_H diff --git a/src/brpc/serialized_request.cpp b/src/brpc/serialized_request.cpp new file mode 100644 index 0000000..ac55e31 --- /dev/null +++ b/src/brpc/serialized_request.cpp @@ -0,0 +1,72 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/serialized_request.h" + +#include "brpc/proto_base.pb.h" +#include "butil/logging.h" + +namespace brpc { + +SerializedRequest::SerializedRequest() + : NonreflectableMessage() { + SharedCtor(); +} + +SerializedRequest::SerializedRequest(const SerializedRequest& from) + : NonreflectableMessage(from) { + SharedCtor(); + MergeFrom(from); +} + +void SerializedRequest::SharedCtor() { +} + +SerializedRequest::~SerializedRequest() { + SharedDtor(); +} + +void SerializedRequest::SharedDtor() { +} + +void SerializedRequest::Clear() { + _serialized.clear(); +} + +size_t SerializedRequest::ByteSizeLong() const { + return _serialized.size(); +} + +void SerializedRequest::MergeFrom(const SerializedRequest& from) { + CHECK_NE(&from, this); + _serialized = from._serialized; +} + +void SerializedRequest::Swap(SerializedRequest* other) { + if (other != this) { + _serialized.swap(other->_serialized); + } +} + +::google::protobuf::Metadata SerializedRequest::GetMetadata() const { + ::google::protobuf::Metadata metadata{}; + metadata.descriptor = SerializedRequestBase::descriptor(); + metadata.reflection = nullptr; + return metadata; +} + +} // namespace brpc diff --git a/src/brpc/serialized_request.h b/src/brpc/serialized_request.h new file mode 100644 index 0000000..4d69aa4 --- /dev/null +++ b/src/brpc/serialized_request.h @@ -0,0 +1,62 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_SERIALIZED_REQUEST_H +#define BRPC_SERIALIZED_REQUEST_H + +#include "brpc/nonreflectable_message.h" +#include "brpc/pb_compat.h" +#include "butil/iobuf.h" + +namespace brpc { + +class SerializedRequest : public NonreflectableMessage { +public: + SerializedRequest(); + ~SerializedRequest() override; + + SerializedRequest(const SerializedRequest& from); + + inline SerializedRequest& operator=(const SerializedRequest& from) { + CopyFrom(from); + return *this; + } + + void Swap(SerializedRequest* other); + + void MergeFrom(const SerializedRequest& from) override; + + // implements Message ---------------------------------------------- + void Clear() override; + size_t ByteSizeLong() const override; + int GetCachedSize() const PB_425_OVERRIDE { return ByteSize(); } + butil::IOBuf& serialized_data() { return _serialized; } + const butil::IOBuf& serialized_data() const { return _serialized; } + + ::google::protobuf::Metadata GetMetadata() const PB_527_OVERRIDE; + +private: + void SharedCtor(); + void SharedDtor(); + + butil::IOBuf _serialized; +}; + +} // namespace brpc + +#endif // BRPC_SERIALIZED_REQUEST_H diff --git a/src/brpc/serialized_response.cpp b/src/brpc/serialized_response.cpp new file mode 100644 index 0000000..c846645 --- /dev/null +++ b/src/brpc/serialized_response.cpp @@ -0,0 +1,72 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/serialized_response.h" + +#include "brpc/proto_base.pb.h" +#include "butil/logging.h" + +namespace brpc { + +SerializedResponse::SerializedResponse() + : NonreflectableMessage() { + SharedCtor(); +} + +SerializedResponse::SerializedResponse(const SerializedResponse& from) + : NonreflectableMessage(from) { + SharedCtor(); + MergeFrom(from); +} + +void SerializedResponse::SharedCtor() { +} + +SerializedResponse::~SerializedResponse() { + SharedDtor(); +} + +void SerializedResponse::SharedDtor() { +} + +void SerializedResponse::Clear() { + _serialized.clear(); +} + +size_t SerializedResponse::ByteSizeLong() const { + return _serialized.size(); +} + +void SerializedResponse::MergeFrom(const SerializedResponse& from) { + CHECK_NE(&from, this); + _serialized = from._serialized; +} + +void SerializedResponse::Swap(SerializedResponse* other) { + if (other != this) { + _serialized.swap(other->_serialized); + } +} + +::google::protobuf::Metadata SerializedResponse::GetMetadata() const { + ::google::protobuf::Metadata metadata{}; + metadata.descriptor = SerializedResponseBase::descriptor(); + metadata.reflection = nullptr; + return metadata; +} + +} // namespace brpc diff --git a/src/brpc/serialized_response.h b/src/brpc/serialized_response.h new file mode 100644 index 0000000..acd18a2 --- /dev/null +++ b/src/brpc/serialized_response.h @@ -0,0 +1,62 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_SERIALIZED_RESPONSE_H +#define BRPC_SERIALIZED_RESPONSE_H + +#include "brpc/nonreflectable_message.h" +#include "brpc/pb_compat.h" +#include "butil/iobuf.h" + +namespace brpc { + +class SerializedResponse : public NonreflectableMessage { +public: + SerializedResponse(); + ~SerializedResponse() override; + + SerializedResponse(const SerializedResponse& from); + + inline SerializedResponse& operator=(const SerializedResponse& from) { + CopyFrom(from); + return *this; + } + + void Swap(SerializedResponse* other); + + void MergeFrom(const SerializedResponse& from) override; + + // implements Message ---------------------------------------------- + void Clear() override; + size_t ByteSizeLong() const override; + int GetCachedSize() const PB_425_OVERRIDE { return ByteSize(); } + butil::IOBuf& serialized_data() { return _serialized; } + const butil::IOBuf& serialized_data() const { return _serialized; } + + ::google::protobuf::Metadata GetMetadata() const PB_527_OVERRIDE; + +private: + void SharedCtor(); + void SharedDtor(); + + butil::IOBuf _serialized; +}; + +} // namespace brpc + +#endif // BRPC_SERIALIZED_RESPONSE_H diff --git a/src/brpc/server.cpp b/src/brpc/server.cpp new file mode 100644 index 0000000..6e1f9e8 --- /dev/null +++ b/src/brpc/server.cpp @@ -0,0 +1,2415 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include // inet_aton +#include // O_CREAT +#include // mkdir +#include +#include // ServiceDescriptor +#include "idl_options.pb.h" // option(idl_support) +#include "bthread/unstable.h" // bthread_keytable_pool_init +#include "butil/macros.h" // ARRAY_SIZE +#include "butil/fd_guard.h" // fd_guard +#include "butil/logging.h" // CHECK +#include "butil/time.h" +#include "butil/class_name.h" +#include "butil/string_printf.h" +#include "butil/strings/string_util.h" +#include "butil/debug/leak_annotations.h" +#include "brpc/log.h" +#include "brpc/compress.h" +#include "brpc/checksum.h" +#include "brpc/policy/nova_pbrpc_protocol.h" +#include "brpc/global.h" +#include "brpc/socket_map.h" // SocketMapList +#include "brpc/acceptor.h" // Acceptor +#include "brpc/details/ssl_helper.h" // CreateServerSSLContext +#include "brpc/protocol.h" // ListProtocols +#include "brpc/nshead_service.h" // NsheadService +#ifdef ENABLE_THRIFT_FRAMED_PROTOCOL +#include "brpc/thrift_service.h" // ThriftService +#endif +#include "brpc/builtin/bad_method_service.h" // BadMethodService +#include "brpc/builtin/get_favicon_service.h" +#include "brpc/builtin/get_js_service.h" +#include "brpc/builtin/grpc_health_check_service.h" // GrpcHealthCheckService +#include "brpc/builtin/version_service.h" +#include "brpc/builtin/health_service.h" +#include "brpc/builtin/list_service.h" +#include "brpc/builtin/status_service.h" +#include "brpc/builtin/protobufs_service.h" +#include "brpc/builtin/threads_service.h" +#include "brpc/builtin/vlog_service.h" +#include "brpc/builtin/index_service.h" // IndexService +#include "brpc/builtin/connections_service.h" // ConnectionsService +#include "brpc/builtin/flags_service.h" // FlagsService +#include "brpc/builtin/vars_service.h" // VarsService +#include "brpc/builtin/rpcz_service.h" // RpczService +#include "brpc/builtin/dir_service.h" // DirService +#include "brpc/builtin/pprof_service.h" // PProfService +#include "brpc/builtin/bthreads_service.h" // BthreadsService +#include "brpc/builtin/ids_service.h" // IdsService +#include "brpc/builtin/sockets_service.h" // SocketsService +#include "brpc/builtin/hotspots_service.h" // HotspotsService +#include "brpc/builtin/prometheus_metrics_service.h" +#include "brpc/builtin/memory_service.h" +#include "brpc/details/method_status.h" +#include "brpc/load_balancer.h" +#include "brpc/naming_service.h" +#include "brpc/simple_data_pool.h" +#include "brpc/server.h" +#include "brpc/trackme.h" +#include "brpc/restful.h" +#include "brpc/rtmp.h" +#include "brpc/builtin/common.h" // GetProgramName +#include "brpc/details/tcmalloc_extension.h" +#include "brpc/rdma/rdma_helper.h" +#include "brpc/baidu_master_service.h" +#include "brpc/transport_factory.h" + +inline std::ostream& operator<<(std::ostream& os, const timeval& tm) { + const char old_fill = os.fill(); + os << tm.tv_sec << '.' << std::setw(6) << std::setfill('0') << tm.tv_usec; + os.fill(old_fill); + return os; +} + +extern "C" { +void* bthread_get_assigned_data(); +} + +DECLARE_int32(task_group_ntags); + +namespace brpc { + +BAIDU_CASSERT(sizeof(int32_t) == sizeof(butil::subtle::Atomic32), + Atomic32_must_be_int32); + +extern const char* const g_server_info_prefix = "rpc_server"; + +const char* status_str(Server::Status s) { + switch (s) { + case Server::UNINITIALIZED: return "UNINITIALIZED"; + case Server::READY: return "READY"; + case Server::RUNNING: return "RUNNING"; + case Server::STOPPING: return "STOPPING"; + } + return "UNKNOWN_STATUS"; +} + +butil::static_atomic g_running_server_count = BUTIL_STATIC_ATOMIC_INIT(0); + +// Following services may have security issues and are disabled by default. +DEFINE_bool(enable_dir_service, false, "Enable /dir"); +DEFINE_bool(enable_threads_service, false, "Enable /threads"); + +DECLARE_int32(usercode_backup_threads); +DECLARE_bool(usercode_in_pthread); + +// NOTE: never make s_ncore extern const whose ctor seq against other +// compilation units is undefined. +const int s_ncore = sysconf(_SC_NPROCESSORS_ONLN); + +ServerOptions::ServerOptions() + : idle_timeout_sec(-1) + , nshead_service(NULL) + , thrift_service(NULL) + , mongo_service_adaptor(NULL) + , auth(NULL) + , server_owns_auth(false) + , interceptor(NULL) + , server_owns_interceptor(false) + , num_threads(8) + , max_concurrency(0) + , session_local_data_factory(NULL) + , reserved_session_local_data(0) + , thread_local_data_factory(NULL) + , reserved_thread_local_data(0) + , bthread_init_fn(NULL) + , bthread_init_args(NULL) + , bthread_init_count(0) + , internal_port(-1) + , has_builtin_services(true) + , force_ssl(false) + , socket_mode(SOCKET_MODE_TCP) + , baidu_master_service(NULL) + , http_master_service(NULL) + , health_reporter(NULL) + , rtmp_service(NULL) + , redis_service(NULL) + , bthread_tag(BTHREAD_TAG_DEFAULT) + , rpc_pb_message_factory(NULL) + , ignore_eovercrowded(false) { + if (s_ncore > 0) { + num_threads = s_ncore + 1; + } +} + +ServerSSLOptions* ServerOptions::mutable_ssl_options() { + if (!_ssl_options) { + _ssl_options.reset(new ServerSSLOptions); + } + return _ssl_options.get(); +} + +Server::MethodProperty::OpaqueParams::OpaqueParams() + : is_tabbed(false) + , allow_default_url(false) + , allow_http_body_to_pb(true) + , pb_bytes_to_base64(false) + , pb_single_repeated_to_array(false) { +} + +Server::MethodProperty::MethodProperty() + : is_builtin_service(false) + , own_method_status(false) + , http_url(NULL) + , service(NULL) + , method(NULL) + , status(NULL) + , ignore_eovercrowded(false) { +} + +static timeval GetUptime(void* arg/*start_time*/) { + return butil::microseconds_to_timeval(butil::cpuwide_time_us() - (intptr_t)arg); +} + +static void PrintStartTime(std::ostream& os, void* arg) { + // Print when this server was Server::Start()-ed. + time_t start_time = static_cast(arg)->last_start_time(); + struct tm timeinfo; + char buf[64]; + strftime(buf, sizeof(buf), "%Y/%m/%d-%H:%M:%S", + localtime_r(&start_time, &timeinfo)); + os << buf; +} + +static void PrintSupportedLB(std::ostream& os, void*) { + LoadBalancerExtension()->List(os, ' '); +} + +static void PrintSupportedNS(std::ostream& os, void*) { + NamingServiceExtension()->List(os, ' '); +} + +static void PrintSupportedProtocols(std::ostream& os, void*) { + std::vector protocols; + ListProtocols(&protocols); + for (size_t i = 0; i < protocols.size(); ++i) { + if (i != 0) { + os << ' '; + } + os << (protocols[i].name ? protocols[i].name : "(null)"); + } +} + +static void PrintSupportedCompressions(std::ostream& os, void*) { + std::vector compressors; + ListCompressHandler(&compressors); + for (size_t i = 0; i < compressors.size(); ++i) { + if (i != 0) { + os << ' '; + } + os << (compressors[i].name ? compressors[i].name : "(null)"); + } +} + +static void PrintSupportedChecksums(std::ostream& os, void*) { + std::vector handlers; + ListChecksumHandler(&handlers); + for (size_t i = 0; i < handlers.size(); ++i) { + if (i != 0) { + os << ' '; + } + os << (handlers[i].name ? handlers[i].name : "(null)"); + } +} + +static void PrintEnabledProfilers(std::ostream& os, void*) { + if (cpu_profiler_enabled) { + os << "cpu "; + } + if (IsHeapProfilerEnabled()) { + if (has_TCMALLOC_SAMPLE_PARAMETER()) { + os << "heap "; + } else { + os << "heap(no TCMALLOC_SAMPLE_PARAMETER in env) "; + } + } + os << "contention"; +} + +static bvar::PassiveStatus s_lb_st( + "rpc_load_balancer", PrintSupportedLB, NULL); + +static bvar::PassiveStatus s_ns_st( + "rpc_naming_service", PrintSupportedNS, NULL); + +static bvar::PassiveStatus s_proto_st( + "rpc_protocols", PrintSupportedProtocols, NULL); + +static bvar::PassiveStatus s_comp_st( + "rpc_compressions", PrintSupportedCompressions, NULL); + +static bvar::PassiveStatus s_cksum_st( + "rpc_checksums", PrintSupportedChecksums, NULL); + +static bvar::PassiveStatus s_prof_st( + "rpc_profilers", PrintEnabledProfilers, NULL); + +static int32_t GetConnectionCount(void* arg) { + ServerStatistics ss; + static_cast(arg)->GetStat(&ss); + return ss.connection_count; +} + +static int32_t GetServiceCount(void* arg) { + ServerStatistics ss; + static_cast(arg)->GetStat(&ss); + return ss.user_service_count; +} + +static int32_t GetBuiltinServiceCount(void* arg) { + ServerStatistics ss; + static_cast(arg)->GetStat(&ss); + return ss.builtin_service_count; +} + +static bvar::Vector GetSessionLocalDataCount(void* arg) { + bvar::Vector v; + SimpleDataPool::Stat s = + static_cast(arg)->session_local_data_pool()->stat(); + v[0] = s.ncreated - s.nfree; + v[1] = s.nfree; + return v; +} + +static int cast_no_barrier_int(void* arg) { + return butil::subtle::NoBarrier_Load(static_cast(arg)); +} + +std::string Server::ServerPrefix() const { + if(_options.server_info_name.empty()) { + return butil::string_printf("%s_%d", g_server_info_prefix, listen_address().port); + } else { + return std::string(g_server_info_prefix) + "_" + _options.server_info_name; + } +} + +void* Server::UpdateDerivedVars(void* arg) { + const int64_t start_us = butil::cpuwide_time_us(); + + Server* server = static_cast(arg); + const std::string prefix = server->ServerPrefix(); + std::vector conns; + std::vector internal_conns; + + server->_nerror_bvar.expose_as(prefix, "error"); + + server->_eps_bvar.expose_as(prefix, "eps"); + + server->_concurrency_bvar.expose_as(prefix, "concurrency"); + + bvar::PassiveStatus uptime_st( + prefix, "uptime", GetUptime, (void*)(intptr_t)start_us); + + bvar::PassiveStatus start_time_st( + prefix, "start_time", PrintStartTime, server); + + bvar::PassiveStatus nconn_st( + prefix, "connection_count", GetConnectionCount, server); + + bvar::PassiveStatus nservice_st( + prefix, "service_count", GetServiceCount, server); + + bvar::PassiveStatus nbuiltinservice_st( + prefix, "builtin_service_count", GetBuiltinServiceCount, server); + + bvar::PassiveStatus > nsessiondata_st( + GetSessionLocalDataCount, server); + if (server->session_local_data_pool()) { + nsessiondata_st.expose_as(prefix, "session_local_data_count"); + nsessiondata_st.set_vector_names("using,free"); + } + + std::string mprefix = prefix; + for (MethodMap::iterator it = server->_method_map.begin(); + it != server->_method_map.end(); ++it) { + // Not expose counters on builtin services. + if (!it->second.is_builtin_service) { + mprefix.resize(prefix.size()); + mprefix.push_back('_'); + bvar::to_underscored_name(&mprefix, it->second.method->full_name()); + it->second.status->Expose(mprefix); + } + } + if (server->options().baidu_master_service) { + server->options().baidu_master_service->Expose(prefix); + } + if (server->options().nshead_service) { + server->options().nshead_service->Expose(prefix); + } + +#ifdef ENABLE_THRIFT_FRAMED_PROTOCOL + if (server->options().thrift_service) { + server->options().thrift_service->Expose(prefix); + } +#endif + + int64_t last_time = butil::cpuwide_time_us(); + int consecutive_nosleep = 0; + while (1) { + const int64_t sleep_us = 1000000L + last_time - butil::cpuwide_time_us(); + if (sleep_us < 1000L) { + if (++consecutive_nosleep >= 2) { + consecutive_nosleep = 0; + LOG(WARNING) << __FUNCTION__ << " is too busy!"; + } + } else { + consecutive_nosleep = 0; + if (bthread_usleep(sleep_us) < 0) { + PLOG_IF(ERROR, errno != ESTOP) << "Fail to sleep"; + return NULL; + } + } + last_time = butil::cpuwide_time_us(); + + // Update stats of accepted sockets. + if (server->_am) { + server->_am->ListConnections(&conns); + } + if (server->_internal_am) { + server->_internal_am->ListConnections(&internal_conns); + } + const int64_t now_ms = butil::cpuwide_time_ms(); + for (size_t i = 0; i < conns.size(); ++i) { + SocketUniquePtr ptr; + if (Socket::Address(conns[i], &ptr) == 0) { + ptr->UpdateStatsEverySecond(now_ms); + } + } + for (size_t i = 0; i < internal_conns.size(); ++i) { + SocketUniquePtr ptr; + if (Socket::Address(internal_conns[i], &ptr) == 0) { + ptr->UpdateStatsEverySecond(now_ms); + } + } + } +} + +const std::string Server::ServiceProperty::service_name() const { + if (service) { + return butil::EnsureString(service->GetDescriptor()->full_name()); + } else if (restful_map) { + return restful_map->service_name(); + } + const static std::string s_unknown_name = ""; + return s_unknown_name; +} + +Server::Server(ProfilerLinker) + : _session_local_data_pool(NULL) + , _status(UNINITIALIZED) + , _builtin_service_count(0) + , _virtual_service_count(0) + , _failed_to_set_max_concurrency_of_method(false) + , _failed_to_set_ignore_eovercrowded(false) + , _am(NULL) + , _internal_am(NULL) + , _first_service(NULL) + , _tab_info_list(NULL) + , _global_restful_map(NULL) + , _last_start_time(0) + , _derivative_thread(INVALID_BTHREAD) + , _keytable_pool(NULL) + , _eps_bvar(&_nerror_bvar) + , _concurrency(0) + , _concurrency_bvar(cast_no_barrier_int, &_concurrency) + , _has_progressive_read_method(false) { + BAIDU_CASSERT(offsetof(Server, _concurrency) % 64 == 0, + Server_concurrency_must_be_aligned_by_cacheline); +} + +Server::~Server() { + Stop(0); + Join(); + ClearServices(); + FreeSSLContexts(); + delete _session_local_data_pool; + _session_local_data_pool = NULL; + + delete _options.nshead_service; + _options.nshead_service = NULL; + +#ifdef ENABLE_THRIFT_FRAMED_PROTOCOL + delete _options.thrift_service; + _options.thrift_service = NULL; +#endif + + delete _options.baidu_master_service; + _options.baidu_master_service = NULL; + + delete _options.http_master_service; + _options.http_master_service = NULL; + + delete _options.rpc_pb_message_factory; + _options.rpc_pb_message_factory = NULL; + + delete _am; + _am = NULL; + delete _internal_am; + _internal_am = NULL; + + delete _tab_info_list; + _tab_info_list = NULL; + + delete _global_restful_map; + _global_restful_map = NULL; + + if (!_options.pid_file.empty()) { + unlink(_options.pid_file.c_str()); + } + if (_options.server_owns_auth) { + delete _options.auth; + _options.auth = NULL; + } + if (_options.server_owns_interceptor) { + delete _options.interceptor; + _options.interceptor = NULL; + } + + delete _options.redis_service; + _options.redis_service = NULL; +} + +int Server::AddBuiltinServices() { + // Firstly add services shown in tabs. + if (AddBuiltinService(new StatusService)) { + LOG(ERROR) << "Fail to add StatusService"; + return -1; + } + if (AddBuiltinService(new VarsService)) { + LOG(ERROR) << "Fail to add VarsService"; + return -1; + } + if (AddBuiltinService(new ConnectionsService)) { + LOG(ERROR) << "Fail to add ConnectionsService"; + return -1; + } + if (AddBuiltinService(new FlagsService)) { + LOG(ERROR) << "Fail to add FlagsService"; + return -1; + } + if (AddBuiltinService(new RpczService)) { + LOG(ERROR) << "Fail to add RpczService"; + return -1; + } + if (AddBuiltinService(new HotspotsService)) { + LOG(ERROR) << "Fail to add HotspotsService"; + return -1; + } + if (AddBuiltinService(new IndexService)) { + LOG(ERROR) << "Fail to add IndexService"; + return -1; + } + + // Add other services. + if (AddBuiltinService(new VersionService(this))) { + LOG(ERROR) << "Fail to add VersionService"; + return -1; + } + if (AddBuiltinService(new HealthService)) { + LOG(ERROR) << "Fail to add HealthService"; + return -1; + } + if (AddBuiltinService(new ProtobufsService(this))) { + LOG(ERROR) << "Fail to add ProtobufsService"; + return -1; + } + if (AddBuiltinService(new BadMethodService)) { + LOG(ERROR) << "Fail to add BadMethodService"; + return -1; + } + if (AddBuiltinService(new ListService(this))) { + LOG(ERROR) << "Fail to add ListService"; + return -1; + } + if (AddBuiltinService(new PrometheusMetricsService)) { + LOG(ERROR) << "Fail to add MetricsService"; + return -1; + } + if (FLAGS_enable_threads_service && + AddBuiltinService(new ThreadsService)) { + LOG(ERROR) << "Fail to add ThreadsService"; + return -1; + } + if (AddBuiltinService(new MemoryService)) { + LOG(ERROR) << "Fail to add MemoryService"; + return -1; + } + +#if !BRPC_WITH_GLOG + if (AddBuiltinService(new VLogService)) { + LOG(ERROR) << "Fail to add VLogService"; + return -1; + } +#endif + + if (AddBuiltinService(new PProfService)) { + LOG(ERROR) << "Fail to add PProfService"; + return -1; + } + if (FLAGS_enable_dir_service && + AddBuiltinService(new DirService)) { + LOG(ERROR) << "Fail to add DirService"; + return -1; + } + if (AddBuiltinService(new BthreadsService)) { + LOG(ERROR) << "Fail to add BthreadsService"; + return -1; + } + if (AddBuiltinService(new IdsService)) { + LOG(ERROR) << "Fail to add IdsService"; + return -1; + } + if (AddBuiltinService(new SocketsService)) { + LOG(ERROR) << "Fail to add SocketsService"; + return -1; + } + if (AddBuiltinService(new GetFaviconService)) { + LOG(ERROR) << "Fail to add GetFaviconService"; + return -1; + } + if (AddBuiltinService(new GetJsService)) { + LOG(ERROR) << "Fail to add GetJsService"; + return -1; + } + if (AddBuiltinService(new GrpcHealthCheckService)) { + LOG(ERROR) << "Fail to add GrpcHealthCheckService"; + return -1; + } + return 0; +} + +bool is_http_protocol(const char* name) { + if (name[0] != 'h') { + return false; + } + return strcmp(name, "http") == 0 || strcmp(name, "h2") == 0; +} + +Acceptor* Server::BuildAcceptor() { + std::set whitelist; + for (butil::StringSplitter sp(_options.enabled_protocols.c_str(), ' '); + sp; ++sp) { + std::string protocol(sp.field(), sp.length()); + whitelist.insert(protocol); + } + const bool has_whitelist = !whitelist.empty(); + Acceptor* acceptor = new (std::nothrow) Acceptor(_keytable_pool); + if (NULL == acceptor) { + LOG(ERROR) << "Fail to new Acceptor"; + return NULL; + } + InputMessageHandler handler; + std::vector protocols; + ListProtocols(&protocols); + for (size_t i = 0; i < protocols.size(); ++i) { + if (protocols[i].process_request == NULL) { + // The protocol does not support server-side. + continue; + } + if (has_whitelist && + !is_http_protocol(protocols[i].name) && + !whitelist.erase(protocols[i].name)) { + // the protocol is not allowed to serve. + RPC_VLOG << "Skip protocol=" << protocols[i].name; + continue; + } + // `process_request' is required at server side + handler.parse = protocols[i].parse; + handler.process = protocols[i].process_request; + handler.verify = protocols[i].verify; + handler.arg = this; + handler.name = protocols[i].name; + if (acceptor->AddHandler(handler) != 0) { + LOG(ERROR) << "Fail to add handler into Acceptor(" + << acceptor << ')'; + delete acceptor; + return NULL; + } + } + if (!whitelist.empty()) { + std::ostringstream err; + err << "ServerOptions.enabled_protocols has unknown protocols=`"; + for (std::set::const_iterator it = whitelist.begin(); + it != whitelist.end(); ++it) { + err << *it << ' '; + } + err << '\''; + delete acceptor; + LOG(ERROR) << err.str(); + return NULL; + } + return acceptor; +} + +int Server::InitializeOnce() { + if (_status != UNINITIALIZED) { + return 0; + } + GlobalInitializeOrDie(); + + if (_status != UNINITIALIZED) { + return 0; + } + _status = READY; + return 0; +} + +int Server::InitALPNOptions(const ServerSSLOptions* options) { + if (options == nullptr) { + LOG(ERROR) << "Fail to init alpn options, ssl options is nullptr."; + return -1; + } + + std::string raw_protocol; + const std::string& alpns = options->alpns; + for (butil::StringSplitter split(alpns.data(), ','); split; ++split) { + butil::StringPiece alpn(split.field(), split.length()); + alpn.trim_spaces(); + + // Check protocol valid(exist and server support) + AdaptiveProtocolType protocol_type(alpn); + const Protocol* protocol = FindProtocol(protocol_type); + if (protocol == nullptr || !protocol->support_server()) { + LOG(ERROR) << "Server does not support alpn=" << alpn; + return -1; + } + raw_protocol.append(ALPNProtocolToString(protocol_type)); + } + _raw_alpns = std::move(raw_protocol); + return 0; +} + +static void* CreateServerTLS(const void* args) { + return static_cast(args)->CreateData(); +} +static void DestroyServerTLS(void* data, const void* void_factory) { + static_cast(void_factory)->DestroyData(data); +} + +struct BthreadInitArgs { + bool (*bthread_init_fn)(void* args); // default: NULL (do nothing) + void* bthread_init_args; // default: NULL + bool result; + bool done; + bool stop; + bthread_t th; +}; + +static void* BthreadInitEntry(void* void_args) { + BthreadInitArgs* args = (BthreadInitArgs*)void_args; + args->result = args->bthread_init_fn(args->bthread_init_args); + args->done = true; + while (!args->stop) { + bthread_usleep(1000); + } + return NULL; +} + +struct RevertServerStatus { + inline void operator()(Server* s) const { + if (s != NULL) { + s->Stop(0); + s->Join(); + } + } +}; + +static int get_port_from_fd(int fd) { + struct sockaddr_in addr; + socklen_t size = sizeof(addr); + if (getsockname(fd, (struct sockaddr*)&addr, &size) < 0) { + return -1; + } + return ntohs(addr.sin_port); +} + +bool Server::CreateConcurrencyLimiter(const AdaptiveMaxConcurrency& amc, + ConcurrencyLimiter** out) { + if (amc.type() == AdaptiveMaxConcurrency::UNLIMITED()) { + *out = NULL; + return true; + } + const ConcurrencyLimiter* cl = + ConcurrencyLimiterExtension()->Find(amc.type().c_str()); + if (cl == NULL) { + LOG(ERROR) << "Fail to find ConcurrencyLimiter by `" << amc.value() << "'"; + return false; + } + ConcurrencyLimiter* cl_copy = cl->New(amc); + if (cl_copy == NULL) { + LOG(ERROR) << "Fail to new ConcurrencyLimiter"; + return false; + } + *out = cl_copy; + return true; +} + + +static AdaptiveMaxConcurrency g_default_max_concurrency_of_method(0); +static bool g_default_ignore_eovercrowded(false); + +inline void copy_and_fill_server_options(ServerOptions& dst, const ServerOptions& src) { +// follow Server::~Server() +#define FREE_PTR_IF_NOT_REUSED(ptr) \ + if (dst.ptr != src.ptr) { \ + delete dst.ptr; \ + dst.ptr = NULL; \ + } + + if (&dst != &src) { + FREE_PTR_IF_NOT_REUSED(nshead_service); + + #ifdef ENABLE_THRIFT_FRAMED_PROTOCOL + FREE_PTR_IF_NOT_REUSED(thrift_service); + #endif + + FREE_PTR_IF_NOT_REUSED(baidu_master_service); + FREE_PTR_IF_NOT_REUSED(http_master_service); + FREE_PTR_IF_NOT_REUSED(rpc_pb_message_factory); + + if (dst.pid_file != src.pid_file && !dst.pid_file.empty()) { + unlink(dst.pid_file.c_str()); + } + + if (dst.server_owns_auth) { + FREE_PTR_IF_NOT_REUSED(auth); + } + + if (dst.server_owns_interceptor) { + FREE_PTR_IF_NOT_REUSED(interceptor); + } + + FREE_PTR_IF_NOT_REUSED(redis_service); + + // copy data members directly + dst = src; + } +#undef FREE_PTR_IF_NOT_REUSED + + // Create the resource if: + // 1. `dst` copied from user and user forgot to create + // 2. `dst` created by our + if (!dst.rpc_pb_message_factory) { + dst.rpc_pb_message_factory = new DefaultRpcPBMessageFactory(); + } +} + +int Server::StartInternal(const butil::EndPoint& endpoint, + const PortRange& port_range, + const ServerOptions *opt) { + std::unique_ptr revert_server(this); + if (_failed_to_set_max_concurrency_of_method) { + _failed_to_set_max_concurrency_of_method = false; + LOG(ERROR) << "previous call to MaxConcurrencyOf() was failed, " + "fix it before starting server"; + return -1; + } + if (_failed_to_set_ignore_eovercrowded) { + _failed_to_set_ignore_eovercrowded = false; + LOG(ERROR) << "previous call to IgnoreEovercrowdedOf() was failed, " + "fix it before starting server"; + return -1; + } + if (InitializeOnce() != 0) { + LOG(ERROR) << "Fail to initialize Server[" << version() << ']'; + return -1; + } + const Status st = status(); + if (st != READY) { + if (st == RUNNING) { + LOG(ERROR) << "Server[" << version() << "] is already running on " + << _listen_addr; + } else { + LOG(ERROR) << "Can't start Server[" << version() + << "] which is " << status_str(status()); + } + return -1; + } + + // Validate the user-provided ServerOptions BEFORE + // copy_and_fill_server_options below. This is important: + // copy_and_fill_server_options unconditionally transfers ownership of + // user-provided pointers (nshead_service, thrift_service, ...) into + // _options. If we instead validated against _options after the copy, + // a failed Start() would leave fake/invalid pointers behind in + // _options, and the NEXT Start() would attempt to `delete` them via + // FREE_PTR_IF_NOT_REUSED, crashing (see RdmaTest.server_option_invalid). + const ServerOptions default_opt; + const ServerOptions& real_opt = opt ? *opt : default_opt; + + if (!real_opt.h2_settings.IsValid(true/*log_error*/)) { + LOG(ERROR) << "Invalid h2_settings"; + return -1; + } + + if (real_opt.bthread_tag < BTHREAD_TAG_DEFAULT || + real_opt.bthread_tag >= FLAGS_task_group_ntags) { + LOG(ERROR) << "Fail to set tag " << real_opt.bthread_tag + << ", tag range is [" << BTHREAD_TAG_DEFAULT << ":" + << FLAGS_task_group_ntags << ")"; + return -1; + } + int ret = TransportFactory::ContextInitOrDie(real_opt.socket_mode, true, &real_opt); + if (ret != 0) { + LOG(ERROR) << "Fail to initialize transport context for server, ret=" << ret; + return -1; + } + + copy_and_fill_server_options(_options, real_opt); + + if (_options.http_master_service) { + // Check requirements for http_master_service: + // has "default_method" & request/response have no fields + const google::protobuf::ServiceDescriptor* sd = + _options.http_master_service->GetDescriptor(); + const google::protobuf::MethodDescriptor* md = + sd->FindMethodByName("default_method"); + if (md == NULL) { + LOG(ERROR) << "http_master_service must have a method named `default_method'"; + return -1; + } + if (md->input_type()->field_count() != 0) { + LOG(ERROR) << "The request type of http_master_service must have " + "no fields, actually " << md->input_type()->field_count(); + return -1; + } + if (md->output_type()->field_count() != 0) { + LOG(ERROR) << "The response type of http_master_service must have " + "no fields, actually " << md->output_type()->field_count(); + return -1; + } + } + + // CAUTION: + // Following code may run multiple times if this server is started and + // stopped more than once. Reuse or delete previous resources! + + if (_options.session_local_data_factory) { + if (_session_local_data_pool == NULL) { + _session_local_data_pool = + new (std::nothrow) SimpleDataPool(_options.session_local_data_factory); + if (NULL == _session_local_data_pool) { + LOG(ERROR) << "Fail to new SimpleDataPool"; + return -1; + } + } else { + _session_local_data_pool->Reset(_options.session_local_data_factory); + } + _session_local_data_pool->Reserve(_options.reserved_session_local_data); + } + + { + // Leak of `_keytable_pool' and others is by design. + // See comments in Server::Join() for details. + // Instruct LeakSanitizer to ignore the designated memory leak. + ANNOTATE_SCOPED_MEMORY_LEAK; + // Init _keytable_pool always. If the server was stopped before, the pool + // should be destroyed in Join(). + _keytable_pool = new bthread_keytable_pool_t; + if (bthread_keytable_pool_init(_keytable_pool) != 0) { + LOG(ERROR) << "Fail to init _keytable_pool"; + delete _keytable_pool; + _keytable_pool = NULL; + return -1; + } + } + + if (_options.thread_local_data_factory) { + _tl_options.thread_local_data_factory = _options.thread_local_data_factory; + if (bthread_key_create2(&_tl_options.tls_key, DestroyServerTLS, + _options.thread_local_data_factory) != 0) { + LOG(ERROR) << "Fail to create thread-local key"; + return -1; + } + if (_options.reserved_thread_local_data) { + bthread_keytable_pool_reserve(_keytable_pool, + _options.reserved_thread_local_data, + _tl_options.tls_key, + CreateServerTLS, + _options.thread_local_data_factory); + } + } else { + _tl_options = ThreadLocalOptions(); + } + + if (_options.bthread_init_count != 0 && + _options.bthread_init_fn != NULL) { + // Create some special bthreads to call the init functions. The + // bthreads will not quit until all bthreads finish the init function. + BthreadInitArgs* init_args + = new BthreadInitArgs[_options.bthread_init_count]; + size_t ncreated = 0; + for (size_t i = 0; i < _options.bthread_init_count; ++i, ++ncreated) { + init_args[i].bthread_init_fn = _options.bthread_init_fn; + init_args[i].bthread_init_args = _options.bthread_init_args; + init_args[i].result = false; + init_args[i].done = false; + init_args[i].stop = false; + bthread_attr_t tmp = BTHREAD_ATTR_NORMAL; + tmp.tag = _options.bthread_tag; + tmp.keytable_pool = _keytable_pool; + if (bthread_start_background( + &init_args[i].th, &tmp, BthreadInitEntry, &init_args[i]) != 0) { + break; + } + } + // Wait until all created bthreads finish the init function. + for (size_t i = 0; i < ncreated; ++i) { + while (!init_args[i].done) { + bthread_usleep(1000); + } + } + // Stop and join created bthreads. + for (size_t i = 0; i < ncreated; ++i) { + init_args[i].stop = true; + } + for (size_t i = 0; i < ncreated; ++i) { + bthread_join(init_args[i].th, NULL); + } + size_t num_failed_result = 0; + for (size_t i = 0; i < ncreated; ++i) { + if (!init_args[i].result) { + ++num_failed_result; + } + } + delete [] init_args; + if (ncreated != _options.bthread_init_count) { + LOG(ERROR) << "Fail to create " + << _options.bthread_init_count - ncreated << " bthreads"; + return -1; + } + if (num_failed_result != 0) { + LOG(ERROR) << num_failed_result << " bthread_init_fn failed"; + return -1; + } + } + + // Free last SSL contexts + FreeSSLContexts(); + if (_options.has_ssl_options()) { + + // Change ServerSSLOptions.alpns to _raw_alpns. + // AddCertificate function maybe access raw_alpns variable. + if (InitALPNOptions(_options.mutable_ssl_options()) != 0) { + return -1; + } + CertInfo& default_cert = _options.mutable_ssl_options()->default_cert; + if (default_cert.certificate.empty()) { + LOG(ERROR) << "default_cert is empty"; + return -1; + } + if (AddCertificate(default_cert) != 0) { + return -1; + } + _default_ssl_ctx = _ssl_ctx_map.begin()->second.ctx; + + const std::vector& certs = _options.mutable_ssl_options()->certs; + for (size_t i = 0; i < certs.size(); ++i) { + if (AddCertificate(certs[i]) != 0) { + return -1; + } + } + } else if (_options.force_ssl) { + LOG(ERROR) << "Fail to force SSL for all connections " + "without ServerOptions.ssl_options"; + return -1; + } + + _concurrency = 0; + + if (_options.has_builtin_services && + _builtin_service_count <= 0 && + AddBuiltinServices() != 0) { + LOG(ERROR) << "Fail to add builtin services"; + return -1; + } + // If a server is started/stopped for mutiple times and one of the options + // sets has_builtin_service to true, builtin services will be enabled for + // any later re-start. Check this case and report to user. + if (!_options.has_builtin_services && _builtin_service_count > 0) { + LOG(ERROR) << "A server started/stopped for multiple times must be " + "consistent on ServerOptions.has_builtin_services"; + return -1; + } + + // Prepare all restful maps + for (ServiceMap::const_iterator it = _fullname_service_map.begin(); + it != _fullname_service_map.end(); ++it) { + if (it->second.restful_map) { + it->second.restful_map->PrepareForFinding(); + } + } + if (_global_restful_map) { + _global_restful_map->PrepareForFinding(); + } + + if (_options.num_threads > 0) { + if (FLAGS_usercode_in_pthread) { + _options.num_threads += FLAGS_usercode_backup_threads; + } + if (_options.num_threads < BTHREAD_MIN_CONCURRENCY) { + _options.num_threads = BTHREAD_MIN_CONCURRENCY; + } + bthread_setconcurrency_by_tag(_options.num_threads, _options.bthread_tag); + } + + for (MethodMap::iterator it = _method_map.begin(); + it != _method_map.end(); ++it) { + if (it->second.is_builtin_service) { + it->second.status->SetConcurrencyLimiter(NULL); + } else { + const AdaptiveMaxConcurrency* amc = &it->second.max_concurrency; + if (amc->type() == AdaptiveMaxConcurrency::UNLIMITED()) { + amc = &_options.method_max_concurrency; + } + ConcurrencyLimiter* cl = NULL; + if (!CreateConcurrencyLimiter(*amc, &cl)) { + LOG(ERROR) << "Fail to create ConcurrencyLimiter for method"; + return -1; + } + it->second.status->SetConcurrencyLimiter(cl); + it->second.max_concurrency.SetConcurrencyLimiter(cl); + } + } + if (0 != SetServiceMaxConcurrency(_options.nshead_service)) { + return -1; + } +#ifdef ENABLE_THRIFT_FRAMED_PROTOCOL + if (0 != SetServiceMaxConcurrency(_options.thrift_service)) { + return -1; + } +#endif + + + // Create listening ports + if (port_range.min_port > port_range.max_port) { + LOG(ERROR) << "Invalid port_range=[" << port_range.min_port << '-' + << port_range.max_port << ']'; + return -1; + } + if (butil::is_endpoint_extended(endpoint) && + (port_range.min_port != endpoint.port || port_range.max_port != endpoint.port)) { + LOG(ERROR) << "Only IPv4 address supports port range feature"; + return -1; + } + _listen_addr = endpoint; + for (int port = port_range.min_port; port <= port_range.max_port; ++port) { + _listen_addr.port = port; + butil::fd_guard sockfd(tcp_listen(_listen_addr)); + if (sockfd < 0) { + if (port != port_range.max_port) { // not the last port, try next + continue; + } + if (port_range.min_port != port_range.max_port) { + LOG(ERROR) << "Fail to listen " << _listen_addr.ip + << ":[" << port_range.min_port << '-' + << port_range.max_port << ']'; + } else { + LOG(ERROR) << "Fail to listen " << _listen_addr; + } + return -1; + } + if (_listen_addr.port == 0) { + // port=0 makes kernel dynamically select a port from + // https://en.wikipedia.org/wiki/Ephemeral_port + _listen_addr.port = get_port_from_fd(sockfd); + if (_listen_addr.port <= 0) { + LOG(ERROR) << "Fail to get port from fd=" << sockfd; + return -1; + } + } + if (_am == NULL) { + _am = BuildAcceptor(); + if (NULL == _am) { + LOG(ERROR) << "Fail to build acceptor"; + return -1; + } + _am->_socket_mode = _options.socket_mode; + _am->_bthread_tag = _options.bthread_tag; + } + // Set `_status' to RUNNING before accepting connections + // to prevent requests being rejected as ELOGOFF + _status = RUNNING; + time(&_last_start_time); + GenerateVersionIfNeeded(); + g_running_server_count.fetch_add(1, butil::memory_order_relaxed); + + // Pass ownership of `sockfd' to `_am' + if (_am->StartAccept(sockfd, _options.idle_timeout_sec, + _default_ssl_ctx, + _options.force_ssl) != 0) { + LOG(ERROR) << "Fail to start acceptor"; + return -1; + } + sockfd.release(); + break; // stop trying + } + if (_options.internal_port >= 0 && _options.has_builtin_services) { + if (_options.internal_port == _listen_addr.port) { + LOG(ERROR) << "ServerOptions.internal_port=" << _options.internal_port + << " is same with port=" << _listen_addr.port << " to Start()"; + return -1; + } + if (_options.internal_port == 0) { + LOG(ERROR) << "ServerOptions.internal_port cannot be 0, which" + " allocates a dynamic and probabaly unfiltered port," + " against the purpose of \"being internal\"."; + return -1; + } + if (butil::is_endpoint_extended(endpoint)) { + LOG(ERROR) << "internal_port is available in IPv4 address only"; + return -1; + } + + butil::EndPoint internal_point = _listen_addr; + internal_point.port = _options.internal_port; + butil::fd_guard sockfd(tcp_listen(internal_point)); + if (sockfd < 0) { + LOG(ERROR) << "Fail to listen " << internal_point << " (internal)"; + return -1; + } + if (NULL == _internal_am) { + _internal_am = BuildAcceptor(); + if (NULL == _internal_am) { + LOG(ERROR) << "Fail to build internal acceptor"; + return -1; + } + } + // Pass ownership of `sockfd' to `_internal_am' + if (_internal_am->StartAccept(sockfd, _options.idle_timeout_sec, + _default_ssl_ctx, + false) != 0) { + LOG(ERROR) << "Fail to start internal_acceptor"; + return -1; + } + sockfd.release(); + } + + PutPidFileIfNeeded(); + + // Launch _derivative_thread. + CHECK_EQ(INVALID_BTHREAD, _derivative_thread); + bthread_attr_t tmp = BTHREAD_ATTR_NORMAL; + tmp.tag = _options.bthread_tag; + bthread_attr_set_name(&tmp, "UpdateDerivedVars"); + if (bthread_start_background(&_derivative_thread, &tmp, + UpdateDerivedVars, this) != 0) { + LOG(ERROR) << "Fail to create _derivative_thread"; + return -1; + } + + // Print tips to server launcher. + if (butil::is_endpoint_extended(_listen_addr)) { + const char* builtin_msg = _options.has_builtin_services ? " with builtin service" : ""; + LOG(INFO) << "Server[" << version() << "] is serving on " << _listen_addr + << builtin_msg << '.'; + //TODO add TrackMe support + } else { + int http_port = _listen_addr.port; + std::ostringstream server_info; + server_info << "Server[" << version() << "] is serving on port=" + << _listen_addr.port; + if (_options.internal_port >= 0 && _options.has_builtin_services) { + http_port = _options.internal_port; + server_info << " and internal_port=" << _options.internal_port; + } + LOG(INFO) << server_info.str() << '.'; + + if (_options.has_builtin_services) { + LOG(INFO) << "Check out http://" << butil::my_hostname() << ':' + << http_port << " in web browser."; + } else { + LOG(WARNING) << "Builtin services are disabled according to " + "ServerOptions.has_builtin_services"; + } + // For trackme reporting + SetTrackMeAddress(butil::EndPoint(butil::my_ip(), http_port)); + } + revert_server.release(); + return 0; +} + +int Server::Start(const butil::EndPoint& endpoint, const ServerOptions* opt) { + return StartInternal( + endpoint, PortRange(endpoint.port, endpoint.port), opt); +} + +int Server::Start(const char* ip_port_str, const ServerOptions* opt) { + butil::EndPoint point; + if (str2endpoint(ip_port_str, &point) != 0 && + hostname2endpoint(ip_port_str, &point) != 0) { + LOG(ERROR) << "Invalid address=`" << ip_port_str << '\''; + return -1; + } + return Start(point, opt); +} + +int Server::Start(int port, const ServerOptions* opt) { + if (port < 0 || port > 65535) { + LOG(ERROR) << "Invalid port=" << port; + return -1; + } + return Start(butil::EndPoint(butil::IP_ANY, port), opt); +} + +int Server::Start(const char* ip_str, PortRange port_range, + const ServerOptions *opt) { + butil::ip_t ip; + if (butil::str2ip(ip_str, &ip) != 0 && + butil::hostname2ip(ip_str, &ip) != 0) { + LOG(ERROR) << "Invalid address=`" << ip_str << '\''; + return -1; + } + return StartInternal(butil::EndPoint(ip, 0), port_range, opt); +} + +int Server::Start(PortRange port_range, const ServerOptions* opt) { + return StartInternal(butil::EndPoint(butil::IP_ANY, 0), port_range, opt); +} + +int Server::Stop(int timeout_ms) { + if (_status != RUNNING) { + return -1; + } + _status = STOPPING; + + LOG(INFO) << "Server[" << version() << "] is going to quit"; + + if (_am) { + _am->StopAccept(timeout_ms); + } + if (_internal_am) { + // TODO: calculate timeout? + _internal_am->StopAccept(timeout_ms); + } + return 0; +} + +// NOTE: Join() can happen before Stop(). +int Server::Join() { + if (_status != RUNNING && _status != STOPPING) { + return -1; + } + if (_am) { + _am->Join(); + } + if (_internal_am) { + _internal_am->Join(); + } + + if (_session_local_data_pool) { + // We can't delete the pool right here because there's a bvar watching + // this pool in _derivative_thread which does not quit yet. + _session_local_data_pool->Reset(NULL); + } + + if (_keytable_pool) { + // Destroy _keytable_pool to delete keytables inside. This has to be + // done here (before leaving Join) because it's legal for users to + // delete bthread keys after Join which makes related objects + // in KeyTables undeletable anymore and leaked. + CHECK_EQ(0, bthread_keytable_pool_destroy(_keytable_pool)); + // TODO: Can't delete _keytable_pool which may be accessed by + // still-running bthreads (created by the server). The memory is + // leaked but servers are unlikely to be started/stopped frequently, + // the leak is acceptable in most scenarios. + _keytable_pool = NULL; + } + + // Delete tls_key as well since we don't need it anymore. + if (_tl_options.tls_key != INVALID_BTHREAD_KEY) { + CHECK_EQ(0, bthread_key_delete(_tl_options.tls_key)); + _tl_options.tls_key = INVALID_BTHREAD_KEY; + } + + // Have to join _derivative_thread, which may assume that server is running + // and services in server are not mutated, otherwise data race happens + // between Add/RemoveService after Join() and the thread. + if (_derivative_thread != INVALID_BTHREAD) { + bthread_stop(_derivative_thread); + bthread_join(_derivative_thread, NULL); + _derivative_thread = INVALID_BTHREAD; + } + + g_running_server_count.fetch_sub(1, butil::memory_order_relaxed); + _status = READY; + return 0; +} + +int Server::AddServiceInternal(google::protobuf::Service* service, + bool is_builtin_service, + const ServiceOptions& svc_opt) { + if (NULL == service) { + LOG(ERROR) << "Parameter[service] is NULL!"; + return -1; + } + const google::protobuf::ServiceDescriptor* sd = service->GetDescriptor(); + if (sd->method_count() == 0) { + LOG(ERROR) << "service=" << sd->full_name() + << " does not have any method."; + return -1; + } + + if (InitializeOnce() != 0) { + LOG(ERROR) << "Fail to initialize Server[" << version() << ']'; + return -1; + } + if (status() != READY) { + LOG(ERROR) << "Can't add service=" << sd->full_name() << " to Server[" + << version() << "] which is " << status_str(status()); + return -1; + } + + if (_fullname_service_map.seek(sd->full_name()) != NULL) { + LOG(ERROR) << "service=" << sd->full_name() << " already exists"; + return -1; + } + ServiceProperty* old_ss = _service_map.seek(sd->name()); + if (old_ss != NULL) { + // names conflict. + LOG(ERROR) << "Conflict service name between " + << sd->full_name() << " and " + << old_ss->service_name(); + return -1; + } + + // defined `option (idl_support) = true' or not. + const bool is_idl_support = sd->file()->options().GetExtension(idl_support); + + Tabbed* tabbed = dynamic_cast(service); + for (int i = 0; i < sd->method_count(); ++i) { + const google::protobuf::MethodDescriptor* md = sd->method(i); + MethodProperty mp; + mp.is_builtin_service = is_builtin_service; + mp.own_method_status = true; + mp.params.is_tabbed = !!tabbed; + mp.params.allow_default_url = svc_opt.allow_default_url; + mp.params.allow_http_body_to_pb = svc_opt.allow_http_body_to_pb; + mp.params.pb_bytes_to_base64 = svc_opt.pb_bytes_to_base64; + mp.params.pb_single_repeated_to_array = svc_opt.pb_single_repeated_to_array; + mp.params.enable_progressive_read = svc_opt.enable_progressive_read; + if (mp.params.enable_progressive_read) { + _has_progressive_read_method = true; + } + mp.service = service; + mp.method = md; + mp.status = new MethodStatus; + _method_map[butil::EnsureString(md->full_name())] = mp; + if (is_idl_support && sd->name() != sd->full_name()/*has ns*/) { + MethodProperty mp2 = mp; + mp2.own_method_status = false; + // have to map service_name + method_name as well because ubrpc + // does not send the namespace before service_name. + std::string full_name_wo_ns; + full_name_wo_ns.reserve(sd->name().size() + 1 + md->name().size()); + full_name_wo_ns.append(sd->name()); + full_name_wo_ns.push_back('.'); + full_name_wo_ns.append(md->name()); + if (_method_map.seek(full_name_wo_ns) == NULL) { + _method_map[full_name_wo_ns] = mp2; + } else { + LOG(ERROR) << '`' << full_name_wo_ns << "' already exists"; + RemoveMethodsOf(service); + return -1; + } + } + } + + const ServiceProperty ss = { + is_builtin_service, svc_opt.ownership, service, NULL }; + _fullname_service_map[butil::EnsureString(sd->full_name())] = ss; + _service_map[butil::EnsureString(sd->name())] = ss; + if (is_builtin_service) { + ++_builtin_service_count; + } else { + if (_first_service == NULL) { + _first_service = service; + } + } + + butil::StringPiece restful_mappings = svc_opt.restful_mappings; + restful_mappings.trim_spaces(); + if (!restful_mappings.empty()) { + // Parse the mappings. + std::vector mappings; + if (!ParseRestfulMappings(restful_mappings, &mappings)) { + LOG(ERROR) << "Fail to parse mappings `" << restful_mappings << '\''; + RemoveService(service); + return -1; + } + if (mappings.empty()) { + // we already trimmed at the beginning, this is impossible. + LOG(ERROR) << "Impossible: Nothing in restful_mappings"; + RemoveService(service); + return -1; + } + + // Due the flexibility of URL matching, it's almost impossible to + // dispatch all kinds of URL to different methods *efficiently* just + // inside the HTTP protocol impl. We would like to match most- + // frequently-used URLs(/Service/Method) fastly and match more complex + // URLs inside separate functions. + // The trick is adding some entries inside the service maps without + // real services, mapping from the first component in the URL to a + // RestfulMap which does the complex matchings. For example: + // "/v1/send => SendFn, /v1/recv => RecvFn, /v2/check => CheckFn" + // We'll create 2 entries in service maps (_fullname_service_map and + // _service_map) mapping from "v1" and "v2" to 2 different RestfulMap + // respectively. When the URL is accessed, we extract the first + // component, find the RestfulMap and do url matchings. Regular url + // handling is not affected. + for (size_t i = 0; i < mappings.size(); ++i) { + const std::string full_method_name = + butil::EnsureString(sd->full_name()) + "." + mappings[i].method_name; + MethodProperty* mp = _method_map.seek(full_method_name); + if (mp == NULL) { + LOG(ERROR) << "Unknown method=`" << full_method_name << '\''; + RemoveService(service); + return -1; + } + + const std::string& svc_name = mappings[i].path.service_name; + if (svc_name.empty()) { + if (_global_restful_map == NULL) { + _global_restful_map = new RestfulMap(""); + } + MethodProperty::OpaqueParams params; + params.is_tabbed = !!tabbed; + params.allow_default_url = svc_opt.allow_default_url; + params.allow_http_body_to_pb = svc_opt.allow_http_body_to_pb; + params.pb_bytes_to_base64 = svc_opt.pb_bytes_to_base64; + params.pb_single_repeated_to_array = svc_opt.pb_single_repeated_to_array; + if (!_global_restful_map->AddMethod( + mappings[i].path, service, params, + mappings[i].method_name, mp->status)) { + LOG(ERROR) << "Fail to map `" << mappings[i].path + << "' to `" << full_method_name << '\''; + RemoveService(service); + return -1; + } + if (mp->http_url == NULL) { + mp->http_url = new std::string(mappings[i].path.to_string()); + } else { + if (!mp->http_url->empty()) { + mp->http_url->append(" @"); + } + mp->http_url->append(mappings[i].path.to_string()); + } + continue; + } + ServiceProperty* sp = _fullname_service_map.seek(svc_name); + ServiceProperty* sp2 = _service_map.seek(svc_name); + if (((!!sp) != (!!sp2)) || + (sp != NULL && sp->service != sp2->service)) { + LOG(ERROR) << "Impossible: _fullname_service and _service_map are" + " inconsistent before inserting " << svc_name; + RemoveService(service); + return -1; + } + RestfulMap* m = NULL; + if (sp == NULL) { + m = new RestfulMap(mappings[i].path.service_name); + } else { + m = sp->restful_map; + } + MethodProperty::OpaqueParams params; + params.is_tabbed = !!tabbed; + params.allow_default_url = svc_opt.allow_default_url; + params.allow_http_body_to_pb = svc_opt.allow_http_body_to_pb; + params.pb_bytes_to_base64 = svc_opt.pb_bytes_to_base64; + params.pb_single_repeated_to_array = svc_opt.pb_single_repeated_to_array; + if (!m->AddMethod(mappings[i].path, service, params, + mappings[i].method_name, mp->status)) { + LOG(ERROR) << "Fail to map `" << mappings[i].path << "' to `" + << sd->full_name() << '.' << mappings[i].method_name + << '\''; + if (sp == NULL) { + delete m; + } + RemoveService(service); + return -1; + } + if (mp->http_url == NULL) { + mp->http_url = new std::string(mappings[i].path.to_string()); + } else { + if (!mp->http_url->empty()) { + mp->http_url->append(" @"); + } + mp->http_url->append(mappings[i].path.to_string()); + } + if (sp == NULL) { + ServiceProperty ss = + { false, SERVER_DOESNT_OWN_SERVICE, NULL, m }; + _fullname_service_map[svc_name] = ss; + _service_map[svc_name] = ss; + ++_virtual_service_count; + } + } + } + + if (tabbed) { + if (_tab_info_list == NULL) { + _tab_info_list = new TabInfoList; + } + const size_t last_size = _tab_info_list->size(); + tabbed->GetTabInfo(_tab_info_list); + const size_t cur_size = _tab_info_list->size(); + for (size_t i = last_size; i != cur_size; ++i) { + const TabInfo& info = (*_tab_info_list)[i]; + if (!info.valid()) { + LOG(ERROR) << "Invalid TabInfo: path=" << info.path + << " tab_name=" << info.tab_name; + _tab_info_list->resize(last_size); + RemoveService(service); + return -1; + } + } + } + return 0; +} + +ServiceOptions::ServiceOptions() + : ownership(SERVER_DOESNT_OWN_SERVICE) + , allow_default_url(false) + , allow_http_body_to_pb(true) +#ifdef BAIDU_INTERNAL + , pb_bytes_to_base64(false) +#else + , pb_bytes_to_base64(true) +#endif + , pb_single_repeated_to_array(false) + , enable_progressive_read(false) + {} + +int Server::AddService(google::protobuf::Service* service, + ServiceOwnership ownership) { + ServiceOptions options; + options.ownership = ownership; + return AddServiceInternal(service, false, options); +} + +int Server::AddService(google::protobuf::Service* service, + ServiceOwnership ownership, + const butil::StringPiece& restful_mappings, + bool allow_default_url) { + ServiceOptions options; + options.ownership = ownership; + // TODO: This is weird + options.restful_mappings = restful_mappings.as_string(); + options.allow_default_url = allow_default_url; + return AddServiceInternal(service, false, options); +} + +int Server::AddService(google::protobuf::Service* service, + const ServiceOptions& options) { + return AddServiceInternal(service, false, options); +} + +int Server::AddBuiltinService(google::protobuf::Service* service) { + ServiceOptions options; + options.ownership = SERVER_OWNS_SERVICE; + int rc = AddServiceInternal(service, true, options); + if (rc != 0) { + // AddServiceInternal does not take ownership of `service' on failure: + // for builtin services the only failure paths (name/fullname conflict) + // return before the service is inserted into any map. Delete it here to + // avoid leaking the object allocated by the caller. + delete service; + } + return rc; +} + +void Server::RemoveMethodsOf(google::protobuf::Service* service) { + const google::protobuf::ServiceDescriptor* sd = service->GetDescriptor(); + const bool is_idl_support = sd->file()->options().GetExtension(idl_support); + std::string full_name_wo_ns; + for (int i = 0; i < sd->method_count(); ++i) { + const google::protobuf::MethodDescriptor* md = sd->method(i); + MethodProperty* mp = _method_map.seek(md->full_name()); + if (is_idl_support) { + full_name_wo_ns.clear(); + full_name_wo_ns.reserve(sd->name().size() + 1 + md->name().size()); + full_name_wo_ns.append(sd->name()); + full_name_wo_ns.push_back('.'); + full_name_wo_ns.append(md->name()); + _method_map.erase(full_name_wo_ns); + } + if (mp == NULL) { + LOG(ERROR) << "Fail to find method=" << md->full_name(); + continue; + } + if (mp->http_url) { + butil::StringSplitter at_sp(mp->http_url->c_str(), '@'); + for (; at_sp; ++at_sp) { + butil::StringPiece path(at_sp.field(), at_sp.length()); + path.trim_spaces(); + butil::StringSplitter slash_sp( + path.data(), path.data() + path.size(), '/'); + if (slash_sp == NULL) { + LOG(ERROR) << "Invalid http_url=" << *mp->http_url; + break; + } + butil::StringPiece v_svc_name(slash_sp.field(), slash_sp.length()); + const ServiceProperty* vsp = FindServicePropertyByName(v_svc_name); + if (vsp == NULL) { + if (_global_restful_map) { + std::string path_str; + path.CopyToString(&path_str); + if (_global_restful_map->RemoveByPathString(path_str)) { + continue; + } + } + LOG(ERROR) << "Impossible: service=" << v_svc_name + << " for restful_map does not exist"; + break; + } + std::string path_str; + path.CopyToString(&path_str); + if (!vsp->restful_map->RemoveByPathString(path_str)) { + LOG(ERROR) << "Fail to find path=" << path + << " in restful_map of service=" << v_svc_name; + } + } + delete mp->http_url; + } + + if (mp->own_method_status) { + delete mp->status; + } + _method_map.erase(md->full_name()); + } +} + +int Server::RemoveService(google::protobuf::Service* service) { + if (NULL == service) { + LOG(ERROR) << "Parameter[service] is NULL"; + return -1; + } + if (status() != READY) { + LOG(ERROR) << "Can't remove service=" + << service->GetDescriptor()->full_name() << " from Server[" + << version() << "] which is " << status_str(status()); + return -1; + } + + const google::protobuf::ServiceDescriptor* sd = service->GetDescriptor(); + ServiceProperty* ss = _fullname_service_map.seek(butil::EnsureString(sd->full_name())); + if (ss == NULL) { + RPC_VLOG << "Fail to find service=" << sd->full_name(); + return -1; + } + RemoveMethodsOf(service); + if (ss->ownership == SERVER_OWNS_SERVICE) { + delete ss->service; + } + const bool is_builtin_service = ss->is_builtin_service; + _fullname_service_map.erase(sd->full_name()); + _service_map.erase(sd->name()); + + // Note: ss is invalidated. + if (is_builtin_service) { + --_builtin_service_count; + } else { + if (_first_service == service) { + _first_service = NULL; + } + } + return 0; +} + +void Server::ClearServices() { + if (status() != READY) { + LOG_IF(ERROR, status() != UNINITIALIZED) + << "Can't clear services from Server[" << version() + << "] which is " << status_str(status()); + return; + } + for (ServiceMap::const_iterator it = _fullname_service_map.begin(); + it != _fullname_service_map.end(); ++it) { + if (it->second.ownership == SERVER_OWNS_SERVICE) { + delete it->second.service; + } + delete it->second.restful_map; + } + for (MethodMap::const_iterator it = _method_map.begin(); + it != _method_map.end(); ++it) { + if (it->second.own_method_status) { + delete it->second.status; + } + delete it->second.http_url; + } + _fullname_service_map.clear(); + _service_map.clear(); + _method_map.clear(); + _builtin_service_count = 0; + _virtual_service_count = 0; + _first_service = NULL; +} + +google::protobuf::Service* Server::FindServiceByFullName( + const butil::StringPiece& full_name) const { + ServiceProperty* ss = _fullname_service_map.seek(full_name); + return (ss ? ss->service : NULL); +} + +google::protobuf::Service* Server::FindServiceByName( + const butil::StringPiece& name) const { + ServiceProperty* ss = _service_map.seek(name); + return (ss ? ss->service : NULL); +} + +void Server::GetStat(ServerStatistics* stat) const { + stat->connection_count = 0; + if (_am) { + stat->connection_count += _am->ConnectionCount(); + } + if (_internal_am) { + stat->connection_count += _internal_am->ConnectionCount(); + } + stat->user_service_count = service_count(); + stat->builtin_service_count = builtin_service_count(); +} + +void Server::ListServices(std::vector *services) { + if (!services) { + return; + } + services->clear(); + services->reserve(service_count()); + for (ServiceMap::const_iterator it = _fullname_service_map.begin(); + it != _fullname_service_map.end(); ++it) { + if (it->second.is_user_service()) { + services->push_back(it->second.service); + } + } +} + +void Server::GenerateVersionIfNeeded() { + if (!_version.empty()) { + return; + } + int extra_count = !!_options.nshead_service + !!_options.rtmp_service + + !!_options.thrift_service + !!_options.redis_service; + _version.reserve((extra_count + service_count()) * 20); + for (ServiceMap::const_iterator it = _fullname_service_map.begin(); + it != _fullname_service_map.end(); ++it) { + if (it->second.is_user_service()) { + if (!_version.empty()) { + _version.push_back('+'); + } + _version.append(butil::class_name_str(*it->second.service)); + } + } + if (_options.nshead_service) { + if (!_version.empty()) { + _version.push_back('+'); + } + _version.append(butil::class_name_str(*_options.nshead_service)); + } + +#ifdef ENABLE_THRIFT_FRAMED_PROTOCOL + if (_options.thrift_service) { + if (!_version.empty()) { + _version.push_back('+'); + } + _version.append(butil::class_name_str(*_options.thrift_service)); + } +#endif + + if (_options.rtmp_service) { + if (!_version.empty()) { + _version.push_back('+'); + } + _version.append(butil::class_name_str(*_options.rtmp_service)); + } + + if (_options.redis_service) { + if (!_version.empty()) { + _version.push_back('+'); + } + _version.append(butil::class_name_str(*_options.redis_service)); + } +} + +void Server::PutPidFileIfNeeded() { + if (_options.pid_file.empty()) { + return; + } + RPC_VLOG << "pid_file = " << _options.pid_file; + // Recursively create directory + for (size_t pos = _options.pid_file.find('/'); pos != std::string::npos; + pos = _options.pid_file.find('/', pos + 1)) { + std::string dir_name =_options.pid_file.substr(0, pos + 1); + int rc = mkdir(dir_name.c_str(), + S_IFDIR | S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP); + if (rc != 0 && errno != EEXIST +#if defined(OS_MACOSX) + && errno != EISDIR +#endif + ) { + PLOG(WARNING) << "Fail to create " << dir_name; + _options.pid_file.clear(); + return; + } + } + int fd = open(_options.pid_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0) { + LOG(WARNING) << "Fail to open " << _options.pid_file; + _options.pid_file.clear(); + return; + } + char buf[32]; + int nw = snprintf(buf, sizeof(buf), "%lld", (long long)getpid()); + CHECK_EQ(nw, write(fd, buf, nw)); + CHECK_EQ(0, close(fd)); +} + +void Server::RunUntilAskedToQuit() { + while (!IsAskedToQuit()) { + bthread_usleep(1000000L); + } + Stop(0/*not used now*/); + Join(); +} + +void* thread_local_data() { + const Server::ThreadLocalOptions* tl_options = + static_cast(bthread_get_assigned_data()); + if (tl_options == NULL) { // not in server threads. + return NULL; + } + if (BAIDU_UNLIKELY(tl_options->thread_local_data_factory == NULL)) { + CHECK(false) << "The protocol impl. may not set tls correctly"; + return NULL; + } + void* data = bthread_getspecific(tl_options->tls_key); + if (data == NULL) { + data = tl_options->thread_local_data_factory->CreateData(); + if (data != NULL) { + CHECK_EQ(0, bthread_setspecific(tl_options->tls_key, data)); + } + } + return data; +} + +inline void tabs_li(std::ostream& os, const char* link, + const char* tab_name, const char* current_tab_name) { + os << "\n"; +} + +void Server::PrintTabsBody(std::ostream& os, + const char* current_tab_name) const { + os << "
    \n"; + if (_tab_info_list) { + for (size_t i = 0; i < _tab_info_list->size(); ++i) { + const TabInfo& info = (*_tab_info_list)[i]; + tabs_li(os, info.path.c_str(), info.tab_name.c_str(), + current_tab_name); + } + } + os << "
  • ?
  • \n
\n" + "
"; // placeholder +} + +static pthread_mutex_t g_dummy_server_mutex = PTHREAD_MUTEX_INITIALIZER; +static Server* g_dummy_server = NULL; + +int StartDummyServerAt(int port, ProfilerLinker) { + if (port < 0 || port >= 65536) { + LOG(ERROR) << "Invalid port=" << port; + return -1; + } + if (g_dummy_server == NULL) { // (1) + BAIDU_SCOPED_LOCK(g_dummy_server_mutex); + if (g_dummy_server == NULL) { + Server* dummy_server = new Server; + dummy_server->set_version(butil::string_printf( + "DummyServerOf(%s)", GetProgramName())); + ServerOptions options; + options.num_threads = 0; + options.bthread_tag = bthread_self_tag(); + if (dummy_server->Start(port, &options) != 0) { + LOG(ERROR) << "Fail to start dummy_server at port=" << port; + return -1; + } + // (1) may see uninitialized dummy_server due to relaxed memory + // fencing, but we only expose a function to test existence + // of g_dummy_server, everything should be fine. + g_dummy_server = dummy_server; + return 0; + } + } + LOG(ERROR) << "Already have dummy_server at port=" + << g_dummy_server->listen_address().port; + return -1; +} + +bool IsDummyServerRunning() { + return g_dummy_server != NULL; +} + +const Server::MethodProperty* +Server::FindMethodPropertyByFullName(const butil::StringPiece& fullname) const { + return _method_map.seek(fullname); +} + +const Server::MethodProperty* +Server::FindMethodPropertyByFullName(const butil::StringPiece& service_name/*full*/, + const butil::StringPiece& method_name) const { + const size_t fullname_len = service_name.size() + 1 + method_name.size(); + if (fullname_len <= 256) { + // Avoid allocation in most cases. + char buf[fullname_len]; + memcpy(buf, service_name.data(), service_name.size()); + buf[service_name.size()] = '.'; + memcpy(buf + service_name.size() + 1, method_name.data(), method_name.size()); + return FindMethodPropertyByFullName(butil::StringPiece(buf, fullname_len)); + } else { + std::string full_method_name; + full_method_name.reserve(fullname_len); + full_method_name.append(service_name.data(), service_name.size()); + full_method_name.push_back('.'); + full_method_name.append(method_name.data(), method_name.size()); + return FindMethodPropertyByFullName(full_method_name); + } +} + +const Server::MethodProperty* +Server::FindMethodPropertyByNameAndIndex(const butil::StringPiece& service_name, + int method_index) const { + const Server::ServiceProperty* sp = FindServicePropertyByName(service_name); + if (NULL == sp) { + return NULL; + } + const google::protobuf::ServiceDescriptor* sd = sp->service->GetDescriptor(); + if (method_index < 0 || method_index >= sd->method_count()) { + return NULL; + } + const google::protobuf::MethodDescriptor* method = sd->method(method_index); + return FindMethodPropertyByFullName(method->full_name()); +} + +const Server::ServiceProperty* +Server::FindServicePropertyByFullName(const butil::StringPiece& fullname) const { + return _fullname_service_map.seek(fullname); +} + +const Server::ServiceProperty* +Server::FindServicePropertyByName(const butil::StringPiece& name) const { + return _service_map.seek(name); +} + +int Server::AddCertificate(const CertInfo& cert) { + if (!_options.has_ssl_options()) { + LOG(ERROR) << "ServerOptions.ssl_options is not configured yet"; + return -1; + } + std::string cert_key(cert.certificate); + cert_key.append(cert.private_key); + if (_ssl_ctx_map.seek(cert_key) != NULL) { + LOG(WARNING) << cert << " already exists"; + return 0; + } + + SSLContext ssl_ctx; + ssl_ctx.filters = cert.sni_filters; + ssl_ctx.ctx = std::make_shared(); + SSL_CTX* raw_ctx = CreateServerSSLContext( + cert.certificate, cert.private_key, + _options.ssl_options(), &_raw_alpns, &ssl_ctx.filters); + if (raw_ctx == NULL) { + return -1; + } + ssl_ctx.ctx->raw_ctx = raw_ctx; + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + SSL_CTX_set_tlsext_servername_callback(ssl_ctx.ctx->raw_ctx, SSLSwitchCTXByHostname); + SSL_CTX_set_tlsext_servername_arg(ssl_ctx.ctx->raw_ctx, this); +#endif + + if (!_reload_cert_maps.Modify(AddCertMapping, ssl_ctx)) { + LOG(ERROR) << "Fail to add mappings into _reload_cert_maps"; + return -1; + } + _ssl_ctx_map[cert_key] = ssl_ctx; + return 0; +} + +bool Server::AddCertMapping(CertMaps& bg, const SSLContext& ssl_ctx) { + for (size_t i = 0; i < ssl_ctx.filters.size(); ++i) { + const char* hostname = ssl_ctx.filters[i].c_str(); + CertMap* cmap = NULL; + if (strncmp(hostname, "*.", 2) == 0) { + cmap = &(bg.wildcard_cert_map); + hostname += 2; + } else { + cmap = &(bg.cert_map); + } + if (cmap->seek(hostname) == NULL) { + cmap->insert(hostname, ssl_ctx.ctx); + } else { + LOG(WARNING) << "Duplicate certificate hostname=" << hostname; + } + } + return true; +} + +int Server::RemoveCertificate(const CertInfo& cert) { + if (!_options.has_ssl_options()) { + LOG(ERROR) << "ServerOptions.ssl_options is not configured yet"; + return -1; + } + std::string cert_key(cert.certificate); + cert_key.append(cert.private_key); + SSLContext* ssl_ctx = _ssl_ctx_map.seek(cert_key); + if (ssl_ctx == NULL) { + LOG(WARNING) << cert << " doesn't exist"; + return 0; + } + if (ssl_ctx->ctx == _default_ssl_ctx) { + LOG(WARNING) << "Cannot remove: " << cert + << " since it's the default certificate"; + return -1; + } + + if (!_reload_cert_maps.Modify(RemoveCertMapping, *ssl_ctx)) { + LOG(ERROR) << "Fail to remove mappings from _reload_cert_maps"; + return -1; + } + + _ssl_ctx_map.erase(cert_key); + return 0; +} + +bool Server::RemoveCertMapping(CertMaps& bg, const SSLContext& ssl_ctx) { + for (size_t i = 0; i < ssl_ctx.filters.size(); ++i) { + const char* hostname = ssl_ctx.filters[i].c_str(); + CertMap* cmap = NULL; + if (strncmp(hostname, "*.", 2) == 0) { + cmap = &(bg.wildcard_cert_map); + hostname += 2; + } else { + cmap = &(bg.cert_map); + } + std::shared_ptr* ctx = cmap->seek(hostname); + if (ctx != NULL && *ctx == ssl_ctx.ctx) { + cmap->erase(hostname); + } + } + return true; +} + +int Server::ResetCertificates(const std::vector& certs) { + if (!_options.has_ssl_options()) { + LOG(ERROR) << "ServerOptions.ssl_options is not configured yet"; + return -1; + } + + SSLContextMap tmp_map; + if (tmp_map.init(certs.size() + 1) != 0) { + LOG(ERROR) << "Fail to init tmp_map"; + return -1; + } + + // Add default certificate into tmp_map first since it can't be reloaded + std::string default_cert_key = + _options.ssl_options().default_cert.certificate + + _options.ssl_options().default_cert.private_key; + tmp_map[default_cert_key] = _ssl_ctx_map[default_cert_key]; + + for (size_t i = 0; i < certs.size(); ++i) { + std::string cert_key(certs[i].certificate); + cert_key.append(certs[i].private_key); + if (tmp_map.seek(cert_key) != NULL) { + LOG(WARNING) << certs[i] << " already exists"; + return 0; + } + + SSLContext ssl_ctx; + ssl_ctx.filters = certs[i].sni_filters; + ssl_ctx.ctx = std::make_shared(); + ssl_ctx.ctx->raw_ctx = CreateServerSSLContext( + certs[i].certificate, certs[i].private_key, + _options.ssl_options(), &_raw_alpns, &ssl_ctx.filters); + if (ssl_ctx.ctx->raw_ctx == NULL) { + return -1; + } + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + SSL_CTX_set_tlsext_servername_callback(ssl_ctx.ctx->raw_ctx, SSLSwitchCTXByHostname); + SSL_CTX_set_tlsext_servername_arg(ssl_ctx.ctx->raw_ctx, this); +#endif + tmp_map[cert_key] = ssl_ctx; + } + + if (!_reload_cert_maps.Modify(ResetCertMappings, tmp_map)) { + return -1; + } + + _ssl_ctx_map.swap(tmp_map); + return 0; +} + +bool Server::ResetCertMappings(CertMaps& bg, const SSLContextMap& ctx_map) { + bg.cert_map.clear(); + bg.wildcard_cert_map.clear(); + + for (SSLContextMap::const_iterator it = + ctx_map.begin(); it != ctx_map.end(); ++it) { + const SSLContext& ssl_ctx = it->second; + for (size_t i = 0; i < ssl_ctx.filters.size(); ++i) { + const char* hostname = ssl_ctx.filters[i].c_str(); + CertMap* cmap = NULL; + if (strncmp(hostname, "*.", 2) == 0) { + cmap = &(bg.wildcard_cert_map); + hostname += 2; + } else { + cmap = &(bg.cert_map); + } + if (cmap->seek(hostname) == NULL) { + cmap->insert(hostname, ssl_ctx.ctx); + } else { + LOG(WARNING) << "Duplicate certificate hostname=" << hostname; + } + } + } + return true; +} + +void Server::FreeSSLContexts() { + _ssl_ctx_map.clear(); + _reload_cert_maps.Modify(ClearCertMapping); + _default_ssl_ctx = NULL; +} + +bool Server::ClearCertMapping(CertMaps& bg) { + bg.cert_map.clear(); + bg.wildcard_cert_map.clear(); + return true; +} + +int Server::ResetMaxConcurrency(int max_concurrency) { + if (!IsRunning()) { + LOG(WARNING) << "ResetMaxConcurrency is only allowed for a Running Server"; + return -1; + } + // Assume that modifying int32 is atomical in X86 + _options.max_concurrency = max_concurrency; + return 0; +} + +AdaptiveMaxConcurrency& Server::MaxConcurrencyOf(MethodProperty* mp) { + if (mp->status == NULL) { + LOG(ERROR) << "method=" << mp->method->full_name() + << " does not support max_concurrency"; + _failed_to_set_max_concurrency_of_method = true; + return g_default_max_concurrency_of_method; + } + return mp->max_concurrency; +} + +int Server::MaxConcurrencyOf(const MethodProperty* mp) const { + if (mp == NULL || mp->status == NULL) { + return 0; + } + return mp->max_concurrency; +} + +AdaptiveMaxConcurrency& Server::MaxConcurrencyOf(const butil::StringPiece& full_method_name) { + do { + if (full_method_name == butil::class_name_str()) { + if (NULL == options().nshead_service) { + break; + } + return options().nshead_service->_max_concurrency; + } +#ifdef ENABLE_THRIFT_FRAMED_PROTOCOL + if (full_method_name == butil::class_name_str()) { + if (NULL == options().thrift_service) { + break; + } + return options().thrift_service->_max_concurrency; + } +#endif + if (full_method_name == butil::class_name_str()) { + if (NULL == options().baidu_master_service) { + break; + } + return options().baidu_master_service->_max_concurrency; + } + + MethodProperty* mp = _method_map.seek(full_method_name); + if (mp == NULL) { + break; + } + return MaxConcurrencyOf(mp); + + } while (false); + + LOG(ERROR) << "Fail to find method=" << full_method_name; + _failed_to_set_max_concurrency_of_method = true; + return g_default_max_concurrency_of_method; +} + +int Server::MaxConcurrencyOf(const butil::StringPiece& full_method_name) const { + return MaxConcurrencyOf(_method_map.seek(full_method_name)); +} + +AdaptiveMaxConcurrency& Server::MaxConcurrencyOf(const butil::StringPiece& full_service_name, + const butil::StringPiece& method_name) { + MethodProperty* mp = const_cast( + FindMethodPropertyByFullName(full_service_name, method_name)); + if (mp == NULL) { + LOG(ERROR) << "Fail to find method=" << full_service_name + << '/' << method_name; + _failed_to_set_max_concurrency_of_method = true; + return g_default_max_concurrency_of_method; + } + return MaxConcurrencyOf(mp); +} + +int Server::MaxConcurrencyOf(const butil::StringPiece& full_service_name, + const butil::StringPiece& method_name) const { + return MaxConcurrencyOf(FindMethodPropertyByFullName( + full_service_name, method_name)); +} + +AdaptiveMaxConcurrency& Server::MaxConcurrencyOf(google::protobuf::Service* service, + const butil::StringPiece& method_name) { + return MaxConcurrencyOf(service->GetDescriptor()->full_name(), method_name); +} + +int Server::MaxConcurrencyOf(google::protobuf::Service* service, + const butil::StringPiece& method_name) const { + return MaxConcurrencyOf(service->GetDescriptor()->full_name(), method_name); +} + +bool& Server::IgnoreEovercrowdedOf(const butil::StringPiece& full_method_name) { + MethodProperty* mp = _method_map.seek(full_method_name); + if (mp == NULL) { + LOG(ERROR) << "Fail to find method=" << full_method_name; + _failed_to_set_ignore_eovercrowded = true; + return g_default_ignore_eovercrowded; + } + if (IsRunning()) { + LOG(WARNING) << "IgnoreEovercrowdedOf is only allowd before Server started"; + return g_default_ignore_eovercrowded; + } + if (mp->status == NULL) { + LOG(ERROR) << "method=" << mp->method->full_name() + << " does not support ignore_eovercrowded"; + _failed_to_set_ignore_eovercrowded = true; + return g_default_ignore_eovercrowded; + } + return mp->ignore_eovercrowded; +} + +bool Server::IgnoreEovercrowdedOf(const butil::StringPiece& full_method_name) const { + MethodProperty* mp = _method_map.seek(full_method_name); + if (IsRunning()) { + LOG(WARNING) << "IgnoreEovercrowdedOf is only allowd before Server started"; + return g_default_ignore_eovercrowded; + } + if (mp == NULL || mp->status == NULL) { + return false; + } + return mp->ignore_eovercrowded; +} + +bool Server::AcceptRequest(Controller* cntl) const { + const Interceptor* interceptor = _options.interceptor; + if (!interceptor) { + return true; + } + + int error_code = 0; + std::string error_text; + if (cntl && + !interceptor->Accept(cntl, error_code, error_text)) { + cntl->SetFailed(error_code, + "Reject by Interceptor: %s", + error_text.c_str()); + return false; + } + + return true; +} + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME +int Server::SSLSwitchCTXByHostname(struct ssl_st* ssl, + int* al, void* se) { + (void)al; + Server* server = reinterpret_cast(se); + const char* hostname = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + bool strict_sni = server->_options.ssl_options().strict_sni; + if (hostname == NULL) { + return strict_sni ? SSL_TLSEXT_ERR_ALERT_FATAL : SSL_TLSEXT_ERR_NOACK; + } + + butil::DoublyBufferedData::ScopedPtr s; + if (server->_reload_cert_maps.Read(&s) != 0) { + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + std::shared_ptr* pctx = s->cert_map.seek(hostname); + if (pctx == NULL) { + const char* dot = hostname; + for (; *dot != '\0'; ++dot) { + if (*dot == '.') { + ++dot; + break; + } + } + if (*dot != '\0') { + pctx = s->wildcard_cert_map.seek(dot); + } + } + if (pctx == NULL) { + if (strict_sni) { + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + // Use default SSL_CTX which is the current one + return SSL_TLSEXT_ERR_OK; + } + + // Switch SSL_CTX to the one with correct hostname + SSL_set_SSL_CTX(ssl, (*pctx)->raw_ctx); + return SSL_TLSEXT_ERR_OK; +} +#endif // SSL_CTRL_SET_TLSEXT_HOSTNAME + +} // namespace brpc diff --git a/src/brpc/server.h b/src/brpc/server.h new file mode 100644 index 0000000..4fbe304 --- /dev/null +++ b/src/brpc/server.h @@ -0,0 +1,818 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_SERVER_H +#define BRPC_SERVER_H + +// To brpc developers: This is a header included by user, don't depend +// on internal structures, use opaque pointers instead. + +#include "bthread/errno.h" // Redefine errno +#include "bthread/bthread.h" // Server may need some bthread functions, + // e.g. bthread_usleep +#include // google::protobuf::Service +#include "butil/macros.h" // DISALLOW_COPY_AND_ASSIGN +#include "butil/containers/doubly_buffered_data.h" // DoublyBufferedData +#include "bvar/bvar.h" +#include "butil/containers/case_ignored_flat_map.h" // [CaseIgnored]FlatMap +#include "butil/ptr_container.h" +#include "brpc/controller.h" // brpc::Controller +#include "brpc/ssl_options.h" // ServerSSLOptions +#include "brpc/describable.h" // User often needs this +#include "brpc/data_factory.h" // DataFactory +#include "brpc/builtin/tabbed.h" +#include "brpc/details/profiler_linker.h" +#include "brpc/health_reporter.h" +#include "brpc/adaptive_max_concurrency.h" +#include "brpc/http2.h" +#include "brpc/redis.h" +#include "brpc/interceptor.h" +#include "brpc/concurrency_limiter.h" +#include "brpc/baidu_master_service.h" +#include "brpc/rpc_pb_message_factory.h" +#include "brpc/socket_mode.h" + +namespace brpc { + +class Acceptor; +class MethodStatus; +class NsheadService; +class ThriftService; +class SimpleDataPool; +class MongoServiceAdaptor; +class RestfulMap; +class RtmpService; +class RedisService; +struct SocketSSLContext; + +struct ServerOptions { + ServerOptions(); // Constructed with default options. + + // connections without data transmission for so many seconds will be closed + // Default: -1 (disabled) + int idle_timeout_sec; + + // If this option is not empty, a file named so containing Process Id + // of the server will be created when the server is started. + // Default: "" + std::string pid_file; + + // Process requests in format of nshead_t + blob. + // Owned by Server and deleted in server's destructor. + // Default: NULL + NsheadService* nshead_service; + + // Process requests in format of thrift_binary_head_t + blob. + // Owned by Server and deleted in server's destructor. + // Default: NULL + ThriftService* thrift_service; + + // Adaptor for Mongo protocol, check src/brpc/mongo_service_adaptor.h for details + // The adaptor will not be deleted by server + // and must remain valid when server is running. + const MongoServiceAdaptor* mongo_service_adaptor; + + // Turn on authentication for all services if `auth' is not NULL. + // Default: NULL + const Authenticator* auth; + + // false: `auth' is not owned by server and must be valid when server is running. + // true: `auth' is owned by server and will be deleted when server is destructed. + // Default: false + bool server_owns_auth; + + // Turn on request interception if `interceptor' is not NULL. + // Default: NULL + const Interceptor* interceptor; + + // false: `interceptor' is not owned by server and must be valid when server is running. + // true: `interceptor' is owned by server and will be deleted when server is destructed. + // Default: false + bool server_owns_interceptor; + + // Number of pthreads that server runs on. Notice that this is just a hint, + // you can't assume that the server uses exactly so many pthreads because + // pthread workers are shared by all servers and channels inside a + // process. And there're no "io-thread" and "worker-thread" anymore, + // brpc automatically schedules "io" and "worker" code for better + // parallelism and less context switches. + // If this option <= 0, number of pthread workers is not changed. + // Default: #cpu-cores + int num_threads; + + // Server-level max concurrency. + // "concurrency" = "number of requests processed in parallel" + // + // In a traditional server, number of pthread workers also limits + // concurrency. However brpc runs requests in bthreads which are + // mapped to pthread workers, when a bthread context switches, it gives + // the pthread worker to another bthread, yielding a higher concurrency + // than number of pthreads. In some situations, higher concurrency may + // consume more resources, to protect the server from running out of + // resources, you may set this option. + // If the server reaches the limitation, it responds client with ELIMIT + // directly without calling service's callback. The client seeing ELIMIT + // shall try another server. + // NOTE: accesses to builtin services are not limited by this option. + // Default: 0 (unlimited) + int max_concurrency; + + // Default value of method-level max concurrencies, + // Overridable by Server.MaxConcurrencyOf(). + AdaptiveMaxConcurrency method_max_concurrency; + + // ------------------------------------------------------- + // Differences between session-local and thread-local data + // ------------------------------------------------------- + // * session-local data has to be got from a server-side Controller. + // thread-local data can be got in any function running inside a server + // thread without an object. + // * session-local data is attached to current RPC and invalid after + // calling `done'. thread-local data is attached to current server + // thread and invalid after leaving Service::CallMethod(). If you need + // to hand down data to an asynchronous `done' which is called outside + // Service::CallMethod(), you must use session-local data. + // ----------------- + // General guideline + // ----------------- + // * Choose a proper value for reserved_xxx_local_data, small value may + // not create enough data for all searching threads, while big value + // wastes memory. + // * Comparing to reserving data, making them as small as possible and + // creating them on demand is better. Passing a lot of stuff via + // session-local data or thread-local data is definitely not a good design. + + // The factory to create/destroy data attached to each RPC session. + // If this option is NULL, Controller::session_local_data() is always NULL. + // NOT owned by Server and must be valid when Server is running. + // Default: NULL + const DataFactory* session_local_data_factory; + + // Prepare so many session-local data before server starts, so that calls + // to Controller::session_local_data() get data directly rather than + // calling session_local_data_factory->Create() at first time. Useful when + // Create() is slow, otherwise the RPC session may be blocked by the + // creation of data and not served within timeout. + // Default: 0 + size_t reserved_session_local_data; + + // The factory to create/destroy data attached to each searching thread + // in server. + // If this option is NULL, brpc::thread_local_data() is always NULL. + // NOT owned by Server and must be valid when Server is running. + // Default: NULL + const DataFactory* thread_local_data_factory; + + // Prepare so many thread-local data before server starts, so that calls + // to brpc::thread_local_data() get data directly rather than calling + // thread_local_data_factory->Create() at first time. Useful when Create() + // is slow, otherwise the RPC session may be blocked by the creation + // of data and not served within timeout. + // Default: 0 + size_t reserved_thread_local_data; + + // Call bthread_init_fn(bthread_init_args) in at least #bthread_init_count + // bthreads before server runs, mainly for initializing bthread locals. + // You have to set both `bthread_init_fn' and `bthread_init_count' to + // enable the feature. + bool (*bthread_init_fn)(void* args); // default: NULL (do nothing) + void* bthread_init_args; // default: NULL + size_t bthread_init_count; // default: 0 + + // Provide builtin services at this port rather than the port to Start(). + // When your server needs to be accessed from public (including traffic + // redirected by nginx or other http front-end servers), set this port + // to a port number that's ONLY accessible from internal network + // so that you can check out the builtin services from this port while + // hiding them from public. Setting this option also enables security + // protection code which we may add constantly. + // Update: this option affects Tabbed services as well. + // Default: -1 + int internal_port; + + // Contain a set of builtin services to ease monitoring/debugging. + // Read docs/cn/builtin_service.md for details. + // DO NOT set this option to false if you don't even know what builtin + // services are. They're very helpful for addressing runtime problems. + // Setting to false makes -internal_port ineffective. + // Default: true + bool has_builtin_services; + + // Enable more secured code which protects internal information from exposure. + bool security_mode() const { return internal_port >= 0 || !has_builtin_services; } + + // SSL related options. Refer to `ServerSSLOptions' for details + bool has_ssl_options() const { return _ssl_options != NULL; } + const ServerSSLOptions& ssl_options() const { return *_ssl_options; } + ServerSSLOptions* mutable_ssl_options(); + + // Force ssl for all connections of the port to Start(). + bool force_ssl; + + // the server socket mode uses tcp or rdma or other + // Default: SOCKET_MODE_TCP + SocketMode socket_mode; + + // [CAUTION] This option is for implementing specialized baidu-std proxies, + // most users don't need it. Don't change this option unless you fully + // understand the description below. + // If this option is set, all baidu-std requests to the server will be delegated + // to this service. + // + // Owned by Server and deleted in server's destructor. + BaiduMasterService* baidu_master_service; + + // [CAUTION] This option is for implementing specialized http proxies, + // most users don't need it. Don't change this option unless you fully + // understand the description below. + // If this option is set, all HTTP requests to the server will be delegated + // to this service which fully decides what to call and what to send back, + // including accesses to builtin services and pb services. + // The service must have a method named "default_method" and the request + // and response must have no fields. + // + // Owned by Server and deleted in server's destructor + ::google::protobuf::Service* http_master_service; + + // If this field is on, contents on /health page is generated by calling + // health_reporter->GenerateReport(). This object is NOT owned by server + // and must remain valid when server is running. + HealthReporter* health_reporter; + + // For processing RTMP connections. Read src/brpc/rtmp.h for details. + // Default: NULL (rtmp support disabled) + RtmpService* rtmp_service; + + // Only enable these protocols, separated by spaces. + // All names inside must be valid, check protocols name in global.cpp + // Default: empty (all protocols) + std::string enabled_protocols; + + // Customize parameters of HTTP2, defined in http2.h + H2Settings h2_settings; + + // For processing Redis connections. Read src/brpc/redis.h for details. + // Owned by Server and deleted in server's destructor. + // Default: NULL (disabled) + RedisService* redis_service; + + // Optional info name for composing server bvar prefix. Read ServerPrefix() method for details; + // Default: "" + std::string server_info_name; + + // Server will run in this tagged bthread worker group + // Default: BTHREAD_TAG_DEFAULT + bthread_tag_t bthread_tag; + + // [CAUTION] This option is for implementing specialized rpc protobuf + // message factory, most users don't need it. Don't change this option + // unless you fully understand the description below. + // If this option is set, all baidu-std rpc request message and response + // message will be created by this factory. + // + // Owned by Server and deleted in server's destructor. + RpcPBMessageFactory* rpc_pb_message_factory; + + // Ignore eovercrowded error on server side, i.e. , if eovercrowded is reported when server is processing a rpc request, + // server will keep processing this request, it is expected to be used by some light-weight control-frame rpcs. + // [CUATION] You should not enabling this option if your rpc is heavy-loaded. + bool ignore_eovercrowded; + +private: + // SSLOptions is large and not often used, allocate it on heap to + // prevent ServerOptions from being bloated in most cases. + butil::PtrContainer _ssl_options; +}; + +// This struct is originally designed to contain basic statistics of the +// server. But bvar contains more stats and is more convenient. +struct ServerStatistics { + size_t connection_count; + int user_service_count; + int builtin_service_count; +}; + +// Represent server's ownership of services. +enum ServiceOwnership { + SERVER_OWNS_SERVICE, + SERVER_DOESNT_OWN_SERVICE +}; + +struct ServiceOptions { + ServiceOptions(); // constructed with default options. + + // SERVER_OWNS_SERVICE: the service will be deleted by the server. + // SERVER_DOESNT_OWN_SERVICE: the service shall be deleted by user after + // stopping the server. + // Default: SERVER_DOESNT_OWN_SERVICE + ServiceOwnership ownership; + + // If this option is non-empty, methods in the service will be exposed + // on specified paths instead of default "/SERVICE/METHOD". + // Mappings are in form of: "PATH1 => NAME1, PATH2 => NAME2 ..." where + // PATHs are valid http paths, NAMEs are method names in the service. + // Default: empty + std::string restful_mappings; + + // Work with restful_mappings, if this flag is false, reject methods accessed + // from default urls (SERVICE/METHOD). + // Default: false + bool allow_default_url; + + // [ Not recommended to change this option ] + // If this flag is true, the service will convert http body to protobuf + // when the pb schema is non-empty in http servings. The body must be + // valid json or protobuf(wire-format) otherwise the request is rejected. + // This option does not affect pure-http services (pb schema is empty). + // Services that use older versions of brpc may need to turn this + // conversion off and handle http requests by their own to keep compatible + // with existing clients. + // Default: true + bool allow_http_body_to_pb; + + // decode json string to protobuf bytes using base64 decoding when this + // option is turned on. + // Default: false if BAIDU_INTERNAL is defined, otherwise true + bool pb_bytes_to_base64; + + // decode json array to protobuf message which contains a single repeated field. + // Default: false. + bool pb_single_repeated_to_array; + + // enable server end progressive reading, mainly for http server + // Default: false. + bool enable_progressive_read; +}; + +// Represent ports inside [min_port, max_port] +struct PortRange { + int min_port; + int max_port; + + PortRange(int min_port2, int max_port2) + : min_port(min_port2), max_port(max_port2) { + } +}; + +// Server dispatches requests from clients to registered services and +// sends responses back to clients. +class Server { +public: + enum Status { + UNINITIALIZED = 0, + READY = 1, + RUNNING = 2, + STOPPING = 3, + }; + struct ServiceProperty { + bool is_builtin_service; + ServiceOwnership ownership; + // `service' and `restful_map' are mutual exclusive, they can't be + // both non-NULL. If `restful_map' is not NULL, the URL should be + // further matched by it. + google::protobuf::Service* service; + RestfulMap* restful_map; + + bool is_user_service() const { + return !is_builtin_service && !restful_map; + } + + const std::string service_name() const; + }; + typedef butil::FlatMap ServiceMap; + + struct MethodProperty { + bool is_builtin_service; + bool own_method_status; + // Parameters which have nothing to do with management of services, but + // will be used when the service is queried. + struct OpaqueParams { + bool is_tabbed; + bool allow_default_url; + bool allow_http_body_to_pb; + bool pb_bytes_to_base64; + bool pb_single_repeated_to_array; + bool enable_progressive_read; + OpaqueParams(); + }; + OpaqueParams params; + // NULL if service of the method was never added as restful. + // "@path1 @path2 ..." if the method was mapped from paths. + std::string* http_url; + google::protobuf::Service* service; + const google::protobuf::MethodDescriptor* method; + MethodStatus* status; + AdaptiveMaxConcurrency max_concurrency; + // ignore_eovercrowded on method-level, it should be used with carefulness. + // It might introduce inbalance between methods, + // as some methods(ignore_eovercrowded=true) might never return eovercrowded + // while other methods(ignore_eovercrowded=false) keep returning eovercrowded. + // currently only valid for baidu_master_service, baidu_rpc, http_rpc, hulu_pbrpc and sofa_pbrpc protocols + bool ignore_eovercrowded; + + MethodProperty(); + }; + typedef butil::FlatMap MethodMap; + + struct ThreadLocalOptions { + bthread_key_t tls_key; + const DataFactory* thread_local_data_factory; + + ThreadLocalOptions() + : tls_key(INVALID_BTHREAD_KEY) + , thread_local_data_factory(NULL) {} + }; + +public: + Server(ProfilerLinker = ProfilerLinker()); + ~Server(); + + // A set of functions to start this server. + // Returns 0 on success, -1 otherwise and errno is set appropriately. + // Notes: + // * Default options are taken if `opt' is NULL. + // * A server can be started more than once if the server is completely + // stopped by Stop() and Join(). + // * port can be 0, which makes kernel to choose a port dynamically. + + // Start on an address in form of "0.0.0.0:8000". + int Start(const char* ip_port_str, const ServerOptions* opt); + int Start(const butil::EndPoint& ip_port, const ServerOptions* opt); + // Start on IP_ANY:port. + int Start(int port, const ServerOptions* opt); + // Start on `ip_str' + any useable port in `range' + int Start(const char* ip_str, PortRange range, const ServerOptions *opt); + // Start on IP_ANY + first useable port in `range' + int Start(PortRange range, const ServerOptions* opt); + + // NOTE: Stop() is paired with Join() to stop a server without losing + // requests. The point of separating them is that you can Stop() multiple + // servers before Join() them, in which case the total time to Join is + // time of the slowest Join(). Otherwise you have to Join() them one by + // one, in which case the total time is sum of all Join(). + + // Stop accepting new connections and requests from existing connections. + // Returns 0 on success, -1 otherwise. + int Stop(int closewait_ms/*not used anymore*/); + + // Wait until requests in progress are done. If Stop() is not called, + // this function NEVER return. If Stop() is called, during the waiting, + // this server responds new requests with `ELOGOFF' error immediately + // without calling any service. When clients see the error, they should + // try other servers. + int Join(); + + // Sleep until Ctrl-C is pressed, then stop and join this server. + // CAUTION: Don't call signal(SIGINT, ...) in your program! + // If signal(SIGINT, ..) is called AFTER calling this function, this + // function may block indefinitely. + void RunUntilAskedToQuit(); + + // Add a service. Arguments are explained in ServiceOptions above. + // NOTE: Adding a service while server is running is forbidden. + // Returns 0 on success, -1 otherwise. + int AddService(google::protobuf::Service* service, + ServiceOwnership ownership); + int AddService(google::protobuf::Service* service, + ServiceOwnership ownership, + const butil::StringPiece& restful_mappings, + bool allow_default_url = false); + int AddService(google::protobuf::Service* service, + const ServiceOptions& options); + + // Remove a service from this server. + // NOTE: removing a service while server is running is forbidden. + // Returns 0 on success, -1 otherwise. + int RemoveService(google::protobuf::Service* service); + + // Remove all services from this server. + // NOTE: clearing services when server is running is forbidden. + void ClearServices(); + + // Dynamically add a new certificate into server. It can be called + // while the server is running, but it's not thread-safe by itself. + // Returns 0 on success, -1 otherwise. + int AddCertificate(const CertInfo& cert); + + // Dynamically remove a former certificate from server. Can be called + // while the server is running, but it's not thread-safe by itself. + // Returns 0 on success, -1 otherwise. + int RemoveCertificate(const CertInfo& cert); + + // Dynamically reset all certificates except the default one. It can be + // called while the server is running, but it's not thread-safe by itself. + // Returns 0 on success, -1 otherwise. + int ResetCertificates(const std::vector& certs); + + // Find a service by its ServiceDescriptor::full_name(). + // Returns the registered service pointer, NULL on not found. + // Notice that for performance concerns, this function does not lock service + // list internally thus races with AddService()/RemoveService(). + google::protobuf::Service* + FindServiceByFullName(const butil::StringPiece& full_name) const; + + // Find a service by its ServiceDescriptor::name(). + // Returns the registered service pointer, NULL on not found. + // Notice that for performance concerns, this function does not lock service + // list internally thus races with AddService()/RemoveService(). + google::protobuf::Service* + FindServiceByName(const butil::StringPiece& name) const; + + // Put all services registered by user into `services' + void ListServices(std::vector* services); + + // Get statistics of this server + void GetStat(ServerStatistics* stat) const; + + // Get the options passed to Start(). + const ServerOptions& options() const { return _options; } + + // Status of this server. + Status status() const { return _status; } + + // Return true iff this server is serving requests. + bool IsRunning() const { return status() == RUNNING; } + + // Return the first service added to this server. If a service was once + // returned by first_service() and then removed, first_service() will + // always be NULL. + // This is useful for some production lines whose protocol does not + // contain a service name, in which case this service works as the + // default service. + google::protobuf::Service* first_service() const + { return _first_service; } + + // Set version string for this server, will be shown in /version page + void set_version(const std::string& version) { _version = version; } + const std::string& version() const { return _version; } + + // Return the address this server is listening + butil::EndPoint listen_address() const { return _listen_addr; } + + // Last time that Start() was successfully called. 0 if Start() was + // never called + time_t last_start_time() const { return _last_start_time; } + + SimpleDataPool* session_local_data_pool() const { return _session_local_data_pool; } + + const ThreadLocalOptions& thread_local_options() const { return _tl_options; } + + // Print the html code of tabs into `os'. + // current_tab_name is the tab highlighted. + void PrintTabsBody(std::ostream& os, const char* current_tab_name) const; + + // This method is already deprecated.You should NOT call it anymore. + int ResetMaxConcurrency(int max_concurrency); + + // Get/set max_concurrency associated with a method. + // Example: + // server.MaxConcurrencyOf("example.EchoService.Echo") = 10; + // or server.MaxConcurrencyOf("example.EchoService", "Echo") = 10; + // or server.MaxConcurrencyOf(&service, "Echo") = 10; + // Note: These interfaces can ONLY be called before the server is started. + // And you should NOT set the max_concurrency when you are going to choose + // an auto concurrency limiter, eg `options.max_concurrency = "auto"`.If you + // still called non-const version of the interface, your changes to the + // maximum concurrency will not take effect. + AdaptiveMaxConcurrency& MaxConcurrencyOf(const butil::StringPiece& full_method_name); + int MaxConcurrencyOf(const butil::StringPiece& full_method_name) const; + + AdaptiveMaxConcurrency& MaxConcurrencyOf(const butil::StringPiece& full_service_name, + const butil::StringPiece& method_name); + int MaxConcurrencyOf(const butil::StringPiece& full_service_name, + const butil::StringPiece& method_name) const; + + AdaptiveMaxConcurrency& MaxConcurrencyOf(google::protobuf::Service* service, + const butil::StringPiece& method_name); + int MaxConcurrencyOf(google::protobuf::Service* service, + const butil::StringPiece& method_name) const; + + bool& IgnoreEovercrowdedOf(const butil::StringPiece& full_method_name); + bool IgnoreEovercrowdedOf(const butil::StringPiece& full_method_name) const; + + int Concurrency() const { + return butil::subtle::NoBarrier_Load(&_concurrency); + }; + + // Returns true if accept request, reject request otherwise. + bool AcceptRequest(Controller* cntl) const; + + bool has_progressive_read_method() const { + return this->_has_progressive_read_method; + } + +private: +friend class StatusService; +friend class ProtobufsService; +friend class ConnectionsService; +friend class BadMethodService; +friend class ServerPrivateAccessor; +friend class PrometheusMetricsService; +friend class Controller; + + int AddServiceInternal(google::protobuf::Service* service, + bool is_builtin_service, + const ServiceOptions& options); + + int AddBuiltinService(google::protobuf::Service* service); + + // Remove all methods of `service' from internal structures. + void RemoveMethodsOf(google::protobuf::Service* service); + + int AddBuiltinServices(); + + // Initialize internal structure. Initializtion is + // ensured to be called only once + int InitializeOnce(); + + int InitALPNOptions(const ServerSSLOptions* options); + + // Create acceptor with handlers of protocols. + Acceptor* BuildAcceptor(); + + int StartInternal(const butil::EndPoint& endpoint, + const PortRange& port_range, + const ServerOptions *opt); + + // Number of user added services, not counting builtin services. + size_t service_count() const { + return _fullname_service_map.size() - + _builtin_service_count - + _virtual_service_count; + } + + // Number of builtin services. + size_t builtin_service_count() const { return _builtin_service_count; } + + static void* UpdateDerivedVars(void*); + + void GenerateVersionIfNeeded(); + void PutPidFileIfNeeded(); + + const MethodProperty* + FindMethodPropertyByFullName(const butil::StringPiece& fullname) const; + + const MethodProperty* + FindMethodPropertyByFullName(const butil::StringPiece& full_service_name, + const butil::StringPiece& method_name) const; + + const MethodProperty* + FindMethodPropertyByNameAndIndex(const butil::StringPiece& service_name, + int method_index) const; + + const ServiceProperty* + FindServicePropertyByFullName(const butil::StringPiece& fullname) const; + + const ServiceProperty* + FindServicePropertyByName(const butil::StringPiece& name) const; + + std::string ServerPrefix() const; + + // Mapping from hostname to corresponding SSL_CTX + typedef butil::CaseIgnoredFlatMap > CertMap; + struct CertMaps { + CertMap cert_map; + CertMap wildcard_cert_map; + }; + + struct SSLContext { + std::shared_ptr ctx; + std::vector filters; + }; + // Mapping from [certificate + private-key] to SSLContext + typedef butil::FlatMap SSLContextMap; + + void FreeSSLContexts(); + + static int SSLSwitchCTXByHostname(struct ssl_st* ssl, + int* al, void* se); + + static bool AddCertMapping(CertMaps& bg, const SSLContext& ssl_ctx); + static bool RemoveCertMapping(CertMaps& bg, const SSLContext& ssl_ctx); + static bool ResetCertMappings(CertMaps& bg, const SSLContextMap& ctx_map); + static bool ClearCertMapping(CertMaps& bg); + + AdaptiveMaxConcurrency& MaxConcurrencyOf(MethodProperty*); + int MaxConcurrencyOf(const MethodProperty*) const; + + static bool CreateConcurrencyLimiter(const AdaptiveMaxConcurrency& amc, + ConcurrencyLimiter** out); + + template + int SetServiceMaxConcurrency(T* service) { + if (NULL != service && NULL != service->_status) { + const AdaptiveMaxConcurrency* amc = &service->_max_concurrency; + if (amc->type() == AdaptiveMaxConcurrency::UNLIMITED()) { + amc = &_options.method_max_concurrency; + } + ConcurrencyLimiter* cl = NULL; + if (!CreateConcurrencyLimiter(*amc, &cl)) { + LOG(ERROR) << "Fail to create ConcurrencyLimiter for method"; + return -1; + } + service->_status->SetConcurrencyLimiter(cl); + } + return 0; + } + + DISALLOW_COPY_AND_ASSIGN(Server); + + // Put frequently-accessed data pool at first. + SimpleDataPool* _session_local_data_pool; + ThreadLocalOptions _tl_options; + + Status _status; + int _builtin_service_count; + // number of the virtual services for mapping URL to methods. + int _virtual_service_count; + bool _failed_to_set_max_concurrency_of_method; + bool _failed_to_set_ignore_eovercrowded; + Acceptor* _am; + Acceptor* _internal_am; + + // Use method->full_name() as key + MethodMap _method_map; + + // Use service->full_name() as key + ServiceMap _fullname_service_map; + + // In order to be compatible with some RPC framework that + // uses service->name() to designate an RPC service + ServiceMap _service_map; + + // The only non-builtin service in _service_map, otherwise NULL. + google::protobuf::Service* _first_service; + + // Store TabInfo of services inheriting Tabbed. + TabInfoList* _tab_info_list; + + // Store url patterns for paths without exact service names, examples: + // *.flv => Method + // abc* => Method + RestfulMap* _global_restful_map; + + // Default certificate which can't be reloaded + std::shared_ptr _default_ssl_ctx; + + // Reloadable SSL mappings + butil::DoublyBufferedData _reload_cert_maps; + + // Holds the memory of all SSL_CTXs + SSLContextMap _ssl_ctx_map; + + ServerOptions _options; + butil::EndPoint _listen_addr; + + // ALPN extention protocol-list format. Server initialize this with alpns options. + // OpenSSL API use this variable to avoid conversion at each handshake. + std::string _raw_alpns; + + std::string _version; + time_t _last_start_time; + bthread_t _derivative_thread; + + bthread_keytable_pool_t* _keytable_pool; + + // mutable is required for `ServerPrivateAccessor' to change this bvar + mutable bvar::Adder _nerror_bvar; + mutable bvar::PerSecond > _eps_bvar; + BAIDU_CACHELINE_ALIGNMENT mutable int32_t _concurrency; + bvar::PassiveStatus _concurrency_bvar; + + bool _has_progressive_read_method; +}; + +// Get the data attached to current searching thread. The data is created by +// ServerOptions.thread_local_data_factory and reused between different threads. +// If ServerOptions.thread_local_data_factory is NULL, return NULL. +// If this function is not called inside a server thread, return NULL. +void* thread_local_data(); + +// Test if a dummy server was already started. +bool IsDummyServerRunning(); + +// Start a dummy server listening at `port'. If a dummy server was already +// running, this function does nothing and fails. +// NOTE: The second parameter(ProfilerLinker) is for linking of profiling +// functions when corresponding macros are defined, just ignore it. +// Returns 0 on success, -1 otherwise. +int StartDummyServerAt(int port, ProfilerLinker = ProfilerLinker()); + +} // namespace brpc + +#endif // BRPC_SERVER_H diff --git a/src/brpc/server_id.cpp b/src/brpc/server_id.cpp new file mode 100644 index 0000000..d745d6c --- /dev/null +++ b/src/brpc/server_id.cpp @@ -0,0 +1,73 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/server_id.h" + + +namespace brpc { + +ServerId2SocketIdMapper::ServerId2SocketIdMapper() { + _tmp.reserve(128); + if (_nref_map.init(128) != 0) { + LOG(WARNING) << "Fail to init _nref_map"; + } +} + +ServerId2SocketIdMapper::~ServerId2SocketIdMapper() { +} + +bool ServerId2SocketIdMapper::AddServer(const ServerId& server) { + return (++_nref_map[server.id] == 1); +} + +bool ServerId2SocketIdMapper::RemoveServer(const ServerId& server) { + int* nref = _nref_map.seek(server.id); + if (nref == NULL) { + LOG(ERROR) << "Unexist SocketId=" << server.id; + return false; + } + if (--*nref <= 0) { + _nref_map.erase(server.id); + return true; + } + return false; +} + +std::vector& ServerId2SocketIdMapper::AddServers( + const std::vector& servers) { + _tmp.clear(); + for (size_t i = 0; i < servers.size(); ++i) { + if (AddServer(servers[i])) { + _tmp.push_back(servers[i].id); + } + } + return _tmp; +} + +std::vector& ServerId2SocketIdMapper::RemoveServers( + const std::vector& servers) { + _tmp.clear(); + for (size_t i = 0; i < servers.size(); ++i) { + if (RemoveServer(servers[i])) { + _tmp.push_back(servers[i].id); + } + } + return _tmp; +} + +} // namespace brpc diff --git a/src/brpc/server_id.h b/src/brpc/server_id.h new file mode 100644 index 0000000..a1d3aee --- /dev/null +++ b/src/brpc/server_id.h @@ -0,0 +1,96 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_SERVER_ID_H +#define BRPC_SERVER_ID_H + +// To brpc developers: This is a header included by user, don't depend +// on internal structures, use opaque pointers instead. + +#include +#include "butil/containers/hash_tables.h" // hash +#include "butil/containers/flat_map.h" +#include "brpc/socket_id.h" + +namespace brpc { + +// Representing a server inside LoadBalancer. +struct ServerId { + ServerId() : id(0) {} + explicit ServerId(SocketId id_in) : id(id_in) {} + ServerId(SocketId id_in, const std::string& tag_in) + : id(id_in), tag(tag_in) {} + + SocketId id; + std::string tag; +}; +inline bool operator==(const ServerId& id1, const ServerId& id2) +{ return id1.id == id2.id && id1.tag == id2.tag; } +inline bool operator!=(const ServerId& id1, const ServerId& id2) +{ return !(id1 == id2); } +inline bool operator<(const ServerId& id1, const ServerId& id2) +{ return id1.id != id2.id ? (id1.id < id2.id) : (id1.tag < id2.tag); } +inline std::ostream& operator<<(std::ostream& os, const ServerId& tsid) { + os << tsid.id; + if (!tsid.tag.empty()) { + os << "(tag=" << tsid.tag << ')'; + } + return os; +} + +// Statefully map ServerId to SocketId. +class ServerId2SocketIdMapper { +public: + ServerId2SocketIdMapper(); + ~ServerId2SocketIdMapper(); + // Remember duplicated count of server.id + // Returns true if server.id does not exist before. + bool AddServer(const ServerId& server); + // Remove 1 duplication of server.id. + // Returns true if server.id does not exist after. + bool RemoveServer(const ServerId& server); + // Remember duplicated counts of all SocketId in servers. + // Returns list of SocketId that do not exist before. + std::vector& AddServers(const std::vector& servers); + // Remove 1 duplication of all SocketId in servers respectively. + // Returns list of SocketId that do not exist after. + std::vector& RemoveServers(const std::vector& servers); +private: + butil::FlatMap _nref_map; + std::vector _tmp; +}; + +} // namespace brpc + + +namespace BUTIL_HASH_NAMESPACE { +#if defined(COMPILER_GCC) +template<> +struct hash { + std::size_t operator()(const ::brpc::ServerId& tagged_id) const { + return hash()(tagged_id.tag) * 101 + tagged_id.id; + } +}; +#elif defined(COMPILER_MSVC) +inline size_t hash_value(const ::brpc::ServerId& tagged_id) { + return hash_value(tagged_id.tag) * 101 + tagged_id.id; +} +#endif // COMPILER +} + +#endif // BRPC_SERVER_ID_H diff --git a/src/brpc/server_node.h b/src/brpc/server_node.h new file mode 100644 index 0000000..865a6b1 --- /dev/null +++ b/src/brpc/server_node.h @@ -0,0 +1,67 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_SERVER_NODE_H +#define BRPC_SERVER_NODE_H + +#include +#include +#include "butil/endpoint.h" + +namespace brpc { + +// Representing a server inside a NamingService. +struct ServerNode { + typedef std::unordered_map MetaMap; + + ServerNode() = default; + + explicit ServerNode(const butil::EndPoint& pt) : addr(pt) {} + + ServerNode(butil::ip_t ip, int port, const std::string& tag2) + : addr(ip, port), tag(tag2) {} + + ServerNode(const butil::EndPoint& pt, const std::string& tag2) + : addr(pt), tag(tag2) {} + + ServerNode(butil::ip_t ip, int port) : addr(ip, port) {} + + butil::EndPoint addr; + std::string tag; + MetaMap meta_map; +}; + +inline bool operator<(const ServerNode& n1, const ServerNode& n2) +{ return n1.addr != n2.addr ? (n1.addr < n2.addr) : (n1.tag < n2.tag); } + +inline bool operator==(const ServerNode& n1, const ServerNode& n2) +{ return n1.addr == n2.addr && n1.tag == n2.tag; } + +inline bool operator!=(const ServerNode& n1, const ServerNode& n2) +{ return !(n1 == n2); } + +inline std::ostream& operator<<(std::ostream& os, const ServerNode& n) { + os << n.addr; + if (!n.tag.empty()) { + os << "(tag=" << n.tag << ')'; + } + return os; +} + +} // namespace brpc + +#endif // BRPC_SERVER_NODE_H diff --git a/src/brpc/shared_object.h b/src/brpc/shared_object.h new file mode 100644 index 0000000..296bdea --- /dev/null +++ b/src/brpc/shared_object.h @@ -0,0 +1,31 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_SHARED_OBJECT_H +#define BRPC_SHARED_OBJECT_H + +#include "butil/shared_object.h" + +namespace brpc { + +using butil::SharedObject; + +} // namespace brpc + + +#endif // BRPC_SHARED_OBJECT_H diff --git a/src/brpc/simple_data_pool.cpp b/src/brpc/simple_data_pool.cpp new file mode 100644 index 0000000..fe1d324 --- /dev/null +++ b/src/brpc/simple_data_pool.cpp @@ -0,0 +1,137 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/simple_data_pool.h" + +namespace brpc { + +SimpleDataPool::SimpleDataPool(const DataFactory* factory) + : _capacity(0) + , _size(0) + , _ncreated(0) + , _pool(NULL) + , _factory(factory) { +} + +SimpleDataPool::~SimpleDataPool() { + Reset(NULL); +} + +void SimpleDataPool::Reset(const DataFactory* factory) { + unsigned saved_size = 0; + void** saved_pool = NULL; + const DataFactory* saved_factory = NULL; + { + BAIDU_SCOPED_LOCK(_mutex); + saved_size = _size; + saved_pool = _pool; + saved_factory = _factory; + _capacity = 0; + _size = 0; + _ncreated.store(0, butil::memory_order_relaxed); + _pool = NULL; + _factory = factory; + } + if (saved_pool) { + if (saved_factory) { + for (unsigned i = 0; i < saved_size; ++i) { + saved_factory->DestroyData(saved_pool[i]); + } + } + free(saved_pool); + } +} + +void SimpleDataPool::Reserve(unsigned n) { + if (_capacity >= n) { + return; + } + BAIDU_SCOPED_LOCK(_mutex); + if (_capacity >= n) { + return; + } + // Resize. + const unsigned new_cap = std::max(_capacity * 3 / 2, n); + void** new_pool = (void**)malloc(new_cap * sizeof(void*)); + if (NULL == new_pool) { + return; + } + if (_pool) { + memcpy(new_pool, _pool, _capacity * sizeof(void*)); + free(_pool); + } + unsigned i = _capacity; + _capacity = new_cap; + _pool = new_pool; + + for (; i < n; ++i) { + void* data = _factory->CreateData(); + if (data == NULL) { + break; + } + _ncreated.fetch_add(1, butil::memory_order_relaxed); + _pool[_size++] = data; + } +} + +void* SimpleDataPool::Borrow() { + if (_size) { + BAIDU_SCOPED_LOCK(_mutex); + if (_size) { + return _pool[--_size]; + } + } + void* data = _factory->CreateData(); + if (data) { + _ncreated.fetch_add(1, butil::memory_order_relaxed); + } + return data; +} + +void SimpleDataPool::Return(void* data) { + if (data == NULL) { + return; + } + if (!_factory->ResetData(data)) { + return _factory->DestroyData(data); + } + std::unique_lock mu(_mutex); + if (_capacity == _size) { + const unsigned new_cap = (_capacity <= 1 ? 128 : (_capacity * 3 / 2)); + void** new_pool = (void**)malloc(new_cap * sizeof(void*)); + if (NULL == new_pool) { + mu.unlock(); + return _factory->DestroyData(data); + } + if (_pool) { + memcpy(new_pool, _pool, _capacity * sizeof(void*)); + free(_pool); + } + _capacity = new_cap; + _pool = new_pool; + } + _pool[_size++] = data; +} + +SimpleDataPool::Stat SimpleDataPool::stat() const { + Stat s = { _size, _ncreated.load(butil::memory_order_relaxed) }; + return s; +} + +} // namespace brpc + diff --git a/src/brpc/simple_data_pool.h b/src/brpc/simple_data_pool.h new file mode 100644 index 0000000..390f6e5 --- /dev/null +++ b/src/brpc/simple_data_pool.h @@ -0,0 +1,60 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_SIMPLE_DATA_POOL_H +#define BRPC_SIMPLE_DATA_POOL_H + +#include "butil/scoped_lock.h" +#include "brpc/data_factory.h" + + +namespace brpc { + +// As the name says, this is a simple unbounded dynamic-size pool for +// reusing void* data. We're assuming that data consumes considerable +// memory and should be reused as much as possible, thus unlike the +// multi-threaded allocator caching objects thread-locally, we just +// put everything in a global list to maximize sharing. It's currently +// used by Server to reuse session-local data. +class SimpleDataPool { +public: + struct Stat { + unsigned nfree; + unsigned ncreated; + }; + + explicit SimpleDataPool(const DataFactory* factory); + ~SimpleDataPool(); + void Reset(const DataFactory* factory); + void Reserve(unsigned n); + void* Borrow(); + void Return(void*); + Stat stat() const; + +private: + butil::Mutex _mutex; + unsigned _capacity; + unsigned _size; + butil::atomic _ncreated; + void** _pool; + const DataFactory* _factory; +}; + +} // namespace brpc + +#endif // BRPC_SIMPLE_DATA_POOL_H diff --git a/src/brpc/socket.cpp b/src/brpc/socket.cpp new file mode 100644 index 0000000..b1dc92e --- /dev/null +++ b/src/brpc/socket.cpp @@ -0,0 +1,3035 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "butil/compat.h" // OS_MACOSX +#include "butil/ssl_compat.h" // BIO_fd_non_fatal_error +#include +#include +#ifdef USE_MESALINK +#include +#include +#include +#endif +#include // getsockopt +#include +#include "bthread/unstable.h" // bthread_timer_del +#include "butil/fd_utility.h" // make_non_blocking +#include "butil/fd_guard.h" // fd_guard +#include "butil/time.h" // cpuwide_time_us +#include "butil/object_pool.h" // get_object +#include "butil/logging.h" // CHECK +#include "butil/macros.h" +#include "butil/class_name.h" // butil::class_name +#include "butil/memory/scope_guard.h" +#include "brpc/log.h" +#include "brpc/reloadable_flags.h" // BRPC_VALIDATE_GFLAG +#include "brpc/errno.pb.h" +#include "brpc/event_dispatcher.h" // RemoveConsumer +#include "brpc/socket.h" +#include "brpc/describable.h" // Describable +#include "brpc/circuit_breaker.h" // CircuitBreaker +#include "brpc/input_messenger.h" +#include "brpc/details/sparse_minute_counter.h" +#include "brpc/stream_impl.h" +#include "brpc/shared_object.h" +#include "brpc/policy/rtmp_protocol.h" // FIXME +#include "brpc/periodic_task.h" +#include "brpc/details/health_check.h" +#include "brpc/transport_factory.h" +#if defined(OS_MACOSX) +#include +#endif + +namespace bthread { +size_t BAIDU_WEAK get_sizes(const bthread_id_list_t* list, size_t* cnt, size_t n); +} + + +namespace brpc { + +// NOTE: This flag was true by default before r31206. Connected to somewhere +// is not an important event now, we can check the connection in /connections +// if we're in doubt. +DEFINE_bool(log_connected, false, "Print log when a connection is established"); +BRPC_VALIDATE_GFLAG(log_connected, PassValidate); + +DEFINE_bool(log_idle_connection_close, false, + "Print log when an idle connection is closed"); +BRPC_VALIDATE_GFLAG(log_idle_connection_close, PassValidate); + +DEFINE_int32(socket_recv_buffer_size, -1, + "Set the recv buffer size of socket if this value is positive"); + +// Default value of SNDBUF is 2500000 on most machines. +DEFINE_int32(socket_send_buffer_size, -1, + "Set send buffer size of sockets if this value is positive"); + +DEFINE_int32(ssl_bio_buffer_size, 16*1024, "Set buffer size for SSL read/write"); + +DEFINE_int32(ssl_handshake_timeout_ms, 5000, + "Max duration of one SSL handshake on a socket. Zero or negative " + "disables the limit and falls back to waiting forever, which can " + "leak ESTABLISHED sockets if the peer never finishes the TLS " + "handshake (e.g. server not actually listening with SSL)."); +BRPC_VALIDATE_GFLAG(ssl_handshake_timeout_ms, PassValidate); + +DEFINE_int64(socket_max_unwritten_bytes, 64 * 1024 * 1024, + "Max unwritten bytes in each socket, if the limit is reached," + " Socket.Write fails with EOVERCROWDED"); + +DEFINE_int64(socket_max_streams_unconsumed_bytes, 0, + "Max stream receivers' unconsumed bytes in one socket," + " it used in stream for receiver buffer control."); + +DEFINE_int32(max_connection_pool_size, 100, + "Max number of pooled connections to a single endpoint"); +BRPC_VALIDATE_GFLAG(max_connection_pool_size, PassValidate); + +DEFINE_int32(connect_timeout_as_unreachable, 3, + "If the socket failed to connect due to ETIMEDOUT for so many " + "times *continuously*, the error is changed to ENETUNREACH which " + "fails the main socket as well when this socket is pooled."); + +DECLARE_bool(usercode_in_coroutine); + +static bool validate_connect_timeout_as_unreachable(const char*, int32_t v) { + return v >= 2 && v < 1000/*large enough*/; +} +BRPC_VALIDATE_GFLAG(connect_timeout_as_unreachable, + validate_connect_timeout_as_unreachable); + +const int WAIT_EPOLLOUT_TIMEOUT_MS = 50; + +class BAIDU_CACHELINE_ALIGNMENT SocketPool { +friend class Socket; +public: + explicit SocketPool(const SocketOptions& opt); + ~SocketPool(); + + // Get an address-able socket. If the pool is empty, create one. + // Returns 0 on success. + int GetSocket(SocketUniquePtr* ptr); + + // Return a socket (which was returned by GetSocket) back to the pool, + // if the pool is full, setfail the socket directly. + void ReturnSocket(Socket* sock); + + // Get all pooled sockets inside. + void ListSockets(std::vector* list, size_t max_count); + +private: + // options used to create this instance + SocketOptions _options; + butil::Mutex _mutex; + std::vector _pool; + butil::EndPoint _remote_side; + butil::atomic _numfree; // #free sockets in all sub pools. + butil::atomic _numinflight; // #inflight sockets in all sub pools. +}; + +// NOTE: sizeof of this class is 1200 bytes. If we have 10K sockets, total +// memory is 12MB, not lightweight, but acceptable. +struct ExtendedSocketStat : public SocketStat { + // For computing stat. + size_t last_in_size; + size_t last_in_num_messages; + size_t last_out_size; + size_t last_out_num_messages; + + struct Sampled { + uint32_t in_size_s; + uint32_t in_num_messages_s; + uint32_t out_size_s; + uint32_t out_num_messages_s; + }; + SparseMinuteCounter _minute_counter; + + ExtendedSocketStat() + : last_in_size(0) + , last_in_num_messages(0) + , last_out_size(0) + , last_out_num_messages(0) { + memset((SocketStat*)this, 0, sizeof(SocketStat)); + } +}; + +// Shared by main socket and derivative sockets. +class Socket::SharedPart : public SharedObject { +public: + // A pool of sockets on which only a request can be sent, corresponding + // to CONNECTION_TYPE_POOLED. When RPC begins, it picks one socket from + // this pool and send the request, when the RPC ends, it returns the + // socket back to this pool. + // Before rev <= r32136, the pool is managed globally in socket_map.cpp + // which has the disadvantage that accesses to different pools contend + // with each other. + butil::atomic socket_pool; + + // The socket newing this object. + SocketId creator_socket_id; + + // Counting number of continuous ETIMEDOUT + butil::atomic num_continuous_connect_timeouts; + + // _in_size, _in_num_messages, _out_size, _out_num_messages of pooled + // sockets are counted into the corresponding fields in their _main_socket. + butil::atomic in_size; + butil::atomic in_num_messages; + butil::atomic out_size; + butil::atomic out_num_messages; + + // For computing stats. + ExtendedSocketStat* extended_stat; + + CircuitBreaker circuit_breaker; + + butil::atomic recent_error_count; + + explicit SharedPart(SocketId creator_socket_id); + ~SharedPart(); + + // Call this method every second (roughly) + void UpdateStatsEverySecond(int64_t now_ms); +}; + +Socket::SharedPart::SharedPart(SocketId creator_socket_id2) + : socket_pool(NULL) + , creator_socket_id(creator_socket_id2) + , num_continuous_connect_timeouts(0) + , in_size(0) + , in_num_messages(0) + , out_size(0) + , out_num_messages(0) + , extended_stat(NULL) + , recent_error_count(0) { +} + +Socket::SharedPart::~SharedPart() { + delete extended_stat; + extended_stat = NULL; + delete socket_pool.exchange(NULL, butil::memory_order_relaxed); +} + +void Socket::SharedPart::UpdateStatsEverySecond(int64_t now_ms) { + ExtendedSocketStat* stat = extended_stat; + if (stat == NULL) { + stat = new (std::nothrow) ExtendedSocketStat; + if (stat == NULL) { + return; + } + extended_stat = stat; + } + + // Save volatile counters. + const size_t in_sz = in_size.load(butil::memory_order_relaxed); + const size_t in_nmsg = in_num_messages.load(butil::memory_order_relaxed); + const size_t out_sz = out_size.load(butil::memory_order_relaxed); + const size_t out_nmsg = out_num_messages.load(butil::memory_order_relaxed); + + // Notice that we don't normalize any data, mainly because normalization + // often make data inaccurate and confuse users. This assumes that this + // function is called exactly every second. This may not be true when the + // running machine gets very busy. TODO(gejun): Figure out a method to + // selectively normalize data when the calling interval is far from 1 second. + stat->in_size_s = in_sz - stat->last_in_size; + stat->in_num_messages_s = in_nmsg - stat->last_in_num_messages; + stat->out_size_s = out_sz - stat->last_out_size; + stat->out_num_messages_s = out_nmsg - stat->last_out_num_messages; + + stat->last_in_size = in_sz; + stat->last_in_num_messages = in_nmsg; + stat->last_out_size = out_sz; + stat->last_out_num_messages = out_nmsg; + + ExtendedSocketStat::Sampled popped; + if (stat->in_size_s |/*bitwise or*/ + stat->in_num_messages_s | + stat->out_size_s | + stat->out_num_messages_s) { + ExtendedSocketStat::Sampled s = { + stat->in_size_s, stat->in_num_messages_s, + stat->out_size_s, stat->out_num_messages_s + }; + stat->in_size_m += s.in_size_s; + stat->in_num_messages_m += s.in_num_messages_s; + stat->out_size_m += s.out_size_s; + stat->out_num_messages_m += s.out_num_messages_s; + if (stat->_minute_counter.Add(now_ms, s, &popped)) { + stat->in_size_m -= popped.in_size_s; + stat->in_num_messages_m -= popped.in_num_messages_s; + stat->out_size_m -= popped.out_size_s; + stat->out_num_messages_m -= popped.out_num_messages_s; + } + } + while (stat->_minute_counter.TryPop(now_ms, &popped)) { + stat->in_size_m -= popped.in_size_s; + stat->in_num_messages_m -= popped.in_num_messages_s; + stat->out_size_m -= popped.out_size_s; + stat->out_num_messages_m -= popped.out_num_messages_s; + } +} + +SocketVarsCollector* g_vars = NULL; + +static pthread_once_t s_create_vars_once = PTHREAD_ONCE_INIT; +static void CreateVars() { + g_vars = new SocketVarsCollector; +} + +void Socket::CreateVarsOnce() { + CHECK_EQ(0, pthread_once(&s_create_vars_once, CreateVars)); +} + +// Used by ConnectionService +int64_t GetChannelConnectionCount() { + if (g_vars) { + return g_vars->channel_conn.get_value(); + } + return 0; +} + +bool Socket::CreatedByConnect() const { + return _user == static_cast(get_client_side_messenger()); +} + +SocketMessage* const DUMMY_USER_MESSAGE = (SocketMessage*)0x1; +const uint32_t MAX_PIPELINED_COUNT = 16384; + +struct BAIDU_CACHELINE_ALIGNMENT Socket::WriteRequest { + static WriteRequest* const UNCONNECTED; + + butil::IOBuf data; + WriteRequest* next; + bthread_id_t id_wait; + + void clear_and_set_control_bits(bool notify_on_success, + bool shutdown_write) { + _socket_and_control_bits.set_extra( + (uint16_t)notify_on_success << 1 | (uint16_t)shutdown_write); + } + + void set_socket(Socket* s) { + _socket_and_control_bits.set(s); + } + + // If this field is set to true, notify when write successfully. + bool is_notify_on_success() const { + return _socket_and_control_bits.extra() & ((uint16_t)1 << 1); + } + + // Whether shutdown write of the socket after this write complete. + bool need_shutdown_write() const { + return _socket_and_control_bits.extra() & (uint16_t)1; + } + + Socket* get_socket() const { + return _socket_and_control_bits.get(); + } + + uint32_t pipelined_count() const { + return _pc_and_udmsg.extra() & 0x3FFF; + } + uint32_t get_auth_flags() const { + return (_pc_and_udmsg.extra() >> 14) & 0x03; + } + void clear_pipelined_count_and_auth_flags() { + _pc_and_udmsg.reset_extra(); + } + SocketMessage* user_message() const { + return _pc_and_udmsg.get(); + } + void clear_user_message() { + _pc_and_udmsg.reset(); + } + void set_pipelined_count_and_user_message( + uint32_t pc, SocketMessage* msg, uint32_t auth_flags) { + if (auth_flags) { + pc |= (auth_flags & 0x03) << 14; + } + _pc_and_udmsg.set_ptr_and_extra(msg, pc); + } + + bool reset_pipelined_count_and_user_message() { + SocketMessage* msg = user_message(); + if (msg) { + if (msg != DUMMY_USER_MESSAGE) { + butil::IOBuf dummy_buf; + // We don't care about the return value since the request + // is already failed. + (void)msg->AppendAndDestroySelf(&dummy_buf, NULL); + } + set_pipelined_count_and_user_message(0, NULL, 0); + return true; + } + return false; + } + + // Register pipelined_count and user_message + void Setup(Socket* s); + +private: + // Socket pointer and some control bits. + PackedPtr _socket_and_control_bits; + // User message pointer, pipelined count auth flag. + PackedPtr _pc_and_udmsg; +}; + +void Socket::WriteRequest::Setup(Socket* s) { + SocketMessage* msg = user_message(); + if (msg) { + clear_user_message(); + if (msg != DUMMY_USER_MESSAGE) { + butil::Status st = msg->AppendAndDestroySelf(&data, s); + if (!st.ok()) { + // Abandon the request. + data.clear(); + bthread_id_error2(id_wait, st.error_code(), st.error_cstr()); + return; + } + } + const int64_t before_write = + s->_unwritten_bytes.fetch_add(data.size(), butil::memory_order_relaxed); + if (before_write + (int64_t)data.size() >= FLAGS_socket_max_unwritten_bytes) { + s->_overcrowded = true; + } + } + const uint32_t pc = pipelined_count(); + if (pc) { + // For positional correspondence between responses and requests, + // which is common in cache servers: memcache, redis... + // The struct will be popped when reading a message from the socket. + PipelinedInfo pi; + pi.count = pc; + pi.auth_flags = get_auth_flags(); + pi.id_wait = id_wait; + clear_pipelined_count_and_auth_flags(); // avoid being pushed again + s->PushPipelinedInfo(pi); + } +} + +Socket::WriteRequest* const Socket::WriteRequest::UNCONNECTED = + (Socket::WriteRequest*)(intptr_t)-1; + +class Socket::EpollOutRequest : public SocketUser { +public: + EpollOutRequest() : fd(-1), timer_id(0) + , on_epollout_event(NULL), data(NULL) {} + + ~EpollOutRequest() override { + // Remove the timer at last inside destructor to avoid + // race with the place that registers the timer + if (timer_id) { + bthread_timer_del(timer_id); + timer_id = 0; + } + } + + void BeforeRecycle(Socket*) override { + // Recycle itself. + delete this; + } + + int fd; + bthread_timer_t timer_id; + int (*on_epollout_event)(int fd, int err, void* data); + void* data; +}; + +static const uint64_t AUTH_FLAG = (1ul << 32); + +Socket::Socket(Forbidden f) + // must be even because Address() relies on evenness of version + : VersionedRefWithId(f) + , _shared_part(NULL) + , _nevent(0) + , _keytable_pool(NULL) + , _fd(-1) + , _tos(0) + , _reset_fd_real_us(-1) + , _fd_version(0) + , _on_edge_triggered_events(NULL) + , _need_on_edge_trigger(false) + , _user(NULL) + , _conn(NULL) + , _preferred_index(-1) + , _hc_count(0) + , _last_msg_size(0) + , _avg_msg_size(0) + , _last_readtime_us(0) + , _parsing_context(NULL) + , _correlation_id(0) + , _health_check_interval_s(-1) + , _is_hc_related_ref_held(false) + , _ninprocess(1) + , _auth_flag_error(0) + , _auth_id(INVALID_BTHREAD_ID) + , _auth_context(NULL) + , _ssl_state(SSL_UNKNOWN) + , _ssl_session(NULL) + , _socket_mode(SOCKET_MODE_TCP) + , _connection_type_for_progressive_read(CONNECTION_TYPE_UNKNOWN) + , _controller_released_socket(false) + , _overcrowded(false) + , _fail_me_at_server_stop(false) + , _logoff_flag(false) + , _error_code(0) + , _pipeline_q(NULL) + , _last_writetime_us(0) + , _unwritten_bytes(0) + , _epollout_butex(NULL) + , _write_head(NULL) + , _is_write_shutdown(false) + , _stream_set(NULL) + , _total_streams_unconsumed_size(0) + , _ninflight_app_health_check(0) + , _tcp_user_timeout_ms(-1) + , _http_request_method(HTTP_METHOD_GET) { + CreateVarsOnce(); + pthread_mutex_init(&_id_wait_list_mutex, NULL); + _epollout_butex = bthread::butex_create_checked >(); +} + +Socket::~Socket() { + pthread_mutex_destroy(&_id_wait_list_mutex); + bthread::butex_destroy(_epollout_butex); +} + +void Socket::ReturnSuccessfulWriteRequest(Socket::WriteRequest* p) { + DCHECK(p->data.empty()); + AddOutputMessages(1); + const bthread_id_t id_wait = p->id_wait; + bool is_notify_on_success = p->is_notify_on_success(); + butil::return_object(p); + // Do not access `p' after it is returned to ObjectPool. + if (id_wait != INVALID_BTHREAD_ID) { + if (is_notify_on_success && !Failed()) { + bthread_id_error(id_wait, 0); + } else { + NotifyOnFailed(id_wait); + } + } +} + +void Socket::ReturnFailedWriteRequest(Socket::WriteRequest* p, int error_code, + const std::string& error_text) { + if (!p->reset_pipelined_count_and_user_message()) { + CancelUnwrittenBytes(p->data.size()); + } + p->data.clear(); // data is probably not written. + const bthread_id_t id_wait = p->id_wait; + butil::return_object(p); + if (id_wait != INVALID_BTHREAD_ID) { + bthread_id_error2(id_wait, error_code, error_text); + } +} + +Socket::WriteRequest* Socket::ReleaseWriteRequestsExceptLast( + Socket::WriteRequest* req, int error_code, const std::string& error_text) { + WriteRequest* p = req; + while (p->next != NULL) { + WriteRequest* const saved_next = p->next; + ReturnFailedWriteRequest(p, error_code, error_text); + p = saved_next; + } + return p; +} + +void Socket::ReleaseAllFailedWriteRequests(Socket::WriteRequest* req) { + CHECK(Failed() || IsWriteShutdown()); + int error_code; + std::string error_text; + if (Failed()) { + pthread_mutex_lock(&_id_wait_list_mutex); + error_code = non_zero_error_code(); + error_text = _error_text; + pthread_mutex_unlock(&_id_wait_list_mutex); + } else { + error_code = ESHUTDOWNWRITE; + error_text = "Shutdown write of the socket"; + } + // Notice that `req' is not tail if Address after IsWriteComplete fails. + do { + req = ReleaseWriteRequestsExceptLast(req, error_code, error_text); + if (!req->reset_pipelined_count_and_user_message()) { + CancelUnwrittenBytes(req->data.size()); + } + req->data.clear(); // MUST, otherwise IsWriteComplete is false + } while (!IsWriteComplete(req, true, NULL)); + ReturnFailedWriteRequest(req, error_code, error_text); +} + +int Socket::ResetFileDescriptor(int fd) { + // Reset message sizes when fd is changed. + _last_msg_size = 0; + _avg_msg_size = 0; + // MUST store `_fd' before adding itself into epoll device to avoid + // race conditions with the callback function inside epoll + static butil::atomic BAIDU_CACHELINE_ALIGNMENT fd_version(0); + _fd_version.store(fd_version.fetch_add(1, butil::memory_order_relaxed), + butil::memory_order_relaxed); + _fd.store(fd, butil::memory_order_release); + _reset_fd_real_us = butil::cpuwide_time_us(); + if (!ValidFileDescriptor(fd)) { + return 0; + } + if (_remote_side == butil::EndPoint()) { + // OK to fail, non-socket fd does not support this. + butil::get_remote_side(fd, &_remote_side); + } + // OK to fail, non-socket fd does not support this. + butil::get_local_side(fd, &_local_side); + + // FIXME : close-on-exec should be set by new syscalls or worse: set right + // after fd-creation syscall. Setting at here has higher probabilities of + // race condition. + butil::make_close_on_exec(fd); + + // Make the fd non-blocking. + if (butil::make_non_blocking(fd) != 0) { + PLOG(ERROR) << "Fail to set fd=" << fd << " to non-blocking"; + _fd.store(-1, butil::memory_order_release); + return -1; + } + // turn off nagling. + // OK to fail, namely unix domain socket does not support this. + butil::make_no_delay(fd); + + SetSocketOptions(fd); + + if (_transport->HasOnEdgeTrigger()) { + if (_io_event.AddConsumer(fd) != 0) { + PLOG(ERROR) << "Fail to add SocketId=" << id() + << " into EventDispatcher"; + _fd.store(-1, butil::memory_order_release); + return -1; + } + } + return 0; +} + +void Socket::SetSocketOptions(int fd) { + if (_tos > 0 && + setsockopt(fd, IPPROTO_IP, IP_TOS, &_tos, sizeof(_tos)) != 0) { + PLOG(ERROR) << "Fail to set tos of fd=" << fd << " to " << _tos; + } + + if (FLAGS_socket_send_buffer_size > 0) { + int buff_size = FLAGS_socket_send_buffer_size; + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &buff_size, sizeof(buff_size)) != 0) { + PLOG(ERROR) << "Fail to set sndbuf of fd=" << fd << " to " + << buff_size; + } + } + + if (FLAGS_socket_recv_buffer_size > 0) { + int buff_size = FLAGS_socket_recv_buffer_size; + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &buff_size, sizeof(buff_size)) != 0) { + PLOG(ERROR) << "Fail to set rcvbuf of fd=" << fd << " to " + << buff_size; + } + } + +#if defined(OS_LINUX) + if (_tcp_user_timeout_ms > 0) { + if (setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, + &_tcp_user_timeout_ms, sizeof(_tcp_user_timeout_ms)) != 0) { + PLOG(ERROR) << "Fail to set TCP_USER_TIMEOUT of fd=" << fd; + } + } +#endif + + if (!_keepalive_options) { + return; + } + + int keepalive = 1; + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive)) != 0) { + PLOG(ERROR) << "Fail to set keepalive of fd=" << fd; + return; + } + +#if defined(OS_LINUX) + if (_keepalive_options->keepalive_idle_s > 0) { + if (setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, + &_keepalive_options->keepalive_idle_s, + sizeof(_keepalive_options->keepalive_idle_s)) != 0) { + PLOG(ERROR) << "Fail to set keepidle of fd=" << fd; + } + } + + if (_keepalive_options->keepalive_interval_s > 0) { + if (setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, + &_keepalive_options->keepalive_interval_s, + sizeof(_keepalive_options->keepalive_interval_s)) != 0) { + PLOG(ERROR) << "Fail to set keepintvl of fd=" << fd; + } + } + + if (_keepalive_options->keepalive_count > 0) { + if (setsockopt(fd, SOL_TCP, TCP_KEEPCNT, + &_keepalive_options->keepalive_count, + sizeof(_keepalive_options->keepalive_count)) != 0) { + PLOG(ERROR) << "Fail to set keepcnt of fd=" << fd; + } + } +#elif defined(OS_MACOSX) + if (_keepalive_options->keepalive_idle_s > 0) { + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, + &_keepalive_options->keepalive_idle_s, + sizeof(_keepalive_options->keepalive_idle_s)) != 0) { + PLOG(ERROR) << "Fail to set keepidle of fd=" << fd; + } + } + + if (_keepalive_options->keepalive_interval_s > 0) { + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, + &_keepalive_options->keepalive_interval_s, + sizeof(_keepalive_options->keepalive_interval_s)) != 0) { + PLOG(ERROR) << "Fail to set keepintvl of fd=" << fd; + } + } + + if (_keepalive_options->keepalive_count > 0) { + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, + &_keepalive_options->keepalive_count, + sizeof(_keepalive_options->keepalive_count)) != 0) { + PLOG(ERROR) << "Fail to set keepcnt of fd=" << fd; + } + } +#endif +} + +// SocketId = 32-bit version + 32-bit slot. +// version: from version part of _versioned_nref, must be an EVEN number. +// slot: designated by ResourcePool. +int Socket::Create(const SocketOptions& options, SocketId* id) { + return VersionedRefWithId::Create(id, options); +} + +int Socket::OnCreated(const SocketOptions& options) { + if (_io_event.Init((void*)id()) != 0) { + LOG(ERROR) << "Fail to init IOEvent"; + SetFailed(ENOMEM, "%s", "Fail to init IOEvent"); + return -1; + } + _io_event.set_bthread_tag(options.bthread_tag); + auto guard = butil::MakeScopeGuard([this] { + _io_event.Reset(); + }); + // start build the transport + _socket_mode = options.socket_mode; + _transport = TransportFactory::CreateTransport(options.socket_mode); + CHECK(NULL != _transport); + _transport->Init(this, options); + + g_vars->nsocket << 1; + CHECK(NULL == _shared_part.load(butil::memory_order_relaxed)); + _nevent.store(0, butil::memory_order_relaxed); + _keytable_pool = options.keytable_pool; + _tos = 0; + _remote_side = options.remote_side; + _local_side = options.local_side; + _device_name = options.device_name; + _on_edge_triggered_events = options.on_edge_triggered_events; + _need_on_edge_trigger = options.need_on_edge_trigger; + _user = options.user; + _conn = options.conn; + _app_connect = _transport->Connect(); + _preferred_index = -1; + _hc_count = 0; + CHECK(_read_buf.empty()); + const int64_t cpuwide_now = butil::cpuwide_time_us(); + _last_readtime_us.store(cpuwide_now, butil::memory_order_relaxed); + reset_parsing_context(options.initial_parsing_context); + _correlation_id = 0; + _health_check_interval_s = options.health_check_interval_s; + _hc_option = options.hc_option; + _is_hc_related_ref_held = false; + _ninprocess.store(1, butil::memory_order_relaxed); + _auth_flag_error.store(0, butil::memory_order_relaxed); + const int rc2 = bthread_id_create(&_auth_id, NULL, NULL); + if (rc2) { + LOG(ERROR) << "Fail to create auth_id: " << berror(rc2); + SetFailed(rc2, "Fail to create auth_id: %s", berror(rc2)); + return -1; + } + _force_ssl = options.force_ssl; + // Disable SSL check if there is no SSL context + _ssl_state = (options.initial_ssl_ctx == NULL ? SSL_OFF : SSL_UNKNOWN); + _ssl_session = NULL; + _ssl_ctx = options.initial_ssl_ctx; + _connection_type_for_progressive_read = CONNECTION_TYPE_UNKNOWN; + _controller_released_socket.store(false, butil::memory_order_relaxed); + _overcrowded = false; + // Maybe non-zero for RTMP connections. + _fail_me_at_server_stop = false; + _logoff_flag.store(false, butil::memory_order_relaxed); + _error_code = 0; + _agent_socket_id.store(INVALID_SOCKET_ID, butil::memory_order_relaxed); + _total_streams_unconsumed_size.store(0, butil::memory_order_relaxed); + _ninflight_app_health_check.store(0, butil::memory_order_relaxed); + // NOTE: last two params are useless in bthread > r32787 + const int rc = bthread_id_list_init(&_id_wait_list, 512, 512); + if (rc) { + LOG(ERROR) << "Fail to init _id_wait_list: " << berror(rc); + SetFailed(rc, "Fail to init _id_wait_list: %s", berror(rc)); + return -1; + } + _last_writetime_us.store(cpuwide_now, butil::memory_order_relaxed); + _unwritten_bytes.store(0, butil::memory_order_relaxed); + _keepalive_options = options.keepalive_options; + _tcp_user_timeout_ms = options.tcp_user_timeout_ms; + CHECK(NULL == _write_head.load(butil::memory_order_relaxed)); + _is_write_shutdown = false; + int fd = options.fd; + if (!ValidFileDescriptor(fd) && options.connect_on_create) { + // Connect on create. + fd = DoConnect(options.connect_abstime, NULL, NULL); + if (fd < 0) { + PLOG(ERROR) << "Fail to connect to " << options.remote_side; + int error_code = errno != 0 ? errno : EHOSTDOWN; + SetFailed(error_code, "Fail to connect to %s: %s", + butil::endpoint2str(options.remote_side).c_str(), + berror(error_code)); + return -1; + } + } + // Must be the last one! Internal fields of this Socket may be accessed + // just after calling ResetFileDescriptor. + if (ResetFileDescriptor(fd) != 0) { + const int saved_errno = errno; + PLOG(ERROR) << "Fail to ResetFileDescriptor"; + SetFailed(saved_errno, "Fail to ResetFileDescriptor: %s", + berror(saved_errno)); + return -1; + } + HoldHCRelatedRef(); + guard.dismiss(); + + return 0; +} + +void Socket::BeforeRecycled() { + const bool create_by_connect = CreatedByConnect(); + if (_app_connect) { + std::shared_ptr tmp; + _app_connect.swap(tmp); + tmp->StopConnect(this); + } + if (_conn) { + SocketConnection* const saved_conn = _conn; + _conn = NULL; + saved_conn->BeforeRecycle(this); + } + if (_user) { + SocketUser* const saved_user = _user; + _user = NULL; + saved_user->BeforeRecycle(this); + } + SharedPart* sp = _shared_part.exchange(NULL, butil::memory_order_acquire); + if (sp) { + sp->RemoveRefManually(); + } + + // Reset `_io_event' at the end. + BRPC_SCOPE_EXIT { + _io_event.Reset(); + }; + const int prev_fd = _fd.exchange(-1, butil::memory_order_relaxed); + if (ValidFileDescriptor(prev_fd)) { + if (_transport->HasOnEdgeTrigger()) { + _io_event.RemoveConsumer(prev_fd); + } + close(prev_fd); + if (create_by_connect) { + g_vars->channel_conn << -1; + } + } + _transport->Release(); + reset_parsing_context(NULL); + _read_buf.clear(); + + _auth_flag_error.store(0, butil::memory_order_relaxed); + bthread_id_error(_auth_id, 0); + + bthread_id_list_destroy(&_id_wait_list); + + if (_ssl_session) { + SSL_free(_ssl_session); + _ssl_session = NULL; + } + + _ssl_ctx = NULL; + + delete _pipeline_q; + _pipeline_q = NULL; + + delete _auth_context; + _auth_context = NULL; + + delete _stream_set; + _stream_set = NULL; + + const SocketId asid = _agent_socket_id.load(butil::memory_order_relaxed); + if (asid != INVALID_SOCKET_ID) { + SocketUniquePtr ptr; + if (Address(asid, &ptr) == 0) { + ptr->ReleaseAdditionalReference(); + } + } + g_vars->nsocket << -1; +} + +void Socket::OnFailed(int error_code, const std::string& error_text) { + // Update _error_text + pthread_mutex_lock(&_id_wait_list_mutex); + _error_code = error_code; + _error_text = error_text; + pthread_mutex_unlock(&_id_wait_list_mutex); + + // Do health-checking even if we're not connected before, needed + // by Channel to revive never-connected socket when server side + // comes online. + if (HCEnabled()) { + GetOrNewSharedPart()->circuit_breaker.MarkAsBroken(); + StartHealthCheck(id(), GetOrNewSharedPart()->circuit_breaker.isolation_duration_ms()); + } + // Wake up all threads waiting on EPOLLOUT when closing fd + _epollout_butex->fetch_add(1, butil::memory_order_relaxed); + bthread::butex_wake_all(_epollout_butex); + + // Wake up all unresponded RPC. + CHECK_EQ(0, bthread_id_list_reset2_pthreadsafe( + &_id_wait_list, error_code, error_text, + &_id_wait_list_mutex)); + ResetAllStreams(error_code, error_text); + // _app_connect shouldn't be set to NULL in SetFailed otherwise + // HC is always not supported. + // FIXME: Design a better interface for AppConnect + // if (_app_connect) { + // AppConnect* const saved_app_connect = _app_connect; + // _app_connect = NULL; + // saved_app_connect->StopConnect(this); + // } +} + +void Socket::AfterRevived() { + if (_user) { + _user->AfterRevived(this); + } else { + LOG(INFO) << "Revived " << description() << " (Connectable)"; + } +} + +std::string Socket::OnDescription() const { + // NOTE: The output of `description()' should be consistent with operator<<() + std::string result; + result.reserve(64); + const int saved_fd = fd(); + if (saved_fd >= 0) { + butil::string_appendf(&result, "fd=%d ", saved_fd); + } + butil::string_appendf(&result, "addr=%s", + butil::endpoint2str(remote_side()).c_str()); + const int local_port = local_side().port; + if (local_port > 0) { + butil::string_appendf(&result, ":%d", local_port); + } + return result; +} + +void Socket::HoldHCRelatedRef() { + if (_health_check_interval_s > 0) { + _is_hc_related_ref_held = true; + AddReference(); + } +} + +void Socket::ReleaseHCRelatedReference() { + if (_health_check_interval_s > 0) { + _is_hc_related_ref_held = false; + Dereference(); + } +} + +int Socket::WaitAndReset(int32_t expected_nref) { + const uint32_t id_ver = VersionOfVRefId(id()); + uint64_t vref; + // Wait until nref == expected_nref. + while (true) { + // The acquire fence pairs with release fence in Dereference to avoid + // inconsistent states to be seen by others. + vref = versioned_ref(); + if (VersionOfVRef(vref) != id_ver + 1) { + LOG(WARNING) << "SocketId=" << id() << " is already alive or recycled"; + return -1; + } + if (NRefOfVRef(vref) > expected_nref) { + if (bthread_usleep(1000L/*FIXME*/) < 0) { + PLOG_IF(FATAL, errno != ESTOP) << "Fail to sleep"; + return -1; + } + } else if (NRefOfVRef(vref) < expected_nref) { + RPC_VLOG << "SocketId=" << id() + << " was abandoned during health checking"; + return -1; + } else { + // nobody holds a health-checking-related reference, + // so no need to do health checking. + if (!_is_hc_related_ref_held) { + RPC_VLOG << "Nobody holds a health-checking-related reference" + << " for SocketId=" << id(); + return -1; + } + + break; + } + } + + // It's safe to close previous fd (provided expected_nref is correct). + const int prev_fd = _fd.exchange(-1, butil::memory_order_relaxed); + if (ValidFileDescriptor(prev_fd)) { + if (_transport->HasOnEdgeTrigger()) { + _io_event.RemoveConsumer(prev_fd); + } + close(prev_fd); + if (CreatedByConnect()) { + g_vars->channel_conn << -1; + } + } + _transport->Reset(expected_nref); + + _local_side = butil::EndPoint(); + if (_ssl_session) { + SSL_free(_ssl_session); + _ssl_session = NULL; + } + _ssl_state = SSL_UNKNOWN; + _nevent.store(0, butil::memory_order_relaxed); + // parsing_context is very likely to be associated with the fd, + // removing it is a safer choice and required by http2. + reset_parsing_context(NULL); + // Must clear _read_buf otehrwise even if the connections is recovered, + // the kept old data is likely to make parsing fail. + _read_buf.clear(); + _ninprocess.store(1, butil::memory_order_relaxed); + _auth_flag_error.store(0, butil::memory_order_relaxed); + bthread_id_error(_auth_id, 0); + const int rc = bthread_id_create(&_auth_id, NULL, NULL); + if (rc != 0) { + LOG(FATAL) << "Fail to create _auth_id, " << berror(rc); + return -1; + } + + const int64_t cpuwide_now = butil::cpuwide_time_us(); + _last_readtime_us.store(cpuwide_now, butil::memory_order_relaxed); + _last_writetime_us.store(cpuwide_now, butil::memory_order_relaxed); + _logoff_flag.store(false, butil::memory_order_relaxed); + { + BAIDU_SCOPED_LOCK(_pipeline_mutex); + if (_pipeline_q) { + _pipeline_q->clear(); + } + } + + SharedPart* sp = GetSharedPart(); + if (sp) { + sp->circuit_breaker.Reset(); + sp->recent_error_count.store(0, butil::memory_order_relaxed); + } + return 0; +} + +void Socket::AddRecentError() { + SharedPart* sp = GetSharedPart(); + if (sp) { + sp->recent_error_count.fetch_add(1, butil::memory_order_relaxed); + } +} + +int64_t Socket::recent_error_count() const { + SharedPart* sp = GetSharedPart(); + if (sp) { + return sp->recent_error_count.load(butil::memory_order_relaxed); + } + return 0; +} + +int Socket::isolated_times() const { + SharedPart* sp = GetSharedPart(); + if (sp) { + return sp->circuit_breaker.isolated_times(); + } + return 0; +} + +void Socket::FeedbackCircuitBreaker(int error_code, int64_t latency_us) { + if (!GetOrNewSharedPart()->circuit_breaker.OnCallEnd(error_code, latency_us)) { + if (SetFailed(main_socket_id()) == 0) { + LOG(ERROR) << "Socket[" << *this << "] isolated by circuit breaker"; + } + } +} + +int Socket::ReleaseReferenceIfIdle(int idle_seconds) { + const int64_t last_active_us = last_active_time_us(); + if (butil::cpuwide_time_us() - last_active_us <= idle_seconds * 1000000L) { + return 0; + } + LOG_IF(WARNING, FLAGS_log_idle_connection_close) + << "Close " << *this << " due to no data transmission for " + << idle_seconds << " seconds"; + if (shall_fail_me_at_server_stop()) { + // sockets for streaming purposes (say RTMP) are probably referenced + // by many places, ReleaseAdditionalReference() cannot notify other + // places to release refs, SetFailed() is a must. + return SetFailed(EUNUSED, "No data transmission for %d seconds", + idle_seconds); + } + return ReleaseAdditionalReference(); +} + + +int Socket::SetFailed() { + return SetFailed(EFAILEDSOCKET, NULL); +} + +int Socket::SetFailed(int error_code, const char* error_fmt, ...) { + std::string error_text; + if (error_fmt != NULL) { + va_list ap; + va_start(ap, error_fmt); + butil::string_vprintf(&error_text, error_fmt, ap); + va_end(ap); + } + return VersionedRefWithId::SetFailed(error_code, error_text); +} + +int Socket::SetFailed(SocketId id) { + SocketUniquePtr ptr; + if (Address(id, &ptr) != 0) { + return -1; + } + + return ptr->SetFailed(EFAILEDSOCKET, NULL); +} + +void Socket::NotifyOnFailed(bthread_id_t id) { + pthread_mutex_lock(&_id_wait_list_mutex); + if (!Failed()) { + const int rc = bthread_id_list_add(&_id_wait_list, id); + pthread_mutex_unlock(&_id_wait_list_mutex); + if (rc != 0) { + bthread_id_error(id, rc); + } + } else { + const int rc = non_zero_error_code(); + const std::string desc = _error_text; + pthread_mutex_unlock(&_id_wait_list_mutex); + bthread_id_error2(id, rc, desc); + } +} + +// For unit-test. +int Socket::Status(SocketId id, int32_t* nref) { + const butil::ResourceId slot = SlotOfVRefId(id); + Socket* const m = address_resource(slot); + if (m != NULL) { + const uint64_t vref = m->versioned_ref(); + if (VersionOfVRef(vref) == VersionOfVRefId(id)) { + if (nref) { + *nref = NRefOfVRef(vref); + } + return 0; + } else if (VersionOfVRef(vref) == VersionOfVRefId(id) + 1) { + if (nref) { + *nref = NRefOfVRef(vref); + } + return 1; + } + } + return -1; +} + +// Check if there're new requests appended. +// If yes, point old_head to reversed new requests and return false; +// If no: +// old_head is fully written, set _write_head to NULL and return true; +// old_head is not written yet, keep _write_head unchanged and return false; +// `old_head' is last new_head got from this function or (in another word) +// tail of current writing list. +// `singular_node' is true iff `old_head' is the only node in its list. +bool Socket::IsWriteComplete(Socket::WriteRequest* old_head, + bool singular_node, + Socket::WriteRequest** new_tail) { + CHECK(NULL == old_head->next); + // Try to set _write_head to NULL to mark that the write is done. + WriteRequest* new_head = old_head; + WriteRequest* desired = NULL; + bool return_when_no_more = true; + if (!old_head->data.empty() || !singular_node) { + desired = old_head; + // Write is obviously not complete if old_head is not fully written. + return_when_no_more = false; + } + if (_write_head.compare_exchange_strong( + new_head, desired, butil::memory_order_acquire)) { + // No one added new requests. + if (new_tail) { + *new_tail = old_head; + } + return return_when_no_more; + } + CHECK_NE(new_head, old_head); + // Above acquire fence pairs release fence of exchange in Write() to make + // sure that we see all fields of requests set. + + // Someone added new requests. + // Reverse the list until old_head. + WriteRequest* tail = NULL; + WriteRequest* p = new_head; + do { + while (p->next == WriteRequest::UNCONNECTED) { + // TODO(gejun): elaborate this + sched_yield(); + } + WriteRequest* const saved_next = p->next; + p->next = tail; + tail = p; + p = saved_next; + CHECK(p != NULL); + } while (p != old_head); + + // Link old list with new list. + old_head->next = tail; + // Call Setup() from oldest to newest, notice that the calling sequence + // matters for protocols using pipelined_count, this is why we don't + // call Setup in above loop which is from newest to oldest. + for (WriteRequest* q = tail; q; q = q->next) { + q->Setup(this); + } + if (new_tail) { + *new_tail = new_head; + } + return false; +} + +int Socket::WaitEpollOut(int fd, bool pollin, const timespec* abstime) { + if (!ValidFileDescriptor(fd)) { + return 0; + } + // Do not need to check addressable since it will be called by + // health checker which called `SetFailed' before + const int expected_val = _epollout_butex->load(butil::memory_order_relaxed); + if (_io_event.RegisterEvent(fd, pollin) != 0) { + return -1; + } + + int rc = bthread::butex_wait(_epollout_butex, expected_val, abstime); + const int saved_errno = errno; + if (rc < 0 && errno == EWOULDBLOCK) { + // Could be writable or spurious wakeup + rc = 0; + } + // Ignore return value since `fd' might have been removed + // by `RemoveConsumer' in `SetFailed' + butil::ignore_result(_io_event.UnregisterEvent(fd, pollin)); + errno = saved_errno; + // Could be writable or spurious wakeup (by former epollout) + return rc; +} + +int Socket::Connect(const timespec* abstime, + int (*on_connect)(int, int, void*), void* data) { + if (_ssl_ctx) { + _ssl_state = SSL_CONNECTING; + } else { + _ssl_state = SSL_OFF; + } + struct sockaddr_storage serv_addr; + socklen_t addr_size = 0; + if (butil::endpoint2sockaddr(remote_side(), &serv_addr, &addr_size) != 0) { + PLOG(ERROR) << "Fail to get sockaddr"; + return -1; + } + butil::fd_guard sockfd(socket(serv_addr.ss_family, SOCK_STREAM, 0)); + if (sockfd < 0) { + PLOG(ERROR) << "Fail to create socket"; + return -1; + } + CHECK_EQ(0, butil::make_close_on_exec(sockfd)); + // We need to do async connect (to manage the timeout by ourselves). + CHECK_EQ(0, butil::make_non_blocking(sockfd)); + if (!_device_name.empty()) { +#ifdef SO_BINDTODEVICE + if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, + _device_name.c_str(), _device_name.size()) < 0) { + PLOG(ERROR) << "Fail to set SO_BINDTODEVICE of fd=" << sockfd + << " to device_name=" << _device_name; + return -1; + } +#else + LOG(ERROR) << "SO_BINDTODEVICE (device_name=" << _device_name + << ") is not supported on this platform"; + return -1; +#endif + } + if (local_side().ip != butil::IP_ANY) { + struct sockaddr_storage cli_addr; + if (butil::endpoint2sockaddr(local_side(), &cli_addr, &addr_size) != 0) { + PLOG(ERROR) << "Fail to get client sockaddr"; + return -1; + } + if (::bind(sockfd, (struct sockaddr*)&cli_addr, addr_size) != 0) { + PLOG(ERROR) << "Fail to bind client socket, errno=" << strerror(errno); + return -1; + } + } + const int rc = ::connect( + sockfd, (struct sockaddr*)&serv_addr, addr_size); + if (rc != 0 && errno != EINPROGRESS) { + PLOG(WARNING) << "Fail to connect to " << remote_side(); + return -1; + } + if (on_connect) { + EpollOutRequest* req = new(std::nothrow) EpollOutRequest; + if (req == NULL) { + LOG(FATAL) << "Fail to new EpollOutRequest"; + return -1; + } + req->fd = sockfd; + req->timer_id = 0; + req->on_epollout_event = on_connect; + req->data = data; + // A temporary Socket to hold `EpollOutRequest', which will + // be added into epoll device soon + SocketId connect_id; + SocketOptions options; + options.bthread_tag = _io_event.bthread_tag(); + options.user = req; + if (Create(options, &connect_id) != 0) { + LOG(FATAL) << "Fail to create Socket"; + delete req; + return -1; + } + // From now on, ownership of `req' has been transferred to + // `connect_id'. We hold an additional reference here to + // ensure `req' to be valid in this scope + SocketUniquePtr s; + CHECK_EQ(0, Address(connect_id, &s)); + + // Add `sockfd' into epoll so that `HandleEpollOutRequest' will + // be called with `req' when epoll event reaches + if (s->_io_event.RegisterEvent(sockfd, false) != 0) { + const int saved_errno = errno; + PLOG(WARNING) << "Fail to add fd=" << sockfd << " into epoll"; + s->SetFailed(saved_errno, "Fail to add fd=%d into epoll: %s", + (int)sockfd, berror(saved_errno)); + return -1; + } + + // Register a timer for EpollOutRequest. Note that the timeout + // callback has no race with the one above as both of them try + // to `SetFailed' `connect_id' while only one of them can succeed + // It also work when `HandleEpollOutRequest' has already been + // called before adding the timer since it will be removed + // inside destructor of `EpollOutRequest' after leaving this scope + if (abstime) { + int rc = bthread_timer_add(&req->timer_id, *abstime, + HandleEpollOutTimeout, + (void*)connect_id); + if (rc) { + LOG(ERROR) << "Fail to add timer: " << berror(rc); + s->SetFailed(rc, "Fail to add timer: %s", berror(rc)); + return -1; + } + } + + } else { + if (WaitEpollOut(sockfd, false, abstime) != 0) { + PLOG(WARNING) << "Fail to wait EPOLLOUT of fd=" << sockfd; + return -1; + } + if (CheckConnected(sockfd) != 0) { + return -1; + } + } + return sockfd.release(); +} + +int Socket::CheckConnected(int sockfd) { + if (sockfd == STREAM_FAKE_FD) { + return 0; + } + int err = 0; + socklen_t errlen = sizeof(err); + if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &errlen) < 0) { + PLOG(ERROR) << "Fail to getsockopt of fd=" << sockfd; + return -1; + } + if (err != 0) { + CHECK_NE(err, EINPROGRESS); + errno = err; + return -1; + } + + if (FLAGS_log_connected) { + butil::EndPoint local_point; + CHECK_EQ(0, butil::get_local_side(sockfd, &local_point)); + LOG(INFO) << "Connected to " << remote_side() + << " via fd=" << sockfd << " SocketId=" << id() + << " local_side=" << local_point; + } + + // Doing SSL handshake after TCP connected + return SSLHandshake(sockfd, false); +} + +int Socket::DoConnect(const timespec* abstime, + int (*on_connect)(int, int, void*), void* data) { + if (_conn) { + return _conn->Connect(this, abstime, on_connect, data); + } else { + return Connect(abstime, on_connect, data); + } +} + +int Socket::ConnectIfNot(const timespec* abstime, WriteRequest* req) { + if (_fd.load(butil::memory_order_consume) >= 0) { + return 0; + } + // Set tag for client side socket + _io_event.set_bthread_tag(bthread_self_tag()); + // Have to hold a reference for `req' + SocketUniquePtr s; + ReAddress(&s); + req->set_socket(s.get()); + if (DoConnect(abstime, KeepWriteIfConnected, req) < 0) { + return -1; + } + s.release(); + return 1; +} + +void Socket::WakeAsEpollOut() { + _epollout_butex->fetch_add(1, butil::memory_order_release); + bthread::butex_wake_except(_epollout_butex, INVALID_BTHREAD); +} + +int Socket::OnOutputEvent(void* user_data, uint32_t, + const bthread_attr_t&) { + auto id = reinterpret_cast(user_data); + SocketUniquePtr s; + // Since Sockets might have been `SetFailed' before they were + // added into epoll, these sockets miss the signal inside + // `SetFailed' and therefore must be signalled here using + // `AddressFailedAsWell' to prevent waiting forever + if (AddressFailedAsWell(id, &s) < 0) { + // Ignore recycled sockets + return -1; + } + + EpollOutRequest* req = dynamic_cast(s->user()); + if (req != NULL) { + return s->HandleEpollOutRequest(0, req); + } + + // Currently `WaitEpollOut' needs `_epollout_butex' + // TODO(jiangrujie): Remove this in the future + s->_epollout_butex->fetch_add(1, butil::memory_order_relaxed); + bthread::butex_wake_except(s->_epollout_butex, 0); + return 0; +} + +void Socket::HandleEpollOutTimeout(void* arg) { + SocketId id = (SocketId)arg; + SocketUniquePtr s; + if (Address(id, &s) != 0) { + return; + } + EpollOutRequest* req = dynamic_cast(s->user()); + if (req == NULL) { + LOG(FATAL) << "Impossible! SocketUser MUST be EpollOutRequest here"; + return; + } + s->HandleEpollOutRequest(ETIMEDOUT, req); +} + +int Socket::HandleEpollOutRequest(int error_code, EpollOutRequest* req) { + // Only one thread can `SetFailed' this `Socket' successfully + // Also after this `req' will be destroyed when its reference + // hits zero + if (SetFailed() != 0) { + return -1; + } + // We've got the right to call user callback + // The timer will be removed inside destructor of EpollOutRequest + butil::ignore_result(_io_event.UnregisterEvent(req->fd, false)); + return req->on_epollout_event(req->fd, error_code, req->data); +} + +void Socket::AfterAppConnected(int err, void* data) { + WriteRequest* req = static_cast(data); + if (err == 0) { + Socket* const s = req->get_socket(); + SharedPart* sp = s->GetSharedPart(); + if (sp) { + sp->num_continuous_connect_timeouts.store(0, butil::memory_order_relaxed); + } + // requests are not setup yet. check the comment on Setup() in Write() + req->Setup(s); + bthread_t th; + bthread_attr_t attr = BTHREAD_ATTR_NORMAL; + bthread_attr_set_name(&attr, "KeepWrite"); + if (bthread_start_background( + &th, &attr, KeepWrite, req) != 0) { + PLOG(WARNING) << "Fail to start KeepWrite"; + KeepWrite(req); + } + } else { + SocketUniquePtr s(req->get_socket()); + if (err == ETIMEDOUT) { + SharedPart* sp = s->GetOrNewSharedPart(); + if (sp->num_continuous_connect_timeouts.fetch_add( + 1, butil::memory_order_relaxed) + 1 >= + FLAGS_connect_timeout_as_unreachable) { + // the race between store and fetch_add(in another thread) is + // OK since a critial error is about to return. + sp->num_continuous_connect_timeouts.store( + 0, butil::memory_order_relaxed); + err = ENETUNREACH; + } + } + + s->SetFailed(err, "Fail to connect %s: %s", + s->description().c_str(), berror(err)); + s->ReleaseAllFailedWriteRequests(req); + } +} + +static void* RunClosure(void* arg) { + google::protobuf::Closure* done = (google::protobuf::Closure*)arg; + done->Run(); + return NULL; +} + +int Socket::KeepWriteIfConnected(int fd, int err, void* data) { + WriteRequest* req = static_cast(data); + Socket* s = req->get_socket(); + if (err == 0 && s->ssl_state() == SSL_CONNECTING) { + // Run ssl connect in a new bthread to avoid blocking + // the current bthread (thus blocking the EventDispatcher) + bthread_t th; + std::unique_ptr thrd_func( + NewCallback(CheckConnectedAndKeepWrite, fd, err, data)); + bthread_attr_t attr = BTHREAD_ATTR_NORMAL; + bthread_attr_set_name(&attr, "CheckConnectedAndKeepWrite"); + if ((err = bthread_start_background(&th, &attr, RunClosure, thrd_func.get())) == 0) { + thrd_func.release(); + return 0; + } else { + PLOG(ERROR) << "Fail to start bthread"; + // Fall through with non zero `err' + } + } + CheckConnectedAndKeepWrite(fd, err, data); + return 0; +} + +void Socket::CheckConnectedAndKeepWrite(int fd, int err, void* data) { + butil::fd_guard sockfd(fd); + WriteRequest* req = static_cast(data); + Socket* s = req->get_socket(); + if (NULL == s->_conn) { + CHECK_GE(sockfd, 0); + } + if (err == 0 && s->CheckConnected(sockfd) == 0 + && s->ResetFileDescriptor(sockfd) == 0) { + if (s->CreatedByConnect()) { + g_vars->channel_conn << 1; + } + if (s->_app_connect) { + s->_app_connect->StartConnect(req->get_socket(), AfterAppConnected, req); + } else { + // Successfully created a connection + AfterAppConnected(0, req); + } + // Release this socket for KeepWrite + sockfd.release(); + } else { + if (err == 0) { + err = errno ? errno : -1; + } + AfterAppConnected(err, req); + } +} + +inline int SetError(bthread_id_t id_wait, int ec) { + if (id_wait != INVALID_BTHREAD_ID) { + bthread_id_error(id_wait, ec); + return 0; + } else { + errno = ec; + return -1; + } +} + +int Socket::ConductError(bthread_id_t id_wait) { + pthread_mutex_lock(&_id_wait_list_mutex); + if (Failed()) { + const int error_code = non_zero_error_code(); + if (id_wait != INVALID_BTHREAD_ID) { + const std::string error_text = _error_text; + pthread_mutex_unlock(&_id_wait_list_mutex); + bthread_id_error2(id_wait, error_code, error_text); + return 0; + } else { + pthread_mutex_unlock(&_id_wait_list_mutex); + errno = error_code; + return -1; + } + } else { + pthread_mutex_unlock(&_id_wait_list_mutex); + return 1; + } +} + +X509* Socket::GetPeerCertificate() const { + if (ssl_state() != SSL_CONNECTED) { + return NULL; + } + BAIDU_SCOPED_LOCK(_ssl_session_mutex); + return SSL_get_peer_certificate(_ssl_session); +} + +int Socket::Write(butil::IOBuf* data, const WriteOptions* options_in) { + WriteOptions opt; + if (options_in) { + opt = *options_in; + } + // An auth write (opt.auth_flags != 0) may carry an empty data buffer: some + // protocols (e.g. mysql) read the server greeting first and send their real + // bytes from the connection-phase handler, not from `data` here. + if (data->empty() && !opt.auth_flags) { + return SetError(opt.id_wait, EINVAL); + } + if (opt.pipelined_count > MAX_PIPELINED_COUNT) { + LOG(ERROR) << "pipelined_count=" << opt.pipelined_count + << " is too large"; + return SetError(opt.id_wait, EOVERFLOW); + } + if (Failed()) { + const int rc = ConductError(opt.id_wait); + if (rc <= 0) { + return rc; + } + } + + if (!opt.ignore_eovercrowded && _overcrowded) { + return SetError(opt.id_wait, EOVERCROWDED); + } + + WriteRequest* req = butil::get_object(); + if (!req) { + return SetError(opt.id_wait, ENOMEM); + } + + req->data.swap(*data); + // Set `req->next' to UNCONNECTED so that the KeepWrite thread will + // wait until it points to a valid WriteRequest or NULL. + req->next = WriteRequest::UNCONNECTED; + req->id_wait = opt.id_wait; + req->clear_and_set_control_bits(opt.notify_on_success, opt.shutdown_write); + req->set_pipelined_count_and_user_message( + opt.pipelined_count, DUMMY_USER_MESSAGE, opt.auth_flags); + return StartWrite(req, opt); +} + +int Socket::Write(SocketMessagePtr<>& msg, const WriteOptions* options_in) { + WriteOptions opt; + if (options_in) { + opt = *options_in; + } + if (opt.pipelined_count > MAX_PIPELINED_COUNT) { + LOG(ERROR) << "pipelined_count=" << opt.pipelined_count + << " is too large"; + return SetError(opt.id_wait, EOVERFLOW); + } + + if (Failed()) { + const int rc = ConductError(opt.id_wait); + if (rc <= 0) { + return rc; + } + } + + if (!opt.ignore_eovercrowded && _overcrowded) { + return SetError(opt.id_wait, EOVERCROWDED); + } + + WriteRequest* req = butil::get_object(); + if (!req) { + return SetError(opt.id_wait, ENOMEM); + } + + // Set `req->next' to UNCONNECTED so that the KeepWrite thread will + // wait until it points to a valid WriteRequest or NULL. + req->next = WriteRequest::UNCONNECTED; + req->id_wait = opt.id_wait; + req->clear_and_set_control_bits(opt.notify_on_success, opt.shutdown_write); + req->set_pipelined_count_and_user_message( + opt.pipelined_count, msg.release(), opt.auth_flags); + return StartWrite(req, opt); +} + +int Socket::StartWrite(WriteRequest* req, const WriteOptions& opt) { + // Release fence makes sure the thread getting request sees *req + WriteRequest* const prev_head = + _write_head.exchange(req, butil::memory_order_release); + if (prev_head != NULL) { + // Someone is writing to the fd. The KeepWrite thread may spin + // until req->next to be non-UNCONNECTED. This process is not + // lock-free, but the duration is so short(1~2 instructions, + // depending on compiler) that the spin rarely occurs in practice + // (I've not seen any spin in highly contended tests). + req->next = prev_head; + return 0; + } + + int saved_errno = 0; + bthread_t th; + bthread_attr_t attr = BTHREAD_ATTR_NORMAL; + bthread_attr_set_name(&attr, "KeepWrite"); + SocketUniquePtr ptr_for_keep_write; + ssize_t nw = 0; + int ret = 0; + + // We've got the right to write. + req->next = NULL; + + // Fast fail when write has been shutdown. + if (_is_write_shutdown) { + goto FAIL_TO_WRITE; + } + _is_write_shutdown = req->need_shutdown_write(); + + // Connect to remote_side() if not. + ret = ConnectIfNot(opt.abstime, req); + if (ret < 0) { + saved_errno = errno; + SetFailed(errno, "Fail to connect %s directly: %m", description().c_str()); + goto FAIL_TO_WRITE; + } else if (ret == 1) { + // We are doing connection. Callback `KeepWriteIfConnected' + // will be called with `req' at any moment after + return 0; + } + + // NOTE: Setup() MUST be called after Connect which may call app_connect, + // which is assumed to run before any SocketMessage.AppendAndDestroySelf() + // in some protocols(namely RTMP). + req->Setup(this); + + if (opt.write_in_background || ssl_state() != SSL_OFF) { + // Writing into SSL may block the current bthread, always write + // in the background. + goto KEEPWRITE_IN_BACKGROUND; + } + + // Write once in the calling thread. If the write is not complete, + // continue it in KeepWrite thread. + if (_conn) { + butil::IOBuf* data_arr[1] = { &req->data }; + nw = _conn->CutMessageIntoFileDescriptor(fd(), data_arr, 1); + } else { + nw = _transport->CutFromIOBuf(&req->data); + } + if (nw < 0) { + // RTMP may return EOVERCROWDED + if (errno != EAGAIN && errno != EOVERCROWDED) { + saved_errno = errno; + // EPIPE is common in pooled connections + backup requests. + PLOG_IF(WARNING, errno != EPIPE) << "Fail to write into " << *this; + SetFailed(saved_errno, "Fail to write into %s: %s", + description().c_str(), berror(saved_errno)); + goto FAIL_TO_WRITE; + } + } else { + AddOutputBytes(nw); + } + if (IsWriteComplete(req, true, NULL)) { + ReturnSuccessfulWriteRequest(req); + return 0; + } + +KEEPWRITE_IN_BACKGROUND: + ReAddress(&ptr_for_keep_write); + req->set_socket(ptr_for_keep_write.release()); + if (bthread_start_background(&th, &attr, + KeepWrite, req) != 0) { + LOG(FATAL) << "Fail to start KeepWrite"; + KeepWrite(req); + } + return 0; + +FAIL_TO_WRITE: + // `SetFailed' before `ReturnFailedWriteRequest' (which will calls + // `on_reset' callback inside the id object) so that we immediately + // know this socket has failed inside the `on_reset' callback + ReleaseAllFailedWriteRequests(req); + errno = saved_errno; + return -1; +} + +static const size_t DATA_LIST_MAX = 256; + +void* Socket::KeepWrite(void* void_arg) { + g_vars->nkeepwrite << 1; + WriteRequest* req = static_cast(void_arg); + SocketUniquePtr s(req->get_socket()); + + // When error occurs, spin until there's no more requests instead of + // returning directly otherwise _write_head is permantly non-NULL which + // makes later Write() abnormal. + WriteRequest* cur_tail = NULL; + do { + // req was written, skip it. + bool need_shutdown = false; + if (req->next != NULL && req->data.empty()) { + WriteRequest* const saved_req = req; + need_shutdown = req->need_shutdown_write(); + req = req->next; + s->ReturnSuccessfulWriteRequest(saved_req); + } + if (need_shutdown) { + LOG(WARNING) << "Shutdown write of " << *s; + break; + } + + const ssize_t nw = s->DoWrite(req); + if (nw < 0) { + if (errno != EAGAIN && errno != EOVERCROWDED) { + const int saved_errno = errno; + PLOG(WARNING) << "Fail to keep-write into " << *s; + s->SetFailed(saved_errno, "Fail to keep-write into %s: %s", + s->description().c_str(), berror(saved_errno)); + break; + } + } else { + s->AddOutputBytes(nw); + } + // Release WriteRequest until non-empty data, last request or shutdown write. + while (req->next != NULL && req->data.empty()) { + WriteRequest* const saved_req = req; + need_shutdown = req->need_shutdown_write(); + req = req->next; + s->ReturnSuccessfulWriteRequest(saved_req); + if (need_shutdown) { + break; + } + } + if (need_shutdown) { + LOG(WARNING) << "Shutdown write of " << *s; + break; + } + // TODO(gejun): wait for epollout when we actually have written + // all the data. This weird heuristic reduces 30us delay... + // Update(12/22/2015): seem not working. better switch to correct code. + // Update(1/8/2016, r31823): Still working. + // Update(8/15/2017): Not working, performance downgraded. + //if (nw <= 0 || req->data.empty()/*note*/) { + if (nw <= 0) { + // NOTE: Waiting epollout within timeout is a must to force + // KeepWrite to check and setup pending WriteRequests periodically, + // which may turn on _overcrowded to stop pending requests from + // growing infinitely. + const timespec duetime = + butil::milliseconds_from_now(WAIT_EPOLLOUT_TIMEOUT_MS); + bool pollin = s->_transport->HasOnEdgeTrigger(); + int ret = s->_transport->WaitEpollOut(s->_epollout_butex, pollin, duetime); + if (ret == 1) { + break; + } + } + if (NULL == cur_tail) { + for (cur_tail = req; cur_tail->next != NULL; + cur_tail = cur_tail->next); + } + // Return when there's no more WriteRequests and req is completely + // written. + if (s->IsWriteComplete(cur_tail, (req == cur_tail), &cur_tail)) { + CHECK_EQ(cur_tail, req); + s->ReturnSuccessfulWriteRequest(req); + return NULL; + } + } while (1); + + // Error occurred, release all requests until no new requests. + s->ReleaseAllFailedWriteRequests(req); + return NULL; +} + +ssize_t Socket::DoWrite(WriteRequest* req) { + // Group butil::IOBuf in the list into a batch array. + butil::IOBuf* data_list[DATA_LIST_MAX]; + size_t ndata = 0; + for (WriteRequest* p = req; p != NULL && ndata < DATA_LIST_MAX; + p = p->next) { + data_list[ndata++] = &p->data; + if (p->need_shutdown_write()) { + // Write WriteRequest until shutdown write. + _is_write_shutdown = true; + break; + } + } + + if (ssl_state() == SSL_OFF) { + // Write IOBuf in the batch array into the fd. + if (_conn) { + return _conn->CutMessageIntoFileDescriptor(fd(), data_list, ndata); + } else { + return _transport->CutFromIOBufList(data_list, ndata); + } + } + + CHECK_EQ(SSL_CONNECTED, ssl_state()); + if (_conn) { + // TODO: Separate SSL stuff from SocketConnection + BAIDU_SCOPED_LOCK(_ssl_session_mutex); + return _conn->CutMessageIntoSSLChannel(_ssl_session, data_list, ndata); + } + int ssl_error = 0; + ssize_t nw = 0; + { + BAIDU_SCOPED_LOCK(_ssl_session_mutex); + nw = butil::IOBuf::cut_multiple_into_SSL_channel(_ssl_session, data_list, ndata, &ssl_error); + } + switch (ssl_error) { + case SSL_ERROR_NONE: + break; + + case SSL_ERROR_WANT_READ: + // Disable renegotiation + errno = EPROTO; + return -1; + + case SSL_ERROR_WANT_WRITE: + errno = EAGAIN; + break; + + default: { + const unsigned long e = ERR_get_error(); + if (e != 0) { + LOG(WARNING) << "Fail to write into ssl_fd=" << fd() << ": " + << SSLError(e); + errno = ESSL; + } else { + // System error with corresponding errno set + PLOG(WARNING) << "Fail to write into ssl_fd=" << fd(); + } + break; + } + } + return nw; +} + +int Socket::SSLHandshake(int fd, bool server_mode) { + if (_ssl_ctx == NULL) { + if (server_mode) { + LOG(ERROR) << "Lack SSL configuration to handle SSL request"; + return -1; + } + return 0; + } + + // TODO: Reuse ssl session id for client + if (_ssl_session) { + // Free the last session, which may be deprecated when socket failed + SSL_free(_ssl_session); + } + _ssl_session = CreateSSLSession(_ssl_ctx->raw_ctx, id(), fd, server_mode); + if (_ssl_session == NULL) { + LOG(ERROR) << "Fail to CreateSSLSession"; + return -1; + } +#if defined(SSL_CTRL_SET_TLSEXT_HOSTNAME) || defined(USE_MESALINK) + if (!_ssl_ctx->sni_name.empty()) { + SSL_set_tlsext_host_name(_ssl_session, _ssl_ctx->sni_name.c_str()); + } +#endif + + _ssl_state = SSL_CONNECTING; + + // Bound the handshake by a deadline; without it, a peer that completes + // the TCP handshake but never returns a TLS Hello (e.g. server not + // configured for SSL) would park this bthread on bthread_fd_wait + // forever. That bthread holds a Socket reference via WriteRequest, so + // the underlying fd would never be recycled and the connection would + // remain ESTABLISHED indefinitely. + const int handshake_timeout_ms = FLAGS_ssl_handshake_timeout_ms; + timespec abstime_storage; + const timespec* abstime = NULL; + if (handshake_timeout_ms > 0) { + abstime_storage = butil::milliseconds_from_now(handshake_timeout_ms); + abstime = &abstime_storage; + } + + // Loop until SSL handshake has completed. For SSL_ERROR_WANT_READ/WRITE, + // we use bthread_fd_timedwait as polling mechanism instead of + // EventDispatcher as it may confuse the origin event processing code. + while (true) { + ERR_clear_error(); + int rc = SSL_do_handshake(_ssl_session); + if (rc == 1) { + // In client, check if server returned ALPN selection is acceptable. + if (!server_mode && !_ssl_ctx->alpn_protocols.empty()) { + const unsigned char *alpn_proto; + unsigned int alpn_proto_length; + SSL_get0_alpn_selected(_ssl_session, &alpn_proto, &alpn_proto_length); + if (!alpn_proto) { + LOG(ERROR) << "Server returned no ALPN protocol"; + return -1; + } + + std::string alpn_protocol( + reinterpret_cast(alpn_proto), + alpn_proto_length + ); + if ( + std::find( + _ssl_ctx->alpn_protocols.begin(), + _ssl_ctx->alpn_protocols.end(), + alpn_protocol + ) == _ssl_ctx->alpn_protocols.end() + ) { + LOG(ERROR) << "Server returned unacceptable ALPN protocol: " + << alpn_protocol; + return -1; + } + } + + _ssl_state = SSL_CONNECTED; + // Adding a BIO layer requires calling BIO_flush manually after SSL_write, + // which could trigger EAGAIN for large packets. However, it's very tedious + // to handle EAGAIN from both SSL_write and BIO_flush under current implementation. + // Also, BIO is a bit outdated for modern TCP as it already contains buffering. + // We decide to disable BIO. + // AddBIOBuffer(_ssl_session, fd, FLAGS_ssl_bio_buffer_size); + return 0; + } + + int ssl_error = SSL_get_error(_ssl_session, rc); + switch (ssl_error) { + case SSL_ERROR_WANT_READ: +#if defined(OS_LINUX) + if (bthread_fd_timedwait(fd, EPOLLIN, abstime) != 0) { +#elif defined(OS_MACOSX) + if (bthread_fd_timedwait(fd, EVFILT_READ, abstime) != 0) { +#endif + if (errno == ETIMEDOUT) { + LOG(WARNING) << "SSL handshake timed out after " + << handshake_timeout_ms + << "ms while waiting for peer data on fd=" + << fd << " remote_side=" << _remote_side; + } + return -1; + } + break; + + case SSL_ERROR_WANT_WRITE: +#if defined(OS_LINUX) + if (bthread_fd_timedwait(fd, EPOLLOUT, abstime) != 0) { +#elif defined(OS_MACOSX) + if (bthread_fd_timedwait(fd, EVFILT_WRITE, abstime) != 0) { +#endif + if (errno == ETIMEDOUT) { + LOG(WARNING) << "SSL handshake timed out after " + << handshake_timeout_ms + << "ms while waiting to send on fd=" << fd + << " remote_side=" << _remote_side; + } + return -1; + } + break; + + default: { + const unsigned long e = ERR_get_error(); + if (ssl_error == SSL_ERROR_ZERO_RETURN || e == 0) { + errno = ECONNRESET; + LOG(ERROR) << "SSL connection was shutdown by peer: " << _remote_side; + } else if (ssl_error == SSL_ERROR_SYSCALL) { + PLOG(ERROR) << "Fail to SSL_do_handshake"; + } else { + errno = ESSL; + LOG(ERROR) << "Fail to SSL_do_handshake: " << SSLError(e); + } + return -1; + } + } + } +} + +ssize_t Socket::DoRead(size_t size_hint) { + if (ssl_state() == SSL_UNKNOWN) { + int error_code = 0; + _ssl_state = DetectSSLState(fd(), &error_code); + switch (ssl_state()) { + case SSL_UNKNOWN: + if (error_code == 0) { // EOF + return 0; + } else { + errno = error_code; + return -1; + } + + case SSL_CONNECTING: + if (SSLHandshake(fd(), true) != 0) { + errno = EINVAL; + return -1; + } + break; + + case SSL_CONNECTED: + CHECK(false) << "Impossible to reach here"; + break; + + case SSL_OFF: + break; + } + } + // _ssl_state has been set + if (ssl_state() == SSL_OFF) { + if (_force_ssl) { + errno = ESSL; + return -1; + } + return _read_buf.append_from_file_descriptor(fd(), size_hint); + } + + CHECK_EQ(SSL_CONNECTED, ssl_state()); + int ssl_error = 0; + ssize_t nr = 0; + { + BAIDU_SCOPED_LOCK(_ssl_session_mutex); + nr = _read_buf.append_from_SSL_channel(_ssl_session, &ssl_error, size_hint); + } + switch (ssl_error) { + case SSL_ERROR_NONE: // `nr' > 0 + break; + + case SSL_ERROR_WANT_READ: + // Regard this error as EAGAIN + errno = EAGAIN; + break; + + case SSL_ERROR_WANT_WRITE: + // Disable renegotiation + errno = EPROTO; + return -1; + + default: { + const unsigned long e = ERR_get_error(); + if (nr == 0) { + if (ssl_error != SSL_ERROR_ZERO_RETURN) { + // Unexpected EOF without proper SSL shutdown (close_notify) + LOG(WARNING) << "Fail to read from ssl_fd=" << fd() + << ": unexpected ssl_error=" << ssl_error; + errno = ESSL; + return -1; + } + // Clean SSL shutdown (close_notify received) + } else if (e != 0) { + LOG(WARNING) << "Fail to read from ssl_fd=" << fd() + << ": " << SSLError(e); + errno = ESSL; + } else { + int saved_errno = errno; + // System error with corresponding errno set. + bool is_fatal_error = (ssl_error != SSL_ERROR_ZERO_RETURN && + ssl_error != SSL_ERROR_SYSCALL) || + BIO_fd_non_fatal_error(saved_errno) != 0 || + nr < 0; + PLOG_IF(WARNING, is_fatal_error) << "Fail to read from ssl_fd=" << fd(); + errno = saved_errno; + } + break; + } + } + return nr; +} + +int Socket::FightAuthentication(int* auth_error) { + // Use relaxed fence since `bthread_id_trylock' ensures thread safety + // Here `flag_error' just acts like a cache information + uint64_t flag_error = _auth_flag_error.load(butil::memory_order_relaxed); + if (flag_error & AUTH_FLAG) { + // Already authenticated + *auth_error = (int32_t)(flag_error & 0xFFFFFFFFul); + return EINVAL; + } + if (0 == bthread_id_trylock(_auth_id, NULL)) { + // Winner + return 0; + } else { + // Use relaxed fence since `bthread_id_join' has acquire fence to ensure + // `_auth_flag_error' to be the latest value + bthread_id_join(_auth_id); + flag_error = _auth_flag_error.load(butil::memory_order_relaxed); + *auth_error = (int32_t)(flag_error & 0xFFFFFFFFul); + return EINVAL; + } +} + +void Socket::SetAuthentication(int error_code) { + uint64_t expected = 0; + // `bthread_id_destroy' has release fence to prevent this CAS being + // reordered after it. + if (_auth_flag_error.compare_exchange_strong( + expected, (AUTH_FLAG | error_code), + butil::memory_order_relaxed)) { + // As expected + if (error_code != 0) { + SetFailed(error_code, "Fail to authenticate %s", description().c_str()); + } + CHECK_EQ(0, bthread_id_unlock_and_destroy(_auth_id)); + } +} + +AuthContext* Socket::mutable_auth_context() { + if (_auth_context != NULL) { + LOG(FATAL) << "Impossible! This function is supposed to be called " + "only once when verification succeeds in server side"; + return NULL; + } + _auth_context = new(std::nothrow) AuthContext(); + CHECK(_auth_context); + return _auth_context; +} + +int Socket::OnInputEvent(void* user_data, uint32_t events, + const bthread_attr_t& thread_attr) { + auto id = reinterpret_cast(user_data); + SocketUniquePtr s; + if (Address(id, &s) < 0) { + return -1; + } + if (!s->_transport->HasOnEdgeTrigger()) { + // Callback can be NULL when receiving error epoll events + // (Added into epoll by `WaitConnected') + return 0; + } + if (s->fd() < 0) { +#if defined(OS_LINUX) + CHECK(!(events & EPOLLIN)) << "epoll_events=" << events; +#elif defined(OS_MACOSX) + CHECK((short)events != EVFILT_READ) << "kqueue filter=" << events; +#endif + return -1; + } + + // if (events & has_epollrdhup) { + // s->_eof = 1; + // } + // Passing e[i].events causes complex visibility issues and + // requires stronger memory fences, since reading the fd returns + // error as well, we don't pass the events. + if (s->_nevent.fetch_add(1, butil::memory_order_acq_rel) == 0) { + // According to the stats, above fetch_add is very effective. In a + // server processing 1 million requests per second, this counter + // is just 1500~1700/s + g_vars->neventthread << 1; + + // transfer ownership as well, don't use s anymore! + Socket* const p = s.release(); + + bthread_attr_t attr = thread_attr; + attr.keytable_pool = p->_keytable_pool; + attr.tag = bthread_self_tag(); + // Only event dispatcher thread has flag BTHREAD_GLOBAL_PRIORITY + attr.flags = attr.flags & (~BTHREAD_GLOBAL_PRIORITY); + p->_transport->ProcessEvent(attr); + } + return 0; +} + +void DereferenceSocket(Socket* s) { + if (s) { + s->Dereference(); + } +} + +void Socket::UpdateStatsEverySecond(int64_t now_ms) { + SharedPart* sp = GetSharedPart(); + if (sp) { + sp->UpdateStatsEverySecond(now_ms); + } +} + +template +struct ObjectPtr { + ObjectPtr(const T* obj) : _obj(obj) {} + const T* _obj; +}; + +template +ObjectPtr ShowObject(const T* obj) { return ObjectPtr(obj); } + +template +std::ostream& operator<<(std::ostream& os, const ObjectPtr& obj) { + if (obj._obj != NULL) { + os << '(' << butil::class_name_str(*obj._obj) << "*)"; + } + return os << obj._obj; +} + +void Socket::DebugSocket(std::ostream& os, SocketId id) { + SocketUniquePtr ptr; + int ret = AddressFailedAsWell(id, &ptr); + if (ret < 0) { + os << "SocketId=" << id << " is invalid or recycled"; + return; + } else if (ret > 0) { + // NOTE: Printing a broken socket w/o HC is informational for + // debugging referential issues. + // if (ptr->_health_check_interval_s <= 0) { + // // Sockets without HC will soon be destroyed + // os << "Invalid SocketId=" << id; + // return; + // } + os << "# This is a broken Socket\n"; + } + const uint64_t vref = ptr->versioned_ref(); + size_t npipelined = 0; + size_t idsizes[4]; + size_t nidsize = 0; + { + BAIDU_SCOPED_LOCK(ptr->_pipeline_mutex); + if (ptr->_pipeline_q) { + npipelined = ptr->_pipeline_q->size(); + } + } + { + BAIDU_SCOPED_LOCK(ptr->_id_wait_list_mutex); + if (bthread::get_sizes) { + nidsize = bthread::get_sizes( + &ptr->_id_wait_list, idsizes, arraysize(idsizes)); + } + } + const int preferred_index = ptr->preferred_index(); + SharedPart* sp = ptr->GetSharedPart(); + os << "version=" << VersionOfVRef(vref); + if (sp) { + os << "\nshared_part={\n ref_count=" << sp->ref_count() + << "\n socket_pool="; + SocketPool* pool = sp->socket_pool.load(butil::memory_order_consume); + if (pool) { + os << '['; + std::vector pooled_sockets; + pool->ListSockets(&pooled_sockets, 0); + for (size_t i = 0; i < pooled_sockets.size(); ++i) { + if (i) { + os << ' '; + } + os << pooled_sockets[i]; + } + os << "]\n numfree=" + << pool->_numfree.load(butil::memory_order_relaxed) + << "\n numinflight=" + << pool->_numinflight.load(butil::memory_order_relaxed); + } else { + os << "null"; + } + os << "\n creator_socket=" << sp->creator_socket_id + << "\n in_size=" << sp->in_size.load(butil::memory_order_relaxed) + << "\n in_num_messages=" << sp->in_num_messages.load(butil::memory_order_relaxed) + << "\n out_size=" << sp->out_size.load(butil::memory_order_relaxed) + << "\n out_num_messages=" << sp->out_num_messages.load(butil::memory_order_relaxed) + << "\n}"; + } + const int fd = ptr->_fd.load(butil::memory_order_relaxed); + os << "\nnref=" << NRefOfVRef(vref) - 1 + // ^ + // minus the ref of current callsite(calling PrintSocket) + << "\nnevent=" << ptr->_nevent.load(butil::memory_order_relaxed) + << "\nfd=" << fd + << "\ntos=" << ptr->_tos + << "\nreset_fd_to_now=" << butil::cpuwide_time_us() - ptr->_reset_fd_real_us << "us" + << "\nremote_side=" << ptr->_remote_side + << "\nlocal_side=" << ptr->_local_side + << "\non_et_events=" << (void*)ptr->_on_edge_triggered_events + << "\nuser=" << ShowObject(ptr->_user) + << "\nthis_id=" << ptr->id() + << "\npreferred_index=" << preferred_index; + InputMessenger* messenger = dynamic_cast(ptr->user()); + if (messenger != NULL) { + os << " (" << messenger->NameOfProtocol(preferred_index) << ')'; + } + const int64_t cpuwide_now = butil::cpuwide_time_us(); + os << "\nhc_count=" << ptr->_hc_count + << "\navg_input_msg_size=" << ptr->_avg_msg_size + // NOTE: We're assuming that butil::IOBuf.size() is thread-safe, it is now + // however it's not guaranteed. + << "\nread_buf=" << ptr->_read_buf.size() + << "\nlast_read_to_now=" << cpuwide_now - ptr->_last_readtime_us << "us" + << "\nlast_write_to_now=" << cpuwide_now - ptr->_last_writetime_us << "us" + << "\novercrowded=" << ptr->_overcrowded; + os << "\nid_wait_list={"; + for (size_t i = 0; i < nidsize; ++i) { + if (i) { + os << ' '; + } + os << idsizes[i]; + } + os << '}'; + Destroyable* const parsing_context = ptr->parsing_context(); + Describable* parsing_context_desc = dynamic_cast(parsing_context); + if (parsing_context_desc) { + os << "\nparsing_context=" << butil::class_name_str(*parsing_context) << '{'; + DescribeOptions opt; + opt.verbose = true; + IndentingOStream os2(os, 2); + parsing_context_desc->Describe(os2, opt); + os << '}'; + } else { + os << "\nparsing_context=" << ShowObject(parsing_context); + } + const SSLState ssl_state = ptr->ssl_state(); + os << "\npipeline_q=" << npipelined + << "\nhc_interval_s=" << ptr->_health_check_interval_s + << "\nis_hc_related_ref_held=" << ptr->_is_hc_related_ref_held + << "\nninprocess=" << ptr->_ninprocess.load(butil::memory_order_relaxed) + << "\nauth_flag_error=" << ptr->_auth_flag_error.load(butil::memory_order_relaxed) + << "\nauth_id=" << ptr->_auth_id.value + << "\nauth_context=" << ptr->_auth_context + << "\nlogoff_flag=" << ptr->_logoff_flag.load(butil::memory_order_relaxed) + << "\n_additional_ref_status=" + << ptr->additional_ref_status() + << "\ntotal_streams_buffer_size=" + << ptr->_total_streams_unconsumed_size.load(butil::memory_order_relaxed) + << "\nninflight_app_health_check=" + << ptr->_ninflight_app_health_check.load(butil::memory_order_relaxed) + << "\nagent_socket_id="; + const SocketId asid = ptr->_agent_socket_id.load(butil::memory_order_relaxed); + if (asid != INVALID_SOCKET_ID) { + os << asid; + } else { + os << "(none)"; + } + os << "\ncid=" << ptr->_correlation_id + << "\nwrite_head=" << ptr->_write_head.load(butil::memory_order_relaxed) + << "\nssl_state=" << SSLStateToString(ssl_state); + const SocketSSLContext* ssl_ctx = ptr->_ssl_ctx.get(); + if (ssl_ctx) { + os << "\ninitial_ssl_ctx=" << ssl_ctx->raw_ctx; + if (!ssl_ctx->sni_name.empty()) { + os << "\nsni_name=" << ssl_ctx->sni_name; + } + } + if (ssl_state == SSL_CONNECTED) { + os << "\nssl_session={\n "; + Print(os, ptr->_ssl_session, "\n "); + os << "\n}"; + } + + os << "\nis_wirte_shutdown=" << ptr->_is_write_shutdown; + + { + int keepalive = 0; + socklen_t len = sizeof(keepalive); + if (getsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, &len) == 0) { + os << "\nkeepalive=" << keepalive; + } + } + + { + int keepidle = 0; + socklen_t len = sizeof(keepidle); +#if defined(OS_MACOSX) + if (getsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &keepidle, &len) == 0) { + os << "\ntcp_keepalive_time=" << keepidle; + } +#elif defined(OS_LINUX) + if (getsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &keepidle, &len) == 0) { + os << "\ntcp_keepalive_time=" << keepidle; + } +#endif + } + + { + int keepintvl = 0; + socklen_t len = sizeof(keepintvl); +#if defined(OS_MACOSX) + if (getsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, &len) == 0) { + os << "\ntcp_keepalive_intvl=" << keepintvl; + } +#elif defined(OS_LINUX) + if (getsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &keepintvl, &len) == 0) { + os << "\ntcp_keepalive_intvl=" << keepintvl; + } +#endif + } + + { + int keepcnt = 0; + socklen_t len = sizeof(keepcnt); +#if defined(OS_MACOSX) + if (getsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, &len) == 0) { + os << "\ntcp_keepalive_probes=" << keepcnt; + } +#elif defined(OS_LINUX) + if (getsockopt(fd, SOL_TCP, TCP_KEEPCNT, &keepcnt, &len) == 0) { + os << "\ntcp_keepalive_probes=" << keepcnt; + } +#endif + } + +#if defined(OS_LINUX) + { + int tcp_user_timeout = 0; + socklen_t len = sizeof(tcp_user_timeout); + if (getsockopt(fd, SOL_TCP, TCP_USER_TIMEOUT, &tcp_user_timeout, &len) == 0) { + os << "\ntcp_user_timeout=" << tcp_user_timeout; + } + } +#endif + +#if defined(OS_MACOSX) + struct tcp_connection_info ti; + socklen_t len = sizeof(ti); + if (fd >= 0 && getsockopt(fd, IPPROTO_TCP, TCP_CONNECTION_INFO, &ti, &len) == 0) { + os << "\ntcpi={\n state=" << (uint32_t)ti.tcpi_state + << "\n snd_wscale=" << (uint32_t)ti.tcpi_snd_wscale + << "\n rcv_wscale=" << (uint32_t)ti.tcpi_rcv_wscale + << "\n options=" << (uint32_t)ti.tcpi_options + << "\n flags=" << (uint32_t)ti.tcpi_flags + << "\n rto=" << ti.tcpi_rto + << "\n maxseg=" << ti.tcpi_maxseg + << "\n snd_ssthresh=" << ti.tcpi_snd_ssthresh + << "\n snd_cwnd=" << ti.tcpi_snd_cwnd + << "\n snd_wnd=" << ti.tcpi_snd_wnd + << "\n snd_sbbytes=" << ti.tcpi_snd_sbbytes + << "\n rcv_wnd=" << ti.tcpi_rcv_wnd + << "\n srtt=" << ti.tcpi_srtt + << "\n rttvar=" << ti.tcpi_rttvar + << "\n}"; + } +#elif defined(OS_LINUX) + struct tcp_info ti; + socklen_t len = sizeof(ti); + if (fd >= 0 && getsockopt(fd, SOL_TCP, TCP_INFO, &ti, &len) == 0) { + os << "\ntcpi={\n state=" << (uint32_t)ti.tcpi_state + << "\n ca_state=" << (uint32_t)ti.tcpi_ca_state + << "\n retransmits=" << (uint32_t)ti.tcpi_retransmits + << "\n probes=" << (uint32_t)ti.tcpi_probes + << "\n backoff=" << (uint32_t)ti.tcpi_backoff + << "\n options=" << (uint32_t)ti.tcpi_options + << "\n snd_wscale=" << (uint32_t)ti.tcpi_snd_wscale + << "\n rcv_wscale=" << (uint32_t)ti.tcpi_rcv_wscale + << "\n rto=" << ti.tcpi_rto + << "\n ato=" << ti.tcpi_ato + << "\n snd_mss=" << ti.tcpi_snd_mss + << "\n rcv_mss=" << ti.tcpi_rcv_mss + << "\n unacked=" << ti.tcpi_unacked + << "\n sacked=" << ti.tcpi_sacked + << "\n lost=" << ti.tcpi_lost + << "\n retrans=" << ti.tcpi_retrans + << "\n fackets=" << ti.tcpi_fackets + << "\n last_data_sent=" << ti.tcpi_last_data_sent + << "\n last_ack_sent=" << ti.tcpi_last_ack_sent + << "\n last_data_recv=" << ti.tcpi_last_data_recv + << "\n last_ack_recv=" << ti.tcpi_last_ack_recv + << "\n pmtu=" << ti.tcpi_pmtu + << "\n rcv_ssthresh=" << ti.tcpi_rcv_ssthresh + << "\n rtt=" << ti.tcpi_rtt // smoothed + << "\n rttvar=" << ti.tcpi_rttvar + << "\n snd_ssthresh=" << ti.tcpi_snd_ssthresh + << "\n snd_cwnd=" << ti.tcpi_snd_cwnd + << "\n advmss=" << ti.tcpi_advmss + << "\n reordering=" << ti.tcpi_reordering + << "\n}"; + } +#endif + + os << "\nrdma={\n"; + ptr->_transport->Debug(os); + os << "}\n"; + + os << "\nbthread_tag=" << ptr->_io_event.bthread_tag(); +} + +int Socket::CheckHealth() { + if (_hc_count == 0) { + LOG(INFO) << "Checking " << *this; + } + const timespec duetime = + butil::milliseconds_from_now(_hc_option.health_check_timeout_ms); + const int connected_fd = Connect(&duetime, NULL, NULL); + if (connected_fd >= 0) { + ::close(connected_fd); + return 0; + } + return errno; +} + +int Socket::AddStream(StreamId stream_id) { + _stream_mutex.lock(); + if (Failed()) { + _stream_mutex.unlock(); + return -1; + } + if (_stream_set == NULL) { + _stream_set = new std::set(); + } + _stream_set->insert(stream_id); + _stream_mutex.unlock(); + return 0; +} + +int Socket::RemoveStream(StreamId stream_id) { + _stream_mutex.lock(); + if (_stream_set == NULL) { + _stream_mutex.unlock(); + CHECK(false) << "AddStream was not called"; + return -1; + } + _stream_set->erase(stream_id); + _stream_mutex.unlock(); + return 0; +} + +void Socket::ResetAllStreams(int error_code, const std::string& error_text) { + DCHECK(Failed()); + std::set saved_stream_set; + _stream_mutex.lock(); + if (_stream_set != NULL) { + // Not delete _stream_set because there are likely more streams added + // after reviving if the Socket is still in use, or it is to be deleted in + // BeforeRecycled() + saved_stream_set.swap(*_stream_set); + } + _stream_mutex.unlock(); + for (auto it = saved_stream_set.begin(); + it != saved_stream_set.end(); ++it) { + Stream::SetFailed(*it, error_code, "%s", error_text.c_str()); + } +} + +int SocketUser::CheckHealth(Socket* ptr) { + return ptr->CheckHealth(); +} + +void SocketUser::AfterRevived(Socket* ptr) { + LOG(INFO) << "Revived " << *ptr << " (Connectable)"; +} + +////////// SocketPool ////////////// + +inline SocketPool::SocketPool(const SocketOptions& opt) + : _options(opt) + , _remote_side(opt.remote_side) + , _numfree(0) + , _numinflight(0) { +} + +inline SocketPool::~SocketPool() { + for (std::vector::iterator it = _pool.begin(); + it != _pool.end(); ++it) { + SocketUniquePtr ptr; + if (Socket::Address(*it, &ptr) == 0) { + ptr->ReleaseAdditionalReference(); + } + } +} + +inline int SocketPool::GetSocket(SocketUniquePtr* ptr) { + const int connection_pool_size = FLAGS_max_connection_pool_size; + + // In prev rev, SocketPool could be sharded into multiple SubSocketPools to + // reduce thread contentions. The sharding key is mixed from pthread-id so + // that data locality are better kept. + // However sharding also makes the socket more frequently to be created + // and closed, especially in real-world applications that one client + // connects to many servers where one socket is lowly contended, different + // threads accessing the socket may create pooled sockets in different sub + // pools without reusing sockets left in other sub pools, which will + // probably be closed by the CloseIdleConnections thread in socket_map.cpp, + // resulting in frequent-create-and-close of connections. + // Thus the sharding is merely a mechanism only meaningful in benchmarking + // scenarios where one server is connected by one client with many threads. + // Starting from r32203 the sharding capability is removed. + + SocketId sid = 0; + if (connection_pool_size > 0) { + for (;;) { + { + BAIDU_SCOPED_LOCK(_mutex); + if (_pool.empty()) { + break; + } + sid = _pool.back(); + _pool.pop_back(); + } + _numfree.fetch_sub(1, butil::memory_order_relaxed); + // Not address inside the lock since at most time the pooled socket + // is likely to be valid. + if (Socket::Address(sid, ptr) == 0) { + _numinflight.fetch_add(1, butil::memory_order_relaxed); + return 0; + } + } + } + // Not found in pool + SocketOptions opt = _options; + opt.health_check_interval_s = -1; + if (get_client_side_messenger()->Create(opt, &sid) == 0 && + Socket::Address(sid, ptr) == 0) { + _numinflight.fetch_add(1, butil::memory_order_relaxed); + return 0; + } + return -1; +} + +inline void SocketPool::ReturnSocket(Socket* sock) { + // NOTE: save the gflag which may be reloaded at any time. + const int connection_pool_size = FLAGS_max_connection_pool_size; + + // Check if the pool is full. + if (_numfree.fetch_add(1, butil::memory_order_relaxed) < + connection_pool_size) { + const SocketId sid = sock->id(); + BAIDU_SCOPED_LOCK(_mutex); + _pool.push_back(sid); + } else { + // Cancel the addition and close the pooled socket. + _numfree.fetch_sub(1, butil::memory_order_relaxed); + sock->SetFailed(EUNUSED, "Close unused pooled socket"); + } + _numinflight.fetch_sub(1, butil::memory_order_relaxed); +} + +inline void SocketPool::ListSockets(std::vector* out, size_t max_count) { + out->clear(); + // NOTE: size() of vector is thread-unsafe and may return a very + // large value during resizing. + _mutex.lock(); + size_t expected_size = _pool.size(); + if (max_count > 0 && max_count < _pool.size()) { + expected_size = max_count; + } + if (out->capacity() < expected_size) { + _mutex.unlock(); + out->reserve(expected_size + 4); // pool may add sockets. + _mutex.lock(); + } + if (max_count == 0) { + out->insert(out->end(), _pool.begin(), _pool.end()); + } else { + for (size_t i = 0; i < expected_size; ++i) { + out->push_back(_pool[i]); + } + } + _mutex.unlock(); +} + +Socket::SharedPart* Socket::GetOrNewSharedPartSlower() { + // Create _shared_part optimistically. + SharedPart* shared_part = GetSharedPart(); + if (shared_part == NULL) { + shared_part = new SharedPart(id()); + shared_part->AddRefManually(); + SharedPart* expected = NULL; + if (!_shared_part.compare_exchange_strong( + expected, shared_part, butil::memory_order_acq_rel)) { + shared_part->RemoveRefManually(); + CHECK(expected); + shared_part = expected; + } + } + return shared_part; +} + +void Socket::ShareStats(Socket* main_socket) { + SharedPart* main_sp = main_socket->GetOrNewSharedPart(); + main_sp->AddRefManually(); + SharedPart* my_sp = + _shared_part.exchange(main_sp, butil::memory_order_acq_rel); + if (my_sp) { + my_sp->RemoveRefManually(); + } +} + +int Socket::GetPooledSocket(SocketUniquePtr* pooled_socket) { + if (pooled_socket == NULL) { + LOG(ERROR) << "pooled_socket is NULL"; + return -1; + } + SharedPart* main_sp = GetOrNewSharedPart(); + if (main_sp == NULL) { + LOG(ERROR) << "_shared_part is NULL"; + return -1; + } + // Create socket_pool optimistically. + SocketPool* socket_pool = main_sp->socket_pool.load(butil::memory_order_consume); + if (socket_pool == NULL) { + SocketOptions opt; + opt.remote_side = remote_side(); + opt.local_side = butil::EndPoint(local_side().ip, 0); + opt.user = user(); + opt.on_edge_triggered_events = _on_edge_triggered_events; + opt.need_on_edge_trigger = _need_on_edge_trigger; + opt.initial_ssl_ctx = _ssl_ctx; + opt.keytable_pool = _keytable_pool; + opt.app_connect = _app_connect; + opt.socket_mode = _socket_mode; + socket_pool = new SocketPool(opt); + SocketPool* expected = NULL; + if (!main_sp->socket_pool.compare_exchange_strong( + expected, socket_pool, butil::memory_order_acq_rel)) { + delete socket_pool; + CHECK(expected); + socket_pool = expected; + } + } + if (socket_pool->GetSocket(pooled_socket) != 0) { + return -1; + } + (*pooled_socket)->ShareStats(this); + CHECK((*pooled_socket)->parsing_context() == NULL) + << "context=" << (*pooled_socket)->parsing_context() + << " is not NULL when " << *(*pooled_socket) << " is got from" + " SocketPool, the protocol implementation is buggy"; + return 0; +} + +int Socket::ReturnToPool() { + SharedPart* sp = _shared_part.exchange(NULL, butil::memory_order_acquire); + if (sp == NULL) { + LOG(ERROR) << "_shared_part is NULL"; + SetFailed(EINVAL, "_shared_part is NULL"); + return -1; + } + SocketPool* pool = sp->socket_pool.load(butil::memory_order_consume); + if (pool == NULL) { + LOG(ERROR) << "_shared_part->socket_pool is NULL"; + SetFailed(EINVAL, "_shared_part->socket_pool is NULL"); + sp->RemoveRefManually(); + return -1; + } + CHECK(parsing_context() == NULL) + << "context=" << parsing_context() << " is not released when " + << *this << " is returned to SocketPool, the protocol " + "implementation is buggy"; + // NOTE: be careful with the sequence. + // - related fields must be reset before returning to pool + // - sp must be released after returning to pool because it owns pool + _connection_type_for_progressive_read = CONNECTION_TYPE_UNKNOWN; + _controller_released_socket.store(false, butil::memory_order_relaxed); + // Reset the write timestamp to make the returned connection live (longer) + // This is useful for using a fake Socket + SocketConnection impl. to integrate + // 3rd-party client into bRPC (like MySQL Client). + _last_writetime_us.store(butil::cpuwide_time_us(), butil::memory_order_relaxed); + pool->ReturnSocket(this); + sp->RemoveRefManually(); + return 0; +} + +bool Socket::HasSocketPool() const { + SharedPart* sp = GetSharedPart(); + if (sp != NULL) { + return sp->socket_pool.load(butil::memory_order_consume) != NULL; + } + return false; +} + +void Socket::ListPooledSockets(std::vector* out, size_t max_count) { + out->clear(); + SharedPart* sp = GetSharedPart(); + if (sp == NULL) { + return; + } + SocketPool* pool = sp->socket_pool.load(butil::memory_order_consume); + if (pool == NULL) { + return; + } + pool->ListSockets(out, max_count); +} + +bool Socket::GetPooledSocketStats(int* numfree, int* numinflight) { + SharedPart* sp = GetSharedPart(); + if (sp == NULL) { + return false; + } + SocketPool* pool = sp->socket_pool.load(butil::memory_order_consume); + if (pool == NULL) { + return false; + } + *numfree = pool->_numfree.load(butil::memory_order_relaxed); + *numinflight = pool->_numinflight.load(butil::memory_order_relaxed); + return true; +} + +int Socket::GetShortSocket(SocketUniquePtr* short_socket) { + if (short_socket == NULL) { + LOG(ERROR) << "short_socket is NULL"; + return -1; + } + SocketId id; + SocketOptions opt; + opt.remote_side = remote_side(); + opt.local_side = butil::EndPoint(local_side().ip, 0); + opt.user = user(); + opt.on_edge_triggered_events = _on_edge_triggered_events; + opt.need_on_edge_trigger = _need_on_edge_trigger; + opt.initial_ssl_ctx = _ssl_ctx; + opt.keytable_pool = _keytable_pool; + opt.app_connect = _app_connect; + opt.socket_mode = _socket_mode; + if (get_client_side_messenger()->Create(opt, &id) != 0 || + Address(id, short_socket) != 0) { + return -1; + } + (*short_socket)->ShareStats(this); + return 0; +} + +int Socket::GetAgentSocket(SocketUniquePtr* out, bool (*checkfn)(Socket*)) { + SocketId id = _agent_socket_id.load(butil::memory_order_relaxed); + SocketUniquePtr tmp_sock; + do { + if (Address(id, &tmp_sock) == 0) { + if (checkfn == NULL || checkfn(tmp_sock.get())) { + out->swap(tmp_sock); + return 0; + } + tmp_sock->ReleaseAdditionalReference(); + } + do { + if (GetShortSocket(&tmp_sock) != 0) { + LOG(ERROR) << "Fail to get short socket from " << *this; + return -1; + } + if (checkfn == NULL || checkfn(tmp_sock.get())) { + break; + } + tmp_sock->ReleaseAdditionalReference(); + } while (1); + + if (_agent_socket_id.compare_exchange_strong( + id, tmp_sock->id(), butil::memory_order_acq_rel)) { + out->swap(tmp_sock); + return 0; + } + tmp_sock->ReleaseAdditionalReference(); + // id was updated, re-address + } while (1); +} + +int Socket::PeekAgentSocket(SocketUniquePtr* out) const { + SocketId id = _agent_socket_id.load(butil::memory_order_relaxed); + if (id == INVALID_SOCKET_ID) { + return -1; + } + return Address(id, out); +} + +void Socket::GetStat(SocketStat* s) const { + BAIDU_CASSERT(offsetof(Socket, _preferred_index) >= 64, different_cacheline); + BAIDU_CASSERT(sizeof(WriteRequest) == 64, sizeof_write_request_is_64); + + SharedPart* sp = GetSharedPart(); + if (sp != NULL && sp->extended_stat != NULL) { + *s = *sp->extended_stat; + } else { + memset(s, 0, sizeof(*s)); + } +} + +void Socket::AddInputBytes(size_t bytes) { + GetOrNewSharedPart()->in_size.fetch_add(bytes, butil::memory_order_relaxed); +} +void Socket::AddInputMessages(size_t count) { + GetOrNewSharedPart()->in_num_messages.fetch_add(count, butil::memory_order_relaxed); +} +void Socket::CancelUnwrittenBytes(size_t bytes) { + const int64_t before_minus = + _unwritten_bytes.fetch_sub(bytes, butil::memory_order_relaxed); + if (before_minus < (int64_t)bytes + FLAGS_socket_max_unwritten_bytes) { + _overcrowded = false; + } +} +void Socket::AddOutputBytes(size_t bytes) { + GetOrNewSharedPart()->out_size.fetch_add(bytes, butil::memory_order_relaxed); + _last_writetime_us.store(butil::cpuwide_time_us(), + butil::memory_order_relaxed); + CancelUnwrittenBytes(bytes); +} +void Socket::AddOutputMessages(size_t count) { + GetOrNewSharedPart()->out_num_messages.fetch_add(count, butil::memory_order_relaxed); +} + +SocketId Socket::main_socket_id() const { + SharedPart* sp = GetSharedPart(); + if (sp) { + return sp->creator_socket_id; + } + return INVALID_SOCKET_ID; +} + +void Socket::OnProgressiveReadCompleted() { + if (is_read_progressive() && + (_controller_released_socket.load(butil::memory_order_relaxed) || + _controller_released_socket.exchange( + true, butil::memory_order_relaxed))) { + if (_connection_type_for_progressive_read == CONNECTION_TYPE_POOLED) { + ReturnToPool(); + } else if (_connection_type_for_progressive_read == CONNECTION_TYPE_SHORT) { + SetFailed(EUNUSED, "[%s]Close short connection", __FUNCTION__); + } + } +} + +SocketSSLContext::SocketSSLContext() + : raw_ctx(NULL) +{} + +SocketSSLContext::~SocketSSLContext() { + if (raw_ctx) { + SSL_CTX_free(raw_ctx); + } +} + +} // namespace brpc + + +namespace std { +ostream& operator<<(ostream& os, const brpc::Socket& sock) { + // NOTE: The output should be consistent with Socket::description() + os << "Socket{id=" << sock.id(); + const int fd = sock.fd(); + if (fd >= 0) { + os << " fd=" << fd; + } + os << " addr=" << sock.remote_side(); + const int local_port = sock.local_side().port; + if (local_port > 0) { + os << ':' << local_port; + } + os << "} (" << (void*)&sock << ')'; + return os; +} +} diff --git a/src/brpc/socket.h b/src/brpc/socket.h new file mode 100644 index 0000000..e4713d8 --- /dev/null +++ b/src/brpc/socket.h @@ -0,0 +1,1056 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_SOCKET_H +#define BRPC_SOCKET_H + +#include // std::ostream +#include // std::deque +#include // std::set +#include "butil/atomicops.h" // butil::atomic +#include "bthread/types.h" // bthread_id_t +#include "butil/iobuf.h" // butil::IOBuf, IOPortal +#include "butil/macros.h" // DISALLOW_COPY_AND_ASSIGN +#include "butil/endpoint.h" // butil::EndPoint +#include "butil/resource_pool.h" // butil::ResourceId +#include "bthread/butex.h" // butex_create_checked +#include "brpc/authenticator.h" // Authenticator +#include "brpc/errno.pb.h" // EFAILEDSOCKET +#include "brpc/details/ssl_helper.h" // SSLState +#include "brpc/stream.h" // StreamId +#include "brpc/destroyable.h" // Destroyable +#include "brpc/options.pb.h" // ConnectionType +#include "brpc/socket_id.h" // SocketId +#include "brpc/socket_message.h" // SocketMessagePtr +#include "bvar/bvar.h" +#include "brpc/http_method.h" +#include "brpc/event_dispatcher.h" +#include "brpc/versioned_ref_with_id.h" +#include "brpc/health_check_option.h" +#include "brpc/socket_mode.h" + +namespace brpc { +namespace policy { +class ConsistentHashingLoadBalancer; +class RtmpContext; +class H2GlobalStreamCreator; +} // namespace policy +namespace schan { +class ChannelBalancer; +} +namespace rdma { +class RdmaEndpoint; +class RdmaConnect; +class RdmaHandshakeClientV2; +class RdmaHandshakeServerV2; +class RdmaHandshakeClientV3; +class RdmaHandshakeServerV3; +} + +class Socket; +class AuthContext; +class EventDispatcher; +class Stream; +class Transport; + +// A special closure for processing the about-to-recycle socket. Socket does +// not delete SocketUser, if you want, `delete this' at the end of +// BeforeRecycle(). +class SocketUser { +public: + virtual ~SocketUser() {} + virtual void BeforeRecycle(Socket*) {} + + // Will be periodically called in a dedicated thread to check the + // health. + // If the return value is 0, the socket is revived. + // If the return value is ESTOP, the health-checking thread quits. + // The default impl is testing health by connection. + virtual int CheckHealth(Socket*); + + // Called after revived. + virtual void AfterRevived(Socket*); +}; + +// TODO: Remove this class which is replace-able with SocketMessage +// A special closure for handling fd related connection. The Socket does +// not delete SocketConnection, if you want, `delete this' at the end of +// BeforeRecycle(). +class SocketConnection { +public: + virtual ~SocketConnection() {} + virtual void BeforeRecycle(Socket*) = 0; + + // Establish a connection, call on_connect after connection finishes + virtual int Connect(Socket*, const timespec*, + int (*on_connect)(int, int, void*), void*) = 0; + + // Cut IOBufs into fd or SSL Channel + virtual ssize_t CutMessageIntoFileDescriptor(int, butil::IOBuf**, size_t) = 0; + virtual ssize_t CutMessageIntoSSLChannel(SSL*, butil::IOBuf**, size_t) = 0; +}; + +// Application-level connect. After TCP connected, the client sends some +// sort of "connect" message to the server to establish application-level +// connection. +// Instances of AppConnect may be shared by multiple sockets and often +// created by std::make_shared() where T implements AppConnect +class AppConnect { +public: + virtual ~AppConnect() {} + + // Called after TCP connected. Call done(error, data) when + // the application-level connection is established. + // Notice that `socket' can only be used for getting information of + // the connection. To write into the socket, write socket->fd() with + // sys_write directly. This is because Socket::Write() does not really + // write out until AppConnect is done. + virtual void StartConnect(const Socket* socket, + void (*done)(int err, void* data), + void* data) = 0; + + // Called when the host socket is setfailed or about to be recycled. + // If the AppConnect is still in-progress, it should be canceled properly. + virtual void StopConnect(Socket*) = 0; +}; + +// _s = per second, _m = per minute +struct SocketStat { + uint32_t in_size_s; + uint32_t out_size_s; + uint32_t in_num_messages_s; + uint32_t out_num_messages_s; + uint64_t in_size_m; // must be 64-bit + uint64_t out_size_m; + uint32_t in_num_messages_m; + uint32_t out_num_messages_m; +}; + +struct SocketVarsCollector { + SocketVarsCollector() + : nsocket("rpc_socket_count") + , channel_conn("rpc_channel_connection_count") + , neventthread_second("rpc_event_thread_second", &neventthread) + , nhealthcheck("rpc_health_check_count") + , nkeepwrite_second("rpc_keepwrite_second", &nkeepwrite) + , nwaitepollout("rpc_waitepollout_count") + , nwaitepollout_second("rpc_waitepollout_second", &nwaitepollout) + {} + + bvar::Adder nsocket; + bvar::Adder channel_conn; + bvar::Adder neventthread; + bvar::PerSecond > neventthread_second; + bvar::Adder nhealthcheck; + bvar::Adder nkeepwrite; + bvar::PerSecond > nkeepwrite_second; + bvar::Adder nwaitepollout; + bvar::PerSecond > nwaitepollout_second; +}; + +struct PipelinedInfo { + PipelinedInfo() { reset(); } + void reset() { + count = 0; + auth_flags = 0; + id_wait = INVALID_BTHREAD_ID; + } + uint32_t count; + uint32_t auth_flags; + bthread_id_t id_wait; +}; + +// A data structure packed with a pointer and +// some extra information using a uint64 variable. +template +class PackedPtr { + static constexpr uint8_t MAX_POINTER_LEN = 48; + static constexpr uint64_t POINTER_MASK = ((uint64_t)1 << MAX_POINTER_LEN) - 1; + static constexpr uint64_t EXTRA_MASK = ~POINTER_MASK; +public: + PackedPtr() : _data(0) { + BAIDU_CASSERT(sizeof(PackedPtr) == 8, sizeof_packed_ptr_must_be_8); + } + + void set(T* ptr) { + // Clear the low 48 bits and then + // store the pointer in the low 48 bits. + _data = (_data & EXTRA_MASK) | + ((uint64_t)(uintptr_t)ptr & POINTER_MASK); + } + + void reset() { + // Clear the low 48 bits. + _data &= EXTRA_MASK; + } + + T* get() const { return (T*)(_data & POINTER_MASK); } + + void set_extra(uint16_t extra) { + // Clear the high 16 bits and then + // store the extra in the high 16 bits. + _data = (_data & POINTER_MASK) | + ((uint64_t)extra << MAX_POINTER_LEN); + } + + void reset_extra() { + // Clear the high 16 bits. + _data &= POINTER_MASK; + } + + uint16_t extra() const { return _data >> MAX_POINTER_LEN; } + + void set_ptr_and_extra(T* p, uint16_t extra) { + _data = ((uint64_t)(uintptr_t)p & POINTER_MASK) | + ((uint64_t)extra << MAX_POINTER_LEN); + } + + void reset_ptr_and_extra() { + _data = 0; + } + +private: + // Pointer is stored in the low 48 bits, + // extra information is stored in the high 16 bits. + uint64_t _data; +}; + + +struct SocketSSLContext { + SocketSSLContext(); + ~SocketSSLContext(); + + SSL_CTX* raw_ctx; // owned + std::string sni_name; // useful for clients + std::vector alpn_protocols; // useful for clients +}; + +struct SocketKeepaliveOptions { + // Start keeplives after this period. + int keepalive_idle_s{-1}; + // Interval between keepalives. + int keepalive_interval_s{-1}; + // Number of keepalives before death. + int keepalive_count{-1}; +}; + +// TODO: Comment fields +struct SocketOptions { + // If `fd' is non-negative, set `fd' to be non-blocking and take the + // ownership. Socket will close the fd(if needed) and call + // user->BeforeRecycle() before recycling. + int fd{-1}; + butil::EndPoint remote_side; + butil::EndPoint local_side; + std::string device_name; + // If `connect_on_create' is true and `fd' is less than 0, + // a client connection will be established to remote_side() + // regarding deadline `connect_abstime' when Socket is being created. + // Default: false, means that a connection will be established + // on first write. + bool connect_on_create{false}; + // Default: NULL, means no timeout. + const timespec* connect_abstime{NULL}; + SocketUser* user{NULL}; + // When *edge-triggered* events happen on the file descriptor, callback + // `on_edge_triggered_events' will be called. Inside the callback, user + // shall read fd() in non-blocking mode until all data has been read + // or EAGAIN is met, otherwise the callback will not be called again + // until new data arrives. The callback will not be called from more than + // one thread at any time. + void (*on_edge_triggered_events)(Socket*){NULL}; + // Indicates that this socket requires an edge-triggered event handler even + // if `on_edge_triggered_events` is left as NULL by the caller. When this + // flag is true and `on_edge_triggered_events` is NULL, the underlying + // transport-specific implementation (e.g. a transport subclass) is allowed + // to install a suitable default `on_edge_triggered_events` callback on + // behalf of the user. Typical usage is by transports/protocols that rely + // on edge-triggered I/O semantics but want the framework to provide the + // actual event handler. + bool need_on_edge_trigger{false}; + int health_check_interval_s{-1}; + // Only accept ssl connection. + bool force_ssl{false}; + std::shared_ptr initial_ssl_ctx; + SocketMode socket_mode{SOCKET_MODE_TCP}; + bthread_keytable_pool_t* keytable_pool{NULL}; + SocketConnection* conn{NULL}; + std::shared_ptr app_connect; + // The created socket will set parsing_context with this value. + Destroyable* initial_parsing_context{NULL}; + + // Socket keepalive related options. + // Refer to `SocketKeepaliveOptions' for details. + std::shared_ptr keepalive_options; + // https://github.com/apache/brpc/issues/1154 + // https://github.com/grpc/grpc/pull/16419/files + // Only linux supports TCP_USER_TIMEOUT. + int tcp_user_timeout_ms{ -1}; + // Tag of this socket + bthread_tag_t bthread_tag{bthread_self_tag()}; + HealthCheckOption hc_option; +}; + +// Abstractions on reading from and writing into file descriptors. +// NOTE: accessed by multiple threads(frequently), align it by cacheline. +class BAIDU_CACHELINE_ALIGNMENT/*note*/ Socket : public VersionedRefWithId { +friend class EventDispatcher; +friend class InputMessenger; +friend class Acceptor; +friend class ConnectionsService; +friend class SocketUser; +friend class Stream; +friend class Controller; +friend class policy::ConsistentHashingLoadBalancer; +friend class policy::RtmpContext; +friend class schan::ChannelBalancer; +friend class rdma::RdmaEndpoint; +friend class rdma::RdmaConnect; +friend class rdma::RdmaHandshakeClientV2; +friend class rdma::RdmaHandshakeServerV2; +friend class rdma::RdmaHandshakeClientV3; +friend class rdma::RdmaHandshakeServerV3; +friend class HealthCheckTask; +friend class OnAppHealthCheckDone; +friend class HealthCheckManager; +friend class policy::H2GlobalStreamCreator; +friend class VersionedRefWithId; +friend class IOEvent; +friend void DereferenceSocket(Socket*); +friend class Transport; +friend class TcpTransport; +friend class RdmaTransport; +friend class TransportFactory; + class SharedPart; + struct WriteRequest; + +public: + const static int STREAM_FAKE_FD = INT_MAX; + // NOTE: User cannot create Socket from constructor. Use Create() + // instead. It's public just because of requirement of ResourcePool. + explicit Socket(Forbidden); + ~Socket() override; + + // Write `msg' into this Socket and clear it. The `msg' should be an + // intact request or response. To prevent messages from interleaving + // with other messages, the internal file descriptor is written by one + // thread at any time. Namely when only one thread tries to write, the + // message is written once directly in the calling thread. If the message + // is not completely written, a KeepWrite thread is created to continue + // the writing. When other threads want to write simultaneously (thread + // contention), they append WriteRequests to the KeepWrite thread in a + // wait-free manner rather than writing to the file descriptor directly. + // KeepWrite will not quit until all WriteRequests are complete. + // Key properties: + // - all threads have similar opportunities to write, no one is starved. + // - Write once when uncontended(most cases). + // - Wait-free when contended. + struct WriteOptions { + // `id_wait' is signalled when this Socket is SetFailed or data is written + // successfully with `notify_on_success=true'. To disable the signal, set + // this field to INVALID_BTHREAD_ID. `on_reset' of `id_wait' is NOT called + // when Write() returns non-zero. + // Default: INVALID_BTHREAD_ID + bthread_id_t id_wait; + + // If this field is set to true and `id_wait' is not INVALID_BTHREAD_ID, + // `id_wait' can be signalled when write successfully. + // Default: false + bool notify_on_success; + + // If no connection exists, a connection will be established to + // remote_side() regarding deadline `abstime'. NULL means no timeout. + // Default: NULL + const timespec* abstime; + + // Will be queued to implement positional correspondence with responses + // Default: 0 + uint32_t pipelined_count; + + // [Only effective when pipelined_count is non-zero] + // The request contains authenticating information which will be + // responded by the server and processed specially when dealing + // with the response. + uint32_t auth_flags; + + // Do not return EOVERCROWDED + // Default: false + bool ignore_eovercrowded; + + // The calling thread directly creates KeepWrite thread to write into + // this socket, skipping writing once. + // In situations like when you are continually issuing lots of + // StreamWrite or async RPC calls in only one thread, directly creating + // KeepWrite thread at first provides batch write effect and better + // performance. Otherwise, each write only writes one `msg` into socket + // and no KeepWrite thread can be created, which brings poor + // performance. + // Default: false + bool write_in_background; + + // After this write complete, shutdown write of the socket. + // Default: false + bool shutdown_write; + + WriteOptions() + : id_wait(INVALID_BTHREAD_ID) + , notify_on_success(false) + , abstime(NULL) + , pipelined_count(0) + , auth_flags(0) + , ignore_eovercrowded(false) + , write_in_background(false) + , shutdown_write(false) {} + }; + + // True if write of socket is shutdown. + bool IsWriteShutdown() const { return _is_write_shutdown; } + + int Write(butil::IOBuf *msg, const WriteOptions* options = NULL); + + // Write an user-defined message. `msg' is released when Write() is + // successful and *may* remain unchanged otherwise. + int Write(SocketMessagePtr<>& msg, const WriteOptions* options = NULL); + + // The file descriptor + int fd() const { return _fd.load(butil::memory_order_relaxed); } + + // The file descriptor version, used to avoid ABA problem. + uint64_t fd_version() const { return _fd_version.load(butil::memory_order_relaxed); } + + // ip/port of the local end of the connection + butil::EndPoint local_side() const { return _local_side; } + + // ip/port of the other end of the connection. + butil::EndPoint remote_side() const { return _remote_side; } + + // Initialized by SocketOptions.health_check_interval_s. + int health_check_interval() const { return _health_check_interval_s; } + + const std::string& health_check_path() const { return _hc_option.health_check_path; } + + int32_t health_check_timeout_ms() const {return _hc_option.health_check_timeout_ms; } + + // True if health checking is enabled. + bool HCEnabled() const { + // This fence makes sure that we see change of + // `_is_hc_related_ref_held' before changing `_versioned_ref. + butil::atomic_thread_fence(butil::memory_order_acquire); + return _health_check_interval_s > 0 && _is_hc_related_ref_held; + } + + // Release the health-checking-related + // reference which is held on created. + void ReleaseHCRelatedReference(); + + // `user' parameter passed to Create(). + SocketUser* user() const { return _user; } + + // `conn' parameter passed to Create() + void set_conn(SocketConnection* conn) { _conn = conn; } + SocketConnection* conn() const { return _conn; } + + // Saved contexts for parsing. Reset before trying new protocols and + // recycling of the socket. + void reset_parsing_context(Destroyable*); + Destroyable* release_parsing_context(); + Destroyable* parsing_context() const + { return _parsing_context.load(butil::memory_order_consume); } + // Try to set _parsing_context to *ctx when _parsing_context is NULL. + // If _parsing_context is NULL, the set is successful and true is returned. + // Otherwise, *ctx is Destroy()-ed and replaced with the value of + // _parsing_context, and false is returned. This process is thread-safe. + template bool initialize_parsing_context(T** ctx); + + // Connection-specific result of authentication. + const AuthContext* auth_context() const { return _auth_context; } + AuthContext* mutable_auth_context(); + + // Create a Socket according to `options', put the identifier into `id'. + // Returns 0 on success, -1 otherwise. + static int Create(const SocketOptions& options, SocketId* id); + + // Mark this Socket or the Socket associated with `id' as failed. + // Any later Address() of the identifier shall return NULL unless the + // Socket was revivied by StartHealthCheck. The Socket is NOT recycled + // after calling this function, instead it will be recycled when no one + // references it. Internal fields of the Socket are still accessible + // after calling this function. Calling SetFailed() of a Socket more + // than once is OK. + // This function is lock-free. + // Returns -1 when the Socket was already SetFailed(), 0 otherwise. + int SetFailed(); + int SetFailed(int error_code, const char* error_fmt, ...) + __attribute__ ((__format__ (__printf__, 3, 4))); + static int SetFailed(SocketId id); + + void AddRecentError(); + + int64_t recent_error_count() const; + + int isolated_times() const; + + void FeedbackCircuitBreaker(int error_code, int64_t latency_us); + + // Notify `id' object (by calling bthread_id_error) when this Socket + // has been `SetFailed'. If it already has, notify `id' immediately + void NotifyOnFailed(bthread_id_t id); + + // `ReleaseAdditionalReference' this Socket iff it has no data + // transmission during the last `idle_seconds' + int ReleaseReferenceIfIdle(int idle_seconds); + + // Set ELOGOFF flag to this `Socket' which means further requests + // through this `Socket' will receive an ELOGOFF error. This only + // affects return value of `IsAvailable' and won't close the inner + // fd. Once set, this flag can only be cleared inside `WaitAndReset'. + void SetLogOff(); + + // Check Whether the socket is available for user requests. + bool IsAvailable() const; + + // Start to process edge-triggered events from the fd. + // This function does not block caller. + static int OnInputEvent(void* user_data, uint32_t events, + const bthread_attr_t& thread_attr); + + static const int PROGRESS_INIT = 1; + bool MoreReadEvents(int* progress); + + // Fight for the right to authenticate this socket. Only one + // fighter will get 0 as return value. Others will wait until + // authentication finishes (succeed or not) and the error code + // will be filled into `auth_error'. The winner MUST call + // authentication finishes (succeed or not). The winner MUST call + // `SetAuthentication' (whether authentication succeed or not) + // to wake up waiters. + // Return 0 on success, error code on failure. + int FightAuthentication(int* auth_error); + + // Set the authentication result and signal all the waiters. + // This function can only be called after a successfule + // `FightAuthentication', otherwise it's regarded as an error + void SetAuthentication(int error_code); + + // Since some protocols are not able to store correlation id in their + // headers (such as nova-pbrpc, http), we have to store it here. Note + // that there can only be 1 RPC call on this socket at any time, otherwise + // use PushPipelinedInfo/PopPipelinedInfo instead. + void set_correlation_id(uint64_t correlation_id) + { _correlation_id = correlation_id; } + uint64_t correlation_id() const { return _correlation_id; } + + // For protocols that need positional correspondence between responses + // and requests. + void PushPipelinedInfo(const PipelinedInfo&); + bool PopPipelinedInfo(PipelinedInfo* info); + // Undo previous PopPipelinedInfo + void GivebackPipelinedInfo(const PipelinedInfo&); + + void set_preferred_index(int index) { _preferred_index = index; } + int preferred_index() const { return _preferred_index; } + + void set_type_of_service(int tos) { _tos = tos; } + + // Call this method every second (roughly) + void UpdateStatsEverySecond(int64_t now_ms); + + // Copy stat into `out'. If UpdateStatsEverySecond was never called, all + // fields will be zero. + void GetStat(SocketStat* out) const; + + // Call this when you receive an EOF event. `SetFailed' will be + // called at last if EOF event is no longer postponed + void SetEOF(); + // Postpone EOF event until `CheckEOF' has been called + void PostponeEOF(); + void CheckEOF(); + + SSLState ssl_state() const { return _ssl_state; } + bool is_ssl() const { return ssl_state() == SSL_CONNECTED; } + X509* GetPeerCertificate() const; + + // Print debugging inforamtion of `id' into the ostream. + static void DebugSocket(std::ostream&, SocketId id); + + // Number of Heahth checking since last socket failure. + int health_check_count() const { return _hc_count; } + + // True if this socket was created by Connect. + bool CreatedByConnect() const; + + // Get an UNUSED socket connecting to the same place as this socket + // from the SocketPool of this socket. + int GetPooledSocket(SocketUniquePtr* pooled_socket); + + // Return this socket which MUST be got from GetPooledSocket to its + // main_socket's pool. + int ReturnToPool(); + + // True if this socket has SocketPool + bool HasSocketPool() const; + + // Put all sockets in _shared_part->socket_pool into `list'. + void ListPooledSockets(std::vector* list, size_t max_count = 0); + + // Return true on success + bool GetPooledSocketStats(int* numfree, int* numinflight); + + // Create a socket connecting to the same place as this socket. + int GetShortSocket(SocketUniquePtr* short_socket); + + // Get and persist a socket connecting to the same place as this socket. + // If an agent socket was already created and persisted, it's returned + // directly (provided other constraints are satisfied) + // If `checkfn' is not NULL, and the checking result on the socket that + // would be returned is false, the socket is abandoned and the getting + // process is restarted. + // For example, http2 connections may run out of stream_id after long time + // running and a new socket should be created. In order not to affect + // LoadBalancers or NamingServices that may reference the Socket, agent + // socket can be used for the communication and replaced periodically but + // the main socket is unchanged. + int GetAgentSocket(SocketUniquePtr* out, bool (*checkfn)(Socket*)); + + // Take a peek at existing agent socket (no creation). + // Returns 0 on success. + int PeekAgentSocket(SocketUniquePtr* out) const; + + // Where the stats of this socket are accumulated to. + SocketId main_socket_id() const; + + // Share the stats with the socket. + void ShareStats(Socket* s); + + // Call this method to let the server SetFailed() this socket when the + // socket becomes idle or Server.Stop() is called. Useful for stopping + // streaming connections which are often referenced by many places, + // without SetFailed(), the ref-count may never hit zero. + void fail_me_at_server_stop() { _fail_me_at_server_stop = true; } + bool shall_fail_me_at_server_stop() const { return _fail_me_at_server_stop; } + + // Tag the socket so that the response coming back from socket will be + // parsed progressively. For example: in HTTP, the RPC may end w/o reading + // the body part fully. + void read_will_be_progressive(ConnectionType t) + { _connection_type_for_progressive_read = t; } + + // True if read_will_be_progressive() was called. + bool is_read_progressive() const + { return _connection_type_for_progressive_read != CONNECTION_TYPE_UNKNOWN; } + + // Handle the socket according to its connection_type when the progressive + // reading is finally done. + void OnProgressiveReadCompleted(); + + // Last cpuwide-time at when this socket was read or write. + int64_t last_active_time_us() const { + return std::max( + _last_readtime_us.load(butil::memory_order_relaxed), + _last_writetime_us.load(butil::memory_order_relaxed)); + } + + // Returns true if the remote side is overcrowded. + bool is_overcrowded() const { return _overcrowded; } + + bthread_keytable_pool_t* keytable_pool() const { return _keytable_pool; } + + void set_http_request_method(const HttpMethod& method) { _http_request_method = method; } + HttpMethod http_request_method() const { return _http_request_method; } + +private: + DISALLOW_COPY_AND_ASSIGN(Socket); + + int ConductError(bthread_id_t); + int StartWrite(WriteRequest*, const WriteOptions&); + + // Create a Socket according to `options', put the identifier into `id'. + // Returns 0 on success, -1 otherwise. + int OnCreated(const SocketOptions& options); + + // Called before returning to pool. + void BeforeRecycled(); + + void OnFailed(int error_code, const std::string& error_text); + + // Make this socket addressable again. + void AfterRevived(); + + std::string OnDescription() const; + + // Hold the health-checking-related + // reference on created. + void HoldHCRelatedRef(); + + static int Status(SocketId, int32_t* nref = NULL); // for unit-test. + + // Perform SSL handshake after TCP connection has been established. + // Create SSL session inside and block (in bthread) until handshake + // has completed. Application layer I/O is forbidden during this + // process to avoid concurrent I/O on the underlying fd + // Returns 0 on success, -1 otherwise + int SSLHandshake(int fd, bool server_mode); + + // Based upon whether the underlying channel is using SSL (if + // SSLState is SSL_UNKNOWN, try to detect at first), read data + // using the corresponding method into `_read_buf'. Returns read + // bytes on success, 0 on EOF, -1 otherwise and errno is set + ssize_t DoRead(size_t size_hint); + + // Based upon whether the underlying channel is using SSL, write + // `req' using the corresponding method. Returns written bytes on + // success, -1 otherwise and errno is set + ssize_t DoWrite(WriteRequest* req); + + // [Not thread-safe] Wait for EPOLLOUT event on `fd'. If `pollin' is + // true, EPOLLIN event will also be included and EPOLL_CTL_MOD will + // be used instead of EPOLL_CTL_ADD. Note that spurious wakeups may + // occur when this function returns, so make sure to check whether fd + // is writable or not even when it returns 0 + int WaitEpollOut(int fd, bool pollin, const timespec* abstime); + + // [Not thread-safe] Establish a tcp connection to `remote_side()' + // If `on_connect' is NULL, this function blocks current thread + // until connected/timeout. Otherwise, it returns immediately after + // starting a connection request and `on_connect' will be called + // when connecting completes (whether it succeeds or not) + // Returns the socket fd on success, -1 otherwise + int DoConnect(const timespec* abstime, + int (*on_connect)(int fd, int err, void* data), void* data); + int Connect(const timespec* abstime, + int (*on_connect)(int fd, int err, void* data), void* data); + + int CheckConnected(int sockfd); + + // [Not thread-safe] Only used by `Write'. + // Returns: + // 0 - Already connected + // 1 - Trying to establish connection + // -1 - Failed to connect to remote side + int ConnectIfNot(const timespec* abstime, WriteRequest* req); + + int ResetFileDescriptor(int fd); + + void SetSocketOptions(int fd); + + // Wait until nref hits `expected_nref' and reset some internal resources. + int WaitAndReset(int32_t expected_nref); + + + static void* KeepWrite(void*); + + bool IsWriteComplete(WriteRequest* old_head, bool singular_node, + WriteRequest** new_tail); + + void ReturnFailedWriteRequest( + WriteRequest*, int error_code, const std::string& error_text); + void ReturnSuccessfulWriteRequest(WriteRequest*); + WriteRequest* ReleaseWriteRequestsExceptLast( + WriteRequest*, int error_code, const std::string& error_text); + void ReleaseAllFailedWriteRequests(WriteRequest*); + + // Try to wake socket just like epollout has arrived + void WakeAsEpollOut(); + + // Generic callback for Socket to handle output event. + static int OnOutputEvent(void* user_data, uint32_t, + const bthread_attr_t&); + + class EpollOutRequest; + // Callback to handle epollout event whose request data + // is `EpollOutRequest' + int HandleEpollOutRequest(int error_code, EpollOutRequest* req); + + // Callback when an EpollOutRequest reaches timeout + static void HandleEpollOutTimeout(void* arg); + + // Callback when connection event reaches (succeeded or not) + // This callback will be passed to `Connect' + static int KeepWriteIfConnected(int fd, int err, void* data); + static void CheckConnectedAndKeepWrite(int fd, int err, void* data); + static void AfterAppConnected(int err, void* data); + + static void CreateVarsOnce(); + + // Default impl. of health checking. + int CheckHealth(); + + // Add a stream over this Socket. And |stream_id| would be automatically + // closed when this socket fails. + // Retuns 0 on success. -1 otherwise, indicating that this is currently a + // broken socket. + int AddStream(StreamId stream_id); + int RemoveStream(StreamId stream_id); + void ResetAllStreams(int error_code, const std::string& error_text); + + bool ValidFileDescriptor(int fd); + + // For stats. + void AddInputBytes(size_t bytes); + void AddInputMessages(size_t count); + void AddOutputBytes(size_t bytes); + void AddOutputMessages(size_t count); + + SharedPart* GetSharedPart() const; + SharedPart* GetOrNewSharedPart(); + SharedPart* GetOrNewSharedPartSlower(); + + void CheckEOFInternal(); + + // _error_code is set after a socket becomes failed, during the time + // gap, _error_code is 0. The race condition is by-design and acceptable. + // To always get a non-zero error_code, readers should call this method + // instead of reading _error_code directly. + int non_zero_error_code() const { + const int tmp = _error_code; + return tmp ? tmp : EFAILEDSOCKET; + } + + void CancelUnwrittenBytes(size_t bytes); + +private: + // In/Out bytes/messages, SocketPool etc + // _shared_part is shared by a main socket and all its pooled sockets. + // Can't use intrusive_ptr because the creation is based on optimistic + // locking and relies on atomic CAS. We manage references manually. + butil::atomic _shared_part; + + // [ Set in dispatcher ] + // To keep the callback in at most one bthread at any time. Read comments + // about ProcessEvent in socket.cpp to understand the tricks. + butil::atomic _nevent; + + // May be set by Acceptor to share keytables between reading threads + // on sockets created by the Acceptor. + bthread_keytable_pool_t* _keytable_pool; + + // [ Set in ResetFileDescriptor ] + butil::atomic _fd; // -1 when not connected. + int _tos; // Type of service which is actually only 8bits. + int64_t _reset_fd_real_us; // When _fd was reset, in microseconds. + // ABA/version counter; written on fd reset and read via fd_version() from + // other threads, so use relaxed atomics to avoid a data race. + butil::atomic _fd_version; // Only used by mysql for now. + + // Address of peer. Initialized by SocketOptions.remote_side. + butil::EndPoint _remote_side; + + // Address of self. Initialized in ResetFileDescriptor(). + butil::EndPoint _local_side; + + // The device name of the client's network adapter. + std::string _device_name; + + // Called when edge-triggered events happened on `_fd'. Read comments + // of EventDispatcher::AddConsumer (event_dispatcher.h) + // carefully before implementing the callback. + void (*_on_edge_triggered_events)(Socket*); + bool _need_on_edge_trigger; + // A set of callbacks to monitor important events of this socket. + // Initialized by SocketOptions.user + SocketUser* _user; + + // Customize creation of the connection. Initialized by SocketOptions.conn + SocketConnection* _conn; + + // User-level connection after TCP-connected. + // Initialized by SocketOptions.app_connect. + std::shared_ptr _app_connect; + + IOEvent _io_event; + + // last chosen index of the protocol as a heuristic value to avoid + // iterating all protocol handlers each time. + int _preferred_index; + + // Number of HC since the last SetFailed() was called. Set to 0 when the + // socket is revived. Only set in HealthCheckTask::OnTriggeringTask() + int _hc_count; + + // Size of current incomplete message, set to 0 on complete. + uint32_t _last_msg_size; + // Average message size of last #MSG_SIZE_WINDOW messages (roughly) + uint32_t _avg_msg_size; + + // Storing data read from `_fd' but cut-off yet. + butil::IOPortal _read_buf; + + // Set with cpuwide_time_us() at last read operation + butil::atomic _last_readtime_us; + + // Saved context for parsing, reset before trying other protocols. + butil::atomic _parsing_context; + + // Saving the correlation_id of RPC on protocols that cannot put + // correlation_id on-wire and do not send multiple requests on one + // connection simultaneously. + uint64_t _correlation_id; + + // Non-zero when health-checking is on. + int _health_check_interval_s; + + // The variable indicates whether the reference related + // to the health checking is held by someone. It can be + // synchronized via _versioned_ref atomic variable. + bool _is_hc_related_ref_held; + + // +-1 bit-+---31 bit---+ + // | flag | counter | + // +-------+------------+ + // 1-bit flag to ensure `SetEOF' to be called only once + // 31-bit counter of requests that are currently being processed + butil::atomic _ninprocess; + + // +---32 bit---+---32 bit---+ + // | auth flag | auth error | + // +------------+------------+ + // Meanings of `auth flag': + // 0 - not authenticated yet + // 1 - authentication completed (whether it succeeded or not + // depends on `auth error') + butil::atomic _auth_flag_error; + bthread_id_t _auth_id; + + // Stores authentication result/context of this socket. This only + // exists in server side + AuthContext* _auth_context; + + // Only accept ssl connection. + bool _force_ssl; + SSLState _ssl_state; + // SSL objects cannot be read and written at the same time. + // Use mutex to protect SSL objects when ssl_state is SSL_CONNECTED. + mutable butil::Mutex _ssl_session_mutex; + SSL* _ssl_session; // owner + std::shared_ptr _ssl_ctx; + + // Should use SOCKET_MODE_RDMA or SOCKET_MODE_TCP or Other, default is SOCKET_MODE_TCP Transport + SocketMode _socket_mode; + std::unique_ptr _transport; + + // Pass from controller, for progressive reading. + ConnectionType _connection_type_for_progressive_read; + butil::atomic _controller_released_socket; + + // True if the socket is too full to write. + volatile bool _overcrowded; + + bool _fail_me_at_server_stop; + + // Set by SetLogOff + butil::atomic _logoff_flag; + + // Concrete error information from SetFailed() + // Accesses to these 2 fields(especially _error_text) must be protected + // by _id_wait_list_mutex + int _error_code; + std::string _error_text; + + butil::atomic _agent_socket_id; + + butil::Mutex _pipeline_mutex; + std::deque* _pipeline_q; + + // For storing call-id of in-progress RPC. + pthread_mutex_t _id_wait_list_mutex; + bthread_id_list_t _id_wait_list; + + // Set with cpuwide_time_us() at last write operation + butil::atomic _last_writetime_us; + // Queued but written + butil::atomic _unwritten_bytes; + + // Butex to wait for EPOLLOUT event + butil::atomic* _epollout_butex; + + // Storing data that are not flushed into `fd' yet. + butil::atomic _write_head; + + bool _is_write_shutdown; + + butil::Mutex _stream_mutex; + std::set *_stream_set; + butil::atomic _total_streams_unconsumed_size; + + butil::atomic _ninflight_app_health_check; + + // Socket keepalive related options. + // Refer to `SocketKeepaliveOptions' for details. + // non-NULL means that keepalive is on. + std::shared_ptr _keepalive_options; + + // Only linux supports TCP_USER_TIMEOUT. + // When the value is greater than 0, it specifies the maximum + // amount of time in milliseconds that transmitted data may + // remain unacknowledged, or bufferred data may remain + // untransmitted (due to zero window size) before TCP will + // forcibly close the corresponding connection and return + // ETIMEDOUT to the application. + int _tcp_user_timeout_ms; + + HttpMethod _http_request_method; + HealthCheckOption _hc_option; +}; + +} // namespace brpc + + +// Sleep a while when `write_expr' returns negative with errno=EOVERCROWDED +// Implemented as a macro rather than a field of Socket.WriteOptions because +// the macro works for other functions besides Socket.Write as well. +#define BRPC_HANDLE_EOVERCROWDED(write_expr) \ + ({ \ + int64_t __ret_code__; \ + int sleep_time = 250; \ + while (true) { \ + __ret_code__ = (write_expr); \ + if (__ret_code__ >= 0 || errno != ::brpc::EOVERCROWDED) { \ + break; \ + } \ + sleep_time *= 2; \ + if (sleep_time > 2000) { sleep_time = 2000; } \ + ::bthread_usleep(sleep_time); \ + } \ + __ret_code__; \ + }) + +// Sleep a while when `write_expr' returns negative with errno=EOVERCROWDED. +// The sleep is done for at most `nretry' times. +#define BRPC_HANDLE_EOVERCROWDED_N(write_expr, nretry) \ + ({ \ + int64_t __ret_code__ = 0; \ + int sleep_time = 250; \ + for (int i = static_cast(nretry); i >= 0; --i) { \ + __ret_code__ = (write_expr); \ + if (__ret_code__ >= 0 || errno != ::brpc::EOVERCROWDED) { \ + break; \ + } \ + sleep_time *= 2; \ + if (sleep_time > 2000) { sleep_time = 2000; } \ + ::bthread_usleep(sleep_time); \ + } \ + __ret_code__; \ + }) + +namespace std { +ostream& operator<<(ostream& os, const brpc::Socket& sock); +} + +#include "brpc/socket_inl.h" + +#endif // BRPC_SOCKET_H diff --git a/src/brpc/socket_id.h b/src/brpc/socket_id.h new file mode 100644 index 0000000..e000c98 --- /dev/null +++ b/src/brpc/socket_id.h @@ -0,0 +1,56 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_SOCKET_ID_H +#define BRPC_SOCKET_ID_H + +// To brpc developers: This is a header included by user, don't depend +// on internal structures, use opaque pointers instead. + +#include "brpc/versioned_ref_with_id.h" + +namespace brpc { + +// Unique identifier of a Socket. +// Users shall store SocketId instead of Sockets and call Socket::Address() +// to convert the identifier to an unique_ptr at each access. Whenever a +// unique_ptr is not destructed, the enclosed Socket will not be recycled. +typedef VRefId SocketId; + +const SocketId INVALID_SOCKET_ID = INVALID_VREF_ID; + +class Socket; + +extern void DereferenceSocket(Socket*); + +// Explicit (full) template specialization to ignore compiler error, +// because Socket is an incomplete type where only this header is included. +template<> +struct VersionedRefWithIdDeleter { + void operator()(Socket* m) const { + DereferenceSocket(m); + } +}; + +typedef VersionedRefWithIdUniquePtr SocketUniquePtr; + + +} // namespace brpc + + +#endif // BRPC_SOCKET_ID_H diff --git a/src/brpc/socket_inl.h b/src/brpc/socket_inl.h new file mode 100644 index 0000000..ea8a392 --- /dev/null +++ b/src/brpc/socket_inl.h @@ -0,0 +1,153 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// This file contains inlined implementation of socket.h + +#ifndef BRPC_SOCKET_INL_H +#define BRPC_SOCKET_INL_H + + +namespace brpc { + +inline bool Socket::MoreReadEvents(int* progress) { + // Fail to CAS means that new events arrived. + return !_nevent.compare_exchange_strong( + *progress, 0, butil::memory_order_release, + butil::memory_order_acquire); +} + +inline void Socket::SetLogOff() { + if (!_logoff_flag.exchange(true, butil::memory_order_relaxed)) { + if (fd() < 0) { + // This socket hasn't been connected before (such as + // short connection), so it won't receive any epoll + // events. We need to `SetFailed' it to trigger health + // checking, otherwise it may be blocked forever + SetFailed(ELOGOFF, "The server at %s is stopping", + butil::endpoint2str(remote_side()).c_str()); + } + } +} + +inline bool Socket::IsAvailable() const { + return !_logoff_flag.load(butil::memory_order_relaxed) && + (_ninflight_app_health_check.load(butil::memory_order_relaxed) == 0); +} + +static const uint32_t EOF_FLAG = (1 << 31); + +inline void Socket::PostponeEOF() { + if (CreatedByConnect()) { // not needed at server-side + _ninprocess.fetch_add(1, butil::memory_order_relaxed); + } +} + +inline void Socket::CheckEOF() { + if (CreatedByConnect()) { // not needed at server-side + CheckEOFInternal(); + } +} + +inline void Socket::CheckEOFInternal() { + uint32_t nref = _ninprocess.fetch_sub(1, butil::memory_order_release); + if ((nref & ~EOF_FLAG) == 1) { + butil::atomic_thread_fence(butil::memory_order_acquire); + // It's safe to call `SetFailed' each time `_ninprocess' hits 0 + SetFailed(EEOF, "Got EOF of %s", description().c_str()); + } +} + +inline void Socket::SetEOF() { + uint32_t nref = _ninprocess.fetch_or(EOF_FLAG, butil::memory_order_relaxed); + if ((nref & EOF_FLAG) == 0) { + // Release the additional reference in `_ninprocess' + CheckEOFInternal(); + } +} + +inline void Socket::reset_parsing_context(Destroyable* new_context) { + Destroyable* old_ctx = _parsing_context.exchange( + new_context, butil::memory_order_acq_rel); + if (old_ctx) { + old_ctx->Destroy(); + } +} + +inline Destroyable* Socket::release_parsing_context() { + return _parsing_context.exchange(NULL, butil::memory_order_acquire); +} + +template +bool Socket::initialize_parsing_context(T** ctx) { + Destroyable* expected = NULL; + if (_parsing_context.compare_exchange_strong( + expected, *ctx, butil::memory_order_acq_rel, + butil::memory_order_acquire)) { + return true; + } else { + (*ctx)->Destroy(); + *ctx = static_cast(expected); + return false; + } +} + +// NOTE: Push/Pop may be called from different threads simultaneously. +inline void Socket::PushPipelinedInfo(const PipelinedInfo& pi) { + BAIDU_SCOPED_LOCK(_pipeline_mutex); + if (_pipeline_q == NULL) { + _pipeline_q = new std::deque; + } + _pipeline_q->push_back(pi); +} + +inline bool Socket::PopPipelinedInfo(PipelinedInfo* info) { + BAIDU_SCOPED_LOCK(_pipeline_mutex); + if (_pipeline_q != NULL && !_pipeline_q->empty()) { + *info = _pipeline_q->front(); + _pipeline_q->pop_front(); + return true; + } + return false; +} + +inline void Socket::GivebackPipelinedInfo(const PipelinedInfo& pi) { + BAIDU_SCOPED_LOCK(_pipeline_mutex); + if (_pipeline_q != NULL) { + _pipeline_q->push_front(pi); + } +} + +inline bool Socket::ValidFileDescriptor(int fd) { + return fd >= 0 && fd != STREAM_FAKE_FD; +} + +inline Socket::SharedPart* Socket::GetSharedPart() const { + return _shared_part.load(butil::memory_order_consume); +} + +inline Socket::SharedPart* Socket::GetOrNewSharedPart() { + SharedPart* shared_part = GetSharedPart(); + if (shared_part != NULL) { // most cases + return shared_part; + } + return GetOrNewSharedPartSlower(); +} + +} // namespace brpc + + +#endif // BRPC_SOCKET_INL_H diff --git a/src/brpc/socket_map.cpp b/src/brpc/socket_map.cpp new file mode 100644 index 0000000..1562e0a --- /dev/null +++ b/src/brpc/socket_map.cpp @@ -0,0 +1,436 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include "bthread/bthread.h" +#include "butil/time.h" +#include "butil/scoped_lock.h" +#include "butil/logging.h" +#include "butil/debug/leak_annotations.h" +#include "brpc/log.h" +#include "brpc/protocol.h" +#include "brpc/input_messenger.h" +#include "brpc/reloadable_flags.h" +#include "brpc/socket_map.h" + +namespace brpc { + +DEFINE_int32(health_check_interval, 3, + "seconds between consecutive health-checkings"); +// NOTE: Must be limited to positive to guarantee correctness of SocketMapRemove. +BRPC_VALIDATE_GFLAG(health_check_interval, PositiveInteger); + +DEFINE_int32(idle_timeout_second, 30, + "Pooled connections without data transmission for so many " + "seconds will be closed. No effect for non-positive values"); +BRPC_VALIDATE_GFLAG(idle_timeout_second, PassValidate); + +DEFINE_int32(defer_close_second, 0, + "Defer close of connections for so many seconds even if the" + " connection is not used by anyone. Close immediately for " + "non-positive values."); +BRPC_VALIDATE_GFLAG(defer_close_second, PassValidate); + +DEFINE_bool(defer_close_respect_idle, false, + "When defer_close_second > 0, close a connection immediately when " + "the last reference is removed and the socket has already been " + "idle for longer than defer_close_second. Disabled by default for " + "backward compatibility."); +BRPC_VALIDATE_GFLAG(defer_close_respect_idle, PassValidate); + +DEFINE_bool(show_socketmap_in_vars, false, + "[DEBUG] Describe SocketMaps in /vars"); +BRPC_VALIDATE_GFLAG(show_socketmap_in_vars, PassValidate); + +DEFINE_bool(reserve_one_idle_socket, false, + "Reserve one idle socket for pooled connections when idle_timeout_second > 0"); + +static pthread_once_t g_socket_map_init = PTHREAD_ONCE_INIT; +static butil::static_atomic g_socket_map = BUTIL_STATIC_ATOMIC_INIT(NULL); + +class GlobalSocketCreator : public SocketCreator { +public: + int CreateSocket(const SocketOptions& opt, SocketId* id) override { + SocketOptions sock_opt = opt; + sock_opt.health_check_interval_s = FLAGS_health_check_interval; + return get_client_side_messenger()->Create(sock_opt, id); + } +}; + +static void CreateClientSideSocketMap() { + SocketMap* socket_map = new SocketMap; + SocketMapOptions options; + options.socket_creator = new GlobalSocketCreator; + options.idle_timeout_second_dynamic = &FLAGS_idle_timeout_second; + options.defer_close_second_dynamic = &FLAGS_defer_close_second; + options.defer_close_respect_idle_dynamic = &FLAGS_defer_close_respect_idle; + if (socket_map->Init(options) != 0) { + LOG(FATAL) << "Fail to init SocketMap"; + exit(1); + } + g_socket_map.store(socket_map, butil::memory_order_release); +} + +SocketMap* get_client_side_socket_map() { + // The consume fence makes sure that we see a NULL or a fully initialized + // SocketMap. + return g_socket_map.load(butil::memory_order_consume); +} +SocketMap* get_or_new_client_side_socket_map() { + get_or_new_client_side_messenger(); + pthread_once(&g_socket_map_init, CreateClientSideSocketMap); + return g_socket_map.load(butil::memory_order_consume); +} + +int SocketMapInsert(const SocketMapKey& key, SocketId* id, + SocketOptions& opt) { + return get_or_new_client_side_socket_map()->Insert(key, id, opt); +} + +int SocketMapFind(const SocketMapKey& key, SocketId* id) { + SocketMap* m = get_client_side_socket_map(); + if (m) { + return m->Find(key, id); + } + return -1; +} + +void SocketMapRemove(const SocketMapKey& key) { + SocketMap* m = get_client_side_socket_map(); + if (m) { + // TODO: We don't have expected_id to pass right now since the callsite + // at NamingServiceThread is hard to be fixed right now. As long as + // FLAGS_health_check_interval is limited to positive, SocketMapInsert + // never replaces the sockets, skipping comparison is still right. + m->Remove(key, INVALID_SOCKET_ID); + } +} + +void SocketMapList(std::vector* ids) { + SocketMap* m = get_client_side_socket_map(); + if (m) { + m->List(ids); + } else { + ids->clear(); + } +} + +// ========== SocketMap impl. ============ + +SocketMapOptions::SocketMapOptions() + : socket_creator(NULL) + , suggested_map_size(1024) + , idle_timeout_second_dynamic(NULL) + , idle_timeout_second(0) + , defer_close_second_dynamic(NULL) + , defer_close_second(0) + , defer_close_respect_idle_dynamic(NULL) { +} + +SocketMap::SocketMap() + : _exposed_in_bvar(false) + , _this_map_bvar(NULL) + , _has_close_idle_thread(false) { +} + +SocketMap::~SocketMap() { + RPC_VLOG << "Destroying SocketMap=" << this; + if (_has_close_idle_thread) { + bthread_stop(_close_idle_thread); + bthread_join(_close_idle_thread, NULL); + } + if (!_map.empty()) { + std::ostringstream err; + int nleft = 0; + for (Map::iterator it = _map.begin(); it != _map.end(); ++it) { + SingleConnection* sc = &it->second; + if ((!sc->socket->Failed() || + sc->socket->HCEnabled()) && + sc->ref_count != 0) { + ++nleft; + if (nleft == 0) { + err << "Left in SocketMap(" << this << "):"; + } + err << ' ' << *sc->socket; + } + } + if (nleft) { + LOG(ERROR) << err.str(); + } + } + + delete _this_map_bvar; + _this_map_bvar = NULL; + + delete _options.socket_creator; + _options.socket_creator = NULL; +} + +int SocketMap::Init(const SocketMapOptions& options) { + if (_options.socket_creator != NULL) { + LOG(ERROR) << "Already initialized"; + return -1; + } + _options = options; + if (_options.socket_creator == NULL) { + LOG(ERROR) << "SocketOptions.socket_creator must be set"; + return -1; + } + if (_map.init(_options.suggested_map_size, 70) != 0) { + LOG(ERROR) << "Fail to init _map"; + return -1; + } + if (_options.idle_timeout_second_dynamic != NULL || + _options.idle_timeout_second > 0) { + bthread_attr_t attr = BTHREAD_ATTR_NORMAL; + bthread_attr_set_name(&attr, "RunWatchConnections"); + if (bthread_start_background(&_close_idle_thread, &attr, + RunWatchConnections, this) != 0) { + LOG(FATAL) << "Fail to start bthread"; + return -1; + } + _has_close_idle_thread = true; + } + return 0; +} + +void SocketMap::Print(std::ostream& os) { + // TODO: Elaborate. + size_t count = 0; + { + std::unique_lock mu(_mutex); + count = _map.size(); + } + os << "count=" << count; +} + +void SocketMap::PrintSocketMap(std::ostream& os, void* arg) { + static_cast(arg)->Print(os); +} + +void SocketMap::ShowSocketMapInBvarIfNeed() { + if (FLAGS_show_socketmap_in_vars && + !_exposed_in_bvar.exchange(true, butil::memory_order_release)) { + char namebuf[32]; + int len = snprintf(namebuf, sizeof(namebuf), "rpc_socketmap_%p", this); + _this_map_bvar = new bvar::PassiveStatus( + butil::StringPiece(namebuf, len), PrintSocketMap, this); + } +} + +int SocketMap::Insert(const SocketMapKey& key, SocketId* id, + SocketOptions& opt) { + ShowSocketMapInBvarIfNeed(); + + std::unique_lock mu(_mutex); + SingleConnection* sc = _map.seek(key); + if (sc) { + if (!sc->socket->Failed() || sc->socket->HCEnabled()) { + ++sc->ref_count; + *id = sc->socket->id(); + return 0; + } + // A socket w/o HC is failed (permanently), replace it. + ReleaseReference(sc->socket); + _map.erase(key); // in principle, we can override the entry in map w/o + // removing and inserting it again. But this would make error branches + // below have to remove the entry before returning, which is + // error-prone. We prefer code maintainability here. + sc = NULL; + } + SocketId tmp_id; + opt.remote_side = key.peer.addr; + if (_options.socket_creator->CreateSocket(opt, &tmp_id) != 0) { + PLOG(FATAL) << "Fail to create socket to " << key.peer; + return -1; + } + // Add a reference to make sure that sc->socket is always accessible. Not + // use SocketUniquePtr which cannot put into containers before c++11. + // The ref will be removed at entry's removal. + SocketUniquePtr ptr; + int rc = Socket::AddressFailedAsWell(tmp_id, &ptr); + if (rc < 0) { + LOG(FATAL) << "Fail to address SocketId=" << tmp_id; + return -1; + } else if (rc > 0 && !ptr->HCEnabled()) { + LOG(FATAL) << "Failed socket is not HC-enabled"; + return -1; + } + // If health check is enabled, a health-checking-related reference + // is hold in Socket::Create. + // If health check is disabled, hold a reference in SocketMap. + SingleConnection new_sc = { 1, ptr->HCEnabled() ? ptr.get() : ptr.release(), 0 }; + _map[key] = new_sc; + *id = tmp_id; + mu.unlock(); + return 0; +} + +void SocketMap::Remove(const SocketMapKey& key, SocketId expected_id) { + return RemoveInternal(key, expected_id, false); +} + +void SocketMap::RemoveInternal(const SocketMapKey& key, + SocketId expected_id, + bool remove_orphan) { + ShowSocketMapInBvarIfNeed(); + + std::unique_lock mu(_mutex); + SingleConnection* sc = _map.seek(key); + if (!sc) { + return; + } + if (!remove_orphan && + (expected_id == INVALID_SOCKET_ID || expected_id == sc->socket->id())) { + --sc->ref_count; + } + if (sc->ref_count == 0) { + // NOTE: save the gflag which may be reloaded at any time + const int defer_close_second = _options.defer_close_second_dynamic ? + *_options.defer_close_second_dynamic + : _options.defer_close_second; + if (!remove_orphan && defer_close_second > 0) { + const int64_t now_us = butil::cpuwide_time_us(); + // NOTE: save the gflag which may be reloaded at any time + const bool defer_close_respect_idle = _options.defer_close_respect_idle_dynamic ? + *_options.defer_close_respect_idle_dynamic : false; + if (!defer_close_respect_idle) { + // Start count down on this Socket. + sc->no_ref_us = now_us; + return; + } + const int64_t defer_us = (int64_t)defer_close_second * 1000000L; + if (sc->no_ref_us <= sc->socket->last_active_time_us() + defer_us) { + // When defer_close_respect_idle is enabled, a connection that has + // already been idle for longer than defer_close_second is closed + // immediately. + sc->no_ref_us = now_us; + return; + } + } + Socket* const s = sc->socket; + _map.erase(key); + mu.unlock(); + s->ReleaseAdditionalReference(); // release extra ref + ReleaseReference(s); + } +} + +void SocketMap::ReleaseReference(Socket* s) { + if (s->HCEnabled()) { + s->ReleaseHCRelatedReference(); + } else { + // Release the extra ref hold in SocketMap::Insert. + SocketUniquePtr ptr(s); + } +} + +int SocketMap::Find(const SocketMapKey& key, SocketId* id) { + BAIDU_SCOPED_LOCK(_mutex); + SingleConnection* sc = _map.seek(key); + if (sc) { + *id = sc->socket->id(); + return 0; + } + return -1; +} + +void SocketMap::List(std::vector* ids) { + ids->clear(); + BAIDU_SCOPED_LOCK(_mutex); + for (Map::iterator it = _map.begin(); it != _map.end(); ++it) { + ids->push_back(it->second.socket->id()); + } +} + +void SocketMap::List(std::vector* pts) { + pts->clear(); + BAIDU_SCOPED_LOCK(_mutex); + for (Map::iterator it = _map.begin(); it != _map.end(); ++it) { + pts->push_back(it->second.socket->remote_side()); + } +} + +void SocketMap::ListOrphans(int64_t defer_us, std::vector* out) { + out->clear(); + const int64_t now = butil::cpuwide_time_us(); + BAIDU_SCOPED_LOCK(_mutex); + for (Map::iterator it = _map.begin(); it != _map.end(); ++it) { + SingleConnection& sc = it->second; + if (sc.ref_count == 0 && now - sc.no_ref_us >= defer_us) { + out->push_back(it->first); + } + } +} + +void* SocketMap::RunWatchConnections(void* arg) { + static_cast(arg)->WatchConnections(); + return NULL; +} + +void SocketMap::WatchConnections() { + // This bthread of SocketMap Singleton runs for the whole process lifetime and + // never returns, so the local objects below live until the process exits and + // their destructors never run. They are reachable from this bthread's stack, + // so the objects themselves are not reported as leaks, but the heap buffers + // they allocate while exposing themselves (variable names, watched path) would + // be. Disable leak detection only around their construction and re-enable it + // right after + std::vector main_sockets; + std::vector pooled_sockets; + std::vector orphan_sockets; + const uint64_t CHECK_INTERVAL_US = 1000000UL; + while (bthread_usleep(CHECK_INTERVAL_US) == 0) { + ANNOTATE_SCOPED_MEMORY_LEAK; + // NOTE: save the gflag which may be reloaded at any time. + const int idle_seconds = _options.idle_timeout_second_dynamic ? + *_options.idle_timeout_second_dynamic + : _options.idle_timeout_second; + if (idle_seconds > 0) { + // Check idle pooled connections + List(&main_sockets); + for (auto main_socket : main_sockets) { + SocketUniquePtr s; + if (Socket::Address(main_socket, &s) == 0) { + s->ListPooledSockets(&pooled_sockets); + for (size_t i = FLAGS_reserve_one_idle_socket ? 1 : 0; + i < pooled_sockets.size(); ++i) { + SocketUniquePtr s2; + if (Socket::Address(pooled_sockets[i], &s2) == 0) { + s2->ReleaseReferenceIfIdle(idle_seconds); + } + } + } + } + } + + // Check connections without Channel. This works when `defer_seconds' + // <= 0, in which case orphan connections will be closed immediately + // NOTE: save the gflag which may be reloaded at any time + const int defer_seconds = _options.defer_close_second_dynamic ? + *_options.defer_close_second_dynamic : + _options.defer_close_second; + ListOrphans(defer_seconds * 1000000L, &orphan_sockets); + for (size_t i = 0; i < orphan_sockets.size(); ++i) { + RemoveInternal(orphan_sockets[i], INVALID_SOCKET_ID, true); + } + } +} + +} // namespace brpc diff --git a/src/brpc/socket_map.h b/src/brpc/socket_map.h new file mode 100644 index 0000000..c4dc5c2 --- /dev/null +++ b/src/brpc/socket_map.h @@ -0,0 +1,236 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_SOCKET_MAP_H +#define BRPC_SOCKET_MAP_H + +#include // std::vector +#include "bvar/bvar.h" // bvar::PassiveStatus +#include "butil/containers/flat_map.h" // FlatMap +#include "brpc/socket_id.h" // SockdetId +#include "brpc/options.pb.h" // ProtocolType +#include "brpc/input_messenger.h" // InputMessageHandler +#include "brpc/server_node.h" // ServerNode + +namespace brpc { + +// Different signature means that the Channel needs separate sockets. +struct ChannelSignature { + uint64_t data[2]; + + ChannelSignature() { Reset(); } + void Reset() { data[0] = data[1] = 0; } +}; + +inline bool operator==(const ChannelSignature& s1, const ChannelSignature& s2) { + return s1.data[0] == s2.data[0] && s1.data[1] == s2.data[1]; +} +inline bool operator!=(const ChannelSignature& s1, const ChannelSignature& s2) { + return !(s1 == s2); +} + +// The following fields uniquely define a Socket. In other word, +// Socket can't be shared between 2 different SocketMapKeys +struct SocketMapKey { + explicit SocketMapKey(const butil::EndPoint& pt) + : peer(pt) + {} + SocketMapKey(const butil::EndPoint& pt, const ChannelSignature& cs) + : peer(pt), channel_signature(cs) + {} + SocketMapKey(const ServerNode& sn, const ChannelSignature& cs) + : peer(sn), channel_signature(cs) + {} + + ServerNode peer; + ChannelSignature channel_signature; +}; + +inline bool operator==(const SocketMapKey& k1, const SocketMapKey& k2) { + return k1.peer == k2.peer && k1.channel_signature == k2.channel_signature; +}; + +struct SocketMapKeyHasher { + size_t operator()(const SocketMapKey& key) const { + size_t h = butil::DefaultHasher()(key.peer.addr); + h = h * 101 + butil::DefaultHasher()(key.peer.tag); + h = h * 101 + key.channel_signature.data[1]; + return h; + } +}; + + +// Try to share the Socket to `key'. If the Socket does not exist, create one. +// The corresponding SocketId is written to `*id'. If this function returns +// successfully, SocketMapRemove() MUST be called when the Socket is not needed. +// Return 0 on success, -1 otherwise. +int SocketMapInsert(const SocketMapKey& key, SocketId* id, + SocketOptions& opt); + +inline int SocketMapInsert(const SocketMapKey& key, SocketId* id, + const std::shared_ptr& ssl_ctx, + SocketMode socket_mode, + const HealthCheckOption& hc_option) { + SocketOptions opt; + opt.remote_side = key.peer.addr; + opt.initial_ssl_ctx = ssl_ctx; + opt.socket_mode = socket_mode; + opt.hc_option = hc_option; + return SocketMapInsert(key, id, opt); +} + +inline int SocketMapInsert(const SocketMapKey& key, SocketId* id, + const std::shared_ptr& ssl_ctx) { + HealthCheckOption hc_option; + return SocketMapInsert(key, id, ssl_ctx, SOCKET_MODE_TCP, hc_option); +} + +inline int SocketMapInsert(const SocketMapKey& key, SocketId* id) { + std::shared_ptr empty_ptr; + HealthCheckOption hc_option; + return SocketMapInsert(key, id, empty_ptr, SOCKET_MODE_TCP, hc_option); +} + +// Find the SocketId associated with `key'. +// Return 0 on found, -1 otherwise. +int SocketMapFind(const SocketMapKey& key, SocketId* id); + +// Called once when the Socket returned by SocketMapInsert() is not needed. +void SocketMapRemove(const SocketMapKey& key); + +// Put all existing Sockets into `ids' +void SocketMapList(std::vector* ids); + +// ====================================================================== +// The underlying class that can be used otherwhere for mapping endpoints +// to sockets. + +// SocketMap creates sockets on-demand by calling this object. +class SocketCreator { +public: + virtual ~SocketCreator() {} + virtual int CreateSocket(const SocketOptions& opt, SocketId* id) = 0; +}; + +struct SocketMapOptions { + // Constructed with default options. + SocketMapOptions(); + + // For creating sockets by need. Owned and deleted by SocketMap. + // Default: NULL (must be set by user). + SocketCreator* socket_creator; + + // Initial size of the map (proper size reduces number of resizes) + // Default: 1024 + size_t suggested_map_size; + + // Pooled connections without data transmission for so many seconds will + // be closed. No effect for non-positive values. + // If idle_timeout_second_dynamic is not NULL, use the dereferenced value + // each time instead of idle_timeout_second. + // Default: 0 (disabled) + const int* idle_timeout_second_dynamic; + int idle_timeout_second; + + // Defer close of connections for so many seconds even if the connection + // is not used by anyone. Close immediately for non-positive values. + // If defer_close_second_dynamic is not NULL, use the dereferenced value + // each time instead of defer_close_second. + // Default: 0 (disabled) + const int* defer_close_second_dynamic; + int defer_close_second; + + // When defer_close_second > 0 and this flag is true, close a connection + // immediately when the last reference is removed and the socket has already + // been idle for longer than defer_close_second. + // If defer_close_respect_idle_dynamic is not NULL, use the dereferenced + // value each time. + // Default: NULL (treated as false) + const bool* defer_close_respect_idle_dynamic; +}; + +// Share sockets to the same EndPoint. +class SocketMap { +public: + SocketMap(); + ~SocketMap(); + int Init(const SocketMapOptions&); + int Insert(const SocketMapKey& key, SocketId* id, + const std::shared_ptr& ssl_ctx, + SocketMode socket_mode, + const HealthCheckOption& hc_option) { + SocketOptions opt; + opt.remote_side = key.peer.addr; + opt.initial_ssl_ctx = ssl_ctx; + opt.socket_mode = socket_mode; + opt.hc_option = hc_option; + return Insert(key, id, opt); +} + + int Insert(const SocketMapKey& key, SocketId* id, + const std::shared_ptr& ssl_ctx) { + HealthCheckOption hc_option; + return Insert(key, id, ssl_ctx, SOCKET_MODE_TCP, hc_option); + } + int Insert(const SocketMapKey& key, SocketId* id) { + std::shared_ptr empty_ptr; + HealthCheckOption hc_option; + return Insert(key, id, empty_ptr, SOCKET_MODE_TCP, hc_option); + } + int Insert(const SocketMapKey& key, SocketId* id, SocketOptions& opt); + + void Remove(const SocketMapKey& key, SocketId expected_id); + int Find(const SocketMapKey& key, SocketId* id); + void List(std::vector* ids); + void List(std::vector* pts); + const SocketMapOptions& options() const { return _options; } + +private: + void RemoveInternal(const SocketMapKey& key, SocketId id, + bool remove_orphan); + static void ReleaseReference(Socket* s); + void ListOrphans(int64_t defer_us, std::vector* out); + void WatchConnections(); + static void* RunWatchConnections(void*); + void Print(std::ostream& os); + static void PrintSocketMap(std::ostream& os, void* arg); + void ShowSocketMapInBvarIfNeed(); + +private: + struct SingleConnection { + int ref_count; + Socket* socket; + int64_t no_ref_us; + }; + + // TODO: When RpcChannels connecting to one EndPoint are frequently created + // and destroyed, a single map+mutex may become hot-spots. + typedef butil::FlatMap Map; + SocketMapOptions _options; + butil::Mutex _mutex; + Map _map; + butil::atomic _exposed_in_bvar; + bvar::PassiveStatus* _this_map_bvar; + bool _has_close_idle_thread; + bthread_t _close_idle_thread; +}; + +} // namespace brpc + +#endif // BRPC_SOCKET_MAP_H diff --git a/src/brpc/socket_message.h b/src/brpc/socket_message.h new file mode 100644 index 0000000..32ecfd7 --- /dev/null +++ b/src/brpc/socket_message.h @@ -0,0 +1,86 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_SOCKET_MESSAGE_H +#define BRPC_SOCKET_MESSAGE_H + +#include "butil/status.h" // butil::Status + + +namespace brpc { + +// Generate the IOBuf to write dynamically, for implementing complex protocols. +// Used in RTMP and HTTP2 right now. +class SocketMessage { +public: + virtual ~SocketMessage() {} + + // Called once and only once to generate the buffer to write. + // This object should destroy itself at the end of this method. + // AppendAndDestroySelf()-s to a Socket are called one by one and the + // generated data are written into the file descriptor in the same order. + // AppendAndDestroySelf() are called _after_ completion of connecting + // including AppConnect. + // Params: + // out - The buffer to be generated, being empty initially, could + // remain empty after being called. + // sock - the socket to write. NULL when the message will be abandoned + // If the status returned is an error, WriteOptions.id_wait (if absent) + // will be signaled with the error. Other messages are not affected. + virtual butil::Status AppendAndDestroySelf(butil::IOBuf* out, Socket* sock) = 0; + + // Estimated size of the buffer generated by AppendAndDestroySelf() + virtual size_t EstimatedByteSize() { return 0; } +}; + +namespace details { +struct SocketMessageDeleter { + void operator()(SocketMessage* msg) const { + butil::IOBuf dummy_buf; + // We don't care about the return value since the message is abandoned + (void)msg->AppendAndDestroySelf(&dummy_buf, NULL); + } +}; +} + +// A RAII pointer to make sure SocketMessage.AppendAndDestroySelf() is always +// called even if the message is rejected by Socket.Write() +// Property: Any SocketMessagePtr can be casted to SocketMessagePtr<> which +// is accepted by Socket.Write() +template struct SocketMessagePtr; + +template <> struct SocketMessagePtr + : public std::unique_ptr { + SocketMessagePtr() {} + SocketMessagePtr(SocketMessage* p) + : std::unique_ptr(p) {} +}; + +template +struct SocketMessagePtr : public SocketMessagePtr<> { + SocketMessagePtr() {} + SocketMessagePtr(T* p) : SocketMessagePtr<>(p) {} + T& operator*() { return static_cast(SocketMessagePtr<>::operator*()); } + T* operator->() { return static_cast(SocketMessagePtr<>::operator->()); } + T* release() { return static_cast(SocketMessagePtr<>::release()); } +}; + +} // namespace brpc + + +#endif // BRPC_SOCKET_MESSAGE_H diff --git a/src/brpc/socket_mode.h b/src/brpc/socket_mode.h new file mode 100644 index 0000000..b5d42be --- /dev/null +++ b/src/brpc/socket_mode.h @@ -0,0 +1,26 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_SOCKET_MODE_H +#define BRPC_SOCKET_MODE_H +namespace brpc { +enum SocketMode { + SOCKET_MODE_TCP = 0, + SOCKET_MODE_RDMA = 1 +}; +} // namespace brpc +#endif //BRPC_SOCKET_MODE_H \ No newline at end of file diff --git a/src/brpc/span.cpp b/src/brpc/span.cpp new file mode 100644 index 0000000..1863f01 --- /dev/null +++ b/src/brpc/span.cpp @@ -0,0 +1,1046 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include +#include "bthread/bthread.h" +#include "butil/scoped_lock.h" +#include "butil/thread_local.h" +#include "butil/string_printf.h" +#include "butil/time.h" +#include "butil/logging.h" +#include "butil/object_pool.h" +#include "butil/fast_rand.h" +#include "butil/file_util.h" +#include "butil/files/file_enumerator.h" +#include "brpc/shared_object.h" +#include "brpc/reloadable_flags.h" +#include "brpc/span.h" + +#define BRPC_SPAN_INFO_SEP "\1" + +namespace brpc { + +// Callback for creating a new bthread span when creating a new bthread. +// This is called by bthread layer when BTHREAD_INHERIT_SPAN flag is set. +// Returns a heap-allocated weak_ptr* as void*, or NULL if span creation fails. +void* CreateBthreadSpanAsVoid() { + const int64_t received_us = butil::cpuwide_time_us(); + const int64_t base_realtime = butil::gettimeofday_us() - received_us; + std::shared_ptr span = Span::CreateBthreadSpan("Bthread", base_realtime); + + if (!span) { + return NULL; + } + return new std::weak_ptr(span); +} + +void DestroyRpczParentSpan(void* ptr) { + if (ptr) { + delete static_cast*>(ptr); + } +} + +void EndBthreadSpan() { + std::shared_ptr span = GetTlsParentSpan(); + if (span) { + span->set_ending_tid(bthread_self()); + } + + ClearTlsParentSpan(); +} + +void SetTlsParentSpan(std::shared_ptr span) { + using namespace bthread; + LocalStorage ls = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_bls); + if (ls.rpcz_parent_span) { + *static_cast*>(ls.rpcz_parent_span) = span; + } else { + ls.rpcz_parent_span = new std::weak_ptr(span); + BAIDU_SET_VOLATILE_THREAD_LOCAL(tls_bls, ls); + } +} + +std::shared_ptr GetTlsParentSpan() { + using namespace bthread; + LocalStorage ls = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_bls); + if (!ls.rpcz_parent_span) { + return nullptr; + } + + auto* weak_ptr = static_cast*>(ls.rpcz_parent_span); + return weak_ptr->lock(); +} + +void ClearTlsParentSpan() { + using namespace bthread; + LocalStorage ls = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_bls); + if (ls.rpcz_parent_span) { + static_cast*>(ls.rpcz_parent_span)->reset(); + } +} + +bool HasTlsParentSpan() { + using namespace bthread; + LocalStorage ls = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_bls); + if (!ls.rpcz_parent_span) { + return false; + } + + auto* weak_ptr = static_cast*>(ls.rpcz_parent_span); + return !weak_ptr->expired(); +} + + +void SpanDeleter::operator()(Span* r) const { + if (r == NULL) { + return; + } + + // All children will be destroyed automatically along with the list. + // The list holds std::shared_ptr<> which will trigger deletion of + // children. + r->_client_list.clear(); + r->_info.clear(); + butil::return_object(r); +} + +const int64_t SPAN_DELETE_INTERVAL_US = 10000000L/*10s*/; + +DEFINE_string(rpcz_database_dir, "./rpc_data/rpcz", + "For storing requests/contexts collected by rpcz."); + +// TODO: collected per second is customizable. +// const int32_t MAX_RPCZ_MAX_SPAN_PER_SECOND = 10000; +// DEFINE_int32(rpcz_max_span_per_second, MAX_RPCZ_MAX_SPAN_PER_SECOND, +// "Index so many spans per second at most"); +// static bool validate_rpcz_max_span_per_second(const char*, int32_t val) { +// return (val >= 1 && val <= MAX_RPCZ_MAX_SPAN_PER_SECOND); +// } +// BRPC_VALIDATE_GFLAG(rpcz_max_span_per_second, +// validate_rpcz_max_span_per_second); + +DEFINE_int32(rpcz_keep_span_seconds, 3600, + "Keep spans for at most so many seconds"); +BRPC_VALIDATE_GFLAG(rpcz_keep_span_seconds, PositiveInteger); + +DEFINE_bool(rpcz_keep_span_db, false, "Don't remove DB of rpcz at program's exit"); + +DEFINE_int64(rpcz_save_span_min_latency_us, 0, "The minimum latency microseconds of span saved"); +BRPC_VALIDATE_GFLAG(rpcz_save_span_min_latency_us, NonNegativeInteger); + +struct IdGen { + bool init; + uint16_t seq; + uint64_t current_random; + butil::FastRandSeed seed; +}; + +static __thread IdGen tls_trace_id_gen = { false, 0, 0, { { 0, 0 } } }; +static __thread IdGen tls_span_id_gen = { false, 0, 0, { { 0, 0 } } }; + +inline uint64_t UpdateTLSRandom64(IdGen* g) { + if (!g->init) { + g->init = true; + init_fast_rand_seed(&g->seed); + } + const uint64_t val = fast_rand(&g->seed); + g->current_random = val; + return val; +} + +inline uint64_t GenerateSpanId() { + // 0 is an invalid Id + IdGen* g = &tls_span_id_gen; + if (g->seq == 0) { + UpdateTLSRandom64(g); + g->seq = 1; + } + return (g->current_random & 0xFFFFFFFFFFFF0000ULL) | g->seq++; +} + +inline uint64_t GenerateTraceId() { + // 0 is an invalid Id + IdGen* g = &tls_trace_id_gen; + if (g->seq == 0) { + UpdateTLSRandom64(g); + g->seq = 1; + } + return (g->current_random & 0xFFFFFFFFFFFF0000ULL) | g->seq++; +} + +Span::Span(Forbidden) { + CHECK_EQ(0, pthread_spin_init(&_info_spinlock, 0)) + << "Failed to initialize _info_spinlock"; + CHECK_EQ(0, pthread_spin_init(&_client_list_spinlock, 0)) + << "Failed to initialize _client_list_spinlock"; +} + +Span::~Span() { + pthread_spin_destroy(&_client_list_spinlock); + pthread_spin_destroy(&_info_spinlock); +} + +std::shared_ptr Span::CreateClientSpan(const std::string& full_method_name, + int64_t base_real_us) { + Span* span_raw = butil::get_object(Forbidden()); + if (__builtin_expect(span_raw == NULL, 0)) { + return nullptr; + } + std::shared_ptr span(span_raw, SpanDeleter()); + span->_log_id = 0; + span->_base_cid = INVALID_BTHREAD_ID; + span->_ending_cid = INVALID_BTHREAD_ID; // Client Span uses ending_cid + span->_type = SPAN_TYPE_CLIENT; + span->_async = false; + span->_protocol = PROTOCOL_UNKNOWN; + span->_error_code = 0; + span->_request_size = 0; + span->_response_size = 0; + span->_base_real_us = base_real_us; + span->_received_real_us = 0; + span->_start_parse_real_us = 0; + span->_start_callback_real_us = 0; + span->_start_send_real_us = 0; + span->_sent_real_us = 0; + span->_full_method_name = full_method_name; + span->_info.clear(); + std::shared_ptr parent = Span::tls_parent(); + if (parent) { + span->_trace_id = parent->trace_id(); + span->_parent_span_id = parent->span_id(); + span->_local_parent = parent; + { + BAIDU_SCOPED_LOCK(parent->_client_list_spinlock); + parent->_client_list.push_back(span); + } + } else { + span->_trace_id = GenerateTraceId(); + span->_parent_span_id = 0; + } + span->_span_id = GenerateSpanId(); + return span; +} + +std::shared_ptr Span::CreateBthreadSpan(const std::string& full_method_name, + int64_t base_real_us) { + std::shared_ptr parent = Span::tls_parent(); + Span* span_raw = butil::get_object(Forbidden()); + if (__builtin_expect(span_raw == NULL, 0)) { + return nullptr; + } + std::shared_ptr span(span_raw, SpanDeleter()); + span->_log_id = 0; + span->_base_cid = INVALID_BTHREAD_ID; + span->_ending_tid = INVALID_BTHREAD; // Bthread Span uses ending_tid + span->_type = SPAN_TYPE_BTHREAD; + span->_async = false; + span->_protocol = PROTOCOL_UNKNOWN; + span->_error_code = 0; + span->_request_size = 0; + span->_response_size = 0; + span->_base_real_us = base_real_us; + span->_received_real_us = 0; + span->_start_parse_real_us = 0; + span->_start_callback_real_us = 0; + span->_start_send_real_us = 0; + span->_sent_real_us = 0; + span->_full_method_name = full_method_name; + span->_info.clear(); + + if (parent) { + span->_trace_id = parent->trace_id(); + span->_parent_span_id = parent->span_id(); + span->_local_parent = parent; + { + BAIDU_SCOPED_LOCK(parent->_client_list_spinlock); + parent->_client_list.push_back(span); + } + } else { + span->_trace_id = GenerateTraceId(); + span->_parent_span_id = 0; + } + + span->_span_id = GenerateSpanId(); + return span; +} + +inline const std::string& unknown_span_name() { + // thread-safe in gcc. + static std::string s_unknown_method_name = "unknown_method"; + return s_unknown_method_name; +} + +std::shared_ptr Span::CreateServerSpan( + const std::string& full_method_name, + uint64_t trace_id, uint64_t span_id, uint64_t parent_span_id, + int64_t base_real_us) { + Span* span_raw = butil::get_object(Forbidden()); + if (__builtin_expect(span_raw == NULL, 0)) { + return nullptr; + } + std::shared_ptr span(span_raw, SpanDeleter()); + span->_trace_id = (trace_id ? trace_id : GenerateTraceId()); + span->_span_id = (span_id ? span_id : GenerateSpanId()); + span->_parent_span_id = parent_span_id; + span->_log_id = 0; + span->_base_cid = INVALID_BTHREAD_ID; + span->_ending_cid = INVALID_BTHREAD_ID; // Server Span uses ending_cid + span->_type = SPAN_TYPE_SERVER; + span->_async = false; + span->_protocol = PROTOCOL_UNKNOWN; + span->_error_code = 0; + span->_request_size = 0; + span->_response_size = 0; + span->_base_real_us = base_real_us; + span->_received_real_us = 0; + span->_start_parse_real_us = 0; + span->_start_callback_real_us = 0; + span->_start_send_real_us = 0; + span->_sent_real_us = 0; + span->_full_method_name = (!full_method_name.empty() ? + full_method_name : unknown_span_name()); + span->_info.clear(); + return span; +} + +std::shared_ptr Span::CreateServerSpan( + uint64_t trace_id, uint64_t span_id, uint64_t parent_span_id, + int64_t base_real_us) { + return CreateServerSpan(unknown_span_name(), trace_id, span_id, + parent_span_id, base_real_us); +} + +void Span::ResetServerSpanName(const std::string& full_method_name) { + _full_method_name = (!full_method_name.empty() ? + full_method_name : unknown_span_name()); +} + +void Span::submit(int64_t cpuwide_us) { + // Note: this method is not called for client-side spans. + EndAsParent(); + // If memory allocation fails, the server span will not be submitted for persistence. + // The server span will be destroyed later when its shared_ptr refcount drops to zero + // Child spans (held in _client_list) will also be destroyed when + // their refcounts reach zero. + (new SpanContainer(shared_from_this()))->submit(cpuwide_us); +} + +void Span::Annotate(const char* fmt, ...) { + const int64_t anno_time = butil::cpuwide_time_us() + _base_real_us; + BAIDU_SCOPED_LOCK(_info_spinlock); + butil::string_appendf(&_info, BRPC_SPAN_INFO_SEP "%lld ", + (long long)anno_time); + va_list ap; + va_start(ap, fmt); + butil::string_vappendf(&_info, fmt, ap); + va_end(ap); +} + +void Span::Annotate(const char* fmt, va_list args) { + const int64_t anno_time = butil::cpuwide_time_us() + _base_real_us; + BAIDU_SCOPED_LOCK(_info_spinlock); + butil::string_appendf(&_info, BRPC_SPAN_INFO_SEP "%lld ", + (long long)anno_time); + butil::string_vappendf(&_info, fmt, args); +} + +void Span::Annotate(const std::string& info) { + const int64_t anno_time = butil::cpuwide_time_us() + _base_real_us; + BAIDU_SCOPED_LOCK(_info_spinlock); + butil::string_appendf(&_info, BRPC_SPAN_INFO_SEP "%lld ", + (long long)anno_time); + _info.append(info); +} + +void Span::AnnotateCStr(const char* info, size_t length) { + const int64_t anno_time = butil::cpuwide_time_us() + _base_real_us; + BAIDU_SCOPED_LOCK(_info_spinlock); + butil::string_appendf(&_info, BRPC_SPAN_INFO_SEP "%lld ", + (long long)anno_time); + if (length <= 0) { + _info.append(info); + } else { + _info.append(info, length); + } +} + +size_t Span::CountClientSpans() const { + size_t n = 1; + { + BAIDU_SCOPED_LOCK(_client_list_spinlock); + for (const auto& child : _client_list) { + n += child->CountClientSpans(); + } + } + return n; +} + +int64_t Span::GetStartRealTimeUs() const { + return _type == SPAN_TYPE_SERVER ? _received_real_us : _start_send_real_us; +} + +int64_t Span::GetEndRealTimeUs() const { + int64_t result = 0; + result = std::max(result, _received_real_us); + result = std::max(result, _start_parse_real_us); + result = std::max(result, _start_callback_real_us); + result = std::max(result, _start_send_real_us); + result = std::max(result, _sent_real_us); + return result; +} + +SpanInfoExtractor::SpanInfoExtractor(const char* info) + : _sp(info, *BRPC_SPAN_INFO_SEP) { +} + +bool SpanInfoExtractor::PopAnnotation( + int64_t before_this_time, int64_t* time, std::string* annotation) { + for (; _sp != NULL; ++_sp) { + butil::StringSplitter sp_time(_sp.field(), _sp.field() + _sp.length(), ' '); + if (sp_time) { + char* endptr; + const int64_t anno_time = strtoll(sp_time.field(), &endptr, 10); + if (*endptr == ' ') { + if (before_this_time <= anno_time) { + return false; + } + *time = anno_time; + ++sp_time; + annotation->assign( + sp_time.field(), + _sp.field() + _sp.length() - sp_time.field()); + ++_sp; + return true; + } + } + LOG(ERROR) << "Unknown annotation: " + << std::string(_sp.field(), _sp.length()); + } + return false; +} + +bool CanAnnotateSpan() { + return HasTlsParentSpan(); +} + +void AnnotateSpan(const char* fmt, ...) { + std::shared_ptr span = GetTlsParentSpan(); + if (span) { // TRACEPRINTF checks CanAnnotateSpan, but this is safer. + va_list ap; + va_start(ap, fmt); + span->Annotate(fmt, ap); + va_end(ap); + } +} + +void AnnotateSpanEx(std::shared_ptr span, const char* fmt, ...) { + if (span) { + va_list ap; + va_start(ap, fmt); + span->Annotate(fmt, ap); + va_end(ap); + } +} + +class SpanDB : public SharedObject { +public: + leveldb::DB* id_db; + leveldb::DB* time_db; + std::string id_db_name; + std::string time_db_name; + + SpanDB() : id_db(NULL), time_db(NULL) { } + static SpanDB* Open(); + leveldb::Status Index(std::shared_ptr span, std::string* value_buf); + leveldb::Status RemoveSpansBefore(int64_t tm); + +private: + static void Swap(SpanDB& db1, SpanDB& db2) { + std::swap(db1.id_db, db2.id_db); + std::swap(db1.id_db_name, db2.id_db_name); + std::swap(db1.time_db, db2.time_db); + std::swap(db1.time_db_name, db2.time_db_name); + } + + ~SpanDB() { + if (id_db == NULL && time_db == NULL) { + return; + } + delete id_db; + delete time_db; + if (!FLAGS_rpcz_keep_span_db) { + butil::DeleteFile(butil::FilePath(id_db_name), true); + butil::DeleteFile(butil::FilePath(time_db_name), true); + } + } +}; + +static bool started_span_indexing = false; +static pthread_once_t start_span_indexing_once = PTHREAD_ONCE_INIT; +static int64_t g_last_time_key = 0; +static int64_t g_last_delete_tm = 0; + +// Following variables are monitored by builtin services, thus non-static. +static pthread_mutex_t g_span_db_mutex = PTHREAD_MUTEX_INITIALIZER; +static bool g_span_ending = false; // don't open span again if this var is true. +// Can't use intrusive_ptr which has ctor/dtor issues. +static SpanDB* g_span_db = NULL; +bool has_span_db() { return !!g_span_db; } +bvar::CollectorSpeedLimit g_span_sl = BVAR_COLLECTOR_SPEED_LIMIT_INITIALIZER; +static bvar::DisplaySamplingRatio s_display_sampling_ratio( + "rpcz_sampling_ratio", &g_span_sl); + +struct SpanEarlier { + bool operator()(const bvar::Collected* c1, const bvar::Collected* c2) const { + const SpanContainer* container1 = static_cast(c1); + const SpanContainer* container2 = static_cast(c2); + + const int64_t time1 = container1->span()->GetStartRealTimeUs(); + const int64_t time2 = container2->span()->GetStartRealTimeUs(); + + return time1 < time2; + } +}; +class SpanPreprocessor : public bvar::CollectorPreprocessor { +public: + void process(std::vector & list) { + // Sort spans by their starting time so that the code on making + // time monotonic in Span::Index works better. + std::sort(list.begin(), list.end(), SpanEarlier()); + } +}; +static SpanPreprocessor* g_span_prep = NULL; + +bvar::CollectorSpeedLimit* Span::speed_limit() { + return &g_span_sl; +} + +bvar::CollectorPreprocessor* Span::preprocessor() { + return g_span_prep; +} + +static void ResetSpanDB(SpanDB* db) { + SpanDB* old_db = NULL; + { + BAIDU_SCOPED_LOCK(g_span_db_mutex); + old_db = g_span_db; + g_span_db = db; + if (g_span_db) { + g_span_db->AddRefManually(); + } + } + if (old_db) { + old_db->RemoveRefManually(); + } +} + +static void RemoveSpanDB() { + g_span_ending = true; + ResetSpanDB(NULL); +} + +static void StartSpanIndexing() { + atexit(RemoveSpanDB); + g_span_prep = new SpanPreprocessor; + started_span_indexing = true; +} + +static int StartIndexingIfNeeded() { + if (pthread_once(&start_span_indexing_once, StartSpanIndexing) != 0) { + return -1; + } + return started_span_indexing ? 0 : -1; +} + +inline int GetSpanDB(butil::intrusive_ptr* db) { + BAIDU_SCOPED_LOCK(g_span_db_mutex); + if (g_span_db != NULL) { + *db = g_span_db; + return 0; + } + return -1; +} + +void Span::Submit(std::shared_ptr span, int64_t cpuwide_time_us) { + // Only submit spans without a local parent (i.e., server spans). + // Server spans hold shared_ptr references to their child spans (via _client_list), + // ensuring child spans remain alive until the server span is submitted and dumped. + // Client spans are not submitted here because their lifetime is managed by their + // parent server span. + if (span->local_parent().expired()) { + span->submit(cpuwide_time_us); + } +} + +static void Span2Proto(const Span* span, RpczSpan* out) { + out->set_trace_id(span->trace_id()); + out->set_span_id(span->span_id()); + out->set_parent_span_id(span->parent_span_id()); + out->set_log_id(span->log_id()); + out->set_base_cid(span->base_cid().value); + out->set_ending_cid(span->ending_cid().value); + out->set_remote_ip(butil::ip2int(span->remote_side().ip)); + out->set_remote_port(span->remote_side().port); + out->set_type(span->type()); + out->set_async(span->async()); + out->set_protocol(span->protocol()); + out->set_request_size(span->request_size()); + out->set_response_size(span->response_size()); + out->set_received_real_us(span->received_real_us()); + out->set_start_parse_real_us(span->start_parse_real_us()); + out->set_start_callback_real_us(span->start_callback_real_us()); + out->set_start_send_real_us(span->start_send_real_us()); + out->set_sent_real_us(span->sent_real_us()); + out->set_full_method_name(span->full_method_name()); + // info() returns by value for thread safety (see span.h for details). + out->set_info(span->info()); + out->set_error_code(span->error_code()); +} + +inline void ToBigEndian(uint64_t n, uint32_t* buf) { + buf[0] = htonl(n >> 32); + buf[1] = htonl(n & 0xFFFFFFFFUL); +} + +inline uint64_t ToLittleEndian(const uint32_t* buf) { + return (((uint64_t)ntohl(buf[0])) << 32) | ntohl(buf[1]); +} + +SpanDB* SpanDB::Open() { + // Remove old rpcz directory even if crash occurs. + if (!FLAGS_rpcz_keep_span_db) { + butil::FileEnumerator dirs(butil::FilePath(FLAGS_rpcz_database_dir), false, + butil::FileEnumerator::DIRECTORIES, "[0-9]*.[0-9]*.[0-9]*"); + for (auto name = dirs.Next(); !name.empty(); name = dirs.Next()) { + butil::DeleteFile(name, true); + } + } + + SpanDB local; + leveldb::Status st; + char prefix[64]; + time_t rawtime; + time(&rawtime); + struct tm lt_buf; + struct tm* timeinfo = localtime_r(&rawtime, <_buf); + const size_t nw = strftime(prefix, sizeof(prefix), + "/%Y%m%d.%H%M%S", timeinfo); + const int nw2 = snprintf(prefix + nw, sizeof(prefix) - nw, ".%d", + getpid()); + leveldb::Options options; + options.create_if_missing = true; + options.error_if_exists = true; + + local.id_db_name.append(FLAGS_rpcz_database_dir); + local.id_db_name.append(prefix, nw + nw2); + // Create the dir first otherwise leveldb fails. + butil::File::Error error; + const butil::FilePath dir(local.id_db_name); + if (!butil::CreateDirectoryAndGetError(dir, &error)) { + LOG(ERROR) << "Fail to create directory=`" << dir.value() << ", " + << error; + return NULL; + } + + local.id_db_name.append("/id.db"); + st = leveldb::DB::Open(options, local.id_db_name.c_str(), &local.id_db); + if (!st.ok()) { + LOG(ERROR) << "Fail to open id_db: " << st.ToString(); + return NULL; + } + + local.time_db_name.append(FLAGS_rpcz_database_dir); + local.time_db_name.append(prefix, nw + nw2); + local.time_db_name.append("/time.db"); + st = leveldb::DB::Open(options, local.time_db_name.c_str(), &local.time_db); + if (!st.ok()) { + LOG(ERROR) << "Fail to open time_db: " << st.ToString(); + return NULL; + } + SpanDB* db = new (std::nothrow) SpanDB; + if (NULL == db) { + return NULL; + } + LOG(INFO) << "Opened " << local.id_db_name << " and " + << local.time_db_name; + Swap(local, *db); + return db; +} + +leveldb::Status SpanDB::Index(std::shared_ptr span, std::string* value_buf) { + leveldb::WriteOptions options; + options.sync = false; + + leveldb::Status st; + + // NOTE: Writing into time_db before id_db so that if the second write + // fails, the entry in time_db will be finally removed when it's out + // of time window. + + const int64_t start_time = span->GetStartRealTimeUs(); + const int64_t latency_us = span->GetEndRealTimeUs() - start_time; + // if latency_us < FLAGS_rpcz_save_span_min_latency_us, don't save this span + if (latency_us < FLAGS_rpcz_save_span_min_latency_us) { + return leveldb::Status::OK(); + } + BriefSpan brief; + brief.set_trace_id(span->trace_id()); + brief.set_span_id(span->span_id()); + brief.set_log_id(span->log_id()); + brief.set_type(span->type()); + brief.set_error_code(span->error_code()); + brief.set_request_size(span->request_size()); + brief.set_response_size(span->response_size()); + brief.set_start_real_us(start_time); + brief.set_latency_us(latency_us); + brief.set_full_method_name(span->full_method_name()); + if (!brief.SerializeToString(value_buf)) { + return leveldb::Status::InvalidArgument( + leveldb::Slice("Fail to serialize BriefSpan")); + } + // We need to make the time monotonic otherwise if older entries are + // overwritten by newer ones, entries in id_db associated with the older + // entries are not evicted. Surely we can call DB::Get() before Put(), but + // that would be too slow due to the storage model of leveldb. One feasible + // method is to maintain recent window of keys to time_db, when there's a + // conflict before Put(), try key+1us until an unused time is found. The + // window could be 5~10s. However this method needs a std::map(slow) or + // hashmap+queue(more memory: remember that we're just a framework), and + // this method can't guarantee no duplication when real time goes back + // significantly. + // Since the time to this method is ALMOST in ascending order, we use a + // very simple strategy: if the time is not greater than last-time, set + // it to be last-time + 1us. This works when time goes back because the + // real time is at least 1000000 / FLAGS_rpcz_max_span_per_second times faster + // and it will finally catch up with our time key. (provided the flag + // is less than 1000000). + int64_t time_key = start_time; + if (time_key <= g_last_time_key) { + time_key = g_last_time_key + 1; + } + g_last_time_key = time_key; + uint32_t time_data[2]; + ToBigEndian(time_key, time_data); + st = time_db->Put(options, + leveldb::Slice((char*)time_data, sizeof(time_data)), + leveldb::Slice(value_buf->data(), value_buf->size())); + if (!st.ok()) { + return st; + } + + uint32_t key_data[4]; + ToBigEndian(span->trace_id(), key_data); + ToBigEndian(span->span_id(), key_data + 2); + leveldb::Slice key((char*)key_data, sizeof(key_data)); + RpczSpan value_proto; + Span2Proto(span.get(), &value_proto); + + std::vector> all_child_spans; + + std::function)> collect_all_spans = + [&](std::shared_ptr current_span) { + if (!current_span) { + return; + } + + std::vector> children; + { + BAIDU_SCOPED_LOCK(current_span->_client_list_spinlock); + children.reserve(current_span->_client_list.size()); + for (const auto& child_span : current_span->_client_list) { + if (child_span) { + children.push_back(child_span); + } + } + } + + for (const auto& child : children) { + collect_all_spans(child); + } + + all_child_spans.push_back(current_span); + }; + + collect_all_spans(span); + + // Traverse in reverse order and insert child elements. + // Only collect ended spans to avoid race conditions - active spans may still + // be modified by other threads, which could lead to inconsistent data when + // serializing to database. + for (auto it = all_child_spans.rbegin(); it != all_child_spans.rend(); ++it) { + if (*it && it->get() != span.get() && !(*it)->is_active()) { + RpczSpan* child_proto = value_proto.add_client_spans(); + Span2Proto((*it).get(), child_proto); + } + } + if (!value_proto.SerializeToString(value_buf)) { + return leveldb::Status::InvalidArgument( + leveldb::Slice("Fail to serialize RpczSpan")); + } + leveldb::Slice value(value_buf->data(), value_buf->size()); + st = id_db->Put(options, key, value); + return st; +} + +// NOTE: may take more than 100ms +leveldb::Status SpanDB::RemoveSpansBefore(int64_t tm) { + if (id_db == NULL || time_db == NULL) { + return leveldb::Status::InvalidArgument(leveldb::Slice("NULL param")); + } + leveldb::Status rc; + leveldb::WriteOptions options; + options.sync = false; + leveldb::Iterator* it = time_db->NewIterator(leveldb::ReadOptions()); + for (it->SeekToFirst(); it->Valid(); it->Next()) { + if (it->key().size() != 8) { + LOG(ERROR) << "Invalid key size: " << it->key().size(); + continue; + } + const int64_t realtime = + ToLittleEndian((const uint32_t*)it->key().data()); + if (realtime >= tm) { // removal is done. + break; + } + BriefSpan brief; + if (brief.ParseFromArray(it->value().data(), it->value().size())) { + uint32_t key_data[4]; + ToBigEndian(brief.trace_id(), key_data); + ToBigEndian(brief.span_id(), key_data + 2); + leveldb::Slice key((char*)key_data, sizeof(key_data)); + rc = id_db->Delete(options, key); + if (!rc.ok()) { + LOG(ERROR) << "Fail to delete from id_db"; + break; + } + } else { + LOG(ERROR) << "Fail to parse value"; + } + rc = time_db->Delete(options, it->key()); + if (!rc.ok()) { + LOG(ERROR) << "Fail to delete from time_db"; + break; + } + } + delete it; + return rc; +} + +// Write span into leveldb. +void Span::dump_to_db() { + StartIndexingIfNeeded(); + + std::string value_buf; + + butil::intrusive_ptr db; + if (GetSpanDB(&db) != 0) { + if (g_span_ending) { + return; + } + SpanDB* db2 = SpanDB::Open(); + if (db2 == NULL) { + LOG(WARNING) << "Fail to open SpanDB"; + return; + } + ResetSpanDB(db2); + db.reset(db2); + } + + leveldb::Status st = db->Index(shared_from_this(), &value_buf); + if (!st.ok()) { + LOG(WARNING) << st.ToString(); + if (st.IsNotFound() || st.IsIOError() || st.IsCorruption()) { + ResetSpanDB(NULL); + return; + } + } + + // Remove old spans + const int64_t now = butil::gettimeofday_us(); + if (now > g_last_delete_tm + SPAN_DELETE_INTERVAL_US) { + g_last_delete_tm = now; + leveldb::Status st = db->RemoveSpansBefore( + now - FLAGS_rpcz_keep_span_seconds * 1000000L); + if (!st.ok()) { + LOG(ERROR) << st.ToString(); + if (st.IsNotFound() || st.IsIOError() || st.IsCorruption()) { + ResetSpanDB(NULL); + return; + } + } + } +} + +// ========== SpanContainer ============ + +// Destroy the span container without persisting to database. +// This is called in abnormal scenarios: +// 1. When the pending sample queue is full (to prevent memory explosion) +// 2. When grab_thread hasn't run for too long (system overload) +// In these cases, we discard the span quickly without expensive I/O. +void SpanContainer::destroy() { + delete this; +} + +// The round parameter is required by bvar::Collected interface but unused here. +// Other implementations (e.g., SampledRequest in rpc_dump.cpp) use it to detect +// new batches and trigger per-round operations like reloading gflags or switching +// output files. SpanContainer doesn't need batch-level operations since it writes +// directly to leveldb without buffering or configuration reloading. +void SpanContainer::dump_and_destroy(size_t round) { + if (_span) { + _span->dump_to_db(); + } + destroy(); +} + +void SpanContainer::submit(int64_t cpuwide_us) { + bvar::Collected::submit(cpuwide_us); +} + +bvar::CollectorSpeedLimit* SpanContainer::speed_limit() { + if (_span) { + return _span->speed_limit(); + } + return NULL; +} + +// ===================================== + +int FindSpan(uint64_t trace_id, uint64_t span_id, RpczSpan* response) { + butil::intrusive_ptr db; + if (GetSpanDB(&db) != 0) { + return -1; + } + uint32_t key_data[4]; + ToBigEndian(trace_id, key_data); + ToBigEndian(span_id, key_data + 2); + leveldb::Slice key((char*)key_data, sizeof(key_data)); + std::string value; + leveldb::Status st = db->id_db->Get(leveldb::ReadOptions(), key, &value); + if (!st.ok()) { + return -1; + } + if (!response->ParseFromString(value)) { + LOG(ERROR) << "Fail to parse from the value"; + return -1; + } + return 0; +} + +void FindSpans(uint64_t trace_id, std::deque* out) { + out->clear(); + butil::intrusive_ptr db; + if (GetSpanDB(&db) != 0) { + return; + } + leveldb::Iterator* it = db->id_db->NewIterator(leveldb::ReadOptions()); + uint32_t key_data[4]; + ToBigEndian(trace_id, key_data); + ToBigEndian(0, key_data + 2); + leveldb::Slice key((char*)key_data, sizeof(key_data)); + for (it->Seek(key); it->Valid(); it->Next()) { + if (it->key().size() != sizeof(key_data)) { + LOG(ERROR) << "Invalid key size: " << it->key().size(); + break; + } + const uint64_t stored_trace_id = + ToLittleEndian((const uint32_t*)it->key().data()); + if (trace_id != stored_trace_id) { + break; + } + RpczSpan span; + if (span.ParseFromArray(it->value().data(), it->value().size())) { + out->push_back(span); + } else { + LOG(ERROR) << "Fail to parse from value"; + } + } + delete it; +} + +void ListSpans(int64_t starting_realtime, size_t max_scan, + std::deque* out, SpanFilter* filter) { + out->clear(); + butil::intrusive_ptr db; + if (GetSpanDB(&db) != 0) { + return; + } + leveldb::Iterator* it = db->time_db->NewIterator(leveldb::ReadOptions()); + uint32_t time_data[2]; + ToBigEndian(starting_realtime, time_data); + leveldb::Slice key((char*)time_data, sizeof(time_data)); + it->Seek(key); + if (!it->Valid()) { + it->SeekToLast(); + } + BriefSpan brief; + size_t nscan = 0; + for (; nscan < max_scan && it->Valid(); it->Prev()) { + const int64_t key_tm = ToLittleEndian((const uint32_t*)it->key().data()); + // May have some bigger time at the beginning, because leveldb returns + // keys >= starting_realtime. + if (key_tm > starting_realtime) { + continue; + } + brief.Clear(); + if (brief.ParseFromArray(it->value().data(), it->value().size())) { + if (NULL == filter || filter->Keep(brief)) { + out->push_back(brief); + } + // We increase the count no matter filter passed or not to avoid + // scaning too many entries. + ++nscan; + } else { + LOG(ERROR) << "Fail to parse from value"; + } + } + delete it; +} + +void DescribeSpanDB(std::ostream& os) { + butil::intrusive_ptr db; + if (GetSpanDB(&db) != 0) { + return; + } + + if (db->id_db != NULL) { + std::string val; + if (db->id_db->GetProperty(leveldb::Slice("leveldb.stats"), &val)) { + os << "[ " << db->id_db_name << " ]\n" << val; + } + if (db->id_db->GetProperty(leveldb::Slice("leveldb.sstables"), &val)) { + os << '\n' << val; + } + } + os << '\n'; + if (db->time_db != NULL) { + std::string val; + if (db->time_db->GetProperty(leveldb::Slice("leveldb.stats"), &val)) { + os << "[ " << db->time_db_name << " ]\n" << val; + } + if (db->time_db->GetProperty(leveldb::Slice("leveldb.sstables"), &val)) { + os << '\n' << val; + } + } +} + +} // namespace brpc diff --git a/src/brpc/span.h b/src/brpc/span.h new file mode 100644 index 0000000..70fdbf4 --- /dev/null +++ b/src/brpc/span.h @@ -0,0 +1,337 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + + +// NOTE: RPC users are not supposed to include this file. + +#ifndef BRPC_SPAN_H +#define BRPC_SPAN_H + +#include +#include +#include +#include +#include +#include +#include +#include "butil/macros.h" +#include "butil/endpoint.h" +#include "butil/string_splitter.h" +#include "bvar/collector.h" +#include "bthread/task_meta.h" +#include "brpc/options.pb.h" // ProtocolType +#include "brpc/span.pb.h" + +namespace bthread { +extern __thread bthread::LocalStorage tls_bls; +} + +namespace brpc { + +class Span; + +void SetTlsParentSpan(std::shared_ptr span); +std::shared_ptr GetTlsParentSpan(); +void ClearTlsParentSpan(); +bool HasTlsParentSpan(); + +void* CreateBthreadSpanAsVoid(); +void DestroyRpczParentSpan(void* ptr); +void EndBthreadSpan(); + +DECLARE_bool(enable_rpcz); + +class Span; +class SpanContainer; + +// Deleter for Span. +struct SpanDeleter { + void operator()(Span* r) const; +}; + +// Collect information required by /rpcz and tracing system whose idea is +// described in http://static.googleusercontent.com/media/research.google.com/en//pubs/archive/36356.pdf +class Span : public std::enable_shared_from_this { +friend class SpanDB; +friend struct SpanDeleter; +friend class SpanContainer; +public: + struct Forbidden {}; + // Call CreateServerSpan/CreateClientSpan instead. + Span(Forbidden); + ~Span(); + + // Create a span to track a request inside server. + static std::shared_ptr CreateServerSpan( + const std::string& full_method_name, + uint64_t trace_id, uint64_t span_id, uint64_t parent_span_id, + int64_t base_real_us); + // Create a span without name to track a request inside server. + static std::shared_ptr CreateServerSpan( + uint64_t trace_id, uint64_t span_id, uint64_t parent_span_id, + int64_t base_real_us); + + // Clear all annotations and reset name of the span. + void ResetServerSpanName(const std::string& name); + + // Create a span to track a request inside channel. + static std::shared_ptr CreateClientSpan(const std::string& full_method_name, + int64_t base_real_us); + + // Create a span to track start bthread + static std::shared_ptr CreateBthreadSpan(const std::string& full_method_name, + int64_t base_real_us); + + static void Submit(std::shared_ptr span, int64_t cpuwide_time_us); + + // Set this span as the TLS parent for subsequent child span creation. + // Typical flow: + // 1. Server span calls AsParent() before user callback to enable tracing + // 2. Client spans created in user code automatically link to this parent + // 3. When client RPC completes, it restores its own parent via AsParent() + // to maintain the trace chain (see Controller::SubmitSpan) + // 4. Server span calls EndAsParent() when submitting to clear TLS parent + void AsParent() { + SetTlsParentSpan(shared_from_this()); + } + + // Add log with time. + void Annotate(const char* fmt, ...); + void Annotate(const char* fmt, va_list args); + void Annotate(const std::string& info); + // When length <= 0, use strlen instead. + void AnnotateCStr(const char* cstr, size_t length); + + // #child spans, Not O(1) + size_t CountClientSpans() const; + + int64_t GetStartRealTimeUs() const; + int64_t GetEndRealTimeUs() const; + + void set_log_id(uint64_t cid) { _log_id = cid; } + void set_base_cid(bthread_id_t id) { _base_cid = id; } + void set_ending_cid(bthread_id_t id) { _ending_cid = id; } + void set_ending_tid(bthread_t tid) { _ending_tid = tid; } + void set_remote_side(const butil::EndPoint& pt) { _remote_side = pt; } + void set_protocol(ProtocolType p) { _protocol = p; } + void set_error_code(int error_code) { _error_code = error_code; } + void set_request_size(int size) { _request_size = size; } + void set_response_size(int size) { _response_size = size; } + void set_async(bool async) { _async = async; } + + void set_base_real_us(int64_t tm) { _base_real_us = tm; } + void set_received_us(int64_t tm) + { _received_real_us = tm + _base_real_us; } + void set_start_parse_us(int64_t tm) + { _start_parse_real_us = tm + _base_real_us; } + void set_start_callback_us(int64_t tm) + { _start_callback_real_us = tm + _base_real_us; } + void set_start_send_us(int64_t tm) + { _start_send_real_us = tm + _base_real_us; } + void set_sent_us(int64_t tm) + { _sent_real_us = tm + _base_real_us; } + + bool is_active() const { + if (_type == SPAN_TYPE_BTHREAD) { + return _ending_tid == INVALID_BTHREAD; + } + return _ending_cid == INVALID_BTHREAD_ID; + } + + std::weak_ptr local_parent() const { return _local_parent; } + static std::shared_ptr tls_parent() { + auto parent = GetTlsParentSpan(); + if (parent && parent->is_active()) { + return parent; + } + return nullptr; + } + + uint64_t trace_id() const { return _trace_id; } + uint64_t parent_span_id() const { return _parent_span_id; } + uint64_t span_id() const { return _span_id; } + uint64_t log_id() const { return _log_id; } + bthread_id_t base_cid() const { return _base_cid; } + bthread_id_t ending_cid() const { return _ending_cid; } + bthread_t ending_tid() const { return _ending_tid; } + const butil::EndPoint& remote_side() const { return _remote_side; } + SpanType type() const { return _type; } + ProtocolType protocol() const { return _protocol; } + int error_code() const { return _error_code; } + int request_size() const { return _request_size; } + int response_size() const { return _response_size; } + int64_t received_real_us() const { return _received_real_us; } + int64_t start_parse_real_us() const { return _start_parse_real_us; } + int64_t start_callback_real_us() const { return _start_callback_real_us; } + int64_t start_send_real_us() const { return _start_send_real_us; } + int64_t sent_real_us() const { return _sent_real_us; } + bool async() const { return _async; } + const std::string& full_method_name() const { return _full_method_name; } + + // Returns a copy instead of a reference for thread safety. + // + // Current usage: Only called by Span2Proto() which immediately passes the result + // to protobuf's set_info(). In this specific scenario, returning a reference would + // also be safe because set_info() copies the string before the reference could be + // invalidated by concurrent Annotate() calls. + // + // However, returning by value is more robust: it prevents potential data races if + // future code holds the reference longer, and has no performance penalty due to + // C++11 move semantics (the temporary is moved, not copied, into protobuf). + std::string info() const { + BAIDU_SCOPED_LOCK(_info_spinlock); + return _info; + } + +private: + DISALLOW_COPY_AND_ASSIGN(Span); + + void dump_to_db(); + void submit(int64_t cpuwide_us); + bvar::CollectorSpeedLimit* speed_limit(); + bvar::CollectorPreprocessor* preprocessor(); + + // Clear this span from TLS parent if it's currently set as the parent. + // Called when server span is being submitted to prevent subsequent spans + // from incorrectly linking to an ended span. Only clears if the current + // TLS parent is this span (avoids clearing if another span has taken over). + void EndAsParent() { + std::shared_ptr current_parent = GetTlsParentSpan(); + if (current_parent.get() == this) { + ClearTlsParentSpan(); + } + } + + uint64_t _trace_id; + uint64_t _span_id; + uint64_t _parent_span_id; + uint64_t _log_id; + bthread_id_t _base_cid; + bthread_id_t _ending_cid; + bthread_t _ending_tid; // Used for bthread span to store the ending bthread tid + butil::EndPoint _remote_side; + SpanType _type; + bool _async; + ProtocolType _protocol; + int _error_code; + int _request_size; + int _response_size; + int64_t _base_real_us; + int64_t _received_real_us; + int64_t _start_parse_real_us; + int64_t _start_callback_real_us; + int64_t _start_send_real_us; + int64_t _sent_real_us; + std::string _full_method_name; + // Format: + // time1_us \s annotation1 + // time2_us \s annotation2 + // ... + std::string _info; + // Protects _info from concurrent modifications. + // Multiple threads may call Annotate() simultaneously (e.g., retry logic, + // network layer, user code via TRACEPRINTF), causing data corruption in + // string concatenation without synchronization. + mutable pthread_spinlock_t _info_spinlock; + + std::weak_ptr _local_parent; + std::list> _client_list; + // Protects _client_list from concurrent modifications. + // In some scenarios, multiple bthreads may simultaneously create child spans + // (e.g.,raft leader parallel RPCs to followers) and push_back to parent's _client_list. + // Also protects against concurrent iteration (e.g., CountClientSpans, SpanDB::Index) + // while the list is being modified. + mutable pthread_spinlock_t _client_list_spinlock; +}; + +class SpanContainer : public bvar::Collected { +public: + explicit SpanContainer(const std::shared_ptr& span) : _span(span) {} + ~SpanContainer() {} + + // Implementations of bvar::Collected + void dump_and_destroy(size_t round_index) override; + void destroy() override; + bvar::CollectorSpeedLimit* speed_limit() override; + + void submit(int64_t cpuwide_us); + + const std::shared_ptr& span() const { return _span; } + +private: + std::shared_ptr _span; +}; + +// Extract name and annotations from Span::info() +class SpanInfoExtractor { +public: + SpanInfoExtractor(const char* info); + bool PopAnnotation(int64_t before_this_time, + int64_t* time, std::string* annotation); +private: + butil::StringSplitter _sp; +}; + +// These two functions can be used for composing TRACEPRINT// Add an annotation to the current span. +// If current bthread is not tracing, this function does nothing. +void AnnotateSpan(const char* fmt, ...); + +// Add an annotation to the given span. +// If the span is NULL, this function does nothing. +void AnnotateSpanEx(std::shared_ptr span, const char* fmt, ...); + + +class SpanFilter { +public: + virtual ~SpanFilter() = default; + virtual bool Keep(const BriefSpan&) = 0; +}; + +class SpanDB; + +// Find a span by its trace_id and span_id, serialize it into `span'. +int FindSpan(uint64_t trace_id, uint64_t span_id, RpczSpan* span); + +// Find spans by their trace_id, serialize them into `out' +void FindSpans(uint64_t trace_id, std::deque* out); + +// Put at most `max_scan' spans before `before_this_time' into `out'. +// If filter is not NULL, only push spans that make SpanFilter::Keep() +// true. +void ListSpans(int64_t before_this_time, size_t max_scan, + std::deque* out, SpanFilter* filter); + +void DescribeSpanDB(std::ostream& os); + +SpanDB* LoadSpanDBFromFile(const char* filepath); +int FindSpan(SpanDB* db, uint64_t trace_id, uint64_t span_id, RpczSpan* span); +void FindSpans(SpanDB* db, uint64_t trace_id, std::deque* out); +void ListSpans(SpanDB* db, int64_t before_this_time, size_t max_scan, + std::deque* out, SpanFilter* filter); + +// Check this function first before creating a span. +// If rpcz of upstream is enabled, local rpcz is enabled automatically. +inline bool IsTraceable(bool is_upstream_traced) { + extern bvar::CollectorSpeedLimit g_span_sl; + return is_upstream_traced || + (FLAGS_enable_rpcz && bvar::is_collectable(&g_span_sl)); +} + +} // namespace brpc + + +#endif // BRPC_SPAN_H diff --git a/src/brpc/span.proto b/src/brpc/span.proto new file mode 100644 index 0000000..d37a53e --- /dev/null +++ b/src/brpc/span.proto @@ -0,0 +1,98 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +import "brpc/options.proto"; + +package brpc; +option java_package="com.brpc"; +option java_outer_classname="Span"; + +enum SpanType { + SPAN_TYPE_SERVER = 0; + SPAN_TYPE_CLIENT = 1; + SPAN_TYPE_BTHREAD = 2; +} + +// We don't unify RpczSpan and TracingSpan as one because the former one needs +// to be as lightweight as possible, say `info' equals span_name + annotations +// in TracingSpan which needs heavier memory allocations. +message RpczSpan { + required uint64 trace_id = 1; + required uint64 span_id = 2; + required uint64 parent_span_id = 3; + optional uint64 log_id = 4; + optional uint64 base_cid = 5; + optional uint64 ending_cid = 6; + // We don't use additional message for EndPoint to save a memory allocation. + optional uint32 remote_ip = 7; + optional uint32 remote_port = 8; + optional SpanType type = 9; + optional bool async = 10; + optional ProtocolType protocol = 11; + optional int32 error_code = 12; + optional int32 request_size = 13; + optional int32 response_size = 14; + optional int64 received_real_us = 15; + optional int64 start_parse_real_us = 16; + optional int64 start_callback_real_us = 17; + optional int64 start_send_real_us = 18; + optional int64 sent_real_us = 19; + optional bytes info = 20; + repeated RpczSpan client_spans = 21; + optional bytes full_method_name = 22; +} + +message BriefSpan { + required uint64 trace_id = 1; + required uint64 span_id = 2; + optional uint64 log_id = 3; + optional SpanType type = 4; + optional int32 error_code = 5; + optional int32 request_size = 6; + optional int32 response_size = 7; + optional int64 start_real_us = 8; + optional int64 latency_us = 9; + optional bytes full_method_name = 10; +} + +message SpanAnnotation { + required int64 realtime_us = 1; + required string content = 2; +} + +message TracingSpan { + required uint64 trace_id = 1; + required uint64 span_id = 2; + required uint64 parent_span_id = 3; + optional uint64 log_id = 4; + optional uint32 remote_ip = 5; + optional uint32 remote_port = 6; + optional SpanType type = 7; + optional ProtocolType protocol = 8; + optional int32 error_code = 9; + optional int32 request_size = 10; + optional int32 response_size = 11; + optional int64 received_real_us = 12; + optional int64 start_parse_real_us = 13; + optional int64 start_callback_real_us = 14; + optional int64 start_send_real_us = 15; + optional int64 sent_real_us = 16; + optional string span_name = 17; + repeated SpanAnnotation annotations = 18; + repeated TracingSpan client_spans = 19; +} diff --git a/src/brpc/ssl_options.cpp b/src/brpc/ssl_options.cpp new file mode 100644 index 0000000..efeab5c --- /dev/null +++ b/src/brpc/ssl_options.cpp @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/ssl_options.h" + +namespace brpc { + +VerifyOptions::VerifyOptions() + : verify_depth(0) + , verify_mode(VerifyMode::NOT_SET) +{} + +ChannelSSLOptions::ChannelSSLOptions() + : ciphers("DEFAULT") + , protocols("TLSv1, TLSv1.1, TLSv1.2, TLSv1.3") +{} + +ServerSSLOptions::ServerSSLOptions() + : strict_sni(false) + , disable_ssl3(true) + , release_buffer(false) + , session_lifetime_s(300) + , session_cache_size(20480) + , ecdhe_curve_name("prime256v1") +{} + +} // namespace brpc diff --git a/src/brpc/ssl_options.h b/src/brpc/ssl_options.h new file mode 100644 index 0000000..3fdb1e8 --- /dev/null +++ b/src/brpc/ssl_options.h @@ -0,0 +1,181 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_SSL_OPTION_H +#define BRPC_SSL_OPTION_H + +#include +#include + +namespace brpc { + +struct CertInfo { + // Certificate in PEM format. + // Note that CN and alt subjects will be extracted from the certificate, + // and will be used as hostnames. Requests to this hostname (provided SNI + // extension supported) will be encrypted using this certificate. + // Supported both file path and raw string + std::string certificate; + + // Private key in PEM format. + // Supported both file path and raw string based on prefix: + std::string private_key; + + // Additional hostnames besides those inside the certificate. Wildcards + // are supported but it can only appear once at the beginning (i.e. *.xxx.com). + std::vector sni_filters; +}; + +enum class VerifyMode { + NOT_SET, + VERIFY_NONE, + VERIFY_PEER, + VERIFY_FAIL_IF_NO_PEER_CERT, +}; + +struct VerifyOptions { + // Constructed with default options + VerifyOptions(); + + // Set the maximum depth of the certificate chain for verification + // If 0, turn off the verification + // Default: 0 + int verify_depth; + + // Set ssl verify mode for openssl + // If VERIFY_FAIL_IF_NO_PEER_CERT, it will set `SSL_VERIFY_FAIL_IF_NO_PEER_CERT | SSL_VERIFY_PEER` + // Default: NOT_SET + VerifyMode verify_mode; + + // Set the trusted CA file to verify the peer's certificate + // If empty, use the system default CA files + // Default: "" + std::string ca_file_path; +}; + +// SSL options at client side +struct ChannelSSLOptions { + // Constructed with default options + ChannelSSLOptions(); + + // Cipher suites used for SSL handshake. + // The format of this string should follow that in `man 1 ciphers'. + // Default: "DEFAULT" + std::string ciphers; + + // SSL protocols used for SSL handshake, separated by comma. + // Available protocols: SSLv3, TLSv1, TLSv1.1, TLSv1.2, TLSv1.3 + // Default: TLSv1, TLSv1.1, TLSv1.2, TLSv1.3 + std::string protocols; + + // When set, fill this into the SNI extension field during handshake, + // which can be used by the server to locate the right certificate. + // Default: empty + std::string sni_name; + + // Certificate used for client authentication + // Default: empty + CertInfo client_cert; + + // Options used to verify the server's certificate + // Default: see above + VerifyOptions verify; + + // Set the protocol preference of ALPN (Application-Layer Protocol Negotiation) + // Default: unset + std::vector alpn_protocols; + + // TODO: Support CRL +}; + +// SSL options at server side +struct ServerSSLOptions { + // Constructed with default options + ServerSSLOptions(); + + // Default certificate which will be loaded into server. Requests + // without hostname or whose hostname doesn't have a corresponding + // certificate will use this certificate. MUST be set to enable SSL. + CertInfo default_cert; + + // Additional certificates which will be loaded into server. These + // provide extra bindings between hostnames and certificates so that + // we can choose different certificates according to different hostnames. + // See `CertInfo' for detail. + std::vector certs; + + // When set, requests without hostname or whose hostname can't be found in + // any of the cerficates above will be dropped. Otherwise, `default_cert' + // will be used. + // Default: false + bool strict_sni; + + // When set, SSLv3 requests will be dropped. Strongly recommended since + // SSLv3 has been found suffering from severe security problems. Note that + // some old versions of browsers may use SSLv3 by default such as IE6.0 + // Default: true + bool disable_ssl3; + + // Flag for SSL_MODE_RELEASE_BUFFERS. When set, release read/write buffers + // when SSL connection is idle, which saves 34KB memory per connection. + // On the other hand, it introduces additional latency and reduces throughput + // Default: false + bool release_buffer; + + // Maximum lifetime for a session to be cached inside OpenSSL in seconds. + // A session can be reused (initiated by client) to save handshake before + // it reaches this timeout. + // Default: 300 + int session_lifetime_s; + + // Maximum number of cached sessions. When cache is full, no more new + // session will be added into the cache until SSL_CTX_flush_sessions is + // called (automatically by SSL_read/write). A special value is 0, which + // means no limit. + // Default: 20480 + int session_cache_size; + + // Cipher suites allowed for each SSL handshake. The format of this string + // should follow that in `man 1 ciphers'. If empty, OpenSSL will choose + // a default cipher based on the certificate information + // Default: "" + std::string ciphers; + + // Name of the elliptic curve used to generate ECDH ephemeral keys + // Default: prime256v1 + std::string ecdhe_curve_name; + + // Options used to verify the client's certificate + // Default: see above + VerifyOptions verify; + + // Options used to choose the most suitable application protocol, separated by comma. + // The NPN protocol is not commonly used, so only ALPN is supported. + // Available protocols: http, h2, baidu_std etc. + // Default: empty + std::string alpns; + + // TODO: Support OSCP stapling +}; + +// Legacy name defined in server.h +typedef ServerSSLOptions SSLOptions; + +} // namespace brpc + +#endif // BRPC_SSL_OPTION_H diff --git a/src/brpc/stream.cpp b/src/brpc/stream.cpp new file mode 100644 index 0000000..01504d3 --- /dev/null +++ b/src/brpc/stream.cpp @@ -0,0 +1,950 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/stream.h" + +#include +#include "butil/time.h" +#include "butil/object_pool.h" +#include "butil/unique_ptr.h" +#include "bthread/unstable.h" +#include "brpc/log.h" +#include "brpc/socket.h" +#include "brpc/controller.h" +#include "brpc/input_messenger.h" +#include "brpc/policy/streaming_rpc_protocol.h" +#include "brpc/policy/baidu_rpc_protocol.h" +#include "brpc/stream_impl.h" + + +namespace brpc { + +DECLARE_bool(usercode_in_pthread); +DECLARE_int64(socket_max_streams_unconsumed_bytes); +DEFINE_uint64(stream_write_max_segment_size, 512 * 1024 * 1024, + "Stream message exceeding this size will be automatically split into smaller segments"); +BRPC_VALIDATE_GFLAG(stream_write_max_segment_size, PositiveInteger); + +const static butil::IOBuf *TIMEOUT_TASK = (butil::IOBuf*)-1L; + +Stream::Stream() + : _host_socket(NULL) + , _fake_socket_weak_ref(NULL) + , _connected(false) + , _closed(false) + , _error_code(0) + , _produced(0) + , _remote_consumed(0) + , _cur_buf_size(0) + , _local_consumed(0) + , _atomic_local_consumed(0) + , _parse_rpc_response(false) + , _pending_buf(NULL) + , _start_idle_timer_us(0) + , _idle_timer(0) +{ + _connect_meta.on_connect = NULL; + CHECK_EQ(0, bthread_mutex_init(&_connect_mutex, NULL)); + CHECK_EQ(0, bthread_mutex_init(&_congestion_control_mutex, NULL)); +} + +Stream::~Stream() { + // Clear pending buffer + if (_pending_buf != NULL) { + delete _pending_buf; + _pending_buf = NULL; + } + CHECK(_host_socket == NULL); + bthread_mutex_destroy(&_connect_mutex); + bthread_mutex_destroy(&_congestion_control_mutex); + bthread_id_list_destroy(&_writable_wait_list); +} + +int Stream::Create(const StreamOptions &options, + const StreamSettings *remote_settings, + StreamId *id, bool parse_rpc_response) { + Stream* s = new Stream(); + s->_host_socket = NULL; + s->_fake_socket_weak_ref = NULL; + s->_connected = false; + s->_options = options; + s->_closed = false; + s->_error_code = 0; + s->_cur_buf_size = options.max_buf_size > 0 ? options.max_buf_size : 0; + if (options.max_buf_size > 0 && options.min_buf_size > options.max_buf_size) { + // set 0 if min_buf_size is invalid. + s->_options.min_buf_size = 0; + LOG(WARNING) << "options.min_buf_size is larger than options.max_buf_size, it will be set to 0."; + } + if (FLAGS_socket_max_streams_unconsumed_bytes > 0 && s->_options.min_buf_size > 0) { + s->_cur_buf_size = s->_options.min_buf_size; + } + + if (remote_settings != NULL) { + s->_remote_settings.MergeFrom(*remote_settings); + } + s->_parse_rpc_response = parse_rpc_response; + if (bthread_id_list_init(&s->_writable_wait_list, 8, 8/*FIXME*/)) { + delete s; + return -1; + } + bthread::ExecutionQueueOptions q_opt; + q_opt.bthread_attr + = FLAGS_usercode_in_pthread ? BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL; + if (bthread::execution_queue_start(&s->_consumer_queue, &q_opt, Consume, s) != 0) { + LOG(FATAL) << "Fail to create ExecutionQueue"; + delete s; + return -1; + } + SocketOptions sock_opt; + sock_opt.conn = s; + SocketId fake_sock_id; + if (Socket::Create(sock_opt, &fake_sock_id) != 0) { + s->BeforeRecycle(NULL); + return -1; + } + SocketUniquePtr ptr; + CHECK_EQ(0, Socket::Address(fake_sock_id, &ptr)); + s->_fake_socket_weak_ref = ptr.get(); + s->_id = fake_sock_id; + *id = s->id(); + return 0; +} + +void Stream::BeforeRecycle(Socket *) { + // No one holds reference now, so we don't need lock here + bthread_id_list_reset(&_writable_wait_list, ECONNRESET); + if (_connected) { + // Send CLOSE frame + RPC_VLOG << "Send close frame"; + CHECK(_host_socket != NULL); + policy::SendStreamClose(_host_socket, + _remote_settings.stream_id(), id()); + } + + if (_host_socket) { + _host_socket->RemoveStream(id()); + } + + // The instance is to be deleted in the consumer thread + bthread::execution_queue_stop(_consumer_queue); +} + +ssize_t Stream::CutMessageIntoFileDescriptor(int /*fd*/, + butil::IOBuf **data_list, + size_t size) { + if (_host_socket == NULL) { + CHECK(false) << "Not connected"; + errno = EBADF; + return -1; + } + if (!_remote_settings.writable()) { + LOG(WARNING) << "The remote side of Stream=" << id() + << "->" << _remote_settings.stream_id() + << "@" << _host_socket->remote_side() + << " doesn't have a handler"; + errno = EBADF; + return -1; + } + butil::IOBuf out; + ssize_t len = 0; + ssize_t unwritten_data_size = 0; + for (size_t i = 0; i < size; ++i) { + butil::IOBuf *data = data_list[i]; + size_t length = data->length(); + if (length > FLAGS_stream_write_max_segment_size) { + if (unwritten_data_size) { + WriteToHostSocket(&out); + unwritten_data_size = 0; + out.clear(); + } + // segmenting large data into multiple parts + butil::IOBuf segment_buf; + bool has_continuation = true; + while (has_continuation) { + data->cutn(&segment_buf, FLAGS_stream_write_max_segment_size); + StreamFrameMeta fm; + fm.set_stream_id(_remote_settings.stream_id()); + fm.set_source_stream_id(id()); + fm.set_frame_type(FRAME_TYPE_DATA); + has_continuation = !data->empty(); + fm.set_has_continuation(has_continuation); + policy::PackStreamMessage(&out, fm, &segment_buf); + len += segment_buf.length(); + segment_buf.clear(); + WriteToHostSocket(&out); + out.clear(); + } + } else { + if (unwritten_data_size + length > FLAGS_stream_write_max_segment_size) { + WriteToHostSocket(&out); + unwritten_data_size = 0; + out.clear(); + } + unwritten_data_size += length; + StreamFrameMeta fm; + fm.set_stream_id(_remote_settings.stream_id()); + fm.set_source_stream_id(id()); + fm.set_frame_type(FRAME_TYPE_DATA); + fm.set_has_continuation(false); + policy::PackStreamMessage(&out, fm, data_list[i]); + len += length; + data_list[i]->clear(); + } + } + + if (!out.empty()) { + WriteToHostSocket(&out); + } + return len; +} + +void Stream::WriteToHostSocket(butil::IOBuf* b) { + BRPC_HANDLE_EOVERCROWDED(_host_socket->Write(b)); +} + +ssize_t Stream::CutMessageIntoSSLChannel(SSL*, butil::IOBuf**, size_t) { + CHECK(false) << "Stream does support SSL"; + errno = EINVAL; + return -1; +} + +void* Stream::RunOnConnect(void *arg) { + ConnectMeta* meta = (ConnectMeta*)arg; + if (meta->ec == 0) { + meta->on_connect(Socket::STREAM_FAKE_FD, 0, meta->arg); + } else { + meta->on_connect(-1, meta->ec, meta->arg); + } + delete meta; + return NULL; +} + +int Stream::Connect(Socket* ptr, const timespec*, + int (*on_connect)(int, int, void *), void *data) { + CHECK_EQ(ptr->id(), _id); + bthread_mutex_lock(&_connect_mutex); + if (_connect_meta.on_connect != NULL) { + CHECK(false) << "Connect is supposed to be called once"; + bthread_mutex_unlock(&_connect_mutex); + return -1; + } + _connect_meta.on_connect = on_connect; + _connect_meta.arg = data; + if (_connected) { + ConnectMeta* meta = new ConnectMeta; + meta->on_connect = _connect_meta.on_connect; + meta->arg = _connect_meta.arg; + meta->ec = _connect_meta.ec; + bthread_mutex_unlock(&_connect_mutex); + bthread_t tid; + if (bthread_start_urgent(&tid, &BTHREAD_ATTR_NORMAL, RunOnConnect, meta) != 0) { + LOG(FATAL) << "Fail to start bthread, " << berror(); + RunOnConnect(meta); + } + return 0; + } + bthread_mutex_unlock(&_connect_mutex); + return 0; +} + +void Stream::SetConnected() { + return SetConnected(NULL); +} + +void Stream::SetConnected(const StreamSettings* remote_settings) { + bthread_mutex_lock(&_connect_mutex); + if (_closed) { + bthread_mutex_unlock(&_connect_mutex); + return; + } + if (_connected) { + CHECK(false); + bthread_mutex_unlock(&_connect_mutex); + return; + } + CHECK(_host_socket != NULL); + if (remote_settings != NULL) { + CHECK(!_remote_settings.IsInitialized()); + _remote_settings.MergeFrom(*remote_settings); + } else { + CHECK(_remote_settings.IsInitialized()); + } + CHECK(_host_socket != NULL); + RPC_VLOG << "stream=" << id() << " is connected to stream_id=" + << _remote_settings.stream_id() << " at host_socket=" << *_host_socket; + _connected.store(true, butil::memory_order_release); + _connect_meta.ec = 0; + TriggerOnConnectIfNeed(); + if (remote_settings == NULL) { + // Start the timer at server-side + // Client-side timer would triggered in Consume after received the first + // message which is the very RPC response + StartIdleTimer(); + } else { + // send first feedback for client-side stream if it already consumed data + if (_remote_settings.need_feedback()) { + auto consumed_bytes = _atomic_local_consumed.load(butil::memory_order_acquire); + if (consumed_bytes > 0) + SendFeedback(consumed_bytes); + } + } +} + +void Stream::TriggerOnConnectIfNeed() { + if (_connect_meta.on_connect != NULL) { + ConnectMeta* meta = new ConnectMeta; + meta->on_connect = _connect_meta.on_connect; + meta->arg = _connect_meta.arg; + meta->ec = _connect_meta.ec; + bthread_mutex_unlock(&_connect_mutex); + bthread_t tid; + if (bthread_start_urgent(&tid, &BTHREAD_ATTR_NORMAL, RunOnConnect, meta) != 0) { + LOG(FATAL) << "Fail to start bthread, " << berror(); + RunOnConnect(meta); + } + return; + } + bthread_mutex_unlock(&_connect_mutex); +} + +int Stream::AppendIfNotFull(const butil::IOBuf &data, + const StreamWriteOptions* options) { + if (_cur_buf_size > 0) { + std::unique_lock lck(_congestion_control_mutex); + if (_produced >= _remote_consumed + _cur_buf_size) { + const size_t saved_produced = _produced; + const size_t saved_remote_consumed = _remote_consumed; + lck.unlock(); + RPC_VLOG << "Stream=" << _id << " is full" + << "_produced=" << saved_produced + << " _remote_consumed=" << saved_remote_consumed + << " gap=" << saved_produced - saved_remote_consumed + << " max_buf_size=" << _cur_buf_size; + return 1; + } + _produced += data.length(); + } + + size_t data_length = data.length(); + butil::IOBuf copied_data(data); + Socket::WriteOptions wopt; + wopt.write_in_background = options != NULL && options->write_in_background; + const int rc = _fake_socket_weak_ref->Write(&copied_data, &wopt); + if (rc != 0) { + // Stream may be closed by peer before + LOG(WARNING) << "Fail to write to _fake_socket, " << berror(); + BAIDU_SCOPED_LOCK(_congestion_control_mutex); + _produced -= data_length; + return -1; + } + if (FLAGS_socket_max_streams_unconsumed_bytes > 0) { + _host_socket->_total_streams_unconsumed_size += data_length; + } + return 0; +} + +void Stream::SetRemoteConsumed(size_t new_remote_consumed) { + CHECK(_cur_buf_size > 0); + bthread_id_list_t tmplist; + bthread_id_list_init(&tmplist, 0, 0); + bthread_mutex_lock(&_congestion_control_mutex); + if (_remote_consumed >= new_remote_consumed) { + bthread_mutex_unlock(&_congestion_control_mutex); + return; + } + const bool was_full = _produced >= _remote_consumed + _cur_buf_size; + + if (FLAGS_socket_max_streams_unconsumed_bytes > 0) { + _host_socket->_total_streams_unconsumed_size -= new_remote_consumed - _remote_consumed; + if (_host_socket->_total_streams_unconsumed_size > FLAGS_socket_max_streams_unconsumed_bytes) { + if (_options.min_buf_size > 0) { + _cur_buf_size = _options.min_buf_size; + } else { + _cur_buf_size /= 2; + } + LOG(INFO) << "stream consumers on socket " << _host_socket->id() << " is crowded, " << "cut stream " << id() << " buffer to " << _cur_buf_size; + } else if (_produced >= new_remote_consumed + _cur_buf_size && (_options.max_buf_size <= 0 || _cur_buf_size < (size_t)_options.max_buf_size)) { + if (_options.max_buf_size > 0 && _cur_buf_size * 2 > (size_t)_options.max_buf_size) { + _cur_buf_size = _options.max_buf_size; + } else { + _cur_buf_size *= 2; + } + } + } + + _remote_consumed = new_remote_consumed; + const bool is_full = _produced >= _remote_consumed + _cur_buf_size; + if (was_full && !is_full) { + bthread_id_list_swap(&tmplist, &_writable_wait_list); + } + bthread_mutex_unlock(&_congestion_control_mutex); + + // broadcast + bthread_id_list_reset(&tmplist, 0); + bthread_id_list_destroy(&tmplist); +} + +void* Stream::RunOnWritable(void* arg) { + WritableMeta *wm = (WritableMeta*)arg; + wm->on_writable(wm->id, wm->arg, wm->error_code); + delete wm; + return NULL; +} + +int Stream::TriggerOnWritable(bthread_id_t id, void *data, int error_code) { + WritableMeta *wm = (WritableMeta*)data; + + if (wm->has_timer) { + bthread_timer_del(wm->timer); + } + wm->error_code = error_code; + if (wm->new_thread) { + const bthread_attr_t* attr = + FLAGS_usercode_in_pthread ? &BTHREAD_ATTR_PTHREAD + : &BTHREAD_ATTR_NORMAL; + bthread_t tid; + if (bthread_start_background(&tid, attr, RunOnWritable, wm) != 0) { + LOG(FATAL) << "Fail to start bthread" << berror(); + RunOnWritable(wm); + } + } else { + RunOnWritable(wm); + } + return bthread_id_unlock_and_destroy(id); +} + +void OnTimedOut(void *arg) { + bthread_id_t id = { reinterpret_cast(arg) }; + bthread_id_error(id, ETIMEDOUT); +} + +void Stream::Wait(void (*on_writable)(StreamId, void*, int), void* arg, + const timespec* due_time, bool new_thread, bthread_id_t *join_id) { + WritableMeta *wm = new WritableMeta; + wm->on_writable = on_writable; + wm->id = id(); + wm->arg = arg; + wm->new_thread = new_thread; + wm->has_timer = false; + bthread_id_t wait_id; + const int rc = bthread_id_create(&wait_id, wm, TriggerOnWritable); + if (rc != 0) { + CHECK(false) << "Fail to create bthread_id, " << berror(rc); + wm->error_code = rc; + RunOnWritable(wm); + return; + } + if (join_id) { + *join_id = wait_id; + } + CHECK_EQ(0, bthread_id_lock(wait_id, NULL)); + if (due_time != NULL) { + wm->has_timer = true; + const int rc = bthread_timer_add(&wm->timer, *due_time, + OnTimedOut, + reinterpret_cast(wait_id.value)); + if (rc != 0) { + LOG(ERROR) << "Fail to add timer, " << berror(rc); + CHECK_EQ(0, TriggerOnWritable(wait_id, wm, rc)); + } + } + bthread_mutex_lock(&_congestion_control_mutex); + if (_cur_buf_size <= 0 + || _produced < _remote_consumed + _cur_buf_size) { + bthread_mutex_unlock(&_congestion_control_mutex); + CHECK_EQ(0, TriggerOnWritable(wait_id, wm, 0)); + return; + } else { + bthread_id_list_add(&_writable_wait_list, wait_id); + bthread_mutex_unlock(&_congestion_control_mutex); + } + CHECK_EQ(0, bthread_id_unlock(wait_id)); +} + +void Stream::Wait(void (*on_writable)(StreamId, void *, int), void *arg, + const timespec* due_time) { + return Wait(on_writable, arg, due_time, true, NULL); +} + +void OnWritable(StreamId, void *arg, int error_code) { + *(int*)arg = error_code; +} + +int Stream::Wait(const timespec* due_time) { + int rc; + bthread_id_t join_id = INVALID_BTHREAD_ID; + Wait(OnWritable, &rc, due_time, false, &join_id); + if (join_id != INVALID_BTHREAD_ID) { + bthread_id_join(join_id); + } + return rc; +} + +int Stream::OnReceived(const StreamFrameMeta& fm, butil::IOBuf *buf, Socket* sock) { + if (_host_socket == NULL) { + if (SetHostSocket(sock) != 0) { + return -1; + } + } + switch (fm.frame_type()) { + case FRAME_TYPE_FEEDBACK: + SetRemoteConsumed(fm.feedback().consumed_size()); + CHECK(buf->empty()); + break; + case FRAME_TYPE_DATA: + if (_pending_buf != NULL) { + _pending_buf->append(*buf); + buf->clear(); + } else { + _pending_buf = new butil::IOBuf; + _pending_buf->swap(*buf); + } + if (!fm.has_continuation()) { + butil::IOBuf *tmp = _pending_buf; + _pending_buf = NULL; + int rc = bthread::execution_queue_execute(_consumer_queue, tmp); + if (rc != 0) { + CHECK(false) << "Fail to push into channel"; + delete tmp; + Close(rc, "Fail to push into channel"); + } + } + break; + case FRAME_TYPE_RST: + RPC_VLOG << "stream=" << id() << " received rst frame"; + Close(ECONNRESET, "Received RST frame"); + break; + case FRAME_TYPE_CLOSE: + RPC_VLOG << "stream=" << id() << " received close frame"; + // TODO:: See the comments in Consume + Close(0, "Received CLOSE frame"); + break; + case FRAME_TYPE_UNKNOWN: + RPC_VLOG << "Received unknown frame"; + return -1; + } + return 0; +} + +class MessageBatcher { +public: + MessageBatcher(butil::IOBuf* storage[], size_t cap, Stream* s) + : _storage(storage) + , _cap(cap) + , _size(0) + , _total_length(0) + , _s(s) + {} + ~MessageBatcher() { flush(); } + void flush() { + if (_size > 0 && _s->_options.handler != NULL) { + _s->_options.handler->on_received_messages( + _s->id(), _storage, _size); + } + for (size_t i = 0; i < _size; ++i) { + delete _storage[i]; + } + _size = 0; + } + void push(butil::IOBuf* buf) { + if (_size == _cap) { + flush(); + } + _storage[_size++] = buf; + _total_length += buf->length(); + + } + size_t total_length() const { return _total_length; } +private: + butil::IOBuf** _storage; + size_t _cap; + size_t _size; + size_t _total_length; + Stream* _s; +}; + +int Stream::Consume(void *meta, bthread::TaskIterator& iter) { + Stream* s = (Stream*)meta; + s->StopIdleTimer(); + if (iter.is_queue_stopped()) { + scoped_ptr recycled_stream(s); + // Indicating the queue was closed. + if (s->_host_socket) { + DereferenceSocket(s->_host_socket); + s->_host_socket = NULL; + } + if (s->_options.handler != NULL) { + int error_code; + std::string error_text; + { + BAIDU_SCOPED_LOCK(s->_connect_mutex); + error_code = s->_error_code; + error_text = s->_error_text; + } + if (error_code != 0) { + // The stream is closed abnormally. + s->_options.handler->on_failed(s->id(), error_code, error_text); + } + s->_options.handler->on_closed(s->id()); + } + return 0; + } + DEFINE_SMALL_ARRAY(butil::IOBuf*, buf_list, s->_options.messages_in_batch, 256); + MessageBatcher mb(buf_list, s->_options.messages_in_batch, s); + bool has_timeout_task = false; + for (; iter; ++iter) { + butil::IOBuf* t= *iter; + if (t == TIMEOUT_TASK) { + has_timeout_task = true; + } else { + if (s->_parse_rpc_response) { + s->_parse_rpc_response = false; + s->HandleRpcResponse(t); + } else { + mb.push(t); + } + } + } + if (s->_options.handler != NULL) { + if (has_timeout_task && mb.total_length() == 0) { + s->_options.handler->on_idle_timeout(s->id()); + } + } + mb.flush(); + + auto total_length = mb.total_length(); + if (total_length > 0) { + // fast path for connected stream + if (s->_connected.load(butil::memory_order_acquire)){ + if (s->_remote_settings.need_feedback()) { + s->_local_consumed += total_length; + s->SendFeedback(s->_local_consumed); + } + } else { + // Under the scenario of batch creation of Streams, there is concurrency between SetConnected and Consume for the same stream, + // and it is necessary to ensure the memory order. + s->_local_consumed = s->_atomic_local_consumed.fetch_add(total_length, butil::memory_order_release) + total_length; + if (s->_connected.load(butil::memory_order_acquire) && s->_remote_settings.need_feedback()) { + s->SendFeedback(s->_local_consumed); + } + } + } + + s->StartIdleTimer(); + return 0; +} + +void Stream::SendFeedback(int64_t _consumed_bytes) { + StreamFrameMeta fm; + fm.set_frame_type(FRAME_TYPE_FEEDBACK); + fm.set_stream_id(_remote_settings.stream_id()); + fm.set_source_stream_id(id()); + fm.mutable_feedback()->set_consumed_size(_consumed_bytes); + butil::IOBuf out; + policy::PackStreamMessage(&out, fm, NULL); + WriteToHostSocket(&out); +} + +int Stream::SetHostSocket(Socket *host_socket) { + std::call_once(_set_host_socket_flag, [this, host_socket]() { + SocketUniquePtr ptr; + host_socket->ReAddress(&ptr); + // TODO add *this to host socke + if (ptr->AddStream(id()) != 0) { + CHECK(false) << id() << " fail to add stream to host socket"; + return; + } + _host_socket = ptr.release(); + }); + return 0; +} + +void Stream::FillSettings(StreamSettings *settings) { + settings->set_stream_id(id()); + settings->set_need_feedback(_cur_buf_size > 0); + settings->set_writable(_options.handler != NULL); +} + +void OnIdleTimeout(void *arg) { + bthread::ExecutionQueueId q = { (uint64_t)arg }; + bthread::execution_queue_execute(q, (butil::IOBuf*)TIMEOUT_TASK); +} + +void Stream::StartIdleTimer() { + if (_options.idle_timeout_ms < 0) { + return; + } + _start_idle_timer_us = butil::gettimeofday_us(); + timespec due_time = butil::microseconds_to_timespec( + _start_idle_timer_us + _options.idle_timeout_ms * 1000); + const int rc = bthread_timer_add(&_idle_timer, due_time, OnIdleTimeout, + (void*)(_consumer_queue.value)); + LOG_IF(WARNING, rc != 0) << "Fail to add timer"; +} + +void Stream::StopIdleTimer() { + if (_options.idle_timeout_ms < 0) { + return; + } + if (_idle_timer != 0) { + bthread_timer_del(_idle_timer); + } +} + +void Stream::Close(int error_code, const char* reason_fmt, ...) { + _fake_socket_weak_ref->SetFailed(); + bthread_mutex_lock(&_connect_mutex); + if (_closed) { + bthread_mutex_unlock(&_connect_mutex); + return; + } + _closed = true; + _error_code = error_code; + + va_list ap; + va_start(ap, reason_fmt); + butil::string_vappendf(&_error_text, reason_fmt, ap); + va_end(ap); + + if (_connected) { + bthread_mutex_unlock(&_connect_mutex); + return; + } + _connect_meta.ec = ECONNRESET; + // Trigger on connect to release the reference of socket + return TriggerOnConnectIfNeed(); +} + +int Stream::SetFailed(StreamId id, int error_code, const char* reason_fmt, ...) { + SocketUniquePtr ptr; + if (Socket::AddressFailedAsWell(id, &ptr) == -1) { + // Don't care recycled stream + return 0; + } + Stream* s = (Stream*)ptr->conn(); + va_list ap; + va_start(ap, reason_fmt); + s->Close(error_code, reason_fmt, ap); + va_end(ap); + return 0; +} + +int Stream::SetFailed(const StreamIds& ids, int error_code, const char* reason_fmt, ...) { + va_list ap; + va_start(ap, reason_fmt); + for(size_t i = 0; i< ids.size(); ++i) { + Stream::SetFailed(ids[i], error_code, reason_fmt, ap); + } + va_end(ap); + return 0; +} + +void Stream::HandleRpcResponse(butil::IOBuf* response_buffer) { + CHECK(!_remote_settings.IsInitialized()); + CHECK(_host_socket != NULL); + std::unique_ptr buf_guard(response_buffer); + ParseResult pr = policy::ParseRpcMessage(response_buffer, NULL, true, NULL); + if (!pr.is_ok()) { + CHECK(false); + Close(EPROTO, "Fail to parse rpc response message"); + return; + } + InputMessageBase* msg = pr.message(); + if (msg == NULL) { + CHECK(false); + Close(ENOMEM, "Message is NULL"); + return; + } + _host_socket->PostponeEOF(); + _host_socket->ReAddress(&msg->_socket); + msg->_received_us = butil::gettimeofday_us(); + msg->_base_real_us = butil::gettimeofday_us(); + msg->_arg = NULL; // ProcessRpcResponse() don't need arg + policy::ProcessRpcResponse(msg); +} + +int StreamWrite(StreamId stream_id, const butil::IOBuf &message, + const StreamWriteOptions* options) { + SocketUniquePtr ptr; + if (Socket::Address(stream_id, &ptr) != 0) { + return EINVAL; + } + Stream* s = (Stream*)ptr->conn(); + const int rc = s->AppendIfNotFull(message, options); + if (rc == 0) { + return 0; + } + return (rc == 1) ? EAGAIN : errno; +} + +void StreamWait(StreamId stream_id, const timespec *due_time, + void (*on_writable)(StreamId, void*, int), void *arg) { + SocketUniquePtr ptr; + if (Socket::Address(stream_id, &ptr) != 0) { + Stream::WritableMeta* wm = new Stream::WritableMeta; + wm->id = stream_id; + wm->arg= arg; + wm->has_timer = false; + wm->on_writable = on_writable; + wm->error_code = EINVAL; + const bthread_attr_t* attr = + FLAGS_usercode_in_pthread ? &BTHREAD_ATTR_PTHREAD + : &BTHREAD_ATTR_NORMAL; + bthread_t tid; + if (bthread_start_background(&tid, attr, Stream::RunOnWritable, wm) != 0) { + PLOG(FATAL) << "Fail to start bthread"; + Stream::RunOnWritable(wm); + } + return; + } + Stream* s = (Stream*)ptr->conn(); + return s->Wait(on_writable, arg, due_time); +} + +int StreamWait(StreamId stream_id, const timespec* due_time) { + SocketUniquePtr ptr; + if (Socket::Address(stream_id, &ptr) != 0) { + return EINVAL; + } + Stream* s = (Stream*)ptr->conn(); + return s->Wait(due_time); +} + +int StreamClose(StreamId stream_id) { + return Stream::SetFailed(stream_id, 0, "Local close"); +} + +int StreamCreate(StreamId *request_stream, Controller &cntl, + const StreamOptions* options) { + if (request_stream == NULL) { + LOG(ERROR) << "request_stream is NULL"; + return -1; + } + StreamIds request_streams; + const int rc = StreamCreate(request_streams, 1, cntl, options); + if (rc != 0) { + return rc; + } + *request_stream = request_streams[0]; + return 0; +} + +int StreamCreate(StreamIds& request_streams, int request_stream_size, Controller & cntl, + const StreamOptions* options) { + if (!cntl._request_streams.empty()) { + LOG(ERROR) << "Can't create request stream more than once"; + return -1; + } + if (!request_streams.empty()) { + LOG(ERROR) << "request_streams should be empty"; + return -1; + } + StreamOptions opt; + if (options != NULL) { + opt = *options; + } + for (auto i = 0; i < request_stream_size; ++i) { + StreamId stream_id; + bool parse_rpc_response = (i == 0); // Only the first stream need parse rpc + if (Stream::Create(opt, NULL, &stream_id, parse_rpc_response) != 0) { + // Close already created streams + Stream::SetFailed(request_streams, 0 , "Fail to create stream at %d index", i); + LOG(ERROR) << "Fail to create stream"; + return -1; + } + cntl._request_streams.push_back(stream_id); + request_streams.push_back(stream_id); + } + return 0; +} + +int StreamAccept(StreamId* response_stream, Controller &cntl, + const StreamOptions* options) { + if (response_stream == NULL) { + LOG(ERROR) << "response_stream is NULL"; + return -1; + } + StreamIds response_streams; + int res = StreamAccept(response_streams, cntl, options); + if(res != 0) { + return res; + } + if(response_streams.size() != 1) { + Stream::SetFailed(response_streams, EINVAL, + "misusing StreamAccept for single stream to accept multiple streams"); + cntl._response_streams.clear(); + LOG(ERROR) << "misusing StreamAccept for single stream to accept multiple streams"; + return -1; + } + *response_stream = response_streams[0]; + return 0; +} + +int StreamAccept(StreamIds& response_streams, Controller& cntl, + const StreamOptions* options) { + if (!cntl._response_streams.empty()) { + LOG(ERROR) << "Can't create response stream more than once"; + return -1; + } + + if (!response_streams.empty()) { + LOG(ERROR) << "response_streams should be empty"; + return -1; + } + if (!cntl.has_remote_stream()) { + LOG(ERROR) << "No stream along with this request"; + return -1; + } + StreamOptions opt; + if (options != NULL) { + opt = *options; + } + StreamId stream_id; + if (Stream::Create(opt, cntl._remote_stream_settings, &stream_id, false) != 0) { + Stream::SetFailed(response_streams, 0, "Fail to accept stream"); + LOG(ERROR) << "Fail to accept stream"; + return -1; + } + + cntl._response_streams.push_back(stream_id); + response_streams.push_back(stream_id); + if(!cntl._remote_stream_settings->extra_stream_ids().empty()) { + StreamSettings stream_remote_settings; + stream_remote_settings.MergeFrom(*cntl._remote_stream_settings); + //Only the first stream needs extra_stream_ids settings + stream_remote_settings.clear_extra_stream_ids(); + for (auto i = 0; i < cntl._remote_stream_settings->extra_stream_ids_size(); ++i) { + stream_remote_settings.set_stream_id(cntl._remote_stream_settings->extra_stream_ids()[i]); + StreamId extra_stream_id; + if (Stream::Create(opt, &stream_remote_settings, &extra_stream_id, false) != 0) { + Stream::SetFailed(response_streams, 0, "Fail to accept stream at %d index", i); + cntl._response_streams.clear(); + response_streams.clear(); + LOG(ERROR) << "Fail to accept stream"; + return -1; + } + cntl._response_streams.push_back(extra_stream_id); + response_streams.push_back(extra_stream_id); + } + } + + return 0; +} + +} // namespace brpc diff --git a/src/brpc/stream.h b/src/brpc/stream.h new file mode 100644 index 0000000..36c0def --- /dev/null +++ b/src/brpc/stream.h @@ -0,0 +1,180 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_STREAM_H +#define BRPC_STREAM_H + +#include "butil/iobuf.h" +#include "butil/scoped_generic.h" +#include "brpc/socket_id.h" + +namespace brpc { + +class Controller; + +typedef SocketId StreamId; +using StreamIds = std::vector; +const StreamId INVALID_STREAM_ID = (StreamId)-1L; + +namespace detail { +struct StreamIdTraits; +}; + +// Auto-closed Stream +typedef butil::ScopedGeneric ScopedStream; + +class StreamInputHandler { +public: + virtual ~StreamInputHandler() = default; + virtual int on_received_messages(StreamId id, + butil::IOBuf *const messages[], + size_t size) = 0; + virtual void on_idle_timeout(StreamId id) = 0; + virtual void on_closed(StreamId id) = 0; + // `on_failed` will be called before `on_closed` + // when the stream is closed abnormally. + virtual void on_failed(StreamId id, int error_code, + const std::string& error_text) {} +}; + +struct StreamOptions { + StreamOptions() + : min_buf_size(1024 * 1024) + , max_buf_size(2 * 1024 * 1024) + , idle_timeout_ms(-1) + , messages_in_batch(128) + , handler(NULL) + {} + + // stream max buffer size limit in [min_buf_size, max_buf_size] + // If |min_buf_size| <= 0, there's no min size limit of buf size + // default: 1048576 (1M) + int min_buf_size; + + // The max size of unconsumed data allowed at remote side. + // If |max_buf_size| <= 0, there's no limit of buf size + // default: 2097152 (2M) + int max_buf_size; + + // Notify user when there's no data for at least |idle_timeout_ms| + // milliseconds since the last time that HandleIdleTimeout or HandleInput + // finished. + // default: -1 + long idle_timeout_ms; + + // Maximum messages in batch passed to handler->on_received_messages + // default: 128 + size_t messages_in_batch; + + // Handle input message, if handler is NULL, the remote side is not allowed to + // write any message, who will get EBADF on writting + // default: NULL + StreamInputHandler* handler; +}; + +struct StreamWriteOptions { + StreamWriteOptions() : write_in_background(false) {} + + // Write message to socket in background thread. + // Provides batch write effect and better performance in situations when + // you are continually issuing lots of StreamWrite or async RPC calls in + // only one thread. Otherwise, each StreamWrite directly writes message into + // socket and brings poor performance. + bool write_in_background; +}; + +// [Called at the client side] +// Create a stream at client-side along with the |cntl|, which will be connected +// when receiving the response with a stream from server-side. If |options| is +// NULL, the stream will be created with default options +// Return 0 on success, -1 otherwise +int StreamCreate(StreamId* request_stream, Controller &cntl, + const StreamOptions* options); + +// [Called at the client side for creating multiple streams] +// Create streams at client-side along with the |cntl|, which will be connected +// when receiving the response with streams from server-side. If |options| is +// NULL, the stream will be created with default options +// Return 0 on success, -1 otherwise +int StreamCreate(StreamIds& request_streams, int request_stream_size, Controller& cntl, + const StreamOptions* options); + +// [Called at the server side] +// Accept the stream. If client didn't create a stream with the request +// (cntl.has_remote_stream() returns false), this method would fail. +// Return 0 on success, -1 otherwise. +int StreamAccept(StreamId* response_stream, Controller &cntl, + const StreamOptions* options); + +// [Called at the server side for accepting multiple streams] +// Accept the streams. If client didn't create streams with the request +// (cntl.has_remote_stream() returns false), this method would fail. +// Return 0 on success, -1 otherwise. +int StreamAccept(StreamIds& response_stream, Controller& cntl, + const StreamOptions* options); +// Write |message| into |stream_id|. The remote-side handler will received the +// message by the written order +// Returns 0 on success, errno otherwise +// Errno: +// - EAGAIN: |stream_id| is created with positive |max_buf_size| and buf size +// which the remote side hasn't consumed yet excceeds the number. +// - EINVAL: |stream_id| is invalied or has been closed +int StreamWrite(StreamId stream_id, const butil::IOBuf &message, + const StreamWriteOptions* options = NULL); + +// Write util the pending buffer size is less than |max_buf_size| or orrur +// occurs +// Returns 0 on success, errno otherwise +// Errno: +// - ETIMEDOUT: when |due_time| is not NULL and time expired this +// - EINVAL: the stream was close during waiting +int StreamWait(StreamId stream_id, const timespec* due_time); + +// Async wait +void StreamWait(StreamId stream_id, const timespec *due_time, + void (*on_writable)(StreamId stream_id, void* arg, + int error_code), + void *arg); + +// Close |stream_id|, after this function is called: +// - All the following |StreamWrite| would fail +// - |StreamWait| wakes up immediately. +// - Both sides |on_closed| would be notifed after all the pending buffers have +// been received +// This function could be called multiple times without side-effects +int StreamClose(StreamId stream_id); + +namespace detail { + +struct StreamIdTraits { + inline static StreamId InvalidValue() { + return INVALID_STREAM_ID; + }; + static void Free(StreamId f) { + if (f != INVALID_STREAM_ID) { + StreamClose(f); + } + } +}; + +} // namespace detail + +} // namespace brpc + + +#endif //BRPC_STREAM_H diff --git a/src/brpc/stream_creator.h b/src/brpc/stream_creator.h new file mode 100644 index 0000000..1bc2f07 --- /dev/null +++ b/src/brpc/stream_creator.h @@ -0,0 +1,83 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_STREAM_CREATOR_H +#define BRPC_STREAM_CREATOR_H + +#include "brpc/socket_id.h" + +namespace brpc { +class Controller; +class StreamUserData; + +// Abstract creation of "user-level connection" over a RPC-like process. +// Lifetime of this object should be guaranteed by user during the RPC, +// generally this object is created before RPC and destroyed after RPC. +class StreamCreator { +public: + virtual ~StreamCreator() = default; + + // Called when the socket for sending request is about to be created. + // If the RPC has retries, this function MAY be called before each retry. + // This function would not be called if some preconditions are not + // satisfied. + // Params: + // inout: pointing to the socket to send requests by default, + // replaceable by user created ones (or keep as it is). remote_side() + // of the replaced socket must be same with the default socket. + // The replaced socket should take cntl->connection_type() into account + // since the framework sends request by the replaced socket directly + // when stream_creator is present. + // cntl: contains contexts of the RPC, if there's any error during + // replacement, call cntl->SetFailed(). + virtual StreamUserData* OnCreatingStream(SocketUniquePtr* inout, + Controller* cntl) = 0; + + // Called when the StreamCreator is about to destroyed. + // This function MUST be called only once at the end of successful RPC + // Call to recycle resources. + // Params: + // cntl: contexts of the RPC + virtual void DestroyStreamCreator(Controller* cntl) = 0; +}; + +// The Intermediate user data created by StreamCreator to record the context +// of a specific stream request. +class StreamUserData { +public: + virtual ~StreamUserData() = default; + + // Called when the streamUserData is about to destroyed. + // This function MUST be called to clean up resources if OnCreatingStream + // of StreamCreator has returned a valid StreamUserData pointer. + // Params: + // sending_sock: The socket chosen by OnCreatingStream(), if an error + // happens during choosing, the enclosed socket is NULL. + // cntl: contexts of the RPC. + // error_code: Use this instead of cntl->ErrorCode(). + // end_of_rpc: true if the RPC is about to destroyed. + virtual void DestroyStreamUserData(SocketUniquePtr& sending_sock, + Controller* cntl, + int error_code, + bool end_of_rpc) = 0; +}; + +} // namespace brpc + + +#endif // BRPC_STREAM_CREATOR_H diff --git a/src/brpc/stream_impl.h b/src/brpc/stream_impl.h new file mode 100644 index 0000000..284b33c --- /dev/null +++ b/src/brpc/stream_impl.h @@ -0,0 +1,145 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_STREAM_IMPL_H +#define BRPC_STREAM_IMPL_H + +#include +#include "bthread/bthread.h" +#include "bthread/execution_queue.h" +#include "brpc/socket.h" +#include "brpc/stream.h" +#include "brpc/streaming_rpc_meta.pb.h" + +namespace brpc { + +class BAIDU_CACHELINE_ALIGNMENT Stream : public SocketConnection { +public: + // |--------------------------------------------------| + // |----------- Implement SocketConnection -----------| + // |--------------------------------------------------| + + int Connect(Socket* ptr, const timespec* due_time, + int (*on_connect)(int, int, void *), void *data); + ssize_t CutMessageIntoFileDescriptor(int, butil::IOBuf **data_list, + size_t size); + ssize_t CutMessageIntoSSLChannel(SSL*, butil::IOBuf**, size_t); + void BeforeRecycle(Socket *); + + // --------------------- SocketConnection -------------- + + int AppendIfNotFull(const butil::IOBuf& msg, + const StreamWriteOptions* options = NULL); + static int Create(const StreamOptions& options, + const StreamSettings *remote_settings, + StreamId *id, bool parse_rpc_response = true); + StreamId id() { return _id; } + + int OnReceived(const StreamFrameMeta& fm, butil::IOBuf *buf, Socket* sock); + void SetRemoteSettings(const StreamSettings& remote_settings) { + _remote_settings.MergeFrom(remote_settings); + } + int SetHostSocket(Socket *host_socket); + void SetConnected(); + void SetConnected(const StreamSettings *remote_settings); + + void Wait(void (*on_writable)(StreamId, void*, int), void *arg, + const timespec *due_time); + int Wait(const timespec* due_time); + void FillSettings(StreamSettings *settings); + static int SetFailed(StreamId id, int error_code, const char* reason_fmt, ...) + __attribute__ ((__format__ (__printf__, 3, 4))); + static int SetFailed(const StreamIds& ids, int error_code, const char* reason_fmt, ...) + __attribute__ ((__format__ (__printf__, 3, 4))); + void Close(int error_code, const char* reason_fmt, ...) + __attribute__ ((__format__ (__printf__, 3, 4))); + +private: +friend void StreamWait(StreamId stream_id, const timespec *due_time, + void (*on_writable)(StreamId, void*, int), void *arg); +friend class MessageBatcher; +friend struct butil::DefaultDeleter; + Stream(); + ~Stream(); + int Init(const StreamOptions options); + void SetRemoteConsumed(size_t _remote_consumed); + void TriggerOnConnectIfNeed(); + void Wait(void (*on_writable)(StreamId, void*, int), void* arg, + const timespec* due_time, bool new_thread, bthread_id_t *join_id); + void SendFeedback(int64_t _consumed_bytes); + void StartIdleTimer(); + void StopIdleTimer(); + void HandleRpcResponse(butil::IOBuf* response_buffer); + void WriteToHostSocket(butil::IOBuf* b); + + static int Consume(void *meta, bthread::TaskIterator& iter); + static int TriggerOnWritable(bthread_id_t id, void *data, int error_code); + static void *RunOnWritable(void* arg); + static void* RunOnConnect(void* arg); + + struct ConnectMeta { + int (*on_connect)(int, int, void*); + int ec; + void* arg; + }; + + struct WritableMeta { + void (*on_writable)(StreamId, void*, int); + StreamId id; + void *arg; + int error_code; + bool new_thread; + bool has_timer; + bthread_timer_t timer; + }; + + Socket* _host_socket; // Every stream within a Socket holds a reference + Socket* _fake_socket_weak_ref; // Not holding reference + StreamId _id; + StreamOptions _options; + + bthread_mutex_t _connect_mutex; + ConnectMeta _connect_meta; + butil::atomic _connected; + bool _closed; + int _error_code; + std::string _error_text; + + bthread_mutex_t _congestion_control_mutex; + size_t _produced; + size_t _remote_consumed; + size_t _cur_buf_size; + bthread_id_list_t _writable_wait_list; + + int64_t _local_consumed; + butil::atomic _atomic_local_consumed; + StreamSettings _remote_settings; + + bool _parse_rpc_response; + bthread::ExecutionQueueId _consumer_queue; + butil::IOBuf *_pending_buf; + int64_t _start_idle_timer_us; + bthread_timer_t _idle_timer; + std::once_flag _set_host_socket_flag; +}; + +} // namespace brpc + + + +#endif //BRPC_STREAM_IMPL_H diff --git a/src/brpc/streaming_rpc_meta.proto b/src/brpc/streaming_rpc_meta.proto new file mode 100644 index 0000000..5474583 --- /dev/null +++ b/src/brpc/streaming_rpc_meta.proto @@ -0,0 +1,49 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; + +package brpc; +option java_package="com.brpc"; +option java_outer_classname="StreamingRpcProto"; + +message StreamSettings { + required int64 stream_id = 1; + optional bool need_feedback = 2 [default = false]; + optional bool writable = 3 [default = false]; + repeated int64 extra_stream_ids = 4; +} + +enum FrameType { + FRAME_TYPE_UNKNOWN = 0; + FRAME_TYPE_RST = 1; + FRAME_TYPE_CLOSE = 2; + FRAME_TYPE_DATA = 3; + FRAME_TYPE_FEEDBACK= 4; +} + +message StreamFrameMeta { + required int64 stream_id = 1; + optional int64 source_stream_id = 2; + optional FrameType frame_type = 3; + optional bool has_continuation = 4; + optional Feedback feedback = 5; +} + +message Feedback { + optional int64 consumed_size = 1; +} diff --git a/src/brpc/tcp_transport.cpp b/src/brpc/tcp_transport.cpp new file mode 100644 index 0000000..27e6ae8 --- /dev/null +++ b/src/brpc/tcp_transport.cpp @@ -0,0 +1,106 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/tcp_transport.h" +#include "brpc/event_dispatcher.h" + +namespace brpc { +DECLARE_bool(usercode_in_coroutine); +DECLARE_bool(usercode_in_pthread); + +extern SocketVarsCollector* g_vars; + +void TcpTransport::Init(Socket* socket, const SocketOptions& options) { + _socket = socket; + _default_connect = options.app_connect; + _on_edge_trigger = options.on_edge_triggered_events; + if (options.need_on_edge_trigger && _on_edge_trigger == NULL) { + _on_edge_trigger = InputMessenger::OnNewMessages; + } +} + +void TcpTransport::Release(){} + +int TcpTransport::Reset(int32_t expected_nref) { + return 0; +} + +int TcpTransport::CutFromIOBuf(butil::IOBuf* buf) { + return buf->cut_into_file_descriptor(_socket->fd()); +} + +std::shared_ptr TcpTransport::Connect() { + return _default_connect; +} + +ssize_t TcpTransport::CutFromIOBufList(butil::IOBuf** buf, size_t ndata) { + return butil::IOBuf::cut_multiple_into_file_descriptor(_socket->fd(), buf, ndata); +} + +int TcpTransport::WaitEpollOut(butil::atomic* _epollout_butex, + bool pollin, timespec duetime) { + g_vars->nwaitepollout << 1; + const int rc = _socket->WaitEpollOut(_socket->fd(), pollin, &duetime); + if (rc < 0 && errno != ETIMEDOUT) { + const int saved_errno = errno; + PLOG(WARNING) << "Fail to wait epollout of " << _socket; + _socket->SetFailed(saved_errno, "Fail to wait epollout of %s: %s", + _socket->description().c_str(), berror(saved_errno)); + return 1; + } + return 0; +} + +void TcpTransport::ProcessEvent(bthread_attr_t attr) { + bthread_t tid; + if (FLAGS_usercode_in_coroutine) { + OnEdge(_socket); + } else if (!EventDispatcherUnsched()) { + auto rc = bthread_start_urgent(&tid, &attr, OnEdge, _socket); + if (rc != 0) { + LOG(FATAL) << "Fail to start ProcessEvent"; + OnEdge(_socket); + } + } else if (bthread_start_background(&tid, &attr, OnEdge, _socket) != 0) { + LOG(FATAL) << "Fail to start ProcessEvent"; + OnEdge(_socket); + } +} +void TcpTransport::QueueMessage(InputMessageClosure& input_msg, + int* num_bthread_created, bool) { + InputMessageBase* to_run_msg = input_msg.release(); + if (!to_run_msg) { + return; + } + // Create bthread for last_msg. The bthread is not scheduled + // until bthread_flush() is called (in the worse case). + bthread_t th; + bthread_attr_t tmp = + (FLAGS_usercode_in_pthread ? BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL) | + BTHREAD_NOSIGNAL; + tmp.keytable_pool = _socket->keytable_pool(); + tmp.tag = bthread_self_tag(); + bthread_attr_set_name(&tmp, "ProcessInputMessage"); + if (!FLAGS_usercode_in_coroutine && bthread_start_background( + &th, &tmp, ProcessInputMessage, to_run_msg) == 0) { + ++*num_bthread_created; + } else { + ProcessInputMessage(to_run_msg); + } +} + +} // namespace brpc diff --git a/src/brpc/tcp_transport.h b/src/brpc/tcp_transport.h new file mode 100644 index 0000000..8a06a85 --- /dev/null +++ b/src/brpc/tcp_transport.h @@ -0,0 +1,41 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_TCP_TRANSPORT_H +#define BRPC_TCP_TRANSPORT_H + +#include "brpc/transport.h" +#include "brpc/socket.h" + +namespace brpc { +class TcpTransport : public Transport { + friend class TransportFactory; +public: + void Init(Socket* socket, const SocketOptions& options) override; + void Release() override; + int Reset(int32_t expected_nref) override; + std::shared_ptr Connect() override; + int CutFromIOBuf(butil::IOBuf* buf) override; + ssize_t CutFromIOBufList(butil::IOBuf** buf, size_t ndata) override; + int WaitEpollOut(butil::atomic* _epollout_butex, bool pollin, timespec duetime) override; + void ProcessEvent(bthread_attr_t attr) override; + void QueueMessage(InputMessageClosure& input_msg, int* num_bthread_created, bool last_msg) override; + void Debug(std::ostream &os) override {} +}; +} // namespace brpc + +#endif //BRPC_TCP_TRANSPORT_H \ No newline at end of file diff --git a/src/brpc/thrift_message.cpp b/src/brpc/thrift_message.cpp new file mode 100644 index 0000000..9265ed9 --- /dev/null +++ b/src/brpc/thrift_message.cpp @@ -0,0 +1,94 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + + +#define INTERNAL_SUPPRESS_PROTOBUF_FIELD_DEPRECATION +#include "brpc/thrift_message.h" + +#include "butil/logging.h" + +namespace brpc { + +ThriftFramedMessage::ThriftFramedMessage() + : NonreflectableMessage() { + SharedCtor(); +} + +void ThriftFramedMessage::SharedCtor() { + field_id = THRIFT_INVALID_FID; + _own_raw_instance = false; + _raw_instance = nullptr; +} + +ThriftFramedMessage::~ThriftFramedMessage() { + SharedDtor(); + if (_own_raw_instance) { + delete _raw_instance; + } +} + +void ThriftFramedMessage::SharedDtor() { +} + +void ThriftFramedMessage::Clear() { + body.clear(); + if (_own_raw_instance) { + delete _raw_instance; + _own_raw_instance = false; + _raw_instance = NULL; + } +} + +size_t ThriftFramedMessage::ByteSizeLong() const { + if (_raw_instance) { + LOG(ERROR) << "ByteSize() is always 0 when _raw_instance is set"; + return 0; + } + return body.size(); +} + +void ThriftFramedMessage::MergeFrom(const ThriftFramedMessage& from) { + CHECK_NE(&from, this); + LOG(ERROR) << "ThriftFramedMessage does not support MergeFrom"; +} + +void ThriftFramedMessage::Swap(ThriftFramedMessage* other) { + if (other != this) { + body.swap(other->body); + std::swap(field_id, other->field_id); + std::swap(_own_raw_instance, other->_own_raw_instance); + std::swap(_raw_instance, other->_raw_instance); + } +} + +::google::protobuf::Metadata ThriftFramedMessage::GetMetadata() const { + ::google::protobuf::Metadata metadata{}; + metadata.descriptor = ThriftFramedMessageBase::descriptor(); + metadata.reflection = nullptr; + return metadata; +} + +void ThriftStub::CallMethod(const char* method_name, + Controller* cntl, + const ThriftFramedMessage* req, + ThriftFramedMessage* res, + ::google::protobuf::Closure* done) { + cntl->_thrift_method_name.assign(method_name); + _channel->CallMethod(NULL, cntl, req, res, done); +} + +} // namespace brpc diff --git a/src/brpc/thrift_message.h b/src/brpc/thrift_message.h new file mode 100644 index 0000000..e31c941 --- /dev/null +++ b/src/brpc/thrift_message.h @@ -0,0 +1,226 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_THRIFT_MESSAGE_H +#define BRPC_THRIFT_MESSAGE_H + +#include "brpc/channel_base.h" +#include "brpc/controller.h" +#include "brpc/nonreflectable_message.h" +#include "brpc/pb_compat.h" +#include "brpc/proto_base.pb.h" +#include "butil/class_name.h" +#include "butil/iobuf.h" + +namespace apache { +namespace thrift { +class TBase; +namespace protocol { +class TProtocol; +} +} +} + +namespace brpc { + +class ThriftStub; + +static const int16_t THRIFT_INVALID_FID = -1; +static const int16_t THRIFT_REQUEST_FID = 1; +static const int16_t THRIFT_RESPONSE_FID = 0; + +// Problem: TBase is absent in thrift 0.9.3 +// Solution: Wrap native messages with templates into instances inheriting +// from ThriftMessageBase which can be stored and handled uniformly. +class ThriftMessageBase { +public: + virtual ~ThriftMessageBase() {} + virtual uint32_t Read(::apache::thrift::protocol::TProtocol* iprot) = 0; + virtual uint32_t Write(::apache::thrift::protocol::TProtocol* oprot) const = 0; +}; + +// Representing a thrift framed request or response. +class ThriftFramedMessage : public NonreflectableMessage { +friend class ThriftStub; +public: + butil::IOBuf body; // ~= "{ raw_instance }" + int16_t field_id; // must be set when body is set. + +private: + bool _own_raw_instance; + ThriftMessageBase* _raw_instance; + +public: + ThriftMessageBase* raw_instance() const { return _raw_instance; } + + template T* Cast(); + + ThriftFramedMessage(); + + ~ThriftFramedMessage() override; + + ThriftFramedMessage(const ThriftFramedMessage& from) = delete; + + ThriftFramedMessage& operator=(const ThriftFramedMessage& from) = delete; + + void Swap(ThriftFramedMessage* other); + + // implements Message ---------------------------------------------- + void MergeFrom(const ThriftFramedMessage& from) override; + void Clear() override; + + size_t ByteSizeLong() const override; + int GetCachedSize() const PB_425_OVERRIDE { return ByteSize(); } + + ::google::protobuf::Metadata GetMetadata() const PB_527_OVERRIDE; + +private: + void SharedCtor(); + void SharedDtor(); +}; + +class ThriftStub { +public: + explicit ThriftStub(ChannelBase* channel) : _channel(channel) {} + + template + void CallMethod(const char* method_name, + Controller* cntl, + const REQUEST* raw_request, + RESPONSE* raw_response, + ::google::protobuf::Closure* done); + + void CallMethod(const char* method_name, + Controller* cntl, + const ThriftFramedMessage* req, + ThriftFramedMessage* res, + ::google::protobuf::Closure* done); + +private: + ChannelBase* _channel; +}; + +namespace policy { +// Implemented in policy/thrift_protocol.cpp +bool ReadThriftStruct(const butil::IOBuf& body, + ThriftMessageBase* raw_msg, + int16_t expected_fid); +} + +namespace details { + +template +class ThriftMessageWrapper final : public ThriftMessageBase { +public: + ThriftMessageWrapper() : msg_ptr(NULL) {} + ThriftMessageWrapper(T* msg2) : msg_ptr(msg2) {} + virtual ~ThriftMessageWrapper() {} + // NOTE: "T::" makes the function call work around vtable + uint32_t Read(::apache::thrift::protocol::TProtocol* iprot) override final + { return msg_ptr->T::read(iprot); } + uint32_t Write(::apache::thrift::protocol::TProtocol* oprot) const override final + { return msg_ptr->T::write(oprot); } + T* msg_ptr; +}; + +template +class ThriftMessageHolder final : public ThriftMessageBase { +public: + virtual ~ThriftMessageHolder() {} + // NOTE: "T::" makes the function call work around vtable + uint32_t Read(::apache::thrift::protocol::TProtocol* iprot) override final + { return msg.T::read(iprot); } + uint32_t Write(::apache::thrift::protocol::TProtocol* oprot) const override final + { return msg.T::write(oprot); } + T msg; +}; + +// A wrapper closure to own additional stuffs required by ThriftStub +template +class ThriftDoneWrapper : public ::google::protobuf::Closure { +public: + explicit ThriftDoneWrapper(::google::protobuf::Closure* done) + : _done(done) {} + void Run() override { + _done->Run(); + delete this; + } +private: + ::google::protobuf::Closure* _done; +public: + ThriftMessageWrapper raw_response_wrapper; + ThriftFramedMessage response; +}; + +} // namespace details + +template +T* ThriftFramedMessage::Cast() { + if (_raw_instance) { + auto p = dynamic_cast*>(_raw_instance); + if (p) { + return &p->msg; + } + delete _raw_instance; + } + auto raw_msg_wrapper = new details::ThriftMessageHolder; + T* raw_msg = &raw_msg_wrapper->msg; + _raw_instance = raw_msg_wrapper; + _own_raw_instance = true; + + if (!body.empty()) { + if (!policy::ReadThriftStruct(body, _raw_instance, field_id)) { + LOG(ERROR) << "Fail to parse " << butil::class_name(); + } + } + return raw_msg; +} + +template +void ThriftStub::CallMethod(const char* method_name, + Controller* cntl, + const REQUEST* raw_request, + RESPONSE* raw_response, + ::google::protobuf::Closure* done) { + cntl->_thrift_method_name.assign(method_name); + + details::ThriftMessageWrapper + raw_request_wrapper(const_cast(raw_request)); + ThriftFramedMessage request; + request._raw_instance = &raw_request_wrapper; + + if (done == NULL) { + // response is guaranteed to be unused after a synchronous RPC, no + // need to allocate it on heap. + ThriftFramedMessage response; + details::ThriftMessageWrapper raw_response_wrapper(raw_response); + response._raw_instance = &raw_response_wrapper; + _channel->CallMethod(NULL, cntl, &request, &response, NULL); + } else { + // Let the new_done own the response and release it after Run(). + details::ThriftDoneWrapper* new_done = + new details::ThriftDoneWrapper(done); + new_done->raw_response_wrapper.msg_ptr = raw_response; + new_done->response._raw_instance = &new_done->raw_response_wrapper; + _channel->CallMethod(NULL, cntl, &request, &new_done->response, new_done); + } +} + +} // namespace brpc + +#endif // BRPC_THRIFT_MESSAGE_H diff --git a/src/brpc/thrift_service.cpp b/src/brpc/thrift_service.cpp new file mode 100644 index 0000000..24b2439 --- /dev/null +++ b/src/brpc/thrift_service.cpp @@ -0,0 +1,52 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "butil/class_name.h" +#include "brpc/thrift_service.h" +#include "brpc/details/method_status.h" + +namespace brpc { + +ThriftService::ThriftService() { + _status = new MethodStatus; +} + +ThriftService::~ThriftService() { + delete _status; + _status = NULL; +} + +void ThriftService::Describe(std::ostream &os, const DescribeOptions&) const { + os << butil::class_name_str(*this); +} + +void ThriftService::Expose(const butil::StringPiece& prefix) { + if (_status == NULL) { + return; + } + std::string s; + const std::string& cached_name = butil::class_name_str(*this); + s.reserve(prefix.size() + 1 + cached_name.size()); + s.append(prefix.data(), prefix.size()); + s.push_back('_'); + s.append(cached_name); + _status->Expose(s); +} + +} // namespace brpc + diff --git a/src/brpc/thrift_service.h b/src/brpc/thrift_service.h new file mode 100644 index 0000000..bd4ca44 --- /dev/null +++ b/src/brpc/thrift_service.h @@ -0,0 +1,75 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_THRIFT_SERVICE_H +#define BRPC_THRIFT_SERVICE_H + +#include "brpc/controller.h" // Controller +#include "brpc/thrift_message.h" // ThriftFramedMessage +#include "brpc/describable.h" +#include "brpc/adaptive_max_concurrency.h" + +namespace brpc { + +class Socket; +class Server; +class MethodStatus; +class StatusService; +namespace policy { +class ThriftClosure; +void ProcessThriftRequest(InputMessageBase* msg_base); +} + +// Inherit this class to let brpc server understands thrift_binary requests. +class ThriftService : public Describable { +public: + ThriftService(); + ~ThriftService() override; + + // Implement this method to handle thrift_binary requests. + // Parameters: + // controller per-rpc settings. controller->Failed() is always false. + // request The thrift_binary request received. + // response The thrift_binary response that you should fill in. + // done You must call done->Run() to end the processing. + virtual void ProcessThriftFramedRequest( + Controller* controller, + ThriftFramedMessage* request, + ThriftFramedMessage* response, + ::google::protobuf::Closure* done) = 0; + + // Put descriptions into the stream. + void Describe(std::ostream &os, const DescribeOptions&) const override; + +private: +DISALLOW_COPY_AND_ASSIGN(ThriftService); +friend class policy::ThriftClosure; +friend void policy::ProcessThriftRequest(InputMessageBase* msg_base); +friend class StatusService; +friend class Server; + +private: + void Expose(const butil::StringPiece& prefix); + + MethodStatus* _status; + AdaptiveMaxConcurrency _max_concurrency; +}; + +} // namespace brpc + +#endif // BRPC_THRIFT_SERVICE_H diff --git a/src/brpc/traceprintf.h b/src/brpc/traceprintf.h new file mode 100644 index 0000000..47a8dcf --- /dev/null +++ b/src/brpc/traceprintf.h @@ -0,0 +1,63 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_TRACEPRINTF_H +#define BRPC_TRACEPRINTF_H + +#include +#include "butil/macros.h" + +// To brpc developers: This is a header included by user, don't depend +// on internal structures, use opaque pointers instead. + + +namespace brpc { + +// Forward declaration +class Span; + +bool CanAnnotateSpan(); +void AnnotateSpan(const char* fmt, ...); + +// Declarations for AnnotateSpanEx used by TRACEPRINTF_SPAN macro +void AnnotateSpanEx(std::shared_ptr span, const char* fmt, ...); + +} // namespace brpc + + +// Use this macro to print log to /rpcz and tracing system. +// If rpcz is not enabled, arguments to this macro is NOT evaluated, don't +// have (critical) side effects in arguments. +#define TRACEPRINTF(fmt, args...) \ + do { \ + if (::brpc::CanAnnotateSpan()) { \ + ::brpc::AnnotateSpan("[" __FILE__ ":" BAIDU_SYMBOLSTR(__LINE__) "] " fmt, ##args); \ + } \ + } while (0) + + +// Use this macro to print log to a specific span. +// If span_ptr is NULL, arguments to this macro is NOT evaluated. +#define TRACEPRINTF_SPAN(span_ptr, fmt, args...) \ + do { \ + if ((span_ptr)) { \ + ::brpc::AnnotateSpanEx((span_ptr), "[" __FILE__ ":" BAIDU_SYMBOLSTR(__LINE__) "] " fmt, ##args); \ + } \ + } while (0) + +#endif // BRPC_TRACEPRINTF_H diff --git a/src/brpc/trackme.cpp b/src/brpc/trackme.cpp new file mode 100644 index 0000000..4decc35 --- /dev/null +++ b/src/brpc/trackme.cpp @@ -0,0 +1,242 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include +#include +#include +#include "butil/fast_rand.h" +#include "brpc/log.h" +#include "brpc/channel.h" +#include "brpc/trackme.pb.h" +#include "brpc/policy/hasher.h" +#include "butil/files/scoped_file.h" + +namespace brpc { + +#ifdef BAIDU_INTERNAL +DEFINE_string(trackme_server, "http://brpc.baidu.com:8877", + "Where the TrackMe requests are sent to"); +#else +DEFINE_string(trackme_server, "", "Where the TrackMe requests are sent to"); +#endif + +static const int32_t TRACKME_MIN_INTERVAL = 30; +static const int32_t TRACKME_MAX_INTERVAL = 600; +static int32_t s_trackme_interval = TRACKME_MIN_INTERVAL; +// Protecting global vars on trackme +static pthread_mutex_t s_trackme_mutex = PTHREAD_MUTEX_INITIALIZER; +// For contacting with trackme_server. +static Channel* s_trackme_chan = NULL; +// Any server address in this process. +static std::string* s_trackme_addr = NULL; + +// Information of bugs. +// Notice that this structure may be a combination of all affected bugs. +// Namely `severity' is severity of the worst bug and `error_text' is +// combination of descriptions of all bugs. Check tools/trackme_server +// for impl. details. +struct BugInfo { + TrackMeSeverity severity; + std::string error_text; + bool operator==(const BugInfo& bug) const { + return severity == bug.severity && error_text == bug.error_text; + } +}; +// If a bug was shown, its info was stored in this var as well so that we +// can avoid showing the same bug repeatly. +static BugInfo* g_bug_info = NULL; +// The timestamp(microseconds) that we sent TrackMeRequest. +static int64_t s_trackme_last_time = 0; + +// version of RPC. +// Since the code for getting BRPC_REVISION often fails, +// BRPC_REVISION must be defined to string and be converted to number +// within our code. +// The code running before main() may see g_rpc_version=0, should be OK. +#if defined(BRPC_REVISION) +const int64_t g_rpc_version = atoll(BRPC_REVISION); +#else +const int64_t g_rpc_version = 0; +#endif + +int ReadJPaasHostPort(int container_port) { + const uid_t uid = getuid(); + struct passwd* pw = getpwuid(uid); + if (pw == NULL) { + RPC_VLOG << "Fail to get password file entry of uid=" << uid; + return -1; + } + char JPAAS_LOG_PATH[64]; + snprintf(JPAAS_LOG_PATH, sizeof(JPAAS_LOG_PATH), + "%s/jpaas_run/logs/env.log", pw->pw_dir); + char* line = NULL; + size_t line_len = 0; + ssize_t nr = 0; + butil::ScopedFILE fp(fopen(JPAAS_LOG_PATH, "r")); + if (!fp) { + RPC_VLOG << "Fail to open `" << JPAAS_LOG_PATH << '\''; + return -1; + } + int host_port = -1; + char prefix[32]; + const int prefix_len = + snprintf(prefix, sizeof(prefix), "JPAAS_HOST_PORT_%d=", container_port); + while ((nr = getline(&line, &line_len, fp.get())) != -1) { + if (line[nr - 1] == '\n') { // remove ending newline + --nr; + } + if (nr > prefix_len && memcmp(line, prefix, prefix_len) == 0) { + host_port = strtol(line + prefix_len, NULL, 10); + break; + } + } + free(line); + RPC_VLOG_IF(host_port < 0) << "No entry starting with `" << prefix << "' found"; + return host_port; +} + +// Called in server.cpp +void SetTrackMeAddress(butil::EndPoint pt) { + BAIDU_SCOPED_LOCK(s_trackme_mutex); + if (s_trackme_addr == NULL) { + // JPAAS has NAT capabilities, read its log to figure out the open port + // accessible from outside. + const int jpaas_port = ReadJPaasHostPort(pt.port); + if (jpaas_port > 0) { + RPC_VLOG << "Use jpaas_host_port=" << jpaas_port + << " instead of jpaas_container_port=" << pt.port; + pt.port = jpaas_port; + } + s_trackme_addr = new std::string(butil::endpoint2str(pt).c_str()); + } +} + +static void HandleTrackMeResponse(Controller* cntl, TrackMeResponse* res) { + if (cntl->Failed()) { + RPC_VLOG << "Fail to access " << FLAGS_trackme_server << ", " << cntl->ErrorText(); + } else { + BugInfo cur_info; + cur_info.severity = res->severity(); + cur_info.error_text = res->error_text(); + bool already_reported = false; + { + BAIDU_SCOPED_LOCK(s_trackme_mutex); + if (g_bug_info != NULL && *g_bug_info == cur_info) { + // we've shown the bug. + already_reported = true; + } else { + // save the bug. + if (g_bug_info == NULL) { + g_bug_info = new BugInfo(cur_info); + } else { + *g_bug_info = cur_info; + } + } + } + if (!already_reported) { + switch (res->severity()) { + case TrackMeOK: + break; + case TrackMeFatal: + LOG(ERROR) << "Your brpc (r" << g_rpc_version + << ") is affected by: " << res->error_text(); + break; + case TrackMeWarning: + LOG(WARNING) << "Your brpc (r" << g_rpc_version + << ") is affected by: " << res->error_text(); + break; + default: + LOG(WARNING) << "Unknown severity=" << res->severity(); + break; + } + } + if (res->has_new_interval()) { + // We can't fully trust the result from trackme_server which may + // have bugs. Make sure the reporting interval is not too short or + // too long + int32_t new_interval = res->new_interval(); + new_interval = std::max(new_interval, TRACKME_MIN_INTERVAL); + new_interval = std::min(new_interval, TRACKME_MAX_INTERVAL); + if (new_interval != s_trackme_interval) { + s_trackme_interval = new_interval; + RPC_VLOG << "Update s_trackme_interval to " << new_interval; + } + } + } + delete cntl; + delete res; +} + +static void TrackMeNow(std::unique_lock& mu) { + if (s_trackme_addr == NULL) { + return; + } + if (s_trackme_chan == NULL) { + Channel* chan = new (std::nothrow) Channel; + if (chan == NULL) { + LOG(FATAL) << "Fail to new trackme channel"; + return; + } + ChannelOptions opt; + // keep #connections on server-side low + opt.connection_type = CONNECTION_TYPE_SHORT; + if (chan->Init(FLAGS_trackme_server.c_str(), "c_murmurhash", &opt) != 0) { + LOG(WARNING) << "Fail to connect to " << FLAGS_trackme_server; + delete chan; + return; + } + s_trackme_chan = chan; + } + mu.unlock(); + TrackMeService_Stub stub(s_trackme_chan); + TrackMeRequest req; + req.set_rpc_version(g_rpc_version); + req.set_server_addr(*s_trackme_addr); + TrackMeResponse* res = new TrackMeResponse; + Controller* cntl = new Controller; + cntl->set_request_code(policy::MurmurHash32(s_trackme_addr->data(), s_trackme_addr->size())); + google::protobuf::Closure* done = + ::brpc::NewCallback(&HandleTrackMeResponse, cntl, res); + stub.TrackMe(cntl, &req, res, done); +} + +// Called in global.cpp +// [Thread-safe] supposed to be called in low frequency. +void TrackMe() { + if (FLAGS_trackme_server.empty()) { + return; + } + int64_t now = butil::cpuwide_time_us(); + std::unique_lock mu(s_trackme_mutex); + if (s_trackme_last_time == 0) { + // Delay the first ping randomly within s_trackme_interval. This + // protects trackme_server from ping storms. + s_trackme_last_time = + now + butil::fast_rand_less_than(s_trackme_interval) * 1000000L; + } + if (now > s_trackme_last_time + 1000000L * s_trackme_interval) { + s_trackme_last_time = now; + return TrackMeNow(mu); + } +} + +} // namespace brpc diff --git a/src/brpc/trackme.h b/src/brpc/trackme.h new file mode 100644 index 0000000..55c25f1 --- /dev/null +++ b/src/brpc/trackme.h @@ -0,0 +1,40 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_TRACKME_H +#define BRPC_TRACKME_H + +// [Internal] RPC users are not supposed to call functions below. + +#include "butil/endpoint.h" + + +namespace brpc { + +// Set the server address for reporting. +// Currently only the first address will be saved. +void SetTrackMeAddress(butil::EndPoint pt); + +// Call this function every second (or every several seconds) to send +// TrackMeRequest to -trackme_server every TRACKME_INTERVAL seconds. +void TrackMe(); + +} // namespace brpc + + +#endif // BRPC_TRACKME_H diff --git a/src/brpc/trackme.proto b/src/brpc/trackme.proto new file mode 100644 index 0000000..a40e5fa --- /dev/null +++ b/src/brpc/trackme.proto @@ -0,0 +1,50 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +option cc_generic_services=true; + +package brpc; + +// The request sent to -trackme_server +message TrackMeRequest { + // the svn revision when baidu-rp being compiled, which will be checked + // by server to tell if the version contains bugs + optional int64 rpc_version = 1; + optional string server_addr = 2; +}; + +enum TrackMeSeverity { + TrackMeOK = 0; + TrackMeWarning = 1; + TrackMeFatal = 2; +}; + +// The response from -trackme_server +message TrackMeResponse { + // Print an ERROR log with error_text when severity=TrackMeFatal + // Print a WARNING log with error_text when severity=TrackMeWarning + // Do nothing when severity=TrackMeOk + optional TrackMeSeverity severity = 1; + optional string error_text = 2; + // If this field is set, send trackme requests with this interval. + optional int32 new_interval = 3; +}; + +service TrackMeService { + rpc TrackMe(TrackMeRequest) returns (TrackMeResponse); +} diff --git a/src/brpc/transport.h b/src/brpc/transport.h new file mode 100644 index 0000000..a2cb868 --- /dev/null +++ b/src/brpc/transport.h @@ -0,0 +1,66 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_TRANSPORT_H +#define BRPC_TRANSPORT_H +#include "brpc/input_messenger.h" +#include "brpc/socket.h" +#include "server.h" + +namespace brpc { +using OnEdgeTrigger = std::function; +class Transport { + friend class TransportFactory; +public: + static void* OnEdge(void* arg) { + // the enclosed Socket is valid and free to access inside this function. + SocketUniquePtr s(static_cast(arg)); + const OnEdgeTrigger on_edge_trigger = s->_transport->GetOnEdgeTrigger(); + on_edge_trigger(s.get()); + return NULL; + } + + static void* ProcessInputMessage(void* void_arg) { + InputMessageBase* msg = static_cast(void_arg); + msg->_process(msg); + return NULL; + } + virtual ~Transport() = default; + virtual void Init(Socket* socket, const SocketOptions& options) = 0; + virtual void Release() = 0; + virtual int Reset(int32_t expected_nref) = 0; + virtual std::shared_ptr Connect() = 0; + virtual int CutFromIOBuf(butil::IOBuf* buf) = 0; + virtual ssize_t CutFromIOBufList(butil::IOBuf** buf, size_t ndata) = 0; + virtual int WaitEpollOut(butil::atomic* _epollout_butex, bool pollin, timespec duetime) = 0; + virtual void ProcessEvent(bthread_attr_t attr) = 0; + virtual void QueueMessage(InputMessageClosure& input_msg, int* num_bthread_created, bool last_msg) = 0; + virtual void Debug(std::ostream &os) = 0; + + bool HasOnEdgeTrigger() { + return _on_edge_trigger != NULL; + } + OnEdgeTrigger GetOnEdgeTrigger() { + return _on_edge_trigger; + } +protected: + Socket* _socket; + std::shared_ptr _default_connect; + OnEdgeTrigger _on_edge_trigger; +}; +} +#endif //BRPC_TRANSPORT_H \ No newline at end of file diff --git a/src/brpc/transport_factory.cpp b/src/brpc/transport_factory.cpp new file mode 100644 index 0000000..b689e2e --- /dev/null +++ b/src/brpc/transport_factory.cpp @@ -0,0 +1,52 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/transport_factory.h" +#include "brpc/tcp_transport.h" +#include "brpc/rdma_transport.h" + +namespace brpc { +int TransportFactory::ContextInitOrDie(SocketMode mode, bool serverOrNot, const void* _options) { + if (mode == SOCKET_MODE_TCP) { + return 0; + } +#if BRPC_WITH_RDMA + else if (mode == SOCKET_MODE_RDMA) { + return RdmaTransport::ContextInitOrDie(serverOrNot, _options); + } +#endif + else { + LOG(ERROR) << "unknown transport type " << mode; + return 1; + } +} + +std::unique_ptr TransportFactory::CreateTransport(SocketMode mode) { + if (mode == SOCKET_MODE_TCP) { + return std::unique_ptr(new TcpTransport()); + } +#if BRPC_WITH_RDMA + else if (mode == SOCKET_MODE_RDMA) { + return std::unique_ptr(new RdmaTransport()); + } +#endif + else { + LOG(ERROR) << "socket_mode set error"; + return nullptr; + } +} +} // namespace brpc \ No newline at end of file diff --git a/src/brpc/transport_factory.h b/src/brpc/transport_factory.h new file mode 100644 index 0000000..d933a13 --- /dev/null +++ b/src/brpc/transport_factory.h @@ -0,0 +1,34 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_TRANSPORT_FACTORY_H +#define BRPC_TRANSPORT_FACTORY_H + +#include "brpc/socket_mode.h" +#include "brpc/transport.h" + +namespace brpc { +// TransportFactory to create transport instance with socket_mode {TCP, RDMA} +class TransportFactory { +public: + static int ContextInitOrDie(SocketMode mode, bool serverOrNot, const void* _options); + // Create transport instance with socket mode. + static std::unique_ptr CreateTransport(SocketMode mode); +}; +} // namespace brpc + +#endif //BRPC_TRANSPORT_FACTORY_H \ No newline at end of file diff --git a/src/brpc/ts.cpp b/src/brpc/ts.cpp new file mode 100644 index 0000000..ebefe53 --- /dev/null +++ b/src/brpc/ts.cpp @@ -0,0 +1,1477 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + + +// [Modified from the code in SRS2 (src/kernel/srs_kernel_ts.cpp)] +// The MIT License (MIT) +// Copyright (c) 2013-2015 SRS(ossrs) +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "brpc/log.h" +#include "brpc/policy/rtmp_protocol.h" +#include "brpc/ts.h" + + +namespace brpc { + +/*- + * Copyright (c) 2008 Joerg Sonnenberger + * Copyright (c) 2009-2012 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +static const uint32_t crctab[] = { + 0x0, + 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, + 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, + 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f, + 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, + 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, + 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, + 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe, + 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, + 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, + 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07, + 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, + 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, + 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, + 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, + 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, + 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f, + 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, + 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, + 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629, + 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, + 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, + 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, + 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8, + 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, + 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, + 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21, + 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, + 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087, + 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, + 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, + 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, + 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09, + 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, + 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + +uint32_t crc32_ts(const void* data, size_t len) { + const uint8_t* p = (const uint8_t*)data; + uint32_t crc = 0xffffffff; + for (size_t i = 0; i < len; ++i) { + crc = (crc << 8) ^ crctab[(crc >> 24) ^ *p++]; + } + return crc; +} + +// Transport Stream packets are 188 bytes in length. +static const size_t TS_PACKET_SIZE = 188; + +static const int TS_PMT_NUMBER = 1; + +// Table 2-3 - PID table, hls-mpeg-ts-iso13818-1.pdf, page 37 +// NOTE - The transport packets with PID values 0x0000, 0x0001, and +// 0x0010-0x1FFE are allowed to carry a PCR. +// Program Association Table(see Table 2-25) +static const TsPid TS_PID_PAT = (TsPid)0x00; +// Conditional Access Table (see Table 2-27) +//static const TsPid TS_PID_CAT = (TsPid)0x01; +// Transport Stream Description Table +//static const TsPid TS_PID_TSDT = (TsPid)0x02; +//static const TsPid TS_PID_RESERVED_START = (TsPid)0x03; +// static const TsPid TS_PID_RESERVED_END = (TsPid)0x0f; +// May be assigned as network_PID; Program_map_PID; elementary_PID; or for +// other purposes +//static const TsPid TS_PID_APP_START = (TsPid)0x10; +static const TsPid TS_PID_VIDEO_AVC = (TsPid)0x100; +static const TsPid TS_PID_AUDIO_AAC = (TsPid)0x101; +static const TsPid TS_PID_AUDIO_MP3 = (TsPid)0x102; +static const TsPid TS_PID_PMT = (TsPid)0x1001; +//static const TsPid TS_PID_APP_END = (TsPid)0x1ffe; +static const TsPid TS_PID_NULL = (TsPid)0x01FFF;// null packets (see Table 2-3) + +// The sync_byte is a fixed 8-bit field whose value is '0100 0111' (0x47). +// Sync_byte emulation in the choice of values for other regularly +// occurring fields, such as PID, should be avoided. +static const int8_t TS_SYNC_BYTE = 0x47; // 8 bits + +const char* TsStream2Str(TsStream stream) { + switch (stream) { + case TS_STREAM_RESERVED: return "Reserved"; + case TS_STREAM_AUDIO_MP3: return "MP3"; + case TS_STREAM_AUDIO_AAC: return "AAC"; + case TS_STREAM_AUDIO_AC3: return "AC3"; + case TS_STREAM_AUDIO_DTS: return "AudioDTS"; + case TS_STREAM_VIDEO_H264: return "H.264"; + case TS_STREAM_VIDEO_MPEG4: return "MP4"; + case TS_STREAM_AUDIO_MPEG4: return "MP4A"; + } + return "Other"; +} + +// the media audio/video message parsed from PES packet. +struct TsWriter::TsMessage { +public: + TsMessage() + : write_pcr(false) + , is_discontinuity(false) + , dts(0) + , pts(0) + , sid(TS_PES_STREAM_ID_UNKNOWN) + , PES_packet_length(0) + , continuity_counter(0) { + } + + // whether this message with pcr(program_clock_reference) + // generally, the video IDR(keyframe) carries the pcr info. + bool write_pcr; + + // whether got discontinuity ts, for example, sequence header changed. + // TODO: always false right now. + bool is_discontinuity; + + // the timestamp in 90khz + int64_t dts; + int64_t pts; + + // the id of pes stream to indicate the payload codec. + TsPESStreamId sid; + + // size of payload, 0 indicates the length() of payload. + uint16_t PES_packet_length; + + uint8_t continuity_counter; + + butil::IOBuf payload; +}; + +// whether the sid indicates the elementary stream audio. +inline bool is_audio(TsPESStreamId sid) { + return ((sid >> 5) & 0x07) == 0x06/*0b110*/; +} + +// whether the sid indicates the elementary stream video. +inline bool is_video(TsPESStreamId sid) { + return ((sid >> 4) & 0x0f) == 0x0e/*0b1110*/; +} + +TsChannelGroup::TsChannelGroup() {} +TsChannelGroup::~TsChannelGroup() {} + +TsChannel* TsChannelGroup::get(TsPid pid) { + return &_pids[pid]; + // std::map::iterator it = _pids.find(pid); + // if (it != _pids.end()) { + // return &it->second; + // } + // return NULL; +} + +TsChannel* TsChannelGroup::set(TsPid pid) { + std::map::iterator it = _pids.find(pid); + if (it == _pids.end()) { + return &_pids[pid]; + } else { + return &it->second; + } +} + +TsPacket::TsPacket(TsChannelGroup* g) + : _modified(false) + , _transport_error_indicator(0) + , _payload_unit_start_indicator(0) + , _transport_priority(0) + , _pid(TS_PID_PAT) + , _transport_scrambling_control(TS_SCRAMBLED_DISABLED) + , _adaptation_field_control(TS_AF_RESERVED) + , _continuity_counter(0) + , _adaptation_field(NULL) + , _payload(NULL) + , _tschan_group(g) { +} + +TsPacket::~TsPacket() { + delete _adaptation_field; + delete _payload; +} + +void TsPacket::Reset() { + delete _payload; + _payload = NULL; + delete _adaptation_field; + _adaptation_field = NULL; + _transport_error_indicator = 0; + _payload_unit_start_indicator = 0; + _transport_priority = 0; + _pid = TS_PID_PAT; + _transport_scrambling_control = TS_SCRAMBLED_DISABLED; + _adaptation_field_control = TS_AF_RESERVED; + _continuity_counter = 0; + _payload = NULL; + _modified = false; +} + +TsAdaptationField* TsPacket::CreateAdaptationField() { + if (_adaptation_field != NULL) { + LOG(ERROR) << "_adaptation_field is not NULL"; + return _adaptation_field; + } + _adaptation_field = new TsAdaptationField; + if (_adaptation_field_control == TS_AF_RESERVED) { + _adaptation_field_control = TS_AF_ADAPTATION_ONLY; + } else if (_adaptation_field_control == TS_AF_PAYLOAD_ONLY) { + _adaptation_field_control = TS_AF_BOTH; + } else { + LOG(ERROR) << "Invalid _adaptation_field_control=" + << _adaptation_field_control; + } + return _adaptation_field; +} + +size_t TsPacket::ByteSize() const { + size_t sz = 4; + if (_adaptation_field) { + sz +=_adaptation_field->ByteSize(); + } + if (_payload) { + sz += _payload->ByteSize(); + } + return sz; +} + +int TsPacket::Encode(void* data) const { + char* p = (char*)data; + + policy::Write1Byte(&p, TS_SYNC_BYTE); + + int16_t pidv = _pid & 0x1FFF; + pidv |= (_transport_priority << 13) & 0x2000; + pidv |= (_transport_error_indicator << 15) & 0x8000; + pidv |= (_payload_unit_start_indicator << 14) & 0x4000; + policy::WriteBigEndian2Bytes(&p, pidv); + + int8_t ccv = _continuity_counter & 0x0F; + ccv |= (_transport_scrambling_control << 6) & 0xC0; + TsAdaptationFieldType af_control = _adaptation_field_control; + if (af_control == TS_AF_RESERVED) { + // In the case of a null packet, af_control shall be set to '01'. + af_control = TS_AF_PAYLOAD_ONLY; + } + ccv |= (af_control << 4) & 0x30; + policy::Write1Byte(&p, ccv); + + if (_adaptation_field) { + if (_adaptation_field->Encode(p, af_control) != 0) { + LOG(ERROR) << "Fail to encode _adaptation_field"; + return -1; + } + p += _adaptation_field->ByteSize(); + } + + if (_payload) { + if (_payload->Encode(p) != 0) { + LOG(ERROR) << "Fail to encode _payload"; + return -1; + } + p += _payload->ByteSize(); + } + return 0; +} + +void TsPacket::AddPadding(size_t num_stuffings) { + const bool no_af_before = (_adaptation_field == NULL); + TsAdaptationField* af = mutable_adaptation_field(); + if (no_af_before) { + const size_t sz = af->ByteSize(); + if (num_stuffings > sz) { + af->nb_af_reserved = num_stuffings - sz; + } + } else { + af->nb_af_reserved += num_stuffings; + } +} + +void TsPacket::CreateAsPAT(int16_t pmt_number, TsPid pmt_pid) { + if (_modified) { + Reset(); + } + _pid = TS_PID_PAT; + _payload_unit_start_indicator = 1; + _adaptation_field_control = TS_AF_PAYLOAD_ONLY; + + TsPayloadPAT* pat = new TsPayloadPAT(this); + pat->pointer_field = 0; + pat->table_id = TS_PSI_ID_PAS; + pat->section_syntax_indicator = 1; + pat->transport_stream_id = 1; + pat->version_number = 0; + pat->current_next_indicator = 1; + pat->section_number = 0; + pat->last_section_number = 0; + pat->programs.push_back(TsPayloadPATProgram(pmt_number, pmt_pid)); + _payload = pat; +} + +int TsPacket::CreateAsPMT(int16_t pmt_number, TsPid pmt_pid, + TsPid vpid, TsStream vs, + TsPid apid, TsStream as) { + if (vs != TS_STREAM_VIDEO_H264 && + as != TS_STREAM_AUDIO_AAC && as != TS_STREAM_AUDIO_MP3) { + LOG(ERROR) << "Unsupported video_stream=" << vs << " audio_stream=" << as; + return -1; + } + + if (_modified) { + Reset(); + } + _pid = pmt_pid; + _payload_unit_start_indicator = 1; + _adaptation_field_control = TS_AF_PAYLOAD_ONLY; + + TsPayloadPMT* pmt = new TsPayloadPMT(this); + pmt->pointer_field = 0; + pmt->table_id = TS_PSI_ID_PMS; + pmt->section_syntax_indicator = 1; + pmt->program_number = pmt_number; + pmt->version_number = 0; + pmt->current_next_indicator = 1; + pmt->section_number = 0; + pmt->last_section_number = 0; + pmt->program_info_length = 0; + if (as == TS_STREAM_AUDIO_AAC || as == TS_STREAM_AUDIO_MP3) { + // use audio to carry pcr by default. + // for hls, there must be at least one audio channel. + pmt->PCR_PID = apid; + pmt->infos.push_back(new TsPayloadPMTESInfo(as, apid)); + } + if (vs == TS_STREAM_VIDEO_H264) { + // if h.264 specified, use video to carry pcr. + pmt->PCR_PID = vpid; + pmt->infos.push_back(new TsPayloadPMTESInfo(vs, vpid)); + } + _payload = pmt; + return 0; +} + +void TsPacket::CreateAsPESFirst(TsPid pid, TsPESStreamId sid, + uint8_t continuity_counter, bool discontinuity, + int64_t pcr, int64_t dts, int64_t pts, int size) { + if (_modified) { + Reset(); + } + _pid = pid; + _payload_unit_start_indicator = 1; + _adaptation_field_control = TS_AF_PAYLOAD_ONLY; + _continuity_counter = continuity_counter; + TsPayloadPES* pes = new TsPayloadPES(this); + pes->stream_id = sid; + pes->PES_packet_length = (size > 0xFFFF ? 0 : size); + pes->PES_scrambling_control = 0; + pes->PES_priority = 0; + pes->data_alignment_indicator = 0; + pes->copyright = 0; + pes->original_or_copy = 0; + pes->PTS_DTS_flags = (dts == pts)? 0x02:0x03; + pes->ESCR_flag = 0; + pes->ES_rate_flag = 0; + pes->DSM_trick_mode_flag = 0; + pes->additional_copy_info_flag = 0; + pes->PES_CRC_flag = 0; + pes->PES_extension_flag = 0; + pes->pts = pts; + pes->dts = dts; + _payload = pes; + + if (pcr >= 0) { + TsAdaptationField* af = mutable_adaptation_field(); + af->discontinuity_indicator = discontinuity; + af->random_access_indicator = 0; + af->elementary_stream_priority_indicator = 0; + af->PCR_flag = 1; + af->OPCR_flag = 0; + af->splicing_point_flag = 0; + af->transport_private_data_flag = 0; + af->adaptation_field_extension_flag = 0; + af->program_clock_reference_base = pcr; + af->program_clock_reference_extension = 0; + } +} + +void TsPacket::CreateAsPESContinue(TsPid pid, uint8_t continuity_counter) { + if (_modified) { + Reset(); + } + _pid = pid; + _adaptation_field_control = TS_AF_PAYLOAD_ONLY; + _continuity_counter = continuity_counter; +} + +TsAdaptationField::TsAdaptationField() + : discontinuity_indicator(0) + , random_access_indicator(0) + , elementary_stream_priority_indicator(0) + , PCR_flag(0) + , OPCR_flag(0) + , splicing_point_flag(0) + , transport_private_data_flag(0) + , adaptation_field_extension_flag(0) + , program_clock_reference_base(0) + , program_clock_reference_extension(0) + , original_program_clock_reference_base(0) + , original_program_clock_reference_extension(0) + , splice_countdown(0) + , transport_private_data_length(0) + , transport_private_data(NULL) + , adaptation_field_extension_length(0) + , ltw_flag(0) + , piecewise_rate_flag(0) + , seamless_splice_flag(0) + , ltw_valid_flag(0) + , ltw_offset(0) + , piecewise_rate(0) + , splice_type(0) + , DTS_next_AU0(0) + , marker_bit0(0) + , DTS_next_AU1(0) + , marker_bit1(0) + , DTS_next_AU2(0) + , marker_bit2(0) + , nb_af_ext_reserved(0) + , nb_af_reserved(0) { +} + +TsAdaptationField::~TsAdaptationField() { + delete [] transport_private_data; +} + +size_t TsAdaptationField::ByteSize() const { + size_t sz = 2; + if (PCR_flag) { sz += 6; } + if (OPCR_flag) { sz += 6; } + if (splicing_point_flag) { ++sz; } + if (transport_private_data_flag) { + sz += 1 + transport_private_data_length; + } + if (adaptation_field_extension_flag) { + sz += 2 + adaptation_field_extension_length; + } + sz += nb_af_ext_reserved; + sz += nb_af_reserved; + return sz; +} + +int TsAdaptationField::Encode( + void* data, TsAdaptationFieldType adaptation_field_control) const { + char* p = (char*)data; + const size_t af_length = adaptation_field_length(); + policy::Write1Byte(&p, af_length); + + if (adaptation_field_control == TS_AF_BOTH) { + if (af_length > 182) { + LOG(ERROR) << "Invalid af_length=" << af_length; + return -1; + } + } else if (adaptation_field_control == TS_AF_ADAPTATION_ONLY) { + if (af_length != 183) { + LOG(ERROR) << "Invalid af_length=" << af_length; + return -1; + } + } + // no adaptation field. + if (af_length == 0) { + return 0; + } + int8_t tmpv = adaptation_field_extension_flag & 0x01; + tmpv |= (discontinuity_indicator << 7) & 0x80; + tmpv |= (random_access_indicator << 6) & 0x40; + tmpv |= (elementary_stream_priority_indicator << 5) & 0x20; + tmpv |= (PCR_flag << 4) & 0x10; + tmpv |= (OPCR_flag << 3) & 0x08; + tmpv |= (splicing_point_flag << 2) & 0x04; + tmpv |= (transport_private_data_flag << 1) & 0x02; + policy::Write1Byte(&p, tmpv); + + if (PCR_flag) { + // @remark, use pcr base and ignore the extension + // @see https://github.com/ossrs/srs/issues/250#issuecomment-71349370 + int64_t pcrv = program_clock_reference_extension & 0x1ff; + pcrv |= (const1_value0 << 9) & 0x7E00; + pcrv |= (program_clock_reference_base << 15) & 0x1FFFFFFFF000000LL; + + const char* pp = (const char*)&pcrv; + *p++ = pp[5]; + *p++ = pp[4]; + *p++ = pp[3]; + *p++ = pp[2]; + *p++ = pp[1]; + *p++ = pp[0]; + } + if (OPCR_flag) { p += 6; } // Ignore OPCR + if (splicing_point_flag) { + policy::Write1Byte(&p, splice_countdown); + } + if (transport_private_data_flag) { + policy::Write1Byte(&p, transport_private_data_length); + if (transport_private_data_length > 0) { + memcpy(p, transport_private_data, transport_private_data_length); + p += transport_private_data_length; + } + } + if (adaptation_field_extension_flag) { + policy::Write1Byte(&p, adaptation_field_extension_length); + int8_t ltwfv = const1_value1 & 0x1F; + ltwfv |= (ltw_flag << 7) & 0x80; + ltwfv |= (piecewise_rate_flag << 6) & 0x40; + ltwfv |= (seamless_splice_flag << 5) & 0x20; + policy::Write1Byte(&p, ltwfv); + const char* const saved_p = p; + if (ltw_flag) { p += 2; } // Ignore ltw + if (piecewise_rate_flag) { p += 3; } // Ignore piecewise_rate + if (seamless_splice_flag) { p += 5; } // Ignore seamless_splice + p += nb_af_ext_reserved; + if (adaptation_field_extension_length != p - saved_p) { + LOG(ERROR) << "af_extension_length=" + << adaptation_field_extension_length + << " does not match other fields"; + return -1; + } + } + p += nb_af_reserved; + return 0; +} + +TsPayload::TsPayload(const TsPacket* p) : _packet(p) {} +TsPayload::~TsPayload() {} + +TsPayloadPES::TsPayloadPES(const TsPacket* p) : TsPayload(p) { + _PES_header_data_length = -1; + PES_private_data = NULL; + pack_field = NULL; + PES_extension_field = NULL; + nb_stuffings = 0; + nb_bytes = 0; + nb_paddings = 0; +} + +TsPayloadPES::~TsPayloadPES() { + delete[] PES_private_data; + delete[] pack_field; + delete[] PES_extension_field; +} + +size_t TsPayloadPES::ByteSize() const { + size_t sz = 0; + + _PES_header_data_length = 0; + const TsPESStreamId sid = stream_id; + + if (sid != TS_PES_STREAM_ID_PROGRAM_STREAM_MAP + && sid != TS_PES_STREAM_ID_PADDING_STREAM + && sid != TS_PES_STREAM_ID_PRIVATE_STREAM2 + && sid != TS_PES_STREAM_ID_ECM_STREAM + && sid != TS_PES_STREAM_ID_EMM_STREAM + && sid != TS_PES_STREAM_ID_PROGRAM_STREAM_DIRECTORY + && sid != TS_PES_STREAM_ID_DSMC_STREAM + && sid != TS_PES_STREAM_ID_H2221TYPE_E) { + sz += 6; + sz += 3; + const size_t old_sz = sz; + + if (PTS_DTS_flags == 0x2) { sz += 5; } + else if (PTS_DTS_flags == 0x3) { sz += 10; } + + if (ESCR_flag) { sz += 6; } + if (ES_rate_flag) { sz += 3; } + if (DSM_trick_mode_flag) { sz += 1; } + if (additional_copy_info_flag) { sz += 1; } + if (PES_CRC_flag) { sz += 2; } + if (PES_extension_flag) { + sz += 1; + if (PES_private_data_flag) { sz += 16; } + if (pack_header_field_flag) { sz += 1 + pack_field_length; } + if (program_packet_sequence_counter_flag) { sz += 2; } + if (P_STD_buffer_flag) { sz += 2; } + if (PES_extension_flag_2) { sz += 1 + PES_extension_field_length; } + } + _PES_header_data_length = sz - old_sz; + + sz += nb_stuffings; + // packet bytes + } else if (sid == TS_PES_STREAM_ID_PROGRAM_STREAM_MAP + || sid == TS_PES_STREAM_ID_PRIVATE_STREAM2 + || sid == TS_PES_STREAM_ID_ECM_STREAM + || sid == TS_PES_STREAM_ID_EMM_STREAM + || sid == TS_PES_STREAM_ID_PROGRAM_STREAM_DIRECTORY + || sid == TS_PES_STREAM_ID_DSMC_STREAM + || sid == TS_PES_STREAM_ID_H2221TYPE_E + ) { + // packet bytes + } else { + // nb_drop + } + return sz; +} + +int TsPayloadPES::Encode(void* data) const { + if (_PES_header_data_length < 0) { + (void)ByteSize(); + CHECK_GE(_PES_header_data_length, 0); + } + char* p = (char*)data; + + // 3B + // Together with the stream_id that follows it constitutes a packet start + // code that identifies the beginning of a packet. + policy::WriteBigEndian3Bytes(&p, 0x1); + // 1B + policy::Write1Byte(&p, stream_id); + // 2B + // the PES_packet_length is the actual bytes size, the pplv write to ts + // is the actual bytes plus the header size. + int32_t pplv = 0; + if (PES_packet_length > 0) { + pplv = PES_packet_length + 3 + _PES_header_data_length; + pplv = (pplv > 0xFFFF)? 0 : pplv; + } + policy::WriteBigEndian2Bytes(&p, pplv); + + int8_t oocv = original_or_copy & 0x01; + oocv |= (const2bits << 6) & 0xC0; + oocv |= (PES_scrambling_control << 4) & 0x30; + oocv |= (PES_priority << 3) & 0x08; + oocv |= (data_alignment_indicator << 2) & 0x04; + oocv |= (copyright << 1) & 0x02; + policy::Write1Byte(&p, oocv); + + int8_t pefv = PES_extension_flag & 0x01; + pefv |= (PTS_DTS_flags << 6) & 0xC0; + pefv |= (ESCR_flag << 5) & 0x20; + pefv |= (ES_rate_flag << 4) & 0x10; + pefv |= (DSM_trick_mode_flag << 3) & 0x08; + pefv |= (additional_copy_info_flag << 2) & 0x04; + pefv |= (PES_CRC_flag << 1) & 0x02; + policy::Write1Byte(&p, pefv); + policy::Write1Byte(&p, _PES_header_data_length); + + if (PTS_DTS_flags == 0x2) { // 5B + encode_33bits_dts_pts(&p, 0x02, pts); + } else if (PTS_DTS_flags == 0x3) { // 10B + encode_33bits_dts_pts(&p, 0x03, pts); + encode_33bits_dts_pts(&p, 0x01, dts); + // the diff of dts and pts should never be greater than 1s. + if (labs(dts - pts) > 90000) { + LOG(WARNING) << "Diff between dts=" << dts << " and pts=" << pts + << " is greater than 1 second"; + } + } + if (ESCR_flag) { p += 6; } + if (ES_rate_flag) { p += 3; } + if (DSM_trick_mode_flag) { p += 1; } + if (additional_copy_info_flag) { p += 1; } + if (PES_CRC_flag) { p += 2; } + if (PES_extension_flag) { + int8_t efv = PES_extension_flag_2 & 0x01; + efv |= (PES_private_data_flag << 7) & 0x80; + efv |= (pack_header_field_flag << 6) & 0x40; + efv |= (program_packet_sequence_counter_flag << 5) & 0x20; + efv |= (P_STD_buffer_flag << 4) & 0x10; + efv |= (const1_value0 << 1) & 0xE0; + policy::Write1Byte(&p, efv); + + if (PES_private_data_flag) { p += 16; } + if (pack_header_field_flag) { p += 1 + pack_field_length; } + if (program_packet_sequence_counter_flag) { p += 2; } + if (P_STD_buffer_flag) { p += 2; } + if (PES_extension_flag_2) { p += 1 + PES_extension_field_length; } + } + // stuffing_byte + p += nb_stuffings; + return 0; +} + +void TsPayloadPES::encode_33bits_dts_pts( + char** data, uint8_t fb, int64_t v) const { + char* p = *data; + int32_t val = (fb << 4) | (((v >> 30) & 0x07) << 1) | 1; + *p++ = val; + + val = (((v >> 15) & 0x7fff) << 1) | 1; + *p++ = (val >> 8); + *p++ = val; + + val = (((v) & 0x7fff) << 1) | 1; + *p++ = (val >> 8); + *p++ = val; + + *data = p; +} + +TsPayloadPSI::TsPayloadPSI(const TsPacket* p) + : TsPayload(p) + , _section_length(-1) + , pointer_field(0) + , section_syntax_indicator(1) + , table_id(TS_PSI_ID_PAS) { +} + +TsPayloadPSI::~TsPayloadPSI() {} + +size_t TsPayloadPSI::ByteSize() const { + size_t sz = 0; + + // section size is the sl plus the crc32 + _section_length = PsiByteSize() + 4; + if (packet()->payload_unit_start_indicator()) { sz += 1; } + sz += 3; + sz += _section_length; + return sz; +} + +int TsPayloadPSI::Encode(void* data) const { + char* p = (char*)data; + if (_section_length < 0) { + (void)ByteSize(); + CHECK_GE(_section_length, 0); + } + if (packet()->payload_unit_start_indicator()) { + policy::Write1Byte(&p, pointer_field); + } + const char* const section_start = p; // to calc the crc32 + policy::Write1Byte(&p, table_id); + int16_t slv = _section_length & 0x0FFF; + slv |= (section_syntax_indicator << 15) & 0x8000; + slv |= (const0_value << 14) & 0x4000; + slv |= (const1_value << 12) & 0x3000; + policy::WriteBigEndian2Bytes(&p, slv); + if (_section_length == 0) { + return 0; + } + if (PsiEncode(p) != 0) { + LOG(ERROR) << "Fail to TsPayloadPSI.PsiEncode"; + return -1; + } + p += _section_length - 4; + const uint32_t crc32 = crc32_ts(section_start, p - section_start); + policy::WriteBigEndian4Bytes(&p, crc32); + return 0; +} + +TsPayloadPATProgram::TsPayloadPATProgram(int16_t number, TsPid pid2) + : program_number(number) + , pid(pid2) { +} + +TsPayloadPATProgram::~TsPayloadPATProgram() {} + +int TsPayloadPATProgram::Encode(void* data) const { + char* p = (char*)data; + int tmpv = pid & 0x1FFF; + tmpv |= (program_number << 16) & 0xFFFF0000; + tmpv |= (const1_value << 13) & 0xE000; + policy::WriteBigEndian4Bytes(&p, tmpv); + return 0; +} + +TsPayloadPAT::TsPayloadPAT(const TsPacket* p) + : TsPayloadPSI(p) + , transport_stream_id(0) + , version_number(0) + , current_next_indicator(0) + , section_number(0) + , last_section_number(0) { +} + +TsPayloadPAT::~TsPayloadPAT() {} + +size_t TsPayloadPAT::PsiByteSize() const { + size_t sz = 5; + for (size_t i = 0; i < programs.size(); ++i) { + sz += programs[i].ByteSize(); + } + return sz; +} + +int TsPayloadPAT::PsiEncode(void* data) const { + char* p = (char*)data; + + policy::WriteBigEndian2Bytes(&p, transport_stream_id); + + int8_t cniv = current_next_indicator & 0x01; + cniv |= (version_number << 1) & 0x3E; + cniv |= (const1_value << 6) & 0xC0; + policy::Write1Byte(&p, cniv); + + policy::Write1Byte(&p, section_number); + policy::Write1Byte(&p, last_section_number); + + for (size_t i = 0; i < programs.size(); ++i) { + if (programs[i].Encode(p) != 0) { + LOG(ERROR) << "Fail to encode TsPayloadPAT.programs[" << i << ']'; + return -1; + } + p += programs[i].ByteSize(); + // update the apply pid table. + packet()->channel_group()->set(programs[i].pid); + } + + // update the apply pid table. + packet()->channel_group()->set(packet()->pid()); + return 0; +} + +TsPayloadPMTESInfo::TsPayloadPMTESInfo(TsStream st, TsPid epid) + : stream_type(st) + , elementary_PID(epid) + , ES_info_length(0) + , ES_info(NULL) { +} + +TsPayloadPMTESInfo::~TsPayloadPMTESInfo() { + delete [] ES_info; +} + +size_t TsPayloadPMTESInfo::ByteSize() const { + return 5 + ES_info_length; +} + +int TsPayloadPMTESInfo::Encode(void* data) const { + char* p = (char*)data; + + policy::Write1Byte(&p, stream_type); + + int16_t epv = elementary_PID & 0x1FFF; + epv |= (const1_value0 << 13) & 0xE000; + policy::WriteBigEndian2Bytes(&p, epv); + + int16_t eilv = ES_info_length & 0x0FFF; + eilv |= (const1_value1 << 12) & 0xF000; + policy::WriteBigEndian2Bytes(&p, eilv); + + if (ES_info_length > 0) { + memcpy(p, ES_info, ES_info_length); + p += ES_info_length; + } + return 0; +} + +TsPayloadPMT::TsPayloadPMT(const TsPacket* p) + : TsPayloadPSI(p) + , program_info_length(0) + , program_info_desc(NULL) { +} + +TsPayloadPMT::~TsPayloadPMT() { + delete [] program_info_desc; + + for (std::vector::iterator it = infos.begin(); + it != infos.end(); ++it) { + delete *it; + } + infos.clear(); +} + +size_t TsPayloadPMT::PsiByteSize() const { + size_t sz = 9; + sz += program_info_length; + for (size_t i = 0; i < infos.size(); ++i) { + sz += infos[i]->ByteSize(); + } + return sz; +} + +int TsPayloadPMT::PsiEncode(void* data) const { + char* p = (char*)data; + + policy::WriteBigEndian2Bytes(&p, program_number); + + int8_t cniv = current_next_indicator & 0x01; + cniv |= (const1_value0 << 6) & 0xC0; + cniv |= (version_number << 1) & 0xFE; + policy::Write1Byte(&p, cniv); + + policy::Write1Byte(&p, section_number); + policy::Write1Byte(&p, last_section_number); + + int16_t ppv = PCR_PID & 0x1FFF; + ppv |= (const1_value1 << 13) & 0xE000; + policy::WriteBigEndian2Bytes(&p, ppv); + + int16_t pilv = program_info_length & 0xFFF; + pilv |= (const1_value2 << 12) & 0xF000; + policy::WriteBigEndian2Bytes(&p, pilv); + + if (program_info_length > 0) { + memcpy(p, program_info_desc, program_info_length); + p += program_info_length; + } + + for (size_t i = 0; i < infos.size(); ++i) { + TsPayloadPMTESInfo* info = infos[i]; + if (info->Encode(p) != 0) { + LOG(ERROR) << "Fail to encode TsPayloadPMT.infos[" << i << ']'; + return -1; + } + p += info->ByteSize(); + + // update the apply pid table + switch (info->stream_type) { + case TS_STREAM_VIDEO_H264: + case TS_STREAM_VIDEO_MPEG4: + packet()->channel_group()->set(info->elementary_PID); + break; + case TS_STREAM_AUDIO_AAC: + case TS_STREAM_AUDIO_AC3: + case TS_STREAM_AUDIO_DTS: + case TS_STREAM_AUDIO_MP3: + packet()->channel_group()->set(info->elementary_PID); + break; + default: + LOG(WARNING) << "Drop pid=" << info->elementary_PID + << " stream=" << info->stream_type; + break; + } + } + + // update the apply pid table. + packet()->channel_group()->set(packet()->pid()); + return 0; +} + +TsStream FlvVideoCodec2TsStream(FlvVideoCodec codec, TsPid* pid) { + switch (codec) { + case FLV_VIDEO_AVC: + if (pid) { + *pid = TS_PID_VIDEO_AVC; + } + return TS_STREAM_VIDEO_H264; + case FLV_VIDEO_JPEG: + case FLV_VIDEO_SORENSON_H263: + case FLV_VIDEO_SCREEN_VIDEO: + case FLV_VIDEO_ON2_VP6: + case FLV_VIDEO_ON2_VP6_WITH_ALPHA_CHANNEL: + case FLV_VIDEO_SCREEN_VIDEO_V2: + case FLV_VIDEO_HEVC: + return TS_STREAM_RESERVED; + } + return TS_STREAM_RESERVED; +} + +TsStream FlvAudioCodec2TsStream(FlvAudioCodec codec, TsPid* pid) { + switch (codec) { + case FLV_AUDIO_AAC: + if (pid) { + *pid = TS_PID_AUDIO_AAC; + } + return TS_STREAM_AUDIO_AAC; + case FLV_AUDIO_MP3: + if (pid) { + *pid = TS_PID_AUDIO_MP3; + } + return TS_STREAM_AUDIO_MP3; + case FLV_AUDIO_LINEAR_PCM_PLATFORM_ENDIAN: + case FLV_AUDIO_ADPCM: + case FLV_AUDIO_LINEAR_PCM_LITTLE_ENDIAN: + case FLV_AUDIO_NELLYMOSER_16KHZ_MONO: + case FLV_AUDIO_NELLYMOSER_8KHZ_MONO: + case FLV_AUDIO_NELLYMOSER: + case FLV_AUDIO_G711_ALAW_LOGARITHMIC_PCM: + case FLV_AUDIO_G711_MULAW_LOGARITHMIC_PCM: + case FLV_AUDIO_RESERVED: + case FLV_AUDIO_SPEEX: + case FLV_AUDIO_MP3_8KHZ: + case FLV_AUDIO_DEVICE_SPECIFIC_SOUND: + return TS_STREAM_RESERVED; + } + return TS_STREAM_RESERVED; +} + +AACProfile AACObjectType2Profile(AACObjectType object_type) { + switch (object_type) { + case AAC_OBJECT_MAIN: + return AAC_PROFILE_MAIN; + case AAC_OBJECT_HE: + case AAC_OBJECT_HEV2: + case AAC_OBJECT_LC: + return AAC_PROFILE_LC; + case AAC_OBJECT_SSR: + return AAC_PROFILE_SSR; + } + return AAC_PROFILE_UNKNOWN; +} + +TsWriter::TsWriter(butil::IOBuf* outbuf) + : _outbuf(outbuf) + , _nalu_format(AVC_NALU_FORMAT_UNKNOWN) + , _has_avc_seq_header(false) + , _has_aac_seq_header(false) + , _encoded_pat_pmt(false) + , _last_video_stream(TS_STREAM_VIDEO_H264) + , _last_video_pid(TS_PID_VIDEO_AVC) + , _last_audio_stream(TS_STREAM_AUDIO_AAC) + , _last_audio_pid(TS_PID_AUDIO_AAC) + , _discontinuity_counter(0) { +} + +TsWriter::~TsWriter() { +} + +butil::Status TsWriter::Write(const RtmpAudioMessage& msg) { + // ts support audio codec: aac/mp3 + if (msg.codec != FLV_AUDIO_AAC && msg.codec != FLV_AUDIO_MP3) { + return butil::Status(EINVAL, "Unsupported codec=%s", + FlvAudioCodec2Str(msg.codec)); + } + const int64_t dts = static_cast(msg.timestamp) * 90; + TsMessage tsmsg; + tsmsg.write_pcr = false; + tsmsg.dts = dts; + tsmsg.pts = dts; + tsmsg.sid = TS_PES_STREAM_ID_AUDIO_COMMON; + + if (msg.codec == FLV_AUDIO_AAC) { + RtmpAACMessage aac_msg; + butil::Status st = aac_msg.Create(msg); + if (!st.ok()) { + return st; + } + // ignore sequence header + if (aac_msg.packet_type == FLV_AAC_PACKET_SEQUENCE_HEADER) { + butil::Status st2 = _aac_seq_header.Create(aac_msg.data); + if (!st2.ok()) { + return st2; + } + _has_aac_seq_header = true; + ++_discontinuity_counter; + return butil::Status::OK(); + } + if (!_has_aac_seq_header) { + return butil::Status(EINVAL, "Lack of AAC sequence header"); + } + if (aac_msg.data.size() > 0x1fff) { + return butil::Status(EINVAL, "Invalid AAC data_size=%" PRIu64, + (uint64_t)aac_msg.data.size()); + } + + // the frame length is the AAC raw data plus the adts header size. + const int32_t frame_length = aac_msg.data.size() + 7; + + // AAC-ADTS + // 6.2 Audio Data Transport Stream, ADTS + // in aac-iso-13818-7.pdf, page 26. + // fixed 7bytes header + uint8_t adts_header[7] = {0xff, 0xf9, 0x00, 0x00, 0x00, 0x0f, 0xfc}; + // profile, 2bits + const AACProfile aac_profile = + AACObjectType2Profile(_aac_seq_header.aac_object); + if (aac_profile == AAC_PROFILE_UNKNOWN) { + return butil::Status(EINVAL, "Invalid aac_object=%d", + (int)_aac_seq_header.aac_object); + } + adts_header[2] = (aac_profile << 6) & 0xc0; + // sampling_frequency_index 4bits + adts_header[2] |= (_aac_seq_header.aac_sample_rate << 2) & 0x3c; + // channel_configuration 3bits + adts_header[2] |= (_aac_seq_header.aac_channels >> 2) & 0x01; + adts_header[3] = (_aac_seq_header.aac_channels << 6) & 0xc0; + // frame_length 13bits + adts_header[3] |= (frame_length >> 11) & 0x03; + adts_header[4] = (frame_length >> 3) & 0xff; + adts_header[5] = ((frame_length << 5) & 0xe0); + // adts_buffer_fullness; //11bits + adts_header[5] |= 0x1f; + + tsmsg.payload.append(adts_header, sizeof(adts_header)); + tsmsg.payload.append(aac_msg.data); + } else if (msg.codec == FLV_AUDIO_MP3) { + tsmsg.payload.append(msg.data); + } + + TsPid apid = TS_PID_NULL; + TsStream as = FlvAudioCodec2TsStream(msg.codec, &apid); + if (as == TS_STREAM_RESERVED) { + return butil::Status(EINVAL, "Unsupported audio codec=%s", + FlvAudioCodec2Str(msg.codec)); + } + return Encode(&tsmsg, as, apid); +} + +// mux the samples in annexb format, +// H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 324. +// 00 00 00 01 // header +// xxxxxxx // data bytes +// 00 00 01 // continue header +// xxxxxxx // data bytes. +// +// nal_unit_type specifies the type of RBSP data structure contained in +// the NAL unit as specified in Table 7-1. +// Table 7-1 - NAL unit type codes, syntax element categories, and NAL +// unit type classes. H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 83. +// 1, Coded slice of a non-IDR picture slice_layer_without_partitioning_rbsp( ) +// 2, Coded slice data partition A slice_data_partition_a_layer_rbsp( ) +// 3, Coded slice data partition B slice_data_partition_b_layer_rbsp( ) +// 4, Coded slice data partition C slice_data_partition_c_layer_rbsp( ) +// 5, Coded slice of an IDR picture slice_layer_without_partitioning_rbsp( ) +// 6, Supplemental enhancement information (SEI) sei_rbsp( ) +// 7, Sequence parameter set seq_parameter_set_rbsp( ) +// 8, Picture parameter set pic_parameter_set_rbsp( ) +// 9, Access unit delimiter access_unit_delimiter_rbsp( ) +// 10, End of sequence end_of_seq_rbsp( ) +// 11, End of stream end_of_stream_rbsp( ) +// 12, Filler data filler_data_rbsp( ) +// 13, Sequence parameter set extension seq_parameter_set_extension_rbsp( ) +// 14, Prefix NAL unit prefix_nal_unit_rbsp( ) +// 15, Subset sequence parameter set subset_seq_parameter_set_rbsp( ) +// 19, Coded slice of an auxiliary coded picture without partitioning slice_layer_without_partitioning_rbsp( ) +// 20, Coded slice extension slice_layer_extension_rbsp( ) +// the first ts message of apple sample: +// annexb 4B header, 2B aud(nal_unit_type:6)(0x09 0xf0) +// annexb 4B header, 19B sps(nal_unit_type:7) +// annexb 3B header, 4B pps(nal_unit_type:8) +// annexb 3B header, 12B nalu(nal_unit_type:6) +// annexb 3B header, 21B nalu(nal_unit_type:6) +// annexb 3B header, 2762B nalu(nal_unit_type:5) +// annexb 3B header, 3535B nalu(nal_unit_type:5) +// the second ts message of apple ts sample: +// annexb 4B header, 2B aud(nal_unit_type:6)(0x09 0xf0) +// annexb 3B header, 21B nalu(nal_unit_type:6) +// annexb 3B header, 379B nalu(nal_unit_type:1) +// annexb 3B header, 406B nalu(nal_unit_type:1) +static const uint8_t fresh_nalu_header[] = { 0x00, 0x00, 0x00, 0x01 }; +static const uint8_t cont_nalu_header[] = { 0x00, 0x00, 0x01 }; + +// the aud(access unit delimiter) before each frame. +// 7.3.2.4 Access unit delimiter RBSP syntax +// H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 66. +// primary_pic_type u(3), the first 3bits, primary_pic_type indicates +// that the slice_type values for all slices of the primary coded picture +// are members of the set listed in Table 7-5 for the given value of +// primary_pic_type. +// 0, slice_type 2, 7 +// 1, slice_type 0, 2, 5, 7 +// 2, slice_type 0, 1, 2, 5, 6, 7 +// 3, slice_type 4, 9 +// 4, slice_type 3, 4, 8, 9 +// 5, slice_type 2, 4, 7, 9 +// 6, slice_type 0, 2, 3, 4, 5, 7, 8, 9 +// 7, slice_type 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 +// 7.4.2.4 Access unit delimiter RBSP semantics +// H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 102. +// +// slice_type specifies the coding type of the slice according to Table 7-6. +// 0, P (P slice) +// 1, B (B slice) +// 2, I (I slice) +// 3, SP (SP slice) +// 4, SI (SI slice) +// 5, P (P slice) +// 6, B (B slice) +// 7, I (I slice) +// 8, SP (SP slice) +// 9, SI (SI slice) +// H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 105. +//static const uint8_t aud_nalu_7[] = { 0x09, 0xf0}; +static const uint8_t fresh_nalu_header_and_aud_nalu_7[] = +{ 0x00, 0x00, 0x00, 0x01, 0x09, 0xf0}; + +butil::Status TsWriter::Write(const RtmpVideoMessage& msg) { + if (msg.frame_type == FLV_VIDEO_FRAME_INFOFRAME) { + // Ignore info frame. + return butil::Status::OK(); + } + if (msg.codec != FLV_VIDEO_AVC) { + return butil::Status(EINVAL, "video_codec=%s is not AVC", + FlvVideoCodec2Str(msg.codec)); + } + RtmpAVCMessage avc_msg; + butil::Status st = avc_msg.Create(msg); + if (!st.ok()) { + return st; + } + // ignore sequence header + if (avc_msg.frame_type == FLV_VIDEO_FRAME_KEYFRAME && + avc_msg.packet_type == FLV_AVC_PACKET_SEQUENCE_HEADER) { + butil::Status st2 = _avc_seq_header.Create(avc_msg.data); + if (!st2.ok()) { + return st2; + } + _has_avc_seq_header = true; + ++_discontinuity_counter; + return butil::Status::OK(); + } + if (!_has_avc_seq_header) { + return butil::Status(EINVAL, "Lack of AVC sequence header"); + } + + const int64_t dts = static_cast(avc_msg.timestamp) * 90; + TsMessage tsmsg; + tsmsg.write_pcr = (msg.frame_type == FLV_VIDEO_FRAME_KEYFRAME); + tsmsg.dts = dts; + tsmsg.pts = dts + static_cast(avc_msg.composition_time) * 90; + tsmsg.sid = TS_PES_STREAM_ID_VIDEO_COMMON; + + // always append a aud nalu for each frame. + tsmsg.payload.append(fresh_nalu_header_and_aud_nalu_7, + arraysize(fresh_nalu_header_and_aud_nalu_7)); + butil::IOBuf nalus; + bool has_idr = false; + for (AVCNaluIterator it(&avc_msg.data, _avc_seq_header.length_size_minus1, + &_nalu_format); it != NULL; ++it) { + // ignore SPS/PPS/AUD + switch (it.nalu_type()) { + case AVC_NALU_IDR: + has_idr = true; + break; + case AVC_NALU_SPS: + case AVC_NALU_PPS: + case AVC_NALU_ACCESSUNITDELIMITER: + continue; + default: + break; + } + + // append cont nalu header before NAL units. + nalus.append(cont_nalu_header, 3); + nalus.append(*it); + } + + // when ts message(samples) contains IDR, insert sps+pps. + if (has_idr) { + bool first = true; + for (size_t i = 0; i < _avc_seq_header.sps_list.size(); ++i) { + if (first) { + tsmsg.payload.append(fresh_nalu_header, arraysize(fresh_nalu_header)); + first = false; + } else { + tsmsg.payload.append(cont_nalu_header, arraysize(cont_nalu_header)); + } + tsmsg.payload.append(_avc_seq_header.sps_list[i]); + RPC_VLOG << "Append sps[" << i << "]=" << _avc_seq_header.sps_list[i].size(); + } + for (size_t i = 0; i < _avc_seq_header.pps_list.size(); ++i) { + if (first) { + tsmsg.payload.append(fresh_nalu_header, arraysize(fresh_nalu_header)); + first = false; + } else { + tsmsg.payload.append(cont_nalu_header, arraysize(cont_nalu_header)); + } + tsmsg.payload.append(_avc_seq_header.pps_list[i]); + RPC_VLOG << "Append pps[" << i << "]=" << _avc_seq_header.pps_list[i].size(); + } + } + tsmsg.payload.append(nalus); + + TsPid vpid = TS_PID_PAT; + TsStream vs = FlvVideoCodec2TsStream(msg.codec, &vpid); + if (vs == TS_STREAM_RESERVED) { + return butil::Status(EINVAL, "Unsupported video codec=%s", + FlvVideoCodec2Str(msg.codec)); + } + return Encode(&tsmsg, vs, vpid); +} + +butil::Status +TsWriter::EncodePATPMT(TsStream vs, TsPid vpid, TsStream as, TsPid apid) { + char buf[TS_PACKET_SIZE]; + + TsPacket pat(&_tschan_group); + pat.CreateAsPAT(TS_PMT_NUMBER, TS_PID_PMT); + // set the left bytes with 0xFF. + const size_t size1 = pat.ByteSize(); + CHECK_LT(size1, TS_PACKET_SIZE); + memset(buf, 0xFF, TS_PACKET_SIZE); + if (pat.Encode(buf) != 0) { + return butil::Status(EINVAL, "Fail to encode PAT"); + } + _outbuf->append(buf, TS_PACKET_SIZE); + + TsPacket pmt(&_tschan_group); + if (pmt.CreateAsPMT(TS_PMT_NUMBER, TS_PID_PMT, vpid, vs, apid, as) != 0) { + return butil::Status(EINVAL, "Fail to CreateAsPMT"); + } + // set the left bytes with 0xFF. + const size_t size2 = pmt.ByteSize(); + CHECK_LT(size2, TS_PACKET_SIZE); + memset(buf, 0xFF, TS_PACKET_SIZE); + if (pmt.Encode(buf) != 0) { + return butil::Status(EINVAL, "Fail to encode PMT"); + } + _outbuf->append(buf, TS_PACKET_SIZE); + return butil::Status::OK(); +} + +butil::Status TsWriter::Encode(TsMessage* msg, TsStream stream, TsPid pid) { + if (stream == TS_STREAM_RESERVED) { + return butil::Status(EINVAL, "Invalid stream=%d", (int)stream); + } + // Encode the media frame to PES packets over TS. + bool add_pat_pmt = false; + if (is_audio(msg->sid)) { + if (stream != _last_audio_stream) { + _last_audio_stream = stream; + _last_audio_pid = pid; + add_pat_pmt = true; + } + } else if (is_video(msg->sid)) { + if (stream != _last_video_stream) { + _last_video_stream = stream; + _last_video_pid = pid; + add_pat_pmt = true; + } + } else { + return butil::Status(EINVAL, "Unknown stream_id=%d", (int)msg->sid); + } + if (!_encoded_pat_pmt) { + _encoded_pat_pmt = true; + add_pat_pmt = true; + } + if (add_pat_pmt) { + butil::Status st = EncodePATPMT(_last_video_stream, _last_video_pid, + _last_audio_stream, _last_audio_pid); + if (!st.ok()) { + return st; + } + } + return EncodePES(msg, stream, pid, + (_last_video_stream == TS_STREAM_RESERVED)); +} + +butil::Status TsWriter::EncodePES(TsMessage* msg, TsStream sid, TsPid pid, + bool pure_audio) { + if (msg->payload.empty()) { + return butil::Status::OK(); + } + if (sid != TS_STREAM_VIDEO_H264 && + sid != TS_STREAM_AUDIO_MP3 && + sid != TS_STREAM_AUDIO_AAC) { + LOG(WARNING) << "Ignore unknown stream_id=" << sid; + return butil::Status::OK(); + } + + TsChannel* channel = _tschan_group.get(pid); + if (channel == NULL) { + return butil::Status(EINVAL, "Fail to get channel on pid=%d", (int)pid); + } + + bool first_msg = true; + while (!msg->payload.empty()) { + TsPacket pkt(&_tschan_group); + if (first_msg) { + first_msg = false; + bool write_pcr = msg->write_pcr; + // for pure audio, always write pcr. + // TODO: maybe only need to write at begin and end of ts. + if (pure_audio && is_audio(msg->sid)) { + write_pcr = true; + } + + // it's ok to set pcr equals to dts, + int64_t pcr = write_pcr ? msg->dts : -1; + + // TODO: FIXME: figure out why use discontinuity of msg + pkt.CreateAsPESFirst(pid, msg->sid, channel->continuity_counter++, + msg->is_discontinuity, pcr, msg->dts, + msg->pts, msg->payload.size()); + } else { + pkt.CreateAsPESContinue(pid, channel->continuity_counter++); + } + + char buf[TS_PACKET_SIZE]; + + // set the left bytes with 0xFF. + size_t pkt_size = pkt.ByteSize(); + CHECK_LT(pkt_size, TS_PACKET_SIZE); + + size_t left = std::min(msg->payload.size(), TS_PACKET_SIZE - pkt_size); + const size_t nb_stuffings = TS_PACKET_SIZE - pkt_size - left; + if (nb_stuffings > 0) { + // set all bytes to stuffings. + memset(buf, 0xFF, TS_PACKET_SIZE); + + pkt.AddPadding(nb_stuffings); + + pkt_size = pkt.ByteSize(); // size changed, recalculate. + CHECK_LT(pkt_size, TS_PACKET_SIZE); + + left = std::min(msg->payload.size(), TS_PACKET_SIZE - pkt_size); + if (TS_PACKET_SIZE != pkt_size + left) { + LOG(ERROR) << "pkt_size=" << pkt_size << " left=" << left + << " stuffing=" << nb_stuffings << " payload=" + << msg->payload.size(); + } + } + msg->payload.cutn(buf + pkt_size, left); + if (pkt.Encode(buf) != 0) { + return butil::Status(EINVAL, "Fail to encode PES"); + } + _outbuf->append(buf, TS_PACKET_SIZE); + } + return butil::Status::OK(); +} + +} // namespace brpc diff --git a/src/brpc/ts.h b/src/brpc/ts.h new file mode 100644 index 0000000..fe18e2f --- /dev/null +++ b/src/brpc/ts.h @@ -0,0 +1,1262 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + + +// [Modified from the code in SRS2 (src/kernel/srs_kernel_ts.cpp)] +// The MIT License (MIT) +// Copyright (c) 2013-2015 SRS(ossrs) +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef BRPC_TS_H +#define BRPC_TS_H + +#include +#include +#include +#include +#include "butil/iobuf.h" +#include "brpc/rtmp.h" + + +namespace brpc { + +class TsAdaptationField; +class TsPayload; +class TsPacket; +class TsChannelGroup; + +// The pid of ts packet (13 bits) +typedef int16_t TsPid; + +// the transport_scrambling_control of ts packet, +// Table 2-4 - Scrambling control values, hls-mpeg-ts-iso13818-1.pdf, page 38 +enum TsScrambled { + TS_SCRAMBLED_DISABLED = 0x00, // Not scrambled + TS_SCRAMBLED_USERDEFINED1 = 0x01, // User-defined + TS_SCRAMBLED_USERDEFINED2 = 0x02, // ^ + TS_SCRAMBLED_USERDEFINED3 = 0x03, // ^ +}; + +// the adaptation_field_control of ts packet, +// Table 2-5 - Adaptation field control values, hls-mpeg-ts-iso13818-1.pdf, page 38 +enum TsAdaptationFieldType { + TS_AF_RESERVED = 0x00, // Reserved for future use by ISO/IEC + TS_AF_PAYLOAD_ONLY = 0x01, // No adaptation_field, payload only + TS_AF_ADAPTATION_ONLY = 0x02, // Adaptation_field only, no payload + TS_AF_BOTH = 0x03, // Adaptation_field followed by payload +}; + +// Table 2-29 - Stream type assignments +enum TsStream { + TS_STREAM_RESERVED = 0x00, // ITU-T | ISO/IEC Reserved + // 0x01: ISO/IEC 11172 Video + // 0x02: ITU-T Rec. H.262 | ISO/IEC 13818-2 Video or ISO/IEC 11172-2 + // constrained parameter video stream + // 0x03: ISO/IEC 11172 Audio + TS_STREAM_AUDIO_MP3 = 0x04, // ISO/IEC 13818-3 Audio + // 0x05: ITU-T Rec. H.222.0 | ISO/IEC 13818-1 private_sections + // 0x06: ITU-T Rec. H.222.0 | ISO/IEC 13818-1 PES packets containing + // private data + // 0x07: ISO/IEC 13522 MHEG + // 0x08: ITU-T Rec. H.222.0 | ISO/IEC 13818-1 Annex A DSM-CC + // 0x09: ITU-T Rec. H.222.1 + // 0x0A: ISO/IEC 13818-6 type A + // 0x0B: ISO/IEC 13818-6 type B + // 0x0C: ISO/IEC 13818-6 type C + // 0x0D: ISO/IEC 13818-6 type D + // 0x0E: ITU-T Rec. H.222.0 | ISO/IEC 13818-1 auxiliary + TS_STREAM_AUDIO_AAC = 0x0F, // ISO/IEC 13818-7 Audio with ADTS transport syntax + TS_STREAM_VIDEO_MPEG4 = 0x10, // ISO/IEC 14496-2 Visual + TS_STREAM_AUDIO_MPEG4 = 0x11, // ISO/IEC 14496-3 Audio with the LATM transport + // syntax as defined in ISO/IEC 14496-3 / AMD 1 + // 0x12: ISO/IEC 14496-1 SL-packetized stream or FlexMux stream carried in + // PES packets + // 0x13: ISO/IEC 14496-1 SL-packetized stream or FlexMux stream carried in + // ISO/IEC14496_sections. + // 0x14: ISO/IEC 13818-6 Synchronized Download Protocol + // 0x15-0x7F: ITU-T Rec. H.222.0 | ISO/IEC 13818-1 Reserved + TS_STREAM_VIDEO_H264 = 0x1B, + // 0x80-0xFF: User Private + TS_STREAM_AUDIO_AC3 = 0x81, + TS_STREAM_AUDIO_DTS = 0x8A, +}; +const char* TsStream2Str(TsStream stream); + +// the stream_id of PES payload of ts packet. +// Table 2-18 - Stream_id assignments, hls-mpeg-ts-iso13818-1.pdf, page 52. +enum TsPESStreamId { + TS_PES_STREAM_ID_PROGRAM_STREAM_MAP = 0xbc, // 0b10111100 + TS_PES_STREAM_ID_PRIVATE_STREAM1 = 0xbd, // 0b10111101 + TS_PES_STREAM_ID_PADDING_STREAM = 0xbe, // 0b10111110 + TS_PES_STREAM_ID_PRIVATE_STREAM2 = 0xbf, // 0b10111111 + // 110x xxxx: ISO/IEC 13818-3 or ISO/IEC 11172-3 or ISO/IEC 13818-7 or + // ISO/IEC 14496-3 audio stream number x xxxx + TS_PES_STREAM_ID_AUDIO_COMMON = 0xc0, // 0b11000000, + // 1110 xxxx: ITU-T Rec. H.262 | ISO/IEC 13818-2 or ISO/IEC 11172-2 or + // ISO/IEC 14496-2 video stream number xxxx + TS_PES_STREAM_ID_VIDEO_COMMON = 0xe0, // 0b11100000 + TS_PES_STREAM_ID_ECM_STREAM = 0xf0, // 0b11110000 + TS_PES_STREAM_ID_EMM_STREAM = 0xf1, // 0b11110001 + TS_PES_STREAM_ID_DSMC_STREAM = 0xf2, // 0b11110010 + TS_PES_STREAM_ID_13522_STREAM = 0xf3, // 0b11110011 + TS_PES_STREAM_ID_H2221TYPE_A = 0xf4, // 0b11110100 + TS_PES_STREAM_ID_H2221TYPE_B = 0xf5, // 0b11110101 + TS_PES_STREAM_ID_H2221TYPE_C = 0xf6, // 0b11110110 + TS_PES_STREAM_ID_H2221TYPE_D = 0xf7, // 0b11110111 + TS_PES_STREAM_ID_H2221TYPE_E = 0xf8, // 0b11111000 + TS_PES_STREAM_ID_ANCILLARY_STREAM = 0xf9, // 0b11111001 + TS_PES_STREAM_ID_SL_PACKETIZED_STREAM = 0xfa, // 0b11111010 + TS_PES_STREAM_ID_FLEX_MUX_STREAM = 0xfb, // 0b11111011 + // 1111 1100 ... 1111 1110 : reserved data stream + TS_PES_STREAM_ID_PROGRAM_STREAM_DIRECTORY = 0xff, // 0b11111111 +}; +static const TsPESStreamId TS_PES_STREAM_ID_UNKNOWN = (TsPESStreamId)0; + +// 2.4.4.4 Table_id assignments, hls-mpeg-ts-iso13818-1.pdf, page 62 +// The table_id field identifies the contents of a Transport Stream PSI +// section as shown in Table 2-26. +enum TsPsiId { + TS_PSI_ID_PAS = 0x00, // program_association_section + TS_PSI_ID_CAS = 0x01, // conditional_access_section (CA_section) + TS_PSI_ID_PMS = 0x02, // TS_program_map_section + TS_PSI_ID_DS = 0x03, // TS_description_section + TS_PSI_ID_SDS = 0x04, // ISO_IEC_14496_scene_description_section + TS_PSI_ID_ODS = 0x05, // ISO_IEC_14496_object_descriptor_section + TS_PSI_ID_ISO138181_START = 0x06, // ITU-T Rec. H.222.0 | ISO/IEC 13818-1 reserved + TS_PSI_ID_ISO138181_END = 0x37, // ^ + TS_PSI_ID_ISO138186_START = 0x38, // Defined in ISO/IEC 13818-6 + TS_PSI_ID_ISO138186_END = 0x3F, // ^ + TS_PSI_ID_USER_START = 0x40, // User private + TS_PSI_ID_USER_END = 0xFE, // ^ + TS_PSI_ID_FORBIDDEN = 0xFF, // forbidden +}; + +// 7.1 Profiles, aac-iso-13818-7.pdf, page 40 +enum AACProfile { + AAC_PROFILE_MAIN = 0, + AAC_PROFILE_LC = 1, + AAC_PROFILE_SSR = 2, +}; +static const AACProfile AAC_PROFILE_UNKNOWN = (AACProfile)4; +AACProfile AACObjectType2Profile(AACObjectType object_type); + +struct TsChannel { + uint8_t continuity_counter; + TsChannel() : continuity_counter(0) {} +}; + +// Map pids to TsChannels +class TsChannelGroup { +public: + TsChannelGroup(); + ~TsChannelGroup(); + TsChannel* get(TsPid pid); + TsChannel* set(TsPid pid); + +private: + std::map _pids; +}; + +// The packet in ts stream. +// 2.4.3.2 Transport Stream packet layer, hls-mpeg-ts-iso13818-1.pdf, page 36 +// Transport Stream packets shall be 188 bytes long. +class TsPacket { +public: + TsPacket(TsChannelGroup* c/*owned by TsWriter*/); + ~TsPacket(); + + void CreateAsPAT(int16_t pmt_number, TsPid pmt_pid); + int CreateAsPMT(int16_t pmt_number, TsPid pmt_pid, + TsPid vpid, TsStream vs, + TsPid apid, TsStream as); + void CreateAsPESFirst(TsPid pid, TsPESStreamId sid, + uint8_t continuity_counter, bool discontinuity, + int64_t pcr, int64_t dts, int64_t pts, int size); + void CreateAsPESContinue(TsPid pid, uint8_t continuity_counter); + + // Returns the encoded size. + size_t ByteSize() const; + + // Encode this TsPacket into `data' which shall be as long as ByteSize(). + // Returns 0 on success, -1 otherwise. + int Encode(void* data) const; + + // Make ByteSize() bigger for `num_stuffings' bytes. + void AddPadding(size_t num_stuffings); + + TsChannelGroup* channel_group() const { return _tschan_group; } + + bool payload_unit_start_indicator() const + { return _payload_unit_start_indicator; } + + TsPid pid() const { return _pid; } + + bool has_adaptation_field() const { return _adaptation_field; } + const TsAdaptationField& adaptation_field() const { return *_adaptation_field; } + TsAdaptationField* mutable_adaptation_field() { + if (_adaptation_field) { + return _adaptation_field; + } + return CreateAdaptationField(); + } + + bool has_payload() const { return _payload; } + const TsPayload& payload() const { return *_payload; } + +private: + void Reset(); + TsAdaptationField* CreateAdaptationField(); + + // Set to true after any CreateAsXXX was called. Set to true in Reset + bool _modified; + + // When set to '1' it indicates that at least 1 uncorrectable bit error + // exists in the associated Transport Stream packet. This bit may be set + // to '1' by entities external to the transport layer. When set to '1' + // this bit shall not be reset to '0' unless the bit value(s) in error + // have been corrected. + int8_t _transport_error_indicator; // 1 bit + + // A flag which has normative meaning for Transport Stream packets that + // carry PES packets (refer to 2.4.3.6) or PSI data (refer to 2.4.4). + // - When the payload of the Transport Stream packet contains PES packet + // data, this flag has the following significance: a '1' indicates that + // the payload of this Transport Stream packet will commence(start) with + // the first byte of a PES packet and a '0' indicates no PES packet shall + // start in this Transport Stream packet. If this flag is set to '1', + // then one and only one PES packet starts in this Transport Stream + // packet. This also applies to private streams of stream_type 6 (refer + // to Table 2-29). + // - When the payload of the Transport Stream packet contains PSI data, + // this flag has the following significance: if the Transport Stream + // packet carries the first byte of a PSI section, this flag shall be '1', + // indicating that the first byte of the payload of this Transport Stream + // packet carries the pointer_field. If the Transport Stream packet does + // not carry the first byte of a PSI section, this flag shall be '0', + // indicating that there is no pointer_field in the payload. Refer to + // 2.4.4.1 and 2.4.4.2. This also applies to private streams of stream_type + // 5 (refer to Table 2-29). + // - For null packets this flag shall be set to '0'. + // - The meaning of this bit for Transport Stream packets carrying only + // private data is not defined in this Specification. + int8_t _payload_unit_start_indicator; // 1 bit + + // When set to '1' it indicates that the associated packet is of greater + // priority than other packets having the same PID which do not have the + // bit set to '1'. The transport mechanism can use this to prioritize its + // data within an elementary stream. Depending on the application the + // transport_priority field may be coded regardless of the PID or within + // one PID only. This field may be changed by channel specific encoders or + // decoders. + int8_t _transport_priority; // 1 bit + + // Indicate the type of the data stored in the packet payload. + // 0x0000: reserved for the Program Association Table (see Table 2-25). + // 0x0001: reserved for the Conditional Access Table (see Table 2-27). + // 0x0002 - 0x000F: reserved. + // 0x1FFF: reserved for null packets (see Table 2-3). + TsPid _pid; // 13 bits + + // Indicate the scrambling mode of the Transport Stream packet payload. + // The Transport Stream packet header, and the adaptation field when + // present, shall not be scrambled. In the case of a null packet the + // value of the transport_scrambling_control field shall be set to '00' + // (see Table 2-4). + TsScrambled _transport_scrambling_control; // 2 bits + + // Indicate whether this Transport Stream packet header is followed by an + // adaptation field and/or payload (see Table 2-5). + // ITU-T Rec. H.222.0 | ISO/IEC 13818-1 decoders shall discard Transport + // Stream packets with this flag set to '00'. In the case of a null packet + // this flag shall be set to '01'. + TsAdaptationFieldType _adaptation_field_control; // 2 bits + + // A field incrementing with each Transport Stream packet with the + // same PID. The continuity_counter wraps around to 0 after its maximum + // value. The continuity_counter shall not be incremented when the + // adaptation_field_control of the packet equals '00'(reseverd) or '10' + // (adaptation field only). + // In Transport Streams, duplicate packets may be sent as two, and only + // two, consecutive Transport Stream packets of the same PID. The + // duplicate packets shall have the same continuity_counter value as the + // original packet and the adaptation_field_control field shall be equal + // to '01'(payload only) or '11'(both). In duplicate packets each byte + // of the original packet shall be duplicated, with the exception that in + // the program clock reference fields, if present, a valid value shall be + // encoded. + // The continuity_counter in a particular Transport Stream packet is + // continuous when it differs by a positive value of one from the + // continuity_counter value in the previous Transport Stream packet of the + // same PID, or when either of the nonincrementing conditions + // (adaptation_field_control set to '00' or '10', or duplicate packets as + // described above) are met. + // The continuity counter may be discontinuous when the + // discontinuity_indicator is set to '1' (refer to 2.4.3.4). In the case + // of a null packet the value of the continuity_counter is undefined. + uint8_t _continuity_counter; // 4 bits + + // Optional. + // TODO: Is it worthwhile to new? + TsAdaptationField* _adaptation_field; + TsPayload* _payload; + + TsChannelGroup* _tschan_group; +}; + +// The adaptation field of ts packet. 2.4.3.5 Semantic definition of fields in +// adaptation field, hls-mpeg-ts-iso13818-1.pdf, page 39. +// Table 2-6 - Transport Stream adaptation field, hls-mpeg-ts-iso13818-1.pdf, +// page 40 +class TsAdaptationField { +public: + TsAdaptationField(); + ~TsAdaptationField(); + + // Returns the encoded size. + size_t ByteSize() const; + + // Encode into `data' according to `adaptation_field_control'. + // Returns 0 on success, -1 otherwise. + int Encode(void* data, TsAdaptationFieldType adaptation_field_control) const; + +public: + // The number of bytes in the adaptation_field immediately following the + // adaptation_field_length. The value 0 is for inserting a single stuffing + // byte in a Transport Stream packet. + // When the adaptation_field_control value is '11', this field shall be in + // the range 0 to 182. + // When the adaptation_field_control value is '10', this field shall be 183. + // For Transport Stream packets carrying PES packets, stuffing is needed + // when there is insufficient PES packet data to completely fill the + // Transport Stream packet payload bytes. Stuffing is accomplished by + // defining an adaptation field longer than the sum of the lengths of the + // data elements in it, so that the payload bytes remaining after the + // adaptation field exactly accommodates the available PES packet data. + // The extra space in the adaptation field is filled with stuffing bytes. + // This is the only method of stuffing allowed for Transport Stream packets + // carrying PES packets. For Transport Stream packets carrying PSI, an + // alternative stuffing method is described in 2.4.4. + uint8_t adaptation_field_length() const { return ByteSize() - 1; } + + // When set to '1' indicates that the discontinuity state is true for the + // current Transport Stream packet. + // When set to '0' or is not present, the discontinuity state is false. + // The discontinuity indicator is used to indicate two types of + // discontinuities, system time-base discontinuities and continuity_counter + // discontinuities. + // A system time-base discontinuity is indicated by the use of the + // discontinuity_indicator in Transport Stream packets of a PID designated + // as a PCR_PID (refer to 2.4.4.9). When the discontinuity state is true + // for a Transport Stream packet of a PID designated as a PCR_PID, the next + // PCR in a Transport Stream packet with that same PID represents a sample + // of a new system time clock for the associated program. The system + // time-base discontinuity point is defined to be the instant in time when + // the first byte of a packet containing a PCR of a new system time-base + // arrives at the input of the T-STD. + // The discontinuity_indicator shall be set to '1' in the packet in which + // the system time-base discontinuity occurs. The discontinuity_indicator + // bit may also be set to '1' in Transport Stream packets of the same + // PCR_PID prior to the packet which contains the new system time-base PCR. + // In this case, once the discontinuity_indicator has been set to '1', it + // shall continue to be set to '1' in all Transport Stream packets of the + // same PCR_PID up to and including the Transport Stream packet which + // contains the first PCR of the new system time-base. After the occurrence + // of a system time-base discontinuity, no fewer than two PCRs for the new + // system time-base shall be received before another system time-base + // discontinuity can occur. Further, except when trick mode status is true, + // data from no more than two system time-bases shall be present in the + // set of T-STD buffers for one program at any time. + // Prior to the occurrence of a system time-base discontinuity, the first + // byte of a Transport Stream packet which contains a PTS or DTS which + // refers to the new system time-base shall not arrive at the input of + // the T-STD. After the occurrence of a system time-base discontinuity, + // the first byte of a Transport Stream packet which contains a PTS or DTS + // which refers to the previous system time-base shall not arrive at the + // input of the T-STD. + // A continuity_counter discontinuity is indicated by the use of the + // discontinuity_indicator in any Transport Stream packet. When the + // discontinuity state is true in any Transport Stream packet of a PID + // not designated as a PCR_PID, the continuity_counter in that packet may + // be discontinuous with respect to the previous Transport Stream packet + // of the same PID. When the discontinuity state is true in a Transport + // Stream packet of a PID that is designated as a PCR_PID, the + // continuity_counter may only be discontinuous in the packet in which + // a system time-base discontinuity occurs. A continuity counter + // discontinuity point occurs when the discontinuity state is true in a + // Transport Stream packet and the continuity_counter in the same packet + // is discontinuous with respect to the previous Transport Stream packet + // of the same PID. A continuity counter discontinuity point shall occur + // at most one time from the initiation of the discontinuity state until + // the conclusion of the discontinuity state. Furthermore, for all PIDs + // that are not designated as PCR_PIDs, when the discontinuity_indicator + // is set to '1' in a packet of a specific PID, the discontinuity_indicator + // may be set to '1' in the next Transport Stream packet of that same PID, + // but shall not be set to '1' in three consecutive Transport Stream packet + // of that same PID. + // For the purpose of this clause, an elementary stream access point is + // defined as follows: + // Video - The first byte of a video sequence header. + // Audio - The first byte of an audio frame. + // After a continuity counter discontinuity in a Transport packet which is + // designated as containing elementary stream data, the first byte of + // elementary stream data in a Transport Stream packet of the same PID + // shall be the first byte of an elementary stream access point or in the + // case of video, the first byte of an elementary stream access point or + // a sequence_end_code followed by an access point. Each Transport Stream + // packet which contains elementary stream data with a PID not designated + // as a PCR_PID, and in which a continuity counter discontinuity point + // occurs, and in which a PTS or DTS occurs, shall arrive at the input of + // the T-STD after the system time-base discontinuity for the associated + // program occurs. In the case where the discontinuity state is true, if + // two consecutive Transport Stream packets of the same PID occur which + // have the same continuity_counter value and have adaptation_field_control + // values set to '01' or '11', the second packet may be discarded. A + // Transport Stream shall not be constructed in such a way that discarding + // such a packet will cause the loss of PES packet payload data or PSI data. + // After the occurrence of a discontinuity_indicator set to '1' in a + // Transport Stream packet which contains PSI information, a single + // discontinuity in the version_number of PSI sections may occur. At the + // occurrence of such a discontinuity, a version of the + // TS_program_map_sections of the appropriate program shall be sent with + // section_length == 13 and the current_next_indicator == 1, such that + // there are no program_descriptors and no elementary streams described. + // This shall then be followed by a version of the TS_program_map_section + // for each affected program with the version_number incremented by one + // and the current_next_indicator == 1, containing a complete program + // definition. This indicates a version change in PSI data. + int8_t discontinuity_indicator; // 1 bit + + // Indicate that the current Transport Stream packet, and possibly + // subsequent Transport Stream packets with the same PID, contain some + // information to aid random access at this point. + // Specifically, when the bit is set to '1', the next PES packet to start + // in the payload of Transport Stream packets with the current PID shall + // contain the first byte of a video sequence header if the PES stream + // type (refer to Table 2-29) is 1 or 2, or shall contain the first byte + // of an audio frame if the PES stream type is 3 or 4. In addition, in + // the case of video, a presentation timestamp shall be present in the + // PES packet containing the first picture following the sequence header. + // In the case of audio, the presentation timestamp shall be present in + // the PES packet containing the first byte of the audio frame. In the + // PCR_PID the random_access_indicator may only be set to '1' in Transport + // Stream packet containing the PCR fields. + int8_t random_access_indicator; // 1 bit + + // Indicate among packets with the same PID, the priority of the elementary + // stream data carried within the payload of this Transport Stream packet. + // A '1' indicates that the payload has a higher priority than the payloads + // of other Transport Stream packets. In the case of video, this field may + // be set to '1' only if the payload contains one or more bytes from an + // intra-coded slice. + // A value of '0' indicates that the payload has the same priority as all + // other packets which do not have this bit set to '1'. + int8_t elementary_stream_priority_indicator; // 1 bit + + // '1' indicates that the adaptation_field contains a PCR field coded in + // two parts. + // '0' indicates that the adaptation field does not contain any PCR field. + int8_t PCR_flag; // 1 bit + + // '1' indicates that the adaptation_field contains an OPCR field coded + // in two parts. + // '0' indicates that the adaptation field does not contain any OPCR field. + int8_t OPCR_flag; // 1 bit + + // '1' indicates that a splice_countdown field shall be present in the + // associated adaptation field, specifying the occurrence of a splicing + // point. + // '0' indicates that a splice_countdown field is not present in the + // adaptation field. + int8_t splicing_point_flag; // 1 bit + + // '1' indicates that the adaptation field contains one or more + // private_data bytes. + // '0' indicates the adaptation field does not contain any private_data + // bytes. + int8_t transport_private_data_flag; // 1 bit + + // '1' indicates the presence of an adaptation field extension. + // '0' indicates that an adaptation field extension is not present in + // the adaptation field. + int8_t adaptation_field_extension_flag; // 1 bit + + // The program_clock_reference(PCR) is a 42-bit field coded in two parts. + // The first part, program_clock_reference_base, is a 33-bit field whose + // value is given by PCR_base(i), as given in equation 2-2. The second part, + // program_clock_reference_extension, is a 9-bit field whose value is + // given by PCR_ext(i), as given in equation 2-3. The PCR indicates the + // intended time of arrival of the byte containing the last bit of the + // program_clock_reference_base at the input of the system target decoder. + int64_t program_clock_reference_base; // 33 bits + static const int8_t const1_value0 = 0x3F; // 6 bits reserved + int16_t program_clock_reference_extension; // 9 bits + + // The optional original program reference (OPCR) is a 42-bit field coded + // in two parts. These two parts, the base and the extension, are coded + // identically to the two corresponding parts of the PCR field. The presence + // of the OPCR is indicated by the OPCR_flag. The OPCR field shall be coded + // only in Transport Stream packets in which the PCR field is present. + // OPCRs are permitted in both single program and multiple program Transport + // Streams. OPCR assists in the reconstruction of a single program Transport + // Stream from another Transport Stream. When reconstructing the original + // single program Transport Stream, the OPCR may be copied to the PCR field. + // The resulting PCR value is valid only if the original single program + // Transport Stream is reconstructed exactly in its entirety. This would + // include at least any PSI and private data packets which were present + // in the original Transport Stream and would possibly require other + // private arrangements. It also means that the OPCR must be an identical + // copy of its associated PCR in the original single program Transport + // Stream. + int64_t original_program_clock_reference_base; // 33 bits + static const int8_t const1_value2 = 0x3F; // 6 bits reserved + int16_t original_program_clock_reference_extension; // 9 bits + + // A positive value specifies the remaining number of Transport Stream + // packets, of the same PID, following the associated Transport Stream + // packet until a splicing point is reached. Duplicate Transport Stream + // packets and Transport Stream packets which only contain adaptation + // fields are excluded. The splicing point is located immediately after + // the last byte of the Transport Stream packet in which the associated + // splice_countdown field reaches zero. In the Transport Stream packet + // where the splice_countdown reaches zero, the last data byte of the + // Transport Stream packet payload shall be the last byte of a coded + // audio frame or a coded picture. In the case of video, the corresponding + // access unit may or may not be terminated by a sequence_end_code. + // Transport Stream packets with the same PID, which follow, may contain + // data from a different elementary stream of the same type. + // The payload of the next Transport Stream packet of the same PID + // (duplicate packets and packets without payload being excluded) shall + // commence with the first byte of a PES packet. In the case of audio, the + // PES packet payload shall commence with an access point. In the case of + // video, the PES packet payload shall commence with an access point, or + // with a sequence_end_code, followed by an access point. Thus, the + // previous coded audio frame or coded picture aligns with the packet + // boundary, or is padded to make this so. Subsequent to the splicing + // point, the countdown field may also be present. When the splice_countdown + // is a negative number whose value is minus n(-n), it indicates that + // the associated Transport Stream packet is the n-th packet following + // the splicing point (duplicate packets and packets without payload + // being excluded). + // For the purposes of this subclause, an access point is defined as follows: + // Video - The first byte of a video_sequence_header. + // Audio - The first byte of an audio frame. + int8_t splice_countdown; // 8 bits + + // The transport_private_data_length is an 8-bit field specifying the + // number of private_data bytes immediately following the transport + // private_data_length field. The number of private_data bytes shall + // not be such that private data extends beyond the adaptation field. + uint8_t transport_private_data_length; // 8 bits + char* transport_private_data; // [transport_private_data_length] bytes + + // The adaptation_field_extension_length is an 8-bit field. It indicates + // the number of bytes of the extended adaptation field data immediately + // following this field, including reserved bytes if present. + uint8_t adaptation_field_extension_length; // 8 bits + + // '1' indicates the presence of the ltw_offset field. + int8_t ltw_flag; // 1 bit + + // '1' indicates the presence of the piecewise_rate field. + int8_t piecewise_rate_flag; // 1 bit + + // When set to '1' indicates that the splice_type and DTS_next_AU fields + // are present. A value of '0' indicates that neither splice_type nor + // DTS_next_AU fields are present. This field shall not be set to '1' in + // Transport Stream packets in which the splicing_point_flag is not set + // to '1'. Once it is set to '1' in a Transport Stream packet in which + // the splice_countdown is positive, it shall be set to '1' in all the + // subsequent Transport Stream packets of the same PID that have the + // splicing_point_flag set to '1', until the packet in which the + // splice_countdown reaches zero (including this packet). When this flag + // is set, if the elementary stream carried in this PID is an audio stream, + // the splice_type field shall be set to '0000'. If the elementary stream + // carried in this PID is a video stream, it shall fulfil the constraints + // indicated by the splice_type value. + int8_t seamless_splice_flag; // 1 bit + static const int8_t const1_value1 = 0x1F; // reserved 5bits + + // '1' indicates that the value of the ltw_offset shall be valid. + // '0' indicates that the value in the ltw_offset field is undefined. + int8_t ltw_valid_flag; // 1 bit + + // A 15-bit field, the value of which is defined only if the ltw_valid + // flag has a value of '1'. When defined, the legal time window offset + // is in units of (300/fs) seconds, where fs is the system clock frequency + // of the program that this PID belongs to, and fulfils: + // offset = t1(i) - t(i) + // ltw_offset = offset//1 + // where i is the index of the first byte of this Transport Stream packet, + // offset is the value encoded in this field, t(i) is the arrival time of + // byte i in the T-STD, and t1(i) is the upper bound in time of a time + // interval called the Legal Time Window which is associated with this + // Transport Stream packet. + int16_t ltw_offset; // 15 bits + + // The meaning of this 22-bit field is only defined when both the ltw_flag + // and the ltw_valid_flag are set to '1'. When defined, it is a positive + // integer specifying a hypothetical bitrate R which is used to define + // the end times of the Legal Time Windows of Transport Stream packets + // of the same PID that follow this packet but do not include the + // legal_time_window_offset field. + int32_t piecewise_rate; // 22 bits + + // This is a 4-bit field. From the first occurrence of this field onwards, + // it shall have the same value in all the subsequent Transport Stream + // packets of the same PID in which it is present, until the packet in + // which the splice_countdown reaches zero (including this packet). If + // the elementary stream carried in that PID is an audio stream, this + // field shall have the value '0000'. If the elementary stream carried + // in that PID is a video stream, this field indicates the conditions + // that shall be respected by this elementary stream for splicing purposes. + // These conditions are defined as a function of profile, level and + // splice_type in Table 2-7 through Table 2-16. + int8_t splice_type; // 4 bits + + // This is a 33-bit field, coded in three parts. In the case of continuous + // and periodic decoding through this splicing point it indicates the + // decoding time of the first access unit following the splicing point. + // This decoding time is expressed in the time base which is valid in the + // Transport Stream packet in which the splice_countdown reaches zero. + // From the first occurrence of this field onwards, it shall have the same + // value in all the subsequent Transport Stream packets of the same PID + // in which it is present, until the packet in which the splice_countdown + // reaches zero (including this packet). + int8_t DTS_next_AU0; // 3 bits + int8_t marker_bit0; // 1 bit + int16_t DTS_next_AU1; // 15 bits + int8_t marker_bit1; // 1 bit + int16_t DTS_next_AU2; // 15 bits + int8_t marker_bit2; // 1 bit + + // This is a fixed 8-bit value equal to '1111 1111' that can be inserted + // by the encoder. It is discarded by the decoder. + int nb_af_ext_reserved; + + // This is a fixed 8-bit value equal to '1111 1111' that can be inserted + // by the encoder. It is discarded by the decoder. + int nb_af_reserved; +}; + +// the payload of ts packet, can be PES or PSI payload. +class TsPayload { +public: + explicit TsPayload(const TsPacket* p); + virtual ~TsPayload(); + virtual size_t ByteSize() const = 0; + virtual int Encode(void* data) const = 0; + const TsPacket* packet() const { return _packet; } +private: + const TsPacket* _packet; +}; + +// the PES payload of ts packet. +// 2.4.3.6 PES packet, hls-mpeg-ts-iso13818-1.pdf, page 49 +class TsPayloadPES : public TsPayload { +public: + explicit TsPayloadPES(const TsPacket* p); + virtual ~TsPayloadPES(); + virtual size_t ByteSize() const; + virtual int Encode(void* data) const; +private: + void encode_33bits_dts_pts(char** data, uint8_t fb, int64_t v) const; + // Specify the total number of bytes occupied by the optional fields and + // any stuffing bytes contained in this PES packet header. The presence + // of optional fields is indicated in the byte that precedes the + // PES_header_data_length field. + mutable int16_t _PES_header_data_length; // 8 bits + +public: + // In Program Streams, the stream_id specifies the type and number of the + // elementary stream as defined by the stream_id Table 2-18. In Transport + // Streams, the stream_id may be set to any valid value which correctly + // describes the elementary stream type as defined in Table 2-18. In + // Transport Streams, the elementary stream type is specified in the + // Program Specific Information as specified in 2.4.4. + TsPESStreamId stream_id; // 8 bits + + // Specify the number of bytes in the PES packet following the last byte + // of the field. A value of 0 indicates that the PES packet length is + // neither specified nor bounded and is allowed only in PES packets whose + // payload consists of bytes from a video elementary stream contained in + // Transport Stream packets. + uint16_t PES_packet_length; // 16 bits + + static const int8_t const2bits = 0x02; // 2 bits const '10' + + // Indicate the scrambling mode of the PES packet payload. When scrambling + // is performed at the PES level, the PES packet header, including the + // optional fields when present, shall not be scrambled (see Table 2-19). + int8_t PES_scrambling_control; // 2 bits + + // This is a 1-bit field indicating the priority of the payload in this PES + // packet. A '1' indicates a higher priority of the payload of the PES + // packet payload than a PES packet payload with this field set to '0'. + // A multiplexor can use the PES_priority bit to prioritize its data within + // an elementary stream. This field shall not be changed by the transport + // mechanism. + int8_t PES_priority; // 1 bit + + // When set to a value of '1' it indicates that the PES packet header is + // immediately followed by the video start code or audio syncword indicated + // in the data_stream_alignment_descriptor in 2.6.10 if this descriptor is + // present. If set to a value of '1' and the descriptor is not present, + // alignment as indicated in alignment_type '01' in Table 2-47 and Table + // 2-48 is required. When set to a value of '0' it is not defined whether + // any such alignment occurs or not. + int8_t data_alignment_indicator; // 1 bit + + // When set to '1' it indicates that the material of the associated PES + // packet payload is protected by copyright. When set to '0' it is not + // defined whether the material is protected by copyright. A copyright + // descriptor described in 2.6.24 is associated with the elementary + // stream which contains this PES packet and the copyright flag is set + // to '1' if the descriptor applies to the material contained in this + // PES packet. + int8_t copyright; // 1 bit + + // When set to '1' the contents of the associated PES packet payload is + // an original. When set to '0' it indicates that the contents of the + // associated PES packet payload is a copy. + int8_t original_or_copy; // 1 bit + + // When the PTS_DTS_flags field is set to '10', the PTS fields shall be + // present in the PES packet header. When the PTS_DTS_flags field is set + // to '11', both the PTS fields and DTS fields shall be present in the + // PES packet header. When the PTS_DTS_flags field is set to '00' no PTS + // or DTS fields shall be present in the PES packet header. The value + // '01' is forbidden. + int8_t PTS_DTS_flags; // 2 bits + + // When set to '1' indicates that ESCR base and extension fields are + // present in the PES packet header. When set to '0' it indicates that + // no ESCR fields are present. + int8_t ESCR_flag; // 1 bit + + // When set to '1' indicates that the ES_rate field is present in the PES + // packet header. When set to '0' it indicates that no ES_rate field is + // present. + int8_t ES_rate_flag; // 1 bit + + // When set to '1' it indicates the presence of an 8-bit trick mode field. + // When set to '0' it indicates that this field is not present. + int8_t DSM_trick_mode_flag; // 1 bit + + // when set to '1' indicates the presence of the additional_copy_info field. + // When set to '0' it indicates that this field is not present. + int8_t additional_copy_info_flag; // 1 bit + + // when set to '1' indicates that a CRC field is present in the PES packet. + // When set to '0' it indicates that this field is not present. + int8_t PES_CRC_flag; // 1 bit + + // when set to '1' indicates that an extension field exists in this PES + // packet header. + // When set to '0' it indicates that this field is not present. + int8_t PES_extension_flag; // 1 bit + + // Presentation times shall be related to decoding times as follows: + // The PTS is a 33-bit number coded in three separate fields. It indicates + // the time of presentation, tp n (k), in the system target decoder of a + // presentation unit k of elementary stream n. The value of PTS is + // specified in units of the period of the system clock frequency divided + // by 300 (yielding 90 kHz). The presentation time is derived from the PTS + // according to equation 2-11 below. Refer to 2.7.4 for constraints on the + // frequency of coding presentation timestamps. + // ===========1B + // 4bits const + // 3bits PTS [32..30] + // 1bit const '1' + // ===========2B + // 15bits PTS [29..15] + // 1bit const '1' + // ===========2B + // 15bits PTS [14..0] + // 1bit const '1' + int64_t pts; // 33 bits + + // The DTS is a 33-bit number coded in three separate fields. It indicates + // the decoding time, td n (j), in the system target decoder of an access + // unit j of elementary stream n. The value of DTS is specified in units + // of the period of the system clock frequency divided by 300 + // (yielding 90 kHz). + // ===========1B + // 4bits const + // 3bits DTS [32..30] + // 1bit const '1' + // ===========2B + // 15bits DTS [29..15] + // 1bit const '1' + // ===========2B + // 15bits DTS [14..0] + // 1bit const '1' + int64_t dts; // 33 bits + + // The elementary stream clock reference is a 42-bit field coded in two + // parts. The first part, ESCR_base, is a 33-bit field whose value is given + // by ESCR_base(i), as given in equation 2-14. The second part, ESCR_ext, + // is a 9-bit field whose value is given by ESCR_ext(i), as given in + // equation 2-15. The ESCR field indicates the intended time of arrival + // of the byte containing the last bit of the ESCR_base at the input of + // the PES-STD for PES streams (refer to 2.5.2.4). + // 2bits reserved + // 3bits ESCR_base[32..30] + // 1bit const '1' + // 15bits ESCR_base[29..15] + // 1bit const '1' + // 15bits ESCR_base[14..0] + // 1bit const '1' + // 9bits ESCR_extension + // 1bit const '1' + int64_t ESCR_base; // 33 bits + int16_t ESCR_extension; // 9 bits + + // The ES_rate field is a 22-bit unsigned integer specifying the rate at + // which the system target decoder receives bytes of the PES packet in + // the case of a PES stream. The ES_rate is valid in the PES packet in + // which it is included and in subsequent PES packets of the same PES + // stream until a new ES_rate field is encountered. The value of the + // ES_rate is measured in units of 50 bytes/second. The value 0 is + // forbidden. The value of the ES_rate is used to define the time of + // arrival of bytes at the input of a P-STD for PES streams defined in + // 2.5.2.4. The value encoded in the ES_rate field may vary from + // PES_packet to PES_packet. + // 1bit const '1' + // 22bits ES_rate + // 1bit const '1' + int32_t ES_rate; // 22 bits + + // Indicates which trick mode is applied to the associated video stream. + // In cases of other types of elementary streams, the meanings of this + // field and those defined by the following five bits are undefined. For + // the definition of trick_mode status, refer to the trick mode section + // of 2.4.2.3. + int8_t trick_mode_control; // 3 bits + int8_t trick_mode_value; // 5 bits + + // 1bit const '1' + + // This 7-bit field contains private data relating to copyright information. + int8_t additional_copy_info; //7bits + + // Contain the CRC value that yields a zero output of the 16 registers + // in the decoder similar to the one defined in Annex A + int16_t previous_PES_packet_CRC; // 16 bits + + // when set to '1' indicates that the PES packet header contains private + // data. + // When set to a value of '0' it indicates that private data is not present + // in the PES header. + int8_t PES_private_data_flag; // 1 bit + + // when set to '1' indicates that an ISO/IEC 11172-1 pack header or a + // Program Stream pack header is stored in this PES packet header. If this + // field is in a PES packet that is contained in a Program Stream, then + // this field shall be set to '0'. In a Transport Stream, when set to the + // value '0' it indicates that no pack header is present in the PES header. + int8_t pack_header_field_flag; // 1 bit + + // When set to '1' indicates that the program_packet_sequence_counter, + // MPEG1_MPEG2_identifier, and original_stuff_length fields are present + // in this PES packet. When set to a value of '0' it indicates that these + // fields are not present in the PES header. + int8_t program_packet_sequence_counter_flag; // 1 bit + + // When set to '1' indicates that the P-STD_buffer_scale and + // P-STD_buffer_size are present in the PES packet header. When set to a + // value of '0' it indicates that these fields are not present in the PES + // header. + int8_t P_STD_buffer_flag; // 1 bit + + static const int8_t const1_value0 = 0x07; // 3 bits reserved + + // When set to '1' indicates the presence of the PES_extension_field_length + // field and associated fields. When set to a value of '0' this indicates + // that the PES_extension_field_length field and any associated fields are + // not present. + int8_t PES_extension_flag_2; // 1 bit + + // Contain private data. This data, combined with the fields before and + // after, shall not emulate the packet_start_code_prefix (0x000001). + char* PES_private_data; // 128 bits + + // Indicates the length in bytes, of the pack_header_field(). + uint8_t pack_field_length; // 8 bits + char* pack_field; //[pack_field_length] bytes + + // 1bit const '1' + + // The program_packet_sequence_counter field is a 7-bit field. It is an + // optional counter that increments with each successive PES packet from a + // Program Stream or from an ISO/IEC 11172-1 Stream or the PES packets + // associated with a single program definition in a Transport Stream, + // providing functionality similar to a continuity counter (refer to + // 2.4.3.2). This allows an application to retrieve the original PES + // packet sequence of a Program Stream or the original packet sequence of + // the original ISO/IEC 11172-1 stream. The counter will wrap around to 0 + // after its maximum value. Repetition of PES packets shall not occur. + // Consequently, no two consecutive PES packets in the program multiplex + // shall have identical program_packet_sequence_counter values. + int8_t program_packet_sequence_counter; // 7 bits + + // 1bit const '1' + + // When set to '1' indicates that this PES packet carries information from + // an ISO/IEC 11172-1 stream. When set to '0' it indicates that this PES + // packet carries information from a Program Stream. + int8_t MPEG1_MPEG2_identifier; // 1 bit + + // Specify the number of stuffing bytes used in the original ITU-T + // Rec. H.222.0 | ISO/IEC 13818-1 PES packet header or in the original + // ISO/IEC 11172-1 packet header. + int8_t original_stuff_length; // 6 bits + + // 2bits const '01' + + // The P-STD_buffer_scale is a 1-bit field, the meaning of which is only + // defined if this PES packet is contained in a Program Stream. It + // indicates the scaling factor used to interpret the subsequent + // P-STD_buffer_size field. If the preceding stream_id indicates an audio + // stream, P-STD_buffer_scale shall have the value '0'. If the preceding + // stream_id indicates a video stream, P-STD_buffer_scale shall have the + // value '1'. For all other stream types, the value may be either '1' + // or '0'. + int8_t P_STD_buffer_scale; // 1 bit + + // The P-STD_buffer_size is a 13-bit unsigned integer, the meaning of + // which is only defined if this PES packet is contained in a Program + // Stream. It defines the size of the input buffer, BS n , in the P-STD. + // If P-STD_buffer_scale has the value '0', then the P-STD_buffer_size + // measures the buffer size in units of 128 bytes. If P-STD_buffer_scale + // has the value '1', then the P-STD_buffer_size measures the buffer size + // in units of 1024 bytes. + int16_t P_STD_buffer_size; // 13 bits + + // 1bit const '1' + + // Specify the length in bytes, of the data following this field in the + // PES extension field up to and including any reserved bytes. + uint8_t PES_extension_field_length; // 7 bits + char* PES_extension_field; //[PES_extension_field_length] bytes + + // A fixed 8-bit value equal to '1111 1111' that can be inserted by the + // encoder, for example to meet the requirements of the channel. + // It is discarded by the decoder. No more than 32 stuffing bytes shall + // be present in one PES packet header. + int nb_stuffings; + + // PES_packet_data_bytes shall be contiguous bytes of data from the + // elementary stream indicated by the packet's stream_id or PID. When the + // elementary stream data conforms to ITU-T Rec. H.262 | ISO/IEC 13818-2 + // or ISO/IEC 13818-3, the PES_packet_data_bytes shall be byte aligned to + // the bytes of this Recommendation | International Standard. The + // byte-order of the elementary stream shall be preserved. The number + // of PES_packet_data_bytes, N, is specified by the PES_packet_length + // field. N shall be equal to the value indicated in the PES_packet_length + // minus the number of bytes between the last byte of the PES_packet_length + // field and the first PES_packet_data_byte. + // In the case of a private_stream_1, private_stream_2, ECM_stream, or + // EMM_stream, the contents of the PES_packet_data_byte field are user + // definable and will not be specified by ITU-T | ISO/IEC in the future. + int nb_bytes; + + // A fixed 8-bit value equal to '1111 1111'. It is discarded by the decoder. + int nb_paddings; +}; + +// the PSI payload of ts packet. +// 2.4.4 Program specific information, hls-mpeg-ts-iso13818-1.pdf, page 59 +class TsPayloadPSI : public TsPayload { +public: + explicit TsPayloadPSI(const TsPacket* p); + virtual ~TsPayloadPSI(); + virtual size_t ByteSize() const; + virtual int Encode(void* data) const; +protected: + virtual size_t PsiByteSize() const = 0; + virtual int PsiEncode(void* data) const = 0; +private: + // This is a 12-bit field, the first two bits of which shall be '00'. + // The remaining 10 bits specify the number of bytes of the section, + // starting immediately following the section_length field, and including + // the CRC. The value in this field shall not exceed 1021 (0x3FD). + mutable int16_t _section_length; //12bits + +public: + // This is an 8-bit field whose value shall be the number of bytes, + // immediately following the pointer_field until the first byte of the + // first section that is present in the payload of the Transport Stream + // packet (so a value of 0x00 in the pointer_field indicates that the + // section starts immediately after the pointer_field). When at least + // one section begins in a given Transport Stream packet, then the + // payload_unit_start_indicator (refer to 2.4.3.2) shall be set to 1 and + // the first byte of the payload of that Transport Stream packet shall + // contain the pointer. When no section begins in a given Transport Stream + // packet, then the payload_unit_start_indicator shall be set to 0 and + // no pointer shall be sent in the payload of that packet. + int8_t pointer_field; + + // A 1-bit field which shall be set to '1'. + int8_t section_syntax_indicator; //1bit + + // An 8-bit field which shall be set to 0x00 as shown in Table 2-26. + TsPsiId table_id; //8bits + + static const int8_t const0_value = 0; //1bit, must be '0' + static const int8_t const1_value = 3; //2bits, reverved value, must be '1' +}; + +// the program of PAT of PSI ts packet. +class TsPayloadPATProgram { +public: + TsPayloadPATProgram(int16_t number, TsPid pid); + ~TsPayloadPATProgram(); + size_t ByteSize() const { return 4; } + int Encode(void* data) const; + +public: + // Specify the program to which the program_map_PID is applicable. When set + // to 0x0000, then the following PID reference shall be the network PID. + // For all other cases the value of this field is user defined. This field + // shall not take any single value more than once within one version of + // the Program Association Table. + int16_t program_number; // 16bits + + static const int8_t const1_value = 7; //3 bits, reserved + + // network_PID - a 13-bit field, which is used only in conjunction with + // the value of the program_number set to 0x0000, specifies the PID of + // the Transport Stream packets which shall contain the Network + // Information Table. The value of the network_PID field is defined by + // the user, but shall only take values as specified in Table 2-3. The + // presence of the network_PID is optional. + TsPid pid; //13bits +}; + +// the PAT payload of PSI ts packet. +// 2.4.4.3 Program association Table, hls-mpeg-ts-iso13818-1.pdf, page 61 +// The Program Association Table provides the correspondence between a +// program_number and the PID value of the Transport Stream packets which +// carry the program definition. The program_number is the numeric label +// associated with a program. +class TsPayloadPAT : public TsPayloadPSI { +public: + explicit TsPayloadPAT(const TsPacket* p); + virtual ~TsPayloadPAT(); +protected: + virtual size_t PsiByteSize() const; + virtual int PsiEncode(void* data) const; + +public: + // A 16-bit field which serves as a label to identify this Transport + // Stream from any other multiplex within a network. Its value is + // defined by the user. + uint16_t transport_stream_id; //16bits + + static const int8_t const3_value = 3; //2bits, reserved + + // This 5-bit field is the version number of the whole Program Association + // Table. The version number shall be incremented by 1 modulo 32 whenever + // the definition of the Program Association Table changes. When the + // current_next_indicator is set to '1', then the version_number shall be + // that of the currently applicable Program Association Table. When the + // current_next_indicator is set to '0', then the version_number shall + // be that of the next applicable Program Association Table. + int8_t version_number; //5bits + + // A 1-bit indicator, which when set to '1' indicates that the Program + // Association Table sent is currently applicable. When the bit is set + // to '0', it indicates that the table sent is not yet applicable and + // shall be the next table to become valid. + int8_t current_next_indicator; //1bit + + // This 8-bit field gives the number of this section. The section_number + // of the first section in the Program Association Table shall be 0x00. + // It shall be incremented by 1 with each additional section in the + // Program Association Table. + uint8_t section_number; //8bits + + // This 8-bit field specifies the number of the last section (that is, + // the section with the highest section_number) of the complete Program + // Association Table. + uint8_t last_section_number; //8bits + + // multiple 4B program data. + std::vector programs; +}; + +// the esinfo for PMT program. +class TsPayloadPMTESInfo { +public: + TsPayloadPMTESInfo(TsStream st, TsPid epid); + ~TsPayloadPMTESInfo(); + size_t ByteSize() const; + int Encode(void* data) const; + +public: + // An 8-bit field specifying the type of program element carried within + // the packets with the PID whose value is specified by the elementary_PID. + // The values of stream_type are specified in Table 2-29. + TsStream stream_type; //8bits + + static const int8_t const1_value0 = 7; //3bits, reserved + + // Specify the PID of the Transport Stream packets which carry the + // associated program element. + TsPid elementary_PID; //13bits + + static const int8_t const1_value1 = 0xf; //4bits, reserved + + // the first two bits of which shall be '00'. The remaining 10 bits + // specify the number of bytes of the descriptors of the associated + // program element immediately following the ES_info_length field. + int16_t ES_info_length; //12bits + char* ES_info; //[ES_info_length] bytes. +}; + +// the PMT payload of PSI ts packet. +// 2.4.4.8 Program Map Table, hls-mpeg-ts-iso13818-1.pdf, page 64 +// The Program Map Table provides the mappings between program numbers and the +// program elements that comprise them. A single instance of such a mapping is +// referred to as a "program definition". The program map table is the complete +// collection of all program definitions for a Transport Stream. This table +// shall be transmitted in packets, the PID values of which are selected by +// the encoder. More than one PID value may be used, if desired. The table is +// contained in one or more sections with the following syntax. It may be +// segmented to occupy multiple sections. In each section, the section number +// field shall be set to zero. Sections are identified by the program_number +// field. +class TsPayloadPMT : public TsPayloadPSI { +public: + explicit TsPayloadPMT(const TsPacket* p); + virtual ~TsPayloadPMT(); +protected: + virtual size_t PsiByteSize() const; + virtual int PsiEncode(void* data) const; + +public: + // Specify the program to which the program_map_PID is applicable. One + // program definition shall be carried within only one + // TS_program_map_section. This implies that a program definition is never + // longer than 1016 (0x3F8). See Informative Annex C for ways to deal with + // the cases when that length is not sufficient. The program_number may be + // used as a designation for a broadcast channel, for example. By + // describing the different program elements belonging to a program, data + // from different sources (e.g. sequential events) can be concatenated + // together to form a continuous set of streams using a program_number. + // For examples of applications refer to Annex C. + uint16_t program_number; //16bits + + static const int8_t const1_value0 = 3; //2bits, reserved + + // This 5-bit field is the version number of the TS_program_map_section. + // The version number shall be incremented by 1 modulo 32 when a change + // in the information carried within the section occurs. Version number + // refers to the definition of a single program, and therefore to a + // single section. When the current_next_indicator is set to '1', then + // the version_number shall be that of the currently applicable + // TS_program_map_section. When the current_next_indicator is set to '0', + // then the version_number shall be that of the next applicable + // TS_program_map_section. + int8_t version_number; //5bits + + // A 1-bit field, which when set to '1' indicates that the + // TS_program_map_section sent is currently applicable. When the bit is set + // to '0', it indicates that the TS_program_map_section sent is not yet + // applicable and shall be the next TS_program_map_section to become valid. + int8_t current_next_indicator; //1bit + + // The value of this 8-bit field shall be 0x00. + uint8_t section_number; //8bits + + // The value of this 8-bit field shall be 0x00. + uint8_t last_section_number; //8bits + + static const int8_t const1_value1 = 7; //3bits, reserved + + // A 13-bit field indicating the PID of the Transport Stream packets which + // shall contain the PCR fields valid for the program specified by + // program_number. If no PCR is associated with a program definition for + // private streams, then this field shall take the value of 0x1FFF. Refer + // to the semantic definition of PCR in 2.4.3.5 and Table 2-3 for + // restrictions on the choice of PCR_PID value. + TsPid PCR_PID; //13bits + + static const int8_t const1_value2 = 0xf; //4bits, reserved + + // A 12-bit field, the first two bits of which shall be '00'. The + // remaining 10 bits specify the number of bytes of the descriptors + // immediately following the program_info_length field. + uint16_t program_info_length; //12bits + char* program_info_desc; //[program_info_length]bytes + + // array of TSPMTESInfo. + std::vector infos; +}; + +// Convert Rtmp audio/video messages into ts packets. +class TsWriter { +public: + explicit TsWriter(butil::IOBuf* outbuf); + ~TsWriter(); + + // Append a video/audio message into the output buffer. + butil::Status Write(const RtmpVideoMessage&); + butil::Status Write(const RtmpAudioMessage&); + + int64_t discontinuity_counter() const { return _discontinuity_counter; } + void add_pat_pmt_on_next_write() { _encoded_pat_pmt = false; } + +private: + struct TsMessage; + + butil::Status Encode(TsMessage* msg, TsStream stream, TsPid pid); + butil::Status EncodePATPMT(TsStream vs, TsPid vpid, TsStream as, TsPid apid); + butil::Status EncodePES(TsMessage* msg, TsStream sid, TsPid pid, bool pure_audio); + + butil::IOBuf* _outbuf; + AVCNaluFormat _nalu_format; + bool _has_avc_seq_header; + bool _has_aac_seq_header; + bool _encoded_pat_pmt; + AVCDecoderConfigurationRecord _avc_seq_header; + AudioSpecificConfig _aac_seq_header; + TsStream _last_video_stream; + TsPid _last_video_pid; + TsStream _last_audio_stream; + TsPid _last_audio_pid; + int64_t _discontinuity_counter; + TsChannelGroup _tschan_group; +}; + +} // namespace brpc + + +#endif // BRPC_TS_H diff --git a/src/brpc/uri.cpp b/src/brpc/uri.cpp new file mode 100644 index 0000000..5391368 --- /dev/null +++ b/src/brpc/uri.cpp @@ -0,0 +1,511 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // isalnum + +#include + +#include "brpc/log.h" +#include "brpc/details/http_parser.h" // http_parser_parse_url +#include "brpc/uri.h" // URI + + +namespace brpc { + +URI::URI() + : _port(-1) + , _query_was_modified(false) + , _initialized_query_map(false) +{} + +URI::~URI() { +} + +void URI::Clear() { + _st.reset(); + _port = -1; + _query_was_modified = false; + _initialized_query_map = false; + _host.clear(); + _path.clear(); + _user_info.clear(); + _fragment.clear(); + _scheme.clear(); + _query.clear(); + _query_map.clear(); +} + +void URI::Swap(URI &rhs) { + _st.swap(rhs._st); + std::swap(_port, rhs._port); + std::swap(_query_was_modified, rhs._query_was_modified); + std::swap(_initialized_query_map, rhs._initialized_query_map); + _host.swap(rhs._host); + _path.swap(rhs._path); + _user_info.swap(rhs._user_info); + _fragment.swap(rhs._fragment); + _scheme.swap(rhs._scheme); + _query.swap(rhs._query); + _query_map.swap(rhs._query_map); +} + +// Parse queries, which is case-sensitive +static void ParseQueries(URI::QueryMap& query_map, const std::string &query) { + query_map.clear(); + if (query.empty()) { + return; + } + for (QuerySplitter sp(query.c_str()); sp; ++sp) { + if (!sp.key().empty()) { + std::string key(sp.key().data(), sp.key().size()); + std::string value(sp.value().data(), sp.value().size()); + query_map[key] = value; + } + } +} + +inline const char* SplitHostAndPort(const char* host_begin, + const char* host_end, + int* port) { + uint64_t port_raw = 0; + uint64_t multiply = 1; + for (const char* q = host_end - 1; q > host_begin; --q) { + if (*q >= '0' && *q <= '9') { + port_raw += (*q - '0') * multiply; + multiply *= 10; + } else if (*q == ':') { + *port = static_cast(port_raw); + return q; + } else { + break; + } + } + *port = -1; + return host_end; +} + +// valid characters in URL +// https://datatracker.ietf.org/doc/html/rfc3986#section-2.1 +// https://datatracker.ietf.org/doc/html/rfc3986#section-2.3 +// https://datatracker.ietf.org/doc/html/rfc3986#section-2.4 +// space is not allowed by rfc3986, but allowed by brpc +static bool is_valid_char(char c) { + static const std::unordered_set other_valid_char = { + ':', '/', '?', '#', '[', ']', '@', '!', '$', '&', + '\'', '(', ')', '*', '+', ',', ';', '=', '-', '.', + '_', '~', '%', ' ' + }; + + return (isalnum(c) || other_valid_char.count(c)); +} + +static bool is_all_spaces(const char* p) { + for (; *p == ' '; ++p) {} + return !*p; +} + +const char URI_PARSE_CONTINUE = 0; +const char URI_PARSE_CHECK = 1; +const char URI_PARSE_BREAK = 2; +static const char g_url_parsing_fast_action_map_raw[] = { + 0/*-128*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0/*-118*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0/*-108*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0/*-98*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0/*-88*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0/*-78*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0/*-68*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0/*-58*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0/*-48*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0/*-38*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0/*-28*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0/*-18*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0/*-8*/, 0, 0, 0, 0, 0, 0, 0, URI_PARSE_BREAK/*\0*/, 0, + 0/*2*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0/*12*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0/*22*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, + URI_PARSE_CHECK/* */, 0, 0, URI_PARSE_BREAK/*#*/, 0, 0, 0, 0, 0, 0, + 0/*42*/, 0, 0, 0, 0, URI_PARSE_BREAK/*/*/, 0, 0, 0, 0, + 0/*52*/, 0, 0, 0, 0, 0, URI_PARSE_CHECK/*:*/, 0, 0, 0, + 0/*62*/, URI_PARSE_BREAK/*?*/, URI_PARSE_CHECK/*@*/, 0, 0, 0, 0, 0, 0, 0, + 0/*72*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0/*82*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0/*92*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0/*102*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0/*112*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0/*122*/, 0, 0, 0, 0, 0 +}; +static const char* const g_url_parsing_fast_action_map = + g_url_parsing_fast_action_map_raw + 128; + +// This implementation is faster than http_parser_parse_url() and allows +// ignoring of scheme("http://") +int URI::SetHttpURL(const char* url) { + Clear(); + + const char* p = url; + // skip heading blanks + if (*p == ' ') { + for (++p; *p == ' '; ++p) {} + } + const char* start = p; + // Find end of host, locate scheme and user_info during the searching + bool need_scheme = true; + bool need_user_info = true; + for (; true; ++p) { + const char action = g_url_parsing_fast_action_map[(int)*p]; + if (action == URI_PARSE_CONTINUE) { + continue; + } + if (action == URI_PARSE_BREAK) { + break; + } + if (!is_valid_char(*p)) { + _st.set_error(EINVAL, "invalid character in url"); + return -1; + } else if (*p == ':') { + if (p[1] == '/' && p[2] == '/' && need_scheme) { + need_scheme = false; + _scheme.assign(start, p - start); + p += 2; + start = p + 1; + } + } else if (*p == '@') { + if (need_user_info) { + need_user_info = false; + _user_info.assign(start, p - start); + start = p + 1; + } + } else if (*p == ' ') { + if (!is_all_spaces(p + 1)) { + _st.set_error(EINVAL, "Invalid space in url"); + return -1; + } + break; + } + } + const char* host_end = SplitHostAndPort(start, p, &_port); + _host.assign(start, host_end - start); + if (*p == '/') { + start = p; //slash pointed by p is counted into _path + ++p; + for (; *p && *p != '?' && *p != '#'; ++p) { + if (*p == ' ') { + if (!is_all_spaces(p + 1)) { + _st.set_error(EINVAL, "Invalid space in path"); + return -1; + } + break; + } + } + _path.assign(start, p - start); + } + if (*p == '?') { + start = ++p; + for (; *p && *p != '#'; ++p) { + if (*p == ' ') { + if (!is_all_spaces(p + 1)) { + _st.set_error(EINVAL, "Invalid space in query"); + return -1; + } + break; + } + } + _query.assign(start, p - start); + } + if (*p == '#') { + start = ++p; + for (; *p; ++p) { + if (*p == ' ') { + if (!is_all_spaces(p + 1)) { + _st.set_error(EINVAL, "Invalid space in fragment"); + return -1; + } + break; + } + } + _fragment.assign(start, p - start); + } + return 0; +} + +int ParseURL(const char* url, + std::string* scheme_out, std::string* host_out, int* port_out) { + const char* p = url; + // skip heading blanks + if (*p == ' ') { + for (++p; *p == ' '; ++p) {} + } + const char* start = p; + // Find end of host, locate scheme and user_info during the searching + bool need_scheme = true; + bool need_user_info = true; + for (; true; ++p) { + const char action = g_url_parsing_fast_action_map[(int)*p]; + if (action == URI_PARSE_CONTINUE) { + continue; + } + if (action == URI_PARSE_BREAK) { + break; + } + if (*p == ':') { + if (p[1] == '/' && p[2] == '/' && need_scheme) { + need_scheme = false; + if (scheme_out) { + scheme_out->assign(start, p - start); + } + p += 2; + start = p + 1; + } + } else if (*p == '@') { + if (need_user_info) { + need_user_info = false; + start = p + 1; + } + } else if (*p == ' ') { + if (!is_all_spaces(p + 1)) { + LOG(ERROR) << "Invalid space in url=`" << url << '\''; + return -1; + } + break; + } + } + int port = -1; + const char* host_end = SplitHostAndPort(start, p, &port); + if (host_out) { + host_out->assign(start, host_end - start); + } + if (port_out) { + *port_out = port; + } + return 0; +} + +void URI::Print(std::ostream& os) const { + if (!_host.empty()) { + if (!_scheme.empty()) { + os << _scheme << "://"; + } else { + os << "http://"; + } + // user_info is passed by Authorization + os << _host; + if (_port >= 0) { + os << ':' << _port; + } + } + PrintWithoutHost(os); +} + +void URI::PrintWithoutHost(std::ostream& os) const { + if (_path.empty()) { + // According to rfc2616#section-5.1.2, the absolute path + // cannot be empty; if none is present in the original URI, it MUST + // be given as "/" (the server root). + os << '/'; + } else { + os << _path; + } + if (_initialized_query_map && _query_was_modified) { + bool is_first = true; + for (QueryIterator it = QueryBegin(); it != QueryEnd(); ++it) { + if (is_first) { + is_first = false; + os << '?'; + } else { + os << '&'; + } + os << it->first; + if (!it->second.empty()) { + os << '=' << it->second; + } + } + } else if (!_query.empty()) { + os << '?' << _query; + } + if (!_fragment.empty()) { + os << '#' << _fragment; + } +} + +void URI::InitializeQueryMap() const { + ParseQueries(_query_map, _query); + _query_was_modified = false; + _initialized_query_map = true; +} + +void URI::AppendQueryString(std::string* query, bool append_question_mark) const { + if (_query_map.empty()) { + return; + } + if (append_question_mark) { + query->push_back('?'); + } + QueryIterator it = QueryBegin(); + query->append(it->first); + if (!it->second.empty()) { + query->push_back('='); + query->append(it->second); + } + ++it; + for (; it != QueryEnd(); ++it) { + query->push_back('&'); + query->append(it->first); + if (!it->second.empty()) { + query->push_back('='); + query->append(it->second); + } + } +} + +void URI::GenerateH2Path(std::string* h2_path) const { + h2_path->reserve(_path.size() + _query.size() + _fragment.size() + 3); + h2_path->clear(); + if (_path.empty()) { + h2_path->push_back('/'); + } else { + h2_path->append(_path); + } + if (_initialized_query_map && _query_was_modified) { + AppendQueryString(h2_path, true); + } else if (!_query.empty()) { + h2_path->push_back('?'); + h2_path->append(_query); + } + if (!_fragment.empty()) { + h2_path->push_back('#'); + h2_path->append(_fragment); + } +} + +void URI::SetHostAndPort(const std::string& host) { + const char* const host_begin = host.c_str(); + const char* host_end = + SplitHostAndPort(host_begin, host_begin + host.size(), &_port); + _host.assign(host_begin, host_end - host_begin); +} + +void URI::SetH2Path(const char* h2_path) { + _path.clear(); + _query.clear(); + _fragment.clear(); + _query_was_modified = false; + _initialized_query_map = false; + _query_map.clear(); + + const char* p = h2_path; + const char* start = p; + for (; *p && *p != '?' && *p != '#'; ++p) {} + _path.assign(start, p - start); + if (*p == '?') { + start = ++p; + for (; *p && *p != '#'; ++p) {} + _query.assign(start, p - start); + } + if (*p == '#') { + start = ++p; + for (; *p; ++p) {} + _fragment.assign(start, p - start); + } +} + +QueryRemover::QueryRemover(const std::string* str) + : _query(str) + , _qs(str->data(), str->data() + str->size()) + , _iterated_len(0) + , _removed_current_key_value(false) + , _ever_removed(false) { +} + +QueryRemover& QueryRemover::operator++() { + if (!_qs) { + return *this; + } + if (!_ever_removed) { + _qs.operator++(); + return *this; + } + if (!_removed_current_key_value) { + _modified_query.resize(_iterated_len); + if (!_modified_query.empty()) { + _modified_query.push_back('&'); + _iterated_len += 1; + } + _modified_query.append(key_and_value().data(), key_and_value().length()); + _iterated_len += key_and_value().length(); + } else { + _removed_current_key_value = false; + } + _qs.operator++(); + return *this; +} + +QueryRemover QueryRemover::operator++(int) { + QueryRemover tmp = *this; + operator++(); + return tmp; +} + +void QueryRemover::remove_current_key_and_value() { + _removed_current_key_value = true; + if (!_ever_removed) { + _ever_removed = true; + size_t offset = key().data() - _query->data(); + size_t len = offset - ((offset > 0 && (*_query)[offset - 1] == '&')? 1: 0); + _modified_query.append(_query->data(), len); + _iterated_len += len; + } + return; +} + +std::string QueryRemover::modified_query() { + if (!_ever_removed) { + return *_query; + } + size_t offset = key().data() - _query->data(); + // find out where the remaining string starts + if (_removed_current_key_value) { + size_t size = key_and_value().length(); + while (offset + size < _query->size() && (*_query)[offset + size] == '&') { + // ingore unnecessary '&' + size += 1; + } + offset += size; + } + _modified_query.resize(_iterated_len); + if (offset < _query->size()) { + if (!_modified_query.empty()) { + _modified_query.push_back('&'); + } + _modified_query.append(*_query, offset, std::string::npos); + } + return _modified_query; +} + +void append_query(std::string *query_string, + const butil::StringPiece& key, + const butil::StringPiece& value) { + if (!query_string->empty() && butil::back_char(*query_string) != '?') { + query_string->push_back('&'); + } + query_string->append(key.data(), key.size()); + query_string->push_back('='); + query_string->append(value.data(), value.size()); +} + +} // namespace brpc diff --git a/src/brpc/uri.h b/src/brpc/uri.h new file mode 100644 index 0000000..f8d551c --- /dev/null +++ b/src/brpc/uri.h @@ -0,0 +1,275 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_URI_H +#define BRPC_URI_H + +#include // std::string +#include "butil/containers/flat_map.h" +#include "butil/status.h" +#include "butil/string_splitter.h" + +// To brpc developers: This is a class exposed to end-user. DON'T put impl. +// details in this header, use opaque pointers instead. + + +namespace brpc { + +// The class for URI scheme : http://en.wikipedia.org/wiki/URI_scheme +// +// foo://username:password@example.com:8042/over/there/index.dtb?type=animal&name=narwhal#nose +// \_/ \_______________/ \_________/ \__/ \___/ \_/ \______________________/ \__/ +// | | | | | | | | +// | userinfo host port | | query fragment +// | \________________________________/\_____________|____|/ \__/ \__/ +// scheme | | | | | | +// authority | | | | | +// path | | interpretable as keys +// | | +// \_______________________________________________|____|/ \____/ \_____/ +// | | | | | +// hierarchical part | | interpretable as values +// | | +// interpretable as filename | +// | +// | +// interpretable as extension +class URI { +public: + typedef butil::FlatMap QueryMap; + typedef QueryMap::const_iterator QueryIterator; + + // You can copy a URI. + URI(); + ~URI(); + + // Exchange internal fields with another URI. + void Swap(URI &rhs); + + // Reset internal fields as if they're just default-constructed. + void Clear(); + + // Decompose `url' and set into corresponding fields. + // heading and trailing spaces are allowed and skipped. + // Returns 0 on success, -1 otherwise and status() is set. + int SetHttpURL(const char* url); + int SetHttpURL(const std::string& url) { return SetHttpURL(url.c_str()); } + // syntactic sugar of SetHttpURL + void operator=(const char* url) { SetHttpURL(url); } + void operator=(const std::string& url) { SetHttpURL(url); } + + // Status of previous SetHttpURL or opreator=. + const butil::Status& status() const { return _st; } + + // Sub fields. Empty string if the field is not set. + const std::string& scheme() const { return _scheme; } + BAIDU_DEPRECATED const std::string& schema() const { return scheme(); } + const std::string& host() const { return _host; } + int port() const { return _port; } // -1 on unset. + const std::string& path() const { return _path; } + const std::string& user_info() const { return _user_info; } + const std::string& fragment() const { return _fragment; } + // NOTE: This method is not thread-safe because it may re-generate the + // query-string if SetQuery()/RemoveQuery() were successfully called. + const std::string& query() const; + // Put path?query#fragment into `h2_path' + void GenerateH2Path(std::string* h2_path) const; + + // Overwrite parts of the URL. + // NOTE: The input MUST be guaranteed to be valid. + void set_scheme(const std::string& scheme) { _scheme = scheme; } + BAIDU_DEPRECATED void set_schema(const std::string& s) { set_scheme(s); } + void set_path(const std::string& path) { _path = path; } + void set_host(const std::string& host) { _host = host; } + void set_port(int port) { _port = port; } + void SetHostAndPort(const std::string& host_and_optional_port); + // Set path/query/fragment with the input in form of "path?query#fragment" + void SetH2Path(const char* h2_path); + void SetH2Path(const std::string& path) { SetH2Path(path.c_str()); } + + // Get the value of a CASE-SENSITIVE key. + // Returns pointer to the value, NULL when the key does not exist. + const std::string* GetQuery(const char* key) const + { return get_query_map().seek(key); } + const std::string* GetQuery(const std::string& key) const + { return get_query_map().seek(key); } + + // Add key/value pair. Override existing value. + void SetQuery(const std::string& key, const std::string& value); + + // Remove value associated with `key'. + // Returns 1 on removed, 0 otherwise. + size_t RemoveQuery(const char* key); + size_t RemoveQuery(const std::string& key); + + // Get query iterators which are invalidated after calling SetQuery() + // or SetHttpURL(). + QueryIterator QueryBegin() const { return get_query_map().begin(); } + QueryIterator QueryEnd() const { return get_query_map().end(); } + // #queries + size_t QueryCount() const { return get_query_map().size(); } + + // Print this URI to the ostream. + // PrintWithoutHost only prints components including and after path. + void PrintWithoutHost(std::ostream& os) const; + void Print(std::ostream& os) const; + +private: +friend class HttpMessage; + + void InitializeQueryMap() const; + + QueryMap& get_query_map() const { + if (!_initialized_query_map) { + InitializeQueryMap(); + } + return _query_map; + } + + // Iterate _query_map and append all queries to `query' + void AppendQueryString(std::string* query, bool append_question_mark) const; + + butil::Status _st; + int _port; + mutable bool _query_was_modified; + mutable bool _initialized_query_map; + std::string _host; + std::string _path; + std::string _user_info; + std::string _fragment; + std::string _scheme; + mutable std::string _query; + mutable QueryMap _query_map; +}; + +// Parse host/port/scheme from `url' if the corresponding parameter is not NULL. +// Returns 0 on success, -1 otherwise. +int ParseURL(const char* url, std::string* scheme, std::string* host, int* port); + +inline void URI::SetQuery(const std::string& key, const std::string& value) { + get_query_map()[key] = value; + _query_was_modified = true; +} + +inline size_t URI::RemoveQuery(const char* key) { + if (get_query_map().erase(key)) { + _query_was_modified = true; + return 1; + } + return 0; +} + +inline size_t URI::RemoveQuery(const std::string& key) { + if (get_query_map().erase(key)) { + _query_was_modified = true; + return 1; + } + return 0; +} + +inline const std::string& URI::query() const { + if (_initialized_query_map && _query_was_modified) { + _query_was_modified = false; + _query.clear(); + AppendQueryString(&_query, false); + } + return _query; +} + +inline std::ostream& operator<<(std::ostream& os, const URI& uri) { + uri.Print(os); + return os; +} + +// Split query in the format of "key1=value1&key2&key3=value3" +class QuerySplitter : public butil::KeyValuePairsSplitter { +public: + inline QuerySplitter(const char* str_begin, const char* str_end) + : KeyValuePairsSplitter(str_begin, str_end, '&', '=') + {} + + inline QuerySplitter(const char* str_begin) + : KeyValuePairsSplitter(str_begin, '&', '=') + {} + + inline QuerySplitter(const butil::StringPiece &sp) + : KeyValuePairsSplitter(sp, '&', '=') + {} +}; + +// A class to remove some specific keys in a query string, +// when removal is over, call modified_query() to get modified +// query. +class QueryRemover { +public: + QueryRemover(const std::string* str); + + butil::StringPiece key() { return _qs.key();} + butil::StringPiece value() { return _qs.value(); } + butil::StringPiece key_and_value() { return _qs.key_and_value(); } + + // Move splitter forward. + QueryRemover& operator++(); + QueryRemover operator++(int); + + operator const void*() const { return _qs; } + + // After this function is called, current query will be removed from + // modified_query(), calling this function more than once has no effect. + void remove_current_key_and_value(); + + // Return the modified query string + std::string modified_query(); + +private: + const std::string* _query; + QuerySplitter _qs; + std::string _modified_query; + size_t _iterated_len; + bool _removed_current_key_value; + bool _ever_removed; +}; + +// This function can append key and value to *query_string +// in consideration of all possible format of *query_string +// for example: +// "" -> "key=value" +// "key1=value1" -> "key1=value1&key=value" +// "/some/path?" -> "/some/path?key=value" +// "/some/path?key1=value1" -> "/some/path?key1=value1&key=value" +void append_query(std::string *query_string, + const butil::StringPiece& key, + const butil::StringPiece& value); + +} // namespace brpc + + +#if __cplusplus < 201103L // < C++11 +#include // std::swap until C++11 +#else +#include // std::swap since C++11 +#endif // __cplusplus < 201103L + +namespace std { +template<> +inline void swap(brpc::URI &lhs, brpc::URI &rhs) { + lhs.Swap(rhs); +} +} // namespace std + +#endif //BRPC_URI_H diff --git a/src/brpc/versioned_ref_with_id.h b/src/brpc/versioned_ref_with_id.h new file mode 100644 index 0000000..f77d5af --- /dev/null +++ b/src/brpc/versioned_ref_with_id.h @@ -0,0 +1,628 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BRPC_VERSIONED_REF_WITH_ID_H +#define BRPC_VERSIONED_REF_WITH_ID_H + +#include +#include "butil/resource_pool.h" +#include "butil/class_name.h" +#include "butil/logging.h" +#include "bthread/bthread.h" +#include "brpc/errno.pb.h" + +namespace brpc { + +// Unique identifier of a T object. +typedef uint64_t VRefId; + +const VRefId INVALID_VREF_ID = (VRefId)-1; + +template +class VersionedRefWithId; + +template +void DereferenceVersionedRefWithId(T* r); + +template +struct VersionedRefWithIdDeleter { + void operator()(T* r) const { + DereferenceVersionedRefWithId(r); + } +}; + +template +using VersionedRefWithIdUniquePtr = + std::unique_ptr>; + +// Utility functions to combine and extract VRefId. +template +BUTIL_FORCE_INLINE VRefId MakeVRefId(uint32_t version, + butil::ResourceId slot) { + return VRefId((((uint64_t)version) << 32) | slot.value); +} + +template +BUTIL_FORCE_INLINE butil::ResourceId SlotOfVRefId(VRefId vref_id) { + return { (vref_id & 0xFFFFFFFFul) }; +} + +BUTIL_FORCE_INLINE uint32_t VersionOfVRefId(VRefId vref_id) { + return (uint32_t)(vref_id >> 32); +} + +// Utility functions to combine and extract _versioned_ref +BUTIL_FORCE_INLINE uint32_t VersionOfVRef(uint64_t vref) { + return (uint32_t)(vref >> 32); +} + +BUTIL_FORCE_INLINE int32_t NRefOfVRef(uint64_t vref) { + return (int32_t)(vref & 0xFFFFFFFFul); +} + +BUTIL_FORCE_INLINE uint64_t MakeVRef(uint32_t version, int32_t nref) { + // 1: Intended conversion to uint32_t, nref=-1 is 00000000FFFFFFFF + return (((uint64_t)version) << 32) | (uint32_t/*1*/)nref; +} + + +template +typename std::enable_if::value, Ret>::type ReturnEmpty() { + return Ret{}; +} + +template +typename std::enable_if::value, Ret>::type ReturnEmpty() {} + +// Call func_name of class_type if class_type implements func_name, +// otherwise call default function. +#define WRAPPER_OF(class_type, func_name, return_type) \ + struct func_name ## Wrapper { \ + template \ + static auto Test(int) -> decltype( \ + std::declval().func_name(std::declval()...), std::true_type()); \ + template \ + static auto Test(...) -> std::false_type; \ + \ + template \ + typename std::enable_if(0))::value, return_type>::type \ + Call(class_type* obj, Args&&... args) { \ + BAIDU_CASSERT((butil::is_result_same< \ + return_type, decltype(&T::func_name), T, Args...>::value), \ + "Params or return type mismatch"); \ + return obj->func_name(std::forward(args)...); \ + } \ + \ + template \ + typename std::enable_if(0))::value, return_type>::type \ + Call(class_type* obj, Args&&...) { \ + return ReturnEmpty(); \ + } \ + } + +#define WRAPPER_CALL(func_name, obj, ...) func_name ## Wrapper().Call(obj, ## __VA_ARGS__) + +// VersionedRefWithId is an efficient data structure, which can be find +// in O(1)-time by VRefId. +// Users shall call VersionedRefWithId::Create() to create T, +// store VRefId instead of T and call VersionedRefWithId::Address() +// to convert the identifier to an unique_ptr at each access. Whenever +// a unique_ptr is not destructed, the enclosed T will not be recycled. +// +// CRTP +// Derived classes implement 6 functions : +// 1. (required) int OnCreated(Args&&... args) : +// Will be called in Create() to initialize T init when T is created successfully. +// If initialization fails, return non-zero. VersionedRefWithId will be `SetFailed' +// and Create() returns non-zero. +// 2. (required) void BeforeRecycled() : +// Will be called in Dereference() before T is recycled. +// 3. (optional) void OnFailed(Args&&... args) : +// Will be called in SetFailed() when VersionedRefWithId is set failed successfully. +// 4. (optional) void BeforeAdditionalRefReleased() : +// Will be called in ReleaseAdditionalReference() before additional ref is released. +// 5. (optional) void AfterRevived() : +// Will be called in Revive() When VersionedRefWithId is revived. +// 6. (optional) std::string OnDescription() const : +// Will be called in description(). +// +// Example usage: +// +// class UserData : public brpc::VersionedRefWithId { +// public: +// explicit UserData(Forbidden f) +// : brpc::VersionedRefWithId(f) +// , _count(0) {} + +// void Add(int c) { +// _count.fetch_add(c, butil::memory_order_relaxed); +// } +// void Sub(int c) { +// _count.fetch_sub(c, butil::memory_order_relaxed); +// } +// private: +// friend class brpc::VersionedRefWithId; +// +// int OnCreated() { +// _count.store(1, butil::memory_order_relaxed); +// return 0; +// } +// void OnFailed(int error_code, const std::string& error_text) { +// _count.fetch_sub(1, butil::memory_order_relaxed); +// } +// void BeforeRecycled() { +// _count.store(0, butil::memory_order_relaxed); +// } +// +// butil::atomic _count; +// }; +// +// typedef brpc::VRefId UserDataId; +// const brpc::VRefId INVALID_EVENT_DATA_ID = brpc::INVALID_VREF_ID; +// typedef brpc::VersionedRefWithIdUniquePtr UserDataUniquePtr; +// +// And to call methods on UserData: +// UserDataId id; +// if (UserData::Create(&id) ! =0) { +// LOG(ERROR) << "Fail to create UserData"; +// return; +// } +// UserDataUniquePtr user_data; +// if (UserData::Address(id, &user_data) != 0) { +// LOG(ERROR) << "Fail to address UserDataId=" << id; +// return; +// } +// user_data->Add(10); +// user_data->SetFailed(); +// UserData::SetFailedById(id); +// +template +class VersionedRefWithId { +protected: + struct Forbidden {}; + +public: + explicit VersionedRefWithId(Forbidden) + // Must be even because Address() relies on evenness of version. + : _versioned_ref(0) + , _this_id(0) + , _additional_ref_status(ADDITIONAL_REF_USING) {} + + virtual ~VersionedRefWithId() = default; + DISALLOW_COPY_AND_ASSIGN(VersionedRefWithId); + + // Create a VersionedRefWithId, put the identifier into `id'. + // `args' will be passed to OnCreated() directly. + // Returns 0 on success, -1 otherwise. + template + static int Create(VRefId* id, Args&&... args); + + // Place the VersionedRefWithId associated with identifier `id' into + // unique_ptr `ptr', which will be released automatically when out + // of scope (w/o explicit std::move). User can still access `ptr' + // after calling ptr->SetFailed() before release of `ptr'. + // This function is wait-free. + // Returns 0 on success, -1 when the Socket was SetFailed(). + static int Address(VRefId id, VersionedRefWithIdUniquePtr* ptr); + + // Returns 0 on success, 1 on failed socket, -1 on recycled. + static int AddressFailedAsWell(VRefId id, VersionedRefWithIdUniquePtr* ptr); + + // Re-address current VersionedRefWithId into `ptr'. + // Always succeed even if this socket is failed. + void ReAddress(VersionedRefWithIdUniquePtr* ptr); + + // Returns signed 32-bit referenced-count. + int32_t nref() const { + return NRefOfVRef(_versioned_ref.load(butil::memory_order_relaxed)); + } + + // Mark this VersionedRefWithId or the VersionedRefWithId associated + // with `id' as failed. + // Any later Address() of the identifier shall return NULL. The + // VersionedRefWithId is NOT recycled after calling this function, + // instead it will be recycled when no one references it. Internal + // fields of the Socket are still accessible after calling this + // function. Calling SetFailed() of a VersionedRefWithId more than + // once is OK. + // T::OnFailed() will be called when SetFailed() successfully. + // This function is lock-free. + // Returns -1 when the Socket was already SetFailed(), 0 otherwise. + template + static int SetFailedById(VRefId id, Args&&... args); + + template + int SetFailed(Args&&... args); + + bool Failed() const { + return VersionOfVRef(_versioned_ref.load(butil::memory_order_relaxed)) + != VersionOfVRefId(_this_id); + } + + // Release the additional reference which added inside `Create' + // before so that `VersionedRefWithId' will be recycled automatically + // once on one is addressing it. + int ReleaseAdditionalReference(); + + VRefId id() const { return _this_id; } + + // A brief description. + std::string description() const; + +protected: +friend void DereferenceVersionedRefWithId<>(T* r); + + // Status flag used to mark that + enum AdditionalRefStatus { + // 1. Additional reference has been increased; + ADDITIONAL_REF_USING, + // 2. Additional reference is increasing; + ADDITIONAL_REF_REVIVING, + // 3. Additional reference has been decreased. + ADDITIONAL_REF_RECYCLED + }; + + AdditionalRefStatus additional_ref_status() const { + return _additional_ref_status.load(butil::memory_order_relaxed); + } + + uint64_t versioned_ref() const { + // The acquire fence pairs with release fence in Dereference to avoid + // inconsistent states to be seen by others. + return _versioned_ref.load(butil::memory_order_acquire); + } + + template + int SetFailedImpl(Args&&... args); + + // Release the reference. If no one is addressing this VersionedRefWithId, + // it will be recycled automatically and T::BeforeRecycled() will be called. + int Dereference(); + + // Increase the reference count by 1. + void AddReference() { + _versioned_ref.fetch_add(1, butil::memory_order_release); + } + + // Make this socket addressable again. + // If nref is less than `at_least_nref', VersionedRefWithId was + // abandoned during revival and cannot be revived. + void Revive(int32_t at_least_nref); + +private: + typedef butil::ResourceId resource_id_t; + + // 1. When `failed_as_well=true', returns 0 on success, + // 1 on failed socket, -1 on recycled. + // 2. When `failed_as_well=true', returns 0 on success, + // -1 when the Socket was SetFailed(). + static int AddressImpl(VRefId id, bool failed_as_well, + VersionedRefWithIdUniquePtr* ptr); + + // Callback wrapper of Derived classes. + WRAPPER_OF(T, OnFailed, void); + WRAPPER_OF(T, BeforeAdditionalRefReleased, void); + WRAPPER_OF(T, AfterRevived, void); + WRAPPER_OF(T, OnDescription, std::string); + + // unsigned 32-bit version + signed 32-bit referenced-count. + // Meaning of version: + // * Created version: no SetFailed() is called on the VersionedRefWithId yet. + // Must be same evenness with initial _versioned_ref because during lifetime + // of a VersionedRefWithId on the slot, the version is added with 1 twice. + // This is also the version encoded in VRefId. + // * Failed version: = created version + 1, SetFailed()-ed but returned. + // * Other versions: the socket is already recycled. + butil::atomic BAIDU_CACHELINE_ALIGNMENT _versioned_ref; + // The unique identifier. + VRefId _this_id; + // Indicates whether additional reference has increased, + // decreased, or is increasing. + // additional ref status: + // `Socket'、`Create': REF_USING + // `SetFailed': REF_USING -> REF_RECYCLED + // `Revive' REF_RECYCLED -> REF_REVIVING -> REF_USING + butil::atomic _additional_ref_status; +}; + +template +void DereferenceVersionedRefWithId(T* r) { + if (r) { + static_cast*>(r)->Dereference(); + } +} + +template +template +int VersionedRefWithId::Create(VRefId* id, Args&&... args) { + resource_id_t slot; + T* const t = butil::get_resource(&slot, Forbidden()); + if (t == NULL) { + LOG(FATAL) << "Fail to get_resource<" + << butil::class_name() << ">"; + return -1; + } + // nref can be non-zero due to concurrent Address(). + // _this_id will only be used in destructor/Destroy of referenced + // slots, which is safe and properly fenced. Although it's better + // to put the id into VersionedRefWithIdUniquePtr. + VersionedRefWithId* const vref_with_id = t; + vref_with_id->_this_id = MakeVRefId( + VersionOfVRef(vref_with_id->_versioned_ref.fetch_add( + 1, butil::memory_order_release)), slot); + vref_with_id->_additional_ref_status.store( + ADDITIONAL_REF_USING, butil::memory_order_relaxed); + BAIDU_CASSERT((butil::is_result_int::value), + "T::OnCreated must accept Args params and return int"); + // At last, call T::OnCreated() to initialize the T object. + if (t->OnCreated(std::forward(args)...) != 0) { + vref_with_id->SetFailed(); + // NOTE: This object may be recycled at this point, + // don't touch anything. + return -1; + } + *id = vref_with_id->_this_id; + return 0; +} + +template +int VersionedRefWithId::Address( + VRefId id, VersionedRefWithIdUniquePtr* ptr) { + return AddressImpl(id, false, ptr); +} + +template +int VersionedRefWithId::AddressFailedAsWell( + VRefId id, VersionedRefWithIdUniquePtr* ptr) { + return AddressImpl(id, true, ptr); +} + +template +int VersionedRefWithId::AddressImpl( + VRefId id, bool failed_as_well, VersionedRefWithIdUniquePtr* ptr) { + const resource_id_t slot = SlotOfVRefId(id); + T* const t = address_resource(slot); + if (__builtin_expect(t != NULL, 1)) { + // acquire fence makes sure this thread sees latest changes before + // Dereference() or Revive(). + VersionedRefWithId* const vref_with_id = t; + const uint64_t vref1 = vref_with_id->_versioned_ref.fetch_add( + 1, butil::memory_order_acquire); + const uint32_t ver1 = VersionOfVRef(vref1); + if (ver1 == VersionOfVRefId(id)) { + ptr->reset(t); + return 0; + } + if (failed_as_well && ver1 == VersionOfVRefId(id) + 1) { + ptr->reset(t); + return 1; + } + + const uint64_t vref2 = vref_with_id->_versioned_ref.fetch_sub( + 1, butil::memory_order_release); + const int32_t nref = NRefOfVRef(vref2); + if (nref > 1) { + return -1; + } else if (__builtin_expect(nref == 1, 1)) { + const uint32_t ver2 = VersionOfVRef(vref2); + if ((ver2 & 1)) { + if (ver1 == ver2 || ver1 + 1 == ver2) { + uint64_t expected_vref = vref2 - 1; + if (vref_with_id->_versioned_ref.compare_exchange_strong( + expected_vref, MakeVRef(ver2 + 1, 0), + butil::memory_order_acquire, + butil::memory_order_relaxed)) { + BAIDU_CASSERT((butil::is_result_void< + decltype(&T::BeforeRecycled), T>::value), + "T::BeforeRecycled must accept Args params" + " and return void"); + t->BeforeRecycled(); + return_resource(slot); + } + } else { + CHECK(false) << "ref-version=" << ver1 + << " unref-version=" << ver2; + } + } else { + // Addressed a free slot. + } + } else { + CHECK(false) << "Over dereferenced SocketId=" << id; + } + } + return -1; +} + +template +void VersionedRefWithId::ReAddress(VersionedRefWithIdUniquePtr* ptr) { + _versioned_ref.fetch_add(1, butil::memory_order_acquire); + ptr->reset(static_cast(this)); +} + +template +template +int VersionedRefWithId::SetFailedById(VRefId id, Args&&... args) { + VersionedRefWithIdUniquePtr ptr; + if (Address(id, &ptr) != 0) { + return -1; + } + return ptr->SetFailed(std::forward(args)...); +} + +template +template +int VersionedRefWithId::SetFailed(Args&&... args) { + return SetFailedImpl(std::forward(args)...); +} + +template +template +int VersionedRefWithId::SetFailedImpl(Args&&... args) { + const uint32_t id_ver = VersionOfVRefId(_this_id); + uint64_t vref = _versioned_ref.load(butil::memory_order_relaxed); + for (;;) { + if (VersionOfVRef(vref) != id_ver) { + return -1; + } + // Try to set version=id_ver+1 (to make later address() return NULL), + // retry on fail. + if (_versioned_ref.compare_exchange_strong( + vref, MakeVRef(id_ver + 1, NRefOfVRef(vref)), + butil::memory_order_release, + butil::memory_order_relaxed)) { + // Call T::OnFailed() to notify the failure of T. + WRAPPER_CALL(OnFailed, static_cast(this), std::forward(args)...); + // Deref additionally which is added at creation so that this + // queue's reference will hit 0(recycle) when no one addresses it. + ReleaseAdditionalReference(); + // NOTE: This object may be recycled at this point, don't + // touch anything. + return 0; + } + } +} + +template +int VersionedRefWithId::ReleaseAdditionalReference() { + do { + AdditionalRefStatus expect = ADDITIONAL_REF_USING; + if (_additional_ref_status.compare_exchange_strong( + expect, ADDITIONAL_REF_RECYCLED, + butil::memory_order_relaxed, + butil::memory_order_relaxed)) { + WRAPPER_CALL(BeforeAdditionalRefReleased, static_cast(this)); + return Dereference(); + } + + if (expect == ADDITIONAL_REF_REVIVING) { + // sched_yield to wait until status is not REF_REVIVING. + sched_yield(); + } else { + return -1; // REF_RECYCLED + } + } while (true); +} + +template +int VersionedRefWithId::Dereference() { + const VRefId id = _this_id; + const uint64_t vref = _versioned_ref.fetch_sub( + 1, butil::memory_order_release); + const int32_t nref = NRefOfVRef(vref); + if (nref > 1) { + return 0; + } + if (__builtin_expect(nref == 1, 1)) { + const uint32_t ver = VersionOfVRef(vref); + const uint32_t id_ver = VersionOfVRefId(id); + // Besides first successful SetFailed() adds 1 to version, one of + // those dereferencing nref from 1->0 adds another 1 to version. + // Notice "one of those": The wait-free Address() may make ref of a + // version-unmatched slot change from 1 to 0 for mutiple times, we + // have to use version as a guard variable to prevent returning the + // VersionedRefWithId to pool more than once. + // + // Note: `ver == id_ver' means this VersionedRefWithId has been `SetRecycle' + // before rather than `SetFailed'; `ver == ide_ver+1' means we + // had `SetFailed' this socket before. We should destroy the + // socket under both situation + if (__builtin_expect(ver == id_ver || ver == id_ver + 1, 1)) { + // sees nref:1->0, try to set version=id_ver+2,--nref. + // No retry: if version changes, the slot is already returned by + // another one who sees nref:1->0 concurrently; if nref changes, + // which must be non-zero, the slot will be returned when + // nref changes from 1->0 again. + // Example: + // SetFailed(): --nref, sees nref:1->0 (1) + // try to set version=id_ver+2 (2) + // Address(): ++nref, unmatched version (3) + // --nref, sees nref:1->0 (4) + // try to set version=id_ver+2 (5) + // 1,2,3,4,5 or 1,3,4,2,5: + // SetFailed() succeeds, Address() fails at (5). + // 1,3,2,4,5: SetFailed() fails with (2), the slot will be + // returned by (5) of Address() + // 1,3,4,5,2: SetFailed() fails with (2), the slot is already + // returned by (5) of Address(). + uint64_t expected_vref = vref - 1; + if (_versioned_ref.compare_exchange_strong( + expected_vref, MakeVRef(id_ver + 2, 0), + butil::memory_order_acquire, + butil::memory_order_relaxed)) { + static_cast(this)->BeforeRecycled(); + return_resource(SlotOfVRefId(id)); + return 1; + } + return 0; + } + LOG(FATAL) << "Invalid VRefId=" << id; + return -1; + } + LOG(FATAL) << "Over dereferenced VRefId=" << id; + return -1; +} + +template +void VersionedRefWithId::Revive(int32_t at_least_nref) { + const uint32_t id_ver = VersionOfVRefId(_this_id); + uint64_t vref = _versioned_ref.load(butil::memory_order_relaxed); + _additional_ref_status.store( + ADDITIONAL_REF_REVIVING, butil::memory_order_relaxed); + while (true) { + CHECK_EQ(id_ver + 1, VersionOfVRef(vref)) << "id=" << id(); + + int32_t nref = NRefOfVRef(vref); + if (nref < at_least_nref) { + // Set the status to REF_RECYCLED since no one uses this socket + _additional_ref_status.store( + ADDITIONAL_REF_RECYCLED, butil::memory_order_relaxed); + CHECK_EQ(1, nref); + LOG(WARNING) << description() << " was abandoned during revival"; + return; + } + // +1 is the additional ref added in Create(). TODO(gejun): we should + // remove this additional nref someday. + if (_versioned_ref.compare_exchange_weak( + vref, MakeVRef(id_ver, nref + 1/*note*/), + butil::memory_order_release, + butil::memory_order_relaxed)) { + // Set the status to REF_USING since we add additional ref again + _additional_ref_status.store( + ADDITIONAL_REF_USING, butil::memory_order_relaxed); + WRAPPER_CALL(AfterRevived, static_cast(this)); + return; + } + } +} + +template +std::string VersionedRefWithId::description() const { + std::string result; + result.reserve(128); + butil::string_appendf(&result, "%s{id=%" PRIu64 " ", butil::class_name(), id()); + result.append(WRAPPER_CALL( + OnDescription, const_cast(static_cast(this)))); + butil::string_appendf(&result, "} (%p)", this); + return result; +} + +} // namespace brpc + +#endif // BRPC_VERSIONED_REF_WITH_ID_H diff --git a/src/bthread/bthread.cpp b/src/bthread/bthread.cpp new file mode 100644 index 0000000..5bcda94 --- /dev/null +++ b/src/bthread/bthread.cpp @@ -0,0 +1,677 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Tue Jul 10 17:40:58 CST 2012 + +#include +#include +#include "butil/macros.h" // BAIDU_CASSERT +#include "butil/logging.h" +#include "butil/thread_local.h" +#include "butil/reloadable_flags.h" +#include "bthread/task_group.h" // TaskGroup +#include "bthread/task_control.h" // TaskControl +#include "bthread/timer_thread.h" +#include "bthread/list_of_abafree_id.h" +#include "bthread/bthread.h" + +namespace bthread { +extern void print_task(std::ostream& os, bthread_t tid, bool enable_trace, + bool ignore_not_matched = false); + +static bool validate_bthread_concurrency(const char*, int32_t val) { + // bthread_setconcurrency sets the flag on success path which should + // not be strictly in a validator. But it's OK for a int flag. + return bthread_setconcurrency(val) == 0; +} +static bool validate_bthread_min_concurrency(const char*, int32_t val); +static bool validate_bthread_current_tag(const char*, int32_t val); +static bool validate_bthread_concurrency_by_tag(const char*, int32_t val); + +DEFINE_int32(bthread_concurrency, 8 + BTHREAD_EPOLL_THREAD_NUM, + "Number of pthread workers"); +BUTIL_VALIDATE_GFLAG(bthread_concurrency, validate_bthread_concurrency); + +DEFINE_int32(bthread_min_concurrency, 0, + "Initial number of pthread workers which will be added on-demand." + " The laziness is disabled when this value is non-positive," + " and workers will be created eagerly according to -bthread_concurrency and bthread_setconcurrency(). "); +BUTIL_VALIDATE_GFLAG(bthread_min_concurrency, validate_bthread_min_concurrency); + +DEFINE_int32(bthread_current_tag, BTHREAD_TAG_INVALID, "Set bthread concurrency for this tag"); +BUTIL_VALIDATE_GFLAG(bthread_current_tag, validate_bthread_current_tag); + +DEFINE_int32(bthread_concurrency_by_tag, 8 + BTHREAD_EPOLL_THREAD_NUM, + "Number of pthread workers of FLAGS_bthread_current_tag"); +BUTIL_VALIDATE_GFLAG(bthread_concurrency_by_tag, validate_bthread_concurrency_by_tag); + +DEFINE_int32(bthread_parking_lot_of_each_tag, 4, "Number of parking lots of each tag"); +BUTIL_VALIDATE_GFLAG(bthread_parking_lot_of_each_tag, [](const char*, int32_t val) { + if (val < BTHREAD_MIN_PARKINGLOT) { + LOG(ERROR) << "bthread_parking_lot_of_each_tag must be greater than or equal to " + << BTHREAD_MIN_PARKINGLOT; + return false; + } + if (val > BTHREAD_MAX_PARKINGLOT) { + LOG(ERROR) << "bthread_parking_lot_of_each_tag must be less than or equal to " + << BTHREAD_MAX_PARKINGLOT; + return false; + } + return true; +}); + +static bool never_set_bthread_concurrency = true; + +BAIDU_CASSERT(sizeof(TaskControl*) == sizeof(butil::atomic), atomic_size_match); + +pthread_mutex_t g_task_control_mutex = PTHREAD_MUTEX_INITIALIZER; +// Referenced in rpc, needs to be extern. +// Notice that we can't declare the variable as atomic which +// are not constructed before main(). +TaskControl* g_task_control = NULL; + +extern BAIDU_THREAD_LOCAL TaskGroup* tls_task_group; +EXTERN_BAIDU_VOLATILE_THREAD_LOCAL(TaskGroup*, tls_task_group); +extern void (*g_worker_startfn)(); +extern void (*g_tagged_worker_startfn)(bthread_tag_t); + +inline TaskControl* get_task_control() { + return g_task_control; +} + +inline TaskControl* get_or_new_task_control() { + butil::atomic* p = (butil::atomic*)&g_task_control; + TaskControl* c = p->load(butil::memory_order_consume); + if (c != NULL) { + return c; + } + BAIDU_SCOPED_LOCK(g_task_control_mutex); + c = p->load(butil::memory_order_consume); + if (c != NULL) { + return c; + } + c = new (std::nothrow) TaskControl; + if (NULL == c) { + return NULL; + } + int concurrency = FLAGS_bthread_min_concurrency > 0 ? + FLAGS_bthread_min_concurrency : + FLAGS_bthread_concurrency; + if (c->init(concurrency) != 0) { + LOG(ERROR) << "Fail to init g_task_control"; + delete c; + return NULL; + } + p->store(c, butil::memory_order_release); + return c; +} + +#ifdef BRPC_BTHREAD_TRACER +BAIDU_THREAD_LOCAL TaskMeta* pthread_fake_meta = NULL; + +bthread_t init_for_pthread_stack_trace() { + if (NULL != pthread_fake_meta) { + return pthread_fake_meta->tid; + } + + TaskControl* c = get_task_control(); + if (NULL == c) { + LOG(ERROR) << "TaskControl has not been created, " + "please use bthread_start_xxx before call this function"; + return INVALID_BTHREAD; + } + + butil::ResourceId slot; + pthread_fake_meta = butil::get_resource(&slot); + if (BAIDU_UNLIKELY(NULL == pthread_fake_meta)) { + LOG(ERROR) << "Fail to get TaskMeta"; + return INVALID_BTHREAD; + } + + pthread_fake_meta->attr = BTHREAD_ATTR_PTHREAD; + pthread_fake_meta->tid = make_tid(*pthread_fake_meta->version_butex, slot); + // Make TaskTracer use signal trace mode for pthread. + c->_task_tracer.set_running_status(syscall(SYS_gettid), pthread_fake_meta); + + // Release the TaskMeta at exit of pthread. + butil::thread_atexit([]() { + // Similar to TaskGroup::task_runner. + bool tracing; + { + BAIDU_SCOPED_LOCK(pthread_fake_meta->version_lock); + tracing = TaskTracer::set_end_status_unsafe(pthread_fake_meta); + // If resulting version is 0, + // change it to 1 to make bthread_t never be 0. + if (0 == ++*pthread_fake_meta->version_butex) { + ++*pthread_fake_meta->version_butex; + } + } + + if (tracing) { + // Wait for tracing completion. + get_task_control()->_task_tracer.WaitForTracing(pthread_fake_meta); + } + get_task_control()->_task_tracer.set_status( + TASK_STATUS_UNKNOWN, pthread_fake_meta); + + butil::return_resource(get_slot(pthread_fake_meta->tid)); + pthread_fake_meta = NULL; + }); + + return pthread_fake_meta->tid; +} + +void stack_trace(std::ostream& os, bthread_t tid) { + TaskControl* c = get_task_control(); + if (NULL == c) { + os << "TaskControl has not been created"; + return; + } + c->stack_trace(os, tid); +} + +std::string stack_trace(bthread_t tid) { + TaskControl* c = get_task_control(); + if (NULL == c) { + return "TaskControl has not been created"; + } + return c->stack_trace(tid); +} + +#endif // BRPC_BTHREAD_TRACER + +// Print all living (started and not finished) bthreads +void print_living_tasks(std::ostream& os, bool enable_trace) { + TaskControl* c = get_task_control(); + if (NULL == c) { + os << "TaskControl has not been created"; + return; + } + auto tids = c->get_living_bthreads(); + if (tids.empty()) { + os << "No living bthreads\n"; + return; + } + for (auto tid : tids) { + print_task(os, tid, enable_trace, true); + } +} + +static int add_workers_for_each_tag(int num) { + int added = 0; + auto c = get_task_control(); + for (auto i = 0; i < num; ++i) { + added += c->add_workers(1, i % FLAGS_task_group_ntags); + } + return added; +} + +static bool validate_bthread_min_concurrency(const char*, int32_t val) { + if (val <= 0) { + return true; + } + if (val < BTHREAD_MIN_CONCURRENCY || val > FLAGS_bthread_concurrency) { + return false; + } + TaskControl* c = get_task_control(); + if (!c) { + return true; + } + BAIDU_SCOPED_LOCK(g_task_control_mutex); + int concurrency = c->concurrency(); + if (val > concurrency) { + int added = bthread::add_workers_for_each_tag(val - concurrency); + return added == (val - concurrency); + } else { + return true; + } +} + +static bool validate_bthread_current_tag(const char*, int32_t val) { + if (val == BTHREAD_TAG_INVALID) { + return true; + } else if (val < BTHREAD_TAG_DEFAULT || val >= FLAGS_task_group_ntags) { + return false; + } + BAIDU_SCOPED_LOCK(bthread::g_task_control_mutex); + auto c = get_task_control(); + if (c == NULL) { + FLAGS_bthread_concurrency_by_tag = 8 + BTHREAD_EPOLL_THREAD_NUM; + return true; + } + FLAGS_bthread_concurrency_by_tag = c->concurrency(val); + return true; +} + +static bool validate_bthread_concurrency_by_tag(const char*, int32_t val) { + return bthread_setconcurrency_by_tag(val, FLAGS_bthread_current_tag) == 0; +} + +__thread TaskGroup* tls_task_group_nosignal = NULL; + +BUTIL_FORCE_INLINE int +start_from_non_worker(bthread_t* __restrict tid, + const bthread_attr_t* __restrict attr, + void* (*fn)(void*), + void* __restrict arg) { + TaskControl* c = get_or_new_task_control(); + if (NULL == c) { + return ENOMEM; + } + bthread_tag_t tag = BTHREAD_TAG_DEFAULT; + if (attr != NULL && attr->tag != BTHREAD_TAG_INVALID) { + tag = attr->tag; + } + if (attr != NULL && (attr->flags & BTHREAD_NOSIGNAL)) { + // Remember the TaskGroup to insert NOSIGNAL tasks for 2 reasons: + // 1. NOSIGNAL is often for creating many bthreads in batch, + // inserting into the same TaskGroup maximizes the batch. + // 2. bthread_flush() needs to know which TaskGroup to flush. + TaskGroup* g = tls_task_group_nosignal; + if (NULL == g) { + g = c->choose_one_group(tag); + tls_task_group_nosignal = g; + } + return g->start_background(tid, attr, fn, arg); + } + return c->choose_one_group(tag)->start_background(tid, attr, fn, arg); +} + +// Meet one of the three conditions, can run in thread local +// attr is nullptr +// tag equal to thread local +// tag equal to BTHREAD_TAG_INVALID +BUTIL_FORCE_INLINE bool can_run_thread_local(const bthread_attr_t* __restrict attr) { + return attr == nullptr || attr->tag == tls_task_group->tag() || + attr->tag == BTHREAD_TAG_INVALID; +} + +struct TidTraits { + static const size_t BLOCK_SIZE = 63; + static const size_t MAX_ENTRIES = 65536; + static const size_t INIT_GC_SIZE = 65536; + static const bthread_t ID_INIT; + static bool exists(bthread_t id) { return bthread::TaskGroup::exists(id); } +}; +const bthread_t TidTraits::ID_INIT = INVALID_BTHREAD; + +typedef ListOfABAFreeId TidList; + +struct TidStopper { + void operator()(bthread_t id) const { bthread_stop(id); } +}; +struct TidJoiner { + void operator()(bthread_t & id) const { + bthread_join(id, NULL); + id = INVALID_BTHREAD; + } +}; + +} // namespace bthread + +extern "C" { + +int bthread_start_urgent(bthread_t* __restrict tid, + const bthread_attr_t* __restrict attr, + void * (*fn)(void*), + void* __restrict arg) { + bthread::TaskGroup* g = bthread::BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + if (g) { + // if attribute is null use thread local task group + if (bthread::can_run_thread_local(attr)) { + return bthread::TaskGroup::start_foreground(&g, tid, attr, fn, arg); + } + } + return bthread::start_from_non_worker(tid, attr, fn, arg); +} + +int bthread_start_background(bthread_t* __restrict tid, + const bthread_attr_t* __restrict attr, + void * (*fn)(void*), + void* __restrict arg) { + bthread::TaskGroup* g = bthread::BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + if (g) { + // if attribute is null use thread local task group + if (bthread::can_run_thread_local(attr)) { + return g->start_background(tid, attr, fn, arg); + } + } + return bthread::start_from_non_worker(tid, attr, fn, arg); +} + +void bthread_flush() { + bthread::TaskGroup* g = bthread::BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + if (g) { + return g->flush_nosignal_tasks(); + } + g = bthread::tls_task_group_nosignal; + if (g) { + // NOSIGNAL tasks were created in this non-worker. + bthread::tls_task_group_nosignal = NULL; + return g->flush_nosignal_tasks_remote(); + } +} + +int bthread_interrupt(bthread_t tid, bthread_tag_t /*tag*/) { + return bthread::TaskGroup::interrupt(tid, bthread::get_task_control()); +} + +int bthread_stop(bthread_t tid) { + bthread::TaskGroup::set_stopped(tid); + return bthread_interrupt(tid); +} + +int bthread_stopped(bthread_t tid) { + return (int)bthread::TaskGroup::is_stopped(tid); +} + +bthread_t bthread_self(void) { + bthread::TaskGroup* g = bthread::BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + // note: return 0 for main tasks now, which include main thread and + // all work threads. So that we can identify main tasks from logs + // more easily. This is probably questionable in the future. + if (g != NULL && !g->is_current_main_task()/*note*/) { + return g->current_tid(); + } + return INVALID_BTHREAD; +} + +int bthread_equal(bthread_t t1, bthread_t t2) { + return t1 == t2; +} + +void bthread_exit(void* retval) { + bthread::TaskGroup* g = bthread::BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + if (g != NULL && !g->is_current_main_task()) { + throw bthread::ExitException(retval); + } else { + pthread_exit(retval); + } +} + +int bthread_join(bthread_t tid, void** thread_return) { + return bthread::TaskGroup::join(tid, thread_return); +} + +int bthread_attr_init(bthread_attr_t* a) { + *a = BTHREAD_ATTR_NORMAL; + return 0; +} + +int bthread_attr_destroy(bthread_attr_t*) { + return 0; +} + +int bthread_getattr(bthread_t tid, bthread_attr_t* attr) { + return bthread::TaskGroup::get_attr(tid, attr); +} + +int bthread_getconcurrency(void) { + return bthread::FLAGS_bthread_concurrency; +} + +int bthread_setconcurrency(int num) { + if (num < BTHREAD_MIN_CONCURRENCY || num > BTHREAD_MAX_CONCURRENCY) { + LOG(ERROR) << "Invalid concurrency=" << num; + return EINVAL; + } + if (bthread::FLAGS_bthread_min_concurrency > 0) { + if (num < bthread::FLAGS_bthread_min_concurrency) { + return EINVAL; + } + if (bthread::never_set_bthread_concurrency) { + bthread::never_set_bthread_concurrency = false; + } + bthread::FLAGS_bthread_concurrency = num; + return 0; + } + bthread::TaskControl* c = bthread::get_task_control(); + if (c != NULL) { + if (num < c->concurrency()) { + return EPERM; + } else if (num == c->concurrency()) { + return 0; + } + } + BAIDU_SCOPED_LOCK(bthread::g_task_control_mutex); + c = bthread::get_task_control(); + if (c == NULL) { + if (bthread::never_set_bthread_concurrency) { + bthread::never_set_bthread_concurrency = false; + bthread::FLAGS_bthread_concurrency = num; + } else if (num > bthread::FLAGS_bthread_concurrency) { + bthread::FLAGS_bthread_concurrency = num; + } + return 0; + } + if (bthread::FLAGS_bthread_concurrency != c->concurrency()) { + LOG(ERROR) << "CHECK failed: bthread_concurrency=" + << bthread::FLAGS_bthread_concurrency + << " != tc_concurrency=" << c->concurrency(); + bthread::FLAGS_bthread_concurrency = c->concurrency(); + } + if (num > bthread::FLAGS_bthread_concurrency) { + // Create more workers if needed. + auto added = bthread::add_workers_for_each_tag(num - bthread::FLAGS_bthread_concurrency); + bthread::FLAGS_bthread_concurrency += added; + } + return (num == bthread::FLAGS_bthread_concurrency ? 0 : EPERM); +} + +int bthread_getconcurrency_by_tag(bthread_tag_t tag) { + BAIDU_SCOPED_LOCK(bthread::g_task_control_mutex); + auto c = bthread::get_task_control(); + if (c == NULL) { + return EPERM; + } + return c->concurrency(tag); +} + +int bthread_setconcurrency_by_tag(int num, bthread_tag_t tag) { + if (tag == BTHREAD_TAG_INVALID) { + return 0; + } else if (tag < BTHREAD_TAG_DEFAULT || tag >= FLAGS_task_group_ntags) { + return EINVAL; + } + if (num < BTHREAD_MIN_CONCURRENCY || num > BTHREAD_MAX_CONCURRENCY) { + LOG(ERROR) << "Invalid concurrency_by_tag=" << num; + return EINVAL; + } + auto c = bthread::get_or_new_task_control(); + BAIDU_SCOPED_LOCK(bthread::g_task_control_mutex); + auto tag_ngroup = c->concurrency(tag); + auto add = num - tag_ngroup; + + if (add >= 0) { + auto added = c->add_workers(add, tag); + bthread::FLAGS_bthread_concurrency += added; + return (add == added ? 0 : EPERM); + } else { + LOG(WARNING) << "Fail to set concurrency by tag: " << tag + << ", tag concurrency should be larger than old oncurrency. old concurrency: " + << tag_ngroup << ", new concurrency: " << num; + return EPERM; + } +} + +int bthread_about_to_quit() { + bthread::TaskGroup* g = bthread::BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + if (g != NULL) { + bthread::TaskMeta* current_task = g->current_task(); + if(!(current_task->attr.flags & BTHREAD_NEVER_QUIT)) { + current_task->about_to_quit = true; + } + return 0; + } + return EPERM; +} + +int bthread_timer_add(bthread_timer_t* id, timespec abstime, + void (*on_timer)(void*), void* arg) { + bthread::TaskControl* c = bthread::get_or_new_task_control(); + if (c == NULL) { + return ENOMEM; + } + bthread::TimerThread* tt = bthread::get_or_create_global_timer_thread(); + if (tt == NULL) { + return ENOMEM; + } + bthread_timer_t tmp = tt->schedule(on_timer, arg, abstime); + if (tmp != 0) { + *id = tmp; + return 0; + } + return ESTOP; +} + +int bthread_timer_del(bthread_timer_t id) { + bthread::TaskControl* c = bthread::get_task_control(); + if (c != NULL) { + bthread::TimerThread* tt = bthread::get_global_timer_thread(); + if (tt == NULL) { + return EINVAL; + } + const int state = tt->unschedule(id); + if (state >= 0) { + return state; + } + } + return EINVAL; +} + +int bthread_usleep(uint64_t microseconds) { + bthread::TaskGroup* g = bthread::BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + if (NULL != g && !g->is_current_pthread_task()) { + return bthread::TaskGroup::usleep(&g, microseconds); + } + return ::usleep(microseconds); +} + +int bthread_yield(void) { + bthread::TaskGroup* g = bthread::BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + if (NULL != g && !g->is_current_pthread_task()) { + bthread::TaskGroup::yield(&g); + return 0; + } + // pthread_yield is not available on MAC + return sched_yield(); +} + +int bthread_set_worker_startfn(void (*start_fn)()) { + if (start_fn == NULL) { + return EINVAL; + } + bthread::g_worker_startfn = start_fn; + return 0; +} + +int bthread_set_tagged_worker_startfn(void (*start_fn)(bthread_tag_t)) { + if (start_fn == NULL) { + return EINVAL; + } + bthread::g_tagged_worker_startfn = start_fn; + return 0; +} + +void bthread_stop_world() { + bthread::TaskControl* c = bthread::get_task_control(); + if (c != NULL) { + c->stop_and_join(); + } +} + +int bthread_list_init(bthread_list_t* list, + unsigned /*size*/, + unsigned /*conflict_size*/) { + list->impl = new (std::nothrow) bthread::TidList; + if (NULL == list->impl) { + return ENOMEM; + } + // Set unused fields to zero as well. + list->head = 0; + list->size = 0; + list->conflict_head = 0; + list->conflict_size = 0; + return 0; +} + +void bthread_list_destroy(bthread_list_t* list) { + delete static_cast(list->impl); + list->impl = NULL; +} + +int bthread_list_add(bthread_list_t* list, bthread_t id) { + if (list->impl == NULL) { + return EINVAL; + } + return static_cast(list->impl)->add(id); +} + +int bthread_list_stop(bthread_list_t* list) { + if (list->impl == NULL) { + return EINVAL; + } + static_cast(list->impl)->apply(bthread::TidStopper()); + return 0; +} + +int bthread_list_join(bthread_list_t* list) { + if (list->impl == NULL) { + return EINVAL; + } + static_cast(list->impl)->apply(bthread::TidJoiner()); + return 0; +} + +bthread_tag_t bthread_self_tag(void) { + bthread::TaskGroup* g = bthread::BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + return g != NULL ? g->tag() : BTHREAD_TAG_DEFAULT; +} + +uint64_t bthread_cpu_clock_ns(void) { + bthread::TaskGroup* g = bthread::BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + if (g != NULL && !g->is_current_main_task()) { + return g->current_task_cpu_clock_ns(); + } + return 0; +} + +int bthread_set_span_funcs(bthread_create_span_fn create_fn, + bthread_destroy_span_fn destroy_fn, + bthread_end_span_fn end_fn) { + if ((create_fn && destroy_fn && end_fn) || + (!create_fn && !destroy_fn && !end_fn)) { + bthread::g_create_bthread_span = create_fn; + bthread::g_rpcz_parent_span_dtor = destroy_fn; + bthread::g_end_bthread_span = end_fn; + return 0; + } + + errno = EINVAL; + return -1; +} + +} // extern "C" + +void bthread_attr_set_name(bthread_attr_t* attr, const char* name) { + if (attr) { + strncpy(attr->name, name, BTHREAD_NAME_MAX_LENGTH); + attr->name[BTHREAD_NAME_MAX_LENGTH] = '\0'; + } +} diff --git a/src/bthread/bthread.h b/src/bthread/bthread.h new file mode 100644 index 0000000..402fe70 --- /dev/null +++ b/src/bthread/bthread.h @@ -0,0 +1,462 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Tue Jul 10 17:40:58 CST 2012 + +#ifndef BTHREAD_BTHREAD_H +#define BTHREAD_BTHREAD_H + +#include +#include +#include "bthread/types.h" +#include "bthread/errno.h" + +#if defined(__cplusplus) +#include +#include "bthread/mutex.h" // use bthread_mutex_t in the RAII way +#include "bthread/condition_variable.h" // use bthread_cond_t in the RAII way +#endif // __cplusplus + +#include "bthread/id.h" + +#if defined(__cplusplus) && defined(BRPC_BTHREAD_TRACER) +namespace bthread { +// Assign a TaskMeta to the pthread and set the state to Running, +// so that `stack_trace()' can trace the call stack of the pthread. +bthread_t init_for_pthread_stack_trace(); + +// Trace the call stack of the bthread, or pthread which has been +// initialized by `init_for_pthread_stack_trace()'. +void stack_trace(std::ostream& os, bthread_t tid); +std::string stack_trace(bthread_t tid); +} // namespace bthread +#endif // __cplusplus && BRPC_BTHREAD_TRACER + +__BEGIN_DECLS + +// Create bthread `fn(args)' with attributes `attr' and put the identifier into +// `tid'. Switch to the new thread and schedule old thread to run. Use this +// function when the new thread is more urgent. +// Returns 0 on success, errno otherwise. +extern int bthread_start_urgent(bthread_t* __restrict tid, + const bthread_attr_t* __restrict attr, + void * (*fn)(void*), + void* __restrict args); + +// Create bthread `fn(args)' with attributes `attr' and put the identifier into +// `tid'. This function behaves closer to pthread_create: after scheduling the +// new thread to run, it returns. In another word, the new thread may take +// longer time than bthread_start_urgent() to run. +// Return 0 on success, errno otherwise. +extern int bthread_start_background(bthread_t* __restrict tid, + const bthread_attr_t* __restrict attr, + void * (*fn)(void*), + void* __restrict args); + +// Wake up operations blocking the thread. Different functions may behave +// differently: +// bthread_usleep(): returns -1 and sets errno to ESTOP if bthread_stop() +// is called, or to EINTR otherwise. +// butex_wait(): returns -1 and sets errno to EINTR +// bthread_mutex_*lock: unaffected (still blocking) +// bthread_cond_*wait: wakes up and returns 0. +// bthread_*join: unaffected. +// Common usage of interruption is to make a thread to quit ASAP. +// [Thread1] [Thread2] +// set stopping flag +// bthread_interrupt(Thread2) +// wake up +// see the flag and quit +// may block again if the flag is unchanged +// bthread_interrupt() guarantees that Thread2 is woken up reliably no matter +// how the 2 threads are interleaved. +// Returns 0 on success, errno otherwise. +extern int bthread_interrupt(bthread_t tid, bthread_tag_t tag = BTHREAD_TAG_DEFAULT); + +// Make bthread_stopped() on the bthread return true and interrupt the bthread. +// Note that current bthread_stop() solely sets the built-in "stop flag" and +// calls bthread_interrupt(), which is different from earlier versions of +// bthread, and replaceable by user-defined stop flags plus calls to +// bthread_interrupt(). +// Returns 0 on success, errno otherwise. +extern int bthread_stop(bthread_t tid); + +// Returns 1 iff bthread_stop(tid) was called or the thread does not exist, +// 0 otherwise. +extern int bthread_stopped(bthread_t tid); + +// Returns identifier of caller if caller is a bthread, 0 otherwise(Id of a +// bthread is never zero) +extern bthread_t bthread_self(void); + +// Compare two bthread identifiers. +// Returns a non-zero value if t1 and t2 are equal, zero otherwise. +extern int bthread_equal(bthread_t t1, bthread_t t2); + +// Terminate calling bthread/pthread and make `retval' available to any +// successful join with the terminating thread. This function does not return. +extern void bthread_exit(void* retval) __attribute__((__noreturn__)); + +// Make calling thread wait for termination of bthread `bt'. Return immediately +// if `bt' is already terminated. +// Notes: +// - All bthreads are "detached" but still joinable. +// - *bthread_return is always set to null. If you need to return value +// from a bthread, pass the value via the `args' created the bthread. +// - bthread_join() is not affected by bthread_interrupt. +// Returns 0 on success, errno otherwise. +extern int bthread_join(bthread_t bt, void** bthread_return); + +// Track and join many bthreads. +// Notice that all bthread_list* functions are NOT thread-safe. +extern int bthread_list_init(bthread_list_t* list, + unsigned size, unsigned conflict_size); +extern void bthread_list_destroy(bthread_list_t* list); +extern int bthread_list_add(bthread_list_t* list, bthread_t tid); +extern int bthread_list_stop(bthread_list_t* list); +extern int bthread_list_join(bthread_list_t* list); + +// ------------------------------------------ +// Functions for handling attributes. +// ------------------------------------------ + +// Initialize thread attribute `attr' with default attributes. +extern int bthread_attr_init(bthread_attr_t* attr); + +// Destroy thread attribute `attr'. +extern int bthread_attr_destroy(bthread_attr_t* attr); + +// Initialize bthread attribute `attr' with attributes corresponding to the +// already running bthread `bt'. It shall be called on unitialized `attr' +// and destroyed with bthread_attr_destroy when no longer needed. +extern int bthread_getattr(bthread_t bt, bthread_attr_t* attr); + +// --------------------------------------------- +// Functions for scheduling control. +// --------------------------------------------- + +// Get number of worker pthreads +extern int bthread_getconcurrency(void); + +// Set number of worker pthreads to `num'. After a successful call, +// bthread_getconcurrency() shall return new set number, but workers may +// take some time to quit or create. +// NOTE: currently concurrency cannot be reduced after any bthread created. +extern int bthread_setconcurrency(int num); + +// Get number of worker pthreads by tag +extern int bthread_getconcurrency_by_tag(bthread_tag_t tag); + +// Set number of worker pthreads to `num' for specified tag +extern int bthread_setconcurrency_by_tag(int num, bthread_tag_t tag); + +// Yield processor to another bthread. +// Notice that current implementation is not fair, which means that +// even if bthread_yield() is called, suspended threads may still starve. +extern int bthread_yield(void); + +// Suspend current thread for at least `microseconds' +// Interruptible by bthread_interrupt(). +extern int bthread_usleep(uint64_t microseconds); + +// --------------------------------------------- +// Functions for mutex handling. +// --------------------------------------------- + +// Initialize `mutex' using attributes in `mutex_attr', or use the +// default values if later is NULL. +// NOTE: mutexattr is not used in current mutex implementation. User shall +// always pass a NULL attribute. +extern int bthread_mutex_init(bthread_mutex_t* __restrict mutex, + const bthread_mutexattr_t* __restrict attr); + +// Destroy `mutex'. +extern int bthread_mutex_destroy(bthread_mutex_t* mutex); + +// Try to lock `mutex'. +extern int bthread_mutex_trylock(bthread_mutex_t* mutex); + +// Wait until lock for `mutex' becomes available and lock it. +extern int bthread_mutex_lock(bthread_mutex_t* mutex); + +// Wait until lock becomes available and lock it or time exceeds `abstime' +extern int bthread_mutex_timedlock(bthread_mutex_t* __restrict mutex, + const struct timespec* __restrict abstime); + +// Unlock `mutex'. +extern int bthread_mutex_unlock(bthread_mutex_t* mutex); + +extern int bthread_mutexattr_init(bthread_mutexattr_t* attr); + +// Disable the contention profile of the mutex. +extern int bthread_mutexattr_disable_csite(bthread_mutexattr_t* attr); + +extern int bthread_mutexattr_destroy(bthread_mutexattr_t* attr); + +// ----------------------------------------------- +// Functions for handling conditional variables. +// ----------------------------------------------- + +// Initialize condition variable `cond' using attributes `cond_attr', or use +// the default values if later is NULL. +// NOTE: cond_attr is not used in current condition implementation. User shall +// always pass a NULL attribute. +extern int bthread_cond_init(bthread_cond_t* __restrict cond, + const bthread_condattr_t* __restrict cond_attr); + +// Destroy condition variable `cond'. +extern int bthread_cond_destroy(bthread_cond_t* cond); + +// Wake up one thread waiting for condition variable `cond'. +extern int bthread_cond_signal(bthread_cond_t* cond); + +// Wake up all threads waiting for condition variables `cond'. +extern int bthread_cond_broadcast(bthread_cond_t* cond); + +// Wait for condition variable `cond' to be signaled or broadcast. +// `mutex' is assumed to be locked before. +extern int bthread_cond_wait(bthread_cond_t* __restrict cond, + bthread_mutex_t* __restrict mutex); + +// Wait for condition variable `cond' to be signaled or broadcast until +// `abstime'. `mutex' is assumed to be locked before. `abstime' is an +// absolute time specification; zero is the beginning of the epoch +// (00:00:00 GMT, January 1, 1970). +extern int bthread_cond_timedwait( + bthread_cond_t* __restrict cond, + bthread_mutex_t* __restrict mutex, + const struct timespec* __restrict abstime); + +// ------------------------------------------- +// Functions for handling read-write locks. +// ------------------------------------------- + +// Initialize read-write lock `rwlock' using attributes `attr', or use +// the default values if later is NULL. +extern int bthread_rwlock_init(bthread_rwlock_t* __restrict rwlock, + const bthread_rwlockattr_t* __restrict attr); + +// Destroy read-write lock `rwlock'. +extern int bthread_rwlock_destroy(bthread_rwlock_t* rwlock); + +// Acquire read lock for `rwlock'. +extern int bthread_rwlock_rdlock(bthread_rwlock_t* rwlock); + +// Try to acquire read lock for `rwlock'. +extern int bthread_rwlock_tryrdlock(bthread_rwlock_t* rwlock); + +// Try to acquire read lock for `rwlock' or return after specfied time. +extern int bthread_rwlock_timedrdlock(bthread_rwlock_t* __restrict rwlock, + const struct timespec* __restrict abstime); + +// Acquire write lock for `rwlock'. +extern int bthread_rwlock_wrlock(bthread_rwlock_t* rwlock); + +// Try to acquire write lock for `rwlock'. +extern int bthread_rwlock_trywrlock(bthread_rwlock_t* rwlock); + +// Try to acquire write lock for `rwlock' or return after specfied time. +extern int bthread_rwlock_timedwrlock(bthread_rwlock_t* __restrict rwlock, + const struct timespec* __restrict abstime); + +// Unlock `rwlock'. +extern int bthread_rwlock_unlock(bthread_rwlock_t* rwlock); + +// --------------------------------------------------- +// Functions for handling read-write lock attributes. +// --------------------------------------------------- + +// Initialize attribute object `attr' with default values. +extern int bthread_rwlockattr_init(bthread_rwlockattr_t* attr); + +// Destroy attribute object `attr'. +extern int bthread_rwlockattr_destroy(bthread_rwlockattr_t* attr); + +// Return current setting of reader/writer preference. +extern int bthread_rwlockattr_getkind_np(const bthread_rwlockattr_t* attr, + int* pref); + +// Set reader/write preference. +extern int bthread_rwlockattr_setkind_np(bthread_rwlockattr_t* attr, + int pref); + +// ------------------------------------------- +// Functions for handling semaphore. +// ------------------------------------------- + +// Initialize the semaphore referred to by `sem'. The value of the +// initialized semaphore shall be `value'. +// Return 0 on success, errno otherwise. +extern int bthread_sem_init(bthread_sem_t* sem, unsigned value); + +// Disable the contention profile of the semaphore referred to by `sem'. +extern int bthread_sem_disable_csite(bthread_sem_t* sem); + +// Destroy the semaphore indicated by `sem'. +// Return 0 on success, errno otherwise. +extern int bthread_sem_destroy(bthread_sem_t* semaphore); + +// Lock the semaphore referenced by `sem' by performing a semaphore +// lock operation on that semaphore. If the semaphore value is currently +// zero, then the calling (b)thread shall not return from the call to +// bthread_sema_wait() function until it locks the semaphore. +// Return 0 on success, errno otherwise. +extern int bthread_sem_wait(bthread_sem_t* sem); + +// Lock the semaphore referenced by `sem' as in the bthread_sem_wait() +// function. However, if the semaphore cannot be locked without waiting +// for another (b)thread to unlock the semaphore by performing a +// bthread_sem_post() function, this wait shall be terminated when the +// specified timeout expires. +// Return 0 on success, errno otherwise. +extern int bthread_sem_timedwait(bthread_sem_t* sem, const struct timespec* abstime); + +// Lock the semaphore referenced by `sem' only if the semaphore is +// currently not locked; that is, if the semaphore value is currently +// positive. Otherwise, it shall not lock the semaphore. +// Return 0 on success, errno otherwise. +extern int bthread_sem_trywait(bthread_sem_t* sem); + +// Unlock the semaphore referenced by `sem' by performing +// a semaphore unlock operation on that semaphore. +// Return 0 on success, errno otherwise. +extern int bthread_sem_post(bthread_sem_t* sem); + +// Unlock the semaphore referenced by `sem' by performing +// `n' semaphore unlock operation on that semaphore. +// Return 0 on success, errno otherwise. +extern int bthread_sem_post_n(bthread_sem_t* sem, size_t n); + + +// ---------------------------------------------------------------------- +// Functions for handling barrier which is a new feature in 1003.1j-2000. +// ---------------------------------------------------------------------- + +extern int bthread_barrier_init(bthread_barrier_t* __restrict barrier, + const bthread_barrierattr_t* __restrict attr, + unsigned count); + +extern int bthread_barrier_destroy(bthread_barrier_t* barrier); + +extern int bthread_barrier_wait(bthread_barrier_t* barrier); + +// --------------------------------------------------------------------- +// Functions for handling thread-specific data. +// Notice that they can be used in pthread: get pthread-specific data in +// pthreads and get bthread-specific data in bthreads. +// --------------------------------------------------------------------- + +// Create a key value identifying a slot in a thread-specific data area. +// Each thread maintains a distinct thread-specific data area. +// `destructor', if non-NULL, is called with the value associated to that key +// when the key is destroyed. `destructor' is not called if the value +// associated is NULL when the key is destroyed. +// Returns 0 on success, error code otherwise. +extern int bthread_key_create(bthread_key_t* key, + void (*destructor)(void* data)); + +// Delete a key previously returned by bthread_key_create(). +// It is the responsibility of the application to free the data related to +// the deleted key in any running thread. No destructor is invoked by +// this function. Any destructor that may have been associated with key +// will no longer be called upon thread exit. +// Returns 0 on success, error code otherwise. +extern int bthread_key_delete(bthread_key_t key); + +// Store `data' in the thread-specific slot identified by `key'. +// bthread_setspecific() is callable from within destructor. If the application +// does so, destructors will be repeatedly called for at most +// PTHREAD_DESTRUCTOR_ITERATIONS times to clear the slots. +// NOTE: If the thread is not created by brpc server and lifetime is +// very short(doing a little thing and exit), avoid using bthread-local. The +// reason is that bthread-local always allocate keytable on first call to +// bthread_setspecific, the overhead is negligible in long-lived threads, +// but noticeable in shortly-lived threads. Threads in brpc server +// are special since they reuse keytables from a bthread_keytable_pool_t +// in the server. +// Returns 0 on success, error code otherwise. +// If the key is invalid or deleted, return EINVAL. +extern int bthread_setspecific(bthread_key_t key, void* data); + +// Return current value of the thread-specific slot identified by `key'. +// If bthread_setspecific() had not been called in the thread, return NULL. +// If the key is invalid or deleted, return NULL. +extern void* bthread_getspecific(bthread_key_t key); + +// Return current bthread tag +extern bthread_tag_t bthread_self_tag(void); + +// The first call to bthread_once() by any thread in a process, with a given +// once_control, will call the init_routine() with no arguments. Subsequent +// calls of bthread_once() with the same once_control will not call the +// init_routine(). On return from bthread_once(), it is guaranteed that +// init_routine() has completed. The once_control parameter is used to +// determine whether the associated initialisation routine has been called. +// Returns 0 on success, error code otherwise. +extern int bthread_once(bthread_once_t* once_control, void (*init_routine)()); + + /** + * @brief Retrieves the CPU time consumed by the current bthread. + * + * This function returns the CPU time (in nanoseconds) used by the current + * bthread, excluding time spent in blocking I/O operations. The result + * provides an accurate measure of CPU time utilized by the bthread's + * execution. + * + * @note The functionality of this function depends on the + * `bthread_enable_cpu_clock_stat` flag. Ensure this flag is enabled + * for the function to provide meaningful results. If the flag is + * disabled, the function may return an invalid value or behave + * unexpectedly. + * + * @return int64_t The CPU time in nanoseconds consumed by the bthread. + */ +extern uint64_t bthread_cpu_clock_ns(void); + +// Span callback function types for tracing bthread lifecycle. +// These callbacks are typically set by upper-layer frameworks (e.g., brpc) +// to integrate distributed tracing with bthread execution. +typedef void* (*bthread_create_span_fn)(void); +typedef void (*bthread_destroy_span_fn)(void*); +typedef void (*bthread_end_span_fn)(void); + +// Set span-related callbacks for bthread tracing. +// This should be called during framework initialization (e.g., in GlobalInitializeOrDie). +// +// Parameters: +// create_fn - Called when creating a bthread with BTHREAD_INHERIT_SPAN flag. +// Should return a heap-allocated span context (e.g., weak_ptr*). +// Returns NULL if span creation is disabled or fails. +// destroy_fn - Called to destroy the span context when bthread exits or cleans up. +// Receives the pointer returned by create_fn. +// end_fn - Called when bthread ends to finalize the span (e.g., set end time). +// +// All three callbacks must be provided together, or all NULL to disable span tracking. +// This function should only be called once during initialization. +// +// Returns: +// 0 on success +// -1 if parameters are invalid (sets errno to EINVAL) +extern int bthread_set_span_funcs(bthread_create_span_fn create_fn, + bthread_destroy_span_fn destroy_fn, + bthread_end_span_fn end_fn); + +__END_DECLS + +#endif // BTHREAD_BTHREAD_H diff --git a/src/bthread/bthread_once.cpp b/src/bthread/bthread_once.cpp new file mode 100644 index 0000000..a5751bc --- /dev/null +++ b/src/bthread/bthread_once.cpp @@ -0,0 +1,81 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "bthread/types.h" +#include "bthread/butex.h" + +bthread_once_t::bthread_once_t() + : _butex(bthread::butex_create_checked>()) { + _butex->store(UNINITIALIZED, butil::memory_order_relaxed); +} + +bthread_once_t::~bthread_once_t() { + bthread::butex_destroy(_butex); +} + +namespace bthread { + +int bthread_once_impl(bthread_once_t* once_control, void (*init_routine)()) { + butil::atomic* butex = once_control->_butex; + // We need acquire memory order for this load because if the value + // signals that initialization has finished, we need to see any + // data modifications done during initialization. + int val = butex->load(butil::memory_order_acquire); + if (BAIDU_LIKELY(val == bthread_once_t::INITIALIZED)) { + // The initialization has already been done. + return 0; + } + val = bthread_once_t::UNINITIALIZED; + if (butex->compare_exchange_strong(val, bthread_once_t::INPROGRESS, + butil::memory_order_relaxed, + butil::memory_order_relaxed)) { + // This (b)thread is the first and the Only one here. Do the initialization. + init_routine(); + // Mark *once_control as having finished the initialization. We need + // release memory order here because we need to synchronize with other + // (b)threads that want to use the initialized data. + butex->store(bthread_once_t::INITIALIZED, butil::memory_order_release); + // Wake up all other (b)threads. + bthread::butex_wake_all(butex); + return 0; + } + + while (true) { + // Same as above, we need acquire memory order. + val = butex->load(butil::memory_order_acquire); + if (BAIDU_LIKELY(val == bthread_once_t::INITIALIZED)) { + // The initialization has already been done. + return 0; + } + // Unless your constructor can be very time consuming, it is very unlikely o hit + // this race. When it does, we just wait the thread until the object has been created. + if (bthread::butex_wait(butex, val, NULL) < 0 && + errno != EWOULDBLOCK && errno != EINTR/*note*/) { + return errno; + } + } +} + +} // namespace bthread + +__BEGIN_DECLS + +int bthread_once(bthread_once_t* once_control, void (*init_routine)()) { + return bthread::bthread_once_impl(once_control, init_routine); +} + +__END_DECLS \ No newline at end of file diff --git a/src/bthread/butex.cpp b/src/bthread/butex.cpp new file mode 100644 index 0000000..92de900 --- /dev/null +++ b/src/bthread/butex.cpp @@ -0,0 +1,758 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Tue Jul 22 17:30:12 CST 2014 + +#include "butil/atomicops.h" // butil::atomic +#include "butil/scoped_lock.h" // BAIDU_SCOPED_LOCK +#include "butil/macros.h" +#include "butil/containers/flat_map.h" +#include "butil/containers/linked_list.h" // LinkNode +#ifdef SHOW_BTHREAD_BUTEX_WAITER_COUNT_IN_VARS +#include "butil/memory/singleton_on_pthread_once.h" +#endif +#include "butil/logging.h" +#include "butil/object_pool.h" +#include "bthread/errno.h" // EWOULDBLOCK +#include "bthread/sys_futex.h" // futex_* +#include "bthread/processor.h" // cpu_relax +#include "bthread/task_control.h" // TaskControl +#include "bthread/task_group.h" // TaskGroup +#include "bthread/timer_thread.h" +#include "bthread/butex.h" +#include "bthread/mutex.h" + +// This file implements butex.h +// Provides futex-like semantics which is sequenced wait and wake operations +// and guaranteed visibilities. +// +// If wait is sequenced before wake: +// [thread1] [thread2] +// wait() value = new_value +// wake() +// wait() sees unmatched value(fail to wait), or wake() sees the waiter. +// +// If wait is sequenced after wake: +// [thread1] [thread2] +// value = new_value +// wake() +// wait() +// wake() must provide some sort of memory fence to prevent assignment +// of value to be reordered after it. Thus the value is visible to wait() +// as well. + +namespace bthread { + +#ifdef SHOW_BTHREAD_BUTEX_WAITER_COUNT_IN_VARS +struct ButexWaiterCount : public bvar::Adder { + ButexWaiterCount() : bvar::Adder("bthread_butex_waiter_count") {} +}; +inline bvar::Adder& butex_waiter_count() { + return *butil::get_leaky_singleton(); +} +#endif + +enum WaiterState { + WAITER_STATE_NONE, + WAITER_STATE_READY, + WAITER_STATE_TIMEDOUT, + WAITER_STATE_UNMATCHEDVALUE, + WAITER_STATE_INTERRUPTED, +}; + +struct Butex; + +struct ButexWaiter : public butil::LinkNode { + // tids of pthreads are 0 + bthread_t tid; + + // Erasing node from middle of LinkedList is thread-unsafe, we need + // to hold its container's lock. + butil::atomic container; +}; + +// non_pthread_task allocates this structure on stack and queue it in +// Butex::waiters. +struct ButexBthreadWaiter : public ButexWaiter { + TaskMeta* task_meta; + TimerThread::TaskId sleep_id; + WaiterState waiter_state; + int expected_value; + Butex* initial_butex; + TaskControl* control; + const timespec* abstime; +}; + +// pthread_task or main_task allocates this structure on stack and queue it +// in Butex::waiters. +struct ButexPthreadWaiter : public ButexWaiter { + butil::atomic sig; +}; + +typedef butil::LinkedList ButexWaiterList; + +enum ButexPthreadSignal { PTHREAD_NOT_SIGNALLED, PTHREAD_SIGNALLED }; + +struct BAIDU_CACHELINE_ALIGNMENT Butex { + Butex() {} + ~Butex() {} + + butil::atomic value; + ButexWaiterList waiters; + FastPthreadMutex waiter_lock; +}; + +BAIDU_CASSERT(offsetof(Butex, value) == 0, offsetof_value_must_0); +BAIDU_CASSERT(sizeof(Butex) == BAIDU_CACHELINE_SIZE, butex_fits_in_one_cacheline); + +} // namespace bthread + +namespace butil { +// Butex object returned to the ObjectPool may be accessed, +// so ObjectPool can not poison the memory region of Butex. +template <> +struct ObjectPoolWithASanPoison : false_type {}; +} // namespace butil + +namespace bthread { + +static void wakeup_pthread(ButexPthreadWaiter* pw) { + // release fence makes wait_pthread see changes before wakeup. + pw->sig.store(PTHREAD_SIGNALLED, butil::memory_order_release); + // At this point, wait_pthread() possibly has woken up and destroyed `pw'. + // In which case, futex_wake_private() should return EFAULT. + // If crash happens in future, `pw' can be made TLS and never destroyed + // to solve the issue. + futex_wake_private(&pw->sig, 1); +} + +bool erase_from_butex(ButexWaiter*, bool, WaiterState); + +int wait_pthread(ButexPthreadWaiter& pw, const timespec* abstime) { + timespec* ptimeout = NULL; + timespec timeout; + int64_t timeout_us = 0; + int rc; + + while (true) { + if (abstime != NULL) { + timeout_us = butil::timespec_to_microseconds(*abstime) - butil::gettimeofday_us(); + timeout = butil::microseconds_to_timespec(timeout_us); + ptimeout = &timeout; + } + if (timeout_us > MIN_SLEEP_US || abstime == NULL) { + rc = futex_wait_private(&pw.sig, PTHREAD_NOT_SIGNALLED, ptimeout); + if (PTHREAD_NOT_SIGNALLED != pw.sig.load(butil::memory_order_acquire)) { + // If `sig' is changed, wakeup_pthread() must be called and `pw' + // is already removed from the butex. + // Acquire fence makes this thread sees changes before wakeup. + return rc; + } + } else { + errno = ETIMEDOUT; + rc = -1; + } + // Handle ETIMEDOUT when abstime is valid. + // If futex_wait_private return EINTR, just continue the loop. + if (rc != 0 && errno == ETIMEDOUT) { + // wait futex timeout, `pw' is still in the queue, remove it. + if (!erase_from_butex(&pw, false, WAITER_STATE_TIMEDOUT)) { + // Another thread is erasing `pw' as well, wait for the signal. + // Acquire fence makes this thread sees changes before wakeup. + if (pw.sig.load(butil::memory_order_acquire) == PTHREAD_NOT_SIGNALLED) { + // already timedout, abstime and ptimeout are expired. + abstime = NULL; + ptimeout = NULL; + continue; + } + } + return rc; + } + } +} + +extern BAIDU_THREAD_LOCAL TaskGroup* tls_task_group; + +// Returns 0 when no need to unschedule or successfully unscheduled, +// -1 otherwise. +inline int unsleep_if_necessary(ButexBthreadWaiter* w, + TimerThread* timer_thread) { + if (!w->sleep_id) { + return 0; + } + if (timer_thread->unschedule(w->sleep_id) > 0) { + // the callback is running. + return -1; + } + w->sleep_id = 0; + return 0; +} + +// Use ObjectPool(which never frees memory) to solve the race between +// butex_wake() and butex_destroy(). The race is as follows: +// +// class Event { +// public: +// void wait() { +// _mutex.lock(); +// if (!_done) { +// _cond.wait(&_mutex); +// } +// _mutex.unlock(); +// } +// void signal() { +// _mutex.lock(); +// if (!_done) { +// _done = true; +// _cond.signal(); +// } +// _mutex.unlock(); /*1*/ +// } +// private: +// bool _done = false; +// Mutex _mutex; +// Condition _cond; +// }; +// +// [Thread1] [Thread2] +// foo() { +// Event event; +// pass_to_thread2(&event); ---> event.signal(); +// event.wait(); +// } <-- event destroyed +// +// Summary: Thread1 passes a stateful condition to Thread2 and waits until +// the condition being signalled, which basically means the associated +// job is done and Thread1 can release related resources including the mutex +// and condition. The scenario is fine and the code is correct. +// The race needs a closer look. The unlock at /*1*/ may have different +// implementations, but in which the last step is probably an atomic store +// and butex_wake(), like this: +// +// locked->store(0); +// butex_wake(locked); +// +// The `locked' represents the locking status of the mutex. The issue is that +// just after the store(), the mutex is already unlocked and the code in +// Event.wait() may successfully grab the lock and go through everything +// left and leave foo() function, destroying the mutex and butex, making +// the butex_wake(locked) crash. +// To solve this issue, one method is to add reference before store and +// release the reference after butex_wake. However reference countings need +// to be added in nearly every user scenario of butex_wake(), which is very +// error-prone. Another method is never freeing butex, with the side effect +// that butex_wake() may wake up an unrelated butex(the one reuses the memory) +// and cause spurious wakeups. According to our observations, the race is +// infrequent, even rare. The extra spurious wakeups should be acceptable. + +void* butex_create() { + Butex* b = butil::get_object(); + if (b) { + return &b->value; + } + return NULL; +} + +void butex_destroy(void* butex) { + if (!butex) { + return; + } + Butex* b = static_cast( + container_of(static_cast*>(butex), Butex, value)); + butil::return_object(b); +} + +// if TaskGroup tls_task_group is belong to tag +inline bool is_same_tag(bthread_tag_t tag) { + return tls_task_group && tls_task_group->tag() == tag; +} + +// nosignal is true & tag is same can return true +inline bool check_nosignal(bool nosignal, bthread_tag_t tag) { + return nosignal && is_same_tag(tag); +} + +// if tag is same return tls_task_group else choose one group with tag +inline TaskGroup* get_task_group(TaskControl* c, bthread_tag_t tag) { + return is_same_tag(tag) ? tls_task_group : c->choose_one_group(tag); +} + +inline void run_in_local_task_group(TaskGroup* g, TaskMeta* next_meta, bool nosignal) { + if (!nosignal) { + TaskGroup::exchange(&g, next_meta); + } else { + g->ready_to_run(next_meta, nosignal); + } +} + +int butex_wake(void* arg, bool nosignal) { + Butex* b = container_of(static_cast*>(arg), Butex, value); + ButexWaiter* front = NULL; + { + BAIDU_SCOPED_LOCK(b->waiter_lock); + if (b->waiters.empty()) { + return 0; + } + front = b->waiters.head()->value(); + front->RemoveFromList(); + front->container.store(NULL, butil::memory_order_relaxed); + } + if (front->tid == 0) { + wakeup_pthread(static_cast(front)); + return 1; + } + ButexBthreadWaiter* bbw = static_cast(front); + unsleep_if_necessary(bbw, get_global_timer_thread()); + TaskGroup* g = get_task_group(bbw->control, bbw->task_meta->attr.tag); + if (g == tls_task_group) { + run_in_local_task_group(g, bbw->task_meta, nosignal); + } else { + g->ready_to_run_remote(bbw->task_meta, check_nosignal(nosignal, g->tag())); + } + return 1; +} + +int butex_wake_n(void* arg, size_t n, bool nosignal) { + Butex* b = container_of(static_cast*>(arg), Butex, value); + + ButexWaiterList bthread_waiters; + ButexWaiterList pthread_waiters; + { + BAIDU_SCOPED_LOCK(b->waiter_lock); + for (size_t i = 0; (n == 0 || i < n) && !b->waiters.empty(); ++i) { + ButexWaiter* bw = b->waiters.head()->value(); + bw->RemoveFromList(); + bw->container.store(NULL, butil::memory_order_relaxed); + if (bw->tid) { + bthread_waiters.Append(bw); + } else { + pthread_waiters.Append(bw); + } + } + } + + int nwakeup = 0; + while (!pthread_waiters.empty()) { + ButexPthreadWaiter* bw = static_cast( + pthread_waiters.head()->value()); + bw->RemoveFromList(); + wakeup_pthread(bw); + ++nwakeup; + } + if (bthread_waiters.empty()) { + return nwakeup; + } + butil::FlatMap nwakeups; + nwakeups.init(FLAGS_task_group_ntags); + // We will exchange with first waiter in the end. + ButexBthreadWaiter* next = static_cast( + bthread_waiters.head()->value()); + next->RemoveFromList(); + unsleep_if_necessary(next, get_global_timer_thread()); + ++nwakeup; + while (!bthread_waiters.empty()) { + // pop reversely + ButexBthreadWaiter* w = static_cast( + bthread_waiters.tail()->value()); + w->RemoveFromList(); + unsleep_if_necessary(w, get_global_timer_thread()); + auto g = get_task_group(w->control, w->task_meta->attr.tag); + g->ready_to_run_general(w->task_meta, true); + nwakeups[g->tag()] = g; + ++nwakeup; + } + for (auto it = nwakeups.begin(); it != nwakeups.end(); ++it) { + auto g = it->second; + if (!check_nosignal(nosignal, g->tag())) { + g->flush_nosignal_tasks_general(); + } + } + auto g = get_task_group(next->control, next->task_meta->attr.tag); + if (g == tls_task_group) { + run_in_local_task_group(g, next->task_meta, nosignal); + } else { + g->ready_to_run_remote(next->task_meta, check_nosignal(nosignal, g->tag())); + } + return nwakeup; +} + +int butex_wake_all(void* arg, bool nosignal) { + return butex_wake_n(arg, 0, nosignal); +} + +int butex_wake_except(void* arg, bthread_t excluded_bthread) { + Butex* b = container_of(static_cast*>(arg), Butex, value); + + ButexWaiterList bthread_waiters; + ButexWaiterList pthread_waiters; + { + ButexWaiter* excluded_waiter = NULL; + BAIDU_SCOPED_LOCK(b->waiter_lock); + while (!b->waiters.empty()) { + ButexWaiter* bw = b->waiters.head()->value(); + bw->RemoveFromList(); + + if (bw->tid) { + if (bw->tid != excluded_bthread) { + bthread_waiters.Append(bw); + bw->container.store(NULL, butil::memory_order_relaxed); + } else { + excluded_waiter = bw; + } + } else { + bw->container.store(NULL, butil::memory_order_relaxed); + pthread_waiters.Append(bw); + } + } + + if (excluded_waiter) { + b->waiters.Append(excluded_waiter); + } + } + + int nwakeup = 0; + while (!pthread_waiters.empty()) { + ButexPthreadWaiter* bw = static_cast( + pthread_waiters.head()->value()); + bw->RemoveFromList(); + wakeup_pthread(bw); + ++nwakeup; + } + + if (bthread_waiters.empty()) { + return nwakeup; + } + butil::FlatMap nwakeups; + nwakeups.init(FLAGS_task_group_ntags); + do { + // pop reversely + ButexBthreadWaiter* w = static_cast(bthread_waiters.tail()->value()); + w->RemoveFromList(); + unsleep_if_necessary(w, get_global_timer_thread()); + auto g = get_task_group(w->control, w->task_meta->attr.tag); + g->ready_to_run_general(w->task_meta, true); + nwakeups[g->tag()] = g; + ++nwakeup; + } while (!bthread_waiters.empty()); + for (auto it = nwakeups.begin(); it != nwakeups.end(); ++it) { + auto g = it->second; + g->flush_nosignal_tasks_general(); + } + return nwakeup; +} + +int butex_requeue(void* arg, void* arg2) { + Butex* b = container_of(static_cast*>(arg), Butex, value); + Butex* m = container_of(static_cast*>(arg2), Butex, value); + + ButexWaiter* front = NULL; + { + std::unique_lock lck1(b->waiter_lock, std::defer_lock); + std::unique_lock lck2(m->waiter_lock, std::defer_lock); + butil::double_lock(lck1, lck2); + if (b->waiters.empty()) { + return 0; + } + + front = b->waiters.head()->value(); + front->RemoveFromList(); + front->container.store(NULL, butil::memory_order_relaxed); + + while (!b->waiters.empty()) { + ButexWaiter* bw = b->waiters.head()->value(); + bw->RemoveFromList(); + m->waiters.Append(bw); + bw->container.store(m, butil::memory_order_relaxed); + } + } + + if (front->tid == 0) { // which is a pthread + wakeup_pthread(static_cast(front)); + return 1; + } + ButexBthreadWaiter* bbw = static_cast(front); + unsleep_if_necessary(bbw, get_global_timer_thread()); + auto g = is_same_tag(bbw->task_meta->attr.tag) ? tls_task_group : NULL; + if (g) { + TaskGroup::exchange(&g, bbw->task_meta); + } else { + g = bbw->control->choose_one_group(bbw->task_meta->attr.tag); + g->ready_to_run_remote(bbw->task_meta); + } + return 1; +} + +// Callable from multiple threads, at most one thread may wake up the waiter. +static void erase_from_butex_and_wakeup(void* arg) { + erase_from_butex(static_cast(arg), true, WAITER_STATE_TIMEDOUT); +} + +// Used in task_group.cpp +bool erase_from_butex_because_of_interruption(ButexWaiter* bw) { + return erase_from_butex(bw, true, WAITER_STATE_INTERRUPTED); +} + +inline bool erase_from_butex(ButexWaiter* bw, bool wakeup, WaiterState state) { + // `bw' is guaranteed to be valid inside this function because waiter + // will wait until this function being cancelled or finished. + // NOTE: This function must be no-op when bw->container is NULL. + bool erased = false; + Butex* b; + int saved_errno = errno; + while ((b = bw->container.load(butil::memory_order_acquire))) { + // b can be NULL when the waiter is scheduled but queued. + BAIDU_SCOPED_LOCK(b->waiter_lock); + if (b == bw->container.load(butil::memory_order_relaxed)) { + bw->RemoveFromList(); + bw->container.store(NULL, butil::memory_order_relaxed); + if (bw->tid) { + static_cast(bw)->waiter_state = state; + } + erased = true; + break; + } + } + if (erased && wakeup) { + if (bw->tid) { + ButexBthreadWaiter* bbw = static_cast(bw); + auto g = get_task_group(bbw->control, bbw->task_meta->attr.tag); + g->ready_to_run_general(bbw->task_meta); + } else { + ButexPthreadWaiter* pw = static_cast(bw); + wakeup_pthread(pw); + } + } + errno = saved_errno; + return erased; +} + +struct WaitForButexArgs { + ButexBthreadWaiter* bw; + bool prepend; +}; + +void wait_for_butex(void* arg) { + auto args = static_cast(arg); + ButexBthreadWaiter* const bw = args->bw; + Butex* const b = bw->initial_butex; + // 1: waiter with timeout should have waiter_state == WAITER_STATE_READY + // before they're queued, otherwise the waiter is already timedout + // and removed by TimerThread, in which case we should stop queueing. + // + // Visibility of waiter_state: + // [bthread] [TimerThread] + // waiter_state = TIMED + // tt_lock { add task } + // tt_lock { get task } + // waiter_lock { waiter_state=TIMEDOUT } + // waiter_lock { use waiter_state } + // tt_lock represents TimerThread::_mutex. Visibility of waiter_state is + // sequenced by two locks, both threads are guaranteed to see the correct + // value. + { + BAIDU_SCOPED_LOCK(b->waiter_lock); + if (b->value.load(butil::memory_order_relaxed) != bw->expected_value) { + bw->waiter_state = WAITER_STATE_UNMATCHEDVALUE; + } else if (bw->waiter_state == WAITER_STATE_READY/*1*/ && + !bw->task_meta->interrupted) { + if (args->prepend) { + b->waiters.Prepend(bw); + } else { + b->waiters.Append(bw); + } + bw->container.store(b, butil::memory_order_relaxed); +#ifdef BRPC_BTHREAD_TRACER + bw->control->_task_tracer.set_status(TASK_STATUS_SUSPENDED, bw->task_meta); +#endif // BRPC_BTHREAD_TRACER + if (bw->abstime != NULL) { + bw->sleep_id = get_global_timer_thread()->schedule( + erase_from_butex_and_wakeup, bw, *bw->abstime); + if (!bw->sleep_id) { // TimerThread stopped. + errno = ESTOP; + erase_from_butex_and_wakeup(bw); + } + } + return; + } + } + + // b->container is NULL which makes erase_from_butex_and_wakeup() and + // TaskGroup::interrupt() no-op, there's no race between following code and + // the two functions. The on-stack ButexBthreadWaiter is safe to use and + // bw->waiter_state will not change again. + // unsleep_if_necessary(bw, get_global_timer_thread()); + tls_task_group->ready_to_run(bw->task_meta); + // FIXME: jump back to original thread is buggy. + + // // Value unmatched or waiter is already woken up by TimerThread, jump + // // back to original bthread. + // TaskGroup* g = tls_task_group; + // ReadyToRunArgs args = { g->current_tid(), false }; + // g->set_remained(TaskGroup::ready_to_run_in_worker, &args); + // // 2: Don't run remained because we're already in a remained function + // // otherwise stack may overflow. + // TaskGroup::sched_to(&g, bw->tid, false/*2*/); +} + +static int butex_wait_from_pthread(TaskGroup* g, Butex* b, int expected_value, + const timespec* abstime, bool prepend) { + TaskMeta* task = NULL; + ButexPthreadWaiter pw; + pw.tid = 0; + pw.sig.store(PTHREAD_NOT_SIGNALLED, butil::memory_order_relaxed); + int rc = 0; + + if (g) { + task = g->current_task(); + task->current_waiter.store(&pw, butil::memory_order_release); + } + b->waiter_lock.lock(); + if (b->value.load(butil::memory_order_relaxed) != expected_value) { + b->waiter_lock.unlock(); + errno = EWOULDBLOCK; + rc = -1; + } else if (task != NULL && task->interrupted) { + b->waiter_lock.unlock(); + // Race with set and may consume multiple interruptions, which are OK. + task->interrupted = false; + errno = EINTR; + rc = -1; + } else { + if (prepend) { + b->waiters.Prepend(&pw); + } else { + b->waiters.Append(&pw); + } + pw.container.store(b, butil::memory_order_relaxed); + b->waiter_lock.unlock(); + +#ifdef SHOW_BTHREAD_BUTEX_WAITER_COUNT_IN_VARS + bvar::Adder& num_waiters = butex_waiter_count(); + num_waiters << 1; +#endif + rc = wait_pthread(pw, abstime); +#ifdef SHOW_BTHREAD_BUTEX_WAITER_COUNT_IN_VARS + num_waiters << -1; +#endif + } + if (task) { + // If current_waiter is NULL, TaskGroup::interrupt() is running and + // using pw, spin until current_waiter != NULL. + BT_LOOP_WHEN(task->current_waiter.exchange( + NULL, butil::memory_order_acquire) == NULL, + 30/*nops before sched_yield*/); + if (task->interrupted) { + task->interrupted = false; + if (rc == 0) { + errno = EINTR; + return -1; + } + } + } + return rc; +} + +int butex_wait(void* arg, int expected_value, const timespec* abstime, bool prepend) { + Butex* b = container_of(static_cast*>(arg), Butex, value); + if (b->value.load(butil::memory_order_relaxed) != expected_value) { + errno = EWOULDBLOCK; + // Sometimes we may take actions immediately after unmatched butex, + // this fence makes sure that we see changes before changing butex. + butil::atomic_thread_fence(butil::memory_order_acquire); + return -1; + } + TaskGroup* g = tls_task_group; + if (NULL == g || g->is_current_pthread_task()) { + return butex_wait_from_pthread(g, b, expected_value, abstime, prepend); + } + ButexBthreadWaiter bbw; + // tid is 0 iff the thread is non-bthread + bbw.tid = g->current_tid(); + bbw.container.store(NULL, butil::memory_order_relaxed); + bbw.task_meta = g->current_task(); + bbw.sleep_id = 0; + bbw.waiter_state = WAITER_STATE_READY; + bbw.expected_value = expected_value; + bbw.initial_butex = b; + bbw.control = g->control(); + bbw.abstime = abstime; + + if (abstime != NULL) { + // Schedule timer before queueing. If the timer is triggered before + // queueing, cancel queueing. This is a kind of optimistic locking. + if (butil::timespec_to_microseconds(*abstime) < + (butil::gettimeofday_us() + MIN_SLEEP_US)) { + // Already timed out. + errno = ETIMEDOUT; + return -1; + } + } +#ifdef SHOW_BTHREAD_BUTEX_WAITER_COUNT_IN_VARS + bvar::Adder& num_waiters = butex_waiter_count(); + num_waiters << 1; +#endif + + // release fence matches with acquire fence in interrupt_and_consume_waiters + // in task_group.cpp to guarantee visibility of `interrupted'. + bbw.task_meta->current_waiter.store(&bbw, butil::memory_order_release); + WaitForButexArgs args{ &bbw, prepend }; + g->set_remained(wait_for_butex, &args); + TaskGroup::sched(&g); + + // erase_from_butex_and_wakeup (called by TimerThread) is possibly still + // running and using bbw. The chance is small, just spin until it's done. + BT_LOOP_WHEN(unsleep_if_necessary(&bbw, get_global_timer_thread()) < 0, + 30/*nops before sched_yield*/); + + // If current_waiter is NULL, TaskGroup::interrupt() is running and using bbw. + // Spin until current_waiter != NULL. + BT_LOOP_WHEN(bbw.task_meta->current_waiter.exchange( + NULL, butil::memory_order_acquire) == NULL, + 30/*nops before sched_yield*/); +#ifdef SHOW_BTHREAD_BUTEX_WAITER_COUNT_IN_VARS + num_waiters << -1; +#endif + + bool is_interrupted = false; + if (bbw.task_meta->interrupted) { + // Race with set and may consume multiple interruptions, which are OK. + bbw.task_meta->interrupted = false; + is_interrupted = true; + } + // If timed out as well as value unmatched, return ETIMEDOUT. + if (WAITER_STATE_TIMEDOUT == bbw.waiter_state) { + errno = ETIMEDOUT; + return -1; + } else if (WAITER_STATE_UNMATCHEDVALUE == bbw.waiter_state) { + errno = EWOULDBLOCK; + return -1; + } else if (is_interrupted) { + errno = EINTR; + return -1; + } + return 0; +} + +} // namespace bthread + +namespace butil { +template <> struct ObjectPoolBlockMaxItem { + static const size_t value = 128; +}; +} diff --git a/src/bthread/butex.h b/src/bthread/butex.h new file mode 100644 index 0000000..bf86611 --- /dev/null +++ b/src/bthread/butex.h @@ -0,0 +1,90 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Tue Jul 22 17:30:12 CST 2014 + +#ifndef BTHREAD_BUTEX_H +#define BTHREAD_BUTEX_H + +#include // users need to check errno +#include // timespec +#include "butil/macros.h" // BAIDU_CASSERT +#include "bthread/types.h" // bthread_t + +namespace bthread { + +// If a thread would suspend for less than so many microseconds, return +// ETIMEDOUT directly. +// Use 1: sleeping for less than 2 microsecond is inefficient and useless. +static const int64_t MIN_SLEEP_US = 2; + +// Create a butex which is a futex-like 32-bit primitive for synchronizing +// bthreads/pthreads. +// Returns a pointer to 32-bit data, NULL on failure. +// NOTE: all butexes are private(not inter-process). +void* butex_create(); + +// Check width of user type before casting. +template T* butex_create_checked() { + BAIDU_CASSERT(sizeof(T) == sizeof(int), sizeof_T_must_equal_int); + return static_cast(butex_create()); +} + +// Destroy the butex. +void butex_destroy(void* butex); + +// Wake up at most 1 thread waiting on |butex|. +// Returns # of threads woken up. +int butex_wake(void* butex, bool nosignal = false); + +// Wake up all threads waiting on |butex| if n is zero, +// Otherwise, wake up at most n thread waiting on |butex|. +// Returns # of threads woken up. +int butex_wake_n(void* butex, size_t n, bool nosignal = false); + +// Wake up all threads waiting on |butex|. +// Returns # of threads woken up. +int butex_wake_all(void* butex, bool nosignal = false); + +// Wake up all threads waiting on |butex| except a bthread whose identifier +// is |excluded_bthread|. This function does not yield. +// Returns # of threads woken up. +int butex_wake_except(void* butex, bthread_t excluded_bthread); + +// Wake up at most 1 thread waiting on |butex1|, let all other threads wait +// on |butex2| instead. +// Returns # of threads woken up. +int butex_requeue(void* butex1, void* butex2); + +// Atomically wait on |butex| if *butex equals |expected_value|, until the +// butex is woken up by butex_wake*, or CLOCK_REALTIME reached |abstime| if +// abstime is not NULL. +// About |abstime|: +// Different from FUTEX_WAIT, butex_wait uses absolute time. +// About |prepend|: +// If |prepend| is true, queue the bthread at the head of the queue, +// otherwise at the tail. +// Returns 0 on success, -1 otherwise and errno is set. +int butex_wait(void* butex, int expected_value, + const timespec* abstime, + bool prepend = false); + +} // namespace bthread + +#endif // BTHREAD_BUTEX_H diff --git a/src/bthread/comlog_initializer.h b/src/bthread/comlog_initializer.h new file mode 100644 index 0000000..f216328 --- /dev/null +++ b/src/bthread/comlog_initializer.h @@ -0,0 +1,49 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Mon Sep 15 10:51:15 CST 2014 + +#ifndef BTHREAD_COMLOG_INITIALIZER_H +#define BTHREAD_COMLOG_INITIALIZER_H + +#include // com_openlog_r, com_closelog_r +#include "butil/macros.h" + +namespace bthread { + +class ComlogInitializer { +public: + ComlogInitializer() { + if (com_logstatus() != LOG_NOT_DEFINED) { + com_openlog_r(); + } + } + ~ComlogInitializer() { + if (com_logstatus() != LOG_NOT_DEFINED) { + com_closelog_r(); + } + } + +private: + DISALLOW_COPY_AND_ASSIGN(ComlogInitializer); +}; + +} + +#endif // BTHREAD_COMLOG_INITIALIZER_H diff --git a/src/bthread/condition_variable.cpp b/src/bthread/condition_variable.cpp new file mode 100644 index 0000000..e04187d --- /dev/null +++ b/src/bthread/condition_variable.cpp @@ -0,0 +1,143 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Sun Aug 3 12:46:15 CST 2014 + +#include "butil/atomicops.h" +#include "butil/macros.h" // BAIDU_CASSERT +#include "bthread/butex.h" // butex_* +#include "bthread/types.h" // bthread_cond_t + +namespace bthread { +struct CondInternal { + butil::atomic m; + butil::atomic* seq; +}; + +BAIDU_CASSERT(sizeof(CondInternal) == sizeof(bthread_cond_t), + sizeof_innercond_must_equal_cond); +BAIDU_CASSERT(offsetof(CondInternal, m) == offsetof(bthread_cond_t, m), + offsetof_cond_mutex_must_equal); +BAIDU_CASSERT(offsetof(CondInternal, seq) == + offsetof(bthread_cond_t, seq), + offsetof_cond_seq_must_equal); +} + +extern "C" { + +extern int bthread_mutex_unlock(bthread_mutex_t*); +extern int bthread_mutex_lock_contended(bthread_mutex_t*); + +int bthread_cond_init(bthread_cond_t* __restrict c, + const bthread_condattr_t*) { + c->m = NULL; + c->seq = bthread::butex_create_checked(); + *c->seq = 0; + return 0; +} + +int bthread_cond_destroy(bthread_cond_t* c) { + bthread::butex_destroy(c->seq); + c->seq = NULL; + return 0; +} + +int bthread_cond_signal(bthread_cond_t* c) { + bthread::CondInternal* ic = reinterpret_cast(c); + // ic is probably dereferenced after fetch_add, save required fields before + // this point + butil::atomic* const saved_seq = ic->seq; + saved_seq->fetch_add(1, butil::memory_order_release); + // don't touch ic any more + bthread::butex_wake(saved_seq); + return 0; +} + +int bthread_cond_broadcast(bthread_cond_t* c) { + bthread::CondInternal* ic = reinterpret_cast(c); + bthread_mutex_t* m = ic->m.load(butil::memory_order_relaxed); + butil::atomic* const saved_seq = ic->seq; + if (!m) { + return 0; + } + void* const saved_butex = m->butex; + // Wakeup one thread and requeue the rest on the mutex. + ic->seq->fetch_add(1, butil::memory_order_release); + bthread::butex_requeue(saved_seq, saved_butex); + return 0; +} + +int bthread_cond_wait(bthread_cond_t* __restrict c, + bthread_mutex_t* __restrict m) { + bthread::CondInternal* ic = reinterpret_cast(c); + const int expected_seq = ic->seq->load(butil::memory_order_relaxed); + if (ic->m.load(butil::memory_order_relaxed) != m) { + // bind m to c + bthread_mutex_t* expected_m = NULL; + if (!ic->m.compare_exchange_strong( + expected_m, m, butil::memory_order_relaxed)) { + return EINVAL; + } + } + bthread_mutex_unlock(m); + int rc1 = 0; + if (bthread::butex_wait(ic->seq, expected_seq, NULL) < 0 && + errno != EWOULDBLOCK && errno != EINTR/*note*/) { + // EINTR should not be returned by cond_*wait according to docs on + // pthread, however spurious wake-up is OK, just as we do here + // so that users can check flags in the loop often companioning + // with the cond_wait ASAP. For example: + // mutex.lock(); + // while (!stop && other-predicates) { + // cond_wait(&mutex); + // } + // mutex.unlock(); + // After interruption, above code should wake up from the cond_wait + // soon and check the `stop' flag and other predicates. + rc1 = errno; + } + const int rc2 = bthread_mutex_lock_contended(m); + return (rc2 ? rc2 : rc1); +} + +int bthread_cond_timedwait(bthread_cond_t* __restrict c, + bthread_mutex_t* __restrict m, + const struct timespec* __restrict abstime) { + bthread::CondInternal* ic = reinterpret_cast(c); + const int expected_seq = ic->seq->load(butil::memory_order_relaxed); + if (ic->m.load(butil::memory_order_relaxed) != m) { + // bind m to c + bthread_mutex_t* expected_m = NULL; + if (!ic->m.compare_exchange_strong( + expected_m, m, butil::memory_order_relaxed)) { + return EINVAL; + } + } + bthread_mutex_unlock(m); + int rc1 = 0; + if (bthread::butex_wait(ic->seq, expected_seq, abstime) < 0 && + errno != EWOULDBLOCK && errno != EINTR/*note*/) { + // note: see comments in bthread_cond_wait on EINTR. + rc1 = errno; + } + const int rc2 = bthread_mutex_lock_contended(m); + return (rc2 ? rc2 : rc1); +} + +} // extern "C" diff --git a/src/bthread/condition_variable.h b/src/bthread/condition_variable.h new file mode 100644 index 0000000..fb6bb4b --- /dev/null +++ b/src/bthread/condition_variable.h @@ -0,0 +1,120 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: 2015/12/14 21:26:26 + +#ifndef BTHREAD_CONDITION_VARIABLE_H +#define BTHREAD_CONDITION_VARIABLE_H + +#include "butil/time.h" +#include "bthread/mutex.h" + +__BEGIN_DECLS +extern int bthread_cond_init(bthread_cond_t* __restrict cond, + const bthread_condattr_t* __restrict cond_attr); +extern int bthread_cond_destroy(bthread_cond_t* cond); +extern int bthread_cond_signal(bthread_cond_t* cond); +extern int bthread_cond_broadcast(bthread_cond_t* cond); +extern int bthread_cond_wait(bthread_cond_t* __restrict cond, + bthread_mutex_t* __restrict mutex); +extern int bthread_cond_timedwait( + bthread_cond_t* __restrict cond, + bthread_mutex_t* __restrict mutex, + const struct timespec* __restrict abstime); +__END_DECLS + +namespace bthread { + +class ConditionVariable { + DISALLOW_COPY_AND_ASSIGN(ConditionVariable); +public: + typedef bthread_cond_t* native_handler_type; + + ConditionVariable() { + CHECK_EQ(0, bthread_cond_init(&_cond, NULL)); + } + ~ConditionVariable() { + CHECK_EQ(0, bthread_cond_destroy(&_cond)); + } + + native_handler_type native_handler() { return &_cond; } + + void wait(std::unique_lock& lock) { + bthread_cond_wait(&_cond, lock.mutex()->native_handler()); + } + + void wait(std::unique_lock& lock) { + bthread_cond_wait(&_cond, lock.mutex()); + } + + template + void wait(std::unique_lock& lock, Predicate p) { + while (!p()) { + bthread_cond_wait(&_cond, lock.mutex()->native_handler()); + } + } + + template + void wait(std::unique_lock& lock, Predicate p) { + while (!p()) { + bthread_cond_wait(&_cond, lock.mutex()); + } + } + + // Unlike std::condition_variable, we return ETIMEDOUT when time expires + // rather than std::timeout + int wait_for(std::unique_lock& lock, + long timeout_us) { + return wait_until(lock, butil::microseconds_from_now(timeout_us)); + } + + int wait_for(std::unique_lock& lock, + long timeout_us) { + return wait_until(lock, butil::microseconds_from_now(timeout_us)); + } + + int wait_until(std::unique_lock& lock, + timespec duetime) { + const int rc = bthread_cond_timedwait( + &_cond, lock.mutex()->native_handler(), &duetime); + return rc == ETIMEDOUT ? ETIMEDOUT : 0; + } + + int wait_until(std::unique_lock& lock, + timespec duetime) { + const int rc = bthread_cond_timedwait( + &_cond, lock.mutex(), &duetime); + return rc == ETIMEDOUT ? ETIMEDOUT : 0; + } + + void notify_one() { + bthread_cond_signal(&_cond); + } + + void notify_all() { + bthread_cond_broadcast(&_cond); + } + +private: + bthread_cond_t _cond; +}; + +} // namespace bthread + +#endif //BTHREAD_CONDITION_VARIABLE_H diff --git a/src/bthread/context.cpp b/src/bthread/context.cpp new file mode 100644 index 0000000..7f913ad --- /dev/null +++ b/src/bthread/context.cpp @@ -0,0 +1,994 @@ +/* + + auto-generated file, do not modify! + libcontext - a slightly more portable version of boost::context + Copyright Martin Husemann 2013. + Copyright Oliver Kowalke 2009. + Copyright Sergue E. Leontiev 2013 + Copyright Thomas Sailer 2013. + Minor modifications by Tomasz Wlostowski 2016. + + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) + +*/ +#include "bthread/context.h" +#if defined(BTHREAD_CONTEXT_PLATFORM_windows_i386) && defined(BTHREAD_CONTEXT_COMPILER_gcc) +__asm ( +".text\n" +".p2align 4,,15\n" +".globl _bthread_jump_fcontext\n" +".def _bthread_jump_fcontext; .scl 2; .type 32; .endef\n" +"_bthread_jump_fcontext:\n" +" mov 0x10(%esp),%ecx\n" +" push %ebp\n" +" push %ebx\n" +" push %esi\n" +" push %edi\n" +" mov %fs:0x18,%edx\n" +" mov (%edx),%eax\n" +" push %eax\n" +" mov 0x4(%edx),%eax\n" +" push %eax\n" +" mov 0x8(%edx),%eax\n" +" push %eax\n" +" mov 0xe0c(%edx),%eax\n" +" push %eax\n" +" mov 0x10(%edx),%eax\n" +" push %eax\n" +" lea -0x8(%esp),%esp\n" +" test %ecx,%ecx\n" +" je nxt1\n" +" stmxcsr (%esp)\n" +" fnstcw 0x4(%esp)\n" +"nxt1:\n" +" mov 0x30(%esp),%eax\n" +" mov %esp,(%eax)\n" +" mov 0x34(%esp),%edx\n" +" mov 0x38(%esp),%eax\n" +" mov %edx,%esp\n" +" test %ecx,%ecx\n" +" je nxt2\n" +" ldmxcsr (%esp)\n" +" fldcw 0x4(%esp)\n" +"nxt2:\n" +" lea 0x8(%esp),%esp\n" +" mov %fs:0x18,%edx\n" +" pop %ecx\n" +" mov %ecx,0x10(%edx)\n" +" pop %ecx\n" +" mov %ecx,0xe0c(%edx)\n" +" pop %ecx\n" +" mov %ecx,0x8(%edx)\n" +" pop %ecx\n" +" mov %ecx,0x4(%edx)\n" +" pop %ecx\n" +" mov %ecx,(%edx)\n" +" pop %edi\n" +" pop %esi\n" +" pop %ebx\n" +" pop %ebp\n" +" pop %edx\n" +" mov %eax,0x4(%esp)\n" +" jmp *%edx\n" +); + +#endif + +#if defined(BTHREAD_CONTEXT_PLATFORM_windows_i386) && defined(BTHREAD_CONTEXT_COMPILER_gcc) +__asm ( +".text\n" +".p2align 4,,15\n" +".globl _bthread_make_fcontext\n" +".def _bthread_make_fcontext; .scl 2; .type 32; .endef\n" +"_bthread_make_fcontext:\n" +"mov 0x4(%esp),%eax\n" +"lea -0x8(%eax),%eax\n" +"and $0xfffffff0,%eax\n" +"lea -0x3c(%eax),%eax\n" +"mov 0x4(%esp),%ecx\n" +"mov %ecx,0x14(%eax)\n" +"mov 0x8(%esp),%edx\n" +"neg %edx\n" +"lea (%ecx,%edx,1),%ecx\n" +"mov %ecx,0x10(%eax)\n" +"mov %ecx,0xc(%eax)\n" +"mov 0xc(%esp),%ecx\n" +"mov %ecx,0x2c(%eax)\n" +"stmxcsr (%eax)\n" +"fnstcw 0x4(%eax)\n" +"mov $finish,%ecx\n" +"mov %ecx,0x30(%eax)\n" +"mov %fs:0x0,%ecx\n" +"walk:\n" +"mov (%ecx),%edx\n" +"inc %edx\n" +"je found\n" +"dec %edx\n" +"xchg %edx,%ecx\n" +"jmp walk\n" +"found:\n" +"mov 0x4(%ecx),%ecx\n" +"mov %ecx,0x3c(%eax)\n" +"mov $0xffffffff,%ecx\n" +"mov %ecx,0x38(%eax)\n" +"lea 0x38(%eax),%ecx\n" +"mov %ecx,0x18(%eax)\n" +"ret\n" +"finish:\n" +"xor %eax,%eax\n" +"mov %eax,(%esp)\n" +"call _exit\n" +"hlt\n" +".def __exit; .scl 2; .type 32; .endef \n" +); + +#endif + +#if defined(BTHREAD_CONTEXT_PLATFORM_windows_x86_64) && defined(BTHREAD_CONTEXT_COMPILER_gcc) +__asm ( +".text\n" +".p2align 4,,15\n" +".globl bthread_jump_fcontext\n" +".def bthread_jump_fcontext; .scl 2; .type 32; .endef\n" +".seh_proc bthread_jump_fcontext\n" +"bthread_jump_fcontext:\n" +".seh_endprologue\n" +" push %rbp\n" +" push %rbx\n" +" push %rsi\n" +" push %rdi\n" +" push %r15\n" +" push %r14\n" +" push %r13\n" +" push %r12\n" +" mov %gs:0x30,%r10\n" +" mov 0x8(%r10),%rax\n" +" push %rax\n" +" mov 0x10(%r10),%rax\n" +" push %rax\n" +" mov 0x1478(%r10),%rax\n" +" push %rax\n" +" mov 0x18(%r10),%rax\n" +" push %rax\n" +" lea -0xa8(%rsp),%rsp\n" +" test %r9,%r9\n" +" je nxt1\n" +" stmxcsr 0xa0(%rsp)\n" +" fnstcw 0xa4(%rsp)\n" +" movaps %xmm6,(%rsp)\n" +" movaps %xmm7,0x10(%rsp)\n" +" movaps %xmm8,0x20(%rsp)\n" +" movaps %xmm9,0x30(%rsp)\n" +" movaps %xmm10,0x40(%rsp)\n" +" movaps %xmm11,0x50(%rsp)\n" +" movaps %xmm12,0x60(%rsp)\n" +" movaps %xmm13,0x70(%rsp)\n" +" movaps %xmm14,0x80(%rsp)\n" +" movaps %xmm15,0x90(%rsp)\n" +"nxt1:\n" +" xor %r10,%r10\n" +" push %r10\n" +" mov %rsp,(%rcx)\n" +" mov %rdx,%rsp\n" +" pop %r10\n" +" test %r9,%r9\n" +" je nxt2\n" +" ldmxcsr 0xa0(%rsp)\n" +" fldcw 0xa4(%rsp)\n" +" movaps (%rsp),%xmm6\n" +" movaps 0x10(%rsp),%xmm7\n" +" movaps 0x20(%rsp),%xmm8\n" +" movaps 0x30(%rsp),%xmm9\n" +" movaps 0x40(%rsp),%xmm10\n" +" movaps 0x50(%rsp),%xmm11\n" +" movaps 0x60(%rsp),%xmm12\n" +" movaps 0x70(%rsp),%xmm13\n" +" movaps 0x80(%rsp),%xmm14\n" +" movaps 0x90(%rsp),%xmm15\n" +"nxt2:\n" +" mov $0xa8,%rcx\n" +" test %r10,%r10\n" +" je nxt3\n" +" add $0x8,%rcx\n" +"nxt3:\n" +" lea (%rsp,%rcx,1),%rsp\n" +" mov %gs:0x30,%r10\n" +" pop %rax\n" +" mov %rax,0x18(%r10)\n" +" pop %rax\n" +" mov %rax,0x1478(%r10)\n" +" pop %rax\n" +" mov %rax,0x10(%r10)\n" +" pop %rax\n" +" mov %rax,0x8(%r10)\n" +" pop %r12\n" +" pop %r13\n" +" pop %r14\n" +" pop %r15\n" +" pop %rdi\n" +" pop %rsi\n" +" pop %rbx\n" +" pop %rbp\n" +" pop %r10\n" +" mov %r8,%rax\n" +" mov %r8,%rcx\n" +" jmpq *%r10\n" +".seh_endproc\n" +); + +#endif + +#if defined(BTHREAD_CONTEXT_PLATFORM_windows_x86_64) && defined(BTHREAD_CONTEXT_COMPILER_gcc) +__asm ( +".text\n" +".p2align 4,,15\n" +".globl bthread_make_fcontext\n" +".def bthread_make_fcontext; .scl 2; .type 32; .endef\n" +".seh_proc bthread_make_fcontext\n" +"bthread_make_fcontext:\n" +".seh_endprologue\n" +"mov %rcx,%rax\n" +"sub $0x28,%rax\n" +"and $0xfffffffffffffff0,%rax\n" +"sub $0x128,%rax\n" +"mov %r8,0x118(%rax)\n" +"mov %rcx,0xd0(%rax)\n" +"neg %rdx\n" +"lea (%rcx,%rdx,1),%rcx\n" +"mov %rcx,0xc8(%rax)\n" +"mov %rcx,0xc0(%rax)\n" +"stmxcsr 0xa8(%rax)\n" +"fnstcw 0xac(%rax)\n" +"leaq finish(%rip), %rcx\n" +"mov %rcx,0x120(%rax)\n" +"mov $0x1,%rcx\n" +"mov %rcx,(%rax)\n" +"retq\n" +"finish:\n" +"xor %rcx,%rcx\n" +"callq 0x63\n" +"hlt\n" +" .seh_endproc\n" +".def _exit; .scl 2; .type 32; .endef \n" +); + +#endif + +#if defined(BTHREAD_CONTEXT_PLATFORM_linux_i386) && defined(BTHREAD_CONTEXT_COMPILER_gcc) +__asm ( +".text\n" +".globl bthread_jump_fcontext\n" +".align 2\n" +".type bthread_jump_fcontext,@function\n" +"bthread_jump_fcontext:\n" +" movl 0x10(%esp), %ecx\n" +" pushl %ebp \n" +" pushl %ebx \n" +" pushl %esi \n" +" pushl %edi \n" +" leal -0x8(%esp), %esp\n" +" test %ecx, %ecx\n" +" je 1f\n" +" stmxcsr (%esp)\n" +" fnstcw 0x4(%esp)\n" +"1:\n" +" movl 0x1c(%esp), %eax\n" +" movl %esp, (%eax)\n" +" movl 0x20(%esp), %edx\n" +" movl 0x24(%esp), %eax\n" +" movl %edx, %esp\n" +" test %ecx, %ecx\n" +" je 2f\n" +" ldmxcsr (%esp)\n" +" fldcw 0x4(%esp)\n" +"2:\n" +" leal 0x8(%esp), %esp\n" +" popl %edi \n" +" popl %esi \n" +" popl %ebx \n" +" popl %ebp \n" +" popl %edx\n" +" movl %eax, 0x4(%esp)\n" +" jmp *%edx\n" +".size bthread_jump_fcontext,.-bthread_jump_fcontext\n" +".section .note.GNU-stack,\"\",%progbits\n" +".previous\n" +); + +#endif + +#if defined(BTHREAD_CONTEXT_PLATFORM_linux_i386) && defined(BTHREAD_CONTEXT_COMPILER_gcc) +__asm ( +".text\n" +".globl bthread_make_fcontext\n" +".align 2\n" +".type bthread_make_fcontext,@function\n" +"bthread_make_fcontext:\n" +" movl 0x4(%esp), %eax\n" +" leal -0x8(%eax), %eax\n" +" andl $-16, %eax\n" +" leal -0x20(%eax), %eax\n" +" movl 0xc(%esp), %edx\n" +" movl %edx, 0x18(%eax)\n" +" stmxcsr (%eax)\n" +" fnstcw 0x4(%eax)\n" +" call 1f\n" +"1: popl %ecx\n" +" addl $finish-1b, %ecx\n" +" movl %ecx, 0x1c(%eax)\n" +" ret \n" +"finish:\n" +" call 2f\n" +"2: popl %ebx\n" +" addl $_GLOBAL_OFFSET_TABLE_+[.-2b], %ebx\n" +" xorl %eax, %eax\n" +" movl %eax, (%esp)\n" +" call _exit@PLT\n" +" hlt\n" +".size bthread_make_fcontext,.-bthread_make_fcontext\n" +".section .note.GNU-stack,\"\",%progbits\n" +".previous\n" +); + +#endif + +#if defined(BTHREAD_CONTEXT_PLATFORM_linux_x86_64) && defined(BTHREAD_CONTEXT_COMPILER_gcc) +__asm ( +".text\n" +".globl bthread_jump_fcontext\n" +".type bthread_jump_fcontext,@function\n" +".align 16\n" +"bthread_jump_fcontext:\n" +" pushq %rbp \n" +" pushq %rbx \n" +" pushq %r15 \n" +" pushq %r14 \n" +" pushq %r13 \n" +" pushq %r12 \n" +" leaq -0x8(%rsp), %rsp\n" +" cmp $0, %rcx\n" +" je 1f\n" +" stmxcsr (%rsp)\n" +" fnstcw 0x4(%rsp)\n" +"1:\n" +" movq %rsp, (%rdi)\n" +" movq %rsi, %rsp\n" +" cmp $0, %rcx\n" +" je 2f\n" +" ldmxcsr (%rsp)\n" +" fldcw 0x4(%rsp)\n" +"2:\n" +" leaq 0x8(%rsp), %rsp\n" +" popq %r12 \n" +" popq %r13 \n" +" popq %r14 \n" +" popq %r15 \n" +" popq %rbx \n" +" popq %rbp \n" +" popq %r8\n" +" movq %rdx, %rax\n" +" movq %rdx, %rdi\n" +" jmp *%r8\n" +".size bthread_jump_fcontext,.-bthread_jump_fcontext\n" +".section .note.GNU-stack,\"\",%progbits\n" +".previous\n" +); + +#endif + +#if defined(BTHREAD_CONTEXT_PLATFORM_linux_x86_64) && defined(BTHREAD_CONTEXT_COMPILER_gcc) +__asm ( +".text\n" +".globl bthread_make_fcontext\n" +".type bthread_make_fcontext,@function\n" +".align 16\n" +"bthread_make_fcontext:\n" +" movq %rdi, %rax\n" +" andq $-16, %rax\n" +" leaq -0x48(%rax), %rax\n" +" movq %rdx, 0x38(%rax)\n" +" stmxcsr (%rax)\n" +" fnstcw 0x4(%rax)\n" +" leaq finish(%rip), %rcx\n" +" movq %rcx, 0x40(%rax)\n" +" ret \n" +"finish:\n" +" xorq %rdi, %rdi\n" +" call _exit@PLT\n" +" hlt\n" +".size bthread_make_fcontext,.-bthread_make_fcontext\n" +".section .note.GNU-stack,\"\",%progbits\n" +".previous\n" +); + +#endif + +#if defined(BTHREAD_CONTEXT_PLATFORM_apple_x86_64) && defined(BTHREAD_CONTEXT_COMPILER_gcc) +__asm ( +".text\n" +".globl _bthread_jump_fcontext\n" +".align 8\n" +"_bthread_jump_fcontext:\n" +" pushq %rbp \n" +" pushq %rbx \n" +" pushq %r15 \n" +" pushq %r14 \n" +" pushq %r13 \n" +" pushq %r12 \n" +" leaq -0x8(%rsp), %rsp\n" +" cmp $0, %rcx\n" +" je 1f\n" +" stmxcsr (%rsp)\n" +" fnstcw 0x4(%rsp)\n" +"1:\n" +" movq %rsp, (%rdi)\n" +" movq %rsi, %rsp\n" +" cmp $0, %rcx\n" +" je 2f\n" +" ldmxcsr (%rsp)\n" +" fldcw 0x4(%rsp)\n" +"2:\n" +" leaq 0x8(%rsp), %rsp\n" +" popq %r12 \n" +" popq %r13 \n" +" popq %r14 \n" +" popq %r15 \n" +" popq %rbx \n" +" popq %rbp \n" +" popq %r8\n" +" movq %rdx, %rax\n" +" movq %rdx, %rdi\n" +" jmp *%r8\n" +); + +#endif + +#if defined(BTHREAD_CONTEXT_PLATFORM_apple_x86_64) && defined(BTHREAD_CONTEXT_COMPILER_gcc) +__asm ( +".text\n" +".globl _bthread_make_fcontext\n" +".align 8\n" +"_bthread_make_fcontext:\n" +" movq %rdi, %rax\n" +" movabs $-16, %r8\n" +" andq %r8, %rax\n" +" leaq -0x48(%rax), %rax\n" +" movq %rdx, 0x38(%rax)\n" +" stmxcsr (%rax)\n" +" fnstcw 0x4(%rax)\n" +" leaq finish(%rip), %rcx\n" +" movq %rcx, 0x40(%rax)\n" +" ret \n" +"finish:\n" +" xorq %rdi, %rdi\n" +" call __exit\n" +" hlt\n" +); + +#endif + +#if defined(BTHREAD_CONTEXT_PLATFORM_apple_i386) && defined(BTHREAD_CONTEXT_COMPILER_gcc) +__asm ( +".text\n" +".globl _bthread_jump_fcontext\n" +".align 2\n" +"_bthread_jump_fcontext:\n" +" movl 0x10(%esp), %ecx\n" +" pushl %ebp \n" +" pushl %ebx \n" +" pushl %esi \n" +" pushl %edi \n" +" leal -0x8(%esp), %esp\n" +" test %ecx, %ecx\n" +" je 1f\n" +" stmxcsr (%esp)\n" +" fnstcw 0x4(%esp)\n" +"1:\n" +" movl 0x1c(%esp), %eax\n" +" movl %esp, (%eax)\n" +" movl 0x20(%esp), %edx\n" +" movl 0x24(%esp), %eax\n" +" movl %edx, %esp\n" +" test %ecx, %ecx\n" +" je 2f\n" +" ldmxcsr (%esp)\n" +" fldcw 0x4(%esp)\n" +"2:\n" +" leal 0x8(%esp), %esp\n" +" popl %edi \n" +" popl %esi \n" +" popl %ebx \n" +" popl %ebp \n" +" popl %edx\n" +" movl %eax, 0x4(%esp)\n" +" jmp *%edx\n" +); + +#endif + +#if defined(BTHREAD_CONTEXT_PLATFORM_apple_i386) && defined(BTHREAD_CONTEXT_COMPILER_gcc) +__asm ( +".text\n" +".globl _bthread_make_fcontext\n" +".align 2\n" +"_bthread_make_fcontext:\n" +" movl 0x4(%esp), %eax\n" +" leal -0x8(%eax), %eax\n" +" andl $-16, %eax\n" +" leal -0x20(%eax), %eax\n" +" movl 0xc(%esp), %edx\n" +" movl %edx, 0x18(%eax)\n" +" stmxcsr (%eax)\n" +" fnstcw 0x4(%eax)\n" +" call 1f\n" +"1: popl %ecx\n" +" addl $finish-1b, %ecx\n" +" movl %ecx, 0x1c(%eax)\n" +" ret \n" +"finish:\n" +" xorl %eax, %eax\n" +" movl %eax, (%esp)\n" +" call __exit\n" +" hlt\n" +); + +#endif + +#if defined(BTHREAD_CONTEXT_PLATFORM_linux_arm32) && defined(BTHREAD_CONTEXT_COMPILER_gcc) +__asm ( +".text\n" +".globl bthread_jump_fcontext\n" +".align 2\n" +".type bthread_jump_fcontext,%function\n" +"bthread_jump_fcontext:\n" +" @ save LR as PC\n" +" push {lr}\n" +" @ save V1-V8,LR\n" +" push {v1-v8,lr}\n" +" @ prepare stack for FPU\n" +" sub sp, sp, #64\n" +" @ test if fpu env should be preserved\n" +" cmp a4, #0\n" +" beq 1f\n" +" @ save S16-S31\n" +" vstmia sp, {d8-d15}\n" +"1:\n" +" @ store RSP (pointing to context-data) in A1\n" +" str sp, [a1]\n" +" @ restore RSP (pointing to context-data) from A2\n" +" mov sp, a2\n" +" @ test if fpu env should be preserved\n" +" cmp a4, #0\n" +" beq 2f\n" +" @ restore S16-S31\n" +" vldmia sp, {d8-d15}\n" +"2:\n" +" @ prepare stack for FPU\n" +" add sp, sp, #64\n" +" @ use third arg as return value after jump\n" +" @ and as first arg in context function\n" +" mov a1, a3\n" +" @ restore v1-V8,LR,PC\n" +" pop {v1-v8,lr}\n" +" pop {pc}\n" +".size bthread_jump_fcontext,.-bthread_jump_fcontext\n" +"@ Mark that we don't need executable stack.\n" +".section .note.GNU-stack,\"\",%progbits\n" +".previous\n" +); + +#endif + +#if defined(BTHREAD_CONTEXT_PLATFORM_linux_arm32) && defined(BTHREAD_CONTEXT_COMPILER_gcc) +__asm ( +".text\n" +".globl bthread_make_fcontext\n" +".align 2\n" +".type bthread_make_fcontext,%function\n" +"bthread_make_fcontext:\n" +" @ shift address in A1 to lower 16 byte boundary\n" +" bic a1, a1, #15\n" +" @ reserve space for context-data on context-stack\n" +" sub a1, a1, #104\n" +" @ third arg of bthread_make_fcontext() == address of context-function\n" +" str a3, [a1,#100]\n" +" @ compute abs address of label finish\n" +" adr a2, finish\n" +" @ save address of finish as return-address for context-function\n" +" @ will be entered after context-function returns\n" +" str a2, [a1,#96]\n" +" bx lr @ return pointer to context-data\n" +"finish:\n" +" @ exit code is zero\n" +" mov a1, #0\n" +" @ exit application\n" +" bl _exit@PLT\n" +".size bthread_make_fcontext,.-bthread_make_fcontext\n" +"@ Mark that we don't need executable stack.\n" +".section .note.GNU-stack,\"\",%progbits\n" +".previous\n" +); + +#endif + +#if defined(BTHREAD_CONTEXT_PLATFORM_linux_arm64) && defined(BTHREAD_CONTEXT_COMPILER_gcc) +__asm ( +".cpu generic+fp+simd\n" +".text\n" +".align 2\n" +".global bthread_jump_fcontext\n" +".type bthread_jump_fcontext, %function\n" +"bthread_jump_fcontext:\n" +" # prepare stack for GP + FPU\n" +" sub sp, sp, #0xb0\n" +"# Because gcc may save integer registers in fp registers across a\n" +"# function call we cannot skip saving the fp registers.\n" +"#\n" +"# Do not reinstate this test unless you fully understand what you\n" +"# are doing.\n" +"#\n" +"# # test if fpu env should be preserved\n" +"# cmp w3, #0\n" +"# b.eq 1f\n" +" # save d8 - d15\n" +" stp d8, d9, [sp, #0x00]\n" +" stp d10, d11, [sp, #0x10]\n" +" stp d12, d13, [sp, #0x20]\n" +" stp d14, d15, [sp, #0x30]\n" +"1:\n" +" # save x19-x30\n" +" stp x19, x20, [sp, #0x40]\n" +" stp x21, x22, [sp, #0x50]\n" +" stp x23, x24, [sp, #0x60]\n" +" stp x25, x26, [sp, #0x70]\n" +" stp x27, x28, [sp, #0x80]\n" +" stp x29, x30, [sp, #0x90]\n" +" # save LR as PC\n" +" str x30, [sp, #0xa0]\n" +" # store RSP (pointing to context-data) in first argument (x0).\n" +" # STR cannot have sp as a target register\n" +" mov x4, sp\n" +" str x4, [x0]\n" +" # restore RSP (pointing to context-data) from A2 (x1)\n" +" mov sp, x1\n" +"# # test if fpu env should be preserved\n" +"# cmp w3, #0\n" +"# b.eq 2f\n" +" # load d8 - d15\n" +" ldp d8, d9, [sp, #0x00]\n" +" ldp d10, d11, [sp, #0x10]\n" +" ldp d12, d13, [sp, #0x20]\n" +" ldp d14, d15, [sp, #0x30]\n" +"2:\n" +" # load x19-x30\n" +" ldp x19, x20, [sp, #0x40]\n" +" ldp x21, x22, [sp, #0x50]\n" +" ldp x23, x24, [sp, #0x60]\n" +" ldp x25, x26, [sp, #0x70]\n" +" ldp x27, x28, [sp, #0x80]\n" +" ldp x29, x30, [sp, #0x90]\n" +" # use third arg as return value after jump\n" +" # and as first arg in context function\n" +" mov x0, x2\n" +" # load pc\n" +" ldr x4, [sp, #0xa0]\n" +" # restore stack from GP + FPU\n" +" add sp, sp, #0xb0\n" +" ret x4\n" +".size bthread_jump_fcontext,.-bthread_jump_fcontext\n" +"# Mark that we don't need executable stack.\n" +".section .note.GNU-stack,\"\",%progbits\n" +".previous\n" +); + +#endif + +#if defined(BTHREAD_CONTEXT_PLATFORM_linux_arm64) && defined(BTHREAD_CONTEXT_COMPILER_gcc) +__asm ( +".cpu generic+fp+simd\n" +".text\n" +".align 2\n" +".global bthread_make_fcontext\n" +".type bthread_make_fcontext, %function\n" +"bthread_make_fcontext:\n" +" # shift address in x0 (allocated stack) to lower 16 byte boundary\n" +" and x0, x0, ~0xF\n" +" # reserve space for context-data on context-stack\n" +" sub x0, x0, #0xb0\n" +" # third arg of bthread_make_fcontext() == address of context-function\n" +" # store address as a PC to jump in\n" +" str x2, [x0, #0xa0]\n" +" # save address of finish as return-address for context-function\n" +" # will be entered after context-function returns (LR register)\n" +" adr x1, finish\n" +" str x1, [x0, #0x98]\n" +" ret x30 \n" +"finish:\n" +" # exit code is zero\n" +" mov x0, #0\n" +" # exit application\n" +" bl _exit\n" +".size bthread_make_fcontext,.-bthread_make_fcontext\n" +"# Mark that we don't need executable stack.\n" +".section .note.GNU-stack,\"\",%progbits\n" +".previous\n" +); + +#endif + + +#if defined(BTHREAD_CONTEXT_PLATFORM_apple_arm64) && defined(BTHREAD_CONTEXT_COMPILER_gcc) +__asm ( +".text\n" +".globl _bthread_jump_fcontext\n" +".balign 16\n" +"_bthread_jump_fcontext:\n" +" ; prepare stack for GP + FPU\n" +" sub sp, sp, #0xb0\n" +"#if (defined(__VFP_FP__) && !defined(__SOFTFP__))\n" +" ; test if fpu env should be preserved\n" +" cmp w3, #0\n" +" b.eq 1f\n" +" ; save d8 - d15\n" +" stp d8, d9, [sp, #0x00]\n" +" stp d10, d11, [sp, #0x10]\n" +" stp d12, d13, [sp, #0x20]\n" +" stp d14, d15, [sp, #0x30]\n" +"1:\n" +"#endif\n" +" ; save x19-x30\n" +" stp x19, x20, [sp, #0x40]\n" +" stp x21, x22, [sp, #0x50]\n" +" stp x23, x24, [sp, #0x60]\n" +" stp x25, x26, [sp, #0x70]\n" +" stp x27, x28, [sp, #0x80]\n" +" stp fp, lr, [sp, #0x90]\n" +" ; save LR as PC\n" +" str lr, [sp, #0xa0]\n" +" ; store RSP (pointing to context-data) in first argument (x0).\n" +" ; STR cannot have sp as a target register\n" +" mov x4, sp\n" +" str x4, [x0]\n" +" ; restore RSP (pointing to context-data) from A2 (x1)\n" +" mov sp, x1\n" +"#if (defined(__VFP_FP__) && !defined(__SOFTFP__))\n" +" ; test if fpu env should be preserved\n" +" cmp w3, #0\n" +" b.eq 2f\n" +" ; load d8 - d15\n" +" ldp d8, d9, [sp, #0x00]\n" +" ldp d10, d11, [sp, #0x10]\n" +" ldp d12, d13, [sp, #0x20]\n" +" ldp d14, d15, [sp, #0x30]\n" +"2:\n" +"#endif\n" +" ; load x19-x30\n" +" ldp x19, x20, [sp, #0x40]\n" +" ldp x21, x22, [sp, #0x50]\n" +" ldp x23, x24, [sp, #0x60]\n" +" ldp x25, x26, [sp, #0x70]\n" +" ldp x27, x28, [sp, #0x80]\n" +" ldp fp, lr, [sp, #0x90]\n" +" ; use third arg as return value after jump\n" +" ; and as first arg in context function\n" +" mov x0, x2\n" +" ; load pc\n" +" ldr x4, [sp, #0xa0]\n" +" ; restore stack from GP + FPU\n" +" add sp, sp, #0xb0\n" +" ret x4\n" +); + +#endif + +#if defined(BTHREAD_CONTEXT_PLATFORM_apple_arm64) && defined(BTHREAD_CONTEXT_COMPILER_gcc) +__asm ( +".text\n" +".globl _bthread_make_fcontext\n" +".balign 16\n" +"_bthread_make_fcontext:\n" +" ; shift address in x0 (allocated stack) to lower 16 byte boundary\n" +" and x0, x0, ~0xF\n" +" ; reserve space for context-data on context-stack\n" +" sub x0, x0, #0xb0\n" +" ; third arg of make_fcontext() == address of context-function\n" +" ; store address as a PC to jump in\n" +" str x2, [x0, #0xa0]\n" +" ; compute abs address of label finish\n" +" ; 0x0c = 3 instructions * size (4) before label 'finish'\n" +" ; TODO: Numeric offset since llvm still does not support labels in ADR. Fix:\n" +" ; http:\n" +" adr x1, 0x0c\n" +" ; save address of finish as return-address for context-function\n" +" ; will be entered after context-function returns (LR register)\n" +" str x1, [x0, #0x98]\n" +" ret lr ; return pointer to context-data (x0)\n" +"finish:\n" +" ; exit code is zero\n" +" mov x0, #0\n" +" ; exit application\n" +" bl __exit\n" +); + +#endif + +#if defined(BTHREAD_CONTEXT_PLATFORM_linux_loongarch64) && defined(BTHREAD_CONTEXT_COMPILER_gcc) +__asm ( +".text\n" +".align 3\n" +".global bthread_jump_fcontext\n" +".type bthread_jump_fcontext, %function\n" +"bthread_jump_fcontext:\n" +" addi.d $sp, $sp, -160\n" + +" st.d $s0, $sp, 64 # save S0\n" +" st.d $s1, $sp, 72 # save S1\n" +" st.d $s2, $sp, 80 # save S2\n" +" st.d $s3, $sp, 88 # save S3\n" +" st.d $s4, $sp, 96 # save S4\n" +" st.d $s5, $sp, 104 # save S5\n" +" st.d $s6, $sp, 112 # save S6\n" +" st.d $s7, $sp, 120 # save S7\n" +" st.d $s8, $sp, 128 # save S8\n" +" st.d $fp, $sp, 136 # save FP\n" +" st.d $ra, $sp, 144 # save RA\n" +" st.d $ra, $sp, 152 # save RA as PC\n" + +" fst.d $fs0, $sp, 0 # save F24\n" +" fst.d $fs1, $sp, 8 # save F25\n" +" fst.d $fs2, $sp, 16 # save F26\n" +" fst.d $fs3, $sp, 24 # save F27\n" +" fst.d $fs4, $sp, 32 # save F28\n" +" fst.d $fs5, $sp, 40 # save F29\n" +" fst.d $fs6, $sp, 48 # save F30\n" +" fst.d $fs7, $sp, 56 # save F31\n" + +" # swap a0(new stack), sp(old stack)\n" +" st.d $sp, $a0, 0\n" +" or $sp, $a1, $zero\n" + +" fld.d $fs0, $sp, 0 # restore F24\n" +" fld.d $fs1, $sp, 8 # restore F25\n" +" fld.d $fs2, $sp, 16 # restore F26\n" +" fld.d $fs3, $sp, 24 # restore F27\n" +" fld.d $fs4, $sp, 32 # restore F28\n" +" fld.d $fs5, $sp, 40 # restore F29\n" +" fld.d $fs6, $sp, 48 # restore F30\n" +" fld.d $fs7, $sp, 56 # restore F31\n" + +" ld.d $s0, $sp, 64 # restore S0\n" +" ld.d $s1, $sp, 72 # restore S1\n" +" ld.d $s2, $sp, 80 # restore S2\n" +" ld.d $s3, $sp, 88 # restore S3\n" +" ld.d $s4, $sp, 96 # restore S4\n" +" ld.d $s5, $sp, 104 # restore S5\n" +" ld.d $s6, $sp, 112 # restore S6\n" +" ld.d $s7, $sp, 120 # restore S7\n" +" ld.d $s8, $sp, 128 # restore S8\n" +" ld.d $fp, $sp, 136 # restore FP\n" +" ld.d $ra, $sp, 144 # restore RA\n" + +" or $a0, $a2, $zero \n" +" # load PC\n" +" ld.d $a4, $sp, 152\n" + +" # adjust stack\n" +" addi.d $sp, $sp, 160\n" + +" # jump to context\n" +" jirl $zero, $a4, 0\n" +); +#endif + +#if defined(BTHREAD_CONTEXT_PLATFORM_linux_loongarch64) && defined(BTHREAD_CONTEXT_COMPILER_gcc) +__asm ( +".text\n" +".align 3\n" +".global bthread_make_fcontext\n" +".type bthread_make_fcontext, %function\n" +"bthread_make_fcontext:\n" +//" andi $a0, $a0, ~0xF\n" +" addi.d $a0, $a0, -160\n" + +" st.d $a2, $a0, 152\n" + +" pcaddi $a1, 3\n" +" st.d $a1, $a0, 144\n" +" jirl $zero, $ra, 0\n" + +"finish:\n" +" or $a0, $zero, $zero\n" +" bl _exit\n" +); + +#endif + +#if defined(BTHREAD_CONTEXT_PLATFORM_linux_riscv64) && defined(BTHREAD_CONTEXT_COMPILER_gcc) +__asm ( +".text\n" +".align 3\n" +".global bthread_jump_fcontext\n" +".type bthread_jump_fcontext, %function\n" +"bthread_jump_fcontext:\n" +" addi sp, sp, -160\n" +" # save callee-saved registers\n" +" sd s0, 64(sp)\n" +" sd s1, 72(sp)\n" +" sd s2, 80(sp)\n" +" sd s3, 88(sp)\n" +" sd s4, 96(sp)\n" +" sd s5, 104(sp)\n" +" sd s6, 112(sp)\n" +" sd s7, 120(sp)\n" +" sd s8, 128(sp)\n" +" sd s9, 136(sp)\n" +" sd s10, 144(sp)\n" +" sd s11, 152(sp)\n" +" sd ra, 0(sp)\n" +" sd fp, 8(sp)\n" +" # save floating point registers\n" +" fsd fs0, 16(sp)\n" +" fsd fs1, 24(sp)\n" +" fsd fs2, 32(sp)\n" +" fsd fs3, 40(sp)\n" +" fsd fs4, 48(sp)\n" +" fsd fs5, 56(sp)\n" +" # store current stack pointer\n" +" sd sp, 0(a0)\n" +" # load new stack pointer\n" +" mv sp, a1\n" +" # restore floating point registers\n" +" fld fs0, 16(sp)\n" +" fld fs1, 24(sp)\n" +" fld fs2, 32(sp)\n" +" fld fs3, 40(sp)\n" +" fld fs4, 48(sp)\n" +" fld fs5, 56(sp)\n" +" # restore callee-saved registers\n" +" ld s0, 64(sp)\n" +" ld s1, 72(sp)\n" +" ld s2, 80(sp)\n" +" ld s3, 88(sp)\n" +" ld s4, 96(sp)\n" +" ld s5, 104(sp)\n" +" ld s6, 112(sp)\n" +" ld s7, 120(sp)\n" +" ld s8, 128(sp)\n" +" ld s9, 136(sp)\n" +" ld s10, 144(sp)\n" +" ld s11, 152(sp)\n" +" ld ra, 0(sp)\n" +" ld fp, 8(sp)\n" +" # restore stack pointer\n" +" addi sp, sp, 160\n" +" # return value in a0\n" +" mv a0, a2\n" +" # jump to new context\n" +" ret\n" +); + +__asm ( +".text\n" +".align 3\n" +".global bthread_make_fcontext\n" +".type bthread_make_fcontext, %function\n" +"bthread_make_fcontext:\n" +" # align stack to 16-byte boundary\n" +" andi a0, a0, -16\n" +" addi a0, a0, -160\n" +" # store function pointer at the top of stack\n" +" sd a2, 0(a0)\n" +" # store finish function address\n" +" la t0, finish\n" +" sd t0, 8(a0)\n" +" # return pointer to context data\n" +" ret\n" +"finish:\n" +" # exit with code 0\n" +" li a0, 0\n" +" # call exit\n" +" call _exit\n" +); +#endif diff --git a/src/bthread/context.h b/src/bthread/context.h new file mode 100644 index 0000000..149c767 --- /dev/null +++ b/src/bthread/context.h @@ -0,0 +1,96 @@ +/* + + libcontext - a slightly more portable version of boost::context + + Copyright Martin Husemann 2013. + Copyright Oliver Kowalke 2009. + Copyright Sergue E. Leontiev 2013. + Copyright Thomas Sailer 2013. + Minor modifications by Tomasz Wlostowski 2016. + + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) + +*/ + +#ifndef BTHREAD_CONTEXT_H +#define BTHREAD_CONTEXT_H + +#include +#include +#include + +#if defined(__GNUC__) || defined(__APPLE__) + + #define BTHREAD_CONTEXT_COMPILER_gcc + + #if defined(__linux__) + #ifdef __x86_64__ + #define BTHREAD_CONTEXT_PLATFORM_linux_x86_64 + #define BTHREAD_CONTEXT_CALL_CONVENTION + + #elif __i386__ + #define BTHREAD_CONTEXT_PLATFORM_linux_i386 + #define BTHREAD_CONTEXT_CALL_CONVENTION + #elif __arm__ + #define BTHREAD_CONTEXT_PLATFORM_linux_arm32 + #define BTHREAD_CONTEXT_CALL_CONVENTION + #elif __aarch64__ + #define BTHREAD_CONTEXT_PLATFORM_linux_arm64 + #define BTHREAD_CONTEXT_CALL_CONVENTION + #elif __loongarch64 + #define BTHREAD_CONTEXT_PLATFORM_linux_loongarch64 + #define BTHREAD_CONTEXT_CALL_CONVENTION + #elif __riscv + #define BTHREAD_CONTEXT_PLATFORM_linux_riscv64 + #define BTHREAD_CONTEXT_CALL_CONVENTION + #endif + + #elif defined(__MINGW32__) || defined (__MINGW64__) + #if defined(__x86_64__) + #define BTHREAD_CONTEXT_COMPILER_gcc + #define BTHREAD_CONTEXT_PLATFORM_windows_x86_64 + #define BTHREAD_CONTEXT_CALL_CONVENTION + #elif defined(__i386__) + #define BTHREAD_CONTEXT_COMPILER_gcc + #define BTHREAD_CONTEXT_PLATFORM_windows_i386 + #define BTHREAD_CONTEXT_CALL_CONVENTION __cdecl + #endif + + #elif defined(__APPLE__) && defined(__MACH__) + #if defined (__i386__) + #define BTHREAD_CONTEXT_PLATFORM_apple_i386 + #define BTHREAD_CONTEXT_CALL_CONVENTION + #elif defined (__x86_64__) + #define BTHREAD_CONTEXT_PLATFORM_apple_x86_64 + #define BTHREAD_CONTEXT_CALL_CONVENTION + #elif defined (__aarch64__) + #define BTHREAD_CONTEXT_PLATFORM_apple_arm64 + #define BTHREAD_CONTEXT_CALL_CONVENTION + #endif + #endif + +#endif + +#if defined(_WIN32_WCE) +typedef int intptr_t; +#endif + +typedef void* bthread_fcontext_t; + +#ifdef __cplusplus +extern "C"{ +#endif + +intptr_t BTHREAD_CONTEXT_CALL_CONVENTION +bthread_jump_fcontext(bthread_fcontext_t * ofc, bthread_fcontext_t nfc, + intptr_t vp, bool preserve_fpu = false); +bthread_fcontext_t BTHREAD_CONTEXT_CALL_CONVENTION +bthread_make_fcontext(void* sp, size_t size, void (* fn)( intptr_t)); + +#ifdef __cplusplus +}; +#endif + +#endif // BTHREAD_CONTEXT_H diff --git a/src/bthread/countdown_event.cpp b/src/bthread/countdown_event.cpp new file mode 100644 index 0000000..1c2c595 --- /dev/null +++ b/src/bthread/countdown_event.cpp @@ -0,0 +1,108 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: 2016/06/03 13:15:24 + +#include "butil/atomicops.h" // butil::atomic +#include "bthread/butex.h" +#include "bthread/countdown_event.h" + +namespace bthread { + +CountdownEvent::CountdownEvent(int initial_count) { + RELEASE_ASSERT_VERBOSE(initial_count >= 0, + "Invalid initial_count=%d", + initial_count); + _butex = butex_create_checked(); + *_butex = initial_count; + _wait_was_invoked = false; +} + +CountdownEvent::~CountdownEvent() { + butex_destroy(_butex); +} + +void CountdownEvent::signal(int sig, bool flush) { + // Have to save _butex, *this is probably defreferenced by the wait thread + // which sees fetch_sub + void* const saved_butex = _butex; + const int prev = ((butil::atomic*)_butex) + ->fetch_sub(sig, butil::memory_order_release); + // DON'T touch *this ever after + if (prev > sig) { + return; + } + LOG_IF(ERROR, prev < sig) << "Counter is over decreased"; + butex_wake_all(saved_butex, flush); +} + +int CountdownEvent::wait() { + _wait_was_invoked = true; + for (;;) { + const int seen_counter = + ((butil::atomic*)_butex)->load(butil::memory_order_acquire); + if (seen_counter <= 0) { + return 0; + } + if (butex_wait(_butex, seen_counter, NULL) < 0 && + errno != EWOULDBLOCK && errno != EINTR) { + return errno; + } + } +} + +void CountdownEvent::add_count(int v) { + if (v <= 0) { + LOG_IF(ERROR, v < 0) << "Invalid count=" << v; + return; + } + LOG_IF(ERROR, _wait_was_invoked) + << "Invoking add_count() after wait() was invoked"; + ((butil::atomic*)_butex)->fetch_add(v, butil::memory_order_release); +} + +void CountdownEvent::reset(int v) { + if (v < 0) { + LOG(ERROR) << "Invalid count=" << v; + return; + } + const int prev_counter = + ((butil::atomic*)_butex) + ->exchange(v, butil::memory_order_release); + LOG_IF(ERROR, _wait_was_invoked && prev_counter) + << "Invoking reset() while count=" << prev_counter; + _wait_was_invoked = false; +} + +int CountdownEvent::timed_wait(const timespec& duetime) { + _wait_was_invoked = true; + for (;;) { + const int seen_counter = + ((butil::atomic*)_butex)->load(butil::memory_order_acquire); + if (seen_counter <= 0) { + return 0; + } + if (butex_wait(_butex, seen_counter, &duetime) < 0 && + errno != EWOULDBLOCK && errno != EINTR) { + return errno; + } + } +} + +} // namespace bthread diff --git a/src/bthread/countdown_event.h b/src/bthread/countdown_event.h new file mode 100644 index 0000000..0f2c234 --- /dev/null +++ b/src/bthread/countdown_event.h @@ -0,0 +1,62 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: 2016/06/03 13:06:40 + +#ifndef BTHREAD_COUNTDOWN_EVENT_H +#define BTHREAD_COUNTDOWN_EVENT_H + +#include "bthread/bthread.h" + +namespace bthread { + +// A synchronization primitive to wait for multiple signallers. +class CountdownEvent { +public: + CountdownEvent(int initial_count = 1); + ~CountdownEvent(); + + // Increase current counter by |v| + void add_count(int v = 1); + + // Reset the counter to |v| + void reset(int v = 1); + + // Decrease the counter by |sig| + // when flush is true, after signal we need to call bthread_flush + void signal(int sig = 1, bool flush = false); + + // Block current thread until the counter reaches 0. + // Returns 0 on success, error code otherwise. + // This method never returns EINTR. + int wait(); + + // Block the current thread until the counter reaches 0 or duetime has expired + // Returns 0 on success, error code otherwise. ETIMEDOUT is for timeout. + // This method never returns EINTR. + int timed_wait(const timespec& duetime); + +private: + int *_butex; + bool _wait_was_invoked; +}; + +} // namespace bthread + +#endif // BTHREAD_COUNTDOWN_EVENT_H diff --git a/src/bthread/errno.cpp b/src/bthread/errno.cpp new file mode 100644 index 0000000..7f5e2ca --- /dev/null +++ b/src/bthread/errno.cpp @@ -0,0 +1,47 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Wed Jul 30 11:47:19 CST 2014 + +#include "bthread/errno.h" + +// Define errno in bthread/errno.h +extern const int ESTOP = -20; + +BAIDU_REGISTER_ERRNO(ESTOP, "The structure is stopping") + +extern "C" { + +#if defined(OS_LINUX) + +extern int *__errno_location() __attribute__((__const__)); + +int *bthread_errno_location() { + return __errno_location(); +} +#elif defined(OS_MACOSX) + +extern int * __error(void); + +int *bthread_errno_location() { + return __error(); +} +#endif + +} // extern "C" diff --git a/src/bthread/errno.h b/src/bthread/errno.h new file mode 100644 index 0000000..2045e76 --- /dev/null +++ b/src/bthread/errno.h @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Wed Jul 30 11:47:19 CST 2014 + +#ifndef BTHREAD_ERRNO_H +#define BTHREAD_ERRNO_H + +#include // errno +#include "butil/errno.h" // berror(), DEFINE_BTHREAD_ERRNO + +__BEGIN_DECLS + +extern int *bthread_errno_location(); + +#ifdef errno +#undef errno +#define errno *bthread_errno_location() +#endif + +// List errno used throughout bthread +extern const int ESTOP; + +__END_DECLS + +#endif //BTHREAD_ERRNO_H diff --git a/src/bthread/execution_queue.cpp b/src/bthread/execution_queue.cpp new file mode 100644 index 0000000..ae6f97b --- /dev/null +++ b/src/bthread/execution_queue.cpp @@ -0,0 +1,474 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: 2016/04/16 18:43:24 + +#include "bthread/execution_queue.h" + +#include "butil/memory/singleton_on_pthread_once.h" +#include "butil/resource_pool.h" // butil::get_resource +#include "butil/threading/platform_thread.h" + +namespace bthread { + +//May be false on different platforms +//BAIDU_CASSERT(sizeof(TaskNode) == 128, sizeof_TaskNode_must_be_128); +//BAIDU_CASSERT(offsetof(TaskNode, static_task_mem) + sizeof(TaskNode().static_task_mem) == 128, sizeof_TaskNode_must_be_128); +BAIDU_CASSERT(sizeof(ExecutionQueue) == sizeof(ExecutionQueueBase), + sizeof_ExecutionQueue_must_be_the_same_with_ExecutionQueueBase); +BAIDU_CASSERT(sizeof(TaskIterator) == sizeof(TaskIteratorBase), + sizeof_TaskIterator_must_be_the_same_with_TaskIteratorBase); +namespace /*anonymous*/ { +typedef butil::ResourceId slot_id_t; + +inline slot_id_t WARN_UNUSED_RESULT slot_of_id(uint64_t id) { + slot_id_t slot = { (id & 0xFFFFFFFFul) }; + return slot; +} + +inline uint64_t make_id(uint32_t version, slot_id_t slot) { + return (((uint64_t)version) << 32) | slot.value; +} +} // namespace anonymous + +struct ExecutionQueueVars { + bvar::Adder running_task_count; + bvar::Adder execq_count; + bvar::Adder execq_active_count; + + ExecutionQueueVars(); +}; + +ExecutionQueueVars::ExecutionQueueVars() + : running_task_count("bthread_execq_running_task_count") + , execq_count("bthread_execq_count") + , execq_active_count("bthread_execq_active_count") { +} + +inline ExecutionQueueVars* get_execq_vars() { + return butil::get_leaky_singleton(); +} + +void ExecutionQueueBase::start_execute(TaskNode* node) { + node->next = TaskNode::UNCONNECTED; + node->status = TaskNode::UNEXECUTED; + node->iterated = false; + if (node->high_priority) { + // Add _high_priority_tasks before pushing this task into queue to + // make sure that _execute_tasks sees the newest number when this + // task is in the queue. Although there might be some useless for + // loops in _execute_tasks if this thread is scheduled out at this + // point, we think it's just fine. + _high_priority_tasks.fetch_add(1, butil::memory_order_relaxed); + } + TaskNode* const prev_head = _head.exchange(node, butil::memory_order_release); + if (prev_head != NULL) { + node->next = prev_head; + return; + } + // Get the right to execute the task, start a bthread to avoid deadlock + // or stack overflow + node->next = NULL; + node->q = this; + + ExecutionQueueVars* const vars = get_execq_vars(); + vars->execq_active_count << 1; + if (node->in_place) { + int niterated = 0; + _execute(node, node->high_priority, &niterated); + TaskNode* tmp = node; + // return if no more + if (node->high_priority) { + _high_priority_tasks.fetch_sub(niterated, butil::memory_order_relaxed); + } + if (!_more_tasks(tmp, &tmp, !node->iterated)) { + vars->execq_active_count << -1; + return_task_node(node); + return; + } + } + + if (nullptr == _options.executor) { + if (_options.use_pthread) { + if (_pthread_started) { + BAIDU_SCOPED_LOCK(_mutex); + _current_head = node; + _cond.Signal(); + } else { + // Start the execution bthread in background once. + if (pthread_create(&_pid, NULL, + _execute_tasks_pthread, + node) != 0) { + PLOG(FATAL) << "Fail to create pthread"; + _execute_tasks(node); + } + _pthread_started = true; + } + } else { + bthread_t tid; + // We start the execution bthread in background instead of foreground as + // we can't determine whether the code after execute() is urgent (like + // unlock a pthread_mutex_t) in which case implicit context switch may + // cause undefined behavior (e.g. deadlock) + if (bthread_start_background(&tid, &_options.bthread_attr, + _execute_tasks, node) != 0) { + PLOG(FATAL) << "Fail to start bthread"; + _execute_tasks(node); + } + } + + } else { + if (_options.executor->submit(_execute_tasks, node) != 0) { + PLOG(FATAL) << "Fail to submit task"; + _execute_tasks(node); + } + } +} + +void* ExecutionQueueBase::_execute_tasks(void* arg) { + ExecutionQueueVars* vars = get_execq_vars(); + TaskNode* head = (TaskNode*)arg; + ExecutionQueueBase* m = (ExecutionQueueBase*)head->q; + TaskNode* cur_tail = NULL; + bool destroy_queue = false; + for (;;) { + if (head->iterated) { + CHECK(head->next != NULL); + TaskNode* saved_head = head; + head = head->next; + m->return_task_node(saved_head); + } + int rc = 0; + if (m->_high_priority_tasks.load(butil::memory_order_relaxed) > 0) { + int nexecuted = 0; + // Don't care the return value + rc = m->_execute(head, true, &nexecuted); + m->_high_priority_tasks.fetch_sub( + nexecuted, butil::memory_order_relaxed); + if (nexecuted == 0) { + // Some high_priority tasks are not in queue + sched_yield(); + } + } else { + rc = m->_execute(head, false, NULL); + } + if (rc == ESTOP) { + destroy_queue = true; + } + // Release TaskNode until uniterated task or last task + while (head->next != NULL && head->iterated) { + TaskNode* saved_head = head; + head = head->next; + m->return_task_node(saved_head); + } + if (cur_tail == NULL) { + for (cur_tail = head; cur_tail->next != NULL; + cur_tail = cur_tail->next) {} + } + // break when no more tasks and head has been executed + if (!m->_more_tasks(cur_tail, &cur_tail, !head->iterated)) { + CHECK_EQ(cur_tail, head); + CHECK(head->iterated); + m->return_task_node(head); + break; + } + } + if (destroy_queue) { + CHECK(m->_head.load(butil::memory_order_relaxed) == NULL); + CHECK(m->_stopped); + // Add _join_butex by 2 to make it equal to the next version of the + // ExecutionQueue from the same slot so that join with old id would + // return immediately. + // + // 1: release fence to make join sees the newest changes when it sees + // the newest _join_butex + m->_join_butex->fetch_add(2, butil::memory_order_release/*1*/); + butex_wake_all(m->_join_butex); + vars->execq_count << -1; + butil::return_resource(slot_of_id(m->_this_id)); + } + vars->execq_active_count << -1; + return NULL; +} + +void* ExecutionQueueBase::_execute_tasks_pthread(void* arg) { + butil::PlatformThread::SetNameSimple("ExecutionQueue"); + auto head = (TaskNode*)arg; + auto m = (ExecutionQueueBase*)head->q; + m->_current_head = head; + while (true) { + BAIDU_SCOPED_LOCK(m->_mutex); + while (!m->_current_head) { + m->_cond.Wait(); + } + _execute_tasks(m->_current_head); + m->_current_head = NULL; + + int expected = _version_of_id(m->_this_id); + if (expected != m->_join_butex->load(butil::memory_order_relaxed)) { + // Execute queue has been stopped and stopped task has been executed, quit. + break; + } + } + return NULL; +} + +void ExecutionQueueBase::return_task_node(TaskNode* node) { + node->clear_before_return(_clear_func); + butil::return_object(node); + get_execq_vars()->running_task_count << -1; +} + +void ExecutionQueueBase::_on_recycle() { + // Push a closed tasks + while (true) { + TaskNode* node = butil::get_object(); + if (BAIDU_LIKELY(node != NULL)) { + get_execq_vars()->running_task_count << 1; + node->stop_task = true; + node->high_priority = false; + node->in_place = false; + start_execute(node); + break; + } + CHECK(false) << "Fail to create task_node_t, " << berror(); + ::bthread_usleep(1000); + } +} + +int ExecutionQueueBase::join(uint64_t id) { + const slot_id_t slot = slot_of_id(id); + ExecutionQueueBase* const m = butil::address_resource(slot); + if (m == NULL) { + // The queue is not created yet, this join is definitely wrong. + return EINVAL; + } + int expected = _version_of_id(id); + // acquire fence makes this thread see changes before changing _join_butex. + while (expected == m->_join_butex->load(butil::memory_order_acquire)) { + if (butex_wait(m->_join_butex, expected, NULL) < 0 && + errno != EWOULDBLOCK && errno != EINTR) { + return errno; + } + } + // Join pthread if it's started. + if (m->_options.use_pthread && m->_pthread_started) { + pthread_join(m->_pid, NULL); + } + return 0; +} + +int ExecutionQueueBase::stop() { + const uint32_t id_ver = _version_of_id(_this_id); + uint64_t vref = _versioned_ref.load(butil::memory_order_relaxed); + for (;;) { + if (_version_of_vref(vref) != id_ver) { + return EINVAL; + } + // Try to set version=id_ver+1 (to make later address() return NULL), + // retry on fail. + if (_versioned_ref.compare_exchange_strong( + vref, _make_vref(id_ver + 1, _ref_of_vref(vref)), + butil::memory_order_release, + butil::memory_order_relaxed)) { + // Set _stopped to make lattern execute() fail immediately + _stopped.store(true, butil::memory_order_release); + // Deref additionally which is added at creation so that this + // queue's reference will hit 0(recycle) when no one addresses it. + _release_additional_reference(); + // NOTE: This queue may be recycled at this point, don't + // touch anything. + return 0; + } + } +} + +int ExecutionQueueBase::_execute(TaskNode* head, bool high_priority, int* niterated) { + if (head != NULL && head->stop_task) { + CHECK(head->next == NULL); + head->iterated = true; + head->status = TaskNode::EXECUTED; + TaskIteratorBase iter(NULL, this, true, false); + _execute_func(_meta, _type_specific_function, iter); + if (niterated) { + *niterated = 1; + } + return ESTOP; + } + TaskIteratorBase iter(head, this, false, high_priority); + if (iter) { + _execute_func(_meta, _type_specific_function, iter); + } + // We must assign |niterated| with num_iterated even if we couldn't peek + // any task to execute at the begining, in which case all the iterated + // tasks have been cancelled at this point. And we must return the + // correct num_iterated() to the caller to update the counter correctly. + if (niterated) { + *niterated = iter.num_iterated(); + } + return 0; +} + +TaskNode* ExecutionQueueBase::allocate_node() { + get_execq_vars()->running_task_count << 1; + return butil::get_object(); +} + +TaskNode* const TaskNode::UNCONNECTED = (TaskNode*)-1L; + +ExecutionQueueBase::scoped_ptr_t ExecutionQueueBase::address(uint64_t id) { + scoped_ptr_t ret; + const slot_id_t slot = slot_of_id(id); + ExecutionQueueBase* const m = butil::address_resource(slot); + if (BAIDU_LIKELY(m != NULL)) { + // acquire fence makes sure this thread sees latest changes before + // _dereference() + const uint64_t vref1 = m->_versioned_ref.fetch_add( + 1, butil::memory_order_acquire); + const uint32_t ver1 = _version_of_vref(vref1); + if (ver1 == _version_of_id(id)) { + ret.reset(m); + return ret.Pass(); + } + + const uint64_t vref2 = m->_versioned_ref.fetch_sub( + 1, butil::memory_order_release); + const int32_t nref = _ref_of_vref(vref2); + if (nref > 1) { + return ret.Pass(); + } else if (__builtin_expect(nref == 1, 1)) { + const uint32_t ver2 = _version_of_vref(vref2); + if ((ver2 & 1)) { + if (ver1 == ver2 || ver1 + 1 == ver2) { + uint64_t expected_vref = vref2 - 1; + if (m->_versioned_ref.compare_exchange_strong( + expected_vref, _make_vref(ver2 + 1, 0), + butil::memory_order_acquire, + butil::memory_order_relaxed)) { + m->_on_recycle(); + // We don't return m immediatly when the reference count + // reaches 0 as there might be in processing tasks. Instead + // _on_recycle would push a `stop_task', after which + // is executed m would be finally reset and returned + } + } else { + CHECK(false) << "ref-version=" << ver1 + << " unref-version=" << ver2; + } + } else { + CHECK_EQ(ver1, ver2); + // Addressed a free slot. + } + } else { + CHECK(false) << "Over dereferenced id=" << id; + } + } + return ret.Pass(); +} + +int ExecutionQueueBase::create(uint64_t* id, const ExecutionQueueOptions* options, + execute_func_t execute_func, + clear_task_mem clear_func, + void* meta, void* type_specific_function) { + if (execute_func == NULL || clear_func == NULL) { + return EINVAL; + } + + slot_id_t slot; + ExecutionQueueBase* const m = butil::get_resource(&slot, Forbidden()); + if (BAIDU_LIKELY(m != NULL)) { + m->_execute_func = execute_func; + m->_clear_func = clear_func; + m->_meta = meta; + m->_type_specific_function = type_specific_function; + CHECK(m->_head.load(butil::memory_order_relaxed) == NULL); + CHECK_EQ(0, m->_high_priority_tasks.load(butil::memory_order_relaxed)); + ExecutionQueueOptions opt; + if (options != NULL) { + opt = *options; + } + m->_options = opt; + m->_stopped.store(false, butil::memory_order_relaxed); + m->_this_id = make_id( + _version_of_vref(m->_versioned_ref.fetch_add( + 1, butil::memory_order_release)), slot); + *id = m->_this_id; + m->_pthread_started = false; + m->_current_head = NULL; + get_execq_vars()->execq_count << 1; + return 0; + } + return ENOMEM; +} + +inline bool TaskIteratorBase::should_break_for_high_priority_tasks() { + if (!_high_priority && + _q->_high_priority_tasks.load(butil::memory_order_relaxed) > 0) { + _should_break = true; + return true; + } + return false; +} + +void TaskIteratorBase::operator++() { + if (!(*this)) { + return; + } + if (_cur_node->iterated) { + _cur_node = _cur_node->next; + } + if (should_break_for_high_priority_tasks()) { + return; + } // else the next high_priority_task would be delayed for at most one task + + while (_cur_node && !_cur_node->stop_task) { + if (_high_priority == _cur_node->high_priority) { + if (!_cur_node->iterated && _cur_node->peek_to_execute()) { + ++_num_iterated; + _cur_node->iterated = true; + return; + } + _num_iterated += !_cur_node->iterated; + _cur_node->iterated = true; + } + _cur_node = _cur_node->next; + } + return; +} + +TaskIteratorBase::~TaskIteratorBase() { + // Set the iterated tasks as EXECUTED here instead of waiting them to be + // returned in _start_execute as the high_priority_task might be in the + // middle of the linked list and is not going to be returned soon + if (_is_stopped) { + return; + } + while (_head != _cur_node) { + if (_head->iterated && _head->high_priority == _high_priority) { + _head->set_executed(); + } + _head = _head->next; + } + if (_should_break && _cur_node != NULL + && _cur_node->high_priority == _high_priority && _cur_node->iterated) { + _cur_node->set_executed(); + } +} + +} // namespace bthread diff --git a/src/bthread/execution_queue.h b/src/bthread/execution_queue.h new file mode 100644 index 0000000..5ceef89 --- /dev/null +++ b/src/bthread/execution_queue.h @@ -0,0 +1,251 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: 2015/10/23 18:16:16 + +#ifndef BTHREAD_EXECUTION_QUEUE_H +#define BTHREAD_EXECUTION_QUEUE_H + +#include "bthread/bthread.h" +#include "butil/type_traits.h" + +namespace bthread { + +// ExecutionQueue is a special wait-free MPSC queue of which the consumer thread +// is auto started by the execute operation and auto quits if there are no more +// tasks, in another word there isn't a daemon bthread waiting to consume tasks. + +template struct ExecutionQueueId; +template class ExecutionQueue; +struct TaskNode; +class ExecutionQueueBase; + +class TaskIteratorBase { +DISALLOW_COPY_AND_ASSIGN(TaskIteratorBase); +friend class ExecutionQueueBase; +public: + // Returns true when the ExecutionQueue is stopped and there will never be + // more tasks and you can safely release all the related resources ever + // after. + bool is_queue_stopped() const { return _is_stopped; } + explicit operator bool() const; +protected: + TaskIteratorBase(TaskNode* head, ExecutionQueueBase* queue, + bool is_stopped, bool high_priority) + : _cur_node(head) + , _head(head) + , _q(queue) + , _is_stopped(is_stopped) + , _high_priority(high_priority) + , _should_break(false) + , _num_iterated(0) + { operator++(); } + ~TaskIteratorBase(); + void operator++(); + TaskNode* cur_node() const { return _cur_node; } +private: + int num_iterated() const { return _num_iterated; } + bool should_break_for_high_priority_tasks(); + + TaskNode* _cur_node; + TaskNode* _head; + ExecutionQueueBase* _q; + bool _is_stopped; + bool _high_priority; + bool _should_break; + int _num_iterated; +}; + +// Iterate over the given tasks +// +// Examples: +// int demo_execute(void* meta, TaskIterator& iter) { +// if (iter.is_queue_stopped()) { +// // destroy meta and related resources +// return 0; +// } +// for (; iter; ++iter) { +// // do_something(*iter) +// // or do_something(iter->a_member_of_T) +// } +// return 0; +// } +template +class TaskIterator : public TaskIteratorBase { +public: + typedef T* pointer; + typedef T& reference; + + TaskIterator() = delete; + reference operator*() const; + pointer operator->() const { return &(operator*()); } + TaskIterator& operator++(); + void operator++(int); +}; + +struct TaskHandle { + TaskHandle(); + TaskNode* node; + int64_t version; +}; + +struct TaskOptions { + TaskOptions(); + TaskOptions(bool high_priority, bool in_place_if_possible); + + // Executor would execute high-priority tasks in the FIFO order but before + // all pending normal-priority tasks. + // NOTE: We don't guarantee any kind of real-time as there might be tasks still + // in process which are uninterruptible. + // + // Default: false + bool high_priority; + + // If |in_place_if_possible| is true, execution_queue_execute would call + // execute immediately instead of starting a bthread if possible + // + // Note: Running callbacks in place might cause the deadlock issue, you + // should be very careful turning this flag on. + // + // Default: false + bool in_place_if_possible; +}; + +const static TaskOptions TASK_OPTIONS_NORMAL = TaskOptions(false, false); +const static TaskOptions TASK_OPTIONS_URGENT = TaskOptions(true, false); +const static TaskOptions TASK_OPTIONS_INPLACE = TaskOptions(false, true); + +class Executor { +public: + virtual ~Executor() = default; + + // Return 0 on success. + virtual int submit(void * (*fn)(void*), void* args) = 0; +}; + +struct ExecutionQueueOptions { + ExecutionQueueOptions(); + + // Execute in resident pthread instead of bthread. default: false. + bool use_pthread; + + // Attribute of the bthread which execute runs on. default: BTHREAD_ATTR_NORMAL + // Bthread will be used when executor = NULL and use_pthread == false. + bthread_attr_t bthread_attr; + + // Executor that tasks run on. default: NULL + // Note that TaskOptions.in_place_if_possible = false will not work, if implementation of + // Executor is in-place(synchronous). + Executor * executor; +}; + +// Start an ExecutionQueue. If |options| is NULL, the queue will be created with +// the default options. +// Returns 0 on success, errno otherwise +// NOTE: type |T| can be non-POD but must be copy-constructive +template +int execution_queue_start( + ExecutionQueueId* id, + const ExecutionQueueOptions* options, + int (*execute)(void* meta, TaskIterator& iter), + void* meta); + +// Stop the ExecutionQueue. +// After this function is called: +// - All the following calls to execution_queue_execute would fail immediately. +// - The executor will call |execute| with TaskIterator::is_queue_stopped() being +// true exactly once when all the pending tasks have been executed, and after +// this point it's ok to release the resource referenced by |meta|. +// Returns 0 on success, errno otherwise. +template +int execution_queue_stop(ExecutionQueueId id); + +// Wait until the stop task (Iterator::is_queue_stopped() returns true) has +// been executed +template +int execution_queue_join(ExecutionQueueId id); + +// Thread-safe and Wait-free. +// Execute a task with default TaskOptions (normal task); +template +int execution_queue_execute(ExecutionQueueId id, + typename butil::add_const_reference::type task); + +// Thread-safe and Wait-free. +// Execute a task with options. e.g +// bthread::execution_queue_execute(queue, task, &bthread::TASK_OPTIONS_URGENT) +// If |options| is NULL, we will use default options (normal task) +// If |handle| is not NULL, we will assign it with the handler of this task. +template +int execution_queue_execute(ExecutionQueueId id, + typename butil::add_const_reference::type task, + const TaskOptions* options); +template +int execution_queue_execute(ExecutionQueueId id, + typename butil::add_const_reference::type task, + const TaskOptions* options, + TaskHandle* handle); + +template +int execution_queue_execute(ExecutionQueueId id, + T&& task); + +template +int execution_queue_execute(ExecutionQueueId id, + T&& task, + const TaskOptions* options); + +template +int execution_queue_execute(ExecutionQueueId id, + T&& task, + const TaskOptions* options, + TaskHandle* handle); + +// [Thread safe and ABA free] Cancel the corresponding task. +// Returns: +// -1: The task was executed or h is an invalid handle +// 0: Success +// 1: The task is executing +int execution_queue_cancel(const TaskHandle& h); + +// Thread-safe and Wait-free +// Address a reference of ExecutionQueue if |id| references to a valid +// ExecutionQueue +// +// |execution_queue_execute| internally fetches a reference of ExecutionQueue at +// the beginning and releases it at the end, which makes 2 additional cache +// updates. In some critical situation where the overhead of +// execution_queue_execute matters, you can avoid this by addressing the +// reference at the beginning of every producer, and execute tasks execatly +// through the reference instead of id. +// +// Note: It makes |execution_queue_stop| a little complicated in the user level, +// as we don't pass the `stop task' to |execute| until no one holds any reference. +// If you are not sure about the ownership of the return value (which releases +// the reference of the very ExecutionQueue in the destructor) and don't that +// care the overhead of ExecutionQueue, DON'T use this function +template +typename ExecutionQueue::scoped_ptr_t +execution_queue_address(ExecutionQueueId id); + +} // namespace bthread + +#include "bthread/execution_queue_inl.h" + +#endif //BTHREAD_EXECUTION_QUEUE_H diff --git a/src/bthread/execution_queue_inl.h b/src/bthread/execution_queue_inl.h new file mode 100644 index 0000000..ddf7bc6 --- /dev/null +++ b/src/bthread/execution_queue_inl.h @@ -0,0 +1,596 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: 2015/10/27 17:39:48 + +#ifndef BTHREAD_EXECUTION_QUEUE_INL_H +#define BTHREAD_EXECUTION_QUEUE_INL_H + +#include "butil/atomicops.h" // butil::atomic +#include "butil/macros.h" // BAIDU_CACHELINE_ALIGNMENT +#include "butil/memory/scoped_ptr.h" // butil::scoped_ptr +#include "butil/logging.h" // LOG +#include "butil/time.h" // butil::cpuwide_time_ns +#include "butil/object_pool.h" // butil::get_object +#include "bvar/bvar.h" // bvar::Adder +#include "bthread/butex.h" // butex_construct +#include "butil/synchronization/condition_variable.h" + +namespace bthread { + +template +struct ExecutionQueueId { + uint64_t value; +}; + +struct TaskNode; +class ExecutionQueueBase; +typedef void (*clear_task_mem)(TaskNode*); + +struct BAIDU_CACHELINE_ALIGNMENT TaskNode { + enum TaskStatus { + UNEXECUTED = 0, + EXECUTING = 1, + EXECUTED = 2 + }; + + TaskNode() + : version(0) + , status(UNEXECUTED) + , stop_task(false) + , iterated(false) + , high_priority(false) + , in_place(false) + , next(UNCONNECTED) + , q(NULL) + {} + ~TaskNode() {} + int cancel(int64_t expected_version) { + BAIDU_SCOPED_LOCK(mutex); + if (version != expected_version) { + return -1; + } + if (status == UNEXECUTED) { + status = EXECUTED; + return 0; + } + return status == EXECUTED ? -1 : 1; + } + void set_executed() { + BAIDU_SCOPED_LOCK(mutex); + status = EXECUTED; + } + bool peek_to_execute() { + BAIDU_SCOPED_LOCK(mutex); + if (status == UNEXECUTED) { + status = EXECUTING; + return true; + } + return false; + } + butil::Mutex mutex; // to guard version and status + int64_t version; + uint8_t status; + bool stop_task; + bool iterated; + bool high_priority; + bool in_place; + TaskNode* next; + ExecutionQueueBase* q; + union { + char static_task_mem[56]; // Make sizeof TaskNode exactly 128 bytes + char* dynamic_task_mem; + }; + + void clear_before_return(clear_task_mem clear_func) { + if (!stop_task) { + clear_func(this); + CHECK(iterated); + } + q = NULL; + std::unique_lock lck(mutex); + ++version; + const int saved_status = status; + status = UNEXECUTED; + lck.unlock(); + CHECK_NE(saved_status, UNEXECUTED); + LOG_IF(WARNING, saved_status == EXECUTING) + << "Return a executing node, did you return before " + "iterator reached the end?"; + } + + static TaskNode* const UNCONNECTED; +}; + +// Specialize TaskNodeAllocator for types with different sizes +template struct TaskAllocatorBase { +}; + +template +struct TaskAllocatorBase { + inline static void* allocate(TaskNode* node) + { return node->static_task_mem; } + inline static void* get_allocated_mem(TaskNode* node) + { return node->static_task_mem; } + inline static void deallocate(TaskNode*) {} +}; + +template +struct TaskAllocatorBase { + inline static void* allocate(TaskNode* node) { + node->dynamic_task_mem = (char*)malloc(size); + return node->dynamic_task_mem; + } + + inline static void* get_allocated_mem(TaskNode* node) + { return node->dynamic_task_mem; } + + inline static void deallocate(TaskNode* node) { + free(node->dynamic_task_mem); + } +}; + +template +struct TaskAllocator : public TaskAllocatorBase< + sizeof(T), sizeof(T) <= sizeof(TaskNode().static_task_mem)> +{}; + +class TaskIteratorBase; + +class BAIDU_CACHELINE_ALIGNMENT ExecutionQueueBase { +DISALLOW_COPY_AND_ASSIGN(ExecutionQueueBase); +struct Forbidden {}; +friend class TaskIteratorBase; + struct Dereferencer { + void operator()(ExecutionQueueBase* queue) { + if (queue != NULL) { + queue->dereference(); + } + } + }; +public: + // User cannot create ExecutionQueue fron construct + ExecutionQueueBase(Forbidden) + : _head(NULL) + , _versioned_ref(0) // join() depends on even version + , _high_priority_tasks(0) + , _pthread_started(false) + , _cond(&_mutex) + , _current_head(NULL) { + _join_butex = butex_create_checked >(); + _join_butex->store(0, butil::memory_order_relaxed); + } + + ~ExecutionQueueBase() { + butex_destroy(_join_butex); + } + + bool stopped() const { return _stopped.load(butil::memory_order_acquire); } + int stop(); + static int join(uint64_t id); +protected: + typedef int (*execute_func_t)(void*, void*, TaskIteratorBase&); + typedef scoped_ptr scoped_ptr_t; + int dereference(); + static int create(uint64_t* id, const ExecutionQueueOptions* options, + execute_func_t execute_func, + clear_task_mem clear_func, + void* meta, void* type_specific_function); + static scoped_ptr_t address(uint64_t id) WARN_UNUSED_RESULT; + void start_execute(TaskNode* node); + TaskNode* allocate_node(); + void return_task_node(TaskNode* node); + +private: + + bool _more_tasks(TaskNode* old_head, TaskNode** new_tail, + bool has_uniterated); + void _release_additional_reference() { + dereference(); + } + void _on_recycle(); + int _execute(TaskNode* head, bool high_priority, int* niterated); + static void* _execute_tasks(void* arg); + static void* _execute_tasks_pthread(void* arg); + + static inline uint32_t _version_of_id(uint64_t id) WARN_UNUSED_RESULT { + return (uint32_t)(id >> 32); + } + + static inline uint32_t _version_of_vref(int64_t vref) WARN_UNUSED_RESULT { + return (uint32_t)(vref >> 32); + } + + static inline uint32_t _ref_of_vref(int64_t vref) WARN_UNUSED_RESULT { + return (int32_t)(vref & 0xFFFFFFFFul); + } + + static inline int64_t _make_vref(uint32_t version, int32_t ref) { + // 1: Intended conversion to uint32_t, nref=-1 is 00000000FFFFFFFF + return (((uint64_t)version) << 32) | (uint32_t/*1*/)ref; + } + + // Don't change the order of _head, _versioned_ref and _stopped unless you + // see improvement of performance in test + BAIDU_CACHELINE_ALIGNMENT butil::atomic _head; + BAIDU_CACHELINE_ALIGNMENT butil::atomic _versioned_ref; + BAIDU_CACHELINE_ALIGNMENT butil::atomic _stopped; + butil::atomic _high_priority_tasks; + uint64_t _this_id; + void* _meta; + void* _type_specific_function; + execute_func_t _execute_func; + clear_task_mem _clear_func; + ExecutionQueueOptions _options; + butil::atomic* _join_butex; + + // For pthread mode. + pthread_t _pid; + bool _pthread_started; + butil::Mutex _mutex; + butil::ConditionVariable _cond; + TaskNode* _current_head; // Current task head of each execution. +}; + +template +class ExecutionQueue : public ExecutionQueueBase { +struct Forbidden {}; +friend class TaskIterator; + typedef ExecutionQueueBase Base; + ExecutionQueue(); +public: + typedef ExecutionQueue self_type; + struct Dereferencer { + void operator()(self_type* queue) { + if (queue != NULL) { + queue->dereference(); + } + } + }; + typedef scoped_ptr scoped_ptr_t; + typedef bthread::ExecutionQueueId id_t; + typedef TaskIterator iterator; + typedef int (*execute_func_t)(void*, iterator&); + typedef TaskAllocator allocator; + BAIDU_CASSERT(sizeof(execute_func_t) == sizeof(void*), + sizeof_function_must_be_equal_to_sizeof_voidptr); + + static void clear_task_mem(TaskNode* node) { + T* const task = (T*)allocator::get_allocated_mem(node); + task->~T(); + allocator::deallocate(node); + } + + static int execute_task(void* meta, void* specific_function, + TaskIteratorBase& it) { + execute_func_t f = (execute_func_t)specific_function; + return f(meta, static_cast(it)); + } + + inline static int create(id_t* id, const ExecutionQueueOptions* options, + execute_func_t execute_func, void* meta) { + return Base::create(&id->value, options, execute_task, + clear_task_mem, meta, (void*)execute_func); + } + + inline static scoped_ptr_t address(id_t id) WARN_UNUSED_RESULT { + Base::scoped_ptr_t ptr = Base::address(id.value); + Base* b = ptr.release(); + scoped_ptr_t ret((self_type*)b); + return ret.Pass(); + } + + int execute(typename butil::add_const_reference::type task) { + return execute(task, NULL, NULL); + } + + int execute(typename butil::add_const_reference::type task, + const TaskOptions* options, TaskHandle* handle) { + return execute(std::forward(const_cast(task)), options, handle); + } + + + int execute(T&& task) { + return execute(std::forward(task), NULL, NULL); + } + + int execute(T&& task, + const TaskOptions* options, TaskHandle* handle) { + if (stopped()) { + return EINVAL; + } + TaskNode* node = allocate_node(); + if (BAIDU_UNLIKELY(node == NULL)) { + return ENOMEM; + } + void* const mem = allocator::allocate(node); + if (BAIDU_UNLIKELY(!mem)) { + return_task_node(node); + return ENOMEM; + } + new (mem) T(std::forward(task)); + node->stop_task = false; + TaskOptions opt; + if (options) { + opt = *options; + } + node->high_priority = opt.high_priority; + node->in_place = opt.in_place_if_possible; + if (handle) { + handle->node = node; + handle->version = node->version; + } + start_execute(node); + return 0; + } +}; + +inline ExecutionQueueOptions::ExecutionQueueOptions() + : use_pthread(false) + , bthread_attr(BTHREAD_ATTR_NORMAL) + , executor(NULL) +{} + +template +inline int execution_queue_start( + ExecutionQueueId* id, + const ExecutionQueueOptions* options, + int (*execute)(void* meta, TaskIterator&), + void* meta) { + return ExecutionQueue::create(id, options, execute, meta); +} + +template +typename ExecutionQueue::scoped_ptr_t +execution_queue_address(ExecutionQueueId id) { + return ExecutionQueue::address(id); +} + +template +inline int execution_queue_execute(ExecutionQueueId id, + typename butil::add_const_reference::type task) { + return execution_queue_execute(id, task, NULL); +} + +template +inline int execution_queue_execute(ExecutionQueueId id, + typename butil::add_const_reference::type task, + const TaskOptions* options) { + return execution_queue_execute(id, task, options, NULL); +} + +template +inline int execution_queue_execute(ExecutionQueueId id, + typename butil::add_const_reference::type task, + const TaskOptions* options, + TaskHandle* handle) { + typename ExecutionQueue::scoped_ptr_t + ptr = ExecutionQueue::address(id); + if (ptr != NULL) { + return ptr->execute(task, options, handle); + } else { + return EINVAL; + } +} + +template +inline int execution_queue_execute(ExecutionQueueId id, + T&& task) { + return execution_queue_execute(id, std::forward(task), NULL); +} + +template +inline int execution_queue_execute(ExecutionQueueId id, + T&& task, + const TaskOptions* options) { + return execution_queue_execute(id, std::forward(task), options, NULL); +} + +template +inline int execution_queue_execute(ExecutionQueueId id, + T&& task, + const TaskOptions* options, + TaskHandle* handle) { + typename ExecutionQueue::scoped_ptr_t + ptr = ExecutionQueue::address(id); + if (ptr != NULL) { + return ptr->execute(std::forward(task), options, handle); + } else { + return EINVAL; + } +} + +template +inline int execution_queue_stop(ExecutionQueueId id) { + typename ExecutionQueue::scoped_ptr_t + ptr = ExecutionQueue::address(id); + if (ptr != NULL) { + return ptr->stop(); + } else { + return EINVAL; + } +} + +template +inline int execution_queue_join(ExecutionQueueId id) { + return ExecutionQueue::join(id.value); +} + +inline TaskOptions::TaskOptions() + : high_priority(false) + , in_place_if_possible(false) +{} + +inline TaskOptions::TaskOptions(bool high_priority, bool in_place_if_possible) + : high_priority(high_priority) + , in_place_if_possible(in_place_if_possible) +{} + +//--------------------- TaskIterator ------------------------ + +inline TaskIteratorBase::operator bool() const { + return !_is_stopped && !_should_break && _cur_node != NULL + && !_cur_node->stop_task; +} + +template +inline typename TaskIterator::reference +TaskIterator::operator*() const { + T* const ptr = (T* const)TaskAllocator::get_allocated_mem(cur_node()); + return *ptr; +} + +template +TaskIterator& TaskIterator::operator++() { + TaskIteratorBase::operator++(); + return *this; +} + +template +void TaskIterator::operator++(int) { + operator++(); +} + +inline TaskHandle::TaskHandle() + : node(NULL) + , version(0) +{} + +inline int execution_queue_cancel(const TaskHandle& h) { + if (h.node == NULL) { + return -1; + } + return h.node->cancel(h.version); +} + +// ---------------------ExecutionQueueBase-------------------- +inline bool ExecutionQueueBase::_more_tasks( + TaskNode* old_head, TaskNode** new_tail, + bool has_uniterated) { + + CHECK(old_head->next == NULL); + // Try to set _head to NULL to mark that the execute is done. + TaskNode* new_head = old_head; + TaskNode* desired = NULL; + bool return_when_no_more = false; + if (has_uniterated) { + desired = old_head; + return_when_no_more = true; + } + if (_head.compare_exchange_strong( + new_head, desired, butil::memory_order_acquire)) { + // No one added new tasks. + return return_when_no_more; + } + CHECK_NE(new_head, old_head); + // Above acquire fence pairs release fence of exchange in Write() to make + // sure that we see all fields of requests set. + + // Someone added new requests. + // Reverse the list until old_head. + TaskNode* tail = NULL; + if (new_tail) { + *new_tail = new_head; + } + TaskNode* p = new_head; + do { + while (p->next == TaskNode::UNCONNECTED) { + // TODO(gejun): elaborate this + sched_yield(); + } + TaskNode* const saved_next = p->next; + p->next = tail; + tail = p; + p = saved_next; + CHECK(p != NULL); + } while (p != old_head); + + // Link old list with new list. + old_head->next = tail; + return true; +} + +inline int ExecutionQueueBase::dereference() { + const uint64_t vref = _versioned_ref.fetch_sub( + 1, butil::memory_order_release); + const int32_t nref = _ref_of_vref(vref); + // We need make the fast path as fast as possible, don't put any extra + // code before this point + if (nref > 1) { + return 0; + } + const uint64_t id = _this_id; + if (__builtin_expect(nref == 1, 1)) { + const uint32_t ver = _version_of_vref(vref); + const uint32_t id_ver = _version_of_id(id); + // Besides first successful stop() adds 1 to version, one of + // those dereferencing nref from 1->0 adds another 1 to version. + // Notice "one of those": The wait-free address() may make ref of a + // version-unmatched slot change from 1 to 0 for mutiple times, we + // have to use version as a guard variable to prevent returning the + // executor to pool more than once. + if (__builtin_expect(ver == id_ver || ver == id_ver + 1, 1)) { + // sees nref:1->0, try to set version=id_ver+2,--nref. + // No retry: if version changes, the slot is already returned by + // another one who sees nref:1->0 concurrently; if nref changes, + // which must be non-zero, the slot will be returned when + // nref changes from 1->0 again. + // Example: + // stop(): --nref, sees nref:1->0 (1) + // try to set version=id_ver+2 (2) + // address(): ++nref, unmatched version (3) + // --nref, sees nref:1->0 (4) + // try to set version=id_ver+2 (5) + // 1,2,3,4,5 or 1,3,4,2,5: + // stop() succeeds, address() fails at (5). + // 1,3,2,4,5: stop() fails with (2), the slot will be + // returned by (5) of address() + // 1,3,4,5,2: stop() fails with (2), the slot is already + // returned by (5) of address(). + uint64_t expected_vref = vref - 1; + if (_versioned_ref.compare_exchange_strong( + expected_vref, _make_vref(id_ver + 2, 0), + butil::memory_order_acquire, + butil::memory_order_relaxed)) { + _on_recycle(); + // We don't return m immediately when the reference count + // reaches 0 as there might be in processing tasks. Instead + // _on_recycle would push a `stop_task' after which is executed + // m would be finally returned and reset + return 1; + } + return 0; + } + LOG(FATAL) << "Invalid id=" << id; + return -1; + } + LOG(FATAL) << "Over dereferenced id=" << id; + return -1; +} + +} // namespace bthread + +namespace butil { +// TaskNode::cancel() may access the TaskNode object returned to the ObjectPool, +// so ObjectPool can not poison the memory region of TaskNode. +template <> +struct ObjectPoolWithASanPoison : false_type {}; +} // namespace butil + +#endif //BTHREAD_EXECUTION_QUEUE_INL_H diff --git a/src/bthread/fd.cpp b/src/bthread/fd.cpp new file mode 100644 index 0000000..17ca63d --- /dev/null +++ b/src/bthread/fd.cpp @@ -0,0 +1,552 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Thu Aug 7 18:56:27 CST 2014 + +#include "butil/compat.h" +#include // std::nothrow +#include // poll() +#if defined(OS_MACOSX) +#include // struct kevent +#include // kevent(), kqueue() +#endif +#include "butil/atomicops.h" +#include "butil/time.h" +#include "butil/fd_utility.h" // make_non_blocking +#include "butil/logging.h" +#include "butil/third_party/murmurhash3/murmurhash3.h" // fmix32 +#include "butil/memory/scope_guard.h" +#include "bthread/butex.h" // butex_* +#include "bthread/task_group.h" // TaskGroup +#include "bthread/bthread.h" // bthread_start_urgent + +namespace butil { +extern int pthread_fd_wait(int fd, unsigned events, const timespec* abstime); +} + +// Implement bthread functions on file descriptors + +namespace bthread { + +extern BAIDU_THREAD_LOCAL TaskGroup* tls_task_group; + +template +class LazyArray { + struct Block { + butil::atomic items[BLOCK_SIZE]; + }; + +public: + LazyArray() { + memset(static_cast(_blocks), 0, sizeof(butil::atomic) * NBLOCK); + } + + butil::atomic* get_or_new(size_t index) { + const size_t block_index = index / BLOCK_SIZE; + if (block_index >= NBLOCK) { + return NULL; + } + const size_t block_offset = index - block_index * BLOCK_SIZE; + Block* b = _blocks[block_index].load(butil::memory_order_consume); + if (b != NULL) { + return b->items + block_offset; + } + b = new (std::nothrow) Block; + if (NULL == b) { + b = _blocks[block_index].load(butil::memory_order_consume); + return (b ? b->items + block_offset : NULL); + } + // Set items to default value of T. + std::fill(b->items, b->items + BLOCK_SIZE, T()); + Block* expected = NULL; + if (_blocks[block_index].compare_exchange_strong( + expected, b, butil::memory_order_release, + butil::memory_order_consume)) { + return b->items + block_offset; + } + delete b; + return expected->items + block_offset; + } + + butil::atomic* get(size_t index) const { + const size_t block_index = index / BLOCK_SIZE; + if (__builtin_expect(block_index < NBLOCK, 1)) { + const size_t block_offset = index - block_index * BLOCK_SIZE; + Block* const b = _blocks[block_index].load(butil::memory_order_consume); + if (__builtin_expect(b != NULL, 1)) { + return b->items + block_offset; + } + } + return NULL; + } + +private: + butil::atomic _blocks[NBLOCK]; +}; + +typedef butil::atomic EpollButex; + +static EpollButex* const CLOSING_GUARD = (EpollButex*)(intptr_t)-1L; + +#ifndef NDEBUG +butil::static_atomic break_nums = BUTIL_STATIC_ATOMIC_INIT(0); +#endif + +// Able to address 67108864 file descriptors, should be enough. +LazyArray fd_butexes; + +static const int BTHREAD_DEFAULT_EPOLL_SIZE = 65536; + +class EpollThread { +public: + EpollThread() + : _epfd(-1) + , _stop(false) + , _tid(0) { + } + + int start(int epoll_size) { + if (started()) { + return -1; + } + _start_mutex.lock(); + // Double check + if (started()) { + _start_mutex.unlock(); + return -1; + } +#if defined(OS_LINUX) + _epfd = epoll_create(epoll_size); +#elif defined(OS_MACOSX) + _epfd = kqueue(); +#endif + _start_mutex.unlock(); + if (_epfd < 0) { + PLOG(FATAL) << "Fail to epoll_create/kqueue"; + return -1; + } + bthread_attr_t attr = BTHREAD_ATTR_NORMAL; + bthread_attr_set_name(&attr, "EpollThread::run_this"); + if (bthread_start_background( + &_tid, &attr, EpollThread::run_this, this) != 0) { + close(_epfd); + _epfd = -1; + LOG(FATAL) << "Fail to create epoll bthread"; + return -1; + } + return 0; + } + + // Note: This function does not wake up suspended fd_wait. This is fine + // since stop_and_join is only called on program's termination + // (g_task_control.stop()), suspended bthreads do not block quit of + // worker pthreads and completion of g_task_control.stop(). + int stop_and_join() { + if (!started()) { + return 0; + } + // No matter what this function returns, _epfd will be set to -1 + // (making started() false) to avoid latter stop_and_join() to + // enter again. + const int saved_epfd = _epfd; + _epfd = -1; + + // epoll_wait cannot be woken up by closing _epfd. We wake up + // epoll_wait by inserting a fd continuously triggering EPOLLOUT. + // Visibility of _stop: constant EPOLLOUT forces epoll_wait to see + // _stop (to be true) finally. + _stop = true; + int closing_epoll_pipe[2]; + if (pipe(closing_epoll_pipe)) { + PLOG(FATAL) << "Fail to create closing_epoll_pipe"; + return -1; + } +#if defined(OS_LINUX) + epoll_event evt = { EPOLLOUT, { NULL } }; + if (epoll_ctl(saved_epfd, EPOLL_CTL_ADD, + closing_epoll_pipe[1], &evt) < 0) { +#elif defined(OS_MACOSX) + struct kevent kqueue_event; + EV_SET(&kqueue_event, closing_epoll_pipe[1], EVFILT_WRITE, EV_ADD | EV_ENABLE, + 0, 0, NULL); + if (kevent(saved_epfd, &kqueue_event, 1, NULL, 0, NULL) < 0) { +#endif + PLOG(FATAL) << "Fail to add closing_epoll_pipe into epfd=" + << saved_epfd; + return -1; + } + + const int rc = bthread_join(_tid, NULL); + if (rc) { + LOG(FATAL) << "Fail to join EpollThread, " << berror(rc); + return -1; + } + close(closing_epoll_pipe[0]); + close(closing_epoll_pipe[1]); + close(saved_epfd); + return 0; + } + + int fd_wait(int fd, unsigned events, const timespec* abstime) { + butil::atomic* p = fd_butexes.get_or_new(fd); + if (NULL == p) { + errno = ENOMEM; + return -1; + } + + EpollButex* butex = p->load(butil::memory_order_consume); + if (NULL == butex) { + // It is rare to wait on one file descriptor from multiple threads + // simultaneously. Creating singleton by optimistic locking here + // saves mutexes for each butex. + butex = butex_create_checked(); + butex->store(0, butil::memory_order_relaxed); + EpollButex* expected = NULL; + if (!p->compare_exchange_strong(expected, butex, + butil::memory_order_release, + butil::memory_order_consume)) { + butex_destroy(butex); + butex = expected; + } + } + + while (butex == CLOSING_GUARD) { // bthread_close() is running. + if (sched_yield() < 0) { + return -1; + } + butex = p->load(butil::memory_order_consume); + } + // Save value of butex before adding to epoll because the butex may + // be changed before butex_wait. No memory fence because EPOLL_CTL_MOD + // and EPOLL_CTL_ADD shall have release fence. + const int expected_val = butex->load(butil::memory_order_relaxed); + +#if defined(OS_LINUX) +# ifdef BAIDU_KERNEL_FIXED_EPOLLONESHOT_BUG + epoll_event evt = { events | EPOLLONESHOT, { butex } }; + if (epoll_ctl(_epfd, EPOLL_CTL_MOD, fd, &evt) < 0) { + if (epoll_ctl(_epfd, EPOLL_CTL_ADD, fd, &evt) < 0 && + errno != EEXIST) { + PLOG(FATAL) << "Fail to add fd=" << fd << " into epfd=" << _epfd; + return -1; + } + } +# else + epoll_event evt; + evt.events = events; + evt.data.fd = fd; + if (epoll_ctl(_epfd, EPOLL_CTL_ADD, fd, &evt) < 0 && + errno != EEXIST) { + PLOG(FATAL) << "Fail to add fd=" << fd << " into epfd=" << _epfd; + return -1; + } +# endif +#elif defined(OS_MACOSX) + struct kevent kqueue_event; + EV_SET(&kqueue_event, fd, events, EV_ADD | EV_ENABLE | EV_ONESHOT, + 0, 0, butex); + if (kevent(_epfd, &kqueue_event, 1, NULL, 0, NULL) < 0) { + PLOG(FATAL) << "Fail to add fd=" << fd << " into kqueuefd=" << _epfd; + return -1; + } +#endif + while (butex->load(butil::memory_order_relaxed) == expected_val) { + if (butex_wait(butex, expected_val, abstime) < 0 && + errno != EWOULDBLOCK && errno != EINTR) { + return -1; + } + } + return 0; + } + + int fd_close(int fd) { + if (fd < 0) { + // what close(-1) returns + errno = EBADF; + return -1; + } + butil::atomic* pbutex = bthread::fd_butexes.get(fd); + if (NULL == pbutex) { + // Did not call bthread_fd functions, close directly. + return close(fd); + } + EpollButex* butex = pbutex->exchange( + CLOSING_GUARD, butil::memory_order_relaxed); + if (butex == CLOSING_GUARD) { + // concurrent double close detected. + errno = EBADF; + return -1; + } + if (butex != NULL) { + butex->fetch_add(1, butil::memory_order_relaxed); + butex_wake_all(butex); + } +#if defined(OS_LINUX) + epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, NULL); +#elif defined(OS_MACOSX) + struct kevent evt; + EV_SET(&evt, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); + kevent(_epfd, &evt, 1, NULL, 0, NULL); + EV_SET(&evt, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); + kevent(_epfd, &evt, 1, NULL, 0, NULL); +#endif + const int rc = close(fd); + pbutex->exchange(butex, butil::memory_order_relaxed); + return rc; + } + + bool started() const { + return _epfd >= 0; + } + +private: + static void* run_this(void* arg) { + return static_cast(arg)->run(); + } + + void* run() { + const int initial_epfd = _epfd; + const size_t MAX_EVENTS = 32; +#if defined(OS_LINUX) + epoll_event* e = new (std::nothrow) epoll_event[MAX_EVENTS]; +#elif defined(OS_MACOSX) + typedef struct kevent KEVENT; + struct kevent* e = new (std::nothrow) KEVENT[MAX_EVENTS]; +#endif + if (NULL == e) { + LOG(FATAL) << "Fail to new epoll_event"; + return NULL; + } + +#if defined(OS_LINUX) +# ifndef BAIDU_KERNEL_FIXED_EPOLLONESHOT_BUG + DLOG(INFO) << "Use DEL+ADD instead of EPOLLONESHOT+MOD due to kernel bug. Performance will be much lower."; +# endif +#endif + while (!_stop) { + const int epfd = _epfd; +#if defined(OS_LINUX) + const int n = epoll_wait(epfd, e, MAX_EVENTS, -1); +#elif defined(OS_MACOSX) + const int n = kevent(epfd, NULL, 0, e, MAX_EVENTS, NULL); +#endif + if (_stop) { + break; + } + + if (n < 0) { + if (errno == EINTR) { +#ifndef NDEBUG + break_nums.fetch_add(1, butil::memory_order_relaxed); + int* p = &errno; + const char* b = berror(); + const char* b2 = berror(errno); + DLOG(FATAL) << "Fail to epoll epfd=" << epfd << ", " + << errno << " " << p << " " << b << " " << b2; +#endif + continue; + } + + PLOG(INFO) << "Fail to epoll epfd=" << epfd; + break; + } + +#if defined(OS_LINUX) +# ifndef BAIDU_KERNEL_FIXED_EPOLLONESHOT_BUG + for (int i = 0; i < n; ++i) { + epoll_ctl(epfd, EPOLL_CTL_DEL, e[i].data.fd, NULL); + } +# endif +#endif + for (int i = 0; i < n; ++i) { +#if defined(OS_LINUX) +# ifdef BAIDU_KERNEL_FIXED_EPOLLONESHOT_BUG + EpollButex* butex = static_cast(e[i].data.ptr); +# else + butil::atomic* pbutex = fd_butexes.get(e[i].data.fd); + EpollButex* butex = pbutex ? + pbutex->load(butil::memory_order_consume) : NULL; +# endif +#elif defined(OS_MACOSX) + EpollButex* butex = static_cast(e[i].udata); +#endif + if (butex != NULL && butex != CLOSING_GUARD) { + butex->fetch_add(1, butil::memory_order_relaxed); + butex_wake_all(butex); + } + } + } + + delete [] e; + DLOG(INFO) << "EpollThread=" << _tid << "(epfd=" + << initial_epfd << ") is about to stop"; + return NULL; + } + + int _epfd; + bool _stop; + bthread_t _tid; + butil::Mutex _start_mutex; +}; + +EpollThread epoll_thread[BTHREAD_EPOLL_THREAD_NUM]; + +static inline EpollThread& get_epoll_thread(int fd) { + if (BTHREAD_EPOLL_THREAD_NUM == 1UL) { + EpollThread& et = epoll_thread[0]; + et.start(BTHREAD_DEFAULT_EPOLL_SIZE); + return et; + } + + EpollThread& et = epoll_thread[butil::fmix32(fd) % BTHREAD_EPOLL_THREAD_NUM]; + et.start(BTHREAD_DEFAULT_EPOLL_SIZE); + return et; +} + +//TODO(zhujiashun): change name +int stop_and_join_epoll_threads() { + // Returns -1 if any epoll thread failed to stop. + int rc = 0; + for (size_t i = 0; i < BTHREAD_EPOLL_THREAD_NUM; ++i) { + if (epoll_thread[i].stop_and_join() < 0) { + rc = -1; + } + } + return rc; +} + +// For pthreads. +int pthread_fd_wait(int fd, unsigned events, + const timespec* abstime) { + return butil::pthread_fd_wait(fd, events, abstime); +} + +} // namespace bthread + +extern "C" { + +int bthread_fd_wait(int fd, unsigned events) { + if (fd < 0) { + errno = EINVAL; + return -1; + } + bthread::TaskGroup* g = bthread::tls_task_group; + if (NULL != g && !g->is_current_pthread_task()) { + return bthread::get_epoll_thread(fd).fd_wait( + fd, events, NULL); + } + return bthread::pthread_fd_wait(fd, events, NULL); +} + +int bthread_fd_timedwait(int fd, unsigned events, + const timespec* abstime) { + if (NULL == abstime) { + return bthread_fd_wait(fd, events); + } + if (fd < 0) { + errno = EINVAL; + return -1; + } + bthread::TaskGroup* g = bthread::tls_task_group; + if (NULL != g && !g->is_current_pthread_task()) { + return bthread::get_epoll_thread(fd).fd_wait( + fd, events, abstime); + } + return bthread::pthread_fd_wait(fd, events, abstime); +} + +int bthread_connect(int sockfd, const sockaddr* serv_addr, + socklen_t addrlen) { + bthread::TaskGroup* g = bthread::tls_task_group; + if (NULL == g || g->is_current_pthread_task()) { + return ::connect(sockfd, serv_addr, addrlen); + } + + bool is_blocking = butil::is_blocking(sockfd); + if (is_blocking) { + butil::make_non_blocking(sockfd); + } + // Scoped non-blocking. + auto guard = butil::MakeScopeGuard([is_blocking, sockfd]() { + if (is_blocking) { + butil::make_blocking(sockfd); + } + }); + + const int rc = ::connect(sockfd, serv_addr, addrlen); + if (rc == 0 || errno != EINPROGRESS) { + return rc; + } +#if defined(OS_LINUX) + if (bthread_fd_wait(sockfd, EPOLLOUT) < 0) { +#elif defined(OS_MACOSX) + if (bthread_fd_wait(sockfd, EVFILT_WRITE) < 0) { +#endif + return -1; + } + + if (butil::is_connected(sockfd) != 0) { + return -1; + } + + return 0; +} + +int bthread_timed_connect(int sockfd, const struct sockaddr* serv_addr, + socklen_t addrlen, const timespec* abstime) { + if (!abstime) { + return bthread_connect(sockfd, serv_addr, addrlen); + } + + bool is_blocking = butil::is_blocking(sockfd); + if (is_blocking) { + butil::make_non_blocking(sockfd); + } + // Scoped non-blocking. + auto guard = butil::MakeScopeGuard([is_blocking, sockfd]() { + if (is_blocking) { + butil::make_blocking(sockfd); + } + }); + + const int rc = ::connect(sockfd, serv_addr, addrlen); + if (rc == 0 || errno != EINPROGRESS) { + return rc; + } +#if defined(OS_LINUX) + if (bthread_fd_timedwait(sockfd, EPOLLOUT, abstime) < 0) { +#elif defined(OS_MACOSX) + if (bthread_fd_timedwait(sockfd, EVFILT_WRITE, abstime) < 0) { +#endif + return -1; + } + + if (butil::is_connected(sockfd) != 0) { + return -1; + } + + return 0; +} + +// This does not wake pthreads calling bthread_fd_*wait. +int bthread_close(int fd) { + return bthread::get_epoll_thread(fd).fd_close(fd); +} + +} // extern "C" diff --git a/src/bthread/id.cpp b/src/bthread/id.cpp new file mode 100644 index 0000000..7aabed6 --- /dev/null +++ b/src/bthread/id.cpp @@ -0,0 +1,800 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Sun Aug 3 12:46:15 CST 2014 + +#include +#include "butil/logging.h" +#include "bthread/butex.h" // butex_* +#include "bthread/mutex.h" +#include "bthread/list_of_abafree_id.h" +#include "butil/resource_pool.h" +#include "bthread/bthread.h" + +namespace bthread { + +// This queue reduces the chance to allocate memory for deque +template +class SmallQueue { +public: + SmallQueue() : _begin(0), _size(0), _full(NULL) {} + + void push(const T& val) { + if (_full != NULL && !_full->empty()) { + _full->push_back(val); + } else if (_size < N) { + int tail = _begin + _size; + if (tail >= N) { + tail -= N; + } + _c[tail] = val; + ++_size; + } else { + if (_full == NULL) { + _full = new std::deque; + } + _full->push_back(val); + } + } + bool pop(T* val) { + if (_size > 0) { + *val = _c[_begin]; + ++_begin; + if (_begin >= N) { + _begin -= N; + } + --_size; + return true; + } else if (_full && !_full->empty()) { + *val = _full->front(); + _full->pop_front(); + return true; + } + return false; + } + bool empty() const { + return _size == 0 && (_full == NULL || _full->empty()); + } + + size_t size() const { + return _size + (_full ? _full->size() : 0); + } + + void clear() { + _size = 0; + _begin = 0; + if (_full) { + _full->clear(); + } + } + + ~SmallQueue() { + delete _full; + _full = NULL; + } + +private: + DISALLOW_COPY_AND_ASSIGN(SmallQueue); + + int _begin; + int _size; + T _c[N]; + std::deque* _full; +}; + +struct PendingError { + bthread_id_t id; + int error_code; + std::string error_text; + const char *location; + + PendingError() : id(INVALID_BTHREAD_ID), error_code(0), location(NULL) {} +}; + +struct BAIDU_CACHELINE_ALIGNMENT Id { + // first_ver ~ locked_ver - 1: unlocked versions + // locked_ver: locked + // unlockable_ver: locked and about to be destroyed + // contended_ver: locked and contended + uint32_t first_ver; + uint32_t locked_ver; + FastPthreadMutex mutex; + void* data; + int (*on_error)(bthread_id_t, void*, int); + int (*on_error2)(bthread_id_t, void*, int, const std::string&); + const char *lock_location; + uint32_t* butex; + uint32_t* join_butex; + SmallQueue pending_q; + + Id() { + // Although value of the butex(as version part of bthread_id_t) + // does not matter, we set it to 0 to make program more deterministic. + butex = bthread::butex_create_checked(); + join_butex = bthread::butex_create_checked(); + *butex = 0; + *join_butex = 0; + } + + ~Id() { + bthread::butex_destroy(butex); + bthread::butex_destroy(join_butex); + } + + inline bool has_version(uint32_t id_ver) const { + return id_ver >= first_ver && id_ver < locked_ver; + } + inline uint32_t contended_ver() const { return locked_ver + 1; } + inline uint32_t unlockable_ver() const { return locked_ver + 2; } + inline uint32_t last_ver() const { return unlockable_ver(); } + + // also the next "first_ver" + inline uint32_t end_ver() const { return last_ver() + 1; } +}; + +BAIDU_CASSERT(sizeof(Id) % 64 == 0, sizeof_Id_must_align); + +typedef butil::ResourceId IdResourceId; + +inline bthread_id_t make_id(uint32_t version, IdResourceId slot) { + const bthread_id_t tmp = + { (((uint64_t)slot.value) << 32) | (uint64_t)version }; + return tmp; +} + +inline IdResourceId get_slot(bthread_id_t id) { + const IdResourceId tmp = { (id.value >> 32) }; + return tmp; +} + +inline uint32_t get_version(bthread_id_t id) { + return (uint32_t)(id.value & 0xFFFFFFFFul); +} + +inline bool id_exists_with_true_negatives(bthread_id_t id) { + Id* const meta = address_resource(get_slot(id)); + if (meta == NULL) { + return false; + } + const uint32_t id_ver = bthread::get_version(id); + return id_ver >= meta->first_ver && id_ver <= meta->last_ver(); +} +// required by unittest +uint32_t id_value(bthread_id_t id) { + Id* const meta = address_resource(get_slot(id)); + if (meta != NULL) { + return *meta->butex; + } + return 0; // valid version never be zero +} + +static int default_bthread_id_on_error(bthread_id_t id, void*, int) { + return bthread_id_unlock_and_destroy(id); +} +static int default_bthread_id_on_error2( + bthread_id_t id, void*, int, const std::string&) { + return bthread_id_unlock_and_destroy(id); +} + +void id_status(bthread_id_t id, std::ostream &os) { + bthread::Id* const meta = address_resource(bthread::get_slot(id)); + if (!meta) { + os << "Invalid id=" << id.value << '\n'; + return; + } + const uint32_t id_ver = bthread::get_version(id); + uint32_t* butex = meta->butex; + bool valid = true; + void* data = NULL; + int (*on_error)(bthread_id_t, void*, int) = NULL; + int (*on_error2)(bthread_id_t, void*, int, const std::string&) = NULL; + uint32_t first_ver = 0; + uint32_t locked_ver = 0; + uint32_t unlockable_ver = 0; + uint32_t contended_ver = 0; + const char *lock_location = NULL; + SmallQueue pending_q; + uint32_t butex_value = 0; + + meta->mutex.lock(); + if (meta->has_version(id_ver)) { + data = meta->data; + on_error = meta->on_error; + on_error2 = meta->on_error2; + first_ver = meta->first_ver; + locked_ver = meta->locked_ver; + unlockable_ver = meta->unlockable_ver(); + contended_ver = meta->contended_ver(); + lock_location = meta->lock_location; + const size_t size = meta->pending_q.size(); + for (size_t i = 0; i < size; ++i) { + PendingError front; + meta->pending_q.pop(&front); + meta->pending_q.push(front); + pending_q.push(front); + } + butex_value = *butex; + } else { + valid = false; + } + meta->mutex.unlock(); + + if (valid) { + os << "First id: " + << bthread::make_id(first_ver, bthread::get_slot(id)).value << '\n' + << "Range: " << locked_ver - first_ver << '\n' + << "Status: "; + if (butex_value != first_ver) { + os << "LOCKED at " << lock_location; + if (butex_value == contended_ver) { + os << " (CONTENDED)"; + } else if (butex_value == unlockable_ver) { + os << " (ABOUT TO DESTROY)"; + } else { + os << " (UNCONTENDED)"; + } + } else { + os << "UNLOCKED"; + } + os << "\nPendingQ:"; + if (pending_q.empty()) { + os << " EMPTY"; + } else { + const size_t size = pending_q.size(); + for (size_t i = 0; i < size; ++i) { + PendingError front; + pending_q.pop(&front); + os << " (" << front.location << "/E" << front.error_code + << '/' << front.error_text << ')'; + } + } + if (on_error) { + if (on_error == default_bthread_id_on_error) { + os << "\nOnError: unlock_and_destroy"; + } else { + os << "\nOnError: " << (void*)on_error; + } + } else { + if (on_error2 == default_bthread_id_on_error2) { + os << "\nOnError2: unlock_and_destroy"; + } else { + os << "\nOnError2: " << (void*)on_error2; + } + } + os << "\nData: " << data; + } else { + os << "Invalid id=" << id.value; + } + os << '\n'; +} + +void id_pool_status(std::ostream &os) { + os << butil::describe_resources() << '\n'; +} + +struct IdTraits { + static const size_t BLOCK_SIZE = 63; + static const size_t MAX_ENTRIES = 100000; + static const size_t INIT_GC_SIZE = 4096; + static const bthread_id_t ID_INIT; + static bool exists(bthread_id_t id) + { return bthread::id_exists_with_true_negatives(id); } +}; +const bthread_id_t IdTraits::ID_INIT = INVALID_BTHREAD_ID; + +typedef ListOfABAFreeId IdList; + +struct IdResetter { + explicit IdResetter(int ec, const std::string& et) + : _error_code(ec), _error_text(et) {} + void operator()(bthread_id_t & id) const { + bthread_id_error2_verbose( + id, _error_code, _error_text, __FILE__ ":" BAIDU_SYMBOLSTR(__LINE__)); + id = INVALID_BTHREAD_ID; + } +private: + int _error_code; + const std::string& _error_text; +}; + +size_t get_sizes(const bthread_id_list_t* list, size_t* cnt, size_t n) { + if (list->impl == NULL) { + return 0; + } + return static_cast(list->impl)->get_sizes(cnt, n); +} + +const int ID_MAX_RANGE = 1024; + +static int id_create_impl( + bthread_id_t* id, void* data, + int (*on_error)(bthread_id_t, void*, int), + int (*on_error2)(bthread_id_t, void*, int, const std::string&)) { + IdResourceId slot; + Id* const meta = get_resource(&slot); + if (meta) { + meta->data = data; + meta->on_error = on_error; + meta->on_error2 = on_error2; + CHECK(meta->pending_q.empty()); + uint32_t* butex = meta->butex; + if (0 == *butex || *butex + ID_MAX_RANGE + 2 < *butex) { + // Skip 0 so that bthread_id_t is never 0 + // avoid overflow to make comparisons simpler. + *butex = 1; + } + *meta->join_butex = *butex; + meta->first_ver = *butex; + meta->locked_ver = *butex + 1; + *id = make_id(*butex, slot); + return 0; + } + return ENOMEM; +} + +static int id_create_ranged_impl( + bthread_id_t* id, void* data, + int (*on_error)(bthread_id_t, void*, int), + int (*on_error2)(bthread_id_t, void*, int, const std::string&), + int range) { + if (range < 1 || range > ID_MAX_RANGE) { + LOG_IF(FATAL, range < 1) << "range must be positive, actually " << range; + LOG_IF(FATAL, range > ID_MAX_RANGE ) << "max of range is " + << ID_MAX_RANGE << ", actually " << range; + return EINVAL; + } + IdResourceId slot; + Id* const meta = get_resource(&slot); + if (meta) { + meta->data = data; + meta->on_error = on_error; + meta->on_error2 = on_error2; + CHECK(meta->pending_q.empty()); + uint32_t* butex = meta->butex; + if (0 == *butex || *butex + ID_MAX_RANGE + 2 < *butex) { + // Skip 0 so that bthread_id_t is never 0 + // avoid overflow to make comparisons simpler. + *butex = 1; + } + *meta->join_butex = *butex; + meta->first_ver = *butex; + meta->locked_ver = *butex + range; + *id = make_id(*butex, slot); + return 0; + } + return ENOMEM; +} + +} // namespace bthread + +extern "C" { + +int bthread_id_create( + bthread_id_t* id, void* data, + int (*on_error)(bthread_id_t, void*, int)) { + return bthread::id_create_impl( + id, data, + (on_error ? on_error : bthread::default_bthread_id_on_error), NULL); +} + +int bthread_id_create_ranged(bthread_id_t* id, void* data, + int (*on_error)(bthread_id_t, void*, int), + int range) { + return bthread::id_create_ranged_impl( + id, data, + (on_error ? on_error : bthread::default_bthread_id_on_error), + NULL, range); +} + +int bthread_id_lock_and_reset_range_verbose( + bthread_id_t id, void **pdata, int range, const char *location) { + bthread::Id* const meta = address_resource(bthread::get_slot(id)); + if (!meta) { + return EINVAL; + } + const uint32_t id_ver = bthread::get_version(id); + uint32_t* butex = meta->butex; + bool ever_contended = false; + meta->mutex.lock(); + while (meta->has_version(id_ver)) { + if (*butex == meta->first_ver) { + // contended locker always wakes up the butex at unlock. + meta->lock_location = location; + if (range == 0) { + // fast path + } else if (range < 0 || + range > bthread::ID_MAX_RANGE || + range + meta->first_ver <= meta->locked_ver) { + LOG_IF(FATAL, range < 0) << "range must be positive, actually " + << range; + LOG_IF(FATAL, range > bthread::ID_MAX_RANGE) + << "max range is " << bthread::ID_MAX_RANGE + << ", actually " << range; + } else { + meta->locked_ver = meta->first_ver + range; + } + *butex = (ever_contended ? meta->contended_ver() : meta->locked_ver); + meta->mutex.unlock(); + if (pdata) { + *pdata = meta->data; + } + return 0; + } else if (*butex != meta->unlockable_ver()) { + *butex = meta->contended_ver(); + uint32_t expected_ver = *butex; + meta->mutex.unlock(); + ever_contended = true; + if (bthread::butex_wait(butex, expected_ver, NULL) < 0 && + errno != EWOULDBLOCK && errno != EINTR) { + return errno; + } + meta->mutex.lock(); + } else { // bthread_id_about_to_destroy was called. + meta->mutex.unlock(); + return EPERM; + } + } + meta->mutex.unlock(); + return EINVAL; +} + +int bthread_id_error_verbose(bthread_id_t id, int error_code, + const char *location) { + return bthread_id_error2_verbose(id, error_code, std::string(), location); +} + +int bthread_id_about_to_destroy(bthread_id_t id) { + bthread::Id* const meta = address_resource(bthread::get_slot(id)); + if (!meta) { + return EINVAL; + } + const uint32_t id_ver = bthread::get_version(id); + uint32_t* butex = meta->butex; + meta->mutex.lock(); + if (!meta->has_version(id_ver)) { + meta->mutex.unlock(); + return EINVAL; + } + if (*butex == meta->first_ver) { + meta->mutex.unlock(); + LOG(FATAL) << "bthread_id=" << id.value << " is not locked!"; + return EPERM; + } + const bool contended = (*butex == meta->contended_ver()); + *butex = meta->unlockable_ver(); + meta->mutex.unlock(); + if (contended) { + // wake up all waiting lockers. + bthread::butex_wake_except(butex, 0); + } + return 0; +} + +int bthread_id_cancel(bthread_id_t id) { + bthread::Id* const meta = address_resource(bthread::get_slot(id)); + if (!meta) { + return EINVAL; + } + uint32_t* butex = meta->butex; + const uint32_t id_ver = bthread::get_version(id); + meta->mutex.lock(); + if (!meta->has_version(id_ver)) { + meta->mutex.unlock(); + return EINVAL; + } + if (*butex != meta->first_ver) { + meta->mutex.unlock(); + return EPERM; + } + *butex = meta->end_ver(); + meta->first_ver = *butex; + meta->locked_ver = *butex; + meta->mutex.unlock(); + return_resource(bthread::get_slot(id)); + return 0; +} + +int bthread_id_join(bthread_id_t id) { + const bthread::IdResourceId slot = bthread::get_slot(id); + bthread::Id* const meta = address_resource(slot); + if (!meta) { + // The id is not created yet, this join is definitely wrong. + return EINVAL; + } + const uint32_t id_ver = bthread::get_version(id); + uint32_t* join_butex = meta->join_butex; + while (1) { + meta->mutex.lock(); + const bool has_ver = meta->has_version(id_ver); + const uint32_t expected_ver = *join_butex; + meta->mutex.unlock(); + if (!has_ver) { + break; + } + if (bthread::butex_wait(join_butex, expected_ver, NULL) < 0 && + errno != EWOULDBLOCK && errno != EINTR) { + return errno; + } + } + return 0; +} + +int bthread_id_trylock(bthread_id_t id, void** pdata) { + bthread::Id* const meta = address_resource(bthread::get_slot(id)); + if (!meta) { + return EINVAL; + } + uint32_t* butex = meta->butex; + const uint32_t id_ver = bthread::get_version(id); + meta->mutex.lock(); + if (!meta->has_version(id_ver)) { + meta->mutex.unlock(); + return EINVAL; + } + if (*butex != meta->first_ver) { + meta->mutex.unlock(); + return EBUSY; + } + *butex = meta->locked_ver; + meta->mutex.unlock(); + if (pdata != NULL) { + *pdata = meta->data; + } + return 0; +} + +int bthread_id_lock_verbose(bthread_id_t id, void** pdata, + const char *location) { + return bthread_id_lock_and_reset_range_verbose(id, pdata, 0, location); +} + +int bthread_id_unlock(bthread_id_t id) { + bthread::Id* const meta = address_resource(bthread::get_slot(id)); + if (!meta) { + return EINVAL; + } + uint32_t* butex = meta->butex; + // Release fence makes sure all changes made before signal visible to + // woken-up waiters. + const uint32_t id_ver = bthread::get_version(id); + meta->mutex.lock(); + if (!meta->has_version(id_ver)) { + meta->mutex.unlock(); + LOG(FATAL) << "Invalid bthread_id=" << id.value; + return EINVAL; + } + if (*butex == meta->first_ver) { + meta->mutex.unlock(); + LOG(FATAL) << "bthread_id=" << id.value << " is not locked!"; + return EPERM; + } + bthread::PendingError front; + if (meta->pending_q.pop(&front)) { + meta->lock_location = front.location; + meta->mutex.unlock(); + if (meta->on_error) { + return meta->on_error(front.id, meta->data, front.error_code); + } else { + return meta->on_error2(front.id, meta->data, front.error_code, + front.error_text); + } + } else { + const bool contended = (*butex == meta->contended_ver()); + *butex = meta->first_ver; + meta->mutex.unlock(); + if (contended) { + // We may wake up already-reused id, but that's OK. + bthread::butex_wake(butex); + } + return 0; + } +} + +int bthread_id_unlock_and_destroy(bthread_id_t id) { + bthread::Id* const meta = address_resource(bthread::get_slot(id)); + if (!meta) { + return EINVAL; + } + uint32_t* butex = meta->butex; + uint32_t* join_butex = meta->join_butex; + const uint32_t id_ver = bthread::get_version(id); + meta->mutex.lock(); + if (!meta->has_version(id_ver)) { + meta->mutex.unlock(); + LOG(FATAL) << "Invalid bthread_id=" << id.value; + return EINVAL; + } + if (*butex == meta->first_ver) { + meta->mutex.unlock(); + LOG(FATAL) << "bthread_id=" << id.value << " is not locked!"; + return EPERM; + } + const uint32_t next_ver = meta->end_ver(); + *butex = next_ver; + *join_butex = next_ver; + meta->first_ver = next_ver; + meta->locked_ver = next_ver; + meta->pending_q.clear(); + meta->mutex.unlock(); + // Notice that butex_wake* returns # of woken-up, not successful or not. + bthread::butex_wake_except(butex, 0); + bthread::butex_wake_all(join_butex); + return_resource(bthread::get_slot(id)); + return 0; +} + +int bthread_id_list_init(bthread_id_list_t* list, + unsigned /*size*/, + unsigned /*conflict_size*/) { + list->impl = NULL; // create on demand. + // Set unused fields to zero as well. + list->head = 0; + list->size = 0; + list->conflict_head = 0; + list->conflict_size = 0; + return 0; +} + +void bthread_id_list_destroy(bthread_id_list_t* list) { + delete static_cast(list->impl); + list->impl = NULL; +} + +int bthread_id_list_add(bthread_id_list_t* list, bthread_id_t id) { + if (list->impl == NULL) { + list->impl = new (std::nothrow) bthread::IdList; + if (NULL == list->impl) { + return ENOMEM; + } + } + return static_cast(list->impl)->add(id); +} + +int bthread_id_list_reset(bthread_id_list_t* list, int error_code) { + return bthread_id_list_reset2(list, error_code, std::string()); +} + +void bthread_id_list_swap(bthread_id_list_t* list1, + bthread_id_list_t* list2) { + std::swap(list1->impl, list2->impl); +} + +int bthread_id_list_reset_pthreadsafe(bthread_id_list_t* list, int error_code, + pthread_mutex_t* mutex) { + return bthread_id_list_reset2_pthreadsafe( + list, error_code, std::string(), mutex); +} + +int bthread_id_list_reset_bthreadsafe(bthread_id_list_t* list, int error_code, + bthread_mutex_t* mutex) { + return bthread_id_list_reset2_bthreadsafe( + list, error_code, std::string(), mutex); +} + +} // extern "C" + +int bthread_id_create2( + bthread_id_t* id, void* data, + int (*on_error)(bthread_id_t, void*, int, const std::string&)) { + return bthread::id_create_impl( + id, data, NULL, + (on_error ? on_error : bthread::default_bthread_id_on_error2)); +} + +int bthread_id_create2_ranged( + bthread_id_t* id, void* data, + int (*on_error)(bthread_id_t, void*, int, const std::string&), + int range) { + return bthread::id_create_ranged_impl( + id, data, NULL, + (on_error ? on_error : bthread::default_bthread_id_on_error2), range); +} + +int bthread_id_error2_verbose(bthread_id_t id, int error_code, + const std::string& error_text, + const char *location) { + bthread::Id* const meta = address_resource(bthread::get_slot(id)); + if (!meta) { + return EINVAL; + } + const uint32_t id_ver = bthread::get_version(id); + uint32_t* butex = meta->butex; + meta->mutex.lock(); + if (!meta->has_version(id_ver)) { + meta->mutex.unlock(); + return EINVAL; + } + if (*butex == meta->first_ver) { + *butex = meta->locked_ver; + meta->lock_location = location; + meta->mutex.unlock(); + if (meta->on_error) { + return meta->on_error(id, meta->data, error_code); + } else { + return meta->on_error2(id, meta->data, error_code, error_text); + } + } else { + bthread::PendingError e; + e.id = id; + e.error_code = error_code; + e.error_text = error_text; + e.location = location; + meta->pending_q.push(e); + meta->mutex.unlock(); + return 0; + } +} + +int bthread_id_list_reset2(bthread_id_list_t* list, + int error_code, + const std::string& error_text) { + if (list->impl != NULL) { + static_cast(list->impl)->apply( + bthread::IdResetter(error_code, error_text)); + } + return 0; +} + +int bthread_id_list_reset2_pthreadsafe(bthread_id_list_t* list, + int error_code, + const std::string& error_text, + pthread_mutex_t* mutex) { + if (mutex == NULL) { + return EINVAL; + } + if (list->impl == NULL) { + return 0; + } + bthread_id_list_t tmplist; + const int rc = bthread_id_list_init(&tmplist, 0, 0); + if (rc != 0) { + return rc; + } + // Swap out the list then reset. The critical section is very small. + pthread_mutex_lock(mutex); + std::swap(list->impl, tmplist.impl); + pthread_mutex_unlock(mutex); + const int rc2 = bthread_id_list_reset2(&tmplist, error_code, error_text); + bthread_id_list_destroy(&tmplist); + return rc2; +} + +int bthread_id_list_reset2_bthreadsafe(bthread_id_list_t* list, + int error_code, + const std::string& error_text, + bthread_mutex_t* mutex) { + if (mutex == NULL) { + return EINVAL; + } + if (list->impl == NULL) { + return 0; + } + bthread_id_list_t tmplist; + const int rc = bthread_id_list_init(&tmplist, 0, 0); + if (rc != 0) { + return rc; + } + // Swap out the list then reset. The critical section is very small. + bthread_mutex_lock(mutex); + std::swap(list->impl, tmplist.impl); + bthread_mutex_unlock(mutex); + const int rc2 = bthread_id_list_reset2(&tmplist, error_code, error_text); + bthread_id_list_destroy(&tmplist); + return rc2; +} diff --git a/src/bthread/id.h b/src/bthread/id.h new file mode 100644 index 0000000..f9fef65 --- /dev/null +++ b/src/bthread/id.h @@ -0,0 +1,193 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Tue Jul 10 17:40:58 CST 2012 + +#ifndef BTHREAD_ID_H +#define BTHREAD_ID_H + +#include "butil/macros.h" // BAIDU_SYMBOLSTR +#include "bthread/types.h" + +__BEGIN_DECLS + +// ---------------------------------------------------------------------- +// Functions to create 64-bit identifiers that can be attached with data +// and locked without ABA issues. All functions can be called from +// multiple threads simultaneously. Notice that bthread_id_t is designed +// for managing a series of non-heavily-contended actions on an object. +// It's slower than mutex and not proper for general synchronizations. +// ---------------------------------------------------------------------- + +// Create a bthread_id_t and put it into *id. Crash when `id' is NULL. +// id->value will never be zero. +// `on_error' will be called after bthread_id_error() is called. +// ------------------------------------------------------------------------- +// ! User must call bthread_id_unlock() or bthread_id_unlock_and_destroy() +// ! inside on_error. +// ------------------------------------------------------------------------- +// Returns 0 on success, error code otherwise. +int bthread_id_create( + bthread_id_t* id, void* data, + int (*on_error)(bthread_id_t id, void* data, int error_code)); + +// When this function is called successfully, *id, *id+1 ... *id + range - 1 +// are mapped to same internal entity. Operations on any of the id work as +// if they're manipulating a same id. `on_error' is called with the id issued +// by corresponding bthread_id_error(). This is designed to let users encode +// versions into identifiers. +// `range' is limited inside [1, 1024]. +int bthread_id_create_ranged( + bthread_id_t* id, void* data, + int (*on_error)(bthread_id_t id, void* data, int error_code), + int range); + +// Wait until `id' being destroyed. +// Waiting on a destroyed bthread_id_t returns immediately. +// Returns 0 on success, error code otherwise. +int bthread_id_join(bthread_id_t id); + +// Destroy a created but never-used bthread_id_t. +// Returns 0 on success, EINVAL otherwise. +int bthread_id_cancel(bthread_id_t id); + +// Issue an error to `id'. +// If `id' is not locked, lock the id and run `on_error' immediately. Otherwise +// `on_error' will be called with the error just before `id' being unlocked. +// If `id' is destroyed, un-called on_error are dropped. +// Returns 0 on success, error code otherwise. +#define bthread_id_error(id, err) \ + bthread_id_error_verbose(id, err, __FILE__ ":" BAIDU_SYMBOLSTR(__LINE__)) + +int bthread_id_error_verbose(bthread_id_t id, int error_code, + const char *location); + +// Make other bthread_id_lock/bthread_id_trylock on the id fail, the id must +// already be locked. If the id is unlocked later rather than being destroyed, +// effect of this function is cancelled. This function avoids useless +// waiting on a bthread_id which will be destroyed soon but still needs to +// be joinable. +// Returns 0 on success, error code otherwise. +int bthread_id_about_to_destroy(bthread_id_t id); + +// Try to lock `id' (for using the data exclusively) +// On success return 0 and set `pdata' with the `data' parameter to +// bthread_id_create[_ranged], EBUSY on already locked, error code otherwise. +int bthread_id_trylock(bthread_id_t id, void** pdata); + +// Lock `id' (for using the data exclusively). If `id' is locked +// by others, wait until `id' is unlocked or destroyed. +// On success return 0 and set `pdata' with the `data' parameter to +// bthread_id_create[_ranged], error code otherwise. +#define bthread_id_lock(id, pdata) \ + bthread_id_lock_verbose(id, pdata, __FILE__ ":" BAIDU_SYMBOLSTR(__LINE__)) +int bthread_id_lock_verbose(bthread_id_t id, void** pdata, + const char *location); + +// Lock `id' (for using the data exclusively) and reset the range. If `id' is +// locked by others, wait until `id' is unlocked or destroyed. if `range' is +// smaller than the original range of this id, nothing happens about the range +#define bthread_id_lock_and_reset_range(id, pdata, range) \ + bthread_id_lock_and_reset_range_verbose(id, pdata, range, \ + __FILE__ ":" BAIDU_SYMBOLSTR(__LINE__)) +int bthread_id_lock_and_reset_range_verbose( + bthread_id_t id, void **pdata, + int range, const char *location); + +// Unlock `id'. Must be called after a successful call to bthread_id_trylock() +// or bthread_id_lock(). +// Returns 0 on success, error code otherwise. +int bthread_id_unlock(bthread_id_t id); + +// Unlock and destroy `id'. Waiters blocking on bthread_id_join() or +// bthread_id_lock() will wake up. Must be called after a successful call to +// bthread_id_trylock() or bthread_id_lock(). +// Returns 0 on success, error code otherwise. +int bthread_id_unlock_and_destroy(bthread_id_t id); + +// ************************************************************************** +// bthread_id_list_xxx functions are NOT thread-safe unless explicitly stated + +// Initialize a list for storing bthread_id_t. When an id is destroyed, it will +// be removed from the list automatically. +// The commented parameters are not used anymore and just kept to not break +// compatibility. +int bthread_id_list_init(bthread_id_list_t* list, + unsigned /*size*/, + unsigned /*conflict_size*/); +// Destroy the list. +void bthread_id_list_destroy(bthread_id_list_t* list); + +// Add a bthread_id_t into the list. +int bthread_id_list_add(bthread_id_list_t* list, bthread_id_t id); + +// Swap internal fields of two lists. +void bthread_id_list_swap(bthread_id_list_t* dest, + bthread_id_list_t* src); + +// Issue error_code to all bthread_id_t inside `list' and clear `list'. +// Notice that this function iterates all id inside the list and may call +// on_error() of each valid id in-place, in another word, this thread-unsafe +// function is not suitable to be enclosed within a lock directly. +// To make the critical section small, swap the list inside the lock and +// reset the swapped list outside the lock as follows: +// bthread_id_list_t tmplist; +// bthread_id_list_init(&tmplist, 0, 0); +// LOCK; +// bthread_id_list_swap(&tmplist, &the_list_to_reset); +// UNLOCK; +// bthread_id_list_reset(&tmplist, error_code); +// bthread_id_list_destroy(&tmplist); +int bthread_id_list_reset(bthread_id_list_t* list, int error_code); +// Following 2 functions wrap above process. +int bthread_id_list_reset_pthreadsafe( + bthread_id_list_t* list, int error_code, pthread_mutex_t* mutex); +int bthread_id_list_reset_bthreadsafe( + bthread_id_list_t* list, int error_code, bthread_mutex_t* mutex); + +__END_DECLS + +#if defined(__cplusplus) +// cpp specific API, with an extra `error_text' so that error information +// is more comprehensive +int bthread_id_create2( + bthread_id_t* id, void* data, + int (*on_error)(bthread_id_t id, void* data, int error_code, + const std::string& error_text)); +int bthread_id_create2_ranged( + bthread_id_t* id, void* data, + int (*on_error)(bthread_id_t id, void* data, int error_code, + const std::string& error_text), + int range); +#define bthread_id_error2(id, ec, et) \ + bthread_id_error2_verbose(id, ec, et, __FILE__ ":" BAIDU_SYMBOLSTR(__LINE__)) +int bthread_id_error2_verbose(bthread_id_t id, int error_code, + const std::string& error_text, + const char *location); +int bthread_id_list_reset2(bthread_id_list_t* list, int error_code, + const std::string& error_text); +int bthread_id_list_reset2_pthreadsafe(bthread_id_list_t* list, int error_code, + const std::string& error_text, + pthread_mutex_t* mutex); +int bthread_id_list_reset2_bthreadsafe(bthread_id_list_t* list, int error_code, + const std::string& error_text, + bthread_mutex_t* mutex); +#endif + +#endif // BTHREAD_ID_H diff --git a/src/bthread/interrupt_pthread.cpp b/src/bthread/interrupt_pthread.cpp new file mode 100644 index 0000000..1c7c78e --- /dev/null +++ b/src/bthread/interrupt_pthread.cpp @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Tue Jul 10 17:40:58 CST 2012 + +#include +#include "bthread/interrupt_pthread.h" + +namespace bthread { + +// TODO: Make sure SIGURG is not used by user. +// This empty handler is simply for triggering EINTR in blocking syscalls. +void do_nothing_handler(int) {} + +static pthread_once_t register_sigurg_once = PTHREAD_ONCE_INIT; + +static void register_sigurg() { + signal(SIGURG, do_nothing_handler); +} + +int interrupt_pthread(pthread_t th) { + pthread_once(®ister_sigurg_once, register_sigurg); + return pthread_kill(th, SIGURG); +} + +} // namespace bthread diff --git a/src/bthread/interrupt_pthread.h b/src/bthread/interrupt_pthread.h new file mode 100644 index 0000000..3381cce --- /dev/null +++ b/src/bthread/interrupt_pthread.h @@ -0,0 +1,35 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Tue Jul 10 17:40:58 CST 2012 + +#ifndef BTHREAD_INTERRUPT_PTHREAD_H +#define BTHREAD_INTERRUPT_PTHREAD_H + +#include + +namespace bthread { + +// Make blocking ops in the pthread returns -1 and EINTR. +// Returns what pthread_kill returns. +int interrupt_pthread(pthread_t th); + +} // namespace bthread + +#endif // BTHREAD_INTERRUPT_PTHREAD_H diff --git a/src/bthread/key.cpp b/src/bthread/key.cpp new file mode 100644 index 0000000..7494583 --- /dev/null +++ b/src/bthread/key.cpp @@ -0,0 +1,676 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Sun Aug 3 12:46:15 CST 2014 + +#include +#include + +#include "bthread/bthread.h" // bthread_create_span_fn and related types +#include "bthread/errno.h" // EAGAIN +#include "bthread/task_group.h" // TaskGroup +#include "butil/atomicops.h" +#include "butil/macros.h" +#include "butil/thread_key.h" +#include "butil/thread_local.h" +#include "bvar/passive_status.h" + +// Implement bthread_key_t related functions + +namespace bthread { + +DEFINE_uint32(key_table_list_size, 4000, + "The maximum length of the KeyTableList. Once this value is " + "exceeded, a portion of the KeyTables will be moved to the " + "global free_keytables list."); + +DEFINE_uint32(borrow_from_globle_size, 200, + "The maximum number of KeyTables retrieved in a single operation " + "from the global free_keytables when no KeyTable exists in the " + "current thread's keytable_list."); + +EXTERN_BAIDU_VOLATILE_THREAD_LOCAL(TaskGroup*, tls_task_group); + +class KeyTable; + +// defined in task_group.cpp +extern __thread LocalStorage tls_bls; +static __thread bool tls_ever_created_keytable = false; + +// We keep thread specific data in a two-level array. The top-level array +// contains at most KEY_1STLEVEL_SIZE pointers to dynamically allocated +// arrays of at most KEY_2NDLEVEL_SIZE data pointers. Many applications +// may just occupy one or two second level array, thus this machanism keeps +// memory footprint smaller and we can change KEY_1STLEVEL_SIZE to a +// bigger number more freely. The tradeoff is an additional memory indirection: +// negligible at most time. +static const uint32_t KEY_2NDLEVEL_SIZE = 32; + +// Notice that we're trying to make the memory of second level and first +// level both 256 bytes to make memory allocator happier. +static const uint32_t KEY_1STLEVEL_SIZE = 31; + +// Max tls in one thread, currently the value is 992 which should be enough +// for most projects throughout baidu. +static const uint32_t KEYS_MAX = KEY_2NDLEVEL_SIZE * KEY_1STLEVEL_SIZE; + +// destructors/version of TLS. +struct KeyInfo { + uint32_t version; + void (*dtor)(void*, const void*); + const void* dtor_args; +}; +static KeyInfo s_key_info[KEYS_MAX] = {}; + +// For allocating keys. +static pthread_mutex_t s_key_mutex = PTHREAD_MUTEX_INITIALIZER; +static size_t nfreekey = 0; +static size_t nkey = 0; +static uint32_t s_free_keys[KEYS_MAX]; + +// Stats. +static butil::static_atomic nkeytable = BUTIL_STATIC_ATOMIC_INIT(0); +static butil::static_atomic nsubkeytable = BUTIL_STATIC_ATOMIC_INIT(0); + +// The second-level array. +// Align with cacheline to avoid false sharing. +class BAIDU_CACHELINE_ALIGNMENT SubKeyTable { +public: + SubKeyTable() { + memset(_data, 0, sizeof(_data)); + nsubkeytable.fetch_add(1, butil::memory_order_relaxed); + } + + // NOTE: Call clear first. + ~SubKeyTable() { + nsubkeytable.fetch_sub(1, butil::memory_order_relaxed); + } + + void clear(uint32_t offset) { + for (uint32_t i = 0; i < KEY_2NDLEVEL_SIZE; ++i) { + void* p = _data[i].ptr; + if (p) { + // Set the position to NULL before calling dtor which may set + // the position again. + _data[i].ptr = NULL; + + KeyInfo info = bthread::s_key_info[offset + i]; + if (info.dtor && _data[i].version == info.version) { + info.dtor(p, info.dtor_args); + } + } + } + } + + bool cleared() const { + // We need to iterate again to check if every slot is empty. An + // alternative is remember if set_data() was called during clear. + for (uint32_t i = 0; i < KEY_2NDLEVEL_SIZE; ++i) { + if (_data[i].ptr) { + return false; + } + } + return true; + } + + inline void* get_data(uint32_t index, uint32_t version) const { + if (_data[index].version == version) { + return _data[index].ptr; + } + return NULL; + } + inline void set_data(uint32_t index, uint32_t version, void* data) { + _data[index].version = version; + _data[index].ptr = data; + } + +private: + struct Data { + uint32_t version; + void* ptr; + }; + Data _data[KEY_2NDLEVEL_SIZE]; +}; + +// The first-level array. +// Align with cacheline to avoid false sharing. +class BAIDU_CACHELINE_ALIGNMENT KeyTable { +public: + KeyTable() : next(NULL) { + memset(_subs, 0, sizeof(_subs)); + nkeytable.fetch_add(1, butil::memory_order_relaxed); + } + + ~KeyTable() { + nkeytable.fetch_sub(1, butil::memory_order_relaxed); + for (int ntry = 0; ntry < PTHREAD_DESTRUCTOR_ITERATIONS; ++ntry) { + for (uint32_t i = 0; i < KEY_1STLEVEL_SIZE; ++i) { + if (_subs[i]) { + _subs[i]->clear(i * KEY_2NDLEVEL_SIZE); + } + } + bool all_cleared = true; + for (uint32_t i = 0; i < KEY_1STLEVEL_SIZE; ++i) { + if (_subs[i] != NULL && !_subs[i]->cleared()) { + all_cleared = false; + break; + } + } + if (all_cleared) { + for (uint32_t i = 0; i < KEY_1STLEVEL_SIZE; ++i) { + delete _subs[i]; + } + return; + } + } + LOG(ERROR) << "Fail to destroy all objects in KeyTable[" << this << ']'; + } + + inline void* get_data(bthread_key_t key) const { + const uint32_t subidx = key.index / KEY_2NDLEVEL_SIZE; + if (subidx < KEY_1STLEVEL_SIZE) { + const SubKeyTable* sub_kt = _subs[subidx]; + if (sub_kt) { + return sub_kt->get_data( + key.index - subidx * KEY_2NDLEVEL_SIZE, key.version); + } + } + return NULL; + } + + inline int set_data(bthread_key_t key, void* data) { + const uint32_t subidx = key.index / KEY_2NDLEVEL_SIZE; + if (subidx < KEY_1STLEVEL_SIZE && + key.version == s_key_info[key.index].version) { + SubKeyTable* sub_kt = _subs[subidx]; + if (sub_kt == NULL) { + sub_kt = new (std::nothrow) SubKeyTable; + if (NULL == sub_kt) { + return ENOMEM; + } + _subs[subidx] = sub_kt; + } + sub_kt->set_data(key.index - subidx * KEY_2NDLEVEL_SIZE, + key.version, data); + return 0; + } + CHECK(false) << "bthread_setspecific is called on invalid " << key; + return EINVAL; + } + +public: + KeyTable* next; +private: + SubKeyTable* _subs[KEY_1STLEVEL_SIZE]; +}; + +class BAIDU_CACHELINE_ALIGNMENT KeyTableList { +public: + KeyTableList() : + _head(NULL), _tail(NULL), _length(0) {} + + ~KeyTableList() { + TaskGroup* g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + KeyTable* old_kt = tls_bls.keytable; + KeyTable* keytable = _head; + while (keytable) { + KeyTable* kt = keytable; + keytable = kt->next; + tls_bls.keytable = kt; + if (g) { + g->current_task()->local_storage.keytable = kt; + } + delete kt; + if (old_kt == kt) { + old_kt = NULL; + } + g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + } + tls_bls.keytable = old_kt; + if (g) { + g->current_task()->local_storage.keytable = old_kt; + } + } + + void append(KeyTable* keytable) { + if (keytable == NULL) { + return; + } + if (_head == NULL) { + _head = _tail = keytable; + } else { + _tail->next = keytable; + _tail = keytable; + } + keytable->next = NULL; + _length++; + } + + KeyTable* remove_front() { + if (_head == NULL) { + return NULL; + } + KeyTable* temp = _head; + _head = _head->next; + _length--; + if (_head == NULL) { + _tail = NULL; + } + return temp; + } + + int move_first_n_to_target(KeyTable** target, uint32_t size) { + if (size > _length || _head == NULL) { + return 0; + } + + KeyTable* current = _head; + KeyTable* prev = NULL; + uint32_t count = 0; + while (current != NULL && count < size) { + prev = current; + current = current->next; + count++; + } + if (prev != NULL) { + if (*target == NULL) { + *target = _head; + prev->next = NULL; + } else { + prev->next = *target; + *target = _head; + } + _head = current; + _length -= count; + if (_head == NULL) { + _tail = NULL; + } + } + return count; + } + + inline uint32_t get_length() const { + return _length; + } + + // Only for test + inline bool check_length() { + KeyTable* current = _head; + uint32_t count = 0; + while (current != NULL) { + current = current->next; + count++; + } + return count == _length; + } + +private: + KeyTable* _head; + KeyTable* _tail; + uint32_t _length; +}; + +KeyTable* borrow_keytable(bthread_keytable_pool_t* pool) { + if (pool != NULL && (pool->list || pool->free_keytables)) { + KeyTable* p; + pthread_rwlock_rdlock(&pool->rwlock); + auto list = (butil::ThreadLocal*)pool->list; + if (list) { + p = list->get()->remove_front(); + if (p) { + pthread_rwlock_unlock(&pool->rwlock); + return p; + } + } + pthread_rwlock_unlock(&pool->rwlock); + if (pool->free_keytables) { + pthread_rwlock_wrlock(&pool->rwlock); + p = (KeyTable*)pool->free_keytables; + if (list) { + for (uint32_t i = 0; i < FLAGS_borrow_from_globle_size; ++i) { + if (p) { + pool->free_keytables = p->next; + list->get()->append(p); + p = (KeyTable*)pool->free_keytables; + --pool->size; + } else { + break; + } + } + KeyTable* result = list->get()->remove_front(); + pthread_rwlock_unlock(&pool->rwlock); + return result; + } else { + if (p) { + pool->free_keytables = p->next; + pthread_rwlock_unlock(&pool->rwlock); + return p; + } + } + pthread_rwlock_unlock(&pool->rwlock); + } + } + return NULL; +} + +// Referenced in task_group.cpp, must be extern. +// Caller of this function must hold the KeyTable +void return_keytable(bthread_keytable_pool_t* pool, KeyTable* kt) { + if (NULL == kt) { + return; + } + if (pool == NULL) { + delete kt; + return; + } + pthread_rwlock_rdlock(&pool->rwlock); + if (pool->destroyed) { + pthread_rwlock_unlock(&pool->rwlock); + delete kt; + return; + } + auto list = (butil::ThreadLocal*)pool->list; + list->get()->append(kt); + if (list->get()->get_length() > FLAGS_key_table_list_size) { + pthread_rwlock_unlock(&pool->rwlock); + pthread_rwlock_wrlock(&pool->rwlock); + if (!pool->destroyed) { + int out = list->get()->move_first_n_to_target( + (KeyTable**)(&pool->free_keytables), + FLAGS_key_table_list_size / 2); + pool->size += out; + } + } + pthread_rwlock_unlock(&pool->rwlock); +} + +static void cleanup_pthread(void* arg) { + KeyTable* kt = static_cast(arg); + if (kt) { + delete kt; + // After deletion: tls may be set during deletion. + tls_bls.keytable = NULL; + } +} + +static void arg_as_dtor(void* data, const void* arg) { + typedef void (*KeyDtor)(void*); + return ((KeyDtor)arg)(data); +} + +static int get_key_count(void*) { + BAIDU_SCOPED_LOCK(bthread::s_key_mutex); + return (int)nkey - (int)nfreekey; +} +static size_t get_keytable_count(void*) { + return nkeytable.load(butil::memory_order_relaxed); +} +static size_t get_keytable_memory(void*) { + const size_t n = nkeytable.load(butil::memory_order_relaxed); + const size_t nsub = nsubkeytable.load(butil::memory_order_relaxed); + return n * sizeof(KeyTable) + nsub * sizeof(SubKeyTable); +} + +static bvar::PassiveStatus s_bthread_key_count( + "bthread_key_count", get_key_count, NULL); +static bvar::PassiveStatus s_bthread_keytable_count( + "bthread_keytable_count", get_keytable_count, NULL); +static bvar::PassiveStatus s_bthread_keytable_memory( + "bthread_keytable_memory", get_keytable_memory, NULL); + +} // namespace bthread + +extern "C" { + +int bthread_keytable_pool_init(bthread_keytable_pool_t* pool) { + if (pool == NULL) { + LOG(ERROR) << "Param[pool] is NULL"; + return EINVAL; + } + pthread_rwlock_init(&pool->rwlock, NULL); + pool->list = new butil::ThreadLocal(); + pool->free_keytables = NULL; + pool->size = 0; + pool->destroyed = 0; + return 0; +} + +int bthread_keytable_pool_destroy(bthread_keytable_pool_t* pool) { + if (pool == NULL) { + LOG(ERROR) << "Param[pool] is NULL"; + return EINVAL; + } + bthread::KeyTable* saved_free_keytables = NULL; + pthread_rwlock_wrlock(&pool->rwlock); + pool->destroyed = 1; + pool->size = 0; + delete (butil::ThreadLocal*)pool->list; + saved_free_keytables = (bthread::KeyTable*)pool->free_keytables; + pool->list = NULL; + pool->free_keytables = NULL; + pthread_rwlock_unlock(&pool->rwlock); + + // Cheat get/setspecific and destroy the keytables. + bthread::TaskGroup* g = + bthread::BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + bthread::KeyTable* old_kt = bthread::tls_bls.keytable; + while (saved_free_keytables) { + bthread::KeyTable* kt = saved_free_keytables; + saved_free_keytables = kt->next; + bthread::tls_bls.keytable = kt; + if (g) { + g->current_task()->local_storage.keytable = kt; + } + delete kt; + g = bthread::BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + } + bthread::tls_bls.keytable = old_kt; + if (g) { + g->current_task()->local_storage.keytable = old_kt; + } + // TODO: return_keytable may race with this function, we don't destroy + // the mutex right now. + // pthread_mutex_destroy(&pool->mutex); + return 0; +} + +int bthread_keytable_pool_getstat(bthread_keytable_pool_t* pool, + bthread_keytable_pool_stat_t* stat) { + if (pool == NULL || stat == NULL) { + LOG(ERROR) << "Param[pool] or Param[stat] is NULL"; + return EINVAL; + } + pthread_rwlock_wrlock(&pool->rwlock); + stat->nfree = pool->size; + pthread_rwlock_unlock(&pool->rwlock); + return 0; +} + +int get_thread_local_keytable_list_length(bthread_keytable_pool_t* pool) { + if (pool == NULL) { + LOG(ERROR) << "Param[pool] is NULL"; + return EINVAL; + } + int length = 0; + pthread_rwlock_rdlock(&pool->rwlock); + if (pool->destroyed) { + pthread_rwlock_unlock(&pool->rwlock); + return length; + } + auto list = (butil::ThreadLocal*)pool->list; + if (list) { + length = (int)(list->get()->get_length()); + if (!list->get()->check_length()) { + LOG(ERROR) << "Length is not equal"; + } + } + pthread_rwlock_unlock(&pool->rwlock); + return length; +} + +// TODO: this is not strict `reserve' because we only check #free. +// Currently there's no way to track KeyTables that may be returned +// to the pool in future. +void bthread_keytable_pool_reserve(bthread_keytable_pool_t* pool, + size_t nfree, + bthread_key_t key, + void* ctor(const void*), + const void* ctor_args) { + if (pool == NULL) { + LOG(ERROR) << "Param[pool] is NULL"; + return; + } + bthread_keytable_pool_stat_t stat; + if (bthread_keytable_pool_getstat(pool, &stat) != 0) { + LOG(ERROR) << "Fail to getstat of pool=" << pool; + return; + } + for (size_t i = stat.nfree; i < nfree; ++i) { + bthread::KeyTable* kt = new (std::nothrow) bthread::KeyTable; + if (kt == NULL) { + break; + } + void* data = ctor(ctor_args); + if (data) { + kt->set_data(key, data); + } // else append kt w/o data. + + pthread_rwlock_wrlock(&pool->rwlock); + if (pool->destroyed) { + pthread_rwlock_unlock(&pool->rwlock); + delete kt; + break; + } + kt->next = (bthread::KeyTable*)pool->free_keytables; + pool->free_keytables = kt; + ++pool->size; + pthread_rwlock_unlock(&pool->rwlock); + if (data == NULL) { + break; + } + } +} + +int bthread_key_create2(bthread_key_t* key, + void (*dtor)(void*, const void*), + const void* dtor_args) { + uint32_t index = 0; + { + BAIDU_SCOPED_LOCK(bthread::s_key_mutex); + if (bthread::nfreekey > 0) { + index = bthread::s_free_keys[--bthread::nfreekey]; + } else if (bthread::nkey < bthread::KEYS_MAX) { + index = bthread::nkey++; + } else { + return EAGAIN; // what pthread_key_create returns in this case. + } + } + bthread::s_key_info[index].dtor = dtor; + bthread::s_key_info[index].dtor_args = dtor_args; + key->index = index; + key->version = bthread::s_key_info[index].version; + if (key->version == 0) { + ++bthread::s_key_info[index].version; + ++key->version; + } + return 0; +} + +int bthread_key_create(bthread_key_t* key, void (*dtor)(void*)) { + if (dtor == NULL) { + return bthread_key_create2(key, NULL, NULL); + } else { + return bthread_key_create2(key, bthread::arg_as_dtor, (const void*)dtor); + } +} + +int bthread_key_delete(bthread_key_t key) { + if (key.index < bthread::KEYS_MAX && + key.version == bthread::s_key_info[key.index].version) { + BAIDU_SCOPED_LOCK(bthread::s_key_mutex); + if (key.version == bthread::s_key_info[key.index].version) { + if (++bthread::s_key_info[key.index].version == 0) { + ++bthread::s_key_info[key.index].version; + } + bthread::s_key_info[key.index].dtor = NULL; + bthread::s_key_info[key.index].dtor_args = NULL; + bthread::s_free_keys[bthread::nfreekey++] = key.index; + return 0; + } + } + CHECK(false) << "bthread_key_delete is called on invalid " << key; + return EINVAL; +} + +// NOTE: Can't borrow_keytable in bthread_setspecific, otherwise following +// memory leak may occur: +// -> bthread_getspecific fails to borrow_keytable and returns NULL. +// -> bthread_setspecific succeeds to borrow_keytable and overwrites old data +// at the position with newly created data, the old data is leaked. +int bthread_setspecific(bthread_key_t key, void* data) { + bthread::KeyTable* kt = bthread::tls_bls.keytable; + if (NULL == kt) { + kt = new (std::nothrow) bthread::KeyTable; + if (NULL == kt) { + return ENOMEM; + } + bthread::tls_bls.keytable = kt; + bthread::TaskGroup* const g = bthread::BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + if (g) { + g->current_task()->local_storage.keytable = kt; + } else { + // Only cleanup keytable created by pthread. + // keytable created by bthread will be deleted + // in `return_keytable' or `bthread_keytable_pool_destroy'. + if (!bthread::tls_ever_created_keytable) { + bthread::tls_ever_created_keytable = true; + CHECK_EQ(0, butil::thread_atexit(bthread::cleanup_pthread, kt)); + } + } + } + return kt->set_data(key, data); +} + +void* bthread_getspecific(bthread_key_t key) { + bthread::KeyTable* kt = bthread::tls_bls.keytable; + if (kt) { + return kt->get_data(key); + } + bthread::TaskGroup* const g = bthread::BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + if (g) { + bthread::TaskMeta* const task = g->current_task(); + kt = bthread::borrow_keytable(task->attr.keytable_pool); + if (kt) { + g->current_task()->local_storage.keytable = kt; + bthread::tls_bls.keytable = kt; + return kt->get_data(key); + } + } + return NULL; +} + +void bthread_assign_data(void* data) { + bthread::tls_bls.assigned_data = data; +} + +void* bthread_get_assigned_data() { + return bthread::tls_bls.assigned_data; +} + +} // extern "C" diff --git a/src/bthread/list_of_abafree_id.h b/src/bthread/list_of_abafree_id.h new file mode 100644 index 0000000..16de5b2 --- /dev/null +++ b/src/bthread/list_of_abafree_id.h @@ -0,0 +1,350 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Mon Jun 20 11:57:23 CST 2016 + +#ifndef BTHREAD_LIST_OF_ABAFREE_ID_H +#define BTHREAD_LIST_OF_ABAFREE_ID_H + +#include +#include +#include +#include "butil/macros.h" + +namespace bthread { + +// A container for storing identifiers that may be invalidated. + +// [Basic Idea] +// identifiers are remembered for error notifications. While insertions are +// easy, removals are hard to be done in O(1) time. More importantly, +// insertions are often done in one thread, while removals come from many +// threads simultaneously. Think about the usage in brpc::Socket, most +// bthread_id_t are inserted by one thread (the thread calling non-contended +// Write or the KeepWrite thread), but removals are from many threads +// processing responses simultaneously. + +// [The approach] +// Don't remove old identifiers eagerly, replace them when new ones are inserted. + +// IdTraits MUST have { +// // #identifiers in each block +// static const size_t BLOCK_SIZE = 63; +// +// // Max #entries. Often has close relationship with concurrency, 65536 +// // is "huge" for most apps. +// static const size_t MAX_ENTRIES = 65536; +// // Initial GC length, when the number of blocks reaches this length, +// // start to initiate list GC operation. It will release useless blocks +// static const size_t INIT_GC_SIZE = 4096; +// +// // Initial value of id. Id with the value is treated as invalid. +// static const Id ID_INIT = ...; +// +// // Returns true if the id is valid. The "validness" must be permanent or +// // stable for a very long period (to make the id ABA-free). +// static bool exists(Id id); +// } + +// This container is NOT thread-safe right now, and shouldn't be +// an issue in current usages throughout brpc. +template +class ListOfABAFreeId { +public: + ListOfABAFreeId(); + ~ListOfABAFreeId(); + + // Add an identifier into the list. + int add(Id id); + + // Apply fn(id) to all identifiers. + template + void apply(const Fn& fn); + + // Put #entries of each level into `counts' + // Returns #levels. + size_t get_sizes(size_t* counts, size_t n); + +private: + DISALLOW_COPY_AND_ASSIGN(ListOfABAFreeId); + struct IdBlock { + Id ids[IdTraits::BLOCK_SIZE]; + IdBlock* next; + }; + void forward_index(); + + struct TempIdBlock { + IdBlock* block; + uint32_t index; + uint32_t nblock; + }; + + int gc(); + int add_to_temp_list(TempIdBlock* temp_list, Id id); + template + int for_each(const Fn& fn); + void free_list(IdBlock* block); + + IdBlock* _cur_block; + uint32_t _cur_index; + uint32_t _nblock; + IdBlock _head_block; + uint32_t _next_gc_size; +}; + +// [impl.] + +template +ListOfABAFreeId::ListOfABAFreeId() + : _cur_block(&_head_block), _cur_index(0), _nblock(1), _next_gc_size(IdTraits::INIT_GC_SIZE) { + for (size_t i = 0; i < IdTraits::BLOCK_SIZE; ++i) { + _head_block.ids[i] = IdTraits::ID_INIT; + } + _head_block.next = NULL; +} + +template +ListOfABAFreeId::~ListOfABAFreeId() { + _cur_block = NULL; + _cur_index = 0; + _nblock = 0; + for (IdBlock* p = _head_block.next; p != NULL;) { + IdBlock* saved_next = p->next; + delete p; + p = saved_next; + } + _head_block.next = NULL; +} + +template +inline void ListOfABAFreeId::forward_index() { + if (++_cur_index >= IdTraits::BLOCK_SIZE) { + _cur_index = 0; + if (_cur_block->next) { + _cur_block = _cur_block->next; + } else { + _cur_block = &_head_block; + } + } +} + +template +int ListOfABAFreeId::add(Id id) { + // Scan for at most 4 positions, if any of them is empty, use the position. + Id* saved_pos[4]; + for (size_t i = 0; i < arraysize(saved_pos); ++i) { + Id* const pos = _cur_block->ids + _cur_index; + forward_index(); + // The position is not used. + if (*pos == IdTraits::ID_INIT || !IdTraits::exists(*pos)) { + *pos = id; + return 0; + } + saved_pos[i] = pos; + } + // If we don't expect a GC to occur in abalist, then an error is reported and EAGAIN is returned. + if (_nblock * IdTraits::BLOCK_SIZE > IdTraits::MAX_ENTRIES) { + return EAGAIN; + } + // If the number of blocks exceeds the minimum GC length, start the GC operation + if (_nblock * IdTraits::BLOCK_SIZE > _next_gc_size) { + uint32_t before_gc_blocks = _nblock; + int rc = gc(); + // To avoid frequent GC operations, we only let the GC be effective enough to continue the GC. + // otherwise we let the next GC occur length * 2. + // + // Condition for a GC to be sufficiently efficient: the number of blocks + // retained after the GC is 1/4 of the previous one. + if ((before_gc_blocks - _nblock) * IdTraits::BLOCK_SIZE < (_next_gc_size - (_next_gc_size >> 2))) { + _next_gc_size <<= 1; + // We want to make sure that GC must occur before MAX_ENTRIES. + static_assert(IdTraits::MAX_ENTRIES > IdTraits::BLOCK_SIZE * 2, "MAX_ENTRIES should be greater than 2 * IdTraits::BLOCK_SIZE"); + if (_next_gc_size >= IdTraits::MAX_ENTRIES) { + _next_gc_size = IdTraits::MAX_ENTRIES - IdTraits::BLOCK_SIZE * 2; + } + } + + return rc; + } + // The list is considered to be "crowded", add a new block and scatter + // the conflict identifiers by inserting an empty entry after each of + // them, so that even if the identifiers are still valid when we walk + // through the area again, we can find an empty entry. + + // The new block is inserted as if it's inserted between xxxx and yyyy, + // where xxxx are the 4 conflict identifiers. + // [..xxxxyyyy] -> [..........] + // block A block B + // + // [..xxxx....] -> [......yyyy] -> [..........] + // block A new block block B + IdBlock* new_block = new (std::nothrow) IdBlock; + if (NULL == new_block) { + return ENOMEM; + } + ++_nblock; + for (size_t i = 0; i < _cur_index; ++i) { + new_block->ids[i] = IdTraits::ID_INIT; + } + for (size_t i = _cur_index; i < IdTraits::BLOCK_SIZE; ++i) { + new_block->ids[i] = _cur_block->ids[i]; + _cur_block->ids[i] = IdTraits::ID_INIT; + } + new_block->next = _cur_block->next; + _cur_block->next = new_block; + // Scatter the conflict identifiers. + // [..xxxx....] -> [......yyyy] -> [..........] + // block A new block block B + // + // [..x.x.x.x.] -> [......yyyy] -> [..........] + // block A new block block B + _cur_block->ids[_cur_index] = *saved_pos[2]; + *saved_pos[2] = *saved_pos[1]; + *saved_pos[1] = IdTraits::ID_INIT; + forward_index(); + forward_index(); + _cur_block->ids[_cur_index] = *saved_pos[3]; + *saved_pos[3] = IdTraits::ID_INIT; + forward_index(); + _cur_block->ids[_cur_index] = id; + forward_index(); + return 0; +} + +template +int ListOfABAFreeId::gc() { + IdBlock* new_block = new (std::nothrow) IdBlock; + if (NULL == new_block) { + return ENOMEM; + } + // reset head block + for (size_t i = 0; i < IdTraits::BLOCK_SIZE; ++i) { + new_block->ids[i] = IdTraits::ID_INIT; + } + new_block->next = NULL; + + TempIdBlock tmp_id_block; + tmp_id_block.block = new_block; + tmp_id_block.nblock = 1; + tmp_id_block.index = 0; + + // Add each element of the old list to the new list + int rc = for_each([&](Id id) { + int rc; + rc = add_to_temp_list(&tmp_id_block, id); + if (rc != 0) { + return rc; + } + rc = add_to_temp_list(&tmp_id_block, IdTraits::ID_INIT); + if (rc != 0) { + return rc; + } + return 0; + }); + + if (rc != 0) { + free_list(new_block); + return rc; + } + + // reset head block + for (size_t i = 0; i < IdTraits::BLOCK_SIZE; ++i) { + _head_block.ids[i] = IdTraits::ID_INIT; + } + + free_list(_head_block.next); + _cur_block = tmp_id_block.block; + _cur_index = tmp_id_block.index; + // nblock and head_block + _nblock = tmp_id_block.nblock + 1; + _head_block.next = new_block; + + return 0; +} + +template +int ListOfABAFreeId::add_to_temp_list(TempIdBlock* block_list, Id id) { + block_list->block->ids[block_list->index++] = id; + // add new list + if (block_list->index == IdTraits::BLOCK_SIZE) { + block_list->index = 0; + block_list->nblock++; + block_list->block->next = new (std::nothrow) IdBlock; + if (NULL == block_list->block->next) { + return ENOMEM; + } + block_list->block = block_list->block->next; + for (size_t i = 0; i < IdTraits::BLOCK_SIZE; ++i) { + block_list->block->ids[i] = IdTraits::ID_INIT; + } + block_list->block->next = NULL; + } + return 0; +} + +template +template +int ListOfABAFreeId::for_each(const Fn& fn) { + for (IdBlock* p = &_head_block; p != NULL; p = p->next) { + for (size_t i = 0; i < IdTraits::BLOCK_SIZE; ++i) { + if (p->ids[i] != IdTraits::ID_INIT && IdTraits::exists(p->ids[i])) { + int rc = fn(p->ids[i]); + if (rc != 0) { + return rc; + } + } + } + } + return 0; +} + +template +template +void ListOfABAFreeId::apply(const Fn& fn) { + for (IdBlock* p = &_head_block; p != NULL; p = p->next) { + for (size_t i = 0; i < IdTraits::BLOCK_SIZE; ++i) { + if (p->ids[i] != IdTraits::ID_INIT && IdTraits::exists(p->ids[i])) { + fn(p->ids[i]); + } + } + } +} + +template +void ListOfABAFreeId::free_list(IdBlock* p) { + for (; p != NULL;) { + IdBlock* saved_next = p->next; + delete p; + p = saved_next; + } +} + +template +size_t ListOfABAFreeId::get_sizes(size_t* cnts, size_t n) { + if (n == 0) { + return 0; + } + // Current impl. only has one level. + cnts[0] = _nblock * IdTraits::BLOCK_SIZE; + return 1; +} + +} // namespace bthread + +#endif // BTHREAD_LIST_OF_ABAFREE_ID_H diff --git a/src/bthread/log.h b/src/bthread/log.h new file mode 100644 index 0000000..3aa1c2c --- /dev/null +++ b/src/bthread/log.h @@ -0,0 +1,31 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Mon Sep 15 10:51:15 CST 2014 + +#ifndef BTHREAD_LOG_H +#define BTHREAD_LOG_H + +#ifdef BAIDU_INTERNAL +#include "bthread/comlog_initializer.h" +#endif + +#define BT_VLOG VLOG(100) + +#endif // BTHREAD_LOG_H diff --git a/src/bthread/mutex.cpp b/src/bthread/mutex.cpp new file mode 100644 index 0000000..dff2da3 --- /dev/null +++ b/src/bthread/mutex.cpp @@ -0,0 +1,1330 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Sun Aug 3 12:46:15 CST 2014 + +#include +#include +#include // dlsym +#include // O_RDONLY +#include "butil/atomicops.h" +#include "bvar/bvar.h" +#include "bvar/collector.h" +#include "butil/macros.h" // BAIDU_CASSERT +#include "butil/containers/flat_map.h" +#include "butil/iobuf.h" +#include "butil/fd_guard.h" +#include "butil/files/file.h" +#include "butil/files/file_path.h" +#include "butil/file_util.h" +#include "butil/unique_ptr.h" +#include "butil/memory/scope_guard.h" +#include "butil/third_party/murmurhash3/murmurhash3.h" +#include "butil/third_party/symbolize/symbolize.h" +#include "butil/logging.h" +#include "butil/object_pool.h" +#include "butil/debug/stack_trace.h" +#include "butil/thread_local.h" +#include "bthread/butex.h" // butex_* +#include "bthread/mutex.h" // bthread_mutex_t +#include "bthread/sys_futex.h" +#include "bthread/log.h" +#include "bthread/processor.h" +#include "bthread/task_group.h" + +__BEGIN_DECLS +extern void* BAIDU_WEAK _dl_sym(void* handle, const char* symbol, void* caller); +__END_DECLS + +namespace bthread { + +EXTERN_BAIDU_VOLATILE_THREAD_LOCAL(TaskGroup*, tls_task_group); + +// Warm up backtrace before main(). +const butil::debug::StackTrace ALLOW_UNUSED dummy_bt; + +// For controlling contentions collected per second. +bvar::CollectorSpeedLimit g_cp_sl = BVAR_COLLECTOR_SPEED_LIMIT_INITIALIZER; + +const size_t MAX_CACHED_CONTENTIONS = 512; +// Skip frames which are always same: the unlock function and submit_contention() +const int SKIPPED_STACK_FRAMES = 2; + +struct SampledContention : public bvar::Collected { + // time taken by lock and unlock, normalized according to sampling_range + int64_t duration_ns; + // number of samples, normalized according to to sampling_range + double count; + void* stack[26]; // backtrace. + int nframes; // #elements in stack + + // Implement bvar::Collected + void dump_and_destroy(size_t round) override; + void destroy() override; + bvar::CollectorSpeedLimit* speed_limit() override { return &g_cp_sl; } + + size_t hash_code() const { + if (nframes == 0) { + return 0; + } + if (_hash_code == 0) { + _hash_code = 1; + uint32_t seed = nframes; + butil::MurmurHash3_x86_32(stack, sizeof(void*) * nframes, seed, &_hash_code); + } + return _hash_code; + } +private: +friend butil::ObjectPool; + SampledContention() + : duration_ns(0), count(0), stack{NULL}, nframes(0), _hash_code(0) {} + ~SampledContention() override = default; + + mutable uint32_t _hash_code; // For combining samples with hashmap. +}; + +BAIDU_CASSERT(sizeof(SampledContention) == 256, be_friendly_to_allocator); + +// Functor to compare contentions. +struct ContentionEqual { + bool operator()(const SampledContention* c1, + const SampledContention* c2) const { + return c1->hash_code() == c2->hash_code() && + c1->nframes == c2->nframes && + memcmp(c1->stack, c2->stack, sizeof(void*) * c1->nframes) == 0; + } +}; + +// Functor to hash contentions. +struct ContentionHash { + size_t operator()(const SampledContention* c) const { + return c->hash_code(); + } +}; + +// The global context for contention profiler. +class ContentionProfiler { +public: + typedef butil::FlatMap ContentionMap; + + explicit ContentionProfiler(const char* name); + ~ContentionProfiler(); + + void dump_and_destroy(SampledContention* c); + + // Write buffered data into resulting file. If `ending' is true, append + // content of /proc/self/maps and retry writing until buffer is empty. + void flush_to_disk(bool ending); + + void init_if_needed(); +private: + bool _init; // false before first dump_and_destroy is called + bool _first_write; // true if buffer was not written to file yet. + std::string _filename; // the file storing profiling result. + butil::IOBuf _disk_buf; // temp buf before saving the file. + ContentionMap _dedup_map; // combining same samples to make result smaller. +}; + +ContentionProfiler::ContentionProfiler(const char* name) + : _init(false) + , _first_write(true) + , _filename(name) { +} + +ContentionProfiler::~ContentionProfiler() { + if (!_init) { + // Don't write file if dump_and_destroy was never called. We may create + // such instances in ContentionProfilerStart. + return; + } + flush_to_disk(true); +} + +void ContentionProfiler::init_if_needed() { + if (!_init) { + // Already output nanoseconds, always set cycles/second to 1000000000. + _disk_buf.append("--- contention\ncycles/second=1000000000\n"); + if (_dedup_map.init(1024, 60) != 0) { + LOG(WARNING) << "Fail to initialize dedup_map"; + } + _init = true; + } +} + +void ContentionProfiler::dump_and_destroy(SampledContention* c) { + init_if_needed(); + // Categorize the contention. + SampledContention** p_c2 = _dedup_map.seek(c); + if (p_c2) { + // Most contentions are caused by several hotspots, this should be + // the common branch. + SampledContention* c2 = *p_c2; + c2->duration_ns += c->duration_ns; + c2->count += c->count; + c->destroy(); + } else { + _dedup_map.insert(c, c); + } + if (_dedup_map.size() > MAX_CACHED_CONTENTIONS) { + flush_to_disk(false); + } +} + +void ContentionProfiler::flush_to_disk(bool ending) { + BT_VLOG << "flush_to_disk(ending=" << ending << ")"; + + // Serialize contentions in _dedup_map into _disk_buf. + if (!_dedup_map.empty()) { + BT_VLOG << "dedup_map=" << _dedup_map.size(); + butil::IOBufBuilder os; + for (ContentionMap::const_iterator + it = _dedup_map.begin(); it != _dedup_map.end(); ++it) { + SampledContention* c = it->second; + os << c->duration_ns << ' ' << (size_t)ceil(c->count) << " @"; + for (int i = SKIPPED_STACK_FRAMES; i < c->nframes; ++i) { + os << ' ' << (void*)c->stack[i]; + } + os << '\n'; + c->destroy(); + } + _dedup_map.clear(); + _disk_buf.append(os.buf()); + } + + // Append /proc/self/maps to the end of the contention file, required by + // pprof.pl, otherwise the functions in sys libs are not interpreted. + if (ending) { + BT_VLOG << "Append /proc/self/maps"; + // Failures are not critical, don't return directly. + butil::IOPortal mem_maps; + const butil::fd_guard fd(open("/proc/self/maps", O_RDONLY)); + if (fd >= 0) { + while (true) { + ssize_t nr = mem_maps.append_from_file_descriptor(fd, 8192); + if (nr < 0) { + if (errno == EINTR) { + continue; + } + PLOG(ERROR) << "Fail to read /proc/self/maps"; + break; + } + if (nr == 0) { + _disk_buf.append(mem_maps); + break; + } + } + } else { + PLOG(ERROR) << "Fail to open /proc/self/maps"; + } + } + // Write _disk_buf into _filename + butil::File::Error error; + butil::FilePath path(_filename); + butil::FilePath dir = path.DirName(); + if (!butil::CreateDirectoryAndGetError(dir, &error)) { + LOG(ERROR) << "Fail to create directory=`" << dir.value() + << "', " << error; + return; + } + // Truncate on first write, append on later writes. + int flag = O_APPEND; + if (_first_write) { + _first_write = false; + flag = O_TRUNC; + } + butil::fd_guard fd(open(_filename.c_str(), O_WRONLY|O_CREAT|flag, 0666)); + if (fd < 0) { + PLOG(ERROR) << "Fail to open " << _filename; + return; + } + // Write once normally, write until empty in the end. + do { + ssize_t nw = _disk_buf.cut_into_file_descriptor(fd); + if (nw < 0) { + if (errno == EINTR) { + continue; + } + PLOG(ERROR) << "Fail to write into " << _filename; + return; + } + BT_VLOG << "Write " << nw << " bytes into " << _filename; + } while (!_disk_buf.empty() && ending); +} + +// If contention profiler is on, this variable will be set with a valid +// instance. NULL otherwise. +BAIDU_CACHELINE_ALIGNMENT ContentionProfiler* g_cp = NULL; +// Need this version to solve an issue that non-empty entries left by +// previous contention profilers should be detected and overwritten. +static uint64_t g_cp_version = 0; +// Protecting accesses to g_cp. +static pthread_mutex_t g_cp_mutex = PTHREAD_MUTEX_INITIALIZER; + +// The map storing information for profiling pthread_mutex. Different from +// bthread_mutex, we can't save stuff into pthread_mutex, we neither can +// save the info in TLS reliably, since a mutex can be unlocked in a different +// thread from the one locked (although rare, undefined behavior) +// This map must be very fast, since it's accessed inside the lock. +// Layout of the map: +// * Align each entry by cacheline so that different threads do not collide. +// * Hash the mutex into the map by its address. If the entry is occupied, +// cancel sampling. +// The canceling rate should be small provided that programs are unlikely to +// lock a lot of mutexes simultaneously. +const size_t MUTEX_MAP_SIZE = 1024; +BAIDU_CASSERT((MUTEX_MAP_SIZE & (MUTEX_MAP_SIZE - 1)) == 0, must_be_power_of_2); +struct BAIDU_CACHELINE_ALIGNMENT MutexMapEntry { + butil::static_atomic versioned_mutex; + bthread_contention_site_t csite; +}; +static MutexMapEntry g_mutex_map[MUTEX_MAP_SIZE] = {}; // zero-initialize + +void SampledContention::dump_and_destroy(size_t /*round*/) { + if (g_cp) { + // Must be protected with mutex to avoid race with deletion of ctx. + // dump_and_destroy is called from dumping thread only so this mutex + // is not contended at most of time. + BAIDU_SCOPED_LOCK(g_cp_mutex); + if (g_cp) { + g_cp->dump_and_destroy(this); + return; + } + } + destroy(); +} + +void SampledContention::destroy() { + _hash_code = 0; + butil::return_object(this); +} + +// Remember the conflict hashes for troubleshooting, should be 0 at most of time. +static butil::static_atomic g_nconflicthash = BUTIL_STATIC_ATOMIC_INIT(0); +static int64_t get_nconflicthash(void*) { + return g_nconflicthash.load(butil::memory_order_relaxed); +} + +// Start profiling contention. +bool ContentionProfilerStart(const char* filename) { + if (filename == NULL) { + LOG(ERROR) << "Parameter [filename] is NULL"; + return false; + } + // g_cp is also the flag marking start/stop. + if (g_cp) { + return false; + } + + // Create related global bvar lazily. + static bvar::PassiveStatus g_nconflicthash_var + ("contention_profiler_conflict_hash", get_nconflicthash, NULL); + static bvar::DisplaySamplingRatio g_sampling_ratio_var( + "contention_profiler_sampling_ratio", &g_cp_sl); + + // Optimistic locking. A not-used ContentionProfiler does not write file. + std::unique_ptr ctx(new ContentionProfiler(filename)); + { + BAIDU_SCOPED_LOCK(g_cp_mutex); + if (g_cp) { + return false; + } + g_cp = ctx.release(); + ++g_cp_version; // invalidate non-empty entries that may exist. + } + return true; +} + +// Stop contention profiler. +void ContentionProfilerStop() { + ContentionProfiler* ctx = NULL; + if (g_cp) { + std::unique_lock mu(g_cp_mutex); + if (g_cp) { + ctx = g_cp; + g_cp = NULL; + mu.unlock(); + + // make sure it's initialiazed in case no sample was gathered, + // otherwise nothing will be written and succeeding pprof will fail. + ctx->init_if_needed(); + // Deletion is safe because usages of g_cp are inside g_cp_mutex. + delete ctx; + return; + } + } + LOG(ERROR) << "Contention profiler is not started!"; +} + +bool is_contention_site_valid(const bthread_contention_site_t& cs) { + return bvar::is_sampling_range_valid(cs.sampling_range); +} + +void make_contention_site_invalid(bthread_contention_site_t* cs) { + cs->sampling_range = 0; +} + +#ifndef NO_PTHREAD_MUTEX_HOOK +// Replace pthread_mutex_lock and pthread_mutex_unlock: +// First call to sys_pthread_mutex_lock sets sys_pthread_mutex_lock to the +// real function so that next calls go to the real function directly. This +// technique avoids calling pthread_once each time. +typedef int (*MutexInitOp)(pthread_mutex_t*, const pthread_mutexattr_t*); +typedef int (*MutexOp)(pthread_mutex_t*); +int first_sys_pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* mutexattr); +int first_sys_pthread_mutex_destroy(pthread_mutex_t* mutex); +int first_sys_pthread_mutex_lock(pthread_mutex_t* mutex); +int first_sys_pthread_mutex_trylock(pthread_mutex_t* mutex); +int first_sys_pthread_mutex_unlock(pthread_mutex_t* mutex); +static MutexInitOp sys_pthread_mutex_init = first_sys_pthread_mutex_init; +static MutexOp sys_pthread_mutex_destroy = first_sys_pthread_mutex_destroy; +static MutexOp sys_pthread_mutex_lock = first_sys_pthread_mutex_lock; +static MutexOp sys_pthread_mutex_trylock = first_sys_pthread_mutex_trylock; +static MutexOp sys_pthread_mutex_unlock = first_sys_pthread_mutex_unlock; +#if HAS_PTHREAD_MUTEX_TIMEDLOCK +typedef int (*TimedMutexOp)(pthread_mutex_t*, const struct timespec*); +int first_sys_pthread_mutex_timedlock(pthread_mutex_t* mutex, + const struct timespec* __abstime); +static TimedMutexOp sys_pthread_mutex_timedlock = first_sys_pthread_mutex_timedlock; +#endif // HAS_PTHREAD_MUTEX_TIMEDLOCK + +static pthread_once_t init_sys_mutex_lock_once = PTHREAD_ONCE_INIT; + +// dlsym may call malloc to allocate space for dlerror and causes contention +// profiler to deadlock at boostraping when the program is linked with +// libunwind. The deadlock bt: +// #0 0x00007effddc99b80 in __nanosleep_nocancel () at ../sysdeps/unix/syscall-template.S:81 +// #1 0x00000000004b4df7 in butil::internal::SpinLockDelay(int volatile*, int, int) () +// #2 0x00000000004b4d57 in SpinLock::SlowLock() () +// #3 0x00000000004b4a63 in tcmalloc::ThreadCache::InitModule() () +// #4 0x00000000004aa2b5 in tcmalloc::ThreadCache::GetCache() () +// #5 0x000000000040c6c5 in (anonymous namespace)::do_malloc_no_errno(unsigned long) [clone.part.16] () +// #6 0x00000000006fc125 in tc_calloc () +// #7 0x00007effdd245690 in _dlerror_run (operate=operate@entry=0x7effdd245130 , args=args@entry=0x7fff483dedf0) at dlerror.c:141 +// #8 0x00007effdd245198 in __dlsym (handle=, name=) at dlsym.c:70 +// #9 0x0000000000666517 in bthread::init_sys_mutex_lock () at bthread/mutex.cpp:358 +// #10 0x00007effddc97a90 in pthread_once () at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_once.S:103 +// #11 0x000000000066649f in bthread::first_sys_pthread_mutex_lock (mutex=0xbaf880 <_ULx86_64_lock>) at bthread/mutex.cpp:366 +// #12 0x00000000006678bc in pthread_mutex_lock_impl (mutex=0xbaf880 <_ULx86_64_lock>) at bthread/mutex.cpp:489 +// #13 pthread_mutex_lock (__mutex=__mutex@entry=0xbaf880 <_ULx86_64_lock>) at bthread/mutex.cpp:751 +// #14 0x00000000004c6ea1 in _ULx86_64_init () at x86_64/Gglobal.c:83 +// #15 0x00000000004c44fb in _ULx86_64_init_local (cursor=0x7fff483df340, uc=0x7fff483def90) at x86_64/Ginit_local.c:47 +// #16 0x00000000004b5012 in GetStackTrace(void**, int, int) () +// #17 0x00000000004b2095 in tcmalloc::PageHeap::GrowHeap(unsigned long) () +// #18 0x00000000004b23a3 in tcmalloc::PageHeap::New(unsigned long) () +// #19 0x00000000004ad457 in tcmalloc::CentralFreeList::Populate() () +// #20 0x00000000004ad628 in tcmalloc::CentralFreeList::FetchFromSpansSafe() () +// #21 0x00000000004ad6a3 in tcmalloc::CentralFreeList::RemoveRange(void**, void**, int) () +// #22 0x00000000004b3ed3 in tcmalloc::ThreadCache::FetchFromCentralCache(unsigned long, unsigned long) () +// #23 0x00000000006fbb9a in tc_malloc () +// Call _dl_sym which is a private function in glibc to workaround the malloc +// causing deadlock temporarily. This fix is hardly portable. + +static void init_sys_mutex_lock() { +// When bRPC library is linked as a shared library, need to make sure bRPC +// shared library is loaded before the pthread shared library. Otherwise, +// it may cause runtime error: undefined symbol: pthread_mutex_xxx. +// Alternatively, static linking can also avoid this problem. +#if defined(OS_LINUX) + // TODO: may need dlvsym when GLIBC has multiple versions of a same symbol. + // http://blog.fesnel.com/blog/2009/08/25/preloading-with-multiple-symbol-versions + if (_dl_sym) { + sys_pthread_mutex_init = (MutexInitOp)_dl_sym( + RTLD_NEXT, "pthread_mutex_init", (void*)init_sys_mutex_lock); + sys_pthread_mutex_destroy = (MutexOp)_dl_sym( + RTLD_NEXT, "pthread_mutex_destroy", (void*)init_sys_mutex_lock); + sys_pthread_mutex_lock = (MutexOp)_dl_sym( + RTLD_NEXT, "pthread_mutex_lock", (void*)init_sys_mutex_lock); + sys_pthread_mutex_unlock = (MutexOp)_dl_sym( + RTLD_NEXT, "pthread_mutex_unlock", (void*)init_sys_mutex_lock); + sys_pthread_mutex_trylock = (MutexOp)_dl_sym( + RTLD_NEXT, "pthread_mutex_trylock", (void*)init_sys_mutex_lock); +#if HAS_PTHREAD_MUTEX_TIMEDLOCK + sys_pthread_mutex_timedlock = (TimedMutexOp)_dl_sym( + RTLD_NEXT, "pthread_mutex_timedlock", (void*)init_sys_mutex_lock); +#endif // HAS_PTHREAD_MUTEX_TIMEDLOCK + } else { + // _dl_sym may be undefined reference in some system, fallback to dlsym + sys_pthread_mutex_init = (MutexInitOp)dlsym(RTLD_NEXT, "pthread_mutex_init"); + sys_pthread_mutex_destroy = (MutexOp)dlsym(RTLD_NEXT, "pthread_mutex_destroy"); + sys_pthread_mutex_lock = (MutexOp)dlsym(RTLD_NEXT, "pthread_mutex_lock"); + sys_pthread_mutex_unlock = (MutexOp)dlsym(RTLD_NEXT, "pthread_mutex_unlock"); + sys_pthread_mutex_trylock = (MutexOp)dlsym(RTLD_NEXT, "pthread_mutex_trylock"); +#if HAS_PTHREAD_MUTEX_TIMEDLOCK + sys_pthread_mutex_timedlock = (TimedMutexOp)dlsym(RTLD_NEXT, "pthread_mutex_timedlock"); +#endif // HAS_PTHREAD_MUTEX_TIMEDLOCK + } +#elif defined(OS_MACOSX) + // TODO: look workaround for dlsym on mac + sys_pthread_mutex_init = (MutexInitOp)dlsym(RTLD_NEXT, "pthread_mutex_init"); + sys_pthread_mutex_destroy = (MutexOp)dlsym(RTLD_NEXT, "pthread_mutex_destroy"); + sys_pthread_mutex_lock = (MutexOp)dlsym(RTLD_NEXT, "pthread_mutex_lock"); + sys_pthread_mutex_trylock = (MutexOp)dlsym(RTLD_NEXT, "pthread_mutex_trylock"); + sys_pthread_mutex_unlock = (MutexOp)dlsym(RTLD_NEXT, "pthread_mutex_unlock"); +#endif +} + +// Make sure pthread functions are ready before main(). +const int ALLOW_UNUSED dummy = pthread_once(&init_sys_mutex_lock_once, init_sys_mutex_lock); + +int first_sys_pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* mutexattr) { + pthread_once(&init_sys_mutex_lock_once, init_sys_mutex_lock); + return sys_pthread_mutex_init(mutex, mutexattr); +} + +int first_sys_pthread_mutex_destroy(pthread_mutex_t* mutex) { + pthread_once(&init_sys_mutex_lock_once, init_sys_mutex_lock); + return sys_pthread_mutex_destroy(mutex); +} + +int first_sys_pthread_mutex_lock(pthread_mutex_t* mutex) { + pthread_once(&init_sys_mutex_lock_once, init_sys_mutex_lock); + return sys_pthread_mutex_lock(mutex); +} + +int first_sys_pthread_mutex_trylock(pthread_mutex_t* mutex) { + pthread_once(&init_sys_mutex_lock_once, init_sys_mutex_lock); + return sys_pthread_mutex_trylock(mutex); +} + +#if HAS_PTHREAD_MUTEX_TIMEDLOCK +int first_sys_pthread_mutex_timedlock(pthread_mutex_t* mutex, + const struct timespec* abstime) { + pthread_once(&init_sys_mutex_lock_once, init_sys_mutex_lock); + return sys_pthread_mutex_timedlock(mutex, abstime); +} +#endif // HAS_PTHREAD_MUTEX_TIMEDLOCK + +int first_sys_pthread_mutex_unlock(pthread_mutex_t* mutex) { + pthread_once(&init_sys_mutex_lock_once, init_sys_mutex_lock); + return sys_pthread_mutex_unlock(mutex); +} +#endif // NO_PTHREAD_MUTEX_HOOK + +template +inline uint64_t hash_mutex_ptr(const Mutex* m) { + return butil::fmix64((uint64_t)m); +} + +// Mark being inside locking so that pthread_mutex calls inside collecting +// code are never sampled, otherwise deadlock may occur. +static __thread bool tls_inside_lock = false; + +// Warn up some singleton objects used in contention profiler +// to avoid deadlock in malloc call stack. +static __thread bool tls_warn_up = false; + +#if BRPC_DEBUG_BTHREAD_SCHE_SAFETY +// ++tls_pthread_lock_count when pthread locking, +// --tls_pthread_lock_count when pthread unlocking. +// Only when it is equal to 0, it is safe for the bthread to be scheduled. +// Note: If a mutex is locked/unlocked in different thread, +// `tls_pthread_lock_count' is inaccurate, so this feature cannot be used. +static __thread int tls_pthread_lock_count = 0; + +#define ADD_TLS_PTHREAD_LOCK_COUNT ++tls_pthread_lock_count +#define SUB_TLS_PTHREAD_LOCK_COUNT --tls_pthread_lock_count + +void CheckBthreadScheSafety() { + if (BAIDU_LIKELY(0 == tls_pthread_lock_count)) { + return; + } + + // It can only be checked once because the counter is messed up. + LOG_BACKTRACE_ONCE(ERROR) << "bthread is suspended while holding " + << tls_pthread_lock_count << " pthread locks."; +} +#else +#define ADD_TLS_PTHREAD_LOCK_COUNT ((void)0) +#define SUB_TLS_PTHREAD_LOCK_COUNT ((void)0) +void CheckBthreadScheSafety() {} +#endif // BRPC_DEBUG_BTHREAD_SCHE_SAFETY + +// Speed up with TLS: +// Most pthread_mutex are locked and unlocked in the same thread. Putting +// contention information in TLS avoids collisions that may occur in +// g_mutex_map. However when user unlocks in another thread, the info cached +// in the locking thread is not removed, making the space bloated. We use a +// simple strategy to solve the issue: If a thread has enough thread-local +// space to store the info, save it, otherwise save it in g_mutex_map. For +// a program that locks and unlocks in the same thread and does not lock a +// lot of mutexes simulateneously, this strategy always uses the TLS. +#ifndef DONT_SPEEDUP_PTHREAD_CONTENTION_PROFILER_WITH_TLS +const int TLS_MAX_COUNT = 3; +struct MutexAndContentionSite { + void* mutex; + bthread_contention_site_t csite; +}; +struct TLSPthreadContentionSites { + int count; + uint64_t cp_version; + MutexAndContentionSite list[TLS_MAX_COUNT]; +}; +static __thread TLSPthreadContentionSites tls_csites = {0,0,{}}; +#endif // DONT_SPEEDUP_PTHREAD_CONTENTION_PROFILER_WITH_TLS + +// Guaranteed in linux/win. +const int PTR_BITS = 48; + +template +inline bthread_contention_site_t* +add_pthread_contention_site(const Mutex* mutex) { + MutexMapEntry& entry = g_mutex_map[hash_mutex_ptr(mutex) & (MUTEX_MAP_SIZE - 1)]; + butil::static_atomic& m = entry.versioned_mutex; + uint64_t expected = m.load(butil::memory_order_relaxed); + // If the entry is not used or used by previous profiler, try to CAS it. + if (expected == 0 || + (expected >> PTR_BITS) != (g_cp_version & ((1 << (64 - PTR_BITS)) - 1))) { + uint64_t desired = (g_cp_version << PTR_BITS) | (uint64_t)mutex; + if (m.compare_exchange_strong( + expected, desired, butil::memory_order_acquire)) { + return &entry.csite; + } + } + g_nconflicthash.fetch_add(1, butil::memory_order_relaxed); + return NULL; +} + +template +inline bool remove_pthread_contention_site(const Mutex* mutex, + bthread_contention_site_t* saved_csite) { + MutexMapEntry& entry = g_mutex_map[hash_mutex_ptr(mutex) & (MUTEX_MAP_SIZE - 1)]; + butil::static_atomic& m = entry.versioned_mutex; + if ((m.load(butil::memory_order_relaxed) & ((((uint64_t)1) << PTR_BITS) - 1)) + != (uint64_t)mutex) { + // This branch should be the most common case since most locks are + // neither contended nor sampled. We have one memory indirection and + // several bitwise operations here, the cost should be ~ 5-50ns + return false; + } + // Although this branch is inside a contended lock, we should also make it + // as simple as possible because altering the critical section too much + // may make unpredictable impact to thread interleaving status, which + // makes profiling result less accurate. + *saved_csite = entry.csite; + make_contention_site_invalid(&entry.csite); + m.store(0, butil::memory_order_release); + return true; +} + +// Submit the contention along with the callsite('s stacktrace) +void submit_contention(const bthread_contention_site_t& csite, int64_t now_ns) { + tls_inside_lock = true; + BRPC_SCOPE_EXIT { + tls_inside_lock = false; + }; + + butil::debug::StackTrace stack(true); // May lock. + if (0 == stack.FrameCount()) { + return; + } + // There are two situations where we need to check whether in the + // malloc call stack: + // 1. Warn up some singleton objects used in `submit_contention' + // to avoid deadlock in malloc call stack. + // 2. LocalPool is empty, GlobalPool may allocate memory by malloc. + if (!tls_warn_up || butil::local_pool_free_empty()) { + // In malloc call stack, can not submit contention. + if (stack.FindSymbol((void*)malloc)) { + return; + } + } + + auto sc = butil::get_object(); + // Normalize duration_us and count so that they're addable in later + // processings. Notice that sampling_range is adjusted periodically by + // collecting thread. + sc->duration_ns = csite.duration_ns * bvar::COLLECTOR_SAMPLING_BASE + / csite.sampling_range; + sc->count = bvar::COLLECTOR_SAMPLING_BASE / (double)csite.sampling_range; + sc->nframes = stack.CopyAddressTo(sc->stack, arraysize(sc->stack)); + sc->submit(now_ns / 1000); // may lock + // Once submit a contention, complete warn up. + tls_warn_up = true; +} + +#if BRPC_DEBUG_LOCK +#define MUTEX_RESET_OWNER_COMMON(owner) \ + ((butil::atomic*)&(owner).hold) \ + ->store(false, butil::memory_order_relaxed) + +#define PTHREAD_MUTEX_SET_OWNER(owner) \ + owner.id = pthread_numeric_id(); \ + ((butil::atomic*)&(owner).hold) \ + ->store(true, butil::memory_order_release) + +// Check if the mutex has been locked by the current thread. +// Double lock on the same thread will cause deadlock. +#define PTHREAD_MUTEX_CHECK_OWNER(owner) \ + bool hold = ((butil::atomic*)&(owner).hold) \ + ->load(butil::memory_order_acquire); \ + if (hold && (owner).id == pthread_numeric_id()) { \ + butil::debug::StackTrace trace(true); \ + LOG(ERROR) << "Detected deadlock caused by double lock of FastPthreadMutex:" \ + << std::endl << trace.ToString(); \ + } +#else +#define MUTEX_RESET_OWNER_COMMON(owner) ((void)owner) +#define PTHREAD_MUTEX_SET_OWNER(owner) ((void)owner) +#define PTHREAD_MUTEX_CHECK_OWNER(owner) ((void)owner) +#endif // BRPC_DEBUG_LOCK + +namespace internal { + +#ifndef NO_PTHREAD_MUTEX_HOOK + +#if BRPC_DEBUG_LOCK +struct BAIDU_CACHELINE_ALIGNMENT MutexOwnerMapEntry { + butil::static_atomic valid; + pthread_mutex_t* mutex; + mutex_owner_t owner; +}; + +// The map storing owner information for pthread_mutex. Different from +// bthread_mutex, we can't save stuff into pthread_mutex, we neither can +// save the info in TLS reliably, since a mutex can be unlocked in a different +// thread from the one locked (although rare). +static MutexOwnerMapEntry g_mutex_owner_map[MUTEX_MAP_SIZE] = {}; // zero-initialize + +static void InitMutexOwnerMapEntry(pthread_mutex_t* mutex, + const pthread_mutexattr_t* mutexattr) { + int type = PTHREAD_MUTEX_DEFAULT; + if (NULL != mutexattr) { + pthread_mutexattr_gettype(mutexattr, &type); + } + // Only normal mutexes are tracked. + if (type != PTHREAD_MUTEX_NORMAL) { + return; + } + + // Fast path: If the hash entry is not used, use it. + MutexOwnerMapEntry& hash_entry = + g_mutex_owner_map[hash_mutex_ptr(mutex) & (MUTEX_MAP_SIZE - 1)]; + if (!hash_entry.valid.exchange(true, butil::memory_order_relaxed)) { + MUTEX_RESET_OWNER_COMMON(hash_entry.owner); + return; + } + + // Slow path: Find an unused entry. + for (auto& entry : g_mutex_owner_map) { + if (!entry.valid.exchange(true, butil::memory_order_relaxed)) { + MUTEX_RESET_OWNER_COMMON(entry.owner); + return; + } + } +} + +static BUTIL_FORCE_INLINE +MutexOwnerMapEntry* FindMutexOwnerMapEntry(pthread_mutex_t* mutex) { + if (NULL == mutex) { + return NULL; + } + + // Fast path. + MutexOwnerMapEntry* hash_entry = + &g_mutex_owner_map[hash_mutex_ptr(mutex) & (MUTEX_MAP_SIZE - 1)]; + if (hash_entry->valid.load(butil::memory_order_relaxed) && hash_entry->mutex == mutex) { + return hash_entry; + } + // Slow path. + for (auto& entry : g_mutex_owner_map) { + if (entry.valid.load(butil::memory_order_relaxed) && entry.mutex == mutex) { + return &entry; + } + } + return NULL; +} + +static void DestroyMutexOwnerMapEntry(pthread_mutex_t* mutex) { + MutexOwnerMapEntry* entry = FindMutexOwnerMapEntry(mutex); + if (NULL != entry) { + entry->valid.store(false, butil::memory_order_relaxed); + } +} + +#define INIT_MUTEX_OWNER_MAP_ENTRY(mutex, mutexattr) \ + ::bthread::internal::InitMutexOwnerMapEntry(mutex, mutexattr) + +#define DESTROY_MUTEX_OWNER_MAP_ENTRY(mutex) \ + ::bthread::internal::DestroyMutexOwnerMapEntry(mutex) + +#define FIND_SYS_PTHREAD_MUTEX_OWNER_MAP_ENTRY(mutex) \ + MutexOwnerMapEntry* entry = ::bthread::internal::FindMutexOwnerMapEntry(mutex) + +#define SYS_PTHREAD_MUTEX_CHECK_OWNER \ + if (NULL != entry) { \ + PTHREAD_MUTEX_CHECK_OWNER(entry->owner); \ + } + +#define SYS_PTHREAD_MUTEX_SET_OWNER \ + if (NULL != entry) { \ + PTHREAD_MUTEX_SET_OWNER(entry->owner); \ + } + +#define SYS_PTHREAD_MUTEX_RESET_OWNER(mutex) \ + FIND_SYS_PTHREAD_MUTEX_OWNER_MAP_ENTRY(mutex); \ + if (NULL != entry) { \ + MUTEX_RESET_OWNER_COMMON(entry->owner); \ + } + +#else +#define INIT_MUTEX_OWNER_MAP_ENTRY(mutex, mutexattr) ((void)0) +#define DESTROY_MUTEX_OWNER_MAP_ENTRY(mutex) ((void)0) +#define FIND_SYS_PTHREAD_MUTEX_OWNER_MAP_ENTRY(mutex) ((void)0) +#define SYS_PTHREAD_MUTEX_CHECK_OWNER ((void)0) +#define SYS_PTHREAD_MUTEX_SET_OWNER ((void)0) +#define SYS_PTHREAD_MUTEX_RESET_OWNER(mutex) ((void)0) +#endif // BRPC_DEBUG_LOCK + + +#if HAS_PTHREAD_MUTEX_TIMEDLOCK +BUTIL_FORCE_INLINE int pthread_mutex_lock_internal(pthread_mutex_t* mutex, + const struct timespec* abstime) { + int rc = 0; + if (NULL == abstime) { + FIND_SYS_PTHREAD_MUTEX_OWNER_MAP_ENTRY(mutex); + SYS_PTHREAD_MUTEX_CHECK_OWNER; + rc = sys_pthread_mutex_lock(mutex); + if (0 == rc) { + SYS_PTHREAD_MUTEX_SET_OWNER; + } + } else { + rc = sys_pthread_mutex_timedlock(mutex, abstime); + if (0 == rc) { + FIND_SYS_PTHREAD_MUTEX_OWNER_MAP_ENTRY(mutex); + SYS_PTHREAD_MUTEX_SET_OWNER; + } + } + if (0 == rc) { + ADD_TLS_PTHREAD_LOCK_COUNT; + } + return rc; +} +#else +BUTIL_FORCE_INLINE int pthread_mutex_lock_internal(pthread_mutex_t* mutex, + const struct timespec*/* Not supported */) { + FIND_SYS_PTHREAD_MUTEX_OWNER_MAP_ENTRY(mutex); + SYS_PTHREAD_MUTEX_CHECK_OWNER; + int rc = sys_pthread_mutex_lock(mutex); + if (0 == rc) { + SYS_PTHREAD_MUTEX_SET_OWNER; + ADD_TLS_PTHREAD_LOCK_COUNT; + } + return rc; +} +#endif // HAS_PTHREAD_MUTEX_TIMEDLOCK + +BUTIL_FORCE_INLINE int pthread_mutex_trylock_internal(pthread_mutex_t* mutex) { + int rc = sys_pthread_mutex_trylock(mutex); + if (0 == rc) { + FIND_SYS_PTHREAD_MUTEX_OWNER_MAP_ENTRY(mutex); + SYS_PTHREAD_MUTEX_SET_OWNER; + ADD_TLS_PTHREAD_LOCK_COUNT; + } + return rc; +} + +BUTIL_FORCE_INLINE int pthread_mutex_unlock_internal(pthread_mutex_t* mutex) { + SYS_PTHREAD_MUTEX_RESET_OWNER(mutex); + SUB_TLS_PTHREAD_LOCK_COUNT; + return sys_pthread_mutex_unlock(mutex); +} +#endif // NO_PTHREAD_MUTEX_HOOK + +BUTIL_FORCE_INLINE int pthread_mutex_lock_internal(FastPthreadMutex* mutex, + const struct timespec* abstime) { + if (NULL == abstime) { + mutex->lock(); + return 0; + } else { + return mutex->timed_lock(abstime) ? 0 : errno; + } +} + +BUTIL_FORCE_INLINE int pthread_mutex_trylock_internal(FastPthreadMutex* mutex) { + return mutex->try_lock() ? 0 : EBUSY; +} + +BUTIL_FORCE_INLINE int pthread_mutex_unlock_internal(FastPthreadMutex* mutex) { + mutex->unlock(); + return 0; +} + +template +BUTIL_FORCE_INLINE int pthread_mutex_lock_impl(Mutex* mutex, const struct timespec* abstime) { + // Don't change behavior of lock when profiler is off. + if (!g_cp || + // collecting code including backtrace() and submit() may call + // pthread_mutex_lock and cause deadlock. Don't sample. + tls_inside_lock) { + return pthread_mutex_lock_internal(mutex, abstime); + } + // Don't slow down non-contended locks. + int rc = pthread_mutex_trylock_internal(mutex); + if (rc != EBUSY) { + return rc; + } + // Ask bvar::Collector if this (contended) locking should be sampled + const size_t sampling_range = bvar::is_collectable(&g_cp_sl); + + bthread_contention_site_t* csite = NULL; +#ifndef DONT_SPEEDUP_PTHREAD_CONTENTION_PROFILER_WITH_TLS + TLSPthreadContentionSites& fast_alt = tls_csites; + if (fast_alt.cp_version != g_cp_version) { + fast_alt.cp_version = g_cp_version; + fast_alt.count = 0; + } + if (fast_alt.count < TLS_MAX_COUNT) { + MutexAndContentionSite& entry = fast_alt.list[fast_alt.count++]; + entry.mutex = mutex; + csite = &entry.csite; + if (!bvar::is_sampling_range_valid(sampling_range)) { + make_contention_site_invalid(&entry.csite); + return pthread_mutex_lock_internal(mutex, abstime); + } + } +#endif + if (!bvar::is_sampling_range_valid(sampling_range)) { // don't sample + return pthread_mutex_lock_internal(mutex, abstime); + } + // Lock and monitor the waiting time. + const int64_t start_ns = butil::cpuwide_time_ns(); + rc = pthread_mutex_lock_internal(mutex, abstime); + if (!rc) { // Inside lock + if (!csite) { + csite = add_pthread_contention_site(mutex); + if (csite == NULL) { + return rc; + } + } + csite->duration_ns = butil::cpuwide_time_ns() - start_ns; + csite->sampling_range = sampling_range; + } // else rare + return rc; +} + +template +BUTIL_FORCE_INLINE int pthread_mutex_trylock_impl(Mutex* mutex) { + return pthread_mutex_trylock_internal(mutex); +} + +template +BUTIL_FORCE_INLINE int pthread_mutex_unlock_impl(Mutex* mutex) { + // Don't change behavior of unlock when profiler is off. + if (!g_cp || tls_inside_lock) { + // This branch brings an issue that an entry created by + // add_pthread_contention_site may not be cleared. Thus we add a + // 16-bit rolling version in the entry to find out such entry. + return pthread_mutex_unlock_internal(mutex); + } + int64_t unlock_start_ns = 0; + bool miss_in_tls = true; + bthread_contention_site_t saved_csite = {0,0}; +#ifndef DONT_SPEEDUP_PTHREAD_CONTENTION_PROFILER_WITH_TLS + TLSPthreadContentionSites& fast_alt = tls_csites; + for (int i = fast_alt.count - 1; i >= 0; --i) { + if (fast_alt.list[i].mutex == mutex) { + if (is_contention_site_valid(fast_alt.list[i].csite)) { + saved_csite = fast_alt.list[i].csite; + unlock_start_ns = butil::cpuwide_time_ns(); + } + fast_alt.list[i] = fast_alt.list[--fast_alt.count]; + miss_in_tls = false; + break; + } + } +#endif + // Check the map to see if the lock is sampled. Notice that we're still + // inside critical section. + if (miss_in_tls) { + if (remove_pthread_contention_site(mutex, &saved_csite)) { + unlock_start_ns = butil::cpuwide_time_ns(); + } + } + const int rc = pthread_mutex_unlock_internal(mutex); + // [Outside lock] + if (unlock_start_ns) { + const int64_t unlock_end_ns = butil::cpuwide_time_ns(); + saved_csite.duration_ns += unlock_end_ns - unlock_start_ns; + submit_contention(saved_csite, unlock_end_ns); + } + return rc; +} + +} + +#ifndef NO_PTHREAD_MUTEX_HOOK +BUTIL_FORCE_INLINE int pthread_mutex_lock_impl(pthread_mutex_t* mutex) { + return internal::pthread_mutex_lock_impl(mutex, NULL); +} + +BUTIL_FORCE_INLINE int pthread_mutex_trylock_impl(pthread_mutex_t* mutex) { + return internal::pthread_mutex_trylock_impl(mutex); +} + +#if HAS_PTHREAD_MUTEX_TIMEDLOCK +BUTIL_FORCE_INLINE int pthread_mutex_timedlock_impl(pthread_mutex_t* mutex, + const struct timespec* abstime) { + return internal::pthread_mutex_lock_impl(mutex, abstime); +} +#endif // HAS_PTHREAD_MUTEX_TIMEDLOCK + +BUTIL_FORCE_INLINE int pthread_mutex_unlock_impl(pthread_mutex_t* mutex) { + return internal::pthread_mutex_unlock_impl(mutex); +} +#endif // NO_PTHREAD_MUTEX_HOOK + +// Implement bthread_mutex_t related functions +struct MutexInternal { + butil::static_atomic locked; + butil::static_atomic contended; + unsigned short padding; +}; + +const MutexInternal MUTEX_CONTENDED_RAW = {{1},{1},0}; +const MutexInternal MUTEX_LOCKED_RAW = {{1},{0},0}; +// Define as macros rather than constants which can't be put in read-only +// section and affected by initialization-order fiasco. +#define BTHREAD_MUTEX_CONTENDED (*(const unsigned*)&bthread::MUTEX_CONTENDED_RAW) +#define BTHREAD_MUTEX_LOCKED (*(const unsigned*)&bthread::MUTEX_LOCKED_RAW) + +BAIDU_CASSERT(sizeof(unsigned) == sizeof(MutexInternal), + sizeof_mutex_internal_must_equal_unsigned); + +#if BRPC_DEBUG_LOCK + +#define BTHREAD_MUTEX_SET_OWNER \ + do { \ + TaskGroup* task_group = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); \ + if (NULL != task_group && !task_group->is_current_main_task()) { \ + m->owner.id = bthread_self(); \ + } else { \ + m->owner.id = pthread_numeric_id(); \ + } \ + ((butil::atomic*)&m->owner.hold) \ + ->store(true, butil::memory_order_release); \ + } while(false) + +// Check if the mutex has been locked by the current thread. +// Double lock on the same thread will cause deadlock. +#define BTHREAD_MUTEX_CHECK_OWNER \ + bool hold = ((butil::atomic*)&m->owner.hold) \ + ->load(butil::memory_order_acquire); \ + bool double_lock = \ + hold && (m->owner.id == bthread_self() || m->owner.id == pthread_numeric_id()); \ + if (double_lock) { \ + butil::debug::StackTrace trace(true); \ + LOG(ERROR) << "Detected deadlock caused by double lock of bthread_mutex_t:" \ + << std::endl << trace.ToString(); \ + } +#else +#define BTHREAD_MUTEX_SET_OWNER ((void)0) +#define BTHREAD_MUTEX_CHECK_OWNER ((void)0) +#endif // BRPC_DEBUG_LOCK + +inline int mutex_trylock_impl(bthread_mutex_t* m) { + MutexInternal* split = (MutexInternal*)m->butex; + if (!split->locked.exchange(1, butil::memory_order_acquire)) { + BTHREAD_MUTEX_SET_OWNER; + return 0; + } + return EBUSY; +} + +const int MAX_SPIN_ITER = 4; + +inline int mutex_lock_contended_impl(bthread_mutex_t* __restrict m, + const struct timespec* __restrict abstime) { + BTHREAD_MUTEX_CHECK_OWNER; + // When a bthread first contends for a lock, active spinning makes sense. + // Spin only few times and only if local `rq' is empty. + TaskGroup* g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + if (BAIDU_UNLIKELY(NULL == g || g->rq_size() == 0)) { + for (int i = 0; i < MAX_SPIN_ITER; ++i) { + cpu_relax(); + } + } + + bool queue_lifo = false; + bool first_wait = true; + auto whole = (butil::atomic*)m->butex; + while (whole->exchange(BTHREAD_MUTEX_CONTENDED) & BTHREAD_MUTEX_LOCKED) { + if (bthread::butex_wait(whole, BTHREAD_MUTEX_CONTENDED, abstime, queue_lifo) < 0 && + errno != EWOULDBLOCK && errno != EINTR/*note*/) { + // A mutex lock should ignore interruptions in general since + // user code is unlikely to check the return value. + return errno; + } + // Ignore EWOULDBLOCK and EINTR. + if (first_wait && 0 == errno) { + first_wait = false; + } + if (!first_wait) { + // Normally, bthreads are queued in FIFO order. But competing with new + // arriving bthreads over the ownership of mutex, a woken up bthread + // has good chances of losing. Because new arriving bthreads are already + // running on CPU and there can be lots of them. In such case, for fairness, + // to avoid starvation, it is queued at the head of the waiter queue. + queue_lifo = true; + } + } + BTHREAD_MUTEX_SET_OWNER; + return 0; +} + +#ifdef BTHREAD_USE_FAST_PTHREAD_MUTEX +namespace internal { + +FastPthreadMutex::FastPthreadMutex() : _futex(0) { + MUTEX_RESET_OWNER_COMMON(_owner); +} + +int FastPthreadMutex::lock_contended(const struct timespec* abstime) { + int64_t abstime_us = 0; + if (NULL != abstime) { + abstime_us = butil::timespec_to_microseconds(*abstime); + } + auto whole = (butil::atomic*)&_futex; + while (whole->exchange(BTHREAD_MUTEX_CONTENDED) & BTHREAD_MUTEX_LOCKED) { + timespec* ptimeout = NULL; + timespec timeout{}; + if (NULL != abstime) { + timeout = butil::microseconds_to_timespec( + abstime_us - butil::gettimeofday_us()); + ptimeout = &timeout; + } + if (NULL == abstime || abstime_us > MIN_SLEEP_US) { + if (futex_wait_private(whole, BTHREAD_MUTEX_CONTENDED, ptimeout) < 0 + && errno != EWOULDBLOCK && errno != EINTR/*note*/) { + // A mutex lock should ignore interruptions in general since + // user code is unlikely to check the return value. + return errno; + } + } else { + errno = ETIMEDOUT; + return errno; + } + } + PTHREAD_MUTEX_SET_OWNER(_owner); + ADD_TLS_PTHREAD_LOCK_COUNT; + return 0; +} + +void FastPthreadMutex::lock() { + if (try_lock()) { + return; + } + + PTHREAD_MUTEX_CHECK_OWNER(_owner); + (void)lock_contended(NULL); +} + +bool FastPthreadMutex::try_lock() { + auto split = (bthread::MutexInternal*)&_futex; + bool lock = !split->locked.exchange(1, butil::memory_order_acquire); + if (lock) { + PTHREAD_MUTEX_SET_OWNER(_owner); + ADD_TLS_PTHREAD_LOCK_COUNT; + } + return lock; +} + +bool FastPthreadMutex::timed_lock(const struct timespec* abstime) { + if (try_lock()) { + return true; + } + return 0 == lock_contended(abstime); +} + +void FastPthreadMutex::unlock() { + SUB_TLS_PTHREAD_LOCK_COUNT; + MUTEX_RESET_OWNER_COMMON(_owner); + auto whole = (butil::atomic*)&_futex; + const unsigned prev = whole->exchange(0, butil::memory_order_release); + // CAUTION: the mutex may be destroyed, check comments before butex_create + if (prev != BTHREAD_MUTEX_LOCKED) { + futex_wake_private(whole, 1); + } +} + +} // namespace internal +#endif // BTHREAD_USE_FAST_PTHREAD_MUTEX + +void FastPthreadMutex::lock() { + internal::pthread_mutex_lock_impl(&_mutex, NULL); +} + +void FastPthreadMutex::unlock() { + internal::pthread_mutex_unlock_impl(&_mutex); +} + +#if defined(BTHREAD_USE_FAST_PTHREAD_MUTEX) || HAS_PTHREAD_MUTEX_TIMEDLOCK +bool FastPthreadMutex::timed_lock(const struct timespec* abstime) { + return internal::pthread_mutex_lock_impl(&_mutex, abstime) == 0; +} +#endif // BTHREAD_USE_FAST_PTHREAD_MUTEX HAS_PTHREAD_MUTEX_TIMEDLOCK + +} // namespace bthread + +__BEGIN_DECLS + +int bthread_mutex_init(bthread_mutex_t* __restrict m, + const bthread_mutexattr_t* __restrict attr) { + bthread::make_contention_site_invalid(&m->csite); + MUTEX_RESET_OWNER_COMMON(m->owner); + m->butex = bthread::butex_create_checked(); + if (!m->butex) { + return ENOMEM; + } + *m->butex = 0; + m->enable_csite = NULL == attr ? true : attr->enable_csite; + return 0; +} + +int bthread_mutex_destroy(bthread_mutex_t* m) { + bthread::butex_destroy(m->butex); + return 0; +} + +int bthread_mutex_trylock(bthread_mutex_t* m) { + return bthread::mutex_trylock_impl(m); +} + +int bthread_mutex_lock_contended(bthread_mutex_t* m) { + return bthread::mutex_lock_contended_impl(m, NULL); +} + +static int bthread_mutex_lock_impl(bthread_mutex_t* __restrict m, + const struct timespec* __restrict abstime) { + if (0 == bthread::mutex_trylock_impl(m)) { + return 0; + } + // Don't sample when contention profiler is off. + if (!bthread::g_cp) { + return bthread::mutex_lock_contended_impl(m, abstime); + } + // Ask Collector if this (contended) locking should be sampled. + const size_t sampling_range = + m->enable_csite ? bvar::is_collectable(&bthread::g_cp_sl) : bvar::INVALID_SAMPLING_RANGE; + if (!bvar::is_sampling_range_valid(sampling_range)) { // Don't sample + return bthread::mutex_lock_contended_impl(m, abstime); + } + // Start sampling. + const int64_t start_ns = butil::cpuwide_time_ns(); + // NOTE: Don't modify m->csite outside lock since multiple threads are + // still contending with each other. + const int rc = bthread::mutex_lock_contended_impl(m, abstime); + if (!rc) { // Inside lock + m->csite.duration_ns = butil::cpuwide_time_ns() - start_ns; + m->csite.sampling_range = sampling_range; + } else if (rc == ETIMEDOUT) { + // Failed to lock due to ETIMEDOUT, submit the elapse directly. + const int64_t end_ns = butil::cpuwide_time_ns(); + const bthread_contention_site_t csite = {end_ns - start_ns, sampling_range}; + bthread::submit_contention(csite, end_ns); + } + return rc; +} + +int bthread_mutex_lock(bthread_mutex_t* m) { + return bthread_mutex_lock_impl(m, NULL); +} + +int bthread_mutex_timedlock(bthread_mutex_t* __restrict m, + const struct timespec* __restrict abstime) { + return bthread_mutex_lock_impl(m, abstime); +} + +int bthread_mutex_unlock(bthread_mutex_t* m) { + auto whole = (butil::atomic*)m->butex; + bthread_contention_site_t saved_csite = {0, 0}; + bool is_valid = bthread::is_contention_site_valid(m->csite); + if (is_valid) { + saved_csite = m->csite; + bthread::make_contention_site_invalid(&m->csite); + } + MUTEX_RESET_OWNER_COMMON(m->owner); + const unsigned prev = whole->exchange(0, butil::memory_order_release); + // CAUTION: the mutex may be destroyed, check comments before butex_create + if (prev == BTHREAD_MUTEX_LOCKED) { + return 0; + } + // Wakeup one waiter + if (!is_valid) { + bthread::butex_wake(whole); + return 0; + } + const int64_t unlock_start_ns = butil::cpuwide_time_ns(); + bthread::butex_wake(whole); + const int64_t unlock_end_ns = butil::cpuwide_time_ns(); + saved_csite.duration_ns += unlock_end_ns - unlock_start_ns; + bthread::submit_contention(saved_csite, unlock_end_ns); + return 0; +} + +int bthread_mutexattr_init(bthread_mutexattr_t* attr) { + attr->enable_csite = true; + return 0; +} + +int bthread_mutexattr_disable_csite(bthread_mutexattr_t* attr) { + attr->enable_csite = false; + return 0; +} + +int bthread_mutexattr_destroy(bthread_mutexattr_t* attr) { + attr->enable_csite = true; + return 0; +} + +#ifndef NO_PTHREAD_MUTEX_HOOK + +int pthread_mutex_init(pthread_mutex_t * __restrict mutex, + const pthread_mutexattr_t* __restrict mutexattr) { + INIT_MUTEX_OWNER_MAP_ENTRY(mutex, mutexattr); + return bthread::sys_pthread_mutex_init(mutex, mutexattr); +} + +int pthread_mutex_destroy(pthread_mutex_t* mutex) { + DESTROY_MUTEX_OWNER_MAP_ENTRY(mutex); + return bthread::sys_pthread_mutex_destroy(mutex); +} + +int pthread_mutex_lock(pthread_mutex_t* mutex) { + return bthread::pthread_mutex_lock_impl(mutex); +} + +#if defined(OS_LINUX) && defined(OS_POSIX) && defined(__USE_XOPEN2K) +int pthread_mutex_timedlock(pthread_mutex_t *__restrict __mutex, + const struct timespec *__restrict __abstime) { + return bthread::pthread_mutex_timedlock_impl(__mutex, __abstime); +} +#endif // OS_POSIX __USE_XOPEN2K + +int pthread_mutex_trylock(pthread_mutex_t* mutex) { + return bthread::pthread_mutex_trylock_impl(mutex); +} + +int pthread_mutex_unlock(pthread_mutex_t* mutex) { + return bthread::pthread_mutex_unlock_impl(mutex); +} +#endif // NO_PTHREAD_MUTEX_HOOK + + +__END_DECLS diff --git a/src/bthread/mutex.h b/src/bthread/mutex.h new file mode 100644 index 0000000..11c7eae --- /dev/null +++ b/src/bthread/mutex.h @@ -0,0 +1,255 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: 2015/12/14 18:17:04 + +#ifndef BTHREAD_MUTEX_H +#define BTHREAD_MUTEX_H + +#include "bthread/types.h" +#include "butil/scoped_lock.h" +#include "bvar/utils/lock_timer.h" + +__BEGIN_DECLS +extern int bthread_mutex_init(bthread_mutex_t* __restrict mutex, + const bthread_mutexattr_t* __restrict attr); +extern int bthread_mutex_destroy(bthread_mutex_t* mutex); +extern int bthread_mutex_trylock(bthread_mutex_t* mutex); +extern int bthread_mutex_lock(bthread_mutex_t* mutex); +extern int bthread_mutex_timedlock(bthread_mutex_t* __restrict mutex, + const struct timespec* __restrict abstime); +extern int bthread_mutex_unlock(bthread_mutex_t* mutex); +extern bthread_t bthread_self(void); +__END_DECLS + +namespace bthread { + +// The C++ Wrapper of bthread_mutex + +// NOTE: Not aligned to cacheline as the container of Mutex is practically aligned +class Mutex { +public: + typedef bthread_mutex_t* native_handler_type; + Mutex() { + int ec = bthread_mutex_init(&_mutex, NULL); + if (ec != 0) { + throw std::system_error(std::error_code(ec, std::system_category()), + "Mutex constructor failed"); + } + } + ~Mutex() { CHECK_EQ(0, bthread_mutex_destroy(&_mutex)); } + native_handler_type native_handler() { return &_mutex; } + void lock() { + int ec = bthread_mutex_lock(&_mutex); + if (ec != 0) { + throw std::system_error(std::error_code(ec, std::system_category()), + "Mutex lock failed"); + } + } + void unlock() { bthread_mutex_unlock(&_mutex); } + bool try_lock() { return 0 == bthread_mutex_trylock(&_mutex); } + bool timed_lock(const struct timespec* abstime) { + return !bthread_mutex_timedlock(&_mutex, abstime); + } + // TODO(chenzhangyi01): Complement interfaces for C++11 +private: + DISALLOW_COPY_AND_ASSIGN(Mutex); + bthread_mutex_t _mutex; +}; + +namespace internal { +#ifdef BTHREAD_USE_FAST_PTHREAD_MUTEX +class FastPthreadMutex { +public: + FastPthreadMutex(); + void lock(); + void unlock(); + bool try_lock(); + bool timed_lock(const struct timespec* abstime); +private: + DISALLOW_COPY_AND_ASSIGN(FastPthreadMutex); + int lock_contended(const struct timespec* abstime); + + unsigned _futex; + // Note: Owner detection of the mutex comes with average execution + // slowdown of about 50%., so it is only used for debugging and is + // only available when the macro `BRPC_DEBUG_LOCK' = 1. + mutex_owner_t _owner; +}; +#else +typedef butil::Mutex FastPthreadMutex; +#endif +} + +class FastPthreadMutex { +public: + FastPthreadMutex() = default; + ~FastPthreadMutex() = default; + DISALLOW_COPY_AND_ASSIGN(FastPthreadMutex); + + void lock(); + void unlock(); + bool try_lock() { return _mutex.try_lock(); } +#if defined(BTHREAD_USE_FAST_PTHREAD_MUTEX) || HAS_PTHREAD_MUTEX_TIMEDLOCK + bool timed_lock(const struct timespec* abstime); +#endif // BTHREAD_USE_FAST_PTHREAD_MUTEX HAS_PTHREAD_MUTEX_TIMEDLOCK + +private: + internal::FastPthreadMutex _mutex; +}; + +} // namespace bthread + +// Specialize std::lock_guard and std::unique_lock for bthread_mutex_t + +namespace std { + +template <> class lock_guard { +public: + explicit lock_guard(bthread_mutex_t& mutex) : _pmutex(&mutex) { +#if !defined(NDEBUG) + const int rc = bthread_mutex_lock(_pmutex); + if (rc) { + LOG(FATAL) << "Fail to lock bthread_mutex_t=" << _pmutex << ", " << berror(rc); + _pmutex = NULL; + } +#else + bthread_mutex_lock(_pmutex); +#endif // NDEBUG + } + + ~lock_guard() { +#ifndef NDEBUG + if (_pmutex) { + bthread_mutex_unlock(_pmutex); + } +#else + bthread_mutex_unlock(_pmutex); +#endif + } + +private: + DISALLOW_COPY_AND_ASSIGN(lock_guard); + bthread_mutex_t* _pmutex; +}; + +template <> class unique_lock { + DISALLOW_COPY_AND_ASSIGN(unique_lock); +public: + typedef bthread_mutex_t mutex_type; + unique_lock() : _mutex(NULL), _owns_lock(false) {} + explicit unique_lock(mutex_type& mutex) + : _mutex(&mutex), _owns_lock(false) { + lock(); + } + unique_lock(mutex_type& mutex, defer_lock_t) + : _mutex(&mutex), _owns_lock(false) + {} + unique_lock(mutex_type& mutex, try_to_lock_t) + : _mutex(&mutex), _owns_lock(bthread_mutex_trylock(&mutex) == 0) + {} + unique_lock(mutex_type& mutex, adopt_lock_t) + : _mutex(&mutex), _owns_lock(true) + {} + + ~unique_lock() { + if (_owns_lock) { + unlock(); + } + } + + void lock() { + if (!_mutex) { + CHECK(false) << "Invalid operation"; + return; + } + if (_owns_lock) { + CHECK(false) << "Detected deadlock issue"; + return; + } + bthread_mutex_lock(_mutex); + _owns_lock = true; + } + + bool try_lock() { + if (!_mutex) { + CHECK(false) << "Invalid operation"; + return false; + } + if (_owns_lock) { + CHECK(false) << "Detected deadlock issue"; + return false; + } + _owns_lock = !bthread_mutex_trylock(_mutex); + return _owns_lock; + } + + void unlock() { + if (!_owns_lock) { + CHECK(false) << "Invalid operation"; + return; + } + if (_mutex) { + bthread_mutex_unlock(_mutex); + _owns_lock = false; + } + } + + void swap(unique_lock& rhs) { + std::swap(_mutex, rhs._mutex); + std::swap(_owns_lock, rhs._owns_lock); + } + + mutex_type* release() { + mutex_type* saved_mutex = _mutex; + _mutex = NULL; + _owns_lock = false; + return saved_mutex; + } + + mutex_type* mutex() { return _mutex; } + bool owns_lock() const { return _owns_lock; } + operator bool() const { return owns_lock(); } + +private: + mutex_type* _mutex; + bool _owns_lock; +}; + +} // namespace std + +namespace bvar { + +template <> +struct MutexConstructor { + bool operator()(bthread_mutex_t* mutex) const { + return bthread_mutex_init(mutex, NULL) == 0; + } +}; + +template <> +struct MutexDestructor { + bool operator()(bthread_mutex_t* mutex) const { + return bthread_mutex_destroy(mutex) == 0; + } +}; + +} // namespace bvar + +#endif //BTHREAD_MUTEX_H diff --git a/src/bthread/offset_inl.list b/src/bthread/offset_inl.list new file mode 100644 index 0000000..1c40963 --- /dev/null +++ b/src/bthread/offset_inl.list @@ -0,0 +1,37 @@ +36809,36821,36833,36847,36857,36871,36877,36887,36899,36901,36913,36919, +36923,36929,36931,36943,36947,36973,36979,36997,37003,37013,37019,37021, +37039,37049,37057,37061,37087,37097,37117,37123,37139,37159,37171,37181, +37189,37199,37201,37217,37223,37243,37253,37273,37277,37307,37309,37313, +37321,37337,37339,37357,37361,37363,37369,37379,37397,37409,37423,37441, +37447,37463,37483,37489,37493,37501,37507,37511,37517,37529,37537,37547, +37549,37561,37567,37571,37573,37579,37589,37591,37607,37619,37633,37643, +37649,37657,37663,37691,37693,37699,37717,37747,37781,37783,37799,37811, +37813,37831,37847,37853,37861,37871,37879,37889,37897,37907,37951,37957, +37963,37967,37987,37991,37993,37997,38011,38039,38047,38053,38069,38083, +38113,38119,38149,38153,38167,38177,38183,38189,38197,38201,38219,38231, +38237,38239,38261,38273,38281,38287,38299,38303,38317,38321,38327,38329, +38333,38351,38371,38377,38393,38431,38447,38449,38453,38459,38461,38501, +38543,38557,38561,38567,38569,38593,38603,38609,38611,38629,38639,38651, +38653,38669,38671,38677,38693,38699,38707,38711,38713,38723,38729,38737, +38747,38749,38767,38783,38791,38803,38821,38833,38839,38851,38861,38867, +38873,38891,38903,38917,38921,38923,38933,38953,38959,38971,38977,38993, +39019,39023,39041,39043,39047,39079,39089,39097,39103,39107,39113,39119, +39133,39139,39157,39161,39163,39181,39191,39199,39209,39217,39227,39229, +39233,39239,39241,39251,39293,39301,39313,39317,39323,39341,39343,39359, +39367,39371,39373,39383,39397,39409,39419,39439,39443,39451,39461,39499, +39503,39509,39511,39521,39541,39551,39563,39569,39581,39607,39619,39623, +39631,39659,39667,39671,39679,39703,39709,39719,39727,39733,39749,39761, +39769,39779,39791,39799,39821,39827,39829,39839,39841,39847,39857,39863, +39869,39877,39883,39887,39901,39929,39937,39953,39971,39979,39983,39989, +40009,40013,40031,40037,40039,40063,40087,40093,40099,40111,40123,40127, +40129,40151,40153,40163,40169,40177,40189,40193,40213,40231,40237,40241, +40253,40277,40283,40289,40343,40351,40357,40361,40387,40423,40427,40429, +40433,40459,40471,40483,40487,40493,40499,40507,40519,40529,40531,40543, +40559,40577,40583,40591,40597,40609,40627,40637,40639,40693,40697,40699, +40709,40739,40751,40759,40763,40771,40787,40801,40813,40819,40823,40829, +40841,40847,40849,40853,40867,40879,40883,40897,40903,40927,40933,40939, +40949,40961,40973,40993,41011,41017,41023,41039,41047,41051,41057,41077, +41081,41113,41117,41131,41141,41143,41149,41161,41177,41179,41183,41189, +41201,41203,41213,41221,41227,41231,41233,41243,41257,41263,41269,41281, +41299,41333,41341,41351,41357,41381,41387,41389,41399,41411,41413,41443, +41453,41467,41479,41491,41507,41513,41519,41521,41539,41543,41549,41579 diff --git a/src/bthread/parking_lot.h b/src/bthread/parking_lot.h new file mode 100644 index 0000000..bbc9a7c --- /dev/null +++ b/src/bthread/parking_lot.h @@ -0,0 +1,99 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: 2017/07/27 23:07:06 + +#ifndef BTHREAD_PARKING_LOT_H +#define BTHREAD_PARKING_LOT_H + +#include +#include "butil/atomicops.h" +#include "bthread/sys_futex.h" + +namespace bthread { + +DECLARE_bool(parking_lot_no_signal_when_no_waiter); + +// Park idle workers. +class BAIDU_CACHELINE_ALIGNMENT ParkingLot { +public: + class State { + public: + State(): val(0) {} + bool stopped() const { return val & 1; } + private: + friend class ParkingLot; + State(int val) : val(val) {} + int val; + }; + + ParkingLot() + : _pending_signal(0), _waiter_num(0) + , _no_signal_when_no_waiter(FLAGS_parking_lot_no_signal_when_no_waiter) {} + + // Wake up at most `num_task' workers. + // Returns #workers woken up. + int signal(int num_task) { + _pending_signal.fetch_add((num_task << 1), butil::memory_order_release); + if (_no_signal_when_no_waiter && _waiter_num.load(butil::memory_order_relaxed) == 0) { + return 0; + } + return futex_wake_private(&_pending_signal, num_task); + } + + // Get a state for later wait(). + State get_state() { + return _pending_signal.load(butil::memory_order_acquire); + } + + // Wait for tasks. + // If the `expected_state' does not match, wait() may finish directly. + void wait(const State& expected_state) { + if (get_state().val != expected_state.val) { + // Fast path, no need to futex_wait. + return; + } + if (_no_signal_when_no_waiter) { + _waiter_num.fetch_add(1, butil::memory_order_relaxed); + } + futex_wait_private(&_pending_signal, expected_state.val, NULL); + if (_no_signal_when_no_waiter) { + _waiter_num.fetch_sub(1, butil::memory_order_relaxed); + } + } + + // Wakeup suspended wait() and make them unwaitable ever. + void stop() { + _pending_signal.fetch_or(1); + futex_wake_private(&_pending_signal, 10000); + } + +private: + // higher 31 bits for signalling, LSB for stopping. + butil::atomic _pending_signal; + butil::atomic _waiter_num; + // Whether to signal when there is no waiter. + // In busy worker scenarios, signal overhead + // can be reduced. + bool _no_signal_when_no_waiter; +}; + +} // namespace bthread + +#endif // BTHREAD_PARKING_LOT_H diff --git a/src/bthread/prime_offset.h b/src/bthread/prime_offset.h new file mode 100644 index 0000000..7137a68 --- /dev/null +++ b/src/bthread/prime_offset.h @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BTHREAD_PRIME_OFFSET_H +#define BTHREAD_PRIME_OFFSET_H + +#include "butil/fast_rand.h" +#include "butil/macros.h" + +namespace bthread { +// Prime number offset for hash function. +inline size_t prime_offset(size_t seed) { + uint32_t offsets[] = { + #include "bthread/offset_inl.list" + }; + return offsets[seed % ARRAY_SIZE(offsets)]; +} + +inline size_t prime_offset() { + return prime_offset(butil::fast_rand()); +} +} + + +#endif // BTHREAD_PRIME_OFFSET_H \ No newline at end of file diff --git a/src/bthread/processor.h b/src/bthread/processor.h new file mode 100644 index 0000000..06e7564 --- /dev/null +++ b/src/bthread/processor.h @@ -0,0 +1,67 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Fri Dec 5 13:40:57 CST 2014 + +#ifndef BTHREAD_PROCESSOR_H +#define BTHREAD_PROCESSOR_H + +#include "butil/build_config.h" + +// Pause instruction to prevent excess processor bus usage, only works in GCC +# ifndef cpu_relax +#if defined(ARCH_CPU_ARM_FAMILY) +# define cpu_relax() asm volatile("yield\n": : :"memory") +#elif defined(ARCH_CPU_RISCV_FAMILY) +// Use the pause hint (Zihintpause extension). Encoding 0x0100000F +// (fence 0, 1) is a HINT on all RISC-V implementations: it never traps +// and is ignored on CPUs without Zihintpause. On CPUs with Zihintpause +// it provides a multi-cycle stall hint that reduces power and improves +// resource fairness during spin-wait loops. Matches the Linux kernel's +// RISC-V cpu_relax() behavior. .word is used instead of .insn or the +// pause mnemonic for maximum assembler compatibility. +# define cpu_relax() asm volatile(".word 0x0100000f\n": : :"memory") +#elif defined(ARCH_CPU_LOONGARCH64_FAMILY) +# define cpu_relax() asm volatile("nop\n": : :"memory"); +#else +# define cpu_relax() asm volatile("pause\n": : :"memory") +#endif +# endif + +// Compile read-write barrier +# ifndef barrier +# define barrier() asm volatile("": : :"memory") +# endif + + +# define BT_LOOP_WHEN(expr, num_spins) \ + do { \ + /*sched_yield may change errno*/ \ + const int saved_errno = errno; \ + for (int cnt = 0, saved_nspin = (num_spins); (expr); ++cnt) { \ + if (cnt < saved_nspin) { \ + cpu_relax(); \ + } else { \ + sched_yield(); \ + } \ + } \ + errno = saved_errno; \ + } while (0) + +#endif // BTHREAD_PROCESSOR_H diff --git a/src/bthread/remote_task_queue.h b/src/bthread/remote_task_queue.h new file mode 100644 index 0000000..ab05bdd --- /dev/null +++ b/src/bthread/remote_task_queue.h @@ -0,0 +1,83 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Sun, 22 Jan 2017 + +#ifndef BTHREAD_REMOTE_TASK_QUEUE_H +#define BTHREAD_REMOTE_TASK_QUEUE_H + +#include "butil/containers/bounded_queue.h" +#include "butil/macros.h" + +namespace bthread { + +class TaskGroup; + +// A queue for storing bthreads created by non-workers. Since non-workers +// randomly choose a TaskGroup to push which distributes the contentions, +// this queue is simply implemented as a queue protected with a lock. +// The function names should be self-explanatory. +class RemoteTaskQueue { +public: + RemoteTaskQueue() {} + + int init(size_t cap) { + const size_t memsize = sizeof(bthread_t) * cap; + void* q_mem = malloc(memsize); + if (q_mem == NULL) { + return -1; + } + butil::BoundedQueue q(q_mem, memsize, butil::OWNS_STORAGE); + _tasks.swap(q); + return 0; + } + + bool pop(bthread_t* task) { + if (_tasks.empty()) { + return false; + } + _mutex.lock(); + const bool result = _tasks.pop(task); + _mutex.unlock(); + return result; + } + + bool push(bthread_t task) { + _mutex.lock(); + const bool res = push_locked(task); + _mutex.unlock(); + return res; + } + + bool push_locked(bthread_t task) { + return _tasks.push(task); + } + + size_t capacity() const { return _tasks.capacity(); } + +private: +friend class TaskGroup; + DISALLOW_COPY_AND_ASSIGN(RemoteTaskQueue); + butil::BoundedQueue _tasks; + butil::Mutex _mutex; +}; + +} // namespace bthread + +#endif // BTHREAD_REMOTE_TASK_QUEUE_H diff --git a/src/bthread/rwlock.cpp b/src/bthread/rwlock.cpp new file mode 100644 index 0000000..e28f5cc --- /dev/null +++ b/src/bthread/rwlock.cpp @@ -0,0 +1,526 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "bvar/collector.h" +#include "butil/memory/scope_guard.h" +#include "bthread/rwlock.h" +#include "bthread/mutex.h" +#include "bthread/butex.h" + +namespace bthread { + +// Defined in bthread/mutex.cpp; reused here so that bthread_rwlock_t +// participates in the global ContentionProfiler just like bthread_mutex_t +// and bthread_sem_t. +class ContentionProfiler; +extern ContentionProfiler* g_cp; +extern bvar::CollectorSpeedLimit g_cp_sl; +extern void submit_contention(const bthread_contention_site_t& csite, int64_t now_ns); + +// Lazily arm sampling on first contention. Caller must declare +// `size_t sampling_range' and `int64_t start_ns' in scope: +// start_ns == 0 -> not yet decided +// start_ns == -1 -> decided NOT to sample (profiler off / not selected) +// start_ns > 0 -> sampling armed; value is the wall-clock start time +#define BTHREAD_RWLOCK_MAYBE_START_SAMPLING \ + do { \ + if (start_ns == 0) { \ + if (BAIDU_UNLIKELY(g_cp != NULL)) { \ + sampling_range = bvar::is_collectable(&g_cp_sl); \ + start_ns = bvar::is_sampling_range_valid(sampling_range) ? \ + butil::cpuwide_time_ns() : -1; \ + } else { \ + start_ns = -1; \ + } \ + } \ + } while (0) + +// Submit one contention sample if sampling was armed for this attempt. +// `start_ns > 0' is the convention used everywhere in this file to indicate +// that BTHREAD_RWLOCK_MAYBE_START_SAMPLING actually decided to sample. +// No-op otherwise. Force-inlined so the uncontended fast path stays cheap. +static BUTIL_FORCE_INLINE void submit_contention_if_sampled( + int64_t start_ns, size_t sampling_range) { + if (BAIDU_UNLIKELY(start_ns > 0)) { + const int64_t end_ns = butil::cpuwide_time_ns(); + const bthread_contention_site_t csite{end_ns - start_ns, sampling_range}; + submit_contention(csite, end_ns); + } +} + +// bthread RWLock +// writer-priority implementation overview +// Three synchronization fields are used: +// +// * `lock_word' (32-bit butex): +// bit 31 : 1 if the write lock is held, 0 otherwise. +// bit 0~30: number of readers currently holding the read lock. +// Mutually exclusive: when bit 31 is set, the lower 31 bits are 0. +// +// * `writer_wait_count' (32-bit butex): +// Number of writers that have entered wrlock() but not yet finished +// (i.e. currently waiting for the mutex / waiting for lock_word==0 / +// holding the write lock). Each writer accounts for itself: it is +// incremented at the very beginning of wrlock() and decremented at +// the very end of unwrlock()/cleanup(). +// Readers consult this field to implement writer-priority: if any +// writer is "in flight", new readers yield by waiting on it. +// +// * `writer_queue_mutex' (bthread_mutex_t): +// Serializes writers so that at most one writer races for `lock_word' +// at any time. Other writers queue up on this mutex. +// +// Wakeup channels: +// * Readers waiting on writers -> wait on writer_wait_count, woken by unwrlock/cleanup +// * Writers waiting on readers -> wait on lock_word, woken by unrdlock +// * Writers waiting on writers -> wait on writer_queue_mutex + +static int rwlock_rdlock(bthread_rwlock_t* rwlock, bool try_lock, + const struct timespec* abstime) { + auto lock_word = (butil::atomic*)rwlock->lock_word; + auto writer_wait_count = (butil::atomic*)rwlock->writer_wait_count; + + // Sampling state for the contention profiler (lazily armed on first + // contention so that the uncontended fast path stays cheap): + // start_ns == 0 -> not yet decided + // start_ns == -1 -> decided NOT to sample + // start_ns > 0 -> sampling armed; submit on exit + // Each reader samples independently and submits once on its own way out; + // we deliberately do NOT use rwlock->writer_csite here because that field + // is exclusively owned by the writer. + size_t sampling_range = bvar::INVALID_SAMPLING_RANGE; + int64_t start_ns = 0; + int rc = 0; + + while (true) { + // Writer-priority: if any writer is in flight, yield to it. + // `relaxed' is sufficient here because: + // - There is no data published via writer_wait_count; + // data visibility is established via the acquire-CAS on + // `lock_word' below paired with the release-CAS in unwrlock(). + // - butex_wait() will re-check the expected value before sleeping, + // so we cannot lose a wakeup even if `w' is slightly stale. + unsigned w = writer_wait_count->load(butil::memory_order_relaxed); + if (w > 0) { + if (try_lock) { + // Don't sample tryrdlock failures: they are by design a + // non-blocking probe, not a contention event. + return EBUSY; + } + // We are about to block on writer_wait_count; arm sampling + // before parking so the wait time is included in the report. + BTHREAD_RWLOCK_MAYBE_START_SAMPLING; + if (butex_wait(writer_wait_count, w, abstime) < 0 && + errno != EWOULDBLOCK && errno != EINTR) { + rc = errno; + break; + } + continue; + } + + // No writer in flight: try to add ourselves to the reader count. + // 2^31 - 1 readers should be enough for any realistic workload. + unsigned l = lock_word->load(butil::memory_order_relaxed); + if ((l >> 31) == 0) { + // Refuse to increment when the reader count has saturated + // the low 31 bits. Otherwise `l + 1' would flip bit 31 and + // we would corrupt lock_word into "writer held" state. + // POSIX-style: report EAGAIN ("max read locks exceeded"). + if (BAIDU_UNLIKELY(l == 0x7FFFFFFFu)) { + LOG(ERROR) << "Too many readers on bthread_rwlock_t=" << rwlock; + rc = EAGAIN; + break; + } + // Acquire on success synchronizes-with the release-CAS in + // unwrlock(), so any data written by the previous writer is + // visible to us before we start reading. + if (lock_word->compare_exchange_weak(l, l + 1, + butil::memory_order_acquire, + butil::memory_order_relaxed)) { + rc = 0; + break; + } + // CAS failed (likely another reader bumped r): retry. + } else if (try_lock) { + // Write lock is currently held. + return EBUSY; + } else { + // Write lock currently held but not yet self-accounted as a + // pending writer (very narrow window inside wrlock). Arm + // sampling now so the spin/wait until writer_wait_count >= 1 + // is also accounted for. + BTHREAD_RWLOCK_MAYBE_START_SAMPLING; + } + // Otherwise (write lock held but not try_lock): spin once more. + // The next iteration will observe writer_wait_count >= 1 (writers + // self-account in writer_wait_count for the entire wrlock lifetime), + // and we will block on it instead of busy spinning. + } + + // Submit one contention sample for this reader (success or failure). + submit_contention_if_sampled(start_ns, sampling_range); + return rc; +} + +static int rwlock_unrdlock(bthread_rwlock_t* rwlock) { + auto lock_word = (butil::atomic*)rwlock->lock_word; + while (true) { + unsigned l = lock_word->load(butil::memory_order_relaxed); + // Misuse detection: the caller must currently hold a read lock. + // l == 0 -> no lock is held (double unlock?) + // (l >> 31) != 0 -> write lock is held, not read lock + if (l == 0 || (l >> 31) != 0) { + LOG(ERROR) << "Invalid unrdlock on bthread_rwlock_t=" << rwlock + << ", lock_word=" << l; + return EINVAL; + } + // Release on success publishes any reads/writes done while holding + // the read lock to the next acquirer (typically a writer's + // acquire-CAS in wrlock()). + if(!(lock_word->compare_exchange_weak(l, l - 1, + butil::memory_order_release, + butil::memory_order_relaxed))) { + continue; + } + // We were the last reader (lock_word transitioned 1 -> 0). Wake the + // single writer (if any) that may be sleeping on `lock_word' inside + // wrlock(). At most one writer can be there because writers are + // serialized by writer_queue_mutex. + // No-op if nobody is waiting; butex_wake() short-circuits cheaply. + if (l == 1) { + butex_wake(lock_word); + } + return 0; + } +} + +// Roll back the side effects of a failed wrlock attempt: +// - Release writer_queue_mutex if we managed to acquire it. +// - Decrement our share of writer_wait_count. +// - If we were the last in-flight writer, wake all readers that have +// been parked by writer-priority (w == 1 means writer_wait_count is now 0). +// Called on EBUSY (try_lock failed), ETIMEDOUT, EINTR-leading-to-fail. +static BUTIL_FORCE_INLINE void rwlock_wrlock_cleanup(bthread_rwlock_t* rwlock, bool write_queue_locked) { + if (write_queue_locked) { + bthread_mutex_unlock(&rwlock->writer_queue_mutex); + } + auto writer_wait_count = (butil::atomic*)rwlock->writer_wait_count; + // Withdraw our writer-priority "vote" so readers can make progress. + auto w = writer_wait_count->fetch_sub(1, butil::memory_order_relaxed); + // w is the value BEFORE the subtraction, so w == 1 means we were the + // last writer in flight; wake every reader parked on writer_wait_count. + if (w == 1) { + butex_wake_all(writer_wait_count); + } +} + +static int rwlock_wrlock(bthread_rwlock_t* rwlock, bool try_lock, + const struct timespec* abstime) { + auto writer_wait_count = (butil::atomic*)rwlock->writer_wait_count; + // Step 1: announce ourselves before doing anything else, so that + // concurrent readers immediately observe writer-priority and back off. + // This MUST happen before we try to acquire writer_queue_mutex, + // otherwise a flood of readers could starve us indefinitely. + // 2^31 in-flight writers should be enough for any realistic workload. + writer_wait_count->fetch_add(1, butil::memory_order_relaxed); + + // Sampling state for the contention profiler. Both wrlock() and + // unwrlock() sample independently: wrlock() submits its own wait time + // on the way out (success or failure); unwrlock() samples its own + // CAS-spin / mutex_unlock / butex_wake_all latency separately. We do + // NOT use rwlock->writer_csite here -- the two operations are not + // forced to share a single sample. + size_t sampling_range = bvar::INVALID_SAMPLING_RANGE; + int64_t start_ns = 0; + + // Step 2: serialize with other writers. At most one writer holds + // `writer_queue_mutex' at a time and races for `lock_word'. + int rc = bthread_mutex_trylock(&rwlock->writer_queue_mutex); + if (0 != rc) { + if (try_lock) { + // Fail to acquire the wrlock. Don't sample trywrlock failures: + // they are by design a non-blocking probe, not a contention event. + rwlock_wrlock_cleanup(rwlock, false); + return rc; + } + // We are about to block on writer_queue_mutex; arm sampling. + // Note: the inner mutex itself has csite disabled (see init), so + // its blocking time is only counted once -- here, by the rwlock. + BTHREAD_RWLOCK_MAYBE_START_SAMPLING; + rc = bthread_mutex_timedlock(&rwlock->writer_queue_mutex, abstime); + if (0 != rc) { + // Fail to acquire the wrlock. Submit the elapsed wait time + // directly (no unwrlock() will run for this writer). + submit_contention_if_sampled(start_ns, sampling_range); + rwlock_wrlock_cleanup(rwlock, false); + return rc; + } + } + + // Step 3: with `writer_queue_mutex' held, wait for all readers to drain + // and then claim the write bit of `lock_word'. + auto lock_word = (butil::atomic*)rwlock->lock_word; + while (true) { + unsigned l = lock_word->load(butil::memory_order_relaxed); + if (l != 0) { + // Readers still hold the lock. Park on `lock_word' until the last + // reader releases (unrdlock will butex_wake on transition 1->0). + if (try_lock) { + errno = EBUSY; + break; + } + // Arm sampling before parking so the wait-for-readers time is + // counted (in case the queue_mutex acquisition above was uncontended). + BTHREAD_RWLOCK_MAYBE_START_SAMPLING; + // Use the freshly read `r' as expected; if lock_word changes + // before we sleep, butex_wait returns EWOULDBLOCK and we retry. + if (butex_wait(lock_word, l, abstime) < 0 && + errno != EWOULDBLOCK && errno != EINTR) { + break; + } + continue; + } + // Acquire on success synchronizes-with release-CAS in + // unrdlock()/unwrlock(): we will see all data published by the + // previous reader/writer before we start writing. + if (lock_word->compare_exchange_weak(l, (unsigned)(1 << 31), + butil::memory_order_acquire, + butil::memory_order_relaxed)) { + // Submit the writer's wait sample immediately on success. + // unwrlock() will sample its own latency separately. + submit_contention_if_sampled(start_ns, sampling_range); + return 0; + } + // CAS may spuriously fail (weak); retry without sleeping. + } + + // Failure path: snapshot errno before cleanup, because + // bthread_mutex_unlock / butex_wake_all inside cleanup may invoke + // syscalls or yield and clobber errno on this thread. + int saved_errno = errno; + // Submit the elapsed wait directly; we never reached unwrlock(). + submit_contention_if_sampled(start_ns, sampling_range); + rwlock_wrlock_cleanup(rwlock, true); + return saved_errno; +} + +static int rwlock_unwrlock(bthread_rwlock_t* rwlock) { + auto lock_word = (butil::atomic*)rwlock->lock_word; + auto writer_wait_count = (butil::atomic*)rwlock->writer_wait_count; + + // Sampling state for the contention profiler. unwrlock() samples + // independently of wrlock(): although the release-CAS itself cannot + // fail due to writer-writer contention (writers are serialized by + // writer_queue_mutex), the body still does mutex_unlock(), + // butex_wake_all() and may spuriously spin on the weak CAS, all of + // which contribute to the critical-section tail latency. + size_t sampling_range = bvar::INVALID_SAMPLING_RANGE; + int64_t start_ns = 0; + BTHREAD_RWLOCK_MAYBE_START_SAMPLING; + + while (true) { + unsigned l = lock_word->load(butil::memory_order_relaxed); + // Misuse detection: we must currently hold the write lock. + if (BAIDU_UNLIKELY(l != (unsigned)(1 << 31))) { + LOG(ERROR) << "Invalid unwrlock!"; + return EINVAL; + } + // Release-CAS publishes all writes performed under the write lock + // to the next acquirer (a reader's acquire-CAS or another writer's + // acquire-CAS). The CAS itself cannot fail due to contention since + // writers are serialized by writer_queue_mutex; weak failure here is + // only a spurious CAS failure -- just retry. + if (!lock_word->compare_exchange_weak(l, 0, + butil::memory_order_release, + butil::memory_order_relaxed)) { + continue; + } + + // ---- Order of the next two operations is INTENTIONAL ---- + // + // We deliberately: + // (1) unlock writer_queue_mutex FIRST, then + // (2) fetch_sub(writer_wait_count) and conditionally wake readers. + // + // Rationale (writer-priority semantics): + // * Any writer queued on writer_queue_mutex has already + // fetch_add'ed its share into writer_wait_count back in wrlock() + // (before it even tried to lock the mutex). So when it wakes + // up here and we later fetch_sub, the counter still reflects + // "there is at least one more writer in flight": w_old >= 2, + // which means w != 1, which means we will NOT wake readers. + // Readers must keep yielding to the next writer -- exactly the + // writer-priority invariant. + // * Only when we are truly the last writer in flight (w_old == 1 + // after our fetch_sub, i.e. writer_wait_count is now 0) do we + // wake_all readers parked on writer_wait_count. + // + // Subtle but harmless effect: + // Between (1) and (2) there is a small window in which our + // own "ghost share" is still counted in writer_wait_count even though + // we have effectively left. New readers entering rdlock() during + // this window will see writer_wait_count >= 1 and park on it; they + // will be woken either by step (2) below (if no successor writer + // appeared) or by the successor writer's eventual unwrlock. + // No wakeup is ever lost: butex_wait re-checks the expected + // value before truly sleeping, and any successor writer will + // itself execute this same wake logic on its way out. + // + // Reversing the order (fetch_sub before unlock mutex) would break + // strict writer-priority because woken readers could grab the + // read lock before a successor writer queued on the mutex even + // gets a chance to CAS lock_word. + bthread_mutex_unlock(&rwlock->writer_queue_mutex); + unsigned w = writer_wait_count->fetch_sub(1, butil::memory_order_relaxed); + if (w == 1) { + butex_wake_all(writer_wait_count); + } + + // Submit our own unwrlock-side sample (CAS spin + mutex_unlock + + // butex_wake_all). This is independent of the wrlock-side sample. + submit_contention_if_sampled(start_ns, sampling_range); + return 0; + } +} + +// Generic unlock entry that dispatches to unwrlock/unrdlock by inspecting +// `lock_word'. This is safe ONLY because the caller must already hold one of +// the two locks: while holding a read lock the high bit of `lock_word' cannot +// flip on, and while holding the write lock the low bits cannot be set. +// Therefore a relaxed load is sufficient to make the dispatch decision. +static int rwlock_unlock(bthread_rwlock_t* rwlock) { + auto lock_word = (butil::atomic*)rwlock->lock_word; + unsigned r = lock_word->load(butil::memory_order_relaxed); + if ((r >> 31) != 0) { + return rwlock_unwrlock(rwlock); + } else { + return rwlock_unrdlock(rwlock); + } +} + +// Deleter that turns butex_create_checked()'s raw pointer into something +// std::unique_ptr can clean up automatically. Using RAII here lets the +// init-error paths just `return rc' without manually unwinding partial +// allocations; ownership is `release()'d only on the all-success path. +struct ButexDeleter { + void operator()(void* butex) const { + if (butex != NULL) { + butex_destroy(butex); + } + } +}; + +static int rwlock_init(bthread_rwlock_t* rwlock) { + std::unique_ptr writer_wait_count( + butex_create_checked()); + if (writer_wait_count == NULL) { + LOG(ERROR) << "Fail to create writer_wait_count butex: out of memory"; + return ENOMEM; + } + std::unique_ptr lock_word(butex_create_checked()); + if (lock_word == NULL) { + LOG(ERROR) << "Fail to create lock_word butex: out of memory"; + return ENOMEM; + } + *writer_wait_count = 0; + *lock_word = 0; + + bthread_mutexattr_t attr; + bthread_mutexattr_init(&attr); + BRPC_SCOPE_EXIT { bthread_mutexattr_destroy(&attr); }; + // Disable csite on the inner queue mutex so the writer's wait time is + // accounted exactly once -- by the rwlock layer, not double-counted via + // the inner mutex. + bthread_mutexattr_disable_csite(&attr); + const int rc = bthread_mutex_init(&rwlock->writer_queue_mutex, &attr); + if (rc != 0) { + LOG(ERROR) << "Fail to init writer_queue_mutex, rc=" << rc; + return rc; + } + + // All resources successfully created; transfer butex ownership to + // rwlock. From here on, bthread_rwlock_destroy() is responsible for + // releasing them. + rwlock->writer_wait_count = writer_wait_count.release(); + rwlock->lock_word = lock_word.release(); + return 0; +} + +static int rwlock_destroy(bthread_rwlock_t* rwlock) { + // Destroy the inner mutex first; bthread_mutex_init() allocates an + // internal butex which would otherwise leak. Pointers are nulled to + // surface accidental double-destroy / use-after-destroy bugs early. + int rc = bthread_mutex_destroy(&rwlock->writer_queue_mutex); + if (rc != 0) { + LOG(ERROR) << "Fail to destroy writer_queue_mutex, rc=" << rc; + } + if (rwlock->writer_wait_count != NULL) { + butex_destroy(rwlock->writer_wait_count); + rwlock->writer_wait_count = NULL; + } + if (rwlock->lock_word != NULL) { + butex_destroy(rwlock->lock_word); + rwlock->lock_word = NULL; + } + return rc; +} + +} // namespace bthread + +__BEGIN_DECLS + +int bthread_rwlock_init(bthread_rwlock_t* __restrict rwlock, + const bthread_rwlockattr_t* __restrict) { + return bthread::rwlock_init(rwlock); +} + +int bthread_rwlock_destroy(bthread_rwlock_t* rwlock) { + return bthread::rwlock_destroy(rwlock); +} + +int bthread_rwlock_rdlock(bthread_rwlock_t* rwlock) { + return bthread::rwlock_rdlock(rwlock, false, NULL); +} + +int bthread_rwlock_tryrdlock(bthread_rwlock_t* rwlock) { + return bthread::rwlock_rdlock(rwlock, true, NULL); +} + +int bthread_rwlock_timedrdlock(bthread_rwlock_t* __restrict rwlock, + const struct timespec* __restrict abstime) { + return bthread::rwlock_rdlock(rwlock, false, abstime); +} + +int bthread_rwlock_wrlock(bthread_rwlock_t* rwlock) { + return bthread::rwlock_wrlock(rwlock, false, NULL); +} + +int bthread_rwlock_trywrlock(bthread_rwlock_t* rwlock) { + return bthread::rwlock_wrlock(rwlock, true, NULL); +} + +int bthread_rwlock_timedwrlock(bthread_rwlock_t* __restrict rwlock, + const struct timespec* __restrict abstime) { + return bthread::rwlock_wrlock(rwlock, false, abstime); +} + +int bthread_rwlock_unlock(bthread_rwlock_t* rwlock) { + return bthread::rwlock_unlock(rwlock); +} + +__END_DECLS diff --git a/src/bthread/rwlock.h b/src/bthread/rwlock.h new file mode 100644 index 0000000..295e643 --- /dev/null +++ b/src/bthread/rwlock.h @@ -0,0 +1,219 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BTHREAD_RWLOCK_H +#define BTHREAD_RWLOCK_H + +#include "bthread/types.h" +#include "bthread/bthread.h" +#include "butil/scoped_lock.h" + +namespace bthread { + +// The C++ Wrapper of bthread_rwlock + +// NOTE: Not aligned to cacheline as the container of RWLock is practically aligned. + +class RWLock { +public: + typedef bthread_rwlock_t* native_handler_type; + + RWLock() { + int rc = bthread_rwlock_init(&_rwlock, NULL); + if (rc) { + throw std::system_error(std::error_code(rc, std::system_category()), + "RWLock constructor failed"); + } + } + + ~RWLock() { + CHECK_EQ(0, bthread_rwlock_destroy(&_rwlock)); + } + + DISALLOW_COPY_AND_ASSIGN(RWLock); + + native_handler_type native_handler() { return &_rwlock; } + + void rdlock() { + int rc = bthread_rwlock_rdlock(&_rwlock); + if (rc) { + throw std::system_error(std::error_code(rc, std::system_category()), + "RWLock rdlock failed"); + } + } + + bool try_rdlock() { + return 0 == bthread_rwlock_tryrdlock(&_rwlock); + } + + bool timed_rdlock(const struct timespec* abstime) { + return 0 == bthread_rwlock_timedrdlock(&_rwlock, abstime); + } + + void wrlock() { + int rc = bthread_rwlock_wrlock(&_rwlock); + if (rc) { + throw std::system_error(std::error_code(rc, std::system_category()), + "RWLock wrlock failed"); + } + } + + bool try_wrlock() { + return 0 == bthread_rwlock_trywrlock(&_rwlock); + } + + bool timed_wrlock(const struct timespec* abstime) { + return 0 == bthread_rwlock_timedwrlock(&_rwlock, abstime); + } + + void unlock() { bthread_rwlock_unlock(&_rwlock); } + +private: + bthread_rwlock_t _rwlock{}; +}; + +// Read lock guard of rwlock. +class RWLockRdGuard { +public: + explicit RWLockRdGuard(bthread_rwlock_t& rwlock) + : _rwlock(&rwlock) { +#if !defined(NDEBUG) + const int rc = bthread_rwlock_rdlock(_rwlock); + if (rc) { + LOG(FATAL) << "Fail to rdlock bthread_rwlock_t=" << _rwlock << ", " << berror(rc); + _rwlock = NULL; + } +#else + bthread_rwlock_rdlock(_rwlock); +#endif // NDEBUG + } + + explicit RWLockRdGuard(RWLock& rwlock) + : RWLockRdGuard(*rwlock.native_handler()) {} + + ~RWLockRdGuard() { +#ifndef NDEBUG + if (NULL != _rwlock) { + bthread_rwlock_unlock(_rwlock); + } +#else + bthread_rwlock_unlock(_rwlock); +#endif // NDEBUG + } + + DISALLOW_COPY_AND_ASSIGN(RWLockRdGuard); + +private: + bthread_rwlock_t* _rwlock; +}; + +// Write lock guard of rwlock. +class RWLockWrGuard { +public: + explicit RWLockWrGuard(bthread_rwlock_t& rwlock) + : _rwlock(&rwlock) { +#if !defined(NDEBUG) + const int rc = bthread_rwlock_wrlock(_rwlock); + if (rc) { + LOG(FATAL) << "Fail to wrlock bthread_rwlock_t=" << _rwlock << ", " << berror(rc); + _rwlock = NULL; + } +#else + bthread_rwlock_wrlock(_rwlock); +#endif // NDEBUG + } + + explicit RWLockWrGuard(RWLock& rwlock) + : RWLockWrGuard(*rwlock.native_handler()) {} + + ~RWLockWrGuard() { +#ifndef NDEBUG + if (NULL != _rwlock) { + bthread_rwlock_unlock(_rwlock); + } +#else + bthread_rwlock_unlock(_rwlock); +#endif // NDEBUG + } + + DISALLOW_COPY_AND_ASSIGN(RWLockWrGuard); + +private: + bthread_rwlock_t* _rwlock; +}; + +} // namespace bthread + +namespace std { + +template <> +class lock_guard { +public: + lock_guard(bthread_rwlock_t& rwlock, bool read) + :_rwlock(&rwlock), _read(read) { +#if !defined(NDEBUG) + int rc; + if (_read) { + rc = bthread_rwlock_rdlock(_rwlock); + } else { + rc = bthread_rwlock_wrlock(_rwlock); + } + if (rc) { + LOG(FATAL) << "Fail to lock bthread_rwlock_t=" << _rwlock << ", " << berror(rc); + _rwlock = NULL; + } +#else + if (_read) { + bthread_rwlock_rdlock(_rwlock); + } else { + bthread_rwlock_wrlock(_rwlock); + } +#endif // NDEBUG + } + + ~lock_guard() { +#ifndef NDEBUG + if (NULL != _rwlock) { + bthread_rwlock_unlock(_rwlock); + } +#else + bthread_rwlock_unlock(_rwlock); +#endif // NDEBUG + } + + DISALLOW_COPY_AND_ASSIGN(lock_guard); + +private: + bthread_rwlock_t* _rwlock; + bool _read; +}; + +template <> +class lock_guard { +public: + lock_guard(bthread::RWLock& rwlock, bool read) + :_rwlock_guard(*rwlock.native_handler(), read) {} + + DISALLOW_COPY_AND_ASSIGN(lock_guard); + +private: + std::lock_guard _rwlock_guard; +}; + +} // namespace std + +#endif //BTHREAD_RWLOCK_H diff --git a/src/bthread/semaphore.cpp b/src/bthread/semaphore.cpp new file mode 100644 index 0000000..3813a8a --- /dev/null +++ b/src/bthread/semaphore.cpp @@ -0,0 +1,173 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "butil/memory/scope_guard.h" +#include "bvar/collector.h" +#include "bthread/bthread.h" +#include "bthread/butex.h" + +namespace bthread { + +// Define in bthread/mutex.cpp +class ContentionProfiler; +extern ContentionProfiler* g_cp; +extern bvar::CollectorSpeedLimit g_cp_sl; +extern bool is_contention_site_valid(const bthread_contention_site_t& cs); +extern void make_contention_site_invalid(bthread_contention_site_t* cs); +extern void submit_contention(const bthread_contention_site_t& csite, int64_t now_ns); + +static inline int bthread_sem_trywait(bthread_sem_t* sema) { + auto whole = (butil::atomic*)sema->butex; + while (true) { + unsigned num = whole->load(butil::memory_order_relaxed); + if (num == 0) { + return EAGAIN; + } + if (whole->compare_exchange_weak(num, num - 1, + butil::memory_order_acquire, + butil::memory_order_relaxed)) { + return 0; + } + } + +} + +static int bthread_sem_wait_impl(bthread_sem_t* sem, const struct timespec* abstime) { + bool queue_lifo = false; + bool first_wait = true; + size_t sampling_range = bvar::INVALID_SAMPLING_RANGE; + // -1: don't sample. + // 0: default value. + // > 0: Start time of sampling. + int64_t start_ns = 0; + auto whole = (butil::atomic*)sem->butex; + while (true) { + unsigned num = whole->load(butil::memory_order_relaxed); + if (num > 0) { + if (whole->compare_exchange_weak(num, num - 1, + butil::memory_order_acquire, + butil::memory_order_relaxed)) { + if (start_ns > 0) { + const int64_t end_ns = butil::cpuwide_time_ns(); + const bthread_contention_site_t csite{end_ns - start_ns, sampling_range}; + bthread::submit_contention(csite, end_ns); + } + + return 0; + } + } + // Don't sample when contention profiler is off. + if (NULL != bthread::g_cp && start_ns == 0 && sem->enable_csite && + !bvar::is_sampling_range_valid(sampling_range)) { + // Ask Collector if this (contended) sem waiting should be sampled. + sampling_range = bvar::is_collectable(&bthread::g_cp_sl); + start_ns = bvar::is_sampling_range_valid(sampling_range) ? + butil::cpuwide_time_ns() : -1; + } else { + start_ns = -1; + } + if (bthread::butex_wait(sem->butex, 0, abstime, queue_lifo) < 0 && + errno != EWOULDBLOCK && errno != EINTR) { + // A sema should ignore interruptions in general since + // user code is unlikely to check the return value. + if (ETIMEDOUT == errno && start_ns > 0) { + // Failed to lock due to ETIMEDOUT, submit the elapse directly. + const int64_t end_ns = butil::cpuwide_time_ns(); + const bthread_contention_site_t csite{end_ns - start_ns, sampling_range}; + bthread::submit_contention(csite, end_ns); + } + + return errno; + } + // Ignore EWOULDBLOCK and EINTR. + if (first_wait && 0 == errno) { + first_wait = false; + } + if (!first_wait) { + // Normally, bthreads are queued in FIFO order. But competing with new + // arriving bthreads over sema, a woken up bthread has good chances of + // losing. Because new arriving bthreads are already running on CPU and + // there can be lots of them. In such case, for fairness, to avoid + // starvation, it is queued at the head of the waiter queue. + queue_lifo = true; + } + } +} + +static inline int bthread_sem_post(bthread_sem_t* sem, size_t num) { + if (num > 0) { + unsigned n = ((butil::atomic*)sem->butex) + ->fetch_add(num, butil::memory_order_relaxed); + const size_t sampling_range = NULL != bthread::g_cp && sem->enable_csite ? + bvar::is_collectable(&bthread::g_cp_sl) : bvar::INVALID_SAMPLING_RANGE; + const int64_t start_ns = bvar::is_sampling_range_valid(sampling_range) ? + butil::cpuwide_time_ns() : -1; + bthread::butex_wake_n(sem->butex, n); + if (start_ns > 0) { + const int64_t end_ns = butil::cpuwide_time_ns(); + const bthread_contention_site_t csite{end_ns - start_ns, sampling_range}; + bthread::submit_contention(csite, end_ns); + } + } + return 0; +} + +} // namespace bthread + +__BEGIN_DECLS + +int bthread_sem_init(bthread_sem_t* sem, unsigned value) { + sem->butex = bthread::butex_create_checked(); + if (!sem->butex) { + return ENOMEM; + } + *sem->butex = value; + sem->enable_csite = true; + return 0; +} + +int bthread_sem_disable_csite(bthread_sem_t* sema) { + sema->enable_csite = false; + return 0; +} + +int bthread_sem_destroy(bthread_sem_t* semaphore) { + bthread::butex_destroy(semaphore->butex); + return 0; +} + +int bthread_sem_trywait(bthread_sem_t* sem) { + return bthread::bthread_sem_trywait(sem); +} + +int bthread_sem_wait(bthread_sem_t* sem) { + return bthread::bthread_sem_wait_impl(sem, NULL); +} + +int bthread_sem_timedwait(bthread_sem_t* sem, const struct timespec* abstime) { + return bthread::bthread_sem_wait_impl(sem, abstime); +} + +int bthread_sem_post(bthread_sem_t* sem) { + return bthread::bthread_sem_post(sem, 1); +} + +int bthread_sem_post_n(bthread_sem_t* sem, size_t n) { + return bthread::bthread_sem_post(sem, n); +} + +__END_DECLS \ No newline at end of file diff --git a/src/bthread/singleton_on_bthread_once.h b/src/bthread/singleton_on_bthread_once.h new file mode 100644 index 0000000..9ea507d --- /dev/null +++ b/src/bthread/singleton_on_bthread_once.h @@ -0,0 +1,61 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_SINGLETON_ON_BTHREAD_ONCE_H +#define BRPC_SINGLETON_ON_BTHREAD_ONCE_H + +#include "bthread/bthread.h" + +namespace bthread { + +template +class GetLeakySingleton { +public: + static T* _instance; + static bthread_once_t* g_create_leaky_singleton_once; + static void create_leaky_singleton(); +}; + +template +T* GetLeakySingleton::_instance = NULL; + +template +bthread_once_t* GetLeakySingleton::g_create_leaky_singleton_once + = new bthread_once_t; + +template +void GetLeakySingleton::create_leaky_singleton() { + _instance = new T; +} + +// To get a never-deleted singleton of a type T, just call +// bthread::get_leaky_singleton(). Most daemon (b)threads +// or objects that need to be always-on can be created by +// this function. This function can be called safely not only +// before main() w/o initialization issues of global variables, +// but also on bthread with hanging operation. +template +inline T* get_leaky_singleton() { + using LeakySingleton = GetLeakySingleton; + bthread_once(LeakySingleton::g_create_leaky_singleton_once, + LeakySingleton::create_leaky_singleton); + return LeakySingleton::_instance; +} + +} // namespace bthread + +#endif // BRPC_SINGLETON_ON_BTHREAD_ONCE_H diff --git a/src/bthread/stack.cpp b/src/bthread/stack.cpp new file mode 100644 index 0000000..71daf9d --- /dev/null +++ b/src/bthread/stack.cpp @@ -0,0 +1,153 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Sun Sep 7 22:37:39 CST 2014 + +#include // getpagesize +#include // mmap, munmap, mprotect +#include // std::max +#include // posix_memalign +#include "butil/macros.h" // BAIDU_CASSERT +#include "butil/memory/singleton_on_pthread_once.h" +#include "butil/third_party/dynamic_annotations/dynamic_annotations.h" // RunningOnValgrind +#include "butil/third_party/valgrind/valgrind.h" // VALGRIND_STACK_REGISTER +#include "bvar/passive_status.h" +#include "bthread/types.h" // BTHREAD_STACKTYPE_* +#include "bthread/stack.h" + +DEFINE_int32(stack_size_small, 32768, "size of small stacks"); +DEFINE_int32(stack_size_normal, 1048576, "size of normal stacks"); +DEFINE_int32(stack_size_large, 8388608, "size of large stacks"); +DEFINE_int32(guard_page_size, 4096, "size of guard page, allocate stacks by malloc if it's 0(not recommended)"); +DEFINE_int32(tc_stack_small, 32, "maximum small stacks cached by each thread"); +DEFINE_int32(tc_stack_normal, 8, "maximum normal stacks cached by each thread"); + +namespace bthread { + +BAIDU_CASSERT(BTHREAD_STACKTYPE_PTHREAD == STACK_TYPE_PTHREAD, must_match); +BAIDU_CASSERT(BTHREAD_STACKTYPE_SMALL == STACK_TYPE_SMALL, must_match); +BAIDU_CASSERT(BTHREAD_STACKTYPE_NORMAL == STACK_TYPE_NORMAL, must_match); +BAIDU_CASSERT(BTHREAD_STACKTYPE_LARGE == STACK_TYPE_LARGE, must_match); +BAIDU_CASSERT(STACK_TYPE_MAIN == 0, must_be_0); + +static butil::static_atomic s_stack_count = BUTIL_STATIC_ATOMIC_INIT(0); +static int64_t get_stack_count(void*) { + return s_stack_count.load(butil::memory_order_relaxed); +} +static bvar::PassiveStatus bvar_stack_count( + "bthread_stack_count", get_stack_count, NULL); + +int allocate_stack_storage(StackStorage* s, int stacksize_in, int guardsize_in) { + const static int PAGESIZE = getpagesize(); + const int PAGESIZE_M1 = PAGESIZE - 1; + const int MIN_STACKSIZE = PAGESIZE * 2; + const int MIN_GUARDSIZE = PAGESIZE; + + // Align stacksize + const int stacksize = + (std::max(stacksize_in, MIN_STACKSIZE) + PAGESIZE_M1) & + ~PAGESIZE_M1; + + if (guardsize_in <= 0) { + void* mem = malloc(stacksize); + if (NULL == mem) { + PLOG_EVERY_SECOND(ERROR) << "Fail to malloc (size=" + << stacksize << ")"; + return -1; + } + s_stack_count.fetch_add(1, butil::memory_order_relaxed); + s->bottom = (char*)mem + stacksize; + s->stacksize = stacksize; + s->guardsize = 0; + if (RunningOnValgrind()) { + s->valgrind_stack_id = VALGRIND_STACK_REGISTER( + s->bottom, (char*)s->bottom - stacksize); + } else { + s->valgrind_stack_id = 0; + } + return 0; + } else { + // Align guardsize + const int guardsize = + (std::max(guardsize_in, MIN_GUARDSIZE) + PAGESIZE_M1) & + ~PAGESIZE_M1; + + const int memsize = stacksize + guardsize; + void* const mem = mmap(NULL, memsize, (PROT_READ | PROT_WRITE), + (MAP_PRIVATE | MAP_ANONYMOUS), -1, 0); + + if (MAP_FAILED == mem) { + PLOG_EVERY_SECOND(ERROR) + << "Fail to mmap size=" << memsize << " stack_count=" + << s_stack_count.load(butil::memory_order_relaxed) + << ", possibly limited by /proc/sys/vm/max_map_count"; + // may fail due to limit of max_map_count (65536 in default) + return -1; + } + + void* aligned_mem = (void*)(((intptr_t)mem + PAGESIZE_M1) & ~PAGESIZE_M1); + if (aligned_mem != mem) { + LOG_ONCE(ERROR) << "addr=" << mem << " returned by mmap is not " + "aligned by pagesize=" << PAGESIZE; + } + const int offset = (char*)aligned_mem - (char*)mem; + if (guardsize <= offset || + mprotect(aligned_mem, guardsize - offset, PROT_NONE) != 0) { + munmap(mem, memsize); + PLOG_EVERY_SECOND(ERROR) + << "Fail to mprotect " << (void*)aligned_mem << " length=" + << guardsize - offset; + return -1; + } + + s_stack_count.fetch_add(1, butil::memory_order_relaxed); + s->bottom = (char*)mem + memsize; + s->stacksize = stacksize; + s->guardsize = guardsize; + if (RunningOnValgrind()) { + s->valgrind_stack_id = VALGRIND_STACK_REGISTER( + s->bottom, (char*)s->bottom - stacksize); + } else { + s->valgrind_stack_id = 0; + } + return 0; + } +} + +void deallocate_stack_storage(StackStorage* s) { + if (RunningOnValgrind()) { + VALGRIND_STACK_DEREGISTER(s->valgrind_stack_id); + } + const int memsize = s->stacksize + s->guardsize; + if ((uintptr_t)s->bottom <= (uintptr_t)memsize) { + return; + } + s_stack_count.fetch_sub(1, butil::memory_order_relaxed); + if (s->guardsize == 0) { + free((char*)s->bottom - memsize); + } else { + munmap((char*)s->bottom - memsize, memsize); + } +} + +int* SmallStackClass::stack_size_flag = &FLAGS_stack_size_small; +int* NormalStackClass::stack_size_flag = &FLAGS_stack_size_normal; +int* LargeStackClass::stack_size_flag = &FLAGS_stack_size_large; + +} // namespace bthread diff --git a/src/bthread/stack.h b/src/bthread/stack.h new file mode 100644 index 0000000..91d1df6 --- /dev/null +++ b/src/bthread/stack.h @@ -0,0 +1,84 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Sun Sep 7 22:37:39 CST 2014 + +#ifndef BTHREAD_ALLOCATE_STACK_H +#define BTHREAD_ALLOCATE_STACK_H + +#include +#include // DECLARE_int32 +#include "bthread/types.h" +#include "bthread/context.h" // bthread_fcontext_t +#include "butil/object_pool.h" + +namespace bthread { + +struct StackStorage { + unsigned stacksize; + unsigned guardsize; + // Assume stack grows upwards. + // http://www.boost.org/doc/libs/1_55_0/libs/context/doc/html/context/stack.html + void* bottom; + unsigned valgrind_stack_id; + + // Clears all members. + void zeroize() { + stacksize = 0; + guardsize = 0; + bottom = NULL; + valgrind_stack_id = 0; + } +}; + +// Allocate a piece of stack. +int allocate_stack_storage(StackStorage* s, int stacksize, int guardsize); +// Deallocate a piece of stack. Parameters MUST be returned or set by the +// corresponding allocate_stack_storage() otherwise behavior is undefined. +void deallocate_stack_storage(StackStorage* s); + +enum StackType { + STACK_TYPE_MAIN = 0, + STACK_TYPE_PTHREAD = BTHREAD_STACKTYPE_PTHREAD, + STACK_TYPE_SMALL = BTHREAD_STACKTYPE_SMALL, + STACK_TYPE_NORMAL = BTHREAD_STACKTYPE_NORMAL, + STACK_TYPE_LARGE = BTHREAD_STACKTYPE_LARGE +}; + +struct ContextualStack { + virtual ~ContextualStack() = default; + bthread_fcontext_t context; + StackType stacktype; + StackStorage storage; +}; + +// Get a stack in the `type' and run `entry' at the first time that the +// stack is jumped. +ContextualStack* get_stack(StackType type, void (*entry)(intptr_t)); +// Recycle a stack. NULL does nothing. +void return_stack(ContextualStack*); +// Jump from stack `from' to stack `to'. `from' must be the stack of callsite +// (to save contexts before jumping) +void jump_stack(ContextualStack* from, ContextualStack* to); + +} // namespace bthread + +#include "bthread/stack_inl.h" + +#endif // BTHREAD_ALLOCATE_STACK_H diff --git a/src/bthread/stack_inl.h b/src/bthread/stack_inl.h new file mode 100644 index 0000000..faa5de0 --- /dev/null +++ b/src/bthread/stack_inl.h @@ -0,0 +1,282 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Sun Sep 7 22:37:39 CST 2014 + +#ifndef BTHREAD_ALLOCATE_STACK_INL_H +#define BTHREAD_ALLOCATE_STACK_INL_H + +DECLARE_int32(guard_page_size); +DECLARE_int32(tc_stack_small); +DECLARE_int32(tc_stack_normal); + +namespace bthread { + +#ifdef BUTIL_USE_ASAN +namespace internal { + +BUTIL_FORCE_INLINE void ASanPoisonMemoryRegion(const StackStorage& storage) { + if (NULL == storage.bottom) { + return; + } + + CHECK_GT((void*)storage.bottom, + reinterpret_cast(storage.stacksize + + storage.guardsize)); + BUTIL_ASAN_POISON_MEMORY_REGION( + (char*)storage.bottom - storage.stacksize, storage.stacksize); +} + +BUTIL_FORCE_INLINE void ASanUnpoisonMemoryRegion(const StackStorage& storage) { + if (NULL == storage.bottom) { + return; + } + CHECK_GT(storage.bottom, + reinterpret_cast(storage.stacksize + storage.guardsize)); + BUTIL_ASAN_UNPOISON_MEMORY_REGION( + (char*)storage.bottom - storage.stacksize, storage.stacksize); +} + + +BUTIL_FORCE_INLINE void StartSwitchFiber(void** fake_stack_save, StackStorage& storage) { + if (NULL == storage.bottom) { + return; + } + RELEASE_ASSERT(storage.bottom > + reinterpret_cast(storage.stacksize + storage.guardsize)); + // Lowest address of this stack. + void* asan_stack_bottom = (char*)storage.bottom - storage.stacksize; + BUTIL_ASAN_START_SWITCH_FIBER(fake_stack_save, asan_stack_bottom, storage.stacksize); +} + +BUTIL_FORCE_INLINE void FinishSwitchFiber(void* fake_stack_save) { + BUTIL_ASAN_FINISH_SWITCH_FIBER(fake_stack_save, NULL, NULL); +} + +class ScopedASanFiberSwitcher { +public: + ScopedASanFiberSwitcher(StackStorage& next_storage) { + StartSwitchFiber(&_fake_stack, next_storage); + } + + ~ScopedASanFiberSwitcher() { + FinishSwitchFiber(_fake_stack); + } + + DISALLOW_COPY_AND_ASSIGN(ScopedASanFiberSwitcher); + +private: + void* _fake_stack{NULL}; +}; + +#define BTHREAD_ASAN_POISON_MEMORY_REGION(storage) \ + ::bthread::internal::ASanPoisonMemoryRegion(storage) + +#define BTHREAD_ASAN_UNPOISON_MEMORY_REGION(storage) \ + ::bthread::internal::ASanUnpoisonMemoryRegion(storage) + +#define BTHREAD_SCOPED_ASAN_FIBER_SWITCHER(storage) \ + ::bthread::internal::ScopedASanFiberSwitcher switcher(storage) + +} // namespace internal +#else + +// If ASan are used, the annotations should be no-ops. +#define BTHREAD_ASAN_POISON_MEMORY_REGION(storage) ((void)(storage)) +#define BTHREAD_ASAN_UNPOISON_MEMORY_REGION(storage) ((void)(storage)) +#define BTHREAD_SCOPED_ASAN_FIBER_SWITCHER(storage) ((void)(storage)) + +#endif // BUTIL_USE_ASAN + +struct MainStackClass {}; + +struct SmallStackClass { + static int* stack_size_flag; + // Older gcc does not allow static const enum, use int instead. + static const int stacktype = (int)STACK_TYPE_SMALL; +}; + +struct NormalStackClass { + static int* stack_size_flag; + static const int stacktype = (int)STACK_TYPE_NORMAL; +}; + +struct LargeStackClass { + static int* stack_size_flag; + static const int stacktype = (int)STACK_TYPE_LARGE; +}; + +template struct StackFactory { + struct Wrapper : public ContextualStack { + explicit Wrapper(void (*entry)(intptr_t)) { + if (allocate_stack_storage(&storage, *StackClass::stack_size_flag, + FLAGS_guard_page_size) != 0) { + storage.zeroize(); + context = NULL; + return; + } + context = bthread_make_fcontext(storage.bottom, storage.stacksize, entry); + stacktype = (StackType)StackClass::stacktype; + // It's poisoned prior to use. + BTHREAD_ASAN_POISON_MEMORY_REGION(storage); + } + ~Wrapper() { + if (context) { + context = NULL; + // Unpoison to avoid affecting other allocator. + BTHREAD_ASAN_UNPOISON_MEMORY_REGION(storage); + deallocate_stack_storage(&storage); + storage.zeroize(); + } + } + }; + + static ContextualStack* get_stack(void (*entry)(intptr_t)) { + ContextualStack* cs = butil::get_object(entry); + // Marks stack as addressable. + BTHREAD_ASAN_UNPOISON_MEMORY_REGION(cs->storage); + return cs; + } + + static void return_stack(ContextualStack* cs) { + // Marks stack as unaddressable. + BTHREAD_ASAN_POISON_MEMORY_REGION(cs->storage); + butil::return_object(static_cast(cs)); + } +}; + +template <> struct StackFactory { + static ContextualStack* get_stack(void (*)(intptr_t)) { + ContextualStack* s = new (std::nothrow) ContextualStack; + if (NULL == s) { + return NULL; + } + s->context = NULL; + s->stacktype = STACK_TYPE_MAIN; + s->storage.zeroize(); + return s; + } + + static void return_stack(ContextualStack* s) { + delete s; + } +}; + +inline ContextualStack* get_stack(StackType type, void (*entry)(intptr_t)) { + switch (type) { + case STACK_TYPE_PTHREAD: + return NULL; + case STACK_TYPE_SMALL: + return StackFactory::get_stack(entry); + case STACK_TYPE_NORMAL: + return StackFactory::get_stack(entry); + case STACK_TYPE_LARGE: + return StackFactory::get_stack(entry); + case STACK_TYPE_MAIN: + return StackFactory::get_stack(entry); + } + return NULL; +} + +inline void return_stack(ContextualStack* s) { + if (NULL == s) { + return; + } + switch (s->stacktype) { + case STACK_TYPE_PTHREAD: + assert(false); + return; + case STACK_TYPE_SMALL: + return StackFactory::return_stack(s); + case STACK_TYPE_NORMAL: + return StackFactory::return_stack(s); + case STACK_TYPE_LARGE: + return StackFactory::return_stack(s); + case STACK_TYPE_MAIN: + return StackFactory::return_stack(s); + } +} + +inline void jump_stack(ContextualStack* from, ContextualStack* to) { + bthread_jump_fcontext(&from->context, to->context, 0/*not skip remained*/); +} + +} // namespace bthread + +namespace butil { + +template <> struct ObjectPoolBlockMaxItem< + bthread::StackFactory::Wrapper> { + static const size_t value = 64; +}; +template <> struct ObjectPoolBlockMaxItem< + bthread::StackFactory::Wrapper> { + static const size_t value = 64; +}; + +template <> struct ObjectPoolBlockMaxItem< + bthread::StackFactory::Wrapper> { + static const size_t value = 64; +}; + +template <> struct ObjectPoolFreeChunkMaxItem< + bthread::StackFactory::Wrapper> { + inline static size_t value() { + return (FLAGS_tc_stack_small <= 0 ? 0 : FLAGS_tc_stack_small); + } +}; + +template <> struct ObjectPoolFreeChunkMaxItem< + bthread::StackFactory::Wrapper> { + inline static size_t value() { + return (FLAGS_tc_stack_normal <= 0 ? 0 : FLAGS_tc_stack_normal); + } +}; + +template <> struct ObjectPoolFreeChunkMaxItem< + bthread::StackFactory::Wrapper> { + inline static size_t value() { return 1UL; } +}; + +template <> struct ObjectPoolValidator< + bthread::StackFactory::Wrapper> { + inline static bool validate( + const bthread::StackFactory::Wrapper* w) { + return w->context != NULL; + } +}; + +template <> struct ObjectPoolValidator< + bthread::StackFactory::Wrapper> { + inline static bool validate( + const bthread::StackFactory::Wrapper* w) { + return w->context != NULL; + } +}; + +template <> struct ObjectPoolValidator< + bthread::StackFactory::Wrapper> { + inline static bool validate( + const bthread::StackFactory::Wrapper* w) { + return w->context != NULL; + } +}; + +} // namespace butil + +#endif // BTHREAD_ALLOCATE_STACK_INL_H diff --git a/src/bthread/sys_futex.cpp b/src/bthread/sys_futex.cpp new file mode 100644 index 0000000..803bec6 --- /dev/null +++ b/src/bthread/sys_futex.cpp @@ -0,0 +1,145 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Wed Mar 14 17:44:58 CST 2018 + +#include "bthread/sys_futex.h" +#include "butil/scoped_lock.h" +#include "butil/atomicops.h" +#include +#include + +#if defined(OS_MACOSX) + +namespace bthread { + +class SimuFutex { +public: + SimuFutex() : counts(0) + , ref(0) { + pthread_mutex_init(&lock, NULL); + pthread_cond_init(&cond, NULL); + } + ~SimuFutex() { + pthread_mutex_destroy(&lock); + pthread_cond_destroy(&cond); + } + +public: + pthread_mutex_t lock; + pthread_cond_t cond; + int32_t counts; + int32_t ref; +}; + +static pthread_mutex_t s_futex_map_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_once_t init_futex_map_once = PTHREAD_ONCE_INIT; +static std::unordered_map* s_futex_map = NULL; +static void InitFutexMap() { + // Leave memory to process's clean up. + s_futex_map = new (std::nothrow) std::unordered_map(); + if (NULL == s_futex_map) { + exit(1); + } + return; +} + +int futex_wait_private(void* addr1, int expected, const timespec* timeout) { + if (pthread_once(&init_futex_map_once, InitFutexMap) != 0) { + LOG(FATAL) << "Fail to pthread_once"; + exit(1); + } + std::unique_lock mu(s_futex_map_mutex); + SimuFutex& simu_futex = (*s_futex_map)[addr1]; + ++simu_futex.ref; + mu.unlock(); + + int rc = 0; + { + std::unique_lock mu1(simu_futex.lock); + if (static_cast*>(addr1)->load() == expected) { + ++simu_futex.counts; + if (timeout) { + timespec timeout_abs = butil::timespec_from_now(*timeout); + if ((rc = pthread_cond_timedwait(&simu_futex.cond, &simu_futex.lock, &timeout_abs)) != 0) { + errno = rc; + rc = -1; + } + } else { + if ((rc = pthread_cond_wait(&simu_futex.cond, &simu_futex.lock)) != 0) { + errno = rc; + rc = -1; + } + } + --simu_futex.counts; + } else { + errno = EAGAIN; + rc = -1; + } + } + + std::unique_lock mu1(s_futex_map_mutex); + if (--simu_futex.ref == 0) { + s_futex_map->erase(addr1); + } + mu1.unlock(); + return rc; +} + +int futex_wake_private(void* addr1, int nwake) { + if (pthread_once(&init_futex_map_once, InitFutexMap) != 0) { + LOG(FATAL) << "Fail to pthread_once"; + exit(1); + } + std::unique_lock mu(s_futex_map_mutex); + auto it = s_futex_map->find(addr1); + if (it == s_futex_map->end()) { + mu.unlock(); + return 0; + } + SimuFutex& simu_futex = it->second; + ++simu_futex.ref; + mu.unlock(); + + int nwakedup = 0; + int rc = 0; + { + std::unique_lock mu1(simu_futex.lock); + nwake = (nwake < simu_futex.counts)? nwake: simu_futex.counts; + for (int i = 0; i < nwake; ++i) { + if ((rc = pthread_cond_signal(&simu_futex.cond)) != 0) { + errno = rc; + break; + } else { + ++nwakedup; + } + } + } + + std::unique_lock mu2(s_futex_map_mutex); + if (--simu_futex.ref == 0) { + s_futex_map->erase(addr1); + } + mu2.unlock(); + return nwakedup; +} + +} // namespace bthread + +#endif diff --git a/src/bthread/sys_futex.h b/src/bthread/sys_futex.h new file mode 100644 index 0000000..786d87e --- /dev/null +++ b/src/bthread/sys_futex.h @@ -0,0 +1,72 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Tue Jul 10 17:40:58 CST 2012 + +#ifndef BTHREAD_SYS_FUTEX_H +#define BTHREAD_SYS_FUTEX_H + +#include "butil/build_config.h" // OS_MACOSX +#include // syscall +#include // timespec +#if defined(OS_LINUX) +#include // SYS_futex +#include // FUTEX_WAIT, FUTEX_WAKE + +namespace bthread { + +#ifndef FUTEX_PRIVATE_FLAG +#define FUTEX_PRIVATE_FLAG 128 +#endif + +inline int futex_wait_private( + void* addr1, int expected, const timespec* timeout) { + return syscall(SYS_futex, addr1, (FUTEX_WAIT | FUTEX_PRIVATE_FLAG), + expected, timeout, NULL, 0); +} + +inline int futex_wake_private(void* addr1, int nwake) { + return syscall(SYS_futex, addr1, (FUTEX_WAKE | FUTEX_PRIVATE_FLAG), + nwake, NULL, NULL, 0); +} + +inline int futex_requeue_private(void* addr1, int nwake, void* addr2) { + return syscall(SYS_futex, addr1, (FUTEX_REQUEUE | FUTEX_PRIVATE_FLAG), + nwake, NULL, addr2, 0); +} + +} // namespace bthread + +#elif defined(OS_MACOSX) + +namespace bthread { + +int futex_wait_private(void* addr1, int expected, const timespec* timeout); + +int futex_wake_private(void* addr1, int nwake); + +int futex_requeue_private(void* addr1, int nwake, void* addr2); + +} // namespace bthread + +#else +#error "Unsupported OS" +#endif + +#endif // BTHREAD_SYS_FUTEX_H diff --git a/src/bthread/task_control.cpp b/src/bthread/task_control.cpp new file mode 100644 index 0000000..beb4130 --- /dev/null +++ b/src/bthread/task_control.cpp @@ -0,0 +1,791 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Tue Jul 10 17:40:58 CST 2012 + +#include +#include +#include +#include // SYS_gettid +#include "butil/scoped_lock.h" // BAIDU_SCOPED_LOCK +#include "butil/errno.h" // berror +#include "butil/logging.h" +#include "butil/threading/platform_thread.h" +#include "butil/third_party/murmurhash3/murmurhash3.h" +#include "bthread/sys_futex.h" // futex_wake_private +#include "bthread/interrupt_pthread.h" +#include "bthread/processor.h" // cpu_relax +#include "bthread/task_group.h" // TaskGroup +#include "bthread/task_control.h" +#include "bthread/timer_thread.h" // global_timer_thread +#include +#include "bthread/log.h" +#if defined(OS_MACOSX) +#include +#endif + +DEFINE_int32(task_group_delete_delay, 1, + "delay deletion of TaskGroup for so many seconds"); +DEFINE_int32(task_group_runqueue_capacity, 4096, + "capacity of runqueue in each TaskGroup"); +DEFINE_int32(task_group_ntags, 1, "TaskGroup will be grouped by number ntags"); +DEFINE_bool(task_group_set_worker_name, true, + "Whether to set the name of the worker thread"); +DEFINE_string(cpu_set, "", + "Set of CPUs to which worker threads are bound. " + "Two formats are supported:\n" + " Legacy (bind all tags to one set): \"0-3,5,7\"\n" + " Per-tag: \"0:0-3,5,7;1:6-9,4\" " + "where the number before ':' is the bthread_tag and the part " + "after ':' is a CPU list in the same format as the legacy value. " + "Tags not mentioned get no CPU binding. Default: disable."); + +DEFINE_int32(event_dispatcher_num, 1, "Number of event dispatcher"); + +namespace bthread { + +DEFINE_bool(parking_lot_no_signal_when_no_waiter, false, + "ParkingLot doesn't signal when there is no waiter. " + "In busy worker scenarios, signal overhead can be reduced."); +DEFINE_bool(enable_bthread_priority_queue, false, "Whether to enable priority queue"); + +DECLARE_int32(bthread_concurrency); +DECLARE_int32(bthread_min_concurrency); +DECLARE_int32(bthread_parking_lot_of_each_tag); + +extern pthread_mutex_t g_task_control_mutex; +extern BAIDU_THREAD_LOCAL TaskGroup* tls_task_group; +void (*g_worker_startfn)() = NULL; +void (*g_tagged_worker_startfn)(bthread_tag_t) = NULL; + +// May be called in other modules to run startfn in non-worker pthreads. +void run_worker_startfn() { + if (g_worker_startfn) { + g_worker_startfn(); + } +} + +void run_tagged_worker_startfn(bthread_tag_t tag) { + if (g_tagged_worker_startfn) { + g_tagged_worker_startfn(tag); + } +} + +struct WorkerThreadArgs { + WorkerThreadArgs(TaskControl* _c, bthread_tag_t _t) : c(_c), tag(_t) {} + TaskControl* c; + bthread_tag_t tag; +}; + +void* TaskControl::worker_thread(void* arg) { + run_worker_startfn(); +#ifdef BAIDU_INTERNAL + logging::ComlogInitializer comlog_initializer; +#endif + + auto dummy = static_cast(arg); + auto c = dummy->c; + auto tag = dummy->tag; + delete dummy; + run_tagged_worker_startfn(tag); + + TaskGroup* g = c->create_group(tag); + TaskStatistics stat; + if (NULL == g) { + LOG(ERROR) << "Fail to create TaskGroup in pthread=" << pthread_self(); + return NULL; + } + + g->_tid = pthread_self(); + // tag_wid is a per-tag monotonic counter: same-tag workers get 0,1,2,... + // Used both for CPU round-robin affinity and the thread name suffix. + int tag_wid = c->_tag_next_worker_id[tag].fetch_add( + 1, butil::memory_order_relaxed); + if (!c->_tag_cpus[tag].empty()) { + const auto& cpus = c->_tag_cpus[tag]; + bind_thread_to_cpu(pthread_self(), cpus[tag_wid % cpus.size()]); + } + if (FLAGS_task_group_set_worker_name) { + std::string worker_thread_name = butil::string_printf( + "brpc_wkr:%d-%d", g->tag(), tag_wid); + butil::PlatformThread::SetNameSimple(worker_thread_name.c_str()); + } + BT_VLOG << "Created worker=" << pthread_self() << " tid=" << g->_tid + << " bthread=" << g->main_tid() << " tag=" << g->tag(); + tls_task_group = g; + c->_nworkers << 1; + c->tag_nworkers(g->tag()) << 1; + + g->run_main_task(); + + stat = g->main_stat(); + BT_VLOG << "Destroying worker=" << pthread_self() << " bthread=" + << g->main_tid() << " idle=" << stat.cputime_ns / 1000000.0 + << "ms uptime=" << g->current_uptime_ns() / 1000000.0 << "ms"; + tls_task_group = NULL; + g->destroy_self(); + c->_nworkers << -1; + c->tag_nworkers(g->tag()) << -1; + return NULL; +} + +TaskGroup* TaskControl::create_group(bthread_tag_t tag) { + TaskGroup* g = new (std::nothrow) TaskGroup(this); + if (NULL == g) { + LOG(FATAL) << "Fail to new TaskGroup"; + return NULL; + } + if (g->init(FLAGS_task_group_runqueue_capacity) != 0) { + LOG(ERROR) << "Fail to init TaskGroup"; + delete g; + return NULL; + } + if (_add_group(g, tag) != 0) { + delete g; + return NULL; + } + return g; +} + +static void print_rq_sizes_in_the_tc(std::ostream &os, void *arg) { + TaskControl *tc = (TaskControl *)arg; + tc->print_rq_sizes(os); +} + +static double get_cumulated_worker_time_from_this(void *arg) { + return static_cast(arg)->get_cumulated_worker_time(); +} + +struct CumulatedWithTagArgs { + CumulatedWithTagArgs(TaskControl* _c, bthread_tag_t _t) : c(_c), t(_t) {} + TaskControl* c; + bthread_tag_t t; +}; + +static double get_cumulated_worker_time_from_this_with_tag(void* arg) { + auto a = static_cast(arg); + auto c = a->c; + auto t = a->t; + return c->get_cumulated_worker_time(t); +} + +static int64_t get_cumulated_switch_count_from_this(void *arg) { + return static_cast(arg)->get_cumulated_switch_count(); +} + +static int64_t get_cumulated_signal_count_from_this(void *arg) { + return static_cast(arg)->get_cumulated_signal_count(); +} + +TaskControl::TaskControl() + // NOTE: all fileds must be initialized before the vars. + : _tagged_ngroup(FLAGS_task_group_ntags) + , _tagged_groups(FLAGS_task_group_ntags) + , _init(false) + , _stop(false) + , _concurrency(0) + , _nworkers("bthread_worker_count") + , _pending_time(NULL) + // Delay exposure of following two vars because they rely on TC which + // is not initialized yet. + , _cumulated_worker_time(get_cumulated_worker_time_from_this, this) + , _worker_usage_second(&_cumulated_worker_time, 1) + , _cumulated_switch_count(get_cumulated_switch_count_from_this, this) + , _switch_per_second(&_cumulated_switch_count) + , _cumulated_signal_count(get_cumulated_signal_count_from_this, this) + , _signal_per_second(&_cumulated_signal_count) + , _status(print_rq_sizes_in_the_tc, this) + , _nbthreads("bthread_count") + , _enable_priority_queue(FLAGS_enable_bthread_priority_queue) + , _ed_priority_queue_num_of_each_tag(FLAGS_event_dispatcher_num) + , _ed_priority_queues( + FLAGS_task_group_ntags * FLAGS_event_dispatcher_num) + , _pl_num_of_each_tag(FLAGS_bthread_parking_lot_of_each_tag) + , _tagged_pl(FLAGS_task_group_ntags) + , _tag_cpus(FLAGS_task_group_ntags) + , _tag_next_worker_id(FLAGS_task_group_ntags) +{} + +int TaskControl::init_ed_priority_queues() { + if (!_enable_priority_queue) { + return 0; + } + for (int i = 0; i < FLAGS_task_group_ntags; ++i) { + for (int j = 0; + j < _ed_priority_queue_num_of_each_tag; ++j) { + if (ed_priority_queue(i, j).init( + BTHREAD_MAX_CONCURRENCY) != 0) { + LOG(ERROR) << "Fail to init priority queue for tag=" << i + << " ed=" << j; + return -1; + } + } + } + return 0; +} + +int TaskControl::init(int concurrency) { + if (_concurrency != 0) { + LOG(ERROR) << "Already initialized"; + return -1; + } + if (concurrency <= 0) { + LOG(ERROR) << "Invalid concurrency=" << concurrency; + return -1; + } + _concurrency = concurrency; + + if (!FLAGS_cpu_set.empty()) { + if (parse_cpuset(FLAGS_cpu_set) == -1) { + LOG(ERROR) << "invalid cpu_set=" << FLAGS_cpu_set; + return -1; + } + } + + // task group group by tags + for (int i = 0; i < FLAGS_task_group_ntags; ++i) { + _tagged_ngroup[i].store(0, std::memory_order_relaxed); + auto tag_str = std::to_string(i); + _tagged_nworkers.push_back(new bvar::Adder("bthread_worker_count", tag_str)); + _tagged_cumulated_worker_time.push_back(new bvar::PassiveStatus( + get_cumulated_worker_time_from_this_with_tag, new CumulatedWithTagArgs{this, i})); + _tagged_worker_usage_second.push_back(new bvar::PerSecond>( + "bthread_worker_usage", tag_str, _tagged_cumulated_worker_time[i], 1)); + _tagged_nbthreads.push_back(new bvar::Adder("bthread_count", tag_str)); + } + + if (init_ed_priority_queues() != 0) { + return -1; + } + + // Make sure TimerThread is ready. + if (get_or_create_global_timer_thread() == NULL) { + LOG(ERROR) << "Fail to get global_timer_thread"; + return -1; + } + +#ifdef BRPC_BTHREAD_TRACER + if (!_task_tracer.Init()) { + LOG(ERROR) << "Fail to init TaskTracer"; + return -1; + } +#endif // BRPC_BTHREAD_TRACER + + _workers.resize(_concurrency); + for (int i = 0; i < _concurrency; ++i) { + auto arg = new WorkerThreadArgs(this, i % FLAGS_task_group_ntags); + const int rc = pthread_create(&_workers[i], NULL, worker_thread, arg); + if (rc) { + delete arg; + PLOG(ERROR) << "Fail to create _workers[" << i << "]"; + return -1; + } + } + _worker_usage_second.expose("bthread_worker_usage"); + _switch_per_second.expose("bthread_switch_second"); + _signal_per_second.expose("bthread_signal_second"); + _status.expose("bthread_group_status"); + + // Wait for at least one group is added so that choose_one_group() + // never returns NULL. + // TODO: Handle the case that worker quits before add_group + for (int i = 0; i < FLAGS_task_group_ntags;) { + if (_tagged_ngroup[i].load(std::memory_order_acquire) == 0) { + usleep(100); // TODO: Elaborate + continue; + } + ++i; + } + + _init.store(true, butil::memory_order_release); + + return 0; +} + +int TaskControl::add_workers(int num, bthread_tag_t tag) { + if (num <= 0) { + return 0; + } + try { + _workers.resize(_concurrency + num); + } catch (...) { + return 0; + } + const int old_concurency = _concurrency.load(butil::memory_order_relaxed); + for (int i = 0; i < num; ++i) { + // Worker will add itself to _idle_workers, so we have to add + // _concurrency before create a worker. + _concurrency.fetch_add(1); + auto arg = new WorkerThreadArgs(this, tag); + const int rc = pthread_create( + &_workers[i + old_concurency], NULL, worker_thread, arg); + if (rc) { + delete arg; + PLOG(WARNING) << "Fail to create _workers[" << i + old_concurency << "]"; + _concurrency.fetch_sub(1, butil::memory_order_release); + break; + } + } + // Cannot fail + _workers.resize(_concurrency.load(butil::memory_order_relaxed)); + return _concurrency.load(butil::memory_order_relaxed) - old_concurency; +} + +TaskGroup* TaskControl::choose_one_group(bthread_tag_t tag) { + CHECK(tag >= BTHREAD_TAG_DEFAULT && tag < FLAGS_task_group_ntags) << tag; + auto& groups = tag_group(tag); + const auto ngroup = tag_ngroup(tag).load(butil::memory_order_acquire); + if (ngroup != 0) { + return groups[butil::fast_rand_less_than(ngroup)]; + } + CHECK(false) << "Impossible: ngroup is 0"; + return NULL; +} + +// Parse a single cpu-range-list such as "0-3,5,7" into a sorted, deduplicated +// vector of CPU IDs. Returns 0 on success, -1 on error. +static int parse_one_cpuset(const std::string& value, std::vector& cpus) { + static std::regex r("(\\d+-)?(\\d+)(,(\\d+-)?(\\d+))*"); + std::smatch match; + std::set cpuset; + if (value.empty()) { + return -1; + } + if (!std::regex_match(value, match, r)) { + return -1; + } + for (butil::StringSplitter split(value.data(), ','); split; ++split) { + butil::StringPiece cpu_ids(split.field(), split.length()); + cpu_ids.trim_spaces(); + butil::StringPiece begin = cpu_ids; + butil::StringPiece end = cpu_ids; + auto dash = cpu_ids.find('-'); + if (dash != cpu_ids.npos) { + begin = cpu_ids.substr(0, dash); + end = cpu_ids.substr(dash + 1); + } + unsigned first = UINT_MAX; + unsigned last = 0; + int ret = butil::StringSplitter(begin, '\t').to_uint(&first); + ret = ret | butil::StringSplitter(end, '\t').to_uint(&last); + if (ret != 0 || first > last) { + return -1; + } + for (auto i = first; i <= last; ++i) { + cpuset.insert(i); + } + } + cpus.assign(cpuset.begin(), cpuset.end()); + return 0; +} + +int TaskControl::parse_cpuset(const std::string& value) { + if (value.empty()) { + return -1; + } + const int ntags = static_cast(_tag_cpus.size()); + // Detect per-tag format by the presence of ':' or ';'. + // Legacy format ("0-3,5,7") never contains these characters. + bool per_tag_format = (value.find(';') != std::string::npos || + value.find(':') != std::string::npos); + + if (per_tag_format) { + // Per-tag format: "0:0-3,5,7;1:6-9,4" + for (butil::StringSplitter seg_split(value.data(), ';'); seg_split; ++seg_split) { + std::string segment(seg_split.field(), seg_split.length()); + // Trim leading/trailing spaces. + auto s = segment.find_first_not_of(' '); + auto e = segment.find_last_not_of(' '); + if (s == std::string::npos) { continue; } // blank segment + segment = segment.substr(s, e - s + 1); + + auto colon = segment.find(':'); + if (colon == std::string::npos) { + LOG(ERROR) << "cpu_set per-tag segment missing ':': " << segment; + return -1; + } + std::string tag_str = segment.substr(0, colon); + std::string cpus_str = segment.substr(colon + 1); + + unsigned tag_id = 0; + butil::StringPiece tag_sp(tag_str); + if (butil::StringSplitter(tag_sp, '\t').to_uint(&tag_id) != 0) { + LOG(ERROR) << "cpu_set invalid tag '" << tag_str << "'"; + return -1; + } + if ((int)tag_id >= ntags) { + LOG(ERROR) << "cpu_set tag " << tag_id + << " >= task_group_ntags " << ntags; + return -1; + } + + std::vector cpus; + if (parse_one_cpuset(cpus_str, cpus) != 0) { + LOG(ERROR) << "cpu_set invalid cpuset for tag " << tag_id + << ": " << cpus_str; + return -1; + } + _tag_cpus[tag_id] = std::move(cpus); + } + } else { + // Legacy format: one cpu-set shared by all tags. + std::vector cpus; + if (parse_one_cpuset(value, cpus) != 0) { + LOG(ERROR) << "cpu_set invalid cpuset: " << value; + return -1; + } + for (int i = 0; i < ntags; ++i) { + _tag_cpus[i] = cpus; + } + } + return 0; +} + +void TaskControl::bind_thread_to_cpu(pthread_t pthread, unsigned cpu_id) { +#if defined(OS_LINUX) + cpu_set_t cs; + CPU_ZERO(&cs); + CPU_SET(cpu_id, &cs); + auto r = pthread_setaffinity_np(pthread, sizeof(cs), &cs); + if (r != 0) { + LOG(WARNING) << "Failed to bind thread to cpu: " << cpu_id; + } + (void)r; +#elif defined(OS_MACOSX) + thread_port_t mach_thread = pthread_mach_thread_np(pthread); + if (mach_thread != MACH_PORT_NULL) { + LOG(WARNING) << "mach_thread is null" + << "Failed to bind thread to cpu: " << cpu_id; + return; + } + thread_affinity_policy_data_t policy; + policy.affinity_tag = cpu_id; + if (thread_policy_set(mach_thread, + THREAD_AFFINITY_POLICY, + (thread_policy_t)&policy, + THREAD_AFFINITY_POLICY_COUNT) != KERN_SUCCESS) { + LOG(WARNING) << "Failed to bind thread to cpu: " << cpu_id; + } +#endif +} + +#ifdef BRPC_BTHREAD_TRACER +void TaskControl::stack_trace(std::ostream& os, bthread_t tid) { + _task_tracer.Trace(os, tid); +} + +std::string TaskControl::stack_trace(bthread_t tid) { + return _task_tracer.Trace(tid); +} +#endif // BRPC_BTHREAD_TRACER + +extern int stop_and_join_epoll_threads(); + +void TaskControl::stop_and_join() { + // Close epoll threads so that worker threads are not waiting on epoll( + // which cannot be woken up by signal_task below) + CHECK_EQ(0, stop_and_join_epoll_threads()); + + // Stop workers + { + BAIDU_SCOPED_LOCK(_modify_group_mutex); + _stop = true; + std::for_each( + _tagged_ngroup.begin(), _tagged_ngroup.end(), + [](butil::atomic& index) { index.store(0, butil::memory_order_relaxed); }); + } + for (int i = 0; i < FLAGS_task_group_ntags; ++i) { + for (auto& pl : _tagged_pl[i]) { + pl.stop(); + } + } + + for (auto worker: _workers) { + // Interrupt blocking operations. +#ifdef BRPC_BTHREAD_TRACER + pthread_kill(worker, _task_tracer.get_trace_signal()); +#else + interrupt_pthread(worker); +#endif // BRPC_BTHREAD_TRACER + } + // Join workers + for (auto worker : _workers) { + pthread_join(worker, NULL); + } +} + +TaskControl::~TaskControl() { + // NOTE: g_task_control is not destructed now because the situation + // is extremely racy. + delete _pending_time.exchange(NULL, butil::memory_order_relaxed); + _worker_usage_second.hide(); + _switch_per_second.hide(); + _signal_per_second.hide(); + _status.hide(); + + stop_and_join(); +} + +int TaskControl::_add_group(TaskGroup* g, bthread_tag_t tag) { + if (__builtin_expect(NULL == g, 0)) { + return -1; + } + std::unique_lock mu(_modify_group_mutex); + if (_stop) { + return -1; + } + g->set_tag(tag); + g->set_pl(&_tagged_pl[tag][butil::fmix64(pthread_numeric_id()) % _pl_num_of_each_tag]); + size_t ngroup = _tagged_ngroup[tag].load(butil::memory_order_relaxed); + if (ngroup < (size_t)BTHREAD_MAX_CONCURRENCY) { + _tagged_groups[tag][ngroup] = g; + _tagged_ngroup[tag].store(ngroup + 1, butil::memory_order_release); + } + mu.unlock(); + // See the comments in _destroy_group + // TODO: Not needed anymore since non-worker pthread cannot have TaskGroup + // signal_task(65536, tag); + return 0; +} + +void TaskControl::delete_task_group(void* arg) { + delete(TaskGroup*)arg; +} + +int TaskControl::_destroy_group(TaskGroup* g) { + if (NULL == g) { + LOG(ERROR) << "Param[g] is NULL"; + return -1; + } + if (g->_control != this) { + LOG(ERROR) << "TaskGroup=" << g + << " does not belong to this TaskControl=" << this; + return -1; + } + bool erased = false; + { + BAIDU_SCOPED_LOCK(_modify_group_mutex); + auto tag = g->tag(); + auto& groups = tag_group(tag); + const size_t ngroup = tag_ngroup(tag).load(butil::memory_order_relaxed); + for (size_t i = 0; i < ngroup; ++i) { + if (groups[i] == g) { + // No need for atomic_thread_fence because lock did it. + groups[i] = groups[ngroup - 1]; + // Change _ngroup and keep _groups unchanged at last so that: + // - If steal_task sees the newest _ngroup, it would not touch + // _groups[ngroup -1] + // - If steal_task sees old _ngroup and is still iterating on + // _groups, it would not miss _groups[ngroup - 1] which was + // swapped to _groups[i]. Although adding new group would + // overwrite it, since we do signal_task in _add_group(), + // we think the pending tasks of _groups[ngroup - 1] would + // not miss. + tag_ngroup(tag).store(ngroup - 1, butil::memory_order_release); + //_groups[ngroup - 1] = NULL; + erased = true; + break; + } + } + } + + // Can't delete g immediately because for performance consideration, + // we don't lock _modify_group_mutex in steal_task which may + // access the removed group concurrently. We use simple strategy here: + // Schedule a function which deletes the TaskGroup after + // FLAGS_task_group_delete_delay seconds + if (erased) { + get_global_timer_thread()->schedule( + delete_task_group, g, + butil::microseconds_from_now(FLAGS_task_group_delete_delay * 1000000L)); + } + return 0; +} + +bool TaskControl::steal_task(bthread_t* tid, size_t* seed, size_t offset) { + auto tag = tls_task_group->tag(); + + if (_enable_priority_queue) { + for (int i = 0; + i < _ed_priority_queue_num_of_each_tag; ++i) { + if (ed_priority_queue(tag, i).steal(tid)) { + return true; + } + } + } + + // 1: Acquiring fence is paired with releasing fence in _add_group to + // avoid accessing uninitialized slot of _groups. + const size_t ngroup = tag_ngroup(tag).load(butil::memory_order_acquire/*1*/); + if (0 == ngroup) { + return false; + } + + // NOTE: Don't return inside `for' iteration since we need to update |seed| + bool stolen = false; + size_t s = *seed; + auto& groups = tag_group(tag); + for (size_t i = 0; i < ngroup; ++i, s += offset) { + TaskGroup* g = groups[s % ngroup]; + // g is possibly NULL because of concurrent _destroy_group + if (g) { + if (g->_rq.steal(tid)) { + stolen = true; + break; + } + if (g->_remote_rq.pop(tid)) { + stolen = true; + break; + } + } + } + *seed = s; + return stolen; +} + +void TaskControl::signal_task(int num_task, bthread_tag_t tag) { + if (num_task <= 0) { + return; + } + // TODO(gejun): Current algorithm does not guarantee enough threads will + // be created to match caller's requests. But in another side, there's also + // many useless signalings according to current impl. Capping the concurrency + // is a good balance between performance and timeliness of scheduling. + if (num_task > 2) { + num_task = 2; + } + auto& pl = tag_pl(tag); + size_t start_index = butil::fmix64(pthread_numeric_id()) % _pl_num_of_each_tag; + for (size_t i = 0; i < _pl_num_of_each_tag && num_task > 0; ++i) { + num_task -= pl[start_index].signal(1); + if (++start_index >= _pl_num_of_each_tag) { + start_index = 0; + } + } + if (num_task > 0 && + FLAGS_bthread_min_concurrency > 0 && // test min_concurrency for performance + _concurrency.load(butil::memory_order_relaxed) < FLAGS_bthread_concurrency) { + // TODO: Reduce this lock + BAIDU_SCOPED_LOCK(g_task_control_mutex); + if (_concurrency.load(butil::memory_order_acquire) < FLAGS_bthread_concurrency) { + add_workers(1, tag); + } + } +} + +void TaskControl::print_rq_sizes(std::ostream& os) { + size_t ngroup = 0; + std::for_each(_tagged_ngroup.begin(), _tagged_ngroup.end(), [&](butil::atomic& index) { + ngroup += index.load(butil::memory_order_relaxed); + }); + DEFINE_SMALL_ARRAY(int, nums, ngroup, 128); + { + BAIDU_SCOPED_LOCK(_modify_group_mutex); + // ngroup > _ngroup: nums[_ngroup ... ngroup-1] = 0 + // ngroup < _ngroup: just ignore _groups[_ngroup ... ngroup-1] + int i = 0; + for_each_task_group([&](TaskGroup* g) { + nums[i] = (g ? g->_rq.volatile_size() : 0); + ++i; + }); + } + for (size_t i = 0; i < ngroup; ++i) { + os << nums[i] << ' '; + } +} + +double TaskControl::get_cumulated_worker_time() { + int64_t cputime_ns = 0; + BAIDU_SCOPED_LOCK(_modify_group_mutex); + for_each_task_group([&](TaskGroup* g) { + cputime_ns += g->cumulated_cputime_ns(); + }); + return cputime_ns / 1000000000.0; +} + +double TaskControl::get_cumulated_worker_time(bthread_tag_t tag) { + int64_t cputime_ns = 0; + BAIDU_SCOPED_LOCK(_modify_group_mutex); + const size_t ngroup = tag_ngroup(tag).load(butil::memory_order_relaxed); + auto& groups = tag_group(tag); + for (size_t i = 0; i < ngroup; ++i) { + cputime_ns += groups[i]->cumulated_cputime_ns(); + } + return cputime_ns / 1000000000.0; +} + +int64_t TaskControl::get_cumulated_switch_count() { + int64_t c = 0; + BAIDU_SCOPED_LOCK(_modify_group_mutex); + for_each_task_group([&](TaskGroup* g) { + if (g) { + c += g->_nswitch; + } + }); + return c; +} + +int64_t TaskControl::get_cumulated_signal_count() { + int64_t c = 0; + BAIDU_SCOPED_LOCK(_modify_group_mutex); + for_each_task_group([&](TaskGroup* g) { + if (g) { + c += g->_nsignaled + g->_remote_nsignaled; + } + }); + return c; +} + +bvar::LatencyRecorder* TaskControl::create_exposed_pending_time() { + bool is_creator = false; + _pending_time_mutex.lock(); + bvar::LatencyRecorder* pt = _pending_time.load(butil::memory_order_consume); + if (!pt) { + pt = new bvar::LatencyRecorder; + _pending_time.store(pt, butil::memory_order_release); + is_creator = true; + } + _pending_time_mutex.unlock(); + if (is_creator) { + pt->expose("bthread_creation"); + } + return pt; +} + +std::vector TaskControl::get_living_bthreads() { + std::vector living_bthread_ids; + living_bthread_ids.reserve(1024); + butil::for_each_resource([&living_bthread_ids](TaskMeta* m) { + // filter out those bthreads created by bthread_start* functions, + // i.e. not those created internally to run main task as they are + // opaque to user. + if (m && m->fn) { + // determine whether the bthread is living by checking version + const uint32_t given_ver = get_version(m->tid); + BAIDU_SCOPED_LOCK(m->version_lock); + if (given_ver == *m->version_butex) { + living_bthread_ids.push_back(m->tid); + } + } + }); + return living_bthread_ids; +} + + +} // namespace bthread diff --git a/src/bthread/task_control.h b/src/bthread/task_control.h new file mode 100644 index 0000000..1dd3dfc --- /dev/null +++ b/src/bthread/task_control.h @@ -0,0 +1,230 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Tue Jul 10 17:40:58 CST 2012 + +#ifndef BTHREAD_TASK_CONTROL_H +#define BTHREAD_TASK_CONTROL_H + +#ifndef NDEBUG +#include // std::ostream +#endif +#include +#include // size_t +#include +#include +#include +#include "butil/atomicops.h" // butil::atomic +#include "bvar/bvar.h" // bvar::PassiveStatus +#include "bthread/task_tracer.h" +#include "bthread/task_meta.h" // TaskMeta +#include "bthread/work_stealing_queue.h" // WorkStealingQueue +#include "bthread/parking_lot.h" + +DECLARE_int32(task_group_ntags); +namespace bthread { + +class TaskGroup; + +// Control all task groups +class TaskControl { +friend class TaskGroup; +friend void wait_for_butex(void*); +#ifdef BRPC_BTHREAD_TRACER +friend bthread_t init_for_pthread_stack_trace(); +#endif // BRPC_BTHREAD_TRACER + +public: + TaskControl(); + ~TaskControl(); + + // Must be called before using. `nconcurrency' is # of worker pthreads. + int init(int nconcurrency); + + // Create a TaskGroup in this control. + TaskGroup* create_group(bthread_tag_t tag); + + // Steal a task from a "random" group. + bool steal_task(bthread_t* tid, size_t* seed, size_t offset); + + // Tell other groups that `n' tasks was just added to caller's runqueue + void signal_task(int num_task, bthread_tag_t tag); + + // Stop and join worker threads in TaskControl. + void stop_and_join(); + + // Get # of worker threads. + int concurrency() const + { return _concurrency.load(butil::memory_order_acquire); } + + int concurrency(bthread_tag_t tag) const + { return _tagged_ngroup[tag].load(butil::memory_order_acquire); } + + void print_rq_sizes(std::ostream& os); + + double get_cumulated_worker_time(); + double get_cumulated_worker_time(bthread_tag_t tag); + int64_t get_cumulated_switch_count(); + int64_t get_cumulated_signal_count(); + + // [Not thread safe] Add more worker threads. + // Return the number of workers actually added, which may be less than |num| + int add_workers(int num, bthread_tag_t tag); + + // Choose one TaskGroup (randomly right now). + // If this method is called after init(), it never returns NULL. + TaskGroup* choose_one_group(bthread_tag_t tag); + + // Parse FLAGS_cpu_set into _tag_cpus. Two formats are accepted: + // Legacy (all tags share one set): "0-3,5,7" + // Per-tag: "0:0-3,5,7;1:6-9,4" + // Tags not mentioned get an empty cpu list (= no binding). + // Returns -1 on parse error. + int parse_cpuset(const std::string& value); + + static void bind_thread_to_cpu(pthread_t pthread, unsigned cpu_id); + +#ifdef BRPC_BTHREAD_TRACER + // A stacktrace of bthread can be helpful in debugging. + void stack_trace(std::ostream& os, bthread_t tid); + std::string stack_trace(bthread_t tid); +#endif // BRPC_BTHREAD_TRACER + + void push_ed_priority_queue( + bthread_tag_t tag, int priority_index, bthread_t tid) { + ed_priority_queue(tag, priority_index).push(tid); + } + + std::vector get_living_bthreads(); + +private: + typedef std::array TaggedGroups; + typedef std::array TaggedParkingLot; + // Add/Remove a TaskGroup. + // Returns 0 on success, -1 otherwise. + int _add_group(TaskGroup*, bthread_tag_t tag); + int _destroy_group(TaskGroup*); + + // Tag group + TaggedGroups& tag_group(bthread_tag_t tag) { return _tagged_groups[tag]; } + + // Tag ngroup + butil::atomic& tag_ngroup(int tag) { return _tagged_ngroup[tag]; } + + // Tag parking slot + TaggedParkingLot& tag_pl(bthread_tag_t tag) { return _tagged_pl[tag]; } + + // Priority queue for a specific ED within a tag + WorkStealingQueue& ed_priority_queue( + bthread_tag_t tag, int index) { + return _ed_priority_queues[ + tag * _ed_priority_queue_num_of_each_tag + index]; + } + + int init_ed_priority_queues(); + + static void delete_task_group(void* arg); + + static void* worker_thread(void* task_control); + + template + void for_each_task_group(F const& f); + + bvar::LatencyRecorder& exposed_pending_time(); + bvar::LatencyRecorder* create_exposed_pending_time(); + bvar::Adder& tag_nworkers(bthread_tag_t tag); + bvar::Adder& tag_nbthreads(bthread_tag_t tag); + + std::vector> _tagged_ngroup; + std::vector _tagged_groups; + butil::Mutex _modify_group_mutex; + + butil::atomic _init; // if not init, bvar will case coredump + bool _stop; + butil::atomic _concurrency; + std::vector _workers; + bvar::Adder _nworkers; + butil::Mutex _pending_time_mutex; + butil::atomic _pending_time; + bvar::PassiveStatus _cumulated_worker_time; + bvar::PerSecond > _worker_usage_second; + bvar::PassiveStatus _cumulated_switch_count; + bvar::PerSecond > _switch_per_second; + bvar::PassiveStatus _cumulated_signal_count; + bvar::PerSecond > _signal_per_second; + bvar::PassiveStatus _status; + bvar::Adder _nbthreads; + + std::vector*> _tagged_nworkers; + std::vector*> _tagged_cumulated_worker_time; + std::vector>*> _tagged_worker_usage_second; + std::vector*> _tagged_nbthreads; + + bool _enable_priority_queue; + int _ed_priority_queue_num_of_each_tag; + std::vector> _ed_priority_queues; + + size_t _pl_num_of_each_tag; + std::vector _tagged_pl; + // Per-tag CPU binding lists. _tag_cpus[tag] is the round-robin list of + // CPU IDs to which workers of that tag are bound. Empty means no binding. + std::vector> _tag_cpus; + // Per-tag monotonic counter for round-robin CPU assignment. + // Incremented once per worker created for that tag (in worker_thread). + std::vector> _tag_next_worker_id; + +#ifdef BRPC_BTHREAD_TRACER + TaskTracer _task_tracer; +#endif // BRPC_BTHREAD_TRACER + +}; + +inline bvar::LatencyRecorder& TaskControl::exposed_pending_time() { + bvar::LatencyRecorder* pt = _pending_time.load(butil::memory_order_consume); + if (!pt) { + pt = create_exposed_pending_time(); + } + return *pt; +} + +inline bvar::Adder& TaskControl::tag_nworkers(bthread_tag_t tag) { + return *_tagged_nworkers[tag]; +} + +inline bvar::Adder& TaskControl::tag_nbthreads(bthread_tag_t tag) { + return *_tagged_nbthreads[tag]; +} + +template +inline void TaskControl::for_each_task_group(F const& f) { + if (_init.load(butil::memory_order_acquire) == false) { + return; + } + for (size_t i = 0; i < _tagged_groups.size(); ++i) { + auto ngroup = tag_ngroup(i).load(butil::memory_order_relaxed); + auto& groups = tag_group(i); + for (size_t j = 0; j < ngroup; ++j) { + f(groups[j]); + } + } +} + +} // namespace bthread + +#endif // BTHREAD_TASK_CONTROL_H diff --git a/src/bthread/task_group.cpp b/src/bthread/task_group.cpp new file mode 100644 index 0000000..46f9a13 --- /dev/null +++ b/src/bthread/task_group.cpp @@ -0,0 +1,1279 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Tue Jul 10 17:40:58 CST 2012 + +#include +#include // size_t +#include +#include "butil/compat.h" // OS_MACOSX +#include "butil/macros.h" // ARRAY_SIZE +#include "butil/scoped_lock.h" // BAIDU_SCOPED_LOCK +#include "butil/fast_rand.h" +#include "butil/unique_ptr.h" +#include "butil/third_party/murmurhash3/murmurhash3.h" // fmix64 +#include "butil/reloadable_flags.h" +#include "bthread/errno.h" // ESTOP +#include "bthread/butex.h" // butex_* +#include "bthread/sys_futex.h" // futex_wake_private +#include "bthread/processor.h" // cpu_relax +#include "bthread/task_control.h" +#include "bthread/task_group.h" +#include "bthread/timer_thread.h" +#include "bthread/bthread.h" + +#ifdef __x86_64__ +#include +#endif // __x86_64__ + +#ifdef __ARM_NEON +#include +#endif // __ARM_NEON + +namespace bthread { + +// Global span function pointers for bthread lifecycle tracing. +// These are set by brpc layer via bthread_set_span_funcs(). +void* (*g_create_bthread_span)() = NULL; +void (*g_rpcz_parent_span_dtor)(void*) = NULL; +void (*g_end_bthread_span)() = NULL; + +static const bthread_attr_t BTHREAD_ATTR_TASKGROUP = { + BTHREAD_STACKTYPE_UNKNOWN, 0, NULL, BTHREAD_TAG_INVALID, {0} }; + +DEFINE_bool(show_bthread_creation_in_vars, false, "When this flags is on, The time " + "from bthread creation to first run will be recorded and shown in /vars"); +BUTIL_VALIDATE_GFLAG(show_bthread_creation_in_vars, butil::PassValidate); + +DEFINE_bool(show_per_worker_usage_in_vars, false, + "Show per-worker usage in /vars/bthread_per_worker_usage_"); +BUTIL_VALIDATE_GFLAG(show_per_worker_usage_in_vars, butil::PassValidate); + +DEFINE_bool(bthread_enable_cpu_clock_stat, false, + "Enable CPU clock statistics for bthread"); +BUTIL_VALIDATE_GFLAG(bthread_enable_cpu_clock_stat, butil::PassValidate); + +BAIDU_VOLATILE_THREAD_LOCAL(TaskGroup*, tls_task_group, NULL); +// Sync with TaskMeta::local_storage when a bthread is created or destroyed. +// During running, the two fields may be inconsistent, use tls_bls as the +// groundtruth. +BAIDU_VOLATILE_THREAD_LOCAL(LocalStorage, tls_bls, BTHREAD_LOCAL_STORAGE_INITIALIZER); + +// defined in bthread/key.cpp +extern void return_keytable(bthread_keytable_pool_t*, KeyTable*); + +// [Hacky] This is a special TLS set by bthread-rpc privately... to save +// overhead of creation keytable, may be removed later. +BAIDU_VOLATILE_THREAD_LOCAL(void*, tls_unique_user_ptr, NULL); + +const TaskStatistics EMPTY_STAT = { 0, 0, 0 }; + +AtomicInteger128::Value AtomicInteger128::load() const { +#ifdef __x86_64__ + (void)_mutex; + (void)_seq; + __m128i value = _mm_load_si128(reinterpret_cast(&_value)); + return {value[0], value[1]}; +#elif defined(__ARM_NEON) + (void)_mutex; + (void)_seq; + int64x2_t value = vld1q_s64(reinterpret_cast(&_value)); + return {value[0], value[1]}; +#elif defined(__riscv) && __riscv_xlen == 64 + (void)_mutex; + // RISC-V: Seqlock-based atomic 128-bit load. + int64_t v1, v2; + uint64_t seq0, seq1; + do { + __asm__ volatile( + "ld %0, %1\n\t" + : "=r"(seq0) + : "m"(_seq) + : "memory" + ); + if (seq0 & 1) continue; + __asm__ volatile("fence r, rw\n\t" ::: "memory"); + __asm__ volatile( + "ld %0, %2\n\t" + "ld %1, %3\n\t" + : "=r"(v1), "=r"(v2) + : "m"(_value.v1), "m"(_value.v2) + : "memory" + ); + __asm__ volatile("fence r, rw\n\t" ::: "memory"); + __asm__ volatile( + "ld %0, %1\n\t" + : "=r"(seq1) + : "m"(_seq) + : "memory" + ); + } while (seq0 != seq1); + return {v1, v2}; +#else + BAIDU_SCOPED_LOCK(const_cast(_mutex)); + return _value; +#endif +} + +void AtomicInteger128::store(Value value) { +#ifdef __x86_64__ + (void)_seq; + __m128i v = _mm_load_si128(reinterpret_cast<__m128i*>(&value)); + _mm_store_si128(reinterpret_cast<__m128i*>(&_value), v); +#elif defined(__ARM_NEON) + (void)_seq; + int64x2_t v = vld1q_s64(reinterpret_cast(&value)); + vst1q_s64(reinterpret_cast(&_value), v); +#elif defined(__riscv) && __riscv_xlen == 64 + (void)_mutex; + // RISC-V: Seqlock-based atomic 128-bit store. + uint64_t old_seq; + __asm__ volatile( + "ld %0, %1\n\t" + : "=r"(old_seq) + : "m"(_seq) + : "memory" + ); + uint64_t new_seq = old_seq + 1; + __asm__ volatile( + "fence w, w\n\t" + "sd %1, %0\n\t" + : "=m"(_seq) + : "r"(new_seq) + : "memory" + ); + __asm__ volatile("fence w, w\n\t" ::: "memory"); + __asm__ volatile( + "sd %2, %0\n\t" + "sd %3, %1\n\t" + : "=m"(_value.v1), "=m"(_value.v2) + : "r"(value.v1), "r"(value.v2) + : "memory" + ); + __asm__ volatile("fence w, w\n\t" ::: "memory"); + new_seq++; + __asm__ volatile( + "sd %1, %0\n\t" + : "=m"(_seq) + : "r"(new_seq) + : "memory" + ); +#else + BAIDU_SCOPED_LOCK(const_cast(_mutex)); + _value = value; +#endif +} + + +int TaskGroup::get_attr(bthread_t tid, bthread_attr_t* out) { + TaskMeta* const m = address_meta(tid); + if (m != NULL) { + const uint32_t given_ver = get_version(tid); + BAIDU_SCOPED_LOCK(m->version_lock); + if (given_ver == *m->version_butex) { + *out = m->attr; + return 0; + } + } + errno = EINVAL; + return -1; +} + +void TaskGroup::set_stopped(bthread_t tid) { + TaskMeta* const m = address_meta(tid); + if (m != NULL) { + const uint32_t given_ver = get_version(tid); + BAIDU_SCOPED_LOCK(m->version_lock); + if (given_ver == *m->version_butex) { + m->stop = true; + } + } +} + +bool TaskGroup::is_stopped(bthread_t tid) { + TaskMeta* const m = address_meta(tid); + if (m != NULL) { + const uint32_t given_ver = get_version(tid); + BAIDU_SCOPED_LOCK(m->version_lock); + if (given_ver == *m->version_butex) { + return m->stop; + } + } + // If the tid does not exist or version does not match, it's intuitive + // to treat the thread as "stopped". + return true; +} + +bool TaskGroup::wait_task(bthread_t* tid) { + do { +#ifndef BTHREAD_DONT_SAVE_PARKING_STATE + if (_last_pl_state.stopped()) { + return false; + } + _pl->wait(_last_pl_state); + if (steal_task(tid)) { + return true; + } +#else + const ParkingLot::State st = _pl->get_state(); + if (st.stopped()) { + return false; + } + if (steal_task(tid)) { + return true; + } + _pl->wait(st); +#endif + } while (true); +} + +static double get_cumulated_cputime_from_this(void* arg) { + return static_cast(arg)->cumulated_cputime_ns() / 1000000000.0; +} + +int64_t TaskGroup::cumulated_cputime_ns() const { + CPUTimeStat cpu_time_stat = _cpu_time_stat.load(); + // Add the elapsed time of running bthread. + int64_t cumulated_cputime_ns = cpu_time_stat.cumulated_cputime_ns(); + if (!cpu_time_stat.is_main_task()) { + cumulated_cputime_ns += butil::cpuwide_time_ns() - cpu_time_stat.last_run_ns(); + } + return cumulated_cputime_ns; +} + +void TaskGroup::run_main_task() { + bvar::PassiveStatus cumulated_cputime( + get_cumulated_cputime_from_this, this); + std::unique_ptr > > usage_bvar; + + TaskGroup* dummy = this; + bthread_t tid; + while (wait_task(&tid)) { + sched_to(&dummy, tid); + DCHECK_EQ(this, dummy); + DCHECK_EQ(_cur_meta->stack, _main_stack); + if (_cur_meta->tid != _main_tid) { + task_runner(1/*skip remained*/); + } + if (FLAGS_show_per_worker_usage_in_vars && !usage_bvar) { + char name[32]; +#if defined(OS_MACOSX) + snprintf(name, sizeof(name), "bthread_worker_usage_%" PRIu64, + pthread_numeric_id()); +#else + snprintf(name, sizeof(name), "bthread_worker_usage_%ld", + (long)syscall(SYS_gettid)); +#endif + usage_bvar.reset(new bvar::PerSecond > + (name, &cumulated_cputime, 1)); + } + } + // Don't forget to add elapse of last wait_task. + current_task()->stat.cputime_ns += + butil::cpuwide_time_ns() - _cpu_time_stat.load_unsafe().last_run_ns(); +} + +TaskGroup::TaskGroup(TaskControl* c) + : _control(c) { + CHECK(c); +} + +TaskGroup::~TaskGroup() { + if (_main_tid) { + TaskMeta* m = address_meta(_main_tid); + CHECK(_main_stack == m->stack); +#ifdef BUTIL_USE_ASAN + _main_stack->storage.bottom = NULL; + _main_stack->storage.stacksize = 0; +#endif // BUTIL_USE_ASAN + return_stack(m->release_stack()); + return_resource(get_slot(_main_tid)); + _main_tid = 0; + } +} + +#ifdef BUTIL_USE_ASAN +// Returns the **highest** address of the calling pthread's stack and its +// total size, matching brpc's `StackStorage::bottom` convention (see comment +// in bthread/stack.h: "Assume stack grows upwards"). Note that on Linux +// `pthread_attr_getstack(3)` returns the lowest address of the region, so +// we have to translate it; on macOS `pthread_get_stackaddr_np(3)` already +// returns the stack base (highest address), so we use it as-is. +int PthreadAttrGetStack(void*& stack_addr, size_t& stack_size) { +#if defined(OS_MACOSX) + stack_addr = pthread_get_stackaddr_np(pthread_self()); + stack_size = pthread_get_stacksize_np(pthread_self()); + return 0; +#else + pthread_attr_t attr; + int rc = pthread_getattr_np(pthread_self(), &attr); + if (0 != rc) { + LOG(ERROR) << "Fail to get pthread attributes: " << berror(rc); + return rc; + } + void* stack_lowest = NULL; + rc = pthread_attr_getstack(&attr, &stack_lowest, &stack_size); + if (0 != rc) { + LOG(ERROR) << "Fail to get pthread stack: " << berror(rc); + } else { + // Translate lowest -> highest to match StackStorage::bottom. + stack_addr = (char*)stack_lowest + stack_size; + } + pthread_attr_destroy(&attr); + return rc; +#endif // OS_MACOSX +} +#endif // BUTIL_USE_ASAN + +int TaskGroup::init(size_t runqueue_capacity) { + if (_rq.init(runqueue_capacity) != 0) { + LOG(FATAL) << "Fail to init _rq"; + return -1; + } + if (_remote_rq.init(runqueue_capacity / 2) != 0) { + LOG(FATAL) << "Fail to init _remote_rq"; + return -1; + } + +#ifdef BUTIL_USE_ASAN + void* stack_addr = NULL; + size_t stack_size = 0; + if (0 != PthreadAttrGetStack(stack_addr, stack_size)) { + return -1; + } +#endif // BUTIL_USE_ASAN + + ContextualStack* stk = get_stack(STACK_TYPE_MAIN, NULL); + if (NULL == stk) { + LOG(FATAL) << "Fail to get main stack container"; + return -1; + } + butil::ResourceId slot; + TaskMeta* m = butil::get_resource(&slot); + if (NULL == m) { + LOG(FATAL) << "Fail to get TaskMeta"; + return -1; + } + m->sleep_failed = false; + m->stop = false; + m->interrupted = false; + m->about_to_quit = false; + m->fn = NULL; + m->arg = NULL; + m->local_storage = LOCAL_STORAGE_INIT; + m->cpuwide_start_ns = butil::cpuwide_time_ns(); + m->stat = EMPTY_STAT; + m->attr = BTHREAD_ATTR_TASKGROUP; + m->tid = make_tid(*m->version_butex, slot); + m->set_stack(stk); + +#ifdef BUTIL_USE_ASAN + stk->storage.bottom = stack_addr; + stk->storage.stacksize = stack_size; + // No guard size required for ASan. +#endif // BUTIL_USE_ASAN + + _cur_meta = m; + _main_tid = m->tid; + _main_stack = stk; + + CPUTimeStat cpu_time_stat; + cpu_time_stat.set_last_run_ns(m->cpuwide_start_ns, true); + _cpu_time_stat.store(cpu_time_stat); + _last_cpu_clock_ns = 0; + + return 0; +} + +#ifdef BUTIL_USE_ASAN +void TaskGroup::asan_task_runner(intptr_t) { + // This is a new thread, and it doesn't have the fake stack yet. ASan will + // create it lazily, for now just pass NULL. + internal::FinishSwitchFiber(NULL); + task_runner(0); +} +#endif // BUTIL_USE_ASAN + +void TaskGroup::task_runner(intptr_t skip_remained) { + // NOTE: tls_task_group is volatile since tasks are moved around + // different groups. + TaskGroup* g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); +#ifdef BRPC_BTHREAD_TRACER + TaskTracer::set_running_status(g->tid(), g->_cur_meta); +#endif // BRPC_BTHREAD_TRACER + + if (!skip_remained) { + while (g->_last_context_remained) { + RemainedFn fn = g->_last_context_remained; + g->_last_context_remained = NULL; + fn(g->_last_context_remained_arg); + g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + } + +#ifndef NDEBUG + --g->_sched_recursive_guard; +#endif + } + + do { + // A task can be stopped before it gets running, in which case + // we may skip user function, but that may confuse user: + // Most tasks have variables to remember running result of the task, + // which is often initialized to values indicating success. If an + // user function is never called, the variables will be unchanged + // however they'd better reflect failures because the task is stopped + // abnormally. + + // Meta and identifier of the task is persistent in this run. + TaskMeta* const m = g->_cur_meta; + + if (FLAGS_show_bthread_creation_in_vars) { + // NOTE: the thread triggering exposure of pending time may spend + // considerable time because a single bvar::LatencyRecorder + // contains many bvar. + g->_control->exposed_pending_time() << + (butil::cpuwide_time_ns() - m->cpuwide_start_ns) / 1000L; + } + + // Not catch exceptions except ExitException which is for implementing + // bthread_exit(). User code is intended to crash when an exception is + // not caught explicitly. This is consistent with other threading + // libraries. + void* thread_return; + try { + thread_return = m->fn(m->arg); + } catch (ExitException& e) { + thread_return = e.value(); + } + + if (m->attr.flags & BTHREAD_INHERIT_SPAN) { + if (g_end_bthread_span) { + g_end_bthread_span(); + } + } + + // TODO: Save thread_return + (void)thread_return; + + // Logging must be done before returning the keytable, since the logging lib + // use bthread local storage internally, or will cause memory leak. + // FIXME: the time from quiting fn to here is not counted into cputime + if (m->attr.flags & BTHREAD_LOG_START_AND_FINISH) { + LOG(INFO) << "Finished bthread " << m->tid << ", cputime=" + << m->stat.cputime_ns / 1000000.0 << "ms"; + } + + // Clean up span if it exists. This must be done before keytable cleanup + // because span cleanup may use bthread local storage (e.g. logging, + // which allocates bthread-local stream arrays via bthread_setspecific). + // If span cleanup ran after keytable cleanup, such allocations would + // re-populate the keytable and never be reclaimed, causing memory leak. + LocalStorage* tls_bls_ptr = BAIDU_GET_PTR_VOLATILE_THREAD_LOCAL(tls_bls); + if (tls_bls_ptr->rpcz_parent_span && g_rpcz_parent_span_dtor) { + g_rpcz_parent_span_dtor(tls_bls_ptr->rpcz_parent_span); + tls_bls_ptr = BAIDU_GET_PTR_VOLATILE_THREAD_LOCAL(tls_bls); + tls_bls_ptr->rpcz_parent_span = NULL; + m->local_storage.rpcz_parent_span = NULL; + } + + // Clean tls variables, must be done before changing version_butex + // otherwise another thread just joined this thread may not see side + // effects of destructing tls variables. + KeyTable* kt = tls_bls_ptr->keytable; + if (kt != NULL) { + return_keytable(m->attr.keytable_pool, kt); + // After deletion: tls may be set during deletion. + tls_bls_ptr = BAIDU_GET_PTR_VOLATILE_THREAD_LOCAL(tls_bls); + tls_bls_ptr->keytable = NULL; + m->local_storage.keytable = NULL; // optional + } + + // During running the function in TaskMeta and deleting the KeyTable in + // return_KeyTable, the group is probably changed. + g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + + // Increase the version and wake up all joiners, if resulting version + // is 0, change it to 1 to make bthread_t never be 0. Any access + // or join to the bthread after changing version will be rejected. + // The spinlock is for visibility of TaskGroup::get_attr. +#ifdef BRPC_BTHREAD_TRACER + bool tracing = false; +#endif // BRPC_BTHREAD_TRACER + { + BAIDU_SCOPED_LOCK(m->version_lock); +#ifdef BRPC_BTHREAD_TRACER + tracing = TaskTracer::set_end_status_unsafe(m); +#endif // BRPC_BTHREAD_TRACER + if (0 == ++*m->version_butex) { + ++*m->version_butex; + } + } + butex_wake_except(m->version_butex, 0); + +#ifdef BRPC_BTHREAD_TRACER + if (tracing) { + // Wait for tracing completion. + g->_control->_task_tracer.WaitForTracing(m); + } + g->_control->_task_tracer.set_status(TASK_STATUS_UNKNOWN, m); +#endif // BRPC_BTHREAD_TRACER + + g->_control->_nbthreads << -1; + g->_control->tag_nbthreads(g->tag()) << -1; + g->set_remained(_release_last_context, m); + ending_sched(&g); + + } while (g->_cur_meta->tid != g->_main_tid); + + // Was called from a pthread and we don't have BTHREAD_STACKTYPE_PTHREAD + // tasks to run, quit for more tasks. +} + +void TaskGroup::_release_last_context(void* arg) { + TaskMeta* m = static_cast(arg); + if (m->stack_type() != STACK_TYPE_PTHREAD) { + return_stack(m->release_stack()/*may be NULL*/); + } else { + // it's _main_stack, don't return. + m->set_stack(NULL); + } + return_resource(get_slot(m->tid)); +} + +int TaskGroup::start_foreground(TaskGroup** pg, + bthread_t* __restrict th, + const bthread_attr_t* __restrict attr, + void * (*fn)(void*), + void* __restrict arg) { + if (__builtin_expect(!fn, 0)) { + return EINVAL; + } + const int64_t start_ns = butil::cpuwide_time_ns(); + const bthread_attr_t using_attr = (attr ? *attr : BTHREAD_ATTR_NORMAL); + butil::ResourceId slot; + TaskMeta* m = butil::get_resource(&slot); + if (BAIDU_UNLIKELY(NULL == m)) { + return ENOMEM; + } + CHECK(m->current_waiter.load(butil::memory_order_relaxed) == NULL); + m->sleep_failed = false; + m->stop = false; + m->interrupted = false; + m->about_to_quit = false; + m->fn = fn; + m->arg = arg; + CHECK(m->stack == NULL); + m->attr = using_attr; + m->local_storage = LOCAL_STORAGE_INIT; + if (using_attr.flags & BTHREAD_INHERIT_SPAN) { + if (g_create_bthread_span) { + m->local_storage.rpcz_parent_span = g_create_bthread_span(); + } else { + m->local_storage.rpcz_parent_span = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_bls).rpcz_parent_span; + } + } + m->cpuwide_start_ns = start_ns; + m->stat = EMPTY_STAT; + m->tid = make_tid(*m->version_butex, slot); + + TaskGroup* g = *pg; + m->priority_index = g->_cur_meta->priority_index; + m->attr.tag = g->tag(); + *th = m->tid; + if (using_attr.flags & BTHREAD_LOG_START_AND_FINISH) { + LOG(INFO) << "Started bthread " << m->tid; + } + + g->_control->_nbthreads << 1; + g->_control->tag_nbthreads(g->tag()) << 1; +#ifdef BRPC_BTHREAD_TRACER + g->_control->_task_tracer.set_status(TASK_STATUS_CREATED, m); +#endif // BRPC_BTHREAD_TRACER + if (g->is_current_pthread_task()) { + // never create foreground task in pthread. + g->ready_to_run(m, using_attr.flags & BTHREAD_NOSIGNAL); + } else { + // NOSIGNAL affects current task, not the new task. + RemainedFn fn = NULL; + auto& cur_attr = g->_cur_meta->attr; + if (g->_control->_enable_priority_queue && cur_attr.flags & BTHREAD_GLOBAL_PRIORITY) { + fn = priority_to_run; + } else if (g->current_task()->about_to_quit) { + fn = ready_to_run_in_worker_ignoresignal; + } else { + fn = ready_to_run_in_worker; + } + ReadyToRunArgs args = { + g->tag(), g->_cur_meta, (bool)(using_attr.flags & BTHREAD_NOSIGNAL) + }; + g->set_remained(fn, &args); + sched_to(pg, m->tid); + } + return 0; +} + +template +int TaskGroup::start_background(bthread_t* __restrict th, + const bthread_attr_t* __restrict attr, + void * (*fn)(void*), + void* __restrict arg) { + if (__builtin_expect(!fn, 0)) { + return EINVAL; + } + const int64_t start_ns = butil::cpuwide_time_ns(); + const bthread_attr_t using_attr = (attr ? *attr : BTHREAD_ATTR_NORMAL); + butil::ResourceId slot; + TaskMeta* m = butil::get_resource(&slot); + if (BAIDU_UNLIKELY(NULL == m)) { + return ENOMEM; + } + CHECK(m->current_waiter.load(butil::memory_order_relaxed) == NULL); + m->sleep_failed = false; + m->stop = false; + m->interrupted = false; + m->about_to_quit = false; + m->fn = fn; + m->arg = arg; + CHECK(m->stack == NULL); + m->attr = using_attr; + m->local_storage = LOCAL_STORAGE_INIT; + if (using_attr.flags & BTHREAD_INHERIT_SPAN) { + if (g_create_bthread_span) { + m->local_storage.rpcz_parent_span = g_create_bthread_span(); + } else { + m->local_storage.rpcz_parent_span = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_bls).rpcz_parent_span; + } + } + m->cpuwide_start_ns = start_ns; + m->stat = EMPTY_STAT; + m->tid = make_tid(*m->version_butex, slot); + m->priority_index = _cur_meta->priority_index; + *th = m->tid; + if (using_attr.flags & BTHREAD_LOG_START_AND_FINISH) { + LOG(INFO) << "Started bthread " << m->tid; + } + m->attr.tag = tag(); + _control->_nbthreads << 1; + _control->tag_nbthreads(tag()) << 1; +#ifdef BRPC_BTHREAD_TRACER + _control->_task_tracer.set_status(TASK_STATUS_CREATED, m); +#endif // BRPC_BTHREAD_TRACER + if (REMOTE) { + ready_to_run_remote(m, (using_attr.flags & BTHREAD_NOSIGNAL)); + } else { + ready_to_run(m, (using_attr.flags & BTHREAD_NOSIGNAL)); + } + return 0; +} + +// Explicit instantiations. +template int +TaskGroup::start_background(bthread_t* __restrict th, + const bthread_attr_t* __restrict attr, + void * (*fn)(void*), + void* __restrict arg); +template int +TaskGroup::start_background(bthread_t* __restrict th, + const bthread_attr_t* __restrict attr, + void * (*fn)(void*), + void* __restrict arg); + +int TaskGroup::join(bthread_t tid, void** return_value) { + if (__builtin_expect(!tid, 0)) { // tid of bthread is never 0. + return EINVAL; + } + TaskMeta* m = address_meta(tid); + if (BAIDU_UNLIKELY(NULL == m)) { + // The bthread is not created yet, this join is definitely wrong. + return EINVAL; + } + TaskGroup* g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + if (g != NULL && g->current_tid() == tid) { + // joining self causes indefinite waiting. + return EINVAL; + } + const uint32_t expected_version = get_version(tid); + while (*m->version_butex == expected_version) { + if (butex_wait(m->version_butex, expected_version, NULL) < 0 && + errno != EWOULDBLOCK && errno != EINTR) { + return errno; + } + } + // Ensure all memory writes made by the joined bthread are visible to + // the joining thread after join returns. This matches the semantic + // guarantee provided by pthread_join() across supported architectures. + butil::atomic_thread_fence(butil::memory_order_acquire); + if (return_value) { + *return_value = NULL; + } + return 0; +} + +bool TaskGroup::exists(bthread_t tid) { + if (tid != 0) { // tid of bthread is never 0. + TaskMeta* m = address_meta(tid); + if (m != NULL) { + return (*m->version_butex == get_version(tid)); + } + } + return false; +} + +TaskStatistics TaskGroup::main_stat() const { + TaskMeta* m = address_meta(_main_tid); + return m ? m->stat : EMPTY_STAT; +} + +void TaskGroup::ending_sched(TaskGroup** pg) { + TaskGroup* g = *pg; + bthread_t next_tid = 0; + // Find next task to run, if none, switch to idle thread of the group. + +#ifndef BTHREAD_FAIR_WSQ + // When BTHREAD_FAIR_WSQ is defined, profiling shows that cpu cost of + // WSQ::steal() in example/multi_threaded_echo_c++ changes from 1.9% + // to 2.9% + const bool popped = g->_rq.pop(&next_tid); +#else + const bool popped = g->_rq.steal(&next_tid); +#endif + if (!popped && !g->steal_task(&next_tid)) { + // Jump to main task if there's no task to run. + next_tid = g->_main_tid; + } + + TaskMeta* const cur_meta = g->_cur_meta; + TaskMeta* next_meta = address_meta(next_tid); + if (next_meta->stack == NULL) { + if (next_meta->stack_type() == cur_meta->stack_type()) { + // Reuse the stack of the current ending task. + // + // also works with pthread_task scheduling to pthread_task, the + // transfered stack is just _main_stack. + next_meta->set_stack(cur_meta->release_stack()); + } else { +#ifdef BUTIL_USE_ASAN + ContextualStack* stk = get_stack( + next_meta->stack_type(), asan_task_runner); +#else + ContextualStack* stk = get_stack(next_meta->stack_type(), task_runner); +#endif // BUTIL_USE_ASAN + if (stk) { + next_meta->set_stack(stk); + } else { + // stack_type is BTHREAD_STACKTYPE_PTHREAD or out of memory, + // In latter case, attr is forced to be BTHREAD_STACKTYPE_PTHREAD. + // This basically means that if we can't allocate stack, run + // the task in pthread directly. + next_meta->attr.stack_type = BTHREAD_STACKTYPE_PTHREAD; + next_meta->set_stack(g->_main_stack); + } + } + } + sched_to(pg, next_meta); +} + +void TaskGroup::sched(TaskGroup** pg) { + TaskGroup* g = *pg; + bthread_t next_tid = 0; + // Find next task to run, if none, switch to idle thread of the group. +#ifndef BTHREAD_FAIR_WSQ + const bool popped = g->_rq.pop(&next_tid); +#else + const bool popped = g->_rq.steal(&next_tid); +#endif + if (!popped && !g->steal_task(&next_tid)) { + // Jump to main task if there's no task to run. + next_tid = g->_main_tid; + } + sched_to(pg, next_tid); +} + +extern void CheckBthreadScheSafety(); + +void TaskGroup::sched_to(TaskGroup** pg, TaskMeta* next_meta) { + TaskGroup* g = *pg; +#ifndef NDEBUG + if ((++g->_sched_recursive_guard) > 1) { + LOG(FATAL) << "Recursively(" << g->_sched_recursive_guard - 1 + << ") call sched_to(" << g << ")"; + } +#endif + // Save errno so that errno is bthread-specific. + int saved_errno = errno; + void* saved_unique_user_ptr = tls_unique_user_ptr; + + TaskMeta* const cur_meta = g->_cur_meta; + int64_t now = butil::cpuwide_time_ns(); + CPUTimeStat cpu_time_stat = g->_cpu_time_stat.load_unsafe(); + int64_t elp_ns = now - cpu_time_stat.last_run_ns(); + cur_meta->stat.cputime_ns += elp_ns; + // Update cpu_time_stat. + cpu_time_stat.set_last_run_ns(now, is_main_task(g, next_meta->tid)); + cpu_time_stat.add_cumulated_cputime_ns(elp_ns, is_main_task(g, cur_meta->tid)); + g->_cpu_time_stat.store(cpu_time_stat); + + if (FLAGS_bthread_enable_cpu_clock_stat) { + const int64_t cpu_thread_time = butil::cputhread_time_ns(); + if (g->_last_cpu_clock_ns != 0) { + cur_meta->stat.cpu_usage_ns += cpu_thread_time - g->_last_cpu_clock_ns; + } + g->_last_cpu_clock_ns = cpu_thread_time; + } else { + g->_last_cpu_clock_ns = 0; + } + + ++cur_meta->stat.nswitch; + ++ g->_nswitch; + // Switch to the task + if (__builtin_expect(next_meta != cur_meta, 1)) { + g->_cur_meta = next_meta; + // Switch tls_bls + cur_meta->local_storage = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_bls); + BAIDU_SET_VOLATILE_THREAD_LOCAL(tls_bls, next_meta->local_storage); + + // Logging must be done after switching the local storage, since the logging lib + // use bthread local storage internally, or will cause memory leak. + if ((cur_meta->attr.flags & BTHREAD_LOG_CONTEXT_SWITCH) || + (next_meta->attr.flags & BTHREAD_LOG_CONTEXT_SWITCH)) { + LOG(INFO) << "Switch bthread: " << cur_meta->tid << " -> " + << next_meta->tid; + } + + if (cur_meta->stack != NULL) { + if (next_meta->stack != cur_meta->stack) { + CheckBthreadScheSafety(); +#ifdef BRPC_BTHREAD_TRACER + g->_control->_task_tracer.set_status(TASK_STATUS_JUMPING, cur_meta); + g->_control->_task_tracer.set_status(TASK_STATUS_JUMPING, next_meta); +#endif // BRPC_BTHREAD_TRACER + { + BTHREAD_SCOPED_ASAN_FIBER_SWITCHER(next_meta->stack->storage); + jump_stack(cur_meta->stack, next_meta->stack); + } + // probably went to another group, need to assign g again. + g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); +#ifdef BRPC_BTHREAD_TRACER + TaskTracer::set_running_status(g->tid(), g->_cur_meta); +#endif // BRPC_BTHREAD_TRACER + } +#ifndef NDEBUG + else { + // else pthread_task is switching to another pthread_task, sc + // can only equal when they're both _main_stack + CHECK(cur_meta->stack == g->_main_stack); + } +#endif + } /* else because of ending_sched(including pthread_task->pthread_task). */ +#ifdef BRPC_BTHREAD_TRACER + else { + // _cur_meta: TASK_STATUS_FIRST_READY -> TASK_STATUS_RUNNING. + TaskTracer::set_running_status(g->tid(), g->_cur_meta); + } +#endif // BRPC_BTHREAD_TRACER + } else { + LOG(FATAL) << "bthread=" << g->current_tid() << " sched_to itself!"; + } + + while (g->_last_context_remained) { + RemainedFn fn = g->_last_context_remained; + g->_last_context_remained = NULL; + fn(g->_last_context_remained_arg); + g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + } + + // Restore errno + errno = saved_errno; + // tls_unique_user_ptr probably changed. + BAIDU_SET_VOLATILE_THREAD_LOCAL(tls_unique_user_ptr, saved_unique_user_ptr); + +#ifndef NDEBUG + --g->_sched_recursive_guard; +#endif + *pg = g; +} + +void TaskGroup::destroy_self() { + if (_control) { + _control->_destroy_group(this); + _control = NULL; + } else { + CHECK(false); + } +} + + +void TaskGroup::ready_to_run(TaskMeta* meta, bool nosignal) { +#ifdef BRPC_BTHREAD_TRACER + _control->_task_tracer.set_status(TASK_STATUS_READY, meta); +#endif // BRPC_BTHREAD_TRACER + push_rq(meta->tid); + if (nosignal) { + ++_num_nosignal; + } else { + const int additional_signal = _num_nosignal; + _num_nosignal = 0; + _nsignaled += 1 + additional_signal; + _control->signal_task(1 + additional_signal, _tag); + } +} + +void TaskGroup::flush_nosignal_tasks() { + const int val = _num_nosignal; + if (val) { + _num_nosignal = 0; + _nsignaled += val; + _control->signal_task(val, _tag); + } +} + +void TaskGroup::ready_to_run_remote(TaskMeta* meta, bool nosignal) { +#ifdef BRPC_BTHREAD_TRACER + _control->_task_tracer.set_status(TASK_STATUS_READY, meta); +#endif // BRPC_BTHREAD_TRACER + _remote_rq._mutex.lock(); + while (!_remote_rq.push_locked(meta->tid)) { + flush_nosignal_tasks_remote_locked(_remote_rq._mutex); + LOG_EVERY_SECOND(ERROR) << "_remote_rq is full, capacity=" + << _remote_rq.capacity(); + ::usleep(1000); + _remote_rq._mutex.lock(); + } + if (nosignal) { + ++_remote_num_nosignal; + _remote_rq._mutex.unlock(); + } else { + const int additional_signal = _remote_num_nosignal; + _remote_num_nosignal = 0; + _remote_nsignaled += 1 + additional_signal; + _remote_rq._mutex.unlock(); + _control->signal_task(1 + additional_signal, _tag); + } +} + +void TaskGroup::flush_nosignal_tasks_remote_locked(butil::Mutex& locked_mutex) { + const int val = _remote_num_nosignal; + if (!val) { + locked_mutex.unlock(); + return; + } + _remote_num_nosignal = 0; + _remote_nsignaled += val; + locked_mutex.unlock(); + _control->signal_task(val, _tag); +} + +void TaskGroup::ready_to_run_general(TaskMeta* meta, bool nosignal) { + if (BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group) == this) { + return ready_to_run(meta, nosignal); + } + return ready_to_run_remote(meta, nosignal); +} + +void TaskGroup::flush_nosignal_tasks_general() { + if (BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group) == this) { + return flush_nosignal_tasks(); + } + return flush_nosignal_tasks_remote(); +} + +void TaskGroup::ready_to_run_in_worker(void* args_in) { + ReadyToRunArgs* args = static_cast(args_in); + return BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group)-> + ready_to_run(args->meta, args->nosignal); +} + +void TaskGroup::ready_to_run_in_worker_ignoresignal(void* args_in) { + ReadyToRunArgs* args = static_cast(args_in); + TaskGroup* g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + +#ifdef BRPC_BTHREAD_TRACER + g->_control->_task_tracer.set_status(TASK_STATUS_READY, args->meta); +#endif // BRPC_BTHREAD_TRACER + return g->push_rq(args->meta->tid); +} + +void TaskGroup::priority_to_run(void* args_in) { + ReadyToRunArgs* args = static_cast(args_in); + TaskGroup* g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); +#ifdef BRPC_BTHREAD_TRACER + g->_control->_task_tracer.set_status(TASK_STATUS_READY, args->meta); +#endif // BRPC_BTHREAD_TRACER + if (args->meta->priority_index < 0) { + return g->push_rq(args->meta->tid); + } + return g->control()->push_ed_priority_queue( + args->tag, args->meta->priority_index, args->meta->tid); +} + +struct SleepArgs { + uint64_t timeout_us; + bthread_t tid; + TaskMeta* meta; + TaskGroup* group; +}; + +static void ready_to_run_from_timer_thread(void* arg) { + CHECK(BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group) == NULL); + const SleepArgs* e = static_cast(arg); + TaskGroup* g = e->group; + bthread_tag_t tag = g->tag(); + g->control()->choose_one_group(tag)->ready_to_run_remote(e->meta); +} + +void TaskGroup::_add_sleep_event(void* void_args) { + // Must copy SleepArgs. After calling TimerThread::schedule(), previous + // thread may be stolen by a worker immediately and the on-stack SleepArgs + // will be gone. + SleepArgs e = *static_cast(void_args); + TaskGroup* g = e.group; +#ifdef BRPC_BTHREAD_TRACER + g->_control->_task_tracer.set_status(TASK_STATUS_SUSPENDED, e.meta); +#endif // BRPC_BTHREAD_TRACER + + TimerThread::TaskId sleep_id; + sleep_id = get_global_timer_thread()->schedule( + ready_to_run_from_timer_thread, void_args, + butil::microseconds_from_now(e.timeout_us)); + + if (!sleep_id) { + e.meta->sleep_failed = true; + // Fail to schedule timer, go back to previous thread. + g->ready_to_run(e.meta); + return; + } + + // Set TaskMeta::current_sleep which is for interruption. + const uint32_t given_ver = get_version(e.tid); + { + BAIDU_SCOPED_LOCK(e.meta->version_lock); + if (given_ver == *e.meta->version_butex && !e.meta->interrupted) { + e.meta->current_sleep = sleep_id; + return; + } + } + // The thread is stopped or interrupted. + // interrupt() always sees that current_sleep == 0. It will not schedule + // the calling thread. The race is between current thread and timer thread. + if (get_global_timer_thread()->unschedule(sleep_id) == 0) { + // added to timer, previous thread may be already woken up by timer and + // even stopped. It's safe to schedule previous thread when unschedule() + // returns 0 which means "the not-run-yet sleep_id is removed". If the + // sleep_id is running(returns 1), ready_to_run_in_worker() will + // schedule previous thread as well. If sleep_id does not exist, + // previous thread is scheduled by timer thread before and we don't + // have to do it again. + g->ready_to_run(e.meta); + } +} + +// To be consistent with sys_usleep, set errno and return -1 on error. +int TaskGroup::usleep(TaskGroup** pg, uint64_t timeout_us) { + if (0 == timeout_us) { + yield(pg); + return 0; + } + TaskGroup* g = *pg; + // We have to schedule timer after we switched to next bthread otherwise + // the timer may wake up(jump to) current still-running context. + SleepArgs e = { timeout_us, g->current_tid(), g->current_task(), g }; + g->set_remained(_add_sleep_event, &e); + sched(pg); + g = *pg; + if (e.meta->sleep_failed) { + // Fail to schedule timer, return error. + e.meta->sleep_failed = false; + errno = ESTOP; + return -1; + } + e.meta->current_sleep = 0; + if (e.meta->interrupted) { + // Race with set and may consume multiple interruptions, which are OK. + e.meta->interrupted = false; + // NOTE: setting errno to ESTOP is not necessary from bthread's + // pespective, however many RPC code expects bthread_usleep to set + // errno to ESTOP when the thread is stopping, and print FATAL + // otherwise. To make smooth transitions, ESTOP is still set instead + // of EINTR when the thread is stopping. + errno = (e.meta->stop ? ESTOP : EINTR); + return -1; + } + return 0; +} + +// Defined in butex.cpp +bool erase_from_butex_because_of_interruption(ButexWaiter* bw); + +static int interrupt_and_consume_waiters( + bthread_t tid, ButexWaiter** pw, uint64_t* sleep_id) { + TaskMeta* const m = TaskGroup::address_meta(tid); + if (m == NULL) { + return EINVAL; + } + const uint32_t given_ver = get_version(tid); + BAIDU_SCOPED_LOCK(m->version_lock); + if (given_ver == *m->version_butex) { + *pw = m->current_waiter.exchange(NULL, butil::memory_order_acquire); + *sleep_id = m->current_sleep; + m->current_sleep = 0; // only one stopper gets the sleep_id + m->interrupted = true; + return 0; + } + return EINVAL; +} + +static int set_butex_waiter(bthread_t tid, ButexWaiter* w) { + TaskMeta* const m = TaskGroup::address_meta(tid); + if (m != NULL) { + const uint32_t given_ver = get_version(tid); + BAIDU_SCOPED_LOCK(m->version_lock); + if (given_ver == *m->version_butex) { + // Release fence makes m->interrupted visible to butex_wait + m->current_waiter.store(w, butil::memory_order_release); + return 0; + } + } + return EINVAL; +} + +// The interruption is "persistent" compared to the ones caused by signals, +// namely if a bthread is interrupted when it's not blocked, the interruption +// is still remembered and will be checked at next blocking. This designing +// choice simplifies the implementation and reduces notification loss caused +// by race conditions. +// TODO: bthreads created by BTHREAD_ATTR_PTHREAD blocking on bthread_usleep() +// can't be interrupted. +int TaskGroup::interrupt(bthread_t tid, TaskControl* c) { + // Consume current_waiter in the TaskMeta, wake it up then set it back. + ButexWaiter* w = NULL; + uint64_t sleep_id = 0; + int rc = interrupt_and_consume_waiters(tid, &w, &sleep_id); + if (rc) { + return rc; + } + // a bthread cannot wait on a butex and be sleepy at the same time. + CHECK(!sleep_id || !w); + if (w != NULL) { + erase_from_butex_because_of_interruption(w); + // If butex_wait() already wakes up before we set current_waiter back, + // the function will spin until current_waiter becomes non-NULL. + rc = set_butex_waiter(tid, w); + if (rc) { + LOG(FATAL) << "butex_wait should spin until setting back waiter"; + return rc; + } + } else if (sleep_id != 0) { + if (get_global_timer_thread()->unschedule(sleep_id) == 0) { + TaskGroup* g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + TaskMeta* m = address_meta(tid); + if (g) { + g->ready_to_run(m); + } else { + if (!c) { + return EINVAL; + } + c->choose_one_group(m->attr.tag)->ready_to_run_remote(m); + } + } + } + return 0; +} + +void TaskGroup::yield(TaskGroup** pg) { + TaskGroup* g = *pg; + ReadyToRunArgs args = { g->tag(), g->_cur_meta, false }; + g->set_remained(ready_to_run_in_worker, &args); + sched(pg); +} + +void print_task(std::ostream& os, bthread_t tid, bool enable_trace, + bool ignore_not_matched = false) { + TaskMeta* const m = TaskGroup::address_meta(tid); + if (m == NULL) { + os << "bthread=" << tid << " : never existed\n"; + return; + } + const uint32_t given_ver = get_version(tid); + bool matched = false; + bool stop = false; + bool interrupted = false; + bool about_to_quit = false; + void* (*fn)(void*) = NULL; + void* arg = NULL; + bthread_attr_t attr = BTHREAD_ATTR_NORMAL; + bool has_tls = false; + int64_t cpuwide_start_ns = 0; + TaskStatistics stat = {0, 0, 0}; + TaskStatus status = TASK_STATUS_UNKNOWN; + bool traced = false; + pthread_t worker_tid{}; + { + BAIDU_SCOPED_LOCK(m->version_lock); + if (given_ver == *m->version_butex) { + matched = true; + stop = m->stop; + interrupted = m->interrupted; + about_to_quit = m->about_to_quit; + fn = m->fn; + arg = m->arg; + attr = m->attr; + has_tls = m->local_storage.keytable; + cpuwide_start_ns = m->cpuwide_start_ns; + stat = m->stat; + status = m->status; + traced = m->traced; + worker_tid = m->worker_tid; + } + } + if (!matched) { + if (!ignore_not_matched) { + os << "bthread=" << tid << " : not exist now\n"; + } + } else { + os << "bthread=" << tid << " :\nstop=" << stop + << "\ninterrupted=" << interrupted + << "\nabout_to_quit=" << about_to_quit + << "\nfn=" << (void*)fn + << "\narg=" << (void*)arg + << "\nattr={stack_type=" << attr.stack_type + << " flags=" << attr.flags + << " specified_tag=" << attr.tag + << " name=" << attr.name + << " keytable_pool=" << attr.keytable_pool + << "}\nhas_tls=" << has_tls + << "\nuptime_ns=" << butil::cpuwide_time_ns() - cpuwide_start_ns + << "\ncputime_ns=" << stat.cputime_ns + << "\nnswitch=" << stat.nswitch +#ifdef BRPC_BTHREAD_TRACER + << "\nstatus=" << status + << "\ntraced=" << traced + << "\nworker_tid=" << worker_tid; + if (enable_trace) { + os << "\nbthread call stack:\n"; + stack_trace(os, tid); + } + os << "\n\n"; + #else + << "\n\n"; + (void)status;(void)traced;(void)worker_tid; +#endif // BRPC_BTHREAD_TRACER + } +} + +} // namespace bthread diff --git a/src/bthread/task_group.h b/src/bthread/task_group.h new file mode 100644 index 0000000..c21e06b --- /dev/null +++ b/src/bthread/task_group.h @@ -0,0 +1,390 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Tue Jul 10 17:40:58 CST 2012 + +#ifndef BTHREAD_TASK_GROUP_H +#define BTHREAD_TASK_GROUP_H + +#include "butil/time.h" // cpuwide_time_ns +#include "bthread/task_control.h" +#include "bthread/task_meta.h" // bthread_t, TaskMeta +#include "bthread/work_stealing_queue.h" // WorkStealingQueue +#include "bthread/remote_task_queue.h" // RemoteTaskQueue +#include "butil/resource_pool.h" // ResourceId +#include "bthread/parking_lot.h" +#include "bthread/prime_offset.h" + +namespace bthread { + +// For exiting a bthread. +class ExitException : public std::exception { +public: + explicit ExitException(void* value) : _value(value) {} + ~ExitException() throw() {} + const char* what() const throw() override { + return "ExitException"; + } + void* value() const { + return _value; + } +private: + void* _value; +}; + +// Refer to https://rigtorp.se/isatomic/, On the modern CPU microarchitectures +// (Skylake and Zen 2) AVX/AVX2 128b/256b aligned loads and stores are atomic +// even though Intel and AMD officially doesn’t guarantee this. +// On X86, SSE instructions can ensure atomic loads and stores. +// Starting from Armv8.4-A, neon can ensure atomic loads and stores. +// Otherwise, use mutex to guarantee atomicity. +class AtomicInteger128 { +public: + struct BAIDU_CACHELINE_ALIGNMENT Value { + int64_t v1; + int64_t v2; + }; + + AtomicInteger128() = default; + explicit AtomicInteger128(Value value) : _value(value) {} + + Value load() const; + Value load_unsafe() const { + return _value; + } + + void store(Value value); + +private: + Value _value{}; + // Used to protect `_cpu_time_stat' on architectures without lock-free 128-bit atomics. + FastPthreadMutex _mutex; + // Sequence counter for RISC-V seqlock implementation. + uint64_t _seq = 0; +}; + +// Thread-local group of tasks. +// Notice that most methods involving context switching are static otherwise +// pointer `this' may change after wakeup. The **pg parameters in following +// function are updated before returning. +class TaskGroup { +public: + // Create task `fn(arg)' with attributes `attr' in TaskGroup *pg and put + // the identifier into `tid'. Switch to the new task and schedule old task + // to run. + // Return 0 on success, errno otherwise. + static int start_foreground(TaskGroup** pg, + bthread_t* __restrict tid, + const bthread_attr_t* __restrict attr, + void * (*fn)(void*), + void* __restrict arg); + + // Create task `fn(arg)' with attributes `attr' in this TaskGroup, put the + // identifier into `tid'. Schedule the new thread to run. + // Called from worker: start_background + // Called from non-worker: start_background + // Return 0 on success, errno otherwise. + template + int start_background(bthread_t* __restrict tid, + const bthread_attr_t* __restrict attr, + void * (*fn)(void*), + void* __restrict arg); + + // Suspend caller and run next bthread in TaskGroup *pg. + static void sched(TaskGroup** pg); + static void ending_sched(TaskGroup** pg); + + // Suspend caller and run bthread `next_tid' in TaskGroup *pg. + // Purpose of this function is to avoid pushing `next_tid' to _rq and + // then being popped by sched(pg), which is not necessary. + static void sched_to(TaskGroup** pg, TaskMeta* next_meta); + static void sched_to(TaskGroup** pg, bthread_t next_tid); + static void exchange(TaskGroup** pg, TaskMeta* next_meta); + + // The callback will be run in the beginning of next-run bthread. + // Can't be called by current bthread directly because it often needs + // the target to be suspended already. + typedef void (*RemainedFn)(void*); + void set_remained(RemainedFn cb, void* arg) { + _last_context_remained = cb; + _last_context_remained_arg = arg; + } + + // Suspend caller for at least |timeout_us| microseconds. + // If |timeout_us| is 0, this function does nothing. + // If |group| is NULL or current thread is non-bthread, call usleep(3) + // instead. This function does not create thread-local TaskGroup. + // Returns: 0 on success, -1 otherwise and errno is set. + static int usleep(TaskGroup** pg, uint64_t timeout_us); + + // Suspend caller and run another bthread. When the caller will resume + // is undefined. + static void yield(TaskGroup** pg); + + // Suspend caller until bthread `tid' terminates. + static int join(bthread_t tid, void** return_value); + + // Returns true iff the bthread `tid' still exists. Notice that it is + // just the result at this very moment which may change soon. + // Don't use this function unless you have to. Never write code like this: + // if (exists(tid)) { + // Wait for events of the thread. // Racy, may block indefinitely. + // } + static bool exists(bthread_t tid); + + // Put attribute associated with `tid' into `*attr'. + // Returns 0 on success, -1 otherwise and errno is set. + static int get_attr(bthread_t tid, bthread_attr_t* attr); + + // Get/set TaskMeta.stop of the tid. + static void set_stopped(bthread_t tid); + static bool is_stopped(bthread_t tid); + + // The bthread running run_main_task(); + bthread_t main_tid() const { return _main_tid; } + TaskStatistics main_stat() const; + // Routine of the main task which should be called from a dedicated pthread. + void run_main_task(); + + // current_task is a function in macOS 10.0+ +#ifdef current_task +#undef current_task +#endif + // Meta/Identifier of current task in this group. + TaskMeta* current_task() const { return _cur_meta; } + bthread_t current_tid() const { return _cur_meta->tid; } + // Uptime of current task in nanoseconds. + int64_t current_uptime_ns() const + { return butil::cpuwide_time_ns() - _cur_meta->cpuwide_start_ns; } + + // True iff current task is the one running run_main_task() + bool is_current_main_task() const { return current_tid() == _main_tid; } + // True iff current task is in pthread-mode. + bool is_current_pthread_task() const + { return _cur_meta->stack == _main_stack; } + + // Active time in nanoseconds spent by this TaskGroup. + int64_t cumulated_cputime_ns() const; + + // Push a bthread into the runqueue + void ready_to_run(TaskMeta* meta, bool nosignal = false); + // Flush tasks pushed to rq but signalled. + void flush_nosignal_tasks(); + + // Push a bthread into the runqueue from another non-worker thread. + void ready_to_run_remote(TaskMeta* meta, bool nosignal = false); + void flush_nosignal_tasks_remote_locked(butil::Mutex& locked_mutex); + void flush_nosignal_tasks_remote(); + + // Automatically decide the caller is remote or local, and call + // the corresponding function. + void ready_to_run_general(TaskMeta* meta, bool nosignal = false); + void flush_nosignal_tasks_general(); + + // The TaskControl that this TaskGroup belongs to. + TaskControl* control() const { return _control; } + + // Call this instead of delete. + void destroy_self(); + + // Wake up blocking ops in the thread. + // Returns 0 on success, errno otherwise. + static int interrupt(bthread_t tid, TaskControl* c); + + // Get the meta associate with the task. + static TaskMeta* address_meta(bthread_t tid); + + // Push a task into _rq, if _rq is full, retry after some time. This + // process make go on indefinitely. + void push_rq(bthread_t tid); + + // Returns size of local run queue. + size_t rq_size() const { + return _rq.volatile_size(); + } + + bthread_tag_t tag() const { return _tag; } + + pthread_t tid() const { return _tid; } + + int64_t current_task_cpu_clock_ns() { + if (_last_cpu_clock_ns == 0) { + return 0; + } + int64_t total_ns = _cur_meta->stat.cpu_usage_ns; + total_ns += butil::cputhread_time_ns() - _last_cpu_clock_ns; + return total_ns; + } + +private: +friend class TaskControl; + + // Last scheduling time, task type and cumulated CPU time. + class CPUTimeStat { + static constexpr int64_t LAST_SCHEDULING_TIME_MASK = 0x7FFFFFFFFFFFFFFFLL; + static constexpr int64_t TASK_TYPE_MASK = 0x8000000000000000LL; + public: + CPUTimeStat() : _last_run_ns_and_type(0), _cumulated_cputime_ns(0) {} + CPUTimeStat(AtomicInteger128::Value value) + : _last_run_ns_and_type(value.v1), _cumulated_cputime_ns(value.v2) {} + + // Convert to AtomicInteger128::Value for atomic operations. + explicit operator AtomicInteger128::Value() const { + return {_last_run_ns_and_type, _cumulated_cputime_ns}; + } + + void set_last_run_ns(int64_t last_run_ns, bool main_task) { + _last_run_ns_and_type = (last_run_ns & LAST_SCHEDULING_TIME_MASK) | + (static_cast(main_task) << 63); + } + int64_t last_run_ns() const { + return _last_run_ns_and_type & LAST_SCHEDULING_TIME_MASK; + } + int64_t last_run_ns_and_type() const { + return _last_run_ns_and_type; + } + + bool is_main_task() const { + return _last_run_ns_and_type & TASK_TYPE_MASK; + } + + void add_cumulated_cputime_ns(int64_t cputime_ns, bool main_task) { + if (main_task) { + return; + } + _cumulated_cputime_ns += cputime_ns; + } + int64_t cumulated_cputime_ns() const { + return _cumulated_cputime_ns; + } + + private: + // The higher bit for task type, main task is 1, otherwise 0. + // Lowest 63 bits for last scheduling time. + int64_t _last_run_ns_and_type; + // Cumulated CPU time in nanoseconds. + int64_t _cumulated_cputime_ns; + }; + + class AtomicCPUTimeStat { + public: + CPUTimeStat load() const { + return _cpu_time_stat.load(); + } + CPUTimeStat load_unsafe() const { + return _cpu_time_stat.load_unsafe(); + } + + void store(CPUTimeStat cpu_time_stat) { + _cpu_time_stat.store(AtomicInteger128::Value(cpu_time_stat)); + } + + private: + AtomicInteger128 _cpu_time_stat; + }; + + // You shall use TaskControl::create_group to create new instance. + explicit TaskGroup(TaskControl* c); + + int init(size_t runqueue_capacity); + + // You shall call destroy_selfm() instead of destructor because deletion + // of groups are postponed to avoid race. + ~TaskGroup(); + +#ifdef BUTIL_USE_ASAN + static void asan_task_runner(intptr_t); +#endif // BUTIL_USE_ASAN + static void task_runner(intptr_t skip_remained); + + // Callbacks for set_remained() + static void _release_last_context(void*); + static void _add_sleep_event(void*); + struct ReadyToRunArgs { + bthread_tag_t tag; + TaskMeta* meta; + bool nosignal; + }; + static void ready_to_run_in_worker(void*); + static void ready_to_run_in_worker_ignoresignal(void*); + static void priority_to_run(void*); + + // Wait for a task to run. + // Returns true on success, false is treated as permanent error and the + // loop calling this function should end. + bool wait_task(bthread_t* tid); + + bool steal_task(bthread_t* tid) { + if (_remote_rq.pop(tid)) { + return true; + } +#ifndef BTHREAD_DONT_SAVE_PARKING_STATE + _last_pl_state = _pl->get_state(); +#endif + return _control->steal_task(tid, &_steal_seed, _steal_offset); + } + + void set_tag(bthread_tag_t tag) { _tag = tag; } + + void set_pl(ParkingLot* pl) { _pl = pl; } + + static bool is_main_task(TaskGroup* g, bthread_t tid) { + return g->_main_tid == tid; + } + + TaskMeta* _cur_meta{NULL}; + + // the control that this group belongs to + TaskControl* _control{NULL}; + int _num_nosignal{0}; + int _nsignaled{0}; + AtomicCPUTimeStat _cpu_time_stat; + // last thread cpu clock + int64_t _last_cpu_clock_ns{0}; + + size_t _nswitch{0}; + RemainedFn _last_context_remained{NULL}; + void* _last_context_remained_arg{NULL}; + + ParkingLot* _pl{NULL}; +#ifndef BTHREAD_DONT_SAVE_PARKING_STATE + ParkingLot::State _last_pl_state; +#endif + size_t _steal_seed{butil::fast_rand()}; + size_t _steal_offset{prime_offset(_steal_seed)}; + ContextualStack* _main_stack{NULL}; + bthread_t _main_tid{INVALID_BTHREAD}; + WorkStealingQueue _rq; + RemoteTaskQueue _remote_rq; + int _remote_num_nosignal{0}; + int _remote_nsignaled{0}; + + int _sched_recursive_guard{0}; + // tag of this taskgroup + bthread_tag_t _tag{BTHREAD_TAG_DEFAULT}; + + // Worker thread id. + pthread_t _tid{}; +}; + +} // namespace bthread + +#include "task_group_inl.h" + +#endif // BTHREAD_TASK_GROUP_H diff --git a/src/bthread/task_group_inl.h b/src/bthread/task_group_inl.h new file mode 100644 index 0000000..faa5683 --- /dev/null +++ b/src/bthread/task_group_inl.h @@ -0,0 +1,113 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Tue Jul 10 17:40:58 CST 2012 + +#ifndef BTHREAD_TASK_GROUP_INL_H +#define BTHREAD_TASK_GROUP_INL_H + +namespace bthread { + +// Utilities to manipulate bthread_t +inline bthread_t make_tid(uint32_t version, butil::ResourceId slot) { + return (((bthread_t)version) << 32) | (bthread_t)slot.value; +} + +inline butil::ResourceId get_slot(bthread_t tid) { + butil::ResourceId id = { (tid & 0xFFFFFFFFul) }; + return id; +} +inline uint32_t get_version(bthread_t tid) { + return (uint32_t)((tid >> 32) & 0xFFFFFFFFul); +} + +inline TaskMeta* TaskGroup::address_meta(bthread_t tid) { + // TaskMeta * m = address_resource(get_slot(tid)); + // if (m != NULL && m->version == get_version(tid)) { + // return m; + // } + // return NULL; + return address_resource(get_slot(tid)); +} + +inline void TaskGroup::exchange(TaskGroup** pg, TaskMeta* next_meta) { + TaskGroup* g = *pg; + if (g->is_current_pthread_task()) { + return g->ready_to_run(next_meta); + } + ReadyToRunArgs args = { g->tag(), g->_cur_meta, false }; + g->set_remained((g->current_task()->about_to_quit + ? ready_to_run_in_worker_ignoresignal + : ready_to_run_in_worker), + &args); + TaskGroup::sched_to(pg, next_meta); +} + +inline void TaskGroup::sched_to(TaskGroup** pg, bthread_t next_tid) { + TaskMeta* next_meta = address_meta(next_tid); + if (next_meta->stack == NULL) { +#ifdef BUTIL_USE_ASAN + ContextualStack* stk = get_stack(next_meta->stack_type(), asan_task_runner); +#else + ContextualStack* stk = get_stack(next_meta->stack_type(), task_runner); +#endif // BUTIL_USE_ASAN + if (stk) { + next_meta->set_stack(stk); + } else { + // stack_type is BTHREAD_STACKTYPE_PTHREAD or out of memory, + // In latter case, attr is forced to be BTHREAD_STACKTYPE_PTHREAD. + // This basically means that if we can't allocate stack, run + // the task in pthread directly. + next_meta->attr.stack_type = BTHREAD_STACKTYPE_PTHREAD; + next_meta->set_stack((*pg)->_main_stack); + } + } + // Update now_ns only when wait_task did yield. + sched_to(pg, next_meta); +} + +inline void TaskGroup::push_rq(bthread_t tid) { + while (!_rq.push(tid)) { + // Created too many bthreads: a promising approach is to insert the + // task into another TaskGroup, but we don't use it because: + // * There're already many bthreads to run, inserting the bthread + // into other TaskGroup does not help. + // * Insertions into other TaskGroups perform worse when all workers + // are busy at creating bthreads (proved by test_input_messenger in + // brpc) + flush_nosignal_tasks(); + LOG_EVERY_SECOND(ERROR) << "_rq is full, capacity=" << _rq.capacity(); + // TODO(gejun): May cause deadlock when all workers are spinning here. + // A better solution is to pop and run existing bthreads, however which + // make set_remained()-callbacks do context switches and need extensive + // reviews on related code. + ::usleep(1000); + } +} + +inline void TaskGroup::flush_nosignal_tasks_remote() { + if (_remote_num_nosignal) { + _remote_rq._mutex.lock(); + flush_nosignal_tasks_remote_locked(_remote_rq._mutex); + } +} + +} // namespace bthread + +#endif // BTHREAD_TASK_GROUP_INL_H diff --git a/src/bthread/task_meta.h b/src/bthread/task_meta.h new file mode 100644 index 0000000..074af43 --- /dev/null +++ b/src/bthread/task_meta.h @@ -0,0 +1,177 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Tue Jul 10 17:40:58 CST 2012 + +#ifndef BTHREAD_TASK_META_H +#define BTHREAD_TASK_META_H + +#include // pthread_spin_init +#include "bthread/butex.h" // butex_construct/destruct +#include "butil/atomicops.h" // butil::atomic +#include "bthread/types.h" // bthread_attr_t +#include "bthread/stack.h" // ContextualStack +#include "bthread/timer_thread.h" +#include "butil/thread_local.h" + +namespace bthread { + +struct TaskStatistics { + int64_t cputime_ns; + int64_t nswitch; + int64_t cpu_usage_ns; +}; + +class KeyTable; +struct ButexWaiter; + +struct LocalStorage { + KeyTable* keytable; + void* assigned_data; + void* rpcz_parent_span; // Points to std::weak_ptr* (managed by brpc) +}; + +#define BTHREAD_LOCAL_STORAGE_INITIALIZER { NULL, NULL, NULL } + +const static LocalStorage LOCAL_STORAGE_INIT = BTHREAD_LOCAL_STORAGE_INITIALIZER; + +EXTERN_BAIDU_VOLATILE_THREAD_LOCAL(LocalStorage, tls_bls); + +enum TaskStatus { + TASK_STATUS_UNKNOWN, + TASK_STATUS_CREATED, + TASK_STATUS_FIRST_READY, + TASK_STATUS_READY, + TASK_STATUS_JUMPING, + TASK_STATUS_RUNNING, + TASK_STATUS_SUSPENDED, + TASK_STATUS_END, +}; + +struct TaskMeta { + // [Not Reset] + butil::atomic current_waiter{NULL}; + uint64_t current_sleep{TimerThread::INVALID_TASK_ID}; + + // A flag to mark if the Timer scheduling failed. + bool sleep_failed{false}; + + // A builtin flag to mark if the thread is stopping. + bool stop{false}; + + // The thread is interrupted and should wake up from some blocking ops. + bool interrupted{false}; + + // Scheduling of the thread can be delayed. + bool about_to_quit{false}; + + // [Not Reset] guarantee visibility of version_butex. + pthread_spinlock_t version_lock{}; + + // [Not Reset] only modified by one bthread at any time, no need to be atomic + uint32_t* version_butex{NULL}; + + // The identifier. It does not have to be here, however many code is + // simplified if they can get tid from TaskMeta. + bthread_t tid{INVALID_BTHREAD}; + + int priority_index{-1}; + + // User function and argument + void* (*fn)(void*){NULL}; + void* arg{NULL}; + + // Stack of this task. + ContextualStack* stack{NULL}; + + // Attributes creating this task + bthread_attr_t attr{BTHREAD_ATTR_NORMAL}; + + // Statistics + int64_t cpuwide_start_ns{0}; + TaskStatistics stat{}; + + // bthread local storage, sync with tls_bls (defined in task_group.cpp) + // when the bthread is created or destroyed. + // DO NOT use this field directly, use tls_bls instead. + LocalStorage local_storage{}; + + // Only used when TaskTracer is enabled. + // Bthread status. + TaskStatus status{TASK_STATUS_UNKNOWN}; + // Whether bthread is traced? + bool traced{false}; + // [Not Reset] guarantee tracing completion before jumping. + pthread_mutex_t trace_lock{}; + // Worker thread id. + pthread_t worker_tid{}; + +public: + // Only initialize [Not Reset] fields, other fields will be reset in + // bthread_start* functions + TaskMeta() { + pthread_spin_init(&version_lock, 0); + version_butex = butex_create_checked(); + *version_butex = 1; + pthread_mutex_init(&trace_lock, NULL); + } + + ~TaskMeta() { + pthread_mutex_destroy(&trace_lock); + butex_destroy(version_butex); + version_butex = NULL; + pthread_spin_destroy(&version_lock); + } + + void set_stack(ContextualStack* s) { + stack = s; + } + + ContextualStack* release_stack() { + ContextualStack* tmp = stack; + stack = NULL; + return tmp; + } + + StackType stack_type() const { + return static_cast(attr.stack_type); + } +}; + +// Global callback for creating a new bthread span when creating a new bthread. +// This is set by brpc layer. When a bthread is created with BTHREAD_INHERIT_SPAN, +// this callback is invoked to create a new span for the bthread. +// The returned void* points to a heap-allocated weak_ptr* managed by brpc layer. +// Returns NULL if span creation is disabled or fails. +extern void* (*g_create_bthread_span)(); + +// Global destructor callback for rpcz_parent_span. +// This is set by brpc layer to clean up the heap-allocated weak_ptr. +// bthread layer doesn't know the concrete type, it just calls this function +// with the void* pointer when cleaning up LocalStorage. +extern void (*g_rpcz_parent_span_dtor)(void*); + +// Global callback invoked when a bthread ends (used by higher layers to +// observe and react to bthread end events, e.g., to finish spans). This +// pointer is set by the upper layer during initialization. +extern void (*g_end_bthread_span)(); + +} // namespace bthread + +#endif // BTHREAD_TASK_META_H diff --git a/src/bthread/task_tracer.cpp b/src/bthread/task_tracer.cpp new file mode 100644 index 0000000..e6049f0 --- /dev/null +++ b/src/bthread/task_tracer.cpp @@ -0,0 +1,462 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +#ifdef BRPC_BTHREAD_TRACER + +#include +#include +#include +#include +#include +#include +#include +#include "bthread/task_tracer.h" +#include "bthread/processor.h" +#include "bthread/task_group.h" +#include "butil/fd_utility.h" +#include "butil/memory/scope_guard.h" +#include "butil/reloadable_flags.h" +#include "absl/debugging/stacktrace.h" +#include "absl/debugging/symbolize.h" + +namespace bthread { + +DEFINE_uint32(signal_trace_timeout_ms, 50, "Timeout for signal trace in ms"); +BUTIL_VALIDATE_GFLAG(signal_trace_timeout_ms, butil::PositiveInteger); +// Note that SIGURG handler may be registered by some library such as cgo +// so let the signal number be configurable +DEFINE_int32(signal_number_for_trace, SIGURG, + "signal number used for stack trace, default to SIGURG"); + +extern BAIDU_THREAD_LOCAL TaskMeta* pthread_fake_meta; + +TaskTracer::SignalSync::~SignalSync() { + if (pipe_fds[0] >= 0) { + close(pipe_fds[0]); + } + if (pipe_fds[1] >= 0) { + close(pipe_fds[1]); + } +} + +bool TaskTracer::SignalSync::Init() { + if (pipe(pipe_fds) != 0) { + PLOG(ERROR) << "Fail to pipe"; + return false; + } + if (butil::make_non_blocking(pipe_fds[0]) != 0) { + PLOG(ERROR) << "Fail to make_non_blocking"; + return false; + } + if (butil::make_non_blocking(pipe_fds[1]) != 0) { + PLOG(ERROR) << "Fail to make_non_blocking"; + return false; + } + + return true; +} + +std::string TaskTracer::Result::OutputToString() const { + std::string str; + if (frame_count > 0) { + str.reserve(1024); + } + char symbol_name[128]; + char unknown_symbol_name[] = ""; + if (frame_count > 0) { + for (size_t i = 0; i < frame_count; ++i) { + butil::string_appendf(&str, "#%zu 0x%016" PRIxPTR " ", i, + reinterpret_cast(ips[i])); + if (absl::Symbolize(ips[i], symbol_name, arraysize(symbol_name))) { + str.append(symbol_name); + } else { + str.append(unknown_symbol_name); + } + if (i + 1 < frame_count) { + str.push_back('\n'); + } + } + } else { + str.append("No frame"); + } + if (error) { + str.append("\nError message: "); + str.append(err_msg); + } + + return str; +} + +void TaskTracer::Result::OutputToStream(std::ostream& os) const { + char symbol_name[128]; + char unknown_symbol_name[] = ""; + if (frame_count > 0) { + for (size_t i = 0; i < frame_count; ++i) { + os << "#" << i << " 0x" + << std::hex << std::setw(16) << std::setfill('0') + << reinterpret_cast(ips[i]) + << std::dec << std::setfill(' ') << " "; + if (absl::Symbolize(ips[i], symbol_name, arraysize(symbol_name))) { + os << symbol_name; + } else { + os << unknown_symbol_name; + } + if (i + 1 < frame_count) { + os << '\n'; + } + } + } else { + os << "No frame"; + } + if (error) { + os << "\nError message: " << err_msg; + } +} + +bool TaskTracer::Init() { + if (_trace_time.expose("bthread_trace_time") != 0) { + LOG(ERROR) << "Fail to expose bthread_trace_time"; + return false; + } + if (!RegisterSignalHandler()) { + return false; + } + // Warm up the libunwind. + unw_cursor_t cursor; + if (0 == unw_getcontext(&_context) && unw_init_local(&cursor, &_context) == 0) { + butil::ignore_result(TraceByLibunwind(cursor)); + } + return true; +} + +void TaskTracer::set_status(TaskStatus s, TaskMeta* m) { + CHECK_NE(TASK_STATUS_RUNNING, s) << "Use `set_running_status' instead"; + CHECK_NE(TASK_STATUS_END, s) << "Use `set_end_status_unsafe' instead"; + + bool tracing = false; + { + BAIDU_SCOPED_LOCK(m->version_lock); + if (TASK_STATUS_UNKNOWN == m->status && TASK_STATUS_JUMPING == s) { + // Do not update status for jumping when bthread is ending. + return; + } + + tracing = m->traced; + // bthread is scheduled for the first time. + if (TASK_STATUS_READY == s && NULL == m->stack) { + m->status = TASK_STATUS_FIRST_READY; + } else { + m->status = s; + } + if (TASK_STATUS_CREATED == s) { + m->worker_tid = pthread_t{}; + } + } + + // Make sure bthread does not jump stack when it is being traced. + if (tracing && TASK_STATUS_JUMPING == s) { + WaitForTracing(m); + } +} + +void TaskTracer::set_running_status(pthread_t worker_tid, TaskMeta* m) { + BAIDU_SCOPED_LOCK(m->version_lock); + m->worker_tid = worker_tid; + m->status = TASK_STATUS_RUNNING; +} + +bool TaskTracer::set_end_status_unsafe(TaskMeta* m) { + m->status = TASK_STATUS_END; + return m->traced; +} + +std::string TaskTracer::Trace(bthread_t tid) { + return TraceImpl(tid).OutputToString(); +} + +void TaskTracer::Trace(std::ostream& os, bthread_t tid) { + TraceImpl(tid).OutputToStream(os); +} + +void TaskTracer::WaitForTracing(TaskMeta* m) { + BAIDU_SCOPED_LOCK(m->trace_lock); + // Acquiring trace_lock means tracing is done. +} + +TaskTracer::Result TaskTracer::TraceImpl(bthread_t tid) { + butil::Timer timer(butil::Timer::STARTED); + BRPC_SCOPE_EXIT { + timer.stop(); + _trace_time << timer.n_elapsed(); + }; + + if (tid == bthread_self() || + (NULL != pthread_fake_meta && tid == pthread_fake_meta->tid)) { + return Result::MakeErrorResult("Forbid to trace self=%d", tid); + } + + // Make sure only one bthread is traced at a time. + BAIDU_SCOPED_LOCK(_trace_request_mutex); + + // The chance to remove unused SignalSyncs. + auto iter = std::remove_if( + _inuse_signal_syncs.begin(), _inuse_signal_syncs.end(), + [](butil::intrusive_ptr& sync) { + return sync->ref_count() == 1; + }); + _inuse_signal_syncs.erase(iter, _inuse_signal_syncs.end()); + + TaskMeta* m = TaskGroup::address_meta(tid); + if (NULL == m) { + return Result::MakeErrorResult("bthread=%d never existed", tid); + } + + BAIDU_SCOPED_LOCK(m->trace_lock); + TaskStatus status; + pthread_t worker_tid; + const uint32_t given_version = get_version(tid); + { + BAIDU_SCOPED_LOCK(m->version_lock); + if (given_version != *m->version_butex) { + return Result::MakeErrorResult("bthread=%d not exist now", tid); + } + + status = m->status; + if (TASK_STATUS_UNKNOWN == status) { + return Result::MakeErrorResult("bthread=%d not exist now", tid); + } else if (TASK_STATUS_CREATED == status) { + return Result::MakeErrorResult("bthread=%d has just been created", tid); + } else if (TASK_STATUS_FIRST_READY == status) { + return Result::MakeErrorResult("bthread=%d is scheduled for the first time", tid); + } else if (TASK_STATUS_END == status) { + return Result::MakeErrorResult("bthread=%d has ended", tid); + } else if (TASK_STATUS_JUMPING == status) { + return Result::MakeErrorResult("bthread=%d is jumping stack", tid); + } + // Start tracing. + m->traced = true; + worker_tid = m->worker_tid; + } + + BRPC_SCOPE_EXIT { + { + BAIDU_SCOPED_LOCK(m->version_lock); + // If m->status is BTHREAD_STATUS_END, the bthread also waits for + // tracing completion, so given_version != *m->version_butex is OK. + m->traced = false; + } + }; + + // The status may be RUNNING, SUSPENDED, or READY, which is traceable. + if (TASK_STATUS_RUNNING == status) { + return SignalTrace(worker_tid); + } else if (TASK_STATUS_SUSPENDED == status || TASK_STATUS_READY == status) { + return ContextTrace(m->stack->context); + } + + return Result::MakeErrorResult("Invalid TaskStatus=%d of bthread=%d", status, tid); +} + +// Instruct ASan to ignore this function. +BUTIL_ATTRIBUTE_NO_SANITIZE_ADDRESS +unw_cursor_t TaskTracer::MakeCursor(bthread_fcontext_t fcontext) { + unw_cursor_t cursor; + unw_init_local(&cursor, &_context); + auto regs = reinterpret_cast(fcontext); + + // Only need RBP, RIP, RSP on x86_64. + // The base pointer (RBP). + if (unw_set_reg(&cursor, UNW_X86_64_RBP, regs[6]) != 0) { + LOG(ERROR) << "Fail to set RBP"; + } + // The instruction pointer (RIP). + if (unw_set_reg(&cursor, UNW_REG_IP, regs[7]) != 0) { + LOG(ERROR) << "Fail to set RIP"; + } +#if UNW_VERSION_MAJOR >= 1 && UNW_VERSION_MINOR >= 7 + // The stack pointer (RSP). + if (unw_set_reg(&cursor, UNW_REG_SP, regs[8]) != 0) { + LOG(ERROR) << "Fail to set RSP"; + } +#endif + + return cursor; +} + +TaskTracer::Result TaskTracer::ContextTrace(bthread_fcontext_t fcontext) { + unw_cursor_t cursor = MakeCursor(fcontext); + return TraceByLibunwind(cursor); +} + +TaskTracer::Result TaskTracer::TraceByLibunwind(unw_cursor_t& cursor) { + Result result; + while (result.frame_count < arraysize(result.ips)) { + int rc = unw_step(&cursor); + if (0 == rc) { + break; + } else if (rc < 0) { + return Result::MakeErrorResult("Fail to unw_step, rc=%d", rc); + } + + unw_word_t ip = 0; + if (0 != unw_get_reg(&cursor, UNW_REG_IP, &ip)) { + continue; + } + result.ips[result.frame_count] = reinterpret_cast(ip); + ++result.frame_count; + } + + return result; +} + +bool TaskTracer::RegisterSignalHandler() { + // Set up the signal handler. + _signal_num = FLAGS_signal_number_for_trace; + struct sigaction old_sa{}; + struct sigaction sa{}; + sa.sa_sigaction = SignalHandler; + sa.sa_flags = SA_SIGINFO; + sigfillset(&sa.sa_mask); + if (sigaction(_signal_num, &sa, &old_sa) != 0) { + PLOG(ERROR) << "Failed to sigaction"; + return false; + } + if (NULL != old_sa.sa_handler || NULL != old_sa.sa_sigaction) { + LOG(ERROR) << "Signal handler of signal number " + << _signal_num << " is already registered"; + return false; + } + + return true; +} + +// Caution: This function should be async-signal-safety. +void TaskTracer::SignalHandler(int, siginfo_t* info, void* context) { + ErrnoGuard guard; + // Ref has been taken before the signal is sent, so no need to add ref here. + butil::intrusive_ptr signal_sync( + static_cast(info->si_value.sival_ptr), false); + if (NULL == signal_sync) { + // The signal is not from Tracer, such as TaskControl, do nothing. + return; + } + // Skip the first frame, which is the signal handler itself. + signal_sync->result.frame_count = absl::DefaultStackUnwinder(signal_sync->result.ips, NULL, + arraysize(signal_sync->result.ips), 1, + context, NULL); + // write() is async-signal-safe. + // Don't care about the return value. + butil::ignore_result(write(signal_sync->pipe_fds[1], "1", 1)); +} + +TaskTracer::Result TaskTracer::SignalTrace(pthread_t worker_tid) { + // CAUTION: + // https://github.com/gperftools/gperftools/wiki/gperftools'-stacktrace-capturing-methods-and-their-issues#libunwind + // Generally, libunwind promises async-signal-safety for capturing backtraces. + // But in practice, it is only partially async-signal-safe due to reliance on + // dl_iterate_phdr API, which is used to enumerate all loaded ELF modules + // (.so files and main executable binary). No libc offers dl_iterate_pdhr that + // is async-signal-safe. In practice, the issue may happen if we take tracing + // signal during an existing dl_iterate_phdr call (such as when the program + // throws an exception) or during dlopen/dlclose-ing some .so module. + // Deadlock call stack: + // #0 __lll_lock_wait (futex=futex@entry=0x7f0d3d7f0990 <_rtld_global+2352>, private=0) at lowlevellock.c:52 + // #1 0x00007f0d3a73c131 in __GI___pthread_mutex_lock (mutex=0x7f0d3d7f0990 <_rtld_global+2352>) at ../nptl/pthread_mutex_lock.c:115 + // #2 0x00007f0d38eb0231 in __GI___dl_iterate_phdr (callback=callback@entry=0x7f0d38c456a0 <_ULx86_64_dwarf_callback>, data=data@entry=0x7f0d07defad0) at dl-iteratephdr.c:40 + // #3 0x00007f0d38c45d79 in _ULx86_64_dwarf_find_proc_info (as=0x7f0d38c4f340 , ip=ip@entry=139694791966897, pi=pi@entry=0x7f0d07df0498, need_unwind_info=need_unwind_info@entry=1, arg=0x7f0d07df0340) at dwarf/Gfind_proc_info-lsb.c:759 + // #4 0x00007f0d38c43260 in fetch_proc_info (c=c@entry=0x7f0d07df0340, ip=139694791966897) at dwarf/Gparser.c:461 + // #5 0x00007f0d38c44e46 in find_reg_state (sr=0x7f0d07defd10, c=0x7f0d07df0340) at dwarf/Gparser.c:925 + // #6 _ULx86_64_dwarf_step (c=c@entry=0x7f0d07df0340) at dwarf/Gparser.c:972 + // #7 0x00007f0d38c40c14 in _ULx86_64_step (cursor=cursor@entry=0x7f0d07df0340) at x86_64/Gstep.c:71 + // #8 0x00007f0d399ed8f6 in GetStackTraceWithContext_libunwind (result=, max_depth=63, skip_count=132054887, ucp=) at src/stacktrace_libunwind-inl.h:138 + // #9 0x00007f0d399ee083 in GetStackTraceWithContext (result=0x7f0d07df07b8, max_depth=63, skip_count=3, uc=0x7f0d07df0a40) at src/stacktrace.cc:305 + // #10 0x00007f0d399ea992 in CpuProfiler::prof_handler (signal_ucontext=, cpu_profiler=0x7f0d399f6600, sig=) at src/profiler.cc:359 + // #11 0x00007f0d399eb633 in ProfileHandler::SignalHandler (sig=27, sinfo=0x7f0d07df0b70, ucontext=0x7f0d07df0a40) at src/profile-handler.cc:530 + // #12 + // #13 0x00007f0d3a73c0b1 in __GI___pthread_mutex_lock (mutex=0x7f0d3d7f0990 <_rtld_global+2352>) at ../nptl/pthread_mutex_lock.c:115 + // #14 0x00007f0d38eb0231 in __GI___dl_iterate_phdr (callback=0x7f0d38f525f0, data=0x7f0d07df10c0) at dl-iteratephdr.c:40 + // #15 0x00007f0d38f536c1 in _Unwind_Find_FDE () from /lib/x86_64-linux-gnu/libgcc_s.so.1 + // #16 0x00007f0d38f4f868 in ?? () from /lib/x86_64-linux-gnu/libgcc_s.so.1 + // #17 0x00007f0d38f50a20 in ?? () from /lib/x86_64-linux-gnu/libgcc_s.so.1 + // #18 0x00007f0d38f50f99 in _Unwind_RaiseException () from /lib/x86_64-linux-gnu/libgcc_s.so.1 + // #19 0x00007f0d390088dc in __cxa_throw () from /lib/x86_64-linux-gnu/libstdc++.so.6 + // #20 0x00007f0d3b5b2245 in __cxxabiv1::__cxa_throw (thrownException=0x7f0d114ea8c0, type=0x7f0d3d6dd830 , destructor=) at /src/folly/folly/experimental/exception_tracer/ExceptionTracerLib.cpp:107 + // + // Therefore, use async-signal-safe absl::DefaultStackUnwinder instead of libunwind. + + if (pthread_equal(worker_tid, pthread_self())) { + return Result::MakeErrorResult("Forbid to trace self"); + } + + // Each signal trace has an independent SignalSync to + // prevent the previous SignalHandler from affecting the new SignalTrace. + butil::intrusive_ptr signal_sync(new SignalSync()); + if (!signal_sync->Init()) { + return Result::MakeErrorResult("Fail to init SignalSync"); + } + + // Add reference for SignalHandler. + signal_sync->AddRefManually(); + + sigval value{}; + value.sival_ptr = signal_sync.get(); + size_t sigqueue_try = 0; + while (pthread_sigqueue(worker_tid, _signal_num, value) != 0) { + if (errno != EAGAIN || sigqueue_try++ >= 3) { + // Remove reference for SignalHandler. + signal_sync->RemoveRefManually(); + return Result::MakeErrorResult("Fail to pthread_sigqueue: %s", berror()); + } + } + + // Wait for the signal handler to complete. + butil::Timer timer; + if (FLAGS_signal_trace_timeout_ms > 0) { + timer.start(); + } + pollfd poll_fd = {signal_sync->pipe_fds[0], POLLIN, 0}; + // Wait for tracing to complete. + while (true) { + int timeout = -1; + if (FLAGS_signal_trace_timeout_ms > 0) { + timer.stop(); + if (timer.m_elapsed() >= FLAGS_signal_trace_timeout_ms) { + return Result::MakeErrorResult("Timeout exceed %dms", FLAGS_signal_trace_timeout_ms); + } + timeout = (int64_t)FLAGS_signal_trace_timeout_ms - timer.m_elapsed(); + } + // poll() is async-signal-safe. + // Self-pipe trick: https://man7.org/tlpi/code/online/dist/altio/self_pipe.c.html + int rc = poll(&poll_fd, 1, timeout); + if (-1 == rc) { + if (EINTR == errno) { + continue; + } + // Extend the lifetime of `signal_sync' to + // avoid it being destroyed in the signal handler. + _inuse_signal_syncs.push_back(signal_sync); + return Result::MakeErrorResult("Fail to poll: %s", berror()); + } + break; + } + + return signal_sync->result; +} + +} // namespace bthread + +#endif // BRPC_BTHREAD_TRACER diff --git a/src/bthread/task_tracer.h b/src/bthread/task_tracer.h new file mode 100644 index 0000000..8844413 --- /dev/null +++ b/src/bthread/task_tracer.h @@ -0,0 +1,128 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BTHREAD_TASK_TRACER_H +#define BTHREAD_TASK_TRACER_H + +#ifdef BRPC_BTHREAD_TRACER + +#include "bthread/mutex.h" +#include "bthread/task_meta.h" +#include "butil/intrusive_ptr.hpp" +#include "butil/shared_object.h" +#include "butil/strings/safe_sprintf.h" +#include "butil/synchronization/condition_variable.h" +#include +#include +#include + +namespace bthread { + +// Tracer for bthread. +class TaskTracer { +public: + // Returns 0 on success, -1 otherwise. + bool Init(); + // Set the status to `s'. + void set_status(TaskStatus s, TaskMeta* meta); + static void set_running_status(pthread_t worker_tid, TaskMeta* meta); + static bool set_end_status_unsafe(TaskMeta* m); + + // Trace the bthread of `tid`. + std::string Trace(bthread_t tid); + void Trace(std::ostream& os, bthread_t tid); + + // When the worker is jumping stack from a bthread to another, + static void WaitForTracing(TaskMeta* m); + + int get_trace_signal() const { + return _signal_num; + } +private: + // Error number guard used in signal handler. + class ErrnoGuard { + public: + ErrnoGuard() : _errno(errno) {} + ~ErrnoGuard() { errno = _errno; } + private: + int _errno; + }; + + struct Result { + template + static Result MakeErrorResult(const char* fmt, Args&&... args) { + Result result; + result.SetError(fmt, std::forward(args)...); + return result; + } + + template + void SetError(const char* fmt, Args&&... args) { + error = true; + butil::ignore_result(butil::strings::SafeSPrintf( + err_msg, fmt, std::forward(args)...)); + } + + std::string OutputToString() const; + void OutputToStream(std::ostream& os) const; + + static constexpr size_t MAX_TRACE_NUM = 64; + + void* ips[MAX_TRACE_NUM]; + size_t frame_count{0}; + char err_msg[64]; + bool error{false}; + }; + + // For signal trace. + struct SignalSync : public butil::SharedObject { + ~SignalSync() override; + bool Init(); + + int pipe_fds[2]{-1, -1}; + Result result; + }; + + Result TraceImpl(bthread_t tid); + + unw_cursor_t MakeCursor(bthread_fcontext_t fcontext); + Result ContextTrace(bthread_fcontext_t fcontext); + static Result TraceByLibunwind(unw_cursor_t& cursor); + + bool RegisterSignalHandler(); + static void SignalHandler(int sig, siginfo_t* info, void* context); + Result SignalTrace(pthread_t worker_tid); + + // Make sure only one bthread is traced at a time. + Mutex _trace_request_mutex; + + // For context trace. + unw_context_t _context{}; + + // Hold SignalSync and wait until no one is using it before releasing it. + // This will avoid deadlock because it will not be released in the signal handler. + std::vector> _inuse_signal_syncs; + + bvar::LatencyRecorder _trace_time{"bthread_trace_time"}; + int _signal_num{SIGURG}; +}; + +} // namespace bthread + +#endif // BRPC_BTHREAD_TRACER + +#endif // BRPC_BTHREAD_TRACER_H diff --git a/src/bthread/timer_thread.cpp b/src/bthread/timer_thread.cpp new file mode 100644 index 0000000..a7ebfa4 --- /dev/null +++ b/src/bthread/timer_thread.cpp @@ -0,0 +1,494 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + + +#include // heap functions +#include +#include "butil/scoped_lock.h" +#include "butil/logging.h" +#include "butil/third_party/murmurhash3/murmurhash3.h" // fmix64 +#include "butil/resource_pool.h" +#include "butil/threading/platform_thread.h" +#include "bvar/bvar.h" +#include "bthread/sys_futex.h" +#include "bthread/timer_thread.h" +#include "bthread/log.h" + +namespace bthread { + +DEFINE_uint32(brpc_timer_num_buckets, 13, "brpc timer num buckets"); + +// Defined in task_control.cpp +void run_worker_startfn(); + +const TimerThread::TaskId TimerThread::INVALID_TASK_ID = 0; + +TimerThreadOptions::TimerThreadOptions() + : num_buckets(13) { +} + +// A task contains the necessary information for running fn(arg). +// Tasks are created in Bucket::schedule and destroyed in TimerThread::run +struct BAIDU_CACHELINE_ALIGNMENT TimerThread::Task { + Task* next; // For linking tasks in a Bucket. + int64_t run_time; // run the task at this realtime + void (*fn)(void*); // the fn(arg) to run + void* arg; + // Current TaskId, checked against version in TimerThread::run to test + // if this task is unscheduled. + TaskId task_id; + // initial_version: not run yet + // initial_version + 1: running + // initial_version + 2: removed (also the version of next Task reused + // this struct) + butil::atomic version; + + Task() : version(2/*skip 0*/) {} + + // Run this task and delete this struct. + // Returns true if fn(arg) did run. + bool run_and_delete(); + + // Delete this struct if this task was unscheduled. + // Returns true on deletion. + bool try_delete(); +}; + +// Timer tasks are sharded into different Buckets to reduce contentions. +class BAIDU_CACHELINE_ALIGNMENT TimerThread::Bucket { +public: + Bucket() + : _nearest_run_time(std::numeric_limits::max()) + , _task_head(NULL) { + } + + ~Bucket() {} + + struct ScheduleResult { + TimerThread::TaskId task_id; + bool earlier; + }; + + // Schedule a task into this bucket. + // Returns the TaskId and if it has the nearest run time. + ScheduleResult schedule(void (*fn)(void*), void* arg, + const timespec& abstime); + + // Pull all scheduled tasks. + // This function is called in timer thread. + Task* consume_tasks(); + +private: + FastPthreadMutex _mutex; + int64_t _nearest_run_time; + Task* _task_head; +}; + +// Utilies for making and extracting TaskId. +inline TimerThread::TaskId make_task_id( + butil::ResourceId slot, uint32_t version) { + return TimerThread::TaskId((((uint64_t)version) << 32) | slot.value); +} + +inline +butil::ResourceId slot_of_task_id(TimerThread::TaskId id) { + butil::ResourceId slot = { (id & 0xFFFFFFFFul) }; + return slot; +} + +inline uint32_t version_of_task_id(TimerThread::TaskId id) { + return (uint32_t)(id >> 32); +} + +inline bool task_greater(const TimerThread::Task* a, const TimerThread::Task* b) { + return a->run_time > b->run_time; +} + +void* TimerThread::run_this(void* arg) { + butil::PlatformThread::SetNameSimple("brpc_timer"); + static_cast(arg)->run(); + return NULL; +} + +TimerThread::TimerThread() + : _started(false) + , _stop(false) + , _buckets(NULL) + , _nearest_run_time(std::numeric_limits::max()) + , _nsignals(0) + , _thread(0) { +} + +TimerThread::~TimerThread() { + stop_and_join(); + delete [] _buckets; + _buckets = NULL; +} + +int TimerThread::start(const TimerThreadOptions* options_in) { + if (_started) { + return 0; + } + if (options_in) { + _options = *options_in; + } + if (_options.num_buckets == 0) { + LOG(ERROR) << "num_buckets can't be 0"; + return EINVAL; + } + if (_options.num_buckets > 1024) { + LOG(ERROR) << "num_buckets=" << _options.num_buckets << " is too big"; + return EINVAL; + } + _buckets = new (std::nothrow) Bucket[_options.num_buckets]; + if (NULL == _buckets) { + LOG(ERROR) << "Fail to new _buckets"; + return ENOMEM; + } + const int ret = pthread_create(&_thread, NULL, TimerThread::run_this, this); + if (ret) { + return ret; + } + _started = true; + return 0; +} + +TimerThread::Task* TimerThread::Bucket::consume_tasks() { + Task* head = NULL; + if (_task_head) { // NOTE: schedule() and consume_tasks() are sequenced + // by TimerThread._nearest_run_time and fenced by TimerThread._mutex. + // We can avoid touching the mutex and related cacheline when the + // bucket is actually empty. + BAIDU_SCOPED_LOCK(_mutex); + if (_task_head) { + head = _task_head; + _task_head = NULL; + _nearest_run_time = std::numeric_limits::max(); + } + } + return head; +} + +TimerThread::Bucket::ScheduleResult +TimerThread::Bucket::schedule(void (*fn)(void*), void* arg, + const timespec& abstime) { + butil::ResourceId slot_id; + Task* task = butil::get_resource(&slot_id); + if (task == NULL) { + ScheduleResult result = { INVALID_TASK_ID, false }; + return result; + } + task->next = NULL; + task->fn = fn; + task->arg = arg; + task->run_time = butil::timespec_to_microseconds(abstime); + uint32_t version = task->version.load(butil::memory_order_relaxed); + if (version == 0) { // skip 0. + task->version.fetch_add(2, butil::memory_order_relaxed); + version = 2; + } + const TaskId id = make_task_id(slot_id, version); + task->task_id = id; + bool earlier = false; + { + BAIDU_SCOPED_LOCK(_mutex); + task->next = _task_head; + _task_head = task; + if (task->run_time < _nearest_run_time) { + _nearest_run_time = task->run_time; + earlier = true; + } + } + ScheduleResult result = { id, earlier }; + return result; +} + +TimerThread::TaskId TimerThread::schedule( + void (*fn)(void*), void* arg, const timespec& abstime) { + if (_stop.load(butil::memory_order_relaxed) || !_started) { + // Not add tasks when TimerThread is about to stop. + return INVALID_TASK_ID; + } + // Hashing by pthread id is better for cache locality. + const Bucket::ScheduleResult result = + _buckets[butil::fmix64(pthread_numeric_id()) % _options.num_buckets] + .schedule(fn, arg, abstime); + if (result.earlier) { + bool earlier = false; + const int64_t run_time = butil::timespec_to_microseconds(abstime); + { + BAIDU_SCOPED_LOCK(_mutex); + if (run_time < _nearest_run_time) { + _nearest_run_time = run_time; + ++_nsignals; + earlier = true; + } + } + if (earlier) { + futex_wake_private(&_nsignals, 1); + } + } + return result.task_id; +} + +// Notice that we don't recycle the Task in this function, let TimerThread::run +// do it. The side effect is that we may allocate many unscheduled tasks before +// TimerThread wakes up. The number is approximately qps * timeout_s. Under the +// precondition that ResourcePool caches 128K for each thread, with some +// further calculations, we can conclude that in a RPC scenario: +// when timeout / latency < 2730 (128K / sizeof(Task)) +// unscheduled tasks do not occupy additional memory. 2730 is a large ratio +// between timeout and latency in most RPC scenarios, this is why we don't +// try to reuse tasks right now inside unschedule() with more complicated code. +int TimerThread::unschedule(TaskId task_id) { + const butil::ResourceId slot_id = slot_of_task_id(task_id); + Task* const task = butil::address_resource(slot_id); + if (task == NULL) { + LOG(ERROR) << "Invalid task_id=" << task_id; + return -1; + } + const uint32_t id_version = version_of_task_id(task_id); + uint32_t expected_version = id_version; + // This CAS is rarely contended, should be fast. + // The acquire fence is paired with release fence in Task::run_and_delete + // to make sure that we see all changes brought by fn(arg). + if (task->version.compare_exchange_strong( + expected_version, id_version + 2, + butil::memory_order_acquire)) { + return 0; + } + return (expected_version == id_version + 1) ? 1 : -1; +} + +bool TimerThread::Task::run_and_delete() { + const uint32_t id_version = version_of_task_id(task_id); + uint32_t expected_version = id_version; + // This CAS is rarely contended, should be fast. + if (version.compare_exchange_strong( + expected_version, id_version + 1, butil::memory_order_relaxed)) { + fn(arg); + // The release fence is paired with acquire fence in + // TimerThread::unschedule to make changes of fn(arg) visible. + version.store(id_version + 2, butil::memory_order_release); + butil::return_resource(slot_of_task_id(task_id)); + return true; + } else if (expected_version == id_version + 2) { + // already unscheduled. + butil::return_resource(slot_of_task_id(task_id)); + return false; + } else { + // Impossible. + LOG(ERROR) << "Invalid version=" << expected_version + << ", expecting " << id_version + 2; + return false; + } +} + +bool TimerThread::Task::try_delete() { + const uint32_t id_version = version_of_task_id(task_id); + if (version.load(butil::memory_order_relaxed) != id_version) { + CHECK_EQ(version.load(butil::memory_order_relaxed), id_version + 2); + butil::return_resource(slot_of_task_id(task_id)); + return true; + } + return false; +} + +template +static T deref_value(void* arg) { + return *(T*)arg; +} + +void TimerThread::run() { + run_worker_startfn(); +#ifdef BAIDU_INTERNAL + logging::ComlogInitializer comlog_initializer; +#endif + + int64_t last_sleep_time = butil::gettimeofday_us(); + BT_VLOG << "Started TimerThread=" << pthread_self(); + + // min heap of tasks (ordered by run_time) + std::vector tasks; + tasks.reserve(4096); + + // vars + size_t nscheduled = 0; + bvar::PassiveStatus nscheduled_var(deref_value, &nscheduled); + bvar::PerSecond > nscheduled_second(&nscheduled_var); + size_t ntriggered = 0; + bvar::PassiveStatus ntriggered_var(deref_value, &ntriggered); + bvar::PerSecond > ntriggered_second(&ntriggered_var); + double busy_seconds = 0; + bvar::PassiveStatus busy_seconds_var(deref_value, &busy_seconds); + bvar::PerSecond > busy_seconds_second(&busy_seconds_var); + if (!_options.bvar_prefix.empty()) { + nscheduled_second.expose_as(_options.bvar_prefix, "scheduled_second"); + ntriggered_second.expose_as(_options.bvar_prefix, "triggered_second"); + busy_seconds_second.expose_as(_options.bvar_prefix, "usage"); + } + + while (!_stop.load(butil::memory_order_relaxed)) { + // Clear _nearest_run_time before consuming tasks from buckets. + // This helps us to be aware of earliest task of the new tasks before we + // would run the consumed tasks. + { + BAIDU_SCOPED_LOCK(_mutex); + // This check of _stop ensures we won't miss the reset of _nearest_run_time + // to 0 in stop_and_join, avoiding potential race conditions. + if (BAIDU_UNLIKELY(_stop.load(butil::memory_order_relaxed))) { + break; + } + _nearest_run_time = std::numeric_limits::max(); + } + + // Pull tasks from buckets. + for (size_t i = 0; i < _options.num_buckets; ++i) { + Bucket& bucket = _buckets[i]; + for (Task* p = bucket.consume_tasks(); p != nullptr; ++nscheduled) { + // p->next should be kept first + // in case of the deletion of Task p which is unscheduled + Task* next_task = p->next; + + if (!p->try_delete()) { // remove the task if it's unscheduled + tasks.push_back(p); + std::push_heap(tasks.begin(), tasks.end(), task_greater); + } + p = next_task; + } + } + + bool pull_again = false; + while (!tasks.empty()) { + Task* task1 = tasks[0]; // the about-to-run task + if (butil::gettimeofday_us() < task1->run_time) { // not ready yet. + break; + } + // Each time before we run the earliest task (that we think), + // check the globally shared _nearest_run_time. If a task earlier + // than task1 was scheduled during pulling from buckets, we'll + // know. In RPC scenarios, _nearest_run_time is not often changed by + // threads because the task needs to be the earliest in its bucket, + // since run_time of scheduled tasks are often in ascending order, + // most tasks are unlikely to be "earliest". (If run_time of tasks + // are in descending orders, all tasks are "earliest" after every + // insertion, and they'll grab _mutex and change _nearest_run_time + // frequently, fortunately this is not true at most of time). + { + BAIDU_SCOPED_LOCK(_mutex); + if (task1->run_time > _nearest_run_time) { + // a task is earlier than task1. We need to check buckets. + pull_again = true; + break; + } + } + std::pop_heap(tasks.begin(), tasks.end(), task_greater); + tasks.pop_back(); + if (task1->run_and_delete()) { + ++ntriggered; + } + } + if (pull_again) { + BT_VLOG << "pull again, tasks=" << tasks.size(); + continue; + } + + // The realtime to wait for. + int64_t next_run_time = std::numeric_limits::max(); + if (!tasks.empty()) { + next_run_time = tasks[0]->run_time; + } + // Similarly with the situation before running tasks, we check + // _nearest_run_time to prevent us from waiting on a non-earliest + // task. We also use the _nsignal to make sure that if new task + // is earlier than the realtime that we wait for, we'll wake up. + int expected_nsignals = 0; + { + BAIDU_SCOPED_LOCK(_mutex); + if (next_run_time > _nearest_run_time) { + // a task is earlier than what we would wait for. + // We need to check the buckets. + continue; + } else { + _nearest_run_time = next_run_time; + expected_nsignals = _nsignals; + } + } + timespec* ptimeout = NULL; + timespec next_timeout = { 0, 0 }; + const int64_t now = butil::gettimeofday_us(); + if (next_run_time != std::numeric_limits::max()) { + next_timeout = butil::microseconds_to_timespec(next_run_time - now); + ptimeout = &next_timeout; + } + busy_seconds += (now - last_sleep_time) / 1000000.0; + futex_wait_private(&_nsignals, expected_nsignals, ptimeout); + last_sleep_time = butil::gettimeofday_us(); + } + BT_VLOG << "Ended TimerThread=" << pthread_self(); +} + +void TimerThread::stop_and_join() { + if (_stop.exchange(true, butil::memory_order_relaxed)) { + return; + } + if (_started) { + { + BAIDU_SCOPED_LOCK(_mutex); + // trigger pull_again and wakeup TimerThread + _nearest_run_time = 0; + ++_nsignals; + } + if (pthread_self() != _thread) { + // stop_and_join was not called from a running task. + // wake up the timer thread in case it is sleeping. + futex_wake_private(&_nsignals, 1); + pthread_join(_thread, NULL); + } + } +} + +static pthread_once_t g_timer_thread_once = PTHREAD_ONCE_INIT; +static TimerThread* g_timer_thread = NULL; +static void init_global_timer_thread() { + g_timer_thread = new (std::nothrow) TimerThread; + if (g_timer_thread == NULL) { + LOG(FATAL) << "Fail to new g_timer_thread"; + return; + } + TimerThreadOptions options; + options.bvar_prefix = "bthread_timer"; + options.num_buckets = FLAGS_brpc_timer_num_buckets; + const int rc = g_timer_thread->start(&options); + if (rc != 0) { + LOG(FATAL) << "Fail to start timer_thread, " << berror(rc); + delete g_timer_thread; + g_timer_thread = NULL; + return; + } +} +TimerThread* get_or_create_global_timer_thread() { + pthread_once(&g_timer_thread_once, init_global_timer_thread); + return g_timer_thread; +} +TimerThread* get_global_timer_thread() { + return g_timer_thread; +} + +} // end namespace bthread diff --git a/src/bthread/timer_thread.h b/src/bthread/timer_thread.h new file mode 100644 index 0000000..1be061c --- /dev/null +++ b/src/bthread/timer_thread.h @@ -0,0 +1,112 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + + +#ifndef BTHREAD_TIMER_THREAD_H +#define BTHREAD_TIMER_THREAD_H + +#include // std::vector +#include // pthread_* +#include "butil/atomicops.h" +#include "butil/time.h" // time utilities +#include "bthread/mutex.h" + +namespace bthread { + +struct TimerThreadOptions { + // Scheduling requests are hashed into different bucket to improve + // scalability. However bigger num_buckets may NOT result in more scalable + // schedule() because bigger values also make each buckets more sparse + // and more likely to lock the global mutex. You better not change + // this value, just leave it to us. + // Default: 13 + size_t num_buckets; + + // If this field is not empty, some bvar for reporting stats of TimerThread + // will be exposed with this prefix. + // Default: "" + std::string bvar_prefix; + + // Constructed with default options. + TimerThreadOptions(); +}; + +// TimerThread is a separate thread to run scheduled tasks at specific time. +// At most one task runs at any time, don't put time-consuming code in the +// callback otherwise the task may delay other tasks significantly. +class TimerThread { +public: + struct Task; + class Bucket; + + typedef uint64_t TaskId; + const static TaskId INVALID_TASK_ID; + + TimerThread(); + ~TimerThread(); + + // Start the timer thread. + // This method should only be called once. + // return 0 if success, errno otherwise. + int start(const TimerThreadOptions* options); + + // Stop the timer thread. Later schedule() will return INVALID_TASK_ID. + void stop_and_join(); + + // Schedule |fn(arg)| to run at realtime |abstime| approximately. + // Returns: identifier of the scheduled task, INVALID_TASK_ID on error. + TaskId schedule(void (*fn)(void*), void* arg, const timespec& abstime); + + // Prevent the task denoted by `task_id' from running. `task_id' must be + // returned by schedule() ever. + // Returns: + // 0 - Removed the task which does not run yet + // -1 - The task does not exist. + // 1 - The task is just running. + int unschedule(TaskId task_id); + + // Get identifier of internal pthread. + // Returns (pthread_t)0 if start() is not called yet. + pthread_t thread_id() const { return _thread; } + +private: + // the timer thread will run this method. + void run(); + static void* run_this(void* arg); + + bool _started; // whether the timer thread was started successfully. + butil::atomic _stop; + + TimerThreadOptions _options; + Bucket* _buckets; // list of tasks to be run + FastPthreadMutex _mutex; // protect _nearest_run_time + int64_t _nearest_run_time; + // the futex for wake up timer thread. can't use _nearest_run_time because + // it's 64-bit. + int _nsignals; + pthread_t _thread; // all scheduled task will be run on this thread +}; + +// Get the global TimerThread which never quits. +TimerThread* get_or_create_global_timer_thread(); +TimerThread* get_global_timer_thread(); + +} // end namespace bthread + +#endif // BTHREAD_TIMER_THREAD_H diff --git a/src/bthread/types.h b/src/bthread/types.h new file mode 100644 index 0000000..d46de1e --- /dev/null +++ b/src/bthread/types.h @@ -0,0 +1,323 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Tue Jul 10 17:40:58 CST 2012 + +#ifndef BTHREAD_TYPES_H +#define BTHREAD_TYPES_H + +#include // uint64_t +#include "butil/macros.h" +#if defined(__cplusplus) +#include "butil/logging.h" // CHECK +#endif + +typedef uint64_t bthread_t; + +// tid returned by bthread_start_* never equals this value. +static const bthread_t INVALID_BTHREAD = 0; + +// bthread tag default is 0 +typedef int bthread_tag_t; +static const bthread_tag_t BTHREAD_TAG_INVALID = -1; +static const bthread_tag_t BTHREAD_TAG_DEFAULT = 0; + +struct sockaddr; + +typedef unsigned bthread_stacktype_t; +static const bthread_stacktype_t BTHREAD_STACKTYPE_UNKNOWN = 0; +static const bthread_stacktype_t BTHREAD_STACKTYPE_PTHREAD = 1; +static const bthread_stacktype_t BTHREAD_STACKTYPE_SMALL = 2; +static const bthread_stacktype_t BTHREAD_STACKTYPE_NORMAL = 3; +static const bthread_stacktype_t BTHREAD_STACKTYPE_LARGE = 4; + +typedef unsigned bthread_attrflags_t; +static const bthread_attrflags_t BTHREAD_LOG_START_AND_FINISH = 8; +static const bthread_attrflags_t BTHREAD_LOG_CONTEXT_SWITCH = 16; +static const bthread_attrflags_t BTHREAD_NOSIGNAL = 32; +static const bthread_attrflags_t BTHREAD_NEVER_QUIT = 64; +static const bthread_attrflags_t BTHREAD_INHERIT_SPAN = 128; +static const bthread_attrflags_t BTHREAD_GLOBAL_PRIORITY = 256; + +// Key of thread-local data, created by bthread_key_create. +typedef struct { + uint32_t index; // index in KeyTable + uint32_t version; // ABA avoidance +} bthread_key_t; + +static const bthread_key_t INVALID_BTHREAD_KEY = { 0, 0 }; + +#if defined(__cplusplus) +// Overload operators for bthread_key_t +inline bool operator==(bthread_key_t key1, bthread_key_t key2) +{ return key1.index == key2.index && key1.version == key2.version; } +inline bool operator!=(bthread_key_t key1, bthread_key_t key2) +{ return !(key1 == key2); } +inline bool operator<(bthread_key_t key1, bthread_key_t key2) { + return key1.index != key2.index ? (key1.index < key2.index) : + (key1.version < key2.version); +} +inline bool operator>(bthread_key_t key1, bthread_key_t key2) +{ return key2 < key1; } +inline bool operator<=(bthread_key_t key1, bthread_key_t key2) +{ return !(key2 < key1); } +inline bool operator>=(bthread_key_t key1, bthread_key_t key2) +{ return !(key1 < key2); } +inline std::ostream& operator<<(std::ostream& os, bthread_key_t key) { + return os << "bthread_key_t{index=" << key.index << " version=" + << key.version << '}'; +} +#endif // __cplusplus + +typedef struct { + pthread_rwlock_t rwlock; + void* list; + void* free_keytables; + size_t size; + int destroyed; +} bthread_keytable_pool_t; + +typedef struct { + size_t nfree; +} bthread_keytable_pool_stat_t; + +static const size_t BTHREAD_NAME_MAX_LENGTH = 31; +// Attributes for thread creation. +typedef struct bthread_attr_t { + bthread_stacktype_t stack_type; + bthread_attrflags_t flags; + bthread_keytable_pool_t* keytable_pool; + bthread_tag_t tag; + char name[BTHREAD_NAME_MAX_LENGTH + 1]; // do not use std::string to keep POD + +#if defined(__cplusplus) + void operator=(unsigned stacktype_and_flags) { + stack_type = (stacktype_and_flags & 7); + flags = (stacktype_and_flags & ~(unsigned)7u); + keytable_pool = NULL; + tag = BTHREAD_TAG_INVALID; + } + bthread_attr_t operator|(unsigned other_flags) const { + CHECK(!(other_flags & 7)) << "flags=" << other_flags; + bthread_attr_t tmp = *this; + tmp.flags |= (other_flags & ~(unsigned)7u); + return tmp; + } +#endif // __cplusplus +} bthread_attr_t; + +void bthread_attr_set_name(bthread_attr_t* attr, const char* name); + +// bthreads started with this attribute will run on stack of worker pthread and +// all bthread functions that would block the bthread will block the pthread. +// The bthread will not allocate its own stack, simply occupying a little meta +// memory. This is required to run JNI code which checks layout of stack. The +// obvious drawback is that you need more worker pthreads when you have a lot +// of such bthreads. +static const bthread_attr_t BTHREAD_ATTR_PTHREAD = +{ BTHREAD_STACKTYPE_PTHREAD, 0, NULL, BTHREAD_TAG_INVALID, {0} }; + +// bthreads created with following attributes will have different size of +// stacks. Default is BTHREAD_ATTR_NORMAL. +static const bthread_attr_t BTHREAD_ATTR_SMALL = {BTHREAD_STACKTYPE_SMALL, 0, NULL, + BTHREAD_TAG_INVALID, {0}}; +static const bthread_attr_t BTHREAD_ATTR_NORMAL = {BTHREAD_STACKTYPE_NORMAL, 0, NULL, + BTHREAD_TAG_INVALID, {0}}; +static const bthread_attr_t BTHREAD_ATTR_LARGE = {BTHREAD_STACKTYPE_LARGE, 0, NULL, + BTHREAD_TAG_INVALID, {0}}; + +// bthreads created with this attribute will print log when it's started, +// context-switched, finished. +static const bthread_attr_t BTHREAD_ATTR_DEBUG = { + BTHREAD_STACKTYPE_NORMAL, BTHREAD_LOG_START_AND_FINISH | BTHREAD_LOG_CONTEXT_SWITCH, NULL, + BTHREAD_TAG_INVALID, {0}}; + +static const size_t BTHREAD_EPOLL_THREAD_NUM = 1; +static const bthread_t BTHREAD_ATOMIC_INIT = 0; + +// Min/Max number of work pthreads. +static const int BTHREAD_MIN_CONCURRENCY = 3 + BTHREAD_EPOLL_THREAD_NUM; +static const int BTHREAD_MAX_CONCURRENCY = 1024; +// Min/max number of ParkingLot. +static const int BTHREAD_MIN_PARKINGLOT = 4; +static const int BTHREAD_MAX_PARKINGLOT = 1024; + +typedef struct { + void* impl; + // following fields are part of previous impl. and not used right now. + // Don't remove them to break ABI compatibility. + unsigned head; + unsigned size; + unsigned conflict_head; + unsigned conflict_size; +} bthread_list_t; + +// TODO: bthread_contention_site_t should be put into butex. +typedef struct { + int64_t duration_ns; + size_t sampling_range; +} bthread_contention_site_t; + +struct mutex_owner_t { + bool hold; + uint64_t id; +}; + +typedef struct bthread_mutex_t { +#if defined(__cplusplus) + bthread_mutex_t() + : butex(NULL), csite{} + , enable_csite(false) + , owner{false, 0} {} + + DISALLOW_COPY_AND_ASSIGN(bthread_mutex_t); +#endif + unsigned* butex; + bthread_contention_site_t csite; + bool enable_csite; + // Note: Owner detection of the mutex comes with average execution + // slowdown of about 50%, so it is only used for debugging and is + // only available when the macro `BRPC_DEBUG_LOCK' = 1. + mutex_owner_t owner; +} bthread_mutex_t; + +typedef struct { + bool enable_csite; +} bthread_mutexattr_t; + +typedef struct bthread_cond_t { +#if defined(__cplusplus) + bthread_cond_t() : m(NULL), seq(NULL) {} + DISALLOW_COPY_AND_ASSIGN(bthread_cond_t); +#endif + bthread_mutex_t* m; + int* seq; +} bthread_cond_t; + +typedef struct { +} bthread_condattr_t; + +typedef struct bthread_sem_t { +#if defined(__cplusplus) + bthread_sem_t() : butex(NULL), enable_csite(true) {} + DISALLOW_COPY_AND_ASSIGN(bthread_sem_t); +#endif + unsigned* butex; + bool enable_csite; +} bthread_sem_t; + +typedef struct bthread_rwlock_t { +#if defined(__cplusplus) + bthread_rwlock_t() + : writer_wait_count(0), lock_word(NULL) {} + DISALLOW_COPY_AND_ASSIGN(bthread_rwlock_t); +#endif + // Number of writers currently in flight (used as a butex): + // writers waiting on writer_queue_mutex, writers waiting for + // lock_word == 0, and the writer currently holding the write lock + // are all counted here. Each writer accounts for itself: incremented + // at the very beginning of wrlock() and decremented at the very end + // of unwrlock()/cleanup(). Readers consult this field to honor + // writer-priority: any non-zero value parks new readers. + unsigned* writer_wait_count; + // Serializes writers so that at most one writer at a time races for + // lock_word. Other writers queue up on this mutex. + bthread_mutex_t writer_queue_mutex; + // Bit-packed atomic lock word (used as a butex): + // bit 31 : 1 if the write lock is held, 0 otherwise. + // bit 0~30: number of readers currently holding the read lock. + // 0 : unlocked. + // The high bit and the low 31 bits are mutually exclusive. + unsigned* lock_word; +} bthread_rwlock_t; + +typedef struct { +} bthread_rwlockattr_t; + +typedef struct { + unsigned int count; +} bthread_barrier_t; + +typedef struct { +} bthread_barrierattr_t; + +#if defined(__cplusplus) +class bthread_once_t; +namespace bthread { +extern int bthread_once_impl(bthread_once_t* once_control, void (*init_routine)()); +} + +class bthread_once_t { +public: +friend int bthread::bthread_once_impl(bthread_once_t* once_control, void (*init_routine)()); + enum State { + UNINITIALIZED = 0, + INPROGRESS, + INITIALIZED, + }; + + + bthread_once_t(); + ~bthread_once_t(); + DISALLOW_COPY_AND_ASSIGN(bthread_once_t); + +private: + butil::atomic* _butex; +}; +#endif + +typedef struct { + uint64_t value; +} bthread_id_t; + +// bthread_id returned by bthread_id_create* can never be this value. +// NOTE: don't confuse with INVALID_BTHREAD! +static const bthread_id_t INVALID_BTHREAD_ID = {0}; + +#if defined(__cplusplus) +// Overload operators for bthread_id_t +inline bool operator==(bthread_id_t id1, bthread_id_t id2) +{ return id1.value == id2.value; } +inline bool operator!=(bthread_id_t id1, bthread_id_t id2) +{ return !(id1 == id2); } +inline bool operator<(bthread_id_t id1, bthread_id_t id2) +{ return id1.value < id2.value; } +inline bool operator>(bthread_id_t id1, bthread_id_t id2) +{ return id2 < id1; } +inline bool operator<=(bthread_id_t id1, bthread_id_t id2) +{ return !(id2 < id1); } +inline bool operator>=(bthread_id_t id1, bthread_id_t id2) +{ return !(id1 < id2); } +inline std::ostream& operator<<(std::ostream& os, bthread_id_t id) +{ return os << id.value; } +#endif // __cplusplus + +typedef struct { + void* impl; + // following fields are part of previous impl. and not used right now. + // Don't remove them to break ABI compatibility. + unsigned head; + unsigned size; + unsigned conflict_head; + unsigned conflict_size; +} bthread_id_list_t; + +typedef uint64_t bthread_timer_t; + +#endif // BTHREAD_TYPES_H diff --git a/src/bthread/unstable.h b/src/bthread/unstable.h new file mode 100644 index 0000000..186d9ce --- /dev/null +++ b/src/bthread/unstable.h @@ -0,0 +1,142 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Tue Jul 10 17:40:58 CST 2012 + +#ifndef BTHREAD_UNSTABLE_H +#define BTHREAD_UNSTABLE_H + +#include +#include +#include "bthread/types.h" +#include "bthread/errno.h" + +// NOTICE: +// As the filename implies, this file lists UNSTABLE bthread functions +// which are likely to be modified or even removed in future release. We +// don't guarantee any kind of backward compatibility. Don't use these +// functions if you're not ready to change your code according to newer +// versions of bthread. + +__BEGIN_DECLS + +// Schedule tasks created by BTHREAD_NOSIGNAL +extern void bthread_flush(); + +// Mark the calling bthread as "about to quit". When the bthread is scheduled, +// worker pthreads are not notified. +extern int bthread_about_to_quit(); + +// Run `on_timer(arg)' at or after real-time `abstime'. Put identifier of the +// timer into *id. +// Return 0 on success, errno otherwise. +extern int bthread_timer_add(bthread_timer_t* id, struct timespec abstime, + void (*on_timer)(void*), void* arg); + +// Unschedule the timer associated with `id'. +// Returns: 0 - exist & not-run; 1 - still running; EINVAL - not exist. +extern int bthread_timer_del(bthread_timer_t id); + +// Suspend caller thread until the file descriptor `fd' has `epoll_events'. +// Returns 0 on success, -1 otherwise and errno is set. +// NOTE: Due to an epoll +// bug(https://web.archive.org/web/20150423184820/https://patchwork.kernel.org/patch/1970231/), +// current implementation relies on EPOLL_CTL_ADD and EPOLL_CTL_DEL which +// are not scalable, don't use bthread_fd_*wait functions in performance +// critical scenario. +extern int bthread_fd_wait(int fd, unsigned events); + +// Suspend caller thread until the file descriptor `fd' has `epoll_events' +// or CLOCK_REALTIME reached `abstime' if abstime is not NULL. +// Returns 0 on success, -1 otherwise and errno is set. +extern int bthread_fd_timedwait(int fd, unsigned epoll_events, + const struct timespec* abstime); + +// Close file descriptor `fd' and wake up all threads waiting on it. +// User should call this function instead of close(2) if bthread_fd_wait, +// bthread_fd_timedwait, bthread_connect were called on the file descriptor, +// otherwise waiters will suspend indefinitely and bthread's internal epoll +// may work abnormally after fork() is called. +// NOTE: This function does not wake up pthread waiters.(tested on linux 2.6.32) +extern int bthread_close(int fd); + +// Replacement of connect(2) in bthreads. +extern int bthread_connect(int sockfd, const struct sockaddr* serv_addr, + socklen_t addrlen); +// Suspend caller thread until connect(2) on `sockfd' succeeds +// or CLOCK_REALTIME reached `abstime' if `abstime' is not NULL. +extern int bthread_timed_connect(int sockfd, const struct sockaddr* serv_addr, + socklen_t addrlen, const timespec* abstime); + +// Add a startup function that each pthread worker will run at the beginning +// To run code at the end, use butil::thread_atexit() +// Returns 0 on success, error code otherwise. +extern int bthread_set_worker_startfn(void (*start_fn)()); + +// Add a startup function with tag +extern int bthread_set_tagged_worker_startfn(void (*start_fn)(bthread_tag_t)); + +// Stop all bthread and worker pthreads. +// You should avoid calling this function which may cause bthread after main() +// suspend indefinitely. +extern void bthread_stop_world(); + +// Create a bthread_key_t with an additional arg to destructor. +// Generally the dtor_arg is for passing the creator of data so that we can +// return the data back to the creator in destructor. Without this arg, we +// have to do an extra heap allocation to contain data and its creator. +extern int bthread_key_create2(bthread_key_t* key, + void (*destructor)(void* data, const void* dtor_arg), + const void* dtor_arg); + +// CAUTION: functions marked with [RPC INTERNAL] are NOT supposed to be called +// by RPC users. + +// [RPC INTERNAL] +// Create a pool to cache KeyTables so that frequently created/destroyed +// bthreads reuse these tables, namely when a bthread needs a KeyTable, +// it fetches one from the pool instead of creating on heap. When a bthread +// exits, it puts the table back to pool instead of deleting it. +// Returns 0 on success, error code otherwise. +extern int bthread_keytable_pool_init(bthread_keytable_pool_t*); + +// [RPC INTERNAL] +// Destroy the pool. All KeyTables inside are destroyed. +// Returns 0 on success, error code otherwise. +extern int bthread_keytable_pool_destroy(bthread_keytable_pool_t*); + +// [RPC INTERNAL] +// Put statistics of `pool' into `stat'. +extern int bthread_keytable_pool_getstat(bthread_keytable_pool_t* pool, + bthread_keytable_pool_stat_t* stat); + +// [RPC INTERNAL] +// Return thread local keytable list length if exist. +extern int get_thread_local_keytable_list_length(bthread_keytable_pool_t* pool); + +// [RPC INTERNAL] +// Reserve at most `nfree' keytables with `key' pointing to data created by +// ctor(args). +extern void bthread_keytable_pool_reserve( + bthread_keytable_pool_t* pool, size_t nfree, + bthread_key_t key, void* ctor(const void* args), const void* args); + +__END_DECLS + +#endif // BTHREAD_UNSTABLE_H diff --git a/src/bthread/work_stealing_queue.h b/src/bthread/work_stealing_queue.h new file mode 100644 index 0000000..138aaa6 --- /dev/null +++ b/src/bthread/work_stealing_queue.h @@ -0,0 +1,157 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Tue Jul 10 17:40:58 CST 2012 + +#ifndef BTHREAD_WORK_STEALING_QUEUE_H +#define BTHREAD_WORK_STEALING_QUEUE_H + +#include "butil/macros.h" +#include "butil/atomicops.h" +#include "butil/logging.h" + +namespace bthread { + +template +class WorkStealingQueue { +public: + WorkStealingQueue() + : _bottom(1) + , _capacity(0) + , _buffer(NULL) + , _top(1) { + } + + ~WorkStealingQueue() { + delete [] _buffer; + _buffer = NULL; + } + + int init(size_t capacity) { + if (_capacity != 0) { + LOG(ERROR) << "Already initialized"; + return -1; + } + if (capacity == 0) { + LOG(ERROR) << "Invalid capacity=" << capacity; + return -1; + } + if (capacity & (capacity - 1)) { + LOG(ERROR) << "Invalid capacity=" << capacity + << " which must be power of 2"; + return -1; + } + _buffer = new(std::nothrow) T[capacity]; + if (NULL == _buffer) { + return -1; + } + _capacity = capacity; + return 0; + } + + // Push an item into the queue. + // Returns true on pushed. + // May run in parallel with steal(). + // Never run in parallel with pop() or another push(). + bool push(const T& x) { + const size_t b = _bottom.load(butil::memory_order_relaxed); + const size_t t = _top.load(butil::memory_order_acquire); + if (b >= t + _capacity) { // Full queue. + return false; + } + _buffer[b & (_capacity - 1)] = x; + _bottom.store(b + 1, butil::memory_order_release); + return true; + } + + // Pop an item from the queue. + // Returns true on popped and the item is written to `val'. + // May run in parallel with steal(). + // Never run in parallel with push() or another pop(). + bool pop(T* val) { + const size_t b = _bottom.load(butil::memory_order_relaxed); + size_t t = _top.load(butil::memory_order_relaxed); + if (t >= b) { + // fast check since we call pop() in each sched. + // Stale _top which is smaller should not enter this branch. + return false; + } + const size_t newb = b - 1; + _bottom.store(newb, butil::memory_order_relaxed); + butil::atomic_thread_fence(butil::memory_order_seq_cst); + t = _top.load(butil::memory_order_relaxed); + if (t > newb) { + _bottom.store(b, butil::memory_order_relaxed); + return false; + } + *val = _buffer[newb & (_capacity - 1)]; + if (t != newb) { + return true; + } + // Single last element, compete with steal() + const bool popped = _top.compare_exchange_strong( + t, t + 1, butil::memory_order_seq_cst, butil::memory_order_relaxed); + _bottom.store(b, butil::memory_order_relaxed); + return popped; + } + + // Steal one item from the queue. + // Returns true on stolen. + // May run in parallel with push() pop() or another steal(). + bool steal(T* val) { + size_t t = _top.load(butil::memory_order_acquire); + size_t b = _bottom.load(butil::memory_order_acquire); + if (t >= b) { + // Permit false negative for performance considerations. + return false; + } + do { + butil::atomic_thread_fence(butil::memory_order_seq_cst); + b = _bottom.load(butil::memory_order_acquire); + if (t >= b) { + return false; + } + *val = _buffer[t & (_capacity - 1)]; + } while (!_top.compare_exchange_weak(t, t + 1, + butil::memory_order_seq_cst, + butil::memory_order_relaxed)); + return true; + } + + size_t volatile_size() const { + const size_t b = _bottom.load(butil::memory_order_relaxed); + const size_t t = _top.load(butil::memory_order_relaxed); + return (b <= t ? 0 : (b - t)); + } + + size_t capacity() const { return _capacity; } + +private: + // Copying a concurrent structure makes no sense. + DISALLOW_COPY_AND_ASSIGN(WorkStealingQueue); + + butil::atomic _bottom; + size_t _capacity; + T* _buffer; + BAIDU_CACHELINE_ALIGNMENT butil::atomic _top; +}; + +} // namespace bthread + +#endif // BTHREAD_WORK_STEALING_QUEUE_H diff --git a/src/butil/arena.cpp b/src/butil/arena.cpp new file mode 100644 index 0000000..9c66217 --- /dev/null +++ b/src/butil/arena.cpp @@ -0,0 +1,104 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Fri Jun 5 18:25:40 CST 2015 + +#include +#include +#include "butil/arena.h" + +namespace butil { + +ArenaOptions::ArenaOptions() + : initial_block_size(64) + , max_block_size(8192) +{} + +Arena::Arena(const ArenaOptions& options) + : _cur_block(NULL) + , _isolated_blocks(NULL) + , _block_size(options.initial_block_size) + , _options(options) { +} + +Arena::~Arena() { + while (_cur_block != NULL) { + Block* const saved_next = _cur_block->next; + free(_cur_block); + _cur_block = saved_next; + } + while (_isolated_blocks != NULL) { + Block* const saved_next = _isolated_blocks->next; + free(_isolated_blocks); + _isolated_blocks = saved_next; + } +} + +void Arena::swap(Arena& other) { + std::swap(_cur_block, other._cur_block); + std::swap(_isolated_blocks, other._isolated_blocks); + std::swap(_block_size, other._block_size); + const ArenaOptions tmp = _options; + _options = other._options; + other._options = tmp; +} + +void Arena::clear() { + // TODO(gejun): Reuse memory + Arena a; + swap(a); +} + +void* Arena::allocate_new_block(size_t n) { + Block* b = (Block*)malloc(offsetof(Block, data) + n); + b->next = _isolated_blocks; + b->alloc_size = n; + b->size = n; + _isolated_blocks = b; + return b->data; +} + +void* Arena::allocate_in_other_blocks(size_t n) { + if (n > _block_size / 4) { // put outlier on separate blocks. + return allocate_new_block(n); + } + // Waste the left space. At most 1/4 of allocated spaces are wasted. + + // Grow the block size gradually. + if (_cur_block != NULL) { + _block_size = std::min(2 * _block_size, _options.max_block_size); + } + size_t new_size = _block_size; + if (new_size < n) { + new_size = n; + } + Block* b = (Block*)malloc(offsetof(Block, data) + new_size); + if (NULL == b) { + return NULL; + } + b->next = NULL; + b->alloc_size = n; + b->size = new_size; + if (_cur_block) { + _cur_block->next = _isolated_blocks; + _isolated_blocks = _cur_block; + } + _cur_block = b; + return b->data; +} + +} // namespace butil diff --git a/src/butil/arena.h b/src/butil/arena.h new file mode 100644 index 0000000..03693a7 --- /dev/null +++ b/src/butil/arena.h @@ -0,0 +1,85 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Fri Jun 5 18:25:40 CST 2015 + +// Do small memory allocations on continuous blocks. + +#ifndef BUTIL_ARENA_H +#define BUTIL_ARENA_H + +#include +#include "butil/macros.h" + +namespace butil { + +struct ArenaOptions { + size_t initial_block_size; + size_t max_block_size; + + // Constructed with default options. + ArenaOptions(); +}; + +// Just a proof-of-concept, will be refactored in future CI. +class Arena { +public: + explicit Arena(const ArenaOptions& options = ArenaOptions()); + ~Arena(); + void swap(Arena&); + void* allocate(size_t n); + void* allocate_aligned(size_t n); // not implemented. + void clear(); + +private: + DISALLOW_COPY_AND_ASSIGN(Arena); + + struct Block { + uint32_t left_space() const { return size - alloc_size; } + + Block* next; + uint32_t alloc_size; + uint32_t size; + char data[0]; + }; + + void* allocate_in_other_blocks(size_t n); + void* allocate_new_block(size_t n); + Block* pop_block(Block* & head) { + Block* saved_head = head; + head = head->next; + return saved_head; + } + + Block* _cur_block; + Block* _isolated_blocks; + size_t _block_size; + ArenaOptions _options; +}; + +inline void* Arena::allocate(size_t n) { + if (_cur_block != NULL && _cur_block->left_space() >= n) { + void* ret = _cur_block->data + _cur_block->alloc_size; + _cur_block->alloc_size += n; + return ret; + } + return allocate_in_other_blocks(n); +} + +} // namespace butil + +#endif // BUTIL_ARENA_H diff --git a/src/butil/at_exit.cc b/src/butil/at_exit.cc new file mode 100644 index 0000000..1c06ec1 --- /dev/null +++ b/src/butil/at_exit.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/at_exit.h" + +#include +#include + +#include "butil/logging.h" + +namespace butil { + +// Keep a stack of registered AtExitManagers. We always operate on the most +// recent, and we should never have more than one outside of testing (for a +// statically linked version of this library). Testing may use the shadow +// version of the constructor, and if we are building a dynamic library we may +// end up with multiple AtExitManagers on the same process. We don't protect +// this for thread-safe access, since it will only be modified in testing. +static AtExitManager* g_top_manager = NULL; + +AtExitManager::AtExitManager() : next_manager_(g_top_manager) { +// If multiple modules instantiate AtExitManagers they'll end up living in this +// module... they have to coexist. +#if !defined(COMPONENT_BUILD) + DCHECK(!g_top_manager); +#endif + g_top_manager = this; +} + +AtExitManager::~AtExitManager() { + if (!g_top_manager) { + NOTREACHED() << "Tried to ~AtExitManager without an AtExitManager"; + return; + } + DCHECK_EQ(this, g_top_manager); + + ProcessCallbacksNow(); + g_top_manager = next_manager_; +} + +// static +void AtExitManager::RegisterCallback(AtExitCallbackType func, void* param) { + DCHECK(func); + if (!g_top_manager) { + NOTREACHED() << "Tried to RegisterCallback without an AtExitManager"; + return; + } + + AutoLock lock(g_top_manager->lock_); + g_top_manager->stack_.push({func, param}); +} + +// static +void AtExitManager::ProcessCallbacksNow() { + if (!g_top_manager) { + NOTREACHED() << "Tried to ProcessCallbacksNow without an AtExitManager"; + return; + } + + AutoLock lock(g_top_manager->lock_); + + while (!g_top_manager->stack_.empty()) { + Callback task = g_top_manager->stack_.top(); + task.func(task.param); + g_top_manager->stack_.pop(); + } +} + +AtExitManager::AtExitManager(bool shadow) : next_manager_(g_top_manager) { + DCHECK(shadow || !g_top_manager); + g_top_manager = this; +} + +} // namespace butil diff --git a/src/butil/at_exit.h b/src/butil/at_exit.h new file mode 100644 index 0000000..4d21daf --- /dev/null +++ b/src/butil/at_exit.h @@ -0,0 +1,76 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_AT_EXIT_H_ +#define BUTIL_AT_EXIT_H_ + +#include + +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/synchronization/lock.h" + +namespace butil { + +// This class provides a facility similar to the CRT atexit(), except that +// we control when the callbacks are executed. Under Windows for a DLL they +// happen at a really bad time and under the loader lock. This facility is +// mostly used by butil::Singleton. +// +// The usage is simple. Early in the main() or WinMain() scope create an +// AtExitManager object on the stack: +// int main(...) { +// butil::AtExitManager exit_manager; +// +// } +// When the exit_manager object goes out of scope, all the registered +// callbacks and singleton destructors will be called. + +class BUTIL_EXPORT AtExitManager { + public: + typedef void (*AtExitCallbackType)(void*); + + AtExitManager(); + + // The dtor calls all the registered callbacks. Do not try to register more + // callbacks after this point. + ~AtExitManager(); + + // Registers the specified function to be called at exit. The prototype of + // the callback function is void func(void*). + static void RegisterCallback(AtExitCallbackType func, void* param); + + // Calls the functions registered with RegisterCallback in LIFO order. It + // is possible to register new callbacks after calling this function. + static void ProcessCallbacksNow(); + + protected: + // This constructor will allow this instance of AtExitManager to be created + // even if one already exists. This should only be used for testing! + // AtExitManagers are kept on a global stack, and it will be removed during + // destruction. This allows you to shadow another AtExitManager. + explicit AtExitManager(bool shadow); + + private: + struct Callback { + AtExitCallbackType func; + void* param; + }; + butil::Lock lock_; + std::stack stack_; + AtExitManager* next_manager_; // Stack of managers to allow shadowing. + + DISALLOW_COPY_AND_ASSIGN(AtExitManager); +}; + +#if defined(UNIT_TEST) +class ShadowingAtExitManager : public AtExitManager { + public: + ShadowingAtExitManager() : AtExitManager(true) {} +}; +#endif // defined(UNIT_TEST) + +} // namespace butil + +#endif // BUTIL_AT_EXIT_H_ diff --git a/src/butil/atomic_ref_count.h b/src/butil/atomic_ref_count.h new file mode 100644 index 0000000..6be14eb --- /dev/null +++ b/src/butil/atomic_ref_count.h @@ -0,0 +1,80 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This is a low level implementation of atomic semantics for reference +// counting. Please use butil/memory/ref_counted.h directly instead. +// +// The implementation includes annotations to avoid some false positives +// when using data race detection tools. + +#ifndef BUTIL_ATOMIC_REF_COUNT_H_ +#define BUTIL_ATOMIC_REF_COUNT_H_ + +#include "butil/atomicops.h" +#include "butil/third_party/dynamic_annotations/dynamic_annotations.h" + +namespace butil { + +typedef subtle::Atomic32 AtomicRefCount; + +// Increment a reference count by "increment", which must exceed 0. +inline void AtomicRefCountIncN(volatile AtomicRefCount *ptr, + AtomicRefCount increment) { + subtle::NoBarrier_AtomicIncrement(ptr, increment); +} + +// Decrement a reference count by "decrement", which must exceed 0, +// and return whether the result is non-zero. +// Insert barriers to ensure that state written before the reference count +// became zero will be visible to a thread that has just made the count zero. +inline bool AtomicRefCountDecN(volatile AtomicRefCount *ptr, + AtomicRefCount decrement) { + ANNOTATE_HAPPENS_BEFORE(ptr); + bool res = (subtle::Barrier_AtomicIncrement(ptr, -decrement) != 0); + if (!res) { + ANNOTATE_HAPPENS_AFTER(ptr); + } + return res; +} + +// Increment a reference count by 1. +inline void AtomicRefCountInc(volatile AtomicRefCount *ptr) { + butil::AtomicRefCountIncN(ptr, 1); +} + +// Decrement a reference count by 1 and return whether the result is non-zero. +// Insert barriers to ensure that state written before the reference count +// became zero will be visible to a thread that has just made the count zero. +inline bool AtomicRefCountDec(volatile AtomicRefCount *ptr) { + return butil::AtomicRefCountDecN(ptr, 1); +} + +// Return whether the reference count is one. If the reference count is used +// in the conventional way, a refrerence count of 1 implies that the current +// thread owns the reference and no other thread shares it. This call performs +// the test for a reference count of one, and performs the memory barrier +// needed for the owning thread to act on the object, knowing that it has +// exclusive access to the object. +inline bool AtomicRefCountIsOne(volatile AtomicRefCount *ptr) { + bool res = (subtle::Acquire_Load(ptr) == 1); + if (res) { + ANNOTATE_HAPPENS_AFTER(ptr); + } + return res; +} + +// Return whether the reference count is zero. With conventional object +// referencing counting, the object will be destroyed, so the reference count +// should never be zero. Hence this is generally used for a debug check. +inline bool AtomicRefCountIsZero(volatile AtomicRefCount *ptr) { + bool res = (subtle::Acquire_Load(ptr) == 0); + if (res) { + ANNOTATE_HAPPENS_AFTER(ptr); + } + return res; +} + +} // namespace butil + +#endif // BUTIL_ATOMIC_REF_COUNT_H_ diff --git a/src/butil/atomic_sequence_num.h b/src/butil/atomic_sequence_num.h new file mode 100644 index 0000000..51aaddb --- /dev/null +++ b/src/butil/atomic_sequence_num.h @@ -0,0 +1,60 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_ATOMIC_SEQUENCE_NUM_H_ +#define BUTIL_ATOMIC_SEQUENCE_NUM_H_ + +#include "butil/atomicops.h" +#include "butil/basictypes.h" + +namespace butil { + +class AtomicSequenceNumber; + +// Static (POD) AtomicSequenceNumber that MUST be used in global scope (or +// non-function scope) ONLY. This implementation does not generate any static +// initializer. Note that it does not implement any constructor which means +// that its fields are not initialized except when it is stored in the global +// data section (.data in ELF). If you want to allocate an atomic sequence +// number on the stack (or heap), please use the AtomicSequenceNumber class +// declared below. +class StaticAtomicSequenceNumber { + public: + inline int GetNext() { + return static_cast( + butil::subtle::NoBarrier_AtomicIncrement(&seq_, 1) - 1); + } + + private: + friend class AtomicSequenceNumber; + + inline void Reset() { + butil::subtle::Release_Store(&seq_, 0); + } + + butil::subtle::Atomic32 seq_; +}; + +// AtomicSequenceNumber that can be stored and used safely (i.e. its fields are +// always initialized as opposed to StaticAtomicSequenceNumber declared above). +// Please use StaticAtomicSequenceNumber if you want to declare an atomic +// sequence number in the global scope. +class AtomicSequenceNumber { + public: + AtomicSequenceNumber() { + seq_.Reset(); + } + + inline int GetNext() { + return seq_.GetNext(); + } + + private: + StaticAtomicSequenceNumber seq_; + DISALLOW_COPY_AND_ASSIGN(AtomicSequenceNumber); +}; + +} // namespace butil + +#endif // BUTIL_ATOMIC_SEQUENCE_NUM_H_ diff --git a/src/butil/atomicops.h b/src/butil/atomicops.h new file mode 100644 index 0000000..7ee3837 --- /dev/null +++ b/src/butil/atomicops.h @@ -0,0 +1,324 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// For atomic operations on reference counts, see atomic_refcount.h. +// For atomic operations on sequence numbers, see atomic_sequence_num.h. + +// The routines exported by this module are subtle. If you use them, even if +// you get the code right, it will depend on careful reasoning about atomicity +// and memory ordering; it will be less readable, and harder to maintain. If +// you plan to use these routines, you should have a good reason, such as solid +// evidence that performance would otherwise suffer, or there being no +// alternative. You should assume only properties explicitly guaranteed by the +// specifications in this file. You are almost certainly _not_ writing code +// just for the x86; if you assume x86 semantics, x86 hardware bugs and +// implementations on other archtectures will cause your code to break. If you +// do not know what you are doing, avoid these routines, and use a Mutex. +// +// It is incorrect to make direct assignments to/from an atomic variable. +// You should use one of the Load or Store routines. The NoBarrier +// versions are provided when no barriers are needed: +// NoBarrier_Store() +// NoBarrier_Load() +// Although there are currently no compiler enforcement, you are encouraged +// to use these. +// + +#ifndef BUTIL_ATOMICOPS_H_ +#define BUTIL_ATOMICOPS_H_ + +#include + +#include "butil/build_config.h" +#include "butil/macros.h" + +#if defined(OS_WIN) && defined(ARCH_CPU_64_BITS) +// windows.h #defines this (only on x64). This causes problems because the +// public API also uses MemoryBarrier at the public name for this fence. So, on +// X64, undef it, and call its documented +// (http://msdn.microsoft.com/en-us/library/windows/desktop/ms684208.aspx) +// implementation directly. +#undef MemoryBarrier +#endif + +namespace butil { +namespace subtle { + +typedef int32_t Atomic32; +#ifdef ARCH_CPU_64_BITS +// We need to be able to go between Atomic64 and AtomicWord implicitly. This +// means Atomic64 and AtomicWord should be the same type on 64-bit. +#if defined(__ILP32__) || defined(OS_NACL) +// NaCl's intptr_t is not actually 64-bits on 64-bit! +// http://code.google.com/p/nativeclient/issues/detail?id=1162 +typedef int64_t Atomic64; +#else +typedef intptr_t Atomic64; +#endif +#endif + +// Use AtomicWord for a machine-sized pointer. It will use the Atomic32 or +// Atomic64 routines below, depending on your architecture. +typedef intptr_t AtomicWord; + +// Atomically execute: +// result = *ptr; +// if (*ptr == old_value) +// *ptr = new_value; +// return result; +// +// I.e., replace "*ptr" with "new_value" if "*ptr" used to be "old_value". +// Always return the old value of "*ptr" +// +// This routine implies no memory barriers. +Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value); + +// Atomically store new_value into *ptr, returning the previous value held in +// *ptr. This routine implies no memory barriers. +Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, Atomic32 new_value); + +// Atomically increment *ptr by "increment". Returns the new value of +// *ptr with the increment applied. This routine implies no memory barriers. +Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, Atomic32 increment); + +Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment); + +// These following lower-level operations are typically useful only to people +// implementing higher-level synchronization operations like spinlocks, +// mutexes, and condition-variables. They combine CompareAndSwap(), a load, or +// a store with appropriate memory-ordering instructions. "Acquire" operations +// ensure that no later memory access can be reordered ahead of the operation. +// "Release" operations ensure that no previous memory access can be reordered +// after the operation. "Barrier" operations have both "Acquire" and "Release" +// semantics. A MemoryBarrier() has "Barrier" semantics, but does no memory +// access. +Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value); +Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value); + +void MemoryBarrier(); +void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value); +void Acquire_Store(volatile Atomic32* ptr, Atomic32 value); +void Release_Store(volatile Atomic32* ptr, Atomic32 value); + +Atomic32 NoBarrier_Load(volatile const Atomic32* ptr); +Atomic32 Acquire_Load(volatile const Atomic32* ptr); +Atomic32 Release_Load(volatile const Atomic32* ptr); + +// 64-bit atomic operations (only available on 64-bit processors). +#ifdef ARCH_CPU_64_BITS +Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value); +Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, Atomic64 new_value); +Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, Atomic64 increment); +Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, Atomic64 increment); + +Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value); +Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value); +void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value); +void Acquire_Store(volatile Atomic64* ptr, Atomic64 value); +void Release_Store(volatile Atomic64* ptr, Atomic64 value); +Atomic64 NoBarrier_Load(volatile const Atomic64* ptr); +Atomic64 Acquire_Load(volatile const Atomic64* ptr); +Atomic64 Release_Load(volatile const Atomic64* ptr); +#endif // ARCH_CPU_64_BITS + +} // namespace subtle +} // namespace butil + +// Include our platform specific implementation. +#if defined(THREAD_SANITIZER) +#include "butil/atomicops_internals_tsan.h" +#elif defined(OS_WIN) && defined(COMPILER_MSVC) && defined(ARCH_CPU_X86_FAMILY) +#include "butil/atomicops_internals_x86_msvc.h" +#elif defined(OS_MACOSX) +#include "butil/atomicops_internals_mac.h" +#elif defined(OS_NACL) +#include "butil/atomicops_internals_gcc.h" +#elif defined(COMPILER_GCC) && defined(ARCH_CPU_ARMEL) +#include "butil/atomicops_internals_arm_gcc.h" +#elif defined(COMPILER_GCC) && defined(ARCH_CPU_ARM64) +#include "butil/atomicops_internals_arm64_gcc.h" +#elif defined(COMPILER_GCC) && defined(ARCH_CPU_X86_FAMILY) +#include "butil/atomicops_internals_x86_gcc.h" +#elif defined(COMPILER_GCC) && defined(ARCH_CPU_MIPS_FAMILY) +#include "butil/atomicops_internals_mips_gcc.h" +#elif defined(COMPILER_GCC) && defined(ARCH_CPU_LOONGARCH64_FAMILY) +#include "butil/atomicops_internals_loongarch64_gcc.h" +#elif defined(COMPILER_GCC) && defined(ARCH_CPU_RISCV_FAMILY) +#include "butil/atomicops_internals_riscv_gcc.h" +#else +#error "Atomic operations are not supported on your platform" +#endif + +// On some platforms we need additional declarations to make +// AtomicWord compatible with our other Atomic* types. +#if defined(OS_MACOSX) || defined(OS_OPENBSD) +#include "butil/atomicops_internals_atomicword_compat.h" +#endif + +// ========= Provide butil::atomic ========= +#if defined(BUTIL_CXX11_ENABLED) + +// gcc supports atomic thread fence since 4.8 checkout +// https://gcc.gnu.org/gcc-4.7/cxx0x_status.html and +// https://gcc.gnu.org/gcc-4.8/cxx0x_status.html for more details +#if defined(__clang__) || \ + !defined(__GNUC__) || \ + (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 >= 40800) + +# include + +#else + +# if __GNUC__ * 10000 + __GNUC_MINOR__ * 100 >= 40500 +// gcc 4.5 renames cstdatomic to atomic +// (https://gcc.gnu.org/gcc-4.5/changes.html) +# include +# else +# include +# endif + +namespace std { + +BUTIL_FORCE_INLINE void atomic_thread_fence(memory_order v) { + switch (v) { + case memory_order_relaxed: + break; + case memory_order_consume: + case memory_order_acquire: + case memory_order_release: + case memory_order_acq_rel: + __asm__ __volatile__("" : : : "memory"); + break; + case memory_order_seq_cst: + __asm__ __volatile__("mfence" : : : "memory"); + break; + } +} + +BUTIL_FORCE_INLINE void atomic_signal_fence(memory_order v) { + if (v != memory_order_relaxed) { + __asm__ __volatile__("" : : : "memory"); + } +} + +} // namespace std + +#endif // __GNUC__ + +namespace butil { +using ::std::memory_order; +using ::std::memory_order_relaxed; +using ::std::memory_order_consume; +using ::std::memory_order_acquire; +using ::std::memory_order_release; +using ::std::memory_order_acq_rel; +using ::std::memory_order_seq_cst; +using ::std::atomic_thread_fence; +using ::std::atomic_signal_fence; +template class atomic : public ::std::atomic { +public: + atomic() {} + atomic(T v) : ::std::atomic(v) {} + atomic& operator=(T v) { + this->store(v); + return *this; + } +private: + DISALLOW_COPY_AND_ASSIGN(atomic); + // Make sure memory layout of std::atomic and boost::atomic + // are same so that different compilation units seeing different + // definitions(enable C++11 or not) should be compatible. + BAIDU_CASSERT(sizeof(T) == sizeof(::std::atomic), size_must_match); +}; +} // namespace butil +#else +#include +namespace butil { +using ::boost::memory_order; +using ::boost::memory_order_relaxed; +using ::boost::memory_order_consume; +using ::boost::memory_order_acquire; +using ::boost::memory_order_release; +using ::boost::memory_order_acq_rel; +using ::boost::memory_order_seq_cst; +using ::boost::atomic_thread_fence; +using ::boost::atomic_signal_fence; +template class atomic : public ::boost::atomic { +public: + atomic() {} + atomic(T v) : ::boost::atomic(v) {} + atomic& operator=(T v) { + this->store(v); + return *this; + } +private: + DISALLOW_COPY_AND_ASSIGN(atomic); + // Make sure memory layout of std::atomic and boost::atomic + // are same so that different compilation units seeing different + // definitions(enable C++11 or not) should be compatible. + BAIDU_CASSERT(sizeof(T) == sizeof(::boost::atomic), size_must_match); +}; +} // namespace butil +#endif + +// static_atomic<> is a work-around for C++03 to declare global atomics +// w/o constructing-order issues. It can also used in C++11 though. +// Example: +// butil::static_atomic g_counter = BUTIL_STATIC_ATOMIC_INIT(0); +// Notice that to make static_atomic work for C++03, it cannot be +// initialized by a constructor. Following code is wrong: +// butil::static_atomic g_counter(0); // Not compile + +#define BUTIL_STATIC_ATOMIC_INIT(val) { (val) } + +namespace butil { +template struct static_atomic { + T val; + + // NOTE: the memory_order parameters must be present. + T load(memory_order o) { return ref().load(o); } + void store(T v, memory_order o) { return ref().store(v, o); } + T exchange(T v, memory_order o) { return ref().exchange(v, o); } + bool compare_exchange_weak(T& e, T d, memory_order o) + { return ref().compare_exchange_weak(e, d, o); } + bool compare_exchange_weak(T& e, T d, memory_order so, memory_order fo) + { return ref().compare_exchange_weak(e, d, so, fo); } + bool compare_exchange_strong(T& e, T d, memory_order o) + { return ref().compare_exchange_strong(e, d, o); } + bool compare_exchange_strong(T& e, T d, memory_order so, memory_order fo) + { return ref().compare_exchange_strong(e, d, so, fo); } + T fetch_add(T v, memory_order o) { return ref().fetch_add(v, o); } + T fetch_sub(T v, memory_order o) { return ref().fetch_sub(v, o); } + T fetch_and(T v, memory_order o) { return ref().fetch_and(v, o); } + T fetch_or(T v, memory_order o) { return ref().fetch_or(v, o); } + T fetch_xor(T v, memory_order o) { return ref().fetch_xor(v, o); } + static_atomic& operator=(T v) { + store(v, memory_order_seq_cst); + return *this; + } +private: + DISALLOW_ASSIGN(static_atomic); + BAIDU_CASSERT(sizeof(T) == sizeof(atomic), size_must_match); + atomic& ref() { + // Suppress strict-alias warnings. + atomic* p = reinterpret_cast*>(&val); + return *p; + } +}; +} // namespace butil + +#endif // BUTIL_ATOMICOPS_H_ diff --git a/src/butil/atomicops_internals_arm64_gcc.h b/src/butil/atomicops_internals_arm64_gcc.h new file mode 100644 index 0000000..0776d71 --- /dev/null +++ b/src/butil/atomicops_internals_arm64_gcc.h @@ -0,0 +1,307 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is an internal atomic implementation, use butil/atomicops.h instead. + +// TODO(rmcilroy): Investigate whether we can use __sync__ intrinsics instead of +// the hand coded assembly without introducing perf regressions. +// TODO(rmcilroy): Investigate whether we can use acquire / release versions of +// exclusive load / store assembly instructions and do away with +// the barriers. + +#ifndef BUTIL_ATOMICOPS_INTERNALS_ARM64_GCC_H_ +#define BUTIL_ATOMICOPS_INTERNALS_ARM64_GCC_H_ + +#if defined(OS_QNX) +#include +#endif + +namespace butil { +namespace subtle { + +inline void MemoryBarrier() { + __asm__ __volatile__ ("dmb ish" ::: "memory"); // NOLINT +} + +// NoBarrier versions of the operation include "memory" in the clobber list. +// This is not required for direct usage of the NoBarrier versions of the +// operations. However this is required for correctness when they are used as +// part of the Acquire or Release versions, to ensure that nothing from outside +// the call is reordered between the operation and the memory barrier. This does +// not change the code generated, so has no or minimal impact on the +// NoBarrier operations. + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev; + int32_t temp; + + __asm__ __volatile__ ( // NOLINT + "0: \n\t" + "ldxr %w[prev], %[ptr] \n\t" // Load the previous value. + "cmp %w[prev], %w[old_value] \n\t" + "bne 1f \n\t" + "stxr %w[temp], %w[new_value], %[ptr] \n\t" // Try to store the new value. + "cbnz %w[temp], 0b \n\t" // Retry if it did not work. + "1: \n\t" + : [prev]"=&r" (prev), + [temp]"=&r" (temp), + [ptr]"+Q" (*ptr) + : [old_value]"IJr" (old_value), + [new_value]"r" (new_value) + : "cc", "memory" + ); // NOLINT + + return prev; +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + Atomic32 result; + int32_t temp; + + __asm__ __volatile__ ( // NOLINT + "0: \n\t" + "ldxr %w[result], %[ptr] \n\t" // Load the previous value. + "stxr %w[temp], %w[new_value], %[ptr] \n\t" // Try to store the new value. + "cbnz %w[temp], 0b \n\t" // Retry if it did not work. + : [result]"=&r" (result), + [temp]"=&r" (temp), + [ptr]"+Q" (*ptr) + : [new_value]"r" (new_value) + : "memory" + ); // NOLINT + + return result; +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + Atomic32 result; + int32_t temp; + + __asm__ __volatile__ ( // NOLINT + "0: \n\t" + "ldxr %w[result], %[ptr] \n\t" // Load the previous value. + "add %w[result], %w[result], %w[increment]\n\t" + "stxr %w[temp], %w[result], %[ptr] \n\t" // Try to store the result. + "cbnz %w[temp], 0b \n\t" // Retry on failure. + : [result]"=&r" (result), + [temp]"=&r" (temp), + [ptr]"+Q" (*ptr) + : [increment]"IJr" (increment) + : "memory" + ); // NOLINT + + return result; +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + MemoryBarrier(); + Atomic32 result = NoBarrier_AtomicIncrement(ptr, increment); + MemoryBarrier(); + + return result; +} + +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev = NoBarrier_CompareAndSwap(ptr, old_value, new_value); + MemoryBarrier(); + + return prev; +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + MemoryBarrier(); + Atomic32 prev = NoBarrier_CompareAndSwap(ptr, old_value, new_value); + + return prev; +} + +inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { + __asm__ __volatile__ ( // NOLINT + "stlr %w[value], %[ptr] \n\t" + : [ptr]"=Q" (*ptr) + : [value]"r" (value) + : "memory" + ); // NOLINT +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { + return *ptr; +} + +inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { + Atomic32 value; + + __asm__ __volatile__ ( // NOLINT + "ldar %w[value], %[ptr] \n\t" + : [value]"=r" (value) + : [ptr]"Q" (*ptr) + : "memory" + ); // NOLINT + + return value; +} + +inline Atomic32 Release_Load(volatile const Atomic32* ptr) { + MemoryBarrier(); + return *ptr; +} + +// 64-bit versions of the operations. +// See the 32-bit versions for comments. + +inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 prev; + int32_t temp; + + __asm__ __volatile__ ( // NOLINT + "0: \n\t" + "ldxr %[prev], %[ptr] \n\t" + "cmp %[prev], %[old_value] \n\t" + "bne 1f \n\t" + "stxr %w[temp], %[new_value], %[ptr] \n\t" + "cbnz %w[temp], 0b \n\t" + "1: \n\t" + : [prev]"=&r" (prev), + [temp]"=&r" (temp), + [ptr]"+Q" (*ptr) + : [old_value]"IJr" (old_value), + [new_value]"r" (new_value) + : "cc", "memory" + ); // NOLINT + + return prev; +} + +inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, + Atomic64 new_value) { + Atomic64 result; + int32_t temp; + + __asm__ __volatile__ ( // NOLINT + "0: \n\t" + "ldxr %[result], %[ptr] \n\t" + "stxr %w[temp], %[new_value], %[ptr] \n\t" + "cbnz %w[temp], 0b \n\t" + : [result]"=&r" (result), + [temp]"=&r" (temp), + [ptr]"+Q" (*ptr) + : [new_value]"r" (new_value) + : "memory" + ); // NOLINT + + return result; +} + +inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + Atomic64 result; + int32_t temp; + + __asm__ __volatile__ ( // NOLINT + "0: \n\t" + "ldxr %[result], %[ptr] \n\t" + "add %[result], %[result], %[increment] \n\t" + "stxr %w[temp], %[result], %[ptr] \n\t" + "cbnz %w[temp], 0b \n\t" + : [result]"=&r" (result), + [temp]"=&r" (temp), + [ptr]"+Q" (*ptr) + : [increment]"IJr" (increment) + : "memory" + ); // NOLINT + + return result; +} + +inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + MemoryBarrier(); + Atomic64 result = NoBarrier_AtomicIncrement(ptr, increment); + MemoryBarrier(); + + return result; +} + +inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 prev = NoBarrier_CompareAndSwap(ptr, old_value, new_value); + MemoryBarrier(); + + return prev; +} + +inline Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + MemoryBarrier(); + Atomic64 prev = NoBarrier_CompareAndSwap(ptr, old_value, new_value); + + return prev; +} + +inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic64* ptr, Atomic64 value) { + __asm__ __volatile__ ( // NOLINT + "stlr %x[value], %[ptr] \n\t" + : [ptr]"=Q" (*ptr) + : [value]"r" (value) + : "memory" + ); // NOLINT +} + +inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) { + return *ptr; +} + +inline Atomic64 Acquire_Load(volatile const Atomic64* ptr) { + Atomic64 value; + + __asm__ __volatile__ ( // NOLINT + "ldar %x[value], %[ptr] \n\t" + : [value]"=r" (value) + : [ptr]"Q" (*ptr) + : "memory" + ); // NOLINT + + return value; +} + +inline Atomic64 Release_Load(volatile const Atomic64* ptr) { + MemoryBarrier(); + return *ptr; +} + +} // namespace butil::subtle +} // namespace butil + +#endif // BUTIL_ATOMICOPS_INTERNALS_ARM64_GCC_H_ diff --git a/src/butil/atomicops_internals_arm_gcc.h b/src/butil/atomicops_internals_arm_gcc.h new file mode 100644 index 0000000..504ace2 --- /dev/null +++ b/src/butil/atomicops_internals_arm_gcc.h @@ -0,0 +1,294 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is an internal atomic implementation, use butil/atomicops.h instead. +// +// LinuxKernelCmpxchg and Barrier_AtomicIncrement are from Google Gears. + +#ifndef BUTIL_ATOMICOPS_INTERNALS_ARM_GCC_H_ +#define BUTIL_ATOMICOPS_INTERNALS_ARM_GCC_H_ + +#if defined(OS_QNX) +#include +#endif + +namespace butil { +namespace subtle { + +// Memory barriers on ARM are funky, but the kernel is here to help: +// +// * ARMv5 didn't support SMP, there is no memory barrier instruction at +// all on this architecture, or when targeting its machine code. +// +// * Some ARMv6 CPUs support SMP. A full memory barrier can be produced by +// writing a random value to a very specific coprocessor register. +// +// * On ARMv7, the "dmb" instruction is used to perform a full memory +// barrier (though writing to the co-processor will still work). +// However, on single core devices (e.g. Nexus One, or Nexus S), +// this instruction will take up to 200 ns, which is huge, even though +// it's completely un-needed on these devices. +// +// * There is no easy way to determine at runtime if the device is +// single or multi-core. However, the kernel provides a useful helper +// function at a fixed memory address (0xffff0fa0), which will always +// perform a memory barrier in the most efficient way. I.e. on single +// core devices, this is an empty function that exits immediately. +// On multi-core devices, it implements a full memory barrier. +// +// * This source could be compiled to ARMv5 machine code that runs on a +// multi-core ARMv6 or ARMv7 device. In this case, memory barriers +// are needed for correct execution. Always call the kernel helper, even +// when targeting ARMv5TE. +// + +inline void MemoryBarrier() { +#if defined(OS_LINUX) || defined(OS_ANDROID) + // Note: This is a function call, which is also an implicit compiler barrier. + typedef void (*KernelMemoryBarrierFunc)(); + ((KernelMemoryBarrierFunc)0xffff0fa0)(); +#elif defined(OS_QNX) + __cpu_membarrier(); +#else +#error MemoryBarrier() is not implemented on this platform. +#endif +} + +// An ARM toolchain would only define one of these depending on which +// variant of the target architecture is being used. This tests against +// any known ARMv6 or ARMv7 variant, where it is possible to directly +// use ldrex/strex instructions to implement fast atomic operations. +#if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || \ + defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || \ + defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || \ + defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || \ + defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev_value; + int reloop; + do { + // The following is equivalent to: + // + // prev_value = LDREX(ptr) + // reloop = 0 + // if (prev_value != old_value) + // reloop = STREX(ptr, new_value) + __asm__ __volatile__(" ldrex %0, [%3]\n" + " mov %1, #0\n" + " cmp %0, %4\n" +#ifdef __thumb2__ + " it eq\n" +#endif + " strexeq %1, %5, [%3]\n" + : "=&r"(prev_value), "=&r"(reloop), "+m"(*ptr) + : "r"(ptr), "r"(old_value), "r"(new_value) + : "cc", "memory"); + } while (reloop != 0); + return prev_value; +} + +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 result = NoBarrier_CompareAndSwap(ptr, old_value, new_value); + MemoryBarrier(); + return result; +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + MemoryBarrier(); + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + Atomic32 value; + int reloop; + do { + // Equivalent to: + // + // value = LDREX(ptr) + // value += increment + // reloop = STREX(ptr, value) + // + __asm__ __volatile__(" ldrex %0, [%3]\n" + " add %0, %0, %4\n" + " strex %1, %0, [%3]\n" + : "=&r"(value), "=&r"(reloop), "+m"(*ptr) + : "r"(ptr), "r"(increment) + : "cc", "memory"); + } while (reloop); + return value; +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + // TODO(digit): Investigate if it's possible to implement this with + // a single MemoryBarrier() operation between the LDREX and STREX. + // See http://crbug.com/246514 + MemoryBarrier(); + Atomic32 result = NoBarrier_AtomicIncrement(ptr, increment); + MemoryBarrier(); + return result; +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + Atomic32 old_value; + int reloop; + do { + // old_value = LDREX(ptr) + // reloop = STREX(ptr, new_value) + __asm__ __volatile__(" ldrex %0, [%3]\n" + " strex %1, %4, [%3]\n" + : "=&r"(old_value), "=&r"(reloop), "+m"(*ptr) + : "r"(ptr), "r"(new_value) + : "cc", "memory"); + } while (reloop != 0); + return old_value; +} + +// This tests against any known ARMv5 variant. +#elif defined(__ARM_ARCH_5__) || defined(__ARM_ARCH_5T__) || \ + defined(__ARM_ARCH_5TE__) || defined(__ARM_ARCH_5TEJ__) + +// The kernel also provides a helper function to perform an atomic +// compare-and-swap operation at the hard-wired address 0xffff0fc0. +// On ARMv5, this is implemented by a special code path that the kernel +// detects and treats specially when thread pre-emption happens. +// On ARMv6 and higher, it uses LDREX/STREX instructions instead. +// +// Note that this always perform a full memory barrier, there is no +// need to add calls MemoryBarrier() before or after it. It also +// returns 0 on success, and 1 on exit. +// +// Available and reliable since Linux 2.6.24. Both Android and ChromeOS +// use newer kernel revisions, so this should not be a concern. +namespace { + +inline int LinuxKernelCmpxchg(Atomic32 old_value, + Atomic32 new_value, + volatile Atomic32* ptr) { + typedef int (*KernelCmpxchgFunc)(Atomic32, Atomic32, volatile Atomic32*); + return ((KernelCmpxchgFunc)0xffff0fc0)(old_value, new_value, ptr); +} + +} // namespace + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev_value; + for (;;) { + prev_value = *ptr; + if (prev_value != old_value) + return prev_value; + if (!LinuxKernelCmpxchg(old_value, new_value, ptr)) + return old_value; + } +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + Atomic32 old_value; + do { + old_value = *ptr; + } while (LinuxKernelCmpxchg(old_value, new_value, ptr)); + return old_value; +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + return Barrier_AtomicIncrement(ptr, increment); +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + for (;;) { + // Atomic exchange the old value with an incremented one. + Atomic32 old_value = *ptr; + Atomic32 new_value = old_value + increment; + if (!LinuxKernelCmpxchg(old_value, new_value, ptr)) { + // The exchange took place as expected. + return new_value; + } + // Otherwise, *ptr changed mid-loop and we need to retry. + } +} + +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev_value; + for (;;) { + prev_value = *ptr; + if (prev_value != old_value) { + // Always ensure acquire semantics. + MemoryBarrier(); + return prev_value; + } + if (!LinuxKernelCmpxchg(old_value, new_value, ptr)) + return old_value; + } +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + // This could be implemented as: + // MemoryBarrier(); + // return NoBarrier_CompareAndSwap(); + // + // But would use 3 barriers per succesful CAS. To save performance, + // use Acquire_CompareAndSwap(). Its implementation guarantees that: + // - A succesful swap uses only 2 barriers (in the kernel helper). + // - An early return due to (prev_value != old_value) performs + // a memory barrier with no store, which is equivalent to the + // generic implementation above. + return Acquire_CompareAndSwap(ptr, old_value, new_value); +} + +#else +# error "Your CPU's ARM architecture is not supported yet" +#endif + +// NOTE: Atomicity of the following load and store operations is only +// guaranteed in case of 32-bit alignement of |ptr| values. + +inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { + MemoryBarrier(); + *ptr = value; +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { return *ptr; } + +inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { + Atomic32 value = *ptr; + MemoryBarrier(); + return value; +} + +inline Atomic32 Release_Load(volatile const Atomic32* ptr) { + MemoryBarrier(); + return *ptr; +} + +} // namespace butil::subtle +} // namespace butil + +#endif // BUTIL_ATOMICOPS_INTERNALS_ARM_GCC_H_ diff --git a/src/butil/atomicops_internals_atomicword_compat.h b/src/butil/atomicops_internals_atomicword_compat.h new file mode 100644 index 0000000..d564286 --- /dev/null +++ b/src/butil/atomicops_internals_atomicword_compat.h @@ -0,0 +1,100 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is an internal atomic implementation, use butil/atomicops.h instead. + +#ifndef BUTIL_ATOMICOPS_INTERNALS_ATOMICWORD_COMPAT_H_ +#define BUTIL_ATOMICOPS_INTERNALS_ATOMICWORD_COMPAT_H_ + +// AtomicWord is a synonym for intptr_t, and Atomic32 is a synonym for int32_t, +// which in turn means int. On some LP32 platforms, intptr_t is an int, but +// on others, it's a long. When AtomicWord and Atomic32 are based on different +// fundamental types, their pointers are incompatible. +// +// This file defines function overloads to allow both AtomicWord and Atomic32 +// data to be used with this interface. +// +// On LP64 platforms, AtomicWord and Atomic64 are both always long, +// so this problem doesn't occur. + +#if !defined(ARCH_CPU_64_BITS) + +namespace butil { +namespace subtle { + +inline AtomicWord NoBarrier_CompareAndSwap(volatile AtomicWord* ptr, + AtomicWord old_value, + AtomicWord new_value) { + return NoBarrier_CompareAndSwap( + reinterpret_cast(ptr), old_value, new_value); +} + +inline AtomicWord NoBarrier_AtomicExchange(volatile AtomicWord* ptr, + AtomicWord new_value) { + return NoBarrier_AtomicExchange( + reinterpret_cast(ptr), new_value); +} + +inline AtomicWord NoBarrier_AtomicIncrement(volatile AtomicWord* ptr, + AtomicWord increment) { + return NoBarrier_AtomicIncrement( + reinterpret_cast(ptr), increment); +} + +inline AtomicWord Barrier_AtomicIncrement(volatile AtomicWord* ptr, + AtomicWord increment) { + return Barrier_AtomicIncrement( + reinterpret_cast(ptr), increment); +} + +inline AtomicWord Acquire_CompareAndSwap(volatile AtomicWord* ptr, + AtomicWord old_value, + AtomicWord new_value) { + return butil::subtle::Acquire_CompareAndSwap( + reinterpret_cast(ptr), old_value, new_value); +} + +inline AtomicWord Release_CompareAndSwap(volatile AtomicWord* ptr, + AtomicWord old_value, + AtomicWord new_value) { + return butil::subtle::Release_CompareAndSwap( + reinterpret_cast(ptr), old_value, new_value); +} + +inline void NoBarrier_Store(volatile AtomicWord *ptr, AtomicWord value) { + NoBarrier_Store( + reinterpret_cast(ptr), value); +} + +inline void Acquire_Store(volatile AtomicWord* ptr, AtomicWord value) { + return butil::subtle::Acquire_Store( + reinterpret_cast(ptr), value); +} + +inline void Release_Store(volatile AtomicWord* ptr, AtomicWord value) { + return butil::subtle::Release_Store( + reinterpret_cast(ptr), value); +} + +inline AtomicWord NoBarrier_Load(volatile const AtomicWord *ptr) { + return NoBarrier_Load( + reinterpret_cast(ptr)); +} + +inline AtomicWord Acquire_Load(volatile const AtomicWord* ptr) { + return butil::subtle::Acquire_Load( + reinterpret_cast(ptr)); +} + +inline AtomicWord Release_Load(volatile const AtomicWord* ptr) { + return butil::subtle::Release_Load( + reinterpret_cast(ptr)); +} + +} // namespace butil::subtle +} // namespace butil + +#endif // !defined(ARCH_CPU_64_BITS) + +#endif // BUTIL_ATOMICOPS_INTERNALS_ATOMICWORD_COMPAT_H_ diff --git a/src/butil/atomicops_internals_gcc.h b/src/butil/atomicops_internals_gcc.h new file mode 100644 index 0000000..4ba9bf4 --- /dev/null +++ b/src/butil/atomicops_internals_gcc.h @@ -0,0 +1,105 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is an internal atomic implementation, include butil/atomicops.h +// instead. This file is for platforms that use GCC intrinsics rather than +// platform-specific assembly code for atomic operations. + +#ifndef BUTIL_ATOMICOPS_INTERNALS_GCC_H_ +#define BUTIL_ATOMICOPS_INTERNALS_GCC_H_ + +namespace butil { +namespace subtle { + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev_value; + do { + if (__sync_bool_compare_and_swap(ptr, old_value, new_value)) + return old_value; + prev_value = *ptr; + } while (prev_value == old_value); + return prev_value; +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + Atomic32 old_value; + do { + old_value = *ptr; + } while (!__sync_bool_compare_and_swap(ptr, old_value, new_value)); + return old_value; +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + return Barrier_AtomicIncrement(ptr, increment); +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + for (;;) { + // Atomic exchange the old value with an incremented one. + Atomic32 old_value = *ptr; + Atomic32 new_value = old_value + increment; + if (__sync_bool_compare_and_swap(ptr, old_value, new_value)) { + // The exchange took place as expected. + return new_value; + } + // Otherwise, *ptr changed mid-loop and we need to retry. + } +} + +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + // Since NoBarrier_CompareAndSwap uses __sync_bool_compare_and_swap, which + // is a full memory barrier, none is needed here or below in Release. + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; +} + +inline void MemoryBarrier() { + __sync_synchronize(); +} + +inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { + MemoryBarrier(); + *ptr = value; +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { + return *ptr; +} + +inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { + Atomic32 value = *ptr; + MemoryBarrier(); + return value; +} + +inline Atomic32 Release_Load(volatile const Atomic32* ptr) { + MemoryBarrier(); + return *ptr; +} + +} // namespace butil::subtle +} // namespace butil + +#endif // BUTIL_ATOMICOPS_INTERNALS_GCC_H_ diff --git a/src/butil/atomicops_internals_loongarch64_gcc.h b/src/butil/atomicops_internals_loongarch64_gcc.h new file mode 100644 index 0000000..07654c8 --- /dev/null +++ b/src/butil/atomicops_internals_loongarch64_gcc.h @@ -0,0 +1,223 @@ +// Copyright (c) 2023 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is an internal atomic implementation, use butil/atomicops.h instead. + +#ifndef BUTIL_ATOMICOPS_INTERNALS_LOONGARCH64_GCC_H_ +#define BUTIL_ATOMICOPS_INTERNALS_LOONGARCH64_GCC_H_ + +#include "butil/atomicops.h" +#include "butil/atomicops_internals_loongarch64_gcc.h" + +namespace butil { +namespace subtle { + +// 32bit +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 ret; + __asm__ __volatile__("1:\n" + "ll.w %0, %1\n" + "or $t0, %3, $zero\n" + "bne %0, %2, 2f\n" + "sc.w $t0, %1\n" + "beqz $t0, 1b\n" + "2:\n" + "dbar 0\n" + : "=&r" (ret), "+ZB"(*ptr) + : "r" (old_value), "r" (new_value) + : "t0", "memory"); + return ret; +} + +// Atomically store new_value into *ptr, returning the previous value held in +// *ptr. This routine implies no memory barriers. +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + Atomic32 ret; + __asm__ __volatile__("amswap_db.w %0, %2, %1\n" + : "=&r"(ret), "+ZB"(*ptr) + : "r"(new_value) + : "memory"); + return ret; +} + +// Atomically increment *ptr by "increment". Returns the new value of +// *ptr with the increment applied. This routine implies no memory barriers. +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + Atomic32 tmp; + __asm__ __volatile__("amadd_db.w %1, %2, %0\n" + : "+ZB"(*ptr), "=&r"(tmp) + : "r"(increment) + : "memory"); + return tmp+increment; +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + MemoryBarrier(); + Atomic32 res = NoBarrier_AtomicIncrement(ptr, increment); + MemoryBarrier(); + return res; +} + +// "Acquire" operations +// ensure that no later memory access can be reordered ahead of the operation. +// "Release" operations ensure that no previous memory access can be reordered +// after the operation. "Barrier" operations have both "Acquire" and "Release" +// semantics. A MemoryBarrier() has "Barrier" semantics, but does no memory +// access. +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 res = NoBarrier_CompareAndSwap(ptr, old_value, new_value); + MemoryBarrier(); + return res; +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + MemoryBarrier(); + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { + MemoryBarrier(); + *ptr = value; +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { + return *ptr; +} + +inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { + Atomic32 value = *ptr; + MemoryBarrier(); + return value; +} + +inline Atomic32 Release_Load(volatile const Atomic32* ptr) { + MemoryBarrier(); + return *ptr; +} + +// 64bit +inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 ret; + __asm__ __volatile__("1:\n" + "ll.d %0, %1\n" + "or $t0, %3, $zero\n" + "bne %0, %2, 2f\n" + "sc.d $t0, %1\n" + "beqz $t0, 1b\n" + "2:\n" + "dbar 0\n" + : "=&r" (ret), "+ZB"(*ptr) + : "r" (old_value), "r" (new_value) + : "t0", "memory"); + return ret; +} + +// Atomically store new_value into *ptr, returning the previous value held in +// *ptr. This routine implies no memory barriers. +inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, + Atomic64 new_value) { + Atomic64 ret; + __asm__ __volatile__("amswap_db.d %0, %2, %1\n" + : "=&r"(ret), "+ZB"(*ptr) + : "r"(new_value) + : "memory"); + return ret; +} + +inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + Atomic64 tmp; + __asm__ __volatile__("amadd_db.d %1, %2, %0\n" + : "+ZB"(*ptr), "=&r"(tmp) + : "r"(increment) + : "memory"); + return tmp+increment; +} + +inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + MemoryBarrier(); + Atomic64 res = NoBarrier_AtomicIncrement(ptr, increment); + MemoryBarrier(); + return res; +} + +// "Acquire" operations +// ensure that no later memory access can be reordered ahead of the operation. +// "Release" operations ensure that no previous memory access can be reordered +// after the operation. "Barrier" operations have both "Acquire" and "Release" +// semantics. A MemoryBarrier() has "Barrier" semantics, but does no memory +// access. +inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 res = NoBarrier_CompareAndSwap(ptr, old_value, new_value); + MemoryBarrier(); + return res; +} + +inline Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + MemoryBarrier(); + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; +} + +inline void MemoryBarrier() { + __asm__ __volatile__("dbar 0x0" : : : "memory"); +} + +inline void Acquire_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic64* ptr, Atomic64 value) { + MemoryBarrier(); + *ptr = value; +} + +inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) { + return *ptr; +} + +inline Atomic64 Acquire_Load(volatile const Atomic64* ptr) { + Atomic64 value = *ptr; + MemoryBarrier(); + return value; +} + +inline Atomic64 Release_Load(volatile const Atomic64* ptr) { + MemoryBarrier(); + return *ptr; +} + +} // namespace butil::subtle +} // namespace butil + +#endif // BUTIL_ATOMICOPS_INTERNALS_LOONGARCH64_GCC_H_ diff --git a/src/butil/atomicops_internals_mac.h b/src/butil/atomicops_internals_mac.h new file mode 100644 index 0000000..1384da2 --- /dev/null +++ b/src/butil/atomicops_internals_mac.h @@ -0,0 +1,197 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is an internal atomic implementation, use butil/atomicops.h instead. + +#ifndef BUTIL_ATOMICOPS_INTERNALS_MAC_H_ +#define BUTIL_ATOMICOPS_INTERNALS_MAC_H_ + +#include + +namespace butil { +namespace subtle { + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev_value; + do { + if (OSAtomicCompareAndSwap32(old_value, new_value, + const_cast(ptr))) { + return old_value; + } + prev_value = *ptr; + } while (prev_value == old_value); + return prev_value; +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + Atomic32 old_value; + do { + old_value = *ptr; + } while (!OSAtomicCompareAndSwap32(old_value, new_value, + const_cast(ptr))); + return old_value; +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + return OSAtomicAdd32(increment, const_cast(ptr)); +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + return OSAtomicAdd32Barrier(increment, const_cast(ptr)); +} + +inline void MemoryBarrier() { + OSMemoryBarrier(); +} + +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev_value; + do { + if (OSAtomicCompareAndSwap32Barrier(old_value, new_value, + const_cast(ptr))) { + return old_value; + } + prev_value = *ptr; + } while (prev_value == old_value); + return prev_value; +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + return Acquire_CompareAndSwap(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { + MemoryBarrier(); + *ptr = value; +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { + return *ptr; +} + +inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { + Atomic32 value = *ptr; + MemoryBarrier(); + return value; +} + +inline Atomic32 Release_Load(volatile const Atomic32* ptr) { + MemoryBarrier(); + return *ptr; +} + +#ifdef __LP64__ + +// 64-bit implementation on 64-bit platform + +inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 prev_value; + do { + if (OSAtomicCompareAndSwap64(old_value, new_value, + reinterpret_cast(ptr))) { + return old_value; + } + prev_value = *ptr; + } while (prev_value == old_value); + return prev_value; +} + +inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, + Atomic64 new_value) { + Atomic64 old_value; + do { + old_value = *ptr; + } while (!OSAtomicCompareAndSwap64(old_value, new_value, + reinterpret_cast(ptr))); + return old_value; +} + +inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + return OSAtomicAdd64(increment, reinterpret_cast(ptr)); +} + +inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + return OSAtomicAdd64Barrier(increment, + reinterpret_cast(ptr)); +} + +inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 prev_value; + do { + if (OSAtomicCompareAndSwap64Barrier( + old_value, new_value, reinterpret_cast(ptr))) { + return old_value; + } + prev_value = *ptr; + } while (prev_value == old_value); + return prev_value; +} + +inline Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + // The lib kern interface does not distinguish between + // Acquire and Release memory barriers; they are equivalent. + return Acquire_CompareAndSwap(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic64* ptr, Atomic64 value) { + MemoryBarrier(); + *ptr = value; +} + +inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) { + return *ptr; +} + +inline Atomic64 Acquire_Load(volatile const Atomic64* ptr) { + Atomic64 value = *ptr; + MemoryBarrier(); + return value; +} + +inline Atomic64 Release_Load(volatile const Atomic64* ptr) { + MemoryBarrier(); + return *ptr; +} + +#endif // defined(__LP64__) + +} // namespace butil::subtle +} // namespace butil + +#endif // BUTIL_ATOMICOPS_INTERNALS_MAC_H_ diff --git a/src/butil/atomicops_internals_mips_gcc.h b/src/butil/atomicops_internals_mips_gcc.h new file mode 100644 index 0000000..8d729c0 --- /dev/null +++ b/src/butil/atomicops_internals_mips_gcc.h @@ -0,0 +1,154 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is an internal atomic implementation, use butil/atomicops.h instead. +// +// LinuxKernelCmpxchg and Barrier_AtomicIncrement are from Google Gears. + +#ifndef BUTIL_ATOMICOPS_INTERNALS_MIPS_GCC_H_ +#define BUTIL_ATOMICOPS_INTERNALS_MIPS_GCC_H_ + +namespace butil { +namespace subtle { + +// Atomically execute: +// result = *ptr; +// if (*ptr == old_value) +// *ptr = new_value; +// return result; +// +// I.e., replace "*ptr" with "new_value" if "*ptr" used to be "old_value". +// Always return the old value of "*ptr" +// +// This routine implies no memory barriers. +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev, tmp; + __asm__ __volatile__(".set push\n" + ".set noreorder\n" + "1:\n" + "ll %0, %5\n" // prev = *ptr + "bne %0, %3, 2f\n" // if (prev != old_value) goto 2 + "move %2, %4\n" // tmp = new_value + "sc %2, %1\n" // *ptr = tmp (with atomic check) + "beqz %2, 1b\n" // start again on atomic error + "nop\n" // delay slot nop + "2:\n" + ".set pop\n" + : "=&r" (prev), "=m" (*ptr), "=&r" (tmp) + : "Ir" (old_value), "r" (new_value), "m" (*ptr) + : "memory"); + return prev; +} + +// Atomically store new_value into *ptr, returning the previous value held in +// *ptr. This routine implies no memory barriers. +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + Atomic32 temp, old; + __asm__ __volatile__(".set push\n" + ".set noreorder\n" + "1:\n" + "ll %1, %2\n" // old = *ptr + "move %0, %3\n" // temp = new_value + "sc %0, %2\n" // *ptr = temp (with atomic check) + "beqz %0, 1b\n" // start again on atomic error + "nop\n" // delay slot nop + ".set pop\n" + : "=&r" (temp), "=&r" (old), "=m" (*ptr) + : "r" (new_value), "m" (*ptr) + : "memory"); + + return old; +} + +// Atomically increment *ptr by "increment". Returns the new value of +// *ptr with the increment applied. This routine implies no memory barriers. +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + Atomic32 temp, temp2; + + __asm__ __volatile__(".set push\n" + ".set noreorder\n" + "1:\n" + "ll %0, %2\n" // temp = *ptr + "addu %1, %0, %3\n" // temp2 = temp + increment + "sc %1, %2\n" // *ptr = temp2 (with atomic check) + "beqz %1, 1b\n" // start again on atomic error + "addu %1, %0, %3\n" // temp2 = temp + increment + ".set pop\n" + : "=&r" (temp), "=&r" (temp2), "=m" (*ptr) + : "Ir" (increment), "m" (*ptr) + : "memory"); + // temp2 now holds the final value. + return temp2; +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + MemoryBarrier(); + Atomic32 res = NoBarrier_AtomicIncrement(ptr, increment); + MemoryBarrier(); + return res; +} + +// "Acquire" operations +// ensure that no later memory access can be reordered ahead of the operation. +// "Release" operations ensure that no previous memory access can be reordered +// after the operation. "Barrier" operations have both "Acquire" and "Release" +// semantics. A MemoryBarrier() has "Barrier" semantics, but does no memory +// access. +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 res = NoBarrier_CompareAndSwap(ptr, old_value, new_value); + MemoryBarrier(); + return res; +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + MemoryBarrier(); + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; +} + +inline void MemoryBarrier() { + __asm__ __volatile__("sync" : : : "memory"); +} + +inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { + MemoryBarrier(); + *ptr = value; +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { + return *ptr; +} + +inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { + Atomic32 value = *ptr; + MemoryBarrier(); + return value; +} + +inline Atomic32 Release_Load(volatile const Atomic32* ptr) { + MemoryBarrier(); + return *ptr; +} + +} // namespace butil::subtle +} // namespace butil + +#endif // BUTIL_ATOMICOPS_INTERNALS_MIPS_GCC_H_ diff --git a/src/butil/atomicops_internals_riscv_gcc.h b/src/butil/atomicops_internals_riscv_gcc.h new file mode 100644 index 0000000..e7bd78b --- /dev/null +++ b/src/butil/atomicops_internals_riscv_gcc.h @@ -0,0 +1,192 @@ +// Copyright 2024 The Apache Software Foundation. All rights reserved. +// Use of this source code is governed by the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +// This file is an internal atomic implementation, use butil/atomicops.h instead. +// RISC-V architecture specific atomic operations implementation using GCC intrinsics. + +#ifndef BUTIL_ATOMICOPS_INTERNALS_RISCV_GCC_H_ +#define BUTIL_ATOMICOPS_INTERNALS_RISCV_GCC_H_ + +namespace butil { +namespace subtle { + +inline void MemoryBarrier() { + __asm__ __volatile__ ("fence" ::: "memory"); // NOLINT +} + +// RISC-V atomic operations using GCC built-in functions +// These are implemented using the standard GCC atomic built-ins which +// are supported on RISC-V since GCC 7.1+ + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev_value; + do { + if (__sync_bool_compare_and_swap(ptr, old_value, new_value)) + return old_value; + prev_value = *ptr; + } while (prev_value == old_value); + return prev_value; +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + Atomic32 old_value; + do { + old_value = *ptr; + } while (!__sync_bool_compare_and_swap(ptr, old_value, new_value)); + return old_value; +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + return Barrier_AtomicIncrement(ptr, increment); +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + for (;;) { + // Atomic exchange the old value with an incremented one. + Atomic32 old_value = *ptr; + Atomic32 new_value = old_value + increment; + if (__sync_bool_compare_and_swap(ptr, old_value, new_value)) { + // The exchange took place as expected. + return new_value; + } + // Otherwise, *ptr changed mid-loop and we need to retry. + } +} + +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + // Since NoBarrier_CompareAndSwap uses __sync_bool_compare_and_swap, which + // is a full memory barrier, none is needed here or below in Release. + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { + MemoryBarrier(); + *ptr = value; +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { + return *ptr; +} + +inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { + Atomic32 value = *ptr; + MemoryBarrier(); + return value; +} + +inline Atomic32 Release_Load(volatile const Atomic32* ptr) { + MemoryBarrier(); + return *ptr; +} + +// 64-bit versions of the operations. +// See the 32-bit versions for comments. + +inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 prev_value; + do { + if (__sync_bool_compare_and_swap(ptr, old_value, new_value)) + return old_value; + prev_value = *ptr; + } while (prev_value == old_value); + return prev_value; +} + +inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, + Atomic64 new_value) { + Atomic64 old_value; + do { + old_value = *ptr; + } while (!__sync_bool_compare_and_swap(ptr, old_value, new_value)); + return old_value; +} + +inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + return Barrier_AtomicIncrement(ptr, increment); +} + +inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + for (;;) { + // Atomic exchange the old value with an incremented one. + Atomic64 old_value = *ptr; + Atomic64 new_value = old_value + increment; + if (__sync_bool_compare_and_swap(ptr, old_value, new_value)) { + // The exchange took place as expected. + return new_value; + } + // Otherwise, *ptr changed mid-loop and we need to retry. + } +} + +inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic64* ptr, Atomic64 value) { + MemoryBarrier(); + *ptr = value; +} + +inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) { + return *ptr; +} + +inline Atomic64 Acquire_Load(volatile const Atomic64* ptr) { + Atomic64 value = *ptr; + MemoryBarrier(); + return value; +} + +inline Atomic64 Release_Load(volatile const Atomic64* ptr) { + MemoryBarrier(); + return *ptr; +} + +} // namespace butil::subtle +} // namespace butil + +#endif // BUTIL_ATOMICOPS_INTERNALS_RISCV_GCC_H_ diff --git a/src/butil/atomicops_internals_tsan.h b/src/butil/atomicops_internals_tsan.h new file mode 100644 index 0000000..dd2bf1c --- /dev/null +++ b/src/butil/atomicops_internals_tsan.h @@ -0,0 +1,186 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is an internal atomic implementation for compiler-based +// ThreadSanitizer. Use butil/atomicops.h instead. + +#ifndef BUTIL_ATOMICOPS_INTERNALS_TSAN_H_ +#define BUTIL_ATOMICOPS_INTERNALS_TSAN_H_ + +#include + +namespace butil { +namespace subtle { + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 cmp = old_value; + __tsan_atomic32_compare_exchange_strong(ptr, &cmp, new_value, + __tsan_memory_order_relaxed, __tsan_memory_order_relaxed); + return cmp; +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + return __tsan_atomic32_exchange(ptr, new_value, + __tsan_memory_order_relaxed); +} + +inline Atomic32 Acquire_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + return __tsan_atomic32_exchange(ptr, new_value, + __tsan_memory_order_acquire); +} + +inline Atomic32 Release_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + return __tsan_atomic32_exchange(ptr, new_value, + __tsan_memory_order_release); +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + return increment + __tsan_atomic32_fetch_add(ptr, increment, + __tsan_memory_order_relaxed); +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + return increment + __tsan_atomic32_fetch_add(ptr, increment, + __tsan_memory_order_acq_rel); +} + +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 cmp = old_value; + __tsan_atomic32_compare_exchange_strong(ptr, &cmp, new_value, + __tsan_memory_order_acquire, __tsan_memory_order_acquire); + return cmp; +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 cmp = old_value; + __tsan_atomic32_compare_exchange_strong(ptr, &cmp, new_value, + __tsan_memory_order_release, __tsan_memory_order_relaxed); + return cmp; +} + +inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { + __tsan_atomic32_store(ptr, value, __tsan_memory_order_relaxed); +} + +inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { + __tsan_atomic32_store(ptr, value, __tsan_memory_order_relaxed); + __tsan_atomic_thread_fence(__tsan_memory_order_seq_cst); +} + +inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { + __tsan_atomic32_store(ptr, value, __tsan_memory_order_release); +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { + return __tsan_atomic32_load(ptr, __tsan_memory_order_relaxed); +} + +inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { + return __tsan_atomic32_load(ptr, __tsan_memory_order_acquire); +} + +inline Atomic32 Release_Load(volatile const Atomic32* ptr) { + __tsan_atomic_thread_fence(__tsan_memory_order_seq_cst); + return __tsan_atomic32_load(ptr, __tsan_memory_order_relaxed); +} + +inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 cmp = old_value; + __tsan_atomic64_compare_exchange_strong(ptr, &cmp, new_value, + __tsan_memory_order_relaxed, __tsan_memory_order_relaxed); + return cmp; +} + +inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, + Atomic64 new_value) { + return __tsan_atomic64_exchange(ptr, new_value, __tsan_memory_order_relaxed); +} + +inline Atomic64 Acquire_AtomicExchange(volatile Atomic64* ptr, + Atomic64 new_value) { + return __tsan_atomic64_exchange(ptr, new_value, __tsan_memory_order_acquire); +} + +inline Atomic64 Release_AtomicExchange(volatile Atomic64* ptr, + Atomic64 new_value) { + return __tsan_atomic64_exchange(ptr, new_value, __tsan_memory_order_release); +} + +inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + return increment + __tsan_atomic64_fetch_add(ptr, increment, + __tsan_memory_order_relaxed); +} + +inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + return increment + __tsan_atomic64_fetch_add(ptr, increment, + __tsan_memory_order_acq_rel); +} + +inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) { + __tsan_atomic64_store(ptr, value, __tsan_memory_order_relaxed); +} + +inline void Acquire_Store(volatile Atomic64* ptr, Atomic64 value) { + __tsan_atomic64_store(ptr, value, __tsan_memory_order_relaxed); + __tsan_atomic_thread_fence(__tsan_memory_order_seq_cst); +} + +inline void Release_Store(volatile Atomic64* ptr, Atomic64 value) { + __tsan_atomic64_store(ptr, value, __tsan_memory_order_release); +} + +inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) { + return __tsan_atomic64_load(ptr, __tsan_memory_order_relaxed); +} + +inline Atomic64 Acquire_Load(volatile const Atomic64* ptr) { + return __tsan_atomic64_load(ptr, __tsan_memory_order_acquire); +} + +inline Atomic64 Release_Load(volatile const Atomic64* ptr) { + __tsan_atomic_thread_fence(__tsan_memory_order_seq_cst); + return __tsan_atomic64_load(ptr, __tsan_memory_order_relaxed); +} + +inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 cmp = old_value; + __tsan_atomic64_compare_exchange_strong(ptr, &cmp, new_value, + __tsan_memory_order_acquire, __tsan_memory_order_acquire); + return cmp; +} + +inline Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 cmp = old_value; + __tsan_atomic64_compare_exchange_strong(ptr, &cmp, new_value, + __tsan_memory_order_release, __tsan_memory_order_relaxed); + return cmp; +} + +inline void MemoryBarrier() { + __tsan_atomic_thread_fence(__tsan_memory_order_seq_cst); +} + +} // namespace butil::subtle +} // namespace butil + +#endif // BUTIL_ATOMICOPS_INTERNALS_TSAN_H_ diff --git a/src/butil/atomicops_internals_x86_gcc.cc b/src/butil/atomicops_internals_x86_gcc.cc new file mode 100644 index 0000000..e0db1bd --- /dev/null +++ b/src/butil/atomicops_internals_x86_gcc.cc @@ -0,0 +1,100 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This module gets enough CPU information to optimize the +// atomicops module on x86. + +#include +#include + +#include "butil/atomicops.h" + +// This file only makes sense with atomicops_internals_x86_gcc.h -- it +// depends on structs that are defined in that file. If atomicops.h +// doesn't sub-include that file, then we aren't needed, and shouldn't +// try to do anything. +#ifdef BUTIL_ATOMICOPS_INTERNALS_X86_GCC_H_ + +// Inline cpuid instruction. In PIC compilations, %ebx contains the address +// of the global offset table. To avoid breaking such executables, this code +// must preserve that register's value across cpuid instructions. +#if defined(__i386__) +#define cpuid(a, b, c, d, inp) \ + asm("mov %%ebx, %%edi\n" \ + "cpuid\n" \ + "xchg %%edi, %%ebx\n" \ + : "=a" (a), "=D" (b), "=c" (c), "=d" (d) : "a" (inp)) +#elif defined(__x86_64__) +#define cpuid(a, b, c, d, inp) \ + asm("mov %%rbx, %%rdi\n" \ + "cpuid\n" \ + "xchg %%rdi, %%rbx\n" \ + : "=a" (a), "=D" (b), "=c" (c), "=d" (d) : "a" (inp)) +#endif + +#if defined(cpuid) // initialize the struct only on x86 + +// Set the flags so that code will run correctly and conservatively, so even +// if we haven't been initialized yet, we're probably single threaded, and our +// default values should hopefully be pretty safe. +struct AtomicOps_x86CPUFeatureStruct AtomicOps_Internalx86CPUFeatures = { + false, // bug can't exist before process spawns multiple threads +}; + +namespace { + +// Initialize the AtomicOps_Internalx86CPUFeatures struct. +void AtomicOps_Internalx86CPUFeaturesInit() { + uint32_t eax; + uint32_t ebx; + uint32_t ecx; + uint32_t edx; + + // Get vendor string (issue CPUID with eax = 0) + cpuid(eax, ebx, ecx, edx, 0); + char vendor[13]; + memcpy(vendor, &ebx, 4); + memcpy(vendor + 4, &edx, 4); + memcpy(vendor + 8, &ecx, 4); + vendor[12] = 0; + + // get feature flags in ecx/edx, and family/model in eax + cpuid(eax, ebx, ecx, edx, 1); + + int family = (eax >> 8) & 0xf; // family and model fields + int model = (eax >> 4) & 0xf; + if (family == 0xf) { // use extended family and model fields + family += (eax >> 20) & 0xff; + model += ((eax >> 16) & 0xf) << 4; + } + + // Opteron Rev E has a bug in which on very rare occasions a locked + // instruction doesn't act as a read-acquire barrier if followed by a + // non-locked read-modify-write instruction. Rev F has this bug in + // pre-release versions, but not in versions released to customers, + // so we test only for Rev E, which is family 15, model 32..63 inclusive. + if (strcmp(vendor, "AuthenticAMD") == 0 && // AMD + family == 15 && + 32 <= model && model <= 63) { + AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug = true; + } else { + AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug = false; + } +} + +class AtomicOpsx86Initializer { + public: + AtomicOpsx86Initializer() { + AtomicOps_Internalx86CPUFeaturesInit(); + } +}; + +// A global to get use initialized on startup via static initialization :/ +AtomicOpsx86Initializer g_initer; + +} // namespace + +#endif // if x86 + +#endif // ifdef BUTIL_ATOMICOPS_INTERNALS_X86_GCC_H_ diff --git a/src/butil/atomicops_internals_x86_gcc.h b/src/butil/atomicops_internals_x86_gcc.h new file mode 100644 index 0000000..97d4b64 --- /dev/null +++ b/src/butil/atomicops_internals_x86_gcc.h @@ -0,0 +1,242 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is an internal atomic implementation, use butil/atomicops.h instead. + +#ifndef BUTIL_ATOMICOPS_INTERNALS_X86_GCC_H_ +#define BUTIL_ATOMICOPS_INTERNALS_X86_GCC_H_ + +#include "butil/base_export.h" + +// This struct is not part of the public API of this module; clients may not +// use it. (However, it's exported via BUTIL_EXPORT because clients implicitly +// do use it at link time by inlining these functions.) +// Features of this x86. Values may not be correct before main() is run, +// but are set conservatively. +struct AtomicOps_x86CPUFeatureStruct { + bool has_amd_lock_mb_bug; // Processor has AMD memory-barrier bug; do lfence + // after acquire compare-and-swap. +}; +BUTIL_EXPORT extern struct AtomicOps_x86CPUFeatureStruct + AtomicOps_Internalx86CPUFeatures; + +#define ATOMICOPS_COMPILER_BARRIER() __asm__ __volatile__("" : : : "memory") + +namespace butil { +namespace subtle { + +// 32-bit low-level operations on any platform. + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev; + __asm__ __volatile__("lock; cmpxchgl %1,%2" + : "=a" (prev) + : "q" (new_value), "m" (*ptr), "0" (old_value) + : "memory"); + return prev; +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + __asm__ __volatile__("xchgl %1,%0" // The lock prefix is implicit for xchg. + : "=r" (new_value) + : "m" (*ptr), "0" (new_value) + : "memory"); + return new_value; // Now it's the previous value. +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + Atomic32 temp = increment; + __asm__ __volatile__("lock; xaddl %0,%1" + : "+r" (temp), "+m" (*ptr) + : : "memory"); + // temp now holds the old value of *ptr + return temp + increment; +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + Atomic32 temp = increment; + __asm__ __volatile__("lock; xaddl %0,%1" + : "+r" (temp), "+m" (*ptr) + : : "memory"); + // temp now holds the old value of *ptr + if (AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug) { + __asm__ __volatile__("lfence" : : : "memory"); + } + return temp + increment; +} + +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 x = NoBarrier_CompareAndSwap(ptr, old_value, new_value); + if (AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug) { + __asm__ __volatile__("lfence" : : : "memory"); + } + return x; +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; +} + +inline void MemoryBarrier() { + __asm__ __volatile__("mfence" : : : "memory"); +} + +inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { + ATOMICOPS_COMPILER_BARRIER(); + *ptr = value; // An x86 store acts as a release barrier. + // See comments in Atomic64 version of Release_Store(), below. +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { + return *ptr; +} + +inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { + Atomic32 value = *ptr; // An x86 load acts as a acquire barrier. + // See comments in Atomic64 version of Release_Store(), below. + ATOMICOPS_COMPILER_BARRIER(); + return value; +} + +inline Atomic32 Release_Load(volatile const Atomic32* ptr) { + MemoryBarrier(); + return *ptr; +} + +#if defined(__x86_64__) + +// 64-bit low-level operations on 64-bit platform. + +inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 prev; + __asm__ __volatile__("lock; cmpxchgq %1,%2" + : "=a" (prev) + : "q" (new_value), "m" (*ptr), "0" (old_value) + : "memory"); + return prev; +} + +inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, + Atomic64 new_value) { + __asm__ __volatile__("xchgq %1,%0" // The lock prefix is implicit for xchg. + : "=r" (new_value) + : "m" (*ptr), "0" (new_value) + : "memory"); + return new_value; // Now it's the previous value. +} + +inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + Atomic64 temp = increment; + __asm__ __volatile__("lock; xaddq %0,%1" + : "+r" (temp), "+m" (*ptr) + : : "memory"); + // temp now contains the previous value of *ptr + return temp + increment; +} + +inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + Atomic64 temp = increment; + __asm__ __volatile__("lock; xaddq %0,%1" + : "+r" (temp), "+m" (*ptr) + : : "memory"); + // temp now contains the previous value of *ptr + if (AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug) { + __asm__ __volatile__("lfence" : : : "memory"); + } + return temp + increment; +} + +inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic64* ptr, Atomic64 value) { + ATOMICOPS_COMPILER_BARRIER(); + + *ptr = value; // An x86 store acts as a release barrier + // for current AMD/Intel chips as of Jan 2008. + // See also Acquire_Load(), below. + + // When new chips come out, check: + // IA-32 Intel Architecture Software Developer's Manual, Volume 3: + // System Programming Guide, Chatper 7: Multiple-processor management, + // Section 7.2, Memory Ordering. + // Last seen at: + // http://developer.intel.com/design/pentium4/manuals/index_new.htm + // + // x86 stores/loads fail to act as barriers for a few instructions (clflush + // maskmovdqu maskmovq movntdq movnti movntpd movntps movntq) but these are + // not generated by the compiler, and are rare. Users of these instructions + // need to know about cache behaviour in any case since all of these involve + // either flushing cache lines or non-temporal cache hints. +} + +inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) { + return *ptr; +} + +inline Atomic64 Acquire_Load(volatile const Atomic64* ptr) { + Atomic64 value = *ptr; // An x86 load acts as a acquire barrier, + // for current AMD/Intel chips as of Jan 2008. + // See also Release_Store(), above. + ATOMICOPS_COMPILER_BARRIER(); + return value; +} + +inline Atomic64 Release_Load(volatile const Atomic64* ptr) { + MemoryBarrier(); + return *ptr; +} + +inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 x = NoBarrier_CompareAndSwap(ptr, old_value, new_value); + if (AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug) { + __asm__ __volatile__("lfence" : : : "memory"); + } + return x; +} + +inline Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +#endif // defined(__x86_64__) + +} // namespace butil::subtle +} // namespace butil + +#undef ATOMICOPS_COMPILER_BARRIER + +#endif // BUTIL_ATOMICOPS_INTERNALS_X86_GCC_H_ diff --git a/src/butil/atomicops_internals_x86_msvc.h b/src/butil/atomicops_internals_x86_msvc.h new file mode 100644 index 0000000..41693f9 --- /dev/null +++ b/src/butil/atomicops_internals_x86_msvc.h @@ -0,0 +1,198 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is an internal atomic implementation, use butil/atomicops.h instead. + +#ifndef BUTIL_ATOMICOPS_INTERNALS_X86_MSVC_H_ +#define BUTIL_ATOMICOPS_INTERNALS_X86_MSVC_H_ + +#include + +#include + +#include "butil/macros.h" + +#if defined(ARCH_CPU_64_BITS) +// windows.h #defines this (only on x64). This causes problems because the +// public API also uses MemoryBarrier at the public name for this fence. So, on +// X64, undef it, and call its documented +// (http://msdn.microsoft.com/en-us/library/windows/desktop/ms684208.aspx) +// implementation directly. +#undef MemoryBarrier +#endif + +namespace butil { +namespace subtle { + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + LONG result = _InterlockedCompareExchange( + reinterpret_cast(ptr), + static_cast(new_value), + static_cast(old_value)); + return static_cast(result); +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + LONG result = _InterlockedExchange( + reinterpret_cast(ptr), + static_cast(new_value)); + return static_cast(result); +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + return _InterlockedExchangeAdd( + reinterpret_cast(ptr), + static_cast(increment)) + increment; +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + return Barrier_AtomicIncrement(ptr, increment); +} + +#if !(defined(_MSC_VER) && _MSC_VER >= 1400) +#error "We require at least vs2005 for MemoryBarrier" +#endif +inline void MemoryBarrier() { +#if defined(ARCH_CPU_64_BITS) + // See #undef and note at the top of this file. + __faststorefence(); +#else + // We use MemoryBarrier from WinNT.h + ::MemoryBarrier(); +#endif +} + +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { + NoBarrier_AtomicExchange(ptr, value); + // acts as a barrier in this implementation +} + +inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; // works w/o barrier for current Intel chips as of June 2005 + // See comments in Atomic64 version of Release_Store() below. +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { + return *ptr; +} + +inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { + Atomic32 value = *ptr; + return value; +} + +inline Atomic32 Release_Load(volatile const Atomic32* ptr) { + MemoryBarrier(); + return *ptr; +} + +#if defined(_WIN64) + +// 64-bit low-level operations on 64-bit platform. + +COMPILE_ASSERT(sizeof(Atomic64) == sizeof(PVOID), atomic_word_is_atomic); + +inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + PVOID result = InterlockedCompareExchangePointer( + reinterpret_cast(ptr), + reinterpret_cast(new_value), reinterpret_cast(old_value)); + return reinterpret_cast(result); +} + +inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, + Atomic64 new_value) { + PVOID result = InterlockedExchangePointer( + reinterpret_cast(ptr), + reinterpret_cast(new_value)); + return reinterpret_cast(result); +} + +inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + return InterlockedExchangeAdd64( + reinterpret_cast(ptr), + static_cast(increment)) + increment; +} + +inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + return Barrier_AtomicIncrement(ptr, increment); +} + +inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic64* ptr, Atomic64 value) { + NoBarrier_AtomicExchange(ptr, value); + // acts as a barrier in this implementation +} + +inline void Release_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; // works w/o barrier for current Intel chips as of June 2005 + + // When new chips come out, check: + // IA-32 Intel Architecture Software Developer's Manual, Volume 3: + // System Programming Guide, Chatper 7: Multiple-processor management, + // Section 7.2, Memory Ordering. + // Last seen at: + // http://developer.intel.com/design/pentium4/manuals/index_new.htm +} + +inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) { + return *ptr; +} + +inline Atomic64 Acquire_Load(volatile const Atomic64* ptr) { + Atomic64 value = *ptr; + return value; +} + +inline Atomic64 Release_Load(volatile const Atomic64* ptr) { + MemoryBarrier(); + return *ptr; +} + +inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + + +#endif // defined(_WIN64) + +} // namespace butil::subtle +} // namespace butil + +#endif // BUTIL_ATOMICOPS_INTERNALS_X86_MSVC_H_ diff --git a/src/butil/auto_reset.h b/src/butil/auto_reset.h new file mode 100644 index 0000000..1decfdf --- /dev/null +++ b/src/butil/auto_reset.h @@ -0,0 +1,41 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_AUTO_RESET_H_ +#define BUTIL_AUTO_RESET_H_ + +#include "butil/basictypes.h" + +// butil::AutoReset<> is useful for setting a variable to a new value only within +// a particular scope. An butil::AutoReset<> object resets a variable to its +// original value upon destruction, making it an alternative to writing +// "var = false;" or "var = old_val;" at all of a block's exit points. +// +// This should be obvious, but note that an butil::AutoReset<> instance should +// have a shorter lifetime than its scoped_variable, to prevent invalid memory +// writes when the butil::AutoReset<> object is destroyed. + +namespace butil { + +template +class AutoReset { + public: + AutoReset(T* scoped_variable, T new_value) + : scoped_variable_(scoped_variable), + original_value_(*scoped_variable) { + *scoped_variable_ = new_value; + } + + ~AutoReset() { *scoped_variable_ = original_value_; } + + private: + T* scoped_variable_; + T original_value_; + + DISALLOW_COPY_AND_ASSIGN(AutoReset); +}; + +} + +#endif // BUTIL_AUTO_RESET_H_ diff --git a/src/butil/base64.cc b/src/butil/base64.cc new file mode 100644 index 0000000..e1966eb --- /dev/null +++ b/src/butil/base64.cc @@ -0,0 +1,37 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/base64.h" + +#include "third_party/modp_b64/modp_b64.h" + +namespace butil { + +void Base64Encode(const StringPiece& input, std::string* output) { + std::string temp; + temp.resize(modp_b64_encode_len(input.size())); // makes room for null byte + + // modp_b64_encode_len() returns at least 1, so temp[0] is safe to use. + size_t output_size = modp_b64_encode(&(temp[0]), input.data(), input.size()); + + temp.resize(output_size); // strips off null byte + output->swap(temp); +} + +bool Base64Decode(const StringPiece& input, std::string* output) { + std::string temp; + temp.resize(modp_b64_decode_len(input.size())); + + // does not null terminate result since result is binary data! + size_t input_size = input.size(); + size_t output_size = modp_b64_decode(&(temp[0]), input.data(), input_size); + if (output_size == MODP_B64_ERROR) + return false; + + temp.resize(output_size); + output->swap(temp); + return true; +} + +} // namespace butil diff --git a/src/butil/base64.h b/src/butil/base64.h new file mode 100644 index 0000000..6cbc5ab --- /dev/null +++ b/src/butil/base64.h @@ -0,0 +1,24 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_BASE64_H__ +#define BUTIL_BASE64_H__ + +#include + +#include "butil/base_export.h" +#include "butil/strings/string_piece.h" + +namespace butil { + +// Encodes the input string in base64. +BUTIL_EXPORT void Base64Encode(const StringPiece& input, std::string* output); + +// Decodes the base64 input string. Returns true if successful and false +// otherwise. The output string is only modified if successful. +BUTIL_EXPORT bool Base64Decode(const StringPiece& input, std::string* output); + +} // namespace butil + +#endif // BUTIL_BASE64_H__ diff --git a/src/butil/base64url.cc b/src/butil/base64url.cc new file mode 100644 index 0000000..f782f33 --- /dev/null +++ b/src/butil/base64url.cc @@ -0,0 +1,96 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/base64.h" +#include "butil/base64url.h" + +#include "third_party/modp_b64/modp_b64_data.h" + +namespace butil { + +// Base64url maps {+, /} to {-, _} in order for the encoded content to be safe +// to use in a URL. These characters will be translated by this implementation. +#define BASE64_CHARS "+/" +#define BASE64_URL_SAFE_CHARS "-_" +#define URL_SAFE_CHAR62 '-' +#define URL_SAFE_CHAR63 '_' + +void Base64UrlEncode(const StringPiece& input, + Base64UrlEncodePolicy policy, + std::string* output) { + Base64Encode(input, output); + + std::replace(output->begin(), output->end(), CHAR62, URL_SAFE_CHAR62); + std::replace(output->begin(), output->end(), CHAR63, URL_SAFE_CHAR63); + + switch (policy) { + case Base64UrlEncodePolicy::INCLUDE_PADDING: + // The padding included in |*output| will not be amended. + break; + case Base64UrlEncodePolicy::OMIT_PADDING: + // The padding included in |*output| will be removed. + const size_t last_non_padding_pos = + output->find_last_not_of(CHARPAD); + if (last_non_padding_pos != std::string::npos) { + output->resize(last_non_padding_pos + 1); + } + break; + } +} + +bool Base64UrlDecode(const StringPiece& input, + Base64UrlDecodePolicy policy, + std::string* output) { + // Characters outside of the base64url alphabet are disallowed, which includes + // the {+, /} characters found in the conventional base64 alphabet. + if (input.find_first_of(BASE64_CHARS) != std::string::npos) + return false; + + const size_t required_padding_characters = input.size() % 4; + const bool needs_replacement = + input.find_first_of(BASE64_URL_SAFE_CHARS) != std::string::npos; + + switch (policy) { + case Base64UrlDecodePolicy::REQUIRE_PADDING: + // Fail if the required padding is not included in |input|. + if (required_padding_characters > 0) + return false; + break; + case Base64UrlDecodePolicy::IGNORE_PADDING: + // Missing padding will be silently appended. + break; + case Base64UrlDecodePolicy::DISALLOW_PADDING: + // Fail if padding characters are included in |input|. + if (input.find_first_of(CHARPAD) != std::string::npos) + return false; + break; + } + + // If the string either needs replacement of URL-safe characters to normal + // base64 ones, or additional padding, a copy of |input| needs to be made in + // order to make these adjustments without side effects. + if (required_padding_characters > 0 || needs_replacement) { + std::string base64_input; + + size_t base64_input_size = input.size(); + if (required_padding_characters > 0) + base64_input_size += 4 - required_padding_characters; + + base64_input.reserve(base64_input_size); + input.AppendToString(&base64_input); + + // Substitute the base64url URL-safe characters to their base64 equivalents. + std::replace(base64_input.begin(), base64_input.end(), URL_SAFE_CHAR62, CHAR62); + std::replace(base64_input.begin(), base64_input.end(), URL_SAFE_CHAR63, CHAR63); + + // Append the necessary padding characters. + base64_input.resize(base64_input_size, '='); + + return Base64Decode(base64_input, output); + } + + return Base64Decode(input, output); +} + +} // namespace butil diff --git a/src/butil/base64url.h b/src/butil/base64url.h new file mode 100644 index 0000000..438f6b7 --- /dev/null +++ b/src/butil/base64url.h @@ -0,0 +1,54 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_BASE64URL_H_ +#define BASE_BASE64URL_H_ + +#include + +#include "butil/base_export.h" +#include "butil/strings/string_piece.h" + +namespace butil { + +enum class Base64UrlEncodePolicy { + // Include the trailing padding in the output, when necessary. + INCLUDE_PADDING, + + // Remove the trailing padding from the output. + OMIT_PADDING +}; + +// Encodes the |input| string in base64url, defined in RFC 4648: +// https://tools.ietf.org/html/rfc4648#section-5 +// +// The |policy| defines whether padding should be included or omitted from the +// encoded |*output|. |input| and |*output| may reference the same storage. +BUTIL_EXPORT void Base64UrlEncode(const StringPiece& input, + Base64UrlEncodePolicy policy, + std::string* output); + +enum class Base64UrlDecodePolicy { + // Require inputs to contain trailing padding if non-aligned. + REQUIRE_PADDING, + + // Accept inputs regardless of whether they have the correct padding. + IGNORE_PADDING, + + // Reject inputs if they contain any trailing padding. + DISALLOW_PADDING +}; + +// Decodes the |input| string in base64url, defined in RFC 4648: +// https://tools.ietf.org/html/rfc4648#section-5 +// +// The |policy| defines whether padding will be required, ignored or disallowed +// altogether. |input| and |*output| may reference the same storage. +BUTIL_EXPORT bool Base64UrlDecode(const StringPiece& input, + Base64UrlDecodePolicy policy, + std::string* output) WARN_UNUSED_RESULT; + +} // namespace butil + +#endif // BASE_BASE64URL_H_ diff --git a/src/butil/base_export.h b/src/butil/base_export.h new file mode 100644 index 0000000..8105484 --- /dev/null +++ b/src/butil/base_export.h @@ -0,0 +1,34 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_BASE_EXPORT_H_ +#define BUTIL_BASE_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(BUTIL_IMPLEMENTATION) +#define BUTIL_EXPORT __declspec(dllexport) +#define BUTIL_EXPORT_PRIVATE __declspec(dllexport) +#else +#define BUTIL_EXPORT __declspec(dllimport) +#define BUTIL_EXPORT_PRIVATE __declspec(dllimport) +#endif // defined(BUTIL_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(BUTIL_IMPLEMENTATION) +#define BUTIL_EXPORT __attribute__((visibility("default"))) +#define BUTIL_EXPORT_PRIVATE __attribute__((visibility("default"))) +#else +#define BUTIL_EXPORT +#define BUTIL_EXPORT_PRIVATE +#endif // defined(BUTIL_IMPLEMENTATION) +#endif + +#else // defined(COMPONENT_BUILD) +#define BUTIL_EXPORT +#define BUTIL_EXPORT_PRIVATE +#endif + +#endif // BUTIL_BASE_EXPORT_H_ diff --git a/src/butil/basictypes.h b/src/butil/basictypes.h new file mode 100644 index 0000000..461f169 --- /dev/null +++ b/src/butil/basictypes.h @@ -0,0 +1,35 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains definitions of our old basic integral types +// ((u)int{8,16,32,64}) and further includes. I recommend that you use the C99 +// standard types instead, and include //etc. as needed. +// Note that the macros and macro-like constructs that were formerly defined in +// this file are now available separately in butil/macros.h. + +#ifndef BUTIL_BASICTYPES_H_ +#define BUTIL_BASICTYPES_H_ + +#include // So we can set the bounds of our types. +#include // For size_t. +#include // For intptr_t. + +#include "butil/macros.h" +#include "butil/port.h" // Types that only need exist on certain systems. + +// DEPRECATED: Please use std::numeric_limits (from ) instead. +const uint8_t kuint8max = (( uint8_t) 0xFF); +const uint16_t kuint16max = ((uint16_t) 0xFFFF); +const uint32_t kuint32max = ((uint32_t) 0xFFFFFFFF); +const uint64_t kuint64max = ((uint64_t) 0xFFFFFFFFFFFFFFFFULL); +const int8_t kint8min = (( int8_t) 0x80); +const int8_t kint8max = (( int8_t) 0x7F); +const int16_t kint16min = (( int16_t) 0x8000); +const int16_t kint16max = (( int16_t) 0x7FFF); +const int32_t kint32min = (( int32_t) 0x80000000); +const int32_t kint32max = (( int32_t) 0x7FFFFFFF); +const int64_t kint64min = (( int64_t) 0x8000000000000000LL); +const int64_t kint64max = (( int64_t) 0x7FFFFFFFFFFFFFFFLL); + +#endif // BUTIL_BASICTYPES_H_ diff --git a/src/butil/big_endian.cc b/src/butil/big_endian.cc new file mode 100644 index 0000000..eef4cb5 --- /dev/null +++ b/src/butil/big_endian.cc @@ -0,0 +1,97 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/big_endian.h" + +#include "butil/strings/string_piece.h" + +namespace butil { + +BigEndianReader::BigEndianReader(const char* buf, size_t len) + : ptr_(buf), end_(ptr_ + len) {} + +bool BigEndianReader::Skip(size_t len) { + if (ptr_ + len > end_) + return false; + ptr_ += len; + return true; +} + +bool BigEndianReader::ReadBytes(void* out, size_t len) { + if (ptr_ + len > end_) + return false; + memcpy(out, ptr_, len); + ptr_ += len; + return true; +} + +bool BigEndianReader::ReadPiece(butil::StringPiece* out, size_t len) { + if (ptr_ + len > end_) + return false; + *out = butil::StringPiece(ptr_, len); + ptr_ += len; + return true; +} + +template +bool BigEndianReader::Read(T* value) { + if (ptr_ + sizeof(T) > end_) + return false; + ReadBigEndian(ptr_, value); + ptr_ += sizeof(T); + return true; +} + +bool BigEndianReader::ReadU8(uint8_t* value) { + return Read(value); +} + +bool BigEndianReader::ReadU16(uint16_t* value) { + return Read(value); +} + +bool BigEndianReader::ReadU32(uint32_t* value) { + return Read(value); +} + +BigEndianWriter::BigEndianWriter(char* buf, size_t len) + : ptr_(buf), end_(ptr_ + len) {} + +bool BigEndianWriter::Skip(size_t len) { + if (ptr_ + len > end_) + return false; + ptr_ += len; + return true; +} + +bool BigEndianWriter::WriteBytes(const void* buf, size_t len) { + if (ptr_ + len > end_) + return false; + memcpy(ptr_, buf, len); + ptr_ += len; + return true; +} + +template +bool BigEndianWriter::Write(T value) { + if (ptr_ + sizeof(T) > end_) + return false; + WriteBigEndian(ptr_, value); + ptr_ += sizeof(T); + return true; +} + +bool BigEndianWriter::WriteU8(uint8_t value) { + return Write(value); +} + +bool BigEndianWriter::WriteU16(uint16_t value) { + return Write(value); +} + +bool BigEndianWriter::WriteU32(uint32_t value) { + return Write(value); +} + +} // namespace butil diff --git a/src/butil/big_endian.h b/src/butil/big_endian.h new file mode 100644 index 0000000..03414f0 --- /dev/null +++ b/src/butil/big_endian.h @@ -0,0 +1,102 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_BIG_ENDIAN_H_ +#define BUTIL_BIG_ENDIAN_H_ + +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/strings/string_piece.h" + +namespace butil { + +// Read an integer (signed or unsigned) from |buf| in Big Endian order. +// Note: this loop is unrolled with -O1 and above. +// NOTE(szym): glibc dns-canon.c and SpdyFrameBuilder use +// ntohs(*(uint16_t*)ptr) which is potentially unaligned. +// This would cause SIGBUS on ARMv5 or earlier and ARMv6-M. +template +inline void ReadBigEndian(const char buf[], T* out) { + *out = buf[0]; + for (size_t i = 1; i < sizeof(T); ++i) { + *out <<= 8; + // Must cast to uint8_t to avoid clobbering by sign extension. + *out |= static_cast(buf[i]); + } +} + +// Write an integer (signed or unsigned) |val| to |buf| in Big Endian order. +// Note: this loop is unrolled with -O1 and above. +template +inline void WriteBigEndian(char buf[], T val) { + for (size_t i = 0; i < sizeof(T); ++i) { + buf[sizeof(T)-i-1] = static_cast(val & 0xFF); + val >>= 8; + } +} + +// Specializations to make clang happy about the (dead code) shifts above. +template<> +inline void ReadBigEndian(const char buf[], uint8_t* out) { + *out = buf[0]; +} + +template<> +inline void WriteBigEndian(char buf[], uint8_t val) { + buf[0] = static_cast(val); +} + +// Allows reading integers in network order (big endian) while iterating over +// an underlying buffer. All the reading functions advance the internal pointer. +class BUTIL_EXPORT BigEndianReader { + public: + BigEndianReader(const char* buf, size_t len); + + const char* ptr() const { return ptr_; } + int remaining() const { return end_ - ptr_; } + + bool Skip(size_t len); + bool ReadBytes(void* out, size_t len); + // Creates a StringPiece in |out| that points to the underlying buffer. + bool ReadPiece(butil::StringPiece* out, size_t len); + bool ReadU8(uint8_t* value); + bool ReadU16(uint16_t* value); + bool ReadU32(uint32_t* value); + + private: + // Hidden to promote type safety. + template + bool Read(T* v); + + const char* ptr_; + const char* end_; +}; + +// Allows writing integers in network order (big endian) while iterating over +// an underlying buffer. All the writing functions advance the internal pointer. +class BUTIL_EXPORT BigEndianWriter { + public: + BigEndianWriter(char* buf, size_t len); + + char* ptr() const { return ptr_; } + int remaining() const { return end_ - ptr_; } + + bool Skip(size_t len); + bool WriteBytes(const void* buf, size_t len); + bool WriteU8(uint8_t value); + bool WriteU16(uint16_t value); + bool WriteU32(uint32_t value); + + private: + // Hidden to promote type safety. + template + bool Write(T v); + + char* ptr_; + char* end_; +}; + +} // namespace butil + +#endif // BUTIL_BIG_ENDIAN_H_ diff --git a/src/butil/binary_printer.cpp b/src/butil/binary_printer.cpp new file mode 100644 index 0000000..182589a --- /dev/null +++ b/src/butil/binary_printer.cpp @@ -0,0 +1,165 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Thu Nov 22 13:57:56 CST 2012 + +#include +#include "butil/iobuf.h" +#include "butil/binary_printer.h" + +namespace butil { + +static char s_binary_char_map[] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F' +}; + +template +class BinaryCharPrinter { +public: + static const size_t BUF_SIZE = 127; + explicit BinaryCharPrinter(Appender* a) : _n(0), _appender(a) {} + ~BinaryCharPrinter() { Flush(); } + void PushChar(unsigned char c); + void Flush(); +private: + uint32_t _n; + Appender* _appender; + char _buf[BUF_SIZE]; +}; + +template +void BinaryCharPrinter::Flush() { + if (_n > 0) { + _appender->Append(_buf, _n); + _n = 0; + } +} + +template +void BinaryCharPrinter::PushChar(unsigned char c) { + if (_n > BUF_SIZE - 3) { + _appender->Append(_buf, _n); + _n = 0; + } + if (c >= 32 && c <= 126) { // displayable ascii characters + if (c != '\\') { + _buf[_n++] = c; + } else { + _buf[_n++] = '\\'; + _buf[_n++] = '\\'; + } + } else { + _buf[_n++] = '\\'; + switch (c) { + case '\b': _buf[_n++] = 'b'; break; + case '\t': _buf[_n++] = 't'; break; + case '\n': _buf[_n++] = 'n'; break; + case '\r': _buf[_n++] = 'r'; break; + default: + _buf[_n++] = s_binary_char_map[c >> 4]; + _buf[_n++] = s_binary_char_map[c & 0xF]; + break; + } + } +} + +class OStreamAppender { +public: + OStreamAppender(std::ostream& os) : _os(&os) {} + void Append(const char* b, size_t n) { _os->write(b, n); } +private: + std::ostream* _os; +}; + +class StringAppender { +public: + StringAppender(std::string* str) : _str(str) {} + void Append(const char* b, size_t n) { _str->append(b, n); } +private: + std::string* _str; +}; + +template +static void PrintIOBuf(Appender* appender, const IOBuf& b, size_t max_length) { + BinaryCharPrinter printer(appender); + const size_t n = b.backing_block_num(); + size_t nw = 0; + for (size_t i = 0; i < n; ++i) { + StringPiece blk = b.backing_block(i); + for (size_t j = 0; j < blk.size(); ++j) { + if (nw >= max_length) { + printer.Flush(); + char buf[48]; + int len = snprintf(buf, sizeof(buf), "...", + (uint64_t)(b.size() - nw)); + appender->Append(buf, len); + return; + } + ++nw; + printer.PushChar(blk[j]); + } + } +} + +template +static void PrintString(Appender* appender, const StringPiece& s, size_t max_length) { + BinaryCharPrinter printer(appender); + for (size_t i = 0; i < s.size(); ++i) { + if (i >= max_length) { + printer.Flush(); + char buf[48]; + int len = snprintf(buf, sizeof(buf), "...", + (uint64_t)(s.size() - i)); + appender->Append(buf, len); + return; + } + printer.PushChar(s[i]); + } +} + +void ToPrintable::Print(std::ostream& os) const { + OStreamAppender appender(os); + if (_iobuf) { + PrintIOBuf(&appender, *_iobuf, _max_length); + } else if (!_str.empty()) { + PrintString(&appender, _str, _max_length); + } +} + +std::string ToPrintableString(const IOBuf& data, size_t max_length) { + std::string result; + StringAppender appender(&result); + PrintIOBuf(&appender, data, max_length); + return result; +} + +std::string ToPrintableString(const StringPiece& data, size_t max_length) { + std::string result; + StringAppender appender(&result); + PrintString(&appender, data, max_length); + return result; +} + +std::string ToPrintableString(const void* data, size_t n, size_t max_length) { + std::string result; + StringAppender appender(&result); + PrintString(&appender, StringPiece((const char*)data, n), max_length); + return result; +} + +} // namespace butil diff --git a/src/butil/binary_printer.h b/src/butil/binary_printer.h new file mode 100644 index 0000000..068ef24 --- /dev/null +++ b/src/butil/binary_printer.h @@ -0,0 +1,70 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Thu Nov 22 13:57:56 CST 2012 + +#ifndef BUTIL_BINARY_PRINTER_H +#define BUTIL_BINARY_PRINTER_H + +#include "butil/strings/string_piece.h" + +namespace butil { +class IOBuf; + +// Print binary content within max length. +// The printing format is optimized for humans and may change in future. + +class ToPrintable { +public: + static const size_t DEFAULT_MAX_LENGTH = 64; + + ToPrintable(const IOBuf& b, size_t max_length = DEFAULT_MAX_LENGTH) + : _iobuf(&b), _max_length(max_length) {} + + ToPrintable(const StringPiece& str, size_t max_length = DEFAULT_MAX_LENGTH) + : _iobuf(NULL), _str(str), _max_length(max_length) {} + + ToPrintable(const void* data, size_t n, size_t max_length = DEFAULT_MAX_LENGTH) + : _iobuf(NULL), _str((const char*)data, n), _max_length(max_length) {} + + void Print(std::ostream& os) const; + +private: + const IOBuf* _iobuf; + StringPiece _str; + size_t _max_length; +}; + +// Keep old name for compatibility. +typedef ToPrintable PrintedAsBinary; + +inline std::ostream& operator<<(std::ostream& os, const ToPrintable& p) { + p.Print(os); + return os; +} + +// Convert binary data to a printable string. +std::string ToPrintableString(const IOBuf& data, + size_t max_length = ToPrintable::DEFAULT_MAX_LENGTH); +std::string ToPrintableString(const StringPiece& data, + size_t max_length = ToPrintable::DEFAULT_MAX_LENGTH); +std::string ToPrintableString(const void* data, size_t n, + size_t max_length = ToPrintable::DEFAULT_MAX_LENGTH); + +} // namespace butil + +#endif // BUTIL_BINARY_PRINTER_H diff --git a/src/butil/bit_array.h b/src/butil/bit_array.h new file mode 100644 index 0000000..3bcc694 --- /dev/null +++ b/src/butil/bit_array.h @@ -0,0 +1,103 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Tue Feb 25 23:43:39 CST 2014 + +// Provide functions to get/set bits of an integral array. These functions +// are not threadsafe because operations on different bits may modify a same +// integer. + +#ifndef BUTIL_BIT_ARRAY_H +#define BUTIL_BIT_ARRAY_H + +#include + +namespace butil { + +#define BIT_ARRAY_LEN(nbit) (((nbit) + 63 ) / 64 * 8) + +// Create an array with at least |nbit| bits. The array is not cleared. +inline uint64_t* bit_array_malloc(size_t nbit) { + if (!nbit) { + return NULL; + } + return (uint64_t*)malloc(BIT_ARRAY_LEN(nbit)/*different from /8*/); +} + +inline void bit_array_free(uint64_t* array) { + free(array); +} + +// Set bit 0 ~ nbit-1 of |array| to be 0 +inline void bit_array_clear(uint64_t* array, size_t nbit) { + const size_t off = (nbit >> 6); + memset(array, 0, off * 8); + const size_t last = (off << 6); + if (last != nbit) { + array[off] &= ~((((uint64_t)1) << (nbit - last)) - 1); + } +} + +// Set i-th bit (from left, counting from 0) of |array| to be 1 +inline void bit_array_set(uint64_t* array, size_t i) { + const size_t off = (i >> 6); + array[off] |= (((uint64_t)1) << (i - (off << 6))); +} + +// Set i-th bit (from left, counting from 0) of |array| to be 0 +inline void bit_array_unset(uint64_t* array, size_t i) { + const size_t off = (i >> 6); + array[off] &= ~(((uint64_t)1) << (i - (off << 6))); +} + +// Get i-th bit (from left, counting from 0) of |array| +inline uint64_t bit_array_get(const uint64_t* array, size_t i) { + const size_t off = (i >> 6); + return (array[off] & (((uint64_t)1) << (i - (off << 6)))); +} + +// Find index of first 1-bit from bit |begin| to |end| in |array|. +// Returns |end| if all bits are 0. +// This function is of O(nbit) complexity. +inline size_t bit_array_first1(const uint64_t* array, size_t begin, size_t end) { + size_t off1 = (begin >> 6); + const size_t first = (off1 << 6); + if (first != begin) { + const uint64_t v = + array[off1] & ~((((uint64_t)1) << (begin - first)) - 1); + if (v) { + return std::min(first + __builtin_ctzl(v), end); + } + ++off1; + } + + const size_t off2 = (end >> 6); + for (size_t i = off1; i < off2; ++i) { + if (array[i]) { + return i * 64 + __builtin_ctzl(array[i]); + } + } + const size_t last = (off2 << 6); + if (last != end && array[off2]) { + return std::min(last + __builtin_ctzl(array[off2]), end); + } + return end; +} + +} // end namespace butil + +#endif // BUTIL_BIT_ARRAY_H diff --git a/src/butil/bits.h b/src/butil/bits.h new file mode 100644 index 0000000..530e479 --- /dev/null +++ b/src/butil/bits.h @@ -0,0 +1,47 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file defines some bit utilities. + +#ifndef BUTIL_BITS_H_ +#define BUTIL_BITS_H_ + +#include "butil/basictypes.h" +#include "butil/logging.h" + +namespace butil { +namespace bits { + +// Returns the integer i such as 2^i <= n < 2^(i+1) +inline int Log2Floor(uint32_t n) { + if (n == 0) + return -1; + int log = 0; + uint32_t value = n; + for (int i = 4; i >= 0; --i) { + int shift = (1 << i); + uint32_t x = value >> shift; + if (x != 0) { + value = x; + log += shift; + } + } + DCHECK_EQ(value, 1u); + return log; +} + +// Returns the integer i such as 2^(i-1) < n <= 2^i +inline int Log2Ceiling(uint32_t n) { + if (n == 0) { + return -1; + } else { + // Log2Floor returns -1 for 0, so the following works correctly for n=1. + return 1 + Log2Floor(n - 1); + } +} + +} // namespace bits +} // namespace butil + +#endif // BUTIL_BITS_H_ diff --git a/src/butil/build_config.h b/src/butil/build_config.h new file mode 100644 index 0000000..18d449b --- /dev/null +++ b/src/butil/build_config.h @@ -0,0 +1,193 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file adds defines about the platform we're currently building on. +// Operating System: +// OS_WIN / OS_MACOSX / OS_LINUX / OS_POSIX (MACOSX or LINUX) / +// OS_NACL (NACL_SFI or NACL_NONSFI) / OS_NACL_SFI / OS_NACL_NONSFI +// Compiler: +// COMPILER_MSVC / COMPILER_GCC +// Processor: +// ARCH_CPU_X86 / ARCH_CPU_X86_64 / ARCH_CPU_X86_FAMILY (X86 or X86_64) +// ARCH_CPU_32_BITS / ARCH_CPU_64_BITS + +#ifndef BUTIL_BUILD_CONFIG_H_ +#define BUTIL_BUILD_CONFIG_H_ + +// A set of macros to use for platform detection. +#if defined(__native_client__) +// __native_client__ must be first, so that other OS_ defines are not set. +#define OS_NACL 1 +// OS_NACL comes in two sandboxing technology flavors, SFI or Non-SFI. +// PNaCl toolchain defines __native_client_nonsfi__ macro in Non-SFI build +// mode, while it does not in SFI build mode. +#if defined(__native_client_nonsfi__) +#define OS_NACL_NONSFI +#else +#define OS_NACL_SFI +#endif +#elif defined(ANDROID) +#define OS_ANDROID 1 +#elif defined(__APPLE__) +// only include TargetConditions after testing ANDROID as some android builds +// on mac don't have this header available and it's not needed unless the target +// is really mac/ios. +#include +#define OS_MACOSX 1 +#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE +#define OS_IOS 1 +#endif // defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE +#elif defined(__linux__) +#define OS_LINUX 1 +// include a system header to pull in features.h for glibc/uclibc macros. +#include +#if defined(__GLIBC__) && !defined(__UCLIBC__) +// we really are using glibc, not uClibc pretending to be glibc +#define LIBC_GLIBC 1 +#endif +#elif defined(_WIN32) +#define OS_WIN 1 +#define TOOLKIT_VIEWS 1 +#elif defined(__FreeBSD__) +#define OS_FREEBSD 1 +#elif defined(__OpenBSD__) +#define OS_OPENBSD 1 +#elif defined(__sun) +#define OS_SOLARIS 1 +#elif defined(__QNXNTO__) +#define OS_QNX 1 +#else +#error Please add support for your platform in butil/build_config.h +#endif + +#if defined(USE_OPENSSL_CERTS) && defined(USE_NSS_CERTS) +#error Cannot use both OpenSSL and NSS for certificates +#endif + +// For access to standard BSD features, use OS_BSD instead of a +// more specific macro. +#if defined(OS_FREEBSD) || defined(OS_OPENBSD) +#define OS_BSD 1 +#endif + +// For access to standard POSIXish features, use OS_POSIX instead of a +// more specific macro. +#if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_FREEBSD) || \ + defined(OS_OPENBSD) || defined(OS_SOLARIS) || defined(OS_ANDROID) || \ + defined(OS_NACL) || defined(OS_QNX) +#define OS_POSIX 1 +#endif + +// Use tcmalloc +#if (defined(OS_WIN) || defined(OS_LINUX) || defined(OS_ANDROID)) && \ + !defined(NO_TCMALLOC) +#define USE_TCMALLOC 1 +#endif + +// Compiler detection. +#if defined(__GNUC__) +#define COMPILER_GCC 1 +#elif defined(_MSC_VER) +#define COMPILER_MSVC 1 +#else +#error Please add support for your compiler in butil/build_config.h +#endif + +// Processor architecture detection. For more info on what's defined, see: +// http://msdn.microsoft.com/en-us/library/b0084kay.aspx +// http://www.agner.org/optimize/calling_conventions.pdf +// or with gcc, run: "echo | gcc -E -dM -" +#if defined(_M_X64) || defined(__x86_64__) +#define ARCH_CPU_X86_FAMILY 1 +#define ARCH_CPU_X86_64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(_M_IX86) || defined(__i386__) +#define ARCH_CPU_X86_FAMILY 1 +#define ARCH_CPU_X86 1 +#define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__ARMEL__) +#define ARCH_CPU_ARM_FAMILY 1 +#define ARCH_CPU_ARMEL 1 +#define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__aarch64__) +#define ARCH_CPU_ARM_FAMILY 1 +#define ARCH_CPU_ARM64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__pnacl__) +#define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__MIPSEL__) +#if defined(__LP64__) +#define ARCH_CPU_MIPS64_FAMILY 1 +#define ARCH_CPU_MIPS64EL 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#else +#define ARCH_CPU_MIPS_FAMILY 1 +#define ARCH_CPU_MIPSEL 1 +#define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#endif +#elif defined(__loongarch64) +#define ARCH_CPU_LOONGARCH64_FAMILY 1 +#define ARCH_CPU_LOONGARCH64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__riscv) +#define ARCH_CPU_RISCV_FAMILY 1 +#if defined(__riscv_xlen) && (__riscv_xlen == 64) +#define ARCH_CPU_RISCV64 1 +#define ARCH_CPU_64_BITS 1 +#else +#define ARCH_CPU_RISCV32 1 +#define ARCH_CPU_32_BITS 1 +#endif +#define ARCH_CPU_LITTLE_ENDIAN 1 +#else +#error Please add support for your architecture in butil/build_config.h +#endif + +// Type detection for wchar_t. +#if defined(OS_WIN) +#define WCHAR_T_IS_UTF16 +#elif defined(OS_POSIX) && defined(COMPILER_GCC) && \ + defined(__WCHAR_MAX__) && \ + (__WCHAR_MAX__ == 0x7fffffff || __WCHAR_MAX__ == 0xffffffff) +#define WCHAR_T_IS_UTF32 +#elif defined(OS_POSIX) && defined(COMPILER_GCC) && \ + defined(__WCHAR_MAX__) && \ + (__WCHAR_MAX__ == 0x7fff || __WCHAR_MAX__ == 0xffff) +// On Posix, we'll detect short wchar_t, but projects aren't guaranteed to +// compile in this mode (in particular, Chrome doesn't). This is intended for +// other projects using base who manage their own dependencies and make sure +// short wchar works for them. +#define WCHAR_T_IS_UTF16 +#else +#error Please add support for your compiler in butil/build_config.h +#endif + +#if defined(OS_ANDROID) +// The compiler thinks std::string::const_iterator and "const char*" are +// equivalent types. +#define STD_STRING_ITERATOR_IS_CHAR_POINTER +// The compiler thinks butil::string16::const_iterator and "char16*" are +// equivalent types. +#define BUTIL_STRING16_ITERATOR_IS_CHAR16_POINTER +#endif + +#if defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L +#define BUTIL_CXX11_ENABLED 1 +#endif + +#if !defined(BUTIL_CXX11_ENABLED) +#define nullptr NULL +#endif + +#define HAVE_DLADDR + +#endif // BUTIL_BUILD_CONFIG_H_ diff --git a/src/butil/cancelable_callback.h b/src/butil/cancelable_callback.h new file mode 100644 index 0000000..f2d0465 --- /dev/null +++ b/src/butil/cancelable_callback.h @@ -0,0 +1,272 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// CancelableCallback is a wrapper around butil::Callback that allows +// cancellation of a callback. CancelableCallback takes a reference on the +// wrapped callback until this object is destroyed or Reset()/Cancel() are +// called. +// +// NOTE: +// +// Calling CancelableCallback::Cancel() brings the object back to its natural, +// default-constructed state, i.e., CancelableCallback::callback() will return +// a null callback. +// +// THREAD-SAFETY: +// +// CancelableCallback objects must be created on, posted to, cancelled on, and +// destroyed on the same thread. +// +// +// EXAMPLE USAGE: +// +// In the following example, the test is verifying that RunIntensiveTest() +// Quit()s the message loop within 4 seconds. The cancelable callback is posted +// to the message loop, the intensive test runs, the message loop is run, +// then the callback is cancelled. +// +// void TimeoutCallback(const std::string& timeout_message) { +// FAIL() << timeout_message; +// MessageLoop::current()->QuitWhenIdle(); +// } +// +// CancelableClosure timeout(butil::Bind(&TimeoutCallback, "Test timed out.")); +// MessageLoop::current()->PostDelayedTask(FROM_HERE, timeout.callback(), +// 4000) // 4 seconds to run. +// RunIntensiveTest(); +// MessageLoop::current()->Run(); +// timeout.Cancel(); // Hopefully this is hit before the timeout callback runs. +// + +#ifndef BUTIL_CANCELABLE_CALLBACK_H_ +#define BUTIL_CANCELABLE_CALLBACK_H_ + +#include "butil/base_export.h" +#include "butil/bind.h" +#include "butil/callback.h" +#include "butil/callback_internal.h" +#include "butil/compiler_specific.h" +#include "butil/logging.h" +#include "butil/memory/weak_ptr.h" + +namespace butil { + +template +class CancelableCallback; + +template <> +class CancelableCallback { + public: + CancelableCallback() : weak_factory_(this) {} + + // |callback| must not be null. + explicit CancelableCallback(const butil::Callback& callback) + : weak_factory_(this), + callback_(callback) { + DCHECK(!callback.is_null()); + InitializeForwarder(); + } + + ~CancelableCallback() {} + + // Cancels and drops the reference to the wrapped callback. + void Cancel() { + weak_factory_.InvalidateWeakPtrs(); + forwarder_.Reset(); + callback_.Reset(); + } + + // Returns true if the wrapped callback has been cancelled. + bool IsCancelled() const { + return callback_.is_null(); + } + + // Sets |callback| as the closure that may be cancelled. |callback| may not + // be null. Outstanding and any previously wrapped callbacks are cancelled. + void Reset(const butil::Callback& callback) { + DCHECK(!callback.is_null()); + + // Outstanding tasks (e.g., posted to a message loop) must not be called. + Cancel(); + + // |forwarder_| is no longer valid after Cancel(), so re-bind. + InitializeForwarder(); + + callback_ = callback; + } + + // Returns a callback that can be disabled by calling Cancel(). + const butil::Callback& callback() const { + return forwarder_; + } + + private: + void Forward() { + callback_.Run(); + } + + // Helper method to bind |forwarder_| using a weak pointer from + // |weak_factory_|. + void InitializeForwarder() { + forwarder_ = butil::Bind(&CancelableCallback::Forward, + weak_factory_.GetWeakPtr()); + } + + // Used to ensure Forward() is not run when this object is destroyed. + butil::WeakPtrFactory > weak_factory_; + + // The wrapper closure. + butil::Callback forwarder_; + + // The stored closure that may be cancelled. + butil::Callback callback_; + + DISALLOW_COPY_AND_ASSIGN(CancelableCallback); +}; + +template +class CancelableCallback { + public: + CancelableCallback() : weak_factory_(this) {} + + // |callback| must not be null. + explicit CancelableCallback(const butil::Callback& callback) + : weak_factory_(this), + callback_(callback) { + DCHECK(!callback.is_null()); + InitializeForwarder(); + } + + ~CancelableCallback() {} + + // Cancels and drops the reference to the wrapped callback. + void Cancel() { + weak_factory_.InvalidateWeakPtrs(); + forwarder_.Reset(); + callback_.Reset(); + } + + // Returns true if the wrapped callback has been cancelled. + bool IsCancelled() const { + return callback_.is_null(); + } + + // Sets |callback| as the closure that may be cancelled. |callback| may not + // be null. Outstanding and any previously wrapped callbacks are cancelled. + void Reset(const butil::Callback& callback) { + DCHECK(!callback.is_null()); + + // Outstanding tasks (e.g., posted to a message loop) must not be called. + Cancel(); + + // |forwarder_| is no longer valid after Cancel(), so re-bind. + InitializeForwarder(); + + callback_ = callback; + } + + // Returns a callback that can be disabled by calling Cancel(). + const butil::Callback& callback() const { + return forwarder_; + } + + private: + void Forward(A1 a1) const { + callback_.Run(a1); + } + + // Helper method to bind |forwarder_| using a weak pointer from + // |weak_factory_|. + void InitializeForwarder() { + forwarder_ = butil::Bind(&CancelableCallback::Forward, + weak_factory_.GetWeakPtr()); + } + + // Used to ensure Forward() is not run when this object is destroyed. + butil::WeakPtrFactory > weak_factory_; + + // The wrapper closure. + butil::Callback forwarder_; + + // The stored closure that may be cancelled. + butil::Callback callback_; + + DISALLOW_COPY_AND_ASSIGN(CancelableCallback); +}; + +template +class CancelableCallback { + public: + CancelableCallback() : weak_factory_(this) {} + + // |callback| must not be null. + explicit CancelableCallback(const butil::Callback& callback) + : weak_factory_(this), + callback_(callback) { + DCHECK(!callback.is_null()); + InitializeForwarder(); + } + + ~CancelableCallback() {} + + // Cancels and drops the reference to the wrapped callback. + void Cancel() { + weak_factory_.InvalidateWeakPtrs(); + forwarder_.Reset(); + callback_.Reset(); + } + + // Returns true if the wrapped callback has been cancelled. + bool IsCancelled() const { + return callback_.is_null(); + } + + // Sets |callback| as the closure that may be cancelled. |callback| may not + // be null. Outstanding and any previously wrapped callbacks are cancelled. + void Reset(const butil::Callback& callback) { + DCHECK(!callback.is_null()); + + // Outstanding tasks (e.g., posted to a message loop) must not be called. + Cancel(); + + // |forwarder_| is no longer valid after Cancel(), so re-bind. + InitializeForwarder(); + + callback_ = callback; + } + + // Returns a callback that can be disabled by calling Cancel(). + const butil::Callback& callback() const { + return forwarder_; + } + + private: + void Forward(A1 a1, A2 a2) const { + callback_.Run(a1, a2); + } + + // Helper method to bind |forwarder_| using a weak pointer from + // |weak_factory_|. + void InitializeForwarder() { + forwarder_ = butil::Bind(&CancelableCallback::Forward, + weak_factory_.GetWeakPtr()); + } + + // Used to ensure Forward() is not run when this object is destroyed. + butil::WeakPtrFactory > weak_factory_; + + // The wrapper closure. + butil::Callback forwarder_; + + // The stored closure that may be cancelled. + butil::Callback callback_; + + DISALLOW_COPY_AND_ASSIGN(CancelableCallback); +}; + +typedef CancelableCallback CancelableClosure; + +} // namespace butil + +#endif // BUTIL_CANCELABLE_CALLBACK_H_ diff --git a/src/butil/class_name.cpp b/src/butil/class_name.cpp new file mode 100644 index 0000000..0312c4b --- /dev/null +++ b/src/butil/class_name.cpp @@ -0,0 +1,61 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Mon. Nov 7 14:47:36 CST 2011 + +#include // __cxa_demangle +#include // std::string +#include // free() + +namespace butil { + +// Try to convert mangled |name| to human-readable name. +// Returns: +// |name| - Fail to demangle |name| +// otherwise - demangled name +std::string demangle(const char* name) { + // mangled_name + // A NULL-terminated character string containing the name to + // be demangled. + // output_buffer: + // A region of memory, allocated with malloc, of *length bytes, + // into which the demangled name is stored. If output_buffer is + // not long enough, it is expanded using realloc. output_buffer + // may instead be NULL; in that case, the demangled name is placed + // in a region of memory allocated with malloc. + // length + // If length is non-NULL, the length of the buffer containing the + // demangled name is placed in *length. + // status + // *status is set to one of the following values: + // 0: The demangling operation succeeded. + // -1: A memory allocation failure occurred. + // -2: mangled_name is not a valid name under the C++ ABI + // mangling rules. + // -3: One of the arguments is invalid. + int status = 0; + char* buf = abi::__cxa_demangle(name, NULL, NULL, &status); + if (status == 0 && buf) { + std::string s(buf); + free(buf); + return s; + } + return std::string(name); +} + +} // namespace butil + diff --git a/src/butil/class_name.h b/src/butil/class_name.h new file mode 100644 index 0000000..29c91a9 --- /dev/null +++ b/src/butil/class_name.h @@ -0,0 +1,58 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Mon. Nov 7 14:47:36 CST 2011 + +// Get name of a class. For example, class_name() returns the name of T +// (with namespace prefixes). This is useful in template classes. + +#ifndef BUTIL_CLASS_NAME_H +#define BUTIL_CLASS_NAME_H + +#include +#include // std::string + +namespace butil { + +std::string demangle(const char* name); + +namespace { +template struct ClassNameHelper { static std::string name; }; +template std::string ClassNameHelper::name = demangle(typeid(T).name()); +} + +// Get name of class |T|, in std::string. +template const std::string& class_name_str() { + // We don't use static-variable-inside-function because before C++11 + // local static variable is not guaranteed to be thread-safe. + return ClassNameHelper::name; +} + +// Get name of class |T|, in const char*. +// Address of returned name never changes. +template const char* class_name() { + return class_name_str().c_str(); +} + +// Get typename of |obj|, in std::string +template std::string class_name_str(T const& obj) { + return demangle(typeid(obj).name()); +} + +} // namespace butil + +#endif // BUTIL_CLASS_NAME_H diff --git a/src/butil/comlog_sink.cc b/src/butil/comlog_sink.cc new file mode 100644 index 0000000..c645447 --- /dev/null +++ b/src/butil/comlog_sink.cc @@ -0,0 +1,392 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Mon Jul 20 12:39:39 CST 2015 + +#include +#include "butil/memory/singleton.h" +#include "butil/comlog_sink.h" +#include "butil/files/file_path.h" +#include "butil/fd_guard.h" +#include "butil/file_util.h" +#include "butil/endpoint.h" + +namespace logging { +DECLARE_bool(log_year); +DECLARE_bool(log_hostname); + +struct ComlogLayoutOptions { + ComlogLayoutOptions() : shorter_log_level(true) {} + + bool shorter_log_level; +}; + +class ComlogLayout : public comspace::Layout { +public: + explicit ComlogLayout(const ComlogLayoutOptions* options); + ~ComlogLayout(); + int format(comspace::Event *evt); +private: + ComlogLayoutOptions _options; +}; + +ComlogLayout::ComlogLayout(const ComlogLayoutOptions* options) { + if (options) { + _options = *options; + } +} + +ComlogLayout::~ComlogLayout() { +} + +// Override Layout::format to have shorter prefixes. Patterns are just ignored. +int ComlogLayout::format(comspace::Event *evt) { + const int bufsize = evt->_render_msgbuf_size; + char* const buf = evt->_render_msgbuf; + if (bufsize < 2){ + return -1; + } + + time_t t = evt->_print_time.tv_sec; + struct tm local_tm = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL}; +#if _MSC_VER >= 1400 + localtime_s(&local_tm, &t); +#else + localtime_r(&t, &local_tm); +#endif + int len = 0; + if (_options.shorter_log_level) { + buf[len++] = *comspace::getLogName(evt->_log_level); + } else { + const char* const name = comspace::getLogName(evt->_log_level); + int cp_len = std::min(bufsize - len, (int)strlen(name)); + memcpy(buf + len, name, cp_len); + len += cp_len; + if (len < bufsize - 1) { + buf[len++] = ' '; + } + } + if (len < bufsize - 1) { + int ret = 0; + if (FLAGS_log_year) { + ret = snprintf(buf + len, bufsize - len, + "%04d%02d%02d %02d:%02d:%02d.%06d %5u ", + local_tm.tm_year + 1900, + local_tm.tm_mon + 1, + local_tm.tm_mday, + local_tm.tm_hour, + local_tm.tm_min, + local_tm.tm_sec, + (int)evt->_print_time.tv_usec, + (unsigned int)evt->_thread_id); + } else { + ret = snprintf(buf + len, bufsize - len, + "%02d%02d %02d:%02d:%02d.%06d %5u ", + local_tm.tm_mon + 1, + local_tm.tm_mday, + local_tm.tm_hour, + local_tm.tm_min, + local_tm.tm_sec, + (int)evt->_print_time.tv_usec, + (unsigned int)evt->_thread_id); + } + if (ret >= 0) { + len += ret; + } else { + // older glibc may return negative which means the buffer is full. + len = bufsize; + } + } + if (len > 0 && len < bufsize - 1) { // not truncated. + // Although it's very stupid, we have to copy the message again due + // to the design of comlog. + int cp_len = std::min(bufsize - len, evt->_msgbuf_len); + memcpy(buf + len, evt->_msgbuf, cp_len); + len += cp_len; + } + if (len >= bufsize - 1) { + len = bufsize - 2; + } + buf[len++] = '\n'; + buf[len] = 0; + evt->_render_msgbuf_len = len; + return 0; +} + +ComlogSink* ComlogSink::GetInstance() { + return Singleton >::get(); +} + +ComlogSinkOptions::ComlogSinkOptions() + : async(false) + , shorter_log_level(true) + , log_dir("log") + , max_log_length(2048) + , print_vlog_as_warning(true) + , split_type(COMLOG_SPLIT_TRUNCT) + , cut_size_megabytes(2048) + , quota_size(0) + , cut_interval_minutes(60) + , quota_day(0) + , quota_hour(0) + , quota_min(0) + , enable_wf_device(false) { +} + +ComlogSink::ComlogSink() + : _init(false), _dev(NULL) { +} + +int ComlogSink::SetupFromConfig(const std::string& conf_path_str) { + Unload(); + butil::FilePath path(conf_path_str); + if (com_loadlog(path.DirName().value().c_str(), + path.BaseName().value().c_str()) != 0) { + LOG(ERROR) << "Fail to create ComlogSink from `" << conf_path_str << "'"; + return -1; + } + _init = true; + return 0; +} + +// This is definitely linux specific. +static std::string GetProcessName() { + butil::fd_guard fd(open("/proc/self/cmdline", O_RDONLY)); + if (fd < 0) { + return "unknown"; + } + char buf[512]; + const ssize_t len = read(fd, buf, sizeof(buf) - 1); + if (len <= 0) { + return "unknown"; + } + buf[len] = '\0'; + // Not string(buf, len) because we needs to buf to be truncated at first \0. + // Under gdb, the first part of cmdline may include path. + return butil::FilePath(std::string(buf)).BaseName().value(); +} + +int ComlogSink::SetupDevice(com_device_t* dev, const char* type, const char* file, bool is_wf) { + butil::FilePath path(file); + snprintf(dev->host, sizeof(dev->host), "%s", path.DirName().value().c_str()); + if (!is_wf) { + snprintf(dev->name, sizeof(dev->name), "%s_0", type); + COMLOG_SETSYSLOG(*dev); + + //snprintf(dev->file, COM_MAXFILENAME, "%s", file); + snprintf(dev->file, sizeof(dev->file), "%s", path.BaseName().value().c_str()); + } else { + snprintf(dev->name, sizeof(dev->name), "%s_1", type); + dev->log_mask = 0; + COMLOG_ADDMASK(*dev, COMLOG_WARNING); + COMLOG_ADDMASK(*dev, COMLOG_FATAL); + + //snprintf(dev->file, COM_MAXFILENAME, "%s.wf", file); + snprintf(dev->file, sizeof(dev->file), "%s.wf", path.BaseName().value().c_str()); + } + + snprintf(dev->type, COM_MAXAPPENDERNAME, "%s", type); + dev->splite_type = static_cast(_options.split_type); + dev->log_size = _options.cut_size_megabytes; // SIZECUT precision in MB + dev->compress = 0; + dev->cuttime = _options.cut_interval_minutes; // DATECUT time precision in min + + // set quota conf + int index = dev->reserved_num; + if (dev->splite_type == COMLOG_SPLIT_SIZECUT) { + if (_options.cut_size_megabytes <= 0) { + LOG(ERROR) << "Invalid ComlogSinkOptions.cut_size_megabytes=" + << _options.cut_size_megabytes; + return -1; + } + if (_options.quota_size < 0) { + LOG(ERROR) << "Invalid ComlogSinkOptions.quota_size=" + << _options.quota_size; + return -1; + } + snprintf(dev->reservedext[index].name, sizeof(dev->reservedext[index].name), + "%s_QUOTA_SIZE", dev->name); + snprintf(dev->reservedext[index].value, sizeof(dev->reservedext[index].value), + "%d", _options.quota_size); + index++; + } else if (dev->splite_type == COMLOG_SPLIT_DATECUT) { + if (_options.quota_day < 0) { + LOG(ERROR) << "Invalid ComlogSinkOptions.quota_day=" << _options.quota_day; + return -1; + } + if (_options.quota_hour < 0) { + LOG(ERROR) << "Invalid ComlogSinkOptions.quota_hour=" << _options.quota_hour; + return -1; + } + if (_options.quota_min < 0) { + LOG(ERROR) << "Invalid ComlogSinkOptions.quota_min=" << _options.quota_min; + return -1; + } + if (_options.quota_day > 0) { + snprintf(dev->reservedext[index].name, sizeof(dev->reservedext[index].name), + "%s_QUOTA_DAY", (char*)dev->name); + snprintf(dev->reservedext[index].value, sizeof(dev->reservedext[index].value), + "%d", _options.quota_day); + index++; + } + if (_options.quota_hour > 0) { + snprintf(dev->reservedext[index].name, sizeof(dev->reservedext[index].name), + "%s_QUOTA_HOUR", (char*)dev->name); + snprintf(dev->reservedext[index].value, sizeof(dev->reservedext[index].value), + "%d", _options.quota_hour); + index++; + } + if (_options.quota_min > 0) { + snprintf(dev->reservedext[index].name, sizeof(dev->reservedext[index].name), + "%s_QUOTA_MIN", (char*)dev->name); + snprintf(dev->reservedext[index].value, sizeof(dev->reservedext[index].value), + "%d", _options.quota_min); + index++; + } + } + dev->reserved_num = index; + dev->reservedconf.item = &dev->reservedext[0]; + dev->reservedconf.num = dev->reserved_num; + dev->reservedconf.size = dev->reserved_num; + + ComlogLayoutOptions layout_options; + layout_options.shorter_log_level = _options.shorter_log_level; + ComlogLayout* layout = new (std::nothrow) ComlogLayout(&layout_options); + if (layout == NULL) { + LOG(FATAL) << "Fail to new layout"; + return -1; + } + dev->layout = layout; + + return 0; +} + +int ComlogSink::Setup(const ComlogSinkOptions* options) { + Unload(); + if (options) { + _options = *options; + } + if (_options.max_log_length > 0) { + comspace::Event::setMaxLogLength(_options.max_log_length); + } + if (_options.process_name.empty()) { + _options.process_name = GetProcessName(); + } + + char type[COM_MAXAPPENDERNAME]; + if (_options.async) { + snprintf(type, COM_MAXAPPENDERNAME, "AFILE"); + } else { + snprintf(type, COM_MAXAPPENDERNAME, "FILE"); + } + butil::FilePath cwd; + if (!_options.log_dir.empty()) { + butil::FilePath log_dir(_options.log_dir); + if (log_dir.IsAbsolute()) { + cwd = log_dir; + } else { + if (!butil::GetCurrentDirectory(&cwd)) { + LOG(ERROR) << "Fail to get cwd"; + return -1; + } + cwd = cwd.Append(log_dir); + } + } else { + if (!butil::GetCurrentDirectory(&cwd)) { + LOG(ERROR) << "Fail to get cwd"; + return -1; + } + } + butil::File::Error err; + if (!butil::CreateDirectoryAndGetError(cwd, &err)) { + LOG(ERROR) << "Fail to create directory, " << err; + return -1; + } + char file[COM_MAXFILENAME]; + snprintf(file, COM_MAXFILENAME, "%s", + cwd.Append(_options.process_name + ".log").value().c_str()); + + int dev_num = (_options.enable_wf_device ? 2 : 1); + _dev = new (std::nothrow) com_device_t[dev_num]; + if (NULL == _dev) { + LOG(FATAL) << "Fail to new com_device_t"; + return -1; + } + if (0 != SetupDevice(&_dev[0], type, file, false)) { + LOG(ERROR) << "Fail to setup first com_device_t"; + return -1; + } + if (dev_num == 2) { + if (0 != SetupDevice(&_dev[1], type, file, true)) { + LOG(ERROR) << "Fail to setup second com_device_t"; + return -1; + } + } + if (com_openlog(_options.process_name.c_str(), _dev, dev_num, NULL) != 0) { + LOG(ERROR) << "Fail to com_openlog"; + return -1; + } + _init = true; + return 0; +} + +void ComlogSink::Unload() { + if (_init) { + com_closelog(0); + _init = false; + } + if (_dev) { + // FIXME(gejun): Can't delete layout, somewhere in comlog may still + // reference the layout after com_closelog. + //delete _dev->layout; + delete [] _dev; + _dev = NULL; + } +} + +ComlogSink::~ComlogSink() { + Unload(); +} + +int const comlog_levels[LOG_NUM_SEVERITIES] = { + COMLOG_TRACE, COMLOG_NOTICE, COMLOG_WARNING, COMLOG_FATAL, COMLOG_FATAL }; + +bool ComlogSink::OnLogMessage(int severity, const char* file, int line, + const butil::StringPiece& content) { + // Print warning for VLOG since many online servers do not enable COMLOG_TRACE. + int comlog_level = 0; + if (severity < 0) { + comlog_level = _options.print_vlog_as_warning ? COMLOG_WARNING : COMLOG_TRACE; + } else { + comlog_level = comlog_levels[severity]; + } + if (FLAGS_log_hostname) { + butil::StringPiece hostname(butil::my_hostname()); + if (hostname.ends_with(".baidu.com")) { // make it shorter + hostname.remove_suffix(10); + } + return com_writelog(comlog_level, "%.*s %s:%d] %.*s", + (int)hostname.size(), hostname.data(), + file, line, + (int)content.size(), content.data()) == 0; + } + // Using %.*s is faster than %s. + return com_writelog(comlog_level, "%s:%d] %.*s", file, line, + (int)content.size(), content.data()) == 0; +} + +} // namespace logging diff --git a/src/butil/comlog_sink.h b/src/butil/comlog_sink.h new file mode 100644 index 0000000..6444ff6 --- /dev/null +++ b/src/butil/comlog_sink.h @@ -0,0 +1,166 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Mon Jul 20 12:39:39 CST 2015 + +// Redirect LOG() into comlog. + +#ifndef BUTIL_COMLOG_SINK_H +#define BUTIL_COMLOG_SINK_H + +#include "butil/logging.h" + +struct com_device_t; +template struct DefaultSingletonTraits; + +namespace comspace { +class Event; +} + +namespace logging { + +enum ComlogSplitType { + COMLOG_SPLIT_TRUNCT = 0, + COMLOG_SPLIT_SIZECUT = 1, + COMLOG_SPLIT_DATECUT = 2, +}; + +// Options to setup ComlogSink. +struct ComlogSinkOptions { + ComlogSinkOptions(); + + // true - "AFILE", false - "FILE" + // default: false. + bool async; + + // Use F W N T instead of FATAL WARNING NOTICE TRACE for shorter prefixes + // and better alignment. + // default: true + bool shorter_log_level; + + // The directory to put logs. Could be absolute or relative path. + // default: "log" + std::string log_dir; + + // Name of the process. Use argv[0] when it's empty. + // default: "" + std::string process_name; + + // Logs longer than this value are truncated. + // default: 2048 + int max_log_length; + + // Print VLOG(n) as WARNING instead of TRACE since many online servers + // disable TRACE logs. + // default: true; + bool print_vlog_as_warning; + + // Split Comlog type: + // COMLOG_SPLIT_TRUNCT: rotate the log file every 2G written. + // COMLOG_SPLIT_SIZECUT: move existing logs into a separate file every xxx MB written. + // COMLOG_SPLIT_DATECUT: move existing logs into a separate file periodically. + // default: COMLOG_SPLIT_TRUNCT + ComlogSplitType split_type; + + // [ Effective when split_type is COMLOG_SPLIT_SIZECUT ] + // Move existing logs into a separate file suffixed with datetime every so many MB written. + // Default: 2048 + int cut_size_megabytes; + // Remove oldest cutoff log files when they exceed so many megabytes(roughly) + // Default: 0 (unlimited) + int quota_size; + + // [ Effective when split_type is COMLOG_SPLIT_DATECUT ] + // Move existing logs into a separate file suffixed with datetime every so many minutes. + // Example: my_app.log is moved to my_app.log.20160905113104 + // Default: 60 + int cut_interval_minutes; + // Remove cutoff log files older than so many minutes: + // quota_day * 24 * 60 + quota_hour * 60 + quota_min + // Default: 0 (unlimited) + int quota_day; + int quota_hour; + int quota_min; + + // Open wf appender device for WARNING/ERROR/FATAL + // default: false + bool enable_wf_device; +}; + +// The LogSink to flush logs into comlog. Notice that this is a singleton class. +// [ Setup from a Configure file ] +// if (logging::ComlogSink::GetInstance()->SetupFromConfig("log/log.conf") != 0) { +// LOG(ERROR) << "Fail to setup comlog"; +// return -1; +// } +// logging::SetLogSink(ComlogSink::GetInstance()); +// +// [ Setup from ComlogSinkOptions ] +// if (logging::ComlogSink::GetInstance()->Setup(NULL/*default options*/) != 0) { +// LOG(ERROR) << "Fail to setup comlog"; +// return -1; +// } +// logging::SetLogSink(ComlogSink::GetInstance()); + +class ComlogSink : public LogSink { +public: + // comlog can have only one instance due to its global open/close. + static ComlogSink* GetInstance(); + + // Setup comlog in different ways: from a Configure file or + // ComlogSinkOptions. Notice that setup can be done multiple times. + int SetupFromConfig(const std::string& conf_path); + int Setup(const ComlogSinkOptions* options); + + // Close comlog and release resources. This method is automatically + // called before Setup and destruction. + void Unload(); + + // @LogSink + bool OnLogMessage(int severity, const char* file, int line, + const butil::StringPiece& content); +private: + ComlogSink(); + ~ComlogSink(); +friend struct DefaultSingletonTraits; + int SetupDevice(com_device_t* dev, const char* type, const char* file, bool is_wf); + + bool _init; + ComlogSinkOptions _options; + com_device_t* _dev; +}; + +class ComlogInitializer { +public: + ComlogInitializer() { + if (com_logstatus() != LOG_NOT_DEFINED) { + com_openlog_r(); + } + } + ~ComlogInitializer() { + if (com_logstatus() != LOG_NOT_DEFINED) { + com_closelog_r(); + } + } + +private: + DISALLOW_COPY_AND_ASSIGN(ComlogInitializer); +}; + +} // namespace logging + +#endif // BUTIL_COMLOG_SINK_H diff --git a/src/butil/compat.h b/src/butil/compat.h new file mode 100644 index 0000000..d75e163 --- /dev/null +++ b/src/butil/compat.h @@ -0,0 +1,91 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BUTIL_COMPAT_H +#define BUTIL_COMPAT_H + +#include "butil/build_config.h" +#include + +#if defined(OS_MACOSX) + +#include +#include +#include // dispatch_semaphore +#include // EINVAL + +__BEGIN_DECLS + +// Implement pthread_spinlock_t for MAC. +struct pthread_spinlock_t { + dispatch_semaphore_t sem; +}; +inline int pthread_spin_init(pthread_spinlock_t *__lock, int __pshared) { + if (__pshared != 0) { + return EINVAL; + } + __lock->sem = dispatch_semaphore_create(1); + return 0; +} +inline int pthread_spin_destroy(pthread_spinlock_t *__lock) { + // TODO(gejun): Not see any destructive API on dispatch_semaphore + (void)__lock; + return 0; +} +inline int pthread_spin_lock(pthread_spinlock_t *__lock) { + return (int)dispatch_semaphore_wait(__lock->sem, DISPATCH_TIME_FOREVER); +} +inline int pthread_spin_trylock(pthread_spinlock_t *__lock) { + if (dispatch_semaphore_wait(__lock->sem, DISPATCH_TIME_NOW) == 0) { + return 0; + } + return EBUSY; +} +inline int pthread_spin_unlock(pthread_spinlock_t *__lock) { + return dispatch_semaphore_signal(__lock->sem); +} + +__END_DECLS + +#elif defined(OS_LINUX) + +#include + +#else + +#error "The platform does not support epoll-like APIs" + +#endif // defined(OS_MACOSX) + +__BEGIN_DECLS + +inline uint64_t pthread_numeric_id() { +#if defined(OS_MACOSX) + uint64_t id; + if (pthread_threadid_np(pthread_self(), &id) == 0) { + return id; + } + return -1; +#else + return pthread_self(); +#endif +} + +__END_DECLS + +#endif // BUTIL_COMPAT_H diff --git a/src/butil/compiler_specific.h b/src/butil/compiler_specific.h new file mode 100644 index 0000000..7c1c266 --- /dev/null +++ b/src/butil/compiler_specific.h @@ -0,0 +1,313 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_COMPILER_SPECIFIC_H_ +#define BUTIL_COMPILER_SPECIFIC_H_ + +#include "butil/build_config.h" + +#if defined(COMPILER_MSVC) + +// Macros for suppressing and disabling warnings on MSVC. +// +// Warning numbers are enumerated at: +// http://msdn.microsoft.com/en-us/library/8x5x43k7(VS.80).aspx +// +// The warning pragma: +// http://msdn.microsoft.com/en-us/library/2c8f766e(VS.80).aspx +// +// Using __pragma instead of #pragma inside macros: +// http://msdn.microsoft.com/en-us/library/d9x1s805.aspx + +// MSVC_SUPPRESS_WARNING disables warning |n| for the remainder of the line and +// for the next line of the source file. +#define MSVC_SUPPRESS_WARNING(n) __pragma(warning(suppress:n)) + +// MSVC_PUSH_DISABLE_WARNING pushes |n| onto a stack of warnings to be disabled. +// The warning remains disabled until popped by MSVC_POP_WARNING. +#define MSVC_PUSH_DISABLE_WARNING(n) __pragma(warning(push)) \ + __pragma(warning(disable:n)) + +// MSVC_PUSH_WARNING_LEVEL pushes |n| as the global warning level. The level +// remains in effect until popped by MSVC_POP_WARNING(). Use 0 to disable all +// warnings. +#define MSVC_PUSH_WARNING_LEVEL(n) __pragma(warning(push, n)) + +// Pop effects of innermost MSVC_PUSH_* macro. +#define MSVC_POP_WARNING() __pragma(warning(pop)) + +#define MSVC_DISABLE_OPTIMIZE() __pragma(optimize("", off)) +#define MSVC_ENABLE_OPTIMIZE() __pragma(optimize("", on)) + +// Allows exporting a class that inherits from a non-exported base class. +// This uses suppress instead of push/pop because the delimiter after the +// declaration (either "," or "{") has to be placed before the pop macro. +// +// Example usage: +// class EXPORT_API Foo : NON_EXPORTED_BASE(public Bar) { +// +// MSVC Compiler warning C4275: +// non dll-interface class 'Bar' used as base for dll-interface class 'Foo'. +// Note that this is intended to be used only when no access to the base class' +// static data is done through derived classes or inline methods. For more info, +// see http://msdn.microsoft.com/en-us/library/3tdb471s(VS.80).aspx +#define NON_EXPORTED_BASE(code) MSVC_SUPPRESS_WARNING(4275) \ + code + +#else // Not MSVC + +#define MSVC_SUPPRESS_WARNING(n) +#define MSVC_PUSH_DISABLE_WARNING(n) +#define MSVC_PUSH_WARNING_LEVEL(n) +#define MSVC_POP_WARNING() +#define MSVC_DISABLE_OPTIMIZE() +#define MSVC_ENABLE_OPTIMIZE() +#define NON_EXPORTED_BASE(code) code + +#endif // COMPILER_MSVC + + +// The C++ standard requires that static const members have an out-of-class +// definition (in a single compilation unit), but MSVC chokes on this (when +// language extensions, which are required, are enabled). (You're only likely to +// notice the need for a definition if you take the address of the member or, +// more commonly, pass it to a function that takes it as a reference argument -- +// probably an STL function.) This macro makes MSVC do the right thing. See +// http://msdn.microsoft.com/en-us/library/34h23df8(v=vs.100).aspx for more +// information. Use like: +// +// In .h file: +// struct Foo { +// static const int kBar = 5; +// }; +// +// In .cc file: +// STATIC_CONST_MEMBER_DEFINITION const int Foo::kBar; +#if defined(COMPILER_MSVC) +#define STATIC_CONST_MEMBER_DEFINITION __declspec(selectany) +#else +#define STATIC_CONST_MEMBER_DEFINITION +#endif + +// Annotate a variable indicating it's ok if the variable is not used. +// (Typically used to silence a compiler warning when the assignment +// is important for some other reason.) +// Use like: +// int x ALLOW_UNUSED = ...; +#if defined(COMPILER_GCC) +#define ALLOW_UNUSED __attribute__((unused)) +#else +#define ALLOW_UNUSED +#endif + +// Annotate a function indicating it should not be inlined. +// Use like: +// NOINLINE void DoStuff() { ... } +#if defined(COMPILER_GCC) +#define NOINLINE __attribute__((noinline)) +#elif defined(COMPILER_MSVC) +#define NOINLINE __declspec(noinline) +#else +#define NOINLINE +#endif + +#ifndef BUTIL_FORCE_INLINE +#if defined(COMPILER_MSVC) +#define BUTIL_FORCE_INLINE __forceinline +#else +#define BUTIL_FORCE_INLINE inline __attribute__((always_inline)) +#endif +#endif // BUTIL_FORCE_INLINE + +// Specify memory alignment for structs, classes, etc. +// Use like: +// class ALIGNAS(16) MyClass { ... } +// ALIGNAS(16) int array[4]; +// +// In most places you can use the C++11 keyword "alignas", which is preferred. +// +// But compilers have trouble mixing __attribute__((...)) syntax with +// alignas(...) syntax. +// +// Doesn't work in clang or gcc: +// struct alignas(16) __attribute__((packed)) S { char c; }; +// Works in clang but not gcc: +// struct __attribute__((packed)) alignas(16) S2 { char c; }; +// Works in clang and gcc: +// struct alignas(16) S3 { char c; } __attribute__((packed)); +// +// There are also some attributes that must be specified *before* a class +// definition: visibility (used for exporting functions/classes) is one of +// these attributes. This means that it is not possible to use alignas() with a +// class that is marked as exported. +#if defined(COMPILER_MSVC) +# define ALIGNAS(byte_alignment) __declspec(align(byte_alignment)) +#elif defined(COMPILER_GCC) +# define ALIGNAS(byte_alignment) __attribute__((aligned(byte_alignment))) +#endif + +// Return the byte alignment of the given type (available at compile time). Use +// sizeof(type) prior to checking __alignof to workaround Visual C++ bug: +// http://goo.gl/isH0C +// Use like: +// ALIGNOF(int32_t) // this would be 4 +#if defined(COMPILER_MSVC) +# define ALIGNOF(type) (sizeof(type) - sizeof(type) + __alignof(type)) +#elif defined(COMPILER_GCC) +# define ALIGNOF(type) __alignof__(type) +#endif + +// Annotate a virtual method indicating it must be overriding a virtual +// method in the parent class. +// Use like: +// virtual void foo() OVERRIDE; +#if defined(__clang__) || defined(COMPILER_MSVC) +#define OVERRIDE override +#elif defined(COMPILER_GCC) && __cplusplus >= 201103 && \ + (__GNUC__ * 10000 + __GNUC_MINOR__ * 100) >= 40700 +// GCC 4.7 supports explicit virtual overrides when C++11 support is enabled. +#define OVERRIDE override +#else +#define OVERRIDE +#endif + +// Annotate a virtual method indicating that subclasses must not override it, +// or annotate a class to indicate that it cannot be subclassed. +// Use like: +// virtual void foo() FINAL; +// class B FINAL : public A {}; +#if defined(__clang__) || defined(COMPILER_MSVC) +#define FINAL final +#elif defined(COMPILER_GCC) && __cplusplus >= 201103 && \ + (__GNUC__ * 10000 + __GNUC_MINOR__ * 100) >= 40700 +// GCC 4.7 supports explicit virtual overrides when C++11 support is enabled. +#define FINAL final +#else +#define FINAL +#endif + +// Annotate a function indicating the caller must examine the return value. +// Use like: +// int foo() WARN_UNUSED_RESULT; +// To explicitly ignore a result, see |ignore_result()| in "butil/basictypes.h". +// FIXME(gejun): GCC 3.4 report "unused" variable incorrectly (actually used). +#if defined(COMPILER_GCC) && __cplusplus >= 201103 && \ + (__GNUC__ * 10000 + __GNUC_MINOR__ * 100) >= 40700 +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define WARN_UNUSED_RESULT +#endif + +// Compiler feature-detection. +// clang.llvm.org/docs/LanguageExtensions.html#has-feature-and-has-extension +#if defined(__has_feature) +#define BUTIL_HAS_FEATURE(FEATURE) __has_feature(FEATURE) +#else +#define BUTIL_HAS_FEATURE(FEATURE) 0 +#endif + +// Instruct ASan is enabled. +#if defined(BUTIL_USE_ASAN) +#error "BUTIL_USE_ASAN cannot be set directly." +#elif BUTIL_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) +#define BUTIL_USE_ASAN +#endif + +// https://github.com/google/sanitizers/wiki/AddressSanitizer#turning-off-instrumentation +// Attribute to instruct ASan to ignore a function. +#if defined(COMPILER_GCC) +# define BUTIL_ATTRIBUTE_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address)) +#else +# define BUTIL_ATTRIBUTE_NO_SANITIZE_ADDRESS +#endif + + +// Tell the compiler a function is using a printf-style format string. +// |format_param| is the one-based index of the format string parameter; +// |dots_param| is the one-based index of the "..." parameter. +// For v*printf functions (which take a va_list), pass 0 for dots_param. +// (This is undocumented but matches what the system C headers do.) +#if defined(COMPILER_GCC) +#define PRINTF_FORMAT(format_param, dots_param) \ + __attribute__((format(printf, format_param, dots_param))) +#else +#define PRINTF_FORMAT(format_param, dots_param) +#endif + +// WPRINTF_FORMAT is the same, but for wide format strings. +// This doesn't appear to yet be implemented in any compiler. +// See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=38308 . +#define WPRINTF_FORMAT(format_param, dots_param) +// If available, it would look like: +// __attribute__((format(wprintf, format_param, dots_param))) + +// MemorySanitizer annotations. +#if defined(MEMORY_SANITIZER) && !defined(OS_NACL) +#include + +// Mark a memory region fully initialized. +// Use this to annotate code that deliberately reads uninitialized data, for +// example a GC scavenging root set pointers from the stack. +#define MSAN_UNPOISON(p, s) __msan_unpoison(p, s) +#else // MEMORY_SANITIZER +#define MSAN_UNPOISON(p, s) +#endif // MEMORY_SANITIZER + +// Macro useful for writing cross-platform function pointers. +#if !defined(CDECL) +#if defined(OS_WIN) +#define CDECL __cdecl +#else // defined(OS_WIN) +#define CDECL +#endif // defined(OS_WIN) +#endif // !defined(CDECL) + +// Mark a branch likely or unlikely to be true. +// We can't remove the BAIDU_ prefix because the name is likely to conflict, +// namely kylin already has the macro. +#if defined(COMPILER_GCC) +# if defined(__cplusplus) +# define BAIDU_LIKELY(expr) (__builtin_expect((bool)(expr), true)) +# define BAIDU_UNLIKELY(expr) (__builtin_expect((bool)(expr), false)) +# else +# define BAIDU_LIKELY(expr) (__builtin_expect(!!(expr), 1)) +# define BAIDU_UNLIKELY(expr) (__builtin_expect(!!(expr), 0)) +# endif +#else +# define BAIDU_LIKELY(expr) (expr) +# define BAIDU_UNLIKELY(expr) (expr) +#endif + +// BAIDU_DEPRECATED void dont_call_me_anymore(int arg); +// ... +// warning: 'void dont_call_me_anymore(int)' is deprecated +#if defined(COMPILER_GCC) +# define BAIDU_DEPRECATED __attribute__((deprecated)) +#elif defined(COMPILER_MSVC) +# define BAIDU_DEPRECATED __declspec(deprecated) +#else +# define BAIDU_DEPRECATED +#endif + +// Mark function as weak. This is GCC only feature. +#if defined(COMPILER_GCC) +# define BAIDU_WEAK __attribute__((weak)) +#else +# define BAIDU_WEAK +#endif + +// Cacheline related -------------------------------------- +#define BAIDU_CACHELINE_SIZE 64 + +#define BAIDU_CACHELINE_ALIGNMENT ALIGNAS(BAIDU_CACHELINE_SIZE) + +#ifndef BAIDU_NOEXCEPT +# if defined(BUTIL_CXX11_ENABLED) +# define BAIDU_NOEXCEPT noexcept +# else +# define BAIDU_NOEXCEPT +# endif +#endif + +#endif // BUTIL_COMPILER_SPECIFIC_H_ diff --git a/src/butil/containers/bounded_queue.h b/src/butil/containers/bounded_queue.h new file mode 100644 index 0000000..55f06e1 --- /dev/null +++ b/src/butil/containers/bounded_queue.h @@ -0,0 +1,307 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Sat Aug 18 12:42:16 CST 2012 + +// A thread-unsafe bounded queue(ring buffer). It can push/pop from both +// sides and is more handy than thread-safe queues in single thread. Use +// boost::lockfree::spsc_queue or boost::lockfree::queue in multi-threaded +// scenarios. + +#ifndef BUTIL_BOUNDED_QUEUE_H +#define BUTIL_BOUNDED_QUEUE_H + +#include "butil/macros.h" +#include "butil/logging.h" + +namespace butil { + +// [Create a on-stack small queue] +// char storage[64]; +// butil::BoundedQueue q(storage, sizeof(storage), butil::NOT_OWN_STORAGE); +// q.push(1); +// q.push(2); +// ... + +// [Initialize a class-member queue] +// class Foo { +// ... +// BoundQueue _queue; +// }; +// int Foo::init() { +// BoundedQueue tmp(capacity); +// if (!tmp.initialized()) { +// LOG(ERROR) << "Fail to create _queue"; +// return -1; +// } +// tmp.swap(_queue); +// } + +enum StorageOwnership { OWNS_STORAGE, NOT_OWN_STORAGE }; + +template +class BoundedQueue { +public: + // You have to pass the memory for storing items at creation. + // The queue contains at most memsize/sizeof(T) items. + BoundedQueue(void* mem, size_t memsize, StorageOwnership ownership) + : _count(0) + , _cap(memsize / sizeof(T)) + , _start(0) + , _ownership(ownership) + , _items(mem) { + DCHECK(_items); + }; + + // Construct a queue with the given capacity. + // The malloc() may fail silently, call initialized() to test validity + // of the queue. + explicit BoundedQueue(size_t capacity) + : _count(0) + , _cap(capacity) + , _start(0) + , _ownership(OWNS_STORAGE) + , _items(malloc(capacity * sizeof(T))) { + DCHECK(_items); + }; + + BoundedQueue() + : _count(0) + , _cap(0) + , _start(0) + , _ownership(NOT_OWN_STORAGE) + , _items(NULL) { + }; + + ~BoundedQueue() { + clear(); + if (_ownership == OWNS_STORAGE) { + free(_items); + _items = NULL; + } + } + + // Push |item| into bottom side of this queue. + // Returns true on success, false if queue is full. + bool push(const T& item) { + if (_count < _cap) { + new ((T*)_items + _mod(_start + _count, _cap)) T(item); + ++_count; + return true; + } + return false; + } + + // Push |item| into bottom side of this queue. If the queue is full, + // pop topmost item first. + void elim_push(const T& item) { + if (_count < _cap) { + new ((T*)_items + _mod(_start + _count, _cap)) T(item); + ++_count; + } else { + ((T*)_items)[_start] = item; + _start = _mod(_start + 1, _cap); + } + } + + // Push a default-constructed item into bottom side of this queue + // Returns address of the item inside this queue + T* push() { + if (_count < _cap) { + return new ((T*)_items + _mod(_start + _count++, _cap)) T(); + } + return NULL; + } + + // Push |item| into top side of this queue + // Returns true on success, false if queue is full. + bool push_top(const T& item) { + if (_count < _cap) { + _start = _start ? (_start - 1) : (_cap - 1); + ++_count; + new ((T*)_items + _start) T(item); + return true; + } + return false; + } + + // Push a default-constructed item into top side of this queue + // Returns address of the item inside this queue + T* push_top() { + if (_count < _cap) { + _start = _start ? (_start - 1) : (_cap - 1); + ++_count; + return new ((T*)_items + _start) T(); + } + return NULL; + } + + // Pop top-most item from this queue + // Returns true on success, false if queue is empty + bool pop() { + if (_count) { + --_count; + ((T*)_items + _start)->~T(); + _start = _mod(_start + 1, _cap); + return true; + } + return false; + } + + // Pop top-most item from this queue and copy into |item|. + // Returns true on success, false if queue is empty + bool pop(T* item) { + if (_count) { + --_count; + T* const p = (T*)_items + _start; + *item = *p; + p->~T(); + _start = _mod(_start + 1, _cap); + return true; + } + return false; + } + + // Pop bottom-most item from this queue + // Returns true on success, false if queue is empty + bool pop_bottom() { + if (_count) { + --_count; + ((T*)_items + _mod(_start + _count, _cap))->~T(); + return true; + } + return false; + } + + // Pop bottom-most item from this queue and copy into |item|. + // Returns true on success, false if queue is empty + bool pop_bottom(T* item) { + if (_count) { + --_count; + T* const p = (T*)_items + _mod(_start + _count, _cap); + *item = *p; + p->~T(); + return true; + } + return false; + } + + // Pop all items + void clear() { + for (uint32_t i = 0; i < _count; ++i) { + ((T*)_items + _mod(_start + i, _cap))->~T(); + } + _count = 0; + _start = 0; + } + + // Get address of top-most item, NULL if queue is empty + T* top() { + return _count ? ((T*)_items + _start) : NULL; + } + const T* top() const { + return _count ? ((const T*)_items + _start) : NULL; + } + + // Randomly access item from top side. + // top(0) == top(), top(size()-1) == bottom() + // Returns NULL if |index| is out of range. + T* top(size_t index) { + if (index < _count) { + return (T*)_items + _mod(_start + index, _cap); + } + return NULL; // including _count == 0 + } + const T* top(size_t index) const { + if (index < _count) { + return (const T*)_items + _mod(_start + index, _cap); + } + return NULL; // including _count == 0 + } + + // Get address of bottom-most item, NULL if queue is empty + T* bottom() { + return _count ? ((T*)_items + _mod(_start + _count - 1, _cap)) : NULL; + } + const T* bottom() const { + return _count ? ((const T*)_items + _mod(_start + _count - 1, _cap)) : NULL; + } + + // Randomly access item from bottom side. + // bottom(0) == bottom(), bottom(size()-1) == top() + // Returns NULL if |index| is out of range. + T* bottom(size_t index) { + if (index < _count) { + return (T*)_items + _mod(_start + _count - index - 1, _cap); + } + return NULL; // including _count == 0 + } + const T* bottom(size_t index) const { + if (index < _count) { + return (const T*)_items + _mod(_start + _count - index - 1, _cap); + } + return NULL; // including _count == 0 + } + + bool empty() const { return !_count; } + bool full() const { return _cap == _count; } + + // Number of items + size_t size() const { return _count; } + + // Maximum number of items that can be in this queue + size_t capacity() const { return _cap; } + + // Maximum value of capacity() + size_t max_capacity() const { return (1UL << (sizeof(_cap) * 8)) - 1; } + + // True if the queue was constructed successfully. + bool initialized() const { return _items != NULL; } + + // Swap internal fields with another queue. + void swap(BoundedQueue& rhs) { + std::swap(_count, rhs._count); + std::swap(_cap, rhs._cap); + std::swap(_start, rhs._start); + std::swap(_ownership, rhs._ownership); + std::swap(_items, rhs._items); + } + +private: + // Since the space is possibly not owned, we disable copying. + DISALLOW_COPY_AND_ASSIGN(BoundedQueue); + + // This is faster than % in this queue because most |off| are smaller + // than |cap|. This is probably not true in other place, be careful + // before you use this trick. + static uint32_t _mod(uint32_t off, uint32_t cap) { + while (off >= cap) { + off -= cap; + } + return off; + } + + uint32_t _count; + uint32_t _cap; + uint32_t _start; + StorageOwnership _ownership; + void* _items; +}; + +} // namespace butil + +#endif // BUTIL_BOUNDED_QUEUE_H diff --git a/src/butil/containers/case_ignored_flat_map.cpp b/src/butil/containers/case_ignored_flat_map.cpp new file mode 100644 index 0000000..a747156 --- /dev/null +++ b/src/butil/containers/case_ignored_flat_map.cpp @@ -0,0 +1,53 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Sun Dec 4 14:57:27 CST 2016 + +namespace butil { + +static const signed char g_tolower_map_base[] = { + -128, -127, -126, -125, -124, -123, -122, -121, -120, + -119, -118, -117, -116, -115, -114, -113, -112, -111, -110, + -109, -108, -107, -106, -105, -104, -103, -102, -101, -100, + -99, -98, -97, -96, -95, -94, -93, -92, -91, -90, + -89, -88, -87, -86, -85, -84, -83, -82, -81, -80, + -79, -78, -77, -76, -75, -74, -73, -72, -71, -70, + -69, -68, -67, -66, -65, -64, -63, -62, -61, -60, + -59, -58, -57, -56, -55, -54, -53, -52, -51, -50, + -49, -48, -47, -46, -45, -44, -43, -42, -41, -40, + -39, -38, -37, -36, -35, -34, -33, -32, -31, -30, + -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, + -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, + -9, -8, -7, -6, -5, -4, -3, -2, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 'a', 'b', 'c', 'd', 'e', + 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', + 'z', 91, 92, 93, 94, 95, 96, 97, 98, 99, + 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127 +}; + +extern const signed char* const g_tolower_map = g_tolower_map_base + 128; + +} // namespace butil diff --git a/src/butil/containers/case_ignored_flat_map.h b/src/butil/containers/case_ignored_flat_map.h new file mode 100644 index 0000000..909e731 --- /dev/null +++ b/src/butil/containers/case_ignored_flat_map.h @@ -0,0 +1,77 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Sun Dec 4 14:57:27 CST 2016 + +#ifndef BUTIL_CASE_IGNORED_FLAT_MAP_H +#define BUTIL_CASE_IGNORED_FLAT_MAP_H + +#include "butil/containers/flat_map.h" + +namespace butil { + +// Using ascii_tolower instead of ::tolower shortens 150ns in +// FlatMapTest.perf_small_string_map (with -O2 added, -O0 by default) +// note: using char caused crashes on ubuntu 20.04 aarch64 (VM on apple M1) +inline char ascii_tolower(int/*note*/ c) { + extern const signed char* const g_tolower_map; + return g_tolower_map[c]; +} + +struct CaseIgnoredHasher { + size_t operator()(const std::string& s) const { + std::size_t result = 0; + for (std::string::const_iterator i = s.begin(); i != s.end(); ++i) { + result = result * 101 + ascii_tolower(*i); + } + return result; + } + size_t operator()(const char* s) const { + std::size_t result = 0; + for (; *s; ++s) { + result = result * 101 + ascii_tolower(*s); + } + return result; + } +}; + +struct CaseIgnoredEqual { + // NOTE: No overload for butil::StringPiece. It needs strncasecmp + // which is much slower than strcasecmp in micro-benchmarking. As a + // result, methods in HttpHeader does not accept StringPiece as well. + bool operator()(const std::string& s1, const std::string& s2) const { + return s1.size() == s2.size() && + strcasecmp(s1.c_str(), s2.c_str()) == 0; + } + bool operator()(const std::string& s1, const char* s2) const + { return strcasecmp(s1.c_str(), s2) == 0; } +}; + +template +class CaseIgnoredFlatMap : public butil::FlatMap< + std::string, T, CaseIgnoredHasher, CaseIgnoredEqual> {}; + +class CaseIgnoredFlatSet : public butil::FlatSet< + std::string, CaseIgnoredHasher, CaseIgnoredEqual> {}; + +template +class CaseIgnoredMultiFlatMap : public butil::FlatMap< + std::string, T, CaseIgnoredHasher, CaseIgnoredEqual, false, PtAllocator, true> {}; + +} // namespace butil + +#endif // BUTIL_CASE_IGNORED_FLAT_MAP_H diff --git a/src/butil/containers/doubly_buffered_data.h b/src/butil/containers/doubly_buffered_data.h new file mode 100644 index 0000000..8ba54ac --- /dev/null +++ b/src/butil/containers/doubly_buffered_data.h @@ -0,0 +1,574 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Mon Sep 22 22:23:13 CST 2014 + +#ifndef BUTIL_DOUBLY_BUFFERED_DATA_H +#define BUTIL_DOUBLY_BUFFERED_DATA_H + +#include +#include +#include +#include +#include "butil/scoped_lock.h" +#include "butil/thread_local.h" +#include "butil/logging.h" +#include "butil/macros.h" +#include "butil/type_traits.h" +#include "butil/errno.h" +#include "butil/atomicops.h" +#include "butil/unique_ptr.h" +#include "butil/type_traits.h" + +namespace butil { + +// This data structure makes Read() almost lock-free by making Modify() +// *much* slower. It's very suitable for implementing LoadBalancers which +// have a lot of concurrent read-only ops from many threads and occasional +// modifications of data. As a side effect, this data structure can store +// a thread-local data for user. +// +// --- `AllowBthreadSuspended=false' --- +// Read(): Begin with a thread-local mutex locked then read the foreground +// instance which will not be changed before the mutex is unlocked. Since the +// mutex is only locked by Modify() with an empty critical section, the +// function is almost lock-free. +// +// Modify(): Modify background instance which is not used by any Read(), flip +// foreground and background, lock thread-local mutexes one by one to make +// sure all existing Read() finish and later Read() see new foreground, +// then modify background(foreground before flip) again. +// +// But, when `AllowBthreadSuspended=false', it is not allowed to suspend bthread +// while reading. Otherwise, it may cause deadlock. +// +// +// --- `AllowBthreadSuspended=true' --- +// It is allowed to suspend bthread while reading. +// It is not allowed to use non-Void TLS. +// If bthread will not be suspended while reading, it also makes Read() almost +// lock-free by making Modify() *much* slower. +// If bthread will be suspended while reading, there is competition among +// bthreads using the same Wrapper. +// +// Read(): Begin with thread-local reference count of foreground instance +// incremented by one which be protected by a thread-local mutex, then read +// the foreground instance which will not be changed before its all thread-local +// reference count become zero. At last, after the query completes, thread-local +// reference count of foreground instance will be decremented by one, and if +// it becomes zero, notifies Modify(). +// +// Modify(): Modify background instance which is not used by any Read(), flip +// foreground and background, lock thread-local mutexes one by one and wait +// until thread-local reference counts which be protected by a thread-local +// mutex become 0 to make sure all existing Read() finish and later Read() +// see new foreground, then modify background(foreground before flip) again. + +class Void { }; + +template struct IsVoid : false_type { }; +template <> struct IsVoid : true_type { }; + +template +class DoublyBufferedData { + class Wrapper; + class WrapperTLSGroup; + typedef int WrapperTLSId; + typedef std::shared_ptr WrapperSharedPtr; + typedef std::weak_ptr WrapperWeakPtr; +public: + class ScopedPtr { + friend class DoublyBufferedData; + public: + ScopedPtr() : _data(NULL), _index(0), _w(NULL) {} + ~ScopedPtr() { + if (_w) { + if (AllowBthreadSuspended) { + _w->EndRead(_index); + } else { + _w->EndRead(); + } + } + } + const T* get() const { return _data; } + const T& operator*() const { return *_data; } + const T* operator->() const { return _data; } + TLS& tls() { return _w->user_tls(); } + + private: + DISALLOW_COPY_AND_ASSIGN(ScopedPtr); + const T* _data; + // Index of foreground instance used by ScopedPtr. + int _index; + WrapperSharedPtr _w; + }; + + DoublyBufferedData(); + ~DoublyBufferedData(); + + // Put foreground instance into ptr. The instance will not be changed until + // ptr is destructed. + // This function is not blocked by Read() and Modify() in other threads. + // Returns 0 on success, -1 otherwise. + int Read(ScopedPtr* ptr); + + // `fn(const T&)' will be called with foreground instance. + // This function is not blocked by Read() and Modify() in other threads. + // Returns 0 on success, otherwise on error. + template + int Read(Fn&& fn); + + // Modify background and foreground instances. fn(T&, ...) will be called + // twice. Modify() from different threads are exclusive from each other. + // NOTE: Call same series of fn to different equivalent instances should + // result in equivalent instances, otherwise foreground and background + // instance will be inconsistent. + template + size_t Modify(Fn&& fn, Args&&... args); + + // fn(T& background, const T& foreground, ...) will be called to background + // and foreground instances respectively. + template + size_t ModifyWithForeground(Fn&& fn, Args&&... args); + +private: + const T* UnsafeRead() const { + return _data + _index.load(butil::memory_order_acquire); + } + + const T* UnsafeRead(int& index) const { + index = _index.load(butil::memory_order_acquire); + return _data + index; + } + + WrapperSharedPtr GetWrapper(); + + // Foreground and background void. + T _data[2]; + + // Index of foreground instance. + butil::atomic _index; + + // Key to access thread-local wrappers. + WrapperTLSId _wrapper_key; + + // All thread-local instances. + std::vector _wrappers; + + // Sequence access to _wrappers. + pthread_mutex_t _wrappers_mutex{}; + + // Sequence modifications. + pthread_mutex_t _modify_mutex{}; +}; + +template +class DoublyBufferedDataWrapperBase { +public: + TLS& user_tls() { return _user_tls; } +protected: + TLS _user_tls; +}; + +template +class DoublyBufferedDataWrapperBase { +}; + +// Use pthread_key store data limits by _SC_THREAD_KEYS_MAX. +// WrapperTLSGroup can store Wrapper in thread local storage. +// WrapperTLSGroup will destruct Wrapper data when thread exits, +// other times only reset Wrapper inner structure. +template +class DoublyBufferedData::WrapperTLSGroup { +public: + const static size_t RAW_BLOCK_SIZE = 4096; + const static size_t ELEMENTS_PER_BLOCK = + RAW_BLOCK_SIZE / sizeof(WrapperSharedPtr) > 0 ? + RAW_BLOCK_SIZE / sizeof(WrapperSharedPtr) : 1; + + struct BAIDU_CACHELINE_ALIGNMENT ThreadBlock { + WrapperSharedPtr at(size_t offset) { + if (NULL == _data[offset]) { + _data[offset] = std::make_shared(); + } + return _data[offset]; + }; + + private: + WrapperSharedPtr _data[ELEMENTS_PER_BLOCK]; + }; + + static WrapperTLSId key_create() { + BAIDU_SCOPED_LOCK(_s_mutex); + WrapperTLSId id = 0; + if (!_get_free_ids().empty()) { + id = _get_free_ids().back(); + _get_free_ids().pop_back(); + } else { + id = _s_id++; + } + return id; + } + + static int key_delete(WrapperTLSId id) { + BAIDU_SCOPED_LOCK(_s_mutex); + if (id < 0 || id >= _s_id) { + errno = EINVAL; + return -1; + } + _get_free_ids().push_back(id); + return 0; + } + + static WrapperSharedPtr get_or_create_tls_data(WrapperTLSId id) { + if (BAIDU_UNLIKELY(id < 0)) { + CHECK(false) << "Invalid id=" << id; + return NULL; + } + if (_s_tls_blocks == NULL) { + _s_tls_blocks = new std::vector; + butil::thread_atexit(_destroy_tls_blocks); + } + const size_t block_id = (size_t)id / ELEMENTS_PER_BLOCK; + if (block_id >= _s_tls_blocks->size()) { + // The 32ul avoid pointless small resizes. + _s_tls_blocks->resize(std::max(block_id + 1, 32ul)); + } + ThreadBlock* tb = (*_s_tls_blocks)[block_id]; + if (tb == NULL) { + tb = new ThreadBlock; + (*_s_tls_blocks)[block_id] = tb; + } + return tb->at(id - block_id * ELEMENTS_PER_BLOCK); + } + +private: + static void _destroy_tls_blocks() { + if (!_s_tls_blocks) { + return; + } + for (size_t i = 0; i < _s_tls_blocks->size(); ++i) { + delete (*_s_tls_blocks)[i]; + } + delete _s_tls_blocks; + _s_tls_blocks = NULL; + } + + inline static std::deque& _get_free_ids() { + if (BAIDU_UNLIKELY(!_s_free_ids)) { + _s_free_ids = new (std::nothrow) std::deque(); + RELEASE_ASSERT(_s_free_ids); + } + return *_s_free_ids; + } + +private: + static pthread_mutex_t _s_mutex; + static WrapperTLSId _s_id; + static std::deque* _s_free_ids; + static __thread std::vector* _s_tls_blocks; +}; + +template +pthread_mutex_t DoublyBufferedData::WrapperTLSGroup::_s_mutex = PTHREAD_MUTEX_INITIALIZER; + +template +std::deque::WrapperTLSId>* + DoublyBufferedData::WrapperTLSGroup::_s_free_ids = NULL; + +template +typename DoublyBufferedData::WrapperTLSId + DoublyBufferedData::WrapperTLSGroup::_s_id = 0; + +template +__thread std::vector::WrapperTLSGroup::ThreadBlock*>* + DoublyBufferedData::WrapperTLSGroup::_s_tls_blocks = NULL; + +template +class BAIDU_CACHELINE_ALIGNMENT DoublyBufferedData::Wrapper + : public DoublyBufferedDataWrapperBase { +friend class DoublyBufferedData; +public: + explicit Wrapper() + : _control(NULL) + , _modify_wait(false) { + pthread_mutex_init(&_mutex, NULL); + if (AllowBthreadSuspended) { + pthread_cond_init(&_cond[0], NULL); + pthread_cond_init(&_cond[1], NULL); + } + } + + ~Wrapper() { + if (AllowBthreadSuspended) { + WaitReadDone(0); + WaitReadDone(1); + + pthread_cond_destroy(&_cond[0]); + pthread_cond_destroy(&_cond[1]); + } + + pthread_mutex_destroy(&_mutex); + } + + // _mutex will be locked by the calling pthread and DoublyBufferedData. + // Most of the time, no modifications are done, so the mutex is + // uncontended and fast. + inline void BeginRead() { + pthread_mutex_lock(&_mutex); + } + + // For `AllowBthreadSuspended=true'. + inline void BeginReadRelease() { + pthread_mutex_unlock(&_mutex); + } + + inline void EndRead() { + pthread_mutex_unlock(&_mutex); + } + + // For `AllowBthreadSuspended=true'. + // Thread-local reference count which be protected by _mutex + // will be decremented by one. + inline void EndRead(int index) { + BAIDU_SCOPED_LOCK(_mutex); + SubRef(index); + SignalReadCond(index); + } + + inline void WaitReadDone() { + BAIDU_SCOPED_LOCK(_mutex); + } + + // For `AllowBthreadSuspended=true'. + // Wait until all read of foreground instance done. + inline void WaitReadDone(int index) { + BAIDU_SCOPED_LOCK(_mutex); + int& ref = index == 0 ? _ref[0] : _ref[1]; + while (ref != 0) { + _modify_wait = true; + pthread_cond_wait(&_cond[index], &_mutex); + } + _modify_wait = false; + } + + // For `AllowBthreadSuspended=true'. + inline void SignalReadCond(int index) { + if (_ref[index] == 0 && _modify_wait) { + pthread_cond_signal(&_cond[index]); + } + } + + // For `AllowBthreadSuspended=true'. + void AddRef(int index) { + ++_ref[index]; + } + + // For `AllowBthreadSuspended=true'. + void SubRef(int index) { + --_ref[index]; + } + +private: + DoublyBufferedData* _control; + pthread_mutex_t _mutex{}; + // For `AllowBthreadSuspended=true'. + // _cond[0] for _ref[0], _cond[1] for _ref[1] + pthread_cond_t _cond[2]{}; + // For `AllowBthreadSuspended=true'. + // _ref[0] is reference count for _data[0], + // _ref[1] is reference count for _data[1]. + int _ref[2]{0, 0}; + // For `AllowBthreadSuspended=true'. + // Whether there is a Modify() waiting for _ref0/_ref1. + bool _modify_wait; +}; + +// Called when thread initializes thread-local wrapper. +template +typename DoublyBufferedData::WrapperSharedPtr +DoublyBufferedData::GetWrapper() { + WrapperSharedPtr w = WrapperTLSGroup::get_or_create_tls_data(_wrapper_key); + if (NULL == w) { + return NULL; + } + if (w->_control == this) { + return w; + } + if (w->_control != NULL) { + LOG(FATAL) << "Get wrapper from tls but control != this"; + return NULL; + } + try { + w->_control = this; + BAIDU_SCOPED_LOCK(_wrappers_mutex); + _wrappers.push_back(w); + // The chance to remove expired weak_ptr. + _wrappers.erase( + std::remove_if(_wrappers.begin(), _wrappers.end(), + [](const WrapperWeakPtr& w) { + return w.expired(); + }), + _wrappers.end()); + } catch (std::exception& e) { + return NULL; + } + return w; +} + +template +DoublyBufferedData::DoublyBufferedData() + : _index(0) + , _wrapper_key(0) { + BAIDU_CASSERT(!(AllowBthreadSuspended && !IsVoid::value), + "Forbidden to allow bthread suspended with non-Void TLS"); + + _wrappers.reserve(64); + pthread_mutex_init(&_modify_mutex, NULL); + pthread_mutex_init(&_wrappers_mutex, NULL); + _wrapper_key = WrapperTLSGroup::key_create(); + // Initialize _data for some POD types. This is essential for pointer + // types because they should be Read() as NULL before any Modify(). + if (is_integral::value || is_floating_point::value || + is_pointer::value || is_member_function_pointer::value) { + _data[0] = T(); + _data[1] = T(); + } +} + +template +DoublyBufferedData::~DoublyBufferedData() { + // User is responsible for synchronizations between Read()/Modify() and + // this function. + + { + BAIDU_SCOPED_LOCK(_wrappers_mutex); + for (size_t i = 0; i < _wrappers.size(); ++i) { + WrapperSharedPtr w = _wrappers[i].lock(); + if (NULL != w) { + w->_control = NULL; // hack: disable removal. + } + } + _wrappers.clear(); + } + WrapperTLSGroup::key_delete(_wrapper_key); + _wrapper_key = -1; + pthread_mutex_destroy(&_modify_mutex); + pthread_mutex_destroy(&_wrappers_mutex); +} + +template +int DoublyBufferedData::Read( + typename DoublyBufferedData::ScopedPtr* ptr) { + WrapperSharedPtr w = GetWrapper(); + if (BAIDU_UNLIKELY(w == NULL)) { + return -1; + } + + if (AllowBthreadSuspended) { + // Use reference count instead of mutex to indicate read of + // foreground instance, so during the read process, there is + // no need to lock mutex and bthread is allowed to be suspended. + w->BeginRead(); + // UnsafeRead will update ptr->_index + ptr->_data = UnsafeRead(ptr->_index); + w->AddRef(ptr->_index); + w->BeginReadRelease(); + } else { + w->BeginRead(); + ptr->_data = UnsafeRead(); + } + ptr->_w.swap(w); + return 0; +} + +template +template +int DoublyBufferedData::Read(Fn&& fn) { + BAIDU_CASSERT((is_result_void::value), + "Fn must accept `const T&' and return void"); + ScopedPtr ptr; + if (Read(&ptr) != 0) { + return -1; + } + fn(*ptr); + return 0; +} + +template +template +size_t DoublyBufferedData::Modify(Fn&& fn, Args&&... args) { + // _modify_mutex sequences modifications. Using a separate mutex rather + // than _wrappers_mutex is to avoid blocking threads calling + // GetWrapper() too long. Most of the time, modifications + // are done by one thread, contention should be negligible. + BAIDU_SCOPED_LOCK(_modify_mutex); + int bg_index = !_index.load(butil::memory_order_relaxed); + // background instance is not accessed by other threads, being safe to + // modify. + const size_t ret = fn(_data[bg_index], std::forward(args)...); + if (!ret) { + return 0; + } + + // Publish, flip background and foreground. + // The release fence matches with the acquire fence in UnsafeRead() to + // make readers which just begin to read the new foreground instance see + // all changes made in fn. + _index.store(bg_index, butil::memory_order_release); + bg_index = !bg_index; + + // Wait until all threads finishes current reading. When they begin next + // read, they should see updated _index. + { + BAIDU_SCOPED_LOCK(_wrappers_mutex); + // The chance to remove expired weak_ptr. + _wrappers.erase( + std::remove_if(_wrappers.begin(), _wrappers.end(), + [bg_index](const WrapperWeakPtr& weak) { + WrapperSharedPtr w = weak.lock(); + bool expired = NULL == w; + if (!expired) { + // Notify all threads waiting for read done. + if (AllowBthreadSuspended) { + w->WaitReadDone(bg_index); + } else { + w->WaitReadDone(); + } + } + // Remove expired weak_ptr. + return expired; + }), + _wrappers.end()); + } + + const size_t ret2 = fn(_data[bg_index], std::forward(args)...); + CHECK_EQ(ret2, ret) << "index=" << _index.load(butil::memory_order_relaxed); + return ret2; +} + +template +template +size_t DoublyBufferedData::ModifyWithForeground(Fn&& fn, Args&&... args) { + return Modify([this, &fn](T& bg, Args&&... args) { + return fn(bg, (const T&)_data[&bg == _data], std::forward(args)...); + }, std::forward(args)...); +} + +} // namespace butil + +#endif // BUTIL_DOUBLY_BUFFERED_DATA_H diff --git a/src/butil/containers/flat_map.h b/src/butil/containers/flat_map.h new file mode 100644 index 0000000..54981b8 --- /dev/null +++ b/src/butil/containers/flat_map.h @@ -0,0 +1,640 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Wed Nov 27 12:59:20 CST 2013 + +// This closed addressing hash-map puts first linked node in bucket array +// directly to save an extra memory indirection. As a result, this map yields +// close performance to raw array on nearly all operations, probably being the +// fastest hashmap for small-sized key/value ever. +// +// Performance comparisons between several maps: +// [ value = 8 bytes ] +// Sequentially inserting 100 into FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 20/20/300/290/2010/210/230 +// Sequentially erasing 100 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 20/20/1700/150/160/170/250 +// Sequentially inserting 1000 into FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 16/15/360/342/206/195/219 +// Sequentially erasing 1000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 15/18/178/159/149/159/149 +// Sequentially inserting 10000 into FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 15/15/415/410/200/192/235 +// Sequentially erasing 10000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 14/17/201/181/151/181/154 +// [ value = 32 bytes ] +// Sequentially inserting 100 into FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 20/10/280/280/250/200/230 +// Sequentially erasing 100 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 20/10/2070/150/160/160/160 +// Sequentially inserting 1000 into FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 17/17/330/329/207/185/212 +// Sequentially erasing 1000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 13/17/172/163/146/157/148 +// Sequentially inserting 10000 into FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 17/17/405/406/197/185/215 +// Sequentially erasing 10000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 13/16/206/188/158/168/159 +// [ value = 128 bytes ] +// Sequentially inserting 100 into FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 20/30/290/290/420/220/250 +// Sequentially erasing 100 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 20/10/180/150/160/160/160 +// Sequentially inserting 1000 into FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 22/25/352/349/213/193/222 +// Sequentially erasing 1000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 13/17/170/165/160/171/157 +// Sequentially inserting 10000 into FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 21/24/416/422/210/206/242 +// Sequentially erasing 10000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 16/16/213/190/163/171/159 +// [ value = 8 bytes ] +// Randomly inserting 100 into FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 20/20/290/260/250/220/220 +// Randomly erasing 100 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 20/20/240/220/170/170/180 +// Randomly inserting 1000 into FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 16/15/315/309/206/191/215 +// Randomly erasing 1000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 15/17/258/240/155/193/156 +// Randomly inserting 10000 into FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 15/16/378/363/208/191/210 +// Randomly erasing 10000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 14/16/311/290/162/187/169 +// [ value = 32 bytes ] +// Randomly inserting 100 into FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 20/20/280/270/240/230/220 +// Randomly erasing 100 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 10/20/250/220/170/180/160 +// Randomly inserting 1000 into FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 18/18/310/304/209/192/209 +// Randomly erasing 1000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 13/16/255/247/155/175/152 +// Randomly inserting 10000 into FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 17/17/381/367/209/197/214 +// Randomly erasing 10000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 15/17/310/296/163/188/168 +// [ value = 128 bytes ] +// Randomly inserting 100 into FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 30/40/300/290/280/230/230 +// Randomly erasing 100 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 20/20/230/220/160/180/170 +// Randomly inserting 1000 into FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 29/33/327/329/219/197/220 +// Randomly erasing 1000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 14/16/257/247/159/182/156 +// Randomly inserting 10000 into FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 35/39/398/400/220/213/246 +// Randomly erasing 10000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 35/36/330/319/221/224/200 +// [ value = 8 bytes ] +// Seeking 100 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 10/20/140/130/60/70/50 +// Seeking 1000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 13/13/172/161/77/54/46 +// Seeking 10000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 13/16/216/211/73/56/51 +// [ value = 32 bytes ] +// Seeking 100 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 10/20/130/130/70/60/50 +// Seeking 1000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 13/13/174/163/73/54/49 +// Seeking 10000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 13/13/218/217/75/58/52 +// [ value = 128 bytes ] +// Seeking 100 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 20/10/140/130/80/50/60 +// Seeking 1000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 13/13/173/171/73/55/49 +// Seeking 10000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 26/22/238/252/107/89/91 +// [ value = 8 bytes ] +// Seeking 100 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 20/10/140/130/70/50/60 +// Seeking 1000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 13/14/180/160/68/57/47 +// Seeking 10000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 13/13/221/210/72/57/51 +// [ value = 32 bytes ] +// Seeking 100 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 20/10/140/130/70/60/50 +// Seeking 1000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 13/13/167/160/69/53/50 +// Seeking 10000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 15/14/224/219/75/59/52 +// [ value = 128 bytes ] +// Seeking 100 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 10/10/140/130/80/50/60 +// Seeking 1000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 13/13/170/167/70/54/52 +// Seeking 10000 from FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map takes 26/22/238/240/85/70/67 + +#ifndef BUTIL_FLAT_MAP_H +#define BUTIL_FLAT_MAP_H + +#include +#include +#include +#include // std::ostream +#include // std::aligned_storage +#include "butil/type_traits.h" +#include "butil/logging.h" +#include "butil/find_cstr.h" +#include "butil/single_threaded_pool.h" // SingleThreadedPool +#include "butil/containers/hash_tables.h" // hash<> +#include "butil/bit_array.h" // bit_array_* +#include "butil/strings/string_piece.h" // StringPiece +#include "butil/memory/scope_guard.h" +#include "butil/containers/optional.h" + +namespace butil { + +template class FlatMapIterator; +template class SparseFlatMapIterator; +template class FlatMapElement; +struct FlatMapVoid {}; // Replace void which is not constructible. +template struct DefaultHasher; +template struct DefaultEqualTo; + +struct BucketInfo { + size_t longest_length; + double average_length; +}; + +#ifndef BRPC_FLATMAP_DEFAULT_NBUCKET +#ifdef FLAT_MAP_ROUND_BUCKET_BY_USE_NEXT_PRIME +#define BRPC_FLATMAP_DEFAULT_NBUCKET 29U +#else +#define BRPC_FLATMAP_DEFAULT_NBUCKET 16U +#endif +#endif // BRPC_FLATMAP_DEFAULT_NBUCKET + +// NOTE: Objects stored in FlatMap MUST be copyable. +template , + // Test equivalence between stored-key and passed-key. + // stored-key is always on LHS, passed-key is always on RHS. + typename _Equal = DefaultEqualTo<_K>, + bool _Sparse = false, + typename _Alloc = PtAllocator, + // If `_Multi' is true, allow containing multiple copies of each key value. + bool _Multi = false> +class FlatMap { +public: + typedef _K key_type; + typedef _T mapped_type; + typedef _Alloc allocator_type; + typedef FlatMapElement<_K, _T> Element; + typedef typename Element::value_type value_type; + typedef typename conditional< + _Sparse, SparseFlatMapIterator, + FlatMapIterator >::type iterator; + typedef typename conditional< + _Sparse, SparseFlatMapIterator, + FlatMapIterator >::type const_iterator; + typedef _Hash hasher; + typedef _Equal key_equal; + static constexpr size_t DEFAULT_NBUCKET = BRPC_FLATMAP_DEFAULT_NBUCKET; + + struct PositionHint { + size_t nbucket; + size_t offset; + bool at_entry; + key_type key; + }; + + explicit FlatMap(const hasher& hashfn = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& alloc = allocator_type()); + FlatMap(const FlatMap& rhs); + ~FlatMap(); + + FlatMap& operator=(const FlatMap& rhs); + void swap(FlatMap & rhs); + + // FlatMap will be automatically initialized with small FlatMap optimization, + // so this function only needs to be call when a large initial number of + // buckets or non-default `load_factor' is required. + // Returns 0 on success, -1 on error, but FlatMap can still be used normally. + // `nbucket' is the initial number of buckets. `load_factor' is the + // maximum value of size()*100/nbucket, if the value is reached, nbucket + // will be doubled and all items stored will be rehashed which is costly. + // Choosing proper values for these 2 parameters reduces costs. + int init(size_t nbucket, u_int load_factor = 80); + + // Insert a pair of |key| and |value|. If size()*100/bucket_count() is + // more than load_factor(), a resize() will be done. + // Returns address of the inserted value, NULL on error. + mapped_type* insert(const key_type& key, const mapped_type& value); + + // Insert a pair of {key, value}. If size()*100/bucket_count() is + // more than load_factor(), a resize() will be done. + // Returns address of the inserted value, NULL on error. + mapped_type* insert(const std::pair& kv); + + // For `_Multi=false'. (Default) + // Remove |key| and all associated value + // Returns: 1 on erased, 0 otherwise. + template + typename std::enable_if::type + erase(const K2& key, mapped_type* old_value = NULL); + // For `_Multi=true'. + // Returns: num of value on erased, 0 otherwise. + template + typename std::enable_if::type + erase(const K2& key, std::vector* old_values = NULL); + + // Remove all items. Allocated spaces are NOT returned by system. + void clear(); + + // Remove all items and return all allocated spaces to system. + void clear_and_reset_pool(); + + // Search for the value associated with |key|. + // If `_Multi=false', Search for any of multiple values associated with |key|. + // Returns: address of the value. + template mapped_type* seek(const K2& key) const; + template std::vector seek_all(const K2& key) const; + + // For `_Multi=false'. (Default) + // Get the value associated with |key|. If |key| does not exist, + // insert with a default-constructed value. If size()*100/bucket_count() + // is more than load_factor, a resize will be done. + // Returns reference of the value + template + typename std::enable_if::type operator[](const key_type& key); + + // Resize this map. This is optional because resizing will be triggered by + // insert() or operator[] if there're too many items. + // Returns successful or not. + bool resize(size_t nbucket); + + // Iterators + iterator begin(); + iterator end(); + const_iterator begin() const; + const_iterator end() const; + + // Iterate FlatMap inconsistently in more-than-one passes. This is used + // in multi-threaded environment to divide the critical sections of + // iterating big maps into smaller ones. "inconsistently" means that: + // * elements added during iteration may be missed. + // * some elements may be iterated more than once. + // * iteration is restarted at beginning when the map is resized. + // Example: (copying all keys in multi-threaded environment) + // LOCK; + // size_t n = 0; + // for (Map::const_iterator it = map.begin(); it != map.end(); ++it) { + // if (++n >= 256/*max iterated one pass*/) { + // Map::PositionHint hint; + // map.save_iterator(it, &hint); + // n = 0; + // UNLOCK; + // LOCK; + // it = map.restore_iterator(hint); + // if (it == map.begin()) { // resized + // keys->clear(); + // } + // if (it == map.end()) { + // break; + // } + // } + // keys->push_back(it->first); + // } + // UNLOCK; + void save_iterator(const const_iterator&, PositionHint*) const; + const_iterator restore_iterator(const PositionHint&) const; + + // Always returns true. + bool initialized() const { return _buckets != NULL; } + + bool empty() const { return _size == 0; } + size_t size() const { return _size; } + size_t bucket_count() const { return _nbucket; } + u_int load_factor () const { return _load_factor; } + + // Returns #nodes of longest bucket in this map. This scans all buckets. + BucketInfo bucket_info() const; + + struct Bucket { + Bucket() : next((Bucket*)-1UL) {} + explicit Bucket(const _K& k) : next(NULL) { + element_space_.Init(k); + } + Bucket(const Bucket& other) : next(NULL) { + element_space_.Init(other.element()); + } + + bool is_valid() const { return next != (const Bucket*)-1UL; } + void set_invalid() { next = (Bucket*)-1UL; } + // NOTE: Only be called when is_valid() is true. + Element& element() { return *element_space_; } + const Element& element() const { return *element_space_; } + void destroy_element() { element_space_.Destroy(); } + + void swap(Bucket& rhs) { + if (!is_valid() && !rhs.is_valid()) { + return; + } else if (is_valid() && !rhs.is_valid()) { + rhs.element_space_.Init(std::move(element())); + destroy_element(); + } else if (!is_valid() && rhs.is_valid()) { + element_space_.Init(std::move(rhs.element())); + rhs.destroy_element(); + } else { + element().swap(rhs.element()); + } + std::swap(next, rhs.next); + } + + Bucket* next; + + private: + ManualConstructor element_space_; + }; + +private: +template friend class FlatMapIterator; +template friend class SparseFlatMapIterator; + + struct NewBucketsInfo { + NewBucketsInfo() + : buckets(NULL), thumbnail(NULL), nbucket(0) {} + NewBucketsInfo(Bucket* b, uint64_t* t, size_t n) + : buckets(b), thumbnail(t), nbucket(n) {} + + Bucket* buckets; + uint64_t* thumbnail; + size_t nbucket; + }; + + optional new_buckets_and_thumbnail(size_t size, size_t new_nbucket); + + // For `_Multi=true'. + // Insert a new default-constructed associated with |key| always. + // If size()*100/bucket_count() is more than load_factor, + // a resize will be done. + // Returns reference of the value + template + typename std::enable_if::type operator[](const key_type& key); + + allocator_type& get_allocator() { return _pool.get_allocator(); } + allocator_type get_allocator() const { return _pool.get_allocator(); } + + // True if buckets need to be resized before holding `size' elements. + bool is_too_crowded(size_t size) const { + return is_too_crowded(size, _nbucket, _load_factor); + } + static bool is_too_crowded(size_t size, size_t nbucket, u_int load_factor) { + return size * 100 >= nbucket * load_factor; + } + + void init_load_factor(u_int load_factor) { + if (_is_default_load_factor) { + _is_default_load_factor = false; + _load_factor = load_factor; + } + } + + // True if using default buckets. + bool is_default_buckets() const { + return _buckets == (Bucket*)(&_default_buckets); + } + + static void init_buckets_and_thumbnail(Bucket* buckets, + uint64_t* thumbnail, + size_t nbucket) { + for (size_t i = 0; i < nbucket; ++i) { + buckets[i].set_invalid(); + } + buckets[nbucket].next = NULL; + if (_Sparse) { + bit_array_clear(thumbnail, nbucket); + } + } + + static const size_t default_nthumbnail = BIT_ARRAY_LEN(DEFAULT_NBUCKET); + // Note: need an extra bucket to let iterator know where buckets end. + // Small map optimization. + Bucket _default_buckets[DEFAULT_NBUCKET + 1]; + uint64_t _default_thumbnail[default_nthumbnail]; + size_t _size; + size_t _nbucket; + Bucket* _buckets; + uint64_t* _thumbnail; + u_int _load_factor; + bool _is_default_load_factor; + hasher _hashfn; + key_equal _eql; + SingleThreadedPool _pool; +}; + +template , + typename _Equal = DefaultEqualTo<_K>, + bool _Sparse = false, + typename _Alloc = PtAllocator> +using MultiFlatMap = FlatMap< + _K, _T, _Hash, _Equal, _Sparse, _Alloc, true>; + +template , + typename _Equal = DefaultEqualTo<_K>, + bool _Sparse = false, + typename _Alloc = PtAllocator> +class FlatSet { +public: + typedef FlatMap<_K, FlatMapVoid, _Hash, _Equal, _Sparse, + _Alloc, false> Map; + typedef typename Map::key_type key_type; + typedef typename Map::value_type value_type; + typedef typename Map::Bucket Bucket; + typedef typename Map::iterator iterator; + typedef typename Map::const_iterator const_iterator; + typedef typename Map::hasher hasher; + typedef typename Map::key_equal key_equal; + typedef typename Map::allocator_type allocator_type; + + explicit FlatSet(const hasher& hashfn = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& alloc = allocator_type()) + : _map(hashfn, eql, alloc) {} + void swap(FlatSet & rhs) { _map.swap(rhs._map); } + + int init(size_t nbucket, u_int load_factor = 80) + { return _map.init(nbucket, load_factor); } + + const void* insert(const key_type& key) + { return _map.insert(key, FlatMapVoid()); } + + template + size_t erase(const K2& key) { return _map.erase(key, NULL); } + + void clear() { return _map.clear(); } + void clear_and_reset_pool() { return _map.clear_and_reset_pool(); } + + template + const void* seek(const K2& key) const { return _map.seek(key); } + + bool resize(size_t nbucket) { return _map.resize(nbucket); } + + iterator begin() { return _map.begin(); } + iterator end() { return _map.end(); } + const_iterator begin() const { return _map.begin(); } + const_iterator end() const { return _map.end(); } + + bool initialized() const { return _map.initialized(); } + bool empty() const { return _map.empty(); } + size_t size() const { return _map.size(); } + size_t bucket_count() const { return _map.bucket_count(); } + u_int load_factor () const { return _map.load_factor(); } + BucketInfo bucket_info() const { return _map.bucket_info(); } + +private: + Map _map; +}; + +template , + typename _Equal = DefaultEqualTo<_K>, + typename _Alloc = PtAllocator, + bool _Multi = false> +class SparseFlatMap : public FlatMap< + _K, _T, _Hash, _Equal, true, _Alloc, _Multi> {}; + +template , + typename _Equal = DefaultEqualTo<_K>, + typename _Alloc = PtAllocator> +class SparseFlatSet : public FlatSet< + _K, _Hash, _Equal, true, _Alloc> {}; + +// Implement FlatMapElement +template +class FlatMapElement { +public: + typedef std::pair value_type; + // NOTE: Have to initialize _value in this way which is treated by GCC + // specially that _value is zeroized(POD) or constructed(non-POD). Other + // methods do not work. For example, if we put _value into the std::pair + // and do initialization by calling _pair(k, T()), _value will be copy + // constructed from the defaultly constructed instance(not zeroized for + // POD) which is wrong generally. + explicit FlatMapElement(const K& k) : _key(k), _value(T()) {} + // ^^^^^^^^^^^ + + FlatMapElement(const FlatMapElement& rhs) + : _key(rhs._key), _value(rhs._value) {} + + FlatMapElement(FlatMapElement&& rhs) noexcept + : _key(std::move(rhs._key)), _value(std::move(rhs._value)) {} + + const K& first_ref() const { return _key; } + T& second_ref() { return _value; } + T&& second_movable_ref() { return std::move(_value); } + value_type& value_ref() { return *reinterpret_cast(this); } + inline static const K& first_ref_from_value(const value_type& v) + { return v.first; } + inline static const T& second_ref_from_value(const value_type& v) + { return v.second; } + inline static T&& second_movable_ref_from_value(value_type& v) + { return std::move(v.second); } + + void swap(FlatMapElement& rhs) { + std::swap(_key, rhs._key); + std::swap(_value, rhs._value); + } + +private: + K _key; + T _value; +}; + +template +class FlatMapElement { +public: + typedef const K value_type; + // See the comment in the above FlatMapElement. + explicit FlatMapElement(const K& k) : _key(k) {} + FlatMapElement(const FlatMapElement& rhs) : _key(rhs._key) {} + FlatMapElement(FlatMapElement&& rhs) noexcept : _key(std::move(rhs._key)) {} + + const K& first_ref() const { return _key; } + FlatMapVoid& second_ref() { return second_ref_from_value(_key); } + FlatMapVoid& second_movable_ref() { return second_ref(); } + value_type& value_ref() { return _key; } + inline static const K& first_ref_from_value(value_type& v) { return v; } + inline static FlatMapVoid& second_ref_from_value(value_type&) { + static FlatMapVoid dummy; + return dummy; + } + inline static const FlatMapVoid& second_movable_ref_from_value(value_type& v) { + return second_ref_from_value(v); + } +private: + K _key; +}; + +// Implement DefaultHasher and DefaultEqualTo +template +struct DefaultHasher : public BUTIL_HASH_NAMESPACE::hash {}; + +template <> +struct DefaultHasher { + std::size_t operator()(const butil::StringPiece& s) const { + std::size_t result = 0; + for (butil::StringPiece::const_iterator + i = s.begin(); i != s.end(); ++i) { + result = result * 101 + *i; + } + return result; + } + std::size_t operator()(const char* s) const { + std::size_t result = 0; + for (; *s; ++s) { + result = result * 101 + *s; + } + return result; + } + std::size_t operator()(const std::string& s) const { + std::size_t result = 0; + for (std::string::const_iterator i = s.begin(); i != s.end(); ++i) { + result = result * 101 + *i; + } + return result; + } +}; + +template +struct DefaultEqualTo : public std::equal_to { +}; + +template <> +struct DefaultEqualTo { + bool operator()(const std::string& s1, const std::string& s2) const + { return s1 == s2; } + bool operator()(const std::string& s1, const butil::StringPiece& s2) const + { return s1 == s2; } + bool operator()(const std::string& s1, const char* s2) const + { return s1 == s2; } +}; + +// find_cstr and find_lowered_cstr +template +const _T* find_cstr(const FlatMap& m, + const char* key) { + return m.seek(key); +} + +template +_T* find_cstr(FlatMap& m, + const char* key) { + return m.seek(key); +} + +template +const _T* find_cstr(const FlatMap& m, + const char* key, size_t length) { + return m.seek(butil::StringPiece(key, length)); +} + +template +_T* find_cstr(FlatMap& m, + const char* key, size_t length) { + return m.seek(butil::StringPiece(key, length)); +} + +template +const _T* find_lowered_cstr( + const FlatMap& m, + const char* key) { + return m.seek(*tls_stringmap_temp.get_lowered_string(key)); +} + +template +_T* find_lowered_cstr(FlatMap& m, + const char* key) { + return m.seek(*tls_stringmap_temp.get_lowered_string(key)); +} + +template +const _T* find_lowered_cstr( + const FlatMap& m, + const char* key, size_t length) { + return m.seek(*tls_stringmap_temp.get_lowered_string(key, length)); +} + +template +_T* find_lowered_cstr(FlatMap& m, + const char* key, size_t length) { + return m.seek(*tls_stringmap_temp.get_lowered_string(key, length)); +} + +} // namespace butil + +#include "butil/containers/flat_map_inl.h" + +#endif //BUTIL_FLAT_MAP_H diff --git a/src/butil/containers/flat_map_inl.h b/src/butil/containers/flat_map_inl.h new file mode 100644 index 0000000..93bcbf9 --- /dev/null +++ b/src/butil/containers/flat_map_inl.h @@ -0,0 +1,847 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Wed Nov 27 12:59:20 CST 2013 + +#ifndef BUTIL_FLAT_MAP_INL_H +#define BUTIL_FLAT_MAP_INL_H + +namespace butil { + +inline uint32_t find_next_prime(uint32_t nbucket) { + static const unsigned long prime_list[] = { + 29ul, + 53ul, 97ul, 193ul, 389ul, 769ul, + 1543ul, 3079ul, 6151ul, 12289ul, 24593ul, + 49157ul, 98317ul, 196613ul, 393241ul, 786433ul, + 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul, + 50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul, + 1610612741ul, 3221225473ul, 4294967291ul + }; + const size_t nprimes = sizeof(prime_list) / sizeof(prime_list[0]); + for (size_t i = 0; i < nprimes; i++) { + if (nbucket <= prime_list[i]) { + return prime_list[i]; + } + } + return nbucket; +} + +// NOTE: find_power2(0) = 0 +inline uint64_t find_power2(uint64_t b) { + b -= 1; + b |= (b >> 1); + b |= (b >> 2); + b |= (b >> 4); + b |= (b >> 8); + b |= (b >> 16); + b |= (b >> 32); + return b + 1; +} + +// Using next prime is slower for 10ns on average (due to %). If quality of +// the hash code is good enough, primeness of nbucket is not important. We +// choose to trust the hash code (or user should use a better hash algorithm +// when the collisions are significant) and still stick to round-to-power-2 +// solution right now. +inline size_t flatmap_round(size_t nbucket) { +#ifdef FLAT_MAP_ROUND_BUCKET_BY_USE_NEXT_PRIME + return find_next_prime(nbucket); +#else + // the lowerbound fixes the corner case of nbucket=0 which results in coredump during seeking the map. + return nbucket <= 8 ? 8 : find_power2(nbucket); +#endif +} + +inline size_t flatmap_mod(size_t hash_code, size_t nbucket) { +#ifdef FLAT_MAP_ROUND_BUCKET_BY_USE_NEXT_PRIME + return hash_code % nbucket; +#else + return hash_code & (nbucket - 1); +#endif +} + +// Iterate FlatMap +template class FlatMapIterator { +public: + typedef Value value_type; + typedef Value& reference; + typedef Value* pointer; + typedef typename add_const::type ConstValue; + typedef ConstValue& const_reference; + typedef ConstValue* const_pointer; + typedef std::forward_iterator_tag iterator_category; + typedef ptrdiff_t difference_type; + typedef typename remove_const::type NonConstValue; + + FlatMapIterator() : _node(NULL), _entry(NULL) {} + FlatMapIterator(const Map* map, size_t pos) { + _entry = map->_buckets + pos; + find_and_set_valid_node(); + } + FlatMapIterator(const FlatMapIterator& rhs) + : _node(rhs._node), _entry(rhs._entry) {} + ~FlatMapIterator() = default; // required by style-checker + + // *this == rhs + bool operator==(const FlatMapIterator& rhs) const + { return _node == rhs._node; } + + // *this != rhs + bool operator!=(const FlatMapIterator& rhs) const + { return _node != rhs._node; } + + // ++ it + FlatMapIterator& operator++() { + if (NULL == _node->next) { + ++_entry; + find_and_set_valid_node(); + } else { + _node = _node->next; + } + return *this; + } + + // it ++ + FlatMapIterator operator++(int) { + FlatMapIterator tmp = *this; + this->operator++(); + return tmp; + } + + reference operator*() { return _node->element().value_ref(); } + pointer operator->() { return &_node->element().value_ref(); } + const_reference operator*() const { return _node->element().value_ref(); } + const_pointer operator->() const { return &_node->element().value_ref(); } + +private: +friend class FlatMapIterator; +friend class FlatMap; + + void find_and_set_valid_node() { + for (; !_entry->is_valid(); ++_entry); + _node = _entry; + } + + typename Map::Bucket* _node; + typename Map::Bucket* _entry; +}; + +// Iterate SparseFlatMap +template class SparseFlatMapIterator { +public: + typedef Value value_type; + typedef Value& reference; + typedef Value* pointer; + typedef typename add_const::type ConstValue; + typedef ConstValue& const_reference; + typedef ConstValue* const_pointer; + typedef std::forward_iterator_tag iterator_category; + typedef ptrdiff_t difference_type; + typedef typename remove_const::type NonConstValue; + + SparseFlatMapIterator() : _node(NULL), _pos(0), _map(NULL) {} + SparseFlatMapIterator(const Map* map, size_t pos) { + _map = map; + _pos = pos; + find_and_set_valid_node(); + } + SparseFlatMapIterator(const SparseFlatMapIterator& rhs) + : _node(rhs._node), _pos(rhs._pos), _map(rhs._map) + {} + ~SparseFlatMapIterator() = default; // required by style-checker + + // *this == rhs + bool operator==(const SparseFlatMapIterator& rhs) const + { return _node == rhs._node; } + + // *this != rhs + bool operator!=(const SparseFlatMapIterator& rhs) const + { return _node != rhs._node; } + + // ++ it + SparseFlatMapIterator& operator++() { + if (NULL == _node->next) { + ++_pos; + find_and_set_valid_node(); + } else { + _node = _node->next; + } + return *this; + } + + // it ++ + SparseFlatMapIterator operator++(int) { + SparseFlatMapIterator tmp = *this; + this->operator++(); + return tmp; + } + + reference operator*() { return _node->element().value_ref(); } + pointer operator->() { return &_node->element().value_ref(); } + const_reference operator*() const { return _node->element().value_ref(); } + const_pointer operator->() const { return &_node->element().value_ref(); } + +private: +friend class SparseFlatMapIterator; + + void find_and_set_valid_node() { + if (!_map->_buckets[_pos].is_valid()) { + _pos = bit_array_first1(_map->_thumbnail, _pos + 1, _map->_nbucket); + } + _node = _map->_buckets + _pos; + } + + typename Map::Bucket* _node; + size_t _pos; + const Map* _map; +}; + +template +FlatMap<_K, _T, _H, _E, _S, _A, _M>::FlatMap(const hasher& hashfn, + const key_equal& eql, + const allocator_type& alloc) + : _size(0) + , _nbucket(DEFAULT_NBUCKET) + , _buckets((Bucket*)(&_default_buckets)) + , _thumbnail(_S ? _default_thumbnail : NULL) + , _load_factor(80) + , _is_default_load_factor(true) + , _hashfn(hashfn) + , _eql(eql) + , _pool(alloc) { + init_buckets_and_thumbnail(_buckets, _thumbnail, _nbucket); +} + +template +FlatMap<_K, _T, _H, _E, _S, _A, _M>::FlatMap(const FlatMap& rhs) + : FlatMap(rhs._hashfn, rhs._eql, rhs.get_allocator()) { + init_buckets_and_thumbnail(_buckets, _thumbnail, _nbucket); + if (!rhs.empty()) { + operator=(rhs); + } +} + +template +FlatMap<_K, _T, _H, _E, _S, _A, _M>::~FlatMap() { + clear(); + if (!is_default_buckets()) { + get_allocator().Free(_buckets); + _buckets = NULL; + bit_array_free(_thumbnail); + _thumbnail = NULL; + } + _nbucket = 0; + _load_factor = 0; +} + +template +FlatMap<_K, _T, _H, _E, _S, _A, _M>& +FlatMap<_K, _T, _H, _E, _S, _A, _M>::operator=( + const FlatMap<_K, _T, _H, _E, _S, _A, _M>& rhs) { + if (this == &rhs) { + return *this; + } + + clear(); + if (rhs.empty()) { + return *this; + } + // NOTE: assignment only changes _load_factor when it is default. + init_load_factor(rhs._load_factor); + if (is_too_crowded(rhs._size)) { + optional info = + new_buckets_and_thumbnail(rhs._size, rhs._nbucket); + if (info.has_value()) { + _nbucket = info->nbucket; + if (!is_default_buckets()) { + get_allocator().Free(_buckets); + if (_S) { + bit_array_free(_thumbnail); + } + } + _buckets = info->buckets; + _thumbnail = info->thumbnail; + } + // Failed new of buckets or thumbnail is OK. + // Use old buckets and thumbnail even if map will be crowded. + } + if (_nbucket == rhs._nbucket) { + // For equivalent _nbucket, walking through _buckets instead of using + // iterators is more efficient. + for (size_t i = 0; i < rhs._nbucket; ++i) { + if (rhs._buckets[i].is_valid()) { + if (_S) { + bit_array_set(_thumbnail, i); + } + new (&_buckets[i]) Bucket(rhs._buckets[i]); + Bucket* p1 = &_buckets[i]; + Bucket* p2 = rhs._buckets[i].next; + while (p2) { + p1->next = new (_pool.get()) Bucket(*p2); + p1 = p1->next; + p2 = p2->next; + } + } + } + _buckets[rhs._nbucket].next = NULL; + _size = rhs._size; + } else { + for (const_iterator it = rhs.begin(); it != rhs.end(); ++it) { + operator[](Element::first_ref_from_value(*it)) = + Element::second_ref_from_value(*it); + } + } + return *this; +} + +template +int FlatMap<_K, _T, _H, _E, _S, _A, _M>::init(size_t nbucket, u_int load_factor) { + if (nbucket <= _nbucket || load_factor < 10 || load_factor > 100 || + !_is_default_load_factor || !empty() || !is_default_buckets()) { + return 0; + } + + init_load_factor(load_factor); + return resize(nbucket) ? 0 : -1; +} + +template +void FlatMap<_K, _T, _H, _E, _S, _A, _M>::swap( + FlatMap<_K, _T, _H, _E, _S, _A, _M>& rhs) { + if (!is_default_buckets() && !rhs.is_default_buckets()) { + std::swap(rhs._buckets, _buckets); + std::swap(rhs._thumbnail, _thumbnail); + } else { + for (size_t i = 0; i < DEFAULT_NBUCKET; ++i) { + _default_buckets[i].swap(rhs._default_buckets[i]); + } + if (_S) { + for (size_t i = 0; i < default_nthumbnail; ++i) { + std::swap(_default_thumbnail[i], rhs._default_thumbnail[i]); + } + } + if (!is_default_buckets() && rhs.is_default_buckets()) { + rhs._buckets = _buckets; + rhs._thumbnail = _thumbnail; + _buckets = _default_buckets; + _thumbnail = _default_thumbnail; + } else if (is_default_buckets() && !rhs.is_default_buckets()) { + _buckets = rhs._buckets; + _thumbnail = rhs._thumbnail; + rhs._buckets = rhs._default_buckets; + rhs._thumbnail = rhs._thumbnail; + } // else both are default buckets which has been swapped, so no need to swap `_buckets'. + } + + std::swap(rhs._size, _size); + std::swap(rhs._nbucket, _nbucket); + std::swap(rhs._is_default_load_factor, _is_default_load_factor); + std::swap(rhs._load_factor, _load_factor); + std::swap(rhs._hashfn, _hashfn); + std::swap(rhs._eql, _eql); + rhs._pool.swap(_pool); +} + +template +_T* FlatMap<_K, _T, _H, _E, _S, _A, _M>::insert( + const key_type& key, const mapped_type& value) { + mapped_type *p = &operator[](key); + *p = value; + return p; +} + +template +_T* FlatMap<_K, _T, _H, _E, _S, _A, _M>::insert( + const std::pair& kv) { + return insert(kv.first, kv.second); +} + +template +template +typename std::enable_if::type +FlatMap<_K, _T, _H, _E, _S, _A, _M>::erase(const K2& key, _T* old_value) { + // TODO: Do we need auto collapsing here? + const size_t index = flatmap_mod(_hashfn(key), _nbucket); + Bucket& first_node = _buckets[index]; + if (!first_node.is_valid()) { + return 0; + } + if (_eql(first_node.element().first_ref(), key)) { + if (old_value) { + *old_value = first_node.element().second_movable_ref(); + } + if (first_node.next == NULL) { + first_node.destroy_element(); + first_node.set_invalid(); + if (_S) { + bit_array_unset(_thumbnail, index); + } + } else { + // A seemingly correct solution is to copy the memory of *p to + // first_node directly like this: + // first_node.destroy_element(); + // first_node = *p; + // It works at most of the time, but is wrong generally. + // If _T references self inside like this: + // Value { + // Value() : num(0), num_ptr(&num) {} + // int num; + // int* num_ptr; + // }; + // After copying, num_ptr will be invalid. + // Calling operator= is the price that we have to pay. + Bucket* p = first_node.next; + first_node.next = p->next; + const_cast<_K&>(first_node.element().first_ref()) = + p->element().first_ref(); + first_node.element().second_ref() = p->element().second_movable_ref(); + p->destroy_element(); + _pool.back(p); + } + --_size; + return 1UL; + } + Bucket *p = first_node.next; + Bucket *last_p = &first_node; + while (p) { + if (_eql(p->element().first_ref(), key)) { + if (old_value) { + *old_value = p->element().second_movable_ref(); + } + last_p->next = p->next; + p->destroy_element(); + _pool.back(p); + --_size; + return 1UL; + } + last_p = p; + p = p->next; + } + return 0; +} + +template +template +typename std::enable_if::type +FlatMap<_K, _T, _H, _E, _S, _A, _M>::erase( + const K2& key, std::vector* old_values) { + // TODO: Do we need auto collapsing here? + const size_t index = flatmap_mod(_hashfn(key), _nbucket); + Bucket& first_node = _buckets[index]; + if (!first_node.is_valid()) { + return 0; + } + + Bucket* new_head = NULL; + Bucket* new_tail = NULL; + Bucket* p = &first_node; + size_t total = _size; + while (NULL != p) { + if (_eql(p->element().first_ref(), key)) { + if (NULL != old_values) { + old_values->push_back(p->element().second_movable_ref()); + } + Bucket* temp = p; + p = p->next; + temp->destroy_element(); + if (temp != &first_node) { + _pool.back(temp); + } + --_size; + } else { + if (NULL == new_head) { + new_head = p; + new_tail = p; + } else { + new_tail->next = p; + new_tail = new_tail->next; + } + p = p->next; + } + } + if (NULL != new_tail) { + new_tail->next = NULL; + } + if (NULL == new_head) { + // Erase all element. + first_node.set_invalid(); + if (_S) { + bit_array_unset(_thumbnail, index); + } + } else if (new_head != &first_node) { + // The First node has been erased, need to move new head node as first node. + first_node.next = new_head->next; + const_cast<_K&>(first_node.element().first_ref()) = + new_head->element().first_ref(); + first_node.element().second_ref() = new_head->element().second_movable_ref(); + new_head->destroy_element(); + _pool.back(new_head); + } + return total - _size; +} + +template +void FlatMap<_K, _T, _H, _E, _S, _A, _M>::clear() { + if (0 == _size) { + return; + } + _size = 0; + if (NULL != _buckets) { + for (size_t i = 0; i < _nbucket; ++i) { + Bucket& first_node = _buckets[i]; + if (first_node.is_valid()) { + first_node.destroy_element(); + Bucket* p = first_node.next; + while (p) { + Bucket* next_p = p->next; + p->destroy_element(); + _pool.back(p); + p = next_p; + } + first_node.set_invalid(); + } + } + } + if (NULL != _thumbnail) { + bit_array_clear(_thumbnail, _nbucket); + } +} + +template +void FlatMap<_K, _T, _H, _E, _S, _A, _M>::clear_and_reset_pool() { + clear(); + _pool.reset(); +} + +template +template +_T* FlatMap<_K, _T, _H, _E, _S, _A, _M>::seek(const K2& key) const { + Bucket& first_node = _buckets[flatmap_mod(_hashfn(key), _nbucket)]; + if (!first_node.is_valid()) { + return NULL; + } + if (_eql(first_node.element().first_ref(), key)) { + return &first_node.element().second_ref(); + } + Bucket *p = first_node.next; + while (p) { + if (_eql(p->element().first_ref(), key)) { + return &p->element().second_ref(); + } + p = p->next; + } + return NULL; +} + +template +template std::vector<_T*> +FlatMap<_K, _T, _H, _E, _S, _A, _M>::seek_all(const K2& key) const { + std::vector<_T*> v; + Bucket& first_node = _buckets[flatmap_mod(_hashfn(key), _nbucket)]; + if (!first_node.is_valid()) { + return v; + } + if (_eql(first_node.element().first_ref(), key)) { + v.push_back(&first_node.element().second_ref()); + } + Bucket *p = first_node.next; + while (p) { + if (_eql(p->element().first_ref(), key)) { + v.push_back(&p->element().second_ref()); + } + p = p->next; + } + return v; +} + +template +template +typename std::enable_if::type +FlatMap<_K, _T, _H, _E, _S, _A, _M>::operator[](const key_type& key) { + const size_t index = flatmap_mod(_hashfn(key), _nbucket); + Bucket& first_node = _buckets[index]; + if (!first_node.is_valid()) { + ++_size; + if (_S) { + bit_array_set(_thumbnail, index); + } + new (&first_node) Bucket(key); + return first_node.element().second_ref(); + } + Bucket *p = &first_node; + while (true) { + if (_eql(p->element().first_ref(), key)) { + return p->element().second_ref(); + } + if (NULL == p->next) { + if (is_too_crowded(_size) && resize(_nbucket + 1)) { + return operator[](key); + } + // Fail to resize is OK. + ++_size; + Bucket* newp = new (_pool.get()) Bucket(key); + p->next = newp; + return newp->element().second_ref(); + } + p = p->next; + } +} + +template +template +typename std::enable_if::type +FlatMap<_K, _T, _H, _E, _S, _A, _M>::operator[](const key_type& key) { + const size_t index = flatmap_mod(_hashfn(key), _nbucket); + Bucket& first_node = _buckets[index]; + if (!first_node.is_valid()) { + ++_size; + if (_S) { + bit_array_set(_thumbnail, index); + } + new (&first_node) Bucket(key); + return first_node.element().second_ref(); + } + if (is_too_crowded(_size)) { + Bucket *p = &first_node; + bool need_scale = false; + while (NULL != p) { + // Increase the capacity of bucket when + // hash collision occur and map is crowded. + if (!_eql(p->element().first_ref(), key)) { + need_scale = true; + break; + } + p = p->next; + } + if (need_scale && resize(_nbucket + 1)) { + return operator[](key); + } + // Failed resize is OK. + } + ++_size; + Bucket* newp = new (_pool.get()) Bucket(key); + newp->next = first_node.next; + first_node.next = newp; + return newp->element().second_ref(); +} + +template +void FlatMap<_K, _T, _H, _E, _S, _A, _M>::save_iterator( + const const_iterator& it, PositionHint* hint) const { + hint->nbucket = _nbucket; + hint->offset = it._entry - _buckets; + if (it != end()) { + hint->at_entry = (it._entry == it._node); + hint->key = it->first; + } else { + hint->at_entry = false; + hint->key = key_type(); + } +} + +template +typename FlatMap<_K, _T, _H, _E, _S, _A, _M>::const_iterator +FlatMap<_K, _T, _H, _E, _S, _A, _M>::restore_iterator( + const PositionHint& hint) const { + if (hint.nbucket != _nbucket) // resized + return begin(); // restart + + if (hint.offset >= _nbucket) // invalid hint, stop the iteration + return end(); + + Bucket& first_node = _buckets[hint.offset]; + if (hint.at_entry) { + return const_iterator(this, hint.offset); + } + if (!first_node.is_valid()) { + // All elements hashed to the entry were removed, try next entry. + return const_iterator(this, hint.offset + 1); + } + Bucket *p = &first_node; + do { + if (_eql(p->element().first_ref(), hint.key)) { + const_iterator it; + it._node = p; + it._entry = &first_node; + return it; + } + p = p->next; + } while (p); + // Last element that we iterated (and saved in PositionHint) was removed, + // don't know which element to start, just restart at the beginning of + // the entry. Some elements in the entry may be revisited, which + // shouldn't be often. + return const_iterator(this, hint.offset); +} + +template +optional::NewBucketsInfo> +FlatMap<_K, _T, _H, _E, _S, _A, _M>::new_buckets_and_thumbnail(size_t size, + size_t new_nbucket) { + size_t bump = 0; + do { + // The first iteration uses 'new_nbucket + 0' ensures that when new_nbucket is a power + // of 2 and is already sufficient to accommodate `size`, it does not need to be doubled. + // Subsequent use of 'new_nbucket + 1' avoids an infinite loop. + new_nbucket = flatmap_round(new_nbucket + bump); + bump = 1; + } while (is_too_crowded(size, new_nbucket, _load_factor)); + if (_nbucket == new_nbucket) { + return nullopt; + } + // Note: need an extra bucket to let iterator know where buckets end. + auto buckets = (Bucket*)get_allocator().Alloc( + sizeof(Bucket) * (new_nbucket + 1/*note*/)); + auto guard = MakeScopeGuard([buckets, this]() { + get_allocator().Free(buckets); + }); + if (NULL == buckets) { + LOG(FATAL) << "Fail to new Buckets"; + return nullopt; + } + + uint64_t* thumbnail = NULL; + if (_S) { + thumbnail = bit_array_malloc(new_nbucket); + if (NULL == thumbnail) { + LOG(FATAL) << "Fail to new thumbnail"; + return nullopt; + } + } + + guard.dismiss(); + init_buckets_and_thumbnail(buckets, thumbnail, new_nbucket); + return NewBucketsInfo{buckets, thumbnail, new_nbucket}; +} + +template +bool FlatMap<_K, _T, _H, _E, _S, _A, _M>::resize(size_t nbucket) { + optional info = new_buckets_and_thumbnail(_size, nbucket); + if (!info.has_value()) { + return false; + } + + for (iterator it = begin(); it != end(); ++it) { + const key_type& key = Element::first_ref_from_value(*it); + const size_t index = flatmap_mod(_hashfn(key), info->nbucket); + Bucket& first_node = info->buckets[index]; + if (!first_node.is_valid()) { + if (_S) { + bit_array_set(info->thumbnail, index); + } + new (&first_node) Bucket(key); + first_node.element().second_ref() = + Element::second_movable_ref_from_value(*it); + } else { + Bucket* newp = new (_pool.get()) Bucket(key); + newp->element().second_ref() = + Element::second_movable_ref_from_value(*it); + newp->next = first_node.next; + first_node.next = newp; + } + } + size_t saved_size = _size; + clear(); + if (!is_default_buckets()) { + get_allocator().Free(_buckets); + if (_S) { + bit_array_free(_thumbnail); + } + } + _nbucket = info->nbucket; + _buckets = info->buckets; + _thumbnail = info->thumbnail; + _size = saved_size; + + return true; +} + +template +BucketInfo FlatMap<_K, _T, _H, _E, _S, _A, _M>::bucket_info() const { + size_t max_n = 0; + size_t nentry = 0; + for (size_t i = 0; i < _nbucket; ++i) { + if (_buckets[i].is_valid()) { + size_t n = 1; + for (Bucket* p = _buckets[i].next; p; p = p->next, ++n); + max_n = std::max(max_n, n); + ++nentry; + } + } + return { max_n, size() / (double)nentry }; +} + +inline std::ostream& operator<<(std::ostream& os, const BucketInfo& info) { + return os << "{maxb=" << info.longest_length + << " avgb=" << info.average_length << '}'; +} + +template +typename FlatMap<_K, _T, _H, _E, _S, _A, _M>::iterator +FlatMap<_K, _T, _H, _E, _S, _A, _M>::begin() { + return iterator(this, 0); +} + +template +typename FlatMap<_K, _T, _H, _E, _S, _A, _M>::iterator +FlatMap<_K, _T, _H, _E, _S, _A, _M>::end() { + return iterator(this, _nbucket); +} + +template +typename FlatMap<_K, _T, _H, _E, _S, _A, _M>::const_iterator +FlatMap<_K, _T, _H, _E, _S, _A, _M>::begin() const { + return const_iterator(this, 0); +} + +template +typename FlatMap<_K, _T, _H, _E, _S, _A, _M>::const_iterator +FlatMap<_K, _T, _H, _E, _S, _A, _M>::end() const { + return const_iterator(this, _nbucket); +} + +} // namespace butil + +#endif //BUTIL_FLAT_MAP_INL_H diff --git a/src/butil/containers/hash_tables.h b/src/butil/containers/hash_tables.h new file mode 100644 index 0000000..39ac3d1 --- /dev/null +++ b/src/butil/containers/hash_tables.h @@ -0,0 +1,260 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// + +// +// Deal with the differences between Microsoft and GNU implemenations +// of hash_map. Allows all platforms to use |butil::hash_map| and +// |butil::hash_set|. +// eg: +// butil::hash_map my_map; +// butil::hash_set my_set; +// +// NOTE: It is an explicit non-goal of this class to provide a generic hash +// function for pointers. If you want to hash a pointers to a particular class, +// please define the template specialization elsewhere (for example, in its +// header file) and keep it specific to just pointers to that class. This is +// because identity hashes are not desirable for all types that might show up +// in containers as pointers. + +#ifndef BUTIL_CONTAINERS_HASH_TABLES_H_ +#define BUTIL_CONTAINERS_HASH_TABLES_H_ + +#include + +#include "butil/basictypes.h" +#include "butil/strings/string16.h" +#include "butil/build_config.h" +#include "butil/third_party/murmurhash3/murmurhash3.h" // fmix64 + +#if defined(COMPILER_MSVC) +#include +#include + +#define BUTIL_HASH_NAMESPACE stdext + +#elif defined(COMPILER_GCC) +#if defined(OS_ANDROID) +#define BUTIL_HASH_NAMESPACE std +#else +#define BUTIL_HASH_NAMESPACE __gnu_cxx +#endif + +// This is a hack to disable the gcc 4.4 warning about hash_map and hash_set +// being deprecated. We can get rid of this when we upgrade to VS2008 and we +// can use and . +#ifdef __DEPRECATED +#define CHROME_OLD__DEPRECATED __DEPRECATED +#undef __DEPRECATED +#endif + +#if defined(OS_ANDROID) +#include +#include +#else +#include +#include +#endif + +#include + +#ifdef CHROME_OLD__DEPRECATED +#define __DEPRECATED CHROME_OLD__DEPRECATED +#undef CHROME_OLD__DEPRECATED +#endif + +namespace BUTIL_HASH_NAMESPACE { + +#if !defined(OS_ANDROID) +// The GNU C++ library provides identity hash functions for many integral types, +// but not for |long long|. This hash function will truncate if |size_t| is +// narrower than |long long|. This is probably good enough for what we will +// use it for. + +#define DEFINE_TRIVIAL_HASH(integral_type) \ + template<> \ + struct hash { \ + std::size_t operator()(integral_type value) const { \ + return static_cast(value); \ + } \ + } + +DEFINE_TRIVIAL_HASH(long long); +DEFINE_TRIVIAL_HASH(unsigned long long); + +#undef DEFINE_TRIVIAL_HASH +#endif // !defined(OS_ANDROID) + +// Implement string hash functions so that strings of various flavors can +// be used as keys in STL maps and sets. The hash algorithm comes from the +// GNU C++ library, in . It is duplicated here because GCC +// versions prior to 4.3.2 are unable to compile when RTTI +// is disabled, as it is in our build. + +#define DEFINE_STRING_HASH(string_type) \ + template<> \ + struct hash { \ + std::size_t operator()(const string_type& s) const { \ + std::size_t result = 0; \ + for (string_type::const_iterator i = s.begin(); i != s.end(); ++i) \ + result = (result * 131) + *i; \ + return result; \ + } \ + } + +DEFINE_STRING_HASH(std::string); +DEFINE_STRING_HASH(butil::string16); + +#undef DEFINE_STRING_HASH + +} // namespace BUTIL_HASH_NAMESPACE + +#else // COMPILER +#error define BUTIL_HASH_NAMESPACE for your compiler +#endif // COMPILER + +namespace butil { +using BUTIL_HASH_NAMESPACE::hash_map; +using BUTIL_HASH_NAMESPACE::hash_multimap; +using BUTIL_HASH_NAMESPACE::hash_multiset; +using BUTIL_HASH_NAMESPACE::hash_set; + +// Implement hashing for pairs of at-most 32 bit integer values. +inline std::size_t HashInts32(uint32_t value1, uint32_t value2) { + uint64_t value1_64 = value1; + uint64_t hash64 = (value1_64 << 32) | value2; + return static_cast(fmix64(hash64)); +} + +// Implement hashing for pairs of up-to 64-bit integer values. +// We use the compound integer hash method to produce a 64-bit hash code, by +// breaking the two 64-bit inputs into 4 32-bit values: +// http://opendatastructures.org/versions/edition-0.1d/ods-java/node33.html#SECTION00832000000000000000 +// Then we reduce our result to 32 bits if required, similar to above. +inline std::size_t HashInts64(uint64_t value1, uint64_t value2) { + uint32_t short_random1 = 842304669U; + uint32_t short_random2 = 619063811U; + uint32_t short_random3 = 937041849U; + uint32_t short_random4 = 3309708029U; + + uint32_t value1a = static_cast(value1 & 0xffffffff); + uint32_t value1b = static_cast((value1 >> 32) & 0xffffffff); + uint32_t value2a = static_cast(value2 & 0xffffffff); + uint32_t value2b = static_cast((value2 >> 32) & 0xffffffff); + + uint64_t product1 = static_cast(value1a) * short_random1; + uint64_t product2 = static_cast(value1b) * short_random2; + uint64_t product3 = static_cast(value2a) * short_random3; + uint64_t product4 = static_cast(value2b) * short_random4; + + uint64_t hash64 = product1 + product2 + product3 + product4; + + if (sizeof(std::size_t) >= sizeof(uint64_t)) + return static_cast(hash64); + + uint64_t odd_random = 1578233944LL << 32 | 194370989LL; + uint32_t shift_random = 20591U << 16; + + hash64 = hash64 * odd_random + shift_random; + std::size_t high_bits = static_cast( + hash64 >> (8 * (sizeof(uint64_t) - sizeof(std::size_t)))); + return high_bits; +} + +#define DEFINE_32BIT_PAIR_HASH(Type1, Type2) \ +inline std::size_t HashPair(Type1 value1, Type2 value2) { \ + return HashInts32(value1, value2); \ +} + +DEFINE_32BIT_PAIR_HASH(int16_t, int16_t); +DEFINE_32BIT_PAIR_HASH(int16_t, uint16_t); +DEFINE_32BIT_PAIR_HASH(int16_t, int32_t); +DEFINE_32BIT_PAIR_HASH(int16_t, uint32_t); +DEFINE_32BIT_PAIR_HASH(uint16_t, int16_t); +DEFINE_32BIT_PAIR_HASH(uint16_t, uint16_t); +DEFINE_32BIT_PAIR_HASH(uint16_t, int32_t); +DEFINE_32BIT_PAIR_HASH(uint16_t, uint32_t); +DEFINE_32BIT_PAIR_HASH(int32_t, int16_t); +DEFINE_32BIT_PAIR_HASH(int32_t, uint16_t); +DEFINE_32BIT_PAIR_HASH(int32_t, int32_t); +DEFINE_32BIT_PAIR_HASH(int32_t, uint32_t); +DEFINE_32BIT_PAIR_HASH(uint32_t, int16_t); +DEFINE_32BIT_PAIR_HASH(uint32_t, uint16_t); +DEFINE_32BIT_PAIR_HASH(uint32_t, int32_t); +DEFINE_32BIT_PAIR_HASH(uint32_t, uint32_t); + +#undef DEFINE_32BIT_PAIR_HASH + +#define DEFINE_64BIT_PAIR_HASH(Type1, Type2) \ +inline std::size_t HashPair(Type1 value1, Type2 value2) { \ + return HashInts64(value1, value2); \ +} + +DEFINE_64BIT_PAIR_HASH(int16_t, int64_t); +DEFINE_64BIT_PAIR_HASH(int16_t, uint64_t); +DEFINE_64BIT_PAIR_HASH(uint16_t, int64_t); +DEFINE_64BIT_PAIR_HASH(uint16_t, uint64_t); +DEFINE_64BIT_PAIR_HASH(int32_t, int64_t); +DEFINE_64BIT_PAIR_HASH(int32_t, uint64_t); +DEFINE_64BIT_PAIR_HASH(uint32_t, int64_t); +DEFINE_64BIT_PAIR_HASH(uint32_t, uint64_t); +DEFINE_64BIT_PAIR_HASH(int64_t, int16_t); +DEFINE_64BIT_PAIR_HASH(int64_t, uint16_t); +DEFINE_64BIT_PAIR_HASH(int64_t, int32_t); +DEFINE_64BIT_PAIR_HASH(int64_t, uint32_t); +DEFINE_64BIT_PAIR_HASH(int64_t, int64_t); +DEFINE_64BIT_PAIR_HASH(int64_t, uint64_t); +DEFINE_64BIT_PAIR_HASH(uint64_t, int16_t); +DEFINE_64BIT_PAIR_HASH(uint64_t, uint16_t); +DEFINE_64BIT_PAIR_HASH(uint64_t, int32_t); +DEFINE_64BIT_PAIR_HASH(uint64_t, uint32_t); +DEFINE_64BIT_PAIR_HASH(uint64_t, int64_t); +DEFINE_64BIT_PAIR_HASH(uint64_t, uint64_t); + +#undef DEFINE_64BIT_PAIR_HASH +} // namespace butil + +namespace BUTIL_HASH_NAMESPACE { + +// Implement methods for hashing a pair of integers, so they can be used as +// keys in STL containers. + +// NOTE(gejun): Specialize ptr as well which is supposed to work with +// containers by default + +#if defined(COMPILER_MSVC) + +template +inline std::size_t hash_value(const std::pair& value) { + return butil::HashPair(value.first, value.second); +} +template +inline std::size_t hash_value(Type* ptr) { + return (uintptr_t)ptr; +} + +#elif defined(COMPILER_GCC) +template +struct hash > { + std::size_t operator()(std::pair value) const { + return butil::HashPair(value.first, value.second); + } +}; +template +struct hash { + std::size_t operator()(Type* ptr) const { + return (uintptr_t)ptr; + } +}; + +#else +#error define hash > for your compiler +#endif // COMPILER + +} + +#undef DEFINE_PAIR_HASH_FUNCTION_START +#undef DEFINE_PAIR_HASH_FUNCTION_END + +#endif // BUTIL_CONTAINERS_HASH_TABLES_H_ diff --git a/src/butil/containers/linked_list.h b/src/butil/containers/linked_list.h new file mode 100644 index 0000000..7874b65 --- /dev/null +++ b/src/butil/containers/linked_list.h @@ -0,0 +1,201 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_CONTAINERS_LINKED_LIST_H_ +#define BUTIL_CONTAINERS_LINKED_LIST_H_ + +#include "butil/macros.h" + +// Simple LinkedList type. (See the Q&A section to understand how this +// differs from std::list). +// +// To use, start by declaring the class which will be contained in the linked +// list, as extending LinkNode (this gives it next/previous pointers). +// +// class MyNodeType : public LinkNode { +// ... +// }; +// +// Next, to keep track of the list's head/tail, use a LinkedList instance: +// +// LinkedList list; +// +// To add elements to the list, use any of LinkedList::Append, +// LinkNode::InsertBefore, or LinkNode::InsertAfter: +// +// LinkNode* n1 = ...; +// LinkNode* n2 = ...; +// LinkNode* n3 = ...; +// +// list.Append(n1); +// list.Append(n3); +// n2->InsertBefore(n3); +// +// Lastly, to iterate through the linked list forwards: +// +// for (LinkNode* node = list.head(); +// node != list.end(); +// node = node->next()) { +// MyNodeType* value = node->value(); +// ... +// } +// +// Or to iterate the linked list backwards: +// +// for (LinkNode* node = list.tail(); +// node != list.end(); +// node = node->previous()) { +// MyNodeType* value = node->value(); +// ... +// } +// +// Questions and Answers: +// +// Q. Should I use std::list or butil::LinkedList? +// +// A. The main reason to use butil::LinkedList over std::list is +// performance. If you don't care about the performance differences +// then use an STL container, as it makes for better code readability. +// +// Comparing the performance of butil::LinkedList to std::list: +// +// * Erasing an element of type T* from butil::LinkedList is +// an O(1) operation. Whereas for std::list it is O(n). +// That is because with std::list you must obtain an +// iterator to the T* element before you can call erase(iterator). +// +// * Insertion operations with butil::LinkedList never require +// heap allocations. +// +// Q. How does butil::LinkedList implementation differ from std::list? +// +// A. Doubly-linked lists are made up of nodes that contain "next" and +// "previous" pointers that reference other nodes in the list. +// +// With butil::LinkedList, the type being inserted already reserves +// space for the "next" and "previous" pointers (butil::LinkNode*). +// Whereas with std::list the type can be anything, so the implementation +// needs to glue on the "next" and "previous" pointers using +// some internal node type. + +namespace butil { + +template +class LinkNode { + public: + // LinkNode are self-referential as default. + LinkNode() : previous_(this), next_(this) {} + + LinkNode(LinkNode* previous, LinkNode* next) + : previous_(previous), next_(next) {} + + // Insert |this| into the linked list, before |e|. + void InsertBefore(LinkNode* e) { + this->next_ = e; + this->previous_ = e->previous_; + e->previous_->next_ = this; + e->previous_ = this; + } + + // Insert |this| as a circular linked list into the linked list, before |e|. + void InsertBeforeAsList(LinkNode* e) { + LinkNode* prev = this->previous_; + prev->next_ = e; + this->previous_ = e->previous_; + e->previous_->next_ = this; + e->previous_ = prev; + } + + // Insert |this| into the linked list, after |e|. + void InsertAfter(LinkNode* e) { + this->next_ = e->next_; + this->previous_ = e; + e->next_->previous_ = this; + e->next_ = this; + } + + // Insert |this| as a circular list into the linked list, after |e|. + void InsertAfterAsList(LinkNode* e) { + LinkNode* prev = this->previous_; + prev->next_ = e->next_; + this->previous_ = e; + e->next_->previous_ = prev; + e->next_ = this; + } + + // Remove |this| from the linked list. + void RemoveFromList() { + this->previous_->next_ = this->next_; + this->next_->previous_ = this->previous_; + // next() and previous() return non-NULL if and only this node is not in any + // list. + this->next_ = this; + this->previous_ = this; + } + + LinkNode* previous() const { + return previous_; + } + + LinkNode* next() const { + return next_; + } + + // Cast from the node-type to the value type. + const T* value() const { + return static_cast(this); + } + + T* value() { + return static_cast(this); + } + + private: + LinkNode* previous_; + LinkNode* next_; + + DISALLOW_COPY_AND_ASSIGN(LinkNode); +}; + +template +class LinkedList { + public: + // The "root" node is self-referential, and forms the basis of a circular + // list (root_.next() will point back to the start of the list, + // and root_->previous() wraps around to the end of the list). + LinkedList() {} + + // Appends |e| to the end of the linked list. + void Append(LinkNode* e) { + e->InsertBefore(&root_); + } + + // Prepend |e| to the head of the linked list. + void Prepend(LinkNode* e) { + e->InsertAfter(&root_); + } + + LinkNode* head() const { + return root_.next(); + } + + LinkNode* tail() const { + return root_.previous(); + } + + const LinkNode* end() const { + return &root_; + } + + bool empty() const { return head() == end(); } + + private: + LinkNode root_; + + DISALLOW_COPY_AND_ASSIGN(LinkedList); +}; + +} // namespace butil + +#endif // BUTIL_CONTAINERS_LINKED_LIST_H_ diff --git a/src/butil/containers/mpsc_queue.h b/src/butil/containers/mpsc_queue.h new file mode 100644 index 0000000..6ba09db --- /dev/null +++ b/src/butil/containers/mpsc_queue.h @@ -0,0 +1,188 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// A Multiple Producer, Single Consumer Queue. +// It allows multiple threads to enqueue, and allows one thread +// (and only one thread) to dequeue. + +#ifndef BUTIL_MPSC_QUEUE_H +#define BUTIL_MPSC_QUEUE_H + +#include "butil/object_pool.h" +#include "butil/type_traits.h" +#include "butil/memory/manual_constructor.h" + +namespace butil { + +template +struct BAIDU_CACHELINE_ALIGNMENT MPSCQueueNode { + static MPSCQueueNode* const UNCONNECTED; + + MPSCQueueNode* next{NULL}; + ManualConstructor data_mem; +}; + +template +MPSCQueueNode* const MPSCQueueNode::UNCONNECTED = (MPSCQueueNode*)(intptr_t)-1; + +// Default allocator for MPSCQueueNode. +template +class DefaultAllocator { +public: + void* Alloc() { return malloc(sizeof(MPSCQueueNode)); } + void Free(void* p) { free(p); } +}; + +// Allocator using ObjectPool for MPSCQueueNode. +template +class ObjectPoolAllocator { +public: + void* Alloc() { return get_object>(); } + void Free(void* p) { return_object(static_cast*>(p)); } +}; + + +template > +class MPSCQueue { +public: + MPSCQueue() + : _head(NULL) + , _cur_enqueue_node(NULL) + , _cur_dequeue_node(NULL) {} + + ~MPSCQueue(); + + // Enqueue data to the queue. + void Enqueue(typename add_const_reference::type data); + void Enqueue(T&& data); + + // Dequeue data from the queue. + bool Dequeue(T& data); + +private: + // Reverse the list until old_head. + void ReverseList(MPSCQueueNode* old_head); + + void EnqueueImpl(MPSCQueueNode* node); + bool DequeueImpl(T* data); + + Alloc _alloc; + atomic*> _head; + atomic*> _cur_enqueue_node; + MPSCQueueNode* _cur_dequeue_node; +}; + +template +MPSCQueue::~MPSCQueue() { + while (DequeueImpl(NULL)); +} + +template +void MPSCQueue::Enqueue(typename add_const_reference::type data) { + auto node = (MPSCQueueNode*)_alloc.Alloc(); + node->next = MPSCQueueNode::UNCONNECTED; + node->data_mem.Init(data); + EnqueueImpl(node); +} + +template +void MPSCQueue::Enqueue(T&& data) { + auto node = (MPSCQueueNode*)_alloc.Alloc(); + node->next = MPSCQueueNode::UNCONNECTED; + node->data_mem.Init(data); + EnqueueImpl(node); +} + +template +void MPSCQueue::EnqueueImpl(MPSCQueueNode* node) { + MPSCQueueNode* prev = _head.exchange(node, memory_order_release); + if (prev) { + node->next = prev; + return; + } + node->next = NULL; + _cur_enqueue_node.store(node, memory_order_relaxed); +} + +template +bool MPSCQueue::Dequeue(T& data) { + return DequeueImpl(&data); +} + +template +bool MPSCQueue::DequeueImpl(T* data) { + MPSCQueueNode* node; + if (_cur_dequeue_node) { + node = _cur_dequeue_node; + } else { + node = _cur_enqueue_node.exchange(NULL, memory_order_relaxed); + } + if (!node) { + return false; + } + + if (data) { + auto mem = (T* const)node->data_mem.get(); + *data = std::move(*mem); + } + MPSCQueueNode* old_node = node; + if (!node->next) { + ReverseList(node); + } + _cur_dequeue_node = node->next; + _alloc.Free(old_node); + + return true; +} + +template +void MPSCQueue::ReverseList(MPSCQueueNode* old_head) { + // Try to set _write_head to NULL to mark that it is done. + MPSCQueueNode* new_head = old_head; + MPSCQueueNode* desired = NULL; + if (_head.compare_exchange_strong( + new_head, desired, memory_order_acquire)) { + // No one added new requests. + return; + } + CHECK_NE(new_head, old_head); + // Above acquire fence pairs release fence of exchange in Enqueue() to make + // sure that we see all fields of requests set. + + // Someone added new requests. + // Reverse the list until old_head. + MPSCQueueNode* tail = NULL; + MPSCQueueNode* p = new_head; + do { + while (p->next == MPSCQueueNode::UNCONNECTED) { + // TODO(gejun): elaborate this + sched_yield(); + } + MPSCQueueNode* const saved_next = p->next; + p->next = tail; + tail = p; + p = saved_next; + CHECK(p); + } while (p != old_head); + + // Link old list with new list. + old_head->next = tail; +} + +} + +#endif // BUTIL_MPSC_QUEUE_H diff --git a/src/butil/containers/mru_cache.h b/src/butil/containers/mru_cache.h new file mode 100644 index 0000000..e2bbef7 --- /dev/null +++ b/src/butil/containers/mru_cache.h @@ -0,0 +1,310 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains a template for a Most Recently Used cache that allows +// constant-time access to items using a key, but easy identification of the +// least-recently-used items for removal. Each key can only be associated with +// one payload item at a time. +// +// The key object will be stored twice, so it should support efficient copying. +// +// NOTE: While all operations are O(1), this code is written for +// legibility rather than optimality. If future profiling identifies this as +// a bottleneck, there is room for smaller values of 1 in the O(1). :] + +#ifndef BUTIL_CONTAINERS_MRU_CACHE_H_ +#define BUTIL_CONTAINERS_MRU_CACHE_H_ + +#include +#include +#include + +#include "butil/basictypes.h" +#include "butil/containers/hash_tables.h" +#include "butil/logging.h" + +namespace butil { + +// MRUCacheBase ---------------------------------------------------------------- + +// This template is used to standardize map type containers that can be used +// by MRUCacheBase. This level of indirection is necessary because of the way +// that template template params and default template params interact. +template +struct MRUCacheStandardMap { + typedef std::map Type; +}; + +// Base class for the MRU cache specializations defined below. +// The deletor will get called on all payloads that are being removed or +// replaced. +template class MapType = MRUCacheStandardMap> +class MRUCacheBase { + public: + // The payload of the list. This maintains a copy of the key so we can + // efficiently delete things given an element of the list. + typedef std::pair value_type; + + private: + typedef std::list PayloadList; + typedef typename MapType::Type KeyIndex; + + public: + typedef typename PayloadList::size_type size_type; + + typedef typename PayloadList::iterator iterator; + typedef typename PayloadList::const_iterator const_iterator; + typedef typename PayloadList::reverse_iterator reverse_iterator; + typedef typename PayloadList::const_reverse_iterator const_reverse_iterator; + + enum { NO_AUTO_EVICT = 0 }; + + // The max_size is the size at which the cache will prune its members to when + // a new item is inserted. If the caller wants to manager this itself (for + // example, maybe it has special work to do when something is evicted), it + // can pass NO_AUTO_EVICT to not restrict the cache size. + explicit MRUCacheBase(size_type max_size) : max_size_(max_size) { + } + + MRUCacheBase(size_type max_size, const DeletorType& deletor) + : max_size_(max_size), deletor_(deletor) { + } + + virtual ~MRUCacheBase() { + iterator i = begin(); + while (i != end()) + i = Erase(i); + } + + size_type max_size() const { return max_size_; } + + // Inserts a payload item with the given key. If an existing item has + // the same key, it is removed prior to insertion. An iterator indicating the + // inserted item will be returned (this will always be the front of the list). + // + // The payload will be copied. In the case of an OwningMRUCache, this function + // will take ownership of the pointer. + iterator Put(const KeyType& key, const PayloadType& payload) { + // Remove any existing payload with that key. + typename KeyIndex::iterator index_iter = index_.find(key); + if (index_iter != index_.end()) { + // Erase the reference to it. This will call the deletor on the removed + // element. The index reference will be replaced in the code below. + Erase(index_iter->second); + } else if (max_size_ != NO_AUTO_EVICT) { + // New item is being inserted which might make it larger than the maximum + // size: kick the oldest thing out if necessary. + ShrinkToSize(max_size_ - 1); + } + + ordering_.push_front(value_type(key, payload)); + index_.insert(std::make_pair(key, ordering_.begin())); + return ordering_.begin(); + } + + // Retrieves the contents of the given key, or end() if not found. This method + // has the side effect of moving the requested item to the front of the + // recency list. + // + // TODO(brettw) We may want a const version of this function in the future. + iterator Get(const KeyType& key) { + typename KeyIndex::iterator index_iter = index_.find(key); + if (index_iter == index_.end()) + return end(); + typename PayloadList::iterator iter = index_iter->second; + + // Move the touched item to the front of the recency ordering. + ordering_.splice(ordering_.begin(), ordering_, iter); + return ordering_.begin(); + } + + // Retrieves the payload associated with a given key and returns it via + // result without affecting the ordering (unlike Get). + iterator Peek(const KeyType& key) { + typename KeyIndex::const_iterator index_iter = index_.find(key); + if (index_iter == index_.end()) + return end(); + return index_iter->second; + } + + const_iterator Peek(const KeyType& key) const { + typename KeyIndex::const_iterator index_iter = index_.find(key); + if (index_iter == index_.end()) + return end(); + return index_iter->second; + } + + // Erases the item referenced by the given iterator. An iterator to the item + // following it will be returned. The iterator must be valid. + iterator Erase(iterator pos) { + deletor_(pos->second); + index_.erase(pos->first); + return ordering_.erase(pos); + } + + // MRUCache entries are often processed in reverse order, so we add this + // convenience function (not typically defined by STL containers). + reverse_iterator Erase(reverse_iterator pos) { + // We have to actually give it the incremented iterator to delete, since + // the forward iterator that base() returns is actually one past the item + // being iterated over. + return reverse_iterator(Erase((++pos).base())); + } + + // Shrinks the cache so it only holds |new_size| items. If |new_size| is + // bigger or equal to the current number of items, this will do nothing. + void ShrinkToSize(size_type new_size) { + for (size_type i = size(); i > new_size; i--) + Erase(rbegin()); + } + + // Deletes everything from the cache. + void Clear() { + for (typename PayloadList::iterator i(ordering_.begin()); + i != ordering_.end(); ++i) + deletor_(i->second); + index_.clear(); + ordering_.clear(); + } + + // Returns the number of elements in the cache. + size_type size() const { + // We don't use ordering_.size() for the return value because + // (as a linked list) it can be O(n). + DCHECK(index_.size() == ordering_.size()); + return index_.size(); + } + + // Allows iteration over the list. Forward iteration starts with the most + // recent item and works backwards. + // + // Note that since these iterators are actually iterators over a list, you + // can keep them as you insert or delete things (as long as you don't delete + // the one you are pointing to) and they will still be valid. + iterator begin() { return ordering_.begin(); } + const_iterator begin() const { return ordering_.begin(); } + iterator end() { return ordering_.end(); } + const_iterator end() const { return ordering_.end(); } + + reverse_iterator rbegin() { return ordering_.rbegin(); } + const_reverse_iterator rbegin() const { return ordering_.rbegin(); } + reverse_iterator rend() { return ordering_.rend(); } + const_reverse_iterator rend() const { return ordering_.rend(); } + + bool empty() const { return ordering_.empty(); } + + private: + PayloadList ordering_; + KeyIndex index_; + + size_type max_size_; + + DeletorType deletor_; + + DISALLOW_COPY_AND_ASSIGN(MRUCacheBase); +}; + +// MRUCache -------------------------------------------------------------------- + +// A functor that does nothing. Used by the MRUCache. +template +class MRUCacheNullDeletor { + public: + void operator()(PayloadType& /*payload*/) { + } +}; + +// A container that does not do anything to free its data. Use this when storing +// value types (as opposed to pointers) in the list. +template +class MRUCache : public MRUCacheBase > { + private: + typedef MRUCacheBase > ParentType; + + public: + // See MRUCacheBase, noting the possibility of using NO_AUTO_EVICT. + explicit MRUCache(typename ParentType::size_type max_size) + : ParentType(max_size) { + } + virtual ~MRUCache() { + } + + private: + DISALLOW_COPY_AND_ASSIGN(MRUCache); +}; + +// OwningMRUCache -------------------------------------------------------------- + +template +class MRUCachePointerDeletor { + public: + void operator()(PayloadType& payload) { + delete payload; + } +}; + +// A cache that owns the payload type, which must be a non-const pointer type. +// The pointers will be deleted when they are removed, replaced, or when the +// cache is destroyed. +template +class OwningMRUCache + : public MRUCacheBase > { + private: + typedef MRUCacheBase > ParentType; + + public: + // See MRUCacheBase, noting the possibility of using NO_AUTO_EVICT. + explicit OwningMRUCache(typename ParentType::size_type max_size) + : ParentType(max_size) { + } + virtual ~OwningMRUCache() { + } + + private: + DISALLOW_COPY_AND_ASSIGN(OwningMRUCache); +}; + +// HashingMRUCache ------------------------------------------------------------ + +template +struct MRUCacheHashMap { + typedef butil::hash_map Type; +}; + +// This class is similar to MRUCache, except that it uses butil::hash_map as +// the map type instead of std::map. Note that your KeyType must be hashable +// to use this cache. +template +class HashingMRUCache : public MRUCacheBase, + MRUCacheHashMap> { + private: + typedef MRUCacheBase, + MRUCacheHashMap> ParentType; + + public: + // See MRUCacheBase, noting the possibility of using NO_AUTO_EVICT. + explicit HashingMRUCache(typename ParentType::size_type max_size) + : ParentType(max_size) { + } + virtual ~HashingMRUCache() { + } + + private: + DISALLOW_COPY_AND_ASSIGN(HashingMRUCache); +}; + +} // namespace butil + +#endif // BUTIL_CONTAINERS_MRU_CACHE_H_ diff --git a/src/butil/containers/optional.h b/src/butil/containers/optional.h new file mode 100644 index 0000000..bde0ad2 --- /dev/null +++ b/src/butil/containers/optional.h @@ -0,0 +1,563 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BUTIL_OPTIONAL_H +#define BUTIL_OPTIONAL_H + +#include "butil/type_traits.h" +#include "butil/memory/manual_constructor.h" + +// The `optional` for managing an optional contained value, +// i.e. a value that may or may not be present, is a C++11 +// compatible version of the C++17 `std::optional` abstraction. +// After C++17, `optional` is an alias for `std::optional`. + +#if __cplusplus >= 201703L + +#include + +namespace butil { +using std::in_place_t; +using std::in_place; +using std::nullopt_t; +using std::nullopt; +using std::bad_optional_access; +using std::optional; +using std::make_optional; +} // namespace butil +#else +namespace butil { + +// Tag type used to specify in-place construction of `optional'. +struct in_place_t {}; + +// Instance used for in-place construction of `optional', +const in_place_t in_place; + +namespace internal { + // Tag type used as a constructor parameter type for `nullopt_t'. + struct optional_forbidden_t {}; +} + +// Used to indicate an optional object that does not contain a value. +struct nullopt_t { + // It must not be default-constructible to avoid ambiguity for `opt = {}'. + explicit nullopt_t(internal::optional_forbidden_t) noexcept {} +}; + +// Instance to use for the construction of `optional`. +const nullopt_t nullopt(internal::optional_forbidden_t{}); + +// Thrown by `optional::value()' when accessing an optional object +// that does not contain a value. +class bad_optional_access : public std::exception { +public: + bad_optional_access() = default; + ~bad_optional_access() override = default; + const char* what() const noexcept override { + return "optional has no value"; + } +}; + +template +class optional; + +namespace internal { + +// Whether `T' is constructible or convertible from `optional'. +template +struct is_constructible_from_optional : integral_constant< + bool, std::is_constructible&>::value || + std::is_constructible&&>::value || + std::is_constructible&>::value || + std::is_constructible&&>::value> {}; + +// Whether `T' is constructible or convertible from `butil::optional'. +template +struct is_convertible_from_optional + : integral_constant&, T>::value || + std::is_convertible&&, T>::value || + std::is_convertible&, T>::value || + std::is_convertible&&, T>::value> {}; + +// Whether `T' is constructible or convertible or assignable from `optional'. +template +struct is_assignable_from_optional + : integral_constant&>::value || + std::is_assignable&&>::value || + std::is_assignable&>::value || + std::is_assignable&&>::value> {}; + +// Whether `T' is constructible or convertible from `optional'. +template +struct is_constructible_convertible_from_optional + : integral_constant::value || + is_convertible_from_optional::value> {}; + +// Whether `T' is constructible or convertible or assignable from `optional'. +template +struct is_constructible_convertible_assignable_from_optional + : integral_constant::value || + is_convertible_from_optional::value || + is_assignable_from_optional::value> {}; + +} // namespace internal + +template +class optional { +public: + typedef T value_type; + + static_assert(!is_same::type, in_place_t>::value, + "Instantiation of optional with in_place_t is ill-formed"); + static_assert(!is_same::type, nullopt_t>::value, + "Instantiation of optional with nullopt_t is ill-formed"); + static_assert(!is_reference::value, + "Instantiation of optional with a reference type is ill-formed"); + static_assert(std::is_destructible::value, + "Instantiation of optional with a non-destructible type is ill-formed"); + static_assert(!is_array::value, + "Instantiation of optional with an array type is ill-formed"); + + optional() : _engaged(false) {} + + optional(nullopt_t) : optional() {} + + optional(const optional& rhs) = default; + + optional(optional&& rhs) noexcept = default; + + template ::value && + std::is_constructible::value && + !internal::is_constructible_convertible_from_optional::value && + std::is_convertible::value, bool>::type = false> + optional(const optional& rhs) : _engaged(rhs.has_value()) { + if (_engaged) { + _storage.Init(*rhs); + } + } + + template ::value && + std::is_constructible::value && + !internal::is_constructible_convertible_from_optional::value && + !std::is_convertible::value, bool>::type = false> + explicit optional(const optional& rhs) : _engaged(rhs.has_value()) { + if (_engaged) { + _storage.Init(*rhs); + } + } + + template ::value && + std::is_constructible::value && + !internal::is_constructible_convertible_from_optional::value && + std::is_convertible::value, bool>::type = false> + optional(optional&& rhs) : _engaged(rhs.has_value()) { + if (_engaged) { + _storage.Init(std::move(*rhs)); + rhs.reset(); + } + } + + template ::value && + std::is_constructible::value && + !internal::is_constructible_convertible_from_optional::value && + !std::is_convertible::value, bool>::type = false> + explicit optional(optional&& rhs) : _engaged(rhs.has_value()) { + if (_engaged) { + _storage.Init(std::move(*rhs)); + rhs.reset(); + } + } + + optional(const T& value) : _engaged(true) { + _storage.Init(value); + } + + optional(T&& value) : _engaged(true) { + _storage.Init(std::move(value)); + } + + template ::value>* = nullptr> + explicit optional(const in_place_t, Args&&... args) : _engaged(true) { + _storage.Init(std::forward(args)...); + } + + template &, Args&&...>::value>::type> + optional(in_place_t, std::initializer_list il, Args&&... args) + : _engaged(true) { + _storage.Init(il, std::forward(args)...); + } + + template ::type>::value && + !std::is_same, typename std::decay::type>::value && + std::is_constructible::value && + std::is_convertible::value, bool>::type = false> + optional(U&& v) : _engaged(true) { + _storage.Init(std::forward(v)); + } + + template ::type>::value && + !std::is_same, typename std::decay::type>::value && + std::is_constructible::value && + !std::is_convertible::value, bool>::type = false> + explicit optional(U&& v) : _engaged(true) { + _storage.Init(std::forward(v)); + } + + ~optional() { + reset(); + } + + optional& operator=(nullopt_t) noexcept { + reset(); + return *this; + } + + optional& operator=(const optional& rhs) = default; + + optional& operator=(optional&& rhs) = default; + + // Value assignment operators + template , typename std::decay::type>::value && + !std::is_same, typename remove_cvref::type>::value && + std::is_constructible::value && std::is_assignable::value && + (!std::is_scalar::value || !std::is_same::type>::value)>::type> + optional& operator=(U&& v) { + reset(); + _storage.Init(std::forward(v)); + _engaged = true; + return *this; + } + + template ::value && + !internal::is_constructible_convertible_assignable_from_optional::value && + std::is_constructible::value && + std::is_assignable::value>::type> + optional& operator=(const optional& rhs) { + if (rhs) { + operator=(*rhs); + } else { + reset(); + } + return *this; + } + + template ::value && + !internal::is_constructible_convertible_assignable_from_optional::value && + std::is_constructible::value && + std::is_assignable::value>::type> + optional& operator=(optional&& rhs) { + if (rhs) { + operator=(std::move(*rhs)); + } else { + reset(); + } + return *this; + } + + // Accesses the contained value. + T& operator*() { + if (!_engaged) { + throw bad_optional_access(); + } + return *_storage; + } + const T& operator*() const { + if (!_engaged) { + throw bad_optional_access(); + } + return *_storage; + } + T* operator->() { + return _storage.get(); + } + const T* operator->() const { + return _storage.get(); + } + + explicit operator bool() const { return _engaged; } + + bool has_value() const { return _engaged; } + + // Returns the contained value if the `optional` is not empty, + // otherwise throws `bad_optional_access`. + const T& value() const & { + if (!_engaged) { + throw bad_optional_access(); + } + + return *_storage; + } + T& value() & { + if (!_engaged) { + throw bad_optional_access(); + } + + return *_storage; + } + T&& value() && { + if (!_engaged) { + throw bad_optional_access(); + } + + return std::move(*_storage); + } + const T&& value() const && { + if (!_engaged) { + throw bad_optional_access(); + } + + return std::move(*_storage); + } + + // Returns the contained value if the `optional` is not empty, + // otherwise returns default `v'. + template + constexpr T value_or(U&& v) const& { + static_assert(std::is_copy_constructible::value, + "T can not be copy constructible"); + static_assert(std::is_convertible::value, + "U can not be convertible to T"); + + return static_cast(*this) ? **this : static_cast(std::forward(v)); + } + template + T value_or(U&& v) && { + static_assert(std::is_move_constructible::value, + "T can not be move constructible"); + static_assert(std::is_convertible::value, + "U can not be convertible to T"); + + return static_cast(*this) ? + std::move(**this) : static_cast(std::forward(v)); + } + + void swap(optional& rhs) noexcept { + if (_engaged && rhs._engaged) { + std::swap(**this, *rhs); + } else if (_engaged) { + rhs = std::move(*this); + reset(); + } else if (rhs._engaged) { + *this = std::move(rhs); + rhs.reset(); + } + } + + // Destroys any contained value if the `optional` is not empty. + void reset() { + if (_engaged) { + _storage.Destroy(); + _engaged = false; + } + } + + // Constructs the contained value in-place. + // Return a reference to the new contained value. + template + T& emplace(Args&&... args) { + static_assert(std::is_constructible::value, + "T can not be constructible with these arguments"); + reset(); + _storage.Init(std::forward(args)...); + _engaged = true; + return *_storage; + } + template + T& emplace(std::initializer_list il, Args&&... args) { + static_assert(std::is_constructible&, Args&&...>::value, + "T can not be constructible with these arguments"); + + return emplace(il, std::forward(args)...); + } + +private: + bool _engaged; + ManualConstructor _storage; +}; + +template +void swap(optional& a, optional& b) noexcept { a.swap(b); } + +// Creates a non-empty 'optional`. +template +optional::type> make_optional(T&& v) { + return optional::type>(std::forward(v)); +} + +template +optional make_optional(Args&&... args) { + return optional(in_place, std::forward(args)...); +} + +template +optional make_optional(std::initializer_list il, Args&&... args) { + return optional(in_place, il, std::forward(args)...); +} + +// Compares optional objects. +// Supports comparisons between 'optional` and 'optional`, +// between 'optional` and `U', and between 'optional` and `nullopt'. +// Empty optionals are considered equal to each other and less than non-empty optionals. +template +bool operator==(const optional& x, const optional& y) { + return static_cast(x) != static_cast(y) + ? false : static_cast(x) == false ? true : *x == *y; +} + +template +bool operator!=(const optional& x, const optional& y) { + return !(x == y); +} + +template +bool operator<=(const optional& x, const optional& y) { + return !x ? true : !y ? false : *x <= *y; +} + +template +bool operator>=(const optional& x, const optional& y) { + return !y ? true : !x ? false : *x >= *y; +} + +template +bool operator<(const optional& x, const optional& y) { + return !(x >= y); +} + +template +bool operator>(const optional& x, const optional& y) { + return !(x <= y); +} + +template +bool operator==(const optional& x, nullopt_t) { return !x; } + +template +bool operator==(nullopt_t, const optional& x) { return !x; } + +template +bool operator!=(const optional& x, nullopt_t) { return static_cast(x); } + +template +bool operator!=(nullopt_t, const optional& x) { return static_cast(x);} + +template +bool operator<(const optional&, nullopt_t) { return false; } + +template +bool operator<(nullopt_t, const optional& x) { return static_cast(x); } + +template +bool operator<=(const optional& x, nullopt_t) { return !x; } + +template +bool operator<=(nullopt_t, const optional&) { return true; } + +template +bool operator>(const optional& x, nullopt_t) { return static_cast(x); } + +template +bool operator>(nullopt_t, const optional&) { return false; } + +template +bool operator>=(const optional&, nullopt_t) { return true; } +template +bool operator>=(nullopt_t, const optional& x) { return !x; } + +template +bool operator==(const optional& x, const U& v) { + return x ? *x == v : false; +} +template +bool operator==(const U& v, const optional& x) { + return x == v; +} +template +bool operator!=(const optional& x, const U& v) { + return x ? *x != v : true; +} +template +bool operator!=(const U& v, const optional& x) { + return x != v; +} + +template +bool operator<=(const optional& x, const U& v) { + return x ? *x <= v : true; +} +template +bool operator<=(const U& v, const optional& x) { + return x ? v <= *x : false; +} +template +bool operator<(const optional& x, const U& v) { + return !(x >= v); +} +template +bool operator<(const U& v, const optional& x) { + return !(v >= x); +} + +template +bool operator>=(const optional& x, const U& v) { + return x ? *x >= v : false; +} +template +bool operator>=(const U& v, const optional& x) { + return x ? v >= *x : true; +} + +template +bool operator>(const optional& x, const U& v) { + return !(x <= v); +} +template +bool operator>(const U& v, const optional& x) { + return !(v <= x); +} + +} // namespace butil + +namespace std { +template +struct hash> { + std::size_t operator()(const butil::optional& opt) const { + return hash()(opt.value_or(T())); + } +}; + +} // namespace std + +#endif // __cplusplus >= 201703L + +#endif // BUTIL_OPTIONAL_H diff --git a/src/butil/containers/pooled_map.h b/src/butil/containers/pooled_map.h new file mode 100644 index 0000000..dab4371 --- /dev/null +++ b/src/butil/containers/pooled_map.h @@ -0,0 +1,196 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Sat Dec 3 13:11:32 CST 2016 + +#ifndef BUTIL_POOLED_MAP_H +#define BUTIL_POOLED_MAP_H + +#include "butil/single_threaded_pool.h" +#include +#include + +namespace butil { +namespace details { +template class PooledAllocator; +} + +// A drop-in replacement for std::map to improve insert/erase performance slightly +// +// When do use PooledMap? +// A std::map with 10~100 elements. insert/erase performance will be slightly +// improved. Performance of find() is unaffected. +// When do NOT use PooledMap? +// When the std::map has less that 10 elements, PooledMap is probably slower +// because it allocates BLOCK_SIZE memory at least. When the std::map has more than +// 100 elements, you should use butil::FlatMap instead. + +// insert/erase comparisons between several maps: +// [ value = 8 bytes ] +// Sequentially inserting 100 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 15/114/54/60 +// Sequentially erasing 100 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 6/123/56/37 +// Sequentially inserting 1000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 9/92/56/54 +// Sequentially erasing 1000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 3/68/51/35 +// Sequentially inserting 10000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 10/99/63/54 +// Sequentially erasing 10000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 4/73/54/35 +// [ value = 32 bytes ] +// Sequentially inserting 100 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 14/107/57/57 +// Sequentially erasing 100 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 5/75/53/37 +// Sequentially inserting 1000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 13/94/55/53 +// Sequentially erasing 1000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 4/67/50/37 +// Sequentially inserting 10000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 13/102/63/54 +// Sequentially erasing 10000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 4/69/53/36 +// [ value = 128 bytes ] +// Sequentially inserting 100 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 35/160/96/98 +// Sequentially erasing 100 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 7/96/53/42 +// Sequentially inserting 1000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 30/159/98/98 +// Sequentially erasing 1000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 6/82/49/43 +// Sequentially inserting 10000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 29/155/114/116 +// Sequentially erasing 10000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 6/81/53/43 + +// [ value = 8 bytes ] +// Randomly inserting 100 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 13/168/103/59 +// Randomly erasing 100 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 6/159/125/37 +// Randomly inserting 1000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 10/157/115/54 +// Randomly erasing 1000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 4/175/138/36 +// Randomly inserting 10000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 11/219/177/56 +// Randomly erasing 10000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 4/229/207/47 +// [ value = 32 bytes ] +// Randomly inserting 100 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 17/178/112/57 +// Randomly erasing 100 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 6/149/117/38 +// Randomly inserting 1000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 15/169/135/54 +// Randomly erasing 1000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 5/157/129/39 +// Randomly inserting 10000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 19/242/203/55 +// Randomly erasing 10000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 5/233/218/54 +// [ value = 128 bytes ] +// Randomly inserting 100 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 36/214/145/96 +// Randomly erasing 100 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 7/166/122/53 +// Randomly inserting 1000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 36/230/174/100 +// Randomly erasing 1000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 6/193/153/65 +// Randomly inserting 10000 into FlatMap/std::map/butil::PooledMap/butil::hash_map takes 45/304/270/115 +// Randomly erasing 10000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes 7/299/246/88 + +template > +class PooledMap + : public std::map, BLOCK_SIZE> > { + +}; + +namespace details { +// Specialize for void +template +class PooledAllocator { +public: + typedef void * pointer; + typedef const void* const_pointer; + typedef void value_type; + template struct rebind { + typedef PooledAllocator other; + }; +}; + +template +class PooledAllocator { +public: + typedef T1 value_type; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + typedef T1* pointer; + typedef const T1* const_pointer; + typedef T1& reference; + typedef const T1& const_reference; + template struct rebind { + typedef PooledAllocator other; + }; + +public: + PooledAllocator() {} + PooledAllocator(const PooledAllocator&) {} + template + PooledAllocator(const PooledAllocator&) {} + void operator=(const PooledAllocator&) {} + template + void operator=(const PooledAllocator&) {} + + void swap(PooledAllocator& other) { _pool.swap(other._pool); } + + // Convert references to pointers. + pointer address(reference r) const { return &r; } + const_pointer address(const_reference r) const { return &r; } + + // Allocate storage for n values of T1. + pointer allocate(size_type n, PooledAllocator::const_pointer = 0) { + if (n == 1) { + return (pointer)_pool.get(); + } else { + return (pointer)malloc(n * sizeof(T1)); + } + } + + // Deallocate storage obtained by a call to allocate. + void deallocate(pointer p, size_type n) { + if (n == 1) { + return _pool.back(p); + } else { + free(p); + } + } + + // Return the largest possible storage available through a call to allocate. + size_type max_size() const { return 0xFFFFFFFF / sizeof(T1); } + + void construct(pointer ptr) { ::new (ptr) T1; } + void construct(pointer ptr, const T1& val) { ::new (ptr) T1(val); } + template void construct(pointer ptr, const U1& val) + { ::new (ptr) T1(val); } + + void destroy(pointer p) { p->T1::~T1(); } + +private: + butil::SingleThreadedPool _pool; +}; + +// Return true if b could be used to deallocate storage obtained through a +// and vice versa. It's clear that our allocator can't be exchanged. +template +bool operator==(const PooledAllocator&, const PooledAllocator&) +{ return false; } +template +bool operator!=(const PooledAllocator& a, const PooledAllocator& b) +{ return !(a == b); } + +} // namespace details +} // namespace butil + +// Since this allocator can't be exchanged(check impl. of operator==) nor +// copied, specializing swap() is a must to make map.swap() work. +#if !defined(BUTIL_CXX11_ENABLED) +#include // std::swap until C++11 +#else +#include // std::swap since C++11 +#endif + +namespace std { +template +inline void swap(::butil::details::PooledAllocator &lhs, + ::butil::details::PooledAllocator &rhs){ + lhs.swap(rhs); +} +} // namespace std + +#endif // BUTIL_POOLED_MAP_H diff --git a/src/butil/containers/scoped_ptr_hash_map.h b/src/butil/containers/scoped_ptr_hash_map.h new file mode 100644 index 0000000..a24e872 --- /dev/null +++ b/src/butil/containers/scoped_ptr_hash_map.h @@ -0,0 +1,157 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_CONTAINERS_SCOPED_PTR_HASH_MAP_H_ +#define BUTIL_CONTAINERS_SCOPED_PTR_HASH_MAP_H_ + +#include +#include + +#include "butil/basictypes.h" +#include "butil/containers/hash_tables.h" +#include "butil/logging.h" +#include "butil/memory/scoped_ptr.h" +#include "butil/stl_util.h" + +namespace butil { + +// This type acts like a hash_map >, based on top of +// butil::hash_map. The ScopedPtrHashMap has ownership of all values in the data +// structure. +template +class ScopedPtrHashMap { + typedef butil::hash_map Container; + + public: + typedef typename Container::key_type key_type; + typedef typename Container::mapped_type mapped_type; + typedef typename Container::value_type value_type; + typedef typename Container::iterator iterator; + typedef typename Container::const_iterator const_iterator; + + ScopedPtrHashMap() {} + + ~ScopedPtrHashMap() { clear(); } + + void swap(ScopedPtrHashMap& other) { + data_.swap(other.data_); + } + + // Replaces value but not key if key is already present. + iterator set(const Key& key, scoped_ptr data) { + iterator it = find(key); + if (it != end()) { + delete it->second; + it->second = data.release(); + return it; + } + + return data_.insert(std::make_pair(key, data.release())).first; + } + + // Does nothing if key is already present + std::pair add(const Key& key, scoped_ptr data) { + std::pair result = + data_.insert(std::make_pair(key, data.get())); + if (result.second) + ignore_result(data.release()); + return result; + } + + void erase(iterator it) { + delete it->second; + data_.erase(it); + } + + size_t erase(const Key& k) { + iterator it = data_.find(k); + if (it == data_.end()) + return 0; + erase(it); + return 1; + } + + scoped_ptr take(iterator it) { + DCHECK(it != data_.end()); + if (it == data_.end()) + return scoped_ptr(); + + scoped_ptr ret(it->second); + it->second = NULL; + return ret.Pass(); + } + + scoped_ptr take(const Key& k) { + iterator it = find(k); + if (it == data_.end()) + return scoped_ptr(); + + return take(it); + } + + scoped_ptr take_and_erase(iterator it) { + DCHECK(it != data_.end()); + if (it == data_.end()) + return scoped_ptr(); + + scoped_ptr ret(it->second); + data_.erase(it); + return ret.Pass(); + } + + scoped_ptr take_and_erase(const Key& k) { + iterator it = find(k); + if (it == data_.end()) + return scoped_ptr(); + + return take_and_erase(it); + } + + // Returns the element in the hash_map that matches the given key. + // If no such element exists it returns NULL. + Value* get(const Key& k) const { + const_iterator it = find(k); + if (it == end()) + return NULL; + return it->second; + } + + inline bool contains(const Key& k) const { return data_.count(k) > 0; } + + inline void clear() { STLDeleteValues(&data_); } + + inline const_iterator find(const Key& k) const { return data_.find(k); } + inline iterator find(const Key& k) { return data_.find(k); } + + inline size_t count(const Key& k) const { return data_.count(k); } + inline std::pair equal_range( + const Key& k) const { + return data_.equal_range(k); + } + inline std::pair equal_range(const Key& k) { + return data_.equal_range(k); + } + + inline size_t size() const { return data_.size(); } + inline size_t max_size() const { return data_.max_size(); } + + inline bool empty() const { return data_.empty(); } + + inline size_t bucket_count() const { return data_.bucket_count(); } + inline void resize(size_t size) { return data_.resize(size); } + + inline iterator begin() { return data_.begin(); } + inline const_iterator begin() const { return data_.begin(); } + inline iterator end() { return data_.end(); } + inline const_iterator end() const { return data_.end(); } + + private: + Container data_; + + DISALLOW_COPY_AND_ASSIGN(ScopedPtrHashMap); +}; + +} // namespace butil + +#endif // BUTIL_CONTAINERS_SCOPED_PTR_HASH_MAP_H_ diff --git a/src/butil/containers/small_map.h b/src/butil/containers/small_map.h new file mode 100644 index 0000000..4b619a1 --- /dev/null +++ b/src/butil/containers/small_map.h @@ -0,0 +1,652 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_CONTAINERS_SMALL_MAP_H_ +#define BUTIL_CONTAINERS_SMALL_MAP_H_ + +#include +#include +#include + +#include "butil/basictypes.h" +#include "butil/containers/hash_tables.h" +#include "butil/logging.h" +#include "butil/memory/manual_constructor.h" + +namespace butil { + +// An STL-like associative container which starts out backed by a simple +// array but switches to some other container type if it grows beyond a +// fixed size. +// +// WHAT TYPE OF MAP SHOULD YOU USE? +// -------------------------------- +// +// - std::map should be the default if you're not sure, since it's the most +// difficult to mess up. Generally this is backed by a red-black tree. It +// will generate a lot of code (if you use a common key type like int or +// string the linker will probably emiminate the duplicates). It will +// do heap allocations for each element. +// +// - If you only ever keep a couple of items and have very simple usage, +// consider whether a using a vector and brute-force searching it will be +// the most efficient. It's not a lot of generated code (less then a +// red-black tree if your key is "weird" and not eliminated as duplicate of +// something else) and will probably be faster and do fewer heap allocations +// than std::map if you have just a couple of items. +// +// - butil::hash_map should be used if you need O(1) lookups. It may waste +// space in the hash table, and it can be easy to write correct-looking +// code with the default hash function being wrong or poorly-behaving. +// +// - SmallMap combines the performance benefits of the brute-force-searched +// vector for small cases (no extra heap allocations), but can efficiently +// fall back if you end up adding many items. It will generate more code +// than std::map (at least 160 bytes for operator[]) which is bad if you +// have a "weird" key where map functions can't be +// duplicate-code-eliminated. If you have a one-off key and aren't in +// performance-critical code, this bloat may negate some of the benefits and +// you should consider on of the other options. +// +// SmallMap will pick up the comparator from the underlying map type. In +// std::map (and in MSVC additionally hash_map) only a "less" operator is +// defined, which requires us to do two comparisons per element when doing the +// brute-force search in the simple array. +// +// We define default overrides for the common map types to avoid this +// double-compare, but you should be aware of this if you use your own +// operator< for your map and supply yor own version of == to the SmallMap. +// You can use regular operator== by just doing: +// +// butil::SmallMap, 4, std::equal_to > +// +// +// USAGE +// ----- +// +// NormalMap: The map type to fall back to. This also defines the key +// and value types for the SmallMap. +// kArraySize: The size of the initial array of results. This will be +// allocated with the SmallMap object rather than separately on +// the heap. Once the map grows beyond this size, the map type +// will be used instead. +// EqualKey: A functor which tests two keys for equality. If the wrapped +// map type has a "key_equal" member (hash_map does), then that will +// be used by default. If the wrapped map type has a strict weak +// ordering "key_compare" (std::map does), that will be used to +// implement equality by default. +// MapInit: A functor that takes a ManualConstructor* and uses it to +// initialize the map. This functor will be called at most once per +// SmallMap, when the map exceeds the threshold of kArraySize and we +// are about to copy values from the array to the map. The functor +// *must* call one of the Init() methods provided by +// ManualConstructor, since after it runs we assume that the NormalMap +// has been initialized. +// +// example: +// butil::SmallMap< std::map > days; +// days["sunday" ] = 0; +// days["monday" ] = 1; +// days["tuesday" ] = 2; +// days["wednesday"] = 3; +// days["thursday" ] = 4; +// days["friday" ] = 5; +// days["saturday" ] = 6; +// +// You should assume that SmallMap might invalidate all the iterators +// on any call to erase(), insert() and operator[]. + +namespace internal { + +template +class SmallMapDefaultInit { + public: + void operator()(ManualConstructor* map) const { + map->Init(); + } +}; + +// has_key_equal::value is true iff there exists a type M::key_equal. This is +// used to dispatch to one of the select_equal_key<> metafunctions below. +template +struct has_key_equal { + typedef char sml; // "small" is sometimes #defined so we use an abbreviation. + typedef struct { char dummy[2]; } big; + // Two functions, one accepts types that have a key_equal member, and one that + // accepts anything. They each return a value of a different size, so we can + // determine at compile-time which function would have been called. + template static big test(typename U::key_equal*); + template static sml test(...); + // Determines if M::key_equal exists by looking at the size of the return + // type of the compiler-chosen test() function. + static const bool value = (sizeof(test(0)) == sizeof(big)); +}; +template const bool has_key_equal::value; + +// Base template used for map types that do NOT have an M::key_equal member, +// e.g., std::map<>. These maps have a strict weak ordering comparator rather +// than an equality functor, so equality will be implemented in terms of that +// comparator. +// +// There's a partial specialization of this template below for map types that do +// have an M::key_equal member. +template +struct select_equal_key { + struct equal_key { + bool operator()(const typename M::key_type& left, + const typename M::key_type& right) { + // Implements equality in terms of a strict weak ordering comparator. + typename M::key_compare comp; + return !comp(left, right) && !comp(right, left); + } + }; +}; + +// Provide overrides to use operator== for key compare for the "normal" map and +// hash map types. If you override the default comparator or allocator for a +// map or hash_map, or use another type of map, this won't get used. +// +// If we switch to using std::unordered_map for butil::hash_map, then the +// hash_map specialization can be removed. +template +struct select_equal_key< std::map, false> { + struct equal_key { + bool operator()(const KeyType& left, const KeyType& right) { + return left == right; + } + }; +}; +template +struct select_equal_key< butil::hash_map, false> { + struct equal_key { + bool operator()(const KeyType& left, const KeyType& right) { + return left == right; + } + }; +}; + +// Partial template specialization handles case where M::key_equal exists, e.g., +// hash_map<>. +template +struct select_equal_key { + typedef typename M::key_equal equal_key; +}; + +} // namespace internal + +template ::value>::equal_key, + typename MapInit = internal::SmallMapDefaultInit > +class SmallMap { + // We cannot rely on the compiler to reject array of size 0. In + // particular, gcc 2.95.3 does it but later versions allow 0-length + // arrays. Therefore, we explicitly reject non-positive kArraySize + // here. + COMPILE_ASSERT(kArraySize > 0, default_initial_size_should_be_positive); + + public: + typedef typename NormalMap::key_type key_type; + typedef typename NormalMap::mapped_type data_type; + typedef typename NormalMap::mapped_type mapped_type; + typedef typename NormalMap::value_type value_type; + typedef EqualKey key_equal; + + SmallMap() : size_(0), functor_(MapInit()) {} + + explicit SmallMap(const MapInit& functor) : size_(0), functor_(functor) {} + + // Allow copy-constructor and assignment, since STL allows them too. + SmallMap(const SmallMap& src) { + // size_ and functor_ are initted in InitFrom() + InitFrom(src); + } + void operator=(const SmallMap& src) { + if (&src == this) return; + + // This is not optimal. If src and dest are both using the small + // array, we could skip the teardown and reconstruct. One problem + // to be resolved is that the value_type itself is pair, and const K is not assignable. + Destroy(); + InitFrom(src); + } + ~SmallMap() { + Destroy(); + } + + class const_iterator; + + class iterator { + public: + typedef typename NormalMap::iterator::iterator_category iterator_category; + typedef typename NormalMap::iterator::value_type value_type; + typedef typename NormalMap::iterator::difference_type difference_type; + typedef typename NormalMap::iterator::pointer pointer; + typedef typename NormalMap::iterator::reference reference; + + inline iterator(): array_iter_(NULL) {} + + inline iterator& operator++() { + if (array_iter_ != NULL) { + ++array_iter_; + } else { + ++hash_iter_; + } + return *this; + } + inline iterator operator++(int /*unused*/) { + iterator result(*this); + ++(*this); + return result; + } + inline iterator& operator--() { + if (array_iter_ != NULL) { + --array_iter_; + } else { + --hash_iter_; + } + return *this; + } + inline iterator operator--(int /*unused*/) { + iterator result(*this); + --(*this); + return result; + } + inline value_type* operator->() const { + if (array_iter_ != NULL) { + return array_iter_->get(); + } else { + return hash_iter_.operator->(); + } + } + + inline value_type& operator*() const { + if (array_iter_ != NULL) { + return *array_iter_->get(); + } else { + return *hash_iter_; + } + } + + inline bool operator==(const iterator& other) const { + if (array_iter_ != NULL) { + return array_iter_ == other.array_iter_; + } else { + return other.array_iter_ == NULL && hash_iter_ == other.hash_iter_; + } + } + + inline bool operator!=(const iterator& other) const { + return !(*this == other); + } + + bool operator==(const const_iterator& other) const; + bool operator!=(const const_iterator& other) const; + + private: + friend class SmallMap; + friend class const_iterator; + inline explicit iterator(ManualConstructor* init) + : array_iter_(init) {} + inline explicit iterator(const typename NormalMap::iterator& init) + : array_iter_(NULL), hash_iter_(init) {} + + ManualConstructor* array_iter_; + typename NormalMap::iterator hash_iter_; + }; + + class const_iterator { + public: + typedef typename NormalMap::const_iterator::iterator_category + iterator_category; + typedef typename NormalMap::const_iterator::value_type value_type; + typedef typename NormalMap::const_iterator::difference_type difference_type; + typedef typename NormalMap::const_iterator::pointer pointer; + typedef typename NormalMap::const_iterator::reference reference; + + inline const_iterator(): array_iter_(NULL) {} + // Non-explicit ctor lets us convert regular iterators to const iterators + inline const_iterator(const iterator& other) + : array_iter_(other.array_iter_), hash_iter_(other.hash_iter_) {} + + inline const_iterator& operator++() { + if (array_iter_ != NULL) { + ++array_iter_; + } else { + ++hash_iter_; + } + return *this; + } + inline const_iterator operator++(int /*unused*/) { + const_iterator result(*this); + ++(*this); + return result; + } + + inline const_iterator& operator--() { + if (array_iter_ != NULL) { + --array_iter_; + } else { + --hash_iter_; + } + return *this; + } + inline const_iterator operator--(int /*unused*/) { + const_iterator result(*this); + --(*this); + return result; + } + + inline const value_type* operator->() const { + if (array_iter_ != NULL) { + return array_iter_->get(); + } else { + return hash_iter_.operator->(); + } + } + + inline const value_type& operator*() const { + if (array_iter_ != NULL) { + return *array_iter_->get(); + } else { + return *hash_iter_; + } + } + + inline bool operator==(const const_iterator& other) const { + if (array_iter_ != NULL) { + return array_iter_ == other.array_iter_; + } else { + return other.array_iter_ == NULL && hash_iter_ == other.hash_iter_; + } + } + + inline bool operator!=(const const_iterator& other) const { + return !(*this == other); + } + + private: + friend class SmallMap; + inline explicit const_iterator( + const ManualConstructor* init) + : array_iter_(init) {} + inline explicit const_iterator( + const typename NormalMap::const_iterator& init) + : array_iter_(NULL), hash_iter_(init) {} + + const ManualConstructor* array_iter_; + typename NormalMap::const_iterator hash_iter_; + }; + + iterator find(const key_type& key) { + key_equal compare; + if (size_ >= 0) { + for (int i = 0; i < size_; i++) { + if (compare(array_[i]->first, key)) { + return iterator(array_ + i); + } + } + return iterator(array_ + size_); + } else { + return iterator(map()->find(key)); + } + } + + const_iterator find(const key_type& key) const { + key_equal compare; + if (size_ >= 0) { + for (int i = 0; i < size_; i++) { + if (compare(array_[i]->first, key)) { + return const_iterator(array_ + i); + } + } + return const_iterator(array_ + size_); + } else { + return const_iterator(map()->find(key)); + } + } + + // Invalidates iterators. + data_type& operator[](const key_type& key) { + key_equal compare; + + if (size_ >= 0) { + // operator[] searches backwards, favoring recently-added + // elements. + for (int i = size_-1; i >= 0; --i) { + if (compare(array_[i]->first, key)) { + return array_[i]->second; + } + } + if (size_ == kArraySize) { + ConvertToRealMap(); + return (*map_)[key]; + } else { + array_[size_].Init(key, data_type()); + return array_[size_++]->second; + } + } else { + return (*map_)[key]; + } + } + + // Invalidates iterators. + std::pair insert(const value_type& x) { + key_equal compare; + + if (size_ >= 0) { + for (int i = 0; i < size_; i++) { + if (compare(array_[i]->first, x.first)) { + return std::make_pair(iterator(array_ + i), false); + } + } + if (size_ == kArraySize) { + ConvertToRealMap(); // Invalidates all iterators! + std::pair ret = map_->insert(x); + return std::make_pair(iterator(ret.first), ret.second); + } else { + array_[size_].Init(x); + return std::make_pair(iterator(array_ + size_++), true); + } + } else { + std::pair ret = map_->insert(x); + return std::make_pair(iterator(ret.first), ret.second); + } + } + + // Invalidates iterators. + template + void insert(InputIterator f, InputIterator l) { + while (f != l) { + insert(*f); + ++f; + } + } + + iterator begin() { + if (size_ >= 0) { + return iterator(array_); + } else { + return iterator(map_->begin()); + } + } + const_iterator begin() const { + if (size_ >= 0) { + return const_iterator(array_); + } else { + return const_iterator(map_->begin()); + } + } + + iterator end() { + if (size_ >= 0) { + return iterator(array_ + size_); + } else { + return iterator(map_->end()); + } + } + const_iterator end() const { + if (size_ >= 0) { + return const_iterator(array_ + size_); + } else { + return const_iterator(map_->end()); + } + } + + void clear() { + if (size_ >= 0) { + for (int i = 0; i < size_; i++) { + array_[i].Destroy(); + } + } else { + map_.Destroy(); + } + size_ = 0; + } + + // Invalidates iterators. + void erase(const iterator& position) { + if (size_ >= 0) { + int i = position.array_iter_ - array_; + array_[i].Destroy(); + --size_; + if (i != size_) { + array_[i].Init(*array_[size_]); + array_[size_].Destroy(); + } + } else { + map_->erase(position.hash_iter_); + } + } + + size_t erase(const key_type& key) { + iterator iter = find(key); + if (iter == end()) return 0u; + erase(iter); + return 1u; + } + + size_t count(const key_type& key) const { + return (find(key) == end()) ? 0 : 1; + } + + size_t size() const { + if (size_ >= 0) { + return static_cast(size_); + } else { + return map_->size(); + } + } + + bool empty() const { + if (size_ >= 0) { + return (size_ == 0); + } else { + return map_->empty(); + } + } + + // Returns true if we have fallen back to using the underlying map + // representation. + bool UsingFullMap() const { + return size_ < 0; + } + + inline NormalMap* map() { + CHECK(UsingFullMap()); + return map_.get(); + } + inline const NormalMap* map() const { + CHECK(UsingFullMap()); + return map_.get(); + } + + private: + int size_; // negative = using hash_map + + MapInit functor_; + + // We want to call constructors and destructors manually, but we don't + // want to allocate and deallocate the memory used for them separately. + // So, we use this crazy ManualConstructor class. + // + // Since array_ and map_ are mutually exclusive, we'll put them in a + // union, too. We add in a dummy_ value which quiets MSVC from otherwise + // giving an erroneous "union member has copy constructor" error message + // (C2621). This dummy member has to come before array_ to quiet the + // compiler. + // + // TODO(brettw) remove this and use C++11 unions when we require C++11. + union { + ManualConstructor dummy_; + ManualConstructor array_[kArraySize]; + ManualConstructor map_; + }; + + void ConvertToRealMap() { + // Move the current elements into a temporary array. + ManualConstructor temp_array[kArraySize]; + + for (int i = 0; i < kArraySize; i++) { + temp_array[i].Init(*array_[i]); + array_[i].Destroy(); + } + + // Initialize the map. + size_ = -1; + functor_(&map_); + + // Insert elements into it. + for (int i = 0; i < kArraySize; i++) { + map_->insert(*temp_array[i]); + temp_array[i].Destroy(); + } + } + + // Helpers for constructors and destructors. + void InitFrom(const SmallMap& src) { + functor_ = src.functor_; + size_ = src.size_; + if (src.size_ >= 0) { + for (int i = 0; i < size_; i++) { + array_[i].Init(*src.array_[i]); + } + } else { + functor_(&map_); + (*map_.get()) = (*src.map_.get()); + } + } + void Destroy() { + if (size_ >= 0) { + for (int i = 0; i < size_; i++) { + array_[i].Destroy(); + } + } else { + map_.Destroy(); + } + } +}; + +template +inline bool SmallMap::iterator::operator==( + const const_iterator& other) const { + return other == *this; +} +template +inline bool SmallMap::iterator::operator!=( + const const_iterator& other) const { + return other != *this; +} + +} // namespace butil + +#endif // BUTIL_CONTAINERS_SMALL_MAP_H_ diff --git a/src/butil/containers/stack_container.h b/src/butil/containers/stack_container.h new file mode 100644 index 0000000..5679ab8 --- /dev/null +++ b/src/butil/containers/stack_container.h @@ -0,0 +1,276 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_CONTAINERS_STACK_CONTAINER_H_ +#define BUTIL_CONTAINERS_STACK_CONTAINER_H_ + +#include +#include + +#include "butil/basictypes.h" +#include "butil/memory/aligned_memory.h" +#include "butil/strings/string16.h" +#include "butil/build_config.h" + +namespace butil { + +// This allocator can be used with STL containers to provide a stack buffer +// from which to allocate memory and overflows onto the heap. This stack buffer +// would be allocated on the stack and allows us to avoid heap operations in +// some situations. +// +// STL likes to make copies of allocators, so the allocator itself can't hold +// the data. Instead, we make the creator responsible for creating a +// StackAllocator::Source which contains the data. Copying the allocator +// merely copies the pointer to this shared source, so all allocators created +// based on our allocator will share the same stack buffer. +// +// This stack buffer implementation is very simple. The first allocation that +// fits in the stack buffer will use the stack buffer. Any subsequent +// allocations will not use the stack buffer, even if there is unused room. +// This makes it appropriate for array-like containers, but the caller should +// be sure to reserve() in the container up to the stack buffer size. Otherwise +// the container will allocate a small array which will "use up" the stack +// buffer. +template +class StackAllocator : public std::allocator { + public: +#if __cplusplus >= 202002L + typedef typename std::allocator_traits > Allocator; +#else + typedef typename std::allocator Allocator; +#endif + + typedef typename Allocator::pointer pointer; + typedef typename Allocator::size_type size_type; + + // Backing store for the allocator. The container owner is responsible for + // maintaining this for as long as any containers using this allocator are + // live. + struct Source { + Source() : used_stack_buffer_(false) { + } + + // Casts the buffer in its right type. + T* stack_buffer() { return stack_buffer_.template data_as(); } + const T* stack_buffer() const { + return stack_buffer_.template data_as(); + } + + // The buffer itself. It is not of type T because we don't want the + // constructors and destructors to be automatically called. Define a POD + // buffer of the right size instead. + butil::AlignedMemory stack_buffer_; +#if defined(__GNUC__) && !defined(ARCH_CPU_X86_FAMILY) + COMPILE_ASSERT(ALIGNOF(T) <= 16, crbug_115612); +#endif + + // Set when the stack buffer is used for an allocation. We do not track + // how much of the buffer is used, only that somebody is using it. + bool used_stack_buffer_; + }; + + // Used by containers when they want to refer to an allocator of type U. + template + struct rebind { + typedef StackAllocator other; + }; + + // For the straight up copy c-tor, we can share storage. + StackAllocator(const StackAllocator& rhs) + : std::allocator(), source_(rhs.source_) { + } + + // ISO C++ requires the following constructor to be defined, + // and std::vector in VC++2008SP1 Release fails with an error + // in the class _Container_base_aux_alloc_real (from ) + // if the constructor does not exist. + // For this constructor, we cannot share storage; there's + // no guarantee that the Source buffer of Ts is large enough + // for Us. + // TODO: If we were fancy pants, perhaps we could share storage + // iff sizeof(T) == sizeof(U). + template + StackAllocator(const StackAllocator& other) + : source_(NULL) { + } + + // This constructor must exist. It creates a default allocator that doesn't + // actually have a stack buffer. glibc's std::string() will compare the + // current allocator against the default-constructed allocator, so this + // should be fast. + StackAllocator() : source_(NULL) { + } + + explicit StackAllocator(Source* source) : source_(source) { + } + + // Actually do the allocation. Use the stack buffer if nobody has used it yet + // and the size requested fits. Otherwise, fall through to the standard + // allocator. + pointer allocate(size_type n, void* hint = 0) { + if (source_ != NULL && !source_->used_stack_buffer_ + && n <= stack_capacity) { + source_->used_stack_buffer_ = true; + return source_->stack_buffer(); + } else { +#if __cplusplus >= 202002L + (void)hint; + return std::allocator::allocate(n); +#else + return std::allocator::allocate(n, hint); +#endif + } + } + + // Free: when trying to free the stack buffer, just mark it as free. For + // non-stack-buffer pointers, just fall though to the standard allocator. + void deallocate(pointer p, size_type n) { + if (source_ != NULL && p == source_->stack_buffer()) + source_->used_stack_buffer_ = false; + else + std::allocator::deallocate(p, n); + } + + private: + Source* source_; +}; + +// A wrapper around STL containers that maintains a stack-sized buffer that the +// initial capacity of the vector is based on. Growing the container beyond the +// stack capacity will transparently overflow onto the heap. The container must +// support reserve(). +// +// WATCH OUT: the ContainerType MUST use the proper StackAllocator for this +// type. This object is really intended to be used only internally. You'll want +// to use the wrappers below for different types. +template +class StackContainer { + public: + typedef TContainerType ContainerType; + typedef typename ContainerType::value_type ContainedType; + typedef StackAllocator Allocator; + + // Allocator must be constructed before the container! + StackContainer() : allocator_(&stack_data_), container_(allocator_) { + // Make the container use the stack allocation by reserving our buffer size + // before doing anything else. + container_.reserve(stack_capacity); + } + + // Getters for the actual container. + // + // Danger: any copies of this made using the copy constructor must have + // shorter lifetimes than the source. The copy will share the same allocator + // and therefore the same stack buffer as the original. Use std::copy to + // copy into a "real" container for longer-lived objects. + ContainerType& container() { return container_; } + const ContainerType& container() const { return container_; } + + // Support operator-> to get to the container. This allows nicer syntax like: + // StackContainer<...> foo; + // std::sort(foo->begin(), foo->end()); + ContainerType* operator->() { return &container_; } + const ContainerType* operator->() const { return &container_; } + +#ifdef UNIT_TEST + // Retrieves the stack source so that that unit tests can verify that the + // buffer is being used properly. + const typename Allocator::Source& stack_data() const { + return stack_data_; + } +#endif + + protected: + typename Allocator::Source stack_data_; + Allocator allocator_; + ContainerType container_; + + DISALLOW_COPY_AND_ASSIGN(StackContainer); +}; + +// StackString ----------------------------------------------------------------- + +template +class StackString : public StackContainer< + std::basic_string, + StackAllocator >, + stack_capacity> { + public: + StackString() : StackContainer< + std::basic_string, + StackAllocator >, + stack_capacity>() { + } + + private: + DISALLOW_COPY_AND_ASSIGN(StackString); +}; + +// StackStrin16 ---------------------------------------------------------------- + +template +class StackString16 : public StackContainer< + std::basic_string >, + stack_capacity> { + public: + StackString16() : StackContainer< + std::basic_string >, + stack_capacity>() { + } + + private: + DISALLOW_COPY_AND_ASSIGN(StackString16); +}; + +// StackVector ----------------------------------------------------------------- + +// Example: +// StackVector foo; +// foo->push_back(22); // we have overloaded operator-> +// foo[0] = 10; // as well as operator[] +template +class StackVector : public StackContainer< + std::vector >, + stack_capacity> { + public: + StackVector() : StackContainer< + std::vector >, + stack_capacity>() { + } + + // We need to put this in STL containers sometimes, which requires a copy + // constructor. We can't call the regular copy constructor because that will + // take the stack buffer from the original. Here, we create an empty object + // and make a stack buffer of its own. + StackVector(const StackVector& other) + : StackContainer< + std::vector >, + stack_capacity>() { + this->container().assign(other->begin(), other->end()); + } + + StackVector& operator=( + const StackVector& other) { + this->container().assign(other->begin(), other->end()); + return *this; + } + + // Vectors are commonly indexed, which isn't very convenient even with + // operator-> (using "->at()" does exception stuff we don't want). + T& operator[](size_t i) { return this->container().operator[](i); } + const T& operator[](size_t i) const { + return this->container().operator[](i); + } +}; + +} // namespace butil + +#endif // BUTIL_CONTAINERS_STACK_CONTAINER_H_ diff --git a/src/butil/cpu.cc b/src/butil/cpu.cc new file mode 100644 index 0000000..aa2fdfb --- /dev/null +++ b/src/butil/cpu.cc @@ -0,0 +1,244 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/cpu.h" + +#include + +#include + +#include "butil/basictypes.h" +#include "butil/build_config.h" + +#if defined(ARCH_CPU_ARM_FAMILY) && (defined(OS_ANDROID) || defined(OS_LINUX)) +#include "butil/file_util.h" +#include "butil/lazy_instance.h" +#endif + +#if defined(ARCH_CPU_X86_FAMILY) +#if defined(_MSC_VER) +#include +#include // For _xgetbv() +#endif +#endif + +namespace butil { + +CPU::CPU() + : signature_(0), + type_(0), + family_(0), + model_(0), + stepping_(0), + ext_model_(0), + ext_family_(0), + has_mmx_(false), + has_sse_(false), + has_sse2_(false), + has_sse3_(false), + has_ssse3_(false), + has_sse41_(false), + has_sse42_(false), + has_avx_(false), + has_avx_hardware_(false), + has_aesni_(false), + has_non_stop_time_stamp_counter_(false), + cpu_vendor_("unknown") { + Initialize(); +} + +namespace { + +#if defined(ARCH_CPU_X86_FAMILY) +#ifndef _MSC_VER + +#if defined(__pic__) && defined(__i386__) + +void __cpuid(int cpu_info[4], int info_type) { + __asm__ volatile ( + "mov %%ebx, %%edi\n" + "cpuid\n" + "xchg %%edi, %%ebx\n" + : "=a"(cpu_info[0]), "=D"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3]) + : "a"(info_type) + ); +} + +#else + +void __cpuid(int cpu_info[4], int info_type) { + __asm__ volatile ( + "cpuid \n\t" + : "=a"(cpu_info[0]), "=b"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3]) + : "a"(info_type) + ); +} + +#endif + +// _xgetbv returns the value of an Intel Extended Control Register (XCR). +// Currently only XCR0 is defined by Intel so |xcr| should always be zero. +uint64_t _xgetbv(uint32_t xcr) { + uint32_t eax, edx; + +// NOTE(gejun): xgetbv does not exist in gcc before 4.4, disable the use of +// AVX instruction set. +#if defined(COMPILER_GCC) && \ + (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)) + __asm__ volatile ("xgetbv" : "=a" (eax), "=d" (edx) : "c" (xcr)); +#else + __asm__ volatile (".byte 0x0f, 0x01, 0xd0" : "=a"(eax),"=d"(edx) : "c"(xcr) : ); +#endif + return (static_cast(edx) << 32) | eax; +} + +#endif // !_MSC_VER +#endif // ARCH_CPU_X86_FAMILY + +#if defined(ARCH_CPU_ARM_FAMILY) && (defined(OS_ANDROID) || defined(OS_LINUX)) + +// Returns the string found in /proc/cpuinfo under the key "model name" or +// "Processor". "model name" is used in Linux 3.8 and later (3.7 and later for +// arm64) and is shown once per CPU. "Processor" is used in earler versions and +// is shown only once at the top of /proc/cpuinfo regardless of the number CPUs. +std::string ParseCpuInfo() { + const char kModelNamePrefix[] = "model name\t: "; + const char kProcessorPrefix[] = "Processor\t: "; + std::string contents; + ReadFileToString(FilePath("/proc/cpuinfo"), &contents); + DCHECK(!contents.empty()); + std::string cpu_brand; + if (!contents.empty()) { + std::istringstream iss(contents); + std::string line; + while (std::getline(iss, line)) { + if (line.compare(0, strlen(kModelNamePrefix), kModelNamePrefix) == 0) { + cpu_brand.assign(line.substr(strlen(kModelNamePrefix))); + break; + } + if (line.compare(0, strlen(kProcessorPrefix), kProcessorPrefix) == 0) { + cpu_brand.assign(line.substr(strlen(kProcessorPrefix))); + break; + } + } + } + return cpu_brand; +} + +class LazyCpuInfoValue { + public: + LazyCpuInfoValue() : value_(ParseCpuInfo()) {} + const std::string& value() { return value_; } + + private: + const std::string value_; + DISALLOW_COPY_AND_ASSIGN(LazyCpuInfoValue); +}; + +butil::LazyInstance g_lazy_cpu_brand = + LAZY_INSTANCE_INITIALIZER; + +const std::string& CpuBrandInfo() { + return g_lazy_cpu_brand.Get().value(); +} + +#endif // defined(ARCH_CPU_ARM_FAMILY) && (defined(OS_ANDROID) || + // defined(OS_LINUX)) + +} // anonymous namespace + +void CPU::Initialize() { +#if defined(ARCH_CPU_X86_FAMILY) + int cpu_info[4] = {-1}; + char cpu_string[48]; + + // __cpuid with an InfoType argument of 0 returns the number of + // valid Ids in CPUInfo[0] and the CPU identification string in + // the other three array elements. The CPU identification string is + // not in linear order. The code below arranges the information + // in a human readable form. The human readable order is CPUInfo[1] | + // CPUInfo[3] | CPUInfo[2]. CPUInfo[2] and CPUInfo[3] are swapped + // before using memcpy to copy these three array elements to cpu_string. + __cpuid(cpu_info, 0); + int num_ids = cpu_info[0]; + std::swap(cpu_info[2], cpu_info[3]); + memcpy(cpu_string, &cpu_info[1], 3 * sizeof(cpu_info[1])); + cpu_vendor_.assign(cpu_string, 3 * sizeof(cpu_info[1])); + + // Interpret CPU feature information. + if (num_ids > 0) { + __cpuid(cpu_info, 1); + signature_ = cpu_info[0]; + stepping_ = cpu_info[0] & 0xf; + model_ = ((cpu_info[0] >> 4) & 0xf) + ((cpu_info[0] >> 12) & 0xf0); + family_ = (cpu_info[0] >> 8) & 0xf; + type_ = (cpu_info[0] >> 12) & 0x3; + ext_model_ = (cpu_info[0] >> 16) & 0xf; + ext_family_ = (cpu_info[0] >> 20) & 0xff; + has_mmx_ = (cpu_info[3] & 0x00800000) != 0; + has_sse_ = (cpu_info[3] & 0x02000000) != 0; + has_sse2_ = (cpu_info[3] & 0x04000000) != 0; + has_sse3_ = (cpu_info[2] & 0x00000001) != 0; + has_ssse3_ = (cpu_info[2] & 0x00000200) != 0; + has_sse41_ = (cpu_info[2] & 0x00080000) != 0; + has_sse42_ = (cpu_info[2] & 0x00100000) != 0; + has_avx_hardware_ = + (cpu_info[2] & 0x10000000) != 0; + // AVX instructions will generate an illegal instruction exception unless + // a) they are supported by the CPU, + // b) XSAVE is supported by the CPU and + // c) XSAVE is enabled by the kernel. + // See http://software.intel.com/en-us/blogs/2011/04/14/is-avx-enabled + // + // In addition, we have observed some crashes with the xgetbv instruction + // even after following Intel's example code. (See crbug.com/375968.) + // Because of that, we also test the XSAVE bit because its description in + // the CPUID documentation suggests that it signals xgetbv support. + has_avx_ = + has_avx_hardware_ && + (cpu_info[2] & 0x04000000) != 0 /* XSAVE */ && + (cpu_info[2] & 0x08000000) != 0 /* OSXSAVE */ && + (_xgetbv(0) & 6) == 6 /* XSAVE enabled by kernel */; + has_aesni_ = (cpu_info[2] & 0x02000000) != 0; + } + + // Get the brand string of the cpu. + __cpuid(cpu_info, 0x80000000); + const int parameter_end = 0x80000004; + int max_parameter = cpu_info[0]; + + if (cpu_info[0] >= parameter_end) { + char* cpu_string_ptr = cpu_string; + + for (int parameter = 0x80000002; parameter <= parameter_end && + cpu_string_ptr < &cpu_string[sizeof(cpu_string)]; parameter++) { + __cpuid(cpu_info, parameter); + memcpy(cpu_string_ptr, cpu_info, sizeof(cpu_info)); + cpu_string_ptr += sizeof(cpu_info); + } + cpu_brand_.assign(cpu_string, cpu_string_ptr - cpu_string); + } + + const int parameter_containing_non_stop_time_stamp_counter = 0x80000007; + if (max_parameter >= parameter_containing_non_stop_time_stamp_counter) { + __cpuid(cpu_info, parameter_containing_non_stop_time_stamp_counter); + has_non_stop_time_stamp_counter_ = (cpu_info[3] & (1 << 8)) != 0; + } +#elif defined(ARCH_CPU_ARM_FAMILY) && (defined(OS_ANDROID) || defined(OS_LINUX)) + cpu_brand_.assign(CpuBrandInfo()); +#endif +} + +CPU::IntelMicroArchitecture CPU::GetIntelMicroArchitecture() const { + if (has_avx()) return AVX; + if (has_sse42()) return SSE42; + if (has_sse41()) return SSE41; + if (has_ssse3()) return SSSE3; + if (has_sse3()) return SSE3; + if (has_sse2()) return SSE2; + if (has_sse()) return SSE; + return PENTIUM; +} + +} // namespace butil diff --git a/src/butil/cpu.h b/src/butil/cpu.h new file mode 100644 index 0000000..465df70 --- /dev/null +++ b/src/butil/cpu.h @@ -0,0 +1,90 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_CPU_H_ +#define BUTIL_CPU_H_ + +#include + +#include "butil/base_export.h" + +namespace butil { + +// Query information about the processor. +class BUTIL_EXPORT CPU { + public: + // Constructor + CPU(); + + enum IntelMicroArchitecture { + PENTIUM, + SSE, + SSE2, + SSE3, + SSSE3, + SSE41, + SSE42, + AVX, + MAX_INTEL_MICRO_ARCHITECTURE + }; + + // Accessors for CPU information. + const std::string& vendor_name() const { return cpu_vendor_; } + int signature() const { return signature_; } + int stepping() const { return stepping_; } + int model() const { return model_; } + int family() const { return family_; } + int type() const { return type_; } + int extended_model() const { return ext_model_; } + int extended_family() const { return ext_family_; } + bool has_mmx() const { return has_mmx_; } + bool has_sse() const { return has_sse_; } + bool has_sse2() const { return has_sse2_; } + bool has_sse3() const { return has_sse3_; } + bool has_ssse3() const { return has_ssse3_; } + bool has_sse41() const { return has_sse41_; } + bool has_sse42() const { return has_sse42_; } + bool has_avx() const { return has_avx_; } + // has_avx_hardware returns true when AVX is present in the CPU. This might + // differ from the value of |has_avx()| because |has_avx()| also tests for + // operating system support needed to actually call AVX instuctions. + // Note: you should never need to call this function. It was added in order + // to workaround a bug in NSS but |has_avx()| is what you want. + bool has_avx_hardware() const { return has_avx_hardware_; } + bool has_aesni() const { return has_aesni_; } + bool has_non_stop_time_stamp_counter() const { + return has_non_stop_time_stamp_counter_; + } + IntelMicroArchitecture GetIntelMicroArchitecture() const; + const std::string& cpu_brand() const { return cpu_brand_; } + + private: + // Query the processor for CPUID information. + void Initialize(); + + int signature_; // raw form of type, family, model, and stepping + int type_; // process type + int family_; // family of the processor + int model_; // model of processor + int stepping_; // processor revision number + int ext_model_; + int ext_family_; + bool has_mmx_; + bool has_sse_; + bool has_sse2_; + bool has_sse3_; + bool has_ssse3_; + bool has_sse41_; + bool has_sse42_; + bool has_avx_; + bool has_avx_hardware_; + bool has_aesni_; + bool has_non_stop_time_stamp_counter_; + std::string cpu_vendor_; + std::string cpu_brand_; +}; + +} // namespace butil + +#endif // BUTIL_CPU_H_ diff --git a/src/butil/crc32c.cc b/src/butil/crc32c.cc new file mode 100644 index 0000000..4b942ee --- /dev/null +++ b/src/butil/crc32c.cc @@ -0,0 +1,875 @@ +// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// Copyright (c) 2011 The LevelDB Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. See the AUTHORS file for names of contributors. +// +// A portable implementation of crc32c, optimized to handle +// four bytes at a time. + +#include "butil/crc32c.h" + +#include +#include +#ifdef __SSE4_2__ +#include +#endif +#include "butil/build_config.h" + +namespace butil { +namespace crc32c { + +static const uint32_t table0_[256] = { + 0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4, + 0xc79a971f, 0x35f1141c, 0x26a1e7e8, 0xd4ca64eb, + 0x8ad958cf, 0x78b2dbcc, 0x6be22838, 0x9989ab3b, + 0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24, + 0x105ec76f, 0xe235446c, 0xf165b798, 0x030e349b, + 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384, + 0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, 0x89d76c54, + 0x5d1d08bf, 0xaf768bbc, 0xbc267848, 0x4e4dfb4b, + 0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a, + 0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35, + 0xaa64d611, 0x580f5512, 0x4b5fa6e6, 0xb93425e5, + 0x6dfe410e, 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa, + 0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45, + 0xf779deae, 0x05125dad, 0x1642ae59, 0xe4292d5a, + 0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a, + 0x7da08661, 0x8fcb0562, 0x9c9bf696, 0x6ef07595, + 0x417b1dbc, 0xb3109ebf, 0xa0406d4b, 0x522bee48, + 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957, + 0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687, + 0x0c38d26c, 0xfe53516f, 0xed03a29b, 0x1f682198, + 0x5125dad3, 0xa34e59d0, 0xb01eaa24, 0x42752927, + 0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38, + 0xdbfc821c, 0x2997011f, 0x3ac7f2eb, 0xc8ac71e8, + 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7, + 0x61c69362, 0x93ad1061, 0x80fde395, 0x72966096, + 0xa65c047d, 0x5437877e, 0x4767748a, 0xb50cf789, + 0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859, + 0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46, + 0x7198540d, 0x83f3d70e, 0x90a324fa, 0x62c8a7f9, + 0xb602c312, 0x44694011, 0x5739b3e5, 0xa55230e6, + 0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36, + 0x3cdb9bdd, 0xceb018de, 0xdde0eb2a, 0x2f8b6829, + 0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c, + 0x456cac67, 0xb7072f64, 0xa457dc90, 0x563c5f93, + 0x082f63b7, 0xfa44e0b4, 0xe9141340, 0x1b7f9043, + 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c, + 0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3, + 0x55326b08, 0xa759e80b, 0xb4091bff, 0x466298fc, + 0x1871a4d8, 0xea1a27db, 0xf94ad42f, 0x0b21572c, + 0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033, + 0xa24bb5a6, 0x502036a5, 0x4370c551, 0xb11b4652, + 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d, + 0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, 0x3bc21e9d, + 0xef087a76, 0x1d63f975, 0x0e330a81, 0xfc588982, + 0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d, + 0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622, + 0x38cc2a06, 0xcaa7a905, 0xd9f75af1, 0x2b9cd9f2, + 0xff56bd19, 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed, + 0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530, + 0x0417b1db, 0xf67c32d8, 0xe52cc12c, 0x1747422f, + 0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff, + 0x8ecee914, 0x7ca56a17, 0x6ff599e3, 0x9d9e1ae0, + 0xd3d3e1ab, 0x21b862a8, 0x32e8915c, 0xc083125f, + 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540, + 0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90, + 0x9e902e7b, 0x6cfbad78, 0x7fab5e8c, 0x8dc0dd8f, + 0xe330a81a, 0x115b2b19, 0x020bd8ed, 0xf0605bee, + 0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1, + 0x69e9f0d5, 0x9b8273d6, 0x88d28022, 0x7ab90321, + 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e, + 0xf36e6f75, 0x0105ec76, 0x12551f82, 0xe03e9c81, + 0x34f4f86a, 0xc69f7b69, 0xd5cf889d, 0x27a40b9e, + 0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e, + 0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351 +}; +static const uint32_t table1_[256] = { + 0x00000000, 0x13a29877, 0x274530ee, 0x34e7a899, + 0x4e8a61dc, 0x5d28f9ab, 0x69cf5132, 0x7a6dc945, + 0x9d14c3b8, 0x8eb65bcf, 0xba51f356, 0xa9f36b21, + 0xd39ea264, 0xc03c3a13, 0xf4db928a, 0xe7790afd, + 0x3fc5f181, 0x2c6769f6, 0x1880c16f, 0x0b225918, + 0x714f905d, 0x62ed082a, 0x560aa0b3, 0x45a838c4, + 0xa2d13239, 0xb173aa4e, 0x859402d7, 0x96369aa0, + 0xec5b53e5, 0xfff9cb92, 0xcb1e630b, 0xd8bcfb7c, + 0x7f8be302, 0x6c297b75, 0x58ced3ec, 0x4b6c4b9b, + 0x310182de, 0x22a31aa9, 0x1644b230, 0x05e62a47, + 0xe29f20ba, 0xf13db8cd, 0xc5da1054, 0xd6788823, + 0xac154166, 0xbfb7d911, 0x8b507188, 0x98f2e9ff, + 0x404e1283, 0x53ec8af4, 0x670b226d, 0x74a9ba1a, + 0x0ec4735f, 0x1d66eb28, 0x298143b1, 0x3a23dbc6, + 0xdd5ad13b, 0xcef8494c, 0xfa1fe1d5, 0xe9bd79a2, + 0x93d0b0e7, 0x80722890, 0xb4958009, 0xa737187e, + 0xff17c604, 0xecb55e73, 0xd852f6ea, 0xcbf06e9d, + 0xb19da7d8, 0xa23f3faf, 0x96d89736, 0x857a0f41, + 0x620305bc, 0x71a19dcb, 0x45463552, 0x56e4ad25, + 0x2c896460, 0x3f2bfc17, 0x0bcc548e, 0x186eccf9, + 0xc0d23785, 0xd370aff2, 0xe797076b, 0xf4359f1c, + 0x8e585659, 0x9dface2e, 0xa91d66b7, 0xbabffec0, + 0x5dc6f43d, 0x4e646c4a, 0x7a83c4d3, 0x69215ca4, + 0x134c95e1, 0x00ee0d96, 0x3409a50f, 0x27ab3d78, + 0x809c2506, 0x933ebd71, 0xa7d915e8, 0xb47b8d9f, + 0xce1644da, 0xddb4dcad, 0xe9537434, 0xfaf1ec43, + 0x1d88e6be, 0x0e2a7ec9, 0x3acdd650, 0x296f4e27, + 0x53028762, 0x40a01f15, 0x7447b78c, 0x67e52ffb, + 0xbf59d487, 0xacfb4cf0, 0x981ce469, 0x8bbe7c1e, + 0xf1d3b55b, 0xe2712d2c, 0xd69685b5, 0xc5341dc2, + 0x224d173f, 0x31ef8f48, 0x050827d1, 0x16aabfa6, + 0x6cc776e3, 0x7f65ee94, 0x4b82460d, 0x5820de7a, + 0xfbc3faf9, 0xe861628e, 0xdc86ca17, 0xcf245260, + 0xb5499b25, 0xa6eb0352, 0x920cabcb, 0x81ae33bc, + 0x66d73941, 0x7575a136, 0x419209af, 0x523091d8, + 0x285d589d, 0x3bffc0ea, 0x0f186873, 0x1cbaf004, + 0xc4060b78, 0xd7a4930f, 0xe3433b96, 0xf0e1a3e1, + 0x8a8c6aa4, 0x992ef2d3, 0xadc95a4a, 0xbe6bc23d, + 0x5912c8c0, 0x4ab050b7, 0x7e57f82e, 0x6df56059, + 0x1798a91c, 0x043a316b, 0x30dd99f2, 0x237f0185, + 0x844819fb, 0x97ea818c, 0xa30d2915, 0xb0afb162, + 0xcac27827, 0xd960e050, 0xed8748c9, 0xfe25d0be, + 0x195cda43, 0x0afe4234, 0x3e19eaad, 0x2dbb72da, + 0x57d6bb9f, 0x447423e8, 0x70938b71, 0x63311306, + 0xbb8de87a, 0xa82f700d, 0x9cc8d894, 0x8f6a40e3, + 0xf50789a6, 0xe6a511d1, 0xd242b948, 0xc1e0213f, + 0x26992bc2, 0x353bb3b5, 0x01dc1b2c, 0x127e835b, + 0x68134a1e, 0x7bb1d269, 0x4f567af0, 0x5cf4e287, + 0x04d43cfd, 0x1776a48a, 0x23910c13, 0x30339464, + 0x4a5e5d21, 0x59fcc556, 0x6d1b6dcf, 0x7eb9f5b8, + 0x99c0ff45, 0x8a626732, 0xbe85cfab, 0xad2757dc, + 0xd74a9e99, 0xc4e806ee, 0xf00fae77, 0xe3ad3600, + 0x3b11cd7c, 0x28b3550b, 0x1c54fd92, 0x0ff665e5, + 0x759baca0, 0x663934d7, 0x52de9c4e, 0x417c0439, + 0xa6050ec4, 0xb5a796b3, 0x81403e2a, 0x92e2a65d, + 0xe88f6f18, 0xfb2df76f, 0xcfca5ff6, 0xdc68c781, + 0x7b5fdfff, 0x68fd4788, 0x5c1aef11, 0x4fb87766, + 0x35d5be23, 0x26772654, 0x12908ecd, 0x013216ba, + 0xe64b1c47, 0xf5e98430, 0xc10e2ca9, 0xd2acb4de, + 0xa8c17d9b, 0xbb63e5ec, 0x8f844d75, 0x9c26d502, + 0x449a2e7e, 0x5738b609, 0x63df1e90, 0x707d86e7, + 0x0a104fa2, 0x19b2d7d5, 0x2d557f4c, 0x3ef7e73b, + 0xd98eedc6, 0xca2c75b1, 0xfecbdd28, 0xed69455f, + 0x97048c1a, 0x84a6146d, 0xb041bcf4, 0xa3e32483 +}; +static const uint32_t table2_[256] = { + 0x00000000, 0xa541927e, 0x4f6f520d, 0xea2ec073, + 0x9edea41a, 0x3b9f3664, 0xd1b1f617, 0x74f06469, + 0x38513ec5, 0x9d10acbb, 0x773e6cc8, 0xd27ffeb6, + 0xa68f9adf, 0x03ce08a1, 0xe9e0c8d2, 0x4ca15aac, + 0x70a27d8a, 0xd5e3eff4, 0x3fcd2f87, 0x9a8cbdf9, + 0xee7cd990, 0x4b3d4bee, 0xa1138b9d, 0x045219e3, + 0x48f3434f, 0xedb2d131, 0x079c1142, 0xa2dd833c, + 0xd62de755, 0x736c752b, 0x9942b558, 0x3c032726, + 0xe144fb14, 0x4405696a, 0xae2ba919, 0x0b6a3b67, + 0x7f9a5f0e, 0xdadbcd70, 0x30f50d03, 0x95b49f7d, + 0xd915c5d1, 0x7c5457af, 0x967a97dc, 0x333b05a2, + 0x47cb61cb, 0xe28af3b5, 0x08a433c6, 0xade5a1b8, + 0x91e6869e, 0x34a714e0, 0xde89d493, 0x7bc846ed, + 0x0f382284, 0xaa79b0fa, 0x40577089, 0xe516e2f7, + 0xa9b7b85b, 0x0cf62a25, 0xe6d8ea56, 0x43997828, + 0x37691c41, 0x92288e3f, 0x78064e4c, 0xdd47dc32, + 0xc76580d9, 0x622412a7, 0x880ad2d4, 0x2d4b40aa, + 0x59bb24c3, 0xfcfab6bd, 0x16d476ce, 0xb395e4b0, + 0xff34be1c, 0x5a752c62, 0xb05bec11, 0x151a7e6f, + 0x61ea1a06, 0xc4ab8878, 0x2e85480b, 0x8bc4da75, + 0xb7c7fd53, 0x12866f2d, 0xf8a8af5e, 0x5de93d20, + 0x29195949, 0x8c58cb37, 0x66760b44, 0xc337993a, + 0x8f96c396, 0x2ad751e8, 0xc0f9919b, 0x65b803e5, + 0x1148678c, 0xb409f5f2, 0x5e273581, 0xfb66a7ff, + 0x26217bcd, 0x8360e9b3, 0x694e29c0, 0xcc0fbbbe, + 0xb8ffdfd7, 0x1dbe4da9, 0xf7908dda, 0x52d11fa4, + 0x1e704508, 0xbb31d776, 0x511f1705, 0xf45e857b, + 0x80aee112, 0x25ef736c, 0xcfc1b31f, 0x6a802161, + 0x56830647, 0xf3c29439, 0x19ec544a, 0xbcadc634, + 0xc85da25d, 0x6d1c3023, 0x8732f050, 0x2273622e, + 0x6ed23882, 0xcb93aafc, 0x21bd6a8f, 0x84fcf8f1, + 0xf00c9c98, 0x554d0ee6, 0xbf63ce95, 0x1a225ceb, + 0x8b277743, 0x2e66e53d, 0xc448254e, 0x6109b730, + 0x15f9d359, 0xb0b84127, 0x5a968154, 0xffd7132a, + 0xb3764986, 0x1637dbf8, 0xfc191b8b, 0x595889f5, + 0x2da8ed9c, 0x88e97fe2, 0x62c7bf91, 0xc7862def, + 0xfb850ac9, 0x5ec498b7, 0xb4ea58c4, 0x11abcaba, + 0x655baed3, 0xc01a3cad, 0x2a34fcde, 0x8f756ea0, + 0xc3d4340c, 0x6695a672, 0x8cbb6601, 0x29faf47f, + 0x5d0a9016, 0xf84b0268, 0x1265c21b, 0xb7245065, + 0x6a638c57, 0xcf221e29, 0x250cde5a, 0x804d4c24, + 0xf4bd284d, 0x51fcba33, 0xbbd27a40, 0x1e93e83e, + 0x5232b292, 0xf77320ec, 0x1d5de09f, 0xb81c72e1, + 0xccec1688, 0x69ad84f6, 0x83834485, 0x26c2d6fb, + 0x1ac1f1dd, 0xbf8063a3, 0x55aea3d0, 0xf0ef31ae, + 0x841f55c7, 0x215ec7b9, 0xcb7007ca, 0x6e3195b4, + 0x2290cf18, 0x87d15d66, 0x6dff9d15, 0xc8be0f6b, + 0xbc4e6b02, 0x190ff97c, 0xf321390f, 0x5660ab71, + 0x4c42f79a, 0xe90365e4, 0x032da597, 0xa66c37e9, + 0xd29c5380, 0x77ddc1fe, 0x9df3018d, 0x38b293f3, + 0x7413c95f, 0xd1525b21, 0x3b7c9b52, 0x9e3d092c, + 0xeacd6d45, 0x4f8cff3b, 0xa5a23f48, 0x00e3ad36, + 0x3ce08a10, 0x99a1186e, 0x738fd81d, 0xd6ce4a63, + 0xa23e2e0a, 0x077fbc74, 0xed517c07, 0x4810ee79, + 0x04b1b4d5, 0xa1f026ab, 0x4bdee6d8, 0xee9f74a6, + 0x9a6f10cf, 0x3f2e82b1, 0xd50042c2, 0x7041d0bc, + 0xad060c8e, 0x08479ef0, 0xe2695e83, 0x4728ccfd, + 0x33d8a894, 0x96993aea, 0x7cb7fa99, 0xd9f668e7, + 0x9557324b, 0x3016a035, 0xda386046, 0x7f79f238, + 0x0b899651, 0xaec8042f, 0x44e6c45c, 0xe1a75622, + 0xdda47104, 0x78e5e37a, 0x92cb2309, 0x378ab177, + 0x437ad51e, 0xe63b4760, 0x0c158713, 0xa954156d, + 0xe5f54fc1, 0x40b4ddbf, 0xaa9a1dcc, 0x0fdb8fb2, + 0x7b2bebdb, 0xde6a79a5, 0x3444b9d6, 0x91052ba8 +}; +static const uint32_t table3_[256] = { + 0x00000000, 0xdd45aab8, 0xbf672381, 0x62228939, + 0x7b2231f3, 0xa6679b4b, 0xc4451272, 0x1900b8ca, + 0xf64463e6, 0x2b01c95e, 0x49234067, 0x9466eadf, + 0x8d665215, 0x5023f8ad, 0x32017194, 0xef44db2c, + 0xe964b13d, 0x34211b85, 0x560392bc, 0x8b463804, + 0x924680ce, 0x4f032a76, 0x2d21a34f, 0xf06409f7, + 0x1f20d2db, 0xc2657863, 0xa047f15a, 0x7d025be2, + 0x6402e328, 0xb9474990, 0xdb65c0a9, 0x06206a11, + 0xd725148b, 0x0a60be33, 0x6842370a, 0xb5079db2, + 0xac072578, 0x71428fc0, 0x136006f9, 0xce25ac41, + 0x2161776d, 0xfc24ddd5, 0x9e0654ec, 0x4343fe54, + 0x5a43469e, 0x8706ec26, 0xe524651f, 0x3861cfa7, + 0x3e41a5b6, 0xe3040f0e, 0x81268637, 0x5c632c8f, + 0x45639445, 0x98263efd, 0xfa04b7c4, 0x27411d7c, + 0xc805c650, 0x15406ce8, 0x7762e5d1, 0xaa274f69, + 0xb327f7a3, 0x6e625d1b, 0x0c40d422, 0xd1057e9a, + 0xaba65fe7, 0x76e3f55f, 0x14c17c66, 0xc984d6de, + 0xd0846e14, 0x0dc1c4ac, 0x6fe34d95, 0xb2a6e72d, + 0x5de23c01, 0x80a796b9, 0xe2851f80, 0x3fc0b538, + 0x26c00df2, 0xfb85a74a, 0x99a72e73, 0x44e284cb, + 0x42c2eeda, 0x9f874462, 0xfda5cd5b, 0x20e067e3, + 0x39e0df29, 0xe4a57591, 0x8687fca8, 0x5bc25610, + 0xb4868d3c, 0x69c32784, 0x0be1aebd, 0xd6a40405, + 0xcfa4bccf, 0x12e11677, 0x70c39f4e, 0xad8635f6, + 0x7c834b6c, 0xa1c6e1d4, 0xc3e468ed, 0x1ea1c255, + 0x07a17a9f, 0xdae4d027, 0xb8c6591e, 0x6583f3a6, + 0x8ac7288a, 0x57828232, 0x35a00b0b, 0xe8e5a1b3, + 0xf1e51979, 0x2ca0b3c1, 0x4e823af8, 0x93c79040, + 0x95e7fa51, 0x48a250e9, 0x2a80d9d0, 0xf7c57368, + 0xeec5cba2, 0x3380611a, 0x51a2e823, 0x8ce7429b, + 0x63a399b7, 0xbee6330f, 0xdcc4ba36, 0x0181108e, + 0x1881a844, 0xc5c402fc, 0xa7e68bc5, 0x7aa3217d, + 0x52a0c93f, 0x8fe56387, 0xedc7eabe, 0x30824006, + 0x2982f8cc, 0xf4c75274, 0x96e5db4d, 0x4ba071f5, + 0xa4e4aad9, 0x79a10061, 0x1b838958, 0xc6c623e0, + 0xdfc69b2a, 0x02833192, 0x60a1b8ab, 0xbde41213, + 0xbbc47802, 0x6681d2ba, 0x04a35b83, 0xd9e6f13b, + 0xc0e649f1, 0x1da3e349, 0x7f816a70, 0xa2c4c0c8, + 0x4d801be4, 0x90c5b15c, 0xf2e73865, 0x2fa292dd, + 0x36a22a17, 0xebe780af, 0x89c50996, 0x5480a32e, + 0x8585ddb4, 0x58c0770c, 0x3ae2fe35, 0xe7a7548d, + 0xfea7ec47, 0x23e246ff, 0x41c0cfc6, 0x9c85657e, + 0x73c1be52, 0xae8414ea, 0xcca69dd3, 0x11e3376b, + 0x08e38fa1, 0xd5a62519, 0xb784ac20, 0x6ac10698, + 0x6ce16c89, 0xb1a4c631, 0xd3864f08, 0x0ec3e5b0, + 0x17c35d7a, 0xca86f7c2, 0xa8a47efb, 0x75e1d443, + 0x9aa50f6f, 0x47e0a5d7, 0x25c22cee, 0xf8878656, + 0xe1873e9c, 0x3cc29424, 0x5ee01d1d, 0x83a5b7a5, + 0xf90696d8, 0x24433c60, 0x4661b559, 0x9b241fe1, + 0x8224a72b, 0x5f610d93, 0x3d4384aa, 0xe0062e12, + 0x0f42f53e, 0xd2075f86, 0xb025d6bf, 0x6d607c07, + 0x7460c4cd, 0xa9256e75, 0xcb07e74c, 0x16424df4, + 0x106227e5, 0xcd278d5d, 0xaf050464, 0x7240aedc, + 0x6b401616, 0xb605bcae, 0xd4273597, 0x09629f2f, + 0xe6264403, 0x3b63eebb, 0x59416782, 0x8404cd3a, + 0x9d0475f0, 0x4041df48, 0x22635671, 0xff26fcc9, + 0x2e238253, 0xf36628eb, 0x9144a1d2, 0x4c010b6a, + 0x5501b3a0, 0x88441918, 0xea669021, 0x37233a99, + 0xd867e1b5, 0x05224b0d, 0x6700c234, 0xba45688c, + 0xa345d046, 0x7e007afe, 0x1c22f3c7, 0xc167597f, + 0xc747336e, 0x1a0299d6, 0x782010ef, 0xa565ba57, + 0xbc65029d, 0x6120a825, 0x0302211c, 0xde478ba4, + 0x31035088, 0xec46fa30, 0x8e647309, 0x5321d9b1, + 0x4a21617b, 0x9764cbc3, 0xf54642fa, 0x2803e842 +}; + +// Lower-level versions of Get... that read directly from a character buffer +// without any bounds checking. + +static inline uint32_t DecodeFixed32(const char* ptr) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) && ARCH_CPU_LITTLE_ENDIAN + // Load the raw bytes + uint32_t result; + memcpy(&result, ptr, sizeof(result)); // gcc optimizes this to a plain load + return result; +#else + return ((static_cast(static_cast(ptr[0]))) + | (static_cast(static_cast(ptr[1])) << 8) + | (static_cast(static_cast(ptr[2])) << 16) + | (static_cast(static_cast(ptr[3])) << 24)); +#endif +} + +inline uint64_t DecodeFixed64(const char* ptr) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) && ARCH_CPU_LITTLE_ENDIAN + // Load the raw bytes + uint64_t result; + memcpy(&result, ptr, sizeof(result)); // gcc optimizes this to a plain load + return result; +#else + uint64_t lo = DecodeFixed32(ptr); + uint64_t hi = DecodeFixed32(ptr + 4); + return (hi << 32) | lo; +#endif +} + +// Used to fetch a naturally-aligned 32-bit word in little endian byte-order +static inline uint32_t LE_LOAD32(const uint8_t *p) { + return DecodeFixed32(reinterpret_cast(p)); +} + +#ifdef __SSE4_2__ +#ifdef __LP64__ +static inline uint64_t LE_LOAD64(const uint8_t *p) { + return DecodeFixed64(reinterpret_cast(p)); +} +#endif +#endif + +static inline void Slow_CRC32(uint64_t* l, uint8_t const **p) { + uint32_t c = static_cast(*l ^ LE_LOAD32(*p)); + *p += 4; + *l = table3_[c & 0xff] ^ + table2_[(c >> 8) & 0xff] ^ + table1_[(c >> 16) & 0xff] ^ + table0_[c >> 24]; + // DO it twice. + c = static_cast(*l ^ LE_LOAD32(*p)); + *p += 4; + *l = table3_[c & 0xff] ^ + table2_[(c >> 8) & 0xff] ^ + table1_[(c >> 16) & 0xff] ^ + table0_[c >> 24]; +} + +static inline void Fast_CRC32(uint64_t* l, uint8_t const **p) { +#ifdef __SSE4_2__ +#ifdef __LP64__ + *l = _mm_crc32_u64(*l, LE_LOAD64(*p)); + *p += 8; +#else + *l = _mm_crc32_u32(static_cast(*l), LE_LOAD32(*p)); + *p += 4; + *l = _mm_crc32_u32(static_cast(*l), LE_LOAD32(*p)); + *p += 4; +#endif +#else + Slow_CRC32(l, p); +#endif +} + +class FastCRC32Functor { +public: + inline void operator()(uint64_t* l, uint8_t const **p) const { + return Fast_CRC32(l , p); + } +}; + +class SlowCRC32Functor { +public: + inline void operator()(uint64_t* l, uint8_t const **p) const { + return Slow_CRC32(l , p); + } +}; + +template +uint32_t ExtendImpl(uint32_t crc, const char* buf, size_t size) { + CRC32Functor CRC32; + + const uint8_t *p = reinterpret_cast(buf); + const uint8_t *e = p + size; + uint64_t l = crc ^ 0xffffffffu; + +// Align n to (1 << m) byte boundary +#define ALIGN(n, m) ((n + ((1 << m) - 1)) & ~((1 << m) - 1)) + +#define STEP1 do { \ + int c = (l & 0xff) ^ *p++; \ + l = table0_[c] ^ (l >> 8); \ +} while (0) + + + // Point x at first 16-byte aligned byte in string. This might be + // just past the end of the string. + const uintptr_t pval = reinterpret_cast(p); + const uint8_t* x = reinterpret_cast(ALIGN(pval, 4)); + if (x <= e) { + // Process bytes until finished or p is 16-byte aligned + while (p != x) { + STEP1; + } + } + // Process bytes 16 at a time + while ((e-p) >= 16) { + CRC32(&l, &p); + CRC32(&l, &p); + } + // Process bytes 8 at a time + while ((e-p) >= 8) { + CRC32(&l, &p); + } + // Process the last few bytes + while (p != e) { + STEP1; + } +#undef STEP1 +#undef ALIGN + return static_cast(l ^ 0xffffffffu); +} + +#if defined(__riscv) && (__riscv_xlen == 64) && (defined(__riscv_zbc) || defined(__riscv_zvbc)) +#include +#if defined(__riscv_zvbc) +#include +#endif + +// RISC-V Zbc carry-less multiplication inline helpers +static inline uint64_t rv_clmul(uint64_t a, uint64_t b) { + uint64_t result; + __asm__ volatile ("clmul %0, %1, %2" : "=r"(result) : "r"(a), "r"(b)); + return result; +} + +static inline uint64_t rv_clmulh(uint64_t a, uint64_t b) { + uint64_t result; + __asm__ volatile ("clmulh %0, %1, %2" : "=r"(result) : "r"(a), "r"(b)); + return result; +} + +// Bitwise CRC32C fallback for small chunks +static inline uint32_t rv_crc32c_bitwise(uint32_t crc, const uint8_t* buf, + size_t len) { + uint32_t c = crc; + for (size_t i = 0; i < len; ++i) { + c ^= buf[i]; + for (int k = 0; k < 8; ++k) { + c = (c >> 1) ^ ((c & 1) ? 0x82F63B78U : 0); + } + } + return c; +} + +// Fold a 128-bit CRC state (lo:hi) with fold constants and XOR in new data +static inline void rv_fold_pair_xor_data(uint64_t* lo, uint64_t* hi, + uint64_t k0, uint64_t k1, + uint64_t d0, uint64_t d1) { + uint64_t l = rv_clmul(*lo, k0) ^ rv_clmul(*hi, k1); + uint64_t h = rv_clmulh(*lo, k0) ^ rv_clmulh(*hi, k1); + *lo = l ^ d0; + *hi = h ^ d1; +} + +// Fold a 128-bit CRC state with fold constants and XOR in another state +static inline void rv_fold_pair_xor_state(uint64_t* lo, uint64_t* hi, + uint64_t k0, uint64_t k1, + uint64_t s0, uint64_t s1) { + uint64_t l = rv_clmul(*lo, k0) ^ rv_clmul(*hi, k1); + uint64_t h = rv_clmulh(*lo, k0) ^ rv_clmulh(*hi, k1); + *lo = l ^ s0; + *hi = h ^ s1; +} + +// Folding constants for CRC32C (Castagnoli polynomial 0x1EDC6F41) +// x^(64*i+64) mod P(x) for i=1..4, in bit-reflected form +static const uint64_t crc32c_fold_const[4] __attribute__((aligned(16))) = { + 0x00000000740eef02ULL, // k1: fold 512->256 + 0x000000009e4addf8ULL, // k2: fold 512->256 + 0x00000000f20c0dfeULL, // k3: fold 256->128 + 0x00000000493c7d27ULL // k4: fold 256->128 +}; + +// Barrett reduction constants for CRC32C finalization +#define RV_CRC32C_CONST_0 0x00000000dd45aab8ULL // x^64 mod P +#define RV_CRC32C_CONST_1 0x00000000493c7d27ULL // x^96 mod P +#define RV_CRC32C_CONST_QUO 0x0000000dea713f1ULL // floor(x^64 / P) +#define RV_CRC32C_CONST_POLY 0x0000000105ec76f1ULL // P(x) true LE full +#define RV_CRC32_MASK32 0x00000000FFFFFFFFULL + +// Hardware-accelerated CRC32C using RISC-V Zbc carry-less multiplication. +// Processes data in 64-byte chunks with 128-bit folding, then Barrett reduces. +#if defined(__riscv_zbc) +static uint32_t rv_crc32c_clmul(uint32_t crc, const char* buf, size_t len) { + // Convert external CRC to internal register state + crc ^= 0xFFFFFFFF; + + const uint8_t* p = reinterpret_cast(buf); + size_t n = len; + + // Small data: use bitwise fallback + if (n < 64) { + return rv_crc32c_bitwise(crc, p, n) ^ 0xFFFFFFFF; + } + + // Align to 16-byte boundary + uintptr_t mis = (uintptr_t)p & 0xF; + if (mis) { + size_t pre = 16 - mis; + if (pre > n) pre = n; + crc = rv_crc32c_bitwise(crc, p, pre); + p += pre; + n -= pre; + if (n < 64) { + return rv_crc32c_bitwise(crc, p, n) ^ 0xFFFFFFFF; + } + } + + // Load first 64 bytes and XOR CRC into the first 8 bytes + uint64_t x0, x1, y0, y1, z0, z1, w0, w1; + memcpy(&x0, p + 0, 8); + memcpy(&x1, p + 8, 8); + memcpy(&y0, p + 16, 8); + memcpy(&y1, p + 24, 8); + memcpy(&z0, p + 32, 8); + memcpy(&z1, p + 40, 8); + memcpy(&w0, p + 48, 8); + memcpy(&w1, p + 56, 8); + + x0 ^= (uint64_t)crc; + p += 64; + n -= 64; + + const uint64_t k1 = crc32c_fold_const[0]; + const uint64_t k2 = crc32c_fold_const[1]; + const uint64_t k3 = crc32c_fold_const[2]; + const uint64_t k4 = crc32c_fold_const[3]; + + // Main loop: fold 64 bytes per iteration using 128-bit folding + while (n >= 64) { + uint64_t d0, d1; + memcpy(&d0, p + 0, 8); + memcpy(&d1, p + 8, 8); + rv_fold_pair_xor_data(&x0, &x1, k1, k2, d0, d1); + memcpy(&d0, p + 16, 8); + memcpy(&d1, p + 24, 8); + rv_fold_pair_xor_data(&y0, &y1, k1, k2, d0, d1); + memcpy(&d0, p + 32, 8); + memcpy(&d1, p + 40, 8); + rv_fold_pair_xor_data(&z0, &z1, k1, k2, d0, d1); + memcpy(&d0, p + 48, 8); + memcpy(&d1, p + 56, 8); + rv_fold_pair_xor_data(&w0, &w1, k1, k2, d0, d1); + p += 64; + n -= 64; + } + + // Reduce 4x128-bit to 1x128-bit + rv_fold_pair_xor_state(&x0, &x1, k3, k4, y0, y1); + rv_fold_pair_xor_state(&x0, &x1, k3, k4, z0, z1); + rv_fold_pair_xor_state(&x0, &x1, k3, k4, w0, w1); + + // Barrett reduction: 128-bit -> 32-bit CRC + uint64_t t4 = rv_clmul(x0, RV_CRC32C_CONST_1); + uint64_t t3 = rv_clmulh(x0, RV_CRC32C_CONST_1); + uint64_t t1 = x1 ^ t4; + t4 = t1 & RV_CRC32_MASK32; + t1 >>= 32; + uint64_t t0 = rv_clmul(t4, RV_CRC32C_CONST_0); + t3 = (t3 << 32) ^ t1 ^ t0; + + t4 = t3 & RV_CRC32_MASK32; + t4 = rv_clmul(t4, RV_CRC32C_CONST_QUO); + t4 &= RV_CRC32_MASK32; + t4 = rv_clmul(t4, RV_CRC32C_CONST_POLY); + t4 ^= t3; + + uint32_t c = (uint32_t)((t4 >> 32) & RV_CRC32_MASK32); + // Handle remaining bytes + if (n) { + c = rv_crc32c_bitwise(c, p, n); + } + // Convert internal register state to external CRC + return c ^ 0xFFFFFFFF; +} +#endif // __riscv_zbc + +// Runtime detection: check if RISC-V CPU supports Zbc extension +static bool is_zbc() { + static const bool zbc_supported = []() { + FILE* f = fopen("/proc/cpuinfo", "r"); + if (!f) return false; + bool supported = false; + char line[1024]; + while (fgets(line, sizeof(line), f)) { + if (strstr(line, "isa") || strstr(line, "hart isa")) { + char* colon = strchr(line, ':'); + if (colon) { + if (strstr(colon, "_zbc")) { + supported = true; + break; + } + } + } + } + fclose(f); + return supported; + }(); + return zbc_supported; +} + +#if defined(__riscv_zvbc) +// Hardware-accelerated CRC32C using RISC-V Zvbc vector carry-less multiplication. +// Uses RVV vclmul/vclmulh to process 2 lanes per vector operation (VLEN=128). +// With VLEN=128, each vector register holds 2 x 64-bit elements. +// 4 lanes are processed using 2 vector register pairs per clmul step. +static uint32_t rv_crc32c_vclmul(uint32_t crc, const char* buf, size_t len) { + crc ^= 0xFFFFFFFF; + + const uint8_t* p = reinterpret_cast(buf); + size_t n = len; + + if (n < 64) { + return rv_crc32c_bitwise(crc, p, n) ^ 0xFFFFFFFF; + } + + // Align to 16-byte boundary + uintptr_t mis = (uintptr_t)p & 0xF; + if (mis) { + size_t pre = 16 - mis; + if (pre > n) pre = n; + crc = rv_crc32c_bitwise(crc, p, pre); + p += pre; + n -= pre; + if (n < 64) { + return rv_crc32c_bitwise(crc, p, n) ^ 0xFFFFFFFF; + } + } + + // Set up RVV for 64-bit elements: vl = min(VLEN/64, 2) = 2 for VLEN=128 + // If VLEN < 128, vl will be 1 and the vector path cannot be used; fall back. + size_t vl = __riscv_vsetvl_e64m1(2); + if (vl < 2) { + return rv_crc32c_bitwise(crc, p, n) ^ 0xFFFFFFFF; + } + + // Construct fold constant vectors: {k1, k2} and {k3, k4} + // Each element gets the appropriate constant for its position: + // element 0 (lo half) uses k1/k3, element 1 (hi half) uses k2/k4 + uint64_t k12_arr[2] = { crc32c_fold_const[0], crc32c_fold_const[1] }; + uint64_t k34_arr[2] = { crc32c_fold_const[2], crc32c_fold_const[3] }; + vuint64m1_t k12_vec = __riscv_vle64_v_u64m1(k12_arr, vl); // {k1, k2} + vuint64m1_t k34_vec = __riscv_vle64_v_u64m1(k34_arr, vl); // {k3, k4} + + // Load first 64 bytes into 4 vector registers. + // Each vector = one 128-bit lane: {lo_64, hi_64} + // Use memcpy to avoid strict-aliasing violations when loading uint8_t* as uint64_t* + uint64_t lane1_buf[2], lane2_buf[2], lane3_buf[2], lane4_buf[2]; + memcpy(lane1_buf, p + 0, 16); + memcpy(lane2_buf, p + 16, 16); + memcpy(lane3_buf, p + 32, 16); + memcpy(lane4_buf, p + 48, 16); + vuint64m1_t lane1 = __riscv_vle64_v_u64m1(lane1_buf, vl); + vuint64m1_t lane2 = __riscv_vle64_v_u64m1(lane2_buf, vl); + vuint64m1_t lane3 = __riscv_vle64_v_u64m1(lane3_buf, vl); + vuint64m1_t lane4 = __riscv_vle64_v_u64m1(lane4_buf, vl); + + // XOR CRC into element 0 of first lane + uint64_t tmp[2]; + __riscv_vse64_v_u64m1(tmp, lane1, vl); + tmp[0] ^= (uint64_t)crc; + lane1 = __riscv_vle64_v_u64m1(tmp, vl); + + p += 64; + n -= 64; + + // Main loop: fold 64 bytes per iteration using vector carry-less multiply. + // + // For each 128-bit lane {lo, hi}, the fold computes: + // new_lo = clmul(lo, k1) ^ clmul(hi, k2) ^ data_lo + // new_hi = clmulh(lo, k1) ^ clmulh(hi, k2) ^ data_hi + // + // With k12_vec = {k1, k2} and element-wise vclmul: + // vclmul(lane, k12_vec) = {clmul(lo, k1), clmul(hi, k2)} (lo halves of products) + // vclmulh(lane, k12_vec) = {clmulh(lo, k1), clmulh(hi, k2)} (hi halves of products) + // + // The 128-bit XOR of (lo*k1) and (hi*k2) decomposes element-wise: + // new_lo = clmul(lo,k1) ^ clmul(hi,k2) = vclmul[0] ^ vclmul[1] + // new_hi = clmulh(lo,k1) ^ clmulh(hi,k2) = vclmulh[0] ^ vclmulh[1] + // + // So we need to XOR across elements. With VLEN=128 (2 elements), we use + // scalar extraction for the cross-element XOR since there's no vector + // permute instruction for just 2 elements that's more efficient. + while (n >= 64) { + uint64_t d1_buf[2], d2_buf[2], d3_buf[2], d4_buf[2]; + memcpy(d1_buf, p + 0, 16); + memcpy(d2_buf, p + 16, 16); + memcpy(d3_buf, p + 32, 16); + memcpy(d4_buf, p + 48, 16); + vuint64m1_t d1 = __riscv_vle64_v_u64m1(d1_buf, vl); + vuint64m1_t d2 = __riscv_vle64_v_u64m1(d2_buf, vl); + vuint64m1_t d3 = __riscv_vle64_v_u64m1(d3_buf, vl); + vuint64m1_t d4 = __riscv_vle64_v_u64m1(d4_buf, vl); + + // Fold each lane using vector clmul with {k1, k2} + uint64_t lo_r[2], hi_r[2], d_r[2]; + + // Lane 1 + __riscv_vse64_v_u64m1(lo_r, __riscv_vclmul_vv_u64m1(lane1, k12_vec, vl), vl); + __riscv_vse64_v_u64m1(hi_r, __riscv_vclmulh_vv_u64m1(lane1, k12_vec, vl), vl); + __riscv_vse64_v_u64m1(d_r, d1, vl); + d_r[0] ^= lo_r[0] ^ lo_r[1]; + d_r[1] ^= hi_r[0] ^ hi_r[1]; + lane1 = __riscv_vle64_v_u64m1(d_r, vl); + + // Lane 2 + __riscv_vse64_v_u64m1(lo_r, __riscv_vclmul_vv_u64m1(lane2, k12_vec, vl), vl); + __riscv_vse64_v_u64m1(hi_r, __riscv_vclmulh_vv_u64m1(lane2, k12_vec, vl), vl); + __riscv_vse64_v_u64m1(d_r, d2, vl); + d_r[0] ^= lo_r[0] ^ lo_r[1]; + d_r[1] ^= hi_r[0] ^ hi_r[1]; + lane2 = __riscv_vle64_v_u64m1(d_r, vl); + + // Lane 3 + __riscv_vse64_v_u64m1(lo_r, __riscv_vclmul_vv_u64m1(lane3, k12_vec, vl), vl); + __riscv_vse64_v_u64m1(hi_r, __riscv_vclmulh_vv_u64m1(lane3, k12_vec, vl), vl); + __riscv_vse64_v_u64m1(d_r, d3, vl); + d_r[0] ^= lo_r[0] ^ lo_r[1]; + d_r[1] ^= hi_r[0] ^ hi_r[1]; + lane3 = __riscv_vle64_v_u64m1(d_r, vl); + + // Lane 4 + __riscv_vse64_v_u64m1(lo_r, __riscv_vclmul_vv_u64m1(lane4, k12_vec, vl), vl); + __riscv_vse64_v_u64m1(hi_r, __riscv_vclmulh_vv_u64m1(lane4, k12_vec, vl), vl); + __riscv_vse64_v_u64m1(d_r, d4, vl); + d_r[0] ^= lo_r[0] ^ lo_r[1]; + d_r[1] ^= hi_r[0] ^ hi_r[1]; + lane4 = __riscv_vle64_v_u64m1(d_r, vl); + + p += 64; + n -= 64; + } + + // Reduce 4 lanes to 1 using {k3, k4} + // Same fold pattern: fold lane_a into lane_b + #define FOLD_INTO(dst, src) do { \ + uint64_t _lo[2], _hi[2], _d[2]; \ + __riscv_vse64_v_u64m1(_lo, __riscv_vclmul_vv_u64m1(src, k34_vec, vl), vl); \ + __riscv_vse64_v_u64m1(_hi, __riscv_vclmulh_vv_u64m1(src, k34_vec, vl), vl); \ + __riscv_vse64_v_u64m1(_d, dst, vl); \ + _d[0] ^= _lo[0] ^ _lo[1]; \ + _d[1] ^= _hi[0] ^ _hi[1]; \ + dst = __riscv_vle64_v_u64m1(_d, vl); \ + } while(0) + + FOLD_INTO(lane2, lane1); // lane2 = fold(lane1) ^ lane2 + FOLD_INTO(lane3, lane2); // lane3 = fold(lane2) ^ lane3 + FOLD_INTO(lane4, lane3); // lane4 = fold(lane3) ^ lane4 + #undef FOLD_INTO + + // Extract final 128-bit state from vector register + uint64_t final_state[2]; + __riscv_vse64_v_u64m1(final_state, lane4, vl); + uint64_t x0 = final_state[0]; + uint64_t x1 = final_state[1]; + + // Barrett reduction: 128-bit -> 32-bit CRC (scalar) + uint64_t t4 = rv_clmul(x0, RV_CRC32C_CONST_1); + uint64_t t3 = rv_clmulh(x0, RV_CRC32C_CONST_1); + uint64_t t1 = x1 ^ t4; + t4 = t1 & RV_CRC32_MASK32; + t1 >>= 32; + uint64_t t0 = rv_clmul(t4, RV_CRC32C_CONST_0); + t3 = (t3 << 32) ^ t1 ^ t0; + + t4 = t3 & RV_CRC32_MASK32; + t4 = rv_clmul(t4, RV_CRC32C_CONST_QUO); + t4 &= RV_CRC32_MASK32; + t4 = rv_clmul(t4, RV_CRC32C_CONST_POLY); + t4 ^= t3; + + uint32_t c = (uint32_t)((t4 >> 32) & RV_CRC32_MASK32); + if (n) { + c = rv_crc32c_bitwise(c, p, n); + } + return c ^ 0xFFFFFFFF; +} + +// Runtime detection: check if RISC-V CPU supports Zvbc extension +static bool is_zvbc() { + static const bool zvbc_supported = []() { + FILE* f = fopen("/proc/cpuinfo", "r"); + if (!f) return false; + bool supported = false; + char line[1024]; + while (fgets(line, sizeof(line), f)) { + if (strstr(line, "isa") || strstr(line, "hart isa")) { + char* colon = strchr(line, ':'); + if (colon) { + if (strstr(colon, "_zvbc")) { + supported = true; + break; + } + } + } + } + fclose(f); + return supported; + }(); + return zvbc_supported; +} +#endif // __riscv_zvbc + +#endif // __riscv && __riscv_xlen == 64 && (__riscv_zbc || __riscv_zvbc) + +// Detect if SSE4.2 or not. +#ifdef __SSE4_2__ +static bool isSSE42() { +#if defined(__GNUC__) && defined(__x86_64__) && !defined(IOS_CROSS_COMPILE) + uint32_t c_; + uint32_t d_; + __asm__("cpuid" : "=c"(c_), "=d"(d_) : "a"(1) : "ebx"); + return c_ & (1U << 20); // copied from CpuId.h in Folly. +#else + return false; +#endif +} +#endif + +typedef uint32_t (*Function)(uint32_t, const char*, size_t); + +static inline Function Choose_Extend() { +#ifdef __SSE4_2__ + if (isSSE42()) { + return (Function)ExtendImpl; + } +#endif +#if defined(__riscv) && (__riscv_xlen == 64) && (defined(__riscv_zbc) || defined(__riscv_zvbc)) +#if defined(__riscv_zvbc) + if (is_zvbc()) { + return (Function)rv_crc32c_vclmul; + } +#endif +#if defined(__riscv_zbc) + if (is_zbc()) { + return (Function)rv_crc32c_clmul; + } +#endif +#endif + return (Function)ExtendImpl; +} + +bool IsFastCrc32Supported() { +#ifdef __SSE4_2__ + if (isSSE42()) return true; +#endif +#if defined(__riscv) && (__riscv_xlen == 64) && (defined(__riscv_zbc) || defined(__riscv_zvbc)) +#if defined(__riscv_zvbc) + if (is_zvbc()) return true; +#endif +#if defined(__riscv_zbc) + if (is_zbc()) return true; +#endif +#endif + return false; +} + +uint32_t Extend(uint32_t crc, const char* buf, size_t size) { + static Function ChosenExtend = Choose_Extend(); + return ChosenExtend(crc, buf, size); +} + +} // namespace crc32c +} // namespace butil diff --git a/src/butil/crc32c.h b/src/butil/crc32c.h new file mode 100644 index 0000000..fd1e5ee --- /dev/null +++ b/src/butil/crc32c.h @@ -0,0 +1,52 @@ +// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// Copyright (c) 2011 The LevelDB Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. See the AUTHORS file for names of contributors. + +#ifndef BUTIL_CRC32C_H +#define BUTIL_CRC32C_H + +#include +#include + +namespace butil { +namespace crc32c { + +extern bool IsFastCrc32Supported(); + +// Return the crc32c of concat(A, data[0,n-1]) where init_crc is the +// crc32c of some string A. Extend() is often used to maintain the +// crc32c of a stream of data. +extern uint32_t Extend(uint32_t init_crc, const char* data, size_t n); + +// Return the crc32c of data[0,n-1] +inline uint32_t Value(const char* data, size_t n) { + return Extend(0, data, n); +} + +static const uint32_t kMaskDelta = 0xa282ead8ul; + +// Return a masked representation of crc. +// +// Motivation: it is problematic to compute the CRC of a string that +// contains embedded CRCs. Therefore we recommend that CRCs stored +// somewhere (e.g., in files) should be masked before being stored. +inline uint32_t Mask(uint32_t crc) { + // Rotate right by 15 bits and add a constant. + return ((crc >> 15) | (crc << 17)) + kMaskDelta; +} + +// Return the crc whose masked representation is masked_crc. +inline uint32_t Unmask(uint32_t masked_crc) { + uint32_t rot = masked_crc - kMaskDelta; + return ((rot >> 17) | (rot << 15)); +} + +} // namespace crc32c +} // namespace butil + +#endif // BUTIL_CRC32C_H diff --git a/src/butil/debug/address_annotations.h b/src/butil/debug/address_annotations.h new file mode 100644 index 0000000..81e7ab1 --- /dev/null +++ b/src/butil/debug/address_annotations.h @@ -0,0 +1,42 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_DEBUG_ADDRESS_ANNOTATIONS_H_ +#define BUTIL_DEBUG_ADDRESS_ANNOTATIONS_H_ + +#include "butil/macros.h" + +// It provides AddressSanitizer annotations for bthread and memory management. +// See for detail of these annotations. + +#ifdef BUTIL_USE_ASAN + +#include + +#define BUTIL_ASAN_POISON_MEMORY_REGION(addr, size) \ + __asan_poison_memory_region(addr, size) + +#define BUTIL_ASAN_UNPOISON_MEMORY_REGION(addr, size) \ + __asan_unpoison_memory_region(addr, size) + +#define BUTIL_ASAN_ADDRESS_IS_POISONED(addr) \ + __asan_address_is_poisoned(addr) + +#define BUTIL_ASAN_START_SWITCH_FIBER(fake_stack_save, bottom, size) \ + __sanitizer_start_switch_fiber(fake_stack_save, bottom, size) + +#define BUTIL_ASAN_FINISH_SWITCH_FIBER(fake_stack_save, bottom_old, size_old) \ + __sanitizer_finish_switch_fiber(fake_stack_save, bottom_old, size_old) + +#else +// If ASan is not used, these annotations are no-ops. +#define BUTIL_ASAN_POISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size)) +#define BUTIL_ASAN_UNPOISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size)) +#define BUTIL_ASAN_START_SWITCH_FIBER(fake_stack_save, bottom, size) \ + ((void)(fake_stack_save), (void)(bottom), (void)(size)) +#define BUTIL_ASAN_FINISH_SWITCH_FIBER(fake_stack_save, bottom_old, size_old) \ + ((void)(fake_stack_save), (void)(bottom_old), (void)(size_old)) +#endif // BUTIL_USE_ASAN + +#endif // BUTIL_DEBUG_ADDRESS_ANNOTATIONS_H_ diff --git a/src/butil/debug/alias.cc b/src/butil/debug/alias.cc new file mode 100644 index 0000000..ff6ac21 --- /dev/null +++ b/src/butil/debug/alias.cc @@ -0,0 +1,23 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/debug/alias.h" +#include "butil/build_config.h" + +namespace butil { +namespace debug { + +#if defined(COMPILER_MSVC) +#pragma optimize("", off) +#endif + +void Alias(const void* var) { +} + +#if defined(COMPILER_MSVC) +#pragma optimize("", on) +#endif + +} // namespace debug +} // namespace butil diff --git a/src/butil/debug/alias.h b/src/butil/debug/alias.h new file mode 100644 index 0000000..7ff8b3c --- /dev/null +++ b/src/butil/debug/alias.h @@ -0,0 +1,21 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_DEBUG_ALIAS_H_ +#define BUTIL_DEBUG_ALIAS_H_ + +#include "butil/base_export.h" + +namespace butil { +namespace debug { + +// Make the optimizer think that var is aliased. This is to prevent it from +// optimizing out variables that that would not otherwise be live at the point +// of a potential crash. +void BUTIL_EXPORT Alias(const void* var); + +} // namespace debug +} // namespace butil + +#endif // BUTIL_DEBUG_ALIAS_H_ diff --git a/src/butil/debug/asan_invalid_access.cc b/src/butil/debug/asan_invalid_access.cc new file mode 100644 index 0000000..5d49251 --- /dev/null +++ b/src/butil/debug/asan_invalid_access.cc @@ -0,0 +1,94 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#if defined(OS_WIN) +#include +#endif + +#include "butil/debug/alias.h" +#include "butil/debug/asan_invalid_access.h" +#include "butil/logging.h" +#include "butil/memory/scoped_ptr.h" + +namespace butil { +namespace debug { + +namespace { + +#if defined(SYZYASAN) +// Corrupt a memory block and make sure that the corruption gets detected either +// when we free it or when another crash happens (if |induce_crash| is set to +// true). +NOINLINE void CorruptMemoryBlock(bool induce_crash) { + // NOTE(sebmarchand): We intentionally corrupt a memory block here in order to + // trigger an Address Sanitizer (ASAN) error report. + static const int kArraySize = 5; + int* array = new int[kArraySize]; + // Encapsulate the invalid memory access into a try-catch statement to prevent + // this function from being instrumented. This way the underflow won't be + // detected but the corruption will (as the allocator will still be hooked). + try { + // Declares the dummy value as volatile to make sure it doesn't get + // optimized away. + int volatile dummy = array[-1]--; + butil::debug::Alias(const_cast(&dummy)); + } catch (...) { + } + if (induce_crash) + CHECK(false); + delete[] array; +} +#endif + +} // namespace + +#if defined(ADDRESS_SANITIZER) || defined(SYZYASAN) +// NOTE(sebmarchand): We intentionally perform some invalid heap access here in +// order to trigger an AddressSanitizer (ASan) error report. + +static const int kArraySize = 5; + +void AsanHeapOverflow() { + scoped_ptr array(new int[kArraySize]); + // Declares the dummy value as volatile to make sure it doesn't get optimized + // away. + int volatile dummy = 0; + dummy = array[kArraySize]; + butil::debug::Alias(const_cast(&dummy)); +} + +void AsanHeapUnderflow() { + scoped_ptr array(new int[kArraySize]); + // Declares the dummy value as volatile to make sure it doesn't get optimized + // away. + int volatile dummy = 0; + dummy = array[-1]; + butil::debug::Alias(const_cast(&dummy)); +} + +void AsanHeapUseAfterFree() { + scoped_ptr array(new int[kArraySize]); + // Declares the dummy value as volatile to make sure it doesn't get optimized + // away. + int volatile dummy = 0; + int* dangling = array.get(); + array.reset(); + dummy = dangling[kArraySize / 2]; + butil::debug::Alias(const_cast(&dummy)); +} + +#endif // ADDRESS_SANITIZER || SYZYASAN + +#if defined(SYZYASAN) +void AsanCorruptHeapBlock() { + CorruptMemoryBlock(false); +} + +void AsanCorruptHeap() { + CorruptMemoryBlock(true); +} +#endif // SYZYASAN + +} // namespace debug +} // namespace butil diff --git a/src/butil/debug/asan_invalid_access.h b/src/butil/debug/asan_invalid_access.h new file mode 100644 index 0000000..72cf4fa --- /dev/null +++ b/src/butil/debug/asan_invalid_access.h @@ -0,0 +1,47 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Defines some functions that intentionally do an invalid memory access in +// order to trigger an AddressSanitizer (ASan) error report. + +#ifndef BUTIL_DEBUG_ASAN_INVALID_ACCESS_H_ +#define BUTIL_DEBUG_ASAN_INVALID_ACCESS_H_ + +#include "butil/base_export.h" +#include "butil/compiler_specific.h" + +namespace butil { +namespace debug { + +#if defined(ADDRESS_SANITIZER) || defined(SYZYASAN) + +// Generates an heap buffer overflow. +BUTIL_EXPORT NOINLINE void AsanHeapOverflow(); + +// Generates an heap buffer underflow. +BUTIL_EXPORT NOINLINE void AsanHeapUnderflow(); + +// Generates an use after free. +BUTIL_EXPORT NOINLINE void AsanHeapUseAfterFree(); + +#endif // ADDRESS_SANITIZER || SYZYASAN + +// The "corrupt-block" and "corrupt-heap" classes of bugs is specific to +// SyzyASan. +#if defined(SYZYASAN) + +// Corrupts a memory block and makes sure that the corruption gets detected when +// we try to free this block. +BUTIL_EXPORT NOINLINE void AsanCorruptHeapBlock(); + +// Corrupts the heap and makes sure that the corruption gets detected when a +// crash occur. +BUTIL_EXPORT NOINLINE void AsanCorruptHeap(); + +#endif // SYZYASAN + +} // namespace debug +} // namespace butil + +#endif // BUTIL_DEBUG_ASAN_INVALID_ACCESS_H_ diff --git a/src/butil/debug/crash_logging.cc b/src/butil/debug/crash_logging.cc new file mode 100644 index 0000000..d17e9c0 --- /dev/null +++ b/src/butil/debug/crash_logging.cc @@ -0,0 +1,202 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/debug/crash_logging.h" + +#include +#include + +#include "butil/debug/stack_trace.h" +#include "butil/format_macros.h" +#include "butil/logging.h" +#include "butil/strings/string_util.h" +#include "butil/strings/stringprintf.h" + +namespace butil { +namespace debug { + +namespace { + +// Global map of crash key names to registration entries. +typedef std::map CrashKeyMap; +CrashKeyMap* g_crash_keys_ = NULL; + +// The maximum length of a single chunk. +size_t g_chunk_max_length_ = 0; + +// String used to format chunked key names. +const char kChunkFormatString[] = "%s-%" PRIuS; + +// The functions that are called to actually set the key-value pairs in the +// crash reportng system. +SetCrashKeyValueFuncT g_set_key_func_ = NULL; +ClearCrashKeyValueFuncT g_clear_key_func_ = NULL; + +// For a given |length|, computes the number of chunks a value of that size +// will occupy. +size_t NumChunksForLength(size_t length) { + return (size_t)std::ceil(length / static_cast(g_chunk_max_length_)); +} + +// The longest max_length allowed by the system. +const size_t kLargestValueAllowed = 1024; + +} // namespace + +void SetCrashKeyValue(const butil::StringPiece& key, + const butil::StringPiece& value) { + if (!g_set_key_func_ || !g_crash_keys_) + return; + + const CrashKey* crash_key = LookupCrashKey(key); + + DCHECK(crash_key) << "All crash keys must be registered before use " + << "(key = " << key << ")"; + + // Handle the un-chunked case. + if (!crash_key || crash_key->max_length <= g_chunk_max_length_) { + g_set_key_func_(key, value); + return; + } + + // Unset the unused chunks. + std::vector chunks = + ChunkCrashKeyValue(*crash_key, value, g_chunk_max_length_); + for (size_t i = chunks.size(); + i < NumChunksForLength(crash_key->max_length); + ++i) { + g_clear_key_func_(butil::StringPrintf(kChunkFormatString, key.data(), i+1)); + } + + // Set the chunked keys. + for (size_t i = 0; i < chunks.size(); ++i) { + g_set_key_func_(butil::StringPrintf(kChunkFormatString, key.data(), i+1), + chunks[i]); + } +} + +void ClearCrashKey(const butil::StringPiece& key) { + if (!g_clear_key_func_ || !g_crash_keys_) + return; + + const CrashKey* crash_key = LookupCrashKey(key); + + // Handle the un-chunked case. + if (!crash_key || crash_key->max_length <= g_chunk_max_length_) { + g_clear_key_func_(key); + return; + } + + for (size_t i = 0; i < NumChunksForLength(crash_key->max_length); ++i) { + g_clear_key_func_(butil::StringPrintf(kChunkFormatString, key.data(), i+1)); + } +} + +void SetCrashKeyToStackTrace(const butil::StringPiece& key, + const StackTrace& trace) { + size_t count = 0; + const void* const* addresses = trace.Addresses(&count); + SetCrashKeyFromAddresses(key, addresses, count); +} + +void SetCrashKeyFromAddresses(const butil::StringPiece& key, + const void* const* addresses, + size_t count) { + std::string value = ""; + if (addresses && count) { + const size_t kBreakpadValueMax = 255; + + std::vector hex_backtrace; + size_t length = 0; + + for (size_t i = 0; i < count; ++i) { + std::string s = butil::StringPrintf("%p", addresses[i]); + length += s.length() + 1; + if (length > kBreakpadValueMax) + break; + hex_backtrace.push_back(s); + } + + value = JoinString(hex_backtrace, ' '); + + // Warn if this exceeds the breakpad limits. + DCHECK_LE(value.length(), kBreakpadValueMax); + } + + SetCrashKeyValue(key, value); +} + +ScopedCrashKey::ScopedCrashKey(const butil::StringPiece& key, + const butil::StringPiece& value) + : key_(key.as_string()) { + SetCrashKeyValue(key, value); +} + +ScopedCrashKey::~ScopedCrashKey() { + ClearCrashKey(key_); +} + +size_t InitCrashKeys(const CrashKey* const keys, size_t count, + size_t chunk_max_length) { + DCHECK(!g_crash_keys_) << "Crash logging may only be initialized once"; + if (!keys) { + delete g_crash_keys_; + g_crash_keys_ = NULL; + return 0; + } + + g_crash_keys_ = new CrashKeyMap; + g_chunk_max_length_ = chunk_max_length; + + size_t total_keys = 0; + for (size_t i = 0; i < count; ++i) { + g_crash_keys_->emplace(keys[i].key_name, keys[i]); + total_keys += NumChunksForLength(keys[i].max_length); + DCHECK_LT(keys[i].max_length, kLargestValueAllowed); + } + DCHECK_EQ(count, g_crash_keys_->size()) + << "Duplicate crash keys were registered"; + + return total_keys; +} + +const CrashKey* LookupCrashKey(const butil::StringPiece& key) { + if (!g_crash_keys_) + return NULL; + CrashKeyMap::const_iterator it = g_crash_keys_->find(key.as_string()); + if (it == g_crash_keys_->end()) + return NULL; + return &(it->second); +} + +void SetCrashKeyReportingFunctions( + SetCrashKeyValueFuncT set_key_func, + ClearCrashKeyValueFuncT clear_key_func) { + g_set_key_func_ = set_key_func; + g_clear_key_func_ = clear_key_func; +} + +std::vector ChunkCrashKeyValue(const CrashKey& crash_key, + const butil::StringPiece& value, + size_t chunk_max_length) { + std::string value_string = value.substr(0, crash_key.max_length).as_string(); + std::vector chunks; + for (size_t offset = 0; offset < value_string.length(); ) { + std::string chunk = value_string.substr(offset, chunk_max_length); + chunks.push_back(chunk); + offset += chunk.length(); + } + return chunks; +} + +void ResetCrashLoggingForTesting() { + delete g_crash_keys_; + g_crash_keys_ = NULL; + g_chunk_max_length_ = 0; + g_set_key_func_ = NULL; + g_clear_key_func_ = NULL; +} + +} // namespace debug +} // namespace butil diff --git a/src/butil/debug/crash_logging.h b/src/butil/debug/crash_logging.h new file mode 100644 index 0000000..d1cb131 --- /dev/null +++ b/src/butil/debug/crash_logging.h @@ -0,0 +1,104 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_DEBUG_CRASH_LOGGING_H_ +#define BUTIL_DEBUG_CRASH_LOGGING_H_ + +#include +#include + +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/strings/string_piece.h" + +// These functions add metadata to the upload payload when sending crash reports +// to the crash server. +// +// IMPORTANT: On OS X and Linux, the key/value pairs are only sent as part of +// the upload and are not included in the minidump! + +namespace butil { +namespace debug { + +class StackTrace; + +// Set or clear a specific key-value pair from the crash metadata. Keys and +// values are terminated at the null byte. +BUTIL_EXPORT void SetCrashKeyValue(const butil::StringPiece& key, + const butil::StringPiece& value); +BUTIL_EXPORT void ClearCrashKey(const butil::StringPiece& key); + +// Records the given StackTrace into a crash key. +BUTIL_EXPORT void SetCrashKeyToStackTrace(const butil::StringPiece& key, + const StackTrace& trace); + +// Formats |count| instruction pointers from |addresses| using %p and +// sets the resulting string as a value for crash key |key|. A maximum of 23 +// items will be encoded, since breakpad limits values to 255 bytes. +BUTIL_EXPORT void SetCrashKeyFromAddresses(const butil::StringPiece& key, + const void* const* addresses, + size_t count); + +// A scoper that sets the specified key to value for the lifetime of the +// object, and clears it on destruction. +class BUTIL_EXPORT ScopedCrashKey { + public: + ScopedCrashKey(const butil::StringPiece& key, const butil::StringPiece& value); + ~ScopedCrashKey(); + + private: + std::string key_; + + DISALLOW_COPY_AND_ASSIGN(ScopedCrashKey); +}; + +// Before setting values for a key, all the keys must be registered. +struct BUTIL_EXPORT CrashKey { + // The name of the crash key, used in the above functions. + const char* key_name; + + // The maximum length for a value. If the value is longer than this, it will + // be truncated. If the value is larger than the |chunk_max_length| passed to + // InitCrashKeys() but less than this value, it will be split into multiple + // numbered chunks. + size_t max_length; +}; + +// Before the crash key logging mechanism can be used, all crash keys must be +// registered with this function. The function returns the amount of space +// the crash reporting implementation should allocate space for the registered +// crash keys. |chunk_max_length| is the maximum size that a value in a single +// chunk can be. +BUTIL_EXPORT size_t InitCrashKeys(const CrashKey* const keys, size_t count, + size_t chunk_max_length); + +// Returns the correspnding crash key object or NULL for a given key. +BUTIL_EXPORT const CrashKey* LookupCrashKey(const butil::StringPiece& key); + +// In the platform crash reporting implementation, these functions set and +// clear the NUL-termianted key-value pairs. +typedef void (*SetCrashKeyValueFuncT)(const butil::StringPiece&, + const butil::StringPiece&); +typedef void (*ClearCrashKeyValueFuncT)(const butil::StringPiece&); + +// Sets the function pointers that are used to integrate with the platform- +// specific crash reporting libraries. +BUTIL_EXPORT void SetCrashKeyReportingFunctions( + SetCrashKeyValueFuncT set_key_func, + ClearCrashKeyValueFuncT clear_key_func); + +// Helper function that breaks up a value according to the parameters +// specified by the crash key object. +BUTIL_EXPORT std::vector ChunkCrashKeyValue( + const CrashKey& crash_key, + const butil::StringPiece& value, + size_t chunk_max_length); + +// Resets the crash key system so it can be reinitialized. For testing only. +BUTIL_EXPORT void ResetCrashLoggingForTesting(); + +} // namespace debug +} // namespace butil + +#endif // BUTIL_DEBUG_CRASH_LOGGING_H_ diff --git a/src/butil/debug/debugger.cc b/src/butil/debug/debugger.cc new file mode 100644 index 0000000..8c5bb01 --- /dev/null +++ b/src/butil/debug/debugger.cc @@ -0,0 +1,41 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/debug/debugger.h" +#include "butil/logging.h" +#include "butil/threading/platform_thread.h" + +namespace butil { +namespace debug { + +static bool is_debug_ui_suppressed = false; + +bool WaitForDebugger(int wait_seconds, bool silent) { +#if defined(OS_ANDROID) + // The pid from which we know which process to attach to are not output by + // android ddms, so we have to print it out explicitly. + DLOG(INFO) << "DebugUtil::WaitForDebugger(pid=" << static_cast(getpid()) + << ")"; +#endif + for (int i = 0; i < wait_seconds * 10; ++i) { + if (BeingDebugged()) { + if (!silent) + BreakDebugger(); + return true; + } + PlatformThread::Sleep(TimeDelta::FromMilliseconds(100)); + } + return false; +} + +void SetSuppressDebugUI(bool suppress) { + is_debug_ui_suppressed = suppress; +} + +bool IsDebugUISuppressed() { + return is_debug_ui_suppressed; +} + +} // namespace debug +} // namespace butil diff --git a/src/butil/debug/debugger.h b/src/butil/debug/debugger.h new file mode 100644 index 0000000..3fc6623 --- /dev/null +++ b/src/butil/debug/debugger.h @@ -0,0 +1,44 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This is a cross platform interface for helper functions related to +// debuggers. You should use this to test if you're running under a debugger, +// and if you would like to yield (breakpoint) into the debugger. + +#ifndef BUTIL_DEBUG_DEBUGGER_H +#define BUTIL_DEBUG_DEBUGGER_H + +#include "butil/base_export.h" + +namespace butil { +namespace debug { + +// Waits wait_seconds seconds for a debugger to attach to the current process. +// When silent is false, an exception is thrown when a debugger is detected. +BUTIL_EXPORT bool WaitForDebugger(int wait_seconds, bool silent); + +// Returns true if the given process is being run under a debugger. +// +// On OS X, the underlying mechanism doesn't work when the sandbox is enabled. +// To get around this, this function caches its value. +// +// WARNING: Because of this, on OS X, a call MUST be made to this function +// BEFORE the sandbox is enabled. +BUTIL_EXPORT bool BeingDebugged(); + +// Break into the debugger, assumes a debugger is present. +BUTIL_EXPORT void BreakDebugger(); + +// Used in test code, this controls whether showing dialogs and breaking into +// the debugger is suppressed for debug errors, even in debug mode (normally +// release mode doesn't do this stuff -- this is controlled separately). +// Normally UI is not suppressed. This is normally used when running automated +// tests where we want a crash rather than a dialog or a debugger. +BUTIL_EXPORT void SetSuppressDebugUI(bool suppress); +BUTIL_EXPORT bool IsDebugUISuppressed(); + +} // namespace debug +} // namespace butil + +#endif // BUTIL_DEBUG_DEBUGGER_H diff --git a/src/butil/debug/debugger_posix.cc b/src/butil/debug/debugger_posix.cc new file mode 100644 index 0000000..0e46353 --- /dev/null +++ b/src/butil/debug/debugger_posix.cc @@ -0,0 +1,257 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/debug/debugger.h" +#include "butil/build_config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if defined(__GLIBCXX__) +#include +#endif + +#if defined(OS_MACOSX) +#include +#endif + +#if defined(OS_MACOSX) || defined(OS_BSD) +#include +#endif + +#if defined(OS_FREEBSD) +#include +#endif + +#include + +#include "butil/basictypes.h" +#include "butil/logging.h" +#include "butil/memory/scoped_ptr.h" +#include "butil/posix/eintr_wrapper.h" +#include "butil/safe_strerror_posix.h" +#include "butil/strings/string_piece.h" + +#if defined(USE_SYMBOLIZE) +#include "butil/third_party/symbolize/symbolize.h" +#endif + +#if defined(OS_ANDROID) +#include "butil/threading/platform_thread.h" +#endif + +namespace butil { +namespace debug { + +#if defined(OS_MACOSX) || defined(OS_BSD) + +// Based on Apple's recommended method as described in +// http://developer.apple.com/qa/qa2004/qa1361.html +bool BeingDebugged() { + // NOTE: This code MUST be async-signal safe (it's used by in-process + // stack dumping signal handler). NO malloc or stdio is allowed here. + // + // While some code used below may be async-signal unsafe, note how + // the result is cached (see |is_set| and |being_debugged| static variables + // right below). If this code is properly warmed-up early + // in the start-up process, it should be safe to use later. + + // If the process is sandboxed then we can't use the sysctl, so cache the + // value. + static bool is_set = false; + static bool being_debugged = false; + + if (is_set) + return being_debugged; + + // Initialize mib, which tells sysctl what info we want. In this case, + // we're looking for information about a specific process ID. + int mib[] = { + CTL_KERN, + KERN_PROC, + KERN_PROC_PID, + getpid() +#if defined(OS_OPENBSD) + , sizeof(struct kinfo_proc), + 0 +#endif + }; + + // Caution: struct kinfo_proc is marked __APPLE_API_UNSTABLE. The source and + // binary interfaces may change. + struct kinfo_proc info; + size_t info_size = sizeof(info); + +#if defined(OS_OPENBSD) + if (sysctl(mib, arraysize(mib), NULL, &info_size, NULL, 0) < 0) + return -1; + + mib[5] = (info_size / sizeof(struct kinfo_proc)); +#endif + + int sysctl_result = sysctl(mib, arraysize(mib), &info, &info_size, NULL, 0); + DCHECK_EQ(sysctl_result, 0); + if (sysctl_result != 0) { + is_set = true; + being_debugged = false; + return being_debugged; + } + + // This process is being debugged if the P_TRACED flag is set. + is_set = true; +#if defined(OS_FREEBSD) + being_debugged = (info.ki_flag & P_TRACED) != 0; +#elif defined(OS_BSD) + being_debugged = (info.p_flag & P_TRACED) != 0; +#else + being_debugged = (info.kp_proc.p_flag & P_TRACED) != 0; +#endif + return being_debugged; +} + +#elif defined(OS_LINUX) || defined(OS_ANDROID) + +// We can look in /proc/self/status for TracerPid. We are likely used in crash +// handling, so we are careful not to use the heap or have side effects. +// Another option that is common is to try to ptrace yourself, but then we +// can't detach without forking(), and that's not so great. +// static +bool BeingDebugged() { + // NOTE: This code MUST be async-signal safe (it's used by in-process + // stack dumping signal handler). NO malloc or stdio is allowed here. + + int status_fd = open("/proc/self/status", O_RDONLY); + if (status_fd == -1) + return false; + + // We assume our line will be in the first 1024 characters and that we can + // read this much all at once. In practice this will generally be true. + // This simplifies and speeds up things considerably. + char buf[1024]; + + ssize_t num_read = HANDLE_EINTR(read(status_fd, buf, sizeof(buf))); + if (IGNORE_EINTR(close(status_fd)) < 0) + return false; + + if (num_read <= 0) + return false; + + StringPiece status(buf, num_read); + StringPiece tracer("TracerPid:\t"); + + StringPiece::size_type pid_index = status.find(tracer); + if (pid_index == StringPiece::npos) + return false; + + // Our pid is 0 without a debugger, assume this for any pid starting with 0. + pid_index += tracer.size(); + return pid_index < status.size() && status[pid_index] != '0'; +} + +#else + +bool BeingDebugged() { + NOTIMPLEMENTED(); + return false; +} + +#endif + +// We want to break into the debugger in Debug mode, and cause a crash dump in +// Release mode. Breakpad behaves as follows: +// +// +-------+-----------------+-----------------+ +// | OS | Dump on SIGTRAP | Dump on SIGABRT | +// +-------+-----------------+-----------------+ +// | Linux | N | Y | +// | Mac | Y | N | +// +-------+-----------------+-----------------+ +// +// Thus we do the following: +// Linux: Debug mode if a debugger is attached, send SIGTRAP; otherwise send +// SIGABRT +// Mac: Always send SIGTRAP. + +#if defined(ARCH_CPU_ARMEL) +#define DEBUG_BREAK_ASM() asm("bkpt 0") +#elif defined(ARCH_CPU_ARM64) +#define DEBUG_BREAK_ASM() asm("brk 0") +#elif defined(ARCH_CPU_LOONGARCH64_FAMILY) +#define DEBUG_BREAK_ASM() asm("break 0") +#elif defined(ARCH_CPU_MIPS_FAMILY) +#define DEBUG_BREAK_ASM() asm("break 2") +#elif defined(ARCH_CPU_X86_FAMILY) +#define DEBUG_BREAK_ASM() asm("int3") +#endif + +#if defined(NDEBUG) && !defined(OS_MACOSX) && !defined(OS_ANDROID) +#define DEBUG_BREAK() abort() +#elif defined(OS_NACL) +// The NaCl verifier doesn't let use use int3. For now, we call abort(). We +// should ask for advice from some NaCl experts about the optimum thing here. +// http://code.google.com/p/nativeclient/issues/detail?id=645 +#define DEBUG_BREAK() abort() +#elif !defined(OS_MACOSX) +// Though Android has a "helpful" process called debuggerd to catch native +// signals on the general assumption that they are fatal errors. If no debugger +// is attached, we call abort since Breakpad needs SIGABRT to create a dump. +// When debugger is attached, for ARM platform the bkpt instruction appears +// to cause SIGBUS which is trapped by debuggerd, and we've had great +// difficulty continuing in a debugger once we stop from SIG triggered by native +// code, use GDB to set |go| to 1 to resume execution; for X86 platform, use +// "int3" to setup breakpiont and raise SIGTRAP. +// +// On other POSIX architectures, except Mac OS X, we use the same logic to +// ensure that breakpad creates a dump on crashes while it is still possible to +// use a debugger. +namespace { +void DebugBreak() { + if (!BeingDebugged()) { + abort(); + } else { +#if defined(DEBUG_BREAK_ASM) + DEBUG_BREAK_ASM(); +#else + volatile int go = 0; + while (!go) { + butil::PlatformThread::Sleep(butil::TimeDelta::FromMilliseconds(100)); + } +#endif + } +} +} // namespace +#define DEBUG_BREAK() DebugBreak() +#elif defined(DEBUG_BREAK_ASM) +#define DEBUG_BREAK() DEBUG_BREAK_ASM() +#else +#error "Don't know how to debug break on this architecture/OS" +#endif + +void BreakDebugger() { + // NOTE: This code MUST be async-signal safe (it's used by in-process + // stack dumping signal handler). NO malloc or stdio is allowed here. + + DEBUG_BREAK(); +#if defined(OS_ANDROID) && !defined(OFFICIAL_BUILD) + // For Android development we always build release (debug builds are + // unmanageably large), so the unofficial build is used for debugging. It is + // helpful to be able to insert BreakDebugger() statements in the source, + // attach the debugger, inspect the state of the program and then resume it by + // setting the 'go' variable above. +#elif defined(NDEBUG) + // Terminate the program after signaling the debug break. + _exit(1); +#endif +} + +} // namespace debug +} // namespace butil diff --git a/src/butil/debug/dump_without_crashing.cc b/src/butil/debug/dump_without_crashing.cc new file mode 100644 index 0000000..b4b2efc --- /dev/null +++ b/src/butil/debug/dump_without_crashing.cc @@ -0,0 +1,32 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/debug/dump_without_crashing.h" + +#include "butil/logging.h" + +namespace { + +// Pointer to the function that's called by DumpWithoutCrashing() to dump the +// process's memory. +void (CDECL *dump_without_crashing_function_)() = NULL; + +} // namespace + +namespace butil { + +namespace debug { + +void DumpWithoutCrashing() { + if (dump_without_crashing_function_) + (*dump_without_crashing_function_)(); +} + +void SetDumpWithoutCrashingFunction(void (CDECL *function)()) { + dump_without_crashing_function_ = function; +} + +} // namespace debug + +} // namespace butil diff --git a/src/butil/debug/dump_without_crashing.h b/src/butil/debug/dump_without_crashing.h new file mode 100644 index 0000000..6812794 --- /dev/null +++ b/src/butil/debug/dump_without_crashing.h @@ -0,0 +1,27 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_DEBUG_DUMP_WITHOUT_CRASHING_H_ +#define BUTIL_DEBUG_DUMP_WITHOUT_CRASHING_H_ + +#include "butil/base_export.h" +#include "butil/compiler_specific.h" +#include "butil/build_config.h" + +namespace butil { + +namespace debug { + +// Handler to silently dump the current process without crashing. +BUTIL_EXPORT void DumpWithoutCrashing(); + +// Sets a function that'll be invoked to dump the current process when +// DumpWithoutCrashing() is called. +BUTIL_EXPORT void SetDumpWithoutCrashingFunction(void (CDECL *function)()); + +} // namespace debug + +} // namespace butil + +#endif // BUTIL_DEBUG_DUMP_WITHOUT_CRASHING_H_ diff --git a/src/butil/debug/leak_annotations.h b/src/butil/debug/leak_annotations.h new file mode 100644 index 0000000..bcbaa00 --- /dev/null +++ b/src/butil/debug/leak_annotations.h @@ -0,0 +1,58 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_DEBUG_LEAK_ANNOTATIONS_H_ +#define BUTIL_DEBUG_LEAK_ANNOTATIONS_H_ + +#include "butil/basictypes.h" +#include "butil/build_config.h" + +// This file defines macros which can be used to annotate intentional memory +// leaks. Support for annotations is implemented in LeakSanitizer. Annotated +// objects will be treated as a source of live pointers, i.e. any heap objects +// reachable by following pointers from an annotated object will not be +// reported as leaks. +// +// ANNOTATE_SCOPED_MEMORY_LEAK: all allocations made in the current scope +// will be annotated as leaks. +// ANNOTATE_LEAKING_OBJECT_PTR(X): the heap object referenced by pointer X will +// be annotated as a leak. + +#if (defined(LEAK_SANITIZER) && !defined(OS_NACL)) || defined(BUTIL_USE_ASAN) + +// Public LSan API from . +extern "C" { +void __lsan_disable(); +void __lsan_enable(); +void __lsan_ignore_object(const void *p); +} // extern "C" + +class ScopedLeakSanitizerDisabler { +public: + ScopedLeakSanitizerDisabler() { __lsan_disable(); } + ~ScopedLeakSanitizerDisabler() { __lsan_enable(); } + + DISALLOW_COPY_AND_ASSIGN(ScopedLeakSanitizerDisabler); +}; + +#define ANNOTATE_SCOPED_MEMORY_LEAK \ + ScopedLeakSanitizerDisabler leak_sanitizer_disabler; static_cast(0) + +#define ANNOTATE_LEAKING_OBJECT_PTR(X) __lsan_ignore_object(X) + +// Manually pair these to mark allocations made in between as intentional non-leaks. +#define ANNOTATE_MEMORY_LEAK_DISABLE() __lsan_disable() +#define ANNOTATE_MEMORY_LEAK_ENABLE() __lsan_enable() + +#else + +// If neither HeapChecker nor LSan are used, the annotations should be no-ops. +#define ANNOTATE_SCOPED_MEMORY_LEAK ((void)0) +#define ANNOTATE_LEAKING_OBJECT_PTR(X) ((void)(X)) +#define ANNOTATE_MEMORY_LEAK_DISABLE() ((void)0) +#define ANNOTATE_MEMORY_LEAK_ENABLE() ((void)0) + +#endif + +#endif // BUTIL_DEBUG_LEAK_ANNOTATIONS_H_ diff --git a/src/butil/debug/leak_tracker.h b/src/butil/debug/leak_tracker.h new file mode 100644 index 0000000..5f584d3 --- /dev/null +++ b/src/butil/debug/leak_tracker.h @@ -0,0 +1,138 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_DEBUG_LEAK_TRACKER_H_ +#define BUTIL_DEBUG_LEAK_TRACKER_H_ + +#include "butil/build_config.h" + +// Only enable leak tracking in non-uClibc debug builds. +#if !defined(NDEBUG) && !defined(__UCLIBC__) +#define ENABLE_LEAK_TRACKER +#endif + +#ifdef ENABLE_LEAK_TRACKER +#include "butil/containers/linked_list.h" +#include "butil/debug/stack_trace.h" +#include "butil/logging.h" +#endif // ENABLE_LEAK_TRACKER + +// LeakTracker is a helper to verify that all instances of a class +// have been destroyed. +// +// It is particularly useful for classes that are bound to a single thread -- +// before destroying that thread, one can check that there are no remaining +// instances of that class. +// +// For example, to enable leak tracking for class net::URLRequest, start by +// adding a member variable of type LeakTracker. +// +// class URLRequest { +// ... +// private: +// butil::LeakTracker leak_tracker_; +// }; +// +// +// Next, when we believe all instances of net::URLRequest have been deleted: +// +// LeakTracker::CheckForLeaks(); +// +// Should the check fail (because there are live instances of net::URLRequest), +// then the allocation callstack for each leaked instances is dumped to +// the error log. +// +// If ENABLE_LEAK_TRACKER is not defined, then the check has no effect. + +namespace butil { +namespace debug { + +#ifndef ENABLE_LEAK_TRACKER + +// If leak tracking is disabled, do nothing. +template +class LeakTracker { + public: + ~LeakTracker() {} + static void CheckForLeaks() {} + static int NumLiveInstances() { return -1; } +}; + +#else + +// If leak tracking is enabled we track where the object was allocated from. + +template +class LeakTracker : public LinkNode > { + public: + LeakTracker() { + instances()->Append(this); + } + + ~LeakTracker() { + this->RemoveFromList(); + } + + static void CheckForLeaks() { + // Walk the allocation list and print each entry it contains. + size_t count = 0; + + // Copy the first 3 leak allocation callstacks onto the stack. + // This way if we hit the CHECK() in a release build, the leak + // information will be available in mini-dump. + const size_t kMaxStackTracesToCopyOntoStack = 3; + StackTrace stacktraces[kMaxStackTracesToCopyOntoStack]; + + for (LinkNode >* node = instances()->head(); + node != instances()->end(); + node = node->next()) { + StackTrace& allocation_stack = node->value()->allocation_stack_; + + if (count < kMaxStackTracesToCopyOntoStack) + stacktraces[count] = allocation_stack; + + ++count; + std::ostringstream err; + err << "Leaked " << node << " which was allocated by:"; + allocation_stack.OutputToStream(&err); + LOG(ERROR) << err.str(); + } + + CHECK_EQ(0u, count); + + // Hack to keep |stacktraces| and |count| alive (so compiler + // doesn't optimize it out, and it will appear in mini-dumps). + if (count == 0x1234) { + for (size_t i = 0; i < kMaxStackTracesToCopyOntoStack; ++i) + stacktraces[i].Print(); + } + } + + static int NumLiveInstances() { + // Walk the allocation list and count how many entries it has. + int count = 0; + for (LinkNode >* node = instances()->head(); + node != instances()->end(); + node = node->next()) { + ++count; + } + return count; + } + + private: + // Each specialization of LeakTracker gets its own static storage. + static LinkedList >* instances() { + static LinkedList > list; + return &list; + } + + StackTrace allocation_stack_; +}; + +#endif // ENABLE_LEAK_TRACKER + +} // namespace debug +} // namespace butil + +#endif // BUTIL_DEBUG_LEAK_TRACKER_H_ diff --git a/src/butil/debug/proc_maps_linux.cc b/src/butil/debug/proc_maps_linux.cc new file mode 100644 index 0000000..4d4888c --- /dev/null +++ b/src/butil/debug/proc_maps_linux.cc @@ -0,0 +1,167 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/debug/proc_maps_linux.h" + +#include + +#if defined(OS_LINUX) || defined(OS_ANDROID) +#include +#endif + +#include "butil/file_util.h" +#include "butil/files/scoped_file.h" +#include "butil/strings/string_split.h" + +#if defined(OS_ANDROID) && !defined(__LP64__) +// In 32-bit mode, Bionic's inttypes.h defines PRI/SCNxPTR as an +// unsigned long int, which is incompatible with Bionic's stdint.h +// defining uintptr_t as an unsigned int: +// https://code.google.com/p/android/issues/detail?id=57218 +#undef SCNxPTR +#define SCNxPTR "x" +#endif + +namespace butil { +namespace debug { + +// Scans |proc_maps| starting from |pos| returning true if the gate VMA was +// found, otherwise returns false. +static bool ContainsGateVMA(std::string* proc_maps, size_t pos) { +#if defined(ARCH_CPU_ARM_FAMILY) + // The gate VMA on ARM kernels is the interrupt vectors page. + return proc_maps->find(" [vectors]\n", pos) != std::string::npos; +#elif defined(ARCH_CPU_X86_64) + // The gate VMA on x86 64-bit kernels is the virtual system call page. + return proc_maps->find(" [vsyscall]\n", pos) != std::string::npos; +#else + // Otherwise assume there is no gate VMA in which case we shouldn't + // get duplicate entires. + return false; +#endif +} + +bool ReadProcMaps(std::string* proc_maps) { + // seq_file only writes out a page-sized amount on each call. Refer to header + // file for details. + const long kReadSize = sysconf(_SC_PAGESIZE); + + butil::ScopedFD fd(HANDLE_EINTR(open("/proc/self/maps", O_RDONLY))); + if (!fd.is_valid()) { + DPLOG(ERROR) << "Couldn't open /proc/self/maps"; + return false; + } + proc_maps->clear(); + + while (true) { + // To avoid a copy, resize |proc_maps| so read() can write directly into it. + // Compute |buffer| afterwards since resize() may reallocate. + size_t pos = proc_maps->size(); + proc_maps->resize(pos + kReadSize); + void* buffer = &(*proc_maps)[pos]; + + ssize_t bytes_read = HANDLE_EINTR(read(fd.get(), buffer, kReadSize)); + if (bytes_read < 0) { + DPLOG(ERROR) << "Couldn't read /proc/self/maps"; + proc_maps->clear(); + return false; + } + + // ... and don't forget to trim off excess bytes. + proc_maps->resize(pos + bytes_read); + + if (bytes_read == 0) + break; + + // The gate VMA is handled as a special case after seq_file has finished + // iterating through all entries in the virtual memory table. + // + // Unfortunately, if additional entries are added at this point in time + // seq_file gets confused and the next call to read() will return duplicate + // entries including the gate VMA again. + // + // Avoid this by searching for the gate VMA and breaking early. + if (ContainsGateVMA(proc_maps, pos)) + break; + } + + return true; +} + +bool ParseProcMaps(const std::string& input, + std::vector* regions_out) { + CHECK(regions_out); + std::vector regions; + + // This isn't async safe nor terribly efficient, but it doesn't need to be at + // this point in time. + std::vector lines; + SplitString(input, '\n', &lines); + + for (size_t i = 0; i < lines.size(); ++i) { + // Due to splitting on '\n' the last line should be empty. + if (i == lines.size() - 1) { + if (!lines[i].empty()) { + DLOG(WARNING) << "Last line not empty"; + return false; + } + break; + } + + MappedMemoryRegion region; + const char* line = lines[i].c_str(); + char permissions[5] = {'\0'}; // Ensure NUL-terminated string. + uint8_t dev_major = 0; + uint8_t dev_minor = 0; + long inode = 0; + int path_index = 0; + + // Sample format from man 5 proc: + // + // address perms offset dev inode pathname + // 08048000-08056000 r-xp 00000000 03:0c 64593 /usr/sbin/gpm + // + // The final %n term captures the offset in the input string, which is used + // to determine the path name. It *does not* increment the return value. + // Refer to man 3 sscanf for details. + if (sscanf(line, "%" SCNxPTR "-%" SCNxPTR " %4c %llx %hhx:%hhx %ld %n", + ®ion.start, ®ion.end, permissions, ®ion.offset, + &dev_major, &dev_minor, &inode, &path_index) < 7) { + DPLOG(WARNING) << "sscanf failed for line: " << line; + return false; + } + + region.permissions = 0; + + if (permissions[0] == 'r') + region.permissions |= MappedMemoryRegion::READ; + else if (permissions[0] != '-') + return false; + + if (permissions[1] == 'w') + region.permissions |= MappedMemoryRegion::WRITE; + else if (permissions[1] != '-') + return false; + + if (permissions[2] == 'x') + region.permissions |= MappedMemoryRegion::EXECUTE; + else if (permissions[2] != '-') + return false; + + if (permissions[3] == 'p') + region.permissions |= MappedMemoryRegion::PRIVATE; + else if (permissions[3] != 's' && permissions[3] != 'S') // Shared memory. + return false; + + // Pushing then assigning saves us a string copy. + regions.push_back(region); + regions.back().path.assign(line + path_index); + } + + regions_out->swap(regions); + return true; +} + +} // namespace debug +} // namespace butil diff --git a/src/butil/debug/proc_maps_linux.h b/src/butil/debug/proc_maps_linux.h new file mode 100644 index 0000000..6b68341 --- /dev/null +++ b/src/butil/debug/proc_maps_linux.h @@ -0,0 +1,90 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_DEBUG_PROC_MAPS_LINUX_H_ +#define BUTIL_DEBUG_PROC_MAPS_LINUX_H_ + +#include +#include + +#include "butil/base_export.h" +#include "butil/basictypes.h" + +namespace butil { +namespace debug { + +// Describes a region of mapped memory and the path of the file mapped. +struct MappedMemoryRegion { + enum Permission { + READ = 1 << 0, + WRITE = 1 << 1, + EXECUTE = 1 << 2, + PRIVATE = 1 << 3, // If set, region is private, otherwise it is shared. + }; + + // The address range [start,end) of mapped memory. + uintptr_t start; + uintptr_t end; + + // Byte offset into |path| of the range mapped into memory. + unsigned long long offset; + + // Bitmask of read/write/execute/private/shared permissions. + uint8_t permissions; + + // Name of the file mapped into memory. + // + // NOTE: path names aren't guaranteed to point at valid files. For example, + // "[heap]" and "[stack]" are used to represent the location of the process' + // heap and stack, respectively. + std::string path; +}; + +// Reads the data from /proc/self/maps and stores the result in |proc_maps|. +// Returns true if successful, false otherwise. +// +// There is *NO* guarantee that the resulting contents will be free of +// duplicates or even contain valid entries by time the method returns. +// +// +// THE GORY DETAILS +// +// Did you know it's next-to-impossible to atomically read the whole contents +// of /proc//maps? You would think that if we passed in a large-enough +// buffer to read() that It Should Just Work(tm), but sadly that's not the case. +// +// Linux's procfs uses seq_file [1] for handling iteration, text formatting, +// and dealing with resulting data that is larger than the size of a page. That +// last bit is especially important because it means that seq_file will never +// return more than the size of a page in a single call to read(). +// +// Unfortunately for a program like Chrome the size of /proc/self/maps is +// larger than the size of page so we're forced to call read() multiple times. +// If the virtual memory table changed in any way between calls to read() (e.g., +// a different thread calling mprotect()), it can make seq_file generate +// duplicate entries or skip entries. +// +// Even if seq_file was changed to keep flushing the contents of its page-sized +// buffer to the usermode buffer inside a single call to read(), it has to +// release its lock on the virtual memory table to handle page faults while +// copying data to usermode. This puts us in the same situation where the table +// can change while we're copying data. +// +// Alternatives such as fork()-and-suspend-the-parent-while-child-reads were +// attempted, but they present more subtle problems than it's worth. Depending +// on your use case your best bet may be to read /proc//maps prior to +// starting other threads. +// +// [1] http://kernelnewbies.org/Documents/SeqFileHowTo +BUTIL_EXPORT bool ReadProcMaps(std::string* proc_maps); + +// Parses /proc//maps input data and stores in |regions|. Returns true +// and updates |regions| if and only if all of |input| was successfully parsed. +BUTIL_EXPORT bool ParseProcMaps(const std::string& input, + std::vector* regions); + +} // namespace debug +} // namespace butil + +#endif // BUTIL_DEBUG_PROC_MAPS_LINUX_H_ diff --git a/src/butil/debug/stack_trace.cc b/src/butil/debug/stack_trace.cc new file mode 100644 index 0000000..97a4cd7 --- /dev/null +++ b/src/butil/debug/stack_trace.cc @@ -0,0 +1,47 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/debug/stack_trace.h" + +#include "butil/basictypes.h" + +#include + +#include +#include + +namespace butil { +namespace debug { + +StackTrace::StackTrace(const void* const* trace, size_t count) { + count = std::min(count, arraysize(trace_)); + if (count) + memcpy(trace_, trace, count * sizeof(trace_[0])); + count_ = count; +} + +const void *const *StackTrace::Addresses(size_t* count) const { + *count = count_; + if (count_) + return trace_; + return NULL; +} + +size_t StackTrace::CopyAddressTo(void** buffer, size_t max_nframes) const { + size_t nframes = std::min(count_, max_nframes); + memcpy(buffer, trace_, nframes * sizeof(void*)); + return nframes; +} + +std::string StackTrace::ToString() const { + std::string str; + str.reserve(1024); +#if !defined(__UCLIBC__) + OutputToString(str); +#endif + return str; +} + +} // namespace debug +} // namespace butil diff --git a/src/butil/debug/stack_trace.h b/src/butil/debug/stack_trace.h new file mode 100644 index 0000000..e812058 --- /dev/null +++ b/src/butil/debug/stack_trace.h @@ -0,0 +1,122 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_DEBUG_STACK_TRACE_H_ +#define BUTIL_DEBUG_STACK_TRACE_H_ + +#include +#include + +#include "butil/base_export.h" +#include "butil/build_config.h" + +#if defined(OS_POSIX) +#include +#endif + +#if defined(OS_WIN) +struct _EXCEPTION_POINTERS; +#endif + +namespace butil { +namespace debug { + +// Enables stack dump to console output on exception and signals. +// When enabled, the process will quit immediately. This is meant to be used in +// unit_tests only! This is not thread-safe: only call from main thread. +BUTIL_EXPORT bool EnableInProcessStackDumping(); + +// A different version of EnableInProcessStackDumping that also works for +// sandboxed processes. For more details take a look at the description +// of EnableInProcessStackDumping. +// Calling this function on Linux opens /proc/self/maps and caches its +// contents. In DEBUG builds, this function also opens the object files that +// are loaded in memory and caches their file descriptors (this cannot be +// done in official builds because it has security implications). +BUTIL_EXPORT bool EnableInProcessStackDumpingForSandbox(); + +// A stacktrace can be helpful in debugging. For example, you can include a +// stacktrace member in a object (probably around #ifndef NDEBUG) so that you +// can later see where the given object was created from. +class BUTIL_EXPORT StackTrace { + public: + // Creates a stacktrace from the current location. + // Exclude constructor frame of StackTrace if |exclude_self| is true. + explicit StackTrace(bool exclude_self = false); + + // Creates a stacktrace from an existing array of instruction + // pointers (such as returned by Addresses()). |count| will be + // trimmed to |kMaxTraces|. + StackTrace(const void* const* trace, size_t count); + +#if defined(OS_WIN) + // Creates a stacktrace for an exception. + // Note: this function will throw an import not found (StackWalk64) exception + // on system without dbghelp 5.1. + StackTrace(const _EXCEPTION_POINTERS* exception_pointers); +#endif + + // Copying and assignment are allowed with the default functions. + + // Gets an array of instruction pointer values. |*count| will be set to the + // number of elements in the returned array. + const void* const* Addresses(size_t* count) const; + + // Gets the number of frames in the stack trace. + size_t FrameCount() const { return count_; } + + // Copies the stack trace to |buffer|, + // where the size is min(max_nframes, frame_count()). + size_t CopyAddressTo(void** dest, size_t max_nframes) const; + + // Whether if the given symbol is found in the stack trace. + bool FindSymbol(void* symbol) const; + + // Prints the stack trace to stderr. + void Print() const; + +#if !defined(__UCLIBC__) + // Resolves backtrace to symbols and write to stream. + void OutputToStream(std::ostream* os) const; + void OutputToString(std::string& str) const; +#endif + + // Resolves backtrace to symbols and returns as string. + std::string ToString() const; + + private: + // From http://msdn.microsoft.com/en-us/library/bb204633.aspx, + // the sum of FramesToSkip and FramesToCapture must be less than 63, + // so set it to 62. Even if on POSIX it could be a larger value, it usually + // doesn't give much more information. + static const int kMaxTraces = 62; + + void* trace_[kMaxTraces]{}; + + // The number of valid frames in |trace_|. + size_t count_; +}; + +namespace internal { + +#if defined(OS_POSIX) && !defined(OS_ANDROID) +// POSIX doesn't define any async-signal safe function for converting +// an integer to ASCII. We'll have to define our own version. +// itoa_r() converts a (signed) integer to ASCII. It returns "buf", if the +// conversion was successful or NULL otherwise. It never writes more than "sz" +// bytes. Output will be truncated as needed, and a NUL character is always +// appended. +BUTIL_EXPORT char *itoa_r(intptr_t i, + char *buf, + size_t sz, + int base, + size_t padding); +#endif // defined(OS_POSIX) && !defined(OS_ANDROID) + +} // namespace internal + +} // namespace debug +} // namespace butil + +#endif // BUTIL_DEBUG_STACK_TRACE_H_ diff --git a/src/butil/debug/stack_trace_posix.cc b/src/butil/debug/stack_trace_posix.cc new file mode 100644 index 0000000..9ef91c2 --- /dev/null +++ b/src/butil/debug/stack_trace_posix.cc @@ -0,0 +1,894 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/debug/stack_trace.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#if defined(__GLIBCXX__) +#include +#endif +#if !defined(__UCLIBC__) +#include +#endif + +#if defined(OS_MACOSX) +#include +#endif + +#include "butil/basictypes.h" +#include "butil/debug/debugger.h" +#include "butil/debug/proc_maps_linux.h" +#include "butil/logging.h" +#include "butil/memory/scoped_ptr.h" +#include "butil/memory/singleton.h" +#include "butil/numerics/safe_conversions.h" +#include "butil/posix/eintr_wrapper.h" +#include "butil/strings/string_number_conversions.h" +#include "butil/build_config.h" + +#if defined(USE_SYMBOLIZE) +#include "butil/third_party/symbolize/symbolize.h" +#endif + +extern int BAIDU_WEAK GetStackTrace(void** result, int max_depth, int skip_count); + +namespace butil { +namespace debug { + +namespace { + +volatile sig_atomic_t in_signal_handler = 0; + +#if !defined(USE_SYMBOLIZE) && defined(__GLIBCXX__) +// The prefix used for mangled symbols, per the Itanium C++ ABI: +// http://www.codesourcery.com/cxx-abi/abi.html#mangling +const char kMangledSymbolPrefix[] = "_Z"; + +// Characters that can be used for symbols, generated by Ruby: +// (('a'..'z').to_a+('A'..'Z').to_a+('0'..'9').to_a + ['_']).join +const char kSymbolCharacters[] = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; +#endif // !defined(USE_SYMBOLIZE) && defined(__GLIBCXX__) + +#if !defined(USE_SYMBOLIZE) +// Demangles C++ symbols in the given text. Example: +// +// "out/Debug/base_unittests(_ZN10StackTraceC1Ev+0x20) [0x817778c]" +// => +// "out/Debug/base_unittests(StackTrace::StackTrace()+0x20) [0x817778c]" +void DemangleSymbols(std::string* text) { + // Note: code in this function is NOT async-signal safe (std::string uses + // malloc internally). + +#if defined(__GLIBCXX__) && !defined(__UCLIBC__) + + std::string::size_type search_from = 0; + while (search_from < text->size()) { + // Look for the start of a mangled symbol, from search_from. + std::string::size_type mangled_start = + text->find(kMangledSymbolPrefix, search_from); + if (mangled_start == std::string::npos) { + break; // Mangled symbol not found. + } + + // Look for the end of the mangled symbol. + std::string::size_type mangled_end = + text->find_first_not_of(kSymbolCharacters, mangled_start); + if (mangled_end == std::string::npos) { + mangled_end = text->size(); + } + std::string mangled_symbol = + text->substr(mangled_start, mangled_end - mangled_start); + + // Try to demangle the mangled symbol candidate. + int status = 0; + scoped_ptr demangled_symbol( + abi::__cxa_demangle(mangled_symbol.c_str(), NULL, 0, &status)); + if (status == 0) { // Demangling is successful. + // Remove the mangled symbol. + text->erase(mangled_start, mangled_end - mangled_start); + // Insert the demangled symbol. + text->insert(mangled_start, demangled_symbol.get()); + // Next time, we'll start right after the demangled symbol we inserted. + search_from = mangled_start + strlen(demangled_symbol.get()); + } else { + // Failed to demangle. Retry after the "_Z" we just found. + search_from = mangled_start + 2; + } + } + +#endif // defined(__GLIBCXX__) && !defined(__UCLIBC__) +} +#endif // !defined(USE_SYMBOLIZE) + +class BacktraceOutputHandler { + public: + virtual void HandleOutput(const char* output) = 0; + + protected: + virtual ~BacktraceOutputHandler() {} +}; + +void OutputPointer(void* pointer, BacktraceOutputHandler* handler) { + // This should be more than enough to store a 64-bit number in hex: + // 16 hex digits + 1 for null-terminator. + char buf[17] = { '\0' }; + handler->HandleOutput("0x"); + internal::itoa_r(reinterpret_cast(pointer), + buf, sizeof(buf), 16, 12); + handler->HandleOutput(buf); +} + +#if defined(USE_SYMBOLIZE) +void OutputFrameId(intptr_t frame_id, BacktraceOutputHandler* handler) { + // Max unsigned 64-bit number in decimal has 20 digits (18446744073709551615). + // Hence, 30 digits should be more than enough to represent it in decimal + // (including the null-terminator). + char buf[30] = { '\0' }; + handler->HandleOutput("#"); + internal::itoa_r(frame_id, buf, sizeof(buf), 10, 1); + handler->HandleOutput(buf); +} +#endif // defined(USE_SYMBOLIZE) + +void ProcessBacktrace(void *const *trace, + size_t size, + BacktraceOutputHandler* handler) { + // NOTE: This code MUST be async-signal safe (it's used by in-process + // stack dumping signal handler). NO malloc or stdio is allowed here. + +#if defined(USE_SYMBOLIZE) + for (size_t i = 0; i < size; ++i) { + OutputFrameId(i, handler); + handler->HandleOutput(" "); + OutputPointer(trace[i], handler); + handler->HandleOutput(" "); + + char buf[1024] = { '\0' }; + + // Subtract by one as return address of function may be in the next + // function when a function is annotated as noreturn. + void* address = static_cast(trace[i]) - 1; + if (google::Symbolize(address, buf, sizeof(buf))) + handler->HandleOutput(buf); + else + handler->HandleOutput(""); + + handler->HandleOutput("\n"); + } +#elif !defined(__UCLIBC__) + bool printed = false; + + // Below part is async-signal unsafe (uses malloc), so execute it only + // when we are not executing the signal handler. + if (in_signal_handler == 0) { + scoped_ptr + trace_symbols(backtrace_symbols(trace, size)); + if (trace_symbols.get()) { + for (size_t i = 0; i < size; ++i) { + std::string trace_symbol = trace_symbols.get()[i]; + DemangleSymbols(&trace_symbol); + handler->HandleOutput(trace_symbol.c_str()); + handler->HandleOutput("\n"); + } + + printed = true; + } + } + + if (!printed) { + for (size_t i = 0; i < size; ++i) { + handler->HandleOutput(" ["); + OutputPointer(trace[i], handler); + handler->HandleOutput("]\n"); + } + } +#endif // defined(USE_SYMBOLIZE) +} + +void PrintToStderr(const char* output) { + // NOTE: This code MUST be async-signal safe (it's used by in-process + // stack dumping signal handler). NO malloc or stdio is allowed here. + ignore_result(HANDLE_EINTR(write(STDERR_FILENO, output, strlen(output)))); +} + +void StackDumpSignalHandler(int signal, siginfo_t* info, void* void_context) { + // NOTE: This code MUST be async-signal safe. + // NO malloc or stdio is allowed here. + + // Record the fact that we are in the signal handler now, so that the rest + // of StackTrace can behave in an async-signal-safe manner. + in_signal_handler = 1; + + if (BeingDebugged()) + BreakDebugger(); + + PrintToStderr("Received signal "); + char buf[1024] = { 0 }; + internal::itoa_r(signal, buf, sizeof(buf), 10, 0); + PrintToStderr(buf); + if (signal == SIGBUS) { + if (info->si_code == BUS_ADRALN) + PrintToStderr(" BUS_ADRALN "); + else if (info->si_code == BUS_ADRERR) + PrintToStderr(" BUS_ADRERR "); + else if (info->si_code == BUS_OBJERR) + PrintToStderr(" BUS_OBJERR "); + else + PrintToStderr(" "); + } else if (signal == SIGFPE) { + if (info->si_code == FPE_FLTDIV) + PrintToStderr(" FPE_FLTDIV "); + else if (info->si_code == FPE_FLTINV) + PrintToStderr(" FPE_FLTINV "); + else if (info->si_code == FPE_FLTOVF) + PrintToStderr(" FPE_FLTOVF "); + else if (info->si_code == FPE_FLTRES) + PrintToStderr(" FPE_FLTRES "); + else if (info->si_code == FPE_FLTSUB) + PrintToStderr(" FPE_FLTSUB "); + else if (info->si_code == FPE_FLTUND) + PrintToStderr(" FPE_FLTUND "); + else if (info->si_code == FPE_INTDIV) + PrintToStderr(" FPE_INTDIV "); + else if (info->si_code == FPE_INTOVF) + PrintToStderr(" FPE_INTOVF "); + else + PrintToStderr(" "); + } else if (signal == SIGILL) { + if (info->si_code == ILL_BADSTK) + PrintToStderr(" ILL_BADSTK "); + else if (info->si_code == ILL_COPROC) + PrintToStderr(" ILL_COPROC "); + else if (info->si_code == ILL_ILLOPN) + PrintToStderr(" ILL_ILLOPN "); + else if (info->si_code == ILL_ILLADR) + PrintToStderr(" ILL_ILLADR "); + else if (info->si_code == ILL_ILLTRP) + PrintToStderr(" ILL_ILLTRP "); + else if (info->si_code == ILL_PRVOPC) + PrintToStderr(" ILL_PRVOPC "); + else if (info->si_code == ILL_PRVREG) + PrintToStderr(" ILL_PRVREG "); + else + PrintToStderr(" "); + } else if (signal == SIGSEGV) { + if (info->si_code == SEGV_MAPERR) + PrintToStderr(" SEGV_MAPERR "); + else if (info->si_code == SEGV_ACCERR) + PrintToStderr(" SEGV_ACCERR "); + else + PrintToStderr(" "); + } + if (signal == SIGBUS || signal == SIGFPE || + signal == SIGILL || signal == SIGSEGV) { + internal::itoa_r(reinterpret_cast(info->si_addr), + buf, sizeof(buf), 16, 12); + PrintToStderr(buf); + } + PrintToStderr("\n"); + + debug::StackTrace().Print(); + +#if defined(OS_LINUX) +#if ARCH_CPU_X86_FAMILY + ucontext_t* context = reinterpret_cast(void_context); + const struct { + const char* label; + greg_t value; + } registers[] = { +#if ARCH_CPU_32_BITS + { " gs: ", context->uc_mcontext.gregs[REG_GS] }, + { " fs: ", context->uc_mcontext.gregs[REG_FS] }, + { " es: ", context->uc_mcontext.gregs[REG_ES] }, + { " ds: ", context->uc_mcontext.gregs[REG_DS] }, + { " edi: ", context->uc_mcontext.gregs[REG_EDI] }, + { " esi: ", context->uc_mcontext.gregs[REG_ESI] }, + { " ebp: ", context->uc_mcontext.gregs[REG_EBP] }, + { " esp: ", context->uc_mcontext.gregs[REG_ESP] }, + { " ebx: ", context->uc_mcontext.gregs[REG_EBX] }, + { " edx: ", context->uc_mcontext.gregs[REG_EDX] }, + { " ecx: ", context->uc_mcontext.gregs[REG_ECX] }, + { " eax: ", context->uc_mcontext.gregs[REG_EAX] }, + { " trp: ", context->uc_mcontext.gregs[REG_TRAPNO] }, + { " err: ", context->uc_mcontext.gregs[REG_ERR] }, + { " ip: ", context->uc_mcontext.gregs[REG_EIP] }, + { " cs: ", context->uc_mcontext.gregs[REG_CS] }, + { " efl: ", context->uc_mcontext.gregs[REG_EFL] }, + { " usp: ", context->uc_mcontext.gregs[REG_UESP] }, + { " ss: ", context->uc_mcontext.gregs[REG_SS] }, +#elif ARCH_CPU_64_BITS + { " r8: ", context->uc_mcontext.gregs[REG_R8] }, + { " r9: ", context->uc_mcontext.gregs[REG_R9] }, + { " r10: ", context->uc_mcontext.gregs[REG_R10] }, + { " r11: ", context->uc_mcontext.gregs[REG_R11] }, + { " r12: ", context->uc_mcontext.gregs[REG_R12] }, + { " r13: ", context->uc_mcontext.gregs[REG_R13] }, + { " r14: ", context->uc_mcontext.gregs[REG_R14] }, + { " r15: ", context->uc_mcontext.gregs[REG_R15] }, + { " di: ", context->uc_mcontext.gregs[REG_RDI] }, + { " si: ", context->uc_mcontext.gregs[REG_RSI] }, + { " bp: ", context->uc_mcontext.gregs[REG_RBP] }, + { " bx: ", context->uc_mcontext.gregs[REG_RBX] }, + { " dx: ", context->uc_mcontext.gregs[REG_RDX] }, + { " ax: ", context->uc_mcontext.gregs[REG_RAX] }, + { " cx: ", context->uc_mcontext.gregs[REG_RCX] }, + { " sp: ", context->uc_mcontext.gregs[REG_RSP] }, + { " ip: ", context->uc_mcontext.gregs[REG_RIP] }, + { " efl: ", context->uc_mcontext.gregs[REG_EFL] }, + { " cgf: ", context->uc_mcontext.gregs[REG_CSGSFS] }, + { " erf: ", context->uc_mcontext.gregs[REG_ERR] }, + { " trp: ", context->uc_mcontext.gregs[REG_TRAPNO] }, + { " msk: ", context->uc_mcontext.gregs[REG_OLDMASK] }, + { " cr2: ", context->uc_mcontext.gregs[REG_CR2] }, +#endif + }; + +#if ARCH_CPU_32_BITS + const int kRegisterPadding = 8; +#elif ARCH_CPU_64_BITS + const int kRegisterPadding = 16; +#endif + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(registers); i++) { + PrintToStderr(registers[i].label); + internal::itoa_r(registers[i].value, buf, sizeof(buf), + 16, kRegisterPadding); + PrintToStderr(buf); + + if ((i + 1) % 4 == 0) + PrintToStderr("\n"); + } + PrintToStderr("\n"); +#endif +#elif defined(OS_MACOSX) + // TODO(shess): Port to 64-bit, and ARM architecture (32 and 64-bit). +#if ARCH_CPU_X86_FAMILY && ARCH_CPU_32_BITS + ucontext_t* context = reinterpret_cast(void_context); + size_t len; + + // NOTE: Even |snprintf()| is not on the approved list for signal + // handlers, but buffered I/O is definitely not on the list due to + // potential for |malloc()|. + len = static_cast( + snprintf(buf, sizeof(buf), + "ax: %x, bx: %x, cx: %x, dx: %x\n", + context->uc_mcontext->__ss.__eax, + context->uc_mcontext->__ss.__ebx, + context->uc_mcontext->__ss.__ecx, + context->uc_mcontext->__ss.__edx)); + write(STDERR_FILENO, buf, std::min(len, sizeof(buf) - 1)); + + len = static_cast( + snprintf(buf, sizeof(buf), + "di: %x, si: %x, bp: %x, sp: %x, ss: %x, flags: %x\n", + context->uc_mcontext->__ss.__edi, + context->uc_mcontext->__ss.__esi, + context->uc_mcontext->__ss.__ebp, + context->uc_mcontext->__ss.__esp, + context->uc_mcontext->__ss.__ss, + context->uc_mcontext->__ss.__eflags)); + write(STDERR_FILENO, buf, std::min(len, sizeof(buf) - 1)); + + len = static_cast( + snprintf(buf, sizeof(buf), + "ip: %x, cs: %x, ds: %x, es: %x, fs: %x, gs: %x\n", + context->uc_mcontext->__ss.__eip, + context->uc_mcontext->__ss.__cs, + context->uc_mcontext->__ss.__ds, + context->uc_mcontext->__ss.__es, + context->uc_mcontext->__ss.__fs, + context->uc_mcontext->__ss.__gs)); + write(STDERR_FILENO, buf, std::min(len, sizeof(buf) - 1)); +#endif // ARCH_CPU_32_BITS +#endif // defined(OS_MACOSX) + _exit(1); +} + +class PrintBacktraceOutputHandler : public BacktraceOutputHandler { + public: + PrintBacktraceOutputHandler() {} + + virtual void HandleOutput(const char* output) OVERRIDE { + // NOTE: This code MUST be async-signal safe (it's used by in-process + // stack dumping signal handler). NO malloc or stdio is allowed here. + PrintToStderr(output); + } + + private: + DISALLOW_COPY_AND_ASSIGN(PrintBacktraceOutputHandler); +}; + +class StreamBacktraceOutputHandler : public BacktraceOutputHandler { + public: + explicit StreamBacktraceOutputHandler(std::ostream* os) : os_(os) { + } + + virtual void HandleOutput(const char* output) OVERRIDE { + (*os_) << output; + } + + private: + std::ostream* os_; + + DISALLOW_COPY_AND_ASSIGN(StreamBacktraceOutputHandler); +}; + +class StringBacktraceOutputHandler : public BacktraceOutputHandler { +public: + explicit StringBacktraceOutputHandler(std::string& str) : _str(str) {} + + DISALLOW_COPY_AND_ASSIGN(StringBacktraceOutputHandler); + + void HandleOutput(const char* output) OVERRIDE { + if (NULL == output) { + return; + } + _str.append(output); + } + +private: + std::string& _str; +}; + +void WarmUpBacktrace() { + // Warm up stack trace infrastructure. It turns out that on the first + // call glibc initializes some internal data structures using pthread_once, + // and even backtrace() can call malloc(), leading to hangs. + // + // Example stack trace snippet (with tcmalloc): + // + // #8 0x0000000000a173b5 in tc_malloc + // at ./third_party/tcmalloc/chromium/src/debugallocation.cc:1161 + // #9 0x00007ffff7de7900 in _dl_map_object_deps at dl-deps.c:517 + // #10 0x00007ffff7ded8a9 in dl_open_worker at dl-open.c:262 + // #11 0x00007ffff7de9176 in _dl_catch_error at dl-error.c:178 + // #12 0x00007ffff7ded31a in _dl_open (file=0x7ffff625e298 "libgcc_s.so.1") + // at dl-open.c:639 + // #13 0x00007ffff6215602 in do_dlopen at dl-libc.c:89 + // #14 0x00007ffff7de9176 in _dl_catch_error at dl-error.c:178 + // #15 0x00007ffff62156c4 in dlerror_run at dl-libc.c:48 + // #16 __GI___libc_dlopen_mode at dl-libc.c:165 + // #17 0x00007ffff61ef8f5 in init + // at ../sysdeps/x86_64/../ia64/backtrace.c:53 + // #18 0x00007ffff6aad400 in pthread_once + // at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_once.S:104 + // #19 0x00007ffff61efa14 in __GI___backtrace + // at ../sysdeps/x86_64/../ia64/backtrace.c:104 + // #20 0x0000000000752a54 in butil::debug::StackTrace::StackTrace + // at butil/debug/stack_trace_posix.cc:175 + // #21 0x00000000007a4ae5 in + // butil::(anonymous namespace)::StackDumpSignalHandler + // at butil/process_util_posix.cc:172 + // #22 + StackTrace stack_trace; +} + +} // namespace + +#if defined(USE_SYMBOLIZE) + +// class SandboxSymbolizeHelper. +// +// The purpose of this class is to prepare and install a "file open" callback +// needed by the stack trace symbolization code +// (butil/third_party/symbolize/symbolize.h) so that it can function properly +// in a sandboxed process. The caveat is that this class must be instantiated +// before the sandboxing is enabled so that it can get the chance to open all +// the object files that are loaded in the virtual address space of the current +// process. +class SandboxSymbolizeHelper { + public: + // Returns the singleton instance. + static SandboxSymbolizeHelper* GetInstance() { + return Singleton::get(); + } + + private: + friend struct DefaultSingletonTraits; + + SandboxSymbolizeHelper() + : is_initialized_(false) { + Init(); + } + + ~SandboxSymbolizeHelper() { + UnregisterCallback(); + CloseObjectFiles(); + } + + // Returns a O_RDONLY file descriptor for |file_path| if it was opened + // sucessfully during the initialization. The file is repositioned at + // offset 0. + // IMPORTANT: This function must be async-signal-safe because it can be + // called from a signal handler (symbolizing stack frames for a crash). + int GetFileDescriptor(const char* file_path) { + int fd = -1; + +#if !defined(NDEBUG) + if (file_path) { + // The assumption here is that iterating over std::map + // using a const_iterator does not allocate dynamic memory, hense it is + // async-signal-safe. + std::map::const_iterator it; + for (it = modules_.begin(); it != modules_.end(); ++it) { + if (strcmp((it->first).c_str(), file_path) == 0) { + // POSIX.1-2004 requires an implementation to guarantee that dup() + // is async-signal-safe. + fd = dup(it->second); + break; + } + } + // POSIX.1-2004 requires an implementation to guarantee that lseek() + // is async-signal-safe. + if (fd >= 0 && lseek(fd, 0, SEEK_SET) < 0) { + // Failed to seek. + fd = -1; + } + } +#endif // !defined(NDEBUG) + + return fd; + } + + // Searches for the object file (from /proc/self/maps) that contains + // the specified pc. If found, sets |start_address| to the start address + // of where this object file is mapped in memory, sets the module base + // address into |base_address|, copies the object file name into + // |out_file_name|, and attempts to open the object file. If the object + // file is opened successfully, returns the file descriptor. Otherwise, + // returns -1. |out_file_name_size| is the size of the file name buffer + // (including the null terminator). + // IMPORTANT: This function must be async-signal-safe because it can be + // called from a signal handler (symbolizing stack frames for a crash). + static int OpenObjectFileContainingPc(uint64_t pc, uint64_t& start_address, + uint64_t& base_address, char* file_path, + int file_path_size) { + // This method can only be called after the singleton is instantiated. + // This is ensured by the following facts: + // * This is the only static method in this class, it is private, and + // the class has no friends (except for the DefaultSingletonTraits). + // The compiler guarantees that it can only be called after the + // singleton is instantiated. + // * This method is used as a callback for the stack tracing code and + // the callback registration is done in the constructor, so logically + // it cannot be called before the singleton is created. + SandboxSymbolizeHelper* instance = GetInstance(); + + // The assumption here is that iterating over + // std::vector using a const_iterator does not allocate + // dynamic memory, hence it is async-signal-safe. + std::vector::const_iterator it; + bool is_first = true; + for (it = instance->regions_.begin(); it != instance->regions_.end(); + ++it, is_first = false) { + const MappedMemoryRegion& region = *it; + if (region.start <= pc && pc < region.end) { + start_address = region.start; + // Don't subtract 'start_address' from the first entry: + // * If a binary is compiled w/o -pie, then the first entry in + // process maps is likely the binary itself (all dynamic libs + // are mapped higher in address space). For such a binary, + // instruction offset in binary coincides with the actual + // instruction address in virtual memory (as code section + // is mapped to a fixed memory range). + // * If a binary is compiled with -pie, all the modules are + // mapped high at address space (in particular, higher than + // shadow memory of the tool), so the module can't be the + // first entry. + base_address = (is_first ? 0U : start_address) - region.offset; + if (file_path && file_path_size > 0) { + strncpy(file_path, region.path.c_str(), file_path_size); + // Ensure null termination. + file_path[file_path_size - 1] = '\0'; + } + return instance->GetFileDescriptor(region.path.c_str()); + } + } + return -1; + } + + // Parses /proc/self/maps in order to compile a list of all object file names + // for the modules that are loaded in the current process. + // Returns true on success. + bool CacheMemoryRegions() { + // Reads /proc/self/maps. + std::string contents; + if (!ReadProcMaps(&contents)) { + LOG(ERROR) << "Failed to read /proc/self/maps"; + return false; + } + + // Parses /proc/self/maps. + if (!ParseProcMaps(contents, ®ions_)) { + LOG(ERROR) << "Failed to parse the contents of /proc/self/maps"; + return false; + } + + is_initialized_ = true; + return true; + } + + // FIXME(gejun): Missing O_CLOEXEC from our linux headers. The flag should + // work on majority machines which installed 2.6.23 or newer kernels. But + // if the kernel is older, I'm not sure that open() will fail or ignore + // the flag. +#ifndef O_CLOEXEC +#define O_CLOEXEC 02000000 +#endif + + // Opens all object files and caches their file descriptors. + void OpenSymbolFiles() { + // Pre-opening and caching the file descriptors of all loaded modules is + // not considered safe for retail builds. Hence it is only done in debug + // builds. For more details, take a look at: http://crbug.com/341966 + // Enabling this to release mode would require approval from the security + // team. +#if !defined(NDEBUG) + // Open the object files for all read-only executable regions and cache + // their file descriptors. + std::vector::const_iterator it; + for (it = regions_.begin(); it != regions_.end(); ++it) { + const MappedMemoryRegion& region = *it; + // Only interesed in read-only executable regions. + if ((region.permissions & MappedMemoryRegion::READ) == + MappedMemoryRegion::READ && + (region.permissions & MappedMemoryRegion::WRITE) == 0 && + (region.permissions & MappedMemoryRegion::EXECUTE) == + MappedMemoryRegion::EXECUTE) { + if (region.path.empty()) { + // Skip regions with empty file names. + continue; + } + if (region.path[0] == '[') { + // Skip pseudo-paths, like [stack], [vdso], [heap], etc ... + continue; + } + // Avoid duplicates. + if (modules_.find(region.path) == modules_.end()) { + int fd = open(region.path.c_str(), O_RDONLY | O_CLOEXEC); + if (fd >= 0) { + modules_.emplace(region.path, fd); + } else { + LOG(WARNING) << "Failed to open file: " << region.path + << "\n Error: " << strerror(errno); + } + } + } + } +#endif // !defined(NDEBUG) + } + + // Initializes and installs the symbolization callback. + void Init() { + if (CacheMemoryRegions()) { + OpenSymbolFiles(); + google::InstallSymbolizeOpenObjectFileCallback( + &OpenObjectFileContainingPc); + } + } + + // Unregister symbolization callback. + void UnregisterCallback() { + if (is_initialized_) { + google::InstallSymbolizeOpenObjectFileCallback(NULL); + is_initialized_ = false; + } + } + + // Closes all file descriptors owned by this instance. + void CloseObjectFiles() { +#if !defined(NDEBUG) + std::map::iterator it; + for (it = modules_.begin(); it != modules_.end(); ++it) { + int ret = IGNORE_EINTR(close(it->second)); + DCHECK(!ret); + it->second = -1; + } + modules_.clear(); +#endif // !defined(NDEBUG) + } + + // Set to true upon successful initialization. + bool is_initialized_; + +#if !defined(NDEBUG) + // Mapping from file name to file descriptor. Includes file descriptors + // for all successfully opened object files and the file descriptor for + // /proc/self/maps. This code is not safe for release builds so + // this is only done for DEBUG builds. + std::map modules_; +#endif // !defined(NDEBUG) + + // Cache for the process memory regions. Produced by parsing the contents + // of /proc/self/maps cache. + std::vector regions_; + + DISALLOW_COPY_AND_ASSIGN(SandboxSymbolizeHelper); +}; +#endif // USE_SYMBOLIZE + +bool EnableInProcessStackDumpingForSandbox() { +#if defined(USE_SYMBOLIZE) + SandboxSymbolizeHelper::GetInstance(); +#endif // USE_SYMBOLIZE + + return EnableInProcessStackDumping(); +} + +bool EnableInProcessStackDumping() { + // When running in an application, our code typically expects SIGPIPE + // to be ignored. Therefore, when testing that same code, it should run + // with SIGPIPE ignored as well. + struct sigaction sigpipe_action; + memset(&sigpipe_action, 0, sizeof(sigpipe_action)); + sigpipe_action.sa_handler = SIG_IGN; + sigemptyset(&sigpipe_action.sa_mask); + bool success = (sigaction(SIGPIPE, &sigpipe_action, NULL) == 0); + + // Avoid hangs during backtrace initialization, see above. + WarmUpBacktrace(); + + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_flags = SA_RESETHAND | SA_SIGINFO; + action.sa_sigaction = &StackDumpSignalHandler; + sigemptyset(&action.sa_mask); + + success &= (sigaction(SIGILL, &action, NULL) == 0); + success &= (sigaction(SIGABRT, &action, NULL) == 0); + success &= (sigaction(SIGFPE, &action, NULL) == 0); + success &= (sigaction(SIGBUS, &action, NULL) == 0); + success &= (sigaction(SIGSEGV, &action, NULL) == 0); +// On Linux, SIGSYS is reserved by the kernel for seccomp-bpf sandboxing. +#if !defined(OS_LINUX) + success &= (sigaction(SIGSYS, &action, NULL) == 0); +#endif // !defined(OS_LINUX) + + return success; +} + +StackTrace::StackTrace(bool exclude_self) { + // NOTE: This code MUST be async-signal safe (it's used by in-process + // stack dumping signal handler). NO malloc or stdio is allowed here. + + if (GetStackTrace) { + count_ = GetStackTrace(trace_, arraysize(trace_), exclude_self ? 1 : 0); + } else { +#if !defined(__UCLIBC__) + // Though the backtrace API man page does not list any possible negative + // return values, we take no chance. + count_ = butil::saturated_cast(backtrace(trace_, arraysize(trace_))); + if (exclude_self && count_ > 1) { + // Skip the top frame. + memmove(trace_, trace_ + 1, (count_ - 1) * sizeof(void*)); + count_--; + } +#else + count_ = 0; +#endif + } +} + +bool StackTrace::FindSymbol(void* symbol) const { +#if !defined(__UCLIBC__) + for (size_t i = 0; i < count_; ++i) { + uint64_t saddr; + // Subtract by one as return address of function may be in the next + // function when a function is annotated as noreturn. + void* address = static_cast(trace_[i]) - 1; + if (!google::SymbolizeAddress(address, &saddr)) { + continue; + } + if ((void*)saddr == symbol) { + return true; + } + } +#endif + return false; +} + +void StackTrace::Print() const { + // NOTE: This code MUST be async-signal safe (it's used by in-process + // stack dumping signal handler). NO malloc or stdio is allowed here. + +#if !defined(__UCLIBC__) + PrintBacktraceOutputHandler handler; + ProcessBacktrace(trace_, count_, &handler); +#endif +} + +#if !defined(__UCLIBC__) +void StackTrace::OutputToStream(std::ostream* os) const { + StreamBacktraceOutputHandler handler(os); + ProcessBacktrace(trace_, count_, &handler); +} + +void StackTrace::OutputToString(std::string& str) const { + StringBacktraceOutputHandler handler(str); + ProcessBacktrace(trace_, count_, &handler); +} +#endif + +namespace internal { + +// NOTE: code from sandbox/linux/seccomp-bpf/demo.cc. +char *itoa_r(intptr_t i, char *buf, size_t sz, int base, size_t padding) { + // Make sure we can write at least one NUL byte. + size_t n = 1; + if (n > sz) + return NULL; + + if (base < 2 || base > 16) { + buf[0] = '\000'; + return NULL; + } + + char *start = buf; + + uintptr_t j = i; + + // Handle negative numbers (only for base 10). + if (i < 0 && base == 10) { + j = -i; + + // Make sure we can write the '-' character. + if (++n > sz) { + buf[0] = '\000'; + return NULL; + } + *start++ = '-'; + } + + // Loop until we have converted the entire number. Output at least one + // character (i.e. '0'). + char *ptr = start; + do { + // Make sure there is still enough space left in our output buffer. + if (++n > sz) { + buf[0] = '\000'; + return NULL; + } + + // Output the next digit. + *ptr++ = "0123456789abcdef"[j % base]; + j /= base; + + if (padding > 0) + padding--; + } while (j > 0 || padding > 0); + + // Terminate the output with a NUL character. + *ptr = '\000'; + + // Conversion to ASCII actually resulted in the digits being in reverse + // order. We can't easily generate them in forward order, as we can't tell + // the number of characters needed until we are done converting. + // So, now, we reverse the string (except for the possible "-" sign). + while (--ptr > start) { + char ch = *ptr; + *ptr = *start; + *start++ = ch; + } + return buf; +} + +} // namespace internal + +} // namespace debug +} // namespace butil diff --git a/src/butil/details/extended_endpoint.hpp b/src/butil/details/extended_endpoint.hpp new file mode 100644 index 0000000..36a6771 --- /dev/null +++ b/src/butil/details/extended_endpoint.hpp @@ -0,0 +1,383 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BUTIL_DETAILS_EXTENDED_ENDPOINT_H +#define BUTIL_DETAILS_EXTENDED_ENDPOINT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "butil/endpoint.h" +#include "butil/logging.h" +#include "butil/strings/string_piece.h" +#include "butil/resource_pool.h" +#include "butil/memory/singleton_on_pthread_once.h" + +namespace butil { +namespace details { + +#if __cplusplus >= 201103L +static_assert(sizeof(EndPoint) == sizeof(EndPoint::ip) + sizeof(EndPoint::port), + "EndPoint size mismatch with the one in POD-style, may cause ABI problem"); +#endif + +// For ipv6/unix socket address. +// +// We have to keep butil::EndPoint ABI compatible because it is used so widely, and the size of butil::EndPoint is +// too small to store more information such as ipv6 address. +// We store enough information about endpoint in such tiny struct by putting real things in another big object +// holding by ResourcePool. The EndPoint::ip saves ResourceId, while EndPoint::port denotes if the EndPoint object +// is an old style ipv4 endpoint. +// Note that since ResourcePool has been implemented in bthread, we copy it into this repo and change its namespace to +// butil::details. Those two headers will not be published. + +// If EndPoint.port equals to this value, we should get the extended endpoint in resource pool. +const static int EXTENDED_ENDPOINT_PORT = 123456789; + +class ExtendedEndPoint; + +// A global unordered set to dedup ExtendedEndPoint +// ExtendedEndPoints which have same ipv6/unix socket address must have same id, +// so that user can simply use the value of EndPoint for comparision. +class GlobalEndPointSet { +public: + ExtendedEndPoint* insert(ExtendedEndPoint* p); + + void erase(ExtendedEndPoint* p); + + static GlobalEndPointSet* instance() { + return ::butil::get_leaky_singleton(); + } + +private: + struct Hash { + size_t operator()(ExtendedEndPoint* const& p) const; + }; + + struct Equals { + bool operator()(ExtendedEndPoint* const& p1, ExtendedEndPoint* const& p2) const; + }; + + typedef std::unordered_set SetType; + SetType _set; + std::mutex _mutex; +}; + +class ExtendedEndPoint { +public: + // Construct ExtendedEndPoint. + // User should use create() functions to get ExtendedEndPoint instance. + ExtendedEndPoint(void) { + _ref_count.store(0, butil::memory_order_relaxed); + _u.sa.sa_family = AF_UNSPEC; + } + +public: + // Create ExtendedEndPoint. + // If creation is successful, create()s will embed the ExtendedEndPoint instance in the given EndPoint*, + // and return it as well. Or else, the given EndPoint* won't be touched. + // + // The format of the parameter is inspired by nginx. + // Valid forms are: + // - ipv6 + // without port: [2400:da00::3b0b] + // with port: [2400:da00::3b0b]:8080 + // - unix domain socket + // abslute path : unix:/path/to/file.sock + // relative path: unix:path/to/file.sock + + static ExtendedEndPoint* create(StringPiece sp, EndPoint* ep) { + sp.trim_spaces(); + if (sp.empty()) { + return NULL; + } + if (sp[0] == '[') { + size_t colon_pos = sp.find(']'); + if (colon_pos == StringPiece::npos || colon_pos == 1 /* [] is invalid */ || ++colon_pos >= sp.size()) { + return NULL; + } + StringPiece port_sp = sp.substr(colon_pos); + if (port_sp.size() < 2 /* colon and at least one integer */ || port_sp[0] != ':') { + return NULL; + } + port_sp.remove_prefix(1); // remove `:' + if (port_sp.size() > 5) { // max 65535 + return NULL; + } + char buf[6]; + buf[port_sp.copy(buf, port_sp.size())] = '\0'; + char* end = NULL; + int port = ::strtol(buf, &end, 10 /* base */); + if (end != buf + port_sp.size()) { + return NULL; + } + return create(sp.substr(0, colon_pos), port, ep); + } else if (sp.starts_with("unix:")) { + return create(sp, EXTENDED_ENDPOINT_PORT, ep); + } + return NULL; + } + + static ExtendedEndPoint* create(StringPiece sp, int port, EndPoint* ep) { + sp.trim_spaces(); + if (sp.empty()) { + return NULL; + } + ExtendedEndPoint* eep = NULL; + if (sp[0] == '[' && port >= 0 && port <= 65535) { + if (sp.back() != ']' || sp.size() == 2 || sp.size() - 2 >= INET6_ADDRSTRLEN) { + return NULL; + } + char buf[INET6_ADDRSTRLEN]; + buf[sp.copy(buf, sp.size() - 2 /* skip `[' and `]' */, 1 /* skip `[' */)] = '\0'; + + in6_addr addr; + if (inet_pton(AF_INET6, buf, &addr) != 1 /* succ */) { + return NULL; + } + + eep = new_extended_endpoint(AF_INET6); + if (eep) { + eep->_u.in6.sin6_addr = addr; + eep->_u.in6.sin6_port = htons(port); + eep->_u.in6.sin6_flowinfo = 0u; + eep->_u.in6.sin6_scope_id = 0u; + eep->_socklen = sizeof(_u.in6); +#if defined(OS_MACOSX) + eep->_u.in6.sin6_len = eep->_socklen; +#endif + } + } else if (sp.starts_with("unix:")) { // ignore port + sp.remove_prefix(5); // remove `unix:' + if (sp.empty() || sp.size() >= UDS_PATH_SIZE) { + return NULL; + } + eep = new_extended_endpoint(AF_UNIX); + if (eep) { + int size = sp.copy(eep->_u.un.sun_path, sp.size()); + eep->_u.un.sun_path[size] = '\0'; + eep->_socklen = offsetof(sockaddr_un, sun_path) + size + 1; +#if defined(OS_MACOSX) + eep->_u.un.sun_len = eep->_socklen; +#endif + } + } + if (eep) { + eep = dedup(eep); + eep->embed_to(ep); + } + return eep; + } + + static ExtendedEndPoint* create(sockaddr_storage* ss, socklen_t size, EndPoint* ep) { + ExtendedEndPoint* eep = NULL; + if (ss->ss_family == AF_INET6 || ss->ss_family == AF_UNIX) { + eep = new_extended_endpoint(ss->ss_family); + } + if (eep) { + memcpy(&eep->_u.ss, ss, size); + eep->_socklen = size; + if (ss->ss_family == AF_UNIX && size == offsetof(sockaddr_un, sun_path)) { + // See unix(7): When the address of an unnamed socket is returned, + // its length is sizeof(sa_family_t), and sun_path should not be inspected. + eep->_u.un.sun_path[0] = '\0'; + } + eep = dedup(eep); + eep->embed_to(ep); + } + return eep; + } + + // Get ExtendedEndPoint instance from EndPoint + static ExtendedEndPoint* address(const EndPoint& ep) { + if (!is_extended(ep)) { + return NULL; + } + ::butil::ResourceId id; + id.value = ep.ip.s_addr; + ExtendedEndPoint* eep = ::butil::address_resource(id); + CHECK(eep) << "fail to address ExtendedEndPoint from EndPoint"; + return eep; + } + + // Check if an EndPoint has embedded ExtendedEndPoint + static bool is_extended(const butil::EndPoint& ep) { + return ep.port == EXTENDED_ENDPOINT_PORT; + } + +private: + friend class GlobalEndPointSet; + + static GlobalEndPointSet* global_set() { + return GlobalEndPointSet::instance(); + } + + static ExtendedEndPoint* new_extended_endpoint(sa_family_t family) { + ::butil::ResourceId id; + ExtendedEndPoint* eep = ::butil::get_resource(&id); + if (eep) { + int64_t old_ref = eep->_ref_count.load(butil::memory_order_relaxed); + CHECK(old_ref == 0) << "new ExtendedEndPoint has reference " << old_ref; + CHECK(eep->_u.sa.sa_family == AF_UNSPEC) << "new ExtendedEndPoint has family " << eep->_u.sa.sa_family << " set"; + eep->_ref_count.store(1, butil::memory_order_relaxed); + eep->_id = id; + eep->_u.sa.sa_family = family; + } + return eep; + } + + void embed_to(EndPoint* ep) const { + CHECK(0 == _id.value >> 32) << "ResourceId beyond index"; + ep->reset(); + ep->ip = ip_t{static_cast(_id.value)}; + ep->port = EXTENDED_ENDPOINT_PORT; + } + + static ExtendedEndPoint* dedup(ExtendedEndPoint* eep) { + eep->_hash = std::hash()(std::string((const char*)&eep->_u, eep->_socklen)); + + ExtendedEndPoint* first_eep = global_set()->insert(eep); + if (first_eep != eep) { + eep->_ref_count.store(0, butil::memory_order_relaxed); + eep->_u.sa.sa_family = AF_UNSPEC; + ::butil::return_resource(eep->_id); + } + return first_eep; + } + +public: + + void dec_ref(void) { + int64_t old_ref = _ref_count.fetch_sub(1, butil::memory_order_relaxed); + CHECK(old_ref >= 1) << "ExtendedEndPoint has unexpected reference " << old_ref; + if (old_ref == 1) { + global_set()->erase(this); + _u.sa.sa_family = AF_UNSPEC; + ::butil::return_resource(_id); + } + } + + void inc_ref(void) { + int64_t old_ref = _ref_count.fetch_add(1, butil::memory_order_relaxed); + CHECK(old_ref >= 1) << "ExtendedEndPoint has unexpected reference " << old_ref; + } + + sa_family_t family(void) const { + return _u.sa.sa_family; + } + + int to(sockaddr_storage* ss) const { + memcpy(ss, &_u.ss, _socklen); + return _socklen; + } + + void to(EndPointStr* ep_str) const { + if (_u.sa.sa_family == AF_UNIX) { + snprintf(ep_str->_buf, sizeof(ep_str->_buf), "unix:%s", _u.un.sun_path); + } else if (_u.sa.sa_family == AF_INET6) { + char buf[INET6_ADDRSTRLEN] = {0}; + const char* ret = inet_ntop(_u.sa.sa_family, &_u.in6.sin6_addr, buf, sizeof(buf)); + CHECK(ret) << "fail to do inet_ntop"; + snprintf(ep_str->_buf, sizeof(ep_str->_buf), "[%s]:%d", buf, ntohs(_u.in6.sin6_port)); + } else { + CHECK(0) << "family " << _u.sa.sa_family << " not supported"; + } + } + + int to_hostname(char* host, size_t host_len) const { + if (_u.sa.sa_family == AF_UNIX) { + snprintf(host, host_len, "unix:%s", _u.un.sun_path); + return 0; + } else if (_u.sa.sa_family == AF_INET6) { + sockaddr_in6 sa = _u.in6; + if (getnameinfo((const sockaddr*) &sa, sizeof(sa), host, host_len, NULL, 0, NI_NAMEREQD) != 0) { + return -1; + } + size_t len = ::strlen(host); + if (len + 1 < host_len) { + snprintf(host + len, host_len - len, ":%d", _u.in6.sin6_port); + } + return 0; + } else { + CHECK(0) << "family " << _u.sa.sa_family << " not supported"; + return -1; + } + } + +private: + static const size_t UDS_PATH_SIZE = sizeof(sockaddr_un::sun_path); + + butil::atomic _ref_count; + butil::ResourceId _id; + size_t _hash; // pre-compute hash code of sockaddr for saving unordered_set query time + socklen_t _socklen; // valid data length of sockaddr + union { + sockaddr sa; + sockaddr_in6 in6; + sockaddr_un un; + sockaddr_storage ss; + } _u; +}; + +inline ExtendedEndPoint* GlobalEndPointSet::insert(ExtendedEndPoint* p) { + std::unique_lock lock(_mutex); + auto it = _set.find(p); + if (it != _set.end()) { + if ((*it)->_ref_count.fetch_add(1, butil::memory_order_relaxed) == 0) { + // another thread is calling dec_ref(), do not reuse it + (*it)->_ref_count.fetch_sub(1, butil::memory_order_relaxed); + _set.erase(it); + _set.insert(p); + return p; + } else { + // the ExtendedEndPoint is valid, reuse it + return *it; + } + } + _set.insert(p); + return p; +} + +inline void GlobalEndPointSet::erase(ExtendedEndPoint* p) { + std::unique_lock lock(_mutex); + auto it = _set.find(p); + if (it == _set.end() || *it != p) { + // another thread has been erase it + return; + } + _set.erase(it); +} + +inline size_t GlobalEndPointSet::Hash::operator()(ExtendedEndPoint* const& p) const { + return p->_hash; +} + +inline bool GlobalEndPointSet::Equals::operator()(ExtendedEndPoint* const& p1, ExtendedEndPoint* const& p2) const { + return p1->_socklen == p2->_socklen + && memcmp(&p1->_u, &p2->_u, p1->_socklen) == 0; +} + +} // namespace details +} // namespace butil + +#endif // BUTIL_DETAILS_EXTENDED_ENDPOINT_H diff --git a/src/butil/endpoint.cpp b/src/butil/endpoint.cpp new file mode 100644 index 0000000..c5a888b --- /dev/null +++ b/src/butil/endpoint.cpp @@ -0,0 +1,696 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Mon. Nov 7 14:47:36 CST 2011 + +#include "butil/compat.h" +#include // inet_pton, inet_ntop +#include // gethostbyname_r +#include // gethostname +#include // errno +#include // strcpy +#include // snprintf +#include // strtol +#include // sockaddr_un +#include // SO_REUSEADDR SO_REUSEPORT +#include +#include +#include +#if defined(OS_MACOSX) +#include // kevent(), kqueue() +#endif +#include "butil/build_config.h" // OS_MACOSX +#include "butil/fd_guard.h" // fd_guard +#include "butil/endpoint.h" // ip_t +#include "butil/logging.h" +#include "butil/memory/singleton_on_pthread_once.h" +#include "butil/strings/string_piece.h" +#include "butil/fd_utility.h" +#include "butil/memory/scope_guard.h" + +//supported since Linux 3.9. +DEFINE_bool(reuse_port, false, "Enable SO_REUSEPORT for all listened sockets"); + +DEFINE_bool(reuse_addr, true, "Enable SO_REUSEADDR for all listened sockets"); + +DEFINE_bool(reuse_uds_path, false, "remove unix domain socket file before listen to it"); + +__BEGIN_DECLS +int BAIDU_WEAK bthread_connect( + int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen) { + return ::connect(sockfd, serv_addr, addrlen); +} + +int BAIDU_WEAK bthread_timed_connect( + int sockfd, const struct sockaddr* serv_addr, + socklen_t addrlen, const timespec* abstime); + +__END_DECLS + +#include "butil/details/extended_endpoint.hpp" + +namespace butil { + +using details::ExtendedEndPoint; + +static void set_endpoint(EndPoint* ep, ip_t ip, int port) { + ep->ip = ip; + ep->port = port; + if (ExtendedEndPoint::is_extended(*ep)) { + ExtendedEndPoint* eep = ExtendedEndPoint::address(*ep); + if (eep) { + eep->inc_ref(); + } else { + ep->ip = IP_ANY; + ep->port = 0; + } + } +} + +void EndPoint::reset(void) { + if (ExtendedEndPoint::is_extended(*this)) { + ExtendedEndPoint* eep = ExtendedEndPoint::address(*this); + if (eep) { + eep->dec_ref(); + } + } + ip = IP_ANY; + port = 0; +} + +EndPoint::EndPoint(ip_t ip2, int port2) : ip(ip2), port(port2) { + // Should never construct an extended endpoint by this way + if (ExtendedEndPoint::is_extended(*this)) { + CHECK(0) << "EndPoint construct with value that points to an extended EndPoint"; + ip = IP_ANY; + port = 0; + } +} + +EndPoint::EndPoint(const EndPoint& rhs) { + set_endpoint(this, rhs.ip, rhs.port); +} + +EndPoint::~EndPoint() { + reset(); +} + +void EndPoint::operator=(const EndPoint& rhs) { + reset(); + set_endpoint(this, rhs.ip, rhs.port); +} + +int str2ip(const char* ip_str, ip_t* ip) { + // ip_str can be NULL when called by EndPoint(0, ...) + if (ip_str != NULL) { + for (; isspace(*ip_str); ++ip_str); + int rc = inet_pton(AF_INET, ip_str, ip); + if (rc > 0) { + return 0; + } + } + return -1; +} + +IPStr ip2str(ip_t ip) { + IPStr str; + if (inet_ntop(AF_INET, &ip, str._buf, INET_ADDRSTRLEN) == NULL) { + return ip2str(IP_NONE); + } + return str; +} + +int ip2hostname(ip_t ip, char* host, size_t host_len) { + if (host == NULL || host_len == 0) { + errno = EINVAL; + return -1; + } + sockaddr_in sa; + bzero((char*)&sa, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = 0; // useless since we don't need server_name + sa.sin_addr = ip; + if (getnameinfo((const sockaddr*)&sa, sizeof(sa), + host, host_len, NULL, 0, NI_NAMEREQD) != 0) { + return -1; + } + // remove baidu-specific domain name (that every name has) + butil::StringPiece str(host); + if (str.ends_with(".baidu.com")) { + host[str.size() - 10] = '\0'; + } + return 0; +} + +int ip2hostname(ip_t ip, std::string* host) { + char buf[128]; + if (ip2hostname(ip, buf, sizeof(buf)) == 0) { + host->assign(buf); + return 0; + } + return -1; +} + +EndPointStr endpoint2str(const EndPoint& point) { + EndPointStr str; + if (ExtendedEndPoint::is_extended(point)) { + ExtendedEndPoint* eep = ExtendedEndPoint::address(point); + if (eep) { + eep->to(&str); + } else { + str._buf[0] = '\0'; + } + return str; + } + if (inet_ntop(AF_INET, &point.ip, str._buf, INET_ADDRSTRLEN) == NULL) { + return endpoint2str(EndPoint(IP_NONE, 0)); + } + char* buf = str._buf + strlen(str._buf); + *buf++ = ':'; + snprintf(buf, 16, "%d", point.port); + return str; +} + +int hostname2ip(const char* hostname, ip_t* ip) { + char buf[256]; + if (NULL == hostname) { + if (gethostname(buf, sizeof(buf)) < 0) { + return -1; + } + hostname = buf; + } else { + // skip heading space + for (; isspace(*hostname); ++hostname); + } + +#if defined(OS_MACOSX) + // gethostbyname on MAC is thread-safe (with current usage) since the + // returned hostent is TLS. Check following link for the ref: + // https://lists.apple.com/archives/darwin-dev/2006/May/msg00008.html + struct hostent* result = gethostbyname(hostname); + if (result == NULL) { + return -1; + } +#else + int aux_buf_len = 1024; + std::unique_ptr aux_buf(new char[aux_buf_len]); + int ret = 0; + int error = 0; + struct hostent ent; + struct hostent* result = NULL; + do { + result = NULL; + error = 0; + ret = gethostbyname_r(hostname, + &ent, + aux_buf.get(), + aux_buf_len, + &result, + &error); + if (ret != ERANGE) { // aux_buf is not long enough + break; + } + aux_buf_len *= 2; + aux_buf.reset(new char[aux_buf_len]); + } while (1); + if (ret != 0 || result == NULL) { + return -1; + } +#endif // defined(OS_MACOSX) + // Only fetch the first address here + bcopy((char*)result->h_addr, (char*)ip, result->h_length); + return 0; +} + +struct MyAddressInfo { + char my_hostname[256]; + ip_t my_ip; + IPStr my_ip_str; + + MyAddressInfo() { + my_ip = IP_ANY; + if (gethostname(my_hostname, sizeof(my_hostname)) < 0) { + my_hostname[0] = '\0'; + } else if (hostname2ip(my_hostname, &my_ip) != 0) { + my_ip = IP_ANY; + } + my_ip_str = ip2str(my_ip); + } +}; + +ip_t my_ip() { + return get_leaky_singleton()->my_ip; +} + +const char* my_ip_cstr() { + return get_leaky_singleton()->my_ip_str.c_str(); +} + +const char* my_hostname() { + return get_leaky_singleton()->my_hostname; +} + +int str2endpoint(const char* str, EndPoint* point) { + if (ExtendedEndPoint::create(str, point)) { + return 0; + } + + // Should be enough to hold ip address + char buf[64]; + size_t i = 0; + for (; i < sizeof(buf) && str[i] != '\0' && str[i] != ':'; ++i) { + buf[i] = str[i]; + } + if (i >= sizeof(buf) || str[i] != ':') { + return -1; + } + buf[i] = '\0'; + if (str2ip(buf, &point->ip) != 0) { + return -1; + } + ++i; + char* end = NULL; + point->port = strtol(str + i, &end, 10); + if (end == str + i) { + return -1; + } else if (*end) { + for (; isspace(*end); ++end); + if (*end) { + return -1; + } + } + if (point->port < 0 || point->port > 65535) { + return -1; + } + return 0; +} + +int str2endpoint(const char* ip_str, int port, EndPoint* point) { + if (ExtendedEndPoint::create(ip_str, port, point)) { + return 0; + } + + if (str2ip(ip_str, &point->ip) != 0) { + return -1; + } + if (port < 0 || port > 65535) { + return -1; + } + point->port = port; + return 0; +} + +int hostname2endpoint(const char* str, EndPoint* point) { + // Should be enough to hold ip address + // The definitive descriptions of the rules for forming domain names appear in RFC 1035, RFC 1123, RFC 2181, + // and RFC 5892. The full domain name may not exceed the length of 253 characters in its textual representation + // (Domain Names - Domain Concepts and Facilities. IETF. doi:10.17487/RFC1034. RFC 1034.). + // For cacheline optimize, use buf size as 256; + char buf[256]; + size_t i = 0; + for (; i < MAX_DOMAIN_LENGTH && str[i] != '\0' && str[i] != ':'; ++i) { + buf[i] = str[i]; + } + + if (i >= MAX_DOMAIN_LENGTH || str[i] != ':') { + return -1; + } + + buf[i] = '\0'; + if (hostname2ip(buf, &point->ip) != 0) { + return -1; + } + if (str[i] == ':') { + ++i; + } + char* end = NULL; + point->port = strtol(str + i, &end, 10); + if (end == str + i) { + return -1; + } else if (*end) { + for (; isspace(*end); ++end); + if (*end) { + return -1; + } + } + if (point->port < 0 || point->port > 65535) { + return -1; + } + return 0; +} + +int hostname2endpoint(const char* name_str, int port, EndPoint* point) { + if (hostname2ip(name_str, &point->ip) != 0) { + return -1; + } + if (port < 0 || port > 65535) { + return -1; + } + point->port = port; + return 0; +} + +int endpoint2hostname(const EndPoint& point, char* host, size_t host_len) { + if (ExtendedEndPoint::is_extended(point)) { + ExtendedEndPoint* eep = ExtendedEndPoint::address(point); + if (eep) { + return eep->to_hostname(host, host_len); + } + return -1; + } + + if (ip2hostname(point.ip, host, host_len) == 0) { + size_t len = strlen(host); + if (len + 1 < host_len) { + snprintf(host + len, host_len - len, ":%d", point.port); + } + return 0; + } + return -1; +} + +int endpoint2hostname(const EndPoint& point, std::string* host) { + char buf[256]; + if (endpoint2hostname(point, buf, sizeof(buf)) == 0) { + host->assign(buf); + return 0; + } + return -1; +} + +#if defined(OS_LINUX) +static short epoll_to_poll_events(uint32_t epoll_events) { + // Most POLL* and EPOLL* share the same numeric values for the basic + // event bits, so a plain mask is enough to translate them. + short poll_events = (epoll_events & + (EPOLLIN | EPOLLPRI | EPOLLOUT | + EPOLLRDNORM | EPOLLRDBAND | + EPOLLWRNORM | EPOLLWRBAND | + EPOLLMSG | EPOLLERR | EPOLLHUP)); + // epoll-only modifier bits (EPOLLET / EPOLLONESHOT / EPOLLRDHUP / ...) + // have no poll(2) counterpart and MUST be silently dropped here: + // * poll(2) is already level-triggered and reports events per call, + // so EPOLLET / EPOLLONESHOT degrade naturally to "no-op". + // * `short` cannot even represent EPOLLET (bit 31 = 0x80000000). + // Without this filtering, a caller invoking bthread_fd_wait(fd, + // EPOLLIN | EPOLLET) from a pthread context would CHECK-fail here. + const uint32_t epoll_modifier_bits = 0u +#ifdef EPOLLET + | EPOLLET +#endif +#ifdef EPOLLONESHOT + | EPOLLONESHOT +#endif +#ifdef EPOLLRDHUP + | EPOLLRDHUP +#endif +#ifdef EPOLLEXCLUSIVE + | EPOLLEXCLUSIVE +#endif +#ifdef EPOLLWAKEUP + | EPOLLWAKEUP +#endif + ; + CHECK_EQ((uint32_t)poll_events, epoll_events & ~epoll_modifier_bits); + return poll_events; +} +#elif defined(OS_MACOSX) +short kqueue_to_poll_events(int kqueue_events) { + //TODO: add more values? + short poll_events = 0; + if (kqueue_events == EVFILT_READ) { + poll_events |= POLLIN; + } + if (kqueue_events == EVFILT_WRITE) { + poll_events |= POLLOUT; + } + return poll_events; +} +#endif + +int pthread_fd_wait(int fd, unsigned events, + const timespec* abstime) { +#if defined(OS_LINUX) + const short poll_events = epoll_to_poll_events(events); +#elif defined(OS_MACOSX) + const short poll_events = kqueue_to_poll_events(events); +#endif + if (poll_events == 0) { + errno = EINVAL; + return -1; + } + pollfd ufds = { fd, poll_events, 0 }; + int64_t abstime_us = -1; + if (NULL != abstime) { + abstime_us = butil::timespec_to_microseconds(*abstime); + } + while (true) { + int diff_ms = -1; + if (NULL != abstime) { + int64_t now_us = butil::gettimeofday_us(); + if (abstime_us <= now_us) { + errno = ETIMEDOUT; + return -1; + } + diff_ms = (abstime_us - now_us + 999L) / 1000L; + } + int rc = poll(&ufds, 1, diff_ms); + if (rc > 0) { + break; + } else if (rc == 0) { + errno = ETIMEDOUT; + return -1; + } else { + if (errno == EINTR) { + continue; + } + return -1; + } + } + if (ufds.revents & POLLNVAL) { + errno = EBADF; + return -1; + } + return 0; +} + +int pthread_timed_connect(int sockfd, const struct sockaddr* serv_addr, + socklen_t addrlen, const timespec* abstime) { + bool is_blocking = butil::is_blocking(sockfd); + if (is_blocking) { + butil::make_non_blocking(sockfd); + } + // Scoped non-blocking. + BRPC_SCOPE_EXIT { + if (is_blocking) { + butil::make_blocking(sockfd); + } + }; + + const int rc = ::connect(sockfd, serv_addr, addrlen); + if (rc == 0 || errno != EINPROGRESS) { + return rc; + } +#if defined(OS_LINUX) + if (pthread_fd_wait(sockfd, EPOLLOUT, abstime) < 0) { +#elif defined(OS_MACOSX) + if (pthread_fd_wait(sockfd, EVFILT_WRITE, abstime) < 0) { +#endif + return -1; + } + + if (is_connected(sockfd) != 0) { + return -1; + } + return 0; +} + +int tcp_connect(EndPoint server, int* self_port) { + return tcp_connect(server, self_port, -1); +} + +int tcp_connect(const EndPoint& server, int* self_port, int connect_timeout_ms) { + struct sockaddr_storage serv_addr{}; + socklen_t serv_addr_size = 0; + if (endpoint2sockaddr(server, &serv_addr, &serv_addr_size) != 0) { + return -1; + } + fd_guard sockfd(socket(serv_addr.ss_family, SOCK_STREAM, 0)); + if (sockfd < 0) { + return -1; + } + timespec abstime{}; + timespec* abstime_ptr = NULL; + if (connect_timeout_ms > 0) { + abstime = butil::milliseconds_from_now(connect_timeout_ms); + abstime_ptr = &abstime; + } + int rc; + if (bthread_timed_connect != NULL) { + rc = bthread_timed_connect(sockfd, (struct sockaddr*)&serv_addr, + serv_addr_size, abstime_ptr); + } else { + rc = pthread_timed_connect(sockfd, (struct sockaddr*) &serv_addr, + serv_addr_size, abstime_ptr); + } + if (rc < 0) { + return -1; + } + if (self_port != NULL) { + EndPoint pt; + if (get_local_side(sockfd, &pt) == 0) { + *self_port = pt.port; + } else { + CHECK(false) << "Fail to get the local port of sockfd=" << sockfd; + } + } + return sockfd.release(); +} + +int tcp_listen(EndPoint point) { + struct sockaddr_storage serv_addr; + socklen_t serv_addr_size = 0; + if (endpoint2sockaddr(point, &serv_addr, &serv_addr_size) != 0) { + return -1; + } + fd_guard sockfd(socket(serv_addr.ss_family, SOCK_STREAM, 0)); + if (sockfd < 0) { + return -1; + } + + if (FLAGS_reuse_addr) { +#if defined(SO_REUSEADDR) + const int on = 1; + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, + &on, sizeof(on)) != 0) { + return -1; + } +#else + LOG(ERROR) << "Missing def of SO_REUSEADDR while -reuse_addr is on"; + return -1; +#endif + } + + if (FLAGS_reuse_port) { +#if defined(SO_REUSEPORT) + const int on = 1; + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, + &on, sizeof(on)) != 0) { + LOG(WARNING) << "Fail to setsockopt SO_REUSEPORT of sockfd=" << sockfd; + } +#else + LOG(ERROR) << "Missing def of SO_REUSEPORT while -reuse_port is on"; + return -1; +#endif + } + + if (FLAGS_reuse_uds_path && serv_addr.ss_family == AF_UNIX) { + ::unlink(((sockaddr_un*) &serv_addr)->sun_path); + } + + if (::bind(sockfd, (struct sockaddr*)& serv_addr, serv_addr_size) != 0) { + return -1; + } + if (listen(sockfd, 65535) != 0) { + // ^^^ kernel would silently truncate backlog to the value + // defined in /proc/sys/net/core/somaxconn if it is less + // than 65535 + return -1; + } + return sockfd.release(); +} + +int get_local_side(int fd, EndPoint *out) { + struct sockaddr_storage addr; + socklen_t socklen = sizeof(addr); + const int rc = getsockname(fd, (struct sockaddr*)&addr, &socklen); + if (rc != 0) { + return rc; + } + if (out) { + return sockaddr2endpoint(&addr, socklen, out); + } + return 0; +} + +int get_remote_side(int fd, EndPoint *out) { + struct sockaddr_storage addr; + bzero(&addr, sizeof(addr)); + socklen_t socklen = sizeof(addr); + const int rc = getpeername(fd, (struct sockaddr*)&addr, &socklen); + if (rc != 0) { + return rc; + } + if (out) { + return sockaddr2endpoint(&addr, socklen, out); + } + return 0; +} + +int endpoint2sockaddr(const EndPoint& point, struct sockaddr_storage* ss, socklen_t* size) { + bzero(ss, sizeof(*ss)); + if (ExtendedEndPoint::is_extended(point)) { + ExtendedEndPoint* eep = ExtendedEndPoint::address(point); + if (!eep) { + return -1; + } + int ret = eep->to(ss); + if (ret < 0) { + return -1; + } + if (size) { + *size = static_cast(ret); + } + return 0; + } + struct sockaddr_in* in4 = (struct sockaddr_in*) ss; + in4->sin_family = AF_INET; + in4->sin_addr = point.ip; + in4->sin_port = htons(point.port); + if (size) { + *size = sizeof(*in4); + } + return 0; +} + +int sockaddr2endpoint(struct sockaddr_storage* ss, socklen_t size, EndPoint* point) { + if (ss->ss_family == AF_INET) { + *point = EndPoint(*(sockaddr_in*)ss); + return 0; + } + if (ExtendedEndPoint::create(ss, size, point)) { + return 0; + } + return -1; +} + +sa_family_t get_endpoint_type(const EndPoint& point) { + if (ExtendedEndPoint::is_extended(point)) { + ExtendedEndPoint* eep = ExtendedEndPoint::address(point); + if (eep) { + return eep->family(); + } + return AF_UNSPEC; + } + return AF_INET; +} + +bool is_endpoint_extended(const EndPoint& point) { + return ExtendedEndPoint::is_extended(point); +} + +} // namespace butil diff --git a/src/butil/endpoint.h b/src/butil/endpoint.h new file mode 100644 index 0000000..c6d00bb --- /dev/null +++ b/src/butil/endpoint.h @@ -0,0 +1,249 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Mon. Nov 7 14:47:36 CST 2011 + +// Wrappers of IP and port. + +#ifndef BUTIL_ENDPOINT_H +#define BUTIL_ENDPOINT_H + +#include // in_addr +#include // sockaddr_un +#include // std::ostream +#include "butil/containers/hash_tables.h" // hashing functions + +namespace butil { + +// Type of an IP address +typedef struct in_addr ip_t; + +static const ip_t IP_ANY = { INADDR_ANY }; +static const ip_t IP_NONE = { INADDR_NONE }; +static const int MAX_DOMAIN_LENGTH = 253; + +// Convert |ip| to an integral +inline in_addr_t ip2int(ip_t ip) { return ip.s_addr; } + +// Convert integral |ip_value| to an IP +inline ip_t int2ip(in_addr_t ip_value) { + const ip_t ip = { ip_value }; + return ip; +} + +// Convert string `ip_str' to ip_t *ip. +// `ip_str' is in IPv4 dotted-quad format: `127.0.0.1', `10.23.249.73' ... +// Returns 0 on success, -1 otherwise. +int str2ip(const char* ip_str, ip_t* ip); + +struct IPStr { + const char* c_str() const { return _buf; } + char _buf[INET_ADDRSTRLEN]; +}; + +// Convert IP to c-style string. Notice that you can serialize ip_t to +// std::ostream directly. Use this function when you don't have streaming log. +// Example: printf("ip=%s\n", ip2str(some_ip).c_str()); +IPStr ip2str(ip_t ip); + +// Convert `hostname' to ip_t *ip. If `hostname' is NULL, use hostname +// of this machine. +// `hostname' is typically in this form: `tc-cm-et21.tc' `db-cos-dev.db01' ... +// Returns 0 on success, -1 otherwise. +int hostname2ip(const char* hostname, ip_t* ip); + +// Convert `ip' to `hostname'. +// Returns 0 on success, -1 otherwise and errno is set. +int ip2hostname(ip_t ip, char* hostname, size_t hostname_len); +int ip2hostname(ip_t ip, std::string* hostname); + +// Hostname of this machine, "" on error. +// NOTE: This function caches result on first call. +const char* my_hostname(); + +// IP of this machine, IP_ANY on error. +// NOTE: This function caches result on first call. +ip_t my_ip(); +// String form. +const char* my_ip_cstr(); + +// For IPv4 endpoint, ip and port are real things. +// For UDS/IPv6 endpoint, to keep ABI compatibility, ip is ResourceId, and port is a special flag. +// See str2endpoint implementation for details. +struct EndPoint { + EndPoint() : ip(IP_ANY), port(0) {} + EndPoint(ip_t ip2, int port2); + explicit EndPoint(const sockaddr_in& in) + : ip(in.sin_addr), port(ntohs(in.sin_port)) {} + + EndPoint(const EndPoint&); + ~EndPoint(); + void operator=(const EndPoint&); + + void reset(void); + + ip_t ip; + int port; +}; + +struct EndPointStr { + const char* c_str() const { return _buf; } + char _buf[sizeof("unix:") + sizeof(sockaddr_un::sun_path)]; +}; + +// Convert EndPoint to c-style string. Notice that you can serialize +// EndPoint to std::ostream directly. Use this function when you don't +// have streaming log. +// Example: printf("point=%s\n", endpoint2str(point).c_str()); +EndPointStr endpoint2str(const EndPoint&); + +// Convert string `ip_and_port_str' to a EndPoint *point. +// Returns 0 on success, -1 otherwise. +int str2endpoint(const char* ip_and_port_str, EndPoint* point); +int str2endpoint(const char* ip_str, int port, EndPoint* point); + +// Convert `hostname_and_port_str' to a EndPoint *point. +// Returns 0 on success, -1 otherwise. +int hostname2endpoint(const char* ip_and_port_str, EndPoint* point); +int hostname2endpoint(const char* name_str, int port, EndPoint* point); + +// Convert `endpoint' to `hostname'. +// Returns 0 on success, -1 otherwise and errno is set. +int endpoint2hostname(const EndPoint& point, char* hostname, size_t hostname_len); +int endpoint2hostname(const EndPoint& point, std::string* host); + +// Create a TCP socket and connect it to `server'. Write port of this side +// into `self_port' if it's not NULL. +// Returns the socket descriptor, -1 otherwise and errno is set. +int tcp_connect(EndPoint server, int* self_port); +// Suspend caller thread until connect(2) on `sockfd' succeeds +// or CLOCK_REALTIME reached `abstime' if `abstime' is not NULL. +// Write port of this side into `self_port' if it's not NULL. +// Returns the socket descriptor, -1 otherwise and errno is set. +int tcp_connect(const EndPoint& server, int* self_port, int connect_timeout_ms); + +// Create and listen to a TCP socket bound with `ip_and_port'. +// To enable SO_REUSEADDR for the whole program, enable gflag -reuse_addr +// To enable SO_REUSEPORT for the whole program, enable gflag -reuse_port +// Returns the socket descriptor, -1 otherwise and errno is set. +int tcp_listen(EndPoint ip_and_port); + +// Get the local end of a socket connection +int get_local_side(int fd, EndPoint *out); + +// Get the other end of a socket connection +int get_remote_side(int fd, EndPoint *out); + +// Get sockaddr from endpoint, return -1 on failed +int endpoint2sockaddr(const EndPoint& point, struct sockaddr_storage* ss, socklen_t* size = NULL); + +// Create endpoint from sockaddr, return -1 on failed +int sockaddr2endpoint(struct sockaddr_storage* ss, socklen_t size, EndPoint* point); + +// Get EndPoint type (AF_INET/AF_INET6/AF_UNIX) +sa_family_t get_endpoint_type(const EndPoint& point); + +// Check if endpoint is extended. +bool is_endpoint_extended(const EndPoint& point); + +} // namespace butil + +// Since ip_t is defined from in_addr which is globally defined, due to ADL +// we have to put overloaded operators globally as well. +inline bool operator<(butil::ip_t lhs, butil::ip_t rhs) { + return butil::ip2int(lhs) < butil::ip2int(rhs); +} +inline bool operator>(butil::ip_t lhs, butil::ip_t rhs) { + return rhs < lhs; +} +inline bool operator>=(butil::ip_t lhs, butil::ip_t rhs) { + return !(lhs < rhs); +} +inline bool operator<=(butil::ip_t lhs, butil::ip_t rhs) { + return !(rhs < lhs); +} +inline bool operator==(butil::ip_t lhs, butil::ip_t rhs) { + return butil::ip2int(lhs) == butil::ip2int(rhs); +} +inline bool operator!=(butil::ip_t lhs, butil::ip_t rhs) { + return !(lhs == rhs); +} + +inline std::ostream& operator<<(std::ostream& os, const butil::IPStr& ip_str) { + return os << ip_str.c_str(); +} +inline std::ostream& operator<<(std::ostream& os, butil::ip_t ip) { + return os << butil::ip2str(ip); +} + +namespace butil { +// Overload operators for EndPoint in the same namespace due to ADL. +inline bool operator<(EndPoint p1, EndPoint p2) { + return (p1.ip != p2.ip) ? (p1.ip < p2.ip) : (p1.port < p2.port); +} +inline bool operator>(EndPoint p1, EndPoint p2) { + return p2 < p1; +} +inline bool operator<=(EndPoint p1, EndPoint p2) { + return !(p2 < p1); +} +inline bool operator>=(EndPoint p1, EndPoint p2) { + return !(p1 < p2); +} +inline bool operator==(EndPoint p1, EndPoint p2) { + return p1.ip == p2.ip && p1.port == p2.port; +} +inline bool operator!=(EndPoint p1, EndPoint p2) { + return !(p1 == p2); +} + +inline std::ostream& operator<<(std::ostream& os, const EndPoint& ep) { + return os << endpoint2str(ep).c_str(); +} +inline std::ostream& operator<<(std::ostream& os, const EndPointStr& ep_str) { + return os << ep_str.c_str(); +} + +} // namespace butil + + +namespace BUTIL_HASH_NAMESPACE { + +// Implement methods for hashing a pair of integers, so they can be used as +// keys in STL containers. + +#if defined(COMPILER_MSVC) + +inline std::size_t hash_value(const butil::EndPoint& ep) { + return butil::HashPair(butil::ip2int(ep.ip), ep.port); +} + +#elif defined(COMPILER_GCC) +template <> +struct hash { + std::size_t operator()(const butil::EndPoint& ep) const { + return butil::HashPair(butil::ip2int(ep.ip), ep.port); + } +}; + +#else +#error define hash for your compiler +#endif // COMPILER + +} + +#endif // BUTIL_ENDPOINT_H diff --git a/src/butil/environment.cc b/src/butil/environment.cc new file mode 100644 index 0000000..a46fc75 --- /dev/null +++ b/src/butil/environment.cc @@ -0,0 +1,237 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/environment.h" + +#include + +#include "butil/strings/string_piece.h" +#include "butil/strings/string_util.h" +#include "butil/strings/utf_string_conversions.h" + +#if defined(OS_POSIX) +#include +#elif defined(OS_WIN) +#include +#endif + +namespace butil { + +namespace { + +class EnvironmentImpl : public butil::Environment { + public: + virtual bool GetVar(const char* variable_name, + std::string* result) OVERRIDE { + if (GetVarImpl(variable_name, result)) + return true; + + // Some commonly used variable names are uppercase while others + // are lowercase, which is inconsistent. Let's try to be helpful + // and look for a variable name with the reverse case. + // I.e. HTTP_PROXY may be http_proxy for some users/systems. + char first_char = variable_name[0]; + std::string alternate_case_var; + if (first_char >= 'a' && first_char <= 'z') + alternate_case_var = StringToUpperASCII(std::string(variable_name)); + else if (first_char >= 'A' && first_char <= 'Z') + alternate_case_var = StringToLowerASCII(std::string(variable_name)); + else + return false; + return GetVarImpl(alternate_case_var.c_str(), result); + } + + virtual bool SetVar(const char* variable_name, + const std::string& new_value) OVERRIDE { + return SetVarImpl(variable_name, new_value); + } + + virtual bool UnSetVar(const char* variable_name) OVERRIDE { + return UnSetVarImpl(variable_name); + } + + private: + bool GetVarImpl(const char* variable_name, std::string* result) { +#if defined(OS_POSIX) + const char* env_value = getenv(variable_name); + if (!env_value) + return false; + // Note that the variable may be defined but empty. + if (result) + *result = env_value; + return true; +#elif defined(OS_WIN) + DWORD value_length = ::GetEnvironmentVariable( + UTF8ToWide(variable_name).c_str(), NULL, 0); + if (value_length == 0) + return false; + if (result) { + scoped_ptr value(new wchar_t[value_length]); + ::GetEnvironmentVariable(UTF8ToWide(variable_name).c_str(), value.get(), + value_length); + *result = WideToUTF8(value.get()); + } + return true; +#else +#error need to port +#endif + } + + bool SetVarImpl(const char* variable_name, const std::string& new_value) { +#if defined(OS_POSIX) + // On success, zero is returned. + return !setenv(variable_name, new_value.c_str(), 1); +#elif defined(OS_WIN) + // On success, a nonzero value is returned. + return !!SetEnvironmentVariable(UTF8ToWide(variable_name).c_str(), + UTF8ToWide(new_value).c_str()); +#endif + } + + bool UnSetVarImpl(const char* variable_name) { +#if defined(OS_POSIX) + // On success, zero is returned. + return !unsetenv(variable_name); +#elif defined(OS_WIN) + // On success, a nonzero value is returned. + return !!SetEnvironmentVariable(UTF8ToWide(variable_name).c_str(), NULL); +#endif + } +}; + +// Parses a null-terminated input string of an environment block. The key is +// placed into the given string, and the total length of the line, including +// the terminating null, is returned. +size_t ParseEnvLine(const NativeEnvironmentString::value_type* input, + NativeEnvironmentString* key) { + // Skip to the equals or end of the string, this is the key. + size_t cur = 0; + while (input[cur] && input[cur] != '=') + cur++; + *key = NativeEnvironmentString(&input[0], cur); + + // Now just skip to the end of the string. + while (input[cur]) + cur++; + return cur + 1; +} + +} // namespace + +namespace env_vars { + +#if defined(OS_POSIX) +// On Posix systems, this variable contains the location of the user's home +// directory. (e.g, /home/username/). +const char kHome[] = "HOME"; +#endif + +} // namespace env_vars + +Environment::~Environment() {} + +// static +Environment* Environment::Create() { + return new EnvironmentImpl(); +} + +bool Environment::HasVar(const char* variable_name) { + return GetVar(variable_name, NULL); +} + +#if defined(OS_WIN) + +string16 AlterEnvironment(const wchar_t* env, + const EnvironmentMap& changes) { + string16 result; + + // First copy all unmodified values to the output. + size_t cur_env = 0; + string16 key; + while (env[cur_env]) { + const wchar_t* line = &env[cur_env]; + size_t line_length = ParseEnvLine(line, &key); + + // Keep only values not specified in the change vector. + EnvironmentMap::const_iterator found_change = changes.find(key); + if (found_change == changes.end()) + result.append(line, line_length); + + cur_env += line_length; + } + + // Now append all modified and new values. + for (EnvironmentMap::const_iterator i = changes.begin(); + i != changes.end(); ++i) { + if (!i->second.empty()) { + result.append(i->first); + result.push_back('='); + result.append(i->second); + result.push_back(0); + } + } + + // An additional null marks the end of the list. We always need a double-null + // in case nothing was added above. + if (result.empty()) + result.push_back(0); + result.push_back(0); + return result; +} + +#elif defined(OS_POSIX) + +scoped_ptr AlterEnvironment(const char* const* const env, + const EnvironmentMap& changes) { + std::string value_storage; // Holds concatenated null-terminated strings. + std::vector result_indices; // Line indices into value_storage. + + // First build up all of the unchanged environment strings. These are + // null-terminated of the form "key=value". + std::string key; + for (size_t i = 0; env[i]; i++) { + size_t line_length = ParseEnvLine(env[i], &key); + + // Keep only values not specified in the change vector. + EnvironmentMap::const_iterator found_change = changes.find(key); + if (found_change == changes.end()) { + result_indices.push_back(value_storage.size()); + value_storage.append(env[i], line_length); + } + } + + // Now append all modified and new values. + for (EnvironmentMap::const_iterator i = changes.begin(); + i != changes.end(); ++i) { + if (!i->second.empty()) { + result_indices.push_back(value_storage.size()); + value_storage.append(i->first); + value_storage.push_back('='); + value_storage.append(i->second); + value_storage.push_back(0); + } + } + + size_t pointer_count_required = + result_indices.size() + 1 + // Null-terminated array of pointers. + (value_storage.size() + sizeof(char*) - 1) / sizeof(char*); // Buffer. + scoped_ptr result(new char*[pointer_count_required]); + + // The string storage goes after the array of pointers. + char* storage_data = reinterpret_cast( + &result.get()[result_indices.size() + 1]); + if (!value_storage.empty()) + memcpy(storage_data, value_storage.data(), value_storage.size()); + + // Fill array of pointers at the beginning of the result. + for (size_t i = 0; i < result_indices.size(); i++) + result[i] = &storage_data[result_indices[i]]; + result[result_indices.size()] = 0; // Null terminator. + + return result.Pass(); +} + +#endif // OS_POSIX + +} // namespace butil diff --git a/src/butil/environment.h b/src/butil/environment.h new file mode 100644 index 0000000..b0e99c7 --- /dev/null +++ b/src/butil/environment.h @@ -0,0 +1,90 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_ENVIRONMENT_H_ +#define BUTIL_ENVIRONMENT_H_ + +#include +#include + +#include "butil/base_export.h" +#include "butil/memory/scoped_ptr.h" +#include "butil/strings/string16.h" +#include "butil/build_config.h" + +namespace butil { + +namespace env_vars { + +#if defined(OS_POSIX) +BUTIL_EXPORT extern const char kHome[]; +#endif + +} // namespace env_vars + +class BUTIL_EXPORT Environment { + public: + virtual ~Environment(); + + // Static factory method that returns the implementation that provide the + // appropriate platform-specific instance. + static Environment* Create(); + + // Gets an environment variable's value and stores it in |result|. + // Returns false if the key is unset. + virtual bool GetVar(const char* variable_name, std::string* result) = 0; + + // Syntactic sugar for GetVar(variable_name, NULL); + virtual bool HasVar(const char* variable_name); + + // Returns true on success, otherwise returns false. + virtual bool SetVar(const char* variable_name, + const std::string& new_value) = 0; + + // Returns true on success, otherwise returns false. + virtual bool UnSetVar(const char* variable_name) = 0; +}; + + +#if defined(OS_WIN) + +typedef string16 NativeEnvironmentString; +typedef std::map + EnvironmentMap; + +// Returns a modified environment vector constructed from the given environment +// and the list of changes given in |changes|. Each key in the environment is +// matched against the first element of the pairs. In the event of a match, the +// value is replaced by the second of the pair, unless the second is empty, in +// which case the key-value is removed. +// +// This Windows version takes and returns a Windows-style environment block +// which is a concatenated list of null-terminated 16-bit strings. The end is +// marked by a double-null terminator. The size of the returned string will +// include the terminators. +BUTIL_EXPORT string16 AlterEnvironment(const wchar_t* env, + const EnvironmentMap& changes); + +#elif defined(OS_POSIX) + +typedef std::string NativeEnvironmentString; +typedef std::map + EnvironmentMap; + +// See general comments for the Windows version above. +// +// This Posix version takes and returns a Posix-style environment block, which +// is a null-terminated list of pointers to null-terminated strings. The +// returned array will have appended to it the storage for the array itself so +// there is only one pointer to manage, but this means that you can't copy the +// array without keeping the original around. +BUTIL_EXPORT scoped_ptr AlterEnvironment( + const char* const* env, + const EnvironmentMap& changes); + +#endif + +} // namespace butil + +#endif // BUTIL_ENVIRONMENT_H_ diff --git a/src/butil/errno.cpp b/src/butil/errno.cpp new file mode 100644 index 0000000..9b964e1 --- /dev/null +++ b/src/butil/errno.cpp @@ -0,0 +1,101 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Fri Sep 10 13:34:25 CST 2010 + +#include "butil/build_config.h" // OS_MACOSX +#include // errno +#include // strerror_r +#include // EXIT_FAILURE +#include // snprintf +#include // pthread_mutex_t +#include // _exit +#include "butil/scoped_lock.h" // BAIDU_SCOPED_LOCK + +namespace butil { + +const int ERRNO_BEGIN = -32768; +const int ERRNO_END = 32768; +static const char* errno_desc[ERRNO_END - ERRNO_BEGIN] = {}; +static pthread_mutex_t modify_desc_mutex = PTHREAD_MUTEX_INITIALIZER; + +const size_t ERROR_BUFSIZE = 64; +__thread char tls_error_buf[ERROR_BUFSIZE]; + +int DescribeCustomizedErrno( + int error_code, const char* error_name, const char* description) { + BAIDU_SCOPED_LOCK(modify_desc_mutex); + if (error_code < ERRNO_BEGIN || error_code >= ERRNO_END) { + // error() is a non-portable GNU extension that should not be used. + fprintf(stderr, "Fail to define %s(%d) which is out of range, abort.", + error_name, error_code); + _exit(1); + } + const char* desc = errno_desc[error_code - ERRNO_BEGIN]; + if (desc) { + if (strcmp(desc, description) == 0) { + fprintf(stderr, "WARNING: Detected shared library loading\n"); + return -1; + } + } else { +#if defined(OS_MACOSX) + const int rc = strerror_r(error_code, tls_error_buf, ERROR_BUFSIZE); + if (rc != EINVAL) +#else + desc = strerror_r(error_code, tls_error_buf, ERROR_BUFSIZE); + if (desc && strncmp(desc, "Unknown error", 13) != 0) +#endif + { + fprintf(stderr, "WARNING: Fail to define %s(%d) which is already defined as `%s'", + error_name, error_code, desc); + } + } + errno_desc[error_code - ERRNO_BEGIN] = description; + return 0; // must +} + +} // namespace butil + +const char* berror(int error_code) { + if (error_code == -1) { + return "General error -1"; + } + if (error_code >= butil::ERRNO_BEGIN && error_code < butil::ERRNO_END) { + const char* s = butil::errno_desc[error_code - butil::ERRNO_BEGIN]; + if (s) { + return s; + } +#if defined(OS_MACOSX) + const int rc = strerror_r(error_code, butil::tls_error_buf, butil::ERROR_BUFSIZE); + if (rc == 0 || rc == ERANGE/*bufsize is not long enough*/) { + return butil::tls_error_buf; + } +#else + s = strerror_r(error_code, butil::tls_error_buf, butil::ERROR_BUFSIZE); + if (s) { + return s; + } +#endif + } + snprintf(butil::tls_error_buf, butil::ERROR_BUFSIZE, + "Unknown error %d", error_code); + return butil::tls_error_buf; +} + +const char* berror() { + return berror(errno); +} diff --git a/src/butil/errno.h b/src/butil/errno.h new file mode 100644 index 0000000..85863d1 --- /dev/null +++ b/src/butil/errno.h @@ -0,0 +1,87 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Fri Sep 10 13:34:25 CST 2010 + +// Add customized errno. + +#ifndef BUTIL_BAIDU_ERRNO_H +#define BUTIL_BAIDU_ERRNO_H + +#ifndef __const__ +// Avoid over-optimizations of TLS variables by GCC>=4.8 +// See: https://github.com/apache/brpc/issues/1693 +#define __const__ __unused__ +#endif + +#include // errno +#include "butil/macros.h" // BAIDU_CONCAT + +//----------------------------------------- +// Use system errno before defining yours ! +//----------------------------------------- +// +// To add new errno, you shall define the errno in header first, either by +// macro or constant, or even in protobuf. +// +// #define ESTOP -114 // C/C++ +// static const int EMYERROR = 30; // C/C++ +// const int EMYERROR2 = -31; // C++ only +// +// Then you can register description of the error by calling +// BAIDU_REGISTER_ERRNO(the_error_number, its_description) in global scope of +// a .cpp or .cc files which will be linked. +// +// BAIDU_REGISTER_ERRNO(ESTOP, "the thread is stopping") +// BAIDU_REGISTER_ERRNO(EMYERROR, "my error") +// +// Once the error is successfully defined: +// berror(error_code) returns the description. +// berror() returns description of last system error code. +// +// %m in printf-alike functions does NOT recognize errors defined by +// BAIDU_REGISTER_ERRNO, you have to explicitly print them by %s. +// +// errno = ESTOP; +// printf("Something got wrong, %m\n"); // NO +// printf("Something got wrong, %s\n", berror()); // YES +// +// When the error number is re-defined, a linking error will be reported: +// +// "redefinition of `class BaiduErrnoHelper<30>'" +// +// Or the program aborts at runtime before entering main(): +// +// "Fail to define EMYERROR(30) which is already defined as `Read-only file system', abort" +// + +namespace butil { +// You should not call this function, use BAIDU_REGISTER_ERRNO instead. +extern int DescribeCustomizedErrno(int, const char*, const char*); +} + +template class BaiduErrnoHelper {}; + +#define BAIDU_REGISTER_ERRNO(error_code, description) \ + const int ALLOW_UNUSED BAIDU_CONCAT(baidu_errno_dummy_, __LINE__) = \ + ::butil::DescribeCustomizedErrno((error_code), #error_code, (description)); \ + template <> class BaiduErrnoHelper<(int)(error_code)> {}; + +const char* berror(int error_code); +const char* berror(); + +#endif //BUTIL_BAIDU_ERRNO_H diff --git a/src/butil/fast_rand.cpp b/src/butil/fast_rand.cpp new file mode 100644 index 0000000..cef4585 --- /dev/null +++ b/src/butil/fast_rand.cpp @@ -0,0 +1,208 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Thu Dec 31 13:35:39 CST 2015 + +#include // numeric_limits +#include +#include "butil/basictypes.h" +#include "butil/macros.h" +#include "butil/time.h" // gettimeofday_us() +#include "butil/fast_rand.h" +#include "butil/numerics/safe_conversions.h" // safe_abs + +namespace butil { + +// This state can be seeded with any value. +typedef uint64_t SplitMix64Seed; + +// A very fast generator passing BigCrush. Due to its 64-bit state, it's +// solely for seeding xorshift128+. +inline uint64_t splitmix64_next(SplitMix64Seed* seed) { + uint64_t z = (*seed += UINT64_C(0x9E3779B97F4A7C15)); + z = (z ^ (z >> 30)) * UINT64_C(0xBF58476D1CE4E5B9); + z = (z ^ (z >> 27)) * UINT64_C(0x94D049BB133111EB); + return z ^ (z >> 31); +} + +// xorshift128+ is the fastest generator passing BigCrush without systematic +// failures +inline uint64_t xorshift128_next(FastRandSeed* seed) { + uint64_t s1 = seed->s[0]; + const uint64_t s0 = seed->s[1]; + seed->s[0] = s0; + s1 ^= s1 << 23; // a + seed->s[1] = s1 ^ s0 ^ (s1 >> 18) ^ (s0 >> 5); // b, c + return seed->s[1] + s0; +} + +// seed xorshift128+ with splitmix64. +void init_fast_rand_seed(FastRandSeed* seed) { + SplitMix64Seed seed4seed = butil::gettimeofday_us(); + seed->s[0] = splitmix64_next(&seed4seed); + seed->s[1] = splitmix64_next(&seed4seed); +} + +inline uint64_t fast_rand_impl(uint64_t range, FastRandSeed* seed) { + // Separating uint64_t values into following intervals: + // [0,range-1][range,range*2-1] ... [uint64_max/range*range,uint64_max] + // If the generated 64-bit random value falls into any interval except the + // last one, the probability of taking any value inside [0, range-1] is + // same. If the value falls into last interval, we retry the process until + // the value falls into other intervals. If min/max are limited to 32-bits, + // the retrying is rare. The amortized retrying count at maximum is 1 when + // range equals 2^32. A corner case is that even if the range is power of + // 2(e.g. min=0 max=65535) in which case the retrying can be avoided, we + // still retry currently. The reason is just to keep the code simpler + // and faster for most cases. + const uint64_t div = std::numeric_limits::max() / range; + uint64_t result; + do { + result = xorshift128_next(seed) / div; + } while (result >= range); + return result; +} + +// Seeds for different threads are stored separately in thread-local storage. +static __thread FastRandSeed _tls_seed = { { 0, 0 } }; + +// True if the seed is (probably) uninitialized. There's definitely false +// positive, but it's OK for us. +inline bool need_init(const FastRandSeed& seed) { + return seed.s[0] == 0 && seed.s[1] == 0; +} + +uint64_t fast_rand() { + if (need_init(_tls_seed)) { + init_fast_rand_seed(&_tls_seed); + } + return xorshift128_next(&_tls_seed); +} + +uint64_t fast_rand(FastRandSeed* seed) { + return xorshift128_next(seed); +} + +uint64_t fast_rand_less_than(uint64_t range) { + if (range == 0) { + return 0; + } + if (need_init(_tls_seed)) { + init_fast_rand_seed(&_tls_seed); + } + return fast_rand_impl(range, &_tls_seed); +} + +int64_t fast_rand_in_64(int64_t min, int64_t max) { + if (need_init(_tls_seed)) { + init_fast_rand_seed(&_tls_seed); + } + if (BAIDU_UNLIKELY(min >= max)) { + if (min == max) { + return min; + } + std::swap(min, max); + } + uint64_t range; + if (min >= 0) { + // Always safe to do subtraction. + range = (uint64_t)(max - min) + 1; + return min + (int64_t)fast_rand_impl(range, &_tls_seed); + } + + uint64_t abs_min = safe_abs(min); + if (max >= 0) { + range = abs_min + (uint64_t)(max) + 1; + } else { + range = abs_min - safe_abs(max) + 1; + } + if (range == 0) { + // max = INT64_MAX, min = INT64_MIN + return (int64_t)xorshift128_next(&_tls_seed); + } + uint64_t r = fast_rand_impl(range, &_tls_seed); + return r >= abs_min ? (int64_t)(r - abs_min) : -((int64_t)(abs_min - r)); +} + +uint64_t fast_rand_in_u64(uint64_t min, uint64_t max) { + if (need_init(_tls_seed)) { + init_fast_rand_seed(&_tls_seed); + } + if (min >= max) { + if (min == max) { + return min; + } + const uint64_t tmp = min; + min = max; + max = tmp; + } + uint64_t range = max - min + 1; + if (range == 0) { + // max = UINT64_MAX, min = UINT64_MIN + return xorshift128_next(&_tls_seed); + } + return min + fast_rand_impl(range, &_tls_seed); +} + +inline double fast_rand_double(FastRandSeed* seed) { + // Copied from rand_util.cc + COMPILE_ASSERT(std::numeric_limits::radix == 2, otherwise_use_scalbn); + static const int kBits = std::numeric_limits::digits; + uint64_t random_bits = xorshift128_next(seed) & ((UINT64_C(1) << kBits) - 1); + double result = ldexp(static_cast(random_bits), -1 * kBits); + return result; +} + +double fast_rand_double() { + if (need_init(_tls_seed)) { + init_fast_rand_seed(&_tls_seed); + } + return fast_rand_double(&_tls_seed); +} + +void fast_rand_bytes(void* output, size_t output_length) { + const size_t n = output_length / 8; + for (size_t i = 0; i < n; ++i) { + static_cast(output)[i] = fast_rand(); + } + const size_t m = output_length - n * 8; + if (m) { + uint8_t* p = static_cast(output) + n * 8; + uint64_t r = fast_rand(); + for (size_t i = 0; i < m; ++i) { + p[i] = (r & 0xFF); + r = (r >> 8); + } + } +} + +std::string fast_rand_printable(size_t length) { + std::string result(length, 0); + const size_t halflen = length/2; + fast_rand_bytes(&result[0], halflen); + for (size_t i = 0; i < halflen; ++i) { + const uint8_t b = result[halflen - 1 - i]; + result[length - 1 - 2*i] = 'A' + (b & 0xF); + result[length - 2 - 2*i] = 'A' + (b >> 4); + } + if (halflen * 2 != length) { + result[0] = 'A' + (fast_rand() % 16); + } + return result; +} + +} // namespace butil diff --git a/src/butil/fast_rand.h b/src/butil/fast_rand.h new file mode 100644 index 0000000..b2ccb4f --- /dev/null +++ b/src/butil/fast_rand.h @@ -0,0 +1,78 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Thu Dec 31 13:35:39 CST 2015 + +#ifndef BUTIL_FAST_RAND_H +#define BUTIL_FAST_RAND_H + +#include +#include +#include + +namespace butil { + +// Generate random values fast without global contentions. +// All functions in this header are thread-safe. + +struct FastRandSeed { + uint64_t s[2]; +}; + +// Initialize the seed. +void init_fast_rand_seed(FastRandSeed* seed); + +// Generate an unsigned 64-bit random number from thread-local or given seed. +// Cost: ~5ns +uint64_t fast_rand(); +uint64_t fast_rand(FastRandSeed*); + +// Generate an unsigned 64-bit random number inside [0, range) from +// thread-local seed. +// Returns 0 when range is 0. +// Cost: ~30ns +// Note that this can be used as an adapter for std::random_shuffle(): +// std::random_shuffle(myvector.begin(), myvector.end(), butil::fast_rand_less_than); +uint64_t fast_rand_less_than(uint64_t range); + +// Generate a 64-bit random number inside [min, max] (inclusive!) +// from thread-local seed. +// NOTE: this function needs to be a template to be overloadable properly. +// Cost: ~30ns +template T fast_rand_in(T min, T max) { + extern int64_t fast_rand_in_64(int64_t min, int64_t max); + extern uint64_t fast_rand_in_u64(uint64_t min, uint64_t max); + if ((T)-1 < 0) { + return fast_rand_in_64((int64_t)min, (int64_t)max); + } else { + return fast_rand_in_u64((uint64_t)min, (uint64_t)max); + } +} + +// Generate a random double in [0, 1) from thread-local seed. +// Cost: ~15ns +double fast_rand_double(); + +// Fills |output_length| bytes of |output| with random data. +void fast_rand_bytes(void *output, size_t output_length); + +// Generate a random printable string of |length| bytes +std::string fast_rand_printable(size_t length); + +} + +#endif // BUTIL_FAST_RAND_H diff --git a/src/butil/fd_guard.h b/src/butil/fd_guard.h new file mode 100644 index 0000000..69a5620 --- /dev/null +++ b/src/butil/fd_guard.h @@ -0,0 +1,79 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Mon. Nov 7 14:47:36 CST 2011 + +#ifndef BUTIL_FD_GUARD_H +#define BUTIL_FD_GUARD_H + +#include // close() + +namespace butil { + +// RAII file descriptor. +// +// Example: +// fd_guard fd1(open(...)); +// if (fd1 < 0) { +// printf("Fail to open\n"); +// return -1; +// } +// if (another-error-happened) { +// printf("Fail to do sth\n"); +// return -1; // *** closing fd1 automatically *** +// } +class fd_guard { +public: + fd_guard() : _fd(-1) {} + explicit fd_guard(int fd) : _fd(fd) {} + + ~fd_guard() { + if (_fd >= 0) { + ::close(_fd); + _fd = -1; + } + } + + // Close current fd and replace with another fd + void reset(int fd) { + if (_fd >= 0) { + ::close(_fd); + _fd = -1; + } + _fd = fd; + } + + // Set internal fd to -1 and return the value before set. + int release() { + const int prev_fd = _fd; + _fd = -1; + return prev_fd; + } + + operator int() const { return _fd; } + +private: + // Copying this makes no sense. + fd_guard(const fd_guard&); + void operator=(const fd_guard&); + + int _fd; +}; + +} // namespace butil + +#endif // BUTIL_FD_GUARD_H diff --git a/src/butil/fd_utility.cpp b/src/butil/fd_utility.cpp new file mode 100644 index 0000000..52410f8 --- /dev/null +++ b/src/butil/fd_utility.cpp @@ -0,0 +1,111 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Mon. Nov 7 14:47:36 CST 2011 + +#include "butil/build_config.h" +#include // fcntl() +#include // IPPROTO_TCP +#include +#include // setsockopt +#include // TCP_NODELAY +#include +#if defined(OS_MACOSX) +#include // TCPS_ESTABLISHED, TCP6S_ESTABLISHED +#endif +#include "butil/logging.h" + +namespace butil { + +bool is_blocking(int fd) { + const int flags = fcntl(fd, F_GETFL, 0); + return flags >= 0 && !(flags & O_NONBLOCK); +} + +int make_non_blocking(int fd) { + const int flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) { + return flags; + } + if (flags & O_NONBLOCK) { + return 0; + } + return fcntl(fd, F_SETFL, flags | O_NONBLOCK); +} + +int make_blocking(int fd) { + const int flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) { + return flags; + } + if (flags & O_NONBLOCK) { + return fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); + } + return 0; +} + +int make_close_on_exec(int fd) { + return fcntl(fd, F_SETFD, FD_CLOEXEC); +} + +int make_no_delay(int sockfd) { + int flag = 1; + return setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(flag)); +} + +int is_connected(int sockfd) { + errno = 0; + int err; + socklen_t errlen = sizeof(err); + if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &errlen) < 0) { + PLOG(FATAL) << "Fail to getsockopt"; + return -1; + } + if (err != 0) { + errno = err; + return -1; + } + +#if defined(OS_LINUX) + struct tcp_info ti{}; + socklen_t len = sizeof(ti); + if(getsockopt(sockfd, SOL_TCP, TCP_INFO, &ti, &len) < 0) { + PLOG(FATAL) << "Fail to getsockopt"; + return -1; + } + if (ti.tcpi_state != TCP_ESTABLISHED) { + errno = ENOTCONN; + return -1; + } +#elif defined(OS_MACOSX) + struct tcp_connection_info ti{}; + socklen_t len = sizeof(ti); + if (getsockopt(sockfd, IPPROTO_TCP, TCP_CONNECTION_INFO, &ti, &len) < 0) { + PLOG(FATAL) << "Fail to getsockopt"; + return -1; + } + if (ti.tcpi_state != TCPS_ESTABLISHED && + ti.tcpi_state != TCP6S_ESTABLISHED) { + errno = ENOTCONN; + return -1; + } +#endif + + return 0; +} + +} // namespace butil diff --git a/src/butil/fd_utility.h b/src/butil/fd_utility.h new file mode 100644 index 0000000..5c920eb --- /dev/null +++ b/src/butil/fd_utility.h @@ -0,0 +1,51 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Mon. Nov 7 14:47:36 CST 2011 + +// Utility functions on file descriptor. + +#ifndef BUTIL_FD_UTILITY_H +#define BUTIL_FD_UTILITY_H + +namespace butil { + +// Returns true when fd is blocking, false otherwise. +bool is_blocking(int fd); + +// Make file descriptor |fd| non-blocking +// Returns 0 on success, -1 otherwise and errno is set (by fcntl) +int make_non_blocking(int fd); + +// Make file descriptor |fd| blocking +// Returns 0 on success, -1 otherwise and errno is set (by fcntl) +int make_blocking(int fd); + +// Make file descriptor |fd| automatically closed during exec() +// Returns 0 on success, -1 when error and errno is set (by fcntl) +int make_close_on_exec(int fd); + +// Disable nagling on file descriptor |socket|. +// Returns 0 on success, -1 when error and errno is set (by setsockopt) +int make_no_delay(int sockfd); + +// Return true if the socket is connected. +int is_connected(int sockfd); + +} // namespace butil + +#endif // BUTIL_FD_UTILITY_H diff --git a/src/butil/file_descriptor_posix.h b/src/butil/file_descriptor_posix.h new file mode 100644 index 0000000..5da58fa --- /dev/null +++ b/src/butil/file_descriptor_posix.h @@ -0,0 +1,50 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_FILE_DESCRIPTOR_POSIX_H_ +#define BUTIL_FILE_DESCRIPTOR_POSIX_H_ + +#include "butil/files/file.h" + +namespace butil { + +// ----------------------------------------------------------------------------- +// We introduct a special structure for file descriptors in order that we are +// able to use template specialisation to special-case their handling. +// +// WARNING: (Chromium only) There are subtleties to consider if serialising +// these objects over IPC. See comments in ipc/ipc_message_utils.h +// above the template specialisation for this structure. +// ----------------------------------------------------------------------------- +struct FileDescriptor { + FileDescriptor() : fd(-1), auto_close(false) {} + + FileDescriptor(int ifd, bool iauto_close) : fd(ifd), auto_close(iauto_close) { + } + + FileDescriptor(File file) : fd(file.TakePlatformFile()), auto_close(true) {} + + bool operator==(const FileDescriptor& other) const { + return (fd == other.fd && auto_close == other.auto_close); + } + + bool operator!=(const FileDescriptor& other) const { + return !operator==(other); + } + + // A comparison operator so that we can use these as keys in a std::map. + bool operator<(const FileDescriptor& other) const { + return other.fd < fd; + } + + int fd; + // If true, this file descriptor should be closed after it has been used. For + // example an IPC system might interpret this flag as indicating that the + // file descriptor it has been given should be closed after use. + bool auto_close; +}; + +} // namespace butil + +#endif // BUTIL_FILE_DESCRIPTOR_POSIX_H_ diff --git a/src/butil/file_util.cc b/src/butil/file_util.cc new file mode 100644 index 0000000..6e47020 --- /dev/null +++ b/src/butil/file_util.cc @@ -0,0 +1,269 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/file_util.h" + +#if defined(OS_WIN) +#include +#endif +#include + +#include +#include + +#include "butil/files/file_enumerator.h" +#include "butil/files/file_path.h" +#include "butil/logging.h" +#include "butil/strings/string_piece.h" +#include "butil/strings/string_util.h" +#include "butil/strings/stringprintf.h" +#include "butil/strings/utf_string_conversions.h" + +namespace butil { + +namespace { + +// The maximum number of 'uniquified' files we will try to create. +// This is used when the filename we're trying to download is already in use, +// so we create a new unique filename by appending " (nnn)" before the +// extension, where 1 <= nnn <= kMaxUniqueFiles. +// Also used by code that cleans up said files. +static const int kMaxUniqueFiles = 100; + +} // namespace + +int64_t ComputeDirectorySize(const FilePath& root_path) { + int64_t running_size = 0; + FileEnumerator file_iter(root_path, true, FileEnumerator::FILES); + while (!file_iter.Next().empty()) + running_size += file_iter.GetInfo().GetSize(); + return running_size; +} + +bool Move(const FilePath& from_path, const FilePath& to_path) { + if (from_path.ReferencesParent() || to_path.ReferencesParent()) + return false; + return internal::MoveUnsafe(from_path, to_path); +} + +bool CopyFile(const FilePath& from_path, const FilePath& to_path) { + if (from_path.ReferencesParent() || to_path.ReferencesParent()) + return false; + return internal::CopyFileUnsafe(from_path, to_path); +} + +bool ContentsEqual(const FilePath& filename1, const FilePath& filename2) { + // We open the file in binary format even if they are text files because + // we are just comparing that bytes are exactly same in both files and not + // doing anything smart with text formatting. + std::ifstream file1(filename1.value().c_str(), + std::ios::in | std::ios::binary); + std::ifstream file2(filename2.value().c_str(), + std::ios::in | std::ios::binary); + + // Even if both files aren't openable (and thus, in some sense, "equal"), + // any unusable file yields a result of "false". + if (!file1.is_open() || !file2.is_open()) + return false; + + const int BUFFER_SIZE = 2056; + char buffer1[BUFFER_SIZE], buffer2[BUFFER_SIZE]; + do { + file1.read(buffer1, BUFFER_SIZE); + file2.read(buffer2, BUFFER_SIZE); + + if ((file1.eof() != file2.eof()) || + (file1.gcount() != file2.gcount()) || + (memcmp(buffer1, buffer2, file1.gcount()))) { + file1.close(); + file2.close(); + return false; + } + } while (!file1.eof() || !file2.eof()); + + file1.close(); + file2.close(); + return true; +} + +bool TextContentsEqual(const FilePath& filename1, const FilePath& filename2) { + std::ifstream file1(filename1.value().c_str(), std::ios::in); + std::ifstream file2(filename2.value().c_str(), std::ios::in); + + // Even if both files aren't openable (and thus, in some sense, "equal"), + // any unusable file yields a result of "false". + if (!file1.is_open() || !file2.is_open()) + return false; + + do { + std::string line1, line2; + getline(file1, line1); + getline(file2, line2); + + // Check for mismatched EOF states, or any error state. + if ((file1.eof() != file2.eof()) || + file1.bad() || file2.bad()) { + return false; + } + + // Trim all '\r' and '\n' characters from the end of the line. + std::string::size_type end1 = line1.find_last_not_of("\r\n"); + if (end1 == std::string::npos) + line1.clear(); + else if (end1 + 1 < line1.length()) + line1.erase(end1 + 1); + + std::string::size_type end2 = line2.find_last_not_of("\r\n"); + if (end2 == std::string::npos) + line2.clear(); + else if (end2 + 1 < line2.length()) + line2.erase(end2 + 1); + + if (line1 != line2) + return false; + } while (!file1.eof() || !file2.eof()); + + return true; +} + +bool ReadFileToString(const FilePath& path, + std::string* contents, + size_t max_size) { + if (contents) + contents->clear(); + if (path.ReferencesParent()) + return false; + FILE* file = OpenFile(path, "rb"); + if (!file) { + return false; + } + + char buf[1 << 16]; + size_t len; + size_t size = 0; + bool read_status = true; + + // Many files supplied in |path| have incorrect size (proc files etc). + // Hence, the file is read sequentially as opposed to a one-shot read. + while ((len = fread(buf, 1, sizeof(buf), file)) > 0) { + if (contents) + contents->append(buf, std::min(len, max_size - size)); + + if ((max_size - size) < len) { + read_status = false; + break; + } + + size += len; + } + read_status = read_status && !ferror(file); + CloseFile(file); + + return read_status; +} + +bool ReadFileToString(const FilePath& path, std::string* contents) { + return ReadFileToString(path, contents, std::numeric_limits::max()); +} + +bool IsDirectoryEmpty(const FilePath& dir_path) { + FileEnumerator files(dir_path, false, + FileEnumerator::FILES | FileEnumerator::DIRECTORIES); + if (files.Next().empty()) + return true; + return false; +} + +FILE* CreateAndOpenTemporaryFile(FilePath* path) { + FilePath directory; + if (!GetTempDir(&directory)) + return NULL; + + return CreateAndOpenTemporaryFileInDir(directory, path); +} + +bool CreateDirectory(const FilePath& full_path) { + return CreateDirectoryAndGetError(full_path, NULL); +} + +bool CreateDirectory(const FilePath& full_path, bool create_parent) { + return CreateDirectoryAndGetError(full_path, NULL, create_parent); +} + +bool CreateDirectoryAndGetError(const FilePath& full_path, + File::Error* error) { + return CreateDirectoryAndGetError(full_path, error, true); +} + +bool GetFileSize(const FilePath& file_path, int64_t* file_size) { + File::Info info; + if (!GetFileInfo(file_path, &info)) + return false; + *file_size = info.size; + return true; +} + +bool TouchFile(const FilePath& path, + const Time& last_accessed, + const Time& last_modified) { + int flags = File::FLAG_OPEN | File::FLAG_WRITE_ATTRIBUTES; + +#if defined(OS_WIN) + // On Windows, FILE_FLAG_BACKUP_SEMANTICS is needed to open a directory. + if (DirectoryExists(path)) + flags |= File::FLAG_BACKUP_SEMANTICS; +#endif // OS_WIN + + File file(path, flags); + if (!file.IsValid()) + return false; + + return file.SetTimes(last_accessed, last_modified); +} + +bool CloseFile(FILE* file) { + if (file == NULL) + return true; + return fclose(file) == 0; +} + +bool TruncateFile(FILE* file) { + if (file == NULL) + return false; + long current_offset = ftell(file); + if (current_offset == -1) + return false; +#if defined(OS_WIN) + int fd = _fileno(file); + if (_chsize(fd, current_offset) != 0) + return false; +#else + int fd = fileno(file); + if (ftruncate(fd, current_offset) != 0) + return false; +#endif + return true; +} + +int GetUniquePathNumber(const FilePath& path, + const FilePath::StringType& suffix) { + bool have_suffix = !suffix.empty(); + if (!PathExists(path) && + (!have_suffix || !PathExists(FilePath(path.value() + suffix)))) { + return 0; + } + + FilePath new_path; + for (int count = 1; count <= kMaxUniqueFiles; ++count) { + new_path = path.InsertBeforeExtensionASCII(StringPrintf(" (%d)", count)); + if (!PathExists(new_path) && + (!have_suffix || !PathExists(FilePath(new_path.value() + suffix)))) { + return count; + } + } + + return -1; +} + +} // namespace butil diff --git a/src/butil/file_util.h b/src/butil/file_util.h new file mode 100644 index 0000000..4eb164b --- /dev/null +++ b/src/butil/file_util.h @@ -0,0 +1,466 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains utility functions for dealing with the local +// filesystem. + +#ifndef BUTIL_FILE_UTIL_H_ +#define BUTIL_FILE_UTIL_H_ + +#include "butil/build_config.h" + +#if defined(OS_WIN) +#include +#elif defined(OS_POSIX) +#include +#include +#endif + +#include + +#include +#include +#include + +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/files/file.h" +#include "butil/files/file_path.h" +#include "butil/memory/scoped_ptr.h" +#include "butil/strings/string16.h" + +#if defined(OS_POSIX) +#include "butil/file_descriptor_posix.h" +#include "butil/logging.h" +#include "butil/posix/eintr_wrapper.h" +#endif + +namespace butil { + +class Time; + +//----------------------------------------------------------------------------- +// Functions that involve filesystem access or modification: + +// Returns an absolute version of a relative path. Returns an empty path on +// error. On POSIX, this function fails if the path does not exist. This +// function can result in I/O so it can be slow. +BUTIL_EXPORT FilePath MakeAbsoluteFilePath(const FilePath& input); + +// Returns the total number of bytes used by all the files under |root_path|. +// If the path does not exist the function returns 0. +// +// This function is implemented using the FileEnumerator class so it is not +// particularly speedy in any platform. +BUTIL_EXPORT int64_t ComputeDirectorySize(const FilePath& root_path); + +// Deletes the given path, whether it's a file or a directory. +// If it's a directory, it's perfectly happy to delete all of the +// directory's contents. Passing true to recursive deletes +// subdirectories and their contents as well. +// Returns true if successful, false otherwise. It is considered successful +// to attempt to delete a file that does not exist. +// +// In posix environment and if |path| is a symbolic link, this deletes only +// the symlink. (even if the symlink points to a non-existent file) +// +// WARNING: USING THIS WITH recursive==true IS EQUIVALENT +// TO "rm -rf", SO USE WITH CAUTION. +BUTIL_EXPORT bool DeleteFile(const FilePath& path, bool recursive); + +#if defined(OS_WIN) +// Schedules to delete the given path, whether it's a file or a directory, until +// the operating system is restarted. +// Note: +// 1) The file/directory to be deleted should exist in a temp folder. +// 2) The directory to be deleted must be empty. +BUTIL_EXPORT bool DeleteFileAfterReboot(const FilePath& path); +#endif + +// Moves the given path, whether it's a file or a directory. +// If a simple rename is not possible, such as in the case where the paths are +// on different volumes, this will attempt to copy and delete. Returns +// true for success. +// This function fails if either path contains traversal components ('..'). +BUTIL_EXPORT bool Move(const FilePath& from_path, const FilePath& to_path); + +// Renames file |from_path| to |to_path|. Both paths must be on the same +// volume, or the function will fail. Destination file will be created +// if it doesn't exist. Prefer this function over Move when dealing with +// temporary files. On Windows it preserves attributes of the target file. +// Returns true on success, leaving *error unchanged. +// Returns false on failure and sets *error appropriately, if it is non-NULL. +BUTIL_EXPORT bool ReplaceFile(const FilePath& from_path, + const FilePath& to_path, + File::Error* error); + +// Copies a single file. Use CopyDirectory to copy directories. +// This function fails if either path contains traversal components ('..'). +// +// This function keeps the metadata on Windows. The read only bit on Windows is +// not kept. +BUTIL_EXPORT bool CopyFile(const FilePath& from_path, const FilePath& to_path); + +// Copies the given path, and optionally all subdirectories and their contents +// as well. +// +// If there are files existing under to_path, always overwrite. Returns true +// if successful, false otherwise. Wildcards on the names are not supported. +// +// This function calls into CopyFile() so the same behavior w.r.t. metadata +// applies. +// +// If you only need to copy a file use CopyFile, it's faster. +BUTIL_EXPORT bool CopyDirectory(const FilePath& from_path, + const FilePath& to_path, + bool recursive); + +// Returns true if the given path exists on the local filesystem, +// false otherwise. +BUTIL_EXPORT bool PathExists(const FilePath& path); + +// Returns true if the given path is writable by the user, false otherwise. +BUTIL_EXPORT bool PathIsWritable(const FilePath& path); + +// Returns true if the given path exists and is a directory, false otherwise. +BUTIL_EXPORT bool DirectoryExists(const FilePath& path); + +// Returns true if the contents of the two files given are equal, false +// otherwise. If either file can't be read, returns false. +BUTIL_EXPORT bool ContentsEqual(const FilePath& filename1, + const FilePath& filename2); + +// Returns true if the contents of the two text files given are equal, false +// otherwise. This routine treats "\r\n" and "\n" as equivalent. +BUTIL_EXPORT bool TextContentsEqual(const FilePath& filename1, + const FilePath& filename2); + +// Reads the file at |path| into |contents| and returns true on success and +// false on error. For security reasons, a |path| containing path traversal +// components ('..') is treated as a read error and |contents| is set to empty. +// In case of I/O error, |contents| holds the data that could be read from the +// file before the error occurred. +// |contents| may be NULL, in which case this function is useful for its side +// effect of priming the disk cache (could be used for unit tests). +BUTIL_EXPORT bool ReadFileToString(const FilePath& path, std::string* contents); + +// Reads the file at |path| into |contents| and returns true on success and +// false on error. For security reasons, a |path| containing path traversal +// components ('..') is treated as a read error and |contents| is set to empty. +// In case of I/O error, |contents| holds the data that could be read from the +// file before the error occurred. When the file size exceeds |max_size|, the +// function returns false with |contents| holding the file truncated to +// |max_size|. +// |contents| may be NULL, in which case this function is useful for its side +// effect of priming the disk cache (could be used for unit tests). +BUTIL_EXPORT bool ReadFileToString(const FilePath& path, + std::string* contents, + size_t max_size); + +#if defined(OS_POSIX) + +// Read exactly |bytes| bytes from file descriptor |fd|, storing the result +// in |buffer|. This function is protected against EINTR and partial reads. +// Returns true iff |bytes| bytes have been successfully read from |fd|. +BUTIL_EXPORT bool ReadFromFD(int fd, char* buffer, size_t bytes); + +// Creates a symbolic link at |symlink| pointing to |target|. Returns +// false on failure. +BUTIL_EXPORT bool CreateSymbolicLink(const FilePath& target, + const FilePath& symlink); + +// Reads the given |symlink| and returns where it points to in |target|. +// Returns false upon failure. +BUTIL_EXPORT bool ReadSymbolicLink(const FilePath& symlink, FilePath* target); + +// Bits and masks of the file permission. +enum FilePermissionBits { + FILE_PERMISSION_MASK = S_IRWXU | S_IRWXG | S_IRWXO, + FILE_PERMISSION_USER_MASK = S_IRWXU, + FILE_PERMISSION_GROUP_MASK = S_IRWXG, + FILE_PERMISSION_OTHERS_MASK = S_IRWXO, + + FILE_PERMISSION_READ_BY_USER = S_IRUSR, + FILE_PERMISSION_WRITE_BY_USER = S_IWUSR, + FILE_PERMISSION_EXECUTE_BY_USER = S_IXUSR, + FILE_PERMISSION_READ_BY_GROUP = S_IRGRP, + FILE_PERMISSION_WRITE_BY_GROUP = S_IWGRP, + FILE_PERMISSION_EXECUTE_BY_GROUP = S_IXGRP, + FILE_PERMISSION_READ_BY_OTHERS = S_IROTH, + FILE_PERMISSION_WRITE_BY_OTHERS = S_IWOTH, + FILE_PERMISSION_EXECUTE_BY_OTHERS = S_IXOTH, +}; + +// Reads the permission of the given |path|, storing the file permission +// bits in |mode|. If |path| is symbolic link, |mode| is the permission of +// a file which the symlink points to. +BUTIL_EXPORT bool GetPosixFilePermissions(const FilePath& path, int* mode); +// Sets the permission of the given |path|. If |path| is symbolic link, sets +// the permission of a file which the symlink points to. +BUTIL_EXPORT bool SetPosixFilePermissions(const FilePath& path, int mode); + +#endif // OS_POSIX + +// Returns true if the given directory is empty +BUTIL_EXPORT bool IsDirectoryEmpty(const FilePath& dir_path); + +// Get the temporary directory provided by the system. +// +// WARNING: In general, you should use CreateTemporaryFile variants below +// instead of this function. Those variants will ensure that the proper +// permissions are set so that other users on the system can't edit them while +// they're open (which can lead to security issues). +BUTIL_EXPORT bool GetTempDir(FilePath* path); + +// Get the home directory. This is more complicated than just getenv("HOME") +// as it knows to fall back on getpwent() etc. +// +// You should not generally call this directly. Instead use DIR_HOME with the +// path service which will use this function but cache the value. +// Path service may also override DIR_HOME. +BUTIL_EXPORT FilePath GetHomeDir(); + +// Creates a temporary file. The full path is placed in |path|, and the +// function returns true if was successful in creating the file. The file will +// be empty and all handles closed after this function returns. +BUTIL_EXPORT bool CreateTemporaryFile(FilePath* path); + +// Same as CreateTemporaryFile but the file is created in |dir|. +BUTIL_EXPORT bool CreateTemporaryFileInDir(const FilePath& dir, + FilePath* temp_file); + +// Create and open a temporary file. File is opened for read/write. +// The full path is placed in |path|. +// Returns a handle to the opened file or NULL if an error occurred. +BUTIL_EXPORT FILE* CreateAndOpenTemporaryFile(FilePath* path); + +// Similar to CreateAndOpenTemporaryFile, but the file is created in |dir|. +BUTIL_EXPORT FILE* CreateAndOpenTemporaryFileInDir(const FilePath& dir, + FilePath* path); + +// Create a new directory. If prefix is provided, the new directory name is in +// the format of prefixyyyy. +// NOTE: prefix is ignored in the POSIX implementation. +// If success, return true and output the full path of the directory created. +BUTIL_EXPORT bool CreateNewTempDirectory(const FilePath::StringType& prefix, + FilePath* new_temp_path); + +// Create a directory within another directory. +// Extra characters will be appended to |prefix| to ensure that the +// new directory does not have the same name as an existing directory. +BUTIL_EXPORT bool CreateTemporaryDirInDir(const FilePath& base_dir, + const FilePath::StringType& prefix, + FilePath* new_dir); + +// Creates a directory, as well as creating any parent directories by default, +// if they don't exist by default. If |create_parents| is false and the parent +// doesn't exists, false would be returned. +// Returns 'true' on successful creation, or if the directory already exists. +// The directory is readable for all the users. +// Returns true on success, leaving *error unchanged. +// Returns false on failure and sets *error appropriately, if it is non-NULL. +BUTIL_EXPORT bool CreateDirectoryAndGetError(const FilePath& full_path, + File::Error* error); +BUTIL_EXPORT bool CreateDirectoryAndGetError(const FilePath& full_path, + File::Error* error, + bool create_parents); + +// Backward-compatible convenience method for the above. +BUTIL_EXPORT bool CreateDirectory(const FilePath& full_path); +BUTIL_EXPORT bool CreateDirectory(const FilePath& full_path, bool create_parents); + + +// Returns the file size. Returns true on success. +BUTIL_EXPORT bool GetFileSize(const FilePath& file_path, int64_t* file_size); + +// Sets |real_path| to |path| with symbolic links and junctions expanded. +// On windows, make sure the path starts with a lettered drive. +// |path| must reference a file. Function will fail if |path| points to +// a directory or to a nonexistent path. On windows, this function will +// fail if |path| is a junction or symlink that points to an empty file, +// or if |real_path| would be longer than MAX_PATH characters. +BUTIL_EXPORT bool NormalizeFilePath(const FilePath& path, FilePath* real_path); + +#if defined(OS_WIN) + +// Given a path in NT native form ("\Device\HarddiskVolumeXX\..."), +// return in |drive_letter_path| the equivalent path that starts with +// a drive letter ("C:\..."). Return false if no such path exists. +BUTIL_EXPORT bool DevicePathToDriveLetterPath(const FilePath& device_path, + FilePath* drive_letter_path); + +// Given an existing file in |path|, set |real_path| to the path +// in native NT format, of the form "\Device\HarddiskVolumeXX\..". +// Returns false if the path can not be found. Empty files cannot +// be resolved with this function. +BUTIL_EXPORT bool NormalizeToNativeFilePath(const FilePath& path, + FilePath* nt_path); +#endif + +// This function will return if the given file is a symlink or not. +BUTIL_EXPORT bool IsLink(const FilePath& file_path); + +// Returns information about the given file path. +BUTIL_EXPORT bool GetFileInfo(const FilePath& file_path, File::Info* info); + +// Sets the time of the last access and the time of the last modification. +BUTIL_EXPORT bool TouchFile(const FilePath& path, + const Time& last_accessed, + const Time& last_modified); + +// Wrapper for fopen-like calls. Returns non-NULL FILE* on success. +BUTIL_EXPORT FILE* OpenFile(const FilePath& filename, const char* mode); + +// Closes file opened by OpenFile. Returns true on success. +BUTIL_EXPORT bool CloseFile(FILE* file); + +// Associates a standard FILE stream with an existing File. Note that this +// functions take ownership of the existing File. +BUTIL_EXPORT FILE* FileToFILE(File file, const char* mode); + +// Truncates an open file to end at the location of the current file pointer. +// This is a cross-platform analog to Windows' SetEndOfFile() function. +BUTIL_EXPORT bool TruncateFile(FILE* file); + +// Reads at most the given number of bytes from the file into the buffer. +// Returns the number of read bytes, or -1 on error. +BUTIL_EXPORT int ReadFile(const FilePath& filename, char* data, int max_size); + +// Writes the given buffer into the file, overwriting any data that was +// previously there. Returns the number of bytes written, or -1 on error. +BUTIL_EXPORT int WriteFile(const FilePath& filename, const char* data, + int size); + +#if defined(OS_POSIX) +// Append the data to |fd|. Does not close |fd| when done. +BUTIL_EXPORT int WriteFileDescriptor(const int fd, const char* data, int size); +#endif + +// Append the given buffer into the file. Returns the number of bytes written, +// or -1 on error. +BUTIL_EXPORT int AppendToFile(const FilePath& filename, + const char* data, int size); + +// Gets the current working directory for the process. +BUTIL_EXPORT bool GetCurrentDirectory(FilePath* path); + +// Sets the current working directory for the process. +BUTIL_EXPORT bool SetCurrentDirectory(const FilePath& path); + +// Attempts to find a number that can be appended to the |path| to make it +// unique. If |path| does not exist, 0 is returned. If it fails to find such +// a number, -1 is returned. If |suffix| is not empty, also checks the +// existence of it with the given suffix. +BUTIL_EXPORT int GetUniquePathNumber(const FilePath& path, + const FilePath::StringType& suffix); + +#if defined(OS_POSIX) +// Test that |path| can only be changed by a given user and members of +// a given set of groups. +// Specifically, test that all parts of |path| under (and including) |base|: +// * Exist. +// * Are owned by a specific user. +// * Are not writable by all users. +// * Are owned by a member of a given set of groups, or are not writable by +// their group. +// * Are not symbolic links. +// This is useful for checking that a config file is administrator-controlled. +// |base| must contain |path|. +BUTIL_EXPORT bool VerifyPathControlledByUser(const butil::FilePath& base, + const butil::FilePath& path, + uid_t owner_uid, + const std::set& group_gids); +#endif // defined(OS_POSIX) + +#if defined(OS_MACOSX) && !defined(OS_IOS) +// Is |path| writable only by a user with administrator privileges? +// This function uses Mac OS conventions. The super user is assumed to have +// uid 0, and the administrator group is assumed to be named "admin". +// Testing that |path|, and every parent directory including the root of +// the filesystem, are owned by the superuser, controlled by the group +// "admin", are not writable by all users, and contain no symbolic links. +// Will return false if |path| does not exist. +BUTIL_EXPORT bool VerifyPathControlledByAdmin(const butil::FilePath& path); +#endif // defined(OS_MACOSX) && !defined(OS_IOS) + +// Returns the maximum length of path component on the volume containing +// the directory |path|, in the number of FilePath::CharType, or -1 on failure. +BUTIL_EXPORT int GetMaximumPathComponentLength(const butil::FilePath& path); + +#if defined(OS_LINUX) +// Broad categories of file systems as returned by statfs() on Linux. +enum FileSystemType { + FILE_SYSTEM_UNKNOWN, // statfs failed. + FILE_SYSTEM_0, // statfs.f_type == 0 means unknown, may indicate AFS. + FILE_SYSTEM_ORDINARY, // on-disk filesystem like ext2 + FILE_SYSTEM_NFS, + FILE_SYSTEM_SMB, + FILE_SYSTEM_CODA, + FILE_SYSTEM_MEMORY, // in-memory file system + FILE_SYSTEM_CGROUP, // cgroup control. + FILE_SYSTEM_OTHER, // any other value. + FILE_SYSTEM_TYPE_COUNT +}; + +// Attempts determine the FileSystemType for |path|. +// Returns false if |path| doesn't exist. +BUTIL_EXPORT bool GetFileSystemType(const FilePath& path, FileSystemType* type); +#endif + +#if defined(OS_POSIX) +// Get a temporary directory for shared memory files. The directory may depend +// on whether the destination is intended for executable files, which in turn +// depends on how /dev/shmem was mounted. As a result, you must supply whether +// you intend to create executable shmem segments so this function can find +// an appropriate location. +BUTIL_EXPORT bool GetShmemTempDir(bool executable, FilePath* path); +#endif + +} // namespace butil + +// ----------------------------------------------------------------------------- + +namespace file_util { + +// Functor for |ScopedFILE| (below). +struct ScopedFILEClose { + inline void operator()(FILE* x) const { + if (x) + fclose(x); + } +}; + +// Automatically closes |FILE*|s. +typedef scoped_ptr ScopedFILE; + +} // namespace file_util + +// Internal -------------------------------------------------------------------- + +namespace butil { +namespace internal { + +// Same as Move but allows paths with traversal components. +// Use only with extreme care. +BUTIL_EXPORT bool MoveUnsafe(const FilePath& from_path, + const FilePath& to_path); + +// Same as CopyFile but allows paths with traversal components. +// Use only with extreme care. +BUTIL_EXPORT bool CopyFileUnsafe(const FilePath& from_path, + const FilePath& to_path); + +#if defined(OS_WIN) +// Copy from_path to to_path recursively and then delete from_path recursively. +// Returns true if all operations succeed. +// This function simulates Move(), but unlike Move() it works across volumes. +// This function is not transactional. +BUTIL_EXPORT bool CopyAndDeleteDirectory(const FilePath& from_path, + const FilePath& to_path); +#endif // defined(OS_WIN) + +} // namespace internal +} // namespace butil + +#endif // BUTIL_FILE_UTIL_H_ diff --git a/src/butil/file_util_linux.cc b/src/butil/file_util_linux.cc new file mode 100644 index 0000000..8ea69fe --- /dev/null +++ b/src/butil/file_util_linux.cc @@ -0,0 +1,96 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/file_util.h" + +#include +#include + +#include "butil/files/file_path.h" + +#ifndef EXT2_SUPER_MAGIC +#define EXT2_SUPER_MAGIC 0xEF53 +#endif +#ifndef MSDOS_SUPER_MAGIC +#define MSDOS_SUPER_MAGIC 0x4d44 /* MD */ +#endif +#ifndef REISERFS_SUPER_MAGIC +#define REISERFS_SUPER_MAGIC 0x52654973 /* used by gcc */ +#endif +#ifndef NFS_SUPER_MAGIC +#define NFS_SUPER_MAGIC 0x6969 +#endif +#ifndef SMB_SUPER_MAGIC +#define SMB_SUPER_MAGIC 0x517B +#endif +#ifndef CODA_SUPER_MAGIC +#define CODA_SUPER_MAGIC 0x73757245 +#endif +#ifndef CGROUP_SUPER_MAGIC +#define CGROUP_SUPER_MAGIC 0x27e0eb +#endif +#ifndef BTRFS_SUPER_MAGIC +#define BTRFS_SUPER_MAGIC 0x9123683E +#endif +#ifndef HUGETLBFS_MAGIC +#define HUGETLBFS_MAGIC 0x958458f6 +#endif +#ifndef RAMFS_MAGIC +#define RAMFS_MAGIC 0x858458f6 +#endif +#ifndef TMPFS_MAGIC +#define TMPFS_MAGIC 0x01021994 +#endif + +namespace butil { + +bool GetFileSystemType(const FilePath& path, FileSystemType* type) { + struct statfs statfs_buf; + if (statfs(path.value().c_str(), &statfs_buf) < 0) { + if (errno == ENOENT) + return false; + *type = FILE_SYSTEM_UNKNOWN; + return true; + } + + // Not all possible |statfs_buf.f_type| values are in linux/magic.h. + // Missing values are copied from the statfs man page. + switch (statfs_buf.f_type) { + case 0: + *type = FILE_SYSTEM_0; + break; + case EXT2_SUPER_MAGIC: // Also ext3 and ext4 + case MSDOS_SUPER_MAGIC: + case REISERFS_SUPER_MAGIC: + case BTRFS_SUPER_MAGIC: + case 0x5346544E: // NTFS + case 0x58465342: // XFS + case 0x3153464A: // JFS + *type = FILE_SYSTEM_ORDINARY; + break; + case NFS_SUPER_MAGIC: + *type = FILE_SYSTEM_NFS; + break; + case SMB_SUPER_MAGIC: + case 0xFF534D42: // CIFS + *type = FILE_SYSTEM_SMB; + break; + case CODA_SUPER_MAGIC: + *type = FILE_SYSTEM_CODA; + break; + case HUGETLBFS_MAGIC: + case RAMFS_MAGIC: + case TMPFS_MAGIC: + *type = FILE_SYSTEM_MEMORY; + break; + case CGROUP_SUPER_MAGIC: + *type = FILE_SYSTEM_CGROUP; + break; + default: + *type = FILE_SYSTEM_OTHER; + } + return true; +} + +} // namespace butil diff --git a/src/butil/file_util_mac.mm b/src/butil/file_util_mac.mm new file mode 100644 index 0000000..25a731b --- /dev/null +++ b/src/butil/file_util_mac.mm @@ -0,0 +1,52 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/file_util.h" + +#import +#include + +#include "butil/basictypes.h" +#include "butil/files/file_path.h" +#include "butil/mac/foundation_util.h" +#include "butil/strings/string_util.h" +#include "butil/threading/thread_restrictions.h" + +namespace butil { +namespace internal { + +bool CopyFileUnsafe(const FilePath& from_path, const FilePath& to_path) { + ThreadRestrictions::AssertIOAllowed(); + return (copyfile(from_path.value().c_str(), + to_path.value().c_str(), NULL, COPYFILE_DATA) == 0); +} + +} // namespace internal + +bool GetTempDir(butil::FilePath* path) { + NSString* tmp = NSTemporaryDirectory(); + if (tmp == nil) + return false; + *path = butil::mac::NSStringToFilePath(tmp); + return true; +} + +FilePath GetHomeDir() { + NSString* tmp = NSHomeDirectory(); + if (tmp != nil) { + FilePath mac_home_dir = butil::mac::NSStringToFilePath(tmp); + if (!mac_home_dir.empty()) + return mac_home_dir; + } + + // Fall back on temp dir if no home directory is defined. + FilePath rv; + if (GetTempDir(&rv)) + return rv; + + // Last resort. + return FilePath("/tmp"); +} + +} // namespace butil diff --git a/src/butil/file_util_posix.cc b/src/butil/file_util_posix.cc new file mode 100644 index 0000000..322e48a --- /dev/null +++ b/src/butil/file_util_posix.cc @@ -0,0 +1,932 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/file_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(OS_MACOSX) +#include +#include "butil/mac/foundation_util.h" +#elif !defined(OS_CHROMEOS) && defined(USE_GLIB) +#include // for g_get_home_dir() +#endif + +#include + +#include "butil/basictypes.h" +#include "butil/files/file_enumerator.h" +#include "butil/files/file_path.h" +#include "butil/files/scoped_file.h" +#include "butil/logging.h" +#include "butil/memory/scoped_ptr.h" +#include "butil/memory/singleton.h" +#include "butil/posix/eintr_wrapper.h" +#include "butil/stl_util.h" +#include "butil/strings/string_util.h" +#include "butil/strings/stringprintf.h" +#include "butil/strings/sys_string_conversions.h" +#include "butil/strings/utf_string_conversions.h" +#include "butil/threading/thread_restrictions.h" +#include "butil/time/time.h" + +#if defined(OS_ANDROID) +#include "butil/android/content_uri_utils.h" +#include "butil/os_compat_android.h" +#endif + +#if !defined(OS_IOS) +#include +#endif + +namespace butil { + +namespace { + +#if defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL) +static int CallStat(const char *path, stat_wrapper_t *sb) { + ThreadRestrictions::AssertIOAllowed(); + return stat(path, sb); +} +static int CallLstat(const char *path, stat_wrapper_t *sb) { + ThreadRestrictions::AssertIOAllowed(); + return lstat(path, sb); +} +#else // defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL) +static int CallStat(const char *path, stat_wrapper_t *sb) { + ThreadRestrictions::AssertIOAllowed(); + return stat64(path, sb); +} +static int CallLstat(const char *path, stat_wrapper_t *sb) { + ThreadRestrictions::AssertIOAllowed(); + return lstat64(path, sb); +} +#endif // !(defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL)) + +// Helper for NormalizeFilePath(), defined below. +bool RealPath(const FilePath& path, FilePath* real_path) { + ThreadRestrictions::AssertIOAllowed(); // For realpath(). + FilePath::CharType buf[PATH_MAX]; + if (!realpath(path.value().c_str(), buf)) + return false; + + *real_path = FilePath(buf); + return true; +} + +// Helper for VerifyPathControlledByUser. +bool VerifySpecificPathControlledByUser(const FilePath& path, + uid_t owner_uid, + const std::set& group_gids) { + stat_wrapper_t stat_info; + if (CallLstat(path.value().c_str(), &stat_info) != 0) { + DPLOG(ERROR) << "Failed to get information on path " + << path.value(); + return false; + } + + if (S_ISLNK(stat_info.st_mode)) { + DLOG(ERROR) << "Path " << path.value() + << " is a symbolic link."; + return false; + } + + if (stat_info.st_uid != owner_uid) { + DLOG(ERROR) << "Path " << path.value() + << " is owned by the wrong user."; + return false; + } + + if ((stat_info.st_mode & S_IWGRP) && + !ContainsKey(group_gids, stat_info.st_gid)) { + DLOG(ERROR) << "Path " << path.value() + << " is writable by an unprivileged group."; + return false; + } + + if (stat_info.st_mode & S_IWOTH) { + DLOG(ERROR) << "Path " << path.value() + << " is writable by any user."; + return false; + } + + return true; +} + +std::string TempFileName() { +#if defined(OS_MACOSX) + return StringPrintf(".%s.XXXXXX", butil::mac::BaseBundleID()); +#endif + +#if defined(GOOGLE_CHROME_BUILD) + return std::string(".com.google.Chrome.XXXXXX"); +#else + return std::string(".org.chromium.Chromium.XXXXXX"); +#endif +} + +// Creates and opens a temporary file in |directory|, returning the +// file descriptor. |path| is set to the temporary file path. +// This function does NOT unlink() the file. +int CreateAndOpenFdForTemporaryFile(FilePath directory, FilePath* path) { + ThreadRestrictions::AssertIOAllowed(); // For call to mkstemp(). + *path = directory.Append(butil::TempFileName()); + const std::string& tmpdir_string = path->value(); + // this should be OK since mkstemp just replaces characters in place + char* buffer = const_cast(tmpdir_string.c_str()); + + return HANDLE_EINTR(mkstemp(buffer)); +} + +#if defined(OS_LINUX) +// Determine if /dev/shm files can be mapped and then mprotect'd PROT_EXEC. +// This depends on the mount options used for /dev/shm, which vary among +// different Linux distributions and possibly local configuration. It also +// depends on details of kernel--ChromeOS uses the noexec option for /dev/shm +// but its kernel allows mprotect with PROT_EXEC anyway. +bool DetermineDevShmExecutable() { + bool result = false; + FilePath path; + + ScopedFD fd(CreateAndOpenFdForTemporaryFile(FilePath("/dev/shm"), &path)); + if (fd.is_valid()) { + DeleteFile(path, false); + long sysconf_result = sysconf(_SC_PAGESIZE); + CHECK_GE(sysconf_result, 0); + size_t pagesize = static_cast(sysconf_result); + CHECK_GE(sizeof(pagesize), sizeof(sysconf_result)); + void* mapping = mmap(NULL, pagesize, PROT_READ, MAP_SHARED, fd.get(), 0); + if (mapping != MAP_FAILED) { + if (mprotect(mapping, pagesize, PROT_READ | PROT_EXEC) == 0) + result = true; + munmap(mapping, pagesize); + } + } + return result; +} +#endif // defined(OS_LINUX) + +} // namespace + +FilePath MakeAbsoluteFilePath(const FilePath& input) { + ThreadRestrictions::AssertIOAllowed(); + char full_path[PATH_MAX]; + if (realpath(input.value().c_str(), full_path) == NULL) + return FilePath(); + return FilePath(full_path); +} + +// TODO(erikkay): The Windows version of this accepts paths like "foo/bar/*" +// which works both with and without the recursive flag. I'm not sure we need +// that functionality. If not, remove from file_util_win.cc, otherwise add it +// here. +bool DeleteFile(const FilePath& path, bool recursive) { + ThreadRestrictions::AssertIOAllowed(); + const char* path_str = path.value().c_str(); + stat_wrapper_t file_info; + int test = CallLstat(path_str, &file_info); + if (test != 0) { + // The Windows version defines this condition as success. + bool ret = (errno == ENOENT || errno == ENOTDIR); + return ret; + } + if (!S_ISDIR(file_info.st_mode)) + return (unlink(path_str) == 0); + if (!recursive) + return (rmdir(path_str) == 0); + + bool success = true; + std::stack directories; + directories.push(path.value()); + FileEnumerator traversal(path, true, + FileEnumerator::FILES | FileEnumerator::DIRECTORIES | + FileEnumerator::SHOW_SYM_LINKS); + for (FilePath current = traversal.Next(); success && !current.empty(); + current = traversal.Next()) { + if (traversal.GetInfo().IsDirectory()) + directories.push(current.value()); + else + success = (unlink(current.value().c_str()) == 0); + } + + while (success && !directories.empty()) { + FilePath dir = FilePath(directories.top()); + directories.pop(); + success = (rmdir(dir.value().c_str()) == 0); + } + return success; +} + +bool ReplaceFile(const FilePath& from_path, + const FilePath& to_path, + File::Error* error) { + ThreadRestrictions::AssertIOAllowed(); + if (rename(from_path.value().c_str(), to_path.value().c_str()) == 0) + return true; + if (error) + *error = File::OSErrorToFileError(errno); + return false; +} + +bool CopyDirectory(const FilePath& from_path, + const FilePath& to_path, + bool recursive) { + ThreadRestrictions::AssertIOAllowed(); + // Some old callers of CopyDirectory want it to support wildcards. + // After some discussion, we decided to fix those callers. + // Break loudly here if anyone tries to do this. + DCHECK(to_path.value().find('*') == std::string::npos); + DCHECK(from_path.value().find('*') == std::string::npos); + + if (from_path.value().size() >= PATH_MAX) { + return false; + } + + // This function does not properly handle destinations within the source + FilePath real_to_path = to_path; + if (PathExists(real_to_path)) { + real_to_path = MakeAbsoluteFilePath(real_to_path); + if (real_to_path.empty()) + return false; + } else { + real_to_path = MakeAbsoluteFilePath(real_to_path.DirName()); + if (real_to_path.empty()) + return false; + } + FilePath real_from_path = MakeAbsoluteFilePath(from_path); + if (real_from_path.empty()) + return false; + if (real_to_path.value().size() >= real_from_path.value().size() && + real_to_path.value().compare(0, real_from_path.value().size(), + real_from_path.value()) == 0) { + return false; + } + + int traverse_type = FileEnumerator::FILES | FileEnumerator::SHOW_SYM_LINKS; + if (recursive) + traverse_type |= FileEnumerator::DIRECTORIES; + FileEnumerator traversal(from_path, recursive, traverse_type); + + // We have to mimic windows behavior here. |to_path| may not exist yet, + // start the loop with |to_path|. + struct stat from_stat; + FilePath current = from_path; + if (stat(from_path.value().c_str(), &from_stat) < 0) { + DLOG(ERROR) << "CopyDirectory() couldn't stat source directory: " + << from_path.value() << " errno = " << errno; + return false; + } + struct stat to_path_stat; + FilePath from_path_base = from_path; + if (recursive && stat(to_path.value().c_str(), &to_path_stat) == 0 && + S_ISDIR(to_path_stat.st_mode)) { + // If the destination already exists and is a directory, then the + // top level of source needs to be copied. + from_path_base = from_path.DirName(); + } + + // The Windows version of this function assumes that non-recursive calls + // will always have a directory for from_path. + // TODO(maruel): This is not necessary anymore. + DCHECK(recursive || S_ISDIR(from_stat.st_mode)); + + bool success = true; + while (success && !current.empty()) { + // current is the source path, including from_path, so append + // the suffix after from_path to to_path to create the target_path. + FilePath target_path(to_path); + if (from_path_base != current) { + if (!from_path_base.AppendRelativePath(current, &target_path)) { + success = false; + break; + } + } + + if (S_ISDIR(from_stat.st_mode)) { + if (mkdir(target_path.value().c_str(), from_stat.st_mode & 01777) != 0 && + errno != EEXIST) { + DLOG(ERROR) << "CopyDirectory() couldn't create directory: " + << target_path.value() << " errno = " << errno; + success = false; + } + } else if (S_ISREG(from_stat.st_mode)) { + if (!CopyFile(current, target_path)) { + DLOG(ERROR) << "CopyDirectory() couldn't create file: " + << target_path.value(); + success = false; + } + } else { + DLOG(WARNING) << "CopyDirectory() skipping non-regular file: " + << current.value(); + } + + current = traversal.Next(); + if (!current.empty()) + from_stat = traversal.GetInfo().stat(); + } + + return success; +} + +bool PathExists(const FilePath& path) { + ThreadRestrictions::AssertIOAllowed(); +#if defined(OS_ANDROID) + if (path.IsContentUri()) { + return ContentUriExists(path); + } +#endif + return access(path.value().c_str(), F_OK) == 0; +} + +bool PathIsWritable(const FilePath& path) { + ThreadRestrictions::AssertIOAllowed(); + return access(path.value().c_str(), W_OK) == 0; +} + +bool DirectoryExists(const FilePath& path) { + ThreadRestrictions::AssertIOAllowed(); + stat_wrapper_t file_info; + if (CallStat(path.value().c_str(), &file_info) == 0) + return S_ISDIR(file_info.st_mode); + return false; +} + +bool ReadFromFD(int fd, char* buffer, size_t bytes) { + size_t total_read = 0; + while (total_read < bytes) { + ssize_t bytes_read = + HANDLE_EINTR(read(fd, buffer + total_read, bytes - total_read)); + if (bytes_read <= 0) + break; + total_read += bytes_read; + } + return total_read == bytes; +} + +bool CreateSymbolicLink(const FilePath& target_path, + const FilePath& symlink_path) { + DCHECK(!symlink_path.empty()); + DCHECK(!target_path.empty()); + return ::symlink(target_path.value().c_str(), + symlink_path.value().c_str()) != -1; +} + +bool ReadSymbolicLink(const FilePath& symlink_path, FilePath* target_path) { + DCHECK(!symlink_path.empty()); + DCHECK(target_path); + char buf[PATH_MAX]; + ssize_t count = ::readlink(symlink_path.value().c_str(), buf, arraysize(buf)); + + if (count <= 0) { + target_path->clear(); + return false; + } + + *target_path = FilePath(FilePath::StringType(buf, count)); + return true; +} + +bool GetPosixFilePermissions(const FilePath& path, int* mode) { + ThreadRestrictions::AssertIOAllowed(); + DCHECK(mode); + + stat_wrapper_t file_info; + // Uses stat(), because on symbolic link, lstat() does not return valid + // permission bits in st_mode + if (CallStat(path.value().c_str(), &file_info) != 0) + return false; + + *mode = file_info.st_mode & FILE_PERMISSION_MASK; + return true; +} + +bool SetPosixFilePermissions(const FilePath& path, + int mode) { + ThreadRestrictions::AssertIOAllowed(); + DCHECK((mode & ~FILE_PERMISSION_MASK) == 0); + + // Calls stat() so that we can preserve the higher bits like S_ISGID. + stat_wrapper_t stat_buf; + if (CallStat(path.value().c_str(), &stat_buf) != 0) + return false; + + // Clears the existing permission bits, and adds the new ones. + mode_t updated_mode_bits = stat_buf.st_mode & ~FILE_PERMISSION_MASK; + updated_mode_bits |= mode & FILE_PERMISSION_MASK; + + if (HANDLE_EINTR(chmod(path.value().c_str(), updated_mode_bits)) != 0) + return false; + + return true; +} + +#if !defined(OS_MACOSX) +// This is implemented in file_util_mac.mm for Mac. +bool GetTempDir(FilePath* path) { + const char* tmp = getenv("TMPDIR"); + if (tmp) { + *path = FilePath(tmp); + } else { + *path = FilePath("/tmp"); + } + return true; +} +#endif // !defined(OS_MACOSX) + +#if !defined(OS_MACOSX) // Mac implementation is in file_util_mac.mm. +FilePath GetHomeDir() { +#if defined(OS_CHROMEOS) + if (SysInfo::IsRunningOnChromeOS()) { + // On Chrome OS chrome::DIR_USER_DATA is overridden with a primary user + // homedir once it becomes available. Return / as the safe option. + return FilePath("/"); + } +#endif + + const char* home_dir = getenv("HOME"); + if (home_dir && home_dir[0]) + return FilePath(home_dir); + +#if defined(OS_ANDROID) + DLOG(WARNING) << "OS_ANDROID: Home directory lookup not yet implemented."; +#elif defined(USE_GLIB) && !defined(OS_CHROMEOS) + // g_get_home_dir calls getpwent, which can fall through to LDAP calls so + // this may do I/O. However, it should be rare that $HOME is not defined and + // this is typically called from the path service which has no threading + // restrictions. The path service will cache the result which limits the + // badness of blocking on I/O. As a result, we don't have a thread + // restriction here. + home_dir = g_get_home_dir(); + if (home_dir && home_dir[0]) + return FilePath(home_dir); +#endif + + FilePath rv; + if (GetTempDir(&rv)) + return rv; + + // Last resort. + return FilePath("/tmp"); +} +#endif // !defined(OS_MACOSX) + +bool CreateTemporaryFile(FilePath* path) { + ThreadRestrictions::AssertIOAllowed(); // For call to close(). + FilePath directory; + if (!GetTempDir(&directory)) + return false; + int fd = CreateAndOpenFdForTemporaryFile(directory, path); + if (fd < 0) + return false; + close(fd); + return true; +} + +FILE* CreateAndOpenTemporaryFileInDir(const FilePath& dir, FilePath* path) { + int fd = CreateAndOpenFdForTemporaryFile(dir, path); + if (fd < 0) + return NULL; + + FILE* file = fdopen(fd, "a+"); + if (!file) + close(fd); + return file; +} + +bool CreateTemporaryFileInDir(const FilePath& dir, FilePath* temp_file) { + ThreadRestrictions::AssertIOAllowed(); // For call to close(). + int fd = CreateAndOpenFdForTemporaryFile(dir, temp_file); + return ((fd >= 0) && !IGNORE_EINTR(close(fd))); +} + +static bool CreateTemporaryDirInDirImpl(const FilePath& base_dir, + const FilePath::StringType& name_tmpl, + FilePath* new_dir) { + ThreadRestrictions::AssertIOAllowed(); // For call to mkdtemp(). + DCHECK(name_tmpl.find("XXXXXX") != FilePath::StringType::npos) + << "Directory name template must contain \"XXXXXX\"."; + + FilePath sub_dir = base_dir.Append(name_tmpl); + std::string sub_dir_string = sub_dir.value(); + + // this should be OK since mkdtemp just replaces characters in place + char* buffer = const_cast(sub_dir_string.c_str()); + char* dtemp = mkdtemp(buffer); + if (!dtemp) { + DPLOG(ERROR) << "mkdtemp"; + return false; + } + *new_dir = FilePath(dtemp); + return true; +} + +bool CreateTemporaryDirInDir(const FilePath& base_dir, + const FilePath::StringType& prefix, + FilePath* new_dir) { + FilePath::StringType mkdtemp_template = prefix; + mkdtemp_template.append(FILE_PATH_LITERAL("XXXXXX")); + return CreateTemporaryDirInDirImpl(base_dir, mkdtemp_template, new_dir); +} + +bool CreateNewTempDirectory(const FilePath::StringType& prefix, + FilePath* new_temp_path) { + FilePath tmpdir; + if (!GetTempDir(&tmpdir)) + return false; + + return CreateTemporaryDirInDirImpl(tmpdir, TempFileName(), new_temp_path); +} + +bool CreateDirectoryAndGetError(const FilePath& full_path, + File::Error* error, + bool create_parents) { + ThreadRestrictions::AssertIOAllowed(); // For call to mkdir(). + if (!create_parents) { + if (DirectoryExists(full_path)) { + return true; + } + if (mkdir(full_path.value().c_str(), 0755/*others can read the dir*/) == 0) { + return true; + } + const int saved_errno = errno; + if (DirectoryExists(full_path)) { + return true; + } + if (error) { + *error = File::OSErrorToFileError(saved_errno); + } + return false; + } + std::vector subpaths; + + // Collect a list of all parent directories. + FilePath last_path = full_path; + subpaths.push_back(full_path); + for (FilePath path = full_path.DirName(); + path.value() != last_path.value(); path = path.DirName()) { + subpaths.push_back(path); + last_path = path; + } + + // Iterate through the parents and create the missing ones. + for (std::vector::reverse_iterator i = subpaths.rbegin(); + i != subpaths.rend(); ++i) { + if (DirectoryExists(*i)) + continue; + // NOTE(gejun): permission bits of dir are different from file's + // -The write bit allows the affected user to create, rename, or delete + // files within the directory, and modify the directory's attributes + // -The read bit allows the affected user to list the files within the + // directory + // -The execute bit allows the affected user to enter the directory, and + // access files and directories inside + // -The sticky bit states that files and directories within that directory + // may only be deleted or renamed by their owner (or root) + if (mkdir(i->value().c_str(), 0755/*others can read the dir*/) == 0) + continue; + // Mkdir failed, but it might have failed with EEXIST, or some other error + // due to the directory appearing out of thin air. This can occur if + // two processes are trying to create the same file system tree at the same + // time. Check to see if it exists and make sure it is a directory. + int saved_errno = errno; + if (!DirectoryExists(*i)) { + if (error) + *error = File::OSErrorToFileError(saved_errno); + return false; + } + } + return true; +} + +bool NormalizeFilePath(const FilePath& path, FilePath* normalized_path) { + FilePath real_path_result; + if (!RealPath(path, &real_path_result)) + return false; + + // To be consistant with windows, fail if |real_path_result| is a + // directory. + stat_wrapper_t file_info; + if (CallStat(real_path_result.value().c_str(), &file_info) != 0 || + S_ISDIR(file_info.st_mode)) + return false; + + *normalized_path = real_path_result; + return true; +} + +// TODO(rkc): Refactor GetFileInfo and FileEnumerator to handle symlinks +// correctly. http://code.google.com/p/chromium-os/issues/detail?id=15948 +bool IsLink(const FilePath& file_path) { + stat_wrapper_t st; + // If we can't lstat the file, it's safe to assume that the file won't at + // least be a 'followable' link. + if (CallLstat(file_path.value().c_str(), &st) != 0) + return false; + + if (S_ISLNK(st.st_mode)) + return true; + else + return false; +} + +bool GetFileInfo(const FilePath& file_path, File::Info* results) { + stat_wrapper_t file_info; +#if defined(OS_ANDROID) + if (file_path.IsContentUri()) { + File file = OpenContentUriForRead(file_path); + if (!file.IsValid()) + return false; + return file.GetInfo(results); + } else { +#endif // defined(OS_ANDROID) + if (CallStat(file_path.value().c_str(), &file_info) != 0) + return false; +#if defined(OS_ANDROID) + } +#endif // defined(OS_ANDROID) + + results->FromStat(file_info); + return true; +} + +FILE* OpenFile(const FilePath& filename, const char* mode) { + ThreadRestrictions::AssertIOAllowed(); + FILE* result = NULL; + do { + result = fopen(filename.value().c_str(), mode); + } while (!result && errno == EINTR); + return result; +} + +// NaCl doesn't implement system calls to open files directly. +#if !defined(OS_NACL) +FILE* FileToFILE(File file, const char* mode) { + FILE* stream = fdopen(file.GetPlatformFile(), mode); + if (stream) + file.TakePlatformFile(); + return stream; +} +#endif // !defined(OS_NACL) + +int ReadFile(const FilePath& filename, char* data, int max_size) { + ThreadRestrictions::AssertIOAllowed(); + int fd = HANDLE_EINTR(open(filename.value().c_str(), O_RDONLY)); + if (fd < 0) + return -1; + + ssize_t bytes_read = HANDLE_EINTR(read(fd, data, max_size)); + if (IGNORE_EINTR(close(fd)) < 0) + return -1; + return bytes_read; +} + +int WriteFile(const FilePath& filename, const char* data, int size) { + ThreadRestrictions::AssertIOAllowed(); + int fd = HANDLE_EINTR(creat(filename.value().c_str(), 0644)); + if (fd < 0) + return -1; + + int bytes_written = WriteFileDescriptor(fd, data, size); + if (IGNORE_EINTR(close(fd)) < 0) + return -1; + return bytes_written; +} + +int WriteFileDescriptor(const int fd, const char* data, int size) { + // Allow for partial writes. + ssize_t bytes_written_total = 0; + for (ssize_t bytes_written_partial = 0; bytes_written_total < size; + bytes_written_total += bytes_written_partial) { + bytes_written_partial = + HANDLE_EINTR(write(fd, data + bytes_written_total, + size - bytes_written_total)); + if (bytes_written_partial < 0) + return -1; + } + + return bytes_written_total; +} + +int AppendToFile(const FilePath& filename, const char* data, int size) { + ThreadRestrictions::AssertIOAllowed(); + int fd = HANDLE_EINTR(open(filename.value().c_str(), O_WRONLY | O_APPEND)); + if (fd < 0) + return -1; + + int bytes_written = WriteFileDescriptor(fd, data, size); + if (IGNORE_EINTR(close(fd)) < 0) + return -1; + return bytes_written; +} + +// Gets the current working directory for the process. +bool GetCurrentDirectory(FilePath* dir) { + // getcwd can return ENOENT, which implies it checks against the disk. + ThreadRestrictions::AssertIOAllowed(); + + char system_buffer[PATH_MAX] = ""; + if (!getcwd(system_buffer, sizeof(system_buffer))) { + NOTREACHED(); + return false; + } + *dir = FilePath(system_buffer); + return true; +} + +// Sets the current working directory for the process. +bool SetCurrentDirectory(const FilePath& path) { + ThreadRestrictions::AssertIOAllowed(); + int ret = chdir(path.value().c_str()); + return !ret; +} + +bool VerifyPathControlledByUser(const FilePath& base, + const FilePath& path, + uid_t owner_uid, + const std::set& group_gids) { + if (base != path && !base.IsParent(path)) { + DLOG(ERROR) << "|base| must be a subdirectory of |path|. base = \"" + << base.value() << "\", path = \"" << path.value() << "\""; + return false; + } + + std::vector base_components; + std::vector path_components; + + base.GetComponents(&base_components); + path.GetComponents(&path_components); + + std::vector::const_iterator ib, ip; + for (ib = base_components.begin(), ip = path_components.begin(); + ib != base_components.end(); ++ib, ++ip) { + // |base| must be a subpath of |path|, so all components should match. + // If these CHECKs fail, look at the test that base is a parent of + // path at the top of this function. + DCHECK(ip != path_components.end()); + DCHECK(*ip == *ib); + } + + FilePath current_path = base; + if (!VerifySpecificPathControlledByUser(current_path, owner_uid, group_gids)) + return false; + + for (; ip != path_components.end(); ++ip) { + current_path = current_path.Append(*ip); + if (!VerifySpecificPathControlledByUser( + current_path, owner_uid, group_gids)) + return false; + } + return true; +} + +#if defined(OS_MACOSX) && !defined(OS_IOS) +bool VerifyPathControlledByAdmin(const FilePath& path) { + const unsigned kRootUid = 0; + const FilePath kFileSystemRoot("/"); + + // The name of the administrator group on mac os. + const char* const kAdminGroupNames[] = { + "admin", + "wheel" + }; + + // Reading the groups database may touch the file system. + ThreadRestrictions::AssertIOAllowed(); + + std::set allowed_group_ids; + for (int i = 0, ie = arraysize(kAdminGroupNames); i < ie; ++i) { + struct group *group_record = getgrnam(kAdminGroupNames[i]); + if (!group_record) { + DPLOG(ERROR) << "Could not get the group ID of group \"" + << kAdminGroupNames[i] << "\"."; + continue; + } + + allowed_group_ids.insert(group_record->gr_gid); + } + + return VerifyPathControlledByUser( + kFileSystemRoot, path, kRootUid, allowed_group_ids); +} +#endif // defined(OS_MACOSX) && !defined(OS_IOS) + +int GetMaximumPathComponentLength(const FilePath& path) { + ThreadRestrictions::AssertIOAllowed(); + return pathconf(path.value().c_str(), _PC_NAME_MAX); +} + +#if !defined(OS_ANDROID) +// This is implemented in file_util_android.cc for that platform. +bool GetShmemTempDir(bool executable, FilePath* path) { +#if defined(OS_LINUX) + bool use_dev_shm = true; + if (executable) { + static const bool s_dev_shm_executable = DetermineDevShmExecutable(); + use_dev_shm = s_dev_shm_executable; + } + if (use_dev_shm) { + *path = FilePath("/dev/shm"); + return true; + } +#endif + return GetTempDir(path); +} +#endif // !defined(OS_ANDROID) + +// ----------------------------------------------------------------------------- + +namespace internal { + +bool MoveUnsafe(const FilePath& from_path, const FilePath& to_path) { + ThreadRestrictions::AssertIOAllowed(); + // Windows compatibility: if to_path exists, from_path and to_path + // must be the same type, either both files, or both directories. + stat_wrapper_t to_file_info; + if (CallStat(to_path.value().c_str(), &to_file_info) == 0) { + stat_wrapper_t from_file_info; + if (CallStat(from_path.value().c_str(), &from_file_info) == 0) { + if (S_ISDIR(to_file_info.st_mode) != S_ISDIR(from_file_info.st_mode)) + return false; + } else { + return false; + } + } + + if (rename(from_path.value().c_str(), to_path.value().c_str()) == 0) + return true; + + if (!CopyDirectory(from_path, to_path, true)) + return false; + + DeleteFile(from_path, true); + return true; +} + +#if !defined(OS_MACOSX) +// Mac has its own implementation, this is for all other Posix systems. +bool CopyFileUnsafe(const FilePath& from_path, const FilePath& to_path) { + ThreadRestrictions::AssertIOAllowed(); + int infile = HANDLE_EINTR(open(from_path.value().c_str(), O_RDONLY)); + if (infile < 0) + return false; + + int outfile = HANDLE_EINTR(creat(to_path.value().c_str(), 0666)); + if (outfile < 0) { + close(infile); + return false; + } + + const size_t kBufferSize = 32768; + std::vector buffer(kBufferSize); + bool result = true; + + while (result) { + ssize_t bytes_read = HANDLE_EINTR(read(infile, &buffer[0], buffer.size())); + if (bytes_read < 0) { + result = false; + break; + } + if (bytes_read == 0) + break; + // Allow for partial writes + ssize_t bytes_written_per_read = 0; + do { + ssize_t bytes_written_partial = HANDLE_EINTR(write( + outfile, + &buffer[bytes_written_per_read], + bytes_read - bytes_written_per_read)); + if (bytes_written_partial < 0) { + result = false; + break; + } + bytes_written_per_read += bytes_written_partial; + } while (bytes_written_per_read < bytes_read); + } + + if (IGNORE_EINTR(close(infile)) < 0) + result = false; + if (IGNORE_EINTR(close(outfile)) < 0) + result = false; + + return result; +} +#endif // !defined(OS_MACOSX) + +} // namespace internal +} // namespace butil diff --git a/src/butil/files/dir_reader_fallback.h b/src/butil/files/dir_reader_fallback.h new file mode 100644 index 0000000..53eb9f2 --- /dev/null +++ b/src/butil/files/dir_reader_fallback.h @@ -0,0 +1,35 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_FILES_DIR_READER_FALLBACK_H_ +#define BUTIL_FILES_DIR_READER_FALLBACK_H_ + +namespace butil { + +class DirReaderFallback { + public: + // Open a directory. If |IsValid| is true, then |Next| can be called to start + // the iteration at the beginning of the directory. + explicit DirReaderFallback(const char* directory_path) {} + + // After construction, IsValid returns true iff the directory was + // successfully opened. + bool IsValid() const { return false; } + + // Move to the next entry returning false if the iteration is complete. + bool Next() { return false; } + + // Return the name of the current directory entry. + const char* name() { return 0;} + + // Return the file descriptor which is being used. + int fd() const { return -1; } + + // Returns true if this is a no-op fallback class (for testing). + static bool IsFallback() { return true; } +}; + +} // namespace butil + +#endif // BUTIL_FILES_DIR_READER_FALLBACK_H_ diff --git a/src/butil/files/dir_reader_linux.h b/src/butil/files/dir_reader_linux.h new file mode 100644 index 0000000..c701546 --- /dev/null +++ b/src/butil/files/dir_reader_linux.h @@ -0,0 +1,98 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_FILES_DIR_READER_LINUX_H_ +#define BUTIL_FILES_DIR_READER_LINUX_H_ + +#include +#include +#include +#include +#include + +#include "butil/logging.h" +#include "butil/posix/eintr_wrapper.h" + +// See the comments in dir_reader_posix.h about this. + +namespace butil { + +struct linux_dirent { + uint64_t d_ino; + int64_t d_off; + unsigned short d_reclen; + unsigned char d_type; + char d_name[0]; +}; + +class DirReaderLinux { + public: + explicit DirReaderLinux(const char* directory_path) + : fd_(open(directory_path, O_RDONLY | O_DIRECTORY)), + offset_(0), + size_(0) { + memset(buf_, 0, sizeof(buf_)); + } + + ~DirReaderLinux() { + if (fd_ >= 0) { + if (IGNORE_EINTR(close(fd_))) + RAW_LOG(ERROR, "Failed to close directory handle"); + } + } + + bool IsValid() const { + return fd_ >= 0; + } + + // Move to the next entry returning false if the iteration is complete. + bool Next() { + if (size_) { + linux_dirent* dirent = reinterpret_cast(buf_ + offset_); + offset_ += dirent->d_reclen; + } + + if (offset_ != size_) + return true; + + const int r = syscall(__NR_getdents64, fd_, buf_, sizeof(buf_)); + if (r == 0) + return false; + if (r == -1) { + DPLOG(FATAL) << "getdents64 returned an error: " << errno; + return false; + } + size_ = r; + offset_ = 0; + return true; + } + + const char* name() const { + if (!size_) + return NULL; + + const linux_dirent* dirent = + reinterpret_cast(&buf_[offset_]); + return dirent->d_name; + } + + int fd() const { + return fd_; + } + + static bool IsFallback() { + return false; + } + + private: + const int fd_; + unsigned char buf_[512]; + size_t offset_, size_; + + DISALLOW_COPY_AND_ASSIGN(DirReaderLinux); +}; + +} // namespace butil + +#endif // BUTIL_FILES_DIR_READER_LINUX_H_ diff --git a/src/butil/files/dir_reader_posix.h b/src/butil/files/dir_reader_posix.h new file mode 100644 index 0000000..9ed08ad --- /dev/null +++ b/src/butil/files/dir_reader_posix.h @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_FILES_DIR_READER_POSIX_H_ +#define BUTIL_FILES_DIR_READER_POSIX_H_ + +#include "butil/build_config.h" + +// This header provides a class, DirReaderPosix, which allows one to open and +// read from directories without allocating memory. For the interface, see +// the generic fallback in dir_reader_fallback.h. + +// Mac note: OS X has getdirentries, but it only works if we restrict Chrome to +// 32-bit inodes. There is a getdirentries64 syscall in 10.6, but it's not +// wrapped and the direct syscall interface is unstable. Using an unstable API +// seems worse than falling back to enumerating all file descriptors so we will +// probably never implement this on the Mac. + +#if defined(OS_LINUX) +#include "butil/files/dir_reader_linux.h" +#elif defined(__APPLE__) +#include "butil/files/dir_reader_unix.h" +#else +#include "butil/files/dir_reader_fallback.h" +#endif + +namespace butil { + +#if defined(OS_LINUX) +typedef DirReaderLinux DirReaderPosix; +#elif defined(__APPLE__) +typedef DirReaderUnix DirReaderPosix; +#else +typedef DirReaderFallback DirReaderPosix; +#endif + +} // namespace butil + +#endif // BUTIL_FILES_DIR_READER_POSIX_H_ diff --git a/src/butil/files/dir_reader_unix.h b/src/butil/files/dir_reader_unix.h new file mode 100644 index 0000000..3c25f79 --- /dev/null +++ b/src/butil/files/dir_reader_unix.h @@ -0,0 +1,90 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BUTIL_FILES_DIR_READER_UNIX_H_ +#define BUTIL_FILES_DIR_READER_UNIX_H_ + +#include +#include +#include +#include +#include +#include + +#include "butil/logging.h" +#include "butil/posix/eintr_wrapper.h" + +// See the comments in dir_reader_posix.h about this. + +namespace butil { + +class DirReaderUnix { + public: + explicit DirReaderUnix(const char* directory_path) + : fd_(open(directory_path, O_RDONLY | O_DIRECTORY)), + dir_(NULL),current_(NULL) { + dir_ = fdopendir(fd_); + } + + ~DirReaderUnix() { + if (NULL != dir_) { + if (IGNORE_EINTR(closedir(dir_)) == 0) { // this implicitly closes fd_ + dir_ = NULL; + } else { + RAW_LOG(ERROR, "Failed to close directory."); + } + } + } + + bool IsValid() const { + return dir_ != NULL; + } + + // Move to the next entry returning false if the iteration is complete. + bool Next() { + int err = readdir_r(dir_,&entry_, ¤t_); + if(0 != err || NULL == current_){ + return false; + } + return true; + } + + const char* name() const { + if (NULL == current_) + return NULL; + return current_->d_name; + } + + int fd() const { + return fd_; + } + + static bool IsFallback() { + return false; + } + + private: + const int fd_; + DIR* dir_; + struct dirent entry_; + struct dirent* current_; +}; + +} // namespace butil + +#endif // BUTIL_FILES_DIR_READER_LINUX_H_ diff --git a/src/butil/files/fd_guard.h b/src/butil/files/fd_guard.h new file mode 100644 index 0000000..82da6d6 --- /dev/null +++ b/src/butil/files/fd_guard.h @@ -0,0 +1,32 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BUTIL_FD_GUARD_H +#define BUTIL_FD_GUARD_H + +#include "butil/files/scoped_file.h" + +namespace butil { +namespace files { +class fd_guard : public ScopedFD { +public: + operator int() const { return get(); } +}; +} // files +} // base + +#endif // BUTIL_FD_GUARD_H diff --git a/src/butil/files/file.cc b/src/butil/files/file.cc new file mode 100644 index 0000000..b3ce118 --- /dev/null +++ b/src/butil/files/file.cc @@ -0,0 +1,127 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/files/file.h" +#include "butil/files/file_path.h" + +namespace butil { + +File::Info::Info() + : size(0), + is_directory(false), + is_symbolic_link(false) { +} + +File::Info::~Info() { +} + +File::File() + : error_details_(FILE_ERROR_FAILED), + created_(false), + async_(false) { +} + +#if !defined(OS_NACL) +File::File(const FilePath& name, uint32_t flags) + : error_details_(FILE_OK), + created_(false), + async_(false) { + Initialize(name, flags); +} +#endif + +File::File(PlatformFile platform_file) + : file_(platform_file), + error_details_(FILE_OK), + created_(false), + async_(false) { +#if defined(OS_POSIX) + DCHECK_GE(platform_file, -1); +#endif +} + +File::File(Error error_details) + : error_details_(error_details), + created_(false), + async_(false) { +} + +File::File(RValue other) + : file_(other.object->TakePlatformFile()), + error_details_(other.object->error_details()), + created_(other.object->created()), + async_(other.object->async_) { +} + +File::~File() { + // Go through the AssertIOAllowed logic. + Close(); +} + +File& File::operator=(RValue other) { + if (this != other.object) { + Close(); + SetPlatformFile(other.object->TakePlatformFile()); + error_details_ = other.object->error_details(); + created_ = other.object->created(); + async_ = other.object->async_; + } + return *this; +} + +#if !defined(OS_NACL) +void File::Initialize(const FilePath& name, uint32_t flags) { + if (name.ReferencesParent()) { + error_details_ = FILE_ERROR_ACCESS_DENIED; + return; + } + InitializeUnsafe(name, flags); +} +#endif + +std::string File::ErrorToString(Error error) { + switch (error) { + case FILE_OK: + return "FILE_OK"; + case FILE_ERROR_FAILED: + return "FILE_ERROR_FAILED"; + case FILE_ERROR_IN_USE: + return "FILE_ERROR_IN_USE"; + case FILE_ERROR_EXISTS: + return "FILE_ERROR_EXISTS"; + case FILE_ERROR_NOT_FOUND: + return "FILE_ERROR_NOT_FOUND"; + case FILE_ERROR_ACCESS_DENIED: + return "FILE_ERROR_ACCESS_DENIED"; + case FILE_ERROR_TOO_MANY_OPENED: + return "FILE_ERROR_TOO_MANY_OPENED"; + case FILE_ERROR_NO_MEMORY: + return "FILE_ERROR_NO_MEMORY"; + case FILE_ERROR_NO_SPACE: + return "FILE_ERROR_NO_SPACE"; + case FILE_ERROR_NOT_A_DIRECTORY: + return "FILE_ERROR_NOT_A_DIRECTORY"; + case FILE_ERROR_INVALID_OPERATION: + return "FILE_ERROR_INVALID_OPERATION"; + case FILE_ERROR_SECURITY: + return "FILE_ERROR_SECURITY"; + case FILE_ERROR_ABORT: + return "FILE_ERROR_ABORT"; + case FILE_ERROR_NOT_A_FILE: + return "FILE_ERROR_NOT_A_FILE"; + case FILE_ERROR_NOT_EMPTY: + return "FILE_ERROR_NOT_EMPTY"; + case FILE_ERROR_INVALID_URL: + return "FILE_ERROR_INVALID_URL"; + case FILE_ERROR_IO: + return "FILE_ERROR_IO"; + case FILE_ERROR_MAX: + break; + } + + NOTREACHED(); + return ""; +} + +} // namespace butil diff --git a/src/butil/files/file.h b/src/butil/files/file.h new file mode 100644 index 0000000..1b9ce17 --- /dev/null +++ b/src/butil/files/file.h @@ -0,0 +1,313 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_FILES_FILE_H_ +#define BUTIL_FILES_FILE_H_ + +#include "butil/build_config.h" +#if defined(OS_WIN) +#include +#endif + +#if defined(OS_POSIX) +#include +#endif + +#include + +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/files/scoped_file.h" +#include "butil/move.h" +#include "butil/time/time.h" + +#if defined(OS_WIN) +#include "butil/win/scoped_handle.h" +#endif + +namespace butil { + +class FilePath; + +#if defined(OS_WIN) +typedef HANDLE PlatformFile; +#elif defined(OS_POSIX) +typedef int PlatformFile; + +#if defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL) +typedef struct stat stat_wrapper_t; +#else +typedef struct stat64 stat_wrapper_t; +#endif +#endif // defined(OS_POSIX) + +// Thin wrapper around an OS-level file. +// Note that this class does not provide any support for asynchronous IO, other +// than the ability to create asynchronous handles on Windows. +// +// Note about const: this class does not attempt to determine if the underlying +// file system object is affected by a particular method in order to consider +// that method const or not. Only methods that deal with member variables in an +// obvious non-modifying way are marked as const. Any method that forward calls +// to the OS is not considered const, even if there is no apparent change to +// member variables. +class BUTIL_EXPORT File { + MOVE_ONLY_TYPE_FOR_CPP_03(File, RValue) + + public: + // FLAG_(OPEN|CREATE).* are mutually exclusive. You should specify exactly one + // of the five (possibly combining with other flags) when opening or creating + // a file. + // FLAG_(WRITE|APPEND) are mutually exclusive. This is so that APPEND behavior + // will be consistent with O_APPEND on POSIX. + // FLAG_EXCLUSIVE_(READ|WRITE) only grant exclusive access to the file on + // creation on POSIX; for existing files, consider using Lock(). + enum Flags { + FLAG_OPEN = 1 << 0, // Opens a file, only if it exists. + FLAG_CREATE = 1 << 1, // Creates a new file, only if it does not + // already exist. + FLAG_OPEN_ALWAYS = 1 << 2, // May create a new file. + FLAG_CREATE_ALWAYS = 1 << 3, // May overwrite an old file. + FLAG_OPEN_TRUNCATED = 1 << 4, // Opens a file and truncates it, only if it + // exists. + FLAG_READ = 1 << 5, + FLAG_WRITE = 1 << 6, + FLAG_APPEND = 1 << 7, + FLAG_EXCLUSIVE_READ = 1 << 8, // EXCLUSIVE is opposite of Windows SHARE. + FLAG_EXCLUSIVE_WRITE = 1 << 9, + FLAG_ASYNC = 1 << 10, + FLAG_TEMPORARY = 1 << 11, // Used on Windows only. + FLAG_HIDDEN = 1 << 12, // Used on Windows only. + FLAG_DELETE_ON_CLOSE = 1 << 13, + FLAG_WRITE_ATTRIBUTES = 1 << 14, // Used on Windows only. + FLAG_SHARE_DELETE = 1 << 15, // Used on Windows only. + FLAG_TERMINAL_DEVICE = 1 << 16, // Serial port flags. + FLAG_BACKUP_SEMANTICS = 1 << 17, // Used on Windows only. + FLAG_EXECUTE = 1 << 18, // Used on Windows only. + }; + + // This enum has been recorded in multiple histograms. If the order of the + // fields needs to change, please ensure that those histograms are obsolete or + // have been moved to a different enum. + // + // FILE_ERROR_ACCESS_DENIED is returned when a call fails because of a + // filesystem restriction. FILE_ERROR_SECURITY is returned when a browser + // policy doesn't allow the operation to be executed. + enum Error { + FILE_OK = 0, + FILE_ERROR_FAILED = -1, + FILE_ERROR_IN_USE = -2, + FILE_ERROR_EXISTS = -3, + FILE_ERROR_NOT_FOUND = -4, + FILE_ERROR_ACCESS_DENIED = -5, + FILE_ERROR_TOO_MANY_OPENED = -6, + FILE_ERROR_NO_MEMORY = -7, + FILE_ERROR_NO_SPACE = -8, + FILE_ERROR_NOT_A_DIRECTORY = -9, + FILE_ERROR_INVALID_OPERATION = -10, + FILE_ERROR_SECURITY = -11, + FILE_ERROR_ABORT = -12, + FILE_ERROR_NOT_A_FILE = -13, + FILE_ERROR_NOT_EMPTY = -14, + FILE_ERROR_INVALID_URL = -15, + FILE_ERROR_IO = -16, + // Put new entries here and increment FILE_ERROR_MAX. + FILE_ERROR_MAX = -17 + }; + + // This explicit mapping matches both FILE_ on Windows and SEEK_ on Linux. + enum Whence { + FROM_BEGIN = 0, + FROM_CURRENT = 1, + FROM_END = 2 + }; + + // Used to hold information about a given file. + // If you add more fields to this structure (platform-specific fields are OK), + // make sure to update all functions that use it in file_util_{win|posix}.cc + // too, and the ParamTraits implementation in + // chrome/common/common_param_traits.cc. + struct BUTIL_EXPORT Info { + Info(); + ~Info(); +#if defined(OS_POSIX) + // Fills this struct with values from |stat_info|. + void FromStat(const stat_wrapper_t& stat_info); +#endif + + // The size of the file in bytes. Undefined when is_directory is true. + int64_t size; + + // True if the file corresponds to a directory. + bool is_directory; + + // True if the file corresponds to a symbolic link. + bool is_symbolic_link; + + // The last modified time of a file. + butil::Time last_modified; + + // The last accessed time of a file. + butil::Time last_accessed; + + // The creation time of a file. + butil::Time creation_time; + }; + + File(); + + // Creates or opens the given file. This will fail with 'access denied' if the + // |name| contains path traversal ('..') components. + File(const FilePath& name, uint32_t flags); + + // Takes ownership of |platform_file|. + explicit File(PlatformFile platform_file); + + // Creates an object with a specific error_details code. + explicit File(Error error_details); + + // Move constructor for C++03 move emulation of this type. + File(RValue other); + + ~File(); + + // Move operator= for C++03 move emulation of this type. + File& operator=(RValue other); + + // Creates or opens the given file. + void Initialize(const FilePath& name, uint32_t flags); + + // Creates or opens the given file, allowing paths with traversal ('..') + // components. Use only with extreme care. + void InitializeUnsafe(const FilePath& name, uint32_t flags); + + bool IsValid() const; + + // Returns true if a new file was created (or an old one truncated to zero + // length to simulate a new file, which can happen with + // FLAG_CREATE_ALWAYS), and false otherwise. + bool created() const { return created_; } + + // Returns the OS result of opening this file. Note that the way to verify + // the success of the operation is to use IsValid(), not this method: + // File file(name, flags); + // if (!file.IsValid()) + // return; + Error error_details() const { return error_details_; } + + PlatformFile GetPlatformFile() const; + PlatformFile TakePlatformFile(); + + // Destroying this object closes the file automatically. + void Close(); + + // Changes current position in the file to an |offset| relative to an origin + // defined by |whence|. Returns the resultant current position in the file + // (relative to the start) or -1 in case of error. + int64_t Seek(Whence whence, int64_t offset); + + // Reads the given number of bytes (or until EOF is reached) starting with the + // given offset. Returns the number of bytes read, or -1 on error. Note that + // this function makes a best effort to read all data on all platforms, so it + // is not intended for stream oriented files but instead for cases when the + // normal expectation is that actually |size| bytes are read unless there is + // an error. + int Read(int64_t offset, char* data, int size); + + // Same as above but without seek. + int ReadAtCurrentPos(char* data, int size); + + // Reads the given number of bytes (or until EOF is reached) starting with the + // given offset, but does not make any effort to read all data on all + // platforms. Returns the number of bytes read, or -1 on error. + int ReadNoBestEffort(int64_t offset, char* data, int size); + + // Same as above but without seek. + int ReadAtCurrentPosNoBestEffort(char* data, int size); + + // Writes the given buffer into the file at the given offset, overwritting any + // data that was previously there. Returns the number of bytes written, or -1 + // on error. Note that this function makes a best effort to write all data on + // all platforms. + // Ignores the offset and writes to the end of the file if the file was opened + // with FLAG_APPEND. + int Write(int64_t offset, const char* data, int size); + + // Save as above but without seek. + int WriteAtCurrentPos(const char* data, int size); + + // Save as above but does not make any effort to write all data on all + // platforms. Returns the number of bytes written, or -1 on error. + int WriteAtCurrentPosNoBestEffort(const char* data, int size); + + // Returns the current size of this file, or a negative number on failure. + int64_t GetLength(); + + // Truncates the file to the given length. If |length| is greater than the + // current size of the file, the file is extended with zeros. If the file + // doesn't exist, |false| is returned. + bool SetLength(int64_t length); + + // Flushes the buffers. + bool Flush(); + + // Updates the file times. + bool SetTimes(Time last_access_time, Time last_modified_time); + + // Returns some basic information for the given file. + bool GetInfo(Info* info); + + // Attempts to take an exclusive write lock on the file. Returns immediately + // (i.e. does not wait for another process to unlock the file). If the lock + // was obtained, the result will be FILE_OK. A lock only guarantees + // that other processes may not also take a lock on the same file with the + // same API - it may still be opened, renamed, unlinked, etc. + // + // Common semantics: + // * Locks are held by processes, but not inherited by child processes. + // * Locks are released by the OS on file close or process termination. + // * Locks are reliable only on local filesystems. + // * Duplicated file handles may also write to locked files. + // Windows-specific semantics: + // * Locks are mandatory for read/write APIs, advisory for mapping APIs. + // * Within a process, locking the same file (by the same or new handle) + // will fail. + // POSIX-specific semantics: + // * Locks are advisory only. + // * Within a process, locking the same file (by the same or new handle) + // will succeed. + // * Closing any descriptor on a given file releases the lock. + Error Lock(); + + // Unlock a file previously locked. + Error Unlock(); + + bool async() const { return async_; } + +#if defined(OS_WIN) + static Error OSErrorToFileError(DWORD last_error); +#elif defined(OS_POSIX) + static Error OSErrorToFileError(int saved_errno); +#endif + + // Converts an error value to a human-readable form. Used for logging. + static std::string ErrorToString(Error error); + + private: + void SetPlatformFile(PlatformFile file); + +#if defined(OS_WIN) + win::ScopedHandle file_; +#elif defined(OS_POSIX) + ScopedFD file_; +#endif + + Error error_details_; + bool created_; + bool async_; +}; + +} // namespace butil + +#endif // BUTIL_FILES_FILE_H_ diff --git a/src/butil/files/file_enumerator.cc b/src/butil/files/file_enumerator.cc new file mode 100644 index 0000000..f8a4a4a --- /dev/null +++ b/src/butil/files/file_enumerator.cc @@ -0,0 +1,21 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/files/file_enumerator.h" + +#include "butil/file_util.h" + +namespace butil { + +FileEnumerator::FileInfo::~FileInfo() { +} + +bool FileEnumerator::ShouldSkip(const FilePath& path) { + FilePath::StringType basename = path.BaseName().value(); + return basename == FILE_PATH_LITERAL(".") || + (basename == FILE_PATH_LITERAL("..") && + !(INCLUDE_DOT_DOT & file_type_)); +} + +} // namespace butil diff --git a/src/butil/files/file_enumerator.h b/src/butil/files/file_enumerator.h new file mode 100644 index 0000000..f696a4b --- /dev/null +++ b/src/butil/files/file_enumerator.h @@ -0,0 +1,159 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_FILES_FILE_ENUMERATOR_H_ +#define BUTIL_FILES_FILE_ENUMERATOR_H_ + +#include +#include + +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/files/file_path.h" +#include "butil/time/time.h" +#include "butil/build_config.h" + +#if defined(OS_WIN) +#include +#elif defined(OS_POSIX) +#include +#include +#endif + +namespace butil { + +// A class for enumerating the files in a provided path. The order of the +// results is not guaranteed. +// +// This is blocking. Do not use on critical threads. +// +// Example: +// +// butil::FileEnumerator enum(my_dir, false, butil::FileEnumerator::FILES, +// FILE_PATH_LITERAL("*.txt")); +// for (butil::FilePath name = enum.Next(); !name.empty(); name = enum.Next()) +// ... +class BUTIL_EXPORT FileEnumerator { + public: + // Note: copy & assign supported. + class BUTIL_EXPORT FileInfo { + public: + FileInfo(); + ~FileInfo(); + + bool IsDirectory() const; + + // The name of the file. This will not include any path information. This + // is in constrast to the value returned by FileEnumerator.Next() which + // includes the |root_path| passed into the FileEnumerator constructor. + FilePath GetName() const; + + int64_t GetSize() const; + Time GetLastModifiedTime() const; + +#if defined(OS_WIN) + // Note that the cAlternateFileName (used to hold the "short" 8.3 name) + // of the WIN32_FIND_DATA will be empty. Since we don't use short file + // names, we tell Windows to omit it which speeds up the query slightly. + const WIN32_FIND_DATA& find_data() const { return find_data_; } +#elif defined(OS_POSIX) + const struct stat& stat() const { return stat_; } +#endif + + private: + friend class FileEnumerator; + +#if defined(OS_WIN) + WIN32_FIND_DATA find_data_; +#elif defined(OS_POSIX) + struct stat stat_; + FilePath filename_; +#endif + }; + + enum FileType { + FILES = 1 << 0, + DIRECTORIES = 1 << 1, + INCLUDE_DOT_DOT = 1 << 2, +#if defined(OS_POSIX) + SHOW_SYM_LINKS = 1 << 4, +#endif + }; + + // |root_path| is the starting directory to search for. It may or may not end + // in a slash. + // + // If |recursive| is true, this will enumerate all matches in any + // subdirectories matched as well. It does a breadth-first search, so all + // files in one directory will be returned before any files in a + // subdirectory. + // + // |file_type|, a bit mask of FileType, specifies whether the enumerator + // should match files, directories, or both. + // + // |pattern| is an optional pattern for which files to match. This + // works like shell globbing. For example, "*.txt" or "Foo???.doc". + // However, be careful in specifying patterns that aren't cross platform + // since the underlying code uses OS-specific matching routines. In general, + // Windows matching is less featureful than others, so test there first. + // If unspecified, this will match all files. + // NOTE: the pattern only matches the contents of root_path, not files in + // recursive subdirectories. + // TODO(erikkay): Fix the pattern matching to work at all levels. + FileEnumerator(const FilePath& root_path, + bool recursive, + int file_type); + FileEnumerator(const FilePath& root_path, + bool recursive, + int file_type, + const FilePath::StringType& pattern); + ~FileEnumerator(); + + // Returns the next file or an empty string if there are no more results. + // + // The returned path will incorporate the |root_path| passed in the + // constructor: "/file_name.txt". If the |root_path| is absolute, + // then so will be the result of Next(). + FilePath Next(); + + // Write the file info into |info|. + FileInfo GetInfo() const; + + private: + // Returns true if the given path should be skipped in enumeration. + bool ShouldSkip(const FilePath& path); + +#if defined(OS_WIN) + // True when find_data_ is valid. + bool has_find_data_; + WIN32_FIND_DATA find_data_; + HANDLE find_handle_; +#elif defined(OS_POSIX) + + // Read the filenames in source into the vector of DirectoryEntryInfo's + static bool ReadDirectory(std::vector* entries, + const FilePath& source, bool show_links); + + // The files in the current directory + std::vector directory_entries_; + + // The next entry to use from the directory_entries_ vector + size_t current_directory_entry_; +#endif + + FilePath root_path_; + bool recursive_; + int file_type_; + FilePath::StringType pattern_; // Empty when we want to find everything. + + // A stack that keeps track of which subdirectories we still need to + // enumerate in the breadth-first search. + std::stack pending_paths_; + + DISALLOW_COPY_AND_ASSIGN(FileEnumerator); +}; + +} // namespace butil + +#endif // BUTIL_FILES_FILE_ENUMERATOR_H_ diff --git a/src/butil/files/file_enumerator_posix.cc b/src/butil/files/file_enumerator_posix.cc new file mode 100644 index 0000000..d816492 --- /dev/null +++ b/src/butil/files/file_enumerator_posix.cc @@ -0,0 +1,168 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/files/file_enumerator.h" + +#include +#include +#include + +#include "butil/logging.h" +#include "butil/threading/thread_restrictions.h" + +namespace butil { + +// FileEnumerator::FileInfo ---------------------------------------------------- + +FileEnumerator::FileInfo::FileInfo() { + memset(&stat_, 0, sizeof(stat_)); +} + +bool FileEnumerator::FileInfo::IsDirectory() const { + return S_ISDIR(stat_.st_mode); +} + +FilePath FileEnumerator::FileInfo::GetName() const { + return filename_; +} + +int64_t FileEnumerator::FileInfo::GetSize() const { + return stat_.st_size; +} + +butil::Time FileEnumerator::FileInfo::GetLastModifiedTime() const { + return butil::Time::FromTimeT(stat_.st_mtime); +} + +// FileEnumerator -------------------------------------------------------------- + +FileEnumerator::FileEnumerator(const FilePath& root_path, + bool recursive, + int file_type) + : current_directory_entry_(0), + root_path_(root_path), + recursive_(recursive), + file_type_(file_type) { + // INCLUDE_DOT_DOT must not be specified if recursive. + DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_))); + pending_paths_.push(root_path); +} + +FileEnumerator::FileEnumerator(const FilePath& root_path, + bool recursive, + int file_type, + const FilePath::StringType& pattern) + : current_directory_entry_(0), + root_path_(root_path), + recursive_(recursive), + file_type_(file_type), + pattern_(root_path.Append(pattern).value()) { + // INCLUDE_DOT_DOT must not be specified if recursive. + DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_))); + // The Windows version of this code appends the pattern to the root_path, + // potentially only matching against items in the top-most directory. + // Do the same here. + if (pattern.empty()) + pattern_ = FilePath::StringType(); + pending_paths_.push(root_path); +} + +FileEnumerator::~FileEnumerator() { +} + +FilePath FileEnumerator::Next() { + ++current_directory_entry_; + + // While we've exhausted the entries in the current directory, do the next + while (current_directory_entry_ >= directory_entries_.size()) { + if (pending_paths_.empty()) + return FilePath(); + + root_path_ = pending_paths_.top(); + root_path_ = root_path_.StripTrailingSeparators(); + pending_paths_.pop(); + + std::vector entries; + if (!ReadDirectory(&entries, root_path_, file_type_ & SHOW_SYM_LINKS)) + continue; + + directory_entries_.clear(); + current_directory_entry_ = 0; + for (std::vector::const_iterator i = entries.begin(); + i != entries.end(); ++i) { + FilePath full_path = root_path_.Append(i->filename_); + if (ShouldSkip(full_path)) + continue; + + if (pattern_.size() && + fnmatch(pattern_.c_str(), full_path.value().c_str(), FNM_NOESCAPE)) + continue; + + if (recursive_ && S_ISDIR(i->stat_.st_mode)) + pending_paths_.push(full_path); + + if ((S_ISDIR(i->stat_.st_mode) && (file_type_ & DIRECTORIES)) || + (!S_ISDIR(i->stat_.st_mode) && (file_type_ & FILES))) + directory_entries_.push_back(*i); + } + } + + return root_path_.Append( + directory_entries_[current_directory_entry_].filename_); +} + +FileEnumerator::FileInfo FileEnumerator::GetInfo() const { + return directory_entries_[current_directory_entry_]; +} + +bool FileEnumerator::ReadDirectory(std::vector* entries, + const FilePath& source, bool show_links) { + butil::ThreadRestrictions::AssertIOAllowed(); + DIR* dir = opendir(source.value().c_str()); + if (!dir) + return false; + +#if !defined(OS_LINUX) && !defined(OS_MACOSX) && !defined(OS_BSD) && \ + !defined(OS_SOLARIS) && !defined(OS_ANDROID) + #error Port warning: depending on the definition of struct dirent, \ + additional space for pathname may be needed +#endif + + struct dirent* dent; + // readdir_r is marked as deprecated since glibc 2.24. + // Using readdir on _different_ DIR* object is already thread-safe in + // most modern libc implementations. +#if defined(__GLIBC__) && \ + (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 24)) + while ((dent = readdir(dir))) { +#else + struct dirent dent_buf; + while (readdir_r(dir, &dent_buf, &dent) == 0 && dent) { +#endif + FileInfo info; + info.filename_ = FilePath(dent->d_name); + + FilePath full_name = source.Append(dent->d_name); + int ret; + if (show_links) + ret = lstat(full_name.value().c_str(), &info.stat_); + else + ret = stat(full_name.value().c_str(), &info.stat_); + if (ret < 0) { + // Print the stat() error message unless it was ENOENT and we're + // following symlinks. + if (!(errno == ENOENT && !show_links)) { + DPLOG(ERROR) << "Couldn't stat " + << source.Append(dent->d_name).value(); + } + memset(&info.stat_, 0, sizeof(info.stat_)); + } + entries->push_back(info); + } + + closedir(dir); + return true; +} + +} // namespace butil diff --git a/src/butil/files/file_path.cc b/src/butil/files/file_path.cc new file mode 100644 index 0000000..e618854 --- /dev/null +++ b/src/butil/files/file_path.cc @@ -0,0 +1,1298 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/files/file_path.h" + +#include +#include + +#include "butil/basictypes.h" +#include "butil/logging.h" + +// These includes are just for the *Hack functions, and should be removed +// when those functions are removed. +#include "butil/strings/string_piece.h" +#include "butil/strings/string_util.h" +#include "butil/strings/sys_string_conversions.h" +#include "butil/strings/utf_string_conversions.h" + +#if defined(OS_MACOSX) +#include "butil/mac/scoped_cftyperef.h" +#include "butil/third_party/icu/icu_utf.h" +#endif + +#if defined(OS_WIN) +#include +#elif defined(OS_MACOSX) +#include +#endif + +namespace butil { + +typedef FilePath::StringType StringType; + +namespace { + +const char* kCommonDoubleExtensionSuffixes[] = { "gz", "z", "bz2" }; +const char* kCommonDoubleExtensions[] = { "user.js" }; + +const FilePath::CharType kStringTerminator = FILE_PATH_LITERAL('\0'); + +// If this FilePath contains a drive letter specification, returns the +// position of the last character of the drive letter specification, +// otherwise returns npos. This can only be true on Windows, when a pathname +// begins with a letter followed by a colon. On other platforms, this always +// returns npos. +StringType::size_type FindDriveLetter(const StringType& path) { +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + // This is dependent on an ASCII-based character set, but that's a + // reasonable assumption. iswalpha can be too inclusive here. + if (path.length() >= 2 && path[1] == L':' && + ((path[0] >= L'A' && path[0] <= L'Z') || + (path[0] >= L'a' && path[0] <= L'z'))) { + return 1; + } +#endif // FILE_PATH_USES_DRIVE_LETTERS + return StringType::npos; +} + +#if defined(FILE_PATH_USES_DRIVE_LETTERS) +bool EqualDriveLetterCaseInsensitive(const StringType& a, + const StringType& b) { + size_t a_letter_pos = FindDriveLetter(a); + size_t b_letter_pos = FindDriveLetter(b); + + if (a_letter_pos == StringType::npos || b_letter_pos == StringType::npos) + return a == b; + + StringType a_letter(a.substr(0, a_letter_pos + 1)); + StringType b_letter(b.substr(0, b_letter_pos + 1)); + if (!StartsWith(a_letter, b_letter, false)) + return false; + + StringType a_rest(a.substr(a_letter_pos + 1)); + StringType b_rest(b.substr(b_letter_pos + 1)); + return a_rest == b_rest; +} +#endif // defined(FILE_PATH_USES_DRIVE_LETTERS) + +bool IsPathAbsolute(const StringType& path) { +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + StringType::size_type letter = FindDriveLetter(path); + if (letter != StringType::npos) { + // Look for a separator right after the drive specification. + return path.length() > letter + 1 && + FilePath::IsSeparator(path[letter + 1]); + } + // Look for a pair of leading separators. + return path.length() > 1 && + FilePath::IsSeparator(path[0]) && FilePath::IsSeparator(path[1]); +#else // FILE_PATH_USES_DRIVE_LETTERS + // Look for a separator in the first position. + return path.length() > 0 && FilePath::IsSeparator(path[0]); +#endif // FILE_PATH_USES_DRIVE_LETTERS +} + +bool AreAllSeparators(const StringType& input) { + for (StringType::const_iterator it = input.begin(); + it != input.end(); ++it) { + if (!FilePath::IsSeparator(*it)) + return false; + } + + return true; +} + +// Find the position of the '.' that separates the extension from the rest +// of the file name. The position is relative to BaseName(), not value(). +// Returns npos if it can't find an extension. +StringType::size_type FinalExtensionSeparatorPosition(const StringType& path) { + // Special case "." and ".." + if (path == FilePath::kCurrentDirectory || path == FilePath::kParentDirectory) + return StringType::npos; + + return path.rfind(FilePath::kExtensionSeparator); +} + +// Same as above, but allow a second extension component of up to 4 +// characters when the rightmost extension component is a common double +// extension (gz, bz2, Z). For example, foo.tar.gz or foo.tar.Z would have +// extension components of '.tar.gz' and '.tar.Z' respectively. +StringType::size_type ExtensionSeparatorPosition(const StringType& path) { + const StringType::size_type last_dot = FinalExtensionSeparatorPosition(path); + + // No extension, or the extension is the whole filename. + if (last_dot == StringType::npos || last_dot == 0U) + return last_dot; + + const StringType::size_type penultimate_dot = + path.rfind(FilePath::kExtensionSeparator, last_dot - 1); + const StringType::size_type last_separator = + path.find_last_of(FilePath::kSeparators, last_dot - 1, + FilePath::kSeparatorsLength - 1); + + if (penultimate_dot == StringType::npos || + (last_separator != StringType::npos && + penultimate_dot < last_separator)) { + return last_dot; + } + + for (size_t i = 0; i < arraysize(kCommonDoubleExtensions); ++i) { + StringType extension(path, penultimate_dot + 1); + if (LowerCaseEqualsASCII(extension, kCommonDoubleExtensions[i])) + return penultimate_dot; + } + + StringType extension(path, last_dot + 1); + for (size_t i = 0; i < arraysize(kCommonDoubleExtensionSuffixes); ++i) { + if (LowerCaseEqualsASCII(extension, kCommonDoubleExtensionSuffixes[i])) { + if ((last_dot - penultimate_dot) <= 5U && + (last_dot - penultimate_dot) > 1U) { + return penultimate_dot; + } + } + } + + return last_dot; +} + +// Returns true if path is "", ".", or "..". +bool IsEmptyOrSpecialCase(const StringType& path) { + // Special cases "", ".", and ".." + if (path.empty() || path == FilePath::kCurrentDirectory || + path == FilePath::kParentDirectory) { + return true; + } + + return false; +} + +} // namespace + +FilePath::FilePath() { +} + +FilePath::FilePath(const FilePath& that) : path_(that.path_) { +} + +FilePath::FilePath(const StringType& path) : path_(path) { + StringType::size_type nul_pos = path_.find(kStringTerminator); + if (nul_pos != StringType::npos) + path_.erase(nul_pos, StringType::npos); +} + +FilePath::~FilePath() { +} + +FilePath& FilePath::operator=(const FilePath& that) { + path_ = that.path_; + return *this; +} + +bool FilePath::operator==(const FilePath& that) const { +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + return EqualDriveLetterCaseInsensitive(this->path_, that.path_); +#else // defined(FILE_PATH_USES_DRIVE_LETTERS) + return path_ == that.path_; +#endif // defined(FILE_PATH_USES_DRIVE_LETTERS) +} + +bool FilePath::operator!=(const FilePath& that) const { +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + return !EqualDriveLetterCaseInsensitive(this->path_, that.path_); +#else // defined(FILE_PATH_USES_DRIVE_LETTERS) + return path_ != that.path_; +#endif // defined(FILE_PATH_USES_DRIVE_LETTERS) +} + +// static +bool FilePath::IsSeparator(CharType character) { + for (size_t i = 0; i < kSeparatorsLength - 1; ++i) { + if (character == kSeparators[i]) { + return true; + } + } + + return false; +} + +void FilePath::GetComponents(std::vector* components) const { + DCHECK(components); + if (!components) + return; + components->clear(); + if (value().empty()) + return; + + std::vector ret_val; + FilePath current = *this; + FilePath base; + + // Capture path components. + while (current != current.DirName()) { + base = current.BaseName(); + if (!AreAllSeparators(base.value())) + ret_val.push_back(base.value()); + current = current.DirName(); + } + + // Capture root, if any. + base = current.BaseName(); + if (!base.value().empty() && base.value() != kCurrentDirectory) + ret_val.push_back(current.BaseName().value()); + + // Capture drive letter, if any. + FilePath dir = current.DirName(); + StringType::size_type letter = FindDriveLetter(dir.value()); + if (letter != StringType::npos) { + ret_val.push_back(StringType(dir.value(), 0, letter + 1)); + } + + *components = std::vector(ret_val.rbegin(), ret_val.rend()); +} + +bool FilePath::IsParent(const FilePath& child) const { + return AppendRelativePath(child, NULL); +} + +bool FilePath::AppendRelativePath(const FilePath& child, + FilePath* path) const { + std::vector parent_components; + std::vector child_components; + GetComponents(&parent_components); + child.GetComponents(&child_components); + + if (parent_components.empty() || + parent_components.size() >= child_components.size()) + return false; + + std::vector::const_iterator parent_comp = + parent_components.begin(); + std::vector::const_iterator child_comp = + child_components.begin(); + +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + // Windows can access case sensitive filesystems, so component + // comparisions must be case sensitive, but drive letters are + // never case sensitive. + if ((FindDriveLetter(*parent_comp) != StringType::npos) && + (FindDriveLetter(*child_comp) != StringType::npos)) { + if (!StartsWith(*parent_comp, *child_comp, false)) + return false; + ++parent_comp; + ++child_comp; + } +#endif // defined(FILE_PATH_USES_DRIVE_LETTERS) + + while (parent_comp != parent_components.end()) { + if (*parent_comp != *child_comp) + return false; + ++parent_comp; + ++child_comp; + } + + if (path != NULL) { + for (; child_comp != child_components.end(); ++child_comp) { + *path = path->Append(*child_comp); + } + } + return true; +} + +// libgen's dirname and basename aren't guaranteed to be thread-safe and aren't +// guaranteed to not modify their input strings, and in fact are implemented +// differently in this regard on different platforms. Don't use them, but +// adhere to their behavior. +FilePath FilePath::DirName() const { + FilePath new_path(path_); + new_path.StripTrailingSeparatorsInternal(); + + // The drive letter, if any, always needs to remain in the output. If there + // is no drive letter, as will always be the case on platforms which do not + // support drive letters, letter will be npos, or -1, so the comparisons and + // resizes below using letter will still be valid. + StringType::size_type letter = FindDriveLetter(new_path.path_); + + StringType::size_type last_separator = + new_path.path_.find_last_of(kSeparators, StringType::npos, + kSeparatorsLength - 1); + if (last_separator == StringType::npos) { + // path_ is in the current directory. + new_path.path_.resize(letter + 1); + } else if (last_separator == letter + 1) { + // path_ is in the root directory. + new_path.path_.resize(letter + 2); + } else if (last_separator == letter + 2 && + IsSeparator(new_path.path_[letter + 1])) { + // path_ is in "//" (possibly with a drive letter); leave the double + // separator intact indicating alternate root. + new_path.path_.resize(letter + 3); + } else if (last_separator != 0) { + // path_ is somewhere else, trim the basename. + new_path.path_.resize(last_separator); + } + + new_path.StripTrailingSeparatorsInternal(); + if (!new_path.path_.length()) + new_path.path_ = kCurrentDirectory; + + return new_path; +} + +FilePath FilePath::BaseName() const { + FilePath new_path(path_); + new_path.StripTrailingSeparatorsInternal(); + + // The drive letter, if any, is always stripped. + StringType::size_type letter = FindDriveLetter(new_path.path_); + if (letter != StringType::npos) { + new_path.path_.erase(0, letter + 1); + } + + // Keep everything after the final separator, but if the pathname is only + // one character and it's a separator, leave it alone. + StringType::size_type last_separator = + new_path.path_.find_last_of(kSeparators, StringType::npos, + kSeparatorsLength - 1); + if (last_separator != StringType::npos && + last_separator < new_path.path_.length() - 1) { + new_path.path_.erase(0, last_separator + 1); + } + + return new_path; +} + +StringType FilePath::Extension() const { + FilePath base(BaseName()); + const StringType::size_type dot = ExtensionSeparatorPosition(base.path_); + if (dot == StringType::npos) + return StringType(); + + return base.path_.substr(dot, StringType::npos); +} + +StringType FilePath::FinalExtension() const { + FilePath base(BaseName()); + const StringType::size_type dot = FinalExtensionSeparatorPosition(base.path_); + if (dot == StringType::npos) + return StringType(); + + return base.path_.substr(dot, StringType::npos); +} + +FilePath FilePath::RemoveExtension() const { + if (Extension().empty()) + return *this; + + const StringType::size_type dot = ExtensionSeparatorPosition(path_); + if (dot == StringType::npos) + return *this; + + return FilePath(path_.substr(0, dot)); +} + +FilePath FilePath::RemoveFinalExtension() const { + if (FinalExtension().empty()) + return *this; + + const StringType::size_type dot = FinalExtensionSeparatorPosition(path_); + if (dot == StringType::npos) + return *this; + + return FilePath(path_.substr(0, dot)); +} + +FilePath FilePath::InsertBeforeExtension(const StringType& suffix) const { + if (suffix.empty()) + return FilePath(path_); + + if (IsEmptyOrSpecialCase(BaseName().value())) + return FilePath(); + + StringType ext = Extension(); + StringType ret = RemoveExtension().value(); + ret.append(suffix); + ret.append(ext); + return FilePath(ret); +} + +FilePath FilePath::InsertBeforeExtensionASCII(const StringPiece& suffix) + const { + DCHECK(IsStringASCII(suffix)); +#if defined(OS_WIN) + return InsertBeforeExtension(ASCIIToUTF16(suffix.as_string())); +#elif defined(OS_POSIX) + return InsertBeforeExtension(suffix.as_string()); +#endif +} + +FilePath FilePath::AddExtension(const StringType& extension) const { + if (IsEmptyOrSpecialCase(BaseName().value())) + return FilePath(); + + // If the new extension is "" or ".", then just return the current FilePath. + if (extension.empty() || extension == StringType(1, kExtensionSeparator)) + return *this; + + StringType str = path_; + if (extension[0] != kExtensionSeparator && + *(str.end() - 1) != kExtensionSeparator) { + str.append(1, kExtensionSeparator); + } + str.append(extension); + return FilePath(str); +} + +FilePath FilePath::ReplaceExtension(const StringType& extension) const { + if (IsEmptyOrSpecialCase(BaseName().value())) + return FilePath(); + + FilePath no_ext = RemoveExtension(); + // If the new extension is "" or ".", then just remove the current extension. + if (extension.empty() || extension == StringType(1, kExtensionSeparator)) + return no_ext; + + StringType str = no_ext.value(); + if (extension[0] != kExtensionSeparator) + str.append(1, kExtensionSeparator); + str.append(extension); + return FilePath(str); +} + +bool FilePath::MatchesExtension(const StringType& extension) const { + DCHECK(extension.empty() || extension[0] == kExtensionSeparator); + + StringType current_extension = Extension(); + + if (current_extension.length() != extension.length()) + return false; + + return FilePath::CompareEqualIgnoreCase(extension, current_extension); +} + +FilePath FilePath::Append(const StringType& component) const { + const StringType* appended = &component; + StringType without_nuls; + + StringType::size_type nul_pos = component.find(kStringTerminator); + if (nul_pos != StringType::npos) { + without_nuls = component.substr(0, nul_pos); + appended = &without_nuls; + } + + DCHECK(!IsPathAbsolute(*appended)); + + if (path_.compare(kCurrentDirectory) == 0) { + // Append normally doesn't do any normalization, but as a special case, + // when appending to kCurrentDirectory, just return a new path for the + // component argument. Appending component to kCurrentDirectory would + // serve no purpose other than needlessly lengthening the path, and + // it's likely in practice to wind up with FilePath objects containing + // only kCurrentDirectory when calling DirName on a single relative path + // component. + return FilePath(*appended); + } + + FilePath new_path(path_); + new_path.StripTrailingSeparatorsInternal(); + + // Don't append a separator if the path is empty (indicating the current + // directory) or if the path component is empty (indicating nothing to + // append). + if (appended->length() > 0 && new_path.path_.length() > 0) { + // Don't append a separator if the path still ends with a trailing + // separator after stripping (indicating the root directory). + if (!IsSeparator(new_path.path_[new_path.path_.length() - 1])) { + // Don't append a separator if the path is just a drive letter. + if (FindDriveLetter(new_path.path_) + 1 != new_path.path_.length()) { + new_path.path_.append(1, kSeparators[0]); + } + } + } + + new_path.path_.append(*appended); + return new_path; +} + +FilePath FilePath::Append(const FilePath& component) const { + return Append(component.value()); +} + +FilePath FilePath::AppendASCII(const StringPiece& component) const { + DCHECK(butil::IsStringASCII(component)); +#if defined(OS_WIN) + return Append(ASCIIToUTF16(component.as_string())); +#elif defined(OS_POSIX) + return Append(component.as_string()); +#endif +} + +bool FilePath::IsAbsolute() const { + return IsPathAbsolute(path_); +} + +bool FilePath::EndsWithSeparator() const { + if (empty()) + return false; + return IsSeparator(path_[path_.size() - 1]); +} + +FilePath FilePath::AsEndingWithSeparator() const { + if (EndsWithSeparator() || path_.empty()) + return *this; + + StringType path_str; + path_str.reserve(path_.length() + 1); // Only allocate string once. + + path_str = path_; + path_str.append(&kSeparators[0], 1); + return FilePath(path_str); +} + +FilePath FilePath::StripTrailingSeparators() const { + FilePath new_path(path_); + new_path.StripTrailingSeparatorsInternal(); + + return new_path; +} + +bool FilePath::ReferencesParent() const { + std::vector components; + GetComponents(&components); + + std::vector::const_iterator it = components.begin(); + for (; it != components.end(); ++it) { + const StringType& component = *it; + // Windows has odd, undocumented behavior with path components containing + // only whitespace and . characters. So, if all we see is . and + // whitespace, then we treat any .. sequence as referencing parent. + // For simplicity we enforce this on all platforms. + if (component.find_first_not_of(FILE_PATH_LITERAL(". \n\r\t")) == + std::string::npos && + component.find(kParentDirectory) != std::string::npos) { + return true; + } + } + return false; +} + +#if defined(OS_POSIX) +// See file_path.h for a discussion of the encoding of paths on POSIX +// platforms. These encoding conversion functions are not quite correct. + +string16 FilePath::LossyDisplayName() const { + return WideToUTF16(SysNativeMBToWide(path_)); +} + +std::string FilePath::MaybeAsASCII() const { + if (butil::IsStringASCII(path_)) + return path_; + return std::string(); +} + +std::string FilePath::AsUTF8Unsafe() const { +#if defined(OS_MACOSX) || defined(OS_CHROMEOS) + return value(); +#else + return WideToUTF8(SysNativeMBToWide(value())); +#endif +} + +string16 FilePath::AsUTF16Unsafe() const { +#if defined(OS_MACOSX) || defined(OS_CHROMEOS) + return UTF8ToUTF16(value()); +#else + return WideToUTF16(SysNativeMBToWide(value())); +#endif +} + +// static +FilePath FilePath::FromUTF8Unsafe(const std::string& utf8) { +#if defined(OS_MACOSX) || defined(OS_CHROMEOS) + return FilePath(utf8); +#else + return FilePath(SysWideToNativeMB(UTF8ToWide(utf8))); +#endif +} + +// static +FilePath FilePath::FromUTF16Unsafe(const string16& utf16) { +#if defined(OS_MACOSX) || defined(OS_CHROMEOS) + return FilePath(UTF16ToUTF8(utf16)); +#else + return FilePath(SysWideToNativeMB(UTF16ToWide(utf16))); +#endif +} + +#elif defined(OS_WIN) +string16 FilePath::LossyDisplayName() const { + return path_; +} + +std::string FilePath::MaybeAsASCII() const { + if (butil::IsStringASCII(path_)) + return UTF16ToASCII(path_); + return std::string(); +} + +std::string FilePath::AsUTF8Unsafe() const { + return WideToUTF8(value()); +} + +string16 FilePath::AsUTF16Unsafe() const { + return value(); +} + +// static +FilePath FilePath::FromUTF8Unsafe(const std::string& utf8) { + return FilePath(UTF8ToWide(utf8)); +} + +// static +FilePath FilePath::FromUTF16Unsafe(const string16& utf16) { + return FilePath(utf16); +} +#endif + +#if defined(OS_WIN) +// Windows specific implementation of file string comparisons + +int FilePath::CompareIgnoreCase(const StringType& string1, + const StringType& string2) { + // Perform character-wise upper case comparison rather than using the + // fully Unicode-aware CompareString(). For details see: + // http://blogs.msdn.com/michkap/archive/2005/10/17/481600.aspx + StringType::const_iterator i1 = string1.begin(); + StringType::const_iterator i2 = string2.begin(); + StringType::const_iterator string1end = string1.end(); + StringType::const_iterator string2end = string2.end(); + for ( ; i1 != string1end && i2 != string2end; ++i1, ++i2) { + wchar_t c1 = (wchar_t)LOWORD(::CharUpperW((LPWSTR)MAKELONG(*i1, 0))); + wchar_t c2 = (wchar_t)LOWORD(::CharUpperW((LPWSTR)MAKELONG(*i2, 0))); + if (c1 < c2) + return -1; + if (c1 > c2) + return 1; + } + if (i1 != string1end) + return 1; + if (i2 != string2end) + return -1; + return 0; +} + +#elif defined(OS_MACOSX) +// Mac OS X specific implementation of file string comparisons + +// cf. http://developer.apple.com/mac/library/technotes/tn/tn1150.html#UnicodeSubtleties +// +// "When using CreateTextEncoding to create a text encoding, you should set +// the TextEncodingBase to kTextEncodingUnicodeV2_0, set the +// TextEncodingVariant to kUnicodeCanonicalDecompVariant, and set the +// TextEncodingFormat to kUnicode16BitFormat. Using these values ensures that +// the Unicode will be in the same form as on an HFS Plus volume, even as the +// Unicode standard evolves." +// +// Another technical article for X 10.4 updates this: one should use +// the new (unambiguous) kUnicodeHFSPlusDecompVariant. +// cf. http://developer.apple.com/mac/library/releasenotes/TextFonts/RN-TEC/index.html +// +// This implementation uses CFStringGetFileSystemRepresentation() to get the +// decomposed form, and an adapted version of the FastUnicodeCompare as +// described in the tech note to compare the strings. + +// Character conversion table for FastUnicodeCompare() +// +// The lower case table consists of a 256-entry high-byte table followed by +// some number of 256-entry subtables. The high-byte table contains either an +// offset to the subtable for characters with that high byte or zero, which +// means that there are no case mappings or ignored characters in that block. +// Ignored characters are mapped to zero. +// +// cf. downloadable file linked in +// http://developer.apple.com/mac/library/technotes/tn/tn1150.html#StringComparisonAlgorithm + +namespace { + +const UInt16 lower_case_table[] = { + // High-byte indices ( == 0 iff no case mapping and no ignorables ) + + /* 0 */ 0x0100, 0x0200, 0x0000, 0x0300, 0x0400, 0x0500, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* 1 */ 0x0600, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* 2 */ 0x0700, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* 3 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* 4 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* 5 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* 6 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* 7 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* 8 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* 9 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* A */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* B */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* C */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* D */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* E */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* F */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0900, 0x0A00, + + // Table 1 (for high byte 0x00) + + /* 0 */ 0xFFFF, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, + /* 1 */ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, + /* 2 */ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, + /* 3 */ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, + /* 4 */ 0x0040, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, + /* 5 */ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, + /* 6 */ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, + /* 7 */ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F, + /* 8 */ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F, + /* 9 */ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F, + /* A */ 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, + 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + /* B */ 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, + 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, + /* C */ 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00E6, 0x00C7, + 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + /* D */ 0x00F0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, + 0x00F8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00FE, 0x00DF, + /* E */ 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, + 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + /* F */ 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, + 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF, + + // Table 2 (for high byte 0x01) + + /* 0 */ 0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107, + 0x0108, 0x0109, 0x010A, 0x010B, 0x010C, 0x010D, 0x010E, 0x010F, + /* 1 */ 0x0111, 0x0111, 0x0112, 0x0113, 0x0114, 0x0115, 0x0116, 0x0117, + 0x0118, 0x0119, 0x011A, 0x011B, 0x011C, 0x011D, 0x011E, 0x011F, + /* 2 */ 0x0120, 0x0121, 0x0122, 0x0123, 0x0124, 0x0125, 0x0127, 0x0127, + 0x0128, 0x0129, 0x012A, 0x012B, 0x012C, 0x012D, 0x012E, 0x012F, + /* 3 */ 0x0130, 0x0131, 0x0133, 0x0133, 0x0134, 0x0135, 0x0136, 0x0137, + 0x0138, 0x0139, 0x013A, 0x013B, 0x013C, 0x013D, 0x013E, 0x0140, + /* 4 */ 0x0140, 0x0142, 0x0142, 0x0143, 0x0144, 0x0145, 0x0146, 0x0147, + 0x0148, 0x0149, 0x014B, 0x014B, 0x014C, 0x014D, 0x014E, 0x014F, + /* 5 */ 0x0150, 0x0151, 0x0153, 0x0153, 0x0154, 0x0155, 0x0156, 0x0157, + 0x0158, 0x0159, 0x015A, 0x015B, 0x015C, 0x015D, 0x015E, 0x015F, + /* 6 */ 0x0160, 0x0161, 0x0162, 0x0163, 0x0164, 0x0165, 0x0167, 0x0167, + 0x0168, 0x0169, 0x016A, 0x016B, 0x016C, 0x016D, 0x016E, 0x016F, + /* 7 */ 0x0170, 0x0171, 0x0172, 0x0173, 0x0174, 0x0175, 0x0176, 0x0177, + 0x0178, 0x0179, 0x017A, 0x017B, 0x017C, 0x017D, 0x017E, 0x017F, + /* 8 */ 0x0180, 0x0253, 0x0183, 0x0183, 0x0185, 0x0185, 0x0254, 0x0188, + 0x0188, 0x0256, 0x0257, 0x018C, 0x018C, 0x018D, 0x01DD, 0x0259, + /* 9 */ 0x025B, 0x0192, 0x0192, 0x0260, 0x0263, 0x0195, 0x0269, 0x0268, + 0x0199, 0x0199, 0x019A, 0x019B, 0x026F, 0x0272, 0x019E, 0x0275, + /* A */ 0x01A0, 0x01A1, 0x01A3, 0x01A3, 0x01A5, 0x01A5, 0x01A6, 0x01A8, + 0x01A8, 0x0283, 0x01AA, 0x01AB, 0x01AD, 0x01AD, 0x0288, 0x01AF, + /* B */ 0x01B0, 0x028A, 0x028B, 0x01B4, 0x01B4, 0x01B6, 0x01B6, 0x0292, + 0x01B9, 0x01B9, 0x01BA, 0x01BB, 0x01BD, 0x01BD, 0x01BE, 0x01BF, + /* C */ 0x01C0, 0x01C1, 0x01C2, 0x01C3, 0x01C6, 0x01C6, 0x01C6, 0x01C9, + 0x01C9, 0x01C9, 0x01CC, 0x01CC, 0x01CC, 0x01CD, 0x01CE, 0x01CF, + /* D */ 0x01D0, 0x01D1, 0x01D2, 0x01D3, 0x01D4, 0x01D5, 0x01D6, 0x01D7, + 0x01D8, 0x01D9, 0x01DA, 0x01DB, 0x01DC, 0x01DD, 0x01DE, 0x01DF, + /* E */ 0x01E0, 0x01E1, 0x01E2, 0x01E3, 0x01E5, 0x01E5, 0x01E6, 0x01E7, + 0x01E8, 0x01E9, 0x01EA, 0x01EB, 0x01EC, 0x01ED, 0x01EE, 0x01EF, + /* F */ 0x01F0, 0x01F3, 0x01F3, 0x01F3, 0x01F4, 0x01F5, 0x01F6, 0x01F7, + 0x01F8, 0x01F9, 0x01FA, 0x01FB, 0x01FC, 0x01FD, 0x01FE, 0x01FF, + + // Table 3 (for high byte 0x03) + + /* 0 */ 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307, + 0x0308, 0x0309, 0x030A, 0x030B, 0x030C, 0x030D, 0x030E, 0x030F, + /* 1 */ 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317, + 0x0318, 0x0319, 0x031A, 0x031B, 0x031C, 0x031D, 0x031E, 0x031F, + /* 2 */ 0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327, + 0x0328, 0x0329, 0x032A, 0x032B, 0x032C, 0x032D, 0x032E, 0x032F, + /* 3 */ 0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337, + 0x0338, 0x0339, 0x033A, 0x033B, 0x033C, 0x033D, 0x033E, 0x033F, + /* 4 */ 0x0340, 0x0341, 0x0342, 0x0343, 0x0344, 0x0345, 0x0346, 0x0347, + 0x0348, 0x0349, 0x034A, 0x034B, 0x034C, 0x034D, 0x034E, 0x034F, + /* 5 */ 0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357, + 0x0358, 0x0359, 0x035A, 0x035B, 0x035C, 0x035D, 0x035E, 0x035F, + /* 6 */ 0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367, + 0x0368, 0x0369, 0x036A, 0x036B, 0x036C, 0x036D, 0x036E, 0x036F, + /* 7 */ 0x0370, 0x0371, 0x0372, 0x0373, 0x0374, 0x0375, 0x0376, 0x0377, + 0x0378, 0x0379, 0x037A, 0x037B, 0x037C, 0x037D, 0x037E, 0x037F, + /* 8 */ 0x0380, 0x0381, 0x0382, 0x0383, 0x0384, 0x0385, 0x0386, 0x0387, + 0x0388, 0x0389, 0x038A, 0x038B, 0x038C, 0x038D, 0x038E, 0x038F, + /* 9 */ 0x0390, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, + 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, + /* A */ 0x03C0, 0x03C1, 0x03A2, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7, + 0x03C8, 0x03C9, 0x03AA, 0x03AB, 0x03AC, 0x03AD, 0x03AE, 0x03AF, + /* B */ 0x03B0, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, + 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, + /* C */ 0x03C0, 0x03C1, 0x03C2, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7, + 0x03C8, 0x03C9, 0x03CA, 0x03CB, 0x03CC, 0x03CD, 0x03CE, 0x03CF, + /* D */ 0x03D0, 0x03D1, 0x03D2, 0x03D3, 0x03D4, 0x03D5, 0x03D6, 0x03D7, + 0x03D8, 0x03D9, 0x03DA, 0x03DB, 0x03DC, 0x03DD, 0x03DE, 0x03DF, + /* E */ 0x03E0, 0x03E1, 0x03E3, 0x03E3, 0x03E5, 0x03E5, 0x03E7, 0x03E7, + 0x03E9, 0x03E9, 0x03EB, 0x03EB, 0x03ED, 0x03ED, 0x03EF, 0x03EF, + /* F */ 0x03F0, 0x03F1, 0x03F2, 0x03F3, 0x03F4, 0x03F5, 0x03F6, 0x03F7, + 0x03F8, 0x03F9, 0x03FA, 0x03FB, 0x03FC, 0x03FD, 0x03FE, 0x03FF, + + // Table 4 (for high byte 0x04) + + /* 0 */ 0x0400, 0x0401, 0x0452, 0x0403, 0x0454, 0x0455, 0x0456, 0x0407, + 0x0458, 0x0459, 0x045A, 0x045B, 0x040C, 0x040D, 0x040E, 0x045F, + /* 1 */ 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0419, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, + /* 2 */ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F, + /* 3 */ 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, + /* 4 */ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F, + /* 5 */ 0x0450, 0x0451, 0x0452, 0x0453, 0x0454, 0x0455, 0x0456, 0x0457, + 0x0458, 0x0459, 0x045A, 0x045B, 0x045C, 0x045D, 0x045E, 0x045F, + /* 6 */ 0x0461, 0x0461, 0x0463, 0x0463, 0x0465, 0x0465, 0x0467, 0x0467, + 0x0469, 0x0469, 0x046B, 0x046B, 0x046D, 0x046D, 0x046F, 0x046F, + /* 7 */ 0x0471, 0x0471, 0x0473, 0x0473, 0x0475, 0x0475, 0x0476, 0x0477, + 0x0479, 0x0479, 0x047B, 0x047B, 0x047D, 0x047D, 0x047F, 0x047F, + /* 8 */ 0x0481, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, + 0x0488, 0x0489, 0x048A, 0x048B, 0x048C, 0x048D, 0x048E, 0x048F, + /* 9 */ 0x0491, 0x0491, 0x0493, 0x0493, 0x0495, 0x0495, 0x0497, 0x0497, + 0x0499, 0x0499, 0x049B, 0x049B, 0x049D, 0x049D, 0x049F, 0x049F, + /* A */ 0x04A1, 0x04A1, 0x04A3, 0x04A3, 0x04A5, 0x04A5, 0x04A7, 0x04A7, + 0x04A9, 0x04A9, 0x04AB, 0x04AB, 0x04AD, 0x04AD, 0x04AF, 0x04AF, + /* B */ 0x04B1, 0x04B1, 0x04B3, 0x04B3, 0x04B5, 0x04B5, 0x04B7, 0x04B7, + 0x04B9, 0x04B9, 0x04BB, 0x04BB, 0x04BD, 0x04BD, 0x04BF, 0x04BF, + /* C */ 0x04C0, 0x04C1, 0x04C2, 0x04C4, 0x04C4, 0x04C5, 0x04C6, 0x04C8, + 0x04C8, 0x04C9, 0x04CA, 0x04CC, 0x04CC, 0x04CD, 0x04CE, 0x04CF, + /* D */ 0x04D0, 0x04D1, 0x04D2, 0x04D3, 0x04D4, 0x04D5, 0x04D6, 0x04D7, + 0x04D8, 0x04D9, 0x04DA, 0x04DB, 0x04DC, 0x04DD, 0x04DE, 0x04DF, + /* E */ 0x04E0, 0x04E1, 0x04E2, 0x04E3, 0x04E4, 0x04E5, 0x04E6, 0x04E7, + 0x04E8, 0x04E9, 0x04EA, 0x04EB, 0x04EC, 0x04ED, 0x04EE, 0x04EF, + /* F */ 0x04F0, 0x04F1, 0x04F2, 0x04F3, 0x04F4, 0x04F5, 0x04F6, 0x04F7, + 0x04F8, 0x04F9, 0x04FA, 0x04FB, 0x04FC, 0x04FD, 0x04FE, 0x04FF, + + // Table 5 (for high byte 0x05) + + /* 0 */ 0x0500, 0x0501, 0x0502, 0x0503, 0x0504, 0x0505, 0x0506, 0x0507, + 0x0508, 0x0509, 0x050A, 0x050B, 0x050C, 0x050D, 0x050E, 0x050F, + /* 1 */ 0x0510, 0x0511, 0x0512, 0x0513, 0x0514, 0x0515, 0x0516, 0x0517, + 0x0518, 0x0519, 0x051A, 0x051B, 0x051C, 0x051D, 0x051E, 0x051F, + /* 2 */ 0x0520, 0x0521, 0x0522, 0x0523, 0x0524, 0x0525, 0x0526, 0x0527, + 0x0528, 0x0529, 0x052A, 0x052B, 0x052C, 0x052D, 0x052E, 0x052F, + /* 3 */ 0x0530, 0x0561, 0x0562, 0x0563, 0x0564, 0x0565, 0x0566, 0x0567, + 0x0568, 0x0569, 0x056A, 0x056B, 0x056C, 0x056D, 0x056E, 0x056F, + /* 4 */ 0x0570, 0x0571, 0x0572, 0x0573, 0x0574, 0x0575, 0x0576, 0x0577, + 0x0578, 0x0579, 0x057A, 0x057B, 0x057C, 0x057D, 0x057E, 0x057F, + /* 5 */ 0x0580, 0x0581, 0x0582, 0x0583, 0x0584, 0x0585, 0x0586, 0x0557, + 0x0558, 0x0559, 0x055A, 0x055B, 0x055C, 0x055D, 0x055E, 0x055F, + /* 6 */ 0x0560, 0x0561, 0x0562, 0x0563, 0x0564, 0x0565, 0x0566, 0x0567, + 0x0568, 0x0569, 0x056A, 0x056B, 0x056C, 0x056D, 0x056E, 0x056F, + /* 7 */ 0x0570, 0x0571, 0x0572, 0x0573, 0x0574, 0x0575, 0x0576, 0x0577, + 0x0578, 0x0579, 0x057A, 0x057B, 0x057C, 0x057D, 0x057E, 0x057F, + /* 8 */ 0x0580, 0x0581, 0x0582, 0x0583, 0x0584, 0x0585, 0x0586, 0x0587, + 0x0588, 0x0589, 0x058A, 0x058B, 0x058C, 0x058D, 0x058E, 0x058F, + /* 9 */ 0x0590, 0x0591, 0x0592, 0x0593, 0x0594, 0x0595, 0x0596, 0x0597, + 0x0598, 0x0599, 0x059A, 0x059B, 0x059C, 0x059D, 0x059E, 0x059F, + /* A */ 0x05A0, 0x05A1, 0x05A2, 0x05A3, 0x05A4, 0x05A5, 0x05A6, 0x05A7, + 0x05A8, 0x05A9, 0x05AA, 0x05AB, 0x05AC, 0x05AD, 0x05AE, 0x05AF, + /* B */ 0x05B0, 0x05B1, 0x05B2, 0x05B3, 0x05B4, 0x05B5, 0x05B6, 0x05B7, + 0x05B8, 0x05B9, 0x05BA, 0x05BB, 0x05BC, 0x05BD, 0x05BE, 0x05BF, + /* C */ 0x05C0, 0x05C1, 0x05C2, 0x05C3, 0x05C4, 0x05C5, 0x05C6, 0x05C7, + 0x05C8, 0x05C9, 0x05CA, 0x05CB, 0x05CC, 0x05CD, 0x05CE, 0x05CF, + /* D */ 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, + 0x05D8, 0x05D9, 0x05DA, 0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF, + /* E */ 0x05E0, 0x05E1, 0x05E2, 0x05E3, 0x05E4, 0x05E5, 0x05E6, 0x05E7, + 0x05E8, 0x05E9, 0x05EA, 0x05EB, 0x05EC, 0x05ED, 0x05EE, 0x05EF, + /* F */ 0x05F0, 0x05F1, 0x05F2, 0x05F3, 0x05F4, 0x05F5, 0x05F6, 0x05F7, + 0x05F8, 0x05F9, 0x05FA, 0x05FB, 0x05FC, 0x05FD, 0x05FE, 0x05FF, + + // Table 6 (for high byte 0x10) + + /* 0 */ 0x1000, 0x1001, 0x1002, 0x1003, 0x1004, 0x1005, 0x1006, 0x1007, + 0x1008, 0x1009, 0x100A, 0x100B, 0x100C, 0x100D, 0x100E, 0x100F, + /* 1 */ 0x1010, 0x1011, 0x1012, 0x1013, 0x1014, 0x1015, 0x1016, 0x1017, + 0x1018, 0x1019, 0x101A, 0x101B, 0x101C, 0x101D, 0x101E, 0x101F, + /* 2 */ 0x1020, 0x1021, 0x1022, 0x1023, 0x1024, 0x1025, 0x1026, 0x1027, + 0x1028, 0x1029, 0x102A, 0x102B, 0x102C, 0x102D, 0x102E, 0x102F, + /* 3 */ 0x1030, 0x1031, 0x1032, 0x1033, 0x1034, 0x1035, 0x1036, 0x1037, + 0x1038, 0x1039, 0x103A, 0x103B, 0x103C, 0x103D, 0x103E, 0x103F, + /* 4 */ 0x1040, 0x1041, 0x1042, 0x1043, 0x1044, 0x1045, 0x1046, 0x1047, + 0x1048, 0x1049, 0x104A, 0x104B, 0x104C, 0x104D, 0x104E, 0x104F, + /* 5 */ 0x1050, 0x1051, 0x1052, 0x1053, 0x1054, 0x1055, 0x1056, 0x1057, + 0x1058, 0x1059, 0x105A, 0x105B, 0x105C, 0x105D, 0x105E, 0x105F, + /* 6 */ 0x1060, 0x1061, 0x1062, 0x1063, 0x1064, 0x1065, 0x1066, 0x1067, + 0x1068, 0x1069, 0x106A, 0x106B, 0x106C, 0x106D, 0x106E, 0x106F, + /* 7 */ 0x1070, 0x1071, 0x1072, 0x1073, 0x1074, 0x1075, 0x1076, 0x1077, + 0x1078, 0x1079, 0x107A, 0x107B, 0x107C, 0x107D, 0x107E, 0x107F, + /* 8 */ 0x1080, 0x1081, 0x1082, 0x1083, 0x1084, 0x1085, 0x1086, 0x1087, + 0x1088, 0x1089, 0x108A, 0x108B, 0x108C, 0x108D, 0x108E, 0x108F, + /* 9 */ 0x1090, 0x1091, 0x1092, 0x1093, 0x1094, 0x1095, 0x1096, 0x1097, + 0x1098, 0x1099, 0x109A, 0x109B, 0x109C, 0x109D, 0x109E, 0x109F, + /* A */ 0x10D0, 0x10D1, 0x10D2, 0x10D3, 0x10D4, 0x10D5, 0x10D6, 0x10D7, + 0x10D8, 0x10D9, 0x10DA, 0x10DB, 0x10DC, 0x10DD, 0x10DE, 0x10DF, + /* B */ 0x10E0, 0x10E1, 0x10E2, 0x10E3, 0x10E4, 0x10E5, 0x10E6, 0x10E7, + 0x10E8, 0x10E9, 0x10EA, 0x10EB, 0x10EC, 0x10ED, 0x10EE, 0x10EF, + /* C */ 0x10F0, 0x10F1, 0x10F2, 0x10F3, 0x10F4, 0x10F5, 0x10C6, 0x10C7, + 0x10C8, 0x10C9, 0x10CA, 0x10CB, 0x10CC, 0x10CD, 0x10CE, 0x10CF, + /* D */ 0x10D0, 0x10D1, 0x10D2, 0x10D3, 0x10D4, 0x10D5, 0x10D6, 0x10D7, + 0x10D8, 0x10D9, 0x10DA, 0x10DB, 0x10DC, 0x10DD, 0x10DE, 0x10DF, + /* E */ 0x10E0, 0x10E1, 0x10E2, 0x10E3, 0x10E4, 0x10E5, 0x10E6, 0x10E7, + 0x10E8, 0x10E9, 0x10EA, 0x10EB, 0x10EC, 0x10ED, 0x10EE, 0x10EF, + /* F */ 0x10F0, 0x10F1, 0x10F2, 0x10F3, 0x10F4, 0x10F5, 0x10F6, 0x10F7, + 0x10F8, 0x10F9, 0x10FA, 0x10FB, 0x10FC, 0x10FD, 0x10FE, 0x10FF, + + // Table 7 (for high byte 0x20) + + /* 0 */ 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, + 0x2008, 0x2009, 0x200A, 0x200B, 0x0000, 0x0000, 0x0000, 0x0000, + /* 1 */ 0x2010, 0x2011, 0x2012, 0x2013, 0x2014, 0x2015, 0x2016, 0x2017, + 0x2018, 0x2019, 0x201A, 0x201B, 0x201C, 0x201D, 0x201E, 0x201F, + /* 2 */ 0x2020, 0x2021, 0x2022, 0x2023, 0x2024, 0x2025, 0x2026, 0x2027, + 0x2028, 0x2029, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x202F, + /* 3 */ 0x2030, 0x2031, 0x2032, 0x2033, 0x2034, 0x2035, 0x2036, 0x2037, + 0x2038, 0x2039, 0x203A, 0x203B, 0x203C, 0x203D, 0x203E, 0x203F, + /* 4 */ 0x2040, 0x2041, 0x2042, 0x2043, 0x2044, 0x2045, 0x2046, 0x2047, + 0x2048, 0x2049, 0x204A, 0x204B, 0x204C, 0x204D, 0x204E, 0x204F, + /* 5 */ 0x2050, 0x2051, 0x2052, 0x2053, 0x2054, 0x2055, 0x2056, 0x2057, + 0x2058, 0x2059, 0x205A, 0x205B, 0x205C, 0x205D, 0x205E, 0x205F, + /* 6 */ 0x2060, 0x2061, 0x2062, 0x2063, 0x2064, 0x2065, 0x2066, 0x2067, + 0x2068, 0x2069, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* 7 */ 0x2070, 0x2071, 0x2072, 0x2073, 0x2074, 0x2075, 0x2076, 0x2077, + 0x2078, 0x2079, 0x207A, 0x207B, 0x207C, 0x207D, 0x207E, 0x207F, + /* 8 */ 0x2080, 0x2081, 0x2082, 0x2083, 0x2084, 0x2085, 0x2086, 0x2087, + 0x2088, 0x2089, 0x208A, 0x208B, 0x208C, 0x208D, 0x208E, 0x208F, + /* 9 */ 0x2090, 0x2091, 0x2092, 0x2093, 0x2094, 0x2095, 0x2096, 0x2097, + 0x2098, 0x2099, 0x209A, 0x209B, 0x209C, 0x209D, 0x209E, 0x209F, + /* A */ 0x20A0, 0x20A1, 0x20A2, 0x20A3, 0x20A4, 0x20A5, 0x20A6, 0x20A7, + 0x20A8, 0x20A9, 0x20AA, 0x20AB, 0x20AC, 0x20AD, 0x20AE, 0x20AF, + /* B */ 0x20B0, 0x20B1, 0x20B2, 0x20B3, 0x20B4, 0x20B5, 0x20B6, 0x20B7, + 0x20B8, 0x20B9, 0x20BA, 0x20BB, 0x20BC, 0x20BD, 0x20BE, 0x20BF, + /* C */ 0x20C0, 0x20C1, 0x20C2, 0x20C3, 0x20C4, 0x20C5, 0x20C6, 0x20C7, + 0x20C8, 0x20C9, 0x20CA, 0x20CB, 0x20CC, 0x20CD, 0x20CE, 0x20CF, + /* D */ 0x20D0, 0x20D1, 0x20D2, 0x20D3, 0x20D4, 0x20D5, 0x20D6, 0x20D7, + 0x20D8, 0x20D9, 0x20DA, 0x20DB, 0x20DC, 0x20DD, 0x20DE, 0x20DF, + /* E */ 0x20E0, 0x20E1, 0x20E2, 0x20E3, 0x20E4, 0x20E5, 0x20E6, 0x20E7, + 0x20E8, 0x20E9, 0x20EA, 0x20EB, 0x20EC, 0x20ED, 0x20EE, 0x20EF, + /* F */ 0x20F0, 0x20F1, 0x20F2, 0x20F3, 0x20F4, 0x20F5, 0x20F6, 0x20F7, + 0x20F8, 0x20F9, 0x20FA, 0x20FB, 0x20FC, 0x20FD, 0x20FE, 0x20FF, + + // Table 8 (for high byte 0x21) + + /* 0 */ 0x2100, 0x2101, 0x2102, 0x2103, 0x2104, 0x2105, 0x2106, 0x2107, + 0x2108, 0x2109, 0x210A, 0x210B, 0x210C, 0x210D, 0x210E, 0x210F, + /* 1 */ 0x2110, 0x2111, 0x2112, 0x2113, 0x2114, 0x2115, 0x2116, 0x2117, + 0x2118, 0x2119, 0x211A, 0x211B, 0x211C, 0x211D, 0x211E, 0x211F, + /* 2 */ 0x2120, 0x2121, 0x2122, 0x2123, 0x2124, 0x2125, 0x2126, 0x2127, + 0x2128, 0x2129, 0x212A, 0x212B, 0x212C, 0x212D, 0x212E, 0x212F, + /* 3 */ 0x2130, 0x2131, 0x2132, 0x2133, 0x2134, 0x2135, 0x2136, 0x2137, + 0x2138, 0x2139, 0x213A, 0x213B, 0x213C, 0x213D, 0x213E, 0x213F, + /* 4 */ 0x2140, 0x2141, 0x2142, 0x2143, 0x2144, 0x2145, 0x2146, 0x2147, + 0x2148, 0x2149, 0x214A, 0x214B, 0x214C, 0x214D, 0x214E, 0x214F, + /* 5 */ 0x2150, 0x2151, 0x2152, 0x2153, 0x2154, 0x2155, 0x2156, 0x2157, + 0x2158, 0x2159, 0x215A, 0x215B, 0x215C, 0x215D, 0x215E, 0x215F, + /* 6 */ 0x2170, 0x2171, 0x2172, 0x2173, 0x2174, 0x2175, 0x2176, 0x2177, + 0x2178, 0x2179, 0x217A, 0x217B, 0x217C, 0x217D, 0x217E, 0x217F, + /* 7 */ 0x2170, 0x2171, 0x2172, 0x2173, 0x2174, 0x2175, 0x2176, 0x2177, + 0x2178, 0x2179, 0x217A, 0x217B, 0x217C, 0x217D, 0x217E, 0x217F, + /* 8 */ 0x2180, 0x2181, 0x2182, 0x2183, 0x2184, 0x2185, 0x2186, 0x2187, + 0x2188, 0x2189, 0x218A, 0x218B, 0x218C, 0x218D, 0x218E, 0x218F, + /* 9 */ 0x2190, 0x2191, 0x2192, 0x2193, 0x2194, 0x2195, 0x2196, 0x2197, + 0x2198, 0x2199, 0x219A, 0x219B, 0x219C, 0x219D, 0x219E, 0x219F, + /* A */ 0x21A0, 0x21A1, 0x21A2, 0x21A3, 0x21A4, 0x21A5, 0x21A6, 0x21A7, + 0x21A8, 0x21A9, 0x21AA, 0x21AB, 0x21AC, 0x21AD, 0x21AE, 0x21AF, + /* B */ 0x21B0, 0x21B1, 0x21B2, 0x21B3, 0x21B4, 0x21B5, 0x21B6, 0x21B7, + 0x21B8, 0x21B9, 0x21BA, 0x21BB, 0x21BC, 0x21BD, 0x21BE, 0x21BF, + /* C */ 0x21C0, 0x21C1, 0x21C2, 0x21C3, 0x21C4, 0x21C5, 0x21C6, 0x21C7, + 0x21C8, 0x21C9, 0x21CA, 0x21CB, 0x21CC, 0x21CD, 0x21CE, 0x21CF, + /* D */ 0x21D0, 0x21D1, 0x21D2, 0x21D3, 0x21D4, 0x21D5, 0x21D6, 0x21D7, + 0x21D8, 0x21D9, 0x21DA, 0x21DB, 0x21DC, 0x21DD, 0x21DE, 0x21DF, + /* E */ 0x21E0, 0x21E1, 0x21E2, 0x21E3, 0x21E4, 0x21E5, 0x21E6, 0x21E7, + 0x21E8, 0x21E9, 0x21EA, 0x21EB, 0x21EC, 0x21ED, 0x21EE, 0x21EF, + /* F */ 0x21F0, 0x21F1, 0x21F2, 0x21F3, 0x21F4, 0x21F5, 0x21F6, 0x21F7, + 0x21F8, 0x21F9, 0x21FA, 0x21FB, 0x21FC, 0x21FD, 0x21FE, 0x21FF, + + // Table 9 (for high byte 0xFE) + + /* 0 */ 0xFE00, 0xFE01, 0xFE02, 0xFE03, 0xFE04, 0xFE05, 0xFE06, 0xFE07, + 0xFE08, 0xFE09, 0xFE0A, 0xFE0B, 0xFE0C, 0xFE0D, 0xFE0E, 0xFE0F, + /* 1 */ 0xFE10, 0xFE11, 0xFE12, 0xFE13, 0xFE14, 0xFE15, 0xFE16, 0xFE17, + 0xFE18, 0xFE19, 0xFE1A, 0xFE1B, 0xFE1C, 0xFE1D, 0xFE1E, 0xFE1F, + /* 2 */ 0xFE20, 0xFE21, 0xFE22, 0xFE23, 0xFE24, 0xFE25, 0xFE26, 0xFE27, + 0xFE28, 0xFE29, 0xFE2A, 0xFE2B, 0xFE2C, 0xFE2D, 0xFE2E, 0xFE2F, + /* 3 */ 0xFE30, 0xFE31, 0xFE32, 0xFE33, 0xFE34, 0xFE35, 0xFE36, 0xFE37, + 0xFE38, 0xFE39, 0xFE3A, 0xFE3B, 0xFE3C, 0xFE3D, 0xFE3E, 0xFE3F, + /* 4 */ 0xFE40, 0xFE41, 0xFE42, 0xFE43, 0xFE44, 0xFE45, 0xFE46, 0xFE47, + 0xFE48, 0xFE49, 0xFE4A, 0xFE4B, 0xFE4C, 0xFE4D, 0xFE4E, 0xFE4F, + /* 5 */ 0xFE50, 0xFE51, 0xFE52, 0xFE53, 0xFE54, 0xFE55, 0xFE56, 0xFE57, + 0xFE58, 0xFE59, 0xFE5A, 0xFE5B, 0xFE5C, 0xFE5D, 0xFE5E, 0xFE5F, + /* 6 */ 0xFE60, 0xFE61, 0xFE62, 0xFE63, 0xFE64, 0xFE65, 0xFE66, 0xFE67, + 0xFE68, 0xFE69, 0xFE6A, 0xFE6B, 0xFE6C, 0xFE6D, 0xFE6E, 0xFE6F, + /* 7 */ 0xFE70, 0xFE71, 0xFE72, 0xFE73, 0xFE74, 0xFE75, 0xFE76, 0xFE77, + 0xFE78, 0xFE79, 0xFE7A, 0xFE7B, 0xFE7C, 0xFE7D, 0xFE7E, 0xFE7F, + /* 8 */ 0xFE80, 0xFE81, 0xFE82, 0xFE83, 0xFE84, 0xFE85, 0xFE86, 0xFE87, + 0xFE88, 0xFE89, 0xFE8A, 0xFE8B, 0xFE8C, 0xFE8D, 0xFE8E, 0xFE8F, + /* 9 */ 0xFE90, 0xFE91, 0xFE92, 0xFE93, 0xFE94, 0xFE95, 0xFE96, 0xFE97, + 0xFE98, 0xFE99, 0xFE9A, 0xFE9B, 0xFE9C, 0xFE9D, 0xFE9E, 0xFE9F, + /* A */ 0xFEA0, 0xFEA1, 0xFEA2, 0xFEA3, 0xFEA4, 0xFEA5, 0xFEA6, 0xFEA7, + 0xFEA8, 0xFEA9, 0xFEAA, 0xFEAB, 0xFEAC, 0xFEAD, 0xFEAE, 0xFEAF, + /* B */ 0xFEB0, 0xFEB1, 0xFEB2, 0xFEB3, 0xFEB4, 0xFEB5, 0xFEB6, 0xFEB7, + 0xFEB8, 0xFEB9, 0xFEBA, 0xFEBB, 0xFEBC, 0xFEBD, 0xFEBE, 0xFEBF, + /* C */ 0xFEC0, 0xFEC1, 0xFEC2, 0xFEC3, 0xFEC4, 0xFEC5, 0xFEC6, 0xFEC7, + 0xFEC8, 0xFEC9, 0xFECA, 0xFECB, 0xFECC, 0xFECD, 0xFECE, 0xFECF, + /* D */ 0xFED0, 0xFED1, 0xFED2, 0xFED3, 0xFED4, 0xFED5, 0xFED6, 0xFED7, + 0xFED8, 0xFED9, 0xFEDA, 0xFEDB, 0xFEDC, 0xFEDD, 0xFEDE, 0xFEDF, + /* E */ 0xFEE0, 0xFEE1, 0xFEE2, 0xFEE3, 0xFEE4, 0xFEE5, 0xFEE6, 0xFEE7, + 0xFEE8, 0xFEE9, 0xFEEA, 0xFEEB, 0xFEEC, 0xFEED, 0xFEEE, 0xFEEF, + /* F */ 0xFEF0, 0xFEF1, 0xFEF2, 0xFEF3, 0xFEF4, 0xFEF5, 0xFEF6, 0xFEF7, + 0xFEF8, 0xFEF9, 0xFEFA, 0xFEFB, 0xFEFC, 0xFEFD, 0xFEFE, 0x0000, + + // Table 10 (for high byte 0xFF) + + /* 0 */ 0xFF00, 0xFF01, 0xFF02, 0xFF03, 0xFF04, 0xFF05, 0xFF06, 0xFF07, + 0xFF08, 0xFF09, 0xFF0A, 0xFF0B, 0xFF0C, 0xFF0D, 0xFF0E, 0xFF0F, + /* 1 */ 0xFF10, 0xFF11, 0xFF12, 0xFF13, 0xFF14, 0xFF15, 0xFF16, 0xFF17, + 0xFF18, 0xFF19, 0xFF1A, 0xFF1B, 0xFF1C, 0xFF1D, 0xFF1E, 0xFF1F, + /* 2 */ 0xFF20, 0xFF41, 0xFF42, 0xFF43, 0xFF44, 0xFF45, 0xFF46, 0xFF47, + 0xFF48, 0xFF49, 0xFF4A, 0xFF4B, 0xFF4C, 0xFF4D, 0xFF4E, 0xFF4F, + /* 3 */ 0xFF50, 0xFF51, 0xFF52, 0xFF53, 0xFF54, 0xFF55, 0xFF56, 0xFF57, + 0xFF58, 0xFF59, 0xFF5A, 0xFF3B, 0xFF3C, 0xFF3D, 0xFF3E, 0xFF3F, + /* 4 */ 0xFF40, 0xFF41, 0xFF42, 0xFF43, 0xFF44, 0xFF45, 0xFF46, 0xFF47, + 0xFF48, 0xFF49, 0xFF4A, 0xFF4B, 0xFF4C, 0xFF4D, 0xFF4E, 0xFF4F, + /* 5 */ 0xFF50, 0xFF51, 0xFF52, 0xFF53, 0xFF54, 0xFF55, 0xFF56, 0xFF57, + 0xFF58, 0xFF59, 0xFF5A, 0xFF5B, 0xFF5C, 0xFF5D, 0xFF5E, 0xFF5F, + /* 6 */ 0xFF60, 0xFF61, 0xFF62, 0xFF63, 0xFF64, 0xFF65, 0xFF66, 0xFF67, + 0xFF68, 0xFF69, 0xFF6A, 0xFF6B, 0xFF6C, 0xFF6D, 0xFF6E, 0xFF6F, + /* 7 */ 0xFF70, 0xFF71, 0xFF72, 0xFF73, 0xFF74, 0xFF75, 0xFF76, 0xFF77, + 0xFF78, 0xFF79, 0xFF7A, 0xFF7B, 0xFF7C, 0xFF7D, 0xFF7E, 0xFF7F, + /* 8 */ 0xFF80, 0xFF81, 0xFF82, 0xFF83, 0xFF84, 0xFF85, 0xFF86, 0xFF87, + 0xFF88, 0xFF89, 0xFF8A, 0xFF8B, 0xFF8C, 0xFF8D, 0xFF8E, 0xFF8F, + /* 9 */ 0xFF90, 0xFF91, 0xFF92, 0xFF93, 0xFF94, 0xFF95, 0xFF96, 0xFF97, + 0xFF98, 0xFF99, 0xFF9A, 0xFF9B, 0xFF9C, 0xFF9D, 0xFF9E, 0xFF9F, + /* A */ 0xFFA0, 0xFFA1, 0xFFA2, 0xFFA3, 0xFFA4, 0xFFA5, 0xFFA6, 0xFFA7, + 0xFFA8, 0xFFA9, 0xFFAA, 0xFFAB, 0xFFAC, 0xFFAD, 0xFFAE, 0xFFAF, + /* B */ 0xFFB0, 0xFFB1, 0xFFB2, 0xFFB3, 0xFFB4, 0xFFB5, 0xFFB6, 0xFFB7, + 0xFFB8, 0xFFB9, 0xFFBA, 0xFFBB, 0xFFBC, 0xFFBD, 0xFFBE, 0xFFBF, + /* C */ 0xFFC0, 0xFFC1, 0xFFC2, 0xFFC3, 0xFFC4, 0xFFC5, 0xFFC6, 0xFFC7, + 0xFFC8, 0xFFC9, 0xFFCA, 0xFFCB, 0xFFCC, 0xFFCD, 0xFFCE, 0xFFCF, + /* D */ 0xFFD0, 0xFFD1, 0xFFD2, 0xFFD3, 0xFFD4, 0xFFD5, 0xFFD6, 0xFFD7, + 0xFFD8, 0xFFD9, 0xFFDA, 0xFFDB, 0xFFDC, 0xFFDD, 0xFFDE, 0xFFDF, + /* E */ 0xFFE0, 0xFFE1, 0xFFE2, 0xFFE3, 0xFFE4, 0xFFE5, 0xFFE6, 0xFFE7, + 0xFFE8, 0xFFE9, 0xFFEA, 0xFFEB, 0xFFEC, 0xFFED, 0xFFEE, 0xFFEF, + /* F */ 0xFFF0, 0xFFF1, 0xFFF2, 0xFFF3, 0xFFF4, 0xFFF5, 0xFFF6, 0xFFF7, + 0xFFF8, 0xFFF9, 0xFFFA, 0xFFFB, 0xFFFC, 0xFFFD, 0xFFFE, 0xFFFF, +}; + +// Returns the next non-ignorable codepoint within string starting from the +// position indicated by index, or zero if there are no more. +// The passed-in index is automatically advanced as the characters in the input +// HFS-decomposed UTF-8 strings are read. +inline int HFSReadNextNonIgnorableCodepoint(const char* string, + int length, + int* index) { + int codepoint = 0; + while (*index < length && codepoint == 0) { + // CBU8_NEXT returns a value < 0 in error cases. For purposes of string + // comparison, we just use that value and flag it with DCHECK. + CBU8_NEXT(string, *index, length, codepoint); + DCHECK_GT(codepoint, 0); + if (codepoint > 0) { + // Check if there is a subtable for this upper byte. + int lookup_offset = lower_case_table[codepoint >> 8]; + if (lookup_offset != 0) + codepoint = lower_case_table[lookup_offset + (codepoint & 0x00FF)]; + // Note: codepoint1 may be again 0 at this point if the character was + // an ignorable. + } + } + return codepoint; +} + +} // anonymous namespace + +// Special UTF-8 version of FastUnicodeCompare. Cf: +// http://developer.apple.com/mac/library/technotes/tn/tn1150.html#StringComparisonAlgorithm +// The input strings must be in the special HFS decomposed form. +int FilePath::HFSFastUnicodeCompare(const StringType& string1, + const StringType& string2) { + int length1 = string1.length(); + int length2 = string2.length(); + int index1 = 0; + int index2 = 0; + + for (;;) { + int codepoint1 = HFSReadNextNonIgnorableCodepoint(string1.c_str(), + length1, + &index1); + int codepoint2 = HFSReadNextNonIgnorableCodepoint(string2.c_str(), + length2, + &index2); + if (codepoint1 != codepoint2) + return (codepoint1 < codepoint2) ? -1 : 1; + if (codepoint1 == 0) { + DCHECK_EQ(index1, length1); + DCHECK_EQ(index2, length2); + return 0; + } + } +} + +StringType FilePath::GetHFSDecomposedForm(const StringType& string) { + ScopedCFTypeRef cfstring( + CFStringCreateWithBytesNoCopy( + NULL, + reinterpret_cast(string.c_str()), + string.length(), + kCFStringEncodingUTF8, + false, + kCFAllocatorNull)); + // Query the maximum length needed to store the result. In most cases this + // will overestimate the required space. The return value also already + // includes the space needed for a terminating 0. + CFIndex length = CFStringGetMaximumSizeOfFileSystemRepresentation(cfstring); + DCHECK_GT(length, 0); // should be at least 1 for the 0-terminator. + // Reserve enough space for CFStringGetFileSystemRepresentation to write into. + // Also set the length to the maximum so that we can shrink it later. + // (Increasing rather than decreasing it would clobber the string contents!) + StringType result; + result.reserve(length); + result.resize(length - 1); + Boolean success = CFStringGetFileSystemRepresentation(cfstring, + &result[0], + length); + if (success) { + // Reduce result.length() to actual string length. + result.resize(strlen(result.c_str())); + } else { + // An error occurred -> clear result. + result.clear(); + } + return result; +} + +int FilePath::CompareIgnoreCase(const StringType& string1, + const StringType& string2) { + // Quick checks for empty strings - these speed things up a bit and make the + // following code cleaner. + if (string1.empty()) + return string2.empty() ? 0 : -1; + if (string2.empty()) + return 1; + + StringType hfs1 = GetHFSDecomposedForm(string1); + StringType hfs2 = GetHFSDecomposedForm(string2); + + // GetHFSDecomposedForm() returns an empty string in an error case. + if (hfs1.empty() || hfs2.empty()) { + NOTREACHED(); + ScopedCFTypeRef cfstring1( + CFStringCreateWithBytesNoCopy( + NULL, + reinterpret_cast(string1.c_str()), + string1.length(), + kCFStringEncodingUTF8, + false, + kCFAllocatorNull)); + ScopedCFTypeRef cfstring2( + CFStringCreateWithBytesNoCopy( + NULL, + reinterpret_cast(string2.c_str()), + string2.length(), + kCFStringEncodingUTF8, + false, + kCFAllocatorNull)); + return CFStringCompare(cfstring1, + cfstring2, + kCFCompareCaseInsensitive); + } + + return HFSFastUnicodeCompare(hfs1, hfs2); +} + +#else // << WIN. MACOSX | other (POSIX) >> + +// Generic (POSIX) implementation of file string comparison. +// TODO(rolandsteiner) check if this is sufficient/correct. +int FilePath::CompareIgnoreCase(const StringType& string1, + const StringType& string2) { + int comparison = strcasecmp(string1.c_str(), string2.c_str()); + if (comparison < 0) + return -1; + if (comparison > 0) + return 1; + return 0; +} + +#endif // OS versions of CompareIgnoreCase() + + +void FilePath::StripTrailingSeparatorsInternal() { + // If there is no drive letter, start will be 1, which will prevent stripping + // the leading separator if there is only one separator. If there is a drive + // letter, start will be set appropriately to prevent stripping the first + // separator following the drive letter, if a separator immediately follows + // the drive letter. + StringType::size_type start = FindDriveLetter(path_) + 2; + + StringType::size_type last_stripped = StringType::npos; + for (StringType::size_type pos = path_.length(); + pos > start && IsSeparator(path_[pos - 1]); + --pos) { + // If the string only has two separators and they're at the beginning, + // don't strip them, unless the string began with more than two separators. + if (pos != start + 1 || last_stripped == start + 2 || + !IsSeparator(path_[start - 1])) { + path_.resize(pos - 1); + last_stripped = pos; + } + } +} + +FilePath FilePath::NormalizePathSeparators() const { + return NormalizePathSeparatorsTo(kSeparators[0]); +} + +FilePath FilePath::NormalizePathSeparatorsTo(CharType separator) const { +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + DCHECK_NE(kSeparators + kSeparatorsLength, + std::find(kSeparators, kSeparators + kSeparatorsLength, separator)); + StringType copy = path_; + for (size_t i = 0; i < kSeparatorsLength; ++i) { + std::replace(copy.begin(), copy.end(), kSeparators[i], separator); + } + return FilePath(copy); +#else + return *this; +#endif +} + +#if defined(OS_ANDROID) +bool FilePath::IsContentUri() const { + return StartsWithASCII(path_, "content://", false /*case_sensitive*/); +} +#endif + +} // namespace butil + +void PrintTo(const butil::FilePath& path, std::ostream* out) { + *out << path.value(); +} diff --git a/src/butil/files/file_path.h b/src/butil/files/file_path.h new file mode 100644 index 0000000..c91f1f5 --- /dev/null +++ b/src/butil/files/file_path.h @@ -0,0 +1,469 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// FilePath is a container for pathnames stored in a platform's native string +// type, providing containers for manipulation in according with the +// platform's conventions for pathnames. It supports the following path +// types: +// +// POSIX Windows +// --------------- ---------------------------------- +// Fundamental type char[] wchar_t[] +// Encoding unspecified* UTF-16 +// Separator / \, tolerant of / +// Drive letters no case-insensitive A-Z followed by : +// Alternate root // (surprise!) \\, for UNC paths +// +// * The encoding need not be specified on POSIX systems, although some +// POSIX-compliant systems do specify an encoding. Mac OS X uses UTF-8. +// Chrome OS also uses UTF-8. +// Linux does not specify an encoding, but in practice, the locale's +// character set may be used. +// +// For more arcane bits of path trivia, see below. +// +// FilePath objects are intended to be used anywhere paths are. An +// application may pass FilePath objects around internally, masking the +// underlying differences between systems, only differing in implementation +// where interfacing directly with the system. For example, a single +// OpenFile(const FilePath &) function may be made available, allowing all +// callers to operate without regard to the underlying implementation. On +// POSIX-like platforms, OpenFile might wrap fopen, and on Windows, it might +// wrap _wfopen_s, perhaps both by calling file_path.value().c_str(). This +// allows each platform to pass pathnames around without requiring conversions +// between encodings, which has an impact on performance, but more imporantly, +// has an impact on correctness on platforms that do not have well-defined +// encodings for pathnames. +// +// Several methods are available to perform common operations on a FilePath +// object, such as determining the parent directory (DirName), isolating the +// final path component (BaseName), and appending a relative pathname string +// to an existing FilePath object (Append). These methods are highly +// recommended over attempting to split and concatenate strings directly. +// These methods are based purely on string manipulation and knowledge of +// platform-specific pathname conventions, and do not consult the filesystem +// at all, making them safe to use without fear of blocking on I/O operations. +// These methods do not function as mutators but instead return distinct +// instances of FilePath objects, and are therefore safe to use on const +// objects. The objects themselves are safe to share between threads. +// +// To aid in initialization of FilePath objects from string literals, a +// FILE_PATH_LITERAL macro is provided, which accounts for the difference +// between char[]-based pathnames on POSIX systems and wchar_t[]-based +// pathnames on Windows. +// +// Paths can't contain NULs as a precaution agaist premature truncation. +// +// Because a FilePath object should not be instantiated at the global scope, +// instead, use a FilePath::CharType[] and initialize it with +// FILE_PATH_LITERAL. At runtime, a FilePath object can be created from the +// character array. Example: +// +// | const FilePath::CharType kLogFileName[] = FILE_PATH_LITERAL("log.txt"); +// | +// | void Function() { +// | FilePath log_file_path(kLogFileName); +// | [...] +// | } +// +// WARNING: FilePaths should ALWAYS be displayed with LTR directionality, even +// when the UI language is RTL. This means you always need to pass filepaths +// through butil::i18n::WrapPathWithLTRFormatting() before displaying it in the +// RTL UI. +// +// This is a very common source of bugs, please try to keep this in mind. +// +// ARCANE BITS OF PATH TRIVIA +// +// - A double leading slash is actually part of the POSIX standard. Systems +// are allowed to treat // as an alternate root, as Windows does for UNC +// (network share) paths. Most POSIX systems don't do anything special +// with two leading slashes, but FilePath handles this case properly +// in case it ever comes across such a system. FilePath needs this support +// for Windows UNC paths, anyway. +// References: +// The Open Group Base Specifications Issue 7, sections 3.266 ("Pathname") +// and 4.12 ("Pathname Resolution"), available at: +// http://www.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_266 +// http://www.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_12 +// +// - Windows treats c:\\ the same way it treats \\. This was intended to +// allow older applications that require drive letters to support UNC paths +// like \\server\share\path, by permitting c:\\server\share\path as an +// equivalent. Since the OS treats these paths specially, FilePath needs +// to do the same. Since Windows can use either / or \ as the separator, +// FilePath treats c://, c:\\, //, and \\ all equivalently. +// Reference: +// The Old New Thing, "Why is a drive letter permitted in front of UNC +// paths (sometimes)?", available at: +// http://blogs.msdn.com/oldnewthing/archive/2005/11/22/495740.aspx + +#ifndef BUTIL_FILES_FILE_PATH_H_ +#define BUTIL_FILES_FILE_PATH_H_ + +#include +#include +#include + +#include "butil/base_export.h" +#include "butil/compiler_specific.h" +#include "butil/containers/hash_tables.h" +#include "butil/strings/string16.h" +#include "butil/strings/string_piece.h" // For implicit conversions. +#include "butil/build_config.h" + +// Windows-style drive letter support and pathname separator characters can be +// enabled and disabled independently, to aid testing. These #defines are +// here so that the same setting can be used in both the implementation and +// in the unit test. +#if defined(OS_WIN) +#define FILE_PATH_USES_DRIVE_LETTERS +#define FILE_PATH_USES_WIN_SEPARATORS +#endif // OS_WIN + +namespace butil { + +// An abstraction to isolate users from the differences between native +// pathnames on different platforms. +class BUTIL_EXPORT FilePath { + public: +#if defined(OS_POSIX) + // On most platforms, native pathnames are char arrays, and the encoding + // may or may not be specified. On Mac OS X, native pathnames are encoded + // in UTF-8. + typedef std::string StringType; +#elif defined(OS_WIN) + // On Windows, for Unicode-aware applications, native pathnames are wchar_t + // arrays encoded in UTF-16. + typedef std::wstring StringType; +#endif // OS_WIN + + typedef StringType::value_type CharType; + + // Null-terminated array of separators used to separate components in + // hierarchical paths. Each character in this array is a valid separator, + // but kSeparators[0] is treated as the canonical separator and will be used + // when composing pathnames. + static const CharType kSeparators[]; + + // arraysize(kSeparators). + static const size_t kSeparatorsLength; + + // A special path component meaning "this directory." + static const CharType kCurrentDirectory[]; + + // A special path component meaning "the parent directory." + static const CharType kParentDirectory[]; + + // The character used to identify a file extension. + static const CharType kExtensionSeparator; + + FilePath(); + FilePath(const FilePath& that); + explicit FilePath(const StringType& path); + ~FilePath(); + FilePath& operator=(const FilePath& that); + + bool operator==(const FilePath& that) const; + + bool operator!=(const FilePath& that) const; + + // Required for some STL containers and operations + bool operator<(const FilePath& that) const { + return path_ < that.path_; + } + + const StringType& value() const { return path_; } + + bool empty() const { return path_.empty(); } + + void clear() { path_.clear(); } + + // Returns true if |character| is in kSeparators. + static bool IsSeparator(CharType character); + + // Returns a vector of all of the components of the provided path. It is + // equivalent to calling DirName().value() on the path's root component, + // and BaseName().value() on each child component. + // + // To make sure this is lossless so we can differentiate absolute and + // relative paths, the root slash will be included even though no other + // slashes will be. The precise behavior is: + // + // Posix: "/foo/bar" -> [ "/", "foo", "bar" ] + // Windows: "C:\foo\bar" -> [ "C:", "\\", "foo", "bar" ] + void GetComponents(std::vector* components) const; + + // Returns true if this FilePath is a strict parent of the |child|. Absolute + // and relative paths are accepted i.e. is /foo parent to /foo/bar and + // is foo parent to foo/bar. Does not convert paths to absolute, follow + // symlinks or directory navigation (e.g. ".."). A path is *NOT* its own + // parent. + bool IsParent(const FilePath& child) const; + + // If IsParent(child) holds, appends to path (if non-NULL) the + // relative path to child and returns true. For example, if parent + // holds "/Users/johndoe/Library/Application Support", child holds + // "/Users/johndoe/Library/Application Support/Google/Chrome/Default", and + // *path holds "/Users/johndoe/Library/Caches", then after + // parent.AppendRelativePath(child, path) is called *path will hold + // "/Users/johndoe/Library/Caches/Google/Chrome/Default". Otherwise, + // returns false. + bool AppendRelativePath(const FilePath& child, FilePath* path) const; + + // Returns a FilePath corresponding to the directory containing the path + // named by this object, stripping away the file component. If this object + // only contains one component, returns a FilePath identifying + // kCurrentDirectory. If this object already refers to the root directory, + // returns a FilePath identifying the root directory. + FilePath DirName() const WARN_UNUSED_RESULT; + + // Returns a FilePath corresponding to the last path component of this + // object, either a file or a directory. If this object already refers to + // the root directory, returns a FilePath identifying the root directory; + // this is the only situation in which BaseName will return an absolute path. + FilePath BaseName() const WARN_UNUSED_RESULT; + + // Returns ".jpg" for path "C:\pics\jojo.jpg", or an empty string if + // the file has no extension. If non-empty, Extension() will always start + // with precisely one ".". The following code should always work regardless + // of the value of path. For common double-extensions like .tar.gz and + // .user.js, this method returns the combined extension. For a single + // component, use FinalExtension(). + // new_path = path.RemoveExtension().value().append(path.Extension()); + // ASSERT(new_path == path.value()); + // NOTE: this is different from the original file_util implementation which + // returned the extension without a leading "." ("jpg" instead of ".jpg") + StringType Extension() const; + + // Returns the path's file extension, as in Extension(), but will + // never return a double extension. + // + // TODO(davidben): Check all our extension-sensitive code to see if + // we can rename this to Extension() and the other to something like + // LongExtension(), defaulting to short extensions and leaving the + // long "extensions" to logic like butil::GetUniquePathNumber(). + StringType FinalExtension() const; + + // Returns "C:\pics\jojo" for path "C:\pics\jojo.jpg" + // NOTE: this is slightly different from the similar file_util implementation + // which returned simply 'jojo'. + FilePath RemoveExtension() const WARN_UNUSED_RESULT; + + // Removes the path's file extension, as in RemoveExtension(), but + // ignores double extensions. + FilePath RemoveFinalExtension() const WARN_UNUSED_RESULT; + + // Inserts |suffix| after the file name portion of |path| but before the + // extension. Returns "" if BaseName() == "." or "..". + // Examples: + // path == "C:\pics\jojo.jpg" suffix == " (1)", returns "C:\pics\jojo (1).jpg" + // path == "jojo.jpg" suffix == " (1)", returns "jojo (1).jpg" + // path == "C:\pics\jojo" suffix == " (1)", returns "C:\pics\jojo (1)" + // path == "C:\pics.old\jojo" suffix == " (1)", returns "C:\pics.old\jojo (1)" + FilePath InsertBeforeExtension( + const StringType& suffix) const WARN_UNUSED_RESULT; + FilePath InsertBeforeExtensionASCII( + const butil::StringPiece& suffix) const WARN_UNUSED_RESULT; + + // Adds |extension| to |file_name|. Returns the current FilePath if + // |extension| is empty. Returns "" if BaseName() == "." or "..". + FilePath AddExtension( + const StringType& extension) const WARN_UNUSED_RESULT; + + // Replaces the extension of |file_name| with |extension|. If |file_name| + // does not have an extension, then |extension| is added. If |extension| is + // empty, then the extension is removed from |file_name|. + // Returns "" if BaseName() == "." or "..". + FilePath ReplaceExtension( + const StringType& extension) const WARN_UNUSED_RESULT; + + // Returns true if the file path matches the specified extension. The test is + // case insensitive. Don't forget the leading period if appropriate. + bool MatchesExtension(const StringType& extension) const; + + // Returns a FilePath by appending a separator and the supplied path + // component to this object's path. Append takes care to avoid adding + // excessive separators if this object's path already ends with a separator. + // If this object's path is kCurrentDirectory, a new FilePath corresponding + // only to |component| is returned. |component| must be a relative path; + // it is an error to pass an absolute path. + FilePath Append(const StringType& component) const WARN_UNUSED_RESULT; + FilePath Append(const FilePath& component) const WARN_UNUSED_RESULT; + + // Although Windows StringType is std::wstring, since the encoding it uses for + // paths is well defined, it can handle ASCII path components as well. + // Mac uses UTF8, and since ASCII is a subset of that, it works there as well. + // On Linux, although it can use any 8-bit encoding for paths, we assume that + // ASCII is a valid subset, regardless of the encoding, since many operating + // system paths will always be ASCII. + FilePath AppendASCII(const butil::StringPiece& component) + const WARN_UNUSED_RESULT; + + // Returns true if this FilePath contains an absolute path. On Windows, an + // absolute path begins with either a drive letter specification followed by + // a separator character, or with two separator characters. On POSIX + // platforms, an absolute path begins with a separator character. + bool IsAbsolute() const; + + // Returns true if the patch ends with a path separator character. + bool EndsWithSeparator() const WARN_UNUSED_RESULT; + + // Returns a copy of this FilePath that ends with a trailing separator. If + // the input path is empty, an empty FilePath will be returned. + FilePath AsEndingWithSeparator() const WARN_UNUSED_RESULT; + + // Returns a copy of this FilePath that does not end with a trailing + // separator. + FilePath StripTrailingSeparators() const WARN_UNUSED_RESULT; + + // Returns true if this FilePath contains an attempt to reference a parent + // directory (e.g. has a path component that is ".."). + bool ReferencesParent() const; + + // Return a Unicode human-readable version of this path. + // Warning: you can *not*, in general, go from a display name back to a real + // path. Only use this when displaying paths to users, not just when you + // want to stuff a string16 into some other API. + string16 LossyDisplayName() const; + + // Return the path as ASCII, or the empty string if the path is not ASCII. + // This should only be used for cases where the FilePath is representing a + // known-ASCII filename. + std::string MaybeAsASCII() const; + + // Return the path as UTF-8. + // + // This function is *unsafe* as there is no way to tell what encoding is + // used in file names on POSIX systems other than Mac and Chrome OS, + // although UTF-8 is practically used everywhere these days. To mitigate + // the encoding issue, this function internally calls + // SysNativeMBToWide() on POSIX systems other than Mac and Chrome OS, + // per assumption that the current locale's encoding is used in file + // names, but this isn't a perfect solution. + // + // Once it becomes safe to to stop caring about non-UTF-8 file names, + // the SysNativeMBToWide() hack will be removed from the code, along + // with "Unsafe" in the function name. + std::string AsUTF8Unsafe() const; + + // Similar to AsUTF8Unsafe, but returns UTF-16 instead. + string16 AsUTF16Unsafe() const; + + // Returns a FilePath object from a path name in UTF-8. This function + // should only be used for cases where you are sure that the input + // string is UTF-8. + // + // Like AsUTF8Unsafe(), this function is unsafe. This function + // internally calls SysWideToNativeMB() on POSIX systems other than Mac + // and Chrome OS, to mitigate the encoding issue. See the comment at + // AsUTF8Unsafe() for details. + static FilePath FromUTF8Unsafe(const std::string& utf8); + + // Similar to FromUTF8Unsafe, but accepts UTF-16 instead. + static FilePath FromUTF16Unsafe(const string16& utf16); + + // Normalize all path separators to backslash on Windows + // (if FILE_PATH_USES_WIN_SEPARATORS is true), or do nothing on POSIX systems. + FilePath NormalizePathSeparators() const; + + // Normalize all path separattors to given type on Windows + // (if FILE_PATH_USES_WIN_SEPARATORS is true), or do nothing on POSIX systems. + FilePath NormalizePathSeparatorsTo(CharType separator) const; + + // Compare two strings in the same way the file system does. + // Note that these always ignore case, even on file systems that are case- + // sensitive. If case-sensitive comparison is ever needed, add corresponding + // methods here. + // The methods are written as a static method so that they can also be used + // on parts of a file path, e.g., just the extension. + // CompareIgnoreCase() returns -1, 0 or 1 for less-than, equal-to and + // greater-than respectively. + static int CompareIgnoreCase(const StringType& string1, + const StringType& string2); + static bool CompareEqualIgnoreCase(const StringType& string1, + const StringType& string2) { + return CompareIgnoreCase(string1, string2) == 0; + } + static bool CompareLessIgnoreCase(const StringType& string1, + const StringType& string2) { + return CompareIgnoreCase(string1, string2) < 0; + } + +#if defined(OS_MACOSX) + // Returns the string in the special canonical decomposed form as defined for + // HFS, which is close to, but not quite, decomposition form D. See + // http://developer.apple.com/mac/library/technotes/tn/tn1150.html#UnicodeSubtleties + // for further comments. + // Returns the epmty string if the conversion failed. + static StringType GetHFSDecomposedForm(const FilePath::StringType& string); + + // Special UTF-8 version of FastUnicodeCompare. Cf: + // http://developer.apple.com/mac/library/technotes/tn/tn1150.html#StringComparisonAlgorithm + // IMPORTANT: The input strings must be in the special HFS decomposed form! + // (cf. above GetHFSDecomposedForm method) + static int HFSFastUnicodeCompare(const StringType& string1, + const StringType& string2); +#endif + +#if defined(OS_ANDROID) + // On android, file selection dialog can return a file with content uri + // scheme(starting with content://). Content uri needs to be opened with + // ContentResolver to guarantee that the app has appropriate permissions + // to access it. + // Returns true if the path is a content uri, or false otherwise. + bool IsContentUri() const; +#endif + + private: + // Remove trailing separators from this object. If the path is absolute, it + // will never be stripped any more than to refer to the absolute root + // directory, so "////" will become "/", not "". A leading pair of + // separators is never stripped, to support alternate roots. This is used to + // support UNC paths on Windows. + void StripTrailingSeparatorsInternal(); + + StringType path_; +}; + +} // namespace butil + +// This is required by googletest to print a readable output on test failures. +BUTIL_EXPORT extern void PrintTo(const butil::FilePath& path, std::ostream* out); + +// Macros for string literal initialization of FilePath::CharType[], and for +// using a FilePath::CharType[] in a printf-style format string. +#if defined(OS_POSIX) +#define FILE_PATH_LITERAL(x) x +#define PRFilePath "s" +#define PRFilePathLiteral "%s" +#elif defined(OS_WIN) +#define FILE_PATH_LITERAL(x) L ## x +#define PRFilePath "ls" +#define PRFilePathLiteral L"%ls" +#endif // OS_WIN + +// Provide a hash function so that hash_sets and maps can contain FilePath +// objects. +namespace BUTIL_HASH_NAMESPACE { +#if defined(COMPILER_GCC) + +template<> +struct hash { + size_t operator()(const butil::FilePath& f) const { + return hash()(f.value()); + } +}; + +#elif defined(COMPILER_MSVC) + +inline size_t hash_value(const butil::FilePath& f) { + return hash_value(f.value()); +} + +#endif // COMPILER + +} // namespace BUTIL_HASH_NAMESPACE + +#endif // BUTIL_FILES_FILE_PATH_H_ diff --git a/src/butil/files/file_path_constants.cc b/src/butil/files/file_path_constants.cc new file mode 100644 index 0000000..a8a8a64 --- /dev/null +++ b/src/butil/files/file_path_constants.cc @@ -0,0 +1,22 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/files/file_path.h" + +namespace butil { + +#if defined(FILE_PATH_USES_WIN_SEPARATORS) +const FilePath::CharType FilePath::kSeparators[] = FILE_PATH_LITERAL("\\/"); +#else // FILE_PATH_USES_WIN_SEPARATORS +const FilePath::CharType FilePath::kSeparators[] = FILE_PATH_LITERAL("/"); +#endif // FILE_PATH_USES_WIN_SEPARATORS + +const size_t FilePath::kSeparatorsLength = arraysize(kSeparators); + +const FilePath::CharType FilePath::kCurrentDirectory[] = FILE_PATH_LITERAL("."); +const FilePath::CharType FilePath::kParentDirectory[] = FILE_PATH_LITERAL(".."); + +const FilePath::CharType FilePath::kExtensionSeparator = FILE_PATH_LITERAL('.'); + +} // namespace butil diff --git a/src/butil/files/file_posix.cc b/src/butil/files/file_posix.cc new file mode 100644 index 0000000..558d092 --- /dev/null +++ b/src/butil/files/file_posix.cc @@ -0,0 +1,486 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/files/file.h" + +#include +#include +#include +#include + +#include "butil/files/file_path.h" +#include "butil/logging.h" +#include "butil/posix/eintr_wrapper.h" +#include "butil/strings/utf_string_conversions.h" +#include "butil/threading/thread_restrictions.h" + +#if defined(OS_ANDROID) +#include "butil/os_compat_android.h" +#endif + +namespace butil { + +// Make sure our Whence mappings match the system headers. +COMPILE_ASSERT(File::FROM_BEGIN == SEEK_SET && + File::FROM_CURRENT == SEEK_CUR && + File::FROM_END == SEEK_END, whence_matches_system); + +namespace { + +#if defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL) +static int CallFstat(int fd, stat_wrapper_t *sb) { + butil::ThreadRestrictions::AssertIOAllowed(); + return fstat(fd, sb); +} +#else +static int CallFstat(int fd, stat_wrapper_t *sb) { + butil::ThreadRestrictions::AssertIOAllowed(); + return fstat64(fd, sb); +} +#endif + +// NaCl doesn't provide the following system calls, so either simulate them or +// wrap them in order to minimize the number of #ifdef's in this file. +#if !defined(OS_NACL) +static bool IsOpenAppend(PlatformFile file) { + return (fcntl(file, F_GETFL) & O_APPEND) != 0; +} + +static int CallFtruncate(PlatformFile file, int64_t length) { + return HANDLE_EINTR(ftruncate(file, length)); +} + +static int CallFsync(PlatformFile file) { + return HANDLE_EINTR(fsync(file)); +} + +static int CallFutimes(PlatformFile file, const struct timeval times[2]) { +#ifdef __USE_XOPEN2K8 + // futimens should be available, but futimes might not be + // http://pubs.opengroup.org/onlinepubs/9699919799/ + + timespec ts_times[2]; + ts_times[0].tv_sec = times[0].tv_sec; + ts_times[0].tv_nsec = times[0].tv_usec * 1000; + ts_times[1].tv_sec = times[1].tv_sec; + ts_times[1].tv_nsec = times[1].tv_usec * 1000; + + return futimens(file, ts_times); +#else + return futimes(file, times); +#endif +} + +static File::Error CallFctnlFlock(PlatformFile file, bool do_lock) { + struct flock lock; + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; // Lock entire file. + if (HANDLE_EINTR(fcntl(file, do_lock ? F_SETLK : F_UNLCK, &lock)) == -1) + return File::OSErrorToFileError(errno); + return File::FILE_OK; +} +#else // defined(OS_NACL) + +static bool IsOpenAppend(PlatformFile file) { + // NaCl doesn't implement fcntl. Since NaCl's write conforms to the POSIX + // standard and always appends if the file is opened with O_APPEND, just + // return false here. + return false; +} + +static int CallFtruncate(PlatformFile file, int64_t length) { + NOTIMPLEMENTED(); // NaCl doesn't implement ftruncate. + return 0; +} + +static int CallFsync(PlatformFile file) { + NOTIMPLEMENTED(); // NaCl doesn't implement fsync. + return 0; +} + +static int CallFutimes(PlatformFile file, const struct timeval times[2]) { + NOTIMPLEMENTED(); // NaCl doesn't implement futimes. + return 0; +} + +static File::Error CallFctnlFlock(PlatformFile file, bool do_lock) { + NOTIMPLEMENTED(); // NaCl doesn't implement flock struct. + return File::FILE_ERROR_INVALID_OPERATION; +} +#endif // defined(OS_NACL) + +} // namespace + +void File::Info::FromStat(const stat_wrapper_t& stat_info) { + is_directory = S_ISDIR(stat_info.st_mode); + is_symbolic_link = S_ISLNK(stat_info.st_mode); + size = stat_info.st_size; + +#if defined(OS_LINUX) + time_t last_modified_sec = stat_info.st_mtim.tv_sec; + int64_t last_modified_nsec = stat_info.st_mtim.tv_nsec; + time_t last_accessed_sec = stat_info.st_atim.tv_sec; + int64_t last_accessed_nsec = stat_info.st_atim.tv_nsec; + time_t creation_time_sec = stat_info.st_ctim.tv_sec; + int64_t creation_time_nsec = stat_info.st_ctim.tv_nsec; +#elif defined(OS_ANDROID) + time_t last_modified_sec = stat_info.st_mtime; + int64_t last_modified_nsec = stat_info.st_mtime_nsec; + time_t last_accessed_sec = stat_info.st_atime; + int64_t last_accessed_nsec = stat_info.st_atime_nsec; + time_t creation_time_sec = stat_info.st_ctime; + int64_t creation_time_nsec = stat_info.st_ctime_nsec; +#elif defined(OS_MACOSX) || defined(OS_IOS) || defined(OS_BSD) + time_t last_modified_sec = stat_info.st_mtimespec.tv_sec; + int64_t last_modified_nsec = stat_info.st_mtimespec.tv_nsec; + time_t last_accessed_sec = stat_info.st_atimespec.tv_sec; + int64_t last_accessed_nsec = stat_info.st_atimespec.tv_nsec; + time_t creation_time_sec = stat_info.st_ctimespec.tv_sec; + int64_t creation_time_nsec = stat_info.st_ctimespec.tv_nsec; +#else + time_t last_modified_sec = stat_info.st_mtime; + int64_t last_modified_nsec = 0; + time_t last_accessed_sec = stat_info.st_atime; + int64_t last_accessed_nsec = 0; + time_t creation_time_sec = stat_info.st_ctime; + int64_t creation_time_nsec = 0; +#endif + + last_modified = + Time::FromTimeT(last_modified_sec) + + TimeDelta::FromMicroseconds(last_modified_nsec / + Time::kNanosecondsPerMicrosecond); + + last_accessed = + Time::FromTimeT(last_accessed_sec) + + TimeDelta::FromMicroseconds(last_accessed_nsec / + Time::kNanosecondsPerMicrosecond); + + creation_time = + Time::FromTimeT(creation_time_sec) + + TimeDelta::FromMicroseconds(creation_time_nsec / + Time::kNanosecondsPerMicrosecond); +} + +// NaCl doesn't implement system calls to open files directly. +#if !defined(OS_NACL) +// TODO(erikkay): does it make sense to support FLAG_EXCLUSIVE_* here? +void File::InitializeUnsafe(const FilePath& name, uint32_t flags) { + butil::ThreadRestrictions::AssertIOAllowed(); + DCHECK(!IsValid()); + + int open_flags = 0; + if (flags & FLAG_CREATE) + open_flags = O_CREAT | O_EXCL; + + created_ = false; + + if (flags & FLAG_CREATE_ALWAYS) { + DCHECK(!open_flags); + DCHECK(flags & FLAG_WRITE); + open_flags = O_CREAT | O_TRUNC; + } + + if (flags & FLAG_OPEN_TRUNCATED) { + DCHECK(!open_flags); + DCHECK(flags & FLAG_WRITE); + open_flags = O_TRUNC; + } + + if (!open_flags && !(flags & FLAG_OPEN) && !(flags & FLAG_OPEN_ALWAYS)) { + NOTREACHED(); + errno = EOPNOTSUPP; + error_details_ = FILE_ERROR_FAILED; + return; + } + + if (flags & FLAG_WRITE && flags & FLAG_READ) { + open_flags |= O_RDWR; + } else if (flags & FLAG_WRITE) { + open_flags |= O_WRONLY; + } else if (!(flags & FLAG_READ) && + !(flags & FLAG_WRITE_ATTRIBUTES) && + !(flags & FLAG_APPEND) && + !(flags & FLAG_OPEN_ALWAYS)) { + NOTREACHED(); + } + + if (flags & FLAG_TERMINAL_DEVICE) + open_flags |= O_NOCTTY | O_NDELAY; + + if (flags & FLAG_APPEND && flags & FLAG_READ) + open_flags |= O_APPEND | O_RDWR; + else if (flags & FLAG_APPEND) + open_flags |= O_APPEND | O_WRONLY; + + COMPILE_ASSERT(O_RDONLY == 0, O_RDONLY_must_equal_zero); + + int mode = S_IRUSR | S_IWUSR; +#if defined(OS_CHROMEOS) + mode |= S_IRGRP | S_IROTH; +#endif + + int descriptor = HANDLE_EINTR(open(name.value().c_str(), open_flags, mode)); + + if (flags & FLAG_OPEN_ALWAYS) { + if (descriptor < 0) { + open_flags |= O_CREAT; + if (flags & FLAG_EXCLUSIVE_READ || flags & FLAG_EXCLUSIVE_WRITE) + open_flags |= O_EXCL; // together with O_CREAT implies O_NOFOLLOW + + descriptor = HANDLE_EINTR(open(name.value().c_str(), open_flags, mode)); + if (descriptor >= 0) + created_ = true; + } + } + + if (descriptor < 0) { + error_details_ = File::OSErrorToFileError(errno); + return; + } + + if (flags & (FLAG_CREATE_ALWAYS | FLAG_CREATE)) + created_ = true; + + if (flags & FLAG_DELETE_ON_CLOSE) + unlink(name.value().c_str()); + + async_ = ((flags & FLAG_ASYNC) == FLAG_ASYNC); + error_details_ = FILE_OK; + file_.reset(descriptor); +} +#endif // !defined(OS_NACL) + +bool File::IsValid() const { + return file_.is_valid(); +} + +PlatformFile File::GetPlatformFile() const { + return file_.get(); +} + +PlatformFile File::TakePlatformFile() { + return file_.release(); +} + +void File::Close() { + if (!IsValid()) + return; + + butil::ThreadRestrictions::AssertIOAllowed(); + file_.reset(); +} + +int64_t File::Seek(Whence whence, int64_t offset) { + butil::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + +#if defined(OS_ANDROID) + COMPILE_ASSERT(sizeof(int64_t) == sizeof(off64_t), off64_t_64_bit); + return lseek64(file_.get(), static_cast(offset), + static_cast(whence)); +#else + COMPILE_ASSERT(sizeof(int64_t) == sizeof(off_t), off_t_64_bit); + return lseek(file_.get(), static_cast(offset), + static_cast(whence)); +#endif +} + +int File::Read(int64_t offset, char* data, int size) { + butil::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + if (size < 0) + return -1; + + int bytes_read = 0; + int rv; + do { + rv = HANDLE_EINTR(pread(file_.get(), data + bytes_read, + size - bytes_read, offset + bytes_read)); + if (rv <= 0) + break; + + bytes_read += rv; + } while (bytes_read < size); + + return bytes_read ? bytes_read : rv; +} + +int File::ReadAtCurrentPos(char* data, int size) { + butil::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + if (size < 0) + return -1; + + int bytes_read = 0; + int rv; + do { + rv = HANDLE_EINTR(read(file_.get(), data + bytes_read, size - bytes_read)); + if (rv <= 0) + break; + + bytes_read += rv; + } while (bytes_read < size); + + return bytes_read ? bytes_read : rv; +} + +int File::ReadNoBestEffort(int64_t offset, char* data, int size) { + butil::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + + return HANDLE_EINTR(pread(file_.get(), data, size, offset)); +} + +int File::ReadAtCurrentPosNoBestEffort(char* data, int size) { + butil::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + if (size < 0) + return -1; + + return HANDLE_EINTR(read(file_.get(), data, size)); +} + +int File::Write(int64_t offset, const char* data, int size) { + butil::ThreadRestrictions::AssertIOAllowed(); + + if (IsOpenAppend(file_.get())) + return WriteAtCurrentPos(data, size); + + DCHECK(IsValid()); + if (size < 0) + return -1; + + int bytes_written = 0; + int rv; + do { + rv = HANDLE_EINTR(pwrite(file_.get(), data + bytes_written, + size - bytes_written, offset + bytes_written)); + if (rv <= 0) + break; + + bytes_written += rv; + } while (bytes_written < size); + + return bytes_written ? bytes_written : rv; +} + +int File::WriteAtCurrentPos(const char* data, int size) { + butil::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + if (size < 0) + return -1; + + int bytes_written = 0; + int rv; + do { + rv = HANDLE_EINTR(write(file_.get(), data + bytes_written, + size - bytes_written)); + if (rv <= 0) + break; + + bytes_written += rv; + } while (bytes_written < size); + + return bytes_written ? bytes_written : rv; +} + +int File::WriteAtCurrentPosNoBestEffort(const char* data, int size) { + butil::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + if (size < 0) + return -1; + + return HANDLE_EINTR(write(file_.get(), data, size)); +} + +int64_t File::GetLength() { + DCHECK(IsValid()); + + stat_wrapper_t file_info; + if (CallFstat(file_.get(), &file_info)) + return false; + + return file_info.st_size; +} + +bool File::SetLength(int64_t length) { + butil::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + return !CallFtruncate(file_.get(), length); +} + +bool File::Flush() { + butil::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + return !CallFsync(file_.get()); +} + +bool File::SetTimes(Time last_access_time, Time last_modified_time) { + butil::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + + timeval times[2]; + times[0] = last_access_time.ToTimeVal(); + times[1] = last_modified_time.ToTimeVal(); + + return !CallFutimes(file_.get(), times); +} + +bool File::GetInfo(Info* info) { + DCHECK(IsValid()); + + stat_wrapper_t file_info; + if (CallFstat(file_.get(), &file_info)) + return false; + + info->FromStat(file_info); + return true; +} + +File::Error File::Lock() { + return CallFctnlFlock(file_.get(), true); +} + +File::Error File::Unlock() { + return CallFctnlFlock(file_.get(), false); +} + +// Static. +File::Error File::OSErrorToFileError(int saved_errno) { + switch (saved_errno) { + case EACCES: + case EISDIR: + case EROFS: + case EPERM: + return FILE_ERROR_ACCESS_DENIED; +#if !defined(OS_NACL) // ETXTBSY not defined by NaCl. + case ETXTBSY: + return FILE_ERROR_IN_USE; +#endif + case EEXIST: + return FILE_ERROR_EXISTS; + case ENOENT: + return FILE_ERROR_NOT_FOUND; + case EMFILE: + return FILE_ERROR_TOO_MANY_OPENED; + case ENOMEM: + return FILE_ERROR_NO_MEMORY; + case ENOSPC: + return FILE_ERROR_NO_SPACE; + case ENOTDIR: + return FILE_ERROR_NOT_A_DIRECTORY; + default: + return FILE_ERROR_FAILED; + } +} + +void File::SetPlatformFile(PlatformFile file) { + DCHECK(!file_.is_valid()); + file_.reset(file); +} + +} // namespace butil diff --git a/src/butil/files/file_watcher.cpp b/src/butil/files/file_watcher.cpp new file mode 100644 index 0000000..5c69765 --- /dev/null +++ b/src/butil/files/file_watcher.cpp @@ -0,0 +1,99 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2010/05/29 + +#include +#include "butil/build_config.h" // OS_MACOSX +#include "butil/files/file_watcher.h" + +namespace butil { + +static const FileWatcher::Timestamp NON_EXIST_TS = + static_cast(-1); + +FileWatcher::FileWatcher() : _last_ts(NON_EXIST_TS) { +} + +int FileWatcher::init(const char* file_path) { + if (init_from_not_exist(file_path) != 0) { + return -1; + } + check_and_consume(NULL); + return 0; +} + +int FileWatcher::init_from_not_exist(const char* file_path) { + if (NULL == file_path) { + return -1; + } + if (!_file_path.empty()) { + return -1; + } + _file_path = file_path; + return 0; +} + +FileWatcher::Change FileWatcher::check(Timestamp* new_timestamp) const { + struct stat st; + const int ret = stat(_file_path.c_str(), &st); + if (ret < 0) { + *new_timestamp = NON_EXIST_TS; + if (NON_EXIST_TS != _last_ts) { + return DELETED; + } else { + return UNCHANGED; + } + } else { + // Use microsecond timestamps which can be used for: + // 2^63 / 1000000 / 3600 / 24 / 365 = 292471 years + const Timestamp cur_ts = +#if defined(OS_MACOSX) + st.st_mtimespec.tv_sec * 1000000L + st.st_mtimespec.tv_nsec / 1000L; +#else + st.st_mtim.tv_sec * 1000000L + st.st_mtim.tv_nsec / 1000L; +#endif + *new_timestamp = cur_ts; + if (NON_EXIST_TS != _last_ts) { + if (cur_ts != _last_ts) { + return UPDATED; + } else { + return UNCHANGED; + } + } else { + return CREATED; + } + } +} + +FileWatcher::Change FileWatcher::check_and_consume(Timestamp* last_timestamp) { + Timestamp new_timestamp; + Change e = check(&new_timestamp); + if (last_timestamp) { + *last_timestamp = _last_ts; + } + if (e != UNCHANGED) { + _last_ts = new_timestamp; + } + return e; +} + +void FileWatcher::restore(Timestamp timestamp) { + _last_ts = timestamp; +} + +} // namespace butil diff --git a/src/butil/files/file_watcher.h b/src/butil/files/file_watcher.h new file mode 100644 index 0000000..548e70b --- /dev/null +++ b/src/butil/files/file_watcher.h @@ -0,0 +1,84 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2010/05/29 + +// Watch timestamp of a file + +#ifndef BUTIL_FILES_FILE_WATCHER_H +#define BUTIL_FILES_FILE_WATCHER_H + +#include // int64_t +#include // std::string + +// Example: +// FileWatcher fw; +// fw.init("to_be_watched_file"); +// .... +// if (fw.check_and_consume() > 0) { +// // the file is created or updated +// ...... +// } + +namespace butil { +class FileWatcher { +public: + enum Change { + DELETED = -1, + UNCHANGED = 0, + UPDATED = 1, + CREATED = 2, + }; + + typedef int64_t Timestamp; + + FileWatcher(); + + // Watch file at `file_path', must be called before calling other methods. + // Returns 0 on success, -1 otherwise. + int init(const char* file_path); + // Let check_and_consume returns CREATE when file_path already exists. + int init_from_not_exist(const char* file_path); + + // Check and consume change of the watched file. Write `last_timestamp' + // if it's not NULL. + // Returns: + // CREATE the file is created since last call to this method. + // UPDATED the file is modified since last call. + // UNCHANGED the file has no change since last call. + // DELETED the file was deleted since last call. + // Note: If the file is updated too frequently, this method may return + // UNCHANGED due to precision of stat(2) and the file system. If the file + // is created and deleted too frequently, the event may not be detected. + Change check_and_consume(Timestamp* last_timestamp = NULL); + + // Set internal timestamp. User can use this method to make + // check_and_consume() replay the change. + void restore(Timestamp timestamp); + + // Get path of watched file + const char* filepath() const { return _file_path.c_str(); } + +private: + Change check(Timestamp* new_timestamp) const; + + std::string _file_path; + Timestamp _last_ts; +}; +} // namespace butil + +#endif // BUTIL_FILES_FILE_WATCHER_H diff --git a/src/butil/files/memory_mapped_file.cc b/src/butil/files/memory_mapped_file.cc new file mode 100644 index 0000000..95dae4f --- /dev/null +++ b/src/butil/files/memory_mapped_file.cc @@ -0,0 +1,53 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/files/memory_mapped_file.h" + +#include "butil/files/file_path.h" +#include "butil/logging.h" + +namespace butil { + +MemoryMappedFile::~MemoryMappedFile() { + CloseHandles(); +} + +bool MemoryMappedFile::Initialize(const FilePath& file_name) { + if (IsValid()) + return false; + + file_.Initialize(file_name, File::FLAG_OPEN | File::FLAG_READ); + + if (!file_.IsValid()) { + DLOG(ERROR) << "Couldn't open " << file_name.AsUTF8Unsafe(); + return false; + } + + if (!MapFileToMemory()) { + CloseHandles(); + return false; + } + + return true; +} + +bool MemoryMappedFile::Initialize(File file) { + if (IsValid()) + return false; + + file_ = file.Pass(); + + if (!MapFileToMemory()) { + CloseHandles(); + return false; + } + + return true; +} + +bool MemoryMappedFile::IsValid() const { + return data_ != NULL; +} + +} // namespace butil diff --git a/src/butil/files/memory_mapped_file.h b/src/butil/files/memory_mapped_file.h new file mode 100644 index 0000000..22ab03b --- /dev/null +++ b/src/butil/files/memory_mapped_file.h @@ -0,0 +1,72 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_FILES_MEMORY_MAPPED_FILE_H_ +#define BUTIL_FILES_MEMORY_MAPPED_FILE_H_ + +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/files/file.h" +#include "butil/build_config.h" + +#if defined(OS_WIN) +#include +#endif + +namespace butil { + +class FilePath; + +class BUTIL_EXPORT MemoryMappedFile { + public: + // The default constructor sets all members to invalid/null values. + MemoryMappedFile(); + ~MemoryMappedFile(); + + // Opens an existing file and maps it into memory. Access is restricted to + // read only. If this object already points to a valid memory mapped file + // then this method will fail and return false. If it cannot open the file, + // the file does not exist, or the memory mapping fails, it will return false. + // Later we may want to allow the user to specify access. + bool Initialize(const FilePath& file_name); + + // As above, but works with an already-opened file. MemoryMappedFile takes + // ownership of |file| and closes it when done. + bool Initialize(File file); + +#if defined(OS_WIN) + // Opens an existing file and maps it as an image section. Please refer to + // the Initialize function above for additional information. + bool InitializeAsImageSection(const FilePath& file_name); +#endif // OS_WIN + + const uint8_t* data() const { return data_; } + size_t length() const { return length_; } + + // Is file_ a valid file handle that points to an open, memory mapped file? + bool IsValid() const; + + private: + // Map the file to memory, set data_ to that memory address. Return true on + // success, false on any kind of failure. This is a helper for Initialize(). + bool MapFileToMemory(); + + // Closes all open handles. + void CloseHandles(); + + File file_; + uint8_t* data_; + size_t length_; + +#if defined(OS_WIN) + win::ScopedHandle file_mapping_; + bool image_; // Map as an image. +#endif + + DISALLOW_COPY_AND_ASSIGN(MemoryMappedFile); +}; + +} // namespace butil + +#endif // BUTIL_FILES_MEMORY_MAPPED_FILE_H_ diff --git a/src/butil/files/memory_mapped_file_posix.cc b/src/butil/files/memory_mapped_file_posix.cc new file mode 100644 index 0000000..2901ac3 --- /dev/null +++ b/src/butil/files/memory_mapped_file_posix.cc @@ -0,0 +1,48 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/files/memory_mapped_file.h" + +#include +#include +#include + +#include "butil/logging.h" +#include "butil/threading/thread_restrictions.h" + +namespace butil { + +MemoryMappedFile::MemoryMappedFile() : data_(NULL), length_(0) { +} + +bool MemoryMappedFile::MapFileToMemory() { + ThreadRestrictions::AssertIOAllowed(); + + struct stat file_stat; + if (fstat(file_.GetPlatformFile(), &file_stat) == -1 ) { + DPLOG(ERROR) << "fstat " << file_.GetPlatformFile(); + return false; + } + length_ = file_stat.st_size; + + data_ = static_cast( + mmap(NULL, length_, PROT_READ, MAP_SHARED, file_.GetPlatformFile(), 0)); + if (data_ == MAP_FAILED) + DPLOG(ERROR) << "mmap " << file_.GetPlatformFile(); + + return data_ != MAP_FAILED; +} + +void MemoryMappedFile::CloseHandles() { + ThreadRestrictions::AssertIOAllowed(); + + if (data_ != NULL) + munmap(data_, length_); + file_.Close(); + + data_ = NULL; + length_ = 0; +} + +} // namespace butil diff --git a/src/butil/files/scoped_file.cc b/src/butil/files/scoped_file.cc new file mode 100644 index 0000000..28a8162 --- /dev/null +++ b/src/butil/files/scoped_file.cc @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/files/scoped_file.h" + +#include "butil/logging.h" + +#if defined(OS_POSIX) +#include + +#include "butil/posix/eintr_wrapper.h" +#endif + +namespace butil { +namespace internal { + +#if defined(OS_POSIX) + +// static +void ScopedFDCloseTraits::Free(int fd) { + // It's important to crash here. + // There are security implications to not closing a file descriptor + // properly. As file descriptors are "capabilities", keeping them open + // would make the current process keep access to a resource. Much of + // Chrome relies on being able to "drop" such access. + // It's especially problematic on Linux with the setuid sandbox, where + // a single open directory would bypass the entire security model. + PCHECK(0 == IGNORE_EINTR(close(fd))); +} + +#endif // OS_POSIX + +} // namespace internal +} // namespace butil diff --git a/src/butil/files/scoped_file.h b/src/butil/files/scoped_file.h new file mode 100644 index 0000000..4d4d6ea --- /dev/null +++ b/src/butil/files/scoped_file.h @@ -0,0 +1,108 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_FILES_SCOPED_FILE_H_ +#define BUTIL_FILES_SCOPED_FILE_H_ + +#include + +#include "butil/base_export.h" +#include "butil/logging.h" +#include "butil/memory/scoped_ptr.h" +#include "butil/scoped_generic.h" +#include "butil/build_config.h" + +namespace butil { + +namespace internal { + +#if defined(OS_POSIX) +struct BUTIL_EXPORT ScopedFDCloseTraits { + static int InvalidValue() { + return -1; + } + static void Free(int fd); +}; +#endif + +} // namespace internal + +// ----------------------------------------------------------------------------- + +#if defined(OS_POSIX) +// A low-level Posix file descriptor closer class. Use this when writing +// platform-specific code, especially that does non-file-like things with the +// FD (like sockets). +// +// If you're writing low-level Windows code, see butil/win/scoped_handle.h +// which provides some additional functionality. +// +// If you're writing cross-platform code that deals with actual files, you +// should generally use butil::File instead which can be constructed with a +// handle, and in addition to handling ownership, has convenient cross-platform +// file manipulation functions on it. +typedef ScopedGeneric ScopedFD; +#endif + +//// Automatically closes |FILE*|s. +class ScopedFILE { + MOVE_ONLY_TYPE_FOR_CPP_03(ScopedFILE, RValue); +public: + ScopedFILE() : _fp(NULL) {} + + // Open file at |path| with |mode|. + // If fopen failed, operator FILE* returns NULL and errno is set. + ScopedFILE(const char *path, const char *mode) { + _fp = fopen(path, mode); + } + + // |fp| must be the return value of fopen as we use fclose in the + // destructor, otherwise the behavior is undefined + explicit ScopedFILE(FILE *fp) :_fp(fp) {} + + ScopedFILE(RValue rvalue) { + _fp = rvalue.object->_fp; + rvalue.object->_fp = NULL; + } + + ~ScopedFILE() { + if (_fp != NULL) { + fclose(_fp); + _fp = NULL; + } + } + + // Close current opened file and open another file at |path| with |mode| + void reset(const char* path, const char* mode) { + reset(fopen(path, mode)); + } + + void reset() { reset(NULL); } + + void reset(FILE *fp) { + if (_fp != NULL) { + fclose(_fp); + _fp = NULL; + } + _fp = fp; + } + + // Set internal FILE* to NULL and return previous value. + FILE* release() { + FILE* const prev_fp = _fp; + _fp = NULL; + return prev_fp; + } + + operator FILE*() const { return _fp; } + + FILE* get() { return _fp; } + +private: + FILE* _fp; +}; + +} // namespace butil + +#endif // BUTIL_FILES_SCOPED_FILE_H_ diff --git a/src/butil/files/scoped_temp_dir.cc b/src/butil/files/scoped_temp_dir.cc new file mode 100644 index 0000000..2a7b0e1 --- /dev/null +++ b/src/butil/files/scoped_temp_dir.cc @@ -0,0 +1,83 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/files/scoped_temp_dir.h" + +#include "butil/file_util.h" +#include "butil/logging.h" + +namespace butil { + +ScopedTempDir::ScopedTempDir() { +} + +ScopedTempDir::~ScopedTempDir() { + if (!path_.empty() && !Delete()) + DLOG(WARNING) << "Could not delete temp dir in dtor."; +} + +bool ScopedTempDir::CreateUniqueTempDir() { + if (!path_.empty()) + return false; + + // This "scoped_dir" prefix is only used on Windows and serves as a template + // for the unique name. + if (!butil::CreateNewTempDirectory(FILE_PATH_LITERAL("scoped_dir"), &path_)) + return false; + + return true; +} + +bool ScopedTempDir::CreateUniqueTempDirUnderPath(const FilePath& base_path) { + if (!path_.empty()) + return false; + + // If |base_path| does not exist, create it. + if (!butil::CreateDirectory(base_path)) + return false; + + // Create a new, uniquely named directory under |base_path|. + if (!butil::CreateTemporaryDirInDir(base_path, + FILE_PATH_LITERAL("scoped_dir_"), + &path_)) + return false; + + return true; +} + +bool ScopedTempDir::Set(const FilePath& path) { + if (!path_.empty()) + return false; + + if (!DirectoryExists(path) && !butil::CreateDirectory(path)) + return false; + + path_ = path; + return true; +} + +bool ScopedTempDir::Delete() { + if (path_.empty()) + return false; + + bool ret = butil::DeleteFile(path_, true); + if (ret) { + // We only clear the path if deleted the directory. + path_.clear(); + } + + return ret; +} + +FilePath ScopedTempDir::Take() { + FilePath ret = path_; + path_ = FilePath(); + return ret; +} + +bool ScopedTempDir::IsValid() const { + return !path_.empty() && DirectoryExists(path_); +} + +} // namespace butil diff --git a/src/butil/files/scoped_temp_dir.h b/src/butil/files/scoped_temp_dir.h new file mode 100644 index 0000000..686d860 --- /dev/null +++ b/src/butil/files/scoped_temp_dir.h @@ -0,0 +1,62 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_FILES_SCOPED_TEMP_DIR_H_ +#define BUTIL_FILES_SCOPED_TEMP_DIR_H_ + +// An object representing a temporary / scratch directory that should be cleaned +// up (recursively) when this object goes out of scope. Note that since +// deletion occurs during the destructor, no further error handling is possible +// if the directory fails to be deleted. As a result, deletion is not +// guaranteed by this class. +// +// Multiple calls to the methods which establish a temporary directory +// (CreateUniqueTempDir, CreateUniqueTempDirUnderPath, and Set) must have +// intervening calls to Delete or Take, or the calls will fail. + +#include "butil/base_export.h" +#include "butil/files/file_path.h" + +namespace butil { + +class BUTIL_EXPORT ScopedTempDir { + public: + // No directory is owned/created initially. + ScopedTempDir(); + + // Recursively delete path. + ~ScopedTempDir(); + + // Creates a unique directory in TempPath, and takes ownership of it. + // See file_util::CreateNewTemporaryDirectory. + bool CreateUniqueTempDir() WARN_UNUSED_RESULT; + + // Creates a unique directory under a given path, and takes ownership of it. + bool CreateUniqueTempDirUnderPath(const FilePath& path) WARN_UNUSED_RESULT; + + // Takes ownership of directory at |path|, creating it if necessary. + // Don't call multiple times unless Take() has been called first. + bool Set(const FilePath& path) WARN_UNUSED_RESULT; + + // Deletes the temporary directory wrapped by this object. + bool Delete() WARN_UNUSED_RESULT; + + // Caller takes ownership of the temporary directory so it won't be destroyed + // when this object goes out of scope. + FilePath Take(); + + const FilePath& path() const { return path_; } + + // Returns true if path_ is non-empty and exists. + bool IsValid() const; + + private: + FilePath path_; + + DISALLOW_COPY_AND_ASSIGN(ScopedTempDir); +}; + +} // namespace butil + +#endif // BUTIL_FILES_SCOPED_TEMP_DIR_H_ diff --git a/src/butil/files/temp_file.cpp b/src/butil/files/temp_file.cpp new file mode 100644 index 0000000..d48499a --- /dev/null +++ b/src/butil/files/temp_file.cpp @@ -0,0 +1,154 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Thu Oct 28 15:27:05 2010 + +#include // open +#include // close +#include // snprintf, vdprintf +#include // mkstemp +#include // strlen +#include // va_list +#include // errno +#include // placement new +#include "temp_file.h" // TempFile + +// Initializing array. Needs to be macro. +#define BASE_FILES_TEMP_FILE_PATTERN "temp_file_XXXXXX"; + +namespace butil { + +TempFile::TempFile() : _ever_opened(0) { + char temp_name[] = BASE_FILES_TEMP_FILE_PATTERN; + _fd = mkstemp(temp_name); + if (_fd >= 0) { + _ever_opened = 1; + snprintf(_fname, sizeof(_fname), "%s", temp_name); + } else { + *_fname = '\0'; + } + +} + +TempFile::TempFile(const char* ext) { + if (NULL == ext || '\0' == *ext) { + new (this) TempFile(); + return; + } + + *_fname = '\0'; + _fd = -1; + _ever_opened = 0; + + // Make a temp file to occupy the filename without ext. + char temp_name[] = BASE_FILES_TEMP_FILE_PATTERN; + const int tmp_fd = mkstemp(temp_name); + if (tmp_fd < 0) { + return; + } + + // Open the temp_file_XXXXXX.ext + snprintf(_fname, sizeof(_fname), "%s.%s", temp_name, ext); + + _fd = open(_fname, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0600); + if (_fd < 0) { + *_fname = '\0'; + } else { + _ever_opened = 1; + } + + // Close and remove temp_file_XXXXXX anyway. + close(tmp_fd); + unlink(temp_name); +} + +int TempFile::_reopen_if_necessary() { + if (_fd < 0) { + _fd = open(_fname, O_CREAT | O_WRONLY | O_TRUNC, 0600); + } + return _fd; +} + +TempFile::~TempFile() { + if (_fd >= 0) { + close(_fd); + _fd = -1; + } + if (_ever_opened) { + // Only remove temp file when _fd >= 0, otherwise we may + // remove another temporary file (occupied the same name) + unlink(_fname); + } +} + +int TempFile::save(const char *content) { + return save_bin(content, strlen(content)); +} + +int TempFile::save_format(const char *fmt, ...) { + if (_reopen_if_necessary() < 0) { + return -1; + } + va_list ap; + va_start(ap, fmt); + const int rc = vdprintf(_fd, fmt, ap); + va_end(ap); + + close(_fd); + _fd = -1; + // TODO: is this right? + return (rc < 0 ? -1 : 0); +} + +// Write until all buffer was written or an error except EINTR. +// Returns: +// -1 error happened, errno is set +// count all written +static ssize_t temp_file_write_all(int fd, const void *buf, size_t count) { + size_t off = 0; + for (;;) { + ssize_t nw = write(fd, (char*)buf + off, count - off); + if (nw == (ssize_t)(count - off)) { // including count==0 + return count; + } + if (nw >= 0) { + off += nw; + } else if (errno != EINTR) { + return -1; + } + } +} + +int TempFile::save_bin(const void *buf, size_t count) { + if (_reopen_if_necessary() < 0) { + return -1; + } + + const ssize_t len = temp_file_write_all(_fd, buf, count); + + close(_fd); + _fd = -1; + if (len < 0) { + return -1; + } else if ((size_t)len != count) { + errno = ENOSPC; + return -1; + } + return 0; +} + +} // namespace butil diff --git a/src/butil/files/temp_file.h b/src/butil/files/temp_file.h new file mode 100644 index 0000000..028dfa3 --- /dev/null +++ b/src/butil/files/temp_file.h @@ -0,0 +1,77 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Thu Oct 28 15:23:09 2010 + +#ifndef BUTIL_FILES_TEMP_FILE_H +#define BUTIL_FILES_TEMP_FILE_H + +#include "butil/macros.h" + +namespace butil { + +// Create a temporary file in current directory, which will be deleted when +// corresponding TempFile object destructs, typically for unit testing. +// +// Usage: +// { +// TempFile tmpfile; // A temporay file shall be created +// tmpfile.save("some text"); // Write into the temporary file +// } +// // The temporary file shall be removed due to destruction of tmpfile + +class TempFile { +public: + // Create a temporary file in current directory. If |ext| is given, + // filename will be temp_file_XXXXXX.|ext|, temp_file_XXXXXX otherwise. + // If temporary file cannot be created, all save*() functions will + // return -1. If |ext| is too long, filename will be truncated. + TempFile(); + explicit TempFile(const char* ext); + + // The temporary file is removed in destructor. + ~TempFile(); + + // Save |content| to file, overwriting existing file. + // Returns 0 when successful, -1 otherwise. + int save(const char *content); + + // Save |fmt| and associated values to file, overwriting existing file. + // Returns 0 when successful, -1 otherwise. + int save_format(const char *fmt, ...) __attribute__((format (printf, 2, 3) )); + + // Save binary data |buf| (|count| bytes) to file, overwriting existing file. + // Returns 0 when successful, -1 otherwise. + int save_bin(const void *buf, size_t count); + + // Get name of the temporary file. + const char *fname() const { return _fname; } + +private: + // TempFile is associated with file, copying makes no sense. + DISALLOW_COPY_AND_ASSIGN(TempFile); + + int _reopen_if_necessary(); + + int _fd; // file descriptor + int _ever_opened; + char _fname[24]; // name of the file +}; + +} // namespace butil + +#endif // BUTIL_FILES_TEMP_FILE_H diff --git a/src/butil/find_cstr.cpp b/src/butil/find_cstr.cpp new file mode 100644 index 0000000..82b2db9 --- /dev/null +++ b/src/butil/find_cstr.cpp @@ -0,0 +1,26 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Tue Jun 23 15:03:24 CST 2015 + +#include "butil/find_cstr.h" + +namespace butil { + +thread_local StringMapThreadLocalTemp tls_stringmap_temp = { false, {} }; + +} // namespace butil diff --git a/src/butil/find_cstr.h b/src/butil/find_cstr.h new file mode 100644 index 0000000..103f9e7 --- /dev/null +++ b/src/butil/find_cstr.h @@ -0,0 +1,162 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Tue Jun 23 15:03:24 CST 2015 + +#ifndef BUTIL_FIND_CSTR_H +#define BUTIL_FIND_CSTR_H + +#include +#include +#include +#include "butil/thread_local.h" +#include "butil/debug/leak_annotations.h" + +// Find c-string in maps with std::string as keys without memory allocations. +// Example: +// std::map string_map; +// +// string_map.find("hello"); // constructed a temporary std::string +// // which needs memory allocation. +// +// find_cstr(string_map, "hello"); // no allocation. +// +// You can specialize find_cstr for other maps. +// Possible prototypes are: +// const_iterator find_cstr(const Map& map, const char* key); +// iterator find_cstr(Map& map, const char* key); +// const_iterator find_cstr(const Map& map, const char* key, size_t length); +// iterator find_cstr(Map& map, const char* key, size_t length); + +namespace butil { + +struct StringMapThreadLocalTemp { + bool initialized; + char buf[sizeof(std::string)]; + + static void delete_tls(void* buf) { + StringMapThreadLocalTemp* temp = (StringMapThreadLocalTemp*)buf; + if (temp->initialized) { + temp->initialized = false; + std::string* temp_string = (std::string*)temp->buf; + temp_string->~basic_string(); + } + } + + inline std::string* get_string(const char* key) { + // This thread-local string (and any buffer it reallocates) is reclaimed + // via thread_atexit when the thread exits. If a thread is still alive at + // leak-check time the allocation would be reported; it is a thread-local + // cache, so mark its allocations as intentional non-leaks. + ANNOTATE_SCOPED_MEMORY_LEAK; + if (!initialized) { + initialized = true; + std::string* tmp = new (buf) std::string(key); + thread_atexit(delete_tls, this); + return tmp; + } else { + std::string* tmp = (std::string*)buf; + tmp->assign(key); + return tmp; + } + } + + inline std::string* get_string(const char* key, size_t length) { + // See the note in the other get_string overload. + ANNOTATE_SCOPED_MEMORY_LEAK; + if (!initialized) { + initialized = true; + std::string* tmp = new (buf) std::string(key, length); + thread_atexit(delete_tls, this); + return tmp; + } else { + std::string* tmp = (std::string*)buf; + tmp->assign(key, length); + return tmp; + } + } + + inline std::string* get_lowered_string(const char* key) { + std::string* tmp = get_string(key); + std::transform(tmp->begin(), tmp->end(), tmp->begin(), ::tolower); + return tmp; + } + + inline std::string* get_lowered_string(const char* key, size_t length) { + std::string* tmp = get_string(key, length); + std::transform(tmp->begin(), tmp->end(), tmp->begin(), ::tolower); + return tmp; + } +}; + +extern thread_local StringMapThreadLocalTemp tls_stringmap_temp; + +template +typename std::map::const_iterator +find_cstr(const std::map& m, const char* key) { + return m.find(*tls_stringmap_temp.get_string(key)); +} + +template +typename std::map::iterator +find_cstr(std::map& m, const char* key) { + return m.find(*tls_stringmap_temp.get_string(key)); +} + +template +typename std::map::const_iterator +find_cstr(const std::map& m, + const char* key, size_t length) { + return m.find(*tls_stringmap_temp.get_string(key, length)); +} + +template +typename std::map::iterator +find_cstr(std::map& m, + const char* key, size_t length) { + return m.find(*tls_stringmap_temp.get_string(key, length)); +} + +template +typename std::map::const_iterator +find_lowered_cstr(const std::map& m, const char* key) { + return m.find(*tls_stringmap_temp.get_lowered_string(key)); +} + +template +typename std::map::iterator +find_lowered_cstr(std::map& m, const char* key) { + return m.find(*tls_stringmap_temp.get_lowered_string(key)); +} + +template +typename std::map::const_iterator +find_lowered_cstr(const std::map& m, + const char* key, size_t length) { + return m.find(*tls_stringmap_temp.get_lowered_string(key, length)); +} + +template +typename std::map::iterator +find_lowered_cstr(std::map& m, + const char* key, size_t length) { + return m.find(*tls_stringmap_temp.get_lowered_string(key, length)); +} + +} // namespace butil + +#endif // BUTIL_FIND_CSTR_H diff --git a/src/butil/float_util.h b/src/butil/float_util.h new file mode 100644 index 0000000..334be44 --- /dev/null +++ b/src/butil/float_util.h @@ -0,0 +1,36 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_FLOAT_UTIL_H_ +#define BUTIL_FLOAT_UTIL_H_ + +#include "butil/build_config.h" + +#include + +#include + +namespace butil { + +template +inline bool IsFinite(const Float& number) { +#if defined(OS_POSIX) + return std::isfinite(number) != 0; +#elif defined(OS_WIN) + return _finite(number) != 0; +#endif +} + +template +inline bool IsNaN(const Float& number) { +#if defined(OS_POSIX) + return std::isnan(number) != 0; +#elif defined(OS_WIN) + return _isnan(number) != 0; +#endif +} + +} // namespace butil + +#endif // BUTIL_FLOAT_UTIL_H_ diff --git a/src/butil/format_macros.h b/src/butil/format_macros.h new file mode 100644 index 0000000..9949b48 --- /dev/null +++ b/src/butil/format_macros.h @@ -0,0 +1,101 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_FORMAT_MACROS_H_ +#define BUTIL_FORMAT_MACROS_H_ + +// This file defines the format macros for some integer types. + +// To print a 64-bit value in a portable way: +// int64_t value; +// printf("xyz:%" PRId64, value); +// The "d" in the macro corresponds to %d; you can also use PRIu64 etc. +// +// For wide strings, prepend "Wide" to the macro: +// int64_t value; +// StringPrintf(L"xyz: %" WidePRId64, value); +// +// To print a size_t value in a portable way: +// size_t size; +// printf("xyz: %" PRIuS, size); +// The "u" in the macro corresponds to %u, and S is for "size". + +#include "butil/build_config.h" + +#if defined(OS_POSIX) + +#if (defined(_INTTYPES_H) || defined(_INTTYPES_H_)) && !defined(PRId64) +#error "inttypes.h has already been included before this header file, but " +#error "without __STDC_FORMAT_MACROS defined." +#endif + +#if !defined(__STDC_FORMAT_MACROS) +#define __STDC_FORMAT_MACROS +#endif + +#include + +// GCC will concatenate wide and narrow strings correctly, so nothing needs to +// be done here. +#define WidePRId64 PRId64 +#define WidePRIu64 PRIu64 +#define WidePRIx64 PRIx64 + +#if !defined(PRIuS) +#define PRIuS "zu" +#endif + +// The size of NSInteger and NSUInteger varies between 32-bit and 64-bit +// architectures and Apple does not provides standard format macros and +// recommends casting. This has many drawbacks, so instead define macros +// for formatting those types. +#if defined(OS_MACOSX) +#if defined(ARCH_CPU_64_BITS) +#if !defined(PRIdNS) +#define PRIdNS "ld" +#endif +#if !defined(PRIuNS) +#define PRIuNS "lu" +#endif +#if !defined(PRIxNS) +#define PRIxNS "lx" +#endif +#else // defined(ARCH_CPU_64_BITS) +#if !defined(PRIdNS) +#define PRIdNS "d" +#endif +#if !defined(PRIuNS) +#define PRIuNS "u" +#endif +#if !defined(PRIxNS) +#define PRIxNS "x" +#endif +#endif +#endif // defined(OS_MACOSX) + +#else // OS_WIN + +#if !defined(PRId64) +#define PRId64 "I64d" +#endif + +#if !defined(PRIu64) +#define PRIu64 "I64u" +#endif + +#if !defined(PRIx64) +#define PRIx64 "I64x" +#endif + +#define WidePRId64 L"I64d" +#define WidePRIu64 L"I64u" +#define WidePRIx64 L"I64x" + +#if !defined(PRIuS) +#define PRIuS "Iu" +#endif + +#endif + +#endif // BUTIL_FORMAT_MACROS_H_ diff --git a/src/butil/gperftools_profiler.h b/src/butil/gperftools_profiler.h new file mode 100644 index 0000000..893fd60 --- /dev/null +++ b/src/butil/gperftools_profiler.h @@ -0,0 +1,83 @@ +/* Copyright (c) 2005, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * --- + * Author: Sanjay Ghemawat + * + * Module for CPU profiling based on periodic pc-sampling. + * + * These functions are thread-safe. + */ + +#ifndef BUTIL_GPERFTOOLS_PROFILER_H_ +#define BUTIL_GPERFTOOLS_PROFILER_H_ + +/* Annoying stuff for windows; makes sure clients can import these functions */ +#ifndef BRPC_DLL_DECL +# ifdef _WIN32 +# define BRPC_DLL_DECL __declspec(dllimport) +# else +# define BRPC_DLL_DECL +# endif +#endif + +/* All this code should be usable from within C apps. */ +#ifdef __cplusplus +extern "C" { +#endif + +/* Start profiling and write profile info into fname, discarding any + * existing profiling data in that file. + * + * This is equivalent to calling ProfilerStartWithOptions(fname, NULL). + */ +BRPC_DLL_DECL int ProfilerStart(const char* fname); + +/* Stop profiling. Can be started again with ProfilerStart(), but + * the currently accumulated profiling data will be cleared. + */ +BRPC_DLL_DECL void ProfilerStop(); + +/* Flush any currently buffered profiling state to the profile file. + * Has no effect if the profiler has not been started. + */ +BRPC_DLL_DECL void ProfilerFlush(); + +/* Returns nonzero if profile is currently enabled, zero if it's not. */ +BRPC_DLL_DECL int ProfilingIsEnabledForAllThreads(); + +/* Routine for registering new threads with the profiler. + */ +BRPC_DLL_DECL void ProfilerRegisterThread(); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* BUTIL_GPERFTOOLS_PROFILER_H_ */ diff --git a/src/butil/gtest_prod_util.h b/src/butil/gtest_prod_util.h new file mode 100644 index 0000000..3fbe0a3 --- /dev/null +++ b/src/butil/gtest_prod_util.h @@ -0,0 +1,85 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_GTEST_PROD_UTIL_H_ +#define BUTIL_GTEST_PROD_UTIL_H_ + +// [ Copied from gtest/gtest_prod.h ] +// When you need to test the private or protected members of a class, +// use the FRIEND_TEST macro to declare your tests as friends of the +// class. For example: +// +// class MyClass { +// private: +// void MyMethod(); +// FRIEND_TEST(MyClassTest, MyMethod); +// }; +// +// class MyClassTest : public testing::Test { +// // ... +// }; +// +// TEST_F(MyClassTest, MyMethod) { +// // Can call MyClass::MyMethod() here. +// } +#define GTEST_FRIEND_TEST(test_case_name, test_name)\ +friend class test_case_name##_##test_name##_Test + +// This is a wrapper for gtest's FRIEND_TEST macro that friends +// test with all possible prefixes. This is very helpful when changing the test +// prefix, because the friend declarations don't need to be updated. +// +// Example usage: +// +// class MyClass { +// private: +// void MyMethod(); +// FRIEND_TEST_ALL_PREFIXES(MyClassTest, MyMethod); +// }; +#define FRIEND_TEST_ALL_PREFIXES(test_case_name, test_name) \ + GTEST_FRIEND_TEST(test_case_name, test_name); \ + GTEST_FRIEND_TEST(test_case_name, DISABLED_##test_name); \ + GTEST_FRIEND_TEST(test_case_name, FLAKY_##test_name) + +// C++ compilers will refuse to compile the following code: +// +// namespace foo { +// class MyClass { +// private: +// FRIEND_TEST_ALL_PREFIXES(MyClassTest, TestMethod); +// bool private_var; +// }; +// } // namespace foo +// +// class MyClassTest::TestMethod() { +// foo::MyClass foo_class; +// foo_class.private_var = true; +// } +// +// Unless you forward declare MyClassTest::TestMethod outside of namespace foo. +// Use FORWARD_DECLARE_TEST to do so for all possible prefixes. +// +// Example usage: +// +// FORWARD_DECLARE_TEST(MyClassTest, TestMethod); +// +// namespace foo { +// class MyClass { +// private: +// FRIEND_TEST_ALL_PREFIXES(::MyClassTest, TestMethod); // NOTE use of :: +// bool private_var; +// }; +// } // namespace foo +// +// class MyClassTest::TestMethod() { +// foo::MyClass foo_class; +// foo_class.private_var = true; +// } + +#define FORWARD_DECLARE_TEST(test_case_name, test_name) \ + class test_case_name##_##test_name##_Test; \ + class test_case_name##_##DISABLED_##test_name##_Test; \ + class test_case_name##_##FLAKY_##test_name##_Test + +#endif // BUTIL_GTEST_PROD_UTIL_H_ diff --git a/src/butil/guid.cc b/src/butil/guid.cc new file mode 100644 index 0000000..04bd139 --- /dev/null +++ b/src/butil/guid.cc @@ -0,0 +1,29 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/guid.h" + +namespace butil { + +bool IsValidGUID(const std::string& guid) { + const size_t kGUIDLength = 36U; + if (guid.length() != kGUIDLength) + return false; + + const std::string hexchars = "0123456789ABCDEF"; + for (uint32_t i = 0; i < guid.length(); ++i) { + char current = guid[i]; + if (i == 8 || i == 13 || i == 18 || i == 23) { + if (current != '-') + return false; + } else { + if (hexchars.find(current) == std::string::npos) + return false; + } + } + + return true; +} + +} // namespace butil diff --git a/src/butil/guid.h b/src/butil/guid.h new file mode 100644 index 0000000..c6aec33 --- /dev/null +++ b/src/butil/guid.h @@ -0,0 +1,32 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_GUID_H_ +#define BUTIL_GUID_H_ + +#include + +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/build_config.h" + +namespace butil { + +// Generate a 128-bit random GUID of the form: "%08X-%04X-%04X-%04X-%012llX". +// If GUID generation fails an empty string is returned. +// The POSIX implementation uses psuedo random number generation to create +// the GUID. The Windows implementation uses system services. +BUTIL_EXPORT std::string GenerateGUID(); + +// Returns true if the input string conforms to the GUID format. +BUTIL_EXPORT bool IsValidGUID(const std::string& guid); + +#if defined(OS_POSIX) +// For unit testing purposes only. Do not use outside of tests. +BUTIL_EXPORT std::string RandomDataToGUIDString(const uint64_t bytes[2]); +#endif + +} // namespace butil + +#endif // BUTIL_GUID_H_ diff --git a/src/butil/guid_posix.cc b/src/butil/guid_posix.cc new file mode 100644 index 0000000..82222d9 --- /dev/null +++ b/src/butil/guid_posix.cc @@ -0,0 +1,28 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/guid.h" + +#include "butil/rand_util.h" +#include "butil/strings/stringprintf.h" + +namespace butil { + +std::string GenerateGUID() { + uint64_t sixteen_bytes[2] = { butil::RandUint64(), butil::RandUint64() }; + return RandomDataToGUIDString(sixteen_bytes); +} + +// TODO(cmasone): Once we're comfortable this works, migrate Windows code to +// use this as well. +std::string RandomDataToGUIDString(const uint64_t bytes[2]) { + return StringPrintf("%08X-%04X-%04X-%04X-%012llX", + static_cast(bytes[0] >> 32), + static_cast((bytes[0] >> 16) & 0x0000ffff), + static_cast(bytes[0] & 0x0000ffff), + static_cast(bytes[1] >> 48), + bytes[1] & 0x0000ffffffffffffULL); +} + +} // namespace guid diff --git a/src/butil/hash.cc b/src/butil/hash.cc new file mode 100644 index 0000000..67fe9d9 --- /dev/null +++ b/src/butil/hash.cc @@ -0,0 +1,18 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/hash.h" + +// Definition in butil/third_party/superfasthash/superfasthash.c. (Third-party +// code did not come with its own header file, so declaring the function here.) +// Note: This algorithm is also in Blink under Source/wtf/StringHasher.h. +extern "C" uint32_t SuperFastHash(const char* data, int len); + +namespace butil { + +uint32_t SuperFastHash(const char* data, int len) { + return ::SuperFastHash(data, len); +} + +} // namespace butil diff --git a/src/butil/hash.h b/src/butil/hash.h new file mode 100644 index 0000000..69468a9 --- /dev/null +++ b/src/butil/hash.h @@ -0,0 +1,38 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_HASH_H_ +#define BUTIL_HASH_H_ + +#include +#include + +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/logging.h" + +namespace butil { + +// WARNING: This hash function should not be used for any cryptographic purpose. +BUTIL_EXPORT uint32_t SuperFastHash(const char* data, int len); + +// Computes a hash of a memory buffer |data| of a given |length|. +// WARNING: This hash function should not be used for any cryptographic purpose. +inline uint32_t Hash(const char* data, size_t length) { + if (length > static_cast(std::numeric_limits::max())) { + NOTREACHED(); + return 0; + } + return SuperFastHash(data, static_cast(length)); +} + +// Computes a hash of a string |str|. +// WARNING: This hash function should not be used for any cryptographic purpose. +inline uint32_t Hash(const std::string& str) { + return Hash(str.data(), str.size()); +} + +} // namespace butil + +#endif // BUTIL_HASH_H_ diff --git a/src/butil/intrusive_ptr.hpp b/src/butil/intrusive_ptr.hpp new file mode 100644 index 0000000..6cec3e8 --- /dev/null +++ b/src/butil/intrusive_ptr.hpp @@ -0,0 +1,296 @@ +#ifndef BUTIL_INTRUSIVE_PTR_HPP +#define BUTIL_INTRUSIVE_PTR_HPP + +// Copyright (c) 2001, 2002 Peter Dimov +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/smart_ptr/intrusive_ptr.html for documentation. +// +// intrusive_ptr +// +// A smart pointer that uses intrusive reference counting. +// +// Relies on unqualified calls to +// +// void intrusive_ptr_add_ref(T * p); +// void intrusive_ptr_release(T * p); +// +// (p != 0) +// +// The object is responsible for destroying itself. + +#include +#include +#include +#include "butil/build_config.h" +#include "butil/containers/hash_tables.h" + +namespace butil { + +namespace detail { + +// NOTE: sp_convertible is different from butil::is_convertible +// (in butil/type_traits.h) that it converts pointers only. Using +// butil::is_convertible results in ctor/dtor issues. +template< class Y, class T > struct sp_convertible { + typedef char (&yes) [1]; + typedef char (&no) [2]; + + static yes f( T* ); + static no f( ... ); + + enum _vt { value = sizeof((f)(static_cast(0))) == sizeof(yes) }; +}; +template< class Y, class T > struct sp_convertible { + enum _vt { value = false }; +}; +template< class Y, class T > struct sp_convertible { + enum _vt { value = sp_convertible::value }; +}; +template struct sp_convertible { + enum _vt { value = sp_convertible::value }; +}; + +struct sp_empty {}; +template< bool > struct sp_enable_if_convertible_impl; +template<> struct sp_enable_if_convertible_impl { typedef sp_empty type; }; +template<> struct sp_enable_if_convertible_impl {}; +template< class Y, class T > struct sp_enable_if_convertible + : public sp_enable_if_convertible_impl::value> {}; + +} // namespace detail + +template class intrusive_ptr { +private: + typedef intrusive_ptr this_type; +public: + typedef T element_type; + intrusive_ptr() BAIDU_NOEXCEPT : px(0) {} + + intrusive_ptr(T * p, bool add_ref = true): px(p) { + if(px != 0 && add_ref) intrusive_ptr_add_ref(px); + } + + template + intrusive_ptr(const intrusive_ptr& rhs, + typename detail::sp_enable_if_convertible::type = detail::sp_empty()) + : px(rhs.get()) { + if(px != 0) intrusive_ptr_add_ref(px); + } + + intrusive_ptr(const intrusive_ptr& rhs): px(rhs.px) { + if(px != 0) intrusive_ptr_add_ref(px); + } + + ~intrusive_ptr() { + if(px != 0) intrusive_ptr_release(px); + } + + template intrusive_ptr & operator=(const intrusive_ptr& rhs) { + this_type(rhs).swap(*this); + return *this; + } + +// Move support +#if defined(BUTIL_CXX11_ENABLED) + intrusive_ptr(intrusive_ptr && rhs) BAIDU_NOEXCEPT : px(rhs.px) { + rhs.px = 0; + } + + intrusive_ptr & operator=(intrusive_ptr && rhs) BAIDU_NOEXCEPT { + this_type(static_cast< intrusive_ptr && >(rhs)).swap(*this); + return *this; + } +#endif + + intrusive_ptr & operator=(const intrusive_ptr& rhs) { + this_type(rhs).swap(*this); + return *this; + } + + intrusive_ptr & operator=(T * rhs) { + this_type(rhs).swap(*this); + return *this; + } + + void reset() BAIDU_NOEXCEPT { + this_type().swap(*this); + } + + void reset(T * rhs) { + this_type(rhs).swap(*this); + } + + void reset(T * rhs, bool add_ref) { + this_type(rhs, add_ref).swap(*this); + } + + T * get() const BAIDU_NOEXCEPT { + return px; + } + + T * detach() BAIDU_NOEXCEPT { + T * ret = px; + px = 0; + return ret; + } + + T & operator*() const { + return *px; + } + + T * operator->() const { + return px; + } + + // implicit conversion to "bool" +#if defined(BUTIL_CXX11_ENABLED) + explicit operator bool () const BAIDU_NOEXCEPT { + return px != 0; + } +#elif defined(__CINT__) + operator bool () const BAIDU_NOEXCEPT { + return px != 0; + } +#elif (defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ < 304)) + typedef element_type * (this_type::*unspecified_bool_type)() const; + operator unspecified_bool_type() const BAIDU_NOEXCEPT { + return px == 0? 0: &this_type::get; + } +#else + typedef element_type * this_type::*unspecified_bool_type; + operator unspecified_bool_type() const BAIDU_NOEXCEPT { + return px == 0? 0: &this_type::px; + } +#endif + + // operator! is redundant, but some compilers need it + bool operator! () const BAIDU_NOEXCEPT { + return px == 0; + } + + void swap(intrusive_ptr & rhs) BAIDU_NOEXCEPT { + T * tmp = px; + px = rhs.px; + rhs.px = tmp; + } + +private: + T * px; +}; + +template +inline bool operator==(const intrusive_ptr& a, const intrusive_ptr& b) { + return a.get() == b.get(); +} + +template +inline bool operator!=(const intrusive_ptr& a, const intrusive_ptr& b) { + return a.get() != b.get(); +} + +template +inline bool operator==(const intrusive_ptr& a, U * b) { + return a.get() == b; +} + +template +inline bool operator!=(const intrusive_ptr& a, U * b) { + return a.get() != b; +} + +template +inline bool operator==(T * a, const intrusive_ptr& b) { + return a == b.get(); +} + +template +inline bool operator!=(T * a, const intrusive_ptr& b) { + return a != b.get(); +} + +#if __GNUC__ == 2 && __GNUC_MINOR__ <= 96 +// Resolve the ambiguity between our op!= and the one in rel_ops +template +inline bool operator!=(const intrusive_ptr& a, const intrusive_ptr& b) { + return a.get() != b.get(); +} +#endif + +#if defined(BUTIL_CXX11_ENABLED) +template +inline bool operator==(const intrusive_ptr& p, std::nullptr_t) BAIDU_NOEXCEPT { + return p.get() == 0; +} +template +inline bool operator==(std::nullptr_t, const intrusive_ptr& p) BAIDU_NOEXCEPT { + return p.get() == 0; +} + +template +inline bool operator!=(const intrusive_ptr& p, std::nullptr_t) BAIDU_NOEXCEPT { + return p.get() != 0; +} +template +inline bool operator!=(std::nullptr_t, const intrusive_ptr& p) BAIDU_NOEXCEPT { + return p.get() != 0; +} +#endif // BUTIL_CXX11_ENABLED + +template +inline bool operator<(const intrusive_ptr& a, const intrusive_ptr& b) { + return std::less()(a.get(), b.get()); +} + +template void swap(intrusive_ptr & lhs, intrusive_ptr & rhs) { + lhs.swap(rhs); +} + +// mem_fn support + +template T * get_pointer(const intrusive_ptr& p) { + return p.get(); +} + +template intrusive_ptr static_pointer_cast(const intrusive_ptr& p) { + return static_cast(p.get()); +} + +template intrusive_ptr const_pointer_cast(const intrusive_ptr& p) { + return const_cast(p.get()); +} + +template intrusive_ptr dynamic_pointer_cast(const intrusive_ptr& p) { + return dynamic_cast(p.get()); +} + +template std::ostream & operator<< (std::ostream & os, const intrusive_ptr& p) { + os << p.get(); + return os; +} + +} // namespace butil + +// hash_value +namespace BUTIL_HASH_NAMESPACE { + +#if defined(COMPILER_GCC) +template +struct hash > { + std::size_t operator()(const butil::intrusive_ptr& p) const { + return hash()(p.get()); + } +}; +#elif defined(COMPILER_MSVC) +template +inline size_t hash_value(const butil::intrusive_ptr& sp) { + return hash_value(p.get()); +} +#endif // COMPILER + +} // namespace BUTIL_HASH_NAMESPACE + +#endif // BUTIL_INTRUSIVE_PTR_HPP diff --git a/src/butil/iobuf.cpp b/src/butil/iobuf.cpp new file mode 100644 index 0000000..a743f79 --- /dev/null +++ b/src/butil/iobuf.cpp @@ -0,0 +1,2281 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// iobuf - A non-continuous zero-copied buffer + +// Date: Thu Nov 22 13:57:56 CST 2012 + +#include "butil/ssl_compat.h" // BIO_fd_non_fatal_error +#include +#include // SSL_* +#ifdef USE_MESALINK +#include +#include +#endif +#include // syscall +#include // O_RDONLY +#include // errno +#include // CHAR_BIT +#include // std::min +#include // std::numeric_limits +#include // std::invalid_argument +#include // gflags +#include "butil/build_config.h" // ARCH_CPU_X86_64 +#include "butil/atomicops.h" // butil::atomic +#include "butil/thread_local.h" // thread_atexit +#include "butil/macros.h" // BAIDU_CASSERT +#include "butil/logging.h" // CHECK, LOG +#include "butil/fd_guard.h" // butil::fd_guard +#include "butil/iobuf.h" +#include "butil/iobuf_profiler.h" + +namespace butil { +static size_t default_block_size = 8192; + +size_t GetDefaultBlockSize() { + return default_block_size; +} + +// This is not thread safe +void SetDefaultBlockSize(size_t block_size) { + if (block_size <= 0) { + LOG(FATAL) << "block_size " << block_size << " should be bigger than 0!!!"; + } + if (block_size / 4096 * 4096 != block_size) { + LOG(FATAL) << "block_size " << block_size << " should be multiply of 4096!!!"; + } + LOG(INFO) << "Update default_block_size from " << default_block_size << " to " << block_size; + default_block_size = block_size; +} + +namespace iobuf { + +DEFINE_int32(iobuf_aligned_buf_block_size, 0, "iobuf aligned buf block size"); + +typedef ssize_t (*iov_function)(int fd, const struct iovec *vector, + int count, off_t offset); + +// Userpsace preadv +static ssize_t user_preadv(int fd, const struct iovec *vector, + int count, off_t offset) { + ssize_t total_read = 0; + for (int i = 0; i < count; ++i) { + const ssize_t rc = ::pread(fd, vector[i].iov_base, vector[i].iov_len, offset); + if (rc <= 0) { + return total_read > 0 ? total_read : rc; + } + total_read += rc; + offset += rc; + if (rc < (ssize_t)vector[i].iov_len) { + break; + } + } + return total_read; +} + +static ssize_t user_pwritev(int fd, const struct iovec* vector, + int count, off_t offset) { + ssize_t total_write = 0; + for (int i = 0; i < count; ++i) { + const ssize_t rc = ::pwrite(fd, vector[i].iov_base, vector[i].iov_len, offset); + if (rc <= 0) { + return total_write > 0 ? total_write : rc; + } + total_write += rc; + offset += rc; + if (rc < (ssize_t)vector[i].iov_len) { + break; + } + } + return total_write; +} + +#if ARCH_CPU_X86_64 + +#ifndef SYS_preadv +#define SYS_preadv 295 +#endif // SYS_preadv + +#ifndef SYS_pwritev +#define SYS_pwritev 296 +#endif // SYS_pwritev + +// SYS_preadv/SYS_pwritev is available since Linux 2.6.30 +static ssize_t sys_preadv(int fd, const struct iovec *vector, + int count, off_t offset) { + return syscall(SYS_preadv, fd, vector, count, offset); +} + +static ssize_t sys_pwritev(int fd, const struct iovec *vector, + int count, off_t offset) { + return syscall(SYS_pwritev, fd, vector, count, offset); +} + +inline iov_function get_preadv_func() { +#if defined(OS_MACOSX) + return user_preadv; +#endif + butil::fd_guard fd(open("/dev/zero", O_RDONLY)); + if (fd < 0) { + PLOG(WARNING) << "Fail to open /dev/zero"; + return user_preadv; + } + char dummy[1]; + iovec vec = { dummy, sizeof(dummy) }; + const int rc = syscall(SYS_preadv, (int)fd, &vec, 1, 0); + if (rc < 0) { + PLOG(WARNING) << "The kernel doesn't support SYS_preadv, " + " use user_preadv instead"; + return user_preadv; + } + return sys_preadv; +} + +inline iov_function get_pwritev_func() { + butil::fd_guard fd(open("/dev/null", O_WRONLY)); + if (fd < 0) { + PLOG(ERROR) << "Fail to open /dev/null"; + return user_pwritev; + } +#if defined(OS_MACOSX) + return user_pwritev; +#endif + char dummy[1]; + iovec vec = { dummy, sizeof(dummy) }; + const int rc = syscall(SYS_pwritev, (int)fd, &vec, 1, 0); + if (rc < 0) { + PLOG(WARNING) << "The kernel doesn't support SYS_pwritev, " + " use user_pwritev instead"; + return user_pwritev; + } + return sys_pwritev; +} + +#else // ARCH_CPU_X86_64 + +#warning "We don't check if the kernel supports SYS_preadv or SYS_pwritev on non-X86_64, use implementation on pread/pwrite directly." + +inline iov_function get_preadv_func() { + return user_preadv; +} + +inline iov_function get_pwritev_func() { + return user_pwritev; +} + +#endif // ARCH_CPU_X86_64 + +#if defined(__riscv) && defined(__riscv_vector) && __has_include() +#include + +// RVV-optimized memory copy using VL-agnostic intrinsics. +// Uses largest available LMUL (e8m8) for maximum vector width. +// Falls back to memcpy for small copies (< 64 bytes). +static inline void* cp_rvv(void* __restrict dest, const void* __restrict src, + size_t n) { + if (n < 64) { + return memcpy(dest, src, n); + } + char* d = static_cast(dest); + const char* s = static_cast(src); + size_t vl; + for (size_t i = 0; i < n; i += vl) { + vl = __riscv_vsetvl_e8m8(n - i); + vuint8m8_t data = + __riscv_vle8_v_u8m8( + reinterpret_cast(s + i), vl); + __riscv_vse8_v_u8m8( + reinterpret_cast(d + i), data, vl); + } + return dest; +} +#define HAS_RVV_CP +#endif + +void* cp(void *__restrict dest, const void *__restrict src, size_t n) { +#if defined(HAS_RVV_CP) + return cp_rvv(dest, src, n); +#else + // memcpy in gcc 4.8 seems to be faster enough. + return memcpy(dest, src, n); +#endif +} + +// Function pointers to allocate or deallocate memory for a IOBuf::Block +void* (*blockmem_allocate)(size_t) = ::malloc; +void (*blockmem_deallocate)(void*) = ::free; + +void remove_tls_block_chain(); + +// Use default function pointers +void reset_blockmem_allocate_and_deallocate() { + // There maybe block allocated by previous hooks, it's wrong to free them using + // mismatched hook. + remove_tls_block_chain(); + blockmem_allocate = ::malloc; + blockmem_deallocate = ::free; +} + +butil::static_atomic g_nblock = BUTIL_STATIC_ATOMIC_INIT(0); +butil::static_atomic g_blockmem = BUTIL_STATIC_ATOMIC_INIT(0); +butil::static_atomic g_newbigview = BUTIL_STATIC_ATOMIC_INIT(0); + +void inc_g_nblock() { + g_nblock.fetch_add(1, butil::memory_order_relaxed); +} +void dec_g_nblock() { + g_nblock.fetch_sub(1, butil::memory_order_relaxed); +} + +void inc_g_blockmem() { + g_blockmem.fetch_add(1, butil::memory_order_relaxed); +} +void dec_g_blockmem() { + g_blockmem.fetch_sub(1, butil::memory_order_relaxed); +} + +} // namespace iobuf + +size_t IOBuf::block_count() { + return iobuf::g_nblock.load(butil::memory_order_relaxed); +} + +size_t IOBuf::block_memory() { + return iobuf::g_blockmem.load(butil::memory_order_relaxed); +} + +size_t IOBuf::new_bigview_count() { + return iobuf::g_newbigview.load(butil::memory_order_relaxed); +} + +namespace iobuf { + +// for unit test +int block_shared_count(IOBuf::Block const* b) { return b->ref_count(); } + +IOBuf::Block* get_portal_next(IOBuf::Block const* b) { + return b->u.portal_next; +} + +uint32_t block_cap(IOBuf::Block const* b) { + return b->cap; +} + +uint32_t block_size(IOBuf::Block const* b) { + return b->size; +} + +inline IOBuf::Block* create_block_aligned(size_t block_size, size_t alignment) { + if (block_size > 0xFFFFFFFFULL) { + LOG(FATAL) << "block_size=" << block_size << " is too large"; + return NULL; + } + char* mem = (char*)iobuf::blockmem_allocate(block_size); + if (mem == NULL) { + return NULL; + } + char* data = mem + sizeof(IOBuf::Block); + // change data pointer & data size make align satisfied + size_t adder = (-reinterpret_cast(data)) & (alignment - 1); + size_t size = + (block_size - sizeof(IOBuf::Block) - adder) & ~(alignment - 1); + return new (mem) IOBuf::Block(data + adder, size); +} + +// === Share TLS blocks between appending operations === + +static __thread TLSData g_tls_data = { NULL, 0, false }; + +// Used in release_tls_block() +TLSData* get_g_tls_data() { return &g_tls_data; } +// Used in UT +IOBuf::Block* get_tls_block_head() { return g_tls_data.block_head; } +int get_tls_block_count() { return g_tls_data.num_blocks; } + +// Number of blocks that can't be returned to TLS which has too many block +// already. This counter should be 0 in most scenarios, otherwise performance +// of appending functions in IOPortal may be lowered. +static butil::static_atomic g_num_hit_tls_threshold = BUTIL_STATIC_ATOMIC_INIT(0); + +void inc_g_num_hit_tls_threshold() { + g_num_hit_tls_threshold.fetch_add(1, butil::memory_order_relaxed); +} + +void dec_g_num_hit_tls_threshold() { + g_num_hit_tls_threshold.fetch_sub(1, butil::memory_order_relaxed); +} + +// Called in UT. +void remove_tls_block_chain() { + TLSData& tls_data = g_tls_data; + IOBuf::Block* b = tls_data.block_head; + if (!b) { + return; + } + tls_data.block_head = NULL; + int n = 0; + do { + IOBuf::Block* const saved_next = b->u.portal_next; + b->dec_ref(); + b = saved_next; + ++n; + } while (b); + CHECK_EQ(n, tls_data.num_blocks); + tls_data.num_blocks = 0; +} + +// Get a (non-full) block from TLS. +// Notice that the block is not removed from TLS. +IOBuf::Block* share_tls_block() { + TLSData& tls_data = g_tls_data; + IOBuf::Block* const b = tls_data.block_head; + if (b != NULL && !b->full()) { + return b; + } + IOBuf::Block* new_block = NULL; + if (b) { + new_block = b; + while (new_block && new_block->full()) { + IOBuf::Block* const saved_next = new_block->u.portal_next; + new_block->dec_ref(); + --tls_data.num_blocks; + new_block = saved_next; + } + } else if (!tls_data.registered) { + tls_data.registered = true; + // Only register atexit at the first time + butil::thread_atexit(remove_tls_block_chain); + } + if (!new_block) { + new_block = create_block(); // may be NULL + if (new_block) { + ++tls_data.num_blocks; + } + } + tls_data.block_head = new_block; + return new_block; +} + +// Return chained blocks to TLS. +// NOTE: b MUST be non-NULL and all blocks linked SHOULD not be full. +void release_tls_block_chain(IOBuf::Block* b) { + TLSData& tls_data = g_tls_data; + size_t n = 0; + if (tls_data.num_blocks >= max_blocks_per_thread()) { + do { + ++n; + IOBuf::Block* const saved_next = b->u.portal_next; + b->dec_ref(); + b = saved_next; + } while (b); + g_num_hit_tls_threshold.fetch_add(n, butil::memory_order_relaxed); + return; + } + IOBuf::Block* first_b = b; + IOBuf::Block* last_b = NULL; + do { + ++n; + CHECK(!b->full()); + if (b->u.portal_next == NULL) { + last_b = b; + break; + } + b = b->u.portal_next; + } while (true); + last_b->u.portal_next = tls_data.block_head; + tls_data.block_head = first_b; + tls_data.num_blocks += n; + if (!tls_data.registered) { + tls_data.registered = true; + butil::thread_atexit(remove_tls_block_chain); + } +} + +// Get and remove one (non-full) block from TLS. If TLS is empty, create one. +IOBuf::Block* acquire_tls_block() { + TLSData& tls_data = g_tls_data; + IOBuf::Block* b = tls_data.block_head; + if (!b) { + return create_block(); + } + while (b->full()) { + IOBuf::Block* const saved_next = b->u.portal_next; + b->dec_ref(); + tls_data.block_head = saved_next; + --tls_data.num_blocks; + b = saved_next; + if (!b) { + return create_block(); + } + } + tls_data.block_head = b->u.portal_next; + --tls_data.num_blocks; + b->u.portal_next = NULL; + return b; +} + +inline IOBuf::BlockRef* acquire_blockref_array(size_t cap) { + iobuf::g_newbigview.fetch_add(1, butil::memory_order_relaxed); + return new IOBuf::BlockRef[cap]; +} + +inline IOBuf::BlockRef* acquire_blockref_array() { + return acquire_blockref_array(IOBuf::INITIAL_CAP); +} + +inline void release_blockref_array(IOBuf::BlockRef* refs, size_t cap) { + delete[] refs; +} + +} // namespace iobuf + +size_t IOBuf::block_count_hit_tls_threshold() { + return iobuf::g_num_hit_tls_threshold.load(butil::memory_order_relaxed); +} + +BAIDU_CASSERT(sizeof(IOBuf::SmallView) == sizeof(IOBuf::BigView), + sizeof_small_and_big_view_should_equal); + +const IOBuf::Area IOBuf::INVALID_AREA; + +IOBuf::IOBuf(const IOBuf& rhs) { + if (rhs._small()) { + _sv = rhs._sv; + if (_sv.refs[0].block) { + _sv.refs[0].block->inc_ref(); + } + if (_sv.refs[1].block) { + _sv.refs[1].block->inc_ref(); + } + } else { + _bv.magic = -1; + _bv.start = 0; + _bv.nref = rhs._bv.nref; + _bv.cap_mask = rhs._bv.cap_mask; + _bv.nbytes = rhs._bv.nbytes; + _bv.refs = iobuf::acquire_blockref_array(_bv.capacity()); + for (size_t i = 0; i < _bv.nref; ++i) { + _bv.refs[i] = rhs._bv.ref_at(i); + _bv.refs[i].block->inc_ref(); + } + } +} + +void IOBuf::operator=(const IOBuf& rhs) { + if (this == &rhs) { + return; + } + if (!rhs._small() && !_small() && _bv.cap_mask == rhs._bv.cap_mask) { + // Reuse array of refs + // Remove references to previous blocks. + for (size_t i = 0; i < _bv.nref; ++i) { + _bv.ref_at(i).block->dec_ref(); + } + // References blocks in rhs. + _bv.start = 0; + _bv.nref = rhs._bv.nref; + _bv.nbytes = rhs._bv.nbytes; + for (size_t i = 0; i < _bv.nref; ++i) { + _bv.refs[i] = rhs._bv.ref_at(i); + _bv.refs[i].block->inc_ref(); + } + } else { + this->~IOBuf(); + new (this) IOBuf(rhs); + } +} + +template +void IOBuf::_push_or_move_back_ref_to_smallview(const BlockRef& r) { + BlockRef* const refs = _sv.refs; + if (NULL == refs[0].block) { + refs[0] = r; + if (!MOVE) { + r.block->inc_ref(); + } + return; + } + if (NULL == refs[1].block) { + if (refs[0].block == r.block && + refs[0].offset + refs[0].length == r.offset) { // Merge ref + refs[0].length += r.length; + if (MOVE) { + r.block->dec_ref(); + } + return; + } + refs[1] = r; + if (!MOVE) { + r.block->inc_ref(); + } + return; + } + if (refs[1].block == r.block && + refs[1].offset + refs[1].length == r.offset) { // Merge ref + refs[1].length += r.length; + if (MOVE) { + r.block->dec_ref(); + } + return; + } + // Convert to BigView + BlockRef* new_refs = iobuf::acquire_blockref_array(); + new_refs[0] = refs[0]; + new_refs[1] = refs[1]; + new_refs[2] = r; + const size_t new_nbytes = refs[0].length + refs[1].length + r.length; + if (!MOVE) { + r.block->inc_ref(); + } + _bv.magic = -1; + _bv.start = 0; + _bv.refs = new_refs; + _bv.nref = 3; + _bv.cap_mask = INITIAL_CAP - 1; + _bv.nbytes = new_nbytes; +} +// Explicitly initialize templates. +template void IOBuf::_push_or_move_back_ref_to_smallview(const BlockRef&); +template void IOBuf::_push_or_move_back_ref_to_smallview(const BlockRef&); + +template +void IOBuf::_push_or_move_back_ref_to_bigview(const BlockRef& r) { + BlockRef& back = _bv.ref_at(_bv.nref - 1); + if (back.block == r.block && back.offset + back.length == r.offset) { + // Merge ref + back.length += r.length; + _bv.nbytes += r.length; + if (MOVE) { + r.block->dec_ref(); + } + return; + } + + if (_bv.nref != _bv.capacity()) { + _bv.ref_at(_bv.nref++) = r; + _bv.nbytes += r.length; + if (!MOVE) { + r.block->inc_ref(); + } + return; + } + // resize, don't modify bv until new_refs is fully assigned + const uint32_t new_cap = _bv.capacity() * 2; + BlockRef* new_refs = iobuf::acquire_blockref_array(new_cap); + for (uint32_t i = 0; i < _bv.nref; ++i) { + new_refs[i] = _bv.ref_at(i); + } + new_refs[_bv.nref++] = r; + + // Change other variables + _bv.start = 0; + iobuf::release_blockref_array(_bv.refs, _bv.capacity()); + _bv.refs = new_refs; + _bv.cap_mask = new_cap - 1; + _bv.nbytes += r.length; + if (!MOVE) { + r.block->inc_ref(); + } +} +// Explicitly initialize templates. +template void IOBuf::_push_or_move_back_ref_to_bigview(const BlockRef&); +template void IOBuf::_push_or_move_back_ref_to_bigview(const BlockRef&); + +template +int IOBuf::_pop_or_moveout_front_ref() { + if (_small()) { + if (_sv.refs[0].block != NULL) { + if (!MOVEOUT) { + _sv.refs[0].block->dec_ref(); + } + _sv.refs[0] = _sv.refs[1]; + reset_block_ref(_sv.refs[1]); + return 0; + } + return -1; + } else { + // _bv.nref must be greater than 2 + const uint32_t start = _bv.start; + if (!MOVEOUT) { + _bv.refs[start].block->dec_ref(); + } + if (--_bv.nref > 2) { + _bv.start = (start + 1) & _bv.cap_mask; + _bv.nbytes -= _bv.refs[start].length; + } else { // count==2, fall back to SmallView + BlockRef* const saved_refs = _bv.refs; + const uint32_t saved_cap_mask = _bv.cap_mask; + _sv.refs[0] = saved_refs[(start + 1) & saved_cap_mask]; + _sv.refs[1] = saved_refs[(start + 2) & saved_cap_mask]; + iobuf::release_blockref_array(saved_refs, saved_cap_mask + 1); + } + return 0; + } +} +// Explicitly initialize templates. +template int IOBuf::_pop_or_moveout_front_ref(); +template int IOBuf::_pop_or_moveout_front_ref(); + +int IOBuf::_pop_back_ref() { + if (_small()) { + if (_sv.refs[1].block != NULL) { + _sv.refs[1].block->dec_ref(); + reset_block_ref(_sv.refs[1]); + return 0; + } else if (_sv.refs[0].block != NULL) { + _sv.refs[0].block->dec_ref(); + reset_block_ref(_sv.refs[0]); + return 0; + } + return -1; + } else { + // _bv.nref must be greater than 2 + const uint32_t start = _bv.start; + IOBuf::BlockRef& back = _bv.refs[(start + _bv.nref - 1) & _bv.cap_mask]; + back.block->dec_ref(); + if (--_bv.nref > 2) { + _bv.nbytes -= back.length; + } else { // count==2, fall back to SmallView + BlockRef* const saved_refs = _bv.refs; + const uint32_t saved_cap_mask = _bv.cap_mask; + _sv.refs[0] = saved_refs[start]; + _sv.refs[1] = saved_refs[(start + 1) & saved_cap_mask]; + iobuf::release_blockref_array(saved_refs, saved_cap_mask + 1); + } + return 0; + } +} + +void IOBuf::clear() { + if (_small()) { + if (_sv.refs[0].block != NULL) { + _sv.refs[0].block->dec_ref(); + reset_block_ref(_sv.refs[0]); + + if (_sv.refs[1].block != NULL) { + _sv.refs[1].block->dec_ref(); + reset_block_ref(_sv.refs[1]); + } + } + } else { + for (uint32_t i = 0; i < _bv.nref; ++i) { + _bv.ref_at(i).block->dec_ref(); + } + iobuf::release_blockref_array(_bv.refs, _bv.capacity()); + new (this) IOBuf; + } +} + +size_t IOBuf::pop_front(size_t n) { + const size_t len = length(); + if (n >= len) { + clear(); + return len; + } + const size_t saved_n = n; + while (n) { // length() == 0 does not enter + IOBuf::BlockRef &r = _front_ref(); + if (r.length > n) { + r.offset += n; + r.length -= n; + if (!_small()) { + _bv.nbytes -= n; + } + return saved_n; + } + n -= r.length; + _pop_front_ref(); + } + return saved_n; +} + +bool IOBuf::cut1(void* c) { + if (empty()) { + return false; + } + IOBuf::BlockRef &r = _front_ref(); + *(char*)c = r.block->data[r.offset]; + if (r.length > 1) { + ++r.offset; + --r.length; + if (!_small()) { + --_bv.nbytes; + } + } else { + _pop_front_ref(); + } + return true; +} + +size_t IOBuf::pop_back(size_t n) { + const size_t len = length(); + if (n >= len) { + clear(); + return len; + } + const size_t saved_n = n; + while (n) { // length() == 0 does not enter + IOBuf::BlockRef &r = _back_ref(); + if (r.length > n) { + r.length -= n; + if (!_small()) { + _bv.nbytes -= n; + } + return saved_n; + } + n -= r.length; + _pop_back_ref(); + } + return saved_n; +} + +size_t IOBuf::cutn(IOBuf* out, size_t n) { + const size_t len = length(); + if (n > len) { + n = len; + } + const size_t saved_n = n; + while (n) { // length() == 0 does not enter + IOBuf::BlockRef &r = _front_ref(); + if (r.length <= n) { + n -= r.length; + out->_move_back_ref(r); + _moveout_front_ref(); + } else { + const IOBuf::BlockRef cr = { r.offset, (uint32_t)n, r.block }; + out->_push_back_ref(cr); + + r.offset += n; + r.length -= n; + if (!_small()) { + _bv.nbytes -= n; + } + return saved_n; + } + } + return saved_n; +} + +size_t IOBuf::cutn(void* out, size_t n) { + const size_t len = length(); + if (n > len) { + n = len; + } + const size_t saved_n = n; + while (n) { // length() == 0 does not enter + IOBuf::BlockRef &r = _front_ref(); + if (r.length <= n) { + iobuf::cp(out, r.block->data + r.offset, r.length); + out = (char*)out + r.length; + n -= r.length; + _pop_front_ref(); + } else { + iobuf::cp(out, r.block->data + r.offset, n); + out = (char*)out + n; + r.offset += n; + r.length -= n; + if (!_small()) { + _bv.nbytes -= n; + } + return saved_n; + } + } + return saved_n; +} + +size_t IOBuf::cutn(std::string* out, size_t n) { + if (n == 0) { + return 0; + } + const size_t len = length(); + if (n > len) { + n = len; + } + const size_t old_size = out->size(); + out->resize(out->size() + n); + return cutn(&(*out)[old_size], n); +} + +int IOBuf::_cut_by_char(IOBuf* out, char d) { + const size_t nref = _ref_num(); + size_t n = 0; + + for (size_t i = 0; i < nref; ++i) { + IOBuf::BlockRef const& r = _ref_at(i); + char const* const s = r.block->data + r.offset; + for (uint32_t j = 0; j < r.length; ++j, ++n) { + if (s[j] == d) { + // There's no way cutn/pop_front fails + cutn(out, n); + pop_front(1); + return 0; + } + } + } + + return -1; +} + +int IOBuf::_cut_by_delim(IOBuf* out, char const* dbegin, size_t ndelim) { + typedef unsigned long SigType; + const size_t NMAX = sizeof(SigType); + + if (ndelim > NMAX || ndelim > length()) { + return -1; + } + + SigType dsig = 0; + for (size_t i = 0; i < ndelim; ++i) { + dsig = (dsig << CHAR_BIT) | static_cast(dbegin[i]); + } + + const SigType SIGMASK = + (ndelim == NMAX ? (SigType)-1 : (((SigType)1 << (ndelim * CHAR_BIT)) - 1)); + + const size_t nref = _ref_num(); + SigType sig = 0; + size_t n = 0; + + for (size_t i = 0; i < nref; ++i) { + IOBuf::BlockRef const& r = _ref_at(i); + char const* const s = r.block->data + r.offset; + + for (uint32_t j = 0; j < r.length; ++j, ++n) { + sig = ((sig << CHAR_BIT) | static_cast(s[j])) & SIGMASK; + if (sig == dsig) { + // There's no way cutn/pop_front fails + cutn(out, n + 1 - ndelim); + pop_front(ndelim); + return 0; + } + } + } + + return -1; +} + +// Since cut_into_file_descriptor() allocates iovec on stack, IOV_MAX=1024 +// is too large(in the worst case) for bthreads with small stacks. +static const size_t IOBUF_IOV_MAX = 256; + +ssize_t IOBuf::pcut_into_file_descriptor(int fd, off_t offset, size_t size_hint) { + if (empty()) { + return 0; + } + + const size_t nref = std::min(_ref_num(), IOBUF_IOV_MAX); + struct iovec vec[nref]; + size_t nvec = 0; + size_t cur_len = 0; + + do { + IOBuf::BlockRef const& r = _ref_at(nvec); + vec[nvec].iov_base = r.block->data + r.offset; + vec[nvec].iov_len = r.length; + ++nvec; + cur_len += r.length; + } while (nvec < nref && cur_len < size_hint); + + ssize_t nw = 0; + + if (offset >= 0) { + static iobuf::iov_function pwritev_func = iobuf::get_pwritev_func(); + nw = pwritev_func(fd, vec, nvec, offset); + } else { + nw = ::writev(fd, vec, nvec); + } + if (nw > 0) { + pop_front(nw); + } + return nw; +} + +ssize_t IOBuf::cut_into_writer(IWriter* writer, size_t size_hint) { + if (empty()) { + return 0; + } + const size_t nref = std::min(_ref_num(), IOBUF_IOV_MAX); + struct iovec vec[nref]; + size_t nvec = 0; + size_t cur_len = 0; + + do { + IOBuf::BlockRef const& r = _ref_at(nvec); + vec[nvec].iov_base = r.block->data + r.offset; + vec[nvec].iov_len = r.length; + ++nvec; + cur_len += r.length; + } while (nvec < nref && cur_len < size_hint); + + const ssize_t nw = writer->WriteV(vec, nvec); + if (nw > 0) { + pop_front(nw); + } + return nw; +} + +ssize_t IOBuf::cut_into_SSL_channel(SSL* ssl, int* ssl_error) { + *ssl_error = SSL_ERROR_NONE; + if (empty()) { + return 0; + } + + IOBuf::BlockRef const& r = _ref_at(0); + ERR_clear_error(); + const int nw = SSL_write(ssl, r.block->data + r.offset, r.length); + if (nw > 0) { + pop_front(nw); + } + *ssl_error = SSL_get_error(ssl, nw); + return nw; +} + +ssize_t IOBuf::cut_multiple_into_SSL_channel(SSL* ssl, IOBuf* const* pieces, + size_t count, int* ssl_error) { + ssize_t nw = 0; + *ssl_error = SSL_ERROR_NONE; + for (size_t i = 0; i < count; ) { + if (pieces[i]->empty()) { + ++i; + continue; + } + + ssize_t rc = pieces[i]->cut_into_SSL_channel(ssl, ssl_error); + if (rc > 0) { + nw += rc; + } else { + if (rc < 0) { + if (*ssl_error == SSL_ERROR_WANT_WRITE + || (*ssl_error == SSL_ERROR_SYSCALL + && BIO_fd_non_fatal_error(errno) == 1)) { + // Non fatal error, tell caller to write again + *ssl_error = SSL_ERROR_WANT_WRITE; + } else { + // Other errors are fatal + return rc; + } + } + if (nw == 0) { + nw = rc; // Nothing written yet, overwrite nw + } + break; + } + } + +#ifndef USE_MESALINK + // BIO is disabled for now (see socket.cpp) and the following implementation is + // NOT correct since it doesn't handle the EAGAIN event of BIO_flush +// BIO* wbio = SSL_get_wbio(ssl); +// if (BIO_wpending(wbio) > 0) { +// int rc = BIO_flush(wbio); +// if (rc <= 0 && BIO_fd_non_fatal_error(errno) == 0) { +// // Fatal error during BIO_flush +// *ssl_error = SSL_ERROR_SYSCALL; +// return rc; +// } +// } +#else + int rc = SSL_flush(ssl); + if (rc <= 0) { + *ssl_error = SSL_ERROR_SYSCALL; + return rc; + } +#endif + + return nw; +} + +ssize_t IOBuf::pcut_multiple_into_file_descriptor( + int fd, off_t offset, IOBuf* const* pieces, size_t count) { + if (BAIDU_UNLIKELY(count == 0)) { + return 0; + } + if (1UL == count) { + return pieces[0]->pcut_into_file_descriptor(fd, offset); + } + struct iovec vec[IOBUF_IOV_MAX]; + size_t nvec = 0; + for (size_t i = 0; i < count; ++i) { + const IOBuf* p = pieces[i]; + const size_t nref = p->_ref_num(); + for (size_t j = 0; j < nref && nvec < IOBUF_IOV_MAX; ++j, ++nvec) { + IOBuf::BlockRef const& r = p->_ref_at(j); + vec[nvec].iov_base = r.block->data + r.offset; + vec[nvec].iov_len = r.length; + } + } + + ssize_t nw = 0; + if (offset >= 0) { + static iobuf::iov_function pwritev_func = iobuf::get_pwritev_func(); + nw = pwritev_func(fd, vec, nvec, offset); + } else { + nw = ::writev(fd, vec, nvec); + } + if (nw <= 0) { + return nw; + } + size_t npop_all = nw; + for (size_t i = 0; i < count; ++i) { + npop_all -= pieces[i]->pop_front(npop_all); + if (npop_all == 0) { + break; + } + } + return nw; +} + +ssize_t IOBuf::cut_multiple_into_writer( + IWriter* writer, IOBuf* const* pieces, size_t count) { + if (BAIDU_UNLIKELY(count == 0)) { + return 0; + } + if (1UL == count) { + return pieces[0]->cut_into_writer(writer); + } + struct iovec vec[IOBUF_IOV_MAX]; + size_t nvec = 0; + for (size_t i = 0; i < count; ++i) { + const IOBuf* p = pieces[i]; + const size_t nref = p->_ref_num(); + for (size_t j = 0; j < nref && nvec < IOBUF_IOV_MAX; ++j, ++nvec) { + IOBuf::BlockRef const& r = p->_ref_at(j); + vec[nvec].iov_base = r.block->data + r.offset; + vec[nvec].iov_len = r.length; + } + } + + const ssize_t nw = writer->WriteV(vec, nvec); + if (nw <= 0) { + return nw; + } + size_t npop_all = nw; + for (size_t i = 0; i < count; ++i) { + npop_all -= pieces[i]->pop_front(npop_all); + if (npop_all == 0) { + break; + } + } + return nw; +} + + +void IOBuf::append(const IOBuf& other) { + const size_t nref = other._ref_num(); + for (size_t i = 0; i < nref; ++i) { + _push_back_ref(other._ref_at(i)); + } +} + +void IOBuf::append(const Movable& movable_other) { + if (empty()) { + swap(movable_other.value()); + } else { + butil::IOBuf& other = movable_other.value(); + const size_t nref = other._ref_num(); + for (size_t i = 0; i < nref; ++i) { + _move_back_ref(other._ref_at(i)); + } + if (!other._small()) { + iobuf::release_blockref_array(other._bv.refs, other._bv.capacity()); + } + new (&other) IOBuf; + } +} + +int IOBuf::push_back(char c) { + IOBuf::Block* b = iobuf::share_tls_block(); + if (BAIDU_UNLIKELY(!b)) { + return -1; + } + b->data[b->size] = c; + const IOBuf::BlockRef r = { b->size, 1, b }; + ++b->size; + _push_back_ref(r); + return 0; +} + +int IOBuf::append(char const* s) { + if (BAIDU_LIKELY(s != NULL)) { + return append(s, strlen(s)); + } + return -1; +} + +int IOBuf::append(void const* data, size_t count) { + if (BAIDU_UNLIKELY(!data)) { + return -1; + } + if (count == 1) { + return push_back(*((char const*)data)); + } + size_t total_nc = 0; + while (total_nc < count) { // excluded count == 0 + IOBuf::Block* b = iobuf::share_tls_block(); + if (BAIDU_UNLIKELY(!b)) { + return -1; + } + const size_t nc = std::min(count - total_nc, b->left_space()); + iobuf::cp(b->data + b->size, (char*)data + total_nc, nc); + + const IOBuf::BlockRef r = { (uint32_t)b->size, (uint32_t)nc, b }; + _push_back_ref(r); + b->size += nc; + total_nc += nc; + } + return 0; +} + +int IOBuf::appendv(const const_iovec* vec, size_t n) { + size_t offset = 0; + for (size_t i = 0; i < n;) { + IOBuf::Block* b = iobuf::share_tls_block(); + if (BAIDU_UNLIKELY(!b)) { + return -1; + } + uint32_t total_cp = 0; + for (; i < n; ++i, offset = 0) { + const const_iovec & vec_i = vec[i]; + const size_t nc = std::min(vec_i.iov_len - offset, b->left_space() - total_cp); + iobuf::cp(b->data + b->size + total_cp, (char*)vec_i.iov_base + offset, nc); + total_cp += nc; + offset += nc; + if (offset != vec_i.iov_len) { + break; + } + } + + const IOBuf::BlockRef r = { (uint32_t)b->size, total_cp, b }; + b->size += total_cp; + _push_back_ref(r); + } + return 0; +} + +int IOBuf::append_user_data_with_meta(void* data, + size_t size, + std::function deleter, + uint64_t meta) { + if (size > 0xFFFFFFFFULL - 100) { + LOG(FATAL) << "data_size=" << size << " is too large"; + return -1; + } + if (!deleter) { + deleter = ::free; + } + if (!size) { + deleter(data); + return 0; + } + char* mem = (char*)malloc(sizeof(IOBuf::Block) + sizeof(UserDataExtension)); + if (mem == NULL) { + return -1; + } + IOBuf::Block* b = new (mem) IOBuf::Block((char*)data, size, std::move(deleter)); + b->u.data_meta = meta; + const IOBuf::BlockRef r = { 0, b->cap, b }; + _move_back_ref(r); + return 0; +} + +uint64_t IOBuf::get_first_data_meta() { + if (_ref_num() == 0) { + return 0; + } + IOBuf::BlockRef const& r = _ref_at(0); + if (!(r.block->flags & IOBUF_BLOCK_FLAGS_USER_DATA)) { + return 0; + } + return r.block->u.data_meta; +} + +int IOBuf::resize(size_t n, char c) { + const size_t saved_len = length(); + if (n < saved_len) { + pop_back(saved_len - n); + return 0; + } + const size_t count = n - saved_len; + size_t total_nc = 0; + while (total_nc < count) { // excluded count == 0 + IOBuf::Block* b = iobuf::share_tls_block(); + if (BAIDU_UNLIKELY(!b)) { + return -1; + } + const size_t nc = std::min(count - total_nc, b->left_space()); + memset(b->data + b->size, c, nc); + + const IOBuf::BlockRef r = { (uint32_t)b->size, (uint32_t)nc, b }; + _push_back_ref(r); + b->size += nc; + total_nc += nc; + } + return 0; +} + +// NOTE: We don't use C++ bitwise fields which make copying slower. +static const int REF_INDEX_BITS = 19; +static const int REF_OFFSET_BITS = 15; +static const int AREA_SIZE_BITS = 30; +static const uint32_t MAX_REF_INDEX = (((uint32_t)1) << REF_INDEX_BITS) - 1; +static const uint32_t MAX_REF_OFFSET = (((uint32_t)1) << REF_OFFSET_BITS) - 1; +static const uint32_t MAX_AREA_SIZE = (((uint32_t)1) << AREA_SIZE_BITS) - 1; + +inline IOBuf::Area make_area(uint32_t ref_index, uint32_t ref_offset, + uint32_t size) { + if (ref_index > MAX_REF_INDEX || + ref_offset > MAX_REF_OFFSET || + size > MAX_AREA_SIZE) { + LOG(ERROR) << "Too big parameters!"; + return IOBuf::INVALID_AREA; + } + return (((uint64_t)ref_index) << (REF_OFFSET_BITS + AREA_SIZE_BITS)) + | (((uint64_t)ref_offset) << AREA_SIZE_BITS) + | size; +} +inline uint32_t get_area_ref_index(IOBuf::Area c) { + return (c >> (REF_OFFSET_BITS + AREA_SIZE_BITS)) & MAX_REF_INDEX; +} +inline uint32_t get_area_ref_offset(IOBuf::Area c) { + return (c >> AREA_SIZE_BITS) & MAX_REF_OFFSET; +} +inline uint32_t get_area_size(IOBuf::Area c) { + return (c & MAX_AREA_SIZE); +} + +IOBuf::Area IOBuf::reserve(size_t count) { + IOBuf::Area result = INVALID_AREA; + size_t total_nc = 0; + while (total_nc < count) { // excluded count == 0 + IOBuf::Block* b = iobuf::share_tls_block(); + if (BAIDU_UNLIKELY(!b)) { + return INVALID_AREA; + } + const size_t nc = std::min(count - total_nc, b->left_space()); + const IOBuf::BlockRef r = { (uint32_t)b->size, (uint32_t)nc, b }; + _push_back_ref(r); + if (total_nc == 0) { + // Encode the area at first time. Notice that the pushed ref may + // be merged with existing ones. + result = make_area(_ref_num() - 1, _back_ref().length - nc, count); + } + total_nc += nc; + b->size += nc; + } + return result; +} + +int IOBuf::unsafe_assign(Area area, const void* data) { + if (area == INVALID_AREA || data == NULL) { + LOG(ERROR) << "Invalid parameters"; + return -1; + } + const uint32_t ref_index = get_area_ref_index(area); + uint32_t ref_offset = get_area_ref_offset(area); + uint32_t length = get_area_size(area); + const size_t nref = _ref_num(); + for (size_t i = ref_index; i < nref; ++i) { + IOBuf::BlockRef& r = _ref_at(i); + // NOTE: we can't check if the block is shared with another IOBuf or + // not since even a single IOBuf may reference a block multiple times + // (by different BlockRef-s) + + const size_t nc = std::min(length, r.length - ref_offset); + iobuf::cp(r.block->data + r.offset + ref_offset, data, nc); + if (length == nc) { + return 0; + } + ref_offset = 0; + length -= nc; + data = (char*)data + nc; + } + + // Use check because we need to see the stack here. + CHECK(false) << "IOBuf(" << size() << ", nref=" << _ref_num() + << ") is shorter than what we reserved(" + << "ref=" << get_area_ref_index(area) + << " off=" << get_area_ref_offset(area) + << " size=" << get_area_size(area) + << "), this assignment probably corrupted something..."; + return -1; +} + +size_t IOBuf::append_to(IOBuf* buf, size_t n, size_t pos) const { + const size_t nref = _ref_num(); + // Skip `pos' bytes. `offset' is the starting position in starting BlockRef. + size_t offset = pos; + size_t i = 0; + for (; offset != 0 && i < nref; ++i) { + IOBuf::BlockRef const& r = _ref_at(i); + if (offset < (size_t)r.length) { + break; + } + offset -= r.length; + } + size_t m = n; + for (; m != 0 && i < nref; ++i) { + IOBuf::BlockRef const& r = _ref_at(i); + const size_t nc = std::min(m, (size_t)r.length - offset); + const IOBuf::BlockRef r2 = { (uint32_t)(r.offset + offset), + (uint32_t)nc, r.block }; + buf->_push_back_ref(r2); + offset = 0; + m -= nc; + } + // If nref == 0, here returns 0 correctly + return n - m; +} + +size_t IOBuf::copy_to(void* d, size_t n, size_t pos) const { + const size_t nref = _ref_num(); + // Skip `pos' bytes. `offset' is the starting position in starting BlockRef. + size_t offset = pos; + size_t i = 0; + for (; offset != 0 && i < nref; ++i) { + IOBuf::BlockRef const& r = _ref_at(i); + if (offset < (size_t)r.length) { + break; + } + offset -= r.length; + } + size_t m = n; + for (; m != 0 && i < nref; ++i) { + IOBuf::BlockRef const& r = _ref_at(i); + const size_t nc = std::min(m, (size_t)r.length - offset); + iobuf::cp(d, r.block->data + r.offset + offset, nc); + offset = 0; + d = (char*)d + nc; + m -= nc; + } + // If nref == 0, here returns 0 correctly + return n - m; +} + +size_t IOBuf::copy_to(std::string* s, size_t n, size_t pos) const { + const size_t len = length(); + if (len <= pos) { + return 0; + } + if (n > len - pos) { // note: n + pos may overflow + n = len - pos; + } + s->resize(n); + return copy_to(&(*s)[0], n, pos); +} + +size_t IOBuf::append_to(std::string* s, size_t n, size_t pos) const { + const size_t len = length(); + if (len <= pos) { + return 0; + } + if (n > len - pos) { // note: n + pos may overflow + n = len - pos; + } + const size_t old_size = s->size(); + s->resize(old_size + n); + return copy_to(&(*s)[old_size], n, pos); +} + + +size_t IOBuf::copy_to_cstr(char* s, size_t n, size_t pos) const { + const size_t nc = copy_to(s, n, pos); + s[nc] = '\0'; + return nc; +} + +void const* IOBuf::fetch(void* d, size_t n) const { + if (n <= length()) { + IOBuf::BlockRef const& r0 = _ref_at(0); + if (n <= r0.length) { + return r0.block->data + r0.offset; + } + + iobuf::cp(d, r0.block->data + r0.offset, r0.length); + size_t total_nc = r0.length; + const size_t nref = _ref_num(); + for (size_t i = 1; i < nref; ++i) { + IOBuf::BlockRef const& r = _ref_at(i); + if (n <= r.length + total_nc) { + iobuf::cp((char*)d + total_nc, + r.block->data + r.offset, n - total_nc); + return d; + } + iobuf::cp((char*)d + total_nc, r.block->data + r.offset, r.length); + total_nc += r.length; + } + } + return NULL; +} + +const void* IOBuf::fetch1() const { + if (!empty()) { + const IOBuf::BlockRef& r0 = _front_ref(); + return r0.block->data + r0.offset; + } + return NULL; +} + +std::ostream& operator<<(std::ostream& os, const IOBuf& buf) { + const size_t n = buf.backing_block_num(); + for (size_t i = 0; i < n; ++i) { + StringPiece blk = buf.backing_block(i); + os.write(blk.data(), blk.size()); + } + return os; +} + +bool IOBuf::equals(const butil::StringPiece& s) const { + if (size() != s.size()) { + return false; + } + const size_t nref = _ref_num(); + size_t soff = 0; + for (size_t i = 0; i < nref; ++i) { + const BlockRef& r = _ref_at(i); + if (memcmp(r.block->data + r.offset, s.data() + soff, r.length) != 0) { + return false; + } + soff += r.length; + } + return true; +} + +StringPiece IOBuf::backing_block(size_t i) const { + if (i < _ref_num()) { + const BlockRef& r = _ref_at(i); + return StringPiece(r.block->data + r.offset, r.length); + } + return StringPiece(); +} + +bool IOBuf::equals(const butil::IOBuf& other) const { + const size_t sz1 = size(); + if (sz1 != other.size()) { + return false; + } + if (!sz1) { + return true; + } + const BlockRef& r1 = _ref_at(0); + const char* d1 = r1.block->data + r1.offset; + size_t len1 = r1.length; + const BlockRef& r2 = other._ref_at(0); + const char* d2 = r2.block->data + r2.offset; + size_t len2 = r2.length; + const size_t nref1 = _ref_num(); + const size_t nref2 = other._ref_num(); + size_t i = 1; + size_t j = 1; + do { + const size_t cmplen = std::min(len1, len2); + if (memcmp(d1, d2, cmplen) != 0) { + return false; + } + len1 -= cmplen; + if (!len1) { + if (i >= nref1) { + return true; + } + const BlockRef& r = _ref_at(i++); + d1 = r.block->data + r.offset; + len1 = r.length; + } else { + d1 += cmplen; + } + len2 -= cmplen; + if (!len2) { + if (j >= nref2) { + return true; + } + const BlockRef& r = other._ref_at(j++); + d2 = r.block->data + r.offset; + len2 = r.length; + } else { + d2 += cmplen; + } + } while (true); + return true; +} + +////////////////////////////// IOPortal ////////////////// +IOPortal::~IOPortal() { return_cached_blocks(); } + +IOPortal& IOPortal::operator=(const IOPortal& rhs) { + IOBuf::operator=(rhs); + return *this; +} + +void IOPortal::clear() { + IOBuf::clear(); + return_cached_blocks(); +} + +const int MAX_APPEND_IOVEC = 64; + +ssize_t IOPortal::pappend_from_file_descriptor( + int fd, off_t offset, size_t max_count) { + iovec vec[MAX_APPEND_IOVEC]; + int nvec = 0; + size_t space = 0; + Block* prev_p = NULL; + Block* p = _block; + // Prepare at most MAX_APPEND_IOVEC blocks or space of blocks >= max_count + do { + if (p == NULL) { + p = iobuf::acquire_tls_block(); + if (BAIDU_UNLIKELY(!p)) { + errno = ENOMEM; + return -1; + } + if (prev_p != NULL) { + prev_p->u.portal_next = p; + } else { + _block = p; + } + } + vec[nvec].iov_base = p->data + p->size; + vec[nvec].iov_len = std::min(p->left_space(), max_count - space); + space += vec[nvec].iov_len; + ++nvec; + if (space >= max_count || nvec >= MAX_APPEND_IOVEC) { + break; + } + prev_p = p; + p = p->u.portal_next; + } while (1); + + ssize_t nr = 0; + if (offset < 0) { + nr = readv(fd, vec, nvec); + } else { + static iobuf::iov_function preadv_func = iobuf::get_preadv_func(); + nr = preadv_func(fd, vec, nvec, offset); + } + if (nr <= 0) { // -1 or 0 + if (empty()) { + return_cached_blocks(); + } + return nr; + } + + size_t total_len = nr; + do { + const size_t len = std::min(total_len, _block->left_space()); + total_len -= len; + const IOBuf::BlockRef r = { _block->size, (uint32_t)len, _block }; + _push_back_ref(r); + _block->size += len; + if (_block->full()) { + Block* const saved_next = _block->u.portal_next; + _block->dec_ref(); // _block may be deleted + _block = saved_next; + } + } while (total_len); + return nr; +} + +ssize_t IOPortal::append_from_reader(IReader* reader, size_t max_count) { + iovec vec[MAX_APPEND_IOVEC]; + int nvec = 0; + size_t space = 0; + Block* prev_p = NULL; + Block* p = _block; + // Prepare at most MAX_APPEND_IOVEC blocks or space of blocks >= max_count + do { + if (p == NULL) { + p = iobuf::acquire_tls_block(); + if (BAIDU_UNLIKELY(!p)) { + errno = ENOMEM; + return -1; + } + if (prev_p != NULL) { + prev_p->u.portal_next = p; + } else { + _block = p; + } + } + vec[nvec].iov_base = p->data + p->size; + vec[nvec].iov_len = std::min(p->left_space(), max_count - space); + space += vec[nvec].iov_len; + ++nvec; + if (space >= max_count || nvec >= MAX_APPEND_IOVEC) { + break; + } + prev_p = p; + p = p->u.portal_next; + } while (1); + + const ssize_t nr = reader->ReadV(vec, nvec); + if (nr <= 0) { // -1 or 0 + if (empty()) { + return_cached_blocks(); + } + return nr; + } + + size_t total_len = nr; + do { + const size_t len = std::min(total_len, _block->left_space()); + total_len -= len; + const IOBuf::BlockRef r = { _block->size, (uint32_t)len, _block }; + _push_back_ref(r); + _block->size += len; + if (_block->full()) { + Block* const saved_next = _block->u.portal_next; + _block->dec_ref(); // _block may be deleted + _block = saved_next; + } + } while (total_len); + return nr; +} + + +ssize_t IOPortal::append_from_SSL_channel( + SSL* ssl, int* ssl_error, size_t max_count) { + size_t nr = 0; + do { + if (!_block) { + _block = iobuf::acquire_tls_block(); + if (BAIDU_UNLIKELY(!_block)) { + errno = ENOMEM; + *ssl_error = SSL_ERROR_SYSCALL; + return -1; + } + } + + const size_t read_len = std::min(_block->left_space(), max_count - nr); + ERR_clear_error(); + const int rc = SSL_read(ssl, _block->data + _block->size, read_len); + *ssl_error = SSL_get_error(ssl, rc); + if (rc > 0) { + const IOBuf::BlockRef r = { (uint32_t)_block->size, (uint32_t)rc, _block }; + _push_back_ref(r); + _block->size += rc; + if (_block->full()) { + Block* const saved_next = _block->u.portal_next; + _block->dec_ref(); // _block may be deleted + _block = saved_next; + } + nr += rc; + } else { + if (rc < 0) { + if (*ssl_error == SSL_ERROR_WANT_READ + || (*ssl_error == SSL_ERROR_SYSCALL + && BIO_fd_non_fatal_error(errno) == 1)) { + // Non fatal error, tell caller to read again + *ssl_error = SSL_ERROR_WANT_READ; + } else { + // Other errors are fatal + return rc; + } + } + return (nr > 0 ? nr : rc); + } + } while (nr < max_count); + return nr; +} + +void IOPortal::return_cached_blocks_impl(Block* b) { + iobuf::release_tls_block_chain(b); +} + +IOBuf::Area IOReserveAlignedBuf::reserve(size_t count) { + IOBuf::Area result = INVALID_AREA; + if (_reserved == true) { + LOG(ERROR) << "Already call reserved"; + return result; + } + _reserved = true; + bool is_power_two = _alignment > 0 && (_alignment & (_alignment - 1)); + if (is_power_two != 0) { + LOG(ERROR) << "Invalid alignment, must power of two"; + return INVALID_AREA; + } + count = (count + _alignment - 1) & ~(_alignment - 1); + size_t total_nc = 0; + while (total_nc < count) { + auto block_size = + std::max(_alignment, 4096UL) * 2 + sizeof(IOBuf::Block); + if (iobuf::FLAGS_iobuf_aligned_buf_block_size != 0) { + block_size = iobuf::FLAGS_iobuf_aligned_buf_block_size; + } + auto b = iobuf::create_block_aligned(block_size, _alignment); + if (BAIDU_UNLIKELY(!b)) { + LOG(ERROR) << "Create block failed"; + return result; + } + const size_t nc = std::min(count - total_nc, b->left_space()); + const IOBuf::BlockRef r = {(uint32_t)b->size, (uint32_t)nc, b}; + _push_back_ref(r); + // aligned block is not from tls, release block ref + b->dec_ref(); + if (total_nc == 0) { + // Encode the area at first time. Notice that the pushed ref may + // be merged with existing ones. + result = make_area(_ref_num() - 1, _back_ref().length - nc, count); + } + // add total nc + total_nc += nc; + b->size += nc; + }; + return result; +} + +//////////////// IOBufCutter //////////////// + +IOBufCutter::IOBufCutter(butil::IOBuf* buf) + : _data(NULL) + , _data_end(NULL) + , _block(NULL) + , _buf(buf) { +} + +IOBufCutter::~IOBufCutter() { + if (_block) { + if (_data != _data_end) { + IOBuf::BlockRef& fr = _buf->_front_ref(); + CHECK_EQ(fr.block, _block); + fr.offset = (uint32_t)((char*)_data - _block->data); + fr.length = (uint32_t)((char*)_data_end - (char*)_data); + } else { + _buf->_pop_front_ref(); + } + } +} +bool IOBufCutter::load_next_ref() { + if (_block) { + _buf->_pop_front_ref(); + } + if (!_buf->_ref_num()) { + _data = NULL; + _data_end = NULL; + _block = NULL; + return false; + } else { + const IOBuf::BlockRef& r = _buf->_front_ref(); + _data = r.block->data + r.offset; + _data_end = (char*)_data + r.length; + _block = r.block; + return true; + } +} + +size_t IOBufCutter::slower_copy_to(void* dst, size_t n) { + size_t size = (char*)_data_end - (char*)_data; + if (size == 0) { + if (!load_next_ref()) { + return 0; + } + size = (char*)_data_end - (char*)_data; + if (n <= size) { + memcpy(dst, _data, n); + return n; + } + } + void* const saved_dst = dst; + memcpy(dst, _data, size); + dst = (char*)dst + size; + n -= size; + const size_t nref = _buf->_ref_num(); + for (size_t i = 1; i < nref; ++i) { + const IOBuf::BlockRef& r = _buf->_ref_at(i); + const size_t nc = std::min(n, (size_t)r.length); + memcpy(dst, r.block->data + r.offset, nc); + dst = (char*)dst + nc; + n -= nc; + if (n == 0) { + break; + } + } + return (char*)dst - (char*)saved_dst; +} + +size_t IOBufCutter::cutn(butil::IOBuf* out, size_t n) { + if (n == 0) { + return 0; + } + const size_t size = (char*)_data_end - (char*)_data; + if (n <= size) { + const IOBuf::BlockRef r = { (uint32_t)((char*)_data - _block->data), + (uint32_t)n, + _block }; + out->_push_back_ref(r); + _data = (char*)_data + n; + return n; + } else if (size != 0) { + const IOBuf::BlockRef r = { (uint32_t)((char*)_data - _block->data), + (uint32_t)size, + _block }; + out->_push_back_ref(r); + _buf->_pop_front_ref(); + _data = NULL; + _data_end = NULL; + _block = NULL; + return _buf->cutn(out, n - size) + size; + } else { + if (_block) { + _data = NULL; + _data_end = NULL; + _block = NULL; + _buf->_pop_front_ref(); + } + return _buf->cutn(out, n); + } +} + +size_t IOBufCutter::cutn(void* out, size_t n) { + if (n == 0) { + return 0; + } + const size_t size = (char*)_data_end - (char*)_data; + if (n <= size) { + memcpy(out, _data, n); + _data = (char*)_data + n; + return n; + } else if (size != 0) { + memcpy(out, _data, size); + _buf->_pop_front_ref(); + _data = NULL; + _data_end = NULL; + _block = NULL; + return _buf->cutn((char*)out + size, n - size) + size; + } else { + if (_block) { + _data = NULL; + _data_end = NULL; + _block = NULL; + _buf->_pop_front_ref(); + } + return _buf->cutn(out, n); + } +} + +IOBufAsZeroCopyInputStream::IOBufAsZeroCopyInputStream(const IOBuf& buf) + : _ref_index(0) + , _add_offset(0) + , _byte_count(0) + , _buf(&buf) { +} + +bool IOBufAsZeroCopyInputStream::Next(const void** data, int* size) { + const IOBuf::BlockRef* cur_ref = _buf->_pref_at(_ref_index); + if (cur_ref == NULL) { + return false; + } + *data = cur_ref->block->data + cur_ref->offset + _add_offset; + // Impl. of Backup/Skip guarantees that _add_offset < cur_ref->length. + *size = cur_ref->length - _add_offset; + _byte_count += cur_ref->length - _add_offset; + _add_offset = 0; + ++_ref_index; + return true; +} + +void IOBufAsZeroCopyInputStream::BackUp(int count) { + if (_ref_index > 0) { + const IOBuf::BlockRef* cur_ref = _buf->_pref_at(--_ref_index); + CHECK(_add_offset == 0 && cur_ref->length >= (uint32_t)count) + << "BackUp() is not after a Next()"; + _add_offset = cur_ref->length - count; + _byte_count -= count; + } else { + LOG(FATAL) << "BackUp an empty ZeroCopyInputStream"; + } +} + +// Skips `count` number of bytes. +// Returns true on success, or false if some input error occurred, or `count` +// exceeds the end of the stream. This function may skip up to `count - 1` +// bytes in case of failure. +// +// Preconditions: +// * `count` is non-negative. +// +bool IOBufAsZeroCopyInputStream::Skip(int count) { + const IOBuf::BlockRef* cur_ref = _buf->_pref_at(_ref_index); + while (cur_ref) { + const int left_bytes = cur_ref->length - _add_offset; + if (count < left_bytes) { + _add_offset += count; + _byte_count += count; + return true; + } + count -= left_bytes; + _add_offset = 0; + _byte_count += left_bytes; + cur_ref = _buf->_pref_at(++_ref_index); + } + return (0 == count); +} + +int64_t IOBufAsZeroCopyInputStream::ByteCount() const { + return _byte_count; +} + +IOBufAsZeroCopyOutputStream::IOBufAsZeroCopyOutputStream(IOBuf* buf) + : _buf(buf), _block_size(0), _cur_block(NULL), _byte_count(0) { +} + +IOBufAsZeroCopyOutputStream::IOBufAsZeroCopyOutputStream( + IOBuf *buf, uint32_t block_size) + : _buf(buf) + , _block_size(block_size) + , _cur_block(NULL) + , _byte_count(0) { + + if (_block_size <= offsetof(IOBuf::Block, data)) { + throw std::invalid_argument("block_size is too small"); + } +} + +IOBufAsZeroCopyOutputStream::~IOBufAsZeroCopyOutputStream() { + _release_block(); +} + +bool IOBufAsZeroCopyOutputStream::Next(void** data, int* size) { + if (_cur_block == NULL || _cur_block->full()) { + _release_block(); + if (_block_size > 0) { + _cur_block = iobuf::create_block(_block_size); + } else { + _cur_block = iobuf::acquire_tls_block(); + } + if (_cur_block == NULL) { + return false; + } + } + const IOBuf::BlockRef r = { _cur_block->size, + (uint32_t)_cur_block->left_space(), + _cur_block }; + *data = _cur_block->data + r.offset; + *size = r.length; + _cur_block->size = _cur_block->cap; + _buf->_push_back_ref(r); + _byte_count += r.length; + return true; +} + +void IOBufAsZeroCopyOutputStream::BackUp(int count) { + while (!_buf->empty()) { + IOBuf::BlockRef& r = _buf->_back_ref(); + if (_cur_block) { + // A ordinary BackUp that should be supported by all ZeroCopyOutputStream + // _cur_block must match end of the IOBuf + if (r.block != _cur_block) { + LOG(FATAL) << "r.block=" << r.block + << " does not match _cur_block=" << _cur_block; + return; + } + if (r.offset + r.length != _cur_block->size) { + LOG(FATAL) << "r.offset(" << r.offset << ") + r.length(" + << r.length << ") != _cur_block->size(" + << _cur_block->size << ")"; + return; + } + } else { + // An extended BackUp which is undefined in regular + // ZeroCopyOutputStream. The `count' given by user is larger than + // size of last _cur_block (already released in last iteration). + if (r.block->ref_count() == 1) { + // A special case: the block is only referenced by last + // BlockRef of _buf. Safe to allocate more on the block. + if (r.offset + r.length != r.block->size) { + LOG(FATAL) << "r.offset(" << r.offset << ") + r.length(" + << r.length << ") != r.block->size(" + << r.block->size << ")"; + return; + } + } else if (r.offset + r.length != r.block->size) { + // Last BlockRef does not match end of the block (which is + // used by other IOBuf already). Unsafe to re-reference + // the block and allocate more, just pop the bytes. + _byte_count -= _buf->pop_back(count); + return; + } // else Last BlockRef matches end of the block. Even if the + // block is shared by other IOBuf, it's safe to allocate bytes + // after block->size. + _cur_block = r.block; + _cur_block->inc_ref(); + } + if (BAIDU_LIKELY(r.length > (uint32_t)count)) { + r.length -= count; + if (!_buf->_small()) { + _buf->_bv.nbytes -= count; + } + _cur_block->size -= count; + _byte_count -= count; + // Release block for TLS before quiting BackUp() for other + // code to reuse the block even if this wrapper object is + // not destructed. Example: + // IOBufAsZeroCopyOutputStream wrapper(...); + // ParseFromZeroCopyStream(&wrapper, ...); // Calls BackUp + // IOBuf buf; + // buf.append("foobar"); // can reuse the TLS block. + if (_block_size == 0) { + iobuf::release_tls_block(_cur_block); + _cur_block = NULL; + } + return; + } + _cur_block->size -= r.length; + _byte_count -= r.length; + count -= r.length; + _buf->_pop_back_ref(); + _release_block(); + if (count == 0) { + return; + } + } + LOG_IF(FATAL, count != 0) << "BackUp an empty IOBuf"; +} + +int64_t IOBufAsZeroCopyOutputStream::ByteCount() const { + return _byte_count; +} + +void IOBufAsZeroCopyOutputStream::_release_block() { + if (_block_size > 0) { + if (_cur_block) { + _cur_block->dec_ref(); + } + } else { + iobuf::release_tls_block(_cur_block); + } + _cur_block = NULL; +} + +std::streambuf::int_type IOBufAsInputStreamBuf::underflow() { + size_t block_num = _buf.backing_block_num(); + StringPiece blk; + while (_block_index < block_num) { + blk = _buf.backing_block(_block_index++); + if (!blk.empty()) { + break; + } + } + if (blk.empty()) { + return traits_type::eof(); + } + // const_cast is safe here: setg() takes char* by API contract, but this + // streambuf never writes through it (no overflow/sputc path). + char* p = const_cast(blk.data()); + setg(p, p, p + blk.size()); + return traits_type::to_int_type(*gptr()); +} + +std::streamsize IOBufAsInputStreamBuf::xsgetn(char* s, std::streamsize n) { + auto kIntMax = static_cast(std::numeric_limits::max()); + std::streamsize total = 0; + while (total < n) { + std::streamsize avail = egptr() - gptr(); + if (avail == 0) { + if (underflow() == traits_type::eof()) { + break; + } + avail = egptr() - gptr(); + } + // Cap step at INT_MAX so gbump(int) cannot overflow when a user-data + // block exceeds 2GB. + std::streamsize step = + std::min(std::min(avail, n - total), kIntMax); + iobuf::cp(s + total, gptr(), static_cast(step)); + gbump(static_cast(step)); + total += step; + } + return total; +} + +std::streamsize IOBufAsInputStreamBuf::showmanyc() { + std::streamsize kMax = std::numeric_limits::max(); + std::streamsize n = egptr() - gptr(); + size_t block_num = _buf.backing_block_num(); + for (size_t i = _block_index; i < block_num; ++i) { + const std::streamsize sz = + static_cast(_buf.backing_block(i).size()); + // Saturate instead of overflowing on pathologically large IOBufs. + if (n > kMax - sz) { + return kMax; + } + n += sz; + } + return n; +} + +IOBufAsOutputStreamBuf::~IOBufAsOutputStreamBuf() { shrink(); } + +void IOBufAsOutputStreamBuf::shrink() { + if (pbase() != NULL) { + std::streamsize unused = epptr() - pptr(); + // _zc.BackUp takes int. A single put area never exceeds one block + // (Next() returns int size), so this fits in int by construction; + // the cap is purely defensive. + int kIntMax = std::numeric_limits::max(); + _zc.BackUp(unused > kIntMax ? kIntMax : static_cast(unused)); + setp(NULL, NULL); + } +} + +std::streambuf::int_type IOBufAsOutputStreamBuf::overflow(int_type ch) { + if (traits_type::eq_int_type(ch, traits_type::eof())) { + return traits_type::not_eof(ch); + } + if (!refresh_put_area()) { + return traits_type::eof(); + } + return sputc(traits_type::to_char_type(ch)); +} + +std::streamsize IOBufAsOutputStreamBuf::xsputn( + const char* s, std::streamsize n) { + auto kIntMax = static_cast(std::numeric_limits::max()); + std::streamsize total = 0; + while (total < n) { + std::streamsize avail = epptr() - pptr(); + if (avail == 0) { + if (!refresh_put_area()) { + break; + } + avail = epptr() - pptr(); + if (avail == 0) { + break; + } + } + // Cap step at INT_MAX so pbump(int) cannot overflow when a dedicated + // block exceeds 2GB. + std::streamsize step = + std::min(std::min(avail, n - total), kIntMax); + iobuf::cp(pptr(), s + total, static_cast(step)); + pbump(static_cast(step)); + total += step; + } + return total; +} + +int IOBufAsOutputStreamBuf::sync() { + shrink(); + return 0; +} + +bool IOBufAsOutputStreamBuf::refresh_put_area() { + void* block = NULL; + int size = 0; + if (!_zc.Next(&block, &size)) { + setp(NULL, NULL); + return false; + } + char* p = static_cast(block); + setp(p, p + size); + return true; +} + +IOBufAsSnappySink::IOBufAsSnappySink(butil::IOBuf& buf) + : _cur_buf(NULL), _cur_len(0), _buf(&buf), _buf_stream(&buf) { +} + +void IOBufAsSnappySink::Append(const char* bytes, size_t n) { + if (_cur_len > 0) { + CHECK(bytes == _cur_buf && static_cast(n) <= _cur_len) + << "bytes must be _cur_buf"; + _buf_stream.BackUp(_cur_len - n); + _cur_len = 0; + } else { + _buf->append(bytes, n); + } +} + +char* IOBufAsSnappySink::GetAppendBuffer(size_t length, char* scratch) { + // TODO: butil::IOBuf supports dynamic sized blocks. + if (length <= 8000/*just a hint*/) { + if (_buf_stream.Next(reinterpret_cast(&_cur_buf), &_cur_len)) { + if (_cur_len >= static_cast(length)) { + return _cur_buf; + } else { + _buf_stream.BackUp(_cur_len); + } + } else { + LOG(FATAL) << "Fail to alloc buffer"; + } + } // else no need to try. + _cur_buf = NULL; + _cur_len = 0; + return scratch; +} + +size_t IOBufAsSnappySource::Available() const { + return _buf->length() - _stream.ByteCount(); +} + +void IOBufAsSnappySource::Skip(size_t n) { + _stream.Skip(n); +} + +const char* IOBufAsSnappySource::Peek(size_t* len) { + const char* buffer = NULL; + int res = 0; + if (_stream.Next((const void**)&buffer, &res)) { + *len = res; + // Source::Peek requires no reposition. + _stream.BackUp(*len); + return buffer; + } else { + *len = 0; + return NULL; + } +} + +IOBufAppender::IOBufAppender() + : _data(NULL) + , _data_end(NULL) + , _zc_stream(&_buf) { +} + +size_t IOBufBytesIterator::append_and_forward(butil::IOBuf* buf, size_t n) { + size_t nc = 0; + while (nc < n && _bytes_left != 0) { + const IOBuf::BlockRef& r = _buf->_ref_at(_block_count - 1); + const size_t block_size = _block_end - _block_begin; + const size_t to_copy = std::min(block_size, n - nc); + IOBuf::BlockRef r2 = { (uint32_t)(_block_begin - r.block->data), + (uint32_t)to_copy, r.block }; + buf->_push_back_ref(r2); + _block_begin += to_copy; + _bytes_left -= to_copy; + nc += to_copy; + if (_block_begin == _block_end) { + try_next_block(); + } + } + return nc; +} + +bool IOBufBytesIterator::forward_one_block(const void** data, size_t* size) { + if (_bytes_left == 0) { + return false; + } + const size_t block_size = _block_end - _block_begin; + *data = _block_begin; + *size = block_size; + _bytes_left -= block_size; + try_next_block(); + return true; +} + +} // namespace butil + +void* fast_memcpy(void *__restrict dest, const void *__restrict src, size_t n) { + return butil::iobuf::cp(dest, src, n); +} // namespace butil diff --git a/src/butil/iobuf.h b/src/butil/iobuf.h new file mode 100644 index 0000000..b92a2e3 --- /dev/null +++ b/src/butil/iobuf.h @@ -0,0 +1,942 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// iobuf - A non-continuous zero-copied buffer + +// Date: Thu Nov 22 13:57:56 CST 2012 + +#ifndef BUTIL_IOBUF_H +#define BUTIL_IOBUF_H + +#include // iovec +#include // uint32_t, int64_t +#include +#include // std::istream +#include // std::streambuf +#include // std::string +#include // std::ostream +#include // ZeroCopyInputStream +#include "butil/strings/string_piece.h" // butil::StringPiece +#include "butil/third_party/snappy/snappy-sinksource.h" +#include "butil/zero_copy_stream_as_streambuf.h" +#include "butil/macros.h" +#include "butil/reader_writer.h" +#include "butil/binary_printer.h" + +// For IOBuf::appendv(const const_iovec*, size_t). The only difference of this +// struct from iovec (defined in sys/uio.h) is that iov_base is `const void*' +// which is assignable by const pointers w/o any error. +extern "C" { +struct const_iovec { + const void* iov_base; + size_t iov_len; +}; +#ifndef USE_MESALINK +struct ssl_st; +#else +#define ssl_st MESALINK_SSL +#endif +} + +namespace butil { + +size_t GetDefaultBlockSize(); + +void SetDefaultBlockSize(size_t block_size); + +// IOBuf is a non-continuous buffer that can be cut and combined w/o copying +// payload. It can be read from or flushed into file descriptors as well. +// IOBuf is [thread-compatible]. Namely using different IOBuf in different +// threads simultaneously is safe, and reading a static IOBuf from different +// threads is safe as well. +// IOBuf is [NOT thread-safe]. Modifying a same IOBuf from different threads +// simultaneously is unsafe and likely to crash. +class IOBuf { +friend class IOBufAsZeroCopyInputStream; +friend class IOBufAsZeroCopyOutputStream; +friend class IOBufBytesIterator; +friend class IOBufCutter; +friend class SingleIOBuf; + +public: + static const size_t INITIAL_CAP = 32; // must be power of 2 + + struct Block; + + // can't directly use `struct iovec' here because we also need to access the + // reference counter(nshared) in Block* + struct BlockRef { + // NOTICE: first bit of `offset' is shared with BigView::start + uint32_t offset; + uint32_t length; + Block* block; + }; + + // IOBuf is essentially a tiny queue of BlockRefs. + struct SmallView { + BlockRef refs[2]; + }; + + struct BigView { + int32_t magic; + uint32_t start; + BlockRef* refs; + uint32_t nref; + uint32_t cap_mask; + size_t nbytes; + + const BlockRef& ref_at(uint32_t i) const + { return refs[(start + i) & cap_mask]; } + + BlockRef& ref_at(uint32_t i) + { return refs[(start + i) & cap_mask]; } + + uint32_t capacity() const { return cap_mask + 1; } + }; + + struct Movable { + explicit Movable(IOBuf& v) : _v(&v) { } + IOBuf& value() const { return *_v; } + private: + IOBuf *_v; + }; + + typedef uint64_t Area; + static const Area INVALID_AREA = 0; + + IOBuf(); + IOBuf(const IOBuf&); + IOBuf(const Movable&); + ~IOBuf() { clear(); } + void operator=(const IOBuf&); + void operator=(const Movable&); + void operator=(const char*); + void operator=(const std::string&); + + // Exchange internal fields with another IOBuf. + void swap(IOBuf&); + + // Pop n bytes from front side + // If n == 0, nothing popped; if n >= length(), all bytes are popped + // Returns bytes popped. + size_t pop_front(size_t n); + + // Pop n bytes from back side + // If n == 0, nothing popped; if n >= length(), all bytes are popped + // Returns bytes popped. + size_t pop_back(size_t n); + + // Cut off n bytes from front side and APPEND to `out' + // If n == 0, nothing cut; if n >= length(), all bytes are cut + // Returns bytes cut. + size_t cutn(IOBuf* out, size_t n); + size_t cutn(void* out, size_t n); + size_t cutn(std::string* out, size_t n); + // Cut off 1 byte from the front side and set to *c + // Return true on cut, false otherwise. + bool cut1(void* c); + + // Cut from front side until the characters matches `delim', append + // data before the matched characters to `out'. + // Returns 0 on success, -1 when there's no match (including empty `delim') + // or other errors. + int cut_until(IOBuf* out, char const* delim); + + // std::string version, `delim' could be binary + int cut_until(IOBuf* out, const std::string& delim); + + // Cut at most `size_hint' bytes(approximately) into the writer + // Returns bytes cut on success, -1 otherwise and errno is set. + ssize_t cut_into_writer(IWriter* writer, size_t size_hint = 1024*1024); + + // Cut at most `size_hint' bytes(approximately) into the file descriptor + // Returns bytes cut on success, -1 otherwise and errno is set. + ssize_t cut_into_file_descriptor(int fd, size_t size_hint = 1024*1024); + + // Cut at most `size_hint' bytes(approximately) into the file descriptor at + // a given offset(from the start of the file). The file offset is not changed. + // If `offset' is negative, does exactly what cut_into_file_descriptor does. + // Returns bytes cut on success, -1 otherwise and errno is set. + // + // NOTE: POSIX requires that a file open with the O_APPEND flag should + // not affect pwrite(). However, on Linux, if |fd| is open with O_APPEND, + // pwrite() appends data to the end of the file, regardless of the value + // of |offset|. + ssize_t pcut_into_file_descriptor(int fd, off_t offset /*NOTE*/, + size_t size_hint = 1024*1024); + + // Cut into SSL channel `ssl'. Returns what `SSL_write' returns + // and the ssl error code will be filled into `ssl_error' + ssize_t cut_into_SSL_channel(struct ssl_st* ssl, int* ssl_error); + + // Cut `count' number of `pieces' into the writer. + // Returns bytes cut on success, -1 otherwise and errno is set. + static ssize_t cut_multiple_into_writer( + IWriter* writer, IOBuf* const* pieces, size_t count); + + // Cut `count' number of `pieces' into the file descriptor. + // Returns bytes cut on success, -1 otherwise and errno is set. + static ssize_t cut_multiple_into_file_descriptor( + int fd, IOBuf* const* pieces, size_t count); + + // Cut `count' number of `pieces' into file descriptor `fd' at a given + // offset. The file offset is not changed. + // If `offset' is negative, does exactly what cut_multiple_into_file_descriptor + // does. + // Read NOTE of pcut_into_file_descriptor. + // Returns bytes cut on success, -1 otherwise and errno is set. + static ssize_t pcut_multiple_into_file_descriptor( + int fd, off_t offset, IOBuf* const* pieces, size_t count); + + // Cut `count' number of `pieces' into SSL channel `ssl'. + // Returns bytes cut on success, -1 otherwise and errno is set. + static ssize_t cut_multiple_into_SSL_channel( + struct ssl_st* ssl, IOBuf* const* pieces, size_t count, int* ssl_error); + + // Append another IOBuf to back side, payload of the IOBuf is shared + // rather than copied. + void append(const IOBuf& other); + // Append content of `other' to self and clear `other'. + void append(const Movable& other); + + // =================================================================== + // Following push_back()/append() are just implemented for convenience + // and occasional usages, they're relatively slow because of the overhead + // of frequent BlockRef-management and reference-countings. If you get + // a lot of push_back/append to do, you should use IOBufAppender or + // IOBufBuilder instead, which reduce overhead by owning IOBuf::Block. + // =================================================================== + + // Append a character to back side. (with copying) + // Returns 0 on success, -1 otherwise. + int push_back(char c); + + // Append `data' with `count' bytes to back side. (with copying) + // Returns 0 on success(include count == 0), -1 otherwise. + int append(void const* data, size_t count); + + // Append multiple data to back side in one call, faster than appending + // one by one separately. + // Returns 0 on success, -1 otherwise. + // Example: + // const_iovec vec[] = { { data1, len1 }, + // { data2, len2 }, + // { data3, len3 } }; + // foo.appendv(vec, arraysize(vec)); + int appendv(const const_iovec vec[], size_t n); + int appendv(const iovec* vec, size_t n) + { return appendv((const const_iovec*)vec, n); } + + // Append a c-style string to back side. (with copying) + // Returns 0 on success, -1 otherwise. + // NOTE: Returns 0 when `s' is empty. + int append(char const* s); + + // Append a std::string to back side. (with copying) + // Returns 0 on success, -1 otherwise. + // NOTE: Returns 0 when `s' is empty. + int append(const std::string& s); + + // Append the user-data to back side WITHOUT copying. + // The user-data can be split and shared by smaller IOBufs and will be + // deleted using the deleter func when no IOBuf references it anymore. + int append_user_data(void* data, size_t size, std::function deleter); + + // Append the user-data to back side WITHOUT copying. + // The meta is associated with this piece of user-data. + int append_user_data_with_meta(void* data, size_t size, std::function deleter, uint64_t meta); + + // Get the data meta of the first byte in this IOBuf. + // The meta is specified with append_user_data_with_meta before. + // 0 means the meta is invalid. + uint64_t get_first_data_meta(); + + // Resizes the buf to a length of n characters. + // If n is smaller than the current length, all bytes after n will be + // truncated. + // If n is greater than the current length, the buffer would be append with + // as many |c| as needed to reach a size of n. If c is not specified, + // null-character would be appended. + // Returns 0 on success, -1 otherwise. + int resize(size_t n) { return resize(n, '\0'); } + int resize(size_t n, char c); + + // Reserve `n' uninitialized bytes at back-side. + // Returns an object representing the reserved area, INVALID_AREA on failure. + // NOTE: reserve(0) returns INVALID_AREA. + Area reserve(size_t n); + + // [EXTREMELY UNSAFE] + // Copy `data' to the reserved `area'. `data' must be as long as the + // reserved size. + // Returns 0 on success, -1 otherwise. + // [Rules] + // 1. Make sure the IOBuf to be assigned was NOT cut/pop from front side + // after reserving, otherwise behavior of this function is undefined, + // even if it returns 0. + // 2. Make sure the IOBuf to be assigned was NOT copied to/from another + // IOBuf after reserving to prevent underlying blocks from being shared, + // otherwise the assignment affects all IOBuf sharing the blocks, which + // is probably not what we want. + int unsafe_assign(Area area, const void* data); + + // Append min(n, length()) bytes starting from `pos' at front side to `buf'. + // The real payload is shared rather than copied. + // Returns bytes copied. + size_t append_to(IOBuf* buf, size_t n = (size_t)-1L, size_t pos = 0) const; + + // Explicitly declare this overload as error to avoid copy_to(butil::IOBuf*) + // from being interpreted as copy_to(void*) by the compiler (which causes + // undefined behavior). + size_t copy_to(IOBuf* buf, size_t n = (size_t)-1L, size_t pos = 0) const + // the error attribute in not available in gcc 3.4 +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) + __attribute__ (( error("Call append_to(IOBuf*) instead") )) +#endif + ; + + // Copy min(n, length()) bytes starting from `pos' at front side into `buf'. + // Returns bytes copied. + size_t copy_to(void* buf, size_t n = (size_t)-1L, size_t pos = 0) const; + + // NOTE: first parameter is not std::string& because user may pass in + // a pointer of std::string by mistake, in which case, the void* overload + // would be wrongly called. + size_t copy_to(std::string* s, size_t n = (size_t)-1L, size_t pos = 0) const; + size_t append_to(std::string* s, size_t n = (size_t)-1L, size_t pos = 0) const; + + // Copy min(n, length()) bytes staring from `pos' at front side into + // `cstr' and end it with '\0'. + // `cstr' must be as long as min(n, length())+1. + // Returns bytes copied (not including ending '\0') + size_t copy_to_cstr(char* cstr, size_t n = (size_t)-1L, size_t pos = 0) const; + + // Convert all data in this buffer to a std::string. + std::string to_string() const; + + // Get `n' front-side bytes with minimum copying. Length of `aux_buffer' + // must not be less than `n'. + // Returns: + // NULL - n is greater than length() + // aux_buffer - n bytes are copied into aux_buffer + // internal buffer - the bytes are stored continuously in the internal + // buffer, no copying is needed. This function does not + // add additional reference to the underlying block, + // so user should not change this IOBuf during using + // the internal buffer. + // If n == 0 and buffer is empty, return value is undefined. + const void* fetch(void* aux_buffer, size_t n) const; + // Fetch one character from front side. + // Returns pointer to the character, NULL on empty. + const void* fetch1() const; + + // Remove all data + void clear(); + + // True iff there's no data + bool empty() const; + + // Number of bytes + size_t length() const; + size_t size() const { return length(); } + + // Get number of Blocks in use. block_memory = block_count * BLOCK_SIZE + static size_t block_count(); + static size_t block_memory(); + static size_t new_bigview_count(); + static size_t block_count_hit_tls_threshold(); + + // Equal with a string/IOBuf or not. + bool equals(const butil::StringPiece&) const; + bool equals(const IOBuf& other) const; + + // Get the number of backing blocks + size_t backing_block_num() const { return _ref_num(); } + + // Get #i backing_block, an empty StringPiece is returned if no such block + StringPiece backing_block(size_t i) const; + + // Make a movable version of self + Movable movable() { return Movable(*this); } + +protected: + int _cut_by_char(IOBuf* out, char); + int _cut_by_delim(IOBuf* out, char const* dbegin, size_t ndelim); + + // Returns: true iff this should be viewed as SmallView + bool _small() const; + + template + void _push_or_move_back_ref_to_smallview(const BlockRef&); + template + void _push_or_move_back_ref_to_bigview(const BlockRef&); + + // Push a BlockRef to back side + // NOTICE: All fields of the ref must be initialized or assigned + // properly, or it will ruin this queue + void _push_back_ref(const BlockRef&); + // Move a BlockRef to back side. After calling this function, content of + // the BlockRef will be invalid and should never be used again. + void _move_back_ref(const BlockRef&); + + // Pop a BlockRef from front side. + // Returns: 0 on success and -1 on empty. + int _pop_front_ref() { return _pop_or_moveout_front_ref(); } + + // Move a BlockRef out from front side. + // Returns: 0 on success and -1 on empty. + int _moveout_front_ref() { return _pop_or_moveout_front_ref(); } + + template + int _pop_or_moveout_front_ref(); + + // Pop a BlockRef from back side. + // Returns: 0 on success and -1 on empty. + int _pop_back_ref(); + + // Number of refs in the queue + size_t _ref_num() const; + + // Get reference to front/back BlockRef in the queue + // should not be called if queue is empty or the behavior is undefined + BlockRef& _front_ref(); + const BlockRef& _front_ref() const; + BlockRef& _back_ref(); + const BlockRef& _back_ref() const; + + // Get reference to n-th BlockRef(counting from front) in the queue + // NOTICE: should not be called if queue is empty and the `n' must + // be inside [0, _ref_num()-1] or behavior is undefined + BlockRef& _ref_at(size_t i); + const BlockRef& _ref_at(size_t i) const; + + // Get pointer to n-th BlockRef(counting from front) + // If i is out-of-range, NULL is returned. + const BlockRef* _pref_at(size_t i) const; + +private: + union { + BigView _bv; + SmallView _sv; + }; +}; + +std::ostream& operator<<(std::ostream&, const IOBuf& buf); + +inline bool operator==(const butil::IOBuf& b, const butil::StringPiece& s) +{ return b.equals(s); } +inline bool operator==(const butil::StringPiece& s, const butil::IOBuf& b) +{ return b.equals(s); } +inline bool operator!=(const butil::IOBuf& b, const butil::StringPiece& s) +{ return !b.equals(s); } +inline bool operator!=(const butil::StringPiece& s, const butil::IOBuf& b) +{ return !b.equals(s); } +inline bool operator==(const butil::IOBuf& b1, const butil::IOBuf& b2) +{ return b1.equals(b2); } +inline bool operator!=(const butil::IOBuf& b1, const butil::IOBuf& b2) +{ return !b1.equals(b2); } + +// IOPortal is a subclass of IOBuf that can read from file descriptors. +// Typically used as the buffer to store bytes from sockets. +class IOPortal : public IOBuf { +public: + IOPortal() : _block(NULL) { } + IOPortal(const IOPortal& rhs) : IOBuf(rhs), _block(NULL) { } + ~IOPortal(); + IOPortal& operator=(const IOPortal& rhs); + + // Read at most `max_count' bytes from the reader and append to self. + ssize_t append_from_reader(IReader* reader, size_t max_count); + + // Read at most `max_count' bytes from file descriptor `fd' and + // append to self. + ssize_t append_from_file_descriptor(int fd, size_t max_count); + + // Read at most `max_count' bytes from file descriptor `fd' at a given + // offset and append to self. The file offset is not changed. + // If `offset' is negative, does exactly what append_from_file_descriptor does. + ssize_t pappend_from_file_descriptor(int fd, off_t offset, size_t max_count); + + // Read as many bytes as possible from SSL channel `ssl', and stop until `max_count'. + // Returns total bytes read and the ssl error code will be filled into `ssl_error' + ssize_t append_from_SSL_channel(struct ssl_st* ssl, int* ssl_error, + size_t max_count = 1024*1024); + + // Remove all data inside and return cached blocks. + void clear(); + + // Return cached blocks to TLS. This function should be called by users + // when this IOPortal are cut into intact messages and becomes empty, to + // let continuing code on IOBuf to reuse the blocks. Calling this function + // after each call to append_xxx does not make sense and may hurt + // performance. Read comments on field `_block' below. + void return_cached_blocks(); + +private: + static void return_cached_blocks_impl(Block*); + + // Cached blocks for appending. Notice that the blocks are released + // until return_cached_blocks()/clear()/dtor() are called, rather than + // released after each append_xxx(), which makes messages read from one + // file descriptor more likely to share blocks and have less BlockRefs. + Block* _block; +}; + +class IOReserveAlignedBuf : public IOBuf { +public: + IOReserveAlignedBuf(size_t alignment) + : _alignment(alignment), _reserved(false) {} + Area reserve(size_t count); + +private: + size_t _alignment; + bool _reserved; +}; + +// Specialized utility to cut from IOBuf faster than using corresponding +// methods in IOBuf. +// Designed for efficiently parsing data from IOBuf. +// The cut IOBuf can be appended during cutting. +class IOBufCutter { +public: + explicit IOBufCutter(butil::IOBuf* buf); + ~IOBufCutter(); + + // Cut off n bytes and APPEND to `out' + // Returns bytes cut. + size_t cutn(butil::IOBuf* out, size_t n); + size_t cutn(std::string* out, size_t n); + size_t cutn(void* out, size_t n); + + // Cut off 1 byte from the front side and set to *c + // Return true on cut, false otherwise. + bool cut1(void* data); + + // Copy n bytes into `data' + // Returns bytes copied. + size_t copy_to(void* data, size_t n); + + // Fetch one character. + // Returns pointer to the character, NULL on empty + const void* fetch1(); + + // Pop n bytes from front side + // Returns bytes popped. + size_t pop_front(size_t n); + + // Uncut bytes + size_t remaining_bytes() const; + +private: + size_t slower_copy_to(void* data, size_t n); + bool load_next_ref(); + +private: + void* _data; + void* _data_end; + IOBuf::Block* _block; + IOBuf* _buf; +}; + +// Parse protobuf message from IOBuf. Notice that this wrapper does not change +// source IOBuf, which also should not change during lifetime of the wrapper. +// Even if a IOBufAsZeroCopyInputStream is created but parsed, the source +// IOBuf should not be changed as well becuase constructor of the stream +// saves internal information of the source IOBuf which is assumed to be +// unchanged. +// Example: +// IOBufAsZeroCopyInputStream wrapper(the_iobuf_with_protobuf_format_data); +// some_pb_message.ParseFromZeroCopyStream(&wrapper); +class IOBufAsZeroCopyInputStream + : public google::protobuf::io::ZeroCopyInputStream { +public: + explicit IOBufAsZeroCopyInputStream(const IOBuf&); + + bool Next(const void** data, int* size) override; + void BackUp(int count) override; + bool Skip(int count) override; + int64_t ByteCount() const override; + +private: + int _ref_index; + int _add_offset; + int64_t _byte_count; + const IOBuf* _buf; +}; + +// Serialize protobuf message into IOBuf. This wrapper does not clear source +// IOBuf before appending. You can change the source IOBuf when stream is +// not used(append sth. to the IOBuf, serialize a protobuf message, append +// sth. again, serialize messages again...). This is different from +// IOBufAsZeroCopyInputStream which needs the source IOBuf to be unchanged. +// Example: +// IOBufAsZeroCopyOutputStream wrapper(&the_iobuf_to_put_data_in); +// some_pb_message.SerializeToZeroCopyStream(&wrapper); +// +// NOTE: Blocks are by default shared among all the ZeroCopyOutputStream in one +// thread. If there are many manipulated streams at one time, there may be many +// fragments. You can create a ZeroCopyOutputStream which has its own block by +// passing a positive `block_size' argument to avoid this problem. +class IOBufAsZeroCopyOutputStream + : public google::protobuf::io::ZeroCopyOutputStream { +public: + explicit IOBufAsZeroCopyOutputStream(IOBuf*); + IOBufAsZeroCopyOutputStream(IOBuf*, uint32_t block_size); + ~IOBufAsZeroCopyOutputStream(); + + bool Next(void** data, int* size) override; + void BackUp(int count) override; // `count' can be as long as ByteCount() + int64_t ByteCount() const override; + +private: + void _release_block(); + + IOBuf* _buf; + uint32_t _block_size; + IOBuf::Block *_cur_block; + int64_t _byte_count; +}; + +// Wrap IOBuf into a std::streambuf for std::istream-based parsers +// (e.g. nlohmann::json::parse(std::istream&)). +// +// Read-only view: the streambuf never writes to the source IOBuf. Forward-only +// (seekoff/seekpos are not overridden). +// +// NOTE: The source IOBuf MUST NOT be modified during the lifetime of this +// streambuf, otherwise the StringPieces returned by backing_block() may be +// invalidated and the stream will read garbage or crash. +class IOBufAsInputStreamBuf : public std::streambuf { +public: + // `buf' must outlive this streambuf and must not be modified while the + // streambuf is in use. + explicit IOBufAsInputStreamBuf(const IOBuf& buf) : _buf(buf) {} + +protected: + int_type underflow() override; + std::streamsize xsgetn(char* s, std::streamsize n) override; + std::streamsize showmanyc() override; + +private: + const IOBuf& _buf; + size_t _block_index{0}; +}; + +// std::istream view over an IOBuf. The IOBuf must outlive this stream and +// must not be modified while the stream is in use. Forward-only — seeking +// is not supported. +// +// Typical use is feeding an IOBuf to a parser that takes std::istream&, +// e.g. nlohmann::json: +// +// butil::IOBufInputStream in(request_body); +// auto j = nlohmann::json::parse(in); +// +// Or formatted extraction: +// +// butil::IOBuf buf; +// buf.append("42 3.14 hello"); +// butil::IOBufInputStream in(buf); +// int i; double d; +// std::string s; +// in >> i >> d >> s; +// +// Bulk read into a buffer (goes through xsgetn, copies one block at a time): +// +// std::string out(buf.length(), '\0'); +// butil::IOBufInputStream in(buf); +// in.read(&out[0], out.size()); +class IOBufInputStream : public std::istream { +public: + // `buf' must outlive this stream and must not be modified while the + // stream is in use. + explicit IOBufInputStream(const IOBuf& buf) + : std::istream(NULL), _sb(buf) { + rdbuf(&_sb); + } + +private: + IOBufAsInputStreamBuf _sb; +}; + +// Wrap IOBuf into a std::streambuf for std::ostream-based serializers +// (e.g. nlohmann::json's `os << j`). Bytes are appended directly into IOBuf +// blocks with no intermediate string copy. +// +// Internally backed by IOBufAsZeroCopyOutputStream: +// - by default, blocks are taken from the per-thread TLS pool (8KB); +// - pass `block_size` to allocate dedicated blocks instead, which avoids +// fragmenting the TLS pool when many output streams coexist. +// +// Append-only — seekoff/seekpos are not overridden. +// +// IMPORTANT: The exact length of the source IOBuf only reflects what was +// written AFTER shrink() / sync() / destruction — `Next()` over-claims the +// remainder of each block and the unused tail is BackUp'd later. If you need +// the precise length mid-stream, call sync() (e.g. via `os.flush()` or +// `_sb.shrink()`). +class IOBufAsOutputStreamBuf : public std::streambuf { +public: + // `buf' must outlive this streambuf. + explicit IOBufAsOutputStreamBuf(IOBuf& buf) : _zc(&buf) {} + IOBufAsOutputStreamBuf(IOBuf& buf, uint32_t block_size) + : _zc(&buf, block_size) {} + ~IOBufAsOutputStreamBuf() override; + + // Return the unused tail of the current put area to the underlying IOBuf + // so that the IOBuf length matches exactly what was written so far. + // Automatically invoked from sync() and the destructor. + void shrink(); + +protected: + int_type overflow(int_type ch) override; + std::streamsize xsputn(const char* s, std::streamsize n) override; + int sync() override; + +private: + bool refresh_put_area(); + + IOBufAsZeroCopyOutputStream _zc; +}; + +// std::ostream view over an IOBuf. Appends written bytes to the referenced +// IOBuf; the IOBuf must outlive this stream. Append-only — seeking is not +// supported. +// +// The IOBuf's length only reflects bytes written AFTER flush()/destruction +// (see IOBufAsOutputStreamBuf for details). +// +// Serialize with a library that writes to std::ostream&, e.g. nlohmann::json +// (zero intermediate string copy — bytes flow straight into IOBuf blocks): +// +// #include +// using nlohmann::json; +// +// json reply = { +// {"status", "ok"}, +// {"items", {1, 2, 3}}, +// }; +// +// butil::IOBuf out; // e.g. controller->response_attachment() +// { +// butil::IOBufOutputStream os(out); +// os << reply; // compact: {"items":[1,2,3],"status":"ok"} +// // os << std::setw(2) << reply; // pretty-print with 2-space indent +// } // dtor runs shrink(); `out` now has the exact serialized bytes. +// +// Formatted insertion (works like any std::ostream): +// +// butil::IOBuf out; +// butil::IOBufOutputStream os(out); +// os << "x=" << 42 << " y=" << 3.14; +// os.flush(); // commit to `out` now +// +// Bulk write of a known-size buffer (goes through xsputn, memcpy per block): +// +// butil::IOBufOutputStream os(out); +// os.write(payload.data(), payload.size()); +// +// Pass `block_size` when many output streams coexist in one thread, to keep +// each stream's allocations off the shared TLS block pool: +// +// butil::IOBufOutputStream os(out, /*block_size=*/64 * 1024); +class IOBufOutputStream : public std::ostream { +public: + // `buf' must outlive this stream. + explicit IOBufOutputStream(IOBuf& buf) + : std::ostream(NULL), _sb(buf) { + rdbuf(&_sb); + } + IOBufOutputStream(IOBuf& buf, uint32_t block_size) + : std::ostream(NULL), _sb(buf, block_size) { + rdbuf(&_sb); + } + +private: + IOBufAsOutputStreamBuf _sb; +}; + +// Wrap IOBuf into input of snappy compression. +class IOBufAsSnappySource : public butil::snappy::Source { +public: + explicit IOBufAsSnappySource(const butil::IOBuf& buf) + : _buf(&buf), _stream(buf) {} + virtual ~IOBufAsSnappySource() {} + + // Return the number of bytes left to read from the source + size_t Available() const override; + + // Peek at the next flat region of the source. + const char* Peek(size_t* len) override; + + // Skip the next n bytes. Invalidates any buffer returned by + // a previous call to Peek(). + void Skip(size_t n) override; + +private: + const butil::IOBuf* _buf; + butil::IOBufAsZeroCopyInputStream _stream; +}; + +// Wrap IOBuf into output of snappy compression. +class IOBufAsSnappySink : public butil::snappy::Sink { +public: + explicit IOBufAsSnappySink(butil::IOBuf& buf); + virtual ~IOBufAsSnappySink() {} + + // Append "bytes[0,n-1]" to this. + void Append(const char* bytes, size_t n) override; + + // Returns a writable buffer of the specified length for appending. + char* GetAppendBuffer(size_t length, char* scratch) override; + +private: + char* _cur_buf; + int _cur_len; + butil::IOBuf* _buf; + butil::IOBufAsZeroCopyOutputStream _buf_stream; +}; + +// A std::ostream to build IOBuf. +// Example: +// IOBufBuilder builder; +// builder << "Anything that can be sent to std::ostream"; +// // You have several methods to fetch the IOBuf. +// target_iobuf.append(builder.buf()); // builder.buf() was not changed +// OR +// builder.move_to(target_iobuf); // builder.buf() was clear()-ed. +class IOBufBuilder : + // Have to use private inheritance to arrange initialization order. + virtual private IOBuf, + virtual private IOBufAsZeroCopyOutputStream, + virtual private ZeroCopyStreamAsStreamBuf, + public std::ostream { +public: + explicit IOBufBuilder() + : IOBufAsZeroCopyOutputStream(this) + , ZeroCopyStreamAsStreamBuf(this) + , std::ostream(this) + { } + + IOBuf& buf() { + this->shrink(); + return *this; + } + void buf(const IOBuf& buf) { + *static_cast(this) = buf; + } + void move_to(IOBuf& target) { + target = Movable(buf()); + } +}; + +// Create IOBuf by appending data *faster* +class IOBufAppender { +public: + IOBufAppender(); + + // Append `n' bytes starting from `data' to back side of the internal buffer + // Costs 2/3 time of IOBuf.append for short data/strings on Intel(R) Xeon(R) + // CPU E5-2620 @ 2.00GHz. Longer data/strings make differences smaller. + // Returns 0 on success, -1 otherwise. + int append(const void* data, size_t n); + int append(const butil::StringPiece& str); + + // Format integer |d| to back side of the internal buffer, which is much faster + // than snprintf(..., "%lu", d). + // Returns 0 on success, -1 otherwise. + int append_decimal(long d); + + // Push the character to back side of the internal buffer. + // Costs ~3ns while IOBuf.push_back costs ~13ns on Intel(R) Xeon(R) CPU + // E5-2620 @ 2.00GHz + // Returns 0 on success, -1 otherwise. + int push_back(char c); + + IOBuf& buf() { + shrink(); + return _buf; + } + void move_to(IOBuf& target) { + target = IOBuf::Movable(buf()); + } + +private: + void shrink(); + int add_block(); + + void* _data; + // Saving _data_end instead of _size avoid modifying _data and _size + // in each push_back() which is probably a hotspot. + void* _data_end; + IOBuf _buf; + IOBufAsZeroCopyOutputStream _zc_stream; +}; + +// Iterate bytes of a IOBuf. +// During iteration, the iobuf should NOT be changed. +class IOBufBytesIterator { +public: + explicit IOBufBytesIterator(const butil::IOBuf& buf); + // Construct from another iterator. + IOBufBytesIterator(const IOBufBytesIterator& it); + IOBufBytesIterator(const IOBufBytesIterator& it, size_t bytes_left); + // Returning unsigned is safer than char which would be more error prone + // to bitwise operations. For example: in "uint32_t value = *it", value + // is (unexpected) 4294967168 when *it returns (char)128. + unsigned char operator*() const { return (unsigned char)*_block_begin; } + operator const void*() const { return (const void*)!!_bytes_left; } + void operator++(); + void operator++(int) { return operator++(); } + // Copy at most n bytes into buf, forwarding this iterator. + // Returns bytes copied. + size_t copy_and_forward(void* buf, size_t n); + size_t copy_and_forward(std::string* s, size_t n); + // Just forward this iterator for at most n bytes. + size_t forward(size_t n); + // Append at most n bytes into buf, forwarding this iterator. Data are + // referenced rather than copied. + size_t append_and_forward(butil::IOBuf* buf, size_t n); + bool forward_one_block(const void** data, size_t* size); + size_t bytes_left() const { return _bytes_left; } +private: + void try_next_block(); + const char* _block_begin; + const char* _block_end; + uint32_t _block_count; + uint32_t _bytes_left; + const butil::IOBuf* _buf; +}; + +} // namespace butil + +// Specialize std::swap for IOBuf +#if __cplusplus < 201103L // < C++11 +#include // std::swap until C++11 +#else +#include // std::swap since C++11 +#endif // __cplusplus < 201103L +namespace std { +template <> +inline void swap(butil::IOBuf& a, butil::IOBuf& b) { + return a.swap(b); +} +} // namespace std + +#include "butil/iobuf_inl.h" + +#endif // BUTIL_IOBUF_H diff --git a/src/butil/iobuf_inl.h b/src/butil/iobuf_inl.h new file mode 100644 index 0000000..756cf8b --- /dev/null +++ b/src/butil/iobuf_inl.h @@ -0,0 +1,652 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// iobuf - A non-continuous zero-copied buffer + +// Date: Thu Nov 22 13:57:56 CST 2012 + +// Inlined implementations of some methods defined in iobuf.h + +#ifndef BUTIL_IOBUF_INL_H +#define BUTIL_IOBUF_INL_H + +#include "butil/atomicops.h" // butil::atomic +#include "butil/thread_local.h" // thread_atexit +#include "butil/logging.h" // CHECK, LOG + +void* fast_memcpy(void *__restrict dest, const void *__restrict src, size_t n); + +namespace butil { + +using UserDataDeleter = std::function; + +struct UserDataExtension { + UserDataDeleter deleter; +}; + +bool IsIOBufProfilerSamplable(); +void SubmitIOBufSample(IOBuf::Block* block, int64_t ref); + +const uint16_t IOBUF_BLOCK_FLAGS_USER_DATA = 1 << 0; +const uint16_t IOBUF_BLOCK_FLAGS_SAMPLED = 1 << 1; + +inline ssize_t IOBuf::cut_into_file_descriptor(int fd, size_t size_hint) { + return pcut_into_file_descriptor(fd, -1, size_hint); +} + +inline ssize_t IOBuf::cut_multiple_into_file_descriptor( + int fd, IOBuf* const* pieces, size_t count) { + return pcut_multiple_into_file_descriptor(fd, -1, pieces, count); +} + +inline int IOBuf::append_user_data(void* data, size_t size, std::function deleter) { + return append_user_data_with_meta(data, size, std::move(deleter), 0); +} + +inline ssize_t IOPortal::append_from_file_descriptor(int fd, size_t max_count) { + return pappend_from_file_descriptor(fd, -1, max_count); +} + +inline void IOPortal::return_cached_blocks() { + if (_block) { + return_cached_blocks_impl(_block); + _block = NULL; + } +} + +inline void reset_block_ref(IOBuf::BlockRef& ref) { + ref.offset = 0; + ref.length = 0; + ref.block = NULL; +} + +inline IOBuf::IOBuf() { + reset_block_ref(_sv.refs[0]); + reset_block_ref(_sv.refs[1]); +} + +inline IOBuf::IOBuf(const Movable& rhs) { + _sv = rhs.value()._sv; + new (&rhs.value()) IOBuf; +} + +inline void IOBuf::operator=(const Movable& rhs) { + clear(); + _sv = rhs.value()._sv; + new (&rhs.value()) IOBuf; +} + +inline void IOBuf::operator=(const char* s) { + clear(); + append(s); +} + +inline void IOBuf::operator=(const std::string& s) { + clear(); + append(s); +} + +inline void IOBuf::swap(IOBuf& other) { + const SmallView tmp = other._sv; + other._sv = _sv; + _sv = tmp; +} + +inline int IOBuf::cut_until(IOBuf* out, char const* delim) { + if (*delim) { + if (!*(delim+1)) { + return _cut_by_char(out, *delim); + } else { + return _cut_by_delim(out, delim, strlen(delim)); + } + } + return -1; +} + +inline int IOBuf::cut_until(IOBuf* out, const std::string& delim) { + if (delim.length() == 1UL) { + return _cut_by_char(out, delim[0]); + } else if (delim.length() > 1UL) { + return _cut_by_delim(out, delim.data(), delim.length()); + } else { + return -1; + } +} + +inline int IOBuf::append(const std::string& s) { + return append(s.data(), s.length()); +} + +inline std::string IOBuf::to_string() const { + std::string s; + copy_to(&s); + return s; +} + +inline bool IOBuf::empty() const { + return _small() ? !_sv.refs[0].block : !_bv.nbytes; +} + +inline size_t IOBuf::length() const { + return _small() ? + (_sv.refs[0].length + _sv.refs[1].length) : _bv.nbytes; +} + +inline bool IOBuf::_small() const { + return _bv.magic >= 0; +} + +inline size_t IOBuf::_ref_num() const { + return _small() + ? (!!_sv.refs[0].block + !!_sv.refs[1].block) : _bv.nref; +} + +inline IOBuf::BlockRef& IOBuf::_front_ref() { + return _small() ? _sv.refs[0] : _bv.refs[_bv.start]; +} + +inline const IOBuf::BlockRef& IOBuf::_front_ref() const { + return _small() ? _sv.refs[0] : _bv.refs[_bv.start]; +} + +inline IOBuf::BlockRef& IOBuf::_back_ref() { + return _small() ? _sv.refs[!!_sv.refs[1].block] : _bv.ref_at(_bv.nref - 1); +} + +inline const IOBuf::BlockRef& IOBuf::_back_ref() const { + return _small() ? _sv.refs[!!_sv.refs[1].block] : _bv.ref_at(_bv.nref - 1); +} + +inline IOBuf::BlockRef& IOBuf::_ref_at(size_t i) { + return _small() ? _sv.refs[i] : _bv.ref_at(i); +} + +inline const IOBuf::BlockRef& IOBuf::_ref_at(size_t i) const { + return _small() ? _sv.refs[i] : _bv.ref_at(i); +} + +inline const IOBuf::BlockRef* IOBuf::_pref_at(size_t i) const { + if (_small()) { + return i < (size_t)(!!_sv.refs[0].block + !!_sv.refs[1].block) ? &_sv.refs[i] : NULL; + } else { + return i < _bv.nref ? &_bv.ref_at(i) : NULL; + } +} + +inline bool operator==(const IOBuf::BlockRef& r1, const IOBuf::BlockRef& r2) { + return r1.offset == r2.offset && r1.length == r2.length && + r1.block == r2.block; +} + +inline bool operator!=(const IOBuf::BlockRef& r1, const IOBuf::BlockRef& r2) { + return !(r1 == r2); +} + +inline void IOBuf::_push_back_ref(const BlockRef& r) { + if (_small()) { + return _push_or_move_back_ref_to_smallview(r); + } else { + return _push_or_move_back_ref_to_bigview(r); + } +} + +inline void IOBuf::_move_back_ref(const BlockRef& r) { + if (_small()) { + return _push_or_move_back_ref_to_smallview(r); + } else { + return _push_or_move_back_ref_to_bigview(r); + } +} + +//////////////// IOBufCutter //////////////// +inline size_t IOBufCutter::remaining_bytes() const { + if (_block) { + return (char*)_data_end - (char*)_data + _buf->size() - _buf->_front_ref().length; + } else { + return _buf->size(); + } +} + +inline bool IOBufCutter::cut1(void* c) { + if (_data == _data_end) { + if (!load_next_ref()) { + return false; + } + } + *(char*)c = *(const char*)_data; + _data = (char*)_data + 1; + return true; +} + +inline const void* IOBufCutter::fetch1() { + if (_data == _data_end) { + if (!load_next_ref()) { + return NULL; + } + } + return _data; +} + +inline size_t IOBufCutter::copy_to(void* out, size_t n) { + size_t size = (char*)_data_end - (char*)_data; + if (n <= size) { + memcpy(out, _data, n); + return n; + } + return slower_copy_to(out, n); +} + +inline size_t IOBufCutter::pop_front(size_t n) { + const size_t saved_n = n; + do { + const size_t size = (char*)_data_end - (char*)_data; + if (n <= size) { + _data = (char*)_data + n; + return saved_n; + } + n -= size; + if (!load_next_ref()) { + return saved_n - n; + } + } while (true); +} + +inline size_t IOBufCutter::cutn(std::string* out, size_t n) { + if (n == 0) { + return 0; + } + const size_t len = remaining_bytes(); + if (n > len) { + n = len; + } + const size_t old_size = out->size(); + out->resize(out->size() + n); + return cutn(&(*out)[old_size], n); +} + +/////////////// IOBufAppender ///////////////// +inline int IOBufAppender::append(const void* src, size_t n) { + do { + const size_t size = (char*)_data_end - (char*)_data; + if (n <= size) { + memcpy(_data, src, n); + _data = (char*)_data + n; + return 0; + } + if (size != 0) { + memcpy(_data, src, size); + src = (const char*)src + size; + n -= size; + } + if (add_block() != 0) { + return -1; + } + } while (true); +} + +inline int IOBufAppender::append(const StringPiece& str) { + return append(str.data(), str.size()); +} + +inline int IOBufAppender::append_decimal(long d) { + char buf[24]; // enough for decimal 64-bit integers + size_t n = sizeof(buf); + bool negative = false; + if (d < 0) { + negative = true; + d = -d; + } + do { + const long q = d / 10; + buf[--n] = d - q * 10 + '0'; + d = q; + } while (d); + if (negative) { + buf[--n] = '-'; + } + return append(buf + n, sizeof(buf) - n); +} + +inline int IOBufAppender::push_back(char c) { + if (_data == _data_end) { + if (add_block() != 0) { + return -1; + } + } + char* const p = (char*)_data; + *p = c; + _data = p + 1; + return 0; +} + +inline int IOBufAppender::add_block() { + int size = 0; + if (_zc_stream.Next(&_data, &size)) { + _data_end = (char*)_data + size; + return 0; + } + _data = NULL; + _data_end = NULL; + return -1; +} + +inline void IOBufAppender::shrink() { + const size_t size = (char*)_data_end - (char*)_data; + if (size != 0) { + _zc_stream.BackUp(size); + _data = NULL; + _data_end = NULL; + } +} + +inline IOBufBytesIterator::IOBufBytesIterator(const butil::IOBuf& buf) + : _block_begin(NULL), _block_end(NULL), _block_count(0), + _bytes_left(buf.length()), _buf(&buf) { + try_next_block(); +} + +inline IOBufBytesIterator::IOBufBytesIterator(const IOBufBytesIterator& it) + : _block_begin(it._block_begin) + , _block_end(it._block_end) + , _block_count(it._block_count) + , _bytes_left(it._bytes_left) + , _buf(it._buf) { +} + +inline IOBufBytesIterator::IOBufBytesIterator( + const IOBufBytesIterator& it, size_t bytes_left) + : _block_begin(it._block_begin) + , _block_end(it._block_end) + , _block_count(it._block_count) + , _bytes_left(bytes_left) + , _buf(it._buf) { + //CHECK_LE(_bytes_left, it._bytes_left); + if (_block_end > _block_begin + _bytes_left) { + _block_end = _block_begin + _bytes_left; + } +} + +inline void IOBufBytesIterator::try_next_block() { + if (_bytes_left == 0) { + return; + } + butil::StringPiece s = _buf->backing_block(_block_count++); + _block_begin = s.data(); + _block_end = s.data() + std::min(s.size(), (size_t)_bytes_left); +} + +inline void IOBufBytesIterator::operator++() { + ++_block_begin; + --_bytes_left; + if (_block_begin == _block_end) { + try_next_block(); + } +} + +inline size_t IOBufBytesIterator::copy_and_forward(void* buf, size_t n) { + size_t nc = 0; + while (nc < n && _bytes_left != 0) { + const size_t block_size = _block_end - _block_begin; + const size_t to_copy = std::min(block_size, n - nc); + memcpy((char*)buf + nc, _block_begin, to_copy); + _block_begin += to_copy; + _bytes_left -= to_copy; + nc += to_copy; + if (_block_begin == _block_end) { + try_next_block(); + } + } + return nc; +} + +inline size_t IOBufBytesIterator::copy_and_forward(std::string* s, size_t n) { + bool resized = false; + if (s->size() < n) { + resized = true; + s->resize(n); + } + const size_t nc = copy_and_forward(const_cast(s->data()), n); + if (nc < n && resized) { + s->resize(nc); + } + return nc; +} + +inline size_t IOBufBytesIterator::forward(size_t n) { + size_t nc = 0; + while (nc < n && _bytes_left != 0) { + const size_t block_size = _block_end - _block_begin; + const size_t to_copy = std::min(block_size, n - nc); + _block_begin += to_copy; + _bytes_left -= to_copy; + nc += to_copy; + if (_block_begin == _block_end) { + try_next_block(); + } + } + return nc; +} + +// Used by max_blocks_per_thread() +bool IsIOBufProfilerEnabled(); + +namespace iobuf { +void inc_g_nblock(); +void dec_g_nblock(); + +void inc_g_blockmem(); +void dec_g_blockmem(); + +void inc_g_num_hit_tls_threshold(); +void dec_g_num_hit_tls_threshold(); + +// Function pointers to allocate or deallocate memory for a IOBuf::Block +extern void* (*blockmem_allocate)(size_t); +extern void (*blockmem_deallocate)(void*); + +} // namespace iobuf + +struct IOBuf::Block { + butil::atomic nshared; + uint16_t flags; + uint16_t abi_check; // original cap, never be zero. + uint32_t size; + uint32_t cap; + // When flag is 0, portal_next is valid. + // When flag & IOBUF_BLOCK_FLAGS_USER_DATA is non-0, data_meta is valid. + union { + Block* portal_next; + uint64_t data_meta; + } u; + // When flag is 0, data points to `size` bytes starting at `(char*)this+sizeof(Block)' + // When flag & IOBUF_BLOCK_FLAGS_USER_DATA is non-0, data points to the user data and + // the deleter is put in UserDataExtension at `(char*)this+sizeof(Block)' + char* data; + + Block(char* data_in, uint32_t data_size) + : nshared(1) + , flags(0) + , abi_check(0) + , size(0) + , cap(data_size) + , u({NULL}) + , data(data_in) { + iobuf::inc_g_nblock(); + iobuf::inc_g_blockmem(); + if (is_samplable()) { + SubmitIOBufSample(this, 1); + } + } + + Block(char* data_in, uint32_t data_size, UserDataDeleter deleter) + : nshared(1) + , flags(IOBUF_BLOCK_FLAGS_USER_DATA) + , abi_check(0) + , size(data_size) + , cap(data_size) + , u({0}) + , data(data_in) { + auto ext = new (get_user_data_extension()) UserDataExtension(); + ext->deleter = std::move(deleter); + if (is_samplable()) { + SubmitIOBufSample(this, 1); + } + } + + // Undefined behavior when (flags & IOBUF_BLOCK_FLAGS_USER_DATA) is 0. + UserDataExtension* get_user_data_extension() { + char* p = (char*)this; + return (UserDataExtension*)(p + sizeof(Block)); + } + + inline void check_abi() { +#ifndef NDEBUG + if (abi_check != 0) { + LOG(FATAL) << "Your program seems to wrongly contain two " + "ABI-incompatible implementations of IOBuf"; + } +#endif +} + + void inc_ref() { + check_abi(); + nshared.fetch_add(1, butil::memory_order_relaxed); + if (sampled()) { + SubmitIOBufSample(this, 1); + } + } + + void dec_ref() { + check_abi(); + if (sampled()) { + SubmitIOBufSample(this, -1); + } + if (nshared.fetch_sub(1, butil::memory_order_release) == 1) { + butil::atomic_thread_fence(butil::memory_order_acquire); + if (!is_user_data()) { + iobuf::dec_g_nblock(); + iobuf::dec_g_blockmem(); + this->~Block(); + iobuf::blockmem_deallocate(this); + } else if (flags & IOBUF_BLOCK_FLAGS_USER_DATA) { + auto ext = get_user_data_extension(); + ext->deleter(data); + ext->~UserDataExtension(); + this->~Block(); + free(this); + } + } + } + + int ref_count() const { + return nshared.load(butil::memory_order_relaxed); + } + + bool full() const { return size >= cap; } + size_t left_space() const { return cap - size; } + +private: + bool is_samplable() { + if (IsIOBufProfilerSamplable()) { + flags |= IOBUF_BLOCK_FLAGS_SAMPLED; + return true; + } + return false; + } + + bool sampled() const { + return flags & IOBUF_BLOCK_FLAGS_SAMPLED; + } + + bool is_user_data() const { + return flags & IOBUF_BLOCK_FLAGS_USER_DATA; + } +}; + +namespace iobuf { +struct TLSData { + // Head of the TLS block chain. + IOBuf::Block* block_head; + + // Number of TLS blocks + int num_blocks; + + // True if the remote_tls_block_chain is registered to the thread. + bool registered; +}; + +// Max number of blocks in each TLS. This is a soft limit namely +// release_tls_block_chain() may exceed this limit sometimes. +const int MAX_BLOCKS_PER_THREAD = 8; + +inline int max_blocks_per_thread() { + // If IOBufProfiler is enabled, do not cache blocks in TLS. + return IsIOBufProfilerEnabled() ? 0 : MAX_BLOCKS_PER_THREAD; +} + +TLSData* get_g_tls_data(); +void remove_tls_block_chain(); + +IOBuf::Block* acquire_tls_block(); + +// Return one block to TLS. +inline void release_tls_block(IOBuf::Block* b) { + if (!b) { + return; + } + TLSData *tls_data = get_g_tls_data(); + if (b->full()) { + b->dec_ref(); + } else if (tls_data->num_blocks >= max_blocks_per_thread()) { + b->dec_ref(); + // g_num_hit_tls_threshold.fetch_add(1, butil::memory_order_relaxed); + inc_g_num_hit_tls_threshold(); + } else { + b->u.portal_next = tls_data->block_head; + tls_data->block_head = b; + ++tls_data->num_blocks; + if (!tls_data->registered) { + tls_data->registered = true; + butil::thread_atexit(remove_tls_block_chain); + } + } +} + +inline IOBuf::Block* create_block(const size_t block_size) { + if (block_size > 0xFFFFFFFFULL) { + LOG(FATAL) << "block_size=" << block_size << " is too large"; + return NULL; + } + char* mem = (char*)iobuf::blockmem_allocate(block_size); + if (mem == NULL) { + return NULL; + } + return new (mem) IOBuf::Block(mem + sizeof(IOBuf::Block), + block_size - sizeof(IOBuf::Block)); +} + +inline IOBuf::Block* create_block() { + return create_block(butil::GetDefaultBlockSize()); +} + +void* cp(void *__restrict dest, const void *__restrict src, size_t n); + +}; // namespace iobuf; + +} // namespace butil + +#endif // BUTIL_IOBUF_INL_H diff --git a/src/butil/iobuf_profiler.cpp b/src/butil/iobuf_profiler.cpp new file mode 100644 index 0000000..a88e7c5 --- /dev/null +++ b/src/butil/iobuf_profiler.cpp @@ -0,0 +1,319 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/iobuf_profiler.h" +#include "butil/strings/string_number_conversions.h" +#include "butil/file_util.h" +#include "butil/fd_guard.h" +#include "butil/fast_rand.h" +#include "butil/hash.h" +#include + +extern int BAIDU_WEAK GetStackTrace(void** result, int max_depth, int skip_count); + +namespace butil { + +namespace iobuf { +extern void* cp(void *__restrict dest, const void *__restrict src, size_t n); +} + +// Max and min sleep time for IOBuf profiler consuming thread +// when `_sample_queue' is empty. +const uint32_t IOBufProfiler::MIN_SLEEP_MS = 10; +const uint32_t IOBufProfiler::MAX_SLEEP_MS = 1000; + +static pthread_once_t g_iobuf_profiler_info_once = PTHREAD_ONCE_INIT; +static bool g_iobuf_profiler_enabled = false; +static uint g_iobuf_profiler_sample_rate = 100; + +// Environment variables: +// 1. ENABLE_IOBUF_PROFILER: set value to 1 to enable IOBuf profiler. +// 2. IOBUF_PROFILER_SAMPLE_RATE: set value between (0, 100] to control sample rate. +static void InitGlobalIOBufProfilerInfo() { + const char* enabled = getenv("ENABLE_IOBUF_PROFILER"); + g_iobuf_profiler_enabled = enabled && strcmp("1", enabled) == 0 && ::GetStackTrace != NULL; + if (!g_iobuf_profiler_enabled) { + return; + } + + char* rate = getenv("IOBUF_PROFILER_SAMPLE_RATE"); + if (rate) { + int tmp = 0; + if (butil::StringToInt(rate, &tmp)) { + if (tmp > 0 && tmp <= 100) { + g_iobuf_profiler_sample_rate = tmp; + } else { + LOG(ERROR) << "IOBUF_PROFILER_SAMPLE_RATE should be in (0, 100], but get " << rate; + } + } else { + LOG(ERROR) << "IOBUF_PROFILER_SAMPLE_RATE should be a number, but get " << rate; + } + } + LOG(INFO) << "g_iobuf_profiler_sample_rate=" << g_iobuf_profiler_sample_rate; +} + +bool IsIOBufProfilerEnabled() { + pthread_once(&g_iobuf_profiler_info_once, InitGlobalIOBufProfilerInfo); + return g_iobuf_profiler_enabled; +} + +bool IsIOBufProfilerSamplable() { + if (!IsIOBufProfilerEnabled()) { + return false; + } + if (g_iobuf_profiler_sample_rate == 100) { + return true; + } + return fast_rand_less_than(100) + 1 <= g_iobuf_profiler_sample_rate; +} + +size_t IOBufSample::stack_hash_code() const { + if (nframes == 0) { + return 0; + } + if (_hash_code == 0) { + _hash_code = SuperFastHash(reinterpret_cast(stack), + sizeof(void*) * nframes); + } + return _hash_code; +} + +IOBufProfiler* IOBufProfiler::GetInstance() { + return ::Singleton>::get(); +} + +IOBufProfiler::IOBufProfiler() + : butil::SimpleThread("IOBufProfiler") + , _stop(false) + , _sleep_ms(MIN_SLEEP_MS) { + if (_stack_map.init(1024) != 0) { + LOG(WARNING) << "Fail to init _stack_map"; + } + if (_block_info_map.init(1024) != 0) { + LOG(WARNING) << "Fail to init _block_info_map"; + } + Start(); +} + +IOBufProfiler::~IOBufProfiler() { + StopAndJoin(); + _block_info_map.clear(); + _stack_map.clear(); + + // Clear `_sample_queue'. + IOBufSample* sample = NULL; + while (_sample_queue.Dequeue(sample)) { + IOBufSample::Destroy(sample); + } + +} + +void IOBufProfiler::Submit(IOBufSample* s) { + if (_stop.load(butil::memory_order_relaxed)) { + return; + } + + _sample_queue.Enqueue(s); +} + +void IOBufProfiler::Dump(IOBufSample* s) { + do { + BAIDU_SCOPED_LOCK(_mutex); + // Categorize the stack. + IOBufRefSampleSharedPtr stack_sample; + IOBufRefSampleSharedPtr* stack_ptr = _stack_map.seek(s); + if (!stack_ptr) { + stack_sample = IOBufSample::CopyAndSharedWithDestroyer(s); + stack_sample->block = NULL; + stack_ptr = &_stack_map[stack_sample.get()]; + *stack_ptr = stack_sample; + } else { + (*stack_ptr)->count += s->count; + } + + BlockInfo* info = _block_info_map.seek(s->block); + if (info) { + // Categorize the IOBufSample. + info->stack_count_map[*stack_ptr] += s->count; + info->ref += s->count; + if (info->ref == 0) { + for (const auto& iter : info->stack_count_map) { + IOBufRefSampleSharedPtr* stack_ptr2 = _stack_map.seek(iter.first.get()); + if (stack_ptr2 && *stack_ptr2) { + (*stack_ptr2)->count -= iter.second; + if ((*stack_ptr2)->count == 0) { + _stack_map.erase(iter.first.get()); + } + } + } + _block_info_map.erase(s->block); + break; + } + } else { + BlockInfo& new_info = _block_info_map[s->block]; + new_info.ref += s->count; + new_info.stack_count_map[*stack_ptr] = s->count; + } + } while (false); + s->block = NULL; +} + +IOBufSample* IOBufSample::Copy(IOBufSample* ref) { + auto copied = IOBufSample::New(); + copied->block = ref->block; + copied->count = ref->count; + copied->_hash_code = ref->_hash_code; + copied->nframes = ref->nframes; + iobuf::cp(copied->stack, ref->stack, sizeof(void*) * ref->nframes); + return copied; +} + +IOBufRefSampleSharedPtr IOBufSample::CopyAndSharedWithDestroyer(IOBufSample* ref) { + return { Copy(ref), detail::Destroyer() }; +} + +void IOBufProfiler::Flush2Disk(const char* filename) { + if (!filename) { + LOG(ERROR) << "Parameter [filename] is NULL"; + return; + } + // Serialize contentions in _stack_map into _disk_buf. + _disk_buf.append("--- contention\ncycles/second=1000000000\n"); + butil::IOBufBuilder os; + { + BAIDU_SCOPED_LOCK(_mutex); + for (auto it = _stack_map.begin(); it != _stack_map.end(); ++it) { + const IOBufRefSampleSharedPtr& c = it->second; + if (c->nframes == 0) { + LOG_EVERY_SECOND(WARNING) << "Invalid stack with nframes=0, count=" << c->count; + continue; + } + os << "0 " << c->count << " @"; + for (int i = 0; i < c->nframes; ++i) { + os << " " << c->stack[i]; + } + os << "\n"; + } + } + _disk_buf.append(os.buf().movable()); + + // Append /proc/self/maps to the end of the contention file, required by + // pprof.pl, otherwise the functions in sys libs are not interpreted. + butil::IOPortal mem_maps; + const butil::fd_guard maps_fd(open("/proc/self/maps", O_RDONLY)); + if (maps_fd >= 0) { + while (true) { + ssize_t nr = mem_maps.append_from_file_descriptor(maps_fd, 8192); + if (nr < 0) { + if (errno == EINTR) { + continue; + } + PLOG(ERROR) << "Fail to read /proc/self/maps"; + break; + } + if (nr == 0) { + _disk_buf.append(mem_maps); + break; + } + } + } else { + PLOG(ERROR) << "Fail to open /proc/self/maps"; + } + // Write _disk_buf into _filename + butil::File::Error error; + butil::FilePath file_path(filename); + butil::FilePath dir = file_path.DirName(); + if (!butil::CreateDirectoryAndGetError(dir, &error)) { + LOG(ERROR) << "Fail to create directory=`" << dir.value() + << ": " << error; + return; + } + + butil::fd_guard fd(open(filename, O_WRONLY | O_CREAT | O_APPEND | O_TRUNC, 0666)); + if (fd < 0) { + PLOG(ERROR) << "Fail to open " << filename; + return; + } + // Write once normally, write until empty in the end. + do { + ssize_t nw = _disk_buf.cut_into_file_descriptor(fd); + if (nw < 0) { + if (errno == EINTR) { + continue; + } + PLOG(ERROR) << "Fail to write into " << filename; + return; + } + LOG(ERROR) << "Write " << nw << " bytes into " << filename; + } while (!_disk_buf.empty()); +} + +void IOBufProfiler::StopAndJoin() { + if (_stop.exchange(true, butil::memory_order_relaxed)) { + return; + } + if (!HasBeenJoined()) { + Join(); + } +} + +void IOBufProfiler::Run() { + while (!_stop.load(butil::memory_order_relaxed)) { + Consume(); + ::usleep(_sleep_ms * 1000); + } +} + +void IOBufProfiler::Consume() { + IOBufSample* sample = NULL; + bool is_empty = true; + while (_sample_queue.Dequeue(sample)) { + Dump(sample); + IOBufSample::Destroy(sample); + is_empty = false; + } + + // If `_sample_queue' is empty, exponential increase in sleep time. + _sleep_ms = !is_empty ? MIN_SLEEP_MS : + std::min(_sleep_ms * 2, MAX_SLEEP_MS); +} + +void SubmitIOBufSample(IOBuf::Block* block, int64_t ref) { + if (!IsIOBufProfilerEnabled()) { + return; + } + + auto sample = IOBufSample::New(); + sample->block = block; + sample->count = ref; + sample->nframes = GetStackTrace(sample->stack, arraysize(sample->stack), 0); + IOBufProfiler::GetInstance()->Submit(sample); +} + +bool IOBufProfilerFlush(const char* filename) { + if (!filename) { + LOG(ERROR) << "Parameter [filename] is NULL"; + return false; + } + + auto profiler = IOBufProfiler::GetInstance(); + profiler->Flush2Disk(filename); + return true; +} + +} \ No newline at end of file diff --git a/src/butil/iobuf_profiler.h b/src/butil/iobuf_profiler.h new file mode 100644 index 0000000..4686886 --- /dev/null +++ b/src/butil/iobuf_profiler.h @@ -0,0 +1,169 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BIGO_BRPC_IOBUF_PROFILER_H +#define BIGO_BRPC_IOBUF_PROFILER_H + +#include +#include +#include "butil/iobuf.h" +#include "butil/object_pool.h" +#include "butil/threading/simple_thread.h" +#include "butil/threading/simple_thread.h" +#include "butil/memory/singleton.h" +#include "butil/containers/flat_map.h" +#include "butil/containers/mpsc_queue.h" + +namespace butil { + +struct IOBufSample; +typedef std::shared_ptr IOBufRefSampleSharedPtr; + +struct IOBufSample { + IOBufSample* next; + IOBuf::Block* block; + int64_t count; // // reference count of the block. + void* stack[28]; // backtrace. + int nframes; // num of frames in stack. + + static IOBufSample* New() { + return get_object(); + } + + static IOBufSample* Copy(IOBufSample* ref); + static IOBufRefSampleSharedPtr CopyAndSharedWithDestroyer(IOBufSample* ref); + static void Destroy(IOBufSample* ref) { + ref->_hash_code = 0; + return_object(ref); + } + + size_t stack_hash_code() const; + +private: +friend ObjectPool; + + IOBufSample() + : next(NULL) + , block(NULL) + , count(0) + , stack{} + , nframes(0) + , _hash_code(0) {} + + ~IOBufSample() = default; + + mutable uint32_t _hash_code; // For combining samples with hashmap. +}; + +BAIDU_CASSERT(sizeof(IOBufSample) == 256, be_friendly_to_allocator); + +namespace detail { +// Functor to compare IOBufRefSample. +template +struct IOBufSampleEqual { + bool operator()(const T& c1, const T& c2) const { + return c1->stack_hash_code() ==c2->stack_hash_code() && + c1->nframes == c2->nframes && + memcmp(c1->stack, c2->stack, sizeof(void*) * c1->nframes) == 0; + } +}; + +// Functor to hash IOBufRefSample. +template +struct IOBufSampleHash { + size_t operator()(const T& c) const { + return c->stack_hash_code(); + } +}; + +struct Destroyer { + void operator()(IOBufSample* ref) const { + if (ref) { + IOBufSample::Destroy(ref); + } + } +}; +} + +class IOBufProfiler : public butil::SimpleThread { +public: + static IOBufProfiler* GetInstance(); + + // Submit the IOBuf sample along with stacktrace. + void Submit(IOBufSample* s); + // Dump IOBuf sample to map. + void Dump(IOBufSample* s); + // Write buffered data into resulting file. + void Flush2Disk(const char* filename); + + void StopAndJoin(); + +private: + friend struct DefaultSingletonTraits; + + typedef butil::FlatMap, + detail::IOBufSampleEqual> IOBufRefMap; + + // + typedef butil::FlatMap, + detail::IOBufSampleEqual> StackCountMap; + struct BlockInfo { + int64_t ref{0}; + StackCountMap stack_count_map; + }; + typedef butil::FlatMap BlockInfoMap; + + IOBufProfiler(); + ~IOBufProfiler() override; + DISALLOW_COPY_AND_ASSIGN(IOBufProfiler); + + void Run() override; + // Consume the IOBuf sample in _sample_queue. + void Consume(); + + // Stop flag of IOBufProfiler. + butil::atomic _stop; + // IOBuf sample queue. + MPSCQueue _sample_queue; + + // Temp buf before saving the file. + butil::IOBuf _disk_buf; + // Combining same samples to make result smaller. + IOBufRefMap _stack_map; + // Record block info. + BlockInfoMap _block_info_map; + Mutex _mutex; + + // Sleep when `_sample_queue' is empty. + uint32_t _sleep_ms; + static const uint32_t MIN_SLEEP_MS; + static const uint32_t MAX_SLEEP_MS; +}; + +bool IsIOBufProfilerEnabled(); +bool IsIOBufProfilerSamplable(); + +void SubmitIOBufSample(IOBuf::Block* block, int64_t ref); + +bool IOBufProfilerFlush(const char* filename); + +} +#endif //BIGO_BRPC_IOBUF_PROFILER_H diff --git a/src/butil/lazy_instance.cc b/src/butil/lazy_instance.cc new file mode 100644 index 0000000..f9444da --- /dev/null +++ b/src/butil/lazy_instance.cc @@ -0,0 +1,59 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/lazy_instance.h" + +#include "butil/at_exit.h" +#include "butil/atomicops.h" +#include "butil/basictypes.h" +#include "butil/threading/platform_thread.h" +#include "butil/third_party/dynamic_annotations/dynamic_annotations.h" + +namespace butil { +namespace internal { + +// TODO(joth): This function could be shared with Singleton, in place of its +// WaitForInstance() call. +bool NeedsLazyInstance(subtle::AtomicWord* state) { + // Try to create the instance, if we're the first, will go from 0 to + // kLazyInstanceStateCreating, otherwise we've already been beaten here. + // The memory access has no memory ordering as state 0 and + // kLazyInstanceStateCreating have no associated data (memory barriers are + // all about ordering of memory accesses to *associated* data). + if (subtle::NoBarrier_CompareAndSwap(state, 0, + kLazyInstanceStateCreating) == 0) + // Caller must create instance + return true; + + // It's either in the process of being created, or already created. Spin. + // The load has acquire memory ordering as a thread which sees + // state_ == STATE_CREATED needs to acquire visibility over + // the associated data (buf_). Pairing Release_Store is in + // CompleteLazyInstance(). + while (subtle::Acquire_Load(state) == kLazyInstanceStateCreating) { + PlatformThread::YieldCurrentThread(); + } + // Someone else created the instance. + return false; +} + +void CompleteLazyInstance(subtle::AtomicWord* state, + subtle::AtomicWord new_instance, + void* lazy_instance, + void (*dtor)(void*)) { + // See the comment to the corresponding HAPPENS_AFTER in Pointer(). + ANNOTATE_HAPPENS_BEFORE(state); + + // Instance is created, go from CREATING to CREATED. + // Releases visibility over private_buf_ to readers. Pairing Acquire_Load's + // are in NeedsInstance() and Pointer(). + subtle::Release_Store(state, new_instance); + + // Make sure that the lazily instantiated object will get destroyed at exit. + if (dtor) + AtExitManager::RegisterCallback(dtor, lazy_instance); +} + +} // namespace internal +} // namespace butil diff --git a/src/butil/lazy_instance.h b/src/butil/lazy_instance.h new file mode 100644 index 0000000..b300faf --- /dev/null +++ b/src/butil/lazy_instance.h @@ -0,0 +1,232 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// The LazyInstance class manages a single instance of Type, +// which will be lazily created on the first time it's accessed. This class is +// useful for places you would normally use a function-level static, but you +// need to have guaranteed thread-safety. The Type constructor will only ever +// be called once, even if two threads are racing to create the object. Get() +// and Pointer() will always return the same, completely initialized instance. +// When the instance is constructed it is registered with AtExitManager. The +// destructor will be called on program exit. +// +// LazyInstance is completely thread safe, assuming that you create it safely. +// The class was designed to be POD initialized, so it shouldn't require a +// static constructor. It really only makes sense to declare a LazyInstance as +// a global variable using the LAZY_INSTANCE_INITIALIZER initializer. +// +// LazyInstance is similar to Singleton, except it does not have the singleton +// property. You can have multiple LazyInstance's of the same type, and each +// will manage a unique instance. It also preallocates the space for Type, as +// to avoid allocating the Type instance on the heap. This may help with the +// performance of creating the instance, and reducing heap fragmentation. This +// requires that Type be a complete type so we can determine the size. +// +// Example usage: +// static LazyInstance my_instance = LAZY_INSTANCE_INITIALIZER; +// void SomeMethod() { +// my_instance.Get().SomeMethod(); // MyClass::SomeMethod() +// +// MyClass* ptr = my_instance.Pointer(); +// ptr->DoDoDo(); // MyClass::DoDoDo +// } + +#ifndef BUTIL_LAZY_INSTANCE_H_ +#define BUTIL_LAZY_INSTANCE_H_ + +#include // For placement new. + +#include "butil/atomicops.h" +#include "butil/base_export.h" +#include "butil/debug/leak_annotations.h" +#include "butil/logging.h" +#include "butil/memory/aligned_memory.h" +#include "butil/third_party/dynamic_annotations/dynamic_annotations.h" +#include "butil/threading/thread_restrictions.h" + +// LazyInstance uses its own struct initializer-list style static +// initialization, as base's LINKER_INITIALIZED requires a constructor and on +// some compilers (notably gcc 4.4) this still ends up needing runtime +// initialization. +#define LAZY_INSTANCE_INITIALIZER { 0, {{0}} } + +namespace butil { + +template +struct DefaultLazyInstanceTraits { + static const bool kRegisterOnExit; +#ifndef NDEBUG + static const bool kAllowedToAccessOnNonjoinableThread; +#endif + + static Type* New(void* instance) { + DCHECK_EQ(reinterpret_cast(instance) & (ALIGNOF(Type) - 1), 0u) + << ": Bad boy, the buffer passed to placement new is not aligned!\n" + "This may break some stuff like SSE-based optimizations assuming the " + " objects are word aligned."; + // Use placement new to initialize our instance in our preallocated space. + // The parenthesis is very important here to force POD type initialization. + return new (instance) Type(); + } + static void Delete(Type* instance) { + // Explicitly call the destructor. + instance->~Type(); + } +}; + +// NOTE(gejun): BullseyeCoverage Compile C++ 8.4.4 complains about `undefined +// reference' on in-place assignments to static constants. +template +const bool DefaultLazyInstanceTraits::kRegisterOnExit = true; +#ifndef NDEBUG +template +const bool DefaultLazyInstanceTraits::kAllowedToAccessOnNonjoinableThread = false; +#endif + +// We pull out some of the functionality into non-templated functions, so we +// can implement the more complicated pieces out of line in the .cc file. +namespace internal { + +// Use LazyInstance::Leaky for a less-verbose call-site typedef; e.g.: +// butil::LazyInstance::Leaky my_leaky_lazy_instance; +// instead of: +// butil::LazyInstance > +// my_leaky_lazy_instance; +// (especially when T is MyLongTypeNameImplClientHolderFactory). +// Only use this internal::-qualified verbose form to extend this traits class +// (depending on its implementation details). +template +struct LeakyLazyInstanceTraits { + static const bool kRegisterOnExit; +#ifndef NDEBUG + static const bool kAllowedToAccessOnNonjoinableThread; +#endif + + static Type* New(void* instance) { + // Instruct LeakSanitizer to ignore the designated memory leak. + ANNOTATE_SCOPED_MEMORY_LEAK; + return DefaultLazyInstanceTraits::New(instance); + } + static void Delete(Type* instance) { + } +}; + +template +const bool LeakyLazyInstanceTraits::kRegisterOnExit = false; +#ifndef NDEBUG +template +const bool LeakyLazyInstanceTraits::kAllowedToAccessOnNonjoinableThread = true; +#endif + +// Our AtomicWord doubles as a spinlock, where a value of +// kBeingCreatedMarker means the spinlock is being held for creation. +static const subtle::AtomicWord kLazyInstanceStateCreating = 1; + +// Check if instance needs to be created. If so return true otherwise +// if another thread has beat us, wait for instance to be created and +// return false. +BUTIL_EXPORT bool NeedsLazyInstance(subtle::AtomicWord* state); + +// After creating an instance, call this to register the dtor to be called +// at program exit and to update the atomic state to hold the |new_instance| +BUTIL_EXPORT void CompleteLazyInstance(subtle::AtomicWord* state, + subtle::AtomicWord new_instance, + void* lazy_instance, + void (*dtor)(void*)); + +} // namespace internal + +template > +class LazyInstance { + public: + // Do not define a destructor, as doing so makes LazyInstance a + // non-POD-struct. We don't want that because then a static initializer will + // be created to register the (empty) destructor with atexit() under MSVC, for + // example. We handle destruction of the contained Type class explicitly via + // the OnExit member function, where needed. + // ~LazyInstance() {} + + // Convenience typedef to avoid having to repeat Type for leaky lazy + // instances. + typedef LazyInstance > Leaky; + + Type& Get() { + return *Pointer(); + } + + Type* Pointer() { +#ifndef NDEBUG + // Avoid making TLS lookup on release builds. + if (!Traits::kAllowedToAccessOnNonjoinableThread) + ThreadRestrictions::AssertSingletonAllowed(); +#endif + // If any bit in the created mask is true, the instance has already been + // fully constructed. + static const subtle::AtomicWord kLazyInstanceCreatedMask = + ~internal::kLazyInstanceStateCreating; + + // We will hopefully have fast access when the instance is already created. + // Since a thread sees private_instance_ == 0 or kLazyInstanceStateCreating + // at most once, the load is taken out of NeedsInstance() as a fast-path. + // The load has acquire memory ordering as a thread which sees + // private_instance_ > creating needs to acquire visibility over + // the associated data (private_buf_). Pairing Release_Store is in + // CompleteLazyInstance(). + subtle::AtomicWord value = subtle::Acquire_Load(&private_instance_); + if (!(value & kLazyInstanceCreatedMask) && + internal::NeedsLazyInstance(&private_instance_)) { + // Create the instance in the space provided by |private_buf_|. + value = reinterpret_cast( + Traits::New(private_buf_.void_data())); + internal::CompleteLazyInstance(&private_instance_, value, this, + Traits::kRegisterOnExit ? OnExit : NULL); + } + + // This annotation helps race detectors recognize correct lock-less + // synchronization between different threads calling Pointer(). + // We suggest dynamic race detection tool that "Traits::New" above + // and CompleteLazyInstance(...) happens before "return instance()" below. + // See the corresponding HAPPENS_BEFORE in CompleteLazyInstance(...). + ANNOTATE_HAPPENS_AFTER(&private_instance_); + return instance(); + } + + bool operator==(Type* p) { + switch (subtle::NoBarrier_Load(&private_instance_)) { + case 0: + return p == NULL; + case internal::kLazyInstanceStateCreating: + return static_cast(p) == private_buf_.void_data(); + default: + return p == instance(); + } + } + + // Effectively private: member data is only public to allow the linker to + // statically initialize it and to maintain a POD class. DO NOT USE FROM + // OUTSIDE THIS CLASS. + + subtle::AtomicWord private_instance_; + // Preallocated space for the Type instance. + butil::AlignedMemory private_buf_; + + private: + Type* instance() { + return reinterpret_cast(subtle::NoBarrier_Load(&private_instance_)); + } + + // Adapter function for use with AtExit. This should be called single + // threaded, so don't synchronize across threads. + // Calling OnExit while the instance is in use by other threads is a mistake. + static void OnExit(void* lazy_instance) { + LazyInstance* me = + reinterpret_cast*>(lazy_instance); + Traits::Delete(me->instance()); + subtle::NoBarrier_Store(&me->private_instance_, 0); + } +}; + +} // namespace butil + +#endif // BUTIL_LAZY_INSTANCE_H_ diff --git a/src/butil/location.cc b/src/butil/location.cc new file mode 100644 index 0000000..cb7b555 --- /dev/null +++ b/src/butil/location.cc @@ -0,0 +1,102 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/build_config.h" + +#if defined(COMPILER_MSVC) +// MSDN says to #include , but that breaks the VS2005 build. +extern "C" { + void* _ReturnAddress(); +} +#endif + +#include "butil/location.h" +#include "butil/strings/string_number_conversions.h" +#include "butil/strings/stringprintf.h" + +namespace tracked_objects { + +Location::Location(const char* function_name, + const char* file_name, + int line_number, + const void* program_counter) + : function_name_(function_name), + file_name_(file_name), + line_number_(line_number), + program_counter_(program_counter) { +} + +Location::Location() + : function_name_("Unknown"), + file_name_("Unknown"), + line_number_(-1), + program_counter_(NULL) { +} + +std::string Location::ToString() const { + return std::string(function_name_) + "@" + file_name_ + ":" + + butil::IntToString(line_number_); +} + +void Location::Write(bool display_filename, bool display_function_name, + std::string* output) const { + butil::StringAppendF(output, "%s[%d] ", + display_filename ? file_name_ : "line", + line_number_); + + if (display_function_name) { + WriteFunctionName(output); + output->push_back(' '); + } +} + +void Location::WriteFunctionName(std::string* output) const { + // Translate "<" to "<" for HTML safety. + // TODO(jar): Support ASCII or html for logging in ASCII. + for (const char *p = function_name_; *p; p++) { + switch (*p) { + case '<': + output->append("<"); + break; + + case '>': + output->append(">"); + break; + + default: + output->push_back(*p); + break; + } + } +} + +//------------------------------------------------------------------------------ +LocationSnapshot::LocationSnapshot() : line_number(-1) { +} + +LocationSnapshot::LocationSnapshot( + const tracked_objects::Location& location) + : file_name(location.file_name()), + function_name(location.function_name()), + line_number(location.line_number()) { +} + +LocationSnapshot::~LocationSnapshot() { +} + +//------------------------------------------------------------------------------ +#if defined(COMPILER_MSVC) +__declspec(noinline) +#endif +BUTIL_EXPORT const void* GetProgramCounter() { +#if defined(COMPILER_MSVC) + return _ReturnAddress(); +#elif defined(COMPILER_GCC) && !defined(OS_NACL) + return __builtin_extract_return_addr(__builtin_return_address(0)); +#else + return NULL; +#endif +} + +} // namespace tracked_objects diff --git a/src/butil/location.h b/src/butil/location.h new file mode 100644 index 0000000..6b73253 --- /dev/null +++ b/src/butil/location.h @@ -0,0 +1,94 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_LOCATION_H_ +#define BUTIL_LOCATION_H_ + +#include + +#include "butil/base_export.h" +#include "butil/basictypes.h" + +namespace tracked_objects { + +// Location provides basic info where of an object was constructed, or was +// significantly brought to life. +class BUTIL_EXPORT Location { + public: + // Constructor should be called with a long-lived char*, such as __FILE__. + // It assumes the provided value will persist as a global constant, and it + // will not make a copy of it. + Location(const char* function_name, + const char* file_name, + int line_number, + const void* program_counter); + + // Provide a default constructor for easy of debugging. + Location(); + + // Comparison operator for insertion into a std::map<> hash tables. + // All we need is *some* (any) hashing distinction. Strings should already + // be unique, so we don't bother with strcmp or such. + // Use line number as the primary key (because it is fast, and usually gets us + // a difference), and then pointers as secondary keys (just to get some + // distinctions). + bool operator < (const Location& other) const { + if (line_number_ != other.line_number_) + return line_number_ < other.line_number_; + if (file_name_ != other.file_name_) + return file_name_ < other.file_name_; + return function_name_ < other.function_name_; + } + + const char* function_name() const { return function_name_; } + const char* file_name() const { return file_name_; } + int line_number() const { return line_number_; } + const void* program_counter() const { return program_counter_; } + + std::string ToString() const; + + // Translate the some of the state in this instance into a human readable + // string with HTML characters in the function names escaped, and append that + // string to |output|. Inclusion of the file_name_ and function_name_ are + // optional, and controlled by the boolean arguments. + void Write(bool display_filename, bool display_function_name, + std::string* output) const; + + // Write function_name_ in HTML with '<' and '>' properly encoded. + void WriteFunctionName(std::string* output) const; + + private: + const char* function_name_; + const char* file_name_; + int line_number_; + const void* program_counter_; +}; + +// A "snapshotted" representation of the Location class that can safely be +// passed across process boundaries. +struct BUTIL_EXPORT LocationSnapshot { + // The default constructor is exposed to support the IPC serialization macros. + LocationSnapshot(); + explicit LocationSnapshot(const tracked_objects::Location& location); + ~LocationSnapshot(); + + std::string file_name; + std::string function_name; + int line_number; +}; + +BUTIL_EXPORT const void* GetProgramCounter(); + +// Define a macro to record the current source location. +#define FROM_HERE FROM_HERE_WITH_EXPLICIT_FUNCTION(__FUNCTION__) + +#define FROM_HERE_WITH_EXPLICIT_FUNCTION(function_name) \ + ::tracked_objects::Location(function_name, \ + __FILE__, \ + __LINE__, \ + ::tracked_objects::GetProgramCounter()) + +} // namespace tracked_objects + +#endif // BUTIL_LOCATION_H_ diff --git a/src/butil/logging.cc b/src/butil/logging.cc new file mode 100644 index 0000000..29d4111 --- /dev/null +++ b/src/butil/logging.cc @@ -0,0 +1,1988 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2012-10-08 23:53:50 + +#include "butil/logging.h" + +#include +DEFINE_bool(log_as_json, false, "Print log as a valid JSON"); +DEFINE_bool(escape_log, false, "Escape log content before printing"); + +#if !BRPC_WITH_GLOG + +#if defined(OS_WIN) +#include +#include +typedef HANDLE FileHandle; +typedef HANDLE MutexHandle; +// Windows warns on using write(). It prefers _write(). +#define write(fd, buf, count) _write(fd, buf, static_cast(count)) +// Windows doesn't define STDERR_FILENO. Define it here. +#define STDERR_FILENO 2 +#elif defined(OS_MACOSX) +#include +#include +#include +#elif defined(OS_POSIX) +#if defined(OS_NACL) || defined(OS_LINUX) +#include // timespec doesn't seem to be in +#else +#include +#endif +#include +#endif +#if defined(OS_POSIX) +#include +#include +#include +#include +#include +#include +#define MAX_PATH PATH_MAX +typedef FILE* FileHandle; +typedef pthread_mutex_t* MutexHandle; +#endif + +#include +#include +#include +#include +#include +#include + +#include "butil/file_util.h" +#include "butil/debug/alias.h" +#include "butil/debug/debugger.h" +#include "butil/debug/stack_trace.h" +#include "butil/debug/leak_annotations.h" +#include "butil/posix/eintr_wrapper.h" +#include "butil/strings/string_util.h" +#include "butil/strings/stringprintf.h" +#include "butil/strings/utf_string_conversions.h" +#include "butil/synchronization/condition_variable.h" +#include "butil/threading/platform_thread.h" +#include "butil/threading/simple_thread.h" +#include "butil/object_pool.h" + +#if defined(OS_POSIX) +#include "butil/errno.h" +#include "butil/fd_guard.h" +#endif +#if defined(OS_LINUX) +#include +#endif + +#if defined(OS_ANDROID) +#include +#endif + +#include +#include +#include +#include +#include "butil/atomicops.h" +#include "butil/thread_local.h" +#include "butil/scoped_lock.h" // BAIDU_SCOPED_LOCK +#include "butil/string_splitter.h" +#include "butil/time.h" +#include "butil/containers/doubly_buffered_data.h" +#include "butil/memory/singleton.h" +#include "butil/endpoint.h" +#include "butil/reloadable_flags.h" +#ifdef BAIDU_INTERNAL +#include "butil/comlog_sink.h" +#endif + +extern "C" { +uint64_t BAIDU_WEAK bthread_self(); +typedef struct { + uint32_t index; // index in KeyTable + uint32_t version; // ABA avoidance +} bthread_key_t; +int BAIDU_WEAK bthread_key_create(bthread_key_t* key, + void (*destructor)(void* data)); +int BAIDU_WEAK bthread_setspecific(bthread_key_t key, void* data); +void* BAIDU_WEAK bthread_getspecific(bthread_key_t key); +} + +namespace logging { + +DEFINE_bool(crash_on_fatal_log, false, + "Crash process when a FATAL log is printed"); +BUTIL_VALIDATE_GFLAG(crash_on_fatal_log, butil::PassValidate); + +DEFINE_bool(print_stack_on_check, true, + "Print the stack trace when a CHECK was failed"); +BUTIL_VALIDATE_GFLAG(print_stack_on_check, butil::PassValidate); + +DEFINE_int32(v, 0, "Show all VLOG(m) messages for m <= this." + " Overridable by --vmodule."); +DEFINE_string(vmodule, "", "per-module verbose level." + " Argument is a comma-separated list of MODULE_NAME=LOG_LEVEL." + " MODULE_NAME is a glob pattern, matched against the filename base" + " (that is, name ignoring .cpp/.h)." + " LOG_LEVEL overrides any value given by --v."); + +DEFINE_bool(log_pid, false, "Log process id"); + +DEFINE_bool(log_bid, true, "Log bthread id"); + +DEFINE_int32(minloglevel, 0, "Any log at or above this level will be " + "displayed. Anything below this level will be silently ignored. " + "0=INFO 1=NOTICE 2=WARNING 3=ERROR 4=FATAL"); +BUTIL_VALIDATE_GFLAG(minloglevel, butil::NonNegativeInteger); + +DEFINE_bool(log_hostname, false, "Add host after pid in each log so" + " that we know where logs came from when using aggregation tools" + " like ELK."); + +DEFINE_bool(log_year, false, "Log year in datetime part in each log"); + +DEFINE_bool(log_func_name, false, "[DEPRECATED]Log function name in each log. " + "Now DefaultLogSink logs function names by default. " + "Customized LogSink can also log function names through " + "corresponding OnLogMessage."); + +DEFINE_bool(async_log, false, "Use async log"); + +DEFINE_bool(async_log_in_background_always, false, "Async log written in background always."); + +DEFINE_int32(max_async_log_queue_size, 100000, "Max async log size. " + "If current log count of async log > max_async_log_size, " + "Use sync log to protect process."); + +DEFINE_int32(sleep_to_flush_async_log_s, 0, + "If the value > 0, sleep before atexit to flush async log"); + +namespace { + +LoggingDestination logging_destination = LOG_DEFAULT; + +// For BLOG_ERROR and above, always print to stderr. +const int kAlwaysPrintErrorLevel = BLOG_ERROR; + +// Which log file to use? This is initialized by InitLogging or +// will be lazily initialized to the default value when it is +// first needed. +#if defined(OS_WIN) +typedef std::wstring PathString; +#else +typedef std::string PathString; +#endif +PathString* log_file_name = NULL; + +// this file is lazily opened and the handle may be NULL +FileHandle log_file = NULL; + +// Should we pop up fatal debug messages in a dialog? +bool show_error_dialogs = false; + +// An assert handler override specified by the client to be called instead of +// the debug message dialog and process termination. +LogAssertHandler log_assert_handler = NULL; + +BAIDU_VOLATILE_THREAD_LOCAL(int32_t, tls_log_pid, 0); +BAIDU_VOLATILE_THREAD_LOCAL(butil::PlatformThreadId, tls_log_tid, 0); + +// Helper functions to wrap platform differences. + +int32_t CurrentProcessId() { +#if defined(OS_WIN) + return GetCurrentProcessId(); +#elif defined(OS_POSIX) + return getpid(); +#endif +} + +void DeleteFilePath(const PathString& log_name) { +#if defined(OS_WIN) + DeleteFile(log_name.c_str()); +#elif defined (OS_NACL) + // Do nothing; unlink() isn't supported on NaCl. +#else + unlink(log_name.c_str()); +#endif +} + +#if defined(OS_LINUX) +static PathString GetProcessName() { + butil::fd_guard fd(open("/proc/self/cmdline", O_RDONLY)); + if (fd < 0) { + return "unknown"; + } + char buf[512]; + const ssize_t len = read(fd, buf, sizeof(buf) - 1); + if (len <= 0) { + return "unknown"; + } + buf[len] = '\0'; + // Not string(buf, len) because we needs to buf to be truncated at first \0. + // Under gdb, the first part of cmdline may include path. + return butil::FilePath(std::string(buf)).BaseName().value(); +} +#endif + +PathString GetDefaultLogFile() { +#if defined(OS_WIN) + // On Windows we use the same path as the exe. + wchar_t module_name[MAX_PATH]; + GetModuleFileName(NULL, module_name, MAX_PATH); + + PathString log_file = module_name; + PathString::size_type last_backslash = + log_file.rfind('\\', log_file.size()); + if (last_backslash != PathString::npos) + log_file.erase(last_backslash + 1); + log_file += L"debug.log"; + return log_file; +#elif defined(OS_LINUX) + return GetProcessName() + ".log"; +#elif defined(OS_POSIX) + // On other platforms we just use the current directory. + return PathString("debug.log"); +#endif +} + +// This class acts as a wrapper for locking the logging files. +// LoggingLock::Init() should be called from the main thread before any logging +// is done. Then whenever logging, be sure to have a local LoggingLock +// instance on the stack. This will ensure that the lock is unlocked upon +// exiting the frame. +// LoggingLocks can not be nested. +class LoggingLock { +public: + LoggingLock() { + LockLogging(); + } + + ~LoggingLock() { + UnlockLogging(); + } + + static void Init(LogLockingState lock_log, const LogChar* new_log_file) { + if (initialized) + return; + lock_log_file = lock_log; + if (lock_log_file == LOCK_LOG_FILE) { +#if defined(OS_WIN) + if (!log_mutex) { + std::wstring safe_name; + if (new_log_file) + safe_name = new_log_file; + else + safe_name = GetDefaultLogFile(); + // \ is not a legal character in mutex names so we replace \ with / + std::replace(safe_name.begin(), safe_name.end(), '\\', '/'); + std::wstring t(L"Global\\"); + t.append(safe_name); + log_mutex = ::CreateMutex(NULL, FALSE, t.c_str()); + + if (log_mutex == NULL) { +#if DEBUG + // Keep the error code for debugging + int error = GetLastError(); // NOLINT + butil::debug::BreakDebugger(); +#endif + // Return nicely without putting initialized to true. + return; + } + } +#endif + } else { + log_lock = new butil::Mutex; + } + initialized = true; + } + +private: + static void LockLogging() { + if (lock_log_file == LOCK_LOG_FILE) { +#if defined(OS_WIN) + ::WaitForSingleObject(log_mutex, INFINITE); + // WaitForSingleObject could have returned WAIT_ABANDONED. We don't + // abort the process here. UI tests might be crashy sometimes, + // and aborting the test binary only makes the problem worse. + // We also don't use LOG macros because that might lead to an infinite + // loop. For more info see http://crbug.com/18028. +#elif defined(OS_POSIX) + pthread_mutex_lock(&log_mutex); +#endif + } else { + // use the lock + log_lock->lock(); + } + } + + static void UnlockLogging() { + if (lock_log_file == LOCK_LOG_FILE) { +#if defined(OS_WIN) + ReleaseMutex(log_mutex); +#elif defined(OS_POSIX) + pthread_mutex_unlock(&log_mutex); +#endif + } else { + log_lock->unlock(); + } + } + + // The lock is used if log file locking is false. It helps us avoid problems + // with multiple threads writing to the log file at the same time. + static butil::Mutex* log_lock; + + // When we don't use a lock, we are using a global mutex. We need to do this + // because LockFileEx is not thread safe. +#if defined(OS_WIN) + static MutexHandle log_mutex; +#elif defined(OS_POSIX) + static pthread_mutex_t log_mutex; +#endif + + static bool initialized; + static LogLockingState lock_log_file; +}; + +// static +bool LoggingLock::initialized = false; +// static +butil::Mutex* LoggingLock::log_lock = NULL; +// static +LogLockingState LoggingLock::lock_log_file = LOCK_LOG_FILE; + +#if defined(OS_WIN) +// static +MutexHandle LoggingLock::log_mutex = NULL; +#elif defined(OS_POSIX) +pthread_mutex_t LoggingLock::log_mutex = PTHREAD_MUTEX_INITIALIZER; +#endif + +// Called by logging functions to ensure that debug_file is initialized +// and can be used for writing. Returns false if the file could not be +// initialized. debug_file will be NULL in this case. +bool InitializeLogFileHandle() { + if (log_file) + return true; + + if (!log_file_name) { + // Nobody has called InitLogging to specify a debug log file, so here we + // initialize the log file name to a default. + log_file_name = new PathString(GetDefaultLogFile()); + } + + if ((logging_destination & LOG_TO_FILE) != 0) { +#if defined(OS_WIN) + log_file = CreateFile(log_file_name->c_str(), GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (log_file == INVALID_HANDLE_VALUE || log_file == NULL) { + // try the current directory + log_file = CreateFile(L".\\debug.log", GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (log_file == INVALID_HANDLE_VALUE || log_file == NULL) { + log_file = NULL; + return false; + } + } + SetFilePointer(log_file, 0, 0, FILE_END); +#elif defined(OS_POSIX) + log_file = fopen(log_file_name->c_str(), "a"); + if (log_file == NULL) { + fprintf(stderr, "Fail to fopen %s: %s", log_file_name->c_str(), berror()); + return false; + } +#endif + } + + return true; +} + +void CloseFile(FileHandle log) { +#if defined(OS_WIN) + CloseHandle(log); +#else + fclose(log); +#endif +} + +void CloseLogFileUnlocked() { + if (!log_file) + return; + + CloseFile(log_file); + log_file = NULL; +} + +void Log2File(const std::string& log) { + // We can have multiple threads and/or processes, so try to prevent them + // from clobbering each other's writes. + // If the client app did not call InitLogging, and the lock has not + // been created do it now. We do this on demand, but if two threads try + // to do this at the same time, there will be a race condition to create + // the lock. This is why InitLogging should be called from the main + // thread at the beginning of execution. + LoggingLock::Init(LOCK_LOG_FILE, NULL); + LoggingLock logging_lock; + if (InitializeLogFileHandle()) { +#if defined(OS_WIN) + SetFilePointer(log_file, 0, 0, SEEK_END); + DWORD num_written; + WriteFile(log_file, static_cast(log.data()), + static_cast(log.size()), &num_written, NULL); +#else + fwrite(log.data(), log.size(), 1, log_file); + fflush(log_file); +#endif + } +} + +} // namespace + +#if defined(OS_LINUX) || defined(OS_MACOSX) +typedef timeval TimeVal; +#else +struct TimeVal { + time_t tv_sec; +}; +#endif + +TimeVal GetTimestamp() { +#if defined(OS_LINUX) || defined(OS_MACOSX) + timeval tv; + gettimeofday(&tv, NULL); + return tv; +#else + return { time(NULL) }; +#endif +} + +struct BAIDU_CACHELINE_ALIGNMENT LogInfo { + ~LogInfo() = default; + void clear() { + file.clear(); + func.clear(); + content.clear(); + } + + std::string file; + std::string func; + std::string content; + TimeVal timestamp{}; + int severity{0}; + int line{0}; + // If `raw' is false, content has been a complete log. + // If raw is true, a complete log consists of all properties of LogInfo. + bool raw{false}; +}; + +struct BAIDU_CACHELINE_ALIGNMENT LogRequest { + static LogRequest* const UNCONNECTED; + + LogRequest* next{NULL}; + LogInfo log_info; +}; + +LogRequest* const LogRequest::UNCONNECTED = (LogRequest*)(intptr_t)-1; + +class AsyncLogger : public butil::SimpleThread { +public: + static AsyncLogger* GetInstance(); + + void Log(const LogInfo& log_info); + void Log(LogInfo&& log_info); + void StopAndJoin(); + +private: +friend struct DefaultSingletonTraits; + + static LogRequest _stop_req; + + AsyncLogger(); + ~AsyncLogger() override; + + static void AtExit() { + GetInstance()->StopAndJoin(); + if (FLAGS_sleep_to_flush_async_log_s > 0) { + ::sleep(FLAGS_sleep_to_flush_async_log_s); + } + } + + void LogImpl(LogRequest* log_req); + + void Run() override; + + void LogTask(LogRequest* req); + + bool IsLogComplete(LogRequest* old_head); + + void DoLog(LogRequest* req); + void DoLog(const LogInfo& log_info); + + butil::atomic _log_head; + butil::Mutex _mutex; + butil::ConditionVariable _cond; + LogRequest* _current_log_request; + butil::atomic _log_request_count; + butil::atomic _stop; +}; + +AsyncLogger* AsyncLogger::GetInstance() { + return Singleton>::get(); +} + +AsyncLogger::AsyncLogger() + : butil::SimpleThread("async_log_thread") + , _log_head(NULL) + , _cond(&_mutex) + , _current_log_request(NULL) + , _stop(false) { + Start(); + // We need to stop async logger and + // flush all async log before exit. + atexit(AtExit); +} + +AsyncLogger::~AsyncLogger() { + StopAndJoin(); +} + +std::string LogInfoToLogStr(int severity, butil::StringPiece file, + int line, butil::StringPiece func, + butil::StringPiece content) { + // There's a copy here to concatenate prefix and content. Since + // DefaultLogSink is hardly used right now, the copy is irrelevant. + // A LogSink focused on performance should also be able to handle + // non-continuous inputs which is a must to maximize performance. + std::ostringstream os; + PrintLog(os, severity, file.data(), line, func.data(), content); + os << '\n'; + return os.str(); +} + +std::string LogInfo2LogStr(const LogInfo& log_info) { + return LogInfoToLogStr(log_info.severity, log_info.file, log_info.line, + log_info.func, log_info.content); +} + +void AsyncLogger::Log(const LogInfo& log_info) { + if (log_info.content.empty()) { + return; + } + + bool is_full = FLAGS_max_async_log_queue_size > 0 && + _log_request_count.fetch_add(1, butil::memory_order_relaxed) > + FLAGS_max_async_log_queue_size; + if (is_full || _stop.load(butil::memory_order_relaxed)) { + // Async logger is full or stopped, fallback to sync log. + DoLog(log_info); + return; + } + + auto log_req = butil::get_object(); + if (!log_req) { + // Async log failed, fallback to sync log. + DoLog(log_info); + return; + } + log_req->log_info = log_info; + LogImpl(log_req); +} + +void AsyncLogger::Log(LogInfo&& log_info) { + if (log_info.content.empty()) { + return; + } + + bool is_full = FLAGS_max_async_log_queue_size > 0 && + _log_request_count.fetch_add(1, butil::memory_order_relaxed) > + FLAGS_max_async_log_queue_size; + if (is_full || _stop.load(butil::memory_order_relaxed)) { + // Async logger is full or stopped, fallback to sync log. + DoLog(log_info); + return; + } + + auto log_req = butil::get_object(); + if (!log_req) { + // Async log failed, fallback to sync log. + DoLog(log_info); + return; + } + ANNOTATE_SCOPED_MEMORY_LEAK; + log_req->log_info = std::move(log_info); + LogImpl(log_req); +} + +void AsyncLogger::LogImpl(LogRequest* log_req) { + log_req->next = LogRequest::UNCONNECTED; + // Release fence makes sure the thread getting request sees *req + LogRequest* const prev_head = + _log_head.exchange(log_req, butil::memory_order_release); + if (prev_head != NULL) { + // Someone is logging. The async_log_thread thread may spin + // until req->next to be non-UNCONNECTED. This process is not + // lock-free, but the duration is so short(1~2 instructions, + // depending on compiler) that the spin rarely occurs in practice + // (I've not seen any spin in highly contended tests). + log_req->next = prev_head; + return; + } + // We've got the right to write. + log_req->next = NULL; + + if (!FLAGS_async_log_in_background_always) { + // Use sync log for the LogRequest + // which has got the right to write. + DoLog(log_req); + // Return when there's no more LogRequests. + if (IsLogComplete(log_req)) { + butil::return_object(log_req); + return; + } + } + + BAIDU_SCOPED_LOCK(_mutex); + if (_stop.load(butil::memory_order_relaxed)) { + // Async logger is stopped, fallback to sync log. + LogTask(log_req); + } else { + // Wake up async logger. + _current_log_request = log_req; + _cond.Signal(); + } +} + +void AsyncLogger::StopAndJoin() { + if (!_stop.exchange(true, butil::memory_order_relaxed)) { + BAIDU_SCOPED_LOCK(_mutex); + _cond.Signal(); + } + if (!HasBeenJoined()) { + Join(); + } +} + +void AsyncLogger::Run() { + while (true) { + BAIDU_SCOPED_LOCK(_mutex); + while (!_stop.load(butil::memory_order_relaxed) && + !_current_log_request) { + _cond.Wait(); + } + if (_stop.load(butil::memory_order_relaxed) && + !_current_log_request) { + break; + } + + LogTask(_current_log_request); + _current_log_request = NULL; + } +} + +void AsyncLogger::LogTask(LogRequest* req) { + do { + // req was logged, skip it. + if (req->next != NULL && req->log_info.content.empty()) { + LogRequest* const saved_req = req; + req = req->next; + butil::return_object(saved_req); + } + + // Log all requests to file. + while (req->next != NULL) { + LogRequest* const saved_req = req; + req = req->next; + if (!saved_req->log_info.content.empty()) { + DoLog(saved_req); + } + // Release LogRequests until last request. + butil::return_object(saved_req); + } + if (!req->log_info.content.empty()) { + DoLog(req); + } + + // Return when there's no more LogRequests. + if (IsLogComplete(req)) { + butil::return_object(req); + return; + } + } while (true); +} + +bool AsyncLogger::IsLogComplete(LogRequest* old_head) { + if (old_head->next) { + fprintf(stderr, "old_head->next should be NULL\n"); + } + LogRequest* new_head = old_head; + LogRequest* desired = NULL; + if (_log_head.compare_exchange_strong( + new_head, desired, butil::memory_order_acquire)) { + // No one added new requests. + return true; + } + if (new_head == old_head) { + fprintf(stderr, "new_head should not be equal to old_head\n"); + } + // Above acquire fence pairs release fence of exchange in Log() to make + // sure that we see all fields of requests set. + + // Someone added new requests. + // Reverse the list until old_head. + LogRequest* tail = NULL; + LogRequest* p = new_head; + do { + while (p->next == LogRequest::UNCONNECTED) { + sched_yield(); + } + LogRequest* const saved_next = p->next; + p->next = tail; + tail = p; + p = saved_next; + if (!p) { + fprintf(stderr, "p should not be NULL\n"); + } + } while (p != old_head); + + // Link old list with new list. + old_head->next = tail; + return false; +} + +void AsyncLogger::DoLog(LogRequest* req) { + DoLog(req->log_info); + req->log_info.clear(); +} + +void AsyncLogger::DoLog(const LogInfo& log_info) { + if (log_info.raw) { + Log2File(LogInfo2LogStr(log_info)); + } else { + Log2File(log_info.content); + } + _log_request_count.fetch_sub(1, butil::memory_order_relaxed); +} + +LoggingSettings::LoggingSettings() + : logging_dest(LOG_DEFAULT), + log_file(NULL), + lock_log(LOCK_LOG_FILE), + delete_old(APPEND_TO_OLD_LOG_FILE) {} + +bool BaseInitLoggingImpl(const LoggingSettings& settings) { +#if defined(OS_NACL) + // Can log only to the system debug log. + CHECK_EQ(settings.logging_dest & ~LOG_TO_SYSTEM_DEBUG_LOG, 0); +#endif + + logging_destination = settings.logging_dest; + + // ignore file options unless logging to file is set. + if ((logging_destination & LOG_TO_FILE) == 0) + return true; + + LoggingLock::Init(settings.lock_log, settings.log_file); + LoggingLock logging_lock; + + // Calling InitLogging twice or after some log call has already opened the + // default log file will re-initialize to the new options. + CloseLogFileUnlocked(); + + if (!log_file_name) + log_file_name = new PathString(); + if (settings.log_file) { + *log_file_name = settings.log_file; + } else { + *log_file_name = GetDefaultLogFile(); + } + if (settings.delete_old == DELETE_OLD_LOG_FILE) + DeleteFilePath(*log_file_name); + + return InitializeLogFileHandle(); +} + +void SetMinLogLevel(int level) { + FLAGS_minloglevel = std::min(BLOG_FATAL, level); +} + +int GetMinLogLevel() { + return FLAGS_minloglevel; +} + +void SetShowErrorDialogs(bool enable_dialogs) { + show_error_dialogs = enable_dialogs; +} + +void SetLogAssertHandler(LogAssertHandler handler) { + log_assert_handler = handler; +} + +const char* const log_severity_names[LOG_NUM_SEVERITIES] = { + "INFO", "NOTICE", "WARNING", "ERROR", "FATAL" }; + +static void PrintLogSeverity(std::ostream& os, int severity) { + if (severity < 0) { + // Add extra space to separate from following datetime. + os << 'V' << -severity << ' '; + } else if (severity < LOG_NUM_SEVERITIES) { + os << log_severity_names[severity][0]; + } else { + os << 'U'; + } +} + +void PrintLogPrefix(std::ostream& os, int severity, + butil::StringPiece file, int line, + butil::StringPiece func, TimeVal tv) { + PrintLogSeverity(os, severity); + time_t t = tv.tv_sec; + struct tm local_tm = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL}; +#if _MSC_VER >= 1400 + localtime_s(&local_tm, &t); +#else + localtime_r(&t, &local_tm); +#endif + const char prev_fill = os.fill('0'); + if (FLAGS_log_year) { + os << std::setw(4) << local_tm.tm_year + 1900; + } + os << std::setw(2) << local_tm.tm_mon + 1 + << std::setw(2) << local_tm.tm_mday << ' ' + << std::setw(2) << local_tm.tm_hour << ':' + << std::setw(2) << local_tm.tm_min << ':' + << std::setw(2) << local_tm.tm_sec; +#if defined(OS_LINUX) || defined(OS_MACOSX) + os << '.' << std::setw(6) << tv.tv_usec; +#endif + if (FLAGS_log_pid) { + int32_t pid = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_log_pid); + if (pid == 0) { + pid = CurrentProcessId(); + BAIDU_SET_VOLATILE_THREAD_LOCAL(tls_log_pid, pid); + } + os << ' ' << std::setfill(' ') << std::setw(5) << pid; + } + butil::PlatformThreadId tid = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_log_tid); + if (tid == 0) { + tid = butil::PlatformThread::CurrentId(); + BAIDU_SET_VOLATILE_THREAD_LOCAL(tls_log_tid, tid); + } + os << ' ' << std::setfill(' ') << std::setw(5) << tid << std::setfill('0'); + if (FLAGS_log_bid && bthread_self) { + os << ' ' << std::setfill(' ') << std::setw(5) << bthread_self(); + } + if (FLAGS_log_hostname) { + butil::StringPiece hostname(butil::my_hostname()); + if (hostname.ends_with(".baidu.com")) { // make it shorter + hostname.remove_suffix(10); + } + os << ' ' << hostname; + } + os << ' ' << file << ':' << line; + if (!func.empty()) { + os << " " << func; + } + os << "] "; + + os.fill(prev_fill); +} + +void PrintLogPrefix(std::ostream& os, int severity, + const char* file, int line) { + PrintLogPrefix(os, severity, file, line, "", GetTimestamp()); +} + +static void PrintLogPrefixAsJSON(std::ostream& os, int severity, + butil::StringPiece file, + butil::StringPiece func, + int line, TimeVal tv) { + // severity + os << "\"L\":\""; + if (severity < 0) { + os << 'V' << -severity; + } else if (severity < LOG_NUM_SEVERITIES) { + os << log_severity_names[severity][0]; + } else { + os << 'U'; + } + // time + os << "\",\"T\":\""; + time_t t = tv.tv_sec; + struct tm local_tm = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL}; +#if _MSC_VER >= 1400 + localtime_s(&local_tm, &t); +#else + localtime_r(&t, &local_tm); +#endif + const char prev_fill = os.fill('0'); + if (FLAGS_log_year) { + os << std::setw(4) << local_tm.tm_year + 1900; + } + os << std::setw(2) << local_tm.tm_mon + 1 + << std::setw(2) << local_tm.tm_mday << ' ' + << std::setw(2) << local_tm.tm_hour << ':' + << std::setw(2) << local_tm.tm_min << ':' + << std::setw(2) << local_tm.tm_sec; +#if defined(OS_LINUX) || defined(OS_MACOSX) + os << '.' << std::setw(6) << tv.tv_usec; +#endif + os << "\","; + os.fill(prev_fill); + + if (FLAGS_log_pid) { + os << "\"pid\":\"" << CurrentProcessId() << "\","; + } + os << "\"tid\":\"" << butil::PlatformThread::CurrentId() << "\","; + if (FLAGS_log_hostname) { + butil::StringPiece hostname(butil::my_hostname()); + if (hostname.ends_with(".baidu.com")) { // make it shorter + hostname.remove_suffix(10); + } + os << "\"host\":\"" << hostname << "\","; + } + os << "\"C\":\"" << file << ':' << line; + if (!func.empty()) { + os << " " << func; + } + os << "\""; +} + +void EscapeJson(std::ostream& os, const butil::StringPiece& s) { + for (auto it = s.begin(); it != s.end(); it++) { + auto c = *it; + switch (c) { + case '"': os << "\\\""; break; + case '\\': os << "\\\\"; break; + case '\b': os << "\\b"; break; + case '\f': os << "\\f"; break; + case '\n': os << "\\n"; break; + case '\r': os << "\\r"; break; + case '\t': os << "\\t"; break; + default: os << c; + } + } +} + +inline void OutputLog(std::ostream& os, const butil::StringPiece& s) { + if (FLAGS_escape_log) { + EscapeJson(os, s); + } else { + os.write(s.data(), s.length()); + } +} + +void PrintLog(std::ostream& os, int severity, const char* file, int line, + const char* func, const butil::StringPiece& content) { + if (!FLAGS_log_as_json) { + PrintLogPrefix(os, severity, file, line, func, GetTimestamp()); + OutputLog(os, content); + } else { + os << '{'; + PrintLogPrefixAsJSON(os, severity, file, func, line, GetTimestamp()); + bool pair_quote = false; + if (content.empty() || content[0] != '"') { + // not a json, add a 'M' field + os << ",\"M\":\""; + pair_quote = true; + } else { + os << ','; + } + OutputLog(os, content); + if (pair_quote) { + os << '"'; + } else if (!content.empty() && content[content.size() -1 ] != '"') { + // Controller may write `"M":"...` which misses the last quote + os << '"'; + } + os << '}'; + } +} + +void PrintLog(std::ostream& os, + int severity, const char* file, int line, + const butil::StringPiece& content) { + PrintLog(os, severity, file, line, "", content); +} + +// A log message handler that gets notified of every log message we process. +class DoublyBufferedLogSink : public butil::DoublyBufferedData { +public: + DoublyBufferedLogSink() {} + static DoublyBufferedLogSink* GetInstance(); +private: +friend struct DefaultSingletonTraits; + DISALLOW_COPY_AND_ASSIGN(DoublyBufferedLogSink); +}; + +DoublyBufferedLogSink* DoublyBufferedLogSink::GetInstance() { + return Singleton >::get(); +} + +struct SetLogSinkFn { + LogSink* new_sink; + LogSink* old_sink; + + bool operator()(LogSink*& ptr) { + old_sink = ptr; + ptr = new_sink; + return true; + } +}; + +LogSink* SetLogSink(LogSink* sink) { + SetLogSinkFn fn = { sink, NULL }; + CHECK(DoublyBufferedLogSink::GetInstance()->Modify(fn)); + return fn.old_sink; +} + +// MSVC doesn't like complex extern templates and DLLs. +#if !defined(COMPILER_MSVC) +// Explicit instantiations for commonly used comparisons. +template std::string* MakeCheckOpString( + const int&, const int&, const char* names); +template std::string* MakeCheckOpString( + const unsigned long&, const unsigned long&, const char* names); +template std::string* MakeCheckOpString( + const unsigned long&, const unsigned int&, const char* names); +template std::string* MakeCheckOpString( + const unsigned int&, const unsigned long&, const char* names); +template std::string* MakeCheckOpString( + const std::string&, const std::string&, const char* name); +#endif + +#if !defined(NDEBUG) +// Displays a message box to the user with the error message in it. +// Used for fatal messages, where we close the app simultaneously. +// This is for developers only; we don't use this in circumstances +// (like release builds) where users could see it, since users don't +// understand these messages anyway. +void DisplayDebugMessageInDialog(const std::string& str) { + if (str.empty()) + return; + + if (!show_error_dialogs) + return; + +#if defined(OS_WIN) + // For Windows programs, it's possible that the message loop is + // messed up on a fatal error, and creating a MessageBox will cause + // that message loop to be run. Instead, we try to spawn another + // process that displays its command line. We look for "Debug + // Message.exe" in the same directory as the application. If it + // exists, we use it, otherwise, we use a regular message box. + wchar_t prog_name[MAX_PATH]; + GetModuleFileNameW(NULL, prog_name, MAX_PATH); + wchar_t* backslash = wcsrchr(prog_name, '\\'); + if (backslash) + backslash[1] = 0; + wcscat_s(prog_name, MAX_PATH, L"debug_message.exe"); + + std::wstring cmdline = butil::UTF8ToWide(str); + if (cmdline.empty()) + return; + + STARTUPINFO startup_info; + memset(&startup_info, 0, sizeof(startup_info)); + startup_info.cb = sizeof(startup_info); + + PROCESS_INFORMATION process_info; + if (CreateProcessW(prog_name, &cmdline[0], NULL, NULL, false, 0, NULL, + NULL, &startup_info, &process_info)) { + WaitForSingleObject(process_info.hProcess, INFINITE); + CloseHandle(process_info.hThread); + CloseHandle(process_info.hProcess); + } else { + // debug process broken, let's just do a message box + MessageBoxW(NULL, &cmdline[0], L"Fatal error", + MB_OK | MB_ICONHAND | MB_TOPMOST); + } +#else + // We intentionally don't implement a dialog on other platforms. + // You can just look at stderr. +#endif +} +#endif // !defined(NDEBUG) + + +bool StringSink::OnLogMessage(int severity, const char* file, int line, + const butil::StringPiece& content) { + return OnLogMessage(severity, file, line, "", content); +} + +bool StringSink::OnLogMessage(int severity, const char* file, + int line, const char* func, + const butil::StringPiece& content) { + std::ostringstream os; + PrintLog(os, severity, file, line, func, content); + const std::string msg = os.str(); + { + butil::AutoLock lock_guard(_lock); + append(msg); + } + return true; +} + +CharArrayStreamBuf::~CharArrayStreamBuf() { + free(_data); +} + +int CharArrayStreamBuf::overflow(int ch) { + if (ch == std::streambuf::traits_type::eof()) { + return ch; + } + size_t new_size = std::max(_size * 3 / 2, (size_t)64); + char* new_data = (char*)malloc(new_size); + if (BAIDU_UNLIKELY(new_data == NULL)) { + setp(NULL, NULL); + return std::streambuf::traits_type::eof(); + } + memcpy(new_data, _data, _size); + free(_data); + _data = new_data; + const size_t old_size = _size; + _size = new_size; + setp(_data, _data + new_size); + pbump(old_size); + // if size == 1, this function will call overflow again. + return sputc(ch); +} + +int CharArrayStreamBuf::sync() { + // data are already there. + return 0; +} + +void CharArrayStreamBuf::reset() { + setp(_data, _data + _size); +} + +LogStream& LogStream::SetPosition(const LogChar* file, int line, + LogSeverity severity) { + _file = file; + _line = line; + _severity = severity; + return *this; +} + +LogStream& LogStream::SetPosition(const LogChar* file, int line, + const LogChar* func, + LogSeverity severity) { + _file = file; + _line = line; + _func = func; + _severity = severity; + return *this; +} + +#if defined(__GNUC__) +static bthread_key_t stream_bkey; +static pthread_key_t stream_pkey; +static pthread_once_t create_stream_key_once = PTHREAD_ONCE_INIT; +inline bool is_bthread_linked() { return bthread_key_create != NULL; } +static void destroy_tls_streams(void* data) { + if (data == NULL) { + return; + } + LogStream** a = (LogStream**)data; + for (int i = 0; i <= LOG_NUM_SEVERITIES; ++i) { + delete a[i]; + } + delete[] a; +} +static void create_stream_key_or_die() { + if (is_bthread_linked()) { + int rc = bthread_key_create(&stream_bkey, destroy_tls_streams); + if (rc) { + fprintf(stderr, "Fail to bthread_key_create"); + exit(1); + } + } else { + int rc = pthread_key_create(&stream_pkey, destroy_tls_streams); + if (rc) { + fprintf(stderr, "Fail to pthread_key_create"); + exit(1); + } + } +} +static LogStream** get_tls_stream_array() { + pthread_once(&create_stream_key_once, create_stream_key_or_die); + if (is_bthread_linked()) { + return (LogStream**)bthread_getspecific(stream_bkey); + } else { + return (LogStream**)pthread_getspecific(stream_pkey); + } +} + +static LogStream** get_or_new_tls_stream_array() { + LogStream** a = get_tls_stream_array(); + if (a == NULL) { + a = new LogStream*[LOG_NUM_SEVERITIES + 1]; + memset(a, 0, sizeof(LogStream*) * (LOG_NUM_SEVERITIES + 1)); + if (is_bthread_linked()) { + bthread_setspecific(stream_bkey, a); + } else { + pthread_setspecific(stream_pkey, a); + } + } + return a; +} + +inline LogStream* CreateLogStream(const LogChar* file, + int line, + const LogChar* func, + LogSeverity severity) { + int slot = 0; + if (severity >= 0) { + DCHECK_LT(severity, LOG_NUM_SEVERITIES); + slot = severity + 1; + } // else vlog + LogStream** stream_array = get_or_new_tls_stream_array(); + LogStream* stream = stream_array[slot]; + if (stream == NULL) { + stream = new LogStream; + stream_array[slot] = stream; + } + if (stream->empty()) { + stream->SetPosition(file, line, func, severity); + } + return stream; +} + +inline LogStream* CreateLogStream(const LogChar* file, + int line, + LogSeverity severity) { + return CreateLogStream(file, line, "", severity); +} + +inline void DestroyLogStream(LogStream* stream) { + if (stream != NULL) { + stream->Flush(); + } +} + +#else + +inline LogStream* CreateLogStream(const LogChar* file, int line, + LogSeverity severity) { + return CreateLogStream(file, line, "", severity); +} + + +inline LogStream* CreateLogStream(const LogChar* file, int line, + const LogChar* func, + LogSeverity severity) { + LogStream* stream = new LogStream; + stream->SetPosition(file, line, func, severity); + return stream; +} + +inline void DestroyLogStream(LogStream* stream) { + delete stream; +} + +#endif // __GNUC__ + +class DefaultLogSink : public LogSink { +public: + static DefaultLogSink* GetInstance() { + return Singleton >::get(); + } + + bool OnLogMessage(int severity, const char* file, int line, + const butil::StringPiece& content) override { + return OnLogMessage(severity, file, line, "", content); + } + + bool OnLogMessage(int severity, const char* file, + int line, const char* func, + const butil::StringPiece& content) override { + std::string log; + if ((logging_destination & LOG_TO_SYSTEM_DEBUG_LOG) != 0 || + severity >= kAlwaysPrintErrorLevel) { + log = LogInfoToLogStr(severity, file, line, func, content); + // When we're only outputting to a log file, above a certain log level, we + // should still output to stderr so that we can better detect and diagnose + // problems with unit tests, especially on the buildbots. + fwrite(log.data(), log.size(), 1, stderr); + fflush(stderr); + } + // write to log file + if ((logging_destination & LOG_TO_FILE) != 0) { + if ((FLAGS_crash_on_fatal_log && severity == BLOG_FATAL) || + !FLAGS_async_log) { + if (log.empty()) { + log = LogInfoToLogStr(severity, file, line, func, content); + } + Log2File(log); + } else { + LogInfo info; + if (log.empty()) { + info.severity = severity; + info.timestamp = GetTimestamp(); + info.file = file; + info.func = func; + info.line = line; + info.content = content.as_string(); + info.raw = true; + } else { + info.content = std::move(log); + info.raw = false; + } + AsyncLogger::GetInstance()->Log(std::move(info)); + } + } + return true; + } +private: + DefaultLogSink() = default; + ~DefaultLogSink() override = default; +friend struct DefaultSingletonTraits; +}; + +void LogStream::FlushWithoutReset() { + if (empty()) { + // Nothing to flush. + return; + } + +#if !defined(OS_NACL) && !defined(__UCLIBC__) + if ((FLAGS_print_stack_on_check && _is_check && _severity == BLOG_FATAL) || _backtrace) { + // Include a stack trace on a fatal. + butil::debug::StackTrace trace; + size_t count = 0; + const void* const* addrs = trace.Addresses(&count); + + *this << std::endl; // Newline to separate from log message. + if (count > 3) { + // Remove top 3 frames which are useless to users. + // #2 may be ~LogStream + // #0 0x00000059ccae butil::debug::StackTrace::StackTrace() + // #1 0x0000005947c7 logging::LogStream::FlushWithoutReset() + // #2 0x000000594b88 logging::LogMessage::~LogMessage() + butil::debug::StackTrace trace_stripped(addrs + 3, count - 3); + trace_stripped.OutputToStream(this); + } else { + trace.OutputToStream(this); + } + } +#endif + // End the data with zero because sink is likely to assume this. + *this << std::ends; + // Move back one step because we don't want to count the zero. + pbump(-1); + + // Give any logsink first dibs on the message. +#ifdef BAIDU_INTERNAL + // If the logsink fails and it's not comlog, try comlog. stderr on last try. + bool tried_comlog = false; +#endif + bool tried_default = false; + { + DoublyBufferedLogSink::ScopedPtr ptr; + if (DoublyBufferedLogSink::GetInstance()->Read(&ptr) == 0 && + (*ptr) != NULL) { + bool result = (*ptr)->OnLogMessage( + _severity, _file, _line, _func, content()); + if (result) { + goto FINISH_LOGGING; + } +#ifdef BAIDU_INTERNAL + tried_comlog = (*ptr == ComlogSink::GetInstance()); +#endif + tried_default = (*ptr == DefaultLogSink::GetInstance()); + } + } + +#ifdef BAIDU_INTERNAL + if (!tried_comlog) { + if (ComlogSink::GetInstance()->OnLogMessage( + _severity, _file, _line, _func, content())) { + goto FINISH_LOGGING; + } + } +#endif + if (!tried_default) { + DefaultLogSink::GetInstance()->OnLogMessage( + _severity, _file, _line, _func, content()); + } + +FINISH_LOGGING: + if (FLAGS_crash_on_fatal_log && _severity == BLOG_FATAL) { + // Ensure the first characters of the string are on the stack so they + // are contained in minidumps for diagnostic purposes. + butil::StringPiece str = content(); + char str_stack[1024]; + str.copy(str_stack, arraysize(str_stack)); + butil::debug::Alias(str_stack); + + if (log_assert_handler) { + // Make a copy of the string for the handler out of paranoia. + log_assert_handler(str.as_string()); + } else { + // Don't use the string with the newline, get a fresh version to send to + // the debug message process. We also don't display assertions to the + // user in release mode. The enduser can't do anything with this + // information, and displaying message boxes when the application is + // hosed can cause additional problems. +#ifndef NDEBUG + DisplayDebugMessageInDialog(str.as_string()); +#endif + // Crash the process to generate a dump. + butil::debug::BreakDebugger(); + } + } +} + +LogMessage::LogMessage(const char* file, int line, LogSeverity severity) + : LogMessage(file, line, "", severity) {} + +LogMessage::LogMessage(const char* file, int line, + const char* func, LogSeverity severity) { + _stream = CreateLogStream(file, line, func, severity); +} + +LogMessage::LogMessage(const char* file, int line, std::string* result) + : LogMessage(file, line, "", result) {} + +LogMessage::LogMessage(const char* file, int line, + const char* func, std::string* result) { + _stream = CreateLogStream(file, line, func, BLOG_FATAL); + *_stream << "Check failed: " << *result; + delete result; +} + +LogMessage::LogMessage(const char* file, int line, LogSeverity severity, + std::string* result) + : LogMessage(file, line, "", severity, result) {} + +LogMessage::LogMessage(const char* file, int line, const char* func, + LogSeverity severity, std::string* result) { + _stream = CreateLogStream(file, line, func, severity); + *_stream << "Check failed: " << *result; + delete result; +} + +LogMessage::~LogMessage() { + DestroyLogStream(_stream); +} + +#if defined(OS_WIN) +// This has already been defined in the header, but defining it again as DWORD +// ensures that the type used in the header is equivalent to DWORD. If not, +// the redefinition is a compile error. +typedef DWORD SystemErrorCode; +#endif + +SystemErrorCode GetLastSystemErrorCode() { +#if defined(OS_WIN) + return ::GetLastError(); +#elif defined(OS_POSIX) + return errno; +#else +#error Not implemented +#endif +} + +void SetLastSystemErrorCode(SystemErrorCode err) { +#if defined(OS_WIN) + ::SetLastError(err); +#elif defined(OS_POSIX) + errno = err; +#else +#error Not implemented +#endif +} + +#if defined(OS_WIN) +BUTIL_EXPORT std::string SystemErrorCodeToString(SystemErrorCode error_code) { + const int error_message_buffer_size = 256; + char msgbuf[error_message_buffer_size]; + DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; + DWORD len = FormatMessageA(flags, NULL, error_code, 0, msgbuf, + arraysize(msgbuf), NULL); + if (len) { + // Messages returned by system end with line breaks. + return butil::CollapseWhitespaceASCII(msgbuf, true) + + butil::StringPrintf(" (0x%X)", error_code); + } + return butil::StringPrintf("Error (0x%X) while retrieving error. (0x%X)", + GetLastError(), error_code); +} +#elif defined(OS_POSIX) +BUTIL_EXPORT std::string SystemErrorCodeToString(SystemErrorCode error_code) { + return berror(error_code); +} +#else +#error Not implemented +#endif + + +#if defined(OS_WIN) +Win32ErrorLogMessage::Win32ErrorLogMessage(const char* file, + int line, + LogSeverity severity, + SystemErrorCode err) + : Win32ErrorLogMessage(file, line, "", severity, err) { +} + +Win32ErrorLogMessage::Win32ErrorLogMessage(const char* file, + int line, + const char* func, + LogSeverity severity, + SystemErrorCode err) + : err_(err) + , log_message_(file, line, func, severity) { +} + +Win32ErrorLogMessage::~Win32ErrorLogMessage() { + stream() << ": " << SystemErrorCodeToString(err_); + // We're about to crash (CHECK). Put |err_| on the stack (by placing it in a + // field) and use Alias in hopes that it makes it into crash dumps. + DWORD last_error = err_; + butil::debug::Alias(&last_error); +} +#elif defined(OS_POSIX) +ErrnoLogMessage::ErrnoLogMessage(const char* file, + int line, + LogSeverity severity, + SystemErrorCode err) + : ErrnoLogMessage(file, line, "", severity, err) {} + +ErrnoLogMessage::ErrnoLogMessage(const char* file, + int line, + const char* func, + LogSeverity severity, + SystemErrorCode err) + : err_(err) + , log_message_(file, line, func, severity) {} + +ErrnoLogMessage::~ErrnoLogMessage() { + stream() << ": " << SystemErrorCodeToString(err_); +} +#endif // OS_WIN + +void CloseLogFile() { + LoggingLock logging_lock; + CloseLogFileUnlocked(); +} + +void RawLog(int level, const char* message) { + if (level >= FLAGS_minloglevel) { + size_t bytes_written = 0; + const size_t message_len = strlen(message); + int rv; + while (bytes_written < message_len) { + rv = HANDLE_EINTR( + write(STDERR_FILENO, message + bytes_written, + message_len - bytes_written)); + if (rv < 0) { + // Give up, nothing we can do now. + break; + } + bytes_written += rv; + } + + if (message_len > 0 && message[message_len - 1] != '\n') { + do { + rv = HANDLE_EINTR(write(STDERR_FILENO, "\n", 1)); + if (rv < 0) { + // Give up, nothing we can do now. + break; + } + } while (rv != 1); + } + } + + if (FLAGS_crash_on_fatal_log && level == BLOG_FATAL) + butil::debug::BreakDebugger(); +} + +// This was defined at the beginning of this file. +#undef write + +#if defined(OS_WIN) +std::wstring GetLogFileFullPath() { + if (log_file_name) + return *log_file_name; + return std::wstring(); +} +#endif + + +// ----------- VLOG stuff ----------------- +struct VLogSite; +struct VModuleList; + +extern const int VLOG_UNINITIALIZED = std::numeric_limits::max(); + +static pthread_mutex_t vlog_site_list_mutex = PTHREAD_MUTEX_INITIALIZER; +static VLogSite* vlog_site_list = NULL; +static VModuleList* vmodule_list = NULL; + +static pthread_mutex_t reset_vmodule_and_v_mutex = PTHREAD_MUTEX_INITIALIZER; + +static const int64_t DELAY_DELETION_SEC = 10; +static std::deque >* +deleting_vmodule_list = NULL; + +struct VLogSite { + VLogSite(const char* filename, int required_v, int line_no) + : _next(0), _v(0), _required_v(required_v), _line_no(line_no) { + // Remove dirname/extname. + butil::StringPiece s(filename); + size_t pos = s.find_last_of("./"); + if (pos != butil::StringPiece::npos) { + if (s[pos] == '.') { + s.remove_suffix(s.size() - pos); + _full_module.assign(s.data(), s.size()); + size_t pos2 = s.find_last_of('/'); + if (pos2 != butil::StringPiece::npos) { + s.remove_prefix(pos2 + 1); + } + } else { + _full_module.assign(s.data(), s.size()); + s.remove_prefix(pos + 1); + } + } // else keep _full_module empty when it equals _module + _module.assign(s.data(), s.size()); + std::transform(_module.begin(), _module.end(), + _module.begin(), ::tolower); + if (!_full_module.empty()) { + std::transform(_full_module.begin(), _full_module.end(), + _full_module.begin(), ::tolower); + } + } + + // The consume/release fence makes the iteration outside lock see + // newly added VLogSite correctly. + VLogSite* next() { return (VLogSite*)butil::subtle::Acquire_Load(&_next); } + const VLogSite* next() const + { return (VLogSite*)butil::subtle::Acquire_Load(&_next); } + void set_next(VLogSite* next) + { butil::subtle::Release_Store(&_next, (butil::subtle::AtomicWord)next); } + + int v() const { return _v; } + int& v() { return _v; } + + int required_v() const { return _required_v; } + int line_no() const { return _line_no; } + + const std::string& module() const { return _module; } + const std::string& full_module() const { return _full_module; } + +private: + // Next site in the list. NULL means no next. + butil::subtle::AtomicWord _next; + + // --vmodule > --v + int _v; + + // vlog is on iff _v >= _required_v + int _required_v; + + // line nubmer of the vlog. + int _line_no; + + // Lowered, dirname & extname removed. + std::string _module; + // Lowered, extname removed. Empty when it equals to _module. + std::string _full_module; +}; + +// Written by Jack Handy +//
jakkhandy@hotmail.com +bool wildcmp(const char* wild, const char* str) { + const char* cp = NULL; + const char* mp = NULL; + + while (*str && *wild != '*') { + if (*wild != *str && *wild != '?') { + return false; + } + ++wild; + ++str; + } + + while (*str) { + if (*wild == '*') { + if (!*++wild) { + return true; + } + mp = wild; + cp = str+1; + } else if (*wild == *str || *wild == '?') { + ++wild; + ++str; + } else { + wild = mp; + str = cp++; + } + } + + while (*wild == '*') { + ++wild; + } + return !*wild; +} + +struct VModuleList { + VModuleList() {} + + int init(const char* vmodules) { + _exact_names.clear(); + _wild_names.clear(); + + for (butil::StringSplitter sp(vmodules, ','); sp; ++sp) { + int verbose_level = std::numeric_limits::max(); + size_t off = 0; + for (; off < sp.length() && sp.field()[off] != '='; ++off) {} + if (off + 1 < sp.length()) { + verbose_level = strtol(sp.field() + off + 1, NULL, 10); + + } + const char* name_begin = sp.field(); + const char* name_end = sp.field() + off - 1; + for (; isspace(*name_begin) && name_begin < sp.field() + off; + ++name_begin) {} + for (; isspace(*name_end) && name_end >= sp.field(); --name_end) {} + + if (name_begin > name_end) { // only has spaces + continue; + } + std::string name(name_begin, name_end - name_begin + 1); + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + if (name.find_first_of("*?") == std::string::npos) { + _exact_names[name] = verbose_level; + } else { + _wild_names.emplace_back(name, verbose_level); + } + } + // Reverse _wild_names so that latter wild cards override former ones. + if (!_wild_names.empty()) { + std::reverse(_wild_names.begin(), _wild_names.end()); + } + return 0; + } + + bool find_verbose_level(const std::string& module, + const std::string& full_module, int* v) const { + if (!_exact_names.empty()) { + std::map::const_iterator + it = _exact_names.find(module); + if (it != _exact_names.end()) { + *v = it->second; + return true; + } + if (!full_module.empty()) { + it = _exact_names.find(full_module); + if (it != _exact_names.end()) { + *v = it->second; + return true; + } + } + } + + for (size_t i = 0; i < _wild_names.size(); ++i) { + if (wildcmp(_wild_names[i].first.c_str(), module.c_str())) { + *v = _wild_names[i].second; + return true; + } + if (!full_module.empty() && + wildcmp(_wild_names[i].first.c_str(), full_module.c_str())) { + *v = _wild_names[i].second; + return true; + } + } + return false; + } + + void print(std::ostream& os) const { + os << "exact:"; + for (std::map::const_iterator + it = _exact_names.begin(); it != _exact_names.end(); ++it) { + os << ' ' << it->first << '=' << it->second; + } + os << ", wild:"; + for (size_t i = 0; i < _wild_names.size(); ++i) { + os << ' ' << _wild_names[i].first << '=' << _wild_names[i].second; + } + } + +private: + std::map _exact_names; + std::vector > _wild_names; +}; + +// [ The idea ] +// Each callsite creates a VLogSite and inserts the site into singly-linked +// vlog_site_list. To keep the critical area small, we use optimistic +// locking : Assign local site w/o locking, then insert the site into +// global list w/ locking, if local_module_list != global_vmodule_list or +// local_default_v != FLAGS_v, repeat the assigment. +// An important property of vlog_site_list is that: It does not remove sites. +// When we need to iterate the list, we don't have to hold the lock. What we +// do is to get the head of the list inside lock and iterate the list w/o +// lock. If new sites is inserted during the iteration, it should see and +// use the updated vmodule_list and FLAGS_v, nothing will be missed. + +static int vlog_site_list_add(VLogSite* site, + VModuleList** expected_module_list, + int* expected_default_v) { + BAIDU_SCOPED_LOCK(vlog_site_list_mutex); + if (vmodule_list != *expected_module_list) { + *expected_module_list = vmodule_list; + return -1; + } + if (*expected_default_v != FLAGS_v) { + *expected_default_v = FLAGS_v; + return -1; + } + site->set_next(vlog_site_list); + vlog_site_list = site; + return 0; +} + +bool add_vlog_site(const int** v, const char* filename, int line_no, + int required_v) { + VLogSite* site = new (std::nothrow) VLogSite(filename, required_v, line_no); + if (site == NULL) { + return false; + } + VModuleList* module_list = vmodule_list; + int default_v = FLAGS_v; + do { + site->v() = default_v; + if (module_list) { + module_list->find_verbose_level( + site->module(), site->full_module(), &site->v()); + } + } while (vlog_site_list_add(site, &module_list, &default_v) != 0); + *v = &site->v(); + return site->v() >= required_v; +} + +void print_vlog_sites(VLogSitePrinter* printer) { + VLogSite* head = NULL; + { + BAIDU_SCOPED_LOCK(vlog_site_list_mutex); + head = vlog_site_list; + } + VLogSitePrinter::Site site; + for (const VLogSite* p = head; p; p = p->next()) { + site.current_verbose_level = p->v(); + site.required_verbose_level = p->required_v(); + site.line_no = p->line_no(); + site.full_module = p->full_module(); + printer->print(site); + } +} + +// [Thread-safe] Reset FLAGS_vmodule. +static int on_reset_vmodule(const char* vmodule) { + // resetting must be serialized. + BAIDU_SCOPED_LOCK(reset_vmodule_and_v_mutex); + + VModuleList* module_list = new (std::nothrow) VModuleList; + if (NULL == module_list) { + LOG(FATAL) << "Fail to new VModuleList"; + return -1; + } + if (module_list->init(vmodule) != 0) { + delete module_list; + LOG(FATAL) << "Fail to init VModuleList"; + return -1; + } + + VModuleList* old_module_list = NULL; + VLogSite* old_vlog_site_list = NULL; + { + { + BAIDU_SCOPED_LOCK(vlog_site_list_mutex); + old_module_list = vmodule_list; + vmodule_list = module_list; + old_vlog_site_list = vlog_site_list; + } + for (VLogSite* p = old_vlog_site_list; p; p = p->next()) { + p->v() = FLAGS_v; + module_list->find_verbose_level( + p->module(), p->full_module(), &p->v()); + } + } + + if (old_module_list) { + //delay the deletion. + if (NULL == deleting_vmodule_list) { + deleting_vmodule_list = + new std::deque >; + } + deleting_vmodule_list->push_back( + std::make_pair(old_module_list, + butil::gettimeofday_us() + DELAY_DELETION_SEC * 1000000L)); + while (!deleting_vmodule_list->empty() && + deleting_vmodule_list->front().second <= butil::gettimeofday_us()) { + delete deleting_vmodule_list->front().first; + deleting_vmodule_list->pop_front(); + } + } + return 0; +} + +static bool validate_vmodule(const char*, const std::string& vmodule) { + return on_reset_vmodule(vmodule.c_str()) == 0; +} + +const bool ALLOW_UNUSED validate_vmodule_dummy = GFLAGS_NAMESPACE::RegisterFlagValidator( + &FLAGS_vmodule, &validate_vmodule); + +// [Thread-safe] Reset FLAGS_v. +static void on_reset_verbose(int default_v) { + VModuleList* cur_module_list = NULL; + VLogSite* cur_vlog_site_list = NULL; + { + // resetting must be serialized. + BAIDU_SCOPED_LOCK(reset_vmodule_and_v_mutex); + { + BAIDU_SCOPED_LOCK(vlog_site_list_mutex); + cur_module_list = vmodule_list; + cur_vlog_site_list = vlog_site_list; + } + for (VLogSite* p = cur_vlog_site_list; p; p = p->next()) { + p->v() = default_v; + if (cur_module_list) { + cur_module_list->find_verbose_level( + p->module(), p->full_module(), &p->v()); + } + } + } +} + +static bool validate_v(const char*, int32_t v) { + on_reset_verbose(v); + return true; +} +BUTIL_VALIDATE_GFLAG(v, validate_v); + +} // namespace logging + +std::ostream& operator<<(std::ostream& out, const wchar_t* wstr) { + return out << butil::WideToUTF8(std::wstring(wstr)); +} + +#endif // BRPC_WITH_GLOG diff --git a/src/butil/logging.h b/src/butil/logging.h new file mode 100644 index 0000000..d612e86 --- /dev/null +++ b/src/butil/logging.h @@ -0,0 +1,1344 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2012-10-08 23:53:50 + +// Merged chromium log and streaming log. + +#ifndef BUTIL_LOGGING_H_ +#define BUTIL_LOGGING_H_ + +#include "butil/config.h" // BRPC_WITH_GLOG + +#include +#include +#include +#include +#include "butil/macros.h" // BAIDU_CONCAT +#include "butil/atomicops.h" // Used by LOG_EVERY_N, LOG_FIRST_N etc +#include "butil/time.h" // gettimeofday_us() + +#if BRPC_WITH_GLOG +# include +# include +// define macros that not implemented in glog +# ifndef DCHECK_IS_ON // glog didn't define DCHECK_IS_ON in older version +# if defined(NDEBUG) +# define DCHECK_IS_ON() 0 +# else +# define DCHECK_IS_ON() 1 +# endif // NDEBUG +# endif // DCHECK_IS_ON +# if DCHECK_IS_ON() +# define DPLOG(...) PLOG(__VA_ARGS__) +# define DPLOG_IF(...) PLOG_IF(__VA_ARGS__) +# define DPCHECK(...) PCHECK(__VA_ARGS__) +# define DVPLOG(...) VLOG(__VA_ARGS__) +# else +# define DPLOG(...) DLOG(__VA_ARGS__) +# define DPLOG_IF(...) DLOG_IF(__VA_ARGS__) +# define DPCHECK(...) DCHECK(__VA_ARGS__) +# define DVPLOG(...) DVLOG(__VA_ARGS__) +# endif // DCHECK_IS_ON() + +#ifndef LOG_BACKTRACE_IF +#define LOG_BACKTRACE_IF(severity, condition) LOG_IF(severity, condition) +#endif // LOG_BACKTRACE_IF + +#ifndef LOG_BACKTRACE_IF_ONCE +#define LOG_BACKTRACE_IF_ONCE(severity, condition) LOG_IF_ONCE(severity, condition) +#endif // LOG_BACKTRACE_IF_ONCE + +#ifndef LOG_BACKTRACE_FIRST_N +#define LOG_BACKTRACE_FIRST_N(severity, N) LOG_FIRST_N(severity, N) +#endif // LOG_BACKTRACE_FIRST_N + +#ifndef LOG_BACKTRACE_IF_FIRST_N +#define LOG_BACKTRACE_IF_FIRST_N(severity, condition, N) LOG_IF_FIRST_N(severity, condition, N) +#endif // LOG_BACKTRACE_IF_FIRST_N + + +#define LOG_AT(severity, file, line) \ + ::google::LogMessage(file, line, ::google::severity).stream() + +#else + +#ifdef BAIDU_INTERNAL +// gejun: com_log.h includes ul_def.h, undef conflict macros +// FIXME(gejun): We have to include com_log which is assumed to be included +// in other modules right now. +#include +#undef Uchar +#undef Ushort +#undef Uint +#undef Max +#undef Min +#undef Exchange +#endif // BAIDU_INTERNAL + +#include +#include + +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/debug/debugger.h" +#include "butil/strings/string_piece.h" +#include "butil/build_config.h" +#include "butil/synchronization/lock.h" +// +// Optional message capabilities +// ----------------------------- +// Assertion failed messages and fatal errors are displayed in a dialog box +// before the application exits. However, running this UI creates a message +// loop, which causes application messages to be processed and potentially +// dispatched to existing application windows. Since the application is in a +// bad state when this assertion dialog is displayed, these messages may not +// get processed and hang the dialog, or the application might go crazy. +// +// Therefore, it can be beneficial to display the error dialog in a separate +// process from the main application. When the logging system needs to display +// a fatal error dialog box, it will look for a program called +// "DebugMessage.exe" in the same directory as the application executable. It +// will run this application with the message as the command line, and will +// not include the name of the application as is traditional for easier +// parsing. +// +// The code for DebugMessage.exe is only one line. In WinMain, do: +// MessageBox(NULL, GetCommandLineW(), L"Fatal Error", 0); +// +// If DebugMessage.exe is not found, the logging code will use a normal +// MessageBox, potentially causing the problems discussed above. + + +// Instructions +// ------------ +// +// Make a bunch of macros for logging. The way to log things is to stream +// things to LOG(). E.g., +// +// LOG(INFO) << "Found " << num_cookies << " cookies"; +// +// You can also do conditional logging: +// +// LOG_IF(INFO, num_cookies > 10) << "Got lots of cookies"; +// +// The CHECK(condition) macro is active in both debug and release builds and +// effectively performs a LOG(FATAL) which terminates the process and +// generates a crashdump unless a debugger is attached. +// +// There are also "debug mode" logging macros like the ones above: +// +// DLOG(INFO) << "Found cookies"; +// +// DLOG_IF(INFO, num_cookies > 10) << "Got lots of cookies"; +// +// All "debug mode" logging is compiled away to nothing for non-debug mode +// compiles. LOG_IF and development flags also work well together +// because the code can be compiled away sometimes. +// +// We also have +// +// LOG_ASSERT(assertion); +// DLOG_ASSERT(assertion); +// +// which is syntactic sugar for {,D}LOG_IF(FATAL, assert fails) << assertion; +// +// There are "verbose level" logging macros. They look like +// +// VLOG(1) << "I'm printed when you run the program with --v=1 or more"; +// VLOG(2) << "I'm printed when you run the program with --v=2 or more"; +// +// These always log at the INFO log level (when they log at all). +// The verbose logging can also be turned on module-by-module. For instance, +// --vmodule=profile=2,icon_loader=1,browser_*=3,*/chromeos/*=4 --v=0 +// will cause: +// a. VLOG(2) and lower messages to be printed from profile.{h,cc} +// b. VLOG(1) and lower messages to be printed from icon_loader.{h,cc} +// c. VLOG(3) and lower messages to be printed from files prefixed with +// "browser" +// d. VLOG(4) and lower messages to be printed from files under a +// "chromeos" directory. +// e. VLOG(0) and lower messages to be printed from elsewhere +// +// The wildcarding functionality shown by (c) supports both '*' (match +// 0 or more characters) and '?' (match any single character) +// wildcards. Any pattern containing a forward or backward slash will +// be tested against the whole pathname and not just the module. +// E.g., "*/foo/bar/*=2" would change the logging level for all code +// in source files under a "foo/bar" directory. +// +// There's also VLOG_IS_ON(n) "verbose level" condition macro. To be used as +// +// if (VLOG_IS_ON(2)) { +// // do some logging preparation and logging +// // that can't be accomplished with just VLOG(2) << ...; +// } +// +// There is also a VLOG_IF "verbose level" condition macro for sample +// cases, when some extra computation and preparation for logs is not +// needed. +// +// VLOG_IF(1, (size > 1024)) +// << "I'm printed when size is more than 1024 and when you run the " +// "program with --v=1 or more"; +// +// Lastly, there is: +// +// PLOG(ERROR) << "Couldn't do foo"; +// DPLOG(ERROR) << "Couldn't do foo"; +// PLOG_IF(ERROR, cond) << "Couldn't do foo"; +// DPLOG_IF(ERROR, cond) << "Couldn't do foo"; +// PCHECK(condition) << "Couldn't do foo"; +// DPCHECK(condition) << "Couldn't do foo"; +// +// which append the last system error to the message in string form (taken from +// GetLastError() on Windows and errno on POSIX). +// +// The supported severity levels for macros that allow you to specify one +// are (in increasing order of severity) INFO, WARNING, ERROR, and FATAL. +// +// Very important: logging a message at the FATAL severity level causes +// the program to terminate (after the message is logged). +// +// There is the special severity of DFATAL, which logs FATAL in debug mode, +// ERROR in normal mode. + +namespace logging { + +// TODO(avi): do we want to do a unification of character types here? +#if defined(OS_WIN) +typedef wchar_t LogChar; +#else +typedef char LogChar; +#endif + +// Where to record logging output? A flat file and/or system debug log +// via OutputDebugString. +enum LoggingDestination { + LOG_TO_NONE = 0, + LOG_TO_FILE = 1 << 0, + LOG_TO_SYSTEM_DEBUG_LOG = 1 << 1, + + LOG_TO_ALL = LOG_TO_FILE | LOG_TO_SYSTEM_DEBUG_LOG, + + // On Windows, use a file next to the exe; on POSIX platforms, where + // it may not even be possible to locate the executable on disk, use + // stderr. +#if defined(OS_WIN) + LOG_DEFAULT = LOG_TO_FILE, +#elif defined(OS_POSIX) + LOG_DEFAULT = LOG_TO_SYSTEM_DEBUG_LOG, +#endif +}; + +// Indicates that the log file should be locked when being written to. +// Unless there is only one single-threaded process that is logging to +// the log file, the file should be locked during writes to make each +// log output atomic. Other writers will block. +// +// All processes writing to the log file must have their locking set for it to +// work properly. Defaults to LOCK_LOG_FILE. +enum LogLockingState { LOCK_LOG_FILE, DONT_LOCK_LOG_FILE }; + +// On startup, should we delete or append to an existing log file (if any)? +// Defaults to APPEND_TO_OLD_LOG_FILE. +enum OldFileDeletionState { DELETE_OLD_LOG_FILE, APPEND_TO_OLD_LOG_FILE }; + +struct BUTIL_EXPORT LoggingSettings { + // The defaults values are: + // + // logging_dest: LOG_DEFAULT + // log_file: NULL + // lock_log: LOCK_LOG_FILE + // delete_old: APPEND_TO_OLD_LOG_FILE + LoggingSettings(); + + LoggingDestination logging_dest; + + // The three settings below have an effect only when LOG_TO_FILE is + // set in |logging_dest|. + const LogChar* log_file; + LogLockingState lock_log; + OldFileDeletionState delete_old; +}; + +// Implementation of the InitLogging() method declared below. +BUTIL_EXPORT bool BaseInitLoggingImpl(const LoggingSettings& settings); + +// Sets the log file name and other global logging state. Calling this function +// is recommended, and is normally done at the beginning of application init. +// If you don't call it, all the flags will be initialized to their default +// values, and there is a race condition that may leak a critical section +// object if two threads try to do the first log at the same time. +// See the definition of the enums above for descriptions and default values. +// +// The default log file is initialized to ".log" on linux and +// "debug.log" otherwise. +// +// This function may be called a second time to re-direct logging (e.g after +// loging in to a user partition), however it should never be called more than +// twice. +inline bool InitLogging(const LoggingSettings& settings) { + return BaseInitLoggingImpl(settings); +} + +// Sets the log level. Anything at or above this level will be written to the +// log file/displayed to the user (if applicable). Anything below this level +// will be silently ignored. The log level defaults to 0 (everything is logged +// up to level INFO) if this function is not called. +BUTIL_EXPORT void SetMinLogLevel(int level); + +// Gets the current log level. +BUTIL_EXPORT int GetMinLogLevel(); + +// Sets whether or not you'd like to see fatal debug messages popped up in +// a dialog box or not. +// Dialogs are not shown by default. +BUTIL_EXPORT void SetShowErrorDialogs(bool enable_dialogs); + +// Sets the Log Assert Handler that will be used to notify of check failures. +// The default handler shows a dialog box and then terminate the process, +// however clients can use this function to override with their own handling +// (e.g. a silent one for Unit Tests) +typedef void (*LogAssertHandler)(const std::string& str); +BUTIL_EXPORT void SetLogAssertHandler(LogAssertHandler handler); + +class LogSink { +public: + LogSink() {} + virtual ~LogSink() {} + // Called when a log is ready to be written out. + // Returns true to stop further processing. + virtual bool OnLogMessage(int severity, const char* file, int line, + const butil::StringPiece& log_content) = 0; + virtual bool OnLogMessage(int severity, const char* file, + int line, const char* /*func*/, + const butil::StringPiece& log_content) { + return OnLogMessage(severity, file, line, log_content); + } +private: + DISALLOW_COPY_AND_ASSIGN(LogSink); +}; + +// Sets the LogSink that gets passed every log message before +// it's sent to default log destinations. +// This function is thread-safe and waits until current LogSink is not used +// anymore. +// Returns previous sink. +BUTIL_EXPORT LogSink* SetLogSink(LogSink* sink); + +// Print |content| with other info into |os|. +void PrintLog(std::ostream& os, + int severity, const char* file, int line, + const butil::StringPiece& content); + +void PrintLog(std::ostream& os, + int severity, const char* file, int line, + const char* func, const butil::StringPiece& content); + +// The LogSink mainly for unit-testing. Logs will be appended to it. +class StringSink : public LogSink, public std::string { +public: + bool OnLogMessage(int severity, const char* file, int line, + const butil::StringPiece& log_content) override; + + bool OnLogMessage(int severity, const char* file, + int line, const char* func, + const butil::StringPiece& log_content) override; +private: + butil::Lock _lock; +}; + +typedef int LogSeverity; +const LogSeverity BLOG_VERBOSE = -1; // This is level 1 verbosity +// Note: the log severities are used to index into the array of names, +// see log_severity_names. +const LogSeverity BLOG_INFO = 0; +const LogSeverity BLOG_NOTICE = 1; +const LogSeverity BLOG_WARNING = 2; +const LogSeverity BLOG_ERROR = 3; +const LogSeverity BLOG_FATAL = 4; +const int LOG_NUM_SEVERITIES = 5; + +// COMBLOG_TRACE is just INFO +const LogSeverity BLOG_TRACE = BLOG_INFO; + +// COMBLOG_DEBUG equals INFO in debug mode and verbose in normal mode. +#ifndef NDEBUG +const LogSeverity BLOG_DEBUG = BLOG_INFO; +#else +const LogSeverity BLOG_DEBUG = BLOG_VERBOSE; +#endif + +// BLOG_DFATAL is BLOG_FATAL in debug mode, ERROR in normal mode +#ifndef NDEBUG +const LogSeverity BLOG_DFATAL = BLOG_FATAL; +#else +const LogSeverity BLOG_DFATAL = BLOG_ERROR; +#endif + +// A few definitions of macros that don't generate much code. These are used +// by LOG() and LOG_IF, etc. Since these are used all over our code, it's +// better to have compact code for these operations. +#define BAIDU_COMPACT_LOG_EX(severity, ClassName, ...) \ + ::logging::ClassName(__FILE__, __LINE__, __func__, \ + ::logging::BLOG_##severity, ##__VA_ARGS__) + +#define BAIDU_COMPACK_LOG(severity) \ + BAIDU_COMPACT_LOG_EX(severity, LogMessage) + +#if defined(OS_WIN) +// wingdi.h defines ERROR to be 0. When we call LOG(ERROR), it gets +// substituted with 0, and it expands to BAIDU_COMPACK_LOG(0). To allow us +// to keep using this syntax, we define this macro to do the same thing +// as BAIDU_COMPACK_LOG(ERROR), and also define ERROR the same way that +// the Windows SDK does for consistency. +#undef ERROR +#define ERROR 0 +// Needed for LOG_IS_ON(ERROR). +const LogSeverity BLOG_0 = BLOG_ERROR; +#endif + +// As special cases, we can assume that LOG_IS_ON(FATAL) always holds. Also, +// LOG_IS_ON(DFATAL) always holds in debug mode. In particular, CHECK()s will +// always fire if they fail. +#define LOG_IS_ON(severity) \ + (::logging::BLOG_##severity >= ::logging::GetMinLogLevel()) + +#if defined(__GNUC__) +// We emit an anonymous static int* variable at every VLOG_IS_ON(n) site. +// (Normally) the first time every VLOG_IS_ON(n) site is hit, +// we determine what variable will dynamically control logging at this site: +// it's either FLAGS_verbose or an appropriate internal variable +// matching the current source file that represents results of +// parsing of --vmodule flag and/or SetVLOGLevel calls. +# define BAIDU_VLOG_IS_ON(verbose_level, filepath) \ + ({ static const int* vlocal = &::logging::VLOG_UNINITIALIZED; \ + const int saved_verbose_level = (verbose_level); \ + (saved_verbose_level >= 0)/*VLOG(-1) is forbidden*/ && \ + (*vlocal >= saved_verbose_level) && \ + ((vlocal != &::logging::VLOG_UNINITIALIZED) || \ + (::logging::add_vlog_site(&vlocal, filepath, __LINE__, \ + saved_verbose_level))); }) +#else +// GNU extensions not available, so we do not support --vmodule. +// Dynamic value of FLAGS_verbose always controls the logging level. +# define BAIDU_VLOG_IS_ON(verbose_level, filepath) \ + (::logging::FLAGS_v >= (verbose_level)) +#endif + +#define VLOG_IS_ON(verbose_level) BAIDU_VLOG_IS_ON(verbose_level, __FILE__) + +DECLARE_int32(v); + +extern const int VLOG_UNINITIALIZED; + +// Called to initialize a VLOG callsite. +bool add_vlog_site(const int** v, const LogChar* filename, + int line_no, int required_v); + +class VLogSitePrinter { +public: + struct Site { + int current_verbose_level; + int required_verbose_level; + int line_no; + std::string full_module; + }; + + virtual void print(const Site& site) = 0; + virtual ~VLogSitePrinter() = default; +}; + +void print_vlog_sites(VLogSitePrinter*); + +// Helper macro which avoids evaluating the arguments to a stream if +// the condition doesn't hold. +#define BAIDU_LAZY_STREAM(stream, condition) \ + !(condition) ? (void) 0 : ::logging::LogMessageVoidify() & (stream) + +// We use the preprocessor's merging operator, "##", so that, e.g., +// LOG(INFO) becomes the token BAIDU_COMPACK_LOG(INFO). There's some funny +// subtle difference between ostream member streaming functions (e.g., +// ostream::operator<<(int) and ostream non-member streaming functions +// (e.g., ::operator<<(ostream&, string&): it turns out that it's +// impossible to stream something like a string directly to an unnamed +// ostream. We employ a neat hack by calling the stream() member +// function of LogMessage which seems to avoid the problem. +#define LOG_STREAM(severity) BAIDU_COMPACK_LOG(severity).stream() + +#define LOG(severity) \ + BAIDU_LAZY_STREAM(LOG_STREAM(severity), LOG_IS_ON(severity)) +#define LOG_IF(severity, condition) \ + BAIDU_LAZY_STREAM(LOG_STREAM(severity), LOG_IS_ON(severity) && (condition)) +#ifndef LOG_BACKTRACE_IF +#define LOG_BACKTRACE_IF(severity, condition) \ + BAIDU_LAZY_STREAM(LOG_STREAM(severity).SetBacktrace(), LOG_IS_ON(severity) && (condition)) +#endif // LOG_BACKTRACE_IF + +// FIXME(gejun): Should always crash. +#define LOG_ASSERT(condition) \ + LOG_IF(FATAL, !(condition)) << "Assert failed: " #condition ". " + +#define SYSLOG(severity) LOG(severity) +#define SYSLOG_IF(severity, condition) LOG_IF(severity, condition) +#define SYSLOG_EVERY_N(severity, N) LOG_EVERY_N(severity, N) +#define SYSLOG_IF_EVERY_N(severity, condition, N) LOG_IF_EVERY_N(severity, condition, N) +#define SYSLOG_FIRST_N(severity, N) LOG_FIRST_N(severity, N) +#define SYSLOG_IF_FIRST_N(severity, condition, N) LOG_IF_FIRST_N(severity, condition, N) +#define SYSLOG_ONCE(severity) LOG_FIRST_N(severity, 1) +#define SYSLOG_IF_ONCE(severity, condition) LOG_IF_FIRST_N(severity, condition, 1) +#define SYSLOG_EVERY_SECOND(severity) LOG_EVERY_SECOND(severity) +#define SYSLOG_IF_EVERY_SECOND(severity, condition) LOG_IF_EVERY_SECOND(severity, condition) + +#define SYSLOG_ASSERT(condition) \ + SYSLOG_IF(FATAL, !(condition)) << "Assert failed: " #condition ". " + +// file/line can be specified at running-time. This is useful for printing +// logs with known file/line inside a LogSink or LogMessageHandler +#define LOG_AT_SELECTOR(_1, _2, _3, _4, NAME, ...) NAME + +#define LOG_AT_STREAM1(severity, file, line) \ + ::logging::LogMessage(file, line, ::logging::BLOG_##severity).stream() +#define LOG_AT_STREAM2(severity, file, line, func) \ + ::logging::LogMessage(file, line, func, ::logging::BLOG_##severity).stream() +#define LOG_AT_STREAM(...) LOG_AT_SELECTOR(__VA_ARGS__, LOG_AT_STREAM2, LOG_AT_STREAM1)(__VA_ARGS__) + +#define LOG_AT1(severity, file, line) \ + BAIDU_LAZY_STREAM(LOG_AT_STREAM(severity, file, line), LOG_IS_ON(severity)) +#define LOG_AT2(severity, file, line, func) \ + BAIDU_LAZY_STREAM(LOG_AT_STREAM(severity, file, line, func), LOG_IS_ON(severity)) +#define LOG_AT(...) LOG_AT_SELECTOR(__VA_ARGS__, LOG_AT2, LOG_AT1)(__VA_ARGS__) + + +// The VLOG macros log with negative verbosities. +#define VLOG_STREAM(verbose_level) \ + ::logging::LogMessage(__FILE__, __LINE__, __func__, -(verbose_level)).stream() + +#define VLOG(verbose_level) \ + BAIDU_LAZY_STREAM(VLOG_STREAM(verbose_level), VLOG_IS_ON(verbose_level)) +#define VLOG_IF(verbose_level, condition) \ + BAIDU_LAZY_STREAM(VLOG_STREAM(verbose_level), \ + VLOG_IS_ON(verbose_level) && (condition)) + +#define VLOG_EVERY_N(verbose_level, N) \ + BAIDU_LOG_IF_EVERY_N_IMPL(VLOG_IF, verbose_level, true, N) +#define VLOG_IF_EVERY_N(verbose_level, condition, N) \ + BAIDU_LOG_IF_EVERY_N_IMPL(VLOG_IF, verbose_level, condition, N) + +#define VLOG_FIRST_N(verbose_level, N) \ + BAIDU_LOG_IF_FIRST_N_IMPL(VLOG_IF, verbose_level, true, N) +#define VLOG_IF_FIRST_N(verbose_level, condition, N) \ + BAIDU_LOG_IF_FIRST_N_IMPL(VLOG_IF, verbose_level, condition, N) + +#define VLOG_ONCE(verbose_level) VLOG_FIRST_N(verbose_level, 1) +#define VLOG_IF_ONCE(verbose_level, condition) VLOG_IF_FIRST_N(verbose_level, condition, 1) + +#define VLOG_EVERY_SECOND(verbose_level) \ + BAIDU_LOG_IF_EVERY_SECOND_IMPL(VLOG_IF, verbose_level, true) +#define VLOG_IF_EVERY_SECOND(verbose_level, condition) \ + BAIDU_LOG_IF_EVERY_SECOND_IMPL(VLOG_IF, verbose_level, condition) + +#if defined (OS_WIN) +#define VPLOG_STREAM(verbose_level) \ + ::logging::Win32ErrorLogMessage(__FILE__, __LINE__, __func__, -verbose_level, \ + ::logging::GetLastSystemErrorCode()).stream() +#elif defined(OS_POSIX) +#define VPLOG_STREAM(verbose_level) \ + ::logging::ErrnoLogMessage(__FILE__, __LINE__, __func__, -verbose_level, \ + ::logging::GetLastSystemErrorCode()).stream() +#endif + +#define VPLOG(verbose_level) \ + BAIDU_LAZY_STREAM(VPLOG_STREAM(verbose_level), VLOG_IS_ON(verbose_level)) + +#define VPLOG_IF(verbose_level, condition) \ + BAIDU_LAZY_STREAM(VPLOG_STREAM(verbose_level), \ + VLOG_IS_ON(verbose_level) && (condition)) + +#if defined(OS_WIN) +#define PLOG_STREAM(severity) \ + BAIDU_COMPACT_LOG_EX(severity, Win32ErrorLogMessage, \ + ::logging::GetLastSystemErrorCode()).stream() +#elif defined(OS_POSIX) +#define PLOG_STREAM(severity) \ + BAIDU_COMPACT_LOG_EX(severity, ErrnoLogMessage, \ + ::logging::GetLastSystemErrorCode()).stream() +#endif + +#define PLOG(severity) \ + BAIDU_LAZY_STREAM(PLOG_STREAM(severity), LOG_IS_ON(severity)) +#define PLOG_IF(severity, condition) \ + BAIDU_LAZY_STREAM(PLOG_STREAM(severity), LOG_IS_ON(severity) && (condition)) + +// The actual stream used isn't important. +#define BAIDU_EAT_STREAM_PARAMS \ + true ? (void) 0 : ::logging::LogMessageVoidify() & LOG_STREAM(FATAL) + +// CHECK dies with a fatal error if condition is not true. It is *not* +// controlled by NDEBUG, so the check will be executed regardless of +// compilation mode. +// +// We make sure CHECK et al. always evaluates their arguments, as +// doing CHECK(FunctionWithSideEffect()) is a common idiom. + +#if defined(OFFICIAL_BUILD) && defined(NDEBUG) + +// Make all CHECK functions discard their log strings to reduce code +// bloat for official release builds. + +// TODO(akalin): This would be more valuable if there were some way to +// remove BreakDebugger() from the backtrace, perhaps by turning it +// into a macro (like __debugbreak() on Windows). +#define CHECK(condition) \ + !(condition) ? ::butil::debug::BreakDebugger() : BAIDU_EAT_STREAM_PARAMS + +#define PCHECK(condition) CHECK(condition) + +#define BAIDU_CHECK_OP(name, op, val1, val2) CHECK((val1) op (val2)) + +#else + +#define CHECK(condition) \ + BAIDU_LAZY_STREAM(LOG_STREAM(FATAL).SetCheck(), !(condition)) \ + << "Check failed: " #condition ". " + +#define PCHECK(condition) \ + BAIDU_LAZY_STREAM(PLOG_STREAM(FATAL).SetCheck(), !(condition)) \ + << "Check failed: " #condition ". " + +// Helper macro for binary operators. +// Don't use this macro directly in your code, use CHECK_EQ et al below. +// +// TODO(akalin): Rewrite this so that constructs like if (...) +// CHECK_EQ(...) else { ... } work properly. +#define BAIDU_CHECK_OP(name, op, val1, val2) \ + if (std::string* _result = \ + ::logging::Check##name##Impl((val1), (val2), \ + #val1 " " #op " " #val2)) \ + ::logging::LogMessage(__FILE__, __LINE__, __func__, _result).stream().SetCheck() + +#endif + +// Build the error message string. This is separate from the "Impl" +// function template because it is not performance critical and so can +// be out of line, while the "Impl" code should be inline. Caller +// takes ownership of the returned string. +template +std::string* MakeCheckOpString(const t1& v1, const t2& v2, const char* names) { + std::ostringstream ss; + ss << names << " (" << v1 << " vs " << v2 << "). "; + std::string* msg = new std::string(ss.str()); + return msg; +} + +// MSVC doesn't like complex extern templates and DLLs. +#if !defined(COMPILER_MSVC) +// Commonly used instantiations of MakeCheckOpString<>. Explicitly instantiated +// in logging.cc. +extern template BUTIL_EXPORT std::string* MakeCheckOpString( + const int&, const int&, const char* names); +extern template BUTIL_EXPORT +std::string* MakeCheckOpString( + const unsigned long&, const unsigned long&, const char* names); +extern template BUTIL_EXPORT +std::string* MakeCheckOpString( + const unsigned long&, const unsigned int&, const char* names); +extern template BUTIL_EXPORT +std::string* MakeCheckOpString( + const unsigned int&, const unsigned long&, const char* names); +extern template BUTIL_EXPORT +std::string* MakeCheckOpString( + const std::string&, const std::string&, const char* name); +#endif + +// Helper functions for BAIDU_CHECK_OP macro. +// The (int, int) specialization works around the issue that the compiler +// will not instantiate the template version of the function on values of +// unnamed enum type - see comment below. +#define BAIDU_DEFINE_CHECK_OP_IMPL(name, op) \ + template \ + inline std::string* Check##name##Impl(const t1& v1, const t2& v2, \ + const char* names) { \ + if (v1 op v2) return NULL; \ + else return MakeCheckOpString(v1, v2, names); \ + } \ + inline std::string* Check##name##Impl(int v1, int v2, const char* names) { \ + if (v1 op v2) return NULL; \ + else return MakeCheckOpString(v1, v2, names); \ + } +BAIDU_DEFINE_CHECK_OP_IMPL(EQ, ==) +BAIDU_DEFINE_CHECK_OP_IMPL(NE, !=) +BAIDU_DEFINE_CHECK_OP_IMPL(LE, <=) +BAIDU_DEFINE_CHECK_OP_IMPL(LT, < ) +BAIDU_DEFINE_CHECK_OP_IMPL(GE, >=) +BAIDU_DEFINE_CHECK_OP_IMPL(GT, > ) +#undef BAIDU_DEFINE_CHECK_OP_IMPL + +#define CHECK_EQ(val1, val2) BAIDU_CHECK_OP(EQ, ==, val1, val2) +#define CHECK_NE(val1, val2) BAIDU_CHECK_OP(NE, !=, val1, val2) +#define CHECK_LE(val1, val2) BAIDU_CHECK_OP(LE, <=, val1, val2) +#define CHECK_LT(val1, val2) BAIDU_CHECK_OP(LT, < , val1, val2) +#define CHECK_GE(val1, val2) BAIDU_CHECK_OP(GE, >=, val1, val2) +#define CHECK_GT(val1, val2) BAIDU_CHECK_OP(GT, > , val1, val2) + +#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON) +#define DCHECK_IS_ON() 0 +#else +#define DCHECK_IS_ON() 1 +#endif + +#define ENABLE_DLOG DCHECK_IS_ON() + +// Definitions for DLOG et al. + +// Need to be this way because `condition' may contain variables that is only +// defined in debug mode. +#if ENABLE_DLOG +#define DLOG_IS_ON(severity) LOG_IS_ON(severity) +#define DLOG_IF(severity, condition) \ + LOG_IF(severity, ENABLE_DLOG && (condition)) +#define DLOG_ASSERT(condition) LOG_ASSERT(!ENABLE_DLOG || condition) +#define DPLOG_IF(severity, condition) \ + PLOG_IF(severity, ENABLE_DLOG && (condition)) +#define DVLOG_IF(verbose_level, condition) \ + VLOG_IF(verbose_level, ENABLE_DLOG && (condition)) +#define DVPLOG_IF(verbose_level, condition) \ + VPLOG_IF(verbose_level, ENABLE_DLOG && (condition)) +#else // ENABLE_DLOG +#define DLOG_IS_ON(severity) false +#define DLOG_IF(severity, condition) BAIDU_EAT_STREAM_PARAMS +#define DLOG_ASSERT(condition) BAIDU_EAT_STREAM_PARAMS +#define DPLOG_IF(severity, condition) BAIDU_EAT_STREAM_PARAMS +#define DVLOG_IF(verbose_level, condition) BAIDU_EAT_STREAM_PARAMS +#define DVPLOG_IF(verbose_level, condition) BAIDU_EAT_STREAM_PARAMS +#endif // ENABLE_DLOG + +#define DLOG(severity) \ + BAIDU_LAZY_STREAM(LOG_STREAM(severity), DLOG_IS_ON(severity)) +#define DLOG_EVERY_N(severity, N) \ + BAIDU_LOG_IF_EVERY_N_IMPL(DLOG_IF, severity, true, N) +#define DLOG_IF_EVERY_N(severity, condition, N) \ + BAIDU_LOG_IF_EVERY_N_IMPL(DLOG_IF, severity, condition, N) +#define DLOG_FIRST_N(severity, N) \ + BAIDU_LOG_IF_FIRST_N_IMPL(DLOG_IF, severity, true, N) +#define DLOG_IF_FIRST_N(severity, condition, N) \ + BAIDU_LOG_IF_FIRST_N_IMPL(DLOG_IF, severity, condition, N) +#define DLOG_ONCE(severity) DLOG_FIRST_N(severity, 1) +#define DLOG_IF_ONCE(severity, condition) DLOG_IF_FIRST_N(severity, condition, 1) +#define DLOG_EVERY_SECOND(severity) \ + BAIDU_LOG_IF_EVERY_SECOND_IMPL(DLOG_IF, severity, true) +#define DLOG_IF_EVERY_SECOND(severity, condition) \ + BAIDU_LOG_IF_EVERY_SECOND_IMPL(DLOG_IF, severity, condition) + +#define DPLOG(severity) \ + BAIDU_LAZY_STREAM(PLOG_STREAM(severity), DLOG_IS_ON(severity)) +#define DPLOG_EVERY_N(severity, N) \ + BAIDU_LOG_IF_EVERY_N_IMPL(DPLOG_IF, severity, true, N) +#define DPLOG_IF_EVERY_N(severity, condition, N) \ + BAIDU_LOG_IF_EVERY_N_IMPL(DPLOG_IF, severity, condition, N) +#define DPLOG_FIRST_N(severity, N) \ + BAIDU_LOG_IF_FIRST_N_IMPL(DPLOG_IF, severity, true, N) +#define DPLOG_IF_FIRST_N(severity, condition, N) \ + BAIDU_LOG_IF_FIRST_N_IMPL(DPLOG_IF, severity, condition, N) +#define DPLOG_ONCE(severity) DPLOG_FIRST_N(severity, 1) +#define DPLOG_IF_ONCE(severity, condition) DPLOG_IF_FIRST_N(severity, condition, 1) +#define DPLOG_EVERY_SECOND(severity) \ + BAIDU_LOG_IF_EVERY_SECOND_IMPL(DPLOG_IF, severity, true) +#define DPLOG_IF_EVERY_SECOND(severity, condition) \ + BAIDU_LOG_IF_EVERY_SECOND_IMPL(DPLOG_IF, severity, condition) + +#define DVLOG(verbose_level) DVLOG_IF(verbose_level, VLOG_IS_ON(verbose_level)) +#define DVLOG_EVERY_N(verbose_level, N) \ + BAIDU_LOG_IF_EVERY_N_IMPL(DVLOG_IF, verbose_level, true, N) +#define DVLOG_IF_EVERY_N(verbose_level, condition, N) \ + BAIDU_LOG_IF_EVERY_N_IMPL(DVLOG_IF, verbose_level, condition, N) +#define DVLOG_FIRST_N(verbose_level, N) \ + BAIDU_LOG_IF_FIRST_N_IMPL(DVLOG_IF, verbose_level, true, N) +#define DVLOG_IF_FIRST_N(verbose_level, condition, N) \ + BAIDU_LOG_IF_FIRST_N_IMPL(DVLOG_IF, verbose_level, condition, N) +#define DVLOG_ONCE(verbose_level) DVLOG_FIRST_N(verbose_level, 1) +#define DVLOG_IF_ONCE(verbose_level, condition) DVLOG_IF_FIRST_N(verbose_level, condition, 1) +#define DVLOG_EVERY_SECOND(verbose_level) \ + BAIDU_LOG_IF_EVERY_SECOND_IMPL(DVLOG_IF, verbose_level, true) +#define DVLOG_IF_EVERY_SECOND(verbose_level, condition) \ + BAIDU_LOG_IF_EVERY_SECOND_IMPL(DVLOG_IF, verbose_level, condition) + +#define DVPLOG(verbose_level) DVPLOG_IF(verbose_level, VLOG_IS_ON(verbose_level)) +#define DVPLOG_EVERY_N(verbose_level, N) \ + BAIDU_LOG_IF_EVERY_N_IMPL(DVPLOG_IF, verbose_level, true, N) +#define DVPLOG_IF_EVERY_N(verbose_level, condition, N) \ + BAIDU_LOG_IF_EVERY_N_IMPL(DVPLOG_IF, verbose_level, condition, N) +#define DVPLOG_FIRST_N(verbose_level, N) \ + BAIDU_LOG_IF_FIRST_N_IMPL(DVPLOG_IF, verbose_level, true, N) +#define DVPLOG_IF_FIRST_N(verbose_level, condition, N) \ + BAIDU_LOG_IF_FIRST_N_IMPL(DVPLOG_IF, verbose_level, condition, N) +#define DVPLOG_ONCE(verbose_level) DVPLOG_FIRST_N(verbose_level, 1) +#define DVPLOG_IF_ONCE(verbose_level, condition) DVPLOG_IF_FIRST_N(verbose_level, condition, 1) +#define DVPLOG_EVERY_SECOND(verbose_level) \ + BAIDU_LOG_IF_EVERY_SECOND_IMPL(DVPLOG_IF, verbose_level, true) +#define DVPLOG_IF_EVERY_SECOND(verbose_level, condition) \ + BAIDU_LOG_IF_EVERY_SECOND_IMPL(DVPLOG_IF, verbose_level, condition) + +// You can assign virtual path to VLOG instead of physical filename. +// [public/foo/bar.cpp] +// VLOG2("a/b/c", 2) << "being filtered by a/b/c rather than public/foo/bar"; +#define VLOG2(virtual_path, verbose_level) \ + BAIDU_LAZY_STREAM(VLOG_STREAM(verbose_level), \ + BAIDU_VLOG_IS_ON(verbose_level, virtual_path)) + +#define VLOG2_IF(virtual_path, verbose_level, condition) \ + BAIDU_LAZY_STREAM(VLOG_STREAM(verbose_level), \ + BAIDU_VLOG_IS_ON(verbose_level, virtual_path) && (condition)) + +#define DVLOG2(virtual_path, verbose_level) \ + VLOG2_IF(virtual_path, verbose_level, ENABLE_DLOG) + +#define DVLOG2_IF(virtual_path, verbose_level, condition) \ + VLOG2_IF(virtual_path, verbose_level, ENABLE_DLOG && (condition)) + +#define VPLOG2(virtual_path, verbose_level) \ + BAIDU_LAZY_STREAM(VPLOG_STREAM(verbose_level), \ + BAIDU_VLOG_IS_ON(verbose_level, virtual_path)) + +#define VPLOG2_IF(virtual_path, verbose_level, condition) \ + BAIDU_LAZY_STREAM(VPLOG_STREAM(verbose_level), \ + BAIDU_VLOG_IS_ON(verbose_level, virtual_path) && (condition)) + +#define DVPLOG2(virtual_path, verbose_level) \ + VPLOG2_IF(virtual_path, verbose_level, ENABLE_DLOG) + +#define DVPLOG2_IF(virtual_path, verbose_level, condition) \ + VPLOG2_IF(virtual_path, verbose_level, ENABLE_DLOG && (condition)) + +// Definitions for DCHECK et al. + +#if DCHECK_IS_ON() + +const LogSeverity BLOG_DCHECK = BLOG_FATAL; + +#else // DCHECK_IS_ON + +const LogSeverity BLOG_DCHECK = BLOG_INFO; + +#endif // DCHECK_IS_ON + +// DCHECK et al. make sure to reference |condition| regardless of +// whether DCHECKs are enabled; this is so that we don't get unused +// variable warnings if the only use of a variable is in a DCHECK. +// This behavior is different from DLOG_IF et al. + +#define DCHECK(condition) \ + BAIDU_LAZY_STREAM(LOG_STREAM(DCHECK), DCHECK_IS_ON() && !(condition)) \ + << "Check failed: " #condition ". " + +#define DPCHECK(condition) \ + BAIDU_LAZY_STREAM(PLOG_STREAM(DCHECK), DCHECK_IS_ON() && !(condition)) \ + << "Check failed: " #condition ". " + +// Helper macro for binary operators. +// Don't use this macro directly in your code, use DCHECK_EQ et al below. +#define BAIDU_DCHECK_OP(name, op, val1, val2) \ + if (DCHECK_IS_ON()) \ + if (std::string* _result = \ + ::logging::Check##name##Impl((val1), (val2), \ + #val1 " " #op " " #val2)) \ + ::logging::LogMessage( \ + __FILE__, __LINE__, __func__, \ + ::logging::BLOG_DCHECK, \ + _result).stream() + +// Equality/Inequality checks - compare two values, and log a +// BLOG_DCHECK message including the two values when the result is not +// as expected. The values must have operator<<(ostream, ...) +// defined. +// +// You may append to the error message like so: +// DCHECK_NE(1, 2) << ": The world must be ending!"; +// +// We are very careful to ensure that each argument is evaluated exactly +// once, and that anything which is legal to pass as a function argument is +// legal here. In particular, the arguments may be temporary expressions +// which will end up being destroyed at the end of the apparent statement, +// for example: +// DCHECK_EQ(string("abc")[1], 'b'); +// +// WARNING: These may not compile correctly if one of the arguments is a pointer +// and the other is NULL. To work around this, simply static_cast NULL to the +// type of the desired pointer. + +#define DCHECK_EQ(val1, val2) BAIDU_DCHECK_OP(EQ, ==, val1, val2) +#define DCHECK_NE(val1, val2) BAIDU_DCHECK_OP(NE, !=, val1, val2) +#define DCHECK_LE(val1, val2) BAIDU_DCHECK_OP(LE, <=, val1, val2) +#define DCHECK_LT(val1, val2) BAIDU_DCHECK_OP(LT, < , val1, val2) +#define DCHECK_GE(val1, val2) BAIDU_DCHECK_OP(GE, >=, val1, val2) +#define DCHECK_GT(val1, val2) BAIDU_DCHECK_OP(GT, > , val1, val2) + +#if defined(OS_WIN) +typedef unsigned long SystemErrorCode; +#elif defined(OS_POSIX) +typedef int SystemErrorCode; +#endif + +// Alias for ::GetLastError() on Windows and errno on POSIX. Avoids having to +// pull in windows.h just for GetLastError() and DWORD. +BUTIL_EXPORT SystemErrorCode GetLastSystemErrorCode(); +BUTIL_EXPORT void SetLastSystemErrorCode(SystemErrorCode err); +BUTIL_EXPORT std::string SystemErrorCodeToString(SystemErrorCode error_code); + +// Underlying buffer to store logs. Comparing to using std::ostringstream +// directly, this utility exposes more low-level methods so that we avoid +// creation of std::string which allocates memory internally. +class CharArrayStreamBuf : public std::streambuf { +public: + explicit CharArrayStreamBuf() : _data(NULL), _size(0) {} + ~CharArrayStreamBuf() override; + + int overflow(int ch) override; + int sync() override; + void reset(); + +private: + char* _data; + size_t _size; +}; + +// A std::ostream to << objects. +// Have to use private inheritance to arrange initialization order. +class LogStream : virtual private CharArrayStreamBuf, public std::ostream { +friend void DestroyLogStream(LogStream*); +public: + LogStream() + : std::ostream(this), _file("-"), _line(0), _func("-") + , _severity(0) , _noflush(false), _is_check(false), _backtrace(false) { + } + + ~LogStream() { + _noflush = false; + Flush(); + } + + inline LogStream& operator<<(LogStream& (*m)(LogStream&)) { + return m(*this); + } + + inline LogStream& operator<<(std::ostream& (*m)(std::ostream&)) { + m(*(std::ostream*)this); + return *this; + } + + template inline LogStream& operator<<(T const& t) { + *(std::ostream*)this << t; + return *this; + } + + // Reset the log prefix: "I0711 15:14:01.830110 12735 server.cpp:93] " + LogStream& SetPosition(const LogChar* file, int line, LogSeverity); + + // Reset the log prefix: "E0711 15:14:01.830110 12735 server.cpp:752 StartInternal] " + LogStream& SetPosition(const LogChar* file, int line, const LogChar* func, LogSeverity); + + // Make FlushIfNeed() no-op once. + LogStream& DontFlushOnce() { + _noflush = true; + return *this; + } + + LogStream& SetCheck() { + _is_check = true; + return *this; + } + + LogStream& SetBacktrace() { + _backtrace = true; + return *this; + } + + bool empty() const { return pbase() == pptr(); } + + butil::StringPiece content() const + { return butil::StringPiece(pbase(), pptr() - pbase()); } + + std::string content_str() const + { return std::string(pbase(), pptr() - pbase()); } + + const LogChar* file() const { return _file; } + int line() const { return _line; } + const LogChar* func() const { return _func; } + LogSeverity severity() const { return _severity; } + +private: + void FlushWithoutReset(); + + // Flush log into sink(if registered) or stderr. + // NOTE: make this method private to limit the callsites so that the + // stack-frame removal in FlushWithoutReset() is always safe. + inline void Flush() { + const bool res = _noflush; + _noflush = false; + if (!res) { + // Save and restore thread-local error code after Flush(). + const SystemErrorCode err = GetLastSystemErrorCode(); + FlushWithoutReset(); + reset(); + clear(); + SetLastSystemErrorCode(err); + _is_check = false; + _backtrace = false; + } + } + + const LogChar* _file; + int _line; + const LogChar* _func; + LogSeverity _severity; + bool _noflush; + bool _is_check; + bool _backtrace; +}; + +// This class more or less represents a particular log message. You +// create an instance of LogMessage and then stream stuff to it. +// When you finish streaming to it, ~LogMessage is called and the +// full message gets streamed to the appropriate destination if `noflush' +// is not present. +// +// You shouldn't actually use LogMessage's constructor to log things, +// though. You should use the LOG() macro (and variants thereof) +// above. +class BUTIL_EXPORT LogMessage { +public: + // Used for LOG(severity). + LogMessage(const char* file, int line, LogSeverity severity); + LogMessage(const char* file, int line, const char* func, + LogSeverity severity); + + // Used for CHECK_EQ(), etc. Takes ownership of the given string. + // Implied severity = BLOG_FATAL. + LogMessage(const char* file, int line, std::string* result); + LogMessage(const char* file, int line, const char* func, + std::string* result); + + // Used for DCHECK_EQ(), etc. Takes ownership of the given string. + LogMessage(const char* file, int line, LogSeverity severity, + std::string* result); + LogMessage(const char* file, int line, const char* func, + LogSeverity severity, std::string* result); + + ~LogMessage(); + + LogStream& stream() { return *_stream; } + +private: + DISALLOW_COPY_AND_ASSIGN(LogMessage); + + // The real data is inside LogStream which may be cached thread-locally. + LogStream* _stream; +}; + +// A non-macro interface to the log facility; (useful +// when the logging level is not a compile-time constant). +inline void LogAtLevel(int const log_level, const butil::StringPiece &msg) { + LogMessage(__FILE__, __LINE__, __func__, + log_level).stream() << msg; +} + +// This class is used to explicitly ignore values in the conditional +// logging macros. This avoids compiler warnings like "value computed +// is not used" and "statement has no effect". +class LogMessageVoidify { +public: + LogMessageVoidify() { } + // This has to be an operator with a precedence lower than << but + // higher than ?: + void operator&(std::ostream&) { } +}; + +#if defined(OS_WIN) +// Appends a formatted system message of the GetLastError() type. +class BUTIL_EXPORT Win32ErrorLogMessage { +public: + Win32ErrorLogMessage(const char* file, + int line, + LogSeverity severity, + SystemErrorCode err); + + Win32ErrorLogMessage(const char* file, + int line, + const char* func, + LogSeverity severity, + SystemErrorCode err); + + // Appends the error message before destructing the encapsulated class. + ~Win32ErrorLogMessage(); + + LogStream& stream() { return log_message_.stream(); } + +private: + SystemErrorCode err_; + LogMessage log_message_; + + DISALLOW_COPY_AND_ASSIGN(Win32ErrorLogMessage); +}; +#elif defined(OS_POSIX) +// Appends a formatted system message of the errno type +class BUTIL_EXPORT ErrnoLogMessage { +public: + ErrnoLogMessage(const char* file, + int line, + LogSeverity severity, + SystemErrorCode err); + + ErrnoLogMessage(const char* file, + int line, + const char* func, + LogSeverity severity, + SystemErrorCode err); + + // Appends the error message before destructing the encapsulated class. + ~ErrnoLogMessage(); + + LogStream& stream() { return log_message_.stream(); } + +private: + SystemErrorCode err_; + LogMessage log_message_; + + DISALLOW_COPY_AND_ASSIGN(ErrnoLogMessage); +}; +#endif // OS_WIN + +// Closes the log file explicitly if open. +// NOTE: Since the log file is opened as necessary by the action of logging +// statements, there's no guarantee that it will stay closed +// after this call. +BUTIL_EXPORT void CloseLogFile(); + +// Async signal safe logging mechanism. +BUTIL_EXPORT void RawLog(int level, const char* message); + +#define RAW_LOG(level, message) \ + ::logging::RawLog(::logging::BLOG_##level, message) + +#define RAW_CHECK(condition, message) \ + do { \ + if (!(condition)) \ + ::logging::RawLog(::logging::BLOG_FATAL, "Check failed: " #condition "\n"); \ + } while (0) + +#if defined(OS_WIN) +// Returns the default log file path. +BUTIL_EXPORT std::wstring GetLogFileFullPath(); +#endif + +inline LogStream& noflush(LogStream& ls) { + ls.DontFlushOnce(); + return ls; +} + +} // namespace logging + +using ::logging::noflush; +using ::logging::VLogSitePrinter; +using ::logging::print_vlog_sites; + +// These functions are provided as a convenience for logging, which is where we +// use streams (it is against Google style to use streams in other places). It +// is designed to allow you to emit non-ASCII Unicode strings to the log file, +// which is normally ASCII. It is relatively slow, so try not to use it for +// common cases. Non-ASCII characters will be converted to UTF-8 by these +// operators. +BUTIL_EXPORT std::ostream& operator<<(std::ostream& out, const wchar_t* wstr); +inline std::ostream& operator<<(std::ostream& out, const std::wstring& wstr) { + return out << wstr.c_str(); +} + +// The NOTIMPLEMENTED() macro annotates codepaths which have +// not been implemented yet. +// +// The implementation of this macro is controlled by NOTIMPLEMENTED_POLICY: +// 0 -- Do nothing (stripped by compiler) +// 1 -- Warn at compile time +// 2 -- Fail at compile time +// 3 -- Fail at runtime (DCHECK) +// 4 -- [default] LOG(ERROR) at runtime +// 5 -- LOG(ERROR) at runtime, only once per call-site + +#endif // BRPC_WITH_GLOG + +#ifndef NOTIMPLEMENTED_POLICY +#if defined(OS_ANDROID) && defined(OFFICIAL_BUILD) +#define NOTIMPLEMENTED_POLICY 0 +#else +// Select default policy: LOG(ERROR) +#define NOTIMPLEMENTED_POLICY 4 +#endif +#endif // NOTIMPLEMENTED_POLICY + +#if defined(COMPILER_GCC) +// On Linux, with GCC, we can use __PRETTY_FUNCTION__ to get the demangled name +// of the current function in the NOTIMPLEMENTED message. +#define NOTIMPLEMENTED_MSG "Not implemented reached in " << __PRETTY_FUNCTION__ +#else +#define NOTIMPLEMENTED_MSG "NOT IMPLEMENTED" +#endif + +#if NOTIMPLEMENTED_POLICY == 0 +#define NOTIMPLEMENTED() BAIDU_EAT_STREAM_PARAMS +#elif NOTIMPLEMENTED_POLICY == 1 +// TODO, figure out how to generate a warning +#define NOTIMPLEMENTED() COMPILE_ASSERT(false, NOT_IMPLEMENTED) +#elif NOTIMPLEMENTED_POLICY == 2 +#define NOTIMPLEMENTED() COMPILE_ASSERT(false, NOT_IMPLEMENTED) +#elif NOTIMPLEMENTED_POLICY == 3 +#define NOTIMPLEMENTED() NOTREACHED() +#elif NOTIMPLEMENTED_POLICY == 4 +#define NOTIMPLEMENTED() LOG(ERROR) << NOTIMPLEMENTED_MSG +#elif NOTIMPLEMENTED_POLICY == 5 +#define NOTIMPLEMENTED() do { \ + static bool logged_once = false; \ + LOG_IF(ERROR, !logged_once) << NOTIMPLEMENTED_MSG; \ + logged_once = true; \ + } while(0); \ + BAIDU_EAT_STREAM_PARAMS +#endif + +#if defined(NDEBUG) && defined(OS_CHROMEOS) +#define NOTREACHED() LOG(ERROR) << "NOTREACHED() hit in " \ + << __FUNCTION__ << ". " +#else +#define NOTREACHED() DCHECK(false) +#endif + +// Helper macro included by all *_EVERY_N macros. +#define BAIDU_LOG_IF_EVERY_N_IMPL(logifmacro, severity, condition, N) \ + static ::butil::subtle::Atomic32 BAIDU_CONCAT(logeveryn_, __LINE__) = -1; \ + const static int BAIDU_CONCAT(logeveryn_sc_, __LINE__) = (N); \ + const int BAIDU_CONCAT(logeveryn_c_, __LINE__) = \ + ::butil::subtle::NoBarrier_AtomicIncrement(&BAIDU_CONCAT(logeveryn_, __LINE__), 1); \ + logifmacro(severity, (condition) && BAIDU_CONCAT(logeveryn_c_, __LINE__) / \ + BAIDU_CONCAT(logeveryn_sc_, __LINE__) * BAIDU_CONCAT(logeveryn_sc_, __LINE__) \ + == BAIDU_CONCAT(logeveryn_c_, __LINE__)) + +// Helper macro included by all *_FIRST_N macros. +#define BAIDU_LOG_IF_FIRST_N_IMPL(logifmacro, severity, condition, N) \ + static ::butil::subtle::Atomic32 BAIDU_CONCAT(logfstn_, __LINE__) = 0; \ + logifmacro(severity, (condition) && BAIDU_CONCAT(logfstn_, __LINE__) < N && \ + ::butil::subtle::NoBarrier_AtomicIncrement(&BAIDU_CONCAT(logfstn_, __LINE__), 1) <= N) + +// Helper macro included by all *_EVERY_SECOND macros. +#define BAIDU_LOG_IF_EVERY_SECOND_IMPL(logifmacro, severity, condition) \ + static ::butil::subtle::Atomic64 BAIDU_CONCAT(logeverys_, __LINE__) = 0; \ + const int64_t BAIDU_CONCAT(logeverys_ts_, __LINE__) = ::butil::gettimeofday_us(); \ + const int64_t BAIDU_CONCAT(logeverys_seen_, __LINE__) = BAIDU_CONCAT(logeverys_, __LINE__); \ + logifmacro(severity, (condition) && BAIDU_CONCAT(logeverys_ts_, __LINE__) >= \ + (BAIDU_CONCAT(logeverys_seen_, __LINE__) + 1000000L) && \ + ::butil::subtle::NoBarrier_CompareAndSwap( \ + &BAIDU_CONCAT(logeverys_, __LINE__), \ + BAIDU_CONCAT(logeverys_seen_, __LINE__), \ + BAIDU_CONCAT(logeverys_ts_, __LINE__)) \ + == BAIDU_CONCAT(logeverys_seen_, __LINE__)) + +// =============================================================== + +// Print a log for at most once. (not present in glog) +// Almost zero overhead when the log was printed. +#ifndef LOG_ONCE +# define LOG_ONCE(severity) LOG_FIRST_N(severity, 1) +# define LOG_BACKTRACE_ONCE(severity) LOG_BACKTRACE_FIRST_N(severity, 1) +# define LOG_IF_ONCE(severity, condition) LOG_IF_FIRST_N(severity, condition, 1) +#ifndef LOG_BACKTRACE_IF_ONCE +# define LOG_BACKTRACE_IF_ONCE(severity, condition) \ + LOG_BACKTRACE_IF_FIRST_N(severity, condition, 1) +#endif // LOG_BACKTRACE_IF_ONCE +#endif // LOG_ONCE + +// Print a log after every N calls. First call always prints. +// Each call to this macro has a cost of relaxed atomic increment. +// The corresponding macro in glog is not thread-safe while this is. +#ifndef LOG_EVERY_N +# define LOG_EVERY_N(severity, N) \ + BAIDU_LOG_IF_EVERY_N_IMPL(LOG_IF, severity, true, N) +# define LOG_IF_EVERY_N(severity, condition, N) \ + BAIDU_LOG_IF_EVERY_N_IMPL(LOG_IF, severity, condition, N) +#endif // LOG_EVERY_N + +// Print logs for first N calls. +// Almost zero overhead when the log was printed for N times +// The corresponding macro in glog is not thread-safe while this is. +#ifndef LOG_FIRST_N +# define LOG_FIRST_N(severity, N) \ + BAIDU_LOG_IF_FIRST_N_IMPL(LOG_IF, severity, true, N) +#ifndef LOG_BACKTRACE_FIRST_N +# define LOG_BACKTRACE_FIRST_N(severity, N) \ + BAIDU_LOG_IF_FIRST_N_IMPL(LOG_BACKTRACE_IF, severity, true, N) +#endif // LOG_BACKTRACE_FIRST_N +# define LOG_IF_FIRST_N(severity, condition, N) \ + BAIDU_LOG_IF_FIRST_N_IMPL(LOG_IF, severity, condition, N) +#ifndef LOG_BACKTRACE_IF_FIRST_N +# define LOG_BACKTRACE_IF_FIRST_N(severity, condition, N) \ + BAIDU_LOG_IF_FIRST_N_IMPL(LOG_BACKTRACE_IF, severity, condition, N) +#endif // LOG_BACKTRACE_IF_FIRST_N +#endif // LOG_FIRST_N + +// Print a log every second. (not present in glog). First call always prints. +// Each call to this macro has a cost of calling gettimeofday. +#ifndef LOG_EVERY_SECOND +# define LOG_EVERY_SECOND(severity) \ + BAIDU_LOG_IF_EVERY_SECOND_IMPL(LOG_IF, severity, true) +# define LOG_IF_EVERY_SECOND(severity, condition) \ + BAIDU_LOG_IF_EVERY_SECOND_IMPL(LOG_IF, severity, condition) +#endif // LOG_EVERY_SECOND + +#ifndef PLOG_EVERY_N +# define PLOG_EVERY_N(severity, N) \ + BAIDU_LOG_IF_EVERY_N_IMPL(PLOG_IF, severity, true, N) +# define PLOG_IF_EVERY_N(severity, condition, N) \ + BAIDU_LOG_IF_EVERY_N_IMPL(PLOG_IF, severity, condition, N) +#endif // PLOG_EVERY_N + +#ifndef PLOG_FIRST_N +# define PLOG_FIRST_N(severity, N) \ + BAIDU_LOG_IF_FIRST_N_IMPL(PLOG_IF, severity, true, N) +# define PLOG_IF_FIRST_N(severity, condition, N) \ + BAIDU_LOG_IF_FIRST_N_IMPL(PLOG_IF, severity, condition, N) +#endif // PLOG_FIRST_N + +#ifndef PLOG_ONCE +# define PLOG_ONCE(severity) PLOG_FIRST_N(severity, 1) +# define PLOG_IF_ONCE(severity, condition) PLOG_IF_FIRST_N(severity, condition, 1) +#endif // PLOG_ONCE + +#ifndef PLOG_EVERY_SECOND +# define PLOG_EVERY_SECOND(severity) \ + BAIDU_LOG_IF_EVERY_SECOND_IMPL(PLOG_IF, severity, true) +# define PLOG_IF_EVERY_SECOND(severity, condition) \ + BAIDU_LOG_IF_EVERY_SECOND_IMPL(PLOG_IF, severity, condition) +#endif // PLOG_EVERY_SECOND + +// DEBUG_MODE is for uses like +// if (DEBUG_MODE) foo.CheckThatFoo(); +// instead of +// #ifndef NDEBUG +// foo.CheckThatFoo(); +// #endif +// +// We tie its state to ENABLE_DLOG. +enum { DEBUG_MODE = DCHECK_IS_ON() }; + + +#endif // BUTIL_LOGGING_H_ diff --git a/src/butil/mac/bundle_locations.h b/src/butil/mac/bundle_locations.h new file mode 100644 index 0000000..0c425f3 --- /dev/null +++ b/src/butil/mac/bundle_locations.h @@ -0,0 +1,67 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_MAC_BUNDLE_LOCATIONS_H_ +#define BUTIL_MAC_BUNDLE_LOCATIONS_H_ + +#include "butil/base_export.h" +#include "butil/files/file_path.h" + +#if defined(__OBJC__) +#import +#else // __OBJC__ +class NSBundle; +class NSString; +#endif // __OBJC__ + +namespace butil { + +class FilePath; + +namespace mac { + +// This file provides several functions to explicitly request the various +// component bundles of Chrome. Please use these methods rather than calling +// +[NSBundle mainBundle] or CFBundleGetMainBundle(). +// +// Terminology +// - "Outer Bundle" - This is the main bundle for Chrome; it's what +// +[NSBundle mainBundle] returns when Chrome is launched normally. +// +// - "Main Bundle" - This is the bundle from which Chrome was launched. +// This will be the same as the outer bundle except when Chrome is launched +// via an app shortcut, in which case this will return the app shortcut's +// bundle rather than the main Chrome bundle. +// +// - "Framework Bundle" - This is the bundle corresponding to the Chrome +// framework. +// +// Guidelines for use: +// - To access a resource, the Framework bundle should be used. +// - If the choice is between the Outer or Main bundles then please choose +// carefully. Most often the Outer bundle will be the right choice, but for +// cases such as adding an app to the "launch on startup" list, the Main +// bundle is probably the one to use. + +// Methods for retrieving the various bundles. +BUTIL_EXPORT NSBundle* MainBundle(); +BUTIL_EXPORT FilePath MainBundlePath(); +BUTIL_EXPORT NSBundle* OuterBundle(); +BUTIL_EXPORT FilePath OuterBundlePath(); +BUTIL_EXPORT NSBundle* FrameworkBundle(); +BUTIL_EXPORT FilePath FrameworkBundlePath(); + +// Set the bundle that the preceding functions will return, overriding the +// default values. Restore the default by passing in |nil|. +BUTIL_EXPORT void SetOverrideOuterBundle(NSBundle* bundle); +BUTIL_EXPORT void SetOverrideFrameworkBundle(NSBundle* bundle); + +// Same as above but accepting a FilePath argument. +BUTIL_EXPORT void SetOverrideOuterBundlePath(const FilePath& file_path); +BUTIL_EXPORT void SetOverrideFrameworkBundlePath(const FilePath& file_path); + +} // namespace mac +} // namespace butil + +#endif // BUTIL_MAC_BUNDLE_LOCATIONS_H_ diff --git a/src/butil/mac/bundle_locations.mm b/src/butil/mac/bundle_locations.mm new file mode 100644 index 0000000..ad8be3a --- /dev/null +++ b/src/butil/mac/bundle_locations.mm @@ -0,0 +1,83 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/mac/bundle_locations.h" + +#include "butil/logging.h" +#include "butil/mac/foundation_util.h" +#include "butil/strings/sys_string_conversions.h" + +namespace butil { +namespace mac { + +// NSBundle isn't threadsafe, all functions in this file must be called on the +// main thread. +static NSBundle* g_override_framework_bundle = nil; +static NSBundle* g_override_outer_bundle = nil; + +NSBundle* MainBundle() { + return [NSBundle mainBundle]; +} + +FilePath MainBundlePath() { + NSBundle* bundle = MainBundle(); + return NSStringToFilePath([bundle bundlePath]); +} + +NSBundle* OuterBundle() { + if (g_override_outer_bundle) + return g_override_outer_bundle; + return [NSBundle mainBundle]; +} + +FilePath OuterBundlePath() { + NSBundle* bundle = OuterBundle(); + return NSStringToFilePath([bundle bundlePath]); +} + +NSBundle* FrameworkBundle() { + if (g_override_framework_bundle) + return g_override_framework_bundle; + return [NSBundle mainBundle]; +} + +FilePath FrameworkBundlePath() { + NSBundle* bundle = FrameworkBundle(); + return NSStringToFilePath([bundle bundlePath]); +} + +static void AssignOverrideBundle(NSBundle* new_bundle, + NSBundle** override_bundle) { + if (new_bundle != *override_bundle) { + [*override_bundle release]; + *override_bundle = [new_bundle retain]; + } +} + +static void AssignOverridePath(const FilePath& file_path, + NSBundle** override_bundle) { + NSString* path = butil::SysUTF8ToNSString(file_path.value()); + NSBundle* new_bundle = [NSBundle bundleWithPath:path]; + DCHECK(new_bundle) << "Failed to load the bundle at " << file_path.value(); + AssignOverrideBundle(new_bundle, override_bundle); +} + +void SetOverrideOuterBundle(NSBundle* bundle) { + AssignOverrideBundle(bundle, &g_override_outer_bundle); +} + +void SetOverrideFrameworkBundle(NSBundle* bundle) { + AssignOverrideBundle(bundle, &g_override_framework_bundle); +} + +void SetOverrideOuterBundlePath(const FilePath& file_path) { + AssignOverridePath(file_path, &g_override_outer_bundle); +} + +void SetOverrideFrameworkBundlePath(const FilePath& file_path) { + AssignOverridePath(file_path, &g_override_framework_bundle); +} + +} // namespace mac +} // namespace butil diff --git a/src/butil/mac/foundation_util.h b/src/butil/mac/foundation_util.h new file mode 100644 index 0000000..12b8e66 --- /dev/null +++ b/src/butil/mac/foundation_util.h @@ -0,0 +1,376 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_MAC_FOUNDATION_UTIL_H_ +#define BUTIL_MAC_FOUNDATION_UTIL_H_ + +#include + +#include +#include + +#include "butil/base_export.h" +#include "butil/logging.h" +#include "butil/mac/scoped_cftyperef.h" + +#if defined(__OBJC__) +#import +@class NSFont; +@class UIFont; +#else // __OBJC__ +#include +class NSBundle; +class NSFont; +class NSString; +class UIFont; +#endif // __OBJC__ + +#if defined(OS_IOS) +#include +#else +#include +#endif + +// Adapted from NSObjCRuntime.h NS_ENUM definition (used in Foundation starting +// with the OS X 10.8 SDK and the iOS 6.0 SDK). +#if __has_extension(cxx_strong_enums) && \ + (defined(OS_IOS) || (defined(MAC_OS_X_VERSION_10_8) && \ + MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8)) +#define CR_FORWARD_ENUM(_type, _name) enum _name : _type +#else +#define CR_FORWARD_ENUM(_type, _name) typedef _type _name +#endif + +// Adapted from NSPathUtilities.h and NSObjCRuntime.h. +#if __LP64__ || NS_BUILD_32_LIKE_64 +CR_FORWARD_ENUM(unsigned long, NSSearchPathDirectory); +typedef unsigned long NSSearchPathDomainMask; +#else +CR_FORWARD_ENUM(unsigned int, NSSearchPathDirectory); +typedef unsigned int NSSearchPathDomainMask; +#endif + +namespace butil { + +class FilePath; + +namespace mac { + +// Returns true if the application is running from a bundle +BUTIL_EXPORT bool AmIBundled(); +BUTIL_EXPORT void SetOverrideAmIBundled(bool value); + +// Returns true if this process is marked as a "Background only process". +BUTIL_EXPORT bool IsBackgroundOnlyProcess(); + +// Returns the path to a resource within the framework bundle. +BUTIL_EXPORT FilePath PathForFrameworkBundleResource(CFStringRef resourceName); + +// Returns the creator code associated with the CFBundleRef at bundle. +OSType CreatorCodeForCFBundleRef(CFBundleRef bundle); + +// Returns the creator code associated with this application, by calling +// CreatorCodeForCFBundleRef for the application's main bundle. If this +// information cannot be determined, returns kUnknownType ('????'). This +// does not respect the override app bundle because it's based on CFBundle +// instead of NSBundle, and because callers probably don't want the override +// app bundle's creator code anyway. +BUTIL_EXPORT OSType CreatorCodeForApplication(); + +// Searches for directories for the given key in only the given |domain_mask|. +// If found, fills result (which must always be non-NULL) with the +// first found directory and returns true. Otherwise, returns false. +BUTIL_EXPORT bool GetSearchPathDirectory(NSSearchPathDirectory directory, + NSSearchPathDomainMask domain_mask, + FilePath* result); + +// Searches for directories for the given key in only the local domain. +// If found, fills result (which must always be non-NULL) with the +// first found directory and returns true. Otherwise, returns false. +BUTIL_EXPORT bool GetLocalDirectory(NSSearchPathDirectory directory, + FilePath* result); + +// Searches for directories for the given key in only the user domain. +// If found, fills result (which must always be non-NULL) with the +// first found directory and returns true. Otherwise, returns false. +BUTIL_EXPORT bool GetUserDirectory(NSSearchPathDirectory directory, + FilePath* result); + +// Returns the ~/Library directory. +BUTIL_EXPORT FilePath GetUserLibraryPath(); + +// Takes a path to an (executable) binary and tries to provide the path to an +// application bundle containing it. It takes the outermost bundle that it can +// find (so for "/Foo/Bar.app/.../Baz.app/..." it produces "/Foo/Bar.app"). +// |exec_name| - path to the binary +// returns - path to the application bundle, or empty on error +BUTIL_EXPORT FilePath GetAppBundlePath(const FilePath& exec_name); + +#define TYPE_NAME_FOR_CF_TYPE_DECL(TypeCF) \ +BUTIL_EXPORT std::string TypeNameForCFType(TypeCF##Ref); + +TYPE_NAME_FOR_CF_TYPE_DECL(CFArray); +TYPE_NAME_FOR_CF_TYPE_DECL(CFBag); +TYPE_NAME_FOR_CF_TYPE_DECL(CFBoolean); +TYPE_NAME_FOR_CF_TYPE_DECL(CFData); +TYPE_NAME_FOR_CF_TYPE_DECL(CFDate); +TYPE_NAME_FOR_CF_TYPE_DECL(CFDictionary); +TYPE_NAME_FOR_CF_TYPE_DECL(CFNull); +TYPE_NAME_FOR_CF_TYPE_DECL(CFNumber); +TYPE_NAME_FOR_CF_TYPE_DECL(CFSet); +TYPE_NAME_FOR_CF_TYPE_DECL(CFString); +TYPE_NAME_FOR_CF_TYPE_DECL(CFURL); +TYPE_NAME_FOR_CF_TYPE_DECL(CFUUID); + +TYPE_NAME_FOR_CF_TYPE_DECL(CGColor); + +TYPE_NAME_FOR_CF_TYPE_DECL(CTFont); +TYPE_NAME_FOR_CF_TYPE_DECL(CTRun); + +#undef TYPE_NAME_FOR_CF_TYPE_DECL + +// Retain/release calls for memory management in C++. +BUTIL_EXPORT void NSObjectRetain(void* obj); +BUTIL_EXPORT void NSObjectRelease(void* obj); + +// CFTypeRefToNSObjectAutorelease transfers ownership of a Core Foundation +// object (one derived from CFTypeRef) to the Foundation memory management +// system. In a traditional managed-memory environment, cf_object is +// autoreleased and returned as an NSObject. In a garbage-collected +// environment, cf_object is marked as eligible for garbage collection. +// +// This function should only be used to convert a concrete CFTypeRef type to +// its equivalent "toll-free bridged" NSObject subclass, for example, +// converting a CFStringRef to NSString. +// +// By calling this function, callers relinquish any ownership claim to +// cf_object. In a managed-memory environment, the object's ownership will be +// managed by the innermost NSAutoreleasePool, so after this function returns, +// callers should not assume that cf_object is valid any longer than the +// returned NSObject. +// +// Returns an id, typed here for C++'s sake as a void*. +BUTIL_EXPORT void* CFTypeRefToNSObjectAutorelease(CFTypeRef cf_object); + +// Returns the base bundle ID, which can be set by SetBaseBundleID but +// defaults to a reasonable string. This never returns NULL. BaseBundleID +// returns a pointer to static storage that must not be freed. +BUTIL_EXPORT const char* BaseBundleID(); + +// Sets the base bundle ID to override the default. The implementation will +// make its own copy of new_base_bundle_id. +BUTIL_EXPORT void SetBaseBundleID(const char* new_base_bundle_id); + +} // namespace mac +} // namespace butil + +#if !defined(__OBJC__) +#define OBJC_CPP_CLASS_DECL(x) class x; +#else // __OBJC__ +#define OBJC_CPP_CLASS_DECL(x) +#endif // __OBJC__ + +// Convert toll-free bridged CFTypes to NSTypes and vice-versa. This does not +// autorelease |cf_val|. This is useful for the case where there is a CFType in +// a call that expects an NSType and the compiler is complaining about const +// casting problems. +// The calls are used like this: +// NSString *foo = CFToNSCast(CFSTR("Hello")); +// CFStringRef foo2 = NSToCFCast(@"Hello"); +// The macro magic below is to enforce safe casting. It could possibly have +// been done using template function specialization, but template function +// specialization doesn't always work intuitively, +// (http://www.gotw.ca/publications/mill17.htm) so the trusty combination +// of macros and function overloading is used instead. + +#define CF_TO_NS_CAST_DECL(TypeCF, TypeNS) \ +OBJC_CPP_CLASS_DECL(TypeNS) \ +\ +namespace butil { \ +namespace mac { \ +BUTIL_EXPORT TypeNS* CFToNSCast(TypeCF##Ref cf_val); \ +BUTIL_EXPORT TypeCF##Ref NSToCFCast(TypeNS* ns_val); \ +} \ +} + +#define CF_TO_NS_MUTABLE_CAST_DECL(name) \ +CF_TO_NS_CAST_DECL(CF##name, NS##name) \ +OBJC_CPP_CLASS_DECL(NSMutable##name) \ +\ +namespace butil { \ +namespace mac { \ +BUTIL_EXPORT NSMutable##name* CFToNSCast(CFMutable##name##Ref cf_val); \ +BUTIL_EXPORT CFMutable##name##Ref NSToCFCast(NSMutable##name* ns_val); \ +} \ +} + +// List of toll-free bridged types taken from: +// http://www.cocoadev.com/index.pl?TollFreeBridged + +CF_TO_NS_MUTABLE_CAST_DECL(Array); +CF_TO_NS_MUTABLE_CAST_DECL(AttributedString); +CF_TO_NS_CAST_DECL(CFCalendar, NSCalendar); +CF_TO_NS_MUTABLE_CAST_DECL(CharacterSet); +CF_TO_NS_MUTABLE_CAST_DECL(Data); +CF_TO_NS_CAST_DECL(CFDate, NSDate); +CF_TO_NS_MUTABLE_CAST_DECL(Dictionary); +CF_TO_NS_CAST_DECL(CFError, NSError); +CF_TO_NS_CAST_DECL(CFLocale, NSLocale); +CF_TO_NS_CAST_DECL(CFNumber, NSNumber); +CF_TO_NS_CAST_DECL(CFRunLoopTimer, NSTimer); +CF_TO_NS_CAST_DECL(CFTimeZone, NSTimeZone); +CF_TO_NS_MUTABLE_CAST_DECL(Set); +CF_TO_NS_CAST_DECL(CFReadStream, NSInputStream); +CF_TO_NS_CAST_DECL(CFWriteStream, NSOutputStream); +CF_TO_NS_MUTABLE_CAST_DECL(String); +CF_TO_NS_CAST_DECL(CFURL, NSURL); + +#if defined(OS_IOS) +CF_TO_NS_CAST_DECL(CTFont, UIFont); +#else +CF_TO_NS_CAST_DECL(CTFont, NSFont); +#endif + +#undef CF_TO_NS_CAST_DECL +#undef CF_TO_NS_MUTABLE_CAST_DECL +#undef OBJC_CPP_CLASS_DECL + +namespace butil { +namespace mac { + +// CFCast<>() and CFCastStrict<>() cast a basic CFTypeRef to a more +// specific CoreFoundation type. The compatibility of the passed +// object is found by comparing its opaque type against the +// requested type identifier. If the supplied object is not +// compatible with the requested return type, CFCast<>() returns +// NULL and CFCastStrict<>() will DCHECK. Providing a NULL pointer +// to either variant results in NULL being returned without +// triggering any DCHECK. +// +// Example usage: +// CFNumberRef some_number = butil::mac::CFCast( +// CFArrayGetValueAtIndex(array, index)); +// +// CFTypeRef hello = CFSTR("hello world"); +// CFStringRef some_string = butil::mac::CFCastStrict(hello); + +template +T CFCast(const CFTypeRef& cf_val); + +template +T CFCastStrict(const CFTypeRef& cf_val); + +#define CF_CAST_DECL(TypeCF) \ +template<> BUTIL_EXPORT TypeCF##Ref \ +CFCast(const CFTypeRef& cf_val);\ +\ +template<> BUTIL_EXPORT TypeCF##Ref \ +CFCastStrict(const CFTypeRef& cf_val); + +CF_CAST_DECL(CFArray); +CF_CAST_DECL(CFBag); +CF_CAST_DECL(CFBoolean); +CF_CAST_DECL(CFData); +CF_CAST_DECL(CFDate); +CF_CAST_DECL(CFDictionary); +CF_CAST_DECL(CFNull); +CF_CAST_DECL(CFNumber); +CF_CAST_DECL(CFSet); +CF_CAST_DECL(CFString); +CF_CAST_DECL(CFURL); +CF_CAST_DECL(CFUUID); + +CF_CAST_DECL(CGColor); + +CF_CAST_DECL(CTFont); +CF_CAST_DECL(CTRun); + +CF_CAST_DECL(SecACL); +CF_CAST_DECL(SecTrustedApplication); + +#undef CF_CAST_DECL + +#if defined(__OBJC__) + +// ObjCCast<>() and ObjCCastStrict<>() cast a basic id to a more +// specific (NSObject-derived) type. The compatibility of the passed +// object is found by checking if it's a kind of the requested type +// identifier. If the supplied object is not compatible with the +// requested return type, ObjCCast<>() returns nil and +// ObjCCastStrict<>() will DCHECK. Providing a nil pointer to either +// variant results in nil being returned without triggering any DCHECK. +// +// The strict variant is useful when retrieving a value from a +// collection which only has values of a specific type, e.g. an +// NSArray of NSStrings. The non-strict variant is useful when +// retrieving values from data that you can't fully control. For +// example, a plist read from disk may be beyond your exclusive +// control, so you'd only want to check that the values you retrieve +// from it are of the expected types, but not crash if they're not. +// +// Example usage: +// NSString* version = butil::mac::ObjCCast( +// [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]); +// +// NSString* str = butil::mac::ObjCCastStrict( +// [ns_arr_of_ns_strs objectAtIndex:0]); +template +T* ObjCCast(id objc_val) { + if ([objc_val isKindOfClass:[T class]]) { + return reinterpret_cast(objc_val); + } + return nil; +} + +template +T* ObjCCastStrict(id objc_val) { + T* rv = ObjCCast(objc_val); + DCHECK(objc_val == nil || rv); + return rv; +} + +#endif // defined(__OBJC__) + +// Helper function for GetValueFromDictionary to create the error message +// that appears when a type mismatch is encountered. +BUTIL_EXPORT std::string GetValueFromDictionaryErrorMessage( + CFStringRef key, const std::string& expected_type, CFTypeRef value); + +// Utility function to pull out a value from a dictionary, check its type, and +// return it. Returns NULL if the key is not present or of the wrong type. +template +T GetValueFromDictionary(CFDictionaryRef dict, CFStringRef key) { + CFTypeRef value = CFDictionaryGetValue(dict, key); + T value_specific = CFCast(value); + + if (value && !value_specific) { + std::string expected_type = TypeNameForCFType(value_specific); + DLOG(WARNING) << GetValueFromDictionaryErrorMessage(key, + expected_type, + value); + } + + return value_specific; +} + +// Converts |path| to an autoreleased NSString. Returns nil if |path| is empty. +BUTIL_EXPORT NSString* FilePathToNSString(const FilePath& path); + +// Converts |str| to a FilePath. Returns an empty path if |str| is nil. +BUTIL_EXPORT FilePath NSStringToFilePath(NSString* str); + +} // namespace mac +} // namespace butil + +// Stream operations for CFTypes. They can be used with NSTypes as well +// by using the NSToCFCast methods above. +// e.g. LOG(INFO) << butil::mac::NSToCFCast(@"foo"); +// Operator << can not be overloaded for ObjectiveC types as the compiler +// can not distinguish between overloads for id with overloads for void*. +BUTIL_EXPORT extern std::ostream& operator<<(std::ostream& o, + const CFErrorRef err); +BUTIL_EXPORT extern std::ostream& operator<<(std::ostream& o, + const CFStringRef str); + +#endif // BUTIL_MAC_FOUNDATION_UTIL_H_ diff --git a/src/butil/mac/foundation_util.mm b/src/butil/mac/foundation_util.mm new file mode 100644 index 0000000..74d26aa --- /dev/null +++ b/src/butil/mac/foundation_util.mm @@ -0,0 +1,445 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/mac/foundation_util.h" + +#include +#include + +#include "butil/files/file_path.h" +#include "butil/logging.h" +#include "butil/mac/bundle_locations.h" +//#include "butil/mac/mac_logging.h" +#include "butil/strings/sys_string_conversions.h" + +#if !defined(OS_IOS) +extern "C" { +CFTypeID SecACLGetTypeID(); +CFTypeID SecTrustedApplicationGetTypeID(); +Boolean _CFIsObjC(CFTypeID typeID, CFTypeRef obj); +} // extern "C" +#endif + +namespace butil { +namespace mac { + +namespace { + +bool g_override_am_i_bundled = false; +bool g_override_am_i_bundled_value = false; + +bool UncachedAmIBundled() { +#if defined(OS_IOS) + // All apps are bundled on iOS. + return true; +#else + if (g_override_am_i_bundled) + return g_override_am_i_bundled_value; + + // Yes, this is cheap. + return [[butil::mac::OuterBundle() bundlePath] hasSuffix:@".app"]; +#endif +} + +} // namespace + +bool AmIBundled() { + // If the return value is not cached, this function will return different + // values depending on when it's called. This confuses some client code, see + // http://crbug.com/63183 . + static bool result = UncachedAmIBundled(); + DCHECK_EQ(result, UncachedAmIBundled()) + << "The return value of AmIBundled() changed. This will confuse tests. " + << "Call SetAmIBundled() override manually if your test binary " + << "delay-loads the framework."; + return result; +} + +void SetOverrideAmIBundled(bool value) { +#if defined(OS_IOS) + // It doesn't make sense not to be bundled on iOS. + if (!value) + NOTREACHED(); +#endif + g_override_am_i_bundled = true; + g_override_am_i_bundled_value = value; +} + +bool IsBackgroundOnlyProcess() { + // This function really does want to examine NSBundle's idea of the main + // bundle dictionary. It needs to look at the actual running .app's + // Info.plist to access its LSUIElement property. + NSDictionary* info_dictionary = [butil::mac::MainBundle() infoDictionary]; + return [[info_dictionary objectForKey:@"LSUIElement"] boolValue] != NO; +} + +FilePath PathForFrameworkBundleResource(CFStringRef resourceName) { + NSBundle* bundle = butil::mac::FrameworkBundle(); + NSString* resourcePath = [bundle pathForResource:(NSString*)resourceName + ofType:nil]; + return NSStringToFilePath(resourcePath); +} + +OSType CreatorCodeForCFBundleRef(CFBundleRef bundle) { + OSType creator = kUnknownType; + CFBundleGetPackageInfo(bundle, NULL, &creator); + return creator; +} + +OSType CreatorCodeForApplication() { + CFBundleRef bundle = CFBundleGetMainBundle(); + if (!bundle) + return kUnknownType; + + return CreatorCodeForCFBundleRef(bundle); +} + +bool GetSearchPathDirectory(NSSearchPathDirectory directory, + NSSearchPathDomainMask domain_mask, + FilePath* result) { + DCHECK(result); + NSArray* dirs = + NSSearchPathForDirectoriesInDomains(directory, domain_mask, YES); + if ([dirs count] < 1) { + return false; + } + *result = NSStringToFilePath([dirs objectAtIndex:0]); + return true; +} + +bool GetLocalDirectory(NSSearchPathDirectory directory, FilePath* result) { + return GetSearchPathDirectory(directory, NSLocalDomainMask, result); +} + +bool GetUserDirectory(NSSearchPathDirectory directory, FilePath* result) { + return GetSearchPathDirectory(directory, NSUserDomainMask, result); +} + +FilePath GetUserLibraryPath() { + FilePath user_library_path; + if (!GetUserDirectory(NSLibraryDirectory, &user_library_path)) { + DLOG(WARNING) << "Could not get user library path"; + } + return user_library_path; +} + +// Takes a path to an (executable) binary and tries to provide the path to an +// application bundle containing it. It takes the outermost bundle that it can +// find (so for "/Foo/Bar.app/.../Baz.app/..." it produces "/Foo/Bar.app"). +// |exec_name| - path to the binary +// returns - path to the application bundle, or empty on error +FilePath GetAppBundlePath(const FilePath& exec_name) { + const char kExt[] = ".app"; + const size_t kExtLength = arraysize(kExt) - 1; + + // Split the path into components. + std::vector components; + exec_name.GetComponents(&components); + + // It's an error if we don't get any components. + if (!components.size()) + return FilePath(); + + // Don't prepend '/' to the first component. + std::vector::const_iterator it = components.begin(); + std::string bundle_name = *it; + DCHECK_GT(it->length(), 0U); + // If the first component ends in ".app", we're already done. + if (it->length() > kExtLength && + !it->compare(it->length() - kExtLength, kExtLength, kExt, kExtLength)) + return FilePath(bundle_name); + + // The first component may be "/" or "//", etc. Only append '/' if it doesn't + // already end in '/'. + if (bundle_name[bundle_name.length() - 1] != '/') + bundle_name += '/'; + + // Go through the remaining components. + for (++it; it != components.end(); ++it) { + DCHECK_GT(it->length(), 0U); + + bundle_name += *it; + + // If the current component ends in ".app", we're done. + if (it->length() > kExtLength && + !it->compare(it->length() - kExtLength, kExtLength, kExt, kExtLength)) + return FilePath(bundle_name); + + // Separate this component from the next one. + bundle_name += '/'; + } + + return FilePath(); +} + +#define TYPE_NAME_FOR_CF_TYPE_DEFN(TypeCF) \ +std::string TypeNameForCFType(TypeCF##Ref) { \ + return #TypeCF; \ +} + +TYPE_NAME_FOR_CF_TYPE_DEFN(CFArray); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFBag); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFBoolean); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFData); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFDate); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFDictionary); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFNull); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFNumber); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFSet); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFString); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFURL); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFUUID); + +TYPE_NAME_FOR_CF_TYPE_DEFN(CGColor); + +TYPE_NAME_FOR_CF_TYPE_DEFN(CTFont); +TYPE_NAME_FOR_CF_TYPE_DEFN(CTRun); + +#undef TYPE_NAME_FOR_CF_TYPE_DEFN + +void NSObjectRetain(void* obj) { + id nsobj = static_cast >(obj); + [nsobj retain]; +} + +void NSObjectRelease(void* obj) { + id nsobj = static_cast >(obj); + [nsobj release]; +} + +void* CFTypeRefToNSObjectAutorelease(CFTypeRef cf_object) { + // When GC is on, NSMakeCollectable marks cf_object for GC and autorelease + // is a no-op. + // + // In the traditional GC-less environment, NSMakeCollectable is a no-op, + // and cf_object is autoreleased, balancing out the caller's ownership claim. + // + // NSMakeCollectable returns nil when used on a NULL object. + return [NSMakeCollectable(cf_object) autorelease]; +} + +static const char* base_bundle_id; + +const char* BaseBundleID() { + if (base_bundle_id) { + return base_bundle_id; + } + +#if defined(GOOGLE_CHROME_BUILD) + return "com.google.Chrome"; +#else + return "org.chromium.Chromium"; +#endif +} + +void SetBaseBundleID(const char* new_base_bundle_id) { + if (new_base_bundle_id != base_bundle_id) { + free((void*)base_bundle_id); + base_bundle_id = new_base_bundle_id ? strdup(new_base_bundle_id) : NULL; + } +} + +// Definitions for the corresponding CF_TO_NS_CAST_DECL macros in +// foundation_util.h. +#define CF_TO_NS_CAST_DEFN(TypeCF, TypeNS) \ +\ +TypeNS* CFToNSCast(TypeCF##Ref cf_val) { \ + DCHECK(!cf_val || TypeCF##GetTypeID() == CFGetTypeID(cf_val)); \ + TypeNS* ns_val = \ + const_cast(reinterpret_cast(cf_val)); \ + return ns_val; \ +} \ +\ +TypeCF##Ref NSToCFCast(TypeNS* ns_val) { \ + TypeCF##Ref cf_val = reinterpret_cast(ns_val); \ + DCHECK(!cf_val || TypeCF##GetTypeID() == CFGetTypeID(cf_val)); \ + return cf_val; \ +} + +#define CF_TO_NS_MUTABLE_CAST_DEFN(name) \ +CF_TO_NS_CAST_DEFN(CF##name, NS##name) \ +\ +NSMutable##name* CFToNSCast(CFMutable##name##Ref cf_val) { \ + DCHECK(!cf_val || CF##name##GetTypeID() == CFGetTypeID(cf_val)); \ + NSMutable##name* ns_val = reinterpret_cast(cf_val); \ + return ns_val; \ +} \ +\ +CFMutable##name##Ref NSToCFCast(NSMutable##name* ns_val) { \ + CFMutable##name##Ref cf_val = \ + reinterpret_cast(ns_val); \ + DCHECK(!cf_val || CF##name##GetTypeID() == CFGetTypeID(cf_val)); \ + return cf_val; \ +} + +CF_TO_NS_MUTABLE_CAST_DEFN(Array); +CF_TO_NS_MUTABLE_CAST_DEFN(AttributedString); +CF_TO_NS_CAST_DEFN(CFCalendar, NSCalendar); +CF_TO_NS_MUTABLE_CAST_DEFN(CharacterSet); +CF_TO_NS_MUTABLE_CAST_DEFN(Data); +CF_TO_NS_CAST_DEFN(CFDate, NSDate); +CF_TO_NS_MUTABLE_CAST_DEFN(Dictionary); +CF_TO_NS_CAST_DEFN(CFError, NSError); +CF_TO_NS_CAST_DEFN(CFLocale, NSLocale); +CF_TO_NS_CAST_DEFN(CFNumber, NSNumber); +CF_TO_NS_CAST_DEFN(CFRunLoopTimer, NSTimer); +CF_TO_NS_CAST_DEFN(CFTimeZone, NSTimeZone); +CF_TO_NS_MUTABLE_CAST_DEFN(Set); +CF_TO_NS_CAST_DEFN(CFReadStream, NSInputStream); +CF_TO_NS_CAST_DEFN(CFWriteStream, NSOutputStream); +CF_TO_NS_MUTABLE_CAST_DEFN(String); +CF_TO_NS_CAST_DEFN(CFURL, NSURL); + +#if defined(OS_IOS) +CF_TO_NS_CAST_DEFN(CTFont, UIFont); +#else +// The NSFont/CTFont toll-free bridging is broken when it comes to type +// checking, so do some special-casing. +// http://www.openradar.me/15341349 rdar://15341349 +NSFont* CFToNSCast(CTFontRef cf_val) { + NSFont* ns_val = + const_cast(reinterpret_cast(cf_val)); + DCHECK(!cf_val || + CTFontGetTypeID() == CFGetTypeID(cf_val) || + (_CFIsObjC(CTFontGetTypeID(), cf_val) && + [ns_val isKindOfClass:NSClassFromString(@"NSFont")])); + return ns_val; +} + +CTFontRef NSToCFCast(NSFont* ns_val) { + CTFontRef cf_val = reinterpret_cast(ns_val); + DCHECK(!cf_val || + CTFontGetTypeID() == CFGetTypeID(cf_val) || + [ns_val isKindOfClass:NSClassFromString(@"NSFont")]); + return cf_val; +} +#endif + +#undef CF_TO_NS_CAST_DEFN +#undef CF_TO_NS_MUTABLE_CAST_DEFN + +#define CF_CAST_DEFN(TypeCF) \ +template<> TypeCF##Ref \ +CFCast(const CFTypeRef& cf_val) { \ + if (cf_val == NULL) { \ + return NULL; \ + } \ + if (CFGetTypeID(cf_val) == TypeCF##GetTypeID()) { \ + return (TypeCF##Ref)(cf_val); \ + } \ + return NULL; \ +} \ +\ +template<> TypeCF##Ref \ +CFCastStrict(const CFTypeRef& cf_val) { \ + TypeCF##Ref rv = CFCast(cf_val); \ + DCHECK(cf_val == NULL || rv); \ + return rv; \ +} + +CF_CAST_DEFN(CFArray); +CF_CAST_DEFN(CFBag); +CF_CAST_DEFN(CFBoolean); +CF_CAST_DEFN(CFData); +CF_CAST_DEFN(CFDate); +CF_CAST_DEFN(CFDictionary); +CF_CAST_DEFN(CFNull); +CF_CAST_DEFN(CFNumber); +CF_CAST_DEFN(CFSet); +CF_CAST_DEFN(CFString); +CF_CAST_DEFN(CFURL); +CF_CAST_DEFN(CFUUID); + +CF_CAST_DEFN(CGColor); + +CF_CAST_DEFN(CTRun); + +#if defined(OS_IOS) +CF_CAST_DEFN(CTFont); +#else +// The NSFont/CTFont toll-free bridging is broken when it comes to type +// checking, so do some special-casing. +// http://www.openradar.me/15341349 rdar://15341349 +template<> CTFontRef +CFCast(const CFTypeRef& cf_val) { + if (cf_val == NULL) { + return NULL; + } + if (CFGetTypeID(cf_val) == CTFontGetTypeID()) { + return (CTFontRef)(cf_val); + } + + if (!_CFIsObjC(CTFontGetTypeID(), cf_val)) + return NULL; + + id ns_val = reinterpret_cast(const_cast(cf_val)); + if ([ns_val isKindOfClass:NSClassFromString(@"NSFont")]) { + return (CTFontRef)(cf_val); + } + return NULL; +} + +template<> CTFontRef +CFCastStrict(const CFTypeRef& cf_val) { + CTFontRef rv = CFCast(cf_val); + DCHECK(cf_val == NULL || rv); + return rv; +} +#endif + +#if !defined(OS_IOS) +CF_CAST_DEFN(SecACL); +CF_CAST_DEFN(SecTrustedApplication); +#endif + +#undef CF_CAST_DEFN + +std::string GetValueFromDictionaryErrorMessage( + CFStringRef key, const std::string& expected_type, CFTypeRef value) { + ScopedCFTypeRef actual_type_ref( + CFCopyTypeIDDescription(CFGetTypeID(value))); + return "Expected value for key " + + butil::SysCFStringRefToUTF8(key) + + " to be " + + expected_type + + " but it was " + + butil::SysCFStringRefToUTF8(actual_type_ref) + + " instead"; +} + +NSString* FilePathToNSString(const FilePath& path) { + if (path.empty()) + return nil; + return [NSString stringWithUTF8String:path.value().c_str()]; +} + +FilePath NSStringToFilePath(NSString* str) { + if (![str length]) + return FilePath(); + return FilePath([str fileSystemRepresentation]); +} + +} // namespace mac +} // namespace butil + +std::ostream& operator<<(std::ostream& o, const CFStringRef string) { + return o << butil::SysCFStringRefToUTF8(string); +} + +std::ostream& operator<<(std::ostream& o, const CFErrorRef err) { + butil::ScopedCFTypeRef desc(CFErrorCopyDescription(err)); + butil::ScopedCFTypeRef user_info(CFErrorCopyUserInfo(err)); + CFStringRef errorDesc = NULL; + if (user_info.get()) { + errorDesc = reinterpret_cast( + CFDictionaryGetValue(user_info.get(), kCFErrorDescriptionKey)); + } + o << "Code: " << CFErrorGetCode(err) + << " Domain: " << CFErrorGetDomain(err) + << " Desc: " << desc.get(); + if(errorDesc) { + o << "(" << errorDesc << ")"; + } + return o; +} diff --git a/src/butil/mac/scoped_cftyperef.h b/src/butil/mac/scoped_cftyperef.h new file mode 100644 index 0000000..626a431 --- /dev/null +++ b/src/butil/mac/scoped_cftyperef.h @@ -0,0 +1,59 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_MAC_SCOPED_CFTYPEREF_H_ +#define BUTIL_MAC_SCOPED_CFTYPEREF_H_ + +#include + +#include "butil/mac/scoped_typeref.h" + +namespace butil { + +// ScopedCFTypeRef<> is patterned after scoped_ptr<>, but maintains ownership +// of a CoreFoundation object: any object that can be represented as a +// CFTypeRef. Style deviations here are solely for compatibility with +// scoped_ptr<>'s interface, with which everyone is already familiar. +// +// By default, ScopedCFTypeRef<> takes ownership of an object (in the +// constructor or in reset()) by taking over the caller's existing ownership +// claim. The caller must own the object it gives to ScopedCFTypeRef<>, and +// relinquishes an ownership claim to that object. ScopedCFTypeRef<> does not +// call CFRetain(). This behavior is parameterized by the |OwnershipPolicy| +// enum. If the value |RETAIN| is passed (in the constructor or in reset()), +// then ScopedCFTypeRef<> will call CFRetain() on the object, and the initial +// ownership is not changed. + +namespace internal { + +struct ScopedCFTypeRefTraits { + static void Retain(CFTypeRef object) { + CFRetain(object); + } + static void Release(CFTypeRef object) { + CFRelease(object); + } +}; + +} // namespace internal + +template +class ScopedCFTypeRef + : public ScopedTypeRef { + public: + typedef CFT element_type; + + explicit ScopedCFTypeRef( + CFT object = NULL, + butil::scoped_policy::OwnershipPolicy policy = butil::scoped_policy::ASSUME) + : ScopedTypeRef(object, policy) {} + + ScopedCFTypeRef(const ScopedCFTypeRef& that) + : ScopedTypeRef(that) {} +}; + +} // namespace butil + +#endif // BUTIL_MAC_SCOPED_CFTYPEREF_H_ diff --git a/src/butil/mac/scoped_mach_port.cc b/src/butil/mac/scoped_mach_port.cc new file mode 100644 index 0000000..b720374 --- /dev/null +++ b/src/butil/mac/scoped_mach_port.cc @@ -0,0 +1,33 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include "butil/mac/scoped_mach_port.h" +#include "butil/logging.h" + +namespace butil { +namespace mac { +namespace internal { + +// static +void SendRightTraits::Free(mach_port_t port) { + kern_return_t kr = mach_port_deallocate(mach_task_self(), port); + LOG_IF(ERROR, kr != KERN_SUCCESS) << "Fail to call mach_port_deallocate"; +} + +// static +void ReceiveRightTraits::Free(mach_port_t port) { + kern_return_t kr = + mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_RECEIVE, -1); + LOG_IF(ERROR, kr != KERN_SUCCESS) << "Fail to call mach_port_mod_refs"; +} + +// static +void PortSetTraits::Free(mach_port_t port) { + kern_return_t kr = + mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_PORT_SET, -1); + LOG_IF(ERROR, kr != KERN_SUCCESS) << "Fail to call mach_port_mod_refs"; +} + +} // namespace internal +} // namespace mac +} // namespace butil diff --git a/src/butil/mac/scoped_mach_port.h b/src/butil/mac/scoped_mach_port.h new file mode 100644 index 0000000..8f4f757 --- /dev/null +++ b/src/butil/mac/scoped_mach_port.h @@ -0,0 +1,67 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MAC_SCOPED_MACH_PORT_H_ +#define BASE_MAC_SCOPED_MACH_PORT_H_ + +#include + +#include "butil/base_export.h" +#include "butil/scoped_generic.h" + +namespace butil { +namespace mac { + +namespace internal { + +struct SendRightTraits { + static mach_port_t InvalidValue() { + return MACH_PORT_NULL; + } + + static void Free(mach_port_t port); +}; + +struct ReceiveRightTraits { + static mach_port_t InvalidValue() { + return MACH_PORT_NULL; + } + + static void Free(mach_port_t port); +}; + +struct PortSetTraits { + static mach_port_t InvalidValue() { + return MACH_PORT_NULL; + } + + static void Free(mach_port_t port); +}; + +} // namespace internal + +// A scoper for handling a Mach port that names a send right. Send rights are +// reference counted, and this takes ownership of the right on construction +// and then removes a reference to the right on destruction. If the reference +// is the last one on the right, the right is deallocated. +using ScopedMachSendRight = + ScopedGeneric; + +// A scoper for handling a Mach port's receive right. There is only one +// receive right per port. This takes ownership of the receive right on +// construction and then destroys the right on destruction, turning all +// outstanding send rights into dead names. +using ScopedMachReceiveRight = + ScopedGeneric; + +// A scoper for handling a Mach port set. A port set can have only one +// reference. This takes ownership of that single reference on construction and +// destroys the port set on destruction. Destroying a port set does not destroy +// the receive rights that are members of the port set. +using ScopedMachPortSet = ScopedGeneric; + +} // namespace mac +} // namespace butil + +#endif // BASE_MAC_SCOPED_MACH_PORT_H_ diff --git a/src/butil/mac/scoped_typeref.h b/src/butil/mac/scoped_typeref.h new file mode 100644 index 0000000..85efbf4 --- /dev/null +++ b/src/butil/mac/scoped_typeref.h @@ -0,0 +1,131 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_MAC_SCOPED_TYPEREF_H_ +#define BUTIL_MAC_SCOPED_TYPEREF_H_ + +#include "butil/basictypes.h" +#include "butil/compiler_specific.h" +#include "butil/logging.h" +#include "butil/memory/scoped_policy.h" + +namespace butil { + +// ScopedTypeRef<> is patterned after scoped_ptr<>, but maintains a ownership +// of a reference to any type that is maintained by Retain and Release methods. +// +// The Traits structure must provide the Retain and Release methods for type T. +// A default ScopedTypeRefTraits is used but not defined, and should be defined +// for each type to use this interface. For example, an appropriate definition +// of ScopedTypeRefTraits for CGLContextObj would be: +// +// template<> +// struct ScopedTypeRefTraits { +// void Retain(CGLContextObj object) { CGLContextRetain(object); } +// void Release(CGLContextObj object) { CGLContextRelease(object); } +// }; +// +// For the many types that have pass-by-pointer create functions, the function +// InitializeInto() is provided to allow direct initialization and assumption +// of ownership of the object. For example, continuing to use the above +// CGLContextObj specialization: +// +// butil::ScopedTypeRef context; +// CGLCreateContext(pixel_format, share_group, context.InitializeInto()); +// +// For initialization with an existing object, the caller may specify whether +// the ScopedTypeRef<> being initialized is assuming the caller's existing +// ownership of the object (and should not call Retain in initialization) or if +// it should not assume this ownership and must create its own (by calling +// Retain in initialization). This behavior is based on the |policy| parameter, +// with |ASSUME| for the former and |RETAIN| for the latter. The default policy +// is to |ASSUME|. + +template +struct ScopedTypeRefTraits; + +template> +class ScopedTypeRef { + public: + typedef T element_type; + + ScopedTypeRef( + T object = NULL, + scoped_policy::OwnershipPolicy policy = scoped_policy::ASSUME) + : object_(object) { + if (object_ && policy == scoped_policy::RETAIN) + Traits::Retain(object_); + } + + ScopedTypeRef(const ScopedTypeRef& that) + : object_(that.object_) { + if (object_) + Traits::Retain(object_); + } + + ~ScopedTypeRef() { + if (object_) + Traits::Release(object_); + } + + ScopedTypeRef& operator=(const ScopedTypeRef& that) { + reset(that.get(), scoped_policy::RETAIN); + return *this; + } + + // This is to be used only to take ownership of objects that are created + // by pass-by-pointer create functions. To enforce this, require that the + // object be reset to NULL before this may be used. + T* InitializeInto() WARN_UNUSED_RESULT { + DCHECK(!object_); + return &object_; + } + + void reset(T object = NULL, + scoped_policy::OwnershipPolicy policy = scoped_policy::ASSUME) { + if (object && policy == scoped_policy::RETAIN) + Traits::Retain(object); + if (object_) + Traits::Release(object_); + object_ = object; + } + + bool operator==(T that) const { + return object_ == that; + } + + bool operator!=(T that) const { + return object_ != that; + } + + operator T() const { + return object_; + } + + T get() const { + return object_; + } + + void swap(ScopedTypeRef& that) { + T temp = that.object_; + that.object_ = object_; + object_ = temp; + } + + // ScopedTypeRef<>::release() is like scoped_ptr<>::release. It is NOT + // a wrapper for Release(). To force a ScopedTypeRef<> object to call + // Release(), use ScopedTypeRef<>::reset(). + T release() WARN_UNUSED_RESULT { + T temp = object_; + object_ = NULL; + return temp; + } + + private: + T object_; +}; + +} // namespace butil + +#endif // BUTIL_MAC_SCOPED_TYPEREF_H_ diff --git a/src/butil/macros.h b/src/butil/macros.h new file mode 100644 index 0000000..d88a82d --- /dev/null +++ b/src/butil/macros.h @@ -0,0 +1,496 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains macros and macro-like constructs (e.g., templates) that +// are commonly used throughout Chromium source. (It may also contain things +// that are closely related to things that are commonly used that belong in this +// file.) + +#ifndef BUTIL_MACROS_H_ +#define BUTIL_MACROS_H_ + +#include // For size_t. +#include // For memcpy. +#include + +#include "butil/compiler_specific.h" // For ALLOW_UNUSED. +#include "butil/string_printf.h" // For butil::string_printf(). + +// There must be many copy-paste versions of these macros which are same +// things, undefine them to avoid conflict. +#undef DISALLOW_COPY +#undef DISALLOW_MOVE +#undef DISALLOW_ASSIGN +#undef DISALLOW_MOVE_ASSIGN +#undef DISALLOW_COPY_AND_ASSIGN +#undef DISALLOW_COPY_AND_MOVE +#undef DISALLOW_EVIL_CONSTRUCTORS +#undef DISALLOW_IMPLICIT_CONSTRUCTORS + +#if !defined(BUTIL_CXX11_ENABLED) +#define BUTIL_DELETE_FUNCTION(decl) decl +#else +#define BUTIL_DELETE_FUNCTION(decl) decl = delete +#endif + +// Declarations for a class to be uncopyable. +#define DISALLOW_COPY(TypeName) \ + BUTIL_DELETE_FUNCTION(TypeName(const TypeName&)) + +// Declarations for a class to be unmovable. +#define DISALLOW_MOVE(TypeName) \ + BUTIL_DELETE_FUNCTION(TypeName(TypeName&&)) + +// Declarations for a class to be unassignable. +#define DISALLOW_ASSIGN(TypeName) \ + BUTIL_DELETE_FUNCTION(TypeName& operator=(const TypeName&)) + +// Declarations for a class to be move-unassignable. +#define DISALLOW_MOVE_ASSIGN(TypeName) \ + BUTIL_DELETE_FUNCTION(TypeName& operator=(TypeName&&)) + +// A macro to disallow the copy constructor and operator= functions. +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + DISALLOW_COPY(TypeName); \ + DISALLOW_ASSIGN(TypeName) + +// A macro to disallow the move constructor and operator= functions. +#define DISALLOW_MOVE_AND_ASSIGN(TypeName) \ + DISALLOW_MOVE(TypeName); \ + DISALLOW_MOVE_ASSIGN(TypeName) + +// A macro to disallow the copy constructor, +// the move constructor and operator= functions. +#define DISALLOW_COPY_AND_MOVE(TypeName) \ + DISALLOW_COPY_AND_ASSIGN(TypeName); \ + DISALLOW_MOVE(TypeName) + +// An older, deprecated, politically incorrect name for the above. +// NOTE: The usage of this macro was banned from our code base, but some +// third_party libraries are yet using it. +// TODO(tfarina): Figure out how to fix the usage of this macro in the +// third_party libraries and get rid of it. +#define DISALLOW_EVIL_CONSTRUCTORS(TypeName) DISALLOW_COPY_AND_ASSIGN(TypeName) + +// A macro to disallow all the implicit constructors, namely the +// default constructor, copy constructor and operator= functions. +// +// This should be used in the private: declarations for a class +// that wants to prevent anyone from instantiating it. This is +// especially useful for classes containing only static methods. +#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + BUTIL_DELETE_FUNCTION(TypeName()); \ + DISALLOW_COPY_AND_ASSIGN(TypeName) + +// Concatenate numbers in c/c++ macros. +#ifndef BAIDU_CONCAT +# define BAIDU_CONCAT(a, b) BAIDU_CONCAT_HELPER(a, b) +# define BAIDU_CONCAT_HELPER(a, b) a##b +#endif + +#undef arraysize +// The arraysize(arr) macro returns the # of elements in an array arr. +// The expression is a compile-time constant, and therefore can be +// used in defining new arrays, for example. If you use arraysize on +// a pointer by mistake, you will get a compile-time error. +// +// One caveat is that arraysize() doesn't accept any array of an +// anonymous type or a type defined inside a function. In these rare +// cases, you have to use the unsafe ARRAYSIZE_UNSAFE() macro below. This is +// due to a limitation in C++'s template system. The limitation might +// eventually be removed, but it hasn't happened yet. + +// This template function declaration is used in defining arraysize. +// Note that the function doesn't need an implementation, as we only +// use its type. +namespace butil { +template +char (&ArraySizeHelper(T (&array)[N]))[N]; +} + +// That gcc wants both of these prototypes seems mysterious. VC, for +// its part, can't decide which to use (another mystery). Matching of +// template overloads: the final frontier. +#ifndef _MSC_VER +namespace butil { +template +char (&ArraySizeHelper(const T (&array)[N]))[N]; +} +#endif + +#define arraysize(array) (sizeof(::butil::ArraySizeHelper(array))) + +// gejun: Following macro was used in other modules. +#undef ARRAY_SIZE +#define ARRAY_SIZE(array) arraysize(array) + +// ARRAYSIZE_UNSAFE performs essentially the same calculation as arraysize, +// but can be used on anonymous types or types defined inside +// functions. It's less safe than arraysize as it accepts some +// (although not all) pointers. Therefore, you should use arraysize +// whenever possible. +// +// The expression ARRAYSIZE_UNSAFE(a) is a compile-time constant of type +// size_t. +// +// ARRAYSIZE_UNSAFE catches a few type errors. If you see a compiler error +// +// "warning: division by zero in ..." +// +// when using ARRAYSIZE_UNSAFE, you are (wrongfully) giving it a pointer. +// You should only use ARRAYSIZE_UNSAFE on statically allocated arrays. +// +// The following comments are on the implementation details, and can +// be ignored by the users. +// +// ARRAYSIZE_UNSAFE(arr) works by inspecting sizeof(arr) (the # of bytes in +// the array) and sizeof(*(arr)) (the # of bytes in one array +// element). If the former is divisible by the latter, perhaps arr is +// indeed an array, in which case the division result is the # of +// elements in the array. Otherwise, arr cannot possibly be an array, +// and we generate a compiler error to prevent the code from +// compiling. +// +// Since the size of bool is implementation-defined, we need to cast +// !(sizeof(a) & sizeof(*(a))) to size_t in order to ensure the final +// result has type size_t. +// +// This macro is not perfect as it wrongfully accepts certain +// pointers, namely where the pointer size is divisible by the pointee +// size. Since all our code has to go through a 32-bit compiler, +// where a pointer is 4 bytes, this means all pointers to a type whose +// size is 3 or greater than 4 will be (righteously) rejected. +#undef ARRAYSIZE_UNSAFE +#define ARRAYSIZE_UNSAFE(a) \ + ((sizeof(a) / sizeof(*(a))) / \ + static_cast(!(sizeof(a) % sizeof(*(a))))) + +// Use implicit_cast as a safe version of static_cast or const_cast +// for upcasting in the type hierarchy (i.e. casting a pointer to Foo +// to a pointer to SuperclassOfFoo or casting a pointer to Foo to +// a const pointer to Foo). +// When you use implicit_cast, the compiler checks that the cast is safe. +// Such explicit implicit_casts are necessary in surprisingly many +// situations where C++ demands an exact type match instead of an +// argument type convertible to a target type. +// +// The From type can be inferred, so the preferred syntax for using +// implicit_cast is the same as for static_cast etc.: +// +// implicit_cast(expr) +// +// implicit_cast would have been part of the C++ standard library, +// but the proposal was submitted too late. It will probably make +// its way into the language in the future. +namespace butil { +template +inline To implicit_cast(From const &f) { + return f; +} +} + +#if defined(BUTIL_CXX11_ENABLED) + +// C++11 supports compile-time assertion directly +#define BAIDU_CASSERT(expr, msg) static_assert(expr, #msg) + +#else + +// Assert constant boolean expressions at compile-time +// Params: +// expr the constant expression to be checked +// msg an error infomation conforming name conventions of C/C++ +// variables(alphabets/numbers/underscores, no blanks). For +// example "cannot_accept_a_number_bigger_than_128" is valid +// while "this number is out-of-range" is illegal. +// +// when an asssertion like "BAIDU_CASSERT(false, you_should_not_be_here)" +// breaks, a compilation error is printed: +// +// foo.cpp:401: error: enumerator value for `you_should_not_be_here___19' not +// integer constant +// +// You can call BAIDU_CASSERT at global scope, inside a class or a function +// +// BAIDU_CASSERT(false, you_should_not_be_here); +// int main () { ... } +// +// struct Foo { +// BAIDU_CASSERT(1 == 0, Never_equals); +// }; +// +// int bar(...) +// { +// BAIDU_CASSERT (value < 10, invalid_value); +// } +// +namespace butil { +template struct CAssert { static const int x = 1; }; +template <> struct CAssert { static const char * x; }; +} + +#define BAIDU_CASSERT(expr, msg) \ + enum { BAIDU_CONCAT(BAIDU_CONCAT(LINE_, __LINE__), __##msg) \ + = ::butil::CAssert::x }; + +#endif // BUTIL_CXX11_ENABLED + +// The impl. of chrome does not work for offsetof(Object, private_filed) +#undef COMPILE_ASSERT +#define COMPILE_ASSERT(expr, msg) BAIDU_CASSERT(expr, msg) + +// bit_cast is a template function that implements the +// equivalent of "*reinterpret_cast(&source)". We need this in +// very low-level functions like the protobuf library and fast math +// support. +// +// float f = 3.14159265358979; +// int i = bit_cast(f); +// // i = 0x40490fdb +// +// The classical address-casting method is: +// +// // WRONG +// float f = 3.14159265358979; // WRONG +// int i = * reinterpret_cast(&f); // WRONG +// +// The address-casting method actually produces undefined behavior +// according to ISO C++ specification section 3.10 -15 -. Roughly, this +// section says: if an object in memory has one type, and a program +// accesses it with a different type, then the result is undefined +// behavior for most values of "different type". +// +// This is true for any cast syntax, either *(int*)&f or +// *reinterpret_cast(&f). And it is particularly true for +// conversions between integral lvalues and floating-point lvalues. +// +// The purpose of 3.10 -15- is to allow optimizing compilers to assume +// that expressions with different types refer to different memory. gcc +// 4.0.1 has an optimizer that takes advantage of this. So a +// non-conforming program quietly produces wildly incorrect output. +// +// The problem is not the use of reinterpret_cast. The problem is type +// punning: holding an object in memory of one type and reading its bits +// back using a different type. +// +// The C++ standard is more subtle and complex than this, but that +// is the basic idea. +// +// Anyways ... +// +// bit_cast<> calls memcpy() which is blessed by the standard, +// especially by the example in section 3.9 . Also, of course, +// bit_cast<> wraps up the nasty logic in one place. +// +// Fortunately memcpy() is very fast. In optimized mode, with a +// constant size, gcc 2.95.3, gcc 4.0.1, and msvc 7.1 produce inline +// code with the minimal amount of data movement. On a 32-bit system, +// memcpy(d,s,4) compiles to one load and one store, and memcpy(d,s,8) +// compiles to two loads and two stores. +// +// I tested this code with gcc 2.95.3, gcc 4.0.1, icc 8.1, and msvc 7.1. +// +// WARNING: if Dest or Source is a non-POD type, the result of the memcpy +// is likely to surprise you. +namespace butil { +template +inline Dest bit_cast(const Source& source) { + COMPILE_ASSERT(sizeof(Dest) == sizeof(Source), VerifySizesAreEqual); + + Dest dest; + memcpy(&dest, &source, sizeof(dest)); + return dest; +} +} // namespace butil + +// Used to explicitly mark the return value of a function as unused. If you are +// really sure you don't want to do anything with the return value of a function +// that has been marked WARN_UNUSED_RESULT, wrap it with this. Example: +// +// scoped_ptr my_var = ...; +// if (TakeOwnership(my_var.get()) == SUCCESS) +// ignore_result(my_var.release()); +// +namespace butil { +template +inline void ignore_result(const T&) { +} +} // namespace butil + +// The following enum should be used only as a constructor argument to indicate +// that the variable has static storage class, and that the constructor should +// do nothing to its state. It indicates to the reader that it is legal to +// declare a static instance of the class, provided the constructor is given +// the butil::LINKER_INITIALIZED argument. Normally, it is unsafe to declare a +// static variable that has a constructor or a destructor because invocation +// order is undefined. However, IF the type can be initialized by filling with +// zeroes (which the loader does for static variables), AND the destructor also +// does nothing to the storage, AND there are no virtual methods, then a +// constructor declared as +// explicit MyClass(butil::LinkerInitialized x) {} +// and invoked as +// static MyClass my_variable_name(butil::LINKER_INITIALIZED); +namespace butil { +enum LinkerInitialized { LINKER_INITIALIZED }; + +// Use these to declare and define a static local variable (static T;) so that +// it is leaked so that its destructors are not called at exit. If you need +// thread-safe initialization, use butil/lazy_instance.h instead. +#undef CR_DEFINE_STATIC_LOCAL +#define CR_DEFINE_STATIC_LOCAL(type, name, arguments) \ + static type& name = *new type arguments + +} // namespace butil + +// Convert symbol to string +#ifndef BAIDU_SYMBOLSTR +# define BAIDU_SYMBOLSTR(a) BAIDU_SYMBOLSTR_HELPER(a) +# define BAIDU_SYMBOLSTR_HELPER(a) #a +#endif + +#ifndef BAIDU_TYPEOF +# if defined(BUTIL_CXX11_ENABLED) +# define BAIDU_TYPEOF decltype +# else +# ifdef _MSC_VER +# include +# define BAIDU_TYPEOF BOOST_TYPEOF +# else +# define BAIDU_TYPEOF typeof +# endif +# endif // BUTIL_CXX11_ENABLED +#endif // BAIDU_TYPEOF + +// ptr: the pointer to the member. +// type: the type of the container struct this is embedded in. +// member: the name of the member within the struct. +#ifndef container_of +# define container_of(ptr, type, member) ({ \ + const BAIDU_TYPEOF( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) +#endif + +// DEFINE_SMALL_ARRAY(MyType, my_array, size, 64); +// my_array is typed `MyType*' and as long as `size'. If `size' is not +// greater than 64, the array is allocated on stack. +// +// NOTE: NEVER use ARRAY_SIZE(my_array) which is always 1. + +#if defined(__cplusplus) +namespace butil { +namespace internal { +template struct ArrayDeleter { + ArrayDeleter() : arr(0) {} + ~ArrayDeleter() { delete[] arr; } + T* arr; +}; +}} + +// Many versions of clang does not support variable-length array with non-pod +// types, have to implement the macro differently. +#if !defined(__clang__) +# define DEFINE_SMALL_ARRAY(Tp, name, size, maxsize) \ + Tp* name = 0; \ + const unsigned name##_size = (size); \ + const unsigned name##_stack_array_size = (name##_size <= (maxsize) ? name##_size : 0); \ + Tp name##_stack_array[name##_stack_array_size]; \ + ::butil::internal::ArrayDeleter name##_array_deleter; \ + if (name##_stack_array_size) { \ + name = name##_stack_array; \ + } else { \ + name = new (::std::nothrow) Tp[name##_size]; \ + name##_array_deleter.arr = name; \ + } +#else +// This implementation works for GCC as well, however it needs extra 16 bytes +// for ArrayCtorDtor. +namespace butil { +namespace internal { +template struct ArrayCtorDtor { + ArrayCtorDtor(void* arr, unsigned size) : _arr((T*)arr), _size(size) { + for (unsigned i = 0; i < size; ++i) { new (_arr + i) T; } + } + ~ArrayCtorDtor() { + for (unsigned i = 0; i < _size; ++i) { _arr[i].~T(); } + } +private: + T* _arr; + unsigned _size; +}; +}} +# define DEFINE_SMALL_ARRAY(Tp, name, size, maxsize) \ + Tp* name = 0; \ + const unsigned name##_size = (size); \ + const unsigned name##_stack_array_size = (name##_size <= (maxsize) ? name##_size : 0); \ + char name##_stack_array[sizeof(Tp) * name##_stack_array_size]; \ + ::butil::internal::ArrayDeleter name##_array_deleter; \ + if (name##_stack_array_size) { \ + name = (Tp*)name##_stack_array; \ + } else { \ + name = (Tp*)new (::std::nothrow) char[sizeof(Tp) * name##_size];\ + name##_array_deleter.arr = (char*)name; \ + } \ + const ::butil::internal::ArrayCtorDtor name##_array_ctor_dtor(name, name##_size); +#endif // !defined(__clang__) +#endif // defined(__cplusplus) + +// Put following code somewhere global to run it before main(): +// +// BAIDU_GLOBAL_INIT() +// { +// ... your code ... +// } +// +// Your can: +// * Write any code and access global variables. +// * Use ASSERT_*. +// * Have multiple BAIDU_GLOBAL_INIT() in one scope. +// +// Since the code run in global scope, quit with exit() or similar functions. + +#if defined(__cplusplus) +# define BAIDU_GLOBAL_INIT \ +namespace { /*anonymous namespace */ \ + struct BAIDU_CONCAT(BaiduGlobalInit, __LINE__) { \ + BAIDU_CONCAT(BaiduGlobalInit, __LINE__)() { init(); } \ + void init(); \ + } BAIDU_CONCAT(baidu_global_init_dummy_, __LINE__); \ +} /* anonymous namespace */ \ + void BAIDU_CONCAT(BaiduGlobalInit, __LINE__)::init +#else +# define BAIDU_GLOBAL_INIT \ + static void __attribute__((constructor)) \ + BAIDU_CONCAT(baidu_global_init_, __LINE__) + +#endif // __cplusplus + +#define ASSERT_LOG(fmt, ...) \ + do { \ + std::string log = butil::string_printf(fmt, ## __VA_ARGS__); \ + LOG(FATAL) << log; \ + } while (false) + +// Assert macro that can crash the process to generate a dump. +#define RELEASE_ASSERT(condition) \ + do { \ + if (!(condition)) { \ + ::abort(); \ + } \ + } while (false) + +// Assert macro that can crash the process to generate a dump and +// supply a verbose explanation of what went wrong. +// For example: +// std::vector v; +// ... +// RELEASE_ASSERT_VERBOSE(v.empty(), "v should be empty, but with size=%zu", v.size()); +#define RELEASE_ASSERT_VERBOSE(condition, fmt, ...) \ + do { \ + if (!(condition)) { \ + ASSERT_LOG("Assert failure: " #condition ". " #fmt, ## __VA_ARGS__); \ + ::abort(); \ + } \ + } while (false) + +#endif // BUTIL_MACROS_H_ diff --git a/src/butil/memory/aligned_memory.cc b/src/butil/memory/aligned_memory.cc new file mode 100644 index 0000000..186032f --- /dev/null +++ b/src/butil/memory/aligned_memory.cc @@ -0,0 +1,46 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/memory/aligned_memory.h" + +#include "butil/logging.h" + +#if defined(OS_ANDROID) +#include +#endif + +namespace butil { + +void* AlignedAlloc(size_t size, size_t alignment) { + DCHECK_GT(size, 0U); + DCHECK_EQ(alignment & (alignment - 1), 0U); + DCHECK_EQ(alignment % sizeof(void*), 0U); + void* ptr = NULL; +#if defined(COMPILER_MSVC) + ptr = _aligned_malloc(size, alignment); +// Android technically supports posix_memalign(), but does not expose it in +// the current version of the library headers used by Chrome. Luckily, +// memalign() on Android returns pointers which can safely be used with +// free(), so we can use it instead. Issue filed to document this: +// http://code.google.com/p/android/issues/detail?id=35391 +#elif defined(OS_ANDROID) + ptr = memalign(alignment, size); +#else + if (posix_memalign(&ptr, alignment, size)) + ptr = NULL; +#endif + // Since aligned allocations may fail for non-memory related reasons, force a + // crash if we encounter a failed allocation; maintaining consistent behavior + // with a normal allocation failure in Chrome. + if (!ptr) { + DLOG(ERROR) << "If you crashed here, your aligned allocation is incorrect: " + << "size=" << size << ", alignment=" << alignment; + CHECK(false); + } + // Sanity check alignment just to be safe. + DCHECK_EQ(reinterpret_cast(ptr) & (alignment - 1), 0U); + return ptr; +} + +} // namespace butil diff --git a/src/butil/memory/aligned_memory.h b/src/butil/memory/aligned_memory.h new file mode 100644 index 0000000..fbc2461 --- /dev/null +++ b/src/butil/memory/aligned_memory.h @@ -0,0 +1,126 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// AlignedMemory is a POD type that gives you a portable way to specify static +// or local stack data of a given alignment and size. For example, if you need +// static storage for a class, but you want manual control over when the object +// is constructed and destructed (you don't want static initialization and +// destruction), use AlignedMemory: +// +// static AlignedMemory my_class; +// +// // ... at runtime: +// new(my_class.void_data()) MyClass(); +// +// // ... use it: +// MyClass* mc = my_class.data_as(); +// +// // ... later, to destruct my_class: +// my_class.data_as()->MyClass::~MyClass(); +// +// Alternatively, a runtime sized aligned allocation can be created: +// +// float* my_array = static_cast(AlignedAlloc(size, alignment)); +// +// // ... later, to release the memory: +// AlignedFree(my_array); +// +// Or using scoped_ptr: +// +// scoped_ptr my_array( +// static_cast(AlignedAlloc(size, alignment))); + +#ifndef BUTIL_MEMORY_ALIGNED_MEMORY_H_ +#define BUTIL_MEMORY_ALIGNED_MEMORY_H_ + +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/compiler_specific.h" + +#if defined(COMPILER_MSVC) +#include +#else +#include +#endif + +namespace butil { + +// AlignedMemory is specialized for all supported alignments. +// Make sure we get a compiler error if someone uses an unsupported alignment. +template +struct AlignedMemory {}; + +// std::aligned_storage has been deprecated in C++23, +// because aligned_* are harmful to codebases and should not be used. +// For details, see https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1413r3.pdf +#if __cplusplus >= 201103L +// In most places, use the C++11 keyword "alignas", which is preferred. +#define DECL_ALIGNED_BUFFER(buffer_name, byte_alignment, size) \ + alignas(byte_alignment) uint8_t buffer_name[size] +#else +#define DECL_ALIGNED_BUFFER(buffer_name, byte_alignment, size) \ + ALIGNAS(byte_alignment) uint8_t buffer_name[size] +#endif + +#define BUTIL_DECL_ALIGNED_MEMORY(byte_alignment) \ + template \ + class AlignedMemory { \ + public: \ + DECL_ALIGNED_BUFFER(data_, byte_alignment, Size); \ + void* void_data() { return static_cast(data_); } \ + const void* void_data() const { \ + return static_cast(data_); \ + } \ + template \ + Type* data_as() { return static_cast(void_data()); } \ + template \ + const Type* data_as() const { \ + return static_cast(void_data()); \ + } \ + private: \ + void* operator new(size_t); \ + void operator delete(void*); \ + } + +// Specialization for all alignments is required because MSVC (as of VS 2008) +// does not understand ALIGNAS(ALIGNOF(Type)) or ALIGNAS(template_param). +// Greater than 4096 alignment is not supported by some compilers, so 4096 is +// the maximum specified here. +BUTIL_DECL_ALIGNED_MEMORY(1); +BUTIL_DECL_ALIGNED_MEMORY(2); +BUTIL_DECL_ALIGNED_MEMORY(4); +BUTIL_DECL_ALIGNED_MEMORY(8); +BUTIL_DECL_ALIGNED_MEMORY(16); +BUTIL_DECL_ALIGNED_MEMORY(32); +BUTIL_DECL_ALIGNED_MEMORY(64); +BUTIL_DECL_ALIGNED_MEMORY(128); +BUTIL_DECL_ALIGNED_MEMORY(256); +BUTIL_DECL_ALIGNED_MEMORY(512); +BUTIL_DECL_ALIGNED_MEMORY(1024); +BUTIL_DECL_ALIGNED_MEMORY(2048); +BUTIL_DECL_ALIGNED_MEMORY(4096); + +#undef BUTIL_DECL_ALIGNED_MEMORY + +BUTIL_EXPORT void* AlignedAlloc(size_t size, size_t alignment); + +inline void AlignedFree(void* ptr) { +#if defined(COMPILER_MSVC) + _aligned_free(ptr); +#else + free(ptr); +#endif +} + +// Deleter for use with scoped_ptr. E.g., use as +// scoped_ptr foo; +struct AlignedFreeDeleter { + inline void operator()(void* ptr) const { + AlignedFree(ptr); + } +}; + +} // namespace butil + +#endif // BUTIL_MEMORY_ALIGNED_MEMORY_H_ diff --git a/src/butil/memory/linked_ptr.h b/src/butil/memory/linked_ptr.h new file mode 100644 index 0000000..5773b17 --- /dev/null +++ b/src/butil/memory/linked_ptr.h @@ -0,0 +1,181 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A "smart" pointer type with reference tracking. Every pointer to a +// particular object is kept on a circular linked list. When the last pointer +// to an object is destroyed or reassigned, the object is deleted. +// +// Used properly, this deletes the object when the last reference goes away. +// There are several caveats: +// - Like all reference counting schemes, cycles lead to leaks. +// - Each smart pointer is actually two pointers (8 bytes instead of 4). +// - Every time a pointer is released, the entire list of pointers to that +// object is traversed. This class is therefore NOT SUITABLE when there +// will often be more than two or three pointers to a particular object. +// - References are only tracked as long as linked_ptr<> objects are copied. +// If a linked_ptr<> is converted to a raw pointer and back, BAD THINGS +// will happen (double deletion). +// +// A good use of this class is storing object references in STL containers. +// You can safely put linked_ptr<> in a vector<>. +// Other uses may not be as good. +// +// Note: If you use an incomplete type with linked_ptr<>, the class +// *containing* linked_ptr<> must have a constructor and destructor (even +// if they do nothing!). +// +// Thread Safety: +// A linked_ptr is NOT thread safe. Copying a linked_ptr object is +// effectively a read-write operation. +// +// Alternative: to linked_ptr is shared_ptr, which +// - is also two pointers in size (8 bytes for 32 bit addresses) +// - is thread safe for copying and deletion +// - supports weak_ptrs + +#ifndef BUTIL_MEMORY_LINKED_PTR_H_ +#define BUTIL_MEMORY_LINKED_PTR_H_ + +#include "butil/logging.h" // for CHECK macros + +// This is used internally by all instances of linked_ptr<>. It needs to be +// a non-template class because different types of linked_ptr<> can refer to +// the same object (linked_ptr(obj) vs linked_ptr(obj)). +// So, it needs to be possible for different types of linked_ptr to participate +// in the same circular linked list, so we need a single class type here. +// +// DO NOT USE THIS CLASS DIRECTLY YOURSELF. Use linked_ptr. +class linked_ptr_internal { + public: + // Create a new circle that includes only this instance. + void join_new() { + next_ = this; + } + + // Join an existing circle. + void join(linked_ptr_internal const* ptr) { + next_ = ptr->next_; + ptr->next_ = this; + } + + // Leave whatever circle we're part of. Returns true iff we were the + // last member of the circle. Once this is done, you can join() another. + bool depart() { + if (next_ == this) return true; + linked_ptr_internal const* p = next_; + while (p->next_ != this) p = p->next_; + p->next_ = next_; + return false; + } + + private: + mutable linked_ptr_internal const* next_; +}; + +template +class linked_ptr { + public: + typedef T element_type; + + // Take over ownership of a raw pointer. This should happen as soon as + // possible after the object is created. + explicit linked_ptr(T* ptr = NULL) { capture(ptr); } + ~linked_ptr() { depart(); } + + // Copy an existing linked_ptr<>, adding ourselves to the list of references. + template linked_ptr(linked_ptr const& ptr) { copy(&ptr); } + + linked_ptr(linked_ptr const& ptr) { + DCHECK_NE(&ptr, this); + copy(&ptr); + } + + // Assignment releases the old value and acquires the new. + template linked_ptr& operator=(linked_ptr const& ptr) { + depart(); + copy(&ptr); + return *this; + } + + linked_ptr& operator=(linked_ptr const& ptr) { + if (&ptr != this) { + depart(); + copy(&ptr); + } + return *this; + } + + // Smart pointer members. + void reset(T* ptr = NULL) { + depart(); + capture(ptr); + } + T* get() const { return value_; } + T* operator->() const { return value_; } + T& operator*() const { return *value_; } + // Release ownership of the pointed object and returns it. + // Sole ownership by this linked_ptr object is required. + T* release() { + bool last = link_.depart(); + CHECK(last); + T* v = value_; + value_ = NULL; + return v; + } + + bool operator==(const T* p) const { return value_ == p; } + bool operator!=(const T* p) const { return value_ != p; } + template + bool operator==(linked_ptr const& ptr) const { + return value_ == ptr.get(); + } + template + bool operator!=(linked_ptr const& ptr) const { + return value_ != ptr.get(); + } + + private: + template + friend class linked_ptr; + + T* value_; + linked_ptr_internal link_; + + void depart() { + if (link_.depart()) delete value_; + } + + void capture(T* ptr) { + value_ = ptr; + link_.join_new(); + } + + template void copy(linked_ptr const* ptr) { + value_ = ptr->get(); + if (value_) + link_.join(&ptr->link_); + else + link_.join_new(); + } +}; + +template inline +bool operator==(T* ptr, const linked_ptr& x) { + return ptr == x.get(); +} + +template inline +bool operator!=(T* ptr, const linked_ptr& x) { + return ptr != x.get(); +} + +// A function to convert T* into linked_ptr +// Doing e.g. make_linked_ptr(new FooBarBaz(arg)) is a shorter notation +// for linked_ptr >(new FooBarBaz(arg)) +template +linked_ptr make_linked_ptr(T* ptr) { + return linked_ptr(ptr); +} + +#endif // BUTIL_MEMORY_LINKED_PTR_H_ diff --git a/src/butil/memory/manual_constructor.h b/src/butil/memory/manual_constructor.h new file mode 100644 index 0000000..111d5ec --- /dev/null +++ b/src/butil/memory/manual_constructor.h @@ -0,0 +1,78 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ManualConstructor statically-allocates space in which to store some +// object, but does not initialize it. You can then call the constructor +// and destructor for the object yourself as you see fit. This is useful +// for memory management optimizations, where you want to initialize and +// destroy an object multiple times but only allocate it once. +// +// (When I say ManualConstructor statically allocates space, I mean that +// the ManualConstructor object itself is forced to be the right size.) +// +// For example usage, check out butil/containers/small_map.h. + +#ifndef BUTIL_MEMORY_MANUAL_CONSTRUCTOR_H_ +#define BUTIL_MEMORY_MANUAL_CONSTRUCTOR_H_ + +#include + +#include "butil/memory/aligned_memory.h" + +namespace butil { + +template +class ManualConstructor { +public: + // No constructor or destructor because one of the most useful uses of + // this class is as part of a union, and members of a union cannot have + // constructors or destructors. And, anyway, the whole point of this + // class is to bypass these. + + // Support users creating arrays of ManualConstructor<>s. This ensures that + // the array itself has the correct alignment. + static void* operator new[](size_t size) { +#if defined(COMPILER_MSVC) + return AlignedAlloc(size, __alignof(Type)); +#else + return AlignedAlloc(size, __alignof__(Type)); +#endif + } + + static void operator delete[](void* mem) { + AlignedFree(mem); + } + + Type* get() { + return _space.template data_as(); + } + + const Type* get() const { + return _space.template data_as(); + } + + Type* operator->() { return get(); } + const Type* operator->() const { return get(); } + + Type& operator*() { return *get(); } + const Type& operator*() const { return *get(); } + + template + void Init(Args&&... args) { + new (_space.void_data()) Type(std::forward(args)...); + } + + void Destroy() { get()->~Type(); } + + private: +#if defined(COMPILER_MSVC) + AlignedMemory _space; +#else + AlignedMemory _space; +#endif +}; + +} // namespace butil + +#endif // BUTIL_MEMORY_MANUAL_CONSTRUCTOR_H_ diff --git a/src/butil/memory/raw_scoped_refptr_mismatch_checker.h b/src/butil/memory/raw_scoped_refptr_mismatch_checker.h new file mode 100644 index 0000000..c2c324f --- /dev/null +++ b/src/butil/memory/raw_scoped_refptr_mismatch_checker.h @@ -0,0 +1,128 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_MEMORY_RAW_SCOPED_REFPTR_MISMATCH_CHECKER_H_ +#define BUTIL_MEMORY_RAW_SCOPED_REFPTR_MISMATCH_CHECKER_H_ + +#include "butil/memory/ref_counted.h" +#include "butil/type_traits.h" +#include "butil/build_config.h" + +// It is dangerous to post a task with a T* argument where T is a subtype of +// RefCounted(Base|ThreadSafeBase), since by the time the parameter is used, the +// object may already have been deleted since it was not held with a +// scoped_refptr. Example: http://crbug.com/27191 +// The following set of traits are designed to generate a compile error +// whenever this antipattern is attempted. + +namespace butil { + +// This is a base internal implementation file used by task.h and callback.h. +// Not for public consumption, so we wrap it in namespace internal. +namespace internal { + +template +struct NeedsScopedRefptrButGetsRawPtr { +#if defined(OS_WIN) + enum { + value = butil::false_type::value + }; +#else + enum { + // Human readable translation: you needed to be a scoped_refptr if you are a + // raw pointer type and are convertible to a RefCounted(Base|ThreadSafeBase) + // type. + value = (is_pointer::value && + (is_convertible::value || + is_convertible::value)) + }; +#endif +}; + +template +struct ParamsUseScopedRefptrCorrectly { + enum { value = 0 }; +}; + +template <> +struct ParamsUseScopedRefptrCorrectly { + enum { value = 1 }; +}; + +template +struct ParamsUseScopedRefptrCorrectly > { + enum { value = !NeedsScopedRefptrButGetsRawPtr::value }; +}; + +template +struct ParamsUseScopedRefptrCorrectly > { + enum { value = !(NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value) }; +}; + +template +struct ParamsUseScopedRefptrCorrectly > { + enum { value = !(NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value) }; +}; + +template +struct ParamsUseScopedRefptrCorrectly > { + enum { value = !(NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value) }; +}; + +template +struct ParamsUseScopedRefptrCorrectly > { + enum { value = !(NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value) }; +}; + +template +struct ParamsUseScopedRefptrCorrectly > { + enum { value = !(NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value) }; +}; + +template +struct ParamsUseScopedRefptrCorrectly > { + enum { value = !(NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value) }; +}; + +template +struct ParamsUseScopedRefptrCorrectly > { + enum { value = !(NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value || + NeedsScopedRefptrButGetsRawPtr::value) }; +}; + +} // namespace internal + +} // namespace butil + +#endif // BUTIL_MEMORY_RAW_SCOPED_REFPTR_MISMATCH_CHECKER_H_ diff --git a/src/butil/memory/ref_counted.cc b/src/butil/memory/ref_counted.cc new file mode 100644 index 0000000..0eb1c9a --- /dev/null +++ b/src/butil/memory/ref_counted.cc @@ -0,0 +1,53 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/memory/ref_counted.h" +#include "butil/threading/thread_collision_warner.h" + +namespace butil { + +namespace subtle { + +bool RefCountedThreadSafeBase::HasOneRef() const { + return AtomicRefCountIsOne( + &const_cast(this)->ref_count_); +} + +RefCountedThreadSafeBase::RefCountedThreadSafeBase() : ref_count_(0) { +#ifndef NDEBUG + in_dtor_ = false; +#endif +} + +RefCountedThreadSafeBase::~RefCountedThreadSafeBase() { +#ifndef NDEBUG + DCHECK(in_dtor_) << "RefCountedThreadSafe object deleted without " + "calling Release()"; +#endif +} + +void RefCountedThreadSafeBase::AddRef() const { +#ifndef NDEBUG + DCHECK(!in_dtor_); +#endif + AtomicRefCountInc(&ref_count_); +} + +bool RefCountedThreadSafeBase::Release() const { +#ifndef NDEBUG + DCHECK(!in_dtor_); + DCHECK(!AtomicRefCountIsZero(&ref_count_)); +#endif + if (!AtomicRefCountDec(&ref_count_)) { +#ifndef NDEBUG + in_dtor_ = true; +#endif + return true; + } + return false; +} + +} // namespace subtle + +} // namespace butil diff --git a/src/butil/memory/ref_counted.h b/src/butil/memory/ref_counted.h new file mode 100644 index 0000000..fb8bc72 --- /dev/null +++ b/src/butil/memory/ref_counted.h @@ -0,0 +1,363 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_MEMORY_REF_COUNTED_H_ +#define BUTIL_MEMORY_REF_COUNTED_H_ + +#include + +#include "butil/atomic_ref_count.h" +#include "butil/base_export.h" +#include "butil/compiler_specific.h" +#ifndef NDEBUG +#include "butil/logging.h" +#endif +#include "butil/threading/thread_collision_warner.h" + +namespace butil { + +namespace subtle { + +class BUTIL_EXPORT RefCountedBase { + public: + bool HasOneRef() const { return ref_count_ == 1; } + + protected: + RefCountedBase() + : ref_count_(0) + , in_dtor_(false) + { + } + + ~RefCountedBase() { + #ifndef NDEBUG + DCHECK(in_dtor_) << "RefCounted object deleted without calling Release()"; + #endif + } + + + void AddRef() const { + // TODO(maruel): Add back once it doesn't assert 500 times/sec. + // Current thread books the critical section "AddRelease" + // without release it. + // DFAKE_SCOPED_LOCK_THREAD_LOCKED(add_release_); + #ifndef NDEBUG + DCHECK(!in_dtor_); + #endif + ++ref_count_; + } + + // Returns true if the object should self-delete. + bool Release() const { + // TODO(maruel): Add back once it doesn't assert 500 times/sec. + // Current thread books the critical section "AddRelease" + // without release it. + // DFAKE_SCOPED_LOCK_THREAD_LOCKED(add_release_); + #ifndef NDEBUG + DCHECK(!in_dtor_); + #endif + if (--ref_count_ == 0) { + #ifndef NDEBUG + in_dtor_ = true; + #endif + return true; + } + return false; + } + + private: + mutable int ref_count_; +#if defined(__clang__) + mutable bool ALLOW_UNUSED in_dtor_; +#else + mutable bool in_dtor_; +#endif + DFAKE_MUTEX(add_release_); + + DISALLOW_COPY_AND_ASSIGN(RefCountedBase); +}; + +class BUTIL_EXPORT RefCountedThreadSafeBase { + public: + bool HasOneRef() const; + + protected: + RefCountedThreadSafeBase(); + ~RefCountedThreadSafeBase(); + + void AddRef() const; + + // Returns true if the object should self-delete. + bool Release() const; + + private: + mutable AtomicRefCount ref_count_; +#if defined(__clang__) + mutable bool ALLOW_UNUSED in_dtor_; +#else + mutable bool in_dtor_; +#endif + + DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafeBase); +}; + +} // namespace subtle + +// +// A base class for reference counted classes. Otherwise, known as a cheap +// knock-off of WebKit's RefCounted class. To use this guy just extend your +// class from it like so: +// +// class MyFoo : public butil::RefCounted { +// ... +// private: +// friend class butil::RefCounted; +// ~MyFoo(); +// }; +// +// You should always make your destructor private, to avoid any code deleting +// the object accidently while there are references to it. +template +class RefCounted : public subtle::RefCountedBase { + public: + RefCounted() {} + + void AddRef() const { + subtle::RefCountedBase::AddRef(); + } + + void Release() const { + if (subtle::RefCountedBase::Release()) { + delete static_cast(this); + } + } + + protected: + ~RefCounted() {} + + private: + DISALLOW_COPY_AND_ASSIGN(RefCounted); +}; + +// Forward declaration. +template class RefCountedThreadSafe; + +// Default traits for RefCountedThreadSafe. Deletes the object when its ref +// count reaches 0. Overload to delete it on a different thread etc. +template +struct DefaultRefCountedThreadSafeTraits { + static void Destruct(const T* x) { + // Delete through RefCountedThreadSafe to make child classes only need to be + // friend with RefCountedThreadSafe instead of this struct, which is an + // implementation detail. + RefCountedThreadSafe::DeleteInternal(x); + } +}; + +// +// A thread-safe variant of RefCounted +// +// class MyFoo : public butil::RefCountedThreadSafe { +// ... +// }; +// +// If you're using the default trait, then you should add compile time +// asserts that no one else is deleting your object. i.e. +// private: +// friend class butil::RefCountedThreadSafe; +// ~MyFoo(); +template > +class RefCountedThreadSafe : public subtle::RefCountedThreadSafeBase { + public: + RefCountedThreadSafe() {} + + void AddRef() const { + subtle::RefCountedThreadSafeBase::AddRef(); + } + + void Release() const { + if (subtle::RefCountedThreadSafeBase::Release()) { + Traits::Destruct(static_cast(this)); + } + } + + protected: + ~RefCountedThreadSafe() {} + + private: + friend struct DefaultRefCountedThreadSafeTraits; + static void DeleteInternal(const T* x) { delete x; } + + DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafe); +}; + +// +// A thread-safe wrapper for some piece of data so we can place other +// things in scoped_refptrs<>. +// +template +class RefCountedData + : public butil::RefCountedThreadSafe< butil::RefCountedData > { + public: + RefCountedData() : data() {} + RefCountedData(const T& in_value) : data(in_value) {} + + T data; + + private: + friend class butil::RefCountedThreadSafe >; + ~RefCountedData() {} +}; + +} // namespace butil + +// +// A smart pointer class for reference counted objects. Use this class instead +// of calling AddRef and Release manually on a reference counted object to +// avoid common memory leaks caused by forgetting to Release an object +// reference. Sample usage: +// +// class MyFoo : public RefCounted { +// ... +// }; +// +// void some_function() { +// scoped_refptr foo = new MyFoo(); +// foo->Method(param); +// // |foo| is released when this function returns +// } +// +// void some_other_function() { +// scoped_refptr foo = new MyFoo(); +// ... +// foo = NULL; // explicitly releases |foo| +// ... +// if (foo) +// foo->Method(param); +// } +// +// The above examples show how scoped_refptr acts like a pointer to T. +// Given two scoped_refptr classes, it is also possible to exchange +// references between the two objects, like so: +// +// { +// scoped_refptr a = new MyFoo(); +// scoped_refptr b; +// +// b.swap(a); +// // now, |b| references the MyFoo object, and |a| references NULL. +// } +// +// To make both |a| and |b| in the above example reference the same MyFoo +// object, simply use the assignment operator: +// +// { +// scoped_refptr a = new MyFoo(); +// scoped_refptr b; +// +// b = a; +// // now, |a| and |b| each own a reference to the same MyFoo object. +// } +// +template +class scoped_refptr { + public: + typedef T element_type; + + scoped_refptr() : ptr_(NULL) { + } + + scoped_refptr(T* p) : ptr_(p) { + if (ptr_) + ptr_->AddRef(); + } + + scoped_refptr(const scoped_refptr& r) : ptr_(r.ptr_) { + if (ptr_) + ptr_->AddRef(); + } + + template + scoped_refptr(const scoped_refptr& r) : ptr_(r.get()) { + if (ptr_) + ptr_->AddRef(); + } + + scoped_refptr(scoped_refptr&& r) noexcept { + ptr_ = r.ptr_; + r.ptr_ = nullptr; + } + + template + scoped_refptr(scoped_refptr&& r) noexcept { + ptr_ = r.ptr_; + r.ptr_ = nullptr; + } + + ~scoped_refptr() { + if (ptr_) + ptr_->Release(); + } + + T* get() const { return ptr_; } + + // Allow scoped_refptr to be used in boolean expression + // and comparison operations. + operator T*() const { return ptr_; } + + T* operator->() const { + assert(ptr_ != NULL); + return ptr_; + } + + scoped_refptr& operator=(T* p) { + // AddRef first so that self assignment should work + if (p) + p->AddRef(); + T* old_ptr = ptr_; + ptr_ = p; + if (old_ptr) + old_ptr->Release(); + return *this; + } + + scoped_refptr& operator=(const scoped_refptr& r) { + return *this = r.ptr_; + } + + template + scoped_refptr& operator=(const scoped_refptr& r) { + return *this = r.get(); + } + + void swap(T** pp) { + T* p = ptr_; + ptr_ = *pp; + *pp = p; + } + + void swap(scoped_refptr& r) { + swap(&r.ptr_); + } + + // Release ownership of ptr_, keeping its reference counter unchanged. + T* release() WARN_UNUSED_RESULT { + T* saved_ptr = NULL; + swap(&saved_ptr); + return saved_ptr; + } + + protected: + T* ptr_; +}; + +// Handy utility for creating a scoped_refptr out of a T* explicitly without +// having to retype all the template arguments +template +scoped_refptr make_scoped_refptr(T* t) { + return scoped_refptr(t); +} + +#endif // BUTIL_MEMORY_REF_COUNTED_H_ diff --git a/src/butil/memory/ref_counted_memory.cc b/src/butil/memory/ref_counted_memory.cc new file mode 100644 index 0000000..0ffd04f --- /dev/null +++ b/src/butil/memory/ref_counted_memory.cc @@ -0,0 +1,100 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/memory/ref_counted_memory.h" + +#include + +#include "butil/logging.h" + +namespace butil { + +bool RefCountedMemory::Equals( + const scoped_refptr& other) const { + return other.get() && + size() == other->size() && + (memcmp(front(), other->front(), size()) == 0); +} + +RefCountedMemory::RefCountedMemory() {} + +RefCountedMemory::~RefCountedMemory() {} + +const unsigned char* RefCountedStaticMemory::front() const { + return data_; +} + +size_t RefCountedStaticMemory::size() const { + return length_; +} + +RefCountedStaticMemory::~RefCountedStaticMemory() {} + +RefCountedBytes::RefCountedBytes() {} + +RefCountedBytes::RefCountedBytes(const std::vector& initializer) + : data_(initializer) { +} + +RefCountedBytes::RefCountedBytes(const unsigned char* p, size_t size) + : data_(p, p + size) {} + +RefCountedBytes* RefCountedBytes::TakeVector( + std::vector* to_destroy) { + RefCountedBytes* bytes = new RefCountedBytes; + bytes->data_.swap(*to_destroy); + return bytes; +} + +const unsigned char* RefCountedBytes::front() const { + // STL will assert if we do front() on an empty vector, but calling code + // expects a NULL. + return size() ? &data_.front() : NULL; +} + +size_t RefCountedBytes::size() const { + return data_.size(); +} + +RefCountedBytes::~RefCountedBytes() {} + +RefCountedString::RefCountedString() {} + +RefCountedString::~RefCountedString() {} + +// static +RefCountedString* RefCountedString::TakeString(std::string* to_destroy) { + RefCountedString* self = new RefCountedString; + to_destroy->swap(self->data_); + return self; +} + +const unsigned char* RefCountedString::front() const { + return data_.empty() ? NULL : + reinterpret_cast(data_.data()); +} + +size_t RefCountedString::size() const { + return data_.size(); +} + +RefCountedMallocedMemory::RefCountedMallocedMemory( + void* data, size_t length) + : data_(reinterpret_cast(data)), length_(length) { + DCHECK(data || length == 0); +} + +const unsigned char* RefCountedMallocedMemory::front() const { + return length_ ? data_ : NULL; +} + +size_t RefCountedMallocedMemory::size() const { + return length_; +} + +RefCountedMallocedMemory::~RefCountedMallocedMemory() { + free(data_); +} + +} // namespace butil diff --git a/src/butil/memory/ref_counted_memory.h b/src/butil/memory/ref_counted_memory.h new file mode 100644 index 0000000..1d7b928 --- /dev/null +++ b/src/butil/memory/ref_counted_memory.h @@ -0,0 +1,146 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_MEMORY_REF_COUNTED_MEMORY_H_ +#define BUTIL_MEMORY_REF_COUNTED_MEMORY_H_ + +#include +#include + +#include "butil/base_export.h" +#include "butil/compiler_specific.h" +#include "butil/memory/ref_counted.h" + +namespace butil { + +// A generic interface to memory. This object is reference counted because one +// of its two subclasses own the data they carry, and we need to have +// heterogeneous containers of these two types of memory. +class BUTIL_EXPORT RefCountedMemory + : public butil::RefCountedThreadSafe { + public: + // Retrieves a pointer to the beginning of the data we point to. If the data + // is empty, this will return NULL. + virtual const unsigned char* front() const = 0; + + // Size of the memory pointed to. + virtual size_t size() const = 0; + + // Returns true if |other| is byte for byte equal. + bool Equals(const scoped_refptr& other) const; + + // Handy method to simplify calling front() with a reinterpret_cast. + template const T* front_as() const { + return reinterpret_cast(front()); + } + + protected: + friend class butil::RefCountedThreadSafe; + RefCountedMemory(); + virtual ~RefCountedMemory(); +}; + +// An implementation of RefCountedMemory, where the ref counting does not +// matter. +class BUTIL_EXPORT RefCountedStaticMemory : public RefCountedMemory { + public: + RefCountedStaticMemory() + : data_(NULL), length_(0) {} + RefCountedStaticMemory(const void* data, size_t length) + : data_(static_cast(length ? data : NULL)), + length_(length) {} + + // Overridden from RefCountedMemory: + virtual const unsigned char* front() const OVERRIDE; + virtual size_t size() const OVERRIDE; + + private: + virtual ~RefCountedStaticMemory(); + + const unsigned char* data_; + size_t length_; + + DISALLOW_COPY_AND_ASSIGN(RefCountedStaticMemory); +}; + +// An implementation of RefCountedMemory, where we own the data in a vector. +class BUTIL_EXPORT RefCountedBytes : public RefCountedMemory { + public: + RefCountedBytes(); + + // Constructs a RefCountedBytes object by _copying_ from |initializer|. + explicit RefCountedBytes(const std::vector& initializer); + + // Constructs a RefCountedBytes object by copying |size| bytes from |p|. + RefCountedBytes(const unsigned char* p, size_t size); + + // Constructs a RefCountedBytes object by performing a swap. (To non + // destructively build a RefCountedBytes, use the constructor that takes a + // vector.) + static RefCountedBytes* TakeVector(std::vector* to_destroy); + + // Overridden from RefCountedMemory: + virtual const unsigned char* front() const OVERRIDE; + virtual size_t size() const OVERRIDE; + + const std::vector& data() const { return data_; } + std::vector& data() { return data_; } + + private: + virtual ~RefCountedBytes(); + + std::vector data_; + + DISALLOW_COPY_AND_ASSIGN(RefCountedBytes); +}; + +// An implementation of RefCountedMemory, where the bytes are stored in an STL +// string. Use this if your data naturally arrives in that format. +class BUTIL_EXPORT RefCountedString : public RefCountedMemory { + public: + RefCountedString(); + + // Constructs a RefCountedString object by performing a swap. (To non + // destructively build a RefCountedString, use the default constructor and + // copy into object->data()). + static RefCountedString* TakeString(std::string* to_destroy); + + // Overridden from RefCountedMemory: + virtual const unsigned char* front() const OVERRIDE; + virtual size_t size() const OVERRIDE; + + const std::string& data() const { return data_; } + std::string& data() { return data_; } + + private: + virtual ~RefCountedString(); + + std::string data_; + + DISALLOW_COPY_AND_ASSIGN(RefCountedString); +}; + +// An implementation of RefCountedMemory that holds a chunk of memory +// previously allocated with malloc or calloc, and that therefore must be freed +// using free(). +class BUTIL_EXPORT RefCountedMallocedMemory : public butil::RefCountedMemory { + public: + RefCountedMallocedMemory(void* data, size_t length); + + // Overridden from RefCountedMemory: + virtual const unsigned char* front() const OVERRIDE; + virtual size_t size() const OVERRIDE; + + private: + virtual ~RefCountedMallocedMemory(); + + unsigned char* data_; + size_t length_; + + DISALLOW_COPY_AND_ASSIGN(RefCountedMallocedMemory); +}; + +} // namespace butil + +#endif // BUTIL_MEMORY_REF_COUNTED_MEMORY_H_ diff --git a/src/butil/memory/scope_guard.h b/src/butil/memory/scope_guard.h new file mode 100644 index 0000000..377819b --- /dev/null +++ b/src/butil/memory/scope_guard.h @@ -0,0 +1,109 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BUTIL_SCOPED_GUARD_H +#define BUTIL_SCOPED_GUARD_H + +#include "butil/type_traits.h" +#include "butil/macros.h" + +namespace butil { + +template::value>> +class ScopeGuard; + +template +ScopeGuard MakeScopeGuard(Callback&& callback) noexcept; + +// ScopeGuard is a simple implementation to guarantee that +// a function is executed upon leaving the current scope. +template +class ScopeGuard { +public: + ScopeGuard(ScopeGuard&& other) noexcept + : _callback(std::move(other._callback)) + , _dismiss(other._dismiss) { + other.dismiss(); + } + + ~ScopeGuard() noexcept { + if(!_dismiss) { + _callback(); + } + } + + void dismiss() noexcept { + _dismiss = true; + } + + ScopeGuard() = delete; + ScopeGuard(const ScopeGuard&) = delete; + ScopeGuard& operator=(const ScopeGuard&) = delete; + ScopeGuard& operator=(ScopeGuard&&) = delete; + +private: +// Only MakeScopeGuard and move constructor can create ScopeGuard. +friend ScopeGuard MakeScopeGuard(Callback&& callback) noexcept; + + explicit ScopeGuard(Callback&& callback) noexcept + :_callback(std::forward(callback)) + , _dismiss(false) {} + +private: + Callback _callback; + bool _dismiss; +}; + +// The MakeScopeGuard() function is used to create a new ScopeGuard object. +// It can be instantiated with a lambda function, a std::function, +// a functor, or a void(*)() function pointer. +template +ScopeGuard MakeScopeGuard(Callback&& callback) noexcept { + return ScopeGuard{ std::forward(callback)}; +} + +namespace internal { +// for BRPC_SCOPE_EXIT. +enum class ScopeExitHelper {}; + +template +ScopeGuard +operator+(ScopeExitHelper, Callback&& callback) { + return MakeScopeGuard(std::forward(callback)); +} +} // namespace internal +} // namespace butil + +#define BRPC_ANONYMOUS_VARIABLE(prefix) BAIDU_CONCAT(prefix, __COUNTER__) + +// The code in the braces of BRPC_SCOPE_EXIT always executes at the end of the scope. +// Variables used within BRPC_SCOPE_EXIT are captured by reference. +// Example: +// int fd = open(...); +// BRPC_SCOPE_EXIT { +// close(fd); +// }; +// use fd ... +// +#define BRPC_SCOPE_EXIT \ + auto BRPC_ANONYMOUS_VARIABLE(SCOPE_EXIT) = \ + ::butil::internal::ScopeExitHelper() + [&]() noexcept + +#define BUTIL_SCOPE_EXIT BRPC_SCOPE_EXIT + +#endif // BUTIL_SCOPED_GUARD_H diff --git a/src/butil/memory/scoped_open_process.h b/src/butil/memory/scoped_open_process.h new file mode 100644 index 0000000..a5a1bfd --- /dev/null +++ b/src/butil/memory/scoped_open_process.h @@ -0,0 +1,48 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_MEMORY_SCOPED_OPEN_PROCESS_H_ +#define BUTIL_MEMORY_SCOPED_OPEN_PROCESS_H_ + +#include "butil/process/process_handle.h" + +namespace butil { + +// A class that opens a process from its process id and closes it when the +// instance goes out of scope. +class ScopedOpenProcess { + public: + ScopedOpenProcess() : handle_(kNullProcessHandle) { + } + + // Automatically close the process. + ~ScopedOpenProcess() { + Close(); + } + + // Open a new process by pid. Closes any previously opened process (even if + // opening the new one fails). + bool Open(ProcessId pid) { + Close(); + return OpenProcessHandle(pid, &handle_); + } + + // Close the previously opened process. + void Close() { + if (handle_ == kNullProcessHandle) + return; + + CloseProcessHandle(handle_); + handle_ = kNullProcessHandle; + } + + ProcessHandle handle() const { return handle_; } + + private: + ProcessHandle handle_; + DISALLOW_COPY_AND_ASSIGN(ScopedOpenProcess); +}; +} // namespace butil + +#endif // BUTIL_MEMORY_SCOPED_OPEN_PROCESS_H_ diff --git a/src/butil/memory/scoped_policy.h b/src/butil/memory/scoped_policy.h new file mode 100644 index 0000000..79ef4ee --- /dev/null +++ b/src/butil/memory/scoped_policy.h @@ -0,0 +1,25 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_MEMORY_SCOPED_POLICY_H_ +#define BUTIL_MEMORY_SCOPED_POLICY_H_ + +namespace butil { +namespace scoped_policy { + +// Defines the ownership policy for a scoped object. +enum OwnershipPolicy { + // The scoped object takes ownership of an object by taking over an existing + // ownership claim. + ASSUME, + + // The scoped object will retain the the object and any initial ownership is + // not changed. + RETAIN +}; + +} // namespace scoped_policy +} // namespace butil + +#endif // BUTIL_MEMORY_SCOPED_POLICY_H_ diff --git a/src/butil/memory/scoped_ptr.h b/src/butil/memory/scoped_ptr.h new file mode 100644 index 0000000..0f435cd --- /dev/null +++ b/src/butil/memory/scoped_ptr.h @@ -0,0 +1,580 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Scopers help you manage ownership of a pointer, helping you easily manage a +// pointer within a scope, and automatically destroying the pointer at the end +// of a scope. There are two main classes you will use, which correspond to the +// operators new/delete and new[]/delete[]. +// +// Example usage (scoped_ptr): +// { +// scoped_ptr foo(new Foo("wee")); +// } // foo goes out of scope, releasing the pointer with it. +// +// { +// scoped_ptr foo; // No pointer managed. +// foo.reset(new Foo("wee")); // Now a pointer is managed. +// foo.reset(new Foo("wee2")); // Foo("wee") was destroyed. +// foo.reset(new Foo("wee3")); // Foo("wee2") was destroyed. +// foo->Method(); // Foo::Method() called. +// foo.get()->Method(); // Foo::Method() called. +// SomeFunc(foo.release()); // SomeFunc takes ownership, foo no longer +// // manages a pointer. +// foo.reset(new Foo("wee4")); // foo manages a pointer again. +// foo.reset(); // Foo("wee4") destroyed, foo no longer +// // manages a pointer. +// } // foo wasn't managing a pointer, so nothing was destroyed. +// +// Example usage (scoped_ptr): +// { +// scoped_ptr foo(new Foo[100]); +// foo.get()->Method(); // Foo::Method on the 0th element. +// foo[10].Method(); // Foo::Method on the 10th element. +// } +// +// These scopers also implement part of the functionality of C++11 unique_ptr +// in that they are "movable but not copyable." You can use the scopers in +// the parameter and return types of functions to signify ownership transfer +// in to and out of a function. When calling a function that has a scoper +// as the argument type, it must be called with the result of an analogous +// scoper's Pass() function or another function that generates a temporary; +// passing by copy will NOT work. Here is an example using scoped_ptr: +// +// void TakesOwnership(scoped_ptr arg) { +// // Do something with arg +// } +// scoped_ptr CreateFoo() { +// // No need for calling Pass() because we are constructing a temporary +// // for the return value. +// return scoped_ptr(new Foo("new")); +// } +// scoped_ptr PassThru(scoped_ptr arg) { +// return arg.Pass(); +// } +// +// { +// scoped_ptr ptr(new Foo("yay")); // ptr manages Foo("yay"). +// TakesOwnership(ptr.Pass()); // ptr no longer owns Foo("yay"). +// scoped_ptr ptr2 = CreateFoo(); // ptr2 owns the return Foo. +// scoped_ptr ptr3 = // ptr3 now owns what was in ptr2. +// PassThru(ptr2.Pass()); // ptr2 is correspondingly NULL. +// } +// +// Notice that if you do not call Pass() when returning from PassThru(), or +// when invoking TakesOwnership(), the code will not compile because scopers +// are not copyable; they only implement move semantics which require calling +// the Pass() function to signify a destructive transfer of state. CreateFoo() +// is different though because we are constructing a temporary on the return +// line and thus can avoid needing to call Pass(). +// +// Pass() properly handles upcast in initialization, i.e. you can use a +// scoped_ptr to initialize a scoped_ptr: +// +// scoped_ptr foo(new Foo()); +// scoped_ptr parent(foo.Pass()); +// +// PassAs<>() should be used to upcast return value in return statement: +// +// scoped_ptr CreateFoo() { +// scoped_ptr result(new FooChild()); +// return result.PassAs(); +// } +// +// Note that PassAs<>() is implemented only for scoped_ptr, but not for +// scoped_ptr. This is because casting array pointers may not be safe. + +#ifndef BUTIL_MEMORY_SCOPED_PTR_H_ +#define BUTIL_MEMORY_SCOPED_PTR_H_ + +// This is an implementation designed to match the anticipated future TR2 +// implementation of the scoped_ptr class. + +#include +#include +#include + +#include // For std::swap(). + +#include "butil/basictypes.h" +#include "butil/compiler_specific.h" +#include "butil/move.h" +#include "butil/type_traits.h" + +namespace butil { + +namespace subtle { +class RefCountedBase; +class RefCountedThreadSafeBase; +} // namespace subtle + +// Function object which deletes its parameter, which must be a pointer. +// If C is an array type, invokes 'delete[]' on the parameter; otherwise, +// invokes 'delete'. The default deleter for scoped_ptr. +template +struct DefaultDeleter { + DefaultDeleter() {} + template DefaultDeleter(const DefaultDeleter&) { + // IMPLEMENTATION NOTE: C++11 20.7.1.1.2p2 only provides this constructor + // if U* is implicitly convertible to T* and U is not an array type. + // + // Correct implementation should use SFINAE to disable this + // constructor. However, since there are no other 1-argument constructors, + // using a COMPILE_ASSERT() based on is_convertible<> and requiring + // complete types is simpler and will cause compile failures for equivalent + // misuses. + // + // Note, the is_convertible check also ensures that U is not an + // array. T is guaranteed to be a non-array, so any U* where U is an array + // cannot convert to T*. + enum { T_must_be_complete = sizeof(T) }; + enum { U_must_be_complete = sizeof(U) }; + COMPILE_ASSERT((butil::is_convertible::value), + U_ptr_must_implicitly_convert_to_T_ptr); + } + inline void operator()(T* ptr) const { + enum { type_must_be_complete = sizeof(T) }; + delete ptr; + } +}; + +// Specialization of DefaultDeleter for array types. +template +struct DefaultDeleter { + inline void operator()(T* ptr) const { + enum { type_must_be_complete = sizeof(T) }; + delete[] ptr; + } + + private: + // Disable this operator for any U != T because it is undefined to execute + // an array delete when the static type of the array mismatches the dynamic + // type. + // + // References: + // C++98 [expr.delete]p3 + // http://cplusplus.github.com/LWG/lwg-defects.html#938 + template void operator()(U* array) const; +}; + +template +struct DefaultDeleter { + // Never allow someone to declare something like scoped_ptr. + COMPILE_ASSERT(sizeof(T) == -1, do_not_use_array_with_size_as_type); +}; + +// Function object which invokes 'free' on its parameter, which must be +// a pointer. Can be used to store malloc-allocated pointers in scoped_ptr: +// +// scoped_ptr foo_ptr( +// static_cast(malloc(sizeof(int)))); +struct FreeDeleter { + inline void operator()(void* ptr) const { + free(ptr); + } +}; + +namespace internal { + +template struct IsNotRefCounted { + enum { + value = !butil::is_convertible::value && + !butil::is_convertible:: + value + }; +}; + +// Minimal implementation of the core logic of scoped_ptr, suitable for +// reuse in both scoped_ptr and its specializations. +template +class scoped_ptr_impl { + public: + explicit scoped_ptr_impl(T* p) : data_(p) { } + + // Initializer for deleters that have data parameters. + scoped_ptr_impl(T* p, const D& d) : data_(p, d) {} + + // Templated constructor that destructively takes the value from another + // scoped_ptr_impl. + template + scoped_ptr_impl(scoped_ptr_impl* other) + : data_(other->release(), other->get_deleter()) { + // We do not support move-only deleters. We could modify our move + // emulation to have butil::subtle::move() and butil::subtle::forward() + // functions that are imperfect emulations of their C++11 equivalents, + // but until there's a requirement, just assume deleters are copyable. + } + + template + void TakeState(scoped_ptr_impl* other) { + // See comment in templated constructor above regarding lack of support + // for move-only deleters. + reset(other->release()); + get_deleter() = other->get_deleter(); + } + + ~scoped_ptr_impl() { + if (data_.ptr != NULL) { + // Not using get_deleter() saves one function call in non-optimized + // builds. + static_cast(data_)(data_.ptr); + } + } + + void reset(T* p) { + // This is a self-reset, which is no longer allowed: http://crbug.com/162971 + RELEASE_ASSERT(p == NULL || p != data_.ptr); + + // Note that running data_.ptr = p can lead to undefined behavior if + // get_deleter()(get()) deletes this. In order to prevent this, reset() + // should update the stored pointer before deleting its old value. + // + // However, changing reset() to use that behavior may cause current code to + // break in unexpected ways. If the destruction of the owned object + // dereferences the scoped_ptr when it is destroyed by a call to reset(), + // then it will incorrectly dispatch calls to |p| rather than the original + // value of |data_.ptr|. + // + // During the transition period, set the stored pointer to NULL while + // deleting the object. Eventually, this safety check will be removed to + // prevent the scenario initially described from occuring and + // http://crbug.com/176091 can be closed. + T* old = data_.ptr; + data_.ptr = NULL; + if (old != NULL) + static_cast(data_)(old); + data_.ptr = p; + } + + T* get() const { return data_.ptr; } + + D& get_deleter() { return data_; } + const D& get_deleter() const { return data_; } + + void swap(scoped_ptr_impl& p2) { + // Standard swap idiom: 'using std::swap' ensures that std::swap is + // present in the overload set, but we call swap unqualified so that + // any more-specific overloads can be used, if available. + using std::swap; + swap(static_cast(data_), static_cast(p2.data_)); + swap(data_.ptr, p2.data_.ptr); + } + + T* release() { + T* old_ptr = data_.ptr; + data_.ptr = NULL; + return old_ptr; + } + + private: + // Needed to allow type-converting constructor. + template friend class scoped_ptr_impl; + + // Use the empty base class optimization to allow us to have a D + // member, while avoiding any space overhead for it when D is an + // empty class. See e.g. http://www.cantrip.org/emptyopt.html for a good + // discussion of this technique. + struct Data : public D { + explicit Data(T* ptr_in) : ptr(ptr_in) {} + Data(T* ptr_in, const D& other) : D(other), ptr(ptr_in) {} + T* ptr; + }; + + Data data_; + + DISALLOW_COPY_AND_ASSIGN(scoped_ptr_impl); +}; + +} // namespace internal + +} // namespace butil + +// A scoped_ptr is like a T*, except that the destructor of scoped_ptr +// automatically deletes the pointer it holds (if any). +// That is, scoped_ptr owns the T object that it points to. +// Like a T*, a scoped_ptr may hold either NULL or a pointer to a T object. +// Also like T*, scoped_ptr is thread-compatible, and once you +// dereference it, you get the thread safety guarantees of T. +// +// The size of scoped_ptr is small. On most compilers, when using the +// DefaultDeleter, sizeof(scoped_ptr) == sizeof(T*). Custom deleters will +// increase the size proportional to whatever state they need to have. See +// comments inside scoped_ptr_impl<> for details. +// +// Current implementation targets having a strict subset of C++11's +// unique_ptr<> features. Known deficiencies include not supporting move-only +// deleteres, function pointers as deleters, and deleters with reference +// types. +template > +class scoped_ptr { + MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr, RValue) + + COMPILE_ASSERT(butil::internal::IsNotRefCounted::value, + T_is_refcounted_type_and_needs_scoped_refptr); + + public: + // The element and deleter types. + typedef T element_type; + typedef D deleter_type; + + // Constructor. Defaults to initializing with NULL. + scoped_ptr() : impl_(NULL) { } + + // Constructor. Takes ownership of p. + explicit scoped_ptr(element_type* p) : impl_(p) { } + + // Constructor. Allows initialization of a stateful deleter. + scoped_ptr(element_type* p, const D& d) : impl_(p, d) { } + + // Constructor. Allows construction from a scoped_ptr rvalue for a + // convertible type and deleter. + // + // IMPLEMENTATION NOTE: C++11 unique_ptr<> keeps this constructor distinct + // from the normal move constructor. By C++11 20.7.1.2.1.21, this constructor + // has different post-conditions if D is a reference type. Since this + // implementation does not support deleters with reference type, + // we do not need a separate move constructor allowing us to avoid one + // use of SFINAE. You only need to care about this if you modify the + // implementation of scoped_ptr. + template + scoped_ptr(scoped_ptr other) : impl_(&other.impl_) { + COMPILE_ASSERT(!butil::is_array::value, U_cannot_be_an_array); + } + + // Constructor. Move constructor for C++03 move emulation of this type. + scoped_ptr(RValue rvalue) : impl_(&rvalue.object->impl_) { } + + // operator=. Allows assignment from a scoped_ptr rvalue for a convertible + // type and deleter. + // + // IMPLEMENTATION NOTE: C++11 unique_ptr<> keeps this operator= distinct from + // the normal move assignment operator. By C++11 20.7.1.2.3.4, this templated + // form has different requirements on for move-only Deleters. Since this + // implementation does not support move-only Deleters, we do not need a + // separate move assignment operator allowing us to avoid one use of SFINAE. + // You only need to care about this if you modify the implementation of + // scoped_ptr. + template + scoped_ptr& operator=(scoped_ptr rhs) { + COMPILE_ASSERT(!butil::is_array::value, U_cannot_be_an_array); + impl_.TakeState(&rhs.impl_); + return *this; + } + + // Reset. Deletes the currently owned object, if any. + // Then takes ownership of a new object, if given. + void reset(element_type* p = NULL) { impl_.reset(p); } + + // Accessors to get the owned object. + // operator* and operator-> will assert() if there is no current object. + element_type& operator*() const { + assert(impl_.get() != NULL); + return *impl_.get(); + } + element_type* operator->() const { + assert(impl_.get() != NULL); + return impl_.get(); + } + element_type* get() const { return impl_.get(); } + + // Access to the deleter. + deleter_type& get_deleter() { return impl_.get_deleter(); } + const deleter_type& get_deleter() const { return impl_.get_deleter(); } + + // Allow scoped_ptr to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + // + // Note that this trick is only safe when the == and != operators + // are declared explicitly, as otherwise "scoped_ptr1 == + // scoped_ptr2" will compile but do the wrong thing (i.e., convert + // to Testable and then do the comparison). + private: + typedef butil::internal::scoped_ptr_impl + scoped_ptr::*Testable; + + public: + operator Testable() const { return impl_.get() ? &scoped_ptr::impl_ : NULL; } + + // Comparison operators. + // These return whether two scoped_ptr refer to the same object, not just to + // two different but equal objects. + bool operator==(const element_type* p) const { return impl_.get() == p; } + bool operator!=(const element_type* p) const { return impl_.get() != p; } + + // Swap two scoped pointers. + void swap(scoped_ptr& p2) { + impl_.swap(p2.impl_); + } + + // Release a pointer. + // The return value is the current pointer held by this object. + // If this object holds a NULL pointer, the return value is NULL. + // After this operation, this object will hold a NULL pointer, + // and will not own the object any more. + element_type* release() WARN_UNUSED_RESULT { + return impl_.release(); + } + + // C++98 doesn't support functions templates with default parameters which + // makes it hard to write a PassAs() that understands converting the deleter + // while preserving simple calling semantics. + // + // Until there is a use case for PassAs() with custom deleters, just ignore + // the custom deleter. + template + scoped_ptr PassAs() { + return scoped_ptr(Pass()); + } + + private: + // Needed to reach into |impl_| in the constructor. + template friend class scoped_ptr; + butil::internal::scoped_ptr_impl impl_; + + // Forbidden for API compatibility with std::unique_ptr. + explicit scoped_ptr(int disallow_construction_from_null); + + // Forbid comparison of scoped_ptr types. If U != T, it totally + // doesn't make sense, and if U == T, it still doesn't make sense + // because you should never have the same object owned by two different + // scoped_ptrs. + template bool operator==(scoped_ptr const& p2) const; + template bool operator!=(scoped_ptr const& p2) const; +}; + +template +class scoped_ptr { + MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr, RValue) + + public: + // The element and deleter types. + typedef T element_type; + typedef D deleter_type; + + // Constructor. Defaults to initializing with NULL. + scoped_ptr() : impl_(NULL) { } + + // Constructor. Stores the given array. Note that the argument's type + // must exactly match T*. In particular: + // - it cannot be a pointer to a type derived from T, because it is + // inherently unsafe in the general case to access an array through a + // pointer whose dynamic type does not match its static type (eg., if + // T and the derived types had different sizes access would be + // incorrectly calculated). Deletion is also always undefined + // (C++98 [expr.delete]p3). If you're doing this, fix your code. + // - it cannot be NULL, because NULL is an integral expression, not a + // pointer to T. Use the no-argument version instead of explicitly + // passing NULL. + // - it cannot be const-qualified differently from T per unique_ptr spec + // (http://cplusplus.github.com/LWG/lwg-active.html#2118). Users wanting + // to work around this may use implicit_cast(). + // However, because of the first bullet in this comment, users MUST + // NOT use implicit_cast() to upcast the static type of the array. + explicit scoped_ptr(element_type* array) : impl_(array) { } + + // Constructor. Move constructor for C++03 move emulation of this type. + scoped_ptr(RValue rvalue) : impl_(&rvalue.object->impl_) { } + + // operator=. Move operator= for C++03 move emulation of this type. + scoped_ptr& operator=(RValue rhs) { + impl_.TakeState(&rhs.object->impl_); + return *this; + } + + // Reset. Deletes the currently owned array, if any. + // Then takes ownership of a new object, if given. + void reset(element_type* array = NULL) { impl_.reset(array); } + + // Accessors to get the owned array. + element_type& operator[](size_t i) const { + assert(impl_.get() != NULL); + return impl_.get()[i]; + } + element_type* get() const { return impl_.get(); } + + // Access to the deleter. + deleter_type& get_deleter() { return impl_.get_deleter(); } + const deleter_type& get_deleter() const { return impl_.get_deleter(); } + + // Allow scoped_ptr to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + private: + typedef butil::internal::scoped_ptr_impl + scoped_ptr::*Testable; + + public: + operator Testable() const { return impl_.get() ? &scoped_ptr::impl_ : NULL; } + + // Comparison operators. + // These return whether two scoped_ptr refer to the same object, not just to + // two different but equal objects. + bool operator==(element_type* array) const { return impl_.get() == array; } + bool operator!=(element_type* array) const { return impl_.get() != array; } + + // Swap two scoped pointers. + void swap(scoped_ptr& p2) { + impl_.swap(p2.impl_); + } + + // Release a pointer. + // The return value is the current pointer held by this object. + // If this object holds a NULL pointer, the return value is NULL. + // After this operation, this object will hold a NULL pointer, + // and will not own the object any more. + element_type* release() WARN_UNUSED_RESULT { + return impl_.release(); + } + + private: + // Force element_type to be a complete type. + enum { type_must_be_complete = sizeof(element_type) }; + + // Actually hold the data. + butil::internal::scoped_ptr_impl impl_; + + // Disable initialization from any type other than element_type*, by + // providing a constructor that matches such an initialization, but is + // private and has no definition. This is disabled because it is not safe to + // call delete[] on an array whose static type does not match its dynamic + // type. + template explicit scoped_ptr(U* array); + explicit scoped_ptr(int disallow_construction_from_null); + + // Disable reset() from any type other than element_type*, for the same + // reasons as the constructor above. + template void reset(U* array); + void reset(int disallow_reset_from_null); + + // Forbid comparison of scoped_ptr types. If U != T, it totally + // doesn't make sense, and if U == T, it still doesn't make sense + // because you should never have the same object owned by two different + // scoped_ptrs. + template bool operator==(scoped_ptr const& p2) const; + template bool operator!=(scoped_ptr const& p2) const; +}; + +// Free functions +template +void swap(scoped_ptr& p1, scoped_ptr& p2) { + p1.swap(p2); +} + +template +bool operator==(T* p1, const scoped_ptr& p2) { + return p1 == p2.get(); +} + +template +bool operator!=(T* p1, const scoped_ptr& p2) { + return p1 != p2.get(); +} + +// A function to convert T* into scoped_ptr +// Doing e.g. make_scoped_ptr(new FooBarBaz(arg)) is a shorter notation +// for scoped_ptr >(new FooBarBaz(arg)) +template +scoped_ptr make_scoped_ptr(T* ptr) { + return scoped_ptr(ptr); +} + +#endif // BUTIL_MEMORY_SCOPED_PTR_H_ diff --git a/src/butil/memory/scoped_vector.h b/src/butil/memory/scoped_vector.h new file mode 100644 index 0000000..a2aeba7 --- /dev/null +++ b/src/butil/memory/scoped_vector.h @@ -0,0 +1,144 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_MEMORY_SCOPED_VECTOR_H_ +#define BUTIL_MEMORY_SCOPED_VECTOR_H_ + +#include + +#include "butil/basictypes.h" +#include "butil/logging.h" +#include "butil/move.h" +#include "butil/stl_util.h" + +namespace butil { + +// ScopedVector wraps a vector deleting the elements from its +// destructor. +template +class ScopedVector { + MOVE_ONLY_TYPE_FOR_CPP_03(ScopedVector, RValue) + + public: + typedef typename std::vector::allocator_type allocator_type; + typedef typename std::vector::size_type size_type; + typedef typename std::vector::difference_type difference_type; + typedef typename std::vector::pointer pointer; + typedef typename std::vector::const_pointer const_pointer; + typedef typename std::vector::reference reference; + typedef typename std::vector::const_reference const_reference; + typedef typename std::vector::value_type value_type; + typedef typename std::vector::iterator iterator; + typedef typename std::vector::const_iterator const_iterator; + typedef typename std::vector::reverse_iterator reverse_iterator; + typedef typename std::vector::const_reverse_iterator + const_reverse_iterator; + + ScopedVector() {} + ~ScopedVector() { clear(); } + ScopedVector(RValue other) { swap(*other.object); } +#if __cplusplus >= 201103L // >= C++11 + ScopedVector(std::initializer_list il) : v_(il) {} +#endif + + ScopedVector& operator=(RValue rhs) { + swap(*rhs.object); + return *this; + } + + reference operator[](size_t index) { return v_[index]; } + const_reference operator[](size_t index) const { return v_[index]; } + + bool empty() const { return v_.empty(); } + size_t size() const { return v_.size(); } + + reverse_iterator rbegin() { return v_.rbegin(); } + const_reverse_iterator rbegin() const { return v_.rbegin(); } + reverse_iterator rend() { return v_.rend(); } + const_reverse_iterator rend() const { return v_.rend(); } + + iterator begin() { return v_.begin(); } + const_iterator begin() const { return v_.begin(); } + iterator end() { return v_.end(); } + const_iterator end() const { return v_.end(); } + + const_reference front() const { return v_.front(); } + reference front() { return v_.front(); } + const_reference back() const { return v_.back(); } + reference back() { return v_.back(); } + + void push_back(T* elem) { v_.push_back(elem); } + + void pop_back() { + DCHECK(!empty()); + delete v_.back(); + v_.pop_back(); + } + + std::vector& get() { return v_; } + const std::vector& get() const { return v_; } + void swap(std::vector& other) { v_.swap(other); } + void swap(ScopedVector& other) { v_.swap(other.v_); } + void release(std::vector* out) { + out->swap(v_); + v_.clear(); + } + + void reserve(size_t capacity) { v_.reserve(capacity); } + + // Resize, deleting elements in the disappearing range if we are shrinking. + void resize(size_t new_size) { + if (v_.size() > new_size) + STLDeleteContainerPointers(v_.begin() + new_size, v_.end()); + v_.resize(new_size); + } + + template + void assign(InputIterator begin, InputIterator end) { + v_.assign(begin, end); + } + + void clear() { STLDeleteElements(&v_); } + + // Like |clear()|, but doesn't delete any elements. + void weak_clear() { v_.clear(); } + + // Lets the ScopedVector take ownership of |x|. + iterator insert(iterator position, T* x) { + return v_.insert(position, x); + } + + // Lets the ScopedVector take ownership of elements in [first,last). + template + void insert(iterator position, InputIterator first, InputIterator last) { + v_.insert(position, first, last); + } + + iterator erase(iterator position) { + delete *position; + return v_.erase(position); + } + + iterator erase(iterator first, iterator last) { + STLDeleteContainerPointers(first, last); + return v_.erase(first, last); + } + + // Like |erase()|, but doesn't delete the element at |position|. + iterator weak_erase(iterator position) { + return v_.erase(position); + } + + // Like |erase()|, but doesn't delete the elements in [first, last). + iterator weak_erase(iterator first, iterator last) { + return v_.erase(first, last); + } + + private: + std::vector v_; +}; + +} // namespace butil + +#endif // BUTIL_MEMORY_SCOPED_VECTOR_H_ diff --git a/src/butil/memory/singleton.cc b/src/butil/memory/singleton.cc new file mode 100644 index 0000000..1e3bdf2 --- /dev/null +++ b/src/butil/memory/singleton.cc @@ -0,0 +1,33 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/memory/singleton.h" +#include "butil/threading/platform_thread.h" + +namespace butil { +namespace internal { + +subtle::AtomicWord WaitForInstance(subtle::AtomicWord* instance) { + // Handle the race. Another thread beat us and either: + // - Has the object in BeingCreated state + // - Already has the object created... + // We know value != NULL. It could be kBeingCreatedMarker, or a valid ptr. + // Unless your constructor can be very time consuming, it is very unlikely + // to hit this race. When it does, we just spin and yield the thread until + // the object has been created. + subtle::AtomicWord value; + while (true) { + // The load has acquire memory ordering as the thread which reads the + // instance pointer must acquire visibility over the associated data. + // The pairing Release_Store operation is in Singleton::get(). + value = subtle::Acquire_Load(instance); + if (value != kBeingCreatedMarker) + break; + PlatformThread::YieldCurrentThread(); + } + return value; +} + +} // namespace internal +} // namespace butil diff --git a/src/butil/memory/singleton.h b/src/butil/memory/singleton.h new file mode 100644 index 0000000..ff132bc --- /dev/null +++ b/src/butil/memory/singleton.h @@ -0,0 +1,300 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// PLEASE READ: Do you really need a singleton? +// +// Singletons make it hard to determine the lifetime of an object, which can +// lead to buggy code and spurious crashes. +// +// Instead of adding another singleton into the mix, try to identify either: +// a) An existing singleton that can manage your object's lifetime +// b) Locations where you can deterministically create the object and pass +// into other objects +// +// If you absolutely need a singleton, please keep them as trivial as possible +// and ideally a leaf dependency. Singletons get problematic when they attempt +// to do too much in their destructor or have circular dependencies. + +#ifndef BUTIL_MEMORY_SINGLETON_H_ +#define BUTIL_MEMORY_SINGLETON_H_ + +#include "butil/at_exit.h" +#include "butil/atomicops.h" +#include "butil/base_export.h" +#include "butil/memory/aligned_memory.h" +#include "butil/third_party/dynamic_annotations/dynamic_annotations.h" + +namespace butil { +namespace internal { + +// Our AtomicWord doubles as a spinlock, where a value of +// kBeingCreatedMarker means the spinlock is being held for creation. +static const subtle::AtomicWord kBeingCreatedMarker = 1; + +// We pull out some of the functionality into a non-templated function, so that +// we can implement the more complicated pieces out of line in the .cc file. +BUTIL_EXPORT subtle::AtomicWord WaitForInstance(subtle::AtomicWord* instance); + +} // namespace internal +} // namespace butil + +// TODO(joth): Move more of this file into namespace butil + +// Default traits for Singleton. Calls operator new and operator delete on +// the object. Registers automatic deletion at process exit. +// Overload if you need arguments or another memory allocation function. +template +struct DefaultSingletonTraits { + // Allocates the object. + static Type* New() { + // The parenthesis is very important here; it forces POD type + // initialization. + return new Type(); + } + + // Destroys the object. + static void Delete(Type* x) { + delete x; + } + + // Set to true to automatically register deletion of the object on process + // exit. See below for the required call that makes this happen. + static const bool kRegisterAtExit; + +#ifndef NDEBUG + // Set to false to disallow access on a non-joinable thread. This is + // different from kRegisterAtExit because StaticMemorySingletonTraits allows + // access on non-joinable threads, and gracefully handles this. + static const bool kAllowedToAccessOnNonjoinableThread; +#endif +}; + +// NOTE(gejun): BullseyeCoverage Compile C++ 8.4.4 complains about `undefined +// reference' on in-place assignments to static constants. +template +const bool DefaultSingletonTraits::kRegisterAtExit = true; +#ifndef NDEBUG +template +const bool DefaultSingletonTraits::kAllowedToAccessOnNonjoinableThread = false; +#endif + +// Alternate traits for use with the Singleton. Identical to +// DefaultSingletonTraits except that the Singleton will not be cleaned up +// at exit. +template +struct LeakySingletonTraits : public DefaultSingletonTraits { + static const bool kRegisterAtExit; +#ifndef NDEBUG + static const bool kAllowedToAccessOnNonjoinableThread; +#endif +}; + +template +const bool LeakySingletonTraits::kRegisterAtExit = false; +#ifndef NDEBUG +template +const bool LeakySingletonTraits::kAllowedToAccessOnNonjoinableThread = true; +#endif + +// Alternate traits for use with the Singleton. Allocates memory +// for the singleton instance from a static buffer. The singleton will +// be cleaned up at exit, but can't be revived after destruction unless +// the Resurrect() method is called. +// +// This is useful for a certain category of things, notably logging and +// tracing, where the singleton instance is of a type carefully constructed to +// be safe to access post-destruction. +// In logging and tracing you'll typically get stray calls at odd times, like +// during static destruction, thread teardown and the like, and there's a +// termination race on the heap-based singleton - e.g. if one thread calls +// get(), but then another thread initiates AtExit processing, the first thread +// may call into an object residing in unallocated memory. If the instance is +// allocated from the data segment, then this is survivable. +// +// The destructor is to deallocate system resources, in this case to unregister +// a callback the system will invoke when logging levels change. Note that +// this is also used in e.g. Chrome Frame, where you have to allow for the +// possibility of loading briefly into someone else's process space, and +// so leaking is not an option, as that would sabotage the state of your host +// process once you've unloaded. +template +struct StaticMemorySingletonTraits { + // WARNING: User has to deal with get() in the singleton class + // this is traits for returning NULL. + static Type* New() { + // Only constructs once and returns pointer; otherwise returns NULL. + if (butil::subtle::NoBarrier_AtomicExchange(&dead_, 1)) + return NULL; + + return new(buffer_.void_data()) Type(); + } + + static void Delete(Type* p) { + if (p != NULL) + p->Type::~Type(); + } + + static const bool kRegisterAtExit = true; + static const bool kAllowedToAccessOnNonjoinableThread = true; + + // Exposed for unittesting. + static void Resurrect() { + butil::subtle::NoBarrier_Store(&dead_, 0); + } + + private: + static butil::AlignedMemory buffer_; + // Signal the object was already deleted, so it is not revived. + static butil::subtle::Atomic32 dead_; +}; + +template butil::AlignedMemory + StaticMemorySingletonTraits::buffer_; +template butil::subtle::Atomic32 + StaticMemorySingletonTraits::dead_ = 0; + +// The Singleton class manages a single +// instance of Type which will be created on first use and will be destroyed at +// normal process exit). The Trait::Delete function will not be called on +// abnormal process exit. +// +// DifferentiatingType is used as a key to differentiate two different +// singletons having the same memory allocation functions but serving a +// different purpose. This is mainly used for Locks serving different purposes. +// +// Example usage: +// +// In your header: +// template struct DefaultSingletonTraits; +// class FooClass { +// public: +// static FooClass* GetInstance(); <-- See comment below on this. +// void Bar() { ... } +// private: +// FooClass() { ... } +// friend struct DefaultSingletonTraits; +// +// DISALLOW_COPY_AND_ASSIGN(FooClass); +// }; +// +// In your source file: +// #include "butil/memory/singleton.h" +// FooClass* FooClass::GetInstance() { +// return Singleton::get(); +// } +// +// And to call methods on FooClass: +// FooClass::GetInstance()->Bar(); +// +// NOTE: The method accessing Singleton::get() has to be named as GetInstance +// and it is important that FooClass::GetInstance() is not inlined in the +// header. This makes sure that when source files from multiple targets include +// this header they don't end up with different copies of the inlined code +// creating multiple copies of the singleton. +// +// Singleton<> has no non-static members and doesn't need to actually be +// instantiated. +// +// This class is itself thread-safe. The underlying Type must of course be +// thread-safe if you want to use it concurrently. Two parameters may be tuned +// depending on the user's requirements. +// +// Glossary: +// RAE = kRegisterAtExit +// +// On every platform, if Traits::RAE is true, the singleton will be destroyed at +// process exit. More precisely it uses butil::AtExitManager which requires an +// object of this type to be instantiated. AtExitManager mimics the semantics +// of atexit() such as LIFO order but under Windows is safer to call. For more +// information see at_exit.h. +// +// If Traits::RAE is false, the singleton will not be freed at process exit, +// thus the singleton will be leaked if it is ever accessed. Traits::RAE +// shouldn't be false unless absolutely necessary. Remember that the heap where +// the object is allocated may be destroyed by the CRT anyway. +// +// Caveats: +// (a) Every call to get(), operator->() and operator*() incurs some overhead +// (16ns on my P4/2.8GHz) to check whether the object has already been +// initialized. You may wish to cache the result of get(); it will not +// change. +// +// (b) Your factory function must never throw an exception. This class is not +// exception-safe. +// +template , + typename DifferentiatingType = Type> +class Singleton { + private: + // Classes using the Singleton pattern should declare a GetInstance() + // method and call Singleton::get() from within that. + friend Type* Type::GetInstance(); + + // Allow TraceLog tests to test tracing after OnExit. + friend class DeleteTraceLogForTesting; + + // This class is safe to be constructed and copy-constructed since it has no + // member. + + // Return a pointer to the one true instance of the class. + static Type* get() { + // The load has acquire memory ordering as the thread which reads the + // instance_ pointer must acquire visibility over the singleton data. + butil::subtle::AtomicWord value = butil::subtle::Acquire_Load(&instance_); + if (value != 0 && value != butil::internal::kBeingCreatedMarker) { + // See the corresponding HAPPENS_BEFORE below. + ANNOTATE_HAPPENS_AFTER(&instance_); + return reinterpret_cast(value); + } + + // Object isn't created yet, maybe we will get to create it, let's try... + if (butil::subtle::Acquire_CompareAndSwap( + &instance_, 0, butil::internal::kBeingCreatedMarker) == 0) { + // instance_ was NULL and is now kBeingCreatedMarker. Only one thread + // will ever get here. Threads might be spinning on us, and they will + // stop right after we do this store. + Type* newval = Traits::New(); + + // This annotation helps race detectors recognize correct lock-less + // synchronization between different threads calling get(). + // See the corresponding HAPPENS_AFTER below and above. + ANNOTATE_HAPPENS_BEFORE(&instance_); + // Releases the visibility over instance_ to the readers. + butil::subtle::Release_Store( + &instance_, reinterpret_cast(newval)); + + if (newval != NULL && Traits::kRegisterAtExit) { + butil::AtExitManager::RegisterCallback(OnExit, NULL); + } + + return newval; + } + + // We hit a race. Wait for the other thread to complete it. + value = butil::internal::WaitForInstance(&instance_); + + // See the corresponding HAPPENS_BEFORE above. + ANNOTATE_HAPPENS_AFTER(&instance_); + return reinterpret_cast(value); + } + + // Adapter function for use with AtExit(). This should be called single + // threaded, so don't use atomic operations. + // Calling OnExit while singleton is in use by other threads is a mistake. + static void OnExit(void* /*unused*/) { + // AtExit should only ever be register after the singleton instance was + // created. We should only ever get here with a valid instance_ pointer. + Traits::Delete( + reinterpret_cast(butil::subtle::NoBarrier_Load(&instance_))); + instance_ = 0; + } + static butil::subtle::AtomicWord instance_; +}; + +template +butil::subtle::AtomicWord Singleton:: + instance_ = 0; + +#endif // BUTIL_MEMORY_SINGLETON_H_ diff --git a/src/butil/memory/singleton_objc.h b/src/butil/memory/singleton_objc.h new file mode 100644 index 0000000..e7edc91 --- /dev/null +++ b/src/butil/memory/singleton_objc.h @@ -0,0 +1,60 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Support for using the Singleton pattern with Objective-C objects. A +// SingletonObjC is the same as a Singleton, except the default traits are +// appropriate for Objective-C objects. A typical Objective-C object of type +// NSExampleType can be maintained as a singleton and accessed with: +// +// NSExampleType* exampleSingleton = SingletonObjC::get(); +// +// The first time this is used, it will create exampleSingleton as the result +// of [[NSExampleType alloc] init]. Subsequent calls will return the same +// NSExampleType* object. The object will be released by calling +// -[NSExampleType release] when Singleton's atexit routines run +// (see singleton.h). +// +// For Objective-C objects initialized through means other than the +// no-parameter -init selector, DefaultSingletonObjCTraits may be extended +// as needed: +// +// struct FooSingletonTraits : public DefaultSingletonObjCTraits { +// static Foo* New() { +// return [[Foo alloc] initWithName:@"selecty"]; +// } +// }; +// ... +// Foo* widgetSingleton = SingletonObjC::get(); + +#ifndef BUTIL_MEMORY_SINGLETON_OBJC_H_ +#define BUTIL_MEMORY_SINGLETON_OBJC_H_ + +#import +#include "butil/memory/singleton.h" + +// Singleton traits usable to manage traditional Objective-C objects, which +// are instantiated by sending |alloc| and |init| messages, and are deallocated +// in a memory-managed environment when their retain counts drop to 0 by +// sending |release| messages. +template +struct DefaultSingletonObjCTraits : public DefaultSingletonTraits { + static Type* New() { + return [[Type alloc] init]; + } + + static void Delete(Type* object) { + [object release]; + } +}; + +// Exactly like Singleton, but without the DefaultSingletonObjCTraits as the +// default trait class. This makes it straightforward for Objective-C++ code +// to hold Objective-C objects as singletons. +template, + typename DifferentiatingType = Type> +class SingletonObjC : public Singleton { +}; + +#endif // BUTIL_MEMORY_SINGLETON_OBJC_H_ diff --git a/src/butil/memory/singleton_on_pthread_once.h b/src/butil/memory/singleton_on_pthread_once.h new file mode 100644 index 0000000..9699bba --- /dev/null +++ b/src/butil/memory/singleton_on_pthread_once.h @@ -0,0 +1,83 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Thu Dec 15 14:37:39 CST 2016 + +#ifndef BUTIL_MEMORY_SINGLETON_ON_PTHREAD_ONCE_H +#define BUTIL_MEMORY_SINGLETON_ON_PTHREAD_ONCE_H + +#include +#include "butil/atomicops.h" + +namespace butil { + +template +T* create_leaky_singleton_obj() { + return new T(); +} + +template +class GetLeakySingleton { +public: + static butil::subtle::AtomicWord g_leaky_singleton_untyped; + static pthread_once_t g_create_leaky_singleton_once; + static void create_leaky_singleton(); +}; +template +butil::subtle::AtomicWord GetLeakySingleton::g_leaky_singleton_untyped = 0; + +template +pthread_once_t GetLeakySingleton::g_create_leaky_singleton_once = PTHREAD_ONCE_INIT; + +template +void GetLeakySingleton::create_leaky_singleton() { + T* obj = create_leaky_singleton_obj(); + butil::subtle::Release_Store( + &g_leaky_singleton_untyped, + reinterpret_cast(obj)); +} + +// To get a never-deleted singleton of a type T, just call get_leaky_singleton(). +// Most daemon threads or objects that need to be always-on can be created by +// this function. +// This function can be called safely before main() w/o initialization issues of +// global variables. +template +inline T* get_leaky_singleton() { + const butil::subtle::AtomicWord value = butil::subtle::Acquire_Load( + &GetLeakySingleton::g_leaky_singleton_untyped); + if (value) { + return reinterpret_cast(value); + } + pthread_once(&GetLeakySingleton::g_create_leaky_singleton_once, + GetLeakySingleton::create_leaky_singleton); + return reinterpret_cast( + GetLeakySingleton::g_leaky_singleton_untyped); +} + +// True(non-NULL) if the singleton is created. +// The returned object (if not NULL) can be used directly. +template +inline T* has_leaky_singleton() { + return reinterpret_cast( + butil::subtle::Acquire_Load( + &GetLeakySingleton::g_leaky_singleton_untyped)); +} + +} // namespace butil + +#endif // BUTIL_MEMORY_SINGLETON_ON_PTHREAD_ONCE_H diff --git a/src/butil/memory/weak_ptr.cc b/src/butil/memory/weak_ptr.cc new file mode 100644 index 0000000..0954572 --- /dev/null +++ b/src/butil/memory/weak_ptr.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/memory/weak_ptr.h" + +namespace butil { +namespace internal { + +WeakReference::Flag::Flag() : is_valid_(true) { +} + +void WeakReference::Flag::Invalidate() { + is_valid_ = false; +} + +bool WeakReference::Flag::IsValid() const { + return is_valid_; +} + +WeakReference::Flag::~Flag() { +} + +WeakReference::WeakReference() { +} + +WeakReference::WeakReference(const Flag* flag) : flag_(flag) { +} + +WeakReference::~WeakReference() { +} + +bool WeakReference::is_valid() const { return flag_.get() && flag_->IsValid(); } + +WeakReferenceOwner::WeakReferenceOwner() { +} + +WeakReferenceOwner::~WeakReferenceOwner() { + Invalidate(); +} + +WeakReference WeakReferenceOwner::GetRef() const { + // If we hold the last reference to the Flag then create a new one. + if (!HasRefs()) + flag_ = new WeakReference::Flag(); + + return WeakReference(flag_.get()); +} + +void WeakReferenceOwner::Invalidate() { + if (flag_.get()) { + flag_->Invalidate(); + flag_ = NULL; + } +} + +WeakPtrBase::WeakPtrBase() { +} + +WeakPtrBase::~WeakPtrBase() { +} + +WeakPtrBase::WeakPtrBase(const WeakReference& ref) : ref_(ref) { +} + +} // namespace internal +} // namespace butil diff --git a/src/butil/memory/weak_ptr.h b/src/butil/memory/weak_ptr.h new file mode 100644 index 0000000..fd65bc9 --- /dev/null +++ b/src/butil/memory/weak_ptr.h @@ -0,0 +1,336 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Weak pointers are pointers to an object that do not affect its lifetime, +// and which may be invalidated (i.e. reset to NULL) by the object, or its +// owner, at any time, most commonly when the object is about to be deleted. + +// Weak pointers are useful when an object needs to be accessed safely by one +// or more objects other than its owner, and those callers can cope with the +// object vanishing and e.g. tasks posted to it being silently dropped. +// Reference-counting such an object would complicate the ownership graph and +// make it harder to reason about the object's lifetime. + +// EXAMPLE: +// +// class Controller { +// public: +// void SpawnWorker() { Worker::StartNew(weak_factory_.GetWeakPtr()); } +// void WorkComplete(const Result& result) { ... } +// private: +// // Member variables should appear before the WeakPtrFactory, to ensure +// // that any WeakPtrs to Controller are invalidated before its members +// // variable's destructors are executed, rendering them invalid. +// WeakPtrFactory weak_factory_; +// }; +// +// class Worker { +// public: +// static void StartNew(const WeakPtr& controller) { +// Worker* worker = new Worker(controller); +// // Kick off asynchronous processing... +// } +// private: +// Worker(const WeakPtr& controller) +// : controller_(controller) {} +// void DidCompleteAsynchronousProcessing(const Result& result) { +// if (controller_) +// controller_->WorkComplete(result); +// } +// WeakPtr controller_; +// }; +// +// With this implementation a caller may use SpawnWorker() to dispatch multiple +// Workers and subsequently delete the Controller, without waiting for all +// Workers to have completed. + +// ------------------------- IMPORTANT: Thread-safety ------------------------- + +// Weak pointers may be passed safely between threads, but must always be +// dereferenced and invalidated on the same thread otherwise checking the +// pointer would be racey. +// +// To ensure correct use, the first time a WeakPtr issued by a WeakPtrFactory +// is dereferenced, the factory and its WeakPtrs become bound to the calling +// thread, and cannot be dereferenced or invalidated on any other thread. Bound +// WeakPtrs can still be handed off to other threads, e.g. to use to post tasks +// back to object on the bound thread. +// +// Invalidating the factory's WeakPtrs un-binds it from the thread, allowing it +// to be passed for a different thread to use or delete it. + +#ifndef BUTIL_MEMORY_WEAK_PTR_H_ +#define BUTIL_MEMORY_WEAK_PTR_H_ + +#include "butil/basictypes.h" +#include "butil/base_export.h" +#include "butil/logging.h" +#include "butil/memory/ref_counted.h" +#include "butil/type_traits.h" + +namespace butil { + +template class SupportsWeakPtr; +template class WeakPtr; + +namespace internal { +// These classes are part of the WeakPtr implementation. +// DO NOT USE THESE CLASSES DIRECTLY YOURSELF. + +class BUTIL_EXPORT WeakReference { + public: + // Although Flag is bound to a specific thread, it may be deleted from another + // via butil::WeakPtr::~WeakPtr(). + class BUTIL_EXPORT Flag : public RefCountedThreadSafe { + public: + Flag(); + + void Invalidate(); + bool IsValid() const; + + private: + friend class butil::RefCountedThreadSafe; + + ~Flag(); + + bool is_valid_; + }; + + WeakReference(); + explicit WeakReference(const Flag* flag); + ~WeakReference(); + + bool is_valid() const; + + private: + scoped_refptr flag_; +}; + +class BUTIL_EXPORT WeakReferenceOwner { + public: + WeakReferenceOwner(); + ~WeakReferenceOwner(); + + WeakReference GetRef() const; + + bool HasRefs() const { + return flag_.get() && !flag_->HasOneRef(); + } + + void Invalidate(); + + private: + mutable scoped_refptr flag_; +}; + +// This class simplifies the implementation of WeakPtr's type conversion +// constructor by avoiding the need for a public accessor for ref_. A +// WeakPtr cannot access the private members of WeakPtr, so this +// base class gives us a way to access ref_ in a protected fashion. +class BUTIL_EXPORT WeakPtrBase { + public: + WeakPtrBase(); + ~WeakPtrBase(); + + protected: + explicit WeakPtrBase(const WeakReference& ref); + + WeakReference ref_; +}; + +// This class provides a common implementation of common functions that would +// otherwise get instantiated separately for each distinct instantiation of +// SupportsWeakPtr<>. +class SupportsWeakPtrBase { + public: + // A safe static downcast of a WeakPtr to WeakPtr. This + // conversion will only compile if there is exists a Base which inherits + // from SupportsWeakPtr. See butil::AsWeakPtr() below for a helper + // function that makes calling this easier. + template + static WeakPtr StaticAsWeakPtr(Derived* t) { + typedef + is_convertible convertible; + COMPILE_ASSERT(convertible::value, + AsWeakPtr_argument_inherits_from_SupportsWeakPtr); + return AsWeakPtrImpl(t, *t); + } + + private: + // This template function uses type inference to find a Base of Derived + // which is an instance of SupportsWeakPtr. We can then safely + // static_cast the Base* to a Derived*. + template + static WeakPtr AsWeakPtrImpl( + Derived* t, const SupportsWeakPtr&) { + WeakPtr ptr = t->Base::AsWeakPtr(); + return WeakPtr(ptr.ref_, static_cast(ptr.ptr_)); + } +}; + +} // namespace internal + +template class WeakPtrFactory; + +// The WeakPtr class holds a weak reference to |T*|. +// +// This class is designed to be used like a normal pointer. You should always +// null-test an object of this class before using it or invoking a method that +// may result in the underlying object being destroyed. +// +// EXAMPLE: +// +// class Foo { ... }; +// WeakPtr foo; +// if (foo) +// foo->method(); +// +template +class WeakPtr : public internal::WeakPtrBase { + public: + WeakPtr() : ptr_(NULL) { + } + + // Allow conversion from U to T provided U "is a" T. Note that this + // is separate from the (implicit) copy constructor. + template + WeakPtr(const WeakPtr& other) : WeakPtrBase(other), ptr_(other.ptr_) { + } + + T* get() const { return ref_.is_valid() ? ptr_ : NULL; } + + T& operator*() const { + DCHECK(get() != NULL); + return *get(); + } + T* operator->() const { + DCHECK(get() != NULL); + return get(); + } + + // Allow WeakPtr to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + // + // Note that this trick is only safe when the == and != operators + // are declared explicitly, as otherwise "weak_ptr1 == weak_ptr2" + // will compile but do the wrong thing (i.e., convert to Testable + // and then do the comparison). + private: + typedef T* WeakPtr::*Testable; + + public: + operator Testable() const { return get() ? &WeakPtr::ptr_ : NULL; } + + void reset() { + ref_ = internal::WeakReference(); + ptr_ = NULL; + } + + private: + // Explicitly declare comparison operators as required by the bool + // trick, but keep them private. + template bool operator==(WeakPtr const&) const; + template bool operator!=(WeakPtr const&) const; + + friend class internal::SupportsWeakPtrBase; + template friend class WeakPtr; + friend class SupportsWeakPtr; + friend class WeakPtrFactory; + + WeakPtr(const internal::WeakReference& ref, T* ptr) + : WeakPtrBase(ref), + ptr_(ptr) { + } + + // This pointer is only valid when ref_.is_valid() is true. Otherwise, its + // value is undefined (as opposed to NULL). + T* ptr_; +}; + +// A class may be composed of a WeakPtrFactory and thereby +// control how it exposes weak pointers to itself. This is helpful if you only +// need weak pointers within the implementation of a class. This class is also +// useful when working with primitive types. For example, you could have a +// WeakPtrFactory that is used to pass around a weak reference to a bool. +template +class WeakPtrFactory { + public: + explicit WeakPtrFactory(T* ptr) : ptr_(ptr) { + } + + ~WeakPtrFactory() { + ptr_ = NULL; + } + + WeakPtr GetWeakPtr() { + DCHECK(ptr_); + return WeakPtr(weak_reference_owner_.GetRef(), ptr_); + } + + // Call this method to invalidate all existing weak pointers. + void InvalidateWeakPtrs() { + DCHECK(ptr_); + weak_reference_owner_.Invalidate(); + } + + // Call this method to determine if any weak pointers exist. + bool HasWeakPtrs() const { + DCHECK(ptr_); + return weak_reference_owner_.HasRefs(); + } + + private: + internal::WeakReferenceOwner weak_reference_owner_; + T* ptr_; + DISALLOW_IMPLICIT_CONSTRUCTORS(WeakPtrFactory); +}; + +// A class may extend from SupportsWeakPtr to let others take weak pointers to +// it. This avoids the class itself implementing boilerplate to dispense weak +// pointers. However, since SupportsWeakPtr's destructor won't invalidate +// weak pointers to the class until after the derived class' members have been +// destroyed, its use can lead to subtle use-after-destroy issues. +template +class SupportsWeakPtr : public internal::SupportsWeakPtrBase { + public: + SupportsWeakPtr() {} + + WeakPtr AsWeakPtr() { + return WeakPtr(weak_reference_owner_.GetRef(), static_cast(this)); + } + + protected: + ~SupportsWeakPtr() {} + + private: + internal::WeakReferenceOwner weak_reference_owner_; + DISALLOW_COPY_AND_ASSIGN(SupportsWeakPtr); +}; + +// Helper function that uses type deduction to safely return a WeakPtr +// when Derived doesn't directly extend SupportsWeakPtr, instead it +// extends a Base that extends SupportsWeakPtr. +// +// EXAMPLE: +// class Base : public butil::SupportsWeakPtr {}; +// class Derived : public Base {}; +// +// Derived derived; +// butil::WeakPtr ptr = butil::AsWeakPtr(&derived); +// +// Note that the following doesn't work (invalid type conversion) since +// Derived::AsWeakPtr() is WeakPtr SupportsWeakPtr::AsWeakPtr(), +// and there's no way to safely cast WeakPtr to WeakPtr at +// the caller. +// +// butil::WeakPtr ptr = derived.AsWeakPtr(); // Fails. + +template +WeakPtr AsWeakPtr(Derived* t) { + return internal::SupportsWeakPtrBase::StaticAsWeakPtr(t); +} + +} // namespace butil + +#endif // BUTIL_MEMORY_WEAK_PTR_H_ diff --git a/src/butil/move.h b/src/butil/move.h new file mode 100644 index 0000000..6613d1c --- /dev/null +++ b/src/butil/move.h @@ -0,0 +1,218 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_MOVE_H_ +#define BUTIL_MOVE_H_ + +// Macro with the boilerplate that makes a type move-only in C++03. +// +// USAGE +// +// This macro should be used instead of DISALLOW_COPY_AND_ASSIGN to create +// a "move-only" type. Unlike DISALLOW_COPY_AND_ASSIGN, this macro should be +// the first line in a class declaration. +// +// A class using this macro must call .Pass() (or somehow be an r-value already) +// before it can be: +// +// * Passed as a function argument +// * Used as the right-hand side of an assignment +// * Returned from a function +// +// Each class will still need to define their own "move constructor" and "move +// operator=" to make this useful. Here's an example of the macro, the move +// constructor, and the move operator= from the scoped_ptr class: +// +// template +// class scoped_ptr { +// MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr, RValue) +// public: +// scoped_ptr(RValue& other) : ptr_(other.release()) { } +// scoped_ptr& operator=(RValue& other) { +// swap(other); +// return *this; +// } +// }; +// +// Note that the constructor must NOT be marked explicit. +// +// For consistency, the second parameter to the macro should always be RValue +// unless you have a strong reason to do otherwise. It is only exposed as a +// macro parameter so that the move constructor and move operator= don't look +// like they're using a phantom type. +// +// +// HOW THIS WORKS +// +// For a thorough explanation of this technique, see: +// +// http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Move_Constructor +// +// The summary is that we take advantage of 2 properties: +// +// 1) non-const references will not bind to r-values. +// 2) C++ can apply one user-defined conversion when initializing a +// variable. +// +// The first lets us disable the copy constructor and assignment operator +// by declaring private version of them with a non-const reference parameter. +// +// For l-values, direct initialization still fails like in +// DISALLOW_COPY_AND_ASSIGN because the copy constructor and assignment +// operators are private. +// +// For r-values, the situation is different. The copy constructor and +// assignment operator are not viable due to (1), so we are trying to call +// a non-existent constructor and non-existing operator= rather than a private +// one. Since we have not committed an error quite yet, we can provide an +// alternate conversion sequence and a constructor. We add +// +// * a private struct named "RValue" +// * a user-defined conversion "operator RValue()" +// * a "move constructor" and "move operator=" that take the RValue& as +// their sole parameter. +// +// Only r-values will trigger this sequence and execute our "move constructor" +// or "move operator=." L-values will match the private copy constructor and +// operator= first giving a "private in this context" error. This combination +// gives us a move-only type. +// +// For signaling a destructive transfer of data from an l-value, we provide a +// method named Pass() which creates an r-value for the current instance +// triggering the move constructor or move operator=. +// +// Other ways to get r-values is to use the result of an expression like a +// function call. +// +// Here's an example with comments explaining what gets triggered where: +// +// class Foo { +// MOVE_ONLY_TYPE_FOR_CPP_03(Foo, RValue); +// +// public: +// ... API ... +// Foo(RValue other); // Move constructor. +// Foo& operator=(RValue rhs); // Move operator= +// }; +// +// Foo MakeFoo(); // Function that returns a Foo. +// +// Foo f; +// Foo f_copy(f); // ERROR: Foo(Foo&) is private in this context. +// Foo f_assign; +// f_assign = f; // ERROR: operator=(Foo&) is private in this context. +// +// +// Foo f(MakeFoo()); // R-value so alternate conversion executed. +// Foo f_copy(f.Pass()); // R-value so alternate conversion executed. +// f = f_copy.Pass(); // R-value so alternate conversion executed. +// +// +// IMPLEMENTATION SUBTLETIES WITH RValue +// +// The RValue struct is just a container for a pointer back to the original +// object. It should only ever be created as a temporary, and no external +// class should ever declare it or use it in a parameter. +// +// It is tempting to want to use the RValue type in function parameters, but +// excluding the limited usage here for the move constructor and move +// operator=, doing so would mean that the function could take both r-values +// and l-values equially which is unexpected. See COMPARED To Boost.Move for +// more details. +// +// An alternate, and incorrect, implementation of the RValue class used by +// Boost.Move makes RValue a fieldless child of the move-only type. RValue& +// is then used in place of RValue in the various operators. The RValue& is +// "created" by doing *reinterpret_cast(this). This has the appeal +// of never creating a temporary RValue struct even with optimizations +// disabled. Also, by virtue of inheritance you can treat the RValue +// reference as if it were the move-only type itself. Unfortunately, +// using the result of this reinterpret_cast<> is actually undefined behavior +// due to C++98 5.2.10.7. In certain compilers (e.g., NaCl) the optimizer +// will generate non-working code. +// +// In optimized builds, both implementations generate the same assembly so we +// choose the one that adheres to the standard. +// +// +// WHY HAVE typedef void MoveOnlyTypeForCPP03 +// +// Callback<>/Bind() needs to understand movable-but-not-copyable semantics +// to call .Pass() appropriately when it is expected to transfer the value. +// The cryptic typedef MoveOnlyTypeForCPP03 is added to make this check +// easy and automatic in helper templates for Callback<>/Bind(). +// See IsMoveOnlyType template and its usage in butil/callback_internal.h +// for more details. +// +// +// COMPARED TO C++11 +// +// In C++11, you would implement this functionality using an r-value reference +// and our .Pass() method would be replaced with a call to std::move(). +// +// This emulation also has a deficiency where it uses up the single +// user-defined conversion allowed by C++ during initialization. This can +// cause problems in some API edge cases. For instance, in scoped_ptr, it is +// impossible to make a function "void Foo(scoped_ptr p)" accept a +// value of type scoped_ptr even if you add a constructor to +// scoped_ptr<> that would make it look like it should work. C++11 does not +// have this deficiency. +// +// +// COMPARED TO Boost.Move +// +// Our implementation similar to Boost.Move, but we keep the RValue struct +// private to the move-only type, and we don't use the reinterpret_cast<> hack. +// +// In Boost.Move, RValue is the boost::rv<> template. This type can be used +// when writing APIs like: +// +// void MyFunc(boost::rv& f) +// +// that can take advantage of rv<> to avoid extra copies of a type. However you +// would still be able to call this version of MyFunc with an l-value: +// +// Foo f; +// MyFunc(f); // Uh oh, we probably just destroyed |f| w/o calling Pass(). +// +// unless someone is very careful to also declare a parallel override like: +// +// void MyFunc(const Foo& f) +// +// that would catch the l-values first. This was declared unsafe in C++11 and +// a C++11 compiler will explicitly fail MyFunc(f). Unfortunately, we cannot +// ensure this in C++03. +// +// Since we have no need for writing such APIs yet, our implementation keeps +// RValue private and uses a .Pass() method to do the conversion instead of +// trying to write a version of "std::move()." Writing an API like std::move() +// would require the RValue struct to be public. +// +// +// CAVEATS +// +// If you include a move-only type as a field inside a class that does not +// explicitly declare a copy constructor, the containing class's implicit +// copy constructor will change from Containing(const Containing&) to +// Containing(Containing&). This can cause some unexpected errors. +// +// http://llvm.org/bugs/show_bug.cgi?id=11528 +// +// The workaround is to explicitly declare your copy constructor. +// +#define MOVE_ONLY_TYPE_FOR_CPP_03(type, rvalue_type) \ + private: \ + struct rvalue_type { \ + explicit rvalue_type(type* object) : object(object) {} \ + type* object; \ + }; \ + type(type&); \ + void operator=(type&); \ + public: \ + operator rvalue_type() { return rvalue_type(this); } \ + type Pass() { return type(rvalue_type(this)); } \ + typedef void MoveOnlyTypeForCPP03; \ + private: + +#endif // BUTIL_MOVE_H_ diff --git a/src/butil/numerics/safe_conversions.h b/src/butil/numerics/safe_conversions.h new file mode 100644 index 0000000..9a48811 --- /dev/null +++ b/src/butil/numerics/safe_conversions.h @@ -0,0 +1,88 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_SAFE_CONVERSIONS_H_ +#define BUTIL_SAFE_CONVERSIONS_H_ + +#include + +#include "butil/logging.h" +#include "butil/numerics/safe_conversions_impl.h" + +namespace butil { + +// Convenience function that returns true if the supplied value is in range +// for the destination type. +template +inline bool IsValueInRangeForNumericType(Src value) { + return internal::DstRangeRelationToSrcRange(value) == + internal::RANGE_VALID; +} + +// checked_cast<> is analogous to static_cast<> for numeric types, +// except that it CHECKs that the specified numeric conversion will not +// overflow or underflow. NaN source will always trigger a CHECK. +template +inline Dst checked_cast(Src value) { + CHECK(IsValueInRangeForNumericType(value)); + return static_cast(value); +} + +// saturated_cast<> is analogous to static_cast<> for numeric types, except +// that the specified numeric conversion will saturate rather than overflow or +// underflow. NaN assignment to an integral will trigger a CHECK condition. +template +inline Dst saturated_cast(Src value) { + // Optimization for floating point values, which already saturate. + if (std::numeric_limits::is_iec559) + return static_cast(value); + + switch (internal::DstRangeRelationToSrcRange(value)) { + case internal::RANGE_VALID: + return static_cast(value); + + case internal::RANGE_UNDERFLOW: + return std::numeric_limits::min(); + + case internal::RANGE_OVERFLOW: + return std::numeric_limits::max(); + + // Should fail only on attempting to assign NaN to a saturated integer. + case internal::RANGE_INVALID: + CHECK(false); + return std::numeric_limits::max(); + } + + NOTREACHED(); + return static_cast(value); +} + +inline uint64_t safe_abs(uint64_t x) { + return x; +} + +inline uint64_t safe_abs(int64_t x) { + return (x >= 0) ? (uint64_t)x : ((~(uint64_t)(x)) + 1); +} + +inline uint32_t safe_abs(uint32_t x) { + return x; +} + +inline uint32_t safe_abs(int32_t x) { + return (uint32_t)safe_abs((int64_t)x); +} + +#if defined(__APPLE__) +inline unsigned long safe_abs(unsigned long x) { + return x; +} +inline unsigned long safe_abs(long x) { + return (x >= 0) ? (unsigned long)x : ((~(unsigned long)(x)) + 1); +} +#endif + +} // namespace butil + +#endif // BUTIL_SAFE_CONVERSIONS_H_ diff --git a/src/butil/numerics/safe_conversions_impl.h b/src/butil/numerics/safe_conversions_impl.h new file mode 100644 index 0000000..d4a4910 --- /dev/null +++ b/src/butil/numerics/safe_conversions_impl.h @@ -0,0 +1,216 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_SAFE_CONVERSIONS_IMPL_H_ +#define BUTIL_SAFE_CONVERSIONS_IMPL_H_ + +#include + +#include "butil/macros.h" +#include "butil/type_traits.h" + +namespace butil { +namespace internal { + +// The std library doesn't provide a binary max_exponent for integers, however +// we can compute one by adding one to the number of non-sign bits. This allows +// for accurate range comparisons between floating point and integer types. +template +struct MaxExponent { + static const int value = std::numeric_limits::is_iec559 + ? std::numeric_limits::max_exponent + : (sizeof(NumericType) * 8 + 1 - + std::numeric_limits::is_signed); +}; + +enum IntegerRepresentation { + INTEGER_REPRESENTATION_UNSIGNED, + INTEGER_REPRESENTATION_SIGNED +}; + +// A range for a given nunmeric Src type is contained for a given numeric Dst +// type if both numeric_limits::max() <= numeric_limits::max() and +// numeric_limits::min() >= numeric_limits::min() are true. +// We implement this as template specializations rather than simple static +// comparisons to ensure type correctness in our comparisons. +enum NumericRangeRepresentation { + NUMERIC_RANGE_NOT_CONTAINED, + NUMERIC_RANGE_CONTAINED +}; + +// Helper templates to statically determine if our destination type can contain +// maximum and minimum values represented by the source type. + +template < + typename Dst, + typename Src, + IntegerRepresentation DstSign = std::numeric_limits::is_signed + ? INTEGER_REPRESENTATION_SIGNED + : INTEGER_REPRESENTATION_UNSIGNED, + IntegerRepresentation SrcSign = + std::numeric_limits::is_signed + ? INTEGER_REPRESENTATION_SIGNED + : INTEGER_REPRESENTATION_UNSIGNED > +struct StaticDstRangeRelationToSrcRange; + +// Same sign: Dst is guaranteed to contain Src only if its range is equal or +// larger. +template +struct StaticDstRangeRelationToSrcRange { + static const NumericRangeRepresentation value = + MaxExponent::value >= MaxExponent::value + ? NUMERIC_RANGE_CONTAINED + : NUMERIC_RANGE_NOT_CONTAINED; +}; + +// Unsigned to signed: Dst is guaranteed to contain source only if its range is +// larger. +template +struct StaticDstRangeRelationToSrcRange { + static const NumericRangeRepresentation value = + MaxExponent::value > MaxExponent::value + ? NUMERIC_RANGE_CONTAINED + : NUMERIC_RANGE_NOT_CONTAINED; +}; + +// Signed to unsigned: Dst cannot be statically determined to contain Src. +template +struct StaticDstRangeRelationToSrcRange { + static const NumericRangeRepresentation value = NUMERIC_RANGE_NOT_CONTAINED; +}; + +enum RangeConstraint { + RANGE_VALID = 0x0, // Value can be represented by the destination type. + RANGE_UNDERFLOW = 0x1, // Value would overflow. + RANGE_OVERFLOW = 0x2, // Value would underflow. + RANGE_INVALID = RANGE_UNDERFLOW | RANGE_OVERFLOW // Invalid (i.e. NaN). +}; + +// Helper function for coercing an int back to a RangeContraint. +inline RangeConstraint GetRangeConstraint(int integer_range_constraint) { + DCHECK(integer_range_constraint >= RANGE_VALID && + integer_range_constraint <= RANGE_INVALID); + return static_cast(integer_range_constraint); +} + +// This function creates a RangeConstraint from an upper and lower bound +// check by taking advantage of the fact that only NaN can be out of range in +// both directions at once. +inline RangeConstraint GetRangeConstraint(bool is_in_upper_bound, + bool is_in_lower_bound) { + return GetRangeConstraint((is_in_upper_bound ? 0 : RANGE_OVERFLOW) | + (is_in_lower_bound ? 0 : RANGE_UNDERFLOW)); +} + +template < + typename Dst, + typename Src, + IntegerRepresentation DstSign = std::numeric_limits::is_signed + ? INTEGER_REPRESENTATION_SIGNED + : INTEGER_REPRESENTATION_UNSIGNED, + IntegerRepresentation SrcSign = std::numeric_limits::is_signed + ? INTEGER_REPRESENTATION_SIGNED + : INTEGER_REPRESENTATION_UNSIGNED, + NumericRangeRepresentation DstRange = + StaticDstRangeRelationToSrcRange::value > +struct DstRangeRelationToSrcRangeImpl; + +// The following templates are for ranges that must be verified at runtime. We +// split it into checks based on signedness to avoid confusing casts and +// compiler warnings on signed an unsigned comparisons. + +// Dst range is statically determined to contain Src: Nothing to check. +template +struct DstRangeRelationToSrcRangeImpl { + static RangeConstraint Check(Src value) { return RANGE_VALID; } +}; + +// Signed to signed narrowing: Both the upper and lower boundaries may be +// exceeded. +template +struct DstRangeRelationToSrcRangeImpl { + static RangeConstraint Check(Src value) { + return std::numeric_limits::is_iec559 + ? GetRangeConstraint(value <= std::numeric_limits::max(), + value >= -std::numeric_limits::max()) + : GetRangeConstraint(value <= std::numeric_limits::max(), + value >= std::numeric_limits::min()); + } +}; + +// Unsigned to unsigned narrowing: Only the upper boundary can be exceeded. +template +struct DstRangeRelationToSrcRangeImpl { + static RangeConstraint Check(Src value) { + return GetRangeConstraint(value <= std::numeric_limits::max(), true); + } +}; + +// Unsigned to signed: The upper boundary may be exceeded. +template +struct DstRangeRelationToSrcRangeImpl { + static RangeConstraint Check(Src value) { + return sizeof(Dst) > sizeof(Src) + ? RANGE_VALID + : GetRangeConstraint( + value <= static_cast(std::numeric_limits::max()), + true); + } +}; + +// Signed to unsigned: The upper boundary may be exceeded for a narrower Dst, +// and any negative value exceeds the lower boundary. +template +struct DstRangeRelationToSrcRangeImpl { + static RangeConstraint Check(Src value) { + return (MaxExponent::value >= MaxExponent::value) + ? GetRangeConstraint(true, value >= static_cast(0)) + : GetRangeConstraint( + value <= static_cast(std::numeric_limits::max()), + value >= static_cast(0)); + } +}; + +template +inline RangeConstraint DstRangeRelationToSrcRange(Src value) { + COMPILE_ASSERT(std::numeric_limits::is_specialized, + argument_must_be_numeric); + COMPILE_ASSERT(std::numeric_limits::is_specialized, + result_must_be_numeric); + return DstRangeRelationToSrcRangeImpl::Check(value); +} + +} // namespace internal +} // namespace butil + +#endif // BUTIL_SAFE_CONVERSIONS_IMPL_H_ diff --git a/src/butil/numerics/safe_math.h b/src/butil/numerics/safe_math.h new file mode 100644 index 0000000..38b0fd2 --- /dev/null +++ b/src/butil/numerics/safe_math.h @@ -0,0 +1,271 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_SAFE_MATH_H_ +#define BUTIL_SAFE_MATH_H_ + +#include "butil/numerics/safe_math_impl.h" + +namespace butil { + +namespace internal { + +// CheckedNumeric implements all the logic and operators for detecting integer +// boundary conditions such as overflow, underflow, and invalid conversions. +// The CheckedNumeric type implicitly converts from floating point and integer +// data types, and contains overloads for basic arithmetic operations (i.e.: +, +// -, *, /, %). +// +// The following methods convert from CheckedNumeric to standard numeric values: +// IsValid() - Returns true if the underlying numeric value is valid (i.e. has +// has not wrapped and is not the result of an invalid conversion). +// ValueOrDie() - Returns the underlying value. If the state is not valid this +// call will crash on a CHECK. +// ValueOrDefault() - Returns the current value, or the supplied default if the +// state is not valid. +// ValueFloating() - Returns the underlying floating point value (valid only +// only for floating point CheckedNumeric types). +// +// Bitwise operations are explicitly not supported, because correct +// handling of some cases (e.g. sign manipulation) is ambiguous. Comparison +// operations are explicitly not supported because they could result in a crash +// on a CHECK condition. You should use patterns like the following for these +// operations: +// Bitwise operation: +// CheckedNumeric checked_int = untrusted_input_value; +// int x = checked_int.ValueOrDefault(0) | kFlagValues; +// Comparison: +// CheckedNumeric checked_size; +// CheckedNumeric checked_size = untrusted_input_value; +// checked_size = checked_size + HEADER LENGTH; +// if (checked_size.IsValid() && checked_size.ValueOrDie() < buffer_size) +// Do stuff... +template +class CheckedNumeric { + public: + typedef T type; + + CheckedNumeric() {} + + // Copy constructor. + template + CheckedNumeric(const CheckedNumeric& rhs) + : state_(rhs.ValueUnsafe(), rhs.validity()) {} + + template + CheckedNumeric(Src value, RangeConstraint validity) + : state_(value, validity) {} + + // This is not an explicit constructor because we implicitly upgrade regular + // numerics to CheckedNumerics to make them easier to use. + template + CheckedNumeric(Src value) + : state_(value) { + COMPILE_ASSERT(std::numeric_limits::is_specialized, + argument_must_be_numeric); + } + + // IsValid() is the public API to test if a CheckedNumeric is currently valid. + bool IsValid() const { return validity() == RANGE_VALID; } + + // ValueOrDie() The primary accessor for the underlying value. If the current + // state is not valid it will CHECK and crash. + T ValueOrDie() const { + CHECK(IsValid()); + return state_.value(); + } + + // ValueOrDefault(T default_value) A convenience method that returns the + // current value if the state is valid, and the supplied default_value for + // any other state. + T ValueOrDefault(T default_value) const { + return IsValid() ? state_.value() : default_value; + } + + // ValueFloating() - Since floating point values include their validity state, + // we provide an easy method for extracting them directly, without a risk of + // crashing on a CHECK. + T ValueFloating() const { + COMPILE_ASSERT(std::numeric_limits::is_iec559, argument_must_be_float); + return CheckedNumeric::cast(*this).ValueUnsafe(); + } + + // validity() - DO NOT USE THIS IN EXTERNAL CODE - It is public right now for + // tests and to avoid a big matrix of friend operator overloads. But the + // values it returns are likely to change in the future. + // Returns: current validity state (i.e. valid, overflow, underflow, nan). + // TODO(jschuh): crbug.com/332611 Figure out and implement semantics for + // saturation/wrapping so we can expose this state consistently and implement + // saturated arithmetic. + RangeConstraint validity() const { return state_.validity(); } + + // ValueUnsafe() - DO NOT USE THIS IN EXTERNAL CODE - It is public right now + // for tests and to avoid a big matrix of friend operator overloads. But the + // values it returns are likely to change in the future. + // Returns: the raw numeric value, regardless of the current state. + // TODO(jschuh): crbug.com/332611 Figure out and implement semantics for + // saturation/wrapping so we can expose this state consistently and implement + // saturated arithmetic. + T ValueUnsafe() const { return state_.value(); } + + // Prototypes for the supported arithmetic operator overloads. + template CheckedNumeric& operator+=(Src rhs); + template CheckedNumeric& operator-=(Src rhs); + template CheckedNumeric& operator*=(Src rhs); + template CheckedNumeric& operator/=(Src rhs); + template CheckedNumeric& operator%=(Src rhs); + + CheckedNumeric operator-() const { + RangeConstraint validity; + T value = CheckedNeg(state_.value(), &validity); + // Negation is always valid for floating point. + if (std::numeric_limits::is_iec559) + return CheckedNumeric(value); + + validity = GetRangeConstraint(state_.validity() | validity); + return CheckedNumeric(value, validity); + } + + CheckedNumeric Abs() const { + RangeConstraint validity; + T value = CheckedAbs(state_.value(), &validity); + // Absolute value is always valid for floating point. + if (std::numeric_limits::is_iec559) + return CheckedNumeric(value); + + validity = GetRangeConstraint(state_.validity() | validity); + return CheckedNumeric(value, validity); + } + + CheckedNumeric& operator++() { + *this += 1; + return *this; + } + + CheckedNumeric operator++(int) { + CheckedNumeric value = *this; + *this += 1; + return value; + } + + CheckedNumeric& operator--() { + *this -= 1; + return *this; + } + + CheckedNumeric operator--(int) { + CheckedNumeric value = *this; + *this -= 1; + return value; + } + + // These static methods behave like a convenience cast operator targeting + // the desired CheckedNumeric type. As an optimization, a reference is + // returned when Src is the same type as T. + template + static CheckedNumeric cast( + Src u, + typename enable_if::is_specialized, int>::type = + 0) { + return u; + } + + template + static CheckedNumeric cast( + const CheckedNumeric& u, + typename enable_if::value, int>::type = 0) { + return u; + } + + static const CheckedNumeric& cast(const CheckedNumeric& u) { return u; } + + private: + CheckedNumericState state_; +}; + +// This is the boilerplate for the standard arithmetic operator overloads. A +// macro isn't the prettiest solution, but it beats rewriting these five times. +// Some details worth noting are: +// * We apply the standard arithmetic promotions. +// * We skip range checks for floating points. +// * We skip range checks for destination integers with sufficient range. +// TODO(jschuh): extract these out into templates. +#define BUTIL_NUMERIC_ARITHMETIC_OPERATORS(NAME, OP, COMPOUND_OP) \ + /* Binary arithmetic operator for CheckedNumerics of the same type. */ \ + template \ + CheckedNumeric::type> operator OP( \ + const CheckedNumeric& lhs, const CheckedNumeric& rhs) { \ + typedef typename ArithmeticPromotion::type Promotion; \ + /* Floating point always takes the fast path */ \ + if (std::numeric_limits::is_iec559) \ + return CheckedNumeric(lhs.ValueUnsafe() OP rhs.ValueUnsafe()); \ + if (IsIntegerArithmeticSafe::value) \ + return CheckedNumeric( \ + lhs.ValueUnsafe() OP rhs.ValueUnsafe(), \ + GetRangeConstraint(rhs.validity() | lhs.validity())); \ + RangeConstraint validity = RANGE_VALID; \ + T result = Checked##NAME(static_cast(lhs.ValueUnsafe()), \ + static_cast(rhs.ValueUnsafe()), \ + &validity); \ + return CheckedNumeric( \ + result, \ + GetRangeConstraint(validity | lhs.validity() | rhs.validity())); \ + } \ + /* Assignment arithmetic operator implementation from CheckedNumeric. */ \ + template \ + template \ + CheckedNumeric& CheckedNumeric::operator COMPOUND_OP(Src rhs) { \ + *this = CheckedNumeric::cast(*this) OP CheckedNumeric::cast(rhs); \ + return *this; \ + } \ + /* Binary arithmetic operator for CheckedNumeric of different type. */ \ + template \ + CheckedNumeric::type> operator OP( \ + const CheckedNumeric& lhs, const CheckedNumeric& rhs) { \ + typedef typename ArithmeticPromotion::type Promotion; \ + if (IsIntegerArithmeticSafe::value) \ + return CheckedNumeric( \ + lhs.ValueUnsafe() OP rhs.ValueUnsafe(), \ + GetRangeConstraint(rhs.validity() | lhs.validity())); \ + return CheckedNumeric::cast(lhs) \ + OP CheckedNumeric::cast(rhs); \ + } \ + /* Binary arithmetic operator for left CheckedNumeric and right numeric. */ \ + template \ + CheckedNumeric::type> operator OP( \ + const CheckedNumeric& lhs, Src rhs) { \ + typedef typename ArithmeticPromotion::type Promotion; \ + if (IsIntegerArithmeticSafe::value) \ + return CheckedNumeric(lhs.ValueUnsafe() OP rhs, \ + lhs.validity()); \ + return CheckedNumeric::cast(lhs) \ + OP CheckedNumeric::cast(rhs); \ + } \ + /* Binary arithmetic operator for right numeric and left CheckedNumeric. */ \ + template \ + CheckedNumeric::type> operator OP( \ + Src lhs, const CheckedNumeric& rhs) { \ + typedef typename ArithmeticPromotion::type Promotion; \ + if (IsIntegerArithmeticSafe::value) \ + return CheckedNumeric(lhs OP rhs.ValueUnsafe(), \ + rhs.validity()); \ + return CheckedNumeric::cast(lhs) \ + OP CheckedNumeric::cast(rhs); \ + } + +BUTIL_NUMERIC_ARITHMETIC_OPERATORS(Add, +, += ) +BUTIL_NUMERIC_ARITHMETIC_OPERATORS(Sub, -, -= ) +BUTIL_NUMERIC_ARITHMETIC_OPERATORS(Mul, *, *= ) +BUTIL_NUMERIC_ARITHMETIC_OPERATORS(Div, /, /= ) +BUTIL_NUMERIC_ARITHMETIC_OPERATORS(Mod, %, %= ) + +#undef BUTIL_NUMERIC_ARITHMETIC_OPERATORS + +} // namespace internal + +using internal::CheckedNumeric; + +} // namespace butil + +#endif // BUTIL_SAFE_MATH_H_ diff --git a/src/butil/numerics/safe_math_impl.h b/src/butil/numerics/safe_math_impl.h new file mode 100644 index 0000000..73f96e0 --- /dev/null +++ b/src/butil/numerics/safe_math_impl.h @@ -0,0 +1,502 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SAFE_MATH_IMPL_H_ +#define SAFE_MATH_IMPL_H_ + +#include + +#include +#include +#include + +#include "butil/macros.h" +#include "butil/numerics/safe_conversions.h" +#include "butil/type_traits.h" + +namespace butil { +namespace internal { + +// Everything from here up to the floating point operations is portable C++, +// but it may not be fast. This code could be split based on +// platform/architecture and replaced with potentially faster implementations. + +// Integer promotion templates used by the portable checked integer arithmetic. +template +struct IntegerForSizeAndSign; +template <> +struct IntegerForSizeAndSign<1, true> { + typedef int8_t type; +}; +template <> +struct IntegerForSizeAndSign<1, false> { + typedef uint8_t type; +}; +template <> +struct IntegerForSizeAndSign<2, true> { + typedef int16_t type; +}; +template <> +struct IntegerForSizeAndSign<2, false> { + typedef uint16_t type; +}; +template <> +struct IntegerForSizeAndSign<4, true> { + typedef int32_t type; +}; +template <> +struct IntegerForSizeAndSign<4, false> { + typedef uint32_t type; +}; +template <> +struct IntegerForSizeAndSign<8, true> { + typedef int64_t type; +}; +template <> +struct IntegerForSizeAndSign<8, false> { + typedef uint64_t type; +}; + +// WARNING: We have no IntegerForSizeAndSign<16, *>. If we ever add one to +// support 128-bit math, then the ArithmeticPromotion template below will need +// to be updated (or more likely replaced with a decltype expression). + +template +struct UnsignedIntegerForSize { + typedef typename enable_if< + std::numeric_limits::is_integer, + typename IntegerForSizeAndSign::type>::type type; +}; + +template +struct SignedIntegerForSize { + typedef typename enable_if< + std::numeric_limits::is_integer, + typename IntegerForSizeAndSign::type>::type type; +}; + +template +struct TwiceWiderInteger { + typedef typename enable_if< + std::numeric_limits::is_integer, + typename IntegerForSizeAndSign< + sizeof(Integer) * 2, + std::numeric_limits::is_signed>::type>::type type; +}; + +template +struct PositionOfSignBit { + static const typename enable_if::is_integer, + size_t>::type value = 8 * sizeof(Integer) - 1; +}; + +// Helper templates for integer manipulations. + +template +bool HasSignBit(T x) { + // Cast to unsigned since right shift on signed is undefined. + return !!(static_cast::type>(x) >> + PositionOfSignBit::value); +} + +// This wrapper undoes the standard integer promotions. +template +T BinaryComplement(T x) { + return ~x; +} + +// Here are the actual portable checked integer math implementations. +// TODO(jschuh): Break this code out from the enable_if pattern and find a clean +// way to coalesce things into the CheckedNumericState specializations below. + +template +typename enable_if::is_integer, T>::type +CheckedAdd(T x, T y, RangeConstraint* validity) { + // Since the value of x+y is undefined if we have a signed type, we compute + // it using the unsigned type of the same size. + typedef typename UnsignedIntegerForSize::type UnsignedDst; + UnsignedDst ux = static_cast(x); + UnsignedDst uy = static_cast(y); + UnsignedDst uresult = ux + uy; + // Addition is valid if the sign of (x + y) is equal to either that of x or + // that of y. + if (std::numeric_limits::is_signed) { + if (HasSignBit(BinaryComplement((uresult ^ ux) & (uresult ^ uy)))) + *validity = RANGE_VALID; + else // Direction of wrap is inverse of result sign. + *validity = HasSignBit(uresult) ? RANGE_OVERFLOW : RANGE_UNDERFLOW; + + } else { // Unsigned is either valid or overflow. + *validity = BinaryComplement(x) >= y ? RANGE_VALID : RANGE_OVERFLOW; + } + return static_cast(uresult); +} + +template +typename enable_if::is_integer, T>::type +CheckedSub(T x, T y, RangeConstraint* validity) { + // Since the value of x+y is undefined if we have a signed type, we compute + // it using the unsigned type of the same size. + typedef typename UnsignedIntegerForSize::type UnsignedDst; + UnsignedDst ux = static_cast(x); + UnsignedDst uy = static_cast(y); + UnsignedDst uresult = ux - uy; + // Subtraction is valid if either x and y have same sign, or (x-y) and x have + // the same sign. + if (std::numeric_limits::is_signed) { + if (HasSignBit(BinaryComplement((uresult ^ ux) & (ux ^ uy)))) + *validity = RANGE_VALID; + else // Direction of wrap is inverse of result sign. + *validity = HasSignBit(uresult) ? RANGE_OVERFLOW : RANGE_UNDERFLOW; + + } else { // Unsigned is either valid or underflow. + *validity = x >= y ? RANGE_VALID : RANGE_UNDERFLOW; + } + return static_cast(uresult); +} + +// Integer multiplication is a bit complicated. In the fast case we just +// we just promote to a twice wider type, and range check the result. In the +// slow case we need to manually check that the result won't be truncated by +// checking with division against the appropriate bound. +template +typename enable_if< + std::numeric_limits::is_integer && sizeof(T) * 2 <= sizeof(uintmax_t), + T>::type +CheckedMul(T x, T y, RangeConstraint* validity) { + typedef typename TwiceWiderInteger::type IntermediateType; + IntermediateType tmp = + static_cast(x) * static_cast(y); + *validity = DstRangeRelationToSrcRange(tmp); + return static_cast(tmp); +} + +template +typename enable_if::is_integer&& std::numeric_limits< + T>::is_signed&&(sizeof(T) * 2 > sizeof(uintmax_t)), + T>::type +CheckedMul(T x, T y, RangeConstraint* validity) { + // if either side is zero then the result will be zero. + if (!(x || y)) { + return RANGE_VALID; + + } else if (x > 0) { + if (y > 0) + *validity = + x <= std::numeric_limits::max() / y ? RANGE_VALID : RANGE_OVERFLOW; + else + *validity = y >= std::numeric_limits::min() / x ? RANGE_VALID + : RANGE_UNDERFLOW; + + } else { + if (y > 0) + *validity = x >= std::numeric_limits::min() / y ? RANGE_VALID + : RANGE_UNDERFLOW; + else + *validity = + y >= std::numeric_limits::max() / x ? RANGE_VALID : RANGE_OVERFLOW; + } + + return x * y; +} + +template +typename enable_if::is_integer && + !std::numeric_limits::is_signed && + (sizeof(T) * 2 > sizeof(uintmax_t)), + T>::type +CheckedMul(T x, T y, RangeConstraint* validity) { + *validity = (y == 0 || x <= std::numeric_limits::max() / y) + ? RANGE_VALID + : RANGE_OVERFLOW; + return x * y; +} + +// Division just requires a check for an invalid negation on signed min/-1. +template +T CheckedDiv( + T x, + T y, + RangeConstraint* validity, + typename enable_if::is_integer, int>::type = 0) { + if (std::numeric_limits::is_signed && x == std::numeric_limits::min() && + y == static_cast(-1)) { + *validity = RANGE_OVERFLOW; + return std::numeric_limits::min(); + } + + *validity = RANGE_VALID; + return x / y; +} + +template +typename enable_if< + std::numeric_limits::is_integer&& std::numeric_limits::is_signed, + T>::type +CheckedMod(T x, T y, RangeConstraint* validity) { + *validity = y > 0 ? RANGE_VALID : RANGE_INVALID; + return x % y; +} + +template +typename enable_if< + std::numeric_limits::is_integer && !std::numeric_limits::is_signed, + T>::type +CheckedMod(T x, T y, RangeConstraint* validity) { + *validity = RANGE_VALID; + return x % y; +} + +template +typename enable_if< + std::numeric_limits::is_integer&& std::numeric_limits::is_signed, + T>::type +CheckedNeg(T value, RangeConstraint* validity) { + *validity = + value != std::numeric_limits::min() ? RANGE_VALID : RANGE_OVERFLOW; + // The negation of signed min is min, so catch that one. + return -value; +} + +template +typename enable_if< + std::numeric_limits::is_integer && !std::numeric_limits::is_signed, + T>::type +CheckedNeg(T value, RangeConstraint* validity) { + // The only legal unsigned negation is zero. + *validity = value ? RANGE_UNDERFLOW : RANGE_VALID; + return static_cast( + -static_cast::type>(value)); +} + +template +typename enable_if< + std::numeric_limits::is_integer&& std::numeric_limits::is_signed, + T>::type +CheckedAbs(T value, RangeConstraint* validity) { + *validity = + value != std::numeric_limits::min() ? RANGE_VALID : RANGE_OVERFLOW; + return std::abs(value); +} + +template +typename enable_if< + std::numeric_limits::is_integer && !std::numeric_limits::is_signed, + T>::type +CheckedAbs(T value, RangeConstraint* validity) { + // Absolute value of a positive is just its identiy. + *validity = RANGE_VALID; + return value; +} + +// These are the floating point stubs that the compiler needs to see. Only the +// negation operation is ever called. +#define BUTIL_FLOAT_ARITHMETIC_STUBS(NAME) \ + template \ + typename enable_if::is_iec559, T>::type \ + Checked##NAME(T, T, RangeConstraint*) { \ + NOTREACHED(); \ + return 0; \ + } + +BUTIL_FLOAT_ARITHMETIC_STUBS(Add) +BUTIL_FLOAT_ARITHMETIC_STUBS(Sub) +BUTIL_FLOAT_ARITHMETIC_STUBS(Mul) +BUTIL_FLOAT_ARITHMETIC_STUBS(Div) +BUTIL_FLOAT_ARITHMETIC_STUBS(Mod) + +#undef BUTIL_FLOAT_ARITHMETIC_STUBS + +template +typename enable_if::is_iec559, T>::type CheckedNeg( + T value, + RangeConstraint*) { + return -value; +} + +template +typename enable_if::is_iec559, T>::type CheckedAbs( + T value, + RangeConstraint*) { + return std::abs(value); +} + +// Floats carry around their validity state with them, but integers do not. So, +// we wrap the underlying value in a specialization in order to hide that detail +// and expose an interface via accessors. +enum NumericRepresentation { + NUMERIC_INTEGER, + NUMERIC_FLOATING, + NUMERIC_UNKNOWN +}; + +template +struct GetNumericRepresentation { + static const NumericRepresentation value = + std::numeric_limits::is_integer + ? NUMERIC_INTEGER + : (std::numeric_limits::is_iec559 ? NUMERIC_FLOATING + : NUMERIC_UNKNOWN); +}; + +template ::value> +class CheckedNumericState {}; + +// Integrals require quite a bit of additional housekeeping to manage state. +template +class CheckedNumericState { + private: + T value_; + RangeConstraint validity_; + + public: + template + friend class CheckedNumericState; + + CheckedNumericState() : value_(0), validity_(RANGE_VALID) {} + + template + CheckedNumericState(Src value, RangeConstraint validity) + : value_(value), + validity_(GetRangeConstraint(validity | + DstRangeRelationToSrcRange(value))) { + COMPILE_ASSERT(std::numeric_limits::is_specialized, + argument_must_be_numeric); + } + + // Copy constructor. + template + CheckedNumericState(const CheckedNumericState& rhs) + : value_(static_cast(rhs.value())), + validity_(GetRangeConstraint( + rhs.validity() | DstRangeRelationToSrcRange(rhs.value()))) {} + + template + explicit CheckedNumericState( + Src value, + typename enable_if::is_specialized, int>::type = + 0) + : value_(static_cast(value)), + validity_(DstRangeRelationToSrcRange(value)) {} + + RangeConstraint validity() const { return validity_; } + T value() const { return value_; } +}; + +// Floating points maintain their own validity, but need translation wrappers. +template +class CheckedNumericState { + private: + T value_; + + public: + template + friend class CheckedNumericState; + + CheckedNumericState() : value_(0.0) {} + + template + CheckedNumericState( + Src value, + RangeConstraint validity, + typename enable_if::is_integer, int>::type = 0) { + switch (DstRangeRelationToSrcRange(value)) { + case RANGE_VALID: + value_ = static_cast(value); + break; + + case RANGE_UNDERFLOW: + value_ = -std::numeric_limits::infinity(); + break; + + case RANGE_OVERFLOW: + value_ = std::numeric_limits::infinity(); + break; + + case RANGE_INVALID: + value_ = std::numeric_limits::quiet_NaN(); + break; + + default: + NOTREACHED(); + } + } + + template + explicit CheckedNumericState( + Src value, + typename enable_if::is_specialized, int>::type = + 0) + : value_(static_cast(value)) {} + + // Copy constructor. + template + CheckedNumericState(const CheckedNumericState& rhs) + : value_(static_cast(rhs.value())) {} + + RangeConstraint validity() const { + return GetRangeConstraint(value_ <= std::numeric_limits::max(), + value_ >= -std::numeric_limits::max()); + } + T value() const { return value_; } +}; + +// For integers less than 128-bit and floats 32-bit or larger, we can distil +// C/C++ arithmetic promotions down to two simple rules: +// 1. The type with the larger maximum exponent always takes precedence. +// 2. The resulting type must be promoted to at least an int. +// The following template specializations implement that promotion logic. +enum ArithmeticPromotionCategory { + LEFT_PROMOTION, + RIGHT_PROMOTION, + DEFAULT_PROMOTION +}; + +template ::value > MaxExponent::value) + ? (MaxExponent::value > MaxExponent::value + ? LEFT_PROMOTION + : DEFAULT_PROMOTION) + : (MaxExponent::value > MaxExponent::value + ? RIGHT_PROMOTION + : DEFAULT_PROMOTION) > +struct ArithmeticPromotion; + +template +struct ArithmeticPromotion { + typedef Lhs type; +}; + +template +struct ArithmeticPromotion { + typedef Rhs type; +}; + +template +struct ArithmeticPromotion { + typedef int type; +}; + +// We can statically check if operations on the provided types can wrap, so we +// can skip the checked operations if they're not needed. So, for an integer we +// care if the destination type preserves the sign and is twice the width of +// the source. +template +struct IsIntegerArithmeticSafe { + static const bool value = !std::numeric_limits::is_iec559 && + StaticDstRangeRelationToSrcRange::value == + NUMERIC_RANGE_CONTAINED && + sizeof(T) >= (2 * sizeof(Lhs)) && + StaticDstRangeRelationToSrcRange::value != + NUMERIC_RANGE_CONTAINED && + sizeof(T) >= (2 * sizeof(Rhs)); +}; + +} // namespace internal +} // namespace butil + +#endif // SAFE_MATH_IMPL_H_ diff --git a/src/butil/object_pool.h b/src/butil/object_pool.h new file mode 100644 index 0000000..92bc403 --- /dev/null +++ b/src/butil/object_pool.h @@ -0,0 +1,114 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Sun Jul 13 15:04:18 CST 2014 + +#ifndef BUTIL_OBJECT_POOL_H +#define BUTIL_OBJECT_POOL_H + +#include // size_t +#include "butil/type_traits.h" + +// ObjectPool is a derivative class of ResourcePool to allocate and +// reuse fixed-size objects without identifiers. + +namespace butil { + +// Specialize following classes to override default parameters for type T. +// namespace butil { +// template <> struct ObjectPoolBlockMaxSize { +// static const size_t value = 1024; +// }; +// } + +// Memory is allocated in blocks, memory size of a block will not exceed: +// min(ObjectPoolBlockMaxSize::value, +// ObjectPoolBlockMaxItem::value * sizeof(T)) +template struct ObjectPoolBlockMaxSize { + static const size_t value = 64 * 1024; // bytes +}; +template struct ObjectPoolBlockMaxItem { + static const size_t value = 256; +}; + +// Free objects of each thread are grouped into a chunk before they are merged +// to the global list. Memory size of objects in one free chunk will not exceed: +// min(ObjectPoolFreeChunkMaxItem::value() * sizeof(T), +// ObjectPoolBlockMaxSize::value, +// ObjectPoolBlockMaxItem::value * sizeof(T)) +template struct ObjectPoolFreeChunkMaxItem { + static size_t value() { return 256; } +}; + +// ObjectPool calls this function on newly constructed objects. If this +// function returns false, the object is destructed immediately and +// get_object() shall return NULL. This is useful when the constructor +// failed internally(namely ENOMEM). +template struct ObjectPoolValidator { + static bool validate(const T*) { return true; } +}; + +// +template +struct ObjectPoolWithASanPoison : true_type {}; + +} // namespace butil + +#include "butil/object_pool_inl.h" + +namespace butil { + +// Whether if FreeChunk of LocalPool is empty. +template inline bool local_pool_free_empty() { + return ObjectPool::singleton()->local_free_empty(); +} + +// Get an object typed |T|. The object should be cleared before usage. +// NOTE: If there are no arguments, T must be default-constructible. +template +inline T* get_object(Args&&... args) { + return ObjectPool::singleton()->get_object(std::forward(args)...); +} + +// Return the object |ptr| back. The object is NOT destructed and will be +// returned by later get_object. Similar with free/delete, validity of +// the object is not checked, user shall not return a not-yet-allocated or +// already-returned object otherwise behavior is undefined. +// Returns 0 when successful, -1 otherwise. +template inline int return_object(T* ptr) { + return ObjectPool::singleton()->return_object(ptr); +} + +// Reclaim all allocated objects typed T if caller is the last thread called +// this function, otherwise do nothing. You rarely need to call this function +// manually because it's called automatically when each thread quits. +template inline void clear_objects() { + ObjectPool::singleton()->clear_objects(); +} + +// Get description of objects typed T. +// This function is possibly slow because it iterates internal structures. +// Don't use it frequently like a "getter" function. +template ObjectPoolInfo describe_objects() { + return ObjectPool::singleton()->describe_objects(); +} + +} // namespace butil + +#endif // BUTIL_OBJECT_POOL_H diff --git a/src/butil/object_pool_inl.h b/src/butil/object_pool_inl.h new file mode 100644 index 0000000..c98ec16 --- /dev/null +++ b/src/butil/object_pool_inl.h @@ -0,0 +1,644 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Sun Jul 13 15:04:18 CST 2014 + +#ifndef BUTIL_OBJECT_POOL_INL_H +#define BUTIL_OBJECT_POOL_INL_H + +#include // std::ostream +#include // pthread_mutex_t +#include // std::max, std::min +#include + +#include "butil/atomicops.h" // butil::atomic +#include "butil/macros.h" // BAIDU_CACHELINE_ALIGNMENT +#include "butil/scoped_lock.h" // BAIDU_SCOPED_LOCK +#include "butil/thread_local.h" // BAIDU_THREAD_LOCAL +#include "butil/memory/aligned_memory.h" // butil::AlignedMemory +#include "butil/debug/address_annotations.h" + +#ifdef BUTIL_OBJECT_POOL_NEED_FREE_ITEM_NUM +#define BAIDU_OBJECT_POOL_FREE_ITEM_NUM_ADD1 \ + (_global_nfree.fetch_add(1, butil::memory_order_relaxed)) +#define BAIDU_OBJECT_POOL_FREE_ITEM_NUM_SUB1 \ + (_global_nfree.fetch_sub(1, butil::memory_order_relaxed)) +#else +#define BAIDU_OBJECT_POOL_FREE_ITEM_NUM_ADD1 +#define BAIDU_OBJECT_POOL_FREE_ITEM_NUM_SUB1 +#endif + +namespace butil { + +template +struct ObjectPoolFreeChunk { + size_t nfree; + T* ptrs[NITEM]; +}; +// for gcc 3.4.5 +template +struct ObjectPoolFreeChunk { + size_t nfree; + T* ptrs[0]; +}; + +struct ObjectPoolInfo { + size_t local_pool_num; + size_t block_group_num; + size_t block_num; + size_t item_num; + size_t block_item_num; + size_t free_chunk_item_num; + size_t total_size; +#ifdef BUTIL_OBJECT_POOL_NEED_FREE_ITEM_NUM + size_t free_item_num; +#endif +}; + +static const size_t OP_MAX_BLOCK_NGROUP = 65536; +static const size_t OP_GROUP_NBLOCK_NBIT = 16; +static const size_t OP_GROUP_NBLOCK = (1UL << OP_GROUP_NBLOCK_NBIT); +static const size_t OP_INITIAL_FREE_LIST_SIZE = 1024; + +template +class ObjectPoolBlockItemNum { + static const size_t N1 = ObjectPoolBlockMaxSize::value / sizeof(T); + static const size_t N2 = (N1 < 1 ? 1 : N1); +public: + static const size_t value = (N2 > ObjectPoolBlockMaxItem::value ? + ObjectPoolBlockMaxItem::value : N2); +}; + +template +class BAIDU_CACHELINE_ALIGNMENT ObjectPool { +private: +#ifdef BUTIL_USE_ASAN + static void asan_poison_memory_region(T* ptr) { + if (!ObjectPoolWithASanPoison::value || NULL == ptr) { + return; + } + // Marks the object as addressable. + BUTIL_ASAN_POISON_MEMORY_REGION(ptr, sizeof(T)); + } + static void asan_unpoison_memory_region(T* ptr) { + if (!ObjectPoolWithASanPoison::value || NULL == ptr) { + return; + } + // Marks the object as unaddressable. + BUTIL_ASAN_UNPOISON_MEMORY_REGION(ptr, sizeof(T)); + } +#define OBJECT_POOL_ASAN_POISON_MEMORY_REGION(ptr) asan_poison_memory_region(ptr) +#define OBJECT_POOL_ASAN_UNPOISON_MEMORY_REGION(ptr) asan_unpoison_memory_region(ptr) +#else +#define OBJECT_POOL_ASAN_POISON_MEMORY_REGION(ptr) ((void)(ptr)) +#define OBJECT_POOL_ASAN_UNPOISON_MEMORY_REGION(ptr) ((void)(ptr)) +#endif // BUTIL_USE_ASAN + +public: + static const size_t BLOCK_NITEM = ObjectPoolBlockItemNum::value; + static const size_t FREE_CHUNK_NITEM = BLOCK_NITEM; + + // Free objects are batched in a FreeChunk before they're added to + // global list(_free_chunks). + typedef ObjectPoolFreeChunk FreeChunk; + typedef ObjectPoolFreeChunk DynamicFreeChunk; +#ifdef BUTIL_USE_ASAN + // According to https://github.com/google/sanitizers/wiki/AddressSanitizerManualPoisoning , + // The allocated chunks should start with 8-aligned addresses, + // so that AlignedMemory starts with at least 8-aligned addresses. + typedef AlignedMemory BlockItem; +#else + typedef AlignedMemory BlockItem; +#endif + + // When a thread needs memory, it allocates a Block. To improve locality, + // items in the Block are only used by the thread. + // To support cache-aligned objects, align Block.items by cacheline. + struct BAIDU_CACHELINE_ALIGNMENT Block { + BlockItem items[BLOCK_NITEM]; + size_t nitem; + + Block() : nitem(0) {} + }; + + // An Object addresses at most OP_MAX_BLOCK_NGROUP BlockGroups, + // each BlockGroup addresses at most OP_GROUP_NBLOCK blocks. So an + // object addresses at most OP_MAX_BLOCK_NGROUP * OP_GROUP_NBLOCK Blocks. + struct BlockGroup { + butil::atomic nblock; + butil::atomic blocks[OP_GROUP_NBLOCK]; + + BlockGroup() : nblock(0) { + // We fetch_add nblock in add_block() before setting the entry, + // thus address_resource() may sees the unset entry. Initialize + // all entries to NULL makes such address_resource() return NULL. + memset(static_cast(blocks), 0, sizeof(butil::atomic) * OP_GROUP_NBLOCK); + } + }; + + // Each thread has an instance of this class. + class BAIDU_CACHELINE_ALIGNMENT LocalPool { + public: + explicit LocalPool(ObjectPool* pool) + : _pool(pool) + , _cur_block(NULL) + , _cur_block_index(0) { + _cur_free.nfree = 0; + } + + ~LocalPool() { + // Add to global _free if there're some free objects + if (_cur_free.nfree) { + _pool->push_free_chunk(_cur_free); + } + + _pool->clear_from_destructor_of_local_pool(); + } + + static void delete_local_pool(void* arg) { + delete (LocalPool*)arg; + } + + // We need following macro to construct T with different CTOR_ARGS + // which may include parenthesis because when T is POD, "new T()" + // and "new T" are different: former one sets all fields to 0 which + // we don't want. +#define BAIDU_OBJECT_POOL_GET(CTOR_ARGS) \ + /* Fetch local free ptr */ \ + if (_cur_free.nfree) { \ + BAIDU_OBJECT_POOL_FREE_ITEM_NUM_SUB1; \ + return _cur_free.ptrs[--_cur_free.nfree]; \ + } \ + /* Fetch a FreeChunk from global. \ + TODO: Popping from _free needs to copy a FreeChunk which is \ + costly, but hardly impacts amortized performance. */ \ + if (_pool->pop_free_chunk(_cur_free)) { \ + BAIDU_OBJECT_POOL_FREE_ITEM_NUM_SUB1; \ + return _cur_free.ptrs[--_cur_free.nfree]; \ + } \ + T* obj = NULL; \ + /* Fetch memory from local block */ \ + if (_cur_block && _cur_block->nitem < BLOCK_NITEM) { \ + auto item = _cur_block->items + _cur_block->nitem; \ + obj = new (item->void_data()) T CTOR_ARGS; \ + if (!ObjectPoolValidator::validate(obj)) { \ + obj->~T(); \ + return NULL; \ + } \ + /* It's poisoned prior to use. */ \ + OBJECT_POOL_ASAN_POISON_MEMORY_REGION(obj); \ + ++_cur_block->nitem; \ + return obj; \ + } \ + /* Fetch a Block from global */ \ + _cur_block = add_block(&_cur_block_index); \ + if (_cur_block != NULL) { \ + auto item = _cur_block->items + _cur_block->nitem; \ + obj = new (item->void_data()) T CTOR_ARGS; \ + if (!ObjectPoolValidator::validate(obj)) { \ + obj->~T(); \ + return NULL; \ + } \ + /* It's poisoned prior to use. */ \ + OBJECT_POOL_ASAN_POISON_MEMORY_REGION(obj); \ + ++_cur_block->nitem; \ + return obj; \ + } \ + return NULL; \ + + + inline T* get() { + BAIDU_OBJECT_POOL_GET(); + } + + template + inline T* get(Args&&... args) { + BAIDU_OBJECT_POOL_GET((std::forward(args)...)); + } + +#undef BAIDU_OBJECT_POOL_GET + + inline int return_object(T* ptr) { + // TODO. Refer to ASan to implement a efficient quarantine mechanism. + OBJECT_POOL_ASAN_POISON_MEMORY_REGION(ptr); + + // Return to local free list + if (_cur_free.nfree < ObjectPool::free_chunk_nitem()) { + _cur_free.ptrs[_cur_free.nfree++] = ptr; + BAIDU_OBJECT_POOL_FREE_ITEM_NUM_ADD1; + return 0; + } + // Local free list is full, return it to global. + // For copying issue, check comment in upper get() + if (_pool->push_free_chunk(_cur_free)) { + _cur_free.nfree = 1; + _cur_free.ptrs[0] = ptr; + BAIDU_OBJECT_POOL_FREE_ITEM_NUM_ADD1; + return 0; + } + return -1; + } + + inline bool free_empty() const { + return 0 == _cur_free.nfree; + } + + private: + ObjectPool* _pool; + Block* _cur_block; + size_t _cur_block_index; + FreeChunk _cur_free; + }; + + inline bool local_free_empty() { + LocalPool* lp = get_or_new_local_pool(); + if (BAIDU_LIKELY(lp != NULL)) { + return lp->free_empty(); + } + return true; + } + + template + inline T* get_object(Args&&... args) { + LocalPool* lp = get_or_new_local_pool(); + T* ptr = NULL; + if (BAIDU_LIKELY(lp != NULL)) { + ptr = lp->get(std::forward(args)...); + OBJECT_POOL_ASAN_UNPOISON_MEMORY_REGION(ptr); + } + return ptr; + } + + inline int return_object(T* ptr) { + LocalPool* lp = get_or_new_local_pool(); + if (BAIDU_LIKELY(lp != NULL)) { + return lp->return_object(ptr); + } + return -1; + } + + void clear_objects() { + LocalPool* lp = _local_pool; + if (lp) { + _local_pool = NULL; + butil::thread_atexit_cancel(LocalPool::delete_local_pool, lp); + delete lp; + } + } + + inline static size_t free_chunk_nitem() { + const size_t n = ObjectPoolFreeChunkMaxItem::value(); + return (n < FREE_CHUNK_NITEM ? n : FREE_CHUNK_NITEM); + } + + // Number of all allocated objects, including being used and free. + ObjectPoolInfo describe_objects() const { + ObjectPoolInfo info; + info.local_pool_num = _nlocal.load(butil::memory_order_relaxed); + info.block_group_num = _ngroup.load(butil::memory_order_acquire); + info.block_num = 0; + info.item_num = 0; + info.free_chunk_item_num = free_chunk_nitem(); + info.block_item_num = BLOCK_NITEM; +#ifdef BUTIL_OBJECT_POOL_NEED_FREE_ITEM_NUM + info.free_item_num = _global_nfree.load(butil::memory_order_relaxed); +#endif + + for (size_t i = 0; i < info.block_group_num; ++i) { + BlockGroup* bg = _block_groups[i].load(butil::memory_order_consume); + if (NULL == bg) { + break; + } + size_t nblock = std::min(bg->nblock.load(butil::memory_order_relaxed), + OP_GROUP_NBLOCK); + info.block_num += nblock; + for (size_t j = 0; j < nblock; ++j) { + Block* b = bg->blocks[j].load(butil::memory_order_consume); + if (NULL != b) { + info.item_num += b->nitem; + } + } + } + info.total_size = info.block_num * info.block_item_num * sizeof(T); + return info; + } + + static inline ObjectPool* singleton() { + ObjectPool* p = _singleton.load(butil::memory_order_consume); + if (p) { + return p; + } + pthread_mutex_lock(&_singleton_mutex); + p = _singleton.load(butil::memory_order_consume); + if (!p) { + p = new ObjectPool(); + _singleton.store(p, butil::memory_order_release); + } + pthread_mutex_unlock(&_singleton_mutex); + return p; + } + +private: + ObjectPool() { + _free_chunks.reserve(OP_INITIAL_FREE_LIST_SIZE); + pthread_mutex_init(&_free_chunks_mutex, NULL); +#if defined(BUTIL_USE_ASAN) && \ + !defined(BAIDU_CLEAR_OBJECT_POOL_AFTER_ALL_THREADS_QUIT) + // Objects returned to the pool stay ASan-poisoned (see return_object()). + // LeakSanitizer skips poisoned memory when scanning for live pointers + // (its `use_poisoned' option is off by default), so heap memory that is + // only reachable through pointers stored inside pooled objects (e.g. + // std::string buffers owned by cached protobuf messages) would be + // falsely reported as leaked at process exit. Un-poison all pooled + // objects right before LSan runs. LSan registers its leak check via + // atexit() very early during sanitizer init, so this handler (registered + // lazily on the first singleton creation) runs before it because atexit + // handlers execute in LIFO order. + // Not needed when BAIDU_CLEAR_OBJECT_POOL_AFTER_ALL_THREADS_QUIT is + // defined: clear_from_destructor_of_local_pool() then destructs and + // frees all pooled objects after the last thread quits. + if (ObjectPoolWithASanPoison::value) { + atexit(unpoison_all_objects_before_leak_check); + } +#endif + } + + ~ObjectPool() { + pthread_mutex_destroy(&_free_chunks_mutex); + } + +#if defined(BUTIL_USE_ASAN) && \ + !defined(BAIDU_CLEAR_OBJECT_POOL_AFTER_ALL_THREADS_QUIT) + // Un-poison every constructed object still held by the pool so that + // LeakSanitizer can follow the pointers inside them. Only un-poisons, does + // not destruct: the pool intentionally keeps objects alive for reuse, and + // they remain reachable from the singleton, so they are not real leaks. + static void unpoison_all_objects_before_leak_check() { + if (NULL == _singleton.load(butil::memory_order_consume)) { + return; + } + const size_t ngroup = _ngroup.load(butil::memory_order_acquire); + for (size_t i = 0; i < ngroup; ++i) { + BlockGroup* bg = _block_groups[i].load(butil::memory_order_consume); + if (NULL == bg) { + break; + } + const size_t nblock = std::min( + bg->nblock.load(butil::memory_order_relaxed), OP_GROUP_NBLOCK); + for (size_t j = 0; j < nblock; ++j) { + Block* b = bg->blocks[j].load(butil::memory_order_consume); + if (NULL == b) { + continue; + } + for (size_t k = 0; k < b->nitem; ++k) { + asan_unpoison_memory_region((T*)&b->items[k]); + } + } + } + } +#endif + + // Create a Block and append it to right-most BlockGroup. + static Block* add_block(size_t* index) { + Block* const new_block = new(std::nothrow) Block; + if (NULL == new_block) { + return NULL; + } + size_t ngroup; + do { + ngroup = _ngroup.load(butil::memory_order_acquire); + if (ngroup >= 1) { + BlockGroup* const g = + _block_groups[ngroup - 1].load(butil::memory_order_consume); + const size_t block_index = + g->nblock.fetch_add(1, butil::memory_order_relaxed); + if (block_index < OP_GROUP_NBLOCK) { + g->blocks[block_index].store( + new_block, butil::memory_order_release); + *index = (ngroup - 1) * OP_GROUP_NBLOCK + block_index; + return new_block; + } + g->nblock.fetch_sub(1, butil::memory_order_relaxed); + } + } while (add_block_group(ngroup)); + + // Fail to add_block_group. + delete new_block; + return NULL; + } + + // Create a BlockGroup and append it to _block_groups. + // Shall be called infrequently because a BlockGroup is pretty big. + static bool add_block_group(size_t old_ngroup) { + BlockGroup* bg = NULL; + BAIDU_SCOPED_LOCK(_block_group_mutex); + const size_t ngroup = _ngroup.load(butil::memory_order_acquire); + if (ngroup != old_ngroup) { + // Other thread got lock and added group before this thread. + return true; + } + if (ngroup < OP_MAX_BLOCK_NGROUP) { + bg = new(std::nothrow) BlockGroup; + if (NULL != bg) { + // Release fence is paired with consume fence in add_block() + // to avoid un-constructed bg to be seen by other threads. + _block_groups[ngroup].store(bg, butil::memory_order_release); + _ngroup.store(ngroup + 1, butil::memory_order_release); + } + } + return bg != NULL; + } + + inline LocalPool* get_or_new_local_pool() { + LocalPool* lp = BAIDU_GET_VOLATILE_THREAD_LOCAL(_local_pool); + if (BAIDU_LIKELY(lp != NULL)) { + return lp; + } + lp = new(std::nothrow) LocalPool(this); + if (NULL == lp) { + return NULL; + } + BAIDU_SCOPED_LOCK(_change_thread_mutex); //avoid race with clear() + BAIDU_SET_VOLATILE_THREAD_LOCAL(_local_pool, lp); + butil::thread_atexit(LocalPool::delete_local_pool, lp); + _nlocal.fetch_add(1, butil::memory_order_relaxed); + return lp; + } + + void clear_from_destructor_of_local_pool() { + // Remove tls + _local_pool = NULL; + + // Do nothing if there're active threads. + if (_nlocal.fetch_sub(1, butil::memory_order_relaxed) != 1) { + return; + } + + // Can't delete global even if all threads(called ObjectPool + // functions) quit because the memory may still be referenced by + // other threads. But we need to validate that all memory can + // be deallocated correctly in tests, so wrap the function with + // a macro which is only defined in unittests. +#ifdef BAIDU_CLEAR_OBJECT_POOL_AFTER_ALL_THREADS_QUIT + BAIDU_SCOPED_LOCK(_change_thread_mutex); // including acquire fence. + // Do nothing if there're active threads. + if (_nlocal.load(butil::memory_order_relaxed) != 0) { + return; + } + // All threads exited and we're holding _change_thread_mutex to avoid + // racing with new threads calling get_object(). + + // Clear global free list. + FreeChunk dummy; + while (pop_free_chunk(dummy)); + + // Delete all memory + const size_t ngroup = _ngroup.exchange(0, butil::memory_order_relaxed); + for (size_t i = 0; i < ngroup; ++i) { + BlockGroup* bg = _block_groups[i].load(butil::memory_order_relaxed); + if (NULL == bg) { + break; + } + size_t nblock = std::min(bg->nblock.load(butil::memory_order_relaxed), + OP_GROUP_NBLOCK); + for (size_t j = 0; j < nblock; ++j) { + Block* b = bg->blocks[j].load(butil::memory_order_relaxed); + if (NULL == b) { + continue; + } + for (size_t k = 0; k < b->nitem; ++k) { + T* obj = (T*)&b->items[k]; + // Unpoison to avoid affecting other allocator. + OBJECT_POOL_ASAN_UNPOISON_MEMORY_REGION(obj); + obj->~T(); + } + delete b; + } + delete bg; + } + + memset(_block_groups, 0, sizeof(BlockGroup*) * OP_MAX_BLOCK_NGROUP); +#endif + } + +private: + bool pop_free_chunk(FreeChunk& c) { + // Critical for the case that most return_object are called in + // different threads of get_object. + if (_free_chunks.empty()) { + return false; + } + pthread_mutex_lock(&_free_chunks_mutex); + if (_free_chunks.empty()) { + pthread_mutex_unlock(&_free_chunks_mutex); + return false; + } + DynamicFreeChunk* p = _free_chunks.back(); + _free_chunks.pop_back(); + pthread_mutex_unlock(&_free_chunks_mutex); + c.nfree = p->nfree; + memcpy(c.ptrs, p->ptrs, sizeof(*p->ptrs) * p->nfree); + free(p); + return true; + } + + bool push_free_chunk(const FreeChunk& c) { + DynamicFreeChunk* p = (DynamicFreeChunk*)malloc( + offsetof(DynamicFreeChunk, ptrs) + sizeof(*c.ptrs) * c.nfree); + if (!p) { + return false; + } + p->nfree = c.nfree; + memcpy(p->ptrs, c.ptrs, sizeof(*c.ptrs) * c.nfree); + pthread_mutex_lock(&_free_chunks_mutex); + _free_chunks.push_back(p); + pthread_mutex_unlock(&_free_chunks_mutex); + return true; + } + + static butil::static_atomic _singleton; + static pthread_mutex_t _singleton_mutex; + STATIC_MEMBER_BAIDU_VOLATILE_THREAD_LOCAL(LocalPool*, _local_pool); + static butil::static_atomic _nlocal; + static butil::static_atomic _ngroup; + static pthread_mutex_t _block_group_mutex; + static pthread_mutex_t _change_thread_mutex; + static butil::static_atomic _block_groups[OP_MAX_BLOCK_NGROUP]; + + std::vector _free_chunks; + pthread_mutex_t _free_chunks_mutex; + +#ifdef BUTIL_OBJECT_POOL_NEED_FREE_ITEM_NUM + static butil::static_atomic _global_nfree; +#endif +}; + +// Declare template static variables: + +template +const size_t ObjectPool::FREE_CHUNK_NITEM; + +template +BAIDU_THREAD_LOCAL typename ObjectPool::LocalPool* +ObjectPool::_local_pool = NULL; + +template +butil::static_atomic*> ObjectPool::_singleton = BUTIL_STATIC_ATOMIC_INIT(NULL); + +template +pthread_mutex_t ObjectPool::_singleton_mutex = PTHREAD_MUTEX_INITIALIZER; + +template +static_atomic ObjectPool::_nlocal = BUTIL_STATIC_ATOMIC_INIT(0); + +template +butil::static_atomic ObjectPool::_ngroup = BUTIL_STATIC_ATOMIC_INIT(0); + +template +pthread_mutex_t ObjectPool::_block_group_mutex = PTHREAD_MUTEX_INITIALIZER; + +template +pthread_mutex_t ObjectPool::_change_thread_mutex = PTHREAD_MUTEX_INITIALIZER; + +template +butil::static_atomic::BlockGroup*> +ObjectPool::_block_groups[OP_MAX_BLOCK_NGROUP] = {}; + +#ifdef BUTIL_OBJECT_POOL_NEED_FREE_ITEM_NUM +template +butil::static_atomic ObjectPool::_global_nfree = BUTIL_STATIC_ATOMIC_INIT(0); +#endif + +inline std::ostream& operator<<(std::ostream& os, + ObjectPoolInfo const& info) { + return os << "local_pool_num: " << info.local_pool_num + << "\nblock_group_num: " << info.block_group_num + << "\nblock_num: " << info.block_num + << "\nitem_num: " << info.item_num + << "\nblock_item_num: " << info.block_item_num + << "\nfree_chunk_item_num: " << info.free_chunk_item_num + << "\ntotal_size: " << info.total_size +#ifdef BUTIL_OBJECT_POOL_NEED_FREE_ITEM_NUM + << "\nfree_num: " << info.free_item_num +#endif + ; +} +} // namespace butil + +#endif // BUTIL_OBJECT_POOL_INL_H diff --git a/src/butil/observer_list.h b/src/butil/observer_list.h new file mode 100644 index 0000000..8f0dd3b --- /dev/null +++ b/src/butil/observer_list.h @@ -0,0 +1,217 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_OBSERVER_LIST_H__ +#define BUTIL_OBSERVER_LIST_H__ + +#include +#include +#include + +#include "butil/basictypes.h" +#include "butil/logging.h" +#include "butil/memory/weak_ptr.h" + +/////////////////////////////////////////////////////////////////////////////// +// +// OVERVIEW: +// +// A container for a list of observers. Unlike a normal STL vector or list, +// this container can be modified during iteration without invalidating the +// iterator. So, it safely handles the case of an observer removing itself +// or other observers from the list while observers are being notified. +// +// TYPICAL USAGE: +// +// class MyWidget { +// public: +// ... +// +// class Observer { +// public: +// virtual void OnFoo(MyWidget* w) = 0; +// virtual void OnBar(MyWidget* w, int x, int y) = 0; +// }; +// +// void AddObserver(Observer* obs) { +// observer_list_.AddObserver(obs); +// } +// +// void RemoveObserver(Observer* obs) { +// observer_list_.RemoveObserver(obs); +// } +// +// void NotifyFoo() { +// FOR_EACH_OBSERVER(Observer, observer_list_, OnFoo(this)); +// } +// +// void NotifyBar(int x, int y) { +// FOR_EACH_OBSERVER(Observer, observer_list_, OnBar(this, x, y)); +// } +// +// private: +// ObserverList observer_list_; +// }; +// +// +/////////////////////////////////////////////////////////////////////////////// + +template +class ObserverListThreadSafe; + +template +class ObserverListBase + : public butil::SupportsWeakPtr > { + public: + // Enumeration of which observers are notified. + enum NotificationType { + // Specifies that any observers added during notification are notified. + // This is the default type if non type is provided to the constructor. + NOTIFY_ALL, + + // Specifies that observers added while sending out notification are not + // notified. + NOTIFY_EXISTING_ONLY + }; + + // An iterator class that can be used to access the list of observers. See + // also the FOR_EACH_OBSERVER macro defined below. + class Iterator { + public: + Iterator(ObserverListBase& list) + : list_(list.AsWeakPtr()), + index_(0), + max_index_(list.type_ == NOTIFY_ALL ? + std::numeric_limits::max() : + list.observers_.size()) { + ++list_->notify_depth_; + } + + ~Iterator() { + if (list_.get() && --list_->notify_depth_ == 0) + list_->Compact(); + } + + ObserverType* GetNext() { + if (!list_.get()) + return NULL; + ListType& observers = list_->observers_; + // Advance if the current element is null + size_t max_index = std::min(max_index_, observers.size()); + while (index_ < max_index && !observers[index_]) + ++index_; + return index_ < max_index ? observers[index_++] : NULL; + } + + private: + butil::WeakPtr > list_; + size_t index_; + size_t max_index_; + }; + + ObserverListBase() : notify_depth_(0), type_(NOTIFY_ALL) {} + explicit ObserverListBase(NotificationType type) + : notify_depth_(0), type_(type) {} + + // Add an observer to the list. An observer should not be added to + // the same list more than once. + void AddObserver(ObserverType* obs) { + if (std::find(observers_.begin(), observers_.end(), obs) + != observers_.end()) { + NOTREACHED() << "Observers can only be added once!"; + return; + } + observers_.push_back(obs); + } + + // Remove an observer from the list if it is in the list. + void RemoveObserver(ObserverType* obs) { + typename ListType::iterator it = + std::find(observers_.begin(), observers_.end(), obs); + if (it != observers_.end()) { + if (notify_depth_) { + *it = 0; + } else { + observers_.erase(it); + } + } + } + + bool HasObserver(ObserverType* observer) const { + for (size_t i = 0; i < observers_.size(); ++i) { + if (observers_[i] == observer) + return true; + } + return false; + } + + void Clear() { + if (notify_depth_) { + for (typename ListType::iterator it = observers_.begin(); + it != observers_.end(); ++it) { + *it = 0; + } + } else { + observers_.clear(); + } + } + + protected: + size_t size() const { return observers_.size(); } + + void Compact() { + observers_.erase( + std::remove(observers_.begin(), observers_.end(), + static_cast(NULL)), observers_.end()); + } + + private: + friend class ObserverListThreadSafe; + + typedef std::vector ListType; + + ListType observers_; + int notify_depth_; + NotificationType type_; + + friend class ObserverListBase::Iterator; + + DISALLOW_COPY_AND_ASSIGN(ObserverListBase); +}; + +template +class ObserverList : public ObserverListBase { + public: + typedef typename ObserverListBase::NotificationType + NotificationType; + + ObserverList() {} + explicit ObserverList(NotificationType type) + : ObserverListBase(type) {} + + ~ObserverList() { + // When check_empty is true, assert that the list is empty on destruction. + if (check_empty) { + ObserverListBase::Compact(); + DCHECK_EQ(ObserverListBase::size(), 0U); + } + } + + bool might_have_observers() const { + return ObserverListBase::size() != 0; + } +}; + +#define FOR_EACH_OBSERVER(ObserverType, observer_list, func) \ + do { \ + if ((observer_list).might_have_observers()) { \ + ObserverListBase::Iterator \ + it_inside_observer_macro(observer_list); \ + ObserverType* obs; \ + while ((obs = it_inside_observer_macro.GetNext()) != NULL) \ + obs->func; \ + } \ + } while (0) + +#endif // BUTIL_OBSERVER_LIST_H__ diff --git a/src/butil/popen.cpp b/src/butil/popen.cpp new file mode 100644 index 0000000..506a0d1 --- /dev/null +++ b/src/butil/popen.cpp @@ -0,0 +1,206 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2017/11/04 17:37:43 + +#include +#include "butil/build_config.h" +#include "butil/logging.h" + +#if defined(OS_LINUX) +// clone is a linux specific syscall +#include +#include +#include +#include + +extern "C" { +int BAIDU_WEAK bthread_usleep(uint64_t microseconds); +} +#endif + +namespace butil { + +#if defined(OS_LINUX) + +const int CHILD_STACK_SIZE = 256 * 1024; + +struct ChildArgs { + const char* cmd; + int pipe_fd0; + int pipe_fd1; +}; + +int launch_child_process(void* args) { + ChildArgs* cargs = (ChildArgs*)args; + dup2(cargs->pipe_fd1, STDOUT_FILENO); + close(cargs->pipe_fd0); + close(cargs->pipe_fd1); + execl("/bin/sh", "sh", "-c", cargs->cmd, NULL); + _exit(1); +} + +int read_command_output_through_clone(std::ostream& os, const char* cmd) { + int pipe_fd[2]; + if (pipe(pipe_fd) != 0) { + PLOG(ERROR) << "Fail to pipe"; + return -1; + } + int saved_errno = 0; + int wstatus = 0; + pid_t cpid; + int rc = 0; + ChildArgs args = { cmd, pipe_fd[0], pipe_fd[1] }; + char buffer[1024]; + + char* child_stack = NULL; + char* child_stack_mem = (char*)malloc(CHILD_STACK_SIZE); + if (!child_stack_mem) { + LOG(ERROR) << "Fail to alloc stack for the child process"; + rc = -1; + goto END; + } + child_stack = child_stack_mem + CHILD_STACK_SIZE; + // ^ Assume stack grows downward + cpid = clone(launch_child_process, child_stack, + __WCLONE | CLONE_VM | SIGCHLD | CLONE_UNTRACED, &args); + if (cpid < 0) { + PLOG(ERROR) << "Fail to clone child process"; + rc = -1; + goto END; + } + close(pipe_fd[1]); + pipe_fd[1] = -1; + + for (;;) { + const ssize_t nr = read(pipe_fd[0], buffer, sizeof(buffer)); + if (nr > 0) { + os.write(buffer, nr); + continue; + } else if (nr == 0) { + break; + } else if (errno != EINTR) { + LOG(ERROR) << "Encountered error while reading for the pipe"; + break; + } + } + + close(pipe_fd[0]); + pipe_fd[0] = -1; + + for (;;) { + pid_t wpid = waitpid(cpid, &wstatus, WNOHANG | __WALL); + if (wpid > 0) { + break; + } + if (wpid == 0) { + if (bthread_usleep != NULL) { + bthread_usleep(1000); + } else { + usleep(1000); + } + continue; + } + rc = -1; + goto END; + } + + if (WIFEXITED(wstatus)) { + rc = WEXITSTATUS(wstatus); + goto END; + } + + if (WIFSIGNALED(wstatus)) { + os << "Child process(" << cpid << ") was killed by signal " + << WTERMSIG(wstatus); + } + + rc = -1; + errno = ECHILD; + +END: + saved_errno = errno; + if (child_stack_mem) { + free(child_stack_mem); + } + if (pipe_fd[0] >= 0) { + close(pipe_fd[0]); + } + if (pipe_fd[1] >= 0) { + close(pipe_fd[1]); + } + errno = saved_errno; + return rc; +} + +DEFINE_bool(run_command_through_clone, false, + "(Linux specific) Run command with clone syscall to " + "avoid the costly page table duplication"); + +#endif // OS_LINUX + +#include + +int read_command_output_through_popen(std::ostream& os, const char* cmd) { + FILE* pipe = popen(cmd, "r"); + if (pipe == NULL) { + return -1; + } + char buffer[1024]; + for (;;) { + size_t nr = fread(buffer, 1, sizeof(buffer), pipe); + if (nr != 0) { + os.write(buffer, nr); + } + if (nr != sizeof(buffer)) { + if (feof(pipe)) { + break; + } else if (ferror(pipe)) { + LOG(ERROR) << "Encountered error while reading for the pipe"; + break; + } + // retry; + } + } + + const int wstatus = pclose(pipe); + + if (wstatus < 0) { + return wstatus; + } + if (WIFEXITED(wstatus)) { + return WEXITSTATUS(wstatus); + } + if (WIFSIGNALED(wstatus)) { + os << "Child process was killed by signal " + << WTERMSIG(wstatus); + } + errno = ECHILD; + return -1; +} + +int read_command_output(std::ostream& os, const char* cmd) { +#if !defined(OS_LINUX) + return read_command_output_through_popen(os, cmd); +#else + return FLAGS_run_command_through_clone + ? read_command_output_through_clone(os, cmd) + : read_command_output_through_popen(os, cmd); +#endif +} + +} // namespace butil diff --git a/src/butil/popen.h b/src/butil/popen.h new file mode 100644 index 0000000..a28442a --- /dev/null +++ b/src/butil/popen.h @@ -0,0 +1,34 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2017/11/04 17:13:18 + +#ifndef PUBLIC_COMMON_POPEN_H +#define PUBLIC_COMMON_POPEN_H + +#include + +namespace butil { + +// Read the stdout of child process executing `cmd'. +// Returns the exit status(0-255) of cmd and all the output is stored in +// |os|. -1 otherwise and errno is set appropriately. +int read_command_output(std::ostream& os, const char* cmd); + +} + +#endif //PUBLIC_COMMON_POPEN_H diff --git a/src/butil/port.h b/src/butil/port.h new file mode 100644 index 0000000..96008ab --- /dev/null +++ b/src/butil/port.h @@ -0,0 +1,48 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_PORT_H_ +#define BUTIL_PORT_H_ + +#include +#include "butil/build_config.h" + +// DEPRECATED: Use ...LL and ...ULL suffixes. +// TODO(viettrungluu): Delete these. These are only here until |GG_(U)INT64_C| +// are deleted (some other header files (re)define |GG_(U)INT64_C|, so our +// definitions of them must exactly match theirs). +#ifdef COMPILER_MSVC +#define GG_LONGLONG(x) x##I64 +#define GG_ULONGLONG(x) x##UI64 +#else +#define GG_LONGLONG(x) x##LL +#define GG_ULONGLONG(x) x##ULL +#endif + +// DEPRECATED: In Chromium, we force-define __STDC_CONSTANT_MACROS, so you can +// just use the regular (U)INTn_C macros from . +// TODO(viettrungluu): Remove the remaining GG_(U)INTn_C macros. +#define GG_INT64_C(x) GG_LONGLONG(x) +#define GG_UINT64_C(x) GG_ULONGLONG(x) + +// It's possible for functions that use a va_list, such as StringPrintf, to +// invalidate the data in it upon use. The fix is to make a copy of the +// structure before using it and use that copy instead. va_copy is provided +// for this purpose. MSVC does not provide va_copy, so define an +// implementation here. It is not guaranteed that assignment is a copy, so the +// StringUtil.VariableArgsFunc unit test tests this capability. +#if defined(COMPILER_GCC) +#define GG_VA_COPY(a, b) (va_copy(a, b)) +#elif defined(COMPILER_MSVC) +#define GG_VA_COPY(a, b) (a = b) +#endif + +// Define an OS-neutral wrapper for shared library entry points +#if defined(OS_WIN) +#define API_CALL __stdcall +#else +#define API_CALL +#endif + +#endif // BUTIL_PORT_H_ diff --git a/src/butil/posix/eintr_wrapper.h b/src/butil/posix/eintr_wrapper.h new file mode 100644 index 0000000..54e037d --- /dev/null +++ b/src/butil/posix/eintr_wrapper.h @@ -0,0 +1,68 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This provides a wrapper around system calls which may be interrupted by a +// signal and return EINTR. See man 7 signal. +// To prevent long-lasting loops (which would likely be a bug, such as a signal +// that should be masked) to go unnoticed, there is a limit after which the +// caller will nonetheless see an EINTR in Debug builds. +// +// On Windows, this wrapper macro does nothing. +// +// Don't wrap close calls in HANDLE_EINTR. Use IGNORE_EINTR if the return +// value of close is significant. See http://crbug.com/269623. + +#ifndef BUTIL_POSIX_EINTR_WRAPPER_H_ +#define BUTIL_POSIX_EINTR_WRAPPER_H_ + +#include "butil/build_config.h" +#include "butil/macros.h" // BAIDU_TYPEOF + +#if defined(OS_POSIX) + +#include + +#if defined(NDEBUG) + +#define HANDLE_EINTR(x) ({ \ + BAIDU_TYPEOF(x) eintr_wrapper_result; \ + do { \ + eintr_wrapper_result = (x); \ + } while (eintr_wrapper_result == -1 && errno == EINTR); \ + eintr_wrapper_result; \ +}) + +#else + +#define HANDLE_EINTR(x) ({ \ + int eintr_wrapper_counter = 0; \ + BAIDU_TYPEOF(x) eintr_wrapper_result; \ + do { \ + eintr_wrapper_result = (x); \ + } while (eintr_wrapper_result == -1 && errno == EINTR && \ + eintr_wrapper_counter++ < 100); \ + eintr_wrapper_result; \ +}) + +#endif // NDEBUG + +#define IGNORE_EINTR(x) ({ \ + BAIDU_TYPEOF(x) eintr_wrapper_result; \ + do { \ + eintr_wrapper_result = (x); \ + if (eintr_wrapper_result == -1 && errno == EINTR) { \ + eintr_wrapper_result = 0; \ + } \ + } while (0); \ + eintr_wrapper_result; \ +}) + +#else + +#define HANDLE_EINTR(x) (x) +#define IGNORE_EINTR(x) (x) + +#endif // OS_POSIX + +#endif // BUTIL_POSIX_EINTR_WRAPPER_H_ diff --git a/src/butil/posix/file_descriptor_shuffle.cc b/src/butil/posix/file_descriptor_shuffle.cc new file mode 100644 index 0000000..c8e2883 --- /dev/null +++ b/src/butil/posix/file_descriptor_shuffle.cc @@ -0,0 +1,100 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/posix/file_descriptor_shuffle.h" + +#include +#include +#include + +#include "butil/posix/eintr_wrapper.h" +#include "butil/logging.h" + +namespace butil { + +bool PerformInjectiveMultimapDestructive( + InjectiveMultimap* m, InjectionDelegate* delegate) { + static const size_t kMaxExtraFDs = 16; + int extra_fds[kMaxExtraFDs]; + unsigned next_extra_fd = 0; + + // DANGER: this function must not allocate or lock. + // Cannot use STL iterators here, since debug iterators use locks. + + for (size_t i_index = 0; i_index < m->size(); ++i_index) { + InjectiveMultimap::value_type* i = &(*m)[i_index]; + int temp_fd = -1; + + // We DCHECK the injectiveness of the mapping. + for (size_t j_index = i_index + 1; j_index < m->size(); ++j_index) { + InjectiveMultimap::value_type* j = &(*m)[j_index]; + DCHECK(i->dest != j->dest) << "Both fd " << i->source + << " and " << j->source << " map to " << i->dest; + } + + const bool is_identity = i->source == i->dest; + + for (size_t j_index = i_index + 1; j_index < m->size(); ++j_index) { + InjectiveMultimap::value_type* j = &(*m)[j_index]; + if (!is_identity && i->dest == j->source) { + if (temp_fd == -1) { + if (!delegate->Duplicate(&temp_fd, i->dest)) + return false; + if (next_extra_fd < kMaxExtraFDs) { + extra_fds[next_extra_fd++] = temp_fd; + } else { + RAW_LOG(ERROR, "PerformInjectiveMultimapDestructive overflowed " + "extra_fds. Leaking file descriptors!"); + } + } + + j->source = temp_fd; + j->close = false; + } + + if (i->close && i->source == j->dest) + i->close = false; + + if (i->close && i->source == j->source) { + i->close = false; + j->close = true; + } + } + + if (!is_identity) { + if (!delegate->Move(i->source, i->dest)) + return false; + } + + if (!is_identity && i->close) + delegate->Close(i->source); + } + + for (unsigned i = 0; i < next_extra_fd; i++) + delegate->Close(extra_fds[i]); + + return true; +} + +bool PerformInjectiveMultimap(const InjectiveMultimap& m_in, + InjectionDelegate* delegate) { + InjectiveMultimap m(m_in); + return PerformInjectiveMultimapDestructive(&m, delegate); +} + +bool FileDescriptorTableInjection::Duplicate(int* result, int fd) { + *result = HANDLE_EINTR(dup(fd)); + return *result >= 0; +} + +bool FileDescriptorTableInjection::Move(int src, int dest) { + return HANDLE_EINTR(dup2(src, dest)) != -1; +} + +void FileDescriptorTableInjection::Close(int fd) { + int ret = IGNORE_EINTR(close(fd)); + DCHECK(ret == 0); +} + +} // namespace butil diff --git a/src/butil/posix/file_descriptor_shuffle.h b/src/butil/posix/file_descriptor_shuffle.h new file mode 100644 index 0000000..7903aa5 --- /dev/null +++ b/src/butil/posix/file_descriptor_shuffle.h @@ -0,0 +1,87 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_POSIX_FILE_DESCRIPTOR_SHUFFLE_H_ +#define BUTIL_POSIX_FILE_DESCRIPTOR_SHUFFLE_H_ + +// This code exists to shuffle file descriptors, which is commonly needed when +// forking subprocesses. The naive approach (just call dup2 to set up the +// desired descriptors) is very simple, but wrong: it won't handle edge cases +// (like mapping 0 -> 1, 1 -> 0) correctly. +// +// In order to unittest this code, it's broken into the abstract action (an +// injective multimap) and the concrete code for dealing with file descriptors. +// Users should use the code like this: +// butil::InjectiveMultimap file_descriptor_map; +// file_descriptor_map.push_back(butil::InjectionArc(devnull, 0, true)); +// file_descriptor_map.push_back(butil::InjectionArc(devnull, 2, true)); +// file_descriptor_map.push_back(butil::InjectionArc(pipe[1], 1, true)); +// butil::ShuffleFileDescriptors(file_descriptor_map); +// +// and trust the the Right Thing will get done. + +#include + +#include "butil/base_export.h" +#include "butil/compiler_specific.h" + +namespace butil { + +// A Delegate which performs the actions required to perform an injective +// multimapping in place. +class InjectionDelegate { + public: + // Duplicate |fd|, an element of the domain, and write a fresh element of the + // domain into |result|. Returns true iff successful. + virtual bool Duplicate(int* result, int fd) = 0; + // Destructively move |src| to |dest|, overwriting |dest|. Returns true iff + // successful. + virtual bool Move(int src, int dest) = 0; + // Delete an element of the domain. + virtual void Close(int fd) = 0; + + protected: + virtual ~InjectionDelegate() {} +}; + +// An implementation of the InjectionDelegate interface using the file +// descriptor table of the current process as the domain. +class BUTIL_EXPORT FileDescriptorTableInjection : public InjectionDelegate { + virtual bool Duplicate(int* result, int fd) OVERRIDE; + virtual bool Move(int src, int dest) OVERRIDE; + virtual void Close(int fd) OVERRIDE; +}; + +// A single arc of the directed graph which describes an injective multimapping. +struct InjectionArc { + InjectionArc(int in_source, int in_dest, bool in_close) + : source(in_source), + dest(in_dest), + close(in_close) { + } + + int source; + int dest; + bool close; // if true, delete the source element after performing the + // mapping. +}; + +typedef std::vector InjectiveMultimap; + +BUTIL_EXPORT bool PerformInjectiveMultimap(const InjectiveMultimap& map, + InjectionDelegate* delegate); + +BUTIL_EXPORT bool PerformInjectiveMultimapDestructive( + InjectiveMultimap* map, + InjectionDelegate* delegate); + +// This function will not call malloc but will mutate |map| +static inline bool ShuffleFileDescriptors(InjectiveMultimap* map) { + FileDescriptorTableInjection delegate; + return PerformInjectiveMultimapDestructive(map, &delegate); +} + +} // namespace butil + +#endif // BUTIL_POSIX_FILE_DESCRIPTOR_SHUFFLE_H_ diff --git a/src/butil/posix/global_descriptors.cc b/src/butil/posix/global_descriptors.cc new file mode 100644 index 0000000..8896f5e --- /dev/null +++ b/src/butil/posix/global_descriptors.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/posix/global_descriptors.h" + +#include +#include + +#include "butil/logging.h" + +namespace butil { + +// static +GlobalDescriptors* GlobalDescriptors::GetInstance() { + typedef Singleton > + GlobalDescriptorsSingleton; + return GlobalDescriptorsSingleton::get(); +} + +int GlobalDescriptors::Get(Key key) const { + const int ret = MaybeGet(key); + + if (ret == -1) + DLOG(FATAL) << "Unknown global descriptor: " << key; + return ret; +} + +int GlobalDescriptors::MaybeGet(Key key) const { + for (Mapping::const_iterator + i = descriptors_.begin(); i != descriptors_.end(); ++i) { + if (i->first == key) + return i->second; + } + + return -1; +} + +void GlobalDescriptors::Set(Key key, int fd) { + for (Mapping::iterator + i = descriptors_.begin(); i != descriptors_.end(); ++i) { + if (i->first == key) { + i->second = fd; + return; + } + } + + descriptors_.emplace_back(key, fd); +} + +void GlobalDescriptors::Reset(const Mapping& mapping) { + descriptors_ = mapping; +} + +GlobalDescriptors::GlobalDescriptors() {} + +GlobalDescriptors::~GlobalDescriptors() {} + +} // namespace butil diff --git a/src/butil/posix/global_descriptors.h b/src/butil/posix/global_descriptors.h new file mode 100644 index 0000000..717e11a --- /dev/null +++ b/src/butil/posix/global_descriptors.h @@ -0,0 +1,74 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_POSIX_GLOBAL_DESCRIPTORS_H_ +#define BUTIL_POSIX_GLOBAL_DESCRIPTORS_H_ + +#include "butil/build_config.h" + +#include +#include + +#include + +#include "butil/memory/singleton.h" + +namespace butil { + +// It's common practice to install file descriptors into well known slot +// numbers before execing a child; stdin, stdout and stderr are ubiqutous +// examples. +// +// However, when using a zygote model, this becomes troublesome. Since the +// descriptors which need to be in these slots generally aren't known, any code +// could open a resource and take one of the reserved descriptors. Simply +// overwriting the slot isn't a viable solution. +// +// We could try to fill the reserved slots as soon as possible, but this is a +// fragile solution since global constructors etc are able to open files. +// +// Instead, we retreat from the idea of installing descriptors in specific +// slots and add a layer of indirection in the form of this singleton object. +// It maps from an abstract key to a descriptor. If independent modules each +// need to define keys, then values should be chosen randomly so as not to +// collide. +class BUTIL_EXPORT GlobalDescriptors { + public: + typedef uint32_t Key; + typedef std::pair KeyFDPair; + typedef std::vector Mapping; + + // Often we want a canonical descriptor for a given Key. In this case, we add + // the following constant to the key value: +#if !defined(OS_ANDROID) + static const int kBaseDescriptor = 3; // 0, 1, 2 are already taken. +#else + static const int kBaseDescriptor = 4; // 3 used by __android_log_write(). +#endif + + // Return the singleton instance of GlobalDescriptors. + static GlobalDescriptors* GetInstance(); + + // Get a descriptor given a key. It is a fatal error if the key is not known. + int Get(Key key) const; + + // Get a descriptor give a key. Returns -1 on error. + int MaybeGet(Key key) const; + + // Set the descriptor for the given key. + void Set(Key key, int fd); + + void Reset(const Mapping& mapping); + + private: + friend struct DefaultSingletonTraits; + GlobalDescriptors(); + ~GlobalDescriptors(); + + Mapping descriptors_; +}; + +} // namespace butil + +#endif // BUTIL_POSIX_GLOBAL_DESCRIPTORS_H_ diff --git a/src/butil/process_util.cc b/src/butil/process_util.cc new file mode 100644 index 0000000..6791744 --- /dev/null +++ b/src/butil/process_util.cc @@ -0,0 +1,103 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Process-related Info + +// Date: Wed Apr 11 14:35:56 CST 2018 + +#include // open +#include // snprintf +#include +#include +#include // read, gitpid +#include // std::ostringstream +#include "butil/fd_guard.h" // butil::fd_guard +#include "butil/logging.h" +#include "butil/popen.h" // read_command_output +#include "butil/process_util.h" + +#if defined(OS_MACOSX) +#include // proc_pidpath +#endif +namespace butil { + +ssize_t ReadCommandLine(char* buf, size_t len, bool with_args) { +#if defined(OS_LINUX) + butil::fd_guard fd(open("/proc/self/cmdline", O_RDONLY)); + if (fd < 0) { + LOG(ERROR) << "Fail to open /proc/self/cmdline"; + return -1; + } + ssize_t nr = read(fd, buf, len); + if (nr <= 0) { + LOG(ERROR) << "Fail to read /proc/self/cmdline"; + return -1; + } +#elif defined(OS_MACOSX) + static pid_t pid = getpid(); + std::ostringstream oss; + char cmdbuf[32]; + snprintf(cmdbuf, sizeof(cmdbuf), "ps -p %ld -o command=", (long)pid); + if (butil::read_command_output(oss, cmdbuf) != 0) { + LOG(ERROR) << "Fail to read cmdline"; + return -1; + } + const std::string& result = oss.str(); + ssize_t nr = std::min(result.size(), len); + memcpy(buf, result.data(), nr); +#else + #error Not Implemented +#endif + + if (with_args) { + if ((size_t)nr == len) { + return len; + } + for (ssize_t i = 0; i < nr; ++i) { + if (buf[i] == '\0') { + buf[i] = '\n'; + } + } + return nr; + } else { + for (ssize_t i = 0; i < nr; ++i) { + // The command in macos is separated with space and ended with '\n' + if (buf[i] == '\0' || buf[i] == '\n' || buf[i] == ' ') { + return i; + } + } + if ((size_t)nr == len) { + LOG(ERROR) << "buf is not big enough"; + return -1; + } + return nr; + } +} + +ssize_t GetProcessAbsolutePath(char *buf, size_t len) { +#if defined(OS_LINUX) + memset(buf, 0, len); + ssize_t nr = readlink("/proc/self/exe", buf, len); + return nr; +#elif defined(OS_MACOSX) + memset(buf, 0, len); + int ret = proc_pidpath(getpid(), buf, len); + return ret; +#endif +} + +} // namespace butil diff --git a/src/butil/process_util.h b/src/butil/process_util.h new file mode 100644 index 0000000..c6e8ab5 --- /dev/null +++ b/src/butil/process_util.h @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Process-related Info + +// Date: Wed Apr 11 14:35:56 CST 2018 + +#ifndef BUTIL_PROCESS_UTIL_H +#define BUTIL_PROCESS_UTIL_H + +#include + +namespace butil { + +// Read command line of this program. If `with_args' is true, args are +// included and separated with spaces. +// Returns length of the command line on success, -1 otherwise. +// NOTE: `buf' does not end with zero. +ssize_t ReadCommandLine(char* buf, size_t len, bool with_args); + +// Get absolute path of this program. +// Returns length of the absolute path on success, -1 otherwise. +// NOTE: `buf' does not end with zero. +ssize_t GetProcessAbsolutePath(char* buf, size_t len); + +} // namespace butil + +#endif // BUTIL_PROCESS_UTIL_H diff --git a/src/butil/ptr_container.h b/src/butil/ptr_container.h new file mode 100644 index 0000000..5732bbd --- /dev/null +++ b/src/butil/ptr_container.h @@ -0,0 +1,80 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Fri Sep 7 12:15:23 CST 2018 + +#ifndef BUTIL_PTR_CONTAINER_H +#define BUTIL_PTR_CONTAINER_H + +namespace butil { + +// Manage lifetime of a pointer. The key difference between PtrContainer and +// unique_ptr is that PtrContainer can be copied and the pointer inside is +// deeply copied or constructed on-demand. +template +class PtrContainer { +public: + PtrContainer() : _ptr(NULL) {} + + explicit PtrContainer(T* obj) : _ptr(obj) {} + + ~PtrContainer() { + delete _ptr; + } + + PtrContainer(const PtrContainer& rhs) + : _ptr(rhs._ptr ? new T(*rhs._ptr) : NULL) {} + + void operator=(const PtrContainer& rhs) { + if (this == &rhs) { + return; + } + + if (rhs._ptr) { + if (_ptr) { + *_ptr = *rhs._ptr; + } else { + _ptr = new T(*rhs._ptr); + } + } else { + delete _ptr; + _ptr = NULL; + } + } + + T* get() const { return _ptr; } + + void reset(T* ptr) { + delete _ptr; + _ptr = ptr; + } + + operator void*() const { return _ptr; } + + explicit operator bool() const { return get() != NULL; } + + T& operator*() const { return *get(); } + + T* operator->() const { return get(); } + +private: + T* _ptr; +}; + +} // namespace butil + +#endif // BUTIL_PTR_CONTAINER_H diff --git a/src/butil/rand_util.cc b/src/butil/rand_util.cc new file mode 100644 index 0000000..133a101 --- /dev/null +++ b/src/butil/rand_util.cc @@ -0,0 +1,71 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/rand_util.h" + +#include + +#include +#include + +#include "butil/basictypes.h" +#include "butil/logging.h" +#include "butil/strings/string_util.h" + +namespace butil { + +int RandInt(int min, int max) { + DCHECK_LE(min, max); + + uint64_t range = static_cast(max) - min + 1; + int result = min + static_cast(butil::RandGenerator(range)); + DCHECK_GE(result, min); + DCHECK_LE(result, max); + return result; +} + +double RandDouble() { + return BitsToOpenEndedUnitInterval(butil::RandUint64()); +} + +double BitsToOpenEndedUnitInterval(uint64_t bits) { + // We try to get maximum precision by masking out as many bits as will fit + // in the target type's mantissa, and raising it to an appropriate power to + // produce output in the range [0, 1). For IEEE 754 doubles, the mantissa + // is expected to accommodate 53 bits. + + COMPILE_ASSERT(std::numeric_limits::radix == 2, otherwise_use_scalbn); + static const int kBits = std::numeric_limits::digits; + uint64_t random_bits = bits & ((GG_UINT64_C(1) << kBits) - 1); + double result = ldexp(static_cast(random_bits), -1 * kBits); + DCHECK_GE(result, 0.0); + DCHECK_LT(result, 1.0); + return result; +} + +uint64_t RandGenerator(uint64_t range) { + DCHECK_GT(range, 0u); + // We must discard random results above this number, as they would + // make the random generator non-uniform (consider e.g. if + // MAX_UINT64 was 7 and |range| was 5, then a result of 1 would be twice + // as likely as a result of 3 or 4). + uint64_t max_acceptable_value = + (std::numeric_limits::max() / range) * range - 1; + + uint64_t value; + do { + value = butil::RandUint64(); + } while (value > max_acceptable_value); + + return value % range; +} + +std::string RandBytesAsString(size_t length) { + DCHECK_GT(length, 0u); + std::string result; + RandBytes(WriteInto(&result, length + 1), length); + return result; +} + +} // namespace butil diff --git a/src/butil/rand_util.h b/src/butil/rand_util.h new file mode 100644 index 0000000..2f1d0bf --- /dev/null +++ b/src/butil/rand_util.h @@ -0,0 +1,65 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_RAND_UTIL_H_ +#define BUTIL_RAND_UTIL_H_ + +#include + +#include "butil/base_export.h" +#include "butil/basictypes.h" + +namespace butil { + +// -------------------------------------------------------------------------- +// NOTE(gejun): Functions in this header read from /dev/urandom in posix +// systems and are not proper for performance critical situations. +// For fast random numbers, check fast_rand.h +// -------------------------------------------------------------------------- + +// Returns a random number in range [0, kuint64max]. Thread-safe. +BUTIL_EXPORT uint64_t RandUint64(); + +// Returns a random number between min and max (inclusive). Thread-safe. +BUTIL_EXPORT int RandInt(int min, int max); + +// Returns a random number in range [0, range). Thread-safe. +// +// Note that this can be used as an adapter for std::random_shuffle(): +// Given a pre-populated |std::vector myvector|, shuffle it as +// std::random_shuffle(myvector.begin(), myvector.end(), butil::RandGenerator); +BUTIL_EXPORT uint64_t RandGenerator(uint64_t range); + +// Returns a random double in range [0, 1). Thread-safe. +BUTIL_EXPORT double RandDouble(); + +// Given input |bits|, convert with maximum precision to a double in +// the range [0, 1). Thread-safe. +BUTIL_EXPORT double BitsToOpenEndedUnitInterval(uint64_t bits); + +// Fills |output_length| bytes of |output| with random data. +// +// WARNING: +// Do not use for security-sensitive purposes. +// See crypto/ for cryptographically secure random number generation APIs. +BUTIL_EXPORT void RandBytes(void* output, size_t output_length); + +// Fills a string of length |length| with random data and returns it. +// |length| should be nonzero. +// +// Note that this is a variation of |RandBytes| with a different return type. +// The returned string is likely not ASCII/UTF-8. Use with care. +// +// WARNING: +// Do not use for security-sensitive purposes. +// See crypto/ for cryptographically secure random number generation APIs. +BUTIL_EXPORT std::string RandBytesAsString(size_t length); + +#if defined(OS_POSIX) +BUTIL_EXPORT int GetUrandomFD(); +#endif + +} // namespace butil + +#endif // BUTIL_RAND_UTIL_H_ diff --git a/src/butil/rand_util_posix.cc b/src/butil/rand_util_posix.cc new file mode 100644 index 0000000..d36aef9 --- /dev/null +++ b/src/butil/rand_util_posix.cc @@ -0,0 +1,59 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/rand_util.h" + +#include +#include +#include + +#include "butil/file_util.h" +#include "butil/lazy_instance.h" +#include "butil/logging.h" + +namespace { + +// We keep the file descriptor for /dev/urandom around so we don't need to +// reopen it (which is expensive), and since we may not even be able to reopen +// it if we are later put in a sandbox. This class wraps the file descriptor so +// we can use LazyInstance to handle opening it on the first access. +class URandomFd { + public: + URandomFd() : fd_(open("/dev/urandom", O_RDONLY)) { + DCHECK_GE(fd_, 0) << "Cannot open /dev/urandom: " << errno; + } + + ~URandomFd() { close(fd_); } + + int fd() const { return fd_; } + + private: + const int fd_; +}; + +butil::LazyInstance::Leaky g_urandom_fd = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +namespace butil { + +// NOTE: This function must be cryptographically secure. http://crbug.com/140076 +uint64_t RandUint64() { + uint64_t number; + RandBytes(&number, sizeof(number)); + return number; +} + +void RandBytes(void* output, size_t output_length) { + const int urandom_fd = g_urandom_fd.Pointer()->fd(); + const bool success = + ReadFromFD(urandom_fd, static_cast(output), output_length); + CHECK(success); +} + +int GetUrandomFD(void) { + return g_urandom_fd.Pointer()->fd(); +} + +} // namespace butil diff --git a/src/butil/raw_pack.h b/src/butil/raw_pack.h new file mode 100644 index 0000000..d4f8167 --- /dev/null +++ b/src/butil/raw_pack.h @@ -0,0 +1,95 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BUTIL_RAW_PACK_H +#define BUTIL_RAW_PACK_H + +#include "butil/sys_byteorder.h" + +namespace butil { + +// ------------------------------------------------------------------------- +// NOTE: RawPacker/RawUnpacker is used for packing/unpacking low-level and +// hard-to-change header. If the fields are likely to be changed in future, +// use protobuf. +// ------------------------------------------------------------------------- + +// This utility class packs 32-bit and 64-bit integers into binary data +// that can be unpacked by RawUnpacker. Notice that the packed data is +// schemaless and user must match pack..() methods with same-width +// unpack..() methods to get the integers back. +// Example: +// char buf[16]; // 4 + 8 + 4 bytes. +// butil::RawPacker(buf).pack32(a).pack64(b).pack32(c); // buf holds packed data +// +// ... network ... +// +// // positional correspondence with pack..() +// butil::Unpacker(buf2).unpack32(a).unpack64(b).unpack32(c); +class RawPacker { +public: + // Notice: User must guarantee `stream' is as long as the packed data. + explicit RawPacker(void* stream) : _stream((char*)stream) {} + ~RawPacker() {} + + // Not using operator<< because some values may be packed differently from + // its type. + RawPacker& pack32(uint32_t host_value) { + *(uint32_t*)_stream = HostToNet32(host_value); + _stream += 4; + return *this; + } + + RawPacker& pack64(uint64_t host_value) { + uint32_t *p = (uint32_t*)_stream; + p[0] = HostToNet32(host_value >> 32); + p[1] = HostToNet32(host_value & 0xFFFFFFFF); + _stream += 8; + return *this; + } + +private: + char* _stream; +}; + +// This utility class unpacks 32-bit and 64-bit integers from binary data +// packed by RawPacker. +class RawUnpacker { +public: + explicit RawUnpacker(const void* stream) : _stream((const char*)stream) {} + ~RawUnpacker() {} + + RawUnpacker& unpack32(uint32_t & host_value) { + host_value = NetToHost32(*(const uint32_t*)_stream); + _stream += 4; + return *this; + } + + RawUnpacker& unpack64(uint64_t & host_value) { + const uint32_t *p = (const uint32_t*)_stream; + host_value = (((uint64_t)NetToHost32(p[0])) << 32) | NetToHost32(p[1]); + _stream += 8; + return *this; + } + +private: + const char* _stream; +}; + +} // namespace butil + +#endif // BUTIL_RAW_PACK_H diff --git a/src/butil/reader_writer.h b/src/butil/reader_writer.h new file mode 100644 index 0000000..167cd8b --- /dev/null +++ b/src/butil/reader_writer.h @@ -0,0 +1,53 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Wed Aug 8 05:51:33 PDT 2018 + +#ifndef BUTIL_READER_WRITER_H +#define BUTIL_READER_WRITER_H + +#include // iovec + +namespace butil { + +// Abstraction for reading data. +// The simplest implementation is to embed a file descriptor and read from it. +class IReader { +public: + virtual ~IReader() {} + + // Semantics of parameters and return value are same as readv(2) except that + // there's no `fd'. + virtual ssize_t ReadV(const iovec* iov, int iovcnt) = 0; +}; + +// Abstraction for writing data. +// The simplest implementation is to embed a file descriptor and writev into it. +class IWriter { +public: + virtual ~IWriter() {} + + // Semantics of parameters and return value are same as writev(2) except that + // there's no `fd'. + // WriteV is required to submit data gathered by multiple appends in one + // run and enable the possibility of atomic writes. + virtual ssize_t WriteV(const iovec* iov, int iovcnt) = 0; +}; + +} // namespace butil + +#endif // BUTIL_READER_WRITER_H diff --git a/src/butil/recordio.cc b/src/butil/recordio.cc new file mode 100644 index 0000000..dc51cf9 --- /dev/null +++ b/src/butil/recordio.cc @@ -0,0 +1,388 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/logging.h" +#include "butil/recordio.h" +#include "butil/sys_byteorder.h" + +namespace butil { + +DEFINE_int64(recordio_max_record_size, 67108864, + "Records exceeding this size will be rejected"); + +#define BRPC_RECORDIO_MAGIC "RDIO" + +const size_t MAX_NAME_SIZE = 256; + +// 8-bit CRC using the polynomial x^8+x^6+x^3+x^2+1, 0x14D. +// Chosen based on Koopman, et al. (0xA6 in his notation = 0x14D >> 1): +// http://www.ece.cmu.edu/~koopman/roses/dsn04/koopman04_crc_poly_embedded.pdf +// +// This implementation is reflected, processing the least-significant bit of the +// input first, has an initial CRC register value of 0xff, and exclusive-or's +// the final register value with 0xff. As a result the CRC of an empty string, +// and therefore the initial CRC value, is zero. +// +// The standard description of this CRC is: +// width=8 poly=0x4d init=0xff refin=true refout=true xorout=0xff check=0xd8 +// name="CRC-8/KOOP" +static unsigned char const crc8_table[] = { + 0xea, 0xd4, 0x96, 0xa8, 0x12, 0x2c, 0x6e, 0x50, 0x7f, 0x41, 0x03, 0x3d, + 0x87, 0xb9, 0xfb, 0xc5, 0xa5, 0x9b, 0xd9, 0xe7, 0x5d, 0x63, 0x21, 0x1f, + 0x30, 0x0e, 0x4c, 0x72, 0xc8, 0xf6, 0xb4, 0x8a, 0x74, 0x4a, 0x08, 0x36, + 0x8c, 0xb2, 0xf0, 0xce, 0xe1, 0xdf, 0x9d, 0xa3, 0x19, 0x27, 0x65, 0x5b, + 0x3b, 0x05, 0x47, 0x79, 0xc3, 0xfd, 0xbf, 0x81, 0xae, 0x90, 0xd2, 0xec, + 0x56, 0x68, 0x2a, 0x14, 0xb3, 0x8d, 0xcf, 0xf1, 0x4b, 0x75, 0x37, 0x09, + 0x26, 0x18, 0x5a, 0x64, 0xde, 0xe0, 0xa2, 0x9c, 0xfc, 0xc2, 0x80, 0xbe, + 0x04, 0x3a, 0x78, 0x46, 0x69, 0x57, 0x15, 0x2b, 0x91, 0xaf, 0xed, 0xd3, + 0x2d, 0x13, 0x51, 0x6f, 0xd5, 0xeb, 0xa9, 0x97, 0xb8, 0x86, 0xc4, 0xfa, + 0x40, 0x7e, 0x3c, 0x02, 0x62, 0x5c, 0x1e, 0x20, 0x9a, 0xa4, 0xe6, 0xd8, + 0xf7, 0xc9, 0x8b, 0xb5, 0x0f, 0x31, 0x73, 0x4d, 0x58, 0x66, 0x24, 0x1a, + 0xa0, 0x9e, 0xdc, 0xe2, 0xcd, 0xf3, 0xb1, 0x8f, 0x35, 0x0b, 0x49, 0x77, + 0x17, 0x29, 0x6b, 0x55, 0xef, 0xd1, 0x93, 0xad, 0x82, 0xbc, 0xfe, 0xc0, + 0x7a, 0x44, 0x06, 0x38, 0xc6, 0xf8, 0xba, 0x84, 0x3e, 0x00, 0x42, 0x7c, + 0x53, 0x6d, 0x2f, 0x11, 0xab, 0x95, 0xd7, 0xe9, 0x89, 0xb7, 0xf5, 0xcb, + 0x71, 0x4f, 0x0d, 0x33, 0x1c, 0x22, 0x60, 0x5e, 0xe4, 0xda, 0x98, 0xa6, + 0x01, 0x3f, 0x7d, 0x43, 0xf9, 0xc7, 0x85, 0xbb, 0x94, 0xaa, 0xe8, 0xd6, + 0x6c, 0x52, 0x10, 0x2e, 0x4e, 0x70, 0x32, 0x0c, 0xb6, 0x88, 0xca, 0xf4, + 0xdb, 0xe5, 0xa7, 0x99, 0x23, 0x1d, 0x5f, 0x61, 0x9f, 0xa1, 0xe3, 0xdd, + 0x67, 0x59, 0x1b, 0x25, 0x0a, 0x34, 0x76, 0x48, 0xf2, 0xcc, 0x8e, 0xb0, + 0xd0, 0xee, 0xac, 0x92, 0x28, 0x16, 0x54, 0x6a, 0x45, 0x7b, 0x39, 0x07, + 0xbd, 0x83, 0xc1, 0xff +}; + +static uint8_t SizeChecksum(uint32_t input) { + uint8_t crc = 0; + crc = crc8_table[crc ^ (input & 0xFF)]; + crc = crc8_table[crc ^ ((input >> 8) & 0xFF)]; + crc = crc8_table[crc ^ ((input >> 16) & 0xFF)]; + crc = crc8_table[crc ^ ((input >> 24) & 0xFF)]; + return crc; +} + +const butil::IOBuf* Record::Meta(const char* name) const { + for (size_t i = 0; i < _metas.size(); ++i) { + if (_metas[i].name == name) { + return _metas[i].data.get(); + } + } + return NULL; +} + +butil::IOBuf* Record::MutableMeta(const char* name_cstr, bool null_on_found) { + const butil::StringPiece name = name_cstr; + for (size_t i = 0; i < _metas.size(); ++i) { + if (_metas[i].name == name) { + return null_on_found ? NULL : _metas[i].data.get(); + } + } + if (name.size() > MAX_NAME_SIZE) { + LOG(ERROR) << "Too long name=" << name; + return NULL; + } else if (name.empty()) { + LOG(ERROR) << "Empty name"; + return NULL; + } + NamedMeta p; + name.CopyToString(&p.name); + p.data = std::make_shared(); + _metas.push_back(p); + return p.data.get(); +} + +butil::IOBuf* Record::MutableMeta(const std::string& name, bool null_on_found) { + for (size_t i = 0; i < _metas.size(); ++i) { + if (_metas[i].name == name) { + return null_on_found ? NULL : _metas[i].data.get(); + } + } + if (name.size() > MAX_NAME_SIZE) { + LOG(ERROR) << "Too long name" << name; + return NULL; + } else if (name.empty()) { + LOG(ERROR) << "Empty name"; + return NULL; + } + NamedMeta p; + p.name = name; + p.data = std::make_shared(); + _metas.push_back(p); + return p.data.get(); +} + +bool Record::RemoveMeta(const butil::StringPiece& name) { + for (size_t i = 0; i < _metas.size(); ++i) { + if (_metas[i].name == name) { + _metas[i] = _metas.back(); + _metas.pop_back(); + return true; + } + } + return false; +} + +void Record::Clear() { + _payload.clear(); + _metas.clear(); +} + +size_t Record::ByteSize() const { + size_t n = 9 + _payload.size(); + for (size_t i = 0; i < _metas.size(); ++i) { + const NamedMeta& m = _metas[i]; + n += 5 + m.name.size() + m.data->size(); + } + return n; +} + +RecordReader::RecordReader(IReader* reader) + : _reader(reader) + , _cutter(&_portal) + , _ncut(0) + , _last_error(0) { +} + +bool RecordReader::ReadNext(Record* out) { + const size_t MAX_READ = 1024 * 1024; + do { + const int rc = CutRecord(out); + if (rc > 0) { + _last_error = 0; + return true; + } else if (rc < 0) { + while (!CutUntilNextRecordCandidate()) { + const ssize_t nr = _portal.append_from_reader(_reader, MAX_READ); + if (nr <= 0) { + _last_error = (nr < 0 ? errno : END_OF_READER); + return false; + } + } + } else { // rc == 0, not enough data to parse + const ssize_t nr = _portal.append_from_reader(_reader, MAX_READ); + if (nr <= 0) { + _last_error = (nr < 0 ? errno : END_OF_READER); + return false; + } + } + } while (true); +} + +bool RecordReader::CutUntilNextRecordCandidate() { + const size_t old_ncut = _ncut; + // Skip beginning magic + char magic[4]; + if (_cutter.copy_to(magic, sizeof(magic)) != sizeof(magic)) { + return false; + } + void* dummy = magic; // suppressing strict-aliasing warning + if (*(const uint32_t*)dummy == *(const uint32_t*)BRPC_RECORDIO_MAGIC) { + _cutter.pop_front(sizeof(magic)); + _ncut += sizeof(magic); + } + char buf[512]; + do { + const size_t nc = _cutter.copy_to(buf, sizeof(buf)); + if (nc < sizeof(magic)) { + return false; + } + const size_t m = nc + 1 - sizeof(magic); + for (size_t i = 0; i < m; ++i) { + void* dummy = buf + i; // suppressing strict-aliasing warning + if (*(const uint32_t*)dummy == *(const uint32_t*)BRPC_RECORDIO_MAGIC) { + _cutter.pop_front(i); + _ncut += i; + LOG(INFO) << "Found record candidate after " << _ncut - old_ncut << " bytes"; + return true; + } + } + _cutter.pop_front(m); + _ncut += m; + if (nc < sizeof(buf)) { + return false; + } + } while (true); +} + +int RecordReader::CutRecord(Record* rec) { + uint8_t headbuf[9]; + if (_cutter.copy_to(headbuf, sizeof(headbuf)) != sizeof(headbuf)) { + return 0; + } + void* dummy = headbuf; // suppressing strict-aliasing warning + if (*(const uint32_t*)dummy != *(const uint32_t*)BRPC_RECORDIO_MAGIC) { + LOG(ERROR) << "Invalid magic_num=" + << butil::PrintedAsBinary(std::string((char*)headbuf, 4)) + << ", offset=" << offset(); + return -1; + } + uint32_t tmp = NetToHost32(*(const uint32_t*)(headbuf + 4)); + const uint8_t checksum = SizeChecksum(tmp); + bool has_meta = (tmp & 0x80000000); + // NOTE: use size_t rather than uint32_t for sizes to avoid potential + // addition overflows + const size_t data_size = (tmp & 0x7FFFFFFF); + if (checksum != headbuf[8]) { + LOG(ERROR) << "Unmatched checksum of 0x" + << std::hex << tmp << std::dec + << "(metabit=" << has_meta + << " size=" << data_size + << " offset=" << offset() + << "), expected=" << (unsigned)headbuf[8] + << " actual=" << (unsigned)checksum; + return -1; + } + if (data_size > (size_t)FLAGS_recordio_max_record_size) { + LOG(ERROR) << "data_size=" << data_size + << " is larger than -recordio_max_record_size=" + << FLAGS_recordio_max_record_size + << ", offset=" << offset(); + return -1; + } + if (_cutter.remaining_bytes() < data_size + sizeof(headbuf)) { + return 0; + } + rec->Clear(); + _cutter.pop_front(sizeof(headbuf)); + _ncut += sizeof(headbuf); + size_t consumed_bytes = 0; + while (has_meta) { + char name_size_buf = 0; + CHECK(_cutter.cut1(&name_size_buf)); + const size_t name_size = (uint8_t)name_size_buf; + std::string name; + _cutter.cutn(&name, name_size); + _cutter.cutn(&tmp, 4); + tmp = NetToHost32(tmp); + has_meta = (tmp & 0x80000000); + const size_t meta_size = (tmp & 0x7FFFFFFF); + _ncut += 5 + name_size; + if (consumed_bytes + 5 + name_size + meta_size > data_size) { + LOG(ERROR) << name << ".meta_size=" << meta_size + << " is inconsistent with its data_size=" << data_size + << ", offset=" << offset(); + return -1; + } + butil::IOBuf* meta = rec->MutableMeta(name, true/*null_on_found*/); + if (meta == NULL) { + LOG(ERROR) << "Fail to add meta=" << name + << ", offset=" << offset(); + return -1; + } + _cutter.cutn(meta, meta_size); + _ncut += meta_size; + consumed_bytes += 5 + name_size + meta_size; + } + const size_t previous_payload_size = rec->Payload().size(); + const size_t cut_bytes = _cutter.cutn(rec->MutablePayload(), data_size - consumed_bytes); + const size_t after_payload_size = rec->Payload().size(); + if (cut_bytes != after_payload_size) { + LOG(ERROR) << "cut_bytes=" << cut_bytes << " does not match after_payload_size=" << after_payload_size << " previous_size=" << previous_payload_size << " data_size=" << data_size << " consumed_bytes=" << consumed_bytes; + } + if (cut_bytes != data_size - consumed_bytes) { + LOG(ERROR) << "cut_bytes=" << cut_bytes << " does not match input=" << data_size - consumed_bytes; + } + _ncut += cut_bytes; + return 1; +} + +RecordWriter::RecordWriter(IWriter* writer) + :_writer(writer) { +} + +int RecordWriter::WriteWithoutFlush(const Record& rec) { + const size_t old_size = _buf.size(); + uint8_t headbuf[9]; + const IOBuf::Area headarea = _buf.reserve(sizeof(headbuf)); + for (size_t i = 0; i < rec.MetaCount(); ++i) { + auto& s = rec.MetaAt(i); + if (s.name.size() > MAX_NAME_SIZE) { + LOG(ERROR) << "Too long name=" << s.name; + _buf.pop_back(_buf.size() - old_size); + return -1; + } + char metabuf[s.name.size() + 5]; + char* p = metabuf; + *p = s.name.size(); + ++p; + memcpy(p, s.name.data(), s.name.size()); + p += s.name.size(); + if (s.data->size() > 0x7FFFFFFFULL) { + LOG(ERROR) << "Meta named `" << s.name << "' is too long, size=" + << s.data->size(); + _buf.pop_back(_buf.size() - old_size); + return -1; + } + uint32_t tmp = s.data->size() & 0x7FFFFFFF; + if (i < rec.MetaCount() - 1) { + tmp |= 0x80000000; + } + *(uint32_t*)p = HostToNet32(tmp); + _buf.append(metabuf, sizeof(metabuf)); + _buf.append(*s.data.get()); + } + if (!rec.Payload().empty()) { + _buf.append(rec.Payload()); + } + void* dummy = headbuf; // suppressing strict-aliasing warning + *(uint32_t*)dummy = *(const uint32_t*)BRPC_RECORDIO_MAGIC; + const size_t data_size = _buf.size() - old_size - sizeof(headbuf); + if (data_size > 0x7FFFFFFFULL) { + LOG(ERROR) << "data_size=" << data_size << " is too long"; + _buf.pop_back(_buf.size() - old_size); + return -1; + } + uint32_t tmp = (data_size & 0x7FFFFFFF); + if (rec.MetaCount() > 0) { + tmp |= 0x80000000; + } + *(uint32_t*)(headbuf + 4) = HostToNet32(tmp); + headbuf[8] = SizeChecksum(tmp); + _buf.unsafe_assign(headarea, headbuf); + return 0; +} + +int RecordWriter::Flush() { + size_t total_nw = 0; + do { + const ssize_t nw = _buf.cut_into_writer(_writer); + if (nw > 0) { + total_nw += nw; + } else { + if (total_nw) { + // We've flushed sth., return as success. + return 0; + } + if (nw == 0) { + return _buf.empty() ? 0 : EAGAIN; + } else { + return errno; + } + } + } while (true); +} + +int RecordWriter::Write(const Record& record) { + const int rc = WriteWithoutFlush(record); + if (rc) { + return rc; + } + return Flush(); +} + + +} // namespace butil diff --git a/src/butil/recordio.h b/src/butil/recordio.h new file mode 100644 index 0000000..8605f59 --- /dev/null +++ b/src/butil/recordio.h @@ -0,0 +1,143 @@ +// recordio - A binary format to transport data from end to end. +// +// 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. + +// Date: Thu Nov 22 13:57:56 CST 2012 + +#ifndef BUTIL_RECORDIO_H +#define BUTIL_RECORDIO_H + +#include "butil/iobuf.h" +#include + +namespace butil { + +// 0-or-1 Payload + 0-or-multiple Metas. +// Payload and metas are often serialized form of protobuf messages. As a +// correspondence, the implementation is not optimized for very small blobs, +// which should be batched properly before inserting(e.g. using repeated +// field in pb) +class Record { +public: + struct NamedMeta { + std::string name; + std::shared_ptr data; + }; + + // Number of metas. Could be 0. + size_t MetaCount() const { return _metas.size(); } + + // Get i-th Meta, out-of-range accesses may crash. + // This method is mainly for iterating all metas. + const NamedMeta& MetaAt(size_t i) const { return _metas[i]; } + + // Get meta by |name|. NULL on not found. + const butil::IOBuf* Meta(const char* name) const; + + // Returns a mutable pointer to the meta with |name|. If the meta does + // not exist, add it first. + // If |null_on_found| is true and meta with |name| is present, NULL is + // returned. This is useful for detecting uniqueness of meta names in some + // scenarios. + // NOTE: With the assumption that there won't be many metas, the impl. + // tests presence by scaning all fields, which may perform badly when metas + // are a lot. + butil::IOBuf* MutableMeta(const char* name, bool null_on_found = false); + butil::IOBuf* MutableMeta(const std::string& name, bool null_on_found = false); + + // Remove meta with the name. The impl. may scan all fields. + // Returns true on erased, false on absent. + bool RemoveMeta(const butil::StringPiece& name); + + // Get the payload. Empty by default. + const butil::IOBuf& Payload() const { return _payload; } + + // Get a mutable pointer to the payload. + butil::IOBuf* MutablePayload() { return &_payload; } + + // Clear payload and remove all meta. + void Clear(); + + // Serialized size of this record. + size_t ByteSize() const; + +private: + butil::IOBuf _payload; + std::vector _metas; +}; + +// Parse records from the IReader, corrupted records will be skipped. +// Example: +// RecordReader rd(...); +// Record rec; +// while (rd.ReadNext(&rec)) { +// // Handle the rec +// } +// if (rd.last_error() != RecordReader::END_OF_READER) { +// LOG(FATAL) << "Critical error occurred"; +// } +class RecordReader { +public: + // A special error code to mark end of input data. + static const int END_OF_READER = -1; + + explicit RecordReader(IReader* reader); + + // Returns true on success and |out| is overwritten by the record. + // False otherwise and last_error() is the error which is treated as permanent. + bool ReadNext(Record* out); + + // 0 means no error. + // END_OF_READER means all data in the IReader are successfully consumed. + int last_error() const { return _last_error; } + + // Total bytes consumed. + // NOTE: this value may not equal to read bytes from the IReader even if + // the reader runs out, due to parsing errors. + size_t offset() const { return _ncut; } + +private: + bool CutUntilNextRecordCandidate(); + int CutRecord(Record* rec); + +private: + IReader* _reader; + IOPortal _portal; + IOBufCutter _cutter; + size_t _ncut; + int _last_error; +}; + +// Write records into the IWriter. +class RecordWriter { +public: + explicit RecordWriter(IWriter* writer); + + // Serialize |record| into internal buffer and NOT flush into the IWriter. + int WriteWithoutFlush(const Record& record); + + // Serialize |record| into internal buffer and flush into the IWriter. + int Write(const Record& record); + + // Flush internal buffer into the IWriter. + // Returns 0 on success, error code otherwise. + int Flush(); + +private: + IOBuf _buf; + IWriter* _writer; +}; + +} // namespace butil + +#endif // BUTIL_RECORDIO_H diff --git a/src/butil/reloadable_flags.h b/src/butil/reloadable_flags.h new file mode 100644 index 0000000..15a9f98 --- /dev/null +++ b/src/butil/reloadable_flags.h @@ -0,0 +1,79 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BUTIL_RELOADABLE_FLAGS_H +#define BUTIL_RELOADABLE_FLAGS_H + +#include +#include // write, _exit +#include +#include "butil/macros.h" +#include "butil/type_traits.h" + +// Register an always-true valiator to a gflag so that the gflag is treated as +// reloadable by brpc. If a validator exists, abort the program. +// You should call this macro within global scope. for example: +// +// DEFINE_int32(foo, 0, "blah blah"); +// BRPC_VALIDATE_GFLAG(foo, brpc::PassValidate); +// +// This macro does not work for string-flags because they're thread-unsafe to +// modify directly. To emphasize this, you have to write the validator by +// yourself and use GFLAGS_NAMESPACE::GetCommandLineOption() to acess the flag. +#define BUTIL_VALIDATE_GFLAG(flag, validate_fn) \ + namespace butil_flags {} \ + const int ALLOW_UNUSED register_FLAGS_ ## flag ## _dummy = \ + ::butil::RegisterFlagValidatorOrDieImpl< \ + decltype(FLAGS_##flag)>(&FLAGS_##flag, (validate_fn)) + + +namespace butil { + +template +bool PassValidate(const char*, T) { + return true; +} + +template +bool PositiveInteger(const char*, T v) { + return v > 0; +} + +template +bool NonNegativeInteger(const char*, T v) { + return v >= 0; +} + +template +bool RegisterFlagValidatorOrDieImpl( + const T* flag, bool (*validate_fn)(const char*, T val)) { + static_assert(!butil::is_same::value, + "Not support string flags"); + if (::GFLAGS_NAMESPACE::RegisterFlagValidator(flag, validate_fn)) { + return true; + } + // Error printed by gflags does not have newline. Add one to it. + char newline = '\n'; + butil::ignore_result(write(2, &newline, 1)); + _exit(1); +} + +} // namespace butil + + +#endif // BUTIL_RELOADABLE_FLAGS_H diff --git a/src/butil/resource_pool.h b/src/butil/resource_pool.h new file mode 100644 index 0000000..e1c09fa --- /dev/null +++ b/src/butil/resource_pool.h @@ -0,0 +1,142 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Sun Jul 13 15:04:18 CST 2014 + +#ifndef BUTIL_RESOURCE_POOL_H +#define BUTIL_RESOURCE_POOL_H + +#include // size_t + +// Efficiently allocate fixed-size (small) objects addressable by identifiers +// in multi-threaded environment. +// +// Comparison with new/delete(glibc 2.3.4) under high contention: +// ----------------------------- +// get=26.1 return=4.7 +// get=46.1 return=5.4 +// get=27.5 return=5.3 +// get=24.8 return=6.5 +// get=26.6 return=5.3 +// get=24.7 return=4.9 +// get=67.8 return=5.5 +// get=36.7 return=5.0 +// -------------------------------- +// new=295.0 delete=234.5 +// new=299.2 delete=359.7 +// new=288.1 delete=219.0 +// new=298.6 delete=428.0 +// new=319.2 delete=426.5 +// new=293.9 delete=651.8 +// new=304.9 delete=672.8 +// new=170.6 delete=292.8 +// -------------------------------- + +namespace butil { + +// Specialize following classes to override default parameters for type T. +// namespace butil { +// template <> struct ResourcePoolBlockMaxSize { +// static const size_t value = 1024; +// }; +// } + +// Memory is allocated in blocks, memory size of a block will not exceed: +// min(ResourcePoolBlockMaxSize::value, +// ResourcePoolBlockMaxItem::value * sizeof(T)) +template struct ResourcePoolBlockMaxSize { + static const size_t value = 64 * 1024; // bytes +}; +template struct ResourcePoolBlockMaxItem { + static const size_t value = 256; +}; + +// Free objects of each thread are grouped into a chunk before they are merged +// to the global list. Memory size of objects in one free chunk will not exceed: +// min(ResourcePoolFreeChunkMaxItem::value(), +// ResourcePoolBlockMaxSize::value, +// ResourcePoolBlockMaxItem::value * sizeof(T)) +template struct ResourcePoolFreeChunkMaxItem { + static size_t value() { return 256; } +}; + +// ResourcePool calls this function on newly constructed objects. If this +// function returns false, the object is destructed immediately and +// get_resource() shall return NULL. This is useful when the constructor +// failed internally(namely ENOMEM). +template struct ResourcePoolValidator { + static bool validate(const T*) { return true; } +}; + +} // namespace butil + +#include "butil/resource_pool_inl.h" + +namespace butil { + +// Get an object typed |T| and write its identifier into |id|. +// The object should be cleared before usage. +// NOTE: If there are no arguments, T must be default-constructible. +template +inline T* get_resource(ResourceId* id, Args&&... args) { + return ResourcePool::singleton()->get_resource(id, std::forward(args)...); +} + +// Return the object associated with identifier |id| back. The object is NOT +// destructed and will be returned by later get_resource. Similar with +// free/delete, validity of the id is not checked, user shall not return a +// not-yet-allocated or already-returned id otherwise behavior is undefined. +// Returns 0 when successful, -1 otherwise. +template inline int return_resource(ResourceId id) { + return ResourcePool::singleton()->return_resource(id); +} + +// Get the object associated with the identifier |id|. +// Returns NULL if |id| was not allocated by get_resource or +// ResourcePool::get_resource() of a variant before. +// Addressing a free(returned to pool) identifier does not return NULL. +// NOTE: Calling this function before any other get_resource/ +// return_resource/address, even if the identifier is valid, +// may race with another thread calling clear_resources. +template inline T* address_resource(ResourceId id) { + return ResourcePool::address_resource(id); +} + +// Reclaim all allocated resources typed T if caller is the last thread called +// this function, otherwise do nothing. You rarely need to call this function +// manually because it's called automatically when each thread quits. +template inline void clear_resources() { + ResourcePool::singleton()->clear_resources(); +} + +// Get description of resources typed T. +// This function is possibly slow because it iterates internal structures. +// Don't use it frequently like a "getter" function. +template ResourcePoolInfo describe_resources() { + return ResourcePool::singleton()->describe_resources(); +} + +// Traverse all allocated resources typed T and apply specified operation +template +void for_each_resource(F const& f) { + ResourcePool::singleton()->for_each_resource(f); +} +} // namespace butil + +#endif // BUTIL_RESOURCE_POOL_H diff --git a/src/butil/resource_pool_inl.h b/src/butil/resource_pool_inl.h new file mode 100644 index 0000000..8264910 --- /dev/null +++ b/src/butil/resource_pool_inl.h @@ -0,0 +1,636 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// bthread - An M:N threading library to make applications more concurrent. + +// Date: Sun Jul 13 15:04:18 CST 2014 + +#ifndef BUTIL_RESOURCE_POOL_INL_H +#define BUTIL_RESOURCE_POOL_INL_H + +#include // std::ostream +#include // pthread_mutex_t +#include // std::max, std::min +#include "butil/atomicops.h" // butil::atomic +#include "butil/macros.h" // BAIDU_CACHELINE_ALIGNMENT +#include "butil/scoped_lock.h" // BAIDU_SCOPED_LOCK +#include "butil/thread_local.h" // thread_atexit +#include "butil/memory/aligned_memory.h" // butil::AlignedMemory +#include + +#ifdef BUTIL_RESOURCE_POOL_NEED_FREE_ITEM_NUM +#define BAIDU_RESOURCE_POOL_FREE_ITEM_NUM_ADD1 \ + (_global_nfree.fetch_add(1, butil::memory_order_relaxed)) +#define BAIDU_RESOURCE_POOL_FREE_ITEM_NUM_SUB1 \ + (_global_nfree.fetch_sub(1, butil::memory_order_relaxed)) +#else +#define BAIDU_RESOURCE_POOL_FREE_ITEM_NUM_ADD1 +#define BAIDU_RESOURCE_POOL_FREE_ITEM_NUM_SUB1 +#endif + +namespace butil { + +template +struct ResourceId { + uint64_t value; + + operator uint64_t() const { + return value; + } + + template + ResourceId cast() const { + ResourceId id = { value }; + return id; + } +}; + +template +struct ResourcePoolFreeChunk { + size_t nfree; + ResourceId ids[NITEM]; +}; +// for gcc 3.4.5 +template +struct ResourcePoolFreeChunk { + size_t nfree; + ResourceId ids[0]; +}; + +struct ResourcePoolInfo { + size_t local_pool_num; + size_t block_group_num; + size_t block_num; + size_t item_num; + size_t block_item_num; + size_t free_chunk_item_num; + size_t total_size; +#ifdef BUTIL_RESOURCE_POOL_NEED_FREE_ITEM_NUM + size_t free_item_num; +#endif +}; + +static const size_t RP_MAX_BLOCK_NGROUP = 65536; +static const size_t RP_GROUP_NBLOCK_NBIT = 16; +static const size_t RP_GROUP_NBLOCK = (1UL << RP_GROUP_NBLOCK_NBIT); +static const size_t RP_INITIAL_FREE_LIST_SIZE = 1024; + +template +class ResourcePoolBlockItemNum { + static const size_t N1 = ResourcePoolBlockMaxSize::value / sizeof(T); + static const size_t N2 = (N1 < 1 ? 1 : N1); +public: + static const size_t value = (N2 > ResourcePoolBlockMaxItem::value ? + ResourcePoolBlockMaxItem::value : N2); +}; + + +template +class BAIDU_CACHELINE_ALIGNMENT ResourcePool { +public: + static const size_t BLOCK_NITEM = ResourcePoolBlockItemNum::value; + static const size_t FREE_CHUNK_NITEM = BLOCK_NITEM; + + // Free identifiers are batched in a FreeChunk before they're added to + // global list(_free_chunks). + typedef ResourcePoolFreeChunk FreeChunk; + typedef ResourcePoolFreeChunk DynamicFreeChunk; + + typedef AlignedMemory BlockItem; + // When a thread needs memory, it allocates a Block. To improve locality, + // items in the Block are only used by the thread. + // To support cache-aligned objects, align Block.items by cacheline. + struct BAIDU_CACHELINE_ALIGNMENT Block { + BlockItem items[BLOCK_NITEM]; + size_t nitem; + + Block() : nitem(0) {} + }; + + // A Resource addresses at most RP_MAX_BLOCK_NGROUP BlockGroups, + // each BlockGroup addresses at most RP_GROUP_NBLOCK blocks. So a + // resource addresses at most RP_MAX_BLOCK_NGROUP * RP_GROUP_NBLOCK Blocks. + struct BlockGroup { + butil::atomic nblock; + butil::atomic blocks[RP_GROUP_NBLOCK]; + + BlockGroup() : nblock(0) { + // We fetch_add nblock in add_block() before setting the entry, + // thus address_resource() may sees the unset entry. Initialize + // all entries to NULL makes such address_resource() return NULL. + memset(static_cast(blocks), 0, sizeof(butil::atomic) * RP_GROUP_NBLOCK); + } + }; + + + // Each thread has an instance of this class. + class BAIDU_CACHELINE_ALIGNMENT LocalPool { + public: + explicit LocalPool(ResourcePool* pool) + : _pool(pool) + , _cur_block(NULL) + , _cur_block_index(0) { + _cur_free.nfree = 0; + } + + ~LocalPool() { + // Add to global _free_chunks if there're some free resources + if (_cur_free.nfree) { + _pool->push_free_chunk(_cur_free); + } + + _pool->clear_from_destructor_of_local_pool(); + } + + static void delete_local_pool(void* arg) { + delete(LocalPool*)arg; + } + + // We need following macro to construct T with different CTOR_ARGS + // which may include parenthesis because when T is POD, "new T()" + // and "new T" are different: former one sets all fields to 0 which + // we don't want. +#define BAIDU_RESOURCE_POOL_GET(CTOR_ARGS) \ + /* Fetch local free id */ \ + if (_cur_free.nfree) { \ + const ResourceId free_id = _cur_free.ids[--_cur_free.nfree]; \ + *id = free_id; \ + BAIDU_RESOURCE_POOL_FREE_ITEM_NUM_SUB1; \ + return unsafe_address_resource(free_id); \ + } \ + /* Fetch a FreeChunk from global. \ + TODO: Popping from _free needs to copy a FreeChunk which is \ + costly, but hardly impacts amortized performance. */ \ + if (_pool->pop_free_chunk(_cur_free)) { \ + --_cur_free.nfree; \ + const ResourceId free_id = _cur_free.ids[_cur_free.nfree]; \ + *id = free_id; \ + BAIDU_RESOURCE_POOL_FREE_ITEM_NUM_SUB1; \ + return unsafe_address_resource(free_id); \ + } \ + T* p = NULL; \ + /* Fetch memory from local block */ \ + if (_cur_block && _cur_block->nitem < BLOCK_NITEM) { \ + id->value = _cur_block_index * BLOCK_NITEM + _cur_block->nitem; \ + auto item = _cur_block->items + _cur_block->nitem; \ + p = new (item->void_data()) T CTOR_ARGS; \ + if (!ResourcePoolValidator::validate(p)) { \ + p->~T(); \ + return NULL; \ + } \ + ++_cur_block->nitem; \ + return p; \ + } \ + /* Fetch a Block from global */ \ + _cur_block = add_block(&_cur_block_index); \ + if (_cur_block != NULL) { \ + id->value = _cur_block_index * BLOCK_NITEM + _cur_block->nitem; \ + auto item = _cur_block->items + _cur_block->nitem; \ + p = new (item->void_data()) T CTOR_ARGS; \ + if (!ResourcePoolValidator::validate(p)) { \ + p->~T(); \ + return NULL; \ + } \ + ++_cur_block->nitem; \ + return p; \ + } \ + return NULL; \ + + + inline T* get(ResourceId* id) { + BAIDU_RESOURCE_POOL_GET(); + } + + template + inline T* get(ResourceId* id, Args&&... args) { + BAIDU_RESOURCE_POOL_GET((std::forward(args)...)); + } + +#undef BAIDU_RESOURCE_POOL_GET + + inline int return_resource(ResourceId id) { + // Return to local free list + if (_cur_free.nfree < ResourcePool::free_chunk_nitem()) { + _cur_free.ids[_cur_free.nfree++] = id; + BAIDU_RESOURCE_POOL_FREE_ITEM_NUM_ADD1; + return 0; + } + // Local free list is full, return it to global. + // For copying issue, check comment in upper get() + if (_pool->push_free_chunk(_cur_free)) { + _cur_free.nfree = 1; + _cur_free.ids[0] = id; + BAIDU_RESOURCE_POOL_FREE_ITEM_NUM_ADD1; + return 0; + } + return -1; + } + + private: + ResourcePool* _pool; + Block* _cur_block; + size_t _cur_block_index; + FreeChunk _cur_free; + }; + + static inline T* unsafe_address_resource(ResourceId id) { + const size_t block_index = id.value / BLOCK_NITEM; + return (T*)(_block_groups[(block_index >> RP_GROUP_NBLOCK_NBIT)] + .load(butil::memory_order_consume) + ->blocks[(block_index & (RP_GROUP_NBLOCK - 1))] + .load(butil::memory_order_consume)->items) + + id.value - block_index * BLOCK_NITEM; + } + + static inline T* address_resource(ResourceId id) { + const size_t block_index = id.value / BLOCK_NITEM; + const size_t group_index = (block_index >> RP_GROUP_NBLOCK_NBIT); + if (__builtin_expect(group_index < RP_MAX_BLOCK_NGROUP, 1)) { + BlockGroup* bg = + _block_groups[group_index].load(butil::memory_order_consume); + if (__builtin_expect(bg != NULL, 1)) { + Block* b = bg->blocks[block_index & (RP_GROUP_NBLOCK - 1)] + .load(butil::memory_order_consume); + if (__builtin_expect(b != NULL, 1)) { + const size_t offset = id.value - block_index * BLOCK_NITEM; + if (__builtin_expect(offset < b->nitem, 1)) { + return (T*)b->items + offset; + } + } + } + } + + return NULL; + } + + template + inline T* get_resource(ResourceId* id, Args&&... args) { + LocalPool* lp = get_or_new_local_pool(); + if (__builtin_expect(lp != NULL, 1)) { + return lp->get(id, std::forward(args)...); + } + return NULL; + } + + inline int return_resource(ResourceId id) { + LocalPool* lp = get_or_new_local_pool(); + if (__builtin_expect(lp != NULL, 1)) { + return lp->return_resource(id); + } + return -1; + } + + void clear_resources() { + LocalPool* lp = _local_pool; + if (lp) { + _local_pool = NULL; + butil::thread_atexit_cancel(LocalPool::delete_local_pool, lp); + delete lp; + } + } + + static inline size_t free_chunk_nitem() { + const size_t n = ResourcePoolFreeChunkMaxItem::value(); + return n < FREE_CHUNK_NITEM ? n : FREE_CHUNK_NITEM; + } + + template + void for_each_resource(F const& f) { + for (size_t i = 0; i < _ngroup.load(butil::memory_order_acquire); ++i) { + BlockGroup* bg = _block_groups[i].load(butil::memory_order_consume); + if (NULL == bg) { + break; + } + size_t nblock = std::min(bg->nblock.load(butil::memory_order_relaxed), + RP_GROUP_NBLOCK); + for (size_t j = 0; j < nblock; ++j) { + Block* b = bg->blocks[j].load(butil::memory_order_consume); + if (NULL != b) { + for (size_t k = 0; k < b->nitem; ++k) { + auto item = b->items + k; + T* obj = (T*)item->void_data(); + f(obj); + } + } + } + } + } + + // Number of all allocated objects, including being used and free. + ResourcePoolInfo describe_resources() const { + ResourcePoolInfo info; + info.local_pool_num = _nlocal.load(butil::memory_order_relaxed); + info.block_group_num = _ngroup.load(butil::memory_order_acquire); + info.block_num = 0; + info.item_num = 0; + info.free_chunk_item_num = free_chunk_nitem(); + info.block_item_num = BLOCK_NITEM; +#ifdef BUTIL_RESOURCE_POOL_NEED_FREE_ITEM_NUM + info.free_item_num = _global_nfree.load(butil::memory_order_relaxed); +#endif + + for (size_t i = 0; i < info.block_group_num; ++i) { + BlockGroup* bg = _block_groups[i].load(butil::memory_order_consume); + if (NULL == bg) { + break; + } + size_t nblock = std::min(bg->nblock.load(butil::memory_order_relaxed), + RP_GROUP_NBLOCK); + info.block_num += nblock; + for (size_t j = 0; j < nblock; ++j) { + Block* b = bg->blocks[j].load(butil::memory_order_consume); + if (NULL != b) { + info.item_num += b->nitem; + } + } + } + info.total_size = info.block_num * info.block_item_num * sizeof(T); + return info; + } + + static inline ResourcePool* singleton() { + ResourcePool* p = _singleton.load(butil::memory_order_consume); + if (p) { + return p; + } + pthread_mutex_lock(&_singleton_mutex); + p = _singleton.load(butil::memory_order_consume); + if (!p) { + p = new ResourcePool(); + _singleton.store(p, butil::memory_order_release); + } + pthread_mutex_unlock(&_singleton_mutex); + return p; + } + +private: + ResourcePool() { + _free_chunks.reserve(RP_INITIAL_FREE_LIST_SIZE); + pthread_mutex_init(&_free_chunks_mutex, NULL); + } + + ~ResourcePool() { + pthread_mutex_destroy(&_free_chunks_mutex); + } + + // Create a Block and append it to right-most BlockGroup. + static Block* add_block(size_t* index) { + Block* const new_block = new (std::nothrow) Block; + if (NULL == new_block) { + return NULL; + } + + size_t ngroup; + do { + ngroup = _ngroup.load(butil::memory_order_acquire); + if (ngroup >= 1) { + BlockGroup* const g = + _block_groups[ngroup - 1].load(butil::memory_order_consume); + const size_t block_index = + g->nblock.fetch_add(1, butil::memory_order_relaxed); + if (block_index < RP_GROUP_NBLOCK) { + g->blocks[block_index].store( + new_block, butil::memory_order_release); + *index = (ngroup - 1) * RP_GROUP_NBLOCK + block_index; + return new_block; + } + g->nblock.fetch_sub(1, butil::memory_order_relaxed); + } + } while (add_block_group(ngroup)); + + // Fail to add_block_group. + delete new_block; + return NULL; + } + + // Create a BlockGroup and append it to _block_groups. + // Shall be called infrequently because a BlockGroup is pretty big. + static bool add_block_group(size_t old_ngroup) { + BlockGroup* bg = NULL; + BAIDU_SCOPED_LOCK(_block_group_mutex); + const size_t ngroup = _ngroup.load(butil::memory_order_acquire); + if (ngroup != old_ngroup) { + // Other thread got lock and added group before this thread. + return true; + } + if (ngroup < RP_MAX_BLOCK_NGROUP) { + bg = new(std::nothrow) BlockGroup; + if (NULL != bg) { + // Release fence is paired with consume fence in address() and + // add_block() to avoid un-constructed bg to be seen by other + // threads. + _block_groups[ngroup].store(bg, butil::memory_order_release); + _ngroup.store(ngroup + 1, butil::memory_order_release); + } + } + return bg != NULL; + } + + inline LocalPool* get_or_new_local_pool() { + LocalPool* lp = BAIDU_GET_VOLATILE_THREAD_LOCAL(_local_pool); + if (lp != NULL) { + return lp; + } + lp = new(std::nothrow) LocalPool(this); + if (NULL == lp) { + return NULL; + } + BAIDU_SCOPED_LOCK(_change_thread_mutex); //avoid race with clear() + BAIDU_SET_VOLATILE_THREAD_LOCAL(_local_pool, lp); + butil::thread_atexit(LocalPool::delete_local_pool, lp); + _nlocal.fetch_add(1, butil::memory_order_relaxed); + return lp; + } + + void clear_from_destructor_of_local_pool() { + // Remove tls + _local_pool = NULL; + + if (_nlocal.fetch_sub(1, butil::memory_order_relaxed) != 1) { + return; + } + + // Can't delete global even if all threads(called ResourcePool + // functions) quit because the memory may still be referenced by + // other threads. But we need to validate that all memory can + // be deallocated correctly in tests, so wrap the function with + // a macro which is only defined in unittests. +#ifdef BAIDU_CLEAR_RESOURCE_POOL_AFTER_ALL_THREADS_QUIT + BAIDU_SCOPED_LOCK(_change_thread_mutex); // including acquire fence. + // Do nothing if there're active threads. + if (_nlocal.load(butil::memory_order_relaxed) != 0) { + return; + } + // All threads exited and we're holding _change_thread_mutex to avoid + // racing with new threads calling get_resource(). + + // Clear global free list. + FreeChunk dummy; + while (pop_free_chunk(dummy)); + + // Delete all memory + const size_t ngroup = _ngroup.exchange(0, butil::memory_order_relaxed); + for (size_t i = 0; i < ngroup; ++i) { + BlockGroup* bg = _block_groups[i].load(butil::memory_order_relaxed); + if (NULL == bg) { + break; + } + size_t nblock = std::min(bg->nblock.load(butil::memory_order_relaxed), + RP_GROUP_NBLOCK); + for (size_t j = 0; j < nblock; ++j) { + Block* b = bg->blocks[j].load(butil::memory_order_relaxed); + if (NULL == b) { + continue; + } + for (size_t k = 0; k < b->nitem; ++k) { + T* const objs = (T*)b->items; + objs[k].~T(); + } + delete b; + } + delete bg; + } + + memset(_block_groups, 0, sizeof(BlockGroup*) * RP_MAX_BLOCK_NGROUP); +#endif + } + +private: + bool pop_free_chunk(FreeChunk& c) { + // Critical for the case that most return_object are called in + // different threads of get_object. + if (_free_chunks.empty()) { + return false; + } + pthread_mutex_lock(&_free_chunks_mutex); + if (_free_chunks.empty()) { + pthread_mutex_unlock(&_free_chunks_mutex); + return false; + } + DynamicFreeChunk* p = _free_chunks.back(); + _free_chunks.pop_back(); + pthread_mutex_unlock(&_free_chunks_mutex); + c.nfree = p->nfree; + memcpy(c.ids, p->ids, sizeof(*p->ids) * p->nfree); + free(p); + return true; + } + + bool push_free_chunk(const FreeChunk& c) { + DynamicFreeChunk* p = (DynamicFreeChunk*)malloc( + offsetof(DynamicFreeChunk, ids) + sizeof(*c.ids) * c.nfree); + if (!p) { + return false; + } + p->nfree = c.nfree; + memcpy(p->ids, c.ids, sizeof(*c.ids) * c.nfree); + pthread_mutex_lock(&_free_chunks_mutex); + _free_chunks.push_back(p); + pthread_mutex_unlock(&_free_chunks_mutex); + return true; + } + + static butil::static_atomic _singleton; + static pthread_mutex_t _singleton_mutex; + STATIC_MEMBER_BAIDU_VOLATILE_THREAD_LOCAL(LocalPool*, _local_pool); + static butil::static_atomic _nlocal; + static butil::static_atomic _ngroup; + static pthread_mutex_t _block_group_mutex; + static pthread_mutex_t _change_thread_mutex; + static butil::static_atomic _block_groups[RP_MAX_BLOCK_NGROUP]; + + std::vector _free_chunks; + pthread_mutex_t _free_chunks_mutex; + +#ifdef BUTIL_RESOURCE_POOL_NEED_FREE_ITEM_NUM + static butil::static_atomic _global_nfree; +#endif +}; + +// Declare template static variables: + +template +const size_t ResourcePool::FREE_CHUNK_NITEM; + +template +BAIDU_THREAD_LOCAL typename ResourcePool::LocalPool* +ResourcePool::_local_pool = NULL; + +template +butil::static_atomic*> ResourcePool::_singleton = + BUTIL_STATIC_ATOMIC_INIT(NULL); + +template +pthread_mutex_t ResourcePool::_singleton_mutex = PTHREAD_MUTEX_INITIALIZER; + +template +butil::static_atomic ResourcePool::_nlocal = BUTIL_STATIC_ATOMIC_INIT(0); + +template +butil::static_atomic ResourcePool::_ngroup = BUTIL_STATIC_ATOMIC_INIT(0); + +template +pthread_mutex_t ResourcePool::_block_group_mutex = PTHREAD_MUTEX_INITIALIZER; + +template +pthread_mutex_t ResourcePool::_change_thread_mutex = + PTHREAD_MUTEX_INITIALIZER; + +template +butil::static_atomic::BlockGroup*> +ResourcePool::_block_groups[RP_MAX_BLOCK_NGROUP] = {}; + +#ifdef BUTIL_RESOURCE_POOL_NEED_FREE_ITEM_NUM +template +butil::static_atomic ResourcePool::_global_nfree = BUTIL_STATIC_ATOMIC_INIT(0); +#endif + +template +inline bool operator==(ResourceId id1, ResourceId id2) { + return id1.value == id2.value; +} + +template +inline bool operator!=(ResourceId id1, ResourceId id2) { + return id1.value != id2.value; +} + +// Disable comparisons between different typed ResourceId +template +bool operator==(ResourceId id1, ResourceId id2); + +template +bool operator!=(ResourceId id1, ResourceId id2); + +inline std::ostream& operator<<(std::ostream& os, + ResourcePoolInfo const& info) { + return os << "local_pool_num: " << info.local_pool_num + << "\nblock_group_num: " << info.block_group_num + << "\nblock_num: " << info.block_num + << "\nitem_num: " << info.item_num + << "\nblock_item_num: " << info.block_item_num + << "\nfree_chunk_item_num: " << info.free_chunk_item_num + << "\ntotal_size: " << info.total_size; +#ifdef BUTIL_RESOURCE_POOL_NEED_FREE_ITEM_NUM + << "\nfree_num: " << info.free_item_num +#endif + ; +} + +} // namespace butil + +#endif // BUTIL_RESOURCE_POOL_INL_H diff --git a/src/butil/safe_strerror_posix.cc b/src/butil/safe_strerror_posix.cc new file mode 100644 index 0000000..ece0c4a --- /dev/null +++ b/src/butil/safe_strerror_posix.cc @@ -0,0 +1,115 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/build_config.h" +#include "butil/safe_strerror_posix.h" + +#include +#include +#include + +#if defined(__GLIBC__) || defined(OS_NACL) +# define USE_HISTORICAL_STRERRO_R 1 +#else +# define USE_HISTORICAL_STRERRO_R 0 +#endif + +#if USE_HISTORICAL_STRERRO_R && defined(__GNUC__) +// GCC will complain about the unused second wrap function unless we tell it +// that we meant for them to be potentially unused, which is exactly what this +// attribute is for. +#define POSSIBLY_UNUSED __attribute__((unused)) +#else +#define POSSIBLY_UNUSED +#endif + +#if USE_HISTORICAL_STRERRO_R +// glibc has two strerror_r functions: a historical GNU-specific one that +// returns type char *, and a POSIX.1-2001 compliant one available since 2.3.4 +// that returns int. This wraps the GNU-specific one. +static void POSSIBLY_UNUSED wrap_posix_strerror_r( + char *(*strerror_r_ptr)(int, char *, size_t), + int err, + char *buf, + size_t len) { + // GNU version. + char *rc = (*strerror_r_ptr)(err, buf, len); + if (rc != buf) { + // glibc did not use buf and returned a static string instead. Copy it + // into buf. + buf[0] = '\0'; + strncat(buf, rc, len - 1); + } + // The GNU version never fails. Unknown errors get an "unknown error" message. + // The result is always null terminated. +} +#endif // USE_HISTORICAL_STRERRO_R + +// Wrapper for strerror_r functions that implement the POSIX interface. POSIX +// does not define the behaviour for some of the edge cases, so we wrap it to +// guarantee that they are handled. This is compiled on all POSIX platforms, but +// it will only be used on Linux if the POSIX strerror_r implementation is +// being used (see below). +static void POSSIBLY_UNUSED wrap_posix_strerror_r( + int (*strerror_r_ptr)(int, char *, size_t), + int err, + char *buf, + size_t len) { + int old_errno = errno; + // Have to cast since otherwise we get an error if this is the GNU version + // (but in such a scenario this function is never called). Sadly we can't use + // C++-style casts because the appropriate one is reinterpret_cast but it's + // considered illegal to reinterpret_cast a type to itself, so we get an + // error in the opposite case. + int result = (*strerror_r_ptr)(err, buf, len); + if (result == 0) { + // POSIX is vague about whether the string will be terminated, although + // it indirectly implies that typically ERANGE will be returned, instead + // of truncating the string. We play it safe by always terminating the + // string explicitly. + buf[len - 1] = '\0'; + } else { + // Error. POSIX is vague about whether the return value is itself a system + // error code or something else. On Linux currently it is -1 and errno is + // set. On BSD-derived systems it is a system error and errno is unchanged. + // We try and detect which case it is so as to put as much useful info as + // we can into our message. + int strerror_error; // The error encountered in strerror + int new_errno = errno; + if (new_errno != old_errno) { + // errno was changed, so probably the return value is just -1 or something + // else that doesn't provide any info, and errno is the error. + strerror_error = new_errno; + } else { + // Either the error from strerror_r was the same as the previous value, or + // errno wasn't used. Assume the latter. + strerror_error = result; + } + // snprintf truncates and always null-terminates. + snprintf(buf, + len, + "Error %d while retrieving error %d", + strerror_error, + err); + } + errno = old_errno; +} + +void safe_strerror_r(int err, char *buf, size_t len) { + if (buf == NULL || len <= 0) { + return; + } + // If using glibc (i.e., Linux), the compiler will automatically select the + // appropriate overloaded function based on the function type of strerror_r. + // The other one will be elided from the translation unit since both are + // static. + wrap_posix_strerror_r(&strerror_r, err, buf, len); +} + +std::string safe_strerror(int err) { + const int buffer_size = 256; + char buf[buffer_size]; + safe_strerror_r(err, buf, sizeof(buf)); + return std::string(buf); +} diff --git a/src/butil/safe_strerror_posix.h b/src/butil/safe_strerror_posix.h new file mode 100644 index 0000000..c88eff4 --- /dev/null +++ b/src/butil/safe_strerror_posix.h @@ -0,0 +1,38 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_SAFE_STRERROR_POSIX_H_ +#define BUTIL_SAFE_STRERROR_POSIX_H_ + +#include + +#include "butil/base_export.h" + +// BEFORE using anything from this file, first look at PLOG and friends in +// logging.h and use them instead if applicable. +// +// This file declares safe, portable alternatives to the POSIX strerror() +// function. strerror() is inherently unsafe in multi-threaded apps and should +// never be used. Doing so can cause crashes. Additionally, the thread-safe +// alternative strerror_r varies in semantics across platforms. Use these +// functions instead. + +// Thread-safe strerror function with dependable semantics that never fails. +// It will write the string form of error "err" to buffer buf of length len. +// If there is an error calling the OS's strerror_r() function then a message to +// that effect will be printed into buf, truncating if necessary. The final +// result is always null-terminated. The value of errno is never changed. +// +// Use this instead of strerror_r(). +BUTIL_EXPORT void safe_strerror_r(int err, char *buf, size_t len); + +// Calls safe_strerror_r with a buffer of suitable size and returns the result +// in a C++ string. +// +// Use this instead of strerror(). Note though that safe_strerror_r will be +// more robust in the case of heap corruption errors, since it doesn't need to +// allocate a string. +BUTIL_EXPORT std::string safe_strerror(int err); + +#endif // BUTIL_SAFE_STRERROR_POSIX_H_ diff --git a/src/butil/scoped_clear_errno.h b/src/butil/scoped_clear_errno.h new file mode 100644 index 0000000..ec80aab --- /dev/null +++ b/src/butil/scoped_clear_errno.h @@ -0,0 +1,34 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_SCOPED_CLEAR_ERRNO_H_ +#define BUTIL_SCOPED_CLEAR_ERRNO_H_ + +#include + +#include "butil/basictypes.h" + +namespace butil { + +// Simple scoper that saves the current value of errno, resets it to 0, and on +// destruction puts the old value back. +class ScopedClearErrno { + public: + ScopedClearErrno() : old_errno_(errno) { + errno = 0; + } + ~ScopedClearErrno() { + if (errno == 0) + errno = old_errno_; + } + + private: + const int old_errno_; + + DISALLOW_COPY_AND_ASSIGN(ScopedClearErrno); +}; + +} // namespace butil + +#endif // BUTIL_SCOPED_CLEAR_ERRNO_H_ diff --git a/src/butil/scoped_generic.h b/src/butil/scoped_generic.h new file mode 100644 index 0000000..d92c095 --- /dev/null +++ b/src/butil/scoped_generic.h @@ -0,0 +1,177 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_SCOPED_GENERIC_H_ +#define BUTIL_SCOPED_GENERIC_H_ + +#include + +#include + +#include "butil/compiler_specific.h" +#include "butil/move.h" +#include "butil/macros.h" + +namespace butil { + +// This class acts like ScopedPtr with a custom deleter (although is slightly +// less fancy in some of the more escoteric respects) except that it keeps a +// copy of the object rather than a pointer, and we require that the contained +// object has some kind of "invalid" value. +// +// Defining a scoper based on this class allows you to get a scoper for +// non-pointer types without having to write custom code for set, reset, and +// move, etc. and get almost identical semantics that people are used to from +// scoped_ptr. +// +// It is intended that you will typedef this class with an appropriate deleter +// to implement clean up tasks for objects that act like pointers from a +// resource management standpoint but aren't, such as file descriptors and +// various types of operating system handles. Using scoped_ptr for these +// things requires that you keep a pointer to the handle valid for the lifetime +// of the scoper (which is easy to mess up). +// +// For an object to be able to be put into a ScopedGeneric, it must support +// standard copyable semantics and have a specific "invalid" value. The traits +// must define a free function and also the invalid value to assign for +// default-constructed and released objects. +// +// struct FooScopedTraits { +// // It's assumed that this is a fast inline function with little-to-no +// // penalty for duplicate calls. This must be a static function even +// // for stateful traits. +// static int InvalidValue() { +// return 0; +// } +// +// // This free function will not be called if f == InvalidValue()! +// static void Free(int f) { +// ::FreeFoo(f); +// } +// }; +// +// typedef ScopedGeneric ScopedFoo; +template +class ScopedGeneric { + MOVE_ONLY_TYPE_FOR_CPP_03(ScopedGeneric, RValue) + + private: + // This must be first since it's used inline below. + // + // Use the empty base class optimization to allow us to have a D + // member, while avoiding any space overhead for it when D is an + // empty class. See e.g. http://www.cantrip.org/emptyopt.html for a good + // discussion of this technique. + struct Data : public Traits { + explicit Data(const T& in) : generic(in) {} + Data(const T& in, const Traits& other) : Traits(other), generic(in) {} + T generic; + }; + + public: + typedef T element_type; + typedef Traits traits_type; + + ScopedGeneric() : data_(traits_type::InvalidValue()) {} + + // Constructor. Takes responsibility for freeing the resource associated with + // the object T. + explicit ScopedGeneric(const element_type& value) : data_(value) {} + + // Constructor. Allows initialization of a stateful traits object. + ScopedGeneric(const element_type& value, const traits_type& traits) + : data_(value, traits) { + } + + // Move constructor for C++03 move emulation. + ScopedGeneric(RValue rvalue) + : data_(rvalue.object->release(), rvalue.object->get_traits()) { + } + + ~ScopedGeneric() { + FreeIfNecessary(); + } + + // Frees the currently owned object, if any. Then takes ownership of a new + // object, if given. Self-resets are not allowed as on scoped_ptr. See + // http://crbug.com/162971 + void reset(const element_type& value = traits_type::InvalidValue()) { + RELEASE_ASSERT(data_.generic == traits_type::InvalidValue() || + data_.generic != value); + FreeIfNecessary(); + data_.generic = value; + } + + void swap(ScopedGeneric& other) { + // Standard swap idiom: 'using std::swap' ensures that std::swap is + // present in the overload set, but we call swap unqualified so that + // any more-specific overloads can be used, if available. + using std::swap; + swap(static_cast(data_), static_cast(other.data_)); + swap(data_.generic, other.data_.generic); + } + + // Release the object. The return value is the current object held by this + // object. After this operation, this object will hold a null value, and + // will not own the object any more. + element_type release() WARN_UNUSED_RESULT { + element_type old_generic = data_.generic; + data_.generic = traits_type::InvalidValue(); + return old_generic; + } + + const element_type& get() const { return data_.generic; } + + // Returns true if this object doesn't hold the special null value for the + // associated data type. + bool is_valid() const { return data_.generic != traits_type::InvalidValue(); } + + bool operator==(const element_type& value) const { + return data_.generic == value; + } + bool operator!=(const element_type& value) const { + return data_.generic != value; + } + + Traits& get_traits() { return data_; } + const Traits& get_traits() const { return data_; } + + private: + void FreeIfNecessary() { + if (data_.generic != traits_type::InvalidValue()) { + data_.Free(data_.generic); + data_.generic = traits_type::InvalidValue(); + } + } + + // Forbid comparison. If U != T, it totally doesn't make sense, and if U == + // T, it still doesn't make sense because you should never have the same + // object owned by two different ScopedGenerics. + template bool operator==( + const ScopedGeneric& p2) const; + template bool operator!=( + const ScopedGeneric& p2) const; + + Data data_; +}; + +template +void swap(const ScopedGeneric& a, + const ScopedGeneric& b) { + a.swap(b); +} + +template +bool operator==(const T& value, const ScopedGeneric& scoped) { + return value == scoped.get(); +} + +template +bool operator!=(const T& value, const ScopedGeneric& scoped) { + return value != scoped.get(); +} + +} // namespace butil + +#endif // BUTIL_SCOPED_GENERIC_H_ diff --git a/src/butil/scoped_lock.h b/src/butil/scoped_lock.h new file mode 100644 index 0000000..1111daa --- /dev/null +++ b/src/butil/scoped_lock.h @@ -0,0 +1,400 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BUTIL_BAIDU_SCOPED_LOCK_H +#define BUTIL_BAIDU_SCOPED_LOCK_H + +#include "butil/build_config.h" + +#if defined(BUTIL_CXX11_ENABLED) +#include // std::lock_guard +#endif + +#include "butil/synchronization/lock.h" +#include "butil/macros.h" +#include "butil/logging.h" +#include "butil/errno.h" + +#if !defined(BUTIL_CXX11_ENABLED) +#define BAIDU_SCOPED_LOCK(ref_of_lock) \ + std::lock_guard \ + BAIDU_CONCAT(scoped_locker_dummy_at_line_, __LINE__)(ref_of_lock) +#else + +// NOTE(gejun): c++11 deduces additional reference to the type. +namespace butil { +namespace detail { +template +std::lock_guard::type> get_lock_guard(); +} // namespace detail +} // namespace butil + +#define BAIDU_SCOPED_LOCK(ref_of_lock) \ + decltype(::butil::detail::get_lock_guard()) \ + BAIDU_CONCAT(scoped_locker_dummy_at_line_, __LINE__)(ref_of_lock) +#endif + +namespace std { + +#if !defined(BUTIL_CXX11_ENABLED) + +// Do not acquire ownership of the mutex +struct defer_lock_t {}; +static const defer_lock_t defer_lock = {}; + +// Try to acquire ownership of the mutex without blocking +struct try_to_lock_t {}; +static const try_to_lock_t try_to_lock = {}; + +// Assume the calling thread already has ownership of the mutex +struct adopt_lock_t {}; +static const adopt_lock_t adopt_lock = {}; + +template class lock_guard { +public: + explicit lock_guard(Mutex & mutex) : _pmutex(&mutex) { _pmutex->lock(); } + ~lock_guard() { _pmutex->unlock(); } +private: + DISALLOW_COPY_AND_ASSIGN(lock_guard); + Mutex* _pmutex; +}; + +template class unique_lock { + DISALLOW_COPY_AND_ASSIGN(unique_lock); +public: + typedef Mutex mutex_type; + unique_lock() : _mutex(NULL), _owns_lock(false) {} + explicit unique_lock(mutex_type& mutex) + : _mutex(&mutex), _owns_lock(true) { + mutex.lock(); + } + unique_lock(mutex_type& mutex, defer_lock_t) + : _mutex(&mutex), _owns_lock(false) + {} + unique_lock(mutex_type& mutex, try_to_lock_t) + : _mutex(&mutex), _owns_lock(mutex.try_lock()) + {} + unique_lock(mutex_type& mutex, adopt_lock_t) + : _mutex(&mutex), _owns_lock(true) + {} + + ~unique_lock() { + if (_owns_lock) { + _mutex->unlock(); + } + } + + void lock() { + if (_owns_lock) { + CHECK(false) << "Detected deadlock issue"; + return; + } + _owns_lock = true; + _mutex->lock(); + } + + bool try_lock() { + if (_owns_lock) { + CHECK(false) << "Detected deadlock issue"; + return false; + } + _owns_lock = _mutex->try_lock(); + return _owns_lock; + } + + void unlock() { + if (!_owns_lock) { + CHECK(false) << "Invalid operation"; + return; + } + _mutex->unlock(); + _owns_lock = false; + } + + void swap(unique_lock& rhs) { + std::swap(_mutex, rhs._mutex); + std::swap(_owns_lock, rhs._owns_lock); + } + + mutex_type* release() { + mutex_type* saved_mutex = _mutex; + _mutex = NULL; + _owns_lock = false; + return saved_mutex; + } + + mutex_type* mutex() { return _mutex; } + bool owns_lock() const { return _owns_lock; } + operator bool() const { return owns_lock(); } + +private: + mutex_type* _mutex; + bool _owns_lock; +}; + +#endif // !defined(BUTIL_CXX11_ENABLED) + +#if defined(OS_POSIX) + +template<> class lock_guard { +public: + explicit lock_guard(pthread_mutex_t & mutex) : _pmutex(&mutex) { +#if !defined(NDEBUG) + const int rc = pthread_mutex_lock(_pmutex); + if (rc) { + LOG(FATAL) << "Fail to lock pthread_mutex_t=" << _pmutex << ", " << berror(rc); + _pmutex = NULL; + } +#else + pthread_mutex_lock(_pmutex); +#endif // NDEBUG + } + + ~lock_guard() { +#ifndef NDEBUG + if (_pmutex) { + pthread_mutex_unlock(_pmutex); + } +#else + pthread_mutex_unlock(_pmutex); +#endif + } + +private: + DISALLOW_COPY_AND_ASSIGN(lock_guard); + pthread_mutex_t* _pmutex; +}; + +template<> class lock_guard { +public: + explicit lock_guard(pthread_spinlock_t & spin) : _pspin(&spin) { +#if !defined(NDEBUG) + const int rc = pthread_spin_lock(_pspin); + if (rc) { + LOG(FATAL) << "Fail to lock pthread_spinlock_t=" << _pspin << ", " << berror(rc); + _pspin = NULL; + } +#else + pthread_spin_lock(_pspin); +#endif // NDEBUG + } + + ~lock_guard() { +#ifndef NDEBUG + if (_pspin) { + pthread_spin_unlock(_pspin); + } +#else + pthread_spin_unlock(_pspin); +#endif + } + +private: + DISALLOW_COPY_AND_ASSIGN(lock_guard); + pthread_spinlock_t* _pspin; +}; + +template<> class unique_lock { + DISALLOW_COPY_AND_ASSIGN(unique_lock); +public: + typedef pthread_mutex_t mutex_type; + unique_lock() : _mutex(NULL), _owns_lock(false) {} + explicit unique_lock(mutex_type& mutex) + : _mutex(&mutex), _owns_lock(true) { + pthread_mutex_lock(_mutex); + } + unique_lock(mutex_type& mutex, defer_lock_t) + : _mutex(&mutex), _owns_lock(false) + {} + unique_lock(mutex_type& mutex, try_to_lock_t) + : _mutex(&mutex), _owns_lock(pthread_mutex_trylock(&mutex) == 0) + {} + unique_lock(mutex_type& mutex, adopt_lock_t) + : _mutex(&mutex), _owns_lock(true) + {} + + ~unique_lock() { + if (_owns_lock) { + pthread_mutex_unlock(_mutex); + } + } + + void lock() { + if (_owns_lock) { + CHECK(false) << "Detected deadlock issue"; + return; + } +#if !defined(NDEBUG) + const int rc = pthread_mutex_lock(_mutex); + if (rc) { + LOG(FATAL) << "Fail to lock pthread_mutex=" << _mutex << ", " << berror(rc); + return; + } + _owns_lock = true; +#else + _owns_lock = true; + pthread_mutex_lock(_mutex); +#endif // NDEBUG + } + + bool try_lock() { + if (_owns_lock) { + CHECK(false) << "Detected deadlock issue"; + return false; + } + _owns_lock = !pthread_mutex_trylock(_mutex); + return _owns_lock; + } + + void unlock() { + if (!_owns_lock) { + CHECK(false) << "Invalid operation"; + return; + } + pthread_mutex_unlock(_mutex); + _owns_lock = false; + } + + void swap(unique_lock& rhs) { + std::swap(_mutex, rhs._mutex); + std::swap(_owns_lock, rhs._owns_lock); + } + + mutex_type* release() { + mutex_type* saved_mutex = _mutex; + _mutex = NULL; + _owns_lock = false; + return saved_mutex; + } + + mutex_type* mutex() { return _mutex; } + bool owns_lock() const { return _owns_lock; } + operator bool() const { return owns_lock(); } + +private: + mutex_type* _mutex; + bool _owns_lock; +}; + +template<> class unique_lock { + DISALLOW_COPY_AND_ASSIGN(unique_lock); +public: + typedef pthread_spinlock_t mutex_type; + unique_lock() : _mutex(NULL), _owns_lock(false) {} + explicit unique_lock(mutex_type& mutex) + : _mutex(&mutex), _owns_lock(true) { + pthread_spin_lock(_mutex); + } + + ~unique_lock() { + if (_owns_lock) { + pthread_spin_unlock(_mutex); + } + } + unique_lock(mutex_type& mutex, defer_lock_t) + : _mutex(&mutex), _owns_lock(false) + {} + unique_lock(mutex_type& mutex, try_to_lock_t) + : _mutex(&mutex), _owns_lock(pthread_spin_trylock(&mutex) == 0) + {} + unique_lock(mutex_type& mutex, adopt_lock_t) + : _mutex(&mutex), _owns_lock(true) + {} + + void lock() { + if (_owns_lock) { + CHECK(false) << "Detected deadlock issue"; + return; + } +#if !defined(NDEBUG) + const int rc = pthread_spin_lock(_mutex); + if (rc) { + LOG(FATAL) << "Fail to lock pthread_spinlock=" << _mutex << ", " << berror(rc); + return; + } + _owns_lock = true; +#else + _owns_lock = true; + pthread_spin_lock(_mutex); +#endif // NDEBUG + } + + bool try_lock() { + if (_owns_lock) { + CHECK(false) << "Detected deadlock issue"; + return false; + } + _owns_lock = !pthread_spin_trylock(_mutex); + return _owns_lock; + } + + void unlock() { + if (!_owns_lock) { + CHECK(false) << "Invalid operation"; + return; + } + pthread_spin_unlock(_mutex); + _owns_lock = false; + } + + void swap(unique_lock& rhs) { + std::swap(_mutex, rhs._mutex); + std::swap(_owns_lock, rhs._owns_lock); + } + + mutex_type* release() { + mutex_type* saved_mutex = _mutex; + _mutex = NULL; + _owns_lock = false; + return saved_mutex; + } + + mutex_type* mutex() { return _mutex; } + bool owns_lock() const { return _owns_lock; } + operator bool() const { return owns_lock(); } + +private: + mutex_type* _mutex; + bool _owns_lock; +}; + +#endif // defined(OS_POSIX) + +} // namespace std + +namespace butil { + +// Lock both lck1 and lck2 without the deadlock issue. +template +void double_lock(std::unique_lock &lck1, std::unique_lock &lck2) { + DCHECK(!lck1.owns_lock()); + DCHECK(!lck2.owns_lock()); + volatile void* const ptr1 = lck1.mutex(); + volatile void* const ptr2 = lck2.mutex(); + DCHECK_NE(ptr1, ptr2); + if (ptr1 < ptr2) { + lck1.lock(); + lck2.lock(); + } else { + lck2.lock(); + lck1.lock(); + } +} + +}; + +#endif // BUTIL_BAIDU_SCOPED_LOCK_H diff --git a/src/butil/scoped_observer.h b/src/butil/scoped_observer.h new file mode 100644 index 0000000..55158c4 --- /dev/null +++ b/src/butil/scoped_observer.h @@ -0,0 +1,56 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_SCOPED_OBSERVER_H_ +#define BUTIL_SCOPED_OBSERVER_H_ + +#include +#include + +#include "butil/basictypes.h" + +// ScopedObserver is used to keep track of the set of sources an object has +// attached itself to as an observer. When ScopedObserver is destroyed it +// removes the object as an observer from all sources it has been added to. +template +class ScopedObserver { + public: + explicit ScopedObserver(Observer* observer) : observer_(observer) {} + + ~ScopedObserver() { + RemoveAll(); + } + + // Adds the object passed to the constructor as an observer on |source|. + void Add(Source* source) { + sources_.push_back(source); + source->AddObserver(observer_); + } + + // Remove the object passed to the constructor as an observer from |source|. + void Remove(Source* source) { + sources_.erase(std::find(sources_.begin(), sources_.end(), source)); + source->RemoveObserver(observer_); + } + + void RemoveAll() { + for (size_t i = 0; i < sources_.size(); ++i) + sources_[i]->RemoveObserver(observer_); + sources_.clear(); + } + + bool IsObserving(Source* source) const { + return std::find(sources_.begin(), sources_.end(), source) != + sources_.end(); + } + + private: + Observer* observer_; + + std::vector sources_; + + DISALLOW_COPY_AND_ASSIGN(ScopedObserver); +}; + +#endif // BUTIL_SCOPED_OBSERVER_H_ diff --git a/src/butil/sha1.h b/src/butil/sha1.h new file mode 100644 index 0000000..b6eb465 --- /dev/null +++ b/src/butil/sha1.h @@ -0,0 +1,29 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_SHA1_H_ +#define BUTIL_SHA1_H_ + +#include + +#include "butil/base_export.h" + +namespace butil { + +// These functions perform SHA-1 operations. + +static const size_t kSHA1Length = 20; // Length in bytes of a SHA-1 hash. + +// Computes the SHA-1 hash of the input string |str| and returns the full +// hash. +BUTIL_EXPORT std::string SHA1HashString(const std::string& str); + +// Computes the SHA-1 hash of the |len| bytes in |data| and puts the hash +// in |hash|. |hash| must be kSHA1Length bytes long. +BUTIL_EXPORT void SHA1HashBytes(const unsigned char* data, size_t len, + unsigned char* hash); + +} // namespace butil + +#endif // BUTIL_SHA1_H_ diff --git a/src/butil/sha1_portable.cc b/src/butil/sha1_portable.cc new file mode 100644 index 0000000..72db4be --- /dev/null +++ b/src/butil/sha1_portable.cc @@ -0,0 +1,215 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/sha1.h" + +#include + +#include "butil/basictypes.h" + +namespace butil { + +// Implementation of SHA-1. Only handles data in byte-sized blocks, +// which simplifies the code a fair bit. + +// Identifier names follow notation in FIPS PUB 180-3, where you'll +// also find a description of the algorithm: +// http://csrc.nist.gov/publications/fips/fips180-3/fips180-3_final.pdf + +// Usage example: +// +// SecureHashAlgorithm sha; +// while(there is data to hash) +// sha.Update(moredata, size of data); +// sha.Final(); +// memcpy(somewhere, sha.Digest(), 20); +// +// to reuse the instance of sha, call sha.Init(); + +// TODO(jhawkins): Replace this implementation with a per-platform +// implementation using each platform's crypto library. See +// http://crbug.com/47218 + +class SecureHashAlgorithm { + public: + SecureHashAlgorithm() { Init(); } + + static const int kDigestSizeBytes; + + void Init(); + void Update(const void* data, size_t nbytes); + void Final(); + + // 20 bytes of message digest. + const unsigned char* Digest() const { + return reinterpret_cast(H); + } + + private: + void Pad(); + void Process(); + + uint32_t A, B, C, D, E; + + uint32_t H[5]; + + union { + uint32_t W[80]; + uint8_t M[64]; + }; + + uint32_t cursor; + uint32_t l; +}; + +static inline uint32_t f(uint32_t t, uint32_t B, uint32_t C, uint32_t D) { + if (t < 20) { + return (B & C) | ((~B) & D); + } else if (t < 40) { + return B ^ C ^ D; + } else if (t < 60) { + return (B & C) | (B & D) | (C & D); + } else { + return B ^ C ^ D; + } +} + +static inline uint32_t S(uint32_t n, uint32_t X) { + return (X << n) | (X >> (32-n)); +} + +static inline uint32_t K(uint32_t t) { + if (t < 20) { + return 0x5a827999; + } else if (t < 40) { + return 0x6ed9eba1; + } else if (t < 60) { + return 0x8f1bbcdc; + } else { + return 0xca62c1d6; + } +} + +static inline void swapends(uint32_t* t) { + *t = ((*t & 0xff000000) >> 24) | + ((*t & 0xff0000) >> 8) | + ((*t & 0xff00) << 8) | + ((*t & 0xff) << 24); +} + +const int SecureHashAlgorithm::kDigestSizeBytes = 20; + +void SecureHashAlgorithm::Init() { + A = 0; + B = 0; + C = 0; + D = 0; + E = 0; + cursor = 0; + l = 0; + H[0] = 0x67452301; + H[1] = 0xefcdab89; + H[2] = 0x98badcfe; + H[3] = 0x10325476; + H[4] = 0xc3d2e1f0; +} + +void SecureHashAlgorithm::Final() { + Pad(); + Process(); + + for (int t = 0; t < 5; ++t) + swapends(&H[t]); +} + +void SecureHashAlgorithm::Update(const void* data, size_t nbytes) { + const uint8_t* d = reinterpret_cast(data); + while (nbytes--) { + M[cursor++] = *d++; + if (cursor >= 64) + Process(); + l += 8; + } +} + +void SecureHashAlgorithm::Pad() { + M[cursor++] = 0x80; + + if (cursor > 64-8) { + // pad out to next block + while (cursor < 64) + M[cursor++] = 0; + + Process(); + } + + while (cursor < 64-4) + M[cursor++] = 0; + + M[64-4] = (l & 0xff000000) >> 24; + M[64-3] = (l & 0xff0000) >> 16; + M[64-2] = (l & 0xff00) >> 8; + M[64-1] = (l & 0xff); +} + +void SecureHashAlgorithm::Process() { + uint32_t t; + + // Each a...e corresponds to a section in the FIPS 180-3 algorithm. + + // a. + // + // W and M are in a union, so no need to memcpy. + // memcpy(W, M, sizeof(M)); + for (t = 0; t < 16; ++t) + swapends(&W[t]); + + // b. + for (t = 16; t < 80; ++t) + W[t] = S(1, W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16]); + + // c. + A = H[0]; + B = H[1]; + C = H[2]; + D = H[3]; + E = H[4]; + + // d. + for (t = 0; t < 80; ++t) { + uint32_t TEMP = S(5, A) + f(t, B, C, D) + E + W[t] + K(t); + E = D; + D = C; + C = S(30, B); + B = A; + A = TEMP; + } + + // e. + H[0] += A; + H[1] += B; + H[2] += C; + H[3] += D; + H[4] += E; + + cursor = 0; +} + +std::string SHA1HashString(const std::string& str) { + char hash[SecureHashAlgorithm::kDigestSizeBytes]; + SHA1HashBytes(reinterpret_cast(str.c_str()), + str.length(), reinterpret_cast(hash)); + return std::string(hash, SecureHashAlgorithm::kDigestSizeBytes); +} + +void SHA1HashBytes(const unsigned char* data, size_t len, + unsigned char* hash) { + SecureHashAlgorithm sha; + sha.Update(data, len); + sha.Final(); + + memcpy(hash, sha.Digest(), SecureHashAlgorithm::kDigestSizeBytes); +} + +} // namespace butil diff --git a/src/butil/shared_object.h b/src/butil/shared_object.h new file mode 100644 index 0000000..abcfd46 --- /dev/null +++ b/src/butil/shared_object.h @@ -0,0 +1,73 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +#ifndef BUTIL_SHARED_OBJECT_H +#define BUTIL_SHARED_OBJECT_H + +#include "butil/intrusive_ptr.hpp" // butil::intrusive_ptr +#include "butil/atomicops.h" + + +namespace butil { + +// Inherit this class to be intrusively shared. Comparing to shared_ptr, +// intrusive_ptr saves one malloc (for shared_count) and gets better cache +// locality when the ref/deref are frequent, in the cost of lack of weak_ptr +// and worse interface. +class SharedObject { +friend void intrusive_ptr_add_ref(SharedObject*); +friend void intrusive_ptr_release(SharedObject*); + +public: + SharedObject() : _nref(0) { } + int ref_count() const { return _nref.load(butil::memory_order_relaxed); } + + // Add ref and returns the ref_count seen before added. + // The effect is basically same as butil::intrusive_ptr(obj).detach() + // except that the latter one does not return the seen ref_count which is + // useful in some scenarios. + int AddRefManually() + { return _nref.fetch_add(1, butil::memory_order_relaxed); } + + // Remove one ref, if the ref_count hit zero, delete this object. + // Same as butil::intrusive_ptr(obj, false).reset(NULL) + void RemoveRefManually() { + if (_nref.fetch_sub(1, butil::memory_order_release) == 1) { + butil::atomic_thread_fence(butil::memory_order_acquire); + delete this; + } + } + +protected: + virtual ~SharedObject() { } +private: + butil::atomic _nref; +}; + +inline void intrusive_ptr_add_ref(SharedObject* obj) { + obj->AddRefManually(); +} + +inline void intrusive_ptr_release(SharedObject* obj) { + obj->RemoveRefManually(); +} + +} // namespace butil + + +#endif // BUTIL_SHARED_OBJECT_H diff --git a/src/butil/single_iobuf.cpp b/src/butil/single_iobuf.cpp new file mode 100644 index 0000000..942c4ba --- /dev/null +++ b/src/butil/single_iobuf.cpp @@ -0,0 +1,285 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// SingleIOBuf - A continuous zero-copied buffer + +#include "butil/logging.h" // CHECK, LOG +#include "butil/iobuf.h" +#include "butil/iobuf_inl.h" +#include "butil/single_iobuf.h" + +namespace butil { + +SingleIOBuf::SingleIOBuf() + : _cur_block(NULL) + , _block_size(0) { + _cur_ref.offset = 0; + _cur_ref.length = 0; + _cur_ref.block = NULL; +} + +SingleIOBuf::SingleIOBuf(const IOBuf::BlockRef& ref) { + _cur_block = NULL; + _block_size = 0; + if (ref.block) { + _cur_ref = ref; + _cur_ref.block->inc_ref(); + } +} + +SingleIOBuf::SingleIOBuf(const SingleIOBuf& other) { + _cur_block = NULL; + _block_size = 0; + if (other._cur_ref.block != NULL) { + _cur_ref = other._cur_ref; + _cur_ref.block->inc_ref(); + } +} + +SingleIOBuf::~SingleIOBuf() { + reset(); +} + +SingleIOBuf& SingleIOBuf::operator=(const SingleIOBuf& rhs) { + reset(); + _block_size = 0; + if (rhs._cur_ref.block != NULL) { + _cur_ref = rhs._cur_ref; + _cur_ref.block->inc_ref(); + } + return *this; +} + +void SingleIOBuf::swap(SingleIOBuf& other) { + if (this == &other) { + return; + } + + IOBuf::BlockRef tmp_ref = _cur_ref; + _cur_ref = other._cur_ref; + other._cur_ref = tmp_ref; + IOBuf::Block* tmp_ptr = other._cur_block; + other._cur_block = _cur_block; + _cur_block = tmp_ptr; + uint32_t tmp_size = other._block_size; + other._block_size = _block_size; + _block_size = tmp_size; +} + +void* SingleIOBuf::allocate(uint32_t size) { + IOBuf::Block* b = alloc_block_by_size(size); + if (!b) { + return NULL; + } + _cur_ref.offset = b->size; + _cur_ref.length = size; + _cur_ref.block = b; + b->size += size; + b->inc_ref(); + return b->data + _cur_ref.offset; +} + +void SingleIOBuf::deallocate(void* p) { + if (_cur_ref.block) { + if (_cur_ref.block->data + _cur_ref.offset == (char*)p) { + reset(); + } + } +} + +IOBuf::Block* SingleIOBuf::alloc_block_by_size(uint32_t data_size) { + if (_cur_block != NULL) { + if (_cur_block->left_space() >= data_size) { + return _cur_block; + } else { + _cur_block->dec_ref(); + _cur_block = NULL; + } + } + uint32_t total_size = data_size + sizeof(IOBuf::Block); + if (total_size <= butil::GetDefaultBlockSize()) { + _cur_block = iobuf::acquire_tls_block(); + if (_cur_block != NULL) { + if (_cur_block->left_space() >= data_size) { + return _cur_block; + } else { + _cur_block->dec_ref(); + _cur_block = NULL; + } + } + _cur_block = iobuf::create_block(); + } else { + _cur_block = iobuf::create_block(total_size); + _block_size = total_size; + } + if (BAIDU_UNLIKELY(!_cur_block)) { + errno = ENOMEM; + _block_size = 0; + return NULL; + } + return _cur_block; +} + +void SingleIOBuf::memcpy_downward(void* old_p, uint32_t old_size, + void* new_p, uint32_t new_size, + uint32_t in_use_back, uint32_t in_use_front) { + memcpy((u_char*)new_p + new_size - in_use_back, (u_char*)old_p + old_size - in_use_back, + in_use_back); + memcpy(new_p, old_p, in_use_front); +} + +void* SingleIOBuf::reallocate_downward(uint32_t new_size, uint32_t in_use_back, + uint32_t in_use_front) { + IOBuf::BlockRef& ref = _cur_ref; + if (BAIDU_UNLIKELY(new_size <= ref.length)) { + LOG(ERROR) << "invalid new size:" << new_size; + errno = EINVAL; + return NULL; + } + if (BAIDU_UNLIKELY(ref.block == NULL)) { + LOG(ERROR) << "SingleIOBuf reallocate_downward failed. Block cannot be null!"; + errno = EINVAL; + return NULL; + } + char* old_p = ref.block->data + ref.offset; + uint32_t old_size = ref.length; + IOBuf::Block* b = alloc_block_by_size(new_size); + if (!b) { + return NULL; + } + char* new_p = b->data + b->size; + memcpy_downward(old_p, old_size, + new_p, new_size, + in_use_back, in_use_front); + ref.block->dec_ref(); + _cur_ref.offset = b->size; + _cur_ref.length = new_size; + _cur_ref.block = b; + b->size += new_size; + b->inc_ref(); + return new_p; +} + +const void* SingleIOBuf::get_begin() const { + if (_cur_ref.block) { + return _cur_ref.block->data + _cur_ref.offset; + } + return NULL; +} + +uint32_t SingleIOBuf::get_length() const { + return _cur_ref.length; +} + +void SingleIOBuf::reset() { + if (_cur_block) { + if (_block_size == 0) { + iobuf::release_tls_block(_cur_block); + } else { + _cur_block->dec_ref(); + _block_size = 0; + } + _cur_block = NULL; + } + if (_cur_ref.block != NULL) { + _cur_ref.block->dec_ref(); + } + _cur_ref.offset = 0; + _cur_ref.length = 0; + _cur_ref.block = NULL; +} + +bool SingleIOBuf::assign(const IOBuf& buf, uint32_t msg_size) { + if (BAIDU_UNLIKELY(buf.length() < msg_size)) { + LOG(ERROR) << "Fail to dump_mult_iobuf msg_size:" << msg_size + << " from source iobuf of size:" << buf.length(); + return false; + } + if (BAIDU_UNLIKELY(msg_size == 0)) { + return true; + } + + const IOBuf::BlockRef& ref = buf._front_ref(); + if (ref.length >= msg_size) { + reset(); + _cur_ref.offset = ref.offset; + _cur_ref.length = msg_size; + _cur_ref.block = ref.block; + _cur_ref.block->inc_ref(); + return true; + } else { + IOBuf::Block* b = alloc_block_by_size(msg_size); + if (!b) { + return false; + } + reset(); + char* out = b->data + b->size; + const size_t nref = buf.backing_block_num(); + uint32_t last_len = msg_size; + for (size_t i = 0; i < nref && last_len > 0; ++i) { + const IOBuf::BlockRef& r = buf._ref_at(i); + uint32_t n = std::min(r.length, last_len); + iobuf::cp(out, r.block->data + r.offset, n); + last_len -= n; + out += n; + } + _cur_ref.offset = b->size; + _cur_ref.length = msg_size; + _cur_ref.block = b; + b->size += msg_size; + _cur_ref.block->inc_ref(); + return true; + } +} + +void SingleIOBuf::append_to(IOBuf* buf) const { + if (buf && _cur_ref.block) { + buf->_push_back_ref(_cur_ref); + } +} + +int SingleIOBuf::assign_user_data(void* data, size_t size, std::function deleter) { + if (size > 0xFFFFFFFFULL - 100) { + LOG(FATAL) << "data_size=" << size << " is too large"; + return -1; + } + char* mem = (char*)malloc(sizeof(IOBuf::Block) + sizeof(UserDataExtension)); + if (mem == NULL) { + return -1; + } + if (deleter == NULL) { + deleter = ::free; + } + reset(); + IOBuf::Block* b = new (mem) IOBuf::Block((char*)data, size, deleter); + _cur_ref.offset = 0; + _cur_ref.length = b->cap; + _cur_ref.block = b; + return 0; +} + +void SingleIOBuf::target_block_inc_ref(void* b) { + IOBuf::Block* block = (IOBuf::Block*)b; + block->inc_ref(); +} + +void SingleIOBuf::target_block_dec_ref(void* b) { + IOBuf::Block* block = (IOBuf::Block*)b; + block->dec_ref(); +} + +} // namespace butil diff --git a/src/butil/single_iobuf.h b/src/butil/single_iobuf.h new file mode 100644 index 0000000..9c4237a --- /dev/null +++ b/src/butil/single_iobuf.h @@ -0,0 +1,109 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// SingleIOBuf - A continuous zero-copied buffer + +#ifndef BUTIL_SINGLE_IOBUF_H +#define BUTIL_SINGLE_IOBUF_H + +#include "butil/iobuf.h" +#include "butil/iobuf_inl.h" + +namespace butil { + +// SingleIOBuf is a lightweight buffer that manages a single IOBuf::Block. +// It always ensures that the underlying memory is contiguous and +// avoids unnecessary memory copies. +// It is primarily used to efficiently serialize and deserialize +// RPC requests in flatbuffers. +class SingleIOBuf { +public: + SingleIOBuf(); + ~SingleIOBuf(); + SingleIOBuf(const IOBuf::BlockRef& ref); + SingleIOBuf(const SingleIOBuf& other); + SingleIOBuf& operator=(const SingleIOBuf& rhs); + void swap(SingleIOBuf& other); + + // Allocates a contiguous memory region of the specified size. + // Returns a pointer to the start of allocated space within the block. + void* allocate(uint32_t size); + // Deallocates the block if the given pointer matches its starting address. + void deallocate(void* p); + + // Reallocates the current buffer to a larger size. + // in_use_back indicates the memory used by message data. + // in_use_front indicates the memory used by metadata. + void* reallocate_downward(uint32_t new_size, + uint32_t in_use_back, + uint32_t in_use_front); + + // Returns a pointer to the beginning of the current buffer data. + const void* get_begin() const; + + // Get the length of the SingleIOBuf. + uint32_t get_length() const; + + // Reset the SingleIOBuf by release the block and clear the BlockRef. + void reset(); + + // Assigns data from the given IOBuf to this SingleIOBuf. + // If the source contains multiple BlockRef segments, + // they will be concatenated into a single contiguous block. + bool assign(const IOBuf& buf, uint32_t msg_size); + + // Appends the current block of the SingleIOBuf to the given IOBuf. + void append_to(IOBuf* buf) const; + + const IOBuf::BlockRef& get_cur_ref() const { return _cur_ref; } + void* get_cur_block() { return (void*)_cur_ref.block; } + + // Returns the number of underlying blocks in the SingleIOBuf, + // which is either 1 if a block exists or 0 if none. + size_t backing_block_num() const { return _cur_ref.block != NULL ? 1 : 0; } + + // Assigns user date to the SingleIOBuf, + // updates _cur_ref to point to the block storing the data. + int assign_user_data(void* data, size_t size, std::function deleter); + + // Increments the reference count of the specified target block. + static void target_block_inc_ref(void* b); + // Decrements the reference count of the specified target block. + static void target_block_dec_ref(void* b); + +protected: + // Copy from the old buffer to the corresponding positions in the new block. + void memcpy_downward(void* old_p, uint32_t old_size, + void* new_p, uint32_t new_size, + uint32_t in_use_back, uint32_t in_use_front); + + // Allocates and returns a memory block large enough to hold the specified data size, + // reusing an existing block when possible or creating a new one if necessary. + IOBuf::Block* alloc_block_by_size(uint32_t data_size); + +private: + // Current block the SingleIOBuf used to allocate memory. + IOBuf::Block* _cur_block; + // Current block total size, include sizeof(IOBuf::Block). + uint32_t _block_size; + // Point to the block storing the data. + IOBuf::BlockRef _cur_ref; +}; + +} // namespace butil + +#endif \ No newline at end of file diff --git a/src/butil/single_threaded_pool.h b/src/butil/single_threaded_pool.h new file mode 100644 index 0000000..7f34b93 --- /dev/null +++ b/src/butil/single_threaded_pool.h @@ -0,0 +1,147 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Mon. Nov 7 14:47:36 CST 2011 + +#ifndef BUTIL_SINGLE_THREADED_POOL_H +#define BUTIL_SINGLE_THREADED_POOL_H + +#include // malloc & free + +namespace butil { + +// A single-threaded pool for very efficient allocations of same-sized items. +// Example: +// SingleThreadedPool<16, 512> pool; +// void* mem = pool.get(); +// pool.back(mem); + +class PtAllocator { +public: + void* Alloc(size_t n) { return malloc(n); } + void Free(void* p) { free(p); } +}; + +template // minimum number of items in one block +class SingleThreadedPool { +public: + // Note: this is a union. The next pointer is set iff when spaces is free, + // ok to be overlapped. + union Node { + Node* next; + char spaces[ITEM_SIZE_IN]; + }; + struct Block { + static const size_t INUSE_SIZE = + BLOCK_SIZE_IN - sizeof(void*) - sizeof(size_t); + static const size_t NITEM = (sizeof(Node) <= INUSE_SIZE ? + (INUSE_SIZE / sizeof(Node)) : MIN_NITEM); + size_t nalloc; + Block* next; + Node nodes[NITEM]; + }; + static const size_t BLOCK_SIZE = sizeof(Block); + static const size_t NITEM = Block::NITEM; + static const size_t ITEM_SIZE = ITEM_SIZE_IN; + + explicit SingleThreadedPool(const Allocator& alloc = Allocator()) + : _free_nodes(NULL), _blocks(NULL), _allocator(alloc) {} + ~SingleThreadedPool() { reset(); } + + DISALLOW_COPY_AND_ASSIGN(SingleThreadedPool); + + void swap(SingleThreadedPool & other) { + std::swap(_free_nodes, other._free_nodes); + std::swap(_blocks, other._blocks); + std::swap(_allocator, other._allocator); + } + + // Get space of an item. The space is as long as ITEM_SIZE. + // Returns NULL on out of memory + void* get() { + if (_free_nodes) { + void* spaces = _free_nodes->spaces; + _free_nodes = _free_nodes->next; + return spaces; + } + if (_blocks == NULL || _blocks->nalloc >= Block::NITEM) { + Block* new_block = (Block*)_allocator.Alloc(sizeof(Block)); + if (new_block == NULL) { + return NULL; + } + new_block->nalloc = 0; + new_block->next = _blocks; + _blocks = new_block; + } + return _blocks->nodes[_blocks->nalloc++].spaces; + } + + // Return a space allocated by get() before. + // Do nothing for NULL. + void back(void* p) { + if (NULL != p) { + Node* node = (Node*)((char*)p - offsetof(Node, spaces)); + node->next = _free_nodes; + _free_nodes = node; + } + } + + // Remove all allocated spaces. Spaces that are not back()-ed yet become + // invalid as well. + void reset() { + _free_nodes = NULL; + while (_blocks) { + Block* next = _blocks->next; + _allocator.Free(_blocks); + _blocks = next; + } + } + + // Count number of allocated/free/actively-used items. + // Notice that these functions walk through all free nodes or blocks and + // are not O(1). + size_t count_allocated() const { + size_t n = 0; + for (Block* p = _blocks; p; p = p->next) { + n += p->nalloc; + } + return n; + } + size_t count_free() const { + size_t n = 0; + for (Node* p = _free_nodes; p; p = p->next, ++n) {} + return n; + } + size_t count_active() const { + return count_allocated() - count_free(); + } + + Allocator& get_allocator() { return _allocator; } + Allocator get_allocator() const { return _allocator; } + +private: + Node* _free_nodes; + Block* _blocks; + Allocator _allocator; +}; + +} // namespace butil + +#endif // BUTIL_SINGLE_THREADED_POOL_H diff --git a/src/butil/ssl_compat.h b/src/butil/ssl_compat.h new file mode 100644 index 0000000..a42c0b4 --- /dev/null +++ b/src/butil/ssl_compat.h @@ -0,0 +1,554 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BUTIL_SSL_COMPAT_H +#define BUTIL_SSL_COMPAT_H + +#include +#include + +/* Provide functions added in newer openssl but missing in older versions or boringssl */ + +#if defined(__cplusplus) || __STDC_VERSION__ >= 199901L/*C99*/ +#define BRPC_INLINE inline +#else +#define BRPC_INLINE static +#endif + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + +#include +#include + +BRPC_INLINE void *OPENSSL_zalloc(size_t num) { + void *ret = OPENSSL_malloc(num); + + if (ret != NULL) + memset(ret, 0, num); + return ret; +} + +BRPC_INLINE int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) { + /* If the fields n and e in r are NULL, the corresponding input + * parameters MUST be non-NULL for n and e. d may be + * left NULL (in case only the public key is used). + */ + if ((r->n == NULL && n == NULL) + || (r->e == NULL && e == NULL)) + return 0; + + if (n != NULL) { + BN_free(r->n); + r->n = n; + } + if (e != NULL) { + BN_free(r->e); + r->e = e; + } + if (d != NULL) { + BN_free(r->d); + r->d = d; + } + + return 1; +} + +BRPC_INLINE int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q) { + /* If the fields p and q in r are NULL, the corresponding input + * parameters MUST be non-NULL. + */ + if ((r->p == NULL && p == NULL) + || (r->q == NULL && q == NULL)) + return 0; + + if (p != NULL) { + BN_free(r->p); + r->p = p; + } + if (q != NULL) { + BN_free(r->q); + r->q = q; + } + + return 1; +} + +BRPC_INLINE int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp) { + /* If the fields dmp1, dmq1 and iqmp in r are NULL, the corresponding input + * parameters MUST be non-NULL. + */ + if ((r->dmp1 == NULL && dmp1 == NULL) + || (r->dmq1 == NULL && dmq1 == NULL) + || (r->iqmp == NULL && iqmp == NULL)) + return 0; + + if (dmp1 != NULL) { + BN_free(r->dmp1); + r->dmp1 = dmp1; + } + if (dmq1 != NULL) { + BN_free(r->dmq1); + r->dmq1 = dmq1; + } + if (iqmp != NULL) { + BN_free(r->iqmp); + r->iqmp = iqmp; + } + + return 1; +} + +BRPC_INLINE void RSA_get0_key(const RSA *r, + const BIGNUM **n, const BIGNUM **e, const BIGNUM **d) { + if (n != NULL) + *n = r->n; + if (e != NULL) + *e = r->e; + if (d != NULL) + *d = r->d; +} + +BRPC_INLINE void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q) { + if (p != NULL) + *p = r->p; + if (q != NULL) + *q = r->q; +} + +BRPC_INLINE void RSA_get0_crt_params(const RSA *r, + const BIGNUM **dmp1, const BIGNUM **dmq1, + const BIGNUM **iqmp) { + if (dmp1 != NULL) + *dmp1 = r->dmp1; + if (dmq1 != NULL) + *dmq1 = r->dmq1; + if (iqmp != NULL) + *iqmp = r->iqmp; +} + +BRPC_INLINE void DSA_get0_pqg(const DSA *d, + const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) { + if (p != NULL) + *p = d->p; + if (q != NULL) + *q = d->q; + if (g != NULL) + *g = d->g; +} + +BRPC_INLINE int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g) { + /* If the fields p, q and g in d are NULL, the corresponding input + * parameters MUST be non-NULL. + */ + if ((d->p == NULL && p == NULL) + || (d->q == NULL && q == NULL) + || (d->g == NULL && g == NULL)) + return 0; + + if (p != NULL) { + BN_free(d->p); + d->p = p; + } + if (q != NULL) { + BN_free(d->q); + d->q = q; + } + if (g != NULL) { + BN_free(d->g); + d->g = g; + } + + return 1; +} + +BRPC_INLINE void DSA_get0_key(const DSA *d, + const BIGNUM **pub_key, const BIGNUM **priv_key) { + if (pub_key != NULL) + *pub_key = d->pub_key; + if (priv_key != NULL) + *priv_key = d->priv_key; +} + +BRPC_INLINE int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key) { + /* If the field pub_key in d is NULL, the corresponding input + * parameters MUST be non-NULL. The priv_key field may + * be left NULL. + */ + if (d->pub_key == NULL && pub_key == NULL) + return 0; + + if (pub_key != NULL) { + BN_free(d->pub_key); + d->pub_key = pub_key; + } + if (priv_key != NULL) { + BN_free(d->priv_key); + d->priv_key = priv_key; + } + + return 1; +} + +BRPC_INLINE void DSA_SIG_get0(const DSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps) { + if (pr != NULL) + *pr = sig->r; + if (ps != NULL) + *ps = sig->s; +} + +BRPC_INLINE int DSA_SIG_set0(DSA_SIG *sig, BIGNUM *r, BIGNUM *s) { + if (r == NULL || s == NULL) + return 0; + BN_clear_free(sig->r); + BN_clear_free(sig->s); + sig->r = r; + sig->s = s; + return 1; +} + +BRPC_INLINE void DH_get0_pqg(const DH *dh, + const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) { + if (p != NULL) + *p = dh->p; + if (q != NULL) + *q = dh->q; + if (g != NULL) + *g = dh->g; +} + +BRPC_INLINE int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g) { + /* If the fields p and g in d are NULL, the corresponding input + * parameters MUST be non-NULL. q may remain NULL. + */ + if ((dh->p == NULL && p == NULL) + || (dh->g == NULL && g == NULL)) + return 0; + + if (p != NULL) { + BN_free(dh->p); + dh->p = p; + } + if (q != NULL) { + BN_free(dh->q); + dh->q = q; + } + if (g != NULL) { + BN_free(dh->g); + dh->g = g; + } + + if (q != NULL) { + dh->length = BN_num_bits(q); + } + + return 1; +} + +BRPC_INLINE void DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key) { + if (pub_key != NULL) + *pub_key = dh->pub_key; + if (priv_key != NULL) + *priv_key = dh->priv_key; +} + +BRPC_INLINE int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key) { + /* If the field pub_key in dh is NULL, the corresponding input + * parameters MUST be non-NULL. The priv_key field may + * be left NULL. + */ + if (dh->pub_key == NULL && pub_key == NULL) + return 0; + + if (pub_key != NULL) { + BN_free(dh->pub_key); + dh->pub_key = pub_key; + } + if (priv_key != NULL) { + BN_free(dh->priv_key); + dh->priv_key = priv_key; + } + + return 1; +} + +BRPC_INLINE int DH_set_length(DH *dh, long length) { + dh->length = length; + return 1; +} + +BRPC_INLINE int RSA_meth_set_priv_enc(RSA_METHOD *meth, + int (*priv_enc) (int flen, const unsigned char *from, + unsigned char *to, RSA *rsa, + int padding)) { + meth->rsa_priv_enc = priv_enc; + return 1; +} + +BRPC_INLINE int RSA_meth_set_priv_dec(RSA_METHOD *meth, + int (*priv_dec) (int flen, const unsigned char *from, + unsigned char *to, RSA *rsa, + int padding)) { + meth->rsa_priv_dec = priv_dec; + return 1; +} + +BRPC_INLINE int RSA_meth_set_finish(RSA_METHOD *meth, int (*finish) (RSA *rsa)) { + meth->finish = finish; + return 1; +} + +BRPC_INLINE void RSA_meth_free(RSA_METHOD *meth) { + if (meth != NULL) { + OPENSSL_free((char *)meth->name); + OPENSSL_free(meth); + } +} + +BRPC_INLINE int RSA_bits(const RSA *r) { + return (BN_num_bits(r->n)); +} + +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ + +#if OPENSSL_VERSION_NUMBER < 0x0090801fL || defined (OPENSSL_IS_BORINGSSL) +BRPC_INLINE BIGNUM* get_rfc2409_prime_1024(BIGNUM* bn) { + static const unsigned char RFC2409_PRIME_1024[] = { + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0x0F,0xDA,0xA2, + 0x21,0x68,0xC2,0x34,0xC4,0xC6,0x62,0x8B,0x80,0xDC,0x1C,0xD1, + 0x29,0x02,0x4E,0x08,0x8A,0x67,0xCC,0x74,0x02,0x0B,0xBE,0xA6, + 0x3B,0x13,0x9B,0x22,0x51,0x4A,0x08,0x79,0x8E,0x34,0x04,0xDD, + 0xEF,0x95,0x19,0xB3,0xCD,0x3A,0x43,0x1B,0x30,0x2B,0x0A,0x6D, + 0xF2,0x5F,0x14,0x37,0x4F,0xE1,0x35,0x6D,0x6D,0x51,0xC2,0x45, + 0xE4,0x85,0xB5,0x76,0x62,0x5E,0x7E,0xC6,0xF4,0x4C,0x42,0xE9, + 0xA6,0x37,0xED,0x6B,0x0B,0xFF,0x5C,0xB6,0xF4,0x06,0xB7,0xED, + 0xEE,0x38,0x6B,0xFB,0x5A,0x89,0x9F,0xA5,0xAE,0x9F,0x24,0x11, + 0x7C,0x4B,0x1F,0xE6,0x49,0x28,0x66,0x51,0xEC,0xE6,0x53,0x81, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + }; + return BN_bin2bn(RFC2409_PRIME_1024, sizeof(RFC2409_PRIME_1024), bn); +} + +BRPC_INLINE BIGNUM* get_rfc3526_prime_2048(BIGNUM* bn) { + static const unsigned char RFC3526_PRIME_2048[] = { + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0x0F,0xDA,0xA2, + 0x21,0x68,0xC2,0x34,0xC4,0xC6,0x62,0x8B,0x80,0xDC,0x1C,0xD1, + 0x29,0x02,0x4E,0x08,0x8A,0x67,0xCC,0x74,0x02,0x0B,0xBE,0xA6, + 0x3B,0x13,0x9B,0x22,0x51,0x4A,0x08,0x79,0x8E,0x34,0x04,0xDD, + 0xEF,0x95,0x19,0xB3,0xCD,0x3A,0x43,0x1B,0x30,0x2B,0x0A,0x6D, + 0xF2,0x5F,0x14,0x37,0x4F,0xE1,0x35,0x6D,0x6D,0x51,0xC2,0x45, + 0xE4,0x85,0xB5,0x76,0x62,0x5E,0x7E,0xC6,0xF4,0x4C,0x42,0xE9, + 0xA6,0x37,0xED,0x6B,0x0B,0xFF,0x5C,0xB6,0xF4,0x06,0xB7,0xED, + 0xEE,0x38,0x6B,0xFB,0x5A,0x89,0x9F,0xA5,0xAE,0x9F,0x24,0x11, + 0x7C,0x4B,0x1F,0xE6,0x49,0x28,0x66,0x51,0xEC,0xE4,0x5B,0x3D, + 0xC2,0x00,0x7C,0xB8,0xA1,0x63,0xBF,0x05,0x98,0xDA,0x48,0x36, + 0x1C,0x55,0xD3,0x9A,0x69,0x16,0x3F,0xA8,0xFD,0x24,0xCF,0x5F, + 0x83,0x65,0x5D,0x23,0xDC,0xA3,0xAD,0x96,0x1C,0x62,0xF3,0x56, + 0x20,0x85,0x52,0xBB,0x9E,0xD5,0x29,0x07,0x70,0x96,0x96,0x6D, + 0x67,0x0C,0x35,0x4E,0x4A,0xBC,0x98,0x04,0xF1,0x74,0x6C,0x08, + 0xCA,0x18,0x21,0x7C,0x32,0x90,0x5E,0x46,0x2E,0x36,0xCE,0x3B, + 0xE3,0x9E,0x77,0x2C,0x18,0x0E,0x86,0x03,0x9B,0x27,0x83,0xA2, + 0xEC,0x07,0xA2,0x8F,0xB5,0xC5,0x5D,0xF0,0x6F,0x4C,0x52,0xC9, + 0xDE,0x2B,0xCB,0xF6,0x95,0x58,0x17,0x18,0x39,0x95,0x49,0x7C, + 0xEA,0x95,0x6A,0xE5,0x15,0xD2,0x26,0x18,0x98,0xFA,0x05,0x10, + 0x15,0x72,0x8E,0x5A,0x8A,0xAC,0xAA,0x68,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF, + }; + return BN_bin2bn(RFC3526_PRIME_2048, sizeof(RFC3526_PRIME_2048), bn); +} + +BRPC_INLINE BIGNUM* get_rfc3526_prime_4096(BIGNUM* bn) { + static const unsigned char RFC3526_PRIME_4096[] = { + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0x0F,0xDA,0xA2, + 0x21,0x68,0xC2,0x34,0xC4,0xC6,0x62,0x8B,0x80,0xDC,0x1C,0xD1, + 0x29,0x02,0x4E,0x08,0x8A,0x67,0xCC,0x74,0x02,0x0B,0xBE,0xA6, + 0x3B,0x13,0x9B,0x22,0x51,0x4A,0x08,0x79,0x8E,0x34,0x04,0xDD, + 0xEF,0x95,0x19,0xB3,0xCD,0x3A,0x43,0x1B,0x30,0x2B,0x0A,0x6D, + 0xF2,0x5F,0x14,0x37,0x4F,0xE1,0x35,0x6D,0x6D,0x51,0xC2,0x45, + 0xE4,0x85,0xB5,0x76,0x62,0x5E,0x7E,0xC6,0xF4,0x4C,0x42,0xE9, + 0xA6,0x37,0xED,0x6B,0x0B,0xFF,0x5C,0xB6,0xF4,0x06,0xB7,0xED, + 0xEE,0x38,0x6B,0xFB,0x5A,0x89,0x9F,0xA5,0xAE,0x9F,0x24,0x11, + 0x7C,0x4B,0x1F,0xE6,0x49,0x28,0x66,0x51,0xEC,0xE4,0x5B,0x3D, + 0xC2,0x00,0x7C,0xB8,0xA1,0x63,0xBF,0x05,0x98,0xDA,0x48,0x36, + 0x1C,0x55,0xD3,0x9A,0x69,0x16,0x3F,0xA8,0xFD,0x24,0xCF,0x5F, + 0x83,0x65,0x5D,0x23,0xDC,0xA3,0xAD,0x96,0x1C,0x62,0xF3,0x56, + 0x20,0x85,0x52,0xBB,0x9E,0xD5,0x29,0x07,0x70,0x96,0x96,0x6D, + 0x67,0x0C,0x35,0x4E,0x4A,0xBC,0x98,0x04,0xF1,0x74,0x6C,0x08, + 0xCA,0x18,0x21,0x7C,0x32,0x90,0x5E,0x46,0x2E,0x36,0xCE,0x3B, + 0xE3,0x9E,0x77,0x2C,0x18,0x0E,0x86,0x03,0x9B,0x27,0x83,0xA2, + 0xEC,0x07,0xA2,0x8F,0xB5,0xC5,0x5D,0xF0,0x6F,0x4C,0x52,0xC9, + 0xDE,0x2B,0xCB,0xF6,0x95,0x58,0x17,0x18,0x39,0x95,0x49,0x7C, + 0xEA,0x95,0x6A,0xE5,0x15,0xD2,0x26,0x18,0x98,0xFA,0x05,0x10, + 0x15,0x72,0x8E,0x5A,0x8A,0xAA,0xC4,0x2D,0xAD,0x33,0x17,0x0D, + 0x04,0x50,0x7A,0x33,0xA8,0x55,0x21,0xAB,0xDF,0x1C,0xBA,0x64, + 0xEC,0xFB,0x85,0x04,0x58,0xDB,0xEF,0x0A,0x8A,0xEA,0x71,0x57, + 0x5D,0x06,0x0C,0x7D,0xB3,0x97,0x0F,0x85,0xA6,0xE1,0xE4,0xC7, + 0xAB,0xF5,0xAE,0x8C,0xDB,0x09,0x33,0xD7,0x1E,0x8C,0x94,0xE0, + 0x4A,0x25,0x61,0x9D,0xCE,0xE3,0xD2,0x26,0x1A,0xD2,0xEE,0x6B, + 0xF1,0x2F,0xFA,0x06,0xD9,0x8A,0x08,0x64,0xD8,0x76,0x02,0x73, + 0x3E,0xC8,0x6A,0x64,0x52,0x1F,0x2B,0x18,0x17,0x7B,0x20,0x0C, + 0xBB,0xE1,0x17,0x57,0x7A,0x61,0x5D,0x6C,0x77,0x09,0x88,0xC0, + 0xBA,0xD9,0x46,0xE2,0x08,0xE2,0x4F,0xA0,0x74,0xE5,0xAB,0x31, + 0x43,0xDB,0x5B,0xFC,0xE0,0xFD,0x10,0x8E,0x4B,0x82,0xD1,0x20, + 0xA9,0x21,0x08,0x01,0x1A,0x72,0x3C,0x12,0xA7,0x87,0xE6,0xD7, + 0x88,0x71,0x9A,0x10,0xBD,0xBA,0x5B,0x26,0x99,0xC3,0x27,0x18, + 0x6A,0xF4,0xE2,0x3C,0x1A,0x94,0x68,0x34,0xB6,0x15,0x0B,0xDA, + 0x25,0x83,0xE9,0xCA,0x2A,0xD4,0x4C,0xE8,0xDB,0xBB,0xC2,0xDB, + 0x04,0xDE,0x8E,0xF9,0x2E,0x8E,0xFC,0x14,0x1F,0xBE,0xCA,0xA6, + 0x28,0x7C,0x59,0x47,0x4E,0x6B,0xC0,0x5D,0x99,0xB2,0x96,0x4F, + 0xA0,0x90,0xC3,0xA2,0x23,0x3B,0xA1,0x86,0x51,0x5B,0xE7,0xED, + 0x1F,0x61,0x29,0x70,0xCE,0xE2,0xD7,0xAF,0xB8,0x1B,0xDD,0x76, + 0x21,0x70,0x48,0x1C,0xD0,0x06,0x91,0x27,0xD5,0xB0,0x5A,0xA9, + 0x93,0xB4,0xEA,0x98,0x8D,0x8F,0xDD,0xC1,0x86,0xFF,0xB7,0xDC, + 0x90,0xA6,0xC0,0x8F,0x4D,0xF4,0x35,0xC9,0x34,0x06,0x31,0x99, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + }; + return BN_bin2bn(RFC3526_PRIME_4096, sizeof(RFC3526_PRIME_4096), bn); +} + + +BRPC_INLINE BIGNUM* get_rfc3526_prime_8192(BIGNUM* bn) { + static const unsigned char RFC3526_PRIME_8192[] = { + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0x0F,0xDA,0xA2, + 0x21,0x68,0xC2,0x34,0xC4,0xC6,0x62,0x8B,0x80,0xDC,0x1C,0xD1, + 0x29,0x02,0x4E,0x08,0x8A,0x67,0xCC,0x74,0x02,0x0B,0xBE,0xA6, + 0x3B,0x13,0x9B,0x22,0x51,0x4A,0x08,0x79,0x8E,0x34,0x04,0xDD, + 0xEF,0x95,0x19,0xB3,0xCD,0x3A,0x43,0x1B,0x30,0x2B,0x0A,0x6D, + 0xF2,0x5F,0x14,0x37,0x4F,0xE1,0x35,0x6D,0x6D,0x51,0xC2,0x45, + 0xE4,0x85,0xB5,0x76,0x62,0x5E,0x7E,0xC6,0xF4,0x4C,0x42,0xE9, + 0xA6,0x37,0xED,0x6B,0x0B,0xFF,0x5C,0xB6,0xF4,0x06,0xB7,0xED, + 0xEE,0x38,0x6B,0xFB,0x5A,0x89,0x9F,0xA5,0xAE,0x9F,0x24,0x11, + 0x7C,0x4B,0x1F,0xE6,0x49,0x28,0x66,0x51,0xEC,0xE4,0x5B,0x3D, + 0xC2,0x00,0x7C,0xB8,0xA1,0x63,0xBF,0x05,0x98,0xDA,0x48,0x36, + 0x1C,0x55,0xD3,0x9A,0x69,0x16,0x3F,0xA8,0xFD,0x24,0xCF,0x5F, + 0x83,0x65,0x5D,0x23,0xDC,0xA3,0xAD,0x96,0x1C,0x62,0xF3,0x56, + 0x20,0x85,0x52,0xBB,0x9E,0xD5,0x29,0x07,0x70,0x96,0x96,0x6D, + 0x67,0x0C,0x35,0x4E,0x4A,0xBC,0x98,0x04,0xF1,0x74,0x6C,0x08, + 0xCA,0x18,0x21,0x7C,0x32,0x90,0x5E,0x46,0x2E,0x36,0xCE,0x3B, + 0xE3,0x9E,0x77,0x2C,0x18,0x0E,0x86,0x03,0x9B,0x27,0x83,0xA2, + 0xEC,0x07,0xA2,0x8F,0xB5,0xC5,0x5D,0xF0,0x6F,0x4C,0x52,0xC9, + 0xDE,0x2B,0xCB,0xF6,0x95,0x58,0x17,0x18,0x39,0x95,0x49,0x7C, + 0xEA,0x95,0x6A,0xE5,0x15,0xD2,0x26,0x18,0x98,0xFA,0x05,0x10, + 0x15,0x72,0x8E,0x5A,0x8A,0xAA,0xC4,0x2D,0xAD,0x33,0x17,0x0D, + 0x04,0x50,0x7A,0x33,0xA8,0x55,0x21,0xAB,0xDF,0x1C,0xBA,0x64, + 0xEC,0xFB,0x85,0x04,0x58,0xDB,0xEF,0x0A,0x8A,0xEA,0x71,0x57, + 0x5D,0x06,0x0C,0x7D,0xB3,0x97,0x0F,0x85,0xA6,0xE1,0xE4,0xC7, + 0xAB,0xF5,0xAE,0x8C,0xDB,0x09,0x33,0xD7,0x1E,0x8C,0x94,0xE0, + 0x4A,0x25,0x61,0x9D,0xCE,0xE3,0xD2,0x26,0x1A,0xD2,0xEE,0x6B, + 0xF1,0x2F,0xFA,0x06,0xD9,0x8A,0x08,0x64,0xD8,0x76,0x02,0x73, + 0x3E,0xC8,0x6A,0x64,0x52,0x1F,0x2B,0x18,0x17,0x7B,0x20,0x0C, + 0xBB,0xE1,0x17,0x57,0x7A,0x61,0x5D,0x6C,0x77,0x09,0x88,0xC0, + 0xBA,0xD9,0x46,0xE2,0x08,0xE2,0x4F,0xA0,0x74,0xE5,0xAB,0x31, + 0x43,0xDB,0x5B,0xFC,0xE0,0xFD,0x10,0x8E,0x4B,0x82,0xD1,0x20, + 0xA9,0x21,0x08,0x01,0x1A,0x72,0x3C,0x12,0xA7,0x87,0xE6,0xD7, + 0x88,0x71,0x9A,0x10,0xBD,0xBA,0x5B,0x26,0x99,0xC3,0x27,0x18, + 0x6A,0xF4,0xE2,0x3C,0x1A,0x94,0x68,0x34,0xB6,0x15,0x0B,0xDA, + 0x25,0x83,0xE9,0xCA,0x2A,0xD4,0x4C,0xE8,0xDB,0xBB,0xC2,0xDB, + 0x04,0xDE,0x8E,0xF9,0x2E,0x8E,0xFC,0x14,0x1F,0xBE,0xCA,0xA6, + 0x28,0x7C,0x59,0x47,0x4E,0x6B,0xC0,0x5D,0x99,0xB2,0x96,0x4F, + 0xA0,0x90,0xC3,0xA2,0x23,0x3B,0xA1,0x86,0x51,0x5B,0xE7,0xED, + 0x1F,0x61,0x29,0x70,0xCE,0xE2,0xD7,0xAF,0xB8,0x1B,0xDD,0x76, + 0x21,0x70,0x48,0x1C,0xD0,0x06,0x91,0x27,0xD5,0xB0,0x5A,0xA9, + 0x93,0xB4,0xEA,0x98,0x8D,0x8F,0xDD,0xC1,0x86,0xFF,0xB7,0xDC, + 0x90,0xA6,0xC0,0x8F,0x4D,0xF4,0x35,0xC9,0x34,0x02,0x84,0x92, + 0x36,0xC3,0xFA,0xB4,0xD2,0x7C,0x70,0x26,0xC1,0xD4,0xDC,0xB2, + 0x60,0x26,0x46,0xDE,0xC9,0x75,0x1E,0x76,0x3D,0xBA,0x37,0xBD, + 0xF8,0xFF,0x94,0x06,0xAD,0x9E,0x53,0x0E,0xE5,0xDB,0x38,0x2F, + 0x41,0x30,0x01,0xAE,0xB0,0x6A,0x53,0xED,0x90,0x27,0xD8,0x31, + 0x17,0x97,0x27,0xB0,0x86,0x5A,0x89,0x18,0xDA,0x3E,0xDB,0xEB, + 0xCF,0x9B,0x14,0xED,0x44,0xCE,0x6C,0xBA,0xCE,0xD4,0xBB,0x1B, + 0xDB,0x7F,0x14,0x47,0xE6,0xCC,0x25,0x4B,0x33,0x20,0x51,0x51, + 0x2B,0xD7,0xAF,0x42,0x6F,0xB8,0xF4,0x01,0x37,0x8C,0xD2,0xBF, + 0x59,0x83,0xCA,0x01,0xC6,0x4B,0x92,0xEC,0xF0,0x32,0xEA,0x15, + 0xD1,0x72,0x1D,0x03,0xF4,0x82,0xD7,0xCE,0x6E,0x74,0xFE,0xF6, + 0xD5,0x5E,0x70,0x2F,0x46,0x98,0x0C,0x82,0xB5,0xA8,0x40,0x31, + 0x90,0x0B,0x1C,0x9E,0x59,0xE7,0xC9,0x7F,0xBE,0xC7,0xE8,0xF3, + 0x23,0xA9,0x7A,0x7E,0x36,0xCC,0x88,0xBE,0x0F,0x1D,0x45,0xB7, + 0xFF,0x58,0x5A,0xC5,0x4B,0xD4,0x07,0xB2,0x2B,0x41,0x54,0xAA, + 0xCC,0x8F,0x6D,0x7E,0xBF,0x48,0xE1,0xD8,0x14,0xCC,0x5E,0xD2, + 0x0F,0x80,0x37,0xE0,0xA7,0x97,0x15,0xEE,0xF2,0x9B,0xE3,0x28, + 0x06,0xA1,0xD5,0x8B,0xB7,0xC5,0xDA,0x76,0xF5,0x50,0xAA,0x3D, + 0x8A,0x1F,0xBF,0xF0,0xEB,0x19,0xCC,0xB1,0xA3,0x13,0xD5,0x5C, + 0xDA,0x56,0xC9,0xEC,0x2E,0xF2,0x96,0x32,0x38,0x7F,0xE8,0xD7, + 0x6E,0x3C,0x04,0x68,0x04,0x3E,0x8F,0x66,0x3F,0x48,0x60,0xEE, + 0x12,0xBF,0x2D,0x5B,0x0B,0x74,0x74,0xD6,0xE6,0x94,0xF9,0x1E, + 0x6D,0xBE,0x11,0x59,0x74,0xA3,0x92,0x6F,0x12,0xFE,0xE5,0xE4, + 0x38,0x77,0x7C,0xB6,0xA9,0x32,0xDF,0x8C,0xD8,0xBE,0xC4,0xD0, + 0x73,0xB9,0x31,0xBA,0x3B,0xC8,0x32,0xB6,0x8D,0x9D,0xD3,0x00, + 0x74,0x1F,0xA7,0xBF,0x8A,0xFC,0x47,0xED,0x25,0x76,0xF6,0x93, + 0x6B,0xA4,0x24,0x66,0x3A,0xAB,0x63,0x9C,0x5A,0xE4,0xF5,0x68, + 0x34,0x23,0xB4,0x74,0x2B,0xF1,0xC9,0x78,0x23,0x8F,0x16,0xCB, + 0xE3,0x9D,0x65,0x2D,0xE3,0xFD,0xB8,0xBE,0xFC,0x84,0x8A,0xD9, + 0x22,0x22,0x2E,0x04,0xA4,0x03,0x7C,0x07,0x13,0xEB,0x57,0xA8, + 0x1A,0x23,0xF0,0xC7,0x34,0x73,0xFC,0x64,0x6C,0xEA,0x30,0x6B, + 0x4B,0xCB,0xC8,0x86,0x2F,0x83,0x85,0xDD,0xFA,0x9D,0x4B,0x7F, + 0xA2,0xC0,0x87,0xE8,0x79,0x68,0x33,0x03,0xED,0x5B,0xDD,0x3A, + 0x06,0x2B,0x3C,0xF5,0xB3,0xA2,0x78,0xA6,0x6D,0x2A,0x13,0xF8, + 0x3F,0x44,0xF8,0x2D,0xDF,0x31,0x0E,0xE0,0x74,0xAB,0x6A,0x36, + 0x45,0x97,0xE8,0x99,0xA0,0x25,0x5D,0xC1,0x64,0xF3,0x1C,0xC5, + 0x08,0x46,0x85,0x1D,0xF9,0xAB,0x48,0x19,0x5D,0xED,0x7E,0xA1, + 0xB1,0xD5,0x10,0xBD,0x7E,0xE7,0x4D,0x73,0xFA,0xF3,0x6B,0xC3, + 0x1E,0xCF,0xA2,0x68,0x35,0x90,0x46,0xF4,0xEB,0x87,0x9F,0x92, + 0x40,0x09,0x43,0x8B,0x48,0x1C,0x6C,0xD7,0x88,0x9A,0x00,0x2E, + 0xD5,0xEE,0x38,0x2B,0xC9,0x19,0x0D,0xA6,0xFC,0x02,0x6E,0x47, + 0x95,0x58,0xE4,0x47,0x56,0x77,0xE9,0xAA,0x9E,0x30,0x50,0xE2, + 0x76,0x56,0x94,0xDF,0xC8,0x1F,0x56,0xE8,0x80,0xB9,0x6E,0x71, + 0x60,0xC9,0x80,0xDD,0x98,0xED,0xD3,0xDF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF, + }; + return BN_bin2bn(RFC3526_PRIME_8192, sizeof(RFC3526_PRIME_8192), bn); +} + +BRPC_INLINE int EVP_PKEY_base_id(const EVP_PKEY *pkey) { + return EVP_PKEY_type(pkey->type); +} + +#endif /* OPENSSL_VERSION_NUMBER < 0x0090801fL || OPENSSL_IS_BORINGSSL */ + +#if defined(OPENSSL_IS_BORINGSSL) +BRPC_INLINE int BIO_fd_non_fatal_error(int err) { + if ( +#ifdef EWOULDBLOCK + err == EWOULDBLOCK || +#endif +#ifdef WSAEWOULDBLOCK + err == WSAEWOULDBLOCK || +#endif +#ifdef ENOTCONN + err == ENOTCONN || +#endif +#ifdef EINTR + err == EINTR || +#endif +#ifdef EAGAIN + err == EAGAIN || +#endif +#ifdef EPROTO + err == EPROTO || +#endif +#ifdef EINPROGRESS + err == EINPROGRESS || +#endif +#ifdef EALREADY + err == EALREADY || +#endif + 0) { + return 1; + } + return 0; +} +#endif /*OPENSSL_IS_BORINGSSL*/ +#endif /* BUTIL_SSL_COMPAT_H */ diff --git a/src/butil/status.cpp b/src/butil/status.cpp new file mode 100644 index 0000000..bb2343e --- /dev/null +++ b/src/butil/status.cpp @@ -0,0 +1,137 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Mon Feb 9 15:04:03 CST 2015 + +#include +#include "butil/status.h" + +namespace butil { + +inline size_t status_size(size_t message_size) { + // Add 1 because even if the sum of size is aligned with int, we need to + // put an ending '\0' + return ((offsetof(Status::State, message) + message_size) + / sizeof(int) + 1) * sizeof(int); +} + +int Status::set_errorv(int c, const char* fmt, va_list args) { + if (0 == c) { + free(_state); + _state = NULL; + return 0; + } + State* new_state = NULL; + State* state = NULL; + if (_state != NULL) { + state = _state; + } else { + const size_t guess_size = std::max(strlen(fmt) * 2, 32UL); + const size_t st_size = status_size(guess_size); + new_state = reinterpret_cast(malloc(st_size)); + if (NULL == new_state) { + return -1; + } + new_state->state_size = st_size; + state = new_state; + } + const size_t cap = state->state_size - offsetof(State, message); + va_list copied_args; + va_copy(copied_args, args); + const int bytes_used = vsnprintf(state->message, cap, fmt, copied_args); + va_end(copied_args); + if (bytes_used < 0) { + free(new_state); + return -1; + } else if ((size_t)bytes_used < cap) { + // There was enough room, just shrink and return. + state->code = c; + state->size = bytes_used; + if (new_state == state) { + _state = new_state; + } + return 0; + } else { + free(new_state); + const size_t st_size = status_size(bytes_used); + new_state = reinterpret_cast(malloc(st_size)); + if (NULL == new_state) { + return -1; + } + new_state->code = c; + new_state->size = bytes_used; + new_state->state_size = st_size; + const int bytes_used2 = + vsnprintf(new_state->message, bytes_used + 1, fmt, args); + if (bytes_used2 != bytes_used) { + free(new_state); + return -1; + } + free(_state); + _state = new_state; + return 0; + } +} + +int Status::set_error(int c, const butil::StringPiece& error_msg) { + if (0 == c) { + free(_state); + _state = NULL; + return 0; + } + const size_t st_size = status_size(error_msg.size()); + if (_state == NULL || _state->state_size < st_size) { + State* new_state = reinterpret_cast(malloc(st_size)); + if (NULL == new_state) { + return -1; + } + new_state->state_size = st_size; + free(_state); + _state = new_state; + } + _state->code = c; + _state->size = error_msg.size(); + memcpy(_state->message, error_msg.data(), error_msg.size()); + _state->message[error_msg.size()] = '\0'; + return 0; +} + +Status::State* Status::copy_state(const State* s) { + const size_t n = status_size(s->size); + State* s2 = reinterpret_cast(malloc(n)); + if (NULL == s2) { + // TODO: If we failed to allocate, the status will be OK. + return NULL; + } + s2->code = s->code; + s2->size = s->size; + s2->state_size = n; + char* msg_head = s2->message; + memcpy(msg_head, s->message, s->size); + msg_head[s->size] = '\0'; + return s2; +}; + +std::string Status::error_str() const { + if (_state == NULL) { + static std::string s_ok_str = "OK"; + return s_ok_str; + } + return std::string(_state->message, _state->size); +} + +} // namespace butil diff --git a/src/butil/status.h b/src/butil/status.h new file mode 100644 index 0000000..ce78169 --- /dev/null +++ b/src/butil/status.h @@ -0,0 +1,157 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BUTIL_STATUS_H +#define BUTIL_STATUS_H + +#include // va_list +#include // free +#include // std::string +#include // std::ostream +#include "butil/strings/string_piece.h" + +namespace butil { + +// A Status encapsulates the result of an operation. It may indicate success, +// or it may indicate an error with an associated error message. It's suitable +// for passing status of functions with richer information than just error_code +// in exception-forbidden code. This utility is inspired by leveldb::Status. +// +// Multiple threads can invoke const methods on a Status without +// external synchronization, but if any of the threads may call a +// non-const method, all threads accessing the same Status must use +// external synchronization. +// +// Since failed status needs to allocate memory, you should be careful when +// failed status is frequent. + +class Status { +public: + struct State { + int code; + unsigned size; // length of message string + unsigned state_size; + char message[0]; + }; + + // Create a success status. + Status() : _state(NULL) { } + // Return a success status. + static Status OK() { return Status(); } + + ~Status() { reset(); } + + // Create a failed status. + // error_text is formatted from `fmt' and following arguments. + Status(int code, const char* fmt, ...) + __attribute__ ((__format__ (__printf__, 3, 4))) + : _state(NULL) { + va_list ap; + va_start(ap, fmt); + set_errorv(code, fmt, ap); + va_end(ap); + } + Status(int code, const butil::StringPiece& error_msg) : _state(NULL) { + set_error(code, error_msg); + } + + // Copy the specified status. Internal fields are deeply copied. + Status(const Status& s); + void operator=(const Status& s); + + // Reset this status to be OK. + void reset(); + + // Reset this status to be failed. + // Returns 0 on success, -1 otherwise and internal fields are not changed. + int set_error(int code, const char* error_format, ...) + __attribute__ ((__format__ (__printf__, 3, 4))); + int set_error(int code, const butil::StringPiece& error_msg); + int set_errorv(int code, const char* error_format, va_list args); + + // Returns true iff the status indicates success. + bool ok() const { return (_state == NULL); } + + // Get the error code + int error_code() const { + return (_state == NULL) ? 0 : _state->code; + } + + // Return a string representation of the status. + // Returns "OK" for success. + // NOTICE: + // * You can print a Status to std::ostream directly + // * if message contains '\0', error_cstr() will not be shown fully. + const char* error_cstr() const { + return (_state == NULL ? "OK" : _state->message); + } + butil::StringPiece error_data() const { + return (_state == NULL ? butil::StringPiece("OK", 2) + : butil::StringPiece(_state->message, _state->size)); + } + std::string error_str() const; + + void swap(butil::Status& other) { std::swap(_state, other._state); } + +private: + // OK status has a NULL _state. Otherwise, _state is a State object + // converted from malloc(). + State* _state; + + static State* copy_state(const State* s); +}; + +inline Status::Status(const Status& s) { + _state = (s._state == NULL) ? NULL : copy_state(s._state); +} + +inline int Status::set_error(int code, const char* msg, ...) { + va_list ap; + va_start(ap, msg); + const int rc = set_errorv(code, msg, ap); + va_end(ap); + return rc; +} + +inline void Status::reset() { + free(_state); + _state = NULL; +} + +inline void Status::operator=(const Status& s) { + // The following condition catches both aliasing (when this == &s), + // and the common case where both s and *this are ok. + if (_state == s._state) { + return; + } + if (s._state == NULL) { + free(_state); + _state = NULL; + } else { + set_error(s._state->code, + butil::StringPiece(s._state->message, s._state->size)); + } +} + +inline std::ostream& operator<<(std::ostream& os, const Status& st) { + // NOTE: don't use st.error_text() which is inaccurate if message has '\0' + return os << st.error_data(); +} + +} // namespace butil + +#endif // BUTIL_STATUS_H diff --git a/src/butil/stl_util.h b/src/butil/stl_util.h new file mode 100644 index 0000000..1cc15d9 --- /dev/null +++ b/src/butil/stl_util.h @@ -0,0 +1,260 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Derived from google3/util/gtl/stl_util.h + +#ifndef BUTIL_STL_UTIL_H_ +#define BUTIL_STL_UTIL_H_ + +#include +#include +#include +#include +#include + +#include "butil/logging.h" + +namespace butil { + +// Clears internal memory of an STL object. +// STL clear()/reserve(0) does not always free internal memory allocated +// This function uses swap/destructor to ensure the internal memory is freed. +template +void STLClearObject(T* obj) { + T tmp; + tmp.swap(*obj); + // Sometimes "T tmp" allocates objects with memory (arena implementation?). + // Hence using additional reserve(0) even if it doesn't always work. + obj->reserve(0); +} + +// For a range within a container of pointers, calls delete (non-array version) +// on these pointers. +// NOTE: for these three functions, we could just implement a DeleteObject +// functor and then call for_each() on the range and functor, but this +// requires us to pull in all of algorithm.h, which seems expensive. +// For hash_[multi]set, it is important that this deletes behind the iterator +// because the hash_set may call the hash function on the iterator when it is +// advanced, which could result in the hash function trying to deference a +// stale pointer. +template +void STLDeleteContainerPointers(ForwardIterator begin, ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete *temp; + } +} + +// For a range within a container of pairs, calls delete (non-array version) on +// BOTH items in the pairs. +// NOTE: Like STLDeleteContainerPointers, it is important that this deletes +// behind the iterator because if both the key and value are deleted, the +// container may call the hash function on the iterator when it is advanced, +// which could result in the hash function trying to dereference a stale +// pointer. +template +void STLDeleteContainerPairPointers(ForwardIterator begin, + ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete temp->first; + delete temp->second; + } +} + +// For a range within a container of pairs, calls delete (non-array version) on +// the FIRST item in the pairs. +// NOTE: Like STLDeleteContainerPointers, deleting behind the iterator. +template +void STLDeleteContainerPairFirstPointers(ForwardIterator begin, + ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete temp->first; + } +} + +// For a range within a container of pairs, calls delete. +// NOTE: Like STLDeleteContainerPointers, deleting behind the iterator. +// Deleting the value does not always invalidate the iterator, but it may +// do so if the key is a pointer into the value object. +template +void STLDeleteContainerPairSecondPointers(ForwardIterator begin, + ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete temp->second; + } +} + +// To treat a possibly-empty vector as an array, use these functions. +// If you know the array will never be empty, you can use &*v.begin() +// directly, but that is undefined behaviour if |v| is empty. +template +inline T* vector_as_array(std::vector* v) { + return v->empty() ? NULL : &*v->begin(); +} + +template +inline const T* vector_as_array(const std::vector* v) { + return v->empty() ? NULL : &*v->begin(); +} + +// Return a mutable char* pointing to a string's internal buffer, +// which may not be null-terminated. Writing through this pointer will +// modify the string. +// +// string_as_array(&str)[i] is valid for 0 <= i < str.size() until the +// next call to a string method that invalidates iterators. +// +// As of 2006-04, there is no standard-blessed way of getting a +// mutable reference to a string's internal buffer. However, issue 530 +// (http://www.open-std.org/JTC1/SC22/WG21/docs/lwg-active.html#530) +// proposes this as the method. According to Matt Austern, this should +// already work on all current implementations. +inline char* string_as_array(std::string* str) { + // DO NOT USE const_cast(str->data()) + return str->empty() ? NULL : &*str->begin(); +} + +// The following functions are useful for cleaning up STL containers whose +// elements point to allocated memory. + +// STLDeleteElements() deletes all the elements in an STL container and clears +// the container. This function is suitable for use with a vector, set, +// hash_set, or any other STL container which defines sensible begin(), end(), +// and clear() methods. +// +// If container is NULL, this function is a no-op. +// +// As an alternative to calling STLDeleteElements() directly, consider +// STLElementDeleter (defined below), which ensures that your container's +// elements are deleted when the STLElementDeleter goes out of scope. +template +void STLDeleteElements(T* container) { + if (!container) + return; + STLDeleteContainerPointers(container->begin(), container->end()); + container->clear(); +} + +// Given an STL container consisting of (key, value) pairs, STLDeleteValues +// deletes all the "value" components and clears the container. Does nothing +// in the case it's given a NULL pointer. +template +void STLDeleteValues(T* container) { + if (!container) + return; + for (typename T::iterator i(container->begin()); i != container->end(); ++i) + delete i->second; + container->clear(); +} + + +// The following classes provide a convenient way to delete all elements or +// values from STL containers when they goes out of scope. This greatly +// simplifies code that creates temporary objects and has multiple return +// statements. Example: +// +// vector tmp_proto; +// STLElementDeleter > d(&tmp_proto); +// if (...) return false; +// ... +// return success; + +// Given a pointer to an STL container this class will delete all the element +// pointers when it goes out of scope. +template +class STLElementDeleter { + public: + STLElementDeleter(T* container) : container_(container) {} + ~STLElementDeleter() { STLDeleteElements(container_); } + + private: + T* container_; +}; + +// Given a pointer to an STL container this class will delete all the value +// pointers when it goes out of scope. +template +class STLValueDeleter { + public: + STLValueDeleter(T* container) : container_(container) {} + ~STLValueDeleter() { STLDeleteValues(container_); } + + private: + T* container_; +}; + +// Test to see if a set, map, hash_set or hash_map contains a particular key. +// Returns true if the key is in the collection. +template +bool ContainsKey(const Collection& collection, const Key& key) { + return collection.find(key) != collection.end(); +} + +// Returns true if the container is sorted. +template +bool STLIsSorted(const Container& cont) { + // Note: Use reverse iterator on container to ensure we only require + // value_type to implement operator<. + return std::adjacent_find(cont.rbegin(), cont.rend(), + std::less()) + == cont.rend(); +} + +// Returns a new ResultType containing the difference of two sorted containers. +template +ResultType STLSetDifference(const Arg1& a1, const Arg2& a2) { + DCHECK(STLIsSorted(a1)); + DCHECK(STLIsSorted(a2)); + ResultType difference; + std::set_difference(a1.begin(), a1.end(), + a2.begin(), a2.end(), + std::inserter(difference, difference.end())); + return difference; +} + +// Returns a new ResultType containing the union of two sorted containers. +template +ResultType STLSetUnion(const Arg1& a1, const Arg2& a2) { + DCHECK(STLIsSorted(a1)); + DCHECK(STLIsSorted(a2)); + ResultType result; + std::set_union(a1.begin(), a1.end(), + a2.begin(), a2.end(), + std::inserter(result, result.end())); + return result; +} + +// Returns a new ResultType containing the intersection of two sorted +// containers. +template +ResultType STLSetIntersection(const Arg1& a1, const Arg2& a2) { + DCHECK(STLIsSorted(a1)); + DCHECK(STLIsSorted(a2)); + ResultType result; + std::set_intersection(a1.begin(), a1.end(), + a2.begin(), a2.end(), + std::inserter(result, result.end())); + return result; +} + +// Returns true if the sorted container |a1| contains all elements of the sorted +// container |a2|. +template +bool STLIncludes(const Arg1& a1, const Arg2& a2) { + DCHECK(STLIsSorted(a1)); + DCHECK(STLIsSorted(a2)); + return std::includes(a1.begin(), a1.end(), + a2.begin(), a2.end()); +} + +} // namespace butil + +#endif // BUTIL_STL_UTIL_H_ diff --git a/src/butil/string_printf.cpp b/src/butil/string_printf.cpp new file mode 100644 index 0000000..cebaf13 --- /dev/null +++ b/src/butil/string_printf.cpp @@ -0,0 +1,155 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // vsnprintf +#include // strlen +#include "butil/string_printf.h" + +namespace butil { + +// Copyright 2012 Facebook, Inc. +// +// 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. + +namespace { +inline int string_printf_impl(std::string& output, const char* format, + va_list args) { + // Tru to the space at the end of output for our output buffer. + // Find out write point then inflate its size temporarily to its + // capacity; we will later shrink it to the size needed to represent + // the formatted string. If this buffer isn't large enough, we do a + // resize and try again. + + const int write_point = output.size(); + int remaining = output.capacity() - write_point; + output.resize(output.capacity()); + + va_list copied_args; + va_copy(copied_args, args); + int bytes_used = vsnprintf(&output[write_point], remaining, format, + copied_args); + va_end(copied_args); + if (bytes_used < 0) { + return -1; + } else if (bytes_used < remaining) { + // There was enough room, just shrink and return. + output.resize(write_point + bytes_used); + } else { + output.resize(write_point + bytes_used + 1); + remaining = bytes_used + 1; + bytes_used = vsnprintf(&output[write_point], remaining, format, args); + if (bytes_used + 1 != remaining) { + return -1; + } + output.resize(write_point + bytes_used); + } + return 0; +} + +inline int string_printf_impl(std::string& output, size_t hint, + const char* format, va_list args) { + if (hint > output.capacity()) { + output.reserve(hint); + } + return string_printf_impl(output, format, args); +} +} // end anonymous namespace + +std::string string_printf(const char* format, ...) { + // snprintf will tell us how large the output buffer should be, but + // we then have to call it a second time, which is costly. By + // guestimating the final size, we avoid the double snprintf in many + // cases, resulting in a performance win. We use this constructor + // of std::string to avoid a double allocation, though it does pad + // the resulting string with nul bytes. Our guestimation is twice + // the format string size, or 32 bytes, whichever is larger. This + // is a heuristic that doesn't affect correctness but attempts to be + // reasonably fast for the most common cases. + std::string ret; + va_list ap; + va_start(ap, format); + if (string_printf_impl(ret, std::max(32UL, strlen(format) * 2), + format, ap) != 0) { + ret.clear(); + } + va_end(ap); + return ret; +} + +std::string string_printf(size_t hint_size, const char* format, ...) { + // snprintf will tell us how large the output buffer should be, but + // we then have to call it a second time, which is costly. By + // passing the hint size of formatted string, we avoid the double + // snprintf in many cases, resulting in a performance win. We use + // this constructor of std::string to avoid a double allocation, + // though it does pad the resulting string with nul bytes. + std::string ret; + va_list ap; + va_start(ap, format); + if (string_printf_impl(ret, std::max(hint_size, strlen(format) * 2), + format, ap) != 0) { + ret.clear(); + } + va_end(ap); + return ret; +} + +// Basic declarations; allow for parameters of strings and string +// pieces to be specified. +int string_appendf(std::string* output, const char* format, ...) { + va_list ap; + va_start(ap, format); + const int rc = string_vappendf(output, format, ap); + va_end(ap); + return rc; +} + +int string_vappendf(std::string* output, const char* format, va_list args) { + const size_t old_size = output->size(); + const int rc = string_printf_impl(*output, format, args); + if (rc != 0) { + output->resize(old_size); + } + return rc; +} + +int string_printf(std::string* output, const char* format, ...) { + va_list ap; + va_start(ap, format); + const int rc = string_vprintf(output, format, ap); + va_end(ap); + return rc; +}; + +int string_vprintf(std::string* output, const char* format, va_list args) { + output->clear(); + const int rc = string_printf_impl(*output, format, args); + if (rc != 0) { + output->clear(); + } + return rc; +}; + +} // namespace butil diff --git a/src/butil/string_printf.h b/src/butil/string_printf.h new file mode 100644 index 0000000..28dc420 --- /dev/null +++ b/src/butil/string_printf.h @@ -0,0 +1,54 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BUTIL_STRING_PRINTF_H +#define BUTIL_STRING_PRINTF_H + +#include // std::string +#include // va_list + +namespace butil { + +// Convert |format| and associated arguments to std::string +std::string string_printf(const char* format, ...) + __attribute__ ((format (printf, 1, 2))); + +// Hint size of formatted string. +std::string string_printf(size_t hint_size, const char* format, ...) + __attribute__ ((format (printf, 2, 3))); + +// Write |format| and associated arguments into |output| +// Returns 0 on success, -1 otherwise. +int string_printf(std::string* output, const char* format, ...) + __attribute__ ((format (printf, 2, 3))); + +// Write |format| and associated arguments in form of va_list into |output|. +// Returns 0 on success, -1 otherwise. +int string_vprintf(std::string* output, const char* format, va_list args); + +// Append |format| and associated arguments to |output| +// Returns 0 on success, -1 otherwise. +int string_appendf(std::string* output, const char* format, ...) + __attribute__ ((format (printf, 2, 3))); + +// Append |format| and associated arguments in form of va_list to |output|. +// Returns 0 on success, -1 otherwise. +int string_vappendf(std::string* output, const char* format, va_list args); + +} // namespace butil + +#endif // BUTIL_STRING_PRINTF_H diff --git a/src/butil/string_splitter.h b/src/butil/string_splitter.h new file mode 100644 index 0000000..b485c2a --- /dev/null +++ b/src/butil/string_splitter.h @@ -0,0 +1,245 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Mon. Apr. 18 19:52:34 CST 2011 + +// Iteratively split a string by one or multiple separators. + +#ifndef BUTIL_STRING_SPLITTER_H +#define BUTIL_STRING_SPLITTER_H + +#include +#include +#include "butil/strings/string_piece.h" + +// It's common to encode data into strings separated by special characters +// and decode them back, but functions such as `split_string' has to modify +// the input string, which is bad. If we parse the string from scratch, the +// code will be filled with pointer operations and obscure to understand. +// +// What we want is: +// - Scan the string once: just do simple things efficiently. +// - Do not modify input string: Changing input is bad, it may bring hidden +// bugs, concurrency issues and non-const propagations. +// - Split the string in-place without additional buffer/array. +// +// StringSplitter does meet these requirements. +// Usage: +// const char* the_string_to_split = ...; +// for (StringSplitter s(the_string_to_split, '\t'); s; ++s) { +// printf("%*s\n", s.length(), s.field()); +// } +// +// "s" behaves as an iterator and evaluates to true before ending. +// "s.field()" and "s.length()" are address and length of current field +// respectively. Notice that "s.field()" may not end with '\0' because +// we don't modify input. You can copy the field to a dedicated buffer +// or apply a function supporting length. + +namespace butil { + +enum EmptyFieldAction { + SKIP_EMPTY_FIELD, + ALLOW_EMPTY_FIELD +}; + +// Split a string with one character +class StringSplitter { +public: + // Split `input' with `separator'. If `action' is SKIP_EMPTY_FIELD, zero- + // length() field() will be skipped. + inline StringSplitter(const char* input, char separator, + EmptyFieldAction action = SKIP_EMPTY_FIELD); + // Allows containing embedded '\0' characters and separator can be '\0', + // if str_end is not NULL. + inline StringSplitter(const char* str_begin, const char* str_end, + char separator, + EmptyFieldAction action = SKIP_EMPTY_FIELD); + // Allows containing embedded '\0' characters and separator can be '\0', + inline StringSplitter(const StringPiece& input, char separator, + EmptyFieldAction action = SKIP_EMPTY_FIELD); + + // Move splitter forward. + inline StringSplitter& operator++(); + inline StringSplitter operator++(int); + + // True iff field() is valid. + inline operator const void*() const; + + // Beginning address and length of the field. *(field() + length()) may + // not be '\0' because we don't modify `input'. + inline const char* field() const; + inline size_t length() const; + inline StringPiece field_sp() const; + + // Cast field to specific type, and write the value into `pv'. + // Returns 0 on success, -1 otherwise. + // NOTE: If separator is a digit, casting functions always return -1. + inline int to_int8(int8_t *pv) const; + inline int to_uint8(uint8_t *pv) const; + inline int to_int(int *pv) const; + inline int to_uint(unsigned int *pv) const; + inline int to_long(long *pv) const; + inline int to_ulong(unsigned long *pv) const; + inline int to_longlong(long long *pv) const; + inline int to_ulonglong(unsigned long long *pv) const; + inline int to_float(float *pv) const; + inline int to_double(double *pv) const; + +private: + inline bool not_end(const char* p) const; + inline void init(); + + const char* _head; + const char* _tail; + const char* _str_tail; + const char _sep; + const EmptyFieldAction _empty_field_action; +}; + +// Split a string with one of the separators +class StringMultiSplitter { +public: + // Split `input' with one character of `separators'. If `action' is + // SKIP_EMPTY_FIELD, zero-length() field() will be skipped. + // NOTE: This utility stores pointer of `separators' directly rather than + // copying the content because this utility is intended to be used + // in ad-hoc manner where lifetime of `separators' is generally + // longer than this utility. + inline StringMultiSplitter(const char* input, const char* separators, + EmptyFieldAction action = SKIP_EMPTY_FIELD); + // Allows containing embedded '\0' characters if str_end is not NULL. + // NOTE: `separators` cannot contain embedded '\0' character. + inline StringMultiSplitter(const char* str_begin, const char* str_end, + const char* separators, + EmptyFieldAction action = SKIP_EMPTY_FIELD); + + // Move splitter forward. + inline StringMultiSplitter& operator++(); + inline StringMultiSplitter operator++(int); + + // True iff field() is valid. + inline operator const void*() const; + + // Beginning address and length of the field. *(field() + length()) may + // not be '\0' because we don't modify `input'. + inline const char* field() const; + inline size_t length() const; + inline StringPiece field_sp() const; + + // Cast field to specific type, and write the value into `pv'. + // Returns 0 on success, -1 otherwise. + // NOTE: If separators contains digit, casting functions always return -1. + inline int to_int8(int8_t *pv) const; + inline int to_uint8(uint8_t *pv) const; + inline int to_int(int *pv) const; + inline int to_uint(unsigned int *pv) const; + inline int to_long(long *pv) const; + inline int to_ulong(unsigned long *pv) const; + inline int to_longlong(long long *pv) const; + inline int to_ulonglong(unsigned long long *pv) const; + inline int to_float(float *pv) const; + inline int to_double(double *pv) const; + +private: + inline bool is_sep(char c) const; + inline bool not_end(const char* p) const; + inline void init(); + + const char* _head; + const char* _tail; + const char* _str_tail; + const char* const _seps; + const EmptyFieldAction _empty_field_action; +}; + +// Split query in the format according to the given delimiters. +// This class can also handle some exceptional cases. +// 1. consecutive pair_delimiter are omitted, for example, +// suppose key_value_delimiter is '=' and pair_delimiter +// is '&', then 'k1=v1&&&k2=v2' is normalized to 'k1=k2&k2=v2'. +// 2. key or value can be empty or both can be empty. +// 3. consecutive key_value_delimiter are not omitted, for example, +// suppose input is 'k1===v2' and key_value_delimiter is '=', then +// key() returns 'k1', value() returns '==v2'. +class KeyValuePairsSplitter { +public: + inline KeyValuePairsSplitter(const char* str_begin, + const char* str_end, + char pair_delimiter, + char key_value_delimiter) + : _sp(str_begin, str_end, pair_delimiter) + , _delim_pos(StringPiece::npos) + , _key_value_delim(key_value_delimiter) { + UpdateDelimiterPosition(); + } + + inline KeyValuePairsSplitter(const char* str_begin, + char pair_delimiter, + char key_value_delimiter) + : KeyValuePairsSplitter(str_begin, NULL, + pair_delimiter, key_value_delimiter) {} + + inline KeyValuePairsSplitter(const StringPiece &sp, + char pair_delimiter, + char key_value_delimiter) + : KeyValuePairsSplitter(sp.begin(), sp.end(), + pair_delimiter, key_value_delimiter) {} + + inline StringPiece key() { + return key_and_value().substr(0, _delim_pos); + } + + inline StringPiece value() { + return key_and_value().substr(_delim_pos + 1); + } + + // Get the current value of key and value + // in the format of "key=value" + inline StringPiece key_and_value() { + return StringPiece(_sp.field(), _sp.length()); + } + + // Move splitter forward. + inline KeyValuePairsSplitter& operator++() { + ++_sp; + UpdateDelimiterPosition(); + return *this; + } + + inline KeyValuePairsSplitter operator++(int) { + KeyValuePairsSplitter tmp = *this; + operator++(); + return tmp; + } + + inline operator const void*() const { return _sp; } + +private: + inline void UpdateDelimiterPosition(); + +private: + StringSplitter _sp; + StringPiece::size_type _delim_pos; + const char _key_value_delim; +}; + +} // namespace butil + +#include "butil/string_splitter_inl.h" + +#endif // BUTIL_STRING_SPLITTER_H diff --git a/src/butil/string_splitter_inl.h b/src/butil/string_splitter_inl.h new file mode 100644 index 0000000..ae035f1 --- /dev/null +++ b/src/butil/string_splitter_inl.h @@ -0,0 +1,331 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Mon. Apr. 18 19:52:34 CST 2011 + +#include + +#ifndef BUTIL_STRING_SPLITTER_INL_H +#define BUTIL_STRING_SPLITTER_INL_H + +namespace butil { + +StringSplitter::StringSplitter(const char* str_begin, + const char* str_end, + const char sep, + EmptyFieldAction action) + : _head(str_begin) + , _str_tail(str_end) + , _sep(sep) + , _empty_field_action(action) { + init(); +} + +StringSplitter::StringSplitter(const char* str, char sep, + EmptyFieldAction action) + : StringSplitter(str, NULL, sep, action) {} + +StringSplitter::StringSplitter(const StringPiece& input, char sep, + EmptyFieldAction action) + : StringSplitter(input.data(), input.data() + input.length(), sep, action) {} + +void StringSplitter::init() { + // Find the starting _head and _tail. + if (__builtin_expect(_head != NULL, 1)) { + if (_empty_field_action == SKIP_EMPTY_FIELD) { + for (; not_end(_head) && *_head == _sep; ++_head) {} + } + for (_tail = _head; not_end(_tail) && *_tail != _sep; ++_tail) {} + } else { + _tail = NULL; + } +} + +StringSplitter& StringSplitter::operator++() { + if (__builtin_expect(_tail != NULL, 1)) { + if (not_end(_tail)) { + ++_tail; + if (_empty_field_action == SKIP_EMPTY_FIELD) { + for (; not_end(_tail) && *_tail == _sep; ++_tail) {} + } + } + _head = _tail; + for (; not_end(_tail) && *_tail != _sep; ++_tail) {} + } + return *this; +} + +StringSplitter StringSplitter::operator++(int) { + StringSplitter tmp = *this; + operator++(); + return tmp; +} + +StringSplitter::operator const void*() const { + return (_head != NULL && not_end(_head)) ? _head : NULL; +} + +const char* StringSplitter::field() const { + return _head; +} + +size_t StringSplitter::length() const { + return static_cast(_tail - _head); +} + +StringPiece StringSplitter::field_sp() const { + return StringPiece(field(), length()); +} + +bool StringSplitter::not_end(const char* p) const { + return (_str_tail == NULL) ? *p : (p != _str_tail); +} + +int StringSplitter::to_int8(int8_t* pv) const { + long v = 0; + if (to_long(&v) == 0 && v >= -128 && v <= 127) { + *pv = (int8_t)v; + return 0; + } + return -1; +} + +int StringSplitter::to_uint8(uint8_t* pv) const { + unsigned long v = 0; + if (to_ulong(&v) == 0 && v <= 255) { + *pv = (uint8_t)v; + return 0; + } + return -1; +} + +int StringSplitter::to_int(int* pv) const { + long v = 0; + if (to_long(&v) == 0 && v >= INT_MIN && v <= INT_MAX) { + *pv = (int)v; + return 0; + } + return -1; +} + +int StringSplitter::to_uint(unsigned int* pv) const { + unsigned long v = 0; + if (to_ulong(&v) == 0 && v <= UINT_MAX) { + *pv = (unsigned int)v; + return 0; + } + return -1; +} + +int StringSplitter::to_long(long* pv) const { + char* endptr = NULL; + *pv = strtol(field(), &endptr, 10); + return (endptr == field() + length()) ? 0 : -1; +} + +int StringSplitter::to_ulong(unsigned long* pv) const { + char* endptr = NULL; + *pv = strtoul(field(), &endptr, 10); + return (endptr == field() + length()) ? 0 : -1; +} + +int StringSplitter::to_longlong(long long* pv) const { + char* endptr = NULL; + *pv = strtoll(field(), &endptr, 10); + return (endptr == field() + length()) ? 0 : -1; +} + +int StringSplitter::to_ulonglong(unsigned long long* pv) const { + char* endptr = NULL; + *pv = strtoull(field(), &endptr, 10); + return (endptr == field() + length()) ? 0 : -1; +} + +int StringSplitter::to_float(float* pv) const { + char* endptr = NULL; + *pv = strtof(field(), &endptr); + return (endptr == field() + length()) ? 0 : -1; +} + +int StringSplitter::to_double(double* pv) const { + char* endptr = NULL; + *pv = strtod(field(), &endptr); + return (endptr == field() + length()) ? 0 : -1; +} + +StringMultiSplitter::StringMultiSplitter ( + const char* str, const char* seps, EmptyFieldAction action) + : _head(str) + , _str_tail(NULL) + , _seps(seps) + , _empty_field_action(action) { + init(); +} + +StringMultiSplitter::StringMultiSplitter ( + const char* str_begin, const char* str_end, + const char* seps, EmptyFieldAction action) + : _head(str_begin) + , _str_tail(str_end) + , _seps(seps) + , _empty_field_action(action) { + init(); +} + +void StringMultiSplitter::init() { + if (__builtin_expect(_head != NULL, 1)) { + if (_empty_field_action == SKIP_EMPTY_FIELD) { + for (; not_end(_head) && is_sep(*_head); ++_head) {} + } + for (_tail = _head; not_end(_tail) && !is_sep(*_tail); ++_tail) {} + } else { + _tail = NULL; + } +} + +StringMultiSplitter& StringMultiSplitter::operator++() { + if (__builtin_expect(_tail != NULL, 1)) { + if (not_end(_tail)) { + ++_tail; + if (_empty_field_action == SKIP_EMPTY_FIELD) { + for (; not_end(_tail) && is_sep(*_tail); ++_tail) {} + } + } + _head = _tail; + for (; not_end(_tail) && !is_sep(*_tail); ++_tail) {} + } + return *this; +} + +StringMultiSplitter StringMultiSplitter::operator++(int) { + StringMultiSplitter tmp = *this; + operator++(); + return tmp; +} + +bool StringMultiSplitter::is_sep(char c) const { + for (const char* p = _seps; *p != '\0'; ++p) { + if (c == *p) { + return true; + } + } + return false; +} + +StringMultiSplitter::operator const void*() const { + return (_head != NULL && not_end(_head)) ? _head : NULL; +} + +const char* StringMultiSplitter::field() const { + return _head; +} + +size_t StringMultiSplitter::length() const { + return static_cast(_tail - _head); +} + +StringPiece StringMultiSplitter::field_sp() const { + return StringPiece(field(), length()); +} + +bool StringMultiSplitter::not_end(const char* p) const { + return (_str_tail == NULL) ? *p : (p != _str_tail); +} + +int StringMultiSplitter::to_int8(int8_t* pv) const { + long v = 0; + if (to_long(&v) == 0 && v >= -128 && v <= 127) { + *pv = (int8_t)v; + return 0; + } + return -1; +} + +int StringMultiSplitter::to_uint8(uint8_t* pv) const { + unsigned long v = 0; + if (to_ulong(&v) == 0 && v <= 255) { + *pv = (uint8_t)v; + return 0; + } + return -1; +} + +int StringMultiSplitter::to_int(int* pv) const { + long v = 0; + if (to_long(&v) == 0 && v >= INT_MIN && v <= INT_MAX) { + *pv = (int)v; + return 0; + } + return -1; +} + +int StringMultiSplitter::to_uint(unsigned int* pv) const { + unsigned long v = 0; + if (to_ulong(&v) == 0 && v <= UINT_MAX) { + *pv = (unsigned int)v; + return 0; + } + return -1; +} + +int StringMultiSplitter::to_long(long* pv) const { + char* endptr = NULL; + *pv = strtol(field(), &endptr, 10); + return (endptr == field() + length()) ? 0 : -1; +} + +int StringMultiSplitter::to_ulong(unsigned long* pv) const { + char* endptr = NULL; + *pv = strtoul(field(), &endptr, 10); + return (endptr == field() + length()) ? 0 : -1; +} + +int StringMultiSplitter::to_longlong(long long* pv) const { + char* endptr = NULL; + *pv = strtoll(field(), &endptr, 10); + return (endptr == field() + length()) ? 0 : -1; +} + +int StringMultiSplitter::to_ulonglong(unsigned long long* pv) const { + char* endptr = NULL; + *pv = strtoull(field(), &endptr, 10); + return (endptr == field() + length()) ? 0 : -1; +} + +int StringMultiSplitter::to_float(float* pv) const { + char* endptr = NULL; + *pv = strtof(field(), &endptr); + return (endptr == field() + length()) ? 0 : -1; +} + +int StringMultiSplitter::to_double(double* pv) const { + char* endptr = NULL; + *pv = strtod(field(), &endptr); + return (endptr == field() + length()) ? 0 : -1; +} + +void KeyValuePairsSplitter::UpdateDelimiterPosition() { + const StringPiece key_value_pair(key_and_value()); + _delim_pos = key_value_pair.find(_key_value_delim); + if (_delim_pos == StringPiece::npos) { + _delim_pos = key_value_pair.length(); + } +} + +} // namespace butil + +#endif // BUTIL_STRING_SPLITTER_INL_H diff --git a/src/butil/strings/latin1_string_conversions.cc b/src/butil/strings/latin1_string_conversions.cc new file mode 100644 index 0000000..bcc2afb --- /dev/null +++ b/src/butil/strings/latin1_string_conversions.cc @@ -0,0 +1,19 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/strings/latin1_string_conversions.h" + +namespace butil { + +string16 Latin1OrUTF16ToUTF16(size_t length, + const Latin1Char* latin1, + const char16* utf16) { + if (!length) + return string16(); + if (latin1) + return string16(latin1, latin1 + length); + return string16(utf16, utf16 + length); +} + +} // namespace butil diff --git a/src/butil/strings/latin1_string_conversions.h b/src/butil/strings/latin1_string_conversions.h new file mode 100644 index 0000000..ca259f2 --- /dev/null +++ b/src/butil/strings/latin1_string_conversions.h @@ -0,0 +1,32 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_STRINGS_LATIN1_STRING_CONVERSIONS_H_ +#define BUTIL_STRINGS_LATIN1_STRING_CONVERSIONS_H_ + +#include + +#include "butil/base_export.h" +#include "butil/strings/string16.h" + +namespace butil { + +// This definition of Latin1Char matches the definition of LChar in Blink. We +// use unsigned char rather than char to make less tempting to mix and match +// Latin-1 and UTF-8 characters.. +typedef unsigned char Latin1Char; + +// This somewhat odd function is designed to help us convert from Blink Strings +// to string16. A Blink string is either backed by an array of Latin-1 +// characters or an array of UTF-16 characters. This function is called by +// WebString::operator string16() to convert one or the other character array +// to string16. This function is defined here rather than in WebString.h to +// avoid binary bloat in all the callers of the conversion operator. +BUTIL_EXPORT string16 Latin1OrUTF16ToUTF16(size_t length, + const Latin1Char* latin1, + const char16* utf16); + +} // namespace butil + +#endif // BUTIL_STRINGS_LATIN1_STRING_CONVERSIONS_H_ diff --git a/src/butil/strings/nullable_string16.cc b/src/butil/strings/nullable_string16.cc new file mode 100644 index 0000000..e5dc49d --- /dev/null +++ b/src/butil/strings/nullable_string16.cc @@ -0,0 +1,17 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/strings/nullable_string16.h" + +#include + +#include "butil/strings/utf_string_conversions.h" + +namespace butil { + +std::ostream& operator<<(std::ostream& out, const NullableString16& value) { + return value.is_null() ? out << "(null)" : out << UTF16ToUTF8(value.string()); +} + +} // namespace butil diff --git a/src/butil/strings/nullable_string16.h b/src/butil/strings/nullable_string16.h new file mode 100644 index 0000000..710401e --- /dev/null +++ b/src/butil/strings/nullable_string16.h @@ -0,0 +1,46 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_STRINGS_NULLABLE_STRING16_H_ +#define BUTIL_STRINGS_NULLABLE_STRING16_H_ + +#include + +#include "butil/base_export.h" +#include "butil/strings/string16.h" + +namespace butil { + +// This class is a simple wrapper for string16 which also contains a null +// state. This should be used only where the difference between null and +// empty is meaningful. +class NullableString16 { + public: + NullableString16() : is_null_(true) { } + NullableString16(const string16& string, bool is_null) + : string_(string), is_null_(is_null) { + } + + const string16& string() const { return string_; } + bool is_null() const { return is_null_; } + + private: + string16 string_; + bool is_null_; +}; + +inline bool operator==(const NullableString16& a, const NullableString16& b) { + return a.is_null() == b.is_null() && a.string() == b.string(); +} + +inline bool operator!=(const NullableString16& a, const NullableString16& b) { + return !(a == b); +} + +BUTIL_EXPORT std::ostream& operator<<(std::ostream& out, + const NullableString16& value); + +} // namespace + +#endif // BUTIL_STRINGS_NULLABLE_STRING16_H_ diff --git a/src/butil/strings/safe_sprintf.cc b/src/butil/strings/safe_sprintf.cc new file mode 100644 index 0000000..09c5abd --- /dev/null +++ b/src/butil/strings/safe_sprintf.cc @@ -0,0 +1,682 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/strings/safe_sprintf.h" + +#include + +#if !defined(NDEBUG) +// In debug builds, we use RAW_CHECK() to print useful error messages, if +// SafeSPrintf() is called with broken arguments. +// As our contract promises that SafeSPrintf() can be called from any +// restricted run-time context, it is not actually safe to call logging +// functions from it; and we only ever do so for debug builds and hope for the +// best. We should _never_ call any logging function other than RAW_CHECK(), +// and we should _never_ include any logging code that is active in production +// builds. Most notably, we should not include these logging functions in +// unofficial release builds, even though those builds would otherwise have +// DEBUG_CHECKS() enabled. +// In other words; please do not remove the #ifdef around this #include. +// Instead, in production builds we opt for returning a degraded result, +// whenever an error is encountered. +// E.g. The broken function call +// SafeSPrintf("errno = %d (%x)", errno, strerror(errno)) +// will print something like +// errno = 13, (%x) +// instead of +// errno = 13 (Access denied) +// In most of the anticipated use cases, that's probably the preferred +// behavior. +#include "butil/logging.h" +#define DEBUG_CHECK RAW_CHECK +#else +#define DEBUG_CHECK(x, msg) do { if (x) { } } while (0) +#endif + +namespace butil { +namespace strings { + +// The code in this file is extremely careful to be async-signal-safe. +// +// Most obviously, we avoid calling any code that could dynamically allocate +// memory. Doing so would almost certainly result in bugs and dead-locks. +// We also avoid calling any other STL functions that could have unintended +// side-effects involving memory allocation or access to other shared +// resources. +// +// But on top of that, we also avoid calling other library functions, as many +// of them have the side-effect of calling getenv() (in order to deal with +// localization) or accessing errno. The latter sounds benign, but there are +// several execution contexts where it isn't even possible to safely read let +// alone write errno. +// +// The stated design goal of the SafeSPrintf() function is that it can be +// called from any context that can safely call C or C++ code (i.e. anything +// that doesn't require assembly code). +// +// For a brief overview of some but not all of the issues with async-signal- +// safety, refer to: +// http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html + +namespace { +const size_t kSSizeMaxConst = ((size_t)(ssize_t)-1) >> 1; + +const char kUpCaseHexDigits[] = "0123456789ABCDEF"; +const char kDownCaseHexDigits[] = "0123456789abcdef"; +} + +#if defined(NDEBUG) +// We would like to define kSSizeMax as std::numeric_limits::max(), +// but C++ doesn't allow us to do that for constants. Instead, we have to +// use careful casting and shifting. We later use a COMPILE_ASSERT to +// verify that this worked correctly. +namespace { +const size_t kSSizeMax = kSSizeMaxConst; +} +#else // defined(NDEBUG) +// For efficiency, we really need kSSizeMax to be a constant. But for unit +// tests, it should be adjustable. This allows us to verify edge cases without +// having to fill the entire available address space. As a compromise, we make +// kSSizeMax adjustable in debug builds, and then only compile that particular +// part of the unit test in debug builds. +namespace { +static size_t kSSizeMax = kSSizeMaxConst; +} + +namespace internal { +void SetSafeSPrintfSSizeMaxForTest(size_t max) { + kSSizeMax = max; +} + +size_t GetSafeSPrintfSSizeMaxForTest() { + return kSSizeMax; +} +} +#endif // defined(NDEBUG) + +namespace { +class Buffer { + public: + // |buffer| is caller-allocated storage that SafeSPrintf() writes to. It + // has |size| bytes of writable storage. It is the caller's responsibility + // to ensure that the buffer is at least one byte in size, so that it fits + // the trailing NUL that will be added by the destructor. The buffer also + // must be smaller or equal to kSSizeMax in size. + Buffer(char* buffer, size_t size) + : buffer_(buffer), + size_(size - 1), // Account for trailing NUL byte + count_(0) { +// The following assertion does not build on Mac and Android and gcc before 4.6 +// This is because static_assert only works with compile-time constants, but +// mac uses libstdc++4.2, android uses stlport and gcc doesn't support keyword +// constexpr until 4.6, which all don't mark numeric_limits::max() as constexp. +#if defined(BUTIL_CXX11_ENABLED) \ + && !(defined(__GNUC__) && __GNUC__ * 10000 + __GNUC_MINOR__ * 100 < 40600) \ + && !defined(OS_ANDROID) && !defined(OS_MACOSX) && !defined(OS_IOS) + BAIDU_CASSERT(kSSizeMaxConst == \ + static_cast(std::numeric_limits::max()), + kSSizeMax_is_the_max_value_of_an_ssize_t); +#endif + DEBUG_CHECK(size > 0, ""); + DEBUG_CHECK(size <= kSSizeMax, ""); + } + + ~Buffer() { + // The code calling the constructor guaranteed that there was enough space + // to store a trailing NUL -- and in debug builds, we are actually + // verifying this with DEBUG_CHECK()s in the constructor. So, we can + // always unconditionally write the NUL byte in the destructor. We do not + // need to adjust the count_, as SafeSPrintf() copies snprintf() in not + // including the NUL byte in its return code. + *GetInsertionPoint() = '\000'; + } + + // Returns true, iff the buffer is filled all the way to |kSSizeMax-1|. The + // caller can now stop adding more data, as GetCount() has reached its + // maximum possible value. + inline bool OutOfAddressableSpace() const { + return count_ == static_cast(kSSizeMax - 1); + } + + // Returns the number of bytes that would have been emitted to |buffer_| + // if it was sized sufficiently large. This number can be larger than + // |size_|, if the caller provided an insufficiently large output buffer. + // But it will never be bigger than |kSSizeMax-1|. + inline ssize_t GetCount() const { + DEBUG_CHECK(count_ < kSSizeMax, ""); + return static_cast(count_); + } + + // Emits one |ch| character into the |buffer_| and updates the |count_| of + // characters that are currently supposed to be in the buffer. + // Returns "false", iff the buffer was already full. + // N.B. |count_| increases even if no characters have been written. This is + // needed so that GetCount() can return the number of bytes that should + // have been allocated for the |buffer_|. + inline bool Out(char ch) { + if (size_ >= 1 && count_ < size_) { + buffer_[count_] = ch; + return IncrementCountByOne(); + } + // |count_| still needs to be updated, even if the buffer has been + // filled completely. This allows SafeSPrintf() to return the number of + // bytes that should have been emitted. + IncrementCountByOne(); + return false; + } + + // Inserts |padding|-|len| bytes worth of padding into the |buffer_|. + // |count_| will also be incremented by the number of bytes that were meant + // to be emitted. The |pad| character is typically either a ' ' space + // or a '0' zero, but other non-NUL values are legal. + // Returns "false", iff the the |buffer_| filled up (i.e. |count_| + // overflowed |size_|) at any time during padding. + inline bool Pad(char pad, size_t padding, size_t len) { + DEBUG_CHECK(pad, ""); + DEBUG_CHECK(padding <= kSSizeMax, ""); + for (; padding > len; --padding) { + if (!Out(pad)) { + if (--padding) { + IncrementCount(padding-len); + } + return false; + } + } + return true; + } + + // POSIX doesn't define any async-signal-safe function for converting + // an integer to ASCII. Define our own version. + // + // This also gives us the ability to make the function a little more + // powerful and have it deal with |padding|, with truncation, and with + // predicting the length of the untruncated output. + // + // IToASCII() converts an integer |i| to ASCII. + // + // Unlike similar functions in the standard C library, it never appends a + // NUL character. This is left for the caller to do. + // + // While the function signature takes a signed int64_t, the code decides at + // run-time whether to treat the argument as signed (int64_t) or as unsigned + // (uint64_t) based on the value of |sign|. + // + // It supports |base|s 2 through 16. Only a |base| of 10 is allowed to have + // a |sign|. Otherwise, |i| is treated as unsigned. + // + // For bases larger than 10, |upcase| decides whether lower-case or upper- + // case letters should be used to designate digits greater than 10. + // + // Padding can be done with either '0' zeros or ' ' spaces. Padding has to + // be positive and will always be applied to the left of the output. + // + // Prepends a |prefix| to the number (e.g. "0x"). This prefix goes to + // the left of |padding|, if |pad| is '0'; and to the right of |padding| + // if |pad| is ' '. + // + // Returns "false", if the |buffer_| overflowed at any time. + bool IToASCII(bool sign, bool upcase, int64_t i, int base, + char pad, size_t padding, const char* prefix); + + private: + // Increments |count_| by |inc| unless this would cause |count_| to + // overflow |kSSizeMax-1|. Returns "false", iff an overflow was detected; + // it then clamps |count_| to |kSSizeMax-1|. + inline bool IncrementCount(size_t inc) { + // "inc" is either 1 or a "padding" value. Padding is clamped at + // run-time to at most kSSizeMax-1. So, we know that "inc" is always in + // the range 1..kSSizeMax-1. + // This allows us to compute "kSSizeMax - 1 - inc" without incurring any + // integer overflows. + DEBUG_CHECK(inc <= kSSizeMax - 1, ""); + if (count_ > kSSizeMax - 1 - inc) { + count_ = kSSizeMax - 1; + return false; + } else { + count_ += inc; + return true; + } + } + + // Convenience method for the common case of incrementing |count_| by one. + inline bool IncrementCountByOne() { + return IncrementCount(1); + } + + // Return the current insertion point into the buffer. This is typically + // at |buffer_| + |count_|, but could be before that if truncation + // happened. It always points to one byte past the last byte that was + // successfully placed into the |buffer_|. + inline char* GetInsertionPoint() const { + size_t idx = count_; + if (idx > size_) { + idx = size_; + } + return buffer_ + idx; + } + + // User-provided buffer that will receive the fully formatted output string. + char* buffer_; + + // Number of bytes that are available in the buffer excluding the trailing + // NUL byte that will be added by the destructor. + const size_t size_; + + // Number of bytes that would have been emitted to the buffer, if the buffer + // was sufficiently big. This number always excludes the trailing NUL byte + // and it is guaranteed to never grow bigger than kSSizeMax-1. + size_t count_; + + DISALLOW_COPY_AND_ASSIGN(Buffer); +}; + + +bool Buffer::IToASCII(bool sign, bool upcase, int64_t i, int base, + char pad, size_t padding, const char* prefix) { + // Sanity check for parameters. None of these should ever fail, but see + // above for the rationale why we can't call CHECK(). + DEBUG_CHECK(base >= 2, ""); + DEBUG_CHECK(base <= 16, ""); + DEBUG_CHECK(!sign || base == 10, ""); + DEBUG_CHECK(pad == '0' || pad == ' ', ""); + DEBUG_CHECK(padding <= kSSizeMax, ""); + DEBUG_CHECK(!(sign && prefix && *prefix), ""); + + // Handle negative numbers, if the caller indicated that |i| should be + // treated as a signed number; otherwise treat |i| as unsigned (even if the + // MSB is set!) + // Details are tricky, because of limited data-types, but equivalent pseudo- + // code would look like: + // if (sign && i < 0) + // prefix = "-"; + // num = abs(i); + int minint = 0; + uint64_t num; + if (sign && i < 0) { + prefix = "-"; + + // Turn our number positive. + if (i == std::numeric_limits::min()) { + // The most negative integer needs special treatment. + minint = 1; + num = static_cast(-(i + 1)); + } else { + // "Normal" negative numbers are easy. + num = static_cast(-i); + } + } else { + num = static_cast(i); + } + + // If padding with '0' zero, emit the prefix or '-' character now. Otherwise, + // make the prefix accessible in reverse order, so that we can later output + // it right between padding and the number. + // We cannot choose the easier approach of just reversing the number, as that + // fails in situations where we need to truncate numbers that have padding + // and/or prefixes. + const char* reverse_prefix = NULL; + if (prefix && *prefix) { + if (pad == '0') { + while (*prefix) { + if (padding) { + --padding; + } + Out(*prefix++); + } + prefix = NULL; + } else { + for (reverse_prefix = prefix; *reverse_prefix; ++reverse_prefix) { + } + } + } else + prefix = NULL; + const size_t prefix_length = reverse_prefix - prefix; + + // Loop until we have converted the entire number. Output at least one + // character (i.e. '0'). + size_t start = count_; + size_t discarded = 0; + bool started = false; + do { + // Make sure there is still enough space left in our output buffer. + if (count_ >= size_) { + if (start < size_) { + // It is rare that we need to output a partial number. But if asked + // to do so, we will still make sure we output the correct number of + // leading digits. + // Since we are generating the digits in reverse order, we actually + // have to discard digits in the order that we have already emitted + // them. This is essentially equivalent to: + // memmove(buffer_ + start, buffer_ + start + 1, size_ - start - 1) + for (char* move = buffer_ + start, *end = buffer_ + size_ - 1; + move < end; + ++move) { + *move = move[1]; + } + ++discarded; + --count_; + } else if (count_ - size_ > 1) { + // Need to increment either |count_| or |discarded| to make progress. + // The latter is more efficient, as it eventually triggers fast + // handling of padding. But we have to ensure we don't accidentally + // change the overall state (i.e. switch the state-machine from + // discarding to non-discarding). |count_| needs to always stay + // bigger than |size_|. + --count_; + ++discarded; + } + } + + // Output the next digit and (if necessary) compensate for the most + // negative integer needing special treatment. This works because, + // no matter the bit width of the integer, the lowest-most decimal + // integer always ends in 2, 4, 6, or 8. + if (!num && started) { + if (reverse_prefix > prefix) { + Out(*--reverse_prefix); + } else { + Out(pad); + } + } else { + started = true; + Out((upcase ? kUpCaseHexDigits : kDownCaseHexDigits)[num%base + minint]); + } + + minint = 0; + num /= base; + + // Add padding, if requested. + if (padding > 0) { + --padding; + + // Performance optimization for when we are asked to output excessive + // padding, but our output buffer is limited in size. Even if we output + // a 64bit number in binary, we would never write more than 64 plus + // prefix non-padding characters. So, once this limit has been passed, + // any further state change can be computed arithmetically; we know that + // by this time, our entire final output consists of padding characters + // that have all already been output. + if (discarded > 8*sizeof(num) + prefix_length) { + IncrementCount(padding); + padding = 0; + } + } + } while (num || padding || (reverse_prefix > prefix)); + + // Conversion to ASCII actually resulted in the digits being in reverse + // order. We can't easily generate them in forward order, as we can't tell + // the number of characters needed until we are done converting. + // So, now, we reverse the string (except for the possible '-' sign). + char* front = buffer_ + start; + char* back = GetInsertionPoint(); + while (--back > front) { + char ch = *back; + *back = *front; + *front++ = ch; + } + + IncrementCount(discarded); + return !discarded; +} + +} // anonymous namespace + +namespace internal { + +ssize_t SafeSNPrintf(char* buf, size_t sz, const char* fmt, const Arg* args, + const size_t max_args) { + // Make sure that at least one NUL byte can be written, and that the buffer + // never overflows kSSizeMax. Not only does that use up most or all of the + // address space, it also would result in a return code that cannot be + // represented. + if (static_cast(sz) < 1) { + return -1; + } else if (sz > kSSizeMax) { + sz = kSSizeMax; + } + + // Iterate over format string and interpret '%' arguments as they are + // encountered. + Buffer buffer(buf, sz); + size_t padding; + char pad; + for (unsigned int cur_arg = 0; *fmt && !buffer.OutOfAddressableSpace(); ) { + if (*fmt++ == '%') { + padding = 0; + pad = ' '; + char ch = *fmt++; + format_character_found: + switch (ch) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + // Found a width parameter. Convert to an integer value and store in + // "padding". If the leading digit is a zero, change the padding + // character from a space ' ' to a zero '0'. + pad = ch == '0' ? '0' : ' '; + for (;;) { + // The maximum allowed padding fills all the available address + // space and leaves just enough space to insert the trailing NUL. + const size_t max_padding = kSSizeMax - 1; + if (padding > max_padding/10 || + 10*padding > max_padding - (ch - '0')) { + DEBUG_CHECK(padding <= max_padding/10 && + 10*padding <= max_padding - (ch - '0'), ""); + // Integer overflow detected. Skip the rest of the width until + // we find the format character, then do the normal error handling. + padding_overflow: + padding = max_padding; + while ((ch = *fmt++) >= '0' && ch <= '9') { + } + if (cur_arg < max_args) { + ++cur_arg; + } + goto fail_to_expand; + } + padding = 10*padding + ch - '0'; + if (padding > max_padding) { + // This doesn't happen for "sane" values of kSSizeMax. But once + // kSSizeMax gets smaller than about 10, our earlier range checks + // are incomplete. Unittests do trigger this artificial corner + // case. + DEBUG_CHECK(padding <= max_padding, ""); + goto padding_overflow; + } + ch = *fmt++; + if (ch < '0' || ch > '9') { + // Reached the end of the width parameter. This is where the format + // character is found. + goto format_character_found; + } + } + break; + case 'c': { // Output an ASCII character. + // Check that there are arguments left to be inserted. + if (cur_arg >= max_args) { + DEBUG_CHECK(cur_arg < max_args, ""); + goto fail_to_expand; + } + + // Check that the argument has the expected type. + const Arg& arg = args[cur_arg++]; + if (arg.type != Arg::INT && arg.type != Arg::UINT) { + DEBUG_CHECK(arg.type == Arg::INT || arg.type == Arg::UINT, ""); + goto fail_to_expand; + } + + // Apply padding, if needed. + buffer.Pad(' ', padding, 1); + + // Convert the argument to an ASCII character and output it. + char ch = static_cast(arg.i); + if (!ch) { + goto end_of_output_buffer; + } + buffer.Out(ch); + break; } + case 'd': // Output a possibly signed decimal value. + case 'o': // Output an unsigned octal value. + case 'x': // Output an unsigned hexadecimal value. + case 'X': + case 'p': { // Output a pointer value. + // Check that there are arguments left to be inserted. + if (cur_arg >= max_args) { + DEBUG_CHECK(cur_arg < max_args, ""); + goto fail_to_expand; + } + + const Arg& arg = args[cur_arg++]; + int64_t i; + const char* prefix = NULL; + if (ch != 'p') { + // Check that the argument has the expected type. + if (arg.type != Arg::INT && arg.type != Arg::UINT) { + DEBUG_CHECK(arg.type == Arg::INT || arg.type == Arg::UINT, ""); + goto fail_to_expand; + } + i = arg.i; + + if (ch != 'd') { + // The Arg() constructor automatically performed sign expansion on + // signed parameters. This is great when outputting a %d decimal + // number, but can result in unexpected leading 0xFF bytes when + // outputting a %x hexadecimal number. Mask bits, if necessary. + // We have to do this here, instead of in the Arg() constructor, as + // the Arg() constructor cannot tell whether we will output a %d + // or a %x. Only the latter should experience masking. + if (arg.width < sizeof(int64_t)) { + i &= (1LL << (8*arg.width)) - 1; + } + } + } else { + // Pointer values require an actual pointer or a string. + if (arg.type == Arg::POINTER) { + i = reinterpret_cast(arg.ptr); + } else if (arg.type == Arg::STRING) { + i = reinterpret_cast(arg.str); + } else if (arg.type == Arg::INT && arg.width == sizeof(NULL) && + arg.i == 0) { // Allow C++'s version of NULL + i = 0; + } else { + DEBUG_CHECK(arg.type == Arg::POINTER || arg.type == Arg::STRING, ""); + goto fail_to_expand; + } + + // Pointers always include the "0x" prefix. + prefix = "0x"; + } + + // Use IToASCII() to convert to ASCII representation. For decimal + // numbers, optionally print a sign. For hexadecimal numbers, + // distinguish between upper and lower case. %p addresses are always + // printed as upcase. Supports base 8, 10, and 16. Prints padding + // and/or prefixes, if so requested. + buffer.IToASCII(ch == 'd' && arg.type == Arg::INT, + ch != 'x', i, + ch == 'o' ? 8 : ch == 'd' ? 10 : 16, + pad, padding, prefix); + break; } + case 's': { + // Check that there are arguments left to be inserted. + if (cur_arg >= max_args) { + DEBUG_CHECK(cur_arg < max_args, ""); + goto fail_to_expand; + } + + // Check that the argument has the expected type. + const Arg& arg = args[cur_arg++]; + const char *s; + if (arg.type == Arg::STRING) { + s = arg.str ? arg.str : ""; + } else if (arg.type == Arg::INT && arg.width == sizeof(NULL) && + arg.i == 0) { // Allow C++'s version of NULL + s = ""; + } else { + DEBUG_CHECK(arg.type == Arg::STRING, ""); + goto fail_to_expand; + } + + // Apply padding, if needed. This requires us to first check the + // length of the string that we are outputting. + if (padding) { + size_t len = 0; + for (const char* src = s; *src++; ) { + ++len; + } + buffer.Pad(' ', padding, len); + } + + // Printing a string involves nothing more than copying it into the + // output buffer and making sure we don't output more bytes than + // available space; Out() takes care of doing that. + for (const char* src = s; *src; ) { + buffer.Out(*src++); + } + break; } + case '%': + // Quoted percent '%' character. + goto copy_verbatim; + fail_to_expand: + // C++ gives us tools to do type checking -- something that snprintf() + // could never really do. So, whenever we see arguments that don't + // match up with the format string, we refuse to output them. But + // since we have to be extremely conservative about being async- + // signal-safe, we are limited in the type of error handling that we + // can do in production builds (in debug builds we can use + // DEBUG_CHECK() and hope for the best). So, all we do is pass the + // format string unchanged. That should eventually get the user's + // attention; and in the meantime, it hopefully doesn't lose too much + // data. + default: + // Unknown or unsupported format character. Just copy verbatim to + // output. + buffer.Out('%'); + DEBUG_CHECK(ch, ""); + if (!ch) { + goto end_of_format_string; + } + buffer.Out(ch); + break; + } + } else { + copy_verbatim: + buffer.Out(fmt[-1]); + } + } + end_of_format_string: + end_of_output_buffer: + return buffer.GetCount(); +} + +} // namespace internal + +ssize_t SafeSNPrintf(char* buf, size_t sz, const char* fmt) { + // Make sure that at least one NUL byte can be written, and that the buffer + // never overflows kSSizeMax. Not only does that use up most or all of the + // address space, it also would result in a return code that cannot be + // represented. + if (static_cast(sz) < 1) { + return -1; + } else if (sz > kSSizeMax) { + sz = kSSizeMax; + } + + Buffer buffer(buf, sz); + + // In the slow-path, we deal with errors by copying the contents of + // "fmt" unexpanded. This means, if there are no arguments passed, the + // SafeSPrintf() function always degenerates to a version of strncpy() that + // de-duplicates '%' characters. + const char* src = fmt; + for (; *src; ++src) { + buffer.Out(*src); + DEBUG_CHECK(src[0] != '%' || src[1] == '%', ""); + if (src[0] == '%' && src[1] == '%') { + ++src; + } + } + return buffer.GetCount(); +} + +} // namespace strings +} // namespace butil diff --git a/src/butil/strings/safe_sprintf.h b/src/butil/strings/safe_sprintf.h new file mode 100644 index 0000000..eea8f36 --- /dev/null +++ b/src/butil/strings/safe_sprintf.h @@ -0,0 +1,417 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_STRINGS_SAFE_SPRINTF_H_ +#define BUTIL_STRINGS_SAFE_SPRINTF_H_ + +#include "butil/build_config.h" + +#include +#include +#include + +#if defined(OS_POSIX) +// For ssize_t +#include +#endif + +#include "butil/base_export.h" +#include "butil/basictypes.h" + +namespace butil { +namespace strings { + +#if defined(_MSC_VER) +// Define ssize_t inside of our namespace. +#if defined(_WIN64) +typedef __int64 ssize_t; +#else +typedef long ssize_t; +#endif +#endif + +// SafeSPrintf() is a type-safe and completely self-contained version of +// snprintf(). +// +// SafeSNPrintf() is an alternative function signature that can be used when +// not dealing with fixed-sized buffers. When possible, SafeSPrintf() should +// always be used instead of SafeSNPrintf() +// +// These functions allow for formatting complicated messages from contexts that +// require strict async-signal-safety. In fact, it is safe to call them from +// any low-level execution context, as they are guaranteed to make no library +// or system calls. It deliberately never touches "errno", either. +// +// The only exception to this rule is that in debug builds the code calls +// RAW_CHECK() to help diagnose problems when the format string does not +// match the rest of the arguments. In release builds, no CHECK()s are used, +// and SafeSPrintf() instead returns an output string that expands only +// those arguments that match their format characters. Mismatched arguments +// are ignored. +// +// The code currently only supports a subset of format characters: +// %c, %o, %d, %x, %X, %p, and %s. +// +// SafeSPrintf() aims to be as liberal as reasonably possible. Integer-like +// values of arbitrary width can be passed to all of the format characters +// that expect integers. Thus, it is explicitly legal to pass an "int" to +// "%c", and output will automatically look at the LSB only. It is also +// explicitly legal to pass either signed or unsigned values, and the format +// characters will automatically interpret the arguments accordingly. +// +// It is still not legal to mix-and-match integer-like values with pointer +// values. For instance, you cannot pass a pointer to %x, nor can you pass an +// integer to %p. +// +// The one exception is "0" zero being accepted by "%p". This works-around +// the problem of C++ defining NULL as an integer-like value. +// +// All format characters take an optional width parameter. This must be a +// positive integer. For %d, %o, %x, %X and %p, if the width starts with +// a leading '0', padding is done with '0' instead of ' ' characters. +// +// There are a few features of snprintf()-style format strings, that +// SafeSPrintf() does not support at this time. +// +// If an actual user showed up, there is no particularly strong reason they +// couldn't be added. But that assumes that the trade-offs between complexity +// and utility are favorable. +// +// For example, adding support for negative padding widths, and for %n are all +// likely to be viewed positively. They are all clearly useful, low-risk, easy +// to test, don't jeopardize the async-signal-safety of the code, and overall +// have little impact on other parts of SafeSPrintf() function. +// +// On the other hands, adding support for alternate forms, positional +// arguments, grouping, wide characters, localization or floating point numbers +// are all unlikely to ever be added. +// +// SafeSPrintf() and SafeSNPrintf() mimic the behavior of snprintf() and they +// return the number of bytes needed to store the untruncated output. This +// does *not* include the terminating NUL byte. +// +// They return -1, iff a fatal error happened. This typically can only happen, +// if the buffer size is a) negative, or b) zero (i.e. not even the NUL byte +// can be written). The return value can never be larger than SSIZE_MAX-1. +// This ensures that the caller can always add one to the signed return code +// in order to determine the amount of storage that needs to be allocated. +// +// While the code supports type checking and while it is generally very careful +// to avoid printing incorrect values, it tends to be conservative in printing +// as much as possible, even when given incorrect parameters. Typically, in +// case of an error, the format string will not be expanded. (i.e. something +// like SafeSPrintf(buf, "%p %d", 1, 2) results in "%p 2"). See above for +// the use of RAW_CHECK() in debug builds, though. +// +// Basic example: +// char buf[20]; +// butil::strings::SafeSPrintf(buf, "The answer: %2d", 42); +// +// Example with dynamically sized buffer (async-signal-safe). This code won't +// work on Visual studio, as it requires dynamically allocating arrays on the +// stack. Consider picking a smaller value for |kMaxSize| if stack size is +// limited and known. On the other hand, if the parameters to SafeSNPrintf() +// are trusted and not controllable by the user, you can consider eliminating +// the check for |kMaxSize| altogether. The current value of SSIZE_MAX is +// essentially a no-op that just illustrates how to implement an upper bound: +// const size_t kInitialSize = 128; +// const size_t kMaxSize = std::numeric_limits::max(); +// size_t size = kInitialSize; +// for (;;) { +// char buf[size]; +// size = SafeSNPrintf(buf, size, "Error message \"%s\"\n", err) + 1; +// if (sizeof(buf) < kMaxSize && size > kMaxSize) { +// size = kMaxSize; +// continue; +// } else if (size > sizeof(buf)) +// continue; +// write(2, buf, size-1); +// break; +// } + +namespace internal { +// Helpers that use C++ overloading, templates, and specializations to deduce +// and record type information from function arguments. This allows us to +// later write a type-safe version of snprintf(). + +struct Arg { + enum Type { INT, UINT, STRING, POINTER }; + + // Any integer-like value. + Arg(signed char c) : i(c), width(sizeof(char)), type(INT) { } + Arg(unsigned char c) : i(c), width(sizeof(char)), type(UINT) { } + Arg(signed short j) : i(j), width(sizeof(short)), type(INT) { } + Arg(unsigned short j) : i(j), width(sizeof(short)), type(UINT) { } + Arg(signed int j) : i(j), width(sizeof(int)), type(INT) { } + Arg(unsigned int j) : i(j), width(sizeof(int)), type(UINT) { } + Arg(signed long j) : i(j), width(sizeof(long)), type(INT) { } + Arg(unsigned long j) : i(j), width(sizeof(long)), type(UINT) { } + Arg(signed long long j) : i(j), width(sizeof(long long)), type(INT) { } + Arg(unsigned long long j) : i(j), width(sizeof(long long)), type(UINT) { } + + // A C-style text string. + Arg(const char* s) : str(s), type(STRING) { } + Arg(char* s) : str(s), type(STRING) { } + + // Any pointer value that can be cast to a "void*". + template Arg(T* p) : ptr((void*)p), type(POINTER) { } + + union { + // An integer-like value. + struct { + int64_t i; + unsigned char width; + }; + + // A C-style text string. + const char* str; + + // A pointer to an arbitrary object. + const void* ptr; + }; + const enum Type type; +}; + +// This is the internal function that performs the actual formatting of +// an snprintf()-style format string. +BUTIL_EXPORT ssize_t SafeSNPrintf(char* buf, size_t sz, const char* fmt, + const Arg* args, size_t max_args); + +#if !defined(NDEBUG) +// In debug builds, allow unit tests to artificially lower the kSSizeMax +// constant that is used as a hard upper-bound for all buffers. In normal +// use, this constant should always be std::numeric_limits::max(). +BUTIL_EXPORT void SetSafeSPrintfSSizeMaxForTest(size_t max); +BUTIL_EXPORT size_t GetSafeSPrintfSSizeMaxForTest(); +#endif + +} // namespace internal + +// TODO(markus): C++11 has a much more concise and readable solution for +// expressing what we are doing here. + +template +ssize_t SafeSNPrintf(char* buf, size_t N, const char* fmt, + T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, + T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) { + // Use Arg() object to record type information and then copy arguments to an + // array to make it easier to iterate over them. + const internal::Arg arg_array[] = { + arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 + }; + return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); +} + +template +ssize_t SafeSPrintf(char (&buf)[N], const char* fmt, + T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, + T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) { + // Use Arg() object to record type information and then copy arguments to an + // array to make it easier to iterate over them. + const internal::Arg arg_array[] = { + arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 + }; + return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); +} + +template +ssize_t SafeSNPrintf(char* buf, size_t N, const char* fmt, + T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, + T5 arg5, T6 arg6, T7 arg7, T8 arg8) { + // Use Arg() object to record type information and then copy arguments to an + // array to make it easier to iterate over them. + const internal::Arg arg_array[] = { + arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 + }; + return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); +} + +template +ssize_t SafeSPrintf(char (&buf)[N], const char* fmt, + T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, + T5 arg5, T6 arg6, T7 arg7, T8 arg8) { + // Use Arg() object to record type information and then copy arguments to an + // array to make it easier to iterate over them. + const internal::Arg arg_array[] = { + arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 + }; + return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); +} + +template +ssize_t SafeSNPrintf(char* buf, size_t N, const char* fmt, + T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, + T5 arg5, T6 arg6, T7 arg7) { + // Use Arg() object to record type information and then copy arguments to an + // array to make it easier to iterate over them. + const internal::Arg arg_array[] = { + arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7 + }; + return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); +} + +template +ssize_t SafeSPrintf(char (&buf)[N], const char* fmt, + T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, + T5 arg5, T6 arg6, T7 arg7) { + // Use Arg() object to record type information and then copy arguments to an + // array to make it easier to iterate over them. + const internal::Arg arg_array[] = { + arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7 + }; + return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); +} + +template +ssize_t SafeSNPrintf(char* buf, size_t N, const char* fmt, + T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, + T5 arg5, T6 arg6) { + // Use Arg() object to record type information and then copy arguments to an + // array to make it easier to iterate over them. + const internal::Arg arg_array[] = { + arg0, arg1, arg2, arg3, arg4, arg5, arg6 + }; + return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); +} + +template +ssize_t SafeSPrintf(char (&buf)[N], const char* fmt, + T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, + T6 arg6) { + // Use Arg() object to record type information and then copy arguments to an + // array to make it easier to iterate over them. + const internal::Arg arg_array[] = { + arg0, arg1, arg2, arg3, arg4, arg5, arg6 + }; + return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); +} + +template +ssize_t SafeSNPrintf(char* buf, size_t N, const char* fmt, + T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { + // Use Arg() object to record type information and then copy arguments to an + // array to make it easier to iterate over them. + const internal::Arg arg_array[] = { arg0, arg1, arg2, arg3, arg4, arg5 }; + return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); +} + +template +ssize_t SafeSPrintf(char (&buf)[N], const char* fmt, + T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { + // Use Arg() object to record type information and then copy arguments to an + // array to make it easier to iterate over them. + const internal::Arg arg_array[] = { arg0, arg1, arg2, arg3, arg4, arg5 }; + return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); +} + +template +ssize_t SafeSNPrintf(char* buf, size_t N, const char* fmt, + T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4) { + // Use Arg() object to record type information and then copy arguments to an + // array to make it easier to iterate over them. + const internal::Arg arg_array[] = { arg0, arg1, arg2, arg3, arg4 }; + return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); +} + +template +ssize_t SafeSPrintf(char (&buf)[N], const char* fmt, T0 arg0, T1 arg1, + T2 arg2, T3 arg3, T4 arg4) { + // Use Arg() object to record type information and then copy arguments to an + // array to make it easier to iterate over them. + const internal::Arg arg_array[] = { arg0, arg1, arg2, arg3, arg4 }; + return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); +} + +template +ssize_t SafeSNPrintf(char* buf, size_t N, const char* fmt, + T0 arg0, T1 arg1, T2 arg2, T3 arg3) { + // Use Arg() object to record type information and then copy arguments to an + // array to make it easier to iterate over them. + const internal::Arg arg_array[] = { arg0, arg1, arg2, arg3 }; + return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); +} + +template +ssize_t SafeSPrintf(char (&buf)[N], const char* fmt, + T0 arg0, T1 arg1, T2 arg2, T3 arg3) { + // Use Arg() object to record type information and then copy arguments to an + // array to make it easier to iterate over them. + const internal::Arg arg_array[] = { arg0, arg1, arg2, arg3 }; + return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); +} + +template +ssize_t SafeSNPrintf(char* buf, size_t N, const char* fmt, + T0 arg0, T1 arg1, T2 arg2) { + // Use Arg() object to record type information and then copy arguments to an + // array to make it easier to iterate over them. + const internal::Arg arg_array[] = { arg0, arg1, arg2 }; + return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); +} + +template +ssize_t SafeSPrintf(char (&buf)[N], const char* fmt, T0 arg0, T1 arg1, + T2 arg2) { + // Use Arg() object to record type information and then copy arguments to an + // array to make it easier to iterate over them. + const internal::Arg arg_array[] = { arg0, arg1, arg2 }; + return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); +} + +template +ssize_t SafeSNPrintf(char* buf, size_t N, const char* fmt, T0 arg0, T1 arg1) { + // Use Arg() object to record type information and then copy arguments to an + // array to make it easier to iterate over them. + const internal::Arg arg_array[] = { arg0, arg1 }; + return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); +} + +template +ssize_t SafeSPrintf(char (&buf)[N], const char* fmt, T0 arg0, T1 arg1) { + // Use Arg() object to record type information and then copy arguments to an + // array to make it easier to iterate over them. + const internal::Arg arg_array[] = { arg0, arg1 }; + return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); +} + +template +ssize_t SafeSNPrintf(char* buf, size_t N, const char* fmt, T0 arg0) { + // Use Arg() object to record type information and then copy arguments to an + // array to make it easier to iterate over them. + const internal::Arg arg_array[] = { arg0 }; + return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); +} + +template +ssize_t SafeSPrintf(char (&buf)[N], const char* fmt, T0 arg0) { + // Use Arg() object to record type information and then copy arguments to an + // array to make it easier to iterate over them. + const internal::Arg arg_array[] = { arg0 }; + return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); +} + +// Fast-path when we don't actually need to substitute any arguments. +BUTIL_EXPORT ssize_t SafeSNPrintf(char* buf, size_t N, const char* fmt); +template +inline ssize_t SafeSPrintf(char (&buf)[N], const char* fmt) { + return SafeSNPrintf(buf, N, fmt); +} + +} // namespace strings +} // namespace butil + +#endif // BUTIL_STRINGS_SAFE_SPRINTF_H_ diff --git a/src/butil/strings/string16.cc b/src/butil/strings/string16.cc new file mode 100644 index 0000000..34524b2 --- /dev/null +++ b/src/butil/strings/string16.cc @@ -0,0 +1,82 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/strings/string16.h" + +#if defined(WCHAR_T_IS_UTF16) + +#error This file should not be used on 2-byte wchar_t systems +// If this winds up being needed on 2-byte wchar_t systems, either the +// definitions below can be used, or the host system's wide character +// functions like wmemcmp can be wrapped. + +#elif defined(WCHAR_T_IS_UTF32) + +#include + +#include "butil/strings/utf_string_conversions.h" + +namespace butil { + +int c16memcmp(const char16* s1, const char16* s2, size_t n) { + // We cannot call memcmp because that changes the semantics. + while (n-- > 0) { + if (*s1 != *s2) { + // We cannot use (*s1 - *s2) because char16 is unsigned. + return ((*s1 < *s2) ? -1 : 1); + } + ++s1; + ++s2; + } + return 0; +} + +size_t c16len(const char16* s) { + const char16 *s_orig = s; + while (*s) { + ++s; + } + return s - s_orig; +} + +const char16* c16memchr(const char16* s, char16 c, size_t n) { + while (n-- > 0) { + if (*s == c) { + return s; + } + ++s; + } + return 0; +} + +char16* c16memmove(char16* s1, const char16* s2, size_t n) { + return static_cast(memmove(s1, s2, n * sizeof(char16))); +} + +char16* c16memcpy(char16* s1, const char16* s2, size_t n) { + return static_cast(memcpy(s1, s2, n * sizeof(char16))); +} + +char16* c16memset(char16* s, char16 c, size_t n) { + char16 *s_orig = s; + while (n-- > 0) { + *s = c; + ++s; + } + return s_orig; +} + +std::ostream& operator<<(std::ostream& out, const string16& str) { + return out << UTF16ToUTF8(str); +} + +void PrintTo(const string16& str, std::ostream* out) { + *out << str; +} + +} // namespace butil + +template class std::basic_string; + +#endif // WCHAR_T_IS_UTF32 diff --git a/src/butil/strings/string16.h b/src/butil/strings/string16.h new file mode 100644 index 0000000..98067a6 --- /dev/null +++ b/src/butil/strings/string16.h @@ -0,0 +1,184 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_STRINGS_STRING16_H_ +#define BUTIL_STRINGS_STRING16_H_ + +// WHAT: +// A version of std::basic_string that provides 2-byte characters even when +// wchar_t is not implemented as a 2-byte type. You can access this class as +// string16. We also define char16, which string16 is based upon. +// +// WHY: +// On Windows, wchar_t is 2 bytes, and it can conveniently handle UTF-16/UCS-2 +// data. Plenty of existing code operates on strings encoded as UTF-16. +// +// On many other platforms, sizeof(wchar_t) is 4 bytes by default. We can make +// it 2 bytes by using the GCC flag -fshort-wchar. But then std::wstring fails +// at run time, because it calls some functions (like wcslen) that come from +// the system's native C library -- which was built with a 4-byte wchar_t! +// It's wasteful to use 4-byte wchar_t strings to carry UTF-16 data, and it's +// entirely improper on those systems where the encoding of wchar_t is defined +// as UTF-32. +// +// Here, we define string16, which is similar to std::wstring but replaces all +// libc functions with custom, 2-byte-char compatible routines. It is capable +// of carrying UTF-16-encoded data. + +#include +#include + +#include "butil/base_export.h" +#include "butil/basictypes.h" + +#if defined(WCHAR_T_IS_UTF16) + +namespace butil { + +typedef wchar_t char16; +typedef std::wstring string16; +typedef std::char_traits string16_char_traits; + +} // namespace butil + +#elif defined(WCHAR_T_IS_UTF32) + +namespace butil { + +typedef uint16_t char16; + +// char16 versions of the functions required by string16_char_traits; these +// are based on the wide character functions of similar names ("w" or "wcs" +// instead of "c16"). +BUTIL_EXPORT int c16memcmp(const char16* s1, const char16* s2, size_t n); +BUTIL_EXPORT size_t c16len(const char16* s); +BUTIL_EXPORT const char16* c16memchr(const char16* s, char16 c, size_t n); +BUTIL_EXPORT char16* c16memmove(char16* s1, const char16* s2, size_t n); +BUTIL_EXPORT char16* c16memcpy(char16* s1, const char16* s2, size_t n); +BUTIL_EXPORT char16* c16memset(char16* s, char16 c, size_t n); + +struct string16_char_traits { + typedef char16 char_type; + typedef int int_type; + + // int_type needs to be able to hold each possible value of char_type, and in + // addition, the distinct value of eof(). + COMPILE_ASSERT(sizeof(int_type) > sizeof(char_type), unexpected_type_width); + + typedef std::streamoff off_type; + typedef mbstate_t state_type; + typedef std::fpos pos_type; + + static void assign(char_type& c1, const char_type& c2) { + c1 = c2; + } + + static bool eq(const char_type& c1, const char_type& c2) { + return c1 == c2; + } + static bool lt(const char_type& c1, const char_type& c2) { + return c1 < c2; + } + + static int compare(const char_type* s1, const char_type* s2, size_t n) { + return c16memcmp(s1, s2, n); + } + + static size_t length(const char_type* s) { + return c16len(s); + } + + static const char_type* find(const char_type* s, size_t n, + const char_type& a) { + return c16memchr(s, a, n); + } + + static char_type* move(char_type* s1, const char_type* s2, int_type n) { + return c16memmove(s1, s2, n); + } + + static char_type* copy(char_type* s1, const char_type* s2, size_t n) { + return c16memcpy(s1, s2, n); + } + + static char_type* assign(char_type* s, size_t n, char_type a) { + return c16memset(s, a, n); + } + + static int_type not_eof(const int_type& c) { + return eq_int_type(c, eof()) ? 0 : c; + } + + static char_type to_char_type(const int_type& c) { + return char_type(c); + } + + static int_type to_int_type(const char_type& c) { + return int_type(c); + } + + static bool eq_int_type(const int_type& c1, const int_type& c2) { + return c1 == c2; + } + + static int_type eof() { + return static_cast(EOF); + } +}; + +typedef std::basic_string string16; + +BUTIL_EXPORT extern std::ostream& operator<<(std::ostream& out, + const string16& str); + +// This is required by googletest to print a readable output on test failures. +BUTIL_EXPORT extern void PrintTo(const string16& str, std::ostream* out); + +} // namespace butil + +// The string class will be explicitly instantiated only once, in string16.cc. +// +// std::basic_string<> in GNU libstdc++ contains a static data member, +// _S_empty_rep_storage, to represent empty strings. When an operation such +// as assignment or destruction is performed on a string, causing its existing +// data member to be invalidated, it must not be freed if this static data +// member is being used. Otherwise, it counts as an attempt to free static +// (and not allocated) data, which is a memory error. +// +// Generally, due to C++ template magic, _S_empty_rep_storage will be marked +// as a coalesced symbol, meaning that the linker will combine multiple +// instances into a single one when generating output. +// +// If a string class is used by multiple shared libraries, a problem occurs. +// Each library will get its own copy of _S_empty_rep_storage. When strings +// are passed across a library boundary for alteration or destruction, memory +// errors will result. GNU libstdc++ contains a configuration option, +// --enable-fully-dynamic-string (_GLIBCXX_FULLY_DYNAMIC_STRING), which +// disables the static data member optimization, but it's a good optimization +// and non-STL code is generally at the mercy of the system's STL +// configuration. Fully-dynamic strings are not the default for GNU libstdc++ +// libstdc++ itself or for the libstdc++ installations on the systems we care +// about, such as Mac OS X and relevant flavors of Linux. +// +// See also http://gcc.gnu.org/bugzilla/show_bug.cgi?id=24196 . +// +// To avoid problems, string classes need to be explicitly instantiated only +// once, in exactly one library. All other string users see it via an "extern" +// declaration. This is precisely how GNU libstdc++ handles +// std::basic_string (string) and std::basic_string (wstring). +// +// This also works around a Mac OS X linker bug in ld64-85.2.1 (Xcode 3.1.2), +// in which the linker does not fully coalesce symbols when dead code +// stripping is enabled. This bug causes the memory errors described above +// to occur even when a std::basic_string<> does not cross shared library +// boundaries, such as in statically-linked executables. +// +// TODO(mark): File this bug with Apple and update this note with a bug number. + +extern template +class BUTIL_EXPORT std::basic_string; + +#endif // WCHAR_T_IS_UTF32 + +#endif // BUTIL_STRINGS_STRING16_H_ diff --git a/src/butil/strings/string_number_conversions.cc b/src/butil/strings/string_number_conversions.cc new file mode 100644 index 0000000..bcf3f49 --- /dev/null +++ b/src/butil/strings/string_number_conversions.cc @@ -0,0 +1,504 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/strings/string_number_conversions.h" + +#include +#include +#include +#include + +#include + +#include "butil/logging.h" +#include "butil/numerics/safe_conversions.h" // safe_abs +#include "butil/scoped_clear_errno.h" +#include "butil/strings/utf_string_conversions.h" +#include "butil/third_party/dmg_fp/dmg_fp.h" + +namespace butil { + +namespace { + +template +struct IntToStringT { + // This set of templates is very similar to the above templates, but + // for testing whether an integer is negative. + template + struct TestNegT {}; + template + struct TestNegT { + static bool TestNeg(INT2 value) { + // value is unsigned, and can never be negative. + return false; + } + }; + template + struct TestNegT { + static bool TestNeg(INT2 value) { + return value < 0; + } + }; + + static STR IntToString(INT value) { + // log10(2) ~= 0.3 bytes needed per bit or per byte log10(2**8) ~= 2.4. + // So round up to allocate 3 output characters per byte, plus 1 for '-'. + const int kOutputBufSize = 3 * sizeof(INT) + 1; + + // Allocate the whole string right away, we will right back to front, and + // then return the substr of what we ended up using. + STR outbuf(kOutputBufSize, 0); + + bool is_neg = TestNegT::TestNeg(value); + UINT res = safe_abs(value); + + typename STR::iterator it(outbuf.end()); + do { + --it; + DCHECK(it != outbuf.begin()); + *it = static_cast((res % 10) + '0'); + res /= 10; + } while (res != 0); + if (is_neg) { + --it; + DCHECK(it != outbuf.begin()); + *it = static_cast('-'); + } + return STR(it, outbuf.end()); + } +}; + +// Utility to convert a character to a digit in a given base +template class BaseCharToDigit { +}; + +// Faster specialization for bases <= 10 +template class BaseCharToDigit { + public: + static bool Convert(CHAR c, uint8_t* digit) { + if (c >= '0' && c < '0' + BASE) { + *digit = c - '0'; + return true; + } + return false; + } +}; + +// Specialization for bases where 10 < base <= 36 +template class BaseCharToDigit { + public: + static bool Convert(CHAR c, uint8_t* digit) { + if (c >= '0' && c <= '9') { + *digit = c - '0'; + } else if (c >= 'a' && c < 'a' + BASE - 10) { + *digit = c - 'a' + 10; + } else if (c >= 'A' && c < 'A' + BASE - 10) { + *digit = c - 'A' + 10; + } else { + return false; + } + return true; + } +}; + +template bool CharToDigit(CHAR c, uint8_t* digit) { + return BaseCharToDigit::Convert(c, digit); +} + +// There is an IsWhitespace for wchars defined in string_util.h, but it is +// locale independent, whereas the functions we are replacing were +// locale-dependent. TBD what is desired, but for the moment let's not introduce +// a change in behaviour. +template class WhitespaceHelper { +}; + +template<> class WhitespaceHelper { + public: + static bool Invoke(char c) { + return 0 != isspace(static_cast(c)); + } +}; + +template<> class WhitespaceHelper { + public: + static bool Invoke(char16 c) { + return 0 != iswspace(c); + } +}; + +template bool LocalIsWhitespace(CHAR c) { + return WhitespaceHelper::Invoke(c); +} + +// IteratorRangeToNumberTraits should provide: +// - a typedef for iterator_type, the iterator type used as input. +// - a typedef for value_type, the target numeric type. +// - static functions min, max (returning the minimum and maximum permitted +// values) +// - constant kBase, the base in which to interpret the input +template +class IteratorRangeToNumber { + public: + typedef IteratorRangeToNumberTraits traits; + typedef typename traits::iterator_type const_iterator; + typedef typename traits::value_type value_type; + + // Generalized iterator-range-to-number conversion. + // + static bool Invoke(const_iterator begin, + const_iterator end, + value_type* output) { + bool valid = true; + + while (begin != end && LocalIsWhitespace(*begin)) { + valid = false; + ++begin; + } + + if (begin != end && *begin == '-') { + if (!std::numeric_limits::is_signed) { + valid = false; + } else if (!Negative::Invoke(begin + 1, end, output)) { + valid = false; + } + } else { + if (begin != end && *begin == '+') { + ++begin; + } + if (!Positive::Invoke(begin, end, output)) { + valid = false; + } + } + + return valid; + } + + private: + // Sign provides: + // - a static function, CheckBounds, that determines whether the next digit + // causes an overflow/underflow + // - a static function, Increment, that appends the next digit appropriately + // according to the sign of the number being parsed. + template + class Base { + public: + static bool Invoke(const_iterator begin, const_iterator end, + typename traits::value_type* output) { + *output = 0; + + if (begin == end) { + return false; + } + + // Note: no performance difference was found when using template + // specialization to remove this check in bases other than 16 + if (traits::kBase == 16 && end - begin > 2 && *begin == '0' && + (*(begin + 1) == 'x' || *(begin + 1) == 'X')) { + begin += 2; + } + + for (const_iterator current = begin; current != end; ++current) { + uint8_t new_digit = 0; + + if (!CharToDigit(*current, &new_digit)) { + return false; + } + + if (current != begin) { + if (!Sign::CheckBounds(output, new_digit)) { + return false; + } + *output *= traits::kBase; + } + + Sign::Increment(new_digit, output); + } + return true; + } + }; + + class Positive : public Base { + public: + static bool CheckBounds(value_type* output, uint8_t new_digit) { + if (*output > static_cast(traits::max() / traits::kBase) || + (*output == static_cast(traits::max() / traits::kBase) && + new_digit > traits::max() % traits::kBase)) { + *output = traits::max(); + return false; + } + return true; + } + static void Increment(uint8_t increment, value_type* output) { + *output += increment; + } + }; + + class Negative : public Base { + public: + static bool CheckBounds(value_type* output, uint8_t new_digit) { + if (*output < traits::min() / traits::kBase || + (*output == traits::min() / traits::kBase && + new_digit > 0 - traits::min() % traits::kBase)) { + *output = traits::min(); + return false; + } + return true; + } + static void Increment(uint8_t increment, value_type* output) { + *output -= increment; + } + }; +}; + +template +class BaseIteratorRangeToNumberTraits { + public: + typedef ITERATOR iterator_type; + typedef VALUE value_type; + static value_type min() { + return std::numeric_limits::min(); + } + static value_type max() { + return std::numeric_limits::max(); + } + static const int kBase = BASE; +}; + +template +class BaseHexIteratorRangeToIntTraits + : public BaseIteratorRangeToNumberTraits { +}; + +template +class BaseHexIteratorRangeToUIntTraits + : public BaseIteratorRangeToNumberTraits { +}; + +template +class BaseHexIteratorRangeToInt64Traits + : public BaseIteratorRangeToNumberTraits { +}; + +template +class BaseHexIteratorRangeToUInt64Traits + : public BaseIteratorRangeToNumberTraits { +}; + +typedef BaseHexIteratorRangeToIntTraits + HexIteratorRangeToIntTraits; + +typedef BaseHexIteratorRangeToUIntTraits + HexIteratorRangeToUIntTraits; + +typedef BaseHexIteratorRangeToInt64Traits + HexIteratorRangeToInt64Traits; + +typedef BaseHexIteratorRangeToUInt64Traits + HexIteratorRangeToUInt64Traits; + +template +bool HexStringToBytesT(const STR& input, std::vector* output) { + DCHECK_EQ(output->size(), 0u); + size_t count = input.size(); + if (count == 0 || (count % 2) != 0) + return false; + for (uintptr_t i = 0; i < count / 2; ++i) { + uint8_t msb = 0; // most significant 4 bits + uint8_t lsb = 0; // least significant 4 bits + if (!CharToDigit<16>(input[i * 2], &msb) || + !CharToDigit<16>(input[i * 2 + 1], &lsb)) + return false; + output->push_back((msb << 4) | lsb); + } + return true; +} + +template +class StringPieceToNumberTraits + : public BaseIteratorRangeToNumberTraits { +}; + +template +bool StringToIntImpl(const StringPiece& input, VALUE* output) { + return IteratorRangeToNumber >::Invoke( + input.begin(), input.end(), output); +} + +template +class StringPiece16ToNumberTraits + : public BaseIteratorRangeToNumberTraits { +}; + +template +bool String16ToIntImpl(const StringPiece16& input, VALUE* output) { + return IteratorRangeToNumber >::Invoke( + input.begin(), input.end(), output); +} + +} // namespace + +std::string IntToString(int value) { + return IntToStringT:: + IntToString(value); +} + +string16 IntToString16(int value) { + return IntToStringT:: + IntToString(value); +} + +std::string UintToString(unsigned int value) { + return IntToStringT:: + IntToString(value); +} + +string16 UintToString16(unsigned int value) { + return IntToStringT:: + IntToString(value); +} + +std::string Int64ToString(int64_t value) { + return IntToStringT::IntToString(value); +} + +string16 Int64ToString16(int64_t value) { + return IntToStringT::IntToString(value); +} + +std::string Uint64ToString(uint64_t value) { + return IntToStringT::IntToString(value); +} + +string16 Uint64ToString16(uint64_t value) { + return IntToStringT::IntToString(value); +} + +std::string SizeTToString(size_t value) { + return IntToStringT::IntToString(value); +} + +string16 SizeTToString16(size_t value) { + return IntToStringT::IntToString(value); +} + +std::string DoubleToString(double value) { + // According to g_fmt.cc, it is sufficient to declare a buffer of size 32. + char buffer[32]; + dmg_fp::g_fmt(buffer, value); + return std::string(buffer); +} + +bool StringToInt(const StringPiece& input, int* output) { + return StringToIntImpl(input, output); +} + +bool StringToInt(const StringPiece16& input, int* output) { + return String16ToIntImpl(input, output); +} + +bool StringToUint(const StringPiece& input, unsigned* output) { + return StringToIntImpl(input, output); +} + +bool StringToUint(const StringPiece16& input, unsigned* output) { + return String16ToIntImpl(input, output); +} + +bool StringToInt64(const StringPiece& input, int64_t* output) { + return StringToIntImpl(input, output); +} + +bool StringToInt64(const StringPiece16& input, int64_t* output) { + return String16ToIntImpl(input, output); +} + +bool StringToUint64(const StringPiece& input, uint64_t* output) { + return StringToIntImpl(input, output); +} + +bool StringToUint64(const StringPiece16& input, uint64_t* output) { + return String16ToIntImpl(input, output); +} + +bool StringToSizeT(const StringPiece& input, size_t* output) { + return StringToIntImpl(input, output); +} + +bool StringToSizeT(const StringPiece16& input, size_t* output) { + return String16ToIntImpl(input, output); +} + +bool StringToDouble(const std::string& input, double* output) { + // Thread-safe? It is on at least Mac, Linux, and Windows. + ScopedClearErrno clear_errno; + + char* endptr = NULL; + *output = dmg_fp::strtod(input.c_str(), &endptr); + + // Cases to return false: + // - If errno is ERANGE, there was an overflow or underflow. + // - If the input string is empty, there was nothing to parse. + // - If endptr does not point to the end of the string, there are either + // characters remaining in the string after a parsed number, or the string + // does not begin with a parseable number. endptr is compared to the + // expected end given the string's stated length to correctly catch cases + // where the string contains embedded NUL characters. + // - If the first character is a space, there was leading whitespace + return errno == 0 && + !input.empty() && + input.c_str() + input.length() == endptr && + !isspace(input[0]); +} + +// Note: if you need to add String16ToDouble, first ask yourself if it's +// really necessary. If it is, probably the best implementation here is to +// convert to 8-bit and then use the 8-bit version. + +// Note: if you need to add an iterator range version of StringToDouble, first +// ask yourself if it's really necessary. If it is, probably the best +// implementation here is to instantiate a string and use the string version. + +std::string HexEncode(const void* bytes, size_t size) { + static const char kHexChars[] = "0123456789ABCDEF"; + + // Each input byte creates two output hex characters. + std::string ret(size * 2, '\0'); + + for (size_t i = 0; i < size; ++i) { + char b = reinterpret_cast(bytes)[i]; + ret[(i * 2)] = kHexChars[(b >> 4) & 0xf]; + ret[(i * 2) + 1] = kHexChars[b & 0xf]; + } + return ret; +} + +bool HexStringToInt(const StringPiece& input, int* output) { + return IteratorRangeToNumber::Invoke( + input.begin(), input.end(), output); +} + +bool HexStringToUInt(const StringPiece& input, uint32_t* output) { + return IteratorRangeToNumber::Invoke( + input.begin(), input.end(), output); +} + +bool HexStringToInt64(const StringPiece& input, int64_t* output) { + return IteratorRangeToNumber::Invoke( + input.begin(), input.end(), output); +} + +bool HexStringToUInt64(const StringPiece& input, uint64_t* output) { + return IteratorRangeToNumber::Invoke( + input.begin(), input.end(), output); +} + +bool HexStringToBytes(const std::string& input, std::vector* output) { + return HexStringToBytesT(input, output); +} + +} // namespace butil diff --git a/src/butil/strings/string_number_conversions.h b/src/butil/strings/string_number_conversions.h new file mode 100644 index 0000000..1ef3ed2 --- /dev/null +++ b/src/butil/strings/string_number_conversions.h @@ -0,0 +1,131 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_STRINGS_STRING_NUMBER_CONVERSIONS_H_ +#define BUTIL_STRINGS_STRING_NUMBER_CONVERSIONS_H_ + +#include +#include + +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/strings/string16.h" +#include "butil/strings/string_piece.h" + +// ---------------------------------------------------------------------------- +// IMPORTANT MESSAGE FROM YOUR SPONSOR +// +// This file contains no "wstring" variants. New code should use string16. If +// you need to make old code work, use the UTF8 version and convert. Please do +// not add wstring variants. +// +// Please do not add "convenience" functions for converting strings to integers +// that return the value and ignore success/failure. That encourages people to +// write code that doesn't properly handle the error conditions. +// ---------------------------------------------------------------------------- + +namespace butil { + +// Number -> string conversions ------------------------------------------------ + +BUTIL_EXPORT std::string IntToString(int value); +BUTIL_EXPORT string16 IntToString16(int value); + +BUTIL_EXPORT std::string UintToString(unsigned value); +BUTIL_EXPORT string16 UintToString16(unsigned value); + +BUTIL_EXPORT std::string Int64ToString(int64_t value); +BUTIL_EXPORT string16 Int64ToString16(int64_t value); + +BUTIL_EXPORT std::string Uint64ToString(uint64_t value); +BUTIL_EXPORT string16 Uint64ToString16(uint64_t value); + +BUTIL_EXPORT std::string SizeTToString(size_t value); +BUTIL_EXPORT string16 SizeTToString16(size_t value); + +// DoubleToString converts the double to a string format that ignores the +// locale. If you want to use locale specific formatting, use ICU. +BUTIL_EXPORT std::string DoubleToString(double value); + +// String -> number conversions ------------------------------------------------ + +// Perform a best-effort conversion of the input string to a numeric type, +// setting |*output| to the result of the conversion. Returns true for +// "perfect" conversions; returns false in the following cases: +// - Overflow. |*output| will be set to the maximum value supported +// by the data type. +// - Underflow. |*output| will be set to the minimum value supported +// by the data type. +// - Trailing characters in the string after parsing the number. |*output| +// will be set to the value of the number that was parsed. +// - Leading whitespace in the string before parsing the number. |*output| will +// be set to the value of the number that was parsed. +// - No characters parseable as a number at the beginning of the string. +// |*output| will be set to 0. +// - Empty string. |*output| will be set to 0. +BUTIL_EXPORT bool StringToInt(const StringPiece& input, int* output); +BUTIL_EXPORT bool StringToInt(const StringPiece16& input, int* output); + +BUTIL_EXPORT bool StringToUint(const StringPiece& input, unsigned* output); +BUTIL_EXPORT bool StringToUint(const StringPiece16& input, unsigned* output); + +BUTIL_EXPORT bool StringToInt64(const StringPiece& input, int64_t* output); +BUTIL_EXPORT bool StringToInt64(const StringPiece16& input, int64_t* output); + +BUTIL_EXPORT bool StringToUint64(const StringPiece& input, uint64_t* output); +BUTIL_EXPORT bool StringToUint64(const StringPiece16& input, uint64_t* output); + +BUTIL_EXPORT bool StringToSizeT(const StringPiece& input, size_t* output); +BUTIL_EXPORT bool StringToSizeT(const StringPiece16& input, size_t* output); + +// For floating-point conversions, only conversions of input strings in decimal +// form are defined to work. Behavior with strings representing floating-point +// numbers in hexadecimal, and strings representing non-fininte values (such as +// NaN and inf) is undefined. Otherwise, these behave the same as the integral +// variants. This expects the input string to NOT be specific to the locale. +// If your input is locale specific, use ICU to read the number. +BUTIL_EXPORT bool StringToDouble(const std::string& input, double* output); + +// Hex encoding ---------------------------------------------------------------- + +// Returns a hex string representation of a binary buffer. The returned hex +// string will be in upper case. This function does not check if |size| is +// within reasonable limits since it's written with trusted data in mind. If +// you suspect that the data you want to format might be large, the absolute +// max size for |size| should be is +// std::numeric_limits::max() / 2 +BUTIL_EXPORT std::string HexEncode(const void* bytes, size_t size); + +// Best effort conversion, see StringToInt above for restrictions. +// Will only successful parse hex values that will fit into |output|, i.e. +// -0x80000000 < |input| < 0x7FFFFFFF. +BUTIL_EXPORT bool HexStringToInt(const StringPiece& input, int* output); + +// Best effort conversion, see StringToInt above for restrictions. +// Will only successful parse hex values that will fit into |output|, i.e. +// 0x00000000 < |input| < 0xFFFFFFFF. +// The string is not required to start with 0x. +BUTIL_EXPORT bool HexStringToUInt(const StringPiece& input, uint32_t* output); + +// Best effort conversion, see StringToInt above for restrictions. +// Will only successful parse hex values that will fit into |output|, i.e. +// -0x8000000000000000 < |input| < 0x7FFFFFFFFFFFFFFF. +BUTIL_EXPORT bool HexStringToInt64(const StringPiece& input, int64_t* output); + +// Best effort conversion, see StringToInt above for restrictions. +// Will only successful parse hex values that will fit into |output|, i.e. +// 0x0000000000000000 < |input| < 0xFFFFFFFFFFFFFFFF. +// The string is not required to start with 0x. +BUTIL_EXPORT bool HexStringToUInt64(const StringPiece& input, uint64_t* output); + +// Similar to the previous functions, except that output is a vector of bytes. +// |*output| will contain as many bytes as were successfully parsed prior to the +// error. There is no overflow, but input.size() must be evenly divisible by 2. +// Leading 0x or +/- are not allowed. +BUTIL_EXPORT bool HexStringToBytes(const std::string& input, + std::vector* output); + +} // namespace butil + +#endif // BUTIL_STRINGS_STRING_NUMBER_CONVERSIONS_H_ diff --git a/src/butil/strings/string_piece.cc b/src/butil/strings/string_piece.cc new file mode 100644 index 0000000..2d24924 --- /dev/null +++ b/src/butil/strings/string_piece.cc @@ -0,0 +1,437 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Copied from strings/stringpiece.cc with modifications + +#include "butil/strings/string_piece.h" + +#include +#include + +namespace butil { +namespace { + +// For each character in characters_wanted, sets the index corresponding +// to the ASCII code of that character to 1 in table. This is used by +// the find_.*_of methods below to tell whether or not a character is in +// the lookup table in constant time. +// The argument `table' must be an array that is large enough to hold all +// the possible values of an unsigned char. Thus it should be be declared +// as follows: +// bool table[UCHAR_MAX + 1] +inline void BuildLookupTable(const StringPiece& characters_wanted, + bool* table) { + const size_t length = characters_wanted.length(); + const char* const data = characters_wanted.data(); + for (size_t i = 0; i < length; ++i) { + table[static_cast(data[i])] = true; + } +} + +} // namespace + +// MSVC doesn't like complex extern templates and DLLs. +#if !defined(COMPILER_MSVC) +template class BasicStringPiece; +template class BasicStringPiece; +#endif + +bool operator==(const StringPiece& x, const StringPiece& y) { + if (x.size() != y.size()) + return false; + + return StringPiece::wordmemcmp(x.data(), y.data(), x.size()) == 0; +} + +std::ostream& operator<<(std::ostream& o, const StringPiece& piece) { + o.write(piece.data(), static_cast(piece.size())); + return o; +} + +namespace internal { + +template +void CopyToStringT(const BasicStringPiece& self, STR* target) { + if (self.empty()) + target->clear(); + else + target->assign(self.data(), self.size()); +} + +void CopyToString(const StringPiece& self, std::string* target) { + CopyToStringT(self, target); +} + +void CopyToString(const StringPiece16& self, string16* target) { + CopyToStringT(self, target); +} + +template +void AppendToStringT(const BasicStringPiece& self, STR* target) { + if (!self.empty()) + target->append(self.data(), self.size()); +} + +void AppendToString(const StringPiece& self, std::string* target) { + AppendToStringT(self, target); +} + +void AppendToString(const StringPiece16& self, string16* target) { + AppendToStringT(self, target); +} + +template +size_t copyT(const BasicStringPiece& self, + typename STR::value_type* buf, + size_t n, + size_t pos) { + size_t ret = std::min(self.size() - pos, n); + memcpy(buf, self.data() + pos, ret * sizeof(typename STR::value_type)); + return ret; +} + +size_t copy(const StringPiece& self, char* buf, size_t n, size_t pos) { + return copyT(self, buf, n, pos); +} + +size_t copy(const StringPiece16& self, char16* buf, size_t n, size_t pos) { + return copyT(self, buf, n, pos); +} + +template +size_t findT(const BasicStringPiece& self, + const BasicStringPiece& s, + size_t pos) { + if (pos > self.size()) + return BasicStringPiece::npos; + + typename BasicStringPiece::const_iterator result = + std::search(self.begin() + pos, self.end(), s.begin(), s.end()); + const size_t xpos = + static_cast(result - self.begin()); + return xpos + s.size() <= self.size() ? xpos : BasicStringPiece::npos; +} + +size_t find(const StringPiece& self, const StringPiece& s, size_t pos) { + return findT(self, s, pos); +} + +size_t find(const StringPiece16& self, const StringPiece16& s, size_t pos) { + return findT(self, s, pos); +} + +template +size_t findT(const BasicStringPiece& self, + typename STR::value_type c, + size_t pos) { + if (pos >= self.size()) + return BasicStringPiece::npos; + + typename BasicStringPiece::const_iterator result = + std::find(self.begin() + pos, self.end(), c); + return result != self.end() ? + static_cast(result - self.begin()) : BasicStringPiece::npos; +} + +size_t find(const StringPiece& self, char c, size_t pos) { + return findT(self, c, pos); +} + +size_t find(const StringPiece16& self, char16 c, size_t pos) { + return findT(self, c, pos); +} + +template +size_t rfindT(const BasicStringPiece& self, + const BasicStringPiece& s, + size_t pos) { + if (self.size() < s.size()) + return BasicStringPiece::npos; + + if (s.empty()) + return std::min(self.size(), pos); + + typename BasicStringPiece::const_iterator last = + self.begin() + std::min(self.size() - s.size(), pos) + s.size(); + typename BasicStringPiece::const_iterator result = + std::find_end(self.begin(), last, s.begin(), s.end()); + return result != last ? + static_cast(result - self.begin()) : BasicStringPiece::npos; +} + +size_t rfind(const StringPiece& self, const StringPiece& s, size_t pos) { + return rfindT(self, s, pos); +} + +size_t rfind(const StringPiece16& self, const StringPiece16& s, size_t pos) { + return rfindT(self, s, pos); +} + +template +size_t rfindT(const BasicStringPiece& self, + typename STR::value_type c, + size_t pos) { + if (self.size() == 0) + return BasicStringPiece::npos; + + for (size_t i = std::min(pos, self.size() - 1); ; + --i) { + if (self.data()[i] == c) + return i; + if (i == 0) + break; + } + return BasicStringPiece::npos; +} + +size_t rfind(const StringPiece& self, char c, size_t pos) { + return rfindT(self, c, pos); +} + +size_t rfind(const StringPiece16& self, char16 c, size_t pos) { + return rfindT(self, c, pos); +} + +// 8-bit version using lookup table. +size_t find_first_of(const StringPiece& self, + const StringPiece& s, + size_t pos) { + if (self.size() == 0 || s.size() == 0) + return StringPiece::npos; + + // Avoid the cost of BuildLookupTable() for a single-character search. + if (s.size() == 1) + return find(self, s.data()[0], pos); + + bool lookup[UCHAR_MAX + 1] = { false }; + BuildLookupTable(s, lookup); + for (size_t i = pos; i < self.size(); ++i) { + if (lookup[static_cast(self.data()[i])]) { + return i; + } + } + return StringPiece::npos; +} + +// 16-bit brute force version. +size_t find_first_of(const StringPiece16& self, + const StringPiece16& s, + size_t pos) { + StringPiece16::const_iterator found = + std::find_first_of(self.begin() + pos, self.end(), s.begin(), s.end()); + if (found == self.end()) + return StringPiece16::npos; + return found - self.begin(); +} + +// 8-bit version using lookup table. +size_t find_first_not_of(const StringPiece& self, + const StringPiece& s, + size_t pos) { + if (self.size() == 0) + return StringPiece::npos; + + if (s.size() == 0) + return 0; + + // Avoid the cost of BuildLookupTable() for a single-character search. + if (s.size() == 1) + return find_first_not_of(self, s.data()[0], pos); + + bool lookup[UCHAR_MAX + 1] = { false }; + BuildLookupTable(s, lookup); + for (size_t i = pos; i < self.size(); ++i) { + if (!lookup[static_cast(self.data()[i])]) { + return i; + } + } + return StringPiece::npos; +} + +// 16-bit brute-force version. +BUTIL_EXPORT size_t find_first_not_of(const StringPiece16& self, + const StringPiece16& s, + size_t pos) { + if (self.size() == 0) + return StringPiece16::npos; + + for (size_t self_i = pos; self_i < self.size(); ++self_i) { + bool found = false; + for (size_t s_i = 0; s_i < s.size(); ++s_i) { + if (self[self_i] == s[s_i]) { + found = true; + break; + } + } + if (!found) + return self_i; + } + return StringPiece16::npos; +} + +template +size_t find_first_not_ofT(const BasicStringPiece& self, + typename STR::value_type c, + size_t pos) { + if (self.size() == 0) + return BasicStringPiece::npos; + + for (; pos < self.size(); ++pos) { + if (self.data()[pos] != c) { + return pos; + } + } + return BasicStringPiece::npos; +} + +size_t find_first_not_of(const StringPiece& self, + char c, + size_t pos) { + return find_first_not_ofT(self, c, pos); +} + +size_t find_first_not_of(const StringPiece16& self, + char16 c, + size_t pos) { + return find_first_not_ofT(self, c, pos); +} + +// 8-bit version using lookup table. +size_t find_last_of(const StringPiece& self, const StringPiece& s, size_t pos) { + if (self.size() == 0 || s.size() == 0) + return StringPiece::npos; + + // Avoid the cost of BuildLookupTable() for a single-character search. + if (s.size() == 1) + return rfind(self, s.data()[0], pos); + + bool lookup[UCHAR_MAX + 1] = { false }; + BuildLookupTable(s, lookup); + for (size_t i = std::min(pos, self.size() - 1); ; --i) { + if (lookup[static_cast(self.data()[i])]) + return i; + if (i == 0) + break; + } + return StringPiece::npos; +} + +// 16-bit brute-force version. +size_t find_last_of(const StringPiece16& self, + const StringPiece16& s, + size_t pos) { + if (self.size() == 0) + return StringPiece16::npos; + + for (size_t self_i = std::min(pos, self.size() - 1); ; + --self_i) { + for (size_t s_i = 0; s_i < s.size(); s_i++) { + if (self.data()[self_i] == s[s_i]) + return self_i; + } + if (self_i == 0) + break; + } + return StringPiece16::npos; +} + +// 8-bit version using lookup table. +size_t find_last_not_of(const StringPiece& self, + const StringPiece& s, + size_t pos) { + if (self.size() == 0) + return StringPiece::npos; + + size_t i = std::min(pos, self.size() - 1); + if (s.size() == 0) + return i; + + // Avoid the cost of BuildLookupTable() for a single-character search. + if (s.size() == 1) + return find_last_not_of(self, s.data()[0], pos); + + bool lookup[UCHAR_MAX + 1] = { false }; + BuildLookupTable(s, lookup); + for (; ; --i) { + if (!lookup[static_cast(self.data()[i])]) + return i; + if (i == 0) + break; + } + return StringPiece::npos; +} + +// 16-bit brute-force version. +size_t find_last_not_of(const StringPiece16& self, + const StringPiece16& s, + size_t pos) { + if (self.size() == 0) + return StringPiece::npos; + + for (size_t self_i = std::min(pos, self.size() - 1); ; --self_i) { + bool found = false; + for (size_t s_i = 0; s_i < s.size(); s_i++) { + if (self.data()[self_i] == s[s_i]) { + found = true; + break; + } + } + if (!found) + return self_i; + if (self_i == 0) + break; + } + return StringPiece16::npos; +} + +template +size_t find_last_not_ofT(const BasicStringPiece& self, + typename STR::value_type c, + size_t pos) { + if (self.size() == 0) + return BasicStringPiece::npos; + + for (size_t i = std::min(pos, self.size() - 1); ; --i) { + if (self.data()[i] != c) + return i; + if (i == 0) + break; + } + return BasicStringPiece::npos; +} + +size_t find_last_not_of(const StringPiece& self, + char c, + size_t pos) { + return find_last_not_ofT(self, c, pos); +} + +size_t find_last_not_of(const StringPiece16& self, + char16 c, + size_t pos) { + return find_last_not_ofT(self, c, pos); +} + +template +BasicStringPiece substrT(const BasicStringPiece& self, + size_t pos, + size_t n) { + if (pos > self.size()) pos = self.size(); + if (n > self.size() - pos) n = self.size() - pos; + return BasicStringPiece(self.data() + pos, n); +} + +StringPiece substr(const StringPiece& self, + size_t pos, + size_t n) { + return substrT(self, pos, n); +} + +StringPiece16 substr(const StringPiece16& self, + size_t pos, + size_t n) { + return substrT(self, pos, n); +} + +} // namespace internal +} // namespace butil diff --git a/src/butil/strings/string_piece.h b/src/butil/strings/string_piece.h new file mode 100644 index 0000000..dbfd69e --- /dev/null +++ b/src/butil/strings/string_piece.h @@ -0,0 +1,519 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Copied from strings/stringpiece.h with modifications +// +// A string-like object that points to a sized piece of memory. +// +// You can use StringPiece as a function or method parameter. A StringPiece +// parameter can receive a double-quoted string literal argument, a "const +// char*" argument, a string argument, or a StringPiece argument with no data +// copying. Systematic use of StringPiece for arguments reduces data +// copies and strlen() calls. +// +// Prefer passing StringPieces by value: +// void MyFunction(StringPiece arg); +// If circumstances require, you may also pass by const reference: +// void MyFunction(const StringPiece& arg); // not preferred +// Both of these have the same lifetime semantics. Passing by value +// generates slightly smaller code. For more discussion, Googlers can see +// the thread go/stringpiecebyvalue on c-users. +// +// StringPiece16 is similar to StringPiece but for butil::string16 instead of +// std::string. We do not define as large of a subset of the STL functions +// from basic_string as in StringPiece, but this can be changed if these +// functions (find, find_first_of, etc.) are found to be useful in this context. +// + +#ifndef BUTIL_STRINGS_STRING_PIECE_H_ +#define BUTIL_STRINGS_STRING_PIECE_H_ + +#include + +#include +#include +#if __cplusplus >= 201703L +#include +#endif // __cplusplus >= 201703L + +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/containers/hash_tables.h" +#include "butil/strings/string16.h" + +namespace butil { + +template class BasicStringPiece; +typedef BasicStringPiece StringPiece; +typedef BasicStringPiece StringPiece16; + +// internal -------------------------------------------------------------------- + +// Many of the StringPiece functions use different implementations for the +// 8-bit and 16-bit versions, and we don't want lots of template expansions in +// this (very common) header that will slow down compilation. +// +// So here we define overloaded functions called by the StringPiece template. +// For those that share an implementation, the two versions will expand to a +// template internal to the .cc file. +namespace internal { + +BUTIL_EXPORT void CopyToString(const StringPiece& self, std::string* target); +BUTIL_EXPORT void CopyToString(const StringPiece16& self, string16* target); + +BUTIL_EXPORT void AppendToString(const StringPiece& self, std::string* target); +BUTIL_EXPORT void AppendToString(const StringPiece16& self, string16* target); + +BUTIL_EXPORT size_t copy(const StringPiece& self, + char* buf, + size_t n, + size_t pos); +BUTIL_EXPORT size_t copy(const StringPiece16& self, + char16* buf, + size_t n, + size_t pos); + +BUTIL_EXPORT size_t find(const StringPiece& self, + const StringPiece& s, + size_t pos); +BUTIL_EXPORT size_t find(const StringPiece16& self, + const StringPiece16& s, + size_t pos); +BUTIL_EXPORT size_t find(const StringPiece& self, + char c, + size_t pos); +BUTIL_EXPORT size_t find(const StringPiece16& self, + char16 c, + size_t pos); + +BUTIL_EXPORT size_t rfind(const StringPiece& self, + const StringPiece& s, + size_t pos); +BUTIL_EXPORT size_t rfind(const StringPiece16& self, + const StringPiece16& s, + size_t pos); +BUTIL_EXPORT size_t rfind(const StringPiece& self, + char c, + size_t pos); +BUTIL_EXPORT size_t rfind(const StringPiece16& self, + char16 c, + size_t pos); + +BUTIL_EXPORT size_t find_first_of(const StringPiece& self, + const StringPiece& s, + size_t pos); +BUTIL_EXPORT size_t find_first_of(const StringPiece16& self, + const StringPiece16& s, + size_t pos); + +BUTIL_EXPORT size_t find_first_not_of(const StringPiece& self, + const StringPiece& s, + size_t pos); +BUTIL_EXPORT size_t find_first_not_of(const StringPiece16& self, + const StringPiece16& s, + size_t pos); +BUTIL_EXPORT size_t find_first_not_of(const StringPiece& self, + char c, + size_t pos); +BUTIL_EXPORT size_t find_first_not_of(const StringPiece16& self, + char16 c, + size_t pos); + +BUTIL_EXPORT size_t find_last_of(const StringPiece& self, + const StringPiece& s, + size_t pos); +BUTIL_EXPORT size_t find_last_of(const StringPiece16& self, + const StringPiece16& s, + size_t pos); +BUTIL_EXPORT size_t find_last_of(const StringPiece& self, + char c, + size_t pos); +BUTIL_EXPORT size_t find_last_of(const StringPiece16& self, + char16 c, + size_t pos); + +BUTIL_EXPORT size_t find_last_not_of(const StringPiece& self, + const StringPiece& s, + size_t pos); +BUTIL_EXPORT size_t find_last_not_of(const StringPiece16& self, + const StringPiece16& s, + size_t pos); +BUTIL_EXPORT size_t find_last_not_of(const StringPiece16& self, + char16 c, + size_t pos); +BUTIL_EXPORT size_t find_last_not_of(const StringPiece& self, + char c, + size_t pos); + +BUTIL_EXPORT StringPiece substr(const StringPiece& self, + size_t pos, + size_t n); +BUTIL_EXPORT StringPiece16 substr(const StringPiece16& self, + size_t pos, + size_t n); + +} // namespace internal + +// BasicStringPiece ------------------------------------------------------------ + +// Defines the types, methods, operators, and data members common to both +// StringPiece and StringPiece16. Do not refer to this class directly, but +// rather to StringPiece, or StringPiece16. +// +// This is templatized by string class type rather than character type, so +// BasicStringPiece or BasicStringPiece. +template class BasicStringPiece { + public: + // Standard STL container boilerplate. + typedef size_t size_type; + typedef typename STRING_TYPE::value_type value_type; + typedef const value_type* pointer; + typedef const value_type* const_pointer; + typedef const value_type& reference; + typedef const value_type& const_reference; + typedef ptrdiff_t difference_type; + typedef const value_type* const_iterator; + typedef std::reverse_iterator const_reverse_iterator; + + static const size_type npos; + + public: + // We provide non-explicit singleton constructors so users can pass + // in a "const char*" or a "string" wherever a "StringPiece" is + // expected (likewise for char16, string16, StringPiece16). + BasicStringPiece() : ptr_(NULL), length_(0) {} + BasicStringPiece(const value_type* str) + : ptr_(str), + length_((str == NULL) ? 0 : STRING_TYPE::traits_type::length(str)) {} +#if __cplusplus >= 201703L + BasicStringPiece( + const std::basic_string_view& str) + : ptr_(str.data()), length_(str.size()) {} +#endif // __cplusplus >= 201703L + BasicStringPiece(const STRING_TYPE& str) + : ptr_(str.data()), length_(str.size()) {} + BasicStringPiece(const value_type* offset, size_type len) + : ptr_(offset), length_(len) {} + BasicStringPiece(const BasicStringPiece& str, size_type pos, size_type len = npos) + : ptr_(str.data() + pos), length_(std::min(len, str.length() - pos)) {} + BasicStringPiece(const typename STRING_TYPE::const_iterator& begin, + const typename STRING_TYPE::const_iterator& end) + : ptr_((end > begin) ? &(*begin) : NULL), + length_((end > begin) ? (size_type)(end - begin) : 0) {} + + // data() may return a pointer to a buffer with embedded NULs, and the + // returned buffer may or may not be null terminated. Therefore it is + // typically a mistake to pass data() to a routine that expects a NUL + // terminated string. + const value_type* data() const { return ptr_; } + size_type size() const { return length_; } + size_type length() const { return length_; } + bool empty() const { return length_ == 0; } + + void clear() { + ptr_ = NULL; + length_ = 0; + } + BasicStringPiece& assign(const BasicStringPiece& str, size_type pos, size_type len = npos) { + ptr_ = str.data() + pos; + length_ = std::min(len, str.length() - pos); + return *this; + } + void set(const value_type* data, size_type len) { + ptr_ = data; + length_ = len; + } + void set(const value_type* str) { + ptr_ = str; + length_ = str ? STRING_TYPE::traits_type::length(str) : 0; + } + + value_type operator[](size_type i) const { return ptr_[i]; } + + void remove_prefix(size_type n) { + ptr_ += n; + length_ -= n; + } + + void remove_suffix(size_type n) { + length_ -= n; + } + + // Remove heading and trailing spaces. + void trim_spaces() { + size_t nsp = 0; + for (; nsp < size() && isspace(ptr_[nsp]); ++nsp) {} + remove_prefix(nsp); + nsp = 0; + for (; nsp < size() && isspace(ptr_[size()-1-nsp]); ++nsp) {} + remove_suffix(nsp); + } + + int compare(const BasicStringPiece& x) const { + int r = wordmemcmp( + ptr_, x.ptr_, (length_ < x.length_ ? length_ : x.length_)); + if (r == 0) { + if (length_ < x.length_) r = -1; + else if (length_ > x.length_) r = +1; + } + return r; + } + + STRING_TYPE as_string() const { + // std::string doesn't like to take a NULL pointer even with a 0 size. + return empty() ? STRING_TYPE() : STRING_TYPE(data(), size()); + } + + // Return the first/last character, UNDEFINED when StringPiece is empty. + char front() const { return *ptr_; } + char back() const { return *(ptr_ + length_ - 1); } + // Return the first/last character, 0 when StringPiece is empty. + char front_or_0() const { return length_ ? *ptr_ : '\0'; } + char back_or_0() const { return length_ ? *(ptr_ + length_ - 1) : '\0'; } + + const_iterator begin() const { return ptr_; } + const_iterator end() const { return ptr_ + length_; } + const_reverse_iterator rbegin() const { + return const_reverse_iterator(ptr_ + length_); + } + const_reverse_iterator rend() const { + return const_reverse_iterator(ptr_); + } + + size_type max_size() const { return length_; } + size_type capacity() const { return length_; } + + static int wordmemcmp(const value_type* p, + const value_type* p2, + size_type N) { + return STRING_TYPE::traits_type::compare(p, p2, N); + } + + // Sets the value of the given string target type to be the current string. + // This saves a temporary over doing |a = b.as_string()| + void CopyToString(STRING_TYPE* target) const { + internal::CopyToString(*this, target); + } + + void AppendToString(STRING_TYPE* target) const { + internal::AppendToString(*this, target); + } + + size_type copy(value_type* buf, size_type n, size_type pos = 0) const { + return internal::copy(*this, buf, n, pos); + } + + // Does "this" start with "x" + bool starts_with(const BasicStringPiece& x) const { + return ((this->length_ >= x.length_) && + (wordmemcmp(this->ptr_, x.ptr_, x.length_) == 0)); + } + + // Does "this" end with "x" + bool ends_with(const BasicStringPiece& x) const { + return ((this->length_ >= x.length_) && + (wordmemcmp(this->ptr_ + (this->length_-x.length_), + x.ptr_, x.length_) == 0)); + } + + // find: Search for a character or substring at a given offset. + size_type find(const BasicStringPiece& s, + size_type pos = 0) const { + return internal::find(*this, s, pos); + } + size_type find(value_type c, size_type pos = 0) const { + return internal::find(*this, c, pos); + } + + // rfind: Reverse find. + size_type rfind(const BasicStringPiece& s, + size_type pos = BasicStringPiece::npos) const { + return internal::rfind(*this, s, pos); + } + size_type rfind(value_type c, size_type pos = BasicStringPiece::npos) const { + return internal::rfind(*this, c, pos); + } + + // find_first_of: Find the first occurence of one of a set of characters. + size_type find_first_of(const BasicStringPiece& s, + size_type pos = 0) const { + return internal::find_first_of(*this, s, pos); + } + size_type find_first_of(value_type c, size_type pos = 0) const { + return find(c, pos); + } + + // find_first_not_of: Find the first occurence not of a set of characters. + size_type find_first_not_of(const BasicStringPiece& s, + size_type pos = 0) const { + return internal::find_first_not_of(*this, s, pos); + } + size_type find_first_not_of(value_type c, size_type pos = 0) const { + return internal::find_first_not_of(*this, c, pos); + } + + // find_last_of: Find the last occurence of one of a set of characters. + size_type find_last_of(const BasicStringPiece& s, + size_type pos = BasicStringPiece::npos) const { + return internal::find_last_of(*this, s, pos); + } + size_type find_last_of(value_type c, + size_type pos = BasicStringPiece::npos) const { + return rfind(c, pos); + } + + // find_last_not_of: Find the last occurence not of a set of characters. + size_type find_last_not_of(const BasicStringPiece& s, + size_type pos = BasicStringPiece::npos) const { + return internal::find_last_not_of(*this, s, pos); + } + size_type find_last_not_of(value_type c, + size_type pos = BasicStringPiece::npos) const { + return internal::find_last_not_of(*this, c, pos); + } + + // substr. + BasicStringPiece substr(size_type pos, + size_type n = BasicStringPiece::npos) const { + return internal::substr(*this, pos, n); + } + + // Converts to `std::basic_string`. + explicit operator STRING_TYPE() const { + if (NULL == data()) { + return {}; + } + return STRING_TYPE(data(), size()); + } + + protected: + const value_type* ptr_; + size_type length_; +}; + +template +const typename BasicStringPiece::size_type +BasicStringPiece::npos = + typename BasicStringPiece::size_type(-1); + +// MSVC doesn't like complex extern templates and DLLs. +#if !defined(COMPILER_MSVC) +extern template class BUTIL_EXPORT BasicStringPiece; +extern template class BUTIL_EXPORT BasicStringPiece; +#endif + +// StringPiece operators -------------------------------------------------------- + +BUTIL_EXPORT bool operator==(const StringPiece& x, const StringPiece& y); + +inline bool operator!=(const StringPiece& x, const StringPiece& y) { + return !(x == y); +} + +inline bool operator<(const StringPiece& x, const StringPiece& y) { + const int r = StringPiece::wordmemcmp( + x.data(), y.data(), (x.size() < y.size() ? x.size() : y.size())); + return ((r < 0) || ((r == 0) && (x.size() < y.size()))); +} + +inline bool operator>(const StringPiece& x, const StringPiece& y) { + return y < x; +} + +inline bool operator<=(const StringPiece& x, const StringPiece& y) { + return !(x > y); +} + +inline bool operator>=(const StringPiece& x, const StringPiece& y) { + return !(x < y); +} + +// StringPiece16 operators ----------------------------------------------------- + +inline bool operator==(const StringPiece16& x, const StringPiece16& y) { + if (x.size() != y.size()) + return false; + + return StringPiece16::wordmemcmp(x.data(), y.data(), x.size()) == 0; +} + +inline bool operator!=(const StringPiece16& x, const StringPiece16& y) { + return !(x == y); +} + +inline bool operator<(const StringPiece16& x, const StringPiece16& y) { + const int r = StringPiece16::wordmemcmp( + x.data(), y.data(), (x.size() < y.size() ? x.size() : y.size())); + return ((r < 0) || ((r == 0) && (x.size() < y.size()))); +} + +inline bool operator>(const StringPiece16& x, const StringPiece16& y) { + return y < x; +} + +inline bool operator<=(const StringPiece16& x, const StringPiece16& y) { + return !(x > y); +} + +inline bool operator>=(const StringPiece16& x, const StringPiece16& y) { + return !(x < y); +} + +BUTIL_EXPORT std::ostream& operator<<(std::ostream& o, + const StringPiece& piece); + +// [ Ease getting first/last character of std::string before C++11 ] +// return the first/last character, UNDEFINED when the string is empty. +inline char front_char(const std::string& s) { return s[0]; } +inline char back_char(const std::string& s) { return s[s.size() - 1]; } +// return the first/last character, 0 when the string is empty. +inline char front_char_or_0(const std::string& s) { return s.empty() ? '\0' : s[0]; } +inline char back_char_or_0(const std::string& s) { return s.empty() ? '\0' : s[s.size() - 1]; } + +} // namespace butil + +// Hashing --------------------------------------------------------------------- + +// We provide appropriate hash functions so StringPiece and StringPiece16 can +// be used as keys in hash sets and maps. + +// This hash function is copied from butil/containers/hash_tables.h. We don't +// use the ones already defined for string and string16 directly because it +// would require the string constructors to be called, which we don't want. +#define HASH_STRING_PIECE(StringPieceType, string_piece) \ + std::size_t result = 0; \ + for (StringPieceType::const_iterator i = string_piece.begin(); \ + i != string_piece.end(); ++i) \ + result = (result * 131) + *i; \ + return result; \ + +namespace BUTIL_HASH_NAMESPACE { +#if defined(COMPILER_GCC) + +template<> +struct hash { + std::size_t operator()(const butil::StringPiece& sp) const { + HASH_STRING_PIECE(butil::StringPiece, sp); + } +}; +template<> +struct hash { + std::size_t operator()(const butil::StringPiece16& sp16) const { + HASH_STRING_PIECE(butil::StringPiece16, sp16); + } +}; + +#elif defined(COMPILER_MSVC) + +inline size_t hash_value(const butil::StringPiece& sp) { + HASH_STRING_PIECE(butil::StringPiece, sp); +} +inline size_t hash_value(const butil::StringPiece16& sp16) { + HASH_STRING_PIECE(butil::StringPiece16, sp16); +} + +#endif // COMPILER + +} // namespace BUTIL_HASH_NAMESPACE + +#endif // BUTIL_STRINGS_STRING_PIECE_H_ diff --git a/src/butil/strings/string_split.cc b/src/butil/strings/string_split.cc new file mode 100644 index 0000000..23b1f89 --- /dev/null +++ b/src/butil/strings/string_split.cc @@ -0,0 +1,289 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/strings/string_split.h" + +#include "butil/logging.h" +#include "butil/strings/string_util.h" +#include "butil/strings/utf_string_conversions.h" +#include "butil/third_party/icu/icu_utf.h" + +namespace butil { + +namespace { + +template +void SplitStringT(const STR& str, + const typename STR::value_type s, + bool trim_whitespace, + std::vector* r) { + r->clear(); + size_t last = 0; + size_t c = str.size(); + for (size_t i = 0; i <= c; ++i) { + if (i == c || str[i] == s) { + STR tmp(str, last, i - last); + if (trim_whitespace) + TrimWhitespace(tmp, TRIM_ALL, &tmp); + // Avoid converting an empty or all-whitespace source string into a vector + // of one empty string. + if (i != c || !r->empty() || !tmp.empty()) + r->push_back(tmp); + last = i + 1; + } + } +} + +template +bool SplitStringIntoKeyValueT(const STR& line, + typename STR::value_type key_value_delimiter, + STR* key, + STR* value) { + key->clear(); + value->clear(); + + // Find the delimiter. + size_t end_key_pos = line.find_first_of(key_value_delimiter); + if (end_key_pos == STR::npos) { + DVLOG(1) << "cannot find delimiter in: " << line; + return false; // no delimiter + } + key->assign(line, 0, end_key_pos); + + // Find the value string. + STR remains(line, end_key_pos, line.size() - end_key_pos); + size_t begin_value_pos = remains.find_first_not_of(key_value_delimiter); + if (begin_value_pos == STR::npos) { + DVLOG(1) << "cannot parse value from line: " << line; + return false; // no value + } + value->assign(remains, begin_value_pos, remains.size() - begin_value_pos); + return true; +} + +template +void SplitStringUsingSubstrT(const STR& str, + const STR& s, + std::vector* r) { + r->clear(); + typename STR::size_type begin_index = 0; + while (true) { + const typename STR::size_type end_index = str.find(s, begin_index); + if (end_index == STR::npos) { + const STR term = str.substr(begin_index); + STR tmp; + TrimWhitespace(term, TRIM_ALL, &tmp); + r->push_back(tmp); + return; + } + const STR term = str.substr(begin_index, end_index - begin_index); + STR tmp; + TrimWhitespace(term, TRIM_ALL, &tmp); + r->push_back(tmp); + begin_index = end_index + s.size(); + } +} + +template +void SplitStringAlongWhitespaceT(const STR& str, std::vector* result) { + result->clear(); + const size_t length = str.length(); + if (!length) + return; + + bool last_was_ws = false; + size_t last_non_ws_start = 0; + for (size_t i = 0; i < length; ++i) { + switch (str[i]) { + // HTML 5 defines whitespace as: space, tab, LF, line tab, FF, or CR. + case L' ': + case L'\t': + case L'\xA': + case L'\xB': + case L'\xC': + case L'\xD': + if (!last_was_ws) { + if (i > 0) { + result->push_back( + str.substr(last_non_ws_start, i - last_non_ws_start)); + } + last_was_ws = true; + } + break; + + default: // Not a space character. + if (last_was_ws) { + last_was_ws = false; + last_non_ws_start = i; + } + break; + } + } + if (!last_was_ws) { + result->push_back( + str.substr(last_non_ws_start, length - last_non_ws_start)); + } +} + +} // namespace + +void SplitString(const string16& str, + char16 c, + std::vector* r) { + DCHECK(CBU16_IS_SINGLE(c)); + SplitStringT(str, c, true, r); +} + +void SplitString(const butil::StringPiece16& str, + char16 c, + std::vector* r) { + DCHECK(CBU16_IS_SINGLE(c)); + SplitStringT(str, c, true, r); +} + +void SplitString(const std::string& str, + char c, + std::vector* r) { +#if CHAR_MIN < 0 + DCHECK(c >= 0); +#endif + DCHECK(c < 0x7F); + SplitStringT(str, c, true, r); +} + +void SplitString(const StringPiece& str, + char c, + std::vector* r) { +#if CHAR_MIN < 0 + DCHECK(c >= 0); +#endif + DCHECK(c < 0x7F); + SplitStringT(str, c, true, r); +} + +template +bool SplitStringIntoKeyValuePairsT(const STR& line, + char key_value_delimiter, + char key_value_pair_delimiter, + std::vector >* key_value_pairs) { + key_value_pairs->clear(); + + std::vector pairs; + SplitString(line, key_value_pair_delimiter, &pairs); + + bool success = true; + for (size_t i = 0; i < pairs.size(); ++i) { + // Don't add empty pairs into the result. + if (pairs[i].empty()) + continue; + + STR key; + STR value; + if (!SplitStringIntoKeyValueT(pairs[i], key_value_delimiter, &key, &value)) { + // Don't return here, to allow for pairs without associated + // value or key; just record that the split failed. + success = false; + } + key_value_pairs->emplace_back(key, value); + } + return success; +} + +bool SplitStringIntoKeyValuePairs(const std::string& line, + char key_value_delimiter, + char key_value_pair_delimiter, + StringPairs* key_value_pairs) { + return SplitStringIntoKeyValuePairsT(line, key_value_delimiter, + key_value_pair_delimiter, key_value_pairs); +} + +bool SplitStringIntoKeyValuePairs(const butil::StringPiece& line, + char key_value_delimiter, + char key_value_pair_delimiter, + StringPiecePairs* key_value_pairs) { + return SplitStringIntoKeyValuePairsT(line, key_value_delimiter, + key_value_pair_delimiter, key_value_pairs); +} + +void SplitStringUsingSubstr(const string16& str, + const string16& s, + std::vector* r) { + SplitStringUsingSubstrT(str, s, r); +} + +void SplitStringUsingSubstr(const butil::StringPiece16& str, + const butil::StringPiece16& s, + std::vector* r) { + SplitStringUsingSubstrT(str, s, r); +} + +void SplitStringUsingSubstr(const std::string& str, + const std::string& s, + std::vector* r) { + SplitStringUsingSubstrT(str, s, r); +} + +void SplitStringUsingSubstr(const butil::StringPiece& str, + const butil::StringPiece& s, + std::vector* r) { + SplitStringUsingSubstrT(str, s, r); +} + +void SplitStringDontTrim(const string16& str, + char16 c, + std::vector* r) { + DCHECK(CBU16_IS_SINGLE(c)); + SplitStringT(str, c, false, r); +} + +void SplitStringDontTrim(const butil::StringPiece16& str, + char16 c, + std::vector* r) { + DCHECK(CBU16_IS_SINGLE(c)); + SplitStringT(str, c, false, r); +} + +void SplitStringDontTrim(const std::string& str, + char c, + std::vector* r) { + DCHECK(IsStringUTF8(str)); +#if CHAR_MIN < 0 + DCHECK(c >= 0); +#endif + DCHECK(c < 0x7F); + SplitStringT(str, c, false, r); +} + +void SplitStringDontTrim(const butil::StringPiece& str, + char c, + std::vector* r) { + DCHECK(IsStringUTF8(str)); +#if CHAR_MIN < 0 + DCHECK(c >= 0); +#endif + DCHECK(c < 0x7F); + SplitStringT(str, c, false, r); +} + +void SplitStringAlongWhitespace(const string16& str, + std::vector* result) { + SplitStringAlongWhitespaceT(str, result); +} + +void SplitStringAlongWhitespace(const butil::StringPiece16& str, + std::vector* result) { + SplitStringAlongWhitespaceT(str, result); +} + +void SplitStringAlongWhitespace(const std::string& str, + std::vector* result) { + SplitStringAlongWhitespaceT(str, result); +} + +void SplitStringAlongWhitespace(const butil::StringPiece& str, + std::vector* result) { + SplitStringAlongWhitespaceT(str, result); +} + +} // namespace butil diff --git a/src/butil/strings/string_split.h b/src/butil/strings/string_split.h new file mode 100644 index 0000000..e4f29e6 --- /dev/null +++ b/src/butil/strings/string_split.h @@ -0,0 +1,110 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_STRINGS_STRING_SPLIT_H_ +#define BUTIL_STRINGS_STRING_SPLIT_H_ + +#include +#include +#include + +#include "butil/base_export.h" +#include "butil/strings/string16.h" +#include "butil/strings/string_piece.h" + +namespace butil { + +// Splits |str| into a vector of strings delimited by |c|, placing the results +// in |r|. If several instances of |c| are contiguous, or if |str| begins with +// or ends with |c|, then an empty string is inserted. +// +// Every substring is trimmed of any leading or trailing white space. +// NOTE: |c| must be in BMP (Basic Multilingual Plane) +BUTIL_EXPORT void SplitString(const string16& str, + char16 c, + std::vector* r); +BUTIL_EXPORT void SplitString(const butil::StringPiece16& str, + char16 c, + std::vector* r); + +// |str| should not be in a multi-byte encoding like Shift-JIS or GBK in which +// the trailing byte of a multi-byte character can be in the ASCII range. +// UTF-8, and other single/multi-byte ASCII-compatible encodings are OK. +// Note: |c| must be in the ASCII range. +BUTIL_EXPORT void SplitString(const std::string& str, + char c, + std::vector* r); +BUTIL_EXPORT void SplitString(const butil::StringPiece& str, + char c, + std::vector* r); + +typedef std::vector > StringPairs; +typedef std::vector > StringPiecePairs; + +// Splits |line| into key value pairs according to the given delimiters and +// removes whitespace leading each key and trailing each value. Returns true +// only if each pair has a non-empty key and value. |key_value_pairs| will +// include ("","") pairs for entries without |key_value_delimiter|. +BUTIL_EXPORT bool SplitStringIntoKeyValuePairs(const std::string& line, + char key_value_delimiter, + char key_value_pair_delimiter, + StringPairs* key_value_pairs); +BUTIL_EXPORT bool SplitStringIntoKeyValuePairs(const butil::StringPiece& line, + char key_value_delimiter, + char key_value_pair_delimiter, + StringPiecePairs* key_value_pairs); + +// The same as SplitString, but use a substring delimiter instead of a char. +BUTIL_EXPORT void SplitStringUsingSubstr(const string16& str, + const string16& s, + std::vector* r); +BUTIL_EXPORT void SplitStringUsingSubstr(const butil::StringPiece16& str, + const butil::StringPiece16& s, + std::vector* r); +BUTIL_EXPORT void SplitStringUsingSubstr(const std::string& str, + const std::string& s, + std::vector* r); +BUTIL_EXPORT void SplitStringUsingSubstr(const butil::StringPiece& str, + const butil::StringPiece& s, + std::vector* r); + +// The same as SplitString, but don't trim white space. +// NOTE: |c| must be in BMP (Basic Multilingual Plane) +BUTIL_EXPORT void SplitStringDontTrim(const string16& str, + char16 c, + std::vector* r); +BUTIL_EXPORT void SplitStringDontTrim(const butil::StringPiece16& str, + char16 c, + std::vector* r); +// |str| should not be in a multi-byte encoding like Shift-JIS or GBK in which +// the trailing byte of a multi-byte character can be in the ASCII range. +// UTF-8, and other single/multi-byte ASCII-compatible encodings are OK. +// Note: |c| must be in the ASCII range. +BUTIL_EXPORT void SplitStringDontTrim(const std::string& str, + char c, + std::vector* r); +BUTIL_EXPORT void SplitStringDontTrim(const butil::StringPiece& str, + char c, + std::vector* r); + +// WARNING: this uses whitespace as defined by the HTML5 spec. If you need +// a function similar to this but want to trim all types of whitespace, then +// factor this out into a function that takes a string containing the characters +// that are treated as whitespace. +// +// Splits the string along whitespace (where whitespace is the five space +// characters defined by HTML 5). Each contiguous block of non-whitespace +// characters is added to result. +BUTIL_EXPORT void SplitStringAlongWhitespace(const string16& str, + std::vector* result); +BUTIL_EXPORT void SplitStringAlongWhitespace(const butil::StringPiece16& str, + std::vector* result); +BUTIL_EXPORT void SplitStringAlongWhitespace(const std::string& str, + std::vector* result); +BUTIL_EXPORT void SplitStringAlongWhitespace(const butil::StringPiece& str, + std::vector* result); + +} // namespace butil + +#endif // BUTIL_STRINGS_STRING_SPLIT_H_ diff --git a/src/butil/strings/string_tokenizer.h b/src/butil/strings/string_tokenizer.h new file mode 100644 index 0000000..017607b --- /dev/null +++ b/src/butil/strings/string_tokenizer.h @@ -0,0 +1,260 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_STRINGS_STRING_TOKENIZER_H_ +#define BUTIL_STRINGS_STRING_TOKENIZER_H_ + +#include +#include + +#include "butil/strings/string_piece.h" + +namespace butil { + +// StringTokenizerT is a simple string tokenizer class. It works like an +// iterator that with each step (see the Advance method) updates members that +// refer to the next token in the input string. The user may optionally +// configure the tokenizer to return delimiters. +// +// Warning: be careful not to pass a C string into the 2-arg constructor: +// StringTokenizer t("this is a test", " "); // WRONG +// This will create a temporary std::string, save the begin() and end() +// iterators, and then the string will be freed before we actually start +// tokenizing it. +// Instead, use a std::string or use the 3 arg constructor of CStringTokenizer. +// +// +// EXAMPLE 1: +// +// char input[] = "this is a test"; +// CStringTokenizer t(input, input + strlen(input), " "); +// while (t.GetNext()) { +// printf("%s\n", t.token().c_str()); +// } +// +// Output: +// +// this +// is +// a +// test +// +// +// EXAMPLE 2: +// +// std::string input = "no-cache=\"foo, bar\", private"; +// StringTokenizer t(input, ", "); +// t.set_quote_chars("\""); +// while (t.GetNext()) { +// printf("%s\n", t.token().c_str()); +// } +// +// Output: +// +// no-cache="foo, bar" +// private +// +// +// EXAMPLE 3: +// +// bool next_is_option = false, next_is_value = false; +// std::string input = "text/html; charset=UTF-8; foo=bar"; +// StringTokenizer t(input, "; ="); +// t.set_options(StringTokenizer::RETURN_DELIMS); +// while (t.GetNext()) { +// if (t.token_is_delim()) { +// switch (*t.token_begin()) { +// case ';': +// next_is_option = true; +// break; +// case '=': +// next_is_value = true; +// break; +// } +// } else { +// const char* label; +// if (next_is_option) { +// label = "option-name"; +// next_is_option = false; +// } else if (next_is_value) { +// label = "option-value"; +// next_is_value = false; +// } else { +// label = "mime-type"; +// } +// printf("%s: %s\n", label, t.token().c_str()); +// } +// } +// +// +template +class StringTokenizerT { + public: + typedef typename str::value_type char_type; + + // Options that may be pass to set_options() + enum { + // Specifies the delimiters should be returned as tokens + RETURN_DELIMS = 1 << 0, + }; + + // The string object must live longer than the tokenizer. (In particular this + // should not be constructed with a temporary.) + StringTokenizerT(const str& string, + const str& delims) { + Init(string.begin(), string.end(), delims); + } + + StringTokenizerT(const_iterator string_begin, + const_iterator string_end, + const str& delims) { + Init(string_begin, string_end, delims); + } + + // Set the options for this tokenizer. By default, this is 0. + void set_options(int options) { options_ = options; } + + // Set the characters to regard as quotes. By default, this is empty. When + // a quote char is encountered, the tokenizer will switch into a mode where + // it ignores delimiters that it finds. It switches out of this mode once it + // finds another instance of the quote char. If a backslash is encountered + // within a quoted string, then the next character is skipped. + void set_quote_chars(const str& quotes) { quotes_ = quotes; } + + // Call this method to advance the tokenizer to the next delimiter. This + // returns false if the tokenizer is complete. This method must be called + // before calling any of the token* methods. + bool GetNext() { + if (quotes_.empty() && options_ == 0) + return QuickGetNext(); + else + return FullGetNext(); + } + + // Start iterating through tokens from the beginning of the string. + void Reset() { + token_end_ = start_pos_; + } + + // Returns true if token is a delimiter. When the tokenizer is constructed + // with the RETURN_DELIMS option, this method can be used to check if the + // returned token is actually a delimiter. + bool token_is_delim() const { return token_is_delim_; } + + // If GetNext() returned true, then these methods may be used to read the + // value of the token. + const_iterator token_begin() const { return token_begin_; } + const_iterator token_end() const { return token_end_; } + str token() const { return str(token_begin_, token_end_); } + butil::StringPiece token_piece() const { + return butil::StringPiece(&*token_begin_, + std::distance(token_begin_, token_end_)); + } + + private: + void Init(const_iterator string_begin, + const_iterator string_end, + const str& delims) { + start_pos_ = string_begin; + token_begin_ = string_begin; + token_end_ = string_begin; + end_ = string_end; + delims_ = delims; + options_ = 0; + token_is_delim_ = false; + } + + // Implementation of GetNext() for when we have no quote characters. We have + // two separate implementations because AdvanceOne() is a hot spot in large + // text files with large tokens. + bool QuickGetNext() { + token_is_delim_ = false; + for (;;) { + token_begin_ = token_end_; + if (token_end_ == end_) + return false; + ++token_end_; + if (delims_.find(*token_begin_) == str::npos) + break; + // else skip over delimiter. + } + while (token_end_ != end_ && delims_.find(*token_end_) == str::npos) + ++token_end_; + return true; + } + + // Implementation of GetNext() for when we have to take quotes into account. + bool FullGetNext() { + AdvanceState state; + token_is_delim_ = false; + for (;;) { + token_begin_ = token_end_; + if (token_end_ == end_) + return false; + ++token_end_; + if (AdvanceOne(&state, *token_begin_)) + break; + if (options_ & RETURN_DELIMS) { + token_is_delim_ = true; + return true; + } + // else skip over delimiter. + } + while (token_end_ != end_ && AdvanceOne(&state, *token_end_)) + ++token_end_; + return true; + } + + bool IsDelim(char_type c) const { + return delims_.find(c) != str::npos; + } + + bool IsQuote(char_type c) const { + return quotes_.find(c) != str::npos; + } + + struct AdvanceState { + bool in_quote; + bool in_escape; + char_type quote_char; + AdvanceState() : in_quote(false), in_escape(false), quote_char('\0') {} + }; + + // Returns true if a delimiter was not hit. + bool AdvanceOne(AdvanceState* state, char_type c) { + if (state->in_quote) { + if (state->in_escape) { + state->in_escape = false; + } else if (c == '\\') { + state->in_escape = true; + } else if (c == state->quote_char) { + state->in_quote = false; + } + } else { + if (IsDelim(c)) + return false; + state->in_quote = IsQuote(state->quote_char = c); + } + return true; + } + + const_iterator start_pos_; + const_iterator token_begin_; + const_iterator token_end_; + const_iterator end_; + str delims_; + str quotes_; + int options_; + bool token_is_delim_; +}; + +typedef StringTokenizerT + StringTokenizer; +typedef StringTokenizerT + WStringTokenizer; +typedef StringTokenizerT CStringTokenizer; + +} // namespace butil + +#endif // BUTIL_STRINGS_STRING_TOKENIZER_H_ diff --git a/src/butil/strings/string_util.cc b/src/butil/strings/string_util.cc new file mode 100644 index 0000000..b7ec353 --- /dev/null +++ b/src/butil/strings/string_util.cc @@ -0,0 +1,911 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/strings/string_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "butil/basictypes.h" +#include "butil/logging.h" +#include "butil/memory/singleton.h" +#include "butil/strings/utf_string_conversion_utils.h" +#include "butil/strings/utf_string_conversions.h" +#include "butil/third_party/icu/icu_utf.h" +#include "butil/build_config.h" + +// Remove when this entire file is in namespace butil. +using butil::char16; +using butil::string16; + +namespace { + +// Force the singleton used by EmptyString[16] to be a unique type. This +// prevents other code that might accidentally use Singleton from +// getting our internal one. +struct EmptyStrings { + EmptyStrings() {} + const std::string s; + const string16 s16; + + static EmptyStrings* GetInstance() { + return Singleton::get(); + } +}; + +// Used by ReplaceStringPlaceholders to track the position in the string of +// replaced parameters. +struct ReplacementOffset { + ReplacementOffset(uintptr_t parameter, size_t offset) + : parameter(parameter), + offset(offset) {} + + // Index of the parameter. + uintptr_t parameter; + + // Starting position in the string. + size_t offset; +}; + +static bool CompareParameter(const ReplacementOffset& elem1, + const ReplacementOffset& elem2) { + return elem1.parameter < elem2.parameter; +} + +} // namespace + +namespace butil { + +bool IsWprintfFormatPortable(const wchar_t* format) { + for (const wchar_t* position = format; *position != '\0'; ++position) { + if (*position == '%') { + bool in_specification = true; + bool modifier_l = false; + while (in_specification) { + // Eat up characters until reaching a known specifier. + if (*++position == '\0') { + // The format string ended in the middle of a specification. Call + // it portable because no unportable specifications were found. The + // string is equally broken on all platforms. + return true; + } + + if (*position == 'l') { + // 'l' is the only thing that can save the 's' and 'c' specifiers. + modifier_l = true; + } else if (((*position == 's' || *position == 'c') && !modifier_l) || + *position == 'S' || *position == 'C' || *position == 'F' || + *position == 'D' || *position == 'O' || *position == 'U') { + // Not portable. + return false; + } + + if (wcschr(L"diouxXeEfgGaAcspn%", *position)) { + // Portable, keep scanning the rest of the format string. + in_specification = false; + } + } + } + } + + return true; +} + +const std::string& EmptyString() { + return EmptyStrings::GetInstance()->s; +} + +const string16& EmptyString16() { + return EmptyStrings::GetInstance()->s16; +} + +template +bool ReplaceCharsT(const STR& input, + const STR& replace_chars, + const STR& replace_with, + STR* output) { + bool removed = false; + size_t replace_length = replace_with.length(); + + *output = input; + + size_t found = output->find_first_of(replace_chars); + while (found != STR::npos) { + removed = true; + output->replace(found, 1, replace_with); + found = output->find_first_of(replace_chars, found + replace_length); + } + + return removed; +} + +bool ReplaceChars(const string16& input, + const butil::StringPiece16& replace_chars, + const string16& replace_with, + string16* output) { + return ReplaceCharsT(input, replace_chars.as_string(), replace_with, output); +} + +bool ReplaceChars(const std::string& input, + const butil::StringPiece& replace_chars, + const std::string& replace_with, + std::string* output) { + return ReplaceCharsT(input, replace_chars.as_string(), replace_with, output); +} + +bool RemoveChars(const string16& input, + const butil::StringPiece16& remove_chars, + string16* output) { + return ReplaceChars(input, remove_chars.as_string(), string16(), output); +} + +bool RemoveChars(const std::string& input, + const butil::StringPiece& remove_chars, + std::string* output) { + return ReplaceChars(input, remove_chars.as_string(), std::string(), output); +} + +template +TrimPositions TrimStringT(const STR& input, + const STR& trim_chars, + TrimPositions positions, + STR* output) { + // Find the edges of leading/trailing whitespace as desired. + const size_t last_char = input.length() - 1; + const size_t first_good_char = (positions & TRIM_LEADING) ? + input.find_first_not_of(trim_chars) : 0; + const size_t last_good_char = (positions & TRIM_TRAILING) ? + input.find_last_not_of(trim_chars) : last_char; + + // When the string was all whitespace, report that we stripped off whitespace + // from whichever position the caller was interested in. For empty input, we + // stripped no whitespace, but we still need to clear |output|. + if (input.empty() || + (first_good_char == STR::npos) || (last_good_char == STR::npos)) { + bool input_was_empty = input.empty(); // in case output == &input + output->clear(); + return input_was_empty ? TRIM_NONE : positions; + } + + // Trim the whitespace. + *output = + input.substr(first_good_char, last_good_char - first_good_char + 1); + + // Return where we trimmed from. + return static_cast( + ((first_good_char == 0) ? TRIM_NONE : TRIM_LEADING) | + ((last_good_char == last_char) ? TRIM_NONE : TRIM_TRAILING)); +} + +bool TrimString(const string16& input, + const butil::StringPiece16& trim_chars, + string16* output) { + return TrimStringT(input, trim_chars.as_string(), TRIM_ALL, output) != + TRIM_NONE; +} + +bool TrimString(const std::string& input, + const butil::StringPiece& trim_chars, + std::string* output) { + return TrimStringT(input, trim_chars.as_string(), TRIM_ALL, output) != + TRIM_NONE; +} + +void TruncateUTF8ToByteSize(const std::string& input, + const size_t byte_size, + std::string* output) { + DCHECK(output); + if (byte_size > input.length()) { + *output = input; + return; + } + DCHECK_LE(byte_size, static_cast(kint32max)); + // Note: This cast is necessary because CBU8_NEXT uses int32s. + int32_t truncation_length = static_cast(byte_size); + int32_t char_index = truncation_length - 1; + const char* data = input.data(); + + // Using CBU8, we will move backwards from the truncation point + // to the beginning of the string looking for a valid UTF8 + // character. Once a full UTF8 character is found, we will + // truncate the string to the end of that character. + while (char_index >= 0) { + int32_t prev = char_index; + uint32_t code_point = 0; + CBU8_NEXT(data, char_index, truncation_length, code_point); + if (!IsValidCharacter(code_point) || + !IsValidCodepoint(code_point)) { + char_index = prev - 1; + } else { + break; + } + } + + if (char_index >= 0 ) + *output = input.substr(0, char_index); + else + output->clear(); +} + +TrimPositions TrimWhitespace(const string16& input, + TrimPositions positions, + string16* output) { + return TrimStringT(input, butil::string16(kWhitespaceUTF16), positions, + output); +} + +TrimPositions TrimWhitespace(const butil::StringPiece16& input, + TrimPositions positions, + butil::StringPiece16* output) { + return TrimStringT(input, butil::StringPiece16(kWhitespaceUTF16), positions, + output); +} + +TrimPositions TrimWhitespaceASCII(const std::string& input, + TrimPositions positions, + std::string* output) { + return TrimStringT(input, std::string(kWhitespaceASCII), positions, output); +} + +TrimPositions TrimWhitespaceASCII(const butil::StringPiece& input, + TrimPositions positions, + butil::StringPiece* output) { + return TrimStringT(input, butil::StringPiece(kWhitespaceASCII), positions, output); +} + +// This function is only for backward-compatibility. +// To be removed when all callers are updated. +TrimPositions TrimWhitespace(const std::string& input, + TrimPositions positions, + std::string* output) { + return TrimWhitespaceASCII(input, positions, output); +} + +TrimPositions TrimWhitespace(const butil::StringPiece& input, + TrimPositions positions, + butil::StringPiece* output) { + return TrimWhitespaceASCII(input, positions, output); +} + +template +STR CollapseWhitespaceT(const STR& text, + bool trim_sequences_with_line_breaks) { + STR result; + result.resize(text.size()); + + // Set flags to pretend we're already in a trimmed whitespace sequence, so we + // will trim any leading whitespace. + bool in_whitespace = true; + bool already_trimmed = true; + + int chars_written = 0; + for (typename STR::const_iterator i(text.begin()); i != text.end(); ++i) { + if (IsWhitespace(*i)) { + if (!in_whitespace) { + // Reduce all whitespace sequences to a single space. + in_whitespace = true; + result[chars_written++] = L' '; + } + if (trim_sequences_with_line_breaks && !already_trimmed && + ((*i == '\n') || (*i == '\r'))) { + // Whitespace sequences containing CR or LF are eliminated entirely. + already_trimmed = true; + --chars_written; + } + } else { + // Non-whitespace chracters are copied straight across. + in_whitespace = false; + already_trimmed = false; + result[chars_written++] = *i; + } + } + + if (in_whitespace && !already_trimmed) { + // Any trailing whitespace is eliminated. + --chars_written; + } + + result.resize(chars_written); + return result; +} + +string16 CollapseWhitespace(const string16& text, + bool trim_sequences_with_line_breaks) { + return CollapseWhitespaceT(text, trim_sequences_with_line_breaks); +} + +std::string CollapseWhitespaceASCII(const std::string& text, + bool trim_sequences_with_line_breaks) { + return CollapseWhitespaceT(text, trim_sequences_with_line_breaks); +} + +bool ContainsOnlyChars(const StringPiece& input, + const StringPiece& characters) { + return input.find_first_not_of(characters) == StringPiece::npos; +} + +bool ContainsOnlyChars(const StringPiece16& input, + const StringPiece16& characters) { + return input.find_first_not_of(characters) == StringPiece16::npos; +} + +template +static bool DoIsStringASCII(const STR& str) { + for (size_t i = 0; i < str.length(); i++) { + typename ToUnsigned::Unsigned c = str[i]; + if (c > 0x7F) + return false; + } + return true; +} + +bool IsStringASCII(const StringPiece& str) { + return DoIsStringASCII(str); +} + +bool IsStringASCII(const string16& str) { + return DoIsStringASCII(str); +} + +bool IsStringUTF8(const StringPiece& str) { + const char *src = str.data(); + int32_t src_len = static_cast(str.length()); + int32_t char_index = 0; + + while (char_index < src_len) { + int32_t code_point; + CBU8_NEXT(src, char_index, src_len, code_point); + if (!IsValidCharacter(code_point)) + return false; + } + return true; +} + +} // namespace butil + +template +static inline bool DoLowerCaseEqualsASCII(Iter a_begin, + Iter a_end, + const char* b) { + for (Iter it = a_begin; it != a_end; ++it, ++b) { + if (!*b || butil::ToLowerASCII(*it) != *b) + return false; + } + return *b == 0; +} + +// Front-ends for LowerCaseEqualsASCII. +bool LowerCaseEqualsASCII(const std::string& a, const char* b) { + return DoLowerCaseEqualsASCII(a.begin(), a.end(), b); +} + +bool LowerCaseEqualsASCII(const string16& a, const char* b) { + return DoLowerCaseEqualsASCII(a.begin(), a.end(), b); +} + +bool LowerCaseEqualsASCII(std::string::const_iterator a_begin, + std::string::const_iterator a_end, + const char* b) { + return DoLowerCaseEqualsASCII(a_begin, a_end, b); +} + +bool LowerCaseEqualsASCII(string16::const_iterator a_begin, + string16::const_iterator a_end, + const char* b) { + return DoLowerCaseEqualsASCII(a_begin, a_end, b); +} + +// TODO(port): Resolve wchar_t/iterator issues that require OS_ANDROID here. +#if !defined(OS_ANDROID) +bool LowerCaseEqualsASCII(const char* a_begin, + const char* a_end, + const char* b) { + return DoLowerCaseEqualsASCII(a_begin, a_end, b); +} + +bool LowerCaseEqualsASCII(const char16* a_begin, + const char16* a_end, + const char* b) { + return DoLowerCaseEqualsASCII(a_begin, a_end, b); +} + +#endif // !defined(OS_ANDROID) + +bool EqualsASCII(const string16& a, const butil::StringPiece& b) { + if (a.length() != b.length()) + return false; + return std::equal(b.begin(), b.end(), a.begin()); +} + +bool StartsWithASCII(const std::string& str, + const std::string& search, + bool case_sensitive) { + if (case_sensitive) + return str.compare(0, search.length(), search) == 0; + else + return butil::strncasecmp(str.c_str(), search.c_str(), search.length()) == 0; +} + +template +bool StartsWithT(const STR& str, const STR& search, bool case_sensitive) { + if (case_sensitive) { + return str.compare(0, search.length(), search) == 0; + } else { + if (search.size() > str.size()) + return false; + return std::equal(search.begin(), search.end(), str.begin(), + butil::CaseInsensitiveCompare()); + } +} + +bool StartsWith(const string16& str, const string16& search, + bool case_sensitive) { + return StartsWithT(str, search, case_sensitive); +} + +template +bool EndsWithT(const STR& str, const STR& search, bool case_sensitive) { + size_t str_length = str.length(); + size_t search_length = search.length(); + if (search_length > str_length) + return false; + if (case_sensitive) + return str.compare(str_length - search_length, search_length, search) == 0; + return std::equal(search.begin(), search.end(), + str.begin() + (str_length - search_length), + butil::CaseInsensitiveCompare()); +} + +bool EndsWith(const std::string& str, const std::string& search, + bool case_sensitive) { + return EndsWithT(str, search, case_sensitive); +} + +bool EndsWith(const string16& str, const string16& search, + bool case_sensitive) { + return EndsWithT(str, search, case_sensitive); +} + +static const char* const kByteStringsUnlocalized[] = { + " B", + " kB", + " MB", + " GB", + " TB", + " PB" +}; + +string16 FormatBytesUnlocalized(int64_t bytes) { + double unit_amount = static_cast(bytes); + size_t dimension = 0; + const int kKilo = 1024; + while (unit_amount >= kKilo && + dimension < arraysize(kByteStringsUnlocalized) - 1) { + unit_amount /= kKilo; + dimension++; + } + + char buf[64]; + if (bytes != 0 && dimension > 0 && unit_amount < 100) { + butil::snprintf(buf, arraysize(buf), "%.1lf%s", unit_amount, + kByteStringsUnlocalized[dimension]); + } else { + butil::snprintf(buf, arraysize(buf), "%.0lf%s", unit_amount, + kByteStringsUnlocalized[dimension]); + } + + return butil::ASCIIToUTF16(buf); +} + +template +void DoReplaceSubstringsAfterOffset(StringType* str, + size_t start_offset, + const StringType& find_this, + const StringType& replace_with, + bool replace_all) { + if ((start_offset == StringType::npos) || (start_offset >= str->length())) + return; + + DCHECK(!find_this.empty()); + for (size_t offs(str->find(find_this, start_offset)); + offs != StringType::npos; offs = str->find(find_this, offs)) { + str->replace(offs, find_this.length(), replace_with); + offs += replace_with.length(); + + if (!replace_all) + break; + } +} + +void ReplaceFirstSubstringAfterOffset(string16* str, + size_t start_offset, + const string16& find_this, + const string16& replace_with) { + DoReplaceSubstringsAfterOffset(str, start_offset, find_this, replace_with, + false); // replace first instance +} + +void ReplaceFirstSubstringAfterOffset(std::string* str, + size_t start_offset, + const std::string& find_this, + const std::string& replace_with) { + DoReplaceSubstringsAfterOffset(str, start_offset, find_this, replace_with, + false); // replace first instance +} + +void ReplaceSubstringsAfterOffset(string16* str, + size_t start_offset, + const string16& find_this, + const string16& replace_with) { + DoReplaceSubstringsAfterOffset(str, start_offset, find_this, replace_with, + true); // replace all instances +} + +void ReplaceSubstringsAfterOffset(std::string* str, + size_t start_offset, + const std::string& find_this, + const std::string& replace_with) { + DoReplaceSubstringsAfterOffset(str, start_offset, find_this, replace_with, + true); // replace all instances +} + + +template +static size_t TokenizeT(const STR& str, + const STR& delimiters, + std::vector* tokens) { + tokens->clear(); + + size_t start = str.find_first_not_of(delimiters); + while (start != STR::npos) { + size_t end = str.find_first_of(delimiters, start + 1); + if (end == STR::npos) { + tokens->push_back(str.substr(start)); + break; + } else { + tokens->push_back(str.substr(start, end - start)); + start = str.find_first_not_of(delimiters, end + 1); + } + } + + return tokens->size(); +} + +size_t Tokenize(const string16& str, + const string16& delimiters, + std::vector* tokens) { + return TokenizeT(str, delimiters, tokens); +} + +size_t Tokenize(const std::string& str, + const std::string& delimiters, + std::vector* tokens) { + return TokenizeT(str, delimiters, tokens); +} + +size_t Tokenize(const butil::StringPiece& str, + const butil::StringPiece& delimiters, + std::vector* tokens) { + return TokenizeT(str, delimiters, tokens); +} + +template +static STR JoinStringT(const std::vector& parts, const STR& sep) { + if (parts.empty()) + return STR(); + + STR result(parts[0]); + typename std::vector::const_iterator iter = parts.begin(); + ++iter; + + for (; iter != parts.end(); ++iter) { + result += sep; + result += *iter; + } + + return result; +} + +std::string JoinString(const std::vector& parts, char sep) { + return JoinStringT(parts, std::string(1, sep)); +} + +string16 JoinString(const std::vector& parts, char16 sep) { + return JoinStringT(parts, string16(1, sep)); +} + +std::string JoinString(const std::vector& parts, + const std::string& separator) { + return JoinStringT(parts, separator); +} + +string16 JoinString(const std::vector& parts, + const string16& separator) { + return JoinStringT(parts, separator); +} + +template +OutStringType DoReplaceStringPlaceholders(const FormatStringType& format_string, + const std::vector& subst, std::vector* offsets) { + size_t substitutions = subst.size(); + + size_t sub_length = 0; + for (typename std::vector::const_iterator iter = subst.begin(); + iter != subst.end(); ++iter) { + sub_length += iter->length(); + } + + OutStringType formatted; + formatted.reserve(format_string.length() + sub_length); + + std::vector r_offsets; + for (typename FormatStringType::const_iterator i = format_string.begin(); + i != format_string.end(); ++i) { + if ('$' == *i) { + if (i + 1 != format_string.end()) { + ++i; + DCHECK('$' == *i || '1' <= *i) << "Invalid placeholder: " << *i; + if ('$' == *i) { + while (i != format_string.end() && '$' == *i) { + formatted.push_back('$'); + ++i; + } + --i; + } else { + uintptr_t index = 0; + while (i != format_string.end() && '0' <= *i && *i <= '9') { + index *= 10; + index += *i - '0'; + ++i; + } + --i; + index -= 1; + if (offsets) { + ReplacementOffset r_offset(index, + static_cast(formatted.size())); + r_offsets.insert(std::lower_bound(r_offsets.begin(), + r_offsets.end(), + r_offset, + &CompareParameter), + r_offset); + } + if (index < substitutions) + formatted.append(subst.at(index)); + } + } + } else { + formatted.push_back(*i); + } + } + if (offsets) { + for (std::vector::const_iterator i = r_offsets.begin(); + i != r_offsets.end(); ++i) { + offsets->push_back(i->offset); + } + } + return formatted; +} + +string16 ReplaceStringPlaceholders(const string16& format_string, + const std::vector& subst, + std::vector* offsets) { + return DoReplaceStringPlaceholders(format_string, subst, offsets); +} + +std::string ReplaceStringPlaceholders(const butil::StringPiece& format_string, + const std::vector& subst, + std::vector* offsets) { + return DoReplaceStringPlaceholders(format_string, subst, offsets); +} + +string16 ReplaceStringPlaceholders(const string16& format_string, + const string16& a, + size_t* offset) { + std::vector offsets; + std::vector subst; + subst.push_back(a); + string16 result = ReplaceStringPlaceholders(format_string, subst, &offsets); + + DCHECK_EQ(1U, offsets.size()); + if (offset) + *offset = offsets[0]; + return result; +} + +static bool IsWildcard(base_icu::UChar32 character) { + return character == '*' || character == '?'; +} + +// Move the strings pointers to the point where they start to differ. +template +static void EatSameChars(const CHAR** pattern, const CHAR* pattern_end, + const CHAR** string, const CHAR* string_end, + NEXT next) { + const CHAR* escape = NULL; + while (*pattern != pattern_end && *string != string_end) { + if (!escape && IsWildcard(**pattern)) { + // We don't want to match wildcard here, except if it's escaped. + return; + } + + // Check if the escapement char is found. If so, skip it and move to the + // next character. + if (!escape && **pattern == '\\') { + escape = *pattern; + next(pattern, pattern_end); + continue; + } + + // Check if the chars match, if so, increment the ptrs. + const CHAR* pattern_next = *pattern; + const CHAR* string_next = *string; + base_icu::UChar32 pattern_char = next(&pattern_next, pattern_end); + if (pattern_char == next(&string_next, string_end) && + pattern_char != (base_icu::UChar32) CBU_SENTINEL) { + *pattern = pattern_next; + *string = string_next; + } else { + // Uh ho, it did not match, we are done. If the last char was an + // escapement, that means that it was an error to advance the ptr here, + // let's put it back where it was. This also mean that the MatchPattern + // function will return false because if we can't match an escape char + // here, then no one will. + if (escape) { + *pattern = escape; + } + return; + } + + escape = NULL; + } +} + +template +static void EatWildcard(const CHAR** pattern, const CHAR* end, NEXT next) { + while (*pattern != end) { + if (!IsWildcard(**pattern)) + return; + next(pattern, end); + } +} + +template +static bool MatchPatternT(const CHAR* eval, const CHAR* eval_end, + const CHAR* pattern, const CHAR* pattern_end, + int depth, + NEXT next) { + const int kMaxDepth = 16; + if (depth > kMaxDepth) + return false; + + // Eat all the matching chars. + EatSameChars(&pattern, pattern_end, &eval, eval_end, next); + + // If the string is empty, then the pattern must be empty too, or contains + // only wildcards. + if (eval == eval_end) { + EatWildcard(&pattern, pattern_end, next); + return pattern == pattern_end; + } + + // Pattern is empty but not string, this is not a match. + if (pattern == pattern_end) + return false; + + // If this is a question mark, then we need to compare the rest with + // the current string or the string with one character eaten. + const CHAR* next_pattern = pattern; + next(&next_pattern, pattern_end); + if (pattern[0] == '?') { + if (MatchPatternT(eval, eval_end, next_pattern, pattern_end, + depth + 1, next)) + return true; + const CHAR* next_eval = eval; + next(&next_eval, eval_end); + if (MatchPatternT(next_eval, eval_end, next_pattern, pattern_end, + depth + 1, next)) + return true; + } + + // This is a *, try to match all the possible substrings with the remainder + // of the pattern. + if (pattern[0] == '*') { + // Collapse duplicate wild cards (********** into *) so that the + // method does not recurse unnecessarily. http://crbug.com/52839 + EatWildcard(&next_pattern, pattern_end, next); + + while (eval != eval_end) { + if (MatchPatternT(eval, eval_end, next_pattern, pattern_end, + depth + 1, next)) + return true; + eval++; + } + + // We reached the end of the string, let see if the pattern contains only + // wildcards. + if (eval == eval_end) { + EatWildcard(&pattern, pattern_end, next); + if (pattern != pattern_end) + return false; + return true; + } + } + + return false; +} + +struct NextCharUTF8 { + base_icu::UChar32 operator()(const char** p, const char* end) { + base_icu::UChar32 c; + int offset = 0; + CBU8_NEXT(*p, offset, end - *p, c); + *p += offset; + return c; + } +}; + +struct NextCharUTF16 { + base_icu::UChar32 operator()(const char16** p, const char16* end) { + base_icu::UChar32 c; + int offset = 0; + CBU16_NEXT(*p, offset, end - *p, c); + *p += offset; + return c; + } +}; + +bool MatchPattern(const butil::StringPiece& eval, + const butil::StringPiece& pattern) { + return MatchPatternT(eval.data(), eval.data() + eval.size(), + pattern.data(), pattern.data() + pattern.size(), + 0, NextCharUTF8()); +} + +bool MatchPattern(const string16& eval, const string16& pattern) { + return MatchPatternT(eval.c_str(), eval.c_str() + eval.size(), + pattern.c_str(), pattern.c_str() + pattern.size(), + 0, NextCharUTF16()); +} + +// The following code is compatible with the OpenBSD lcpy interface. See: +// http://www.gratisoft.us/todd/papers/strlcpy.html +// ftp://ftp.openbsd.org/pub/OpenBSD/src/lib/libc/string/{wcs,str}lcpy.c + +namespace { + +template +size_t lcpyT(CHAR* dst, const CHAR* src, size_t dst_size) { + for (size_t i = 0; i < dst_size; ++i) { + if ((dst[i] = src[i]) == 0) // We hit and copied the terminating NULL. + return i; + } + + // We were left off at dst_size. We over copied 1 byte. Null terminate. + if (dst_size != 0) + dst[dst_size - 1] = 0; + + // Count the rest of the |src|, and return it's length in characters. + while (src[dst_size]) ++dst_size; + return dst_size; +} + +} // namespace + +size_t butil::strlcpy(char* dst, const char* src, size_t dst_size) { + return lcpyT(dst, src, dst_size); +} +size_t butil::wcslcpy(wchar_t* dst, const wchar_t* src, size_t dst_size) { + return lcpyT(dst, src, dst_size); +} diff --git a/src/butil/strings/string_util.h b/src/butil/strings/string_util.h new file mode 100644 index 0000000..bd3328a --- /dev/null +++ b/src/butil/strings/string_util.h @@ -0,0 +1,556 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This file defines utility functions for working with strings. + +#ifndef BUTIL_STRINGS_STRING_UTIL_H_ +#define BUTIL_STRINGS_STRING_UTIL_H_ + +#include +#include // va_list + +#include +#include +#include +#include + +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/compiler_specific.h" +#include "butil/strings/string16.h" +#include "butil/strings/string_piece.h" // For implicit conversions. + +namespace butil { + +// C standard-library functions like "strncasecmp" and "snprintf" that aren't +// cross-platform are provided as "butil::strncasecmp", and their prototypes +// are listed below. These functions are then implemented as inline calls +// to the platform-specific equivalents in the platform-specific headers. + +// Compares the two strings s1 and s2 without regard to case using +// the current locale; returns 0 if they are equal, 1 if s1 > s2, and -1 if +// s2 > s1 according to a lexicographic comparison. +int strcasecmp(const char* s1, const char* s2); + +// Compares up to count characters of s1 and s2 without regard to case using +// the current locale; returns 0 if they are equal, 1 if s1 > s2, and -1 if +// s2 > s1 according to a lexicographic comparison. +int strncasecmp(const char* s1, const char* s2, size_t count); + +// Same as strncmp but for char16 strings. +int strncmp16(const char16* s1, const char16* s2, size_t count); + +// Wrapper for vsnprintf that always null-terminates and always returns the +// number of characters that would be in an untruncated formatted +// string, even when truncation occurs. +int vsnprintf(char* buffer, size_t size, const char* format, va_list arguments) + PRINTF_FORMAT(3, 0); + +// Some of these implementations need to be inlined. + +// We separate the declaration from the implementation of this inline +// function just so the PRINTF_FORMAT works. +inline int snprintf(char* buffer, size_t size, const char* format, ...) + PRINTF_FORMAT(3, 4); +inline int snprintf(char* buffer, size_t size, const char* format, ...) { + va_list arguments; + va_start(arguments, format); + int result = vsnprintf(buffer, size, format, arguments); + va_end(arguments); + return result; +} + +// BSD-style safe and consistent string copy functions. +// Copies |src| to |dst|, where |dst_size| is the total allocated size of |dst|. +// Copies at most |dst_size|-1 characters, and always NULL terminates |dst|, as +// long as |dst_size| is not 0. Returns the length of |src| in characters. +// If the return value is >= dst_size, then the output was truncated. +// NOTE: All sizes are in number of characters, NOT in bytes. +BUTIL_EXPORT size_t strlcpy(char* dst, const char* src, size_t dst_size); +BUTIL_EXPORT size_t wcslcpy(wchar_t* dst, const wchar_t* src, size_t dst_size); + +// Scan a wprintf format string to determine whether it's portable across a +// variety of systems. This function only checks that the conversion +// specifiers used by the format string are supported and have the same meaning +// on a variety of systems. It doesn't check for other errors that might occur +// within a format string. +// +// Nonportable conversion specifiers for wprintf are: +// - 's' and 'c' without an 'l' length modifier. %s and %c operate on char +// data on all systems except Windows, which treat them as wchar_t data. +// Use %ls and %lc for wchar_t data instead. +// - 'S' and 'C', which operate on wchar_t data on all systems except Windows, +// which treat them as char data. Use %ls and %lc for wchar_t data +// instead. +// - 'F', which is not identified by Windows wprintf documentation. +// - 'D', 'O', and 'U', which are deprecated and not available on all systems. +// Use %ld, %lo, and %lu instead. +// +// Note that there is no portable conversion specifier for char data when +// working with wprintf. +// +// This function is intended to be called from butil::vswprintf. +BUTIL_EXPORT bool IsWprintfFormatPortable(const wchar_t* format); + +// ASCII-specific tolower. The standard library's tolower is locale sensitive, +// so we don't want to use it here. +template inline Char ToLowerASCII(Char c) { + return (c >= 'A' && c <= 'Z') ? (c + ('a' - 'A')) : c; +} + +// ASCII-specific toupper. The standard library's toupper is locale sensitive, +// so we don't want to use it here. +template inline Char ToUpperASCII(Char c) { + return (c >= 'a' && c <= 'z') ? (c + ('A' - 'a')) : c; +} + +// Function objects to aid in comparing/searching strings. + +template struct CaseInsensitiveCompare { + public: + bool operator()(Char x, Char y) const { + // TODO(darin): Do we really want to do locale sensitive comparisons here? + // See http://crbug.com/24917 + return tolower(x) == tolower(y); + } +}; + +template struct CaseInsensitiveCompareASCII { + public: + bool operator()(Char x, Char y) const { + return ToLowerASCII(x) == ToLowerASCII(y); + } +}; + +// These threadsafe functions return references to globally unique empty +// strings. +// +// It is likely faster to construct a new empty string object (just a few +// instructions to set the length to 0) than to get the empty string singleton +// returned by these functions (which requires threadsafe singleton access). +// +// Therefore, DO NOT USE THESE AS A GENERAL-PURPOSE SUBSTITUTE FOR DEFAULT +// CONSTRUCTORS. There is only one case where you should use these: functions +// which need to return a string by reference (e.g. as a class member +// accessor), and don't have an empty string to use (e.g. in an error case). +// These should not be used as initializers, function arguments, or return +// values for functions which return by value or outparam. +BUTIL_EXPORT const std::string& EmptyString(); +BUTIL_EXPORT const string16& EmptyString16(); + +// Contains the set of characters representing whitespace in the corresponding +// encoding. Null-terminated. +BUTIL_EXPORT extern const wchar_t kWhitespaceWide[]; +BUTIL_EXPORT extern const char16 kWhitespaceUTF16[]; +BUTIL_EXPORT extern const char kWhitespaceASCII[]; + +// Null-terminated string representing the UTF-8 byte order mark. +BUTIL_EXPORT extern const char kUtf8ByteOrderMark[]; + +// Removes characters in |remove_chars| from anywhere in |input|. Returns true +// if any characters were removed. |remove_chars| must be null-terminated. +// NOTE: Safe to use the same variable for both |input| and |output|. +BUTIL_EXPORT bool RemoveChars(const string16& input, + const butil::StringPiece16& remove_chars, + string16* output); +BUTIL_EXPORT bool RemoveChars(const std::string& input, + const butil::StringPiece& remove_chars, + std::string* output); + +// Replaces characters in |replace_chars| from anywhere in |input| with +// |replace_with|. Each character in |replace_chars| will be replaced with +// the |replace_with| string. Returns true if any characters were replaced. +// |replace_chars| must be null-terminated. +// NOTE: Safe to use the same variable for both |input| and |output|. +BUTIL_EXPORT bool ReplaceChars(const string16& input, + const butil::StringPiece16& replace_chars, + const string16& replace_with, + string16* output); +BUTIL_EXPORT bool ReplaceChars(const std::string& input, + const butil::StringPiece& replace_chars, + const std::string& replace_with, + std::string* output); + +// Removes characters in |trim_chars| from the beginning and end of |input|. +// |trim_chars| must be null-terminated. +// NOTE: Safe to use the same variable for both |input| and |output|. +BUTIL_EXPORT bool TrimString(const string16& input, + const butil::StringPiece16& trim_chars, + string16* output); +BUTIL_EXPORT bool TrimString(const std::string& input, + const butil::StringPiece& trim_chars, + std::string* output); + +// Truncates a string to the nearest UTF-8 character that will leave +// the string less than or equal to the specified byte size. +BUTIL_EXPORT void TruncateUTF8ToByteSize(const std::string& input, + const size_t byte_size, + std::string* output); + +// Trims any whitespace from either end of the input string. Returns where +// whitespace was found. +// The non-wide version has two functions: +// * TrimWhitespaceASCII() +// This function is for ASCII strings and only looks for ASCII whitespace; +// Please choose the best one according to your usage. +// NOTE: Safe to use the same variable for both input and output. +enum TrimPositions { + TRIM_NONE = 0, + TRIM_LEADING = 1 << 0, + TRIM_TRAILING = 1 << 1, + TRIM_ALL = TRIM_LEADING | TRIM_TRAILING, +}; +BUTIL_EXPORT TrimPositions TrimWhitespace(const string16& input, + TrimPositions positions, + butil::string16* output); +BUTIL_EXPORT TrimPositions TrimWhitespace(const butil::StringPiece16& input, + TrimPositions positions, + butil::StringPiece16* output); +BUTIL_EXPORT TrimPositions TrimWhitespaceASCII(const std::string& input, + TrimPositions positions, + std::string* output); +BUTIL_EXPORT TrimPositions TrimWhitespaceASCII(const butil::StringPiece& input, + TrimPositions positions, + butil::StringPiece* output); + +// Deprecated. This function is only for backward compatibility and calls +// TrimWhitespaceASCII(). +BUTIL_EXPORT TrimPositions TrimWhitespace(const std::string& input, + TrimPositions positions, + std::string* output); +BUTIL_EXPORT TrimPositions TrimWhitespace(const butil::StringPiece& input, + TrimPositions positions, + butil::StringPiece* output); + +// Searches for CR or LF characters. Removes all contiguous whitespace +// strings that contain them. This is useful when trying to deal with text +// copied from terminals. +// Returns |text|, with the following three transformations: +// (1) Leading and trailing whitespace is trimmed. +// (2) If |trim_sequences_with_line_breaks| is true, any other whitespace +// sequences containing a CR or LF are trimmed. +// (3) All other whitespace sequences are converted to single spaces. +BUTIL_EXPORT string16 CollapseWhitespace( + const string16& text, + bool trim_sequences_with_line_breaks); +BUTIL_EXPORT std::string CollapseWhitespaceASCII( + const std::string& text, + bool trim_sequences_with_line_breaks); + +// Returns true if |input| is empty or contains only characters found in +// |characters|. +BUTIL_EXPORT bool ContainsOnlyChars(const StringPiece& input, + const StringPiece& characters); +BUTIL_EXPORT bool ContainsOnlyChars(const StringPiece16& input, + const StringPiece16& characters); + +// Returns true if the specified string matches the criteria. How can a wide +// string be 8-bit or UTF8? It contains only characters that are < 256 (in the +// first case) or characters that use only 8-bits and whose 8-bit +// representation looks like a UTF-8 string (the second case). +// +// Note that IsStringUTF8 checks not only if the input is structurally +// valid but also if it doesn't contain any non-character codepoint +// (e.g. U+FFFE). It's done on purpose because all the existing callers want +// to have the maximum 'discriminating' power from other encodings. If +// there's a use case for just checking the structural validity, we have to +// add a new function for that. +BUTIL_EXPORT bool IsStringUTF8(const StringPiece& str); +BUTIL_EXPORT bool IsStringASCII(const StringPiece& str); +BUTIL_EXPORT bool IsStringASCII(const string16& str); + +inline std::string EnsureString(const std::string& s) { + return s; +} + +inline std::string EnsureString(std::string&& s) { + return std::move(s); +} + +inline std::string EnsureString(const char* s) { + return s ? std::string(s) : std::string(); +} + +// Enabled only when std::string is constructible from T. +template ::value>::type> +inline std::string EnsureString(T&& v) { + return std::string(std::forward(v)); +} + +} // namespace butil + +#if defined(OS_WIN) +#include "butil/strings/string_util_win.h" +#elif defined(OS_POSIX) +#include "butil/strings/string_util_posix.h" +#else +#error Define string operations appropriately for your platform +#endif + +// Converts the elements of the given string. This version uses a pointer to +// clearly differentiate it from the non-pointer variant. +template inline void StringToLowerASCII(str* s) { + for (typename str::iterator i = s->begin(); i != s->end(); ++i) + *i = butil::ToLowerASCII(*i); +} + +template inline str StringToLowerASCII(const str& s) { + // for std::string and std::wstring + str output(s); + StringToLowerASCII(&output); + return output; +} + +// Converts the elements of the given string. This version uses a pointer to +// clearly differentiate it from the non-pointer variant. +template inline void StringToUpperASCII(str* s) { + for (typename str::iterator i = s->begin(); i != s->end(); ++i) + *i = butil::ToUpperASCII(*i); +} + +template inline str StringToUpperASCII(const str& s) { + // for std::string and std::wstring + str output(s); + StringToUpperASCII(&output); + return output; +} + +// Compare the lower-case form of the given string against the given ASCII +// string. This is useful for doing checking if an input string matches some +// token, and it is optimized to avoid intermediate string copies. This API is +// borrowed from the equivalent APIs in Mozilla. +BUTIL_EXPORT bool LowerCaseEqualsASCII(const std::string& a, const char* b); +BUTIL_EXPORT bool LowerCaseEqualsASCII(const butil::string16& a, const char* b); + +// Same thing, but with string iterators instead. +BUTIL_EXPORT bool LowerCaseEqualsASCII(std::string::const_iterator a_begin, + std::string::const_iterator a_end, + const char* b); +BUTIL_EXPORT bool LowerCaseEqualsASCII(butil::string16::const_iterator a_begin, + butil::string16::const_iterator a_end, + const char* b); +BUTIL_EXPORT bool LowerCaseEqualsASCII(const char* a_begin, + const char* a_end, + const char* b); +BUTIL_EXPORT bool LowerCaseEqualsASCII(const butil::char16* a_begin, + const butil::char16* a_end, + const char* b); + +// Performs a case-sensitive string compare. The behavior is undefined if both +// strings are not ASCII. +BUTIL_EXPORT bool EqualsASCII(const butil::string16& a, const butil::StringPiece& b); + +// Returns true if str starts with search, or false otherwise. +BUTIL_EXPORT bool StartsWithASCII(const std::string& str, + const std::string& search, + bool case_sensitive); +BUTIL_EXPORT bool StartsWith(const butil::string16& str, + const butil::string16& search, + bool case_sensitive); + +// Returns true if str ends with search, or false otherwise. +BUTIL_EXPORT bool EndsWith(const std::string& str, + const std::string& search, + bool case_sensitive); +BUTIL_EXPORT bool EndsWith(const butil::string16& str, + const butil::string16& search, + bool case_sensitive); + + +// Determines the type of ASCII character, independent of locale (the C +// library versions will change based on locale). +template +inline bool IsAsciiWhitespace(Char c) { + return c == ' ' || c == '\r' || c == '\n' || c == '\t'; +} +template +inline bool IsAsciiAlpha(Char c) { + return ((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')); +} +template +inline bool IsAsciiDigit(Char c) { + return c >= '0' && c <= '9'; +} + +template +inline bool IsHexDigit(Char c) { + return (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'F') || + (c >= 'a' && c <= 'f'); +} + +template +inline Char HexDigitToInt(Char c) { + DCHECK(IsHexDigit(c)); + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return 0; +} + +// Returns true if it's a whitespace character. +inline bool IsWhitespace(wchar_t c) { + return wcschr(butil::kWhitespaceWide, c) != NULL; +} + +inline bool IsBlankString(const butil::StringPiece &s) { + return butil::ContainsOnlyChars(s, " \r\n\t"); +} + +// Return a byte string in human-readable format with a unit suffix. Not +// appropriate for use in any UI; use of FormatBytes and friends in ui/base is +// highly recommended instead. TODO(avi): Figure out how to get callers to use +// FormatBytes instead; remove this. +BUTIL_EXPORT butil::string16 FormatBytesUnlocalized(int64_t bytes); + +// Starting at |start_offset| (usually 0), replace the first instance of +// |find_this| with |replace_with|. +BUTIL_EXPORT void ReplaceFirstSubstringAfterOffset( + butil::string16* str, + size_t start_offset, + const butil::string16& find_this, + const butil::string16& replace_with); +BUTIL_EXPORT void ReplaceFirstSubstringAfterOffset( + std::string* str, + size_t start_offset, + const std::string& find_this, + const std::string& replace_with); + +// Starting at |start_offset| (usually 0), look through |str| and replace all +// instances of |find_this| with |replace_with|. +// +// This does entire substrings; use std::replace in for single +// characters, for example: +// std::replace(str.begin(), str.end(), 'a', 'b'); +BUTIL_EXPORT void ReplaceSubstringsAfterOffset( + butil::string16* str, + size_t start_offset, + const butil::string16& find_this, + const butil::string16& replace_with); +BUTIL_EXPORT void ReplaceSubstringsAfterOffset(std::string* str, + size_t start_offset, + const std::string& find_this, + const std::string& replace_with); + +// Reserves enough memory in |str| to accommodate |length_with_null| characters, +// sets the size of |str| to |length_with_null - 1| characters, and returns a +// pointer to the underlying contiguous array of characters. This is typically +// used when calling a function that writes results into a character array, but +// the caller wants the data to be managed by a string-like object. It is +// convenient in that is can be used inline in the call, and fast in that it +// avoids copying the results of the call from a char* into a string. +// +// |length_with_null| must be at least 2, since otherwise the underlying string +// would have size 0, and trying to access &((*str)[0]) in that case can result +// in a number of problems. +// +// Internally, this takes linear time because the resize() call 0-fills the +// underlying array for potentially all +// (|length_with_null - 1| * sizeof(string_type::value_type)) bytes. Ideally we +// could avoid this aspect of the resize() call, as we expect the caller to +// immediately write over this memory, but there is no other way to set the size +// of the string, and not doing that will mean people who access |str| rather +// than str.c_str() will get back a string of whatever size |str| had on entry +// to this function (probably 0). +template +inline typename string_type::value_type* WriteInto(string_type* str, + size_t length_with_null) { + DCHECK_GT(length_with_null, 1u); + str->reserve(length_with_null); + str->resize(length_with_null - 1); + return &((*str)[0]); +} + +//----------------------------------------------------------------------------- + +// Splits a string into its fields delimited by any of the characters in +// |delimiters|. Each field is added to the |tokens| vector. Returns the +// number of tokens found. +BUTIL_EXPORT size_t Tokenize(const butil::string16& str, + const butil::string16& delimiters, + std::vector* tokens); +BUTIL_EXPORT size_t Tokenize(const std::string& str, + const std::string& delimiters, + std::vector* tokens); +BUTIL_EXPORT size_t Tokenize(const butil::StringPiece& str, + const butil::StringPiece& delimiters, + std::vector* tokens); + +// Does the opposite of SplitString(). +BUTIL_EXPORT butil::string16 JoinString(const std::vector& parts, + butil::char16 s); +BUTIL_EXPORT std::string JoinString( + const std::vector& parts, char s); + +// Join |parts| using |separator|. +BUTIL_EXPORT std::string JoinString( + const std::vector& parts, + const std::string& separator); +BUTIL_EXPORT butil::string16 JoinString( + const std::vector& parts, + const butil::string16& separator); + +// Replace $1-$2-$3..$9 in the format string with |a|-|b|-|c|..|i| respectively. +// Additionally, any number of consecutive '$' characters is replaced by that +// number less one. Eg $$->$, $$$->$$, etc. The offsets parameter here can be +// NULL. This only allows you to use up to nine replacements. +BUTIL_EXPORT butil::string16 ReplaceStringPlaceholders( + const butil::string16& format_string, + const std::vector& subst, + std::vector* offsets); + +BUTIL_EXPORT std::string ReplaceStringPlaceholders( + const butil::StringPiece& format_string, + const std::vector& subst, + std::vector* offsets); + +// Single-string shortcut for ReplaceStringHolders. |offset| may be NULL. +BUTIL_EXPORT butil::string16 ReplaceStringPlaceholders( + const butil::string16& format_string, + const butil::string16& a, + size_t* offset); + +// Returns true if the string passed in matches the pattern. The pattern +// string can contain wildcards like * and ? +// The backslash character (\) is an escape character for * and ? +// We limit the patterns to having a max of 16 * or ? characters. +// ? matches 0 or 1 character, while * matches 0 or more characters. +BUTIL_EXPORT bool MatchPattern(const butil::StringPiece& string, + const butil::StringPiece& pattern); +BUTIL_EXPORT bool MatchPattern(const butil::string16& string, + const butil::string16& pattern); + +// Hack to convert any char-like type to its unsigned counterpart. +// For example, it will convert char, signed char and unsigned char to unsigned +// char. +template +struct ToUnsigned { + typedef T Unsigned; +}; + +template<> +struct ToUnsigned { + typedef unsigned char Unsigned; +}; +template<> +struct ToUnsigned { + typedef unsigned char Unsigned; +}; +template<> +struct ToUnsigned { +#if defined(WCHAR_T_IS_UTF16) + typedef unsigned short Unsigned; +#elif defined(WCHAR_T_IS_UTF32) + typedef uint32_t Unsigned; +#endif +}; +template<> +struct ToUnsigned { + typedef unsigned short Unsigned; +}; + +#endif // BUTIL_STRINGS_STRING_UTIL_H_ diff --git a/src/butil/strings/string_util_constants.cc b/src/butil/strings/string_util_constants.cc new file mode 100644 index 0000000..ab05947 --- /dev/null +++ b/src/butil/strings/string_util_constants.cc @@ -0,0 +1,57 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/strings/string_util.h" + +namespace butil { + +#define WHITESPACE_UNICODE \ + 0x0009, /* CHARACTER TABULATION */ \ + 0x000A, /* LINE FEED (LF) */ \ + 0x000B, /* LINE TABULATION */ \ + 0x000C, /* FORM FEED (FF) */ \ + 0x000D, /* CARRIAGE RETURN (CR) */ \ + 0x0020, /* SPACE */ \ + 0x0085, /* NEXT LINE (NEL) */ \ + 0x00A0, /* NO-BREAK SPACE */ \ + 0x1680, /* OGHAM SPACE MARK */ \ + 0x2000, /* EN QUAD */ \ + 0x2001, /* EM QUAD */ \ + 0x2002, /* EN SPACE */ \ + 0x2003, /* EM SPACE */ \ + 0x2004, /* THREE-PER-EM SPACE */ \ + 0x2005, /* FOUR-PER-EM SPACE */ \ + 0x2006, /* SIX-PER-EM SPACE */ \ + 0x2007, /* FIGURE SPACE */ \ + 0x2008, /* PUNCTUATION SPACE */ \ + 0x2009, /* THIN SPACE */ \ + 0x200A, /* HAIR SPACE */ \ + 0x2028, /* LINE SEPARATOR */ \ + 0x2029, /* PARAGRAPH SEPARATOR */ \ + 0x202F, /* NARROW NO-BREAK SPACE */ \ + 0x205F, /* MEDIUM MATHEMATICAL SPACE */ \ + 0x3000, /* IDEOGRAPHIC SPACE */ \ + 0 + +const wchar_t kWhitespaceWide[] = { + WHITESPACE_UNICODE +}; + +const char16 kWhitespaceUTF16[] = { + WHITESPACE_UNICODE +}; + +const char kWhitespaceASCII[] = { + 0x09, // CHARACTER TABULATION + 0x0A, // LINE FEED (LF) + 0x0B, // LINE TABULATION + 0x0C, // FORM FEED (FF) + 0x0D, // CARRIAGE RETURN (CR) + 0x20, // SPACE + 0 +}; + +const char kUtf8ByteOrderMark[] = "\xEF\xBB\xBF"; + +} // namespace butil diff --git a/src/butil/strings/string_util_posix.h b/src/butil/strings/string_util_posix.h new file mode 100644 index 0000000..3496014 --- /dev/null +++ b/src/butil/strings/string_util_posix.h @@ -0,0 +1,52 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_STRINGS_STRING_UTIL_POSIX_H_ +#define BUTIL_STRINGS_STRING_UTIL_POSIX_H_ + +#include +#include +#include +#include + +#include "butil/logging.h" + +namespace butil { + +// Chromium code style is to not use malloc'd strings; this is only for use +// for interaction with APIs that require it. +inline char* strdup(const char* str) { + return ::strdup(str); +} + +inline int strcasecmp(const char* string1, const char* string2) { + return ::strcasecmp(string1, string2); +} + +inline int strncasecmp(const char* string1, const char* string2, size_t count) { + return ::strncasecmp(string1, string2, count); +} + +inline int vsnprintf(char* buffer, size_t size, + const char* format, va_list arguments) { + return ::vsnprintf(buffer, size, format, arguments); +} + +inline int strncmp16(const char16* s1, const char16* s2, size_t count) { +#if defined(WCHAR_T_IS_UTF16) + return ::wcsncmp(s1, s2, count); +#elif defined(WCHAR_T_IS_UTF32) + return c16memcmp(s1, s2, count); +#endif +} + +inline int vswprintf(wchar_t* buffer, size_t size, + const wchar_t* format, va_list arguments) { + DCHECK(IsWprintfFormatPortable(format)); + return ::vswprintf(buffer, size, format, arguments); +} + +} // namespace butil + +#endif // BUTIL_STRINGS_STRING_UTIL_POSIX_H_ diff --git a/src/butil/strings/stringize_macros.h b/src/butil/strings/stringize_macros.h new file mode 100644 index 0000000..ac480d2 --- /dev/null +++ b/src/butil/strings/stringize_macros.h @@ -0,0 +1,31 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This file defines preprocessor macros for stringizing preprocessor +// symbols (or their output) and manipulating preprocessor symbols +// that define strings. + +#ifndef BUTIL_STRINGS_STRINGIZE_MACROS_H_ +#define BUTIL_STRINGS_STRINGIZE_MACROS_H_ + +#include "butil/build_config.h" + +// This is not very useful as it does not expand defined symbols if +// called directly. Use its counterpart without the _NO_EXPANSION +// suffix, below. +#define STRINGIZE_NO_EXPANSION(x) #x + +// Use this to quote the provided parameter, first expanding it if it +// is a preprocessor symbol. +// +// For example, if: +// #define A FOO +// #define B(x) myobj->FunctionCall(x) +// +// Then: +// STRINGIZE(A) produces "FOO" +// STRINGIZE(B(y)) produces "myobj->FunctionCall(y)" +#define STRINGIZE(x) STRINGIZE_NO_EXPANSION(x) + +#endif // BUTIL_STRINGS_STRINGIZE_MACROS_H_ diff --git a/src/butil/strings/stringprintf.cc b/src/butil/strings/stringprintf.cc new file mode 100644 index 0000000..0ca6366 --- /dev/null +++ b/src/butil/strings/stringprintf.cc @@ -0,0 +1,190 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/strings/stringprintf.h" + +#include + +#include "butil/scoped_clear_errno.h" +#include "butil/strings/string_util.h" +#include "butil/strings/utf_string_conversions.h" + +// gcc7 reports that the first arg to vsnprintfT in StringAppendVT is NULL, +// which I can't figure out why, turn off the warning right now. +#if defined(__GNUC__) && __GNUC__ >= 7 +#pragma GCC diagnostic warning "-Wformat-truncation=0" +#endif + +namespace butil { + +namespace { + +// Overloaded wrappers around vsnprintf and vswprintf. The buf_size parameter +// is the size of the buffer. These return the number of characters in the +// formatted string excluding the NUL terminator. If the buffer is not +// large enough to accommodate the formatted string without truncation, they +// return the number of characters that would be in the fully-formatted string +// (vsnprintf, and vswprintf on Windows), or -1 (vswprintf on POSIX platforms). +inline int vsnprintfT(char* buffer, + size_t buf_size, + const char* format, + va_list argptr) { + return butil::vsnprintf(buffer, buf_size, format, argptr); +} + +#if !defined(OS_ANDROID) +inline int vsnprintfT(wchar_t* buffer, + size_t buf_size, + const wchar_t* format, + va_list argptr) { + return butil::vswprintf(buffer, buf_size, format, argptr); +} +#endif + +// Templatized backend for StringPrintF/StringAppendF. This does not finalize +// the va_list, the caller is expected to do that. +template +static void StringAppendVT(StringType* dst, + const typename StringType::value_type* format, + va_list ap) { + // First try with a small fixed size buffer. + // This buffer size should be kept in sync with StringUtilTest.GrowBoundary + // and StringUtilTest.StringPrintfBounds. + typename StringType::value_type stack_buf[1024]; + + va_list ap_copy; + GG_VA_COPY(ap_copy, ap); + +#if !defined(OS_WIN) + ScopedClearErrno clear_errno; +#endif + int result = vsnprintfT(stack_buf, arraysize(stack_buf), format, ap_copy); + va_end(ap_copy); + + if (result >= 0 && result < static_cast(arraysize(stack_buf))) { + // It fit. + dst->append(stack_buf, result); + return; + } + + // Repeatedly increase buffer size until it fits. + int mem_length = arraysize(stack_buf); + while (true) { + if (result < 0) { +#if defined(OS_WIN) + // On Windows, vsnprintfT always returns the number of characters in a + // fully-formatted string, so if we reach this point, something else is + // wrong and no amount of buffer-doubling is going to fix it. + return; +#else + if (errno != 0 && errno != EOVERFLOW && errno != E2BIG) + return; + // Try doubling the buffer size. + mem_length *= 2; +#endif + } else { + // We need exactly "result + 1" characters. + mem_length = result + 1; + } + + if (mem_length > 32 * 1024 * 1024) { + // That should be plenty, don't try anything larger. This protects + // against huge allocations when using vsnprintfT implementations that + // return -1 for reasons other than overflow without setting errno. + DLOG(WARNING) << "Unable to printf the requested string due to size."; + return; + } + + std::vector mem_buf(mem_length); + + // NOTE: You can only use a va_list once. Since we're in a while loop, we + // need to make a new copy each time so we don't use up the original. + GG_VA_COPY(ap_copy, ap); + result = vsnprintfT(&mem_buf[0], mem_length, format, ap_copy); + va_end(ap_copy); + + if ((result >= 0) && (result < mem_length)) { + // It fit. + dst->append(&mem_buf[0], result); + return; + } + } +} + +} // namespace + +std::string StringPrintf(const char* format, ...) { + va_list ap; + va_start(ap, format); + std::string result; + StringAppendV(&result, format, ap); + va_end(ap); + return result; +} + +#if !defined(OS_ANDROID) +std::wstring StringPrintf(const wchar_t* format, ...) { + va_list ap; + va_start(ap, format); + std::wstring result; + StringAppendV(&result, format, ap); + va_end(ap); + return result; +} +#endif + +std::string StringPrintV(const char* format, va_list ap) { + std::string result; + StringAppendV(&result, format, ap); + return result; +} + +const std::string& SStringPrintf(std::string* dst, const char* format, ...) { + va_list ap; + va_start(ap, format); + dst->clear(); + StringAppendV(dst, format, ap); + va_end(ap); + return *dst; +} + +#if !defined(OS_ANDROID) +const std::wstring& SStringPrintf(std::wstring* dst, + const wchar_t* format, ...) { + va_list ap; + va_start(ap, format); + dst->clear(); + StringAppendV(dst, format, ap); + va_end(ap); + return *dst; +} +#endif + +void StringAppendF(std::string* dst, const char* format, ...) { + va_list ap; + va_start(ap, format); + StringAppendV(dst, format, ap); + va_end(ap); +} + +#if !defined(OS_ANDROID) +void StringAppendF(std::wstring* dst, const wchar_t* format, ...) { + va_list ap; + va_start(ap, format); + StringAppendV(dst, format, ap); + va_end(ap); +} +#endif + +void StringAppendV(std::string* dst, const char* format, va_list ap) { + StringAppendVT(dst, format, ap); +} + +#if !defined(OS_ANDROID) +void StringAppendV(std::wstring* dst, const wchar_t* format, va_list ap) { + StringAppendVT(dst, format, ap); +} +#endif + +} // namespace butil diff --git a/src/butil/strings/stringprintf.h b/src/butil/strings/stringprintf.h new file mode 100644 index 0000000..9b744dc --- /dev/null +++ b/src/butil/strings/stringprintf.h @@ -0,0 +1,62 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_STRINGS_STRINGPRINTF_H_ +#define BUTIL_STRINGS_STRINGPRINTF_H_ + +#include // va_list + +#include + +#include "butil/base_export.h" +#include "butil/compiler_specific.h" + +namespace butil { + +// Return a C++ string given printf-like input. +BUTIL_EXPORT std::string StringPrintf(const char* format, ...) + PRINTF_FORMAT(1, 2); +// OS_ANDROID's libc does not support wchar_t, so several overloads are omitted. +#if !defined(OS_ANDROID) +BUTIL_EXPORT std::wstring StringPrintf(const wchar_t* format, ...) + WPRINTF_FORMAT(1, 2); +#endif + +// Return a C++ string given vprintf-like input. +BUTIL_EXPORT std::string StringPrintV(const char* format, va_list ap) + PRINTF_FORMAT(1, 0); + +// Store result into a supplied string and return it. +BUTIL_EXPORT const std::string& SStringPrintf(std::string* dst, + const char* format, ...) + PRINTF_FORMAT(2, 3); +#if !defined(OS_ANDROID) +BUTIL_EXPORT const std::wstring& SStringPrintf(std::wstring* dst, + const wchar_t* format, ...) + WPRINTF_FORMAT(2, 3); +#endif + +// Append result to a supplied string. +BUTIL_EXPORT void StringAppendF(std::string* dst, const char* format, ...) + PRINTF_FORMAT(2, 3); +#if !defined(OS_ANDROID) +// TODO(evanm): this is only used in a few places in the code; +// replace with string16 version. +BUTIL_EXPORT void StringAppendF(std::wstring* dst, const wchar_t* format, ...) + WPRINTF_FORMAT(2, 3); +#endif + +// Lower-level routine that takes a va_list and appends to a specified +// string. All other routines are just convenience wrappers around it. +BUTIL_EXPORT void StringAppendV(std::string* dst, const char* format, va_list ap) + PRINTF_FORMAT(2, 0); +#if !defined(OS_ANDROID) +BUTIL_EXPORT void StringAppendV(std::wstring* dst, + const wchar_t* format, va_list ap) + WPRINTF_FORMAT(2, 0); +#endif + +} // namespace butil + +#endif // BUTIL_STRINGS_STRINGPRINTF_H_ diff --git a/src/butil/strings/sys_string_conversions.h b/src/butil/strings/sys_string_conversions.h new file mode 100644 index 0000000..7316c5e --- /dev/null +++ b/src/butil/strings/sys_string_conversions.h @@ -0,0 +1,83 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_STRINGS_SYS_STRING_CONVERSIONS_H_ +#define BUTIL_STRINGS_SYS_STRING_CONVERSIONS_H_ + +// Provides system-dependent string type conversions for cases where it's +// necessary to not use ICU. Generally, you should not need this in Chrome, +// but it is used in some shared code. Dependencies should be minimal. + +#include + +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/strings/string16.h" +#include "butil/strings/string_piece.h" + +#if defined(OS_MACOSX) +#include +#ifdef __OBJC__ +@class NSString; +#else +class NSString; +#endif +#endif // OS_MACOSX + +namespace butil { + +// Converts between wide and UTF-8 representations of a string. On error, the +// result is system-dependent. +BUTIL_EXPORT std::string SysWideToUTF8(const std::wstring& wide); +BUTIL_EXPORT std::wstring SysUTF8ToWide(const StringPiece& utf8); + +// Converts between wide and the system multi-byte representations of a string. +// DANGER: This will lose information and can change (on Windows, this can +// change between reboots). +BUTIL_EXPORT std::string SysWideToNativeMB(const std::wstring& wide); +BUTIL_EXPORT std::wstring SysNativeMBToWide(const StringPiece& native_mb); + +// Windows-specific ------------------------------------------------------------ + +#if defined(OS_WIN) + +// Converts between 8-bit and wide strings, using the given code page. The +// code page identifier is one accepted by the Windows function +// MultiByteToWideChar(). +BUTIL_EXPORT std::wstring SysMultiByteToWide(const StringPiece& mb, + uint32_t code_page); +BUTIL_EXPORT std::string SysWideToMultiByte(const std::wstring& wide, + uint32_t code_page); + +#endif // defined(OS_WIN) + +// Mac-specific ---------------------------------------------------------------- + +#if defined(OS_MACOSX) + +// Converts between STL strings and CFStringRefs/NSStrings. + +// Creates a string, and returns it with a refcount of 1. You are responsible +// for releasing it. Returns NULL on failure. +BUTIL_EXPORT CFStringRef SysUTF8ToCFStringRef(const std::string& utf8); +BUTIL_EXPORT CFStringRef SysUTF16ToCFStringRef(const string16& utf16); + +// Same, but returns an autoreleased NSString. +BUTIL_EXPORT NSString* SysUTF8ToNSString(const std::string& utf8); +BUTIL_EXPORT NSString* SysUTF16ToNSString(const string16& utf16); + +// Converts a CFStringRef to an STL string. Returns an empty string on failure. +BUTIL_EXPORT std::string SysCFStringRefToUTF8(CFStringRef ref); +BUTIL_EXPORT string16 SysCFStringRefToUTF16(CFStringRef ref); + +// Same, but accepts NSString input. Converts nil NSString* to the appropriate +// string type of length 0. +BUTIL_EXPORT std::string SysNSStringToUTF8(NSString* ref); +BUTIL_EXPORT string16 SysNSStringToUTF16(NSString* ref); + +#endif // defined(OS_MACOSX) + +} // namespace butil + +#endif // BUTIL_STRINGS_SYS_STRING_CONVERSIONS_H_ diff --git a/src/butil/strings/sys_string_conversions_mac.mm b/src/butil/strings/sys_string_conversions_mac.mm new file mode 100644 index 0000000..804b614 --- /dev/null +++ b/src/butil/strings/sys_string_conversions_mac.mm @@ -0,0 +1,186 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/strings/sys_string_conversions.h" + +#import + +#include + +#include "butil/mac/foundation_util.h" +#include "butil/mac/scoped_cftyperef.h" +#include "butil/strings/string_piece.h" + +namespace butil { + +namespace { + +// Convert the supplied CFString into the specified encoding, and return it as +// an STL string of the template type. Returns an empty string on failure. +// +// Do not assert in this function since it is used by the asssertion code! +template +static StringType CFStringToSTLStringWithEncodingT(CFStringRef cfstring, + CFStringEncoding encoding) { + CFIndex length = CFStringGetLength(cfstring); + if (length == 0) + return StringType(); + + CFRange whole_string = CFRangeMake(0, length); + CFIndex out_size; + CFIndex converted = CFStringGetBytes(cfstring, + whole_string, + encoding, + 0, // lossByte + false, // isExternalRepresentation + NULL, // buffer + 0, // maxBufLen + &out_size); + if (converted == 0 || out_size == 0) + return StringType(); + + // out_size is the number of UInt8-sized units needed in the destination. + // A buffer allocated as UInt8 units might not be properly aligned to + // contain elements of StringType::value_type. Use a container for the + // proper value_type, and convert out_size by figuring the number of + // value_type elements per UInt8. Leave room for a NUL terminator. + typename StringType::size_type elements = + out_size * sizeof(UInt8) / sizeof(typename StringType::value_type) + 1; + + std::vector out_buffer(elements); + converted = CFStringGetBytes(cfstring, + whole_string, + encoding, + 0, // lossByte + false, // isExternalRepresentation + reinterpret_cast(&out_buffer[0]), + out_size, + NULL); // usedBufLen + if (converted == 0) + return StringType(); + + out_buffer[elements - 1] = '\0'; + return StringType(&out_buffer[0], elements - 1); +} + +// Given an STL string |in| with an encoding specified by |in_encoding|, +// convert it to |out_encoding| and return it as an STL string of the +// |OutStringType| template type. Returns an empty string on failure. +// +// Do not assert in this function since it is used by the asssertion code! +template +static OutStringType STLStringToSTLStringWithEncodingsT( + const InStringType& in, + CFStringEncoding in_encoding, + CFStringEncoding out_encoding) { + typename InStringType::size_type in_length = in.length(); + if (in_length == 0) + return OutStringType(); + + butil::ScopedCFTypeRef cfstring(CFStringCreateWithBytesNoCopy( + NULL, + reinterpret_cast(in.data()), + in_length * sizeof(typename InStringType::value_type), + in_encoding, + false, + kCFAllocatorNull)); + if (!cfstring) + return OutStringType(); + + return CFStringToSTLStringWithEncodingT(cfstring, + out_encoding); +} + +// Given an STL string |in| with an encoding specified by |in_encoding|, +// return it as a CFStringRef. Returns NULL on failure. +template +static CFStringRef STLStringToCFStringWithEncodingsT( + const StringType& in, + CFStringEncoding in_encoding) { + typename StringType::size_type in_length = in.length(); + if (in_length == 0) + return CFSTR(""); + + return CFStringCreateWithBytes(kCFAllocatorDefault, + reinterpret_cast(in.data()), + in_length * + sizeof(typename StringType::value_type), + in_encoding, + false); +} + +// Specify the byte ordering explicitly, otherwise CFString will be confused +// when strings don't carry BOMs, as they typically won't. +static const CFStringEncoding kNarrowStringEncoding = kCFStringEncodingUTF8; +#ifdef __BIG_ENDIAN__ +static const CFStringEncoding kMediumStringEncoding = kCFStringEncodingUTF16BE; +static const CFStringEncoding kWideStringEncoding = kCFStringEncodingUTF32BE; +#elif defined(__LITTLE_ENDIAN__) +static const CFStringEncoding kMediumStringEncoding = kCFStringEncodingUTF16LE; +static const CFStringEncoding kWideStringEncoding = kCFStringEncodingUTF32LE; +#endif // __LITTLE_ENDIAN__ + +} // namespace + +// Do not assert in this function since it is used by the asssertion code! +std::string SysWideToUTF8(const std::wstring& wide) { + return STLStringToSTLStringWithEncodingsT( + wide, kWideStringEncoding, kNarrowStringEncoding); +} + +// Do not assert in this function since it is used by the asssertion code! +std::wstring SysUTF8ToWide(const StringPiece& utf8) { + return STLStringToSTLStringWithEncodingsT( + utf8, kNarrowStringEncoding, kWideStringEncoding); +} + +std::string SysWideToNativeMB(const std::wstring& wide) { + return SysWideToUTF8(wide); +} + +std::wstring SysNativeMBToWide(const StringPiece& native_mb) { + return SysUTF8ToWide(native_mb); +} + +CFStringRef SysUTF8ToCFStringRef(const std::string& utf8) { + return STLStringToCFStringWithEncodingsT(utf8, kNarrowStringEncoding); +} + +CFStringRef SysUTF16ToCFStringRef(const string16& utf16) { + return STLStringToCFStringWithEncodingsT(utf16, kMediumStringEncoding); +} + +NSString* SysUTF8ToNSString(const std::string& utf8) { + return (NSString*)butil::mac::CFTypeRefToNSObjectAutorelease( + SysUTF8ToCFStringRef(utf8)); +} + +NSString* SysUTF16ToNSString(const string16& utf16) { + return (NSString*)butil::mac::CFTypeRefToNSObjectAutorelease( + SysUTF16ToCFStringRef(utf16)); +} + +std::string SysCFStringRefToUTF8(CFStringRef ref) { + return CFStringToSTLStringWithEncodingT(ref, + kNarrowStringEncoding); +} + +string16 SysCFStringRefToUTF16(CFStringRef ref) { + return CFStringToSTLStringWithEncodingT(ref, + kMediumStringEncoding); +} + +std::string SysNSStringToUTF8(NSString* nsstring) { + if (!nsstring) + return std::string(); + return SysCFStringRefToUTF8(reinterpret_cast(nsstring)); +} + +string16 SysNSStringToUTF16(NSString* nsstring) { + if (!nsstring) + return string16(); + return SysCFStringRefToUTF16(reinterpret_cast(nsstring)); +} + +} // namespace butil diff --git a/src/butil/strings/sys_string_conversions_posix.cc b/src/butil/strings/sys_string_conversions_posix.cc new file mode 100644 index 0000000..1255b4d --- /dev/null +++ b/src/butil/strings/sys_string_conversions_posix.cc @@ -0,0 +1,161 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/strings/sys_string_conversions.h" + +#include + +#include "butil/strings/string_piece.h" +#include "butil/strings/utf_string_conversions.h" + +namespace butil { + +std::string SysWideToUTF8(const std::wstring& wide) { + // In theory this should be using the system-provided conversion rather + // than our ICU, but this will do for now. + return WideToUTF8(wide); +} +std::wstring SysUTF8ToWide(const StringPiece& utf8) { + // In theory this should be using the system-provided conversion rather + // than our ICU, but this will do for now. + std::wstring out; + UTF8ToWide(utf8.data(), utf8.size(), &out); + return out; +} + +#if defined(OS_CHROMEOS) || defined(OS_ANDROID) +// TODO(port): Consider reverting the OS_ANDROID when we have wcrtomb() +// support and a better understanding of what calls these routines. + +// ChromeOS always runs in UTF-8 locale. +std::string SysWideToNativeMB(const std::wstring& wide) { + return WideToUTF8(wide); +} + +std::wstring SysNativeMBToWide(const StringPiece& native_mb) { + return SysUTF8ToWide(native_mb); +} + +#else + +std::string SysWideToNativeMB(const std::wstring& wide) { + mbstate_t ps; + + // Calculate the number of multi-byte characters. We walk through the string + // without writing the output, counting the number of multi-byte characters. + size_t num_out_chars = 0; + memset(&ps, 0, sizeof(ps)); + for (size_t i = 0; i < wide.size(); ++i) { + const wchar_t src = wide[i]; + // Use a temp buffer since calling wcrtomb with an output of NULL does not + // calculate the output length. + char buf[16]; + // Skip NULLs to avoid wcrtomb's special handling of them. + size_t res = src ? wcrtomb(buf, src, &ps) : 0; + switch (res) { + // Handle any errors and return an empty string. + case static_cast(-1): + return std::string(); + break; + case 0: + // We hit an embedded null byte, keep going. + ++num_out_chars; + break; + default: + num_out_chars += res; + break; + } + } + + if (num_out_chars == 0) + return std::string(); + + std::string out; + out.resize(num_out_chars); + + // We walk the input string again, with |i| tracking the index of the + // wide input, and |j| tracking the multi-byte output. + memset(&ps, 0, sizeof(ps)); + for (size_t i = 0, j = 0; i < wide.size(); ++i) { + const wchar_t src = wide[i]; + // We don't want wcrtomb to do its funkiness for embedded NULLs. + size_t res = src ? wcrtomb(&out[j], src, &ps) : 0; + switch (res) { + // Handle any errors and return an empty string. + case static_cast(-1): + return std::string(); + break; + case 0: + // We hit an embedded null byte, keep going. + ++j; // Output is already zeroed. + break; + default: + j += res; + break; + } + } + + return out; +} + +std::wstring SysNativeMBToWide(const StringPiece& native_mb) { + mbstate_t ps; + + // Calculate the number of wide characters. We walk through the string + // without writing the output, counting the number of wide characters. + size_t num_out_chars = 0; + memset(&ps, 0, sizeof(ps)); + for (size_t i = 0; i < native_mb.size(); ) { + const char* src = native_mb.data() + i; + size_t res = mbrtowc(NULL, src, native_mb.size() - i, &ps); + switch (res) { + // Handle any errors and return an empty string. + case static_cast(-2): + case static_cast(-1): + return std::wstring(); + break; + case 0: + // We hit an embedded null byte, keep going. + i += 1; // Fall through. + default: + i += res; + ++num_out_chars; + break; + } + } + + if (num_out_chars == 0) + return std::wstring(); + + std::wstring out; + out.resize(num_out_chars); + + memset(&ps, 0, sizeof(ps)); // Clear the shift state. + // We walk the input string again, with |i| tracking the index of the + // multi-byte input, and |j| tracking the wide output. + for (size_t i = 0, j = 0; i < native_mb.size(); ++j) { + const char* src = native_mb.data() + i; + wchar_t* dst = &out[j]; + size_t res = mbrtowc(dst, src, native_mb.size() - i, &ps); + switch (res) { + // Handle any errors and return an empty string. + case static_cast(-2): + case static_cast(-1): + return std::wstring(); + break; + case 0: + i += 1; // Skip null byte. + break; + default: + i += res; + break; + } + } + + return out; +} + +#endif // OS_CHROMEOS + +} // namespace butil diff --git a/src/butil/strings/utf_offset_string_conversions.cc b/src/butil/strings/utf_offset_string_conversions.cc new file mode 100644 index 0000000..2981059 --- /dev/null +++ b/src/butil/strings/utf_offset_string_conversions.cc @@ -0,0 +1,260 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/strings/utf_offset_string_conversions.h" + +#include + +#include "butil/logging.h" +#include "butil/memory/scoped_ptr.h" +#include "butil/strings/string_piece.h" +#include "butil/strings/utf_string_conversion_utils.h" + +namespace butil { + +OffsetAdjuster::Adjustment::Adjustment(size_t original_offset, + size_t original_length, + size_t output_length) + : original_offset(original_offset), + original_length(original_length), + output_length(output_length) { +} + +// static +void OffsetAdjuster::AdjustOffsets( + const Adjustments& adjustments, + std::vector* offsets_for_adjustment) { + if (!offsets_for_adjustment || adjustments.empty()) + return; + for (std::vector::iterator i(offsets_for_adjustment->begin()); + i != offsets_for_adjustment->end(); ++i) + AdjustOffset(adjustments, &(*i)); +} + +// static +void OffsetAdjuster::AdjustOffset(const Adjustments& adjustments, + size_t* offset) { + if (*offset == string16::npos) + return; + int adjustment = 0; + for (Adjustments::const_iterator i = adjustments.begin(); + i != adjustments.end(); ++i) { + if (*offset <= i->original_offset) + break; + if (*offset < (i->original_offset + i->original_length)) { + *offset = string16::npos; + return; + } + adjustment += static_cast(i->original_length - i->output_length); + } + *offset -= adjustment; +} + +// static +void OffsetAdjuster::UnadjustOffsets( + const Adjustments& adjustments, + std::vector* offsets_for_unadjustment) { + if (!offsets_for_unadjustment || adjustments.empty()) + return; + for (std::vector::iterator i(offsets_for_unadjustment->begin()); + i != offsets_for_unadjustment->end(); ++i) + UnadjustOffset(adjustments, &(*i)); +} + +// static +void OffsetAdjuster::UnadjustOffset(const Adjustments& adjustments, + size_t* offset) { + if (*offset == string16::npos) + return; + int adjustment = 0; + for (Adjustments::const_iterator i = adjustments.begin(); + i != adjustments.end(); ++i) { + if (*offset + adjustment <= i->original_offset) + break; + adjustment += static_cast(i->original_length - i->output_length); + if ((*offset + adjustment) < + (i->original_offset + i->original_length)) { + *offset = string16::npos; + return; + } + } + *offset += adjustment; +} + +// static +void OffsetAdjuster::MergeSequentialAdjustments( + const Adjustments& first_adjustments, + Adjustments* adjustments_on_adjusted_string) { + Adjustments::iterator adjusted_iter = adjustments_on_adjusted_string->begin(); + Adjustments::const_iterator first_iter = first_adjustments.begin(); + // Simultaneously iterate over all |adjustments_on_adjusted_string| and + // |first_adjustments|, adding adjustments to or correcting the adjustments + // in |adjustments_on_adjusted_string| as we go. |shift| keeps track of the + // current number of characters collapsed by |first_adjustments| up to this + // point. |currently_collapsing| keeps track of the number of characters + // collapsed by |first_adjustments| into the current |adjusted_iter|'s + // length. These are characters that will change |shift| as soon as we're + // done processing the current |adjusted_iter|; they are not yet reflected in + // |shift|. + size_t shift = 0; + size_t currently_collapsing = 0; + while (adjusted_iter != adjustments_on_adjusted_string->end()) { + if ((first_iter == first_adjustments.end()) || + ((adjusted_iter->original_offset + shift + + adjusted_iter->original_length) <= first_iter->original_offset)) { + // Entire |adjusted_iter| (accounting for its shift and including its + // whole original length) comes before |first_iter|. + // + // Correct the offset at |adjusted_iter| and move onto the next + // adjustment that needs revising. + adjusted_iter->original_offset += shift; + shift += currently_collapsing; + currently_collapsing = 0; + ++adjusted_iter; + } else if ((adjusted_iter->original_offset + shift) > + first_iter->original_offset) { + // |first_iter| comes before the |adjusted_iter| (as adjusted by |shift|). + + // It's not possible for the adjustments to overlap. (It shouldn't + // be possible that we have an |adjusted_iter->original_offset| that, + // when adjusted by the computed |shift|, is in the middle of + // |first_iter|'s output's length. After all, that would mean the + // current adjustment_on_adjusted_string somehow points to an offset + // that was supposed to have been eliminated by the first set of + // adjustments.) + DCHECK_LE(first_iter->original_offset + first_iter->output_length, + adjusted_iter->original_offset + shift); + + // Add the |first_adjustment_iter| to the full set of adjustments while + // making sure |adjusted_iter| continues pointing to the same element. + // We do this by inserting the |first_adjustment_iter| right before + // |adjusted_iter|, then incrementing |adjusted_iter| so it points to + // the following element. + shift += first_iter->original_length - first_iter->output_length; + adjusted_iter = adjustments_on_adjusted_string->insert( + adjusted_iter, *first_iter); + ++adjusted_iter; + ++first_iter; + } else { + // The first adjustment adjusted something that then got further adjusted + // by the second set of adjustments. In other words, |first_iter| points + // to something in the range covered by |adjusted_iter|'s length (after + // accounting for |shift|). Precisely, + // adjusted_iter->original_offset + shift + // <= + // first_iter->original_offset + // <= + // adjusted_iter->original_offset + shift + + // adjusted_iter->original_length + + // Modify the current |adjusted_iter| to include whatever collapsing + // happened in |first_iter|, then advance to the next |first_adjustments| + // because we dealt with the current one. + const int collapse = static_cast(first_iter->original_length) - + static_cast(first_iter->output_length); + // This function does not know how to deal with a string that expands and + // then gets modified, only strings that collapse and then get modified. + DCHECK_GT(collapse, 0); + adjusted_iter->original_length += collapse; + currently_collapsing += collapse; + ++first_iter; + } + } + DCHECK_EQ(0u, currently_collapsing); + if (first_iter != first_adjustments.end()) { + // Only first adjustments are left. These do not need to be modified. + // (Their offsets are already correct with respect to the original string.) + // Append them all. + DCHECK(adjusted_iter == adjustments_on_adjusted_string->end()); + adjustments_on_adjusted_string->insert( + adjustments_on_adjusted_string->end(), first_iter, + first_adjustments.end()); + } +} + +// Converts the given source Unicode character type to the given destination +// Unicode character type as a STL string. The given input buffer and size +// determine the source, and the given output STL string will be replaced by +// the result. If non-NULL, |adjustments| is set to reflect the all the +// alterations to the string that are not one-character-to-one-character. +// It will always be sorted by increasing offset. +template +bool ConvertUnicode(const SrcChar* src, + size_t src_len, + DestStdString* output, + OffsetAdjuster::Adjustments* adjustments) { + if (adjustments) + adjustments->clear(); + // ICU requires 32-bit numbers. + bool success = true; + int32_t src_len32 = static_cast(src_len); + for (int32_t i = 0; i < src_len32; i++) { + uint32_t code_point; + size_t original_i = i; + size_t chars_written = 0; + if (ReadUnicodeCharacter(src, src_len32, &i, &code_point)) { + chars_written = WriteUnicodeCharacter(code_point, output); + } else { + chars_written = WriteUnicodeCharacter(0xFFFD, output); + success = false; + } + + // Only bother writing an adjustment if this modification changed the + // length of this character. + // NOTE: ReadUnicodeCharacter() adjusts |i| to point _at_ the last + // character read, not after it (so that incrementing it in the loop + // increment will place it at the right location), so we need to account + // for that in determining the amount that was read. + if (adjustments && ((i - original_i + 1) != chars_written)) { + adjustments->push_back(OffsetAdjuster::Adjustment( + original_i, i - original_i + 1, chars_written)); + } + } + return success; +} + +bool UTF8ToUTF16WithAdjustments( + const char* src, + size_t src_len, + string16* output, + butil::OffsetAdjuster::Adjustments* adjustments) { + PrepareForUTF16Or32Output(src, src_len, output); + return ConvertUnicode(src, src_len, output, adjustments); +} + +string16 UTF8ToUTF16WithAdjustments( + const butil::StringPiece& utf8, + butil::OffsetAdjuster::Adjustments* adjustments) { + string16 result; + UTF8ToUTF16WithAdjustments(utf8.data(), utf8.length(), &result, adjustments); + return result; +} + +string16 UTF8ToUTF16AndAdjustOffsets( + const butil::StringPiece& utf8, + std::vector* offsets_for_adjustment) { + std::for_each(offsets_for_adjustment->begin(), + offsets_for_adjustment->end(), + LimitOffset(utf8.length())); + OffsetAdjuster::Adjustments adjustments; + string16 result = UTF8ToUTF16WithAdjustments(utf8, &adjustments); + OffsetAdjuster::AdjustOffsets(adjustments, offsets_for_adjustment); + return result; +} + +std::string UTF16ToUTF8AndAdjustOffsets( + const butil::StringPiece16& utf16, + std::vector* offsets_for_adjustment) { + std::for_each(offsets_for_adjustment->begin(), + offsets_for_adjustment->end(), + LimitOffset(utf16.length())); + std::string result; + PrepareForUTF8Output(utf16.data(), utf16.length(), &result); + OffsetAdjuster::Adjustments adjustments; + ConvertUnicode(utf16.data(), utf16.length(), &result, &adjustments); + OffsetAdjuster::AdjustOffsets(adjustments, offsets_for_adjustment); + return result; +} + +} // namespace butil diff --git a/src/butil/strings/utf_offset_string_conversions.h b/src/butil/strings/utf_offset_string_conversions.h new file mode 100644 index 0000000..4984900 --- /dev/null +++ b/src/butil/strings/utf_offset_string_conversions.h @@ -0,0 +1,126 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_STRINGS_UTF_OFFSET_STRING_CONVERSIONS_H_ +#define BUTIL_STRINGS_UTF_OFFSET_STRING_CONVERSIONS_H_ + +#include +#include + +#include "butil/base_export.h" +#include "butil/strings/string16.h" +#include "butil/strings/string_piece.h" + +namespace butil { + +// A helper class and associated data structures to adjust offsets into a +// string in response to various adjustments one might do to that string +// (e.g., eliminating a range). For details on offsets, see the comments by +// the AdjustOffsets() function below. +class BUTIL_EXPORT OffsetAdjuster { + public: + struct BUTIL_EXPORT Adjustment { + Adjustment(size_t original_offset, + size_t original_length, + size_t output_length); + + size_t original_offset; + size_t original_length; + size_t output_length; + }; + typedef std::vector Adjustments; + + // Adjusts all offsets in |offsets_for_adjustment| to reflect the adjustments + // recorded in |adjustments|. + // + // Offsets represents insertion/selection points between characters: if |src| + // is "abcd", then 0 is before 'a', 2 is between 'b' and 'c', and 4 is at the + // end of the string. Valid input offsets range from 0 to |src_len|. On + // exit, each offset will have been modified to point at the same logical + // position in the output string. If an offset cannot be successfully + // adjusted (e.g., because it points into the middle of a multibyte sequence), + // it will be set to string16::npos. + static void AdjustOffsets(const Adjustments& adjustments, + std::vector* offsets_for_adjustment); + + // Adjusts the single |offset| to reflect the adjustments recorded in + // |adjustments|. + static void AdjustOffset(const Adjustments& adjustments, + size_t* offset); + + // Adjusts all offsets in |offsets_for_unadjustment| to reflect the reverse + // of the adjustments recorded in |adjustments|. In other words, the offsets + // provided represent offsets into an adjusted string and the caller wants + // to know the offsets they correspond to in the original string. If an + // offset cannot be successfully unadjusted (e.g., because it points into + // the middle of a multibyte sequence), it will be set to string16::npos. + static void UnadjustOffsets(const Adjustments& adjustments, + std::vector* offsets_for_unadjustment); + + // Adjusts the single |offset| to reflect the reverse of the adjustments + // recorded in |adjustments|. + static void UnadjustOffset(const Adjustments& adjustments, + size_t* offset); + + // Combines two sequential sets of adjustments, storing the combined revised + // adjustments in |adjustments_on_adjusted_string|. That is, suppose a + // string was altered in some way, with the alterations recorded as + // adjustments in |first_adjustments|. Then suppose the resulting string is + // further altered, with the alterations recorded as adjustments scored in + // |adjustments_on_adjusted_string|, with the offsets recorded in these + // adjustments being with respect to the intermediate string. This function + // combines the two sets of adjustments into one, storing the result in + // |adjustments_on_adjusted_string|, whose offsets are correct with respect + // to the original string. + // + // Assumes both parameters are sorted by increasing offset. + // + // WARNING: Only supports |first_adjustments| that involve collapsing ranges + // of text, not expanding ranges. + static void MergeSequentialAdjustments( + const Adjustments& first_adjustments, + Adjustments* adjustments_on_adjusted_string); +}; + +// Like the conversions in utf_string_conversions.h, but also fills in an +// |adjustments| parameter that reflects the alterations done to the string. +// It may be NULL. +BUTIL_EXPORT bool UTF8ToUTF16WithAdjustments( + const char* src, + size_t src_len, + string16* output, + butil::OffsetAdjuster::Adjustments* adjustments); +BUTIL_EXPORT string16 UTF8ToUTF16WithAdjustments( + const butil::StringPiece& utf8, + butil::OffsetAdjuster::Adjustments* adjustments); +// As above, but instead internally examines the adjustments and applies them +// to |offsets_for_adjustment|. See comments by AdjustOffsets(). +BUTIL_EXPORT string16 UTF8ToUTF16AndAdjustOffsets( + const butil::StringPiece& utf8, + std::vector* offsets_for_adjustment); + +BUTIL_EXPORT std::string UTF16ToUTF8AndAdjustOffsets( + const butil::StringPiece16& utf16, + std::vector* offsets_for_adjustment); + +// Limiting function callable by std::for_each which will replace any value +// which is greater than |limit| with npos. Typically this is called with a +// string length to clamp offsets into the string to [0, length] (as opposed to +// [0, length); see comments above). +template +struct LimitOffset { + explicit LimitOffset(size_t limit) + : limit_(limit) {} + + void operator()(size_t& offset) { + if (offset > limit_) + offset = T::npos; + } + + size_t limit_; +}; + +} // namespace butil + +#endif // BUTIL_STRINGS_UTF_OFFSET_STRING_CONVERSIONS_H_ diff --git a/src/butil/strings/utf_string_conversion_utils.cc b/src/butil/strings/utf_string_conversion_utils.cc new file mode 100644 index 0000000..432b2c2 --- /dev/null +++ b/src/butil/strings/utf_string_conversion_utils.cc @@ -0,0 +1,148 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/strings/utf_string_conversion_utils.h" + +#include "butil/third_party/icu/icu_utf.h" + +namespace butil { + +// ReadUnicodeCharacter -------------------------------------------------------- + +bool ReadUnicodeCharacter(const char* src, + int32_t src_len, + int32_t* char_index, + uint32_t* code_point_out) { + // U8_NEXT expects to be able to use -1 to signal an error, so we must + // use a signed type for code_point. But this function returns false + // on error anyway, so code_point_out is unsigned. + int32_t code_point; + CBU8_NEXT(src, *char_index, src_len, code_point); + *code_point_out = static_cast(code_point); + + // The ICU macro above moves to the next char, we want to point to the last + // char consumed. + (*char_index)--; + + // Validate the decoded value. + return IsValidCodepoint(code_point); +} + +bool ReadUnicodeCharacter(const char16* src, + int32_t src_len, + int32_t* char_index, + uint32_t* code_point) { + if (CBU16_IS_SURROGATE(src[*char_index])) { + if (!CBU16_IS_SURROGATE_LEAD(src[*char_index]) || + *char_index + 1 >= src_len || + !CBU16_IS_TRAIL(src[*char_index + 1])) { + // Invalid surrogate pair. + return false; + } + + // Valid surrogate pair. + *code_point = CBU16_GET_SUPPLEMENTARY(src[*char_index], + src[*char_index + 1]); + (*char_index)++; + } else { + // Not a surrogate, just one 16-bit word. + *code_point = src[*char_index]; + } + + return IsValidCodepoint(*code_point); +} + +#if defined(WCHAR_T_IS_UTF32) +bool ReadUnicodeCharacter(const wchar_t* src, + int32_t src_len, + int32_t* char_index, + uint32_t* code_point) { + // Conversion is easy since the source is 32-bit. + *code_point = src[*char_index]; + + // Validate the value. + return IsValidCodepoint(*code_point); +} +#endif // defined(WCHAR_T_IS_UTF32) + +// WriteUnicodeCharacter ------------------------------------------------------- + +size_t WriteUnicodeCharacter(uint32_t code_point, std::string* output) { + if (code_point <= 0x7f) { + // Fast path the common case of one byte. + output->push_back(code_point); + return 1; + } + + + // CBU8_APPEND_UNSAFE can append up to 4 bytes. + size_t char_offset = output->length(); + size_t original_char_offset = char_offset; + output->resize(char_offset + CBU8_MAX_LENGTH); + + CBU8_APPEND_UNSAFE(&(*output)[0], char_offset, code_point); + + // CBU8_APPEND_UNSAFE will advance our pointer past the inserted character, so + // it will represent the new length of the string. + output->resize(char_offset); + return char_offset - original_char_offset; +} + +size_t WriteUnicodeCharacter(uint32_t code_point, string16* output) { + if (CBU16_LENGTH(code_point) == 1) { + // Thie code point is in the Basic Multilingual Plane (BMP). + output->push_back(static_cast(code_point)); + return 1; + } + // Non-BMP characters use a double-character encoding. + size_t char_offset = output->length(); + output->resize(char_offset + CBU16_MAX_LENGTH); + CBU16_APPEND_UNSAFE(&(*output)[0], char_offset, code_point); + return CBU16_MAX_LENGTH; +} + +// Generalized Unicode converter ----------------------------------------------- + +template +void PrepareForUTF8Output(const CHAR* src, + size_t src_len, + std::string* output) { + output->clear(); + if (src_len == 0) + return; + if (src[0] < 0x80) { + // Assume that the entire input will be ASCII. + output->reserve(src_len); + } else { + // Assume that the entire input is non-ASCII and will have 3 bytes per char. + output->reserve(src_len * 3); + } +} + +// Instantiate versions we know callers will need. +template void PrepareForUTF8Output(const wchar_t*, size_t, std::string*); +template void PrepareForUTF8Output(const char16*, size_t, std::string*); + +template +void PrepareForUTF16Or32Output(const char* src, + size_t src_len, + STRING* output) { + output->clear(); + if (src_len == 0) + return; + if (static_cast(src[0]) < 0x80) { + // Assume the input is all ASCII, which means 1:1 correspondence. + output->reserve(src_len); + } else { + // Otherwise assume that the UTF-8 sequences will have 2 bytes for each + // character. + output->reserve(src_len / 2); + } +} + +// Instantiate versions we know callers will need. +template void PrepareForUTF16Or32Output(const char*, size_t, std::wstring*); +template void PrepareForUTF16Or32Output(const char*, size_t, string16*); + +} // namespace butil diff --git a/src/butil/strings/utf_string_conversion_utils.h b/src/butil/strings/utf_string_conversion_utils.h new file mode 100644 index 0000000..61cfe06 --- /dev/null +++ b/src/butil/strings/utf_string_conversion_utils.h @@ -0,0 +1,97 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_STRINGS_UTF_STRING_CONVERSION_UTILS_H_ +#define BUTIL_STRINGS_UTF_STRING_CONVERSION_UTILS_H_ + +// This should only be used by the various UTF string conversion files. + +#include "butil/base_export.h" +#include "butil/strings/string16.h" + +namespace butil { + +inline bool IsValidCodepoint(uint32_t code_point) { + // Excludes the surrogate code points ([0xD800, 0xDFFF]) and + // codepoints larger than 0x10FFFF (the highest codepoint allowed). + // Non-characters and unassigned codepoints are allowed. + return code_point < 0xD800u || + (code_point >= 0xE000u && code_point <= 0x10FFFFu); +} + +inline bool IsValidCharacter(uint32_t code_point) { + // Excludes non-characters (U+FDD0..U+FDEF, and all codepoints ending in + // 0xFFFE or 0xFFFF) from the set of valid code points. + return code_point < 0xD800u || (code_point >= 0xE000u && + code_point < 0xFDD0u) || (code_point > 0xFDEFu && + code_point <= 0x10FFFFu && (code_point & 0xFFFEu) != 0xFFFEu); +} + +// ReadUnicodeCharacter -------------------------------------------------------- + +// Reads a UTF-8 stream, placing the next code point into the given output +// |*code_point|. |src| represents the entire string to read, and |*char_index| +// is the character offset within the string to start reading at. |*char_index| +// will be updated to index the last character read, such that incrementing it +// (as in a for loop) will take the reader to the next character. +// +// Returns true on success. On false, |*code_point| will be invalid. +BUTIL_EXPORT bool ReadUnicodeCharacter(const char* src, + int32_t src_len, + int32_t* char_index, + uint32_t* code_point_out); + +// Reads a UTF-16 character. The usage is the same as the 8-bit version above. +BUTIL_EXPORT bool ReadUnicodeCharacter(const char16* src, + int32_t src_len, + int32_t* char_index, + uint32_t* code_point); + +#if defined(WCHAR_T_IS_UTF32) +// Reads UTF-32 character. The usage is the same as the 8-bit version above. +BUTIL_EXPORT bool ReadUnicodeCharacter(const wchar_t* src, + int32_t src_len, + int32_t* char_index, + uint32_t* code_point); +#endif // defined(WCHAR_T_IS_UTF32) + +// WriteUnicodeCharacter ------------------------------------------------------- + +// Appends a UTF-8 character to the given 8-bit string. Returns the number of +// bytes written. +// TODO(brettw) Bug 79631: This function should not be exposed. +BUTIL_EXPORT size_t WriteUnicodeCharacter(uint32_t code_point, + std::string* output); + +// Appends the given code point as a UTF-16 character to the given 16-bit +// string. Returns the number of 16-bit values written. +BUTIL_EXPORT size_t WriteUnicodeCharacter(uint32_t code_point, string16* output); + +#if defined(WCHAR_T_IS_UTF32) +// Appends the given UTF-32 character to the given 32-bit string. Returns the +// number of 32-bit values written. +inline size_t WriteUnicodeCharacter(uint32_t code_point, std::wstring* output) { + // This is the easy case, just append the character. + output->push_back(code_point); + return 1; +} +#endif // defined(WCHAR_T_IS_UTF32) + +// Generalized Unicode converter ----------------------------------------------- + +// Guesses the length of the output in UTF-8 in bytes, clears that output +// string, and reserves that amount of space. We assume that the input +// character types are unsigned, which will be true for UTF-16 and -32 on our +// systems. +template +void PrepareForUTF8Output(const CHAR* src, size_t src_len, std::string* output); + +// Prepares an output buffer (containing either UTF-16 or -32 data) given some +// UTF-8 input that will be converted to it. See PrepareForUTF8Output(). +template +void PrepareForUTF16Or32Output(const char* src, size_t src_len, STRING* output); + +} // namespace butil + +#endif // BUTIL_STRINGS_UTF_STRING_CONVERSION_UTILS_H_ diff --git a/src/butil/strings/utf_string_conversions.cc b/src/butil/strings/utf_string_conversions.cc new file mode 100644 index 0000000..065fbe3 --- /dev/null +++ b/src/butil/strings/utf_string_conversions.cc @@ -0,0 +1,190 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/strings/utf_string_conversions.h" + +#include "butil/strings/string_piece.h" +#include "butil/strings/string_util.h" +#include "butil/strings/utf_string_conversion_utils.h" + +namespace butil { + +namespace { + +// Generalized Unicode converter ----------------------------------------------- + +// Converts the given source Unicode character type to the given destination +// Unicode character type as a STL string. The given input buffer and size +// determine the source, and the given output STL string will be replaced by +// the result. +template +bool ConvertUnicode(const SRC_CHAR* src, + size_t src_len, + DEST_STRING* output) { + // ICU requires 32-bit numbers. + bool success = true; + int32_t src_len32 = static_cast(src_len); + for (int32_t i = 0; i < src_len32; i++) { + uint32_t code_point; + if (ReadUnicodeCharacter(src, src_len32, &i, &code_point)) { + WriteUnicodeCharacter(code_point, output); + } else { + WriteUnicodeCharacter(0xFFFD, output); + success = false; + } + } + + return success; +} + +} // namespace + +// UTF-8 <-> Wide -------------------------------------------------------------- + +bool WideToUTF8(const wchar_t* src, size_t src_len, std::string* output) { + PrepareForUTF8Output(src, src_len, output); + return ConvertUnicode(src, src_len, output); +} + +std::string WideToUTF8(const std::wstring& wide) { + std::string ret; + // Ignore the success flag of this call, it will do the best it can for + // invalid input, which is what we want here. + WideToUTF8(wide.data(), wide.length(), &ret); + return ret; +} + +bool UTF8ToWide(const char* src, size_t src_len, std::wstring* output) { + PrepareForUTF16Or32Output(src, src_len, output); + return ConvertUnicode(src, src_len, output); +} + +std::wstring UTF8ToWide(const StringPiece& utf8) { + std::wstring ret; + UTF8ToWide(utf8.data(), utf8.length(), &ret); + return ret; +} + +// UTF-16 <-> Wide ------------------------------------------------------------- + +#if defined(WCHAR_T_IS_UTF16) + +// When wide == UTF-16, then conversions are a NOP. +bool WideToUTF16(const wchar_t* src, size_t src_len, string16* output) { + output->assign(src, src_len); + return true; +} + +string16 WideToUTF16(const std::wstring& wide) { + return wide; +} + +bool UTF16ToWide(const char16* src, size_t src_len, std::wstring* output) { + output->assign(src, src_len); + return true; +} + +std::wstring UTF16ToWide(const string16& utf16) { + return utf16; +} + +#elif defined(WCHAR_T_IS_UTF32) + +bool WideToUTF16(const wchar_t* src, size_t src_len, string16* output) { + output->clear(); + // Assume that normally we won't have any non-BMP characters so the counts + // will be the same. + output->reserve(src_len); + return ConvertUnicode(src, src_len, output); +} + +string16 WideToUTF16(const std::wstring& wide) { + string16 ret; + WideToUTF16(wide.data(), wide.length(), &ret); + return ret; +} + +bool UTF16ToWide(const char16* src, size_t src_len, std::wstring* output) { + output->clear(); + // Assume that normally we won't have any non-BMP characters so the counts + // will be the same. + output->reserve(src_len); + return ConvertUnicode(src, src_len, output); +} + +std::wstring UTF16ToWide(const string16& utf16) { + std::wstring ret; + UTF16ToWide(utf16.data(), utf16.length(), &ret); + return ret; +} + +#endif // defined(WCHAR_T_IS_UTF32) + +// UTF16 <-> UTF8 -------------------------------------------------------------- + +#if defined(WCHAR_T_IS_UTF32) + +bool UTF8ToUTF16(const char* src, size_t src_len, string16* output) { + PrepareForUTF16Or32Output(src, src_len, output); + return ConvertUnicode(src, src_len, output); +} + +string16 UTF8ToUTF16(const StringPiece& utf8) { + string16 ret; + // Ignore the success flag of this call, it will do the best it can for + // invalid input, which is what we want here. + UTF8ToUTF16(utf8.data(), utf8.length(), &ret); + return ret; +} + +bool UTF16ToUTF8(const char16* src, size_t src_len, std::string* output) { + PrepareForUTF8Output(src, src_len, output); + return ConvertUnicode(src, src_len, output); +} + +std::string UTF16ToUTF8(const string16& utf16) { + std::string ret; + // Ignore the success flag of this call, it will do the best it can for + // invalid input, which is what we want here. + UTF16ToUTF8(utf16.data(), utf16.length(), &ret); + return ret; +} + +#elif defined(WCHAR_T_IS_UTF16) +// Easy case since we can use the "wide" versions we already wrote above. + +bool UTF8ToUTF16(const char* src, size_t src_len, string16* output) { + return UTF8ToWide(src, src_len, output); +} + +string16 UTF8ToUTF16(const StringPiece& utf8) { + return UTF8ToWide(utf8); +} + +bool UTF16ToUTF8(const char16* src, size_t src_len, std::string* output) { + return WideToUTF8(src, src_len, output); +} + +std::string UTF16ToUTF8(const string16& utf16) { + return WideToUTF8(utf16); +} + +#endif + +std::wstring ASCIIToWide(const StringPiece& ascii) { + DCHECK(IsStringASCII(ascii)) << ascii; + return std::wstring(ascii.begin(), ascii.end()); +} + +string16 ASCIIToUTF16(const StringPiece& ascii) { + DCHECK(IsStringASCII(ascii)) << ascii; + return string16(ascii.begin(), ascii.end()); +} + +std::string UTF16ToASCII(const string16& utf16) { + DCHECK(IsStringASCII(utf16)) << UTF16ToUTF8(utf16); + return std::string(utf16.begin(), utf16.end()); +} + +} // namespace butil diff --git a/src/butil/strings/utf_string_conversions.h b/src/butil/strings/utf_string_conversions.h new file mode 100644 index 0000000..b9bd2d6 --- /dev/null +++ b/src/butil/strings/utf_string_conversions.h @@ -0,0 +1,53 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_STRINGS_UTF_STRING_CONVERSIONS_H_ +#define BUTIL_STRINGS_UTF_STRING_CONVERSIONS_H_ + +#include + +#include "butil/base_export.h" +#include "butil/strings/string16.h" +#include "butil/strings/string_piece.h" + +namespace butil { + +// These convert between UTF-8, -16, and -32 strings. They are potentially slow, +// so avoid unnecessary conversions. The low-level versions return a boolean +// indicating whether the conversion was 100% valid. In this case, it will still +// do the best it can and put the result in the output buffer. The versions that +// return strings ignore this error and just return the best conversion +// possible. +BUTIL_EXPORT bool WideToUTF8(const wchar_t* src, size_t src_len, + std::string* output); +BUTIL_EXPORT std::string WideToUTF8(const std::wstring& wide); +BUTIL_EXPORT bool UTF8ToWide(const char* src, size_t src_len, + std::wstring* output); +BUTIL_EXPORT std::wstring UTF8ToWide(const StringPiece& utf8); + +BUTIL_EXPORT bool WideToUTF16(const wchar_t* src, size_t src_len, + string16* output); +BUTIL_EXPORT string16 WideToUTF16(const std::wstring& wide); +BUTIL_EXPORT bool UTF16ToWide(const char16* src, size_t src_len, + std::wstring* output); +BUTIL_EXPORT std::wstring UTF16ToWide(const string16& utf16); + +BUTIL_EXPORT bool UTF8ToUTF16(const char* src, size_t src_len, string16* output); +BUTIL_EXPORT string16 UTF8ToUTF16(const StringPiece& utf8); +BUTIL_EXPORT bool UTF16ToUTF8(const char16* src, size_t src_len, + std::string* output); +BUTIL_EXPORT std::string UTF16ToUTF8(const string16& utf16); + +// These convert an ASCII string, typically a hardcoded constant, to a +// UTF16/Wide string. +BUTIL_EXPORT std::wstring ASCIIToWide(const StringPiece& ascii); +BUTIL_EXPORT string16 ASCIIToUTF16(const StringPiece& ascii); + +// Converts to 7-bit ASCII by truncating. The result must be known to be ASCII +// beforehand. +BUTIL_EXPORT std::string UTF16ToASCII(const string16& utf16); + +} // namespace butil + +#endif // BUTIL_STRINGS_UTF_STRING_CONVERSIONS_H_ diff --git a/src/butil/synchronization/cancellation_flag.cc b/src/butil/synchronization/cancellation_flag.cc new file mode 100644 index 0000000..c919dae --- /dev/null +++ b/src/butil/synchronization/cancellation_flag.cc @@ -0,0 +1,22 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/synchronization/cancellation_flag.h" + +#include "butil/logging.h" + +namespace butil { + +void CancellationFlag::Set() { +#if !defined(NDEBUG) + DCHECK_EQ(set_on_, PlatformThread::CurrentId()); +#endif + butil::subtle::Release_Store(&flag_, 1); +} + +bool CancellationFlag::IsSet() const { + return butil::subtle::Acquire_Load(&flag_) != 0; +} + +} // namespace butil diff --git a/src/butil/synchronization/cancellation_flag.h b/src/butil/synchronization/cancellation_flag.h new file mode 100644 index 0000000..b805e3b --- /dev/null +++ b/src/butil/synchronization/cancellation_flag.h @@ -0,0 +1,43 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_SYNCHRONIZATION_CANCELLATION_FLAG_H_ +#define BUTIL_SYNCHRONIZATION_CANCELLATION_FLAG_H_ + +#include "butil/base_export.h" +#include "butil/atomicops.h" +#include "butil/threading/platform_thread.h" + +namespace butil { + +// CancellationFlag allows one thread to cancel jobs executed on some worker +// thread. Calling Set() from one thread and IsSet() from a number of threads +// is thread-safe. +// +// This class IS NOT intended for synchronization between threads. +class BUTIL_EXPORT CancellationFlag { + public: + CancellationFlag() : flag_(false) { +#if !defined(NDEBUG) + set_on_ = PlatformThread::CurrentId(); +#endif + } + ~CancellationFlag() {} + + // Set the flag. May only be called on the thread which owns the object. + void Set(); + bool IsSet() const; // Returns true iff the flag was set. + + private: + butil::subtle::Atomic32 flag_; +#if !defined(NDEBUG) + PlatformThreadId set_on_; +#endif + + DISALLOW_COPY_AND_ASSIGN(CancellationFlag); +}; + +} // namespace butil + +#endif // BUTIL_SYNCHRONIZATION_CANCELLATION_FLAG_H_ diff --git a/src/butil/synchronization/condition_variable.h b/src/butil/synchronization/condition_variable.h new file mode 100644 index 0000000..2d03e72 --- /dev/null +++ b/src/butil/synchronization/condition_variable.h @@ -0,0 +1,114 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ConditionVariable wraps pthreads condition variable synchronization or, on +// Windows, simulates it. This functionality is very helpful for having +// several threads wait for an event, as is common with a thread pool managed +// by a master. The meaning of such an event in the (worker) thread pool +// scenario is that additional tasks are now available for processing. It is +// used in Chrome in the DNS prefetching system to notify worker threads that +// a queue now has items (tasks) which need to be tended to. A related use +// would have a pool manager waiting on a ConditionVariable, waiting for a +// thread in the pool to announce (signal) that there is now more room in a +// (bounded size) communications queue for the manager to deposit tasks, or, +// as a second example, that the queue of tasks is completely empty and all +// workers are waiting. +// +// USAGE NOTE 1: spurious signal events are possible with this and +// most implementations of condition variables. As a result, be +// *sure* to retest your condition before proceeding. The following +// is a good example of doing this correctly: +// +// while (!work_to_be_done()) Wait(...); +// +// In contrast do NOT do the following: +// +// if (!work_to_be_done()) Wait(...); // Don't do this. +// +// Especially avoid the above if you are relying on some other thread only +// issuing a signal up *if* there is work-to-do. There can/will +// be spurious signals. Recheck state on waiting thread before +// assuming the signal was intentional. Caveat caller ;-). +// +// USAGE NOTE 2: Broadcast() frees up all waiting threads at once, +// which leads to contention for the locks they all held when they +// called Wait(). This results in POOR performance. A much better +// approach to getting a lot of threads out of Wait() is to have each +// thread (upon exiting Wait()) call Signal() to free up another +// Wait'ing thread. Look at condition_variable_unittest.cc for +// both examples. +// +// Broadcast() can be used nicely during teardown, as it gets the job +// done, and leaves no sleeping threads... and performance is less +// critical at that point. +// +// The semantics of Broadcast() are carefully crafted so that *all* +// threads that were waiting when the request was made will indeed +// get signaled. Some implementations mess up, and don't signal them +// all, while others allow the wait to be effectively turned off (for +// a while while waiting threads come around). This implementation +// appears correct, as it will not "lose" any signals, and will guarantee +// that all threads get signaled by Broadcast(). +// +// This implementation offers support for "performance" in its selection of +// which thread to revive. Performance, in direct contrast with "fairness," +// assures that the thread that most recently began to Wait() is selected by +// Signal to revive. Fairness would (if publicly supported) assure that the +// thread that has Wait()ed the longest is selected. The default policy +// may improve performance, as the selected thread may have a greater chance of +// having some of its stack data in various CPU caches. +// +// For a discussion of the many very subtle implementation details, see the FAQ +// at the end of condition_variable_win.cc. + +#ifndef BUTIL_SYNCHRONIZATION_CONDITION_VARIABLE_H_ +#define BUTIL_SYNCHRONIZATION_CONDITION_VARIABLE_H_ + +#include "butil/build_config.h" + +#if defined(OS_POSIX) +#include +#endif + +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/synchronization/lock.h" + +namespace butil { + +class ConditionVarImpl; +class TimeDelta; + +class BUTIL_EXPORT ConditionVariable { + public: + // Construct a cv for use with ONLY one user lock. + explicit ConditionVariable(Mutex* user_lock); + + ~ConditionVariable(); + + // Wait() releases the caller's critical section atomically as it starts to + // sleep, and the reacquires it when it is signaled. + void Wait(); + void TimedWait(const TimeDelta& max_time); + + // Broadcast() revives all waiting threads. + void Broadcast(); + // Signal() revives one waiting thread. + void Signal(); + + private: + +#if defined(OS_WIN) + ConditionVarImpl* impl_; +#elif defined(OS_POSIX) + pthread_cond_t condition_; + pthread_mutex_t* user_mutex_; +#endif + + DISALLOW_COPY_AND_ASSIGN(ConditionVariable); +}; + +} // namespace butil + +#endif // BUTIL_SYNCHRONIZATION_CONDITION_VARIABLE_H_ diff --git a/src/butil/synchronization/condition_variable_posix.cc b/src/butil/synchronization/condition_variable_posix.cc new file mode 100644 index 0000000..4a4b9f3 --- /dev/null +++ b/src/butil/synchronization/condition_variable_posix.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/synchronization/condition_variable.h" + +#include +#include + +#include "butil/logging.h" +#include "butil/synchronization/lock.h" +#include "butil/threading/thread_restrictions.h" +#include "butil/time/time.h" + +namespace butil { + +ConditionVariable::ConditionVariable(Mutex* user_lock) + : user_mutex_(user_lock->native_handle()) { + // NOTE(gejun): Disable monotonic clock always due to difficulty of adapting + // all versions of gcc + int rv = pthread_cond_init(&condition_, NULL); + DCHECK_EQ(0, rv); +} + +ConditionVariable::~ConditionVariable() { + int rv = pthread_cond_destroy(&condition_); + DCHECK_EQ(0, rv); +} + +void ConditionVariable::Wait() { + butil::ThreadRestrictions::AssertWaitAllowed(); + int rv = pthread_cond_wait(&condition_, user_mutex_); + DCHECK_EQ(0, rv); +} + +void ConditionVariable::TimedWait(const TimeDelta& max_time) { + butil::ThreadRestrictions::AssertWaitAllowed(); + int64_t usecs = max_time.InMicroseconds(); + struct timespec relative_time; + relative_time.tv_sec = usecs / Time::kMicrosecondsPerSecond; + relative_time.tv_nsec = + (usecs % Time::kMicrosecondsPerSecond) * Time::kNanosecondsPerMicrosecond; + +#if defined(OS_MACOSX) + int rv = pthread_cond_timedwait_relative_np( + &condition_, user_mutex_, &relative_time); +#else + struct timeval now; + gettimeofday(&now, NULL); + struct timespec absolute_time; + absolute_time.tv_sec = now.tv_sec; + absolute_time.tv_nsec = now.tv_usec * Time::kNanosecondsPerMicrosecond; + + absolute_time.tv_sec += relative_time.tv_sec; + absolute_time.tv_nsec += relative_time.tv_nsec; + absolute_time.tv_sec += absolute_time.tv_nsec / Time::kNanosecondsPerSecond; + absolute_time.tv_nsec %= Time::kNanosecondsPerSecond; + DCHECK_GE(absolute_time.tv_sec, now.tv_sec); // Overflow paranoia + + int rv = pthread_cond_timedwait(&condition_, user_mutex_, &absolute_time); +#endif // OS_MACOSX + + DCHECK(rv == 0 || rv == ETIMEDOUT); +} + +void ConditionVariable::Broadcast() { + int rv = pthread_cond_broadcast(&condition_); + DCHECK_EQ(0, rv); +} + +void ConditionVariable::Signal() { + int rv = pthread_cond_signal(&condition_); + DCHECK_EQ(0, rv); +} + +} // namespace butil diff --git a/src/butil/synchronization/lock.h b/src/butil/synchronization/lock.h new file mode 100644 index 0000000..e62c76c --- /dev/null +++ b/src/butil/synchronization/lock.h @@ -0,0 +1,178 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BUTIL_SYNCHRONIZATION_LOCK_H_ +#define BUTIL_SYNCHRONIZATION_LOCK_H_ + +#include "butil/build_config.h" +#if defined(OS_WIN) +#include +#elif defined(OS_POSIX) +#include +#if defined(OS_LINUX) && defined(__USE_XOPEN2K) +#define HAS_PTHREAD_MUTEX_TIMEDLOCK 1 +#else +#define HAS_PTHREAD_MUTEX_TIMEDLOCK 0 +#endif // OS_LINUX __USE_XOPEN2K +#endif // OS_POSIX + +#include "butil/base_export.h" +#include "butil/macros.h" +#include "butil/compat.h" + +namespace butil { + +// A convenient wrapper for an OS specific critical section. +class BUTIL_EXPORT Mutex { + DISALLOW_COPY_AND_ASSIGN(Mutex); +public: +#if defined(OS_WIN) + typedef CRITICAL_SECTION NativeHandle; +#elif defined(OS_POSIX) + typedef pthread_mutex_t NativeHandle; +#endif + +public: + Mutex() { +#if defined(OS_WIN) + // The second parameter is the spin count, for short-held locks it avoid the + // contending thread from going to sleep which helps performance greatly. + ::InitializeCriticalSectionAndSpinCount(&_native_handle, 2000); +#elif defined(OS_POSIX) + pthread_mutex_init(&_native_handle, NULL); +#endif + } + + ~Mutex() { +#if defined(OS_WIN) + ::DeleteCriticalSection(&_native_handle); +#elif defined(OS_POSIX) + pthread_mutex_destroy(&_native_handle); +#endif + } + + // Locks the mutex. If another thread has already locked the mutex, a call + // to lock will block execution until the lock is acquired. + void lock() { +#if defined(OS_WIN) + ::EnterCriticalSection(&_native_handle); +#elif defined(OS_POSIX) + pthread_mutex_lock(&_native_handle); +#endif + } + + // Unlocks the mutex. The mutex must be locked by the current thread of + // execution, otherwise, the behavior is undefined. + void unlock() { +#if defined(OS_WIN) + ::LeaveCriticalSection(&_native_handle); +#elif defined(OS_POSIX) + pthread_mutex_unlock(&_native_handle); +#endif + } + + // Tries to lock the mutex. Returns immediately. + // On successful lock acquisition returns true, otherwise returns false. + bool try_lock() { +#if defined(OS_WIN) + return (::TryEnterCriticalSection(&_native_handle) != FALSE); +#elif defined(OS_POSIX) + return pthread_mutex_trylock(&_native_handle) == 0; +#endif + } + +#if HAS_PTHREAD_MUTEX_TIMEDLOCK + bool timed_lock(const struct timespec* abstime) { + return pthread_mutex_timedlock(&_native_handle, abstime) == 0; + } +#endif // HAS_PTHREAD_MUTEX_TIMEDLOCK + + // Returns the underlying implementation-defined native handle object. + NativeHandle* native_handle() { return &_native_handle; } + +private: +#if defined(OS_POSIX) + // The posix implementation of ConditionVariable needs to be able + // to see our lock and tweak our debugging counters, as it releases + // and acquires locks inside of pthread_cond_{timed,}wait. +friend class ConditionVariable; +#elif defined(OS_WIN) +// The Windows Vista implementation of ConditionVariable needs the +// native handle of the critical section. +friend class WinVistaCondVar; +#endif + + NativeHandle _native_handle; +}; + +// TODO: Remove this type. +class BUTIL_EXPORT Lock : public Mutex { + DISALLOW_COPY_AND_ASSIGN(Lock); +public: + Lock() {} + ~Lock() {} + void Acquire() { lock(); } + void Release() { unlock(); } + bool Try() { return try_lock(); } + void AssertAcquired() const {} +}; + +// A helper class that acquires the given Lock while the AutoLock is in scope. +class AutoLock { +public: + struct AlreadyAcquired {}; + + explicit AutoLock(Lock& lock) : lock_(lock) { + lock_.Acquire(); + } + + AutoLock(Lock& lock, const AlreadyAcquired&) : lock_(lock) { + lock_.AssertAcquired(); + } + + ~AutoLock() { + lock_.AssertAcquired(); + lock_.Release(); + } + +private: + Lock& lock_; + DISALLOW_COPY_AND_ASSIGN(AutoLock); +}; + +// AutoUnlock is a helper that will Release() the |lock| argument in the +// constructor, and re-Acquire() it in the destructor. +class AutoUnlock { +public: + explicit AutoUnlock(Lock& lock) : lock_(lock) { + // We require our caller to have the lock. + lock_.AssertAcquired(); + lock_.Release(); + } + + ~AutoUnlock() { + lock_.Acquire(); + } + +private: + Lock& lock_; + DISALLOW_COPY_AND_ASSIGN(AutoUnlock); +}; + +} // namespace butil + +#endif // BUTIL_SYNCHRONIZATION_LOCK_H_ diff --git a/src/butil/synchronization/spin_wait.h b/src/butil/synchronization/spin_wait.h new file mode 100644 index 0000000..79f68da --- /dev/null +++ b/src/butil/synchronization/spin_wait.h @@ -0,0 +1,50 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file provides a macro ONLY for use in testing. +// DO NOT USE IN PRODUCTION CODE. There are much better ways to wait. + +// This code is very helpful in testing multi-threaded code, without depending +// on almost any primitives. This is especially helpful if you are testing +// those primitive multi-threaded constructs. + +// We provide a simple one argument spin wait (for 1 second), and a generic +// spin wait (for longer periods of time). + +#ifndef BUTIL_SYNCHRONIZATION_SPIN_WAIT_H_ +#define BUTIL_SYNCHRONIZATION_SPIN_WAIT_H_ + +#include "butil/threading/platform_thread.h" +#include "butil/time/time.h" + +// Provide a macro that will wait no longer than 1 second for an asynchronous +// change is the value of an expression. +// A typical use would be: +// +// SPIN_FOR_1_SECOND_OR_UNTIL_TRUE(0 == f(x)); +// +// The expression will be evaluated repeatedly until it is true, or until +// the time (1 second) expires. +// Since tests generally have a 5 second watch dog timer, this spin loop is +// typically used to get the padding needed on a given test platform to assure +// that the test passes, even if load varies, and external events vary. + +#define SPIN_FOR_1_SECOND_OR_UNTIL_TRUE(expression) \ + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(butil::TimeDelta::FromSeconds(1), \ + (expression)) + +#define SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(delta, expression) do { \ + butil::TimeTicks start = butil::TimeTicks::Now(); \ + const butil::TimeDelta kTimeout = delta; \ + while (!(expression)) { \ + if (kTimeout < butil::TimeTicks::Now() - start) { \ + EXPECT_LE((butil::TimeTicks::Now() - start).InMilliseconds(), \ + kTimeout.InMilliseconds()) << "Timed out"; \ + break; \ + } \ + butil::PlatformThread::Sleep(butil::TimeDelta::FromMilliseconds(50)); \ + } \ + } while (0) + +#endif // BUTIL_SYNCHRONIZATION_SPIN_WAIT_H_ diff --git a/src/butil/synchronization/waitable_event.h b/src/butil/synchronization/waitable_event.h new file mode 100644 index 0000000..fcd7275 --- /dev/null +++ b/src/butil/synchronization/waitable_event.h @@ -0,0 +1,182 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_SYNCHRONIZATION_WAITABLE_EVENT_H_ +#define BUTIL_SYNCHRONIZATION_WAITABLE_EVENT_H_ + +#include "butil/base_export.h" +#include "butil/basictypes.h" + +#if defined(OS_WIN) +#include +#endif + +#if defined(OS_POSIX) +#include +#include +#include "butil/memory/ref_counted.h" +#include "butil/synchronization/lock.h" +#endif + +namespace butil { + +// This replaces INFINITE from Win32 +static const int kNoTimeout = -1; + +class TimeDelta; + +// A WaitableEvent can be a useful thread synchronization tool when you want to +// allow one thread to wait for another thread to finish some work. For +// non-Windows systems, this can only be used from within a single address +// space. +// +// Use a WaitableEvent when you would otherwise use a Lock+ConditionVariable to +// protect a simple boolean value. However, if you find yourself using a +// WaitableEvent in conjunction with a Lock to wait for a more complex state +// change (e.g., for an item to be added to a queue), then you should probably +// be using a ConditionVariable instead of a WaitableEvent. +// +// NOTE: On Windows, this class provides a subset of the functionality afforded +// by a Windows event object. This is intentional. If you are writing Windows +// specific code and you need other features of a Windows event, then you might +// be better off just using an Windows event directly. +class BUTIL_EXPORT WaitableEvent { + public: + // If manual_reset is true, then to set the event state to non-signaled, a + // consumer must call the Reset method. If this parameter is false, then the + // system automatically resets the event state to non-signaled after a single + // waiting thread has been released. + WaitableEvent(bool manual_reset, bool initially_signaled); + +#if defined(OS_WIN) + // Create a WaitableEvent from an Event HANDLE which has already been + // created. This objects takes ownership of the HANDLE and will close it when + // deleted. + explicit WaitableEvent(HANDLE event_handle); + + // Releases ownership of the handle from this object. + HANDLE Release(); +#endif + + ~WaitableEvent(); + + // Put the event in the un-signaled state. + void Reset(); + + // Put the event in the signaled state. Causing any thread blocked on Wait + // to be woken up. + void Signal(); + + // Returns true if the event is in the signaled state, else false. If this + // is not a manual reset event, then this test will cause a reset. + bool IsSignaled(); + + // Wait indefinitely for the event to be signaled. + void Wait(); + + // Wait up until max_time has passed for the event to be signaled. Returns + // true if the event was signaled. If this method returns false, then it + // does not necessarily mean that max_time was exceeded. + bool TimedWait(const TimeDelta& max_time); + +#if defined(OS_WIN) + HANDLE handle() const { return handle_; } +#endif + + // Wait, synchronously, on multiple events. + // waitables: an array of WaitableEvent pointers + // count: the number of elements in @waitables + // + // returns: the index of a WaitableEvent which has been signaled. + // + // You MUST NOT delete any of the WaitableEvent objects while this wait is + // happening. + static size_t WaitMany(WaitableEvent** waitables, size_t count); + + // For asynchronous waiting, see WaitableEventWatcher + + // This is a private helper class. It's here because it's used by friends of + // this class (such as WaitableEventWatcher) to be able to enqueue elements + // of the wait-list + class Waiter { + public: + // Signal the waiter to wake up. + // + // Consider the case of a Waiter which is in multiple WaitableEvent's + // wait-lists. Each WaitableEvent is automatic-reset and two of them are + // signaled at the same time. Now, each will wake only the first waiter in + // the wake-list before resetting. However, if those two waiters happen to + // be the same object (as can happen if another thread didn't have a chance + // to dequeue the waiter from the other wait-list in time), two auto-resets + // will have happened, but only one waiter has been signaled! + // + // Because of this, a Waiter may "reject" a wake by returning false. In + // this case, the auto-reset WaitableEvent shouldn't act as if anything has + // been notified. + virtual bool Fire(WaitableEvent* signaling_event) = 0; + + // Waiters may implement this in order to provide an extra condition for + // two Waiters to be considered equal. In WaitableEvent::Dequeue, if the + // pointers match then this function is called as a final check. See the + // comments in ~Handle for why. + virtual bool Compare(void* tag) = 0; + + protected: + virtual ~Waiter() {} + }; + + private: + friend class WaitableEventWatcher; + +#if defined(OS_WIN) + HANDLE handle_; +#else + // On Windows, one can close a HANDLE which is currently being waited on. The + // MSDN documentation says that the resulting behaviour is 'undefined', but + // it doesn't crash. However, if we were to include the following members + // directly then, on POSIX, one couldn't use WaitableEventWatcher to watch an + // event which gets deleted. This mismatch has bitten us several times now, + // so we have a kernel of the WaitableEvent, which is reference counted. + // WaitableEventWatchers may then take a reference and thus match the Windows + // behaviour. + struct WaitableEventKernel : + public RefCountedThreadSafe { + public: + WaitableEventKernel(bool manual_reset, bool initially_signaled); + + bool Dequeue(Waiter* waiter, void* tag); + + butil::Lock lock_; + const bool manual_reset_; + bool signaled_; + std::list waiters_; + + private: + friend class RefCountedThreadSafe; + ~WaitableEventKernel(); + }; + + typedef std::pair WaiterAndIndex; + + // When dealing with arrays of WaitableEvent*, we want to sort by the address + // of the WaitableEvent in order to have a globally consistent locking order. + // In that case we keep them, in sorted order, in an array of pairs where the + // second element is the index of the WaitableEvent in the original, + // unsorted, array. + static size_t EnqueueMany(WaiterAndIndex* waitables, + size_t count, Waiter* waiter); + + bool SignalAll(); + bool SignalOne(); + void Enqueue(Waiter* waiter); + + scoped_refptr kernel_; +#endif + + DISALLOW_COPY_AND_ASSIGN(WaitableEvent); +}; + +} // namespace butil + +#endif // BUTIL_SYNCHRONIZATION_WAITABLE_EVENT_H_ diff --git a/src/butil/synchronization/waitable_event_posix.cc b/src/butil/synchronization/waitable_event_posix.cc new file mode 100644 index 0000000..adeb573 --- /dev/null +++ b/src/butil/synchronization/waitable_event_posix.cc @@ -0,0 +1,408 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "butil/logging.h" +#include "butil/synchronization/waitable_event.h" +#include "butil/synchronization/condition_variable.h" +#include "butil/synchronization/lock.h" +#include "butil/threading/thread_restrictions.h" +#include "butil/time/time.h" + +// ----------------------------------------------------------------------------- +// A WaitableEvent on POSIX is implemented as a wait-list. Currently we don't +// support cross-process events (where one process can signal an event which +// others are waiting on). Because of this, we can avoid having one thread per +// listener in several cases. +// +// The WaitableEvent maintains a list of waiters, protected by a lock. Each +// waiter is either an async wait, in which case we have a Task and the +// MessageLoop to run it on, or a blocking wait, in which case we have the +// condition variable to signal. +// +// Waiting involves grabbing the lock and adding oneself to the wait list. Async +// waits can be canceled, which means grabbing the lock and removing oneself +// from the list. +// +// Waiting on multiple events is handled by adding a single, synchronous wait to +// the wait-list of many events. An event passes a pointer to itself when +// firing a waiter and so we can store that pointer to find out which event +// triggered. +// ----------------------------------------------------------------------------- + +namespace butil { + +// ----------------------------------------------------------------------------- +// This is just an abstract base class for waking the two types of waiters +// ----------------------------------------------------------------------------- +WaitableEvent::WaitableEvent(bool manual_reset, bool initially_signaled) + : kernel_(new WaitableEventKernel(manual_reset, initially_signaled)) { +} + +WaitableEvent::~WaitableEvent() { +} + +void WaitableEvent::Reset() { + butil::AutoLock locked(kernel_->lock_); + kernel_->signaled_ = false; +} + +void WaitableEvent::Signal() { + butil::AutoLock locked(kernel_->lock_); + + if (kernel_->signaled_) + return; + + if (kernel_->manual_reset_) { + SignalAll(); + kernel_->signaled_ = true; + } else { + // In the case of auto reset, if no waiters were woken, we remain + // signaled. + if (!SignalOne()) + kernel_->signaled_ = true; + } +} + +bool WaitableEvent::IsSignaled() { + butil::AutoLock locked(kernel_->lock_); + + const bool result = kernel_->signaled_; + if (result && !kernel_->manual_reset_) + kernel_->signaled_ = false; + return result; +} + +// ----------------------------------------------------------------------------- +// Synchronous waits + +// ----------------------------------------------------------------------------- +// This is a synchronous waiter. The thread is waiting on the given condition +// variable and the fired flag in this object. +// ----------------------------------------------------------------------------- +class SyncWaiter : public WaitableEvent::Waiter { + public: + SyncWaiter() + : fired_(false), + signaling_event_(NULL), + lock_(), + cv_(&lock_) { + } + + virtual bool Fire(WaitableEvent* signaling_event) OVERRIDE { + butil::AutoLock locked(lock_); + + if (fired_) + return false; + + fired_ = true; + signaling_event_ = signaling_event; + + cv_.Broadcast(); + + // Unlike AsyncWaiter objects, SyncWaiter objects are stack-allocated on + // the blocking thread's stack. There is no |delete this;| in Fire. The + // SyncWaiter object is destroyed when it goes out of scope. + + return true; + } + + WaitableEvent* signaling_event() const { + return signaling_event_; + } + + // --------------------------------------------------------------------------- + // These waiters are always stack allocated and don't delete themselves. Thus + // there's no problem and the ABA tag is the same as the object pointer. + // --------------------------------------------------------------------------- + virtual bool Compare(void* tag) OVERRIDE { + return this == tag; + } + + // --------------------------------------------------------------------------- + // Called with lock held. + // --------------------------------------------------------------------------- + bool fired() const { + return fired_; + } + + // --------------------------------------------------------------------------- + // During a TimedWait, we need a way to make sure that an auto-reset + // WaitableEvent doesn't think that this event has been signaled between + // unlocking it and removing it from the wait-list. Called with lock held. + // --------------------------------------------------------------------------- + void Disable() { + fired_ = true; + } + + butil::Lock* lock() { + return &lock_; + } + + butil::ConditionVariable* cv() { + return &cv_; + } + + private: + bool fired_; + WaitableEvent* signaling_event_; // The WaitableEvent which woke us + butil::Lock lock_; + butil::ConditionVariable cv_; +}; + +void WaitableEvent::Wait() { + bool result = TimedWait(TimeDelta::FromSeconds(-1)); + DCHECK(result) << "TimedWait() should never fail with infinite timeout"; +} + +bool WaitableEvent::TimedWait(const TimeDelta& max_time) { + butil::ThreadRestrictions::AssertWaitAllowed(); + const TimeTicks end_time(TimeTicks::Now() + max_time); + const bool finite_time = max_time.ToInternalValue() >= 0; + + kernel_->lock_.Acquire(); + if (kernel_->signaled_) { + if (!kernel_->manual_reset_) { + // In this case we were signaled when we had no waiters. Now that + // someone has waited upon us, we can automatically reset. + kernel_->signaled_ = false; + } + + kernel_->lock_.Release(); + return true; + } + + SyncWaiter sw; + sw.lock()->Acquire(); + + Enqueue(&sw); + kernel_->lock_.Release(); + // We are violating locking order here by holding the SyncWaiter lock but not + // the WaitableEvent lock. However, this is safe because we don't lock @lock_ + // again before unlocking it. + + for (;;) { + const TimeTicks current_time(TimeTicks::Now()); + + if (sw.fired() || (finite_time && current_time >= end_time)) { + const bool return_value = sw.fired(); + + // We can't acquire @lock_ before releasing the SyncWaiter lock (because + // of locking order), however, in between the two a signal could be fired + // and @sw would accept it, however we will still return false, so the + // signal would be lost on an auto-reset WaitableEvent. Thus we call + // Disable which makes sw::Fire return false. + sw.Disable(); + sw.lock()->Release(); + + kernel_->lock_.Acquire(); + kernel_->Dequeue(&sw, &sw); + kernel_->lock_.Release(); + + return return_value; + } + + if (finite_time) { + const TimeDelta max_wait(end_time - current_time); + sw.cv()->TimedWait(max_wait); + } else { + sw.cv()->Wait(); + } + } +} + +// ----------------------------------------------------------------------------- +// Synchronous waiting on multiple objects. + +static bool // StrictWeakOrdering +cmp_fst_addr(const std::pair &a, + const std::pair &b) { + return a.first < b.first; +} + +// static +size_t WaitableEvent::WaitMany(WaitableEvent** raw_waitables, + size_t count) { + butil::ThreadRestrictions::AssertWaitAllowed(); + DCHECK(count) << "Cannot wait on no events"; + + // We need to acquire the locks in a globally consistent order. Thus we sort + // the array of waitables by address. We actually sort a pairs so that we can + // map back to the original index values later. + std::vector > waitables; + waitables.reserve(count); + for (size_t i = 0; i < count; ++i) + waitables.emplace_back(raw_waitables[i], i); + + DCHECK_EQ(count, waitables.size()); + + sort(waitables.begin(), waitables.end(), cmp_fst_addr); + + // The set of waitables must be distinct. Since we have just sorted by + // address, we can check this cheaply by comparing pairs of consecutive + // elements. + for (size_t i = 0; i < waitables.size() - 1; ++i) { + DCHECK(waitables[i].first != waitables[i+1].first); + } + + SyncWaiter sw; + + const size_t r = EnqueueMany(&waitables[0], count, &sw); + if (r) { + // One of the events is already signaled. The SyncWaiter has not been + // enqueued anywhere. EnqueueMany returns the count of remaining waitables + // when the signaled one was seen, so the index of the signaled event is + // @count - @r. + return waitables[count - r].second; + } + + // At this point, we hold the locks on all the WaitableEvents and we have + // enqueued our waiter in them all. + sw.lock()->Acquire(); + // Release the WaitableEvent locks in the reverse order + for (size_t i = 0; i < count; ++i) { + waitables[count - (1 + i)].first->kernel_->lock_.Release(); + } + + for (;;) { + if (sw.fired()) + break; + + sw.cv()->Wait(); + } + sw.lock()->Release(); + + // The address of the WaitableEvent which fired is stored in the SyncWaiter. + WaitableEvent *const signaled_event = sw.signaling_event(); + // This will store the index of the raw_waitables which fired. + size_t signaled_index = 0; + + // Take the locks of each WaitableEvent in turn (except the signaled one) and + // remove our SyncWaiter from the wait-list + for (size_t i = 0; i < count; ++i) { + if (raw_waitables[i] != signaled_event) { + raw_waitables[i]->kernel_->lock_.Acquire(); + // There's no possible ABA issue with the address of the SyncWaiter here + // because it lives on the stack. Thus the tag value is just the pointer + // value again. + raw_waitables[i]->kernel_->Dequeue(&sw, &sw); + raw_waitables[i]->kernel_->lock_.Release(); + } else { + signaled_index = i; + } + } + + return signaled_index; +} + +// ----------------------------------------------------------------------------- +// If return value == 0: +// The locks of the WaitableEvents have been taken in order and the Waiter has +// been enqueued in the wait-list of each. None of the WaitableEvents are +// currently signaled +// else: +// None of the WaitableEvent locks are held. The Waiter has not been enqueued +// in any of them and the return value is the index of the first WaitableEvent +// which was signaled, from the end of the array. +// ----------------------------------------------------------------------------- +// static +size_t WaitableEvent::EnqueueMany + (std::pair* waitables, + size_t count, Waiter* waiter) { + if (!count) + return 0; + + waitables[0].first->kernel_->lock_.Acquire(); + if (waitables[0].first->kernel_->signaled_) { + if (!waitables[0].first->kernel_->manual_reset_) + waitables[0].first->kernel_->signaled_ = false; + waitables[0].first->kernel_->lock_.Release(); + return count; + } + + const size_t r = EnqueueMany(waitables + 1, count - 1, waiter); + if (r) { + waitables[0].first->kernel_->lock_.Release(); + } else { + waitables[0].first->Enqueue(waiter); + } + + return r; +} + +// ----------------------------------------------------------------------------- + + +// ----------------------------------------------------------------------------- +// Private functions... + +WaitableEvent::WaitableEventKernel::WaitableEventKernel(bool manual_reset, + bool initially_signaled) + : manual_reset_(manual_reset), + signaled_(initially_signaled) { +} + +WaitableEvent::WaitableEventKernel::~WaitableEventKernel() { +} + +// ----------------------------------------------------------------------------- +// Wake all waiting waiters. Called with lock held. +// ----------------------------------------------------------------------------- +bool WaitableEvent::SignalAll() { + bool signaled_at_least_one = false; + + for (std::list::iterator + i = kernel_->waiters_.begin(); i != kernel_->waiters_.end(); ++i) { + if ((*i)->Fire(this)) + signaled_at_least_one = true; + } + + kernel_->waiters_.clear(); + return signaled_at_least_one; +} + +// --------------------------------------------------------------------------- +// Try to wake a single waiter. Return true if one was woken. Called with lock +// held. +// --------------------------------------------------------------------------- +bool WaitableEvent::SignalOne() { + for (;;) { + if (kernel_->waiters_.empty()) + return false; + + const bool r = (*kernel_->waiters_.begin())->Fire(this); + kernel_->waiters_.pop_front(); + if (r) + return true; + } +} + +// ----------------------------------------------------------------------------- +// Add a waiter to the list of those waiting. Called with lock held. +// ----------------------------------------------------------------------------- +void WaitableEvent::Enqueue(Waiter* waiter) { + kernel_->waiters_.push_back(waiter); +} + +// ----------------------------------------------------------------------------- +// Remove a waiter from the list of those waiting. Return true if the waiter was +// actually removed. Called with lock held. +// ----------------------------------------------------------------------------- +bool WaitableEvent::WaitableEventKernel::Dequeue(Waiter* waiter, void* tag) { + for (std::list::iterator + i = waiters_.begin(); i != waiters_.end(); ++i) { + if (*i == waiter && (*i)->Compare(tag)) { + waiters_.erase(i); + return true; + } + } + + return false; +} + +// ----------------------------------------------------------------------------- + +} // namespace butil diff --git a/src/butil/synchronous_event.h b/src/butil/synchronous_event.h new file mode 100644 index 0000000..b0839e5 --- /dev/null +++ b/src/butil/synchronous_event.h @@ -0,0 +1,233 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Sun Nov 7 21:43:34 CST 2010 + +#ifndef BUTIL_SYNCHRONOUS_EVENT_H +#define BUTIL_SYNCHRONOUS_EVENT_H + +#include // std::vector +#include // std::find +#include // errno +#include "butil/logging.h" + +// Synchronous event notification. +// Observers to an event will be called immediately in the same context where +// the event is notified. This utility uses a vector to store all observers +// thus is only suitable for a relatively small amount of observers. +// +// Example: +// // Declare event type +// typedef SynchronousEvent FooEvent; +// +// // Implement observer type +// class FooObserver : public FooEvent::Observer { +// void on_event(int, const Foo*) { +// ... handle the event ... +// } +// }; +// +// FooEvent foo_event; // An instance of the event +// FooObserver foo_observer; // An instance of the observer +// foo_event.subscribe(&foo_observer); // register the observer to the event +// foo_event.notify(1, NULL); // foo_observer.on_event(1, NULL) is +// // called *immediately* + +namespace butil { + +namespace detail { +// NOTE: This is internal class. Inherit SynchronousEvent<..>::Observer instead. +template class EventObserver; +} + +// All methods are *NOT* thread-safe. +// You can copy a SynchronousEvent. +template +class SynchronousEvent { +public: + typedef detail::EventObserver<_A1, _A2, _A3> Observer; + typedef std::vector ObserverSet; + + SynchronousEvent() : _n(0) {} + + // Add an observer, callable inside on_event() and added observers + // will be called with the same event in the same run. + // Returns 0 when successful, -1 when the obsever is NULL or already added. + int subscribe(Observer* ob) { + if (NULL == ob) { + LOG(ERROR) << "Observer is NULL"; + return -1; + } + if (std::find(_obs.begin(), _obs.end(), ob) != _obs.end()) { + return -1; + } + _obs.push_back(ob); + ++_n; + return 0; + } + + // Remove an observer, callable inside on_event(). + // Users are responsible for removing observers before destroying them. + // Returns 0 when successful, -1 when the observer is NULL or already removed. + int unsubscribe(Observer* ob) { + if (NULL == ob) { + LOG(ERROR) << "Observer is NULL"; + return -1; + } + typename ObserverSet::iterator + it = std::find(_obs.begin(), _obs.end(), ob); + if (it == _obs.end()) { + return -1; + } + *it = NULL; + --_n; + return 0; + } + + // Remove all observers, callable inside on_event() + void clear() { + for (typename ObserverSet::iterator + it = _obs.begin(); it != _obs.end(); ++it) { + *it = NULL; + } + _n = 0; + } + + // Get number of observers + size_t size() const { return _n; } + + // No observers or not + bool empty() const { return size() == 0UL; } + + // Notify observers without parameter, errno will not be changed + void notify() { + const int saved_errno = errno; + for (size_t i = 0; i < _obs.size(); ++i) { + if (_obs[i]) { + _obs[i]->on_event(); + } + } + _shrink(); + errno = saved_errno; + } + + // Notify observers with 1 parameter, errno will not be changed + template void notify(const _B1& b1) { + const int saved_errno = errno; + for (size_t i = 0; i < _obs.size(); ++i) { + if (_obs[i]) { + _obs[i]->on_event(b1); + } + } + _shrink(); + errno = saved_errno; + } + + // Notify observers with 2 parameters, errno will not be changed + template + void notify(const _B1& b1, const _B2& b2) { + const int saved_errno = errno; + for (size_t i = 0; i < _obs.size(); ++i) { + if (_obs[i]) { + _obs[i]->on_event(b1, b2); + } + } + _shrink(); + errno = saved_errno; + } + + // Notify observers with 3 parameters, errno will not be changed + template + void notify(const _B1& b1, const _B2& b2, const _B3& b3) { + const int saved_errno = errno; + for (size_t i = 0; i < _obs.size(); ++i) { + if (_obs[i]) { + _obs[i]->on_event(b1, b2, b3); + } + } + _shrink(); + errno = saved_errno; + } + +private: + void _shrink() { + if (_n == _obs.size()) { + return; + } + for (typename ObserverSet::iterator + it1 = _obs.begin(), + it2 = _obs.begin(); it2 != _obs.end(); ++it2) { + if (*it2) { + *it1++ = *it2; + } + } + _obs.resize(_n); + } + + size_t _n; + ObserverSet _obs; +}; + +namespace detail { + +// Add const reference for types which is larger than sizeof(void*). This +// is reasonable in most cases and making signature of SynchronousEvent<...> +// cleaner. +template struct _AddConstRef { typedef const T& type; }; +template struct _AddConstRef { typedef T& type; }; + +// We have to re-invent some wheels to avoid dependence on +template +struct if_c { typedef T1 type; }; + +template +struct if_c { typedef T2 type; }; + +template +struct AddConstRef : public if_c<(sizeof(T)<=sizeof(void*)), + T, typename _AddConstRef::type> {}; + +template <> class EventObserver { +public: + virtual ~EventObserver() {} + virtual void on_event() = 0; +}; + +template class EventObserver<_A1, void, void> { +public: + virtual ~EventObserver() {} + virtual void on_event(typename AddConstRef<_A1>::type) = 0; +}; + +template class EventObserver<_A1, _A2, void> { +public: + virtual ~EventObserver() {} + virtual void on_event(typename AddConstRef<_A1>::type, + typename AddConstRef<_A2>::type) = 0; +}; + +template class EventObserver { +public: + virtual ~EventObserver() {} + virtual void on_event(typename AddConstRef<_A1>::type, + typename AddConstRef<_A2>::type, + typename AddConstRef<_A3>::type) = 0; +}; +} // end namespace detail +} // end namespace butil + +#endif // BUTIL_SYNCHRONOUS_EVENT_H diff --git a/src/butil/sys_byteorder.h b/src/butil/sys_byteorder.h new file mode 100644 index 0000000..ae79203 --- /dev/null +++ b/src/butil/sys_byteorder.h @@ -0,0 +1,141 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This header defines cross-platform ByteSwap() implementations for 16, 32 and +// 64-bit values, and NetToHostXX() / HostToNextXX() functions equivalent to +// the traditional ntohX() and htonX() functions. +// Use the functions defined here rather than using the platform-specific +// functions directly. + +#ifndef BUTIL_SYS_BYTEORDER_H_ +#define BUTIL_SYS_BYTEORDER_H_ + +#include "butil/basictypes.h" +#include "butil/build_config.h" + +#if defined(OS_WIN) +#include +#else +#include +#endif + +#if defined(COMPILER_MSVC) +#include // for _byteswap_* +#elif defined(OS_MACOSX) +// Mac OS X / Darwin features +#include // for OSSwapInt* +#elif defined(OS_LINUX) +#include // for bswap_* +#endif + +namespace butil { + +#if defined(COMPILER_MSVC) +inline uint16_t ByteSwap(uint16_t x) { return _byteswap_ushort(x); } +inline uint32_t ByteSwap(uint32_t x) { return _byteswap_ulong(x); } +inline uint64_t ByteSwap(uint64_t x) { return _byteswap_uint64(x); } + +#elif defined(OS_MACOSX) +inline uint16_t ByteSwap(uint16_t x) { return OSSwapInt16(x); } +inline uint32_t ByteSwap(uint32_t x) { return OSSwapInt32(x); } +inline uint64_t ByteSwap(uint64_t x) { return OSSwapInt64(x); } + +#elif defined(OS_LINUX) +inline uint16_t ByteSwap(uint16_t x) { return bswap_16(x); } +inline uint32_t ByteSwap(uint32_t x) { return bswap_32(x); } +inline uint64_t ByteSwap(uint64_t x) { return bswap_64(x); } + +#else +// Returns a value with all bytes in |x| swapped, i.e. reverses the endianness. +inline uint16_t ByteSwap(uint16_t x) { + return (x << 8) | (x >> 8); +} + +inline uint32_t ByteSwap(uint32_t x) { + x = ((x & 0xff00ff00UL) >> 8) | ((x & 0x00ff00ffUL) << 8); + return (x >> 16) | (x << 16); +} + +inline uint64_t ByteSwap(uint64_t x) { + x = ((x & 0xff00ff00ff00ff00ULL) >> 8) | ((x & 0x00ff00ff00ff00ffULL) << 8); + x = ((x & 0xffff0000ffff0000ULL) >> 16) | ((x & 0x0000ffff0000ffffULL) << 16); + return (x >> 32) | (x << 32); +} +#endif + +// Converts the bytes in |x| from host order (endianness) to little endian, and +// returns the result. +inline uint16_t ByteSwapToLE16(uint16_t x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return x; +#else + return ByteSwap(x); +#endif +} +inline uint32_t ByteSwapToLE32(uint32_t x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return x; +#else + return ByteSwap(x); +#endif +} +inline uint64_t ByteSwapToLE64(uint64_t x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return x; +#else + return ByteSwap(x); +#endif +} + +// Converts the bytes in |x| from network to host order (endianness), and +// returns the result. +inline uint16_t NetToHost16(uint16_t x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return ByteSwap(x); +#else + return x; +#endif +} +inline uint32_t NetToHost32(uint32_t x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return ByteSwap(x); +#else + return x; +#endif +} +inline uint64_t NetToHost64(uint64_t x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return ByteSwap(x); +#else + return x; +#endif +} + +// Converts the bytes in |x| from host to network order (endianness), and +// returns the result. +inline uint16_t HostToNet16(uint16_t x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return ByteSwap(x); +#else + return x; +#endif +} +inline uint32_t HostToNet32(uint32_t x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return ByteSwap(x); +#else + return x; +#endif +} +inline uint64_t HostToNet64(uint64_t x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return ByteSwap(x); +#else + return x; +#endif +} + +} // namespace butil + +#endif // BUTIL_SYS_BYTEORDER_H_ diff --git a/src/butil/third_party/dmg_fp/README.chromium b/src/butil/third_party/dmg_fp/README.chromium new file mode 100644 index 0000000..33ab78b --- /dev/null +++ b/src/butil/third_party/dmg_fp/README.chromium @@ -0,0 +1,22 @@ +Name: David M. Gay's floating point routines +URL: http://www.netlib.org/fp/ +License: MIT-like + +Original dtoa.c file can be found at . +Original g_fmt.c file can be found at . + +List of changes made to original code: + - wrapped functions in dmg_fp namespace + - renamed .c files to .cc + - added dmg_fp.h header + - added #define IEEE_8087 to dtoa.cc + - added #define NO_HEX_FP to dtoa.cc + - made some minor changes to allow clean compilation under g++ -Wall, see + gcc_warnings.patch. + - made some minor changes to build on 64-bit, see gcc_64_bit.patch. + - made minor changes for -Wextra for Mac build, see mac_wextra.patch + - crash fix for running with reduced CPU float precision, see + float_precision_crash.patch and crbug.com/123157 + - Fix for 'warning C4703: potentially uninitialized local pointer variable' + in VS2012. + - Disable optimization on VS2013 pending fix of compiler optimization bug. diff --git a/src/butil/third_party/dmg_fp/dmg_fp.h b/src/butil/third_party/dmg_fp/dmg_fp.h new file mode 100644 index 0000000..4795397 --- /dev/null +++ b/src/butil/third_party/dmg_fp/dmg_fp.h @@ -0,0 +1,30 @@ +// Copyright (c) 2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_DMG_FP_H_ +#define THIRD_PARTY_DMG_FP_H_ + +namespace dmg_fp { + +// Return a nearest machine number to the input decimal +// string (or set errno to ERANGE). With IEEE arithmetic, ties are +// broken by the IEEE round-even rule. Otherwise ties are broken by +// biased rounding (add half and chop). +double strtod(const char* s00, char** se); + +// Convert double to ASCII string. For meaning of parameters +// see dtoa.cc file. +char* dtoa(double d, int mode, int ndigits, + int* decpt, int* sign, char** rve); + +// Must be used to free values returned by dtoa. +void freedtoa(char* s); + +// Store the closest decimal approximation to x in b (null terminated). +// Returns a pointer to b. It is sufficient for |b| to be 32 characters. +char* g_fmt(char* b, double x); + +} // namespace dmg_fp + +#endif // THIRD_PARTY_DMG_FP_H_ diff --git a/src/butil/third_party/dmg_fp/dtoa.cc b/src/butil/third_party/dmg_fp/dtoa.cc new file mode 100644 index 0000000..ef624af --- /dev/null +++ b/src/butil/third_party/dmg_fp/dtoa.cc @@ -0,0 +1,4210 @@ +/**************************************************************** + * + * The author of this software is David M. Gay. + * + * Copyright (c) 1991, 2000, 2001 by Lucent Technologies. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose without fee is hereby granted, provided that this entire notice + * is included in all copies of any software which is or includes a copy + * or modification of this software and in all copies of the supporting + * documentation for such software. + * + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + * + ***************************************************************/ + +/* Please send bug reports to David M. Gay (dmg at acm dot org, + * with " at " changed at "@" and " dot " changed to "."). */ + +/* On a machine with IEEE extended-precision registers, it is + * necessary to specify double-precision (53-bit) rounding precision + * before invoking strtod or dtoa. If the machine uses (the equivalent + * of) Intel 80x87 arithmetic, the call + * _control87(PC_53, MCW_PC); + * does this with many compilers. Whether this or another call is + * appropriate depends on the compiler; for this to work, it may be + * necessary to #include "float.h" or another system-dependent header + * file. + */ + +/* strtod for IEEE-, VAX-, and IBM-arithmetic machines. + * + * This strtod returns a nearest machine number to the input decimal + * string (or sets errno to ERANGE). With IEEE arithmetic, ties are + * broken by the IEEE round-even rule. Otherwise ties are broken by + * biased rounding (add half and chop). + * + * Inspired loosely by William D. Clinger's paper "How to Read Floating + * Point Numbers Accurately" [Proc. ACM SIGPLAN '90, pp. 92-101]. + * + * Modifications: + * + * 1. We only require IEEE, IBM, or VAX double-precision + * arithmetic (not IEEE double-extended). + * 2. We get by with floating-point arithmetic in a case that + * Clinger missed -- when we're computing d * 10^n + * for a small integer d and the integer n is not too + * much larger than 22 (the maximum integer k for which + * we can represent 10^k exactly), we may be able to + * compute (d*10^k) * 10^(e-k) with just one roundoff. + * 3. Rather than a bit-at-a-time adjustment of the binary + * result in the hard case, we use floating-point + * arithmetic to determine the adjustment to within + * one bit; only in really hard cases do we need to + * compute a second residual. + * 4. Because of 3., we don't need a large table of powers of 10 + * for ten-to-e (just some small tables, e.g. of 10^k + * for 0 <= k <= 22). + */ + +/* + * #define IEEE_8087 for IEEE-arithmetic machines where the least + * significant byte has the lowest address. + * #define IEEE_MC68k for IEEE-arithmetic machines where the most + * significant byte has the lowest address. + * #define Long int on machines with 32-bit ints and 64-bit longs. + * #define IBM for IBM mainframe-style floating-point arithmetic. + * #define VAX for VAX-style floating-point arithmetic (D_floating). + * #define No_leftright to omit left-right logic in fast floating-point + * computation of dtoa. + * #define Honor_FLT_ROUNDS if FLT_ROUNDS can assume the values 2 or 3 + * and strtod and dtoa should round accordingly. Unless Trust_FLT_ROUNDS + * is also #defined, fegetround() will be queried for the rounding mode. + * Note that both FLT_ROUNDS and fegetround() are specified by the C99 + * standard (and are specified to be consistent, with fesetround() + * affecting the value of FLT_ROUNDS), but that some (Linux) systems + * do not work correctly in this regard, so using fegetround() is more + * portable than using FLT_FOUNDS directly. + * #define Check_FLT_ROUNDS if FLT_ROUNDS can assume the values 2 or 3 + * and Honor_FLT_ROUNDS is not #defined. + * #define RND_PRODQUOT to use rnd_prod and rnd_quot (assembly routines + * that use extended-precision instructions to compute rounded + * products and quotients) with IBM. + * #define ROUND_BIASED for IEEE-format with biased rounding. + * #define Inaccurate_Divide for IEEE-format with correctly rounded + * products but inaccurate quotients, e.g., for Intel i860. + * #define NO_LONG_LONG on machines that do not have a "long long" + * integer type (of >= 64 bits). On such machines, you can + * #define Just_16 to store 16 bits per 32-bit Long when doing + * high-precision integer arithmetic. Whether this speeds things + * up or slows things down depends on the machine and the number + * being converted. If long long is available and the name is + * something other than "long long", #define Llong to be the name, + * and if "unsigned Llong" does not work as an unsigned version of + * Llong, #define #ULLong to be the corresponding unsigned type. + * #define KR_headers for old-style C function headers. + * #define Bad_float_h if your system lacks a float.h or if it does not + * define some or all of DBL_DIG, DBL_MAX_10_EXP, DBL_MAX_EXP, + * FLT_RADIX, FLT_ROUNDS, and DBL_MAX. + * #define MALLOC your_malloc, where your_malloc(n) acts like malloc(n) + * if memory is available and otherwise does something you deem + * appropriate. If MALLOC is undefined, malloc will be invoked + * directly -- and assumed always to succeed. Similarly, if you + * want something other than the system's free() to be called to + * recycle memory acquired from MALLOC, #define FREE to be the + * name of the alternate routine. (FREE or free is only called in + * pathological cases, e.g., in a dtoa call after a dtoa return in + * mode 3 with thousands of digits requested.) + * #define Omit_Private_Memory to omit logic (added Jan. 1998) for making + * memory allocations from a private pool of memory when possible. + * When used, the private pool is PRIVATE_MEM bytes long: 2304 bytes, + * unless #defined to be a different length. This default length + * suffices to get rid of MALLOC calls except for unusual cases, + * such as decimal-to-binary conversion of a very long string of + * digits. The longest string dtoa can return is about 751 bytes + * long. For conversions by strtod of strings of 800 digits and + * all dtoa conversions in single-threaded executions with 8-byte + * pointers, PRIVATE_MEM >= 7400 appears to suffice; with 4-byte + * pointers, PRIVATE_MEM >= 7112 appears adequate. + * #define NO_INFNAN_CHECK if you do not wish to have INFNAN_CHECK + * #defined automatically on IEEE systems. On such systems, + * when INFNAN_CHECK is #defined, strtod checks + * for Infinity and NaN (case insensitively). On some systems + * (e.g., some HP systems), it may be necessary to #define NAN_WORD0 + * appropriately -- to the most significant word of a quiet NaN. + * (On HP Series 700/800 machines, -DNAN_WORD0=0x7ff40000 works.) + * When INFNAN_CHECK is #defined and No_Hex_NaN is not #defined, + * strtod also accepts (case insensitively) strings of the form + * NaN(x), where x is a string of hexadecimal digits and spaces; + * if there is only one string of hexadecimal digits, it is taken + * for the 52 fraction bits of the resulting NaN; if there are two + * or more strings of hex digits, the first is for the high 20 bits, + * the second and subsequent for the low 32 bits, with intervening + * white space ignored; but if this results in none of the 52 + * fraction bits being on (an IEEE Infinity symbol), then NAN_WORD0 + * and NAN_WORD1 are used instead. + * #define MULTIPLE_THREADS if the system offers preemptively scheduled + * multiple threads. In this case, you must provide (or suitably + * #define) two locks, acquired by ACQUIRE_DTOA_LOCK(n) and freed + * by FREE_DTOA_LOCK(n) for n = 0 or 1. (The second lock, accessed + * in pow5mult, ensures lazy evaluation of only one copy of high + * powers of 5; omitting this lock would introduce a small + * probability of wasting memory, but would otherwise be harmless.) + * You must also invoke freedtoa(s) to free the value s returned by + * dtoa. You may do so whether or not MULTIPLE_THREADS is #defined. + * #define NO_IEEE_Scale to disable new (Feb. 1997) logic in strtod that + * avoids underflows on inputs whose result does not underflow. + * If you #define NO_IEEE_Scale on a machine that uses IEEE-format + * floating-point numbers and flushes underflows to zero rather + * than implementing gradual underflow, then you must also #define + * Sudden_Underflow. + * #define USE_LOCALE to use the current locale's decimal_point value. + * #define SET_INEXACT if IEEE arithmetic is being used and extra + * computation should be done to set the inexact flag when the + * result is inexact and avoid setting inexact when the result + * is exact. In this case, dtoa.c must be compiled in + * an environment, perhaps provided by #include "dtoa.c" in a + * suitable wrapper, that defines two functions, + * int get_inexact(void); + * void clear_inexact(void); + * such that get_inexact() returns a nonzero value if the + * inexact bit is already set, and clear_inexact() sets the + * inexact bit to 0. When SET_INEXACT is #defined, strtod + * also does extra computations to set the underflow and overflow + * flags when appropriate (i.e., when the result is tiny and + * inexact or when it is a numeric value rounded to +-infinity). + * #define NO_ERRNO if strtod should not assign errno = ERANGE when + * the result overflows to +-Infinity or underflows to 0. + * #define NO_HEX_FP to omit recognition of hexadecimal floating-point + * values by strtod. + * #define NO_STRTOD_BIGCOMP (on IEEE-arithmetic systems only for now) + * to disable logic for "fast" testing of very long input strings + * to strtod. This testing proceeds by initially truncating the + * input string, then if necessary comparing the whole string with + * a decimal expansion to decide close cases. This logic is only + * used for input more than STRTOD_DIGLIM digits long (default 40). + */ + +#define IEEE_8087 +#define NO_HEX_FP + +#ifndef Long +#if __LP64__ +#define Long int +#else +#define Long long +#endif +#endif +#ifndef ULong +typedef unsigned Long ULong; +#endif + +#ifdef DEBUG +#include "stdio.h" +#define Bug(x) {fprintf(stderr, "%s\n", x); exit(1);} +#endif + +#include "stdlib.h" +#include "string.h" + +#ifdef USE_LOCALE +#include "locale.h" +#endif + +#ifdef Honor_FLT_ROUNDS +#ifndef Trust_FLT_ROUNDS +#include +#endif +#endif + +#ifdef MALLOC +#ifdef KR_headers +extern char *MALLOC(); +#else +extern void *MALLOC(size_t); +#endif +#else +#define MALLOC malloc +#endif + +#ifndef Omit_Private_Memory +#ifndef PRIVATE_MEM +#define PRIVATE_MEM 2304 +#endif +#define PRIVATE_mem ((unsigned)((PRIVATE_MEM+sizeof(double)-1)/sizeof(double))) +static double private_mem[PRIVATE_mem], *pmem_next = private_mem; +#endif + +#undef IEEE_Arith +#undef Avoid_Underflow +#ifdef IEEE_MC68k +#define IEEE_Arith +#endif +#ifdef IEEE_8087 +#define IEEE_Arith +#endif + +#ifdef IEEE_Arith +#ifndef NO_INFNAN_CHECK +#undef INFNAN_CHECK +#define INFNAN_CHECK +#endif +#else +#undef INFNAN_CHECK +#define NO_STRTOD_BIGCOMP +#endif + +#include "errno.h" + +#ifdef Bad_float_h + +#ifdef IEEE_Arith +#define DBL_DIG 15 +#define DBL_MAX_10_EXP 308 +#define DBL_MAX_EXP 1024 +#define FLT_RADIX 2 +#endif /*IEEE_Arith*/ + +#ifdef IBM +#define DBL_DIG 16 +#define DBL_MAX_10_EXP 75 +#define DBL_MAX_EXP 63 +#define FLT_RADIX 16 +#define DBL_MAX 7.2370055773322621e+75 +#endif + +#ifdef VAX +#define DBL_DIG 16 +#define DBL_MAX_10_EXP 38 +#define DBL_MAX_EXP 127 +#define FLT_RADIX 2 +#define DBL_MAX 1.7014118346046923e+38 +#endif + +#ifndef LONG_MAX +#define LONG_MAX 2147483647 +#endif + +#else /* ifndef Bad_float_h */ +#include "float.h" +#endif /* Bad_float_h */ + +#ifndef __MATH_H__ +#include "math.h" +#endif + +namespace dmg_fp { + +#ifndef CONST +#ifdef KR_headers +#define CONST /* blank */ +#else +#define CONST const +#endif +#endif + +#if defined(IEEE_8087) + defined(IEEE_MC68k) + defined(VAX) + defined(IBM) != 1 +Exactly one of IEEE_8087, IEEE_MC68k, VAX, or IBM should be defined. +#endif + +typedef union { double d; ULong L[2]; } U; + +#ifdef IEEE_8087 +#define word0(x) (x)->L[1] +#define word1(x) (x)->L[0] +#else +#define word0(x) (x)->L[0] +#define word1(x) (x)->L[1] +#endif +#define dval(x) (x)->d + +#ifndef STRTOD_DIGLIM +#define STRTOD_DIGLIM 40 +#endif + +#ifdef DIGLIM_DEBUG +extern int strtod_diglim; +#else +#define strtod_diglim STRTOD_DIGLIM +#endif + +/* The following definition of Storeinc is appropriate for MIPS processors. + * An alternative that might be better on some machines is + * #define Storeinc(a,b,c) (*a++ = b << 16 | c & 0xffff) + */ +#if defined(IEEE_8087) + defined(VAX) +#define Storeinc(a,b,c) (((unsigned short *)a)[1] = (unsigned short)b, \ +((unsigned short *)a)[0] = (unsigned short)c, a++) +#else +#define Storeinc(a,b,c) (((unsigned short *)a)[0] = (unsigned short)b, \ +((unsigned short *)a)[1] = (unsigned short)c, a++) +#endif + +/* #define P DBL_MANT_DIG */ +/* Ten_pmax = floor(P*log(2)/log(5)) */ +/* Bletch = (highest power of 2 < DBL_MAX_10_EXP) / 16 */ +/* Quick_max = floor((P-1)*log(FLT_RADIX)/log(10) - 1) */ +/* Int_max = floor(P*log(FLT_RADIX)/log(10) - 1) */ + +#ifdef IEEE_Arith +#define Exp_shift 20 +#define Exp_shift1 20 +#define Exp_msk1 0x100000 +#define Exp_msk11 0x100000 +#define Exp_mask 0x7ff00000 +#define P 53 +#define Nbits 53 +#define Bias 1023 +#define Emax 1023 +#define Emin (-1022) +#define Exp_1 0x3ff00000 +#define Exp_11 0x3ff00000 +#define Ebits 11 +#define Frac_mask 0xfffff +#define Frac_mask1 0xfffff +#define Ten_pmax 22 +#define Bletch 0x10 +#define Bndry_mask 0xfffff +#define Bndry_mask1 0xfffff +#define LSB 1 +#define Sign_bit 0x80000000 +#define Log2P 1 +#define Tiny0 0 +#define Tiny1 1 +#define Quick_max 14 +#define Int_max 14 +#ifndef NO_IEEE_Scale +#define Avoid_Underflow +#ifdef Flush_Denorm /* debugging option */ +#undef Sudden_Underflow +#endif +#endif + +#ifndef Flt_Rounds +#ifdef FLT_ROUNDS +#define Flt_Rounds FLT_ROUNDS +#else +#define Flt_Rounds 1 +#endif +#endif /*Flt_Rounds*/ + +#ifdef Honor_FLT_ROUNDS +#undef Check_FLT_ROUNDS +#define Check_FLT_ROUNDS +#else +#define Rounding Flt_Rounds +#endif + +#else /* ifndef IEEE_Arith */ +#undef Check_FLT_ROUNDS +#undef Honor_FLT_ROUNDS +#undef SET_INEXACT +#undef Sudden_Underflow +#define Sudden_Underflow +#ifdef IBM +#undef Flt_Rounds +#define Flt_Rounds 0 +#define Exp_shift 24 +#define Exp_shift1 24 +#define Exp_msk1 0x1000000 +#define Exp_msk11 0x1000000 +#define Exp_mask 0x7f000000 +#define P 14 +#define Nbits 56 +#define Bias 65 +#define Emax 248 +#define Emin (-260) +#define Exp_1 0x41000000 +#define Exp_11 0x41000000 +#define Ebits 8 /* exponent has 7 bits, but 8 is the right value in b2d */ +#define Frac_mask 0xffffff +#define Frac_mask1 0xffffff +#define Bletch 4 +#define Ten_pmax 22 +#define Bndry_mask 0xefffff +#define Bndry_mask1 0xffffff +#define LSB 1 +#define Sign_bit 0x80000000 +#define Log2P 4 +#define Tiny0 0x100000 +#define Tiny1 0 +#define Quick_max 14 +#define Int_max 15 +#else /* VAX */ +#undef Flt_Rounds +#define Flt_Rounds 1 +#define Exp_shift 23 +#define Exp_shift1 7 +#define Exp_msk1 0x80 +#define Exp_msk11 0x800000 +#define Exp_mask 0x7f80 +#define P 56 +#define Nbits 56 +#define Bias 129 +#define Emax 126 +#define Emin (-129) +#define Exp_1 0x40800000 +#define Exp_11 0x4080 +#define Ebits 8 +#define Frac_mask 0x7fffff +#define Frac_mask1 0xffff007f +#define Ten_pmax 24 +#define Bletch 2 +#define Bndry_mask 0xffff007f +#define Bndry_mask1 0xffff007f +#define LSB 0x10000 +#define Sign_bit 0x8000 +#define Log2P 1 +#define Tiny0 0x80 +#define Tiny1 0 +#define Quick_max 15 +#define Int_max 15 +#endif /* IBM, VAX */ +#endif /* IEEE_Arith */ + +#ifndef IEEE_Arith +#define ROUND_BIASED +#endif + +#ifdef RND_PRODQUOT +#define rounded_product(a,b) a = rnd_prod(a, b) +#define rounded_quotient(a,b) a = rnd_quot(a, b) +#ifdef KR_headers +extern double rnd_prod(), rnd_quot(); +#else +extern double rnd_prod(double, double), rnd_quot(double, double); +#endif +#else +#define rounded_product(a,b) a *= b +#define rounded_quotient(a,b) a /= b +#endif + +#define Big0 (Frac_mask1 | Exp_msk1*(DBL_MAX_EXP+Bias-1)) +#define Big1 0xffffffff + +#ifndef Pack_32 +#define Pack_32 +#endif + +typedef struct BCinfo BCinfo; + struct +BCinfo { int dp0, dp1, dplen, dsign, e0, inexact, nd, nd0, rounding, scale, uflchk; }; + +#ifdef KR_headers +#define FFFFFFFF ((((unsigned long)0xffff)<<16)|(unsigned long)0xffff) +#else +#define FFFFFFFF 0xffffffffUL +#endif + +#ifdef NO_LONG_LONG +#undef ULLong +#ifdef Just_16 +#undef Pack_32 +/* When Pack_32 is not defined, we store 16 bits per 32-bit Long. + * This makes some inner loops simpler and sometimes saves work + * during multiplications, but it often seems to make things slightly + * slower. Hence the default is now to store 32 bits per Long. + */ +#endif +#else /* long long available */ +#ifndef Llong +#define Llong long long +#endif +#ifndef ULLong +#define ULLong unsigned Llong +#endif +#endif /* NO_LONG_LONG */ + +#ifndef MULTIPLE_THREADS +#define ACQUIRE_DTOA_LOCK(n) /*nothing*/ +#define FREE_DTOA_LOCK(n) /*nothing*/ +#endif + +#define Kmax 7 + +double strtod(const char *s00, char **se); +char *dtoa(double d, int mode, int ndigits, + int *decpt, int *sign, char **rve); + + struct +Bigint { + struct Bigint *next; + int k, maxwds, sign, wds; + ULong x[1]; + }; + + typedef struct Bigint Bigint; + + static Bigint *freelist[Kmax+1]; + + static Bigint * +Balloc +#ifdef KR_headers + (k) int k; +#else + (int k) +#endif +{ + int x; + Bigint *rv; +#ifndef Omit_Private_Memory + unsigned int len; +#endif + + ACQUIRE_DTOA_LOCK(0); + /* The k > Kmax case does not need ACQUIRE_DTOA_LOCK(0), */ + /* but this case seems very unlikely. */ + if (k <= Kmax && (rv = freelist[k])) + freelist[k] = rv->next; + else { + x = 1 << k; +#ifdef Omit_Private_Memory + rv = (Bigint *)MALLOC(sizeof(Bigint) + (x-1)*sizeof(ULong)); +#else + len = (sizeof(Bigint) + (x-1)*sizeof(ULong) + sizeof(double) - 1) + /sizeof(double); + if (k <= Kmax && pmem_next - private_mem + len <= PRIVATE_mem) { + rv = (Bigint*)pmem_next; + pmem_next += len; + } + else + rv = (Bigint*)MALLOC(len*sizeof(double)); +#endif + rv->k = k; + rv->maxwds = x; + } + FREE_DTOA_LOCK(0); + rv->sign = rv->wds = 0; + return rv; + } + + static void +Bfree +#ifdef KR_headers + (v) Bigint *v; +#else + (Bigint *v) +#endif +{ + if (v) { + if (v->k > Kmax) +#ifdef FREE + FREE((void*)v); +#else + free((void*)v); +#endif + else { + ACQUIRE_DTOA_LOCK(0); + v->next = freelist[v->k]; + freelist[v->k] = v; + FREE_DTOA_LOCK(0); + } + } + } + +#define Bcopy(x,y) memcpy((char *)&x->sign, (char *)&y->sign, \ +y->wds*sizeof(Long) + 2*sizeof(int)) + + static Bigint * +multadd +#ifdef KR_headers + (b, m, a) Bigint *b; int m, a; +#else + (Bigint *b, int m, int a) /* multiply by m and add a */ +#endif +{ + int i, wds; +#ifdef ULLong + ULong *x; + ULLong carry, y; +#else + ULong carry, *x, y; +#ifdef Pack_32 + ULong xi, z; +#endif +#endif + Bigint *b1; + + wds = b->wds; + x = b->x; + i = 0; + carry = a; + do { +#ifdef ULLong + y = *x * (ULLong)m + carry; + carry = y >> 32; + *x++ = y & FFFFFFFF; +#else +#ifdef Pack_32 + xi = *x; + y = (xi & 0xffff) * m + carry; + z = (xi >> 16) * m + (y >> 16); + carry = z >> 16; + *x++ = (z << 16) + (y & 0xffff); +#else + y = *x * m + carry; + carry = y >> 16; + *x++ = y & 0xffff; +#endif +#endif + } + while(++i < wds); + if (carry) { + if (wds >= b->maxwds) { + b1 = Balloc(b->k+1); + Bcopy(b1, b); + Bfree(b); + b = b1; + } + b->x[wds++] = carry; + b->wds = wds; + } + return b; + } + + static Bigint * +s2b +#ifdef KR_headers + (s, nd0, nd, y9, dplen) CONST char *s; int nd0, nd, dplen; ULong y9; +#else + (CONST char *s, int nd0, int nd, ULong y9, int dplen) +#endif +{ + Bigint *b; + int i, k; + Long x, y; + + x = (nd + 8) / 9; + for(k = 0, y = 1; x > y; y <<= 1, k++) ; +#ifdef Pack_32 + b = Balloc(k); + b->x[0] = y9; + b->wds = 1; +#else + b = Balloc(k+1); + b->x[0] = y9 & 0xffff; + b->wds = (b->x[1] = y9 >> 16) ? 2 : 1; +#endif + + i = 9; + if (9 < nd0) { + s += 9; + do b = multadd(b, 10, *s++ - '0'); + while(++i < nd0); + s += dplen; + } + else + s += dplen + 9; + for(; i < nd; i++) + b = multadd(b, 10, *s++ - '0'); + return b; + } + + static int +hi0bits +#ifdef KR_headers + (x) ULong x; +#else + (ULong x) +#endif +{ + int k = 0; + + if (!(x & 0xffff0000)) { + k = 16; + x <<= 16; + } + if (!(x & 0xff000000)) { + k += 8; + x <<= 8; + } + if (!(x & 0xf0000000)) { + k += 4; + x <<= 4; + } + if (!(x & 0xc0000000)) { + k += 2; + x <<= 2; + } + if (!(x & 0x80000000)) { + k++; + if (!(x & 0x40000000)) + return 32; + } + return k; + } + + static int +lo0bits +#ifdef KR_headers + (y) ULong *y; +#else + (ULong *y) +#endif +{ + int k; + ULong x = *y; + + if (x & 7) { + if (x & 1) + return 0; + if (x & 2) { + *y = x >> 1; + return 1; + } + *y = x >> 2; + return 2; + } + k = 0; + if (!(x & 0xffff)) { + k = 16; + x >>= 16; + } + if (!(x & 0xff)) { + k += 8; + x >>= 8; + } + if (!(x & 0xf)) { + k += 4; + x >>= 4; + } + if (!(x & 0x3)) { + k += 2; + x >>= 2; + } + if (!(x & 1)) { + k++; + x >>= 1; + if (!x) + return 32; + } + *y = x; + return k; + } + + static Bigint * +i2b +#ifdef KR_headers + (i) int i; +#else + (int i) +#endif +{ + Bigint *b; + + b = Balloc(1); + b->x[0] = i; + b->wds = 1; + return b; + } + + static Bigint * +mult +#ifdef KR_headers + (a, b) Bigint *a, *b; +#else + (Bigint *a, Bigint *b) +#endif +{ + Bigint *c; + int k, wa, wb, wc; + ULong *x, *xa, *xae, *xb, *xbe, *xc, *xc0; + ULong y; +#ifdef ULLong + ULLong carry, z; +#else + ULong carry, z; +#ifdef Pack_32 + ULong z2; +#endif +#endif + + if (a->wds < b->wds) { + c = a; + a = b; + b = c; + } + k = a->k; + wa = a->wds; + wb = b->wds; + wc = wa + wb; + if (wc > a->maxwds) + k++; + c = Balloc(k); + for(x = c->x, xa = x + wc; x < xa; x++) + *x = 0; + xa = a->x; + xae = xa + wa; + xb = b->x; + xbe = xb + wb; + xc0 = c->x; +#ifdef ULLong + for(; xb < xbe; xc0++) { + if ((y = *xb++)) { + x = xa; + xc = xc0; + carry = 0; + do { + z = *x++ * (ULLong)y + *xc + carry; + carry = z >> 32; + *xc++ = z & FFFFFFFF; + } + while(x < xae); + *xc = carry; + } + } +#else +#ifdef Pack_32 + for(; xb < xbe; xb++, xc0++) { + if (y = *xb & 0xffff) { + x = xa; + xc = xc0; + carry = 0; + do { + z = (*x & 0xffff) * y + (*xc & 0xffff) + carry; + carry = z >> 16; + z2 = (*x++ >> 16) * y + (*xc >> 16) + carry; + carry = z2 >> 16; + Storeinc(xc, z2, z); + } + while(x < xae); + *xc = carry; + } + if (y = *xb >> 16) { + x = xa; + xc = xc0; + carry = 0; + z2 = *xc; + do { + z = (*x & 0xffff) * y + (*xc >> 16) + carry; + carry = z >> 16; + Storeinc(xc, z, z2); + z2 = (*x++ >> 16) * y + (*xc & 0xffff) + carry; + carry = z2 >> 16; + } + while(x < xae); + *xc = z2; + } + } +#else + for(; xb < xbe; xc0++) { + if (y = *xb++) { + x = xa; + xc = xc0; + carry = 0; + do { + z = *x++ * y + *xc + carry; + carry = z >> 16; + *xc++ = z & 0xffff; + } + while(x < xae); + *xc = carry; + } + } +#endif +#endif + for(xc0 = c->x, xc = xc0 + wc; wc > 0 && !*--xc; --wc) ; + c->wds = wc; + return c; + } + + static Bigint *p5s; + + static Bigint * +pow5mult +#ifdef KR_headers + (b, k) Bigint *b; int k; +#else + (Bigint *b, int k) +#endif +{ + Bigint *b1, *p5, *p51; + int i; + static int p05[3] = { 5, 25, 125 }; + + if ((i = k & 3)) + b = multadd(b, p05[i-1], 0); + + if (!(k >>= 2)) + return b; + if (!(p5 = p5s)) { + /* first time */ +#ifdef MULTIPLE_THREADS + ACQUIRE_DTOA_LOCK(1); + if (!(p5 = p5s)) { + p5 = p5s = i2b(625); + p5->next = 0; + } + FREE_DTOA_LOCK(1); +#else + p5 = p5s = i2b(625); + p5->next = 0; +#endif + } + for(;;) { + if (k & 1) { + b1 = mult(b, p5); + Bfree(b); + b = b1; + } + if (!(k >>= 1)) + break; + if (!(p51 = p5->next)) { +#ifdef MULTIPLE_THREADS + ACQUIRE_DTOA_LOCK(1); + if (!(p51 = p5->next)) { + p51 = p5->next = mult(p5,p5); + p51->next = 0; + } + FREE_DTOA_LOCK(1); +#else + p51 = p5->next = mult(p5,p5); + p51->next = 0; +#endif + } + p5 = p51; + } + return b; + } + + static Bigint * +lshift +#ifdef KR_headers + (b, k) Bigint *b; int k; +#else + (Bigint *b, int k) +#endif +{ + int i, k1, n, n1; + Bigint *b1; + ULong *x, *x1, *xe, z; + +#ifdef Pack_32 + n = k >> 5; +#else + n = k >> 4; +#endif + k1 = b->k; + n1 = n + b->wds + 1; + for(i = b->maxwds; n1 > i; i <<= 1) + k1++; + b1 = Balloc(k1); + x1 = b1->x; + for(i = 0; i < n; i++) + *x1++ = 0; + x = b->x; + xe = x + b->wds; +#ifdef Pack_32 + if (k &= 0x1f) { + k1 = 32 - k; + z = 0; + do { + *x1++ = *x << k | z; + z = *x++ >> k1; + } + while(x < xe); + if ((*x1 = z)) + ++n1; + } +#else + if (k &= 0xf) { + k1 = 16 - k; + z = 0; + do { + *x1++ = *x << k & 0xffff | z; + z = *x++ >> k1; + } + while(x < xe); + if (*x1 = z) + ++n1; + } +#endif + else do + *x1++ = *x++; + while(x < xe); + b1->wds = n1 - 1; + Bfree(b); + return b1; + } + + static int +cmp +#ifdef KR_headers + (a, b) Bigint *a, *b; +#else + (Bigint *a, Bigint *b) +#endif +{ + ULong *xa, *xa0, *xb, *xb0; + int i, j; + + i = a->wds; + j = b->wds; +#ifdef DEBUG + if (i > 1 && !a->x[i-1]) + Bug("cmp called with a->x[a->wds-1] == 0"); + if (j > 1 && !b->x[j-1]) + Bug("cmp called with b->x[b->wds-1] == 0"); +#endif + if (i -= j) + return i; + xa0 = a->x; + xa = xa0 + j; + xb0 = b->x; + xb = xb0 + j; + for(;;) { + if (*--xa != *--xb) + return *xa < *xb ? -1 : 1; + if (xa <= xa0) + break; + } + return 0; + } + + static Bigint * +diff +#ifdef KR_headers + (a, b) Bigint *a, *b; +#else + (Bigint *a, Bigint *b) +#endif +{ + Bigint *c; + int i, wa, wb; + ULong *xa, *xae, *xb, *xbe, *xc; +#ifdef ULLong + ULLong borrow, y; +#else + ULong borrow, y; +#ifdef Pack_32 + ULong z; +#endif +#endif + + i = cmp(a,b); + if (!i) { + c = Balloc(0); + c->wds = 1; + c->x[0] = 0; + return c; + } + if (i < 0) { + c = a; + a = b; + b = c; + i = 1; + } + else + i = 0; + c = Balloc(a->k); + c->sign = i; + wa = a->wds; + xa = a->x; + xae = xa + wa; + wb = b->wds; + xb = b->x; + xbe = xb + wb; + xc = c->x; + borrow = 0; +#ifdef ULLong + do { + y = (ULLong)*xa++ - *xb++ - borrow; + borrow = y >> 32 & (ULong)1; + *xc++ = y & FFFFFFFF; + } + while(xb < xbe); + while(xa < xae) { + y = *xa++ - borrow; + borrow = y >> 32 & (ULong)1; + *xc++ = y & FFFFFFFF; + } +#else +#ifdef Pack_32 + do { + y = (*xa & 0xffff) - (*xb & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*xa++ >> 16) - (*xb++ >> 16) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(xc, z, y); + } + while(xb < xbe); + while(xa < xae) { + y = (*xa & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*xa++ >> 16) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(xc, z, y); + } +#else + do { + y = *xa++ - *xb++ - borrow; + borrow = (y & 0x10000) >> 16; + *xc++ = y & 0xffff; + } + while(xb < xbe); + while(xa < xae) { + y = *xa++ - borrow; + borrow = (y & 0x10000) >> 16; + *xc++ = y & 0xffff; + } +#endif +#endif + while(!*--xc) + wa--; + c->wds = wa; + return c; + } + + static double +ulp +#ifdef KR_headers + (x) U *x; +#else + (U *x) +#endif +{ + Long L; + U u; + + L = (word0(x) & Exp_mask) - (P-1)*Exp_msk1; +#ifndef Avoid_Underflow +#ifndef Sudden_Underflow + if (L > 0) { +#endif +#endif +#ifdef IBM + L |= Exp_msk1 >> 4; +#endif + word0(&u) = L; + word1(&u) = 0; +#ifndef Avoid_Underflow +#ifndef Sudden_Underflow + } + else { + L = -L >> Exp_shift; + if (L < Exp_shift) { + word0(&u) = 0x80000 >> L; + word1(&u) = 0; + } + else { + word0(&u) = 0; + L -= Exp_shift; + word1(&u) = L >= 31 ? 1 : 1 << 31 - L; + } + } +#endif +#endif + return dval(&u); + } + + static double +b2d +#ifdef KR_headers + (a, e) Bigint *a; int *e; +#else + (Bigint *a, int *e) +#endif +{ + ULong *xa, *xa0, w, y, z; + int k; + U d; +#ifdef VAX + ULong d0, d1; +#else +#define d0 word0(&d) +#define d1 word1(&d) +#endif + + xa0 = a->x; + xa = xa0 + a->wds; + y = *--xa; +#ifdef DEBUG + if (!y) Bug("zero y in b2d"); +#endif + k = hi0bits(y); + *e = 32 - k; +#ifdef Pack_32 + if (k < Ebits) { + d0 = Exp_1 | y >> (Ebits - k); + w = xa > xa0 ? *--xa : 0; + d1 = y << ((32-Ebits) + k) | w >> (Ebits - k); + goto ret_d; + } + z = xa > xa0 ? *--xa : 0; + if (k -= Ebits) { + d0 = Exp_1 | y << k | z >> (32 - k); + y = xa > xa0 ? *--xa : 0; + d1 = z << k | y >> (32 - k); + } + else { + d0 = Exp_1 | y; + d1 = z; + } +#else + if (k < Ebits + 16) { + z = xa > xa0 ? *--xa : 0; + d0 = Exp_1 | y << k - Ebits | z >> Ebits + 16 - k; + w = xa > xa0 ? *--xa : 0; + y = xa > xa0 ? *--xa : 0; + d1 = z << k + 16 - Ebits | w << k - Ebits | y >> 16 + Ebits - k; + goto ret_d; + } + z = xa > xa0 ? *--xa : 0; + w = xa > xa0 ? *--xa : 0; + k -= Ebits + 16; + d0 = Exp_1 | y << k + 16 | z << k | w >> 16 - k; + y = xa > xa0 ? *--xa : 0; + d1 = w << k + 16 | y << k; +#endif + ret_d: +#ifdef VAX + word0(&d) = d0 >> 16 | d0 << 16; + word1(&d) = d1 >> 16 | d1 << 16; +#else +#undef d0 +#undef d1 +#endif + return dval(&d); + } + + static Bigint * +d2b +#ifdef KR_headers + (d, e, bits) U *d; int *e, *bits; +#else + (U *d, int *e, int *bits) +#endif +{ + Bigint *b; + int de, k; + ULong *x, y, z; +#ifndef Sudden_Underflow + int i; +#endif +#ifdef VAX + ULong d0, d1; + d0 = word0(d) >> 16 | word0(d) << 16; + d1 = word1(d) >> 16 | word1(d) << 16; +#else +#define d0 word0(d) +#define d1 word1(d) +#endif + +#ifdef Pack_32 + b = Balloc(1); +#else + b = Balloc(2); +#endif + x = b->x; + + z = d0 & Frac_mask; + d0 &= 0x7fffffff; /* clear sign bit, which we ignore */ +#ifdef Sudden_Underflow + de = (int)(d0 >> Exp_shift); +#ifndef IBM + z |= Exp_msk11; +#endif +#else + if ((de = (int)(d0 >> Exp_shift))) + z |= Exp_msk1; +#endif +#ifdef Pack_32 + if ((y = d1)) { + if ((k = lo0bits(&y))) { + x[0] = y | z << (32 - k); + z >>= k; + } + else + x[0] = y; +#ifndef Sudden_Underflow + i = +#endif + b->wds = (x[1] = z) ? 2 : 1; + } + else { + k = lo0bits(&z); + x[0] = z; +#ifndef Sudden_Underflow + i = +#endif + b->wds = 1; + k += 32; + } +#else + if (y = d1) { + if (k = lo0bits(&y)) + if (k >= 16) { + x[0] = y | z << 32 - k & 0xffff; + x[1] = z >> k - 16 & 0xffff; + x[2] = z >> k; + i = 2; + } + else { + x[0] = y & 0xffff; + x[1] = y >> 16 | z << 16 - k & 0xffff; + x[2] = z >> k & 0xffff; + x[3] = z >> k+16; + i = 3; + } + else { + x[0] = y & 0xffff; + x[1] = y >> 16; + x[2] = z & 0xffff; + x[3] = z >> 16; + i = 3; + } + } + else { +#ifdef DEBUG + if (!z) + Bug("Zero passed to d2b"); +#endif + k = lo0bits(&z); + if (k >= 16) { + x[0] = z; + i = 0; + } + else { + x[0] = z & 0xffff; + x[1] = z >> 16; + i = 1; + } + k += 32; + } + while(!x[i]) + --i; + b->wds = i + 1; +#endif +#ifndef Sudden_Underflow + if (de) { +#endif +#ifdef IBM + *e = (de - Bias - (P-1) << 2) + k; + *bits = 4*P + 8 - k - hi0bits(word0(d) & Frac_mask); +#else + *e = de - Bias - (P-1) + k; + *bits = P - k; +#endif +#ifndef Sudden_Underflow + } + else { + *e = de - Bias - (P-1) + 1 + k; +#ifdef Pack_32 + *bits = 32*i - hi0bits(x[i-1]); +#else + *bits = (i+2)*16 - hi0bits(x[i]); +#endif + } +#endif + return b; + } +#undef d0 +#undef d1 + + static double +ratio +#ifdef KR_headers + (a, b) Bigint *a, *b; +#else + (Bigint *a, Bigint *b) +#endif +{ + U da, db; + int k, ka, kb; + + dval(&da) = b2d(a, &ka); + dval(&db) = b2d(b, &kb); +#ifdef Pack_32 + k = ka - kb + 32*(a->wds - b->wds); +#else + k = ka - kb + 16*(a->wds - b->wds); +#endif +#ifdef IBM + if (k > 0) { + word0(&da) += (k >> 2)*Exp_msk1; + if (k &= 3) + dval(&da) *= 1 << k; + } + else { + k = -k; + word0(&db) += (k >> 2)*Exp_msk1; + if (k &= 3) + dval(&db) *= 1 << k; + } +#else + if (k > 0) + word0(&da) += k*Exp_msk1; + else { + k = -k; + word0(&db) += k*Exp_msk1; + } +#endif + return dval(&da) / dval(&db); + } + + static CONST double +tens[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, + 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, + 1e20, 1e21, 1e22 +#ifdef VAX + , 1e23, 1e24 +#endif + }; + + static CONST double +#ifdef IEEE_Arith +bigtens[] = { 1e16, 1e32, 1e64, 1e128, 1e256 }; +static CONST double tinytens[] = { 1e-16, 1e-32, 1e-64, 1e-128, +#ifdef Avoid_Underflow + 9007199254740992.*9007199254740992.e-256 + /* = 2^106 * 1e-256 */ +#else + 1e-256 +#endif + }; +/* The factor of 2^53 in tinytens[4] helps us avoid setting the underflow */ +/* flag unnecessarily. It leads to a song and dance at the end of strtod. */ +#define Scale_Bit 0x10 +#define n_bigtens 5 +#else +#ifdef IBM +bigtens[] = { 1e16, 1e32, 1e64 }; +static CONST double tinytens[] = { 1e-16, 1e-32, 1e-64 }; +#define n_bigtens 3 +#else +bigtens[] = { 1e16, 1e32 }; +static CONST double tinytens[] = { 1e-16, 1e-32 }; +#define n_bigtens 2 +#endif +#endif + +#undef Need_Hexdig +#ifdef INFNAN_CHECK +#ifndef No_Hex_NaN +#define Need_Hexdig +#endif +#endif + +#ifndef Need_Hexdig +#ifndef NO_HEX_FP +#define Need_Hexdig +#endif +#endif + +#ifdef Need_Hexdig /*{*/ +static unsigned char hexdig[256]; + + static void +#ifdef KR_headers +htinit(h, s, inc) unsigned char *h; unsigned char *s; int inc; +#else +htinit(unsigned char *h, unsigned char *s, int inc) +#endif +{ + int i, j; + for(i = 0; (j = s[i]) !=0; i++) + h[j] = i + inc; + } + + static void +#ifdef KR_headers +hexdig_init() +#else +hexdig_init(void) +#endif +{ +#define USC (unsigned char *) + htinit(hexdig, USC "0123456789", 0x10); + htinit(hexdig, USC "abcdef", 0x10 + 10); + htinit(hexdig, USC "ABCDEF", 0x10 + 10); + } +#endif /* } Need_Hexdig */ + +#ifdef INFNAN_CHECK + +#ifndef NAN_WORD0 +#define NAN_WORD0 0x7ff80000 +#endif + +#ifndef NAN_WORD1 +#define NAN_WORD1 0 +#endif + + static int +match +#ifdef KR_headers + (sp, t) char **sp, *t; +#else + (CONST char **sp, CONST char *t) +#endif +{ + int c, d; + CONST char *s = *sp; + + while((d = *t++)) { + if ((c = *++s) >= 'A' && c <= 'Z') + c += 'a' - 'A'; + if (c != d) + return 0; + } + *sp = s + 1; + return 1; + } + +#ifndef No_Hex_NaN + static void +hexnan +#ifdef KR_headers + (rvp, sp) U *rvp; CONST char **sp; +#else + (U *rvp, CONST char **sp) +#endif +{ + ULong c, x[2]; + CONST char *s; + int c1, havedig, udx0, xshift; + + if (!hexdig[(int)'0']) + hexdig_init(); + x[0] = x[1] = 0; + havedig = xshift = 0; + udx0 = 1; + s = *sp; + /* allow optional initial 0x or 0X */ + while((c = *(CONST unsigned char*)(s+1)) && c <= ' ') + ++s; + if (s[1] == '0' && (s[2] == 'x' || s[2] == 'X')) + s += 2; + while((c = *(CONST unsigned char*)++s)) { + if ((c1 = hexdig[c])) + c = c1 & 0xf; + else if (c <= ' ') { + if (udx0 && havedig) { + udx0 = 0; + xshift = 1; + } + continue; + } +#ifdef GDTOA_NON_PEDANTIC_NANCHECK + else if (/*(*/ c == ')' && havedig) { + *sp = s + 1; + break; + } + else + return; /* invalid form: don't change *sp */ +#else + else { + do { + if (/*(*/ c == ')') { + *sp = s + 1; + break; + } + } while((c = *++s)); + break; + } +#endif + havedig = 1; + if (xshift) { + xshift = 0; + x[0] = x[1]; + x[1] = 0; + } + if (udx0) + x[0] = (x[0] << 4) | (x[1] >> 28); + x[1] = (x[1] << 4) | c; + } + if ((x[0] &= 0xfffff) || x[1]) { + word0(rvp) = Exp_mask | x[0]; + word1(rvp) = x[1]; + } + } +#endif /*No_Hex_NaN*/ +#endif /* INFNAN_CHECK */ + +#ifdef Pack_32 +#define ULbits 32 +#define kshift 5 +#define kmask 31 +#else +#define ULbits 16 +#define kshift 4 +#define kmask 15 +#endif +#ifndef NO_HEX_FP /*{*/ + + static void +#ifdef KR_headers +rshift(b, k) Bigint *b; int k; +#else +rshift(Bigint *b, int k) +#endif +{ + ULong *x, *x1, *xe, y; + int n; + + x = x1 = b->x; + n = k >> kshift; + if (n < b->wds) { + xe = x + b->wds; + x += n; + if (k &= kmask) { + n = 32 - k; + y = *x++ >> k; + while(x < xe) { + *x1++ = (y | (*x << n)) & 0xffffffff; + y = *x++ >> k; + } + if ((*x1 = y) !=0) + x1++; + } + else + while(x < xe) + *x1++ = *x++; + } + if ((b->wds = x1 - b->x) == 0) + b->x[0] = 0; + } + + static ULong +#ifdef KR_headers +any_on(b, k) Bigint *b; int k; +#else +any_on(Bigint *b, int k) +#endif +{ + int n, nwds; + ULong *x, *x0, x1, x2; + + x = b->x; + nwds = b->wds; + n = k >> kshift; + if (n > nwds) + n = nwds; + else if (n < nwds && (k &= kmask)) { + x1 = x2 = x[n]; + x1 >>= k; + x1 <<= k; + if (x1 != x2) + return 1; + } + x0 = x; + x += n; + while(x > x0) + if (*--x) + return 1; + return 0; + } + +enum { /* rounding values: same as FLT_ROUNDS */ + Round_zero = 0, + Round_near = 1, + Round_up = 2, + Round_down = 3 + }; + + static Bigint * +#ifdef KR_headers +increment(b) Bigint *b; +#else +increment(Bigint *b) +#endif +{ + ULong *x, *xe; + Bigint *b1; + + x = b->x; + xe = x + b->wds; + do { + if (*x < (ULong)0xffffffffL) { + ++*x; + return b; + } + *x++ = 0; + } while(x < xe); + { + if (b->wds >= b->maxwds) { + b1 = Balloc(b->k+1); + Bcopy(b1,b); + Bfree(b); + b = b1; + } + b->x[b->wds++] = 1; + } + return b; + } + + void +#ifdef KR_headers +gethex(sp, rvp, rounding, sign) + CONST char **sp; U *rvp; int rounding, sign; +#else +gethex( CONST char **sp, U *rvp, int rounding, int sign) +#endif +{ + Bigint *b; + CONST unsigned char *decpt, *s0, *s, *s1; + Long e, e1; + ULong L, lostbits, *x; + int big, denorm, esign, havedig, k, n, nbits, up, zret; +#ifdef IBM + int j; +#endif + enum { +#ifdef IEEE_Arith /*{{*/ + emax = 0x7fe - Bias - P + 1, + emin = Emin - P + 1 +#else /*}{*/ + emin = Emin - P, +#ifdef VAX + emax = 0x7ff - Bias - P + 1 +#endif +#ifdef IBM + emax = 0x7f - Bias - P +#endif +#endif /*}}*/ + }; +#ifdef USE_LOCALE + int i; +#ifdef NO_LOCALE_CACHE + const unsigned char *decimalpoint = (unsigned char*) + localeconv()->decimal_point; +#else + const unsigned char *decimalpoint; + static unsigned char *decimalpoint_cache; + if (!(s0 = decimalpoint_cache)) { + s0 = (unsigned char*)localeconv()->decimal_point; + if ((decimalpoint_cache = (unsigned char*) + MALLOC(strlen((CONST char*)s0) + 1))) { + strcpy((char*)decimalpoint_cache, (CONST char*)s0); + s0 = decimalpoint_cache; + } + } + decimalpoint = s0; +#endif +#endif + + if (!hexdig['0']) + hexdig_init(); + havedig = 0; + s0 = *(CONST unsigned char **)sp + 2; + while(s0[havedig] == '0') + havedig++; + s0 += havedig; + s = s0; + decpt = 0; + zret = 0; + e = 0; + if (hexdig[*s]) + havedig++; + else { + zret = 1; +#ifdef USE_LOCALE + for(i = 0; decimalpoint[i]; ++i) { + if (s[i] != decimalpoint[i]) + goto pcheck; + } + decpt = s += i; +#else + if (*s != '.') + goto pcheck; + decpt = ++s; +#endif + if (!hexdig[*s]) + goto pcheck; + while(*s == '0') + s++; + if (hexdig[*s]) + zret = 0; + havedig = 1; + s0 = s; + } + while(hexdig[*s]) + s++; +#ifdef USE_LOCALE + if (*s == *decimalpoint && !decpt) { + for(i = 1; decimalpoint[i]; ++i) { + if (s[i] != decimalpoint[i]) + goto pcheck; + } + decpt = s += i; +#else + if (*s == '.' && !decpt) { + decpt = ++s; +#endif + while(hexdig[*s]) + s++; + }/*}*/ + if (decpt) + e = -(((Long)(s-decpt)) << 2); + pcheck: + s1 = s; + big = esign = 0; + switch(*s) { + case 'p': + case 'P': + switch(*++s) { + case '-': + esign = 1; + // fall through + case '+': + s++; + } + if ((n = hexdig[*s]) == 0 || n > 0x19) { + s = s1; + break; + } + e1 = n - 0x10; + while((n = hexdig[*++s]) !=0 && n <= 0x19) { + if (e1 & 0xf8000000) + big = 1; + e1 = 10*e1 + n - 0x10; + } + if (esign) + e1 = -e1; + e += e1; + } + *sp = (char*)s; + if (!havedig) + *sp = (char*)s0 - 1; + if (zret) + goto retz1; + if (big) { + if (esign) { +#ifdef IEEE_Arith + switch(rounding) { + case Round_up: + if (sign) + break; + goto ret_tiny; + case Round_down: + if (!sign) + break; + goto ret_tiny; + } +#endif + goto retz; +#ifdef IEEE_Arith + ret_tiny: +#ifndef NO_ERRNO + errno = ERANGE; +#endif + word0(rvp) = 0; + word1(rvp) = 1; + return; +#endif /* IEEE_Arith */ + } + switch(rounding) { + case Round_near: + goto ovfl1; + case Round_up: + if (!sign) + goto ovfl1; + goto ret_big; + case Round_down: + if (sign) + goto ovfl1; + goto ret_big; + } + ret_big: + word0(rvp) = Big0; + word1(rvp) = Big1; + return; + } + n = s1 - s0 - 1; + for(k = 0; n > (1 << (kshift-2)) - 1; n >>= 1) + k++; + b = Balloc(k); + x = b->x; + n = 0; + L = 0; +#ifdef USE_LOCALE + for(i = 0; decimalpoint[i+1]; ++i); +#endif + while(s1 > s0) { +#ifdef USE_LOCALE + if (*--s1 == decimalpoint[i]) { + s1 -= i; + continue; + } +#else + if (*--s1 == '.') + continue; +#endif + if (n == ULbits) { + *x++ = L; + L = 0; + n = 0; + } + L |= (hexdig[*s1] & 0x0f) << n; + n += 4; + } + *x++ = L; + b->wds = n = x - b->x; + n = ULbits*n - hi0bits(L); + nbits = Nbits; + lostbits = 0; + x = b->x; + if (n > nbits) { + n -= nbits; + if (any_on(b,n)) { + lostbits = 1; + k = n - 1; + if (x[k>>kshift] & 1 << (k & kmask)) { + lostbits = 2; + if (k > 0 && any_on(b,k)) + lostbits = 3; + } + } + rshift(b, n); + e += n; + } + else if (n < nbits) { + n = nbits - n; + b = lshift(b, n); + e -= n; + x = b->x; + } + if (e > Emax) { + ovfl: + Bfree(b); + ovfl1: +#ifndef NO_ERRNO + errno = ERANGE; +#endif + word0(rvp) = Exp_mask; + word1(rvp) = 0; + return; + } + denorm = 0; + if (e < emin) { + denorm = 1; + n = emin - e; + if (n >= nbits) { +#ifdef IEEE_Arith /*{*/ + switch (rounding) { + case Round_near: + if (n == nbits && (n < 2 || any_on(b,n-1))) + goto ret_tiny; + break; + case Round_up: + if (!sign) + goto ret_tiny; + break; + case Round_down: + if (sign) + goto ret_tiny; + } +#endif /* } IEEE_Arith */ + Bfree(b); + retz: +#ifndef NO_ERRNO + errno = ERANGE; +#endif + retz1: + rvp->d = 0.; + return; + } + k = n - 1; + if (lostbits) + lostbits = 1; + else if (k > 0) + lostbits = any_on(b,k); + if (x[k>>kshift] & 1 << (k & kmask)) + lostbits |= 2; + nbits -= n; + rshift(b,n); + e = emin; + } + if (lostbits) { + up = 0; + switch(rounding) { + case Round_zero: + break; + case Round_near: + if (lostbits & 2 + && (lostbits & 1) | (x[0] & 1)) + up = 1; + break; + case Round_up: + up = 1 - sign; + break; + case Round_down: + up = sign; + } + if (up) { + k = b->wds; + b = increment(b); + x = b->x; + if (denorm) { +#if 0 + if (nbits == Nbits - 1 + && x[nbits >> kshift] & 1 << (nbits & kmask)) + denorm = 0; /* not currently used */ +#endif + } + else if (b->wds > k + || ((n = nbits & kmask) !=0 + && hi0bits(x[k-1]) < 32-n)) { + rshift(b,1); + if (++e > Emax) + goto ovfl; + } + } + } +#ifdef IEEE_Arith + if (denorm) + word0(rvp) = b->wds > 1 ? b->x[1] & ~0x100000 : 0; + else + word0(rvp) = (b->x[1] & ~0x100000) | ((e + 0x3ff + 52) << 20); + word1(rvp) = b->x[0]; +#endif +#ifdef IBM + if ((j = e & 3)) { + k = b->x[0] & ((1 << j) - 1); + rshift(b,j); + if (k) { + switch(rounding) { + case Round_up: + if (!sign) + increment(b); + break; + case Round_down: + if (sign) + increment(b); + break; + case Round_near: + j = 1 << (j-1); + if (k & j && ((k & (j-1)) | lostbits)) + increment(b); + } + } + } + e >>= 2; + word0(rvp) = b->x[1] | ((e + 65 + 13) << 24); + word1(rvp) = b->x[0]; +#endif +#ifdef VAX + /* The next two lines ignore swap of low- and high-order 2 bytes. */ + /* word0(rvp) = (b->x[1] & ~0x800000) | ((e + 129 + 55) << 23); */ + /* word1(rvp) = b->x[0]; */ + word0(rvp) = ((b->x[1] & ~0x800000) >> 16) | ((e + 129 + 55) << 7) | (b->x[1] << 16); + word1(rvp) = (b->x[0] >> 16) | (b->x[0] << 16); +#endif + Bfree(b); + } +#endif /*}!NO_HEX_FP*/ + + static int +#ifdef KR_headers +dshift(b, p2) Bigint *b; int p2; +#else +dshift(Bigint *b, int p2) +#endif +{ + int rv = hi0bits(b->x[b->wds-1]) - 4; + if (p2 > 0) + rv -= p2; + return rv & kmask; + } + + static int +quorem +#ifdef KR_headers + (b, S) Bigint *b, *S; +#else + (Bigint *b, Bigint *S) +#endif +{ + int n; + ULong *bx, *bxe, q, *sx, *sxe; +#ifdef ULLong + ULLong borrow, carry, y, ys; +#else + ULong borrow, carry, y, ys; +#ifdef Pack_32 + ULong si, z, zs; +#endif +#endif + + n = S->wds; +#ifdef DEBUG + /*debug*/ if (b->wds > n) + /*debug*/ Bug("oversize b in quorem"); +#endif + if (b->wds < n) + return 0; + sx = S->x; + sxe = sx + --n; + bx = b->x; + bxe = bx + n; + q = *bxe / (*sxe + 1); /* ensure q <= true quotient */ +#ifdef DEBUG + /*debug*/ if (q > 9) + /*debug*/ Bug("oversized quotient in quorem"); +#endif + if (q) { + borrow = 0; + carry = 0; + do { +#ifdef ULLong + ys = *sx++ * (ULLong)q + carry; + carry = ys >> 32; + y = *bx - (ys & FFFFFFFF) - borrow; + borrow = y >> 32 & (ULong)1; + *bx++ = y & FFFFFFFF; +#else +#ifdef Pack_32 + si = *sx++; + ys = (si & 0xffff) * q + carry; + zs = (si >> 16) * q + (ys >> 16); + carry = zs >> 16; + y = (*bx & 0xffff) - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*bx >> 16) - (zs & 0xffff) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(bx, z, y); +#else + ys = *sx++ * q + carry; + carry = ys >> 16; + y = *bx - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + *bx++ = y & 0xffff; +#endif +#endif + } + while(sx <= sxe); + if (!*bxe) { + bx = b->x; + while(--bxe > bx && !*bxe) + --n; + b->wds = n; + } + } + if (cmp(b, S) >= 0) { + q++; + borrow = 0; + carry = 0; + bx = b->x; + sx = S->x; + do { +#ifdef ULLong + ys = *sx++ + carry; + carry = ys >> 32; + y = *bx - (ys & FFFFFFFF) - borrow; + borrow = y >> 32 & (ULong)1; + *bx++ = y & FFFFFFFF; +#else +#ifdef Pack_32 + si = *sx++; + ys = (si & 0xffff) + carry; + zs = (si >> 16) + (ys >> 16); + carry = zs >> 16; + y = (*bx & 0xffff) - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*bx >> 16) - (zs & 0xffff) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(bx, z, y); +#else + ys = *sx++ + carry; + carry = ys >> 16; + y = *bx - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + *bx++ = y & 0xffff; +#endif +#endif + } + while(sx <= sxe); + bx = b->x; + bxe = bx + n; + if (!*bxe) { + while(--bxe > bx && !*bxe) + --n; + b->wds = n; + } + } + return q; + } + +#ifndef NO_STRTOD_BIGCOMP + + static void +bigcomp +#ifdef KR_headers + (rv, s0, bc) + U *rv; CONST char *s0; BCinfo *bc; +#else + (U *rv, CONST char *s0, BCinfo *bc) +#endif +{ + Bigint *b, *d; + int b2, bbits, d2, dd, dig, dsign, i, j, nd, nd0, p2, p5, speccase; + + dsign = bc->dsign; + nd = bc->nd; + nd0 = bc->nd0; + p5 = nd + bc->e0 - 1; + dd = speccase = 0; +#ifndef Sudden_Underflow + if (rv->d == 0.) { /* special case: value near underflow-to-zero */ + /* threshold was rounded to zero */ + b = i2b(1); + p2 = Emin - P + 1; + bbits = 1; +#ifdef Avoid_Underflow + word0(rv) = (P+2) << Exp_shift; +#else + word1(rv) = 1; +#endif + i = 0; +#ifdef Honor_FLT_ROUNDS + if (bc->rounding == 1) +#endif + { + speccase = 1; + --p2; + dsign = 0; + goto have_i; + } + } + else +#endif + b = d2b(rv, &p2, &bbits); +#ifdef Avoid_Underflow + p2 -= bc->scale; +#endif + /* floor(log2(rv)) == bbits - 1 + p2 */ + /* Check for denormal case. */ + i = P - bbits; + if (i > (j = P - Emin - 1 + p2)) { +#ifdef Sudden_Underflow + Bfree(b); + b = i2b(1); + p2 = Emin; + i = P - 1; +#ifdef Avoid_Underflow + word0(rv) = (1 + bc->scale) << Exp_shift; +#else + word0(rv) = Exp_msk1; +#endif + word1(rv) = 0; +#else + i = j; +#endif + } +#ifdef Honor_FLT_ROUNDS + if (bc->rounding != 1) { + if (i > 0) + b = lshift(b, i); + if (dsign) + b = increment(b); + } + else +#endif + { + b = lshift(b, ++i); + b->x[0] |= 1; + } +#ifndef Sudden_Underflow + have_i: +#endif + p2 -= p5 + i; + d = i2b(1); + /* Arrange for convenient computation of quotients: + * shift left if necessary so divisor has 4 leading 0 bits. + */ + if (p5 > 0) + d = pow5mult(d, p5); + else if (p5 < 0) + b = pow5mult(b, -p5); + if (p2 > 0) { + b2 = p2; + d2 = 0; + } + else { + b2 = 0; + d2 = -p2; + } + i = dshift(d, d2); + if ((b2 += i) > 0) + b = lshift(b, b2); + if ((d2 += i) > 0) + d = lshift(d, d2); + + /* Now b/d = exactly half-way between the two floating-point values */ + /* on either side of the input string. Compute first digit of b/d. */ + + if (!(dig = quorem(b,d))) { + b = multadd(b, 10, 0); /* very unlikely */ + dig = quorem(b,d); + } + + /* Compare b/d with s0 */ + + for(i = 0; i < nd0; ) { + if ((dd = s0[i++] - '0' - dig)) + goto ret; + if (!b->x[0] && b->wds == 1) { + if (i < nd) + dd = 1; + goto ret; + } + b = multadd(b, 10, 0); + dig = quorem(b,d); + } + for(j = bc->dp1; i++ < nd;) { + if ((dd = s0[j++] - '0' - dig)) + goto ret; + if (!b->x[0] && b->wds == 1) { + if (i < nd) + dd = 1; + goto ret; + } + b = multadd(b, 10, 0); + dig = quorem(b,d); + } + if (b->x[0] || b->wds > 1) + dd = -1; + ret: + Bfree(b); + Bfree(d); +#ifdef Honor_FLT_ROUNDS + if (bc->rounding != 1) { + if (dd < 0) { + if (bc->rounding == 0) { + if (!dsign) + goto retlow1; + } + else if (dsign) + goto rethi1; + } + else if (dd > 0) { + if (bc->rounding == 0) { + if (dsign) + goto rethi1; + goto ret1; + } + if (!dsign) + goto rethi1; + dval(rv) += 2.*ulp(rv); + } + else { + bc->inexact = 0; + if (dsign) + goto rethi1; + } + } + else +#endif + if (speccase) { + if (dd <= 0) + rv->d = 0.; + } + else if (dd < 0) { + if (!dsign) /* does not happen for round-near */ +retlow1: + dval(rv) -= ulp(rv); + } + else if (dd > 0) { + if (dsign) { + rethi1: + dval(rv) += ulp(rv); + } + } + else { + /* Exact half-way case: apply round-even rule. */ + if (word1(rv) & 1) { + if (dsign) + goto rethi1; + goto retlow1; + } + } + +#ifdef Honor_FLT_ROUNDS + ret1: +#endif + return; + } +#endif /* NO_STRTOD_BIGCOMP */ + + double +strtod +#ifdef KR_headers + (s00, se) CONST char *s00; char **se; +#else + (CONST char *s00, char **se) +#endif +{ + int bb2, bb5, bbe, bd2, bd5, bbbits, bs2, c, e, e1; + int esign, i, j, k, nd, nd0, nf, nz, nz0, sign; + CONST char *s, *s0, *s1; + double aadj, aadj1; + Long L; + U aadj2, adj, rv, rv0; + ULong y, z; + BCinfo bc; + Bigint *bb, *bb1, *bd, *bd0, *bs, *delta; +#ifdef SET_INEXACT + int oldinexact; +#endif +#ifdef Honor_FLT_ROUNDS /*{*/ +#ifdef Trust_FLT_ROUNDS /*{{ only define this if FLT_ROUNDS really works! */ + bc.rounding = Flt_Rounds; +#else /*}{*/ + bc.rounding = 1; + switch(fegetround()) { + case FE_TOWARDZERO: bc.rounding = 0; break; + case FE_UPWARD: bc.rounding = 2; break; + case FE_DOWNWARD: bc.rounding = 3; + } +#endif /*}}*/ +#endif /*}*/ +#ifdef USE_LOCALE + CONST char *s2; +#endif + + sign = nz0 = nz = bc.dplen = bc.uflchk = 0; + dval(&rv) = 0.; + for(s = s00;;s++) switch(*s) { + case '-': + sign = 1; + // fall through + case '+': + if (*++s) + goto break2; + // fall through + case 0: + goto ret0; + case '\t': + case '\n': + case '\v': + case '\f': + case '\r': + case ' ': + continue; + default: + goto break2; + } + break2: + if (*s == '0') { +#ifndef NO_HEX_FP /*{*/ + switch(s[1]) { + case 'x': + case 'X': +#ifdef Honor_FLT_ROUNDS + gethex(&s, &rv, bc.rounding, sign); +#else + gethex(&s, &rv, 1, sign); +#endif + goto ret; + } +#endif /*}*/ + nz0 = 1; + while(*++s == '0') ; + if (!*s) + goto ret; + } + s0 = s; + y = z = 0; + for(nd = nf = 0; (c = *s) >= '0' && c <= '9'; nd++, s++) + if (nd < 9) + y = 10*y + c - '0'; + else if (nd < 16) + z = 10*z + c - '0'; + nd0 = nd; + bc.dp0 = bc.dp1 = s - s0; +#ifdef USE_LOCALE + s1 = localeconv()->decimal_point; + if (c == *s1) { + c = '.'; + if (*++s1) { + s2 = s; + for(;;) { + if (*++s2 != *s1) { + c = 0; + break; + } + if (!*++s1) { + s = s2; + break; + } + } + } + } +#endif + if (c == '.') { + c = *++s; + bc.dp1 = s - s0; + bc.dplen = bc.dp1 - bc.dp0; + if (!nd) { + for(; c == '0'; c = *++s) + nz++; + if (c > '0' && c <= '9') { + s0 = s; + nf += nz; + nz = 0; + goto have_dig; + } + goto dig_done; + } + for(; c >= '0' && c <= '9'; c = *++s) { + have_dig: + nz++; + if (c -= '0') { + nf += nz; + for(i = 1; i < nz; i++) + if (nd++ < 9) + y *= 10; + else if (nd <= DBL_DIG + 1) + z *= 10; + if (nd++ < 9) + y = 10*y + c; + else if (nd <= DBL_DIG + 1) + z = 10*z + c; + nz = 0; + } + } + } + dig_done: + e = 0; + if (c == 'e' || c == 'E') { + if (!nd && !nz && !nz0) { + goto ret0; + } + s00 = s; + esign = 0; + switch(c = *++s) { + case '-': + esign = 1; + // fall through + case '+': + c = *++s; + } + if (c >= '0' && c <= '9') { + while(c == '0') + c = *++s; + if (c > '0' && c <= '9') { + L = c - '0'; + s1 = s; + while((c = *++s) >= '0' && c <= '9') + L = 10*L + c - '0'; + if (s - s1 > 8 || L > 19999) + /* Avoid confusion from exponents + * so large that e might overflow. + */ + e = 19999; /* safe for 16 bit ints */ + else + e = (int)L; + if (esign) + e = -e; + } + else + e = 0; + } + else + s = s00; + } + if (!nd) { + if (!nz && !nz0) { +#ifdef INFNAN_CHECK + /* Check for Nan and Infinity */ + if (!bc.dplen) + switch(c) { + case 'i': + case 'I': + if (match(&s,"nf")) { + --s; + if (!match(&s,"inity")) + ++s; + word0(&rv) = 0x7ff00000; + word1(&rv) = 0; + goto ret; + } + break; + case 'n': + case 'N': + if (match(&s, "an")) { + word0(&rv) = NAN_WORD0; + word1(&rv) = NAN_WORD1; +#ifndef No_Hex_NaN + if (*s == '(') /*)*/ + hexnan(&rv, &s); +#endif + goto ret; + } + } +#endif /* INFNAN_CHECK */ + ret0: + s = s00; + sign = 0; + } + goto ret; + } + bc.e0 = e1 = e -= nf; + + /* Now we have nd0 digits, starting at s0, followed by a + * decimal point, followed by nd-nd0 digits. The number we're + * after is the integer represented by those digits times + * 10**e */ + + if (!nd0) + nd0 = nd; + k = nd < DBL_DIG + 1 ? nd : DBL_DIG + 1; + dval(&rv) = y; + if (k > 9) { +#ifdef SET_INEXACT + if (k > DBL_DIG) + oldinexact = get_inexact(); +#endif + dval(&rv) = tens[k - 9] * dval(&rv) + z; + } + bd0 = 0; + if (nd <= DBL_DIG +#ifndef RND_PRODQUOT +#ifndef Honor_FLT_ROUNDS + && Flt_Rounds == 1 +#endif +#endif + ) { + if (!e) + goto ret; + if (e > 0) { + if (e <= Ten_pmax) { +#ifdef VAX + goto vax_ovfl_check; +#else +#ifdef Honor_FLT_ROUNDS + /* round correctly FLT_ROUNDS = 2 or 3 */ + if (sign) { + rv.d = -rv.d; + sign = 0; + } +#endif + /* rv = */ rounded_product(dval(&rv), tens[e]); + goto ret; +#endif + } + i = DBL_DIG - nd; + if (e <= Ten_pmax + i) { + /* A fancier test would sometimes let us do + * this for larger i values. + */ +#ifdef Honor_FLT_ROUNDS + /* round correctly FLT_ROUNDS = 2 or 3 */ + if (sign) { + rv.d = -rv.d; + sign = 0; + } +#endif + e -= i; + dval(&rv) *= tens[i]; +#ifdef VAX + /* VAX exponent range is so narrow we must + * worry about overflow here... + */ + vax_ovfl_check: + word0(&rv) -= P*Exp_msk1; + /* rv = */ rounded_product(dval(&rv), tens[e]); + if ((word0(&rv) & Exp_mask) + > Exp_msk1*(DBL_MAX_EXP+Bias-1-P)) + goto ovfl; + word0(&rv) += P*Exp_msk1; +#else + /* rv = */ rounded_product(dval(&rv), tens[e]); +#endif + goto ret; + } + } +#ifndef Inaccurate_Divide + else if (e >= -Ten_pmax) { +#ifdef Honor_FLT_ROUNDS + /* round correctly FLT_ROUNDS = 2 or 3 */ + if (sign) { + rv.d = -rv.d; + sign = 0; + } +#endif + /* rv = */ rounded_quotient(dval(&rv), tens[-e]); + goto ret; + } +#endif + } + e1 += nd - k; + +#ifdef IEEE_Arith +#ifdef SET_INEXACT + bc.inexact = 1; + if (k <= DBL_DIG) + oldinexact = get_inexact(); +#endif +#ifdef Avoid_Underflow + bc.scale = 0; +#endif +#ifdef Honor_FLT_ROUNDS + if (bc.rounding >= 2) { + if (sign) + bc.rounding = bc.rounding == 2 ? 0 : 2; + else + if (bc.rounding != 2) + bc.rounding = 0; + } +#endif +#endif /*IEEE_Arith*/ + + /* Get starting approximation = rv * 10**e1 */ + + if (e1 > 0) { + if ((i = e1 & 15)) + dval(&rv) *= tens[i]; + if (e1 &= ~15) { + if (e1 > DBL_MAX_10_EXP) { + ovfl: +#ifndef NO_ERRNO + errno = ERANGE; +#endif + /* Can't trust HUGE_VAL */ +#ifdef IEEE_Arith +#ifdef Honor_FLT_ROUNDS + switch(bc.rounding) { + case 0: /* toward 0 */ + case 3: /* toward -infinity */ + word0(&rv) = Big0; + word1(&rv) = Big1; + break; + default: + word0(&rv) = Exp_mask; + word1(&rv) = 0; + } +#else /*Honor_FLT_ROUNDS*/ + word0(&rv) = Exp_mask; + word1(&rv) = 0; +#endif /*Honor_FLT_ROUNDS*/ +#ifdef SET_INEXACT + /* set overflow bit */ + dval(&rv0) = 1e300; + dval(&rv0) *= dval(&rv0); +#endif +#else /*IEEE_Arith*/ + word0(&rv) = Big0; + word1(&rv) = Big1; +#endif /*IEEE_Arith*/ + goto ret; + } + e1 >>= 4; + for(j = 0; e1 > 1; j++, e1 >>= 1) + if (e1 & 1) + dval(&rv) *= bigtens[j]; + /* The last multiplication could overflow. */ + word0(&rv) -= P*Exp_msk1; + dval(&rv) *= bigtens[j]; + if ((z = word0(&rv) & Exp_mask) + > Exp_msk1*(DBL_MAX_EXP+Bias-P)) + goto ovfl; + if (z > Exp_msk1*(DBL_MAX_EXP+Bias-1-P)) { + /* set to largest number */ + /* (Can't trust DBL_MAX) */ + word0(&rv) = Big0; + word1(&rv) = Big1; + } + else + word0(&rv) += P*Exp_msk1; + } + } + else if (e1 < 0) { + e1 = -e1; + if ((i = e1 & 15)) + dval(&rv) /= tens[i]; + if (e1 >>= 4) { + if (e1 >= 1 << n_bigtens) + goto undfl; +#ifdef Avoid_Underflow + if (e1 & Scale_Bit) + bc.scale = 2*P; + for(j = 0; e1 > 0; j++, e1 >>= 1) + if (e1 & 1) + dval(&rv) *= tinytens[j]; + if (bc.scale && (j = 2*P + 1 - ((word0(&rv) & Exp_mask) + >> Exp_shift)) > 0) { + /* scaled rv is denormal; clear j low bits */ + if (j >= 32) { + word1(&rv) = 0; + if (j >= 53) + word0(&rv) = (P+2)*Exp_msk1; + else + word0(&rv) &= 0xffffffff << (j-32); + } + else + word1(&rv) &= 0xffffffff << j; + } +#else + for(j = 0; e1 > 1; j++, e1 >>= 1) + if (e1 & 1) + dval(&rv) *= tinytens[j]; + /* The last multiplication could underflow. */ + dval(&rv0) = dval(&rv); + dval(&rv) *= tinytens[j]; + if (!dval(&rv)) { + dval(&rv) = 2.*dval(&rv0); + dval(&rv) *= tinytens[j]; +#endif + if (!dval(&rv)) { + undfl: + dval(&rv) = 0.; +#ifndef NO_ERRNO + errno = ERANGE; +#endif + goto ret; + } +#ifndef Avoid_Underflow + word0(&rv) = Tiny0; + word1(&rv) = Tiny1; + /* The refinement below will clean + * this approximation up. + */ + } +#endif + } + } + + /* Now the hard part -- adjusting rv to the correct value.*/ + + /* Put digits into bd: true value = bd * 10^e */ + + bc.nd = nd; +#ifndef NO_STRTOD_BIGCOMP + bc.nd0 = nd0; /* Only needed if nd > strtod_diglim, but done here */ + /* to silence an erroneous warning about bc.nd0 */ + /* possibly not being initialized. */ + if (nd > strtod_diglim) { + /* ASSERT(strtod_diglim >= 18); 18 == one more than the */ + /* minimum number of decimal digits to distinguish double values */ + /* in IEEE arithmetic. */ + i = j = 18; + if (i > nd0) + j += bc.dplen; + for(;;) { + if (--j <= bc.dp1 && j >= bc.dp0) + j = bc.dp0 - 1; + if (s0[j] != '0') + break; + --i; + } + e += nd - i; + nd = i; + if (nd0 > nd) + nd0 = nd; + if (nd < 9) { /* must recompute y */ + y = 0; + for(i = 0; i < nd0; ++i) + y = 10*y + s0[i] - '0'; + for(j = bc.dp1; i < nd; ++i) + y = 10*y + s0[j++] - '0'; + } + } +#endif + bd0 = s2b(s0, nd0, nd, y, bc.dplen); + + for(;;) { + bd = Balloc(bd0->k); + Bcopy(bd, bd0); + bb = d2b(&rv, &bbe, &bbbits); /* rv = bb * 2^bbe */ + bs = i2b(1); + + if (e >= 0) { + bb2 = bb5 = 0; + bd2 = bd5 = e; + } + else { + bb2 = bb5 = -e; + bd2 = bd5 = 0; + } + if (bbe >= 0) + bb2 += bbe; + else + bd2 -= bbe; + bs2 = bb2; +#ifdef Honor_FLT_ROUNDS + if (bc.rounding != 1) + bs2++; +#endif +#ifdef Avoid_Underflow + j = bbe - bc.scale; + i = j + bbbits - 1; /* logb(rv) */ + if (i < Emin) /* denormal */ + j += P - Emin; + else + j = P + 1 - bbbits; +#else /*Avoid_Underflow*/ +#ifdef Sudden_Underflow +#ifdef IBM + j = 1 + 4*P - 3 - bbbits + ((bbe + bbbits - 1) & 3); +#else + j = P + 1 - bbbits; +#endif +#else /*Sudden_Underflow*/ + j = bbe; + i = j + bbbits - 1; /* logb(rv) */ + if (i < Emin) /* denormal */ + j += P - Emin; + else + j = P + 1 - bbbits; +#endif /*Sudden_Underflow*/ +#endif /*Avoid_Underflow*/ + bb2 += j; + bd2 += j; +#ifdef Avoid_Underflow + bd2 += bc.scale; +#endif + i = bb2 < bd2 ? bb2 : bd2; + if (i > bs2) + i = bs2; + if (i > 0) { + bb2 -= i; + bd2 -= i; + bs2 -= i; + } + if (bb5 > 0) { + bs = pow5mult(bs, bb5); + bb1 = mult(bs, bb); + Bfree(bb); + bb = bb1; + } + if (bb2 > 0) + bb = lshift(bb, bb2); + if (bd5 > 0) + bd = pow5mult(bd, bd5); + if (bd2 > 0) + bd = lshift(bd, bd2); + if (bs2 > 0) + bs = lshift(bs, bs2); + delta = diff(bb, bd); + bc.dsign = delta->sign; + delta->sign = 0; + i = cmp(delta, bs); +#ifndef NO_STRTOD_BIGCOMP + if (bc.nd > nd && i <= 0) { + if (bc.dsign) + break; /* Must use bigcomp(). */ +#ifdef Honor_FLT_ROUNDS + if (bc.rounding != 1) { + if (i < 0) + break; + } + else +#endif + { + bc.nd = nd; + i = -1; /* Discarded digits make delta smaller. */ + } + } +#endif +#ifdef Honor_FLT_ROUNDS + if (bc.rounding != 1) { + if (i < 0) { + /* Error is less than an ulp */ + if (!delta->x[0] && delta->wds <= 1) { + /* exact */ +#ifdef SET_INEXACT + bc.inexact = 0; +#endif + break; + } + if (bc.rounding) { + if (bc.dsign) { + adj.d = 1.; + goto apply_adj; + } + } + else if (!bc.dsign) { + adj.d = -1.; + if (!word1(&rv) + && !(word0(&rv) & Frac_mask)) { + y = word0(&rv) & Exp_mask; +#ifdef Avoid_Underflow + if (!bc.scale || y > 2*P*Exp_msk1) +#else + if (y) +#endif + { + delta = lshift(delta,Log2P); + if (cmp(delta, bs) <= 0) + adj.d = -0.5; + } + } + apply_adj: +#ifdef Avoid_Underflow + if (bc.scale && (y = word0(&rv) & Exp_mask) + <= 2*P*Exp_msk1) + word0(&adj) += (2*P+1)*Exp_msk1 - y; +#else +#ifdef Sudden_Underflow + if ((word0(&rv) & Exp_mask) <= + P*Exp_msk1) { + word0(&rv) += P*Exp_msk1; + dval(&rv) += adj.d*ulp(dval(&rv)); + word0(&rv) -= P*Exp_msk1; + } + else +#endif /*Sudden_Underflow*/ +#endif /*Avoid_Underflow*/ + dval(&rv) += adj.d*ulp(&rv); + } + break; + } + adj.d = ratio(delta, bs); + if (adj.d < 1.) + adj.d = 1.; + if (adj.d <= 0x7ffffffe) { + /* adj = rounding ? ceil(adj) : floor(adj); */ + y = adj.d; + if (y != adj.d) { + if (!((bc.rounding>>1) ^ bc.dsign)) + y++; + adj.d = y; + } + } +#ifdef Avoid_Underflow + if (bc.scale && (y = word0(&rv) & Exp_mask) <= 2*P*Exp_msk1) + word0(&adj) += (2*P+1)*Exp_msk1 - y; +#else +#ifdef Sudden_Underflow + if ((word0(&rv) & Exp_mask) <= P*Exp_msk1) { + word0(&rv) += P*Exp_msk1; + adj.d *= ulp(dval(&rv)); + if (bc.dsign) + dval(&rv) += adj.d; + else + dval(&rv) -= adj.d; + word0(&rv) -= P*Exp_msk1; + goto cont; + } +#endif /*Sudden_Underflow*/ +#endif /*Avoid_Underflow*/ + adj.d *= ulp(&rv); + if (bc.dsign) { + if (word0(&rv) == Big0 && word1(&rv) == Big1) + goto ovfl; + dval(&rv) += adj.d; + } + else + dval(&rv) -= adj.d; + goto cont; + } +#endif /*Honor_FLT_ROUNDS*/ + + if (i < 0) { + /* Error is less than half an ulp -- check for + * special case of mantissa a power of two. + */ + if (bc.dsign || word1(&rv) || word0(&rv) & Bndry_mask +#ifdef IEEE_Arith +#ifdef Avoid_Underflow + || (word0(&rv) & Exp_mask) <= (2*P+1)*Exp_msk1 +#else + || (word0(&rv) & Exp_mask) <= Exp_msk1 +#endif +#endif + ) { +#ifdef SET_INEXACT + if (!delta->x[0] && delta->wds <= 1) + bc.inexact = 0; +#endif + break; + } + if (!delta->x[0] && delta->wds <= 1) { + /* exact result */ +#ifdef SET_INEXACT + bc.inexact = 0; +#endif + break; + } + delta = lshift(delta,Log2P); + if (cmp(delta, bs) > 0) + goto drop_down; + break; + } + if (i == 0) { + /* exactly half-way between */ + if (bc.dsign) { + if ((word0(&rv) & Bndry_mask1) == Bndry_mask1 + && word1(&rv) == ( +#ifdef Avoid_Underflow + (bc.scale && (y = word0(&rv) & Exp_mask) <= 2*P*Exp_msk1) + ? (0xffffffff & (0xffffffff << (2*P+1-(y>>Exp_shift)))) : +#endif + 0xffffffff)) { + /*boundary case -- increment exponent*/ + word0(&rv) = (word0(&rv) & Exp_mask) + + Exp_msk1 +#ifdef IBM + | Exp_msk1 >> 4 +#endif + ; + word1(&rv) = 0; +#ifdef Avoid_Underflow + bc.dsign = 0; +#endif + break; + } + } + else if (!(word0(&rv) & Bndry_mask) && !word1(&rv)) { + drop_down: + /* boundary case -- decrement exponent */ +#ifdef Sudden_Underflow /*{{*/ + L = word0(&rv) & Exp_mask; +#ifdef IBM + if (L < Exp_msk1) +#else +#ifdef Avoid_Underflow + if (L <= (bc.scale ? (2*P+1)*Exp_msk1 : Exp_msk1)) +#else + if (L <= Exp_msk1) +#endif /*Avoid_Underflow*/ +#endif /*IBM*/ + { + if (bc.nd >nd) { + bc.uflchk = 1; + break; + } + goto undfl; + } + L -= Exp_msk1; +#else /*Sudden_Underflow}{*/ +#ifdef Avoid_Underflow + if (bc.scale) { + L = word0(&rv) & Exp_mask; + if (L <= (2*P+1)*Exp_msk1) { + if (L > (P+2)*Exp_msk1) + /* round even ==> */ + /* accept rv */ + break; + /* rv = smallest denormal */ + if (bc.nd >nd) { + bc.uflchk = 1; + break; + } + goto undfl; + } + } +#endif /*Avoid_Underflow*/ + L = (word0(&rv) & Exp_mask) - Exp_msk1; +#endif /*Sudden_Underflow}}*/ + word0(&rv) = L | Bndry_mask1; + word1(&rv) = 0xffffffff; +#ifdef IBM + goto cont; +#else + break; +#endif + } +#ifndef ROUND_BIASED + if (!(word1(&rv) & LSB)) + break; +#endif + if (bc.dsign) + dval(&rv) += ulp(&rv); +#ifndef ROUND_BIASED + else { + dval(&rv) -= ulp(&rv); +#ifndef Sudden_Underflow + if (!dval(&rv)) { + if (bc.nd >nd) { + bc.uflchk = 1; + break; + } + goto undfl; + } +#endif + } +#ifdef Avoid_Underflow + bc.dsign = 1 - bc.dsign; +#endif +#endif + break; + } + if ((aadj = ratio(delta, bs)) <= 2.) { + if (bc.dsign) + aadj = aadj1 = 1.; + else if (word1(&rv) || word0(&rv) & Bndry_mask) { +#ifndef Sudden_Underflow + if (word1(&rv) == Tiny1 && !word0(&rv)) { + if (bc.nd >nd) { + bc.uflchk = 1; + break; + } + goto undfl; + } +#endif + aadj = 1.; + aadj1 = -1.; + } + else { + /* special case -- power of FLT_RADIX to be */ + /* rounded down... */ + + if (aadj < 2./FLT_RADIX) + aadj = 1./FLT_RADIX; + else + aadj *= 0.5; + aadj1 = -aadj; + } + } + else { + aadj *= 0.5; + aadj1 = bc.dsign ? aadj : -aadj; +#ifdef Check_FLT_ROUNDS + switch(bc.rounding) { + case 2: /* towards +infinity */ + aadj1 -= 0.5; + break; + case 0: /* towards 0 */ + case 3: /* towards -infinity */ + aadj1 += 0.5; + } +#else + if (Flt_Rounds == 0) + aadj1 += 0.5; +#endif /*Check_FLT_ROUNDS*/ + } + y = word0(&rv) & Exp_mask; + + /* Check for overflow */ + + if (y == Exp_msk1*(DBL_MAX_EXP+Bias-1)) { + dval(&rv0) = dval(&rv); + word0(&rv) -= P*Exp_msk1; + adj.d = aadj1 * ulp(&rv); + dval(&rv) += adj.d; + if ((word0(&rv) & Exp_mask) >= + Exp_msk1*(DBL_MAX_EXP+Bias-P)) { + if (word0(&rv0) == Big0 && word1(&rv0) == Big1) + goto ovfl; + word0(&rv) = Big0; + word1(&rv) = Big1; + goto cont; + } + else + word0(&rv) += P*Exp_msk1; + } + else { +#ifdef Avoid_Underflow + if (bc.scale && y <= 2*P*Exp_msk1) { + if (aadj <= 0x7fffffff) { + if ((z = (ULong)aadj) <= 0) + z = 1; + aadj = z; + aadj1 = bc.dsign ? aadj : -aadj; + } + dval(&aadj2) = aadj1; + word0(&aadj2) += (2*P+1)*Exp_msk1 - y; + aadj1 = dval(&aadj2); + } + adj.d = aadj1 * ulp(&rv); + dval(&rv) += adj.d; +#else +#ifdef Sudden_Underflow + if ((word0(&rv) & Exp_mask) <= P*Exp_msk1) { + dval(&rv0) = dval(&rv); + word0(&rv) += P*Exp_msk1; + adj.d = aadj1 * ulp(&rv); + dval(&rv) += adj.d; +#ifdef IBM + if ((word0(&rv) & Exp_mask) < P*Exp_msk1) +#else + if ((word0(&rv) & Exp_mask) <= P*Exp_msk1) +#endif + { + if (word0(&rv0) == Tiny0 + && word1(&rv0) == Tiny1) { + if (bc.nd >nd) { + bc.uflchk = 1; + break; + } + goto undfl; + } + word0(&rv) = Tiny0; + word1(&rv) = Tiny1; + goto cont; + } + else + word0(&rv) -= P*Exp_msk1; + } + else { + adj.d = aadj1 * ulp(&rv); + dval(&rv) += adj.d; + } +#else /*Sudden_Underflow*/ + /* Compute adj so that the IEEE rounding rules will + * correctly round rv + adj in some half-way cases. + * If rv * ulp(rv) is denormalized (i.e., + * y <= (P-1)*Exp_msk1), we must adjust aadj to avoid + * trouble from bits lost to denormalization; + * example: 1.2e-307 . + */ + if (y <= (P-1)*Exp_msk1 && aadj > 1.) { + aadj1 = (double)(int)(aadj + 0.5); + if (!bc.dsign) + aadj1 = -aadj1; + } + adj.d = aadj1 * ulp(&rv); + dval(&rv) += adj.d; +#endif /*Sudden_Underflow*/ +#endif /*Avoid_Underflow*/ + } + z = word0(&rv) & Exp_mask; +#ifndef SET_INEXACT + if (bc.nd == nd) { +#ifdef Avoid_Underflow + if (!bc.scale) +#endif + if (y == z) { + /* Can we stop now? */ + L = (Long)aadj; + aadj -= L; + /* The tolerances below are conservative. */ + if (bc.dsign || word1(&rv) || word0(&rv) & Bndry_mask) { + if (aadj < .4999999 || aadj > .5000001) + break; + } + else if (aadj < .4999999/FLT_RADIX) + break; + } + } +#endif + cont: + Bfree(bb); + Bfree(bd); + Bfree(bs); + Bfree(delta); + } + Bfree(bb); + Bfree(bd); + Bfree(bs); + Bfree(bd0); + Bfree(delta); +#ifndef NO_STRTOD_BIGCOMP + if (bc.nd > nd) + bigcomp(&rv, s0, &bc); +#endif +#ifdef SET_INEXACT + if (bc.inexact) { + if (!oldinexact) { + word0(&rv0) = Exp_1 + (70 << Exp_shift); + word1(&rv0) = 0; + dval(&rv0) += 1.; + } + } + else if (!oldinexact) + clear_inexact(); +#endif +#ifdef Avoid_Underflow + if (bc.scale) { + word0(&rv0) = Exp_1 - 2*P*Exp_msk1; + word1(&rv0) = 0; + dval(&rv) *= dval(&rv0); +#ifndef NO_ERRNO + /* try to avoid the bug of testing an 8087 register value */ +#ifdef IEEE_Arith + if (!(word0(&rv) & Exp_mask)) +#else + if (word0(&rv) == 0 && word1(&rv) == 0) +#endif + errno = ERANGE; +#endif + } +#endif /* Avoid_Underflow */ +#ifdef SET_INEXACT + if (bc.inexact && !(word0(&rv) & Exp_mask)) { + /* set underflow bit */ + dval(&rv0) = 1e-300; + dval(&rv0) *= dval(&rv0); + } +#endif + ret: + if (se) + *se = (char *)s; + return sign ? -dval(&rv) : dval(&rv); + } + +#ifndef MULTIPLE_THREADS + static char *dtoa_result; +#endif + + static char * +#ifdef KR_headers +rv_alloc(i) int i; +#else +rv_alloc(int i) +#endif +{ + int j, k, *r; + + j = sizeof(ULong); + for(k = 0; + sizeof(Bigint) - sizeof(ULong) - sizeof(int) + j <= (size_t)i; + j <<= 1) + k++; + r = (int*)Balloc(k); + *r = k; + return +#ifndef MULTIPLE_THREADS + dtoa_result = +#endif + (char *)(r+1); + } + + static char * +#ifdef KR_headers +nrv_alloc(s, rve, n) char *s, **rve; int n; +#else +nrv_alloc(CONST char *s, char **rve, int n) +#endif +{ + char *rv, *t; + + t = rv = rv_alloc(n); + while((*t = *s++)) t++; + if (rve) + *rve = t; + return rv; + } + +/* freedtoa(s) must be used to free values s returned by dtoa + * when MULTIPLE_THREADS is #defined. It should be used in all cases, + * but for consistency with earlier versions of dtoa, it is optional + * when MULTIPLE_THREADS is not defined. + */ + + void +#ifdef KR_headers +freedtoa(s) char *s; +#else +freedtoa(char *s) +#endif +{ + Bigint *b = (Bigint *)((int *)s - 1); + b->maxwds = 1 << (b->k = *(int*)b); + Bfree(b); +#ifndef MULTIPLE_THREADS + if (s == dtoa_result) + dtoa_result = 0; +#endif + } + +/* dtoa for IEEE arithmetic (dmg): convert double to ASCII string. + * + * Inspired by "How to Print Floating-Point Numbers Accurately" by + * Guy L. Steele, Jr. and Jon L. White [Proc. ACM SIGPLAN '90, pp. 112-126]. + * + * Modifications: + * 1. Rather than iterating, we use a simple numeric overestimate + * to determine k = floor(log10(d)). We scale relevant + * quantities using O(log2(k)) rather than O(k) multiplications. + * 2. For some modes > 2 (corresponding to ecvt and fcvt), we don't + * try to generate digits strictly left to right. Instead, we + * compute with fewer bits and propagate the carry if necessary + * when rounding the final digit up. This is often faster. + * 3. Under the assumption that input will be rounded nearest, + * mode 0 renders 1e23 as 1e23 rather than 9.999999999999999e22. + * That is, we allow equality in stopping tests when the + * round-nearest rule will give the same floating-point value + * as would satisfaction of the stopping test with strict + * inequality. + * 4. We remove common factors of powers of 2 from relevant + * quantities. + * 5. When converting floating-point integers less than 1e16, + * we use floating-point arithmetic rather than resorting + * to multiple-precision integers. + * 6. When asked to produce fewer than 15 digits, we first try + * to get by with floating-point arithmetic; we resort to + * multiple-precision integer arithmetic only if we cannot + * guarantee that the floating-point calculation has given + * the correctly rounded result. For k requested digits and + * "uniformly" distributed input, the probability is + * something like 10^(k-15) that we must resort to the Long + * calculation. + */ + + char * +dtoa +#ifdef KR_headers + (dd, mode, ndigits, decpt, sign, rve) + double dd; int mode, ndigits, *decpt, *sign; char **rve; +#else + (double dd, int mode, int ndigits, int *decpt, int *sign, char **rve) +#endif +{ + /* Arguments ndigits, decpt, sign are similar to those + of ecvt and fcvt; trailing zeros are suppressed from + the returned string. If not null, *rve is set to point + to the end of the return value. If d is +-Infinity or NaN, + then *decpt is set to 9999. + + mode: + 0 ==> shortest string that yields d when read in + and rounded to nearest. + 1 ==> like 0, but with Steele & White stopping rule; + e.g. with IEEE P754 arithmetic , mode 0 gives + 1e23 whereas mode 1 gives 9.999999999999999e22. + 2 ==> max(1,ndigits) significant digits. This gives a + return value similar to that of ecvt, except + that trailing zeros are suppressed. + 3 ==> through ndigits past the decimal point. This + gives a return value similar to that from fcvt, + except that trailing zeros are suppressed, and + ndigits can be negative. + 4,5 ==> similar to 2 and 3, respectively, but (in + round-nearest mode) with the tests of mode 0 to + possibly return a shorter string that rounds to d. + With IEEE arithmetic and compilation with + -DHonor_FLT_ROUNDS, modes 4 and 5 behave the same + as modes 2 and 3 when FLT_ROUNDS != 1. + 6-9 ==> Debugging modes similar to mode - 4: don't try + fast floating-point estimate (if applicable). + + Values of mode other than 0-9 are treated as mode 0. + + Sufficient space is allocated to the return value + to hold the suppressed trailing zeros. + */ + + int bbits, b2, b5, be, dig, i, ieps, ilim, ilim0, ilim1, + j, j1, k, k0, k_check, leftright, m2, m5, s2, s5, + spec_case, try_quick; + Long L; +#ifndef Sudden_Underflow + int denorm; + ULong x; +#endif + Bigint *b, *b1, *delta, *mlo = NULL, *mhi, *S; + U d2, eps, u; + double ds; + char *s, *s0; +#ifdef SET_INEXACT + int inexact, oldinexact; +#endif +#ifdef Honor_FLT_ROUNDS /*{*/ + int Rounding; +#ifdef Trust_FLT_ROUNDS /*{{ only define this if FLT_ROUNDS really works! */ + Rounding = Flt_Rounds; +#else /*}{*/ + Rounding = 1; + switch(fegetround()) { + case FE_TOWARDZERO: Rounding = 0; break; + case FE_UPWARD: Rounding = 2; break; + case FE_DOWNWARD: Rounding = 3; + } +#endif /*}}*/ +#endif /*}*/ + +#ifndef MULTIPLE_THREADS + if (dtoa_result) { + freedtoa(dtoa_result); + dtoa_result = 0; + } +#endif + + u.d = dd; + if (word0(&u) & Sign_bit) { + /* set sign for everything, including 0's and NaNs */ + *sign = 1; + word0(&u) &= ~Sign_bit; /* clear sign bit */ + } + else + *sign = 0; + +#if defined(IEEE_Arith) + defined(VAX) +#ifdef IEEE_Arith + if ((word0(&u) & Exp_mask) == Exp_mask) +#else + if (word0(&u) == 0x8000) +#endif + { + /* Infinity or NaN */ + *decpt = 9999; +#ifdef IEEE_Arith + if (!word1(&u) && !(word0(&u) & 0xfffff)) + return nrv_alloc("Infinity", rve, 8); +#endif + return nrv_alloc("NaN", rve, 3); + } +#endif +#ifdef IBM + dval(&u) += 0; /* normalize */ +#endif + if (!dval(&u)) { + *decpt = 1; + return nrv_alloc("0", rve, 1); + } + +#ifdef SET_INEXACT + try_quick = oldinexact = get_inexact(); + inexact = 1; +#endif +#ifdef Honor_FLT_ROUNDS + if (Rounding >= 2) { + if (*sign) + Rounding = Rounding == 2 ? 0 : 2; + else + if (Rounding != 2) + Rounding = 0; + } +#endif + + b = d2b(&u, &be, &bbits); +#ifdef Sudden_Underflow + i = (int)(word0(&u) >> Exp_shift1 & (Exp_mask>>Exp_shift1)); +#else + if ((i = (int)(word0(&u) >> Exp_shift1 & (Exp_mask>>Exp_shift1)))) { +#endif + dval(&d2) = dval(&u); + word0(&d2) &= Frac_mask1; + word0(&d2) |= Exp_11; +#ifdef IBM + if (j = 11 - hi0bits(word0(&d2) & Frac_mask)) + dval(&d2) /= 1 << j; +#endif + + /* log(x) ~=~ log(1.5) + (x-1.5)/1.5 + * log10(x) = log(x) / log(10) + * ~=~ log(1.5)/log(10) + (x-1.5)/(1.5*log(10)) + * log10(d) = (i-Bias)*log(2)/log(10) + log10(d2) + * + * This suggests computing an approximation k to log10(d) by + * + * k = (i - Bias)*0.301029995663981 + * + ( (d2-1.5)*0.289529654602168 + 0.176091259055681 ); + * + * We want k to be too large rather than too small. + * The error in the first-order Taylor series approximation + * is in our favor, so we just round up the constant enough + * to compensate for any error in the multiplication of + * (i - Bias) by 0.301029995663981; since |i - Bias| <= 1077, + * and 1077 * 0.30103 * 2^-52 ~=~ 7.2e-14, + * adding 1e-13 to the constant term more than suffices. + * Hence we adjust the constant term to 0.1760912590558. + * (We could get a more accurate k by invoking log10, + * but this is probably not worthwhile.) + */ + + i -= Bias; +#ifdef IBM + i <<= 2; + i += j; +#endif +#ifndef Sudden_Underflow + denorm = 0; + } + else { + /* d is denormalized */ + + i = bbits + be + (Bias + (P-1) - 1); + x = i > 32 ? word0(&u) << (64 - i) | word1(&u) >> (i - 32) + : word1(&u) << (32 - i); + dval(&d2) = x; + word0(&d2) -= 31*Exp_msk1; /* adjust exponent */ + i -= (Bias + (P-1) - 1) + 1; + denorm = 1; + } +#endif + ds = (dval(&d2)-1.5)*0.289529654602168 + 0.1760912590558 + i*0.301029995663981; + k = (int)ds; + if (ds < 0. && ds != k) + k--; /* want k = floor(ds) */ + k_check = 1; + if (k >= 0 && k <= Ten_pmax) { + if (dval(&u) < tens[k]) + k--; + k_check = 0; + } + j = bbits - i - 1; + if (j >= 0) { + b2 = 0; + s2 = j; + } + else { + b2 = -j; + s2 = 0; + } + if (k >= 0) { + b5 = 0; + s5 = k; + s2 += k; + } + else { + b2 -= k; + b5 = -k; + s5 = 0; + } + if (mode < 0 || mode > 9) + mode = 0; + +#ifndef SET_INEXACT +#ifdef Check_FLT_ROUNDS + try_quick = Rounding == 1; +#else + try_quick = 1; +#endif +#endif /*SET_INEXACT*/ + + if (mode > 5) { + mode -= 4; + try_quick = 0; + } + leftright = 1; + ilim = ilim1 = -1; /* Values for cases 0 and 1; done here to */ + /* silence erroneous "gcc -Wall" warning. */ + switch(mode) { + case 0: + case 1: + i = 18; + ndigits = 0; + break; + case 2: + leftright = 0; + // fall through + case 4: + if (ndigits <= 0) + ndigits = 1; + ilim = ilim1 = i = ndigits; + break; + case 3: + leftright = 0; + // fall through + case 5: + i = ndigits + k + 1; + ilim = i; + ilim1 = i - 1; + if (i <= 0) + i = 1; + } + s = s0 = rv_alloc(i); + +#ifdef Honor_FLT_ROUNDS + if (mode > 1 && Rounding != 1) + leftright = 0; +#endif + + if (ilim >= 0 && ilim <= Quick_max && try_quick) { + + /* Try to get by with floating-point arithmetic. */ + + i = 0; + dval(&d2) = dval(&u); + k0 = k; + ilim0 = ilim; + ieps = 2; /* conservative */ + if (k > 0) { + ds = tens[k&0xf]; + j = k >> 4; + if (j & Bletch) { + /* prevent overflows */ + j &= Bletch - 1; + dval(&u) /= bigtens[n_bigtens-1]; + ieps++; + } + for(; j; j >>= 1, i++) + if (j & 1) { + ieps++; + ds *= bigtens[i]; + } + dval(&u) /= ds; + } + else if ((j1 = -k)) { + dval(&u) *= tens[j1 & 0xf]; + for(j = j1 >> 4; j; j >>= 1, i++) + if (j & 1) { + ieps++; + dval(&u) *= bigtens[i]; + } + } + if (k_check && dval(&u) < 1. && ilim > 0) { + if (ilim1 <= 0) + goto fast_failed; + ilim = ilim1; + k--; + dval(&u) *= 10.; + ieps++; + } + dval(&eps) = ieps*dval(&u) + 7.; + word0(&eps) -= (P-1)*Exp_msk1; + if (ilim == 0) { + S = mhi = 0; + dval(&u) -= 5.; + if (dval(&u) > dval(&eps)) + goto one_digit; + if (dval(&u) < -dval(&eps)) + goto no_digits; + goto fast_failed; + } +#ifndef No_leftright + if (leftright) { + /* Use Steele & White method of only + * generating digits needed. + */ + dval(&eps) = 0.5/tens[ilim-1] - dval(&eps); + for(i = 0;;) { + L = (Long)dval(&u); + dval(&u) -= L; + *s++ = '0' + (int)L; + if (dval(&u) < dval(&eps)) + goto ret1; + if (1. - dval(&u) < dval(&eps)) + goto bump_up; + if (++i >= ilim) + break; + dval(&eps) *= 10.; + dval(&u) *= 10.; + } + } + else { +#endif + /* Generate ilim digits, then fix them up. */ + dval(&eps) *= tens[ilim-1]; + for(i = 1;; i++, dval(&u) *= 10.) { + L = (Long)(dval(&u)); + if (!(dval(&u) -= L)) + ilim = i; + *s++ = '0' + (int)L; + if (i == ilim) { + if (dval(&u) > 0.5 + dval(&eps)) + goto bump_up; + else if (dval(&u) < 0.5 - dval(&eps)) { + while(*--s == '0') {} + s++; + goto ret1; + } + break; + } + } +#ifndef No_leftright + } +#endif + fast_failed: + s = s0; + dval(&u) = dval(&d2); + k = k0; + ilim = ilim0; + } + + /* Do we have a "small" integer? */ + + if (be >= 0 && k <= Int_max) { + /* Yes. */ + ds = tens[k]; + if (ndigits < 0 && ilim <= 0) { + S = mhi = 0; + if (ilim < 0 || dval(&u) <= 5*ds) + goto no_digits; + goto one_digit; + } + for(i = 1; i <= k + 1; i++, dval(&u) *= 10.) { + L = (Long)(dval(&u) / ds); + dval(&u) -= L*ds; +#ifdef Check_FLT_ROUNDS + /* If FLT_ROUNDS == 2, L will usually be high by 1 */ + if (dval(&u) < 0) { + L--; + dval(&u) += ds; + } +#endif + *s++ = '0' + (int)L; + if (!dval(&u)) { +#ifdef SET_INEXACT + inexact = 0; +#endif + break; + } + if (i == ilim) { +#ifdef Honor_FLT_ROUNDS + if (mode > 1) + switch(Rounding) { + case 0: goto ret1; + case 2: goto bump_up; + } +#endif + dval(&u) += dval(&u); + if (dval(&u) > ds || (dval(&u) == ds && L & 1)) { + bump_up: + while(*--s == '9') + if (s == s0) { + k++; + *s = '0'; + break; + } + ++*s++; + } + break; + } + } + goto ret1; + } + + m2 = b2; + m5 = b5; + mhi = mlo = 0; + if (leftright) { + i = +#ifndef Sudden_Underflow + denorm ? be + (Bias + (P-1) - 1 + 1) : +#endif +#ifdef IBM + 1 + 4*P - 3 - bbits + ((bbits + be - 1) & 3); +#else + 1 + P - bbits; +#endif + b2 += i; + s2 += i; + mhi = i2b(1); + } + if (m2 > 0 && s2 > 0) { + i = m2 < s2 ? m2 : s2; + b2 -= i; + m2 -= i; + s2 -= i; + } + if (b5 > 0) { + if (leftright) { + if (m5 > 0) { + mhi = pow5mult(mhi, m5); + b1 = mult(mhi, b); + Bfree(b); + b = b1; + } + if ((j = b5 - m5)) + b = pow5mult(b, j); + } + else + b = pow5mult(b, b5); + } + S = i2b(1); + if (s5 > 0) + S = pow5mult(S, s5); + + /* Check for special case that d is a normalized power of 2. */ + + spec_case = 0; + if ((mode < 2 || leftright) +#ifdef Honor_FLT_ROUNDS + && Rounding == 1 +#endif + ) { + if (!word1(&u) && !(word0(&u) & Bndry_mask) +#ifndef Sudden_Underflow + && word0(&u) & (Exp_mask & ~Exp_msk1) +#endif + ) { + /* The special case */ + b2 += Log2P; + s2 += Log2P; + spec_case = 1; + } + } + + /* Arrange for convenient computation of quotients: + * shift left if necessary so divisor has 4 leading 0 bits. + * + * Perhaps we should just compute leading 28 bits of S once + * and for all and pass them and a shift to quorem, so it + * can do shifts and ors to compute the numerator for q. + */ +#ifdef Pack_32 + if ((i = ((s5 ? 32 - hi0bits(S->x[S->wds-1]) : 1) + s2) & 0x1f)) + i = 32 - i; +#define iInc 28 +#else + if (i = ((s5 ? 32 - hi0bits(S->x[S->wds-1]) : 1) + s2) & 0xf) + i = 16 - i; +#define iInc 12 +#endif + i = dshift(S, s2); + b2 += i; + m2 += i; + s2 += i; + if (b2 > 0) + b = lshift(b, b2); + if (s2 > 0) + S = lshift(S, s2); + if (k_check) { + if (cmp(b,S) < 0) { + k--; + b = multadd(b, 10, 0); /* we botched the k estimate */ + if (leftright) + mhi = multadd(mhi, 10, 0); + ilim = ilim1; + } + } + if (ilim <= 0 && (mode == 3 || mode == 5)) { + if (ilim < 0 || cmp(b,S = multadd(S,5,0)) <= 0) { + /* no digits, fcvt style */ + no_digits: + k = -1 - ndigits; + goto ret; + } + one_digit: + *s++ = '1'; + k++; + goto ret; + } + if (leftright) { + if (m2 > 0) + mhi = lshift(mhi, m2); + + /* Compute mlo -- check for special case + * that d is a normalized power of 2. + */ + + mlo = mhi; + if (spec_case) { + mhi = Balloc(mhi->k); + Bcopy(mhi, mlo); + mhi = lshift(mhi, Log2P); + } + + for(i = 1;;i++) { + dig = quorem(b,S) + '0'; + /* Do we yet have the shortest decimal string + * that will round to d? + */ + j = cmp(b, mlo); + delta = diff(S, mhi); + j1 = delta->sign ? 1 : cmp(b, delta); + Bfree(delta); +#ifndef ROUND_BIASED + if (j1 == 0 && mode != 1 && !(word1(&u) & 1) +#ifdef Honor_FLT_ROUNDS + && Rounding >= 1 +#endif + ) { + if (dig == '9') + goto round_9_up; + if (j > 0) + dig++; +#ifdef SET_INEXACT + else if (!b->x[0] && b->wds <= 1) + inexact = 0; +#endif + *s++ = dig; + goto ret; + } +#endif + if (j < 0 || (j == 0 && mode != 1 +#ifndef ROUND_BIASED + && !(word1(&u) & 1) +#endif + )) { + if (!b->x[0] && b->wds <= 1) { +#ifdef SET_INEXACT + inexact = 0; +#endif + goto accept_dig; + } +#ifdef Honor_FLT_ROUNDS + if (mode > 1) + switch(Rounding) { + case 0: goto accept_dig; + case 2: goto keep_dig; + } +#endif /*Honor_FLT_ROUNDS*/ + if (j1 > 0) { + b = lshift(b, 1); + j1 = cmp(b, S); + if ((j1 > 0 || (j1 == 0 && dig & 1)) + && dig++ == '9') + goto round_9_up; + } + accept_dig: + *s++ = dig; + goto ret; + } + if (j1 > 0) { +#ifdef Honor_FLT_ROUNDS + if (!Rounding) + goto accept_dig; +#endif + if (dig == '9') { /* possible if i == 1 */ + round_9_up: + *s++ = '9'; + goto roundoff; + } + *s++ = dig + 1; + goto ret; + } +#ifdef Honor_FLT_ROUNDS + keep_dig: +#endif + *s++ = dig; + if (i == ilim) + break; + b = multadd(b, 10, 0); + if (mlo == mhi) + mlo = mhi = multadd(mhi, 10, 0); + else { + mlo = multadd(mlo, 10, 0); + mhi = multadd(mhi, 10, 0); + } + } + } + else + for(i = 1;; i++) { + *s++ = dig = quorem(b,S) + '0'; + if (!b->x[0] && b->wds <= 1) { +#ifdef SET_INEXACT + inexact = 0; +#endif + goto ret; + } + if (i >= ilim) + break; + b = multadd(b, 10, 0); + } + + /* Round off last digit */ + +#ifdef Honor_FLT_ROUNDS + switch(Rounding) { + case 0: goto trimzeros; + case 2: goto roundoff; + } +#endif + b = lshift(b, 1); + j = cmp(b, S); + if (j > 0 || (j == 0 && dig & 1)) { + roundoff: + while(*--s == '9') + if (s == s0) { + k++; + *s++ = '1'; + goto ret; + } + ++*s++; + } + else { +#ifdef Honor_FLT_ROUNDS + trimzeros: +#endif + while(*--s == '0') {} + s++; + } + ret: + Bfree(S); + if (mhi) { + if (mlo && mlo != mhi) + Bfree(mlo); + Bfree(mhi); + } + ret1: +#ifdef SET_INEXACT + if (inexact) { + if (!oldinexact) { + word0(&u) = Exp_1 + (70 << Exp_shift); + word1(&u) = 0; + dval(&u) += 1.; + } + } + else if (!oldinexact) + clear_inexact(); +#endif + Bfree(b); + *s = 0; + *decpt = k + 1; + if (rve) + *rve = s; + return s0; + } + +} // namespace dmg_fp diff --git a/src/butil/third_party/dmg_fp/dtoa_wrapper.cc b/src/butil/third_party/dmg_fp/dtoa_wrapper.cc new file mode 100644 index 0000000..15476a5 --- /dev/null +++ b/src/butil/third_party/dmg_fp/dtoa_wrapper.cc @@ -0,0 +1,46 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// The purpose of this file is to supply the macro definintions necessary +// to make third_party/dmg_fp/dtoa.cc threadsafe. +#include "butil/lazy_instance.h" +#include "butil/logging.h" +#include "butil/synchronization/lock.h" + +// We need two locks because they're sometimes grabbed at the same time. +// A single lock would lead to an attempted recursive grab. +static butil::LazyInstance::Leaky + dtoa_lock_0 = LAZY_INSTANCE_INITIALIZER; +static butil::LazyInstance::Leaky + dtoa_lock_1 = LAZY_INSTANCE_INITIALIZER; + +/* + * This define and the code below is to trigger thread-safe behavior + * in dtoa.cc, per this comment from the file: + * + * #define MULTIPLE_THREADS if the system offers preemptively scheduled + * multiple threads. In this case, you must provide (or suitably + * #define) two locks, acquired by ACQUIRE_DTOA_LOCK(n) and freed + * by FREE_DTOA_LOCK(n) for n = 0 or 1. (The second lock, accessed + * in pow5mult, ensures lazy evaluation of only one copy of high + * powers of 5; omitting this lock would introduce a small + * probability of wasting memory, but would otherwise be harmless.) + * You must also invoke freedtoa(s) to free the value s returned by + * dtoa. You may do so whether or not MULTIPLE_THREADS is #defined. + */ +#define MULTIPLE_THREADS + +inline static void ACQUIRE_DTOA_LOCK(size_t n) { + DCHECK(n < 2); + butil::Lock* lock = n == 0 ? dtoa_lock_0.Pointer() : dtoa_lock_1.Pointer(); + lock->Acquire(); +} + +inline static void FREE_DTOA_LOCK(size_t n) { + DCHECK(n < 2); + butil::Lock* lock = n == 0 ? dtoa_lock_0.Pointer() : dtoa_lock_1.Pointer(); + lock->Release(); +} + +#include "butil/third_party/dmg_fp/dtoa.cc" diff --git a/src/butil/third_party/dmg_fp/float_precision_crash.patch b/src/butil/third_party/dmg_fp/float_precision_crash.patch new file mode 100644 index 0000000..df64e7e --- /dev/null +++ b/src/butil/third_party/dmg_fp/float_precision_crash.patch @@ -0,0 +1,13 @@ +diff --git a/base/third_party/dmg_fp/dtoa.cc b/base/third_party/dmg_fp/dtoa.cc +index 3f7e794..3312fa4 100644 +--- dtoa.cc ++++ dtoa.cc +@@ -3891,7 +3891,7 @@ dtoa + goto no_digits; + goto one_digit; + } +- for(i = 1;; i++, dval(&u) *= 10.) { ++ for(i = 1; i <= k + 1; i++, dval(&u) *= 10.) { + L = (Long)(dval(&u) / ds); + dval(&u) -= L*ds; + #ifdef Check_FLT_ROUNDS diff --git a/src/butil/third_party/dmg_fp/g_fmt.cc b/src/butil/third_party/dmg_fp/g_fmt.cc new file mode 100644 index 0000000..9ef5bef --- /dev/null +++ b/src/butil/third_party/dmg_fp/g_fmt.cc @@ -0,0 +1,102 @@ +/**************************************************************** + * + * The author of this software is David M. Gay. + * + * Copyright (c) 1991, 1996 by Lucent Technologies. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose without fee is hereby granted, provided that this entire notice + * is included in all copies of any software which is or includes a copy + * or modification of this software and in all copies of the supporting + * documentation for such software. + * + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + * + ***************************************************************/ + +/* g_fmt(buf,x) stores the closest decimal approximation to x in buf; + * it suffices to declare buf + * char buf[32]; + */ + +#include "dmg_fp.h" + +namespace dmg_fp { + + char * +g_fmt(char *b, double x) +{ + int i, k; + char *s; + int decpt, j, sign; + char *b0, *s0, *se; + + b0 = b; +#ifdef IGNORE_ZERO_SIGN + if (!x) { + *b++ = '0'; + *b = 0; + goto done; + } +#endif + s = s0 = dtoa(x, 0, 0, &decpt, &sign, &se); + if (sign) + *b++ = '-'; + if (decpt == 9999) /* Infinity or Nan */ { + while((*b++ = *s++)) {} + goto done0; + } + if (decpt <= -4 || decpt > se - s + 5) { + *b++ = *s++; + if (*s) { + *b++ = '.'; + while((*b = *s++)) + b++; + } + *b++ = 'e'; + /* sprintf(b, "%+.2d", decpt - 1); */ + if (--decpt < 0) { + *b++ = '-'; + decpt = -decpt; + } + else + *b++ = '+'; + for(j = 2, k = 10; 10*k <= decpt; j++, k *= 10) {} + for(;;) { + i = decpt / k; + *b++ = i + '0'; + if (--j <= 0) + break; + decpt -= i*k; + decpt *= 10; + } + *b = 0; + } + else if (decpt <= 0) { + *b++ = '.'; + for(; decpt < 0; decpt++) + *b++ = '0'; + while((*b++ = *s++)) {} + } + else { + while((*b = *s++)) { + b++; + if (--decpt == 0 && *s) + *b++ = '.'; + } + for(; decpt > 0; decpt--) + *b++ = '0'; + *b = 0; + } + done0: + freedtoa(s0); +#ifdef IGNORE_ZERO_SIGN + done: +#endif + return b0; + } + +} // namespace dmg_fp diff --git a/src/butil/third_party/dmg_fp/gcc_64_bit.patch b/src/butil/third_party/dmg_fp/gcc_64_bit.patch new file mode 100644 index 0000000..ab943c0 --- /dev/null +++ b/src/butil/third_party/dmg_fp/gcc_64_bit.patch @@ -0,0 +1,25 @@ +Index: dtoa.cc +--- dtoa.cc (old copy) ++++ dtoa.cc (working copy) +@@ -183,8 +183,12 @@ + #define NO_HEX_FP + + #ifndef Long ++#if __LP64__ ++#define Long int ++#else + #define Long long + #endif ++#endif + #ifndef ULong + typedef unsigned Long ULong; + #endif +@@ -221,7 +225,7 @@ extern void *MALLOC(size_t); + #ifndef PRIVATE_MEM + #define PRIVATE_MEM 2304 + #endif +-#define PRIVATE_mem ((PRIVATE_MEM+sizeof(double)-1)/sizeof(double)) ++#define PRIVATE_mem ((unsigned)((PRIVATE_MEM+sizeof(double)-1)/sizeof(double))) + static double private_mem[PRIVATE_mem], *pmem_next = private_mem; + #endif + diff --git a/src/butil/third_party/dmg_fp/gcc_warnings.patch b/src/butil/third_party/dmg_fp/gcc_warnings.patch new file mode 100644 index 0000000..4262237 --- /dev/null +++ b/src/butil/third_party/dmg_fp/gcc_warnings.patch @@ -0,0 +1,126 @@ +Index: dtoa.cc +--- dtoa.cc (old copy) ++++ dtoa.cc (working copy) +@@ -179,6 +179,9 @@ + * used for input more than STRTOD_DIGLIM digits long (default 40). + */ + ++#define IEEE_8087 ++#define NO_HEX_FP ++ + #ifndef Long + #define Long long + #endif +@@ -280,9 +283,7 @@ + #include "math.h" + #endif + +-#ifdef __cplusplus +-extern "C" { +-#endif ++namespace dmg_fp { + + #ifndef CONST + #ifdef KR_headers +@@ -511,11 +512,9 @@ + + #define Kmax 7 + +-#ifdef __cplusplus +-extern "C" double strtod(const char *s00, char **se); +-extern "C" char *dtoa(double d, int mode, int ndigits, ++double strtod(const char *s00, char **se); ++char *dtoa(double d, int mode, int ndigits, + int *decpt, int *sign, char **rve); +-#endif + + struct + Bigint { +@@ -1527,7 +1526,7 @@ + #ifdef KR_headers + (sp, t) char **sp, *t; + #else +- (CONST char **sp, char *t) ++ (CONST char **sp, CONST char *t) + #endif + { + int c, d; +@@ -2234,7 +2234,7 @@ bigcomp + nd = bc->nd; + nd0 = bc->nd0; + p5 = nd + bc->e0 - 1; +- speccase = 0; ++ dd = speccase = 0; + #ifndef Sudden_Underflow + if (rv->d == 0.) { /* special case: value near underflow-to-zero */ + /* threshold was rounded to zero */ +@@ -3431,7 +3430,7 @@ + + j = sizeof(ULong); + for(k = 0; +- sizeof(Bigint) - sizeof(ULong) - sizeof(int) + j <= i; ++ sizeof(Bigint) - sizeof(ULong) - sizeof(int) + j <= (size_t)i; + j <<= 1) + k++; + r = (int*)Balloc(k); +@@ -3447,7 +3446,7 @@ + #ifdef KR_headers + nrv_alloc(s, rve, n) char *s, **rve; int n; + #else +-nrv_alloc(char *s, char **rve, int n) ++nrv_alloc(CONST char *s, char **rve, int n) + #endif + { + char *rv, *t; +@@ -4202,6 +4201,5 @@ + *rve = s; + return s0; + } +-#ifdef __cplusplus +-} +-#endif ++ ++} // namespace dmg_fp +Index: g_fmt.cc +--- g_fmt.cc (old copy) ++++ g_fmt.cc (new copy) +@@ -46,14 +46,14 @@ g_fmt(register char *b, double x) + if (sign) + *b++ = '-'; + if (decpt == 9999) /* Infinity or Nan */ { +- while(*b++ = *s++); ++ while((*b++ = *s++)); + goto done0; + } + if (decpt <= -4 || decpt > se - s + 5) { + *b++ = *s++; + if (*s) { + *b++ = '.'; +- while(*b = *s++) ++ while((*b = *s++)) + b++; + } + *b++ = 'e'; +@@ -79,10 +79,10 @@ g_fmt(register char *b, double x) + *b++ = '.'; + for(; decpt < 0; decpt++) + *b++ = '0'; +- while(*b++ = *s++); ++ while((*b++ = *s++)); + } + else { +- while(*b = *s++) { ++ while((*b = *s++)) { + b++; + if (--decpt == 0 && *s) + *b++ = '.'; +@@ -93,7 +93,9 @@ g_fmt(register char *b, double x) + } + done0: + freedtoa(s0); ++#ifdef IGNORE_ZERO_SIGN + done: ++#endif + return b0; + } + diff --git a/src/butil/third_party/dmg_fp/mac_wextra.patch b/src/butil/third_party/dmg_fp/mac_wextra.patch new file mode 100644 index 0000000..15340f2 --- /dev/null +++ b/src/butil/third_party/dmg_fp/mac_wextra.patch @@ -0,0 +1,53 @@ +Index: g_fmt.cc +=================================================================== +--- g_fmt.cc (revision 49784) ++++ g_fmt.cc (working copy) +@@ -46,7 +46,7 @@ + if (sign) + *b++ = '-'; + if (decpt == 9999) /* Infinity or Nan */ { +- while((*b++ = *s++)); ++ while((*b++ = *s++)) {} + goto done0; + } + if (decpt <= -4 || decpt > se - s + 5) { +@@ -64,7 +64,7 @@ + } + else + *b++ = '+'; +- for(j = 2, k = 10; 10*k <= decpt; j++, k *= 10); ++ for(j = 2, k = 10; 10*k <= decpt; j++, k *= 10) {} + for(;;) { + i = decpt / k; + *b++ = i + '0'; +@@ -79,7 +79,7 @@ + *b++ = '.'; + for(; decpt < 0; decpt++) + *b++ = '0'; +- while((*b++ = *s++)); ++ while((*b++ = *s++)) {} + } + else { + while((*b = *s++)) { +Index: dtoa.cc +=================================================================== +--- dtoa.cc (revision 49784) ++++ dtoa.cc (working copy) +@@ -3863,7 +3863,7 @@ + if (dval(&u) > 0.5 + dval(&eps)) + goto bump_up; + else if (dval(&u) < 0.5 - dval(&eps)) { +- while(*--s == '0'); ++ while(*--s == '0') {} + s++; + goto ret1; + } +@@ -4176,7 +4176,7 @@ + #ifdef Honor_FLT_ROUNDS + trimzeros: + #endif +- while(*--s == '0'); ++ while(*--s == '0') {} + s++; + } + ret: diff --git a/src/butil/third_party/dmg_fp/vs2013-optimization.patch b/src/butil/third_party/dmg_fp/vs2013-optimization.patch new file mode 100644 index 0000000..d91b370 --- /dev/null +++ b/src/butil/third_party/dmg_fp/vs2013-optimization.patch @@ -0,0 +1,18 @@ +Index: base/third_party/dmg_fp/dtoa.cc +diff --git a/base/third_party/dmg_fp/dtoa.cc b/base/third_party/dmg_fp/dtoa.cc +index 4eb9f0efd94221b3ab95f84554bbc92f112bf973..b03ccff569f9403eb67a95737b0e19740e56ef33 100644 +--- a/base/third_party/dmg_fp/dtoa.cc ++++ b/base/third_party/dmg_fp/dtoa.cc +@@ -179,6 +179,12 @@ + * used for input more than STRTOD_DIGLIM digits long (default 40). + */ + ++#if defined _MSC_VER && _MSC_VER == 1800 ++// TODO(scottmg): VS2013 RC ICEs on a bunch of functions in this file. ++// This should be removed after RTM. See http://crbug.com/288948. ++#pragma optimize("", off) ++#endif ++ + #define IEEE_8087 + #define NO_HEX_FP + diff --git a/src/butil/third_party/dmg_fp/win_vs2012.patch b/src/butil/third_party/dmg_fp/win_vs2012.patch new file mode 100644 index 0000000..f3f599c --- /dev/null +++ b/src/butil/third_party/dmg_fp/win_vs2012.patch @@ -0,0 +1,13 @@ +Index: dtoa.cc +=================================================================== +--- dtoa.cc (revision 161424) ++++ dtoa.cc (working copy) +@@ -3569,7 +3569,7 @@ + int denorm; + ULong x; + #endif +- Bigint *b, *b1, *delta, *mlo, *mhi, *S; ++ Bigint *b, *b1, *delta, *mlo = NULL, *mhi, *S; + U d2, eps, u; + double ds; + char *s, *s0; diff --git a/src/butil/third_party/dynamic_annotations/README.chromium b/src/butil/third_party/dynamic_annotations/README.chromium new file mode 100644 index 0000000..dc8bdef --- /dev/null +++ b/src/butil/third_party/dynamic_annotations/README.chromium @@ -0,0 +1,13 @@ +Name: dynamic annotations +URL: http://code.google.com/p/data-race-test/wiki/DynamicAnnotations +Version: 4384 +License: BSD + +One header and one source file (dynamic_annotations.h and dynamic_annotations.c) +in this directory define runtime macros useful for annotating synchronization +utilities and benign data races so data race detectors can handle Chromium code +with better precision. + +These files were taken from +http://code.google.com/p/data-race-test/source/browse/?#svn/trunk/dynamic_annotations +The files are covered under BSD license as described within the files. diff --git a/src/butil/third_party/dynamic_annotations/dynamic_annotations.c b/src/butil/third_party/dynamic_annotations/dynamic_annotations.c new file mode 100644 index 0000000..b746203 --- /dev/null +++ b/src/butil/third_party/dynamic_annotations/dynamic_annotations.c @@ -0,0 +1,269 @@ +/* Copyright (c) 2011, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef _MSC_VER +# include +#endif + +#ifdef __cplusplus +# error "This file should be built as pure C to avoid name mangling" +#endif + +#include +#include + +#include "butil/third_party/dynamic_annotations/dynamic_annotations.h" + +#ifdef __GNUC__ +/* valgrind.h uses gcc extensions so it won't build with other compilers */ +# include "butil/third_party/valgrind/valgrind.h" +#endif + +/* Compiler-based ThreadSanitizer defines + DYNAMIC_ANNOTATIONS_EXTERNAL_IMPL = 1 + and provides its own definitions of the functions. */ + +#ifndef DYNAMIC_ANNOTATIONS_EXTERNAL_IMPL +# define DYNAMIC_ANNOTATIONS_EXTERNAL_IMPL 0 +#endif + +/* Each function is empty and called (via a macro) only in debug mode. + The arguments are captured by dynamic tools at runtime. */ + +#if DYNAMIC_ANNOTATIONS_ENABLED == 1 \ + && DYNAMIC_ANNOTATIONS_EXTERNAL_IMPL == 0 + +/* Identical code folding(-Wl,--icf=all) countermeasures. + This makes all Annotate* functions different, which prevents the linker from + folding them. */ +#ifdef __COUNTER__ +#define DYNAMIC_ANNOTATIONS_IMPL \ + volatile short lineno = (__LINE__ << 8) + __COUNTER__; (void)lineno; +#else +#define DYNAMIC_ANNOTATIONS_IMPL \ + volatile short lineno = (__LINE__ << 8); (void)lineno; +#endif + +/* WARNING: always add new annotations to the end of the list. + Otherwise, lineno (see above) numbers for different Annotate* functions may + conflict. */ +void DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockCreate)( + const char *file, int line, const volatile void *lock) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockDestroy)( + const char *file, int line, const volatile void *lock) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockAcquired)( + const char *file, int line, const volatile void *lock, long is_w) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockReleased)( + const char *file, int line, const volatile void *lock, long is_w) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierInit)( + const char *file, int line, const volatile void *barrier, long count, + long reinitialization_allowed) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierWaitBefore)( + const char *file, int line, const volatile void *barrier) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierWaitAfter)( + const char *file, int line, const volatile void *barrier) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierDestroy)( + const char *file, int line, const volatile void *barrier) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateCondVarWait)( + const char *file, int line, const volatile void *cv, + const volatile void *lock) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateCondVarSignal)( + const char *file, int line, const volatile void *cv) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateCondVarSignalAll)( + const char *file, int line, const volatile void *cv) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateHappensBefore)( + const char *file, int line, const volatile void *obj) +{DYNAMIC_ANNOTATIONS_IMPL}; + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateHappensAfter)( + const char *file, int line, const volatile void *obj) +{DYNAMIC_ANNOTATIONS_IMPL}; + +void DYNAMIC_ANNOTATIONS_NAME(AnnotatePublishMemoryRange)( + const char *file, int line, const volatile void *address, long size) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateUnpublishMemoryRange)( + const char *file, int line, const volatile void *address, long size) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQCreate)( + const char *file, int line, const volatile void *pcq) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQDestroy)( + const char *file, int line, const volatile void *pcq) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQPut)( + const char *file, int line, const volatile void *pcq) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQGet)( + const char *file, int line, const volatile void *pcq) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateNewMemory)( + const char *file, int line, const volatile void *mem, long size) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateExpectRace)( + const char *file, int line, const volatile void *mem, + const char *description) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateFlushExpectedRaces)( + const char *file, int line) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBenignRace)( + const char *file, int line, const volatile void *mem, + const char *description) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBenignRaceSized)( + const char *file, int line, const volatile void *mem, size_t size, + const char *description) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateMutexIsUsedAsCondVar)( + const char *file, int line, const volatile void *mu) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateMutexIsNotPHB)( + const char *file, int line, const volatile void *mu) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateTraceMemory)( + const char *file, int line, const volatile void *arg) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateThreadName)( + const char *file, int line, const char *name) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreReadsBegin)( + const char *file, int line) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreReadsEnd)( + const char *file, int line) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreWritesBegin)( + const char *file, int line) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreWritesEnd)( + const char *file, int line) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreSyncBegin)( + const char *file, int line) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreSyncEnd)( + const char *file, int line) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateEnableRaceDetection)( + const char *file, int line, int enable) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateNoOp)( + const char *file, int line, const volatile void *arg) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateFlushState)( + const char *file, int line) +{DYNAMIC_ANNOTATIONS_IMPL} + +#endif /* DYNAMIC_ANNOTATIONS_ENABLED == 1 + && DYNAMIC_ANNOTATIONS_EXTERNAL_IMPL == 0 */ + +#if DYNAMIC_ANNOTATIONS_PROVIDE_RUNNING_ON_VALGRIND == 1 \ + && DYNAMIC_ANNOTATIONS_EXTERNAL_IMPL == 0 +static int GetRunningOnValgrind(void) { +#ifdef RUNNING_ON_VALGRIND + if (RUNNING_ON_VALGRIND) return 1; +#endif + +#ifndef _MSC_VER + char *running_on_valgrind_str = getenv("RUNNING_ON_VALGRIND"); + if (running_on_valgrind_str) { + return strcmp(running_on_valgrind_str, "0") != 0; + } +#else + /* Visual Studio issues warnings if we use getenv, + * so we use GetEnvironmentVariableA instead. + */ + char value[100] = "1"; + int res = GetEnvironmentVariableA("RUNNING_ON_VALGRIND", + value, sizeof(value)); + /* value will remain "1" if res == 0 or res >= sizeof(value). The latter + * can happen only if the given value is long, in this case it can't be "0". + */ + if (res > 0 && strcmp(value, "0") != 0) + return 1; +#endif + return 0; +} + +/* See the comments in dynamic_annotations.h */ +int DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK RunningOnValgrind(void) { + static volatile int running_on_valgrind = -1; + /* C doesn't have thread-safe initialization of statics, and we + don't want to depend on pthread_once here, so hack it. */ + int local_running_on_valgrind = running_on_valgrind; + if (local_running_on_valgrind == -1) + running_on_valgrind = local_running_on_valgrind = GetRunningOnValgrind(); + return local_running_on_valgrind; +} + +#endif /* DYNAMIC_ANNOTATIONS_PROVIDE_RUNNING_ON_VALGRIND == 1 + && DYNAMIC_ANNOTATIONS_EXTERNAL_IMPL == 0 */ diff --git a/src/butil/third_party/dynamic_annotations/dynamic_annotations.h b/src/butil/third_party/dynamic_annotations/dynamic_annotations.h new file mode 100644 index 0000000..7b3ae95 --- /dev/null +++ b/src/butil/third_party/dynamic_annotations/dynamic_annotations.h @@ -0,0 +1,595 @@ +/* Copyright (c) 2011, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* This file defines dynamic annotations for use with dynamic analysis + tool such as valgrind, PIN, etc. + + Dynamic annotation is a source code annotation that affects + the generated code (that is, the annotation is not a comment). + Each such annotation is attached to a particular + instruction and/or to a particular object (address) in the program. + + The annotations that should be used by users are macros in all upper-case + (e.g., ANNOTATE_NEW_MEMORY). + + Actual implementation of these macros may differ depending on the + dynamic analysis tool being used. + + See http://code.google.com/p/data-race-test/ for more information. + + This file supports the following dynamic analysis tools: + - None (DYNAMIC_ANNOTATIONS_ENABLED is not defined or zero). + Macros are defined empty. + - ThreadSanitizer, Helgrind, DRD (DYNAMIC_ANNOTATIONS_ENABLED is 1). + Macros are defined as calls to non-inlinable empty functions + that are intercepted by Valgrind. */ + +#ifndef __DYNAMIC_ANNOTATIONS_H__ +#define __DYNAMIC_ANNOTATIONS_H__ + +#ifndef DYNAMIC_ANNOTATIONS_PREFIX +# define DYNAMIC_ANNOTATIONS_PREFIX +#endif + +#ifndef DYNAMIC_ANNOTATIONS_PROVIDE_RUNNING_ON_VALGRIND +# define DYNAMIC_ANNOTATIONS_PROVIDE_RUNNING_ON_VALGRIND 1 +#endif + +#ifdef DYNAMIC_ANNOTATIONS_WANT_ATTRIBUTE_WEAK +# ifdef __GNUC__ +# define DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK __attribute__((weak)) +# else +/* TODO(glider): for Windows support we may want to change this macro in order + to prepend __declspec(selectany) to the annotations' declarations. */ +# error weak annotations are not supported for your compiler +# endif +#else +# define DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK +#endif + +/* The following preprocessor magic prepends the value of + DYNAMIC_ANNOTATIONS_PREFIX to annotation function names. */ +#define DYNAMIC_ANNOTATIONS_GLUE0(A, B) A##B +#define DYNAMIC_ANNOTATIONS_GLUE(A, B) DYNAMIC_ANNOTATIONS_GLUE0(A, B) +#define DYNAMIC_ANNOTATIONS_NAME(name) \ + DYNAMIC_ANNOTATIONS_GLUE(DYNAMIC_ANNOTATIONS_PREFIX, name) + +#ifndef DYNAMIC_ANNOTATIONS_ENABLED +# define DYNAMIC_ANNOTATIONS_ENABLED 0 +#endif + +#if DYNAMIC_ANNOTATIONS_ENABLED != 0 + + /* ------------------------------------------------------------- + Annotations useful when implementing condition variables such as CondVar, + using conditional critical sections (Await/LockWhen) and when constructing + user-defined synchronization mechanisms. + + The annotations ANNOTATE_HAPPENS_BEFORE() and ANNOTATE_HAPPENS_AFTER() can + be used to define happens-before arcs in user-defined synchronization + mechanisms: the race detector will infer an arc from the former to the + latter when they share the same argument pointer. + + Example 1 (reference counting): + + void Unref() { + ANNOTATE_HAPPENS_BEFORE(&refcount_); + if (AtomicDecrementByOne(&refcount_) == 0) { + ANNOTATE_HAPPENS_AFTER(&refcount_); + delete this; + } + } + + Example 2 (message queue): + + void MyQueue::Put(Type *e) { + MutexLock lock(&mu_); + ANNOTATE_HAPPENS_BEFORE(e); + PutElementIntoMyQueue(e); + } + + Type *MyQueue::Get() { + MutexLock lock(&mu_); + Type *e = GetElementFromMyQueue(); + ANNOTATE_HAPPENS_AFTER(e); + return e; + } + + Note: when possible, please use the existing reference counting and message + queue implementations instead of inventing new ones. */ + + /* Report that wait on the condition variable at address "cv" has succeeded + and the lock at address "lock" is held. */ + #define ANNOTATE_CONDVAR_LOCK_WAIT(cv, lock) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateCondVarWait)(__FILE__, __LINE__, cv, lock) + + /* Report that wait on the condition variable at "cv" has succeeded. Variant + w/o lock. */ + #define ANNOTATE_CONDVAR_WAIT(cv) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateCondVarWait)(__FILE__, __LINE__, cv, NULL) + + /* Report that we are about to signal on the condition variable at address + "cv". */ + #define ANNOTATE_CONDVAR_SIGNAL(cv) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateCondVarSignal)(__FILE__, __LINE__, cv) + + /* Report that we are about to signal_all on the condition variable at address + "cv". */ + #define ANNOTATE_CONDVAR_SIGNAL_ALL(cv) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateCondVarSignalAll)(__FILE__, __LINE__, cv) + + /* Annotations for user-defined synchronization mechanisms. */ + #define ANNOTATE_HAPPENS_BEFORE(obj) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateHappensBefore)(__FILE__, __LINE__, obj) + #define ANNOTATE_HAPPENS_AFTER(obj) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateHappensAfter)(__FILE__, __LINE__, obj) + + /* DEPRECATED. Don't use it. */ + #define ANNOTATE_PUBLISH_MEMORY_RANGE(pointer, size) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotatePublishMemoryRange)(__FILE__, __LINE__, \ + pointer, size) + + /* DEPRECATED. Don't use it. */ + #define ANNOTATE_UNPUBLISH_MEMORY_RANGE(pointer, size) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateUnpublishMemoryRange)(__FILE__, __LINE__, \ + pointer, size) + + /* DEPRECATED. Don't use it. */ + #define ANNOTATE_SWAP_MEMORY_RANGE(pointer, size) \ + do { \ + ANNOTATE_UNPUBLISH_MEMORY_RANGE(pointer, size); \ + ANNOTATE_PUBLISH_MEMORY_RANGE(pointer, size); \ + } while (0) + + /* Instruct the tool to create a happens-before arc between mu->Unlock() and + mu->Lock(). This annotation may slow down the race detector and hide real + races. Normally it is used only when it would be difficult to annotate each + of the mutex's critical sections individually using the annotations above. + This annotation makes sense only for hybrid race detectors. For pure + happens-before detectors this is a no-op. For more details see + http://code.google.com/p/data-race-test/wiki/PureHappensBeforeVsHybrid . */ + #define ANNOTATE_PURE_HAPPENS_BEFORE_MUTEX(mu) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateMutexIsUsedAsCondVar)(__FILE__, __LINE__, \ + mu) + + /* Opposite to ANNOTATE_PURE_HAPPENS_BEFORE_MUTEX. + Instruct the tool to NOT create h-b arcs between Unlock and Lock, even in + pure happens-before mode. For a hybrid mode this is a no-op. */ + #define ANNOTATE_NOT_HAPPENS_BEFORE_MUTEX(mu) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateMutexIsNotPHB)(__FILE__, __LINE__, mu) + + /* Deprecated. Use ANNOTATE_PURE_HAPPENS_BEFORE_MUTEX. */ + #define ANNOTATE_MUTEX_IS_USED_AS_CONDVAR(mu) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateMutexIsUsedAsCondVar)(__FILE__, __LINE__, \ + mu) + + /* ------------------------------------------------------------- + Annotations useful when defining memory allocators, or when memory that + was protected in one way starts to be protected in another. */ + + /* Report that a new memory at "address" of size "size" has been allocated. + This might be used when the memory has been retrieved from a free list and + is about to be reused, or when a the locking discipline for a variable + changes. */ + #define ANNOTATE_NEW_MEMORY(address, size) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateNewMemory)(__FILE__, __LINE__, address, \ + size) + + /* ------------------------------------------------------------- + Annotations useful when defining FIFO queues that transfer data between + threads. */ + + /* Report that the producer-consumer queue (such as ProducerConsumerQueue) at + address "pcq" has been created. The ANNOTATE_PCQ_* annotations + should be used only for FIFO queues. For non-FIFO queues use + ANNOTATE_HAPPENS_BEFORE (for put) and ANNOTATE_HAPPENS_AFTER (for get). */ + #define ANNOTATE_PCQ_CREATE(pcq) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQCreate)(__FILE__, __LINE__, pcq) + + /* Report that the queue at address "pcq" is about to be destroyed. */ + #define ANNOTATE_PCQ_DESTROY(pcq) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQDestroy)(__FILE__, __LINE__, pcq) + + /* Report that we are about to put an element into a FIFO queue at address + "pcq". */ + #define ANNOTATE_PCQ_PUT(pcq) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQPut)(__FILE__, __LINE__, pcq) + + /* Report that we've just got an element from a FIFO queue at address + "pcq". */ + #define ANNOTATE_PCQ_GET(pcq) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQGet)(__FILE__, __LINE__, pcq) + + /* ------------------------------------------------------------- + Annotations that suppress errors. It is usually better to express the + program's synchronization using the other annotations, but these can + be used when all else fails. */ + + /* Report that we may have a benign race at "pointer", with size + "sizeof(*(pointer))". "pointer" must be a non-void* pointer. Insert at the + point where "pointer" has been allocated, preferably close to the point + where the race happens. See also ANNOTATE_BENIGN_RACE_STATIC. */ + #define ANNOTATE_BENIGN_RACE(pointer, description) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateBenignRaceSized)(__FILE__, __LINE__, \ + pointer, sizeof(*(pointer)), description) + + /* Same as ANNOTATE_BENIGN_RACE(address, description), but applies to + the memory range [address, address+size). */ + #define ANNOTATE_BENIGN_RACE_SIZED(address, size, description) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateBenignRaceSized)(__FILE__, __LINE__, \ + address, size, description) + + /* Request the analysis tool to ignore all reads in the current thread + until ANNOTATE_IGNORE_READS_END is called. + Useful to ignore intentional racey reads, while still checking + other reads and all writes. + See also ANNOTATE_UNPROTECTED_READ. */ + #define ANNOTATE_IGNORE_READS_BEGIN() \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreReadsBegin)(__FILE__, __LINE__) + + /* Stop ignoring reads. */ + #define ANNOTATE_IGNORE_READS_END() \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreReadsEnd)(__FILE__, __LINE__) + + /* Similar to ANNOTATE_IGNORE_READS_BEGIN, but ignore writes. */ + #define ANNOTATE_IGNORE_WRITES_BEGIN() \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreWritesBegin)(__FILE__, __LINE__) + + /* Stop ignoring writes. */ + #define ANNOTATE_IGNORE_WRITES_END() \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreWritesEnd)(__FILE__, __LINE__) + + /* Start ignoring all memory accesses (reads and writes). */ + #define ANNOTATE_IGNORE_READS_AND_WRITES_BEGIN() \ + do {\ + ANNOTATE_IGNORE_READS_BEGIN();\ + ANNOTATE_IGNORE_WRITES_BEGIN();\ + }while(0)\ + + /* Stop ignoring all memory accesses. */ + #define ANNOTATE_IGNORE_READS_AND_WRITES_END() \ + do {\ + ANNOTATE_IGNORE_WRITES_END();\ + ANNOTATE_IGNORE_READS_END();\ + }while(0)\ + + /* Similar to ANNOTATE_IGNORE_READS_BEGIN, but ignore synchronization events: + RWLOCK* and CONDVAR*. */ + #define ANNOTATE_IGNORE_SYNC_BEGIN() \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreSyncBegin)(__FILE__, __LINE__) + + /* Stop ignoring sync events. */ + #define ANNOTATE_IGNORE_SYNC_END() \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreSyncEnd)(__FILE__, __LINE__) + + + /* Enable (enable!=0) or disable (enable==0) race detection for all threads. + This annotation could be useful if you want to skip expensive race analysis + during some period of program execution, e.g. during initialization. */ + #define ANNOTATE_ENABLE_RACE_DETECTION(enable) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateEnableRaceDetection)(__FILE__, __LINE__, \ + enable) + + /* ------------------------------------------------------------- + Annotations useful for debugging. */ + + /* Request to trace every access to "address". */ + #define ANNOTATE_TRACE_MEMORY(address) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateTraceMemory)(__FILE__, __LINE__, address) + + /* Report the current thread name to a race detector. */ + #define ANNOTATE_THREAD_NAME(name) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateThreadName)(__FILE__, __LINE__, name) + + /* ------------------------------------------------------------- + Annotations useful when implementing locks. They are not + normally needed by modules that merely use locks. + The "lock" argument is a pointer to the lock object. */ + + /* Report that a lock has been created at address "lock". */ + #define ANNOTATE_RWLOCK_CREATE(lock) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockCreate)(__FILE__, __LINE__, lock) + + /* Report that the lock at address "lock" is about to be destroyed. */ + #define ANNOTATE_RWLOCK_DESTROY(lock) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockDestroy)(__FILE__, __LINE__, lock) + + /* Report that the lock at address "lock" has been acquired. + is_w=1 for writer lock, is_w=0 for reader lock. */ + #define ANNOTATE_RWLOCK_ACQUIRED(lock, is_w) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockAcquired)(__FILE__, __LINE__, lock, \ + is_w) + + /* Report that the lock at address "lock" is about to be released. */ + #define ANNOTATE_RWLOCK_RELEASED(lock, is_w) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockReleased)(__FILE__, __LINE__, lock, \ + is_w) + + /* ------------------------------------------------------------- + Annotations useful when implementing barriers. They are not + normally needed by modules that merely use barriers. + The "barrier" argument is a pointer to the barrier object. */ + + /* Report that the "barrier" has been initialized with initial "count". + If 'reinitialization_allowed' is true, initialization is allowed to happen + multiple times w/o calling barrier_destroy() */ + #define ANNOTATE_BARRIER_INIT(barrier, count, reinitialization_allowed) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierInit)(__FILE__, __LINE__, barrier, \ + count, reinitialization_allowed) + + /* Report that we are about to enter barrier_wait("barrier"). */ + #define ANNOTATE_BARRIER_WAIT_BEFORE(barrier) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierWaitBefore)(__FILE__, __LINE__, \ + barrier) + + /* Report that we just exited barrier_wait("barrier"). */ + #define ANNOTATE_BARRIER_WAIT_AFTER(barrier) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierWaitAfter)(__FILE__, __LINE__, \ + barrier) + + /* Report that the "barrier" has been destroyed. */ + #define ANNOTATE_BARRIER_DESTROY(barrier) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierDestroy)(__FILE__, __LINE__, \ + barrier) + + /* ------------------------------------------------------------- + Annotations useful for testing race detectors. */ + + /* Report that we expect a race on the variable at "address". + Use only in unit tests for a race detector. */ + #define ANNOTATE_EXPECT_RACE(address, description) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateExpectRace)(__FILE__, __LINE__, address, \ + description) + + #define ANNOTATE_FLUSH_EXPECTED_RACES() \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateFlushExpectedRaces)(__FILE__, __LINE__) + + /* A no-op. Insert where you like to test the interceptors. */ + #define ANNOTATE_NO_OP(arg) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateNoOp)(__FILE__, __LINE__, arg) + + /* Force the race detector to flush its state. The actual effect depends on + * the implementation of the detector. */ + #define ANNOTATE_FLUSH_STATE() \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateFlushState)(__FILE__, __LINE__) + + +#else /* DYNAMIC_ANNOTATIONS_ENABLED == 0 */ + + #define ANNOTATE_RWLOCK_CREATE(lock) /* empty */ + #define ANNOTATE_RWLOCK_DESTROY(lock) /* empty */ + #define ANNOTATE_RWLOCK_ACQUIRED(lock, is_w) /* empty */ + #define ANNOTATE_RWLOCK_RELEASED(lock, is_w) /* empty */ + #define ANNOTATE_BARRIER_INIT(barrier, count, reinitialization_allowed) /* */ + #define ANNOTATE_BARRIER_WAIT_BEFORE(barrier) /* empty */ + #define ANNOTATE_BARRIER_WAIT_AFTER(barrier) /* empty */ + #define ANNOTATE_BARRIER_DESTROY(barrier) /* empty */ + #define ANNOTATE_CONDVAR_LOCK_WAIT(cv, lock) /* empty */ + #define ANNOTATE_CONDVAR_WAIT(cv) /* empty */ + #define ANNOTATE_CONDVAR_SIGNAL(cv) /* empty */ + #define ANNOTATE_CONDVAR_SIGNAL_ALL(cv) /* empty */ + #define ANNOTATE_HAPPENS_BEFORE(obj) /* empty */ + #define ANNOTATE_HAPPENS_AFTER(obj) /* empty */ + #define ANNOTATE_PUBLISH_MEMORY_RANGE(address, size) /* empty */ + #define ANNOTATE_UNPUBLISH_MEMORY_RANGE(address, size) /* empty */ + #define ANNOTATE_SWAP_MEMORY_RANGE(address, size) /* empty */ + #define ANNOTATE_PCQ_CREATE(pcq) /* empty */ + #define ANNOTATE_PCQ_DESTROY(pcq) /* empty */ + #define ANNOTATE_PCQ_PUT(pcq) /* empty */ + #define ANNOTATE_PCQ_GET(pcq) /* empty */ + #define ANNOTATE_NEW_MEMORY(address, size) /* empty */ + #define ANNOTATE_EXPECT_RACE(address, description) /* empty */ + #define ANNOTATE_FLUSH_EXPECTED_RACES(address, description) /* empty */ + #define ANNOTATE_BENIGN_RACE(address, description) /* empty */ + #define ANNOTATE_BENIGN_RACE_SIZED(address, size, description) /* empty */ + #define ANNOTATE_PURE_HAPPENS_BEFORE_MUTEX(mu) /* empty */ + #define ANNOTATE_MUTEX_IS_USED_AS_CONDVAR(mu) /* empty */ + #define ANNOTATE_TRACE_MEMORY(arg) /* empty */ + #define ANNOTATE_THREAD_NAME(name) /* empty */ + #define ANNOTATE_IGNORE_READS_BEGIN() /* empty */ + #define ANNOTATE_IGNORE_READS_END() /* empty */ + #define ANNOTATE_IGNORE_WRITES_BEGIN() /* empty */ + #define ANNOTATE_IGNORE_WRITES_END() /* empty */ + #define ANNOTATE_IGNORE_READS_AND_WRITES_BEGIN() /* empty */ + #define ANNOTATE_IGNORE_READS_AND_WRITES_END() /* empty */ + #define ANNOTATE_IGNORE_SYNC_BEGIN() /* empty */ + #define ANNOTATE_IGNORE_SYNC_END() /* empty */ + #define ANNOTATE_ENABLE_RACE_DETECTION(enable) /* empty */ + #define ANNOTATE_NO_OP(arg) /* empty */ + #define ANNOTATE_FLUSH_STATE() /* empty */ + +#endif /* DYNAMIC_ANNOTATIONS_ENABLED */ + +/* Use the macros above rather than using these functions directly. */ +#ifdef __cplusplus +extern "C" { +#endif + + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockCreate)( + const char *file, int line, + const volatile void *lock) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockDestroy)( + const char *file, int line, + const volatile void *lock) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockAcquired)( + const char *file, int line, + const volatile void *lock, long is_w) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockReleased)( + const char *file, int line, + const volatile void *lock, long is_w) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierInit)( + const char *file, int line, const volatile void *barrier, long count, + long reinitialization_allowed) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierWaitBefore)( + const char *file, int line, + const volatile void *barrier) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierWaitAfter)( + const char *file, int line, + const volatile void *barrier) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierDestroy)( + const char *file, int line, + const volatile void *barrier) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateCondVarWait)( + const char *file, int line, const volatile void *cv, + const volatile void *lock) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateCondVarSignal)( + const char *file, int line, + const volatile void *cv) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateCondVarSignalAll)( + const char *file, int line, + const volatile void *cv) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateHappensBefore)( + const char *file, int line, + const volatile void *obj) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateHappensAfter)( + const char *file, int line, + const volatile void *obj) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotatePublishMemoryRange)( + const char *file, int line, + const volatile void *address, long size) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateUnpublishMemoryRange)( + const char *file, int line, + const volatile void *address, long size) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQCreate)( + const char *file, int line, + const volatile void *pcq) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQDestroy)( + const char *file, int line, + const volatile void *pcq) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQPut)( + const char *file, int line, + const volatile void *pcq) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQGet)( + const char *file, int line, + const volatile void *pcq) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateNewMemory)( + const char *file, int line, + const volatile void *mem, long size) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateExpectRace)( + const char *file, int line, const volatile void *mem, + const char *description) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateFlushExpectedRaces)( + const char *file, int line) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBenignRace)( + const char *file, int line, const volatile void *mem, + const char *description) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBenignRaceSized)( + const char *file, int line, const volatile void *mem, size_t size, + const char *description) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateMutexIsUsedAsCondVar)( + const char *file, int line, + const volatile void *mu) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateMutexIsNotPHB)( + const char *file, int line, + const volatile void *mu) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateTraceMemory)( + const char *file, int line, + const volatile void *arg) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateThreadName)( + const char *file, int line, + const char *name) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreReadsBegin)( + const char *file, int line) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreReadsEnd)( + const char *file, int line) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreWritesBegin)( + const char *file, int line) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreWritesEnd)( + const char *file, int line) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreSyncBegin)( + const char *file, int line) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreSyncEnd)( + const char *file, int line) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateEnableRaceDetection)( + const char *file, int line, int enable) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateNoOp)( + const char *file, int line, + const volatile void *arg) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateFlushState)( + const char *file, int line) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; + +#if DYNAMIC_ANNOTATIONS_PROVIDE_RUNNING_ON_VALGRIND == 1 +/* Return non-zero value if running under valgrind. + + If "valgrind.h" is included into dynamic_annotations.c, + the regular valgrind mechanism will be used. + See http://valgrind.org/docs/manual/manual-core-adv.html about + RUNNING_ON_VALGRIND and other valgrind "client requests". + The file "valgrind.h" may be obtained by doing + svn co svn://svn.valgrind.org/valgrind/trunk/include + + If for some reason you can't use "valgrind.h" or want to fake valgrind, + there are two ways to make this function return non-zero: + - Use environment variable: export RUNNING_ON_VALGRIND=1 + - Make your tool intercept the function RunningOnValgrind() and + change its return value. + */ +int RunningOnValgrind(void) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +#endif /* DYNAMIC_ANNOTATIONS_PROVIDE_RUNNING_ON_VALGRIND == 1 */ + +#ifdef __cplusplus +} +#endif + +#if DYNAMIC_ANNOTATIONS_ENABLED != 0 && defined(__cplusplus) + + /* ANNOTATE_UNPROTECTED_READ is the preferred way to annotate racey reads. + + Instead of doing + ANNOTATE_IGNORE_READS_BEGIN(); + ... = x; + ANNOTATE_IGNORE_READS_END(); + one can use + ... = ANNOTATE_UNPROTECTED_READ(x); */ + template + inline T ANNOTATE_UNPROTECTED_READ(const volatile T &x) { + ANNOTATE_IGNORE_READS_BEGIN(); + T res = x; + ANNOTATE_IGNORE_READS_END(); + return res; + } + /* Apply ANNOTATE_BENIGN_RACE_SIZED to a static variable. */ + #define ANNOTATE_BENIGN_RACE_STATIC(static_var, description) \ + namespace { \ + class static_var ## _annotator { \ + public: \ + static_var ## _annotator() { \ + ANNOTATE_BENIGN_RACE_SIZED(&static_var, \ + sizeof(static_var), \ + # static_var ": " description); \ + } \ + }; \ + static static_var ## _annotator the ## static_var ## _annotator;\ + } +#else /* DYNAMIC_ANNOTATIONS_ENABLED == 0 */ + + #define ANNOTATE_UNPROTECTED_READ(x) (x) + #define ANNOTATE_BENIGN_RACE_STATIC(static_var, description) /* empty */ + +#endif /* DYNAMIC_ANNOTATIONS_ENABLED */ + +#endif /* __DYNAMIC_ANNOTATIONS_H__ */ diff --git a/src/butil/third_party/icu/README.chromium b/src/butil/third_party/icu/README.chromium new file mode 100644 index 0000000..6a9a15a --- /dev/null +++ b/src/butil/third_party/icu/README.chromium @@ -0,0 +1,16 @@ +Name: ICU +URL: http://site.icu-project.org/ +License: MIT +License File: NOT_SHIPPED + +This file has the relevant components from ICU copied to handle basic +UTF8/16/32 conversions. Components are copied from utf.h utf8.h utf16.h and +utf_impl.c + +The same module appears in third_party/icu, so we don't repeat the license +file here. + +The main change is that U_/U8_/U16_ prefixes have been replaced with +CBU_/CBU8_/CBU16_ (for "Chrome Base") to avoid confusion with the "real" ICU +macros should ICU be in use on the system. For the same reason, the functions +and types have been put in the "base_icu" namespace. diff --git a/src/butil/third_party/icu/icu_utf.cc b/src/butil/third_party/icu/icu_utf.cc new file mode 100644 index 0000000..ee1d6cc --- /dev/null +++ b/src/butil/third_party/icu/icu_utf.cc @@ -0,0 +1,230 @@ +/* +****************************************************************************** +* +* Copyright (C) 1999-2006, International Business Machines +* Corporation and others. All Rights Reserved. +* +****************************************************************************** +* file name: utf_impl.c +* encoding: US-ASCII +* tab size: 8 (not used) +* indentation:4 +* +* created on: 1999sep13 +* created by: Markus W. Scherer +* +* This file provides implementation functions for macros in the utfXX.h +* that would otherwise be too long as macros. +*/ + +#include "butil/third_party/icu/icu_utf.h" + +namespace base_icu { + +/** + * UTF8_ERROR_VALUE_1 and UTF8_ERROR_VALUE_2 are special error values for UTF-8, + * which need 1 or 2 bytes in UTF-8: + * \code + * U+0015 = NAK = Negative Acknowledge, C0 control character + * U+009f = highest C1 control character + * \endcode + * + * These are used by UTF8_..._SAFE macros so that they can return an error value + * that needs the same number of code units (bytes) as were seen by + * a macro. They should be tested with UTF_IS_ERROR() or UTF_IS_VALID(). + * + * @deprecated ICU 2.4. Obsolete, see utf_old.h. + */ +#define CBUTF8_ERROR_VALUE_1 0x15 + +/** + * See documentation on UTF8_ERROR_VALUE_1 for details. + * + * @deprecated ICU 2.4. Obsolete, see utf_old.h. + */ +#define CBUTF8_ERROR_VALUE_2 0x9f + + +/** + * Error value for all UTFs. This code point value will be set by macros with e> + * checking if an error is detected. + * + * @deprecated ICU 2.4. Obsolete, see utf_old.h. + */ +#define CBUTF_ERROR_VALUE 0xffff + +/* + * This table could be replaced on many machines by + * a few lines of assembler code using an + * "index of first 0-bit from msb" instruction and + * one or two more integer instructions. + * + * For example, on an i386, do something like + * - MOV AL, leadByte + * - NOT AL (8-bit, leave b15..b8==0..0, reverse only b7..b0) + * - MOV AH, 0 + * - BSR BX, AX (16-bit) + * - MOV AX, 6 (result) + * - JZ finish (ZF==1 if leadByte==0xff) + * - SUB AX, BX (result) + * -finish: + * (BSR: Bit Scan Reverse, scans for a 1-bit, starting from the MSB) + * + * In Unicode, all UTF-8 byte sequences with more than 4 bytes are illegal; + * lead bytes above 0xf4 are illegal. + * We keep them in this table for skipping long ISO 10646-UTF-8 sequences. + */ +const uint8_t +utf8_countTrailBytes[256]={ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, + 3, 3, 3, /* illegal in Unicode */ + 4, 4, 4, 4, /* illegal in Unicode */ + 5, 5, /* illegal in Unicode */ + 0, 0 /* illegal bytes 0xfe and 0xff */ +}; + +static const UChar32 +utf8_minLegal[4]={ 0, 0x80, 0x800, 0x10000 }; + +static const UChar32 +utf8_errorValue[6]={ + CBUTF8_ERROR_VALUE_1, CBUTF8_ERROR_VALUE_2, CBUTF_ERROR_VALUE, 0x10ffff, + 0x3ffffff, 0x7fffffff +}; + +/* + * Handle the non-inline part of the U8_NEXT() macro and its obsolete sibling + * UTF8_NEXT_CHAR_SAFE(). + * + * The "strict" parameter controls the error behavior: + * <0 "Safe" behavior of U8_NEXT(): All illegal byte sequences yield a negative + * code point result. + * 0 Obsolete "safe" behavior of UTF8_NEXT_CHAR_SAFE(..., FALSE): + * All illegal byte sequences yield a positive code point such that this + * result code point would be encoded with the same number of bytes as + * the illegal sequence. + * >0 Obsolete "strict" behavior of UTF8_NEXT_CHAR_SAFE(..., TRUE): + * Same as the obsolete "safe" behavior, but non-characters are also treated + * like illegal sequences. + * + * The special negative (<0) value -2 is used for lenient treatment of surrogate + * code points as legal. Some implementations use this for roundtripping of + * Unicode 16-bit strings that are not well-formed UTF-16, that is, they + * contain unpaired surrogates. + * + * Note that a UBool is the same as an int8_t. + */ +UChar32 +utf8_nextCharSafeBody(const uint8_t *s, int32_t *pi, int32_t length, UChar32 c, UBool strict) { + int32_t i=*pi; + uint8_t count=CBU8_COUNT_TRAIL_BYTES(c); + if((i)+count<=(length)) { + uint8_t trail, illegal=0; + + CBU8_MASK_LEAD_BYTE((c), count); + /* count==0 for illegally leading trail bytes and the illegal bytes 0xfe and 0xff */ + switch(count) { + /* each branch falls through to the next one */ + case 5: + case 4: + /* count>=4 is always illegal: no more than 3 trail bytes in Unicode's UTF-8 */ + illegal=1; + break; + case 3: + trail=s[(i)++]; + (c)=((c)<<6)|(trail&0x3f); + if(c<0x110) { + illegal|=(trail&0xc0)^0x80; + } else { + /* code point>0x10ffff, outside Unicode */ + illegal=1; + break; + } + // fall through + case 2: + trail=s[(i)++]; + (c)=((c)<<6)|(trail&0x3f); + illegal|=(trail&0xc0)^0x80; + // fall through + case 1: + trail=s[(i)++]; + (c)=((c)<<6)|(trail&0x3f); + illegal|=(trail&0xc0)^0x80; + break; + case 0: + if(strict>=0) { + return CBUTF8_ERROR_VALUE_1; + } else { + return (UChar32)CBU_SENTINEL; + } + /* no default branch to optimize switch() - all values are covered */ + } + + /* + * All the error handling should return a value + * that needs count bytes so that UTF8_GET_CHAR_SAFE() works right. + * + * Starting with Unicode 3.0.1, non-shortest forms are illegal. + * Starting with Unicode 3.2, surrogate code points must not be + * encoded in UTF-8, and there are no irregular sequences any more. + * + * U8_ macros (new in ICU 2.4) return negative values for error conditions. + */ + + /* correct sequence - all trail bytes have (b7..b6)==(10)? */ + /* illegal is also set if count>=4 */ + if(illegal || (c)0 && CBU8_IS_TRAIL(s[i])) { + ++(i); + --count; + } + if(strict>=0) { + c=utf8_errorValue[errorCount-count]; + } else { + c=(UChar32)CBU_SENTINEL; + } + } else if((strict)>0 && CBU_IS_UNICODE_NONCHAR(c)) { + /* strict: forbid non-characters like U+fffe */ + c=utf8_errorValue[count]; + } + } else /* too few bytes left */ { + /* error handling */ + int32_t i0=i; + /* don't just set (i)=(length) in case there is an illegal sequence */ + while((i)<(length) && CBU8_IS_TRAIL(s[i])) { + ++(i); + } + if(strict>=0) { + c=utf8_errorValue[i-i0]; + } else { + c=(UChar32)CBU_SENTINEL; + } + } + *pi=i; + return c; +} + +} // namespace base_icu diff --git a/src/butil/third_party/icu/icu_utf.h b/src/butil/third_party/icu/icu_utf.h new file mode 100644 index 0000000..45b7049 --- /dev/null +++ b/src/butil/third_party/icu/icu_utf.h @@ -0,0 +1,391 @@ +/* +******************************************************************************* +* +* Copyright (C) 1999-2004, International Business Machines +* Corporation and others. All Rights Reserved. +* +******************************************************************************* +* file name: utf.h +* encoding: US-ASCII +* tab size: 8 (not used) +* indentation:4 +* +* created on: 1999sep09 +* created by: Markus W. Scherer +*/ + +#ifndef BUTIL_THIRD_PARTY_ICU_ICU_UTF_H_ +#define BUTIL_THIRD_PARTY_ICU_ICU_UTF_H_ + +#include "butil/basictypes.h" + +namespace base_icu { + +typedef uint32_t UChar32; +typedef uint16_t UChar; +typedef int8_t UBool; + +// General --------------------------------------------------------------------- +// from utf.h + +/** + * This value is intended for sentinel values for APIs that + * (take or) return single code points (UChar32). + * It is outside of the Unicode code point range 0..0x10ffff. + * + * For example, a "done" or "error" value in a new API + * could be indicated with CBU_SENTINEL. + * + * ICU APIs designed before ICU 2.4 usually define service-specific "done" + * values, mostly 0xffff. + * Those may need to be distinguished from + * actual U+ffff text contents by calling functions like + * CharacterIterator::hasNext() or UnicodeString::length(). + * + * @return -1 + * @see UChar32 + * @stable ICU 2.4 + */ +#define CBU_SENTINEL (-1) + +/** + * Is this code point a Unicode noncharacter? + * @param c 32-bit code point + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU_IS_UNICODE_NONCHAR(c) \ + ((c)>=0xfdd0 && \ + ((uint32_t)(c)<=0xfdef || ((c)&0xfffe)==0xfffe) && \ + (uint32_t)(c)<=0x10ffff) + +/** + * Is c a Unicode code point value (0..U+10ffff) + * that can be assigned a character? + * + * Code points that are not characters include: + * - single surrogate code points (U+d800..U+dfff, 2048 code points) + * - the last two code points on each plane (U+__fffe and U+__ffff, 34 code points) + * - U+fdd0..U+fdef (new with Unicode 3.1, 32 code points) + * - the highest Unicode code point value is U+10ffff + * + * This means that all code points below U+d800 are character code points, + * and that boundary is tested first for performance. + * + * @param c 32-bit code point + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU_IS_UNICODE_CHAR(c) \ + ((uint32_t)(c)<0xd800 || \ + ((uint32_t)(c)>0xdfff && \ + (uint32_t)(c)<=0x10ffff && \ + !CBU_IS_UNICODE_NONCHAR(c))) + +/** + * Is this code point a surrogate (U+d800..U+dfff)? + * @param c 32-bit code point + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU_IS_SURROGATE(c) (((c)&0xfffff800)==0xd800) + +/** + * Assuming c is a surrogate code point (U_IS_SURROGATE(c)), + * is it a lead surrogate? + * @param c 32-bit code point + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU_IS_SURROGATE_LEAD(c) (((c)&0x400)==0) + + +// UTF-8 macros ---------------------------------------------------------------- +// from utf8.h + +extern const uint8_t utf8_countTrailBytes[256]; + +/** + * Count the trail bytes for a UTF-8 lead byte. + * @internal + */ +#define CBU8_COUNT_TRAIL_BYTES(leadByte) (base_icu::utf8_countTrailBytes[(uint8_t)leadByte]) + +/** + * Mask a UTF-8 lead byte, leave only the lower bits that form part of the code point value. + * @internal + */ +#define CBU8_MASK_LEAD_BYTE(leadByte, countTrailBytes) ((leadByte)&=(1<<(6-(countTrailBytes)))-1) + +/** + * Does this code unit (byte) encode a code point by itself (US-ASCII 0..0x7f)? + * @param c 8-bit code unit (byte) + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU8_IS_SINGLE(c) (((c)&0x80)==0) + +/** + * Is this code unit (byte) a UTF-8 lead byte? + * @param c 8-bit code unit (byte) + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU8_IS_LEAD(c) ((uint8_t)((c)-0xc0)<0x3e) + +/** + * Is this code unit (byte) a UTF-8 trail byte? + * @param c 8-bit code unit (byte) + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU8_IS_TRAIL(c) (((c)&0xc0)==0x80) + +/** + * How many code units (bytes) are used for the UTF-8 encoding + * of this Unicode code point? + * @param c 32-bit code point + * @return 1..4, or 0 if c is a surrogate or not a Unicode code point + * @stable ICU 2.4 + */ +#define CBU8_LENGTH(c) \ + ((uint32_t)(c)<=0x7f ? 1 : \ + ((uint32_t)(c)<=0x7ff ? 2 : \ + ((uint32_t)(c)<=0xd7ff ? 3 : \ + ((uint32_t)(c)<=0xdfff || (uint32_t)(c)>0x10ffff ? 0 : \ + ((uint32_t)(c)<=0xffff ? 3 : 4)\ + ) \ + ) \ + ) \ + ) + +/** + * The maximum number of UTF-8 code units (bytes) per Unicode code point (U+0000..U+10ffff). + * @return 4 + * @stable ICU 2.4 + */ +#define CBU8_MAX_LENGTH 4 + +/** + * Function for handling "next code point" with error-checking. + * @internal + */ +UChar32 utf8_nextCharSafeBody(const uint8_t *s, int32_t *pi, int32_t length, UChar32 c, UBool strict); + +/** + * Get a code point from a string at a code point boundary offset, + * and advance the offset to the next code point boundary. + * (Post-incrementing forward iteration.) + * "Safe" macro, checks for illegal sequences and for string boundaries. + * + * The offset may point to the lead byte of a multi-byte sequence, + * in which case the macro will read the whole sequence. + * If the offset points to a trail byte or an illegal UTF-8 sequence, then + * c is set to a negative value. + * + * @param s const uint8_t * string + * @param i string offset, i=0x80) { \ + if(CBU8_IS_LEAD(c)) { \ + (c)=base_icu::utf8_nextCharSafeBody((const uint8_t *)s, &(i), (int32_t)(length), c, -1); \ + } else { \ + (c)=(base_icu::UChar32)CBU_SENTINEL; \ + } \ + } \ +} + +/** + * Append a code point to a string, overwriting 1 to 4 bytes. + * The offset points to the current end of the string contents + * and is advanced (post-increment). + * "Unsafe" macro, assumes a valid code point and sufficient space in the string. + * Otherwise, the result is undefined. + * + * @param s const uint8_t * string buffer + * @param i string offset + * @param c code point to append + * @see CBU8_APPEND + * @stable ICU 2.4 + */ +#define CBU8_APPEND_UNSAFE(s, i, c) { \ + if((uint32_t)(c)<=0x7f) { \ + (s)[(i)++]=(uint8_t)(c); \ + } else { \ + if((uint32_t)(c)<=0x7ff) { \ + (s)[(i)++]=(uint8_t)(((c)>>6)|0xc0); \ + } else { \ + if((uint32_t)(c)<=0xffff) { \ + (s)[(i)++]=(uint8_t)(((c)>>12)|0xe0); \ + } else { \ + (s)[(i)++]=(uint8_t)(((c)>>18)|0xf0); \ + (s)[(i)++]=(uint8_t)((((c)>>12)&0x3f)|0x80); \ + } \ + (s)[(i)++]=(uint8_t)((((c)>>6)&0x3f)|0x80); \ + } \ + (s)[(i)++]=(uint8_t)(((c)&0x3f)|0x80); \ + } \ +} + +// UTF-16 macros --------------------------------------------------------------- +// from utf16.h + +/** + * Does this code unit alone encode a code point (BMP, not a surrogate)? + * @param c 16-bit code unit + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU16_IS_SINGLE(c) !CBU_IS_SURROGATE(c) + +/** + * Is this code unit a lead surrogate (U+d800..U+dbff)? + * @param c 16-bit code unit + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU16_IS_LEAD(c) (((c)&0xfffffc00)==0xd800) + +/** + * Is this code unit a trail surrogate (U+dc00..U+dfff)? + * @param c 16-bit code unit + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU16_IS_TRAIL(c) (((c)&0xfffffc00)==0xdc00) + +/** + * Is this code unit a surrogate (U+d800..U+dfff)? + * @param c 16-bit code unit + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU16_IS_SURROGATE(c) CBU_IS_SURROGATE(c) + +/** + * Assuming c is a surrogate code point (U16_IS_SURROGATE(c)), + * is it a lead surrogate? + * @param c 16-bit code unit + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU16_IS_SURROGATE_LEAD(c) (((c)&0x400)==0) + +/** + * Helper constant for CBU16_GET_SUPPLEMENTARY. + * @internal + */ +#define CBU16_SURROGATE_OFFSET ((0xd800<<10UL)+0xdc00-0x10000) + +/** + * Get a supplementary code point value (U+10000..U+10ffff) + * from its lead and trail surrogates. + * The result is undefined if the input values are not + * lead and trail surrogates. + * + * @param lead lead surrogate (U+d800..U+dbff) + * @param trail trail surrogate (U+dc00..U+dfff) + * @return supplementary code point (U+10000..U+10ffff) + * @stable ICU 2.4 + */ +#define CBU16_GET_SUPPLEMENTARY(lead, trail) \ + (((base_icu::UChar32)(lead)<<10UL)+(base_icu::UChar32)(trail)-CBU16_SURROGATE_OFFSET) + + +/** + * Get the lead surrogate (0xd800..0xdbff) for a + * supplementary code point (0x10000..0x10ffff). + * @param supplementary 32-bit code point (U+10000..U+10ffff) + * @return lead surrogate (U+d800..U+dbff) for supplementary + * @stable ICU 2.4 + */ +#define CBU16_LEAD(supplementary) \ + (base_icu::UChar)(((supplementary)>>10)+0xd7c0) + +/** + * Get the trail surrogate (0xdc00..0xdfff) for a + * supplementary code point (0x10000..0x10ffff). + * @param supplementary 32-bit code point (U+10000..U+10ffff) + * @return trail surrogate (U+dc00..U+dfff) for supplementary + * @stable ICU 2.4 + */ +#define CBU16_TRAIL(supplementary) \ + (base_icu::UChar)(((supplementary)&0x3ff)|0xdc00) + +/** + * How many 16-bit code units are used to encode this Unicode code point? (1 or 2) + * The result is not defined if c is not a Unicode code point (U+0000..U+10ffff). + * @param c 32-bit code point + * @return 1 or 2 + * @stable ICU 2.4 + */ +#define CBU16_LENGTH(c) ((uint32_t)(c)<=0xffff ? 1 : 2) + +/** + * The maximum number of 16-bit code units per Unicode code point (U+0000..U+10ffff). + * @return 2 + * @stable ICU 2.4 + */ +#define CBU16_MAX_LENGTH 2 + +/** + * Get a code point from a string at a code point boundary offset, + * and advance the offset to the next code point boundary. + * (Post-incrementing forward iteration.) + * "Safe" macro, handles unpaired surrogates and checks for string boundaries. + * + * The offset may point to the lead surrogate unit + * for a supplementary code point, in which case the macro will read + * the following trail surrogate as well. + * If the offset points to a trail surrogate or + * to a single, unpaired lead surrogate, then that itself + * will be returned as the code point. + * + * @param s const UChar * string + * @param i string offset, i>10)+0xd7c0); \ + (s)[(i)++]=(uint16_t)(((c)&0x3ff)|0xdc00); \ + } \ +} + +} // namesapce base_icu + +#endif // BUTIL_THIRD_PARTY_ICU_ICU_UTF_H_ diff --git a/src/butil/third_party/modp_b64/README.chromium b/src/butil/third_party/modp_b64/README.chromium new file mode 100644 index 0000000..35b9e54 --- /dev/null +++ b/src/butil/third_party/modp_b64/README.chromium @@ -0,0 +1,15 @@ +Name: modp base64 decoder +Short Name: stringencoders +URL: http://code.google.com/p/stringencoders/ +Version: unknown +License: BSD +Security Critical: yes + +Description: +The modp_b64.c file was modified to remove the inclusion of modp's config.h +and to fix compilation errors that occur under VC8. The file was renamed +modp_b64.cc to force it to be compiled as C++ so that the inclusion of +basictypes.h could be possible. + +The modp_b64.cc and modp_b64.h files were modified to make them safe on +64-bit systems. diff --git a/src/butil/third_party/modp_b64/modp_b64.cc b/src/butil/third_party/modp_b64/modp_b64.cc new file mode 100644 index 0000000..e5f6cf1 --- /dev/null +++ b/src/butil/third_party/modp_b64/modp_b64.cc @@ -0,0 +1,265 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */ +/* vi: set expandtab shiftwidth=4 tabstop=4: */ +/** + * \file + *
+ * MODP_B64 - High performance base64 encoder/decoder
+ * Version 1.3 -- 17-Mar-2006
+ * http://modp.com/release/base64
+ *
+ * Copyright © 2005, 2006  Nick Galbreath -- nickg [at] modp [dot] com
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ *   Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ *   Neither the name of the modp.com nor the names of its
+ *   contributors may be used to endorse or promote products derived from
+ *   this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This is the standard "new" BSD license:
+ * http://www.opensource.org/licenses/bsd-license.php
+ * 
+ */ + +/* public header */ +#include "modp_b64.h" + +/* + * If you are ripping this out of the library, comment out the next + * line and uncomment the next lines as approrpiate + */ +//#include "config.h" + +/* if on motoral, sun, ibm; uncomment this */ +/* #define WORDS_BIGENDIAN 1 */ +/* else for Intel, Amd; uncomment this */ +/* #undef WORDS_BIGENDIAN */ + +#include "modp_b64_data.h" + +#define BADCHAR 0x01FFFFFF + +/** + * you can control if we use padding by commenting out this + * next line. However, I highly recommend you use padding and not + * using it should only be for compatability with a 3rd party. + * Also, 'no padding' is not tested! + */ +#define DOPAD 1 + +/* + * if we aren't doing padding + * set the pad character to NULL + */ +#ifndef DOPAD +#undef CHARPAD +#define CHARPAD '\0' +#endif + +size_t modp_b64_encode(char* dest, const char* str, size_t len) +{ + size_t i = 0; + uint8_t* p = (uint8_t*) dest; + + /* unsigned here is important! */ + uint8_t t1, t2, t3; + + if (len > 2) { + for (; i < len - 2; i += 3) { + t1 = str[i]; t2 = str[i+1]; t3 = str[i+2]; + *p++ = e0[t1]; + *p++ = e1[((t1 & 0x03) << 4) | ((t2 >> 4) & 0x0F)]; + *p++ = e1[((t2 & 0x0F) << 2) | ((t3 >> 6) & 0x03)]; + *p++ = e2[t3]; + } + } + + switch (len - i) { + case 0: + break; + case 1: + t1 = str[i]; + *p++ = e0[t1]; + *p++ = e1[(t1 & 0x03) << 4]; + *p++ = CHARPAD; + *p++ = CHARPAD; + break; + default: /* case 2 */ + t1 = str[i]; t2 = str[i+1]; + *p++ = e0[t1]; + *p++ = e1[((t1 & 0x03) << 4) | ((t2 >> 4) & 0x0F)]; + *p++ = e2[(t2 & 0x0F) << 2]; + *p++ = CHARPAD; + } + + *p = '\0'; + return p - (uint8_t*)dest; +} + +#ifdef WORDS_BIGENDIAN /* BIG ENDIAN -- SUN / IBM / MOTOROLA */ +int modp_b64_decode(char* dest, const char* src, int len) +{ + if (len == 0) return 0; + +#ifdef DOPAD + /* if padding is used, then the message must be at least + 4 chars and be a multiple of 4. + there can be at most 2 pad chars at the end */ + if (len < 4 || (len % 4 != 0)) return MODP_B64_ERROR; + if (src[len-1] == CHARPAD) { + len--; + if (src[len -1] == CHARPAD) { + len--; + } + } +#endif /* DOPAD */ + + size_t i; + int leftover = len % 4; + size_t chunks = (leftover == 0) ? len / 4 - 1 : len /4; + + uint8_t* p = (uint8_t*) dest; + uint32_t x = 0; + uint32_t* destInt = (uint32_t*) p; + uint32_t* srcInt = (uint32_t*) src; + uint32_t y = *srcInt++; + for (i = 0; i < chunks; ++i) { + x = d0[y >> 24 & 0xff] | d1[y >> 16 & 0xff] | + d2[y >> 8 & 0xff] | d3[y & 0xff]; + + if (x >= BADCHAR) return MODP_B64_ERROR; + *destInt = x << 8; + p += 3; + destInt = (uint32_t*)p; + y = *srcInt++; + } + + switch (leftover) { + case 0: + x = d0[y >> 24 & 0xff] | d1[y >> 16 & 0xff] | + d2[y >> 8 & 0xff] | d3[y & 0xff]; + if (x >= BADCHAR) return MODP_B64_ERROR; + *p++ = ((uint8_t*)&x)[1]; + *p++ = ((uint8_t*)&x)[2]; + *p = ((uint8_t*)&x)[3]; + return (chunks+1)*3; + case 1: + x = d3[y >> 24]; + *p = (uint8_t)x; + break; + case 2: + x = d3[y >> 24] *64 + d3[(y >> 16) & 0xff]; + *p = (uint8_t)(x >> 4); + break; + default: /* case 3 */ + x = (d3[y >> 24] *64 + d3[(y >> 16) & 0xff])*64 + + d3[(y >> 8) & 0xff]; + *p++ = (uint8_t) (x >> 10); + *p = (uint8_t) (x >> 2); + break; + } + + if (x >= BADCHAR) return MODP_B64_ERROR; + return 3*chunks + (6*leftover)/8; +} + +#else /* LITTLE ENDIAN -- INTEL AND FRIENDS */ + +size_t modp_b64_decode(char* dest, const char* src, size_t len) +{ + if (len == 0) return 0; + +#ifdef DOPAD + /* + * if padding is used, then the message must be at least + * 4 chars and be a multiple of 4 + */ + if (len < 4 || (len % 4 != 0)) return MODP_B64_ERROR; /* error */ + /* there can be at most 2 pad chars at the end */ + if (src[len-1] == CHARPAD) { + len--; + if (src[len -1] == CHARPAD) { + len--; + } + } +#endif + + size_t i; + int leftover = len % 4; + size_t chunks = (leftover == 0) ? len / 4 - 1 : len /4; + + uint8_t* p = (uint8_t*)dest; + uint32_t x = 0; + uint32_t* destInt = (uint32_t*) p; + uint32_t* srcInt = (uint32_t*) src; + uint32_t y = *srcInt++; + for (i = 0; i < chunks; ++i) { + x = d0[y & 0xff] | + d1[(y >> 8) & 0xff] | + d2[(y >> 16) & 0xff] | + d3[(y >> 24) & 0xff]; + + if (x >= BADCHAR) return MODP_B64_ERROR; + *destInt = x ; + p += 3; + destInt = (uint32_t*)p; + y = *srcInt++;} + + + switch (leftover) { + case 0: + x = d0[y & 0xff] | + d1[(y >> 8) & 0xff] | + d2[(y >> 16) & 0xff] | + d3[(y >> 24) & 0xff]; + + if (x >= BADCHAR) return MODP_B64_ERROR; + *p++ = ((uint8_t*)(&x))[0]; + *p++ = ((uint8_t*)(&x))[1]; + *p = ((uint8_t*)(&x))[2]; + return (chunks+1)*3; + break; + case 1: /* with padding this is an impossible case */ + x = d0[y & 0xff]; + *p = *((uint8_t*)(&x)); // i.e. first char/byte in int + break; + case 2: // * case 2, 1 output byte */ + x = d0[y & 0xff] | d1[y >> 8 & 0xff]; + *p = *((uint8_t*)(&x)); // i.e. first char + break; + default: /* case 3, 2 output bytes */ + x = d0[y & 0xff] | + d1[y >> 8 & 0xff ] | + d2[y >> 16 & 0xff]; /* 0x3c */ + *p++ = ((uint8_t*)(&x))[0]; + *p = ((uint8_t*)(&x))[1]; + break; + } + + if (x >= BADCHAR) return MODP_B64_ERROR; + + return 3*chunks + (6*leftover)/8; +} + +#endif /* if bigendian / else / endif */ diff --git a/src/butil/third_party/modp_b64/modp_b64.h b/src/butil/third_party/modp_b64/modp_b64.h new file mode 100644 index 0000000..3270e5f --- /dev/null +++ b/src/butil/third_party/modp_b64/modp_b64.h @@ -0,0 +1,171 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */ +/* vi: set expandtab shiftwidth=4 tabstop=4: */ + +/** + * \file + *
+ * High performance base64 encoder / decoder
+ * Version 1.3 -- 17-Mar-2006
+ *
+ * Copyright © 2005, 2006, Nick Galbreath -- nickg [at] modp [dot] com
+ * All rights reserved.
+ *
+ * http://modp.com/release/base64
+ *
+ * Released under bsd license.  See modp_b64.c for details.
+ * 
+ * + * The default implementation is the standard b64 encoding with padding. + * It's easy to change this to use "URL safe" characters and to remove + * padding. See the modp_b64.c source code for details. + * + */ + +#ifndef MODP_B64 +#define MODP_B64 + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Encode a raw binary string into base 64. + * src contains the bytes + * len contains the number of bytes in the src + * dest should be allocated by the caller to contain + * at least modp_b64_encode_len(len) bytes (see below) + * This will contain the null-terminated b64 encoded result + * returns length of the destination string plus the ending null byte + * i.e. the result will be equal to strlen(dest) + 1 + * + * Example + * + * \code + * char* src = ...; + * int srclen = ...; //the length of number of bytes in src + * char* dest = (char*) malloc(modp_b64_encode_len); + * int len = modp_b64_encode(dest, src, sourcelen); + * if (len == -1) { + * printf("Error\n"); + * } else { + * printf("b64 = %s\n", dest); + * } + * \endcode + * + */ +size_t modp_b64_encode(char* dest, const char* str, size_t len); + +/** + * Decode a base64 encoded string + * + * src should contain exactly len bytes of b64 characters. + * if src contains -any- non-base characters (such as white + * space, -1 is returned. + * + * dest should be allocated by the caller to contain at least + * len * 3 / 4 bytes. + * + * Returns the length (strlen) of the output, or -1 if unable to + * decode + * + * \code + * char* src = ...; + * int srclen = ...; // or if you don't know use strlen(src) + * char* dest = (char*) malloc(modp_b64_decode_len(srclen)); + * int len = modp_b64_decode(dest, src, sourcelen); + * if (len == -1) { error } + * \endcode + */ +size_t modp_b64_decode(char* dest, const char* src, size_t len); + +/** + * Given a source string of length len, this returns the amount of + * memory the destination string should have. + * + * remember, this is integer math + * 3 bytes turn into 4 chars + * ceiling[len / 3] * 4 + 1 + * + * +1 is for any extra null. + */ +#define modp_b64_encode_len(A) ((A+2)/3 * 4 + 1) + +/** + * Given a base64 string of length len, + * this returns the amount of memory required for output string + * It maybe be more than the actual number of bytes written. + * NOTE: remember this is integer math + * this allocates a bit more memory than traditional versions of b64 + * decode 4 chars turn into 3 bytes + * floor[len * 3/4] + 2 + */ +#define modp_b64_decode_len(A) (A / 4 * 3 + 2) + +/** + * Will return the strlen of the output from encoding. + * This may be less than the required number of bytes allocated. + * + * This allows you to 'deserialized' a struct + * \code + * char* b64encoded = "..."; + * int len = strlen(b64encoded); + * + * struct datastuff foo; + * if (modp_b64_encode_strlen(sizeof(struct datastuff)) != len) { + * // wrong size + * return false; + * } else { + * // safe to do; + * if (modp_b64_decode((char*) &foo, b64encoded, len) == -1) { + * // bad characters + * return false; + * } + * } + * // foo is filled out now + * \endcode + */ +#define modp_b64_encode_strlen(A) ((A + 2)/ 3 * 4) + +#define MODP_B64_ERROR ((size_t)-1) + +#ifdef __cplusplus +} + +#include + +inline std::string& modp_b64_encode(std::string& s) +{ + std::string x(modp_b64_encode_len(s.size()), '\0'); + size_t d = modp_b64_encode(const_cast(x.data()), s.data(), (int)s.size()); + x.erase(d, std::string::npos); + s.swap(x); + return s; +} + +/** + * base 64 decode a string (self-modifing) + * On failure, the string is empty. + * + * This function is for C++ only (duh) + * + * \param[in,out] s the string to be decoded + * \return a reference to the input string + */ +inline std::string& modp_b64_decode(std::string& s) +{ + std::string x(modp_b64_decode_len(s.size()), '\0'); + size_t d = modp_b64_decode(const_cast(x.data()), s.data(), (int)s.size()); + if (d == MODP_B64_ERROR) { + x.clear(); + } else { + x.erase(d, std::string::npos); + } + s.swap(x); + return s; +} + +#endif /* __cplusplus */ + +#endif /* MODP_B64 */ diff --git a/src/butil/third_party/modp_b64/modp_b64_data.h b/src/butil/third_party/modp_b64/modp_b64_data.h new file mode 100644 index 0000000..c38d451 --- /dev/null +++ b/src/butil/third_party/modp_b64/modp_b64_data.h @@ -0,0 +1,482 @@ +#include "butil/build_config.h" +#include + +#define CHAR62 '+' +#define CHAR63 '/' +#define CHARPAD '=' +static const char e0[256] = { + 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'C', 'C', + 'C', 'C', 'D', 'D', 'D', 'D', 'E', 'E', 'E', 'E', + 'F', 'F', 'F', 'F', 'G', 'G', 'G', 'G', 'H', 'H', + 'H', 'H', 'I', 'I', 'I', 'I', 'J', 'J', 'J', 'J', + 'K', 'K', 'K', 'K', 'L', 'L', 'L', 'L', 'M', 'M', + 'M', 'M', 'N', 'N', 'N', 'N', 'O', 'O', 'O', 'O', + 'P', 'P', 'P', 'P', 'Q', 'Q', 'Q', 'Q', 'R', 'R', + 'R', 'R', 'S', 'S', 'S', 'S', 'T', 'T', 'T', 'T', + 'U', 'U', 'U', 'U', 'V', 'V', 'V', 'V', 'W', 'W', + 'W', 'W', 'X', 'X', 'X', 'X', 'Y', 'Y', 'Y', 'Y', + 'Z', 'Z', 'Z', 'Z', 'a', 'a', 'a', 'a', 'b', 'b', + 'b', 'b', 'c', 'c', 'c', 'c', 'd', 'd', 'd', 'd', + 'e', 'e', 'e', 'e', 'f', 'f', 'f', 'f', 'g', 'g', + 'g', 'g', 'h', 'h', 'h', 'h', 'i', 'i', 'i', 'i', + 'j', 'j', 'j', 'j', 'k', 'k', 'k', 'k', 'l', 'l', + 'l', 'l', 'm', 'm', 'm', 'm', 'n', 'n', 'n', 'n', + 'o', 'o', 'o', 'o', 'p', 'p', 'p', 'p', 'q', 'q', + 'q', 'q', 'r', 'r', 'r', 'r', 's', 's', 's', 's', + 't', 't', 't', 't', 'u', 'u', 'u', 'u', 'v', 'v', + 'v', 'v', 'w', 'w', 'w', 'w', 'x', 'x', 'x', 'x', + 'y', 'y', 'y', 'y', 'z', 'z', 'z', 'z', '0', '0', + '0', '0', '1', '1', '1', '1', '2', '2', '2', '2', + '3', '3', '3', '3', '4', '4', '4', '4', '5', '5', + '5', '5', '6', '6', '6', '6', '7', '7', '7', '7', + '8', '8', '8', '8', '9', '9', '9', '9', '+', '+', + '+', '+', '/', '/', '/', '/' +}; + +static const char e1[256] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', 'A', 'B', + 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '+', '/' +}; + +static const char e2[256] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', 'A', 'B', + 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '+', '/' +}; + + + +#ifdef WORDS_BIGENDIAN + + +/* SPECIAL DECODE TABLES FOR BIG ENDIAN (IBM/MOTOROLA/SUN) CPUS */ + +static const uint32_t d0[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00f80000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00fc0000, +0x00d00000, 0x00d40000, 0x00d80000, 0x00dc0000, 0x00e00000, 0x00e40000, +0x00e80000, 0x00ec0000, 0x00f00000, 0x00f40000, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00040000, 0x00080000, 0x000c0000, 0x00100000, 0x00140000, 0x00180000, +0x001c0000, 0x00200000, 0x00240000, 0x00280000, 0x002c0000, 0x00300000, +0x00340000, 0x00380000, 0x003c0000, 0x00400000, 0x00440000, 0x00480000, +0x004c0000, 0x00500000, 0x00540000, 0x00580000, 0x005c0000, 0x00600000, +0x00640000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00680000, 0x006c0000, 0x00700000, 0x00740000, 0x00780000, +0x007c0000, 0x00800000, 0x00840000, 0x00880000, 0x008c0000, 0x00900000, +0x00940000, 0x00980000, 0x009c0000, 0x00a00000, 0x00a40000, 0x00a80000, +0x00ac0000, 0x00b00000, 0x00b40000, 0x00b80000, 0x00bc0000, 0x00c00000, +0x00c40000, 0x00c80000, 0x00cc0000, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d1[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0003e000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x0003f000, +0x00034000, 0x00035000, 0x00036000, 0x00037000, 0x00038000, 0x00039000, +0x0003a000, 0x0003b000, 0x0003c000, 0x0003d000, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00001000, 0x00002000, 0x00003000, 0x00004000, 0x00005000, 0x00006000, +0x00007000, 0x00008000, 0x00009000, 0x0000a000, 0x0000b000, 0x0000c000, +0x0000d000, 0x0000e000, 0x0000f000, 0x00010000, 0x00011000, 0x00012000, +0x00013000, 0x00014000, 0x00015000, 0x00016000, 0x00017000, 0x00018000, +0x00019000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0001a000, 0x0001b000, 0x0001c000, 0x0001d000, 0x0001e000, +0x0001f000, 0x00020000, 0x00021000, 0x00022000, 0x00023000, 0x00024000, +0x00025000, 0x00026000, 0x00027000, 0x00028000, 0x00029000, 0x0002a000, +0x0002b000, 0x0002c000, 0x0002d000, 0x0002e000, 0x0002f000, 0x00030000, +0x00031000, 0x00032000, 0x00033000, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d2[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00000f80, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000fc0, +0x00000d00, 0x00000d40, 0x00000d80, 0x00000dc0, 0x00000e00, 0x00000e40, +0x00000e80, 0x00000ec0, 0x00000f00, 0x00000f40, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00000040, 0x00000080, 0x000000c0, 0x00000100, 0x00000140, 0x00000180, +0x000001c0, 0x00000200, 0x00000240, 0x00000280, 0x000002c0, 0x00000300, +0x00000340, 0x00000380, 0x000003c0, 0x00000400, 0x00000440, 0x00000480, +0x000004c0, 0x00000500, 0x00000540, 0x00000580, 0x000005c0, 0x00000600, +0x00000640, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00000680, 0x000006c0, 0x00000700, 0x00000740, 0x00000780, +0x000007c0, 0x00000800, 0x00000840, 0x00000880, 0x000008c0, 0x00000900, +0x00000940, 0x00000980, 0x000009c0, 0x00000a00, 0x00000a40, 0x00000a80, +0x00000ac0, 0x00000b00, 0x00000b40, 0x00000b80, 0x00000bc0, 0x00000c00, +0x00000c40, 0x00000c80, 0x00000cc0, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d3[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0000003e, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x0000003f, +0x00000034, 0x00000035, 0x00000036, 0x00000037, 0x00000038, 0x00000039, +0x0000003a, 0x0000003b, 0x0000003c, 0x0000003d, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00000001, 0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000006, +0x00000007, 0x00000008, 0x00000009, 0x0000000a, 0x0000000b, 0x0000000c, +0x0000000d, 0x0000000e, 0x0000000f, 0x00000010, 0x00000011, 0x00000012, +0x00000013, 0x00000014, 0x00000015, 0x00000016, 0x00000017, 0x00000018, +0x00000019, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0000001a, 0x0000001b, 0x0000001c, 0x0000001d, 0x0000001e, +0x0000001f, 0x00000020, 0x00000021, 0x00000022, 0x00000023, 0x00000024, +0x00000025, 0x00000026, 0x00000027, 0x00000028, 0x00000029, 0x0000002a, +0x0000002b, 0x0000002c, 0x0000002d, 0x0000002e, 0x0000002f, 0x00000030, +0x00000031, 0x00000032, 0x00000033, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +#else + + +/* SPECIAL DECODE TABLES FOR LITTLE ENDIAN (INTEL) CPUS */ + +static const uint32_t d0[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x000000f8, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x000000fc, +0x000000d0, 0x000000d4, 0x000000d8, 0x000000dc, 0x000000e0, 0x000000e4, +0x000000e8, 0x000000ec, 0x000000f0, 0x000000f4, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00000004, 0x00000008, 0x0000000c, 0x00000010, 0x00000014, 0x00000018, +0x0000001c, 0x00000020, 0x00000024, 0x00000028, 0x0000002c, 0x00000030, +0x00000034, 0x00000038, 0x0000003c, 0x00000040, 0x00000044, 0x00000048, +0x0000004c, 0x00000050, 0x00000054, 0x00000058, 0x0000005c, 0x00000060, +0x00000064, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00000068, 0x0000006c, 0x00000070, 0x00000074, 0x00000078, +0x0000007c, 0x00000080, 0x00000084, 0x00000088, 0x0000008c, 0x00000090, +0x00000094, 0x00000098, 0x0000009c, 0x000000a0, 0x000000a4, 0x000000a8, +0x000000ac, 0x000000b0, 0x000000b4, 0x000000b8, 0x000000bc, 0x000000c0, +0x000000c4, 0x000000c8, 0x000000cc, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d1[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0000e003, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x0000f003, +0x00004003, 0x00005003, 0x00006003, 0x00007003, 0x00008003, 0x00009003, +0x0000a003, 0x0000b003, 0x0000c003, 0x0000d003, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00001000, 0x00002000, 0x00003000, 0x00004000, 0x00005000, 0x00006000, +0x00007000, 0x00008000, 0x00009000, 0x0000a000, 0x0000b000, 0x0000c000, +0x0000d000, 0x0000e000, 0x0000f000, 0x00000001, 0x00001001, 0x00002001, +0x00003001, 0x00004001, 0x00005001, 0x00006001, 0x00007001, 0x00008001, +0x00009001, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x0000a001, 0x0000b001, 0x0000c001, 0x0000d001, 0x0000e001, +0x0000f001, 0x00000002, 0x00001002, 0x00002002, 0x00003002, 0x00004002, +0x00005002, 0x00006002, 0x00007002, 0x00008002, 0x00009002, 0x0000a002, +0x0000b002, 0x0000c002, 0x0000d002, 0x0000e002, 0x0000f002, 0x00000003, +0x00001003, 0x00002003, 0x00003003, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d2[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00800f00, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00c00f00, +0x00000d00, 0x00400d00, 0x00800d00, 0x00c00d00, 0x00000e00, 0x00400e00, +0x00800e00, 0x00c00e00, 0x00000f00, 0x00400f00, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00400000, 0x00800000, 0x00c00000, 0x00000100, 0x00400100, 0x00800100, +0x00c00100, 0x00000200, 0x00400200, 0x00800200, 0x00c00200, 0x00000300, +0x00400300, 0x00800300, 0x00c00300, 0x00000400, 0x00400400, 0x00800400, +0x00c00400, 0x00000500, 0x00400500, 0x00800500, 0x00c00500, 0x00000600, +0x00400600, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x00800600, 0x00c00600, 0x00000700, 0x00400700, 0x00800700, +0x00c00700, 0x00000800, 0x00400800, 0x00800800, 0x00c00800, 0x00000900, +0x00400900, 0x00800900, 0x00c00900, 0x00000a00, 0x00400a00, 0x00800a00, +0x00c00a00, 0x00000b00, 0x00400b00, 0x00800b00, 0x00c00b00, 0x00000c00, +0x00400c00, 0x00800c00, 0x00c00c00, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +static const uint32_t d3[256] = { +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x003e0000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x003f0000, +0x00340000, 0x00350000, 0x00360000, 0x00370000, 0x00380000, 0x00390000, +0x003a0000, 0x003b0000, 0x003c0000, 0x003d0000, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, +0x00010000, 0x00020000, 0x00030000, 0x00040000, 0x00050000, 0x00060000, +0x00070000, 0x00080000, 0x00090000, 0x000a0000, 0x000b0000, 0x000c0000, +0x000d0000, 0x000e0000, 0x000f0000, 0x00100000, 0x00110000, 0x00120000, +0x00130000, 0x00140000, 0x00150000, 0x00160000, 0x00170000, 0x00180000, +0x00190000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x001a0000, 0x001b0000, 0x001c0000, 0x001d0000, 0x001e0000, +0x001f0000, 0x00200000, 0x00210000, 0x00220000, 0x00230000, 0x00240000, +0x00250000, 0x00260000, 0x00270000, 0x00280000, 0x00290000, 0x002a0000, +0x002b0000, 0x002c0000, 0x002d0000, 0x002e0000, 0x002f0000, 0x00300000, +0x00310000, 0x00320000, 0x00330000, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff +}; + + +#endif diff --git a/src/butil/third_party/murmurhash3/murmurhash3.cpp b/src/butil/third_party/murmurhash3/murmurhash3.cpp new file mode 100644 index 0000000..b41c09d --- /dev/null +++ b/src/butil/third_party/murmurhash3/murmurhash3.cpp @@ -0,0 +1,714 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// MurmurHash3 was written by Austin Appleby, and is placed in the public +// domain. The author hereby disclaims copyright to this source code. + + +// Note - The x86 and x64 versions do _not_ produce the same results, as the +// algorithms are optimized for their respective platforms. You can still +// compile and run any of them on any platform, but your performance with the +// non-native version will be less than optimal. + +#include // size_t +#include // memcpy +#include // std::min +#include "butil/third_party/murmurhash3/murmurhash3.h" + +// Too many fallthroughs in this file to mark, just ignore the warning. +#if defined(__GNUC__) && __GNUC__ >= 7 +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#endif + +//----------------------------------------------------------------------------- +// Platform-specific functions and macros + +// Microsoft Visual Studio + +#if defined(_MSC_VER) + +#include + +#define ROTL32(x,y) _rotl(x,y) +#define ROTL64(x,y) _rotl64(x,y) + +// Other compilers + +#else // defined(_MSC_VER) + +inline uint32_t rotl32 ( uint32_t x, int8_t r ) +{ + return (x << r) | (x >> (32 - r)); +} + +inline uint64_t rotl64 ( uint64_t x, int8_t r ) +{ + return (x << r) | (x >> (64 - r)); +} + +#define ROTL32(x,y) rotl32(x,y) +#define ROTL64(x,y) rotl64(x,y) + +#endif // !defined(_MSC_VER) + +namespace butil { +//----------------------------------------------------------------------------- +// Block read - if your platform needs to do endian-swapping or can only +// handle aligned reads, do the conversion here + +MURMURHASH_FORCE_INLINE uint32_t getblock32 ( const uint32_t * p, int i ) { + return p[i]; +} + +MURMURHASH_FORCE_INLINE uint64_t getblock64 ( const uint64_t * p, int i ) { + return p[i]; +} + +//----------------------------------------------------------------------------- + +void MurmurHash3_x86_32 ( const void * key, int len, + uint32_t seed, void * out ) { + const uint8_t * data = (const uint8_t*)key; + const int nblocks = len / 4; + + uint32_t h1 = seed; + + const uint32_t c1 = 0xcc9e2d51; + const uint32_t c2 = 0x1b873593; + + //---------- + // body + + const uint32_t * blocks = (const uint32_t *)(data + nblocks*4); + + for(int i = -nblocks; i; i++) + { + uint32_t k1 = getblock32(blocks,i); + + k1 *= c1; + k1 = ROTL32(k1,15); + k1 *= c2; + + h1 ^= k1; + h1 = ROTL32(h1,13); + h1 = h1*5+0xe6546b64; + } + + //---------- + // tail + + const uint8_t * tail = (const uint8_t*)(data + nblocks*4); + + uint32_t k1 = 0; + + switch(len & 3) + { + case 3: k1 ^= tail[2] << 16; + case 2: k1 ^= tail[1] << 8; + case 1: k1 ^= tail[0]; + k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; + }; + + //---------- + // finalization + + h1 ^= len; + + h1 = fmix32(h1); + + *(uint32_t*)out = h1; +} + +//----------------------------------------------------------------------------- + +void MurmurHash3_x86_128 ( const void * key, const int len, + uint32_t seed, void * out ) { + const uint8_t * data = (const uint8_t*)key; + const int nblocks = len / 16; + + uint32_t h1 = seed; + uint32_t h2 = seed; + uint32_t h3 = seed; + uint32_t h4 = seed; + + const uint32_t c1 = 0x239b961b; + const uint32_t c2 = 0xab0e9789; + const uint32_t c3 = 0x38b34ae5; + const uint32_t c4 = 0xa1e38b93; + + //---------- + // body + + const uint32_t * blocks = (const uint32_t *)(data + nblocks*16); + + for(int i = -nblocks; i; i++) + { + uint32_t k1 = getblock32(blocks,i*4+0); + uint32_t k2 = getblock32(blocks,i*4+1); + uint32_t k3 = getblock32(blocks,i*4+2); + uint32_t k4 = getblock32(blocks,i*4+3); + + k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; + + h1 = ROTL32(h1,19); h1 += h2; h1 = h1*5+0x561ccd1b; + + k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2; + + h2 = ROTL32(h2,17); h2 += h3; h2 = h2*5+0x0bcaa747; + + k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3; + + h3 = ROTL32(h3,15); h3 += h4; h3 = h3*5+0x96cd1c35; + + k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4; + + h4 = ROTL32(h4,13); h4 += h1; h4 = h4*5+0x32ac3b17; + } + + //---------- + // tail + + const uint8_t * tail = (const uint8_t*)(data + nblocks*16); + + uint32_t k1 = 0; + uint32_t k2 = 0; + uint32_t k3 = 0; + uint32_t k4 = 0; + + switch(len & 15) + { + case 15: k4 ^= tail[14] << 16; + case 14: k4 ^= tail[13] << 8; + case 13: k4 ^= tail[12] << 0; + k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4; + + case 12: k3 ^= tail[11] << 24; + case 11: k3 ^= tail[10] << 16; + case 10: k3 ^= tail[ 9] << 8; + case 9: k3 ^= tail[ 8] << 0; + k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3; + + case 8: k2 ^= tail[ 7] << 24; + case 7: k2 ^= tail[ 6] << 16; + case 6: k2 ^= tail[ 5] << 8; + case 5: k2 ^= tail[ 4] << 0; + k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2; + + case 4: k1 ^= tail[ 3] << 24; + case 3: k1 ^= tail[ 2] << 16; + case 2: k1 ^= tail[ 1] << 8; + case 1: k1 ^= tail[ 0] << 0; + k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; + }; + + //---------- + // finalization + + h1 ^= len; h2 ^= len; h3 ^= len; h4 ^= len; + + h1 += h2; h1 += h3; h1 += h4; + h2 += h1; h3 += h1; h4 += h1; + + h1 = fmix32(h1); + h2 = fmix32(h2); + h3 = fmix32(h3); + h4 = fmix32(h4); + + h1 += h2; h1 += h3; h1 += h4; + h2 += h1; h3 += h1; h4 += h1; + + ((uint32_t*)out)[0] = h1; + ((uint32_t*)out)[1] = h2; + ((uint32_t*)out)[2] = h3; + ((uint32_t*)out)[3] = h4; +} + +//----------------------------------------------------------------------------- + +void MurmurHash3_x64_128 ( const void * key, const int len, + const uint32_t seed, void * out ) { + const uint8_t * data = (const uint8_t*)key; + const int nblocks = len / 16; + + uint64_t h1 = seed; + uint64_t h2 = seed; + + const uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5); + const uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f); + + //---------- + // body + + const uint64_t * blocks = (const uint64_t *)(data); + + for(int i = 0; i < nblocks; i++) + { + uint64_t k1 = getblock64(blocks,i*2+0); + uint64_t k2 = getblock64(blocks,i*2+1); + + k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1; + + h1 = ROTL64(h1,27); h1 += h2; h1 = h1*5+0x52dce729; + + k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2; + + h2 = ROTL64(h2,31); h2 += h1; h2 = h2*5+0x38495ab5; + } + + //---------- + // tail + + const uint8_t * tail = (const uint8_t*)(data + nblocks*16); + + uint64_t k1 = 0; + uint64_t k2 = 0; + + switch(len & 15) + { + case 15: k2 ^= ((uint64_t)tail[14]) << 48; + case 14: k2 ^= ((uint64_t)tail[13]) << 40; + case 13: k2 ^= ((uint64_t)tail[12]) << 32; + case 12: k2 ^= ((uint64_t)tail[11]) << 24; + case 11: k2 ^= ((uint64_t)tail[10]) << 16; + case 10: k2 ^= ((uint64_t)tail[ 9]) << 8; + case 9: k2 ^= ((uint64_t)tail[ 8]) << 0; + k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2; + + case 8: k1 ^= ((uint64_t)tail[ 7]) << 56; + case 7: k1 ^= ((uint64_t)tail[ 6]) << 48; + case 6: k1 ^= ((uint64_t)tail[ 5]) << 40; + case 5: k1 ^= ((uint64_t)tail[ 4]) << 32; + case 4: k1 ^= ((uint64_t)tail[ 3]) << 24; + case 3: k1 ^= ((uint64_t)tail[ 2]) << 16; + case 2: k1 ^= ((uint64_t)tail[ 1]) << 8; + case 1: k1 ^= ((uint64_t)tail[ 0]) << 0; + k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1; + }; + + //---------- + // finalization + + h1 ^= len; h2 ^= len; + + h1 += h2; + h2 += h1; + + h1 = fmix64(h1); + h2 = fmix64(h2); + + h1 += h2; + h2 += h1; + + ((uint64_t*)out)[0] = h1; + ((uint64_t*)out)[1] = h2; +} + +// ============= iterative versions ================== + +void MurmurHash3_x86_128_Init(MurmurHash3_x86_128_Context* ctx, uint32_t seed) +{ + ctx->h1 = seed; + ctx->h2 = seed; + ctx->h3 = seed; + ctx->h4 = seed; + ctx->total_len = 0; + ctx->tail_len = 0; +} + +void MurmurHash3_x86_128_Update( + MurmurHash3_x86_128_Context* ctx, const void * key, int len) +{ + const uint8_t * data = (const uint8_t*)key; + + uint32_t h1 = ctx->h1; + uint32_t h2 = ctx->h2; + uint32_t h3 = ctx->h3; + uint32_t h4 = ctx->h4; + + const uint32_t c1 = 0x239b961b; + const uint32_t c2 = 0xab0e9789; + const uint32_t c3 = 0x38b34ae5; + const uint32_t c4 = 0xa1e38b93; + + if (ctx->tail_len > 0) { + const int append = std::min(len, 16 - ctx->tail_len); + memcpy(ctx->tail + ctx->tail_len, data, append); + ctx->total_len += append; + ctx->tail_len += append; + data += append; + len -= append; + if (ctx->tail_len == 16) { + uint32_t k1 = getblock32((uint32_t*)ctx->tail, 0); + uint32_t k2 = getblock32((uint32_t*)ctx->tail, 1); + uint32_t k3 = getblock32((uint32_t*)ctx->tail, 2); + uint32_t k4 = getblock32((uint32_t*)ctx->tail, 3); + + k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; + + h1 = ROTL32(h1,19); h1 += h2; h1 = h1*5+0x561ccd1b; + + k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2; + + h2 = ROTL32(h2,17); h2 += h3; h2 = h2*5+0x0bcaa747; + + k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3; + + h3 = ROTL32(h3,15); h3 += h4; h3 = h3*5+0x96cd1c35; + + k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4; + + h4 = ROTL32(h4,13); h4 += h1; h4 = h4*5+0x32ac3b17; + + ctx->tail_len = 0; + } + } + + const int nblocks = len / 16; + + //---------- + // body + + const uint32_t * blocks = (const uint32_t *)(data + nblocks*16); + + for (int i = -nblocks; i; i++) + { + uint32_t k1 = getblock32(blocks,i*4+0); + uint32_t k2 = getblock32(blocks,i*4+1); + uint32_t k3 = getblock32(blocks,i*4+2); + uint32_t k4 = getblock32(blocks,i*4+3); + + k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; + + h1 = ROTL32(h1,19); h1 += h2; h1 = h1*5+0x561ccd1b; + + k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2; + + h2 = ROTL32(h2,17); h2 += h3; h2 = h2*5+0x0bcaa747; + + k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3; + + h3 = ROTL32(h3,15); h3 += h4; h3 = h3*5+0x96cd1c35; + + k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4; + + h4 = ROTL32(h4,13); h4 += h1; h4 = h4*5+0x32ac3b17; + } + + //---------- + // tail + + const int tail_len = len & 15; + if (tail_len > 0) { + memcpy(ctx->tail, data + nblocks * 16, tail_len); + ctx->tail_len = tail_len; + } + ctx->h1 = h1; + ctx->h2 = h2; + ctx->h3 = h3; + ctx->h4 = h4; + ctx->total_len += len; +} + +void MurmurHash3_x86_128_Final(void * out, const MurmurHash3_x86_128_Context* ctx) +{ + uint32_t h1 = ctx->h1; + uint32_t h2 = ctx->h2; + uint32_t h3 = ctx->h3; + uint32_t h4 = ctx->h4; + const int len = ctx->total_len; + + const uint8_t * tail = ctx->tail; + + const uint32_t c1 = 0x239b961b; + const uint32_t c2 = 0xab0e9789; + const uint32_t c3 = 0x38b34ae5; + const uint32_t c4 = 0xa1e38b93; + + uint32_t k1 = 0; + uint32_t k2 = 0; + uint32_t k3 = 0; + uint32_t k4 = 0; + + switch (ctx->tail_len) + { + case 15: k4 ^= tail[14] << 16; + case 14: k4 ^= tail[13] << 8; + case 13: k4 ^= tail[12] << 0; + k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4; + + case 12: k3 ^= tail[11] << 24; + case 11: k3 ^= tail[10] << 16; + case 10: k3 ^= tail[ 9] << 8; + case 9: k3 ^= tail[ 8] << 0; + k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3; + + case 8: k2 ^= tail[ 7] << 24; + case 7: k2 ^= tail[ 6] << 16; + case 6: k2 ^= tail[ 5] << 8; + case 5: k2 ^= tail[ 4] << 0; + k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2; + + case 4: k1 ^= tail[ 3] << 24; + case 3: k1 ^= tail[ 2] << 16; + case 2: k1 ^= tail[ 1] << 8; + case 1: k1 ^= tail[ 0] << 0; + k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; + }; + + + //---------- + // finalization + + h1 ^= len; h2 ^= len; h3 ^= len; h4 ^= len; + + h1 += h2; h1 += h3; h1 += h4; + h2 += h1; h3 += h1; h4 += h1; + + h1 = fmix32(h1); + h2 = fmix32(h2); + h3 = fmix32(h3); + h4 = fmix32(h4); + + h1 += h2; h1 += h3; h1 += h4; + h2 += h1; h3 += h1; h4 += h1; + + ((uint32_t*)out)[0] = h1; + ((uint32_t*)out)[1] = h2; + ((uint32_t*)out)[2] = h3; + ((uint32_t*)out)[3] = h4; +} + +//----------------------------------------------------------------------------- + +void MurmurHash3_x64_128_Init(MurmurHash3_x64_128_Context* ctx, uint32_t seed) +{ + ctx->h1 = seed; + ctx->h2 = seed; + ctx->total_len = 0; + ctx->tail_len = 0; +} + +void MurmurHash3_x64_128_Update( + MurmurHash3_x64_128_Context* ctx, const void * key, int len) +{ + uint64_t h1 = ctx->h1; + uint64_t h2 = ctx->h2; + + const uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5); + const uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f); + + const uint8_t * data = (const uint8_t*)key; + if (ctx->tail_len > 0) { + const int append = std::min(len, 16 - ctx->tail_len); + memcpy(ctx->tail + ctx->tail_len, data, append); + ctx->total_len += append; + ctx->tail_len += append; + data += append; + len -= append; + if (ctx->tail_len == 16) { + uint64_t k1 = getblock64((uint64_t*)ctx->tail, 0); + uint64_t k2 = getblock64((uint64_t*)ctx->tail, 1); + + k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1; + + h1 = ROTL64(h1,27); h1 += h2; h1 = h1*5+0x52dce729; + + k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2; + + h2 = ROTL64(h2,31); h2 += h1; h2 = h2*5+0x38495ab5; + ctx->tail_len = 0; + } + } + + const int nblocks = len / 16; + + //---------- + // body + + const uint64_t * blocks = (const uint64_t *)(data); + + for (int i = 0; i < nblocks; i++) + { + uint64_t k1 = getblock64(blocks,i*2+0); + uint64_t k2 = getblock64(blocks,i*2+1); + + k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1; + + h1 = ROTL64(h1,27); h1 += h2; h1 = h1*5+0x52dce729; + + k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2; + + h2 = ROTL64(h2,31); h2 += h1; h2 = h2*5+0x38495ab5; + } + + // tail + const int tail_len = len & 15; + if (tail_len > 0) { + memcpy(ctx->tail, data + nblocks * 16, tail_len); + ctx->tail_len = tail_len; + } + + ctx->h1 = h1; + ctx->h2 = h2; + ctx->total_len += len; +} + +void MurmurHash3_x64_128_Final(void * out, const MurmurHash3_x64_128_Context* ctx) +{ + uint64_t h1 = ctx->h1; + uint64_t h2 = ctx->h2; + const uint64_t len = ctx->total_len; + //---------- + // tail + + const uint8_t * tail = ctx->tail; + const uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5); + const uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f); + + uint64_t k1 = 0; + uint64_t k2 = 0; + + switch (ctx->tail_len) + { + case 15: k2 ^= ((uint64_t)tail[14]) << 48; + case 14: k2 ^= ((uint64_t)tail[13]) << 40; + case 13: k2 ^= ((uint64_t)tail[12]) << 32; + case 12: k2 ^= ((uint64_t)tail[11]) << 24; + case 11: k2 ^= ((uint64_t)tail[10]) << 16; + case 10: k2 ^= ((uint64_t)tail[ 9]) << 8; + case 9: k2 ^= ((uint64_t)tail[ 8]) << 0; + k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2; + + case 8: k1 ^= ((uint64_t)tail[ 7]) << 56; + case 7: k1 ^= ((uint64_t)tail[ 6]) << 48; + case 6: k1 ^= ((uint64_t)tail[ 5]) << 40; + case 5: k1 ^= ((uint64_t)tail[ 4]) << 32; + case 4: k1 ^= ((uint64_t)tail[ 3]) << 24; + case 3: k1 ^= ((uint64_t)tail[ 2]) << 16; + case 2: k1 ^= ((uint64_t)tail[ 1]) << 8; + case 1: k1 ^= ((uint64_t)tail[ 0]) << 0; + k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1; + }; + + + //---------- + // finalization + + h1 ^= len; h2 ^= len; + + h1 += h2; + h2 += h1; + + h1 = fmix64(h1); + h2 = fmix64(h2); + + h1 += h2; + h2 += h1; + + ((uint64_t*)out)[0] = h1; + ((uint64_t*)out)[1] = h2; +} + +void MurmurHash3_x86_32_Init(MurmurHash3_x86_32_Context* ctx, uint32_t seed) { + ctx->h1 = seed; + ctx->total_len = 0; + ctx->tail_len = 0; +} + +void MurmurHash3_x86_32_Update(MurmurHash3_x86_32_Context* ctx, const void* key, int len) { + + uint32_t h1 = ctx->h1; + const uint32_t c1 = 0xcc9e2d51; + const uint32_t c2 = 0x1b873593; + const uint8_t * data = (const uint8_t*)key; + if (ctx->tail_len > 0) { + const int append = std::min(len, 4 - ctx->tail_len); + memcpy(ctx->tail + ctx->tail_len, data, append); + ctx->total_len += append; + ctx->tail_len += append; + data += append; + len -= append; + if (ctx->tail_len == 4) { + uint32_t k1 = getblock32((uint32_t*)ctx->tail,0); + + k1 *= c1; + k1 = ROTL32(k1,15); + k1 *= c2; + + h1 ^= k1; + h1 = ROTL32(h1,13); + h1 = h1*5+0xe6546b64; + ctx->tail_len = 0; + } + } + + const int nblocks = len / 4; + + //---------- + // body + + const uint32_t * blocks = (const uint32_t *)(data + nblocks*4); + + for (int i = -nblocks; i; i++) + { + uint32_t k1 = getblock32(blocks,i); + + k1 *= c1; + k1 = ROTL32(k1,15); + k1 *= c2; + + h1 ^= k1; + h1 = ROTL32(h1,13); + h1 = h1*5+0xe6546b64; + } + + //---------- + // tail + + const int tail_len = len & 3; + if (tail_len > 0) { + memcpy(ctx->tail, data + nblocks * 4, tail_len); + ctx->tail_len = tail_len; + } + + //---------- + // finalization + ctx->h1 = h1; + ctx->total_len += len; +} + +void MurmurHash3_x86_32_Final(void * out, const MurmurHash3_x86_32_Context* ctx) { + const uint32_t c1 = 0xcc9e2d51; + const uint32_t c2 = 0x1b873593; + uint32_t h1 = ctx->h1; + + uint32_t k1 = 0; + const uint8_t* tail = ctx->tail; + switch (ctx->tail_len) + { + case 3: k1 ^= tail[2] << 16; + case 2: k1 ^= tail[1] << 8; + case 1: k1 ^= tail[0]; + k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; + }; + + h1 ^= ctx->total_len; + h1 = fmix32(h1); + *(uint32_t*)out = h1; +} + +} // namespace butil diff --git a/src/butil/third_party/murmurhash3/murmurhash3.h b/src/butil/third_party/murmurhash3/murmurhash3.h new file mode 100644 index 0000000..c4021b1 --- /dev/null +++ b/src/butil/third_party/murmurhash3/murmurhash3.h @@ -0,0 +1,116 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// MurmurHash3 was written by Austin Appleby, and is placed in the public +// domain. The author hereby disclaims copyright to this source code. + + +#ifndef BUTIL_THIRD_PARTY_MURMURHASH3_MURMURHASH3_H +#define BUTIL_THIRD_PARTY_MURMURHASH3_MURMURHASH3_H + +// ======= Platform-specific functions and macros ======= +// Microsoft Visual Studio +#if defined(_MSC_VER) && (_MSC_VER < 1600) +typedef unsigned char uint8_t; +typedef unsigned int uint32_t; +typedef unsigned __int64 uint64_t; + +// Other compilers + +#else // defined(_MSC_VER) +#include +#endif // !defined(_MSC_VER) + +#if defined(_MSC_VER) +#define MURMURHASH_FORCE_INLINE __forceinline +#define BIG_CONSTANT(x) (x) +#else +#define MURMURHASH_FORCE_INLINE inline __attribute__((always_inline)) +#define BIG_CONSTANT(x) (x##LLU) +#endif + +namespace butil { + +// Finalization mix - force all bits of a hash block to avalanche +MURMURHASH_FORCE_INLINE uint32_t fmix32 (uint32_t h) { + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + return h; +} +MURMURHASH_FORCE_INLINE uint64_t fmix64 (uint64_t k) { + k ^= k >> 33; + k *= BIG_CONSTANT(0xff51afd7ed558ccd); + k ^= k >> 33; + k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53); + k ^= k >> 33; + return k; +} + +// ========= Original version =========== +void MurmurHash3_x86_32(const void* key, int len, uint32_t seed, void* out); +void MurmurHash3_x86_128(const void* key, int len, uint32_t seed, void* out); +void MurmurHash3_x64_128(const void* key, int len, uint32_t seed, void* out); + +// ========= Iterative version ========== +// for computing hashcode for very large inputs, say file contents. The API are +// similar with iterative MD5 API. +// Notice: |ctx| must be non-NULL and valid, otherwise the behavior is undefined. +struct MurmurHash3_x86_32_Context { + uint32_t h1; + int total_len; + int tail_len; + uint8_t tail[4]; +}; +void MurmurHash3_x86_32_Init(MurmurHash3_x86_32_Context* ctx, uint32_t seed); +void MurmurHash3_x86_32_Update(MurmurHash3_x86_32_Context* ctx, const void* key, int len); +void MurmurHash3_x86_32_Final(void* out, const MurmurHash3_x86_32_Context* ctx); + +struct MurmurHash3_x86_128_Context { + uint32_t h1; + uint32_t h2; + uint32_t h3; + uint32_t h4; + // Notice: may overflow, but fine. + int total_len; + int tail_len; + uint8_t tail[16]; +}; +void MurmurHash3_x86_128_Init(MurmurHash3_x86_128_Context* ctx, uint32_t seed); +void MurmurHash3_x86_128_Update(MurmurHash3_x86_128_Context* ctx, const void* key, int len); +void MurmurHash3_x86_128_Final(void* out, const MurmurHash3_x86_128_Context* ctx); + +struct MurmurHash3_x64_128_Context { + uint64_t h1; + uint64_t h2; + // Notice: + // different from MurmurHash3_x86_128_Context where total_len is int. + // When total_len >= 2^31, this is also (slightly) different from + // MurmurHash3_x64_128() because len in the function is int. + uint64_t total_len; + int tail_len; + uint8_t tail[16]; +}; +void MurmurHash3_x64_128_Init(MurmurHash3_x64_128_Context* ctx, uint32_t seed); +void MurmurHash3_x64_128_Update(MurmurHash3_x64_128_Context* ctx, const void* key, int len); +void MurmurHash3_x64_128_Final(void* out, const MurmurHash3_x64_128_Context* ctx); + +} // namespace butil + +#endif // BUTIL_THIRD_PARTY_MURMURHASH3_MURMURHASH3_H diff --git a/src/butil/third_party/rapidjson/allocators.h b/src/butil/third_party/rapidjson/allocators.h new file mode 100644 index 0000000..7ac0007 --- /dev/null +++ b/src/butil/third_party/rapidjson/allocators.h @@ -0,0 +1,265 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ALLOCATORS_H_ +#define RAPIDJSON_ALLOCATORS_H_ + +#include "rapidjson.h" + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Allocator + +/*! \class rapidjson::Allocator + \brief Concept for allocating, resizing and freeing memory block. + + Note that Malloc() and Realloc() are non-static but Free() is static. + + So if an allocator need to support Free(), it needs to put its pointer in + the header of memory block. + +\code +concept Allocator { + static const bool kNeedFree; //!< Whether this allocator needs to call Free(). + + // Allocate a memory block. + // \param size of the memory block in bytes. + // \returns pointer to the memory block. + void* Malloc(size_t size); + + // Resize a memory block. + // \param originalPtr The pointer to current memory block. Null pointer is permitted. + // \param originalSize The current size in bytes. (Design issue: since some allocator may not book-keep this, explicitly pass to it can save memory.) + // \param newSize the new size in bytes. + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize); + + // Free a memory block. + // \param pointer to the memory block. Null pointer is permitted. + static void Free(void *ptr); +}; +\endcode +*/ + +/////////////////////////////////////////////////////////////////////////////// +// CrtAllocator + +//! C-runtime library allocator. +/*! This class is just wrapper for standard C library memory routines. + \note implements Allocator concept +*/ +class CrtAllocator { +public: + static const bool kNeedFree = true; + void* Malloc(size_t size) { + if (size) // behavior of malloc(0) is implementation defined. + return std::malloc(size); + else + return NULL; // standardize to returning NULL. + } + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { + (void)originalSize; + if (newSize == 0) { + std::free(originalPtr); + return NULL; + } + return std::realloc(originalPtr, newSize); + } + static void Free(void *ptr) { std::free(ptr); } +}; + +/////////////////////////////////////////////////////////////////////////////// +// MemoryPoolAllocator + +//! Default memory allocator used by the parser and DOM. +/*! This allocator allocate memory blocks from pre-allocated memory chunks. + + It does not free memory blocks. And Realloc() only allocate new memory. + + The memory chunks are allocated by BaseAllocator, which is CrtAllocator by default. + + User may also supply a buffer as the first chunk. + + If the user-buffer is full then additional chunks are allocated by BaseAllocator. + + The user-buffer is not deallocated by this allocator. + + \tparam BaseAllocator the allocator type for allocating memory chunks. Default is CrtAllocator. + \note implements Allocator concept +*/ +template +class MemoryPoolAllocator { +public: + static const bool kNeedFree = false; //!< Tell users that no need to call Free() with this allocator. (concept Allocator) + + //! Constructor with chunkSize. + /*! \param chunkSize The size of memory chunk. The default is kDefaultChunkSize. + \param baseAllocator The allocator for allocating memory chunks. + */ + MemoryPoolAllocator(size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) : + chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(0), baseAllocator_(baseAllocator), ownBaseAllocator_(0) + { + } + + //! Constructor with user-supplied buffer. + /*! The user buffer will be used firstly. When it is full, memory pool allocates new chunk with chunk size. + + The user buffer will not be deallocated when this allocator is destructed. + + \param buffer User supplied buffer. + \param size Size of the buffer in bytes. It must at least larger than sizeof(ChunkHeader). + \param chunkSize The size of memory chunk. The default is kDefaultChunkSize. + \param baseAllocator The allocator for allocating memory chunks. + */ + MemoryPoolAllocator(void *buffer, size_t size, size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) : + chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(buffer), baseAllocator_(baseAllocator), ownBaseAllocator_(0) + { + RAPIDJSON_ASSERT(buffer != 0); + RAPIDJSON_ASSERT(size > sizeof(ChunkHeader)); + chunkHead_ = reinterpret_cast(buffer); + chunkHead_->capacity = size - sizeof(ChunkHeader); + chunkHead_->size = 0; + chunkHead_->next = 0; + } + + //! Destructor. + /*! This deallocates all memory chunks, excluding the user-supplied buffer. + */ + ~MemoryPoolAllocator() { + Clear(); + RAPIDJSON_DELETE(ownBaseAllocator_); + } + + //! Deallocates all memory chunks, excluding the user-supplied buffer. + void Clear() { + while (chunkHead_ && chunkHead_ != userBuffer_) { + ChunkHeader* next = chunkHead_->next; + baseAllocator_->Free(chunkHead_); + chunkHead_ = next; + } + if (chunkHead_ && chunkHead_ == userBuffer_) + chunkHead_->size = 0; // Clear user buffer + } + + //! Computes the total capacity of allocated memory chunks. + /*! \return total capacity in bytes. + */ + size_t Capacity() const { + size_t capacity = 0; + for (ChunkHeader* c = chunkHead_; c != 0; c = c->next) + capacity += c->capacity; + return capacity; + } + + //! Computes the memory blocks allocated. + /*! \return total used bytes. + */ + size_t Size() const { + size_t size = 0; + for (ChunkHeader* c = chunkHead_; c != 0; c = c->next) + size += c->size; + return size; + } + + //! Allocates a memory block. (concept Allocator) + void* Malloc(size_t size) { + if (!size) + return NULL; + + size = RAPIDJSON_ALIGN(size); + if (chunkHead_ == 0 || chunkHead_->size + size > chunkHead_->capacity) + AddChunk(chunk_capacity_ > size ? chunk_capacity_ : size); + + void *buffer = reinterpret_cast(chunkHead_) + RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + chunkHead_->size; + chunkHead_->size += size; + return buffer; + } + + //! Resizes a memory block (concept Allocator) + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { + if (originalPtr == 0) + return Malloc(newSize); + + if (newSize == 0) + return NULL; + + // Do not shrink if new size is smaller than original + if (originalSize >= newSize) + return originalPtr; + + // Simply expand it if it is the last allocation and there is sufficient space + if (originalPtr == (char *)(chunkHead_) + RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + chunkHead_->size - originalSize) { + size_t increment = static_cast(newSize - originalSize); + increment = RAPIDJSON_ALIGN(increment); + if (chunkHead_->size + increment <= chunkHead_->capacity) { + chunkHead_->size += increment; + return originalPtr; + } + } + + // Realloc process: allocate and copy memory, do not free original buffer. + void* newBuffer = Malloc(newSize); + RAPIDJSON_ASSERT(newBuffer != 0); // Do not handle out-of-memory explicitly. + if (originalSize) + std::memcpy(newBuffer, originalPtr, originalSize); + return newBuffer; + } + + //! Frees a memory block (concept Allocator) + static void Free(void *ptr) { (void)ptr; } // Do nothing + +private: + //! Copy constructor is not permitted. + MemoryPoolAllocator(const MemoryPoolAllocator& rhs) /* = delete */; + //! Copy assignment operator is not permitted. + MemoryPoolAllocator& operator=(const MemoryPoolAllocator& rhs) /* = delete */; + + //! Creates a new chunk. + /*! \param capacity Capacity of the chunk in bytes. + */ + void AddChunk(size_t capacity) { + if (!baseAllocator_) + ownBaseAllocator_ = baseAllocator_ = RAPIDJSON_NEW(BaseAllocator()); + ChunkHeader* chunk = reinterpret_cast(baseAllocator_->Malloc(RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + capacity)); + chunk->capacity = capacity; + chunk->size = 0; + chunk->next = chunkHead_; + chunkHead_ = chunk; + } + + static const int kDefaultChunkCapacity = 64 * 1024; //!< Default chunk capacity. + + //! Chunk header for perpending to each chunk. + /*! Chunks are stored as a singly linked list. + */ + struct ChunkHeader { + size_t capacity; //!< Capacity of the chunk in bytes (excluding the header itself). + size_t size; //!< Current size of allocated memory in bytes. + ChunkHeader *next; //!< Next chunk in the linked list. + }; + + ChunkHeader *chunkHead_; //!< Head of the chunk linked-list. Only the head chunk serves allocation. + size_t chunk_capacity_; //!< The minimum capacity of chunk when they are allocated. + void *userBuffer_; //!< User supplied buffer. + BaseAllocator* baseAllocator_; //!< base allocator for allocating memory chunks. + BaseAllocator* ownBaseAllocator_; //!< base allocator created by this object. +}; + +// ccover g++ may complain undefined reference w/o this declaration. +template +const bool MemoryPoolAllocator::kNeedFree; + +BUTIL_RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ENCODINGS_H_ diff --git a/src/butil/third_party/rapidjson/document.h b/src/butil/third_party/rapidjson/document.h new file mode 100644 index 0000000..783186b --- /dev/null +++ b/src/butil/third_party/rapidjson/document.h @@ -0,0 +1,2056 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_DOCUMENT_H_ +#define RAPIDJSON_DOCUMENT_H_ + +/*! \file document.h */ + +#include "reader.h" +#include "internal/meta.h" +#include "internal/strfunc.h" +#include // placement new + +#ifdef _MSC_VER +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant +#elif defined(__GNUC__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_HAS_STDSTRING + +#ifndef RAPIDJSON_HAS_STDSTRING +#ifdef RAPIDJSON_DOXYGEN_RUNNING +#define RAPIDJSON_HAS_STDSTRING 1 // force generation of documentation +#else +#define RAPIDJSON_HAS_STDSTRING 0 // no std::string support by default +#endif +/*! \def RAPIDJSON_HAS_STDSTRING + \ingroup RAPIDJSON_CONFIG + \brief Enable RapidJSON support for \c std::string + + By defining this preprocessor symbol to \c 1, several convenience functions for using + \ref rapidjson::GenericValue with \c std::string are enabled, especially + for construction and comparison. + + \hideinitializer +*/ +#endif // !defined(RAPIDJSON_HAS_STDSTRING) + +#if RAPIDJSON_HAS_STDSTRING +#include +#endif // RAPIDJSON_HAS_STDSTRING + +#ifndef RAPIDJSON_NOMEMBERITERATORCLASS +#include // std::iterator, std::random_access_iterator_tag +#endif + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS +#include // std::move +#endif + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN + +// Forward declaration. +template +class GenericValue; + +//! Name-value pair in a JSON object value. +/*! + This class was internal to GenericValue. It used to be a inner struct. + But a compiler (IBM XL C/C++ for AIX) have reported to have problem with that so it moved as a namespace scope struct. + https://code.google.com/p/rapidjson/issues/detail?id=64 +*/ +template +struct GenericMember { + GenericValue name; //!< name of member (must be a string) + GenericValue value; //!< value of member. +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericMemberIterator + +#ifndef RAPIDJSON_NOMEMBERITERATORCLASS + +//! (Constant) member iterator for a JSON object value +/*! + \tparam Const Is this a constant iterator? + \tparam Encoding Encoding of the value. (Even non-string values need to have the same encoding in a document) + \tparam Allocator Allocator type for allocating memory of object, array and string. + + This class implements a Random Access Iterator for GenericMember elements + of a GenericValue, see ISO/IEC 14882:2003(E) C++ standard, 24.1 [lib.iterator.requirements]. + + \note This iterator implementation is mainly intended to avoid implicit + conversions from iterator values to \c NULL, + e.g. from GenericValue::FindMember. + + \note Define \c RAPIDJSON_NOMEMBERITERATORCLASS to fall back to a + pointer-based implementation, if your platform doesn't provide + the C++ header. + + \see GenericMember, GenericValue::MemberIterator, GenericValue::ConstMemberIterator + */ +template +class GenericMemberIterator + : public std::iterator >::Type> { + + friend class GenericValue; + template friend class GenericMemberIterator; + + typedef GenericMember PlainType; + typedef typename internal::MaybeAddConst::Type ValueType; + typedef std::iterator BaseType; + +public: + //! Iterator type itself + typedef GenericMemberIterator Iterator; + //! Constant iterator type + typedef GenericMemberIterator ConstIterator; + //! Non-constant iterator type + typedef GenericMemberIterator NonConstIterator; + + //! Pointer to (const) GenericMember + typedef typename BaseType::pointer Pointer; + //! Reference to (const) GenericMember + typedef typename BaseType::reference Reference; + //! Signed integer type (e.g. \c ptrdiff_t) + typedef typename BaseType::difference_type DifferenceType; + + //! Default constructor (singular value) + /*! Creates an iterator pointing to no element. + \note All operations, except for comparisons, are undefined on such values. + */ + GenericMemberIterator() : ptr_() {} + + //! Iterator conversions to more const + /*! + \param it (Non-const) iterator to copy from + + Allows the creation of an iterator from another GenericMemberIterator + that is "less const". Especially, creating a non-constant iterator + from a constant iterator are disabled: + \li const -> non-const (not ok) + \li const -> const (ok) + \li non-const -> const (ok) + \li non-const -> non-const (ok) + + \note If the \c Const template parameter is already \c false, this + constructor effectively defines a regular copy-constructor. + Otherwise, the copy constructor is implicitly defined. + */ + GenericMemberIterator(const NonConstIterator & it) : ptr_(it.ptr_) {} + + //! @name stepping + //@{ + Iterator& operator++(){ ++ptr_; return *this; } + Iterator& operator--(){ --ptr_; return *this; } + Iterator operator++(int){ Iterator old(*this); ++ptr_; return old; } + Iterator operator--(int){ Iterator old(*this); --ptr_; return old; } + //@} + + //! @name increment/decrement + //@{ + Iterator operator+(DifferenceType n) const { return Iterator(ptr_+n); } + Iterator operator-(DifferenceType n) const { return Iterator(ptr_-n); } + + Iterator& operator+=(DifferenceType n) { ptr_+=n; return *this; } + Iterator& operator-=(DifferenceType n) { ptr_-=n; return *this; } + //@} + + //! @name relations + //@{ + bool operator==(ConstIterator that) const { return ptr_ == that.ptr_; } + bool operator!=(ConstIterator that) const { return ptr_ != that.ptr_; } + bool operator<=(ConstIterator that) const { return ptr_ <= that.ptr_; } + bool operator>=(ConstIterator that) const { return ptr_ >= that.ptr_; } + bool operator< (ConstIterator that) const { return ptr_ < that.ptr_; } + bool operator> (ConstIterator that) const { return ptr_ > that.ptr_; } + //@} + + //! @name dereference + //@{ + Reference operator*() const { return *ptr_; } + Pointer operator->() const { return ptr_; } + Reference operator[](DifferenceType n) const { return ptr_[n]; } + //@} + + //! Distance + DifferenceType operator-(ConstIterator that) const { return ptr_-that.ptr_; } + +private: + //! Internal constructor from plain pointer + explicit GenericMemberIterator(Pointer p) : ptr_(p) {} + + Pointer ptr_; //!< raw pointer +}; + +#else // RAPIDJSON_NOMEMBERITERATORCLASS + +// class-based member iterator implementation disabled, use plain pointers + +template +struct GenericMemberIterator; + +//! non-const GenericMemberIterator +template +struct GenericMemberIterator { + //! use plain pointer as iterator type + typedef GenericMember* Iterator; +}; +//! const GenericMemberIterator +template +struct GenericMemberIterator { + //! use plain const pointer as iterator type + typedef const GenericMember* Iterator; +}; + +#endif // RAPIDJSON_NOMEMBERITERATORCLASS + +/////////////////////////////////////////////////////////////////////////////// +// GenericStringRef + +//! Reference to a constant string (not taking a copy) +/*! + \tparam CharType character type of the string + + This helper class is used to automatically infer constant string + references for string literals, especially from \c const \b (!) + character arrays. + + The main use is for creating JSON string values without copying the + source string via an \ref Allocator. This requires that the referenced + string pointers have a sufficient lifetime, which exceeds the lifetime + of the associated GenericValue. + + \b Example + \code + Value v("foo"); // ok, no need to copy & calculate length + const char foo[] = "foo"; + v.SetString(foo); // ok + + const char* bar = foo; + // Value x(bar); // not ok, can't rely on bar's lifetime + Value x(StringRef(bar)); // lifetime explicitly guaranteed by user + Value y(StringRef(bar, 3)); // ok, explicitly pass length + \endcode + + \see StringRef, GenericValue::SetString +*/ +template +struct GenericStringRef { + typedef CharType Ch; //!< character type of the string + + //! Create string reference from \c const character array + /*! + This constructor implicitly creates a constant string reference from + a \c const character array. It has better performance than + \ref StringRef(const CharType*) by inferring the string \ref length + from the array length, and also supports strings containing null + characters. + + \tparam N length of the string, automatically inferred + + \param str Constant character array, lifetime assumed to be longer + than the use of the string in e.g. a GenericValue + + \post \ref s == str + + \note Constant complexity. + \note There is a hidden, private overload to disallow references to + non-const character arrays to be created via this constructor. + By this, e.g. function-scope arrays used to be filled via + \c snprintf are excluded from consideration. + In such cases, the referenced string should be \b copied to the + GenericValue instead. + */ + template + GenericStringRef(const CharType (&str)[N]) RAPIDJSON_NOEXCEPT + : s(str), length(N-1) {} + + //! Explicitly create string reference from \c const character pointer + /*! + This constructor can be used to \b explicitly create a reference to + a constant string pointer. + + \see StringRef(const CharType*) + + \param str Constant character pointer, lifetime assumed to be longer + than the use of the string in e.g. a GenericValue + + \post \ref s == str + + \note There is a hidden, private overload to disallow references to + non-const character arrays to be created via this constructor. + By this, e.g. function-scope arrays used to be filled via + \c snprintf are excluded from consideration. + In such cases, the referenced string should be \b copied to the + GenericValue instead. + */ + explicit GenericStringRef(const CharType* str) + : s(str), length(internal::StrLen(str)){ RAPIDJSON_ASSERT(s != NULL); } + + //! Create constant string reference from pointer and length + /*! \param str constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \param len length of the string, excluding the trailing NULL terminator + + \post \ref s == str && \ref length == len + \note Constant complexity. + */ + GenericStringRef(const CharType* str, SizeType len) + : s(str), length(len) { RAPIDJSON_ASSERT(s != NULL); } + + // Required by clang++ 11 on MacOS catalina + GenericStringRef(const GenericStringRef& other) + : s(other.s), length(other.length) { RAPIDJSON_ASSERT(s != NULL); } + + //! implicit conversion to plain CharType pointer + operator const Ch *() const { return s; } + + const Ch* const s; //!< plain CharType pointer + const SizeType length; //!< length of the string (excluding the trailing NULL terminator) + +private: + //! Disallow copy-ctor&assignment + GenericStringRef operator=(const GenericStringRef&) = delete; + //! Disallow construction from non-const array + template + GenericStringRef(CharType (&str)[N]) = delete; +}; + +//! Mark a character pointer as constant string +/*! Mark a plain character pointer as a "string literal". This function + can be used to avoid copying a character string to be referenced as a + value in a JSON GenericValue object, if the string's lifetime is known + to be valid long enough. + \tparam CharType Character type of the string + \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \return GenericStringRef string reference object + \relatesalso GenericStringRef + + \see GenericValue::GenericValue(StringRefType), GenericValue::operator=(StringRefType), GenericValue::SetString(StringRefType), GenericValue::PushBack(StringRefType, Allocator&), GenericValue::AddMember +*/ +template +inline GenericStringRef StringRef(const CharType* str) { + return GenericStringRef(str, internal::StrLen(str)); +} + +//! Mark a character pointer as constant string +/*! Mark a plain character pointer as a "string literal". This function + can be used to avoid copying a character string to be referenced as a + value in a JSON GenericValue object, if the string's lifetime is known + to be valid long enough. + + This version has better performance with supplied length, and also + supports string containing null characters. + + \tparam CharType character type of the string + \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \param length The length of source string. + \return GenericStringRef string reference object + \relatesalso GenericStringRef +*/ +template +inline GenericStringRef StringRef(const CharType* str, size_t length) { + return GenericStringRef(str, SizeType(length)); +} + +#if RAPIDJSON_HAS_STDSTRING +//! Mark a string object as constant string +/*! Mark a string object (e.g. \c std::string) as a "string literal". + This function can be used to avoid copying a string to be referenced as a + value in a JSON GenericValue object, if the string's lifetime is known + to be valid long enough. + + \tparam CharType character type of the string + \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \return GenericStringRef string reference object + \relatesalso GenericStringRef + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. +*/ +template +inline GenericStringRef StringRef(const std::basic_string& str) { + return GenericStringRef(str.data(), SizeType(str.size())); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +// GenericValue type traits +namespace internal { + +template +struct IsGenericValueImpl : FalseType {}; + +// select candidates according to nested encoding and allocator types +template struct IsGenericValueImpl::Type, typename Void::Type> + : IsBaseOf, T>::Type {}; + +// helper to match arbitrary GenericValue instantiations, including derived classes +template struct IsGenericValue : IsGenericValueImpl::Type {}; + +} // namespace internal + +/////////////////////////////////////////////////////////////////////////////// +// GenericValue + +//! Represents a JSON value. Use Value for UTF8 encoding and default allocator. +/*! + A JSON value can be one of 7 types. This class is a variant type supporting + these types. + + Use the Value if UTF8 and default allocator + + \tparam Encoding Encoding of the value. (Even non-string values need to have the same encoding in a document) + \tparam Allocator Allocator type for allocating memory of object, array and string. +*/ +template > +class GenericValue { +public: + //! Name-value pair in an object. + typedef GenericMember Member; + typedef Encoding EncodingType; //!< Encoding type from template parameter. + typedef Allocator AllocatorType; //!< Allocator type from template parameter. + typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. + typedef GenericStringRef StringRefType; //!< Reference to a constant string + typedef typename GenericMemberIterator::Iterator MemberIterator; //!< Member iterator for iterating in object. + typedef typename GenericMemberIterator::Iterator ConstMemberIterator; //!< Constant member iterator for iterating in object. + typedef GenericValue* ValueIterator; //!< Value iterator for iterating in array. + typedef const GenericValue* ConstValueIterator; //!< Constant value iterator for iterating in array. + typedef GenericValue ValueType; //!< Value type of itself. + + //!@name Constructors and destructor. + //@{ + + //! Default constructor creates a null value. + GenericValue() RAPIDJSON_NOEXCEPT : data_(), flags_(kNullFlag) {} + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericValue(GenericValue&& rhs) RAPIDJSON_NOEXCEPT : data_(rhs.data_), flags_(rhs.flags_) { + rhs.flags_ = kNullFlag; // give up contents + } +#endif + +private: + //! Copy constructor is not permitted. + GenericValue(const GenericValue& rhs); + +public: + + //! Constructor with JSON value type. + /*! This creates a Value of specified type with default content. + \param type Type of the value. + \note Default content for number is zero. + */ + explicit GenericValue(Type type) RAPIDJSON_NOEXCEPT : data_(), flags_() { + static const unsigned defaultFlags[7] = { + kNullFlag, kFalseFlag, kTrueFlag, kObjectFlag, kArrayFlag, kShortStringFlag, + kNumberAnyFlag + }; + RAPIDJSON_ASSERT(type <= kNumberType); + flags_ = defaultFlags[type]; + + // Use ShortString to store empty string. + if (type == kStringType) + data_.ss.SetLength(0); + } + + //! Explicit copy constructor (with allocator) + /*! Creates a copy of a Value by using the given Allocator + \tparam SourceAllocator allocator of \c rhs + \param rhs Value to copy from (read-only) + \param allocator Allocator for allocating copied elements and buffers. Commonly use GenericDocument::GetAllocator(). + \see CopyFrom() + */ + template< typename SourceAllocator > + GenericValue(const GenericValue& rhs, Allocator & allocator); + + //! Constructor for boolean value. + /*! \param b Boolean value + \note This constructor is limited to \em real boolean values and rejects + implicitly converted types like arbitrary pointers. Use an explicit cast + to \c bool, if you want to construct a boolean JSON value in such cases. + */ +#ifndef RAPIDJSON_DOXYGEN_RUNNING // hide SFINAE from Doxygen + template + explicit GenericValue(T b, RAPIDJSON_ENABLEIF((internal::IsSame))) RAPIDJSON_NOEXCEPT +#else + explicit GenericValue(bool b) RAPIDJSON_NOEXCEPT +#endif + : data_(), flags_(b ? kTrueFlag : kFalseFlag) { + // safe-guard against failing SFINAE + RAPIDJSON_STATIC_ASSERT((internal::IsSame::Value)); + } + + //! Constructor for int value. + explicit GenericValue(int i) RAPIDJSON_NOEXCEPT : data_(), flags_(kNumberIntFlag) { + data_.n.i64 = i; + if (i >= 0) + flags_ |= kUintFlag | kUint64Flag; + } + + //! Constructor for unsigned value. + explicit GenericValue(unsigned u) RAPIDJSON_NOEXCEPT : data_(), flags_(kNumberUintFlag) { + data_.n.u64 = u; + if (!(u & 0x80000000)) + flags_ |= kIntFlag | kInt64Flag; + } + + //! Constructor for int64_t value. + explicit GenericValue(int64_t i64) RAPIDJSON_NOEXCEPT : data_(), flags_(kNumberInt64Flag) { + data_.n.i64 = i64; + if (i64 >= 0) { + flags_ |= kNumberUint64Flag; + if (!(static_cast(i64) & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x00000000))) + flags_ |= kUintFlag; + if (!(static_cast(i64) & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) + flags_ |= kIntFlag; + } + else if (i64 >= static_cast(RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) + flags_ |= kIntFlag; + } + + //! Constructor for uint64_t value. + explicit GenericValue(uint64_t u64) RAPIDJSON_NOEXCEPT : data_(), flags_(kNumberUint64Flag) { + data_.n.u64 = u64; + if (!(u64 & RAPIDJSON_UINT64_C2(0x80000000, 0x00000000))) + flags_ |= kInt64Flag; + if (!(u64 & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x00000000))) + flags_ |= kUintFlag; + if (!(u64 & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) + flags_ |= kIntFlag; + } + + //! Constructor for double value. + explicit GenericValue(double d) RAPIDJSON_NOEXCEPT : data_(), flags_(kNumberDoubleFlag) { data_.n.d = d; } + + //! Constructor for constant string (i.e. do not make a copy of string) + GenericValue(const Ch* s, SizeType length) RAPIDJSON_NOEXCEPT : data_(), flags_() { SetStringRaw(StringRef(s, length)); } + + //! Constructor for constant string (i.e. do not make a copy of string) + explicit GenericValue(StringRefType s) RAPIDJSON_NOEXCEPT : data_(), flags_() { SetStringRaw(s); } + + //! Constructor for copy-string (i.e. do make a copy of string) + GenericValue(const Ch* s, SizeType length, Allocator& allocator) : data_(), flags_() { SetStringRaw(StringRef(s, length), allocator); } + + //! Constructor for copy-string (i.e. do make a copy of string) + GenericValue(const Ch*s, Allocator& allocator) : data_(), flags_() { SetStringRaw(StringRef(s), allocator); } + +#if RAPIDJSON_HAS_STDSTRING + //! Constructor for copy-string from a string object (i.e. do make a copy of string) + /*! \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + GenericValue(const std::basic_string& s, Allocator& allocator) : data_(), flags_() { SetStringRaw(StringRef(s), allocator); } +#endif + + //! Destructor. + /*! Need to destruct elements of array, members of object, or copy-string. + */ + ~GenericValue() { + if (Allocator::kNeedFree) { // Shortcut by Allocator's trait + switch(flags_) { + case kArrayFlag: + for (GenericValue* v = data_.a.elements; v != data_.a.elements + data_.a.size; ++v) + v->~GenericValue(); + Allocator::Free(data_.a.elements); + break; + + case kObjectFlag: + for (MemberIterator m = MemberBegin(); m != MemberEnd(); ++m) + m->~Member(); + Allocator::Free(data_.o.members); + break; + + case kCopyStringFlag: + Allocator::Free(const_cast(data_.s.str)); + break; + + default: + break; // Do nothing for other types. + } + } + } + + //@} + + //!@name Assignment operators + //@{ + + //! Assignment with move semantics. + /*! \param rhs Source of the assignment. It will become a null value after assignment. + */ + GenericValue& operator=(GenericValue& rhs) RAPIDJSON_NOEXCEPT { + RAPIDJSON_ASSERT(this != &rhs); + this->~GenericValue(); + RawAssign(rhs); + return *this; + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move assignment in C++11 + GenericValue& operator=(GenericValue&& rhs) RAPIDJSON_NOEXCEPT { + return *this = rhs.Move(); + } +#endif + + //! Assignment of constant string reference (no copy) + /*! \param str Constant string reference to be assigned + \note This overload is needed to avoid clashes with the generic primitive type assignment overload below. + \see GenericStringRef, operator=(T) + */ + GenericValue& operator=(StringRefType str) RAPIDJSON_NOEXCEPT { + GenericValue s(str); + return *this = s; + } + + //! Assignment with primitive types. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param value The value to be assigned. + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref SetString(const Ch*, Allocator&) (for copying) or + \ref StringRef() (to explicitly mark the pointer as constant) instead. + All other pointer types would implicitly convert to \c bool, + use \ref SetBool() instead. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::IsPointer), (GenericValue&)) + operator=(T value) { + GenericValue v(value); + return *this = v; + } + + //! Deep-copy assignment from Value + /*! Assigns a \b copy of the Value to the current Value object + \tparam SourceAllocator Allocator type of \c rhs + \param rhs Value to copy from (read-only) + \param allocator Allocator to use for copying + */ + template + GenericValue& CopyFrom(const GenericValue& rhs, Allocator& allocator) { + RAPIDJSON_ASSERT((void*)this != (void const*)&rhs); + this->~GenericValue(); + new (this) GenericValue(rhs, allocator); + return *this; + } + + //! Exchange the contents of this value with those of other. + /*! + \param other Another value. + \note Constant complexity. + */ + GenericValue& Swap(GenericValue& other) RAPIDJSON_NOEXCEPT { + GenericValue temp; + temp.RawAssign(*this); + RawAssign(other); + other.RawAssign(temp); + return *this; + } + + //! Prepare Value for move semantics + /*! \return *this */ + GenericValue& Move() RAPIDJSON_NOEXCEPT { return *this; } + //@} + + //!@name Equal-to and not-equal-to operators + //@{ + //! Equal-to operator + /*! + \note If an object contains duplicated named member, comparing equality with any object is always \c false. + \note Linear time complexity (number of all values in the subtree and total lengths of all strings). + */ + template + bool operator==(const GenericValue& rhs) const { + typedef GenericValue RhsType; + if (GetType() != rhs.GetType()) + return false; + + switch (GetType()) { + case kObjectType: // Warning: O(n^2) inner-loop + if (data_.o.size != rhs.data_.o.size) + return false; + for (ConstMemberIterator lhsMemberItr = MemberBegin(); lhsMemberItr != MemberEnd(); ++lhsMemberItr) { + typename RhsType::ConstMemberIterator rhsMemberItr = rhs.FindMember(lhsMemberItr->name); + if (rhsMemberItr == rhs.MemberEnd() || lhsMemberItr->value != rhsMemberItr->value) + return false; + } + return true; + + case kArrayType: + if (data_.a.size != rhs.data_.a.size) + return false; + for (SizeType i = 0; i < data_.a.size; i++) + if ((*this)[i] != rhs[i]) + return false; + return true; + + case kStringType: + return StringEqual(rhs); + + case kNumberType: + if (IsDouble() || rhs.IsDouble()) { + double a = GetDouble(); // May convert from integer to double. + double b = rhs.GetDouble(); // Ditto + return a >= b && a <= b; // Prevent -Wfloat-equal + } + else + return data_.n.u64 == rhs.data_.n.u64; + + default: // kTrueType, kFalseType, kNullType + return true; + } + } + + //! Equal-to operator with const C-string pointer + bool operator==(const Ch* rhs) const { return *this == GenericValue(StringRef(rhs)); } + +#if RAPIDJSON_HAS_STDSTRING + //! Equal-to operator with string object + /*! \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + bool operator==(const std::basic_string& rhs) const { return *this == GenericValue(StringRef(rhs)); } +#endif + + //! Equal-to operator with primitive types + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c double, \c true, \c false + */ + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr,internal::IsGenericValue >), (bool)) operator==(const T& rhs) const { return *this == GenericValue(rhs); } + + //! Not-equal-to operator + /*! \return !(*this == rhs) + */ + template + bool operator!=(const GenericValue& rhs) const { return !(*this == rhs); } + + //! Not-equal-to operator with const C-string pointer + bool operator!=(const Ch* rhs) const { return !(*this == rhs); } + + //! Not-equal-to operator with arbitrary types + /*! \return !(*this == rhs) + */ + template RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator!=(const T& rhs) const { return !(*this == rhs); } + + //! Equal-to operator with arbitrary types (symmetric version) + /*! \return (rhs == lhs) + */ + template friend RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator==(const T& lhs, const GenericValue& rhs) { return rhs == lhs; } + + //! Not-Equal-to operator with arbitrary types (symmetric version) + /*! \return !(rhs == lhs) + */ + template friend RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator!=(const T& lhs, const GenericValue& rhs) { return !(rhs == lhs); } + //@} + + //!@name Type + //@{ + + Type GetType() const { return static_cast(flags_ & kTypeMask); } + bool IsNull() const { return flags_ == kNullFlag; } + bool IsFalse() const { return flags_ == kFalseFlag; } + bool IsTrue() const { return flags_ == kTrueFlag; } + bool IsBool() const { return (flags_ & kBoolFlag) != 0; } + bool IsObject() const { return flags_ == kObjectFlag; } + bool IsArray() const { return flags_ == kArrayFlag; } + bool IsNumber() const { return (flags_ & kNumberFlag) != 0; } + bool IsInt() const { return (flags_ & kIntFlag) != 0; } + bool IsUint() const { return (flags_ & kUintFlag) != 0; } + bool IsInt64() const { return (flags_ & kInt64Flag) != 0; } + bool IsUint64() const { return (flags_ & kUint64Flag) != 0; } + bool IsDouble() const { return (flags_ & kDoubleFlag) != 0; } + bool IsString() const { return (flags_ & kStringFlag) != 0; } + + //@} + + //!@name Null + //@{ + + GenericValue& SetNull() { this->~GenericValue(); new (this) GenericValue(); return *this; } + + //@} + + //!@name Bool + //@{ + + bool GetBool() const { RAPIDJSON_ASSERT(IsBool()); return flags_ == kTrueFlag; } + //!< Set boolean value + /*! \post IsBool() == true */ + GenericValue& SetBool(bool b) { this->~GenericValue(); new (this) GenericValue(b); return *this; } + + //@} + + //!@name Object + //@{ + + //! Set this value as an empty object. + /*! \post IsObject() == true */ + GenericValue& SetObject() { this->~GenericValue(); new (this) GenericValue(kObjectType); return *this; } + + //! Get the number of members in the object. + SizeType MemberCount() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.size; } + + //! Check whether the object is empty. + bool ObjectEmpty() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.size == 0; } + + //! Get a value from an object associated with the name. + /*! \pre IsObject() == true + \tparam T Either \c Ch or \c const \c Ch (template used for disambiguation with \ref operator[](SizeType)) + \note In version 0.1x, if the member is not found, this function returns a null value. This makes issue 7. + Since 0.2, if the name is not correct, it will assert. + If user is unsure whether a member exists, user should use HasMember() first. + A better approach is to use FindMember(). + \note Linear time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >),(GenericValue&)) operator[](T* name) { + GenericValue n(StringRef(name)); + return (*this)[n]; + } + template + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >),(const GenericValue&)) operator[](T* name) const { return const_cast(*this)[name]; } + + //! Get a value from an object associated with the name. + /*! \pre IsObject() == true + \tparam SourceAllocator Allocator of the \c name value + + \note Compared to \ref operator[](T*), this version is faster because it does not need a StrLen(). + And it can also handle strings with embedded null characters. + + \note Linear time complexity. + */ + template + GenericValue& operator[](const GenericValue& name) { + MemberIterator member = FindMember(name); + if (member != MemberEnd()) + return member->value; + else { + RAPIDJSON_ASSERT(false); // see above note + static GenericValue NullValue; + return NullValue; + } + } + template + const GenericValue& operator[](const GenericValue& name) const { return const_cast(*this)[name]; } + +#if RAPIDJSON_HAS_STDSTRING + //! Get a value from an object associated with name (string object). + GenericValue& operator[](const std::basic_string& name) { return (*this)[GenericValue(StringRef(name))]; } + const GenericValue& operator[](const std::basic_string& name) const { return (*this)[GenericValue(StringRef(name))]; } +#endif + + //! Const member iterator + /*! \pre IsObject() == true */ + ConstMemberIterator MemberBegin() const { RAPIDJSON_ASSERT(IsObject()); return ConstMemberIterator(data_.o.members); } + //! Const \em past-the-end member iterator + /*! \pre IsObject() == true */ + ConstMemberIterator MemberEnd() const { RAPIDJSON_ASSERT(IsObject()); return ConstMemberIterator(data_.o.members + data_.o.size); } + //! Member iterator + /*! \pre IsObject() == true */ + MemberIterator MemberBegin() { RAPIDJSON_ASSERT(IsObject()); return MemberIterator(data_.o.members); } + //! \em Past-the-end member iterator + /*! \pre IsObject() == true */ + MemberIterator MemberEnd() { RAPIDJSON_ASSERT(IsObject()); return MemberIterator(data_.o.members + data_.o.size); } + + //! Check whether a member exists in the object. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + bool HasMember(const Ch* name) const { return FindMember(name) != MemberEnd(); } + +#if RAPIDJSON_HAS_STDSTRING + //! Check whether a member exists in the object with string object. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + bool HasMember(const std::basic_string& name) const { return FindMember(name) != MemberEnd(); } +#endif + + //! Check whether a member exists in the object with GenericValue name. + /*! + This version is faster because it does not need a StrLen(). It can also handle string with null character. + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + template + bool HasMember(const GenericValue& name) const { return FindMember(name) != MemberEnd(); } + + //! Find member by name. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + + \note Earlier versions of Rapidjson returned a \c NULL pointer, in case + the requested member doesn't exist. For consistency with e.g. + \c std::map, this has been changed to MemberEnd() now. + \note Linear time complexity. + */ + MemberIterator FindMember(const Ch* name) { + GenericValue n(StringRef(name)); + return FindMember(n); + } + + ConstMemberIterator FindMember(const Ch* name) const { return const_cast(*this).FindMember(name); } + + //! Find member by name. + /*! + This version is faster because it does not need a StrLen(). It can also handle string with null character. + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + + \note Earlier versions of Rapidjson returned a \c NULL pointer, in case + the requested member doesn't exist. For consistency with e.g. + \c std::map, this has been changed to MemberEnd() now. + \note Linear time complexity. + */ + template + MemberIterator FindMember(const GenericValue& name) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(name.IsString()); + MemberIterator member = MemberBegin(); + for ( ; member != MemberEnd(); ++member) + if (name.StringEqual(member->name)) + break; + return member; + } + template ConstMemberIterator FindMember(const GenericValue& name) const { return const_cast(*this).FindMember(name); } + +#if RAPIDJSON_HAS_STDSTRING + //! Find member by string object name. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + */ + MemberIterator FindMember(const std::basic_string& name) { return FindMember(StringRef(name)); } + ConstMemberIterator FindMember(const std::basic_string& name) const { return FindMember(StringRef(name)); } +#endif + + //! Add a member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value Value of any type. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note The ownership of \c name and \c value will be transferred to this object on success. + \pre IsObject() && name.IsString() + \post name.IsNull() && value.IsNull() + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, GenericValue& value, Allocator& allocator) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(name.IsString()); + + Object& o = data_.o; + if (o.size >= o.capacity) { + if (o.capacity == 0) { + o.capacity = kDefaultObjectCapacity; + o.members = reinterpret_cast(allocator.Malloc(o.capacity * sizeof(Member))); + } + else { + SizeType oldCapacity = o.capacity; + o.capacity += (oldCapacity + 1) / 2; // grow by factor 1.5 + o.members = reinterpret_cast(allocator.Realloc(o.members, oldCapacity * sizeof(Member), o.capacity * sizeof(Member))); + } + } + o.members[o.size].name.RawAssign(name); + o.members[o.size].value.RawAssign(value); + o.size++; + return *this; + } + + //! Add a constant string value as member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(GenericValue&,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, StringRefType value, Allocator& allocator) { + GenericValue v(value); + return AddMember(name, v, allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Add a string object as member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(GenericValue&,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, std::basic_string& value, Allocator& allocator) { + GenericValue v(value, allocator); + return AddMember(name, v, allocator); + } +#endif + + //! Add any primitive value as member (name-value pair) to the object. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param name A string value as name of member. + \param value Value of primitive type \c T as value of member + \param allocator Allocator for reallocating memory. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref AddMember(StringRefType, GenericValue&, Allocator&) or \ref + AddMember(StringRefType, StringRefType, Allocator&). + All other pointer types would implicitly convert to \c bool, + use an explicit cast instead, if needed. + \note Amortized Constant time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericValue&)) + AddMember(GenericValue& name, T value, Allocator& allocator) { + GenericValue v(value); + return AddMember(name, v, allocator); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericValue& AddMember(GenericValue&& name, GenericValue&& value, Allocator& allocator) { + return AddMember(name, value, allocator); + } + GenericValue& AddMember(GenericValue&& name, GenericValue& value, Allocator& allocator) { + return AddMember(name, value, allocator); + } + GenericValue& AddMember(GenericValue& name, GenericValue&& value, Allocator& allocator) { + return AddMember(name, value, allocator); + } + GenericValue& AddMember(StringRefType name, GenericValue&& value, Allocator& allocator) { + GenericValue n(name); + return AddMember(n, value, allocator); + } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + + + //! Add a member (name-value pair) to the object. + /*! \param name A constant string reference as name of member. + \param value Value of any type. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note The ownership of \c value will be transferred to this object on success. + \pre IsObject() + \post value.IsNull() + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(StringRefType name, GenericValue& value, Allocator& allocator) { + GenericValue n(name); + return AddMember(n, value, allocator); + } + + //! Add a constant string value as member (name-value pair) to the object. + /*! \param name A constant string reference as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(StringRefType,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(StringRefType name, StringRefType value, Allocator& allocator) { + GenericValue v(value); + return AddMember(name, v, allocator); + } + + //! Add any primitive value as member (name-value pair) to the object. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param name A constant string reference as name of member. + \param value Value of primitive type \c T as value of member + \param allocator Allocator for reallocating memory. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref AddMember(StringRefType, GenericValue&, Allocator&) or \ref + AddMember(StringRefType, StringRefType, Allocator&). + All other pointer types would implicitly convert to \c bool, + use an explicit cast instead, if needed. + \note Amortized Constant time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericValue&)) + AddMember(StringRefType name, T value, Allocator& allocator) { + GenericValue n(name); + return AddMember(n, value, allocator); + } + + //! Remove all members in the object. + /*! This function do not deallocate memory in the object, i.e. the capacity is unchanged. + \note Linear time complexity. + */ + void RemoveAllMembers() { + RAPIDJSON_ASSERT(IsObject()); + for (MemberIterator m = MemberBegin(); m != MemberEnd(); ++m) + m->~Member(); + data_.o.size = 0; + } + + //! Remove a member in object by its name. + /*! \param name Name of member to be removed. + \return Whether the member existed. + \note This function may reorder the object members. Use \ref + EraseMember(ConstMemberIterator) if you need to preserve the + relative order of the remaining members. + \note Linear time complexity. + */ + bool RemoveMember(const Ch* name) { + GenericValue n(StringRef(name)); + return RemoveMember(n); + } + +#if RAPIDJSON_HAS_STDSTRING + bool RemoveMember(const std::basic_string& name) { return RemoveMember(GenericValue(StringRef(name))); } +#endif + + template + bool RemoveMember(const GenericValue& name) { + MemberIterator m = FindMember(name); + if (m != MemberEnd()) { + RemoveMember(m); + return true; + } + else + return false; + } + + //! Remove a member in object by iterator. + /*! \param m member iterator (obtained by FindMember() or MemberBegin()). + \return the new iterator after removal. + \note This function may reorder the object members. Use \ref + EraseMember(ConstMemberIterator) if you need to preserve the + relative order of the remaining members. + \note Constant time complexity. + */ + MemberIterator RemoveMember(MemberIterator m) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(data_.o.size > 0); + RAPIDJSON_ASSERT(data_.o.members != 0); + RAPIDJSON_ASSERT(m >= MemberBegin() && m < MemberEnd()); + + MemberIterator last(data_.o.members + (data_.o.size - 1)); + if (data_.o.size > 1 && m != last) { + // Move the last one to this place + *m = *last; + } + else { + // Only one left, just destroy + m->~Member(); + } + --data_.o.size; + return m; + } + + //! Remove a member from an object by iterator. + /*! \param pos iterator to the member to remove + \pre IsObject() == true && \ref MemberBegin() <= \c pos < \ref MemberEnd() + \return Iterator following the removed element. + If the iterator \c pos refers to the last element, the \ref MemberEnd() iterator is returned. + \note This function preserves the relative order of the remaining object + members. If you do not need this, use the more efficient \ref RemoveMember(MemberIterator). + \note Linear time complexity. + */ + MemberIterator EraseMember(ConstMemberIterator pos) { + return EraseMember(pos, pos +1); + } + + //! Remove members in the range [first, last) from an object. + /*! \param first iterator to the first member to remove + \param last iterator following the last member to remove + \pre IsObject() == true && \ref MemberBegin() <= \c first <= \c last <= \ref MemberEnd() + \return Iterator following the last removed element. + \note This function preserves the relative order of the remaining object + members. + \note Linear time complexity. + */ + MemberIterator EraseMember(ConstMemberIterator first, ConstMemberIterator last) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(data_.o.size > 0); + RAPIDJSON_ASSERT(data_.o.members != 0); + RAPIDJSON_ASSERT(first >= MemberBegin()); + RAPIDJSON_ASSERT(first <= last); + RAPIDJSON_ASSERT(last <= MemberEnd()); + + MemberIterator pos = MemberBegin() + (first - MemberBegin()); + for (MemberIterator itr = pos; itr != last; ++itr) + itr->~Member(); + std::memmove(&*pos, &*last, (MemberEnd() - last) * sizeof(Member)); + data_.o.size -= (last - first); + return pos; + } + + //! Erase a member in object by its name. + /*! \param name Name of member to be removed. + \return Whether the member existed. + \note Linear time complexity. + */ + bool EraseMember(const Ch* name) { + GenericValue n(StringRef(name)); + return EraseMember(n); + } + +#if RAPIDJSON_HAS_STDSTRING + bool EraseMember(const std::basic_string& name) { return EraseMember(GenericValue(StringRef(name))); } +#endif + + template + bool EraseMember(const GenericValue& name) { + MemberIterator m = FindMember(name); + if (m != MemberEnd()) { + EraseMember(m); + return true; + } + else + return false; + } + + //@} + + //!@name Array + //@{ + + //! Set this value as an empty array. + /*! \post IsArray == true */ + GenericValue& SetArray() { this->~GenericValue(); new (this) GenericValue(kArrayType); return *this; } + + //! Get the number of elements in array. + SizeType Size() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size; } + + //! Get the capacity of array. + SizeType Capacity() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.capacity; } + + //! Check whether the array is empty. + bool Empty() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size == 0; } + + //! Remove all elements in the array. + /*! This function do not deallocate memory in the array, i.e. the capacity is unchanged. + \note Linear time complexity. + */ + void Clear() { + RAPIDJSON_ASSERT(IsArray()); + for (SizeType i = 0; i < data_.a.size; ++i) + data_.a.elements[i].~GenericValue(); + data_.a.size = 0; + } + + //! Get an element from array by index. + /*! \pre IsArray() == true + \param index Zero-based index of element. + \see operator[](T*) + */ + GenericValue& operator[](SizeType index) { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(index < data_.a.size); + return data_.a.elements[index]; + } + const GenericValue& operator[](SizeType index) const { return const_cast(*this)[index]; } + + //! Element iterator + /*! \pre IsArray() == true */ + ValueIterator Begin() { RAPIDJSON_ASSERT(IsArray()); return data_.a.elements; } + //! \em Past-the-end element iterator + /*! \pre IsArray() == true */ + ValueIterator End() { RAPIDJSON_ASSERT(IsArray()); return data_.a.elements + data_.a.size; } + //! Constant element iterator + /*! \pre IsArray() == true */ + ConstValueIterator Begin() const { return const_cast(*this).Begin(); } + //! Constant \em past-the-end element iterator + /*! \pre IsArray() == true */ + ConstValueIterator End() const { return const_cast(*this).End(); } + + //! Request the array to have enough capacity to store elements. + /*! \param newCapacity The capacity that the array at least need to have. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note Linear time complexity. + */ + GenericValue& Reserve(SizeType newCapacity, Allocator &allocator) { + RAPIDJSON_ASSERT(IsArray()); + if (newCapacity > data_.a.capacity) { + data_.a.elements = (GenericValue*)allocator.Realloc(data_.a.elements, data_.a.capacity * sizeof(GenericValue), newCapacity * sizeof(GenericValue)); + data_.a.capacity = newCapacity; + } + return *this; + } + + //! Append a GenericValue at the end of the array. + /*! \param value Value to be appended. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \pre IsArray() == true + \post value.IsNull() == true + \return The value itself for fluent API. + \note The ownership of \c value will be transferred to this array on success. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + \note Amortized constant time complexity. + */ + GenericValue& PushBack(GenericValue& value, Allocator& allocator) { + RAPIDJSON_ASSERT(IsArray()); + if (data_.a.size >= data_.a.capacity) + Reserve(data_.a.capacity == 0 ? kDefaultArrayCapacity : (data_.a.capacity + (data_.a.capacity + 1) / 2), allocator); + data_.a.elements[data_.a.size++].RawAssign(value); + return *this; + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericValue& PushBack(GenericValue&& value, Allocator& allocator) { + return PushBack(value, allocator); + } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + + //! Append a constant string reference at the end of the array. + /*! \param value Constant string reference to be appended. + \param allocator Allocator for reallocating memory. It must be the same one used previously. Commonly use GenericDocument::GetAllocator(). + \pre IsArray() == true + \return The value itself for fluent API. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + \note Amortized constant time complexity. + \see GenericStringRef + */ + GenericValue& PushBack(StringRefType value, Allocator& allocator) { + return (*this).template PushBack(value, allocator); + } + + //! Append a primitive value at the end of the array. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param value Value of primitive type T to be appended. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \pre IsArray() == true + \return The value itself for fluent API. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref PushBack(GenericValue&, Allocator&) or \ref + PushBack(StringRefType, Allocator&). + All other pointer types would implicitly convert to \c bool, + use an explicit cast instead, if needed. + \note Amortized constant time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericValue&)) + PushBack(T value, Allocator& allocator) { + GenericValue v(value); + return PushBack(v, allocator); + } + + //! Remove the last element in the array. + /*! + \note Constant time complexity. + */ + GenericValue& PopBack() { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(!Empty()); + data_.a.elements[--data_.a.size].~GenericValue(); + return *this; + } + + //! Remove an element of array by iterator. + /*! + \param pos iterator to the element to remove + \pre IsArray() == true && \ref Begin() <= \c pos < \ref End() + \return Iterator following the removed element. If the iterator pos refers to the last element, the End() iterator is returned. + \note Linear time complexity. + */ + ValueIterator Erase(ConstValueIterator pos) { + return Erase(pos, pos + 1); + } + + //! Remove elements in the range [first, last) of the array. + /*! + \param first iterator to the first element to remove + \param last iterator following the last element to remove + \pre IsArray() == true && \ref Begin() <= \c first <= \c last <= \ref End() + \return Iterator following the last removed element. + \note Linear time complexity. + */ + ValueIterator Erase(ConstValueIterator first, ConstValueIterator last) { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(data_.a.size > 0); + RAPIDJSON_ASSERT(data_.a.elements != 0); + RAPIDJSON_ASSERT(first >= Begin()); + RAPIDJSON_ASSERT(first <= last); + RAPIDJSON_ASSERT(last <= End()); + ValueIterator pos = Begin() + (first - Begin()); + for (ValueIterator itr = pos; itr != last; ++itr) + itr->~GenericValue(); + std::memmove(pos, last, (End() - last) * sizeof(GenericValue)); + data_.a.size -= (last - first); + return pos; + } + + //@} + + //!@name Number + //@{ + + int GetInt() const { RAPIDJSON_ASSERT(flags_ & kIntFlag); return data_.n.i.i; } + unsigned GetUint() const { RAPIDJSON_ASSERT(flags_ & kUintFlag); return data_.n.u.u; } + int64_t GetInt64() const { RAPIDJSON_ASSERT(flags_ & kInt64Flag); return data_.n.i64; } + uint64_t GetUint64() const { RAPIDJSON_ASSERT(flags_ & kUint64Flag); return data_.n.u64; } + + double GetDouble() const { + RAPIDJSON_ASSERT(IsNumber()); + if ((flags_ & kDoubleFlag) != 0) return data_.n.d; // exact type, no conversion. + if ((flags_ & kIntFlag) != 0) return data_.n.i.i; // int -> double + if ((flags_ & kUintFlag) != 0) return data_.n.u.u; // unsigned -> double + if ((flags_ & kInt64Flag) != 0) return (double)data_.n.i64; // int64_t -> double (may lose precision) + RAPIDJSON_ASSERT((flags_ & kUint64Flag) != 0); return (double)data_.n.u64; // uint64_t -> double (may lose precision) + } + + GenericValue& SetInt(int i) { this->~GenericValue(); new (this) GenericValue(i); return *this; } + GenericValue& SetUint(unsigned u) { this->~GenericValue(); new (this) GenericValue(u); return *this; } + GenericValue& SetInt64(int64_t i64) { this->~GenericValue(); new (this) GenericValue(i64); return *this; } + GenericValue& SetUint64(uint64_t u64) { this->~GenericValue(); new (this) GenericValue(u64); return *this; } + GenericValue& SetDouble(double d) { this->~GenericValue(); new (this) GenericValue(d); return *this; } + + //@} + + //!@name String + //@{ + + const Ch* GetString() const { RAPIDJSON_ASSERT(IsString()); return ((flags_ & kInlineStrFlag) ? data_.ss.str : data_.s.str); } + + //! Get the length of string. + /*! Since rapidjson permits "\\u0000" in the json string, strlen(v.GetString()) may not equal to v.GetStringLength(). + */ + SizeType GetStringLength() const { RAPIDJSON_ASSERT(IsString()); return ((flags_ & kInlineStrFlag) ? (data_.ss.GetLength()) : data_.s.length); } + + //! Set this value as a string without copying source string. + /*! This version has better performance with supplied length, and also support string containing null character. + \param s source string pointer. + \param length The length of source string, excluding the trailing null terminator. + \return The value itself for fluent API. + \post IsString() == true && GetString() == s && GetStringLength() == length + \see SetString(StringRefType) + */ + GenericValue& SetString(const Ch* s, SizeType length) { return SetString(StringRef(s, length)); } + + //! Set this value as a string without copying source string. + /*! \param s source string reference + \return The value itself for fluent API. + \post IsString() == true && GetString() == s && GetStringLength() == s.length + */ + GenericValue& SetString(StringRefType s) { this->~GenericValue(); SetStringRaw(s); return *this; } + + //! Set this value as a string by copying from source string. + /*! This version has better performance with supplied length, and also support string containing null character. + \param s source string. + \param length The length of source string, excluding the trailing null terminator. + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s && strcmp(GetString(),s) == 0 && GetStringLength() == length + */ + GenericValue& SetString(const Ch* s, SizeType length, Allocator& allocator) { this->~GenericValue(); SetStringRaw(StringRef(s, length), allocator); return *this; } + + //! Set this value as a string by copying from source string. + /*! \param s source string. + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s && strcmp(GetString(),s) == 0 && GetStringLength() == length + */ + GenericValue& SetString(const Ch* s, Allocator& allocator) { return SetString(s, internal::StrLen(s), allocator); } + +#if RAPIDJSON_HAS_STDSTRING + //! Set this value as a string by copying from source string. + /*! \param s source string. + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s.data() && strcmp(GetString(),s.data() == 0 && GetStringLength() == s.size() + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + GenericValue& SetString(const std::basic_string& s, Allocator& allocator) { return SetString(s.data(), SizeType(s.size()), allocator); } +#endif + + //@} + + //! Generate events of this value to a Handler. + /*! This function adopts the GoF visitor pattern. + Typical usage is to output this JSON value as JSON text via Writer, which is a Handler. + It can also be used to deep clone this value via GenericDocument, which is also a Handler. + \tparam Handler type of handler. + \param handler An object implementing concept Handler. + */ + template + bool Accept(Handler& handler) const { + switch(GetType()) { + case kNullType: return handler.Null(); + case kFalseType: return handler.Bool(false); + case kTrueType: return handler.Bool(true); + + case kObjectType: + if (!handler.StartObject()) + return false; + for (ConstMemberIterator m = MemberBegin(); m != MemberEnd(); ++m) { + RAPIDJSON_ASSERT(m->name.IsString()); // User may change the type of name by MemberIterator. + if (!handler.Key(m->name.GetString(), m->name.GetStringLength(), (m->name.flags_ & kCopyFlag) != 0)) + return false; + if (!m->value.Accept(handler)) + return false; + } + return handler.EndObject(data_.o.size); + + case kArrayType: + if (!handler.StartArray()) + return false; + for (GenericValue* v = data_.a.elements; v != data_.a.elements + data_.a.size; ++v) + if (!v->Accept(handler)) + return false; + return handler.EndArray(data_.a.size); + + case kStringType: + return handler.String(GetString(), GetStringLength(), (flags_ & kCopyFlag) != 0); + + default: + RAPIDJSON_ASSERT(GetType() == kNumberType); + if (IsInt()) return handler.AddInt(data_.n.i.i); + else if (IsUint()) return handler.AddUint(data_.n.u.u); + else if (IsInt64()) return handler.AddInt64(data_.n.i64); + else if (IsUint64()) return handler.AddUint64(data_.n.u64); + else return handler.Double(data_.n.d); + } + } + +private: + template friend class GenericValue; + template friend class GenericDocument; + + enum { + kBoolFlag = 0x100, + kNumberFlag = 0x200, + kIntFlag = 0x400, + kUintFlag = 0x800, + kInt64Flag = 0x1000, + kUint64Flag = 0x2000, + kDoubleFlag = 0x4000, + kStringFlag = 0x100000, + kCopyFlag = 0x200000, + kInlineStrFlag = 0x400000, + + // Initial flags of different types. + kNullFlag = kNullType, + kTrueFlag = kTrueType | kBoolFlag, + kFalseFlag = kFalseType | kBoolFlag, + kNumberIntFlag = kNumberType | kNumberFlag | kIntFlag | kInt64Flag, + kNumberUintFlag = kNumberType | kNumberFlag | kUintFlag | kUint64Flag | kInt64Flag, + kNumberInt64Flag = kNumberType | kNumberFlag | kInt64Flag, + kNumberUint64Flag = kNumberType | kNumberFlag | kUint64Flag, + kNumberDoubleFlag = kNumberType | kNumberFlag | kDoubleFlag, + kNumberAnyFlag = kNumberType | kNumberFlag | kIntFlag | kInt64Flag | kUintFlag | kUint64Flag | kDoubleFlag, + kConstStringFlag = kStringType | kStringFlag, + kCopyStringFlag = kStringType | kStringFlag | kCopyFlag, + kShortStringFlag = kStringType | kStringFlag | kCopyFlag | kInlineStrFlag, + kObjectFlag = kObjectType, + kArrayFlag = kArrayType, + + kTypeMask = 0xFF // bitwise-and with mask of 0xFF can be optimized by compiler + }; + + static const SizeType kDefaultArrayCapacity = 16; + static const SizeType kDefaultObjectCapacity = 16; + + struct String { + const Ch* str; + SizeType length; + unsigned hashcode; //!< reserved + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + // implementation detail: ShortString can represent zero-terminated strings up to MaxSize chars + // (excluding the terminating zero) and store a value to determine the length of the contained + // string in the last character str[LenPos] by storing "MaxSize - length" there. If the string + // to store has the maximal length of MaxSize then str[LenPos] will be 0 and therefore act as + // the string terminator as well. For getting the string length back from that value just use + // "MaxSize - str[LenPos]". + // This allows to store 11-chars strings in 32-bit mode and 15-chars strings in 64-bit mode + // inline (for `UTF8`-encoded strings). + struct ShortString { + enum { MaxChars = sizeof(String) / sizeof(Ch), MaxSize = MaxChars - 1, LenPos = MaxSize }; + Ch str[MaxChars]; + + inline static bool Usable(SizeType len) { return (MaxSize >= len); } + inline void SetLength(SizeType len) { str[LenPos] = (Ch)(MaxSize - len); } + inline SizeType GetLength() const { return (SizeType)(MaxSize - str[LenPos]); } + }; // at most as many bytes as "String" above => 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + // By using proper binary layout, retrieval of different integer types do not need conversions. + union Number { +#if RAPIDJSON_ENDIAN == RAPIDJSON_LITTLEENDIAN + struct I { + int i; + char padding[4]; + }i; + struct U { + unsigned u; + char padding2[4]; + }u; +#else + struct I { + char padding[4]; + int i; + }i; + struct U { + char padding2[4]; + unsigned u; + }u; +#endif + int64_t i64; + uint64_t u64; + double d; + }; // 8 bytes + + struct Object { + Member* members; + SizeType size; + SizeType capacity; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + struct Array { + GenericValue* elements; + SizeType size; + SizeType capacity; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + union Data { + String s; + ShortString ss; + Number n; + Object o; + Array a; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + // Initialize this value as array with initial data, without calling destructor. + void SetArrayRaw(GenericValue* values, SizeType count, Allocator& allocator) { + flags_ = kArrayFlag; + if (count) { + data_.a.elements = (GenericValue*)allocator.Malloc(count * sizeof(GenericValue)); + std::memcpy(static_cast(data_.a.elements), values, count * sizeof(GenericValue)); + } + else + data_.a.elements = NULL; + data_.a.size = data_.a.capacity = count; + } + + //! Initialize this value as object with initial data, without calling destructor. + void SetObjectRaw(Member* members, SizeType count, Allocator& allocator) { + flags_ = kObjectFlag; + if (count) { + data_.o.members = (Member*)allocator.Malloc(count * sizeof(Member)); + std::memcpy(static_cast(data_.o.members), members, count * sizeof(Member)); + } + else + data_.o.members = NULL; + data_.o.size = data_.o.capacity = count; + } + + //! Initialize this value as constant string, without calling destructor. + void SetStringRaw(StringRefType s) RAPIDJSON_NOEXCEPT { + flags_ = kConstStringFlag; + data_.s.str = s; + data_.s.length = s.length; + } + + //! Initialize this value as copy string with initial data, without calling destructor. + void SetStringRaw(StringRefType s, Allocator& allocator) { + Ch* str = NULL; + if(ShortString::Usable(s.length)) { + flags_ = kShortStringFlag; + data_.ss.SetLength(s.length); + str = data_.ss.str; + } else { + flags_ = kCopyStringFlag; + data_.s.length = s.length; + str = (Ch *)allocator.Malloc((s.length + 1) * sizeof(Ch)); + data_.s.str = str; + } + std::memcpy(str, s, s.length * sizeof(Ch)); + str[s.length] = '\0'; + } + + //! Assignment without calling destructor + void RawAssign(GenericValue& rhs) RAPIDJSON_NOEXCEPT { + data_ = rhs.data_; + flags_ = rhs.flags_; + rhs.flags_ = kNullFlag; + } + + template + bool StringEqual(const GenericValue& rhs) const { + RAPIDJSON_ASSERT(IsString()); + RAPIDJSON_ASSERT(rhs.IsString()); + + const SizeType len1 = GetStringLength(); + const SizeType len2 = rhs.GetStringLength(); + if(len1 != len2) { return false; } + + const Ch* const str1 = GetString(); + const Ch* const str2 = rhs.GetString(); + if(str1 == str2) { return true; } // fast path for constant string + + return (std::memcmp(str1, str2, sizeof(Ch) * len1) == 0); + } + + Data data_; + unsigned flags_; +}; + +//! GenericValue with UTF8 encoding +typedef GenericValue > Value; + +/////////////////////////////////////////////////////////////////////////////// +// GenericDocument + +//! A document for parsing JSON text as DOM. +/*! + \note implements Handler concept + \tparam Encoding Encoding for both parsing and string storage. + \tparam Allocator Allocator for allocating memory for the DOM + \tparam StackAllocator Allocator for allocating memory for stack during parsing. + \warning Although GenericDocument inherits from GenericValue, the API does \b not provide any virtual functions, especially no virtual destructor. To avoid memory leaks, do not \c delete a GenericDocument object via a pointer to a GenericValue. +*/ +template , typename StackAllocator = CrtAllocator> +class GenericDocument : public GenericValue { +public: + typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. + typedef GenericValue ValueType; //!< Value type of the document. + typedef Allocator AllocatorType; //!< Allocator type from template parameter. + + //! Constructor + /*! \param allocator Optional allocator for allocating memory. + \param stackCapacity Optional initial capacity of stack in bytes. + \param stackAllocator Optional allocator for allocating memory for stack. + */ + GenericDocument(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity, StackAllocator* stackAllocator = 0) : + allocator_(allocator), ownAllocator_(0), stack_(stackAllocator, stackCapacity), parseResult_() + { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericDocument(GenericDocument&& rhs) RAPIDJSON_NOEXCEPT + : ValueType(std::move(rhs)), + allocator_(rhs.allocator_), + ownAllocator_(rhs.ownAllocator_), + stack_(std::move(rhs.stack_)), + parseResult_(rhs.parseResult_) + { + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.parseResult_ = ParseResult(); + } +#endif + + ~GenericDocument() { + Destroy(); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move assignment in C++11 + GenericDocument& operator=(GenericDocument&& rhs) RAPIDJSON_NOEXCEPT + { + // The cast to ValueType is necessary here, because otherwise it would + // attempt to call GenericValue's templated assignment operator. + ValueType::operator=(std::forward(rhs)); + + // Calling the destructor here would prematurely call stack_'s destructor + Destroy(); + + allocator_ = rhs.allocator_; + ownAllocator_ = rhs.ownAllocator_; + stack_ = std::move(rhs.stack_); + parseResult_ = rhs.parseResult_; + + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.parseResult_ = ParseResult(); + + return *this; + } +#endif + + //!@name Parse from stream + //!@{ + + //! Parse JSON text from an input stream (with Encoding conversion) + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam SourceEncoding Encoding of input stream + \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseStream(InputStream& is) { + ValueType::SetNull(); // Remove existing root if exist + GenericReader reader(&stack_.GetAllocator()); + ClearStackOnExit scope(*this); + parseResult_ = reader.template Parse(is, *this); + if (parseResult_) { + RAPIDJSON_ASSERT(stack_.GetSize() == sizeof(ValueType)); // Got one and only one root object + this->RawAssign(*stack_.template Pop(1)); // Add this-> to prevent issue 13. + } + return *this; + } + + //! Parse JSON text from an input stream + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseStream(InputStream& is) { + return ParseStream(is); + } + + //! Parse JSON text from an input stream (with \ref kParseDefaultFlags) + /*! \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseStream(InputStream& is) { + return ParseStream(is); + } + //!@} + + //!@name Parse in-place from mutable string + //!@{ + + //! Parse JSON text from a mutable string + /*! \tparam parseFlags Combination of \ref ParseFlag. + \param str Mutable zero-terminated string to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseInsitu(Ch* str) { + GenericInsituStringStream s(str); + return ParseStream(s); + } + + //! Parse JSON text from a mutable string (with \ref kParseDefaultFlags) + /*! \param str Mutable zero-terminated string to be parsed. + \return The document itself for fluent API. + */ + GenericDocument& ParseInsitu(Ch* str) { + return ParseInsitu(str); + } + //!@} + + //!@name Parse from read-only string + //!@{ + + //! Parse JSON text from a read-only string (with Encoding conversion) + /*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag). + \tparam SourceEncoding Transcoding from input Encoding + \param str Read-only zero-terminated string to be parsed. + */ + template + GenericDocument& Parse(const Ch* str) { + RAPIDJSON_ASSERT(!(parseFlags & kParseInsituFlag)); + GenericStringStream s(str); + return ParseStream(s); + } + + //! Parse JSON text from a read-only string + /*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag). + \param str Read-only zero-terminated string to be parsed. + */ + template + GenericDocument& Parse(const Ch* str) { + return Parse(str); + } + + //! Parse JSON text from a read-only string (with \ref kParseDefaultFlags) + /*! \param str Read-only zero-terminated string to be parsed. + */ + GenericDocument& Parse(const Ch* str) { + return Parse(str); + } + //!@} + + //!@name Handling parse errors + //!@{ + + //! Whether a parse error has occured in the last parsing. + bool HasParseError() const { return parseResult_.IsError(); } + + //! Get the \ref ParseErrorCode of last parsing. + ParseErrorCode GetParseError() const { return parseResult_.Code(); } + + //! Get the position of last parsing error in input, 0 otherwise. + size_t GetErrorOffset() const { return parseResult_.Offset(); } + + //!@} + + //! Get the allocator of this document. + Allocator& GetAllocator() { return *allocator_; } + + //! Get the capacity of stack in bytes. + size_t GetStackCapacity() const { return stack_.GetCapacity(); } + +private: + // clear stack on any exit from ParseStream, e.g. due to exception + struct ClearStackOnExit { + explicit ClearStackOnExit(GenericDocument& d) : d_(d) {} + ~ClearStackOnExit() { d_.ClearStack(); } + private: + ClearStackOnExit(const ClearStackOnExit&); + ClearStackOnExit& operator=(const ClearStackOnExit&); + GenericDocument& d_; + }; + + // callers of the following private Handler functions + template friend class GenericReader; // for parsing + template friend class GenericValue; // for deep copying + +// NOTE(jrj): Make the following methods public to enable outside parser/writer +public: + // Implementation of Handler + bool Null() { new (stack_.template Push()) ValueType(); return true; } + bool Bool(bool b) { new (stack_.template Push()) ValueType(b); return true; } + bool AddInt(int i) { new (stack_.template Push()) ValueType(i); return true; } + bool AddUint(unsigned i) { new (stack_.template Push()) ValueType(i); return true; } + bool AddInt64(int64_t i) { new (stack_.template Push()) ValueType(i); return true; } + bool AddUint64(uint64_t i) { new (stack_.template Push()) ValueType(i); return true; } + bool Double(double d) { new (stack_.template Push()) ValueType(d); return true; } + + bool String(const Ch* str, SizeType length, bool copy) { + if (copy) + new (stack_.template Push()) ValueType(str, length, GetAllocator()); + else + new (stack_.template Push()) ValueType(str, length); + return true; + } + + bool StartObject() { new (stack_.template Push()) ValueType(kObjectType); return true; } + + bool Key(const Ch* str, SizeType length, bool copy) { return String(str, length, copy); } + + bool EndObject(SizeType memberCount) { + typename ValueType::Member* members = stack_.template Pop(memberCount); + stack_.template Top()->SetObjectRaw(members, (SizeType)memberCount, GetAllocator()); + return true; + } + + bool StartArray() { new (stack_.template Push()) ValueType(kArrayType); return true; } + + bool EndArray(SizeType elementCount) { + ValueType* elements = stack_.template Pop(elementCount); + stack_.template Top()->SetArrayRaw(elements, elementCount, GetAllocator()); + return true; + } + + // NOTE(jrj): Extract the last element from the stack and move into `this' GenericDocument + void FinalizeHandler() { + if (StackSize() > 0) { + ValueType* rhs = stack_.template Pop(1); + this->RawAssign(*rhs); // Transfer ownership + } + RAPIDJSON_ASSERT(stack_.Empty()); + } + + size_t StackSize() const { return stack_.GetSize(); } + +private: + //! Prohibit copying + GenericDocument(const GenericDocument&); + //! Prohibit assignment + GenericDocument& operator=(const GenericDocument&); + + void ClearStack() { + if (Allocator::kNeedFree) + while (stack_.GetSize() > 0) // Here assumes all elements in stack array are GenericValue (Member is actually 2 GenericValue objects) + (stack_.template Pop(1))->~ValueType(); + else + stack_.Clear(); + stack_.ShrinkToFit(); + } + + void Destroy() { + RAPIDJSON_DELETE(ownAllocator_); + } + + static const size_t kDefaultStackCapacity = 1024; + Allocator* allocator_; + Allocator* ownAllocator_; + internal::Stack stack_; + ParseResult parseResult_; +}; + +//! GenericDocument with UTF8 encoding +typedef GenericDocument > Document; + +// defined here due to the dependency on GenericDocument +template +template +inline +GenericValue::GenericValue(const GenericValue& rhs, Allocator& allocator) +{ + switch (rhs.GetType()) { + case kObjectType: + case kArrayType: { // perform deep copy via SAX Handler + GenericDocument d(&allocator); + rhs.Accept(d); + RawAssign(*d.stack_.template Pop(1)); + } + break; + case kStringType: + if (rhs.flags_ == kConstStringFlag) { + flags_ = rhs.flags_; + data_ = *reinterpret_cast(&rhs.data_); + } else { + SetStringRaw(StringRef(rhs.GetString(), rhs.GetStringLength()), allocator); + } + break; + default: // kNumberType, kTrueType, kFalseType, kNullType + flags_ = rhs.flags_; + data_ = *reinterpret_cast(&rhs.data_); + } +} + +BUTIL_RAPIDJSON_NAMESPACE_END + +#if defined(_MSC_VER) || defined(__GNUC__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_DOCUMENT_H_ diff --git a/src/butil/third_party/rapidjson/encodedstream.h b/src/butil/third_party/rapidjson/encodedstream.h new file mode 100644 index 0000000..b08d7bc --- /dev/null +++ b/src/butil/third_party/rapidjson/encodedstream.h @@ -0,0 +1,261 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ENCODEDSTREAM_H_ +#define RAPIDJSON_ENCODEDSTREAM_H_ + +#include "rapidjson.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN + +//! Input byte stream wrapper with a statically bound encoding. +/*! + \tparam Encoding The interpretation of encoding of the stream. Either UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE. + \tparam InputByteStream Type of input byte stream. For example, FileReadStream. +*/ +template +class EncodedInputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); +public: + typedef typename Encoding::Ch Ch; + + EncodedInputStream(InputByteStream& is) : is_(is) { + current_ = Encoding::TakeBOM(is_); + } + + Ch Peek() const { return current_; } + Ch Take() { Ch c = current_; current_ = Encoding::Take(is_); return c; } + size_t Tell() const { return is_.Tell(); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + EncodedInputStream(const EncodedInputStream&); + EncodedInputStream& operator=(const EncodedInputStream&); + + InputByteStream& is_; + Ch current_; +}; + +//! Output byte stream wrapper with statically bound encoding. +/*! + \tparam Encoding The interpretation of encoding of the stream. Either UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE. + \tparam InputByteStream Type of input byte stream. For example, FileWriteStream. +*/ +template +class EncodedOutputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); +public: + typedef typename Encoding::Ch Ch; + + EncodedOutputStream(OutputByteStream& os, bool putBOM = true) : os_(os) { + if (putBOM) + Encoding::PutBOM(os_); + } + + void Put(Ch c) { Encoding::Put(os_, c); } + void Flush() { os_.Flush(); } + + // Not implemented + Ch Peek() const { RAPIDJSON_ASSERT(false); } + Ch Take() { RAPIDJSON_ASSERT(false); } + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + EncodedOutputStream(const EncodedOutputStream&); + EncodedOutputStream& operator=(const EncodedOutputStream&); + + OutputByteStream& os_; +}; + +#define RAPIDJSON_ENCODINGS_FUNC(x) UTF8::x, UTF16LE::x, UTF16BE::x, UTF32LE::x, UTF32BE::x + +//! Input stream wrapper with dynamically bound encoding and automatic encoding detection. +/*! + \tparam CharType Type of character for reading. + \tparam InputByteStream type of input byte stream to be wrapped. +*/ +template +class AutoUTFInputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); +public: + typedef CharType Ch; + + //! Constructor. + /*! + \param is input stream to be wrapped. + \param type UTF encoding type if it is not detected from the stream. + */ + AutoUTFInputStream(InputByteStream& is, UTFType type = kUTF8) : is_(&is), type_(type), hasBOM_(false) { + RAPIDJSON_ASSERT(type >= kUTF8 && type <= kUTF32BE); + DetectType(); + static const TakeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Take) }; + takeFunc_ = f[type_]; + current_ = takeFunc_(*is_); + } + + UTFType GetType() const { return type_; } + bool HasBOM() const { return hasBOM_; } + + Ch Peek() const { return current_; } + Ch Take() { Ch c = current_; current_ = takeFunc_(*is_); return c; } + size_t Tell() const { return is_->Tell(); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + AutoUTFInputStream(const AutoUTFInputStream&); + AutoUTFInputStream& operator=(const AutoUTFInputStream&); + + // Detect encoding type with BOM or RFC 4627 + void DetectType() { + // BOM (Byte Order Mark): + // 00 00 FE FF UTF-32BE + // FF FE 00 00 UTF-32LE + // FE FF UTF-16BE + // FF FE UTF-16LE + // EF BB BF UTF-8 + + const unsigned char* c = (const unsigned char *)is_->Peek4(); + if (!c) + return; + + unsigned bom = c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24); + hasBOM_ = false; + if (bom == 0xFFFE0000) { type_ = kUTF32BE; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); is_->Take(); } + else if (bom == 0x0000FEFF) { type_ = kUTF32LE; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); is_->Take(); } + else if ((bom & 0xFFFF) == 0xFFFE) { type_ = kUTF16BE; hasBOM_ = true; is_->Take(); is_->Take(); } + else if ((bom & 0xFFFF) == 0xFEFF) { type_ = kUTF16LE; hasBOM_ = true; is_->Take(); is_->Take(); } + else if ((bom & 0xFFFFFF) == 0xBFBBEF) { type_ = kUTF8; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); } + + // RFC 4627: Section 3 + // "Since the first two characters of a JSON text will always be ASCII + // characters [RFC0020], it is possible to determine whether an octet + // stream is UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or LE) by looking + // at the pattern of nulls in the first four octets." + // 00 00 00 xx UTF-32BE + // 00 xx 00 xx UTF-16BE + // xx 00 00 00 UTF-32LE + // xx 00 xx 00 UTF-16LE + // xx xx xx xx UTF-8 + + if (!hasBOM_) { + unsigned pattern = (c[0] ? 1 : 0) | (c[1] ? 2 : 0) | (c[2] ? 4 : 0) | (c[3] ? 8 : 0); + switch (pattern) { + case 0x08: type_ = kUTF32BE; break; + case 0x0A: type_ = kUTF16BE; break; + case 0x01: type_ = kUTF32LE; break; + case 0x05: type_ = kUTF16LE; break; + case 0x0F: type_ = kUTF8; break; + default: break; // Use type defined by user. + } + } + + // Runtime check whether the size of character type is sufficient. It only perform checks with assertion. + if (type_ == kUTF16LE || type_ == kUTF16BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 2); + if (type_ == kUTF32LE || type_ == kUTF32BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 4); + } + + typedef Ch (*TakeFunc)(InputByteStream& is); + InputByteStream* is_; + UTFType type_; + Ch current_; + TakeFunc takeFunc_; + bool hasBOM_; +}; + +//! Output stream wrapper with dynamically bound encoding and automatic encoding detection. +/*! + \tparam CharType Type of character for writing. + \tparam InputByteStream type of output byte stream to be wrapped. +*/ +template +class AutoUTFOutputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); +public: + typedef CharType Ch; + + //! Constructor. + /*! + \param os output stream to be wrapped. + \param type UTF encoding type. + \param putBOM Whether to write BOM at the beginning of the stream. + */ + AutoUTFOutputStream(OutputByteStream& os, UTFType type, bool putBOM) : os_(&os), type_(type) { + RAPIDJSON_ASSERT(type >= kUTF8 && type <= kUTF32BE); + + // Runtime check whether the size of character type is sufficient. It only perform checks with assertion. + if (type_ == kUTF16LE || type_ == kUTF16BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 2); + if (type_ == kUTF32LE || type_ == kUTF32BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 4); + + static const PutFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Put) }; + putFunc_ = f[type_]; + + if (putBOM) + PutBOM(); + } + + UTFType GetType() const { return type_; } + + void Put(Ch c) { putFunc_(*os_, c); } + void Flush() { os_->Flush(); } + + // Not implemented + Ch Peek() const { RAPIDJSON_ASSERT(false); } + Ch Take() { RAPIDJSON_ASSERT(false); } + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + AutoUTFOutputStream(const AutoUTFOutputStream&); + AutoUTFOutputStream& operator=(const AutoUTFOutputStream&); + + void PutBOM() { + typedef void (*PutBOMFunc)(OutputByteStream&); + static const PutBOMFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(PutBOM) }; + f[type_](*os_); + } + + typedef void (*PutFunc)(OutputByteStream&, Ch); + + OutputByteStream* os_; + UTFType type_; + PutFunc putFunc_; +}; + +#undef RAPIDJSON_ENCODINGS_FUNC + +BUTIL_RAPIDJSON_NAMESPACE_END + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/src/butil/third_party/rapidjson/encodings.h b/src/butil/third_party/rapidjson/encodings.h new file mode 100644 index 0000000..d6859ae --- /dev/null +++ b/src/butil/third_party/rapidjson/encodings.h @@ -0,0 +1,625 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ENCODINGS_H_ +#define RAPIDJSON_ENCODINGS_H_ + +#include "rapidjson.h" + +#ifdef _MSC_VER +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4244) // conversion from 'type1' to 'type2', possible loss of data +RAPIDJSON_DIAG_OFF(4702) // unreachable code +#elif defined(__GNUC__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +RAPIDJSON_DIAG_OFF(overflow) +#endif + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Encoding + +/*! \class rapidjson::Encoding + \brief Concept for encoding of Unicode characters. + +\code +concept Encoding { + typename Ch; //! Type of character. A "character" is actually a code unit in unicode's definition. + + enum { supportUnicode = 1 }; // or 0 if not supporting unicode + + //! \brief Encode a Unicode codepoint to an output stream. + //! \param os Output stream. + //! \param codepoint An unicode codepoint, ranging from 0x0 to 0x10FFFF inclusively. + template + static void Encode(OutputStream& os, unsigned codepoint); + + //! \brief Decode a Unicode codepoint from an input stream. + //! \param is Input stream. + //! \param codepoint Output of the unicode codepoint. + //! \return true if a valid codepoint can be decoded from the stream. + template + static bool Decode(InputStream& is, unsigned* codepoint); + + //! \brief Validate one Unicode codepoint from an encoded stream. + //! \param is Input stream to obtain codepoint. + //! \param os Output for copying one codepoint. + //! \return true if it is valid. + //! \note This function just validating and copying the codepoint without actually decode it. + template + static bool Validate(InputStream& is, OutputStream& os); + + // The following functions are deal with byte streams. + + //! Take a character from input byte stream, skip BOM if exist. + template + static CharType TakeBOM(InputByteStream& is); + + //! Take a character from input byte stream. + template + static Ch Take(InputByteStream& is); + + //! Put BOM to output byte stream. + template + static void PutBOM(OutputByteStream& os); + + //! Put a character to output byte stream. + template + static void Put(OutputByteStream& os, Ch c); +}; +\endcode +*/ + +/////////////////////////////////////////////////////////////////////////////// +// UTF8 + +//! UTF-8 encoding. +/*! http://en.wikipedia.org/wiki/UTF-8 + http://tools.ietf.org/html/rfc3629 + \tparam CharType Code unit for storing 8-bit UTF-8 data. Default is char. + \note implements Encoding concept +*/ +template +struct UTF8 { + typedef CharType Ch; + + enum { supportUnicode = 1 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + if (codepoint <= 0x7F) + os.Put(static_cast(codepoint & 0xFF)); + else if (codepoint <= 0x7FF) { + os.Put(static_cast(0xC0 | ((codepoint >> 6) & 0xFF))); + os.Put(static_cast(0x80 | ((codepoint & 0x3F)))); + } + else if (codepoint <= 0xFFFF) { + os.Put(static_cast(0xE0 | ((codepoint >> 12) & 0xFF))); + os.Put(static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + os.Put(static_cast(0x80 | (codepoint & 0x3F))); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + os.Put(static_cast(0xF0 | ((codepoint >> 18) & 0xFF))); + os.Put(static_cast(0x80 | ((codepoint >> 12) & 0x3F))); + os.Put(static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + os.Put(static_cast(0x80 | (codepoint & 0x3F))); + } + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { +#define COPY() c = is.Take(); *codepoint = (*codepoint << 6) | ((unsigned char)c & 0x3Fu) +#define TRANS(mask) result &= ((GetRange((unsigned char)c) & mask) != 0) +#define TAIL() COPY(); TRANS(0x70) + Ch c = is.Take(); + if (!(c & 0x80)) { + *codepoint = (unsigned char)c; + return true; + } + + unsigned char type = GetRange((unsigned char)c); + *codepoint = (0xFF >> type) & (unsigned char)c; + bool result = true; + switch (type) { + case 2: TAIL(); return result; + case 3: TAIL(); TAIL(); return result; + case 4: COPY(); TRANS(0x50); TAIL(); return result; + case 5: COPY(); TRANS(0x10); TAIL(); TAIL(); return result; + case 6: TAIL(); TAIL(); TAIL(); return result; + case 10: COPY(); TRANS(0x20); TAIL(); return result; + case 11: COPY(); TRANS(0x60); TAIL(); TAIL(); return result; + default: return false; + } +#undef COPY +#undef TRANS +#undef TAIL + } + + template + static bool Validate(InputStream& is, OutputStream& os) { +#define COPY() os.Put(c = is.Take()) +#define TRANS(mask) result &= ((GetRange((unsigned char)c) & mask) != 0) +#define TAIL() COPY(); TRANS(0x70) + Ch c; + COPY(); + if (!(c & 0x80)) + return true; + + bool result = true; + switch (GetRange((unsigned char)c)) { + case 2: TAIL(); return result; + case 3: TAIL(); TAIL(); return result; + case 4: COPY(); TRANS(0x50); TAIL(); return result; + case 5: COPY(); TRANS(0x10); TAIL(); TAIL(); return result; + case 6: TAIL(); TAIL(); TAIL(); return result; + case 10: COPY(); TRANS(0x20); TAIL(); return result; + case 11: COPY(); TRANS(0x60); TAIL(); TAIL(); return result; + default: return false; + } +#undef COPY +#undef TRANS +#undef TAIL + } + + static unsigned char GetRange(unsigned char c) { + // Referring to DFA of http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + // With new mapping 1 -> 0x10, 7 -> 0x20, 9 -> 0x40, such that AND operation can test multiple types. + static const unsigned char type[] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, + 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + }; + return type[c]; + } + + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + Ch c = Take(is); + if ((unsigned char)c != 0xEFu) return c; + c = is.Take(); + if ((unsigned char)c != 0xBBu) return c; + c = is.Take(); + if ((unsigned char)c != 0xBFu) return c; + c = is.Take(); + return c; + } + + template + static Ch Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + return is.Take(); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(0xEFu); os.Put(0xBBu); os.Put(0xBFu); + } + + template + static void Put(OutputByteStream& os, Ch c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(c)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// UTF16 + +//! UTF-16 encoding. +/*! http://en.wikipedia.org/wiki/UTF-16 + http://tools.ietf.org/html/rfc2781 + \tparam CharType Type for storing 16-bit UTF-16 data. Default is wchar_t. C++11 may use char16_t instead. + \note implements Encoding concept + + \note For in-memory access, no need to concern endianness. The code units and code points are represented by CPU's endianness. + For streaming, use UTF16LE and UTF16BE, which handle endianness. +*/ +template +struct UTF16 { + typedef CharType Ch; + RAPIDJSON_STATIC_ASSERT(sizeof(Ch) >= 2); + + enum { supportUnicode = 1 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); + if (codepoint <= 0xFFFF) { + RAPIDJSON_ASSERT(codepoint < 0xD800 || codepoint > 0xDFFF); // Code point itself cannot be surrogate pair + os.Put(static_cast(codepoint)); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + unsigned v = codepoint - 0x10000; + os.Put(static_cast((v >> 10) | 0xD800)); + os.Put((v & 0x3FF) | 0xDC00); + } + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 2); + Ch c = is.Take(); + if (c < 0xD800 || c > 0xDFFF) { + *codepoint = c; + return true; + } + else if (c <= 0xDBFF) { + *codepoint = (c & 0x3FF) << 10; + c = is.Take(); + *codepoint |= (c & 0x3FF); + *codepoint += 0x10000; + return c >= 0xDC00 && c <= 0xDFFF; + } + return false; + } + + template + static bool Validate(InputStream& is, OutputStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 2); + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); + Ch c; + os.Put(c = is.Take()); + if (c < 0xD800 || c > 0xDFFF) + return true; + else if (c <= 0xDBFF) { + os.Put(c = is.Take()); + return c >= 0xDC00 && c <= 0xDFFF; + } + return false; + } +}; + +//! UTF-16 little endian encoding. +template +struct UTF16LE : UTF16 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return (unsigned short)c == 0xFEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = (unsigned char)is.Take(); + c |= (unsigned char)is.Take() << 8; + return c; + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(0xFFu); os.Put(0xFEu); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(c & 0xFFu); + os.Put((c >> 8) & 0xFFu); + } +}; + +//! UTF-16 big endian encoding. +template +struct UTF16BE : UTF16 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return (unsigned short)c == 0xFEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = (unsigned char)is.Take() << 8; + c |= (unsigned char)is.Take(); + return c; + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(0xFEu); os.Put(0xFFu); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put((c >> 8) & 0xFFu); + os.Put(c & 0xFFu); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// UTF32 + +//! UTF-32 encoding. +/*! http://en.wikipedia.org/wiki/UTF-32 + \tparam CharType Type for storing 32-bit UTF-32 data. Default is unsigned. C++11 may use char32_t instead. + \note implements Encoding concept + + \note For in-memory access, no need to concern endianness. The code units and code points are represented by CPU's endianness. + For streaming, use UTF32LE and UTF32BE, which handle endianness. +*/ +template +struct UTF32 { + typedef CharType Ch; + RAPIDJSON_STATIC_ASSERT(sizeof(Ch) >= 4); + + enum { supportUnicode = 1 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 4); + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + os.Put(codepoint); + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 4); + Ch c = is.Take(); + *codepoint = c; + return c <= 0x10FFFF; + } + + template + static bool Validate(InputStream& is, OutputStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 4); + Ch c; + os.Put(c = is.Take()); + return c <= 0x10FFFF; + } +}; + +//! UTF-32 little endian enocoding. +template +struct UTF32LE : UTF32 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return (unsigned)c == 0x0000FEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = (unsigned char)is.Take(); + c |= (unsigned char)is.Take() << 8; + c |= (unsigned char)is.Take() << 16; + c |= (unsigned char)is.Take() << 24; + return c; + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(0xFFu); os.Put(0xFEu); os.Put(0x00u); os.Put(0x00u); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(c & 0xFFu); + os.Put((c >> 8) & 0xFFu); + os.Put((c >> 16) & 0xFFu); + os.Put((c >> 24) & 0xFFu); + } +}; + +//! UTF-32 big endian encoding. +template +struct UTF32BE : UTF32 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return (unsigned)c == 0x0000FEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = (unsigned char)is.Take() << 24; + c |= (unsigned char)is.Take() << 16; + c |= (unsigned char)is.Take() << 8; + c |= (unsigned char)is.Take(); + return c; + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(0x00u); os.Put(0x00u); os.Put(0xFEu); os.Put(0xFFu); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put((c >> 24) & 0xFFu); + os.Put((c >> 16) & 0xFFu); + os.Put((c >> 8) & 0xFFu); + os.Put(c & 0xFFu); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// ASCII + +//! ASCII encoding. +/*! http://en.wikipedia.org/wiki/ASCII + \tparam CharType Code unit for storing 7-bit ASCII data. Default is char. + \note implements Encoding concept +*/ +template +struct ASCII { + typedef CharType Ch; + + enum { supportUnicode = 0 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + RAPIDJSON_ASSERT(codepoint <= 0x7F); + os.Put(static_cast(codepoint & 0xFF)); + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { + unsigned char c = static_cast(is.Take()); + *codepoint = c; + return c <= 0X7F; + } + + template + static bool Validate(InputStream& is, OutputStream& os) { + unsigned char c = is.Take(); + os.Put(c); + return c <= 0x7F; + } + + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + Ch c = Take(is); + return c; + } + + template + static Ch Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + return is.Take(); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + (void)os; + } + + template + static void Put(OutputByteStream& os, Ch c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(c)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// AutoUTF + +//! Runtime-specified UTF encoding type of a stream. +enum UTFType { + kUTF8 = 0, //!< UTF-8. + kUTF16LE = 1, //!< UTF-16 little endian. + kUTF16BE = 2, //!< UTF-16 big endian. + kUTF32LE = 3, //!< UTF-32 little endian. + kUTF32BE = 4 //!< UTF-32 big endian. +}; + +//! Dynamically select encoding according to stream's runtime-specified UTF encoding type. +/*! \note This class can be used with AutoUTFInputtStream and AutoUTFOutputStream, which provides GetType(). +*/ +template +struct AutoUTF { + typedef CharType Ch; + + enum { supportUnicode = 1 }; + +#define RAPIDJSON_ENCODINGS_FUNC(x) UTF8::x, UTF16LE::x, UTF16BE::x, UTF32LE::x, UTF32BE::x + + template + RAPIDJSON_FORCEINLINE static void Encode(OutputStream& os, unsigned codepoint) { + typedef void (*EncodeFunc)(OutputStream&, unsigned); + static const EncodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Encode) }; + (*f[os.GetType()])(os, codepoint); + } + + template + RAPIDJSON_FORCEINLINE static bool Decode(InputStream& is, unsigned* codepoint) { + typedef bool (*DecodeFunc)(InputStream&, unsigned*); + static const DecodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Decode) }; + return (*f[is.GetType()])(is, codepoint); + } + + template + RAPIDJSON_FORCEINLINE static bool Validate(InputStream& is, OutputStream& os) { + typedef bool (*ValidateFunc)(InputStream&, OutputStream&); + static const ValidateFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Validate) }; + return (*f[is.GetType()])(is, os); + } + +#undef RAPIDJSON_ENCODINGS_FUNC +}; + +/////////////////////////////////////////////////////////////////////////////// +// Transcoder + +//! Encoding conversion. +template +struct Transcoder { + //! Take one Unicode codepoint from source encoding, convert it to target encoding and put it to the output stream. + template + RAPIDJSON_FORCEINLINE static bool Transcode(InputStream& is, OutputStream& os) { + unsigned codepoint; + if (!SourceEncoding::Decode(is, &codepoint)) + return false; + TargetEncoding::Encode(os, codepoint); + return true; + } + + //! Validate one Unicode codepoint from an encoded stream. + template + RAPIDJSON_FORCEINLINE static bool Validate(InputStream& is, OutputStream& os) { + return Transcode(is, os); // Since source/target encoding is different, must transcode. + } +}; + +//! Specialization of Transcoder with same source and target encoding. +template +struct Transcoder { + template + RAPIDJSON_FORCEINLINE static bool Transcode(InputStream& is, OutputStream& os) { + os.Put(is.Take()); // Just copy one code unit. This semantic is different from primary template class. + return true; + } + + template + RAPIDJSON_FORCEINLINE static bool Validate(InputStream& is, OutputStream& os) { + return Encoding::Validate(is, os); // source/target encoding are the same + } +}; + +BUTIL_RAPIDJSON_NAMESPACE_END + +#if defined(__GNUC__) || defined(_MSV_VER) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_ENCODINGS_H_ diff --git a/src/butil/third_party/rapidjson/error/en.h b/src/butil/third_party/rapidjson/error/en.h new file mode 100644 index 0000000..c1d2c1a --- /dev/null +++ b/src/butil/third_party/rapidjson/error/en.h @@ -0,0 +1,65 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ERROR_EN_H__ +#define RAPIDJSON_ERROR_EN_H__ + +#include "error.h" + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN + +//! Maps error code of parsing into error message. +/*! + \ingroup RAPIDJSON_ERRORS + \param parseErrorCode Error code obtained in parsing. + \return the error message. + \note User can make a copy of this function for localization. + Using switch-case is safer for future modification of error codes. +*/ +inline const RAPIDJSON_ERROR_CHARTYPE* GetParseError_En(ParseErrorCode parseErrorCode) { + switch (parseErrorCode) { + case kParseErrorNone: return RAPIDJSON_ERROR_STRING("No error."); + + case kParseErrorDocumentEmpty: return RAPIDJSON_ERROR_STRING("The document is empty."); + case kParseErrorDocumentRootNotSingular: return RAPIDJSON_ERROR_STRING("The document root must not follow by other values."); + + case kParseErrorValueInvalid: return RAPIDJSON_ERROR_STRING("Invalid value."); + + case kParseErrorObjectMissName: return RAPIDJSON_ERROR_STRING("Missing a name for object member."); + case kParseErrorObjectMissColon: return RAPIDJSON_ERROR_STRING("Missing a colon after a name of object member."); + case kParseErrorObjectMissCommaOrCurlyBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or '}' after an object member."); + + case kParseErrorArrayMissCommaOrSquareBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or ']' after an array element."); + + case kParseErrorStringUnicodeEscapeInvalidHex: return RAPIDJSON_ERROR_STRING("Incorrect hex digit after \\u escape in string."); + case kParseErrorStringUnicodeSurrogateInvalid: return RAPIDJSON_ERROR_STRING("The surrogate pair in string is invalid."); + case kParseErrorStringEscapeInvalid: return RAPIDJSON_ERROR_STRING("Invalid escape character in string."); + case kParseErrorStringMissQuotationMark: return RAPIDJSON_ERROR_STRING("Missing a closing quotation mark in string."); + case kParseErrorStringInvalidEncoding: return RAPIDJSON_ERROR_STRING("Invalid encoding in string."); + + case kParseErrorNumberTooBig: return RAPIDJSON_ERROR_STRING("Number too big to be stored in double."); + case kParseErrorNumberMissFraction: return RAPIDJSON_ERROR_STRING("Miss fraction part in number."); + case kParseErrorNumberMissExponent: return RAPIDJSON_ERROR_STRING("Miss exponent in number."); + + case kParseErrorTermination: return RAPIDJSON_ERROR_STRING("Terminate parsing due to Handler error."); + case kParseErrorUnspecificSyntaxError: return RAPIDJSON_ERROR_STRING("Unspecific syntax error."); + + default: + return RAPIDJSON_ERROR_STRING("Unknown error."); + } +} + +BUTIL_RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ERROR_EN_H__ diff --git a/src/butil/third_party/rapidjson/error/error.h b/src/butil/third_party/rapidjson/error/error.h new file mode 100644 index 0000000..648c54c --- /dev/null +++ b/src/butil/third_party/rapidjson/error/error.h @@ -0,0 +1,147 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ERROR_ERROR_H__ +#define RAPIDJSON_ERROR_ERROR_H__ + +#include "../rapidjson.h" + +/*! \file error.h */ + +/*! \defgroup RAPIDJSON_ERRORS RapidJSON error handling */ + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ERROR_CHARTYPE + +//! Character type of error messages. +/*! \ingroup RAPIDJSON_ERRORS + The default character type is \c char. + On Windows, user can define this macro as \c TCHAR for supporting both + unicode/non-unicode settings. +*/ +#ifndef RAPIDJSON_ERROR_CHARTYPE +#define RAPIDJSON_ERROR_CHARTYPE char +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ERROR_STRING + +//! Macro for converting string literial to \ref RAPIDJSON_ERROR_CHARTYPE[]. +/*! \ingroup RAPIDJSON_ERRORS + By default this conversion macro does nothing. + On Windows, user can define this macro as \c _T(x) for supporting both + unicode/non-unicode settings. +*/ +#ifndef RAPIDJSON_ERROR_STRING +#define RAPIDJSON_ERROR_STRING(x) x +#endif + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// ParseErrorCode + +//! Error code of parsing. +/*! \ingroup RAPIDJSON_ERRORS + \see GenericReader::Parse, GenericReader::GetParseErrorCode +*/ +enum ParseErrorCode { + kParseErrorNone = 0, //!< No error. + + kParseErrorDocumentEmpty, //!< The document is empty. + kParseErrorDocumentRootNotSingular, //!< The document root must not follow by other values. + + kParseErrorValueInvalid, //!< Invalid value. + + kParseErrorObjectMissName, //!< Missing a name for object member. + kParseErrorObjectMissColon, //!< Missing a colon after a name of object member. + kParseErrorObjectMissCommaOrCurlyBracket, //!< Missing a comma or '}' after an object member. + + kParseErrorArrayMissCommaOrSquareBracket, //!< Missing a comma or ']' after an array element. + + kParseErrorStringUnicodeEscapeInvalidHex, //!< Incorrect hex digit after \\u escape in string. + kParseErrorStringUnicodeSurrogateInvalid, //!< The surrogate pair in string is invalid. + kParseErrorStringEscapeInvalid, //!< Invalid escape character in string. + kParseErrorStringMissQuotationMark, //!< Missing a closing quotation mark in string. + kParseErrorStringInvalidEncoding, //!< Invalid encoding in string. + + kParseErrorNumberTooBig, //!< Number too big to be stored in double. + kParseErrorNumberMissFraction, //!< Miss fraction part in number. + kParseErrorNumberMissExponent, //!< Miss exponent in number. + + kParseErrorTermination, //!< Parsing was terminated. + kParseErrorUnspecificSyntaxError, //!< Unspecific syntax error. + kParseErrorParseStringToStreamInvalidContent //!< Parse string to stream error invalid content +}; + +//! Result of parsing (wraps ParseErrorCode) +/*! + \ingroup RAPIDJSON_ERRORS + \code + Document doc; + ParseResult ok = doc.Parse("[42]"); + if (!ok) { + fprintf(stderr, "JSON parse error: %s (%u)", + GetParseError_En(ok.Code()), ok.Offset()); + exit(EXIT_FAILURE); + } + \endcode + \see GenericReader::Parse, GenericDocument::Parse +*/ +struct ParseResult { + + //! Default constructor, no error. + ParseResult() : code_(kParseErrorNone), offset_(0) {} + //! Constructor to set an error. + ParseResult(ParseErrorCode code, size_t offset) : code_(code), offset_(offset) {} + + //! Get the error code. + ParseErrorCode Code() const { return code_; } + //! Get the error offset, if \ref IsError(), 0 otherwise. + size_t Offset() const { return offset_; } + + //! Conversion to \c bool, returns \c true, iff !\ref IsError(). + operator bool() const { return !IsError(); } + //! Whether the result is an error. + bool IsError() const { return code_ != kParseErrorNone; } + + bool operator==(const ParseResult& that) const { return code_ == that.code_; } + bool operator==(ParseErrorCode code) const { return code_ == code; } + friend bool operator==(ParseErrorCode code, const ParseResult & err) { return code == err.code_; } + + //! Reset error code. + void Clear() { Set(kParseErrorNone); } + //! Update error code and offset. + void Set(ParseErrorCode code, size_t offset = 0) { code_ = code; offset_ = offset; } + +private: + ParseErrorCode code_; + size_t offset_; +}; + +//! Function pointer type of GetParseError(). +/*! \ingroup RAPIDJSON_ERRORS + + This is the prototype for \c GetParseError_X(), where \c X is a locale. + User can dynamically change locale in runtime, e.g.: +\code + GetParseErrorFunc GetParseError = GetParseError_En; // or whatever + const RAPIDJSON_ERROR_CHARTYPE* s = GetParseError(document.GetParseErrorCode()); +\endcode +*/ +typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetParseErrorFunc)(ParseErrorCode); + +BUTIL_RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ERROR_ERROR_H__ diff --git a/src/butil/third_party/rapidjson/filereadstream.h b/src/butil/third_party/rapidjson/filereadstream.h new file mode 100644 index 0000000..976709e --- /dev/null +++ b/src/butil/third_party/rapidjson/filereadstream.h @@ -0,0 +1,88 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_FILEREADSTREAM_H_ +#define RAPIDJSON_FILEREADSTREAM_H_ + +#include "rapidjson.h" +#include + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN + +//! File byte stream for input using fread(). +/*! + \note implements Stream concept +*/ +class FileReadStream { +public: + typedef char Ch; //!< Character type (byte). + + //! Constructor. + /*! + \param fp File pointer opened for read. + \param buffer user-supplied buffer. + \param bufferSize size of buffer in bytes. Must >=4 bytes. + */ + FileReadStream(std::FILE* fp, char* buffer, size_t bufferSize) : fp_(fp), buffer_(buffer), bufferSize_(bufferSize), bufferLast_(0), current_(buffer_), readCount_(0), count_(0), eof_(false) { + RAPIDJSON_ASSERT(fp_ != 0); + RAPIDJSON_ASSERT(bufferSize >= 4); + Read(); + } + + Ch Peek() const { return *current_; } + Ch Take() { Ch c = *current_; Read(); return c; } + size_t Tell() const { return count_ + static_cast(current_ - buffer_); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + // For encoding detection only. + const Ch* Peek4() const { + return (current_ + 4 <= bufferLast_) ? current_ : 0; + } + +private: + void Read() { + if (current_ < bufferLast_) + ++current_; + else if (!eof_) { + count_ += readCount_; + readCount_ = fread(buffer_, 1, bufferSize_, fp_); + bufferLast_ = buffer_ + readCount_ - 1; + current_ = buffer_; + + if (readCount_ < bufferSize_) { + buffer_[readCount_] = '\0'; + ++bufferLast_; + eof_ = true; + } + } + } + + std::FILE* fp_; + Ch *buffer_; + size_t bufferSize_; + Ch *bufferLast_; + Ch *current_; + size_t readCount_; + size_t count_; //!< Number of characters read + bool eof_; +}; + +BUTIL_RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/src/butil/third_party/rapidjson/filewritestream.h b/src/butil/third_party/rapidjson/filewritestream.h new file mode 100644 index 0000000..f18c2bd --- /dev/null +++ b/src/butil/third_party/rapidjson/filewritestream.h @@ -0,0 +1,91 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_FILEWRITESTREAM_H_ +#define RAPIDJSON_FILEWRITESTREAM_H_ + +#include "rapidjson.h" +#include + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN + +//! Wrapper of C file stream for input using fread(). +/*! + \note implements Stream concept +*/ +class FileWriteStream { +public: + typedef char Ch; //!< Character type. Only support char. + + FileWriteStream(std::FILE* fp, char* buffer, size_t bufferSize) : fp_(fp), buffer_(buffer), bufferEnd_(buffer + bufferSize), current_(buffer_) { + RAPIDJSON_ASSERT(fp_ != 0); + } + + void Put(char c) { + if (current_ >= bufferEnd_) + Flush(); + + *current_++ = c; + } + + void PutN(char c, size_t n) { + size_t avail = static_cast(bufferEnd_ - current_); + while (n > avail) { + std::memset(current_, c, avail); + current_ += avail; + Flush(); + n -= avail; + avail = static_cast(bufferEnd_ - current_); + } + + if (n > 0) { + std::memset(current_, c, n); + current_ += n; + } + } + + void Flush() { + if (current_ != buffer_) { + fwrite(buffer_, 1, static_cast(current_ - buffer_), fp_); + current_ = buffer_; + } + } + + // Not implemented + char Peek() const { RAPIDJSON_ASSERT(false); return 0; } + char Take() { RAPIDJSON_ASSERT(false); return 0; } + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + char* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(char*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + // Prohibit copy constructor & assignment operator. + FileWriteStream(const FileWriteStream&); + FileWriteStream& operator=(const FileWriteStream&); + + std::FILE* fp_; + char *buffer_; + char *bufferEnd_; + char *current_; +}; + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(FileWriteStream& stream, char c, size_t n) { + stream.PutN(c, n); +} + +BUTIL_RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/src/butil/third_party/rapidjson/internal/biginteger.h b/src/butil/third_party/rapidjson/internal/biginteger.h new file mode 100644 index 0000000..32d66f8 --- /dev/null +++ b/src/butil/third_party/rapidjson/internal/biginteger.h @@ -0,0 +1,280 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_BIGINTEGER_H_ +#define RAPIDJSON_BIGINTEGER_H_ + +#include "../rapidjson.h" + +#if defined(_MSC_VER) && defined(_M_AMD64) +#include // for _umul128 +#endif + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +class BigInteger { +public: + typedef uint64_t Type; + + BigInteger(const BigInteger& rhs) : count_(rhs.count_) { + std::memcpy(digits_, rhs.digits_, count_ * sizeof(Type)); + } + + explicit BigInteger(uint64_t u) : count_(1) { + digits_[0] = u; + } + + BigInteger(const char* decimals, size_t length) : count_(1) { + RAPIDJSON_ASSERT(length > 0); + digits_[0] = 0; + size_t i = 0; + const size_t kMaxDigitPerIteration = 19; // 2^64 = 18446744073709551616 > 10^19 + while (length >= kMaxDigitPerIteration) { + AppendDecimal64(decimals + i, decimals + i + kMaxDigitPerIteration); + length -= kMaxDigitPerIteration; + i += kMaxDigitPerIteration; + } + + if (length > 0) + AppendDecimal64(decimals + i, decimals + i + length); + } + + BigInteger& operator=(uint64_t u) { + digits_[0] = u; + count_ = 1; + return *this; + } + + BigInteger& operator+=(uint64_t u) { + Type backup = digits_[0]; + digits_[0] += u; + for (size_t i = 0; i < count_ - 1; i++) { + if (digits_[i] >= backup) + return *this; // no carry + backup = digits_[i + 1]; + digits_[i + 1] += 1; + } + + // Last carry + if (digits_[count_ - 1] < backup) + PushBack(1); + + return *this; + } + + BigInteger& operator*=(uint64_t u) { + if (u == 0) return *this = 0; + if (u == 1) return *this; + if (*this == 1) return *this = u; + + uint64_t k = 0; + for (size_t i = 0; i < count_; i++) { + uint64_t hi; + digits_[i] = MulAdd64(digits_[i], u, k, &hi); + k = hi; + } + + if (k > 0) + PushBack(k); + + return *this; + } + + BigInteger& operator*=(uint32_t u) { + if (u == 0) return *this = 0; + if (u == 1) return *this; + if (*this == 1) return *this = u; + + uint64_t k = 0; + for (size_t i = 0; i < count_; i++) { + const uint64_t c = digits_[i] >> 32; + const uint64_t d = digits_[i] & 0xFFFFFFFF; + const uint64_t uc = u * c; + const uint64_t ud = u * d; + const uint64_t p0 = ud + k; + const uint64_t p1 = uc + (p0 >> 32); + digits_[i] = (p0 & 0xFFFFFFFF) | (p1 << 32); + k = p1 >> 32; + } + + if (k > 0) + PushBack(k); + + return *this; + } + + BigInteger& operator<<=(size_t shift) { + if (IsZero() || shift == 0) return *this; + + size_t offset = shift / kTypeBit; + size_t interShift = shift % kTypeBit; + RAPIDJSON_ASSERT(count_ + offset <= kCapacity); + + if (interShift == 0) { + std::memmove(&digits_[count_ - 1 + offset], &digits_[count_ - 1], count_ * sizeof(Type)); + count_ += offset; + } + else { + digits_[count_] = 0; + for (size_t i = count_; i > 0; i--) + digits_[i + offset] = (digits_[i] << interShift) | (digits_[i - 1] >> (kTypeBit - interShift)); + digits_[offset] = digits_[0] << interShift; + count_ += offset; + if (digits_[count_]) + count_++; + } + + std::memset(digits_, 0, offset * sizeof(Type)); + + return *this; + } + + bool operator==(const BigInteger& rhs) const { + return count_ == rhs.count_ && std::memcmp(digits_, rhs.digits_, count_ * sizeof(Type)) == 0; + } + + bool operator==(const Type rhs) const { + return count_ == 1 && digits_[0] == rhs; + } + + BigInteger& MultiplyPow5(unsigned exp) { + static const uint32_t kPow5[12] = { + 5, + 5 * 5, + 5 * 5 * 5, + 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 + }; + if (exp == 0) return *this; + for (; exp >= 27; exp -= 27) *this *= RAPIDJSON_UINT64_C2(0X6765C793, 0XFA10079D); // 5^27 + for (; exp >= 13; exp -= 13) *this *= static_cast(1220703125u); // 5^13 + if (exp > 0) *this *= kPow5[exp - 1]; + return *this; + } + + // Compute absolute difference of this and rhs. + // Assume this != rhs + bool Difference(const BigInteger& rhs, BigInteger* out) const { + int cmp = Compare(rhs); + RAPIDJSON_ASSERT(cmp != 0); + const BigInteger *a, *b; // Makes a > b + bool ret; + if (cmp < 0) { a = &rhs; b = this; ret = true; } + else { a = this; b = &rhs; ret = false; } + + Type borrow = 0; + for (size_t i = 0; i < a->count_; i++) { + Type d = a->digits_[i] - borrow; + if (i < b->count_) + d -= b->digits_[i]; + borrow = (d > a->digits_[i]) ? 1 : 0; + out->digits_[i] = d; + if (d != 0) + out->count_ = i + 1; + } + + return ret; + } + + int Compare(const BigInteger& rhs) const { + if (count_ != rhs.count_) + return count_ < rhs.count_ ? -1 : 1; + + for (size_t i = count_; i-- > 0;) + if (digits_[i] != rhs.digits_[i]) + return digits_[i] < rhs.digits_[i] ? -1 : 1; + + return 0; + } + + size_t GetCount() const { return count_; } + Type GetDigit(size_t index) const { RAPIDJSON_ASSERT(index < count_); return digits_[index]; } + bool IsZero() const { return count_ == 1 && digits_[0] == 0; } + +private: + void AppendDecimal64(const char* begin, const char* end) { + uint64_t u = ParseUint64(begin, end); + if (IsZero()) + *this = u; + else { + unsigned exp = static_cast(end - begin); + (MultiplyPow5(exp) <<= exp) += u; // *this = *this * 10^exp + u + } + } + + void PushBack(Type digit) { + RAPIDJSON_ASSERT(count_ < kCapacity); + digits_[count_++] = digit; + } + + static uint64_t ParseUint64(const char* begin, const char* end) { + uint64_t r = 0; + for (const char* p = begin; p != end; ++p) { + RAPIDJSON_ASSERT(*p >= '0' && *p <= '9'); + r = r * 10 + (*p - '0'); + } + return r; + } + + // Assume a * b + k < 2^128 + static uint64_t MulAdd64(uint64_t a, uint64_t b, uint64_t k, uint64_t* outHigh) { +#if defined(_MSC_VER) && defined(_M_AMD64) + uint64_t low = _umul128(a, b, outHigh) + k; + if (low < k) + (*outHigh)++; + return low; +#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__) + __extension__ typedef unsigned __int128 uint128; + uint128 p = static_cast(a) * static_cast(b); + p += k; + *outHigh = static_cast(p >> 64); + return static_cast(p); +#else + const uint64_t a0 = a & 0xFFFFFFFF, a1 = a >> 32, b0 = b & 0xFFFFFFFF, b1 = b >> 32; + uint64_t x0 = a0 * b0, x1 = a0 * b1, x2 = a1 * b0, x3 = a1 * b1; + x1 += (x0 >> 32); // can't give carry + x1 += x2; + if (x1 < x2) + x3 += (static_cast(1) << 32); + uint64_t lo = (x1 << 32) + (x0 & 0xFFFFFFFF); + uint64_t hi = x3 + (x1 >> 32); + + lo += k; + if (lo < k) + hi++; + *outHigh = hi; + return lo; +#endif + } + + static const size_t kBitCount = 3328; // 64bit * 54 > 10^1000 + static const size_t kCapacity = kBitCount / sizeof(Type); + static const size_t kTypeBit = sizeof(Type) * 8; + + Type digits_[kCapacity]; + size_t count_; +}; + +} // namespace internal +BUTIL_RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_BIGINTEGER_H_ diff --git a/src/butil/third_party/rapidjson/internal/diyfp.h b/src/butil/third_party/rapidjson/internal/diyfp.h new file mode 100644 index 0000000..946c0bb --- /dev/null +++ b/src/butil/third_party/rapidjson/internal/diyfp.h @@ -0,0 +1,247 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// 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. + +// This is a C++ header-only implementation of Grisu2 algorithm from the publication: +// Loitsch, Florian. "Printing floating-point numbers quickly and accurately with +// integers." ACM Sigplan Notices 45.6 (2010): 233-243. + +#ifndef RAPIDJSON_DIYFP_H_ +#define RAPIDJSON_DIYFP_H_ + +#include "../rapidjson.h" + +#if defined(_MSC_VER) && defined(_M_AMD64) +#include +#pragma intrinsic(_BitScanReverse64) +#endif + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +struct DiyFp { + DiyFp() {} + + DiyFp(uint64_t fp, int exp) : f(fp), e(exp) {} + + explicit DiyFp(double d) { + union { + double d; + uint64_t u64; + } u = { d }; + + int biased_e = static_cast((u.u64 & kDpExponentMask) >> kDpSignificandSize); + uint64_t significand = (u.u64 & kDpSignificandMask); + if (biased_e != 0) { + f = significand + kDpHiddenBit; + e = biased_e - kDpExponentBias; + } + else { + f = significand; + e = kDpMinExponent + 1; + } + } + + DiyFp operator-(const DiyFp& rhs) const { + return DiyFp(f - rhs.f, e); + } + + DiyFp operator*(const DiyFp& rhs) const { +#if defined(_MSC_VER) && defined(_M_AMD64) + uint64_t h; + uint64_t l = _umul128(f, rhs.f, &h); + if (l & (uint64_t(1) << 63)) // rounding + h++; + return DiyFp(h, e + rhs.e + 64); +#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__) + __extension__ typedef unsigned __int128 uint128; + uint128 p = static_cast(f) * static_cast(rhs.f); + uint64_t h = static_cast(p >> 64); + uint64_t l = static_cast(p); + if (l & (uint64_t(1) << 63)) // rounding + h++; + return DiyFp(h, e + rhs.e + 64); +#else + const uint64_t M32 = 0xFFFFFFFF; + const uint64_t a = f >> 32; + const uint64_t b = f & M32; + const uint64_t c = rhs.f >> 32; + const uint64_t d = rhs.f & M32; + const uint64_t ac = a * c; + const uint64_t bc = b * c; + const uint64_t ad = a * d; + const uint64_t bd = b * d; + uint64_t tmp = (bd >> 32) + (ad & M32) + (bc & M32); + tmp += 1U << 31; /// mult_round + return DiyFp(ac + (ad >> 32) + (bc >> 32) + (tmp >> 32), e + rhs.e + 64); +#endif + } + + DiyFp Normalize() const { +#if defined(_MSC_VER) && defined(_M_AMD64) + unsigned long index; + _BitScanReverse64(&index, f); + return DiyFp(f << (63 - index), e - (63 - index)); +#elif defined(__GNUC__) && __GNUC__ >= 4 + int s = __builtin_clzll(f); + return DiyFp(f << s, e - s); +#else + DiyFp res = *this; + while (!(res.f & (static_cast(1) << 63))) { + res.f <<= 1; + res.e--; + } + return res; +#endif + } + + DiyFp NormalizeBoundary() const { + DiyFp res = *this; + while (!(res.f & (kDpHiddenBit << 1))) { + res.f <<= 1; + res.e--; + } + res.f <<= (kDiySignificandSize - kDpSignificandSize - 2); + res.e = res.e - (kDiySignificandSize - kDpSignificandSize - 2); + return res; + } + + void NormalizedBoundaries(DiyFp* minus, DiyFp* plus) const { + DiyFp pl = DiyFp((f << 1) + 1, e - 1).NormalizeBoundary(); + DiyFp mi = (f == kDpHiddenBit) ? DiyFp((f << 2) - 1, e - 2) : DiyFp((f << 1) - 1, e - 1); + mi.f <<= mi.e - pl.e; + mi.e = pl.e; + *plus = pl; + *minus = mi; + } + + double ToDouble() const { + union { + double d; + uint64_t u64; + }u; + const uint64_t be = (e == kDpDenormalExponent && (f & kDpHiddenBit) == 0) ? 0 : + static_cast(e + kDpExponentBias); + u.u64 = (f & kDpSignificandMask) | (be << kDpSignificandSize); + return u.d; + } + + static const int kDiySignificandSize = 64; + static const int kDpSignificandSize = 52; + static const int kDpExponentBias = 0x3FF + kDpSignificandSize; + static const int kDpMaxExponent = 0x7FF - kDpExponentBias; + static const int kDpMinExponent = -kDpExponentBias; + static const int kDpDenormalExponent = -kDpExponentBias + 1; + static const uint64_t kDpExponentMask = RAPIDJSON_UINT64_C2(0x7FF00000, 0x00000000); + static const uint64_t kDpSignificandMask = RAPIDJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF); + static const uint64_t kDpHiddenBit = RAPIDJSON_UINT64_C2(0x00100000, 0x00000000); + + uint64_t f; + int e; +}; + +inline DiyFp GetCachedPowerByIndex(size_t index) { + // 10^-348, 10^-340, ..., 10^340 + static const uint64_t kCachedPowers_F[] = { + RAPIDJSON_UINT64_C2(0xfa8fd5a0, 0x081c0288), RAPIDJSON_UINT64_C2(0xbaaee17f, 0xa23ebf76), + RAPIDJSON_UINT64_C2(0x8b16fb20, 0x3055ac76), RAPIDJSON_UINT64_C2(0xcf42894a, 0x5dce35ea), + RAPIDJSON_UINT64_C2(0x9a6bb0aa, 0x55653b2d), RAPIDJSON_UINT64_C2(0xe61acf03, 0x3d1a45df), + RAPIDJSON_UINT64_C2(0xab70fe17, 0xc79ac6ca), RAPIDJSON_UINT64_C2(0xff77b1fc, 0xbebcdc4f), + RAPIDJSON_UINT64_C2(0xbe5691ef, 0x416bd60c), RAPIDJSON_UINT64_C2(0x8dd01fad, 0x907ffc3c), + RAPIDJSON_UINT64_C2(0xd3515c28, 0x31559a83), RAPIDJSON_UINT64_C2(0x9d71ac8f, 0xada6c9b5), + RAPIDJSON_UINT64_C2(0xea9c2277, 0x23ee8bcb), RAPIDJSON_UINT64_C2(0xaecc4991, 0x4078536d), + RAPIDJSON_UINT64_C2(0x823c1279, 0x5db6ce57), RAPIDJSON_UINT64_C2(0xc2109436, 0x4dfb5637), + RAPIDJSON_UINT64_C2(0x9096ea6f, 0x3848984f), RAPIDJSON_UINT64_C2(0xd77485cb, 0x25823ac7), + RAPIDJSON_UINT64_C2(0xa086cfcd, 0x97bf97f4), RAPIDJSON_UINT64_C2(0xef340a98, 0x172aace5), + RAPIDJSON_UINT64_C2(0xb23867fb, 0x2a35b28e), RAPIDJSON_UINT64_C2(0x84c8d4df, 0xd2c63f3b), + RAPIDJSON_UINT64_C2(0xc5dd4427, 0x1ad3cdba), RAPIDJSON_UINT64_C2(0x936b9fce, 0xbb25c996), + RAPIDJSON_UINT64_C2(0xdbac6c24, 0x7d62a584), RAPIDJSON_UINT64_C2(0xa3ab6658, 0x0d5fdaf6), + RAPIDJSON_UINT64_C2(0xf3e2f893, 0xdec3f126), RAPIDJSON_UINT64_C2(0xb5b5ada8, 0xaaff80b8), + RAPIDJSON_UINT64_C2(0x87625f05, 0x6c7c4a8b), RAPIDJSON_UINT64_C2(0xc9bcff60, 0x34c13053), + RAPIDJSON_UINT64_C2(0x964e858c, 0x91ba2655), RAPIDJSON_UINT64_C2(0xdff97724, 0x70297ebd), + RAPIDJSON_UINT64_C2(0xa6dfbd9f, 0xb8e5b88f), RAPIDJSON_UINT64_C2(0xf8a95fcf, 0x88747d94), + RAPIDJSON_UINT64_C2(0xb9447093, 0x8fa89bcf), RAPIDJSON_UINT64_C2(0x8a08f0f8, 0xbf0f156b), + RAPIDJSON_UINT64_C2(0xcdb02555, 0x653131b6), RAPIDJSON_UINT64_C2(0x993fe2c6, 0xd07b7fac), + RAPIDJSON_UINT64_C2(0xe45c10c4, 0x2a2b3b06), RAPIDJSON_UINT64_C2(0xaa242499, 0x697392d3), + RAPIDJSON_UINT64_C2(0xfd87b5f2, 0x8300ca0e), RAPIDJSON_UINT64_C2(0xbce50864, 0x92111aeb), + RAPIDJSON_UINT64_C2(0x8cbccc09, 0x6f5088cc), RAPIDJSON_UINT64_C2(0xd1b71758, 0xe219652c), + RAPIDJSON_UINT64_C2(0x9c400000, 0x00000000), RAPIDJSON_UINT64_C2(0xe8d4a510, 0x00000000), + RAPIDJSON_UINT64_C2(0xad78ebc5, 0xac620000), RAPIDJSON_UINT64_C2(0x813f3978, 0xf8940984), + RAPIDJSON_UINT64_C2(0xc097ce7b, 0xc90715b3), RAPIDJSON_UINT64_C2(0x8f7e32ce, 0x7bea5c70), + RAPIDJSON_UINT64_C2(0xd5d238a4, 0xabe98068), RAPIDJSON_UINT64_C2(0x9f4f2726, 0x179a2245), + RAPIDJSON_UINT64_C2(0xed63a231, 0xd4c4fb27), RAPIDJSON_UINT64_C2(0xb0de6538, 0x8cc8ada8), + RAPIDJSON_UINT64_C2(0x83c7088e, 0x1aab65db), RAPIDJSON_UINT64_C2(0xc45d1df9, 0x42711d9a), + RAPIDJSON_UINT64_C2(0x924d692c, 0xa61be758), RAPIDJSON_UINT64_C2(0xda01ee64, 0x1a708dea), + RAPIDJSON_UINT64_C2(0xa26da399, 0x9aef774a), RAPIDJSON_UINT64_C2(0xf209787b, 0xb47d6b85), + RAPIDJSON_UINT64_C2(0xb454e4a1, 0x79dd1877), RAPIDJSON_UINT64_C2(0x865b8692, 0x5b9bc5c2), + RAPIDJSON_UINT64_C2(0xc83553c5, 0xc8965d3d), RAPIDJSON_UINT64_C2(0x952ab45c, 0xfa97a0b3), + RAPIDJSON_UINT64_C2(0xde469fbd, 0x99a05fe3), RAPIDJSON_UINT64_C2(0xa59bc234, 0xdb398c25), + RAPIDJSON_UINT64_C2(0xf6c69a72, 0xa3989f5c), RAPIDJSON_UINT64_C2(0xb7dcbf53, 0x54e9bece), + RAPIDJSON_UINT64_C2(0x88fcf317, 0xf22241e2), RAPIDJSON_UINT64_C2(0xcc20ce9b, 0xd35c78a5), + RAPIDJSON_UINT64_C2(0x98165af3, 0x7b2153df), RAPIDJSON_UINT64_C2(0xe2a0b5dc, 0x971f303a), + RAPIDJSON_UINT64_C2(0xa8d9d153, 0x5ce3b396), RAPIDJSON_UINT64_C2(0xfb9b7cd9, 0xa4a7443c), + RAPIDJSON_UINT64_C2(0xbb764c4c, 0xa7a44410), RAPIDJSON_UINT64_C2(0x8bab8eef, 0xb6409c1a), + RAPIDJSON_UINT64_C2(0xd01fef10, 0xa657842c), RAPIDJSON_UINT64_C2(0x9b10a4e5, 0xe9913129), + RAPIDJSON_UINT64_C2(0xe7109bfb, 0xa19c0c9d), RAPIDJSON_UINT64_C2(0xac2820d9, 0x623bf429), + RAPIDJSON_UINT64_C2(0x80444b5e, 0x7aa7cf85), RAPIDJSON_UINT64_C2(0xbf21e440, 0x03acdd2d), + RAPIDJSON_UINT64_C2(0x8e679c2f, 0x5e44ff8f), RAPIDJSON_UINT64_C2(0xd433179d, 0x9c8cb841), + RAPIDJSON_UINT64_C2(0x9e19db92, 0xb4e31ba9), RAPIDJSON_UINT64_C2(0xeb96bf6e, 0xbadf77d9), + RAPIDJSON_UINT64_C2(0xaf87023b, 0x9bf0ee6b) + }; + static const int16_t kCachedPowers_E[] = { + -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, -343, -316, -289, -263, -236, -210, -183, + -157, -130, -103, -77, -50, -24, 3, 30, 56, 83, + 109, 136, 162, 189, 216, 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 + }; + return DiyFp(kCachedPowers_F[index], kCachedPowers_E[index]); +} + +inline DiyFp GetCachedPower(int e, int* K) { + + //int k = static_cast(ceil((-61 - e) * 0.30102999566398114)) + 374; + double dk = (-61 - e) * 0.30102999566398114 + 347; // dk must be positive, so can do ceiling in positive + int k = static_cast(dk); + if (dk - k > 0.0) + k++; + + unsigned index = static_cast((k >> 3) + 1); + *K = -(-348 + static_cast(index << 3)); // decimal exponent no need lookup table + + return GetCachedPowerByIndex(index); +} + +inline DiyFp GetCachedPower10(int exp, int *outExp) { + unsigned index = (exp + 348) / 8; + *outExp = -348 + index * 8; + return GetCachedPowerByIndex(index); + } + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +} // namespace internal +BUTIL_RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_DIYFP_H_ diff --git a/src/butil/third_party/rapidjson/internal/dtoa.h b/src/butil/third_party/rapidjson/internal/dtoa.h new file mode 100644 index 0000000..a04a58d --- /dev/null +++ b/src/butil/third_party/rapidjson/internal/dtoa.h @@ -0,0 +1,217 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// 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. + +// This is a C++ header-only implementation of Grisu2 algorithm from the publication: +// Loitsch, Florian. "Printing floating-point numbers quickly and accurately with +// integers." ACM Sigplan Notices 45.6 (2010): 233-243. + +#ifndef RAPIDJSON_DTOA_ +#define RAPIDJSON_DTOA_ + +#include "itoa.h" // GetDigitsLut() +#include "diyfp.h" +#include "ieee754.h" + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +inline void GrisuRound(char* buffer, int len, uint64_t delta, uint64_t rest, uint64_t ten_kappa, uint64_t wp_w) { + while (rest < wp_w && delta - rest >= ten_kappa && + (rest + ten_kappa < wp_w || /// closer + wp_w - rest > rest + ten_kappa - wp_w)) { + buffer[len - 1]--; + rest += ten_kappa; + } +} + +inline unsigned CountDecimalDigit32(uint32_t n) { + // Simple pure C++ implementation was faster than __builtin_clz version in this situation. + if (n < 10) return 1; + if (n < 100) return 2; + if (n < 1000) return 3; + if (n < 10000) return 4; + if (n < 100000) return 5; + if (n < 1000000) return 6; + if (n < 10000000) return 7; + if (n < 100000000) return 8; + // Will not reach 10 digits in DigitGen() + //if (n < 1000000000) return 9; + //return 10; + return 9; +} + +inline void DigitGen(const DiyFp& W, const DiyFp& Mp, uint64_t delta, char* buffer, int* len, int* K) { + static const uint32_t kPow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; + const DiyFp one(uint64_t(1) << -Mp.e, Mp.e); + const DiyFp wp_w = Mp - W; + uint32_t p1 = static_cast(Mp.f >> -one.e); + uint64_t p2 = Mp.f & (one.f - 1); + int kappa = CountDecimalDigit32(p1); // kappa in [0, 9] + *len = 0; + + while (kappa > 0) { + uint32_t d = 0; + switch (kappa) { + case 9: d = p1 / 100000000; p1 %= 100000000; break; + case 8: d = p1 / 10000000; p1 %= 10000000; break; + case 7: d = p1 / 1000000; p1 %= 1000000; break; + case 6: d = p1 / 100000; p1 %= 100000; break; + case 5: d = p1 / 10000; p1 %= 10000; break; + case 4: d = p1 / 1000; p1 %= 1000; break; + case 3: d = p1 / 100; p1 %= 100; break; + case 2: d = p1 / 10; p1 %= 10; break; + case 1: d = p1; p1 = 0; break; + default:; + } + if (d || *len) + buffer[(*len)++] = static_cast('0' + static_cast(d)); + kappa--; + uint64_t tmp = (static_cast(p1) << -one.e) + p2; + if (tmp <= delta) { + *K += kappa; + GrisuRound(buffer, *len, delta, tmp, static_cast(kPow10[kappa]) << -one.e, wp_w.f); + return; + } + } + + // kappa = 0 + for (;;) { + p2 *= 10; + delta *= 10; + char d = static_cast(p2 >> -one.e); + if (d || *len) + buffer[(*len)++] = static_cast('0' + d); + p2 &= one.f - 1; + kappa--; + if (p2 < delta) { + *K += kappa; + GrisuRound(buffer, *len, delta, p2, one.f, wp_w.f * kPow10[-kappa]); + return; + } + } +} + +inline void Grisu2(double value, char* buffer, int* length, int* K) { + const DiyFp v(value); + DiyFp w_m, w_p; + v.NormalizedBoundaries(&w_m, &w_p); + + const DiyFp c_mk = GetCachedPower(w_p.e, K); + const DiyFp W = v.Normalize() * c_mk; + DiyFp Wp = w_p * c_mk; + DiyFp Wm = w_m * c_mk; + Wm.f++; + Wp.f--; + DigitGen(W, Wp, Wp.f - Wm.f, buffer, length, K); +} + +inline char* WriteExponent(int K, char* buffer) { + if (K < 0) { + *buffer++ = '-'; + K = -K; + } + + if (K >= 100) { + *buffer++ = static_cast('0' + static_cast(K / 100)); + K %= 100; + const char* d = GetDigitsLut() + K * 2; + *buffer++ = d[0]; + *buffer++ = d[1]; + } + else if (K >= 10) { + const char* d = GetDigitsLut() + K * 2; + *buffer++ = d[0]; + *buffer++ = d[1]; + } + else + *buffer++ = static_cast('0' + static_cast(K)); + + return buffer; +} + +inline char* Prettify(char* buffer, int length, int k) { + const int kk = length + k; // 10^(kk-1) <= v < 10^kk + + if (length <= kk && kk <= 21) { + // 1234e7 -> 12340000000 + for (int i = length; i < kk; i++) + buffer[i] = '0'; + buffer[kk] = '.'; + buffer[kk + 1] = '0'; + return &buffer[kk + 2]; + } + else if (0 < kk && kk <= 21) { + // 1234e-2 -> 12.34 + std::memmove(&buffer[kk + 1], &buffer[kk], length - kk); + buffer[kk] = '.'; + return &buffer[length + 1]; + } + else if (-6 < kk && kk <= 0) { + // 1234e-6 -> 0.001234 + const int offset = 2 - kk; + std::memmove(&buffer[offset], &buffer[0], length); + buffer[0] = '0'; + buffer[1] = '.'; + for (int i = 2; i < offset; i++) + buffer[i] = '0'; + return &buffer[length + offset]; + } + else if (length == 1) { + // 1e30 + buffer[1] = 'e'; + return WriteExponent(kk - 1, &buffer[2]); + } + else { + // 1234e30 -> 1.234e33 + std::memmove(&buffer[2], &buffer[1], length - 1); + buffer[1] = '.'; + buffer[length + 1] = 'e'; + return WriteExponent(kk - 1, &buffer[0 + length + 2]); + } +} + +inline char* dtoa(double value, char* buffer) { + Double d(value); + if (d.IsZero()) { + if (d.Sign()) + *buffer++ = '-'; // -0.0, Issue #289 + buffer[0] = '0'; + buffer[1] = '.'; + buffer[2] = '0'; + return &buffer[3]; + } + else { + if (value < 0) { + *buffer++ = '-'; + value = -value; + } + int length, K; + Grisu2(value, buffer, &length, &K); + return Prettify(buffer, length, K); + } +} + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +} // namespace internal +BUTIL_RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_DTOA_ diff --git a/src/butil/third_party/rapidjson/internal/ieee754.h b/src/butil/third_party/rapidjson/internal/ieee754.h new file mode 100644 index 0000000..4d26692 --- /dev/null +++ b/src/butil/third_party/rapidjson/internal/ieee754.h @@ -0,0 +1,77 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_IEEE754_ +#define RAPIDJSON_IEEE754_ + +#include "../rapidjson.h" + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +class Double { +public: + Double() {} + Double(double d) : d_(d) {} + Double(uint64_t u) : u_(u) {} + + double Value() const { return d_; } + uint64_t Uint64Value() const { return u_; } + + double NextPositiveDouble() const { + RAPIDJSON_ASSERT(!Sign()); + return Double(u_ + 1).Value(); + } + + bool Sign() const { return (u_ & kSignMask) != 0; } + uint64_t Significand() const { return u_ & kSignificandMask; } + int Exponent() const { return static_cast(((u_ & kExponentMask) >> kSignificandSize) - kExponentBias); } + + bool IsNan() const { return (u_ & kExponentMask) == kExponentMask && Significand() != 0; } + bool IsInf() const { return (u_ & kExponentMask) == kExponentMask && Significand() == 0; } + bool IsNormal() const { return (u_ & kExponentMask) != 0 || Significand() == 0; } + bool IsZero() const { return (u_ & (kExponentMask | kSignificandMask)) == 0; } + + uint64_t IntegerSignificand() const { return IsNormal() ? Significand() | kHiddenBit : Significand(); } + int IntegerExponent() const { return (IsNormal() ? Exponent() : kDenormalExponent) - kSignificandSize; } + uint64_t ToBias() const { return (u_ & kSignMask) ? ~u_ + 1 : u_ | kSignMask; } + + static unsigned EffectiveSignificandSize(int order) { + if (order >= -1021) + return 53; + else if (order <= -1074) + return 0; + else + return order + 1074; + } + +private: + static const int kSignificandSize = 52; + static const int kExponentBias = 0x3FF; + static const int kDenormalExponent = 1 - kExponentBias; + static const uint64_t kSignMask = RAPIDJSON_UINT64_C2(0x80000000, 0x00000000); + static const uint64_t kExponentMask = RAPIDJSON_UINT64_C2(0x7FF00000, 0x00000000); + static const uint64_t kSignificandMask = RAPIDJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF); + static const uint64_t kHiddenBit = RAPIDJSON_UINT64_C2(0x00100000, 0x00000000); + + union { + double d_; + uint64_t u_; + }; +}; + +} // namespace internal +BUTIL_RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_IEEE754_ diff --git a/src/butil/third_party/rapidjson/internal/itoa.h b/src/butil/third_party/rapidjson/internal/itoa.h new file mode 100644 index 0000000..7b3de2c --- /dev/null +++ b/src/butil/third_party/rapidjson/internal/itoa.h @@ -0,0 +1,304 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ITOA_ +#define RAPIDJSON_ITOA_ + +#include "../rapidjson.h" + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +inline const char* GetDigitsLut() { + static const char cDigitsLut[200] = { + '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' + }; + return cDigitsLut; +} + +inline char* u32toa(uint32_t value, char* buffer) { + const char* cDigitsLut = GetDigitsLut(); + + if (value < 10000) { + const uint32_t d1 = (value / 100) << 1; + const uint32_t d2 = (value % 100) << 1; + + if (value >= 1000) + *buffer++ = cDigitsLut[d1]; + if (value >= 100) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 10) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + } + else if (value < 100000000) { + // value = bbbbcccc + const uint32_t b = value / 10000; + const uint32_t c = value % 10000; + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + if (value >= 10000000) + *buffer++ = cDigitsLut[d1]; + if (value >= 1000000) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 100000) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + else { + // value = aabbbbcccc in decimal + + const uint32_t a = value / 100000000; // 1 to 42 + value %= 100000000; + + if (a >= 10) { + const unsigned i = a << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else + *buffer++ = static_cast('0' + static_cast(a)); + + const uint32_t b = value / 10000; // 0 to 9999 + const uint32_t c = value % 10000; // 0 to 9999 + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + *buffer++ = cDigitsLut[d1]; + *buffer++ = cDigitsLut[d1 + 1]; + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + return buffer; +} + +inline char* i32toa(int32_t value, char* buffer) { + uint32_t u = static_cast(value); + if (value < 0) { + *buffer++ = '-'; + u = ~u + 1; + } + + return u32toa(u, buffer); +} + +inline char* u64toa(uint64_t value, char* buffer) { + const char* cDigitsLut = GetDigitsLut(); + const uint64_t kTen8 = 100000000; + const uint64_t kTen9 = kTen8 * 10; + const uint64_t kTen10 = kTen8 * 100; + const uint64_t kTen11 = kTen8 * 1000; + const uint64_t kTen12 = kTen8 * 10000; + const uint64_t kTen13 = kTen8 * 100000; + const uint64_t kTen14 = kTen8 * 1000000; + const uint64_t kTen15 = kTen8 * 10000000; + const uint64_t kTen16 = kTen8 * kTen8; + + if (value < kTen8) { + uint32_t v = static_cast(value); + if (v < 10000) { + const uint32_t d1 = (v / 100) << 1; + const uint32_t d2 = (v % 100) << 1; + + if (v >= 1000) + *buffer++ = cDigitsLut[d1]; + if (v >= 100) + *buffer++ = cDigitsLut[d1 + 1]; + if (v >= 10) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + } + else { + // value = bbbbcccc + const uint32_t b = v / 10000; + const uint32_t c = v % 10000; + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + if (value >= 10000000) + *buffer++ = cDigitsLut[d1]; + if (value >= 1000000) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 100000) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + } + else if (value < kTen16) { + const uint32_t v0 = static_cast(value / kTen8); + const uint32_t v1 = static_cast(value % kTen8); + + const uint32_t b0 = v0 / 10000; + const uint32_t c0 = v0 % 10000; + + const uint32_t d1 = (b0 / 100) << 1; + const uint32_t d2 = (b0 % 100) << 1; + + const uint32_t d3 = (c0 / 100) << 1; + const uint32_t d4 = (c0 % 100) << 1; + + const uint32_t b1 = v1 / 10000; + const uint32_t c1 = v1 % 10000; + + const uint32_t d5 = (b1 / 100) << 1; + const uint32_t d6 = (b1 % 100) << 1; + + const uint32_t d7 = (c1 / 100) << 1; + const uint32_t d8 = (c1 % 100) << 1; + + if (value >= kTen15) + *buffer++ = cDigitsLut[d1]; + if (value >= kTen14) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= kTen13) + *buffer++ = cDigitsLut[d2]; + if (value >= kTen12) + *buffer++ = cDigitsLut[d2 + 1]; + if (value >= kTen11) + *buffer++ = cDigitsLut[d3]; + if (value >= kTen10) + *buffer++ = cDigitsLut[d3 + 1]; + if (value >= kTen9) + *buffer++ = cDigitsLut[d4]; + if (value >= kTen8) + *buffer++ = cDigitsLut[d4 + 1]; + + *buffer++ = cDigitsLut[d5]; + *buffer++ = cDigitsLut[d5 + 1]; + *buffer++ = cDigitsLut[d6]; + *buffer++ = cDigitsLut[d6 + 1]; + *buffer++ = cDigitsLut[d7]; + *buffer++ = cDigitsLut[d7 + 1]; + *buffer++ = cDigitsLut[d8]; + *buffer++ = cDigitsLut[d8 + 1]; + } + else { + const uint32_t a = static_cast(value / kTen16); // 1 to 1844 + value %= kTen16; + + if (a < 10) + *buffer++ = static_cast('0' + static_cast(a)); + else if (a < 100) { + const uint32_t i = a << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else if (a < 1000) { + *buffer++ = static_cast('0' + static_cast(a / 100)); + + const uint32_t i = (a % 100) << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else { + const uint32_t i = (a / 100) << 1; + const uint32_t j = (a % 100) << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + *buffer++ = cDigitsLut[j]; + *buffer++ = cDigitsLut[j + 1]; + } + + const uint32_t v0 = static_cast(value / kTen8); + const uint32_t v1 = static_cast(value % kTen8); + + const uint32_t b0 = v0 / 10000; + const uint32_t c0 = v0 % 10000; + + const uint32_t d1 = (b0 / 100) << 1; + const uint32_t d2 = (b0 % 100) << 1; + + const uint32_t d3 = (c0 / 100) << 1; + const uint32_t d4 = (c0 % 100) << 1; + + const uint32_t b1 = v1 / 10000; + const uint32_t c1 = v1 % 10000; + + const uint32_t d5 = (b1 / 100) << 1; + const uint32_t d6 = (b1 % 100) << 1; + + const uint32_t d7 = (c1 / 100) << 1; + const uint32_t d8 = (c1 % 100) << 1; + + *buffer++ = cDigitsLut[d1]; + *buffer++ = cDigitsLut[d1 + 1]; + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + *buffer++ = cDigitsLut[d5]; + *buffer++ = cDigitsLut[d5 + 1]; + *buffer++ = cDigitsLut[d6]; + *buffer++ = cDigitsLut[d6 + 1]; + *buffer++ = cDigitsLut[d7]; + *buffer++ = cDigitsLut[d7 + 1]; + *buffer++ = cDigitsLut[d8]; + *buffer++ = cDigitsLut[d8 + 1]; + } + + return buffer; +} + +inline char* i64toa(int64_t value, char* buffer) { + uint64_t u = static_cast(value); + if (value < 0) { + *buffer++ = '-'; + u = ~u + 1; + } + + return u64toa(u, buffer); +} + +} // namespace internal +BUTIL_RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ITOA_ diff --git a/src/butil/third_party/rapidjson/internal/meta.h b/src/butil/third_party/rapidjson/internal/meta.h new file mode 100644 index 0000000..28956c2 --- /dev/null +++ b/src/butil/third_party/rapidjson/internal/meta.h @@ -0,0 +1,181 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_META_H_ +#define RAPIDJSON_INTERNAL_META_H_ + +#include "../rapidjson.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif +#if defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(6334) +#endif + +#if RAPIDJSON_HAS_CXX11_TYPETRAITS +#include +#endif + +//@cond RAPIDJSON_INTERNAL +BUTIL_RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +// Helper to wrap/convert arbitrary types to void, useful for arbitrary type matching +template struct Void { typedef void Type; }; + +/////////////////////////////////////////////////////////////////////////////// +// BoolType, TrueType, FalseType +// +template struct BoolType { + static const bool Value = Cond; + typedef BoolType Type; +}; +typedef BoolType TrueType; +typedef BoolType FalseType; + + +/////////////////////////////////////////////////////////////////////////////// +// SelectIf, BoolExpr, NotExpr, AndExpr, OrExpr +// + +template struct SelectIfImpl { template struct Apply { typedef T1 Type; }; }; +template <> struct SelectIfImpl { template struct Apply { typedef T2 Type; }; }; +template struct SelectIfCond : SelectIfImpl::template Apply {}; +template struct SelectIf : SelectIfCond {}; + +template struct AndExprCond : FalseType {}; +template <> struct AndExprCond : TrueType {}; +template struct OrExprCond : TrueType {}; +template <> struct OrExprCond : FalseType {}; + +template struct BoolExpr : SelectIf::Type {}; +template struct NotExpr : SelectIf::Type {}; +template struct AndExpr : AndExprCond::Type {}; +template struct OrExpr : OrExprCond::Type {}; + + +/////////////////////////////////////////////////////////////////////////////// +// AddConst, MaybeAddConst, RemoveConst +template struct AddConst { typedef const T Type; }; +template struct MaybeAddConst : SelectIfCond {}; +template struct RemoveConst { typedef T Type; }; +template struct RemoveConst { typedef T Type; }; + + +/////////////////////////////////////////////////////////////////////////////// +// IsSame, IsConst, IsMoreConst, IsPointer +// +template struct IsSame : FalseType {}; +template struct IsSame : TrueType {}; + +template struct IsConst : FalseType {}; +template struct IsConst : TrueType {}; + +template +struct IsMoreConst + : AndExpr::Type, typename RemoveConst::Type>, + BoolType::Value >= IsConst::Value> >::Type {}; + +template struct IsPointer : FalseType {}; +template struct IsPointer : TrueType {}; + +/////////////////////////////////////////////////////////////////////////////// +// IsBaseOf +// +#if RAPIDJSON_HAS_CXX11_TYPETRAITS + +template struct IsBaseOf + : BoolType< ::std::is_base_of::value> {}; + +#else // simplified version adopted from Boost + +template struct IsBaseOfImpl { + RAPIDJSON_STATIC_ASSERT(sizeof(B) != 0); + RAPIDJSON_STATIC_ASSERT(sizeof(D) != 0); + + typedef char (&Yes)[1]; + typedef char (&No) [2]; + + template + static Yes Check(const D*, T); + static No Check(const B*, int); + + struct Host { + operator const B*() const; + operator const D*(); + }; + + enum { Value = (sizeof(Check(Host(), 0)) == sizeof(Yes)) }; +}; + +template struct IsBaseOf + : OrExpr, BoolExpr > >::Type {}; + +#endif // RAPIDJSON_HAS_CXX11_TYPETRAITS + + +////////////////////////////////////////////////////////////////////////// +// EnableIf / DisableIf +// +template struct EnableIfCond { typedef T Type; }; +template struct EnableIfCond { /* empty */ }; + +template struct DisableIfCond { typedef T Type; }; +template struct DisableIfCond { /* empty */ }; + +template +struct EnableIf : EnableIfCond {}; + +template +struct DisableIf : DisableIfCond {}; + +// SFINAE helpers +struct SfinaeTag {}; +template struct RemoveSfinaeTag; +template struct RemoveSfinaeTag { typedef T Type; }; + +#define RAPIDJSON_REMOVEFPTR_(type) \ + typename ::BUTIL_RAPIDJSON_NAMESPACE::internal::RemoveSfinaeTag \ + < ::BUTIL_RAPIDJSON_NAMESPACE::internal::SfinaeTag&(*) type>::Type + +#define RAPIDJSON_ENABLEIF(cond) \ + typename ::BUTIL_RAPIDJSON_NAMESPACE::internal::EnableIf \ + ::Type * = NULL + +#define RAPIDJSON_DISABLEIF(cond) \ + typename ::BUTIL_RAPIDJSON_NAMESPACE::internal::DisableIf \ + ::Type * = NULL + +#define RAPIDJSON_ENABLEIF_RETURN(cond,returntype) \ + typename ::BUTIL_RAPIDJSON_NAMESPACE::internal::EnableIf \ + ::Type + +#define RAPIDJSON_DISABLEIF_RETURN(cond,returntype) \ + typename ::BUTIL_RAPIDJSON_NAMESPACE::internal::DisableIf \ + ::Type + +} // namespace internal +BUTIL_RAPIDJSON_NAMESPACE_END +//@endcond + +#if defined(__GNUC__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_INTERNAL_META_H_ diff --git a/src/butil/third_party/rapidjson/internal/pow10.h b/src/butil/third_party/rapidjson/internal/pow10.h new file mode 100644 index 0000000..76d5944 --- /dev/null +++ b/src/butil/third_party/rapidjson/internal/pow10.h @@ -0,0 +1,55 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_POW10_ +#define RAPIDJSON_POW10_ + +#include "../rapidjson.h" + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +//! Computes integer powers of 10 in double (10.0^n). +/*! This function uses lookup table for fast and accurate results. + \param n non-negative exponent. Must <= 308. + \return 10.0^n +*/ +inline double Pow10(int n) { + static const double e[] = { // 1e-0...1e308: 309 * 8 bytes = 2472 bytes + 1e+0, + 1e+1, 1e+2, 1e+3, 1e+4, 1e+5, 1e+6, 1e+7, 1e+8, 1e+9, 1e+10, 1e+11, 1e+12, 1e+13, 1e+14, 1e+15, 1e+16, 1e+17, 1e+18, 1e+19, 1e+20, + 1e+21, 1e+22, 1e+23, 1e+24, 1e+25, 1e+26, 1e+27, 1e+28, 1e+29, 1e+30, 1e+31, 1e+32, 1e+33, 1e+34, 1e+35, 1e+36, 1e+37, 1e+38, 1e+39, 1e+40, + 1e+41, 1e+42, 1e+43, 1e+44, 1e+45, 1e+46, 1e+47, 1e+48, 1e+49, 1e+50, 1e+51, 1e+52, 1e+53, 1e+54, 1e+55, 1e+56, 1e+57, 1e+58, 1e+59, 1e+60, + 1e+61, 1e+62, 1e+63, 1e+64, 1e+65, 1e+66, 1e+67, 1e+68, 1e+69, 1e+70, 1e+71, 1e+72, 1e+73, 1e+74, 1e+75, 1e+76, 1e+77, 1e+78, 1e+79, 1e+80, + 1e+81, 1e+82, 1e+83, 1e+84, 1e+85, 1e+86, 1e+87, 1e+88, 1e+89, 1e+90, 1e+91, 1e+92, 1e+93, 1e+94, 1e+95, 1e+96, 1e+97, 1e+98, 1e+99, 1e+100, + 1e+101,1e+102,1e+103,1e+104,1e+105,1e+106,1e+107,1e+108,1e+109,1e+110,1e+111,1e+112,1e+113,1e+114,1e+115,1e+116,1e+117,1e+118,1e+119,1e+120, + 1e+121,1e+122,1e+123,1e+124,1e+125,1e+126,1e+127,1e+128,1e+129,1e+130,1e+131,1e+132,1e+133,1e+134,1e+135,1e+136,1e+137,1e+138,1e+139,1e+140, + 1e+141,1e+142,1e+143,1e+144,1e+145,1e+146,1e+147,1e+148,1e+149,1e+150,1e+151,1e+152,1e+153,1e+154,1e+155,1e+156,1e+157,1e+158,1e+159,1e+160, + 1e+161,1e+162,1e+163,1e+164,1e+165,1e+166,1e+167,1e+168,1e+169,1e+170,1e+171,1e+172,1e+173,1e+174,1e+175,1e+176,1e+177,1e+178,1e+179,1e+180, + 1e+181,1e+182,1e+183,1e+184,1e+185,1e+186,1e+187,1e+188,1e+189,1e+190,1e+191,1e+192,1e+193,1e+194,1e+195,1e+196,1e+197,1e+198,1e+199,1e+200, + 1e+201,1e+202,1e+203,1e+204,1e+205,1e+206,1e+207,1e+208,1e+209,1e+210,1e+211,1e+212,1e+213,1e+214,1e+215,1e+216,1e+217,1e+218,1e+219,1e+220, + 1e+221,1e+222,1e+223,1e+224,1e+225,1e+226,1e+227,1e+228,1e+229,1e+230,1e+231,1e+232,1e+233,1e+234,1e+235,1e+236,1e+237,1e+238,1e+239,1e+240, + 1e+241,1e+242,1e+243,1e+244,1e+245,1e+246,1e+247,1e+248,1e+249,1e+250,1e+251,1e+252,1e+253,1e+254,1e+255,1e+256,1e+257,1e+258,1e+259,1e+260, + 1e+261,1e+262,1e+263,1e+264,1e+265,1e+266,1e+267,1e+268,1e+269,1e+270,1e+271,1e+272,1e+273,1e+274,1e+275,1e+276,1e+277,1e+278,1e+279,1e+280, + 1e+281,1e+282,1e+283,1e+284,1e+285,1e+286,1e+287,1e+288,1e+289,1e+290,1e+291,1e+292,1e+293,1e+294,1e+295,1e+296,1e+297,1e+298,1e+299,1e+300, + 1e+301,1e+302,1e+303,1e+304,1e+305,1e+306,1e+307,1e+308 + }; + RAPIDJSON_ASSERT(n >= 0 && n <= 308); + return e[n]; +} + +} // namespace internal +BUTIL_RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_POW10_ diff --git a/src/butil/third_party/rapidjson/internal/stack.h b/src/butil/third_party/rapidjson/internal/stack.h new file mode 100644 index 0000000..36b0774 --- /dev/null +++ b/src/butil/third_party/rapidjson/internal/stack.h @@ -0,0 +1,179 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_STACK_H_ +#define RAPIDJSON_INTERNAL_STACK_H_ + +#include "../rapidjson.h" + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +/////////////////////////////////////////////////////////////////////////////// +// Stack + +//! A type-unsafe stack for storing different types of data. +/*! \tparam Allocator Allocator for allocating stack memory. +*/ +template +class Stack { +public: + // Optimization note: Do not allocate memory for stack_ in constructor. + // Do it lazily when first Push() -> Expand() -> Resize(). + Stack(Allocator* allocator, size_t stackCapacity) : allocator_(allocator), ownAllocator_(0), stack_(0), stackTop_(0), stackEnd_(0), initialCapacity_(stackCapacity) { + RAPIDJSON_ASSERT(stackCapacity > 0); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + Stack(Stack&& rhs) + : allocator_(rhs.allocator_), + ownAllocator_(rhs.ownAllocator_), + stack_(rhs.stack_), + stackTop_(rhs.stackTop_), + stackEnd_(rhs.stackEnd_), + initialCapacity_(rhs.initialCapacity_) + { + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.stack_ = 0; + rhs.stackTop_ = 0; + rhs.stackEnd_ = 0; + rhs.initialCapacity_ = 0; + } +#endif + + ~Stack() { + Destroy(); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + Stack& operator=(Stack&& rhs) { + if (&rhs != this) + { + Destroy(); + + allocator_ = rhs.allocator_; + ownAllocator_ = rhs.ownAllocator_; + stack_ = rhs.stack_; + stackTop_ = rhs.stackTop_; + stackEnd_ = rhs.stackEnd_; + initialCapacity_ = rhs.initialCapacity_; + + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.stack_ = 0; + rhs.stackTop_ = 0; + rhs.stackEnd_ = 0; + rhs.initialCapacity_ = 0; + } + return *this; + } +#endif + + void Clear() { stackTop_ = stack_; } + + void ShrinkToFit() { + if (Empty()) { + // If the stack is empty, completely deallocate the memory. + Allocator::Free(stack_); + stack_ = 0; + stackTop_ = 0; + stackEnd_ = 0; + } + else + Resize(GetSize()); + } + + // Optimization note: try to minimize the size of this function for force inline. + // Expansion is run very infrequently, so it is moved to another (probably non-inline) function. + template + RAPIDJSON_FORCEINLINE T* Push(size_t count = 1) { + // Expand the stack if needed + if (stackTop_ + sizeof(T) * count >= stackEnd_) + Expand(count); + + T* ret = reinterpret_cast(stackTop_); + stackTop_ += sizeof(T) * count; + return ret; + } + + template + T* Pop(size_t count) { + RAPIDJSON_ASSERT(GetSize() >= count * sizeof(T)); + stackTop_ -= count * sizeof(T); + return reinterpret_cast(stackTop_); + } + + template + T* Top() { + RAPIDJSON_ASSERT(GetSize() >= sizeof(T)); + return reinterpret_cast(stackTop_ - sizeof(T)); + } + + template + T* Bottom() { return (T*)stack_; } + + Allocator& GetAllocator() { return *allocator_; } + bool Empty() const { return stackTop_ == stack_; } + size_t GetSize() const { return static_cast(stackTop_ - stack_); } + size_t GetCapacity() const { return static_cast(stackEnd_ - stack_); } + +private: + template + void Expand(size_t count) { + // Only expand the capacity if the current stack exists. Otherwise just create a stack with initial capacity. + size_t newCapacity; + if (stack_ == 0) { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); + newCapacity = initialCapacity_; + } else { + newCapacity = GetCapacity(); + newCapacity += (newCapacity + 1) / 2; + } + size_t newSize = GetSize() + sizeof(T) * count; + if (newCapacity < newSize) + newCapacity = newSize; + + Resize(newCapacity); + } + + void Resize(size_t newCapacity) { + const size_t size = GetSize(); // Backup the current size + stack_ = (char*)allocator_->Realloc(stack_, GetCapacity(), newCapacity); + stackTop_ = stack_ + size; + stackEnd_ = stack_ + newCapacity; + } + + void Destroy() { + Allocator::Free(stack_); + RAPIDJSON_DELETE(ownAllocator_); // Only delete if it is owned by the stack + } + + // Prohibit copy constructor & assignment operator. + Stack(const Stack&); + Stack& operator=(const Stack&); + + Allocator* allocator_; + Allocator* ownAllocator_; + char *stack_; + char *stackTop_; + char *stackEnd_; + size_t initialCapacity_; +}; + +} // namespace internal +BUTIL_RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_STACK_H_ diff --git a/src/butil/third_party/rapidjson/internal/strfunc.h b/src/butil/third_party/rapidjson/internal/strfunc.h new file mode 100644 index 0000000..25527ad --- /dev/null +++ b/src/butil/third_party/rapidjson/internal/strfunc.h @@ -0,0 +1,39 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_STRFUNC_H_ +#define RAPIDJSON_INTERNAL_STRFUNC_H_ + +#include "../rapidjson.h" + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +//! Custom strlen() which works on different character types. +/*! \tparam Ch Character type (e.g. char, wchar_t, short) + \param s Null-terminated input string. + \return Number of characters in the string. + \note This has the same semantics as strlen(), the return value is not number of Unicode codepoints. +*/ +template +inline SizeType StrLen(const Ch* s) { + const Ch* p = s; + while (*p) ++p; + return SizeType(p - s); +} + +} // namespace internal +BUTIL_RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_INTERNAL_STRFUNC_H_ diff --git a/src/butil/third_party/rapidjson/internal/strtod.h b/src/butil/third_party/rapidjson/internal/strtod.h new file mode 100644 index 0000000..b334e81 --- /dev/null +++ b/src/butil/third_party/rapidjson/internal/strtod.h @@ -0,0 +1,270 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_STRTOD_ +#define RAPIDJSON_STRTOD_ + +#include "../rapidjson.h" +#include "ieee754.h" +#include "biginteger.h" +#include "diyfp.h" +#include "pow10.h" + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +inline double FastPath(double significand, int exp) { + if (exp < -308) + return 0.0; + else if (exp >= 0) + return significand * internal::Pow10(exp); + else + return significand / internal::Pow10(-exp); +} + +inline double StrtodNormalPrecision(double d, int p) { + if (p < -308) { + // Prevent expSum < -308, making Pow10(p) = 0 + d = FastPath(d, -308); + d = FastPath(d, p + 308); + } + else + d = FastPath(d, p); + return d; +} + +template +inline T Min3(T a, T b, T c) { + T m = a; + if (m > b) m = b; + if (m > c) m = c; + return m; +} + +inline int CheckWithinHalfULP(double b, const BigInteger& d, int dExp) { + const Double db(b); + const uint64_t bInt = db.IntegerSignificand(); + const int bExp = db.IntegerExponent(); + const int hExp = bExp - 1; + + int dS_Exp2 = 0, dS_Exp5 = 0, bS_Exp2 = 0, bS_Exp5 = 0, hS_Exp2 = 0, hS_Exp5 = 0; + + // Adjust for decimal exponent + if (dExp >= 0) { + dS_Exp2 += dExp; + dS_Exp5 += dExp; + } + else { + bS_Exp2 -= dExp; + bS_Exp5 -= dExp; + hS_Exp2 -= dExp; + hS_Exp5 -= dExp; + } + + // Adjust for binary exponent + if (bExp >= 0) + bS_Exp2 += bExp; + else { + dS_Exp2 -= bExp; + hS_Exp2 -= bExp; + } + + // Adjust for half ulp exponent + if (hExp >= 0) + hS_Exp2 += hExp; + else { + dS_Exp2 -= hExp; + bS_Exp2 -= hExp; + } + + // Remove common power of two factor from all three scaled values + int common_Exp2 = Min3(dS_Exp2, bS_Exp2, hS_Exp2); + dS_Exp2 -= common_Exp2; + bS_Exp2 -= common_Exp2; + hS_Exp2 -= common_Exp2; + + BigInteger dS = d; + dS.MultiplyPow5(dS_Exp5) <<= dS_Exp2; + + BigInteger bS(bInt); + bS.MultiplyPow5(bS_Exp5) <<= bS_Exp2; + + BigInteger hS(1); + hS.MultiplyPow5(hS_Exp5) <<= hS_Exp2; + + BigInteger delta(0); + dS.Difference(bS, &delta); + + return delta.Compare(hS); +} + +inline bool StrtodFast(double d, int p, double* result) { + // Use fast path for string-to-double conversion if possible + // see http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/ + if (p > 22 && p < 22 + 16) { + // Fast Path Cases In Disguise + d *= internal::Pow10(p - 22); + p = 22; + } + + if (p >= -22 && p <= 22 && d <= 9007199254740991.0) { // 2^53 - 1 + *result = FastPath(d, p); + return true; + } + else + return false; +} + +// Compute an approximation and see if it is within 1/2 ULP +inline bool StrtodDiyFp(const char* decimals, size_t length, size_t decimalPosition, int exp, double* result) { + uint64_t significand = 0; + size_t i = 0; // 2^64 - 1 = 18446744073709551615, 1844674407370955161 = 0x1999999999999999 + for (; i < length; i++) { + if (significand > RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) || + (significand == RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) && decimals[i] > '5')) + break; + significand = significand * 10 + (decimals[i] - '0'); + } + + if (i < length && decimals[i] >= '5') // Rounding + significand++; + + size_t remaining = length - i; + const unsigned kUlpShift = 3; + const unsigned kUlp = 1 << kUlpShift; + int error = (remaining == 0) ? 0 : kUlp / 2; + + DiyFp v(significand, 0); + v = v.Normalize(); + error <<= -v.e; + + const int dExp = (int)decimalPosition - (int)i + exp; + + int actualExp; + DiyFp cachedPower = GetCachedPower10(dExp, &actualExp); + if (actualExp != dExp) { + static const DiyFp kPow10[] = { + DiyFp(RAPIDJSON_UINT64_C2(0xa0000000, 00000000), -60), // 10^1 + DiyFp(RAPIDJSON_UINT64_C2(0xc8000000, 00000000), -57), // 10^2 + DiyFp(RAPIDJSON_UINT64_C2(0xfa000000, 00000000), -54), // 10^3 + DiyFp(RAPIDJSON_UINT64_C2(0x9c400000, 00000000), -50), // 10^4 + DiyFp(RAPIDJSON_UINT64_C2(0xc3500000, 00000000), -47), // 10^5 + DiyFp(RAPIDJSON_UINT64_C2(0xf4240000, 00000000), -44), // 10^6 + DiyFp(RAPIDJSON_UINT64_C2(0x98968000, 00000000), -40) // 10^7 + }; + int adjustment = dExp - actualExp - 1; + RAPIDJSON_ASSERT(adjustment >= 0 && adjustment < 7); + v = v * kPow10[adjustment]; + if (length + adjustment > 19) // has more digits than decimal digits in 64-bit + error += kUlp / 2; + } + + v = v * cachedPower; + + error += kUlp + (error == 0 ? 0 : 1); + + const int oldExp = v.e; + v = v.Normalize(); + error <<= oldExp - v.e; + + const unsigned effectiveSignificandSize = Double::EffectiveSignificandSize(64 + v.e); + unsigned precisionSize = 64 - effectiveSignificandSize; + if (precisionSize + kUlpShift >= 64) { + unsigned scaleExp = (precisionSize + kUlpShift) - 63; + v.f >>= scaleExp; + v.e += scaleExp; + error = (error >> scaleExp) + 1 + kUlp; + precisionSize -= scaleExp; + } + + DiyFp rounded(v.f >> precisionSize, v.e + precisionSize); + const uint64_t precisionBits = (v.f & ((uint64_t(1) << precisionSize) - 1)) * kUlp; + const uint64_t halfWay = (uint64_t(1) << (precisionSize - 1)) * kUlp; + if (precisionBits >= halfWay + error) { + rounded.f++; + if (rounded.f & (DiyFp::kDpHiddenBit << 1)) { // rounding overflows mantissa (issue #340) + rounded.f >>= 1; + rounded.e++; + } + } + + *result = rounded.ToDouble(); + + return halfWay - error >= precisionBits || precisionBits >= halfWay + error; +} + +inline double StrtodBigInteger(double approx, const char* decimals, size_t length, size_t decimalPosition, int exp) { + const BigInteger dInt(decimals, length); + const int dExp = (int)decimalPosition - (int)length + exp; + Double a(approx); + int cmp = CheckWithinHalfULP(a.Value(), dInt, dExp); + if (cmp < 0) + return a.Value(); // within half ULP + else if (cmp == 0) { + // Round towards even + if (a.Significand() & 1) + return a.NextPositiveDouble(); + else + return a.Value(); + } + else // adjustment + return a.NextPositiveDouble(); +} + +inline double StrtodFullPrecision(double d, int p, const char* decimals, size_t length, size_t decimalPosition, int exp) { + RAPIDJSON_ASSERT(d >= 0.0); + RAPIDJSON_ASSERT(length >= 1); + + double result; + if (StrtodFast(d, p, &result)) + return result; + + // Trim leading zeros + while (*decimals == '0' && length > 1) { + length--; + decimals++; + decimalPosition--; + } + + // Trim trailing zeros + while (decimals[length - 1] == '0' && length > 1) { + length--; + decimalPosition--; + exp++; + } + + // Trim right-most digits + const int kMaxDecimalDigit = 780; + if ((int)length > kMaxDecimalDigit) { + int delta = (int(length) - kMaxDecimalDigit); + exp += delta; + decimalPosition -= delta; + length = kMaxDecimalDigit; + } + + // If too small, underflow to zero + if (int(length) + exp < -324) + return 0.0; + + if (StrtodDiyFp(decimals, length, decimalPosition, exp, &result)) + return result; + + // Use approximation from StrtodDiyFp and make adjustment with BigInteger comparison + return StrtodBigInteger(result, decimals, length, decimalPosition, exp); +} + +} // namespace internal +BUTIL_RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_STRTOD_ diff --git a/src/butil/third_party/rapidjson/memorybuffer.h b/src/butil/third_party/rapidjson/memorybuffer.h new file mode 100644 index 0000000..8e266c6 --- /dev/null +++ b/src/butil/third_party/rapidjson/memorybuffer.h @@ -0,0 +1,70 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_MEMORYBUFFER_H_ +#define RAPIDJSON_MEMORYBUFFER_H_ + +#include "rapidjson.h" +#include "internal/stack.h" + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN + +//! Represents an in-memory output byte stream. +/*! + This class is mainly for being wrapped by EncodedOutputStream or AutoUTFOutputStream. + + It is similar to FileWriteBuffer but the destination is an in-memory buffer instead of a file. + + Differences between MemoryBuffer and StringBuffer: + 1. StringBuffer has Encoding but MemoryBuffer is only a byte buffer. + 2. StringBuffer::GetString() returns a null-terminated string. MemoryBuffer::GetBuffer() returns a buffer without terminator. + + \tparam Allocator type for allocating memory buffer. + \note implements Stream concept +*/ +template +struct GenericMemoryBuffer { + typedef char Ch; // byte + + GenericMemoryBuffer(Allocator* allocator = 0, size_t capacity = kDefaultCapacity) : stack_(allocator, capacity) {} + + void Put(Ch c) { *stack_.template Push() = c; } + void Flush() {} + + void Clear() { stack_.Clear(); } + void ShrinkToFit() { stack_.ShrinkToFit(); } + Ch* Push(size_t count) { return stack_.template Push(count); } + void Pop(size_t count) { stack_.template Pop(count); } + + const Ch* GetBuffer() const { + return stack_.template Bottom(); + } + + size_t GetSize() const { return stack_.GetSize(); } + + static const size_t kDefaultCapacity = 256; + mutable internal::Stack stack_; +}; + +typedef GenericMemoryBuffer<> MemoryBuffer; + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(MemoryBuffer& memoryBuffer, char c, size_t n) { + std::memset(memoryBuffer.stack_.Push(n), c, n * sizeof(c)); +} + +BUTIL_RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_MEMORYBUFFER_H_ diff --git a/src/butil/third_party/rapidjson/memorystream.h b/src/butil/third_party/rapidjson/memorystream.h new file mode 100644 index 0000000..456ea73 --- /dev/null +++ b/src/butil/third_party/rapidjson/memorystream.h @@ -0,0 +1,61 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_MEMORYSTREAM_H_ +#define RAPIDJSON_MEMORYSTREAM_H_ + +#include "rapidjson.h" + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN + +//! Represents an in-memory input byte stream. +/*! + This class is mainly for being wrapped by EncodedInputStream or AutoUTFInputStream. + + It is similar to FileReadBuffer but the source is an in-memory buffer instead of a file. + + Differences between MemoryStream and StringStream: + 1. StringStream has encoding but MemoryStream is a byte stream. + 2. MemoryStream needs size of the source buffer and the buffer don't need to be null terminated. StringStream assume null-terminated string as source. + 3. MemoryStream supports Peek4() for encoding detection. StringStream is specified with an encoding so it should not have Peek4(). + \note implements Stream concept +*/ +struct MemoryStream { + typedef char Ch; // byte + + MemoryStream(const Ch *src, size_t size) : src_(src), begin_(src), end_(src + size), size_(size) {} + + Ch Peek() const { return (src_ == end_) ? '\0' : *src_; } + Ch Take() { return (src_ == end_) ? '\0' : *src_++; } + size_t Tell() const { return static_cast(src_ - begin_); } + + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + // For encoding detection only. + const Ch* Peek4() const { + return Tell() + 4 <= size_ ? src_ : 0; + } + + const Ch* src_; //!< Current read position. + const Ch* begin_; //!< Original head of the string. + const Ch* end_; //!< End of stream. + size_t size_; //!< Size of the stream. +}; + +BUTIL_RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_MEMORYBUFFER_H_ diff --git a/src/butil/third_party/rapidjson/msinttypes/inttypes.h b/src/butil/third_party/rapidjson/msinttypes/inttypes.h new file mode 100644 index 0000000..1811128 --- /dev/null +++ b/src/butil/third_party/rapidjson/msinttypes/inttypes.h @@ -0,0 +1,316 @@ +// ISO C9x compliant inttypes.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2013 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the product nor the names of its contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +// The above software in this distribution may have been modified by +// THL A29 Limited ("Tencent Modifications"). +// All Tencent Modifications are Copyright (C) 2015 THL A29 Limited. + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_INTTYPES_H_ // [ +#define _MSC_INTTYPES_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#include "stdint.h" + +// miloyip: VC supports inttypes.h since VC2013 +#if _MSC_VER >= 1800 +#include +#else + +// 7.8 Format conversion of integer types + +typedef struct { + intmax_t quot; + intmax_t rem; +} imaxdiv_t; + +// 7.8.1 Macros for format specifiers + +#if !defined(__cplusplus) || defined(__STDC_FORMAT_MACROS) // [ See footnote 185 at page 198 + +// The fprintf macros for signed integers are: +#define PRId8 "d" +#define PRIi8 "i" +#define PRIdLEAST8 "d" +#define PRIiLEAST8 "i" +#define PRIdFAST8 "d" +#define PRIiFAST8 "i" + +#define PRId16 "hd" +#define PRIi16 "hi" +#define PRIdLEAST16 "hd" +#define PRIiLEAST16 "hi" +#define PRIdFAST16 "hd" +#define PRIiFAST16 "hi" + +#define PRId32 "I32d" +#define PRIi32 "I32i" +#define PRIdLEAST32 "I32d" +#define PRIiLEAST32 "I32i" +#define PRIdFAST32 "I32d" +#define PRIiFAST32 "I32i" + +#define PRId64 "I64d" +#define PRIi64 "I64i" +#define PRIdLEAST64 "I64d" +#define PRIiLEAST64 "I64i" +#define PRIdFAST64 "I64d" +#define PRIiFAST64 "I64i" + +#define PRIdMAX "I64d" +#define PRIiMAX "I64i" + +#define PRIdPTR "Id" +#define PRIiPTR "Ii" + +// The fprintf macros for unsigned integers are: +#define PRIo8 "o" +#define PRIu8 "u" +#define PRIx8 "x" +#define PRIX8 "X" +#define PRIoLEAST8 "o" +#define PRIuLEAST8 "u" +#define PRIxLEAST8 "x" +#define PRIXLEAST8 "X" +#define PRIoFAST8 "o" +#define PRIuFAST8 "u" +#define PRIxFAST8 "x" +#define PRIXFAST8 "X" + +#define PRIo16 "ho" +#define PRIu16 "hu" +#define PRIx16 "hx" +#define PRIX16 "hX" +#define PRIoLEAST16 "ho" +#define PRIuLEAST16 "hu" +#define PRIxLEAST16 "hx" +#define PRIXLEAST16 "hX" +#define PRIoFAST16 "ho" +#define PRIuFAST16 "hu" +#define PRIxFAST16 "hx" +#define PRIXFAST16 "hX" + +#define PRIo32 "I32o" +#define PRIu32 "I32u" +#define PRIx32 "I32x" +#define PRIX32 "I32X" +#define PRIoLEAST32 "I32o" +#define PRIuLEAST32 "I32u" +#define PRIxLEAST32 "I32x" +#define PRIXLEAST32 "I32X" +#define PRIoFAST32 "I32o" +#define PRIuFAST32 "I32u" +#define PRIxFAST32 "I32x" +#define PRIXFAST32 "I32X" + +#define PRIo64 "I64o" +#define PRIu64 "I64u" +#define PRIx64 "I64x" +#define PRIX64 "I64X" +#define PRIoLEAST64 "I64o" +#define PRIuLEAST64 "I64u" +#define PRIxLEAST64 "I64x" +#define PRIXLEAST64 "I64X" +#define PRIoFAST64 "I64o" +#define PRIuFAST64 "I64u" +#define PRIxFAST64 "I64x" +#define PRIXFAST64 "I64X" + +#define PRIoMAX "I64o" +#define PRIuMAX "I64u" +#define PRIxMAX "I64x" +#define PRIXMAX "I64X" + +#define PRIoPTR "Io" +#define PRIuPTR "Iu" +#define PRIxPTR "Ix" +#define PRIXPTR "IX" + +// The fscanf macros for signed integers are: +#define SCNd8 "d" +#define SCNi8 "i" +#define SCNdLEAST8 "d" +#define SCNiLEAST8 "i" +#define SCNdFAST8 "d" +#define SCNiFAST8 "i" + +#define SCNd16 "hd" +#define SCNi16 "hi" +#define SCNdLEAST16 "hd" +#define SCNiLEAST16 "hi" +#define SCNdFAST16 "hd" +#define SCNiFAST16 "hi" + +#define SCNd32 "ld" +#define SCNi32 "li" +#define SCNdLEAST32 "ld" +#define SCNiLEAST32 "li" +#define SCNdFAST32 "ld" +#define SCNiFAST32 "li" + +#define SCNd64 "I64d" +#define SCNi64 "I64i" +#define SCNdLEAST64 "I64d" +#define SCNiLEAST64 "I64i" +#define SCNdFAST64 "I64d" +#define SCNiFAST64 "I64i" + +#define SCNdMAX "I64d" +#define SCNiMAX "I64i" + +#ifdef _WIN64 // [ +# define SCNdPTR "I64d" +# define SCNiPTR "I64i" +#else // _WIN64 ][ +# define SCNdPTR "ld" +# define SCNiPTR "li" +#endif // _WIN64 ] + +// The fscanf macros for unsigned integers are: +#define SCNo8 "o" +#define SCNu8 "u" +#define SCNx8 "x" +#define SCNX8 "X" +#define SCNoLEAST8 "o" +#define SCNuLEAST8 "u" +#define SCNxLEAST8 "x" +#define SCNXLEAST8 "X" +#define SCNoFAST8 "o" +#define SCNuFAST8 "u" +#define SCNxFAST8 "x" +#define SCNXFAST8 "X" + +#define SCNo16 "ho" +#define SCNu16 "hu" +#define SCNx16 "hx" +#define SCNX16 "hX" +#define SCNoLEAST16 "ho" +#define SCNuLEAST16 "hu" +#define SCNxLEAST16 "hx" +#define SCNXLEAST16 "hX" +#define SCNoFAST16 "ho" +#define SCNuFAST16 "hu" +#define SCNxFAST16 "hx" +#define SCNXFAST16 "hX" + +#define SCNo32 "lo" +#define SCNu32 "lu" +#define SCNx32 "lx" +#define SCNX32 "lX" +#define SCNoLEAST32 "lo" +#define SCNuLEAST32 "lu" +#define SCNxLEAST32 "lx" +#define SCNXLEAST32 "lX" +#define SCNoFAST32 "lo" +#define SCNuFAST32 "lu" +#define SCNxFAST32 "lx" +#define SCNXFAST32 "lX" + +#define SCNo64 "I64o" +#define SCNu64 "I64u" +#define SCNx64 "I64x" +#define SCNX64 "I64X" +#define SCNoLEAST64 "I64o" +#define SCNuLEAST64 "I64u" +#define SCNxLEAST64 "I64x" +#define SCNXLEAST64 "I64X" +#define SCNoFAST64 "I64o" +#define SCNuFAST64 "I64u" +#define SCNxFAST64 "I64x" +#define SCNXFAST64 "I64X" + +#define SCNoMAX "I64o" +#define SCNuMAX "I64u" +#define SCNxMAX "I64x" +#define SCNXMAX "I64X" + +#ifdef _WIN64 // [ +# define SCNoPTR "I64o" +# define SCNuPTR "I64u" +# define SCNxPTR "I64x" +# define SCNXPTR "I64X" +#else // _WIN64 ][ +# define SCNoPTR "lo" +# define SCNuPTR "lu" +# define SCNxPTR "lx" +# define SCNXPTR "lX" +#endif // _WIN64 ] + +#endif // __STDC_FORMAT_MACROS ] + +// 7.8.2 Functions for greatest-width integer types + +// 7.8.2.1 The imaxabs function +#define imaxabs _abs64 + +// 7.8.2.2 The imaxdiv function + +// This is modified version of div() function from Microsoft's div.c found +// in %MSVC.NET%\crt\src\div.c +#ifdef STATIC_IMAXDIV // [ +static +#else // STATIC_IMAXDIV ][ +_inline +#endif // STATIC_IMAXDIV ] +imaxdiv_t __cdecl imaxdiv(intmax_t numer, intmax_t denom) +{ + imaxdiv_t result; + + result.quot = numer / denom; + result.rem = numer % denom; + + if (numer < 0 && result.rem > 0) { + // did division wrong; must fix up + ++result.quot; + result.rem -= denom; + } + + return result; +} + +// 7.8.2.3 The strtoimax and strtoumax functions +#define strtoimax _strtoi64 +#define strtoumax _strtoui64 + +// 7.8.2.4 The wcstoimax and wcstoumax functions +#define wcstoimax _wcstoi64 +#define wcstoumax _wcstoui64 + +#endif // _MSC_VER >= 1800 + +#endif // _MSC_INTTYPES_H_ ] diff --git a/src/butil/third_party/rapidjson/msinttypes/stdint.h b/src/butil/third_party/rapidjson/msinttypes/stdint.h new file mode 100644 index 0000000..a26fff4 --- /dev/null +++ b/src/butil/third_party/rapidjson/msinttypes/stdint.h @@ -0,0 +1,300 @@ +// ISO C9x compliant stdint.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2013 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the product nor the names of its contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +// The above software in this distribution may have been modified by +// THL A29 Limited ("Tencent Modifications"). +// All Tencent Modifications are Copyright (C) 2015 THL A29 Limited. + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_STDINT_H_ // [ +#define _MSC_STDINT_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +// miloyip: Originally Visual Studio 2010 uses its own stdint.h. However it generates warning with INT64_C(), so change to use this file for vs2010. +#if _MSC_VER >= 1600 // [ +#include + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +#undef INT8_C +#undef INT16_C +#undef INT32_C +#undef INT64_C +#undef UINT8_C +#undef UINT16_C +#undef UINT32_C +#undef UINT64_C + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +// These #ifndef's are needed to prevent collisions with . +// Check out Issue 9 for the details. +#ifndef INTMAX_C // [ +# define INTMAX_C INT64_C +#endif // INTMAX_C ] +#ifndef UINTMAX_C // [ +# define UINTMAX_C UINT64_C +#endif // UINTMAX_C ] + +#endif // __STDC_CONSTANT_MACROS ] + +#else // ] _MSC_VER >= 1700 [ + +#include + +// For Visual Studio 6 in C++ mode and for many Visual Studio versions when +// compiling for ARM we should wrap include with 'extern "C++" {}' +// or compiler give many errors like this: +// error C2733: second C linkage of overloaded function 'wmemchr' not allowed +#ifdef __cplusplus +extern "C" { +#endif +# include +#ifdef __cplusplus +} +#endif + +// Define _W64 macros to mark types changing their size, like intptr_t. +#ifndef _W64 +# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 +# define _W64 __w64 +# else +# define _W64 +# endif +#endif + + +// 7.18.1 Integer types + +// 7.18.1.1 Exact-width integer types + +// Visual Studio 6 and Embedded Visual C++ 4 doesn't +// realize that, e.g. char has the same size as __int8 +// so we give up on __intX for them. +#if (_MSC_VER < 1300) + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; +#else + typedef signed __int8 int8_t; + typedef signed __int16 int16_t; + typedef signed __int32 int32_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; +#endif +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + + +// 7.18.1.2 Minimum-width integer types +typedef int8_t int_least8_t; +typedef int16_t int_least16_t; +typedef int32_t int_least32_t; +typedef int64_t int_least64_t; +typedef uint8_t uint_least8_t; +typedef uint16_t uint_least16_t; +typedef uint32_t uint_least32_t; +typedef uint64_t uint_least64_t; + +// 7.18.1.3 Fastest minimum-width integer types +typedef int8_t int_fast8_t; +typedef int16_t int_fast16_t; +typedef int32_t int_fast32_t; +typedef int64_t int_fast64_t; +typedef uint8_t uint_fast8_t; +typedef uint16_t uint_fast16_t; +typedef uint32_t uint_fast32_t; +typedef uint64_t uint_fast64_t; + +// 7.18.1.4 Integer types capable of holding object pointers +#ifdef _WIN64 // [ + typedef signed __int64 intptr_t; + typedef unsigned __int64 uintptr_t; +#else // _WIN64 ][ + typedef _W64 signed int intptr_t; + typedef _W64 unsigned int uintptr_t; +#endif // _WIN64 ] + +// 7.18.1.5 Greatest-width integer types +typedef int64_t intmax_t; +typedef uint64_t uintmax_t; + + +// 7.18.2 Limits of specified-width integer types + +#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 + +// 7.18.2.1 Limits of exact-width integer types +#define INT8_MIN ((int8_t)_I8_MIN) +#define INT8_MAX _I8_MAX +#define INT16_MIN ((int16_t)_I16_MIN) +#define INT16_MAX _I16_MAX +#define INT32_MIN ((int32_t)_I32_MIN) +#define INT32_MAX _I32_MAX +#define INT64_MIN ((int64_t)_I64_MIN) +#define INT64_MAX _I64_MAX +#define UINT8_MAX _UI8_MAX +#define UINT16_MAX _UI16_MAX +#define UINT32_MAX _UI32_MAX +#define UINT64_MAX _UI64_MAX + +// 7.18.2.2 Limits of minimum-width integer types +#define INT_LEAST8_MIN INT8_MIN +#define INT_LEAST8_MAX INT8_MAX +#define INT_LEAST16_MIN INT16_MIN +#define INT_LEAST16_MAX INT16_MAX +#define INT_LEAST32_MIN INT32_MIN +#define INT_LEAST32_MAX INT32_MAX +#define INT_LEAST64_MIN INT64_MIN +#define INT_LEAST64_MAX INT64_MAX +#define UINT_LEAST8_MAX UINT8_MAX +#define UINT_LEAST16_MAX UINT16_MAX +#define UINT_LEAST32_MAX UINT32_MAX +#define UINT_LEAST64_MAX UINT64_MAX + +// 7.18.2.3 Limits of fastest minimum-width integer types +#define INT_FAST8_MIN INT8_MIN +#define INT_FAST8_MAX INT8_MAX +#define INT_FAST16_MIN INT16_MIN +#define INT_FAST16_MAX INT16_MAX +#define INT_FAST32_MIN INT32_MIN +#define INT_FAST32_MAX INT32_MAX +#define INT_FAST64_MIN INT64_MIN +#define INT_FAST64_MAX INT64_MAX +#define UINT_FAST8_MAX UINT8_MAX +#define UINT_FAST16_MAX UINT16_MAX +#define UINT_FAST32_MAX UINT32_MAX +#define UINT_FAST64_MAX UINT64_MAX + +// 7.18.2.4 Limits of integer types capable of holding object pointers +#ifdef _WIN64 // [ +# define INTPTR_MIN INT64_MIN +# define INTPTR_MAX INT64_MAX +# define UINTPTR_MAX UINT64_MAX +#else // _WIN64 ][ +# define INTPTR_MIN INT32_MIN +# define INTPTR_MAX INT32_MAX +# define UINTPTR_MAX UINT32_MAX +#endif // _WIN64 ] + +// 7.18.2.5 Limits of greatest-width integer types +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX +#define UINTMAX_MAX UINT64_MAX + +// 7.18.3 Limits of other integer types + +#ifdef _WIN64 // [ +# define PTRDIFF_MIN _I64_MIN +# define PTRDIFF_MAX _I64_MAX +#else // _WIN64 ][ +# define PTRDIFF_MIN _I32_MIN +# define PTRDIFF_MAX _I32_MAX +#endif // _WIN64 ] + +#define SIG_ATOMIC_MIN INT_MIN +#define SIG_ATOMIC_MAX INT_MAX + +#ifndef SIZE_MAX // [ +# ifdef _WIN64 // [ +# define SIZE_MAX _UI64_MAX +# else // _WIN64 ][ +# define SIZE_MAX _UI32_MAX +# endif // _WIN64 ] +#endif // SIZE_MAX ] + +// WCHAR_MIN and WCHAR_MAX are also defined in +#ifndef WCHAR_MIN // [ +# define WCHAR_MIN 0 +#endif // WCHAR_MIN ] +#ifndef WCHAR_MAX // [ +# define WCHAR_MAX _UI16_MAX +#endif // WCHAR_MAX ] + +#define WINT_MIN 0 +#define WINT_MAX _UI16_MAX + +#endif // __STDC_LIMIT_MACROS ] + + +// 7.18.4 Limits of other integer types + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +// These #ifndef's are needed to prevent collisions with . +// Check out Issue 9 for the details. +#ifndef INTMAX_C // [ +# define INTMAX_C INT64_C +#endif // INTMAX_C ] +#ifndef UINTMAX_C // [ +# define UINTMAX_C UINT64_C +#endif // UINTMAX_C ] + +#endif // __STDC_CONSTANT_MACROS ] + +#endif // _MSC_VER >= 1600 ] + +#endif // _MSC_STDINT_H_ ] diff --git a/src/butil/third_party/rapidjson/optimized_writer.h b/src/butil/third_party/rapidjson/optimized_writer.h new file mode 100644 index 0000000..b038fcb --- /dev/null +++ b/src/butil/third_party/rapidjson/optimized_writer.h @@ -0,0 +1,114 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef RAPIDJSON_OPTIMIZED_WRITER_H +#define RAPIDJSON_OPTIMIZED_WRITER_H + +#include "writer.h" + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN + +//! Optimized writer +/*! This class mainly inherit writer class in rapidjson + * It optimised WriteString function in writer class, + * which not compare and push character one by one and + * in applications when SourceEncoding and TargetEncoding is the same method, + * transcode could be omit. + * When TargetEncoding support unicode, and SourceEncoding and TargetEncoding + * is the same method, WriteString could improve 65% effciency compare with + * writer class in rapidjson. + * */ +template, + typename TargetEncoding = UTF8<>, typename StackAllocator = CrtAllocator> +class OptimizedWriter : + public Writer { +public: + typedef Writer Base; + typedef typename SourceEncoding::Ch Ch; + explicit + OptimizedWriter(OutputStream& os, StackAllocator* stackAllocator = 0, + size_t levelDepth = Base::kDefaultLevelDepth) : + Base(os, stackAllocator, levelDepth) {} + + explicit + OptimizedWriter(StackAllocator* allocator = 0, + size_t levelDepth = Base::kDefaultLevelDepth) : + Base(allocator, levelDepth) {} + + bool String(const Ch* str, SizeType length, bool copy = false) { + (void)copy; + Base::Prefix(kStringType); + return WriteString(str, length); + } + +protected: + bool WriteString(const Ch* str, SizeType length) { + //if TargetEncoding support Unicode + //and SourceEncoding and TargetEncoding are the same type + //just use memcpy to improve efficiency + if (TargetEncoding::supportUnicode && is_same::value) { + static const char hexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + static const char escape[256] = { +#define ESCAPE_ZERO_16 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'b', 't', 'n', 'u', 'f', 'r', 'u', 'u', // 00 + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', // 10 + 0, 0, '"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20 + ESCAPE_ZERO_16, ESCAPE_ZERO_16, // 30~4F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '\\', 0, 0, 0, // 50 + ESCAPE_ZERO_16, ESCAPE_ZERO_16, ESCAPE_ZERO_16, ESCAPE_ZERO_16, ESCAPE_ZERO_16, + ESCAPE_ZERO_16, ESCAPE_ZERO_16, ESCAPE_ZERO_16, ESCAPE_ZERO_16, ESCAPE_ZERO_16 // 60~FF +#undef ESCAPE_ZERO_16 + }; + Base::os_->Put('\"'); + size_t index = 0; + size_t pos = 0; + while (pos < length) { + Ch c = str[pos]; + if ((sizeof(Ch) == 1 || (unsigned)c < 256) && escape[(unsigned char)c]) { + Base::os_->Puts(str + index, pos - index); + index = pos + 1; + Base::os_->Put('\\'); + Base::os_->Put(escape[(unsigned char)str[pos]]); + if (escape[(unsigned char)str[pos]] == 'u') { + Base::os_->Put('0'); + Base::os_->Put('0'); + Base::os_->Put(hexDigits[(unsigned char)str[pos] >> 4]); + Base::os_->Put(hexDigits[(unsigned char)str[pos] & 0xF]); + } + } + pos++; + } + if (index < length) { + Base::os_->Puts(str + index, length - index); + } + Base::os_->Put('\"'); + return true; + } else { + return Base::WriteString(str, length); + } + } + +private: + // Prohibit copy constructor & assignment operator. + OptimizedWriter(const OptimizedWriter&); + OptimizedWriter& operator=(const OptimizedWriter&); +}; +BUTIL_RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_OPTIMIZED_WRITER_H diff --git a/src/butil/third_party/rapidjson/pointer.h b/src/butil/third_party/rapidjson/pointer.h new file mode 100644 index 0000000..de5c9f7 --- /dev/null +++ b/src/butil/third_party/rapidjson/pointer.h @@ -0,0 +1,1313 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_POINTER_H_ +#define RAPIDJSON_POINTER_H_ + +#include "document.h" +#include "internal/itoa.h" + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN + +static const SizeType kPointerInvalidIndex = ~SizeType(0); //!< Represents an invalid index in GenericPointer::Token + +//! Error code of parsing. +/*! \ingroup RAPIDJSON_ERRORS + \see GenericPointer::GenericPointer, GenericPointer::GetParseErrorCode +*/ +enum PointerParseErrorCode { + kPointerParseErrorNone = 0, //!< The parse is successful + + kPointerParseErrorTokenMustBeginWithSolidus, //!< A token must begin with a '/' + kPointerParseErrorInvalidEscape, //!< Invalid escape + kPointerParseErrorInvalidPercentEncoding, //!< Invalid percent encoding in URI fragment + kPointerParseErrorCharacterMustPercentEncode //!< A character must percent encoded in URI fragment +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericPointer + +//! Represents a JSON Pointer. Use Pointer for UTF8 encoding and default allocator. +/*! + This class implements RFC 6901 "JavaScript Object Notation (JSON) Pointer" + (https://tools.ietf.org/html/rfc6901). + + A JSON pointer is for identifying a specific value in a JSON document + (GenericDocument). It can simplify coding of DOM tree manipulation, because it + can access multiple-level depth of DOM tree with single API call. + + After it parses a string representation (e.g. "/foo/0" or URI fragment + representation (e.g. "#/foo/0") into its internal representation (tokens), + it can be used to resolve a specific value in multiple documents, or sub-tree + of documents. + + Contrary to GenericValue, Pointer can be copy constructed and copy assigned. + Apart from assignment, a Pointer cannot be modified after construction. + + Although Pointer is very convenient, please aware that constructing Pointer + involves parsing and dynamic memory allocation. A special constructor with user- + supplied tokens eliminates these. + + GenericPointer depends on GenericDocument and GenericValue. + + \tparam ValueType The value type of the DOM tree. E.g. GenericValue > + \tparam Allocator The allocator type for allocating memory for internal representation. + + \note GenericPointer uses same encoding of ValueType. + However, Allocator of GenericPointer is independent of Allocator of Value. +*/ +template +class GenericPointer { +public: + typedef typename ValueType::EncodingType EncodingType; //!< Encoding type from Value + typedef typename EncodingType::Ch Ch; //!< Character type from Value + + //! A token is the basic units of internal representation. + /*! + A JSON pointer string representation "/foo/123" is parsed to two tokens: + "foo" and 123. 123 will be represented in both numeric form and string form. + They are resolved according to the actual value type (object or array). + + For token that are not numbers, or the numeric value is out of bound + (greater than limits of SizeType), they are only treated as string form + (i.e. the token's index will be equal to kPointerInvalidIndex). + + This struct is public so that user can create a Pointer without parsing and + allocation, using a special constructor. + */ + struct Token { + const Ch* name; //!< Name of the token. It has null character at the end but it can contain null character. + SizeType length; //!< Length of the name. + SizeType index; //!< A valid array index, if it is not equal to kPointerInvalidIndex. + }; + + //!@name Constructors and destructor. + //@{ + + //! Default constructor. + GenericPointer() : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} + + //! Constructor that parses a string or URI fragment representation. + /*! + \param source A null-terminated, string or URI fragment representation of JSON pointer. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + */ + explicit GenericPointer(const Ch* source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source, internal::StrLen(source)); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Constructor that parses a string or URI fragment representation. + /*! + \param source A string or URI fragment representation of JSON pointer. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + explicit GenericPointer(const std::basic_string& source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source.c_str(), source.size()); + } +#endif + + //! Constructor that parses a string or URI fragment representation, with length of the source string. + /*! + \param source A string or URI fragment representation of JSON pointer. + \param length Length of source. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + \note Slightly faster than the overload without length. + */ + GenericPointer(const Ch* source, size_t length, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source, length); + } + + //! Constructor with user-supplied tokens. + /*! + This constructor let user supplies const array of tokens. + This prevents the parsing process and eliminates allocation. + This is preferred for memory constrained environments. + + \param tokens An constant array of tokens representing the JSON pointer. + \param tokenCount Number of tokens. + + \b Example + \code + #define NAME(s) { s, sizeof(s) / sizeof(s[0]) - 1, kPointerInvalidIndex } + #define INDEX(i) { #i, sizeof(#i) - 1, i } + + static const Pointer::Token kTokens[] = { NAME("foo"), INDEX(123) }; + static const Pointer p(kTokens, sizeof(kTokens) / sizeof(kTokens[0])); + // Equivalent to static const Pointer p("/foo/123"); + + #undef NAME + #undef INDEX + \endcode + */ + GenericPointer(const Token* tokens, size_t tokenCount) : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(const_cast(tokens)), tokenCount_(tokenCount), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} + + //! Copy constructor. + GenericPointer(const GenericPointer& rhs) : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + *this = rhs; + } + + //! Destructor. + ~GenericPointer() { + if (nameBuffer_) // If user-supplied tokens constructor is used, nameBuffer_ is nullptr and tokens_ are not deallocated. + Allocator::Free(tokens_); + RAPIDJSON_DELETE(ownAllocator_); + } + + //! Assignment operator. + GenericPointer& operator=(const GenericPointer& rhs) { + if (this != &rhs) { + // Do not delete ownAllcator + if (nameBuffer_) + Allocator::Free(tokens_); + + tokenCount_ = rhs.tokenCount_; + parseErrorOffset_ = rhs.parseErrorOffset_; + parseErrorCode_ = rhs.parseErrorCode_; + + if (rhs.nameBuffer_) + CopyFromRaw(rhs); // Normally parsed tokens. + else { + tokens_ = rhs.tokens_; // User supplied const tokens. + nameBuffer_ = 0; + } + } + return *this; + } + + //@} + + //!@name Append token + //@{ + + //! Append a token and return a new Pointer + /*! + \param token Token to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const Token& token, Allocator* allocator = 0) const { + GenericPointer r; + r.allocator_ = allocator; + Ch *p = r.CopyFromRaw(*this, 1, token.length + 1); + std::memcpy(p, token.name, (token.length + 1) * sizeof(Ch)); + r.tokens_[tokenCount_].name = p; + r.tokens_[tokenCount_].length = token.length; + r.tokens_[tokenCount_].index = token.index; + return r; + } + + //! Append a name token with length, and return a new Pointer + /*! + \param name Name to be appended. + \param length Length of name. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const Ch* name, SizeType length, Allocator* allocator = 0) const { + Token token = { name, length, kPointerInvalidIndex }; + return Append(token, allocator); + } + + //! Append a name token without length, and return a new Pointer + /*! + \param name Name (const Ch*) to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >), (GenericPointer)) + Append(T* name, Allocator* allocator = 0) const { + return Append(name, StrLen(name), allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Append a name token, and return a new Pointer + /*! + \param name Name to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const std::basic_string& name, Allocator* allocator = 0) const { + return Append(name.c_str(), static_cast(name.size()), allocator); + } +#endif + + //! Append a index token, and return a new Pointer + /*! + \param index Index to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(SizeType index, Allocator* allocator = 0) const { + char buffer[21]; + SizeType length = (sizeof(SizeType) == 4 ? internal::u32toa(index, buffer): internal::u64toa(index, buffer)) - buffer; + buffer[length] = '\0'; + + if (sizeof(Ch) == 1) { + Token token = { (Ch*)buffer, length, index }; + return Append(token, allocator); + } + else { + Ch name[21]; + for (size_t i = 0; i <= length; i++) + name[i] = buffer[i]; + Token token = { name, length, index }; + return Append(token, allocator); + } + } + + //! Append a token by value, and return a new Pointer + /*! + \param value Value (either AddUint or String) to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const ValueType& token, Allocator* allocator = 0) const { + if (token.IsString()) + return Append(token.GetString(), token.GetStringLength(), allocator); + else { + RAPIDJSON_ASSERT(token.IsUint64()); + RAPIDJSON_ASSERT(token.GetUint64() <= SizeType(~0)); + return Append(static_cast(token.GetUint64()), allocator); + } + } + + //!@name Handling Parse Error + //@{ + + //! Check whether this is a valid pointer. + bool IsValid() const { return parseErrorCode_ == kPointerParseErrorNone; } + + //! Get the parsing error offset in code unit. + size_t GetParseErrorOffset() const { return parseErrorOffset_; } + + //! Get the parsing error code. + PointerParseErrorCode GetParseErrorCode() const { return parseErrorCode_; } + + //@} + + //!@name Tokens + //@{ + + //! Get the token array (const version only). + const Token* GetTokens() const { return tokens_; } + + //! Get the number of tokens. + size_t GetTokenCount() const { return tokenCount_; } + + //@} + + //!@name Equality/inequality operators + //@{ + + //! Equality operator. + /*! + \note When any pointers are invalid, always returns false. + */ + bool operator==(const GenericPointer& rhs) const { + if (!IsValid() || !rhs.IsValid() || tokenCount_ != rhs.tokenCount_) + return false; + + for (size_t i = 0; i < tokenCount_; i++) { + if (tokens_[i].index != rhs.tokens_[i].index || + tokens_[i].length != rhs.tokens_[i].length || + (tokens_[i].length != 0 && std::memcmp(tokens_[i].name, rhs.tokens_[i].name, sizeof(Ch)* tokens_[i].length) != 0)) + { + return false; + } + } + + return true; + } + + //! Inequality operator. + /*! + \note When any pointers are invalid, always returns true. + */ + bool operator!=(const GenericPointer& rhs) const { return !(*this == rhs); } + + //@} + + //!@name Stringify + //@{ + + //! Stringify the pointer into string representation. + /*! + \tparam OutputStream Type of output stream. + \param os The output stream. + */ + template + bool Stringify(OutputStream& os) const { + return Stringify(os); + } + + //! Stringify the pointer into URI fragment representation. + /*! + \tparam OutputStream Type of output stream. + \param os The output stream. + */ + template + bool StringifyUriFragment(OutputStream& os) const { + return Stringify(os); + } + + //@} + + //!@name Create value + //@{ + + //! Create a value in a subtree. + /*! + If the value is not exist, it creates all parent values and a JSON Null value. + So it always succeed and return the newly created or existing value. + + Remind that it may change types of parents according to tokens, so it + potentially removes previously stored values. For example, if a document + was an array, and "/foo" is used to create a value, then the document + will be changed to an object, and all existing array elements are lost. + + \param root Root value of a DOM subtree to be resolved. It can be any value other than document root. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \param alreadyExist If non-null, it stores whether the resolved value is already exist. + \return The resolved newly created (a JSON Null value), or already exists value. + */ + ValueType& Create(ValueType& root, typename ValueType::AllocatorType& allocator, bool* alreadyExist = 0) const { + RAPIDJSON_ASSERT(IsValid()); + ValueType* v = &root; + bool exist = true; + for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + if (v->IsArray() && t->name[0] == '-' && t->length == 1) { + v->PushBack(Value().Move(), allocator); + v = &((*v)[v->Size() - 1]); + exist = false; + } + else { + if (t->index == kPointerInvalidIndex) { // must be object name + if (!v->IsObject()) + v->SetObject(); // Change to Object + } + else { // object name or array index + if (!v->IsArray() && !v->IsObject()) + v->SetArray(); // Change to Array + } + + if (v->IsArray()) { + if (t->index >= v->Size()) { + v->Reserve(t->index + 1, allocator); + while (t->index >= v->Size()) + v->PushBack(Value().Move(), allocator); + exist = false; + } + v = &((*v)[t->index]); + } + else { + typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); + if (m == v->MemberEnd()) { + v->AddMember(Value(t->name, t->length, allocator).Move(), Value().Move(), allocator); + v = &(--v->MemberEnd())->value; // Assumes AddMember() appends at the end + exist = false; + } + else + v = &m->value; + } + } + } + + if (alreadyExist) + *alreadyExist = exist; + + return *v; + } + + //! Creates a value in a document. + /*! + \param document A document to be resolved. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \param alreadyExist If non-null, it stores whether the resolved value is already exist. + \return The resolved newly created, or already exists value. + */ + template + ValueType& Create(GenericDocument& document, bool* alreadyExist = 0) const { + return Create(document, document.GetAllocator(), alreadyExist); + } + + //@} + + //!@name Query value + //@{ + + //! Query a value in a subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \return Pointer to the value if it can be resolved. Otherwise null. + */ + ValueType* Get(ValueType& root) const { + RAPIDJSON_ASSERT(IsValid()); + ValueType* v = &root; + for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + switch (v->GetType()) { + case kObjectType: + { + typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); + if (m == v->MemberEnd()) + return 0; + v = &m->value; + } + break; + case kArrayType: + if (t->index == kPointerInvalidIndex || t->index >= v->Size()) + return 0; + v = &((*v)[t->index]); + break; + default: + return 0; + } + } + return v; + } + + //! Query a const value in a const subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \return Pointer to the value if it can be resolved. Otherwise null. + */ + const ValueType* Get(const ValueType& root) const { return Get(const_cast(root)); } + + //@} + + //!@name Query a value with default + //@{ + + //! Query a value in a subtree with default value. + /*! + Similar to Get(), but if the specified value do not exists, it creates all parents and clone the default value. + So that this function always succeed. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param defaultValue Default value to be cloned if the value was not exists. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ + ValueType& GetWithDefault(ValueType& root, const ValueType& defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + Value& v = Create(root, allocator, &alreadyExist); + return alreadyExist ? v : v.CopyFrom(defaultValue, allocator); + } + + //! Query a value in a subtree with default null-terminated string. + ValueType& GetWithDefault(ValueType& root, const Ch* defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + Value& v = Create(root, allocator, &alreadyExist); + return alreadyExist ? v : v.SetString(defaultValue, allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Query a value in a subtree with default std::basic_string. + ValueType& GetWithDefault(ValueType& root, const std::basic_string& defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + Value& v = Create(root, allocator, &alreadyExist); + return alreadyExist ? v : v.SetString(defaultValue, allocator); + } +#endif + + //! Query a value in a subtree with default primitive value. + /*! + \tparam T \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + GetWithDefault(ValueType& root, T defaultValue, typename ValueType::AllocatorType& allocator) const { + return GetWithDefault(root, ValueType(defaultValue).Move(), allocator); + } + + //! Query a value in a document with default value. + template + ValueType& GetWithDefault(GenericDocument& document, const ValueType& defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } + + //! Query a value in a document with default null-terminated string. + template + ValueType& GetWithDefault(GenericDocument& document, const Ch* defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Query a value in a document with default std::basic_string. + template + ValueType& GetWithDefault(GenericDocument& document, const std::basic_string& defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } +#endif + + //! Query a value in a document with default primitive value. + /*! + \tparam T \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + GetWithDefault(GenericDocument& document, T defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } + + //@} + + //!@name Set a value + //@{ + + //! Set a value in a subtree, with move semantics. + /*! + It creates all parents if they are not exist or types are different to the tokens. + So this function always succeeds but potentially remove existing values. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param value Value to be set. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ + ValueType& Set(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = value; + } + + //! Set a value in a subtree, with copy semantics. + ValueType& Set(ValueType& root, const ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator).CopyFrom(value, allocator); + } + + //! Set a null-terminated string in a subtree. + ValueType& Set(ValueType& root, const Ch* value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = ValueType(value, allocator).Move(); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Set a std::basic_string in a subtree. + ValueType& Set(ValueType& root, const std::basic_string& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = ValueType(value, allocator).Move(); + } +#endif + + //! Set a primitive value in a subtree. + /*! + \tparam T \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + Set(ValueType& root, T value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = ValueType(value).Move(); + } + + //! Set a value in a document, with move semantics. + template + ValueType& Set(GenericDocument& document, ValueType& value) const { + return Create(document) = value; + } + + //! Set a value in a document, with copy semantics. + template + ValueType& Set(GenericDocument& document, const ValueType& value) const { + return Create(document).CopyFrom(value, document.GetAllocator()); + } + + //! Set a null-terminated string in a document. + template + ValueType& Set(GenericDocument& document, const Ch* value) const { + return Create(document) = ValueType(value, document.GetAllocator()).Move(); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Sets a std::basic_string in a document. + template + ValueType& Set(GenericDocument& document, const std::basic_string& value) const { + return Create(document) = ValueType(value, document.GetAllocator()).Move(); + } +#endif + + //! Set a primitive value in a document. + /*! + \tparam T \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + Set(GenericDocument& document, T value) const { + return Create(document) = value; + } + + //@} + + //!@name Swap a value + //@{ + + //! Swap a value with a value in a subtree. + /*! + It creates all parents if they are not exist or types are different to the tokens. + So this function always succeeds but potentially remove existing values. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param value Value to be swapped. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ + ValueType& Swap(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator).Swap(value); + } + + //! Swap a value with a value in a document. + template + ValueType& Swap(GenericDocument& document, ValueType& value) const { + return Create(document).Swap(value); + } + + //@} + + //! Erase a value in a subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \return Whether the resolved value is found and erased. + + \note Erasing with an empty pointer \c Pointer(""), i.e. the root, always fail and return false. + */ + bool Erase(ValueType& root) const { + RAPIDJSON_ASSERT(IsValid()); + if (tokenCount_ == 0) // Cannot erase the root + return false; + + ValueType* v = &root; + const Token* last = tokens_ + (tokenCount_ - 1); + for (const Token *t = tokens_; t != last; ++t) { + switch (v->GetType()) { + case kObjectType: + { + typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); + if (m == v->MemberEnd()) + return false; + v = &m->value; + } + break; + case kArrayType: + if (t->index == kPointerInvalidIndex || t->index >= v->Size()) + return false; + v = &((*v)[t->index]); + break; + default: + return false; + } + } + + switch (v->GetType()) { + case kObjectType: + return v->EraseMember(GenericStringRef(last->name, last->length)); + case kArrayType: + if (last->index == kPointerInvalidIndex || last->index >= v->Size()) + return false; + v->Erase(v->Begin() + last->index); + return true; + default: + return false; + } + } + +private: + //! Clone the content from rhs to this. + /*! + \param rhs Source pointer. + \param extraToken Extra tokens to be allocated. + \param extraNameBufferSize Extra name buffer size (in number of Ch) to be allocated. + \return Start of non-occupied name buffer, for storing extra names. + */ + Ch* CopyFromRaw(const GenericPointer& rhs, size_t extraToken = 0, size_t extraNameBufferSize = 0) { + if (!allocator_) // allocator is independently owned. + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); + + size_t nameBufferSize = rhs.tokenCount_; // null terminators for tokens + for (Token *t = rhs.tokens_; t != rhs.tokens_ + rhs.tokenCount_; ++t) + nameBufferSize += t->length; + + tokenCount_ = rhs.tokenCount_ + extraToken; + tokens_ = static_cast(allocator_->Malloc(tokenCount_ * sizeof(Token) + (nameBufferSize + extraNameBufferSize) * sizeof(Ch))); + nameBuffer_ = reinterpret_cast(tokens_ + tokenCount_); + std::memcpy(tokens_, rhs.tokens_, rhs.tokenCount_ * sizeof(Token)); + std::memcpy(nameBuffer_, rhs.nameBuffer_, nameBufferSize * sizeof(Ch)); + + // Adjust pointers to name buffer + std::ptrdiff_t diff = nameBuffer_ - rhs.nameBuffer_; + for (Token *t = tokens_; t != tokens_ + rhs.tokenCount_; ++t) + t->name += diff; + + return nameBuffer_ + nameBufferSize; + } + + //! Check whether a character should be percent-encoded. + /*! + According to RFC 3986 2.3 Unreserved Characters. + \param c The character (code unit) to be tested. + */ + bool NeedPercentEncode(Ch c) const { + return !((c >= '0' && c <= '9') || (c >= 'A' && c <='Z') || (c >= 'a' && c <= 'z') || c == '-' || c == '.' || c == '_' || c =='~'); + } + + //! Parse a JSON String or its URI fragment representation into tokens. + /*! + \param source Either a JSON Pointer string, or its URI fragment representation. Not need to be null terminated. + \param length Length of the source string. + \note Source cannot be JSON String Representation of JSON Pointer, e.g. In "/\u0000", \u0000 will not be unescaped. + */ + void Parse(const Ch* source, size_t length) { + RAPIDJSON_ASSERT(source != NULL); + RAPIDJSON_ASSERT(nameBuffer_ == 0); + RAPIDJSON_ASSERT(tokens_ == 0); + + // Create own allocator if user did not supply. + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); + + // Count number of '/' as tokenCount + tokenCount_ = 0; + for (const Ch* s = source; s != source + length; s++) + if (*s == '/') + tokenCount_++; + + Token* token = tokens_ = static_cast(allocator_->Malloc(tokenCount_ * sizeof(Token) + length * sizeof(Ch))); + Ch* name = nameBuffer_ = reinterpret_cast(tokens_ + tokenCount_); + size_t i = 0; + + // Detect if it is a URI fragment + bool uriFragment = false; + if (source[i] == '#') { + uriFragment = true; + i++; + } + + if (i != length && source[i] != '/') { + parseErrorCode_ = kPointerParseErrorTokenMustBeginWithSolidus; + goto error; + } + + while (i < length) { + RAPIDJSON_ASSERT(source[i] == '/'); + i++; // consumes '/' + + token->name = name; + bool isNumber = true; + + while (i < length && source[i] != '/') { + Ch c = source[i]; + if (uriFragment) { + // Decoding percent-encoding for URI fragment + if (c == '%') { + PercentDecodeStream is(&source[i], source + length); + GenericInsituStringStream os(name); + Ch* begin = os.PutBegin(); + if (!Transcoder, EncodingType>().Validate(is, os) || !is.IsValid()) { + parseErrorCode_ = kPointerParseErrorInvalidPercentEncoding; + goto error; + } + size_t len = os.PutEnd(begin); + i += is.Tell() - 1; + if (len == 1) + c = *name; + else { + name += len; + isNumber = false; + i++; + continue; + } + } + else if (NeedPercentEncode(c)) { + parseErrorCode_ = kPointerParseErrorCharacterMustPercentEncode; + goto error; + } + } + + i++; + + // Escaping "~0" -> '~', "~1" -> '/' + if (c == '~') { + if (i < length) { + c = source[i]; + if (c == '0') c = '~'; + else if (c == '1') c = '/'; + else { + parseErrorCode_ = kPointerParseErrorInvalidEscape; + goto error; + } + i++; + } + else { + parseErrorCode_ = kPointerParseErrorInvalidEscape; + goto error; + } + } + + // First check for index: all of characters are digit + if (c < '0' || c > '9') + isNumber = false; + + *name++ = c; + } + token->length = name - token->name; + if (token->length == 0) + isNumber = false; + *name++ = '\0'; // Null terminator + + // Second check for index: more than one digit cannot have leading zero + if (isNumber && token->length > 1 && token->name[0] == '0') + isNumber = false; + + // String to SizeType conversion + SizeType n = 0; + if (isNumber) { + for (size_t j = 0; j < token->length; j++) { + SizeType m = n * 10 + static_cast(token->name[j] - '0'); + if (m < n) { // overflow detection + isNumber = false; + break; + } + n = m; + } + } + + token->index = isNumber ? n : kPointerInvalidIndex; + token++; + } + + RAPIDJSON_ASSERT(name <= nameBuffer_ + length); // Should not overflow buffer + parseErrorCode_ = kPointerParseErrorNone; + return; + + error: + Allocator::Free(tokens_); + nameBuffer_ = 0; + tokens_ = 0; + tokenCount_ = 0; + parseErrorOffset_ = i; + return; + } + + //! Stringify to string or URI fragment representation. + /*! + \tparam uriFragment True for stringifying to URI fragment representation. False for string representation. + \tparam OutputStream type of output stream. + \param os The output stream. + */ + template + bool Stringify(OutputStream& os) const { + RAPIDJSON_ASSERT(IsValid()); + + if (uriFragment) + os.Put('#'); + + for (Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + os.Put('/'); + for (size_t j = 0; j < t->length; j++) { + Ch c = t->name[j]; + if (c == '~') { + os.Put('~'); + os.Put('0'); + } + else if (c == '/') { + os.Put('~'); + os.Put('1'); + } + else if (uriFragment && NeedPercentEncode(c)) { + // Transcode to UTF8 sequence + GenericStringStream source(&t->name[j]); + PercentEncodeStream target(os); + if (!Transcoder >().Validate(source, target)) + return false; + j += source.Tell() - 1; + } + else + os.Put(c); + } + } + return true; + } + + //! A helper stream for decoding a percent-encoded sequence into code unit. + /*! + This stream decodes %XY triplet into code unit (0-255). + If it encounters invalid characters, it sets output code unit as 0 and + mark invalid, and to be checked by IsValid(). + */ + class PercentDecodeStream { + public: + //! Constructor + /*! + \param source Start of the stream + \param end Past-the-end of the stream. + */ + PercentDecodeStream(const Ch* source, const Ch* end) : src_(source), head_(source), end_(end), valid_(true) {} + + Ch Take() { + if (*src_ != '%' || src_ + 3 > end_) { // %XY triplet + valid_ = false; + return 0; + } + src_++; + Ch c = 0; + for (int j = 0; j < 2; j++) { + c <<= 4; + Ch h = *src_; + if (h >= '0' && h <= '9') c += h - '0'; + else if (h >= 'A' && h <= 'F') c += h - 'A' + 10; + else if (h >= 'a' && h <= 'f') c += h - 'a' + 10; + else { + valid_ = false; + return 0; + } + src_++; + } + return c; + } + + size_t Tell() const { return src_ - head_; } + bool IsValid() const { return valid_; } + + private: + const Ch* src_; //!< Current read position. + const Ch* head_; //!< Original head of the string. + const Ch* end_; //!< Past-the-end position. + bool valid_; //!< Whether the parsing is valid. + }; + + //! A helper stream to encode character (UTF-8 code unit) into percent-encoded sequence. + template + class PercentEncodeStream { + public: + PercentEncodeStream(OutputStream& os) : os_(os) {} + void Put(char c) { // UTF-8 must be byte + unsigned char u = static_cast(c); + static const char hexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + os_.Put('%'); + os_.Put(hexDigits[u >> 4]); + os_.Put(hexDigits[u & 15]); + } + private: + OutputStream& os_; + }; + + Allocator* allocator_; //!< The current allocator. It is either user-supplied or equal to ownAllocator_. + Allocator* ownAllocator_; //!< Allocator owned by this Pointer. + Ch* nameBuffer_; //!< A buffer containing all names in tokens. + Token* tokens_; //!< A list of tokens. + size_t tokenCount_; //!< Number of tokens in tokens_. + size_t parseErrorOffset_; //!< Offset in code unit when parsing fail. + PointerParseErrorCode parseErrorCode_; //!< Parsing error code. +}; + +//! GenericPointer for Value (UTF-8, default allocator). +typedef GenericPointer Pointer; + +//!@name Helper functions for GenericPointer +//@{ + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& CreateValueByPointer(T& root, const GenericPointer& pointer, typename T::AllocatorType& a) { + return pointer.Create(root, a); +} + +template +typename T::ValueType& CreateValueByPointer(T& root, const CharType(&source)[N], typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Create(root, a); +} + +// No allocator parameter + +template +typename DocumentType::ValueType& CreateValueByPointer(DocumentType& document, const GenericPointer& pointer) { + return pointer.Create(document); +} + +template +typename DocumentType::ValueType& CreateValueByPointer(DocumentType& document, const CharType(&source)[N]) { + return GenericPointer(source, N - 1).Create(document); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType* GetValueByPointer(T& root, const GenericPointer& pointer) { + return pointer.Get(root); +} + +template +const typename T::ValueType* GetValueByPointer(const T& root, const GenericPointer& pointer) { + return pointer.Get(root); +} + +template +typename T::ValueType* GetValueByPointer(T& root, const CharType (&source)[N]) { + return GenericPointer(source, N - 1).Get(root); +} + +template +const typename T::ValueType* GetValueByPointer(const T& root, const CharType(&source)[N]) { + return GenericPointer(source, N - 1).Get(root); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::Ch* defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const std::basic_string& defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, T2 defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::Ch* defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const std::basic_string& defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +GetValueByPointerWithDefault(T& root, const CharType(&source)[N], T2 defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} + +// No allocator parameter + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::ValueType& defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::Ch* defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const std::basic_string& defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, T2 defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const typename DocumentType::ValueType& defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const typename DocumentType::Ch* defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const std::basic_string& defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], T2 defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::Ch* value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const std::basic_string& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +SetValueByPointer(T& root, const GenericPointer& pointer, T2 value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::Ch* value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const std::basic_string& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +SetValueByPointer(T& root, const CharType(&source)[N], T2 value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +// No allocator parameter + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, typename DocumentType::ValueType& value) { + return pointer.Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::ValueType& value) { + return pointer.Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::Ch* value) { + return pointer.Set(document, value); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const std::basic_string& value) { + return pointer.Set(document, value); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +SetValueByPointer(DocumentType& document, const GenericPointer& pointer, T2 value) { + return pointer.Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], typename DocumentType::ValueType& value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const typename DocumentType::ValueType& value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const typename DocumentType::Ch* value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const std::basic_string& value) { + return GenericPointer(source, N - 1).Set(document, value); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +SetValueByPointer(DocumentType& document, const CharType(&source)[N], T2 value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& SwapValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Swap(root, value, a); +} + +template +typename T::ValueType& SwapValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Swap(root, value, a); +} + +template +typename DocumentType::ValueType& SwapValueByPointer(DocumentType& document, const GenericPointer& pointer, typename DocumentType::ValueType& value) { + return pointer.Swap(document, value); +} + +template +typename DocumentType::ValueType& SwapValueByPointer(DocumentType& document, const CharType(&source)[N], typename DocumentType::ValueType& value) { + return GenericPointer(source, N - 1).Swap(document, value); +} + +////////////////////////////////////////////////////////////////////////////// + +template +bool EraseValueByPointer(T& root, const GenericPointer& pointer) { + return pointer.Erase(root); +} + +template +bool EraseValueByPointer(T& root, const CharType(&source)[N]) { + return GenericPointer(source, N - 1).Erase(root); +} + +//@} + +BUTIL_RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_POINTER_H_ diff --git a/src/butil/third_party/rapidjson/prettywriter.h b/src/butil/third_party/rapidjson/prettywriter.h new file mode 100644 index 0000000..9b0b53a --- /dev/null +++ b/src/butil/third_party/rapidjson/prettywriter.h @@ -0,0 +1,207 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_PRETTYWRITER_H_ +#define RAPIDJSON_PRETTYWRITER_H_ + +#include "optimized_writer.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN + +//! Writer with indentation and spacing. +/*! + \tparam OutputStream Type of ouptut os. + \tparam SourceEncoding Encoding of source string. + \tparam TargetEncoding Encoding of output stream. + \tparam StackAllocator Type of allocator for allocating memory of stack. +*/ +template, typename TargetEncoding = UTF8<>, typename StackAllocator = CrtAllocator> +class PrettyWriter : public OptimizedWriter { +public: + typedef OptimizedWriter Base; + typedef typename Base::Ch Ch; + + //! Constructor + /*! \param os Output stream. + \param allocator User supplied allocator. If it is null, it will create a private one. + \param levelDepth Initial capacity of stack. + */ + PrettyWriter(OutputStream& os, StackAllocator* allocator = 0, size_t levelDepth = Base::kDefaultLevelDepth) : + Base(os, allocator, levelDepth), indentChar_(' '), indentCharCount_(4) {} + + //! Set custom indentation. + /*! \param indentChar Character for indentation. Must be whitespace character (' ', '\\t', '\\n', '\\r'). + \param indentCharCount Number of indent characters for each indentation level. + \note The default indentation is 4 spaces. + */ + PrettyWriter& SetIndent(Ch indentChar, unsigned indentCharCount) { + RAPIDJSON_ASSERT(indentChar == ' ' || indentChar == '\t' || indentChar == '\n' || indentChar == '\r'); + indentChar_ = indentChar; + indentCharCount_ = indentCharCount; + return *this; + } + + /*! @name Implementation of Handler + \see Handler + */ + //@{ + + bool Null() { PrettyPrefix(kNullType); return Base::WriteNull(); } + bool Bool(bool b) { PrettyPrefix(b ? kTrueType : kFalseType); return Base::WriteBool(b); } + bool AddInt(int i) { PrettyPrefix(kNumberType); return Base::WriteInt(i); } + bool AddUint(unsigned u) { PrettyPrefix(kNumberType); return Base::WriteUint(u); } + bool AddInt64(int64_t i64) { PrettyPrefix(kNumberType); return Base::WriteInt64(i64); } + bool AddUint64(uint64_t u64) { PrettyPrefix(kNumberType); return Base::WriteUint64(u64); } + bool Double(double d) { PrettyPrefix(kNumberType); return Base::WriteDouble(d); } + + bool String(const Ch* str, SizeType length, bool copy = false) { + (void)copy; + PrettyPrefix(kStringType); + return Base::WriteString(str, length); + } + +#if RAPIDJSON_HAS_STDSTRING + bool String(const std::basic_string& str) { + return String(str.data(), SizeType(str.size())); + } +#endif + + bool StartObject() { + PrettyPrefix(kObjectType); + new (Base::level_stack_.template Push()) typename Base::Level(false); + return Base::WriteStartObject(); + } + + bool Key(const Ch* str, SizeType length, bool copy = false) { return String(str, length, copy); } + + bool EndObject(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level)); + RAPIDJSON_ASSERT(!Base::level_stack_.template Top()->inArray); + bool empty = Base::level_stack_.template Pop(1)->valueCount == 0; + + if (!empty) { + Base::os_->Put('\n'); + WriteIndent(); + } + bool ret = Base::WriteEndObject(); + (void)ret; + RAPIDJSON_ASSERT(ret == true); + if (Base::level_stack_.Empty()) // end of json text + Base::os_->Flush(); + return true; + } + + bool StartArray() { + PrettyPrefix(kArrayType); + new (Base::level_stack_.template Push()) typename Base::Level(true); + return Base::WriteStartArray(); + } + + bool EndArray(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level)); + RAPIDJSON_ASSERT(Base::level_stack_.template Top()->inArray); + bool empty = Base::level_stack_.template Pop(1)->valueCount == 0; + + if (!empty) { + Base::os_->Put('\n'); + WriteIndent(); + } + bool ret = Base::WriteEndArray(); + (void)ret; + RAPIDJSON_ASSERT(ret == true); + if (Base::level_stack_.Empty()) // end of json text + Base::os_->Flush(); + return true; + } + + //@} + + /*! @name Convenience extensions */ + //@{ + + //! Simpler but slower overload. + bool String(const Ch* str) { return String(str, internal::StrLen(str)); } + bool Key(const Ch* str) { return Key(str, internal::StrLen(str)); } + + //@} +protected: + void PrettyPrefix(Type type) { + (void)type; + if (Base::level_stack_.GetSize() != 0) { // this value is not at root + typename Base::Level* level = Base::level_stack_.template Top(); + + if (level->inArray) { + if (level->valueCount > 0) { + Base::os_->Put(','); // add comma if it is not the first element in array + Base::os_->Put('\n'); + } + else + Base::os_->Put('\n'); + WriteIndent(); + } + else { // in object + if (level->valueCount > 0) { + if (level->valueCount % 2 == 0) { + Base::os_->Put(','); + Base::os_->Put('\n'); + } + else { + Base::os_->Put(':'); + Base::os_->Put(' '); + } + } + else + Base::os_->Put('\n'); + + if (level->valueCount % 2 == 0) + WriteIndent(); + } + if (!level->inArray && level->valueCount % 2 == 0) + RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name + level->valueCount++; + } + else { + RAPIDJSON_ASSERT(!Base::hasRoot_); // Should only has one and only one root. + Base::hasRoot_ = true; + } + } + + void WriteIndent() { + size_t count = (Base::level_stack_.GetSize() / sizeof(typename Base::Level)) * indentCharCount_; + PutN(*Base::os_, indentChar_, count); + } + + Ch indentChar_; + unsigned indentCharCount_; + +private: + // Prohibit copy constructor & assignment operator. + PrettyWriter(const PrettyWriter&); + PrettyWriter& operator=(const PrettyWriter&); +}; + +BUTIL_RAPIDJSON_NAMESPACE_END + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/src/butil/third_party/rapidjson/rapidjson.h b/src/butil/third_party/rapidjson/rapidjson.h new file mode 100644 index 0000000..8df9dbd --- /dev/null +++ b/src/butil/third_party/rapidjson/rapidjson.h @@ -0,0 +1,667 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_RAPIDJSON_H_ +#define RAPIDJSON_RAPIDJSON_H_ + +/*!\file rapidjson.h + \brief common definitions and configuration + + \see RAPIDJSON_CONFIG + */ + +/*! \defgroup RAPIDJSON_CONFIG RapidJSON configuration + \brief Configuration macros for library features + + Some RapidJSON features are configurable to adapt the library to a wide + variety of platforms, environments and usage scenarios. Most of the + features can be configured in terms of overriden or predefined + preprocessor macros at compile-time. + + Some additional customization is available in the \ref RAPIDJSON_ERRORS APIs. + + \note These macros should be given on the compiler command-line + (where applicable) to avoid inconsistent values when compiling + different translation units of a single application. + */ + +#include // malloc(), realloc(), free(), size_t +#include // memset(), memcpy(), memmove(), memcmp() + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_VERSION_STRING +// +// ALWAYS synchronize the following 3 macros with corresponding variables in /CMakeLists.txt. +// + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +// token stringification +#define RAPIDJSON_STRINGIFY(x) RAPIDJSON_DO_STRINGIFY(x) +#define RAPIDJSON_DO_STRINGIFY(x) #x +//!@endcond + +/*! \def RAPIDJSON_MAJOR_VERSION + \ingroup RAPIDJSON_CONFIG + \brief Major version of RapidJSON in integer. +*/ +/*! \def RAPIDJSON_MINOR_VERSION + \ingroup RAPIDJSON_CONFIG + \brief Minor version of RapidJSON in integer. +*/ +/*! \def RAPIDJSON_PATCH_VERSION + \ingroup RAPIDJSON_CONFIG + \brief Patch version of RapidJSON in integer. +*/ +/*! \def RAPIDJSON_VERSION_STRING + \ingroup RAPIDJSON_CONFIG + \brief Version of RapidJSON in ".." string format. +*/ +#define RAPIDJSON_MAJOR_VERSION 1 +#define RAPIDJSON_MINOR_VERSION 0 +#define RAPIDJSON_PATCH_VERSION 2 +#define RAPIDJSON_VERSION_STRING \ + RAPIDJSON_STRINGIFY(RAPIDJSON_MAJOR_VERSION.RAPIDJSON_MINOR_VERSION.RAPIDJSON_PATCH_VERSION) + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NAMESPACE_(BEGIN|END) +/*! \def RAPIDJSON_NAMESPACE + \ingroup RAPIDJSON_CONFIG + \brief provide custom rapidjson namespace + + In order to avoid symbol clashes and/or "One Definition Rule" errors + between multiple inclusions of (different versions of) RapidJSON in + a single binary, users can customize the name of the main RapidJSON + namespace. + + In case of a single nesting level, defining \c RAPIDJSON_NAMESPACE + to a custom name (e.g. \c MyRapidJSON) is sufficient. If multiple + levels are needed, both \ref RAPIDJSON_NAMESPACE_BEGIN and \ref + RAPIDJSON_NAMESPACE_END need to be defined as well: + + \code + // in some .cpp file + #define RAPIDJSON_NAMESPACE my::rapidjson + #define RAPIDJSON_NAMESPACE_BEGIN namespace my { namespace rapidjson { + #define RAPIDJSON_NAMESPACE_END } } + #include "rapidjson/..." + \endcode + + \see rapidjson + */ +/*! \def RAPIDJSON_NAMESPACE_BEGIN + \ingroup RAPIDJSON_CONFIG + \brief provide custom rapidjson namespace (opening expression) + \see RAPIDJSON_NAMESPACE +*/ +/*! \def RAPIDJSON_NAMESPACE_END + \ingroup RAPIDJSON_CONFIG + \brief provide custom rapidjson namespace (closing expression) + \see RAPIDJSON_NAMESPACE +*/ +// NOTE: +// Add prefix 'BUTIL_' to original 'RAPIDJSON_NAMESPACE', 'RAPIDJSON_NAMESPACE_BEGIN' +// and 'RAPIDJSON_NAMESPACE_END', and set BUTIL_RAPIDJSON_NAMESPACE to butil::rapidjson, +// in order to distinguish with other version RapidJSON in a single binary. (Please refer +// to the reason mentioned in the comments above) +// When using RapidJSON API inside brpc, please use 'BUTIL_RAPIDJSON_NAMESPACE::' instead of +// 'rapidjson::'. +#ifndef BUTIL_RAPIDJSON_NAMESPACE +#define BUTIL_RAPIDJSON_NAMESPACE butil::rapidjson +#endif +#ifndef BUTIL_RAPIDJSON_NAMESPACE_BEGIN +#define BUTIL_RAPIDJSON_NAMESPACE_BEGIN namespace butil { namespace rapidjson { +#endif +#ifndef BUTIL_RAPIDJSON_NAMESPACE_END +#define BUTIL_RAPIDJSON_NAMESPACE_END } } +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NO_INT64DEFINE + +/*! \def RAPIDJSON_NO_INT64DEFINE + \ingroup RAPIDJSON_CONFIG + \brief Use external 64-bit integer types. + + RapidJSON requires the 64-bit integer types \c int64_t and \c uint64_t types + to be available at global scope. + + If users have their own definition, define RAPIDJSON_NO_INT64DEFINE to + prevent RapidJSON from defining its own types. +*/ +#ifndef RAPIDJSON_NO_INT64DEFINE +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#ifdef _MSC_VER +#include "msinttypes/stdint.h" +#include "msinttypes/inttypes.h" +#else +// Other compilers should have this. +#include +#include +#endif +//!@endcond +#ifdef RAPIDJSON_DOXYGEN_RUNNING +#define RAPIDJSON_NO_INT64DEFINE +#endif +#endif // RAPIDJSON_NO_INT64TYPEDEF + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_FORCEINLINE + +#ifndef RAPIDJSON_FORCEINLINE +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#if defined(_MSC_VER) && !defined(NDEBUG) +#define RAPIDJSON_FORCEINLINE __forceinline +#elif defined(__GNUC__) && __GNUC__ >= 4 && !defined(NDEBUG) +#define RAPIDJSON_FORCEINLINE __attribute__((always_inline)) +#else +#define RAPIDJSON_FORCEINLINE +#endif +//!@endcond +#endif // RAPIDJSON_FORCEINLINE + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ENDIAN +#define RAPIDJSON_LITTLEENDIAN 0 //!< Little endian machine +#define RAPIDJSON_BIGENDIAN 1 //!< Big endian machine + +//! Endianness of the machine. +/*! + \def RAPIDJSON_ENDIAN + \ingroup RAPIDJSON_CONFIG + + GCC 4.6 provided macro for detecting endianness of the target machine. But other + compilers may not have this. User can define RAPIDJSON_ENDIAN to either + \ref RAPIDJSON_LITTLEENDIAN or \ref RAPIDJSON_BIGENDIAN. + + Default detection implemented with reference to + \li https://gcc.gnu.org/onlinedocs/gcc-4.6.0/cpp/Common-Predefined-Macros.html + \li http://www.boost.org/doc/libs/1_42_0/boost/detail/endian.hpp +*/ +#ifndef RAPIDJSON_ENDIAN +// Detect with GCC 4.6's macro +# ifdef __BYTE_ORDER__ +# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +# else +# error Unknown machine endianess detected. User needs to define RAPIDJSON_ENDIAN. +# endif // __BYTE_ORDER__ +// Detect with GLIBC's endian.h +# elif defined(__GLIBC__) +# include +# if (__BYTE_ORDER == __LITTLE_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif (__BYTE_ORDER == __BIG_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +# else +# error Unknown machine endianess detected. User needs to define RAPIDJSON_ENDIAN. +# endif // __GLIBC__ +// Detect with _LITTLE_ENDIAN and _BIG_ENDIAN macro +# elif defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +// Detect with architecture macros +# elif defined(__sparc) || defined(__sparc__) || defined(_POWER) || defined(__powerpc__) || defined(__ppc__) || defined(__hpux) || defined(__hppa) || defined(_MIPSEB) || defined(_POWER) || defined(__s390__) +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +# elif defined(__i386__) || defined(__alpha__) || defined(__ia64) || defined(__ia64__) || defined(_M_IX86) || defined(_M_IA64) || defined(_M_ALPHA) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) || defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || defined(__bfin__) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif defined(RAPIDJSON_DOXYGEN_RUNNING) +# define RAPIDJSON_ENDIAN +# else +# error Unknown machine endianess detected. User needs to define RAPIDJSON_ENDIAN. +# endif +#endif // RAPIDJSON_ENDIAN + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_64BIT + +//! Whether using 64-bit architecture +#ifndef RAPIDJSON_64BIT +#if defined(__LP64__) || defined(_WIN64) || defined(__EMSCRIPTEN__) +#define RAPIDJSON_64BIT 1 +#else +#define RAPIDJSON_64BIT 0 +#endif +#endif // RAPIDJSON_64BIT + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ALIGN + +//! Data alignment of the machine. +/*! \ingroup RAPIDJSON_CONFIG + \param x pointer to align + + Some machines require strict data alignment. Currently the default uses 4 bytes + alignment. User can customize by defining the RAPIDJSON_ALIGN function macro., +*/ +#ifndef RAPIDJSON_ALIGN +#if RAPIDJSON_64BIT == 1 +#define RAPIDJSON_ALIGN(x) ((x + 7u) & ~7u) +#else +#define RAPIDJSON_ALIGN(x) ((x + 3u) & ~3u) +#endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_UINT64_C2 + +//! Construct a 64-bit literal by a pair of 32-bit integer. +/*! + 64-bit literal with or without ULL suffix is prone to compiler warnings. + UINT64_C() is C macro which cause compilation problems. + Use this macro to define 64-bit constants by a pair of 32-bit integer. +*/ +#ifndef RAPIDJSON_UINT64_C2 +#define RAPIDJSON_UINT64_C2(high32, low32) ((static_cast(high32) << 32) | static_cast(low32)) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_SSE2/RAPIDJSON_SSE42/RAPIDJSON_SIMD + +/*! \def RAPIDJSON_SIMD + \ingroup RAPIDJSON_CONFIG + \brief Enable SSE2/SSE4.2 optimization. + + RapidJSON supports optimized implementations for some parsing operations + based on the SSE2 or SSE4.2 SIMD extensions on modern Intel-compatible + processors. + + To enable these optimizations, two different symbols can be defined; + \code + // Enable SSE2 optimization. + #define RAPIDJSON_SSE2 + + // Enable SSE4.2 optimization. + #define RAPIDJSON_SSE42 + \endcode + + \c RAPIDJSON_SSE42 takes precedence, if both are defined. + + If any of these symbols is defined, RapidJSON defines the macro + \c RAPIDJSON_SIMD to indicate the availability of the optimized code. +*/ +#if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) \ + || defined(RAPIDJSON_DOXYGEN_RUNNING) +#define RAPIDJSON_SIMD +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NO_SIZETYPEDEFINE + +#ifndef RAPIDJSON_NO_SIZETYPEDEFINE +/*! \def RAPIDJSON_NO_SIZETYPEDEFINE + \ingroup RAPIDJSON_CONFIG + \brief User-provided \c SizeType definition. + + In order to avoid using 32-bit size types for indexing strings and arrays, + define this preprocessor symbol and provide the type rapidjson::SizeType + before including RapidJSON: + \code + #define RAPIDJSON_NO_SIZETYPEDEFINE + namespace rapidjson { typedef ::std::size_t SizeType; } + #include "rapidjson/..." + \endcode + + \see rapidjson::SizeType +*/ +#ifdef RAPIDJSON_DOXYGEN_RUNNING +#define RAPIDJSON_NO_SIZETYPEDEFINE +#endif +BUTIL_RAPIDJSON_NAMESPACE_BEGIN +//! Size type (for string lengths, array sizes, etc.) +/*! RapidJSON uses 32-bit array/string indices even on 64-bit platforms, + instead of using \c size_t. Users may override the SizeType by defining + \ref RAPIDJSON_NO_SIZETYPEDEFINE. +*/ +typedef unsigned SizeType; +BUTIL_RAPIDJSON_NAMESPACE_END +#endif + +// always import std::size_t to rapidjson namespace +BUTIL_RAPIDJSON_NAMESPACE_BEGIN +using std::size_t; +BUTIL_RAPIDJSON_NAMESPACE_END + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ASSERT + +//! Assertion. +/*! \ingroup RAPIDJSON_CONFIG + By default, rapidjson uses C \c assert() for internal assertions. + User can override it by defining RAPIDJSON_ASSERT(x) macro. + + \note Parsing errors are handled and can be customized by the + \ref RAPIDJSON_ERRORS APIs. +*/ +#ifndef RAPIDJSON_ASSERT +#include +#define RAPIDJSON_ASSERT(x) assert(x) +#endif // RAPIDJSON_ASSERT + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_STATIC_ASSERT + +// Adopt from boost +#ifndef RAPIDJSON_STATIC_ASSERT +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +BUTIL_RAPIDJSON_NAMESPACE_BEGIN +template struct STATIC_ASSERTION_FAILURE; +template <> struct STATIC_ASSERTION_FAILURE { enum { value = 1 }; }; +template struct StaticAssertTest {}; +BUTIL_RAPIDJSON_NAMESPACE_END + +#define RAPIDJSON_JOIN(X, Y) RAPIDJSON_DO_JOIN(X, Y) +#define RAPIDJSON_DO_JOIN(X, Y) RAPIDJSON_DO_JOIN2(X, Y) +#define RAPIDJSON_DO_JOIN2(X, Y) X##Y + +#if defined(__GNUC__) +#define RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE __attribute__((unused)) +#else +#define RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE +#endif +//!@endcond + +/*! \def RAPIDJSON_STATIC_ASSERT + \brief (Internal) macro to check for conditions at compile-time + \param x compile-time condition + \hideinitializer + */ +#define RAPIDJSON_STATIC_ASSERT(x) \ + typedef ::BUTIL_RAPIDJSON_NAMESPACE::StaticAssertTest< \ + sizeof(::BUTIL_RAPIDJSON_NAMESPACE::STATIC_ASSERTION_FAILURE)> \ + RAPIDJSON_JOIN(StaticAssertTypedef, __LINE__) RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Helpers + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN + +#define RAPIDJSON_MULTILINEMACRO_BEGIN do { +#define RAPIDJSON_MULTILINEMACRO_END \ +} while((void)0, 0) + +// adopted from Boost +#define RAPIDJSON_VERSION_CODE(x,y,z) \ + (((x)*100000) + ((y)*100) + (z)) + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_DIAG_PUSH/POP, RAPIDJSON_DIAG_OFF + +#if defined(__GNUC__) +#define RAPIDJSON_GNUC \ + RAPIDJSON_VERSION_CODE(__GNUC__,__GNUC_MINOR__,__GNUC_PATCHLEVEL__) +#endif + +#if defined(__clang__) || (defined(RAPIDJSON_GNUC) && RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,2,0)) + +#define RAPIDJSON_PRAGMA(x) _Pragma(RAPIDJSON_STRINGIFY(x)) +#define RAPIDJSON_DIAG_PRAGMA(x) RAPIDJSON_PRAGMA(GCC diagnostic x) +#define RAPIDJSON_DIAG_OFF(x) \ + RAPIDJSON_DIAG_PRAGMA(ignored RAPIDJSON_STRINGIFY(RAPIDJSON_JOIN(-W,x))) + +// push/pop support in Clang and GCC>=4.6 +#if defined(__clang__) || (defined(RAPIDJSON_GNUC) && RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,6,0)) +#define RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_PRAGMA(push) +#define RAPIDJSON_DIAG_POP RAPIDJSON_DIAG_PRAGMA(pop) +#else // GCC >= 4.2, < 4.6 +#define RAPIDJSON_DIAG_PUSH /* ignored */ +#define RAPIDJSON_DIAG_POP /* ignored */ +#endif + +#elif defined(_MSC_VER) + +// pragma (MSVC specific) +#define RAPIDJSON_PRAGMA(x) __pragma(x) +#define RAPIDJSON_DIAG_PRAGMA(x) RAPIDJSON_PRAGMA(warning(x)) + +#define RAPIDJSON_DIAG_OFF(x) RAPIDJSON_DIAG_PRAGMA(disable: x) +#define RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_PRAGMA(push) +#define RAPIDJSON_DIAG_POP RAPIDJSON_DIAG_PRAGMA(pop) + +#else + +#define RAPIDJSON_DIAG_OFF(x) /* ignored */ +#define RAPIDJSON_DIAG_PUSH /* ignored */ +#define RAPIDJSON_DIAG_POP /* ignored */ + +#endif // RAPIDJSON_DIAG_* + +/////////////////////////////////////////////////////////////////////////////// +// C++11 features + +#ifndef RAPIDJSON_HAS_CXX11_RVALUE_REFS +#if defined(__clang__) +# if __has_feature(cxx_rvalue_references) && \ + (defined(_LIBCPP_VERSION) || defined(__GLIBCXX__) && __GLIBCXX__ >= 20080306) +# define RAPIDJSON_HAS_CXX11_RVALUE_REFS 1 +# else +# define RAPIDJSON_HAS_CXX11_RVALUE_REFS 0 +# endif +#elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,3,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) || \ + (defined(_MSC_VER) && _MSC_VER >= 1600) + +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 1 +#else +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 0 +#endif +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + +#ifndef RAPIDJSON_HAS_CXX11_NOEXCEPT +#if defined(__clang__) +#define RAPIDJSON_HAS_CXX11_NOEXCEPT __has_feature(cxx_noexcept) +#elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,6,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) +// (defined(_MSC_VER) && _MSC_VER >= ????) // not yet supported +#define RAPIDJSON_HAS_CXX11_NOEXCEPT 1 +#else +#define RAPIDJSON_HAS_CXX11_NOEXCEPT 0 +#endif +#endif +#if RAPIDJSON_HAS_CXX11_NOEXCEPT +#define RAPIDJSON_NOEXCEPT noexcept +#else +#define RAPIDJSON_NOEXCEPT /* noexcept */ +#endif // RAPIDJSON_HAS_CXX11_NOEXCEPT + +// no automatic detection, yet +#ifndef RAPIDJSON_HAS_CXX11_TYPETRAITS +#define RAPIDJSON_HAS_CXX11_TYPETRAITS 0 +#endif + +//!@endcond + +/////////////////////////////////////////////////////////////////////////////// +// new/delete + +#ifndef RAPIDJSON_NEW +///! customization point for global \c new +#define RAPIDJSON_NEW(x) new x +#endif +#ifndef RAPIDJSON_DELETE +///! customization point for global \c delete +#define RAPIDJSON_DELETE(x) delete x +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Allocators and Encodings + +#include "allocators.h" +#include "encodings.h" + +/*! \namespace rapidjson + \brief main RapidJSON namespace + \see BUTIL_RAPIDJSON_NAMESPACE +*/ +BUTIL_RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Stream + +/*! \class rapidjson::Stream + \brief Concept for reading and writing characters. + + For read-only stream, no need to implement PutBegin(), Put(), Flush() and PutEnd(). + + For write-only stream, only need to implement Put() and Flush(). + +\code +concept Stream { + typename Ch; //!< Character type of the stream. + + //! Read the current character from stream without moving the read cursor. + Ch Peek() const; + + //! Read the current character from stream and moving the read cursor to next character. + Ch Take(); + + //! Get the current read cursor. + //! \return Number of characters read from start. + size_t Tell(); + + //! Begin writing operation at the current read pointer. + //! \return The begin writer pointer. + Ch* PutBegin(); + + //! Write a character. + void Put(Ch c); + + //! Flush the buffer. + void Flush(); + + //! End the writing operation. + //! \param begin The begin write pointer returned by PutBegin(). + //! \return Number of characters written. + size_t PutEnd(Ch* begin); +} +\endcode +*/ + +//! Provides additional information for stream. +/*! + By using traits pattern, this type provides a default configuration for stream. + For custom stream, this type can be specialized for other configuration. + See TEST(Reader, CustomStringStream) in readertest.cpp for example. +*/ +template +struct StreamTraits { + //! Whether to make local copy of stream for optimization during parsing. + /*! + By default, for safety, streams do not use local copy optimization. + Stream that can be copied fast should specialize this, like StreamTraits. + */ + enum { copyOptimization = 0 }; +}; + +//! Put N copies of a character to a stream. +template +inline void PutN(Stream& stream, Ch c, size_t n) { + for (size_t i = 0; i < n; i++) + stream.Put(c); +} + +/////////////////////////////////////////////////////////////////////////////// +// StringStream + +//! Read-only string stream. +/*! \note implements Stream concept +*/ +template +struct GenericStringStream { + typedef typename Encoding::Ch Ch; + + GenericStringStream(const Ch *src) : src_(src), head_(src) {} + + Ch Peek() const { return *src_; } + Ch Take() { return *src_++; } + const Ch* TakeWithAddr() { return src_++; } + bool ReadBlockTail() { return false; } + size_t Tell() const { return static_cast(src_ - head_); } + + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + const Ch* src_; //!< Current read position. + const Ch* head_; //!< Original head of the string. +}; + +template +struct StreamTraits > { + enum { copyOptimization = 1 }; +}; + +//! String stream with UTF8 encoding. +typedef GenericStringStream > StringStream; + +/////////////////////////////////////////////////////////////////////////////// +// InsituStringStream + +//! A read-write string stream. +/*! This string stream is particularly designed for in-situ parsing. + \note implements Stream concept +*/ +template +struct GenericInsituStringStream { + typedef typename Encoding::Ch Ch; + + GenericInsituStringStream(Ch *src) : src_(src), dst_(0), head_(src) {} + + // Read + Ch Peek() { return *src_; } + Ch Take() { return *src_++; } + size_t Tell() { return static_cast(src_ - head_); } + + // Write + void Put(Ch c) { RAPIDJSON_ASSERT(dst_ != 0); *dst_++ = c; } + + Ch* PutBegin() { return dst_ = src_; } + size_t PutEnd(Ch* begin) { return static_cast(dst_ - begin); } + void Flush() {} + + Ch* Push(size_t count) { Ch* begin = dst_; dst_ += count; return begin; } + void Pop(size_t count) { dst_ -= count; } + + Ch* src_; + Ch* dst_; + Ch* head_; +}; + +template +struct StreamTraits > { + enum { copyOptimization = 1 }; +}; + +//! Insitu string stream with UTF8 encoding. +typedef GenericInsituStringStream > InsituStringStream; + +/////////////////////////////////////////////////////////////////////////////// +// Type + +//! Type of JSON value +enum Type { + kNullType = 0, //!< null + kFalseType = 1, //!< false + kTrueType = 2, //!< true + kObjectType = 3, //!< object + kArrayType = 4, //!< array + kStringType = 5, //!< string + kNumberType = 6 //!< number +}; + +BUTIL_RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/src/butil/third_party/rapidjson/reader.h b/src/butil/third_party/rapidjson/reader.h new file mode 100644 index 0000000..26e53c2 --- /dev/null +++ b/src/butil/third_party/rapidjson/reader.h @@ -0,0 +1,1576 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_READER_H_ +#define RAPIDJSON_READER_H_ + +/*! \file reader.h */ + +#include "rapidjson.h" +#include "encodings.h" +#include "internal/meta.h" +#include "internal/stack.h" +#include "internal/strtod.h" + +#if defined(RAPIDJSON_SIMD) && defined(_MSC_VER) +#include +#pragma intrinsic(_BitScanForward) +#endif +#ifdef RAPIDJSON_SSE42 +#include +#elif defined(RAPIDJSON_SSE2) +#include +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant +RAPIDJSON_DIAG_OFF(4702) // unreachable code +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define RAPIDJSON_NOTHING /* deliberately empty */ +#ifndef RAPIDJSON_PARSE_ERROR_EARLY_RETURN +#define RAPIDJSON_PARSE_ERROR_EARLY_RETURN(value) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + if (HasParseError()) { return value; } \ + RAPIDJSON_MULTILINEMACRO_END +#endif +#define RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID \ + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(RAPIDJSON_NOTHING) +//!@endcond + +/*! \def RAPIDJSON_PARSE_ERROR_NORETURN + \ingroup RAPIDJSON_ERRORS + \brief Macro to indicate a parse error. + \param parseErrorCode \ref rapidjson::ParseErrorCode of the error + \param offset position of the error in JSON input (\c size_t) + + This macros can be used as a customization point for the internal + error handling mechanism of RapidJSON. + + A common usage model is to throw an exception instead of requiring the + caller to explicitly check the \ref rapidjson::GenericReader::Parse's + return value: + + \code + #define RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode,offset) \ + throw ParseException(parseErrorCode, #parseErrorCode, offset) + + #include // std::runtime_error + #include "rapidjson/error/error.h" // rapidjson::ParseResult + + struct ParseException : std::runtime_error, rapidjson::ParseResult { + ParseException(rapidjson::ParseErrorCode code, const char* msg, size_t offset) + : std::runtime_error(msg), ParseResult(code, offset) {} + }; + + #include "rapidjson/reader.h" + \endcode + + \see RAPIDJSON_PARSE_ERROR, rapidjson::GenericReader::Parse + */ +#ifndef RAPIDJSON_PARSE_ERROR_NORETURN +#define RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode, offset) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + RAPIDJSON_ASSERT(!HasParseError()); /* Error can only be assigned once */ \ + SetParseError(parseErrorCode, offset); \ + RAPIDJSON_MULTILINEMACRO_END +#endif + +/*! \def RAPIDJSON_PARSE_ERROR + \ingroup RAPIDJSON_ERRORS + \brief (Internal) macro to indicate and handle a parse error. + \param parseErrorCode \ref rapidjson::ParseErrorCode of the error + \param offset position of the error in JSON input (\c size_t) + + Invokes RAPIDJSON_PARSE_ERROR_NORETURN and stops the parsing. + + \see RAPIDJSON_PARSE_ERROR_NORETURN + \hideinitializer + */ +#ifndef RAPIDJSON_PARSE_ERROR +#define RAPIDJSON_PARSE_ERROR(parseErrorCode, offset) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode, offset); \ + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; \ + RAPIDJSON_MULTILINEMACRO_END +#endif + +#include "error/error.h" // ParseErrorCode, ParseResult + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN + +template +struct is_same { + static const bool value = false; +}; + +template +struct is_same { + static const bool value = true; +}; + +// BullseyeCoverage Compile C++ 8.9.39 Linux-x64 requires this. +template const bool is_same::value; +template const bool is_same::value; + +/////////////////////////////////////////////////////////////////////////////// +// ParseFlag + +/*! \def RAPIDJSON_PARSE_DEFAULT_FLAGS + \ingroup RAPIDJSON_CONFIG + \brief User-defined kParseDefaultFlags definition. + + User can define this as any \c ParseFlag combinations. +*/ +#ifndef RAPIDJSON_PARSE_DEFAULT_FLAGS +#define RAPIDJSON_PARSE_DEFAULT_FLAGS kParseNoFlags +#endif + +//! Combination of parseFlags +/*! \see Reader::Parse, Document::Parse, Document::ParseInsitu, Document::ParseStream + */ +enum ParseFlag { + kParseNoFlags = 0, //!< No flags are set. + kParseInsituFlag = 1, //!< In-situ(destructive) parsing. + kParseValidateEncodingFlag = 2, //!< Validate encoding of JSON strings. + kParseIterativeFlag = 4, //!< Iterative(constant complexity in terms of function call stack size) parsing. + kParseStopWhenDoneFlag = 8, //!< After parsing a complete JSON root from stream, stop further processing the rest of stream. When this flag is used, parser will not generate kParseErrorDocumentRootNotSingular error. + kParseFullPrecisionFlag = 16, //!< Parse number in full precision (but slower). + kParseDefaultFlags = RAPIDJSON_PARSE_DEFAULT_FLAGS //!< Default parse flags. Can be customized by defining RAPIDJSON_PARSE_DEFAULT_FLAGS +}; + +/////////////////////////////////////////////////////////////////////////////// +// Handler + +/*! \class rapidjson::Handler + \brief Concept for receiving events from GenericReader upon parsing. + The functions return true if no error occurs. If they return false, + the event publisher should terminate the process. +\code +concept Handler { + typename Ch; + + bool Null(); + bool Bool(bool b); + bool AddInt(int i); + bool AddUint(unsigned i); + bool AddInt64(int64_t i); + bool AddUint64(uint64_t i); + bool Double(double d); + bool String(const Ch* str, SizeType length, bool copy); + bool StartObject(); + bool Key(const Ch* str, SizeType length, bool copy); + bool EndObject(SizeType memberCount); + bool StartArray(); + bool EndArray(SizeType elementCount); +}; +\endcode +*/ +/////////////////////////////////////////////////////////////////////////////// +// BaseReaderHandler + +//! Default implementation of Handler. +/*! This can be used as base class of any reader handler. + \note implements Handler concept +*/ +template, typename Derived = void> +struct BaseReaderHandler { + typedef typename Encoding::Ch Ch; + + typedef typename internal::SelectIf, BaseReaderHandler, Derived>::Type Override; + + bool Default() { return true; } + bool Null() { return static_cast(*this).Default(); } + bool Bool(bool) { return static_cast(*this).Default(); } + bool AddInt(int) { return static_cast(*this).Default(); } + bool AddUint(unsigned) { return static_cast(*this).Default(); } + bool AddInt64(int64_t) { return static_cast(*this).Default(); } + bool AddUint64(uint64_t) { return static_cast(*this).Default(); } + bool Double(double) { return static_cast(*this).Default(); } + bool String(const Ch*, SizeType, bool) { return static_cast(*this).Default(); } + bool StartObject() { return static_cast(*this).Default(); } + bool Key(const Ch* str, SizeType len, bool copy) { return static_cast(*this).String(str, len, copy); } + bool EndObject(SizeType) { return static_cast(*this).Default(); } + bool StartArray() { return static_cast(*this).Default(); } + bool EndArray(SizeType) { return static_cast(*this).Default(); } +}; + +/////////////////////////////////////////////////////////////////////////////// +// StreamLocalCopy + +namespace internal { + +template::copyOptimization> +class StreamLocalCopy; + +//! Do copy optimization. +template +class StreamLocalCopy { +public: + StreamLocalCopy(Stream& original) : s(original), original_(original) {} + ~StreamLocalCopy() { original_ = s; } + + Stream s; + +private: + StreamLocalCopy& operator=(const StreamLocalCopy&) /* = delete */; + + Stream& original_; +}; + +//! Keep reference. +template +class StreamLocalCopy { +public: + StreamLocalCopy(Stream& original) : s(original) {} + + Stream& s; + +private: + StreamLocalCopy& operator=(const StreamLocalCopy&) /* = delete */; +}; + +} // namespace internal + +/////////////////////////////////////////////////////////////////////////////// +// SkipWhitespace + +//! Skip the JSON white spaces in a stream. +/*! \param is A input stream for skipping white spaces. + \note This function has SSE2/SSE4.2 specialization. +*/ +template +void SkipWhitespace(InputStream& is) { + internal::StreamLocalCopy copy(is); + InputStream& s(copy.s); + + while (s.Peek() == ' ' || s.Peek() == '\n' || s.Peek() == '\r' || s.Peek() == '\t') + s.Take(); +} + +#ifdef RAPIDJSON_SSE42 +//! Skip whitespace with SSE 4.2 pcmpistrm instruction, testing 16 8-byte characters at once. +inline const char *SkipWhitespace_SIMD(const char* p) { + // Fast return for single non-whitespace + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // 16-byte align to the next boundary + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & ~15); + while (p != nextAligned) + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // The rest of string using SIMD + static const char whitespace[16] = " \n\r\t"; + const __m128i w = _mm_load_si128((const __m128i *)&whitespace[0]); + + for (;; p += 16) { + const __m128i s = _mm_load_si128((const __m128i *)p); + const unsigned r = _mm_cvtsi128_si32(_mm_cmpistrm(w, s, _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_BIT_MASK | _SIDD_NEGATIVE_POLARITY)); + if (r != 0) { // some of characters is non-whitespace +#ifdef _MSC_VER // Find the index of first non-whitespace + unsigned long offset; + _BitScanForward(&offset, r); + return p + offset; +#else + return p + __builtin_ffs(r) - 1; +#endif + } + } +} + +#elif defined(RAPIDJSON_SSE2) + +//! Skip whitespace with SSE2 instructions, testing 16 8-byte characters at once. +inline const char *SkipWhitespace_SIMD(const char* p) { + // Fast return for single non-whitespace + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // 16-byte align to the next boundary + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & ~15); + while (p != nextAligned) + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // The rest of string + static const char whitespaces[4][17] = { + " ", + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", + "\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r", + "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"}; + + const __m128i w0 = _mm_loadu_si128((const __m128i *)&whitespaces[0][0]); + const __m128i w1 = _mm_loadu_si128((const __m128i *)&whitespaces[1][0]); + const __m128i w2 = _mm_loadu_si128((const __m128i *)&whitespaces[2][0]); + const __m128i w3 = _mm_loadu_si128((const __m128i *)&whitespaces[3][0]); + + for (;; p += 16) { + const __m128i s = _mm_load_si128((const __m128i *)p); + __m128i x = _mm_cmpeq_epi8(s, w0); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w1)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w2)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w3)); + unsigned short r = (unsigned short)~_mm_movemask_epi8(x); + if (r != 0) { // some of characters may be non-whitespace +#ifdef _MSC_VER // Find the index of first non-whitespace + unsigned long offset; + _BitScanForward(&offset, r); + return p + offset; +#else + return p + __builtin_ffs(r) - 1; +#endif + } + } +} + +#endif // RAPIDJSON_SSE2 + +#ifdef RAPIDJSON_SIMD +//! Template function specialization for InsituStringStream +template<> inline void SkipWhitespace(InsituStringStream& is) { + is.src_ = const_cast(SkipWhitespace_SIMD(is.src_)); +} + +//! Template function specialization for StringStream +template<> inline void SkipWhitespace(StringStream& is) { + is.src_ = SkipWhitespace_SIMD(is.src_); +} +#endif // RAPIDJSON_SIMD + +/////////////////////////////////////////////////////////////////////////////// +// GenericReader + +//! SAX-style JSON parser. Use \ref Reader for UTF8 encoding and default allocator. +/*! GenericReader parses JSON text from a stream, and send events synchronously to an + object implementing Handler concept. + + It needs to allocate a stack for storing a single decoded string during + non-destructive parsing. + + For in-situ parsing, the decoded string is directly written to the source + text string, no temporary buffer is required. + + A GenericReader object can be reused for parsing multiple JSON text. + + \tparam SourceEncoding Encoding of the input stream. + \tparam TargetEncoding Encoding of the parse output. + \tparam StackAllocator Allocator type for stack. +*/ +template +class GenericReader { +public: + typedef typename SourceEncoding::Ch Ch; //!< SourceEncoding character type + + //! Constructor. + /*! \param stackAllocator Optional allocator for allocating stack memory. (Only use for non-destructive parsing) + \param stackCapacity stack capacity in bytes for storing a single decoded string. (Only use for non-destructive parsing) + */ + GenericReader(StackAllocator* stackAllocator = 0, size_t stackCapacity = kDefaultStackCapacity) + : stack_(stackAllocator, stackCapacity), parseResult_() {} + + //! Parse JSON text. + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam InputStream Type of input stream, implementing Stream concept. + \tparam Handler Type of handler, implementing Handler concept. + \param is Input stream to be parsed. + \param handler The handler to receive events. + \return Whether the parsing is successful. + */ + template + ParseResult Parse(InputStream& is, Handler& handler) { + if (parseFlags & kParseIterativeFlag) + return IterativeParse(is, handler); + + parseResult_.Clear(); + + ClearStackOnExit scope(*this); + + SkipWhitespace(is); + + if (is.Peek() == '\0') { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorDocumentEmpty, is.Tell()); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + } + else { + ParseValue(is, handler); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + + if (!(parseFlags & kParseStopWhenDoneFlag)) { + SkipWhitespace(is); + + if (is.Peek() != '\0') { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorDocumentRootNotSingular, is.Tell()); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + } + } else { + // jge: Update parseResult_.Offset() when kParseStopwhendoneflag + // is set which means the user needs to know where to resume + // parsing in next calls to JsonToProtoMessage() + SetParseError(kParseErrorNone, is.Tell()); + } + } + + return parseResult_; + } + + //! Parse JSON text (with \ref kParseDefaultFlags) + /*! \tparam InputStream Type of input stream, implementing Stream concept + \tparam Handler Type of handler, implementing Handler concept. + \param is Input stream to be parsed. + \param handler The handler to receive events. + \return Whether the parsing is successful. + */ + template + ParseResult Parse(InputStream& is, Handler& handler) { + return Parse(is, handler); + } + + //! Whether a parse error has occured in the last parsing. + bool HasParseError() const { return parseResult_.IsError(); } + + //! Get the \ref ParseErrorCode of last parsing. + ParseErrorCode GetParseErrorCode() const { return parseResult_.Code(); } + + //! Get the position of last parsing error in input, 0 otherwise. + size_t GetErrorOffset() const { return parseResult_.Offset(); } + +protected: + void SetParseError(ParseErrorCode code, size_t offset) { parseResult_.Set(code, offset); } + +private: + // Prohibit copy constructor & assignment operator. + GenericReader(const GenericReader&); + GenericReader& operator=(const GenericReader&); + + void ClearStack() { stack_.Clear(); } + + // clear stack on any exit from ParseStream, e.g. due to exception + struct ClearStackOnExit { + explicit ClearStackOnExit(GenericReader& r) : r_(r) {} + ~ClearStackOnExit() { r_.ClearStack(); } + private: + GenericReader& r_; + ClearStackOnExit(const ClearStackOnExit&); + ClearStackOnExit& operator=(const ClearStackOnExit&); + }; + + // Parse object: { string : value, ... } + template + void ParseObject(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == '{'); + is.Take(); // Skip '{' + + if (!handler.StartObject()) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + + SkipWhitespace(is); + + if (is.Peek() == '}') { + is.Take(); + if (!handler.EndObject(0)) // empty object + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + } + + for (SizeType memberCount = 0;;) { + if (is.Peek() != '"') + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissName, is.Tell()); + + ParseString(is, handler, true); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + SkipWhitespace(is); + + if (is.Take() != ':') + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissColon, is.Tell()); + + SkipWhitespace(is); + + ParseValue(is, handler); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + SkipWhitespace(is); + + ++memberCount; + + switch (is.Take()) { + case ',': SkipWhitespace(is); break; + case '}': + if (!handler.EndObject(memberCount)) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + default: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissCommaOrCurlyBracket, is.Tell()); + } + } + } + + // Parse array: [ value, ... ] + template + void ParseArray(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == '['); + is.Take(); // Skip '[' + + if (!handler.StartArray()) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + + SkipWhitespace(is); + + if (is.Peek() == ']') { + is.Take(); + if (!handler.EndArray(0)) // empty array + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + } + + for (SizeType elementCount = 0;;) { + ParseValue(is, handler); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + ++elementCount; + SkipWhitespace(is); + + switch (is.Take()) { + case ',': SkipWhitespace(is); break; + case ']': + if (!handler.EndArray(elementCount)) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + default: RAPIDJSON_PARSE_ERROR(kParseErrorArrayMissCommaOrSquareBracket, is.Tell()); + } + } + } + + template + void ParseNull(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == 'n'); + is.Take(); + + if (is.Take() == 'u' && is.Take() == 'l' && is.Take() == 'l') { + if (!handler.Null()) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell() - 1); + } + + template + void ParseTrue(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == 't'); + is.Take(); + + if (is.Take() == 'r' && is.Take() == 'u' && is.Take() == 'e') { + if (!handler.Bool(true)) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell() - 1); + } + + template + void ParseFalse(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == 'f'); + is.Take(); + + if (is.Take() == 'a' && is.Take() == 'l' && is.Take() == 's' && is.Take() == 'e') { + if (!handler.Bool(false)) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell() - 1); + } + + // Helper function to parse four hexidecimal digits in \uXXXX in ParseString(). + template + unsigned ParseHex4(InputStream& is) { + unsigned codepoint = 0; + for (int i = 0; i < 4; i++) { + Ch c = is.Take(); + codepoint <<= 4; + codepoint += static_cast(c); + if (c >= '0' && c <= '9') + codepoint -= '0'; + else if (c >= 'A' && c <= 'F') + codepoint -= 'A' - 10; + else if (c >= 'a' && c <= 'f') + codepoint -= 'a' - 10; + else { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorStringUnicodeEscapeInvalidHex, is.Tell() - 1); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(0); + } + } + return codepoint; + } + + template + class StackStream { + public: + typedef CharType Ch; + + StackStream(internal::Stack& stack) : stack_(stack), length_(0) {} + RAPIDJSON_FORCEINLINE void Put(Ch c) { + *stack_.template Push() = c; + ++length_; + } + RAPIDJSON_FORCEINLINE void Puts(Ch* str, SizeType length) { + Ch* pos = stack_.template Push(length); + memcpy(pos, str, length); + length_ += length; + } + size_t Length() const { return length_; } + Ch* Pop() { + return stack_.template Pop(length_); + } + + private: + StackStream(const StackStream&); + StackStream& operator=(const StackStream&); + + internal::Stack& stack_; + SizeType length_; + }; + + // Parse string and generate String event. Different code paths for kParseInsituFlag. + template + void ParseString(InputStream& is, Handler& handler, bool isKey = false) { + internal::StreamLocalCopy copy(is); + InputStream& s(copy.s); + + bool success = false; + if (parseFlags == kParseDefaultFlags && is_same::value) { + StackStream stackStream(stack_); + ParseStringToStream(s, stackStream); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + SizeType length = static_cast(stackStream.Length()) - 1; + const typename SourceEncoding::Ch* const str = stackStream.Pop(); + success = (isKey ? handler.Key(str, length, true) : handler.String(str, length, true)); + } + else if (parseFlags & kParseInsituFlag) { + typename InputStream::Ch *head = s.PutBegin(); + ParseStringToStream(s, s); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + size_t length = s.PutEnd(head) - 1; + RAPIDJSON_ASSERT(length <= 0xFFFFFFFF); + const typename TargetEncoding::Ch* const str = (typename TargetEncoding::Ch*)head; + success = (isKey ? handler.Key(str, SizeType(length), false) : handler.String(str, SizeType(length), false)); + } + else { + StackStream stackStream(stack_); + ParseStringToStream(s, stackStream); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + SizeType length = static_cast(stackStream.Length()) - 1; + const typename TargetEncoding::Ch* const str = stackStream.Pop(); + success = (isKey ? handler.Key(str, length, true) : handler.String(str, length, true)); + } + if (!success) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, s.Tell()); + } + + template + inline void PushContentToStream(OutputStream& os, Ch*& start, size_t& length) { + if (length > 0) { + os.Puts(start, length); + start = NULL; + length = 0; + } + } + + // When parseFlags is kParseDefaultFlags and SourceEncoding equals TargetEncoding + // will use this optimized ParseStringToStream to improve efficiency. + // Compare with not optimized ParseStringToStream it will improve by 30%. + // Parse string to an output is + // This function handles the prefix/suffix double quotes, escaping, and optional encoding validation. + template + RAPIDJSON_FORCEINLINE void ParseStringToStream(InputStream& is, OutputStream& os) { +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define Z16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + static const char escape[256] = { + Z16, Z16, 0, 0,'\"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'/', + Z16, Z16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, + 0, 0,'\b', 0, 0, 0,'\f', 0, 0, 0, 0, 0, 0, 0,'\n', 0, + 0, 0,'\r', 0,'\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 + }; +#undef Z16 +//!@endcond + RAPIDJSON_ASSERT(is.Peek() == '\"'); + is.Take(); // Skip '\"' + Ch* start = NULL; + size_t length = 0; + for (;;) { + Ch* pos = const_cast(is.TakeWithAddr()); + if (pos == NULL) { + RAPIDJSON_PARSE_ERROR(kParseErrorParseStringToStreamInvalidContent, is.Tell()); + return; + } + Ch c = *pos; + if (c == '\\') { // Escape + Ch e = is.Take(); + if ((sizeof(Ch) == 1 || unsigned(e) < 256) && escape[(unsigned char)e]) { + PushContentToStream(os, start, length); + os.Put(escape[(unsigned char)e]); + } + else if (e == 'u') { // Unicode + PushContentToStream(os, start, length); + unsigned codepoint = ParseHex4(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + if (codepoint >= 0xD800 && codepoint <= 0xDBFF) { + // Handle UTF-16 surrogate pair + if (is.Take() != '\\' || is.Take() != 'u') { + RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, is.Tell() - 2);} + unsigned codepoint2 = ParseHex4(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + if (codepoint2 < 0xDC00 || codepoint2 > 0xDFFF) { + RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, is.Tell() - 2);} + codepoint = (((codepoint - 0xD800) << 10) | (codepoint2 - 0xDC00)) + 0x10000; + } + TEncoding::Encode(os, codepoint); + } + else { + RAPIDJSON_PARSE_ERROR(kParseErrorStringEscapeInvalid, is.Tell() - 1); } + } + else if (c == '"') { // Closing double quote + PushContentToStream(os, start, length); + os.Put('\0'); // null-terminate the string + return; + } + else if (c == '\0') { + RAPIDJSON_PARSE_ERROR(kParseErrorStringMissQuotationMark, is.Tell() - 1); } + else if ((unsigned)c < 0x20) { // RFC 4627: unescaped = %x20-21 / %x23-5B / %x5D-10FFFF { + RAPIDJSON_PARSE_ERROR(kParseErrorStringEscapeInvalid, is.Tell() - 1); } + else { + if (!start) { + start = pos; + length = 0; + } + length++; + if (is.ReadBlockTail()) { + PushContentToStream(os, start, length); + } + } + } + } + + // Parse string to an output is + // This function handles the prefix/suffix double quotes, escaping, and optional encoding validation. + template + RAPIDJSON_FORCEINLINE void ParseStringToStream(InputStream& is, OutputStream& os) { +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define Z16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + static const char escape[256] = { + Z16, Z16, 0, 0,'\"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'/', + Z16, Z16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, + 0, 0,'\b', 0, 0, 0,'\f', 0, 0, 0, 0, 0, 0, 0,'\n', 0, + 0, 0,'\r', 0,'\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 + }; +#undef Z16 +//!@endcond + + RAPIDJSON_ASSERT(is.Peek() == '\"'); + is.Take(); // Skip '\"' + + for (;;) { + Ch c = is.Peek(); + if (c == '\\') { // Escape + is.Take(); + Ch e = is.Take(); + if ((sizeof(Ch) == 1 || unsigned(e) < 256) && escape[(unsigned char)e]) { + os.Put(escape[(unsigned char)e]); + } + else if (e == 'u') { // Unicode + unsigned codepoint = ParseHex4(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + if (codepoint >= 0xD800 && codepoint <= 0xDBFF) { + // Handle UTF-16 surrogate pair + if (is.Take() != '\\' || is.Take() != 'u') + RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, is.Tell() - 2); + unsigned codepoint2 = ParseHex4(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + if (codepoint2 < 0xDC00 || codepoint2 > 0xDFFF) + RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, is.Tell() - 2); + codepoint = (((codepoint - 0xD800) << 10) | (codepoint2 - 0xDC00)) + 0x10000; + } + TEncoding::Encode(os, codepoint); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorStringEscapeInvalid, is.Tell() - 1); + } + else if (c == '"') { // Closing double quote + is.Take(); + os.Put('\0'); // null-terminate the string + return; + } + else if (c == '\0') + RAPIDJSON_PARSE_ERROR(kParseErrorStringMissQuotationMark, is.Tell() - 1); + else if ((unsigned)c < 0x20) // RFC 4627: unescaped = %x20-21 / %x23-5B / %x5D-10FFFF + RAPIDJSON_PARSE_ERROR(kParseErrorStringEscapeInvalid, is.Tell() - 1); + else { + if (parseFlags & kParseValidateEncodingFlag ? + !Transcoder::Validate(is, os) : + !Transcoder::Transcode(is, os)) + RAPIDJSON_PARSE_ERROR(kParseErrorStringInvalidEncoding, is.Tell()); + } + } + } + + template + class NumberStream; + + template + class NumberStream { + public: + NumberStream(GenericReader& reader, InputStream& s) : is(s) { (void)reader; } + ~NumberStream() {} + + RAPIDJSON_FORCEINLINE Ch Peek() const { return is.Peek(); } + RAPIDJSON_FORCEINLINE Ch TakePush() { return is.Take(); } + RAPIDJSON_FORCEINLINE Ch Take() { return is.Take(); } + size_t Tell() { return is.Tell(); } + size_t Length() { return 0; } + const char* Pop() { return 0; } + + protected: + NumberStream& operator=(const NumberStream&); + + InputStream& is; + }; + + template + class NumberStream : public NumberStream { + typedef NumberStream Base; + public: + NumberStream(GenericReader& reader, InputStream& is) : NumberStream(reader, is), stackStream(reader.stack_) {} + ~NumberStream() {} + + RAPIDJSON_FORCEINLINE Ch TakePush() { + stackStream.Put((char)Base::is.Peek()); + return Base::is.Take(); + } + + size_t Length() { return stackStream.Length(); } + + const char* Pop() { + stackStream.Put('\0'); + return stackStream.Pop(); + } + + private: + StackStream stackStream; + }; + + template + void ParseNumber(InputStream& is, Handler& handler) { + internal::StreamLocalCopy copy(is); + NumberStream s(*this, copy.s); + + // Parse minus + bool minus = false; + if (s.Peek() == '-') { + minus = true; + s.Take(); + } + + // Parse int: zero / ( digit1-9 *DIGIT ) + unsigned i = 0; + uint64_t i64 = 0; + bool use64bit = false; + int significandDigit = 0; + if (s.Peek() == '0') { + i = 0; + s.TakePush(); + } + else if (s.Peek() >= '1' && s.Peek() <= '9') { + i = static_cast(s.TakePush() - '0'); + + if (minus) + while (s.Peek() >= '0' && s.Peek() <= '9') { + if (i >= 214748364) { // 2^31 = 2147483648 + if (i != 214748364 || s.Peek() > '8') { + i64 = i; + use64bit = true; + break; + } + } + i = i * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + else + while (s.Peek() >= '0' && s.Peek() <= '9') { + if (i >= 429496729) { // 2^32 - 1 = 4294967295 + if (i != 429496729 || s.Peek() > '5') { + i64 = i; + use64bit = true; + break; + } + } + i = i * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); + + // Parse 64bit int + bool useDouble = false; + double d = 0.0; + if (use64bit) { + if (minus) + while (s.Peek() >= '0' && s.Peek() <= '9') { + if (i64 >= RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC)) // 2^63 = 9223372036854775808 + if (i64 != RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC) || s.Peek() > '8') { + d = i64; + useDouble = true; + break; + } + i64 = i64 * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + else + while (s.Peek() >= '0' && s.Peek() <= '9') { + if (i64 >= RAPIDJSON_UINT64_C2(0x19999999, 0x99999999)) // 2^64 - 1 = 18446744073709551615 + if (i64 != RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) || s.Peek() > '5') { + d = i64; + useDouble = true; + break; + } + i64 = i64 * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + } + + // Force double for big integer + if (useDouble) { + while (s.Peek() >= '0' && s.Peek() <= '9') { + if (d >= 1.7976931348623157e307) // DBL_MAX / 10.0 + RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, s.Tell()); + d = d * 10 + (s.TakePush() - '0'); + } + } + + // Parse frac = decimal-point 1*DIGIT + int expFrac = 0; + size_t decimalPosition; + if (s.Peek() == '.') { + s.Take(); + decimalPosition = s.Length(); + + if (!(s.Peek() >= '0' && s.Peek() <= '9')) + RAPIDJSON_PARSE_ERROR(kParseErrorNumberMissFraction, s.Tell()); + + if (!useDouble) { +#if RAPIDJSON_64BIT + // Use i64 to store significand in 64-bit architecture + if (!use64bit) + i64 = i; + + while (s.Peek() >= '0' && s.Peek() <= '9') { + if (i64 > RAPIDJSON_UINT64_C2(0x1FFFFF, 0xFFFFFFFF)) // 2^53 - 1 for fast path + break; + else { + i64 = i64 * 10 + static_cast(s.TakePush() - '0'); + --expFrac; + if (i64 != 0) + significandDigit++; + } + } + + d = (double)i64; +#else + // Use double to store significand in 32-bit architecture + d = use64bit ? (double)i64 : (double)i; +#endif + useDouble = true; + } + + while (s.Peek() >= '0' && s.Peek() <= '9') { + if (significandDigit < 17) { + d = d * 10.0 + (s.TakePush() - '0'); + --expFrac; + if (d > 0.0) + significandDigit++; + } + else + s.TakePush(); + } + } + else + decimalPosition = s.Length(); // decimal position at the end of integer. + + // Parse exp = e [ minus / plus ] 1*DIGIT + int exp = 0; + if (s.Peek() == 'e' || s.Peek() == 'E') { + if (!useDouble) { + d = use64bit ? i64 : i; + useDouble = true; + } + s.Take(); + + bool expMinus = false; + if (s.Peek() == '+') + s.Take(); + else if (s.Peek() == '-') { + s.Take(); + expMinus = true; + } + + if (s.Peek() >= '0' && s.Peek() <= '9') { + exp = s.Take() - '0'; + if (expMinus) { + while (s.Peek() >= '0' && s.Peek() <= '9') { + exp = exp * 10 + (s.Take() - '0'); + if (exp >= 214748364) { // Issue #313: prevent overflow exponent + while (s.Peek() >= '0' && s.Peek() <= '9') // Consume the rest of exponent + s.Take(); + } + } + } + else { // positive exp + int maxExp = 308 - expFrac; + while (s.Peek() >= '0' && s.Peek() <= '9') { + exp = exp * 10 + (s.Take() - '0'); + if (exp > maxExp) + RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, s.Tell()); + } + } + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorNumberMissExponent, s.Tell()); + + if (expMinus) + exp = -exp; + } + + // Finish parsing, call event according to the type of number. + bool cont = true; + size_t length = s.Length(); + const char* decimal = s.Pop(); // Pop stack no matter if it will be used or not. + + if (useDouble) { + int p = exp + expFrac; + if (parseFlags & kParseFullPrecisionFlag) + d = internal::StrtodFullPrecision(d, p, decimal, length, decimalPosition, exp); + else + d = internal::StrtodNormalPrecision(d, p); + + cont = handler.Double(minus ? -d : d); + } + else { + if (use64bit) { + if (minus) + cont = handler.AddInt64(static_cast(~i64 + 1)); + else + cont = handler.AddUint64(i64); + } + else { + if (minus) + cont = handler.AddInt(static_cast(~i + 1)); + else + cont = handler.AddUint(i); + } + } + if (!cont) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, s.Tell()); + } + + // Parse any JSON value + template + void ParseValue(InputStream& is, Handler& handler) { + switch (is.Peek()) { + case 'n': ParseNull (is, handler); break; + case 't': ParseTrue (is, handler); break; + case 'f': ParseFalse (is, handler); break; + case '"': ParseString(is, handler); break; + case '{': ParseObject(is, handler); break; + case '[': ParseArray (is, handler); break; + default : ParseNumber(is, handler); + } + } + + // Iterative Parsing + + // States + enum IterativeParsingState { + IterativeParsingStartState = 0, + IterativeParsingFinishState, + IterativeParsingErrorState, + + // Object states + IterativeParsingObjectInitialState, + IterativeParsingMemberKeyState, + IterativeParsingKeyValueDelimiterState, + IterativeParsingMemberValueState, + IterativeParsingMemberDelimiterState, + IterativeParsingObjectFinishState, + + // Array states + IterativeParsingArrayInitialState, + IterativeParsingElementState, + IterativeParsingElementDelimiterState, + IterativeParsingArrayFinishState, + + // Single value state + IterativeParsingValueState, + + cIterativeParsingStateCount + }; + + // Tokens + enum Token { + LeftBracketToken = 0, + RightBracketToken, + + LeftCurlyBracketToken, + RightCurlyBracketToken, + + CommaToken, + ColonToken, + + StringToken, + FalseToken, + TrueToken, + NullToken, + NumberToken, + + kTokenCount + }; + + RAPIDJSON_FORCEINLINE Token Tokenize(Ch c) { + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define N NumberToken +#define N16 N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N + // Maps from ASCII to Token + static const unsigned char tokenMap[256] = { + N16, // 00~0F + N16, // 10~1F + N, N, StringToken, N, N, N, N, N, N, N, N, N, CommaToken, N, N, N, // 20~2F + N, N, N, N, N, N, N, N, N, N, ColonToken, N, N, N, N, N, // 30~3F + N16, // 40~4F + N, N, N, N, N, N, N, N, N, N, N, LeftBracketToken, N, RightBracketToken, N, N, // 50~5F + N, N, N, N, N, N, FalseToken, N, N, N, N, N, N, N, NullToken, N, // 60~6F + N, N, N, N, TrueToken, N, N, N, N, N, N, LeftCurlyBracketToken, N, RightCurlyBracketToken, N, N, // 70~7F + N16, N16, N16, N16, N16, N16, N16, N16 // 80~FF + }; +#undef N +#undef N16 +//!@endcond + + if (sizeof(Ch) == 1 || unsigned(c) < 256) + return (Token)tokenMap[(unsigned char)c]; + else + return NumberToken; + } + + RAPIDJSON_FORCEINLINE IterativeParsingState Predict(IterativeParsingState state, Token token) { + // current state x one lookahead token -> new state + static const char G[cIterativeParsingStateCount][kTokenCount] = { + // Start + { + IterativeParsingArrayInitialState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingValueState, // String + IterativeParsingValueState, // False + IterativeParsingValueState, // True + IterativeParsingValueState, // Null + IterativeParsingValueState // Number + }, + // Finish(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // Error(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // ObjectInitial + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingObjectFinishState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingMemberKeyState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // MemberKey + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingKeyValueDelimiterState, // Colon + IterativeParsingErrorState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // KeyValueDelimiter + { + IterativeParsingArrayInitialState, // Left bracket(push MemberValue state) + IterativeParsingErrorState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket(push MemberValue state) + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingMemberValueState, // String + IterativeParsingMemberValueState, // False + IterativeParsingMemberValueState, // True + IterativeParsingMemberValueState, // Null + IterativeParsingMemberValueState // Number + }, + // MemberValue + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingObjectFinishState, // Right curly bracket + IterativeParsingMemberDelimiterState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingErrorState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // MemberDelimiter + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingMemberKeyState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // ObjectFinish(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // ArrayInitial + { + IterativeParsingArrayInitialState, // Left bracket(push Element state) + IterativeParsingArrayFinishState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket(push Element state) + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingElementState, // String + IterativeParsingElementState, // False + IterativeParsingElementState, // True + IterativeParsingElementState, // Null + IterativeParsingElementState // Number + }, + // Element + { + IterativeParsingErrorState, // Left bracket + IterativeParsingArrayFinishState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingElementDelimiterState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingErrorState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // ElementDelimiter + { + IterativeParsingArrayInitialState, // Left bracket(push Element state) + IterativeParsingErrorState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket(push Element state) + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingElementState, // String + IterativeParsingElementState, // False + IterativeParsingElementState, // True + IterativeParsingElementState, // Null + IterativeParsingElementState // Number + }, + // ArrayFinish(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // Single Value (sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + } + }; // End of G + + return (IterativeParsingState)G[state][token]; + } + + // Make an advance in the token stream and state based on the candidate destination state which was returned by Transit(). + // May return a new state on state pop. + template + RAPIDJSON_FORCEINLINE IterativeParsingState Transit(IterativeParsingState src, Token token, IterativeParsingState dst, InputStream& is, Handler& handler) { + (void)token; + + switch (dst) { + case IterativeParsingErrorState: + return dst; + + case IterativeParsingObjectInitialState: + case IterativeParsingArrayInitialState: + { + // Push the state(Element or MemeberValue) if we are nested in another array or value of member. + // In this way we can get the correct state on ObjectFinish or ArrayFinish by frame pop. + IterativeParsingState n = src; + if (src == IterativeParsingArrayInitialState || src == IterativeParsingElementDelimiterState) + n = IterativeParsingElementState; + else if (src == IterativeParsingKeyValueDelimiterState) + n = IterativeParsingMemberValueState; + // Push current state. + *stack_.template Push(1) = n; + // Initialize and push the member/element count. + *stack_.template Push(1) = 0; + // Call handler + bool hr = (dst == IterativeParsingObjectInitialState) ? handler.StartObject() : handler.StartArray(); + // On handler short circuits the parsing. + if (!hr) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); + return IterativeParsingErrorState; + } + else { + is.Take(); + return dst; + } + } + + case IterativeParsingMemberKeyState: + ParseString(is, handler, true); + if (HasParseError()) + return IterativeParsingErrorState; + else + return dst; + + case IterativeParsingKeyValueDelimiterState: + RAPIDJSON_ASSERT(token == ColonToken); + is.Take(); + return dst; + + case IterativeParsingMemberValueState: + // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. + ParseValue(is, handler); + if (HasParseError()) { + return IterativeParsingErrorState; + } + return dst; + + case IterativeParsingElementState: + // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. + ParseValue(is, handler); + if (HasParseError()) { + return IterativeParsingErrorState; + } + return dst; + + case IterativeParsingMemberDelimiterState: + case IterativeParsingElementDelimiterState: + is.Take(); + // Update member/element count. + *stack_.template Top() = *stack_.template Top() + 1; + return dst; + + case IterativeParsingObjectFinishState: + { + // Get member count. + SizeType c = *stack_.template Pop(1); + // If the object is not empty, count the last member. + if (src == IterativeParsingMemberValueState) + ++c; + // Restore the state. + IterativeParsingState n = static_cast(*stack_.template Pop(1)); + // Transit to Finish state if this is the topmost scope. + if (n == IterativeParsingStartState) + n = IterativeParsingFinishState; + // Call handler + bool hr = handler.EndObject(c); + // On handler short circuits the parsing. + if (!hr) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); + return IterativeParsingErrorState; + } + else { + is.Take(); + return n; + } + } + + case IterativeParsingArrayFinishState: + { + // Get element count. + SizeType c = *stack_.template Pop(1); + // If the array is not empty, count the last element. + if (src == IterativeParsingElementState) + ++c; + // Restore the state. + IterativeParsingState n = static_cast(*stack_.template Pop(1)); + // Transit to Finish state if this is the topmost scope. + if (n == IterativeParsingStartState) + n = IterativeParsingFinishState; + // Call handler + bool hr = handler.EndArray(c); + // On handler short circuits the parsing. + if (!hr) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); + return IterativeParsingErrorState; + } + else { + is.Take(); + return n; + } + } + + default: + // This branch is for IterativeParsingValueState actually. + // Use `default:` rather than + // `case IterativeParsingValueState:` is for code coverage. + + // The IterativeParsingStartState is not enumerated in this switch-case. + // It is impossible for that case. And it can be caught by following assertion. + + // The IterativeParsingFinishState is not enumerated in this switch-case either. + // It is a "derivative" state which cannot triggered from Predict() directly. + // Therefore it cannot happen here. And it can be caught by following assertion. + RAPIDJSON_ASSERT(dst == IterativeParsingValueState); + + // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. + ParseValue(is, handler); + if (HasParseError()) { + return IterativeParsingErrorState; + } + return IterativeParsingFinishState; + } + } + + template + void HandleError(IterativeParsingState src, InputStream& is) { + if (HasParseError()) { + // Error flag has been set. + return; + } + + switch (src) { + case IterativeParsingStartState: RAPIDJSON_PARSE_ERROR(kParseErrorDocumentEmpty, is.Tell()); return; + case IterativeParsingFinishState: RAPIDJSON_PARSE_ERROR(kParseErrorDocumentRootNotSingular, is.Tell()); return; + case IterativeParsingObjectInitialState: + case IterativeParsingMemberDelimiterState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissName, is.Tell()); return; + case IterativeParsingMemberKeyState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissColon, is.Tell()); return; + case IterativeParsingMemberValueState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissCommaOrCurlyBracket, is.Tell()); return; + case IterativeParsingElementState: RAPIDJSON_PARSE_ERROR(kParseErrorArrayMissCommaOrSquareBracket, is.Tell()); return; + default: RAPIDJSON_PARSE_ERROR(kParseErrorUnspecificSyntaxError, is.Tell()); + } + } + + template + ParseResult IterativeParse(InputStream& is, Handler& handler) { + parseResult_.Clear(); + ClearStackOnExit scope(*this); + IterativeParsingState state = IterativeParsingStartState; + + SkipWhitespace(is); + while (is.Peek() != '\0') { + Token t = Tokenize(is.Peek()); + IterativeParsingState n = Predict(state, t); + IterativeParsingState d = Transit(state, t, n, is, handler); + + if (d == IterativeParsingErrorState) { + HandleError(state, is); + break; + } + + state = d; + + // Do not further consume streams if a root JSON has been parsed. + if ((parseFlags & kParseStopWhenDoneFlag) && state == IterativeParsingFinishState) { + // wwb: Update parseResult_.Offset() when kParseStopWhenDoneFlag + // is set which means the user needs to know where to resume + // parsing in next calls to JsonToProtoMessage() + if (is.Peek() != '\0') { + SetParseError(kParseErrorNone, is.Tell()); + } + break; + } + SkipWhitespace(is); + } + + // Handle the end of file. + if (state != IterativeParsingFinishState) + HandleError(state, is); + + return parseResult_; + } + + static const size_t kDefaultStackCapacity = 256; //!< Default stack capacity in bytes for storing a single decoded string. + internal::Stack stack_; //!< A stack for storing decoded string temporarily during non-destructive parsing. + ParseResult parseResult_; +}; // class GenericReader + +//! Reader with UTF8 encoding and default allocator. +typedef GenericReader, UTF8<> > Reader; + +BUTIL_RAPIDJSON_NAMESPACE_END + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_READER_H_ diff --git a/src/butil/third_party/rapidjson/rename.patch b/src/butil/third_party/rapidjson/rename.patch new file mode 100644 index 0000000..cb990f5 --- /dev/null +++ b/src/butil/third_party/rapidjson/rename.patch @@ -0,0 +1,139 @@ +Index: reader.h +=================================================================== +--- reader.h (revision 30390) ++++ reader.h (working copy) +@@ -156,10 +156,10 @@ + + bool Null(); + bool Bool(bool b); +- bool Int(int i); +- bool Uint(unsigned i); +- bool Int64(int64_t i); +- bool Uint64(uint64_t i); ++ bool AddInt(int i); ++ bool AddUint(unsigned i); ++ bool AddInt64(int64_t i); ++ bool AddUint64(uint64_t i); + bool Double(double d); + bool String(const Ch* str, SizeType length, bool copy); + bool StartObject(); +@@ -186,10 +186,10 @@ + bool Default() { return true; } + bool Null() { return static_cast(*this).Default(); } + bool Bool(bool) { return static_cast(*this).Default(); } +- bool Int(int) { return static_cast(*this).Default(); } +- bool Uint(unsigned) { return static_cast(*this).Default(); } +- bool Int64(int64_t) { return static_cast(*this).Default(); } +- bool Uint64(uint64_t) { return static_cast(*this).Default(); } ++ bool AddInt(int) { return static_cast(*this).Default(); } ++ bool AddUint(unsigned) { return static_cast(*this).Default(); } ++ bool AddInt64(int64_t) { return static_cast(*this).Default(); } ++ bool AddUint64(uint64_t) { return static_cast(*this).Default(); } + bool Double(double) { return static_cast(*this).Default(); } + bool String(const Ch*, SizeType, bool) { return static_cast(*this).Default(); } + bool StartObject() { return static_cast(*this).Default(); } +@@ -967,15 +967,15 @@ + else { + if (use64bit) { + if (minus) +- cont = handler.Int64(static_cast(~i64 + 1)); ++ cont = handler.AddInt64(static_cast(~i64 + 1)); + else +- cont = handler.Uint64(i64); ++ cont = handler.AddUint64(i64); + } + else { + if (minus) +- cont = handler.Int(static_cast(~i + 1)); ++ cont = handler.AddInt(static_cast(~i + 1)); + else +- cont = handler.Uint(i); ++ cont = handler.AddUint(i); + } + } + if (!cont) +Index: writer.h +=================================================================== +--- writer.h (revision 30390) ++++ writer.h (working copy) +@@ -107,10 +107,10 @@ + + bool Null() { Prefix(kNullType); return WriteNull(); } + bool Bool(bool b) { Prefix(b ? kTrueType : kFalseType); return WriteBool(b); } +- bool Int(int i) { Prefix(kNumberType); return WriteInt(i); } +- bool Uint(unsigned u) { Prefix(kNumberType); return WriteUint(u); } +- bool Int64(int64_t i64) { Prefix(kNumberType); return WriteInt64(i64); } +- bool Uint64(uint64_t u64) { Prefix(kNumberType); return WriteUint64(u64); } ++ bool AddInt(int i) { Prefix(kNumberType); return WriteInt(i); } ++ bool AddUint(unsigned u) { Prefix(kNumberType); return WriteUint(u); } ++ bool AddInt64(int64_t i64) { Prefix(kNumberType); return WriteInt64(i64); } ++ bool AddUint64(uint64_t u64) { Prefix(kNumberType); return WriteUint64(u64); } + + //! Writes the given \c double value to the stream + /*! +Index: document.h +=================================================================== +--- document.h (revision 30390) ++++ document.h (working copy) +@@ -1537,10 +1537,10 @@ + + default: + RAPIDJSON_ASSERT(GetType() == kNumberType); +- if (IsInt()) return handler.Int(data_.n.i.i); +- else if (IsUint()) return handler.Uint(data_.n.u.u); +- else if (IsInt64()) return handler.Int64(data_.n.i64); +- else if (IsUint64()) return handler.Uint64(data_.n.u64); ++ if (IsInt()) return handler.AddInt(data_.n.i.i); ++ else if (IsUint()) return handler.AddUint(data_.n.u.u); ++ else if (IsInt64()) return handler.AddInt64(data_.n.i64); ++ else if (IsUint64()) return handler.AddUint64(data_.n.u64); + else return handler.Double(data_.n.d); + } + } +@@ -1941,10 +1941,10 @@ + // Implementation of Handler + bool Null() { new (stack_.template Push()) ValueType(); return true; } + bool Bool(bool b) { new (stack_.template Push()) ValueType(b); return true; } +- bool Int(int i) { new (stack_.template Push()) ValueType(i); return true; } +- bool Uint(unsigned i) { new (stack_.template Push()) ValueType(i); return true; } +- bool Int64(int64_t i) { new (stack_.template Push()) ValueType(i); return true; } +- bool Uint64(uint64_t i) { new (stack_.template Push()) ValueType(i); return true; } ++ bool AddInt(int i) { new (stack_.template Push()) ValueType(i); return true; } ++ bool AddUint(unsigned i) { new (stack_.template Push()) ValueType(i); return true; } ++ bool AddInt64(int64_t i) { new (stack_.template Push()) ValueType(i); return true; } ++ bool AddUint64(uint64_t i) { new (stack_.template Push()) ValueType(i); return true; } + bool Double(double d) { new (stack_.template Push()) ValueType(d); return true; } + + bool String(const Ch* str, SizeType length, bool copy) { +Index: pointer.h +=================================================================== +--- pointer.h (revision 30390) ++++ pointer.h (working copy) +@@ -271,7 +271,7 @@ + + //! Append a token by value, and return a new Pointer + /*! +- \param value Value (either Uint or String) to be appended. ++ \param value Value (either AddUint or String) to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ +Index: prettywriter.h +=================================================================== +--- prettywriter.h (revision 30390) ++++ prettywriter.h (working copy) +@@ -64,10 +64,10 @@ + + bool Null() { PrettyPrefix(kNullType); return Base::WriteNull(); } + bool Bool(bool b) { PrettyPrefix(b ? kTrueType : kFalseType); return Base::WriteBool(b); } +- bool Int(int i) { PrettyPrefix(kNumberType); return Base::WriteInt(i); } +- bool Uint(unsigned u) { PrettyPrefix(kNumberType); return Base::WriteUint(u); } +- bool Int64(int64_t i64) { PrettyPrefix(kNumberType); return Base::WriteInt64(i64); } +- bool Uint64(uint64_t u64) { PrettyPrefix(kNumberType); return Base::WriteUint64(u64); } ++ bool AddInt(int i) { PrettyPrefix(kNumberType); return Base::WriteInt(i); } ++ bool AddUint(unsigned u) { PrettyPrefix(kNumberType); return Base::WriteUint(u); } ++ bool AddInt64(int64_t i64) { PrettyPrefix(kNumberType); return Base::WriteInt64(i64); } ++ bool AddUint64(uint64_t u64) { PrettyPrefix(kNumberType); return Base::WriteUint64(u64); } + bool Double(double d) { PrettyPrefix(kNumberType); return Base::WriteDouble(d); } + + bool String(const Ch* str, SizeType length, bool copy = false) { diff --git a/src/butil/third_party/rapidjson/stringbuffer.h b/src/butil/third_party/rapidjson/stringbuffer.h new file mode 100644 index 0000000..9265777 --- /dev/null +++ b/src/butil/third_party/rapidjson/stringbuffer.h @@ -0,0 +1,97 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_STRINGBUFFER_H_ +#define RAPIDJSON_STRINGBUFFER_H_ + +#include "rapidjson.h" + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS +#include // std::move +#endif + +#include "internal/stack.h" + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN + +//! Represents an in-memory output stream. +/*! + \tparam Encoding Encoding of the stream. + \tparam Allocator type for allocating memory buffer. + \note implements Stream concept +*/ +template +class GenericStringBuffer { +public: + typedef typename Encoding::Ch Ch; + + GenericStringBuffer(Allocator* allocator = 0, size_t capacity = kDefaultCapacity) : stack_(allocator, capacity) {} + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericStringBuffer(GenericStringBuffer&& rhs) : stack_(std::move(rhs.stack_)) {} + GenericStringBuffer& operator=(GenericStringBuffer&& rhs) { + if (&rhs != this) + stack_ = std::move(rhs.stack_); + return *this; + } +#endif + + void Put(Ch c) { *stack_.template Push() = c; } + void Puts(const Ch* c, size_t length) { + Ch* pos = stack_.template Push(length); + memcpy(pos, c, length * sizeof(Ch)); + } + void Flush() {} + + void Clear() { stack_.Clear(); } + void ShrinkToFit() { + // Push and pop a null terminator. This is safe. + *stack_.template Push() = '\0'; + stack_.ShrinkToFit(); + stack_.template Pop(1); + } + Ch* Push(size_t count) { return stack_.template Push(count); } + void Pop(size_t count) { stack_.template Pop(count); } + + const Ch* GetString() const { + // Push and pop a null terminator. This is safe. + *stack_.template Push() = '\0'; + stack_.template Pop(1); + + return stack_.template Bottom(); + } + + size_t GetSize() const { return stack_.GetSize(); } + + static const size_t kDefaultCapacity = 256; + mutable internal::Stack stack_; + +private: + // Prohibit copy constructor & assignment operator. + GenericStringBuffer(const GenericStringBuffer&); + GenericStringBuffer& operator=(const GenericStringBuffer&); +}; + +//! String buffer with UTF8 encoding +typedef GenericStringBuffer > StringBuffer; + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(GenericStringBuffer >& stream, char c, size_t n) { + std::memset(stream.stack_.Push(n), c, n * sizeof(c)); +} + +BUTIL_RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_STRINGBUFFER_H_ diff --git a/src/butil/third_party/rapidjson/writer.h b/src/butil/third_party/rapidjson/writer.h new file mode 100644 index 0000000..136a635 --- /dev/null +++ b/src/butil/third_party/rapidjson/writer.h @@ -0,0 +1,395 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_WRITER_H_ +#define RAPIDJSON_WRITER_H_ + +#include "rapidjson.h" +#include "internal/stack.h" +#include "internal/strfunc.h" +#include "internal/dtoa.h" +#include "internal/itoa.h" +#include "stringbuffer.h" +#include // placement new + +#if RAPIDJSON_HAS_STDSTRING +#include +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant +#endif + +BUTIL_RAPIDJSON_NAMESPACE_BEGIN + +//! JSON writer +/*! Writer implements the concept Handler. + It generates JSON text by events to an output os. + + User may programmatically calls the functions of a writer to generate JSON text. + + On the other side, a writer can also be passed to objects that generates events, + + for example Reader::Parse() and Document::Accept(). + + \tparam OutputStream Type of output stream. + \tparam SourceEncoding Encoding of source string. + \tparam TargetEncoding Encoding of output stream. + \tparam StackAllocator Type of allocator for allocating memory of stack. + \note implements Handler concept +*/ +template, typename TargetEncoding = UTF8<>, typename StackAllocator = CrtAllocator> +class Writer { +public: + typedef typename SourceEncoding::Ch Ch; + + //! Constructor + /*! \param os Output stream. + \param stackAllocator User supplied allocator. If it is null, it will create a private one. + \param levelDepth Initial capacity of stack. + */ + explicit + Writer(OutputStream& os, StackAllocator* stackAllocator = 0, size_t levelDepth = kDefaultLevelDepth) : + os_(&os), level_stack_(stackAllocator, levelDepth * sizeof(Level)), hasRoot_(false) {} + + explicit + Writer(StackAllocator* allocator = 0, size_t levelDepth = kDefaultLevelDepth) : + os_(0), level_stack_(allocator, levelDepth * sizeof(Level)), hasRoot_(false) {} + + //! Reset the writer with a new stream. + /*! + This function reset the writer with a new stream and default settings, + in order to make a Writer object reusable for output multiple JSONs. + + \param os New output stream. + \code + Writer writer(os1); + writer.StartObject(); + // ... + writer.EndObject(); + + writer.Reset(os2); + writer.StartObject(); + // ... + writer.EndObject(); + \endcode + */ + void Reset(OutputStream& os) { + os_ = &os; + hasRoot_ = false; + level_stack_.Clear(); + } + + //! Checks whether the output is a complete JSON. + /*! + A complete JSON has a complete root object or array. + */ + bool IsComplete() const { + return hasRoot_ && level_stack_.Empty(); + } + + /*!@name Implementation of Handler + \see Handler + */ + //@{ + + bool Null() { Prefix(kNullType); return WriteNull(); } + bool Bool(bool b) { Prefix(b ? kTrueType : kFalseType); return WriteBool(b); } + bool AddInt(int i) { Prefix(kNumberType); return WriteInt(i); } + bool AddUint(unsigned u) { Prefix(kNumberType); return WriteUint(u); } + bool AddInt64(int64_t i64) { Prefix(kNumberType); return WriteInt64(i64); } + bool AddUint64(uint64_t u64) { Prefix(kNumberType); return WriteUint64(u64); } + + //! Writes the given \c double value to the stream + /*! + \param d The value to be written. + \return Whether it is succeed. + */ + bool Double(double d) { Prefix(kNumberType); return WriteDouble(d); } + + bool String(const Ch* str, SizeType length, bool copy = false) { + (void)copy; + Prefix(kStringType); + return WriteString(str, length); + } + +#if RAPIDJSON_HAS_STDSTRING + bool String(const std::basic_string& str) { + return String(str.data(), SizeType(str.size())); + } +#endif + + bool StartObject() { + Prefix(kObjectType); + new (level_stack_.template Push()) Level(false); + return WriteStartObject(); + } + + bool Key(const Ch* str, SizeType length, bool copy = false) { return String(str, length, copy); } + + bool EndObject(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(level_stack_.GetSize() >= sizeof(Level)); + RAPIDJSON_ASSERT(!level_stack_.template Top()->inArray); + level_stack_.template Pop(1); + bool ret = WriteEndObject(); + if (level_stack_.Empty()) // end of json text + os_->Flush(); + return ret; + } + + bool StartArray() { + Prefix(kArrayType); + new (level_stack_.template Push()) Level(true); + return WriteStartArray(); + } + + bool EndArray(SizeType elementCount = 0) { + (void)elementCount; + RAPIDJSON_ASSERT(level_stack_.GetSize() >= sizeof(Level)); + RAPIDJSON_ASSERT(level_stack_.template Top()->inArray); + level_stack_.template Pop(1); + bool ret = WriteEndArray(); + if (level_stack_.Empty()) // end of json text + os_->Flush(); + return ret; + } + //@} + + /*! @name Convenience extensions */ + //@{ + + //! Simpler but slower overload. + bool String(const Ch* str) { return String(str, internal::StrLen(str)); } + bool Key(const Ch* str) { return Key(str, internal::StrLen(str)); } + + //@} + +protected: + //! Information for each nested level + struct Level { + Level(bool inArray_) : valueCount(0), inArray(inArray_) {} + size_t valueCount; //!< number of values in this level + bool inArray; //!< true if in array, otherwise in object + }; + + static const size_t kDefaultLevelDepth = 32; + + bool WriteNull() { + os_->Put('n'); os_->Put('u'); os_->Put('l'); os_->Put('l'); return true; + } + + bool WriteBool(bool b) { + if (b) { + os_->Put('t'); os_->Put('r'); os_->Put('u'); os_->Put('e'); + } + else { + os_->Put('f'); os_->Put('a'); os_->Put('l'); os_->Put('s'); os_->Put('e'); + } + return true; + } + + bool WriteInt(int i) { + char buffer[11]; + const char* end = internal::i32toa(i, buffer); + for (const char* p = buffer; p != end; ++p) + os_->Put(*p); + return true; + } + + bool WriteUint(unsigned u) { + char buffer[10]; + const char* end = internal::u32toa(u, buffer); + for (const char* p = buffer; p != end; ++p) + os_->Put(*p); + return true; + } + + bool WriteInt64(int64_t i64) { + char buffer[21]; + const char* end = internal::i64toa(i64, buffer); + for (const char* p = buffer; p != end; ++p) + os_->Put(*p); + return true; + } + + bool WriteUint64(uint64_t u64) { + char buffer[20]; + char* end = internal::u64toa(u64, buffer); + for (char* p = buffer; p != end; ++p) + os_->Put(*p); + return true; + } + + bool WriteDouble(double d) { + char buffer[25]; + char* end = internal::dtoa(d, buffer); + for (char* p = buffer; p != end; ++p) + os_->Put(*p); + return true; + } + + bool WriteString(const Ch* str, SizeType length) { + static const char hexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + static const char escape[256] = { +#define Z16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'b', 't', 'n', 'u', 'f', 'r', 'u', 'u', // 00 + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', // 10 + 0, 0, '"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20 + Z16, Z16, // 30~4F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, // 50 + Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 // 60~FF +#undef Z16 + }; + + os_->Put('\"'); + GenericStringStream is(str); + while (is.Tell() < length) { + const Ch c = is.Peek(); + if (!TargetEncoding::supportUnicode && (unsigned)c >= 0x80) { + // Unicode escaping + unsigned codepoint; + if (!SourceEncoding::Decode(is, &codepoint)) + return false; + os_->Put('\\'); + os_->Put('u'); + if (codepoint <= 0xD7FF || (codepoint >= 0xE000 && codepoint <= 0xFFFF)) { + os_->Put(hexDigits[(codepoint >> 12) & 15]); + os_->Put(hexDigits[(codepoint >> 8) & 15]); + os_->Put(hexDigits[(codepoint >> 4) & 15]); + os_->Put(hexDigits[(codepoint ) & 15]); + } + else { + RAPIDJSON_ASSERT(codepoint >= 0x010000 && codepoint <= 0x10FFFF); + // Surrogate pair + unsigned s = codepoint - 0x010000; + unsigned lead = (s >> 10) + 0xD800; + unsigned trail = (s & 0x3FF) + 0xDC00; + os_->Put(hexDigits[(lead >> 12) & 15]); + os_->Put(hexDigits[(lead >> 8) & 15]); + os_->Put(hexDigits[(lead >> 4) & 15]); + os_->Put(hexDigits[(lead ) & 15]); + os_->Put('\\'); + os_->Put('u'); + os_->Put(hexDigits[(trail >> 12) & 15]); + os_->Put(hexDigits[(trail >> 8) & 15]); + os_->Put(hexDigits[(trail >> 4) & 15]); + os_->Put(hexDigits[(trail ) & 15]); + } + } + else if ((sizeof(Ch) == 1 || (unsigned)c < 256) && escape[(unsigned char)c]) { + is.Take(); + os_->Put('\\'); + os_->Put(escape[(unsigned char)c]); + if (escape[(unsigned char)c] == 'u') { + os_->Put('0'); + os_->Put('0'); + os_->Put(hexDigits[(unsigned char)c >> 4]); + os_->Put(hexDigits[(unsigned char)c & 0xF]); + } + } + else + if (!Transcoder::Transcode(is, *os_)) + return false; + } + os_->Put('\"'); + return true; + } + + bool WriteStartObject() { os_->Put('{'); return true; } + bool WriteEndObject() { os_->Put('}'); return true; } + bool WriteStartArray() { os_->Put('['); return true; } + bool WriteEndArray() { os_->Put(']'); return true; } + + void Prefix(Type type) { + (void)type; + if (level_stack_.GetSize() != 0) { // this value is not at root + Level* level = level_stack_.template Top(); + if (level->valueCount > 0) { + if (level->inArray) + os_->Put(','); // add comma if it is not the first element in array + else // in object + os_->Put((level->valueCount % 2 == 0) ? ',' : ':'); + } + if (!level->inArray && level->valueCount % 2 == 0) + RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name + level->valueCount++; + } + else { + RAPIDJSON_ASSERT(!hasRoot_); // Should only has one and only one root. + hasRoot_ = true; + } + } + + OutputStream* os_; + internal::Stack level_stack_; + bool hasRoot_; + +private: + // Prohibit copy constructor & assignment operator. + Writer(const Writer&); + Writer& operator=(const Writer&); +}; + +// Full specialization for StringStream to prevent memory copying + +template<> +inline bool Writer::WriteInt(int i) { + char *buffer = os_->Push(11); + const char* end = internal::i32toa(i, buffer); + os_->Pop(11 - (end - buffer)); + return true; +} + +template<> +inline bool Writer::WriteUint(unsigned u) { + char *buffer = os_->Push(10); + const char* end = internal::u32toa(u, buffer); + os_->Pop(10 - (end - buffer)); + return true; +} + +template<> +inline bool Writer::WriteInt64(int64_t i64) { + char *buffer = os_->Push(21); + const char* end = internal::i64toa(i64, buffer); + os_->Pop(21 - (end - buffer)); + return true; +} + +template<> +inline bool Writer::WriteUint64(uint64_t u) { + char *buffer = os_->Push(20); + const char* end = internal::u64toa(u, buffer); + os_->Pop(20 - (end - buffer)); + return true; +} + +template<> +inline bool Writer::WriteDouble(double d) { + char *buffer = os_->Push(25); + char* end = internal::dtoa(d, buffer); + os_->Pop(25 - (end - buffer)); + return true; +} + +BUTIL_RAPIDJSON_NAMESPACE_END + +#ifdef _MSC_VER +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/src/butil/third_party/snappy/format_description.txt b/src/butil/third_party/snappy/format_description.txt new file mode 100644 index 0000000..20db66c --- /dev/null +++ b/src/butil/third_party/snappy/format_description.txt @@ -0,0 +1,110 @@ +Snappy compressed format description +Last revised: 2011-10-05 + + +This is not a formal specification, but should suffice to explain most +relevant parts of how the Snappy format works. It is originally based on +text by Zeev Tarantov. + +Snappy is a LZ77-type compressor with a fixed, byte-oriented encoding. +There is no entropy encoder backend nor framing layer -- the latter is +assumed to be handled by other parts of the system. + +This document only describes the format, not how the Snappy compressor nor +decompressor actually works. The correctness of the decompressor should not +depend on implementation details of the compressor, and vice versa. + + +1. Preamble + +The stream starts with the uncompressed length (up to a maximum of 2^32 - 1), +stored as a little-endian varint. Varints consist of a series of bytes, +where the lower 7 bits are data and the upper bit is set iff there are +more bytes to be read. In other words, an uncompressed length of 64 would +be stored as 0x40, and an uncompressed length of 2097150 (0x1FFFFE) +would be stored as 0xFE 0xFF 0x7F. + + +2. The compressed stream itself + +There are two types of elements in a Snappy stream: Literals and +copies (backreferences). There is no restriction on the order of elements, +except that the stream naturally cannot start with a copy. (Having +two literals in a row is never optimal from a compression point of +view, but nevertheless fully permitted.) Each element starts with a tag byte, +and the lower two bits of this tag byte signal what type of element will +follow: + + 00: Literal + 01: Copy with 1-byte offset + 10: Copy with 2-byte offset + 11: Copy with 4-byte offset + +The interpretation of the upper six bits are element-dependent. + + +2.1. Literals (00) + +Literals are uncompressed data stored directly in the byte stream. +The literal length is stored differently depending on the length +of the literal: + + - For literals up to and including 60 bytes in length, the upper + six bits of the tag byte contain (len-1). The literal follows + immediately thereafter in the bytestream. + - For longer literals, the (len-1) value is stored after the tag byte, + little-endian. The upper six bits of the tag byte describe how + many bytes are used for the length; 60, 61, 62 or 63 for + 1-4 bytes, respectively. The literal itself follows after the + length. + + +2.2. Copies + +Copies are references back into previous decompressed data, telling +the decompressor to reuse data it has previously decoded. +They encode two values: The _offset_, saying how many bytes back +from the current position to read, and the _length_, how many bytes +to copy. Offsets of zero can be encoded, but are not legal; +similarly, it is possible to encode backreferences that would +go past the end of the block (offset > current decompressed position), +which is also nonsensical and thus not allowed. + +As in most LZ77-based compressors, the length can be larger than the offset, +yielding a form of run-length encoding (RLE). For instance, +"xababab" could be encoded as + + + +Note that since the current Snappy compressor works in 32 kB +blocks and does not do matching across blocks, it will never produce +a bitstream with offsets larger than about 32768. However, the +decompressor should not rely on this, as it may change in the future. + +There are several different kinds of copy elements, depending on +the amount of bytes to be copied (length), and how far back the +data to be copied is (offset). + + +2.2.1. Copy with 1-byte offset (01) + +These elements can encode lengths between [4..11] bytes and offsets +between [0..2047] bytes. (len-4) occupies three bits and is stored +in bits [2..4] of the tag byte. The offset occupies 11 bits, of which the +upper three are stored in the upper three bits ([5..7]) of the tag byte, +and the lower eight are stored in a byte following the tag byte. + + +2.2.2. Copy with 2-byte offset (10) + +These elements can encode lengths between [1..64] and offsets from +[0..65535]. (len-1) occupies six bits and is stored in the upper +six bits ([2..7]) of the tag byte. The offset is stored as a +little-endian 16-bit integer in the two bytes following the tag byte. + + +2.2.3. Copy with 4-byte offset (11) + +These are like the copies with 2-byte offsets (see previous subsection), +except that the offset is stored as a 32-bit integer instead of a +16-bit integer (and thus will occupy four bytes). diff --git a/src/butil/third_party/snappy/framing_format.txt b/src/butil/third_party/snappy/framing_format.txt new file mode 100644 index 0000000..9764e83 --- /dev/null +++ b/src/butil/third_party/snappy/framing_format.txt @@ -0,0 +1,135 @@ +Snappy framing format description +Last revised: 2013-10-25 + +This format decribes a framing format for Snappy, allowing compressing to +files or streams that can then more easily be decompressed without having +to hold the entire stream in memory. It also provides data checksums to +help verify integrity. It does not provide metadata checksums, so it does +not protect against e.g. all forms of truncations. + +Implementation of the framing format is optional for Snappy compressors and +decompressor; it is not part of the Snappy core specification. + + +1. General structure + +The file consists solely of chunks, lying back-to-back with no padding +in between. Each chunk consists first a single byte of chunk identifier, +then a three-byte little-endian length of the chunk in bytes (from 0 to +16777215, inclusive), and then the data if any. The four bytes of chunk +header is not counted in the data length. + +The different chunk types are listed below. The first chunk must always +be the stream identifier chunk (see section 4.1, below). The stream +ends when the file ends -- there is no explicit end-of-file marker. + + +2. File type identification + +The following identifiers for this format are recommended where appropriate. +However, note that none have been registered officially, so this is only to +be taken as a guideline. We use "Snappy framed" to distinguish between this +format and raw Snappy data. + + File extension: .sz + MIME type: application/x-snappy-framed + HTTP Content-Encoding: x-snappy-framed + + +3. Checksum format + +Some chunks have data protected by a checksum (the ones that do will say so +explicitly). The checksums are always masked CRC-32Cs. + +A description of CRC-32C can be found in RFC 3720, section 12.1, with +examples in section B.4. + +Checksums are not stored directly, but masked, as checksumming data and +then its own checksum can be problematic. The masking is the same as used +in Apache Hadoop: Rotate the checksum by 15 bits, then add the constant +0xa282ead8 (using wraparound as normal for unsigned integers). This is +equivalent to the following C code: + + uint32_t mask_checksum(uint32_t x) { + return ((x >> 15) | (x << 17)) + 0xa282ead8; + } + +Note that the masking is reversible. + +The checksum is always stored as a four bytes long integer, in little-endian. + + +4. Chunk types + +The currently supported chunk types are described below. The list may +be extended in the future. + + +4.1. Stream identifier (chunk type 0xff) + +The stream identifier is always the first element in the stream. +It is exactly six bytes long and contains "sNaPpY" in ASCII. This means that +a valid Snappy framed stream always starts with the bytes + + 0xff 0x06 0x00 0x00 0x73 0x4e 0x61 0x50 0x70 0x59 + +The stream identifier chunk can come multiple times in the stream besides +the first; if such a chunk shows up, it should simply be ignored, assuming +it has the right length and contents. This allows for easy concatenation of +compressed files without the need for re-framing. + + +4.2. Compressed data (chunk type 0x00) + +Compressed data chunks contain a normal Snappy compressed bitstream; +see the compressed format specification. The compressed data is preceded by +the CRC-32C (see section 3) of the _uncompressed_ data. + +Note that the data portion of the chunk, i.e., the compressed contents, +can be at most 16777211 bytes (2^24 - 1, minus the checksum). +However, we place an additional restriction that the uncompressed data +in a chunk must be no longer than 65536 bytes. This allows consumers to +easily use small fixed-size buffers. + + +4.3. Uncompressed data (chunk type 0x01) + +Uncompressed data chunks allow a compressor to send uncompressed, +raw data; this is useful if, for instance, uncompressible or +near-incompressible data is detected, and faster decompression is desired. + +As in the compressed chunks, the data is preceded by its own masked +CRC-32C (see section 3). + +An uncompressed data chunk, like compressed data chunks, should contain +no more than 65536 data bytes, so the maximum legal chunk length with the +checksum is 65540. + + +4.4. Padding (chunk type 0xfe) + +Padding chunks allow a compressor to increase the size of the data stream +so that it complies with external demands, e.g. that the total number of +bytes is a multiple of some value. + +All bytes of the padding chunk, except the chunk byte itself and the length, +should be zero, but decompressors must not try to interpret or verify the +padding data in any way. + + +4.5. Reserved unskippable chunks (chunk types 0x02-0x7f) + +These are reserved for future expansion. A decoder that sees such a chunk +should immediately return an error, as it must assume it cannot decode the +stream correctly. + +Future versions of this specification may define meanings for these chunks. + + +4.6. Reserved skippable chunks (chunk types 0x80-0xfd) + +These are also reserved for future expansion, but unlike the chunks +described in 4.5, a decoder seeing these must skip them and continue +decoding. + +Future versions of this specification may define meanings for these chunks. diff --git a/src/butil/third_party/snappy/snappy-internal.h b/src/butil/third_party/snappy/snappy-internal.h new file mode 100644 index 0000000..3822a99 --- /dev/null +++ b/src/butil/third_party/snappy/snappy-internal.h @@ -0,0 +1,152 @@ +// Copyright 2008 Google Inc. All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Internals shared between the Snappy implementation and its unittest. + +#ifndef BUTIL_THIRD_PARTY_SNAPPY_SNAPPY_INTERNAL_H_ +#define BUTIL_THIRD_PARTY_SNAPPY_SNAPPY_INTERNAL_H_ + +#include "snappy-stubs-internal.h" + +namespace butil { +namespace snappy { +namespace internal { + +class WorkingMemory { +public: + WorkingMemory() : large_table_(NULL) { } + ~WorkingMemory() { delete[] large_table_; } + + // Allocates and clears a hash table using memory in "*this", + // stores the number of buckets in "*table_size" and returns a pointer to + // the base of the hash table. + uint16_t* GetHashTable(size_t input_size, int* table_size); + +private: + uint16_t small_table_[1<<10]; // 2KB + uint16_t* large_table_; // Allocated only when needed + + DISALLOW_COPY_AND_ASSIGN(WorkingMemory); +}; + +// Flat array compression that does not emit the "uncompressed length" +// prefix. Compresses "input" string to the "*op" buffer. +// +// REQUIRES: "input_length <= kBlockSize" +// REQUIRES: "op" points to an array of memory that is at least +// "MaxCompressedLength(input_length)" in size. +// REQUIRES: All elements in "table[0..table_size-1]" are initialized to zero. +// REQUIRES: "table_size" is a power of two +// +// Returns an "end" pointer into "op" buffer. +// "end - op" is the compressed size of "input". +char* CompressFragment(const char* input, + size_t input_length, + char* op, + uint16_t* table, + const int table_size); + +// Return the largest n such that +// +// s1[0,n-1] == s2[0,n-1] +// and n <= (s2_limit - s2). +// +// Does not read *s2_limit or beyond. +// Does not read *(s1 + (s2_limit - s2)) or beyond. +// Requires that s2_limit >= s2. +// +// Separate implementation for x86_64, for speed. Uses the fact that +// x86_64 is little endian. +#if defined(ARCH_K8) +static inline int FindMatchLength(const char* s1, + const char* s2, + const char* s2_limit) { + assert(s2_limit >= s2); + int matched = 0; + + // Find out how long the match is. We loop over the data 64 bits at a + // time until we find a 64-bit block that doesn't match; then we find + // the first non-matching bit and use that to calculate the total + // length of the match. + while (BAIDU_LIKELY(s2 <= s2_limit - 8)) { + if (UNALIGNED_LOAD64(s2) == UNALIGNED_LOAD64(s1 + matched)) { + s2 += 8; + matched += 8; + } else { + // On current (mid-2008) Opteron models there is a 3% more + // efficient code sequence to find the first non-matching byte. + // However, what follows is ~10% better on Intel Core 2 and newer, + // and we expect AMD's bsf instruction to improve. + uint64_t x = UNALIGNED_LOAD64(s2) ^ UNALIGNED_LOAD64(s1 + matched); + int matching_bits = Bits::FindLSBSetNonZero64(x); + matched += matching_bits >> 3; + return matched; + } + } + while (BAIDU_LIKELY(s2 < s2_limit)) { + if (s1[matched] == *s2) { + ++s2; + ++matched; + } else { + return matched; + } + } + return matched; +} +#else +static inline int FindMatchLength(const char* s1, + const char* s2, + const char* s2_limit) { + // Implementation based on the x86-64 version, above. + assert(s2_limit >= s2); + int matched = 0; + + while (s2 <= s2_limit - 4 && + UNALIGNED_LOAD32(s2) == UNALIGNED_LOAD32(s1 + matched)) { + s2 += 4; + matched += 4; + } + if (LittleEndian::IsLittleEndian() && s2 <= s2_limit - 4) { + uint32_t x = UNALIGNED_LOAD32(s2) ^ UNALIGNED_LOAD32(s1 + matched); + int matching_bits = Bits::FindLSBSetNonZero(x); + matched += matching_bits >> 3; + } else { + while ((s2 < s2_limit) && (s1[matched] == *s2)) { + ++s2; + ++matched; + } + } + return matched; +} +#endif + +} // end namespace internal +} // end namespace snappy +} // end namespace butil + +#endif // BUTIL_THIRD_PARTY_SNAPPY_SNAPPY_INTERNAL_H_ diff --git a/src/butil/third_party/snappy/snappy-sinksource.cc b/src/butil/third_party/snappy/snappy-sinksource.cc new file mode 100644 index 0000000..58136b4 --- /dev/null +++ b/src/butil/third_party/snappy/snappy-sinksource.cc @@ -0,0 +1,106 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include "butil/third_party/snappy/snappy-sinksource.h" + +namespace butil { +namespace snappy { + +Source::~Source() { } + +Sink::~Sink() { } + +char* Sink::GetAppendBuffer(size_t length, char* scratch) { + return scratch; +} + +char* Sink::GetAppendBufferVariable( + size_t min_size, size_t desired_size_hint, char* scratch, + size_t scratch_size, size_t* allocated_size) { + *allocated_size = scratch_size; + return scratch; +} + +void Sink::AppendAndTakeOwnership( + char* bytes, size_t n, + void (*deleter)(void*, const char*, size_t), + void *deleter_arg) { + Append(bytes, n); + (*deleter)(deleter_arg, bytes, n); +} + +ByteArraySource::~ByteArraySource() { } + +size_t ByteArraySource::Available() const { return left_; } + +const char* ByteArraySource::Peek(size_t* len) { + *len = left_; + return ptr_; +} + +void ByteArraySource::Skip(size_t n) { + left_ -= n; + ptr_ += n; +} + +UncheckedByteArraySink::~UncheckedByteArraySink() { } + +void UncheckedByteArraySink::Append(const char* data, size_t n) { + // Do no copying if the caller filled in the result of GetAppendBuffer() + if (data != dest_) { + memcpy(dest_, data, n); + } + dest_ += n; +} + +char* UncheckedByteArraySink::GetAppendBuffer(size_t len, char* scratch) { + return dest_; +} + +void UncheckedByteArraySink::AppendAndTakeOwnership( + char* data, size_t n, + void (*deleter)(void*, const char*, size_t), + void *deleter_arg) { + if (data != dest_) { + memcpy(dest_, data, n); + (*deleter)(deleter_arg, data, n); + } + dest_ += n; +} + +char* UncheckedByteArraySink::GetAppendBufferVariable( + size_t min_size, size_t desired_size_hint, char* scratch, + size_t scratch_size, size_t* allocated_size) { + *allocated_size = desired_size_hint; + return dest_; +} + +} // namespace snappy +} // namespace butil diff --git a/src/butil/third_party/snappy/snappy-sinksource.h b/src/butil/third_party/snappy/snappy-sinksource.h new file mode 100644 index 0000000..b34fb4c --- /dev/null +++ b/src/butil/third_party/snappy/snappy-sinksource.h @@ -0,0 +1,184 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BUTIL_THIRD_PARTY_SNAPPY_SNAPPY_SINKSOURCE_H_ +#define BUTIL_THIRD_PARTY_SNAPPY_SNAPPY_SINKSOURCE_H_ + +#include + +namespace butil { +namespace snappy { + +// A Sink is an interface that consumes a sequence of bytes. +class Sink { + public: + Sink() { } + virtual ~Sink(); + + // Append "bytes[0,n-1]" to this. + virtual void Append(const char* bytes, size_t n) = 0; + + // Returns a writable buffer of the specified length for appending. + // May return a pointer to the caller-owned scratch buffer which + // must have at least the indicated length. The returned buffer is + // only valid until the next operation on this Sink. + // + // After writing at most "length" bytes, call Append() with the + // pointer returned from this function and the number of bytes + // written. Many Append() implementations will avoid copying + // bytes if this function returned an internal buffer. + // + // If a non-scratch buffer is returned, the caller may only pass a + // prefix of it to Append(). That is, it is not correct to pass an + // interior pointer of the returned array to Append(). + // + // The default implementation always returns the scratch buffer. + virtual char* GetAppendBuffer(size_t length, char* scratch); + + // For higher performance, Sink implementations can provide custom + // AppendAndTakeOwnership() and GetAppendBufferVariable() methods. + // These methods can reduce the number of copies done during + // compression/decompression. + + // Append "bytes[0,n-1] to the sink. Takes ownership of "bytes" + // and calls the deleter function as (*deleter)(deleter_arg, bytes, n) + // to free the buffer. deleter function must be non NULL. + // + // The default implementation just calls Append and frees "bytes". + // Other implementations may avoid a copy while appending the buffer. + virtual void AppendAndTakeOwnership( + char* bytes, size_t n, void (*deleter)(void*, const char*, size_t), + void *deleter_arg); + + // Returns a writable buffer for appending and writes the buffer's capacity to + // *allocated_size. Guarantees *allocated_size >= min_size. + // May return a pointer to the caller-owned scratch buffer which must have + // scratch_size >= min_size. + // + // The returned buffer is only valid until the next operation + // on this ByteSink. + // + // After writing at most *allocated_size bytes, call Append() with the + // pointer returned from this function and the number of bytes written. + // Many Append() implementations will avoid copying bytes if this function + // returned an internal buffer. + // + // If the sink implementation allocates or reallocates an internal buffer, + // it should use the desired_size_hint if appropriate. If a caller cannot + // provide a reasonable guess at the desired capacity, it should set + // desired_size_hint = 0. + // + // If a non-scratch buffer is returned, the caller may only pass + // a prefix to it to Append(). That is, it is not correct to pass an + // interior pointer to Append(). + // + // The default implementation always returns the scratch buffer. + virtual char* GetAppendBufferVariable( + size_t min_size, size_t desired_size_hint, char* scratch, + size_t scratch_size, size_t* allocated_size); + + private: + // No copying + Sink(const Sink&); + void operator=(const Sink&); +}; + +// A Source is an interface that yields a sequence of bytes +class Source { + public: + Source() { } + virtual ~Source(); + + // Return the number of bytes left to read from the source + virtual size_t Available() const = 0; + + // Peek at the next flat region of the source. Does not reposition + // the source. The returned region is empty iff Available()==0. + // + // Returns a pointer to the beginning of the region and store its + // length in *len. + // + // The returned region is valid until the next call to Skip() or + // until this object is destroyed, whichever occurs first. + // + // The returned region may be larger than Available() (for example + // if this ByteSource is a view on a substring of a larger source). + // The caller is responsible for ensuring that it only reads the + // Available() bytes. + virtual const char* Peek(size_t* len) = 0; + + // Skip the next n bytes. Invalidates any buffer returned by + // a previous call to Peek(). + // REQUIRES: Available() >= n + virtual void Skip(size_t n) = 0; + + private: + // No copying + Source(const Source&); + void operator=(const Source&); +}; + +// A Source implementation that yields the contents of a flat array +class ByteArraySource : public Source { + public: + ByteArraySource(const char* p, size_t n) : ptr_(p), left_(n) { } + virtual ~ByteArraySource(); + size_t Available() const override; + const char* Peek(size_t* len) override; + void Skip(size_t n) override; + private: + const char* ptr_; + size_t left_; +}; + +// A Sink implementation that writes to a flat array without any bound checks. +class UncheckedByteArraySink : public Sink { + public: + explicit UncheckedByteArraySink(char* dest) : dest_(dest) { } + virtual ~UncheckedByteArraySink(); + void Append(const char* data, size_t n) override; + char* GetAppendBuffer(size_t len, char* scratch) override; + char* GetAppendBufferVariable( + size_t min_size, size_t desired_size_hint, char* scratch, + size_t scratch_size, size_t* allocated_size) override; + void AppendAndTakeOwnership( + char* bytes, size_t n, void (*deleter)(void*, const char*, size_t), + void *deleter_arg) override; + + // Return the current output pointer so that a caller can see how + // many bytes were produced. + // Note: this is not a Sink method. + char* CurrentDestination() const { return dest_; } + private: + char* dest_; +}; + +} // namespace snappy +} // namespace butil + +#endif // BUTIL_THIRD_PARTY_SNAPPY_SNAPPY_SINKSOURCE_H_ diff --git a/src/butil/third_party/snappy/snappy-stubs-internal.cc b/src/butil/third_party/snappy/snappy-stubs-internal.cc new file mode 100644 index 0000000..f357132 --- /dev/null +++ b/src/butil/third_party/snappy/snappy-stubs-internal.cc @@ -0,0 +1,44 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include + +#include "butil/third_party/snappy/snappy-stubs-internal.h" + +namespace butil { +namespace snappy { + +void Varint::Append32(std::string* s, uint32_t value) { + char buf[Varint::kMax32]; + const char* p = Varint::Encode32(buf, value); + s->append(buf, p - buf); +} + +} // namespace snappy +} // namespace butil diff --git a/src/butil/third_party/snappy/snappy-stubs-internal.h b/src/butil/third_party/snappy/snappy-stubs-internal.h new file mode 100644 index 0000000..764f97b --- /dev/null +++ b/src/butil/third_party/snappy/snappy-stubs-internal.h @@ -0,0 +1,418 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Various stubs for the open-source version of Snappy. + +#ifndef BUTIL_THIRD_PARTY_SNAPPY_OPENSOURCE_SNAPPY_STUBS_INTERNAL_H_ +#define BUTIL_THIRD_PARTY_SNAPPY_OPENSOURCE_SNAPPY_STUBS_INTERNAL_H_ + +#include +#include +#include +#include +#include "butil/compiler_specific.h" +#include "butil/basictypes.h" +#include "butil/sys_byteorder.h" + +#define SNAPPY_MAJOR 1 +#define SNAPPY_MINOR 1 +#define SNAPPY_PATCHLEVEL 3 +#define SNAPPY_VERSION \ + ((SNAPPY_MAJOR << 16) | (SNAPPY_MINOR << 8) | SNAPPY_PATCHLEVEL) + +#if defined(__x86_64__) + +// Enable 64-bit optimized versions of some routines. +#define ARCH_K8 1 + +#endif + +// Needed by OS X, among others. +#ifndef MAP_ANONYMOUS +#define MAP_ANONYMOUS MAP_ANON +#endif + +// This is only used for recomputing the tag byte table used during +// decompression; for simplicity we just remove it from the open-source +// version (anyone who wants to regenerate it can just do the call +// themselves within main()). +#define DEFINE_bool(flag_name, default_value, description) \ + bool FLAGS_ ## flag_name = default_value +#define DECLARE_bool(flag_name) \ + extern bool FLAGS_ ## flag_name + +namespace butil { +namespace snappy { + +// x86 and PowerPC can simply do these loads and stores native. + +#if defined(__i386__) || defined(__x86_64__) || defined(__powerpc__) + +#define UNALIGNED_LOAD16(_p) (*reinterpret_cast(_p)) +#define UNALIGNED_LOAD32(_p) (*reinterpret_cast(_p)) +#define UNALIGNED_LOAD64(_p) (*reinterpret_cast(_p)) + +#define UNALIGNED_STORE16(_p, _val) (*reinterpret_cast(_p) = (_val)) +#define UNALIGNED_STORE32(_p, _val) (*reinterpret_cast(_p) = (_val)) +#define UNALIGNED_STORE64(_p, _val) (*reinterpret_cast(_p) = (_val)) + +// ARMv7 and newer support native unaligned accesses, but only of 16-bit +// and 32-bit values (not 64-bit); older versions either raise a fatal signal, +// do an unaligned read and rotate the words around a bit, or do the reads very +// slowly (trip through kernel mode). There's no simple #define that says just +// “ARMv7 or higher”, so we have to filter away all ARMv5 and ARMv6 +// sub-architectures. +// +// This is a mess, but there's not much we can do about it. + +#elif defined(__arm__) && \ + !defined(__ARM_ARCH_4__) && \ + !defined(__ARM_ARCH_4T__) && \ + !defined(__ARM_ARCH_5__) && \ + !defined(__ARM_ARCH_5T__) && \ + !defined(__ARM_ARCH_5TE__) && \ + !defined(__ARM_ARCH_5TEJ__) && \ + !defined(__ARM_ARCH_6__) && \ + !defined(__ARM_ARCH_6J__) && \ + !defined(__ARM_ARCH_6K__) && \ + !defined(__ARM_ARCH_6Z__) && \ + !defined(__ARM_ARCH_6ZK__) && \ + !defined(__ARM_ARCH_6T2__) + +#define UNALIGNED_LOAD16(_p) (*reinterpret_cast(_p)) +#define UNALIGNED_LOAD32(_p) (*reinterpret_cast(_p)) + +#define UNALIGNED_STORE16(_p, _val) (*reinterpret_cast(_p) = (_val)) +#define UNALIGNED_STORE32(_p, _val) (*reinterpret_cast(_p) = (_val)) + +// TODO(user): NEON supports unaligned 64-bit loads and stores. +// See if that would be more efficient on platforms supporting it, +// at least for copies. + +inline uint64_t UNALIGNED_LOAD64(const void *p) { + uint64_t t; + memcpy(&t, p, sizeof t); + return t; +} + +inline void UNALIGNED_STORE64(void *p, uint64_t v) { + memcpy(p, &v, sizeof v); +} + +#else + +// These functions are provided for architectures that don't support +// unaligned loads and stores. + +inline uint16_t UNALIGNED_LOAD16(const void *p) { + uint16_t t; + memcpy(&t, p, sizeof t); + return t; +} + +inline uint32_t UNALIGNED_LOAD32(const void *p) { + uint32_t t; + memcpy(&t, p, sizeof t); + return t; +} + +inline uint64_t UNALIGNED_LOAD64(const void *p) { + uint64_t t; + memcpy(&t, p, sizeof t); + return t; +} + +inline void UNALIGNED_STORE16(void *p, uint16_t v) { + memcpy(p, &v, sizeof v); +} + +inline void UNALIGNED_STORE32(void *p, uint32_t v) { + memcpy(p, &v, sizeof v); +} + +inline void UNALIGNED_STORE64(void *p, uint64_t v) { + memcpy(p, &v, sizeof v); +} + +#endif + +// This can be more efficient than UNALIGNED_LOAD64 + UNALIGNED_STORE64 +// on some platforms, in particular ARM. +inline void UnalignedCopy64(const void *src, void *dst) { +#if defined(__riscv) && __riscv_xlen == 64 + // RISC-V optimized: single ld/sd pair for 8-byte copy (aligned only) + if ((((uintptr_t)src | (uintptr_t)dst) & 7) == 0) { + uint64_t tmp; + __asm__ volatile( + "ld %0, %1\n\t" + "sd %0, %2\n\t" + : "=&r"(tmp) + : "m"(*(const uint64_t*)src), "m"(*(uint64_t*)dst) + : "memory"); + } else { + // Unaligned: fall back to memcpy-based approach + memcpy(dst, src, 8); + } +#else + if (sizeof(void *) == 8) { + UNALIGNED_STORE64(dst, UNALIGNED_LOAD64(src)); + } else { + const char *src_char = reinterpret_cast(src); + char *dst_char = reinterpret_cast(dst); + + UNALIGNED_STORE32(dst_char, UNALIGNED_LOAD32(src_char)); + UNALIGNED_STORE32(dst_char + 4, UNALIGNED_LOAD32(src_char + 4)); + } +#endif +} + +// Convert to little-endian storage, opposite of network format. +// Convert x from host to little endian: x = LittleEndian.FromHost(x); +// convert x from little endian to host: x = LittleEndian.ToHost(x); +// +// Store values into unaligned memory converting to little endian order: +// LittleEndian.Store16(p, x); +// +// Load unaligned values stored in little endian converting to host order: +// x = LittleEndian.Load16(p); +class LittleEndian { +public: + // Conversion functions. +#if defined(ARCH_CPU_LITTLE_ENDIAN) + static uint16_t FromHost16(uint16_t x) { return x; } + static uint16_t ToHost16(uint16_t x) { return x; } + + static uint32_t FromHost32(uint32_t x) { return x; } + static uint32_t ToHost32(uint32_t x) { return x; } + + static bool IsLittleEndian() { return true; } + +#else // !defined(ARCH_CPU_LITTLE_ENDIAN) + static uint16_t FromHost16(uint16_t x) { return ByteSwap(x); } + static uint16_t ToHost16(uint16_t x) { return ByteSwap(x); } + + static uint32_t FromHost32(uint32_t x) { return ByteSwap(x); } + static uint32_t ToHost32(uint32_t x) { return ByteSwap(x); } + + static bool IsLittleEndian() { return false; } +#endif // !defined(ARCH_CPU_LITTLE_ENDIAN) + + // Functions to do unaligned loads and stores in little-endian order. + static uint16_t Load16(const void *p) { + return ToHost16(UNALIGNED_LOAD16(p)); + } + + static void Store16(void *p, uint16_t v) { + UNALIGNED_STORE16(p, FromHost16(v)); + } + + static uint32_t Load32(const void *p) { + return ToHost32(UNALIGNED_LOAD32(p)); + } + + static void Store32(void *p, uint32_t v) { + UNALIGNED_STORE32(p, FromHost32(v)); + } +}; + +// Some bit-manipulation functions. +class Bits { +public: + // Return floor(log2(n)) for positive integer n. Returns -1 iff n == 0. + static int Log2Floor(uint32_t n); + + // Return the first set least / most significant bit, 0-indexed. Returns an + // undefined value if n == 0. FindLSBSetNonZero() is similar to ffs() except + // that it's 0-indexed. + static int FindLSBSetNonZero(uint32_t n); + static int FindLSBSetNonZero64(uint64_t n); + +private: + DISALLOW_COPY_AND_ASSIGN(Bits); +}; + +#if defined(COMPILER_GCC) + +inline int Bits::Log2Floor(uint32_t n) { + return n == 0 ? -1 : 31 ^ __builtin_clz(n); +} + +inline int Bits::FindLSBSetNonZero(uint32_t n) { + return __builtin_ctz(n); +} + +inline int Bits::FindLSBSetNonZero64(uint64_t n) { + return __builtin_ctzll(n); +} + +#else // Portable versions. + +inline int Bits::Log2Floor(uint32_t n) { + if (n == 0) + return -1; + int log = 0; + uint32_t value = n; + for (int i = 4; i >= 0; --i) { + int shift = (1 << i); + uint32_t x = value >> shift; + if (x != 0) { + value = x; + log += shift; + } + } + assert(value == 1); + return log; +} + +inline int Bits::FindLSBSetNonZero(uint32_t n) { + int rc = 31; + for (int i = 4, shift = 1 << 4; i >= 0; --i) { + const uint32_t x = n << shift; + if (x != 0) { + n = x; + rc -= shift; + } + shift >>= 1; + } + return rc; +} + +// FindLSBSetNonZero64() is defined in terms of FindLSBSetNonZero(). +inline int Bits::FindLSBSetNonZero64(uint64_t n) { + const uint32_t bottombits = static_cast(n); + if (bottombits == 0) { + // Bottom bits are zero, so scan in top bits + return 32 + FindLSBSetNonZero(static_cast(n >> 32)); + } else { + return FindLSBSetNonZero(bottombits); + } +} + +#endif // End portable versions. + +// Variable-length integer encoding. +class Varint { +public: + // Maximum lengths of varint encoding of uint32. + static const int kMax32 = 5; + + // Attempts to parse a varint32 from a prefix of the bytes in [ptr,limit-1]. + // Never reads a character at or beyond limit. If a valid/terminated varint32 + // was found in the range, stores it in *OUTPUT and returns a pointer just + // past the last byte of the varint32. Else returns NULL. On success, + // "result <= limit". + static const char* Parse32WithLimit(const char* ptr, const char* limit, + uint32_t* OUTPUT); + + // REQUIRES "ptr" points to a buffer of length sufficient to hold "v". + // EFFECTS Encodes "v" into "ptr" and returns a pointer to the + // byte just past the last encoded byte. + static char* Encode32(char* ptr, uint32_t v); + + // EFFECTS Appends the varint representation of "value" to "*s". + static void Append32(std::string* s, uint32_t value); +}; + +inline const char* Varint::Parse32WithLimit(const char* p, + const char* l, + uint32_t* OUTPUT) { + const unsigned char* ptr = reinterpret_cast(p); + const unsigned char* limit = reinterpret_cast(l); + uint32_t b, result; + if (ptr >= limit) return NULL; + b = *(ptr++); result = b & 127; if (b < 128) goto done; + if (ptr >= limit) return NULL; + b = *(ptr++); result |= (b & 127) << 7; if (b < 128) goto done; + if (ptr >= limit) return NULL; + b = *(ptr++); result |= (b & 127) << 14; if (b < 128) goto done; + if (ptr >= limit) return NULL; + b = *(ptr++); result |= (b & 127) << 21; if (b < 128) goto done; + if (ptr >= limit) return NULL; + b = *(ptr++); result |= (b & 127) << 28; if (b < 16) goto done; + return NULL; // Value is too long to be a varint32 +done: + *OUTPUT = result; + return reinterpret_cast(ptr); +} + +inline char* Varint::Encode32(char* sptr, uint32_t v) { + // Operate on characters as unsigneds + unsigned char* ptr = reinterpret_cast(sptr); + static const int B = 128; + if (v < (1<<7)) { + *(ptr++) = v; + } else if (v < (1<<14)) { + *(ptr++) = v | B; + *(ptr++) = v>>7; + } else if (v < (1<<21)) { + *(ptr++) = v | B; + *(ptr++) = (v>>7) | B; + *(ptr++) = v>>14; + } else if (v < (1<<28)) { + *(ptr++) = v | B; + *(ptr++) = (v>>7) | B; + *(ptr++) = (v>>14) | B; + *(ptr++) = v>>21; + } else { + *(ptr++) = v | B; + *(ptr++) = (v>>7) | B; + *(ptr++) = (v>>14) | B; + *(ptr++) = (v>>21) | B; + *(ptr++) = v>>28; + } + return reinterpret_cast(ptr); +} + +// If you know the internal layout of the std::string in use, you can +// replace this function with one that resizes the string without +// filling the new space with zeros (if applicable) -- +// it will be non-portable but faster. +inline void STLStringResizeUninitialized(std::string* s, size_t new_size) { + s->resize(new_size); +} + +// Return a mutable char* pointing to a string's internal buffer, +// which may not be null-terminated. Writing through this pointer will +// modify the string. +// +// string_as_array(&str)[i] is valid for 0 <= i < str.size() until the +// next call to a string method that invalidates iterators. +// +// As of 2006-04, there is no standard-blessed way of getting a +// mutable reference to a string's internal buffer. However, issue 530 +// (http://www.open-std.org/JTC1/SC22/WG21/docs/lwg-defects.html#530) +// proposes this as the method. It will officially be part of the standard +// for C++0x. This should already work on all current implementations. +inline char* string_as_array(std::string* str) { + return str->empty() ? NULL : &*str->begin(); +} + +} // namespace snappy +} // namespace butil + +#endif // BUTIL_THIRD_PARTY_SNAPPY_OPENSOURCE_SNAPPY_STUBS_INTERNAL_H_ diff --git a/src/butil/third_party/snappy/snappy.cc b/src/butil/third_party/snappy/snappy.cc new file mode 100644 index 0000000..bcbf39e --- /dev/null +++ b/src/butil/third_party/snappy/snappy.cc @@ -0,0 +1,1578 @@ +// Copyright 2005 Google Inc. All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "butil/third_party/snappy/snappy.h" +#include "butil/third_party/snappy/snappy-internal.h" +#include "butil/third_party/snappy/snappy-sinksource.h" + +#include + +#include +#include +#include + +namespace butil { +namespace snappy { + +// Any hash function will produce a valid compressed bitstream, but a good +// hash function reduces the number of collisions and thus yields better +// compression for compressible input, and more speed for incompressible +// input. Of course, it doesn't hurt if the hash function is reasonably fast +// either, as it gets called a lot. +static inline uint32_t HashBytes(uint32_t bytes, int shift) { + uint32_t kMul = 0x1e35a7bd; + return (bytes * kMul) >> shift; +} +static inline uint32_t Hash(const char* p, int shift) { + return HashBytes(UNALIGNED_LOAD32(p), shift); +} + +size_t MaxCompressedLength(size_t source_len) { + // Compressed data can be defined as: + // compressed := item* literal* + // item := literal* copy + // + // The trailing literal sequence has a space blowup of at most 62/60 + // since a literal of length 60 needs one tag byte + one extra byte + // for length information. + // + // Item blowup is trickier to measure. Suppose the "copy" op copies + // 4 bytes of data. Because of a special check in the encoding code, + // we produce a 4-byte copy only if the offset is < 65536. Therefore + // the copy op takes 3 bytes to encode, and this type of item leads + // to at most the 62/60 blowup for representing literals. + // + // Suppose the "copy" op copies 5 bytes of data. If the offset is big + // enough, it will take 5 bytes to encode the copy op. Therefore the + // worst case here is a one-byte literal followed by a five-byte copy. + // I.e., 6 bytes of input turn into 7 bytes of "compressed" data. + // + // This last factor dominates the blowup, so the final estimate is: + return 32 + source_len + source_len/6; +} + +enum { + LITERAL = 0, + COPY_1_BYTE_OFFSET = 1, // 3 bit length + 3 bits of offset in opcode + COPY_2_BYTE_OFFSET = 2, + COPY_4_BYTE_OFFSET = 3 +}; +static const uint32_t kMaximumTagLength = 5; // COPY_4_BYTE_OFFSET plus the actual offset. + +// Copy "len" bytes from "src" to "op", one byte at a time. Used for +// handling COPY operations where the input and output regions may +// overlap. For example, suppose: +// src == "ab" +// op == src + 2 +// len == 20 +// After IncrementalCopy(src, op, len), the result will have +// eleven copies of "ab" +// ababababababababababab +// Note that this does not match the semantics of either memcpy() +// or memmove(). +static inline void IncrementalCopy(const char* src, char* op, ssize_t len) { + assert(len > 0); +#if defined(__riscv) && __riscv_xlen == 64 + // RISC-V optimized: use 8-byte copies when aligned and safe + if (len >= 8 && (op - src >= 8 || src - op >= 8) && + (((uintptr_t)src | (uintptr_t)op) & 7) == 0) { + do { + uint64_t tmp; + __asm__ volatile( + "ld %0, %1\n\t" + "sd %0, %2\n\t" + : "=&r"(tmp) + : "m"(*(const uint64_t*)src), "m"(*(uint64_t*)op) + : "memory"); + src += 8; + op += 8; + len -= 8; + } while (len >= 8); + } + while (len > 0) { + *op++ = *src++; + --len; + } +#else + do { + *op++ = *src++; + } while (--len > 0); +#endif +} + +// Equivalent to IncrementalCopy except that it can write up to ten extra +// bytes after the end of the copy, and that it is faster. +// +// The main part of this loop is a simple copy of eight bytes at a time until +// we've copied (at least) the requested amount of bytes. However, if op and +// src are less than eight bytes apart (indicating a repeating pattern of +// length < 8), we first need to expand the pattern in order to get the correct +// results. For instance, if the buffer looks like this, with the eight-byte +// and patterns marked as intervals: +// +// abxxxxxxxxxxxx +// [------] src +// [------] op +// +// a single eight-byte copy from to will repeat the pattern once, +// after which we can move two bytes without moving : +// +// ababxxxxxxxxxx +// [------] src +// [------] op +// +// and repeat the exercise until the two no longer overlap. +// +// This allows us to do very well in the special case of one single byte +// repeated many times, without taking a big hit for more general cases. +// +// The worst case of extra writing past the end of the match occurs when +// op - src == 1 and len == 1; the last copy will read from byte positions +// [0..7] and write to [4..11], whereas it was only supposed to write to +// position 1. Thus, ten excess bytes. + +namespace { + +const uint32_t kMaxIncrementCopyOverflow = 10; + +inline void IncrementalCopyFastPath(const char* src, char* op, ssize_t len) { + while (BAIDU_UNLIKELY(op - src < 8)) { + UnalignedCopy64(src, op); + len -= op - src; + op += op - src; + } + while (len > 0) { + UnalignedCopy64(src, op); + src += 8; + op += 8; + len -= 8; + } +} + +} // namespace + +static inline char* EmitLiteral(char* op, + const char* literal, + int len, + bool allow_fast_path) { + int n = len - 1; // Zero-length literals are disallowed + if (n < 60) { + // Fits in tag byte + *op++ = LITERAL | (n << 2); + + // The vast majority of copies are below 16 bytes, for which a + // call to memcpy is overkill. This fast path can sometimes + // copy up to 15 bytes too much, but that is okay in the + // main loop, since we have a bit to go on for both sides: + // + // - The input will always have kInputMarginBytes = 15 extra + // available bytes, as long as we're in the main loop, and + // if not, allow_fast_path = false. + // - The output will always have 32 spare bytes (see + // MaxCompressedLength). + if (allow_fast_path && len <= 16) { + UnalignedCopy64(literal, op); + UnalignedCopy64(literal + 8, op + 8); + return op + len; + } + } else { + // Encode in upcoming bytes + char* base = op; + int count = 0; + op++; + while (n > 0) { + *op++ = n & 0xff; + n >>= 8; + count++; + } + assert(count >= 1); + assert(count <= 4); + *base = LITERAL | ((59+count) << 2); + } + memcpy(op, literal, len); + return op + len; +} + +static inline char* EmitCopyLessThan64(char* op, size_t offset, int len) { + assert(len <= 64); + assert(len >= 4); + assert(offset < 65536); + + if ((len < 12) && (offset < 2048)) { + size_t len_minus_4 = len - 4; + assert(len_minus_4 < 8); // Must fit in 3 bits + *op++ = COPY_1_BYTE_OFFSET + ((len_minus_4) << 2) + ((offset >> 8) << 5); + *op++ = offset & 0xff; + } else { + *op++ = COPY_2_BYTE_OFFSET + ((len-1) << 2); + LittleEndian::Store16(op, offset); + op += 2; + } + return op; +} + +static inline char* EmitCopy(char* op, size_t offset, int len) { + // Emit 64 byte copies but make sure to keep at least four bytes reserved + while (BAIDU_UNLIKELY(len >= 68)) { + op = EmitCopyLessThan64(op, offset, 64); + len -= 64; + } + + // Emit an extra 60 byte copy if have too much data to fit in one copy + if (len > 64) { + op = EmitCopyLessThan64(op, offset, 60); + len -= 60; + } + + // Emit remainder + op = EmitCopyLessThan64(op, offset, len); + return op; +} + + +bool GetUncompressedLength(const char* start, size_t n, size_t* result) { + uint32_t v = 0; + const char* limit = start + n; + if (Varint::Parse32WithLimit(start, limit, &v) != NULL) { + *result = v; + return true; + } else { + return false; + } +} + +namespace internal { +uint16_t* WorkingMemory::GetHashTable(size_t input_size, int* table_size) { + // Use smaller hash table when input.size() is smaller, since we + // fill the table, incurring O(hash table size) overhead for + // compression, and if the input is short, we won't need that + // many hash table entries anyway. + assert(kMaxHashTableSize >= 256); + size_t htsize = 256; + while (htsize < kMaxHashTableSize && htsize < input_size) { + htsize <<= 1; + } + + uint16_t* table; + if (htsize <= arraysize(small_table_)) { + table = small_table_; + } else { + if (large_table_ == NULL) { + large_table_ = new uint16_t[kMaxHashTableSize]; + } + table = large_table_; + } + + *table_size = htsize; + memset(table, 0, htsize * sizeof(*table)); + return table; +} +} // end namespace internal + +// For 0 <= offset <= 4, GetUint32AtOffset(GetEightBytesAt(p), offset) will +// equal UNALIGNED_LOAD32(p + offset). Motivation: On x86-64 hardware we have +// empirically found that overlapping loads such as +// UNALIGNED_LOAD32(p) ... UNALIGNED_LOAD32(p+1) ... UNALIGNED_LOAD32(p+2) +// are slower than UNALIGNED_LOAD64(p) followed by shifts and casts to uint32. +// +// We have different versions for 64- and 32-bit; ideally we would avoid the +// two functions and just inline the UNALIGNED_LOAD64 call into +// GetUint32AtOffset, but GCC (at least not as of 4.6) is seemingly not clever +// enough to avoid loading the value multiple times then. For 64-bit, the load +// is done when GetEightBytesAt() is called, whereas for 32-bit, the load is +// done at GetUint32AtOffset() time. + +#ifdef ARCH_K8 + +typedef uint64_t EightBytesReference; + +static inline EightBytesReference GetEightBytesAt(const char* ptr) { + return UNALIGNED_LOAD64(ptr); +} + +static inline uint32_t GetUint32AtOffset(uint64_t v, int offset) { + assert(offset >= 0); + assert(offset <= 4); + return v >> (LittleEndian::IsLittleEndian() ? 8 * offset : 32 - 8 * offset); +} + +#else + +typedef const char* EightBytesReference; + +static inline EightBytesReference GetEightBytesAt(const char* ptr) { + return ptr; +} + +static inline uint32_t GetUint32AtOffset(const char* v, int offset) { + assert(offset >= 0); + assert(offset <= 4); + return UNALIGNED_LOAD32(v + offset); +} + +#endif + +// Flat array compression that does not emit the "uncompressed length" +// prefix. Compresses "input" string to the "*op" buffer. +// +// REQUIRES: "input" is at most "kBlockSize" bytes long. +// REQUIRES: "op" points to an array of memory that is at least +// "MaxCompressedLength(input.size())" in size. +// REQUIRES: All elements in "table[0..table_size-1]" are initialized to zero. +// REQUIRES: "table_size" is a power of two +// +// Returns an "end" pointer into "op" buffer. +// "end - op" is the compressed size of "input". +namespace internal { +char* CompressFragment(const char* input, + size_t input_size, + char* op, + uint16_t* table, + const int table_size) { + // "ip" is the input pointer, and "op" is the output pointer. + const char* ip = input; + assert(input_size <= kBlockSize); + assert((table_size & (table_size - 1)) == 0); // table must be power of two + const int shift = 32 - Bits::Log2Floor(table_size); + assert(static_cast(kuint32max >> shift) == table_size - 1); + const char* ip_end = input + input_size; + const char* base_ip = ip; + // Bytes in [next_emit, ip) will be emitted as literal bytes. Or + // [next_emit, ip_end) after the main loop. + const char* next_emit = ip; + + const size_t kInputMarginBytes = 15; + if (BAIDU_LIKELY(input_size >= kInputMarginBytes)) { + const char* ip_limit = input + input_size - kInputMarginBytes; + + for (uint32_t next_hash = Hash(++ip, shift); ; ) { + assert(next_emit < ip); + // The body of this loop calls EmitLiteral once and then EmitCopy one or + // more times. (The exception is that when we're close to exhausting + // the input we goto emit_remainder.) + // + // In the first iteration of this loop we're just starting, so + // there's nothing to copy, so calling EmitLiteral once is + // necessary. And we only start a new iteration when the + // current iteration has determined that a call to EmitLiteral will + // precede the next call to EmitCopy (if any). + // + // Step 1: Scan forward in the input looking for a 4-byte-long match. + // If we get close to exhausting the input then goto emit_remainder. + // + // Heuristic match skipping: If 32 bytes are scanned with no matches + // found, start looking only at every other byte. If 32 more bytes are + // scanned, look at every third byte, etc.. When a match is found, + // immediately go back to looking at every byte. This is a small loss + // (~5% performance, ~0.1% density) for compressible data due to more + // bookkeeping, but for non-compressible data (such as JPEG) it's a huge + // win since the compressor quickly "realizes" the data is incompressible + // and doesn't bother looking for matches everywhere. + // + // The "skip" variable keeps track of how many bytes there are since the + // last match; dividing it by 32 (ie. right-shifting by five) gives the + // number of bytes to move ahead for each iteration. + uint32_t skip = 32; + + const char* next_ip = ip; + const char* candidate; + do { + ip = next_ip; + uint32_t hash = next_hash; + assert(hash == Hash(ip, shift)); + uint32_t bytes_between_hash_lookups = skip++ >> 5; + next_ip = ip + bytes_between_hash_lookups; + if (BAIDU_UNLIKELY(next_ip > ip_limit)) { + goto emit_remainder; + } + next_hash = Hash(next_ip, shift); + candidate = base_ip + table[hash]; + assert(candidate >= base_ip); + assert(candidate < ip); + + table[hash] = ip - base_ip; + } while (BAIDU_LIKELY(UNALIGNED_LOAD32(ip) != + UNALIGNED_LOAD32(candidate))); + + // Step 2: A 4-byte match has been found. We'll later see if more + // than 4 bytes match. But, prior to the match, input + // bytes [next_emit, ip) are unmatched. Emit them as "literal bytes." + assert(next_emit + 16 <= ip_end); + op = EmitLiteral(op, next_emit, ip - next_emit, true); + + // Step 3: Call EmitCopy, and then see if another EmitCopy could + // be our next move. Repeat until we find no match for the + // input immediately after what was consumed by the last EmitCopy call. + // + // If we exit this loop normally then we need to call EmitLiteral next, + // though we don't yet know how big the literal will be. We handle that + // by proceeding to the next iteration of the main loop. We also can exit + // this loop via goto if we get close to exhausting the input. + EightBytesReference input_bytes; + uint32_t candidate_bytes = 0; + + do { + // We have a 4-byte match at ip, and no need to emit any + // "literal bytes" prior to ip. + const char* base = ip; + int matched = 4 + FindMatchLength(candidate + 4, ip + 4, ip_end); + ip += matched; + size_t offset = base - candidate; + assert(0 == memcmp(base, candidate, matched)); + op = EmitCopy(op, offset, matched); + // We could immediately start working at ip now, but to improve + // compression we first update table[Hash(ip - 1, ...)]. + const char* insert_tail = ip - 1; + next_emit = ip; + if (BAIDU_UNLIKELY(ip >= ip_limit)) { + goto emit_remainder; + } + input_bytes = GetEightBytesAt(insert_tail); + uint32_t prev_hash = HashBytes(GetUint32AtOffset(input_bytes, 0), shift); + table[prev_hash] = ip - base_ip - 1; + uint32_t cur_hash = HashBytes(GetUint32AtOffset(input_bytes, 1), shift); + candidate = base_ip + table[cur_hash]; + candidate_bytes = UNALIGNED_LOAD32(candidate); + table[cur_hash] = ip - base_ip; + } while (GetUint32AtOffset(input_bytes, 1) == candidate_bytes); + + next_hash = HashBytes(GetUint32AtOffset(input_bytes, 2), shift); + ++ip; + } + } + +emit_remainder: + // Emit the remaining bytes as a literal + if (next_emit < ip_end) { + op = EmitLiteral(op, next_emit, ip_end - next_emit, false); + } + + return op; +} +} // end namespace internal + +// Signature of output types needed by decompression code. +// The decompression code is templatized on a type that obeys this +// signature so that we do not pay virtual function call overhead in +// the middle of a tight decompression loop. +// +// class DecompressionWriter { +// public: +// // Called before decompression +// void SetExpectedLength(size_t length); +// +// // Called after decompression +// bool CheckLength() const; +// +// // Called repeatedly during decompression +// bool Append(const char* ip, size_t length); +// bool AppendFromSelf(uint32_t offset, size_t length); +// +// // The rules for how TryFastAppend differs from Append are somewhat +// // convoluted: +// // +// // - TryFastAppend is allowed to decline (return false) at any +// // time, for any reason -- just "return false" would be +// // a perfectly legal implementation of TryFastAppend. +// // The intention is for TryFastAppend to allow a fast path +// // in the common case of a small append. +// // - TryFastAppend is allowed to read up to bytes +// // from the input buffer, whereas Append is allowed to read +// // . However, if it returns true, it must leave +// // at least five (kMaximumTagLength) bytes in the input buffer +// // afterwards, so that there is always enough space to read the +// // next tag without checking for a refill. +// // - TryFastAppend must always return decline (return false) +// // if is 61 or more, as in this case the literal length is not +// // decoded fully. In practice, this should not be a big problem, +// // as it is unlikely that one would implement a fast path accepting +// // this much data. +// // +// bool TryFastAppend(const char* ip, size_t available, size_t length); +// }; + +// ----------------------------------------------------------------------- +// Lookup table for decompression code. Generated by ComputeTable() below. +// ----------------------------------------------------------------------- + +// Mapping from i in range [0,4] to a mask to extract the bottom 8*i bits +static const uint32_t wordmask[] = { + 0u, 0xffu, 0xffffu, 0xffffffu, 0xffffffffu +}; + +// Data stored per entry in lookup table: +// Range Bits-used Description +// ------------------------------------ +// 1..64 0..7 Literal/copy length encoded in opcode byte +// 0..7 8..10 Copy offset encoded in opcode byte / 256 +// 0..4 11..13 Extra bytes after opcode +// +// We use eight bits for the length even though 7 would have sufficed +// because of efficiency reasons: +// (1) Extracting a byte is faster than a bit-field +// (2) It properly aligns copy offset so we do not need a <<8 +static const uint16_t char_table[256] = { + 0x0001, 0x0804, 0x1001, 0x2001, 0x0002, 0x0805, 0x1002, 0x2002, + 0x0003, 0x0806, 0x1003, 0x2003, 0x0004, 0x0807, 0x1004, 0x2004, + 0x0005, 0x0808, 0x1005, 0x2005, 0x0006, 0x0809, 0x1006, 0x2006, + 0x0007, 0x080a, 0x1007, 0x2007, 0x0008, 0x080b, 0x1008, 0x2008, + 0x0009, 0x0904, 0x1009, 0x2009, 0x000a, 0x0905, 0x100a, 0x200a, + 0x000b, 0x0906, 0x100b, 0x200b, 0x000c, 0x0907, 0x100c, 0x200c, + 0x000d, 0x0908, 0x100d, 0x200d, 0x000e, 0x0909, 0x100e, 0x200e, + 0x000f, 0x090a, 0x100f, 0x200f, 0x0010, 0x090b, 0x1010, 0x2010, + 0x0011, 0x0a04, 0x1011, 0x2011, 0x0012, 0x0a05, 0x1012, 0x2012, + 0x0013, 0x0a06, 0x1013, 0x2013, 0x0014, 0x0a07, 0x1014, 0x2014, + 0x0015, 0x0a08, 0x1015, 0x2015, 0x0016, 0x0a09, 0x1016, 0x2016, + 0x0017, 0x0a0a, 0x1017, 0x2017, 0x0018, 0x0a0b, 0x1018, 0x2018, + 0x0019, 0x0b04, 0x1019, 0x2019, 0x001a, 0x0b05, 0x101a, 0x201a, + 0x001b, 0x0b06, 0x101b, 0x201b, 0x001c, 0x0b07, 0x101c, 0x201c, + 0x001d, 0x0b08, 0x101d, 0x201d, 0x001e, 0x0b09, 0x101e, 0x201e, + 0x001f, 0x0b0a, 0x101f, 0x201f, 0x0020, 0x0b0b, 0x1020, 0x2020, + 0x0021, 0x0c04, 0x1021, 0x2021, 0x0022, 0x0c05, 0x1022, 0x2022, + 0x0023, 0x0c06, 0x1023, 0x2023, 0x0024, 0x0c07, 0x1024, 0x2024, + 0x0025, 0x0c08, 0x1025, 0x2025, 0x0026, 0x0c09, 0x1026, 0x2026, + 0x0027, 0x0c0a, 0x1027, 0x2027, 0x0028, 0x0c0b, 0x1028, 0x2028, + 0x0029, 0x0d04, 0x1029, 0x2029, 0x002a, 0x0d05, 0x102a, 0x202a, + 0x002b, 0x0d06, 0x102b, 0x202b, 0x002c, 0x0d07, 0x102c, 0x202c, + 0x002d, 0x0d08, 0x102d, 0x202d, 0x002e, 0x0d09, 0x102e, 0x202e, + 0x002f, 0x0d0a, 0x102f, 0x202f, 0x0030, 0x0d0b, 0x1030, 0x2030, + 0x0031, 0x0e04, 0x1031, 0x2031, 0x0032, 0x0e05, 0x1032, 0x2032, + 0x0033, 0x0e06, 0x1033, 0x2033, 0x0034, 0x0e07, 0x1034, 0x2034, + 0x0035, 0x0e08, 0x1035, 0x2035, 0x0036, 0x0e09, 0x1036, 0x2036, + 0x0037, 0x0e0a, 0x1037, 0x2037, 0x0038, 0x0e0b, 0x1038, 0x2038, + 0x0039, 0x0f04, 0x1039, 0x2039, 0x003a, 0x0f05, 0x103a, 0x203a, + 0x003b, 0x0f06, 0x103b, 0x203b, 0x003c, 0x0f07, 0x103c, 0x203c, + 0x0801, 0x0f08, 0x103d, 0x203d, 0x1001, 0x0f09, 0x103e, 0x203e, + 0x1801, 0x0f0a, 0x103f, 0x203f, 0x2001, 0x0f0b, 0x1040, 0x2040 +}; + +// In debug mode, allow optional computation of the table at startup. +// Also, check that the decompression table is correct. +#if 0 +DEFINE_bool(snappy_dump_decompression_table, false, + "If true, we print the decompression table at startup."); + +static uint16_t MakeEntry(unsigned int extra, + unsigned int len, + unsigned int copy_offset) { + // Check that all of the fields fit within the allocated space + assert(extra == (extra & 0x7)); // At most 3 bits + assert(copy_offset == (copy_offset & 0x7)); // At most 3 bits + assert(len == (len & 0x7f)); // At most 7 bits + return len | (copy_offset << 8) | (extra << 11); +} + +static void ALLOW_UNUSED ComputeTable() { + uint16_t dst[256]; + + // Place invalid entries in all places to detect missing initialization + int assigned = 0; + for (int i = 0; i < 256; i++) { + dst[i] = 0xffff; + } + + // Small LITERAL entries. We store (len-1) in the top 6 bits. + for (unsigned int len = 1; len <= 60; len++) { + dst[LITERAL | ((len-1) << 2)] = MakeEntry(0, len, 0); + assigned++; + } + + // Large LITERAL entries. We use 60..63 in the high 6 bits to + // encode the number of bytes of length info that follow the opcode. + for (unsigned int extra_bytes = 1; extra_bytes <= 4; extra_bytes++) { + // We set the length field in the lookup table to 1 because extra + // bytes encode len-1. + dst[LITERAL | ((extra_bytes+59) << 2)] = MakeEntry(extra_bytes, 1, 0); + assigned++; + } + + // COPY_1_BYTE_OFFSET. + // + // The tag byte in the compressed data stores len-4 in 3 bits, and + // offset/256 in 5 bits. offset%256 is stored in the next byte. + // + // This format is used for length in range [4..11] and offset in + // range [0..2047] + for (unsigned int len = 4; len < 12; len++) { + for (unsigned int offset = 0; offset < 2048; offset += 256) { + dst[COPY_1_BYTE_OFFSET | ((len-4)<<2) | ((offset>>8)<<5)] = + MakeEntry(1, len, offset>>8); + assigned++; + } + } + + // COPY_2_BYTE_OFFSET. + // Tag contains len-1 in top 6 bits, and offset in next two bytes. + for (unsigned int len = 1; len <= 64; len++) { + dst[COPY_2_BYTE_OFFSET | ((len-1)<<2)] = MakeEntry(2, len, 0); + assigned++; + } + + // COPY_4_BYTE_OFFSET. + // Tag contents len-1 in top 6 bits, and offset in next four bytes. + for (unsigned int len = 1; len <= 64; len++) { + dst[COPY_4_BYTE_OFFSET | ((len-1)<<2)] = MakeEntry(4, len, 0); + assigned++; + } + + // Check that each entry was initialized exactly once. + if (assigned != 256) { + fprintf(stderr, "ComputeTable: assigned only %d of 256\n", assigned); + abort(); + } + for (int i = 0; i < 256; i++) { + if (dst[i] == 0xffff) { + fprintf(stderr, "ComputeTable: did not assign byte %d\n", i); + abort(); + } + } + + if (FLAGS_snappy_dump_decompression_table) { + printf("static const uint16_t char_table[256] = {\n "); + for (int i = 0; i < 256; i++) { + printf("0x%04x%s", + dst[i], + ((i == 255) ? "\n" : (((i%8) == 7) ? ",\n " : ", "))); + } + printf("};\n"); + } + + // Check that computed table matched recorded table + for (int i = 0; i < 256; i++) { + if (dst[i] != char_table[i]) { + fprintf(stderr, "ComputeTable: byte %d: computed (%x), expect (%x)\n", + i, static_cast(dst[i]), static_cast(char_table[i])); + abort(); + } + } +} +#endif //disabled + +// Helper class for decompression +class SnappyDecompressor { +private: + Source* reader_; // Underlying source of bytes to decompress + const char* ip_; // Points to next buffered byte + const char* ip_limit_; // Points just past buffered bytes + uint32_t peeked_; // Bytes peeked from reader (need to skip) + bool eof_; // Hit end of input without an error? + char scratch_[kMaximumTagLength]; // See RefillTag(). + + // Ensure that all of the tag metadata for the next tag is available + // in [ip_..ip_limit_-1]. Also ensures that [ip,ip+4] is readable even + // if (ip_limit_ - ip_ < 5). + // + // Returns true on success, false on error or end of input. + bool RefillTag(); + +public: + explicit SnappyDecompressor(Source* reader) + : reader_(reader), + ip_(NULL), + ip_limit_(NULL), + peeked_(0), + eof_(false) { + } + + ~SnappyDecompressor() { + // Advance past any bytes we peeked at from the reader + reader_->Skip(peeked_); + } + + // Returns true iff we have hit the end of the input without an error. + bool eof() const { + return eof_; + } + + // Read the uncompressed length stored at the start of the compressed data. + // On succcess, stores the length in *result and returns true. + // On failure, returns false. + bool ReadUncompressedLength(uint32_t* result) { + assert(ip_ == NULL); // Must not have read anything yet + // Length is encoded in 1..5 bytes + *result = 0; + uint32_t shift = 0; + while (true) { + if (shift >= 32) return false; + size_t n; + const char* ip = reader_->Peek(&n); + if (n == 0) return false; + const unsigned char c = *(reinterpret_cast(ip)); + reader_->Skip(1); + *result |= static_cast(c & 0x7f) << shift; + if (c < 128) { + break; + } + shift += 7; + } + return true; + } + + // Process the next item found in the input. + // Returns true if successful, false on error or end of input. + template + void DecompressAllTags(Writer* writer) { + const char* ip = ip_; + + // We could have put this refill fragment only at the beginning of the loop. + // However, duplicating it at the end of each branch gives the compiler more + // scope to optimize the expression based on the local + // context, which overall increases speed. +#define MAYBE_REFILL() \ + if (ip_limit_ - ip < kMaximumTagLength) { \ + ip_ = ip; \ + if (!RefillTag()) return; \ + ip = ip_; \ + } + + MAYBE_REFILL(); + for ( ;; ) { + const unsigned char c = *(reinterpret_cast(ip++)); + + if ((c & 0x3) == LITERAL) { + size_t literal_length = (c >> 2) + 1u; + if (writer->TryFastAppend(ip, ip_limit_ - ip, literal_length)) { + assert(literal_length < 61); + ip += literal_length; + // NOTE(user): There is no MAYBE_REFILL() here, as TryFastAppend() + // will not return true unless there's already at least five spare + // bytes in addition to the literal. + continue; + } + if (BAIDU_UNLIKELY(literal_length >= 61)) { + // Long literal. + const size_t literal_length_length = literal_length - 60; + literal_length = + (LittleEndian::Load32(ip) & wordmask[literal_length_length]) + 1; + ip += literal_length_length; + } + + size_t avail = ip_limit_ - ip; + while (avail < literal_length) { + if (!writer->Append(ip, avail)) return; + literal_length -= avail; + reader_->Skip(peeked_); + size_t n; + ip = reader_->Peek(&n); + avail = n; + peeked_ = avail; + if (avail == 0) return; // Premature end of input + ip_limit_ = ip + avail; + } + if (!writer->Append(ip, literal_length)) { + return; + } + ip += literal_length; + MAYBE_REFILL(); + } else { + const uint32_t entry = char_table[c]; + const uint32_t trailer = LittleEndian::Load32(ip) & wordmask[entry >> 11]; + const uint32_t length = entry & 0xff; + ip += entry >> 11; + + // copy_offset/256 is encoded in bits 8..10. By just fetching + // those bits, we get copy_offset (since the bit-field starts at + // bit 8). + const uint32_t copy_offset = entry & 0x700; + if (!writer->AppendFromSelf(copy_offset + trailer, length)) { + return; + } + MAYBE_REFILL(); + } + } + +#undef MAYBE_REFILL + } +}; + +bool SnappyDecompressor::RefillTag() { + const char* ip = ip_; + if (ip == ip_limit_) { + // Fetch a new fragment from the reader + reader_->Skip(peeked_); // All peeked bytes are used up + size_t n; + ip = reader_->Peek(&n); + peeked_ = n; + if (n == 0) { + eof_ = true; + return false; + } + ip_limit_ = ip + n; + } + + // Read the tag character + assert(ip < ip_limit_); + const unsigned char c = *(reinterpret_cast(ip)); + const uint32_t entry = char_table[c]; + const uint32_t needed = (entry >> 11) + 1; // +1 byte for 'c' + assert(needed <= sizeof(scratch_)); + + // Read more bytes from reader if needed + uint32_t nbuf = ip_limit_ - ip; + if (nbuf < needed) { + // Stitch together bytes from ip and reader to form the word + // contents. We store the needed bytes in "scratch_". They + // will be consumed immediately by the caller since we do not + // read more than we need. + memmove(scratch_, ip, nbuf); + reader_->Skip(peeked_); // All peeked bytes are used up + peeked_ = 0; + while (nbuf < needed) { + size_t length; + const char* src = reader_->Peek(&length); + if (length == 0) return false; + uint32_t to_add = std::min(needed - nbuf, length); + memcpy(scratch_ + nbuf, src, to_add); + nbuf += to_add; + reader_->Skip(to_add); + } + assert(nbuf == needed); + ip_ = scratch_; + ip_limit_ = scratch_ + needed; + } else if (nbuf < kMaximumTagLength) { + // Have enough bytes, but move into scratch_ so that we do not + // read past end of input + memmove(scratch_, ip, nbuf); + reader_->Skip(peeked_); // All peeked bytes are used up + peeked_ = 0; + ip_ = scratch_; + ip_limit_ = scratch_ + nbuf; + } else { + // Pass pointer to buffer returned by reader_. + ip_ = ip; + } + return true; +} + +template +static bool InternalUncompress(Source* r, Writer* writer) { + // Read the uncompressed length from the front of the compressed input + SnappyDecompressor decompressor(r); + uint32_t uncompressed_len = 0; + if (!decompressor.ReadUncompressedLength(&uncompressed_len)) return false; + return InternalUncompressAllTags(&decompressor, writer, uncompressed_len); +} + +template +static bool InternalUncompressAllTags(SnappyDecompressor* decompressor, + Writer* writer, + uint32_t uncompressed_len) { + writer->SetExpectedLength(uncompressed_len); + + // Process the entire input + decompressor->DecompressAllTags(writer); + writer->Flush(); + return (decompressor->eof() && writer->CheckLength()); +} + +bool GetUncompressedLength(Source* source, uint32_t* result) { + SnappyDecompressor decompressor(source); + return decompressor.ReadUncompressedLength(result); +} + +size_t Compress(Source* reader, Sink* writer) { + size_t written = 0; + size_t N = reader->Available(); + char ulength[Varint::kMax32]; + char* p = Varint::Encode32(ulength, N); + writer->Append(ulength, p-ulength); + written += (p - ulength); + + internal::WorkingMemory wmem; + char* scratch = NULL; + char* scratch_output = NULL; + + while (N > 0) { + // Get next block to compress (without copying if possible) + size_t fragment_size; + const char* fragment = reader->Peek(&fragment_size); + assert(fragment_size != 0); // premature end of input + const size_t num_to_read = std::min(N, kBlockSize); + size_t bytes_read = fragment_size; + + size_t pending_advance = 0; + if (bytes_read >= num_to_read) { + // Buffer returned by reader is large enough + pending_advance = num_to_read; + fragment_size = num_to_read; + } else { + // Read into scratch buffer + if (scratch == NULL) { + // If this is the last iteration, we want to allocate N bytes + // of space, otherwise the max possible kBlockSize space. + // num_to_read contains exactly the correct value + scratch = new char[num_to_read]; + } + memcpy(scratch, fragment, bytes_read); + reader->Skip(bytes_read); + + while (bytes_read < num_to_read) { + fragment = reader->Peek(&fragment_size); + size_t n = std::min(fragment_size, num_to_read - bytes_read); + memcpy(scratch + bytes_read, fragment, n); + bytes_read += n; + reader->Skip(n); + } + assert(bytes_read == num_to_read); + fragment = scratch; + fragment_size = num_to_read; + } + assert(fragment_size == num_to_read); + + // Get encoding table for compression + int table_size; + uint16_t* table = wmem.GetHashTable(num_to_read, &table_size); + + // Compress input_fragment and append to dest + const int max_output = MaxCompressedLength(num_to_read); + + // Need a scratch buffer for the output, in case the byte sink doesn't + // have room for us directly. + if (scratch_output == NULL) { + scratch_output = new char[max_output]; + } else { + // Since we encode kBlockSize regions followed by a region + // which is <= kBlockSize in length, a previously allocated + // scratch_output[] region is big enough for this iteration. + } + char* dest = writer->GetAppendBuffer(max_output, scratch_output); + char* end = internal::CompressFragment(fragment, fragment_size, + dest, table, table_size); + writer->Append(dest, end - dest); + written += (end - dest); + + N -= num_to_read; + reader->Skip(pending_advance); + } + + delete[] scratch; + delete[] scratch_output; + + return written; +} + +// ----------------------------------------------------------------------- +// IOVec interfaces +// ----------------------------------------------------------------------- + +// A type that writes to an iovec. +// Note that this is not a "ByteSink", but a type that matches the +// Writer template argument to SnappyDecompressor::DecompressAllTags(). +class SnappyIOVecWriter { +private: + const struct iovec* output_iov_; + const size_t output_iov_count_; + + // We are currently writing into output_iov_[curr_iov_index_]. + int curr_iov_index_; + + // Bytes written to output_iov_[curr_iov_index_] so far. + size_t curr_iov_written_; + + // Total bytes decompressed into output_iov_ so far. + size_t total_written_; + + // Maximum number of bytes that will be decompressed into output_iov_. + size_t output_limit_; + + inline char* GetIOVecPointer(int index, size_t offset) { + return reinterpret_cast(output_iov_[index].iov_base) + + offset; + } + +public: + // Does not take ownership of iov. iov must be valid during the + // entire lifetime of the SnappyIOVecWriter. + inline SnappyIOVecWriter(const struct iovec* iov, size_t iov_count) + : output_iov_(iov), + output_iov_count_(iov_count), + curr_iov_index_(0), + curr_iov_written_(0), + total_written_(0), + output_limit_((size_t)-1) { + } + + inline void SetExpectedLength(size_t len) { + output_limit_ = len; + } + + inline bool CheckLength() const { + return total_written_ == output_limit_; + } + + inline bool Append(const char* ip, size_t len) { + if (total_written_ + len > output_limit_) { + return false; + } + + while (len > 0) { + assert(curr_iov_written_ <= output_iov_[curr_iov_index_].iov_len); + if (curr_iov_written_ >= output_iov_[curr_iov_index_].iov_len) { + // This iovec is full. Go to the next one. + if ((size_t)(curr_iov_index_ + 1) >= output_iov_count_) { + return false; + } + curr_iov_written_ = 0; + ++curr_iov_index_; + } + + const size_t to_write = std::min( + len, output_iov_[curr_iov_index_].iov_len - curr_iov_written_); + memcpy(GetIOVecPointer(curr_iov_index_, curr_iov_written_), + ip, + to_write); + curr_iov_written_ += to_write; + total_written_ += to_write; + ip += to_write; + len -= to_write; + } + + return true; + } + + inline bool TryFastAppend(const char* ip, size_t available, size_t len) { + const size_t space_left = output_limit_ - total_written_; + if (len <= 16 && available >= 16 + kMaximumTagLength && space_left >= 16 && + output_iov_[curr_iov_index_].iov_len - curr_iov_written_ >= 16) { + // Fast path, used for the majority (about 95%) of invocations. + char* ptr = GetIOVecPointer(curr_iov_index_, curr_iov_written_); + UnalignedCopy64(ip, ptr); + UnalignedCopy64(ip + 8, ptr + 8); + curr_iov_written_ += len; + total_written_ += len; + return true; + } + + return false; + } + + inline bool AppendFromSelf(size_t offset, size_t len) { + if (offset > total_written_ || offset == 0) { + return false; + } + const size_t space_left = output_limit_ - total_written_; + if (len > space_left) { + return false; + } + + // Locate the iovec from which we need to start the copy. + int from_iov_index = curr_iov_index_; + size_t from_iov_offset = curr_iov_written_; + while (offset > 0) { + if (from_iov_offset >= offset) { + from_iov_offset -= offset; + break; + } + + offset -= from_iov_offset; + --from_iov_index; + assert(from_iov_index >= 0); + from_iov_offset = output_iov_[from_iov_index].iov_len; + } + + // Copy bytes starting from the iovec pointed to by from_iov_index to + // the current iovec. + while (len > 0) { + assert(from_iov_index <= curr_iov_index_); + if (from_iov_index != curr_iov_index_) { + const size_t to_copy = std::min( + output_iov_[from_iov_index].iov_len - from_iov_offset, + len); + Append(GetIOVecPointer(from_iov_index, from_iov_offset), to_copy); + len -= to_copy; + if (len > 0) { + ++from_iov_index; + from_iov_offset = 0; + } + } else { + assert(curr_iov_written_ <= output_iov_[curr_iov_index_].iov_len); + size_t to_copy = std::min(output_iov_[curr_iov_index_].iov_len - + curr_iov_written_, + len); + if (to_copy == 0) { + // This iovec is full. Go to the next one. + if ((size_t)(curr_iov_index_ + 1) >= output_iov_count_) { + return false; + } + ++curr_iov_index_; + curr_iov_written_ = 0; + continue; + } + if (to_copy > len) { + to_copy = len; + } + IncrementalCopy(GetIOVecPointer(from_iov_index, from_iov_offset), + GetIOVecPointer(curr_iov_index_, curr_iov_written_), + to_copy); + curr_iov_written_ += to_copy; + from_iov_offset += to_copy; + total_written_ += to_copy; + len -= to_copy; + } + } + + return true; + } + + inline void Flush() {} +}; + +bool RawUncompressToIOVec(const char* compressed, size_t compressed_length, + const struct iovec* iov, size_t iov_cnt) { + ByteArraySource reader(compressed, compressed_length); + return RawUncompressToIOVec(&reader, iov, iov_cnt); +} + +bool RawUncompressToIOVec(Source* compressed, const struct iovec* iov, + size_t iov_cnt) { + SnappyIOVecWriter output(iov, iov_cnt); + return InternalUncompress(compressed, &output); +} + +// ----------------------------------------------------------------------- +// Flat array interfaces +// ----------------------------------------------------------------------- + +// A type that writes to a flat array. +// Note that this is not a "ByteSink", but a type that matches the +// Writer template argument to SnappyDecompressor::DecompressAllTags(). +class SnappyArrayWriter { +private: + char* base_; + char* op_; + char* op_limit_; + +public: + inline explicit SnappyArrayWriter(char* dst) + : base_(dst), + op_(dst), + op_limit_(dst) { + } + + inline void SetExpectedLength(size_t len) { + op_limit_ = op_ + len; + } + + inline bool CheckLength() const { + return op_ == op_limit_; + } + + inline bool Append(const char* ip, size_t len) { + char* op = op_; + const size_t space_left = op_limit_ - op; + if (space_left < len) { + return false; + } + memcpy(op, ip, len); + op_ = op + len; + return true; + } + + inline bool TryFastAppend(const char* ip, size_t available, size_t len) { + char* op = op_; + const size_t space_left = op_limit_ - op; + if (len <= 16 && available >= 16 + kMaximumTagLength && space_left >= 16) { + // Fast path, used for the majority (about 95%) of invocations. + UnalignedCopy64(ip, op); + UnalignedCopy64(ip + 8, op + 8); + op_ = op + len; + return true; + } else { + return false; + } + } + + inline bool AppendFromSelf(size_t offset, size_t len) { + char* op = op_; + const size_t space_left = op_limit_ - op; + + // Check if we try to append from before the start of the buffer. + // Normally this would just be a check for "produced < offset", + // but "produced <= offset - 1u" is equivalent for every case + // except the one where offset==0, where the right side will wrap around + // to a very big number. This is convenient, as offset==0 is another + // invalid case that we also want to catch, so that we do not go + // into an infinite loop. + assert(op >= base_); + size_t produced = op - base_; + if (produced <= offset - 1u) { + return false; + } + if (len <= 16 && offset >= 8 && space_left >= 16) { + // Fast path, used for the majority (70-80%) of dynamic invocations. + UnalignedCopy64(op - offset, op); + UnalignedCopy64(op - offset + 8, op + 8); + } else { + if (space_left >= len + kMaxIncrementCopyOverflow) { + IncrementalCopyFastPath(op - offset, op, len); + } else { + if (space_left < len) { + return false; + } + IncrementalCopy(op - offset, op, len); + } + } + + op_ = op + len; + return true; + } + inline size_t Produced() const { + return op_ - base_; + } + inline void Flush() {} +}; + +bool RawUncompress(const char* compressed, size_t n, char* uncompressed) { + ByteArraySource reader(compressed, n); + return RawUncompress(&reader, uncompressed); +} + +bool RawUncompress(Source* compressed, char* uncompressed) { + SnappyArrayWriter output(uncompressed); + return InternalUncompress(compressed, &output); +} + +bool Uncompress(const char* compressed, size_t n, std::string* uncompressed) { + size_t ulength; + if (!GetUncompressedLength(compressed, n, &ulength)) { + return false; + } + // On 32-bit builds: max_size() < kuint32max. Check for that instead + // of crashing (e.g., consider externally specified compressed data). + if (ulength > uncompressed->max_size()) { + return false; + } + STLStringResizeUninitialized(uncompressed, ulength); + return RawUncompress(compressed, n, string_as_array(uncompressed)); +} + +// A Writer that drops everything on the floor and just does validation +class SnappyDecompressionValidator { +private: + size_t expected_; + size_t produced_; + +public: + inline SnappyDecompressionValidator() : expected_(0), produced_(0) { } + inline void SetExpectedLength(size_t len) { + expected_ = len; + } + inline bool CheckLength() const { + return expected_ == produced_; + } + inline bool Append(const char* ip, size_t len) { + produced_ += len; + return produced_ <= expected_; + } + inline bool TryFastAppend(const char* ip, size_t available, size_t length) { + return false; + } + inline bool AppendFromSelf(size_t offset, size_t len) { + // See SnappyArrayWriter::AppendFromSelf for an explanation of + // the "offset - 1u" trick. + if (produced_ <= offset - 1u) return false; + produced_ += len; + return produced_ <= expected_; + } + inline void Flush() {} +}; + +bool IsValidCompressedBuffer(const char* compressed, size_t n) { + ByteArraySource reader(compressed, n); + SnappyDecompressionValidator writer; + return InternalUncompress(&reader, &writer); +} + +bool IsValidCompressed(Source* compressed) { + SnappyDecompressionValidator writer; + return InternalUncompress(compressed, &writer); +} + +void RawCompress(const char* input, + size_t input_length, + char* compressed, + size_t* compressed_length) { + ByteArraySource reader(input, input_length); + UncheckedByteArraySink writer(compressed); + Compress(&reader, &writer); + + // Compute how many bytes were added + *compressed_length = (writer.CurrentDestination() - compressed); +} + +size_t Compress(const char* input, size_t input_length, std::string* compressed) { + // Pre-grow the buffer to the max length of the compressed output + compressed->resize(MaxCompressedLength(input_length)); + + size_t compressed_length; + RawCompress(input, input_length, string_as_array(compressed), + &compressed_length); + compressed->resize(compressed_length); + return compressed_length; +} + +// ----------------------------------------------------------------------- +// Sink interface +// ----------------------------------------------------------------------- + +// A type that decompresses into a Sink. The template parameter +// Allocator must export one method "char* Allocate(int size);", which +// allocates a buffer of "size" and appends that to the destination. +template +class SnappyScatteredWriter { + Allocator allocator_; + + // We need random access into the data generated so far. Therefore + // we keep track of all of the generated data as an array of blocks. + // All of the blocks except the last have length kBlockSize. + std::vector blocks_; + size_t expected_; + + // Total size of all fully generated blocks so far + size_t full_size_; + + // Pointer into current output block + char* op_base_; // Base of output block + char* op_ptr_; // Pointer to next unfilled byte in block + char* op_limit_; // Pointer just past block + + inline size_t Size() const { + return full_size_ + (op_ptr_ - op_base_); + } + + bool SlowAppend(const char* ip, size_t len); + bool SlowAppendFromSelf(size_t offset, size_t len); + +public: + inline explicit SnappyScatteredWriter(const Allocator& allocator) + : allocator_(allocator), + full_size_(0), + op_base_(NULL), + op_ptr_(NULL), + op_limit_(NULL) { + } + + inline void SetExpectedLength(size_t len) { + assert(blocks_.empty()); + expected_ = len; + } + + inline bool CheckLength() const { + return Size() == expected_; + } + + // Return the number of bytes actually uncompressed so far + inline size_t Produced() const { + return Size(); + } + + inline bool Append(const char* ip, size_t len) { + size_t avail = op_limit_ - op_ptr_; + if (len <= avail) { + // Fast path + memcpy(op_ptr_, ip, len); + op_ptr_ += len; + return true; + } else { + return SlowAppend(ip, len); + } + } + + inline bool TryFastAppend(const char* ip, size_t available, size_t length) { + char* op = op_ptr_; + const int space_left = op_limit_ - op; + if (length <= 16 && available >= 16 + kMaximumTagLength && + space_left >= 16) { + // Fast path, used for the majority (about 95%) of invocations. + UNALIGNED_STORE64(op, UNALIGNED_LOAD64(ip)); + UNALIGNED_STORE64(op + 8, UNALIGNED_LOAD64(ip + 8)); + op_ptr_ = op + length; + return true; + } else { + return false; + } + } + + inline bool AppendFromSelf(size_t offset, size_t len) { + // See SnappyArrayWriter::AppendFromSelf for an explanation of + // the "offset - 1u" trick. + if (offset - 1u < (size_t)(op_ptr_ - op_base_)) { + // ^ Fix warning in gcc 3.4 + const size_t space_left = op_limit_ - op_ptr_; + if (space_left >= len + kMaxIncrementCopyOverflow) { + // Fast path: src and dst in current block. + IncrementalCopyFastPath(op_ptr_ - offset, op_ptr_, len); + op_ptr_ += len; + return true; + } + } + return SlowAppendFromSelf(offset, len); + } + + // Called at the end of the decompress. We ask the allocator + // write all blocks to the sink. + inline void Flush() { allocator_.Flush(Produced()); } +}; + +template +bool SnappyScatteredWriter::SlowAppend(const char* ip, size_t len) { + size_t avail = op_limit_ - op_ptr_; + while (len > avail) { + // Completely fill this block + memcpy(op_ptr_, ip, avail); + op_ptr_ += avail; + assert(op_limit_ - op_ptr_ == 0); + full_size_ += (op_ptr_ - op_base_); + len -= avail; + ip += avail; + + // Bounds check + if (full_size_ + len > expected_) { + return false; + } + + // Make new block + size_t bsize = std::min(kBlockSize, expected_ - full_size_); + op_base_ = allocator_.Allocate(bsize); + op_ptr_ = op_base_; + op_limit_ = op_base_ + bsize; + blocks_.push_back(op_base_); + avail = bsize; + } + + memcpy(op_ptr_, ip, len); + op_ptr_ += len; + return true; +} + +template +bool SnappyScatteredWriter::SlowAppendFromSelf(size_t offset, + size_t len) { + // Overflow check + // See SnappyArrayWriter::AppendFromSelf for an explanation of + // the "offset - 1u" trick. + const size_t cur = Size(); + if (offset - 1u >= cur) return false; + if (expected_ - cur < len) return false; + + // Currently we shouldn't ever hit this path because Compress() chops the + // input into blocks and does not create cross-block copies. However, it is + // nice if we do not rely on that, since we can get better compression if we + // allow cross-block copies and thus might want to change the compressor in + // the future. + size_t src = cur - offset; + while (len-- > 0) { + char c = blocks_[src >> kBlockLog][src & (kBlockSize-1)]; + Append(&c, 1); + src++; + } + return true; +} + +class SnappySinkAllocator { +public: + explicit SnappySinkAllocator(Sink* dest): dest_(dest) {} + ~SnappySinkAllocator() {} + + char* Allocate(int size) { + Datablock block(new char[size], size); + blocks_.push_back(block); + return block.data; + } + + // We flush only at the end, because the writer wants + // random access to the blocks and once we hand the + // block over to the sink, we can't access it anymore. + // Also we don't write more than has been actually written + // to the blocks. + void Flush(size_t size) { + size_t size_written = 0; + size_t block_size; + for (size_t i = 0; i < blocks_.size(); ++i) { + block_size = std::min(blocks_[i].size, size - size_written); + dest_->AppendAndTakeOwnership(blocks_[i].data, block_size, + &SnappySinkAllocator::Deleter, NULL); + size_written += block_size; + } + blocks_.clear(); + } + +private: + struct Datablock { + char* data; + size_t size; + Datablock(char* p, size_t s) : data(p), size(s) {} + }; + + static void Deleter(void* arg, const char* bytes, size_t size) { + delete[] bytes; + } + + Sink* dest_; + std::vector blocks_; + + // Note: copying this object is allowed +}; + +size_t UncompressAsMuchAsPossible(Source* compressed, Sink* uncompressed) { + SnappySinkAllocator allocator(uncompressed); + SnappyScatteredWriter writer(allocator); + InternalUncompress(compressed, &writer); + return writer.Produced(); +} + +bool Uncompress(Source* compressed, Sink* uncompressed) { + // Read the uncompressed length from the front of the compressed input + SnappyDecompressor decompressor(compressed); + uint32_t uncompressed_len = 0; + if (!decompressor.ReadUncompressedLength(&uncompressed_len)) { + return false; + } + + char c; + size_t allocated_size; + char* buf = uncompressed->GetAppendBufferVariable( + 1, uncompressed_len, &c, 1, &allocated_size); + + // If we can get a flat buffer, then use it, otherwise do block by block + // uncompression + if (allocated_size >= uncompressed_len) { + SnappyArrayWriter writer(buf); + bool result = InternalUncompressAllTags( + &decompressor, &writer, uncompressed_len); + uncompressed->Append(buf, writer.Produced()); + return result; + } else { + SnappySinkAllocator allocator(uncompressed); + SnappyScatteredWriter writer(allocator); + return InternalUncompressAllTags(&decompressor, &writer, uncompressed_len); + } +} + +} // end namespace snappy +} // end namespace butil diff --git a/src/butil/third_party/snappy/snappy.h b/src/butil/third_party/snappy/snappy.h new file mode 100644 index 0000000..a096030 --- /dev/null +++ b/src/butil/third_party/snappy/snappy.h @@ -0,0 +1,210 @@ +// Copyright 2005 and onwards Google Inc. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// A light-weight compression algorithm. It is designed for speed of +// compression and decompression, rather than for the utmost in space +// savings. +// +// For getting better compression ratios when you are compressing data +// with long repeated sequences or compressing data that is similar to +// other data, while still compressing fast, you might look at first +// using BMDiff and then compressing the output of BMDiff with +// Snappy. + +#ifndef BUTIL_THIRD_PARTY_SNAPPY_SNAPPY_H__ +#define BUTIL_THIRD_PARTY_SNAPPY_SNAPPY_H__ + +#include "butil/basictypes.h" +#include + +namespace butil { +namespace snappy { +class Source; +class Sink; + +// Windows does not have an iovec type, yet the concept is universally useful. +// It is simple to define it ourselves, so we put it inside our own namespace. +struct iovec { + void* iov_base; + size_t iov_len; +}; + +// ------------------------------------------------------------------------ +// Generic compression/decompression routines. +// ------------------------------------------------------------------------ + +// Compress the bytes read from "*source" and append to "*sink". Return the +// number of bytes written. +size_t Compress(Source* source, Sink* sink); + +// Find the uncompressed length of the given stream, as given by the header. +// Note that the true length could deviate from this; the stream could e.g. +// be truncated. +// +// Also note that this leaves "*source" in a state that is unsuitable for +// further operations, such as RawUncompress(). You will need to rewind +// or recreate the source yourself before attempting any further calls. +bool GetUncompressedLength(Source* source, uint32_t* result); + +// ------------------------------------------------------------------------ +// Higher-level string based routines (should be sufficient for most users) +// ------------------------------------------------------------------------ + +// Sets "*output" to the compressed version of "input[0,input_length-1]". +// Original contents of *output are lost. +// +// REQUIRES: "input[]" is not an alias of "*output". +size_t Compress(const char* input, size_t input_length, std::string* output); + +// Decompresses "compressed[0,compressed_length-1]" to "*uncompressed". +// Original contents of "*uncompressed" are lost. +// +// REQUIRES: "compressed[]" is not an alias of "*uncompressed". +// +// returns false if the message is corrupted and could not be decompressed +bool Uncompress(const char* compressed, size_t compressed_length, + std::string* uncompressed); + +// Decompresses "compressed" to "*uncompressed". +// +// returns false if the message is corrupted and could not be decompressed +bool Uncompress(Source* compressed, Sink* uncompressed); + +// This routine uncompresses as much of the "compressed" as possible +// into sink. It returns the number of valid bytes added to sink +// (extra invalid bytes may have been added due to errors; the caller +// should ignore those). The emitted data typically has length +// GetUncompressedLength(), but may be shorter if an error is +// encountered. +size_t UncompressAsMuchAsPossible(Source* compressed, Sink* uncompressed); + +// ------------------------------------------------------------------------ +// Lower-level character array based routines. May be useful for +// efficiency reasons in certain circumstances. +// ------------------------------------------------------------------------ + +// REQUIRES: "compressed" must point to an area of memory that is at +// least "MaxCompressedLength(input_length)" bytes in length. +// +// Takes the data stored in "input[0..input_length]" and stores +// it in the array pointed to by "compressed". +// +// "*compressed_length" is set to the length of the compressed output. +// +// Example: +// char* output = new char[snappy::MaxCompressedLength(input_length)]; +// size_t output_length; +// RawCompress(input, input_length, output, &output_length); +// ... Process(output, output_length) ... +// delete [] output; +void RawCompress(const char* input, + size_t input_length, + char* compressed, + size_t* compressed_length); + +// Given data in "compressed[0..compressed_length-1]" generated by +// calling the Snappy::Compress routine, this routine +// stores the uncompressed data to +// uncompressed[0..GetUncompressedLength(compressed)-1] +// returns false if the message is corrupted and could not be decrypted +bool RawUncompress(const char* compressed, size_t compressed_length, + char* uncompressed); + +// Given data from the byte source 'compressed' generated by calling +// the Snappy::Compress routine, this routine stores the uncompressed +// data to +// uncompressed[0..GetUncompressedLength(compressed,compressed_length)-1] +// returns false if the message is corrupted and could not be decrypted +bool RawUncompress(Source* compressed, char* uncompressed); + +// Given data in "compressed[0..compressed_length-1]" generated by +// calling the Snappy::Compress routine, this routine +// stores the uncompressed data to the iovec "iov". The number of physical +// buffers in "iov" is given by iov_cnt and their cumulative size +// must be at least GetUncompressedLength(compressed). The individual buffers +// in "iov" must not overlap with each other. +// +// returns false if the message is corrupted and could not be decrypted +bool RawUncompressToIOVec(const char* compressed, size_t compressed_length, + const struct iovec* iov, size_t iov_cnt); + +// Given data from the byte source 'compressed' generated by calling +// the Snappy::Compress routine, this routine stores the uncompressed +// data to the iovec "iov". The number of physical +// buffers in "iov" is given by iov_cnt and their cumulative size +// must be at least GetUncompressedLength(compressed). The individual buffers +// in "iov" must not overlap with each other. +// +// returns false if the message is corrupted and could not be decrypted +bool RawUncompressToIOVec(Source* compressed, const struct iovec* iov, + size_t iov_cnt); + +// Returns the maximal size of the compressed representation of +// input data that is "source_bytes" bytes in length; +size_t MaxCompressedLength(size_t source_bytes); + +// REQUIRES: "compressed[]" was produced by RawCompress() or Compress() +// Returns true and stores the length of the uncompressed data in +// *result normally. Returns false on parsing error. +// This operation takes O(1) time. +bool GetUncompressedLength(const char* compressed, size_t compressed_length, + size_t* result); + +// Returns true iff the contents of "compressed[]" can be uncompressed +// successfully. Does not return the uncompressed data. Takes +// time proportional to compressed_length, but is usually at least +// a factor of four faster than actual decompression. +bool IsValidCompressedBuffer(const char* compressed, + size_t compressed_length); + +// Returns true iff the contents of "compressed" can be uncompressed +// successfully. Does not return the uncompressed data. Takes +// time proportional to *compressed length, but is usually at least +// a factor of four faster than actual decompression. +// On success, consumes all of *compressed. On failure, consumes an +// unspecified prefix of *compressed. +bool IsValidCompressed(Source* compressed); + +// The size of a compression block. Note that many parts of the compression +// code assumes that kBlockSize <= 65536; in particular, the hash table +// can only store 16-bit offsets, and EmitCopy() also assumes the offset +// is 65535 bytes or less. Note also that if you change this, it will +// affect the framing format (see framing_format.txt). +// +// Note that there might be older data around that is compressed with larger +// block sizes, so the decompression code should not rely on the +// non-existence of long backreferences. +static const int kBlockLog = 16; +static const size_t kBlockSize = 1 << kBlockLog; + +static const int kMaxHashTableBits = 14; +static const size_t kMaxHashTableSize = 1 << kMaxHashTableBits; +} // end namespace snappy +} // end namespace butil + +#endif // BUTIL_THIRD_PARTY_SNAPPY_SNAPPY_H__ diff --git a/src/butil/third_party/superfasthash/README.chromium b/src/butil/third_party/superfasthash/README.chromium new file mode 100644 index 0000000..d41ed77 --- /dev/null +++ b/src/butil/third_party/superfasthash/README.chromium @@ -0,0 +1,29 @@ +Name: Paul Hsieh's SuperFastHash +Short Name: SuperFastHash +URL: http://www.azillionmonkeys.com/qed/hash.html +Version: 0 +Date: 2012-02-21 +License: BSD +License File: LICENSE +Security Critical: yes + +Description: +A fast string hashing algorithm. + +Local Modifications: +- Added LICENSE. +- Added license text as a comment to the top of superfasthash.c. +- #include instead of "pstdint.h". +- #include . + +The license is a standard 3-clause BSD license with the following minor changes: + +"nor the names of its contributors may be used" +is replaced with: +"nor the names of any other contributors to the code use may not be used" + +and + +"IN NO EVENT SHALL BE LIABLE" +is replaced with: +"IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE" diff --git a/src/butil/third_party/superfasthash/superfasthash.c b/src/butil/third_party/superfasthash/superfasthash.c new file mode 100644 index 0000000..6e7687e --- /dev/null +++ b/src/butil/third_party/superfasthash/superfasthash.c @@ -0,0 +1,84 @@ +// Copyright (c) 2010, Paul Hsieh +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither my name, Paul Hsieh, nor the names of any other contributors to the +// code use may not be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif + +uint32_t SuperFastHash (const char * data, int len) { +uint32_t hash = len, tmp; +int rem; + + if (len <= 0 || data == NULL) return 0; + + rem = len & 3; + len >>= 2; + + /* Main loop */ + for (;len > 0; len--) { + hash += get16bits (data); + tmp = (get16bits (data+2) << 11) ^ hash; + hash = (hash << 16) ^ tmp; + data += 2*sizeof (uint16_t); + hash += hash >> 11; + } + + /* Handle end cases */ + switch (rem) { + case 3: hash += get16bits (data); + hash ^= hash << 16; + hash ^= ((signed char)data[sizeof (uint16_t)]) << 18; + hash += hash >> 11; + break; + case 2: hash += get16bits (data); + hash ^= hash << 11; + hash += hash >> 17; + break; + case 1: hash += (signed char)*data; + hash ^= hash << 10; + hash += hash >> 1; + } + + /* Force "avalanching" of final 127 bits */ + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + + return hash; +} diff --git a/src/butil/third_party/symbolize/README.chromium b/src/butil/third_party/symbolize/README.chromium new file mode 100644 index 0000000..de92794 --- /dev/null +++ b/src/butil/third_party/symbolize/README.chromium @@ -0,0 +1,18 @@ +Name: google-glog's symbolization library +URL: http://code.google.com/p/google-glog/ +License: BSD + +The following files are copied AS-IS from: +http://code.google.com/p/google-glog/source/browse/#svn/trunk/src (r141) + +- demangle.cc +- demangle.h +- symbolize.cc +- symbolize.h + +The following files are minimal stubs created for use in Chromium: + +- config.h +- glog/logging.h +- glog/raw_logging.h +- utilities.h diff --git a/src/butil/third_party/symbolize/config.h b/src/butil/third_party/symbolize/config.h new file mode 100644 index 0000000..945f5a6 --- /dev/null +++ b/src/butil/third_party/symbolize/config.h @@ -0,0 +1,7 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#define GOOGLE_NAMESPACE google +#define _END_GOOGLE_NAMESPACE_ } +#define _START_GOOGLE_NAMESPACE_ namespace google { diff --git a/src/butil/third_party/symbolize/demangle.cc b/src/butil/third_party/symbolize/demangle.cc new file mode 100644 index 0000000..e858181 --- /dev/null +++ b/src/butil/third_party/symbolize/demangle.cc @@ -0,0 +1,1304 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Satoru Takabayashi +// +// For reference check out: +// http://www.codesourcery.com/public/cxx-abi/abi.html#mangling +// +// Note that we only have partial C++0x support yet. + +#include // for NULL +#include "demangle.h" + +_START_GOOGLE_NAMESPACE_ + +typedef struct { + const char *abbrev; + const char *real_name; +} AbbrevPair; + +// List of operators from Itanium C++ ABI. +static const AbbrevPair kOperatorList[] = { + { "nw", "new" }, + { "na", "new[]" }, + { "dl", "delete" }, + { "da", "delete[]" }, + { "ps", "+" }, + { "ng", "-" }, + { "ad", "&" }, + { "de", "*" }, + { "co", "~" }, + { "pl", "+" }, + { "mi", "-" }, + { "ml", "*" }, + { "dv", "/" }, + { "rm", "%" }, + { "an", "&" }, + { "or", "|" }, + { "eo", "^" }, + { "aS", "=" }, + { "pL", "+=" }, + { "mI", "-=" }, + { "mL", "*=" }, + { "dV", "/=" }, + { "rM", "%=" }, + { "aN", "&=" }, + { "oR", "|=" }, + { "eO", "^=" }, + { "ls", "<<" }, + { "rs", ">>" }, + { "lS", "<<=" }, + { "rS", ">>=" }, + { "eq", "==" }, + { "ne", "!=" }, + { "lt", "<" }, + { "gt", ">" }, + { "le", "<=" }, + { "ge", ">=" }, + { "nt", "!" }, + { "aa", "&&" }, + { "oo", "||" }, + { "pp", "++" }, + { "mm", "--" }, + { "cm", "," }, + { "pm", "->*" }, + { "pt", "->" }, + { "cl", "()" }, + { "ix", "[]" }, + { "qu", "?" }, + { "st", "sizeof" }, + { "sz", "sizeof" }, + { NULL, NULL }, +}; + +// List of builtin types from Itanium C++ ABI. +static const AbbrevPair kBuiltinTypeList[] = { + { "v", "void" }, + { "w", "wchar_t" }, + { "b", "bool" }, + { "c", "char" }, + { "a", "signed char" }, + { "h", "unsigned char" }, + { "s", "short" }, + { "t", "unsigned short" }, + { "i", "int" }, + { "j", "unsigned int" }, + { "l", "long" }, + { "m", "unsigned long" }, + { "x", "long long" }, + { "y", "unsigned long long" }, + { "n", "__int128" }, + { "o", "unsigned __int128" }, + { "f", "float" }, + { "d", "double" }, + { "e", "long double" }, + { "g", "__float128" }, + { "z", "ellipsis" }, + { NULL, NULL } +}; + +// List of substitutions Itanium C++ ABI. +static const AbbrevPair kSubstitutionList[] = { + { "St", "" }, + { "Sa", "allocator" }, + { "Sb", "basic_string" }, + // std::basic_string,std::allocator > + { "Ss", "string"}, + // std::basic_istream > + { "Si", "istream" }, + // std::basic_ostream > + { "So", "ostream" }, + // std::basic_iostream > + { "Sd", "iostream" }, + { NULL, NULL } +}; + +// State needed for demangling. +typedef struct { + const char *mangled_cur; // Cursor of mangled name. + char *out_cur; // Cursor of output string. + const char *out_begin; // Beginning of output string. + const char *out_end; // End of output string. + const char *prev_name; // For constructors/destructors. + int prev_name_length; // For constructors/destructors. + short nest_level; // For nested names. + bool append; // Append flag. + bool overflowed; // True if output gets overflowed. +} State; + +// We don't use strlen() in libc since it's not guaranteed to be async +// signal safe. +static size_t StrLen(const char *str) { + size_t len = 0; + while (*str != '\0') { + ++str; + ++len; + } + return len; +} + +// Returns true if "str" has at least "n" characters remaining. +static bool AtLeastNumCharsRemaining(const char *str, int n) { + for (int i = 0; i < n; ++i) { + if (str[i] == '\0') { + return false; + } + } + return true; +} + +// Returns true if "str" has "prefix" as a prefix. +static bool StrPrefix(const char *str, const char *prefix) { + size_t i = 0; + while (str[i] != '\0' && prefix[i] != '\0' && + str[i] == prefix[i]) { + ++i; + } + return prefix[i] == '\0'; // Consumed everything in "prefix". +} + +static void InitState(State *state, const char *mangled, + char *out, int out_size) { + state->mangled_cur = mangled; + state->out_cur = out; + state->out_begin = out; + state->out_end = out + out_size; + state->prev_name = NULL; + state->prev_name_length = -1; + state->nest_level = -1; + state->append = true; + state->overflowed = false; +} + +// Returns true and advances "mangled_cur" if we find "one_char_token" +// at "mangled_cur" position. It is assumed that "one_char_token" does +// not contain '\0'. +static bool ParseOneCharToken(State *state, const char one_char_token) { + if (state->mangled_cur[0] == one_char_token) { + ++state->mangled_cur; + return true; + } + return false; +} + +// Returns true and advances "mangled_cur" if we find "two_char_token" +// at "mangled_cur" position. It is assumed that "two_char_token" does +// not contain '\0'. +static bool ParseTwoCharToken(State *state, const char *two_char_token) { + if (state->mangled_cur[0] == two_char_token[0] && + state->mangled_cur[1] == two_char_token[1]) { + state->mangled_cur += 2; + return true; + } + return false; +} + +// Returns true and advances "mangled_cur" if we find any character in +// "char_class" at "mangled_cur" position. +static bool ParseCharClass(State *state, const char *char_class) { + const char *p = char_class; + for (; *p != '\0'; ++p) { + if (state->mangled_cur[0] == *p) { + ++state->mangled_cur; + return true; + } + } + return false; +} + +// This function is used for handling an optional non-terminal. +static bool Optional(bool) { + return true; +} + +// This function is used for handling + syntax. +typedef bool (*ParseFunc)(State *); +static bool OneOrMore(ParseFunc parse_func, State *state) { + if (parse_func(state)) { + while (parse_func(state)) { + } + return true; + } + return false; +} + +// This function is used for handling * syntax. The function +// always returns true and must be followed by a termination token or a +// terminating sequence not handled by parse_func (e.g. +// ParseOneCharToken(state, 'E')). +static bool ZeroOrMore(ParseFunc parse_func, State *state) { + while (parse_func(state)) { + } + return true; +} + +// Append "str" at "out_cur". If there is an overflow, "overflowed" +// is set to true for later use. The output string is ensured to +// always terminate with '\0' as long as there is no overflow. +static void Append(State *state, const char * const str, const int length) { + int i; + for (i = 0; i < length; ++i) { + if (state->out_cur + 1 < state->out_end) { // +1 for '\0' + *state->out_cur = str[i]; + ++state->out_cur; + } else { + state->overflowed = true; + break; + } + } + if (!state->overflowed) { + *state->out_cur = '\0'; // Terminate it with '\0' + } +} + +// We don't use equivalents in libc to avoid locale issues. +static bool IsLower(char c) { + return c >= 'a' && c <= 'z'; +} + +static bool IsAlpha(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +static bool IsDigit(char c) { + return c >= '0' && c <= '9'; +} + +// Returns true if "str" is a function clone suffix. These suffixes are used +// by GCC 4.5.x and later versions to indicate functions which have been +// cloned during optimization. We treat any sequence (.+.+)+ as +// a function clone suffix. +static bool IsFunctionCloneSuffix(const char *str) { + size_t i = 0; + while (str[i] != '\0') { + // Consume a single .+.+ sequence. + if (str[i] != '.' || !IsAlpha(str[i + 1])) { + return false; + } + i += 2; + while (IsAlpha(str[i])) { + ++i; + } + if (str[i] != '.' || !IsDigit(str[i + 1])) { + return false; + } + i += 2; + while (IsDigit(str[i])) { + ++i; + } + } + return true; // Consumed everything in "str". +} + +// Append "str" with some tweaks, iff "append" state is true. +// Returns true so that it can be placed in "if" conditions. +static void MaybeAppendWithLength(State *state, const char * const str, + const int length) { + if (state->append && length > 0) { + // Append a space if the output buffer ends with '<' and "str" + // starts with '<' to avoid <<<. + if (str[0] == '<' && state->out_begin < state->out_cur && + state->out_cur[-1] == '<') { + Append(state, " ", 1); + } + // Remember the last identifier name for ctors/dtors. + if (IsAlpha(str[0]) || str[0] == '_') { + state->prev_name = state->out_cur; + state->prev_name_length = length; + } + Append(state, str, length); + } +} + +// A convenient wrapper arount MaybeAppendWithLength(). +static bool MaybeAppend(State *state, const char * const str) { + if (state->append) { + int length = StrLen(str); + MaybeAppendWithLength(state, str, length); + } + return true; +} + +// This function is used for handling nested names. +static bool EnterNestedName(State *state) { + state->nest_level = 0; + return true; +} + +// This function is used for handling nested names. +static bool LeaveNestedName(State *state, short prev_value) { + state->nest_level = prev_value; + return true; +} + +// Disable the append mode not to print function parameters, etc. +static bool DisableAppend(State *state) { + state->append = false; + return true; +} + +// Restore the append mode to the previous state. +static bool RestoreAppend(State *state, bool prev_value) { + state->append = prev_value; + return true; +} + +// Increase the nest level for nested names. +static void MaybeIncreaseNestLevel(State *state) { + if (state->nest_level > -1) { + ++state->nest_level; + } +} + +// Appends :: for nested names if necessary. +static void MaybeAppendSeparator(State *state) { + if (state->nest_level >= 1) { + MaybeAppend(state, "::"); + } +} + +// Cancel the last separator if necessary. +static void MaybeCancelLastSeparator(State *state) { + if (state->nest_level >= 1 && state->append && + state->out_begin <= state->out_cur - 2) { + state->out_cur -= 2; + *state->out_cur = '\0'; + } +} + +// Returns true if the identifier of the given length pointed to by +// "mangled_cur" is anonymous namespace. +static bool IdentifierIsAnonymousNamespace(State *state, int length) { + static const char anon_prefix[] = "_GLOBAL__N_"; + return (length > (int)sizeof(anon_prefix) - 1 && // Should be longer. + StrPrefix(state->mangled_cur, anon_prefix)); +} + +// Forward declarations of our parsing functions. +static bool ParseMangledName(State *state); +static bool ParseEncoding(State *state); +static bool ParseName(State *state); +static bool ParseUnscopedName(State *state); +static bool ParseUnscopedTemplateName(State *state); +static bool ParseNestedName(State *state); +static bool ParsePrefix(State *state); +static bool ParseUnqualifiedName(State *state); +static bool ParseSourceName(State *state); +static bool ParseLocalSourceName(State *state); +static bool ParseNumber(State *state, int *number_out); +static bool ParseFloatNumber(State *state); +static bool ParseSeqId(State *state); +static bool ParseIdentifier(State *state, int length); +static bool ParseOperatorName(State *state); +static bool ParseSpecialName(State *state); +static bool ParseCallOffset(State *state); +static bool ParseNVOffset(State *state); +static bool ParseVOffset(State *state); +static bool ParseCtorDtorName(State *state); +static bool ParseType(State *state); +static bool ParseCVQualifiers(State *state); +static bool ParseBuiltinType(State *state); +static bool ParseFunctionType(State *state); +static bool ParseBareFunctionType(State *state); +static bool ParseClassEnumType(State *state); +static bool ParseArrayType(State *state); +static bool ParsePointerToMemberType(State *state); +static bool ParseTemplateParam(State *state); +static bool ParseTemplateTemplateParam(State *state); +static bool ParseTemplateArgs(State *state); +static bool ParseTemplateArg(State *state); +static bool ParseExpression(State *state); +static bool ParseExprPrimary(State *state); +static bool ParseLocalName(State *state); +static bool ParseDiscriminator(State *state); +static bool ParseSubstitution(State *state); + +// Implementation note: the following code is a straightforward +// translation of the Itanium C++ ABI defined in BNF with a couple of +// exceptions. +// +// - Support GNU extensions not defined in the Itanium C++ ABI +// - and are combined to avoid infinite loop +// - Reorder patterns to shorten the code +// - Reorder patterns to give greedier functions precedence +// We'll mark "Less greedy than" for these cases in the code +// +// Each parsing function changes the state and returns true on +// success. Otherwise, don't change the state and returns false. To +// ensure that the state isn't changed in the latter case, we save the +// original state before we call more than one parsing functions +// consecutively with &&, and restore the state if unsuccessful. See +// ParseEncoding() as an example of this convention. We follow the +// convention throughout the code. +// +// Originally we tried to do demangling without following the full ABI +// syntax but it turned out we needed to follow the full syntax to +// parse complicated cases like nested template arguments. Note that +// implementing a full-fledged demangler isn't trivial (libiberty's +// cp-demangle.c has +4300 lines). +// +// Note that (foo) in <(foo) ...> is a modifier to be ignored. +// +// Reference: +// - Itanium C++ ABI +// + +// ::= _Z +static bool ParseMangledName(State *state) { + return ParseTwoCharToken(state, "_Z") && ParseEncoding(state); +} + +// ::= <(function) name> +// ::= <(data) name> +// ::= +static bool ParseEncoding(State *state) { + State copy = *state; + if (ParseName(state) && ParseBareFunctionType(state)) { + return true; + } + *state = copy; + + if (ParseName(state) || ParseSpecialName(state)) { + return true; + } + return false; +} + +// ::= +// ::= +// ::= +// ::= +static bool ParseName(State *state) { + if (ParseNestedName(state) || ParseLocalName(state)) { + return true; + } + + State copy = *state; + if (ParseUnscopedTemplateName(state) && + ParseTemplateArgs(state)) { + return true; + } + *state = copy; + + // Less greedy than . + if (ParseUnscopedName(state)) { + return true; + } + return false; +} + +// ::= +// ::= St +static bool ParseUnscopedName(State *state) { + if (ParseUnqualifiedName(state)) { + return true; + } + + State copy = *state; + if (ParseTwoCharToken(state, "St") && + MaybeAppend(state, "std::") && + ParseUnqualifiedName(state)) { + return true; + } + *state = copy; + return false; +} + +// ::= +// ::= +static bool ParseUnscopedTemplateName(State *state) { + return ParseUnscopedName(state) || ParseSubstitution(state); +} + +// ::= N [] E +// ::= N [] E +static bool ParseNestedName(State *state) { + State copy = *state; + if (ParseOneCharToken(state, 'N') && + EnterNestedName(state) && + Optional(ParseCVQualifiers(state)) && + ParsePrefix(state) && + LeaveNestedName(state, copy.nest_level) && + ParseOneCharToken(state, 'E')) { + return true; + } + *state = copy; + return false; +} + +// This part is tricky. If we literally translate them to code, we'll +// end up infinite loop. Hence we merge them to avoid the case. +// +// ::= +// ::= +// ::= +// ::= +// ::= # empty +// ::= <(template) unqualified-name> +// ::= +// ::= +static bool ParsePrefix(State *state) { + bool has_something = false; + while (true) { + MaybeAppendSeparator(state); + if (ParseTemplateParam(state) || + ParseSubstitution(state) || + ParseUnscopedName(state)) { + has_something = true; + MaybeIncreaseNestLevel(state); + continue; + } + MaybeCancelLastSeparator(state); + if (has_something && ParseTemplateArgs(state)) { + return ParsePrefix(state); + } else { + break; + } + } + return true; +} + +// ::= +// ::= +// ::= +// ::= +static bool ParseUnqualifiedName(State *state) { + return (ParseOperatorName(state) || + ParseCtorDtorName(state) || + ParseSourceName(state) || + ParseLocalSourceName(state)); +} + +// ::= +static bool ParseSourceName(State *state) { + State copy = *state; + int length = -1; + if (ParseNumber(state, &length) && ParseIdentifier(state, length)) { + return true; + } + *state = copy; + return false; +} + +// ::= L [] +// +// References: +// http://gcc.gnu.org/bugzilla/show_bug.cgi?id=31775 +// http://gcc.gnu.org/viewcvs?view=rev&revision=124467 +static bool ParseLocalSourceName(State *state) { + State copy = *state; + if (ParseOneCharToken(state, 'L') && ParseSourceName(state) && + Optional(ParseDiscriminator(state))) { + return true; + } + *state = copy; + return false; +} + +// ::= [n] +// If "number_out" is non-null, then *number_out is set to the value of the +// parsed number on success. +static bool ParseNumber(State *state, int *number_out) { + int sign = 1; + if (ParseOneCharToken(state, 'n')) { + sign = -1; + } + const char *p = state->mangled_cur; + int number = 0; + for (;*p != '\0'; ++p) { + if (IsDigit(*p)) { + number = number * 10 + (*p - '0'); + } else { + break; + } + } + if (p != state->mangled_cur) { // Conversion succeeded. + state->mangled_cur = p; + if (number_out != NULL) { + *number_out = number * sign; + } + return true; + } + return false; +} + +// Floating-point literals are encoded using a fixed-length lowercase +// hexadecimal string. +static bool ParseFloatNumber(State *state) { + const char *p = state->mangled_cur; + for (;*p != '\0'; ++p) { + if (!IsDigit(*p) && !(*p >= 'a' && *p <= 'f')) { + break; + } + } + if (p != state->mangled_cur) { // Conversion succeeded. + state->mangled_cur = p; + return true; + } + return false; +} + +// The is a sequence number in base 36, +// using digits and upper case letters +static bool ParseSeqId(State *state) { + const char *p = state->mangled_cur; + for (;*p != '\0'; ++p) { + if (!IsDigit(*p) && !(*p >= 'A' && *p <= 'Z')) { + break; + } + } + if (p != state->mangled_cur) { // Conversion succeeded. + state->mangled_cur = p; + return true; + } + return false; +} + +// ::= (of given length) +static bool ParseIdentifier(State *state, int length) { + if (length == -1 || + !AtLeastNumCharsRemaining(state->mangled_cur, length)) { + return false; + } + if (IdentifierIsAnonymousNamespace(state, length)) { + MaybeAppend(state, "(anonymous namespace)"); + } else { + MaybeAppendWithLength(state, state->mangled_cur, length); + } + state->mangled_cur += length; + return true; +} + +// ::= nw, and other two letters cases +// ::= cv # (cast) +// ::= v # vendor extended operator +static bool ParseOperatorName(State *state) { + if (!AtLeastNumCharsRemaining(state->mangled_cur, 2)) { + return false; + } + // First check with "cv" (cast) case. + State copy = *state; + if (ParseTwoCharToken(state, "cv") && + MaybeAppend(state, "operator ") && + EnterNestedName(state) && + ParseType(state) && + LeaveNestedName(state, copy.nest_level)) { + return true; + } + *state = copy; + + // Then vendor extended operators. + if (ParseOneCharToken(state, 'v') && ParseCharClass(state, "0123456789") && + ParseSourceName(state)) { + return true; + } + *state = copy; + + // Other operator names should start with a lower alphabet followed + // by a lower/upper alphabet. + if (!(IsLower(state->mangled_cur[0]) && + IsAlpha(state->mangled_cur[1]))) { + return false; + } + // We may want to perform a binary search if we really need speed. + const AbbrevPair *p; + for (p = kOperatorList; p->abbrev != NULL; ++p) { + if (state->mangled_cur[0] == p->abbrev[0] && + state->mangled_cur[1] == p->abbrev[1]) { + MaybeAppend(state, "operator"); + if (IsLower(*p->real_name)) { // new, delete, etc. + MaybeAppend(state, " "); + } + MaybeAppend(state, p->real_name); + state->mangled_cur += 2; + return true; + } + } + return false; +} + +// ::= TV +// ::= TT +// ::= TI +// ::= TS +// ::= Tc <(base) encoding> +// ::= GV <(object) name> +// ::= T <(base) encoding> +// G++ extensions: +// ::= TC <(offset) number> _ <(base) type> +// ::= TF +// ::= TJ +// ::= GR +// ::= GA +// ::= Th <(base) encoding> +// ::= Tv <(base) encoding> +// +// Note: we don't care much about them since they don't appear in +// stack traces. The are special data. +static bool ParseSpecialName(State *state) { + State copy = *state; + if (ParseOneCharToken(state, 'T') && + ParseCharClass(state, "VTIS") && + ParseType(state)) { + return true; + } + *state = copy; + + if (ParseTwoCharToken(state, "Tc") && ParseCallOffset(state) && + ParseCallOffset(state) && ParseEncoding(state)) { + return true; + } + *state = copy; + + if (ParseTwoCharToken(state, "GV") && + ParseName(state)) { + return true; + } + *state = copy; + + if (ParseOneCharToken(state, 'T') && ParseCallOffset(state) && + ParseEncoding(state)) { + return true; + } + *state = copy; + + // G++ extensions + if (ParseTwoCharToken(state, "TC") && ParseType(state) && + ParseNumber(state, NULL) && ParseOneCharToken(state, '_') && + DisableAppend(state) && + ParseType(state)) { + RestoreAppend(state, copy.append); + return true; + } + *state = copy; + + if (ParseOneCharToken(state, 'T') && ParseCharClass(state, "FJ") && + ParseType(state)) { + return true; + } + *state = copy; + + if (ParseTwoCharToken(state, "GR") && ParseName(state)) { + return true; + } + *state = copy; + + if (ParseTwoCharToken(state, "GA") && ParseEncoding(state)) { + return true; + } + *state = copy; + + if (ParseOneCharToken(state, 'T') && ParseCharClass(state, "hv") && + ParseCallOffset(state) && ParseEncoding(state)) { + return true; + } + *state = copy; + return false; +} + +// ::= h _ +// ::= v _ +static bool ParseCallOffset(State *state) { + State copy = *state; + if (ParseOneCharToken(state, 'h') && + ParseNVOffset(state) && ParseOneCharToken(state, '_')) { + return true; + } + *state = copy; + + if (ParseOneCharToken(state, 'v') && + ParseVOffset(state) && ParseOneCharToken(state, '_')) { + return true; + } + *state = copy; + + return false; +} + +// ::= <(offset) number> +static bool ParseNVOffset(State *state) { + return ParseNumber(state, NULL); +} + +// ::= <(offset) number> _ <(virtual offset) number> +static bool ParseVOffset(State *state) { + State copy = *state; + if (ParseNumber(state, NULL) && ParseOneCharToken(state, '_') && + ParseNumber(state, NULL)) { + return true; + } + *state = copy; + return false; +} + +// ::= C1 | C2 | C3 +// ::= D0 | D1 | D2 +static bool ParseCtorDtorName(State *state) { + State copy = *state; + if (ParseOneCharToken(state, 'C') && + ParseCharClass(state, "123")) { + const char * const prev_name = state->prev_name; + const int prev_name_length = state->prev_name_length; + MaybeAppendWithLength(state, prev_name, prev_name_length); + return true; + } + *state = copy; + + if (ParseOneCharToken(state, 'D') && + ParseCharClass(state, "012")) { + const char * const prev_name = state->prev_name; + const int prev_name_length = state->prev_name_length; + MaybeAppend(state, "~"); + MaybeAppendWithLength(state, prev_name, prev_name_length); + return true; + } + *state = copy; + return false; +} + +// ::= +// ::= P # pointer-to +// ::= R # reference-to +// ::= O # rvalue reference-to (C++0x) +// ::= C # complex pair (C 2000) +// ::= G # imaginary (C 2000) +// ::= U # vendor extended type qualifier +// ::= +// ::= +// ::= +// ::= +// ::= +// ::= +// ::= +// ::= +// ::= Dp # pack expansion of (C++0x) +// ::= Dt E # decltype of an id-expression or class +// # member access (C++0x) +// ::= DT E # decltype of an expression (C++0x) +// +static bool ParseType(State *state) { + // We should check CV-qualifers, and PRGC things first. + State copy = *state; + if (ParseCVQualifiers(state) && ParseType(state)) { + return true; + } + *state = copy; + + if (ParseCharClass(state, "OPRCG") && ParseType(state)) { + return true; + } + *state = copy; + + if (ParseTwoCharToken(state, "Dp") && ParseType(state)) { + return true; + } + *state = copy; + + if (ParseOneCharToken(state, 'D') && ParseCharClass(state, "tT") && + ParseExpression(state) && ParseOneCharToken(state, 'E')) { + return true; + } + *state = copy; + + if (ParseOneCharToken(state, 'U') && ParseSourceName(state) && + ParseType(state)) { + return true; + } + *state = copy; + + if (ParseBuiltinType(state) || + ParseFunctionType(state) || + ParseClassEnumType(state) || + ParseArrayType(state) || + ParsePointerToMemberType(state) || + ParseSubstitution(state)) { + return true; + } + + if (ParseTemplateTemplateParam(state) && + ParseTemplateArgs(state)) { + return true; + } + *state = copy; + + // Less greedy than . + if (ParseTemplateParam(state)) { + return true; + } + + return false; +} + +// ::= [r] [V] [K] +// We don't allow empty to avoid infinite loop in +// ParseType(). +static bool ParseCVQualifiers(State *state) { + int num_cv_qualifiers = 0; + num_cv_qualifiers += ParseOneCharToken(state, 'r'); + num_cv_qualifiers += ParseOneCharToken(state, 'V'); + num_cv_qualifiers += ParseOneCharToken(state, 'K'); + return num_cv_qualifiers > 0; +} + +// ::= v, etc. +// ::= u +static bool ParseBuiltinType(State *state) { + const AbbrevPair *p; + for (p = kBuiltinTypeList; p->abbrev != NULL; ++p) { + if (state->mangled_cur[0] == p->abbrev[0]) { + MaybeAppend(state, p->real_name); + ++state->mangled_cur; + return true; + } + } + + State copy = *state; + if (ParseOneCharToken(state, 'u') && ParseSourceName(state)) { + return true; + } + *state = copy; + return false; +} + +// ::= F [Y] E +static bool ParseFunctionType(State *state) { + State copy = *state; + if (ParseOneCharToken(state, 'F') && + Optional(ParseOneCharToken(state, 'Y')) && + ParseBareFunctionType(state) && ParseOneCharToken(state, 'E')) { + return true; + } + *state = copy; + return false; +} + +// ::= <(signature) type>+ +static bool ParseBareFunctionType(State *state) { + State copy = *state; + DisableAppend(state); + if (OneOrMore(ParseType, state)) { + RestoreAppend(state, copy.append); + MaybeAppend(state, "()"); + return true; + } + *state = copy; + return false; +} + +// ::= +static bool ParseClassEnumType(State *state) { + return ParseName(state); +} + +// ::= A <(positive dimension) number> _ <(element) type> +// ::= A [<(dimension) expression>] _ <(element) type> +static bool ParseArrayType(State *state) { + State copy = *state; + if (ParseOneCharToken(state, 'A') && ParseNumber(state, NULL) && + ParseOneCharToken(state, '_') && ParseType(state)) { + return true; + } + *state = copy; + + if (ParseOneCharToken(state, 'A') && Optional(ParseExpression(state)) && + ParseOneCharToken(state, '_') && ParseType(state)) { + return true; + } + *state = copy; + return false; +} + +// ::= M <(class) type> <(member) type> +static bool ParsePointerToMemberType(State *state) { + State copy = *state; + if (ParseOneCharToken(state, 'M') && ParseType(state) && + ParseType(state)) { + return true; + } + *state = copy; + return false; +} + +// ::= T_ +// ::= T _ +static bool ParseTemplateParam(State *state) { + if (ParseTwoCharToken(state, "T_")) { + MaybeAppend(state, "?"); // We don't support template substitutions. + return true; + } + + State copy = *state; + if (ParseOneCharToken(state, 'T') && ParseNumber(state, NULL) && + ParseOneCharToken(state, '_')) { + MaybeAppend(state, "?"); // We don't support template substitutions. + return true; + } + *state = copy; + return false; +} + + +// ::= +// ::= +static bool ParseTemplateTemplateParam(State *state) { + return (ParseTemplateParam(state) || + ParseSubstitution(state)); +} + +// ::= I + E +static bool ParseTemplateArgs(State *state) { + State copy = *state; + DisableAppend(state); + if (ParseOneCharToken(state, 'I') && + OneOrMore(ParseTemplateArg, state) && + ParseOneCharToken(state, 'E')) { + RestoreAppend(state, copy.append); + MaybeAppend(state, "<>"); + return true; + } + *state = copy; + return false; +} + +// ::= +// ::= +// ::= I * E # argument pack +// ::= X E +static bool ParseTemplateArg(State *state) { + State copy = *state; + if (ParseOneCharToken(state, 'I') && + ZeroOrMore(ParseTemplateArg, state) && + ParseOneCharToken(state, 'E')) { + return true; + } + *state = copy; + + if (ParseType(state) || + ParseExprPrimary(state)) { + return true; + } + *state = copy; + + if (ParseOneCharToken(state, 'X') && ParseExpression(state) && + ParseOneCharToken(state, 'E')) { + return true; + } + *state = copy; + return false; +} + +// ::= +// ::= +// ::= +// ::= +// ::= +// +// ::= st +// ::= sr +// ::= sr +static bool ParseExpression(State *state) { + if (ParseTemplateParam(state) || ParseExprPrimary(state)) { + return true; + } + + State copy = *state; + if (ParseOperatorName(state) && + ParseExpression(state) && + ParseExpression(state) && + ParseExpression(state)) { + return true; + } + *state = copy; + + if (ParseOperatorName(state) && + ParseExpression(state) && + ParseExpression(state)) { + return true; + } + *state = copy; + + if (ParseOperatorName(state) && + ParseExpression(state)) { + return true; + } + *state = copy; + + if (ParseTwoCharToken(state, "st") && ParseType(state)) { + return true; + } + *state = copy; + + if (ParseTwoCharToken(state, "sr") && ParseType(state) && + ParseUnqualifiedName(state) && + ParseTemplateArgs(state)) { + return true; + } + *state = copy; + + if (ParseTwoCharToken(state, "sr") && ParseType(state) && + ParseUnqualifiedName(state)) { + return true; + } + *state = copy; + return false; +} + +// ::= L <(value) number> E +// ::= L <(value) float> E +// ::= L E +// // A bug in g++'s C++ ABI version 2 (-fabi-version=2). +// ::= LZ E +static bool ParseExprPrimary(State *state) { + State copy = *state; + if (ParseOneCharToken(state, 'L') && ParseType(state) && + ParseNumber(state, NULL) && + ParseOneCharToken(state, 'E')) { + return true; + } + *state = copy; + + if (ParseOneCharToken(state, 'L') && ParseType(state) && + ParseFloatNumber(state) && + ParseOneCharToken(state, 'E')) { + return true; + } + *state = copy; + + if (ParseOneCharToken(state, 'L') && ParseMangledName(state) && + ParseOneCharToken(state, 'E')) { + return true; + } + *state = copy; + + if (ParseTwoCharToken(state, "LZ") && ParseEncoding(state) && + ParseOneCharToken(state, 'E')) { + return true; + } + *state = copy; + + return false; +} + +// := Z <(function) encoding> E <(entity) name> +// [] +// := Z <(function) encoding> E s [] +static bool ParseLocalName(State *state) { + State copy = *state; + if (ParseOneCharToken(state, 'Z') && ParseEncoding(state) && + ParseOneCharToken(state, 'E') && MaybeAppend(state, "::") && + ParseName(state) && Optional(ParseDiscriminator(state))) { + return true; + } + *state = copy; + + if (ParseOneCharToken(state, 'Z') && ParseEncoding(state) && + ParseTwoCharToken(state, "Es") && Optional(ParseDiscriminator(state))) { + return true; + } + *state = copy; + return false; +} + +// := _ <(non-negative) number> +static bool ParseDiscriminator(State *state) { + State copy = *state; + if (ParseOneCharToken(state, '_') && ParseNumber(state, NULL)) { + return true; + } + *state = copy; + return false; +} + +// ::= S_ +// ::= S _ +// ::= St, etc. +static bool ParseSubstitution(State *state) { + if (ParseTwoCharToken(state, "S_")) { + MaybeAppend(state, "?"); // We don't support substitutions. + return true; + } + + State copy = *state; + if (ParseOneCharToken(state, 'S') && ParseSeqId(state) && + ParseOneCharToken(state, '_')) { + MaybeAppend(state, "?"); // We don't support substitutions. + return true; + } + *state = copy; + + // Expand abbreviations like "St" => "std". + if (ParseOneCharToken(state, 'S')) { + const AbbrevPair *p; + for (p = kSubstitutionList; p->abbrev != NULL; ++p) { + if (state->mangled_cur[0] == p->abbrev[1]) { + MaybeAppend(state, "std"); + if (p->real_name[0] != '\0') { + MaybeAppend(state, "::"); + MaybeAppend(state, p->real_name); + } + ++state->mangled_cur; + return true; + } + } + } + *state = copy; + return false; +} + +// Parse , optionally followed by either a function-clone suffix +// or version suffix. Returns true only if all of "mangled_cur" was consumed. +static bool ParseTopLevelMangledName(State *state) { + if (ParseMangledName(state)) { + if (state->mangled_cur[0] != '\0') { + // Drop trailing function clone suffix, if any. + if (IsFunctionCloneSuffix(state->mangled_cur)) { + return true; + } + // Append trailing version suffix if any. + // ex. _Z3foo@@GLIBCXX_3.4 + if (state->mangled_cur[0] == '@') { + MaybeAppend(state, state->mangled_cur); + return true; + } + return false; // Unconsumed suffix. + } + return true; + } + return false; +} + +// The demangler entry point. +bool Demangle(const char *mangled, char *out, int out_size) { + State state; + InitState(&state, mangled, out, out_size); + return ParseTopLevelMangledName(&state) && !state.overflowed; +} + +_END_GOOGLE_NAMESPACE_ diff --git a/src/butil/third_party/symbolize/demangle.h b/src/butil/third_party/symbolize/demangle.h new file mode 100644 index 0000000..a8b308e --- /dev/null +++ b/src/butil/third_party/symbolize/demangle.h @@ -0,0 +1,84 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Satoru Takabayashi +// +// An async-signal-safe and thread-safe demangler for Itanium C++ ABI +// (aka G++ V3 ABI). + +// The demangler is implemented to be used in async signal handlers to +// symbolize stack traces. We cannot use libstdc++'s +// abi::__cxa_demangle() in such signal handlers since it's not async +// signal safe (it uses malloc() internally). +// +// Note that this demangler doesn't support full demangling. More +// specifically, it doesn't print types of function parameters and +// types of template arguments. It just skips them. However, it's +// still very useful to extract basic information such as class, +// function, constructor, destructor, and operator names. +// +// See the implementation note in demangle.cc if you are interested. +// +// Example: +// +// | Mangled Name | The Demangler | abi::__cxa_demangle() +// |---------------|---------------|----------------------- +// | _Z1fv | f() | f() +// | _Z1fi | f() | f(int) +// | _Z3foo3bar | foo() | foo(bar) +// | _Z1fIiEvi | f<>() | void f(int) +// | _ZN1N1fE | N::f | N::f +// | _ZN3Foo3BarEv | Foo::Bar() | Foo::Bar() +// | _Zrm1XS_" | operator%() | operator%(X, X) +// | _ZN3FooC1Ev | Foo::Foo() | Foo::Foo() +// | _Z1fSs | f() | f(std::basic_string, +// | | | std::allocator >) +// +// See the unit test for more examples. +// +// Note: we might want to write demanglers for ABIs other than Itanium +// C++ ABI in the future. +// + +#ifndef BUTIL_DEMANGLE_H_ +#define BUTIL_DEMANGLE_H_ + +#include "config.h" + +_START_GOOGLE_NAMESPACE_ + +// Demangle "mangled". On success, return true and write the +// demangled symbol name to "out". Otherwise, return false. +// "out" is modified even if demangling is unsuccessful. +bool Demangle(const char *mangled, char *out, int out_size); + +_END_GOOGLE_NAMESPACE_ + +#endif // BUTIL_DEMANGLE_H_ diff --git a/src/butil/third_party/symbolize/glog/logging.h b/src/butil/third_party/symbolize/glog/logging.h new file mode 100644 index 0000000..a42c306 --- /dev/null +++ b/src/butil/third_party/symbolize/glog/logging.h @@ -0,0 +1,5 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Empty. diff --git a/src/butil/third_party/symbolize/glog/raw_logging.h b/src/butil/third_party/symbolize/glog/raw_logging.h new file mode 100644 index 0000000..f5515c4 --- /dev/null +++ b/src/butil/third_party/symbolize/glog/raw_logging.h @@ -0,0 +1,6 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#define WARNING 1; +#define RAW_LOG(severity, ...); // Do nothing. diff --git a/src/butil/third_party/symbolize/symbolize.cc b/src/butil/third_party/symbolize/symbolize.cc new file mode 100644 index 0000000..0b4000a --- /dev/null +++ b/src/butil/third_party/symbolize/symbolize.cc @@ -0,0 +1,948 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Satoru Takabayashi +// Stack-footprint reduction work done by Raksit Ashok +// +// Implementation note: +// +// We don't use heaps but only use stacks. We want to reduce the +// stack consumption so that the symbolizer can run on small stacks. +// +// Here are some numbers collected with GCC 4.1.0 on x86: +// - sizeof(Elf32_Sym) = 16 +// - sizeof(Elf32_Shdr) = 40 +// - sizeof(Elf64_Sym) = 24 +// - sizeof(Elf64_Shdr) = 64 +// +// This implementation is intended to be async-signal-safe but uses +// some functions which are not guaranteed to be so, such as memchr() +// and memmove(). We assume they are async-signal-safe. +// +// Additional header can be specified by the GLOG_BUILD_CONFIG_INCLUDE +// macro to add platform specific defines (e.g. OS_OPENBSD). + +#ifdef GLOG_BUILD_CONFIG_INCLUDE +#include GLOG_BUILD_CONFIG_INCLUDE +#endif // GLOG_BUILD_CONFIG_INCLUDE + +#include "utilities.h" + +#if defined(HAVE_SYMBOLIZE) + +#include + +#include "symbolize.h" +#include "demangle.h" +#include "butil/compiler_specific.h" + +_START_GOOGLE_NAMESPACE_ + +// We don't use assert() since it's not guaranteed to be +// async-signal-safe. Instead we define a minimal assertion +// macro. So far, we don't need pretty printing for __FILE__, etc. + +// A wrapper for abort() to make it callable in ? :. +static int AssertFail() { + abort(); + return 0; // Should not reach. +} + +#define SAFE_ASSERT(expr) ((expr) ? 0 : AssertFail()) + +// NOTE(gejun): Mark as weak symbol to avoid conflict with same functions in +// glog, same reason applies to other functions marked weak in this file. +static SymbolizeCallback g_symbolize_callback = NULL; +void BAIDU_WEAK InstallSymbolizeCallback(SymbolizeCallback callback) { + g_symbolize_callback = callback; +} + +static SymbolizeOpenObjectFileCallback g_symbolize_open_object_file_callback = + NULL; +void BAIDU_WEAK InstallSymbolizeOpenObjectFileCallback( + SymbolizeOpenObjectFileCallback callback) { + g_symbolize_open_object_file_callback = callback; +} + +// This function wraps the Demangle function to provide an interface +// where the input symbol is demangled in-place. +// To keep stack consumption low, we would like this function to not +// get inlined. +static ATTRIBUTE_NOINLINE void DemangleInplace(char *out, int out_size) { + char demangled[256]; // Big enough for sane demangled symbols. + if (Demangle(out, demangled, sizeof(demangled))) { + // Demangling succeeded. Copy to out if the space allows. + size_t len = strlen(demangled); + if (len + 1 <= (size_t)out_size) { // +1 for '\0'. + SAFE_ASSERT(len < sizeof(demangled)); + memmove(out, demangled, len + 1); + } + } +} + +_END_GOOGLE_NAMESPACE_ + +#if defined(__ELF__) + +#include +#if defined(OS_OPENBSD) +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "symbolize.h" +#include "config.h" +#include "glog/raw_logging.h" + +// Re-runs fn until it doesn't cause EINTR. +#define NO_INTR(fn) do {} while ((fn) < 0 && errno == EINTR) + +_START_GOOGLE_NAMESPACE_ + +// Read up to "count" bytes from file descriptor "fd" into the buffer +// starting at "buf" while handling short reads and EINTR. On +// success, return the number of bytes read. Otherwise, return -1. +static ssize_t ReadPersistent(const int fd, void *buf, const size_t count) { + SAFE_ASSERT(fd >= 0); + SAFE_ASSERT(count <= (size_t)std::numeric_limits::max()); + char *buf0 = reinterpret_cast(buf); + ssize_t num_bytes = 0; + while ((size_t)num_bytes < count) { + ssize_t len; + NO_INTR(len = read(fd, buf0 + num_bytes, count - num_bytes)); + if (len < 0) { // There was an error other than EINTR. + return -1; + } + if (len == 0) { // Reached EOF. + break; + } + num_bytes += len; + } + SAFE_ASSERT((size_t)num_bytes <= count); + return num_bytes; +} + +// Read up to "count" bytes from "offset" in the file pointed by file +// descriptor "fd" into the buffer starting at "buf". On success, +// return the number of bytes read. Otherwise, return -1. +static ssize_t ReadFromOffset(const int fd, void *buf, + const size_t count, const off_t offset) { + off_t off = lseek(fd, offset, SEEK_SET); + if (off == (off_t)-1) { + return -1; + } + return ReadPersistent(fd, buf, count); +} + +// Try reading exactly "count" bytes from "offset" bytes in a file +// pointed by "fd" into the buffer starting at "buf" while handling +// short reads and EINTR. On success, return true. Otherwise, return +// false. +static bool ReadFromOffsetExact(const int fd, void *buf, + const size_t count, const off_t offset) { + ssize_t len = ReadFromOffset(fd, buf, count, offset); + return len == (ssize_t)count; +} + +// Returns elf_header.e_type if the file pointed by fd is an ELF binary. +static int FileGetElfType(const int fd) { + ElfW(Ehdr) elf_header; + if (!ReadFromOffsetExact(fd, &elf_header, sizeof(elf_header), 0)) { + return -1; + } + if (memcmp(elf_header.e_ident, ELFMAG, SELFMAG) != 0) { + return -1; + } + return elf_header.e_type; +} + +// Read the section headers in the given ELF binary, and if a section +// of the specified type is found, set the output to this section header +// and return true. Otherwise, return false. +// To keep stack consumption low, we would like this function to not get +// inlined. +static ATTRIBUTE_NOINLINE bool +GetSectionHeaderByType(const int fd, ElfW(Half) sh_num, const off_t sh_offset, + ElfW(Word) type, ElfW(Shdr) *out) { + // Read at most 16 section headers at a time to save read calls. + ElfW(Shdr) buf[16]; + for (int i = 0; i < sh_num;) { + const ssize_t num_bytes_left = (sh_num - i) * sizeof(buf[0]); + const ssize_t num_bytes_to_read = + ((ssize_t)sizeof(buf) > num_bytes_left) ? num_bytes_left : sizeof(buf); + const ssize_t len = ReadFromOffset(fd, buf, num_bytes_to_read, + sh_offset + i * sizeof(buf[0])); + SAFE_ASSERT(len % sizeof(buf[0]) == 0); + const ssize_t num_headers_in_buf = len / sizeof(buf[0]); + SAFE_ASSERT((size_t)num_headers_in_buf <= sizeof(buf) / sizeof(buf[0])); + for (int j = 0; j < num_headers_in_buf; ++j) { + if (buf[j].sh_type == type) { + *out = buf[j]; + return true; + } + } + i += num_headers_in_buf; + } + return false; +} + +// There is no particular reason to limit section name to 63 characters, +// but there has (as yet) been no need for anything longer either. +const int kMaxSectionNameLen = 64; + +// name_len should include terminating '\0'. +bool BAIDU_WEAK GetSectionHeaderByName(int fd, const char *name, size_t name_len, + ElfW(Shdr) *out) { + ElfW(Ehdr) elf_header; + if (!ReadFromOffsetExact(fd, &elf_header, sizeof(elf_header), 0)) { + return false; + } + + ElfW(Shdr) shstrtab; + off_t shstrtab_offset = (elf_header.e_shoff + + elf_header.e_shentsize * elf_header.e_shstrndx); + if (!ReadFromOffsetExact(fd, &shstrtab, sizeof(shstrtab), shstrtab_offset)) { + return false; + } + + for (int i = 0; i < elf_header.e_shnum; ++i) { + off_t section_header_offset = (elf_header.e_shoff + + elf_header.e_shentsize * i); + if (!ReadFromOffsetExact(fd, out, sizeof(*out), section_header_offset)) { + return false; + } + char header_name[kMaxSectionNameLen]; + if (sizeof(header_name) < name_len) { + RAW_LOG(WARNING, "Section name '%s' is too long (%" PRIuS "); " + "section will not be found (even if present).", name, name_len); + // No point in even trying. + return false; + } + off_t name_offset = shstrtab.sh_offset + out->sh_name; + ssize_t n_read = ReadFromOffset(fd, &header_name, name_len, name_offset); + if (n_read == -1) { + return false; + } else if ((size_t)n_read != name_len) { + // Short read -- name could be at end of file. + continue; + } + if (memcmp(header_name, name, name_len) == 0) { + return true; + } + } + return false; +} + +// Read a symbol table and look for the symbol containing the +// pc. Iterate over symbols in a symbol table and look for the symbol +// containing "pc". On success, return true and write the symbol name +// to out. Otherwise, return false. +// To keep stack consumption low, we would like this function to not get +// inlined. +static ATTRIBUTE_NOINLINE bool +FindSymbol(uint64_t pc, const int fd, char *out, int out_size, + uint64_t *out_saddr, uint64_t symbol_offset, + const ElfW(Shdr) *strtab, const ElfW(Shdr) *symtab) { + if (symtab == NULL) { + return false; + } + const int num_symbols = symtab->sh_size / symtab->sh_entsize; + for (int i = 0; i < num_symbols;) { + off_t offset = symtab->sh_offset + i * symtab->sh_entsize; + + // If we are reading Elf64_Sym's, we want to limit this array to + // 32 elements (to keep stack consumption low), otherwise we can + // have a 64 element Elf32_Sym array. +#if __WORDSIZE == 64 +#define NUM_SYMBOLS 32 +#else +#define NUM_SYMBOLS 64 +#endif + + // Read at most NUM_SYMBOLS symbols at once to save read() calls. + ElfW(Sym) buf[NUM_SYMBOLS]; + const ssize_t len = ReadFromOffset(fd, &buf, sizeof(buf), offset); + SAFE_ASSERT(len % sizeof(buf[0]) == 0); + const ssize_t num_symbols_in_buf = len / sizeof(buf[0]); + SAFE_ASSERT((size_t)num_symbols_in_buf <= sizeof(buf)/sizeof(buf[0])); + for (int j = 0; j < num_symbols_in_buf; ++j) { + const ElfW(Sym)& symbol = buf[j]; + uint64_t start_address = symbol.st_value; + start_address += symbol_offset; + uint64_t end_address = start_address + symbol.st_size; + if (symbol.st_value != 0 && // Skip null value symbols. + symbol.st_shndx != 0 && // Skip undefined symbols. + start_address <= pc && pc < end_address) { + if (NULL != out) { + ssize_t len1 = ReadFromOffset( + fd, out, out_size, strtab->sh_offset + symbol.st_name); + if (len1 <= 0 || memchr(out, '\0', out_size) == NULL) { + return false; + } + } + if (NULL != out_saddr) { + *out_saddr = start_address; + } + return true; // Obtained the symbol name. + } + } + i += num_symbols_in_buf; + } + return false; +} + +// Get the symbol name of "pc" from the file pointed by "fd". Process +// both regular and dynamic symbol tables if necessary. On success, +// write the symbol name to "out" and return true. Otherwise, return +// false. +// `base_address` is the runtime VA that corresponds to ELF VA 0 of the +// object identified by `fd`. For ET_EXEC binaries it is ignored +// (symbols already hold absolute runtime addresses); for ET_DYN +// objects (PIE executables and shared libraries) the caller must +// supply a precise value (see +// OpenObjectFileContainingPcAndGetStartAddress() below, which derives +// it from the object's PT_LOAD program headers rather than from the +// /proc/self/maps file_offset field). +static bool GetSymbolFromObjectFile(const int fd, uint64_t pc, + char *out, int out_size, + uint64_t *out_saddr, + uint64_t base_address) { + // Read the ELF header. + ElfW(Ehdr) elf_header; + if (!ReadFromOffsetExact(fd, &elf_header, sizeof(elf_header), 0)) { + return false; + } + + uint64_t symbol_offset = 0; + if (elf_header.e_type == ET_DYN) { // DSO needs offset adjustment. + symbol_offset = base_address; + } + + ElfW(Shdr) symtab, strtab; + + // Consult a regular symbol table first. + if (GetSectionHeaderByType(fd, elf_header.e_shnum, elf_header.e_shoff, + SHT_SYMTAB, &symtab)) { + if (!ReadFromOffsetExact(fd, &strtab, sizeof(strtab), elf_header.e_shoff + + symtab.sh_link * sizeof(symtab))) { + return false; + } + if (FindSymbol(pc, fd, out, out_size, out_saddr, + symbol_offset, &strtab, &symtab)) { + return true; // Found the symbol in a regular symbol table. + } + } + + // If the symbol is not found, then consult a dynamic symbol table. + if (GetSectionHeaderByType(fd, elf_header.e_shnum, elf_header.e_shoff, + SHT_DYNSYM, &symtab)) { + if (!ReadFromOffsetExact(fd, &strtab, sizeof(strtab), elf_header.e_shoff + + symtab.sh_link * sizeof(symtab))) { + return false; + } + if (FindSymbol(pc, fd, out, out_size, out_saddr, + symbol_offset, &strtab, &symtab)) { + return true; // Found the symbol in a dynamic symbol table. + } + } + + return false; +} + +namespace { +// Thin wrapper around a file descriptor so that the file descriptor +// gets closed for sure. +struct FileDescriptor { + const int fd_; + explicit FileDescriptor(int fd) : fd_(fd) {} + ~FileDescriptor() { + if (fd_ >= 0) { + NO_INTR(close(fd_)); + } + } + int get() { return fd_; } + + private: + explicit FileDescriptor(const FileDescriptor&); + void operator=(const FileDescriptor&); +}; + +// Helper class for reading lines from file. +// +// Note: we don't use ProcMapsIterator since the object is big (it has +// a 5k array member) and uses async-unsafe functions such as sscanf() +// and snprintf(). +class LineReader { + public: + explicit LineReader(int fd, char *buf, int buf_len) : fd_(fd), + buf_(buf), buf_len_(buf_len), bol_(buf), eol_(buf), eod_(buf) { + } + + // Read '\n'-terminated line from file. On success, modify "bol" + // and "eol", then return true. Otherwise, return false. + // + // Note: if the last line doesn't end with '\n', the line will be + // dropped. It's an intentional behavior to make the code simple. + bool ReadLine(const char **bol, const char **eol) { + if (BufferIsEmpty()) { // First time. + const ssize_t num_bytes = ReadPersistent(fd_, buf_, buf_len_); + if (num_bytes <= 0) { // EOF or error. + return false; + } + eod_ = buf_ + num_bytes; + bol_ = buf_; + } else { + bol_ = eol_ + 1; // Advance to the next line in the buffer. + SAFE_ASSERT(bol_ <= eod_); // "bol_" can point to "eod_". + if (!HasCompleteLine()) { + const int incomplete_line_length = eod_ - bol_; + // Move the trailing incomplete line to the beginning. + memmove(buf_, bol_, incomplete_line_length); + // Read text from file and append it. + char * const append_pos = buf_ + incomplete_line_length; + const int capacity_left = buf_len_ - incomplete_line_length; + const ssize_t num_bytes = ReadPersistent(fd_, append_pos, + capacity_left); + if (num_bytes <= 0) { // EOF or error. + return false; + } + eod_ = append_pos + num_bytes; + bol_ = buf_; + } + } + eol_ = FindLineFeed(); + if (eol_ == NULL) { // '\n' not found. Malformed line. + return false; + } + *eol_ = '\0'; // Replace '\n' with '\0'. + + *bol = bol_; + *eol = eol_; + return true; + } + + // Beginning of line. + const char *bol() { + return bol_; + } + + // End of line. + const char *eol() { + return eol_; + } + + private: + explicit LineReader(const LineReader&); + void operator=(const LineReader&); + + char *FindLineFeed() { + return reinterpret_cast(memchr(bol_, '\n', eod_ - bol_)); + } + + bool BufferIsEmpty() { + return buf_ == eod_; + } + + bool HasCompleteLine() { + return !BufferIsEmpty() && FindLineFeed() != NULL; + } + + const int fd_; + char * const buf_; + const int buf_len_; + char *bol_; + char *eol_; + const char *eod_; // End of data in "buf_". +}; +} // namespace + +// Place the hex number read from "start" into "*hex". The pointer to +// the first non-hex character or "end" is returned. +static char *GetHex(const char *start, const char *end, uint64_t *hex) { + *hex = 0; + const char *p; + for (p = start; p < end; ++p) { + int ch = *p; + if ((ch >= '0' && ch <= '9') || + (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f')) { + *hex = (*hex << 4) | (ch < 'A' ? ch - '0' : (ch & 0xF) + 9); + } else { // Encountered the first non-hex character. + break; + } + } + SAFE_ASSERT(p <= end); + return const_cast(p); +} + +// Searches for the object file (from /proc/self/maps) that contains +// the specified pc. If found, sets |start_address| to the start address +// of where this object file is mapped in memory, sets the module base +// address into |base_address|, copies the object file name into +// |out_file_name|, and attempts to open the object file. If the object +// file is opened successfully, returns the file descriptor. Otherwise, +// returns -1. |out_file_name_size| is the size of the file name buffer +// (including the null-terminator). +static ATTRIBUTE_NOINLINE int +OpenObjectFileContainingPcAndGetStartAddress(uint64_t pc, + uint64_t &start_address, + uint64_t &base_address, + char *out_file_name, + int out_file_name_size) { + int object_fd; + + // Open /proc/self/maps. + int maps_fd; + NO_INTR(maps_fd = open("/proc/self/maps", O_RDONLY)); + FileDescriptor wrapped_maps_fd(maps_fd); + if (wrapped_maps_fd.get() < 0) { + return -1; + } + + // Also open /proc/self/mem so we can read ELF headers / program + // headers directly out of the mapped binary, which is the only + // reliable way to compute the runtime ELF base address for PIE + // executables and shared libraries (the file_offset reported by + // /proc/self/maps is per-mapping and does not necessarily match the + // ELF base when modern toolchains use `-z separate-code` or when + // the first PT_LOAD has a non-zero p_vaddr). Mirrors the + // implementation in glog upstream. + int mem_fd; + NO_INTR(mem_fd = open("/proc/self/mem", O_RDONLY)); + FileDescriptor wrapped_mem_fd(mem_fd); + if (wrapped_mem_fd.get() < 0) { + return -1; + } + + // Iterate over maps and look for the map containing the pc. Then + // look into the symbol tables inside. + char buf[1024]; // Big enough for line of sane /proc/self/maps + LineReader reader(wrapped_maps_fd.get(), buf, sizeof(buf)); + while (true) { + const char *cursor; + const char *eol; + if (!reader.ReadLine(&cursor, &eol)) { // EOF or malformed line. + return -1; + } + + // Start parsing line in /proc/self/maps. Here is an example: + // + // 08048000-0804c000 r-xp 00000000 08:01 2142121 /bin/cat + // + // We want start address (08048000), end address (0804c000), flags + // (r-xp) and file name (/bin/cat). + + // Read start address. + cursor = GetHex(cursor, eol, &start_address); + if (cursor == eol || *cursor != '-') { + return -1; // Malformed line. + } + ++cursor; // Skip '-'. + + // Read end address. + uint64_t end_address; + cursor = GetHex(cursor, eol, &end_address); + if (cursor == eol || *cursor != ' ') { + return -1; // Malformed line. + } + ++cursor; // Skip ' '. + + // Read flags. Skip flags until we encounter a space or eol. + const char * const flags_start = cursor; + while (cursor < eol && *cursor != ' ') { + ++cursor; + } + // We expect at least four letters for flags (ex. "r-xp"). + if (cursor == eol || cursor < flags_start + 4) { + return -1; // Malformed line. + } + + // Determine the base address by reading ELF headers in process + // memory. We must do this for *every* readable map, not just the + // r-x map that contains PC, because a PIE binary or shared + // library's ELF header is typically mapped by an earlier r-- map + // (separate-code layout). Once we encounter the r-- map carrying + // the ELF magic, we record base_address for the whole object. + // When we later reach the r-x map containing PC, base_address is + // already correct. + if (flags_start[0] == 'r') { + ElfW(Ehdr) ehdr; + if (ReadFromOffsetExact(wrapped_mem_fd.get(), &ehdr, sizeof(ehdr), + start_address) && + memcmp(ehdr.e_ident, ELFMAG, SELFMAG) == 0) { + switch (ehdr.e_type) { + case ET_EXEC: + base_address = 0; + break; + case ET_DYN: + // Find the PT_LOAD segment with p_offset == 0 (i.e. the + // segment that contains the ELF header). Its p_vaddr is + // the ELF VA that corresponds to the bytes we just read + // at `start_address`, so base_address = start_address - + // p_vaddr. Normally p_vaddr is 0 and base_address == + // start_address, but some non-standard linker scripts + // place the first LOAD at a non-zero VA. Fall back to + // start_address if no such PT_LOAD is found. + base_address = start_address; + for (unsigned i = 0; i != ehdr.e_phnum; ++i) { + ElfW(Phdr) phdr; + if (ReadFromOffsetExact(wrapped_mem_fd.get(), &phdr, + sizeof(phdr), + start_address + ehdr.e_phoff + + i * sizeof(phdr)) && + phdr.p_type == PT_LOAD && phdr.p_offset == 0) { + base_address = start_address - phdr.p_vaddr; + break; + } + } + break; + default: + // ET_REL or ET_CORE. Not directly executable, leave + // base_address untouched. + break; + } + } + } + + // Check start and end addresses. + if (!(start_address <= pc && pc < end_address)) { + continue; // We skip this map. PC isn't in this map. + } + + // Check flags. We are only interested in "r-x" maps. + if (memcmp(flags_start, "r-x", 3) != 0) { // Not a "r-x" map. + continue; // We skip this map. + } + ++cursor; // Skip ' '. + + // Read file offset (parsed but no longer used for base_address; + // base_address is now computed from PT_LOAD program headers + // above. Keep the parse so the cursor advances to the file name). + uint64_t file_offset; + cursor = GetHex(cursor, eol, &file_offset); + if (cursor == eol || *cursor != ' ') { + return -1; // Malformed line. + } + ++cursor; // Skip ' '. + (void)file_offset; + + // Skip to file name. "cursor" now points to dev. We need to + // skip at least two spaces for dev and inode. + int num_spaces = 0; + while (cursor < eol) { + if (*cursor == ' ') { + ++num_spaces; + } else if (num_spaces >= 2) { + // The first non-space character after skipping two spaces + // is the beginning of the file name. + break; + } + ++cursor; + } + if (cursor == eol) { + return -1; // Malformed line. + } + + // Finally, "cursor" now points to file name of our interest. + NO_INTR(object_fd = open(cursor, O_RDONLY)); + if (object_fd < 0) { + // Failed to open object file. Copy the object file name to + // |out_file_name|. + strncpy(out_file_name, cursor, out_file_name_size); + // Making sure |out_file_name| is always null-terminated. + out_file_name[out_file_name_size - 1] = '\0'; + return -1; + } + return object_fd; + } +} + +// POSIX doesn't define any async-signal safe function for converting +// an integer to ASCII. We'll have to define our own version. +// itoa_r() converts a (signed) integer to ASCII. It returns "buf", if the +// conversion was successful or NULL otherwise. It never writes more than "sz" +// bytes. Output will be truncated as needed, and a NUL character is always +// appended. +// NOTE: code from sandbox/linux/seccomp-bpf/demo.cc. +char *itoa_r(intptr_t i, char *buf, size_t sz, int base, size_t padding) { + // Make sure we can write at least one NUL byte. + size_t n = 1; + if (n > sz) + return NULL; + + if (base < 2 || base > 16) { + buf[0] = '\000'; + return NULL; + } + + char *start = buf; + + uintptr_t j = i; + + // Handle negative numbers (only for base 10). + if (i < 0 && base == 10) { + j = -i; + + // Make sure we can write the '-' character. + if (++n > sz) { + buf[0] = '\000'; + return NULL; + } + *start++ = '-'; + } + + // Loop until we have converted the entire number. Output at least one + // character (i.e. '0'). + char *ptr = start; + do { + // Make sure there is still enough space left in our output buffer. + if (++n > sz) { + buf[0] = '\000'; + return NULL; + } + + // Output the next digit. + *ptr++ = "0123456789abcdef"[j % base]; + j /= base; + + if (padding > 0) + padding--; + } while (j > 0 || padding > 0); + + // Terminate the output with a NUL character. + *ptr = '\000'; + + // Conversion to ASCII actually resulted in the digits being in reverse + // order. We can't easily generate them in forward order, as we can't tell + // the number of characters needed until we are done converting. + // So, now, we reverse the string (except for the possible "-" sign). + while (--ptr > start) { + char ch = *ptr; + *ptr = *start; + *start++ = ch; + } + return buf; +} + +// Safely appends string |source| to string |dest|. Never writes past the +// buffer size |dest_size| and guarantees that |dest| is null-terminated. +void SafeAppendString(const char* source, char* dest, int dest_size) { + int dest_string_length = strlen(dest); + SAFE_ASSERT(dest_string_length < dest_size); + dest += dest_string_length; + dest_size -= dest_string_length; + strncpy(dest, source, dest_size); + // Making sure |dest| is always null-terminated. + dest[dest_size - 1] = '\0'; +} + +// Converts a 64-bit value into a hex string, and safely appends it to |dest|. +// Never writes past the buffer size |dest_size| and guarantees that |dest| is +// null-terminated. +void SafeAppendHexNumber(uint64_t value, char* dest, int dest_size) { + // 64-bit numbers in hex can have up to 16 digits. + char buf[17] = {'\0'}; + SafeAppendString(itoa_r(value, buf, sizeof(buf), 16, 0), dest, dest_size); +} + +// The implementation of our symbolization routine. If it +// successfully finds the symbol containing "pc" and obtains the +// symbol name, returns true and write the symbol name to "out". +// Otherwise, returns false. If Callback function is installed via +// InstallSymbolizeCallback(), the function is also called in this function, +// and "out" is used as its output. +// To keep stack consumption low, we would like this function to not +// get inlined. +static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out, + int out_size, + uint64_t *out_saddr) { + uint64_t pc0 = reinterpret_cast(pc); + uint64_t start_address = 0; + uint64_t base_address = 0; + int object_fd = -1; + + if ((NULL == out || out_size < 1) && + NULL == out_saddr) { + return false; + } + if (NULL != out) { + out[0] = '\0'; + SafeAppendString("(", out, out_size); + } + + if (g_symbolize_open_object_file_callback) { + object_fd = g_symbolize_open_object_file_callback(pc0, start_address, + base_address, out + 1, + out_size - 1); + } else { + object_fd = OpenObjectFileContainingPcAndGetStartAddress(pc0, start_address, + base_address, + out + 1, + out_size - 1); + } + + // Check whether a file name was returned. + if (object_fd < 0) { + if (NULL != out && out[1] && NULL == out_saddr) { + // The object file containing PC was determined successfully however the + // object file was not opened successfully. This is still considered + // success because the object file name and offset are known and tools + // like asan_symbolize.py can be used for the symbolization. + out[out_size - 1] = '\0'; // Making sure |out| is always null-terminated. + SafeAppendString("+0x", out, out_size); + SafeAppendHexNumber(pc0 - base_address, out, out_size); + SafeAppendString(")", out, out_size); + return true; + } + // Failed to determine the object file containing PC. Bail out. + return false; + } + FileDescriptor wrapped_object_fd(object_fd); + int elf_type = FileGetElfType(wrapped_object_fd.get()); + if (elf_type == -1) { + return false; + } + if (g_symbolize_callback) { + // Run the call back if it's installed. + // Note: relocation (and much of the rest of this code) will be + // wrong for prelinked shared libraries and PIE executables. + uint64_t relocation = (elf_type == ET_DYN) ? start_address : 0; + int num_bytes_written = g_symbolize_callback(wrapped_object_fd.get(), + pc, out, out_size, + relocation); + if (num_bytes_written > 0) { + out += num_bytes_written; + out_size -= num_bytes_written; + } + } + // Use `base_address` (computed by + // OpenObjectFileContainingPcAndGetStartAddress() via the object's + // PT_LOAD program headers) as the relocation offset, NOT + // `start_address`. For ET_DYN objects produced by modern toolchains + // (binutils >= 2.31, lld) using `-z separate-code`, the r-x mapping + // starts at a non-zero file offset and `start_address` no longer + // equals the ELF base; only `base_address` is the correct value + // such that `symbol.st_value + base_address` recovers the runtime + // VA of a symbol. + if (!GetSymbolFromObjectFile(wrapped_object_fd.get(), pc0, + out, out_size, out_saddr, + base_address)) { + return false; + } + + if (NULL != out) { + // Symbolization succeeded. Now we try to demangle the symbol. + DemangleInplace(out, out_size); + } + return true; +} + +_END_GOOGLE_NAMESPACE_ + +#elif defined(OS_MACOSX) && defined(HAVE_DLADDR) + +#include +#include + +_START_GOOGLE_NAMESPACE_ + +static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out, + int out_size, + uint64_t *out_saddr) { + Dl_info info{}; + if (0 == dladdr(pc, &info)) { + return false; + } + if (NULL != out) { + if ((int)strlen(info.dli_sname) >= out_size) { + return false; + } + strcpy(out, info.dli_sname); + // Symbolization succeeded. Now we try to demangle the symbol. + DemangleInplace(out, out_size); + } + if (NULL != out_saddr) { + *out_saddr = (uint64_t)info.dli_saddr; + } + return true; +} + +_END_GOOGLE_NAMESPACE_ + +#else +# error BUG: HAVE_SYMBOLIZE was wrongly set +#endif + +_START_GOOGLE_NAMESPACE_ + +bool BAIDU_WEAK Symbolize(void *pc, char *out, int out_size) { + SAFE_ASSERT(out_size >= 0); + return SymbolizeAndDemangle(pc, out, out_size, NULL); +} + +bool BAIDU_WEAK SymbolizeAddress(void *pc, uint64_t *out) { + SAFE_ASSERT(NULL != out); + return SymbolizeAndDemangle(pc, NULL, 0, out); +} + +_END_GOOGLE_NAMESPACE_ + +#else /* HAVE_SYMBOLIZE */ + +#include + +#include "config.h" + +_START_GOOGLE_NAMESPACE_ + +// TODO: Support other environments. +bool BAIDU_WEAK Symbolize(void *pc, char *out, int out_size) { + assert(0); + return false; +} + +bool BAIDU_WEAK SymbolizeAddress(void *pc, uint64_t *out) { + assert(0); + return false; +} + +_END_GOOGLE_NAMESPACE_ + +#endif diff --git a/src/butil/third_party/symbolize/symbolize.h b/src/butil/third_party/symbolize/symbolize.h new file mode 100644 index 0000000..7bd31e4 --- /dev/null +++ b/src/butil/third_party/symbolize/symbolize.h @@ -0,0 +1,157 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Satoru Takabayashi +// +// This library provides Symbolize() function that symbolizes program +// counters to their corresponding symbol names on linux platforms. +// This library has a minimal implementation of an ELF symbol table +// reader (i.e. it doesn't depend on libelf, etc.). +// +// The algorithm used in Symbolize() is as follows. +// +// 1. Go through a list of maps in /proc/self/maps and find the map +// containing the program counter. +// +// 2. Open the mapped file and find a regular symbol table inside. +// Iterate over symbols in the symbol table and look for the symbol +// containing the program counter. If such a symbol is found, +// obtain the symbol name, and demangle the symbol if possible. +// If the symbol isn't found in the regular symbol table (binary is +// stripped), try the same thing with a dynamic symbol table. +// +// Note that Symbolize() is originally implemented to be used in +// FailureSignalHandler() in butil/google.cc. Hence it doesn't use +// malloc() and other unsafe operations. It should be both +// thread-safe and async-signal-safe. + +#ifndef BUTIL_SYMBOLIZE_H_ +#define BUTIL_SYMBOLIZE_H_ + +#include "utilities.h" +#include "config.h" +#include "glog/logging.h" + +#ifdef HAVE_SYMBOLIZE + +#if defined(__ELF__) // defined by gcc +#if defined(__OpenBSD__) +#include +#else +#include +#endif + +#if !defined(ANDROID) +#include // For ElfW() macro. +#endif + +// For systems where SIZEOF_VOID_P is not defined, determine it +// based on __LP64__ (defined by gcc on 64-bit systems) +#if !defined(SIZEOF_VOID_P) +# if defined(__LP64__) +# define SIZEOF_VOID_P 8 +# else +# define SIZEOF_VOID_P 4 +# endif +#endif + +// If there is no ElfW macro, let's define it by ourself. +#ifndef ElfW +# if SIZEOF_VOID_P == 4 +# define ElfW(type) Elf32_##type +# elif SIZEOF_VOID_P == 8 +# define ElfW(type) Elf64_##type +# else +# error "Unknown sizeof(void *)" +# endif +#endif + +_START_GOOGLE_NAMESPACE_ + +// Gets the section header for the given name, if it exists. Returns true on +// success. Otherwise, returns false. +bool GetSectionHeaderByName(int fd, const char *name, size_t name_len, + ElfW(Shdr) *out); + +_END_GOOGLE_NAMESPACE_ + +#endif /* __ELF__ */ + +_START_GOOGLE_NAMESPACE_ + +// Restrictions on the callbacks that follow: +// - The callbacks must not use heaps but only use stacks. +// - The callbacks must be async-signal-safe. + +// Installs a callback function, which will be called right before a symbol name +// is printed. The callback is intended to be used for showing a file name and a +// line number preceding a symbol name. +// "fd" is a file descriptor of the object file containing the program +// counter "pc". The callback function should write output to "out" +// and return the size of the output written. On error, the callback +// function should return -1. +typedef int (*SymbolizeCallback)(int fd, void *pc, char *out, size_t out_size, + uint64_t relocation); +void InstallSymbolizeCallback(SymbolizeCallback callback); + +// Installs a callback function, which will be called instead of +// OpenObjectFileContainingPcAndGetStartAddress. The callback is expected +// to searches for the object file (from /proc/self/maps) that contains +// the specified pc. If found, sets |start_address| to the start address +// of where this object file is mapped in memory, sets the module base +// address into |base_address|, copies the object file name into +// |out_file_name|, and attempts to open the object file. If the object +// file is opened successfully, returns the file descriptor. Otherwise, +// returns -1. |out_file_name_size| is the size of the file name buffer +// (including the null-terminator). +typedef int (*SymbolizeOpenObjectFileCallback)(uint64_t pc, + uint64_t &start_address, + uint64_t &base_address, + char *out_file_name, + int out_file_name_size); +void InstallSymbolizeOpenObjectFileCallback( + SymbolizeOpenObjectFileCallback callback); + +_END_GOOGLE_NAMESPACE_ + +#endif + +_START_GOOGLE_NAMESPACE_ + +// Symbolizes a program counter. On success, returns true and write the +// symbol name to "out". The symbol name is demangled if possible +// (supports symbols generated by GCC 3.x or newer). Otherwise, +// returns false. +bool Symbolize(void *pc, char *out, int out_size); + +bool SymbolizeAddress(void *pc, uint64_t *out); + +_END_GOOGLE_NAMESPACE_ + +#endif // BUTIL_SYMBOLIZE_H_ diff --git a/src/butil/third_party/symbolize/utilities.h b/src/butil/third_party/symbolize/utilities.h new file mode 100644 index 0000000..02f9b10 --- /dev/null +++ b/src/butil/third_party/symbolize/utilities.h @@ -0,0 +1,10 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include +#define HAVE_SYMBOLIZE 1 +#define ATTRIBUTE_NOINLINE __attribute__ ((noinline)) diff --git a/src/butil/third_party/valgrind/valgrind.h b/src/butil/third_party/valgrind/valgrind.h new file mode 100644 index 0000000..cc2cf3d --- /dev/null +++ b/src/butil/third_party/valgrind/valgrind.h @@ -0,0 +1,4840 @@ +/* -*- c -*- + ---------------------------------------------------------------- + + Notice that the following BSD-style license applies to this one + file (valgrind.h) only. The rest of Valgrind is licensed under the + terms of the GNU General Public License, version 2, unless + otherwise indicated. See the COPYING file in the source + distribution for details. + + ---------------------------------------------------------------- + + This file is part of Valgrind, a dynamic binary instrumentation + framework. + + Copyright (C) 2000-2012 Julian Seward. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. The origin of this software must not be misrepresented; you must + not claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + + 3. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original software. + + 4. The name of the author may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + ---------------------------------------------------------------- + + Notice that the above BSD-style license applies to this one file + (valgrind.h) only. The entire rest of Valgrind is licensed under + the terms of the GNU General Public License, version 2. See the + COPYING file in the source distribution for details. + + ---------------------------------------------------------------- +*/ + + +/* This file is for inclusion into client (your!) code. + + You can use these macros to manipulate and query Valgrind's + execution inside your own programs. + + The resulting executables will still run without Valgrind, just a + little bit more slowly than they otherwise would, but otherwise + unchanged. When not running on valgrind, each client request + consumes very few (eg. 7) instructions, so the resulting performance + loss is negligible unless you plan to execute client requests + millions of times per second. Nevertheless, if that is still a + problem, you can compile with the NVALGRIND symbol defined (gcc + -DNVALGRIND) so that client requests are not even compiled in. */ + +#ifndef __VALGRIND_H +#define __VALGRIND_H + + +/* ------------------------------------------------------------------ */ +/* VERSION NUMBER OF VALGRIND */ +/* ------------------------------------------------------------------ */ + +/* Specify Valgrind's version number, so that user code can + conditionally compile based on our version number. Note that these + were introduced at version 3.6 and so do not exist in version 3.5 + or earlier. The recommended way to use them to check for "version + X.Y or later" is (eg) + +#if defined(__VALGRIND_MAJOR__) && defined(__VALGRIND_MINOR__) \ + && (__VALGRIND_MAJOR__ > 3 \ + || (__VALGRIND_MAJOR__ == 3 && __VALGRIND_MINOR__ >= 6)) +*/ +#define __VALGRIND_MAJOR__ 3 +#define __VALGRIND_MINOR__ 8 + + +#include + +/* Nb: this file might be included in a file compiled with -ansi. So + we can't use C++ style "//" comments nor the "asm" keyword (instead + use "__asm__"). */ + +/* Derive some tags indicating what the target platform is. Note + that in this file we're using the compiler's CPP symbols for + identifying architectures, which are different to the ones we use + within the rest of Valgrind. Note, __powerpc__ is active for both + 32 and 64-bit PPC, whereas __powerpc64__ is only active for the + latter (on Linux, that is). + + Misc note: how to find out what's predefined in gcc by default: + gcc -Wp,-dM somefile.c +*/ +#undef PLAT_x86_darwin +#undef PLAT_amd64_darwin +#undef PLAT_x86_win32 +#undef PLAT_x86_linux +#undef PLAT_amd64_linux +#undef PLAT_ppc32_linux +#undef PLAT_ppc64_linux +#undef PLAT_arm_linux +#undef PLAT_s390x_linux +#undef PLAT_mips32_linux + + +#if defined(__APPLE__) && defined(__i386__) +# define PLAT_x86_darwin 1 +#elif defined(__APPLE__) && defined(__x86_64__) +# define PLAT_amd64_darwin 1 +#elif defined(__MINGW32__) || defined(__CYGWIN32__) \ + || (defined(_WIN32) && defined(_M_IX86)) +# define PLAT_x86_win32 1 +#elif defined(__linux__) && defined(__i386__) +# define PLAT_x86_linux 1 +#elif defined(__linux__) && defined(__x86_64__) +# define PLAT_amd64_linux 1 +#elif defined(__linux__) && defined(__powerpc__) && !defined(__powerpc64__) +# define PLAT_ppc32_linux 1 +#elif defined(__linux__) && defined(__powerpc__) && defined(__powerpc64__) +# define PLAT_ppc64_linux 1 +#elif defined(__linux__) && defined(__arm__) +# define PLAT_arm_linux 1 +#elif defined(__linux__) && defined(__s390__) && defined(__s390x__) +# define PLAT_s390x_linux 1 +#elif defined(__linux__) && defined(__mips__) +# define PLAT_mips32_linux 1 +#else +/* If we're not compiling for our target platform, don't generate + any inline asms. */ +# if !defined(NVALGRIND) +# define NVALGRIND 1 +# endif +#endif + + +/* ------------------------------------------------------------------ */ +/* ARCHITECTURE SPECIFICS for SPECIAL INSTRUCTIONS. There is nothing */ +/* in here of use to end-users -- skip to the next section. */ +/* ------------------------------------------------------------------ */ + +/* + * VALGRIND_DO_CLIENT_REQUEST(): a statement that invokes a Valgrind client + * request. Accepts both pointers and integers as arguments. + * + * VALGRIND_DO_CLIENT_REQUEST_STMT(): a statement that invokes a Valgrind + * client request that does not return a value. + + * VALGRIND_DO_CLIENT_REQUEST_EXPR(): a C expression that invokes a Valgrind + * client request and whose value equals the client request result. Accepts + * both pointers and integers as arguments. Note that such calls are not + * necessarily pure functions -- they may have side effects. + */ + +#define VALGRIND_DO_CLIENT_REQUEST(_zzq_rlval, _zzq_default, \ + _zzq_request, _zzq_arg1, _zzq_arg2, \ + _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + do { (_zzq_rlval) = VALGRIND_DO_CLIENT_REQUEST_EXPR((_zzq_default), \ + (_zzq_request), (_zzq_arg1), (_zzq_arg2), \ + (_zzq_arg3), (_zzq_arg4), (_zzq_arg5)); } while (0) + +#define VALGRIND_DO_CLIENT_REQUEST_STMT(_zzq_request, _zzq_arg1, \ + _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + do { (void) VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ + (_zzq_request), (_zzq_arg1), (_zzq_arg2), \ + (_zzq_arg3), (_zzq_arg4), (_zzq_arg5)); } while (0) + +#if defined(NVALGRIND) + +/* Define NVALGRIND to completely remove the Valgrind magic sequence + from the compiled code (analogous to NDEBUG's effects on + assert()) */ +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + (_zzq_default) + +#else /* ! NVALGRIND */ + +/* The following defines the magic code sequences which the JITter + spots and handles magically. Don't look too closely at them as + they will rot your brain. + + The assembly code sequences for all architectures is in this one + file. This is because this file must be stand-alone, and we don't + want to have multiple files. + + For VALGRIND_DO_CLIENT_REQUEST, we must ensure that the default + value gets put in the return slot, so that everything works when + this is executed not under Valgrind. Args are passed in a memory + block, and so there's no intrinsic limit to the number that could + be passed, but it's currently five. + + The macro args are: + _zzq_rlval result lvalue + _zzq_default default value (result returned when running on real CPU) + _zzq_request request code + _zzq_arg1..5 request params + + The other two macros are used to support function wrapping, and are + a lot simpler. VALGRIND_GET_NR_CONTEXT returns the value of the + guest's NRADDR pseudo-register and whatever other information is + needed to safely run the call original from the wrapper: on + ppc64-linux, the R2 value at the divert point is also needed. This + information is abstracted into a user-visible type, OrigFn. + + VALGRIND_CALL_NOREDIR_* behaves the same as the following on the + guest, but guarantees that the branch instruction will not be + redirected: x86: call *%eax, amd64: call *%rax, ppc32/ppc64: + branch-and-link-to-r11. VALGRIND_CALL_NOREDIR is just text, not a + complete inline asm, since it needs to be combined with more magic + inline asm stuff to be useful. +*/ + +/* ------------------------- x86-{linux,darwin} ---------------- */ + +#if defined(PLAT_x86_linux) || defined(PLAT_x86_darwin) \ + || (defined(PLAT_x86_win32) && defined(__GNUC__)) + +typedef + struct { + unsigned int nraddr; /* where's the code? */ + } + OrigFn; + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "roll $3, %%edi ; roll $13, %%edi\n\t" \ + "roll $29, %%edi ; roll $19, %%edi\n\t" + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + __extension__ \ + ({volatile unsigned int _zzq_args[6]; \ + volatile unsigned int _zzq_result; \ + _zzq_args[0] = (unsigned int)(_zzq_request); \ + _zzq_args[1] = (unsigned int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned int)(_zzq_arg5); \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %EDX = client_request ( %EAX ) */ \ + "xchgl %%ebx,%%ebx" \ + : "=d" (_zzq_result) \ + : "a" (&_zzq_args[0]), "0" (_zzq_default) \ + : "cc", "memory" \ + ); \ + _zzq_result; \ + }) + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + volatile unsigned int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %EAX = guest_NRADDR */ \ + "xchgl %%ecx,%%ecx" \ + : "=a" (__addr) \ + : \ + : "cc", "memory" \ + ); \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_CALL_NOREDIR_EAX \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* call-noredir *%EAX */ \ + "xchgl %%edx,%%edx\n\t" +#endif /* PLAT_x86_linux || PLAT_x86_darwin || (PLAT_x86_win32 && __GNUC__) */ + +/* ------------------------- x86-Win32 ------------------------- */ + +#if defined(PLAT_x86_win32) && !defined(__GNUC__) + +typedef + struct { + unsigned int nraddr; /* where's the code? */ + } + OrigFn; + +#if defined(_MSC_VER) + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + __asm rol edi, 3 __asm rol edi, 13 \ + __asm rol edi, 29 __asm rol edi, 19 + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + valgrind_do_client_request_expr((uintptr_t)(_zzq_default), \ + (uintptr_t)(_zzq_request), (uintptr_t)(_zzq_arg1), \ + (uintptr_t)(_zzq_arg2), (uintptr_t)(_zzq_arg3), \ + (uintptr_t)(_zzq_arg4), (uintptr_t)(_zzq_arg5)) + +static __inline uintptr_t +valgrind_do_client_request_expr(uintptr_t _zzq_default, uintptr_t _zzq_request, + uintptr_t _zzq_arg1, uintptr_t _zzq_arg2, + uintptr_t _zzq_arg3, uintptr_t _zzq_arg4, + uintptr_t _zzq_arg5) +{ + volatile uintptr_t _zzq_args[6]; + volatile unsigned int _zzq_result; + _zzq_args[0] = (uintptr_t)(_zzq_request); + _zzq_args[1] = (uintptr_t)(_zzq_arg1); + _zzq_args[2] = (uintptr_t)(_zzq_arg2); + _zzq_args[3] = (uintptr_t)(_zzq_arg3); + _zzq_args[4] = (uintptr_t)(_zzq_arg4); + _zzq_args[5] = (uintptr_t)(_zzq_arg5); + __asm { __asm lea eax, _zzq_args __asm mov edx, _zzq_default + __SPECIAL_INSTRUCTION_PREAMBLE + /* %EDX = client_request ( %EAX ) */ + __asm xchg ebx,ebx + __asm mov _zzq_result, edx + } + return _zzq_result; +} + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + volatile unsigned int __addr; \ + __asm { __SPECIAL_INSTRUCTION_PREAMBLE \ + /* %EAX = guest_NRADDR */ \ + __asm xchg ecx,ecx \ + __asm mov __addr, eax \ + } \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_CALL_NOREDIR_EAX ERROR + +#else +#error Unsupported compiler. +#endif + +#endif /* PLAT_x86_win32 */ + +/* ------------------------ amd64-{linux,darwin} --------------- */ + +#if defined(PLAT_amd64_linux) || defined(PLAT_amd64_darwin) + +typedef + struct { + unsigned long long int nraddr; /* where's the code? */ + } + OrigFn; + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "rolq $3, %%rdi ; rolq $13, %%rdi\n\t" \ + "rolq $61, %%rdi ; rolq $51, %%rdi\n\t" + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + __extension__ \ + ({ volatile unsigned long long int _zzq_args[6]; \ + volatile unsigned long long int _zzq_result; \ + _zzq_args[0] = (unsigned long long int)(_zzq_request); \ + _zzq_args[1] = (unsigned long long int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned long long int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned long long int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned long long int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned long long int)(_zzq_arg5); \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %RDX = client_request ( %RAX ) */ \ + "xchgq %%rbx,%%rbx" \ + : "=d" (_zzq_result) \ + : "a" (&_zzq_args[0]), "0" (_zzq_default) \ + : "cc", "memory" \ + ); \ + _zzq_result; \ + }) + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + volatile unsigned long long int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %RAX = guest_NRADDR */ \ + "xchgq %%rcx,%%rcx" \ + : "=a" (__addr) \ + : \ + : "cc", "memory" \ + ); \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_CALL_NOREDIR_RAX \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* call-noredir *%RAX */ \ + "xchgq %%rdx,%%rdx\n\t" +#endif /* PLAT_amd64_linux || PLAT_amd64_darwin */ + +/* ------------------------ ppc32-linux ------------------------ */ + +#if defined(PLAT_ppc32_linux) + +typedef + struct { + unsigned int nraddr; /* where's the code? */ + } + OrigFn; + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "rlwinm 0,0,3,0,0 ; rlwinm 0,0,13,0,0\n\t" \ + "rlwinm 0,0,29,0,0 ; rlwinm 0,0,19,0,0\n\t" + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + \ + __extension__ \ + ({ unsigned int _zzq_args[6]; \ + unsigned int _zzq_result; \ + unsigned int* _zzq_ptr; \ + _zzq_args[0] = (unsigned int)(_zzq_request); \ + _zzq_args[1] = (unsigned int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned int)(_zzq_arg5); \ + _zzq_ptr = _zzq_args; \ + __asm__ volatile("mr 3,%1\n\t" /*default*/ \ + "mr 4,%2\n\t" /*ptr*/ \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = client_request ( %R4 ) */ \ + "or 1,1,1\n\t" \ + "mr %0,3" /*result*/ \ + : "=b" (_zzq_result) \ + : "b" (_zzq_default), "b" (_zzq_ptr) \ + : "cc", "memory", "r3", "r4"); \ + _zzq_result; \ + }) + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + unsigned int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = guest_NRADDR */ \ + "or 2,2,2\n\t" \ + "mr %0,3" \ + : "=b" (__addr) \ + : \ + : "cc", "memory", "r3" \ + ); \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* branch-and-link-to-noredir *%R11 */ \ + "or 3,3,3\n\t" +#endif /* PLAT_ppc32_linux */ + +/* ------------------------ ppc64-linux ------------------------ */ + +#if defined(PLAT_ppc64_linux) + +typedef + struct { + unsigned long long int nraddr; /* where's the code? */ + unsigned long long int r2; /* what tocptr do we need? */ + } + OrigFn; + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "rotldi 0,0,3 ; rotldi 0,0,13\n\t" \ + "rotldi 0,0,61 ; rotldi 0,0,51\n\t" + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + \ + __extension__ \ + ({ unsigned long long int _zzq_args[6]; \ + unsigned long long int _zzq_result; \ + unsigned long long int* _zzq_ptr; \ + _zzq_args[0] = (unsigned long long int)(_zzq_request); \ + _zzq_args[1] = (unsigned long long int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned long long int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned long long int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned long long int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned long long int)(_zzq_arg5); \ + _zzq_ptr = _zzq_args; \ + __asm__ volatile("mr 3,%1\n\t" /*default*/ \ + "mr 4,%2\n\t" /*ptr*/ \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = client_request ( %R4 ) */ \ + "or 1,1,1\n\t" \ + "mr %0,3" /*result*/ \ + : "=b" (_zzq_result) \ + : "b" (_zzq_default), "b" (_zzq_ptr) \ + : "cc", "memory", "r3", "r4"); \ + _zzq_result; \ + }) + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + unsigned long long int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = guest_NRADDR */ \ + "or 2,2,2\n\t" \ + "mr %0,3" \ + : "=b" (__addr) \ + : \ + : "cc", "memory", "r3" \ + ); \ + _zzq_orig->nraddr = __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = guest_NRADDR_GPR2 */ \ + "or 4,4,4\n\t" \ + "mr %0,3" \ + : "=b" (__addr) \ + : \ + : "cc", "memory", "r3" \ + ); \ + _zzq_orig->r2 = __addr; \ + } + +#define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* branch-and-link-to-noredir *%R11 */ \ + "or 3,3,3\n\t" + +#endif /* PLAT_ppc64_linux */ + +/* ------------------------- arm-linux ------------------------- */ + +#if defined(PLAT_arm_linux) + +typedef + struct { + unsigned int nraddr; /* where's the code? */ + } + OrigFn; + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "mov r12, r12, ror #3 ; mov r12, r12, ror #13 \n\t" \ + "mov r12, r12, ror #29 ; mov r12, r12, ror #19 \n\t" + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + \ + __extension__ \ + ({volatile unsigned int _zzq_args[6]; \ + volatile unsigned int _zzq_result; \ + _zzq_args[0] = (unsigned int)(_zzq_request); \ + _zzq_args[1] = (unsigned int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned int)(_zzq_arg5); \ + __asm__ volatile("mov r3, %1\n\t" /*default*/ \ + "mov r4, %2\n\t" /*ptr*/ \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* R3 = client_request ( R4 ) */ \ + "orr r10, r10, r10\n\t" \ + "mov %0, r3" /*result*/ \ + : "=r" (_zzq_result) \ + : "r" (_zzq_default), "r" (&_zzq_args[0]) \ + : "cc","memory", "r3", "r4"); \ + _zzq_result; \ + }) + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + unsigned int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* R3 = guest_NRADDR */ \ + "orr r11, r11, r11\n\t" \ + "mov %0, r3" \ + : "=r" (__addr) \ + : \ + : "cc", "memory", "r3" \ + ); \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* branch-and-link-to-noredir *%R4 */ \ + "orr r12, r12, r12\n\t" + +#endif /* PLAT_arm_linux */ + +/* ------------------------ s390x-linux ------------------------ */ + +#if defined(PLAT_s390x_linux) + +typedef + struct { + unsigned long long int nraddr; /* where's the code? */ + } + OrigFn; + +/* __SPECIAL_INSTRUCTION_PREAMBLE will be used to identify Valgrind specific + * code. This detection is implemented in platform specific toIR.c + * (e.g. VEX/priv/guest_s390_decoder.c). + */ +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "lr 15,15\n\t" \ + "lr 1,1\n\t" \ + "lr 2,2\n\t" \ + "lr 3,3\n\t" + +#define __CLIENT_REQUEST_CODE "lr 2,2\n\t" +#define __GET_NR_CONTEXT_CODE "lr 3,3\n\t" +#define __CALL_NO_REDIR_CODE "lr 4,4\n\t" + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + __extension__ \ + ({volatile unsigned long long int _zzq_args[6]; \ + volatile unsigned long long int _zzq_result; \ + _zzq_args[0] = (unsigned long long int)(_zzq_request); \ + _zzq_args[1] = (unsigned long long int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned long long int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned long long int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned long long int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned long long int)(_zzq_arg5); \ + __asm__ volatile(/* r2 = args */ \ + "lgr 2,%1\n\t" \ + /* r3 = default */ \ + "lgr 3,%2\n\t" \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + __CLIENT_REQUEST_CODE \ + /* results = r3 */ \ + "lgr %0, 3\n\t" \ + : "=d" (_zzq_result) \ + : "a" (&_zzq_args[0]), "0" (_zzq_default) \ + : "cc", "2", "3", "memory" \ + ); \ + _zzq_result; \ + }) + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + volatile unsigned long long int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + __GET_NR_CONTEXT_CODE \ + "lgr %0, 3\n\t" \ + : "=a" (__addr) \ + : \ + : "cc", "3", "memory" \ + ); \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_CALL_NOREDIR_R1 \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + __CALL_NO_REDIR_CODE + +#endif /* PLAT_s390x_linux */ + +/* ------------------------- mips32-linux ---------------- */ + +#if defined(PLAT_mips32_linux) + +typedef + struct { + unsigned int nraddr; /* where's the code? */ + } + OrigFn; + +/* .word 0x342 + * .word 0x742 + * .word 0xC2 + * .word 0x4C2*/ +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "srl $0, $0, 13\n\t" \ + "srl $0, $0, 29\n\t" \ + "srl $0, $0, 3\n\t" \ + "srl $0, $0, 19\n\t" + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + __extension__ \ + ({ volatile unsigned int _zzq_args[6]; \ + volatile unsigned int _zzq_result; \ + _zzq_args[0] = (unsigned int)(_zzq_request); \ + _zzq_args[1] = (unsigned int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned int)(_zzq_arg5); \ + __asm__ volatile("move $11, %1\n\t" /*default*/ \ + "move $12, %2\n\t" /*ptr*/ \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* T3 = client_request ( T4 ) */ \ + "or $13, $13, $13\n\t" \ + "move %0, $11\n\t" /*result*/ \ + : "=r" (_zzq_result) \ + : "r" (_zzq_default), "r" (&_zzq_args[0]) \ + : "cc","memory", "t3", "t4"); \ + _zzq_result; \ + }) + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + volatile unsigned int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %t9 = guest_NRADDR */ \ + "or $14, $14, $14\n\t" \ + "move %0, $11" /*result*/ \ + : "=r" (__addr) \ + : \ + : "cc", "memory" , "t3" \ + ); \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_CALL_NOREDIR_T9 \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* call-noredir *%t9 */ \ + "or $15, $15, $15\n\t" +#endif /* PLAT_mips32_linux */ + +/* Insert assembly code for other platforms here... */ + +#endif /* NVALGRIND */ + + +/* ------------------------------------------------------------------ */ +/* PLATFORM SPECIFICS for FUNCTION WRAPPING. This is all very */ +/* ugly. It's the least-worst tradeoff I can think of. */ +/* ------------------------------------------------------------------ */ + +/* This section defines magic (a.k.a appalling-hack) macros for doing + guaranteed-no-redirection macros, so as to get from function + wrappers to the functions they are wrapping. The whole point is to + construct standard call sequences, but to do the call itself with a + special no-redirect call pseudo-instruction that the JIT + understands and handles specially. This section is long and + repetitious, and I can't see a way to make it shorter. + + The naming scheme is as follows: + + CALL_FN_{W,v}_{v,W,WW,WWW,WWWW,5W,6W,7W,etc} + + 'W' stands for "word" and 'v' for "void". Hence there are + different macros for calling arity 0, 1, 2, 3, 4, etc, functions, + and for each, the possibility of returning a word-typed result, or + no result. +*/ + +/* Use these to write the name of your wrapper. NOTE: duplicates + VG_WRAP_FUNCTION_Z{U,Z} in pub_tool_redir.h. NOTE also: inserts + the default behaviour equivalance class tag "0000" into the name. + See pub_tool_redir.h for details -- normally you don't need to + think about this, though. */ + +/* Use an extra level of macroisation so as to ensure the soname/fnname + args are fully macro-expanded before pasting them together. */ +#define VG_CONCAT4(_aa,_bb,_cc,_dd) _aa##_bb##_cc##_dd + +#define I_WRAP_SONAME_FNNAME_ZU(soname,fnname) \ + VG_CONCAT4(_vgw00000ZU_,soname,_,fnname) + +#define I_WRAP_SONAME_FNNAME_ZZ(soname,fnname) \ + VG_CONCAT4(_vgw00000ZZ_,soname,_,fnname) + +/* Use this macro from within a wrapper function to collect the + context (address and possibly other info) of the original function. + Once you have that you can then use it in one of the CALL_FN_ + macros. The type of the argument _lval is OrigFn. */ +#define VALGRIND_GET_ORIG_FN(_lval) VALGRIND_GET_NR_CONTEXT(_lval) + +/* Also provide end-user facilities for function replacement, rather + than wrapping. A replacement function differs from a wrapper in + that it has no way to get hold of the original function being + called, and hence no way to call onwards to it. In a replacement + function, VALGRIND_GET_ORIG_FN always returns zero. */ + +#define I_REPLACE_SONAME_FNNAME_ZU(soname,fnname) \ + VG_CONCAT4(_vgr00000ZU_,soname,_,fnname) + +#define I_REPLACE_SONAME_FNNAME_ZZ(soname,fnname) \ + VG_CONCAT4(_vgr00000ZZ_,soname,_,fnname) + +/* Derivatives of the main macros below, for calling functions + returning void. */ + +#define CALL_FN_v_v(fnptr) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_v(_junk,fnptr); } while (0) + +#define CALL_FN_v_W(fnptr, arg1) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_W(_junk,fnptr,arg1); } while (0) + +#define CALL_FN_v_WW(fnptr, arg1,arg2) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_WW(_junk,fnptr,arg1,arg2); } while (0) + +#define CALL_FN_v_WWW(fnptr, arg1,arg2,arg3) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_WWW(_junk,fnptr,arg1,arg2,arg3); } while (0) + +#define CALL_FN_v_WWWW(fnptr, arg1,arg2,arg3,arg4) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_WWWW(_junk,fnptr,arg1,arg2,arg3,arg4); } while (0) + +#define CALL_FN_v_5W(fnptr, arg1,arg2,arg3,arg4,arg5) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_5W(_junk,fnptr,arg1,arg2,arg3,arg4,arg5); } while (0) + +#define CALL_FN_v_6W(fnptr, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_6W(_junk,fnptr,arg1,arg2,arg3,arg4,arg5,arg6); } while (0) + +#define CALL_FN_v_7W(fnptr, arg1,arg2,arg3,arg4,arg5,arg6,arg7) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_7W(_junk,fnptr,arg1,arg2,arg3,arg4,arg5,arg6,arg7); } while (0) + +/* ------------------------- x86-{linux,darwin} ---------------- */ + +#if defined(PLAT_x86_linux) || defined(PLAT_x86_darwin) + +/* These regs are trashed by the hidden call. No need to mention eax + as gcc can already see that, plus causes gcc to bomb. */ +#define __CALLER_SAVED_REGS /*"eax"*/ "ecx", "edx" + +/* Macros to save and align the stack before making a function + call and restore it afterwards as gcc may not keep the stack + pointer aligned if it doesn't realise calls are being made + to other functions. */ + +#define VALGRIND_ALIGN_STACK \ + "movl %%esp,%%edi\n\t" \ + "andl $0xfffffff0,%%esp\n\t" +#define VALGRIND_RESTORE_STACK \ + "movl %%edi,%%esp\n\t" + +/* These CALL_FN_ macros assume that on x86-linux, sizeof(unsigned + long) == 4. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[1]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[2]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "subl $12, %%esp\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "subl $8, %%esp\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[4]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "subl $4, %%esp\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[5]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[6]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "subl $12, %%esp\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[7]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "subl $8, %%esp\n\t" \ + "pushl 24(%%eax)\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[8]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "subl $4, %%esp\n\t" \ + "pushl 28(%%eax)\n\t" \ + "pushl 24(%%eax)\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[9]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "pushl 32(%%eax)\n\t" \ + "pushl 28(%%eax)\n\t" \ + "pushl 24(%%eax)\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[10]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "subl $12, %%esp\n\t" \ + "pushl 36(%%eax)\n\t" \ + "pushl 32(%%eax)\n\t" \ + "pushl 28(%%eax)\n\t" \ + "pushl 24(%%eax)\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[11]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "subl $8, %%esp\n\t" \ + "pushl 40(%%eax)\n\t" \ + "pushl 36(%%eax)\n\t" \ + "pushl 32(%%eax)\n\t" \ + "pushl 28(%%eax)\n\t" \ + "pushl 24(%%eax)\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ + arg6,arg7,arg8,arg9,arg10, \ + arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[12]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "subl $4, %%esp\n\t" \ + "pushl 44(%%eax)\n\t" \ + "pushl 40(%%eax)\n\t" \ + "pushl 36(%%eax)\n\t" \ + "pushl 32(%%eax)\n\t" \ + "pushl 28(%%eax)\n\t" \ + "pushl 24(%%eax)\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ + arg6,arg7,arg8,arg9,arg10, \ + arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[13]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + _argvec[12] = (unsigned long)(arg12); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "pushl 48(%%eax)\n\t" \ + "pushl 44(%%eax)\n\t" \ + "pushl 40(%%eax)\n\t" \ + "pushl 36(%%eax)\n\t" \ + "pushl 32(%%eax)\n\t" \ + "pushl 28(%%eax)\n\t" \ + "pushl 24(%%eax)\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_x86_linux || PLAT_x86_darwin */ + +/* ------------------------ amd64-{linux,darwin} --------------- */ + +#if defined(PLAT_amd64_linux) || defined(PLAT_amd64_darwin) + +/* ARGREGS: rdi rsi rdx rcx r8 r9 (the rest on stack in R-to-L order) */ + +/* These regs are trashed by the hidden call. */ +#define __CALLER_SAVED_REGS /*"rax",*/ "rcx", "rdx", "rsi", \ + "rdi", "r8", "r9", "r10", "r11" + +/* This is all pretty complex. It's so as to make stack unwinding + work reliably. See bug 243270. The basic problem is the sub and + add of 128 of %rsp in all of the following macros. If gcc believes + the CFA is in %rsp, then unwinding may fail, because what's at the + CFA is not what gcc "expected" when it constructs the CFIs for the + places where the macros are instantiated. + + But we can't just add a CFI annotation to increase the CFA offset + by 128, to match the sub of 128 from %rsp, because we don't know + whether gcc has chosen %rsp as the CFA at that point, or whether it + has chosen some other register (eg, %rbp). In the latter case, + adding a CFI annotation to change the CFA offset is simply wrong. + + So the solution is to get hold of the CFA using + __builtin_dwarf_cfa(), put it in a known register, and add a + CFI annotation to say what the register is. We choose %rbp for + this (perhaps perversely), because: + + (1) %rbp is already subject to unwinding. If a new register was + chosen then the unwinder would have to unwind it in all stack + traces, which is expensive, and + + (2) %rbp is already subject to precise exception updates in the + JIT. If a new register was chosen, we'd have to have precise + exceptions for it too, which reduces performance of the + generated code. + + However .. one extra complication. We can't just whack the result + of __builtin_dwarf_cfa() into %rbp and then add %rbp to the + list of trashed registers at the end of the inline assembly + fragments; gcc won't allow %rbp to appear in that list. Hence + instead we need to stash %rbp in %r15 for the duration of the asm, + and say that %r15 is trashed instead. gcc seems happy to go with + that. + + Oh .. and this all needs to be conditionalised so that it is + unchanged from before this commit, when compiled with older gccs + that don't support __builtin_dwarf_cfa. Furthermore, since + this header file is freestanding, it has to be independent of + config.h, and so the following conditionalisation cannot depend on + configure time checks. + + Although it's not clear from + 'defined(__GNUC__) && defined(__GCC_HAVE_DWARF2_CFI_ASM)', + this expression excludes Darwin. + .cfi directives in Darwin assembly appear to be completely + different and I haven't investigated how they work. + + For even more entertainment value, note we have to use the + completely undocumented __builtin_dwarf_cfa(), which appears to + really compute the CFA, whereas __builtin_frame_address(0) claims + to but actually doesn't. See + https://bugs.kde.org/show_bug.cgi?id=243270#c47 +*/ +#if defined(__GNUC__) && defined(__GCC_HAVE_DWARF2_CFI_ASM) +# define __FRAME_POINTER \ + ,"r"(__builtin_dwarf_cfa()) +# define VALGRIND_CFI_PROLOGUE \ + "movq %%rbp, %%r15\n\t" \ + "movq %2, %%rbp\n\t" \ + ".cfi_remember_state\n\t" \ + ".cfi_def_cfa rbp, 0\n\t" +# define VALGRIND_CFI_EPILOGUE \ + "movq %%r15, %%rbp\n\t" \ + ".cfi_restore_state\n\t" +#else +# define __FRAME_POINTER +# define VALGRIND_CFI_PROLOGUE +# define VALGRIND_CFI_EPILOGUE +#endif + +/* Macros to save and align the stack before making a function + call and restore it afterwards as gcc may not keep the stack + pointer aligned if it doesn't realise calls are being made + to other functions. */ + +#define VALGRIND_ALIGN_STACK \ + "movq %%rsp,%%r14\n\t" \ + "andq $0xfffffffffffffff0,%%rsp\n\t" +#define VALGRIND_RESTORE_STACK \ + "movq %%r14,%%rsp\n\t" + +/* These CALL_FN_ macros assume that on amd64-linux, sizeof(unsigned + long) == 8. */ + +/* NB 9 Sept 07. There is a nasty kludge here in all these CALL_FN_ + macros. In order not to trash the stack redzone, we need to drop + %rsp by 128 before the hidden call, and restore afterwards. The + nastyness is that it is only by luck that the stack still appears + to be unwindable during the hidden call - since then the behaviour + of any routine using this macro does not match what the CFI data + says. Sigh. + + Why is this important? Imagine that a wrapper has a stack + allocated local, and passes to the hidden call, a pointer to it. + Because gcc does not know about the hidden call, it may allocate + that local in the redzone. Unfortunately the hidden call may then + trash it before it comes to use it. So we must step clear of the + redzone, for the duration of the hidden call, to make it safe. + + Probably the same problem afflicts the other redzone-style ABIs too + (ppc64-linux); but for those, the stack is + self describing (none of this CFI nonsense) so at least messing + with the stack pointer doesn't give a danger of non-unwindable + stack. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[1]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $128,%%rsp\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[2]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $128,%%rsp\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $128,%%rsp\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[4]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $128,%%rsp\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[5]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $128,%%rsp\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[6]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $128,%%rsp\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[7]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $128,%%rsp\n\t" \ + "movq 48(%%rax), %%r9\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[8]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $136,%%rsp\n\t" \ + "pushq 56(%%rax)\n\t" \ + "movq 48(%%rax), %%r9\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[9]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $128,%%rsp\n\t" \ + "pushq 64(%%rax)\n\t" \ + "pushq 56(%%rax)\n\t" \ + "movq 48(%%rax), %%r9\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[10]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $136,%%rsp\n\t" \ + "pushq 72(%%rax)\n\t" \ + "pushq 64(%%rax)\n\t" \ + "pushq 56(%%rax)\n\t" \ + "movq 48(%%rax), %%r9\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[11]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $128,%%rsp\n\t" \ + "pushq 80(%%rax)\n\t" \ + "pushq 72(%%rax)\n\t" \ + "pushq 64(%%rax)\n\t" \ + "pushq 56(%%rax)\n\t" \ + "movq 48(%%rax), %%r9\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[12]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $136,%%rsp\n\t" \ + "pushq 88(%%rax)\n\t" \ + "pushq 80(%%rax)\n\t" \ + "pushq 72(%%rax)\n\t" \ + "pushq 64(%%rax)\n\t" \ + "pushq 56(%%rax)\n\t" \ + "movq 48(%%rax), %%r9\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[13]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + _argvec[12] = (unsigned long)(arg12); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $128,%%rsp\n\t" \ + "pushq 96(%%rax)\n\t" \ + "pushq 88(%%rax)\n\t" \ + "pushq 80(%%rax)\n\t" \ + "pushq 72(%%rax)\n\t" \ + "pushq 64(%%rax)\n\t" \ + "pushq 56(%%rax)\n\t" \ + "movq 48(%%rax), %%r9\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_amd64_linux || PLAT_amd64_darwin */ + +/* ------------------------ ppc32-linux ------------------------ */ + +#if defined(PLAT_ppc32_linux) + +/* This is useful for finding out about the on-stack stuff: + + extern int f9 ( int,int,int,int,int,int,int,int,int ); + extern int f10 ( int,int,int,int,int,int,int,int,int,int ); + extern int f11 ( int,int,int,int,int,int,int,int,int,int,int ); + extern int f12 ( int,int,int,int,int,int,int,int,int,int,int,int ); + + int g9 ( void ) { + return f9(11,22,33,44,55,66,77,88,99); + } + int g10 ( void ) { + return f10(11,22,33,44,55,66,77,88,99,110); + } + int g11 ( void ) { + return f11(11,22,33,44,55,66,77,88,99,110,121); + } + int g12 ( void ) { + return f12(11,22,33,44,55,66,77,88,99,110,121,132); + } +*/ + +/* ARGREGS: r3 r4 r5 r6 r7 r8 r9 r10 (the rest on stack somewhere) */ + +/* These regs are trashed by the hidden call. */ +#define __CALLER_SAVED_REGS \ + "lr", "ctr", "xer", \ + "cr0", "cr1", "cr2", "cr3", "cr4", "cr5", "cr6", "cr7", \ + "r0", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", \ + "r11", "r12", "r13" + +/* Macros to save and align the stack before making a function + call and restore it afterwards as gcc may not keep the stack + pointer aligned if it doesn't realise calls are being made + to other functions. */ + +#define VALGRIND_ALIGN_STACK \ + "mr 28,1\n\t" \ + "rlwinm 1,1,0,0,27\n\t" +#define VALGRIND_RESTORE_STACK \ + "mr 1,28\n\t" + +/* These CALL_FN_ macros assume that on ppc32-linux, + sizeof(unsigned long) == 4. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[1]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[2]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[4]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[5]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[6]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[7]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 8,24(11)\n\t" \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[8]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 8,24(11)\n\t" \ + "lwz 9,28(11)\n\t" \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[9]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 8,24(11)\n\t" \ + "lwz 9,28(11)\n\t" \ + "lwz 10,32(11)\n\t" /* arg8->r10 */ \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[10]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + _argvec[9] = (unsigned long)arg9; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "addi 1,1,-16\n\t" \ + /* arg9 */ \ + "lwz 3,36(11)\n\t" \ + "stw 3,8(1)\n\t" \ + /* args1-8 */ \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 8,24(11)\n\t" \ + "lwz 9,28(11)\n\t" \ + "lwz 10,32(11)\n\t" /* arg8->r10 */ \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[11]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + _argvec[9] = (unsigned long)arg9; \ + _argvec[10] = (unsigned long)arg10; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "addi 1,1,-16\n\t" \ + /* arg10 */ \ + "lwz 3,40(11)\n\t" \ + "stw 3,12(1)\n\t" \ + /* arg9 */ \ + "lwz 3,36(11)\n\t" \ + "stw 3,8(1)\n\t" \ + /* args1-8 */ \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 8,24(11)\n\t" \ + "lwz 9,28(11)\n\t" \ + "lwz 10,32(11)\n\t" /* arg8->r10 */ \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[12]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + _argvec[9] = (unsigned long)arg9; \ + _argvec[10] = (unsigned long)arg10; \ + _argvec[11] = (unsigned long)arg11; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "addi 1,1,-32\n\t" \ + /* arg11 */ \ + "lwz 3,44(11)\n\t" \ + "stw 3,16(1)\n\t" \ + /* arg10 */ \ + "lwz 3,40(11)\n\t" \ + "stw 3,12(1)\n\t" \ + /* arg9 */ \ + "lwz 3,36(11)\n\t" \ + "stw 3,8(1)\n\t" \ + /* args1-8 */ \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 8,24(11)\n\t" \ + "lwz 9,28(11)\n\t" \ + "lwz 10,32(11)\n\t" /* arg8->r10 */ \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[13]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + _argvec[9] = (unsigned long)arg9; \ + _argvec[10] = (unsigned long)arg10; \ + _argvec[11] = (unsigned long)arg11; \ + _argvec[12] = (unsigned long)arg12; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "addi 1,1,-32\n\t" \ + /* arg12 */ \ + "lwz 3,48(11)\n\t" \ + "stw 3,20(1)\n\t" \ + /* arg11 */ \ + "lwz 3,44(11)\n\t" \ + "stw 3,16(1)\n\t" \ + /* arg10 */ \ + "lwz 3,40(11)\n\t" \ + "stw 3,12(1)\n\t" \ + /* arg9 */ \ + "lwz 3,36(11)\n\t" \ + "stw 3,8(1)\n\t" \ + /* args1-8 */ \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 8,24(11)\n\t" \ + "lwz 9,28(11)\n\t" \ + "lwz 10,32(11)\n\t" /* arg8->r10 */ \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_ppc32_linux */ + +/* ------------------------ ppc64-linux ------------------------ */ + +#if defined(PLAT_ppc64_linux) + +/* ARGREGS: r3 r4 r5 r6 r7 r8 r9 r10 (the rest on stack somewhere) */ + +/* These regs are trashed by the hidden call. */ +#define __CALLER_SAVED_REGS \ + "lr", "ctr", "xer", \ + "cr0", "cr1", "cr2", "cr3", "cr4", "cr5", "cr6", "cr7", \ + "r0", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", \ + "r11", "r12", "r13" + +/* Macros to save and align the stack before making a function + call and restore it afterwards as gcc may not keep the stack + pointer aligned if it doesn't realise calls are being made + to other functions. */ + +#define VALGRIND_ALIGN_STACK \ + "mr 28,1\n\t" \ + "rldicr 1,1,0,59\n\t" +#define VALGRIND_RESTORE_STACK \ + "mr 1,28\n\t" + +/* These CALL_FN_ macros assume that on ppc64-linux, sizeof(unsigned + long) == 8. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+0]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+1]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+2]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+3]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+4]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+5]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+6]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+7]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+8]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 10, 64(11)\n\t" /* arg8->r10 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+9]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "addi 1,1,-128\n\t" /* expand stack frame */ \ + /* arg9 */ \ + "ld 3,72(11)\n\t" \ + "std 3,112(1)\n\t" \ + /* args1-8 */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 10, 64(11)\n\t" /* arg8->r10 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+10]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + _argvec[2+10] = (unsigned long)arg10; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "addi 1,1,-128\n\t" /* expand stack frame */ \ + /* arg10 */ \ + "ld 3,80(11)\n\t" \ + "std 3,120(1)\n\t" \ + /* arg9 */ \ + "ld 3,72(11)\n\t" \ + "std 3,112(1)\n\t" \ + /* args1-8 */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 10, 64(11)\n\t" /* arg8->r10 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+11]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + _argvec[2+10] = (unsigned long)arg10; \ + _argvec[2+11] = (unsigned long)arg11; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "addi 1,1,-144\n\t" /* expand stack frame */ \ + /* arg11 */ \ + "ld 3,88(11)\n\t" \ + "std 3,128(1)\n\t" \ + /* arg10 */ \ + "ld 3,80(11)\n\t" \ + "std 3,120(1)\n\t" \ + /* arg9 */ \ + "ld 3,72(11)\n\t" \ + "std 3,112(1)\n\t" \ + /* args1-8 */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 10, 64(11)\n\t" /* arg8->r10 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+12]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + _argvec[2+10] = (unsigned long)arg10; \ + _argvec[2+11] = (unsigned long)arg11; \ + _argvec[2+12] = (unsigned long)arg12; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "addi 1,1,-144\n\t" /* expand stack frame */ \ + /* arg12 */ \ + "ld 3,96(11)\n\t" \ + "std 3,136(1)\n\t" \ + /* arg11 */ \ + "ld 3,88(11)\n\t" \ + "std 3,128(1)\n\t" \ + /* arg10 */ \ + "ld 3,80(11)\n\t" \ + "std 3,120(1)\n\t" \ + /* arg9 */ \ + "ld 3,72(11)\n\t" \ + "std 3,112(1)\n\t" \ + /* args1-8 */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 10, 64(11)\n\t" /* arg8->r10 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_ppc64_linux */ + +/* ------------------------- arm-linux ------------------------- */ + +#if defined(PLAT_arm_linux) + +/* These regs are trashed by the hidden call. */ +#define __CALLER_SAVED_REGS "r0", "r1", "r2", "r3","r4","r14" + +/* Macros to save and align the stack before making a function + call and restore it afterwards as gcc may not keep the stack + pointer aligned if it doesn't realise calls are being made + to other functions. */ + +/* This is a bit tricky. We store the original stack pointer in r10 + as it is callee-saves. gcc doesn't allow the use of r11 for some + reason. Also, we can't directly "bic" the stack pointer in thumb + mode since r13 isn't an allowed register number in that context. + So use r4 as a temporary, since that is about to get trashed + anyway, just after each use of this macro. Side effect is we need + to be very careful about any future changes, since + VALGRIND_ALIGN_STACK simply assumes r4 is usable. */ +#define VALGRIND_ALIGN_STACK \ + "mov r10, sp\n\t" \ + "mov r4, sp\n\t" \ + "bic r4, r4, #7\n\t" \ + "mov sp, r4\n\t" +#define VALGRIND_RESTORE_STACK \ + "mov sp, r10\n\t" + +/* These CALL_FN_ macros assume that on arm-linux, sizeof(unsigned + long) == 4. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[1]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[2]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[4]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[5]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[6]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "sub sp, sp, #4 \n\t" \ + "ldr r0, [%1, #20] \n\t" \ + "push {r0} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[7]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr r0, [%1, #20] \n\t" \ + "ldr r1, [%1, #24] \n\t" \ + "push {r0, r1} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[8]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "sub sp, sp, #4 \n\t" \ + "ldr r0, [%1, #20] \n\t" \ + "ldr r1, [%1, #24] \n\t" \ + "ldr r2, [%1, #28] \n\t" \ + "push {r0, r1, r2} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[9]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr r0, [%1, #20] \n\t" \ + "ldr r1, [%1, #24] \n\t" \ + "ldr r2, [%1, #28] \n\t" \ + "ldr r3, [%1, #32] \n\t" \ + "push {r0, r1, r2, r3} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[10]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "sub sp, sp, #4 \n\t" \ + "ldr r0, [%1, #20] \n\t" \ + "ldr r1, [%1, #24] \n\t" \ + "ldr r2, [%1, #28] \n\t" \ + "ldr r3, [%1, #32] \n\t" \ + "ldr r4, [%1, #36] \n\t" \ + "push {r0, r1, r2, r3, r4} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[11]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr r0, [%1, #40] \n\t" \ + "push {r0} \n\t" \ + "ldr r0, [%1, #20] \n\t" \ + "ldr r1, [%1, #24] \n\t" \ + "ldr r2, [%1, #28] \n\t" \ + "ldr r3, [%1, #32] \n\t" \ + "ldr r4, [%1, #36] \n\t" \ + "push {r0, r1, r2, r3, r4} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ + arg6,arg7,arg8,arg9,arg10, \ + arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[12]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "sub sp, sp, #4 \n\t" \ + "ldr r0, [%1, #40] \n\t" \ + "ldr r1, [%1, #44] \n\t" \ + "push {r0, r1} \n\t" \ + "ldr r0, [%1, #20] \n\t" \ + "ldr r1, [%1, #24] \n\t" \ + "ldr r2, [%1, #28] \n\t" \ + "ldr r3, [%1, #32] \n\t" \ + "ldr r4, [%1, #36] \n\t" \ + "push {r0, r1, r2, r3, r4} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ + arg6,arg7,arg8,arg9,arg10, \ + arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[13]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + _argvec[12] = (unsigned long)(arg12); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr r0, [%1, #40] \n\t" \ + "ldr r1, [%1, #44] \n\t" \ + "ldr r2, [%1, #48] \n\t" \ + "push {r0, r1, r2} \n\t" \ + "ldr r0, [%1, #20] \n\t" \ + "ldr r1, [%1, #24] \n\t" \ + "ldr r2, [%1, #28] \n\t" \ + "ldr r3, [%1, #32] \n\t" \ + "ldr r4, [%1, #36] \n\t" \ + "push {r0, r1, r2, r3, r4} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_arm_linux */ + +/* ------------------------- s390x-linux ------------------------- */ + +#if defined(PLAT_s390x_linux) + +/* Similar workaround as amd64 (see above), but we use r11 as frame + pointer and save the old r11 in r7. r11 might be used for + argvec, therefore we copy argvec in r1 since r1 is clobbered + after the call anyway. */ +#if defined(__GNUC__) && defined(__GCC_HAVE_DWARF2_CFI_ASM) +# define __FRAME_POINTER \ + ,"d"(__builtin_dwarf_cfa()) +# define VALGRIND_CFI_PROLOGUE \ + ".cfi_remember_state\n\t" \ + "lgr 1,%1\n\t" /* copy the argvec pointer in r1 */ \ + "lgr 7,11\n\t" \ + "lgr 11,%2\n\t" \ + ".cfi_def_cfa r11, 0\n\t" +# define VALGRIND_CFI_EPILOGUE \ + "lgr 11, 7\n\t" \ + ".cfi_restore_state\n\t" +#else +# define __FRAME_POINTER +# define VALGRIND_CFI_PROLOGUE \ + "lgr 1,%1\n\t" +# define VALGRIND_CFI_EPILOGUE +#endif + +/* Nb: On s390 the stack pointer is properly aligned *at all times* + according to the s390 GCC maintainer. (The ABI specification is not + precise in this regard.) Therefore, VALGRIND_ALIGN_STACK and + VALGRIND_RESTORE_STACK are not defined here. */ + +/* These regs are trashed by the hidden call. Note that we overwrite + r14 in s390_irgen_noredir (VEX/priv/guest_s390_irgen.c) to give the + function a proper return address. All others are ABI defined call + clobbers. */ +#define __CALLER_SAVED_REGS "0","1","2","3","4","5","14", \ + "f0","f1","f2","f3","f4","f5","f6","f7" + +/* Nb: Although r11 is modified in the asm snippets below (inside + VALGRIND_CFI_PROLOGUE) it is not listed in the clobber section, for + two reasons: + (1) r11 is restored in VALGRIND_CFI_EPILOGUE, so effectively it is not + modified + (2) GCC will complain that r11 cannot appear inside a clobber section, + when compiled with -O -fno-omit-frame-pointer + */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[1]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-160\n\t" \ + "lg 1, 0(1)\n\t" /* target->r1 */ \ + VALGRIND_CALL_NOREDIR_R1 \ + "lgr %0, 2\n\t" \ + "aghi 15,160\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=d" (_res) \ + : /*in*/ "d" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +/* The call abi has the arguments in r2-r6 and stack */ +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[2]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-160\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "lgr %0, 2\n\t" \ + "aghi 15,160\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1, arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-160\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "lgr %0, 2\n\t" \ + "aghi 15,160\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1, arg2, arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[4]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-160\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 4,24(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "lgr %0, 2\n\t" \ + "aghi 15,160\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1, arg2, arg3, arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[5]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-160\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 4,24(1)\n\t" \ + "lg 5,32(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "lgr %0, 2\n\t" \ + "aghi 15,160\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1, arg2, arg3, arg4, arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[6]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-160\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 4,24(1)\n\t" \ + "lg 5,32(1)\n\t" \ + "lg 6,40(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "lgr %0, 2\n\t" \ + "aghi 15,160\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ + arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[7]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-168\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 4,24(1)\n\t" \ + "lg 5,32(1)\n\t" \ + "lg 6,40(1)\n\t" \ + "mvc 160(8,15), 48(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "lgr %0, 2\n\t" \ + "aghi 15,168\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ + arg6, arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[8]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-176\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 4,24(1)\n\t" \ + "lg 5,32(1)\n\t" \ + "lg 6,40(1)\n\t" \ + "mvc 160(8,15), 48(1)\n\t" \ + "mvc 168(8,15), 56(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "lgr %0, 2\n\t" \ + "aghi 15,176\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ + arg6, arg7 ,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[9]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-184\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 4,24(1)\n\t" \ + "lg 5,32(1)\n\t" \ + "lg 6,40(1)\n\t" \ + "mvc 160(8,15), 48(1)\n\t" \ + "mvc 168(8,15), 56(1)\n\t" \ + "mvc 176(8,15), 64(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "lgr %0, 2\n\t" \ + "aghi 15,184\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ + arg6, arg7 ,arg8, arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[10]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + _argvec[9] = (unsigned long)arg9; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-192\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 4,24(1)\n\t" \ + "lg 5,32(1)\n\t" \ + "lg 6,40(1)\n\t" \ + "mvc 160(8,15), 48(1)\n\t" \ + "mvc 168(8,15), 56(1)\n\t" \ + "mvc 176(8,15), 64(1)\n\t" \ + "mvc 184(8,15), 72(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "lgr %0, 2\n\t" \ + "aghi 15,192\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ + arg6, arg7 ,arg8, arg9, arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[11]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + _argvec[9] = (unsigned long)arg9; \ + _argvec[10] = (unsigned long)arg10; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-200\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 4,24(1)\n\t" \ + "lg 5,32(1)\n\t" \ + "lg 6,40(1)\n\t" \ + "mvc 160(8,15), 48(1)\n\t" \ + "mvc 168(8,15), 56(1)\n\t" \ + "mvc 176(8,15), 64(1)\n\t" \ + "mvc 184(8,15), 72(1)\n\t" \ + "mvc 192(8,15), 80(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "lgr %0, 2\n\t" \ + "aghi 15,200\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ + arg6, arg7 ,arg8, arg9, arg10, arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[12]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + _argvec[9] = (unsigned long)arg9; \ + _argvec[10] = (unsigned long)arg10; \ + _argvec[11] = (unsigned long)arg11; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-208\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 4,24(1)\n\t" \ + "lg 5,32(1)\n\t" \ + "lg 6,40(1)\n\t" \ + "mvc 160(8,15), 48(1)\n\t" \ + "mvc 168(8,15), 56(1)\n\t" \ + "mvc 176(8,15), 64(1)\n\t" \ + "mvc 184(8,15), 72(1)\n\t" \ + "mvc 192(8,15), 80(1)\n\t" \ + "mvc 200(8,15), 88(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "lgr %0, 2\n\t" \ + "aghi 15,208\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ + arg6, arg7 ,arg8, arg9, arg10, arg11, arg12)\ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[13]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + _argvec[9] = (unsigned long)arg9; \ + _argvec[10] = (unsigned long)arg10; \ + _argvec[11] = (unsigned long)arg11; \ + _argvec[12] = (unsigned long)arg12; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-216\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 4,24(1)\n\t" \ + "lg 5,32(1)\n\t" \ + "lg 6,40(1)\n\t" \ + "mvc 160(8,15), 48(1)\n\t" \ + "mvc 168(8,15), 56(1)\n\t" \ + "mvc 176(8,15), 64(1)\n\t" \ + "mvc 184(8,15), 72(1)\n\t" \ + "mvc 192(8,15), 80(1)\n\t" \ + "mvc 200(8,15), 88(1)\n\t" \ + "mvc 208(8,15), 96(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "lgr %0, 2\n\t" \ + "aghi 15,216\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + + +#endif /* PLAT_s390x_linux */ + +/* ------------------------- mips-linux ------------------------- */ + +#if defined(PLAT_mips32_linux) + +/* These regs are trashed by the hidden call. */ +#define __CALLER_SAVED_REGS "$2", "$3", "$4", "$5", "$6", \ +"$7", "$8", "$9", "$10", "$11", "$12", "$13", "$14", "$15", "$24", \ +"$25", "$31" + +/* These CALL_FN_ macros assume that on mips-linux, sizeof(unsigned + long) == 4. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[1]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $gp, 0($sp) \n\t" \ + "sw $ra, 4($sp) \n\t" \ + "subu $29, $29, 16 \n\t" \ + "lw $t9, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $29, $29, 16\n\t" \ + "lw $gp, 0($sp) \n\t" \ + "lw $ra, 4($sp) \n\t" \ + "addu $29, $29, 8 \n\t" \ + "move %0, $v0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[2]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $gp, 0($sp) \n\t" \ + "sw $ra, 4($sp) \n\t" \ + "subu $29, $29, 16 \n\t" \ + "lw $a0, 4(%1) \n\t" /* arg1*/ \ + "lw $t9, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $29, $29, 16 \n\t" \ + "lw $gp, 0($sp) \n\t" \ + "lw $ra, 4($sp) \n\t" \ + "addu $29, $29, 8 \n\t" \ + "move %0, $v0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $gp, 0($sp) \n\t" \ + "sw $ra, 4($sp) \n\t" \ + "subu $29, $29, 16 \n\t" \ + "lw $a0, 4(%1) \n\t" \ + "lw $a1, 8(%1) \n\t" \ + "lw $t9, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $29, $29, 16 \n\t" \ + "lw $gp, 0($sp) \n\t" \ + "lw $ra, 4($sp) \n\t" \ + "addu $29, $29, 8 \n\t" \ + "move %0, $v0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[4]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $gp, 0($sp) \n\t" \ + "sw $ra, 4($sp) \n\t" \ + "subu $29, $29, 16 \n\t" \ + "lw $a0, 4(%1) \n\t" \ + "lw $a1, 8(%1) \n\t" \ + "lw $a2, 12(%1) \n\t" \ + "lw $t9, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $29, $29, 16 \n\t" \ + "lw $gp, 0($sp) \n\t" \ + "lw $ra, 4($sp) \n\t" \ + "addu $29, $29, 8 \n\t" \ + "move %0, $v0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[5]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $gp, 0($sp) \n\t" \ + "sw $ra, 4($sp) \n\t" \ + "subu $29, $29, 16 \n\t" \ + "lw $a0, 4(%1) \n\t" \ + "lw $a1, 8(%1) \n\t" \ + "lw $a2, 12(%1) \n\t" \ + "lw $a3, 16(%1) \n\t" \ + "lw $t9, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $29, $29, 16 \n\t" \ + "lw $gp, 0($sp) \n\t" \ + "lw $ra, 4($sp) \n\t" \ + "addu $29, $29, 8 \n\t" \ + "move %0, $v0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[6]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $gp, 0($sp) \n\t" \ + "sw $ra, 4($sp) \n\t" \ + "lw $a0, 20(%1) \n\t" \ + "subu $sp, $sp, 24\n\t" \ + "sw $a0, 16($sp) \n\t" \ + "lw $a0, 4(%1) \n\t" \ + "lw $a1, 8(%1) \n\t" \ + "lw $a2, 12(%1) \n\t" \ + "lw $a3, 16(%1) \n\t" \ + "lw $t9, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $29, $29, 24 \n\t" \ + "lw $gp, 0($sp) \n\t" \ + "lw $ra, 4($sp) \n\t" \ + "addu $sp, $sp, 8 \n\t" \ + "move %0, $v0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[7]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $gp, 0($sp) \n\t" \ + "sw $ra, 4($sp) \n\t" \ + "lw $a0, 20(%1) \n\t" \ + "subu $sp, $sp, 32\n\t" \ + "sw $a0, 16($sp) \n\t" \ + "lw $a0, 24(%1) \n\t" \ + "nop\n\t" \ + "sw $a0, 20($sp) \n\t" \ + "lw $a0, 4(%1) \n\t" \ + "lw $a1, 8(%1) \n\t" \ + "lw $a2, 12(%1) \n\t" \ + "lw $a3, 16(%1) \n\t" \ + "lw $t9, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $sp, $sp, 32 \n\t" \ + "lw $gp, 0($sp) \n\t" \ + "lw $ra, 4($sp) \n\t" \ + "addu $sp, $sp, 8 \n\t" \ + "move %0, $v0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[8]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $gp, 0($sp) \n\t" \ + "sw $ra, 4($sp) \n\t" \ + "lw $a0, 20(%1) \n\t" \ + "subu $sp, $sp, 32\n\t" \ + "sw $a0, 16($sp) \n\t" \ + "lw $a0, 24(%1) \n\t" \ + "sw $a0, 20($sp) \n\t" \ + "lw $a0, 28(%1) \n\t" \ + "sw $a0, 24($sp) \n\t" \ + "lw $a0, 4(%1) \n\t" \ + "lw $a1, 8(%1) \n\t" \ + "lw $a2, 12(%1) \n\t" \ + "lw $a3, 16(%1) \n\t" \ + "lw $t9, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $sp, $sp, 32 \n\t" \ + "lw $gp, 0($sp) \n\t" \ + "lw $ra, 4($sp) \n\t" \ + "addu $sp, $sp, 8 \n\t" \ + "move %0, $v0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[9]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $gp, 0($sp) \n\t" \ + "sw $ra, 4($sp) \n\t" \ + "lw $a0, 20(%1) \n\t" \ + "subu $sp, $sp, 40\n\t" \ + "sw $a0, 16($sp) \n\t" \ + "lw $a0, 24(%1) \n\t" \ + "sw $a0, 20($sp) \n\t" \ + "lw $a0, 28(%1) \n\t" \ + "sw $a0, 24($sp) \n\t" \ + "lw $a0, 32(%1) \n\t" \ + "sw $a0, 28($sp) \n\t" \ + "lw $a0, 4(%1) \n\t" \ + "lw $a1, 8(%1) \n\t" \ + "lw $a2, 12(%1) \n\t" \ + "lw $a3, 16(%1) \n\t" \ + "lw $t9, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $sp, $sp, 40 \n\t" \ + "lw $gp, 0($sp) \n\t" \ + "lw $ra, 4($sp) \n\t" \ + "addu $sp, $sp, 8 \n\t" \ + "move %0, $v0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[10]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $gp, 0($sp) \n\t" \ + "sw $ra, 4($sp) \n\t" \ + "lw $a0, 20(%1) \n\t" \ + "subu $sp, $sp, 40\n\t" \ + "sw $a0, 16($sp) \n\t" \ + "lw $a0, 24(%1) \n\t" \ + "sw $a0, 20($sp) \n\t" \ + "lw $a0, 28(%1) \n\t" \ + "sw $a0, 24($sp) \n\t" \ + "lw $a0, 32(%1) \n\t" \ + "sw $a0, 28($sp) \n\t" \ + "lw $a0, 36(%1) \n\t" \ + "sw $a0, 32($sp) \n\t" \ + "lw $a0, 4(%1) \n\t" \ + "lw $a1, 8(%1) \n\t" \ + "lw $a2, 12(%1) \n\t" \ + "lw $a3, 16(%1) \n\t" \ + "lw $t9, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $sp, $sp, 40 \n\t" \ + "lw $gp, 0($sp) \n\t" \ + "lw $ra, 4($sp) \n\t" \ + "addu $sp, $sp, 8 \n\t" \ + "move %0, $v0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[11]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $gp, 0($sp) \n\t" \ + "sw $ra, 4($sp) \n\t" \ + "lw $a0, 20(%1) \n\t" \ + "subu $sp, $sp, 48\n\t" \ + "sw $a0, 16($sp) \n\t" \ + "lw $a0, 24(%1) \n\t" \ + "sw $a0, 20($sp) \n\t" \ + "lw $a0, 28(%1) \n\t" \ + "sw $a0, 24($sp) \n\t" \ + "lw $a0, 32(%1) \n\t" \ + "sw $a0, 28($sp) \n\t" \ + "lw $a0, 36(%1) \n\t" \ + "sw $a0, 32($sp) \n\t" \ + "lw $a0, 40(%1) \n\t" \ + "sw $a0, 36($sp) \n\t" \ + "lw $a0, 4(%1) \n\t" \ + "lw $a1, 8(%1) \n\t" \ + "lw $a2, 12(%1) \n\t" \ + "lw $a3, 16(%1) \n\t" \ + "lw $t9, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $sp, $sp, 48 \n\t" \ + "lw $gp, 0($sp) \n\t" \ + "lw $ra, 4($sp) \n\t" \ + "addu $sp, $sp, 8 \n\t" \ + "move %0, $v0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ + arg6,arg7,arg8,arg9,arg10, \ + arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[12]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $gp, 0($sp) \n\t" \ + "sw $ra, 4($sp) \n\t" \ + "lw $a0, 20(%1) \n\t" \ + "subu $sp, $sp, 48\n\t" \ + "sw $a0, 16($sp) \n\t" \ + "lw $a0, 24(%1) \n\t" \ + "sw $a0, 20($sp) \n\t" \ + "lw $a0, 28(%1) \n\t" \ + "sw $a0, 24($sp) \n\t" \ + "lw $a0, 32(%1) \n\t" \ + "sw $a0, 28($sp) \n\t" \ + "lw $a0, 36(%1) \n\t" \ + "sw $a0, 32($sp) \n\t" \ + "lw $a0, 40(%1) \n\t" \ + "sw $a0, 36($sp) \n\t" \ + "lw $a0, 44(%1) \n\t" \ + "sw $a0, 40($sp) \n\t" \ + "lw $a0, 4(%1) \n\t" \ + "lw $a1, 8(%1) \n\t" \ + "lw $a2, 12(%1) \n\t" \ + "lw $a3, 16(%1) \n\t" \ + "lw $t9, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $sp, $sp, 48 \n\t" \ + "lw $gp, 0($sp) \n\t" \ + "lw $ra, 4($sp) \n\t" \ + "addu $sp, $sp, 8 \n\t" \ + "move %0, $v0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ + arg6,arg7,arg8,arg9,arg10, \ + arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[13]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + _argvec[12] = (unsigned long)(arg12); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $gp, 0($sp) \n\t" \ + "sw $ra, 4($sp) \n\t" \ + "lw $a0, 20(%1) \n\t" \ + "subu $sp, $sp, 56\n\t" \ + "sw $a0, 16($sp) \n\t" \ + "lw $a0, 24(%1) \n\t" \ + "sw $a0, 20($sp) \n\t" \ + "lw $a0, 28(%1) \n\t" \ + "sw $a0, 24($sp) \n\t" \ + "lw $a0, 32(%1) \n\t" \ + "sw $a0, 28($sp) \n\t" \ + "lw $a0, 36(%1) \n\t" \ + "sw $a0, 32($sp) \n\t" \ + "lw $a0, 40(%1) \n\t" \ + "sw $a0, 36($sp) \n\t" \ + "lw $a0, 44(%1) \n\t" \ + "sw $a0, 40($sp) \n\t" \ + "lw $a0, 48(%1) \n\t" \ + "sw $a0, 44($sp) \n\t" \ + "lw $a0, 4(%1) \n\t" \ + "lw $a1, 8(%1) \n\t" \ + "lw $a2, 12(%1) \n\t" \ + "lw $a3, 16(%1) \n\t" \ + "lw $t9, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $sp, $sp, 56 \n\t" \ + "lw $gp, 0($sp) \n\t" \ + "lw $ra, 4($sp) \n\t" \ + "addu $sp, $sp, 8 \n\t" \ + "move %0, $v0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_mips32_linux */ + + +/* ------------------------------------------------------------------ */ +/* ARCHITECTURE INDEPENDENT MACROS for CLIENT REQUESTS. */ +/* */ +/* ------------------------------------------------------------------ */ + +/* Some request codes. There are many more of these, but most are not + exposed to end-user view. These are the public ones, all of the + form 0x1000 + small_number. + + Core ones are in the range 0x00000000--0x0000ffff. The non-public + ones start at 0x2000. +*/ + +/* These macros are used by tools -- they must be public, but don't + embed them into other programs. */ +#define VG_USERREQ_TOOL_BASE(a,b) \ + ((unsigned int)(((a)&0xff) << 24 | ((b)&0xff) << 16)) +#define VG_IS_TOOL_USERREQ(a, b, v) \ + (VG_USERREQ_TOOL_BASE(a,b) == ((v) & 0xffff0000)) + +/* !! ABIWARNING !! ABIWARNING !! ABIWARNING !! ABIWARNING !! + This enum comprises an ABI exported by Valgrind to programs + which use client requests. DO NOT CHANGE THE ORDER OF THESE + ENTRIES, NOR DELETE ANY -- add new ones at the end. */ +typedef + enum { VG_USERREQ__RUNNING_ON_VALGRIND = 0x1001, + VG_USERREQ__DISCARD_TRANSLATIONS = 0x1002, + + /* These allow any function to be called from the simulated + CPU but run on the real CPU. Nb: the first arg passed to + the function is always the ThreadId of the running + thread! So CLIENT_CALL0 actually requires a 1 arg + function, etc. */ + VG_USERREQ__CLIENT_CALL0 = 0x1101, + VG_USERREQ__CLIENT_CALL1 = 0x1102, + VG_USERREQ__CLIENT_CALL2 = 0x1103, + VG_USERREQ__CLIENT_CALL3 = 0x1104, + + /* Can be useful in regression testing suites -- eg. can + send Valgrind's output to /dev/null and still count + errors. */ + VG_USERREQ__COUNT_ERRORS = 0x1201, + + /* Allows a string (gdb monitor command) to be passed to the tool + Used for interaction with vgdb/gdb */ + VG_USERREQ__GDB_MONITOR_COMMAND = 0x1202, + + /* These are useful and can be interpreted by any tool that + tracks malloc() et al, by using vg_replace_malloc.c. */ + VG_USERREQ__MALLOCLIKE_BLOCK = 0x1301, + VG_USERREQ__RESIZEINPLACE_BLOCK = 0x130b, + VG_USERREQ__FREELIKE_BLOCK = 0x1302, + /* Memory pool support. */ + VG_USERREQ__CREATE_MEMPOOL = 0x1303, + VG_USERREQ__DESTROY_MEMPOOL = 0x1304, + VG_USERREQ__MEMPOOL_ALLOC = 0x1305, + VG_USERREQ__MEMPOOL_FREE = 0x1306, + VG_USERREQ__MEMPOOL_TRIM = 0x1307, + VG_USERREQ__MOVE_MEMPOOL = 0x1308, + VG_USERREQ__MEMPOOL_CHANGE = 0x1309, + VG_USERREQ__MEMPOOL_EXISTS = 0x130a, + + /* Allow printfs to valgrind log. */ + /* The first two pass the va_list argument by value, which + assumes it is the same size as or smaller than a UWord, + which generally isn't the case. Hence are deprecated. + The second two pass the vargs by reference and so are + immune to this problem. */ + /* both :: char* fmt, va_list vargs (DEPRECATED) */ + VG_USERREQ__PRINTF = 0x1401, + VG_USERREQ__PRINTF_BACKTRACE = 0x1402, + /* both :: char* fmt, va_list* vargs */ + VG_USERREQ__PRINTF_VALIST_BY_REF = 0x1403, + VG_USERREQ__PRINTF_BACKTRACE_VALIST_BY_REF = 0x1404, + + /* Stack support. */ + VG_USERREQ__STACK_REGISTER = 0x1501, + VG_USERREQ__STACK_DEREGISTER = 0x1502, + VG_USERREQ__STACK_CHANGE = 0x1503, + + /* Wine support */ + VG_USERREQ__LOAD_PDB_DEBUGINFO = 0x1601, + + /* Querying of debug info. */ + VG_USERREQ__MAP_IP_TO_SRCLOC = 0x1701, + + /* Disable/enable error reporting level. Takes a single + Word arg which is the delta to this thread's error + disablement indicator. Hence 1 disables or further + disables errors, and -1 moves back towards enablement. + Other values are not allowed. */ + VG_USERREQ__CHANGE_ERR_DISABLEMENT = 0x1801 + } Vg_ClientRequest; + +#if !defined(__GNUC__) +# define __extension__ /* */ +#endif + + +/* Returns the number of Valgrinds this code is running under. That + is, 0 if running natively, 1 if running under Valgrind, 2 if + running under Valgrind which is running under another Valgrind, + etc. */ +#define RUNNING_ON_VALGRIND \ + (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* if not */, \ + VG_USERREQ__RUNNING_ON_VALGRIND, \ + 0, 0, 0, 0, 0) \ + + +/* Discard translation of code in the range [_qzz_addr .. _qzz_addr + + _qzz_len - 1]. Useful if you are debugging a JITter or some such, + since it provides a way to make sure valgrind will retranslate the + invalidated area. Returns no value. */ +#define VALGRIND_DISCARD_TRANSLATIONS(_qzz_addr,_qzz_len) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DISCARD_TRANSLATIONS, \ + _qzz_addr, _qzz_len, 0, 0, 0) + + +/* These requests are for getting Valgrind itself to print something. + Possibly with a backtrace. This is a really ugly hack. The return value + is the number of characters printed, excluding the "**** " part at the + start and the backtrace (if present). */ + +#if defined(__GNUC__) || defined(__INTEL_COMPILER) && !defined(_MSC_VER) +/* Modern GCC will optimize the static routine out if unused, + and unused attribute will shut down warnings about it. */ +static int VALGRIND_PRINTF(const char *format, ...) + __attribute__((format(__printf__, 1, 2), __unused__)); +#endif +static int +#if defined(_MSC_VER) +__inline +#endif +VALGRIND_PRINTF(const char *format, ...) +{ +#if defined(NVALGRIND) + return 0; +#else /* NVALGRIND */ +#if defined(_MSC_VER) + uintptr_t _qzz_res; +#else + unsigned long _qzz_res; +#endif + va_list vargs; + va_start(vargs, format); +#if defined(_MSC_VER) + _qzz_res = VALGRIND_DO_CLIENT_REQUEST_EXPR(0, + VG_USERREQ__PRINTF_VALIST_BY_REF, + (uintptr_t)format, + (uintptr_t)&vargs, + 0, 0, 0); +#else + _qzz_res = VALGRIND_DO_CLIENT_REQUEST_EXPR(0, + VG_USERREQ__PRINTF_VALIST_BY_REF, + (unsigned long)format, + (unsigned long)&vargs, + 0, 0, 0); +#endif + va_end(vargs); + return (int)_qzz_res; +#endif /* NVALGRIND */ +} + +#if defined(__GNUC__) || defined(__INTEL_COMPILER) && !defined(_MSC_VER) +static int VALGRIND_PRINTF_BACKTRACE(const char *format, ...) + __attribute__((format(__printf__, 1, 2), __unused__)); +#endif +static int +#if defined(_MSC_VER) +__inline +#endif +VALGRIND_PRINTF_BACKTRACE(const char *format, ...) +{ +#if defined(NVALGRIND) + return 0; +#else /* NVALGRIND */ +#if defined(_MSC_VER) + uintptr_t _qzz_res; +#else + unsigned long _qzz_res; +#endif + va_list vargs; + va_start(vargs, format); +#if defined(_MSC_VER) + _qzz_res = VALGRIND_DO_CLIENT_REQUEST_EXPR(0, + VG_USERREQ__PRINTF_BACKTRACE_VALIST_BY_REF, + (uintptr_t)format, + (uintptr_t)&vargs, + 0, 0, 0); +#else + _qzz_res = VALGRIND_DO_CLIENT_REQUEST_EXPR(0, + VG_USERREQ__PRINTF_BACKTRACE_VALIST_BY_REF, + (unsigned long)format, + (unsigned long)&vargs, + 0, 0, 0); +#endif + va_end(vargs); + return (int)_qzz_res; +#endif /* NVALGRIND */ +} + + +/* These requests allow control to move from the simulated CPU to the + real CPU, calling an arbitary function. + + Note that the current ThreadId is inserted as the first argument. + So this call: + + VALGRIND_NON_SIMD_CALL2(f, arg1, arg2) + + requires f to have this signature: + + Word f(Word tid, Word arg1, Word arg2) + + where "Word" is a word-sized type. + + Note that these client requests are not entirely reliable. For example, + if you call a function with them that subsequently calls printf(), + there's a high chance Valgrind will crash. Generally, your prospects of + these working are made higher if the called function does not refer to + any global variables, and does not refer to any libc or other functions + (printf et al). Any kind of entanglement with libc or dynamic linking is + likely to have a bad outcome, for tricky reasons which we've grappled + with a lot in the past. +*/ +#define VALGRIND_NON_SIMD_CALL0(_qyy_fn) \ + VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ + VG_USERREQ__CLIENT_CALL0, \ + _qyy_fn, \ + 0, 0, 0, 0) + +#define VALGRIND_NON_SIMD_CALL1(_qyy_fn, _qyy_arg1) \ + VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ + VG_USERREQ__CLIENT_CALL1, \ + _qyy_fn, \ + _qyy_arg1, 0, 0, 0) + +#define VALGRIND_NON_SIMD_CALL2(_qyy_fn, _qyy_arg1, _qyy_arg2) \ + VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ + VG_USERREQ__CLIENT_CALL2, \ + _qyy_fn, \ + _qyy_arg1, _qyy_arg2, 0, 0) + +#define VALGRIND_NON_SIMD_CALL3(_qyy_fn, _qyy_arg1, _qyy_arg2, _qyy_arg3) \ + VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ + VG_USERREQ__CLIENT_CALL3, \ + _qyy_fn, \ + _qyy_arg1, _qyy_arg2, \ + _qyy_arg3, 0) + + +/* Counts the number of errors that have been recorded by a tool. Nb: + the tool must record the errors with VG_(maybe_record_error)() or + VG_(unique_error)() for them to be counted. */ +#define VALGRIND_COUNT_ERRORS \ + (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + 0 /* default return */, \ + VG_USERREQ__COUNT_ERRORS, \ + 0, 0, 0, 0, 0) + +/* Several Valgrind tools (Memcheck, Massif, Helgrind, DRD) rely on knowing + when heap blocks are allocated in order to give accurate results. This + happens automatically for the standard allocator functions such as + malloc(), calloc(), realloc(), memalign(), new, new[], free(), delete, + delete[], etc. + + But if your program uses a custom allocator, this doesn't automatically + happen, and Valgrind will not do as well. For example, if you allocate + superblocks with mmap() and then allocates chunks of the superblocks, all + Valgrind's observations will be at the mmap() level and it won't know that + the chunks should be considered separate entities. In Memcheck's case, + that means you probably won't get heap block overrun detection (because + there won't be redzones marked as unaddressable) and you definitely won't + get any leak detection. + + The following client requests allow a custom allocator to be annotated so + that it can be handled accurately by Valgrind. + + VALGRIND_MALLOCLIKE_BLOCK marks a region of memory as having been allocated + by a malloc()-like function. For Memcheck (an illustrative case), this + does two things: + + - It records that the block has been allocated. This means any addresses + within the block mentioned in error messages will be + identified as belonging to the block. It also means that if the block + isn't freed it will be detected by the leak checker. + + - It marks the block as being addressable and undefined (if 'is_zeroed' is + not set), or addressable and defined (if 'is_zeroed' is set). This + controls how accesses to the block by the program are handled. + + 'addr' is the start of the usable block (ie. after any + redzone), 'sizeB' is its size. 'rzB' is the redzone size if the allocator + can apply redzones -- these are blocks of padding at the start and end of + each block. Adding redzones is recommended as it makes it much more likely + Valgrind will spot block overruns. `is_zeroed' indicates if the memory is + zeroed (or filled with another predictable value), as is the case for + calloc(). + + VALGRIND_MALLOCLIKE_BLOCK should be put immediately after the point where a + heap block -- that will be used by the client program -- is allocated. + It's best to put it at the outermost level of the allocator if possible; + for example, if you have a function my_alloc() which calls + internal_alloc(), and the client request is put inside internal_alloc(), + stack traces relating to the heap block will contain entries for both + my_alloc() and internal_alloc(), which is probably not what you want. + + For Memcheck users: if you use VALGRIND_MALLOCLIKE_BLOCK to carve out + custom blocks from within a heap block, B, that has been allocated with + malloc/calloc/new/etc, then block B will be *ignored* during leak-checking + -- the custom blocks will take precedence. + + VALGRIND_FREELIKE_BLOCK is the partner to VALGRIND_MALLOCLIKE_BLOCK. For + Memcheck, it does two things: + + - It records that the block has been deallocated. This assumes that the + block was annotated as having been allocated via + VALGRIND_MALLOCLIKE_BLOCK. Otherwise, an error will be issued. + + - It marks the block as being unaddressable. + + VALGRIND_FREELIKE_BLOCK should be put immediately after the point where a + heap block is deallocated. + + VALGRIND_RESIZEINPLACE_BLOCK informs a tool about reallocation. For + Memcheck, it does four things: + + - It records that the size of a block has been changed. This assumes that + the block was annotated as having been allocated via + VALGRIND_MALLOCLIKE_BLOCK. Otherwise, an error will be issued. + + - If the block shrunk, it marks the freed memory as being unaddressable. + + - If the block grew, it marks the new area as undefined and defines a red + zone past the end of the new block. + + - The V-bits of the overlap between the old and the new block are preserved. + + VALGRIND_RESIZEINPLACE_BLOCK should be put after allocation of the new block + and before deallocation of the old block. + + In many cases, these three client requests will not be enough to get your + allocator working well with Memcheck. More specifically, if your allocator + writes to freed blocks in any way then a VALGRIND_MAKE_MEM_UNDEFINED call + will be necessary to mark the memory as addressable just before the zeroing + occurs, otherwise you'll get a lot of invalid write errors. For example, + you'll need to do this if your allocator recycles freed blocks, but it + zeroes them before handing them back out (via VALGRIND_MALLOCLIKE_BLOCK). + Alternatively, if your allocator reuses freed blocks for allocator-internal + data structures, VALGRIND_MAKE_MEM_UNDEFINED calls will also be necessary. + + Really, what's happening is a blurring of the lines between the client + program and the allocator... after VALGRIND_FREELIKE_BLOCK is called, the + memory should be considered unaddressable to the client program, but the + allocator knows more than the rest of the client program and so may be able + to safely access it. Extra client requests are necessary for Valgrind to + understand the distinction between the allocator and the rest of the + program. + + Ignored if addr == 0. +*/ +#define VALGRIND_MALLOCLIKE_BLOCK(addr, sizeB, rzB, is_zeroed) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MALLOCLIKE_BLOCK, \ + addr, sizeB, rzB, is_zeroed, 0) + +/* See the comment for VALGRIND_MALLOCLIKE_BLOCK for details. + Ignored if addr == 0. +*/ +#define VALGRIND_RESIZEINPLACE_BLOCK(addr, oldSizeB, newSizeB, rzB) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__RESIZEINPLACE_BLOCK, \ + addr, oldSizeB, newSizeB, rzB, 0) + +/* See the comment for VALGRIND_MALLOCLIKE_BLOCK for details. + Ignored if addr == 0. +*/ +#define VALGRIND_FREELIKE_BLOCK(addr, rzB) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__FREELIKE_BLOCK, \ + addr, rzB, 0, 0, 0) + +/* Create a memory pool. */ +#define VALGRIND_CREATE_MEMPOOL(pool, rzB, is_zeroed) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CREATE_MEMPOOL, \ + pool, rzB, is_zeroed, 0, 0) + +/* Destroy a memory pool. */ +#define VALGRIND_DESTROY_MEMPOOL(pool) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DESTROY_MEMPOOL, \ + pool, 0, 0, 0, 0) + +/* Associate a piece of memory with a memory pool. */ +#define VALGRIND_MEMPOOL_ALLOC(pool, addr, size) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MEMPOOL_ALLOC, \ + pool, addr, size, 0, 0) + +/* Disassociate a piece of memory from a memory pool. */ +#define VALGRIND_MEMPOOL_FREE(pool, addr) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MEMPOOL_FREE, \ + pool, addr, 0, 0, 0) + +/* Disassociate any pieces outside a particular range. */ +#define VALGRIND_MEMPOOL_TRIM(pool, addr, size) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MEMPOOL_TRIM, \ + pool, addr, size, 0, 0) + +/* Resize and/or move a piece associated with a memory pool. */ +#define VALGRIND_MOVE_MEMPOOL(poolA, poolB) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MOVE_MEMPOOL, \ + poolA, poolB, 0, 0, 0) + +/* Resize and/or move a piece associated with a memory pool. */ +#define VALGRIND_MEMPOOL_CHANGE(pool, addrA, addrB, size) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MEMPOOL_CHANGE, \ + pool, addrA, addrB, size, 0) + +/* Return 1 if a mempool exists, else 0. */ +#define VALGRIND_MEMPOOL_EXISTS(pool) \ + (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ + VG_USERREQ__MEMPOOL_EXISTS, \ + pool, 0, 0, 0, 0) + +/* Mark a piece of memory as being a stack. Returns a stack id. */ +#define VALGRIND_STACK_REGISTER(start, end) \ + (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ + VG_USERREQ__STACK_REGISTER, \ + start, end, 0, 0, 0) + +/* Unmark the piece of memory associated with a stack id as being a + stack. */ +#define VALGRIND_STACK_DEREGISTER(id) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__STACK_DEREGISTER, \ + id, 0, 0, 0, 0) + +/* Change the start and end address of the stack id. */ +#define VALGRIND_STACK_CHANGE(id, start, end) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__STACK_CHANGE, \ + id, start, end, 0, 0) + +/* Load PDB debug info for Wine PE image_map. */ +#define VALGRIND_LOAD_PDB_DEBUGINFO(fd, ptr, total_size, delta) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__LOAD_PDB_DEBUGINFO, \ + fd, ptr, total_size, delta, 0) + +/* Map a code address to a source file name and line number. buf64 + must point to a 64-byte buffer in the caller's address space. The + result will be dumped in there and is guaranteed to be zero + terminated. If no info is found, the first byte is set to zero. */ +#define VALGRIND_MAP_IP_TO_SRCLOC(addr, buf64) \ + (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ + VG_USERREQ__MAP_IP_TO_SRCLOC, \ + addr, buf64, 0, 0, 0) + +/* Disable error reporting for this thread. Behaves in a stack like + way, so you can safely call this multiple times provided that + VALGRIND_ENABLE_ERROR_REPORTING is called the same number of times + to re-enable reporting. The first call of this macro disables + reporting. Subsequent calls have no effect except to increase the + number of VALGRIND_ENABLE_ERROR_REPORTING calls needed to re-enable + reporting. Child threads do not inherit this setting from their + parents -- they are always created with reporting enabled. */ +#define VALGRIND_DISABLE_ERROR_REPORTING \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CHANGE_ERR_DISABLEMENT, \ + 1, 0, 0, 0, 0) + +/* Re-enable error reporting, as per comments on + VALGRIND_DISABLE_ERROR_REPORTING. */ +#define VALGRIND_ENABLE_ERROR_REPORTING \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CHANGE_ERR_DISABLEMENT, \ + -1, 0, 0, 0, 0) + +#undef PLAT_x86_darwin +#undef PLAT_amd64_darwin +#undef PLAT_x86_win32 +#undef PLAT_x86_linux +#undef PLAT_amd64_linux +#undef PLAT_ppc32_linux +#undef PLAT_ppc64_linux +#undef PLAT_arm_linux +#undef PLAT_s390x_linux +#undef PLAT_mips32_linux + +#endif /* __VALGRIND_H */ diff --git a/src/butil/thread_key.cpp b/src/butil/thread_key.cpp new file mode 100644 index 0000000..3bf4bb0 --- /dev/null +++ b/src/butil/thread_key.cpp @@ -0,0 +1,171 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "thread_key.h" +#include "pthread.h" +#include +#include "butil/thread_local.h" + +namespace butil { + +// Check whether an entry is unused. +#define KEY_UNUSED(p) (((p) & 1) == 0) + +// Check whether a key is usable. We cannot reuse an allocated key if +// the sequence counter would overflow after the next destroy call. +// This would mean that we potentially free memory for a key with the +// same sequence. This is *very* unlikely to happen, A program would +// have to create and destroy a key 2^31 times. If it should happen we +// simply don't use this specific key anymore. +#define KEY_USABLE(p) (((size_t) (p)) < ((size_t) ((p) + 2))) + +static const uint32_t THREAD_KEY_RESERVE = 8096; +pthread_mutex_t g_thread_key_mutex = PTHREAD_MUTEX_INITIALIZER; +static size_t g_id = 0; +static std::deque* g_free_ids = NULL; +static std::vector* g_thread_keys = NULL; +static __thread std::vector* thread_key_tls_data = NULL; + +ThreadKey& ThreadKey::operator=(ThreadKey&& other) noexcept { + if (this == &other) { + return *this; + } + + _id = other._id; + _seq = other._seq; + other.Reset(); + return *this; +} + +bool ThreadKey::Valid() const { + return _id != InvalidID && !KEY_UNUSED(_seq); +} + +static void DestroyTlsData() { + if (!thread_key_tls_data) { + return; + } + std::vector dummy_keys; + { + BAIDU_SCOPED_LOCK(g_thread_key_mutex); + dummy_keys.insert(dummy_keys.end(), + g_thread_keys->begin(), + g_thread_keys->end()); + } + for (size_t i = 0; i < thread_key_tls_data->size(); ++i) { + if (!KEY_UNUSED(dummy_keys[i].seq) && dummy_keys[i].dtor) { + dummy_keys[i].dtor((*thread_key_tls_data)[i].data); + } + } + delete thread_key_tls_data; + thread_key_tls_data = NULL; +} + +int thread_key_create(ThreadKey& thread_key, DtorFunction dtor) { + BAIDU_SCOPED_LOCK(g_thread_key_mutex); + if (BAIDU_UNLIKELY(!g_free_ids)) { + g_free_ids = new std::deque; + } + size_t id; + if (!g_free_ids->empty()) { + id = g_free_ids->back(); + g_free_ids->pop_back(); + } else { + if (g_id >= ThreadKey::InvalidID) { + // No more available ids. + return EAGAIN; + } + id = g_id++; + if (BAIDU_UNLIKELY(!g_thread_keys)) { + g_thread_keys = new std::vector; + g_thread_keys->reserve(THREAD_KEY_RESERVE); + } + g_thread_keys->resize(id + 1); + } + + ++((*g_thread_keys)[id].seq); + (*g_thread_keys)[id].dtor = dtor; + thread_key._id = id; + thread_key._seq = (*g_thread_keys)[id].seq; + + return 0; +} + +int thread_key_delete(ThreadKey& thread_key) { + if (BAIDU_UNLIKELY(!thread_key.Valid())) { + return EINVAL; + } + + BAIDU_SCOPED_LOCK(g_thread_key_mutex); + size_t id = thread_key._id; + size_t seq = thread_key._seq; + if (id >= g_thread_keys->size() || + seq != (*g_thread_keys)[id].seq || + KEY_UNUSED((*g_thread_keys)[id].seq)) { + thread_key.Reset(); + return EINVAL; + } + + ++((*g_thread_keys)[id].seq); + // Collect the usable key id for reuse. + if (KEY_USABLE((*g_thread_keys)[id].seq)) { + g_free_ids->push_back(id); + } + thread_key.Reset(); + + return 0; +} + +int thread_setspecific(ThreadKey& thread_key, void* data) { + if (BAIDU_UNLIKELY(!thread_key.Valid())) { + return EINVAL; + } + size_t id = thread_key._id; + size_t seq = thread_key._seq; + if (BAIDU_UNLIKELY(!thread_key_tls_data)) { + thread_key_tls_data = new std::vector; + thread_key_tls_data->reserve(THREAD_KEY_RESERVE); + // Register the destructor of tls_data in this thread. + butil::thread_atexit(DestroyTlsData); + } + + if (id >= thread_key_tls_data->size()) { + thread_key_tls_data->resize(id + 1); + } + + (*thread_key_tls_data)[id].seq = seq; + (*thread_key_tls_data)[id].data = data; + + return 0; +} + +void* thread_getspecific(ThreadKey& thread_key) { + if (BAIDU_UNLIKELY(!thread_key.Valid())) { + return NULL; + } + size_t id = thread_key._id; + size_t seq = thread_key._seq; + if (BAIDU_UNLIKELY(!thread_key_tls_data || + id >= thread_key_tls_data->size() || + (*thread_key_tls_data)[id].seq != seq)){ + return NULL; + } + + return (*thread_key_tls_data)[id].data; +} + +} // namespace butil \ No newline at end of file diff --git a/src/butil/thread_key.h b/src/butil/thread_key.h new file mode 100644 index 0000000..77f346d --- /dev/null +++ b/src/butil/thread_key.h @@ -0,0 +1,221 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BUTIL_THREAD_KEY_H +#define BUTIL_THREAD_KEY_H + +#include +#include +#include +#include +#include +#include "butil/scoped_lock.h" +#include "butil/type_traits.h" + +namespace butil { + +typedef void (*DtorFunction)(void *); + +class ThreadKey { +public: + friend int thread_key_create(ThreadKey& thread_key, DtorFunction dtor); + friend int thread_key_delete(ThreadKey& thread_key); + friend int thread_setspecific(ThreadKey& thread_key, void* data); + friend void* thread_getspecific(ThreadKey& thread_key); + + static constexpr size_t InvalidID = std::numeric_limits::max(); + static constexpr size_t InitSeq = 0; + + constexpr ThreadKey() : _id(InvalidID), _seq(InitSeq) {} + + ~ThreadKey() { + Reset(); + } + + ThreadKey(ThreadKey&& other) noexcept + : _id(other._id) + , _seq(other._seq) { + other.Reset(); + } + + ThreadKey& operator=(ThreadKey&& other) noexcept; + + ThreadKey(const ThreadKey& other) = delete; + ThreadKey& operator=(const ThreadKey& other) = delete; + + bool Valid() const; + + void Reset() { + _id = InvalidID; + _seq = InitSeq; + } + +private: + size_t _id; // Key id. + // Sequence number form g_thread_keys set in thread_key_create. + size_t _seq; +}; + +struct ThreadKeyInfo { + ThreadKeyInfo() : seq(0), dtor(NULL) {} + + size_t seq; // Already allocated? + DtorFunction dtor; // Destruction routine. +}; + +struct ThreadKeyTLS { + ThreadKeyTLS() : seq(0), data(NULL) {} + + // Sequence number form ThreadKey, + // set in `thread_setspecific', + // used to check if the key is valid in `thread_getspecific'. + size_t seq; + void* data; // User data. +}; + +// pthread_key_xxx implication without num limit of key. +int thread_key_create(ThreadKey& thread_key, DtorFunction dtor); +int thread_key_delete(ThreadKey& thread_key); +int thread_setspecific(ThreadKey& thread_key, void* data); +void* thread_getspecific(ThreadKey& thread_key); + + +template +class ThreadLocal { +public: + ThreadLocal() : ThreadLocal(false) {} + + explicit ThreadLocal(bool delete_on_thread_exit); + + ~ThreadLocal(); + + // non-copyable + ThreadLocal(const ThreadLocal&) = delete; + ThreadLocal& operator=(const ThreadLocal&) = delete; + + T* get(); + + T* operator->() { return get(); } + + T& operator*() { return *get(); } + + // Iterate through all thread local objects. + // Callback, which must accept Args params and return void, + // will be called under a thread lock. + template + void for_each(Callback&& callback) { + BAIDU_CASSERT( + (is_result_void::value), + "Callback must accept Args params and return void"); + BAIDU_SCOPED_LOCK(_mutex); + for (auto ptr : ptrs) { + callback(ptr); + } + } + + void reset(T* ptr); + + void reset() { + reset(NULL); + } + +private: + static void DefaultDtor(void* ptr) { + if (ptr) { + delete static_cast(ptr); + } + } + + ThreadKey _key; + pthread_mutex_t _mutex; + // All pointers of data allocated by the ThreadLocal. + std::vector ptrs; + // Delete data on thread exit or destructor of ThreadLocal. + bool _delete_on_thread_exit; +}; + +template +ThreadLocal::ThreadLocal(bool delete_on_thread_exit) + : _mutex(PTHREAD_MUTEX_INITIALIZER) + , _delete_on_thread_exit(delete_on_thread_exit) { + DtorFunction dtor = _delete_on_thread_exit ? DefaultDtor : NULL; + thread_key_create(_key, dtor); +} + + +template +ThreadLocal::~ThreadLocal() { + thread_key_delete(_key); + if (!_delete_on_thread_exit) { + BAIDU_SCOPED_LOCK(_mutex); + for (auto ptr : ptrs) { + DefaultDtor(ptr); + } + } + pthread_mutex_destroy(&_mutex); +} + +template +T* ThreadLocal::get() { + T* ptr = static_cast(thread_getspecific(_key)); + if (!ptr) { + ptr = new (std::nothrow) T; + if (!ptr) { + return NULL; + } + int rc = thread_setspecific(_key, ptr); + if (rc != 0) { + DefaultDtor(ptr); + return NULL; + } + { + BAIDU_SCOPED_LOCK(_mutex); + ptrs.push_back(ptr); + } + } + return ptr; +} + +template +void ThreadLocal::reset(T* ptr) { + T* old_ptr = get(); + if (ptr == old_ptr) { + return; + } + if (thread_setspecific(_key, ptr) != 0) { + return; + } + { + BAIDU_SCOPED_LOCK(_mutex); + if (ptr) { + ptrs.push_back(ptr); + } + // Remove and delete old_ptr. + if (old_ptr) { + auto iter = std::remove(ptrs.begin(), ptrs.end(), old_ptr); + if (iter != ptrs.end()) { + ptrs.erase(iter, ptrs.end()); + } + DefaultDtor(old_ptr); + } + } +} + +} + + +#endif // BUTIL_THREAD_KEY_H diff --git a/src/butil/thread_local.cpp b/src/butil/thread_local.cpp new file mode 100644 index 0000000..f10c5b1 --- /dev/null +++ b/src/butil/thread_local.cpp @@ -0,0 +1,160 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Mon. Nov 7 14:47:36 CST 2011 + +#include // errno +#include // pthread_key_t +#include +#include // std::find +#include // std::vector +#include // abort, atexit + +namespace butil { +namespace detail { + +class ThreadExitHelper { +public: + typedef void (*Fn)(void*); + typedef std::pair Pair; + + ~ThreadExitHelper() { + // Call function reversely. + while (!_fns.empty()) { + Pair back = _fns.back(); + _fns.pop_back(); + // Notice that _fns may be changed after calling Fn. + back.first(back.second); + } + } + + int add(Fn fn, void* arg) { + try { + if (_fns.capacity() < 16) { + _fns.reserve(16); + } + _fns.emplace_back(fn, arg); + } catch (...) { + errno = ENOMEM; + return -1; + } + return 0; + } + + void remove(Fn fn, void* arg) { + std::vector::iterator + it = std::find(_fns.begin(), _fns.end(), std::make_pair(fn, arg)); + if (it != _fns.end()) { + std::vector::iterator ite = it + 1; + for (; ite != _fns.end() && ite->first == fn && ite->second == arg; + ++ite) {} + _fns.erase(it, ite); + } + } + +private: + std::vector _fns; +}; + +static pthread_key_t thread_atexit_key; +static pthread_once_t thread_atexit_once = PTHREAD_ONCE_INIT; + +static void delete_thread_exit_helper(void* arg) { + delete static_cast(arg); +} + +static void helper_exit_global() { + detail::ThreadExitHelper* h = + (detail::ThreadExitHelper*)pthread_getspecific(detail::thread_atexit_key); + if (h) { + pthread_setspecific(detail::thread_atexit_key, NULL); + delete h; + } +} + +static void make_thread_atexit_key() { + if (pthread_key_create(&thread_atexit_key, delete_thread_exit_helper) != 0) { + fprintf(stderr, "Fail to create thread_atexit_key, abort\n"); + abort(); + } + // If caller is not pthread, delete_thread_exit_helper will not be called. + // We have to rely on atexit(). + atexit(helper_exit_global); +} + +detail::ThreadExitHelper* get_or_new_thread_exit_helper() { + pthread_once(&detail::thread_atexit_once, detail::make_thread_atexit_key); + + detail::ThreadExitHelper* h = + (detail::ThreadExitHelper*)pthread_getspecific(detail::thread_atexit_key); + if (NULL == h) { + h = new (std::nothrow) detail::ThreadExitHelper; + if (NULL != h) { + pthread_setspecific(detail::thread_atexit_key, h); + } + } + return h; +} + +detail::ThreadExitHelper* get_thread_exit_helper() { + pthread_once(&detail::thread_atexit_once, detail::make_thread_atexit_key); + return (detail::ThreadExitHelper*)pthread_getspecific(detail::thread_atexit_key); +} + +static void call_single_arg_fn(void* fn) { + ((void (*)())fn)(); +} + +} // namespace detail + +int thread_atexit(void (*fn)(void*), void* arg) { + if (NULL == fn) { + errno = EINVAL; + return -1; + } + detail::ThreadExitHelper* h = detail::get_or_new_thread_exit_helper(); + if (h) { + return h->add(fn, arg); + } + errno = ENOMEM; + return -1; +} + +int thread_atexit(void (*fn)()) { + if (NULL == fn) { + errno = EINVAL; + return -1; + } + return thread_atexit(detail::call_single_arg_fn, (void*)fn); +} + +void thread_atexit_cancel(void (*fn)(void*), void* arg) { + if (fn != NULL) { + detail::ThreadExitHelper* h = detail::get_thread_exit_helper(); + if (h) { + h->remove(fn, arg); + } + } +} + +void thread_atexit_cancel(void (*fn)()) { + if (NULL != fn) { + thread_atexit_cancel(detail::call_single_arg_fn, (void*)fn); + } +} + +} // namespace butil diff --git a/src/butil/thread_local.h b/src/butil/thread_local.h new file mode 100644 index 0000000..973ad14 --- /dev/null +++ b/src/butil/thread_local.h @@ -0,0 +1,116 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Mon. Nov 7 14:47:36 CST 2011 + +#ifndef BUTIL_THREAD_LOCAL_H +#define BUTIL_THREAD_LOCAL_H + +#include // std::nothrow +#include // NULL +#include "butil/macros.h" + +#ifdef _MSC_VER +#define BAIDU_THREAD_LOCAL __declspec(thread) +#else +#define BAIDU_THREAD_LOCAL __thread +#endif // _MSC_VER + +#define BAIDU_VOLATILE_THREAD_LOCAL(type, var_name, default_value) \ + BAIDU_THREAD_LOCAL type var_name = default_value; \ + __attribute__((noinline, unused)) type get_##var_name(void) { \ + asm volatile(""); \ + return var_name; \ + } \ + __attribute__((noinline, unused)) type *get_ptr_##var_name(void) { \ + type *ptr = &var_name; \ + asm volatile("" : "+rm"(ptr)); \ + return ptr; \ + } \ + __attribute__((noinline, unused)) void set_##var_name(type v) { \ + asm volatile(""); \ + var_name = v; \ + } + + #define STATIC_MEMBER_BAIDU_VOLATILE_THREAD_LOCAL(type, var_name) \ + static BAIDU_THREAD_LOCAL type var_name; \ + static __attribute__((noinline, unused)) type get_##var_name(void) { \ + asm volatile(""); \ + return var_name; \ + } \ + static __attribute__((noinline, unused)) type *get_ptr_##var_name(void) { \ + type *ptr = &var_name; \ + asm volatile("" : "+rm"(ptr)); \ + return ptr; \ + } \ + static __attribute__((noinline, unused)) void set_##var_name(type v) { \ + asm volatile(""); \ + var_name = v; \ + } + +#if (defined (__aarch64__) && defined (__GNUC__)) || defined(__clang__) +// GNU compiler under aarch and Clang compiler is incorrectly caching the +// address of thread_local variables across a suspend-point. The following +// macros used to disable the volatile thread local access optimization. +#define BAIDU_GET_VOLATILE_THREAD_LOCAL(var_name) get_##var_name() +#define BAIDU_GET_PTR_VOLATILE_THREAD_LOCAL(var_name) get_ptr_##var_name() +#define BAIDU_SET_VOLATILE_THREAD_LOCAL(var_name, value) set_##var_name(value) + +#define EXTERN_BAIDU_VOLATILE_THREAD_LOCAL(type, var_name) \ + extern type get_##var_name(void); \ + extern type *get_ptr_##var_name(void); \ + extern void set_##var_name(type v) +#else +#define BAIDU_GET_VOLATILE_THREAD_LOCAL(var_name) var_name +#define BAIDU_GET_PTR_VOLATILE_THREAD_LOCAL(var_name) &var_name +#define BAIDU_SET_VOLATILE_THREAD_LOCAL(var_name, value) var_name = value +#define EXTERN_BAIDU_VOLATILE_THREAD_LOCAL(type, var_name) \ + extern BAIDU_THREAD_LOCAL type var_name +#endif + +namespace butil { + +// Get a thread-local object typed T. The object will be default-constructed +// at the first call to this function, and will be deleted when thread +// exits. +template inline T* get_thread_local(); + +// |fn| or |fn(arg)| will be called at caller's exit. If caller is not a +// thread, fn will be called at program termination. Calling sequence is LIFO: +// last registered function will be called first. Duplication of functions +// are not checked. This function is often used for releasing thread-local +// resources declared with __thread which is much faster than +// pthread_getspecific or boost::thread_specific_ptr. +// Returns 0 on success, -1 otherwise and errno is set. +int thread_atexit(void (*fn)()); +int thread_atexit(void (*fn)(void*), void* arg); + +// Remove registered function, matched functions will not be called. +void thread_atexit_cancel(void (*fn)()); +void thread_atexit_cancel(void (*fn)(void*), void* arg); + +// Delete the typed-T object whose address is `arg'. This is a common function +// to thread_atexit. +template void delete_object(void* arg) { + delete static_cast(arg); +} + +} // namespace butil + +#include "thread_local_inl.h" + +#endif // BUTIL_THREAD_LOCAL_H diff --git a/src/butil/thread_local_inl.h b/src/butil/thread_local_inl.h new file mode 100644 index 0000000..12f10e3 --- /dev/null +++ b/src/butil/thread_local_inl.h @@ -0,0 +1,53 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Tue Sep 16 12:39:12 CST 2014 + +#ifndef BUTIL_THREAD_LOCAL_INL_H +#define BUTIL_THREAD_LOCAL_INL_H + +namespace butil { + +namespace detail { + +template +class ThreadLocalHelper { +public: + inline static T* get() { + if (__builtin_expect(value != NULL, 1)) { + return value; + } + value = new (std::nothrow) T; + if (value != NULL) { + butil::thread_atexit(delete_object, value); + } + return value; + } + static BAIDU_THREAD_LOCAL T* value; +}; + +template BAIDU_THREAD_LOCAL T* ThreadLocalHelper::value = NULL; + +} // namespace detail + +template inline T* get_thread_local() { + return detail::ThreadLocalHelper::get(); +} + +} // namespace butil + +#endif // BUTIL_THREAD_LOCAL_INL_H diff --git a/src/butil/threading/non_thread_safe.h b/src/butil/threading/non_thread_safe.h new file mode 100644 index 0000000..6637c33 --- /dev/null +++ b/src/butil/threading/non_thread_safe.h @@ -0,0 +1,71 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_THREADING_NON_THREAD_SAFE_H_ +#define BUTIL_THREADING_NON_THREAD_SAFE_H_ + +// Classes deriving from NonThreadSafe may need to suppress MSVC warning 4275: +// non dll-interface class 'Bar' used as base for dll-interface class 'Foo'. +// There is a specific macro to do it: NON_EXPORTED_BASE(), defined in +// compiler_specific.h +#include "butil/compiler_specific.h" + +// See comment at top of thread_checker.h +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) +#define ENABLE_NON_THREAD_SAFE 1 +#else +#define ENABLE_NON_THREAD_SAFE 0 +#endif + +#include "butil/threading/non_thread_safe_impl.h" + +namespace butil { + +// Do nothing implementation of NonThreadSafe, for release mode. +// +// Note: You should almost always use the NonThreadSafe class to get +// the right version of the class for your build configuration. +class NonThreadSafeDoNothing { + public: + bool CalledOnValidThread() const { + return true; + } + + protected: + ~NonThreadSafeDoNothing() {} + void DetachFromThread() {} +}; + +// NonThreadSafe is a helper class used to help verify that methods of a +// class are called from the same thread. One can inherit from this class +// and use CalledOnValidThread() to verify. +// +// This is intended to be used with classes that appear to be thread safe, but +// aren't. For example, a service or a singleton like the preferences system. +// +// Example: +// class MyClass : public butil::NonThreadSafe { +// public: +// void Foo() { +// DCHECK(CalledOnValidThread()); +// ... (do stuff) ... +// } +// } +// +// Note that butil::ThreadChecker offers identical functionality to +// NonThreadSafe, but does not require inheritance. In general, it is preferable +// to have a butil::ThreadChecker as a member, rather than inherit from +// NonThreadSafe. For more details about when to choose one over the other, see +// the documentation for butil::ThreadChecker. +#if ENABLE_NON_THREAD_SAFE +typedef NonThreadSafeImpl NonThreadSafe; +#else +typedef NonThreadSafeDoNothing NonThreadSafe; +#endif // ENABLE_NON_THREAD_SAFE + +#undef ENABLE_NON_THREAD_SAFE + +} // namespace butil + +#endif // BUTIL_THREADING_NON_THREAD_SAFE_H_ diff --git a/src/butil/threading/non_thread_safe_impl.cc b/src/butil/threading/non_thread_safe_impl.cc new file mode 100644 index 0000000..a4aa4b6 --- /dev/null +++ b/src/butil/threading/non_thread_safe_impl.cc @@ -0,0 +1,23 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/threading/non_thread_safe_impl.h" + +#include "butil/logging.h" + +namespace butil { + +bool NonThreadSafeImpl::CalledOnValidThread() const { + return thread_checker_.CalledOnValidThread(); +} + +NonThreadSafeImpl::~NonThreadSafeImpl() { + DCHECK(CalledOnValidThread()); +} + +void NonThreadSafeImpl::DetachFromThread() { + thread_checker_.DetachFromThread(); +} + +} // namespace butil diff --git a/src/butil/threading/non_thread_safe_impl.h b/src/butil/threading/non_thread_safe_impl.h new file mode 100644 index 0000000..211030f --- /dev/null +++ b/src/butil/threading/non_thread_safe_impl.h @@ -0,0 +1,39 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_THREADING_NON_THREAD_SAFE_IMPL_H_ +#define BUTIL_THREADING_NON_THREAD_SAFE_IMPL_H_ + +#include "butil/base_export.h" +#include "butil/threading/thread_checker_impl.h" + +namespace butil { + +// Full implementation of NonThreadSafe, for debug mode or for occasional +// temporary use in release mode e.g. when you need to CHECK on a thread +// bug that only occurs in the wild. +// +// Note: You should almost always use the NonThreadSafe class to get +// the right version of the class for your build configuration. +class BUTIL_EXPORT NonThreadSafeImpl { + public: + bool CalledOnValidThread() const; + + protected: + ~NonThreadSafeImpl(); + + // Changes the thread that is checked for in CalledOnValidThread. The next + // call to CalledOnValidThread will attach this class to a new thread. It is + // up to the NonThreadSafe derived class to decide to expose this or not. + // This may be useful when an object may be created on one thread and then + // used exclusively on another thread. + void DetachFromThread(); + + private: + ThreadCheckerImpl thread_checker_; +}; + +} // namespace butil + +#endif // BUTIL_THREADING_NON_THREAD_SAFE_IMPL_H_ diff --git a/src/butil/threading/platform_thread.h b/src/butil/threading/platform_thread.h new file mode 100644 index 0000000..fb36d45 --- /dev/null +++ b/src/butil/threading/platform_thread.h @@ -0,0 +1,201 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// WARNING: You should *NOT* be using this class directly. PlatformThread is +// the low-level platform-specific abstraction to the OS's threading interface. +// You should instead be using a message-loop driven Thread, see thread.h. + +#ifndef BUTIL_THREADING_PLATFORM_THREAD_H_ +#define BUTIL_THREADING_PLATFORM_THREAD_H_ + +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/time/time.h" +#include "butil/build_config.h" + +#if defined(OS_WIN) +#include +#elif defined(OS_POSIX) +#include +#include +#endif + +namespace butil { + +// Used for logging. Always an integer value. +#if defined(OS_WIN) +typedef DWORD PlatformThreadId; +#elif defined(OS_POSIX) +typedef pid_t PlatformThreadId; +#endif + +// Used for thread checking and debugging. +// Meant to be as fast as possible. +// These are produced by PlatformThread::CurrentRef(), and used to later +// check if we are on the same thread or not by using ==. These are safe +// to copy between threads, but can't be copied to another process as they +// have no meaning there. Also, the internal identifier can be re-used +// after a thread dies, so a PlatformThreadRef cannot be reliably used +// to distinguish a new thread from an old, dead thread. +class PlatformThreadRef { + public: +#if defined(OS_WIN) + typedef DWORD RefType; +#elif defined(OS_POSIX) + typedef pthread_t RefType; +#endif + PlatformThreadRef() + : id_(0) { + } + + explicit PlatformThreadRef(RefType id) + : id_(id) { + } + + bool operator==(PlatformThreadRef other) const { + return id_ == other.id_; + } + + bool is_null() const { + return id_ == 0; + } + private: + RefType id_; +}; + +// Used to operate on threads. +class PlatformThreadHandle { + public: +#if defined(OS_WIN) + typedef void* Handle; +#elif defined(OS_POSIX) + typedef pthread_t Handle; +#endif + + PlatformThreadHandle() + : handle_(0), + id_(0) { + } + + explicit PlatformThreadHandle(Handle handle) + : handle_(handle), + id_(0) { + } + + PlatformThreadHandle(Handle handle, + PlatformThreadId id) + : handle_(handle), + id_(id) { + } + + bool is_equal(const PlatformThreadHandle& other) const { + return handle_ == other.handle_; + } + + bool is_null() const { + return !handle_; + } + + Handle platform_handle() const { + return handle_; + } + + private: + friend class PlatformThread; + + Handle handle_; + PlatformThreadId id_; +}; + +const PlatformThreadId kInvalidThreadId(0); + +// Valid values for SetThreadPriority() +enum ThreadPriority{ + kThreadPriority_Normal, + // Suitable for low-latency, glitch-resistant audio. + kThreadPriority_RealtimeAudio, + // Suitable for threads which generate data for the display (at ~60Hz). + kThreadPriority_Display, + // Suitable for threads that shouldn't disrupt high priority work. + kThreadPriority_Background +}; + +// A namespace for low-level thread functions. +class BUTIL_EXPORT PlatformThread { + public: + // Implement this interface to run code on a background thread. Your + // ThreadMain method will be called on the newly created thread. + class BUTIL_EXPORT Delegate { + public: + virtual void ThreadMain() = 0; + + protected: + virtual ~Delegate() {} + }; + + // Gets the current thread id, which may be useful for logging purposes. + static PlatformThreadId CurrentId(); + + // Gets the current thread reference, which can be used to check if + // we're on the right thread quickly. + static PlatformThreadRef CurrentRef(); + + // Get the current handle. + static PlatformThreadHandle CurrentHandle(); + + // Yield the current thread so another thread can be scheduled. + static void YieldCurrentThread(); + + // Sleeps for the specified duration. + static void Sleep(butil::TimeDelta duration); + + // Sets the thread name visible to debuggers/tools. This has no effect + // otherwise. This name pointer is not copied internally. Thus, it must stay + // valid until the thread ends. + static void SetName(const char* name); + static void SetNameSimple(const char* name); + + // Gets the thread name, if previously set by SetName. + static const char* GetName(); + + // Creates a new thread. The |stack_size| parameter can be 0 to indicate + // that the default stack size should be used. Upon success, + // |*thread_handle| will be assigned a handle to the newly created thread, + // and |delegate|'s ThreadMain method will be executed on the newly created + // thread. + // NOTE: When you are done with the thread handle, you must call Join to + // release system resources associated with the thread. You must ensure that + // the Delegate object outlives the thread. + static bool Create(size_t stack_size, Delegate* delegate, + PlatformThreadHandle* thread_handle); + + // CreateWithPriority() does the same thing as Create() except the priority of + // the thread is set based on |priority|. Can be used in place of Create() + // followed by SetThreadPriority(). SetThreadPriority() has not been + // implemented on the Linux platform yet, this is the only way to get a high + // priority thread on Linux. + static bool CreateWithPriority(size_t stack_size, Delegate* delegate, + PlatformThreadHandle* thread_handle, + ThreadPriority priority); + + // CreateNonJoinable() does the same thing as Create() except the thread + // cannot be Join()'d. Therefore, it also does not output a + // PlatformThreadHandle. + static bool CreateNonJoinable(size_t stack_size, Delegate* delegate); + + // Joins with a thread created via the Create function. This function blocks + // the caller until the designated thread exits. This will invalidate + // |thread_handle|. + static void Join(PlatformThreadHandle thread_handle); + + static void SetThreadPriority(PlatformThreadHandle handle, + ThreadPriority priority); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(PlatformThread); +}; + +} // namespace butil + +#endif // BUTIL_THREADING_PLATFORM_THREAD_H_ diff --git a/src/butil/threading/platform_thread_freebsd.cc b/src/butil/threading/platform_thread_freebsd.cc new file mode 100644 index 0000000..ad5fa94 --- /dev/null +++ b/src/butil/threading/platform_thread_freebsd.cc @@ -0,0 +1,106 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/threading/platform_thread.h" + +#include +#include + +#include "butil/lazy_instance.h" +#include "butil/logging.h" +#include "butil/memory/scoped_ptr.h" +#include "butil/safe_strerror_posix.h" +#include "butil/threading/thread_id_name_manager.h" +#include "butil/threading/thread_restrictions.h" + +#if !defined(OS_NACL) +#include +#include +#include +#include +#include +#endif + +namespace butil { + +namespace { +int ThreadNiceValue(ThreadPriority priority) { + switch (priority) { + case kThreadPriority_RealtimeAudio: + return -10; + case kThreadPriority_Background: + return 10; + case kThreadPriority_Normal: + return 0; + case kThreadPriority_Display: + return -6; + default: + NOTREACHED() << "Unknown priority."; + return 0; + } +} +} // namespace + +// static +void PlatformThread::SetName(const char* name) { + ThreadIdNameManager::GetInstance()->SetName(CurrentId(), name); + + SetNameSimple(name); +} + +// static +void PlatformThread::SetNameSimple(const char* name) { +#if !defined(OS_NACL) + // On FreeBSD we can get the thread names to show up in the debugger by + // setting the process name for the LWP. We don't want to do this for the + // main thread because that would rename the process, causing tools like + // killall to stop working. + if (PlatformThread::CurrentId() == getpid()) + return; + setproctitle("%s", name); +#endif // !defined(OS_NACL) +} + +// static +void PlatformThread::SetThreadPriority(PlatformThreadHandle handle, + ThreadPriority priority) { +#if !defined(OS_NACL) + if (priority == kThreadPriority_RealtimeAudio) { + const struct sched_param kRealTimePrio = { 8 }; + if (pthread_setschedparam(pthread_self(), SCHED_RR, &kRealTimePrio) == 0) { + // Got real time priority, no need to set nice level. + return; + } + } + + // setpriority(2) will set a thread's priority if it is passed a tid as + // the 'process identifier', not affecting the rest of the threads in the + // process. Setting this priority will only succeed if the user has been + // granted permission to adjust nice values on the system. + DCHECK_NE(handle.id_, kInvalidThreadId); + const int kNiceSetting = ThreadNiceValue(priority); + if (setpriority(PRIO_PROCESS, handle.id_, kNiceSetting)) { + DVPLOG(1) << "Failed to set nice value of thread (" + << handle.id_ << ") to " << kNiceSetting; + } +#endif // !defined(OS_NACL) +} + +void InitThreading() {} + +void InitOnThread() {} + +void TerminateOnThread() {} + +size_t GetDefaultThreadStackSize(const pthread_attr_t& attributes) { +#if !defined(THREAD_SANITIZER) + return 0; +#else + // ThreadSanitizer bloats the stack heavily. Evidence has been that the + // default stack size isn't enough for some browser tests. + return 2 * (1 << 23); // 2 times 8192K (the default stack size on Linux). +#endif +} + +} // namespace butil diff --git a/src/butil/threading/platform_thread_linux.cc b/src/butil/threading/platform_thread_linux.cc new file mode 100644 index 0000000..7a70560 --- /dev/null +++ b/src/butil/threading/platform_thread_linux.cc @@ -0,0 +1,123 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/threading/platform_thread.h" + +#include +#include + +#include "butil/lazy_instance.h" +#include "butil/logging.h" +#include "butil/memory/scoped_ptr.h" +#include "butil/safe_strerror_posix.h" +#include "butil/threading/thread_id_name_manager.h" +#include "butil/threading/thread_restrictions.h" + +#if !defined(OS_NACL) +#include +#include +#include +#include +#include +#endif + +namespace butil { + +namespace { + +int ThreadNiceValue(ThreadPriority priority) { + switch (priority) { + case kThreadPriority_RealtimeAudio: + return -10; + case kThreadPriority_Background: + return 10; + case kThreadPriority_Normal: + return 0; + case kThreadPriority_Display: + return -6; + default: + NOTREACHED() << "Unknown priority."; + return 0; + } +} + +} // namespace + +// NOTE(gejun): PR_SET_NAME was added in 2.6.9, should be working on most of +// our machines, but missing from our linux headers. +#if !defined(PR_SET_NAME) +#define PR_SET_NAME 15 +#endif + +// static +void PlatformThread::SetName(const char* name) { + ThreadIdNameManager::GetInstance()->SetName(CurrentId(), name); + + SetNameSimple(name); +} +// static +void PlatformThread::SetNameSimple(const char* name) { +#if !defined(OS_NACL) + // On linux we can get the thread names to show up in the debugger by setting + // the process name for the LWP. We don't want to do this for the main + // thread because that would rename the process, causing tools like killall + // to stop working. + if (PlatformThread::CurrentId() == getpid()) + return; + + // http://0pointer.de/blog/projects/name-your-threads.html + // Set the name for the LWP (which gets truncated to 15 characters). + // Note that glibc also has a 'pthread_setname_np' api, but it may not be + // available everywhere and it's only benefit over using prctl directly is + // that it can set the name of threads other than the current thread. + int err = prctl(PR_SET_NAME, name); + // We expect EPERM failures in sandboxed processes, just ignore those. + if (err < 0 && errno != EPERM) + DPLOG(ERROR) << "prctl(PR_SET_NAME)"; +#endif // !defined(OS_NACL) +} + +// static +void PlatformThread::SetThreadPriority(PlatformThreadHandle handle, + ThreadPriority priority) { +#if !defined(OS_NACL) + if (priority == kThreadPriority_RealtimeAudio) { + const struct sched_param kRealTimePrio = { 8 }; + if (pthread_setschedparam(pthread_self(), SCHED_RR, &kRealTimePrio) == 0) { + // Got real time priority, no need to set nice level. + return; + } + } + + // setpriority(2) will set a thread's priority if it is passed a tid as + // the 'process identifier', not affecting the rest of the threads in the + // process. Setting this priority will only succeed if the user has been + // granted permission to adjust nice values on the system. + DCHECK_NE(handle.id_, kInvalidThreadId); + const int kNiceSetting = ThreadNiceValue(priority); + if (setpriority(PRIO_PROCESS, handle.id_, kNiceSetting)) { + DVPLOG(1) << "Failed to set nice value of thread (" + << handle.id_ << ") to " << kNiceSetting; + } +#endif // !defined(OS_NACL) +} + +void InitThreading() {} + +void InitOnThread() {} + +void TerminateOnThread() {} + +size_t GetDefaultThreadStackSize(const pthread_attr_t& attributes) { +#if !defined(THREAD_SANITIZER) && !defined(MEMORY_SANITIZER) + return 0; +#else + // ThreadSanitizer bloats the stack heavily. Evidence has been that the + // default stack size isn't enough for some browser tests. + // MemorySanitizer needs this as a temporary fix for http://crbug.com/353687 + return 2 * (1 << 23); // 2 times 8192K (the default stack size on Linux). +#endif +} + +} // namespace butil diff --git a/src/butil/threading/platform_thread_mac.mm b/src/butil/threading/platform_thread_mac.mm new file mode 100644 index 0000000..a9f74f4 --- /dev/null +++ b/src/butil/threading/platform_thread_mac.mm @@ -0,0 +1,223 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/threading/platform_thread.h" + +#import +#include +#include +#include +#include + +#include + +#include "butil/lazy_instance.h" +#include "butil/logging.h" +#include "butil/threading/thread_id_name_manager.h" +//#include "butil/tracked_objects.h" + +namespace butil { + +// If Cocoa is to be used on more than one thread, it must know that the +// application is multithreaded. Since it's possible to enter Cocoa code +// from threads created by pthread_thread_create, Cocoa won't necessarily +// be aware that the application is multithreaded. Spawning an NSThread is +// enough to get Cocoa to set up for multithreaded operation, so this is done +// if necessary before pthread_thread_create spawns any threads. +// +// http://developer.apple.com/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/chapter_4_section_4.html +void InitThreading() { + static BOOL multithreaded = [NSThread isMultiThreaded]; + if (!multithreaded) { + // +[NSObject class] is idempotent. + [NSThread detachNewThreadSelector:@selector(class) + toTarget:[NSObject class] + withObject:nil]; + multithreaded = YES; + + DCHECK([NSThread isMultiThreaded]); + } +} + +// static +void PlatformThread::SetName(const char* name) { + ThreadIdNameManager::GetInstance()->SetName(CurrentId(), name); + // TODO: add tracked_objects related headers + //tracked_objects::ThreadData::InitializeThreadContext(name); + + SetNameSimple(name); +} + +// static +void PlatformThread::SetNameSimple(const char* name) { + // Mac OS X does not expose the length limit of the name, so + // hardcode it. + const int kMaxNameLength = 63; + std::string shortened_name = std::string(name).substr(0, kMaxNameLength); + // pthread_setname() fails (harmlessly) in the sandbox, ignore when it does. + // See http://crbug.com/47058 + pthread_setname_np(shortened_name.c_str()); +} + +namespace { + +void SetPriorityNormal(mach_port_t mach_thread_id) { + // Make thread standard policy. + // Please note that this call could fail in rare cases depending + // on runtime conditions. + thread_standard_policy policy; + kern_return_t result = + thread_policy_set(mach_thread_id, + THREAD_STANDARD_POLICY, + reinterpret_cast(&policy), + THREAD_STANDARD_POLICY_COUNT); + + if (result != KERN_SUCCESS) + DVLOG(1) << "Fail to call thread_policy_set"; +} + +// Enables time-contraint policy and priority suitable for low-latency, +// glitch-resistant audio. +void SetPriorityRealtimeAudio(mach_port_t mach_thread_id) { + // Increase thread priority to real-time. + + // Please note that the thread_policy_set() calls may fail in + // rare cases if the kernel decides the system is under heavy load + // and is unable to handle boosting the thread priority. + // In these cases we just return early and go on with life. + + // Make thread fixed priority. + thread_extended_policy_data_t policy; + policy.timeshare = 0; // Set to 1 for a non-fixed thread. + kern_return_t result = + thread_policy_set(mach_thread_id, + THREAD_EXTENDED_POLICY, + reinterpret_cast(&policy), + THREAD_EXTENDED_POLICY_COUNT); + if (result != KERN_SUCCESS) { + DVLOG(1) << "Fail to call thread_policy_set"; + return; + } + + // Set to relatively high priority. + thread_precedence_policy_data_t precedence; + precedence.importance = 63; + result = thread_policy_set(mach_thread_id, + THREAD_PRECEDENCE_POLICY, + reinterpret_cast(&precedence), + THREAD_PRECEDENCE_POLICY_COUNT); + if (result != KERN_SUCCESS) { + DVLOG(1) << "Fail to call thread_policy_set"; + return; + } + + // Most important, set real-time constraints. + + // Define the guaranteed and max fraction of time for the audio thread. + // These "duty cycle" values can range from 0 to 1. A value of 0.5 + // means the scheduler would give half the time to the thread. + // These values have empirically been found to yield good behavior. + // Good means that audio performance is high and other threads won't starve. + const double kGuaranteedAudioDutyCycle = 0.75; + const double kMaxAudioDutyCycle = 0.85; + + // Define constants determining how much time the audio thread can + // use in a given time quantum. All times are in milliseconds. + + // About 128 frames @44.1KHz + const double kTimeQuantum = 2.9; + + // Time guaranteed each quantum. + const double kAudioTimeNeeded = kGuaranteedAudioDutyCycle * kTimeQuantum; + + // Maximum time each quantum. + const double kMaxTimeAllowed = kMaxAudioDutyCycle * kTimeQuantum; + + // Get the conversion factor from milliseconds to absolute time + // which is what the time-constraints call needs. + mach_timebase_info_data_t tb_info; + mach_timebase_info(&tb_info); + double ms_to_abs_time = + (static_cast(tb_info.denom) / tb_info.numer) * 1000000; + + thread_time_constraint_policy_data_t time_constraints; + time_constraints.period = kTimeQuantum * ms_to_abs_time; + time_constraints.computation = kAudioTimeNeeded * ms_to_abs_time; + time_constraints.constraint = kMaxTimeAllowed * ms_to_abs_time; + time_constraints.preemptible = 0; + + result = + thread_policy_set(mach_thread_id, + THREAD_TIME_CONSTRAINT_POLICY, + reinterpret_cast(&time_constraints), + THREAD_TIME_CONSTRAINT_POLICY_COUNT); + if (result != KERN_SUCCESS) { + DVLOG(1) << "Fail to call thread_policy_set"; + } + return; +} + +} // anonymous namespace + +// static +void PlatformThread::SetThreadPriority(PlatformThreadHandle handle, + ThreadPriority priority) { + // Convert from pthread_t to mach thread identifier. + mach_port_t mach_thread_id = pthread_mach_thread_np(handle.handle_); + + switch (priority) { + case kThreadPriority_Normal: + SetPriorityNormal(mach_thread_id); + break; + case kThreadPriority_RealtimeAudio: + SetPriorityRealtimeAudio(mach_thread_id); + break; + default: + NOTREACHED() << "Unknown priority."; + break; + } +} + +size_t GetDefaultThreadStackSize(const pthread_attr_t& attributes) { +#if defined(OS_IOS) + return 0; +#else + // The Mac OS X default for a pthread stack size is 512kB. + // Libc-594.1.4/pthreads/pthread.c's pthread_attr_init uses + // DEFAULT_STACK_SIZE for this purpose. + // + // 512kB isn't quite generous enough for some deeply recursive threads that + // otherwise request the default stack size by specifying 0. Here, adopt + // glibc's behavior as on Linux, which is to use the current stack size + // limit (ulimit -s) as the default stack size. See + // glibc-2.11.1/nptl/nptl-init.c's __pthread_initialize_minimal_internal. To + // avoid setting the limit below the Mac OS X default or the minimum usable + // stack size, these values are also considered. If any of these values + // can't be determined, or if stack size is unlimited (ulimit -s unlimited), + // stack_size is left at 0 to get the system default. + // + // Mac OS X normally only applies ulimit -s to the main thread stack. On + // contemporary OS X and Linux systems alike, this value is generally 8MB + // or in that neighborhood. + size_t default_stack_size = 0; + struct rlimit stack_rlimit; + if (pthread_attr_getstacksize(&attributes, &default_stack_size) == 0 && + getrlimit(RLIMIT_STACK, &stack_rlimit) == 0 && + stack_rlimit.rlim_cur != RLIM_INFINITY) { + default_stack_size = + std::max(std::max(default_stack_size, + static_cast(PTHREAD_STACK_MIN)), + static_cast(stack_rlimit.rlim_cur)); + } + return default_stack_size; +#endif +} + +void InitOnThread() { +} + +void TerminateOnThread() { +} + +} // namespace butil diff --git a/src/butil/threading/platform_thread_posix.cc b/src/butil/threading/platform_thread_posix.cc new file mode 100644 index 0000000..5e1403d --- /dev/null +++ b/src/butil/threading/platform_thread_posix.cc @@ -0,0 +1,237 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/threading/platform_thread.h" + +#include +#include + +#include "butil/lazy_instance.h" +#include "butil/logging.h" +#include "butil/memory/scoped_ptr.h" +#include "butil/safe_strerror_posix.h" +#include "butil/synchronization/waitable_event.h" +#include "butil/threading/thread_id_name_manager.h" +#include "butil/threading/thread_restrictions.h" + +#if defined(OS_MACOSX) +#include +#include +#endif + +#if defined(OS_LINUX) +#include +#include +#include +#include +#include +#endif + +namespace butil { + +void InitThreading(); +void InitOnThread(); +void TerminateOnThread(); +size_t GetDefaultThreadStackSize(const pthread_attr_t& attributes); + +namespace { + +struct ThreadParams { + ThreadParams() + : delegate(NULL), + joinable(false), + priority(kThreadPriority_Normal), + handle(NULL), + handle_set(false, false) { + } + + PlatformThread::Delegate* delegate; + bool joinable; + ThreadPriority priority; + PlatformThreadHandle* handle; + WaitableEvent handle_set; +}; + +void* ThreadFunc(void* params) { + butil::InitOnThread(); + ThreadParams* thread_params = static_cast(params); + + PlatformThread::Delegate* delegate = thread_params->delegate; + if (!thread_params->joinable) + butil::ThreadRestrictions::SetSingletonAllowed(false); + + if (thread_params->priority != kThreadPriority_Normal) { + PlatformThread::SetThreadPriority(PlatformThread::CurrentHandle(), + thread_params->priority); + } + + // Stash the id in the handle so the calling thread has a complete + // handle, and unblock the parent thread. + *(thread_params->handle) = PlatformThreadHandle(pthread_self(), + PlatformThread::CurrentId()); + thread_params->handle_set.Signal(); + + ThreadIdNameManager::GetInstance()->RegisterThread( + PlatformThread::CurrentHandle().platform_handle(), + PlatformThread::CurrentId()); + + delegate->ThreadMain(); + + ThreadIdNameManager::GetInstance()->RemoveName( + PlatformThread::CurrentHandle().platform_handle(), + PlatformThread::CurrentId()); + + butil::TerminateOnThread(); + return NULL; +} + +bool CreateThread(size_t stack_size, bool joinable, + PlatformThread::Delegate* delegate, + PlatformThreadHandle* thread_handle, + ThreadPriority priority) { + butil::InitThreading(); + + bool success = false; + pthread_attr_t attributes; + pthread_attr_init(&attributes); + + // Pthreads are joinable by default, so only specify the detached + // attribute if the thread should be non-joinable. + if (!joinable) { + pthread_attr_setdetachstate(&attributes, PTHREAD_CREATE_DETACHED); + } + + // Get a better default if available. + if (stack_size == 0) + stack_size = butil::GetDefaultThreadStackSize(attributes); + + if (stack_size > 0) + pthread_attr_setstacksize(&attributes, stack_size); + + ThreadParams params; + params.delegate = delegate; + params.joinable = joinable; + params.priority = priority; + params.handle = thread_handle; + + pthread_t handle; + int err = pthread_create(&handle, + &attributes, + ThreadFunc, + ¶ms); + success = !err; + if (!success) { + // Value of |handle| is undefined if pthread_create fails. + handle = 0; + errno = err; + PLOG(ERROR) << "pthread_create"; + } + + pthread_attr_destroy(&attributes); + + // Don't let this call complete until the thread id + // is set in the handle. + if (success) + params.handle_set.Wait(); + CHECK_EQ(handle, thread_handle->platform_handle()); + + return success; +} + +} // namespace + +// static +PlatformThreadId PlatformThread::CurrentId() { + // Pthreads doesn't have the concept of a thread ID, so we have to reach down + // into the kernel. +#if defined(OS_MACOSX) + return pthread_mach_thread_np(pthread_self()); +#elif defined(OS_LINUX) + return syscall(__NR_gettid); +#elif defined(OS_ANDROID) + return gettid(); +#elif defined(OS_SOLARIS) || defined(OS_QNX) + return pthread_self(); +#elif defined(OS_NACL) && defined(__GLIBC__) + return pthread_self(); +#elif defined(OS_NACL) && !defined(__GLIBC__) + // Pointers are 32-bits in NaCl. + return reinterpret_cast(pthread_self()); +#elif defined(OS_POSIX) + return reinterpret_cast(pthread_self()); +#endif +} + +// static +PlatformThreadRef PlatformThread::CurrentRef() { + return PlatformThreadRef(pthread_self()); +} + +// static +PlatformThreadHandle PlatformThread::CurrentHandle() { + return PlatformThreadHandle(pthread_self(), CurrentId()); +} + +// static +void PlatformThread::YieldCurrentThread() { + sched_yield(); +} + +// static +void PlatformThread::Sleep(TimeDelta duration) { + struct timespec sleep_time, remaining; + + // Break the duration into seconds and nanoseconds. + // NOTE: TimeDelta's microseconds are int64s while timespec's + // nanoseconds are longs, so this unpacking must prevent overflow. + sleep_time.tv_sec = duration.InSeconds(); + duration -= TimeDelta::FromSeconds(sleep_time.tv_sec); + sleep_time.tv_nsec = duration.InMicroseconds() * 1000; // nanoseconds + + while (nanosleep(&sleep_time, &remaining) == -1 && errno == EINTR) + sleep_time = remaining; +} + +// static +const char* PlatformThread::GetName() { + return ThreadIdNameManager::GetInstance()->GetName(CurrentId()); +} + +// static +bool PlatformThread::Create(size_t stack_size, Delegate* delegate, + PlatformThreadHandle* thread_handle) { + butil::ThreadRestrictions::ScopedAllowWait allow_wait; + return CreateThread(stack_size, true /* joinable thread */, + delegate, thread_handle, kThreadPriority_Normal); +} + +// static +bool PlatformThread::CreateWithPriority(size_t stack_size, Delegate* delegate, + PlatformThreadHandle* thread_handle, + ThreadPriority priority) { + butil::ThreadRestrictions::ScopedAllowWait allow_wait; + return CreateThread(stack_size, true, // joinable thread + delegate, thread_handle, priority); +} + +// static +bool PlatformThread::CreateNonJoinable(size_t stack_size, Delegate* delegate) { + PlatformThreadHandle unused; + + butil::ThreadRestrictions::ScopedAllowWait allow_wait; + bool result = CreateThread(stack_size, false /* non-joinable thread */, + delegate, &unused, kThreadPriority_Normal); + return result; +} + +// static +void PlatformThread::Join(PlatformThreadHandle thread_handle) { + // Joining another thread may block the current thread for a long time, since + // the thread referred to by |thread_handle| may still be running long-lived / + // blocking tasks. + butil::ThreadRestrictions::AssertIOAllowed(); + CHECK_EQ(0, pthread_join(thread_handle.handle_, NULL)); +} + +} // namespace butil diff --git a/src/butil/threading/simple_thread.cc b/src/butil/threading/simple_thread.cc new file mode 100644 index 0000000..40559ba --- /dev/null +++ b/src/butil/threading/simple_thread.cc @@ -0,0 +1,159 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/threading/simple_thread.h" + +#include "butil/logging.h" +#include "butil/strings/string_number_conversions.h" +#include "butil/threading/platform_thread.h" +#include "butil/threading/thread_restrictions.h" + +namespace butil { + +SimpleThread::SimpleThread(const std::string& name_prefix) + : name_prefix_(name_prefix), name_(name_prefix), + thread_(), event_(true, false), tid_(0), joined_(false) { +} + +SimpleThread::SimpleThread(const std::string& name_prefix, + const Options& options) + : name_prefix_(name_prefix), name_(name_prefix), options_(options), + thread_(), event_(true, false), tid_(0), joined_(false) { +} + +SimpleThread::~SimpleThread() { + DCHECK(HasBeenStarted()) << "SimpleThread was never started."; + DCHECK(HasBeenJoined()) << "SimpleThread destroyed without being Join()ed."; +} + +void SimpleThread::Start() { + DCHECK(!HasBeenStarted()) << "Tried to Start a thread multiple times."; + bool success = PlatformThread::Create(options_.stack_size(), this, &thread_); + DCHECK(success); + butil::ThreadRestrictions::ScopedAllowWait allow_wait; + event_.Wait(); // Wait for the thread to complete initialization. +} + +void SimpleThread::Join() { + DCHECK(HasBeenStarted()) << "Tried to Join a never-started thread."; + DCHECK(!HasBeenJoined()) << "Tried to Join a thread multiple times."; + PlatformThread::Join(thread_); + joined_ = true; +} + +bool SimpleThread::HasBeenStarted() { + butil::ThreadRestrictions::ScopedAllowWait allow_wait; + return event_.IsSignaled(); +} + +void SimpleThread::ThreadMain() { + tid_ = PlatformThread::CurrentId(); + // Construct our full name of the form "name_prefix_/TID". + name_.push_back('/'); + name_.append(IntToString(tid_)); + PlatformThread::SetName(name_.c_str()); + + // We've initialized our new thread, signal that we're done to Start(). + event_.Signal(); + + Run(); +} + +DelegateSimpleThread::DelegateSimpleThread(Delegate* delegate, + const std::string& name_prefix) + : SimpleThread(name_prefix), + delegate_(delegate) { +} + +DelegateSimpleThread::DelegateSimpleThread(Delegate* delegate, + const std::string& name_prefix, + const Options& options) + : SimpleThread(name_prefix, options), + delegate_(delegate) { +} + +DelegateSimpleThread::~DelegateSimpleThread() { +} + +void DelegateSimpleThread::Run() { + DCHECK(delegate_) << "Tried to call Run without a delegate (called twice?)"; + delegate_->Run(); + delegate_ = NULL; +} + +DelegateSimpleThreadPool::DelegateSimpleThreadPool( + const std::string& name_prefix, + int num_threads) + : name_prefix_(name_prefix), + num_threads_(num_threads), + dry_(true, false) { +} + +DelegateSimpleThreadPool::~DelegateSimpleThreadPool() { + DCHECK(threads_.empty()); + DCHECK(delegates_.empty()); + DCHECK(!dry_.IsSignaled()); +} + +void DelegateSimpleThreadPool::Start() { + DCHECK(threads_.empty()) << "Start() called with outstanding threads."; + for (int i = 0; i < num_threads_; ++i) { + DelegateSimpleThread* thread = new DelegateSimpleThread(this, name_prefix_); + thread->Start(); + threads_.push_back(thread); + } +} + +void DelegateSimpleThreadPool::JoinAll() { + DCHECK(!threads_.empty()) << "JoinAll() called with no outstanding threads."; + + // Tell all our threads to quit their worker loop. + AddWork(NULL, num_threads_); + + // Join and destroy all the worker threads. + for (int i = 0; i < num_threads_; ++i) { + threads_[i]->Join(); + delete threads_[i]; + } + threads_.clear(); + DCHECK(delegates_.empty()); +} + +void DelegateSimpleThreadPool::AddWork(Delegate* delegate, int repeat_count) { + AutoLock locked(lock_); + for (int i = 0; i < repeat_count; ++i) + delegates_.push(delegate); + // If we were empty, signal that we have work now. + if (!dry_.IsSignaled()) + dry_.Signal(); +} + +void DelegateSimpleThreadPool::Run() { + Delegate* work = NULL; + + while (true) { + dry_.Wait(); + { + AutoLock locked(lock_); + if (!dry_.IsSignaled()) + continue; + + DCHECK(!delegates_.empty()); + work = delegates_.front(); + delegates_.pop(); + + // Signal to any other threads that we're currently out of work. + if (delegates_.empty()) + dry_.Reset(); + } + + // A NULL delegate pointer signals us to quit. + if (!work) + break; + + work->Run(); + } +} + +} // namespace butil diff --git a/src/butil/threading/simple_thread.h b/src/butil/threading/simple_thread.h new file mode 100644 index 0000000..7eb6790 --- /dev/null +++ b/src/butil/threading/simple_thread.h @@ -0,0 +1,190 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// WARNING: You should probably be using Thread (thread.h) instead. Thread is +// Chrome's message-loop based Thread abstraction, and if you are a +// thread running in the browser, there will likely be assumptions +// that your thread will have an associated message loop. +// +// This is a simple thread interface that backs to a native operating system +// thread. You should use this only when you want a thread that does not have +// an associated MessageLoop. Unittesting is the best example of this. +// +// The simplest interface to use is DelegateSimpleThread, which will create +// a new thread, and execute the Delegate's virtual Run() in this new thread +// until it has completed, exiting the thread. +// +// NOTE: You *MUST* call Join on the thread to clean up the underlying thread +// resources. You are also responsible for destructing the SimpleThread object. +// It is invalid to destroy a SimpleThread while it is running, or without +// Start() having been called (and a thread never created). The Delegate +// object should live as long as a DelegateSimpleThread. +// +// Thread Safety: A SimpleThread is not completely thread safe. It is safe to +// access it from the creating thread or from the newly created thread. This +// implies that the creator thread should be the thread that calls Join. +// +// Example: +// class MyThreadRunner : public DelegateSimpleThread::Delegate { ... }; +// MyThreadRunner runner; +// DelegateSimpleThread thread(&runner, "good_name_here"); +// thread.Start(); +// // Start will return after the Thread has been successfully started and +// // initialized. The newly created thread will invoke runner->Run(), and +// // run until it returns. +// thread.Join(); // Wait until the thread has exited. You *MUST* Join! +// // The SimpleThread object is still valid, however you may not call Join +// // or Start again. + +#ifndef BUTIL_THREADING_SIMPLE_THREAD_H_ +#define BUTIL_THREADING_SIMPLE_THREAD_H_ + +#include +#include +#include + +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/compiler_specific.h" +#include "butil/threading/platform_thread.h" +#include "butil/synchronization/lock.h" +#include "butil/synchronization/waitable_event.h" + +namespace butil { + +// This is the base SimpleThread. You can derive from it and implement the +// virtual Run method, or you can use the DelegateSimpleThread interface. +class BUTIL_EXPORT SimpleThread : public PlatformThread::Delegate { + public: + class BUTIL_EXPORT Options { + public: + Options() : stack_size_(0) { } + ~Options() { } + + // We use the standard compiler-supplied copy constructor. + + // A custom stack size, or 0 for the system default. + void set_stack_size(size_t size) { stack_size_ = size; } + size_t stack_size() const { return stack_size_; } + private: + size_t stack_size_; + }; + + // Create a SimpleThread. |options| should be used to manage any specific + // configuration involving the thread creation and management. + // Every thread has a name, in the form of |name_prefix|/TID, for example + // "my_thread/321". The thread will not be created until Start() is called. + explicit SimpleThread(const std::string& name_prefix); + SimpleThread(const std::string& name_prefix, const Options& options); + + virtual ~SimpleThread(); + + virtual void Start(); + virtual void Join(); + + // Subclasses should override the Run method. + virtual void Run() = 0; + + // Return the thread name prefix, or "unnamed" if none was supplied. + std::string name_prefix() { return name_prefix_; } + + // Return the completed name including TID, only valid after Start(). + std::string name() { return name_; } + + // Return the thread id, only valid after Start(). + PlatformThreadId tid() { return tid_; } + + // Return True if Start() has ever been called. + bool HasBeenStarted(); + + // Return True if Join() has evern been called. + bool HasBeenJoined() { return joined_; } + + // Overridden from PlatformThread::Delegate: + virtual void ThreadMain() OVERRIDE; + + // Only set priorities with a careful understanding of the consequences. + // This is meant for very limited use cases. + void SetThreadPriority(ThreadPriority priority) { + PlatformThread::SetThreadPriority(thread_, priority); + } + + private: + const std::string name_prefix_; + std::string name_; + const Options options_; + PlatformThreadHandle thread_; // PlatformThread handle, invalid after Join! + WaitableEvent event_; // Signaled if Start() was ever called. + PlatformThreadId tid_; // The backing thread's id. + bool joined_; // True if Join has been called. +}; + +class BUTIL_EXPORT DelegateSimpleThread : public SimpleThread { + public: + class BUTIL_EXPORT Delegate { + public: + Delegate() { } + virtual ~Delegate() { } + virtual void Run() = 0; + }; + + DelegateSimpleThread(Delegate* delegate, + const std::string& name_prefix); + DelegateSimpleThread(Delegate* delegate, + const std::string& name_prefix, + const Options& options); + + virtual ~DelegateSimpleThread(); + virtual void Run() OVERRIDE; + private: + Delegate* delegate_; +}; + +// DelegateSimpleThreadPool allows you to start up a fixed number of threads, +// and then add jobs which will be dispatched to the threads. This is +// convenient when you have a lot of small work that you want done +// multi-threaded, but don't want to spawn a thread for each small bit of work. +// +// You just call AddWork() to add a delegate to the list of work to be done. +// JoinAll() will make sure that all outstanding work is processed, and wait +// for everything to finish. You can reuse a pool, so you can call Start() +// again after you've called JoinAll(). +class BUTIL_EXPORT DelegateSimpleThreadPool + : public DelegateSimpleThread::Delegate { + public: + typedef DelegateSimpleThread::Delegate Delegate; + + DelegateSimpleThreadPool(const std::string& name_prefix, int num_threads); + virtual ~DelegateSimpleThreadPool(); + + // Start up all of the underlying threads, and start processing work if we + // have any. + void Start(); + + // Make sure all outstanding work is finished, and wait for and destroy all + // of the underlying threads in the pool. + void JoinAll(); + + // It is safe to AddWork() any time, before or after Start(). + // Delegate* should always be a valid pointer, NULL is reserved internally. + void AddWork(Delegate* work, int repeat_count); + void AddWork(Delegate* work) { + AddWork(work, 1); + } + + // We implement the Delegate interface, for running our internal threads. + virtual void Run() OVERRIDE; + + private: + const std::string name_prefix_; + int num_threads_; + std::vector threads_; + std::queue delegates_; + butil::Lock lock_; // Locks delegates_ + WaitableEvent dry_; // Not signaled when there is no work to do. +}; + +} // namespace butil + +#endif // BUTIL_THREADING_SIMPLE_THREAD_H_ diff --git a/src/butil/threading/thread_checker.h b/src/butil/threading/thread_checker.h new file mode 100644 index 0000000..72a39f5 --- /dev/null +++ b/src/butil/threading/thread_checker.h @@ -0,0 +1,83 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_THREADING_THREAD_CHECKER_H_ +#define BUTIL_THREADING_THREAD_CHECKER_H_ + +// Apart from debug builds, we also enable the thread checker in +// builds with DCHECK_ALWAYS_ON so that trybots and waterfall bots +// with this define will get the same level of thread checking as +// debug bots. +// +// Note that this does not perfectly match situations where DCHECK is +// enabled. For example a non-official release build may have +// DCHECK_ALWAYS_ON undefined (and therefore ThreadChecker would be +// disabled) but have DCHECKs enabled at runtime. +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) +#define ENABLE_THREAD_CHECKER 1 +#else +#define ENABLE_THREAD_CHECKER 0 +#endif + +#include "butil/threading/thread_checker_impl.h" + +namespace butil { + +// Do nothing implementation, for use in release mode. +// +// Note: You should almost always use the ThreadChecker class to get the +// right version for your build configuration. +class ThreadCheckerDoNothing { + public: + bool CalledOnValidThread() const { + return true; + } + + void DetachFromThread() {} +}; + +// ThreadChecker is a helper class used to help verify that some methods of a +// class are called from the same thread. It provides identical functionality to +// butil::NonThreadSafe, but it is meant to be held as a member variable, rather +// than inherited from butil::NonThreadSafe. +// +// While inheriting from butil::NonThreadSafe may give a clear indication about +// the thread-safety of a class, it may also lead to violations of the style +// guide with regard to multiple inheritance. The choice between having a +// ThreadChecker member and inheriting from butil::NonThreadSafe should be based +// on whether: +// - Derived classes need to know the thread they belong to, as opposed to +// having that functionality fully encapsulated in the base class. +// - Derived classes should be able to reassign the base class to another +// thread, via DetachFromThread. +// +// If neither of these are true, then having a ThreadChecker member and calling +// CalledOnValidThread is the preferable solution. +// +// Example: +// class MyClass { +// public: +// void Foo() { +// DCHECK(thread_checker_.CalledOnValidThread()); +// ... (do stuff) ... +// } +// +// private: +// ThreadChecker thread_checker_; +// } +// +// In Release mode, CalledOnValidThread will always return true. +#if ENABLE_THREAD_CHECKER +class ThreadChecker : public ThreadCheckerImpl { +}; +#else +class ThreadChecker : public ThreadCheckerDoNothing { +}; +#endif // ENABLE_THREAD_CHECKER + +#undef ENABLE_THREAD_CHECKER + +} // namespace butil + +#endif // BUTIL_THREADING_THREAD_CHECKER_H_ diff --git a/src/butil/threading/thread_checker_impl.cc b/src/butil/threading/thread_checker_impl.cc new file mode 100644 index 0000000..ebce21d --- /dev/null +++ b/src/butil/threading/thread_checker_impl.cc @@ -0,0 +1,34 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/threading/thread_checker_impl.h" + +namespace butil { + +ThreadCheckerImpl::ThreadCheckerImpl() + : valid_thread_id_() { + EnsureThreadIdAssigned(); +} + +ThreadCheckerImpl::~ThreadCheckerImpl() {} + +bool ThreadCheckerImpl::CalledOnValidThread() const { + EnsureThreadIdAssigned(); + AutoLock auto_lock(lock_); + return valid_thread_id_ == PlatformThread::CurrentRef(); +} + +void ThreadCheckerImpl::DetachFromThread() { + AutoLock auto_lock(lock_); + valid_thread_id_ = PlatformThreadRef(); +} + +void ThreadCheckerImpl::EnsureThreadIdAssigned() const { + AutoLock auto_lock(lock_); + if (valid_thread_id_.is_null()) { + valid_thread_id_ = PlatformThread::CurrentRef(); + } +} + +} // namespace butil diff --git a/src/butil/threading/thread_checker_impl.h b/src/butil/threading/thread_checker_impl.h new file mode 100644 index 0000000..883f831 --- /dev/null +++ b/src/butil/threading/thread_checker_impl.h @@ -0,0 +1,43 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_THREADING_THREAD_CHECKER_IMPL_H_ +#define BUTIL_THREADING_THREAD_CHECKER_IMPL_H_ + +#include "butil/base_export.h" +#include "butil/synchronization/lock.h" +#include "butil/threading/platform_thread.h" + +namespace butil { + +// Real implementation of ThreadChecker, for use in debug mode, or +// for temporary use in release mode (e.g. to CHECK on a threading issue +// seen only in the wild). +// +// Note: You should almost always use the ThreadChecker class to get the +// right version for your build configuration. +class BUTIL_EXPORT ThreadCheckerImpl { + public: + ThreadCheckerImpl(); + ~ThreadCheckerImpl(); + + bool CalledOnValidThread() const; + + // Changes the thread that is checked for in CalledOnValidThread. This may + // be useful when an object may be created on one thread and then used + // exclusively on another thread. + void DetachFromThread(); + + private: + void EnsureThreadIdAssigned() const; + + mutable butil::Lock lock_; + // This is mutable so that CalledOnValidThread can set it. + // It's guarded by |lock_|. + mutable PlatformThreadRef valid_thread_id_; +}; + +} // namespace butil + +#endif // BUTIL_THREADING_THREAD_CHECKER_IMPL_H_ diff --git a/src/butil/threading/thread_collision_warner.cc b/src/butil/threading/thread_collision_warner.cc new file mode 100644 index 0000000..b65a139 --- /dev/null +++ b/src/butil/threading/thread_collision_warner.cc @@ -0,0 +1,64 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/threading/thread_collision_warner.h" + +#include "butil/logging.h" +#include "butil/threading/platform_thread.h" + +namespace butil { + +void DCheckAsserter::warn() { + NOTREACHED() << "Thread Collision"; +} + +static subtle::Atomic32 CurrentThread() { + const PlatformThreadId current_thread_id = PlatformThread::CurrentId(); + // We need to get the thread id into an atomic data type. This might be a + // truncating conversion, but any loss-of-information just increases the + // chance of a fault negative, not a false positive. + const subtle::Atomic32 atomic_thread_id = + static_cast(current_thread_id); + + return atomic_thread_id; +} + +void ThreadCollisionWarner::EnterSelf() { + // If the active thread is 0 then I'll write the current thread ID + // if two or more threads arrive here only one will succeed to + // write on valid_thread_id_ the current thread ID. + subtle::Atomic32 current_thread_id = CurrentThread(); + + int previous_value = subtle::NoBarrier_CompareAndSwap(&valid_thread_id_, + 0, + current_thread_id); + if (previous_value != 0 && previous_value != current_thread_id) { + // gotcha! a thread is trying to use the same class and that is + // not current thread. + asserter_->warn(); + } + + subtle::NoBarrier_AtomicIncrement(&counter_, 1); +} + +void ThreadCollisionWarner::Enter() { + subtle::Atomic32 current_thread_id = CurrentThread(); + + if (subtle::NoBarrier_CompareAndSwap(&valid_thread_id_, + 0, + current_thread_id) != 0) { + // gotcha! another thread is trying to use the same class. + asserter_->warn(); + } + + subtle::NoBarrier_AtomicIncrement(&counter_, 1); +} + +void ThreadCollisionWarner::Leave() { + if (subtle::Barrier_AtomicIncrement(&counter_, -1) == 0) { + subtle::NoBarrier_Store(&valid_thread_id_, 0); + } +} + +} // namespace butil diff --git a/src/butil/threading/thread_collision_warner.h b/src/butil/threading/thread_collision_warner.h new file mode 100644 index 0000000..c2de0b2 --- /dev/null +++ b/src/butil/threading/thread_collision_warner.h @@ -0,0 +1,245 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_THREADING_THREAD_COLLISION_WARNER_H_ +#define BUTIL_THREADING_THREAD_COLLISION_WARNER_H_ + +#include + +#include "butil/atomicops.h" +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/compiler_specific.h" + +// A helper class alongside macros to be used to verify assumptions about thread +// safety of a class. +// +// Example: Queue implementation non thread-safe but still usable if clients +// are synchronized somehow. +// +// In this case the macro DFAKE_SCOPED_LOCK has to be +// used, it checks that if a thread is inside the push/pop then +// noone else is still inside the pop/push +// +// class NonThreadSafeQueue { +// public: +// ... +// void push(int) { DFAKE_SCOPED_LOCK(push_pop_); ... } +// int pop() { DFAKE_SCOPED_LOCK(push_pop_); ... } +// ... +// private: +// DFAKE_MUTEX(push_pop_); +// }; +// +// +// Example: Queue implementation non thread-safe but still usable if clients +// are synchronized somehow, it calls a method to "protect" from +// a "protected" method +// +// In this case the macro DFAKE_SCOPED_RECURSIVE_LOCK +// has to be used, it checks that if a thread is inside the push/pop +// then noone else is still inside the pop/push +// +// class NonThreadSafeQueue { +// public: +// void push(int) { +// DFAKE_SCOPED_LOCK(push_pop_); +// ... +// } +// int pop() { +// DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_); +// bar(); +// ... +// } +// void bar() { DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_); ... } +// ... +// private: +// DFAKE_MUTEX(push_pop_); +// }; +// +// +// Example: Queue implementation not usable even if clients are synchronized, +// so only one thread in the class life cycle can use the two members +// push/pop. +// +// In this case the macro DFAKE_SCOPED_LOCK_THREAD_LOCKED pins the +// specified +// critical section the first time a thread enters push or pop, from +// that time on only that thread is allowed to execute push or pop. +// +// class NonThreadSafeQueue { +// public: +// ... +// void push(int) { DFAKE_SCOPED_LOCK_THREAD_LOCKED(push_pop_); ... } +// int pop() { DFAKE_SCOPED_LOCK_THREAD_LOCKED(push_pop_); ... } +// ... +// private: +// DFAKE_MUTEX(push_pop_); +// }; +// +// +// Example: Class that has to be constructed/destroyed on same thread, it has +// a "shareable" method (with external synchronization) and a not +// shareable method (even with external synchronization). +// +// In this case 3 Critical sections have to be defined +// +// class ExoticClass { +// public: +// ExoticClass() { DFAKE_SCOPED_LOCK_THREAD_LOCKED(ctor_dtor_); ... } +// ~ExoticClass() { DFAKE_SCOPED_LOCK_THREAD_LOCKED(ctor_dtor_); ... } +// +// void Shareable() { DFAKE_SCOPED_LOCK(shareable_section_); ... } +// void NotShareable() { DFAKE_SCOPED_LOCK_THREAD_LOCKED(ctor_dtor_); ... } +// ... +// private: +// DFAKE_MUTEX(ctor_dtor_); +// DFAKE_MUTEX(shareable_section_); +// }; + + +#if !defined(NDEBUG) + +// Defines a class member that acts like a mutex. It is used only as a +// verification tool. +#define DFAKE_MUTEX(obj) \ + mutable butil::ThreadCollisionWarner obj +// Asserts the call is never called simultaneously in two threads. Used at +// member function scope. +#define DFAKE_SCOPED_LOCK(obj) \ + butil::ThreadCollisionWarner::ScopedCheck s_check_##obj(&obj) +// Asserts the call is never called simultaneously in two threads. Used at +// member function scope. Same as DFAKE_SCOPED_LOCK but allows recursive locks. +#define DFAKE_SCOPED_RECURSIVE_LOCK(obj) \ + butil::ThreadCollisionWarner::ScopedRecursiveCheck sr_check_##obj(&obj) +// Asserts the code is always executed in the same thread. +#define DFAKE_SCOPED_LOCK_THREAD_LOCKED(obj) \ + butil::ThreadCollisionWarner::Check check_##obj(&obj) + +#else + +#define DFAKE_MUTEX(obj) typedef void InternalFakeMutexType##obj +#define DFAKE_SCOPED_LOCK(obj) ((void)0) +#define DFAKE_SCOPED_RECURSIVE_LOCK(obj) ((void)0) +#define DFAKE_SCOPED_LOCK_THREAD_LOCKED(obj) ((void)0) + +#endif + +namespace butil { + +// The class ThreadCollisionWarner uses an Asserter to notify the collision +// AsserterBase is the interfaces and DCheckAsserter is the default asserter +// used. During the unit tests is used another class that doesn't "DCHECK" +// in case of collision (check thread_collision_warner_unittests.cc) +struct BUTIL_EXPORT AsserterBase { + virtual ~AsserterBase() {} + virtual void warn() = 0; +}; + +struct BUTIL_EXPORT DCheckAsserter : public AsserterBase { + virtual ~DCheckAsserter() {} + virtual void warn() OVERRIDE; +}; + +class BUTIL_EXPORT ThreadCollisionWarner { + public: + // The parameter asserter is there only for test purpose + explicit ThreadCollisionWarner(AsserterBase* asserter = new DCheckAsserter()) + : valid_thread_id_(0), + counter_(0), + asserter_(asserter) {} + + ~ThreadCollisionWarner() { + delete asserter_; + } + + // This class is meant to be used through the macro + // DFAKE_SCOPED_LOCK_THREAD_LOCKED + // it doesn't leave the critical section, as opposed to ScopedCheck, + // because the critical section being pinned is allowed to be used only + // from one thread + class BUTIL_EXPORT Check { + public: + explicit Check(ThreadCollisionWarner* warner) + : warner_(warner) { + warner_->EnterSelf(); + } + + ~Check() {} + + private: + ThreadCollisionWarner* warner_; + + DISALLOW_COPY_AND_ASSIGN(Check); + }; + + // This class is meant to be used through the macro + // DFAKE_SCOPED_LOCK + class BUTIL_EXPORT ScopedCheck { + public: + explicit ScopedCheck(ThreadCollisionWarner* warner) + : warner_(warner) { + warner_->Enter(); + } + + ~ScopedCheck() { + warner_->Leave(); + } + + private: + ThreadCollisionWarner* warner_; + + DISALLOW_COPY_AND_ASSIGN(ScopedCheck); + }; + + // This class is meant to be used through the macro + // DFAKE_SCOPED_RECURSIVE_LOCK + class BUTIL_EXPORT ScopedRecursiveCheck { + public: + explicit ScopedRecursiveCheck(ThreadCollisionWarner* warner) + : warner_(warner) { + warner_->EnterSelf(); + } + + ~ScopedRecursiveCheck() { + warner_->Leave(); + } + + private: + ThreadCollisionWarner* warner_; + + DISALLOW_COPY_AND_ASSIGN(ScopedRecursiveCheck); + }; + + private: + // This method stores the current thread identifier and does a DCHECK + // if a another thread has already done it, it is safe if same thread + // calls this multiple time (recursion allowed). + void EnterSelf(); + + // Same as EnterSelf but recursion is not allowed. + void Enter(); + + // Removes the thread_id stored in order to allow other threads to + // call EnterSelf or Enter. + void Leave(); + + // This stores the thread id that is inside the critical section, if the + // value is 0 then no thread is inside. + volatile subtle::Atomic32 valid_thread_id_; + + // Counter to trace how many time a critical section was "pinned" + // (when allowed) in order to unpin it when counter_ reaches 0. + volatile subtle::Atomic32 counter_; + + // Here only for class unit tests purpose, during the test I need to not + // DCHECK but notify the collision with something else. + AsserterBase* asserter_; + + DISALLOW_COPY_AND_ASSIGN(ThreadCollisionWarner); +}; + +} // namespace butil + +#endif // BUTIL_THREADING_THREAD_COLLISION_WARNER_H_ diff --git a/src/butil/threading/thread_id_name_manager.cc b/src/butil/threading/thread_id_name_manager.cc new file mode 100644 index 0000000..cb0de0f --- /dev/null +++ b/src/butil/threading/thread_id_name_manager.cc @@ -0,0 +1,113 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/threading/thread_id_name_manager.h" + +#include +#include + +#include "butil/logging.h" +#include "butil/memory/singleton.h" +#include "butil/strings/string_util.h" + +namespace butil { +namespace { + +static const char kDefaultName[] = ""; +static std::string* g_default_name; + +} + +ThreadIdNameManager::ThreadIdNameManager() + : main_process_name_(NULL), + main_process_id_(kInvalidThreadId) { + g_default_name = new std::string(kDefaultName); + + AutoLock locked(lock_); + name_to_interned_name_[kDefaultName] = g_default_name; +} + +ThreadIdNameManager::~ThreadIdNameManager() { +} + +ThreadIdNameManager* ThreadIdNameManager::GetInstance() { + return Singleton >::get(); +} + +const char* ThreadIdNameManager::GetDefaultInternedString() { + return g_default_name->c_str(); +} + +void ThreadIdNameManager::RegisterThread(PlatformThreadHandle::Handle handle, + PlatformThreadId id) { + AutoLock locked(lock_); + thread_id_to_handle_[id] = handle; + thread_handle_to_interned_name_[handle] = + name_to_interned_name_[kDefaultName]; +} + +void ThreadIdNameManager::SetName(PlatformThreadId id, const char* name) { + std::string str_name(name); + + AutoLock locked(lock_); + NameToInternedNameMap::iterator iter = name_to_interned_name_.find(str_name); + std::string* leaked_str = NULL; + if (iter != name_to_interned_name_.end()) { + leaked_str = iter->second; + } else { + leaked_str = new std::string(str_name); + name_to_interned_name_[str_name] = leaked_str; + } + + ThreadIdToHandleMap::iterator id_to_handle_iter = + thread_id_to_handle_.find(id); + + // The main thread of a process will not be created as a Thread object which + // means there is no PlatformThreadHandler registered. + if (id_to_handle_iter == thread_id_to_handle_.end()) { + main_process_name_ = leaked_str; + main_process_id_ = id; + return; + } + thread_handle_to_interned_name_[id_to_handle_iter->second] = leaked_str; +} + +const char* ThreadIdNameManager::GetName(PlatformThreadId id) { + AutoLock locked(lock_); + + if (id == main_process_id_) + return main_process_name_->c_str(); + + ThreadIdToHandleMap::iterator id_to_handle_iter = + thread_id_to_handle_.find(id); + if (id_to_handle_iter == thread_id_to_handle_.end()) + return name_to_interned_name_[kDefaultName]->c_str(); + + ThreadHandleToInternedNameMap::iterator handle_to_name_iter = + thread_handle_to_interned_name_.find(id_to_handle_iter->second); + return handle_to_name_iter->second->c_str(); +} + +void ThreadIdNameManager::RemoveName(PlatformThreadHandle::Handle handle, + PlatformThreadId id) { + AutoLock locked(lock_); + ThreadHandleToInternedNameMap::iterator handle_to_name_iter = + thread_handle_to_interned_name_.find(handle); + + DCHECK(handle_to_name_iter != thread_handle_to_interned_name_.end()); + thread_handle_to_interned_name_.erase(handle_to_name_iter); + + ThreadIdToHandleMap::iterator id_to_handle_iter = + thread_id_to_handle_.find(id); + DCHECK((id_to_handle_iter!= thread_id_to_handle_.end())); + // The given |id| may have been re-used by the system. Make sure the + // mapping points to the provided |handle| before removal. + if (id_to_handle_iter->second != handle) + return; + + thread_id_to_handle_.erase(id_to_handle_iter); +} + +} // namespace butil diff --git a/src/butil/threading/thread_id_name_manager.h b/src/butil/threading/thread_id_name_manager.h new file mode 100644 index 0000000..16d7a60 --- /dev/null +++ b/src/butil/threading/thread_id_name_manager.h @@ -0,0 +1,67 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_THREADING_THREAD_ID_NAME_MANAGER_H_ +#define BUTIL_THREADING_THREAD_ID_NAME_MANAGER_H_ + +#include +#include + +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/synchronization/lock.h" +#include "butil/threading/platform_thread.h" + +template struct DefaultSingletonTraits; + +namespace butil { + +class BUTIL_EXPORT ThreadIdNameManager { + public: + static ThreadIdNameManager* GetInstance(); + + static const char* GetDefaultInternedString(); + + // Register the mapping between a thread |id| and |handle|. + void RegisterThread(PlatformThreadHandle::Handle handle, PlatformThreadId id); + + // Set the name for the given id. + void SetName(PlatformThreadId id, const char* name); + + // Get the name for the given id. + const char* GetName(PlatformThreadId id); + + // Remove the name for the given id. + void RemoveName(PlatformThreadHandle::Handle handle, PlatformThreadId id); + + private: + friend struct DefaultSingletonTraits; + + typedef std::map + ThreadIdToHandleMap; + typedef std::map + ThreadHandleToInternedNameMap; + typedef std::map NameToInternedNameMap; + + ThreadIdNameManager(); + ~ThreadIdNameManager(); + + // lock_ protects the name_to_interned_name_, thread_id_to_handle_ and + // thread_handle_to_interned_name_ maps. + Lock lock_; + + NameToInternedNameMap name_to_interned_name_; + ThreadIdToHandleMap thread_id_to_handle_; + ThreadHandleToInternedNameMap thread_handle_to_interned_name_; + + // Treat the main process specially as there is no PlatformThreadHandle. + std::string* main_process_name_; + PlatformThreadId main_process_id_; + + DISALLOW_COPY_AND_ASSIGN(ThreadIdNameManager); +}; + +} // namespace butil + +#endif // BUTIL_THREADING_THREAD_ID_NAME_MANAGER_H_ diff --git a/src/butil/threading/thread_local.h b/src/butil/threading/thread_local.h new file mode 100644 index 0000000..4eda937 --- /dev/null +++ b/src/butil/threading/thread_local.h @@ -0,0 +1,133 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// WARNING: Thread local storage is a bit tricky to get right. Please make +// sure that this is really the proper solution for what you're trying to +// achieve. Don't prematurely optimize, most likely you can just use a Lock. +// +// These classes implement a wrapper around the platform's TLS storage +// mechanism. On construction, they will allocate a TLS slot, and free the +// TLS slot on destruction. No memory management (creation or destruction) is +// handled. This means for uses of ThreadLocalPointer, you must correctly +// manage the memory yourself, these classes will not destroy the pointer for +// you. There are no at-thread-exit actions taken by these classes. +// +// ThreadLocalPointer wraps a Type*. It performs no creation or +// destruction, so memory management must be handled elsewhere. The first call +// to Get() on a thread will return NULL. You can update the pointer with a +// call to Set(). +// +// ThreadLocalBoolean wraps a bool. It will default to false if it has never +// been set otherwise with Set(). +// +// Thread Safety: An instance of ThreadLocalStorage is completely thread safe +// once it has been created. If you want to dynamically create an instance, +// you must of course properly deal with safety and race conditions. This +// means a function-level static initializer is generally inappropiate. +// +// In Android, the system TLS is limited, the implementation is backed with +// ThreadLocalStorage. +// +// Example usage: +// // My class is logically attached to a single thread. We cache a pointer +// // on the thread it was created on, so we can implement current(). +// MyClass::MyClass() { +// DCHECK(Singleton >::get()->Get() == NULL); +// Singleton >::get()->Set(this); +// } +// +// MyClass::~MyClass() { +// DCHECK(Singleton >::get()->Get() != NULL); +// Singleton >::get()->Set(NULL); +// } +// +// // Return the current MyClass associated with the calling thread, can be +// // NULL if there isn't a MyClass associated. +// MyClass* MyClass::current() { +// return Singleton >::get()->Get(); +// } + +#ifndef BUTIL_THREADING_THREAD_LOCAL_H_ +#define BUTIL_THREADING_THREAD_LOCAL_H_ + +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/threading/thread_local_storage.h" + +#if defined(OS_POSIX) +#include +#endif + +namespace butil { +namespace internal { + +// Helper functions that abstract the cross-platform APIs. Do not use directly. +struct BUTIL_EXPORT ThreadLocalPlatform { +#if defined(OS_WIN) + typedef unsigned long SlotType; +#elif defined(OS_ANDROID) + typedef ThreadLocalStorage::StaticSlot SlotType; +#elif defined(OS_POSIX) + typedef pthread_key_t SlotType; +#endif + + static void AllocateSlot(SlotType* slot); + static void FreeSlot(SlotType slot); + static void* GetValueFromSlot(SlotType slot); + static void SetValueInSlot(SlotType slot, void* value); +}; + +} // namespace internal + +template +class ThreadLocalPointer { + public: + ThreadLocalPointer() : slot_() { + internal::ThreadLocalPlatform::AllocateSlot(&slot_); + } + + ~ThreadLocalPointer() { + internal::ThreadLocalPlatform::FreeSlot(slot_); + } + + Type* Get() { + return static_cast( + internal::ThreadLocalPlatform::GetValueFromSlot(slot_)); + } + + void Set(Type* ptr) { + internal::ThreadLocalPlatform::SetValueInSlot( + slot_, const_cast(static_cast(ptr))); + } + + private: + typedef internal::ThreadLocalPlatform::SlotType SlotType; + + SlotType slot_; + + DISALLOW_COPY_AND_ASSIGN(ThreadLocalPointer); +}; + +class ThreadLocalBoolean { + public: + ThreadLocalBoolean() {} + ~ThreadLocalBoolean() {} + + bool Get() { + return tlp_.Get() != NULL; + } + + void Set(bool val) { + tlp_.Set(val ? this : NULL); + } + + private: + ThreadLocalPointer tlp_; + + DISALLOW_COPY_AND_ASSIGN(ThreadLocalBoolean); +}; + +} // namespace butil + +#endif // BUTIL_THREADING_THREAD_LOCAL_H_ diff --git a/src/butil/threading/thread_local_posix.cc b/src/butil/threading/thread_local_posix.cc new file mode 100644 index 0000000..8af300d --- /dev/null +++ b/src/butil/threading/thread_local_posix.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/threading/thread_local.h" + +#include + +#include "butil/logging.h" + +#if !defined(OS_ANDROID) + +namespace butil { +namespace internal { + +// static +void ThreadLocalPlatform::AllocateSlot(SlotType* slot) { + int error = pthread_key_create(slot, NULL); + CHECK_EQ(error, 0); +} + +// static +void ThreadLocalPlatform::FreeSlot(SlotType slot) { + int error = pthread_key_delete(slot); + DCHECK_EQ(0, error); +} + +// static +void* ThreadLocalPlatform::GetValueFromSlot(SlotType slot) { + return pthread_getspecific(slot); +} + +// static +void ThreadLocalPlatform::SetValueInSlot(SlotType slot, void* value) { + int error = pthread_setspecific(slot, value); + DCHECK_EQ(error, 0); +} + +} // namespace internal +} // namespace butil + +#endif // !defined(OS_ANDROID) diff --git a/src/butil/threading/thread_local_storage.cc b/src/butil/threading/thread_local_storage.cc new file mode 100644 index 0000000..6d6c42c --- /dev/null +++ b/src/butil/threading/thread_local_storage.cc @@ -0,0 +1,250 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/threading/thread_local_storage.h" + +#include "butil/atomicops.h" +#include "butil/logging.h" + +using butil::internal::PlatformThreadLocalStorage; + +namespace { +// In order to make TLS destructors work, we need to keep around a function +// pointer to the destructor for each slot. We keep this array of pointers in a +// global (static) array. +// We use the single OS-level TLS slot (giving us one pointer per thread) to +// hold a pointer to a per-thread array (table) of slots that we allocate to +// Chromium consumers. + +// g_native_tls_key is the one native TLS that we use. It stores our table. +butil::subtle::AtomicWord g_native_tls_key = + PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES; + +// g_last_used_tls_key is the high-water-mark of allocated thread local storage. +// Each allocation is an index into our g_tls_destructors[]. Each such index is +// assigned to the instance variable slot_ in a ThreadLocalStorage::Slot +// instance. We reserve the value slot_ == 0 to indicate that the corresponding +// instance of ThreadLocalStorage::Slot has been freed (i.e., destructor called, +// etc.). This reserved use of 0 is then stated as the initial value of +// g_last_used_tls_key, so that the first issued index will be 1. +butil::subtle::Atomic32 g_last_used_tls_key = 0; + +// The maximum number of 'slots' in our thread local storage stack. +const int kThreadLocalStorageSize = 256; + +// The maximum number of times to try to clear slots by calling destructors. +// Use pthread naming convention for clarity. +const int kMaxDestructorIterations = kThreadLocalStorageSize; + +// An array of destructor function pointers for the slots. If a slot has a +// destructor, it will be stored in its corresponding entry in this array. +// The elements are volatile to ensure that when the compiler reads the value +// to potentially call the destructor, it does so once, and that value is tested +// for null-ness and then used. Yes, that would be a weird de-optimization, +// but I can imagine some register machines where it was just as easy to +// re-fetch an array element, and I want to be sure a call to free the key +// (i.e., null out the destructor entry) that happens on a separate thread can't +// hurt the racy calls to the destructors on another thread. +volatile butil::ThreadLocalStorage::TLSDestructorFunc + g_tls_destructors[kThreadLocalStorageSize]; + +// This function is called to initialize our entire Chromium TLS system. +// It may be called very early, and we need to complete most all of the setup +// (initialization) before calling *any* memory allocator functions, which may +// recursively depend on this initialization. +// As a result, we use Atomics, and avoid anything (like a singleton) that might +// require memory allocations. +void** ConstructTlsVector() { + PlatformThreadLocalStorage::TLSKey key = + butil::subtle::NoBarrier_Load(&g_native_tls_key); + if (key == PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES) { + CHECK(PlatformThreadLocalStorage::AllocTLS(&key)); + + // The TLS_KEY_OUT_OF_INDEXES is used to find out whether the key is set or + // not in NoBarrier_CompareAndSwap, but Posix doesn't have invalid key, we + // define an almost impossible value be it. + // If we really get TLS_KEY_OUT_OF_INDEXES as value of key, just alloc + // another TLS slot. + if (key == PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES) { + PlatformThreadLocalStorage::TLSKey tmp = key; + CHECK(PlatformThreadLocalStorage::AllocTLS(&key) && + key != PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES); + PlatformThreadLocalStorage::FreeTLS(tmp); + } + // Atomically test-and-set the tls_key. If the key is + // TLS_KEY_OUT_OF_INDEXES, go ahead and set it. Otherwise, do nothing, as + // another thread already did our dirty work. + if (PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES != + butil::subtle::NoBarrier_CompareAndSwap(&g_native_tls_key, + PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES, key)) { + // We've been shortcut. Another thread replaced g_native_tls_key first so + // we need to destroy our index and use the one the other thread got + // first. + PlatformThreadLocalStorage::FreeTLS(key); + key = butil::subtle::NoBarrier_Load(&g_native_tls_key); + } + } + CHECK(!PlatformThreadLocalStorage::GetTLSValue(key)); + + // Some allocators, such as TCMalloc, make use of thread local storage. + // As a result, any attempt to call new (or malloc) will lazily cause such a + // system to initialize, which will include registering for a TLS key. If we + // are not careful here, then that request to create a key will call new back, + // and we'll have an infinite loop. We avoid that as follows: + // Use a stack allocated vector, so that we don't have dependence on our + // allocator until our service is in place. (i.e., don't even call new until + // after we're setup) + void* stack_allocated_tls_data[kThreadLocalStorageSize]; + memset(stack_allocated_tls_data, 0, sizeof(stack_allocated_tls_data)); + // Ensure that any rentrant calls change the temp version. + PlatformThreadLocalStorage::SetTLSValue(key, stack_allocated_tls_data); + + // Allocate an array to store our data. + void** tls_data = new void*[kThreadLocalStorageSize]; + memcpy(tls_data, stack_allocated_tls_data, sizeof(stack_allocated_tls_data)); + PlatformThreadLocalStorage::SetTLSValue(key, tls_data); + return tls_data; +} + +void OnThreadExitInternal(void* value) { + DCHECK(value); + void** tls_data = static_cast(value); + // Some allocators, such as TCMalloc, use TLS. As a result, when a thread + // terminates, one of the destructor calls we make may be to shut down an + // allocator. We have to be careful that after we've shutdown all of the + // known destructors (perchance including an allocator), that we don't call + // the allocator and cause it to resurrect itself (with no possibly destructor + // call to follow). We handle this problem as follows: + // Switch to using a stack allocated vector, so that we don't have dependence + // on our allocator after we have called all g_tls_destructors. (i.e., don't + // even call delete[] after we're done with destructors.) + void* stack_allocated_tls_data[kThreadLocalStorageSize]; + memcpy(stack_allocated_tls_data, tls_data, sizeof(stack_allocated_tls_data)); + // Ensure that any re-entrant calls change the temp version. + PlatformThreadLocalStorage::TLSKey key = + butil::subtle::NoBarrier_Load(&g_native_tls_key); + PlatformThreadLocalStorage::SetTLSValue(key, stack_allocated_tls_data); + delete[] tls_data; // Our last dependence on an allocator. + + int remaining_attempts = kMaxDestructorIterations; + bool need_to_scan_destructors = true; + while (need_to_scan_destructors) { + need_to_scan_destructors = false; + // Try to destroy the first-created-slot (which is slot 1) in our last + // destructor call. That user was able to function, and define a slot with + // no other services running, so perhaps it is a basic service (like an + // allocator) and should also be destroyed last. If we get the order wrong, + // then we'll itterate several more times, so it is really not that + // critical (but it might help). + butil::subtle::Atomic32 last_used_tls_key = + butil::subtle::NoBarrier_Load(&g_last_used_tls_key); + for (int slot = last_used_tls_key; slot > 0; --slot) { + void* value = stack_allocated_tls_data[slot]; + if (value == NULL) + continue; + + butil::ThreadLocalStorage::TLSDestructorFunc destructor = + g_tls_destructors[slot]; + if (destructor == NULL) + continue; + stack_allocated_tls_data[slot] = NULL; // pre-clear the slot. + destructor(value); + // Any destructor might have called a different service, which then set + // a different slot to a non-NULL value. Hence we need to check + // the whole vector again. This is a pthread standard. + need_to_scan_destructors = true; + } + if (--remaining_attempts <= 0) { + NOTREACHED(); // Destructors might not have been called. + break; + } + } + + // Remove our stack allocated vector. + PlatformThreadLocalStorage::SetTLSValue(key, NULL); +} + +} // namespace + +namespace butil { + +namespace internal { + +#if defined(OS_WIN) +void PlatformThreadLocalStorage::OnThreadExit() { + PlatformThreadLocalStorage::TLSKey key = + butil::subtle::NoBarrier_Load(&g_native_tls_key); + if (key == PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES) + return; + void *tls_data = GetTLSValue(key); + // Maybe we have never initialized TLS for this thread. + if (!tls_data) + return; + OnThreadExitInternal(tls_data); +} +#elif defined(OS_POSIX) +void PlatformThreadLocalStorage::OnThreadExit(void* value) { + OnThreadExitInternal(value); +} +#endif // defined(OS_WIN) + +} // namespace internal + +ThreadLocalStorage::Slot::Slot(TLSDestructorFunc destructor) { + initialized_ = false; + slot_ = 0; + Initialize(destructor); +} + +bool ThreadLocalStorage::StaticSlot::Initialize(TLSDestructorFunc destructor) { + PlatformThreadLocalStorage::TLSKey key = + butil::subtle::NoBarrier_Load(&g_native_tls_key); + if (key == PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES || + !PlatformThreadLocalStorage::GetTLSValue(key)) + ConstructTlsVector(); + + // Grab a new slot. + slot_ = butil::subtle::NoBarrier_AtomicIncrement(&g_last_used_tls_key, 1); + DCHECK_GT(slot_, 0); + CHECK_LT(slot_, kThreadLocalStorageSize); + + // Setup our destructor. + g_tls_destructors[slot_] = destructor; + initialized_ = true; + return true; +} + +void ThreadLocalStorage::StaticSlot::Free() { + // At this time, we don't reclaim old indices for TLS slots. + // So all we need to do is wipe the destructor. + DCHECK_GT(slot_, 0); + DCHECK_LT(slot_, kThreadLocalStorageSize); + g_tls_destructors[slot_] = NULL; + slot_ = 0; + initialized_ = false; +} + +void* ThreadLocalStorage::StaticSlot::Get() const { + void** tls_data = static_cast( + PlatformThreadLocalStorage::GetTLSValue( + butil::subtle::NoBarrier_Load(&g_native_tls_key))); + if (!tls_data) + tls_data = ConstructTlsVector(); + DCHECK_GT(slot_, 0); + DCHECK_LT(slot_, kThreadLocalStorageSize); + return tls_data[slot_]; +} + +void ThreadLocalStorage::StaticSlot::Set(void* value) { + void** tls_data = static_cast( + PlatformThreadLocalStorage::GetTLSValue( + butil::subtle::NoBarrier_Load(&g_native_tls_key))); + if (!tls_data) + tls_data = ConstructTlsVector(); + DCHECK_GT(slot_, 0); + DCHECK_LT(slot_, kThreadLocalStorageSize); + tls_data[slot_] = value; +} + +} // namespace butil diff --git a/src/butil/threading/thread_local_storage.h b/src/butil/threading/thread_local_storage.h new file mode 100644 index 0000000..d055bc5 --- /dev/null +++ b/src/butil/threading/thread_local_storage.h @@ -0,0 +1,144 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_THREADING_THREAD_LOCAL_STORAGE_H_ +#define BUTIL_THREADING_THREAD_LOCAL_STORAGE_H_ + +#include "butil/base_export.h" +#include "butil/basictypes.h" + +#if defined(OS_WIN) +#include +#elif defined(OS_POSIX) +#include +#endif + +namespace butil { + +namespace internal { + +// WARNING: You should *NOT* be using this class directly. +// PlatformThreadLocalStorage is low-level abstraction to the OS's TLS +// interface, you should instead be using ThreadLocalStorage::StaticSlot/Slot. +class BUTIL_EXPORT PlatformThreadLocalStorage { + public: + +#if defined(OS_WIN) + typedef unsigned long TLSKey; + enum { TLS_KEY_OUT_OF_INDEXES = TLS_OUT_OF_INDEXES }; +#elif defined(OS_POSIX) + typedef pthread_key_t TLSKey; + // The following is a "reserved key" which is used in our generic Chromium + // ThreadLocalStorage implementation. We expect that an OS will not return + // such a key, but if it is returned (i.e., the OS tries to allocate it) we + // will just request another key. + enum { TLS_KEY_OUT_OF_INDEXES = 0x7FFFFFFF }; +#endif + + // The following methods need to be supported on each OS platform, so that + // the Chromium ThreadLocalStore functionality can be constructed. + // Chromium will use these methods to acquire a single OS slot, and then use + // that to support a much larger number of Chromium slots (independent of the + // OS restrictions). + // The following returns true if it successfully is able to return an OS + // key in |key|. + static bool AllocTLS(TLSKey* key); + // Note: FreeTLS() doesn't have to be called, it is fine with this leak, OS + // might not reuse released slot, you might just reset the TLS value with + // SetTLSValue(). + static void FreeTLS(TLSKey key); + static void SetTLSValue(TLSKey key, void* value); + static void* GetTLSValue(TLSKey key); + + // Each platform (OS implementation) is required to call this method on each + // terminating thread when the thread is about to terminate. This method + // will then call all registered destructors for slots in Chromium + // ThreadLocalStorage, until there are no slot values remaining as having + // been set on this thread. + // Destructors may end up being called multiple times on a terminating + // thread, as other destructors may re-set slots that were previously + // destroyed. +#if defined(OS_WIN) + // Since Windows which doesn't support TLS destructor, the implementation + // should use GetTLSValue() to retrieve the value of TLS slot. + static void OnThreadExit(); +#elif defined(OS_POSIX) + // |Value| is the data stored in TLS slot, The implementation can't use + // GetTLSValue() to retrieve the value of slot as it has already been reset + // in Posix. + static void OnThreadExit(void* value); +#endif +}; + +} // namespace internal + +// Wrapper for thread local storage. This class doesn't do much except provide +// an API for portability. +class BUTIL_EXPORT ThreadLocalStorage { + public: + + // Prototype for the TLS destructor function, which can be optionally used to + // cleanup thread local storage on thread exit. 'value' is the data that is + // stored in thread local storage. + typedef void (*TLSDestructorFunc)(void* value); + + // StaticSlot uses its own struct initializer-list style static + // initialization, as base's LINKER_INITIALIZED requires a constructor and on + // some compilers (notably gcc 4.4) this still ends up needing runtime + // initialization. +#define TLS_INITIALIZER {false, 0} + + // A key representing one value stored in TLS. + // Initialize like + // ThreadLocalStorage::StaticSlot my_slot = TLS_INITIALIZER; + // If you're not using a static variable, use the convenience class + // ThreadLocalStorage::Slot (below) instead. + struct BUTIL_EXPORT StaticSlot { + // Set up the TLS slot. Called by the constructor. + // 'destructor' is a pointer to a function to perform per-thread cleanup of + // this object. If set to NULL, no cleanup is done for this TLS slot. + // Returns false on error. + bool Initialize(TLSDestructorFunc destructor); + + // Free a previously allocated TLS 'slot'. + // If a destructor was set for this slot, removes + // the destructor so that remaining threads exiting + // will not free data. + void Free(); + + // Get the thread-local value stored in slot 'slot'. + // Values are guaranteed to initially be zero. + void* Get() const; + + // Set the thread-local value stored in slot 'slot' to + // value 'value'. + void Set(void* value); + + bool initialized() const { return initialized_; } + + // The internals of this struct should be considered private. + bool initialized_; + int slot_; + }; + + // A convenience wrapper around StaticSlot with a constructor. Can be used + // as a member variable. + class BUTIL_EXPORT Slot : public StaticSlot { + public: + // Calls StaticSlot::Initialize(). + explicit Slot(TLSDestructorFunc destructor = NULL); + + private: + using StaticSlot::initialized_; + using StaticSlot::slot_; + + DISALLOW_COPY_AND_ASSIGN(Slot); + }; + + DISALLOW_COPY_AND_ASSIGN(ThreadLocalStorage); +}; + +} // namespace butil + +#endif // BUTIL_THREADING_THREAD_LOCAL_STORAGE_H_ diff --git a/src/butil/threading/thread_local_storage_posix.cc b/src/butil/threading/thread_local_storage_posix.cc new file mode 100644 index 0000000..b06007a --- /dev/null +++ b/src/butil/threading/thread_local_storage_posix.cc @@ -0,0 +1,34 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/threading/thread_local_storage.h" + +#include "butil/logging.h" + +namespace butil { + +namespace internal { + +bool PlatformThreadLocalStorage::AllocTLS(TLSKey* key) { + return !pthread_key_create(key, + butil::internal::PlatformThreadLocalStorage::OnThreadExit); +} + +void PlatformThreadLocalStorage::FreeTLS(TLSKey key) { + int ret = pthread_key_delete(key); + DCHECK_EQ(ret, 0); +} + +void* PlatformThreadLocalStorage::GetTLSValue(TLSKey key) { + return pthread_getspecific(key); +} + +void PlatformThreadLocalStorage::SetTLSValue(TLSKey key, void* value) { + int ret = pthread_setspecific(key, value); + DCHECK_EQ(ret, 0); +} + +} // namespace internal + +} // namespace butil diff --git a/src/butil/threading/thread_restrictions.cc b/src/butil/threading/thread_restrictions.cc new file mode 100644 index 0000000..d0b7cdb --- /dev/null +++ b/src/butil/threading/thread_restrictions.cc @@ -0,0 +1,85 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/threading/thread_restrictions.h" + +#if ENABLE_THREAD_RESTRICTIONS + +#include "butil/lazy_instance.h" +#include "butil/logging.h" +#include "butil/threading/thread_local.h" + +namespace butil { + +namespace { + +LazyInstance::Leaky + g_io_disallowed = LAZY_INSTANCE_INITIALIZER; + +LazyInstance::Leaky + g_singleton_disallowed = LAZY_INSTANCE_INITIALIZER; + +LazyInstance::Leaky + g_wait_disallowed = LAZY_INSTANCE_INITIALIZER; + +} // anonymous namespace + +// static +bool ThreadRestrictions::SetIOAllowed(bool allowed) { + bool previous_disallowed = g_io_disallowed.Get().Get(); + g_io_disallowed.Get().Set(!allowed); + return !previous_disallowed; +} + +// static +void ThreadRestrictions::AssertIOAllowed() { + if (g_io_disallowed.Get().Get()) { + LOG(FATAL) << + "Function marked as IO-only was called from a thread that " + "disallows IO! If this thread really should be allowed to " + "make IO calls, adjust the call to " + "butil::ThreadRestrictions::SetIOAllowed() in this thread's " + "startup."; + } +} + +// static +bool ThreadRestrictions::SetSingletonAllowed(bool allowed) { + bool previous_disallowed = g_singleton_disallowed.Get().Get(); + g_singleton_disallowed.Get().Set(!allowed); + return !previous_disallowed; +} + +// static +void ThreadRestrictions::AssertSingletonAllowed() { + if (g_singleton_disallowed.Get().Get()) { + LOG(FATAL) << "LazyInstance/Singleton is not allowed to be used on this " + << "thread. Most likely it's because this thread is not " + << "joinable, so AtExitManager may have deleted the object " + << "on shutdown, leading to a potential shutdown crash."; + } +} + +// static +void ThreadRestrictions::DisallowWaiting() { + g_wait_disallowed.Get().Set(true); +} + +// static +void ThreadRestrictions::AssertWaitAllowed() { + if (g_wait_disallowed.Get().Get()) { + LOG(FATAL) << "Waiting is not allowed to be used on this thread to prevent" + << "jank and deadlock."; + } +} + +bool ThreadRestrictions::SetWaitAllowed(bool allowed) { + bool previous_disallowed = g_wait_disallowed.Get().Get(); + g_wait_disallowed.Get().Set(!allowed); + return !previous_disallowed; +} + +} // namespace butil + +#endif // ENABLE_THREAD_RESTRICTIONS diff --git a/src/butil/threading/thread_restrictions.h b/src/butil/threading/thread_restrictions.h new file mode 100644 index 0000000..f5b2e52 --- /dev/null +++ b/src/butil/threading/thread_restrictions.h @@ -0,0 +1,257 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_THREADING_THREAD_RESTRICTIONS_H_ +#define BUTIL_THREADING_THREAD_RESTRICTIONS_H_ + +#include "butil/base_export.h" +#include "butil/basictypes.h" + +// See comment at top of thread_checker.h +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) +#define ENABLE_THREAD_RESTRICTIONS 1 +#else +#define ENABLE_THREAD_RESTRICTIONS 0 +#endif + +class AcceleratedPresenter; +class BrowserProcessImpl; +class HistogramSynchronizer; +class MetricsService; +class NativeBackendKWallet; +class ScopedAllowWaitForLegacyWebViewApi; +class TestingAutomationProvider; + +namespace browser_sync { +class NonFrontendDataTypeController; +class UIModelWorker; +} +namespace cc { +class CompletionEvent; +} +namespace chromeos { +class AudioMixerAlsa; +class BlockingMethodCaller; +namespace system { +class StatisticsProviderImpl; +} +} +namespace chrome_browser_net { +class Predictor; +} +namespace content { +class BrowserGpuChannelHostFactory; +class BrowserShutdownProfileDumper; +class BrowserTestBase; +class GLHelper; +class GpuChannelHost; +class NestedMessagePumpAndroid; +class RenderWidgetHelper; +class ScopedAllowWaitForAndroidLayoutTests; +class TextInputClientMac; +} +namespace dbus { +class Bus; +} +namespace disk_cache { +class BackendImpl; +class InFlightIO; +} +namespace media { +class AudioOutputController; +} +namespace net { +class FileStreamPosix; +class FileStreamWin; +namespace internal { +class AddressTrackerLinux; +} +} + +namespace remoting { +class AutoThread; +} + +namespace butil { + +namespace android { +class JavaHandlerThread; +} + +class SequencedWorkerPool; +class SimpleThread; +class Thread; +class ThreadTestHelper; + +// Certain behavior is disallowed on certain threads. ThreadRestrictions helps +// enforce these rules. Examples of such rules: +// +// * Do not do blocking IO (makes the thread janky) +// * Do not access Singleton/LazyInstance (may lead to shutdown crashes) +// +// Here's more about how the protection works: +// +// 1) If a thread should not be allowed to make IO calls, mark it: +// butil::ThreadRestrictions::SetIOAllowed(false); +// By default, threads *are* allowed to make IO calls. +// In Chrome browser code, IO calls should be proxied to the File thread. +// +// 2) If a function makes a call that will go out to disk, check whether the +// current thread is allowed: +// butil::ThreadRestrictions::AssertIOAllowed(); +// +// +// Style tip: where should you put AssertIOAllowed checks? It's best +// if you put them as close to the disk access as possible, at the +// lowest level. This rule is simple to follow and helps catch all +// callers. For example, if your function GoDoSomeBlockingDiskCall() +// only calls other functions in Chrome and not fopen(), you should go +// add the AssertIOAllowed checks in the helper functions. + +class BUTIL_EXPORT ThreadRestrictions { + public: + // Constructing a ScopedAllowIO temporarily allows IO for the current + // thread. Doing this is almost certainly always incorrect. + class BUTIL_EXPORT ScopedAllowIO { + public: + ScopedAllowIO() { previous_value_ = SetIOAllowed(true); } + ~ScopedAllowIO() { SetIOAllowed(previous_value_); } + private: + // Whether IO is allowed when the ScopedAllowIO was constructed. + bool previous_value_; + + DISALLOW_COPY_AND_ASSIGN(ScopedAllowIO); + }; + + // Constructing a ScopedAllowSingleton temporarily allows accessing for the + // current thread. Doing this is almost always incorrect. + class BUTIL_EXPORT ScopedAllowSingleton { + public: + ScopedAllowSingleton() { previous_value_ = SetSingletonAllowed(true); } + ~ScopedAllowSingleton() { SetSingletonAllowed(previous_value_); } + private: + // Whether singleton use is allowed when the ScopedAllowSingleton was + // constructed. + bool previous_value_; + + DISALLOW_COPY_AND_ASSIGN(ScopedAllowSingleton); + }; + +#if ENABLE_THREAD_RESTRICTIONS + // Set whether the current thread to make IO calls. + // Threads start out in the *allowed* state. + // Returns the previous value. + static bool SetIOAllowed(bool allowed); + + // Check whether the current thread is allowed to make IO calls, + // and DCHECK if not. See the block comment above the class for + // a discussion of where to add these checks. + static void AssertIOAllowed(); + + // Set whether the current thread can use singletons. Returns the previous + // value. + static bool SetSingletonAllowed(bool allowed); + + // Check whether the current thread is allowed to use singletons (Singleton / + // LazyInstance). DCHECKs if not. + static void AssertSingletonAllowed(); + + // Disable waiting on the current thread. Threads start out in the *allowed* + // state. Returns the previous value. + static void DisallowWaiting(); + + // Check whether the current thread is allowed to wait, and DCHECK if not. + static void AssertWaitAllowed(); +#else + // Inline the empty definitions of these functions so that they can be + // compiled out. + static bool SetIOAllowed(bool) { return true; } + static void AssertIOAllowed() {} + static bool SetSingletonAllowed(bool) { return true; } + static void AssertSingletonAllowed() {} + static void DisallowWaiting() {} + static void AssertWaitAllowed() {} +#endif + + private: + // DO NOT ADD ANY OTHER FRIEND STATEMENTS, talk to jam or brettw first. + // BEGIN ALLOWED USAGE. + friend class content::BrowserShutdownProfileDumper; + friend class content::BrowserTestBase; + friend class content::NestedMessagePumpAndroid; + friend class content::RenderWidgetHelper; + friend class content::ScopedAllowWaitForAndroidLayoutTests; + friend class ::HistogramSynchronizer; + friend class ::ScopedAllowWaitForLegacyWebViewApi; + friend class ::TestingAutomationProvider; + friend class cc::CompletionEvent; + friend class remoting::AutoThread; + friend class MessagePumpDefault; + friend class SequencedWorkerPool; + friend class SimpleThread; + friend class Thread; + friend class ThreadTestHelper; + friend class PlatformThread; + friend class android::JavaHandlerThread; + + // END ALLOWED USAGE. + // BEGIN USAGE THAT NEEDS TO BE FIXED. + friend class ::chromeos::AudioMixerAlsa; // http://crbug.com/125206 + friend class ::chromeos::BlockingMethodCaller; // http://crbug.com/125360 + friend class ::chromeos::system::StatisticsProviderImpl; // http://crbug.com/125385 + friend class browser_sync::NonFrontendDataTypeController; // http://crbug.com/19757 + friend class browser_sync::UIModelWorker; // http://crbug.com/19757 + friend class chrome_browser_net::Predictor; // http://crbug.com/78451 + friend class + content::BrowserGpuChannelHostFactory; // http://crbug.com/125248 + friend class content::GLHelper; // http://crbug.com/125415 + friend class content::GpuChannelHost; // http://crbug.com/125264 + friend class content::TextInputClientMac; // http://crbug.com/121917 + friend class dbus::Bus; // http://crbug.com/125222 + friend class disk_cache::BackendImpl; // http://crbug.com/74623 + friend class disk_cache::InFlightIO; // http://crbug.com/74623 + friend class media::AudioOutputController; // http://crbug.com/120973 + friend class net::FileStreamPosix; // http://crbug.com/115067 + friend class net::FileStreamWin; // http://crbug.com/115067 + friend class net::internal::AddressTrackerLinux; // http://crbug.com/125097 + friend class ::AcceleratedPresenter; // http://crbug.com/125391 + friend class ::BrowserProcessImpl; // http://crbug.com/125207 + friend class ::MetricsService; // http://crbug.com/124954 + friend class ::NativeBackendKWallet; // http://crbug.com/125331 + // END USAGE THAT NEEDS TO BE FIXED. + +#if ENABLE_THREAD_RESTRICTIONS + static bool SetWaitAllowed(bool allowed); +#else + static bool SetWaitAllowed(bool) { return true; } +#endif + + // FIXME(gejun): ScopedAllowWait can't be accessed by SequencedWorkerPool::Inner + // in gcc 3.4 (SequencedWorkerPool is a friend class) +#if __GNUC__ == 3 +public: +#endif + // Constructing a ScopedAllowWait temporarily allows waiting on the current + // thread. Doing this is almost always incorrect, which is why we limit who + // can use this through friend. If you find yourself needing to use this, find + // another way. Talk to jam or brettw. + class BUTIL_EXPORT ScopedAllowWait { + public: + ScopedAllowWait() { previous_value_ = SetWaitAllowed(true); } + ~ScopedAllowWait() { SetWaitAllowed(previous_value_); } + private: + // Whether singleton use is allowed when the ScopedAllowWait was + // constructed. + bool previous_value_; + + DISALLOW_COPY_AND_ASSIGN(ScopedAllowWait); + }; + +private: + DISALLOW_IMPLICIT_CONSTRUCTORS(ThreadRestrictions); +}; + +} // namespace butil + +#endif // BUTIL_THREADING_THREAD_RESTRICTIONS_H_ diff --git a/src/butil/threading/watchdog.cc b/src/butil/threading/watchdog.cc new file mode 100644 index 0000000..82d3789 --- /dev/null +++ b/src/butil/threading/watchdog.cc @@ -0,0 +1,184 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/threading/watchdog.h" + +#include "butil/compiler_specific.h" +#include "butil/lazy_instance.h" +#include "butil/logging.h" +#include "butil/threading/platform_thread.h" + +namespace butil { + +namespace { + +// When the debugger breaks (when we alarm), all the other alarms that are +// armed will expire (also alarm). To diminish this effect, we track any +// delay due to debugger breaks, and we *try* to adjust the effective start +// time of other alarms to step past the debugging break. +// Without this safety net, any alarm will typically trigger a host of follow +// on alarms from callers that specify old times. + +struct StaticData { + // Lock for access of static data... + Lock lock; + + // When did we last alarm and get stuck (for a while) in a debugger? + TimeTicks last_debugged_alarm_time; + + // How long did we sit on a break in the debugger? + TimeDelta last_debugged_alarm_delay; +}; + +LazyInstance::Leaky g_static_data = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +// Start thread running in a Disarmed state. +Watchdog::Watchdog(const TimeDelta& duration, + const std::string& thread_watched_name, + bool enabled) + : enabled_(enabled), + lock_(), + condition_variable_(&lock_), + state_(DISARMED), + duration_(duration), + thread_watched_name_(thread_watched_name), + delegate_(this) { + if (!enabled_) + return; // Don't start thread, or doing anything really. + enabled_ = PlatformThread::Create(0, // Default stack size. + &delegate_, + &handle_); + DCHECK(enabled_); +} + +// Notify watchdog thread, and wait for it to finish up. +Watchdog::~Watchdog() { + if (!enabled_) + return; + if (!IsJoinable()) + Cleanup(); + condition_variable_.Signal(); + PlatformThread::Join(handle_); +} + +void Watchdog::Cleanup() { + if (!enabled_) + return; + { + AutoLock lock(lock_); + state_ = SHUTDOWN; + } + condition_variable_.Signal(); +} + +bool Watchdog::IsJoinable() { + if (!enabled_) + return true; + AutoLock lock(lock_); + return (state_ == JOINABLE); +} + +void Watchdog::Arm() { + ArmAtStartTime(TimeTicks::Now()); +} + +void Watchdog::ArmSomeTimeDeltaAgo(const TimeDelta& time_delta) { + ArmAtStartTime(TimeTicks::Now() - time_delta); +} + +// Start clock for watchdog. +void Watchdog::ArmAtStartTime(const TimeTicks start_time) { + { + AutoLock lock(lock_); + start_time_ = start_time; + state_ = ARMED; + } + // Force watchdog to wake up, and go to sleep with the timer ticking with the + // proper duration. + condition_variable_.Signal(); +} + +// Disable watchdog so that it won't do anything when time expires. +void Watchdog::Disarm() { + AutoLock lock(lock_); + state_ = DISARMED; + // We don't need to signal, as the watchdog will eventually wake up, and it + // will check its state and time, and act accordingly. +} + +void Watchdog::Alarm() { + DVLOG(1) << "Watchdog alarmed for " << thread_watched_name_; +} + +//------------------------------------------------------------------------------ +// Internal private methods that the watchdog thread uses. + +void Watchdog::ThreadDelegate::ThreadMain() { + SetThreadName(); + TimeDelta remaining_duration; + StaticData* static_data = g_static_data.Pointer(); + while (1) { + AutoLock lock(watchdog_->lock_); + while (DISARMED == watchdog_->state_) + watchdog_->condition_variable_.Wait(); + if (SHUTDOWN == watchdog_->state_) { + watchdog_->state_ = JOINABLE; + return; + } + DCHECK(ARMED == watchdog_->state_); + remaining_duration = watchdog_->duration_ - + (TimeTicks::Now() - watchdog_->start_time_); + if (remaining_duration.InMilliseconds() > 0) { + // Spurios wake? Timer drifts? Go back to sleep for remaining time. + watchdog_->condition_variable_.TimedWait(remaining_duration); + continue; + } + // We overslept, so this seems like a real alarm. + // Watch out for a user that stopped the debugger on a different alarm! + { + AutoLock static_lock(static_data->lock); + if (static_data->last_debugged_alarm_time > watchdog_->start_time_) { + // False alarm: we started our clock before the debugger break (last + // alarm time). + watchdog_->start_time_ += static_data->last_debugged_alarm_delay; + if (static_data->last_debugged_alarm_time > watchdog_->start_time_) + // Too many alarms must have taken place. + watchdog_->state_ = DISARMED; + continue; + } + } + watchdog_->state_ = DISARMED; // Only alarm at most once. + TimeTicks last_alarm_time = TimeTicks::Now(); + { + AutoUnlock lock(watchdog_->lock_); + watchdog_->Alarm(); // Set a break point here to debug on alarms. + } + TimeDelta last_alarm_delay = TimeTicks::Now() - last_alarm_time; + if (last_alarm_delay <= TimeDelta::FromMilliseconds(2)) + continue; + // Ignore race of two alarms/breaks going off at roughly the same time. + AutoLock static_lock(static_data->lock); + // This was a real debugger break. + static_data->last_debugged_alarm_time = last_alarm_time; + static_data->last_debugged_alarm_delay = last_alarm_delay; + } +} + +void Watchdog::ThreadDelegate::SetThreadName() const { + std::string name = watchdog_->thread_watched_name_ + " Watchdog"; + PlatformThread::SetName(name.c_str()); + DVLOG(1) << "Watchdog active: " << name; +} + +// static +void Watchdog::ResetStaticData() { + StaticData* static_data = g_static_data.Pointer(); + AutoLock lock(static_data->lock); + static_data->last_debugged_alarm_time = TimeTicks(); + static_data->last_debugged_alarm_delay = TimeDelta(); +} + +} // namespace butil diff --git a/src/butil/threading/watchdog.h b/src/butil/threading/watchdog.h new file mode 100644 index 0000000..8405e41 --- /dev/null +++ b/src/butil/threading/watchdog.h @@ -0,0 +1,94 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// The Watchdog class creates a second thread that can Alarm if a specific +// duration of time passes without proper attention. The duration of time is +// specified at construction time. The Watchdog may be used many times by +// simply calling Arm() (to start timing) and Disarm() (to reset the timer). +// The Watchdog is typically used under a debugger, where the stack traces on +// other threads can be examined if/when the Watchdog alarms. + +// Some watchdogs will be enabled or disabled via command line switches. To +// facilitate such code, an "enabled" argument for the constuctor can be used +// to permanently disable the watchdog. Disabled watchdogs don't even spawn +// a second thread, and their methods call (Arm() and Disarm()) return very +// quickly. + +#ifndef BUTIL_THREADING_WATCHDOG_H_ +#define BUTIL_THREADING_WATCHDOG_H_ + +#include + +#include "butil/base_export.h" +#include "butil/compiler_specific.h" +#include "butil/synchronization/condition_variable.h" +#include "butil/synchronization/lock.h" +#include "butil/threading/platform_thread.h" +#include "butil/time/time.h" + +namespace butil { + +class BUTIL_EXPORT Watchdog { + public: + // Constructor specifies how long the Watchdog will wait before alarming. + Watchdog(const TimeDelta& duration, + const std::string& thread_watched_name, + bool enabled); + virtual ~Watchdog(); + + // Notify watchdog thread to finish up. Sets the state_ to SHUTDOWN. + void Cleanup(); + + // Returns true if we state_ is JOINABLE (which indicates that Watchdog has + // exited). + bool IsJoinable(); + + // Start timing, and alarm when time expires (unless we're disarm()ed.) + void Arm(); // Arm starting now. + void ArmSomeTimeDeltaAgo(const TimeDelta& time_delta); + void ArmAtStartTime(const TimeTicks start_time); + + // Reset time, and do not set off the alarm. + void Disarm(); + + // Alarm is called if the time expires after an Arm() without someone calling + // Disarm(). This method can be overridden to create testable classes. + virtual void Alarm(); + + // Reset static data to initial state. Useful for tests, to ensure + // they are independent. + static void ResetStaticData(); + + private: + class ThreadDelegate : public PlatformThread::Delegate { + public: + explicit ThreadDelegate(Watchdog* watchdog) : watchdog_(watchdog) { + } + virtual void ThreadMain() OVERRIDE; + private: + void SetThreadName() const; + + Watchdog* watchdog_; + }; + + enum State {ARMED, DISARMED, SHUTDOWN, JOINABLE }; + + bool enabled_; + + Lock lock_; // Mutex for state_. + ConditionVariable condition_variable_; + State state_; + const TimeDelta duration_; // How long after start_time_ do we alarm? + const std::string thread_watched_name_; + PlatformThreadHandle handle_; + ThreadDelegate delegate_; // Store it, because it must outlive the thread. + + TimeTicks start_time_; // Start of epoch, and alarm after duration_. + + DISALLOW_COPY_AND_ASSIGN(Watchdog); +}; + +} // namespace butil + +#endif // BUTIL_THREADING_WATCHDOG_H_ diff --git a/src/butil/time.cpp b/src/butil/time.cpp new file mode 100644 index 0000000..2c726d9 --- /dev/null +++ b/src/butil/time.cpp @@ -0,0 +1,158 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Fri Aug 29 15:01:15 CST 2014 + +#include // close +#include // open +#include // ^ +#include // ^ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include // memmem +#undef _GNU_SOURCE + +#include "butil/time.h" + +#if defined(NO_CLOCK_GETTIME_IN_MAC) +#include // mach_absolute_time +#include // mach_timebase_info +#include // pthread_once +#include // exit + +static mach_timebase_info_data_t s_timebase; +static timespec s_init_time; +static uint64_t s_init_ticks; +static pthread_once_t s_init_clock_once = PTHREAD_ONCE_INIT; + +static void InitClock() { + if (mach_timebase_info(&s_timebase) != 0) { + exit(1); + } + timeval now; + if (gettimeofday(&now, NULL) != 0) { + exit(1); + } + s_init_time.tv_sec = now.tv_sec; + s_init_time.tv_nsec = now.tv_usec * 1000L; + s_init_ticks = mach_absolute_time(); +} + +int clock_gettime(clockid_t id, timespec* time) { + if (pthread_once(&s_init_clock_once, InitClock) != 0) { + exit(1); + } + uint64_t clock = mach_absolute_time() - s_init_ticks; + uint64_t elapsed = clock * (uint64_t)s_timebase.numer / (uint64_t)s_timebase.denom; + *time = s_init_time; + time->tv_sec += elapsed / 1000000000L; + time->tv_nsec += elapsed % 1000000000L; + time->tv_sec += time->tv_nsec / 1000000000L; + time->tv_nsec = time->tv_nsec % 1000000000L; + return 0; +} + +#endif + +namespace butil { + +int64_t monotonic_time_ns() { + // MONOTONIC_RAW is slower than MONOTONIC in linux 2.6.32, trying to + // use the RAW version does not make sense anymore. + // NOTE: Not inline to keep ABI-compatible with previous versions. + timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return now.tv_sec * 1000000000L + now.tv_nsec; +} + +namespace detail { + +// read_cpu_frequency() is modified from source code of glibc. +int64_t read_cpu_frequency(bool* invariant_tsc) { + /* We read the information from the /proc filesystem. It contains at + least one line like + cpu MHz : 497.840237 + or also + cpu MHz : 497.841 + We search for this line and convert the number in an integer. */ + + const int fd = open("/proc/cpuinfo", O_RDONLY); + if (fd < 0) { + return 0; + } + + int64_t result = 0; + char buf[4096]; // should be enough + const ssize_t n = read(fd, buf, sizeof(buf)); + if (n > 0) { + char *mhz = static_cast(memmem(buf, n, "cpu MHz", 7)); + + if (mhz != NULL) { + char *endp = buf + n; + int seen_decpoint = 0; + int ndigits = 0; + + /* Search for the beginning of the string. */ + while (mhz < endp && (*mhz < '0' || *mhz > '9') && *mhz != '\n') { + ++mhz; + } + while (mhz < endp && *mhz != '\n') { + if (*mhz >= '0' && *mhz <= '9') { + result *= 10; + result += *mhz - '0'; + if (seen_decpoint) + ++ndigits; + } else if (*mhz == '.') { + seen_decpoint = 1; + } + ++mhz; + } + + /* Compensate for missing digits at the end. */ + while (ndigits++ < 6) { + result *= 10; + } + } + + if (invariant_tsc) { + char* flags_pos = static_cast(memmem(buf, n, "flags", 5)); + *invariant_tsc = + (flags_pos && + memmem(flags_pos, buf + n - flags_pos, "constant_tsc", 12) && + memmem(flags_pos, buf + n - flags_pos, "nonstop_tsc", 11)); + } + } + close (fd); + return result; +} + +// Return value must be >= 0 +int64_t read_invariant_cpu_frequency() { + bool invariant_tsc = false; + const int64_t freq = read_cpu_frequency(&invariant_tsc); + if (!invariant_tsc || freq < 0) { + return 0; + } + return freq; +} + +int64_t invariant_cpu_freq = -1; +} // namespace detail + +} // namespace butil diff --git a/src/butil/time.h b/src/butil/time.h new file mode 100644 index 0000000..c57000e --- /dev/null +++ b/src/butil/time.h @@ -0,0 +1,427 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Wed Aug 11 10:38:17 2010 + +// Measuring time + +#ifndef BUTIL_BAIDU_TIME_H +#define BUTIL_BAIDU_TIME_H + +#include // timespec, clock_gettime +#include // timeval, gettimeofday +#include // int64_t, uint64_t + +#if defined(NO_CLOCK_GETTIME_IN_MAC) +#include +# define CLOCK_REALTIME CALENDAR_CLOCK +# define CLOCK_MONOTONIC SYSTEM_CLOCK + +typedef int clockid_t; + +// clock_gettime is not available in MacOS < 10.12 +int clock_gettime(clockid_t id, timespec* time); + +#endif + +namespace butil { + +// Get SVN revision of this copy. +const char* last_changed_revision(); + +// ---------------------- +// timespec manipulations +// ---------------------- + +// Let tm->tv_nsec be in [0, 1,000,000,000) if it's not. +inline void timespec_normalize(timespec* tm) { + if (tm->tv_nsec >= 1000000000L) { + const int64_t added_sec = tm->tv_nsec / 1000000000L; + tm->tv_sec += added_sec; + tm->tv_nsec -= added_sec * 1000000000L; + } else if (tm->tv_nsec < 0) { + const int64_t sub_sec = (tm->tv_nsec - 999999999L) / 1000000000L; + tm->tv_sec += sub_sec; + tm->tv_nsec -= sub_sec * 1000000000L; + } +} + +// Add timespec |span| into timespec |*tm|. +inline void timespec_add(timespec *tm, const timespec& span) { + tm->tv_sec += span.tv_sec; + tm->tv_nsec += span.tv_nsec; + timespec_normalize(tm); +} + +// Minus timespec |span| from timespec |*tm|. +// tm->tv_nsec will be inside [0, 1,000,000,000) +inline void timespec_minus(timespec *tm, const timespec& span) { + tm->tv_sec -= span.tv_sec; + tm->tv_nsec -= span.tv_nsec; + timespec_normalize(tm); +} + +// ------------------------------------------------------------------ +// Get the timespec after specified duration from |start_time| +// ------------------------------------------------------------------ +inline timespec nanoseconds_from(timespec start_time, int64_t nanoseconds) { + start_time.tv_nsec += nanoseconds; + timespec_normalize(&start_time); + return start_time; +} + +inline timespec microseconds_from(timespec start_time, int64_t microseconds) { + return nanoseconds_from(start_time, microseconds * 1000L); +} + +inline timespec milliseconds_from(timespec start_time, int64_t milliseconds) { + return nanoseconds_from(start_time, milliseconds * 1000000L); +} + +inline timespec seconds_from(timespec start_time, int64_t seconds) { + return nanoseconds_from(start_time, seconds * 1000000000L); +} + +// -------------------------------------------------------------------- +// Get the timespec after specified duration from now (CLOCK_REALTIME) +// -------------------------------------------------------------------- +inline timespec nanoseconds_from_now(int64_t nanoseconds) { + timespec time; + clock_gettime(CLOCK_REALTIME, &time); + return nanoseconds_from(time, nanoseconds); +} + +inline timespec microseconds_from_now(int64_t microseconds) { + return nanoseconds_from_now(microseconds * 1000L); +} + +inline timespec milliseconds_from_now(int64_t milliseconds) { + return nanoseconds_from_now(milliseconds * 1000000L); +} + +inline timespec seconds_from_now(int64_t seconds) { + return nanoseconds_from_now(seconds * 1000000000L); +} + +inline timespec timespec_from_now(const timespec& span) { + timespec time; + clock_gettime(CLOCK_REALTIME, &time); + timespec_add(&time, span); + return time; +} + +// --------------------------------------------------------------------- +// Convert timespec to and from a single integer. +// For conversions between timespec and timeval, use TIMEVAL_TO_TIMESPEC +// and TIMESPEC_TO_TIMEVAL defined in +// ---------------------------------------------------------------------1 +inline int64_t timespec_to_nanoseconds(const timespec& ts) { + return ts.tv_sec * 1000000000L + ts.tv_nsec; +} + +inline int64_t timespec_to_microseconds(const timespec& ts) { + return timespec_to_nanoseconds(ts) / 1000L; +} + +inline int64_t timespec_to_milliseconds(const timespec& ts) { + return timespec_to_nanoseconds(ts) / 1000000L; +} + +inline int64_t timespec_to_seconds(const timespec& ts) { + return timespec_to_nanoseconds(ts) / 1000000000L; +} + +inline timespec nanoseconds_to_timespec(int64_t ns) { + timespec ts; + ts.tv_sec = ns / 1000000000L; + ts.tv_nsec = ns - ts.tv_sec * 1000000000L; + return ts; +} + +inline timespec microseconds_to_timespec(int64_t us) { + return nanoseconds_to_timespec(us * 1000L); +} + +inline timespec milliseconds_to_timespec(int64_t ms) { + return nanoseconds_to_timespec(ms * 1000000L); +} + +inline timespec seconds_to_timespec(int64_t s) { + return nanoseconds_to_timespec(s * 1000000000L); +} + +// --------------------------------------------------------------------- +// Convert timeval to and from a single integer. +// For conversions between timespec and timeval, use TIMEVAL_TO_TIMESPEC +// and TIMESPEC_TO_TIMEVAL defined in +// --------------------------------------------------------------------- +inline int64_t timeval_to_microseconds(const timeval& tv) { + return tv.tv_sec * 1000000L + tv.tv_usec; +} + +inline int64_t timeval_to_milliseconds(const timeval& tv) { + return timeval_to_microseconds(tv) / 1000L; +} + +inline int64_t timeval_to_seconds(const timeval& tv) { + return timeval_to_microseconds(tv) / 1000000L; +} + +inline timeval microseconds_to_timeval(int64_t us) { + timeval tv; + tv.tv_sec = us / 1000000L; + tv.tv_usec = us - tv.tv_sec * 1000000L; + return tv; +} + +inline timeval milliseconds_to_timeval(int64_t ms) { + return microseconds_to_timeval(ms * 1000L); +} + +inline timeval seconds_to_timeval(int64_t s) { + return microseconds_to_timeval(s * 1000000L); +} + +// --------------------------------------------------------------- +// Get system-wide monotonic time. +// --------------------------------------------------------------- +extern int64_t monotonic_time_ns(); + +inline int64_t monotonic_time_us() { + return monotonic_time_ns() / 1000L; +} + +inline int64_t monotonic_time_ms() { + return monotonic_time_ns() / 1000000L; +} + +inline int64_t monotonic_time_s() { + return monotonic_time_ns() / 1000000000L; +} + +namespace detail { +inline uint64_t clock_cycles() { +#if defined(__x86_64__) || defined(__amd64__) + unsigned int lo = 0; + unsigned int hi = 0; + // We cannot use "=A", since this would use %rax on x86_64 + __asm__ __volatile__ ( + "rdtsc" + : "=a" (lo), "=d" (hi) + ); + return ((uint64_t)hi << 32) | lo; +#elif defined(__aarch64__) + uint64_t virtual_timer_value; + asm volatile("mrs %0, cntvct_el0" : "=r"(virtual_timer_value)); + return virtual_timer_value; +#elif defined(__ARM_ARCH) + #if (__ARM_ARCH >= 6) + unsigned int pmccntr; + unsigned int pmuseren; + unsigned int pmcntenset; + // Read the user mode perf monitor counter access permissions. + asm volatile ("mrc p15, 0, %0, c9, c14, 0" : "=r" (pmuseren)); + if (pmuseren & 1) { // Allows reading perfmon counters for user mode code. + asm volatile ("mrc p15, 0, %0, c9, c12, 1" : "=r" (pmcntenset)); + if (pmcntenset & 0x80000000ul) { // Is it counting? + asm volatile ("mrc p15, 0, %0, c9, c13, 0" : "=r" (pmccntr)); + // The counter is set up to count every 64th cycle + return static_cast(pmccntr) * 64; // Should optimize to << 6 + } + } + #else + #error "unsupported arm_arch" + #endif +#elif defined(__loongarch64) + uint64_t stable_counter; + uint64_t counter_id; + __asm__ __volatile__ ( + "rdtime.d %1, %0" + : "=r" (stable_counter), "=r" (counter_id) + ); + return stable_counter; +#elif defined(__riscv) + uint64_t cycles; + __asm__ __volatile__ ( + "rdcycle %0" + : "=r" (cycles) + ); + return cycles; +#else + #error "unsupported arch" +#endif +} +extern int64_t read_invariant_cpu_frequency(); +// Be positive iff: +// 1 Intel x86_64 CPU (multiple cores) supporting constant_tsc and +// nonstop_tsc(check flags in /proc/cpuinfo) +extern int64_t invariant_cpu_freq; +} // namespace detail + +// --------------------------------------------------------------- +// Get cpu-wide (wall-) time. +// Cost ~9ns on Intel(R) Xeon(R) CPU E5620 @ 2.40GHz +// --------------------------------------------------------------- +// note: Inlining shortens time cost per-call for 15ns in a loop of many +// calls to this function. +inline int64_t cpuwide_time_ns() { +#if !defined(BAIDU_INTERNAL) + // nearly impossible to get the correct invariant cpu frequency on + // different CPU and machines. CPU-ID rarely works and frequencies + // in "model name" and "cpu Mhz" are both unreliable. + // Since clock_gettime() in newer glibc/kernel is much faster(~30ns) + // which is closer to the previous impl. of cpuwide_time(~10ns), we + // simply use the monotonic time to get rid of all related issues. + timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return now.tv_sec * 1000000000L + now.tv_nsec; +#else + int64_t cpu_freq = detail::invariant_cpu_freq; + if (cpu_freq > 0) { + const uint64_t tsc = detail::clock_cycles(); + //Try to avoid overflow + const uint64_t sec = tsc / cpu_freq; + const uint64_t remain = tsc % cpu_freq; + // TODO: should be OK until CPU's frequency exceeds 16GHz. + return remain * 1000000000L / cpu_freq + sec * 1000000000L; + } else if (!cpu_freq) { + // Lack of necessary features, return system-wide monotonic time instead. + return monotonic_time_ns(); + } else { + // Use a thread-unsafe method(OK to us) to initialize the freq + // to save a "if" test comparing to using a local static variable + detail::invariant_cpu_freq = detail::read_invariant_cpu_frequency(); + return cpuwide_time_ns(); + } +#endif // defined(BAIDU_INTERNAL) +} + +// Get cpu clock time of the current thread in nanoseconds without the time spent in blocking I/O operations. +// Cost ~200ns +inline int64_t cputhread_time_ns() { + timespec now; + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &now); + return now.tv_sec * 1000000000L + now.tv_nsec; +} + +inline int64_t cpuwide_time_us() { + return cpuwide_time_ns() / 1000L; +} + +inline int64_t cpuwide_time_ms() { + return cpuwide_time_ns() / 1000000L; +} + +inline int64_t cpuwide_time_s() { + return cpuwide_time_ns() / 1000000000L; +} + +// -------------------------------------------------------------------- +// Get elapse since the Epoch. +// No gettimeofday_ns() because resolution of timeval is microseconds. +// Cost ~40ns on 2.6.32_1-12-0-0, Intel(R) Xeon(R) CPU E5620 @ 2.40GHz +// -------------------------------------------------------------------- +inline int64_t gettimeofday_us() { + timeval now; + gettimeofday(&now, NULL); + return now.tv_sec * 1000000L + now.tv_usec; +} + +inline int64_t gettimeofday_ms() { + return gettimeofday_us() / 1000L; +} + +inline int64_t gettimeofday_s() { + return gettimeofday_us() / 1000000L; +} + +// ---------------------------------------- +// Control frequency of operations. +// ---------------------------------------- +// Example: +// EveryManyUS every_1s(1000000L); +// while (1) { +// ... +// if (every_1s) { +// // be here at most once per second +// } +// } +class EveryManyUS { +public: + explicit EveryManyUS(int64_t interval_us) + : _last_time_us(cpuwide_time_us()) + , _interval_us(interval_us) {} + + operator bool() { + const int64_t now_us = cpuwide_time_us(); + if (now_us < _last_time_us + _interval_us) { + return false; + } + _last_time_us = now_us; + return true; + } + +private: + int64_t _last_time_us; + const int64_t _interval_us; +}; + +// --------------- +// Count elapses +// --------------- +class Timer { +public: + + enum TimerType { + STARTED, + }; + + Timer() : _stop(0), _start(0) {} + explicit Timer(const TimerType) : Timer() { + start(); + } + + // Start this timer + void start() { + _start = cpuwide_time_ns(); + _stop = _start; + } + + // Stop this timer + void stop() { + _stop = cpuwide_time_ns(); + } + + // Get the elapse from start() to stop(), in various units. + int64_t n_elapsed() const { return _stop - _start; } + int64_t u_elapsed() const { return n_elapsed() / 1000L; } + int64_t m_elapsed() const { return u_elapsed() / 1000L; } + int64_t s_elapsed() const { return m_elapsed() / 1000L; } + + double n_elapsed(double) const { return (double)(_stop - _start); } + double u_elapsed(double) const { return (double)n_elapsed() / 1000.0; } + double m_elapsed(double) const { return (double)u_elapsed() / 1000.0; } + double s_elapsed(double) const { return (double)m_elapsed() / 1000.0; } + +private: + int64_t _stop; + int64_t _start; +}; + +} // namespace butil + +#endif // BUTIL_BAIDU_TIME_H diff --git a/src/butil/time/clock.cc b/src/butil/time/clock.cc new file mode 100644 index 0000000..77e6f85 --- /dev/null +++ b/src/butil/time/clock.cc @@ -0,0 +1,11 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/time/clock.h" + +namespace butil { + +Clock::~Clock() {} + +} // namespace butil diff --git a/src/butil/time/clock.h b/src/butil/time/clock.h new file mode 100644 index 0000000..ec8b2a3 --- /dev/null +++ b/src/butil/time/clock.h @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_CLOCK_H_ +#define BUTIL_CLOCK_H_ + +#include "butil/base_export.h" +#include "butil/time/time.h" + +namespace butil { + +// A Clock is an interface for objects that vend Times. It is +// intended to be able to test the behavior of classes with respect to +// time. +// +// See DefaultClock (butil/time/default_clock.h) for the default +// implementation that simply uses Time::Now(). +// +// (An implementation that uses Time::SystemTime() should be added as +// needed.) +// +// See SimpleTestClock (butil/test/simple_test_clock.h) for a simple +// test implementation. +// +// See TickClock (butil/time/tick_clock.h) for the equivalent interface for +// TimeTicks. +class BUTIL_EXPORT Clock { + public: + virtual ~Clock(); + + // Now() must be safe to call from any thread. The caller cannot + // make any ordering assumptions about the returned Time. For + // example, the system clock may change to an earlier time. + virtual Time Now() = 0; +}; + +} // namespace butil + +#endif // BUTIL_CLOCK_H_ diff --git a/src/butil/time/default_clock.cc b/src/butil/time/default_clock.cc new file mode 100644 index 0000000..eaa6f91 --- /dev/null +++ b/src/butil/time/default_clock.cc @@ -0,0 +1,15 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/time/default_clock.h" + +namespace butil { + +DefaultClock::~DefaultClock() {} + +Time DefaultClock::Now() { + return Time::Now(); +} + +} // namespace butil diff --git a/src/butil/time/default_clock.h b/src/butil/time/default_clock.h new file mode 100644 index 0000000..224957f --- /dev/null +++ b/src/butil/time/default_clock.h @@ -0,0 +1,25 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_DEFAULT_CLOCK_H_ +#define BUTIL_DEFAULT_CLOCK_H_ + +#include "butil/base_export.h" +#include "butil/compiler_specific.h" +#include "butil/time/clock.h" + +namespace butil { + +// DefaultClock is a Clock implementation that uses Time::Now(). +class BUTIL_EXPORT DefaultClock : public Clock { + public: + virtual ~DefaultClock(); + + // Simply returns Time::Now(). + virtual Time Now() OVERRIDE; +}; + +} // namespace butil + +#endif // BUTIL_DEFAULT_CLOCK_H_ diff --git a/src/butil/time/default_tick_clock.cc b/src/butil/time/default_tick_clock.cc new file mode 100644 index 0000000..abc3090 --- /dev/null +++ b/src/butil/time/default_tick_clock.cc @@ -0,0 +1,15 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/time/default_tick_clock.h" + +namespace butil { + +DefaultTickClock::~DefaultTickClock() {} + +TimeTicks DefaultTickClock::NowTicks() { + return TimeTicks::Now(); +} + +} // namespace butil diff --git a/src/butil/time/default_tick_clock.h b/src/butil/time/default_tick_clock.h new file mode 100644 index 0000000..5029b02 --- /dev/null +++ b/src/butil/time/default_tick_clock.h @@ -0,0 +1,25 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_DEFAULT_TICK_CLOCK_H_ +#define BUTIL_DEFAULT_TICK_CLOCK_H_ + +#include "butil/base_export.h" +#include "butil/compiler_specific.h" +#include "butil/time/tick_clock.h" + +namespace butil { + +// DefaultClock is a Clock implementation that uses TimeTicks::Now(). +class BUTIL_EXPORT DefaultTickClock : public TickClock { + public: + virtual ~DefaultTickClock(); + + // Simply returns TimeTicks::Now(). + virtual TimeTicks NowTicks() OVERRIDE; +}; + +} // namespace butil + +#endif // BUTIL_DEFAULT_CLOCK_H_ diff --git a/src/butil/time/tick_clock.cc b/src/butil/time/tick_clock.cc new file mode 100644 index 0000000..c83ad89 --- /dev/null +++ b/src/butil/time/tick_clock.cc @@ -0,0 +1,11 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/time/tick_clock.h" + +namespace butil { + +TickClock::~TickClock() {} + +} // namespace butil diff --git a/src/butil/time/tick_clock.h b/src/butil/time/tick_clock.h new file mode 100644 index 0000000..5b17502 --- /dev/null +++ b/src/butil/time/tick_clock.h @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_TICK_CLOCK_H_ +#define BUTIL_TICK_CLOCK_H_ + +#include "butil/base_export.h" +#include "butil/time/time.h" + +namespace butil { + +// A TickClock is an interface for objects that vend TimeTicks. It is +// intended to be able to test the behavior of classes with respect to +// non-decreasing time. +// +// See DefaultTickClock (butil/time/default_tick_clock.h) for the default +// implementation that simply uses TimeTicks::Now(). +// +// (Other implementations that use TimeTicks::HighResNow() or +// TimeTicks::NowFromSystemTime() should be added as needed.) +// +// See SimpleTestTickClock (butil/test/simple_test_tick_clock.h) for a +// simple test implementation. +// +// See Clock (butil/time/clock.h) for the equivalent interface for Times. +class BUTIL_EXPORT TickClock { + public: + virtual ~TickClock(); + + // NowTicks() must be safe to call from any thread. The caller may + // assume that NowTicks() is monotonic (but not strictly monotonic). + // In other words, the returned TimeTicks will never decrease with + // time, although they might "stand still". + virtual TimeTicks NowTicks() = 0; +}; + +} // namespace butil + +#endif // BUTIL_TICK_CLOCK_H_ diff --git a/src/butil/time/time.cc b/src/butil/time/time.cc new file mode 100644 index 0000000..8cb3b97 --- /dev/null +++ b/src/butil/time/time.cc @@ -0,0 +1,259 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/time/time.h" + +#include +#include + +#include "butil/float_util.h" +#include "butil/lazy_instance.h" +#include "butil/logging.h" + +namespace butil { + +// TimeDelta ------------------------------------------------------------------ + +// static +TimeDelta TimeDelta::Max() { + return TimeDelta(std::numeric_limits::max()); +} + +int TimeDelta::InDays() const { + if (is_max()) { + // Preserve max to prevent overflow. + return std::numeric_limits::max(); + } + return static_cast(delta_ / Time::kMicrosecondsPerDay); +} + +int TimeDelta::InHours() const { + if (is_max()) { + // Preserve max to prevent overflow. + return std::numeric_limits::max(); + } + return static_cast(delta_ / Time::kMicrosecondsPerHour); +} + +int TimeDelta::InMinutes() const { + if (is_max()) { + // Preserve max to prevent overflow. + return std::numeric_limits::max(); + } + return static_cast(delta_ / Time::kMicrosecondsPerMinute); +} + +double TimeDelta::InSecondsF() const { + if (is_max()) { + // Preserve max to prevent overflow. + return std::numeric_limits::infinity(); + } + return static_cast(delta_) / Time::kMicrosecondsPerSecond; +} + +int64_t TimeDelta::InSeconds() const { + if (is_max()) { + // Preserve max to prevent overflow. + return std::numeric_limits::max(); + } + return delta_ / Time::kMicrosecondsPerSecond; +} + +double TimeDelta::InMillisecondsF() const { + if (is_max()) { + // Preserve max to prevent overflow. + return std::numeric_limits::infinity(); + } + return static_cast(delta_) / Time::kMicrosecondsPerMillisecond; +} + +int64_t TimeDelta::InMilliseconds() const { + if (is_max()) { + // Preserve max to prevent overflow. + return std::numeric_limits::max(); + } + return delta_ / Time::kMicrosecondsPerMillisecond; +} + +int64_t TimeDelta::InMillisecondsRoundedUp() const { + if (is_max()) { + // Preserve max to prevent overflow. + return std::numeric_limits::max(); + } + return (delta_ + Time::kMicrosecondsPerMillisecond - 1) / + Time::kMicrosecondsPerMillisecond; +} + +int64_t TimeDelta::InMicroseconds() const { + if (is_max()) { + // Preserve max to prevent overflow. + return std::numeric_limits::max(); + } + return delta_; +} + +// Time ----------------------------------------------------------------------- + +// static +Time Time::Max() { + return Time(std::numeric_limits::max()); +} + +// static +Time Time::FromTimeT(time_t tt) { + if (tt == 0) + return Time(); // Preserve 0 so we can tell it doesn't exist. + if (tt == std::numeric_limits::max()) + return Max(); + return Time((tt * kMicrosecondsPerSecond) + kTimeTToMicrosecondsOffset); +} + +time_t Time::ToTimeT() const { + if (is_null()) + return 0; // Preserve 0 so we can tell it doesn't exist. + if (is_max()) { + // Preserve max without offset to prevent overflow. + return std::numeric_limits::max(); + } + if (std::numeric_limits::max() - kTimeTToMicrosecondsOffset <= us_) { + DLOG(WARNING) << "Overflow when converting butil::Time with internal " << + "value " << us_ << " to time_t."; + return std::numeric_limits::max(); + } + return (us_ - kTimeTToMicrosecondsOffset) / kMicrosecondsPerSecond; +} + +// static +Time Time::FromDoubleT(double dt) { + if (dt == 0 || IsNaN(dt)) + return Time(); // Preserve 0 so we can tell it doesn't exist. + if (dt == std::numeric_limits::infinity()) + return Max(); + return Time(static_cast((dt * + static_cast(kMicrosecondsPerSecond)) + + kTimeTToMicrosecondsOffset)); +} + +double Time::ToDoubleT() const { + if (is_null()) + return 0; // Preserve 0 so we can tell it doesn't exist. + if (is_max()) { + // Preserve max without offset to prevent overflow. + return std::numeric_limits::infinity(); + } + return (static_cast(us_ - kTimeTToMicrosecondsOffset) / + static_cast(kMicrosecondsPerSecond)); +} + +#if defined(OS_POSIX) +// static +Time Time::FromTimeSpec(const timespec& ts) { + return FromDoubleT(ts.tv_sec + + static_cast(ts.tv_nsec) / + butil::Time::kNanosecondsPerSecond); +} +#endif + +// static +Time Time::FromJsTime(double ms_since_epoch) { + // The epoch is a valid time, so this constructor doesn't interpret + // 0 as the null time. + if (ms_since_epoch == std::numeric_limits::infinity()) + return Max(); + return Time(static_cast(ms_since_epoch * kMicrosecondsPerMillisecond) + + kTimeTToMicrosecondsOffset); +} + +double Time::ToJsTime() const { + if (is_null()) { + // Preserve 0 so the invalid result doesn't depend on the platform. + return 0; + } + if (is_max()) { + // Preserve max without offset to prevent overflow. + return std::numeric_limits::infinity(); + } + return (static_cast(us_ - kTimeTToMicrosecondsOffset) / + kMicrosecondsPerMillisecond); +} + +int64_t Time::ToJavaTime() const { + if (is_null()) { + // Preserve 0 so the invalid result doesn't depend on the platform. + return 0; + } + if (is_max()) { + // Preserve max without offset to prevent overflow. + return std::numeric_limits::max(); + } + return ((us_ - kTimeTToMicrosecondsOffset) / + kMicrosecondsPerMillisecond); +} + +// static +Time Time::UnixEpoch() { + Time time; + time.us_ = kTimeTToMicrosecondsOffset; + return time; +} + +Time Time::LocalMidnight() const { + Exploded exploded; + LocalExplode(&exploded); + exploded.hour = 0; + exploded.minute = 0; + exploded.second = 0; + exploded.millisecond = 0; + return FromLocalExploded(exploded); +} + +// static +bool Time::FromStringInternal(const char* time_string, + bool is_local, + Time* parsed_time) { + // TODO(zhujiashun): after removing nspr, this function + // is left unimplemented. + return false; +} + +// Local helper class to hold the conversion from Time to TickTime at the +// time of the Unix epoch. +class UnixEpochSingleton { + public: + UnixEpochSingleton() + : unix_epoch_(TimeTicks::Now() - (Time::Now() - Time::UnixEpoch())) {} + + TimeTicks unix_epoch() const { return unix_epoch_; } + + private: + const TimeTicks unix_epoch_; + + DISALLOW_COPY_AND_ASSIGN(UnixEpochSingleton); +}; + +static LazyInstance::Leaky + leaky_unix_epoch_singleton_instance = LAZY_INSTANCE_INITIALIZER; + +// Static +TimeTicks TimeTicks::UnixEpoch() { + return leaky_unix_epoch_singleton_instance.Get().unix_epoch(); +} + +// Time::Exploded ------------------------------------------------------------- + +inline bool is_in_range(int value, int lo, int hi) { + return lo <= value && value <= hi; +} + +bool Time::Exploded::HasValidValues() const { + return is_in_range(month, 1, 12) && + is_in_range(day_of_week, 0, 6) && + is_in_range(day_of_month, 1, 31) && + is_in_range(hour, 0, 23) && + is_in_range(minute, 0, 59) && + is_in_range(second, 0, 60) && + is_in_range(millisecond, 0, 999); +} + +} // namespace butil diff --git a/src/butil/time/time.h b/src/butil/time/time.h new file mode 100644 index 0000000..cdc57b6 --- /dev/null +++ b/src/butil/time/time.h @@ -0,0 +1,774 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Time represents an absolute point in coordinated universal time (UTC), +// internally represented as microseconds (s/1,000,000) since the Windows epoch +// (1601-01-01 00:00:00 UTC) (See http://crbug.com/14734). System-dependent +// clock interface routines are defined in time_PLATFORM.cc. +// +// TimeDelta represents a duration of time, internally represented in +// microseconds. +// +// TimeTicks represents an abstract time that is most of the time incrementing +// for use in measuring time durations. It is internally represented in +// microseconds. It can not be converted to a human-readable time, but is +// guaranteed not to decrease (if the user changes the computer clock, +// Time::Now() may actually decrease or jump). But note that TimeTicks may +// "stand still", for example if the computer suspended. +// +// These classes are represented as only a 64-bit value, so they can be +// efficiently passed by value. + +#ifndef BUTIL_TIME_TIME_H_ +#define BUTIL_TIME_TIME_H_ + +#include + +#include "butil/base_export.h" +#include "butil/basictypes.h" +#include "butil/build_config.h" + +#if defined(OS_MACOSX) +#include +// Avoid Mac system header macro leak. +#undef TYPE_BOOL +#endif + +#if defined(OS_POSIX) +#include +#include +#endif + +#if defined(OS_WIN) +// For FILETIME in FromFileTime, until it moves to a new converter class. +// See TODO(iyengar) below. +#include +#endif + +#include + +#ifdef BAIDU_INTERNAL +// gejun: Force inclusion of ul_def.h and undef conflict macros +#include +#undef Uchar +#undef Ushort +#undef Uint +#undef Max +#undef Min +#undef Exchange +#endif // BAIDU_INTERNAL + +namespace butil { + +class Time; +class TimeTicks; + +// TimeDelta ------------------------------------------------------------------ + +class BUTIL_EXPORT TimeDelta { + public: + TimeDelta() : delta_(0) { + } + + // Required by clang++ 11 on MacOS catalina + TimeDelta(const TimeDelta& other) : delta_(other.delta_) {} + + // Converts units of time to TimeDeltas. + static TimeDelta FromDays(int days); + static TimeDelta FromHours(int hours); + static TimeDelta FromMinutes(int minutes); + static TimeDelta FromSeconds(int64_t secs); + static TimeDelta FromMilliseconds(int64_t ms); + static TimeDelta FromMicroseconds(int64_t us); + static TimeDelta FromSecondsD(double secs); + static TimeDelta FromMillisecondsD(double ms); + static TimeDelta FromMicrosecondsD(double us); + +#if defined(OS_WIN) + static TimeDelta FromQPCValue(LONGLONG qpc_value); +#endif + + // Converts an integer value representing TimeDelta to a class. This is used + // when deserializing a |TimeDelta| structure, using a value known to be + // compatible. It is not provided as a constructor because the integer type + // may be unclear from the perspective of a caller. + static TimeDelta FromInternalValue(int64_t delta) { + return TimeDelta(delta); + } + + // Returns the maximum time delta, which should be greater than any reasonable + // time delta we might compare it to. Adding or subtracting the maximum time + // delta to a time or another time delta has an undefined result. + static TimeDelta Max(); + + // Returns the internal numeric value of the TimeDelta object. Please don't + // use this and do arithmetic on it, as it is more error prone than using the + // provided operators. + // For serializing, use FromInternalValue to reconstitute. + int64_t ToInternalValue() const { + return delta_; + } + + // Returns true if the time delta is the maximum time delta. + bool is_max() const { + return delta_ == std::numeric_limits::max(); + } + +#if defined(OS_POSIX) + struct timespec ToTimeSpec() const; +#endif + + // Returns the time delta in some unit. The F versions return a floating + // point value, the "regular" versions return a rounded-down value. + // + // InMillisecondsRoundedUp() instead returns an integer that is rounded up + // to the next full millisecond. + int InDays() const; + int InHours() const; + int InMinutes() const; + double InSecondsF() const; + int64_t InSeconds() const; + double InMillisecondsF() const; + int64_t InMilliseconds() const; + int64_t InMillisecondsRoundedUp() const; + int64_t InMicroseconds() const; + + TimeDelta& operator=(TimeDelta other) { + delta_ = other.delta_; + return *this; + } + + // Computations with other deltas. + TimeDelta operator+(TimeDelta other) const { + return TimeDelta(delta_ + other.delta_); + } + TimeDelta operator-(TimeDelta other) const { + return TimeDelta(delta_ - other.delta_); + } + + TimeDelta& operator+=(TimeDelta other) { + delta_ += other.delta_; + return *this; + } + TimeDelta& operator-=(TimeDelta other) { + delta_ -= other.delta_; + return *this; + } + TimeDelta operator-() const { + return TimeDelta(-delta_); + } + + // Computations with ints, note that we only allow multiplicative operations + // with ints, and additive operations with other deltas. + TimeDelta operator*(int64_t a) const { + return TimeDelta(delta_ * a); + } + TimeDelta operator/(int64_t a) const { + return TimeDelta(delta_ / a); + } + TimeDelta& operator*=(int64_t a) { + delta_ *= a; + return *this; + } + TimeDelta& operator/=(int64_t a) { + delta_ /= a; + return *this; + } + int64_t operator/(TimeDelta a) const { + return delta_ / a.delta_; + } + + // Defined below because it depends on the definition of the other classes. + Time operator+(Time t) const; + TimeTicks operator+(TimeTicks t) const; + + // Comparison operators. + bool operator==(TimeDelta other) const { + return delta_ == other.delta_; + } + bool operator!=(TimeDelta other) const { + return delta_ != other.delta_; + } + bool operator<(TimeDelta other) const { + return delta_ < other.delta_; + } + bool operator<=(TimeDelta other) const { + return delta_ <= other.delta_; + } + bool operator>(TimeDelta other) const { + return delta_ > other.delta_; + } + bool operator>=(TimeDelta other) const { + return delta_ >= other.delta_; + } + + private: + friend class Time; + friend class TimeTicks; + friend TimeDelta operator*(int64_t a, TimeDelta td); + + // Constructs a delta given the duration in microseconds. This is private + // to avoid confusion by callers with an integer constructor. Use + // FromSeconds, FromMilliseconds, etc. instead. + explicit TimeDelta(int64_t delta_us) : delta_(delta_us) { + } + + // Delta in microseconds. + int64_t delta_; +}; + +inline TimeDelta operator*(int64_t a, TimeDelta td) { + return TimeDelta(a * td.delta_); +} + +// Time ----------------------------------------------------------------------- + +// Represents a wall clock time in UTC. +class BUTIL_EXPORT Time { + public: + static const int64_t kMillisecondsPerSecond = 1000; + static const int64_t kMicrosecondsPerMillisecond = 1000; + static const int64_t kMicrosecondsPerSecond = kMicrosecondsPerMillisecond * + kMillisecondsPerSecond; + static const int64_t kMicrosecondsPerMinute = kMicrosecondsPerSecond * 60; + static const int64_t kMicrosecondsPerHour = kMicrosecondsPerMinute * 60; + static const int64_t kMicrosecondsPerDay = kMicrosecondsPerHour * 24; + static const int64_t kMicrosecondsPerWeek = kMicrosecondsPerDay * 7; + static const int64_t kNanosecondsPerMicrosecond = 1000; + static const int64_t kNanosecondsPerSecond = kNanosecondsPerMicrosecond * + kMicrosecondsPerSecond; + +#if !defined(OS_WIN) + // On Mac & Linux, this value is the delta from the Windows epoch of 1601 to + // the Posix delta of 1970. This is used for migrating between the old + // 1970-based epochs to the new 1601-based ones. It should be removed from + // this global header and put in the platform-specific ones when we remove the + // migration code. + static const int64_t kWindowsEpochDeltaMicroseconds; +#endif + + // Represents an exploded time that can be formatted nicely. This is kind of + // like the Win32 SYSTEMTIME structure or the Unix "struct tm" with a few + // additions and changes to prevent errors. + struct BUTIL_EXPORT Exploded { + int year; // Four digit year "2007" + int month; // 1-based month (values 1 = January, etc.) + int day_of_week; // 0-based day of week (0 = Sunday, etc.) + int day_of_month; // 1-based day of month (1-31) + int hour; // Hour within the current day (0-23) + int minute; // Minute within the current hour (0-59) + int second; // Second within the current minute (0-59 plus leap + // seconds which may take it up to 60). + int millisecond; // Milliseconds within the current second (0-999) + + // A cursory test for whether the data members are within their + // respective ranges. A 'true' return value does not guarantee the + // Exploded value can be successfully converted to a Time value. + bool HasValidValues() const; + }; + + // Contains the NULL time. Use Time::Now() to get the current time. + Time() : us_(0) { + } + + // Required by clang++ 11 on MacOS catalina + Time(const Time& other) : us_(other.us_) {} + + // Returns true if the time object has not been initialized. + bool is_null() const { + return us_ == 0; + } + + // Returns true if the time object is the maximum time. + bool is_max() const { + return us_ == std::numeric_limits::max(); + } + + // Returns the time for epoch in Unix-like system (Jan 1, 1970). + static Time UnixEpoch(); + + // Returns the current time. Watch out, the system might adjust its clock + // in which case time will actually go backwards. We don't guarantee that + // times are increasing, or that two calls to Now() won't be the same. + static Time Now(); + + // Returns the maximum time, which should be greater than any reasonable time + // with which we might compare it. + static Time Max(); + + // Returns the current time. Same as Now() except that this function always + // uses system time so that there are no discrepancies between the returned + // time and system time even on virtual environments including our test bot. + // For timing sensitive unittests, this function should be used. + static Time NowFromSystemTime(); + + // Converts to/from time_t in UTC and a Time class. + // TODO(brettw) this should be removed once everybody starts using the |Time| + // class. + static Time FromTimeT(time_t tt); + time_t ToTimeT() const; + + // Converts time to/from a double which is the number of seconds since epoch + // (Jan 1, 1970). Webkit uses this format to represent time. + // Because WebKit initializes double time value to 0 to indicate "not + // initialized", we map it to empty Time object that also means "not + // initialized". + static Time FromDoubleT(double dt); + double ToDoubleT() const; + +#if defined(OS_POSIX) + // Converts the timespec structure to time. MacOS X 10.8.3 (and tentatively, + // earlier versions) will have the |ts|'s tv_nsec component zeroed out, + // having a 1 second resolution, which agrees with + // https://developer.apple.com/legacy/library/#technotes/tn/tn1150.html#HFSPlusDates. + static Time FromTimeSpec(const timespec& ts); +#endif + + // Converts to/from the Javascript convention for times, a number of + // milliseconds since the epoch: + // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date/getTime. + static Time FromJsTime(double ms_since_epoch); + double ToJsTime() const; + + // Converts to Java convention for times, a number of + // milliseconds since the epoch. + int64_t ToJavaTime() const; + +#if defined(OS_POSIX) + static Time FromTimeVal(struct timeval t); + struct timeval ToTimeVal() const; +#endif + +#if defined(OS_MACOSX) + static Time FromCFAbsoluteTime(CFAbsoluteTime t); + CFAbsoluteTime ToCFAbsoluteTime() const; +#endif + +#if defined(OS_WIN) + static Time FromFileTime(FILETIME ft); + FILETIME ToFileTime() const; + + // The minimum time of a low resolution timer. This is basically a windows + // constant of ~15.6ms. While it does vary on some older OS versions, we'll + // treat it as static across all windows versions. + static const int kMinLowResolutionThresholdMs = 16; + + // Enable or disable Windows high resolution timer. If the high resolution + // timer is not enabled, calls to ActivateHighResolutionTimer will fail. + // When disabling the high resolution timer, this function will not cause + // the high resolution timer to be deactivated, but will prevent future + // activations. + // Must be called from the main thread. + // For more details see comments in time_win.cc. + static void EnableHighResolutionTimer(bool enable); + + // Activates or deactivates the high resolution timer based on the |activate| + // flag. If the HighResolutionTimer is not Enabled (see + // EnableHighResolutionTimer), this function will return false. Otherwise + // returns true. Each successful activate call must be paired with a + // subsequent deactivate call. + // All callers to activate the high resolution timer must eventually call + // this function to deactivate the high resolution timer. + static bool ActivateHighResolutionTimer(bool activate); + + // Returns true if the high resolution timer is both enabled and activated. + // This is provided for testing only, and is not tracked in a thread-safe + // way. + static bool IsHighResolutionTimerInUse(); +#endif + + // Converts an exploded structure representing either the local time or UTC + // into a Time class. + static Time FromUTCExploded(const Exploded& exploded) { + return FromExploded(false, exploded); + } + static Time FromLocalExploded(const Exploded& exploded) { + return FromExploded(true, exploded); + } + + // Converts an integer value representing Time to a class. This is used + // when deserializing a |Time| structure, using a value known to be + // compatible. It is not provided as a constructor because the integer type + // may be unclear from the perspective of a caller. + static Time FromInternalValue(int64_t us) { + return Time(us); + } + + // Converts a string representation of time to a Time object. + // An example of a time string which is converted is as below:- + // "Tue, 15 Nov 1994 12:45:26 GMT". If the timezone is not specified + // in the input string, FromString assumes local time and FromUTCString + // assumes UTC. A timezone that cannot be parsed (e.g. "UTC" which is not + // specified in RFC822) is treated as if the timezone is not specified. + // TODO(iyengar) Move the FromString/FromTimeT/ToTimeT/FromFileTime to + // a new time converter class. + static bool FromString(const char* time_string, Time* parsed_time) { + return FromStringInternal(time_string, true, parsed_time); + } + static bool FromUTCString(const char* time_string, Time* parsed_time) { + return FromStringInternal(time_string, false, parsed_time); + } + + // For serializing, use FromInternalValue to reconstitute. Please don't use + // this and do arithmetic on it, as it is more error prone than using the + // provided operators. + int64_t ToInternalValue() const { + return us_; + } + + // Fills the given exploded structure with either the local time or UTC from + // this time structure (containing UTC). + void UTCExplode(Exploded* exploded) const { + return Explode(false, exploded); + } + void LocalExplode(Exploded* exploded) const { + return Explode(true, exploded); + } + + // Rounds this time down to the nearest day in local time. It will represent + // midnight on that day. + Time LocalMidnight() const; + + Time& operator=(Time other) { + us_ = other.us_; + return *this; + } + + // Compute the difference between two times. + TimeDelta operator-(Time other) const { + return TimeDelta(us_ - other.us_); + } + + // Modify by some time delta. + Time& operator+=(TimeDelta delta) { + us_ += delta.delta_; + return *this; + } + Time& operator-=(TimeDelta delta) { + us_ -= delta.delta_; + return *this; + } + + // Return a new time modified by some delta. + Time operator+(TimeDelta delta) const { + return Time(us_ + delta.delta_); + } + Time operator-(TimeDelta delta) const { + return Time(us_ - delta.delta_); + } + + // Comparison operators + bool operator==(Time other) const { + return us_ == other.us_; + } + bool operator!=(Time other) const { + return us_ != other.us_; + } + bool operator<(Time other) const { + return us_ < other.us_; + } + bool operator<=(Time other) const { + return us_ <= other.us_; + } + bool operator>(Time other) const { + return us_ > other.us_; + } + bool operator>=(Time other) const { + return us_ >= other.us_; + } + + private: + friend class TimeDelta; + + explicit Time(int64_t us) : us_(us) { + } + + // Explodes the given time to either local time |is_local = true| or UTC + // |is_local = false|. + void Explode(bool is_local, Exploded* exploded) const; + + // Unexplodes a given time assuming the source is either local time + // |is_local = true| or UTC |is_local = false|. + static Time FromExploded(bool is_local, const Exploded& exploded); + + // Converts a string representation of time to a Time object. + // An example of a time string which is converted is as below:- + // "Tue, 15 Nov 1994 12:45:26 GMT". If the timezone is not specified + // in the input string, local time |is_local = true| or + // UTC |is_local = false| is assumed. A timezone that cannot be parsed + // (e.g. "UTC" which is not specified in RFC822) is treated as if the + // timezone is not specified. + static bool FromStringInternal(const char* time_string, + bool is_local, + Time* parsed_time); + + // The representation of Jan 1, 1970 UTC in microseconds since the + // platform-dependent epoch. + static const int64_t kTimeTToMicrosecondsOffset; + +#if defined(OS_WIN) + // Indicates whether fast timers are usable right now. For instance, + // when using battery power, we might elect to prevent high speed timers + // which would draw more power. + static bool high_resolution_timer_enabled_; + // Count of activations on the high resolution timer. Only use in tests + // which are single threaded. + static int high_resolution_timer_activated_; +#endif + + // Time in microseconds in UTC. + int64_t us_; +}; + +// Inline the TimeDelta factory methods, for fast TimeDelta construction. + +// static +inline TimeDelta TimeDelta::FromDays(int days) { + // Preserve max to prevent overflow. + if (days == std::numeric_limits::max()) + return Max(); + return TimeDelta(days * Time::kMicrosecondsPerDay); +} + +// static +inline TimeDelta TimeDelta::FromHours(int hours) { + // Preserve max to prevent overflow. + if (hours == std::numeric_limits::max()) + return Max(); + return TimeDelta(hours * Time::kMicrosecondsPerHour); +} + +// static +inline TimeDelta TimeDelta::FromMinutes(int minutes) { + // Preserve max to prevent overflow. + if (minutes == std::numeric_limits::max()) + return Max(); + return TimeDelta(minutes * Time::kMicrosecondsPerMinute); +} + +// static +inline TimeDelta TimeDelta::FromSeconds(int64_t secs) { + // Preserve max to prevent overflow. + if (secs == std::numeric_limits::max()) + return Max(); + return TimeDelta(secs * Time::kMicrosecondsPerSecond); +} + +// static +inline TimeDelta TimeDelta::FromMilliseconds(int64_t ms) { + // Preserve max to prevent overflow. + if (ms == std::numeric_limits::max()) + return Max(); + return TimeDelta(ms * Time::kMicrosecondsPerMillisecond); +} + +// static +inline TimeDelta TimeDelta::FromSecondsD(double secs) { + // Preserve max to prevent overflow. + if (secs == std::numeric_limits::infinity()) + return Max(); + return TimeDelta((int64_t)(secs * Time::kMicrosecondsPerSecond)); +} + +// static +inline TimeDelta TimeDelta::FromMillisecondsD(double ms) { + // Preserve max to prevent overflow. + if (ms == std::numeric_limits::infinity()) + return Max(); + return TimeDelta((int64_t)(ms * Time::kMicrosecondsPerMillisecond)); +} + +// static +inline TimeDelta TimeDelta::FromMicroseconds(int64_t us) { + // Preserve max to prevent overflow. + if (us == std::numeric_limits::max()) + return Max(); + return TimeDelta(us); +} + +// static +inline TimeDelta TimeDelta::FromMicrosecondsD(double us) { + // Preserve max to prevent overflow. + if (us == std::numeric_limits::infinity()) + return Max(); + return TimeDelta((int64_t)us); +} + +inline Time TimeDelta::operator+(Time t) const { + return Time(t.us_ + delta_); +} + +// TimeTicks ------------------------------------------------------------------ + +class BUTIL_EXPORT TimeTicks { + public: + // We define this even without OS_CHROMEOS for seccomp sandbox testing. +#if defined(OS_LINUX) + // Force definition of the system trace clock; it is a chromeos-only api + // at the moment and surfacing it in the right place requires mucking + // with glibc et al. + static const clockid_t kClockSystemTrace = 11; +#endif + + TimeTicks() : ticks_(0) { + } + + // Required by clang++ 11 on MacOS catalina + TimeTicks(const TimeTicks& other) : ticks_(other.ticks_) {} + + // Platform-dependent tick count representing "right now." + // The resolution of this clock is ~1-15ms. Resolution varies depending + // on hardware/operating system configuration. + static TimeTicks Now(); + + // Returns a platform-dependent high-resolution tick count. Implementation + // is hardware dependent and may or may not return sub-millisecond + // resolution. THIS CALL IS GENERALLY MUCH MORE EXPENSIVE THAN Now() AND + // SHOULD ONLY BE USED WHEN IT IS REALLY NEEDED. + static TimeTicks HighResNow(); + + static bool IsHighResNowFastAndReliable(); + + // FIXME(gejun): CLOCK_THREAD_CPUTIME_ID behaves differently in different + // glibc. To avoid potential bug, disable ThreadNow() always. + // Returns true if ThreadNow() is supported on this system. + static bool IsThreadNowSupported() { + return false; + } + + // Returns thread-specific CPU-time on systems that support this feature. + // Needs to be guarded with a call to IsThreadNowSupported(). Use this timer + // to (approximately) measure how much time the calling thread spent doing + // actual work vs. being de-scheduled. May return bogus results if the thread + // migrates to another CPU between two calls. + static TimeTicks ThreadNow(); + + // Returns the current system trace time or, if none is defined, the current + // high-res time (i.e. HighResNow()). On systems where a global trace clock + // is defined, timestamping TraceEvents's with this value guarantees + // synchronization between events collected inside chrome and events + // collected outside (e.g. kernel, X server). + static TimeTicks NowFromSystemTraceTime(); + +#if defined(OS_WIN) + // Get the absolute value of QPC time drift. For testing. + static int64_t GetQPCDriftMicroseconds(); + + static TimeTicks FromQPCValue(LONGLONG qpc_value); + + // Returns true if the high resolution clock is working on this system. + // This is only for testing. + static bool IsHighResClockWorking(); + + // Enable high resolution time for TimeTicks::Now(). This function will + // test for the availability of a working implementation of + // QueryPerformanceCounter(). If one is not available, this function does + // nothing and the resolution of Now() remains 1ms. Otherwise, all future + // calls to TimeTicks::Now() will have the higher resolution provided by QPC. + // Returns true if high resolution time was successfully enabled. + static bool SetNowIsHighResNowIfSupported(); + + // Returns a time value that is NOT rollover protected. + static TimeTicks UnprotectedNow(); +#endif + + // Returns true if this object has not been initialized. + bool is_null() const { + return ticks_ == 0; + } + + // Converts an integer value representing TimeTicks to a class. This is used + // when deserializing a |TimeTicks| structure, using a value known to be + // compatible. It is not provided as a constructor because the integer type + // may be unclear from the perspective of a caller. + static TimeTicks FromInternalValue(int64_t ticks) { + return TimeTicks(ticks); + } + + // Get the TimeTick value at the time of the UnixEpoch. This is useful when + // you need to relate the value of TimeTicks to a real time and date. + // Note: Upon first invocation, this function takes a snapshot of the realtime + // clock to establish a reference point. This function will return the same + // value for the duration of the application, but will be different in future + // application runs. + static TimeTicks UnixEpoch(); + + // Returns the internal numeric value of the TimeTicks object. + // For serializing, use FromInternalValue to reconstitute. + int64_t ToInternalValue() const { + return ticks_; + } + + TimeTicks& operator=(TimeTicks other) { + ticks_ = other.ticks_; + return *this; + } + + // Compute the difference between two times. + TimeDelta operator-(TimeTicks other) const { + return TimeDelta(ticks_ - other.ticks_); + } + + // Modify by some time delta. + TimeTicks& operator+=(TimeDelta delta) { + ticks_ += delta.delta_; + return *this; + } + TimeTicks& operator-=(TimeDelta delta) { + ticks_ -= delta.delta_; + return *this; + } + + // Return a new TimeTicks modified by some delta. + TimeTicks operator+(TimeDelta delta) const { + return TimeTicks(ticks_ + delta.delta_); + } + TimeTicks operator-(TimeDelta delta) const { + return TimeTicks(ticks_ - delta.delta_); + } + + // Comparison operators + bool operator==(TimeTicks other) const { + return ticks_ == other.ticks_; + } + bool operator!=(TimeTicks other) const { + return ticks_ != other.ticks_; + } + bool operator<(TimeTicks other) const { + return ticks_ < other.ticks_; + } + bool operator<=(TimeTicks other) const { + return ticks_ <= other.ticks_; + } + bool operator>(TimeTicks other) const { + return ticks_ > other.ticks_; + } + bool operator>=(TimeTicks other) const { + return ticks_ >= other.ticks_; + } + + protected: + friend class TimeDelta; + + // Please use Now() to create a new object. This is for internal use + // and testing. Ticks is in microseconds. + explicit TimeTicks(int64_t ticks) : ticks_(ticks) { + } + + // Tick count in microseconds. + int64_t ticks_; + +#if defined(OS_WIN) + typedef DWORD (*TickFunctionType)(void); + static TickFunctionType SetMockTickFunction(TickFunctionType ticker); +#endif +}; + +inline TimeTicks TimeDelta::operator+(TimeTicks t) const { + return TimeTicks(t.ticks_ + delta_); +} + +} // namespace butil + +#endif // BUTIL_TIME_TIME_H_ diff --git a/src/butil/time/time_mac.cc b/src/butil/time/time_mac.cc new file mode 100644 index 0000000..98e818a --- /dev/null +++ b/src/butil/time/time_mac.cc @@ -0,0 +1,240 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/time/time.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "butil/basictypes.h" +#include "butil/logging.h" +#include "butil/mac/scoped_cftyperef.h" +#include "butil/mac/scoped_mach_port.h" + +namespace { + +uint64_t ComputeCurrentTicks() { +#if defined(OS_IOS) + // On iOS mach_absolute_time stops while the device is sleeping. Instead use + // now - KERN_BOOTTIME to get a time difference that is not impacted by clock + // changes. KERN_BOOTTIME will be updated by the system whenever the system + // clock change. + struct timeval boottime; + int mib[2] = {CTL_KERN, KERN_BOOTTIME}; + size_t size = sizeof(boottime); + int kr = sysctl(mib, arraysize(mib), &boottime, &size, NULL, 0); + DCHECK_EQ(KERN_SUCCESS, kr); + butil::TimeDelta time_difference = butil::Time::Now() - + (butil::Time::FromTimeT(boottime.tv_sec) + + butil::TimeDelta::FromMicroseconds(boottime.tv_usec)); + return time_difference.InMicroseconds(); +#else + uint64_t absolute_micro; + + static mach_timebase_info_data_t timebase_info; + if (timebase_info.denom == 0) { + // Zero-initialization of statics guarantees that denom will be 0 before + // calling mach_timebase_info. mach_timebase_info will never set denom to + // 0 as that would be invalid, so the zero-check can be used to determine + // whether mach_timebase_info has already been called. This is + // recommended by Apple's QA1398. + kern_return_t kr = mach_timebase_info(&timebase_info); + DCHECK(kr == KERN_SUCCESS) << "Fail to call mach_timebase_info"; + } + + // mach_absolute_time is it when it comes to ticks on the Mac. Other calls + // with less precision (such as TickCount) just call through to + // mach_absolute_time. + + // timebase_info converts absolute time tick units into nanoseconds. Convert + // to microseconds up front to stave off overflows. + absolute_micro = + mach_absolute_time() / butil::Time::kNanosecondsPerMicrosecond * + timebase_info.numer / timebase_info.denom; + + // Don't bother with the rollover handling that the Windows version does. + // With numer and denom = 1 (the expected case), the 64-bit absolute time + // reported in nanoseconds is enough to last nearly 585 years. + return absolute_micro; +#endif // defined(OS_IOS) +} + +uint64_t ComputeThreadTicks() { +#if defined(OS_IOS) + NOTREACHED(); + return 0; +#else + butil::mac::ScopedMachSendRight thread(mach_thread_self()); + mach_msg_type_number_t thread_info_count = THREAD_BASIC_INFO_COUNT; + thread_basic_info_data_t thread_info_data; + + if (thread.get() == MACH_PORT_NULL) { + DLOG(ERROR) << "Failed to get mach_thread_self()"; + return 0; + } + + kern_return_t kr = thread_info( + thread.get(), + THREAD_BASIC_INFO, + reinterpret_cast(&thread_info_data), + &thread_info_count); + DCHECK(kr == KERN_SUCCESS) << "Fail to call thread_info"; + + return (thread_info_data.user_time.seconds * + butil::Time::kMicrosecondsPerSecond) + + thread_info_data.user_time.microseconds; +#endif // defined(OS_IOS) +} + +} // namespace + +namespace butil { + +// The Time routines in this file use Mach and CoreFoundation APIs, since the +// POSIX definition of time_t in Mac OS X wraps around after 2038--and +// there are already cookie expiration dates, etc., past that time out in +// the field. Using CFDate prevents that problem, and using mach_absolute_time +// for TimeTicks gives us nice high-resolution interval timing. + +// Time ----------------------------------------------------------------------- + +// Core Foundation uses a double second count since 2001-01-01 00:00:00 UTC. +// The UNIX epoch is 1970-01-01 00:00:00 UTC. +// Windows uses a Gregorian epoch of 1601. We need to match this internally +// so that our time representations match across all platforms. See bug 14734. +// irb(main):010:0> Time.at(0).getutc() +// => Thu Jan 01 00:00:00 UTC 1970 +// irb(main):011:0> Time.at(-11644473600).getutc() +// => Mon Jan 01 00:00:00 UTC 1601 +static const int64_t kWindowsEpochDeltaSeconds = GG_INT64_C(11644473600); + +// static +const int64_t Time::kWindowsEpochDeltaMicroseconds = + kWindowsEpochDeltaSeconds * Time::kMicrosecondsPerSecond; + +// Some functions in time.cc use time_t directly, so we provide an offset +// to convert from time_t (Unix epoch) and internal (Windows epoch). +// static +const int64_t Time::kTimeTToMicrosecondsOffset = kWindowsEpochDeltaMicroseconds; + +// static +Time Time::Now() { + return FromCFAbsoluteTime(CFAbsoluteTimeGetCurrent()); +} + +// static +Time Time::FromCFAbsoluteTime(CFAbsoluteTime t) { + COMPILE_ASSERT(std::numeric_limits::has_infinity, + numeric_limits_infinity_is_undefined_when_not_has_infinity); + if (t == 0) + return Time(); // Consider 0 as a null Time. + if (t == std::numeric_limits::infinity()) + return Max(); + return Time(static_cast( + (t + kCFAbsoluteTimeIntervalSince1970) * kMicrosecondsPerSecond) + + kWindowsEpochDeltaMicroseconds); +} + +CFAbsoluteTime Time::ToCFAbsoluteTime() const { + COMPILE_ASSERT(std::numeric_limits::has_infinity, + numeric_limits_infinity_is_undefined_when_not_has_infinity); + if (is_null()) + return 0; // Consider 0 as a null Time. + if (is_max()) + return std::numeric_limits::infinity(); + return (static_cast(us_ - kWindowsEpochDeltaMicroseconds) / + kMicrosecondsPerSecond) - kCFAbsoluteTimeIntervalSince1970; +} + +// static +Time Time::NowFromSystemTime() { + // Just use Now() because Now() returns the system time. + return Now(); +} + +// static +Time Time::FromExploded(bool is_local, const Exploded& exploded) { + CFGregorianDate date; + date.second = exploded.second + + exploded.millisecond / static_cast(kMillisecondsPerSecond); + date.minute = exploded.minute; + date.hour = exploded.hour; + date.day = exploded.day_of_month; + date.month = exploded.month; + date.year = exploded.year; + + butil::ScopedCFTypeRef time_zone( + is_local ? CFTimeZoneCopySystem() : NULL); + CFAbsoluteTime seconds = CFGregorianDateGetAbsoluteTime(date, time_zone) + + kCFAbsoluteTimeIntervalSince1970; + return Time(static_cast(seconds * kMicrosecondsPerSecond) + + kWindowsEpochDeltaMicroseconds); +} + +void Time::Explode(bool is_local, Exploded* exploded) const { + // Avoid rounding issues, by only putting the integral number of seconds + // (rounded towards -infinity) into a |CFAbsoluteTime| (which is a |double|). + int64_t microsecond = us_ % kMicrosecondsPerSecond; + if (microsecond < 0) + microsecond += kMicrosecondsPerSecond; + CFAbsoluteTime seconds = ((us_ - microsecond) / kMicrosecondsPerSecond) - + kWindowsEpochDeltaSeconds - + kCFAbsoluteTimeIntervalSince1970; + + butil::ScopedCFTypeRef time_zone( + is_local ? CFTimeZoneCopySystem() : NULL); + CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(seconds, time_zone); + // 1 = Monday, ..., 7 = Sunday. + int cf_day_of_week = CFAbsoluteTimeGetDayOfWeek(seconds, time_zone); + + exploded->year = date.year; + exploded->month = date.month; + exploded->day_of_week = cf_day_of_week % 7; + exploded->day_of_month = date.day; + exploded->hour = date.hour; + exploded->minute = date.minute; + // Make sure seconds are rounded down towards -infinity. + exploded->second = floor(date.second); + // Calculate milliseconds ourselves, since we rounded the |seconds|, making + // sure to round towards -infinity. + exploded->millisecond = + (microsecond >= 0) ? microsecond / kMicrosecondsPerMillisecond : + (microsecond - kMicrosecondsPerMillisecond + 1) / + kMicrosecondsPerMillisecond; +} + +// TimeTicks ------------------------------------------------------------------ + +// static +TimeTicks TimeTicks::Now() { + return TimeTicks(ComputeCurrentTicks()); +} + +// static +TimeTicks TimeTicks::HighResNow() { + return Now(); +} + +// static +bool TimeTicks::IsHighResNowFastAndReliable() { + return true; +} + +// static +TimeTicks TimeTicks::ThreadNow() { + return TimeTicks(ComputeThreadTicks()); +} + +// static +TimeTicks TimeTicks::NowFromSystemTraceTime() { + return HighResNow(); +} + +} // namespace butil diff --git a/src/butil/time/time_posix.cc b/src/butil/time/time_posix.cc new file mode 100644 index 0000000..2b36383 --- /dev/null +++ b/src/butil/time/time_posix.cc @@ -0,0 +1,386 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/time/time.h" + +#include +#include +#include +#if defined(OS_ANDROID) && !defined(__LP64__) +#include +#endif +#include + +#include +#include + +#include "butil/basictypes.h" +#include "butil/logging.h" +#include "butil/port.h" +#include "butil/build_config.h" + +#if defined(OS_ANDROID) +#include "butil/os_compat_android.h" +#elif defined(OS_NACL) +#include "butil/os_compat_nacl.h" +#endif + +namespace { + +#if !defined(OS_MACOSX) +// Define a system-specific SysTime that wraps either to a time_t or +// a time64_t depending on the host system, and associated convertion. +// See crbug.com/162007 +#if defined(OS_ANDROID) && !defined(__LP64__) +typedef time64_t SysTime; + +SysTime SysTimeFromTimeStruct(struct tm* timestruct, bool is_local) { + if (is_local) + return mktime64(timestruct); + else + return timegm64(timestruct); +} + +void SysTimeToTimeStruct(SysTime t, struct tm* timestruct, bool is_local) { + if (is_local) + localtime64_r(&t, timestruct); + else + gmtime64_r(&t, timestruct); +} + +#else // OS_ANDROID && !__LP64__ +typedef time_t SysTime; + +SysTime SysTimeFromTimeStruct(struct tm* timestruct, bool is_local) { + if (is_local) + return mktime(timestruct); + else + return timegm(timestruct); +} + +void SysTimeToTimeStruct(SysTime t, struct tm* timestruct, bool is_local) { + if (is_local) + localtime_r(&t, timestruct); + else + gmtime_r(&t, timestruct); +} +#endif // OS_ANDROID + +// Helper function to get results from clock_gettime() as TimeTicks object. +// Minimum requirement is MONOTONIC_CLOCK to be supported on the system. +// FreeBSD 6 has CLOCK_MONOTONIC but defines _POSIX_MONOTONIC_CLOCK to -1. +#if (defined(OS_POSIX) && \ + defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK >= 0) || \ + defined(OS_BSD) || defined(OS_ANDROID) +butil::TimeTicks ClockNow(clockid_t clk_id) { + uint64_t absolute_micro; + + struct timespec ts; + if (clock_gettime(clk_id, &ts) != 0) { + NOTREACHED() << "clock_gettime(" << clk_id << ") failed."; + return butil::TimeTicks(); + } + + absolute_micro = + (static_cast(ts.tv_sec) * butil::Time::kMicrosecondsPerSecond) + + (static_cast(ts.tv_nsec) / butil::Time::kNanosecondsPerMicrosecond); + + return butil::TimeTicks::FromInternalValue(absolute_micro); +} +#else // _POSIX_MONOTONIC_CLOCK +#error No usable tick clock function on this platform. +#endif // _POSIX_MONOTONIC_CLOCK +#endif // !defined(OS_MACOSX) + +} // namespace + +namespace butil { + +struct timespec TimeDelta::ToTimeSpec() const { + int64_t microseconds = InMicroseconds(); + time_t seconds = 0; + if (microseconds >= Time::kMicrosecondsPerSecond) { + seconds = InSeconds(); + microseconds -= seconds * Time::kMicrosecondsPerSecond; + } + struct timespec result = + {seconds, + static_cast(microseconds * Time::kNanosecondsPerMicrosecond)}; + return result; +} + +#if !defined(OS_MACOSX) +// The Time routines in this file use standard POSIX routines, or almost- +// standard routines in the case of timegm. We need to use a Mach-specific +// function for TimeTicks::Now() on Mac OS X. + +// Time ----------------------------------------------------------------------- + +// Windows uses a Gregorian epoch of 1601. We need to match this internally +// so that our time representations match across all platforms. See bug 14734. +// irb(main):010:0> Time.at(0).getutc() +// => Thu Jan 01 00:00:00 UTC 1970 +// irb(main):011:0> Time.at(-11644473600).getutc() +// => Mon Jan 01 00:00:00 UTC 1601 +static const int64_t kWindowsEpochDeltaSeconds = GG_INT64_C(11644473600); + +// static +const int64_t Time::kWindowsEpochDeltaMicroseconds = + kWindowsEpochDeltaSeconds * Time::kMicrosecondsPerSecond; + +// Some functions in time.cc use time_t directly, so we provide an offset +// to convert from time_t (Unix epoch) and internal (Windows epoch). +// static +const int64_t Time::kTimeTToMicrosecondsOffset = kWindowsEpochDeltaMicroseconds; + +// static +Time Time::Now() { + struct timeval tv; + struct timezone tz = { 0, 0 }; // UTC + if (gettimeofday(&tv, &tz) != 0) { + DCHECK(0) << "Could not determine time of day"; + PLOG(ERROR) << "Call to gettimeofday failed."; + // Return null instead of uninitialized |tv| value, which contains random + // garbage data. This may result in the crash seen in crbug.com/147570. + return Time(); + } + // Combine seconds and microseconds in a 64-bit field containing microseconds + // since the epoch. That's enough for nearly 600 centuries. Adjust from + // Unix (1970) to Windows (1601) epoch. + return Time((tv.tv_sec * kMicrosecondsPerSecond + tv.tv_usec) + + kWindowsEpochDeltaMicroseconds); +} + +// static +Time Time::NowFromSystemTime() { + // Just use Now() because Now() returns the system time. + return Now(); +} + +void Time::Explode(bool is_local, Exploded* exploded) const { + // Time stores times with microsecond resolution, but Exploded only carries + // millisecond resolution, so begin by being lossy. Adjust from Windows + // epoch (1601) to Unix epoch (1970); + int64_t microseconds = us_ - kWindowsEpochDeltaMicroseconds; + // The following values are all rounded towards -infinity. + int64_t milliseconds; // Milliseconds since epoch. + SysTime seconds; // Seconds since epoch. + int millisecond; // Exploded millisecond value (0-999). + if (microseconds >= 0) { + // Rounding towards -infinity <=> rounding towards 0, in this case. + milliseconds = microseconds / kMicrosecondsPerMillisecond; + seconds = milliseconds / kMillisecondsPerSecond; + millisecond = milliseconds % kMillisecondsPerSecond; + } else { + // Round these *down* (towards -infinity). + milliseconds = (microseconds - kMicrosecondsPerMillisecond + 1) / + kMicrosecondsPerMillisecond; + seconds = (milliseconds - kMillisecondsPerSecond + 1) / + kMillisecondsPerSecond; + // Make this nonnegative (and between 0 and 999 inclusive). + millisecond = milliseconds % kMillisecondsPerSecond; + if (millisecond < 0) + millisecond += kMillisecondsPerSecond; + } + + struct tm timestruct; + SysTimeToTimeStruct(seconds, ×truct, is_local); + + exploded->year = timestruct.tm_year + 1900; + exploded->month = timestruct.tm_mon + 1; + exploded->day_of_week = timestruct.tm_wday; + exploded->day_of_month = timestruct.tm_mday; + exploded->hour = timestruct.tm_hour; + exploded->minute = timestruct.tm_min; + exploded->second = timestruct.tm_sec; + exploded->millisecond = millisecond; +} + +// static +Time Time::FromExploded(bool is_local, const Exploded& exploded) { + struct tm timestruct; + timestruct.tm_sec = exploded.second; + timestruct.tm_min = exploded.minute; + timestruct.tm_hour = exploded.hour; + timestruct.tm_mday = exploded.day_of_month; + timestruct.tm_mon = exploded.month - 1; + timestruct.tm_year = exploded.year - 1900; + timestruct.tm_wday = exploded.day_of_week; // mktime/timegm ignore this + timestruct.tm_yday = 0; // mktime/timegm ignore this + timestruct.tm_isdst = -1; // attempt to figure it out +#if !defined(OS_NACL) && !defined(OS_SOLARIS) + timestruct.tm_gmtoff = 0; // not a POSIX field, so mktime/timegm ignore + timestruct.tm_zone = NULL; // not a POSIX field, so mktime/timegm ignore +#endif + + + int64_t milliseconds; + SysTime seconds; + + // Certain exploded dates do not really exist due to daylight saving times, + // and this causes mktime() to return implementation-defined values when + // tm_isdst is set to -1. On Android, the function will return -1, while the + // C libraries of other platforms typically return a liberally-chosen value. + // Handling this requires the special code below. + + // SysTimeFromTimeStruct() modifies the input structure, save current value. + struct tm timestruct0 = timestruct; + + seconds = SysTimeFromTimeStruct(×truct, is_local); + if (seconds == -1) { + // Get the time values with tm_isdst == 0 and 1, then select the closest one + // to UTC 00:00:00 that isn't -1. + timestruct = timestruct0; + timestruct.tm_isdst = 0; + int64_t seconds_isdst0 = SysTimeFromTimeStruct(×truct, is_local); + + timestruct = timestruct0; + timestruct.tm_isdst = 1; + int64_t seconds_isdst1 = SysTimeFromTimeStruct(×truct, is_local); + + // seconds_isdst0 or seconds_isdst1 can be -1 for some timezones. + // E.g. "CLST" (Chile Summer Time) returns -1 for 'tm_isdt == 1'. + if (seconds_isdst0 < 0) + seconds = seconds_isdst1; + else if (seconds_isdst1 < 0) + seconds = seconds_isdst0; + else + seconds = std::min(seconds_isdst0, seconds_isdst1); + } + + // Handle overflow. Clamping the range to what mktime and timegm might + // return is the best that can be done here. It's not ideal, but it's better + // than failing here or ignoring the overflow case and treating each time + // overflow as one second prior to the epoch. + if (seconds == -1 && + (exploded.year < 1969 || exploded.year > 1970)) { + // If exploded.year is 1969 or 1970, take -1 as correct, with the + // time indicating 1 second prior to the epoch. (1970 is allowed to handle + // time zone and DST offsets.) Otherwise, return the most future or past + // time representable. Assumes the time_t epoch is 1970-01-01 00:00:00 UTC. + // + // The minimum and maximum representible times that mktime and timegm could + // return are used here instead of values outside that range to allow for + // proper round-tripping between exploded and counter-type time + // representations in the presence of possible truncation to time_t by + // division and use with other functions that accept time_t. + // + // When representing the most distant time in the future, add in an extra + // 999ms to avoid the time being less than any other possible value that + // this function can return. + + // On Android, SysTime is int64_t, special care must be taken to avoid + // overflows. + const int64_t min_seconds = (sizeof(SysTime) < sizeof(int64_t)) + ? std::numeric_limits::min() + : std::numeric_limits::min(); + const int64_t max_seconds = (sizeof(SysTime) < sizeof(int64_t)) + ? std::numeric_limits::max() + : std::numeric_limits::max(); + if (exploded.year < 1969) { + milliseconds = min_seconds * kMillisecondsPerSecond; + } else { + milliseconds = max_seconds * kMillisecondsPerSecond; + milliseconds += (kMillisecondsPerSecond - 1); + } + } else { + milliseconds = seconds * kMillisecondsPerSecond + exploded.millisecond; + } + + // Adjust from Unix (1970) to Windows (1601) epoch. + return Time((milliseconds * kMicrosecondsPerMillisecond) + + kWindowsEpochDeltaMicroseconds); +} + +// TimeTicks ------------------------------------------------------------------ +// static +TimeTicks TimeTicks::Now() { + return ClockNow(CLOCK_MONOTONIC); +} + +// static +TimeTicks TimeTicks::HighResNow() { + return Now(); +} + +// static +bool TimeTicks::IsHighResNowFastAndReliable() { + return true; +} + +// static +TimeTicks TimeTicks::ThreadNow() { +#if (defined(_POSIX_THREAD_CPUTIME) && (_POSIX_THREAD_CPUTIME >= 0)) || \ + defined(OS_ANDROID) + return ClockNow(CLOCK_THREAD_CPUTIME_ID); +#else + NOTREACHED(); + return TimeTicks(); +#endif +} + +// Use the Chrome OS specific system-wide clock. +#if defined(OS_CHROMEOS) +// static +TimeTicks TimeTicks::NowFromSystemTraceTime() { + uint64_t absolute_micro; + + struct timespec ts; + if (clock_gettime(kClockSystemTrace, &ts) != 0) { + // NB: fall-back for a chrome os build running on linux + return HighResNow(); + } + + absolute_micro = + (static_cast(ts.tv_sec) * Time::kMicrosecondsPerSecond) + + (static_cast(ts.tv_nsec) / Time::kNanosecondsPerMicrosecond); + + return TimeTicks(absolute_micro); +} + +#else // !defined(OS_CHROMEOS) + +// static +TimeTicks TimeTicks::NowFromSystemTraceTime() { + return HighResNow(); +} + +#endif // defined(OS_CHROMEOS) + +#endif // !OS_MACOSX + +// static +Time Time::FromTimeVal(struct timeval t) { + DCHECK_LT(t.tv_usec, static_cast(Time::kMicrosecondsPerSecond)); + DCHECK_GE(t.tv_usec, 0); + if (t.tv_usec == 0 && t.tv_sec == 0) + return Time(); + if (t.tv_usec == static_cast(Time::kMicrosecondsPerSecond) - 1 && + t.tv_sec == std::numeric_limits::max()) + return Max(); + return Time( + (static_cast(t.tv_sec) * Time::kMicrosecondsPerSecond) + + t.tv_usec + + kTimeTToMicrosecondsOffset); +} + +struct timeval Time::ToTimeVal() const { + struct timeval result; + if (is_null()) { + result.tv_sec = 0; + result.tv_usec = 0; + return result; + } + if (is_max()) { + result.tv_sec = std::numeric_limits::max(); + result.tv_usec = static_cast(Time::kMicrosecondsPerSecond) - 1; + return result; + } + int64_t us = us_ - kTimeTToMicrosecondsOffset; + result.tv_sec = us / Time::kMicrosecondsPerSecond; + result.tv_usec = us % Time::kMicrosecondsPerSecond; + return result; +} + +} // namespace butil diff --git a/src/butil/type_traits.h b/src/butil/type_traits.h new file mode 100644 index 0000000..735e1e5 --- /dev/null +++ b/src/butil/type_traits.h @@ -0,0 +1,401 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_TYPE_TRAITS_H +#define BUTIL_TYPE_TRAITS_H + +#include // For size_t. +#include "butil/build_config.h" + +#if defined(BUTIL_CXX11_ENABLED) +#include +#endif + +namespace butil { + +// integral_constant, defined in tr1, is a wrapper for an integer +// value. We don't really need this generality; we could get away +// with hardcoding the integer type to bool. We use the fully +// general integer_constant for compatibility with tr1. +template +struct integral_constant { + static const T value = v; + typedef T value_type; + typedef integral_constant type; +}; + +template const T integral_constant::value; + +typedef integral_constant true_type; +typedef integral_constant false_type; + + +template struct is_integral; +template struct is_floating_point; +template struct is_pointer; +template struct is_member_function_pointer; +template struct is_enum; +template struct is_void; +template struct is_pod; +template struct is_const; +template struct is_array; +template struct is_reference; +template struct is_non_const_reference; +template struct is_same; +template struct remove_const; +template struct remove_volatile; +template struct remove_cv; +template struct remove_reference; +template struct remove_const_reference; +template struct add_const; +template struct add_volatile; +template struct add_cv; +template struct add_reference; +template struct add_const_reference; +template struct remove_pointer; +template struct add_cr_non_integral; +template struct is_convertible; +template struct conditional; + + +// is_integral is false except for the built-in integer types. +template struct is_integral : false_type { }; +template<> struct is_integral : true_type { }; +template<> struct is_integral : true_type { }; +template<> struct is_integral : true_type { }; +template<> struct is_integral : true_type { }; +#if defined(_MSC_VER) +// wchar_t is not by default a distinct type from unsigned short in +// Microsoft C. +// See http://msdn2.microsoft.com/en-us/library/dh8che7s(VS.80).aspx +template<> struct is_integral<__wchar_t> : true_type { }; +#else +template<> struct is_integral : true_type { }; +#endif +template<> struct is_integral : true_type { }; +template<> struct is_integral : true_type { }; +template<> struct is_integral : true_type { }; +template<> struct is_integral : true_type { }; +template<> struct is_integral : true_type { }; +template<> struct is_integral : true_type { }; +template<> struct is_integral : true_type { }; +template<> struct is_integral : true_type { }; + +// is_floating_point is false except for the built-in floating-point types. +template struct is_floating_point : false_type { }; +template<> struct is_floating_point : true_type { }; +template<> struct is_floating_point : true_type { }; +template<> struct is_floating_point : true_type { }; + +template struct is_pointer : false_type {}; +template struct is_pointer : true_type {}; + +#if defined(BUTIL_CXX11_ENABLED) + +#if __cplusplus >= 202002L +template struct is_pod +: integral_constant::value && + std::is_trivial::value)> {}; +#else +template struct is_pod : std::is_pod {}; +#endif // __cplusplus + +#else +// We can't get is_pod right without compiler help, so fail conservatively. +// We will assume it's false except for arithmetic types, enumerations, +// pointers and cv-qualified versions thereof. Note that std::pair +// is not a POD even if T and U are PODs. +template struct is_pod +: integral_constant::value || + is_floating_point::value || + is_enum::value || + is_pointer::value)> { }; +template struct is_pod : is_pod { }; +template struct is_pod : is_pod { }; +template struct is_pod : is_pod { }; +#endif + +// Member function pointer detection up to four params. Add more as needed +// below. This is built-in to C++ 11, and we can remove this when we switch. +template +struct is_member_function_pointer : false_type {}; + +template +struct is_member_function_pointer : true_type {}; +template +struct is_member_function_pointer : true_type {}; + +template +struct is_member_function_pointer : true_type {}; +template +struct is_member_function_pointer : true_type {}; + +template +struct is_member_function_pointer : true_type {}; +template +struct is_member_function_pointer : true_type {}; + +template +struct is_member_function_pointer : true_type {}; +template +struct is_member_function_pointer : true_type {}; + +template +struct is_member_function_pointer : true_type {}; +template +struct is_member_function_pointer : true_type {}; + +// Specified by TR1 [4.6] Relationships between types +template struct is_same : public false_type {}; +template struct is_same : true_type {}; + +template struct is_array : public false_type {}; +template struct is_array : public true_type {}; +template struct is_array : public true_type {}; + +template struct is_non_const_reference : false_type {}; +template struct is_non_const_reference : true_type {}; +template struct is_non_const_reference : false_type {}; + +template struct is_const : false_type {}; +template struct is_const : true_type {}; + +template struct is_void : false_type {}; +template <> struct is_void : true_type {}; + +namespace internal { + +// Types YesType and NoType are guaranteed such that sizeof(YesType) < +// sizeof(NoType). +typedef char YesType; + +struct NoType { + YesType dummy[2]; +}; + +// This class is an implementation detail for is_convertible, and you +// don't need to know how it works to use is_convertible. For those +// who care: we declare two different functions, one whose argument is +// of type To and one with a variadic argument list. We give them +// return types of different size, so we can use sizeof to trick the +// compiler into telling us which function it would have chosen if we +// had called it with an argument of type From. See Alexandrescu's +// _Modern C++ Design_ for more details on this sort of trick. + +struct ConvertHelper { + template + static YesType Test(To); + + template + static NoType Test(...); + + template + static From& Create(); +}; + +// Used to determine if a type is a struct/union/class. Inspired by Boost's +// is_class type_trait implementation. +struct IsClassHelper { + template + static YesType Test(void(C::*)(void)); + + template + static NoType Test(...); +}; + +// For implementing is_empty +#if defined(COMPILER_MSVC) +#pragma warning(push) +#pragma warning(disable:4624) // destructor could not be generated +#endif + +template +struct EmptyHelper1 : public T { + EmptyHelper1(); // hh compiler bug workaround + int i[256]; +private: + // suppress compiler warnings: + EmptyHelper1(const EmptyHelper1&); + EmptyHelper1& operator=(const EmptyHelper1&); +}; + +#if defined(COMPILER_MSVC) +#pragma warning(pop) +#endif + +struct EmptyHelper2 { + int i[256]; +}; + +} // namespace internal + + +// Inherits from true_type if From is convertible to To, false_type otherwise. +// +// Note that if the type is convertible, this will be a true_type REGARDLESS +// of whether or not the conversion would emit a warning. +template +struct is_convertible + : integral_constant( + internal::ConvertHelper::Create())) == + sizeof(internal::YesType)> { +}; + +template +struct is_class + : integral_constant(0)) == + sizeof(internal::YesType)> { +}; + +// True if T is an empty class/struct +// NOTE: not work for union +template +struct is_empty : integral_constant::value && + sizeof(internal::EmptyHelper1) == sizeof(internal::EmptyHelper2)> {}; + +template +struct enable_if {}; + +template +struct enable_if { typedef T type; }; + +// Select type by C. +template +struct conditional { + typedef TrueType type; +}; +template +struct conditional { + typedef FalseType type; +}; + +// Specified by TR1 [4.7.1] +template struct add_const { typedef _Tp const type; }; +template struct add_volatile { typedef _Tp volatile type; }; +template struct add_cv { + typedef typename add_const::type>::type type; +}; +template struct remove_const { typedef T type; }; +template struct remove_const { typedef T type; }; +template struct remove_volatile { typedef T type; }; +template struct remove_volatile { typedef T type; }; +template struct remove_cv { + typedef typename remove_const::type>::type type; +}; + +// Specified by TR1 [4.7.2] Reference modifications. +template struct remove_reference { typedef T type; }; +template struct remove_reference { typedef T type; }; + +template struct add_reference { typedef T& type; }; +template struct add_reference { typedef T& type; }; +// Specializations for void which can't be referenced. +template <> struct add_reference { typedef void type; }; +template <> struct add_reference { typedef void const type; }; +template <> struct add_reference { typedef void volatile type; }; +template <> struct add_reference { typedef void const volatile type; }; + +// Shortcut for adding/removing const& +template struct add_const_reference { + typedef typename add_reference::type>::type type; +}; +template struct remove_const_reference { + typedef typename remove_const::type>::type type; +}; + +// Add const& for non-integral types. +// add_cr_non_integral::type -> int +// add_cr_non_integral::type -> const FooClass& +template struct add_cr_non_integral { + typedef typename conditional::value, T, + typename add_reference::type>::type>::type type; +}; + +// Specified by TR1 [4.7.4] Pointer modifications. +template struct remove_pointer { typedef T type; }; +template struct remove_pointer { typedef T type; }; +template struct remove_pointer { typedef T type; }; +template struct remove_pointer { typedef T type; }; +template struct remove_pointer { + typedef T type; +}; + +// Shortcut for removing const, volatile and reference. +#if __cplusplus >= 202002L +template +using remove_cvref = std::remove_cvref; +#else +template +struct remove_cvref { + typedef typename remove_cv::type>::type type; +}; +#endif // __cplusplus + +// is_reference is false except for reference types. +template struct is_reference : false_type {}; +template struct is_reference : true_type {}; + +namespace internal { +// is_convertible chokes if the first argument is an array. That's why +// we use add_reference here. +template struct is_enum_impl + : is_convertible::type, int> { }; + +template struct is_enum_impl : false_type { }; + +} + +template struct is_enum + : internal::is_enum_impl< + is_same::value || + is_integral::value || + is_floating_point::value || + is_reference::value || + is_class::value, T> { }; + +template struct is_enum : is_enum { }; +template struct is_enum : is_enum { }; +template struct is_enum : is_enum { }; + +// Deduces the return type of an INVOKE expression +// at compile time. +// If the callable is non-static member function, +// the first argument should be the class type. +#if __cplusplus >= 201703L +// std::result_of is deprecated in C++17 and removed in C++20, +// use std::invoke_result instead. +template +struct result_of; +template +struct result_of : std::invoke_result {}; +#elif __cplusplus >= 201103L +template +using result_of = std::result_of; +#else +#error Only C++11 or later is supported. +#endif // __cplusplus + +template +using result_of_t = typename result_of::type; + +// Whether a callable returns type which is same as ReturnType. +template +struct is_result_same + : public butil::is_same> {}; + +// Whether a callable returns void. +template +struct is_result_void : public is_result_same {}; + +// Whether a callable returns int. +template +struct is_result_int : public is_result_same {}; + +} // namespace butil + +#endif // BUTIL_TYPE_TRAITS_H diff --git a/src/butil/unique_ptr.h b/src/butil/unique_ptr.h new file mode 100644 index 0000000..4fa44b8 --- /dev/null +++ b/src/butil/unique_ptr.h @@ -0,0 +1,472 @@ +// Copyright 2009 Howard Hinnant, Ion Gaztañaga. +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// See http://www.boost.org/libs/foreach for documentation + +// This is a C++03 emulation of std::unique_ptr placed in namespace boost. +// Reference http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2800.pdf +// for the latest unique_ptr specification, and +// reference http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html +// for any pending issues against this specification. + +#ifndef BUTIL_UNIQUE_PTR_H +#define BUTIL_UNIQUE_PTR_H + +#include "butil/build_config.h" + +#if defined(BUTIL_CXX11_ENABLED) + +#include // std::unique_ptr + +#elif !defined(BAIDU_NO_EMULATED_UNIQUE_PTR) + +#include // std::swap until C++11 +#include "butil/type_traits.h" +#include "butil/macros.h" // BAIDU_CASSERT + +namespace std { + +namespace up_detail { + +typedef char one; +struct two {one _[2];}; + +// An is_convertible that considers From an rvalue (consistent with C++0X). +// This is a simplified version neglecting the types function, array, void and abstract types +// I had to make a special case out of is_convertible to make move-only +// types happy. + +namespace is_conv_imp { +template one test1(const T&); +template two test1(...); +template one test2(T); +template two test2(...); +template T source(); +} + +template +struct is_convertible { + static const bool value = sizeof(is_conv_imp::test1(is_conv_imp::source())) == 1; +}; + +template +struct is_convertible { + static const bool value = sizeof(is_conv_imp::test2(is_conv_imp::source())) == 1; +}; + +template +class rv { + T& r_; + +public: + explicit rv(T& r) : r_(r) {} + T* operator->() {return &r_;} + T& operator*() {return r_;} +}; + +template +struct identity { + typedef T type; +}; + +} // up_detail + +template +inline +typename butil::enable_if< + !up_detail::is_convertible >::value, + T& +>::type +move(T& t) { + return t; +} + +template +inline +typename butil::enable_if < + !up_detail::is_convertible >::value, + const T& +>::type +move(const T& t) { + return t; +} + +template +inline +typename butil::enable_if < + up_detail::is_convertible >::value, + T +>::type +move(T& t) { + return T(up_detail::rv(t)); +} + +template +inline +typename butil::enable_if < + butil::is_reference::value, + T +>::type +forward(typename up_detail::identity::type t) { + return t; +} + +template +inline +typename butil::enable_if < + !butil::is_reference::value, + T +>::type +forward(typename up_detail::identity::type& t) { + return move(t); +} + +template +inline +typename butil::enable_if < + !butil::is_reference::value, + T +>::type +forward(const typename up_detail::identity::type& t) { + return move(const_cast(t)); +} + +namespace up_detail { + +// A move-aware but stripped-down compressed_pair which only optimizes storage for T2 +template ::value> +class UniquePtrStorage { + T1 t1_; + T2 t2_; + + typedef typename butil::add_reference::type T2_reference; + typedef typename butil::add_reference::type T2_const_reference; + + UniquePtrStorage(const UniquePtrStorage&); + UniquePtrStorage& operator=(const UniquePtrStorage&); +public: + operator rv() {return rv(*this);} + + UniquePtrStorage() : t1_(), t2_() {} + explicit UniquePtrStorage(T1 t1) : t1_(move(t1)), t2_() {} + UniquePtrStorage(T1 t1, T2 t2) : t1_(move(t1)), t2_(forward(t2)) {} + + T1& first() {return t1_;} + const T1& first() const {return t1_;} + + T2_reference second() {return t2_;} + T2_const_reference second() const {return t2_;} +}; + +template +class UniquePtrStorage : private T2 { + T1 t1_; + typedef T2 t2_; + + UniquePtrStorage(const UniquePtrStorage&); + UniquePtrStorage& operator=(const UniquePtrStorage&); +public: + operator rv() {return rv(*this);} + + UniquePtrStorage() : t1_() {} + explicit UniquePtrStorage(T1 t1) : t1_(move(t1)) {} + UniquePtrStorage(T1 t1, T2 t2) : t2_(move(t2)), t1_(move(t1)) {} + + T1& first() {return t1_;} + const T1& first() const {return t1_;} + + T2& second() {return *this;} + const T2& second() const {return *this;} +}; + +template +inline +void +swap(UniquePtrStorage& x, UniquePtrStorage& y) { + using std::swap; + swap(x.first(), y.first()); + swap(x.second(), y.second()); +} + +} // up_detail + +template +struct default_delete { + default_delete() {} + template + default_delete(const default_delete&, + typename butil::enable_if::value>::type* = 0) {} + + void operator()(T* ptr) const { + BAIDU_CASSERT(sizeof(T) > 0, T_is_empty); + delete ptr; + } +}; + +template +struct default_delete { + void operator()(T* ptr) const { + BAIDU_CASSERT(sizeof(T) > 0, T_is_empty); + delete [] ptr; + } + +private: + + template void operator()(U*) const; +}; + +namespace up_detail { + +namespace pointer_type_imp { + +template static two test(...); +template static one test(typename U::pointer* = 0); + +} // pointer_type_imp + +template +struct has_pointer_type { + static const bool value = sizeof(pointer_type_imp::test(0)) == 1; +}; + +namespace pointer_type_imp { + +template ::value> +struct pointer_type { + typedef typename D::pointer type; +}; + +template +struct pointer_type { + typedef T* type; +}; + +} // pointer_type_imp + +template +struct pointer_type { + typedef typename pointer_type_imp::pointer_type::type>::type type; +}; + +} // up_detail + +template > +class unique_ptr { +public: + typedef T element_type; + typedef D deleter_type; + typedef typename up_detail::pointer_type::type pointer; + +private: + up_detail::UniquePtrStorage ptr_; + + typedef typename butil::add_reference::type deleter_reference; + typedef typename butil::add_reference::type deleter_const_reference; + + struct nat {int for_bool_;}; + + unique_ptr(unique_ptr&); + unique_ptr& operator=(unique_ptr&); + +public: + operator up_detail::rv() { + return up_detail::rv(*this); + } + unique_ptr(up_detail::rv r) + : ptr_(r->release(), forward(r->get_deleter())) {} + unique_ptr& operator=(up_detail::rv r) { + reset(r->release()); + ptr_.second() = move(r->get_deleter()); + return *this; + } + + unique_ptr() {} + + explicit unique_ptr(pointer p) : ptr_(p) {} + + unique_ptr(pointer p, typename butil::conditional::value, + volatile typename butil::remove_reference::type&, D>::type d) + : ptr_(move(p), forward(const_cast::type>(d))) {} + + template + unique_ptr(unique_ptr u, + typename butil::enable_if < + !butil::is_array::value && + up_detail::is_convertible::pointer, pointer>::value +&& + up_detail::is_convertible::value && + ( + !butil::is_reference::value || + butil::is_same::value) + >::type* = 0) + : ptr_(u.release(), forward(forward(u.get_deleter()))) {} + + ~unique_ptr() { + BAIDU_CASSERT(!butil::is_reference::value, deleter_is_ref); + BAIDU_CASSERT(!butil::is_pointer::value, deleter_is_pointer); + reset(); + } + + unique_ptr& operator=(int nat::*) { + reset(); + return *this; + } + + template + unique_ptr& operator=(unique_ptr u) { + reset(u.release()); + ptr_.second() = move(u.get_deleter()); + return *this; + } + + typename butil::add_reference::type operator*() const {return *get();} + pointer operator->() const {return get();} + pointer get() const {return ptr_.first();} + deleter_reference get_deleter() {return ptr_.second();} + deleter_const_reference get_deleter() const {return ptr_.second();} + operator int nat::*() const {return get() ? &nat::for_bool_ : 0;} + + void reset(pointer p = pointer()) { + pointer t = get(); + if (t != pointer()) + get_deleter()(t); + ptr_.first() = p; + } + + pointer release() { + pointer tmp = get(); + ptr_.first() = pointer(); + return tmp; + } + + void swap(unique_ptr& u) {up_detail::swap(ptr_, u.ptr_);} +}; + +template +class unique_ptr { +public: + typedef T element_type; + typedef D deleter_type; + typedef typename up_detail::pointer_type::type pointer; + +private: + up_detail::UniquePtrStorage ptr_; + + typedef typename butil::add_reference::type deleter_reference; + typedef typename butil::add_reference::type deleter_const_reference; + + struct nat {int for_bool_;}; + + unique_ptr(unique_ptr&); + unique_ptr& operator=(unique_ptr&); + +public: + operator up_detail::rv() {return up_detail::rv(*this);} + unique_ptr(up_detail::rv r) + : ptr_(r->release(), forward(r->get_deleter())) {} + unique_ptr& operator=(up_detail::rv r) { + reset(r->release()); + ptr_.second() = move(r->get_deleter()); + return *this; + } + + unique_ptr() {} + + explicit unique_ptr(pointer p) : ptr_(p) {} + + unique_ptr(pointer p, typename butil::conditional::value, + volatile typename butil::remove_reference::type&, D>::type d) + : ptr_(move(p), forward(const_cast::type>(d))) {} + + ~unique_ptr() { + BAIDU_CASSERT(!butil::is_reference::value, deleter_is_ref); + BAIDU_CASSERT(!butil::is_pointer::value, deleter_is_pointer); + reset(); + } + + T& operator[](size_t i) const {return get()[i];} + pointer get() const {return ptr_.first();} + deleter_reference get_deleter() {return ptr_.second();} + deleter_const_reference get_deleter() const {return ptr_.second();} + operator int nat::*() const {return get() ? &nat::for_bool_ : 0;} + + void reset(pointer p = pointer()) { + pointer t = get(); + if (t != pointer()) + get_deleter()(t); + ptr_.first() = p; + } + + pointer release() { + pointer tmp = get(); + ptr_.first() = pointer(); + return tmp; + } + + void swap(unique_ptr& u) {up_detail::swap(ptr_, u.ptr_);} +private: + template + explicit unique_ptr( + U, typename butil::enable_if::value>::type* = 0); + + template + unique_ptr(U, typename butil::conditional::value, + volatile typename butil::remove_reference::type&, D>::type, + typename butil::enable_if::value>::type* = 0); +}; + +template +inline +void +swap(unique_ptr& x, unique_ptr& y) { + x.swap(y); +} + +template +inline +bool +operator==(const unique_ptr& x, const unique_ptr& y) { + return x.get() == y.get(); +} + +template +inline +bool +operator!=(const unique_ptr& x, const unique_ptr& y) { + return !(x == y); +} + +template +inline +bool +operator<(const unique_ptr& x, const unique_ptr& y) { + return x.get() < y.get(); +} + +template +inline +bool +operator<=(const unique_ptr& x, const unique_ptr& y) { + return !(y < x); +} + +template +inline +bool +operator>(const unique_ptr& x, const unique_ptr& y) { + return y < x; +} + +template +inline +bool +operator>=(const unique_ptr& x, const unique_ptr& y) { + return !(x < y); +} + +} // namespace std + +#endif // BUTIL_CXX11_ENABLED +#endif // BUTIL_UNIQUE_PTR_H diff --git a/src/butil/unix_socket.cpp b/src/butil/unix_socket.cpp new file mode 100644 index 0000000..732a9f6 --- /dev/null +++ b/src/butil/unix_socket.cpp @@ -0,0 +1,75 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Mon. Jan 27 23:08:35 CST 2014 + +#include // socket +#include // ^ +#include // unix domain socket +#include "butil/fd_guard.h" // fd_guard +#include "butil/logging.h" + +namespace butil { + +int unix_socket_listen(const char* sockname, bool remove_previous_file) { + struct sockaddr_un addr; + addr.sun_family = AF_LOCAL; + snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", sockname); + + fd_guard fd(socket(AF_LOCAL, SOCK_STREAM, 0)); + if (fd < 0) { + PLOG(ERROR) << "Fail to create unix socket"; + return -1; + } + if (remove_previous_file) { + remove(sockname); + } + if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) != 0) { + PLOG(ERROR) << "Fail to bind sockfd=" << fd << " as unix socket=" + << sockname; + return -1; + } + if (listen(fd, SOMAXCONN) != 0) { + PLOG(ERROR) << "Fail to listen to sockfd=" << fd; + return -1; + } + return fd.release(); +} + +int unix_socket_listen(const char* sockname) { + return unix_socket_listen(sockname, true); +} + +int unix_socket_connect(const char* sockname) { + struct sockaddr_un addr; + addr.sun_family = AF_LOCAL; + snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", sockname); + + fd_guard fd(socket(AF_LOCAL, SOCK_STREAM, 0)); + if (fd < 0) { + PLOG(ERROR) << "Fail to create unix socket"; + return -1; + } + if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) != 0) { + PLOG(ERROR) << "Fail to connect to unix socket=" << sockname + << " via sockfd=" << fd; + return -1; + } + return fd.release(); +} + +} // namespace butil diff --git a/src/butil/unix_socket.h b/src/butil/unix_socket.h new file mode 100644 index 0000000..e2f6f32 --- /dev/null +++ b/src/butil/unix_socket.h @@ -0,0 +1,41 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Mon. Jan 27 23:08:35 CST 2014 + +// Wrappers of unix domain sockets, mainly for unit-test of network stuff. + +#ifndef BUTIL_UNIX_SOCKET_H +#define BUTIL_UNIX_SOCKET_H + +namespace butil { + +// Create an unix domain socket at `sockname' and listen to it. +// If remove_previous_file is true or absent, remove previous file before +// creating the socket. +// Returns the file descriptor on success, -1 otherwise and errno is set. +int unix_socket_listen(const char* sockname, bool remove_previous_file); +int unix_socket_listen(const char* sockname); + +// Create an unix domain socket and connect it to another listening unix domain +// socket at `sockname'. +// Returns the file descriptor on success, -1 otherwise and errno is set. +int unix_socket_connect(const char* sockname); + +} // namespace butil + +#endif // BUTIL_UNIX_SOCKET_H diff --git a/src/butil/version.cc b/src/butil/version.cc new file mode 100644 index 0000000..f276d6f --- /dev/null +++ b/src/butil/version.cc @@ -0,0 +1,178 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/version.h" + +#include + +#include + +#include "butil/logging.h" +#include "butil/strings/string_number_conversions.h" +#include "butil/strings/string_split.h" +#include "butil/strings/string_util.h" + +namespace butil { + +namespace { + +// Parses the |numbers| vector representing the different numbers +// inside the version string and constructs a vector of valid integers. It stops +// when it reaches an invalid item (including the wildcard character). |parsed| +// is the resulting integer vector. Function returns true if all numbers were +// parsed successfully, false otherwise. +bool ParseVersionNumbers(const std::string& version_str, + std::vector* parsed) { + std::vector numbers; + SplitString(version_str, '.', &numbers); + if (numbers.empty()) + return false; + + for (std::vector::const_iterator it = numbers.begin(); + it != numbers.end(); ++it) { + int num; + if (!StringToInt(*it, &num)) + return false; + + if (num < 0) + return false; + + const uint16_t max = 0xFFFF; + if (num > max) + return false; + + // This throws out things like +3, or 032. + if (IntToString(num) != *it) + return false; + + parsed->push_back(static_cast(num)); + } + return true; +} + +// Compares version components in |components1| with components in +// |components2|. Returns -1, 0 or 1 if |components1| is less than, equal to, +// or greater than |components2|, respectively. +int CompareVersionComponents(const std::vector& components1, + const std::vector& components2) { + const size_t count = std::min(components1.size(), components2.size()); + for (size_t i = 0; i < count; ++i) { + if (components1[i] > components2[i]) + return 1; + if (components1[i] < components2[i]) + return -1; + } + if (components1.size() > components2.size()) { + for (size_t i = count; i < components1.size(); ++i) { + if (components1[i] > 0) + return 1; + } + } else if (components1.size() < components2.size()) { + for (size_t i = count; i < components2.size(); ++i) { + if (components2[i] > 0) + return -1; + } + } + return 0; +} + +} // namespace + +Version::Version() { +} + +Version::~Version() { +} + +Version::Version(const std::string& version_str) { + std::vector parsed; + if (!ParseVersionNumbers(version_str, &parsed)) + return; + + components_.swap(parsed); +} + +bool Version::IsValid() const { + return (!components_.empty()); +} + +// static +bool Version::IsValidWildcardString(const std::string& wildcard_string) { + std::string version_string = wildcard_string; + if (EndsWith(wildcard_string.c_str(), ".*", false)) + version_string = wildcard_string.substr(0, wildcard_string.size() - 2); + + Version version(version_string); + return version.IsValid(); +} + +bool Version::IsOlderThan(const std::string& version_str) const { + Version proposed_ver(version_str); + if (!proposed_ver.IsValid()) + return false; + return (CompareTo(proposed_ver) < 0); +} + +int Version::CompareToWildcardString(const std::string& wildcard_string) const { + DCHECK(IsValid()); + DCHECK(Version::IsValidWildcardString(wildcard_string)); + + // Default behavior if the string doesn't end with a wildcard. + if (!EndsWith(wildcard_string.c_str(), ".*", false)) { + Version version(wildcard_string); + DCHECK(version.IsValid()); + return CompareTo(version); + } + + std::vector parsed; + const bool success = ParseVersionNumbers( + wildcard_string.substr(0, wildcard_string.length() - 2), &parsed); + DCHECK(success); + const int comparison = CompareVersionComponents(components_, parsed); + // If the version is smaller than the wildcard version's |parsed| vector, + // then the wildcard has no effect (e.g. comparing 1.2.3 and 1.3.*) and the + // version is still smaller. Same logic for equality (e.g. comparing 1.2.2 to + // 1.2.2.* is 0 regardless of the wildcard). Under this logic, + // 1.2.0.0.0.0 compared to 1.2.* is 0. + if (comparison == -1 || comparison == 0) + return comparison; + + // Catch the case where the digits of |parsed| are found in |components_|, + // which means that the two are equal since |parsed| has a trailing "*". + // (e.g. 1.2.3 vs. 1.2.* will return 0). All other cases return 1 since + // components is greater (e.g. 3.2.3 vs 1.*). + DCHECK_GT(parsed.size(), 0UL); + const size_t min_num_comp = std::min(components_.size(), parsed.size()); + for (size_t i = 0; i < min_num_comp; ++i) { + if (components_[i] != parsed[i]) + return 1; + } + return 0; +} + +bool Version::Equals(const Version& that) const { + DCHECK(IsValid()); + DCHECK(that.IsValid()); + return (CompareTo(that) == 0); +} + +int Version::CompareTo(const Version& other) const { + DCHECK(IsValid()); + DCHECK(other.IsValid()); + return CompareVersionComponents(components_, other.components_); +} + +const std::string Version::GetString() const { + DCHECK(IsValid()); + std::string version_str; + size_t count = components_.size(); + for (size_t i = 0; i < count - 1; ++i) { + version_str.append(IntToString(components_[i])); + version_str.append("."); + } + version_str.append(IntToString(components_[count - 1])); + return version_str; +} + +} // namespace butil diff --git a/src/butil/version.h b/src/butil/version.h new file mode 100644 index 0000000..d7b533a --- /dev/null +++ b/src/butil/version.h @@ -0,0 +1,72 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_VERSION_H_ +#define BUTIL_VERSION_H_ + +#include +#include + +#include "butil/base_export.h" +#include "butil/basictypes.h" + +namespace butil { + +// Version represents a dotted version number, like "1.2.3.4", supporting +// parsing and comparison. +class BUTIL_EXPORT Version { + public: + // The only thing you can legally do to a default constructed + // Version object is assign to it. + Version(); + + ~Version(); + + // Initializes from a decimal dotted version number, like "0.1.1". + // Each component is limited to a uint16_t. Call IsValid() to learn + // the outcome. + explicit Version(const std::string& version_str); + + // Returns true if the object contains a valid version number. + bool IsValid() const; + + // Returns true if the version wildcard string is valid. The version wildcard + // string may end with ".*" (e.g. 1.2.*, 1.*). Any other arrangement with "*" + // is invalid (e.g. 1.*.3 or 1.2.3*). This functions defaults to standard + // Version behavior (IsValid) if no wildcard is present. + static bool IsValidWildcardString(const std::string& wildcard_string); + + // Commonly used pattern. Given a valid version object, compare if a + // |version_str| results in a newer version. Returns true if the + // string represents valid version and if the version is greater than + // than the version of this object. + bool IsOlderThan(const std::string& version_str) const; + + bool Equals(const Version& other) const; + + // Returns -1, 0, 1 for <, ==, >. + int CompareTo(const Version& other) const; + + // Given a valid version object, compare if a |wildcard_string| results in a + // newer version. This function will default to CompareTo if the string does + // not end in wildcard sequence ".*". IsValidWildcard(wildcard_string) must be + // true before using this function. + int CompareToWildcardString(const std::string& wildcard_string) const; + + // Return the string representation of this version. + const std::string GetString() const; + + const std::vector& components() const { return components_; } + + private: + std::vector components_; +}; + +} // namespace butil + +// TODO(xhwang) remove this when all users are updated to explicitly use the +// namespace +using butil::Version; + +#endif // BUTIL_VERSION_H_ diff --git a/src/butil/zero_copy_stream_as_streambuf.cpp b/src/butil/zero_copy_stream_as_streambuf.cpp new file mode 100644 index 0000000..540a5bc --- /dev/null +++ b/src/butil/zero_copy_stream_as_streambuf.cpp @@ -0,0 +1,71 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Thu Nov 22 13:57:56 CST 2012 + +#include "butil/macros.h" +#include "butil/zero_copy_stream_as_streambuf.h" + +namespace butil { + +BAIDU_CASSERT(sizeof(std::streambuf::char_type) == sizeof(char), + only_support_char); + +int ZeroCopyStreamAsStreamBuf::overflow(int ch) { + if (ch == std::streambuf::traits_type::eof()) { + return ch; + } + void* block = NULL; + int size = 0; + if (_zero_copy_stream->Next(&block, &size)) { + setp((char*)block, (char*)block + size); + // if size == 0, this function will call overflow again. + return sputc(ch); + } else { + setp(NULL, NULL); + return std::streambuf::traits_type::eof(); + } +} + +int ZeroCopyStreamAsStreamBuf::sync() { + // data are already in IOBuf. + return 0; +} + +ZeroCopyStreamAsStreamBuf::~ZeroCopyStreamAsStreamBuf() { + shrink(); +} + +void ZeroCopyStreamAsStreamBuf::shrink() { + if (pbase() != NULL) { + _zero_copy_stream->BackUp(epptr() - pptr()); + setp(NULL, NULL); + } +} + +std::streampos ZeroCopyStreamAsStreamBuf::seekoff( + std::streamoff off, + std::ios_base::seekdir way, + std::ios_base::openmode which) { + if (off == 0 && way == std::ios_base::cur) { + return _zero_copy_stream->ByteCount() - (epptr() - pptr()); + } + return (std::streampos)(std::streamoff)-1; +} + + +} // namespace butil diff --git a/src/butil/zero_copy_stream_as_streambuf.h b/src/butil/zero_copy_stream_as_streambuf.h new file mode 100644 index 0000000..f6e86a4 --- /dev/null +++ b/src/butil/zero_copy_stream_as_streambuf.h @@ -0,0 +1,54 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Thu Nov 22 13:57:56 CST 2012 + +#ifndef BUTIL_ZERO_COPY_STREAM_AS_STREAMBUF_H +#define BUTIL_ZERO_COPY_STREAM_AS_STREAMBUF_H + +#include +#include + +namespace butil { + +// Wrap a ZeroCopyOutputStream into std::streambuf. Notice that before +// destruction or shrink(), BackUp() of the stream are not called. In another +// word, if the stream is wrapped from IOBuf, the IOBuf may be larger than +// appended data. +class ZeroCopyStreamAsStreamBuf : public std::streambuf { +public: + ZeroCopyStreamAsStreamBuf(google::protobuf::io::ZeroCopyOutputStream* stream) + : _zero_copy_stream(stream) {} + virtual ~ZeroCopyStreamAsStreamBuf(); + + // BackUp() unused bytes. Automatically called in destructor. + void shrink(); + +protected: + int overflow(int ch) override; + int sync() override; + std::streampos seekoff(std::streamoff off, + std::ios_base::seekdir way, + std::ios_base::openmode which) override; + +private: + google::protobuf::io::ZeroCopyOutputStream* _zero_copy_stream; +}; + +} // namespace butil + +#endif // BUTIL_ZERO_COPY_STREAM_AS_STREAMBUF_H diff --git a/src/bvar/bvar.h b/src/bvar/bvar.h new file mode 100644 index 0000000..85e7442 --- /dev/null +++ b/src/bvar/bvar.h @@ -0,0 +1,32 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2014/12/29 14:54:11 + +#ifndef BVAR_BVAR_H +#define BVAR_BVAR_H + +#include "bvar/reducer.h" +#include "bvar/recorder.h" +#include "bvar/status.h" +#include "bvar/passive_status.h" +#include "bvar/latency_recorder.h" +#include "bvar/gflag.h" +#include "bvar/scoped_timer.h" +#include "bvar/mvariable.h" + +#endif //BVAR_BVAR_H diff --git a/src/bvar/collector.cpp b/src/bvar/collector.cpp new file mode 100644 index 0000000..c4adf63 --- /dev/null +++ b/src/bvar/collector.cpp @@ -0,0 +1,442 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Mon Dec 14 19:12:30 CST 2015 + +#include +#include +#include "butil/memory/singleton_on_pthread_once.h" +#include "butil/threading/platform_thread.h" +#include "bvar/bvar.h" +#include "bvar/collector.h" + +namespace bvar { + +// TODO: Do we need to expose this flag? Dumping thread may dump different +// kind of samples, users are unlikely to make good decisions on this value. +DEFINE_int32(bvar_collector_max_pending_samples, 1000, + "Destroy unprocessed samples when they're too many"); + +DEFINE_int32(bvar_collector_expected_per_second, 1000, + "Expected number of samples to be collected per second"); + +// CAUTION: Don't change this value unless you know exactly what it means. +static const int64_t COLLECTOR_GRAB_INTERVAL_US = 100000L; // 100ms + +BAIDU_CASSERT(!(COLLECTOR_SAMPLING_BASE & (COLLECTOR_SAMPLING_BASE - 1)), + must_be_power_of_2); + +// Combine two circular linked list into one. +struct CombineCollected { + void operator()(Collected* & s1, Collected* s2) const { + if (s2 == NULL) { + return; + } + if (s1 == NULL) { + s1 = s2; + return; + } + s1->InsertBeforeAsList(s2); + } +}; + +// A thread and a special bvar to collect samples submitted. +class Collector : public bvar::Reducer { +public: + Collector(); + ~Collector(); + + int64_t last_active_cpuwide_us() const { return _last_active_cpuwide_us; } + + void wakeup_grab_thread(); + +private: + // The thread for collecting TLS submissions. + void grab_thread(); + + // The thread for calling user's callbacks. + void dump_thread(); + + // Adjust speed_limit if grab_thread collected too many in one round. + void update_speed_limit(CollectorSpeedLimit* speed_limit, + size_t* last_ngrab, size_t cur_ngrab, + int64_t interval_us); + + static void* run_grab_thread(void* arg) { + butil::PlatformThread::SetNameSimple("bvar_collector_grabber"); + static_cast(arg)->grab_thread(); + return NULL; + } + + static void* run_dump_thread(void* arg) { + butil::PlatformThread::SetNameSimple("bvar_collector_dumper"); + static_cast(arg)->dump_thread(); + return NULL; + } + + static int64_t get_pending_count(void* arg) { + Collector* d = static_cast(arg); + return d->_ngrab - d->_ndump - d->_ndrop; + } + +private: + // periodically modified by grab_thread, accessed by every submit. + // Make sure that this cacheline does not include frequently modified field. + int64_t _last_active_cpuwide_us; + + bool _created; // Mark validness of _grab_thread. + bool _stop; // Set to true in dtor. + pthread_t _grab_thread; // For joining. + pthread_t _dump_thread; + int64_t _ngrab BAIDU_CACHELINE_ALIGNMENT; + int64_t _ndrop; + int64_t _ndump; + pthread_mutex_t _dump_thread_mutex; + pthread_cond_t _dump_thread_cond; + butil::LinkNode _dump_root; + pthread_mutex_t _sleep_mutex; + pthread_cond_t _sleep_cond; +}; + +Collector::Collector() + : _last_active_cpuwide_us(butil::cpuwide_time_us()) + , _created(false) + , _stop(false) + , _grab_thread(0) + , _dump_thread(0) + , _ngrab(0) + , _ndrop(0) + , _ndump(0) { + pthread_mutex_init(&_dump_thread_mutex, NULL); + pthread_cond_init(&_dump_thread_cond, NULL); + pthread_mutex_init(&_sleep_mutex, NULL); + pthread_cond_init(&_sleep_cond, NULL); + int rc = pthread_create(&_grab_thread, NULL, run_grab_thread, this); + if (rc != 0) { + LOG(ERROR) << "Fail to create Collector, " << berror(rc); + } else { + _created = true; + } +} + +Collector::~Collector() { + if (_created) { + _stop = true; + pthread_join(_grab_thread, NULL); + _created = false; + } + pthread_mutex_destroy(&_dump_thread_mutex); + pthread_cond_destroy(&_dump_thread_cond); + pthread_mutex_destroy(&_sleep_mutex); + pthread_cond_destroy(&_sleep_cond); +} + +template +static T deref_value(void* arg) { + return *(T*)arg; +} + +// for limiting samples returning NULL in speed_limit() +static CollectorSpeedLimit g_null_speed_limit = BVAR_COLLECTOR_SPEED_LIMIT_INITIALIZER; + +void Collector::grab_thread() { + _last_active_cpuwide_us = butil::cpuwide_time_us(); + int64_t last_before_update_sl = _last_active_cpuwide_us; + + // This is the thread for collecting TLS submissions. User's callbacks are + // called inside the separate _dump_thread to prevent a slow callback + // (caused by busy disk generally) from blocking collecting code too long + // that pending requests may explode memory. + CHECK_EQ(0, pthread_create(&_dump_thread, NULL, run_dump_thread, this)); + + // vars + bvar::PassiveStatus pending_sampled_data( + "bvar_collector_pending_samples", get_pending_count, this); + double busy_seconds = 0; + bvar::PassiveStatus busy_seconds_var(deref_value, &busy_seconds); + bvar::PerSecond > busy_seconds_second( + "bvar_collector_grab_thread_usage", &busy_seconds_var); + + bvar::PassiveStatus ngrab_var(deref_value, &_ngrab); + bvar::PerSecond > ngrab_second( + "bvar_collector_grab_second", &ngrab_var); + + // Maps for calculating speed limit. + typedef std::map GrapMap; + GrapMap last_ngrab_map; + GrapMap ngrab_map; + // Map for group samples by preprocessors. + typedef std::map > + PreprocessorMap; + PreprocessorMap prep_map; + + // The main loop. + while (!_stop) { + const int64_t abstime = _last_active_cpuwide_us + COLLECTOR_GRAB_INTERVAL_US; + + // Clear and reuse vectors in prep_map, don't clear prep_map directly. + for (PreprocessorMap::iterator it = prep_map.begin(); it != prep_map.end(); + ++it) { + it->second.clear(); + } + + // Collect TLS submissions and give them to dump_thread. + butil::LinkNode* head = this->reset(); + if (head) { + butil::LinkNode tmp_root; + head->InsertBeforeAsList(&tmp_root); + head = NULL; + + // Group samples by preprocessors. + for (butil::LinkNode* p = tmp_root.next(); p != &tmp_root;) { + butil::LinkNode* saved_next = p->next(); + p->RemoveFromList(); + CollectorPreprocessor* prep = p->value()->preprocessor(); + prep_map[prep].push_back(p->value()); + p = saved_next; + } + // Iterate prep_map + butil::LinkNode root; + for (PreprocessorMap::iterator it = prep_map.begin(); + it != prep_map.end(); ++it) { + std::vector & list = it->second; + if (it->second.empty()) { + // don't call preprocessor when there's no samples. + continue; + } + if (it->first != NULL) { + it->first->process(list); + } + for (size_t i = 0; i < list.size(); ++i) { + Collected* p = list[i]; + CollectorSpeedLimit* speed_limit = p->speed_limit(); + if (speed_limit == NULL) { + ++ngrab_map[&g_null_speed_limit]; + } else { + // Add up the samples of certain type. + ++ngrab_map[speed_limit]; + } + // Drop samples if dump_thread is too busy. + // FIXME: equal probabilities to drop. + ++_ngrab; + if (_ngrab >= _ndrop + _ndump + + FLAGS_bvar_collector_max_pending_samples) { + ++_ndrop; + p->destroy(); + } else { + p->InsertBefore(&root); + } + } + } + // Give the samples to dump_thread + if (root.next() != &root) { // non empty + butil::LinkNode* head2 = root.next(); + root.RemoveFromList(); + BAIDU_SCOPED_LOCK(_dump_thread_mutex); + head2->InsertBeforeAsList(&_dump_root); + pthread_cond_signal(&_dump_thread_cond); + } + } + int64_t now = butil::cpuwide_time_us(); + int64_t interval = now - last_before_update_sl; + last_before_update_sl = now; + for (GrapMap::iterator it = ngrab_map.begin(); + it != ngrab_map.end(); ++it) { + update_speed_limit(it->first, &last_ngrab_map[it->first], + it->second, interval); + } + + now = butil::cpuwide_time_us(); + // calcuate thread usage. + busy_seconds += (now - _last_active_cpuwide_us) / 1000000.0; + _last_active_cpuwide_us = now; + + // sleep for the next round. + if (!_stop && abstime > now) { + timespec abstimespec = butil::microseconds_from_now(abstime - now); + pthread_mutex_lock(&_sleep_mutex); + pthread_cond_timedwait(&_sleep_cond, &_sleep_mutex, &abstimespec); + pthread_mutex_unlock(&_sleep_mutex); + } + _last_active_cpuwide_us = butil::cpuwide_time_us(); + } + // make sure _stop is true, we may have other reasons to quit above loop + { + BAIDU_SCOPED_LOCK(_dump_thread_mutex); + _stop = true; + pthread_cond_signal(&_dump_thread_cond); + } + CHECK_EQ(0, pthread_join(_dump_thread, NULL)); +} + +void Collector::wakeup_grab_thread() { + pthread_mutex_lock(&_sleep_mutex); + pthread_cond_signal(&_sleep_cond); + pthread_mutex_unlock(&_sleep_mutex); +} + +// Adjust speed_limit to match collected samples per second +void Collector::update_speed_limit(CollectorSpeedLimit* sl, + size_t* last_ngrab, size_t cur_ngrab, + int64_t interval_us) { + // FIXME: May become too large at startup. + const size_t round_ngrab = cur_ngrab - *last_ngrab; + if (round_ngrab == 0) { + return; + } + *last_ngrab = cur_ngrab; + if (interval_us < 0) { + interval_us = 0; + } + size_t new_sampling_range = 0; + const size_t old_sampling_range = sl->sampling_range; + if (!sl->ever_grabbed) { + if (sl->first_sample_us) { + interval_us = butil::cpuwide_time_us() - sl->first_sample_us; + if (interval_us < 0) { + interval_us = 0; + } + } else { + // Rare. the timestamp is still not set or visible yet. Just + // use the default interval which may make the calculated + // sampling_range larger. + } + new_sampling_range = FLAGS_bvar_collector_expected_per_second + * interval_us * COLLECTOR_SAMPLING_BASE / (1000000L * round_ngrab); + } else { + // NOTE: the multiplications are unlikely to overflow. + new_sampling_range = FLAGS_bvar_collector_expected_per_second + * interval_us * old_sampling_range / (1000000L * round_ngrab); + // Don't grow or shrink too fast. + if (interval_us < 1000000L) { + new_sampling_range = + (new_sampling_range * interval_us + + old_sampling_range * (1000000L - interval_us)) / 1000000L; + } + } + // Make sure new value is sane. + if (new_sampling_range == 0) { + new_sampling_range = 1; + } else if (new_sampling_range > COLLECTOR_SAMPLING_BASE) { + new_sampling_range = COLLECTOR_SAMPLING_BASE; + } + + // NOTE: don't update unmodified fields in sl to avoid meaningless + // flushing of the cacheline. + if (new_sampling_range != old_sampling_range) { + sl->sampling_range = new_sampling_range; + } + if (!sl->ever_grabbed) { + sl->ever_grabbed = true; + } +} + +size_t is_collectable_before_first_time_grabbed(CollectorSpeedLimit* sl) { + if (!sl->ever_grabbed) { + int before_add = sl->count_before_grabbed.fetch_add( + 1, butil::memory_order_relaxed); + if (before_add == 0) { + sl->first_sample_us = butil::cpuwide_time_us(); + } else if (before_add >= FLAGS_bvar_collector_expected_per_second) { + butil::get_leaky_singleton()->wakeup_grab_thread(); + } + } + return sl->sampling_range; +} + +// Call user's callbacks in this thread. +void Collector::dump_thread() { + int64_t last_ns = butil::cpuwide_time_ns(); + + // vars + double busy_seconds = 0; + bvar::PassiveStatus busy_seconds_var(deref_value, &busy_seconds); + bvar::PerSecond > busy_seconds_second( + "bvar_collector_dump_thread_usage", &busy_seconds_var); + + bvar::PassiveStatus ndumped_var(deref_value, &_ndump); + bvar::PerSecond > ndumped_second( + "bvar::collector_dump_second", &ndumped_var); + + butil::LinkNode root; + size_t round = 0; + + // The main loop + while (!_stop) { + ++round; + // Get new samples set by grab_thread. + butil::LinkNode* newhead = NULL; + { + BAIDU_SCOPED_LOCK(_dump_thread_mutex); + while (!_stop && _dump_root.next() == &_dump_root) { + const int64_t now_ns = butil::cpuwide_time_ns(); + busy_seconds += (now_ns - last_ns) / 1000000000.0; + pthread_cond_wait(&_dump_thread_cond, &_dump_thread_mutex); + last_ns = butil::cpuwide_time_ns(); + } + if (_stop) { + break; + } + newhead = _dump_root.next(); + _dump_root.RemoveFromList(); + } + CHECK(newhead != &_dump_root); + newhead->InsertBeforeAsList(&root); + + // Call callbacks. + for (butil::LinkNode* p = root.next(); !_stop && p != &root;) { + // We remove p from the list, save next first. + butil::LinkNode* saved_next = p->next(); + p->RemoveFromList(); + Collected* s = p->value(); + s->dump_and_destroy(round); + ++_ndump; + p = saved_next; + } + } +} + +// Submit a sample for asynchronous dumping. The Collector holds only the Collected* +// pointer (e.g., SpanContainer*). Regardless of which branch is taken below, the +// sample will eventually be destroyed via either dump_and_destroy() or destroy(), +// both of which call 'delete this' to release the container and decrement the +// reference count of any managed resources (e.g., shared_ptr). +void Collected::submit(int64_t cpuwide_us) { + Collector* d = butil::get_leaky_singleton(); + // Destroy the sample in-place if the grab_thread did not run for twice + // of the normal interval. This also applies to the situation that + // grab_thread aborts due to severe errors. + // Collector::_last_active_cpuwide_us is periodically modified by grab_thread, + // cache bouncing is tolerable. + if (cpuwide_us < d->last_active_cpuwide_us() + COLLECTOR_GRAB_INTERVAL_US * 2) { + *d << this; + } else { + destroy(); + } +} + +static double get_sampling_ratio(void* arg) { + return ((const CollectorSpeedLimit*)arg)->sampling_range / + (double)COLLECTOR_SAMPLING_BASE; +} + +DisplaySamplingRatio::DisplaySamplingRatio(const char* name, + const CollectorSpeedLimit* sl) + : _var(name, get_sampling_ratio, (void*)sl) { +} + +} // namespace bvar diff --git a/src/bvar/collector.h b/src/bvar/collector.h new file mode 100644 index 0000000..473d4ac --- /dev/null +++ b/src/bvar/collector.h @@ -0,0 +1,144 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Mon Dec 14 19:12:30 CST 2015 + +#ifndef BVAR_COLLECTOR_H +#define BVAR_COLLECTOR_H + +#include "butil/containers/linked_list.h" +#include "butil/fast_rand.h" +#include "butil/time.h" +#include "butil/atomicops.h" +#include "bvar/passive_status.h" + +namespace bvar { + +static const size_t INVALID_SAMPLING_RANGE = 0; + +inline bool is_sampling_range_valid(size_t sampling_range) { + return sampling_range > 0; +} + +// Containing the context for limiting sampling speed. +struct CollectorSpeedLimit { + // [Managed by Collector, don't change!] + size_t sampling_range; + bool ever_grabbed; + butil::static_atomic count_before_grabbed; + int64_t first_sample_us; +}; + +static const size_t COLLECTOR_SAMPLING_BASE = 16384; + +#define BVAR_COLLECTOR_SPEED_LIMIT_INITIALIZER \ + { ::bvar::COLLECTOR_SAMPLING_BASE, false, BUTIL_STATIC_ATOMIC_INIT(0), 0 } + +class Collected; + +// For processing samples in batch before dumping. +class CollectorPreprocessor { +public: + virtual ~CollectorPreprocessor() = default; + virtual void process(std::vector& samples) = 0; +}; + +// Steps for sampling and dumping sth: +// 1. Implement Collected +// 2. Create an instance and fill in data. +// 3. submit() the instance. +class Collected : public butil::LinkNode { +public: + virtual ~Collected() {} + + // Sumbit the sample for later dumping, a sample can only be submitted once. + // submit() is implemented as writing a value to bvar::Reducer which does + // not compete globally. This function generally does not alter the + // interleaving status of threads even in highly contended situations. + // You should also create the sample using a malloc() impl. that are + // unlikely to contend, keeping interruptions minimal. + // `cpuwide_us' should be got from butil::cpuwide_time_us(). If it's far + // from the timestamp updated by collecting thread(which basically means + // the thread is not scheduled by OS in time), this sample is directly + // destroy()-ed to avoid memory explosion. + void submit(int64_t cpuwide_us); + void submit() { submit(butil::cpuwide_time_us()); } + + // Implement this method to dump the sample into files and destroy it. + // This method is called in a separate thread and can be blocked + // indefinitely long(not recommended). If too many samples wait for + // this funcion due to previous sample's blocking, they'll be destroy()-ed. + // If you need to run destruction code upon thread's exit, use + // butil::thread_atexit. Dumping thread run this function in batch, each + // batch is counted as one "round", `round_index' is the round that + // dumping thread is currently at, counting from 1. + virtual void dump_and_destroy(size_t round_index) = 0; + + // Destroy the sample. Will be called for at most once. Since dumping + // thread generally quits upon the termination of program, some samples + // are directly recycled along with program w/o calling destroy(). + virtual void destroy() = 0; + + // Returns an object to control #samples collected per second. + // If NULL is returned, samples collected per second is limited by a + // global speed limit shared with other samples also returning NULL. + // All instances of a subclass of Collected should return a same instance + // of CollectorSpeedLimit. The instance should remain valid during lifetime + // of program. + virtual CollectorSpeedLimit* speed_limit() = 0; + + // If this method returns a non-NULL instance, it will be applied to + // samples in batch before dumping. You can sort or shuffle the samples + // in the impl. + // All instances of a subclass of Collected should return a same instance + // of CollectorPreprocessor. The instance should remain valid during + // lifetime of program. + virtual CollectorPreprocessor* preprocessor() { return NULL; } +}; + +// To know if an instance should be sampled. +// Returns a positive number when the object should be sampled, 0 otherwise. +// The number is approximately the current probability of sampling times +// COLLECTOR_SAMPLING_BASE, it varies from seconds to seconds being adjusted +// by collecting thread to control the samples collected per second. +// This function should cost less than 10ns in most cases. +inline size_t is_collectable(CollectorSpeedLimit* speed_limit) { + if (speed_limit->ever_grabbed) { // most common case + const size_t sampling_range = speed_limit->sampling_range; + // fast_rand is faster than fast_rand_in + if ((butil::fast_rand() & (COLLECTOR_SAMPLING_BASE - 1)) >= sampling_range) { + return INVALID_SAMPLING_RANGE; + } + return sampling_range; + } + // Slower, only runs before -bvar_collector_expected_per_second samples are + // collected to calculate a more reasonable sampling_range for the type. + extern size_t is_collectable_before_first_time_grabbed(CollectorSpeedLimit*); + return is_collectable_before_first_time_grabbed(speed_limit); +} + +// An utility for displaying current sampling ratio according to speed limit. +class DisplaySamplingRatio { +public: + DisplaySamplingRatio(const char* name, const CollectorSpeedLimit*); +private: + bvar::PassiveStatus _var; +}; + +} // namespace bvar + +#endif // BVAR_COLLECTOR_H diff --git a/src/bvar/default_variables.cpp b/src/bvar/default_variables.cpp new file mode 100644 index 0000000..40d30c5 --- /dev/null +++ b/src/bvar/default_variables.cpp @@ -0,0 +1,891 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Thu Jul 30 17:44:54 CST 2015 + +#include // getpagesize +#include +#include // getrusage +#include // dirent +#include // setw +#include +#include +#if defined(__APPLE__) +#include +#include +#else +#endif + +#include "butil/time.h" +#include "butil/memory/singleton_on_pthread_once.h" +#include "butil/scoped_lock.h" +#include "butil/files/scoped_file.h" +#include "butil/files/dir_reader_posix.h" +#include "butil/file_util.h" +#include "butil/process_util.h" // ReadCommandLine +#include "butil/popen.h" // read_command_output +#include "bvar/passive_status.h" + +namespace bvar { + +template M get_member_type(M T::*); + +#define BVAR_MEMBER_TYPE(member) BAIDU_TYPEOF(bvar::get_member_type(member)) + +int do_link_default_variables = 0; +const int64_t CACHED_INTERVAL_US = 100000L; // 100ms + +// ====================================== +struct ProcStat { + int pid; + //std::string comm; + char state; + int ppid; + int pgrp; + int session; + int tty_nr; + int tpgid; + unsigned flags; + unsigned long minflt; + unsigned long cminflt; + unsigned long majflt; + unsigned long cmajflt; + unsigned long utime; + unsigned long stime; + unsigned long cutime; + unsigned long cstime; + long priority; + long nice; + long num_threads; +}; + +static bool read_proc_status(ProcStat &stat) { + stat = ProcStat(); + errno = 0; +#if defined(OS_LINUX) + // Read status from /proc/self/stat. Information from `man proc' is out of date, + // see http://man7.org/linux/man-pages/man5/proc.5.html + butil::ScopedFILE fp("/proc/self/stat", "r"); + if (NULL == fp) { + static bool ever_printed_stat_err = false; + if (!ever_printed_stat_err) { + fprintf(stderr, "WARNING: Fail to open /proc/self/stat, errno=%d. " + "Process status related bvars will be unavailable.\n", errno); + ever_printed_stat_err = true; + } + return false; + } + if (fscanf(fp, "%d %*s %c " + "%d %d %d %d %d " + "%u %lu %lu %lu " + "%lu %lu %lu %lu %lu " + "%ld %ld %ld", + &stat.pid, &stat.state, + &stat.ppid, &stat.pgrp, &stat.session, &stat.tty_nr, &stat.tpgid, + &stat.flags, &stat.minflt, &stat.cminflt, &stat.majflt, + &stat.cmajflt, &stat.utime, &stat.stime, &stat.cutime, &stat.cstime, + &stat.priority, &stat.nice, &stat.num_threads) != 19) { + fprintf(stderr, "WARNING: Fail to fscanf /proc/self/stat, errno=%d. " + "Process status related bvars will be unavailable.\n", errno); + return false; + } + return true; +#elif defined(OS_MACOSX) + // TODO(zhujiashun): get remaining state in MacOS. + memset(&stat, 0, sizeof(stat)); + static pid_t pid = getpid(); + std::ostringstream oss; + char cmdbuf[128]; + snprintf(cmdbuf, sizeof(cmdbuf), + "ps -p %ld -o pid,ppid,pgid,sess" + ",tpgid,flags,pri,nice | tail -n1", (long)pid); + if (butil::read_command_output(oss, cmdbuf) != 0) { + LOG(ERROR) << "Fail to read stat"; + return false; + } + const std::string& result = oss.str(); + if (sscanf(result.c_str(), "%d %d %d %d" + "%d %u %ld %ld", + &stat.pid, &stat.ppid, &stat.pgrp, &stat.session, + &stat.tpgid, &stat.flags, &stat.priority, &stat.nice) != 8) { + PLOG(WARNING) << "Fail to sscanf"; + return false; + } + return true; +#else + return false; +#endif +} + +// Reduce pressures to functions to get system metrics. +template +class CachedReader { +public: + CachedReader() : _mtime_us(0), _cached{} { + CHECK_EQ(0, pthread_mutex_init(&_mutex, NULL)); + } + ~CachedReader() { + pthread_mutex_destroy(&_mutex); + } + + // NOTE: may return a volatile value that may be overwritten at any time. + // This is acceptable right now. Both 32-bit and 64-bit numbers are atomic + // to fetch in 64-bit machines(most of baidu machines) and the code inside + // this .cpp utilizing this class generally return a struct with 32-bit + // and 64-bit numbers. + template + static const T& get_value(const ReadFn& fn) { + CachedReader* p = butil::get_leaky_singleton(); + const int64_t now = butil::cpuwide_time_us(); + if (now > p->_mtime_us + CACHED_INTERVAL_US) { + pthread_mutex_lock(&p->_mutex); + if (now > p->_mtime_us + CACHED_INTERVAL_US) { + p->_mtime_us = now; + pthread_mutex_unlock(&p->_mutex); + // don't run fn inside lock otherwise a slow fn may + // block all concurrent bvar dumppers. (e.g. /vars) + T result{}; + if (fn(&result)) { + pthread_mutex_lock(&p->_mutex); + p->_cached = result; + } else { + pthread_mutex_lock(&p->_mutex); + } + } + pthread_mutex_unlock(&p->_mutex); + } + return p->_cached; + } + +private: + int64_t _mtime_us; + pthread_mutex_t _mutex; + T _cached; +}; + +class ProcStatReader { +public: + bool operator()(ProcStat* stat) const { + return read_proc_status(*stat); + } + template + static T get_field(void*) { + return *(T*)((char*)&CachedReader::get_value( + ProcStatReader()) + offset); + } +}; + +#define BVAR_DEFINE_PROC_STAT_FIELD(field) \ + PassiveStatus g_##field( \ + ProcStatReader::get_field, NULL); + +#define BVAR_DEFINE_PROC_STAT_FIELD2(field, name) \ + PassiveStatus g_##field( \ + name, \ + ProcStatReader::get_field, NULL); + +// ================================================== + +struct ProcMemory { + long size; // total program size + long resident; // resident set size + long share; // shared pages + long trs; // text (code) + long lrs; // library + long drs; // data/stack + long dt; // dirty pages +}; + +static bool read_proc_memory(ProcMemory &m) { + m = ProcMemory(); + errno = 0; +#if defined(OS_LINUX) + butil::ScopedFILE fp("/proc/self/statm", "r"); + if (NULL == fp) { + PLOG_ONCE(WARNING) << "Fail to open /proc/self/statm"; + return false; + } + if (fscanf(fp, "%ld %ld %ld %ld %ld %ld %ld", + &m.size, &m.resident, &m.share, + &m.trs, &m.lrs, &m.drs, &m.dt) != 7) { + PLOG(WARNING) << "Fail to fscanf /proc/self/statm"; + return false; + } + return true; +#elif defined(OS_MACOSX) + // TODO(zhujiashun): get remaining memory info in MacOS. + memset(&m, 0, sizeof(m)); + static pid_t pid = getpid(); + static int64_t pagesize = getpagesize(); + std::ostringstream oss; + char cmdbuf[128]; + snprintf(cmdbuf, sizeof(cmdbuf), "ps -p %ld -o rss=,vsz=", (long)pid); + if (butil::read_command_output(oss, cmdbuf) != 0) { + LOG(ERROR) << "Fail to read memory state"; + return false; + } + const std::string& result = oss.str(); + if (sscanf(result.c_str(), "%ld %ld", &m.resident, &m.size) != 2) { + PLOG(WARNING) << "Fail to sscanf"; + return false; + } + // resident and size in Kbytes + m.resident = m.resident * 1024 / pagesize; + m.size = m.size * 1024 / pagesize; + return true; +#else + return false; +#endif +} + +class ProcMemoryReader { +public: + bool operator()(ProcMemory* stat) const { + return read_proc_memory(*stat); + }; + template + static T get_field(void*) { + static int64_t pagesize = getpagesize(); + return *(T*)((char*)&CachedReader::get_value( + ProcMemoryReader()) + offset) * pagesize; + } +}; + +#define BVAR_DEFINE_PROC_MEMORY_FIELD(field, name) \ + PassiveStatus g_##field( \ + name, \ + ProcMemoryReader::get_field, NULL); + +// ================================================== + +struct LoadAverage { + double loadavg_1m; + double loadavg_5m; + double loadavg_15m; +}; + +static bool read_load_average(LoadAverage &m) { +#if defined(OS_LINUX) + butil::ScopedFILE fp("/proc/loadavg", "r"); + if (NULL == fp) { + PLOG_ONCE(WARNING) << "Fail to open /proc/loadavg"; + return false; + } + m = LoadAverage(); + errno = 0; + if (fscanf(fp, "%lf %lf %lf", + &m.loadavg_1m, &m.loadavg_5m, &m.loadavg_15m) != 3) { + PLOG(WARNING) << "Fail to fscanf"; + return false; + } + return true; +#elif defined(OS_MACOSX) + std::ostringstream oss; + if (butil::read_command_output(oss, "sysctl -n vm.loadavg") != 0) { + LOG(ERROR) << "Fail to read loadavg"; + return false; + } + const std::string& result = oss.str(); + if (sscanf(result.c_str(), "{ %lf %lf %lf }", + &m.loadavg_1m, &m.loadavg_5m, &m.loadavg_15m) != 3) { + PLOG(WARNING) << "Fail to sscanf"; + return false; + } + return true; + +#else + return false; +#endif +} + +class LoadAverageReader { +public: + bool operator()(LoadAverage* stat) const { + return read_load_average(*stat); + }; + template + static T get_field(void*) { + return *(T*)((char*)&CachedReader::get_value( + LoadAverageReader()) + offset); + } +}; + +#define BVAR_DEFINE_LOAD_AVERAGE_FIELD(field, name) \ + PassiveStatus g_##field( \ + name, \ + LoadAverageReader::get_field, NULL); + +// ================================================== + +static int get_fd_count(int limit) { +#if defined(OS_LINUX) + butil::DirReaderPosix dr("/proc/self/fd"); + int count = 0; + if (!dr.IsValid()) { + PLOG(WARNING) << "Fail to open /proc/self/fd"; + return -1; + } + // Have to limit the scaning which consumes a lot of CPU when #fd + // are huge (100k+) + for (; dr.Next() && count <= limit + 3; ++count) {} + return count - 3 /* skipped ., .. and the fd in dr*/; +#elif defined(OS_MACOSX) + // TODO(zhujiashun): following code will cause core dump with some + // probability under mac when program exits. Fix it. + /* + static pid_t pid = getpid(); + std::ostringstream oss; + char cmdbuf[128]; + snprintf(cmdbuf, sizeof(cmdbuf), + "lsof -p %ld | grep -v \"txt\" | wc -l", (long)pid); + if (butil::read_command_output(oss, cmdbuf) != 0) { + LOG(ERROR) << "Fail to read open files"; + return -1; + } + const std::string& result = oss.str(); + int count = 0; + if (sscanf(result.c_str(), "%d", &count) != 1) { + PLOG(WARNING) << "Fail to sscanf"; + return -1; + } + // skipped . and first column line + count = count - 2; + return std::min(count, limit); + */ + return 0; +#else + return 0; +#endif +} + +extern PassiveStatus g_fd_num; + +const int MAX_FD_SCAN_COUNT = 10003; +static butil::static_atomic s_ever_reached_fd_scan_limit = BUTIL_STATIC_ATOMIC_INIT(false); +class FdReader { +public: + bool operator()(int* stat) const { + if (s_ever_reached_fd_scan_limit.load(butil::memory_order_relaxed)) { + // Never update the count again. + return false; + } + const int count = get_fd_count(MAX_FD_SCAN_COUNT); + if (count < 0) { + return false; + } + if (count == MAX_FD_SCAN_COUNT - 2 + && s_ever_reached_fd_scan_limit.exchange( + true, butil::memory_order_relaxed) == false) { + // Rename the bvar to notify user. + g_fd_num.hide(); + g_fd_num.expose("process_fd_num_too_many"); + } + *stat = count; + return true; + } +}; + +static int print_fd_count(void*) { + return CachedReader::get_value(FdReader()); +} + +// ================================================== +struct ProcIO { + // number of bytes the process read, using any read-like system call (from + // files, pipes, tty...). + size_t rchar; + + // number of bytes the process wrote using any write-like system call. + size_t wchar; + + // number of read-like system call invocations that the process performed. + size_t syscr; + + // number of write-like system call invocations that the process performed. + size_t syscw; + + // number of bytes the process directly read from disk. + size_t read_bytes; + + // number of bytes the process originally dirtied in the page-cache + // (assuming they will go to disk later). + size_t write_bytes; + + // number of bytes the process "un-dirtied" - e.g. using an "ftruncate" + // call that truncated pages from the page-cache. + size_t cancelled_write_bytes; +}; + +static bool read_proc_io(ProcIO* s) { +#if defined(OS_LINUX) + butil::ScopedFILE fp("/proc/self/io", "r"); + if (NULL == fp) { + static bool ever_printed_io_err = false; + if (!ever_printed_io_err) { + fprintf(stderr, "WARNING: Fail to open /proc/self/io, errno=%d. " + "I/O related bvars will be unavailable.\n", errno); + ever_printed_io_err = true; + } + return false; + } + errno = 0; + if (fscanf(fp, "%*s %lu %*s %lu %*s %lu %*s %lu %*s %lu %*s %lu %*s %lu", + &s->rchar, &s->wchar, &s->syscr, &s->syscw, + &s->read_bytes, &s->write_bytes, &s->cancelled_write_bytes) + != 7) { + PLOG(WARNING) << "Fail to fscanf"; + return false; + } + return true; +#elif defined(OS_MACOSX) + // TODO(zhujiashun): get rchar, wchar, syscr, syscw, cancelled_write_bytes + // in MacOS. + memset(s, 0, sizeof(ProcIO)); + static pid_t pid = getpid(); + rusage_info_current rusage; + if (proc_pid_rusage(pid, RUSAGE_INFO_CURRENT, (void**)&rusage) != 0) { + PLOG(WARNING) << "Fail to proc_pid_rusage"; + return false; + } + s->read_bytes = rusage.ri_diskio_bytesread; + s->write_bytes = rusage.ri_diskio_byteswritten; + return true; +#else + return false; +#endif +} + +class ProcIOReader { +public: + bool operator()(ProcIO* stat) const { + return read_proc_io(stat); + } + template + static T get_field(void*) { + return *(T*)((char*)&CachedReader::get_value( + ProcIOReader()) + offset); + } +}; + +#define BVAR_DEFINE_PROC_IO_FIELD(field) \ + PassiveStatus g_##field( \ + ProcIOReader::get_field, NULL); + +// ================================================== +// Refs: +// https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats +// https://www.kernel.org/doc/Documentation/iostats.txt +// +// The /proc/diskstats file displays the I/O statistics of block devices. +// Each line contains the following 14 fields: +struct DiskStat { + long long major_number; + long long minor_mumber; + char device_name[64]; + + // The total number of reads completed successfully. + long long reads_completed; // wMB/s wKB/s + + // Reads and writes which are adjacent to each other may be merged for + // efficiency. Thus two 4K reads may become one 8K read before it is + // ultimately handed to the disk, and so it will be counted (and queued) + // as only one I/O. This field lets you know how often this was done. + long long reads_merged; // rrqm/s + + // The total number of sectors read successfully. + long long sectors_read; // rsec/s + + // The total number of milliseconds spent by all reads (as + // measured from __make_request() to end_that_request_last()). + long long time_spent_reading_ms; + + // The total number of writes completed successfully. + long long writes_completed; // rKB/s rMB/s + + // See description of reads_merged + long long writes_merged; // wrqm/s + + // The total number of sectors written successfully. + long long sectors_written; // wsec/s + + // The total number of milliseconds spent by all writes (as + // measured from __make_request() to end_that_request_last()). + long long time_spent_writing_ms; + + // The only field that should go to zero. Incremented as requests are + // given to appropriate struct request_queue and decremented as they finish. + long long io_in_progress; + + // This field increases so long as `io_in_progress' is nonzero. + long long time_spent_io_ms; + + // This field is incremented at each I/O start, I/O completion, I/O + // merge, or read of these stats by the number of I/Os in progress + // `io_in_progress' times the number of milliseconds spent doing + // I/O since the last update of this field. This can provide an easy + // measure of both I/O completion time and the backlog that may be + // accumulating. + long long weighted_time_spent_io_ms; +}; + +static bool read_disk_stat(DiskStat* s) { +#if defined(OS_LINUX) + butil::ScopedFILE fp("/proc/diskstats", "r"); + if (NULL == fp) { + PLOG_ONCE(WARNING) << "Fail to open /proc/diskstats"; + return false; + } + errno = 0; + if (fscanf(fp, "%lld %lld %s %lld %lld %lld %lld %lld %lld %lld " + "%lld %lld %lld %lld", + &s->major_number, + &s->minor_mumber, + s->device_name, + &s->reads_completed, + &s->reads_merged, + &s->sectors_read, + &s->time_spent_reading_ms, + &s->writes_completed, + &s->writes_merged, + &s->sectors_written, + &s->time_spent_writing_ms, + &s->io_in_progress, + &s->time_spent_io_ms, + &s->weighted_time_spent_io_ms) != 14) { + PLOG(WARNING) << "Fail to fscanf"; + return false; + } + return true; +#elif defined(OS_MACOSX) + // TODO(zhujiashun) + return false; +#else + return false; +#endif +} + +class DiskStatReader { +public: + bool operator()(DiskStat* stat) const { + return read_disk_stat(stat); + } + template + static T get_field(void*) { + return *(T*)((char*)&CachedReader::get_value( + DiskStatReader()) + offset); + } +}; + +#define BVAR_DEFINE_DISK_STAT_FIELD(field) \ + PassiveStatus g_##field( \ + DiskStatReader::get_field, NULL); + +// ===================================== + +struct ReadSelfCmdline { + std::string content; + ReadSelfCmdline() { + char buf[1024]; + const ssize_t nr = butil::ReadCommandLine(buf, sizeof(buf), true); + content.append(buf, nr); + } +}; +static void get_cmdline(std::ostream& os, void*) { + os << butil::get_leaky_singleton()->content; +} + +struct ReadVersion { + std::string content; + ReadVersion() { + std::ostringstream oss; + if (butil::read_command_output(oss, "uname -ap") != 0) { + LOG(ERROR) << "Fail to read kernel version"; + return; + } + content.append(oss.str()); + } +}; +static void get_kernel_version(std::ostream& os, void*) { + os << butil::get_leaky_singleton()->content; +} + +// ====================================== + +static int64_t g_starting_time = butil::cpuwide_time_us(); + +static timeval get_uptime(void*) { + int64_t uptime_us = butil::cpuwide_time_us() - g_starting_time; + timeval tm; + tm.tv_sec = uptime_us / 1000000L; + tm.tv_usec = uptime_us - tm.tv_sec * 1000000L; + return tm; +} + +// ====================================== + +class RUsageReader { +public: + bool operator()(rusage* stat) const { + const int rc = getrusage(RUSAGE_SELF, stat); + if (rc < 0) { + PLOG(WARNING) << "Fail to getrusage"; + return false; + } + return true; + } + template + static T get_field(void*) { + return *(T*)((char*)&CachedReader::get_value( + RUsageReader()) + offset); + } +}; + +#define BVAR_DEFINE_RUSAGE_FIELD(field) \ + PassiveStatus g_##field( \ + RUsageReader::get_field, NULL); \ + +#define BVAR_DEFINE_RUSAGE_FIELD2(field, name) \ + PassiveStatus g_##field( \ + name, \ + RUsageReader::get_field, NULL); \ + +// ====================================== + +BVAR_DEFINE_PROC_STAT_FIELD2(pid, "pid"); +BVAR_DEFINE_PROC_STAT_FIELD2(ppid, "ppid"); +BVAR_DEFINE_PROC_STAT_FIELD2(pgrp, "pgrp"); + +static void get_username(std::ostream& os, void*) { + char buf[32]; + if (getlogin_r(buf, sizeof(buf)) == 0) { + buf[sizeof(buf)-1] = '\0'; + os << buf; + } else { + os << "unknown (" << berror() << ')' ; + } +} + +PassiveStatus g_username( + "process_username", get_username, NULL); + +BVAR_DEFINE_PROC_STAT_FIELD(minflt); +PerSecond > g_minflt_second( + "process_faults_minor_second", &g_minflt); +BVAR_DEFINE_PROC_STAT_FIELD2(majflt, "process_faults_major"); + +BVAR_DEFINE_PROC_STAT_FIELD2(priority, "process_priority"); +BVAR_DEFINE_PROC_STAT_FIELD2(nice, "process_nice"); + +BVAR_DEFINE_PROC_STAT_FIELD2(num_threads, "process_thread_count"); +PassiveStatus g_fd_num("process_fd_count", print_fd_count, NULL); + +BVAR_DEFINE_PROC_MEMORY_FIELD(size, "process_memory_virtual"); +BVAR_DEFINE_PROC_MEMORY_FIELD(resident, "process_memory_resident"); +BVAR_DEFINE_PROC_MEMORY_FIELD(share, "process_memory_shared"); +BVAR_DEFINE_PROC_MEMORY_FIELD(trs, "process_memory_text"); +BVAR_DEFINE_PROC_MEMORY_FIELD(drs, "process_memory_data_and_stack"); + +BVAR_DEFINE_LOAD_AVERAGE_FIELD(loadavg_1m, "system_loadavg_1m"); +BVAR_DEFINE_LOAD_AVERAGE_FIELD(loadavg_5m, "system_loadavg_5m"); +BVAR_DEFINE_LOAD_AVERAGE_FIELD(loadavg_15m, "system_loadavg_15m"); + +BVAR_DEFINE_PROC_IO_FIELD(rchar); +BVAR_DEFINE_PROC_IO_FIELD(wchar); +PerSecond > g_io_read_second( + "process_io_read_bytes_second", &g_rchar); +PerSecond > g_io_write_second( + "process_io_write_bytes_second", &g_wchar); + +BVAR_DEFINE_PROC_IO_FIELD(syscr); +BVAR_DEFINE_PROC_IO_FIELD(syscw); +PerSecond > g_io_num_reads_second( + "process_io_read_second", &g_syscr); +PerSecond > g_io_num_writes_second( + "process_io_write_second", &g_syscw); + +BVAR_DEFINE_PROC_IO_FIELD(read_bytes); +BVAR_DEFINE_PROC_IO_FIELD(write_bytes); +PerSecond > g_disk_read_second( + "process_disk_read_bytes_second", &g_read_bytes); +PerSecond > g_disk_write_second( + "process_disk_write_bytes_second", &g_write_bytes); + +BVAR_DEFINE_RUSAGE_FIELD(ru_utime); +BVAR_DEFINE_RUSAGE_FIELD(ru_stime); +PassiveStatus g_uptime("process_uptime", get_uptime, NULL); + +static int get_core_num(void*) { + return sysconf(_SC_NPROCESSORS_ONLN); +} +PassiveStatus g_core_num("system_core_count", get_core_num, NULL); + +struct TimePercent { + int64_t time_us; + int64_t real_time_us; + + void operator-=(const TimePercent& rhs) { + time_us -= rhs.time_us; + real_time_us -= rhs.real_time_us; + } + void operator+=(const TimePercent& rhs) { + time_us += rhs.time_us; + real_time_us += rhs.real_time_us; + } +}; +inline std::ostream& operator<<(std::ostream& os, const TimePercent& tp) { + if (tp.real_time_us <= 0) { + return os << "0"; + } else { + return os << std::fixed << std::setprecision(3) + << (double)tp.time_us / tp.real_time_us; + } +} + +static TimePercent get_cputime_percent(void*) { + TimePercent tp = { butil::timeval_to_microseconds(g_ru_stime.get_value()) + + butil::timeval_to_microseconds(g_ru_utime.get_value()), + butil::timeval_to_microseconds(g_uptime.get_value()) }; + return tp; +} +PassiveStatus g_cputime_percent(get_cputime_percent, NULL); +Window, SERIES_IN_SECOND> g_cputime_percent_second( + "process_cpu_usage", &g_cputime_percent, FLAGS_bvar_dump_interval); + +static TimePercent get_stime_percent(void*) { + TimePercent tp = { butil::timeval_to_microseconds(g_ru_stime.get_value()), + butil::timeval_to_microseconds(g_uptime.get_value()) }; + return tp; +} +PassiveStatus g_stime_percent(get_stime_percent, NULL); +Window, SERIES_IN_SECOND> g_stime_percent_second( + "process_cpu_usage_system", &g_stime_percent, FLAGS_bvar_dump_interval); + +static TimePercent get_utime_percent(void*) { + TimePercent tp = { butil::timeval_to_microseconds(g_ru_utime.get_value()), + butil::timeval_to_microseconds(g_uptime.get_value()) }; + return tp; +} +PassiveStatus g_utime_percent(get_utime_percent, NULL); +Window, SERIES_IN_SECOND> g_utime_percent_second( + "process_cpu_usage_user", &g_utime_percent, FLAGS_bvar_dump_interval); + +// According to http://man7.org/linux/man-pages/man2/getrusage.2.html +// Unsupported fields in linux: +// ru_ixrss +// ru_idrss +// ru_isrss +// ru_nswap +// ru_nsignals +BVAR_DEFINE_RUSAGE_FIELD(ru_inblock); +BVAR_DEFINE_RUSAGE_FIELD(ru_oublock); +BVAR_DEFINE_RUSAGE_FIELD(ru_nvcsw); +BVAR_DEFINE_RUSAGE_FIELD(ru_nivcsw); +PerSecond > g_ru_inblock_second( + "process_inblocks_second", &g_ru_inblock); +PerSecond > g_ru_oublock_second( + "process_outblocks_second", &g_ru_oublock); +PerSecond > cs_vol_second( + "process_context_switches_voluntary_second", &g_ru_nvcsw); +PerSecond > cs_invol_second( + "process_context_switches_involuntary_second", &g_ru_nivcsw); + +PassiveStatus g_cmdline("process_cmdline", get_cmdline, NULL); +PassiveStatus g_kernel_version( + "kernel_version", get_kernel_version, NULL); + +static std::string* s_gcc_version = NULL; +pthread_once_t g_gen_gcc_version_once = PTHREAD_ONCE_INIT; + +void gen_gcc_version() { + +#if defined(__GNUC__) + const int gcc_major = __GNUC__; +#else + const int gcc_major = -1; +#endif + +#if defined(__GNUC_MINOR__) + const int gcc_minor = __GNUC_MINOR__; +#else + const int gcc_minor = -1; +#endif + +#if defined(__GNUC_PATCHLEVEL__) + const int gcc_patchlevel = __GNUC_PATCHLEVEL__; +#else + const int gcc_patchlevel = -1; +#endif + + s_gcc_version = new std::string; + + if (gcc_major == -1) { + *s_gcc_version = "unknown"; + return; + } + std::ostringstream oss; + oss << gcc_major; + if (gcc_minor == -1) { + return; + } + oss << '.' << gcc_minor; + + if (gcc_patchlevel == -1) { + return; + } + oss << '.' << gcc_patchlevel; + + *s_gcc_version = oss.str(); + +} + +void get_gcc_version(std::ostream& os, void*) { + pthread_once(&g_gen_gcc_version_once, gen_gcc_version); + os << *s_gcc_version; +} + +// ============================================= +PassiveStatus g_gcc_version("gcc_version", get_gcc_version, NULL); + +void get_work_dir(std::ostream& os, void*) { + butil::FilePath path; + const bool rc = butil::GetCurrentDirectory(&path); + LOG_IF(WARNING, !rc) << "Fail to GetCurrentDirectory"; + os << path.value(); +} +PassiveStatus g_work_dir("process_work_dir", get_work_dir, NULL); + +#undef BVAR_MEMBER_TYPE +#undef BVAR_DEFINE_PROC_STAT_FIELD +#undef BVAR_DEFINE_PROC_STAT_FIELD2 +#undef BVAR_DEFINE_PROC_MEMORY_FIELD +#undef BVAR_DEFINE_RUSAGE_FIELD +#undef BVAR_DEFINE_RUSAGE_FIELD2 + +} // namespace bvar + +// In the same scope where timeval is defined. Required by clang. +inline std::ostream& operator<<(std::ostream& os, const timeval& tm) { + return os << tm.tv_sec << '.' << std::setw(6) << std::setfill('0') << tm.tv_usec; +} diff --git a/src/bvar/detail/agent_group.h b/src/bvar/detail/agent_group.h new file mode 100644 index 0000000..db327f2 --- /dev/null +++ b/src/bvar/detail/agent_group.h @@ -0,0 +1,202 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date 2014/09/24 19:34:24 + +#ifndef BVAR_DETAIL__AGENT_GROUP_H +#define BVAR_DETAIL__AGENT_GROUP_H + +#include // pthread_mutex_* +#include // abort + +#include // std::nothrow +#include // std::deque +#include // std::vector + +#include "butil/errno.h" // errno +#include "butil/thread_local.h" // thread_atexit +#include "butil/macros.h" // BAIDU_CACHELINE_ALIGNMENT +#include "butil/scoped_lock.h" +#include "butil/logging.h" + +namespace bvar { +namespace detail { + +typedef int AgentId; + +// General NOTES: +// * Don't use bound-checking vector::at. +// * static functions in template class are not guaranteed to be inlined, +// add inline keyword explicitly. +// * Put fast path in "if" branch, which is more cpu-wise. +// * don't use __builtin_expect excessively because CPU may predict the branch +// better than you. Only hint branches that are definitely unusual. + +template +class AgentGroup { +public: + typedef Agent agent_type; + + // TODO: We should remove the template parameter and unify AgentGroup + // of all bvar with a same one, to reuse the memory between different + // type of bvar. The unified AgentGroup allocates small structs in-place + // and large structs on heap, thus keeping batch efficiencies on small + // structs and improving memory usage on large structs. + const static size_t RAW_BLOCK_SIZE = 4096; + const static size_t ELEMENTS_PER_BLOCK = + (RAW_BLOCK_SIZE + sizeof(Agent) - 1) / sizeof(Agent); + + // The most generic method to allocate agents is to call ctor when + // agent is needed, however we construct all agents when initializing + // ThreadBlock, which has side effects: + // * calling ctor ELEMENTS_PER_BLOCK times is slower. + // * calling ctor of non-pod types may be unpredictably slow. + // * non-pod types may allocate space inside ctor excessively. + // * may return non-null for unexist id. + // * lifetime of agent is more complex. User has to reset the agent before + // destroying id otherwise when the agent is (implicitly) reused by + // another one who gets the reused id, things are screwed. + // TODO(chenzhangyi01): To fix these problems, a method is to keep a bitmap + // along with ThreadBlock* in _s_tls_blocks, each bit in the bitmap + // represents that the agent is constructed or not. Drawback of this method + // is that the bitmap may take 32bytes (for 256 agents, which is common) so + // that addressing on _s_tls_blocks may be slower if identifiers distribute + // sparsely. Another method is to put the bitmap in ThreadBlock. But this + // makes alignment of ThreadBlock harder and to address the agent we have + // to touch an additional cacheline: the bitmap. Whereas in the first + // method, bitmap and ThreadBlock* are in one cacheline. + struct BAIDU_CACHELINE_ALIGNMENT ThreadBlock { + inline Agent* at(size_t offset) { return _agents + offset; }; + + private: + Agent _agents[ELEMENTS_PER_BLOCK]; + }; + + inline static AgentId create_new_agent() { + BAIDU_SCOPED_LOCK(_s_mutex); + AgentId agent_id = 0; + if (!_get_free_ids().empty()) { + agent_id = _get_free_ids().back(); + _get_free_ids().pop_back(); + } else { + agent_id = _s_agent_kinds++; + } + return agent_id; + } + + inline static int destroy_agent(AgentId id) { + // TODO: How to avoid double free? + BAIDU_SCOPED_LOCK(_s_mutex); + if (id < 0 || id >= _s_agent_kinds) { + errno = EINVAL; + return -1; + } + _get_free_ids().push_back(id); + return 0; + } + + // Note: May return non-null for unexist id, see notes on ThreadBlock + // We need this function to be as fast as possible. + inline static Agent* get_tls_agent(AgentId id) { + if (__builtin_expect(id >= 0, 1)) { + if (_s_tls_blocks) { + const size_t block_id = (size_t)id / ELEMENTS_PER_BLOCK; + if (block_id < _s_tls_blocks->size()) { + ThreadBlock* const tb = (*_s_tls_blocks)[block_id]; + if (tb) { + return tb->at(id - block_id * ELEMENTS_PER_BLOCK); + } + } + } + } + return NULL; + } + + // Note: May return non-null for unexist id, see notes on ThreadBlock + inline static Agent* get_or_create_tls_agent(AgentId id) { + if (__builtin_expect(id < 0, 0)) { + CHECK(false) << "Invalid id=" << id; + return NULL; + } + if (_s_tls_blocks == NULL) { + _s_tls_blocks = new (std::nothrow) std::vector; + if (__builtin_expect(_s_tls_blocks == NULL, 0)) { + LOG(FATAL) << "Fail to create vector, " << berror(); + return NULL; + } + butil::thread_atexit(_destroy_tls_blocks); + } + const size_t block_id = (size_t)id / ELEMENTS_PER_BLOCK; + if (block_id >= _s_tls_blocks->size()) { + // The 32ul avoid pointless small resizes. + _s_tls_blocks->resize(std::max(block_id + 1, 32ul)); + } + ThreadBlock* tb = (*_s_tls_blocks)[block_id]; + if (tb == NULL) { + ThreadBlock *new_block = new (std::nothrow) ThreadBlock; + if (__builtin_expect(new_block == NULL, 0)) { + return NULL; + } + tb = new_block; + (*_s_tls_blocks)[block_id] = new_block; + } + return tb->at(id - block_id * ELEMENTS_PER_BLOCK); + } + +private: + static void _destroy_tls_blocks() { + if (!_s_tls_blocks) { + return; + } + for (size_t i = 0; i < _s_tls_blocks->size(); ++i) { + delete (*_s_tls_blocks)[i]; + } + delete _s_tls_blocks; + _s_tls_blocks = NULL; + } + + inline static std::deque &_get_free_ids() { + if (__builtin_expect(!_s_free_ids, 0)) { + _s_free_ids = new (std::nothrow) std::deque(); + RELEASE_ASSERT(_s_free_ids); + } + return *_s_free_ids; + } + + static pthread_mutex_t _s_mutex; + static AgentId _s_agent_kinds; + static std::deque *_s_free_ids; + static __thread std::vector *_s_tls_blocks; +}; + +template +pthread_mutex_t AgentGroup::_s_mutex = PTHREAD_MUTEX_INITIALIZER; + +template +std::deque* AgentGroup::_s_free_ids = NULL; + +template +AgentId AgentGroup::_s_agent_kinds = 0; + +template +__thread std::vector::ThreadBlock *> +*AgentGroup::_s_tls_blocks = NULL; + +} // namespace detail +} // namespace bvar + +#endif //BVAR_DETAIL__AGENT_GROUP_H diff --git a/src/bvar/detail/call_op_returning_void.h b/src/bvar/detail/call_op_returning_void.h new file mode 100644 index 0000000..e0b95bc --- /dev/null +++ b/src/bvar/detail/call_op_returning_void.h @@ -0,0 +1,35 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date 2014/09/22 11:57:43 + +#ifndef BVAR_DETAIL_CALL_OP_RETURNING_VOID_H +#define BVAR_DETAIL_CALL_OP_RETURNING_VOID_H + +namespace bvar { +namespace detail { + +template +inline void call_op_returning_void( + const Op& op, T1& v1, const T2& v2) { + return op(v1, v2); +} + +} // namespace detail +} // namespace bvar + +#endif //BVAR_DETAIL_CALL_OP_RETURNING_VOID_H diff --git a/src/bvar/detail/combiner.h b/src/bvar/detail/combiner.h new file mode 100644 index 0000000..3007f50 --- /dev/null +++ b/src/bvar/detail/combiner.h @@ -0,0 +1,396 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date 2014/09/22 11:57:43 + +#ifndef BVAR_COMBINER_H +#define BVAR_COMBINER_H + +#include // std::string +#include // std::vector +#include +#include "butil/atomicops.h" // butil::atomic +#include "butil/scoped_lock.h" // BAIDU_SCOPED_LOCK +#include "butil/type_traits.h" // butil::add_cr_non_integral +#include "butil/synchronization/lock.h" // butil::Lock +#include "butil/containers/linked_list.h"// LinkNode +#include "bvar/detail/agent_group.h" // detail::AgentGroup +#include "bvar/detail/is_atomical.h" +#include "bvar/detail/call_op_returning_void.h" + +namespace bvar { +namespace detail { + +// Parameter to merge_global. +template +class GlobalValue { +public: + typedef typename Combiner::result_type result_type; + typedef typename Combiner::Agent agent_type; + + GlobalValue(agent_type* a, Combiner* c) : _a(a), _c(c) {} + + // Call this method to unlock tls element and lock the combiner. + // Unlocking tls element avoids potential deadlock with + // AgentCombiner::reset(), which also means that tls element may be + // changed during calling of this method. BE AWARE OF THIS! + // After this method is called (and before unlock), tls element and + // global_result will not be changed provided this method is called + // from the thread owning the agent. + result_type* lock() { + _a->element._lock.Release(); + _c->_lock.Acquire(); + return &_c->_global_result; + } + + // Call this method to unlock the combiner and lock tls element again. + void unlock() { + _c->_lock.Release(); + _a->element._lock.Acquire(); + } + +private: + agent_type* _a; + Combiner* _c; +}; + +// Abstraction of tls element whose operations are all atomic. +template +class ElementContainer { +template friend class GlobalValue; +public: + void load(T* out) { + butil::AutoLock guard(_lock); + *out = _value; + } + + void store(const T& new_value) { + butil::AutoLock guard(_lock); + _value = new_value; + } + + void exchange(T* prev, const T& new_value) { + butil::AutoLock guard(_lock); + *prev = _value; + _value = new_value; + } + + template + void modify(const Op &op, const T1 &value2) { + butil::AutoLock guard(_lock); + call_op_returning_void(op, _value, value2); + } + + // [Unique] + template + void merge_global(const Op &op, GlobalValue & global_value) { + _lock.Acquire(); + op(global_value, _value); + _lock.Release(); + } + +private: + T _value; + butil::Lock _lock; +}; + +template +class ElementContainer< + T, typename butil::enable_if::value>::type> { +public: + // We don't need any memory fencing here, every op is relaxed. + + inline void load(T* out) { + *out = _value.load(butil::memory_order_relaxed); + } + + inline void store(T new_value) { + _value.store(new_value, butil::memory_order_relaxed); + } + + inline void exchange(T* prev, T new_value) { + *prev = _value.exchange(new_value, butil::memory_order_relaxed); + } + + // [Unique] + inline bool compare_exchange_weak(T& expected, T new_value) { + return _value.compare_exchange_weak(expected, new_value, + butil::memory_order_relaxed); + } + + template + void modify(const Op &op, const T1 &value2) { + T old_value = _value.load(butil::memory_order_relaxed); + T new_value = old_value; + call_op_returning_void(op, new_value, value2); + // There's a contention with the reset operation of combiner, + // if the tls value has been modified during _op, the + // compare_exchange_weak operation will fail and recalculation is + // to be processed according to the new version of value + while (!_value.compare_exchange_weak( + old_value, new_value, butil::memory_order_relaxed)) { + new_value = old_value; + call_op_returning_void(op, new_value, value2); + } + } + +private: + butil::atomic _value; +}; + +template +class AgentCombiner + : public std::enable_shared_from_this> { + +public: + typedef ResultTp result_type; + typedef ElementTp element_type; + typedef AgentCombiner self_type; + typedef std::shared_ptr self_shared_type; + typedef std::weak_ptr self_weak_type; +friend class GlobalValue; + + struct Agent : public butil::LinkNode { + ~Agent() { + self_shared_type c = combiner.lock(); + if (NULL != c) { + c->commit_and_erase(this); + } + } + + void reset(const ElementTp& val, const self_shared_type& c) { + combiner = c; + element.store(val); + } + + // Call op(GlobalValue &, ElementTp &) to merge tls element + // into global_result. The common impl. is: + // struct XXX { + // void operator()(GlobalValue & global_value, + // ElementTp & local_value) const { + // if (test_for_merging(local_value)) { + // + // // Unlock tls element and lock combiner. Obviously + // // tls element can be changed during lock(). + // ResultTp* g = global_value.lock(); + // + // // *g and local_value are not changed provided + // // merge_global is called from the thread owning + // // the agent. + // merge(*g, local_value); + // + // // unlock combiner and lock tls element again. + // global_value.unlock(); + // } + // + // // safe to modify local_value because it's already locked + // // or locked again after merging. + // ... + // } + // }; + // + // NOTE: Only available to non-atomic types. + template + void merge_global(const Op &op, self_shared_type& c) { + const self_shared_type& c_ref = NULL != c ? c : combiner.lock(); + if (NULL != c_ref) { + GlobalValue g(this, c_ref.get()); + element.merge_global(op, g); + } + } + + ElementContainer element; + private: + friend class AgentCombiner; + self_weak_type combiner; + }; + + typedef detail::AgentGroup AgentGroup; + + explicit AgentCombiner(const ResultTp result_identity = ResultTp(), + const ElementTp element_identity = ElementTp(), + const BinaryOp& op = BinaryOp()) + : _id(AgentGroup::create_new_agent()) + , _op(op) + , _global_result(result_identity) + , _result_identity(result_identity) + , _element_identity(element_identity) { + } + + ~AgentCombiner() { + if (_id >= 0) { + // NOTE: We intentionally do NOT walk `_agents` here (e.g. via the + // previously existed `clear_all_agents()`). + // + // `Agent` instances live inside per-thread `ThreadBlock`s owned by + // `AgentGroup` and are destroyed when their owning thread exits + // (via `_destroy_tls_blocks`). At that point `~Agent` calls + // `combiner.lock()`; if the combiner has already started its + // destruction the `weak_ptr` is expired and the agent will skip + // `commit_and_erase`, leaving its `LinkNode` linked to this + // combiner's `_agents`. If we tried to traverse `_agents` here we + // could touch agent nodes whose `ThreadBlock` was just freed by + // a concurrent thread-exit, causing heap-use-after-free + // (see issue #2937 follow-up). + // + // It is safe to leave the list "dirty" because: + // * `butil::LinkedList` / `butil::LinkNode` have trivial + // destructors and never traverse on destruction, so tearing + // down `_agents` here does not dereference any agent node. + // * After this combiner is gone, every still-alive `Agent` will + // observe `combiner.expired() == true` in `~Agent` and skip + // `commit_and_erase`, so the dangling `prev_/next_` pointers + // in those agents are never read. + // * If the freed `_id` is later reused by a new combiner and the + // same TLS slot is taken, `get_or_create_tls_agent` will call + // `Agent::reset` and `Append` the agent into the new + // combiner's `_agents`. `LinkNode::InsertBefore` only writes + // `prev_/next_` (never reads their stale values), so the + // dangling pointers are safely overwritten. + // * `Agent::element` is destroyed together with the `ThreadBlock`, + // so any non-POD resource it holds is still released; if the + // agent slot is reused, `Agent::reset` will overwrite the + // element value before it is observed again. + AgentGroup::destroy_agent(_id); + _id = -1; + } + } + + // [Threadsafe] May be called from anywhere + ResultTp combine_agents() const { + ElementTp tls_value; + butil::AutoLock guard(_lock); + ResultTp ret = _global_result; + for (butil::LinkNode* node = _agents.head(); + node != _agents.end(); node = node->next()) { + node->value()->element.load(&tls_value); + call_op_returning_void(_op, ret, tls_value); + } + return ret; + } + + typename butil::add_cr_non_integral::type + element_identity() const { return _element_identity; } + typename butil::add_cr_non_integral::type + result_identity() const { return _result_identity; } + + // [Threadsafe] May be called from anywhere. + ResultTp reset_all_agents() { + ElementTp prev; + butil::AutoLock guard(_lock); + ResultTp tmp = _global_result; + _global_result = _result_identity; + for (butil::LinkNode* node = _agents.head(); + node != _agents.end(); node = node->next()) { + node->value()->element.exchange(&prev, _element_identity); + call_op_returning_void(_op, tmp, prev); + } + return tmp; + } + + // Always called from the thread owning the agent. + void commit_and_erase(Agent* agent) { + if (NULL == agent) { + return; + } + ElementTp local; + butil::AutoLock guard(_lock); + // TODO: For non-atomic types, we can pass the reference to op directly. + // But atomic types cannot. The code is a little troublesome to write. + agent->element.load(&local); + call_op_returning_void(_op, _global_result, local); + agent->RemoveFromList(); + } + + // Always called from the thread owning the agent + void commit_and_clear(Agent* agent) { + if (NULL == agent) { + return; + } + ElementTp prev; + butil::AutoLock guard(_lock); + agent->element.exchange(&prev, _element_identity); + call_op_returning_void(_op, _global_result, prev); + } + + // We need this function to be as fast as possible. + Agent* get_or_create_tls_agent() { + Agent* agent = AgentGroup::get_tls_agent(_id); + if (!agent) { + // Create the agent + agent = AgentGroup::get_or_create_tls_agent(_id); + if (NULL == agent) { + LOG(FATAL) << "Fail to create agent"; + return NULL; + } + } + if (!agent->combiner.expired()) { + return agent; + } + agent->reset(_element_identity, this->shared_from_this()); + // TODO: Is uniqueness-checking necessary here? + { + butil::AutoLock guard(_lock); + _agents.Append(agent); + } + return agent; + } + + // NOTE: `clear_all_agents()` is intentionally kept but no longer called + // from `~AgentCombiner` (see the long comment in `~AgentCombiner`). + // + // Calling it from the destructor is unsafe: by the time the destructor + // runs, agent weak_ptrs have already expired and `~Agent` will skip + // `commit_and_erase`; a concurrent thread-exit can therefore free the + // `ThreadBlock` (and the agents inside it) while we are still walking + // `_agents` here, which is a heap-use-after-free. + // + // The body is left around (commented out) for reference / future use -- + // do NOT re-enable it from `~AgentCombiner`. + // + // void clear_all_agents() { + // butil::AutoLock guard(_lock); + // // Resetting agents is a must because the agent object may be + // // reused. Set element to be default-constructed so that if it's + // // non-pod, internal allocations should be released. + // for (butil::LinkNode* node = _agents.head(); + // node != _agents.end();) { + // node->value()->reset(ElementTp(), NULL); + // butil::LinkNode* const saved_next = node->next(); + // node->RemoveFromList(); + // node = saved_next; + // } + // } + + const BinaryOp& op() const { return _op; } + + bool valid() const { return _id >= 0; } + +private: + AgentId _id; + BinaryOp _op; + mutable butil::Lock _lock; + ResultTp _global_result; + ResultTp _result_identity; + ElementTp _element_identity; + butil::LinkedList _agents; +}; + +} // namespace detail +} // namespace bvar + +#endif // BVAR_COMBINER_H diff --git a/src/bvar/detail/is_atomical.h b/src/bvar/detail/is_atomical.h new file mode 100644 index 0000000..3b84128 --- /dev/null +++ b/src/bvar/detail/is_atomical.h @@ -0,0 +1,41 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BVAR_DETAIL_IS_ATOMICAL_H +#define BVAR_DETAIL_IS_ATOMICAL_H + +#include "butil/type_traits.h" + +namespace bvar { +namespace detail { +template struct is_atomical +: butil::integral_constant::value || + butil::is_floating_point::value) + // FIXME(gejun): Not work in gcc3.4 + // butil::is_enum::value || + // NOTE(gejun): Ops on pointers are not + // atomic generally + // butil::is_pointer::value + > {}; +template struct is_atomical : is_atomical { }; +template struct is_atomical : is_atomical { }; +template struct is_atomical : is_atomical { }; + +} // namespace detail +} // namespace bvar + +#endif diff --git a/src/bvar/detail/percentile.cpp b/src/bvar/detail/percentile.cpp new file mode 100644 index 0000000..99de328 --- /dev/null +++ b/src/bvar/detail/percentile.cpp @@ -0,0 +1,167 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2015/09/15 15:14:32 + +#include "bvar/detail/percentile.h" +#include "butil/logging.h" + +namespace bvar { +namespace detail { +#if !WITH_BABYLON_COUNTER +inline uint32_t ones32(uint32_t x) { + /* 32-bit recursive reduction using SWAR... + * but first step is mapping 2-bit values + * into sum of 2 1-bit values in sneaky way + */ + x -= ((x >> 1) & 0x55555555); + x = (((x >> 2) & 0x33333333) + (x & 0x33333333)); + x = (((x >> 4) + x) & 0x0f0f0f0f); + x += (x >> 8); + x += (x >> 16); + return (x & 0x0000003f); +} + +inline uint32_t log2(uint32_t x) { + int y = (x & (x - 1)); + y |= -y; + y >>= 31; + x |= (x >> 1); + x |= (x >> 2); + x |= (x >> 4); + x |= (x >> 8); + x |= (x >> 16); + return(ones32(x) - 1 - y); +} + +inline size_t get_interval_index(int64_t &x) { + if (x <= 2) { + return 0; + } else if (x > std::numeric_limits::max()) { + x = std::numeric_limits::max(); + return 31; + } else { + return log2(x) - 1; + } +} + +class AddLatency { +public: + AddLatency(int64_t latency) : _latency(latency) {} + + void operator()(GlobalValue& global_value, + ThreadLocalPercentileSamples& local_value) const { + // Copy to latency since get_interval_index may change input. + int64_t latency = _latency; + const size_t index = get_interval_index(latency); + PercentileInterval& + interval = local_value.get_interval_at(index); + if (interval.full()) { + GlobalPercentileSamples* g = global_value.lock(); + g->get_interval_at(index).merge(interval); + g->_num_added += interval.added_count(); + global_value.unlock(); + local_value._num_added -= interval.added_count(); + interval.clear(); + } + interval.add64(latency); + ++local_value._num_added; + } +private: + int64_t _latency; +}; + +Percentile::Percentile() + : _combiner(std::make_shared()), _sampler(NULL) {} + +Percentile::~Percentile() { + // Have to destroy sampler first to avoid the race between destruction and + // sampler + if (_sampler != NULL) { + _sampler->destroy(); + _sampler = NULL; + } +} + +Percentile::value_type Percentile::reset() { + return _combiner->reset_all_agents(); +} + +Percentile::value_type Percentile::get_value() const { + return _combiner->combine_agents(); +} + +Percentile &Percentile::operator<<(int64_t latency) { + agent_type* agent = _combiner->get_or_create_tls_agent(); + if (BAIDU_UNLIKELY(!agent)) { + LOG(FATAL) << "Fail to create agent"; + return *this; + } + if (latency < 0) { + // we don't check overflow(of uint32) in percentile because the + // overflowed value which is included in last range does not affect + // overall distribution of other values too much. + if (!_debug_name.empty()) { + LOG(WARNING) << "Input=" << latency << " to `" << _debug_name + << "' is negative, drop"; + } else { + LOG(WARNING) << "Input=" << latency << " to Percentile(" + << (void*)this << ") is negative, drop"; + } + return *this; + } + agent->merge_global(AddLatency(latency), _combiner); + return *this; +} +#else +Percentile::value_type Percentile::reset() { + constexpr static size_t SAMPLE_SIZE = value_type::SAMPLE_SIZE; + value_type result; + _concurrent_sampler.for_each([&]( + size_t index, const babylon::ConcurrentSampler::SampleBucket& bucket) { + result.merge(bucket, index); + auto capacity = _concurrent_sampler.bucket_capacity(index); + auto num_added = bucket.record_num.load(::std::memory_order_relaxed); + if (capacity < SAMPLE_SIZE && num_added > capacity) { + capacity = std::min(SAMPLE_SIZE, num_added * 1.5); + _concurrent_sampler.set_bucket_capacity(index, capacity); + } + }); + _concurrent_sampler.reset(); + return result; +} + +Percentile& Percentile::operator<<(int64_t value) { + if (BAIDU_UNLIKELY(value < 0)) { + // we don't check overflow(of uint32) in percentile because the + // overflowed value which is included in last range does not affect + // overall distribution of other values too much. + if (!_debug_name.empty()) { + LOG_EVERY_SECOND(WARNING) << "Input=" << value << " to `" << _debug_name + << "' is negative, drop"; + } else { + LOG_EVERY_SECOND(WARNING) << "Input=" << value << " to Percentile(" + << (void*)this << ") is negative, drop"; + } + } else { + _concurrent_sampler << value; + } + return *this; +} +#endif // WITH_BABYLON_COUNTER +} // namespace detail +} // namespace bvar diff --git a/src/bvar/detail/percentile.h b/src/bvar/detail/percentile.h new file mode 100644 index 0000000..e6acbf3 --- /dev/null +++ b/src/bvar/detail/percentile.h @@ -0,0 +1,625 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2015/09/15 10:44:17 + +#ifndef BVAR_DETAIL_PERCENTILE_H +#define BVAR_DETAIL_PERCENTILE_H + +#include // memset memcmp +#include // uint32_t +#include // std::numeric_limits +#include // std::ostream +#include // std::sort +#include // ceil +#include "butil/macros.h" // ARRAY_SIZE +#include "bvar/reducer.h" // Reducer +#include "bvar/window.h" // Window +#include "bvar/detail/combiner.h" // AgentCombiner +#include "bvar/detail/sampler.h" // ReducerSampler +#include "butil/fast_rand.h" +#if WITH_BABYLON_COUNTER +#include "babylon/concurrent/counter.h" +#endif // WITH_BABYLON_COUNTER + +namespace bvar { +namespace detail { + +// Round of expectation of a rational number |a/b| to a natural number. +inline unsigned long round_of_expectation(unsigned long a, unsigned long b) { + if (BAIDU_UNLIKELY(b == 0)) { + return 0; + } + return a / b + (butil::fast_rand_less_than(b) < a % b); +} + +// Storing latencies inside a interval. +template +class PercentileInterval { +public: + PercentileInterval() + : _num_added(0) + , _sorted(false) + , _num_samples(0) { + } + + // Get index-th sample in ascending order. + uint32_t get_sample_at(size_t index) { + const size_t saved_num = _num_samples; + if (index >= saved_num) { + if (saved_num == 0) { + return 0; + } + index = saved_num - 1; + } + if (!_sorted) { + std::sort(_samples, _samples + saved_num); + _sorted = true; + } + CHECK_EQ(saved_num, _num_samples) << "You must call get_number() on" + " a unchanging PercentileInterval"; + return _samples[index]; + } + + // Add samples of another interval. This function tries to make each + // sample in merged _samples has (approximately) equal probability to + // remain. + // This method is invoked when merging ThreadLocalPercentileSamples in to + // GlobalPercentileSamples + template + void merge(const PercentileInterval &rhs) { + if (rhs._num_added == 0) { + return; + } + BAIDU_CASSERT(SAMPLE_SIZE >= size2, + must_merge_small_interval_into_larger_one_currently); + CHECK_EQ(rhs._num_samples, rhs._num_added); + // Assume that the probability of each sample in |this| is a0/b0 and + // the probability of each sample in |rhs| is a1/b1. + // We are going to randomly pick some samples from |this| and |rhs| to + // satisfy the constraint that each sample stands for the probability + // of + // * 1 (SAMPLE_SIZE >= |b0 + b1|), which indicates that no sample + // has been dropped + // * SAMPLE_SIZE / |b0 + b1| (SAMPLE_SIZE < |b0 + b1|) + // So we should keep |b0*SAMPLE_SIZE/(b0+b1)| from |this| + // |b1*SAMPLE_SIZE/(b0+b1)| from |rhs|. + if (_num_added + rhs._num_added <= SAMPLE_SIZE) { + // No sample should be dropped + CHECK_EQ(_num_samples, _num_added) + << "_num_added=" << _num_added + << " rhs._num_added=" << rhs._num_added + << " _num_samples=" << _num_samples + << " rhs._num_samples=" << rhs._num_samples + << " SAMPLE_SIZE=" << SAMPLE_SIZE + << " size2=" << size2; + memcpy(_samples + _num_samples, rhs._samples, + sizeof(_samples[0]) * rhs._num_samples); + _num_samples += rhs._num_samples; + } else { + // |num_remain| must be less than _num_samples: + // if _num_added = _num_samples: + // SAMPLE_SIZE / (_num_added + rhs._num_added) < 1 so that + // num_remain < _num_added = _num_samples + // otherwise: + // _num_samples = SAMPLE_SIZE; + // _num_added / (_num_added + rhs._num_added) < 1 so that + // num_remain < SAMPLE_SIZE = _num_added + size_t num_remain = round_of_expectation( + _num_added * SAMPLE_SIZE, _num_added + rhs._num_added); + CHECK_LE(num_remain, _num_samples); + // Randomly drop samples of this + for (size_t i = _num_samples; i > num_remain; --i) { + _samples[butil::fast_rand_less_than(i)] = _samples[i - 1]; + } + const size_t num_remain_from_rhs = SAMPLE_SIZE - num_remain; + CHECK_LE(num_remain_from_rhs, rhs._num_samples); + // Have to copy data from rhs to shuffle since it's const + DEFINE_SMALL_ARRAY(uint32_t, tmp, rhs._num_samples, 64); + memcpy(tmp, rhs._samples, sizeof(uint32_t) * rhs._num_samples); + for (size_t i = 0; i < num_remain_from_rhs; ++i) { + const int index = butil::fast_rand_less_than(rhs._num_samples - i); + _samples[num_remain++] = tmp[index]; + tmp[index] = tmp[rhs._num_samples - i - 1]; + } + _num_samples = num_remain; + CHECK_EQ(_num_samples, SAMPLE_SIZE); + } + _num_added += rhs._num_added; + } + +#if WITH_BABYLON_COUNTER + size_t merge(const babylon::ConcurrentSampler::SampleBucket& bucket) { + auto num_added = bucket.record_num.load(std::memory_order_acquire); + if (num_added == 0) { + return 0; + } + auto num_samples = std::min(num_added, static_cast(bucket.capacity)); + // If there is space, deposit directly. + if (_num_samples + num_samples <= SAMPLE_SIZE) { + __builtin_memcpy(_samples + _num_samples, bucket.data, + sizeof(uint32_t) * num_samples); + _num_samples += num_samples; + } else { + // Sample probability weighting. + float ratio = static_cast(num_samples) / num_added; + // Try to deposit directly first. + if (_num_samples < SAMPLE_SIZE) { + auto copy_size = SAMPLE_SIZE - _num_samples; + num_samples -= copy_size; + __builtin_memcpy(_samples + _num_samples, + bucket.data + num_samples, sizeof(uint32_t) * copy_size); + } + // The remaining samples are stored according to probability. + for (size_t i = 0; i < num_samples; ++i) { + auto index = butil::fast_rand() % + static_cast((_num_added + i) * ratio + 1); + if (index < SAMPLE_SIZE) { + _samples[index] = bucket.data[i]; + } + } + _num_samples = SAMPLE_SIZE; + } + _num_added += num_added; + return num_added; + } +#endif // WITH_BABYLON_COUNTER + + // Randomly pick n samples from mutable_rhs to |this| + template + void merge_with_expectation(PercentileInterval& mutable_rhs, size_t n) { + CHECK(n <= mutable_rhs._num_samples); + _num_added += mutable_rhs._num_added; + if (_num_samples + n <= SAMPLE_SIZE && n == mutable_rhs._num_samples) { + memcpy(_samples + _num_samples, mutable_rhs._samples, sizeof(_samples[0]) * n); + _num_samples += n; + return; + } + for (size_t i = 0; i < n; ++i) { + size_t index = butil::fast_rand_less_than(mutable_rhs._num_samples - i); + if (_num_samples < SAMPLE_SIZE) { + _samples[_num_samples++] = mutable_rhs._samples[index]; + } else { + _samples[butil::fast_rand_less_than(_num_samples)] + = mutable_rhs._samples[index]; + } + std::swap(mutable_rhs._samples[index], + mutable_rhs._samples[mutable_rhs._num_samples - i - 1]); + } + } + + // Add an unsigned 32-bit latency (what percentile actually accepts) to a + // non-full interval, which is invoked by Percentile::operator<< to add a + // sample into the ThreadLocalPercentileSamples. + // Returns true if the input was stored. + bool add32(uint32_t x) { + if (BAIDU_UNLIKELY(_num_samples >= SAMPLE_SIZE)) { + LOG(ERROR) << "This interval was full"; + return false; + } + ++_num_added; + _samples[_num_samples++] = x; + return true; + } + + // Add a signed latency. + bool add64(int64_t x) { + if (x >= 0) { + return add32((uint32_t)x); + } + return false; + } + + // Remove all samples inside. + void clear() { + _num_added = 0; + _sorted = false; + _num_samples = 0; + } + + // True if no more room for new samples. + bool full() const { return _num_samples == SAMPLE_SIZE; } + + // True if there's no samples. + bool empty() const { return !_num_samples; } + + // #samples ever added by calling add*() + uint32_t added_count() const { return _num_added; } + + // #samples stored. + uint32_t sample_count() const { return _num_samples; } + + // For debuggin. + void describe(std::ostream &os) const { + os << "(num_added=" << added_count() << ")["; + for (size_t j = 0; j < _num_samples; ++j) { + os << ' ' << _samples[j]; + } + os << " ]"; + } + + // True if two PercentileInterval are exactly same, namely same # of added and + // same samples, mainly for debuggin. + bool operator==(const PercentileInterval& rhs) const { + return (_num_added == rhs._num_added && + _num_samples == rhs._num_samples && + memcmp(_samples, rhs._samples, _num_samples * sizeof(uint32_t)) == 0); + } + +private: +template friend class PercentileInterval; + BAIDU_CASSERT(SAMPLE_SIZE <= 65536, SAMPLE_SIZE_must_be_16bit); + + uint32_t _num_added; + bool _sorted; + uint16_t _num_samples; + uint32_t _samples[SAMPLE_SIZE]; +}; + +static const size_t NUM_INTERVALS = 32; + +// This declartion is a must for gcc 3.4 +class AddLatency; + +// Group of PercentileIntervals. +template +class PercentileSamples { +public: +#if !WITH_BABYLON_COUNTER +friend class AddLatency; +#endif // WITH_BABYLON_COUNTER + + static const size_t SAMPLE_SIZE = SAMPLE_SIZE_IN; + + PercentileSamples() { + memset(this, 0, sizeof(*this)); + } + + ~PercentileSamples() { + for (size_t i = 0; i < NUM_INTERVALS; ++i) { + if (_intervals[i]) { + delete _intervals[i]; + } + } + } + + // Copy-construct from another PercentileSamples. + // Copy/assigning happen at per-second scale. should be OK. + PercentileSamples(const PercentileSamples& rhs) { + _num_added = rhs._num_added; + for (size_t i = 0; i < NUM_INTERVALS; ++i) { + if (rhs._intervals[i] && !rhs._intervals[i]->empty()) { + _intervals[i] = new PercentileInterval(*rhs._intervals[i]); + } else { + _intervals[i] = NULL; + } + } + } + + // Assign from another PercentileSamples. + // Notice that we keep empty _intervals to avoid future allocations. + void operator=(const PercentileSamples& rhs) { + _num_added = rhs._num_added; + for (size_t i = 0; i < NUM_INTERVALS; ++i) { + if (rhs._intervals[i] && !rhs._intervals[i]->empty()) { + get_interval_at(i) = *rhs._intervals[i]; + } else if (_intervals[i]) { + _intervals[i]->clear(); + } + } + } + + // Get the `ratio'-ile value. E.g. 0.99 means 99%-ile value. + // Since we store samples in different intervals internally. We first + // address the interval by multiplying ratio with _num_added, then + // find the sample inside the interval. We've tested an alternative + // method that store all samples together w/o any intervals (or in another + // word, only one interval), the method is much simpler but is not as + // stable as current impl. CDF plotted by the method changes dramatically + // from seconds to seconds. It seems that separating intervals probably + // keep more long-tail values. + uint32_t get_number(double ratio) { + size_t n = (size_t)ceil(ratio * _num_added); + if (n > _num_added) { + n = _num_added; + } else if (n == 0) { + return 0; + } + for (size_t i = 0; i < NUM_INTERVALS; ++i) { + if (_intervals[i] == NULL) { + continue; + } + PercentileInterval& invl = *_intervals[i]; + if (n <= invl.added_count()) { + size_t sample_n = n * invl.sample_count() / invl.added_count(); + size_t sample_index = (sample_n ? sample_n - 1 : 0); + return invl.get_sample_at(sample_index); + } + n -= invl.added_count(); + } + CHECK(false) << "Can't reach here"; + return std::numeric_limits::max(); + } + + // Add samples in another PercentileSamples. + template + void merge(const PercentileSamples &rhs) { + _num_added += rhs._num_added; + for (size_t i = 0; i < NUM_INTERVALS; ++i) { + if (rhs._intervals[i] && !rhs._intervals[i]->empty()) { + get_interval_at(i).merge(*rhs._intervals[i]); + } + } + } + +#if WITH_BABYLON_COUNTER + void merge(const babylon::ConcurrentSampler::SampleBucket& bucket, size_t index) { + _num_added += get_interval_at(index).merge(bucket); + } +#endif // WITH_BABYLON_COUNTER + + // Combine multiple into a single PercentileSamples + template + void combine_of(const Iterator& begin, const Iterator& end) { + if (_num_added) { + // Very unlikely + for (size_t i = 0; i < NUM_INTERVALS; ++i) { + if (_intervals[i]) { + _intervals[i]->clear(); + } + } + _num_added = 0; + } + + for (Iterator iter = begin; iter != end; ++iter) { + _num_added += iter->_num_added; + } + + // Calculate probabilities for each interval + for (size_t i = 0; i < NUM_INTERVALS; ++i) { + size_t total = 0; + size_t total_sample=0; + for (Iterator iter = begin; iter != end; ++iter) { + if (iter->_intervals[i]) { + total += iter->_intervals[i]->added_count(); + total_sample += iter->_intervals[i]->sample_count(); + } + } + if (total == 0) { + // Empty interval + continue; + } + + + // Consider that sub interval took |a| samples out of |b| totally, + // each sample won the probability of |a/b| according to the + // algorithm of add32(), now we should pick some samples into the + // combined interval that satisfied each sample has the + // probability of |SAMPLE_SIZE/total|, so each sample has the + // probability of |(SAMPLE_SIZE*b)/(a*total) to remain and the + // expected number of samples in this interval is + // |(SAMPLE_SIZE*b)/total| + for (Iterator iter = begin; iter != end; ++iter) { + if (!iter->_intervals[i] || iter->_intervals[i]->empty()) { + continue; + } + typename butil::add_reference_intervals[i]))>::type + invl = *(iter->_intervals[i]); + if (total <= SAMPLE_SIZE) { + get_interval_at(i).merge_with_expectation( + invl, invl.sample_count()); + continue; + } + // Each + const size_t b = invl.added_count(); + const size_t remain = std::min( + round_of_expectation(b * SAMPLE_SIZE, total), + (size_t)invl.sample_count()); + get_interval_at(i).merge_with_expectation(invl, remain); + } + } + } + + // For debuggin. + void describe(std::ostream &os) const { + os << this << "{num_added=" << _num_added; + for (size_t i = 0; i < NUM_INTERVALS; ++i) { + if (_intervals[i] && !_intervals[i]->empty()) { + os << " interval[" << i << "]="; + _intervals[i]->describe(os); + } + } + os << '}'; + } + + // True if intervals of two PercentileSamples are exactly same. + bool operator==(const PercentileSamples& rhs) const { + for (size_t i = 0; i < NUM_INTERVALS; ++i) { + if (_intervals != rhs._intervals[i]) { + return false; + } + } + return true; + } + +private: +template friend class PercentileSamples; + + // Get/create interval on-demand. + PercentileInterval& get_interval_at(size_t index) { + if (_intervals[index] == NULL) { + _intervals[index] = new PercentileInterval; + } + return *_intervals[index]; + } + // sum of _num_added of all intervals. we update this value after any + // changes to intervals inside to make it O(1)-time accessible. + size_t _num_added; + PercentileInterval* _intervals[NUM_INTERVALS]; +}; + +template const size_t PercentileSamples::SAMPLE_SIZE; + +template +std::ostream &operator<<(std::ostream &os, const PercentileInterval &p) { + p.describe(os); + return os; +} + +template +std::ostream &operator<<(std::ostream &os, const PercentileSamples &p) { + p.describe(os); + return os; +} + +// NOTE: we intentionally minus 2 uint32_t from sample-size to make the struct +// size be power of 2 and more friendly to memory allocators. +typedef PercentileSamples<254> GlobalPercentileSamples; +typedef PercentileSamples<30> ThreadLocalPercentileSamples; + +namespace detail { +struct AddPercentileSamples { + template + void operator()(PercentileSamples &b1, + const PercentileSamples &b2) const { + b1.merge(b2); + } +}; +} // namespace detail + +// A specialized reducer for finding the percentile of latencies. +// NOTE: DON'T use it directly, use LatencyRecorder instead. +#if !WITH_BABYLON_COUNTER +class Percentile { +public: + typedef GlobalPercentileSamples value_type; + typedef ReducerSamplersampler_type; + typedef AgentCombiner combiner_type; + typedef combiner_type::self_shared_type shared_combiner_type; + typedef combiner_type::Agent agent_type; + + Percentile(); + ~Percentile(); + + detail::AddPercentileSamples op() const { + return detail::AddPercentileSamples(); + } + VoidOp inv_op() const { return VoidOp(); } + + // The sampler for windows over percentile. + sampler_type* get_sampler() { + if (NULL == _sampler) { + _sampler = new sampler_type(this); + _sampler->schedule(); + } + return _sampler; + } + + value_type reset(); + + value_type get_value() const; + + Percentile& operator<<(int64_t latency); + + bool valid() const { return _combiner != NULL && _combiner->valid(); } + + // This name is useful for warning negative latencies in operator<< + void set_debug_name(const butil::StringPiece& name) { + _debug_name.assign(name.data(), name.size()); + } + +private: + DISALLOW_COPY_AND_ASSIGN(Percentile); + + shared_combiner_type _combiner; + sampler_type* _sampler; + std::string _debug_name; +}; +#else +class Percentile { +public: + typedef GlobalPercentileSamples value_type; + typedef detail::AddPercentileSamples AddPercentileSamples; + typedef AddPercentileSamples Op; + typedef VoidOp InvOp; + typedef ReducerSampler sampler_type; + + Percentile() { + // babylon::ConcurrentSampler defaults every per-value-range + // bucket to capacity 30 (see _bucket_capacity[32] in + // babylon/concurrent/counter.h) and only grows it inside + // Percentile::reset() after observing how many samples + // arrived. That means the *first* collection round always + // observes badly under-sampled buckets, which destabilises + // mid-quantile estimates (e.g. p60/p70 on a uniform 1..10000 + // stream can land at ~5482/~6284 instead of ~6000/~7000 and + // make PercentileTest.add fail). Pre-size every bucket to the + // full SAMPLE_SIZE on construction so the very first reset() + // already sees representative samples. + for (size_t i = 0; i < NUM_INTERVALS; ++i) { + _concurrent_sampler.set_bucket_capacity(i, value_type::SAMPLE_SIZE); + } + } + + DISALLOW_COPY_AND_MOVE(Percentile); + ~Percentile() noexcept { + if (NULL != _sampler) { + _sampler->destroy(); + } + } + + Op op() const { return Op(); } + InvOp inv_op() const { return InvOp(); } + + sampler_type* get_sampler() { + if (NULL == _sampler) { + _sampler = new sampler_type(this); + _sampler->schedule(); + } + return _sampler; + } + + value_type reset(); + + value_type get_value() const { + LOG_EVERY_SECOND(ERROR) << "Percentile should never call this get_value()"; + return value_type(); + } + + Percentile& operator<<(int64_t value); + + bool valid() const { return true; } + + // This name is useful for warning negative latencies in operator<< + void set_debug_name(const butil::StringPiece& name) { + _debug_name.assign(name.data(), name.size()); + } + +private: + babylon::ConcurrentSampler _concurrent_sampler; + sampler_type* _sampler{NULL}; + std::string _debug_name; +}; +#endif // WITH_BABYLON_COUNTER + +} // namespace detail +} // namespace bvar + +#endif //BVAR_DETAIL_PERCENTILE_H diff --git a/src/bvar/detail/sampler.cpp b/src/bvar/detail/sampler.cpp new file mode 100644 index 0000000..4632938 --- /dev/null +++ b/src/bvar/detail/sampler.cpp @@ -0,0 +1,220 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Tue Jul 28 18:14:40 CST 2015 + +#include +#include "butil/threading/platform_thread.h" +#include "butil/time.h" +#include "butil/memory/singleton_on_pthread_once.h" +#include "bvar/reducer.h" +#include "bvar/detail/sampler.h" +#include "bvar/passive_status.h" +#include "bvar/window.h" + +namespace bvar { +namespace detail { + +const int WARN_NOSLEEP_THRESHOLD = 2; + +// Combine two circular linked list into one. +struct CombineSampler { + void operator()(Sampler* & s1, Sampler* s2) const { + if (s2 == NULL) { + return; + } + if (s1 == NULL) { + s1 = s2; + return; + } + s1->InsertBeforeAsList(s2); + } +}; + +// True iff pthread_atfork was called. The callback to atfork works for child +// of child as well, no need to register in the child again. +static bool registered_atfork = false; + +// Call take_sample() of all scheduled samplers. +// This can be done with regular timer thread, but it's way too slow(global +// contention + log(N) heap manipulations). We need it to be super fast so that +// creation overhead of Window<> is negliable. +// The trick is to use Reducer. Each Sampler is +// doubly linked, thus we can reduce multiple Samplers into one cicurlarly +// doubly linked list, and multiple lists into larger lists. We create a +// dedicated thread to periodically get_value() which is just the combined +// list of Samplers. Waking through the list and call take_sample(). +// If a Sampler needs to be deleted, we just mark it as unused and the +// deletion is taken place in the thread as well. +class SamplerCollector : public bvar::Reducer { +public: + SamplerCollector() + : _created(false) + , _stop(false) + , _cumulated_time_us(0) { + create_sampling_thread(); + } + ~SamplerCollector() { + if (_created) { + _stop = true; + pthread_join(_tid, NULL); + _created = false; + } + } + +private: + // Support for fork: + // * The singleton can be null before forking, the child callback will not + // be registered. + // * If the singleton is not null before forking, the child callback will + // be registered and the sampling thread will be re-created. + // * A forked program can be forked again. + + static void child_callback_atfork() { + butil::get_leaky_singleton()->after_forked_as_child(); + } + + void create_sampling_thread() { + const int rc = pthread_create(&_tid, NULL, sampling_thread, this); + if (rc != 0) { + LOG(FATAL) << "Fail to create sampling_thread, " << berror(rc); + } else { + _created = true; + if (!registered_atfork) { + registered_atfork = true; + pthread_atfork(NULL, NULL, child_callback_atfork); + } + } + } + + void after_forked_as_child() { + _created = false; + create_sampling_thread(); + } + + void run(); + + static void* sampling_thread(void* arg) { + butil::PlatformThread::SetNameSimple("bvar_sampler"); + static_cast(arg)->run(); + return NULL; + } + + static double get_cumulated_time(void* arg) { + return static_cast(arg)->_cumulated_time_us / 1000.0 / 1000.0; + } + +private: + bool _created; + bool _stop; + int64_t _cumulated_time_us; + pthread_t _tid; +}; + +#ifndef UNIT_TEST +static PassiveStatus* s_cumulated_time_bvar = NULL; +static bvar::PerSecond >* s_sampling_thread_usage_bvar = NULL; +#endif + +DEFINE_int32(bvar_sampler_thread_start_delay_us, 10000, "bvar sampler thread start delay us"); + +void SamplerCollector::run() { + ::usleep(FLAGS_bvar_sampler_thread_start_delay_us); + +#ifndef UNIT_TEST + // NOTE: + // * Following vars can't be created on thread's stack since this thread + // may be abandoned at any time after forking. + // * They can't created inside the constructor of SamplerCollector as well, + // which results in deadlock. + if (s_cumulated_time_bvar == NULL) { + s_cumulated_time_bvar = + new PassiveStatus(get_cumulated_time, this); + } + if (s_sampling_thread_usage_bvar == NULL) { + s_sampling_thread_usage_bvar = + new bvar::PerSecond >( + "bvar_sampler_collector_usage", s_cumulated_time_bvar, 10); + } +#endif + + butil::LinkNode root; + int consecutive_nosleep = 0; + while (!_stop) { + int64_t abstime = butil::cpuwide_time_us(); + Sampler* s = this->reset(); + if (s) { + s->InsertBeforeAsList(&root); + } + for (butil::LinkNode* p = root.next(); p != &root;) { + // We may remove p from the list, save next first. + butil::LinkNode* saved_next = p->next(); + Sampler* s = p->value(); + s->_mutex.lock(); + if (!s->_used) { + s->_mutex.unlock(); + p->RemoveFromList(); + delete s; + } else { + s->take_sample(); + s->_mutex.unlock(); + } + p = saved_next; + } + bool slept = false; + int64_t now = butil::cpuwide_time_us(); + _cumulated_time_us += now - abstime; + abstime += 1000000L; + while (abstime > now) { + ::usleep(abstime - now); + slept = true; + now = butil::cpuwide_time_us(); + } + if (slept) { + consecutive_nosleep = 0; + } else { + if (++consecutive_nosleep >= WARN_NOSLEEP_THRESHOLD) { + consecutive_nosleep = 0; + LOG(WARNING) << "bvar is busy at sampling for " + << WARN_NOSLEEP_THRESHOLD << " seconds!"; + } + } + } +} + +Sampler::Sampler() : _used(true) {} + +Sampler::~Sampler() {} + +DEFINE_bool(bvar_enable_sampling, true, "is enable bvar sampling"); + +void Sampler::schedule() { + // since the SamplerCollector is initialized before the program starts + // flags will not take effect if used in the SamplerCollector constructor + if (FLAGS_bvar_enable_sampling) { + *butil::get_leaky_singleton() << this; + } +} + +void Sampler::destroy() { + _mutex.lock(); + _used = false; + _mutex.unlock(); +} + +} // namespace detail +} // namespace bvar diff --git a/src/bvar/detail/sampler.h b/src/bvar/detail/sampler.h new file mode 100644 index 0000000..32b976d --- /dev/null +++ b/src/bvar/detail/sampler.h @@ -0,0 +1,223 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Tue Jul 28 18:15:57 CST 2015 + +#ifndef BVAR_DETAIL_SAMPLER_H +#define BVAR_DETAIL_SAMPLER_H + +#include +#include "butil/containers/linked_list.h"// LinkNode +#include "butil/scoped_lock.h" // BAIDU_SCOPED_LOCK +#include "butil/logging.h" // LOG() +#include "butil/containers/bounded_queue.h"// BoundedQueue +#include "butil/type_traits.h" // is_same +#include "butil/time.h" // cpuwide_time_us +#include "butil/class_name.h" + +namespace bvar { +namespace detail { + +template +struct Sample { + T data; + int64_t time_us; + + Sample() : data(), time_us(0) {} + Sample(const T& data2, int64_t time2) : data(data2), time_us(time2) {} +}; + +// The base class for all samplers whose take_sample() are called periodically. +class Sampler : public butil::LinkNode { +public: + Sampler(); + + // This function will be called every second(approximately) in a + // dedicated thread if schedule() is called. + virtual void take_sample() = 0; + + // Register this sampler globally so that take_sample() will be called + // periodically. + void schedule(); + + // Call this function instead of delete to destroy the sampler. Deletion + // of the sampler may be delayed for seconds. + void destroy(); + +protected: + virtual ~Sampler(); + +friend class SamplerCollector; + bool _used; + // Sync destroy() and take_sample(). + butil::Mutex _mutex; +}; + +// Representing a non-existing operator so that we can test +// is_same::value to write code for different branches. +// The false branch should be removed by compiler at compile-time. +struct VoidOp { + template + T operator()(const T&, const T&) const { + CHECK(false) << "This function should never be called, abort"; + abort(); + } +}; + +// The sampler for reducer-alike variables. +// The R should have following methods: +// - T reset(); +// - T get_value(); +// - Op op(); +// - InvOp inv_op(); +template +class ReducerSampler : public Sampler { +public: + static const time_t MAX_SECONDS_LIMIT = 3600; + + explicit ReducerSampler(R* reducer) + : _reducer(reducer) + , _window_size(1) { + + // Invoked take_sample at begining so the value of the first second + // would not be ignored + take_sample(); + } + ~ReducerSampler() {} + + void take_sample() override { + // Make _q ready. + // If _window_size is larger than what _q can hold, e.g. a larger + // Window<> is created after running of sampler, make _q larger. + if ((size_t)_window_size + 1 > _q.capacity()) { + const size_t new_cap = + std::max(_q.capacity() * 2, (size_t)_window_size + 1); + const size_t memsize = sizeof(Sample) * new_cap; + void* mem = malloc(memsize); + if (NULL == mem) { + return; + } + butil::BoundedQueue > new_q( + mem, memsize, butil::OWNS_STORAGE); + Sample tmp; + while (_q.pop(&tmp)) { + new_q.push(tmp); + } + new_q.swap(_q); + } + + Sample latest; + if (butil::is_same::value) { + // The operator can't be inversed. + // We reset the reducer and save the result as a sample. + // Suming up samples gives the result within a window. + // In this case, get_value() of _reducer gives wrong answer and + // should not be called. + latest.data = _reducer->reset(); + } else { + // The operator can be inversed. + // We save the result as a sample. + // Inversed operation between latest and oldest sample within a + // window gives result. + // get_value() of _reducer can still be called. + latest.data = _reducer->get_value(); + } + latest.time_us = butil::cpuwide_time_us(); + _q.elim_push(latest); + } + + bool get_value(time_t window_size, Sample* result) { + if (window_size <= 0) { + LOG(FATAL) << "Invalid window_size=" << window_size; + return false; + } + BAIDU_SCOPED_LOCK(_mutex); + if (_q.size() <= 1UL) { + // We need more samples to get reasonable result. + return false; + } + Sample* oldest = _q.bottom(window_size); + if (NULL == oldest) { + oldest = _q.top(); + } + Sample* latest = _q.bottom(); + DCHECK(latest != oldest); + if (butil::is_same::value) { + // No inverse op. Sum up all samples within the window. + result->data = latest->data; + for (int i = 1; true; ++i) { + Sample* e = _q.bottom(i); + if (e == oldest) { + break; + } + _reducer->op()(result->data, e->data); + } + } else { + // Diff the latest and oldest sample within the window. + result->data = latest->data; + _reducer->inv_op()(result->data, oldest->data); + } + result->time_us = latest->time_us - oldest->time_us; + return true; + } + + // Change the time window which can only go larger. + int set_window_size(time_t window_size) { + if (window_size <= 0 || window_size > MAX_SECONDS_LIMIT) { + LOG(ERROR) << "Invalid window_size=" << window_size; + return -1; + } + BAIDU_SCOPED_LOCK(_mutex); + if (window_size > _window_size) { + _window_size = window_size; + } + return 0; + } + + void get_samples(std::vector *samples, time_t window_size) { + if (window_size <= 0) { + LOG(FATAL) << "Invalid window_size=" << window_size; + return; + } + BAIDU_SCOPED_LOCK(_mutex); + if (_q.size() <= 1) { + // We need more samples to get reasonable result. + return; + } + Sample* oldest = _q.bottom(window_size); + if (NULL == oldest) { + oldest = _q.top(); + } + for (int i = 1; true; ++i) { + Sample* e = _q.bottom(i); + if (e == oldest) { + break; + } + samples->push_back(e->data); + } + } + +private: + R* _reducer; + time_t _window_size; + butil::BoundedQueue > _q; +}; + +} // namespace detail +} // namespace bvar + +#endif // BVAR_DETAIL_SAMPLER_H diff --git a/src/bvar/detail/series.h b/src/bvar/detail/series.h new file mode 100644 index 0000000..3ceb913 --- /dev/null +++ b/src/bvar/detail/series.h @@ -0,0 +1,332 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Tue Jul 28 18:15:57 CST 2015 + +#ifndef BVAR_DETAIL_SERIES_H +#define BVAR_DETAIL_SERIES_H + +#include // round +#include +#include "butil/scoped_lock.h" // BAIDU_SCOPED_LOCK +#include "butil/type_traits.h" +#include "bvar/vector.h" +#include "bvar/detail/call_op_returning_void.h" +#include "butil/string_splitter.h" + +namespace bvar { +namespace detail { + +template +struct DivideOnAddition { + static void inplace_divide(T& /*obj*/, const Op&, int /*number*/) { + // do nothing + } +}; + +template +struct ProbablyAddtition { + ProbablyAddtition(const Op& op) { + T res(32); + call_op_returning_void(op, res, T(64)); + _ok = (res == T(96)); // works for integral/floating point. + } + operator bool() const { return _ok; } +private: + bool _ok; +}; + +template +struct DivideOnAddition::value>::type> { + static void inplace_divide(T& obj, const Op& op, int number) { + static ProbablyAddtition probably_add(op); + if (probably_add) { + obj = (T)round(obj / (double)number); + } + } +}; + +template +struct DivideOnAddition::value>::type> { + static void inplace_divide(T& obj, const Op& op, int number) { + static ProbablyAddtition probably_add(op); + if (probably_add) { + obj /= number; + } + } +}; + +template +struct DivideOnAddition, Op, typename butil::enable_if< + butil::is_integral::value>::type> { + static void inplace_divide(Vector& obj, const Op& op, int number) { + static ProbablyAddtition, Op> probably_add(op); + if (probably_add) { + for (size_t i = 0; i < N; ++i) { + obj[i] = (T)round(obj[i] / (double)number); + } + } + } +}; + +template +struct DivideOnAddition, Op, typename butil::enable_if< + butil::is_floating_point::value>::type> { + static void inplace_divide(Vector& obj, const Op& op, int number) { + static ProbablyAddtition, Op> probably_add(op); + if (probably_add) { + obj /= number; + } + } +}; + +template +class SeriesBase { +public: + explicit SeriesBase(const Op& op) + : _op(op) + , _nsecond(0) + , _nminute(0) + , _nhour(0) + , _nday(0) { + pthread_mutex_init(&_mutex, NULL); + } + ~SeriesBase() { + pthread_mutex_destroy(&_mutex); + } + + void append(const T& value) { + BAIDU_SCOPED_LOCK(_mutex); + return append_second(value, _op); + } + +private: + void append_second(const T& value, const Op& op); + void append_minute(const T& value, const Op& op); + void append_hour(const T& value, const Op& op); + void append_day(const T& value); + + struct Data { + public: + Data() { + // is_pod does not work for gcc 3.4 + if (butil::is_integral::value || + butil::is_floating_point::value) { + memset(static_cast(_array), 0, sizeof(_array)); + } + } + + T& second(int index) { return _array[index]; } + const T& second(int index) const { return _array[index]; } + + T& minute(int index) { return _array[60 + index]; } + const T& minute(int index) const { return _array[60 + index]; } + + T& hour(int index) { return _array[120 + index]; } + const T& hour(int index) const { return _array[120 + index]; } + + T& day(int index) { return _array[144 + index]; } + const T& day(int index) const { return _array[144 + index]; } + private: + T _array[60 + 60 + 24 + 30]; + }; + +protected: + Op _op; + mutable pthread_mutex_t _mutex; + char _nsecond; + char _nminute; + char _nhour; + char _nday; + Data _data; +}; + +template +void SeriesBase::append_second(const T& value, const Op& op) { + _data.second(_nsecond) = value; + ++_nsecond; + if (_nsecond >= 60) { + _nsecond = 0; + T tmp = _data.second(0); + for (int i = 1; i < 60; ++i) { + call_op_returning_void(op, tmp, _data.second(i)); + } + DivideOnAddition::inplace_divide(tmp, op, 60); + append_minute(tmp, op); + } +} + +template +void SeriesBase::append_minute(const T& value, const Op& op) { + _data.minute(_nminute) = value; + ++_nminute; + if (_nminute >= 60) { + _nminute = 0; + T tmp = _data.minute(0); + for (int i = 1; i < 60; ++i) { + call_op_returning_void(op, tmp, _data.minute(i)); + } + DivideOnAddition::inplace_divide(tmp, op, 60); + append_hour(tmp, op); + } +} + +template +void SeriesBase::append_hour(const T& value, const Op& op) { + _data.hour(_nhour) = value; + ++_nhour; + if (_nhour >= 24) { + _nhour = 0; + T tmp = _data.hour(0); + for (int i = 1; i < 24; ++i) { + call_op_returning_void(op, tmp, _data.hour(i)); + } + DivideOnAddition::inplace_divide(tmp, op, 24); + append_day(tmp); + } +} + +template +void SeriesBase::append_day(const T& value) { + _data.day(_nday) = value; + ++_nday; + if (_nday >= 30) { + _nday = 0; + } +} + +template +class Series : public SeriesBase { + typedef SeriesBase Base; +public: + explicit Series(const Op& op) : Base(op) {} + void describe(std::ostream& os, const std::string* vector_names) const; +}; + +template +class Series, Op> : public SeriesBase, Op> { + typedef SeriesBase, Op> Base; +public: + explicit Series(const Op& op) : Base(op) {} + void describe(std::ostream& os, const std::string* vector_names) const; +}; + +template +void Series::describe(std::ostream& os, + const std::string* vector_names) const { + CHECK(vector_names == NULL); + pthread_mutex_lock(&this->_mutex); + const int second_begin = this->_nsecond; + const int minute_begin = this->_nminute; + const int hour_begin = this->_nhour; + const int day_begin = this->_nday; + // NOTE: we don't save _data which may be inconsistent sometimes, but + // this output is generally for "peeking the trend" and does not need + // to exactly accurate. + pthread_mutex_unlock(&this->_mutex); + int c = 0; + os << "{\"label\":\"trend\",\"data\":["; + for (int i = 0; i < 30; ++i, ++c) { + if (c) { + os << ','; + } + os << '[' << c << ',' << this->_data.day((i + day_begin) % 30) << ']'; + } + for (int i = 0; i < 24; ++i, ++c) { + if (c) { + os << ','; + } + os << '[' << c << ',' << this->_data.hour((i + hour_begin) % 24) << ']'; + } + for (int i = 0; i < 60; ++i, ++c) { + if (c) { + os << ','; + } + os << '[' << c << ',' << this->_data.minute((i + minute_begin) % 60) << ']'; + } + for (int i = 0; i < 60; ++i, ++c) { + if (c) { + os << ','; + } + os << '[' << c << ',' << this->_data.second((i + second_begin) % 60) << ']'; + } + os << "]}"; +} + +template +void Series, Op>::describe(std::ostream& os, + const std::string* vector_names) const { + pthread_mutex_lock(&this->_mutex); + const int second_begin = this->_nsecond; + const int minute_begin = this->_nminute; + const int hour_begin = this->_nhour; + const int day_begin = this->_nday; + // NOTE: we don't save _data which may be inconsistent sometimes, but + // this output is generally for "peeking the trend" and does not need + // to exactly accurate. + pthread_mutex_unlock(&this->_mutex); + + butil::StringSplitter sp(vector_names ? vector_names->c_str() : "", ','); + os << '['; + for (size_t j = 0; j < N; ++j) { + if (j) { + os << ','; + } + int c = 0; + os << "{\"label\":\""; + if (sp) { + os << butil::StringPiece(sp.field(), sp.length()); + ++sp; + } else { + os << "Vector[" << j << ']'; + } + os << "\",\"data\":["; + for (int i = 0; i < 30; ++i, ++c) { + if (c) { + os << ','; + } + os << '[' << c << ',' << this->_data.day((i + day_begin) % 30)[j] << ']'; + } + for (int i = 0; i < 24; ++i, ++c) { + if (c) { + os << ','; + } + os << '[' << c << ',' << this->_data.hour((i + hour_begin) % 24)[j] << ']'; + } + for (int i = 0; i < 60; ++i, ++c) { + if (c) { + os << ','; + } + os << '[' << c << ',' << this->_data.minute((i + minute_begin) % 60)[j] << ']'; + } + for (int i = 0; i < 60; ++i, ++c) { + if (c) { + os << ','; + } + os << '[' << c << ',' << this->_data.second((i + second_begin) % 60)[j] << ']'; + } + os << "]}"; + } + os << ']'; +} + +} // namespace detail +} // namespace bvar + +#endif // BVAR_DETAIL_SERIES_H diff --git a/src/bvar/gflag.cpp b/src/bvar/gflag.cpp new file mode 100644 index 0000000..5525795 --- /dev/null +++ b/src/bvar/gflag.cpp @@ -0,0 +1,91 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Sun Aug 9 12:26:03 CST 2015 + +#include +#include +#include "bvar/gflag.h" + +namespace bvar { + +GFlag::GFlag(const butil::StringPiece& gflag_name) { + expose(gflag_name); +} + +GFlag::GFlag(const butil::StringPiece& prefix, + const butil::StringPiece& gflag_name) + : _gflag_name(gflag_name.data(), gflag_name.size()) { + expose_as(prefix, gflag_name); +} + +void GFlag::describe(std::ostream& os, bool quote_string) const { + GFLAGS_NAMESPACE::CommandLineFlagInfo info; + if (!GFLAGS_NAMESPACE::GetCommandLineFlagInfo(gflag_name().c_str(), &info)) { + if (quote_string) { + os << '"'; + } + os << "Unknown gflag=" << gflag_name(); + if (quote_string) { + os << '"'; + } + } else { + if (quote_string && info.type == "string") { + os << '"' << info.current_value << '"'; + } else { + os << info.current_value; + } + } +} + +#ifdef BAIDU_INTERNAL +void GFlag::get_value(boost::any* value) const { + GFLAGS_NAMESPACE::CommandLineFlagInfo info; + if (!GFLAGS_NAMESPACE::GetCommandLineFlagInfo(gflag_name().c_str(), &info)) { + *value = "Unknown gflag=" + gflag_name(); + } else if (info.type == "string") { + *value = info.current_value; + } else if (info.type == "int32") { + *value = static_cast(atoi(info.current_value.c_str())); + } else if (info.type == "int64") { + *value = static_cast(atoll(info.current_value.c_str())); + } else if (info.type == "uint64") { + *value = static_cast( + strtoull(info.current_value.c_str(), NULL, 10)); + } else if (info.type == "bool") { + *value = (info.current_value == "true"); + } else if (info.type == "double") { + *value = strtod(info.current_value.c_str(), NULL); + } else { + *value = "Unknown type=" + info.type + " of gflag=" + gflag_name(); + } +} +#endif + +std::string GFlag::get_value() const { + std::string str; + if (!GFLAGS_NAMESPACE::GetCommandLineOption(gflag_name().c_str(), &str)) { + return "Unknown gflag=" + gflag_name(); + } + return str; +} + +bool GFlag::set_value(const char* value) { + return !GFLAGS_NAMESPACE::SetCommandLineOption(gflag_name().c_str(), value).empty(); +} + +} // namespace bvar diff --git a/src/bvar/gflag.h b/src/bvar/gflag.h new file mode 100644 index 0000000..17f1f33 --- /dev/null +++ b/src/bvar/gflag.h @@ -0,0 +1,65 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Sun Aug 9 12:26:03 CST 2015 + +#ifndef BVAR_GFLAG_H +#define BVAR_GFLAG_H + +#include // std::string +#include "bvar/variable.h" + +namespace bvar { + +// Expose important gflags as bvar so that they're monitored. +class GFlag : public Variable { +public: + GFlag(const butil::StringPiece& gflag_name); + + GFlag(const butil::StringPiece& prefix, + const butil::StringPiece& gflag_name); + + // Calling hide() in dtor manually is a MUST required by Variable. + ~GFlag() { hide(); } + + void describe(std::ostream& os, bool quote_string) const override; + +#ifdef BAIDU_INTERNAL + void get_value(boost::any* value) const override; +#endif + + // Get value of the gflag. + // We don't bother making the return type generic. This function + // is just for consistency with other classes. + std::string get_value() const; + + // Set the gflag with a new value. + // Returns true on success. + bool set_value(const char* value); + + // name of the gflag. + const std::string& gflag_name() const { + return _gflag_name.empty() ? name() : _gflag_name; + } + +private: + std::string _gflag_name; +}; + +} // namespace bvar + +#endif //BVAR_GFLAG_H diff --git a/src/bvar/latency_recorder.cpp b/src/bvar/latency_recorder.cpp new file mode 100644 index 0000000..a951376 --- /dev/null +++ b/src/bvar/latency_recorder.cpp @@ -0,0 +1,302 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2014/09/22 11:57:43 + +#include +#include "butil/unique_ptr.h" +#include "butil/reloadable_flags.h" +#include "bvar/latency_recorder.h" + +namespace bvar { + +static bool valid_percentile(const char*, int32_t v) { + return v > 0 && v < 100; +} + +// Reloading following gflags does not change names of the corresponding bvars. +// Avoid reloading in practice. +DEFINE_int32(bvar_latency_p1, 80, "First latency percentile"); +BUTIL_VALIDATE_GFLAG(bvar_latency_p1, valid_percentile); + +DEFINE_int32(bvar_latency_p2, 90, "Second latency percentile"); +BUTIL_VALIDATE_GFLAG(bvar_latency_p2, valid_percentile); + +DEFINE_int32(bvar_latency_p3, 99, "Third latency percentile"); +BUTIL_VALIDATE_GFLAG(bvar_latency_p3, valid_percentile); + +namespace detail { + +typedef PercentileSamples<1022> CombinedPercentileSamples; + +CDF::CDF(PercentileWindow* w) : _w(w) {} + +CDF::~CDF() { + hide(); +} + +void CDF::describe(std::ostream& os, bool) const { + os << "\"click to view\""; +} + +int CDF::describe_series( + std::ostream& os, const SeriesOptions& options) const { + if (_w == NULL) { + return 1; + } + if (options.test_only) { + return 0; + } + std::unique_ptr cb(new CombinedPercentileSamples); + std::vector buckets; + _w->get_samples(&buckets); + for (size_t i = 0; i < buckets.size(); ++i) { + cb->combine_of(buckets.begin(), buckets.end()); + } + std::pair values[20]; + size_t n = 0; + for (int i = 1; i < 10; ++i) { + values[n++] = std::make_pair(i*10, cb->get_number(i * 0.1)); + } + for (int i = 91; i < 100; ++i) { + values[n++] = std::make_pair(i, cb->get_number(i * 0.01)); + } + values[n++] = std::make_pair(100, cb->get_number(0.999)); + values[n++] = std::make_pair(101, cb->get_number(0.9999)); + CHECK_EQ(n, arraysize(values)); + os << "{\"label\":\"cdf\",\"data\":["; + for (size_t i = 0; i < n; ++i) { + if (i) { + os << ','; + } + os << '[' << values[i].first << ',' << values[i].second << ']'; + } + os << "]}"; + return 0; +} + +// Return random int value with expectation = `dval' +static int64_t double_to_random_int(double dval) { + int64_t ival = static_cast(dval); + if (dval > ival + butil::fast_rand_double()) { + ival += 1; + } + return ival; +} + +static int64_t get_window_recorder_qps(void* arg) { + Sample s; + static_cast(arg)->get_span(&s); + // Use floating point to avoid overflow. + if (s.time_us <= 0) { + return 0; + } + return double_to_random_int(s.data.num * 1000000.0 / s.time_us); +} + +static int64_t get_recorder_count(void* arg) { + return static_cast(arg)->get_value().num; +} + +// Caller is responsible for deleting the return value. +static CombinedPercentileSamples* combine(PercentileWindow* w) { + CombinedPercentileSamples* cb = new CombinedPercentileSamples; + std::vector buckets; + w->get_samples(&buckets); + cb->combine_of(buckets.begin(), buckets.end()); + return cb; +} + +template +static int64_t get_percetile(void* arg) { + return ((LatencyRecorder*)arg)->latency_percentile( + (double)numerator / double(denominator)); +} + +static int64_t get_p1(void* arg) { + LatencyRecorder* lr = static_cast(arg); + return lr->latency_percentile(FLAGS_bvar_latency_p1 / 100.0); +} +static int64_t get_p2(void* arg) { + LatencyRecorder* lr = static_cast(arg); + return lr->latency_percentile(FLAGS_bvar_latency_p2 / 100.0); +} +static int64_t get_p3(void* arg) { + LatencyRecorder* lr = static_cast(arg); + return lr->latency_percentile(FLAGS_bvar_latency_p3 / 100.0); +} + +static Vector get_latencies(void *arg) { + std::unique_ptr cb( + combine((PercentileWindow*)arg)); + // NOTE: We don't show 99.99% since it's often significantly larger than + // other values and make other curves on the plotted graph small and + // hard to read. + Vector result; + result[0] = cb->get_number(FLAGS_bvar_latency_p1 / 100.0); + result[1] = cb->get_number(FLAGS_bvar_latency_p2 / 100.0); + result[2] = cb->get_number(FLAGS_bvar_latency_p3 / 100.0); + result[3] = cb->get_number(0.999); + return result; +} + +LatencyRecorderBase::LatencyRecorderBase(time_t window_size) + : _max_latency(0) + , _latency_window(&_latency, window_size) + , _max_latency_window(&_max_latency, window_size) + , _count(get_recorder_count, &_latency) + , _qps(get_window_recorder_qps, &_latency_window) + , _latency_percentile_window(&_latency_percentile, window_size) + , _latency_p1(get_p1, this) + , _latency_p2(get_p2, this) + , _latency_p3(get_p3, this) + , _latency_999(get_percetile<999, 1000>, this) + , _latency_9999(get_percetile<9999, 10000>, this) + , _latency_cdf(&_latency_percentile_window) + , _latency_percentiles(get_latencies, &_latency_percentile_window) +{} + +} // namespace detail + +Vector LatencyRecorder::latency_percentiles() const { + // const_cast here is just to adapt parameter type and safe. + return detail::get_latencies( + const_cast(&_latency_percentile_window)); +} + +int64_t LatencyRecorder::qps(time_t window_size) const { + detail::Sample s; + _latency_window.get_span(window_size, &s); + // Use floating point to avoid overflow. + if (s.time_us <= 0) { + return 0; + } + return detail::double_to_random_int(s.data.num * 1000000.0 / s.time_us); +} + +int LatencyRecorder::expose(const butil::StringPiece& prefix1, + const butil::StringPiece& prefix2) { + if (prefix2.empty()) { + LOG(ERROR) << "Parameter[prefix2] is empty"; + return -1; + } + butil::StringPiece prefix = prefix2; + // User may add "_latency" as the suffix, remove it. + if (prefix.ends_with("latency") || prefix.ends_with("Latency")) { + prefix.remove_suffix(7); + if (prefix.empty()) { + LOG(ERROR) << "Invalid prefix2=" << prefix2; + return -1; + } + } + std::string tmp; + if (!prefix1.empty()) { + tmp.reserve(prefix1.size() + prefix.size() + 1); + tmp.append(prefix1.data(), prefix1.size()); + tmp.push_back('_'); // prefix1 ending with _ is good. + tmp.append(prefix.data(), prefix.size()); + prefix = tmp; + } + + // set debug names for printing helpful error log. + _latency.set_debug_name(prefix); + _latency_percentile.set_debug_name(prefix); + + if (_latency_window.expose_as(prefix, "latency") != 0) { + return -1; + } + if (_max_latency_window.expose_as(prefix, "max_latency") != 0) { + return -1; + } + if (_count.expose_as(prefix, "count") != 0) { + return -1; + } + if (_qps.expose_as(prefix, "qps") != 0) { + return -1; + } + char namebuf[32]; + snprintf(namebuf, sizeof(namebuf), "latency_%d", (int)FLAGS_bvar_latency_p1); + if (_latency_p1.expose_as(prefix, namebuf, DISPLAY_ON_PLAIN_TEXT) != 0) { + return -1; + } + snprintf(namebuf, sizeof(namebuf), "latency_%d", (int)FLAGS_bvar_latency_p2); + if (_latency_p2.expose_as(prefix, namebuf, DISPLAY_ON_PLAIN_TEXT) != 0) { + return -1; + } + snprintf(namebuf, sizeof(namebuf), "latency_%u", (int)FLAGS_bvar_latency_p3); + if (_latency_p3.expose_as(prefix, namebuf, DISPLAY_ON_PLAIN_TEXT) != 0) { + return -1; + } + if (_latency_999.expose_as(prefix, "latency_999", DISPLAY_ON_PLAIN_TEXT) != 0) { + return -1; + } + if (_latency_9999.expose_as(prefix, "latency_9999") != 0) { + return -1; + } + if (_latency_cdf.expose_as(prefix, "latency_cdf", DISPLAY_ON_HTML) != 0) { + return -1; + } + if (_latency_percentiles.expose_as(prefix, "latency_percentiles", DISPLAY_ON_HTML) != 0) { + return -1; + } + if (FLAGS_save_series) { + snprintf(namebuf, sizeof(namebuf), "%d%%,%d%%,%d%%,99.9%%", + (int)FLAGS_bvar_latency_p1, (int)FLAGS_bvar_latency_p2, + (int)FLAGS_bvar_latency_p3); + CHECK_EQ(0, _latency_percentiles.set_vector_names(namebuf)); + } + return 0; +} + +int64_t LatencyRecorder::latency_percentile(double ratio) const { + std::unique_ptr cb( + combine((detail::PercentileWindow*)&_latency_percentile_window)); + return cb->get_number(ratio); +} + +void LatencyRecorder::hide() { + _latency_window.hide(); + _max_latency_window.hide(); + _count.hide(); + _qps.hide(); + _latency_p1.hide(); + _latency_p2.hide(); + _latency_p3.hide(); + _latency_999.hide(); + _latency_9999.hide(); + _latency_cdf.hide(); + _latency_percentiles.hide(); +} + +DEFINE_uint64(latency_scale_factor, 1, "latency scale factor, used by method status, etc., latency_us = latency * latency_scale_factor"); + +LatencyRecorder& LatencyRecorder::operator<<(int64_t latency) { + latency = latency / FLAGS_latency_scale_factor; + _latency << latency; + _max_latency << latency; + _latency_percentile << latency; + return *this; +} + +std::ostream& operator<<(std::ostream& os, const LatencyRecorder& rec) { + return os << "{latency=" << rec.latency() + << " max" << rec.window_size() << '=' << rec.max_latency() + << " qps=" << rec.qps() + << " count=" << rec.count() << '}'; +} + +} // namespace bvar diff --git a/src/bvar/latency_recorder.h b/src/bvar/latency_recorder.h new file mode 100644 index 0000000..874b117 --- /dev/null +++ b/src/bvar/latency_recorder.h @@ -0,0 +1,164 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2014/09/22 11:57:43 + +#ifndef BVAR_LATENCY_RECORDER_H +#define BVAR_LATENCY_RECORDER_H + +#include "bvar/recorder.h" +#include "bvar/reducer.h" +#include "bvar/passive_status.h" +#include "bvar/detail/percentile.h" + +namespace bvar { +namespace detail { + +class Percentile; +typedef Window RecorderWindow; +typedef Window, SERIES_IN_SECOND> MaxWindow; +typedef Window PercentileWindow; + +// NOTE: Always use int64_t in the interfaces no matter what the impl. is. + +class CDF : public Variable { +public: + explicit CDF(PercentileWindow* w); + ~CDF() override; + void describe(std::ostream& os, bool quote_string) const override; + int describe_series(std::ostream& os, const SeriesOptions& options) const override; +private: + PercentileWindow* _w; +}; + +// For mimic constructor inheritance. +class LatencyRecorderBase { +public: + explicit LatencyRecorderBase(time_t window_size); + time_t window_size() const { return _latency_window.window_size(); } +protected: + IntRecorder _latency; + Maxer _max_latency; + Percentile _latency_percentile; + + RecorderWindow _latency_window; + MaxWindow _max_latency_window; + PassiveStatus _count; + PassiveStatus _qps; + PercentileWindow _latency_percentile_window; + PassiveStatus _latency_p1; + PassiveStatus _latency_p2; + PassiveStatus _latency_p3; + PassiveStatus _latency_999; // 99.9% + PassiveStatus _latency_9999; // 99.99% + CDF _latency_cdf; + PassiveStatus > _latency_percentiles; +}; +} // namespace detail + +// Specialized structure to record latency. +// It's not a Variable, but it contains multiple bvar inside. +class LatencyRecorder : public detail::LatencyRecorderBase { + typedef detail::LatencyRecorderBase Base; +public: + LatencyRecorder() : Base(-1) {} + explicit LatencyRecorder(time_t window_size) : Base(window_size) {} + LatencyRecorder(const butil::StringPiece& prefix) : Base(-1) { + expose(prefix); + } + LatencyRecorder(const butil::StringPiece& prefix, + time_t window_size) : Base(window_size) { + expose(prefix); + } + LatencyRecorder(const butil::StringPiece& prefix1, + const butil::StringPiece& prefix2) : Base(-1) { + expose(prefix1, prefix2); + } + LatencyRecorder(const butil::StringPiece& prefix1, + const butil::StringPiece& prefix2, + time_t window_size) : Base(window_size) { + expose(prefix1, prefix2); + } + + ~LatencyRecorder() { hide(); } + + // Record the latency. + LatencyRecorder& operator<<(int64_t latency); + + // Expose all internal variables using `prefix' as prefix. + // Returns 0 on success, -1 otherwise. + // Example: + // LatencyRecorder rec; + // rec.expose("foo_bar_write"); // foo_bar_write_latency + // // foo_bar_write_max_latency + // // foo_bar_write_count + // // foo_bar_write_qps + // rec.expose("foo_bar", "read"); // foo_bar_read_latency + // // foo_bar_read_max_latency + // // foo_bar_read_count + // // foo_bar_read_qps + int expose(const butil::StringPiece& prefix) { + return expose(butil::StringPiece(), prefix); + } + int expose(const butil::StringPiece& prefix1, + const butil::StringPiece& prefix2); + + // Hide all internal variables, called in dtor as well. + void hide(); + + // Get the average latency in recent |window_size| seconds + // If |window_size| is absent, use the window_size to ctor. + int64_t latency(time_t window_size) const + { return _latency_window.get_value(window_size).get_average_int(); } + int64_t latency() const + { return _latency_window.get_value().get_average_int(); } + + // Get p1/p2/p3/99.9-ile latencies in recent window_size-to-ctor seconds. + Vector latency_percentiles() const; + + // Get the max latency in recent window_size-to-ctor seconds. + int64_t max_latency() const { return _max_latency_window.get_value(); } + + // Get the total number of recorded latencies. + int64_t count() const { return _latency.get_value().num; } + + // Get qps in recent |window_size| seconds. The `q' means latencies + // recorded by operator<<(). + // If |window_size| is absent, use the window_size to ctor. + int64_t qps(time_t window_size) const; + int64_t qps() const { return _qps.get_value(); } + + // Get |ratio|-ile latency in recent |window_size| seconds + // E.g. 0.99 means 99%-ile + int64_t latency_percentile(double ratio) const; + + // Get name of a sub-bvar. + const std::string& latency_name() const { return _latency_window.name(); } + const std::string& latency_percentiles_name() const + { return _latency_percentiles.name(); } + const std::string& latency_cdf_name() const { return _latency_cdf.name(); } + const std::string& max_latency_name() const + { return _max_latency_window.name(); } + const std::string& count_name() const { return _count.name(); } + const std::string& qps_name() const { return _qps.name(); } +}; + +std::ostream& operator<<(std::ostream& os, const LatencyRecorder&); + +} // namespace bvar + +#endif //BVAR_LATENCY_RECORDER_H diff --git a/src/bvar/multi_dimension.h b/src/bvar/multi_dimension.h new file mode 100644 index 0000000..ad352a0 --- /dev/null +++ b/src/bvar/multi_dimension.h @@ -0,0 +1,235 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2021/11/17 10:57:43 + +#ifndef BVAR_MULTI_DIMENSION_H +#define BVAR_MULTI_DIMENSION_H + +#include +#include +#include "butil/logging.h" // LOG +#include "butil/macros.h" // BAIDU_CASSERT +#include "butil/scoped_lock.h" // BAIDU_SCOPE_LOCK +#include "butil/containers/doubly_buffered_data.h" // DBD +#include "butil/containers/flat_map.h" // butil::FlatMap +#include "butil/strings/string_piece.h" +#include "bvar/mvariable.h" + +namespace bvar { + +// KeyType requirements: +// 1. KeyType must be a container type with iterator, e.g. std::vector, std::list, std::set. +// 2. KeyType::value_type must be std::string. +// 3. KeyType::size() returns the number of labels. +// 4. KeyType::push_back() adds a label to the end of the container. +// +// If `Shared' is false, `get_stats' returns a raw pointer, +// `delete_stats' and `clear_stats' are not thread safe. +// If `Shared' is true, `get_stats` returns a shared_ptr, +// `delete_stats' and `clear_stats' are thread safe. +// Note: The shared mode may be less performant than the non-shared mode. +template , bool Shared = false> +class MultiDimension : public MVariable { + typedef std::shared_ptr shared_value_type; +public: + enum STATS_OP { + READ_ONLY, + READ_OR_INSERT, + }; + + typedef KeyType key_type; + typedef T value_type; + typedef typename std::conditional::type value_ptr_type; + typedef MVariable Base; + + struct KeyHash { + template + size_t operator() (const K& key) const { + size_t hash_value = 0; + for (auto& k : key) { + hash_value += BUTIL_HASH_NAMESPACE::hash()( + butil::StringPiece(k)); + } + return hash_value; + } + }; + + struct KeyEqualTo { + template + bool operator()(const key_type& k1, const K& k2) const { + return k1.size() == k2.size() && + std::equal(k1.cbegin(), k1.cend(), k2.cbegin()); + } + }; + + typedef value_ptr_type op_value_type; + typedef butil::FlatMap MetricMap; + + typedef typename MetricMap::const_iterator MetricMapConstIterator; + typedef butil::DoublyBufferedData MetricMapDBD; + typedef typename MetricMapDBD::ScopedPtr MetricMapScopedPtr; + + explicit MultiDimension(const key_type& labels); + + MultiDimension(const butil::StringPiece& name, + const key_type& labels); + + MultiDimension(const butil::StringPiece& prefix, + const butil::StringPiece& name, + const key_type& labels); + + ~MultiDimension() override; + + // Implement this method to print the variable into ostream. + void describe(std::ostream& os) override; + + // Dump real bvar pointer + size_t dump(Dumper* dumper, const DumpOptions* options) override { + return dump_impl(dumper, options); + } + + // Get real bvar pointer object + // Return real bvar pointer on success, NULL otherwise. + // K requirements: + // 1. K must be a container type with iterator, + // e.g. std::vector, std::list, std::set, std::array. + // 2. K::value_type must be able to convert to std::string and butil::StringPiece + // through operator std::string() function and operator butil::StringPiece() function. + // 3. K::value_type must be able to compare with std::string. + // + // Returns a shared_ptr if `Shared' is true, otherwise returns a raw pointer. + template + value_ptr_type get_stats(const K& labels_value) { + return get_stats_impl(labels_value, READ_OR_INSERT); + } + + // `delete_stats' and `clear_stats' are thread safe + // if `Shared' is true, otherwise not. + // Remove stat so those not count and dump + template + void delete_stats(const K& labels_value); + + // Remove all stat + void clear_stats(); + + // True if bvar pointer exists + template + bool has_stats(const K& labels_value); + + // Get number of stats + size_t count_stats(); + + // Put name of all stats label into `names' + void list_stats(std::vector* names); + + void set_max_stats_count(size_t max_stats_count) { + _max_stats_count = std::max(max_stats_count, max_stats_count); + } + +#ifdef UNIT_TEST + // Get real bvar pointer object + // Return real bvar pointer if labels_name exist, NULL otherwise. + // CAUTION!!! Just For Debug!!! + template + value_ptr_type get_stats_read_only(const K& labels_value) { + return get_stats_impl(labels_value); + } + + // Get real bvar pointer object + // Return real bvar pointer if labels_name exist, otherwise(not exist) create bvar pointer. + // CAUTION!!! Just For Debug!!! + template + value_ptr_type get_stats_read_or_insert(const K& labels_value, bool* do_write = NULL) { + return get_stats_impl(labels_value, READ_OR_INSERT, do_write); + } +#endif + +private: + template + value_ptr_type get_stats_impl(const K& labels_value); + + template + value_ptr_type get_stats_impl( + const K& labels_value, STATS_OP stats_op, bool* do_write = NULL); + + template + static typename std::enable_if::value>::type + insert_metrics_map(MetricMap& bg, const K& labels_value, op_value_type metric) { + bg.insert(labels_value, metric); + } + + template + static typename std::enable_if::value>::type + insert_metrics_map(MetricMap& bg, const K& labels_value, op_value_type metric) { + // key_type::value_type must be able to convert to std::string. + key_type labels_value_str(labels_value.cbegin(), labels_value.cend()); + bg.insert(labels_value_str, metric); + } + + template + typename std::enable_if::value, size_t>::type + dump_impl(Dumper* dumper, const DumpOptions* options); + + template + typename std::enable_if::value, size_t>::type + dump_impl(Dumper* dumper, const DumpOptions* options); + + void make_dump_key(std::ostream& os, const key_type& labels_value, + const std::string& suffix = "", int quantile = 0); + + void make_labels_kvpair_string( + std::ostream& os, const key_type& labels_value, int quantile); + + + template + bool is_valid_lables_value(const K& labels_value) const; + + // Remove all stats so those not count and dump + void delete_stats(); + + static size_t init_flatmap(MetricMap& bg); + + // If Shared is true, return std::shared_ptr, otherwise return raw pointer. + template + typename std::enable_if::type new_value() { + return std::make_shared(); + } + template + typename std::enable_if::type new_value() { + return new value_type(); + } + + // If Shared is true, reset std::shared_ptr, otherwise delete raw pointer. + template + typename std::enable_if::type delete_value(value_ptr_type& v) { + v.reset(); + } + template + typename std::enable_if::type delete_value(value_ptr_type& v) { + delete v; + } + + size_t _max_stats_count; + MetricMapDBD _metric_map; +}; + +} // namespace bvar + +#include "bvar/multi_dimension_inl.h" + +#endif // BVAR_MULTI_DIMENSION_H diff --git a/src/bvar/multi_dimension_inl.h b/src/bvar/multi_dimension_inl.h new file mode 100644 index 0000000..c407567 --- /dev/null +++ b/src/bvar/multi_dimension_inl.h @@ -0,0 +1,398 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2021/11/17 10:57:43 + +#ifndef BVAR_MULTI_DIMENSION_INL_H +#define BVAR_MULTI_DIMENSION_INL_H + +#include +#include "butil/compiler_specific.h" + +namespace bvar { + +DECLARE_int32(bvar_latency_p1); +DECLARE_int32(bvar_latency_p2); +DECLARE_int32(bvar_latency_p3); +DECLARE_uint32(max_multi_dimension_stats_count); + +static const std::string ALLOW_UNUSED METRIC_TYPE_COUNTER = "counter"; +static const std::string ALLOW_UNUSED METRIC_TYPE_SUMMARY = "summary"; +static const std::string ALLOW_UNUSED METRIC_TYPE_HISTOGRAM = "histogram"; +static const std::string ALLOW_UNUSED METRIC_TYPE_GAUGE = "gauge"; + +template +MultiDimension::MultiDimension(const key_type& labels) + : Base(labels) + , _max_stats_count(FLAGS_max_multi_dimension_stats_count) { + _metric_map.Modify(init_flatmap); +} + +template +MultiDimension::MultiDimension(const butil::StringPiece& name, + const key_type& labels) + : MultiDimension(labels) { + this->expose(name); +} + +template +MultiDimension::MultiDimension(const butil::StringPiece& prefix, + const butil::StringPiece& name, + const key_type& labels) + : MultiDimension(labels) { + this->expose_as(prefix, name); +} + +template +MultiDimension::~MultiDimension() { + this->hide(); + delete_stats(); +} + +template +size_t MultiDimension::init_flatmap(MetricMap& bg) { + // size = 1 << 13 + CHECK_EQ(0, bg.init(8192, 80)); + return 1; +} + +template +size_t MultiDimension::count_stats() { + MetricMapScopedPtr metric_map_ptr; + if (_metric_map.Read(&metric_map_ptr) != 0) { + LOG(ERROR) << "Fail to read dbd"; + return 0; + } + return metric_map_ptr->size(); +} + +template +template +void MultiDimension::delete_stats(const K& labels_value) { + if (is_valid_lables_value(labels_value)) { + // Because there are two copies(foreground and background) in DBD, + // we need to use an empty tmp_metric, get the deleted value of + // second copy into tmp_metric, which can prevent the bvar object + // from being deleted twice. + op_value_type tmp_metric = NULL; + auto erase_fn = [&labels_value, &tmp_metric](MetricMap& bg) { + return bg.erase(labels_value, &tmp_metric); + }; + _metric_map.Modify(erase_fn); + if (tmp_metric) { + delete_value(tmp_metric); + } + } +} + +template +void MultiDimension::delete_stats() { + // Because there are two copies(foreground and background) in DBD, we need to use an empty tmp_map, + // swap two copies with empty, and get the value of second copy into tmp_map, + // then traversal tmp_map and delete bvar object, + // which can prevent the bvar object from being deleted twice. + MetricMap tmp_map; + CHECK_EQ(0, tmp_map.init(8192, 80)); + auto clear_fn = [&tmp_map](MetricMap& map) -> size_t { + if (!tmp_map.empty()) { + tmp_map.clear(); + } + tmp_map.swap(map); + return 1; + }; + int ret = _metric_map.Modify(clear_fn); + CHECK_EQ(1, ret); + for (auto& kv : tmp_map) { + delete_value(kv.second); + } +} + +template +void MultiDimension::list_stats(std::vector* names) { + if (names == NULL) { + return; + } + names->clear(); + MetricMapScopedPtr metric_map_ptr; + if (_metric_map.Read(&metric_map_ptr) != 0) { + LOG(ERROR) << "Fail to read dbd"; + return; + } + names->reserve(metric_map_ptr->size()); + for (auto it = metric_map_ptr->begin(); it != metric_map_ptr->end(); ++it) { + names->emplace_back(it->first); + } +} + +template +template +typename MultiDimension::value_ptr_type +MultiDimension::get_stats_impl(const K& labels_value) { + if (!is_valid_lables_value(labels_value)) { + return NULL; + } + MetricMapScopedPtr metric_map_ptr; + if (_metric_map.Read(&metric_map_ptr) != 0) { + LOG(ERROR) << "Fail to read dbd"; + return NULL; + } + + auto it = metric_map_ptr->seek(labels_value); + if (NULL == it) { + return NULL; + } + return (*it); +} + +template +template +typename MultiDimension::value_ptr_type +MultiDimension::get_stats_impl( + const K& labels_value, STATS_OP stats_op, bool* do_write) { + if (!is_valid_lables_value(labels_value)) { + return NULL; + } + { + MetricMapScopedPtr metric_map_ptr; + if (0 != _metric_map.Read(&metric_map_ptr)) { + LOG(ERROR) << "Fail to read dbd"; + return NULL; + } + + auto it = metric_map_ptr->seek(labels_value); + if (NULL != it) { + return (*it); + } else if (READ_ONLY == stats_op) { + return NULL; + } + + if (metric_map_ptr->size() > _max_stats_count) { + LOG(ERROR) << "Too many stats seen, overflow detected, max stats count=" + << _max_stats_count; + return NULL; + } + } + + // Because DBD has two copies(foreground and background) MetricMap, both copies need to be modified, + // In order to avoid new duplicate bvar object, need use cache_metric to cache the new bvar object, + // In this way, when modifying the second copy, can directly use the cache_metric bvar object. + op_value_type cache_metric = NULL; + auto insert_fn = [this, &labels_value, &cache_metric, &do_write](MetricMap& bg) { + auto bg_metric = bg.seek(labels_value); + if (NULL != bg_metric) { + cache_metric = *bg_metric; + return 0; + } + if (do_write) { + *do_write = true; + } + + if (NULL == cache_metric) { + cache_metric = new_value(); + } + insert_metrics_map(bg, labels_value, cache_metric); + return 1; + }; + _metric_map.Modify(insert_fn); + return cache_metric; +} + +template +void MultiDimension::clear_stats() { + delete_stats(); +} + +template +template +bool MultiDimension::has_stats(const K& labels_value) { + return get_stats_impl(labels_value) != NULL; +} + +template +template +typename std::enable_if::value, size_t>::type +MultiDimension::dump_impl(Dumper* dumper, const DumpOptions* options) { + std::vector label_names; + list_stats(&label_names); + if (label_names.empty() || !dumper->dump_comment(this->name(), METRIC_TYPE_GAUGE)) { + return 0; + } + size_t n = 0; + for (auto &label_name : label_names) { + value_ptr_type bvar = get_stats_impl(label_name); + if (NULL == bvar) { + continue; + } + std::ostringstream oss; + bvar->describe(oss, options->quote_string); + std::ostringstream oss_key; + make_dump_key(oss_key, label_name); + if (!dumper->dump_mvar(oss_key.str(), oss.str())) { + continue; + } + n++; + } + return n; +} + +template +template +typename std::enable_if::value, size_t>::type +MultiDimension::dump_impl(Dumper* dumper, const DumpOptions*) { + std::vector label_names; + list_stats(&label_names); + if (label_names.empty()) { + return 0; + } + size_t n = 0; + // To meet prometheus specification, we must guarantee no second TYPE line for one metric name + + // latency comment + dumper->dump_comment(this->name() + "_latency", METRIC_TYPE_GAUGE); + for (auto &label_name : label_names) { + bvar::LatencyRecorder* bvar = get_stats_impl(label_name); + if (!bvar) { + continue; + } + + // latency + std::ostringstream oss_latency_key; + make_dump_key(oss_latency_key, label_name, "_latency"); + if (dumper->dump_mvar(oss_latency_key.str(), std::to_string(bvar->latency()))) { + n++; + } + // latency_percentiles + // p1/p2/p3 + int latency_percentiles[3] {FLAGS_bvar_latency_p1, FLAGS_bvar_latency_p2, FLAGS_bvar_latency_p3}; + for (auto lp : latency_percentiles) { + std::ostringstream oss_lp_key; + make_dump_key(oss_lp_key, label_name, "_latency", lp); + if (dumper->dump_mvar(oss_lp_key.str(), std::to_string(bvar->latency_percentile(lp / 100.0)))) { + n++; + } + } + // 999 + std::ostringstream oss_p999_key; + make_dump_key(oss_p999_key, label_name, "_latency", 999); + if (dumper->dump_mvar(oss_p999_key.str(), std::to_string(bvar->latency_percentile(0.999)))) { + n++; + } + // 9999 + std::ostringstream oss_p9999_key; + make_dump_key(oss_p9999_key, label_name, "_latency", 9999); + if (dumper->dump_mvar(oss_p9999_key.str(), std::to_string(bvar->latency_percentile(0.9999)))) { + n++; + } + } + + // max_latency comment + dumper->dump_comment(this->name() + "_max_latency", METRIC_TYPE_GAUGE); + for (auto &label_name : label_names) { + LatencyRecorder* bvar = get_stats_impl(label_name); + if (NULL == bvar) { + continue; + } + std::ostringstream oss_max_latency_key; + make_dump_key(oss_max_latency_key, label_name, "_max_latency"); + if (dumper->dump_mvar(oss_max_latency_key.str(), std::to_string(bvar->max_latency()))) { + n++; + } + } + + // qps comment + dumper->dump_comment(this->name() + "_qps", METRIC_TYPE_GAUGE); + for (auto &label_name : label_names) { + LatencyRecorder* bvar = get_stats_impl(label_name); + if (NULL == bvar) { + continue; + } + std::ostringstream oss_qps_key; + make_dump_key(oss_qps_key, label_name, "_qps"); + if (dumper->dump_mvar(oss_qps_key.str(), std::to_string(bvar->qps()))) { + n++; + } + } + + // count comment + dumper->dump_comment(this->name() + "_count", METRIC_TYPE_COUNTER); + for (auto &label_name : label_names) { + LatencyRecorder* bvar = get_stats_impl(label_name); + if (NULL == bvar) { + continue; + } + std::ostringstream oss_count_key; + make_dump_key(oss_count_key, label_name, "_count"); + if (dumper->dump_mvar(oss_count_key.str(), std::to_string(bvar->count()))) { + n++; + } + } + return n; +} + +template +void MultiDimension::make_dump_key(std::ostream& os, const key_type& labels_value, + const std::string& suffix, int quantile) { + os << this->name(); + if (!suffix.empty()) { + os << suffix; + } + make_labels_kvpair_string(os, labels_value, quantile); +} + +template +void MultiDimension::make_labels_kvpair_string( + std::ostream& os, const key_type& labels_value, int quantile) { + os << "{"; + auto label_key = this->_labels.cbegin(); + auto label_value = labels_value.cbegin(); + char comma[2] = {'\0', '\0'}; + for (; label_key != this->_labels.cend() && label_value != labels_value.cend(); + label_key++, label_value++) { + os << comma << label_key->c_str() << "=\"" << label_value->c_str() << "\""; + comma[0] = ','; + } + if (quantile > 0) { + os << comma << "quantile=\"" << quantile << "\""; + } + os << "}"; +} + +template +template +bool MultiDimension::is_valid_lables_value(const K& labels_value) const { + if (this->count_labels() != labels_value.size()) { + LOG(ERROR) << "Invalid labels count" << this->count_labels() + << " != " << labels_value.size(); + return false; + } + return true; +} + +template +void MultiDimension::describe(std::ostream& os) { + os << "{\"name\" : \"" << this->name() << "\", \"labels\" : ["; + char comma[3] = {'\0', ' ', '\0'}; + for (auto& label : this->_labels) { + os << comma << "\"" << label << "\""; + comma[0] = ','; + } + os << "], \"stats_count\" : " << count_stats() << "}"; +} + +} // namespace bvar + +#endif // BVAR_MULTI_DIMENSION_INL_H diff --git a/src/bvar/mvariable.cpp b/src/bvar/mvariable.cpp new file mode 100644 index 0000000..5503748 --- /dev/null +++ b/src/bvar/mvariable.cpp @@ -0,0 +1,271 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2021/11/17 14:37:53 + +#include +#include +#include "butil/logging.h" // LOG +#include "butil/errno.h" // berror +#include "butil/containers/flat_map.h" // butil::FlatMap +#include "butil/scoped_lock.h" // BAIDU_SCOPE_LOCK +#include "butil/file_util.h" // butil::FilePath +#include "butil/reloadable_flags.h" +#include "bvar/variable.h" +#include "bvar/mvariable.h" + +namespace bvar { + +DECLARE_bool(bvar_abort_on_same_name); + +extern bool s_bvar_may_abort; + +static bool validator_bvar_max_multi_dimension_metric_number(const char*, int32_t v) { + if (v < 1) { + LOG(ERROR) << "Invalid bvar_max_multi_dimension_metric_number=" << v; + return false; + } + return true; +} + +DEFINE_int32(bvar_max_multi_dimension_metric_number, 1024, "Max number of multi dimension"); +BUTIL_VALIDATE_GFLAG(bvar_max_multi_dimension_metric_number, + validator_bvar_max_multi_dimension_metric_number); + +static bool validator_bvar_max_dump_multi_dimension_metric_number(const char*, int32_t v) { + if (v < 0) { + LOG(ERROR) << "Invalid bvar_max_dump_multi_dimension_metric_number=" << v; + return false; + } + return true; +} +DEFINE_int32(bvar_max_dump_multi_dimension_metric_number, 1024, + "Max number of multi dimension metric number to dump by prometheus rpc service"); +BUTIL_VALIDATE_GFLAG(bvar_max_dump_multi_dimension_metric_number, + validator_bvar_max_dump_multi_dimension_metric_number); + +static bool validator_max_multi_dimension_stats_count(const char*, uint32_t v) { + if (v < 1) { + LOG(ERROR) << "Invalid max_multi_dimension_stats_count=" << v; + return false; + } + return true; +} +DEFINE_uint32(max_multi_dimension_stats_count, 20000, "Max stats count of a multi dimension metric."); +BUTIL_VALIDATE_GFLAG(max_multi_dimension_stats_count, + validator_max_multi_dimension_stats_count); + +class MVarEntry { +public: + MVarEntry() : var(NULL) {} + + MVariableBase* var; +}; + +typedef butil::FlatMap MVarMap; + +struct MVarMapWithLock : public MVarMap { + pthread_mutex_t mutex; + + MVarMapWithLock() { + if (init(256) != 0) { + LOG(WARNING) << "Fail to init"; + } + pthread_mutex_init(&mutex, NULL); + } +}; + +// We have to initialize global map on need because bvar is possibly used +// before main(). +static pthread_once_t s_mvar_map_once = PTHREAD_ONCE_INIT; +static MVarMapWithLock* s_mvar_map = NULL; + +static void init_mvar_map() { + // It's probably slow to initialize all sub maps, but rpc often expose + // variables before user. So this should not be an issue to users. + s_mvar_map = new MVarMapWithLock(); +} + +inline MVarMapWithLock& get_mvar_map() { + pthread_once(&s_mvar_map_once, init_mvar_map); + return *s_mvar_map; +} + +MVariableBase::~MVariableBase() { + CHECK(!hide()) << "Subclass of MVariableBase MUST call hide() manually in their " + "dtors to avoid displaying a variable that is just destructing"; +} + +std::string MVariableBase::get_description() { + std::ostringstream os; + describe(os); + return os.str(); +} + +int MVariableBase::describe_exposed(const std::string& name, + std::ostream& os) { + MVarMapWithLock& m = get_mvar_map(); + BAIDU_SCOPED_LOCK(m.mutex); + MVarEntry* entry = m.seek(name); + if (entry == NULL) { + return -1; + } + entry->var->describe(os); + return 0; +} + +std::string MVariableBase::describe_exposed(const std::string& name) { + std::ostringstream oss; + if (describe_exposed(name, oss) == 0) { + return oss.str(); + } + return std::string(); +} + +int MVariableBase::expose_impl(const butil::StringPiece& prefix, + const butil::StringPiece& name) { + if (name.empty()) { + LOG(ERROR) << "Parameter[name] is empty"; + return -1; + } + // NOTE: It's impossible to atomically erase from a submap and insert into + // another submap without a global lock. When the to-be-exposed name + // already exists, there's a chance that we can't insert back previous + // name. But it should be fine generally because users are unlikely to + // expose a variable more than once and calls to expose() are unlikely + // to contend heavily. + + // remove previous pointer from the map if needed. + hide(); + + // Build the name. + _name.clear(); + _name.reserve((prefix.size() + name.size()) * 5 / 4); + if (!prefix.empty()) { + to_underscored_name(&_name, prefix); + if (!_name.empty() && butil::back_char(_name) != '_') { + _name.push_back('_'); + } + } + to_underscored_name(&_name, name); + + if (count_exposed() > (size_t)FLAGS_bvar_max_multi_dimension_metric_number) { + LOG(ERROR) << "Too many metric seen, overflow detected, max metric count:" << FLAGS_bvar_max_multi_dimension_metric_number; + return -1; + } + + MVarMapWithLock& m = get_mvar_map(); + { + BAIDU_SCOPED_LOCK(m.mutex); + MVarEntry* entry = m.seek(_name); + if (entry == NULL) { + entry = &m[_name]; + entry->var = this; + return 0; + } + } + + RELEASE_ASSERT_VERBOSE(!FLAGS_bvar_abort_on_same_name, + "Abort due to name conflict"); + if (!s_bvar_may_abort) { + // Mark name conflict occurs, If this conflict happens before + // initialization of bvar_abort_on_same_name, the validator will + // abort the program if needed. + s_bvar_may_abort = true; + } + + LOG(WARNING) << "Already exposed `" << _name << "' whose describe is`" + << get_description() << "'"; + _name.clear(); + return 0; +} + +bool MVariableBase::hide() { + if (_name.empty()) { + return false; + } + + MVarMapWithLock& m = get_mvar_map(); + BAIDU_SCOPED_LOCK(m.mutex); + MVarEntry* entry = m.seek(_name); + if (entry) { + CHECK_EQ(1UL, m.erase(_name)); + } else { + CHECK(false) << "`" << _name << "' must exist"; + } + _name.clear(); + return true; +} + +#ifdef UNIT_TEST +void MVariableBase::hide_all() { + MVarMapWithLock& m = get_mvar_map(); + BAIDU_SCOPED_LOCK(m.mutex); + m.clear(); +} +#endif // end UNIT_TEST + +size_t MVariableBase::count_exposed() { + MVarMapWithLock& m = get_mvar_map(); + BAIDU_SCOPED_LOCK(m.mutex); + return m.size(); +} + +void MVariableBase::list_exposed(std::vector* names) { + if (names == NULL) { + return; + } + + names->clear(); + + MVarMapWithLock& mvar_map = get_mvar_map(); + BAIDU_SCOPED_LOCK(mvar_map.mutex); + names->reserve(mvar_map.size()); + for (MVarMap::const_iterator it = mvar_map.begin(); it != mvar_map.end(); ++it) { + names->push_back(it->first); + } +} + +size_t MVariableBase::dump_exposed(Dumper* dumper, const DumpOptions* options) { + if (NULL == dumper) { + LOG(ERROR) << "Parameter[dumper] is NULL"; + return -1; + } + DumpOptions opt; + if (options) { + opt = *options; + } + std::vector mvars; + list_exposed(&mvars); + size_t n = 0; + for (auto& mvar : mvars) { + MVarMapWithLock& m = get_mvar_map(); + BAIDU_SCOPED_LOCK(m.mutex); + MVarEntry* entry = m.seek(mvar); + if (entry) { + n += entry->var->dump(dumper, &opt); + } + if (n > static_cast(FLAGS_bvar_max_dump_multi_dimension_metric_number)) { + LOG(WARNING) << "truncated because of exceed max dump multi dimension label number[" + << FLAGS_bvar_max_dump_multi_dimension_metric_number << "]"; + break; + } + } + return n; +} + +} // namespace bvar diff --git a/src/bvar/mvariable.h b/src/bvar/mvariable.h new file mode 100644 index 0000000..2271955 --- /dev/null +++ b/src/bvar/mvariable.h @@ -0,0 +1,136 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2021/11/17 14:37:53 + +#ifndef BVAR_MVARIABLE_H +#define BVAR_MVARIABLE_H + +#include // std::ostream +#include // std::ostringstream +#include // std::list +#include // std::string +#include "butil/macros.h" // DISALLOW_COPY_AND_ASSIGN +#include "butil/strings/string_piece.h" // butil::StringPiece + +namespace bvar { + +class Dumper; +struct DumpOptions; + +class MVariableBase { +public: + MVariableBase() = default; + + virtual ~MVariableBase(); + + // Implement this method to print the mvariable info into ostream. + virtual void describe(std::ostream&) = 0; + + // string form of describe(). + std::string get_description(); + + // Get mvariable name + const std::string& name() const { return _name; } + + // Expose this mvariable globally so that it's counted in following + // functions: + // list_exposed + // count_exposed + // Return 0 on success, -1 otherwise. + int expose(const butil::StringPiece& name) { + return expose_impl(butil::StringPiece(), name); + } + + // Expose this mvariable globally with a prefix + // Return 0 on success, -1 otherwise. + int expose_as(const butil::StringPiece& prefix, + const butil::StringPiece& name) { + return expose_impl(prefix, name); + } + + // Dump this mvariable + virtual size_t dump(Dumper* dumper, const DumpOptions* options) = 0; + + // Hide this variable so that it's not counted in *_exposed functions. + // Returns false if this variable is already hidden. + // CAUTION!! Subclasses must call hide() manually to avoid displaying + // a variable that is just destructing. + bool hide(); + + // Get number of exposed mvariables. + static size_t count_exposed(); + + // Put names of all exposed mvariable into `name'. + static void list_exposed(std::vector *names); + + // Find all exposed mvariables matching `white_wildcards' but + // `black_wildcards' and send them to `dumper'. + // Use default options when `options' is NULL. + // Return number of dumped mvariables, -1 on error. + static size_t dump_exposed(Dumper* dumper, const DumpOptions* options); + + // Find an exposed mvariable by `name' and put its description into `os'. + // Returns 0 on found, -1 otherwise. + static int describe_exposed(const std::string& name, + std::ostream& os); + + // String form. Returns empty string when not found. + static std::string describe_exposed(const std::string& name); + +#ifdef UNIT_TEST + // Hide all mvariables so that all mvariables not counted in following + // functions: + // list_exposed + // count_exposed + // CAUTION!!! Just For Debug!!! + static void hide_all(); +#endif + +protected: + int expose_impl(const butil::StringPiece& prefix, + const butil::StringPiece& name); + +protected: + std::string _name; + + // mbvar uses bvar, bvar uses TLS, thus copying/assignment need to copy TLS stuff as well, + // which is heavy. We disable copying/assignment now. + DISALLOW_COPY_AND_ASSIGN(MVariableBase); +}; + +template +class MVariable : public MVariableBase { +public: + explicit MVariable(const KeyType& labels) : _labels(labels.cbegin(), labels.cend()) { + static_assert(std::is_same::value, + "value_type of KeyType must be std::string"); + } + + // Get mvariable labels + const KeyType& labels() const { return _labels; } + + // Get number of mvariable labels + size_t count_labels() const { return _labels.size(); } + +protected: + KeyType _labels; +}; + +} // namespace bvar + +#endif // BVAR_MVARIABLE_H diff --git a/src/bvar/passive_status.h b/src/bvar/passive_status.h new file mode 100644 index 0000000..eb4900d --- /dev/null +++ b/src/bvar/passive_status.h @@ -0,0 +1,280 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date 2014/09/22 11:57:43 + +#ifndef BVAR_PASSIVE_STATUS_H +#define BVAR_PASSIVE_STATUS_H + +#include "bvar/variable.h" +#include "bvar/reducer.h" + +namespace bvar { + +// Display a updated-by-need value. This is done by passing in an user callback +// which is called to produce the value. +// Example: +// int print_number(void* arg) { +// ... +// return 5; +// } +// +// // number1 : 5 +// bvar::PassiveStatus status1("number1", print_number, arg); +// +// // foo_number2 : 5 +// bvar::PassiveStatus status2("Foo", "number2", print_number, arg); +template +class PassiveStatus : public Variable { +public: + typedef Tp value_type; + typedef detail::ReducerSampler, + detail::MinusFrom > sampler_type; + struct PlaceHolderOp { + void operator()(Tp&, const Tp&) const {} + }; + static const bool ADDITIVE = (butil::is_integral::value || + butil::is_floating_point::value || + is_vector::value); + class SeriesSampler : public detail::Sampler { + public: + typedef typename butil::conditional< + ADDITIVE, detail::AddTo, PlaceHolderOp>::type Op; + explicit SeriesSampler(PassiveStatus* owner) + : _owner(owner), _vector_names(NULL), _series(Op()) {} + ~SeriesSampler() { + delete _vector_names; + } + void take_sample() override { _series.append(_owner->get_value()); } + void describe(std::ostream& os) { _series.describe(os, _vector_names); } + void set_vector_names(const std::string& names) { + if (_vector_names == NULL) { + _vector_names = new std::string; + } + *_vector_names = names; + } + private: + PassiveStatus* _owner; + std::string* _vector_names; + detail::Series _series; + }; + +public: + // NOTE: You must be very careful about lifetime of `arg' which should be + // valid during lifetime of PassiveStatus. + PassiveStatus(const butil::StringPiece& name, + Tp (*getfn)(void*), void* arg) + : _getfn(getfn) + , _arg(arg) + , _sampler(NULL) + , _series_sampler(NULL) { + expose(name); + } + + PassiveStatus(const butil::StringPiece& prefix, + const butil::StringPiece& name, + Tp (*getfn)(void*), void* arg) + : _getfn(getfn) + , _arg(arg) + , _sampler(NULL) + , _series_sampler(NULL) { + expose_as(prefix, name); + } + + PassiveStatus(Tp (*getfn)(void*), void* arg) + : _getfn(getfn) + , _arg(arg) + , _sampler(NULL) + , _series_sampler(NULL) { + } + + ~PassiveStatus() { + hide(); + if (_sampler) { + _sampler->destroy(); + _sampler = NULL; + } + if (_series_sampler) { + _series_sampler->destroy(); + _series_sampler = NULL; + } + } + + int set_vector_names(const std::string& names) { + if (_series_sampler) { + _series_sampler->set_vector_names(names); + return 0; + } + return -1; + } + + void describe(std::ostream& os, bool /*quote_string*/) const override { + os << get_value(); + } + +#ifdef BAIDU_INTERNAL + void get_value(boost::any* value) const override { + if (_getfn) { + *value = _getfn(_arg); + } else { + *value = Tp(); + } + } +#endif + + Tp get_value() const { + return (_getfn ? _getfn(_arg) : Tp()); + } + + sampler_type* get_sampler() { + if (NULL == _sampler) { + _sampler = new sampler_type(this); + _sampler->schedule(); + } + return _sampler; + } + + detail::AddTo op() const { return detail::AddTo(); } + detail::MinusFrom inv_op() const { return detail::MinusFrom(); } + + int describe_series(std::ostream& os, const SeriesOptions& options) const override { + if (_series_sampler == NULL) { + return 1; + } + if (!options.test_only) { + _series_sampler->describe(os); + } + return 0; + } + + Tp reset() { + CHECK(false) << "PassiveStatus::reset() should never be called, abort"; + abort(); + } + +protected: + int expose_impl(const butil::StringPiece& prefix, + const butil::StringPiece& name, + DisplayFilter display_filter) override { + const int rc = Variable::expose_impl(prefix, name, display_filter); + if (ADDITIVE && + rc == 0 && + _series_sampler == NULL && + FLAGS_save_series) { + _series_sampler = new SeriesSampler(this); + _series_sampler->schedule(); + } + return rc; + } + +private: + Tp (*_getfn)(void*); + void* _arg; + sampler_type* _sampler; + SeriesSampler* _series_sampler; +}; + +// ccover g++ may complain about ADDITIVE is undefined unless it's +// explicitly declared here. +template const bool PassiveStatus::ADDITIVE; + +// Specialize std::string for using std::ostream& as a more friendly +// interface for user's callback. +template <> +class PassiveStatus : public Variable { +public: + // NOTE: You must be very careful about lifetime of `arg' which should be + // valid during lifetime of PassiveStatus. + PassiveStatus(const butil::StringPiece& name, + void (*print)(std::ostream&, void*), void* arg) + : _print(print), _arg(arg) { + expose(name); + } + + PassiveStatus(const butil::StringPiece& prefix, + const butil::StringPiece& name, + void (*print)(std::ostream&, void*), void* arg) + : _print(print), _arg(arg) { + expose_as(prefix, name); + } + + PassiveStatus(void (*print)(std::ostream&, void*), void* arg) + : _print(print), _arg(arg) {} + + ~PassiveStatus() { + hide(); + } + + void describe(std::ostream& os, bool quote_string) const override { + if (quote_string) { + if (_print) { + os << '"'; + _print(os, _arg); + os << '"'; + } else { + os << "\"null\""; + } + } else { + if (_print) { + _print(os, _arg); + } else { + os << "null"; + } + } + } + +private: + void (*_print)(std::ostream&, void*); + void* _arg; +}; + +template +class BasicPassiveStatus : public PassiveStatus { +public: + BasicPassiveStatus(const butil::StringPiece& name, + Tp (*getfn)(void*), void* arg) + : PassiveStatus(name, getfn, arg) {} + + BasicPassiveStatus(const butil::StringPiece& prefix, + const butil::StringPiece& name, + Tp (*getfn)(void*), void* arg) + : PassiveStatus(prefix, name, getfn, arg) {} + + BasicPassiveStatus(Tp (*getfn)(void*), void* arg) + : PassiveStatus(getfn, arg) {} +}; + +template <> +class BasicPassiveStatus : public PassiveStatus { +public: + BasicPassiveStatus(const butil::StringPiece& name, + void (*print)(std::ostream&, void*), void* arg) + : PassiveStatus(name, print, arg) {} + + BasicPassiveStatus(const butil::StringPiece& prefix, + const butil::StringPiece& name, + void (*print)(std::ostream&, void*), void* arg) + : PassiveStatus(prefix, name, print, arg) {} + + BasicPassiveStatus(void (*print)(std::ostream&, void*), void* arg) + : PassiveStatus(print, arg) {} +}; + + +} // namespace bvar + +#endif //BVAR_PASSIVE_STATUS_H diff --git a/src/bvar/recorder.h b/src/bvar/recorder.h new file mode 100644 index 0000000..b28b637 --- /dev/null +++ b/src/bvar/recorder.h @@ -0,0 +1,394 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date 2014/09/25 17:50:21 + +#ifndef BVAR_RECORDER_H +#define BVAR_RECORDER_H + +#include // int64_t uint64_t +#include "butil/macros.h" // BAIDU_CASSERT +#include "butil/logging.h" // LOG +#include "bvar/detail/combiner.h" // detail::AgentCombiner +#include "bvar/variable.h" +#include "bvar/window.h" +#include "bvar/detail/sampler.h" +#if WITH_BABYLON_COUNTER +#include "babylon/concurrent/counter.h" +#endif // WITH_BABYLON_COUNTER + +namespace bvar { + +struct Stat { + Stat() : sum(0), num(0) {} + Stat(int64_t sum2, int64_t num2) : sum(sum2), num(num2) {} + int64_t sum; + int64_t num; + + int64_t get_average_int() const { + //num can be changed by sampling thread, use tmp_num + int64_t tmp_num = num; + if (tmp_num == 0) { + return 0; + } + return sum / (int64_t)tmp_num; + } + double get_average_double() const { + int64_t tmp_num = num; + if (tmp_num == 0) { + return 0.0; + } + return (double)sum / (double)tmp_num; + } + Stat operator-(const Stat& rhs) const { + return Stat(sum - rhs.sum, num - rhs.num); + } + void operator-=(const Stat& rhs) { + sum -= rhs.sum; + num -= rhs.num; + } + Stat operator+(const Stat& rhs) const { + return Stat(sum + rhs.sum, num + rhs.num); + } + void operator+=(const Stat& rhs) { + sum += rhs.sum; + num += rhs.num; + } +}; + +inline std::ostream& operator<<(std::ostream& os, const Stat& s) { + const int64_t v = s.get_average_int(); + if (v != 0) { + return os << v; + } else { + return os << s.get_average_double(); + } +} + +namespace detail { +struct AddStat { + void operator()(Stat& s1, const Stat& s2) const { s1 += s2; } +}; + +struct MinusStat { + void operator()(Stat& s1, const Stat& s2) const { s1 -= s2; } +}; +} // namespace detail + +// For calculating average of numbers. +// Example: +// IntRecorder latency; +// latency << 1 << 3 << 5; +// CHECK_EQ(3, latency.average()); +#if !WITH_BABYLON_COUNTER +class IntRecorder : public Variable { +public: + // Compressing format: + // | 20 bits (unsigned) | sign bit | 43 bits | + // num sum + const static size_t SUM_BIT_WIDTH=44; + const static uint64_t MAX_SUM_PER_THREAD = (1ul << SUM_BIT_WIDTH) - 1; + const static uint64_t MAX_NUM_PER_THREAD = (1ul << (64ul - SUM_BIT_WIDTH)) - 1; + BAIDU_CASSERT(SUM_BIT_WIDTH > 32 && SUM_BIT_WIDTH < 64, + SUM_BIT_WIDTH_must_be_between_33_and_63); + + typedef Stat value_type; + typedef detail::ReducerSampler sampler_type; + + typedef Stat SampleSet; + + struct AddToStat { + void operator()(Stat& lhs, uint64_t rhs) const { + lhs.sum += _extend_sign_bit(_get_sum(rhs)); + lhs.num += _get_num(rhs); + } + }; + + typedef detail::AgentCombiner combiner_type; + typedef typename combiner_type::self_shared_type shared_combiner_type; + typedef combiner_type::Agent agent_type; + + IntRecorder() : _combiner(std::make_shared()), _sampler(NULL) {} + + IntRecorder(const butil::StringPiece& name) : IntRecorder() { + expose(name); + } + + IntRecorder(const butil::StringPiece& prefix, const butil::StringPiece& name) + : IntRecorder() { + expose_as(prefix, name); + } + + ~IntRecorder() override { + hide(); + if (_sampler) { + _sampler->destroy(); + _sampler = NULL; + } + } + + // Note: The input type is acutally int. Use int64_t to check overflow. + IntRecorder& operator<<(int64_t/*note*/ sample); + + int64_t average() const { + return _combiner->combine_agents().get_average_int(); + } + + double average(double) const { + return _combiner->combine_agents().get_average_double(); + } + + Stat get_value() const { + return _combiner->combine_agents(); + } + + Stat reset() { + return _combiner->reset_all_agents(); + } + + detail::AddStat op() const { return detail::AddStat(); } + detail::MinusStat inv_op() const { return detail::MinusStat(); } + + void describe(std::ostream& os, bool /*quote_string*/) const override { + os << get_value(); + } + + bool valid() const { return _combiner->valid(); } + + sampler_type* get_sampler() { + if (NULL == _sampler) { + _sampler = new sampler_type(this); + _sampler->schedule(); + } + return _sampler; + } + + // This name is useful for printing overflow log in operator<< since + // IntRecorder is often used as the source of data and not exposed. + void set_debug_name(const butil::StringPiece& name) { + _debug_name.assign(name.data(), name.size()); + } + +private: + // TODO: The following numeric functions should be independent utils + static uint64_t _get_sum(const uint64_t n) { + return (n & MAX_SUM_PER_THREAD); + } + + static uint64_t _get_num(const uint64_t n) { + return n >> SUM_BIT_WIDTH; + } + + // Fill all the first (64 - SUM_BIT_WIDTH + 1) bits with 1 if the sign bit is 1 + // to represent a complete 64-bit negative number + // Check out http://en.wikipedia.org/wiki/Signed_number_representations if + // you are confused + static int64_t _extend_sign_bit(const uint64_t sum) { + return (((1ul << (64ul - SUM_BIT_WIDTH + 1)) - 1) + * ((1ul << (SUM_BIT_WIDTH - 1) & sum))) + | (int64_t)sum; + } + + // Convert complement into a |SUM_BIT_WIDTH|-bit unsigned integer + static uint64_t _get_complement(int64_t n) { + return n & (MAX_SUM_PER_THREAD); + } + + static uint64_t _compress(const uint64_t num, const uint64_t sum) { + return (num << SUM_BIT_WIDTH) + // There is a redundant '1' in the front of sum which was + // combined with two negative number, so truncation has to be + // done here + | (sum & MAX_SUM_PER_THREAD) + ; + } + + // Check whether the sum of the two integer overflows the range of signed + // integer with the width of SUM_BIT_WIDTH, which is + // [-2^(SUM_BIT_WIDTH -1), 2^(SUM_BIT_WIDTH -1) - 1) (eg. [-128, 127) for + // signed 8-bit integer) + static bool _will_overflow(const int64_t lhs, const int rhs) { + return + // Both integers are positive and the sum is larger than the largest + // number + ((lhs > 0) && (rhs > 0) + && (lhs + rhs > ((int64_t)MAX_SUM_PER_THREAD >> 1))) + // Or both integers are negative and the sum is less than the lowest + // number + || ((lhs < 0) && (rhs < 0) + && (lhs + rhs < (-((int64_t)MAX_SUM_PER_THREAD >> 1)) - 1)) + // otherwise the sum cannot overflow iff lhs does not overflow + // because |sum| < |lhs| + ; + } + +private: + shared_combiner_type _combiner; + sampler_type* _sampler; + std::string _debug_name; +}; + +inline IntRecorder& IntRecorder::operator<<(int64_t sample) { + if (BAIDU_UNLIKELY((int64_t)(int)sample != sample)) { + const char* reason = NULL; + if (sample > std::numeric_limits::max()) { + reason = "overflows"; + sample = std::numeric_limits::max(); + } else { + reason = "underflows"; + sample = std::numeric_limits::min(); + } + // Truncate to be max or min of int. We're using 44 bits to store the + // sum thus following aggregations are not likely to be over/underflow. + if (!name().empty()) { + LOG(WARNING) << "Input=" << sample << " to `" << name() + << "\' " << reason; + } else if (!_debug_name.empty()) { + LOG(WARNING) << "Input=" << sample << " to `" << _debug_name + << "\' " << reason; + } else { + LOG(WARNING) << "Input=" << sample << " to IntRecorder(" + << (void*)this << ") " << reason; + } + } + agent_type* agent = _combiner->get_or_create_tls_agent(); + if (BAIDU_UNLIKELY(!agent)) { + LOG(FATAL) << "Fail to create agent"; + return *this; + } + uint64_t n; + agent->element.load(&n); + const uint64_t complement = _get_complement(sample); + uint64_t num; + uint64_t sum; + do { + num = _get_num(n); + sum = _get_sum(n); + if (BAIDU_UNLIKELY((num + 1 > MAX_NUM_PER_THREAD) || + _will_overflow(_extend_sign_bit(sum), sample))) { + // Although agent->element might have been cleared at this + // point, it is just OK because the very value is 0 in + // this case + _combiner->commit_and_clear(agent); + sum = 0; + num = 0; + n = 0; + } + } while (!agent->element.compare_exchange_weak( + n, _compress(num + 1, sum + complement))); + return *this; +} +#else // WITH_BABYLON_COUNTER +class IntRecorder : public Variable { +public: + typedef Stat value_type; + typedef detail::AddStat Op; + typedef detail::MinusStat InvOp; + typedef detail::ReducerSampler sampler_type; + + COMMON_VARIABLE_CONSTRUCTOR(IntRecorder); + + DISALLOW_COPY_AND_MOVE(IntRecorder); + + ~IntRecorder() override { + hide(); + if (NULL != _sampler) { + _sampler->destroy(); + } + } + + // Note: The input type is acutally int. Use int64_t to check overflow. + IntRecorder& operator<<(int64_t value) { + if (BAIDU_UNLIKELY((int64_t)(int)value != value)) { + const char* reason = NULL; + if (value > std::numeric_limits::max()) { + reason = "overflows"; + value = std::numeric_limits::max(); + } else { + reason = "underflows"; + value = std::numeric_limits::min(); + } + // Truncate to be max or min of int. We're using 44 bits to store the + // sum thus following aggregations are not likely to be over/underflow. + if (!name().empty()) { + LOG(WARNING) << "Input=" << value << " to `" << name() + << "\' " << reason; + } else if (!_debug_name.empty()) { + LOG(WARNING) << "Input=" << value << " to `" << _debug_name + << "\' " << reason; + } else { + LOG(WARNING) << "Input=" << value << " to IntRecorder(" + << (void*)this << ") " << reason; + } + } + + _summer << value; + return *this; + } + + int64_t average() const { + return get_value().get_average_int(); + } + + double average(double) const { + return get_value().get_average_double(); + } + + value_type get_value() const { + auto summary = _summer.value(); + return value_type{summary.sum, static_cast(summary.num)}; + } + + value_type reset() { + LOG_EVERY_SECOND(ERROR) << "IntRecorder with babylon counter should never call reset()"; + return get_value(); + } + + Op op() const { return Op(); } + InvOp inv_op() const { return InvOp(); } + + void describe(::std::ostream& os, bool) const override { + os << get_value(); + } + + bool valid() const { return true; } + + sampler_type* get_sampler() { + if (NULL == _sampler) { + _sampler = new sampler_type(this); + _sampler->schedule(); + } + return _sampler; + } + + // This name is useful for printing overflow log in operator<< since + // IntRecorder is often used as the source of data and not exposed. + void set_debug_name(const butil::StringPiece& name) { + _debug_name.assign(name.data(), name.size()); + } +private: + babylon::ConcurrentSummer _summer; + sampler_type* _sampler{NULL}; + std::string _debug_name; +}; +#endif // WITH_BABYLON_COUNTER + +} // namespace bvar + +#endif //BVAR_RECORDER_H diff --git a/src/bvar/reducer.h b/src/bvar/reducer.h new file mode 100644 index 0000000..543e77c --- /dev/null +++ b/src/bvar/reducer.h @@ -0,0 +1,533 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date 2014/09/24 16:01:08 + +#ifndef BVAR_REDUCER_H +#define BVAR_REDUCER_H + +#include // std::numeric_limits +#include "butil/logging.h" // LOG() +#include "butil/type_traits.h" // butil::add_cr_non_integral +#include "butil/class_name.h" // class_name_str +#include "bvar/variable.h" // Variable +#include "bvar/detail/combiner.h" // detail::AgentCombiner +#include "bvar/detail/sampler.h" // ReducerSampler +#include "bvar/detail/series.h" +#include "bvar/window.h" +#if WITH_BABYLON_COUNTER +#include "babylon/concurrent/counter.h" +#endif // WITH_BABYLON_COUNTER + +namespace bvar { + +namespace detail { +template +class SeriesSamplerImpl : public Sampler { +public: + SeriesSamplerImpl(O* owner, const Op& op) + : _owner(owner), _series(op) {} + void take_sample() override { _series.append(_owner->get_value()); } + void describe(std::ostream& os) { _series.describe(os, NULL); } + +private: + O* _owner; + Series _series; +}; + +#if WITH_BABYLON_COUNTER +template +class BabylonVariable: public Variable { +public: + typedef ReducerSampler sampler_type; + typedef SeriesSamplerImpl series_sampler_type; + + BabylonVariable() = default; + + template::value, bool>::type = false> + BabylonVariable(U) {} + // For Maxer. + template::value, bool>::type = false> + BabylonVariable(U default_value) : _counter(default_value) {} + + DISALLOW_COPY_AND_MOVE(BabylonVariable); + + ~BabylonVariable() override { + hide(); + if (NULL != _sampler) { + _sampler->destroy(); + } + if (NULL != _series_sampler) { + _series_sampler->destroy(); + } + } + + BabylonVariable& operator<<(T value) { + _counter << value; + return *this; + } + + sampler_type* get_sampler() { + if (NULL == _sampler) { + _sampler = new sampler_type(this); + _sampler->schedule(); + } + return _sampler; + } + + T get_value() const { + return _counter.value(); + } + + T reset() { + if (BAIDU_UNLIKELY((!butil::is_same::value))) { + CHECK(false) << "You should not call Reducer<" << butil::class_name_str() + << ", " << butil::class_name_str() << ">::get_value() when a" + << " Window<> is used because the operator does not have inverse."; + return get_value(); + } + + T result = _counter.value(); + _counter.reset(); + return result; + } + + bool valid() const { return true; } + + const Op& op() const { return _op; } + const InvOp& inv_op() const { return _inv_op;} + + void describe(std::ostream& os, bool quote_string) const override { + if (butil::is_same::value && quote_string) { + os << '"' << get_value() << '"'; + } else { + os << get_value(); + } + } + + int describe_series(std::ostream& os, const SeriesOptions& options) const override { + if (NULL == _series_sampler) { + return 1; + } + if (!options.test_only) { + _series_sampler->describe(os); + } + return 0; + } + +protected: + int expose_impl(const butil::StringPiece& prefix, + const butil::StringPiece& name, + DisplayFilter display_filter) override { + const int rc = Variable::expose_impl(prefix, name, display_filter); + if (rc == 0 && NULL == _series_sampler && + !butil::is_same::value && + !butil::is_same::value && + FLAGS_save_series) { + _series_sampler = new series_sampler_type(this, _op); + _series_sampler->schedule(); + } + return rc; + } + +private: + Counter _counter; + sampler_type* _sampler{NULL}; + series_sampler_type* _series_sampler{NULL}; + Op _op; + InvOp _inv_op; +}; +#endif // WITH_BABYLON_COUNTER +} // namespace detail + +// Reduce multiple values into one with `Op': e1 Op e2 Op e3 ... +// `Op' shall satisfy: +// - associative: a Op (b Op c) == (a Op b) Op c +// - commutative: a Op b == b Op a; +// - no side effects: a Op b never changes if a and b are fixed. +// otherwise the result is undefined. +// +// For performance issues, we don't let Op return value, instead it shall +// set the result to the first parameter in-place. Namely to add two values, +// "+=" should be implemented rather than "+". +// +// Reducer works for non-primitive T which satisfies: +// - T() should be the identity of Op. +// - stream << v should compile and put description of v into the stream +// Example: +// class MyType { +// friend std::ostream& operator<<(std::ostream& os, const MyType&); +// public: +// MyType() : _x(0) {} +// explicit MyType(int x) : _x(x) {} +// void operator+=(const MyType& rhs) const { +// _x += rhs._x; +// } +// private: +// int _x; +// }; +// std::ostream& operator<<(std::ostream& os, const MyType& value) { +// return os << "MyType{" << value._x << "}"; +// } +// bvar::Adder my_type_sum; +// my_type_sum << MyType(1) << MyType(2) << MyType(3); +// LOG(INFO) << my_type_sum; // "MyType{6}" + +template +class Reducer : public Variable { +public: + typedef detail::AgentCombiner combiner_type; + typedef typename combiner_type::self_shared_type shared_combiner_type; + typedef typename combiner_type::Agent agent_type; + typedef detail::ReducerSampler sampler_type; + typedef detail::SeriesSamplerImpl SeriesSampler; + + // The `identify' must satisfy: identity Op a == a + explicit Reducer(typename butil::add_cr_non_integral::type identity = T(), + const Op& op = Op(), const InvOp& inv_op = InvOp()) + : _combiner(std::make_shared(identity, identity, op)) + , _sampler(NULL) , _series_sampler(NULL) , _inv_op(inv_op) {} + + ~Reducer() override { + // Calling hide() manually is a MUST required by Variable. + hide(); + if (_sampler) { + _sampler->destroy(); + _sampler = NULL; + } + if (_series_sampler) { + _series_sampler->destroy(); + _series_sampler = NULL; + } + } + + // Add a value. + // Returns self reference for chaining. + Reducer& operator<<(typename butil::add_cr_non_integral::type value); + + // Get reduced value. + // Notice that this function walks through threads that ever add values + // into this reducer. You should avoid calling it frequently. + T get_value() const { + CHECK(!(butil::is_same::value) || _sampler == NULL) + << "You should not call Reducer<" << butil::class_name_str() + << ", " << butil::class_name_str() << ">::get_value() when a" + << " Window<> is used because the operator does not have inverse."; + return _combiner->combine_agents(); + } + + + // Reset the reduced value to T(). + // Returns the reduced value before reset. + T reset() { return _combiner->reset_all_agents(); } + + void describe(std::ostream& os, bool quote_string) const override { + if (butil::is_same::value && quote_string) { + os << '"' << get_value() << '"'; + } else { + os << get_value(); + } + } + +#ifdef BAIDU_INTERNAL + void get_value(boost::any* value) const override { *value = get_value(); } +#endif + + // True if this reducer is constructed successfully. + bool valid() const { return _combiner->valid(); } + + // Get instance of Op. + const Op& op() const { return _combiner->op(); } + const InvOp& inv_op() const { return _inv_op; } + + sampler_type* get_sampler() { + if (NULL == _sampler) { + _sampler = new sampler_type(this); + _sampler->schedule(); + } + return _sampler; + } + + int describe_series(std::ostream& os, const SeriesOptions& options) const override { + if (_series_sampler == NULL) { + return 1; + } + if (!options.test_only) { + _series_sampler->describe(os); + } + return 0; + } + +protected: + int expose_impl(const butil::StringPiece& prefix, + const butil::StringPiece& name, + DisplayFilter display_filter) override { + const int rc = Variable::expose_impl(prefix, name, display_filter); + if (rc == 0 && + _series_sampler == NULL && + !butil::is_same::value && + !butil::is_same::value && + FLAGS_save_series) { + _series_sampler = new SeriesSampler(this, _combiner->op()); + _series_sampler->schedule(); + } + return rc; + } + +private: + shared_combiner_type _combiner; + sampler_type* _sampler; + SeriesSampler* _series_sampler; + InvOp _inv_op; +}; + +template +inline Reducer& Reducer::operator<<( + typename butil::add_cr_non_integral::type value) { + // It's wait-free for most time + agent_type* agent = _combiner->get_or_create_tls_agent(); + if (__builtin_expect(!agent, 0)) { + LOG(FATAL) << "Fail to create agent"; + return *this; + } + agent->element.modify(_combiner->op(), value); + return *this; +} + +// =================== Common reducers =================== + +// bvar::Adder sum; +// sum << 1 << 2 << 3 << 4; +// LOG(INFO) << sum.get_value(); // 10 +// Commonly used functors +namespace detail { +template +struct AddTo { + void operator()(Tp & lhs, + typename butil::add_cr_non_integral::type rhs) const + { lhs += rhs; } +}; +template +struct MinusFrom { + void operator()(Tp & lhs, + typename butil::add_cr_non_integral::type rhs) const + { lhs -= rhs; } +}; +} // namespace detail + +template +class Adder : public Reducer, detail::MinusFrom > { +public: + typedef Reducer, detail::MinusFrom > Base; + typedef T value_type; + typedef typename Base::sampler_type sampler_type; + + Adder() : Base() {} + Adder(const butil::StringPiece& name) : Base() { + this->expose(name); + } + Adder(const butil::StringPiece& prefix, + const butil::StringPiece& name) : Base() { + this->expose_as(prefix, name); + } + ~Adder() override { Variable::hide(); } +}; + +#if WITH_BABYLON_COUNTER +// Numerical types supported by babylon counter. +template +class Adder>::value>> + : public detail::BabylonVariable, + detail::AddTo, detail::MinusFrom> { +public: + typedef T value_type; +private: + typedef detail::BabylonVariable, + detail::AddTo, detail::MinusFrom> Base; +public: + typedef detail::AddTo Op; + typedef detail::MinusFrom InvOp; + typedef typename Base::sampler_type sampler_type; + + COMMON_VARIABLE_CONSTRUCTOR(Adder); +}; +#endif // WITH_BABYLON_COUNTER + +// bvar::Maxer max_value; +// max_value << 1 << 2 << 3 << 4; +// LOG(INFO) << max_value.get_value(); // 4 +namespace detail { +template +struct MaxTo { + void operator()(Tp & lhs, + typename butil::add_cr_non_integral::type rhs) const { + // Use operator< as well. + if (lhs < rhs) { + lhs = rhs; + } + } +}; + +class LatencyRecorderBase; +} // namespace detail + +template +class Maxer : public Reducer > { +public: + typedef Reducer > Base; + typedef T value_type; + typedef typename Base::sampler_type sampler_type; + + Maxer() : Base(std::numeric_limits::min()) {} + Maxer(const butil::StringPiece& name) + : Base(std::numeric_limits::min()) { + this->expose(name); + } + Maxer(const butil::StringPiece& prefix, const butil::StringPiece& name) + : Base(std::numeric_limits::min()) { + this->expose_as(prefix, name); + } + ~Maxer() override { Variable::hide(); } + +private: + friend class detail::LatencyRecorderBase; + // The following private funcition a now used in LatencyRecorder, + // it's dangerous so we don't make them public + explicit Maxer(T default_value) : Base(default_value) { + } + Maxer(T default_value, const butil::StringPiece& prefix, + const butil::StringPiece& name) + : Base(default_value) { + this->expose_as(prefix, name); + } + Maxer(T default_value, const butil::StringPiece& name) : Base(default_value) { + this->expose(name); + } +}; + +#if WITH_BABYLON_COUNTER +namespace detail { +template +class ConcurrentMaxer : public babylon::GenericsConcurrentMaxer { + typedef babylon::GenericsConcurrentMaxer Base; +public: + ConcurrentMaxer() = default; + ConcurrentMaxer(T default_value) : _default_value(default_value) {} + + T value() const { + T result; + if (!Base::value(result)) { + return _default_value; + } + return std::max(result, _default_value); + } +private: + T _default_value{0}; +}; +} // namespace detail + +// Numerical types supported by babylon counter. +template +class Maxer>::value>> + : public detail::BabylonVariable, + detail::MaxTo, detail::VoidOp> { +public: + typedef T value_type; +private: + typedef detail::BabylonVariable, + detail::MaxTo, detail::VoidOp> Base; +public: + typedef detail::MaxTo Op; + typedef detail::VoidOp InvOp; + typedef typename Base::sampler_type sampler_type; + + COMMON_VARIABLE_CONSTRUCTOR(Maxer); + +private: +friend class detail::LatencyRecorderBase; + + Maxer(T default_value) : Base(default_value) {} + Maxer(T default_value, const butil::StringPiece& prefix, const butil::StringPiece& name) + : Base(default_value) { + Variable::expose_as(prefix, name); + } + Maxer(T default_value, const butil::StringPiece& name) + : Base(default_value) { + Variable::expose(name); + } +}; +#endif // WITH_BABYLON_COUNTER + +// bvar::Miner min_value; +// min_value << 1 << 2 << 3 << 4; +// LOG(INFO) << min_value.get_value(); // 1 +namespace detail { +template +struct MinTo { + void operator()(Tp & lhs, + typename butil::add_cr_non_integral::type rhs) const { + if (rhs < lhs) { + lhs = rhs; + } + } +}; +} // namespace detail + +template +class Miner : public Reducer > { +public: + typedef Reducer > Base; + typedef T value_type; + typedef typename Base::sampler_type sampler_type; + + Miner() : Base(std::numeric_limits::max()) {} + Miner(const butil::StringPiece& name) + : Base(std::numeric_limits::max()) { + this->expose(name); + } + Miner(const butil::StringPiece& prefix, const butil::StringPiece& name) + : Base(std::numeric_limits::max()) { + this->expose_as(prefix, name); + } + ~Miner() override { Variable::hide(); } +}; + +#if WITH_BABYLON_COUNTER +// Numerical types supported by babylon counter. +template +class Miner>::value>> + : public detail::BabylonVariable, + detail::MinTo, detail::VoidOp> { +public: + typedef T value_type; +private: + typedef detail::BabylonVariable, + detail::MinTo, detail::VoidOp> Base; +public: + typedef detail::MinTo Op; + typedef detail::VoidOp InvOp; + typedef typename Base::sampler_type sampler_type; + + COMMON_VARIABLE_CONSTRUCTOR(Miner); +}; +#endif // WITH_BABYLON_COUNTER + +} // namespace bvar + +#endif //BVAR_REDUCER_H diff --git a/src/bvar/scoped_timer.h b/src/bvar/scoped_timer.h new file mode 100644 index 0000000..159c3ee --- /dev/null +++ b/src/bvar/scoped_timer.h @@ -0,0 +1,59 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Fri Jul 14 11:29:21 CST 2017 + +#ifndef BVAR_SCOPED_TIMER_H +#define BVAR_SCOPED_TIMER_H + +#include "butil/time.h" + +// Accumulate microseconds spent by scopes into bvar, useful for debugging. +// Example: +// bvar::Adder g_function1_spent; +// ... +// void function1() { +// // time cost by function1() will be sent to g_spent_time when +// // the function returns. +// bvar::ScopedTimer tm(g_function1_spent); +// ... +// } +// To check how many microseconds the function spend in last second, you +// can wrap the bvar within PerSecond and make it viewable from /vars +// bvar::PerSecond > g_function1_spent_second( +// "function1_spent_second", &g_function1_spent); +namespace bvar{ +template +class ScopedTimer { +public: + explicit ScopedTimer(T& bvar) + : _start_time(butil::cpuwide_time_us()), _bvar(&bvar) {} + + ~ScopedTimer() { + *_bvar << (butil::cpuwide_time_us() - _start_time); + } + + void reset() { _start_time = butil::cpuwide_time_us(); } + +private: + DISALLOW_COPY_AND_ASSIGN(ScopedTimer); + int64_t _start_time; + T* _bvar; +}; +} // namespace bvar + +#endif //BVAR_SCOPED_TIMER_H diff --git a/src/bvar/status.h b/src/bvar/status.h new file mode 100644 index 0000000..3798642 --- /dev/null +++ b/src/bvar/status.h @@ -0,0 +1,242 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date 2014/09/22 11:57:43 + +#ifndef BVAR_STATUS_H +#define BVAR_STATUS_H + +#include // std::string +#include "butil/atomicops.h" +#include "butil/type_traits.h" +#include "butil/string_printf.h" +#include "butil/synchronization/lock.h" +#include "bvar/detail/is_atomical.h" +#include "bvar/variable.h" +#include "bvar/reducer.h" + +namespace bvar { + +// Display a rarely or periodically updated value. +// Usage: +// bvar::Status foo_count1(17); +// foo_count1.expose("my_value"); +// +// bvar::Status foo_count2; +// foo_count2.set_value(17); +// +// bvar::Status foo_count3("my_value", 17); +template +class Status : public Variable { +public: + Status() {} + Status(const T& value) : _value(value) {} + Status(const butil::StringPiece& name, const T& value) : _value(value) { + this->expose(name); + } + Status(const butil::StringPiece& prefix, + const butil::StringPiece& name, const T& value) : _value(value) { + this->expose_as(prefix, name); + } + // Calling hide() manually is a MUST required by Variable. + ~Status() { hide(); } + + void describe(std::ostream& os, bool /*quote_string*/) const override { + os << get_value(); + } + +#ifdef BAIDU_INTERNAL + void get_value(boost::any* value) const override { + butil::AutoLock guard(_lock); + *value = _value; + } +#endif + + T get_value() const { + butil::AutoLock guard(_lock); + const T res = _value; + return res; + } + + void set_value(const T& value) { + butil::AutoLock guard(_lock); + _value = value; + } + +private: + T _value; + // We use lock rather than butil::atomic for generic values because + // butil::atomic requires the type to be memcpy-able (POD basically) + mutable butil::Lock _lock; +}; + +template +class Status::value>::type> + : public Variable { +public: + struct PlaceHolderOp { + void operator()(T&, const T&) const {} + }; + class SeriesSampler : public detail::Sampler { + public: + typedef typename butil::conditional< + true, detail::AddTo, PlaceHolderOp>::type Op; + explicit SeriesSampler(Status* owner) + : _owner(owner), _series(Op()) {} + void take_sample() { _series.append(_owner->get_value()); } + void describe(std::ostream& os) { _series.describe(os, NULL); } + private: + Status* _owner; + detail::Series _series; + }; + +public: + Status() : _series_sampler(NULL) {} + Status(const T& value) : _value(value), _series_sampler(NULL) { } + Status(const butil::StringPiece& name, const T& value) + : _value(value), _series_sampler(NULL) { + this->expose(name); + } + Status(const butil::StringPiece& prefix, + const butil::StringPiece& name, const T& value) + : _value(value), _series_sampler(NULL) { + this->expose_as(prefix, name); + } + ~Status() { + hide(); + if (_series_sampler) { + _series_sampler->destroy(); + _series_sampler = NULL; + } + } + + void describe(std::ostream& os, bool /*quote_string*/) const override { + os << get_value(); + } + +#ifdef BAIDU_INTERNAL + void get_value(boost::any* value) const override { + *value = get_value(); + } +#endif + + T get_value() const { + return _value.load(butil::memory_order_relaxed); + } + + void set_value(const T& value) { + _value.store(value, butil::memory_order_relaxed); + } + + int describe_series(std::ostream& os, const SeriesOptions& options) const override { + if (_series_sampler == NULL) { + return 1; + } + if (!options.test_only) { + _series_sampler->describe(os); + } + return 0; + } + +protected: + int expose_impl(const butil::StringPiece& prefix, + const butil::StringPiece& name, + DisplayFilter display_filter) override { + const int rc = Variable::expose_impl(prefix, name, display_filter); + if (rc == 0 && + _series_sampler == NULL && + FLAGS_save_series) { + _series_sampler = new SeriesSampler(this); + _series_sampler->schedule(); + } + return rc; + } + +private: + butil::atomic _value; + SeriesSampler* _series_sampler; +}; + +// Specialize for std::string, adding a printf-style set_value(). +template <> +class Status : public Variable { +public: + Status() {} + Status(const butil::StringPiece& name, const char* fmt, ...) { + if (fmt) { + va_list ap; + va_start(ap, fmt); + butil::string_vprintf(&_value, fmt, ap); + va_end(ap); + } + expose(name); + } + Status(const butil::StringPiece& prefix, + const butil::StringPiece& name, const char* fmt, ...) { + if (fmt) { + va_list ap; + va_start(ap, fmt); + butil::string_vprintf(&_value, fmt, ap); + va_end(ap); + } + expose_as(prefix, name); + } + + ~Status() { hide(); } + + void describe(std::ostream& os, bool quote_string) const override { + if (quote_string) { + os << '"' << get_value() << '"'; + } else { + os << get_value(); + } + } + + std::string get_value() const { + butil::AutoLock guard(_lock); + return _value; + } + +#ifdef BAIDU_INTERNAL + void get_value(boost::any* value) const override { + *value = get_value(); + } +#endif + + void set_value(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + { + butil::AutoLock guard(_lock); + butil::string_vprintf(&_value, fmt, ap); + } + va_end(ap); + } + + void set_value(const std::string& s) { + butil::AutoLock guard(_lock); + _value = s; + } + +private: + std::string _value; + mutable butil::Lock _lock; +}; + +} // namespace bvar + +#endif //BVAR_STATUS_H diff --git a/src/bvar/utils/lock_timer.h b/src/bvar/utils/lock_timer.h new file mode 100644 index 0000000..f41024a --- /dev/null +++ b/src/bvar/utils/lock_timer.h @@ -0,0 +1,433 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2015/03/06 17:13:17 + +#ifndef BVAR_LOCK_TIMER_H +#define BVAR_LOCK_TIMER_H + +#include "butil/time.h" // butil::Timer +#include "butil/scoped_lock.h" // std::lock_guard std::unique_lock +#include "butil/macros.h" // DISALLOW_COPY_AND_ASSIGN + +#include "bvar/recorder.h" // IntRecorder +#include "bvar/latency_recorder.h" // LatencyRecorder + +// Monitor the time for acquiring a lock. +// We provide some wrappers of mutex which can also be maintained by +// std::lock_guard and std::unique_lock to record the time it takes to wait for +// the acquisition of the very mutex (in microsecond) except for the contention +// caused by condition variables which unlock the mutex before waiting and lock +// the same mutex after waking up. +// +// About Performance: +// The utilities are designed and implemented to be suitable to measure the +// mutex from all the common scenarios. Saying that you can use them freely +// without concerning about the overhead. Except that the mutex is very +// frequently acquired (>1M/s) with very little contention, in which case, the +// overhead of timers and bvars is noticable. +// +// There are two kinds of special Mutex: +// - MutexWithRecorder: Create a mutex along with a shared IntRecorder which +// only records the average latency from intialization +// - MutexWithLatencyRecorder: Create a mutex along with a shared +// LatencyRecorder which also provides percentile +// calculation, time window management besides +// IntRecorder. +// +// Examples: +// #include "bvar/utils/lock_timer.h" +// +// bvar::LatencyRecorder +// g_mutex_contention("my_mutex_contention"); +// // ^^^ +// // you can replace this with a meaningful name +// +// typedef ::bvar::MutexWithLatencyRecorder my_mutex_t; +// // ^^^ +// // you can use std::mutex (since c++11) +// // or bthread_mutex_t (in bthread) +// +// // Define the mutex +// my_mutex_t mutex(g_mutex_contention); +// +// // Use it with std::lock_guard +// void critical_routine_with_lock_guard() { +// std::lock_guard guard(mutex); +// // ^^^ +// // Or you can use BAIDU_SCOPED_LOCK(mutex) to make it simple +// ... +// doing something inside the critical section +// ... +// // and |mutex| is auto unlocked and this contention is recorded out of +// // the scope +// } +// +// // Use it with unique_lock +// void critical_routine_with_unique_lock() { +// std::unique_lock lck(mutex); +// std::condition_variable cond; // available since C++11 +// ... +// doing something inside the critical section +// ... +// cond.wait(lck); // It's ok if my_mutex_t is defined with the template +// // parameter being std::mutex +// ... +// doing other things when come back into the critical section +// ... +// // and |mutex| is auto unlocked and this contention is recorded out of +// // the scope +// } + +namespace bvar { +namespace utils { +// To be compatible with the old version +using namespace ::bvar; +} // namespace utils +} // namespace bvar + +namespace bvar { + +// Specialize MutexConstructor and MutexDestructor for the Non-RAII mutexes such +// as pthread_mutex_t +template +struct MutexConstructor { + bool operator()(Mutex*) const { return true; } +}; + +template +struct MutexDestructor { + bool operator()(Mutex*) const { return true; } +}; + +// Specialize for pthread_mutex_t +template <> +struct MutexConstructor { + bool operator()(pthread_mutex_t* mutex) const { +#ifndef NDEBUG + const int rc = pthread_mutex_init(mutex, NULL); + CHECK_EQ(0, rc) << "Fail to init pthread_mutex, " << berror(rc); + return rc == 0; +#else + return pthread_mutex_init(mutex, NULL) == 0; +#endif + } +}; + +template <> +struct MutexDestructor { + bool operator()(pthread_mutex_t* mutex) const { +#ifndef NDEBUG + const int rc = pthread_mutex_destroy(mutex); + CHECK_EQ(0, rc) << "Fail to destroy pthread_mutex, " << berror(rc); + return rc == 0; +#else + return pthread_mutex_destroy(mutex) == 0; +#endif + } +}; + +namespace detail { + +template +class MutexWithRecorderBase { + DISALLOW_COPY_AND_ASSIGN(MutexWithRecorderBase); +public: + typedef Mutex mutex_type; + typedef Recorder recorder_type; + typedef MutexWithRecorderBase self_type; + + explicit MutexWithRecorderBase(recorder_type &recorder) + : _recorder(&recorder) { + MCtor()(&_mutex); + } + + MutexWithRecorderBase() : _recorder(NULL) { + MCtor()(&_mutex); + } + + ~MutexWithRecorderBase() { + MDtor()(&_mutex); + } + + void set_recorder(recorder_type& recorder) { + _recorder = &recorder; + } + + mutex_type& mutex() { return _mutex; } + operator mutex_type&() { return _mutex; } + + template + self_type& operator<<(T value) { + if (_recorder) { + *_recorder << value; + } + return *this; + } + +private: + mutex_type _mutex; + // We don't own _recorder. Make sure it is valid before the destruction of + // this instance + recorder_type *_recorder; +}; + +template +class LockGuardBase { + DISALLOW_COPY_AND_ASSIGN(LockGuardBase); +public: + LockGuardBase(Mutex& m) + : _timer(m), _lock_guard(m.mutex()) { + _timer.timer.stop(); + } +private: + // This trick makes the recoding happens after the destructor of _lock_guard + struct TimerAndMutex { + TimerAndMutex(Mutex &m) + : timer(butil::Timer::STARTED), mutex(&m) {} + ~TimerAndMutex() { + *mutex << timer.u_elapsed(); + } + butil::Timer timer; + Mutex* mutex; + }; + // Don't change the order of the fields as the implementation depends on + // the order of the constructors and destructors + TimerAndMutex _timer; + std::lock_guard _lock_guard; +}; + +template +class UniqueLockBase { + DISALLOW_COPY_AND_ASSIGN(UniqueLockBase); +public: + typedef Mutex mutex_type; + explicit UniqueLockBase(mutex_type& mutex) + : _timer(butil::Timer::STARTED), _lock(mutex.mutex()), + _mutex(&mutex) { + _timer.stop(); + } + + UniqueLockBase(mutex_type& mutex, std::defer_lock_t defer_lock) + : _timer(), _lock(mutex.mutex(), defer_lock), _mutex(&mutex) { + } + + UniqueLockBase(mutex_type& mutex, std::try_to_lock_t try_to_lock) + : _timer(butil::Timer::STARTED) + , _lock(mutex.mutex(), try_to_lock) + , _mutex(&mutex) { + + _timer.stop(); + if (!owns_lock()) { + *_mutex << _timer.u_elapsed(); + } + } + + ~UniqueLockBase() { + if (_lock.owns_lock()) { + unlock(); + } + } + + operator std::unique_lock&() { return _lock; } + void lock() { + _timer.start(); + _lock.lock(); + _timer.stop(); + } + + bool try_lock() { + _timer.start(); + const bool rc = _lock.try_lock(); + _timer.stop(); + if (!rc) { + _mutex->recorder() << _timer.u_elapsed(); + } + return rc; + } + + void unlock() { + _lock.unlock(); + // Recorde the time out of the critical section + *_mutex << _timer.u_elapsed(); + } + + mutex_type* release() { + if (_lock.owns_lock()) { + // We have to recorde this time in the critical section owtherwise + // the event will be lost + *_mutex << _timer.u_elapsed(); + } + mutex_type* saved_mutex = _mutex; + _mutex = NULL; + _lock.release(); + return saved_mutex; + } + + mutex_type* mutex() { return _mutex; } + bool owns_lock() const { return _lock.owns_lock(); } + operator bool() const { return static_cast(_lock); } + +#if __cplusplus >= 201103L + template + bool try_lock_for( + const std::chrono::duration& timeout_duration) { + _timer.start(); + const bool rc = _lock.try_lock_for(timeout_duration); + _timer.stop(); + if (!rc) { + *_mutex << _timer.u_elapsed(); + } + return rc; + } + + template + bool try_lock_until( + const std::chrono::time_point& timeout_time ) { + _timer.start(); + const bool rc = _lock.try_lock_until(timeout_time); + _timer.stop(); + if (!rc) { + // Out of the criticle section. Otherwise the time will be recorded + // in unlock + *_mutex << _timer.u_elapsed(); + } + return rc; + } +#endif + +private: + // Don't change the order or timer and _lck; + butil::Timer _timer; + std::unique_lock _lock; + mutex_type* _mutex; +}; + +} // namespace detail + +// Wappers of Mutex along with a shared LatencyRecorder +template +struct MutexWithRecorder + : public detail::MutexWithRecorderBase< + Mutex, IntRecorder, + MutexConstructor, MutexDestructor > { + typedef detail::MutexWithRecorderBase< + Mutex, IntRecorder, + MutexConstructor, MutexDestructor > Base; + + explicit MutexWithRecorder(IntRecorder& recorder) + : Base(recorder) + {} + + MutexWithRecorder() : Base() {} + +}; + +// Wappers of Mutex along with a shared LatencyRecorder +template +struct MutexWithLatencyRecorder + : public detail::MutexWithRecorderBase< + Mutex, LatencyRecorder, + MutexConstructor, MutexDestructor > { + typedef detail::MutexWithRecorderBase< + Mutex, LatencyRecorder, + MutexConstructor, MutexDestructor > Base; + + explicit MutexWithLatencyRecorder(LatencyRecorder& recorder) + : Base(recorder) + {} + MutexWithLatencyRecorder() : Base() {} +}; + +} // namespace bvar + +namespace std { + +// Specialize lock_guard and unique_lock +template +class lock_guard > + : public ::bvar::detail:: + LockGuardBase< ::bvar::MutexWithRecorder > { +public: + typedef ::bvar::detail:: + LockGuardBase > Base; + explicit lock_guard(::bvar::MutexWithRecorder &mutex) + : Base(mutex) + {} +}; + +template +class lock_guard > + : public ::bvar::detail:: + LockGuardBase< ::bvar::MutexWithLatencyRecorder > { +public: + typedef ::bvar::detail:: + LockGuardBase > Base; + explicit lock_guard(::bvar::MutexWithLatencyRecorder &mutex) + : Base(mutex) + {} +}; + +template +class unique_lock > + : public ::bvar::detail:: + UniqueLockBase< ::bvar::MutexWithRecorder > { +public: + typedef ::bvar::detail:: + UniqueLockBase< ::bvar::MutexWithRecorder > Base; + typedef typename Base::mutex_type mutex_type; + + explicit unique_lock(mutex_type& mutex) + : Base(mutex) + {} + + unique_lock(mutex_type& mutex, std::defer_lock_t defer_lock) + : Base(mutex, defer_lock) + {} + + unique_lock(mutex_type& mutex, std::try_to_lock_t try_to_lock) + : Base(mutex, try_to_lock) + {} +}; + +template +class unique_lock > + : public ::bvar::detail:: + UniqueLockBase< ::bvar::MutexWithLatencyRecorder > { +public: + typedef ::bvar::detail:: + UniqueLockBase< ::bvar::MutexWithLatencyRecorder > Base; + typedef typename Base::mutex_type mutex_type; + + explicit unique_lock(mutex_type& mutex) + : Base(mutex) + {} + + unique_lock(mutex_type& mutex, std::defer_lock_t defer_lock) + : Base(mutex, defer_lock) + {} + + unique_lock(mutex_type& mutex, std::try_to_lock_t try_to_lock) + : Base(mutex, try_to_lock) + {} +}; + +} // namespace std + +#endif // BVAR_LOCK_TIMER_H diff --git a/src/bvar/variable.cpp b/src/bvar/variable.cpp new file mode 100644 index 0000000..80c3049 --- /dev/null +++ b/src/bvar/variable.cpp @@ -0,0 +1,971 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2014/09/22 19:04:47 + +#include +#include // std::set +#include // std::ifstream +#include // std::ostringstream +#include +#include "butil/macros.h" // BAIDU_CASSERT +#include "butil/containers/flat_map.h" // butil::FlatMap +#include "butil/scoped_lock.h" // BAIDU_SCOPE_LOCK +#include "butil/string_splitter.h" // butil::StringSplitter +#include "butil/errno.h" // berror +#include "butil/time.h" // milliseconds_from_now +#include "butil/file_util.h" // butil::FilePath +#include "butil/threading/platform_thread.h" +#include "butil/reloadable_flags.h" +#include "bvar/gflag.h" +#include "bvar/variable.h" +#include "bvar/mvariable.h" + +namespace bvar { + +DEFINE_bool(save_series, true, + "Save values of last 60 seconds, last 60 minutes," + " last 24 hours and last 30 days for plotting"); + +DEFINE_bool(quote_vector, true, + "Quote description of Vector<> to make it valid to noah"); + +// Remember abort request before bvar_abort_on_same_name is initialized. +bool s_bvar_may_abort = false; +static bool validate_bvar_abort_on_same_name(const char*, bool v) { + RELEASE_ASSERT_VERBOSE(!v || !s_bvar_may_abort, "Abort due to name conflict"); + return true; +} +DEFINE_bool(bvar_abort_on_same_name, false, "Abort when names of bvar are same"); +BUTIL_VALIDATE_GFLAG(bvar_abort_on_same_name, validate_bvar_abort_on_same_name); + + +DEFINE_bool(bvar_log_dumpped, false, + "[For debugging] print dumpped info" + " into logstream before call Dumpper"); +BUTIL_VALIDATE_GFLAG(bvar_log_dumpped, butil::PassValidate); + +const size_t SUB_MAP_COUNT = 32; // must be power of 2 +BAIDU_CASSERT(!(SUB_MAP_COUNT & (SUB_MAP_COUNT - 1)), must_be_power_of_2); + +class VarEntry { +public: + VarEntry() : var(NULL), display_filter(DISPLAY_ON_ALL) {} + + Variable* var; + DisplayFilter display_filter; +}; + +typedef butil::FlatMap VarMap; + +struct VarMapWithLock : public VarMap { + pthread_mutex_t mutex; + + VarMapWithLock() { + if (init(1024) != 0) { + LOG(WARNING) << "Fail to init VarMap"; + } + + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mutex, &attr); + pthread_mutexattr_destroy(&attr); + } +}; + +// We have to initialize global map on need because bvar is possibly used +// before main(). +static pthread_once_t s_var_maps_once = PTHREAD_ONCE_INIT; +static VarMapWithLock* s_var_maps = NULL; + +static void init_var_maps() { + // It's probably slow to initialize all sub maps, but rpc often expose + // variables before user. So this should not be an issue to users. + s_var_maps = new VarMapWithLock[SUB_MAP_COUNT]; +} + +inline size_t sub_map_index(const std::string& str) { + if (str.empty()) { + return 0; + } + size_t h = 0; + // we're assume that str is ended with '\0', which may not be in general + for (const char* p = str.c_str(); *p; ++p) { + h = h * 5 + *p; + } + return h & (SUB_MAP_COUNT - 1); +} + +inline VarMapWithLock* get_var_maps() { + pthread_once(&s_var_maps_once, init_var_maps); + return s_var_maps; +} + +inline VarMapWithLock& get_var_map(const std::string& name) { + VarMapWithLock& m = get_var_maps()[sub_map_index(name)]; + return m; +} + +Variable::~Variable() { + CHECK(!hide()) << "Subclass of Variable MUST call hide() manually in their" + " dtors to avoid displaying a variable that is just destructing"; +} + +int Variable::expose_impl(const butil::StringPiece& prefix, + const butil::StringPiece& name, + DisplayFilter display_filter) { + if (name.empty()) { + LOG(ERROR) << "Parameter[name] is empty"; + return -1; + } + // NOTE: It's impossible to atomically erase from a submap and insert into + // another submap without a global lock. When the to-be-exposed name + // already exists, there's a chance that we can't insert back previous + // name. But it should be fine generally because users are unlikely to + // expose a variable more than once and calls to expose() are unlikely + // to contend heavily. + + // remove previous pointer from the map if needed. + hide(); + + // Build the name. + _name.clear(); + _name.reserve((prefix.size() + name.size()) * 5 / 4); + if (!prefix.empty()) { + to_underscored_name(&_name, prefix); + if (!_name.empty() && butil::back_char(_name) != '_') { + _name.push_back('_'); + } + } + to_underscored_name(&_name, name); + + VarMapWithLock& m = get_var_map(_name); + { + BAIDU_SCOPED_LOCK(m.mutex); + VarEntry* entry = m.seek(_name); + if (entry == NULL) { + entry = &m[_name]; + entry->var = this; + entry->display_filter = display_filter; + return 0; + } + } + RELEASE_ASSERT_VERBOSE(!FLAGS_bvar_abort_on_same_name, + "Abort due to name conflict"); + if (!s_bvar_may_abort) { + // Mark name conflict occurs, If this conflict happens before + // initialization of bvar_abort_on_same_name, the validator will + // abort the program if needed. + s_bvar_may_abort = true; + } + + LOG(ERROR) << "Already exposed `" << _name << "' whose value is `" + << describe_exposed(_name) << '\''; + _name.clear(); + return -1; +} + +bool Variable::is_hidden() const { + return _name.empty(); +} + +bool Variable::hide() { + if (_name.empty()) { + return false; + } + VarMapWithLock& m = get_var_map(_name); + BAIDU_SCOPED_LOCK(m.mutex); + VarEntry* entry = m.seek(_name); + if (entry) { + CHECK_EQ(1UL, m.erase(_name)); + } else { + CHECK(false) << "`" << _name << "' must exist"; + } + _name.clear(); + return true; +} + +void Variable::list_exposed(std::vector* names, + DisplayFilter display_filter) { + if (names == NULL) { + return; + } + names->clear(); + if (names->capacity() < 32) { + names->reserve(count_exposed()); + } + VarMapWithLock* var_maps = get_var_maps(); + for (size_t i = 0; i < SUB_MAP_COUNT; ++i) { + VarMapWithLock& m = var_maps[i]; + std::unique_lock mu(m.mutex); + size_t n = 0; + for (VarMap::const_iterator it = m.begin(); it != m.end(); ++it) { + if (++n >= 256/*max iterated one pass*/) { + VarMap::PositionHint hint; + m.save_iterator(it, &hint); + n = 0; + mu.unlock(); // yield + mu.lock(); + it = m.restore_iterator(hint); + if (it == m.begin()) { // resized + names->clear(); + } + if (it == m.end()) { + break; + } + } + if (it->second.display_filter & display_filter) { + names->push_back(it->first); + } + } + } +} + +size_t Variable::count_exposed() { + size_t n = 0; + VarMapWithLock* var_maps = get_var_maps(); + for (size_t i = 0; i < SUB_MAP_COUNT; ++i) { + n += var_maps[i].size(); + } + return n; +} + +int Variable::describe_exposed(const std::string& name, std::ostream& os, + bool quote_string, + DisplayFilter display_filter) { + VarMapWithLock& m = get_var_map(name); + BAIDU_SCOPED_LOCK(m.mutex); + VarEntry* p = m.seek(name); + if (p == NULL) { + return -1; + } + if (!(display_filter & p->display_filter)) { + return -1; + } + p->var->describe(os, quote_string); + return 0; +} + +std::string Variable::describe_exposed(const std::string& name, + bool quote_string, + DisplayFilter display_filter) { + std::ostringstream oss; + if (describe_exposed(name, oss, quote_string, display_filter) == 0) { + return oss.str(); + } + return std::string(); +} + +std::string Variable::get_description() const { + std::ostringstream os; + describe(os, false); + return os.str(); +} + +#ifdef BAIDU_INTERNAL +void Variable::get_value(boost::any* value) const { + std::ostringstream os; + describe(os, false); + *value = os.str(); +} +#endif + +int Variable::describe_series_exposed(const std::string& name, + std::ostream& os, + const SeriesOptions& options) { + VarMapWithLock& m = get_var_map(name); + BAIDU_SCOPED_LOCK(m.mutex); + VarEntry* p = m.seek(name); + if (p == NULL) { + return -1; + } + return p->var->describe_series(os, options); +} + +#ifdef BAIDU_INTERNAL +int Variable::get_exposed(const std::string& name, boost::any* value) { + VarMapWithLock& m = get_var_map(name); + BAIDU_SCOPED_LOCK(m.mutex); + VarEntry* p = m.seek(name); + if (p == NULL) { + return -1; + } + p->var->get_value(value); + return 0; +} +#endif + +// TODO(gejun): This is copied from otherwhere, common it if possible. + +// Underlying buffer to store logs. Comparing to using std::ostringstream +// directly, this utility exposes more low-level methods so that we avoid +// creation of std::string which allocates memory internally. +class CharArrayStreamBuf : public std::streambuf { +public: + explicit CharArrayStreamBuf() : _data(NULL), _size(0) {} + ~CharArrayStreamBuf(); + + int overflow(int ch) override; + int sync() override; + void reset(); + butil::StringPiece data() { + return butil::StringPiece(pbase(), pptr() - pbase()); + } + +private: + char* _data; + size_t _size; +}; + +CharArrayStreamBuf::~CharArrayStreamBuf() { + free(_data); +} + +int CharArrayStreamBuf::overflow(int ch) { + if (ch == std::streambuf::traits_type::eof()) { + return ch; + } + size_t new_size = std::max(_size * 3 / 2, (size_t)64); + char* new_data = (char*)malloc(new_size); + if (BAIDU_UNLIKELY(new_data == NULL)) { + setp(NULL, NULL); + return std::streambuf::traits_type::eof(); + } + memcpy(new_data, _data, _size); + free(_data); + _data = new_data; + const size_t old_size = _size; + _size = new_size; + setp(_data, _data + new_size); + pbump(old_size); + // if size == 1, this function will call overflow again. + return sputc(ch); +} + +int CharArrayStreamBuf::sync() { + // data are already there. + return 0; +} + +void CharArrayStreamBuf::reset() { + setp(_data, _data + _size); +} + + +// Written by Jack Handy +//
jakkhandy@hotmail.com +inline bool wildcmp(const char* wild, const char* str, char question_mark) { + const char* cp = NULL; + const char* mp = NULL; + + while (*str && *wild != '*') { + if (*wild != *str && *wild != question_mark) { + return false; + } + ++wild; + ++str; + } + + while (*str) { + if (*wild == '*') { + if (!*++wild) { + return true; + } + mp = wild; + cp = str+1; + } else if (*wild == *str || *wild == question_mark) { + ++wild; + ++str; + } else { + wild = mp; + str = cp++; + } + } + + while (*wild == '*') { + ++wild; + } + return !*wild; +} + +class WildcardMatcher { +public: + WildcardMatcher(const std::string& wildcards, + char question_mark, + bool on_both_empty) + : _question_mark(question_mark) + , _on_both_empty(on_both_empty) { + if (wildcards.empty()) { + return; + } + std::string name; + const char wc_pattern[3] = { '*', question_mark, '\0' }; + for (butil::StringMultiSplitter sp(wildcards.c_str(), ",;"); + sp != NULL; ++sp) { + name.assign(sp.field(), sp.length()); + if (name.find_first_of(wc_pattern) != std::string::npos) { + if (_wcs.empty()) { + _wcs.reserve(8); + } + _wcs.push_back(name); + } else { + _exact.insert(name); + } + } + } + + bool match(const std::string& name) const { + if (!_exact.empty()) { + if (_exact.find(name) != _exact.end()) { + return true; + } + } else if (_wcs.empty()) { + return _on_both_empty; + } + for (size_t i = 0; i < _wcs.size(); ++i) { + if (wildcmp(_wcs[i].c_str(), name.c_str(), _question_mark)) { + return true; + } + } + return false; + } + + const std::vector& wildcards() const { return _wcs; } + const std::set& exact_names() const { return _exact; } + +private: + char _question_mark; + bool _on_both_empty; + std::vector _wcs; + std::set _exact; +}; + +DumpOptions::DumpOptions() + : quote_string(true) + , question_mark('?') + , display_filter(DISPLAY_ON_PLAIN_TEXT) +{} + +int Variable::dump_exposed(Dumper* dumper, const DumpOptions* poptions) { + if (NULL == dumper) { + LOG(ERROR) << "Parameter[dumper] is NULL"; + return -1; + } + DumpOptions opt; + if (poptions) { + opt = *poptions; + } + CharArrayStreamBuf streambuf; + std::ostream os(&streambuf); + int count = 0; + WildcardMatcher black_matcher(opt.black_wildcards, + opt.question_mark, + false); + WildcardMatcher white_matcher(opt.white_wildcards, + opt.question_mark, + true); + + std::ostringstream dumpped_info; + const bool log_dummped = FLAGS_bvar_log_dumpped; + + if (white_matcher.wildcards().empty() && + !white_matcher.exact_names().empty()) { + for (std::set::const_iterator + it = white_matcher.exact_names().begin(); + it != white_matcher.exact_names().end(); ++it) { + const std::string& name = *it; + if (!black_matcher.match(name)) { + if (bvar::Variable::describe_exposed( + name, os, opt.quote_string, opt.display_filter) != 0) { + continue; + } + if (log_dummped) { + dumpped_info << '\n' << name << ": " << streambuf.data(); + } + if (!dumper->dump(name, streambuf.data())) { + return -1; + } + streambuf.reset(); + ++count; + } + } + } else { + // Have to iterate all variables. + std::vector varnames; + bvar::Variable::list_exposed(&varnames, opt.display_filter); + // Sort the names to make them more readable. + std::sort(varnames.begin(), varnames.end()); + for (std::vector::const_iterator + it = varnames.begin(); it != varnames.end(); ++it) { + const std::string& name = *it; + if (white_matcher.match(name) && !black_matcher.match(name)) { + if (bvar::Variable::describe_exposed( + name, os, opt.quote_string, opt.display_filter) != 0) { + continue; + } + if (log_dummped) { + dumpped_info << '\n' << name << ": " << streambuf.data(); + } + if (!dumper->dump(name, streambuf.data())) { + return -1; + } + streambuf.reset(); + ++count; + } + } + } + if (log_dummped) { + LOG(INFO) << "Dumpped variables:" << dumpped_info.str(); + } + return count; +} + + +// ============= export to files ============== + +std::string read_command_name() { + std::ifstream fin("/proc/self/stat"); + if (!fin.is_open()) { + return std::string(); + } + int pid = 0; + std::string command_name; + fin >> pid >> command_name; + if (!fin.good()) { + return std::string(); + } + // Although the man page says the command name is in parenthesis, for + // safety we normalize the name. + std::string s; + if (command_name.size() >= 2UL && command_name[0] == '(' && + butil::back_char(command_name) == ')') { + // remove parenthesis. + to_underscored_name(&s, + butil::StringPiece(command_name.data() + 1, + command_name.size() - 2UL)); + } else { + to_underscored_name(&s, command_name); + } + return s; +} + +class FileDumper : public Dumper { +public: + FileDumper(const std::string& filename, butil::StringPiece s/*prefix*/) + : _filename(filename), _fp(NULL) { + // setting prefix. + // remove trailing spaces. + const char* p = s.data() + s.size(); + for (; p != s.data() && isspace(p[-1]); --p) {} + s.remove_suffix(s.data() + s.size() - p); + // normalize it. + if (!s.empty()) { + to_underscored_name(&_prefix, s); + if (butil::back_char(_prefix) != '_') { + _prefix.push_back('_'); + } + } + } + + ~FileDumper() { + close(); + } + void close() { + if (_fp) { + fclose(_fp); + _fp = NULL; + } + } + +protected: + bool dump_impl(const std::string& name, const butil::StringPiece& desc, const std::string& separator) { + if (_fp == NULL) { + butil::File::Error error; + butil::FilePath dir = butil::FilePath(_filename).DirName(); + if (!butil::CreateDirectoryAndGetError(dir, &error)) { + LOG(ERROR) << "Fail to create directory=`" << dir.value() + << "', " << error; + return false; + } + _fp = fopen(_filename.c_str(), "w"); + if (NULL == _fp) { + LOG(ERROR) << "Fail to open " << _filename; + return false; + } + } + if (fprintf(_fp, "%.*s%.*s %.*s %.*s\r\n", + (int)_prefix.size(), _prefix.data(), + (int)name.size(), name.data(), + (int)separator.size(), separator.data(), + (int)desc.size(), desc.data()) < 0) { + PLOG(ERROR) << "Fail to write into " << _filename; + return false; + } + return true; + } +private: + std::string _filename; + FILE* _fp; + std::string _prefix; +}; + +class CommonFileDumper : public FileDumper { +public: + CommonFileDumper(const std::string& filename, butil::StringPiece prefix) + : FileDumper(filename, prefix) + , _separator(":") {} + bool dump(const std::string& name, const butil::StringPiece& desc) { + return dump_impl(name, desc, _separator); + } +private: + std::string _separator; +}; + +class PrometheusFileDumper : public FileDumper { +public: + PrometheusFileDumper(const std::string& filename, butil::StringPiece prefix) + : FileDumper(filename, prefix) + , _separator(" ") {} + bool dump(const std::string& name, const butil::StringPiece& desc) { + return dump_impl(name, desc, _separator); + } +private: + std::string _separator; +}; + +class FileDumperGroup : public Dumper { +public: + FileDumperGroup(std::string tabs, std::string filename, + butil::StringPiece s/*prefix*/) { + butil::FilePath path(filename); + if (path.FinalExtension() == ".data") { + // .data will be appended later + path = path.RemoveFinalExtension(); + } + + for (butil::KeyValuePairsSplitter sp(tabs, ';', '='); sp; ++sp) { + std::string key = sp.key().as_string(); + std::string value = sp.value().as_string(); + FileDumper *f = new CommonFileDumper( + path.AddExtension(key).AddExtension("data").value(), s); + WildcardMatcher *m = new WildcardMatcher(value, '?', true); + dumpers.emplace_back(f, m); + } + dumpers.emplace_back( + new CommonFileDumper(path.AddExtension("data").value(), s), + (WildcardMatcher *)NULL); + } + ~FileDumperGroup() { + for (size_t i = 0; i < dumpers.size(); ++i) { + delete dumpers[i].first; + delete dumpers[i].second; + } + dumpers.clear(); + } + + bool dump(const std::string& name, const butil::StringPiece& desc) override { + for (size_t i = 0; i < dumpers.size() - 1; ++i) { + if (dumpers[i].second->match(name)) { + return dumpers[i].first->dump(name, desc); + } + } + // dump to default file + return dumpers.back().first->dump(name, desc); + } +private: + std::vector > dumpers; +}; + +static pthread_once_t dumping_thread_once = PTHREAD_ONCE_INIT; +static bool created_dumping_thread = false; +static pthread_mutex_t dump_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t dump_cond = PTHREAD_COND_INITIALIZER; + +DEFINE_bool(bvar_dump, false, + "Create a background thread dumping all bvar periodically, " + "all bvar_dump_* flags are not effective when this flag is off"); +DEFINE_int32(bvar_dump_interval, 10, "Seconds between consecutive dump"); +DEFINE_string(bvar_dump_file, "monitor/bvar..data", "Dump bvar into this file"); +DEFINE_string(bvar_dump_include, "", "Dump bvar matching these wildcards, " + "separated by semicolon(;), empty means including all"); +DEFINE_string(bvar_dump_exclude, "", "Dump bvar excluded from these wildcards, " + "separated by semicolon(;), empty means no exclusion"); +DEFINE_string(bvar_dump_prefix, "", "Every dumped name starts with this prefix"); +DEFINE_string(bvar_dump_tabs, "latency=*_latency*" + ";qps=*_qps*" + ";error=*_error*" + ";system=*process_*,*malloc_*,*kernel_*", + "Dump bvar into different tabs according to the filters (separated by semicolon), " + "format: *(tab_name=wildcards;)"); + +DEFINE_bool(mbvar_dump, false, + "Create a background thread dumping(shares the same thread as bvar_dump) all mbvar periodically, " + "all mbvar_dump_* flags are not effective when this flag is off"); +DEFINE_string(mbvar_dump_file, "monitor/mbvar..data", "Dump mbvar into this file"); +DEFINE_string(mbvar_dump_prefix, "", "Every dumped name starts with this prefix"); +DEFINE_string(mbvar_dump_format, "common", "Dump mbvar write format"); + +#if !defined(BVAR_NOT_LINK_DEFAULT_VARIABLES) +// Expose bvar-releated gflags so that they're collected by noah. +// Maybe useful when debugging process of monitoring. +static GFlag s_gflag_bvar_dump_interval("bvar_dump_interval"); +#endif + +// The background thread to export all bvar periodically. +static void* dumping_thread(void*) { + // NOTE: this variable was declared as static <= r34381, which was + // destructed when program exits and caused coredumps. + butil::PlatformThread::SetNameSimple("bvar_dumper"); + const std::string command_name = read_command_name(); + std::string last_filename; + std::string mbvar_last_filename; + while (1) { + // We can't access string flags directly because it's thread-unsafe. + std::string filename; + DumpOptions options; + std::string prefix; + std::string tabs; + std::string mbvar_filename; + std::string mbvar_prefix; + std::string mbvar_format; + if (!GFLAGS_NAMESPACE::GetCommandLineOption("bvar_dump_file", &filename)) { + LOG(ERROR) << "Fail to get gflag bvar_dump_file"; + return NULL; + } + if (!GFLAGS_NAMESPACE::GetCommandLineOption("bvar_dump_include", + &options.white_wildcards)) { + LOG(ERROR) << "Fail to get gflag bvar_dump_include"; + return NULL; + } + if (!GFLAGS_NAMESPACE::GetCommandLineOption("bvar_dump_exclude", + &options.black_wildcards)) { + LOG(ERROR) << "Fail to get gflag bvar_dump_exclude"; + return NULL; + } + if (!GFLAGS_NAMESPACE::GetCommandLineOption("bvar_dump_prefix", &prefix)) { + LOG(ERROR) << "Fail to get gflag bvar_dump_prefix"; + return NULL; + } + if (!GFLAGS_NAMESPACE::GetCommandLineOption("bvar_dump_tabs", &tabs)) { + LOG(ERROR) << "Fail to get gflags bvar_dump_tabs"; + return NULL; + } + + // We can't access string flags directly because it's thread-unsafe. + if (!GFLAGS_NAMESPACE::GetCommandLineOption("mbvar_dump_file", &mbvar_filename)) { + LOG(ERROR) << "Fail to get gflag mbvar_dump_file"; + return NULL; + } + if (!GFLAGS_NAMESPACE::GetCommandLineOption("mbvar_dump_prefix", &mbvar_prefix)) { + LOG(ERROR) << "Fail to get gflag mbvar_dump_prefix"; + return NULL; + } + if (!GFLAGS_NAMESPACE::GetCommandLineOption("mbvar_dump_format", &mbvar_format)) { + LOG(ERROR) << "Fail to get gflag mbvar_dump_format"; + return NULL; + } + + if (FLAGS_bvar_dump && !filename.empty()) { + // Replace first in filename with program name. We can't use + // pid because a same binary should write the data to the same + // place, otherwise restarting of app may confuse noah with a lot + // of *.data. noah takes 1.5 days to figure out that some data is + // outdated and to be removed. + const size_t pos = filename.find(""); + if (pos != std::string::npos) { + filename.replace(pos, 5/**/, command_name); + } + if (last_filename != filename) { + last_filename = filename; + LOG(INFO) << "Write all bvar to " << filename << " every " + << FLAGS_bvar_dump_interval << " seconds."; + } + const size_t pos2 = prefix.find(""); + if (pos2 != std::string::npos) { + prefix.replace(pos2, 5/**/, command_name); + } + FileDumperGroup dumper(tabs, filename, prefix); + int nline = Variable::dump_exposed(&dumper, &options); + if (nline < 0) { + LOG(ERROR) << "Fail to dump vars into " << filename; + } + } + + // Dump multi dimension bvar + if (FLAGS_mbvar_dump && !mbvar_filename.empty()) { + // Replace first in filename with program name. We can't use + // pid because a same binary should write the data to the same + // place, otherwise restarting of app may confuse noah with a lot + // of *.data. noah takes 1.5 days to figure out that some data is + // outdated and to be removed. + const size_t pos = mbvar_filename.find(""); + if (pos != std::string::npos) { + mbvar_filename.replace(pos, 5/**/, command_name); + } + if (mbvar_last_filename != mbvar_filename) { + mbvar_last_filename = mbvar_filename; + LOG(INFO) << "Write all mbvar to " << mbvar_filename << " every " + << FLAGS_bvar_dump_interval << " seconds."; + } + const size_t pos2 = mbvar_prefix.find(""); + if (pos2 != std::string::npos) { + mbvar_prefix.replace(pos2, 5/**/, command_name); + } + + Dumper* dumper = NULL; + if ("common" == mbvar_format) { + dumper = new CommonFileDumper(mbvar_filename, mbvar_prefix); + } else if ("prometheus" == mbvar_format) { + dumper = new PrometheusFileDumper(mbvar_filename, mbvar_prefix); + } + int nline = MVariableBase::dump_exposed(dumper, &options); + if (nline < 0) { + LOG(ERROR) << "Fail to dump mvars into " << filename; + } + delete dumper; + dumper = NULL; + } + + // We need to separate the sleeping into a long interruptible sleep + // and a short uninterruptible sleep. Doing this because we wake up + // this thread in gflag validators. If this thread dumps just after + // waking up from the condition, the gflags may not even be updated. + const int post_sleep_ms = 50; + int cond_sleep_ms = FLAGS_bvar_dump_interval * 1000 - post_sleep_ms; + if (cond_sleep_ms < 0) { + LOG(ERROR) << "Bad cond_sleep_ms=" << cond_sleep_ms; + cond_sleep_ms = 10000; + } + timespec deadline = butil::milliseconds_from_now(cond_sleep_ms); + pthread_mutex_lock(&dump_mutex); + pthread_cond_timedwait(&dump_cond, &dump_mutex, &deadline); + pthread_mutex_unlock(&dump_mutex); + usleep(post_sleep_ms * 1000); + } +} + +static void launch_dumping_thread() { + pthread_t thread_id; + int rc = pthread_create(&thread_id, NULL, dumping_thread, NULL); + if (rc != 0) { + LOG(FATAL) << "Fail to launch dumping thread: " << berror(rc); + return; + } + // Detach the thread because no one would join it. + CHECK_EQ(0, pthread_detach(thread_id)); + created_dumping_thread = true; +} + +// Start dumping_thread for only once. +static bool enable_dumping_thread() { + pthread_once(&dumping_thread_once, launch_dumping_thread); + return created_dumping_thread; +} + +static bool validate_bvar_dump(const char*, bool enabled) { + if (enabled) { + return enable_dumping_thread(); + } + return true; +} +BUTIL_VALIDATE_GFLAG(bvar_dump, validate_bvar_dump); + +// validators (to make these gflags reloadable in brpc) +static bool validate_bvar_dump_interval(const char*, int32_t v) { + // FIXME: -bvar_dump_interval is actually unreloadable but we need to + // check validity of it, so we still add this validator. In practice + // this is just fine since people rarely have the intention of modifying + // this flag at runtime. + if (v < 1) { + LOG(ERROR) << "Invalid bvar_dump_interval=" << v; + return false; + } + return true; +} +BUTIL_VALIDATE_GFLAG(bvar_dump_interval, validate_bvar_dump_interval); + +static bool wakeup_dumping_thread(const char*, const std::string&) { + // We're modifying a flag, wake up dumping_thread to generate + // a new file soon. + pthread_cond_signal(&dump_cond); + return true; +} + +const bool ALLOW_UNUSED dummy_bvar_dump_file = GFLAGS_NAMESPACE::RegisterFlagValidator( + &FLAGS_bvar_dump_file, wakeup_dumping_thread); +const bool ALLOW_UNUSED dummy_bvar_dump_filter = GFLAGS_NAMESPACE::RegisterFlagValidator( + &FLAGS_bvar_dump_include, wakeup_dumping_thread); +const bool ALLOW_UNUSED dummy_bvar_dump_exclude = GFLAGS_NAMESPACE::RegisterFlagValidator( + &FLAGS_bvar_dump_exclude, wakeup_dumping_thread); +const bool ALLOW_UNUSED dummy_bvar_dump_prefix = GFLAGS_NAMESPACE::RegisterFlagValidator( + &FLAGS_bvar_dump_prefix, wakeup_dumping_thread); +const bool ALLOW_UNUSED dummy_bvar_dump_tabs = GFLAGS_NAMESPACE::RegisterFlagValidator( + &FLAGS_bvar_dump_tabs, wakeup_dumping_thread); + +BUTIL_VALIDATE_GFLAG(mbvar_dump, validate_bvar_dump); +const bool ALLOW_UNUSED dummy_mbvar_dump_prefix = GFLAGS_NAMESPACE::RegisterFlagValidator( + &FLAGS_mbvar_dump_prefix, wakeup_dumping_thread); +const bool ALLOW_UNUSED dump_mbvar_dump_file = GFLAGS_NAMESPACE::RegisterFlagValidator( + &FLAGS_mbvar_dump_file, wakeup_dumping_thread); + +static bool validate_mbvar_dump_format(const char*, const std::string& format) { + if (format != "common" + && format != "prometheus") { + LOG(ERROR) << "Invalid mbvar_dump_format=" << format; + return false; + } + + // We're modifying a flag, wake up dumping_thread to generate + // a new file soon. + pthread_cond_signal(&dump_cond); + return true; +} + +const bool ALLOW_UNUSED dummy_mbvar_dump_format = GFLAGS_NAMESPACE::RegisterFlagValidator( + &FLAGS_mbvar_dump_format, validate_mbvar_dump_format); + +void to_underscored_name(std::string* name, const butil::StringPiece& src) { + name->reserve(name->size() + src.size() + 8/*just guess*/); + for (const char* p = src.data(); p != src.data() + src.size(); ++p) { + if (isalpha(*p)) { + if (*p < 'a') { // upper cases + if (p != src.data() && !isupper(p[-1]) && + butil::back_char(*name) != '_') { + name->push_back('_'); + } + name->push_back(*p - 'A' + 'a'); + } else { + name->push_back(*p); + } + } else if (isdigit(*p)) { + name->push_back(*p); + } else if (name->empty() || butil::back_char(*name) != '_') { + name->push_back('_'); + } + } +} + +// UT don't need default variables. +#if !defined(BVAR_NOT_LINK_DEFAULT_VARIABLES) +// Without these, default_variables.o are stripped. +// At least working in gcc 4.8 +extern int do_link_default_variables; +int dummy = do_link_default_variables; +#endif + +} // namespace bvar diff --git a/src/bvar/variable.h b/src/bvar/variable.h new file mode 100644 index 0000000..f01626f --- /dev/null +++ b/src/bvar/variable.h @@ -0,0 +1,266 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2014/09/22 11:57:43 + +#ifndef BVAR_VARIABLE_H +#define BVAR_VARIABLE_H + +#include // std::ostream +#include // std::string +#include // std::vector +#include +#include "butil/macros.h" // DISALLOW_COPY_AND_ASSIGN +#include "butil/strings/string_piece.h" // butil::StringPiece + +#ifdef BAIDU_INTERNAL +#include +#else +namespace boost { +class any; +} +#endif + +namespace bvar { + +DECLARE_bool(save_series); + +#define COMMON_VARIABLE_CONSTRUCTOR(TypeName) \ + TypeName() = default; \ + TypeName(const butil::StringPiece& name) { \ + this->expose(name); \ + } \ + TypeName(const butil::StringPiece& prefix, const butil::StringPiece& name) { \ + this->expose_as(prefix, name); \ + } \ + + +// Bitwise masks of displayable targets +enum DisplayFilter { + DISPLAY_ON_HTML = 1, + DISPLAY_ON_PLAIN_TEXT = 2, + DISPLAY_ON_ALL = 3, +}; + +// Implement this class to write variables into different places. +// If dump() returns false, Variable::dump_exposed() stops and returns -1. +class Dumper { +public: + virtual ~Dumper() { } + virtual bool dump(const std::string& name, + const butil::StringPiece& description) = 0; + // Only for dumping value of multiple dimension var to prometheus service + virtual bool dump_mvar(const std::string& name, + const butil::StringPiece& description) { + return true; + } + // Only for dumping comment of multiple dimension var to prometheus service + virtual bool dump_comment(const std::string&, const std::string& /*type*/) { + return true; + } +}; + +// Options for Variable::dump_exposed(). +struct DumpOptions { + // Constructed with default options. + DumpOptions(); + + // If this is true, string-type values will be quoted. + bool quote_string; + + // The ? in wildcards. Wildcards in URL need to use another character + // because ? is reserved. + char question_mark; + + // Dump variables with matched display_filter + DisplayFilter display_filter; + + // Name matched by these wildcards (or exact names) are kept. + std::string white_wildcards; + + // Name matched by these wildcards (or exact names) are skipped. + std::string black_wildcards; +}; + +struct SeriesOptions { + SeriesOptions() : fixed_length(true), test_only(false) {} + + bool fixed_length; // useless now + bool test_only; +}; + +// Base class of all bvar. +// +// About thread-safety: +// bvar is thread-compatible: +// Namely you can create/destroy/expose/hide or do whatever you want to +// different bvar simultaneously in different threads. +// bvar is NOT thread-safe: +// You should not operate one bvar from different threads simultaneously. +// If you need to, protect the ops with locks. Similarly with ordinary +// variables, const methods are thread-safe, namely you can call +// describe()/get_description()/get_value() etc from diferent threads +// safely (provided that there's no non-const methods going on). +class Variable { +public: + Variable() {} + virtual ~Variable(); + + // Implement this method to print the variable into ostream. + virtual void describe(std::ostream&, bool quote_string) const = 0; + + // string form of describe(). + std::string get_description() const; + +#ifdef BAIDU_INTERNAL + // Get value. + // If subclass does not override this method, the value is the description + // and the type is std::string. + virtual void get_value(boost::any* value) const; +#endif + + // Describe saved series as a json-string into the stream. + // The output will be ploted by flot.js + // Returns 0 on success, 1 otherwise(this variable does not save series). + virtual int describe_series(std::ostream&, const SeriesOptions&) const + { return 1; } + + // Expose this variable globally so that it's counted in following + // functions: + // list_exposed + // count_exposed + // describe_exposed + // find_exposed + // Return 0 on success, -1 otherwise. + int expose(const butil::StringPiece& name, + DisplayFilter display_filter = DISPLAY_ON_ALL) { + return expose_impl(butil::StringPiece(), name, display_filter); + } + + // Expose this variable with a prefix. + // Example: + // namespace foo { + // namespace bar { + // class ApplePie { + // ApplePie() { + // // foo_bar_apple_pie_error + // _error.expose_as("foo_bar_apple_pie", "error"); + // } + // private: + // bvar::Adder _error; + // }; + // } // foo + // } // bar + // Returns 0 on success, -1 otherwise. + int expose_as(const butil::StringPiece& prefix, + const butil::StringPiece& name, + DisplayFilter display_filter = DISPLAY_ON_ALL) { + return expose_impl(prefix, name, display_filter); + } + + // Hide this variable so that it's not counted in *_exposed functions. + // Returns false if this variable is already hidden. + // CAUTION!! Subclasses must call hide() manually to avoid displaying + // a variable that is just destructing. + bool hide(); + + // Check if this variable is is_hidden. + bool is_hidden() const; + + // Get exposed name. If this variable is not exposed, the name is empty. + const std::string& name() const { return _name; } + + // ==================================================================== + + // Put names of all exposed variables into `names'. + // If you want to print all variables, you have to go through `names' + // and call `describe_exposed' on each name. This prevents an iteration + // from taking the lock too long. + static void list_exposed(std::vector* names, + DisplayFilter = DISPLAY_ON_ALL); + + // Get number of exposed variables. + static size_t count_exposed(); + + // Find an exposed variable by `name' and put its description into `os'. + // Returns 0 on found, -1 otherwise. + static int describe_exposed(const std::string& name, + std::ostream& os, + bool quote_string = false, + DisplayFilter = DISPLAY_ON_ALL); + // String form. Returns empty string when not found. + static std::string describe_exposed(const std::string& name, + bool quote_string = false, + DisplayFilter = DISPLAY_ON_ALL); + + // Describe saved series of variable `name' as a json-string into `os'. + // The output will be ploted by flot.js + // Returns 0 on success, 1 when the variable does not save series, -1 + // otherwise (no variable named so). + static int describe_series_exposed(const std::string& name, + std::ostream&, + const SeriesOptions&); + +#ifdef BAIDU_INTERNAL + // Find an exposed variable by `name' and put its value into `value'. + // Returns 0 on found, -1 otherwise. + static int get_exposed(const std::string& name, boost::any* value); +#endif + + // Find all exposed variables matching `white_wildcards' but + // `black_wildcards' and send them to `dumper'. + // Use default options when `options' is NULL. + // Return number of dumped variables, -1 on error. + static int dump_exposed(Dumper* dumper, const DumpOptions* options); + +protected: + virtual int expose_impl(const butil::StringPiece& prefix, + const butil::StringPiece& name, + DisplayFilter display_filter); + +private: + std::string _name; + + // bvar uses TLS, thus copying/assignment need to copy TLS stuff as well, + // which is heavy. We disable copying/assignment now. + DISALLOW_COPY_AND_ASSIGN(Variable); +}; + +// Make name only use lowercased alphabets / digits / underscores, and append +// the result to `out'. +// Examples: +// foo-inl.h -> foo_inl_h +// foo::bar::Apple -> foo_bar_apple +// Car_Rot -> car_rot +// FooBar -> foo_bar +// RPCTest -> rpctest +// HELLO -> hello +void to_underscored_name(std::string* out, const butil::StringPiece& name); + +} // namespace bvar + +// Make variables printable. +namespace std { + +inline ostream& operator<<(ostream &os, const ::bvar::Variable &var) { + var.describe(os, false); + return os; +} + +} // namespace std + +#endif // BVAR_VARIABLE_H diff --git a/src/bvar/vector.h b/src/bvar/vector.h new file mode 100644 index 0000000..b76cee9 --- /dev/null +++ b/src/bvar/vector.h @@ -0,0 +1,121 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Sun Sep 20 12:25:11 CST 2015 + +#ifndef BVAR_VECTOR_H +#define BVAR_VECTOR_H + +#include +#include + +namespace bvar { + +DECLARE_bool(quote_vector); + +// Data inside a Vector will be plotted in a same graph. +template +class Vector { +public: + static const size_t WIDTH = N; + + Vector() { + for (size_t i = 0; i < N; ++i) { + _data[i] = T(); + } + } + + Vector(const T& initial_value) { + for (size_t i = 0; i < N; ++i) { + _data[i] = initial_value; + } + } + + void operator+=(const Vector& rhs) { + for (size_t i = 0; i < N; ++i) { + _data[i] += rhs._data[i]; + } + } + + void operator-=(const Vector& rhs) { + for (size_t i = 0; i < N; ++i) { + _data[i] -= rhs._data[i]; + } + } + + template + void operator*=(const S& scalar) { + for (size_t i = 0; i < N; ++i) { + _data[i] *= scalar; + } + } + + template + void operator/=(const S& scalar) { + for (size_t i = 0; i < N; ++i) { + _data[i] /= scalar; + } + } + + bool operator==(const Vector& rhs) const { + for (size_t i = 0; i < N; ++i) { + if (!(_data[i] == rhs._data[i])) { + return false; + } + } + return true; + } + + bool operator!=(const Vector& rhs) const { + return !operator==(rhs); + } + + T& operator[](int index) { return _data[index]; } + const T& operator[](int index) const { return _data[index]; } + +private: + T _data[N]; +}; + +template +std::ostream& operator<<(std::ostream& os, const Vector& vec) { + if (FLAGS_quote_vector) { + os << '"'; + } + os << '['; + if (N != 0) { + os << vec[0]; + for (size_t i = 1; i < N; ++i) { + os << ',' << vec[i]; + } + } + os << ']'; + if (FLAGS_quote_vector) { + os << '"'; + } + return os; +} + +template +struct is_vector : public butil::false_type {}; + +template +struct is_vector > : public butil::true_type {}; + +} // namespace bvar + +#endif //BVAR_VECTOR_H diff --git a/src/bvar/window.h b/src/bvar/window.h new file mode 100644 index 0000000..e0e02e5 --- /dev/null +++ b/src/bvar/window.h @@ -0,0 +1,362 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Wed Jul 29 23:25:43 CST 2015 + +#ifndef BVAR_WINDOW_H +#define BVAR_WINDOW_H + +#include // std::numeric_limits +#include // round +#include +#include "butil/logging.h" // LOG +#include "bvar/detail/sampler.h" +#include "bvar/detail/series.h" +#include "bvar/variable.h" + +namespace bvar { + +DECLARE_int32(bvar_dump_interval); + +enum SeriesFrequency { + SERIES_IN_WINDOW = 0, + SERIES_IN_SECOND = 1 +}; + +namespace detail { +// Just for constructor reusing of Window<> +template +class WindowBase : public Variable { +public: + typedef typename R::value_type value_type; + typedef typename R::sampler_type sampler_type; + + class SeriesSampler : public detail::Sampler { + public: + struct Op { + explicit Op(R* var) : _var(var) {} + void operator()(value_type& v1, const value_type& v2) const { + _var->op()(v1, v2); + } + private: + R* _var; + }; + SeriesSampler(WindowBase* owner, R* var) + : _owner(owner), _series(Op(var)) {} + ~SeriesSampler() {} + void take_sample() override { + if (series_freq == SERIES_IN_SECOND) { + // Get one-second window value for PerSecond<>, otherwise the + // "smoother" plot may hide peaks. + _series.append(_owner->get_value(1)); + } else { + // Get the value inside the full window. "get_value(1)" is + // incorrect when users intend to see aggregated values of + // the full window in the plot. + _series.append(_owner->get_value()); + } + } + void describe(std::ostream& os) { _series.describe(os, NULL); } + private: + WindowBase* _owner; + detail::Series _series; + }; + + WindowBase(R* var, time_t window_size) + : _var(var) + , _window_size(window_size > 0 ? window_size : FLAGS_bvar_dump_interval) + , _sampler(var->get_sampler()) + , _series_sampler(NULL) { + CHECK_EQ(0, _sampler->set_window_size(_window_size)); + } + + ~WindowBase() { + hide(); + if (_series_sampler) { + _series_sampler->destroy(); + _series_sampler = NULL; + } + } + + bool get_span(time_t window_size, detail::Sample* result) const { + return _sampler->get_value(window_size, result); + } + + bool get_span(detail::Sample* result) const { + return get_span(_window_size, result); + } + + virtual value_type get_value(time_t window_size) const { + detail::Sample tmp; + if (get_span(window_size, &tmp)) { + return tmp.data; + } + return value_type(); + } + + value_type get_value() const { return get_value(_window_size); } + + void describe(std::ostream& os, bool quote_string) const override { + if (butil::is_same::value && quote_string) { + os << '"' << get_value() << '"'; + } else { + os << get_value(); + } + } + +#ifdef BAIDU_INTERNAL + void get_value(boost::any* value) const override { *value = get_value(); } +#endif + + time_t window_size() const { return _window_size; } + + int describe_series(std::ostream& os, const SeriesOptions& options) const override { + if (_series_sampler == NULL) { + return 1; + } + if (!options.test_only) { + _series_sampler->describe(os); + } + return 0; + } + + void get_samples(std::vector *samples) const { + samples->clear(); + samples->reserve(_window_size); + return _sampler->get_samples(samples, _window_size); + } + +protected: + int expose_impl(const butil::StringPiece& prefix, + const butil::StringPiece& name, + DisplayFilter display_filter) override { + const int rc = Variable::expose_impl(prefix, name, display_filter); + if (rc == 0 && + _series_sampler == NULL && + FLAGS_save_series) { + _series_sampler = new SeriesSampler(this, _var); + _series_sampler->schedule(); + } + return rc; + } + + R* _var; + time_t _window_size; + sampler_type* _sampler; + SeriesSampler* _series_sampler; +}; + +} // namespace detail + +// Get data within a time window. +// The time unit is 1 second fixed. +// Window relies on other bvar which should be constructed before this window +// and destructs after this window. + +// R must: +// - have get_sampler() (not require thread-safe) +// - defined value_type and sampler_type +template +class Window : public detail::WindowBase { + typedef detail::WindowBase Base; + typedef typename R::value_type value_type; + typedef typename R::sampler_type sampler_type; +public: + // Different from PerSecond, we require window_size here because get_value + // of Window is largely affected by window_size while PerSecond is not. + Window(R* var, time_t window_size) : Base(var, window_size) {} + Window(const butil::StringPiece& name, R* var, time_t window_size) + : Base(var, window_size) { + this->expose(name); + } + Window(const butil::StringPiece& prefix, + const butil::StringPiece& name, R* var, time_t window_size) + : Base(var, window_size) { + this->expose_as(prefix, name); + } +}; + +// Get data per second within a time window. +// The only difference between PerSecond and Window is that PerSecond divides +// the data by time duration. +template +class PerSecond : public detail::WindowBase { + typedef detail::WindowBase Base; + typedef typename R::value_type value_type; + typedef typename R::sampler_type sampler_type; +public: + // If window_size is non-positive or absent, use FLAGS_bvar_dump_interval. + PerSecond(R* var) : Base(var, -1) {} + PerSecond(R* var, time_t window_size) : Base(var, window_size) {} + PerSecond(const butil::StringPiece& name, R* var) : Base(var, -1) { + this->expose(name); + } + PerSecond(const butil::StringPiece& name, R* var, time_t window_size) + : Base(var, window_size) { + this->expose(name); + } + PerSecond(const butil::StringPiece& prefix, + const butil::StringPiece& name, R* var) + : Base(var, -1) { + this->expose_as(prefix, name); + } + PerSecond(const butil::StringPiece& prefix, + const butil::StringPiece& name, R* var, time_t window_size) + : Base(var, window_size) { + this->expose_as(prefix, name); + } + + value_type get_value(time_t window_size) const override { + detail::Sample s; + this->get_span(window_size, &s); + // We may test if the multiplication overflows and use integral ops + // if possible. However signed/unsigned 32-bit/64-bit make the solution + // complex. Since this function is not called often, we use floating + // point for simplicity. + if (s.time_us <= 0) { + return static_cast(0); + } + if (butil::is_floating_point::value) { + return static_cast(s.data * 1000000.0 / s.time_us); + } else { + return static_cast(round(s.data * 1000000.0 / s.time_us)); + } + } + + value_type get_value() const { return Base::get_value(); } +}; + +namespace adapter { + +template +class WindowExType { +public: + typedef R var_type; + typedef bvar::Window window_type; + typedef typename R::value_type value_type; + struct WindowExVar { + WindowExVar(time_t window_size) : window(&var, window_size) {} + var_type var; + window_type window; + }; +}; + +template +class PerSecondExType { +public: + typedef R var_type; + typedef bvar::PerSecond window_type; + typedef typename R::value_type value_type; + struct WindowExVar { + WindowExVar(time_t window_size) : window(&var, window_size) {} + var_type var; + window_type window; + }; +}; + +template +class WindowExAdapter : public Variable{ +public: + typedef typename R::value_type value_type; + typedef typename WindowType::WindowExVar WindowExVar; + + WindowExAdapter(time_t window_size) + : _window_size(window_size > 0 ? window_size : FLAGS_bvar_dump_interval) + , _window_ex_var(_window_size) { + } + + value_type get_value() const { + return _window_ex_var.window.get_value(); + } + + template + WindowExAdapter& operator<<(ANT_TYPE value) { + _window_ex_var.var << value; + return *this; + } + + // Implement Variable::describe() + void describe(std::ostream& os, bool quote_string) const { + if (butil::is_same::value && quote_string) { + os << '"' << get_value() << '"'; + } else { + os << get_value(); + } + } + + virtual ~WindowExAdapter() { + hide(); + } + +private: + time_t _window_size; + WindowExVar _window_ex_var; +}; + +} // namespace adapter + +// Get data within a time window. +// The time unit is 1 second fixed. +// Window not relies on other bvar. + +// R must: +// - window_size must be a constant +template +class WindowEx : public adapter::WindowExAdapter > { +public: + typedef adapter::WindowExAdapter > Base; + + WindowEx() : Base(window_size) {} + + WindowEx(const butil::StringPiece& name) : Base(window_size) { + this->expose(name); + } + + WindowEx(const butil::StringPiece& prefix, + const butil::StringPiece& name) + : Base(window_size) { + this->expose_as(prefix, name); + } +}; + +// Get data per second within a time window. +// The only difference between PerSecondEx and WindowEx is that PerSecondEx divides +// the data by time duration. + +// R must: +// - window_size must be a constant +template +class PerSecondEx : public adapter::WindowExAdapter > { +public: + typedef adapter::WindowExAdapter > Base; + + PerSecondEx() : Base(window_size) {} + + PerSecondEx(const butil::StringPiece& name) : Base(window_size) { + this->expose(name); + } + + PerSecondEx(const butil::StringPiece& prefix, + const butil::StringPiece& name) + : Base(window_size) { + this->expose_as(prefix, name); + } +}; + +} // namespace bvar + +#endif //BVAR_WINDOW_H diff --git a/src/idl_options.proto b/src/idl_options.proto new file mode 100644 index 0000000..5687699 --- /dev/null +++ b/src/idl_options.proto @@ -0,0 +1,54 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +// mcpack2pb - Make protobuf be front-end of mcpack/compack + +import "google/protobuf/descriptor.proto"; + +extend google.protobuf.FileOptions { + // True to generate mcpack parsing/serializing code + optional bool idl_support = 91000; +} + +enum ConvertibleIdlType { + IDL_AUTO = 0; + IDL_INT8 = 1; + IDL_INT16 = 2; + IDL_INT32 = 3; + IDL_INT64 = 4; + IDL_UINT8 = 5; + IDL_UINT16 = 6; + IDL_UINT32 = 7; + IDL_UINT64 = 8; + IDL_BOOL = 9; + IDL_FLOAT = 10; + IDL_DOUBLE = 11; + IDL_BINARY = 12; + IDL_STRING = 13; +} + +extend google.protobuf.FieldOptions { + // Mark the idl-type which is inconsistent with proto-type. + optional ConvertibleIdlType idl_type = 91001; + + // Mark the non-optional() vector/array in idl. + optional int32 idl_on = 91002; + + // Use this name as the field name for packing instead of the one in proto. + optional string idl_name = 91003; +} diff --git a/src/json2pb/encode_decode.cpp b/src/json2pb/encode_decode.cpp new file mode 100644 index 0000000..95b7819 --- /dev/null +++ b/src/json2pb/encode_decode.cpp @@ -0,0 +1,114 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include "encode_decode.h" + +namespace json2pb { + +inline int match_pattern(const std::string& str, int index) { + //pattern: _Zxxx_ + const char digit = '0'; + const int pattern_length = 6; + + int length = str.size(); + if (length <= index || length - index < pattern_length) { + return -1; + } + if (str[index] != '_' || + str[index + 1] != 'Z' || + str[index + 5] != '_' || + !isdigit(str[index + 2]) || + !isdigit(str[index + 3]) || + !isdigit(str[index + 4])) { + return -1; + } + int sum = (str[index + 2] - digit) * 100 + (str[index + 3] - digit) * 10 + (str[index + 4] - digit); + return sum < 256 ? sum : -1; +} + +bool encode_name(const std::string& content, std::string& encoded_content) { + int index = 0; + size_t begin = 0; + bool convert = false; + for (std::string::const_iterator it = content.begin(); it != content.end(); ++it, ++index) { + if ((!isalnum(*it) && + (*it != '_')) || + (it == content.begin() && + isdigit(*it))) { + if (!convert) { + encoded_content.clear(); + encoded_content.reserve(2*content.size()); + convert = true; + } + encoded_content.append(content, begin, index - begin); + begin = index + 1; + + char pattern[6]; + pattern[0] = '_'; + pattern[1] = 'Z'; + int first = *it / 100; + int second = (*it - first * 100) / 10; + int third = *it - first * 100 - second * 10; + pattern[2] = first + '0'; + pattern[3] = second + '0'; + pattern[4] = third + '0'; + pattern[5] = '_'; + encoded_content.append(pattern, sizeof(pattern)); + } + } + if (!convert) { + return false; + } else { + encoded_content.append(content, begin, index - begin); + return true; + } +} + +bool decode_name(const std::string& content, std::string& decoded_content) { + const int pattern_length = 6; + int begin = 0; + int second = 0; + bool convert = false; + for (std::string::const_iterator it = content.begin(); it < content.end(); ++it, ++second) { + if (*it != '_') { + continue; + } + int val = match_pattern(content, second); + if (val != -1) { + if (!convert) { + decoded_content.clear(); + decoded_content.reserve(content.size()); + convert = true; + } + decoded_content.append(content, begin, second - begin); + decoded_content.push_back(static_cast(val)); + second += pattern_length - 1; + begin = second + 1; + it += pattern_length - 1; + } + } + if (!convert) { + return false; + } else { + decoded_content.append(content, begin, second - begin); + return true; + } +} + +} // namespace json2pb diff --git a/src/json2pb/encode_decode.h b/src/json2pb/encode_decode.h new file mode 100644 index 0000000..7a2ba3a --- /dev/null +++ b/src/json2pb/encode_decode.h @@ -0,0 +1,43 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +#ifndef BRPC_JSON2PB_ENCODE_DECODE_H +#define BRPC_JSON2PB_ENCODE_DECODE_H + +namespace json2pb { + +//pattern: _Zxxx_ +//rules: keep original lower-case characters, upper-case characters, +//digital charactors and '_' in the original position, +//change other special characters to '_Zxxx_', +//xxx is the character's decimal digit +//fg: 'abc123_ABC-' convert to 'abc123_ABC_Z045_' + +//params: content: content need to encode +//params: encoded_content: content encoded +//return value: false: no need to encode, true: need to encode. +//note: when return value is false, no change in encoded_content. +bool encode_name(const std::string& content, std::string & encoded_content); + +//params: content: content need to decode +//params: decoded_content: content decoded +//return value: false: no need to decode, true: need to decode. +//note: when return value is false, no change in decoded_content. +bool decode_name(const std::string& content, std::string& decoded_content); + +} + +#endif //BRPC_JSON2PB_ENCODE_DECODE_H diff --git a/src/json2pb/json_to_pb.cpp b/src/json2pb/json_to_pb.cpp new file mode 100644 index 0000000..00e12d9 --- /dev/null +++ b/src/json2pb/json_to_pb.cpp @@ -0,0 +1,776 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "butil/strings/string_number_conversions.h" +#include "butil/strings/string_util.h" +#include "butil/third_party/rapidjson/error/error.h" +#include "butil/third_party/rapidjson/rapidjson.h" +#include "json2pb/json_to_pb.h" +#include "json2pb/zero_copy_stream_reader.h" // ZeroCopyStreamReader +#include "json2pb/encode_decode.h" +#include "json2pb/protobuf_map.h" +#include "json2pb/rapidjson.h" +#include "json2pb/protobuf_type_resolver.h" +#include "butil/base64.h" +#include "butil/iobuf.h" + +#ifdef __GNUC__ +// Ignore -Wnonnull for `(::google::protobuf::Message*)nullptr' of J2PERROR by design. +#pragma GCC diagnostic ignored "-Wnonnull" +#endif + +#define J2PERROR(perr, fmt, ...) \ + J2PERROR_WITH_PB((::google::protobuf::Message*)nullptr, perr, fmt, ##__VA_ARGS__) + +#define J2PERROR_WITH_PB(pb, perr, fmt, ...) \ + if (perr) { \ + if (!perr->empty()) { \ + perr->append(", ", 2); \ + } \ + butil::string_appendf(perr, fmt, ##__VA_ARGS__); \ + if ((pb) != nullptr) { \ + butil::string_appendf(perr, " [%s]", \ + butil::EnsureString((pb)->GetDescriptor()->name()).c_str()); \ + } \ + } else { } + +namespace json2pb { + +// Use iterative parsing to avoid stack overflow. +const int RAPIDJSON_PARSE_FLAG_DEFAULT = BUTIL_RAPIDJSON_NAMESPACE::kParseIterativeFlag; +const int RAPIDJSON_PARSE_FLAG_STOP_WHEN_DONE = BUTIL_RAPIDJSON_NAMESPACE::kParseStopWhenDoneFlag | RAPIDJSON_PARSE_FLAG_DEFAULT; + +DEFINE_int32(json2pb_max_recursion_depth, 100, "Maximum recursion depth of JSON parser"); + +Json2PbOptions::Json2PbOptions() +#ifdef BAIDU_INTERNAL + : base64_to_bytes(false) +#else + : base64_to_bytes(true) +#endif + , array_to_single_repeated(false) + , allow_remaining_bytes_after_parsing(false) { +} + +enum MatchType { + TYPE_MATCH = 0x00, + REQUIRED_OR_REPEATED_TYPE_MISMATCH = 0x01, + OPTIONAL_TYPE_MISMATCH = 0x02 +}; + +static void string_append_value(const BUTIL_RAPIDJSON_NAMESPACE::Value& value, + std::string* output) { + if (value.IsNull()) { + output->append("null"); + } else if (value.IsBool()) { + output->append(value.GetBool() ? "true" : "false"); + } else if (value.IsInt()) { + butil::string_appendf(output, "%d", value.GetInt()); + } else if (value.IsUint()) { + butil::string_appendf(output, "%u", value.GetUint()); + } else if (value.IsInt64()) { + butil::string_appendf(output, "%" PRId64, value.GetInt64()); + } else if (value.IsUint64()) { + butil::string_appendf(output, "%" PRIu64, value.GetUint64()); + } else if (value.IsDouble()) { + butil::string_appendf(output, "%f", value.GetDouble()); + } else if (value.IsString()) { + output->push_back('"'); + output->append(value.GetString(), value.GetStringLength()); + output->push_back('"'); + } else if (value.IsArray()) { + output->append("array"); + } else if (value.IsObject()) { + output->append("object"); + } +} + +//It will be called when type mismatch occurs, fg: convert string to uint, +//and will also be called when invalid value appears, fg: invalid enum name, +//invalid enum number, invalid string content to convert to double or float. +//for optional field error will just append error into error message +//and ends with ',' and return true. +//otherwise will append error into error message and return false. +inline bool value_invalid(const google::protobuf::FieldDescriptor* field, const char* type, + const BUTIL_RAPIDJSON_NAMESPACE::Value& value, std::string* err) { + bool optional = !field->is_required() && !field->is_repeated(); + if (err) { + if (!err->empty()) { + err->append(", "); + } + err->append("Invalid value `"); + string_append_value(value, err); + butil::string_appendf(err, "' for %sfield `%s' which SHOULD be %s", + optional ? "optional " : "", + butil::EnsureString(field->full_name()).c_str(), type); + } + if (!optional) { + return false; + } + return true; +} + +template +inline bool convert_string_to_double_float_type( + void (google::protobuf::Reflection::*func)( + google::protobuf::Message* message, + const google::protobuf::FieldDescriptor* field, T value) const, + google::protobuf::Message* message, + const google::protobuf::FieldDescriptor* field, + const google::protobuf::Reflection* reflection, + const BUTIL_RAPIDJSON_NAMESPACE::Value& item, + std::string* err) { + const char* limit_type = item.GetString(); // MUST be string here + if (std::numeric_limits::has_quiet_NaN && + strcasecmp(limit_type, "NaN") == 0) { + (reflection->*func)(message, field, std::numeric_limits::quiet_NaN()); + return true; + } + if (std::numeric_limits::has_infinity && + strcasecmp(limit_type, "Infinity") == 0) { + (reflection->*func)(message, field, std::numeric_limits::infinity()); + return true; + } + if (std::numeric_limits::has_infinity && + strcasecmp(limit_type, "-Infinity") == 0) { + (reflection->*func)(message, field, -std::numeric_limits::infinity()); + return true; + } + return value_invalid(field, typeid(T).name(), item, err); +} + +inline bool convert_float_type(const BUTIL_RAPIDJSON_NAMESPACE::Value& item, bool repeated, + google::protobuf::Message* message, + const google::protobuf::FieldDescriptor* field, + const google::protobuf::Reflection* reflection, + std::string* err) { + if (item.IsNumber()) { + if (repeated) { + reflection->AddFloat(message, field, item.GetDouble()); + } else { + reflection->SetFloat(message, field, item.GetDouble()); + } + } else if (item.IsString()) { + if (!convert_string_to_double_float_type( + (repeated ? &google::protobuf::Reflection::AddFloat + : &google::protobuf::Reflection::SetFloat), + message, field, reflection, item, err)) { + return false; + } + } else { + return value_invalid(field, "float", item, err); + } + return true; +} + +inline bool convert_double_type(const BUTIL_RAPIDJSON_NAMESPACE::Value& item, bool repeated, + google::protobuf::Message* message, + const google::protobuf::FieldDescriptor* field, + const google::protobuf::Reflection* reflection, + std::string* err) { + if (item.IsNumber()) { + if (repeated) { + reflection->AddDouble(message, field, item.GetDouble()); + } else { + reflection->SetDouble(message, field, item.GetDouble()); + } + } else if (item.IsString()) { + if (!convert_string_to_double_float_type( + (repeated ? &google::protobuf::Reflection::AddDouble + : &google::protobuf::Reflection::SetDouble), + message, field, reflection, item, err)) { + return false; + } + } else { + return value_invalid(field, "double", item, err); + } + return true; +} + +inline bool convert_enum_type(const BUTIL_RAPIDJSON_NAMESPACE::Value&item, bool repeated, + google::protobuf::Message* message, + const google::protobuf::FieldDescriptor* field, + const google::protobuf::Reflection* reflection, + std::string* err) { + const google::protobuf::EnumValueDescriptor * enum_value_descriptor = NULL; + if (item.IsInt()) { + enum_value_descriptor = field->enum_type()->FindValueByNumber(item.GetInt()); + } else if (item.IsString()) { + enum_value_descriptor = field->enum_type()->FindValueByName(item.GetString()); + } + if (!enum_value_descriptor) { + return value_invalid(field, "enum", item, err); + } + if (repeated) { + reflection->AddEnum(message, field, enum_value_descriptor); + } else { + reflection->SetEnum(message, field, enum_value_descriptor); + } + return true; +} + +inline bool convert_int64_type(const BUTIL_RAPIDJSON_NAMESPACE::Value& item, bool repeated, + google::protobuf::Message* message, + const google::protobuf::FieldDescriptor* field, + const google::protobuf::Reflection* reflection, + std::string* err) { + + int64_t num; + if (item.IsInt64()) { + if (repeated) { + reflection->AddInt64(message, field, item.GetInt64()); + } else { + reflection->SetInt64(message, field, item.GetInt64()); + } + } else if (item.IsString() && + butil::StringToInt64({item.GetString(), item.GetStringLength()}, + &num)) { + if (repeated) { + reflection->AddInt64(message, field, num); + } else { + reflection->SetInt64(message, field, num); + } + } else { + return value_invalid(field, "INT64", item, err); + } + return true; +} + +inline bool convert_uint64_type(const BUTIL_RAPIDJSON_NAMESPACE::Value& item, + bool repeated, + google::protobuf::Message* message, + const google::protobuf::FieldDescriptor* field, + const google::protobuf::Reflection* reflection, + std::string* err) { + uint64_t num; + if (item.IsUint64()) { + if (repeated) { + reflection->AddUInt64(message, field, item.GetUint64()); + } else { + reflection->SetUInt64(message, field, item.GetUint64()); + } + } else if (item.IsString() && + butil::StringToUint64({item.GetString(), item.GetStringLength()}, + &num)) { + if (repeated) { + reflection->AddUInt64(message, field, num); + } else { + reflection->SetUInt64(message, field, num); + } + } else { + return value_invalid(field, "UINT64", item, err); + } + return true; +} + +bool JsonValueToProtoMessage(const BUTIL_RAPIDJSON_NAMESPACE::Value& json_value, + google::protobuf::Message* message, + const Json2PbOptions& options, + std::string* err, + int depth); +//Json value to protobuf convert rules for type: +//Json value type Protobuf type convert rules +//int int uint int64 uint64 valid convert is available +//uint int uint int64 uint64 valid convert is available +//int64 int uint int64 uint64 valid convert is available +//uint64 int uint int64 uint64 valid convert is available +//int uint int64 uint64 float double available +//"NaN" "Infinity" "-Infinity" float double only "NaN" "Infinity" "-Infinity" is available +//int enum valid enum number value is available +//string enum valid enum name value is available +//string int64 uint64 valid convert is available +//other mismatch type convertion will be regarded as error. +#define J2PCHECKTYPE(value, cpptype, jsontype) ({ \ + MatchType match_type = TYPE_MATCH; \ + if (!value.Is##jsontype()) { \ + match_type = OPTIONAL_TYPE_MISMATCH; \ + if (!value_invalid(field, #cpptype, value, err)) { \ + return false; \ + } \ + } \ + match_type; \ + }) + + +static bool JsonValueToProtoField(const BUTIL_RAPIDJSON_NAMESPACE::Value& value, + const google::protobuf::FieldDescriptor* field, + google::protobuf::Message* message, + const Json2PbOptions& options, + std::string* err, + int depth) { + if (value.IsNull()) { + if (field->is_required()) { + J2PERROR(err, "Missing required field: %s", butil::EnsureString(field->full_name()).c_str()); + return false; + } + return true; + } + + if (field->is_repeated()) { + if (!value.IsArray()) { + J2PERROR(err, "Invalid value for repeated field: %s", + butil::EnsureString(field->full_name()).c_str()); + return false; + } + } + + const google::protobuf::Reflection* reflection = message->GetReflection(); + switch (field->cpp_type()) { +#define CASE_FIELD_TYPE(cpptype, method, jsontype) \ + case google::protobuf::FieldDescriptor::CPPTYPE_##cpptype: { \ + if (field->is_repeated()) { \ + const BUTIL_RAPIDJSON_NAMESPACE::SizeType size = value.Size(); \ + for (BUTIL_RAPIDJSON_NAMESPACE::SizeType index = 0; index < size; ++index) { \ + const BUTIL_RAPIDJSON_NAMESPACE::Value & item = value[index]; \ + if (TYPE_MATCH == J2PCHECKTYPE(item, cpptype, jsontype)) { \ + reflection->Add##method(message, field, item.Get##jsontype()); \ + } \ + } \ + } else if (TYPE_MATCH == J2PCHECKTYPE(value, cpptype, jsontype)) { \ + reflection->Set##method(message, field, value.Get##jsontype()); \ + } \ + break; \ + } \ + + CASE_FIELD_TYPE(INT32, Int32, Int); + CASE_FIELD_TYPE(UINT32, UInt32, Uint); + CASE_FIELD_TYPE(BOOL, Bool, Bool); +#undef CASE_FIELD_TYPE + + case google::protobuf::FieldDescriptor::CPPTYPE_INT64: + if (field->is_repeated()) { + const BUTIL_RAPIDJSON_NAMESPACE::SizeType size = value.Size(); + for (BUTIL_RAPIDJSON_NAMESPACE::SizeType index = 0; index < size; + ++index) { + const BUTIL_RAPIDJSON_NAMESPACE::Value& item = value[index]; + if (!convert_int64_type(item, true, message, field, reflection, + err)) { + return false; + } + } + } else if (!convert_int64_type(value, false, message, field, reflection, + err)) { + return false; + } + break; + + case google::protobuf::FieldDescriptor::CPPTYPE_UINT64: + if (field->is_repeated()) { + const BUTIL_RAPIDJSON_NAMESPACE::SizeType size = value.Size(); + for (BUTIL_RAPIDJSON_NAMESPACE::SizeType index = 0; index < size; + ++index) { + const BUTIL_RAPIDJSON_NAMESPACE::Value& item = value[index]; + if (!convert_uint64_type(item, true, message, field, reflection, + err)) { + return false; + } + } + } else if (!convert_uint64_type(value, false, message, field, reflection, + err)) { + return false; + } + break; + + case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT: + if (field->is_repeated()) { + const BUTIL_RAPIDJSON_NAMESPACE::SizeType size = value.Size(); + for (BUTIL_RAPIDJSON_NAMESPACE::SizeType index = 0; index < size; ++index) { + const BUTIL_RAPIDJSON_NAMESPACE::Value & item = value[index]; + if (!convert_float_type(item, true, message, field, + reflection, err)) { + return false; + } + } + } else if (!convert_float_type(value, false, message, field, + reflection, err)) { + return false; + } + break; + + case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE: + if (field->is_repeated()) { + const BUTIL_RAPIDJSON_NAMESPACE::SizeType size = value.Size(); + for (BUTIL_RAPIDJSON_NAMESPACE::SizeType index = 0; index < size; ++index) { + const BUTIL_RAPIDJSON_NAMESPACE::Value & item = value[index]; + if (!convert_double_type(item, true, message, field, + reflection, err)) { + return false; + } + } + } else if (!convert_double_type(value, false, message, field, + reflection, err)) { + return false; + } + break; + + case google::protobuf::FieldDescriptor::CPPTYPE_STRING: + if (field->is_repeated()) { + const BUTIL_RAPIDJSON_NAMESPACE::SizeType size = value.Size(); + for (BUTIL_RAPIDJSON_NAMESPACE::SizeType index = 0; index < size; ++index) { + const BUTIL_RAPIDJSON_NAMESPACE::Value & item = value[index]; + if (TYPE_MATCH == J2PCHECKTYPE(item, string, String)) { + std::string str(item.GetString(), item.GetStringLength()); + if (field->type() == google::protobuf::FieldDescriptor::TYPE_BYTES && + options.base64_to_bytes) { + std::string str_decoded; + if (!butil::Base64Decode(str, &str_decoded)) { + J2PERROR_WITH_PB(message, err, "Fail to decode base64 string=%s", str.c_str()); + return false; + } + str = str_decoded; + } + reflection->AddString(message, field, str); + } + } + } else if (TYPE_MATCH == J2PCHECKTYPE(value, string, String)) { + std::string str(value.GetString(), value.GetStringLength()); + if (field->type() == google::protobuf::FieldDescriptor::TYPE_BYTES && + options.base64_to_bytes) { + std::string str_decoded; + if (!butil::Base64Decode(str, &str_decoded)) { + J2PERROR_WITH_PB(message, err, "Fail to decode base64 string=%s", str.c_str()); + return false; + } + str = str_decoded; + } + reflection->SetString(message, field, str); + } + break; + + case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: + if (field->is_repeated()) { + const BUTIL_RAPIDJSON_NAMESPACE::SizeType size = value.Size(); + for (BUTIL_RAPIDJSON_NAMESPACE::SizeType index = 0; index < size; ++index) { + const BUTIL_RAPIDJSON_NAMESPACE::Value & item = value[index]; + if (!convert_enum_type(item, true, message, field, + reflection, err)) { + return false; + } + } + } else if (!convert_enum_type(value, false, message, field, + reflection, err)) { + return false; + } + break; + + case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: + if (field->is_repeated()) { + const BUTIL_RAPIDJSON_NAMESPACE::SizeType size = value.Size(); + for (BUTIL_RAPIDJSON_NAMESPACE::SizeType index = 0; index < size; ++index) { + const BUTIL_RAPIDJSON_NAMESPACE::Value& item = value[index]; + if (TYPE_MATCH == J2PCHECKTYPE(item, message, Object)) { + if (!JsonValueToProtoMessage( + item, reflection->AddMessage(message, field), options, err, depth + 1)) { + return false; + } + } + } + } else if (!JsonValueToProtoMessage( + value, reflection->MutableMessage(message, field), options, err, depth + 1)) { + return false; + } + break; + } + return true; +} + +bool JsonMapToProtoMap(const BUTIL_RAPIDJSON_NAMESPACE::Value& value, + const google::protobuf::FieldDescriptor* map_desc, + google::protobuf::Message* message, + const Json2PbOptions& options, + std::string* err, + int depth) { + if (!value.IsObject()) { + J2PERROR(err, "Non-object value for map field: %s", + butil::EnsureString(map_desc->full_name()).c_str()); + return false; + } + + const google::protobuf::Reflection* reflection = message->GetReflection(); + const google::protobuf::FieldDescriptor* key_desc = + map_desc->message_type()->FindFieldByName(KEY_NAME); + const google::protobuf::FieldDescriptor* value_desc = + map_desc->message_type()->FindFieldByName(VALUE_NAME); + + for (BUTIL_RAPIDJSON_NAMESPACE::Value::ConstMemberIterator it = + value.MemberBegin(); it != value.MemberEnd(); ++it) { + google::protobuf::Message* entry = reflection->AddMessage(message, map_desc); + const google::protobuf::Reflection* entry_reflection = entry->GetReflection(); + entry_reflection->SetString( + entry, key_desc, std::string(it->name.GetString(), + it->name.GetStringLength())); + if (!JsonValueToProtoField(it->value, value_desc, entry, options, err, depth + 1)) { + return false; + } + } + return true; +} + +bool JsonValueToProtoMessage(const BUTIL_RAPIDJSON_NAMESPACE::Value& json_value, + google::protobuf::Message* message, + const Json2PbOptions& options, + std::string* err, + int depth) { + if (depth > FLAGS_json2pb_max_recursion_depth) { + J2PERROR_WITH_PB(message, err, "Exceeded maximum recursion depth"); + return false; + } + const google::protobuf::Descriptor* descriptor = message->GetDescriptor(); + if (!json_value.IsObject() && + !(json_value.IsArray() && options.array_to_single_repeated && depth == 0)) { + J2PERROR_WITH_PB(message, err, "The input is not a json object"); + return false; + } + + const google::protobuf::Reflection* reflection = message->GetReflection(); + + std::vector fields; + fields.reserve(64); + for (int i = 0; i < descriptor->extension_range_count(); ++i) { + const google::protobuf::Descriptor::ExtensionRange* + ext_range = descriptor->extension_range(i); +#if GOOGLE_PROTOBUF_VERSION < 4024000 + for (int tag_number = ext_range->start; tag_number < ext_range->end; ++tag_number) +#else + for (int tag_number = ext_range->start_number(); tag_number < ext_range->end_number(); ++tag_number) +#endif + { + const google::protobuf::FieldDescriptor* field = + reflection->FindKnownExtensionByNumber(tag_number); + if (field) { + fields.push_back(field); + } + } + } + for (int i = 0; i < descriptor->field_count(); ++i) { + fields.push_back(descriptor->field(i)); + } + + if (json_value.IsArray()) { + if (fields.size() == 1 && fields.front()->is_repeated()) { + return JsonValueToProtoField(json_value, fields.front(), message, options, err, depth); + } + + J2PERROR_WITH_PB(message, err, "the input json can't be array here"); + return false; + } + + std::string field_name_str_temp; + const BUTIL_RAPIDJSON_NAMESPACE::Value* value_ptr = NULL; + for (size_t i = 0; i < fields.size(); ++i) { + const google::protobuf::FieldDescriptor* field = fields[i]; + + const std::string orig_name = butil::EnsureString(field->name()); + bool res = decode_name(orig_name, field_name_str_temp); + const std::string& field_name_str = (res ? field_name_str_temp : orig_name); + +#ifndef RAPIDJSON_VERSION_0_1 + BUTIL_RAPIDJSON_NAMESPACE::Value::ConstMemberIterator member = + json_value.FindMember(field_name_str.data()); + if (member == json_value.MemberEnd()) { + if (field->is_required()) { + J2PERROR(err, "Missing required field: %s", butil::EnsureString(field->full_name()).c_str()); + return false; + } + continue; + } + value_ptr = &(member->value); +#else + const BUTIL_RAPIDJSON_NAMESPACE::Value::Member* member = + json_value.FindMember(field_name_str.data()); + if (member == NULL) { + if (field->is_required()) { + J2PERROR(err, "Missing required field: %s", butil::EnsureString(field->full_name()).c_str()); + return false; + } + continue; + } + value_ptr = &(member->value); +#endif + + if (IsProtobufMap(field) && value_ptr->IsObject()) { + // Try to parse json like {"key":value, ...} into protobuf map + if (!JsonMapToProtoMap(*value_ptr, field, message, options, err, depth)) { + return false; + } + } else { + if (!JsonValueToProtoField(*value_ptr, field, message, options, err, depth)) { + return false; + } + } + } + return true; +} + +inline bool JsonToProtoMessageInline(const std::string& json_string, + google::protobuf::Message* message, + const Json2PbOptions& options, + std::string* error, + size_t* parsed_offset) { + if (error) { + error->clear(); + } + BUTIL_RAPIDJSON_NAMESPACE::Document d; + if (options.allow_remaining_bytes_after_parsing) { + d.Parse(json_string.c_str()); + if (parsed_offset != nullptr) { + *parsed_offset = d.GetErrorOffset(); + } + } else { + d.Parse(json_string.c_str()); + } + if (d.HasParseError()) { + if (options.allow_remaining_bytes_after_parsing) { + if (d.GetParseError() == BUTIL_RAPIDJSON_NAMESPACE::kParseErrorDocumentEmpty) { + // This is usual when parsing multiple jsons, don't waste time + // on setting the `empty error' + return false; + } + } + J2PERROR_WITH_PB(message, error, "Invalid json: %s", BUTIL_RAPIDJSON_NAMESPACE::GetParseError_En(d.GetParseError())); + return false; + } + return JsonValueToProtoMessage(d, message, options, error, 0); +} + +bool JsonToProtoMessage(const std::string& json_string, + google::protobuf::Message* message, + const Json2PbOptions& options, + std::string* error, + size_t* parsed_offset) { + return JsonToProtoMessageInline(json_string, message, options, error, parsed_offset); +} + +bool JsonToProtoMessage(google::protobuf::io::ZeroCopyInputStream* stream, + google::protobuf::Message* message, + const Json2PbOptions& options, + std::string* error, + size_t* parsed_offset) { + ZeroCopyStreamReader reader(stream); + return JsonToProtoMessage(&reader, message, options, error, parsed_offset); +} + +bool JsonToProtoMessage(ZeroCopyStreamReader* reader, + google::protobuf::Message* message, + const Json2PbOptions& options, + std::string* error, + size_t* parsed_offset) { + if (error) { + error->clear(); + } + BUTIL_RAPIDJSON_NAMESPACE::Document d; + if (options.allow_remaining_bytes_after_parsing) { + d.ParseStream>(*reader); + if (parsed_offset != nullptr) { + *parsed_offset = d.GetErrorOffset(); + } + } else { + d.ParseStream>(*reader); + } + if (d.HasParseError()) { + if (options.allow_remaining_bytes_after_parsing) { + if (d.GetParseError() == BUTIL_RAPIDJSON_NAMESPACE::kParseErrorDocumentEmpty) { + // This is usual when parsing multiple jsons, don't waste time + // on setting the `empty error' + return false; + } + } + J2PERROR_WITH_PB(message, error, "Invalid json: %s", BUTIL_RAPIDJSON_NAMESPACE::GetParseError_En(d.GetParseError())); + return false; + } + return JsonValueToProtoMessage(d, message, options, error, 0); +} + +bool JsonToProtoMessage(const std::string& json_string, + google::protobuf::Message* message, + std::string* error) { + return JsonToProtoMessageInline(json_string, message, Json2PbOptions(), error, nullptr); +} + +// For ABI compatibility with 1.0.0.0 +// (https://svn.baidu.com/public/tags/protobuf-json/protobuf-json_1-0-0-0_PD_BL) +// This method should not be exposed in header, otherwise calls to +// JsonToProtoMessage will be ambiguous. +bool JsonToProtoMessage(std::string json_string, + google::protobuf::Message* message, + std::string* error) { + return JsonToProtoMessageInline(json_string, message, Json2PbOptions(), error, nullptr); +} + +bool JsonToProtoMessage(google::protobuf::io::ZeroCopyInputStream *stream, + google::protobuf::Message* message, + std::string* error) { + return JsonToProtoMessage(stream, message, Json2PbOptions(), error, nullptr); +} + +bool ProtoJsonToProtoMessage(google::protobuf::io::ZeroCopyInputStream* json, + google::protobuf::Message* message, + const ProtoJson2PbOptions& options, + std::string* error) { +#if GOOGLE_PROTOBUF_VERSION >= 6031000 + auto st = google::protobuf::json::JsonStreamToMessage(json, message, options); + bool ok = st.ok(); + if (!ok && NULL != error) { + *error = st.ToString(); + } + return ok; +#else + TypeResolverUniqueptr type_resolver = GetTypeResolver(*message); + std::string type_url = GetTypeUrl(*message); + butil::IOBuf buf; + butil::IOBufAsZeroCopyOutputStream output_stream(&buf); + auto st = google::protobuf::util::JsonToBinaryStream( + type_resolver.get(), type_url, json, &output_stream, options); + if (!st.ok()) { + if (NULL != error) { + *error = st.ToString(); + } + return false; + } + + butil::IOBufAsZeroCopyInputStream input_stream(buf); + google::protobuf::io::CodedInputStream decoder(&input_stream); + bool ok = message->ParseFromCodedStream(&decoder); + if (!ok && NULL != error) { + *error = "Fail to ParseFromCodedStream"; + } + return ok; +#endif // GOOGLE_PROTOBUF_VERSION >= 6031000 +} + +bool ProtoJsonToProtoMessage(const std::string& json, google::protobuf::Message* message, + const ProtoJson2PbOptions& options, std::string* error) { + google::protobuf::io::ArrayInputStream input_stream(json.data(), json.size()); + return ProtoJsonToProtoMessage(&input_stream, message, options, error); +} + +} //namespace json2pb + +#undef J2PERROR +#undef J2PCHECKTYPE diff --git a/src/json2pb/json_to_pb.h b/src/json2pb/json_to_pb.h new file mode 100644 index 0000000..3734ef3 --- /dev/null +++ b/src/json2pb/json_to_pb.h @@ -0,0 +1,103 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// protobuf-json: Conversions between protobuf and json. + +#ifndef BRPC_JSON2PB_JSON_TO_PB_H +#define BRPC_JSON2PB_JSON_TO_PB_H + +#include "json2pb/zero_copy_stream_reader.h" +#include +#include // ZeroCopyInputStream +#include + +namespace json2pb { + +struct Json2PbOptions { + Json2PbOptions(); + + // Decode string in json using base64 decoding if the type of + // corresponding field is bytes when this option is turned on. + // Default: false for baidu-interal, true otherwise. + bool base64_to_bytes; + + // Allow decoding json array iff there is only one repeated field. + // Default: false. + bool array_to_single_repeated; + + // Allow more bytes remaining in the input after parsing the first json + // object. Useful when the input contains more than one json object. + bool allow_remaining_bytes_after_parsing; +}; + +// Convert `json' to protobuf `message' according to `options'. +// Returns true on success. `error' (if not NULL) will be set with error +// message on failure. +// +// [When options.allow_remaining_bytes_after_parsing is true] +// * `parse_offset' will be set with #bytes parsed +// * the function still returns false on empty document but the `error' is set +// to empty string instead of `The document is empty'. +bool JsonToProtoMessage(const std::string& json, + google::protobuf::Message* message, + const Json2PbOptions& options, + std::string* error = nullptr, + size_t* parsed_offset = nullptr); + +// Use ZeroCopyInputStream as input instead of std::string. +bool JsonToProtoMessage(google::protobuf::io::ZeroCopyInputStream* json, + google::protobuf::Message* message, + const Json2PbOptions& options, + std::string* error = nullptr, + size_t* parsed_offset = nullptr); + +// Use ZeroCopyStreamReader as input instead of std::string. +// If you need to parse multiple jsons from IOBuf, you should use this +// overload instead of the ZeroCopyInputStream one which bases on this +// and recreates a ZeroCopyStreamReader internally that can't be reused +// between continuous calls. +bool JsonToProtoMessage(ZeroCopyStreamReader* json, + google::protobuf::Message* message, + const Json2PbOptions& options, + std::string* error = nullptr, + size_t* parsed_offset = nullptr); + +// Using default Json2PbOptions. +bool JsonToProtoMessage(const std::string& json, + google::protobuf::Message* message, + std::string* error = nullptr); + +bool JsonToProtoMessage(google::protobuf::io::ZeroCopyInputStream* stream, + google::protobuf::Message* message, + std::string* error = nullptr); + +// See for details. +using ProtoJson2PbOptions = google::protobuf::util::JsonParseOptions; + +// Convert ProtoJSON formatted `json' to protobuf `message' according to `options'. +// See https://protobuf.dev/programming-guides/json/ for details. +bool ProtoJsonToProtoMessage(google::protobuf::io::ZeroCopyInputStream* json, + google::protobuf::Message* message, + const ProtoJson2PbOptions& options = ProtoJson2PbOptions(), + std::string* error = NULL); +bool ProtoJsonToProtoMessage(const std::string& json, google::protobuf::Message* message, + const ProtoJson2PbOptions& options = ProtoJson2PbOptions(), + std::string* error = NULL); + +} // namespace json2pb + +#endif // BRPC_JSON2PB_JSON_TO_PB_H diff --git a/src/json2pb/pb_to_json.cpp b/src/json2pb/pb_to_json.cpp new file mode 100644 index 0000000..c1fd528 --- /dev/null +++ b/src/json2pb/pb_to_json.cpp @@ -0,0 +1,452 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include "json2pb/zero_copy_stream_writer.h" +#include "json2pb/encode_decode.h" +#include "json2pb/protobuf_map.h" +#include "json2pb/rapidjson.h" +#include "json2pb/pb_to_json.h" +#include "json2pb/protobuf_type_resolver.h" + +#include "butil/base64.h" +#include "butil/iobuf.h" +#include "butil/strings/string_util.h" + +namespace json2pb { + +DECLARE_int32(json2pb_max_recursion_depth); + +// Helper function to check if the maximum depth of a message is exceeded. +bool ExceedMaxDepth(const google::protobuf::Message& message, int current_depth) { + if (current_depth > FLAGS_json2pb_max_recursion_depth) { + return true; + } + + const google::protobuf::Descriptor* descriptor = message.GetDescriptor(); + const google::protobuf::Reflection* reflection = message.GetReflection(); + + std::vector fields; + // Collect declared fields. + for (int i = 0; i < descriptor->field_count(); ++i) { + fields.push_back(descriptor->field(i)); + } + // Collect extension fields (if any). + { + std::vector ext_fields; + descriptor->file()->pool()->FindAllExtensions(descriptor, &ext_fields); + fields.insert(fields.end(), ext_fields.begin(), ext_fields.end()); + } + + for (const auto* field : fields) { + if (field->cpp_type() != google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE) { + continue; + } + + if (field->is_repeated()) { + const int count = reflection->FieldSize(message, field); + for (int j = 0; j < count; ++j) { + const google::protobuf::Message& sub_message = + reflection->GetRepeatedMessage(message, field, j); + if (ExceedMaxDepth(sub_message, current_depth + 1)) { + return true; + } + } + } else { + if (reflection->HasField(message, field)) { + const google::protobuf::Message& sub_message = + reflection->GetMessage(message, field); + if (ExceedMaxDepth(sub_message, current_depth + 1)) { + return true; + } + } + } + } + return false; +} + +Pb2JsonOptions::Pb2JsonOptions() + : enum_option(OUTPUT_ENUM_BY_NAME) + , pretty_json(false) + , enable_protobuf_map(true) +#ifdef BAIDU_INTERNAL + , bytes_to_base64(false) +#else + , bytes_to_base64(true) +#endif + , jsonify_empty_array(false) + , always_print_primitive_fields(false) + , single_repeated_to_array(false) { +} + +class PbToJsonConverter { +public: + explicit PbToJsonConverter(const Pb2JsonOptions& opt) : _option(opt) {} + + template + bool Convert(const google::protobuf::Message& message, Handler& handler, int depth = 0); + + const std::string& ErrorText() const { return _error; } + +private: + template + bool _PbFieldToJson(const google::protobuf::Message& message, + const google::protobuf::FieldDescriptor* field, + Handler& handler, int depth); + + std::string _error; + Pb2JsonOptions _option; +}; + +template +bool PbToJsonConverter::Convert(const google::protobuf::Message& message, Handler& handler, int depth) { + if (depth > FLAGS_json2pb_max_recursion_depth) { + _error = "Exceeded maximum recursion depth"; + return false; + } + const google::protobuf::Reflection* reflection = message.GetReflection(); + const google::protobuf::Descriptor* descriptor = message.GetDescriptor(); + + int ext_range_count = descriptor->extension_range_count(); + int field_count = descriptor->field_count(); + std::vector fields; + fields.reserve(64); + for (int i = 0; i < ext_range_count; ++i) { + const google::protobuf::Descriptor::ExtensionRange* + ext_range = descriptor->extension_range(i); +#if GOOGLE_PROTOBUF_VERSION < 4024000 + for (int tag_number = ext_range->start; tag_number < ext_range->end; ++tag_number) +#else + for (int tag_number = ext_range->start_number(); tag_number < ext_range->end_number(); ++tag_number) +#endif + { + const google::protobuf::FieldDescriptor* field = + reflection->FindKnownExtensionByNumber(tag_number); + if (field) { + fields.push_back(field); + } + } + } + std::vector map_fields; + for (int i = 0; i < field_count; ++i) { + const google::protobuf::FieldDescriptor* field = descriptor->field(i); + if (_option.enable_protobuf_map && json2pb::IsProtobufMap(field)) { + map_fields.push_back(field); + } else { + fields.push_back(field); + } + } + + if (depth == 0 && _option.single_repeated_to_array) { + if (map_fields.empty() && fields.size() == 1 && fields.front()->is_repeated()) { + return _PbFieldToJson(message, fields.front(), handler, depth); + } + } + + handler.StartObject(); + + // Fill in non-map fields + std::string field_name_str; + for (size_t i = 0; i < fields.size(); ++i) { + const google::protobuf::FieldDescriptor* field = fields[i]; + if (!field->is_repeated() && !reflection->HasField(message, field)) { + // Field that has not been set + if (field->is_required()) { + _error = "Missing required field: " + butil::EnsureString(field->full_name()); + return false; + } + // Whether dumps default fields + if (!_option.always_print_primitive_fields) { + continue; + } + } else if (field->is_repeated() + && reflection->FieldSize(message, field) == 0 + && !_option.jsonify_empty_array) { + // Repeated field that has no entry + continue; + } + + const std::string orig_name = butil::EnsureString(field->name()); + bool decoded = decode_name(orig_name, field_name_str); + const std::string& name = decoded ? field_name_str : orig_name; + handler.Key(name.data(), name.size(), false); + if (!_PbFieldToJson(message, field, handler, depth)) { + return false; + } + } + + // Fill in map fields + for (size_t i = 0; i < map_fields.size(); ++i) { + const google::protobuf::FieldDescriptor* map_desc = map_fields[i]; + const google::protobuf::FieldDescriptor* key_desc = + map_desc->message_type()->field(json2pb::KEY_INDEX); + const google::protobuf::FieldDescriptor* value_desc = + map_desc->message_type()->field(json2pb::VALUE_INDEX); + + // Write a json object corresponding to hold protobuf map + // such as {"key": value, ...} + const std::string orig_name = butil::EnsureString(map_desc->name()); + bool decoded = decode_name(orig_name, field_name_str); + const std::string& name = decoded ? field_name_str : orig_name; + handler.Key(name.data(), name.size(), false); + handler.StartObject(); + std::string entry_name; + for (int j = 0; j < reflection->FieldSize(message, map_desc); ++j) { + const google::protobuf::Message& entry = + reflection->GetRepeatedMessage(message, map_desc, j); + const google::protobuf::Reflection* entry_reflection = entry.GetReflection(); + entry_name = entry_reflection->GetStringReference( + entry, key_desc, &entry_name); + handler.Key(entry_name.data(), entry_name.size(), false); + + // Fill in entries into this json object + if (!_PbFieldToJson(entry, value_desc, handler, depth)) { + return false; + } + } + // Hack: Pass 0 as parameter since Writer doesn't care this + handler.EndObject(0); + } + // Hack: Pass 0 as parameter since Writer doesn't care this + handler.EndObject(0); + return true; +} + +template +bool PbToJsonConverter::_PbFieldToJson( + const google::protobuf::Message& message, + const google::protobuf::FieldDescriptor* field, + Handler& handler, int depth) { + const google::protobuf::Reflection* reflection = message.GetReflection(); + switch (field->cpp_type()) { +#define CASE_FIELD_TYPE(cpptype, method, valuetype, handle) \ + case google::protobuf::FieldDescriptor::CPPTYPE_##cpptype: { \ + if (field->is_repeated()) { \ + int field_size = reflection->FieldSize(message, field); \ + handler.StartArray(); \ + for (int index = 0; index < field_size; ++index) { \ + handler.handle(static_cast( \ + reflection->GetRepeated##method( \ + message, field, index))); \ + } \ + handler.EndArray(field_size); \ + \ + } else { \ + handler.handle(static_cast( \ + reflection->Get##method(message, field))); \ + } \ + break; \ + } + + CASE_FIELD_TYPE(BOOL, Bool, bool, Bool); + CASE_FIELD_TYPE(INT32, Int32, int, AddInt); + CASE_FIELD_TYPE(UINT32, UInt32, unsigned int, AddUint); + CASE_FIELD_TYPE(INT64, Int64, int64_t, AddInt64); + CASE_FIELD_TYPE(UINT64, UInt64, uint64_t, AddUint64); + CASE_FIELD_TYPE(FLOAT, Float, double, Double); + CASE_FIELD_TYPE(DOUBLE, Double, double, Double); +#undef CASE_FIELD_TYPE + + case google::protobuf::FieldDescriptor::CPPTYPE_STRING: { + std::string value; + if (field->is_repeated()) { + int field_size = reflection->FieldSize(message, field); + handler.StartArray(); + for (int index = 0; index < field_size; ++index) { + value = reflection->GetRepeatedStringReference( + message, field, index, &value); + if (field->type() == google::protobuf::FieldDescriptor::TYPE_BYTES + && _option.bytes_to_base64) { + std::string value_decoded; + butil::Base64Encode(value, &value_decoded); + handler.String(value_decoded.data(), value_decoded.size(), false); + } else { + handler.String(value.data(), value.size(), false); + } + } + handler.EndArray(field_size); + + } else { + value = reflection->GetStringReference(message, field, &value); + if (field->type() == google::protobuf::FieldDescriptor::TYPE_BYTES + && _option.bytes_to_base64) { + std::string value_decoded; + butil::Base64Encode(value, &value_decoded); + handler.String(value_decoded.data(), value_decoded.size(), false); + } else { + handler.String(value.data(), value.size(), false); + } + } + break; + } + + case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: { + if (field->is_repeated()) { + int field_size = reflection->FieldSize(message, field); + handler.StartArray(); + if (_option.enum_option == OUTPUT_ENUM_BY_NAME) { + for (int index = 0; index < field_size; ++index) { + const std::string enum_name = butil::EnsureString(reflection->GetRepeatedEnum( + message, field, index)->name()); + handler.String(enum_name.data(), enum_name.size(), false); + } + } else { + for (int index = 0; index < field_size; ++index) { + handler.AddInt(reflection->GetRepeatedEnum( + message, field, index)->number()); + } + } + handler.EndArray(); + + } else { + if (_option.enum_option == OUTPUT_ENUM_BY_NAME) { + const std::string& enum_name = + butil::EnsureString(reflection->GetEnum(message, field)->name()); + handler.String(enum_name.data(), enum_name.size(), false); + } else { + handler.AddInt(reflection->GetEnum(message, field)->number()); + } + } + break; + } + + case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: { + if (field->is_repeated()) { + int field_size = reflection->FieldSize(message, field); + handler.StartArray(); + for (int index = 0; index < field_size; ++index) { + if (!Convert(reflection->GetRepeatedMessage( + message, field, index), handler, depth + 1)) { + return false; + } + } + handler.EndArray(field_size); + + } else { + if (!Convert(reflection->GetMessage(message, field), handler, depth + 1)) { + return false; + } + } + break; + } + } + return true; +} + +template +bool ProtoMessageToJsonStream(const google::protobuf::Message& message, + const Pb2JsonOptions& options, + OutputStream& os, std::string* error) { + PbToJsonConverter converter(options); + bool succ = false; + if (options.pretty_json) { + BUTIL_RAPIDJSON_NAMESPACE::PrettyWriter writer(os); + succ = converter.Convert(message, writer); + } else { + BUTIL_RAPIDJSON_NAMESPACE::OptimizedWriter writer(os); + succ = converter.Convert(message, writer); + } + if (!succ && error) { + error->clear(); + error->append(converter.ErrorText()); + } + return succ; +} + +bool ProtoMessageToJson(const google::protobuf::Message& message, + std::string* json, + const Pb2JsonOptions& options, + std::string* error) { + // TODO(gejun): We could further wrap a std::string as a buffer to reduce + // a copying. + BUTIL_RAPIDJSON_NAMESPACE::StringBuffer buffer; + if (json2pb::ProtoMessageToJsonStream(message, options, buffer, error)) { + json->append(buffer.GetString(), buffer.GetSize()); + return true; + } + return false; +} + +bool ProtoMessageToJson(const google::protobuf::Message& message, + std::string* json, std::string* error) { + return ProtoMessageToJson(message, json, Pb2JsonOptions(), error); +} + +bool ProtoMessageToJson(const google::protobuf::Message& message, + google::protobuf::io::ZeroCopyOutputStream* stream, + const Pb2JsonOptions& options, std::string* error) { + json2pb::ZeroCopyStreamWriter wrapper(stream); + return json2pb::ProtoMessageToJsonStream(message, options, wrapper, error); +} + +bool ProtoMessageToJson(const google::protobuf::Message& message, + google::protobuf::io::ZeroCopyOutputStream* stream, + std::string* error) { + return ProtoMessageToJson(message, stream, Pb2JsonOptions(), error); +} + +bool ProtoMessageToProtoJson(const google::protobuf::Message& message, + google::protobuf::io::ZeroCopyOutputStream* json, + const Pb2ProtoJsonOptions& options, std::string* error) { + if (ExceedMaxDepth(message, 0)) { + if (error) { + *error = "Exceeded maximum recursion depth"; + } + return false; + } +#if GOOGLE_PROTOBUF_VERSION >= 6031000 + auto st = google::protobuf::json::MessageToJsonStream(message, json, options); + bool ok = st.ok(); + if (!ok && NULL != error) { + *error = st.ToString(); + } + return ok; +#else + butil::IOBuf buf; + butil::IOBufAsZeroCopyOutputStream output_stream(&buf); + if (!message.SerializeToZeroCopyStream(&output_stream)) { + return false; + } + + TypeResolverUniqueptr type_resolver = GetTypeResolver(message); + butil::IOBufAsZeroCopyInputStream input_stream(buf); + auto st = google::protobuf::util::BinaryToJsonStream( + type_resolver.get(), GetTypeUrl(message), &input_stream, json, options); + + bool ok = st.ok(); + if (!ok && NULL != error) { + *error = st.ToString(); + } + return ok; +#endif // GOOGLE_PROTOBUF_VERSION >= 6031000 +} + +bool ProtoMessageToProtoJson(const google::protobuf::Message& message, std::string* json, + const Pb2ProtoJsonOptions& options, std::string* error) { + google::protobuf::io::StringOutputStream output_stream(json); + return ProtoMessageToProtoJson(message, &output_stream, options, error); +} + +} // namespace json2pb diff --git a/src/json2pb/pb_to_json.h b/src/json2pb/pb_to_json.h new file mode 100644 index 0000000..4dda3a7 --- /dev/null +++ b/src/json2pb/pb_to_json.h @@ -0,0 +1,119 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// protobuf-json: Conversions between protobuf and json. + +#ifndef BRPC_JSON2PB_PB_TO_JSON_H +#define BRPC_JSON2PB_PB_TO_JSON_H + +#include +#include +#include // ZeroCopyOutputStream +#include + +namespace json2pb { + +enum EnumOption { + OUTPUT_ENUM_BY_NAME = 0, // Output enum by its name + OUTPUT_ENUM_BY_NUMBER = 1, // Output enum by its value +}; + +struct Pb2JsonOptions { + Pb2JsonOptions(); + + // Control how enum fields are output + // Default: OUTPUT_ENUM_BY_NAME + EnumOption enum_option; + + // Use rapidjson::PrettyWriter to generate the json when this option is on. + // NOTE: currently PrettyWriter is not optimized yet thus the conversion + // functions may be slower when this option is turned on. + // Default: false + bool pretty_json; + + // Convert "repeated { required string key = 1; required string value = 2; }" + // to a map object of json and vice versa when this option is turned on. + // Default: true + bool enable_protobuf_map; + + // Encode the field of type bytes to string in json using base64 + // encoding when this option is turned on. + // Default: false for baidu-internal, true otherwise. + bool bytes_to_base64; + + // Convert the repeated field that has no entry + // to a empty array of json when this option is turned on. + // Default: false + bool jsonify_empty_array; + + // Whether to always print primitive fields. By default proto3 primitive + // fields with default values will be omitted in JSON output. For example, an + // int32 field set to 0 will be omitted. Set this flag to true will override + // the default behavior and print primitive fields regardless of their values. + bool always_print_primitive_fields; + + // Convert the single repeated field to a json array when this option is turned on. + // Default: false. + bool single_repeated_to_array; +}; + +// Convert protobuf `messge' to `json' according to `options'. +// Returns true on success. `error' (if not NULL) will be set with error +// message on failure. +bool ProtoMessageToJson(const google::protobuf::Message& message, + std::string* json, + const Pb2JsonOptions& options, + std::string* error = NULL); +// send output to ZeroCopyOutputStream instead of std::string. +bool ProtoMessageToJson(const google::protobuf::Message& message, + google::protobuf::io::ZeroCopyOutputStream* json, + const Pb2JsonOptions& options, + std::string* error = NULL); + +// Using default Pb2JsonOptions. +bool ProtoMessageToJson(const google::protobuf::Message& message, + std::string* json, + std::string* error = NULL); +bool ProtoMessageToJson(const google::protobuf::Message& message, + google::protobuf::io::ZeroCopyOutputStream* json, + std::string* error = NULL); + +// See for details. +#if GOOGLE_PROTOBUF_VERSION >= 6030000 +using Pb2ProtoJsonOptions = google::protobuf::util::JsonPrintOptions; +#else +using Pb2ProtoJsonOptions = google::protobuf::util::JsonOptions; +#endif + +#if GOOGLE_PROTOBUF_VERSION >= 5026002 +#define AlwaysPrintPrimitiveFields(options) options.always_print_fields_with_no_presence +#else +#define AlwaysPrintPrimitiveFields(options) options.always_print_primitive_fields +#endif + +// Convert protobuf `messge' to `json' in ProtoJSON format according to `options'. +// See https://protobuf.dev/programming-guides/json/ for details. +bool ProtoMessageToProtoJson(const google::protobuf::Message& message, + google::protobuf::io::ZeroCopyOutputStream* json, + const Pb2ProtoJsonOptions& options = Pb2ProtoJsonOptions(), + std::string* error = NULL); +bool ProtoMessageToProtoJson(const google::protobuf::Message& message, std::string* json, + const Pb2ProtoJsonOptions& options = Pb2ProtoJsonOptions(), + std::string* error = NULL); +} // namespace json2pb + +#endif // BRPC_JSON2PB_PB_TO_JSON_H diff --git a/src/json2pb/protobuf_map.cpp b/src/json2pb/protobuf_map.cpp new file mode 100644 index 0000000..7553523 --- /dev/null +++ b/src/json2pb/protobuf_map.cpp @@ -0,0 +1,52 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "protobuf_map.h" +#include + +namespace json2pb { + +using google::protobuf::Descriptor; +using google::protobuf::FieldDescriptor; + +bool IsProtobufMap(const FieldDescriptor* field) { + if (field->type() != FieldDescriptor::TYPE_MESSAGE || !field->is_repeated()) { + return false; + } + const Descriptor* entry_desc = field->message_type(); + if (entry_desc == NULL) { + return false; + } + if (entry_desc->field_count() != 2) { + return false; + } + const FieldDescriptor* key_desc = entry_desc->field(KEY_INDEX); + if (NULL == key_desc + || key_desc->is_repeated() + || key_desc->cpp_type() != FieldDescriptor::CPPTYPE_STRING + || key_desc->name() != KEY_NAME) { + return false; + } + const FieldDescriptor* value_desc = entry_desc->field(VALUE_INDEX); + if (NULL == value_desc + || value_desc->name() != VALUE_NAME) { + return false; + } + return true; +} + +} // namespace json2pb diff --git a/src/json2pb/protobuf_map.h b/src/json2pb/protobuf_map.h new file mode 100644 index 0000000..244fb5e --- /dev/null +++ b/src/json2pb/protobuf_map.h @@ -0,0 +1,52 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_JSON2PB_JSON_PROTOBUF_MAP_H +#define BRPC_JSON2PB_JSON_PROTOBUF_MAP_H + +#include + +namespace json2pb { + +const char* const KEY_NAME = "key"; +const char* const VALUE_NAME = "value"; +const int KEY_INDEX = 0; +const int VALUE_INDEX = 1; + +// Map inside protobuf is officially supported in proto3 using +// statement like: map my_map = N; +// However, we can still emmulate this map in proto2 by writing: +// message MapFieldEntry { +// required string key = 1; // MUST be the first +// required string value = 2; // MUST be the second +// } +// repeated MapFieldEntry my_map = N; +// +// Natually, when converting this map to json, it should be like: +// { "my_map": {"key1": value1, "key2": value2 } } +// instead of: +// { "my_map": [{"key": "key1", "value": value1}, +// {"key": "key2", "value": value2}] } +// In order to get the former one, the type of `key' field MUST be +// string since JSON only supports string keys + +// Check whether `field' is a map type field and is convertable +bool IsProtobufMap(const google::protobuf::FieldDescriptor* field); + +} // namespace json2pb + +#endif // BRPC_JSON2PB_JSON_PROTOBUF_MAP_H diff --git a/src/json2pb/protobuf_type_resolver.cpp b/src/json2pb/protobuf_type_resolver.cpp new file mode 100644 index 0000000..a75974f --- /dev/null +++ b/src/json2pb/protobuf_type_resolver.cpp @@ -0,0 +1,36 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "json2pb/protobuf_type_resolver.h" + +namespace json2pb { + +using google::protobuf::DescriptorPool; +using google::protobuf::util::TypeResolver; +using google::protobuf::util::NewTypeResolverForDescriptorPool; + +TypeResolverUniqueptr GetTypeResolver(const google::protobuf::Message& message) { + auto pool = message.GetDescriptor()->file()->pool(); + bool is_generated_pool = pool == DescriptorPool::generated_pool(); + TypeResolver* resolver = is_generated_pool + ? butil::get_leaky_singleton() + : NewTypeResolverForDescriptorPool(PROTOBUF_TYPE_URL_PREFIX, pool); + return { resolver, TypeResolverDeleter(is_generated_pool) }; +} + +} // namespace json2pb + diff --git a/src/json2pb/protobuf_type_resolver.h b/src/json2pb/protobuf_type_resolver.h new file mode 100644 index 0000000..7eff6c6 --- /dev/null +++ b/src/json2pb/protobuf_type_resolver.h @@ -0,0 +1,74 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_PROTOBUF_TYPE_RESOLVER_H +#define BRPC_PROTOBUF_TYPE_RESOLVER_H + +#include +#include +#include +#include +#include +#include "butil/memory/singleton_on_pthread_once.h" +#include "butil/string_printf.h" +#include "butil/strings/string_util.h" + +namespace json2pb { + +#define PROTOBUF_TYPE_URL_PREFIX "type.googleapis.com" + +inline std::string GetTypeUrl(const google::protobuf::Message& message) { + return butil::string_printf(PROTOBUF_TYPE_URL_PREFIX"/%s", + butil::EnsureString(message.GetDescriptor()->full_name()).c_str()); +} + +// unique_ptr deleter for TypeResolver only deletes the object +// when it's not from the generated pool. +class TypeResolverDeleter { +public: + explicit TypeResolverDeleter(bool is_generated_pool) + : _is_generated_pool(is_generated_pool) {} + + void operator()(google::protobuf::util::TypeResolver* resolver) const { + if (!_is_generated_pool) { + delete resolver; + } + } +private: + bool _is_generated_pool; +}; + +using TypeResolverUniqueptr = std::unique_ptr< + google::protobuf::util::TypeResolver, TypeResolverDeleter>; + +TypeResolverUniqueptr GetTypeResolver(const google::protobuf::Message& message); + +} // namespace json2pb + +namespace butil { + +// Customized singleton object creation for google::protobuf::util::TypeResolver. +template<> +inline google::protobuf::util::TypeResolver* +create_leaky_singleton_obj() { + return google::protobuf::util::NewTypeResolverForDescriptorPool( + PROTOBUF_TYPE_URL_PREFIX, google::protobuf::DescriptorPool::generated_pool()); +} + +} // namespace butil + +#endif // BRPC_PROTOBUF_TYPE_RESOLVER_H diff --git a/src/json2pb/rapidjson.h b/src/json2pb/rapidjson.h new file mode 100644 index 0000000..fa5d354 --- /dev/null +++ b/src/json2pb/rapidjson.h @@ -0,0 +1,48 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_JSON2PB_RAPIDJSON_H +#define BRPC_JSON2PB_RAPIDJSON_H + + +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) + +#pragma GCC diagnostic push + +#pragma GCC diagnostic ignored "-Wunused-local-typedefs" + +#endif + +#include "butil/third_party/rapidjson/allocators.h" +#include "butil/third_party/rapidjson/document.h" +#include "butil/third_party/rapidjson/encodedstream.h" +#include "butil/third_party/rapidjson/encodings.h" +#include "butil/third_party/rapidjson/filereadstream.h" +#include "butil/third_party/rapidjson/filewritestream.h" +#include "butil/third_party/rapidjson/prettywriter.h" +#include "butil/third_party/rapidjson/rapidjson.h" +#include "butil/third_party/rapidjson/reader.h" +#include "butil/third_party/rapidjson/stringbuffer.h" +#include "butil/third_party/rapidjson/writer.h" +#include "butil/third_party/rapidjson/optimized_writer.h" +#include "butil/third_party/rapidjson/error/en.h" // GetErrorCode_En + +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) +#pragma GCC diagnostic pop +#endif + +#endif //BRPC_JSON2PB_RAPIDJSON_H diff --git a/src/json2pb/zero_copy_stream_reader.h b/src/json2pb/zero_copy_stream_reader.h new file mode 100644 index 0000000..6c19d33 --- /dev/null +++ b/src/json2pb/zero_copy_stream_reader.h @@ -0,0 +1,85 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_JSON2PB_ZERO_COPY_STREAM_READER_H +#define BRPC_JSON2PB_ZERO_COPY_STREAM_READER_H + +#include // ZeroCopyInputStream + +namespace json2pb { + +class ZeroCopyStreamReader { +public: + typedef char Ch; + ZeroCopyStreamReader(google::protobuf::io::ZeroCopyInputStream *stream) + : _data(NULL), _data_size(0), _nread(0), _stream(stream) { + } + //Take a charactor and return its address. + const char* PeekAddr() { + if (!ReadBlockTail()) { + return _data; + } + while (_stream->Next((const void **)&_data, &_data_size)) { + if (!ReadBlockTail()) { + return _data; + } + } + return NULL; + } + const char* TakeWithAddr() { + const char* c = PeekAddr(); + if (c) { + ++_nread; + --_data_size; + return _data++; + } + return NULL; + } + char Take() { + const char* c = PeekAddr(); + if (c) { + ++_nread; + --_data_size; + ++_data; + return *c; + } + return '\0'; + } + + char Peek() { + const char* c = PeekAddr(); + return (c ? *c : '\0'); + } + //Tell whether read the end of this block. + bool ReadBlockTail() { + return !_data_size; + } + size_t Tell() { return _nread; } + void Put(char) {} + void Flush() {} + char *PutBegin() { return NULL; } + size_t PutEnd(char *) { return 0; } +private: + const char *_data; + int _data_size; + size_t _nread; + google::protobuf::io::ZeroCopyInputStream *_stream; +}; + +} // namespace json2pb + +#endif //BRPC_JSON2PB_ZERO_COPY_STREAM_READER_H diff --git a/src/json2pb/zero_copy_stream_writer.h b/src/json2pb/zero_copy_stream_writer.h new file mode 100644 index 0000000..6404211 --- /dev/null +++ b/src/json2pb/zero_copy_stream_writer.h @@ -0,0 +1,114 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_JSON2PB_ZERO_COPY_STREAM_WRITER_H +#define BRPC_JSON2PB_ZERO_COPY_STREAM_WRITER_H + +#include // ZeroCopyOutputStream +#include + +//class IOBufAsZeroCopyOutputStream +// : public google::protobuf::io::ZeroCopyOutputStream { +//public: +// explicit IOBufAsZeroCopyOutputStream(IOBuf*); +// +// // Interfaces of ZeroCopyOutputStream +// bool Next(void** data, int* size); +// void BackUp(int count); +// int64_t ByteCount() const; +// +//private: +// IOBuf* _buf; +// size_t _initial_length; +//}; + +namespace json2pb { + +class ZeroCopyStreamWriter { +public: + typedef char Ch; + ZeroCopyStreamWriter(google::protobuf::io::ZeroCopyOutputStream *stream) + : _stream(stream), _data(NULL), + _cursor(NULL), _data_size(0) { + } + ~ZeroCopyStreamWriter() { + if (_stream && _data) { + _stream->BackUp(RemainSize()); + } + _stream = NULL; + } + + void Put(char c) { + if (__builtin_expect(AcquireNextBuf(), 1)) { + *_cursor = c; + ++_cursor; + } + } + void PutN(char c, size_t n) { + while (AcquireNextBuf() && n > 0) { + size_t remain_size = RemainSize(); + size_t to_write = n > remain_size ? remain_size : n; + memset(_cursor, c, to_write); + _cursor += to_write; + n -= to_write; + } + } + void Puts(const char* str, size_t length) { + while (AcquireNextBuf() && length > 0) { + size_t remain_size = RemainSize(); + size_t to_write = length > remain_size ? remain_size : length; + memcpy(_cursor, str, to_write); + _cursor += to_write; + str += to_write; + length -= to_write; + } + } + + void Flush() {} + + // TODO: Add BIADU_CHECK + char Peek() { return 0; } + char Take() { return 0; } + size_t Tell() { return 0; } + char *PutBegin() { return NULL; } + size_t PutEnd(char *) { return 0; } +private: + bool AcquireNextBuf() { + if (__builtin_expect(!_stream, 0)) { + return false; + } + if (_data == NULL || _cursor == _data + _data_size) { + if (!_stream->Next((void **)&_data, &_data_size)) { + return false; + } + _cursor = _data; + } + return true; + } + size_t RemainSize() { + return _data_size - (_cursor - _data); + } + + google::protobuf::io::ZeroCopyOutputStream *_stream; + char *_data; + char *_cursor; + int _data_size; +}; + +} // namespace json2pb + +#endif //BRPC_JSON2PB_ZERO_COPY_STREAM_WRITER_H diff --git a/src/mcpack2pb/field_type.cpp b/src/mcpack2pb/field_type.cpp new file mode 100644 index 0000000..a3c686e --- /dev/null +++ b/src/mcpack2pb/field_type.cpp @@ -0,0 +1,56 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// mcpack2pb - Make protobuf be front-end of mcpack/compack + +// Date: Mon Oct 19 17:17:36 CST 2015 + +#include "mcpack2pb/field_type.h" + +namespace mcpack2pb { + +const char* type2str(FieldType type) { + bool is_short = false; + if (type & FIELD_SHORT_MASK) { + is_short = true; + type = (FieldType)(type & ~FIELD_SHORT_MASK); + } + switch (type) { + case FIELD_OBJECT: return "object"; + case FIELD_ARRAY: return "array"; + case FIELD_ISOARRAY: return "isoarray"; + case FIELD_OBJECTISOARRAY: return "object_isoarray"; + case FIELD_STRING: return (is_short ? "string(short)" : "string"); + case FIELD_BINARY: return (is_short ? "binary(short)" : "binary"); + case FIELD_INT8: return "int8"; + case FIELD_INT16: return "int16"; + case FIELD_INT32: return "int32"; + case FIELD_INT64: return "int64"; + case FIELD_UINT8: return "uint8"; + case FIELD_UINT16: return "uint16"; + case FIELD_UINT32: return "uint32"; + case FIELD_UINT64: return "uint64"; + case FIELD_BOOL: return "bool"; + case FIELD_FLOAT: return "float"; + case FIELD_DOUBLE: return "double"; + case FIELD_DATE: return "date"; + case FIELD_NULL: return "null"; + } + return "unknown_field_type"; +} + +} // namespace mcpack2pb diff --git a/src/mcpack2pb/field_type.h b/src/mcpack2pb/field_type.h new file mode 100644 index 0000000..7261f9a --- /dev/null +++ b/src/mcpack2pb/field_type.h @@ -0,0 +1,150 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// mcpack2pb - Make protobuf be front-end of mcpack/compack + +// Date: Mon Oct 19 17:17:36 CST 2015 + +#ifndef MCPACK2PB_MCPACK_FIELD_TYPE_H +#define MCPACK2PB_MCPACK_FIELD_TYPE_H + +#include +#include + +namespace mcpack2pb { + +enum FieldType { + // NOTE: All names end with 0 and all name_size count 0. + + // Group of fields. + // | FieldLongHead | Name | ItemsHead | Item1 | Item2 | ... + FIELD_OBJECT = 0x10, + FIELD_ARRAY = 0x20, + + // Isomorphic array of primitive type. Notice that the items are values + // without any header. E.g. If type is int32_t, an items occupies 4 bytes. + // | FieldLongHead | Name | IsoItemsHead | Item1 | Item2 | ... + FIELD_ISOARRAY = 0x30, + + // Layout of repeated objects. + // [{a=1,b=2},{a=3,b=4}] => {a=[1,3],b=[2,4]} + FIELD_OBJECTISOARRAY = 0x40, + + // C-style strings (ending with 0) + // strlen <= 254 : | FieldShortHead | Name | String | + // otherwise : | FieldLongHead | Name | String | + // ^ ending with 0 + FIELD_STRING = 0x50, + + // Binary data + // length <= 255: | FieldShortHead | Name | Data | + // otherwise: | FieldLongHead | Name | Data | + FIELD_BINARY = 0x60, + + // Primitive types. + // | FieldFixedHead | Name | Value | + FIELD_INT8 = 0x11, + FIELD_INT16 = 0x12, + FIELD_INT32 = 0x14, + FIELD_INT64 = 0x18, + FIELD_UINT8 = 0x21, + FIELD_UINT16 = 0x22, + FIELD_UINT32 = 0x24, + FIELD_UINT64 = 0x28, + FIELD_BOOL = 0x31, + FIELD_FLOAT = 0x44, + FIELD_DOUBLE = 0x48, + + // TODO(gejun): Don't know what this is. Seems to be timestamp, but grep nothing + // from public/idlcompiler and public/mcpack + FIELD_DATE = 0x58, + + // Represent absent field in OBJECTISOARRAY + // | FieldFixedHead | Name | char(0) | + FIELD_NULL = 0x61 +}; + +// Get description of the type. +const char* type2str(FieldType type); + +inline const char* type2str(uint8_t type) { + return type2str((FieldType)type); +} + +// Masks of types. +// String <= 254 or BinaryData <= 255 may have this bit set to save 3 bytes. +static const uint8_t FIELD_SHORT_MASK = 0x80; + +// primitive-types & FIELD_FIXED_MASK = non-zero. +// non-primitive-types & FIELD_FIXED_MASK = 0. +static const uint8_t FIELD_FIXED_MASK = 0xf; + +// Valid field & FIELD_NON_DELETED_MASK = non-zero. +// Deleted fields should be skipped. +static const uint8_t FIELD_NON_DELETED_MASK = 0x70; + +// Represent fields unknown/unset/invalid. +static const FieldType FIELD_UNKNOWN = (FieldType)0; + +// Maximum of level of array/object +// Parsing/Serialization would fail if the level exceeds this value. +static const int MAX_DEPTH = 128; + +enum PrimitiveFieldType { + PRIMITIVE_FIELD_INT8 = FIELD_INT8, + PRIMITIVE_FIELD_INT16 = FIELD_INT16, + PRIMITIVE_FIELD_INT32 = FIELD_INT32, + PRIMITIVE_FIELD_INT64 = FIELD_INT64, + PRIMITIVE_FIELD_UINT8 = FIELD_UINT8, + PRIMITIVE_FIELD_UINT16 = FIELD_UINT16, + PRIMITIVE_FIELD_UINT32 = FIELD_UINT32, + PRIMITIVE_FIELD_UINT64 = FIELD_UINT64, + PRIMITIVE_FIELD_BOOL = FIELD_BOOL, + PRIMITIVE_FIELD_FLOAT = FIELD_FLOAT, + PRIMITIVE_FIELD_DOUBLE = FIELD_DOUBLE +}; + +static const PrimitiveFieldType PRIMITIVE_FIELD_UNKNOWN = + (PrimitiveFieldType)FIELD_UNKNOWN; + +inline const char* type2str(PrimitiveFieldType type) { + return type2str((FieldType)type); +} + +inline bool is_primitive(FieldType type) { + return type & 0xF; +} + +inline bool is_integral(PrimitiveFieldType type) { + return (type & 0xF0) < 0x40; +} + +inline bool is_floating_point(PrimitiveFieldType type) { + return (type & 0xF0) == 0x40; +} + +inline size_t get_primitive_type_size(PrimitiveFieldType type) { + return (type & 0xF); +} + +inline size_t get_primitive_type_size(FieldType type) { + return (type & 0xF); +} + +} // namespace mcpack2pb + +#endif // MCPACK2PB_MCPACK_FIELD_TYPE_H diff --git a/src/mcpack2pb/generator.cpp b/src/mcpack2pb/generator.cpp new file mode 100644 index 0000000..26aa4b5 --- /dev/null +++ b/src/mcpack2pb/generator.cpp @@ -0,0 +1,1429 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// mcpack2pb - Make protobuf be front-end of mcpack/compack + +// Date: Mon Oct 19 17:17:36 CST 2015 + +#include +#include +#include +#include +#include + +#include "butil/file_util.h" +#include "butil/string_printf.h" +#include "butil/strings/string_util.h" +#include "idl_options.pb.h" +#include "mcpack2pb/mcpack2pb.h" + +namespace mcpack2pb { + +const std::string get_idl_name(const google::protobuf::FieldDescriptor* f) { + const std::string real_name = butil::EnsureString(f->options().GetExtension(idl_name)); + return real_name.empty() ? butil::EnsureString(f->name()) : real_name; +} + +bool is_integral_type(ConvertibleIdlType type) { + switch (type) { + case IDL_INT8: + case IDL_INT16: + case IDL_INT32: + case IDL_INT64: + case IDL_UINT8: + case IDL_UINT16: + case IDL_UINT32: + case IDL_UINT64: + return true; + default: + return false; + } +} + +const std::string field_to_string(const google::protobuf::FieldDescriptor* f) { + switch (f->type()) { + case google::protobuf::FieldDescriptor::TYPE_DOUBLE: return "double"; + case google::protobuf::FieldDescriptor::TYPE_FLOAT: return "float"; + case google::protobuf::FieldDescriptor::TYPE_INT64: return "int64"; + case google::protobuf::FieldDescriptor::TYPE_UINT64: return "uint64"; + case google::protobuf::FieldDescriptor::TYPE_INT32: return "int32"; + case google::protobuf::FieldDescriptor::TYPE_FIXED64: return "fixed64"; + case google::protobuf::FieldDescriptor::TYPE_FIXED32: return "fixed32"; + case google::protobuf::FieldDescriptor::TYPE_BOOL: return "bool"; + case google::protobuf::FieldDescriptor::TYPE_STRING: return "string"; + case google::protobuf::FieldDescriptor::TYPE_GROUP: + case google::protobuf::FieldDescriptor::TYPE_MESSAGE: + return butil::EnsureString(f->message_type()->name()); + case google::protobuf::FieldDescriptor::TYPE_BYTES: return "bytes"; + case google::protobuf::FieldDescriptor::TYPE_UINT32: return "uint32"; + case google::protobuf::FieldDescriptor::TYPE_ENUM: + return butil::EnsureString(f->enum_type()->name()); + case google::protobuf::FieldDescriptor::TYPE_SFIXED32: return "sfixed32"; + case google::protobuf::FieldDescriptor::TYPE_SFIXED64: return "sfixed64"; + case google::protobuf::FieldDescriptor::TYPE_SINT32: return "sint32"; + case google::protobuf::FieldDescriptor::TYPE_SINT64: return "sint64"; + } + return "unknown_protobuf_type"; +} + +const char* to_mcpack_typestr(const google::protobuf::FieldDescriptor* f) { + switch (f->type()) { + case google::protobuf::FieldDescriptor::TYPE_DOUBLE: return "double"; + case google::protobuf::FieldDescriptor::TYPE_FLOAT: return "float"; + case google::protobuf::FieldDescriptor::TYPE_INT64: return "int64"; + case google::protobuf::FieldDescriptor::TYPE_UINT64: return "uint64"; + case google::protobuf::FieldDescriptor::TYPE_INT32: return "int32"; + case google::protobuf::FieldDescriptor::TYPE_FIXED64: return "uint64"; + case google::protobuf::FieldDescriptor::TYPE_FIXED32: return "uint32"; + case google::protobuf::FieldDescriptor::TYPE_BOOL: return "bool"; + case google::protobuf::FieldDescriptor::TYPE_STRING: return "string"; + case google::protobuf::FieldDescriptor::TYPE_GROUP: return "object"; + case google::protobuf::FieldDescriptor::TYPE_MESSAGE: return "object"; + case google::protobuf::FieldDescriptor::TYPE_BYTES: return "binary"; + case google::protobuf::FieldDescriptor::TYPE_UINT32: return "uint32"; + case google::protobuf::FieldDescriptor::TYPE_ENUM: return "int32"; + case google::protobuf::FieldDescriptor::TYPE_SFIXED32: return "int32"; + case google::protobuf::FieldDescriptor::TYPE_SFIXED64: return "int64"; + case google::protobuf::FieldDescriptor::TYPE_SINT32: return "int32"; + case google::protobuf::FieldDescriptor::TYPE_SINT64: return "int64"; + } + return "unknown_protobuf_type"; +} + +const char* to_mcpack_typestr(ConvertibleIdlType type, + const google::protobuf::FieldDescriptor* f) { + switch (type) { + case IDL_AUTO: return to_mcpack_typestr(f); + case IDL_INT8: return "int8"; + case IDL_INT16: return "int16"; + case IDL_INT32: return "int32"; + case IDL_INT64: return "int64"; + case IDL_UINT8: return "uint8"; + case IDL_UINT16: return "uint16"; + case IDL_UINT32: return "uint32"; + case IDL_UINT64: return "uint64"; + case IDL_BOOL: return "bool"; + case IDL_FLOAT: return "float"; + case IDL_DOUBLE: return "double"; + case IDL_BINARY: return "binary"; + case IDL_STRING: return "string"; + } + return "unknown"; +} + +const char* to_mcpack_typestr_uppercase( + ConvertibleIdlType type, + const google::protobuf::FieldDescriptor* f) { + const char* s = to_mcpack_typestr(type, f); + static char tempbuf[32]; + char* p = tempbuf; + for (; *s; ++s, ++p) { + *p = ::toupper(*s); + } + *p = 0; + return tempbuf; +} + +const char* describe_idl_type(ConvertibleIdlType type) { + switch (type) { + case IDL_AUTO: return "IDL_AUTO"; + case IDL_INT8: return "IDL_INT8"; + case IDL_INT16: return "IDL_INT16"; + case IDL_INT32: return "IDL_INT32"; + case IDL_INT64: return "IDL_INT64"; + case IDL_UINT8: return "IDL_UINT8"; + case IDL_UINT16: return "IDL_UINT16"; + case IDL_UINT32: return "IDL_UINT32"; + case IDL_UINT64: return "IDL_UINT64"; + case IDL_BOOL: return "IDL_BOOL"; + case IDL_FLOAT: return "IDL_FLOAT"; + case IDL_DOUBLE: return "IDL_DOUBLE"; + case IDL_BINARY: return "IDL_BINARY"; + case IDL_STRING: return "IDL_STRING"; + } + return "Bad ConvertibleIdlType"; +} + +static std::string to_var_name(const std::string& name) { + std::string result = name; + for (size_t i = 0; i < result.size(); ++i) { + if (result[i] == '.') { + result[i] = '_'; + } + } + return result; +} + +static std::string to_cpp_name(const std::string& full_name) { + std::string cname; + cname.reserve(full_name.size() + 8); + for (size_t i = 0; i < full_name.size(); ++i) { + if (full_name[i] == '.') { + cname.append("::", 2); + } else { + cname.push_back(full_name[i]); + } + } + return cname; +} + +bool generate_declarations(const std::set& ref_msgs, + const std::set& ref_maps, + google::protobuf::io::Printer& decl) { + for (std::set::const_iterator + it = ref_msgs.begin(); it != ref_msgs.end(); ++it) { + decl.Print( + "extern bool parse_$vmsg$_body_internal(\n" + " ::google::protobuf::Message* msg,\n" + " ::mcpack2pb::UnparsedValue& value);\n" + "extern void serialize_$vmsg$_body(\n" + " const ::google::protobuf::Message& msg,\n" + " ::mcpack2pb::Serializer& serializer,\n" + " ::mcpack2pb::SerializationFormat format);\n" + "extern ::mcpack2pb::FieldMap* g_$vmsg$_fields;\n" + , "vmsg", *it); + } + for (std::set::const_iterator + it = ref_maps.begin(); it != ref_maps.end(); ++it) { + decl.Print( + "extern bool set_$vmsg$_value(::google::protobuf::Message* msg,\n" + " ::mcpack2pb::UnparsedValue& value);\n" + , "vmsg", *it); + } + + return !decl.failed(); +} + +#define TEMPLATE_OF_SET_FUNC_SIGNATURE() \ + "bool set_$vmsg$_$lcfield$(::google::protobuf::Message* msg_base,\n" \ + " ::mcpack2pb::UnparsedValue& value) " + +#define TEMPLATE_OF_SET_FUNC_BODY(fntype) \ + "{\n" \ + " static_cast<$msg$*>(msg_base)->set_$lcfield$(value.as_"#fntype"(\"$field$\"));\n" \ + " return value.stream()->good();\n" \ + "}\n" + +#define TEMPLATE_OF_ADD_FUNC_SIGNATURE() \ + "bool set_$vmsg$_$lcfield$(::google::protobuf::Message* msg_base,\n" \ + " ::mcpack2pb::UnparsedValue& value) " \ + +#define TEMPLATE_OF_ADD_FUNC_BODY(fntype, pbtype) \ + "{\n" \ + " $msg$* const msg = static_cast<$msg$*>(msg_base);\n" \ + " if (value.type() == ::mcpack2pb::FIELD_ISOARRAY) {\n" \ + " ::mcpack2pb::ISOArrayIterator it(value);\n" \ + " msg->mutable_$lcfield$()->Reserve(it.item_count());\n" \ + " for (; it != NULL; ++it) {\n" \ + " msg->add_$lcfield$(it.as_"#fntype "());\n" \ + " }\n" \ + " return value.stream()->good();\n" \ + " } else if (value.type() == ::mcpack2pb::FIELD_ARRAY) {\n" \ + " ::mcpack2pb::ArrayIterator it(value);\n" \ + " msg->mutable_$lcfield$()->Reserve(it.item_count());\n" \ + " for (; it != NULL; ++it) {\n" \ + " msg->add_$lcfield$(it->as_"#fntype "(\"$field$\"));\n" \ + " }\n" \ + " return value.stream()->good();\n" \ + " }\n" \ + " LOG(ERROR) << \"Can't set \" << value << \" to $field$ (repeated " #pbtype ")\";\n" \ + " return false;\n" \ + "}\n" + +static bool is_map_entry(const google::protobuf::Descriptor* d) { + if (d->field_count() != 2 || + d->field(0)->name() != "key" || + d->field(0)->number() != 1 || + d->field(1)->name() != "value" || + d->field(1)->number() != 2) { + return false; + } + if (d->field(0)->is_repeated()) { + LOG(ERROR) << d->field(0)->full_name() << " must be required or optional"; + return false; + } + if (d->field(1)->is_repeated()) { + LOG(ERROR) << d->field(1)->full_name() << " must be required or optional"; + return false; + } + if (d->field(0)->cpp_type() != google::protobuf::FieldDescriptor::CPPTYPE_STRING) { + LOG(ERROR) << "key of idl map must be string"; + return false; + } + return true; +} + +static bool generate_parsing(const google::protobuf::Descriptor* d, + std::set & ref_msgs, + std::set & ref_maps, + google::protobuf::io::Printer& impl) { + std::string var_name = mcpack2pb::to_var_name(butil::EnsureString(d->full_name())); + std::string cpp_name = mcpack2pb::to_cpp_name(butil::EnsureString(d->full_name())); + ref_msgs.insert(var_name); + + impl.Print("\n// $msg$ from mcpack\n", "msg", d->full_name()); + for (int i = 0; i < d->field_count(); ++i) { + const google::protobuf::FieldDescriptor* f = d->field(i); + if (f->is_repeated()) { + impl.Print("// repeated $type$ $name$ = $number$;\n" + , "type", field_to_string(f) + , "name", f->name() + , "number", butil::string_printf("%d", f->number())); + impl.Print(TEMPLATE_OF_ADD_FUNC_SIGNATURE() + , "vmsg", var_name + , "lcfield", f->lowercase_name()); + switch (f->cpp_type()) { + case google::protobuf::FieldDescriptor::CPPTYPE_INT32: + impl.Print(TEMPLATE_OF_ADD_FUNC_BODY(int32, int32) + , "msg", cpp_name + , "field", f->full_name() + , "lcfield", f->lowercase_name()); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_INT64: + impl.Print(TEMPLATE_OF_ADD_FUNC_BODY(int64, int64) + , "msg", cpp_name + , "field", f->full_name() + , "lcfield", f->lowercase_name()); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_UINT32: + impl.Print(TEMPLATE_OF_ADD_FUNC_BODY(uint32, uint32) + , "msg", cpp_name + , "field", f->full_name() + , "lcfield", f->lowercase_name()); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_UINT64: + impl.Print(TEMPLATE_OF_ADD_FUNC_BODY(uint64, uint64) + , "msg", cpp_name + , "field", f->full_name() + , "lcfield", f->lowercase_name()); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_BOOL: + impl.Print(TEMPLATE_OF_ADD_FUNC_BODY(bool, bool) + , "msg", cpp_name + , "field", f->full_name() + , "lcfield", f->lowercase_name()); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: + impl.Print( + "{\n" + " $msg$* const msg = static_cast<$msg$*>(msg_base);\n" + " if (value.type() == ::mcpack2pb::FIELD_ISOARRAY) {\n" + " ::mcpack2pb::ISOArrayIterator it(value);\n" + " msg->mutable_$lcfield$()->Reserve(it.item_count());\n" + " for (; it != NULL; ++it) {\n" + " msg->add_$lcfield$(($enum$)it.as_int32());\n" + " }\n" + " return value.stream()->good();\n" + " } else if (value.type() == ::mcpack2pb::FIELD_ARRAY) {\n" + " ::mcpack2pb::ArrayIterator it(value);\n" + " msg->mutable_$lcfield$()->Reserve(it.item_count());\n" + " for (; it != NULL; ++it) {\n" + " msg->add_$lcfield$(($enum$)it->as_int32(\"$enum$\"));\n" + " }\n" + " return value.stream()->good();\n" + " }\n" + " LOG(ERROR) << \"Can't set \" << value << \" to repeated $enum$\";\n" + " return false;\n" + "}\n" + , "msg", cpp_name + , "enum", to_cpp_name(butil::EnsureString(f->enum_type()->full_name())) + , "lcfield", f->lowercase_name()); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT: + impl.Print(TEMPLATE_OF_ADD_FUNC_BODY(float, float) + , "msg", cpp_name + , "field", f->full_name() + , "lcfield", f->lowercase_name()); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE: + impl.Print(TEMPLATE_OF_ADD_FUNC_BODY(double, double) + , "msg", cpp_name + , "field", f->full_name() + , "lcfield", f->lowercase_name()); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_STRING: + impl.Print( + "{\n" + " $msg$* const msg = static_cast<$msg$*>(msg_base);\n" + " if (value.type() == ::mcpack2pb::FIELD_ARRAY) {\n" + " ::mcpack2pb::ArrayIterator it(value);\n" + " msg->mutable_$lcfield$()->Reserve(it.item_count());\n" + " for (; it != NULL; ++it) {\n" + " if (it->type() == ::mcpack2pb::FIELD_STRING) {\n" + " it->as_string(msg->add_$lcfield$(), \"$field$\");\n" + " } else if (it->type() == ::mcpack2pb::FIELD_BINARY) {\n" + " it->as_binary(msg->add_$lcfield$(), \"$field$\");\n" + " } else {\n" + " LOG(ERROR) << \"Can't add \" << *it << \" to $field$ (repeated string)\";\n" + " return false;\n" + " }\n" + " }\n" + " return value.stream()->good();\n" + " }\n" + " LOG(ERROR) << \"Can't set \" << value << \" to $field$ (repeated string)\";\n" + " return false;\n" + "}\n" + , "msg", cpp_name + , "field", f->full_name() + , "lcfield", f->lowercase_name()); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: { + std::string var_name2 = mcpack2pb::to_var_name(butil::EnsureString(f->message_type()->full_name())); + std::string cpp_name2 = mcpack2pb::to_cpp_name(butil::EnsureString(f->message_type()->full_name())); + if (is_map_entry(f->message_type())) { + ref_maps.insert(var_name2); + impl.Print( + "{\n" + " $msg$* const msg = static_cast<$msg$*>(msg_base);\n" + " if (value.type() == ::mcpack2pb::FIELD_OBJECT) {\n" + " ::mcpack2pb::ObjectIterator it(value);\n" + " for (; it != NULL; ++it) {\n" + " $msg2$* sub = msg->add_$lcfield$();\n" + " sub->set_key(it->name.data(), it->name.size());\n" + , "msg", cpp_name + , "msg2", cpp_name2 + , "lcfield", f->lowercase_name()); + impl.Print( + " if (!set_$vmsg2$_value(sub, it->value)) {\n" + " return false;\n" + " }\n" + " }\n" + " return value.stream()->good();\n" + " }\n" + " LOG(ERROR) << \"Can't set \" << value << \" to $field$ (repeated $msg2$)\";\n" + " return false;\n" + "}\n" + , "vmsg2", var_name2 + , "msg2", f->message_type()->full_name() + , "field", f->full_name()); + break; + } + ref_msgs.insert(var_name2); + impl.Print( + "{\n" + " $msg$* const msg = static_cast<$msg$*>(msg_base);\n" + " if (value.type() == ::mcpack2pb::FIELD_OBJECTISOARRAY) {\n" + " ::mcpack2pb::ObjectIterator it(value);\n" + " for (; it != NULL; ++it) {\n" + " ::mcpack2pb::SetFieldFn* fn = g_$vmsg2$_fields->seek(it->name);\n" + " if (!fn) {\n" + " if (!FLAGS_mcpack2pb_absent_field_is_error) {\n" + " continue;\n" + " } else {\n" + " LOG(ERROR) << \"No field=\" << it->name << \" (\"\n" + " << value << \") in $msg$\";\n" + " return false;\n" + " }\n" + " }\n" + " if (it->value.type() == ::mcpack2pb::FIELD_ARRAY) {\n" + " ::mcpack2pb::ArrayIterator it2(it->value);\n" + " int i = 0;\n" + " for (; it2 != NULL; ++it2, ++i) {\n" + " ::google::protobuf::Message* sub_msg = NULL;\n" + " if (i < msg->$lcfield$_size()) {\n" + " sub_msg = msg->mutable_$lcfield$(i);\n" + " } else {\n" + " sub_msg = msg->add_$lcfield$();\n" + " }\n" + " if (it2->type() != ::mcpack2pb::FIELD_NULL) {\n" + " if (!(*fn)(sub_msg, *it2)) {\n" + " LOG(ERROR) << \"Fail to set item of \" << it->name;\n" + " return false;\n" + " }\n" + " }\n" + " }\n" + " } else if (it->value.type() == ::mcpack2pb::FIELD_ISOARRAY) {\n" + " LOG(ERROR) << \"Shouldn't be a iso array, name=\" << it->name;\n" + " return false;\n" + " } else {\n" + " LOG(ERROR) << \"Can't add \" << value << \" to repeated $msg$\";\n" + " return false;\n" + " }\n" + " }\n" + " return value.stream()->good();\n" + " } else if (value.type() == ::mcpack2pb::FIELD_ARRAY) {\n" + " ::mcpack2pb::ArrayIterator it(value);\n" + " msg->mutable_$lcfield$()->Reserve(it.item_count());\n" + " for (; it != NULL; ++it) {\n" + " if (it->type() == ::mcpack2pb::FIELD_OBJECT) {\n" + " if (!parse_$vmsg2$_body_internal(msg->add_$lcfield$(), *it)) {\n" + " return false;\n" + " }\n" + " } else {\n" + " LOG(ERROR) << \"Can't add \" << *it << \" to repeated $msg$\";\n" + " return false;\n" + " }\n" + " }\n" + " return value.stream()->good();\n" + " }\n" + , "vmsg2", var_name2 + , "msg", cpp_name + , "lcfield", f->lowercase_name()); + impl.Print( + " LOG(ERROR) << \"Can't set \" << value << \" to $field$ (repeated $msg2$)\";\n" + " return false;\n" + "}\n" + , "msg2", f->message_type()->full_name() + , "field", f->full_name()); + } break; + } // switch + } else { + if (!f->is_required() && !f->is_repeated()) { + impl.Print("// optional $type$ $name$ = $number$;\n" + , "type", field_to_string(f) + , "name", f->name() + , "number", butil::string_printf("%d", f->number())); + } else { + impl.Print("// required $type$ $name$ = $number$;\n" + , "type", field_to_string(f) + , "name", f->name() + , "number", butil::string_printf("%d", f->number())); + } + impl.Print(TEMPLATE_OF_SET_FUNC_SIGNATURE() + , "vmsg", var_name + , "lcfield", f->lowercase_name()); + switch (f->cpp_type()) { + case google::protobuf::FieldDescriptor::CPPTYPE_INT32: + impl.Print(TEMPLATE_OF_SET_FUNC_BODY(int32) + , "msg", cpp_name + , "field", f->full_name() + , "lcfield", f->lowercase_name()); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_INT64: + impl.Print(TEMPLATE_OF_SET_FUNC_BODY(int64) + , "msg", cpp_name + , "field", f->full_name() + , "lcfield", f->lowercase_name()); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_UINT32: + impl.Print(TEMPLATE_OF_SET_FUNC_BODY(uint32) + , "msg", cpp_name + , "field", f->full_name() + , "lcfield", f->lowercase_name()); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_UINT64: + impl.Print(TEMPLATE_OF_SET_FUNC_BODY(uint64) + , "msg", cpp_name + , "field", f->full_name() + , "lcfield", f->lowercase_name()); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_BOOL: + impl.Print(TEMPLATE_OF_SET_FUNC_BODY(bool) + , "msg", cpp_name + , "field", f->full_name() + , "lcfield", f->lowercase_name()); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT: + impl.Print(TEMPLATE_OF_SET_FUNC_BODY(float) + , "msg", cpp_name + , "field", f->full_name() + , "lcfield", f->lowercase_name()); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE: + impl.Print(TEMPLATE_OF_SET_FUNC_BODY(double) + , "msg", cpp_name + , "field", f->full_name() + , "lcfield", f->lowercase_name()); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: + impl.Print( + "{\n" + " static_cast<$msg$*>(msg_base)->set_$lcfield$(static_cast<$enum$>(value.as_int32(\"$enum$\")));\n" + " return value.stream()->good();\n" + "}\n" + , "msg", cpp_name + , "enum", to_cpp_name(butil::EnsureString(f->enum_type()->full_name())) + , "lcfield", f->lowercase_name()); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_STRING: + // TODO: Encoding checking/conversion for pb strings(utf8). + impl.Print( + "{\n" + " if (value.type() == ::mcpack2pb::FIELD_STRING) {\n" + " value.as_string(static_cast<$msg$*>(msg_base)->mutable_$lcfield$(), \"$field$\");\n" + " return value.stream()->good();\n" + " } else if (value.type() == ::mcpack2pb::FIELD_BINARY) {\n" + " value.as_binary(static_cast<$msg$*>(msg_base)->mutable_$lcfield$(), \"$field$\");\n" + " return value.stream()->good();\n" + " }\n" + " LOG(ERROR) << \"Can't set \" << value << \" to $field$ (string)\";\n" + " return false;\n" + "}\n" + , "msg", cpp_name + , "field", f->full_name() + , "lcfield", f->lowercase_name()); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: { + std::string var_name2 = mcpack2pb::to_var_name(butil::EnsureString(f->message_type()->full_name())); + ref_msgs.insert(var_name2); + impl.Print( + "{\n" + " if (value.type() == ::mcpack2pb::FIELD_OBJECT) {\n" + " return parse_$vmsg2$_body_internal(static_cast<$msg$*>(msg_base)->mutable_$lcfield$(), value);\n" + " }\n" + , "vmsg2", var_name2 + , "msg", cpp_name + , "lcfield", f->lowercase_name()); + impl.Print( + // FIXME: describe type. + " LOG(ERROR) << \"Can't set \" << value << \" to $field$\";\n" + " return false;\n" + "}\n" + , "field", f->full_name()); + } break; + } // switch + } // else + } + + impl.Print( + "bool parse_$vmsg$_body_internal(\n" + " ::google::protobuf::Message* msg,\n" + " ::mcpack2pb::UnparsedValue& value) {\n" + " ::mcpack2pb::ObjectIterator it(value);\n" + " for (; it != NULL; ++it) {\n" + " ::mcpack2pb::SetFieldFn* fn = g_$vmsg$_fields->seek(it->name);\n" + " if (!fn) {\n" + " if (!FLAGS_mcpack2pb_absent_field_is_error) {\n" + " continue;\n" + " } else {\n" + " LOG(ERROR) << \"No field=\" << it->name << \" (\"\n" + " << it->value << \") in $msg$\";\n" + " return false;\n" + " }\n" + " }\n" + " if (!(*fn)(msg, it->value)) {\n" + " return false;\n" + " }\n" + " }\n" + " return value.stream()->good();\n" + "}\n" + "bool parse_$vmsg$_body(\n" + " ::google::protobuf::Message* msg,\n" + " ::google::protobuf::io::ZeroCopyInputStream* input,\n" + " size_t size) {\n" + " ::mcpack2pb::InputStream mc_stream(input);\n" + " ::mcpack2pb::UnparsedValue value(::mcpack2pb::FIELD_OBJECT, &mc_stream, size);\n" + " if (!parse_$vmsg$_body_internal(msg, value)) {\n" + " return false;\n" + " }\n" + " if (!msg->IsInitialized()) {\n" + " LOG(ERROR) << \"Missing required fields: \" << msg->InitializationErrorString();\n" + " return false;\n" + " }\n" + " return true;\n" + "}\n" + "size_t parse_$vmsg$(\n" + " ::google::protobuf::Message* msg,\n" + " ::google::protobuf::io::ZeroCopyInputStream* input) {\n" + " ::mcpack2pb::InputStream mc_stream(input);\n" + " const size_t value_size = ::mcpack2pb::unbox(&mc_stream);\n" + " if (!value_size) {\n" + " LOG(ERROR) << \"Fail to unbox\";\n" + " return 0;\n" + " }\n" + " ::mcpack2pb::UnparsedValue value(::mcpack2pb::FIELD_OBJECT, &mc_stream, value_size);\n" + " if (!parse_$vmsg$_body_internal(msg, value)) {\n" + " return 0;\n" + " }\n" + " if (!msg->IsInitialized()) {\n" + " LOG(ERROR) << \"Missing required fields: \" << msg->InitializationErrorString();\n" + " return 0;\n" + " }\n" + " return 6/*sizeof(FieldLongHead)*/ + value_size;\n" + "}\n" + , "vmsg", var_name + , "msg", d->full_name()); + return !impl.failed(); +} + +#define TEMPLATE_SERIALIZE_REPEATED(cit, printer, field, strict_cond, looser_cond) \ + if (strict_cond) { \ + (printer).Print( \ + "if (msg.$lcfield$_size()) {\n" \ + " if (format == ::mcpack2pb::FORMAT_COMPACK) {\n" \ + " serializer.begin_compack_array(\"$field$\", ::mcpack2pb::FIELD_$TYPE$);\n" \ + " } else {\n" \ + " serializer.begin_mcpack_array(\"$field$\", ::mcpack2pb::FIELD_$TYPE$);\n" \ + " }\n" \ + , "lcfield", (field)->lowercase_name() \ + , "field", get_idl_name(field) \ + , "TYPE", to_mcpack_typestr_uppercase(cit, (field))); \ + (printer).Print( \ + " serializer.add_multiple_$type$(msg.$lcfield$().data(), msg.$lcfield$_size());\n" \ + " serializer.end_array();\n" \ + "}" \ + , "type", to_mcpack_typestr(cit, (field)) \ + , "field", get_idl_name(field) \ + , "lcfield", (field)->lowercase_name()); \ + if ((field)->options().GetExtension(idl_on)) { \ + (printer).Print( \ + " else {\n" \ + " serializer.add_empty_array(\"$field$\");\n" \ + "}\n" \ + , "field", get_idl_name(field)); \ + } else { \ + (printer).Print("\n"); \ + } \ + } else if (looser_cond) { \ + (printer).Print( \ + "if (msg.$lcfield$_size()) {\n" \ + " if (format == ::mcpack2pb::FORMAT_COMPACK) {\n" \ + " serializer.begin_compack_array(\"$field$\", ::mcpack2pb::FIELD_$TYPE$);\n" \ + " } else {\n" \ + " serializer.begin_mcpack_array(\"$field$\", ::mcpack2pb::FIELD_$TYPE$);\n" \ + " }\n" \ + , "lcfield", (field)->lowercase_name() \ + , "field", get_idl_name(field) \ + , "TYPE", to_mcpack_typestr_uppercase(cit, (field))); \ + (printer).Print( \ + " for (int i = 0; i < msg.$lcfield$_size(); ++i) {\n" \ + " serializer.add_$type$(msg.$lcfield$(i));\n" \ + " }\n" \ + " serializer.end_array();\n" \ + "}" \ + , "type", to_mcpack_typestr(cit, (field)) \ + , "field", get_idl_name(field) \ + , "lcfield", (field)->lowercase_name()); \ + if ((field)->options().GetExtension(idl_on)) { \ + (printer).Print( \ + " else {\n" \ + " serializer.add_empty_array(\"$field$\");\n" \ + "}\n" \ + , "field", get_idl_name(field)); \ + } else { \ + (printer).Print("\n"); \ + } \ + } else { \ + if ((field)->type() == google::protobuf::FieldDescriptor::TYPE_ENUM) { \ + LOG(ERROR) << "Disallow converting " << (field)->full_name() \ + << " (" << (field)->enum_type()->full_name() << ") to " \ + << to_mcpack_typestr(cit, (field)) << " (idl)"; \ + } else { \ + LOG(ERROR) << "Disallow converting " << (field)->full_name() \ + << " (" << field_to_string(field) << ") to " \ + << to_mcpack_typestr(cit, (field)) << " (idl)"; \ + } \ + return false; \ + } \ + +#define TEMPLATE_SERIALIZE_REPEATED_INTEGRAL(cit, printer, field, strict_cond) \ + TEMPLATE_SERIALIZE_REPEATED(cit, printer, field, ((cit) == IDL_AUTO || (strict_cond)), \ + is_integral_type(cit)) + + +#define TEMPLATE_SERIALIZE(cit, printer, field, cond) \ + if (!(cond)) { \ + if ((field)->type() == google::protobuf::FieldDescriptor::TYPE_ENUM) { \ + LOG(ERROR) << "Disallow converting " << (field)->full_name() \ + << " (" << (field)->enum_type()->full_name() << ") to " \ + << to_mcpack_typestr(cit, (field)) << " (idl)"; \ + } else { \ + LOG(ERROR) << "Disallow converting " << (field)->full_name() \ + << " (" << field_to_string(field) << ") to " \ + << to_mcpack_typestr(cit, (field)) << " (idl)"; \ + } \ + return false; \ + } \ + (printer).Print( \ + "if (msg.has_$lcfield$()) {\n" \ + " serializer.add_$type$(\"$field$\", msg.$lcfield$());\n" \ + "}" \ + , "type", to_mcpack_typestr(cit, (field)) \ + , "field", get_idl_name(field) \ + , "lcfield", (field)->lowercase_name()); \ + if ((field)->options().GetExtension(idl_on)) { \ + (printer).Print( \ + " else {\n" \ + " serializer.add_empty_array(\"$field$\");\n" \ + "}\n" \ + , "field", get_idl_name(field)); \ + } + +#define TEMPLATE_SERIALIZE_INTEGRAL(cit, printer, field) \ + TEMPLATE_SERIALIZE(cit, printer, field, \ + ((cit) == IDL_AUTO || is_integral_type(cit))) + + +#define TEMPLATE_INNER_SERIALIZE_REPEATED(msg, cit, printer, field, \ + strict_cond, looser_cond) \ + if ((strict_cond)) { \ + (printer).Print( \ + "if ($msg$.$lcfield$_size()) {\n" \ + " serializer.begin_compack_array(::mcpack2pb::FIELD_$TYPE$);\n" \ + , "msg", msg \ + , "TYPE", to_mcpack_typestr_uppercase(cit, (field)) \ + , "lcfield", (field)->lowercase_name()); \ + (printer).Print( \ + " serializer.add_multiple_$type$($msg$.$lcfield$().data(), $msg$.$lcfield$_size());\n" \ + " serializer.end_array();\n" \ + "}" \ + , "msg", msg \ + , "type", to_mcpack_typestr(cit, (field)) \ + , "lcfield", (field)->lowercase_name()); \ + if ((field)->options().GetExtension(idl_on)) { \ + (printer).Print( \ + " else {\n" \ + " serializer.add_empty_array();\n" \ + "}\n"); \ + } else { \ + (printer).Print( \ + " else {\n" \ + " serializer.add_null();\n" \ + "}\n"); \ + } \ + } else if (looser_cond) { \ + (printer).Print( \ + "if ($msg$.$lcfield$_size()) {\n" \ + " serializer.begin_compack_array(::mcpack2pb::FIELD_$TYPE$);\n" \ + , "msg", msg \ + , "TYPE", to_mcpack_typestr_uppercase(cit, (field)) \ + , "lcfield", (field)->lowercase_name()); \ + (printer).Print( \ + " for (int j = 0; j < $msg$.$lcfield$_size(); ++j) {\n" \ + " serializer.add_$type$($msg$.$lcfield$(j));\n" \ + " }\n" \ + " serializer.end_array();\n" \ + "}" \ + , "msg", msg \ + , "type", to_mcpack_typestr(cit, (field)) \ + , "lcfield", (field)->lowercase_name()); \ + if ((field)->options().GetExtension(idl_on)) { \ + (printer).Print( \ + " else {\n" \ + " serializer.add_empty_array();\n" \ + "}\n"); \ + } else { \ + (printer).Print( \ + " else {\n" \ + " serializer.add_null();\n" \ + "}\n"); \ + } \ + } else { \ + if ((field)->type() == google::protobuf::FieldDescriptor::TYPE_ENUM) { \ + LOG(ERROR) << "Disallow converting " << (field)->full_name() \ + << " (" << (field)->enum_type()->full_name() << ") to " \ + << to_mcpack_typestr(cit, (field)) << " (idl)"; \ + } else { \ + LOG(ERROR) << "Disallow converting " << (field)->full_name() \ + << " (" << field_to_string(field) << ") to " \ + << to_mcpack_typestr(cit, (field)) << " (idl)"; \ + } \ + return false; \ + } \ + +#define TEMPLATE_INNER_SERIALIZE_REPEATED_INTEGRAL(msg, cit, printer, field, strict_cond) \ + TEMPLATE_INNER_SERIALIZE_REPEATED(msg, cit, printer, field, ((cit) == IDL_AUTO || (strict_cond)), \ + is_integral_type(cit)) + + +#define TEMPLATE_INNER_SERIALIZE(msg, cit, printer, field, cond) \ + if (!(cond)) { \ + if ((field)->type() == google::protobuf::FieldDescriptor::TYPE_ENUM) { \ + LOG(ERROR) << "Disallow converting " << (field)->full_name() \ + << " (" << (field)->enum_type()->full_name() << ") to " \ + << to_mcpack_typestr(cit, (field)) << " (idl)"; \ + } else { \ + LOG(ERROR) << "Disallow converting " << (field)->full_name() \ + << " (" << field_to_string(field) << ") to " \ + << to_mcpack_typestr(cit, (field)) << " (idl)"; \ + } \ + return false; \ + } \ + (printer).Print("if ($msg$.has_$lcfield$()) {\n" \ + " serializer.add_$type$($msg$.$lcfield$());\n" \ + "} else {\n" \ + " serializer.add_null();\n" \ + "}\n" \ + , "msg", msg \ + , "type", to_mcpack_typestr(cit, (field)) \ + , "lcfield", (field)->lowercase_name()) + +#define TEMPLATE_INNER_SERIALIZE_INTEGRAL(msg, cit, printer, field) \ + TEMPLATE_INNER_SERIALIZE(msg, cit, printer, field, \ + ((cit) == IDL_AUTO || is_integral_type(cit))) + +static bool generate_serializing(const google::protobuf::Descriptor* d, + std::set & ref_msgs, + std::set & ref_maps, + google::protobuf::io::Printer & impl) { + std::string var_name = mcpack2pb::to_var_name(butil::EnsureString(d->full_name())); + std::string cpp_name = mcpack2pb::to_cpp_name(butil::EnsureString(d->full_name())); + ref_msgs.insert(var_name); + impl.Print( + "void serialize_$vmsg$_body(\n" + " const ::google::protobuf::Message& msg_base,\n" + " ::mcpack2pb::Serializer& serializer,\n" + " ::mcpack2pb::SerializationFormat format) {\n" + " (void)format; // suppress compiler warning when it's not used\n" + , "vmsg", var_name); + if (d->field_count()) { + impl.Print( + " const $msg$& msg = static_cast(msg_base);\n" + , "msg", cpp_name); + } else { + impl.Print(" (void)msg_base; // ^\n" + " (void)serializer; // ^\n"); + } + impl.Indent(); + for (int i = 0; i < d->field_count(); ++i) { + const google::protobuf::FieldDescriptor* f = d->field(i); + ConvertibleIdlType cit = f->options().GetExtension(idl_type); + // Print the field as comment. + std::string comment_template; + if (cit == IDL_AUTO) { + butil::string_printf(&comment_template, + "// %s $type$ $name$ = $number$;\n", + (f->is_repeated() ? "repeated" : + (f->is_required() ? "required" : "optional"))); + } else { + butil::string_printf(&comment_template, + "// %s $type$ $name$ = $number$ [(idl_type)=%s];\n", + (f->is_repeated() ? "repeated" : + (f->is_required() ? "required" : "optional")), + describe_idl_type(cit)); + } + impl.Print(comment_template.c_str() + , "type", field_to_string(f) + , "name", f->name() + , "number", butil::string_printf("%d", f->number())); + if (f->is_repeated()) { + switch (f->cpp_type()) { + case google::protobuf::FieldDescriptor::CPPTYPE_INT32: + case google::protobuf::FieldDescriptor::CPPTYPE_UINT32: + case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: + TEMPLATE_SERIALIZE_REPEATED_INTEGRAL( + cit, impl, f, (cit == IDL_INT32 || cit == IDL_UINT32)); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_INT64: + case google::protobuf::FieldDescriptor::CPPTYPE_UINT64: + TEMPLATE_SERIALIZE_REPEATED_INTEGRAL( + cit, impl, f, (cit == IDL_INT64 || cit == IDL_UINT64)); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_BOOL: + TEMPLATE_SERIALIZE_REPEATED( + cit, impl, f, (cit == IDL_AUTO || cit == IDL_BOOL), false); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT: + TEMPLATE_SERIALIZE_REPEATED( + cit, impl, f, + (cit == IDL_AUTO || cit == IDL_FLOAT), (cit == IDL_DOUBLE)); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE: + TEMPLATE_SERIALIZE_REPEATED( + cit, impl, f, + (cit == IDL_AUTO || cit == IDL_DOUBLE), (cit == IDL_FLOAT)); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_STRING: + if (f->type() == google::protobuf::FieldDescriptor::TYPE_STRING) { + TEMPLATE_SERIALIZE_REPEATED( + cit, impl, f, false, (cit == IDL_AUTO || cit == IDL_STRING)); + } else if (f->type() == google::protobuf::FieldDescriptor::TYPE_BYTES) { + TEMPLATE_SERIALIZE_REPEATED( + cit, impl, f, false, + (cit == IDL_AUTO || cit == IDL_BINARY || cit == IDL_STRING)); + } else { + LOG(ERROR) << "Unknown pb type=" << f->type(); + return false; + } + break; + case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: { + if (cit != IDL_AUTO) { + LOG(ERROR) << "Disallow converting " << f->full_name() + << " (" << f->message_type()->full_name() << ") to " + << to_mcpack_typestr(cit, f) << " (idl)"; + return false; + } + const google::protobuf::Descriptor* msg2 = f->message_type(); + std::string var_name2 = mcpack2pb::to_var_name(butil::EnsureString(msg2->full_name())); + std::string cpp_name2 = mcpack2pb::to_cpp_name(butil::EnsureString(msg2->full_name())); + if (is_map_entry(msg2)) { + ref_maps.insert(var_name2); + impl.Print( + "serializer.begin_object(\"$field$\");\n" + "for (int i = 0; i < msg.$lcfield$_size(); ++i) {\n" + " const $msg2$& pair = msg.$lcfield$(i);\n" + , "field", get_idl_name(f) + , "lcfield", f->lowercase_name() + , "msg2", cpp_name2); + const google::protobuf::FieldDescriptor* value_desc = msg2->field(1); + switch (value_desc->cpp_type()) { + case google::protobuf::FieldDescriptor::CPPTYPE_INT32: + impl.Print(" serializer.add_int32(pair.key(), pair.value());\n"); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_UINT32: + impl.Print(" serializer.add_uint32(pair.key(), pair.value());\n"); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: + impl.Print(" serializer.add_int32(pair.key(), (int32_t)pair.value());\n"); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_INT64: + impl.Print(" serializer.add_int64(pair.key(), pair.value());\n"); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_UINT64: + impl.Print(" serializer.add_uint64(pair.key(), pair.value());\n"); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_BOOL: + impl.Print(" serializer.add_bool(pair.key(), pair.value());\n"); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT: + impl.Print(" serializer.add_float(pair.key(), pair.value());\n"); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE: + impl.Print(" serializer.add_double(pair.key(), pair.value());\n"); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_STRING: + impl.Print(" serializer.add_string(pair.key(), pair.value());\n"); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: { + std::string var_name3 = mcpack2pb::to_var_name( + butil::EnsureString(value_desc->message_type()->full_name())); + ref_msgs.insert(var_name3); + impl.Print( + " serializer.begin_object(pair.key());\n" + " serialize_$vmsg3$_body(pair.value(), serializer, format);\n" + " serializer.end_object();\n" + , "vmsg3", var_name3); + } break; + } // switch + impl.Print( + "}\n" + "serializer.end_object();\n"); + break; + } + + ref_msgs.insert(var_name2); + impl.Print( + "if (format == ::mcpack2pb::FORMAT_MCPACK_V2) {\n" + " if (msg.$lcfield$_size()) {\n" + " serializer.begin_mcpack_array(\"$field$\", ::mcpack2pb::FIELD_OBJECT);\n" + " for (int i = 0; i < msg.$lcfield$_size(); ++i) {\n" + " serializer.begin_object();\n" + " serialize_$vmsg2$_body(msg.$lcfield$(i), serializer, format);\n" + " serializer.end_object();\n" + " }\n" + " serializer.end_array();\n" + " }" + , "field", get_idl_name(f) + , "lcfield", f->lowercase_name() + , "vmsg2", var_name2); + if (f->options().GetExtension(idl_on)) { + impl.Print( + " else {\n" + " serializer.add_empty_array(\"$field$\");\n" + " }\n", "field", get_idl_name(f)); + } else { + impl.Print("\n"); + } + impl.Print("} else if (msg.$lcfield$_size()) {\n" + , "lcfield", f->lowercase_name()); + impl.Indent(); + impl.Print("serializer.begin_object(\"$field$\");\n" + , "field", get_idl_name(f)); + for (int j = 0; j < msg2->field_count(); ++j) { + const google::protobuf::FieldDescriptor* f2 = msg2->field(j); + ConvertibleIdlType cit2 = f2->options().GetExtension(idl_type); + impl.Print( + "serializer.begin_mcpack_array(\"$f2$\", ::mcpack2pb::FIELD_$TYPE$);\n" + "for (int i = 0; i < msg.$lcfield$_size(); ++i) {\n" + , "f2", get_idl_name(f2) + , "TYPE", (f2->is_repeated() ? "ARRAY" : to_mcpack_typestr_uppercase(cit2, (f2))) + , "lcfield", f->lowercase_name()); + impl.Indent(); + if (f2->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE) { + if (cit2 != IDL_AUTO) { + LOG(ERROR) << "Disallow converting " << f2->full_name() + << " (" << f2->message_type()->full_name() << ") to " + << to_mcpack_typestr(cit2, f2) << " (idl)"; + return false; + } + std::string var_name3 = mcpack2pb::to_var_name(butil::EnsureString(f2->message_type()->full_name())); + ref_msgs.insert(var_name3); + if (f2->is_repeated()) { + impl.Print( + "const int $lcfield2$_size = msg.$lcfield$(i).$lcfield2$_size();\n" + "if ($lcfield2$_size) {\n" + " serializer.begin_mcpack_array(::mcpack2pb::FIELD_OBJECT);\n" + " for (int j = 0; j < $lcfield2$_size; ++j) {\n" + " serializer.begin_object();\n" + " serialize_$vmsg3$_body(msg.$lcfield$(i).$lcfield2$(j), serializer, format);\n" + " serializer.end_object();\n" + " }\n" + " serializer.end_array();\n" + "}" + , "vmsg3", var_name3 + , "lcfield", f->lowercase_name() + , "lcfield2", f2->lowercase_name()); + if (f2->options().GetExtension(idl_on)) { + impl.Print( + " else {\n" + " serializer.add_empty_array();\n" + "}\n"); + } else { + impl.Print( + " else {\n" + " serializer.add_null();\n" + "}\n"); + } + } else { + impl.Print( + "if (msg.$lcfield$(i).has_$lcfield2$()) {\n" + " serializer.begin_object();\n" + " serialize_$vmsg3$_body(msg.$lcfield$(i).$lcfield2$(), serializer, format);\n" + " serializer.end_object();\n" + "} else {\n" + " serializer.add_null();\n" + "}\n" + , "vmsg3", var_name3 + , "lcfield", f->lowercase_name() + , "lcfield2", f2->lowercase_name()); + } + } else if (f2->is_repeated()) { + const std::string msgstr = butil::string_printf( + "msg.%s(i)", butil::EnsureString(f->lowercase_name()).c_str()); + switch (f2->cpp_type()) { + case google::protobuf::FieldDescriptor::CPPTYPE_INT32: + case google::protobuf::FieldDescriptor::CPPTYPE_UINT32: + case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: + TEMPLATE_INNER_SERIALIZE_REPEATED_INTEGRAL( + msgstr.c_str(), cit2, impl, f2, + (cit2 == IDL_INT32 || cit2 == IDL_UINT32)); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_INT64: + case google::protobuf::FieldDescriptor::CPPTYPE_UINT64: + TEMPLATE_INNER_SERIALIZE_REPEATED_INTEGRAL( + msgstr.c_str(), cit2, impl, f2, + (cit2 == IDL_INT64 || cit2 == IDL_UINT64)); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_BOOL: + TEMPLATE_INNER_SERIALIZE_REPEATED( + msgstr.c_str(), cit2, impl, f2, + (cit2 == IDL_AUTO || cit2 == IDL_BOOL), false); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT: + TEMPLATE_INNER_SERIALIZE_REPEATED( + msgstr.c_str(), cit2, impl, f2, + (cit2 == IDL_AUTO || cit2 == IDL_FLOAT), (cit2 == IDL_DOUBLE)); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE: + TEMPLATE_INNER_SERIALIZE_REPEATED( + msgstr.c_str(), cit2, impl, f2, + (cit2 == IDL_AUTO || cit2 == IDL_DOUBLE), (cit2 == IDL_FLOAT)); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_STRING: + if (f2->type() == google::protobuf::FieldDescriptor::TYPE_STRING) { + TEMPLATE_INNER_SERIALIZE_REPEATED( + msgstr.c_str(), cit2, impl, f2, false, + (cit2 == IDL_AUTO || cit2 == IDL_STRING)); + } else if (f2->type() == google::protobuf::FieldDescriptor::TYPE_BYTES) { + TEMPLATE_INNER_SERIALIZE_REPEATED( + msgstr.c_str(), cit2, impl, f2, false, + (cit2 == IDL_AUTO || cit2 == IDL_BINARY || cit2 == IDL_STRING)); + } else { + LOG(ERROR) << "Unknown pb type=" << f2->type(); + return false; + } + break; + case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: + CHECK(false) << "Impossible"; + return false; + } + } else { + const std::string msgstr = butil::string_printf( + "msg.%s(i)", butil::EnsureString(f->lowercase_name()).c_str()); + switch (f2->cpp_type()) { + case google::protobuf::FieldDescriptor::CPPTYPE_INT32: + case google::protobuf::FieldDescriptor::CPPTYPE_INT64: + case google::protobuf::FieldDescriptor::CPPTYPE_UINT32: + case google::protobuf::FieldDescriptor::CPPTYPE_UINT64: + case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: + TEMPLATE_INNER_SERIALIZE_INTEGRAL(msgstr.c_str(), cit2, impl, f2); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_BOOL: + TEMPLATE_INNER_SERIALIZE( + msgstr.c_str(), cit2, impl, f2, + (cit2 == IDL_AUTO || cit2 == IDL_BOOL)); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT: + TEMPLATE_INNER_SERIALIZE( + msgstr.c_str(), cit2, impl, f2, + (cit2 == IDL_AUTO || cit2 == IDL_FLOAT)); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE: + TEMPLATE_INNER_SERIALIZE( + msgstr.c_str(), cit2, impl, f2, + (cit2 == IDL_AUTO || cit2 == IDL_FLOAT || cit2 == IDL_DOUBLE)); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_STRING: + if (f2->type() == google::protobuf::FieldDescriptor::TYPE_STRING) { + TEMPLATE_INNER_SERIALIZE( + msgstr.c_str(), cit2, impl, f2, + (cit2 == IDL_AUTO || cit2 == IDL_STRING)); + } else if (f2->type() == google::protobuf::FieldDescriptor::TYPE_BYTES) { + TEMPLATE_INNER_SERIALIZE( + msgstr.c_str(), cit2, impl, f2, + (cit2 == IDL_AUTO || cit2 == IDL_BINARY || cit2 == IDL_STRING)); + } else { + LOG(ERROR) << "Unknown pb type=" << f2->type(); + return false; + } + break; + case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: + LOG(ERROR) << "Impossible"; + return false; + } + } + impl.Outdent(); + impl.Print("}\n" + "serializer.end_array();\n"); + } + impl.Print("serializer.end_object_iso();\n"); + impl.Outdent(); + impl.Print("} else {\n" + " serializer.begin_object(\"$field$\");\n" + " serializer.end_object_iso();\n" + "}\n" + , "field", get_idl_name(f)); + } break; + } // switch + } else { + switch (f->cpp_type()) { + case google::protobuf::FieldDescriptor::CPPTYPE_INT32: + case google::protobuf::FieldDescriptor::CPPTYPE_INT64: + case google::protobuf::FieldDescriptor::CPPTYPE_UINT32: + case google::protobuf::FieldDescriptor::CPPTYPE_UINT64: + case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: + TEMPLATE_SERIALIZE_INTEGRAL(cit, impl, f); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_BOOL: + TEMPLATE_SERIALIZE(cit, impl, f, + (cit == IDL_AUTO || cit == IDL_BOOL)); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT: + TEMPLATE_SERIALIZE(cit, impl, f, + (cit == IDL_AUTO || cit == IDL_FLOAT)); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE: + TEMPLATE_SERIALIZE( + cit, impl, f, + (cit == IDL_AUTO || cit == IDL_FLOAT || cit == IDL_DOUBLE)); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_STRING: + if (f->type() == google::protobuf::FieldDescriptor::TYPE_STRING) { + TEMPLATE_SERIALIZE( + cit, impl, f, (cit == IDL_AUTO || cit == IDL_STRING)); + } else if (f->type() == google::protobuf::FieldDescriptor::TYPE_BYTES) { + TEMPLATE_SERIALIZE( + cit, impl, f, + (cit == IDL_AUTO || cit == IDL_BINARY || cit == IDL_STRING)); + } else { + LOG(ERROR) << "Unknown pb type=" << f->type(); + return false; + } + break; + case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: { + if (cit != IDL_AUTO) { + LOG(ERROR) << "Disallow converting " << f->full_name() + << " (" << f->message_type()->full_name() << ") to " + << to_mcpack_typestr(cit, f) << " (idl)"; + return false; + } + std::string var_name2 = mcpack2pb::to_var_name(butil::EnsureString(f->message_type()->full_name())); + ref_msgs.insert(var_name2); + impl.Print("if (msg.has_$lcfield$()) {\n" + " serializer.begin_object(\"$field$\");\n" + " serialize_$vmsg$_body(msg.$lcfield$(), serializer, format);\n" + " serializer.end_object();\n" + "}" + , "field", get_idl_name(f) + , "lcfield", f->lowercase_name() + , "vmsg", var_name2); + } break; + } // switch + if (f->is_required()) { + impl.Print(" else {\n" + " LOG(ERROR) << \"Missing required $field$\";\n" + " return serializer.set_bad();\n" + "}\n", "field", f->full_name()); + } else { + impl.Print("\n"); + } + } // else + } + impl.Outdent(); + impl.Print( + "}\n" + "bool serialize_$vmsg$(\n" + " const ::google::protobuf::Message& msg_base,\n" + " ::google::protobuf::io::ZeroCopyOutputStream* output,\n" + " ::mcpack2pb::SerializationFormat format) {\n" + " ::mcpack2pb::OutputStream ostream(output);\n" + " ::mcpack2pb::Serializer serializer(&ostream);\n" + " serializer.begin_object();\n" + " serialize_$vmsg$_body(msg_base, serializer, format);\n" + " serializer.end_object();\n" + " ostream.done();\n" + " return serializer.good();\n" + "}\n" + , "vmsg", var_name); + return !impl.failed(); +} + +static std::string protobuf_style_normalize_filename(const std::string & fname) { + std::string norm_fname; + norm_fname.reserve(fname.size() + 10); + for (size_t i = 0; i < fname.size(); ++i) { + if (fname[i] == '_' || isdigit(fname[i]) || isalpha(fname[i])) { + norm_fname.push_back(fname[i]); + } else { + char symbol[4]; + snprintf(symbol, sizeof(symbol), "_%02x", (int)fname[i]); + norm_fname.append(symbol, 3); + } + } + return norm_fname; +} + +static bool generate_registration( + const google::protobuf::FileDescriptor* file, + google::protobuf::io::Printer & impl) { + const std::string cpp_ns = to_cpp_name(butil::EnsureString(file->package())); + std::string norm_fname = protobuf_style_normalize_filename(butil::EnsureString(file->name())); + impl.Print( + "\n// register all message handlers\n" + "struct RegisterMcpackFunctions_$norm_fname$ {\n" + " RegisterMcpackFunctions_$norm_fname$() {\n" + , "norm_fname", norm_fname); + impl.Indent(); + impl.Indent(); + for (int i = 0; i < file->message_type_count(); ++i) { + const google::protobuf::Descriptor* d = file->message_type(i); + std::string var_name = mcpack2pb::to_var_name(butil::EnsureString(d->full_name())); + + impl.Print( + "\n" + "g_$vmsg$_fields = new ::mcpack2pb::FieldMap;\n" + "CHECK_EQ(0, g_$vmsg$_fields->init(std::max($field_count$, 1), 30));\n" + , "vmsg", var_name + , "field_count", ::butil::string_printf("%d", d->field_count())); + for (int i = 0; i < d->field_count(); ++i) { + const google::protobuf::FieldDescriptor* f = d->field(i); + impl.Print("(*g_$vmsg$_fields)[\"$field$\"] = ::set_$vmsg$_$lcfield$;\n" + , "vmsg", var_name + , "field", get_idl_name(f) + , "lcfield", f->lowercase_name()); + } + impl.Print( + "::mcpack2pb::MessageHandler $vmsg$_handler = {\n" + " parse_$vmsg$,\n" + " parse_$vmsg$_body,\n" + " serialize_$vmsg$,\n" + " serialize_$vmsg$_body\n" + "};\n" + "::mcpack2pb::register_message_handler_or_die(\"$fmsg$\", $vmsg$_handler);\n" + , "fmsg", d->full_name() + , "vmsg", var_name); + } + impl.Outdent(); + impl.Outdent(); + impl.Print(" }\n" + "} static_init_mcpack_$suffix$;\n" + , "suffix", norm_fname); + return !impl.failed(); +} + +class McpackToProtobuf : public google::protobuf::compiler::CodeGenerator { +public: + bool Generate(const google::protobuf::FileDescriptor* file, + const std::string& parameter, + google::protobuf::compiler::GeneratorContext*, + std::string* error) const override; +}; + +bool McpackToProtobuf::Generate(const google::protobuf::FileDescriptor* file, + const std::string& /*parameter*/, + google::protobuf::compiler::GeneratorContext* ctx, + std::string* error) const { + if (!file->options().GetExtension(idl_support)) { + // skip the file. + return true; + } + + std::string cpp_name = butil::EnsureString(file->name()); + const size_t pos = cpp_name.find_last_of('.'); + if (pos == std::string::npos) { + ::butil::string_printf(error, "Bad filename=%s", cpp_name.c_str()); + return false; + } + cpp_name.resize(pos); + cpp_name.append(".pb.cc"); + + google::protobuf::io::Printer inc_printer( + ctx->OpenForInsert(cpp_name, "includes"), '$'); + inc_printer.Print("#include \n" + "#include \n" + "#include \n"); + + google::protobuf::io::Printer gdecl_printer( + ctx->OpenForInsert(cpp_name, "includes"), '$'); + google::protobuf::io::Printer gimpl_printer( + ctx->OpenForInsert(cpp_name, "global_scope"), '$'); + + gdecl_printer.Print( + "\n// ==== declarations generated by brpc/mcpack2pb/protoc-gen-mcpack ====\n" + "DECLARE_bool(mcpack2pb_absent_field_is_error);\n"); + + std::set ref_msgs; + std::set ref_maps; + for (int i = 0; i < file->message_type_count(); ++i) { + const google::protobuf::Descriptor* d = file->message_type(i); + if (!generate_parsing(d, ref_msgs, ref_maps, gimpl_printer)) { + ::butil::string_printf( + error, "Fail to generate parsing code for %s", + butil::EnsureString(d->full_name()).c_str()); + return false; + } + if (!generate_serializing(d, ref_msgs, ref_maps, gimpl_printer)) { + ::butil::string_printf( + error, "Fail to generate serializing code for %s", + butil::EnsureString(d->full_name()).c_str()); + return false; + } + std::string var_name = mcpack2pb::to_var_name(butil::EnsureString(d->full_name())); + gdecl_printer.Print( + "::mcpack2pb::FieldMap* g_$vmsg$_fields = NULL;\n" + , "vmsg", var_name); + } + if (!generate_declarations(ref_msgs, ref_maps, gdecl_printer)) { + ::butil::string_printf( + error, "Fail to generate declarations for %s", + cpp_name.c_str()); + return false; + } + if (!generate_registration(file, gimpl_printer)) { + ::butil::string_printf( + error, "Fail to generate registration code for %s", + cpp_name.c_str()); + return false; + } + return true; +} +} // namespace mcpack2pb + +int main(int argc, char* argv[]) { + ::mcpack2pb::McpackToProtobuf generator; + return google::protobuf::compiler::PluginMain(argc, argv, &generator); +} diff --git a/src/mcpack2pb/mcpack2pb.cpp b/src/mcpack2pb/mcpack2pb.cpp new file mode 100644 index 0000000..7ab24e3 --- /dev/null +++ b/src/mcpack2pb/mcpack2pb.cpp @@ -0,0 +1,59 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// mcpack2pb - Make protobuf be front-end of mcpack/compack + +// Date: Mon Oct 19 17:17:36 CST 2015 + +#include +#include "mcpack2pb/mcpack2pb.h" + +DEFINE_bool(mcpack2pb_absent_field_is_error, false, "Parsing fails if the " + "field in compack/mcpack does not exist in protobuf"); + +namespace mcpack2pb { + +static pthread_once_t s_init_handler_map_once = PTHREAD_ONCE_INIT; +static butil::FlatMap* s_handler_map = NULL; +static void init_handler_map() { + s_handler_map = new butil::FlatMap; + if (s_handler_map->init(64, 50) != 0) { + LOG(WARNING) << "Fail to init s_handler_map"; + } +} +void register_message_handler_or_die(const std::string& full_name, + const MessageHandler& handler) { + pthread_once(&s_init_handler_map_once, init_handler_map); + if (s_handler_map->seek(full_name) != NULL) { + LOG(ERROR) << full_name << " was registered before!"; + exit(1); + } else { + (*s_handler_map)[full_name] = handler; + } +} + +MessageHandler find_message_handler(const std::string& full_name) { + pthread_once(&s_init_handler_map_once, init_handler_map); + MessageHandler* handler = s_handler_map->seek(full_name); + if (handler != NULL) { + return *handler; + } + MessageHandler null_handler = { NULL, NULL, NULL, NULL }; + return null_handler; +} + +} // namespace mcpack2pb diff --git a/src/mcpack2pb/mcpack2pb.h b/src/mcpack2pb/mcpack2pb.h new file mode 100644 index 0000000..4dc22a6 --- /dev/null +++ b/src/mcpack2pb/mcpack2pb.h @@ -0,0 +1,165 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// mcpack2pb - Make protobuf be front-end of mcpack/compack + +// Date: Mon Oct 19 17:17:36 CST 2015 + +#ifndef MCPACK2PB_MCPACK_MCPACK2PB_H +#define MCPACK2PB_MCPACK_MCPACK2PB_H + +#include +#include +#include "butil/containers/flat_map.h" +#include "butil/iobuf.h" +#include "mcpack2pb/parser.h" +#include "mcpack2pb/serializer.h" + +namespace mcpack2pb { + +typedef bool (*SetFieldFn)(::google::protobuf::Message* msg, + UnparsedValue& value); + +// Mapping from filed name to its parsing&setting function. +typedef butil::FlatMap FieldMap; + +enum SerializationFormat { + FORMAT_COMPACK, + FORMAT_MCPACK_V2 +}; + +struct MessageHandler { + // Parse `msg' from `input' as a mcpack_v2 or compack object. + // Returns number of bytes consumed. + size_t (*parse)(::google::protobuf::Message* msg, + ::google::protobuf::io::ZeroCopyInputStream* input); + + // Parse `msg' from `input' as a mcpack_v2 or compack object removed with header. + // Returns true on success. + bool (*parse_body)(::google::protobuf::Message* msg, + ::google::protobuf::io::ZeroCopyInputStream* input, + size_t size); + + // Serialize `msg' as a mcpack_v2 or compack object into `output'. + // The serialization format is decided by `format'. + // Returns true on success. + bool (*serialize)(const ::google::protobuf::Message& msg, + ::google::protobuf::io::ZeroCopyOutputStream* output, + SerializationFormat format); + + // Serialize `msg' as a mcpack_v2 or compack object without header into + // `serializer'. The serialization format is decided by `format'. + // Returns true on success. + void (*serialize_body)(const ::google::protobuf::Message& msg, + Serializer& serializer, + SerializationFormat format); + + // ------------------- + // Helper functions + // ------------------- + + // Parse `msg' from IOBuf or array which may contain more data than just + // the message. + // Returns bytes parsed, 0 on error. + size_t parse_from_iobuf_prefix(::google::protobuf::Message* msg, + const ::butil::IOBuf& buf); + size_t parse_from_array_prefix(::google::protobuf::Message* msg, + const void* data, int size); + // Parse `msg' from IOBuf or array which may just contain the message. + // Returns true on success. + bool parse_from_iobuf(::google::protobuf::Message* msg, + const ::butil::IOBuf& buf); + bool parse_from_array(::google::protobuf::Message* msg, + const void* data, int size); + // Serialize `msg' to IOBuf or string. + // Returns true on success. + bool serialize_to_iobuf(const ::google::protobuf::Message& msg, + ::butil::IOBuf* buf, SerializationFormat format); + + // TODO(gejun): serialize_to_string is not supported because OutputStream + // requires the embedded zero-copy stream to return permanent memory blocks + // to support reserve() however the string inside StringOutputStream may + // be resized and invalidates previous returned memory blocks. +}; + +static const MessageHandler INVALID_MESSAGE_HANDLER = {NULL, NULL, NULL, NULL}; + +// if the *.pb.cc and *.pb.h was generated by mcpack2pb. This function will be +// called with the mcpack/compack parser and serializer BEFORE main(). +void register_message_handler_or_die(const std::string& full_name, + const MessageHandler& handler); + +// Find the registered parser/serializer by `full_name' +// e.g. "example.SampleMessage". +// If the handler was not registered, function pointers inside are NULL. +MessageHandler find_message_handler(const std::string& full_name); + +// inline impl. +inline size_t MessageHandler::parse_from_iobuf_prefix( + ::google::protobuf::Message* msg, const ::butil::IOBuf& buf) { + if (parse == NULL) { + LOG(ERROR) << "`parse' is NULL"; + return 0; + } + ::butil::IOBufAsZeroCopyInputStream zc_stream(buf); + return parse(msg, &zc_stream); +} + +inline bool MessageHandler::parse_from_iobuf( + ::google::protobuf::Message* msg, const ::butil::IOBuf& buf) { + if (parse == NULL) { + LOG(ERROR) << "`parse' is NULL"; + return 0; + } + ::butil::IOBufAsZeroCopyInputStream zc_stream(buf); + return parse(msg, &zc_stream) == buf.size(); +} + +inline size_t MessageHandler::parse_from_array_prefix( + ::google::protobuf::Message* msg, const void* data, int size) { + if (parse == NULL) { + LOG(ERROR) << "`parse' is NULL"; + return 0; + } + ::google::protobuf::io::ArrayInputStream zc_stream(data, size); + return parse(msg, &zc_stream); +} + +inline bool MessageHandler::parse_from_array( + ::google::protobuf::Message* msg, const void* data, int size) { + if (parse == NULL) { + LOG(ERROR) << "`parse' is NULL"; + return 0; + } + ::google::protobuf::io::ArrayInputStream zc_stream(data, size); + return (int)parse(msg, &zc_stream) == size; +} + +inline bool MessageHandler::serialize_to_iobuf( + const ::google::protobuf::Message& msg, + ::butil::IOBuf* buf, SerializationFormat format) { + if (serialize == NULL) { + LOG(ERROR) << "`serialize' is NULL"; + return false; + } + ::butil::IOBufAsZeroCopyOutputStream zc_stream(buf); + return serialize(msg, &zc_stream, format); +} + +} // namespace mcpack2pb + +#endif // MCPACK2PB_MCPACK_MCPACK2PB_H diff --git a/src/mcpack2pb/parser-inl.h b/src/mcpack2pb/parser-inl.h new file mode 100644 index 0000000..76d03fe --- /dev/null +++ b/src/mcpack2pb/parser-inl.h @@ -0,0 +1,267 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// mcpack2pb - Make protobuf be front-end of mcpack/compack + +// Date: Mon Oct 19 17:17:36 CST 2015 + +#ifndef MCPACK2PB_MCPACK_PARSER_INL_H +#define MCPACK2PB_MCPACK_PARSER_INL_H + +namespace mcpack2pb { + +// Binary head before items of array/object except isomorphic array. +struct ItemsHead { + uint32_t item_count; +} __attribute__((__packed__)); + +inline size_t InputStream::popn(size_t n) { + const size_t saved_n = n; + do { + if (_size >= (int64_t)n) { + _data = (const char*)_data + n; + _size -= n; + _popped_bytes += saved_n; + return saved_n; + } + n -= _size; + } while (_zc_stream->Next(&_data, &_size)); + _data = NULL; + _size = 0; + _popped_bytes += saved_n - n; + return saved_n - n; +} + +inline size_t InputStream::cutn(void* out, size_t n) { + const size_t saved_n = n; + do { + if (_size >= (int64_t)n) { + memcpy(out, _data, n); + _data = (const char*)_data + n; + _size -= n; + _popped_bytes += saved_n; + return saved_n; + } + if (_size) { + memcpy(out, _data, _size); + out = (char*)out + _size; + n -= _size; + } + } while (_zc_stream->Next(&_data, &_size)); + _data = NULL; + _size = 0; + _popped_bytes += saved_n - n; + return saved_n - n; +} + +template +inline size_t InputStream::cut_packed_pod(T* packed_pod) { + if (_size >= (int)sizeof(T)) { + *packed_pod = *(T*)_data; + _data = (const char*)_data + sizeof(T); + _size -= sizeof(T); + _popped_bytes += sizeof(T); + return sizeof(T); + } + return cutn(packed_pod, sizeof(T)); +} + +template +inline T InputStream::cut_packed_pod() { + T packed_pod; + if (_size >= (int)sizeof(T)) { + packed_pod = *(T*)_data; + _data = (const char*)_data + sizeof(T); + _size -= sizeof(T); + _popped_bytes += sizeof(T); + return packed_pod; + } + cutn(&packed_pod, sizeof(T)); + return packed_pod; +} + +inline butil::StringPiece InputStream::ref_cut(std::string* aux, size_t n) { + if (_size >= (int64_t)n) { + butil::StringPiece ret((const char*)_data, n); + _data = (const char*)_data + n; + _size -= n; + _popped_bytes += n; + return ret; + } + aux->resize(n); + size_t m = cutn(&(*aux)[0], n); + if (m != n) { + aux->resize(m); + } + return *aux; +} + +inline uint8_t InputStream::peek1() { + if (_size > 0) { + return *(const uint8_t*)_data; + } + while (_zc_stream->Next(&_data, &_size)) { + if (_size > 0) { + return *(const uint8_t*)_data; + } + } + return 0; +} + +// Binary head before items of isomorphic array. +struct IsoItemsHead { + uint8_t type; +} __attribute__((__packed__)); + +inline ObjectIterator UnparsedValue::as_object() { + return ObjectIterator(_stream, _size); +} + +inline ArrayIterator UnparsedValue::as_array() { + return ArrayIterator(_stream, _size); +} + +inline ISOArrayIterator UnparsedValue::as_iso_array() { + return ISOArrayIterator(_stream, _size); +} + +inline void ObjectIterator::init(InputStream* stream, size_t size) { + _field_count = 0; + _stream = stream; + _expected_popped_bytes = _stream->popped_bytes() + sizeof(ItemsHead); + _expected_popped_end = _stream->popped_bytes() + size; + ItemsHead items_head; + if (_stream->cut_packed_pod(&items_head) != sizeof(ItemsHead)) { + CHECK(false) << "buffer(size=" << size << ") is not enough"; + return set_bad(); + } + _field_count = items_head.item_count; + operator++(); +} + +inline void ArrayIterator::init(InputStream* stream, size_t size) { + _item_count = 0; + _stream = stream; + _expected_popped_bytes = _stream->popped_bytes() + sizeof(ItemsHead); + _expected_popped_end = _stream->popped_bytes() + size; + ItemsHead items_head; + if (_stream->cut_packed_pod(&items_head) != sizeof(ItemsHead)) { + CHECK(false) << "buffer(size=" << size << ") is not enough"; + return set_bad(); + } + _item_count = items_head.item_count; + operator++(); +} + +inline void ISOArrayIterator::init(InputStream* stream, size_t size) { + _buf_index = 0; + _buf_count = 0; + _stream = stream; + _item_type = (PrimitiveFieldType)0; + _item_size = 0; + _item_count = 0; + _left_item_count = 0; + IsoItemsHead items_head; + if (_stream->cut_packed_pod(&items_head) != sizeof(IsoItemsHead)) { + CHECK(false) << "Not enough data"; + return set_bad(); + } + _item_type = (PrimitiveFieldType)items_head.type; + _item_size = get_primitive_type_size(_item_type); + if (!_item_size) { + CHECK(false) << "type=" << type2str(_item_type) + << " in primitive isoarray is not primitive"; + return set_bad(); + } + const size_t items_full_size = size - sizeof(IsoItemsHead); + _item_count = items_full_size / _item_size; + if (_item_count * _item_size != items_full_size) { + CHECK(false) << "inconsistent item_count(" << _item_count + << ") and value_size(" << items_full_size + << "), item_size=" << _item_size; + return set_bad(); + } + _left_item_count = _item_count; + operator++(); +} + +inline void ISOArrayIterator::operator++() { + if (_buf_index + 1 < _buf_count) { + ++_buf_index; + return; + } + // Iterate all items in isomorphic array. We have to do this + // right here because the items are lacking of headings. + // Call on_primitives in batch to reduce overhead. + if (_left_item_count == 0) { + set_end(); + return; + } + _buf_count = std::min((uint32_t)sizeof(_item_buf) / _item_size, _left_item_count); + _buf_index = 0; + if (_stream->cutn(_item_buf, _buf_count * _item_size) != + _buf_count * _item_size) { + CHECK(false) << "Not enough data"; + return set_bad(); + } + _left_item_count -= _buf_count; +} + +template +inline T ISOArrayIterator::as_integer() const { + const void* ptr = (_item_buf + _buf_index * _item_size); + switch ((PrimitiveFieldType)_item_type) { + case PRIMITIVE_FIELD_INT8: + return *static_cast(ptr); + case PRIMITIVE_FIELD_INT16: + return *static_cast(ptr); + case PRIMITIVE_FIELD_INT32: + return *static_cast(ptr); + case PRIMITIVE_FIELD_INT64: + return *static_cast(ptr); + case PRIMITIVE_FIELD_UINT8: + return *static_cast(ptr); + case PRIMITIVE_FIELD_UINT16: + return *static_cast(ptr); + case PRIMITIVE_FIELD_UINT32: + return *static_cast(ptr); + case PRIMITIVE_FIELD_UINT64: + return *static_cast(ptr); + case PRIMITIVE_FIELD_BOOL: + return *static_cast(ptr); + case PRIMITIVE_FIELD_FLOAT: + return 0; + case PRIMITIVE_FIELD_DOUBLE: + return 0; + } + return 0; +} + +template +inline T ISOArrayIterator::as_fp() const { + const void* ptr = (_item_buf + _buf_index * _item_size); + if (_item_type == PRIMITIVE_FIELD_FLOAT) { + return *static_cast(ptr); + } else if (_item_type == PRIMITIVE_FIELD_DOUBLE) { + return *static_cast(ptr); + } + return T(); +} + +} // namespace mcpack2pb + +#endif // MCPACK2PB_MCPACK_PARSER_INL_H diff --git a/src/mcpack2pb/parser.cpp b/src/mcpack2pb/parser.cpp new file mode 100644 index 0000000..5c785dc --- /dev/null +++ b/src/mcpack2pb/parser.cpp @@ -0,0 +1,608 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// mcpack2pb - Make protobuf be front-end of mcpack/compack + +// Date: Mon Oct 19 17:17:36 CST 2015 + +#include "mcpack2pb/parser.h" + +namespace mcpack2pb { + +// Binary head before fixed-size types. +class FieldFixedHead { +public: + void set_type(uint8_t type) { _type = type; } + uint8_t type() const { return _type; } + size_t name_offset() const { return sizeof(FieldFixedHead); } + size_t name_size() const { return _name_size; } + void set_name_size(size_t name_size) { _name_size = name_size; } + size_t value_offset() const { return name_offset() + name_size(); } + size_t value_size() const { return (_type & FIELD_FIXED_MASK); } + size_t full_size() const { return value_offset() + value_size(); } +private: + uint8_t _type; // FieldType + uint8_t _name_size; +} __attribute__((__packed__)); + +// Binary head before string<=254 or raw<=255 +class FieldShortHead { +public: + void set_type(uint8_t type) { _type = type; } + uint8_t type() const { return _type; } + size_t name_offset() const { return sizeof(FieldShortHead); } + size_t name_size() const { return _name_size; } + void set_name_size(size_t name_size) { _name_size = name_size; } + size_t value_offset() const { return name_offset() + name_size(); } + size_t value_size() const { return _value_size; } + void set_value_size(size_t value_size) { _value_size = value_size; } + size_t full_size() const { return value_offset() + value_size(); } +private: + uint8_t _type; + uint8_t _name_size; + uint8_t _value_size; +} __attribute__((__packed__)); + +// Binary head before variable-size fields. +class FieldLongHead { +public: + void set_type(uint8_t type) { _type = type; } + uint8_t type() const { return _type; } + size_t name_offset() const { return sizeof(FieldLongHead); } + size_t name_size() const { return _name_size; } + void set_name_size(size_t name_size) { _name_size = name_size; } + size_t value_offset() const { return name_offset() + name_size(); } + size_t value_size() const { return _value_size; } + void set_value_size(size_t value_size) { _value_size = value_size; } + size_t full_size() const { return value_offset() + value_size(); } +private: + uint8_t _type; + uint8_t _name_size; + uint32_t _value_size; +} __attribute__((__packed__)); + +// Assert type sizes: +BAIDU_CASSERT(sizeof(FieldFixedHead) == 2, size_assert); +BAIDU_CASSERT(sizeof(FieldShortHead) == 3, size_assert); +BAIDU_CASSERT(sizeof(FieldLongHead) == 6, size_assert); +BAIDU_CASSERT(sizeof(ItemsHead) == 4, size_assert); +BAIDU_CASSERT(sizeof(IsoItemsHead) == 1, size_assert); + +void ObjectIterator::operator++() { + if (_stream->popped_bytes() != _expected_popped_bytes) { + if (_stream->popped_bytes() + _current_field.value.size() == + _expected_popped_bytes) { + // skipping untouched values is acceptible. + _stream->popn(_current_field.value.size()); + } else if (_stream->popped_bytes() < _expected_popped_bytes) { + CHECK(false) << "value of name=" << _current_field.name + << " is not fully consumed, expected=" + << _expected_popped_bytes << " actually=" + << _stream->popped_bytes(); + return set_bad(); + } else { + CHECK(false) << "Over popped in value of name=" << _current_field.name + << " expected=" << _expected_popped_bytes << " actually=" + << _stream->popped_bytes(); + return set_bad(); + } + } + if (_expected_popped_bytes >= _expected_popped_end) { + return set_end(); + } + const uint8_t first_byte = _stream->peek1(); + if (first_byte & FIELD_FIXED_MASK) { + FieldFixedHead head; + if (_stream->cut_packed_pod(&head) != sizeof(FieldFixedHead) || + left_size() < head.full_size()) { + CHECK(false) << "buffer(size=" << left_size() << ") is not enough"; + return set_bad(); + } + _expected_popped_bytes = _stream->popped_bytes() + head.full_size() + - sizeof(FieldFixedHead); + if (!(head.type() & FIELD_NON_DELETED_MASK)) { + _stream->popn(head.full_size() - sizeof(FieldFixedHead)); + return operator++(); // tailr + } + _current_field.name = _stream->ref_cut(&_name_backup_string, head.name_size()); + if (!_current_field.name.empty()) { + _current_field.name.remove_suffix(1); + } + _current_field.value.set((FieldType)head.type(), _stream, head.value_size()); + } else if (first_byte & FIELD_SHORT_MASK) { + FieldShortHead head; + if (_stream->cut_packed_pod(&head) != sizeof(FieldShortHead) || + left_size() < head.full_size()) { + CHECK(false) << "buffer(size=" << left_size() << ") is not enough"; + return set_bad(); + } + _expected_popped_bytes = _stream->popped_bytes() + head.full_size() + - sizeof(FieldShortHead); + if (!(head.type() & FIELD_NON_DELETED_MASK)) { + // Skip deleted field. + _stream->popn(head.full_size() - sizeof(FieldShortHead)); + return operator++(); // tailr + } + // Remove FIELD_SHORT_MASK. + FieldType type = (FieldType)(head.type() & ~FIELD_SHORT_MASK); + _current_field.name = _stream->ref_cut(&_name_backup_string, head.name_size()); + if (!_current_field.name.empty()) { + _current_field.name.remove_suffix(1); + } + _current_field.value.set(type, _stream, head.value_size()); + } else { + FieldLongHead head; + if (_stream->cut_packed_pod(&head) != sizeof(FieldLongHead) || + left_size() < head.full_size()) { + CHECK(false) << "buffer(size=" << left_size() << ") is not enough"; + return set_bad(); + } + _expected_popped_bytes = _stream->popped_bytes() + head.full_size() + - sizeof(FieldLongHead); + if (!(head.type() & FIELD_NON_DELETED_MASK)) { + // Skip deleted field. + _stream->popn(head.full_size() - sizeof(FieldLongHead)); + return operator++(); // tailr + } + _current_field.name = _stream->ref_cut(&_name_backup_string, head.name_size()); + if (!_current_field.name.empty()) { + _current_field.name.remove_suffix(1); + } + _current_field.value.set((FieldType)head.type(), _stream, head.value_size()); + } +} + +void ArrayIterator::operator++() { + if (_stream->popped_bytes() != _expected_popped_bytes) { + if (_stream->popped_bytes() + _current_field.size() == + _expected_popped_bytes) { + // skipping untouched values is acceptible. + _stream->popn(_current_field.size()); + } else if (_stream->popped_bytes() < _expected_popped_bytes) { + CHECK(false) << "previous value is not fully consumed, expected=" + << _expected_popped_bytes << " actually=" + << _stream->popped_bytes(); + return set_bad(); + } else { + CHECK(false) << "Over popped in previous value, expected=" + << _expected_popped_bytes << " actually=" + << _stream->popped_bytes(); + return set_bad(); + } + } + if (_expected_popped_bytes >= _expected_popped_end) { + return set_end(); + } + const uint8_t first_byte = _stream->peek1(); + if (first_byte & FIELD_FIXED_MASK) { + FieldFixedHead head; + if (_stream->cut_packed_pod(&head) != sizeof(FieldFixedHead) || + left_size() < head.full_size()) { + CHECK(false) << "buffer(size=" << left_size() << ") is not enough"; + return set_bad(); + } + _expected_popped_bytes = _stream->popped_bytes() + head.full_size() + - sizeof(FieldFixedHead); + if (!(head.type() & FIELD_NON_DELETED_MASK)) { + // Skip deleted fields. + _stream->popn(head.full_size() - sizeof(FieldFixedHead)); + return operator++(); // tailr + } + const uint32_t name_size = head.name_size(); + if (name_size) { + _stream->popn(name_size); + } + _current_field.set((FieldType)head.type(), _stream, head.value_size()); + } else if (first_byte & FIELD_SHORT_MASK) { + FieldShortHead head; + if (_stream->cut_packed_pod(&head) != sizeof(FieldShortHead) || + left_size() < head.full_size()) { + CHECK(false) << "buffer(size=" << left_size() << ") is not enough"; + return set_bad(); + } + _expected_popped_bytes = _stream->popped_bytes() + head.full_size() + - sizeof(FieldShortHead); + if (!(head.type() & FIELD_NON_DELETED_MASK)) { + // Skip deleted field. + _stream->popn(head.full_size() - sizeof(FieldShortHead)); + return operator++(); // tailr + } + // Remove FIELD_SHORT_MASK. + const FieldType type = (FieldType)(head.type() & ~FIELD_SHORT_MASK); + const uint32_t name_size = head.name_size(); + if (name_size) { + _stream->popn(name_size); + } + _current_field.set(type, _stream, head.value_size()); + } else { + FieldLongHead head; + if (_stream->cut_packed_pod(&head) != sizeof(FieldLongHead) || + left_size() < head.full_size()) { + CHECK(false) << "buffer(size=" << left_size() << ") is not enough"; + return set_bad(); + } + _expected_popped_bytes = _stream->popped_bytes() + head.full_size() + - sizeof(FieldLongHead); + if (!(head.type() & FIELD_NON_DELETED_MASK)) { + // Skip deleted field. + _stream->popn(head.full_size() - sizeof(FieldLongHead)); + return operator++(); // tailr + } + const uint32_t name_size = head.name_size(); + if (name_size) { + _stream->popn(name_size); + } + _current_field.set((FieldType)head.type(), _stream, head.value_size()); + } +} + +size_t unbox(InputStream* stream) { + FieldLongHead head; + if (stream->cut_packed_pod(&head) != sizeof(FieldLongHead)) { + CHECK(false) << "Input buffer is not enough"; + return 0; + } + if (head.type() != FIELD_OBJECT) { + CHECK(false) << "type=" << type2str(head.type()) << " is not object"; + return 0; + } + if (!(head.type() & FIELD_NON_DELETED_MASK)) { + CHECK(false) << "The item is deleted"; + return 0; + } + if (head.name_size() != 0) { + CHECK(false) << "The object should not have name"; + return 0; + } + return head.value_size(); +} + +std::ostream& operator<<(std::ostream& os, const UnparsedValue& value) { + // TODO(gejun): More info. + return os << type2str(value.type()); +} + +// NOTE: following functions are too big to be inlined. Actually non-inlining +// them perform better in tests. +int64_t UnparsedValue::as_int64(const char* var) { + switch ((PrimitiveFieldType)_type) { + case PRIMITIVE_FIELD_INT8: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_INT16: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_INT32: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_INT64: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_UINT8: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_UINT16: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_UINT32: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_UINT64: { + const uint64_t value = _stream->cut_packed_pod(); + if (value <= (uint64_t)std::numeric_limits::max()) { + return (int64_t)value; + } + CHECK(false) << "uint64=" << value << " to " << var << " overflows"; + _stream->set_bad(); + return std::numeric_limits::max(); + } + case PRIMITIVE_FIELD_BOOL: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_FLOAT: + CHECK(false) << "Can't set float=" << _stream->cut_packed_pod() + << " to " << var; + _stream->set_bad(); + return 0; + case PRIMITIVE_FIELD_DOUBLE: + CHECK(false) << "Can't set double=" << _stream->cut_packed_pod() + << " to " << var; + _stream->set_bad(); + return 0; + } + CHECK(false) << "Can't set type=" << type2str(_type) << " to " << var; + _stream->set_bad(); + return 0; +} + +uint64_t UnparsedValue::as_uint64(const char* var) { + switch ((PrimitiveFieldType)_type) { + case PRIMITIVE_FIELD_INT8: { + const int8_t value = _stream->cut_packed_pod(); + if (value >= 0) { + return (uint64_t)value; + } + CHECK(false) << "Can't set int8=" << value << " to " << var; + _stream->set_bad(); + return 0; + } + case PRIMITIVE_FIELD_INT16: { + const int16_t value = _stream->cut_packed_pod(); + if (value >= 0) { + return (uint64_t)value; + } + CHECK(false) << "Can't set int16=" << value << " to " << var; + _stream->set_bad(); + return 0; + } + case PRIMITIVE_FIELD_INT32: { + const int32_t value = _stream->cut_packed_pod(); + if (value >= 0) { + return (uint64_t)value; + } + CHECK(false) << "Can't set int32=" << value << " to " << var; + _stream->set_bad(); + return 0; + } + case PRIMITIVE_FIELD_INT64: { + const int64_t value = _stream->cut_packed_pod(); + if (value >= 0) { + return (uint64_t)value; + } + CHECK(false) << "Can't set int64=" << value << " to " << var; + _stream->set_bad(); + return 0; + } + case PRIMITIVE_FIELD_UINT8: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_UINT16: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_UINT32: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_UINT64: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_BOOL: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_FLOAT: + CHECK(false) << "Can't set float=" << _stream->cut_packed_pod() + << " to " << var; + _stream->set_bad(); + return 0; + case PRIMITIVE_FIELD_DOUBLE: + CHECK(false) << "Can't set double=" << _stream->cut_packed_pod() + << " to " << var; + _stream->set_bad(); + return 0; + } + CHECK(false) << "Can't set type=" << type2str(_type) << " to " << var; + _stream->set_bad(); + return 0; +} + +int32_t UnparsedValue::as_int32(const char* var) { + switch ((PrimitiveFieldType)_type) { + case PRIMITIVE_FIELD_INT8: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_INT16: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_INT32: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_INT64: { + const int64_t value = _stream->cut_packed_pod(); + if (value > std::numeric_limits::max()) { + CHECK(false) << "int64=" << value << " to " << var << " overflows"; + _stream->set_bad(); + return std::numeric_limits::max(); + } else if (value < std::numeric_limits::min()) { + CHECK(false) << "int64=" << value << " to " << var << " underflows"; + _stream->set_bad(); + return std::numeric_limits::min(); + } + return (int32_t)value; + } + case PRIMITIVE_FIELD_UINT8: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_UINT16: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_UINT32: { + const uint32_t value = _stream->cut_packed_pod(); + if (value <= (uint32_t)std::numeric_limits::max()) { + return (int32_t)value; + } + CHECK(false) << "uint32=" << value << " to " << var << " overflows"; + _stream->set_bad(); + return std::numeric_limits::max(); + } + case PRIMITIVE_FIELD_UINT64: { + const uint64_t value = _stream->cut_packed_pod(); + if (value <= (uint64_t)std::numeric_limits::max()) { + return (int32_t)value; + } + CHECK(false) << "uint64=" << value << " to " << var << " overflows"; + _stream->set_bad(); + return std::numeric_limits::max(); + } + case PRIMITIVE_FIELD_BOOL: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_FLOAT: + CHECK(false) << "Can't set float=" << _stream->cut_packed_pod() + << " to " << var; + _stream->set_bad(); + return 0; + case PRIMITIVE_FIELD_DOUBLE: + CHECK(false) << "Can't set double=" << _stream->cut_packed_pod() + << " to " << var; + _stream->set_bad(); + return 0; + } + CHECK(false) << "Can't set type=" << type2str(_type) << " to " << var; + _stream->set_bad(); + return 0; +} + +uint32_t UnparsedValue::as_uint32(const char* var) { + switch ((PrimitiveFieldType)_type) { + case PRIMITIVE_FIELD_INT8: { + const int8_t value = _stream->cut_packed_pod(); + if (value >= 0) { + return (uint32_t)value; + } + CHECK(false) << "Can't set int8=" << value << " to " << var; + _stream->set_bad(); + return 0; + } + case PRIMITIVE_FIELD_INT16: { + const int16_t value = _stream->cut_packed_pod(); + if (value >= 0) { + return (uint32_t)value; + } + CHECK(false) << "Can't set int16=" << value << " to " << var; + _stream->set_bad(); + return 0; + } + case PRIMITIVE_FIELD_INT32: { + const int32_t value = _stream->cut_packed_pod(); + if (value >= 0) { + return (uint32_t)value; + } + CHECK(false) << "Can't set int32=" << value << " to " << var; + _stream->set_bad(); + return 0; + } + case PRIMITIVE_FIELD_INT64: { + const int64_t value = _stream->cut_packed_pod(); + if (value >= 0 && + value <= (int64_t)std::numeric_limits::max()) { + return (uint32_t)value; + } + CHECK(false) << "Can't set int64=" << value << " to " << var; + _stream->set_bad(); + return 0; + } + case PRIMITIVE_FIELD_UINT8: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_UINT16: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_UINT32: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_UINT64: { + const uint64_t value = _stream->cut_packed_pod(); + if (value <= std::numeric_limits::max()) { + return (uint32_t)value; + } + CHECK(false) << "uint64=" << value << " to " << var << " overflows"; + _stream->set_bad(); + return std::numeric_limits::max(); + } + case PRIMITIVE_FIELD_BOOL: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_FLOAT: + CHECK(false) << "Can't set float=" << _stream->cut_packed_pod() + << " to " << var; + _stream->set_bad(); + return 0; + case PRIMITIVE_FIELD_DOUBLE: + CHECK(false) << "Can't set double=" << _stream->cut_packed_pod() + << " to " << var; + _stream->set_bad(); + return 0; + } + CHECK(false) << "Can't set type=" << type2str(_type) << " to " << var; + _stream->set_bad(); + return 0; +} + +bool UnparsedValue::as_bool(const char* var) { + switch ((PrimitiveFieldType)_type) { + case PRIMITIVE_FIELD_INT8: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_INT16: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_INT32: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_INT64: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_UINT8: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_UINT16: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_UINT32: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_UINT64: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_BOOL: + return _stream->cut_packed_pod(); + case PRIMITIVE_FIELD_FLOAT: + CHECK(false) << "Can't set float=" << _stream->cut_packed_pod() + << " to " << var; + _stream->set_bad(); + return false; + case PRIMITIVE_FIELD_DOUBLE: + CHECK(false) << "Can't set double=" << _stream->cut_packed_pod() + << " to " << var; + _stream->set_bad(); + return false; + } + CHECK(false) << "Can't set type=" << type2str(_type) << " to " << var; + _stream->set_bad(); + return false; +} + +float UnparsedValue::as_float(const char* var) { + if (_type == FIELD_DOUBLE) { + return (float)_stream->cut_packed_pod(); + } else if (_type == FIELD_FLOAT) { + return _stream->cut_packed_pod(); + } + CHECK(false) << "Can't set type=" << type2str(_type) << " to " << var; + _stream->set_bad(); + return 0; +} + +double UnparsedValue::as_double(const char* var) { + if (_type == FIELD_DOUBLE) { + return _stream->cut_packed_pod(); + } else if (_type == FIELD_FLOAT) { + return _stream->cut_packed_pod(); + } + CHECK(false) << "Can't set type=" << type2str(_type) << " to " << var; + _stream->set_bad(); + return 0; +} + +void UnparsedValue::as_string(std::string* out, const char* var) { + out->resize(_size - 1); + if (_stream->cutn(&(*out)[0], _size - 1) != _size - 1) { + CHECK(false) << "Not enough data for " << var; + return; + } + _stream->popn(1); +} + +std::string UnparsedValue::as_string(const char* var) { + std::string result; + as_string(&result, var); + return result; +} + +void UnparsedValue::as_binary(std::string* out, const char* var) { + out->resize(_size); + if (_stream->cutn(&(*out)[0], _size) != _size) { + CHECK(false) << "Not enough data for " << var; + return; + } +} + +std::string UnparsedValue::as_binary(const char* var) { + std::string result; + as_binary(&result, var); + return result; +} + +} // namespace mcpack2pb diff --git a/src/mcpack2pb/parser.h b/src/mcpack2pb/parser.h new file mode 100644 index 0000000..d897c5e --- /dev/null +++ b/src/mcpack2pb/parser.h @@ -0,0 +1,273 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// mcpack2pb - Make protobuf be front-end of mcpack/compack + +// Date: Mon Oct 19 17:17:36 CST 2015 + +#ifndef MCPACK2PB_MCPACK_PARSER_H +#define MCPACK2PB_MCPACK_PARSER_H + +#include // std::numeric_limits +#include +#include "butil/logging.h" +#include "butil/strings/string_piece.h" +#include "mcpack2pb/field_type.h" + +// CAUTION: Methods in this header is not intended to be public to users of +// brpc, and subject to change at any future time. + +namespace mcpack2pb { + +// Read bytes from ZeroCopyInputStream +class InputStream { +public: + InputStream(google::protobuf::io::ZeroCopyInputStream* stream) + : _good(true) + , _size(0) + , _data(NULL) + , _zc_stream(stream) + , _popped_bytes(0) + {} + + ~InputStream() { } + + // Pop at-most n bytes from front side. + // Returns bytes popped. + size_t popn(size_t n); + + // Cut off at-most n bytes from front side and copy to `out'. + // Returns bytes cut. + size_t cutn(void* out, size_t n); + + template size_t cut_packed_pod(T* packed_pod); + template T cut_packed_pod(); + + // Cut off at-most n bytes from front side. If the data is stored in + // continuous memory, return the reference directly, otherwise copy + // the data into `aux' and return reference of `aux'. + // Returns a StringPiece referencing the cut-off data. + butil::StringPiece ref_cut(std::string* aux, size_t n); + + // Peek at the first character. If the stream is empty, 0 is returned. + uint8_t peek1(); + + // Returns bytes popped and cut since creation of this stream. + size_t popped_bytes() const { return _popped_bytes; } + + // Returns false if error occurred in other consuming functions. + bool good() const { return _good; } + + // If the error prevents parsing from going on, call this method. + // This method is also called in other functions in this class. + void set_bad() { _good = false; } + +private: + bool _good; + int _size; + const void* _data; + google::protobuf::io::ZeroCopyInputStream* _zc_stream; + size_t _popped_bytes; +}; + +class ObjectIterator; +class ArrayIterator; +class ISOArrayIterator; + +// Represent a piece of unparsed(and unread) data of InputStream. +struct UnparsedValue { + UnparsedValue() + : _type(FIELD_UNKNOWN), _stream(NULL), _size(0) {} + UnparsedValue(FieldType type, InputStream* stream, size_t size) + : _type(type), _stream(stream), _size(size) {} + void set(FieldType type, InputStream* stream, size_t size) { + _type = type; + _stream = stream; + _size = size; + } + + FieldType type() const { return _type; } + InputStream* stream() { return _stream; } + const InputStream* stream() const { return _stream; } + size_t size() const { return _size; } + + // Convert to concrete value. These functions can only be called once! + ObjectIterator as_object(); + ArrayIterator as_array(); + ISOArrayIterator as_iso_array(); + // `var' in following methods should be a description of the field being + // parsed, for better error reporting. It has nothing to do with the result. + int64_t as_int64(const char* var); + uint64_t as_uint64(const char* var); + int32_t as_int32(const char* var); + uint32_t as_uint32(const char* var); + bool as_bool(const char* var); + float as_float(const char* var); + double as_double(const char* var); + void as_string(std::string* out, const char* var); + std::string as_string(const char* var); + void as_binary(std::string* out, const char* var); + std::string as_binary(const char* var); + +private: +friend class ObjectIterator; +friend class ArrayIterator; + void set_end() { _type = FIELD_UNKNOWN; } + + FieldType _type; + InputStream* _stream; + size_t _size; +}; + +std::ostream& operator<<(std::ostream& os, const UnparsedValue& value); + +// Remove the header of the wrapping object. +// Returns the value size. +size_t unbox(InputStream* stream); + +// Iterator all fields in an object which should be created like this: +// ObjectIterator it(unparsed_value); +// for (; it != NULL; ++it) { +// std::cout << "name=" << it->name << " value=" << it->value << std::endl; +// } +class ObjectIterator { +public: + struct Field { + butil::StringPiece name; + UnparsedValue value; + }; + + // Parse `n' bytes from `stream' as fields of an object. + ObjectIterator(InputStream* stream, size_t n) { init(stream, n); } + explicit ObjectIterator(UnparsedValue& value) + { init(value.stream(), value.size()); } + ~ObjectIterator() {} + + Field* operator->() { return &_current_field; } + Field& operator*() { return _current_field; } + void operator++(); + operator void*() const { return (void*)_current_field.value.type(); } + + // Number of fields in the object. + uint32_t field_count() const { return _field_count; } + +private: + void init(InputStream* stream, size_t n); + void set_bad() { + set_end(); + _stream->set_bad(); + } + void set_end() { _current_field.value._type = FIELD_UNKNOWN; } + size_t left_size() const + { return _expected_popped_end - _expected_popped_bytes; } + + Field _current_field; + uint32_t _field_count; + std::string _name_backup_string; + InputStream* _stream; + size_t _expected_popped_bytes; + size_t _expected_popped_end; +}; + +// Iterator all items in a (mcpack) array which should be created like this: +// ArrayIterator it(unparsed_value); +// for (; it != NULL; ++it) { +// std::cout << " item=" << *it << std::endl; +// } +class ArrayIterator { +public: + typedef UnparsedValue Field; + + ArrayIterator(InputStream* stream, size_t size) { init(stream, size); } + explicit ArrayIterator(UnparsedValue& value) + { init(value.stream(), value.size()); } + ~ArrayIterator() {} + + Field* operator->() { return &_current_field; } + Field& operator*() { return _current_field; } + void operator++(); + operator void*() const { return (void*)_current_field.type(); } + + // Number of items in the array. + uint32_t item_count() const { return _item_count; } + +private: + void init(InputStream* stream, size_t n); + void set_bad() { + set_end(); + _stream->set_bad(); + } + void set_end() { _current_field._type = FIELD_UNKNOWN; } + size_t left_size() const + { return _expected_popped_end - _expected_popped_bytes; } + + Field _current_field; + uint32_t _item_count; + InputStream* _stream; + size_t _expected_popped_bytes; + size_t _expected_popped_end; +}; + +// Iterator all items in an isomorphic array which should be created like this: +// ISOArrayIterator it(unparsed_value); +class ISOArrayIterator { +public: + ISOArrayIterator(InputStream* stream, size_t size) { init(stream, size); } + explicit ISOArrayIterator(UnparsedValue& value) + { init(value.stream(), value.size()); } + ~ISOArrayIterator() {} + void operator++(); + operator void*() const { return (void*)(uintptr_t)_item_type; } + + template T as_integer() const; + template T as_fp() const; + int64_t as_int64() const { return as_integer(); } + uint64_t as_uint64() const { return as_integer(); } + int32_t as_int32() const { return as_integer(); } + uint32_t as_uint32() const { return as_integer(); } + bool as_bool() const { return as_integer(); } + float as_float() const { return as_fp(); } + double as_double() const { return as_fp(); } + + PrimitiveFieldType item_type() const { return _item_type; } + + // Number of items in the array. + uint32_t item_count() const { return _item_count; } + +private: + void init(InputStream* stream, size_t n); + void set_bad() { + set_end(); + _stream->set_bad(); + } + void set_end() { _item_type = (PrimitiveFieldType)0; } + + int _buf_index; + int _buf_count; + InputStream* _stream; + PrimitiveFieldType _item_type; + uint32_t _item_size; + uint32_t _item_count; + uint32_t _left_item_count; + char _item_buf[1024]; +}; + +} // namespace mcpack2pb + +#include "mcpack2pb/parser-inl.h" + +#endif // MCPACK2PB_MCPACK_PARSER_H diff --git a/src/mcpack2pb/serializer-inl.h b/src/mcpack2pb/serializer-inl.h new file mode 100644 index 0000000..9c5a262 --- /dev/null +++ b/src/mcpack2pb/serializer-inl.h @@ -0,0 +1,304 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// mcpack2pb - Make protobuf be front-end of mcpack/compack + +// Date: Mon Oct 19 17:17:36 CST 2015 + +#ifndef MCPACK2PB_MCPACK_SERIALIZER_INL_H +#define MCPACK2PB_MCPACK_SERIALIZER_INL_H + +void* fast_memcpy(void *__restrict dest, const void *__restrict src, size_t n); + +namespace mcpack2pb { + +inline OutputStream::Area::Area(const Area& rhs) + : _addr1(rhs._addr1) + , _addr2(rhs._addr2) + , _size1(rhs._size1) + , _size2(rhs._size2) + , _addional_area(NULL) { + + if (rhs._addional_area) { + _addional_area = new std::vector(*rhs._addional_area); + } +} + +inline OutputStream::Area::~Area() { + if (_addional_area) { + delete _addional_area; + _addional_area = NULL; + } +} + +inline OutputStream::Area& OutputStream::Area::operator=(const OutputStream::Area& rhs) { + if (this == &rhs) { + return *this; + } + this->~Area(); + new (this) Area(rhs); + return *this; +} + +inline void OutputStream::Area::add(void* data, size_t n) { + if (!data) { + return; + } + if (_addr1 == NULL) { + _addr1 = data; + _size1 = n; + } else if (_addr2 == NULL) { + _addr2 = data; + _size2 = n; + } else { + if (_addional_area == NULL) { + _addional_area = new std::vector; + } + _addional_area->push_back(butil::StringPiece((const char*)data, n)); + } +} + +inline void OutputStream::Area::assign(const void* data) const { + if (_addr1) { + fast_memcpy(_addr1, data, _size1); + if (!_addr2) { + return; + } + fast_memcpy(_addr2, (const char*)data + _size1, _size2); + if (!_addional_area) { + return; + } + size_t offset = _size1 + _size2; + for (std::vector::const_iterator iter = + _addional_area->begin(); iter != _addional_area->end(); ++iter) { + fast_memcpy((void*)iter->data(), (const char*)data + offset, iter->size()); + offset += iter->size(); + } + } +} + +inline void OutputStream::done() { + if (_good && _size) { + _zc_stream->BackUp(_size); + _size = 0; + _fullsize = 0; + } +} + +inline void OutputStream::append(const void* data, int n) { + const int saved_n = n; + do { + if (n <= _size) { + fast_memcpy(_data, data, n); + _data = (char*)_data + n; + _size -= n; + _pushed_bytes += saved_n; + return; + } + fast_memcpy(_data, data, _size); + data = (const char*)data + _size; + n -= _size; + if (!_zc_stream->Next(&_data, &_size)) { + break; + } + _fullsize = _size; + } while (1); + _data = NULL; + _size = 0; + _fullsize = 0; + _pushed_bytes += (saved_n - n); + if (n) { + set_bad(); + } +} + +template +inline void OutputStream::append_packed_pod(const T& packed_pod) { + // if (sizeof(T) <= _size) { + // *(T*)_data = packed_pod; + // _data = (char*)_data + sizeof(T); + // _size -= sizeof(T); + // _pushed_bytes += sizeof(T); + // return; + // } + return append(&packed_pod, sizeof(T)); +} + +inline void OutputStream::push_back(char c) { + do { + if (_size > 0) { + *(char*)_data = c; + _data = (char*)_data + 1; + --_size; + ++_pushed_bytes; + return; + } + if (!_zc_stream->Next(&_data, &_size)) { + break; + } + _fullsize = _size; + } while (1); + _data = NULL; + _size = 0; + _fullsize = 0; + set_bad(); +} + +inline void* OutputStream::skip_continuous(int n) { + if (_size >= n) { + void* ret = _data; + _data = (char*)_data + n; + _size -= n; + _pushed_bytes += n; + return ret; + } + return NULL; +} + +inline OutputStream::Area OutputStream::reserve(int n) { + Area area; + const int saved_n = n; + do { + if (n <= _size) { + area.add(_data, n); + _data = (char*)_data + n; + _size -= n; + _pushed_bytes += saved_n; + return area; + } + area.add(_data, _size); + n -= _size; + if (!_zc_stream->Next(&_data, &_size)) { + break; + } + _fullsize = _size; + } while (1); + _data = NULL; + _size = 0; + _fullsize = 0; + _pushed_bytes += (saved_n - n); + if (n) { + set_bad(); + } + return area; +} + +inline void OutputStream::assign(const Area& area, const void* data) { + area.assign(data); +} + +inline void OutputStream::backup(int n) { + if (_fullsize >= _size + n) { + _size += n; + _data = (char*)_data - n; + _pushed_bytes -= n; + return; + } + const int64_t saved_bytecount = _zc_stream->ByteCount(); + // Backup the remaining size + what user requests. The implementation + // <= r33563 backups n + _size - _fullsize which is wrong. + _zc_stream->BackUp(n + _size); + const int64_t nbackup = saved_bytecount - _zc_stream->ByteCount(); + if (nbackup != n + _size) { + CHECK(false) << "Expect output stream backward for " << n + _size + << " bytes, actually " << nbackup << " bytes"; + } + _size = 0; + _fullsize = 0; + _data = NULL; + _pushed_bytes -= n; +} + +inline Serializer::GroupInfo* Serializer::push_group_info() { + if (_ndepth + 1 < (int)arraysize(_group_info_fast)) { + return &_group_info_fast[++_ndepth]; + } + if (_ndepth >= MAX_DEPTH) { + return NULL; + } + if (_group_info_more == NULL) { + _group_info_more = + (GroupInfo*)malloc((MAX_DEPTH + 1 - arraysize(_group_info_fast)) + * sizeof(GroupInfo)); + if (_group_info_more == NULL) { + return NULL; + } + } + return &_group_info_more[++_ndepth - arraysize(_group_info_fast)]; +} + +inline Serializer::GroupInfo& Serializer::peek_group_info() { + if (_ndepth < (int)arraysize(_group_info_fast)) { + return _group_info_fast[_ndepth]; + } else { + return _group_info_more[_ndepth - arraysize(_group_info_fast)]; + } +} + +inline void Serializer::add_multiple_int8(const uint8_t* values, size_t count) { + return add_multiple_int8((const int8_t*)values, count); +} +inline void Serializer::add_multiple_int16(const uint16_t* values, size_t count) { + return add_multiple_int16((const int16_t*)values, count); +} +inline void Serializer::add_multiple_int32(const uint32_t* values, size_t count) { + return add_multiple_int32((const int32_t*)values, count); +} +inline void Serializer::add_multiple_int64(const uint64_t* values, size_t count) { + return add_multiple_int64((const int64_t*)values, count); +} + +inline void Serializer::add_multiple_uint8(const int8_t* values, size_t count) { + return add_multiple_uint8((const uint8_t*)values, count); +} +inline void Serializer::add_multiple_uint16(const int16_t* values, size_t count) { + return add_multiple_uint16((const uint16_t*)values, count); +} +inline void Serializer::add_multiple_uint32(const int32_t* values, size_t count) { + return add_multiple_uint32((const uint32_t*)values, count); +} +inline void Serializer::add_multiple_uint64(const int64_t* values, size_t count) { + return add_multiple_uint64((const uint64_t*)values, count); +} + +inline void Serializer::begin_mcpack_array(const StringWrapper& name, FieldType item_type) +{ begin_array_internal(name, item_type, false); } + +inline void Serializer::begin_mcpack_array(FieldType item_type) +{ begin_array_internal(item_type, false); } + +inline void Serializer::begin_compack_array(const StringWrapper& name, FieldType item_type) +{ begin_array_internal(name, item_type, true); } + +inline void Serializer::begin_compack_array(FieldType item_type) +{ begin_array_internal(item_type, true); } + +inline void Serializer::begin_object(const StringWrapper& name) +{ begin_object_internal(name); } + +inline void Serializer::begin_object() +{ begin_object_internal(); } + +inline void Serializer::end_object() +{ end_object_internal(false); } + +inline void Serializer::end_object_iso() +{ end_object_internal(true); } + +} // namespace mcpack2pb + +#endif // MCPACK2PB_MCPACK_SERIALIZER_INL_H diff --git a/src/mcpack2pb/serializer.cpp b/src/mcpack2pb/serializer.cpp new file mode 100644 index 0000000..26fcbea --- /dev/null +++ b/src/mcpack2pb/serializer.cpp @@ -0,0 +1,816 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// mcpack2pb - Make protobuf be front-end of mcpack/compack + +// Date: Mon Oct 19 17:17:36 CST 2015 + +#include "mcpack2pb/serializer.h" + +namespace mcpack2pb { + +const static OutputStream::Area INVALID_AREA(butil::LINKER_INITIALIZED); + +// Binary head before fixed-size types. +struct FieldFixedHead { +public: + void set_type(uint8_t type) { _type = type; } + uint8_t type() const { return _type; } + size_t name_offset() const { return sizeof(FieldFixedHead); } + size_t name_size() const { return _name_size; } + void set_name_size(size_t name_size) { _name_size = name_size; } + size_t value_offset() const { return name_offset() + name_size(); } + size_t value_size() const { return (_type & FIELD_FIXED_MASK); } + size_t full_size() const { return value_offset() + value_size(); } +public:// NOTE(gejun): initializer requires public members. + uint8_t _type; // FieldType + uint8_t _name_size; +} __attribute__((__packed__)); + +// Binary head before string<=254 or raw<=255 +class FieldShortHead { +public: + void set_type(uint8_t type) { _type = type; } + uint8_t type() const { return _type; } + size_t name_offset() const { return sizeof(FieldShortHead); } + size_t name_size() const { return _name_size; } + void set_name_size(size_t name_size) { _name_size = name_size; } + size_t value_offset() const { return name_offset() + name_size(); } + size_t value_size() const { return _value_size; } + void set_value_size(size_t value_size) { _value_size = value_size; } + size_t full_size() const { return value_offset() + value_size(); } +private: + uint8_t _type; + uint8_t _name_size; + uint8_t _value_size; +} __attribute__((__packed__)); + +// Binary head before variable-size fields. +class FieldLongHead { +public: + void set_type(uint8_t type) { _type = type; } + uint8_t type() const { return _type; } + size_t name_offset() const { return sizeof(FieldLongHead); } + size_t name_size() const { return _name_size; } + void set_name_size(size_t name_size) { _name_size = name_size; } + size_t value_offset() const { return name_offset() + name_size(); } + size_t value_size() const { return _value_size; } + void set_value_size(size_t value_size) { _value_size = value_size; } + size_t full_size() const { return value_offset() + value_size(); } +private: + uint8_t _type; + uint8_t _name_size; + uint32_t _value_size; +} __attribute__((__packed__)); + +// Binary head before items of array/object except isomorphic array. +struct ItemsHead { + uint32_t item_count; +} __attribute__((__packed__)); + +// Assert type sizes: +BAIDU_CASSERT(sizeof(FieldFixedHead) == 2, size_assert); +BAIDU_CASSERT(sizeof(FieldShortHead) == 3, size_assert); +BAIDU_CASSERT(sizeof(FieldLongHead) == 6, size_assert); +BAIDU_CASSERT(sizeof(ItemsHead) == 4, size_assert); + +template struct GetFieldType {}; + +template<> struct GetFieldType { + static const FieldType value = FIELD_INT8; +}; +template<> struct GetFieldType { + static const FieldType value = FIELD_INT16; +}; +template<> struct GetFieldType { + static const FieldType value = FIELD_INT32; +}; +template<> struct GetFieldType { + static const FieldType value = FIELD_INT64; +}; +template<> struct GetFieldType { + static const FieldType value = FIELD_UINT8; +}; +template<> struct GetFieldType { + static const FieldType value = FIELD_UINT16; +}; +template<> struct GetFieldType { + static const FieldType value = FIELD_UINT32; +}; +template<> struct GetFieldType { + static const FieldType value = FIELD_UINT64; +}; +template<> struct GetFieldType { + static const FieldType value = FIELD_FLOAT; +}; +template<> struct GetFieldType { + static const FieldType value = FIELD_DOUBLE; +}; +template<> struct GetFieldType { + static const FieldType value = FIELD_BOOL; +}; + +void Serializer::GroupInfo::print(std::ostream& os) const { + os << type2str(type); + if (type == FIELD_ARRAY) { + os << '[' << type2str(item_type) << ']'; + } + + // os << type2str(type) << '='; + // const uint8_t first_byte = *head_buf; + // butil::StringPiece name; + // if (first_byte & FIELD_FIXED_MASK) { + // FieldFixedHead head; + // memcpy(&head, head_buf, sizeof(FieldFixedHead)); + // name.set(head_buf + head.name_offset(), head.name_size()); + // } else if (first_byte & FIELD_SHORT_MASK) { + // FieldShortHead head; + // memcpy(&head, head_buf, sizeof(FieldFixedHead)); + // name.set(head_buf + head.name_offset(), head.name_size()); + // } else { + // FieldLongHead head; + // memcpy(&head, head_buf, sizeof(FieldFixedHead)); + // name.set(head_buf + head.name_offset(), head.name_size()); + // } + // if (name.empty()) { + // os << ""; + // } else { + // os << '`' << name << '\''; + // } +} + +std::ostream& operator<<(std::ostream& os, const Serializer::GroupInfo& gi) { + gi.print(os); + return os; +} + +Serializer::Serializer(OutputStream* stream) + : _stream(stream) + , _ndepth(0) + , _group_info_more(NULL) { + GroupInfo & info = _group_info_fast[0]; + info.item_count = 0; + info.isomorphic = false; + info.item_type = 0; + info.type = FIELD_OBJECT; + info.name_size = 0; + info.output_offset = 0; + info.pending_null_count = 0; + info.head_area = INVALID_AREA; + info.items_head_area = INVALID_AREA; +} + +Serializer::~Serializer() { + if (_ndepth && good()/*error always causes opening braces*/) { + std::ostringstream oss; + oss << "Serializer(" << this << ") has opening"; + for (; _ndepth > 0; --_ndepth) { + oss << ' ' << peek_group_info(); + } + CHECK(false) << oss.str(); + } + free(_group_info_more); + _group_info_more = NULL; +} + +void add_pending_nulls(OutputStream* stream, + Serializer::GroupInfo& group_info); + +inline bool object_add_item(Serializer::GroupInfo & group_info, + const StringWrapper& name) { + if (name.size() > 254) { + CHECK(false) << "Too long name=`" << name << '\''; + return false; + } + if (group_info.type != FIELD_OBJECT) { + CHECK(false) << "Cannot add `" << name << "' to " << group_info; + return false; + } + ++group_info.item_count; + return true; +} + +inline bool array_add_item(OutputStream* stream, + Serializer::GroupInfo & group_info, + FieldType item_type, + uint32_t n) { + if (group_info.pending_null_count) { + add_pending_nulls(stream, group_info); + } + if (group_info.item_type == item_type || + (group_info.item_type == FIELD_OBJECT && item_type == FIELD_ARRAY)) { + group_info.item_count += n; + return true; + } + if (group_info.type == FIELD_ARRAY) { + CHECK(false) << "Different item_type=" << type2str(item_type) + << " from " << group_info; + return false; + } + if (group_info.output_offset == 0) { + // Enable anynomous object at first level. + group_info.item_count += n; + return true; + } else { + CHECK(false) << "Cannot add field without name to " << group_info; + return false; + } +} + +//========================= +//adding primitive types + +template +struct FixedHeadAndValue { + FieldFixedHead head; + T value; +} __attribute__((__packed__)); + +template +inline void add_primitive( + OutputStream* stream, Serializer::GroupInfo & group_info, T value) { + if (!stream->good()) { + return; + } + if (!array_add_item(stream, group_info, GetFieldType::value, 1)) { + return stream->set_bad(); + } + if (group_info.isomorphic) { + stream->append_packed_pod(value); + } else { + FixedHeadAndValue head_and_value; + head_and_value.head.set_type(GetFieldType::value); + head_and_value.head.set_name_size(0); + head_and_value.value = value; + stream->append_packed_pod(head_and_value); + } +} + +template +inline void add_primitive(OutputStream* stream, + Serializer::GroupInfo & group_info, + const StringWrapper& name, + T value) { + if (name.empty()) { + return add_primitive(stream, group_info, value); + } + if (!stream->good()) { + return; + } + if (!object_add_item(group_info, name)) { + return stream->set_bad(); + } + FieldFixedHead head; + head.set_type(GetFieldType::value); + head.set_name_size(name.size() + 1); + void* data = stream->skip_continuous( + sizeof(FieldFixedHead) + name.size() + 1 + sizeof(T)); + if (data) { + // the stream has enough continuous space, we do the copying directly. + // Comparing to the branch below, it saves 2 memcpy and many if/else. + *(FieldFixedHead*)data = head; + data = (char*)data + sizeof(FieldFixedHead); + fast_memcpy(data, name.data(), name.size() + 1); + data = (char*)data + name.size() + 1; + *(T*)data = value; + } else { + stream->append_packed_pod(head); + stream->append(name.data(), name.size() + 1); + stream->append_packed_pod(value); + } +} + +template +inline void add_primitives(OutputStream* stream, + Serializer::GroupInfo & group_info, + const T* values, size_t n) { + if (!stream->good()) { + return; + } + if (!array_add_item(stream, group_info, GetFieldType::value, n)) { + return stream->set_bad(); + } + if (group_info.isomorphic) { + stream->append(values, sizeof(T) * n); + } else { + // Even for mcpack arrays, we need to batch the values into an array + // of head+value and write the array into stream for (much) better + // throughput. + static const size_t BATCH = 128; + size_t nwritten = 0; + while (n) { + const size_t cur_batch = std::min(n, BATCH); + FixedHeadAndValue tmp[cur_batch]; + for (size_t i = 0; i < cur_batch; ++i) { + tmp[i].head.set_type(GetFieldType::value); + tmp[i].head.set_name_size(0); + tmp[i].value = values[nwritten + i]; + } + nwritten += cur_batch; + n -= cur_batch; + stream->append(tmp, sizeof(FixedHeadAndValue) * cur_batch); + } + } +} + +void Serializer::add_int8(const StringWrapper& name, int8_t value) +{ add_primitive(_stream, peek_group_info(), name, value); } +void Serializer::add_int16(const StringWrapper& name, int16_t value) +{ add_primitive(_stream, peek_group_info(), name, value); } +void Serializer::add_int32(const StringWrapper& name, int32_t value) +{ add_primitive(_stream, peek_group_info(), name, value); } +void Serializer::add_int64(const StringWrapper& name, int64_t value) +{ add_primitive(_stream, peek_group_info(), name, value); } +void Serializer::add_uint8(const StringWrapper& name, uint8_t value) +{ add_primitive(_stream, peek_group_info(), name, value); } +void Serializer::add_uint16(const StringWrapper& name, uint16_t value) +{ add_primitive(_stream, peek_group_info(), name, value); } +void Serializer::add_uint32(const StringWrapper& name, uint32_t value) +{ add_primitive(_stream, peek_group_info(), name, value); } +void Serializer::add_uint64(const StringWrapper& name, uint64_t value) +{ add_primitive(_stream, peek_group_info(), name, value); } +void Serializer::add_bool(const StringWrapper& name, bool value) +{ add_primitive(_stream, peek_group_info(), name, value); } +void Serializer::add_float(const StringWrapper& name, float value) +{ add_primitive(_stream, peek_group_info(), name, value); } +void Serializer::add_double(const StringWrapper& name, double value) +{ add_primitive(_stream, peek_group_info(), name, value); } + +void Serializer::add_int8(int8_t value) +{ add_primitive(_stream, peek_group_info(), value); } +void Serializer::add_int16(int16_t value) +{ add_primitive(_stream, peek_group_info(), value); } +void Serializer::add_int32(int32_t value) +{ add_primitive(_stream, peek_group_info(), value); } +void Serializer::add_int64(int64_t value) +{ add_primitive(_stream, peek_group_info(), value); } +void Serializer::add_uint8(uint8_t value) +{ add_primitive(_stream, peek_group_info(), value); } +void Serializer::add_uint16(uint16_t value) +{ add_primitive(_stream, peek_group_info(), value); } +void Serializer::add_uint32(uint32_t value) +{ add_primitive(_stream, peek_group_info(), value); } +void Serializer::add_uint64(uint64_t value) +{ add_primitive(_stream, peek_group_info(), value); } +void Serializer::add_bool(bool value) +{ add_primitive(_stream, peek_group_info(), value); } +void Serializer::add_float(float value) +{ add_primitive(_stream, peek_group_info(), value); } +void Serializer::add_double(double value) +{ add_primitive(_stream, peek_group_info(), value); } + +void Serializer::add_multiple_int8(const int8_t* values, size_t count) +{ add_primitives(_stream, peek_group_info(), values, count); } +void Serializer::add_multiple_int16(const int16_t* values, size_t count) +{ add_primitives(_stream, peek_group_info(), values, count); } +void Serializer::add_multiple_int32(const int32_t* values, size_t count) +{ add_primitives(_stream, peek_group_info(), values, count); } +void Serializer::add_multiple_int64(const int64_t* values, size_t count) +{ add_primitives(_stream, peek_group_info(), values, count); } +void Serializer::add_multiple_uint8(const uint8_t* values, size_t count) +{ add_primitives(_stream, peek_group_info(), values, count); } +void Serializer::add_multiple_uint16(const uint16_t* values, size_t count) +{ add_primitives(_stream, peek_group_info(), values, count); } +void Serializer::add_multiple_uint32(const uint32_t* values, size_t count) +{ add_primitives(_stream, peek_group_info(), values, count); } +void Serializer::add_multiple_uint64(const uint64_t* values, size_t count) +{ add_primitives(_stream, peek_group_info(), values, count); } +void Serializer::add_multiple_bool(const bool* values, size_t count) +{ add_primitives(_stream, peek_group_info(), values, count); } +void Serializer::add_multiple_float(const float* values, size_t count) +{ add_primitives(_stream, peek_group_info(), values, count); } +void Serializer::add_multiple_double(const double* values, size_t count) +{ add_primitives(_stream, peek_group_info(), values, count); } + +// ================== +// append string/raw + +inline void add_binary_internal(OutputStream* stream, + Serializer::GroupInfo & group_info, + const butil::StringPiece& value, + FieldType type) { + if (!stream->good()) { + return; + } + if (!array_add_item(stream, group_info, type, 1)) { + return stream->set_bad(); + } + if (value.size() <= 255) { + FieldShortHead shead; + shead.set_type(type | FIELD_SHORT_MASK); + shead.set_name_size(0); + shead.set_value_size(value.size()); + stream->append_packed_pod(shead); + stream->append(value.data(), value.size()); + } else { + FieldLongHead lhead; + lhead.set_type(type); + lhead.set_name_size(0); + lhead.set_value_size(value.size()); + stream->append_packed_pod(lhead); + stream->append(value.data(), value.size()); + } +} + +inline void add_binary_internal(OutputStream* stream, + Serializer::GroupInfo & group_info, + const StringWrapper& name, + const butil::StringPiece& value, + FieldType type) { + if (name.empty()) { + return add_binary_internal(stream, group_info, value, type); + } + if (!stream->good()) { + return; + } + if (!object_add_item(group_info, name)) { + return stream->set_bad(); + } + if (value.size() <= 255) { + FieldShortHead shead; + shead.set_type(type | FIELD_SHORT_MASK); + shead.set_name_size(name.size() + 1); + shead.set_value_size(value.size()); + stream->append_packed_pod(shead); + stream->append(name.data(), name.size() + 1); + stream->append(value.data(), value.size()); + } else { + FieldLongHead lhead; + lhead.set_type(type); + lhead.set_name_size(name.size() + 1); + lhead.set_value_size(value.size()); + stream->append_packed_pod(lhead); + stream->append(name.data(), name.size() + 1); + stream->append(value.data(), value.size()); + } +} + +void Serializer::add_string(const StringWrapper& name, const StringWrapper& s) { + add_binary_internal(_stream, peek_group_info(), name, + butil::StringPiece(s.data(), s.size() + 1), FIELD_STRING); +} +void Serializer::add_string(const StringWrapper& s) { + add_binary_internal(_stream, peek_group_info(), + butil::StringPiece(s.data(), s.size() + 1), FIELD_STRING); +} +void Serializer::add_binary(const StringWrapper& name, + const std::string& data) { + add_binary_internal(_stream, peek_group_info(), name, data, FIELD_BINARY); +} +void Serializer::add_binary(const StringWrapper& name, + const void* data, size_t n) { + add_binary_internal(_stream, peek_group_info(), name, + butil::StringPiece((const char*)data, n), FIELD_BINARY); +} +void Serializer::add_binary(const std::string& data) { + add_binary_internal(_stream, peek_group_info(), data, FIELD_BINARY); +} +void Serializer::add_binary(const void* data, size_t n) { + add_binary_internal(_stream, peek_group_info(), + butil::StringPiece((const char*)data, n), FIELD_BINARY); +} + +// =============== +// append null. + +inline void add_null_internal(OutputStream* stream, + Serializer::GroupInfo& group_info) { + ++group_info.pending_null_count; +} + +struct NullLayout { + FieldFixedHead head; + char zero; +} __attribute__((__packed__)); + +#define MCPACK_NULL_INITIALIZER {{FIELD_NULL,0},0} +#define MCPACK_NULL_ARRAY_INITIALIZER_4 \ + MCPACK_NULL_INITIALIZER, MCPACK_NULL_INITIALIZER, \ + MCPACK_NULL_INITIALIZER, MCPACK_NULL_INITIALIZER +#define MCPACK_NULL_ARRAY_INITIALIZER_16 \ + MCPACK_NULL_ARRAY_INITIALIZER_4, MCPACK_NULL_ARRAY_INITIALIZER_4, \ + MCPACK_NULL_ARRAY_INITIALIZER_4, MCPACK_NULL_ARRAY_INITIALIZER_4 +#define MCPACK_NULL_ARRAY_INITIALIZER_64 \ + MCPACK_NULL_ARRAY_INITIALIZER_16, MCPACK_NULL_ARRAY_INITIALIZER_16, \ + MCPACK_NULL_ARRAY_INITIALIZER_16, MCPACK_NULL_ARRAY_INITIALIZER_16 + +static NullLayout s_null_array[] = {MCPACK_NULL_ARRAY_INITIALIZER_64}; + +// no inline. inline makes branches in array_add_item worse. +void add_pending_nulls(OutputStream* stream, + Serializer::GroupInfo& group_info) { + if (!stream->good()) { + return; + } + if (group_info.type != FIELD_ARRAY) { + CHECK(false) << "Cannot add nulls without name to " << group_info; + return stream->set_bad(); + } + if (group_info.isomorphic) { + CHECK(false) << "Cannot add nulls to isomorphic " << group_info; + return stream->set_bad(); + } + int n = group_info.pending_null_count; + group_info.pending_null_count = 0; + group_info.item_count += n; + // layout of nulls = [{FIELD_NULL,0,0},{FIELD_NULL,0,0},...] + while (n) { + const int cur_batch = std::min(n, (int)arraysize(s_null_array)); + n -= cur_batch; + stream->append(s_null_array, cur_batch * sizeof(NullLayout)); + } +} + +inline void add_null_internal(OutputStream* stream, + Serializer::GroupInfo & group_info, + const StringWrapper& name) { + if (name.empty()) { + return add_null_internal(stream, group_info); + } + if (!stream->good()) { + return; + } + if (!object_add_item(group_info, name)) { + return stream->set_bad(); + } + FieldFixedHead head; + head.set_type(FIELD_NULL); + head.set_name_size(name.size() + 1); + stream->append_packed_pod(head); + stream->append(name.data(), name.size() + 1); + stream->push_back(0); +} + +void Serializer::add_null(const StringWrapper& name) { + add_null_internal(_stream, peek_group_info(), name); +} +void Serializer::add_null() { + add_null_internal(_stream, peek_group_info()); +} + +// =============== +// append empty_array. + +struct ArrayHead { + FieldLongHead head; + ItemsHead items_head; +} __attribute__((__packed__)); + +struct ObjectHead { + FieldLongHead head; + ItemsHead fields_head; +} __attribute__((__packed__)); + + +inline void add_empty_array_internal(OutputStream* stream, + Serializer::GroupInfo& group_info) { + if (!stream->good()) { + return; + } + if (!array_add_item(stream, group_info, FIELD_ARRAY, 1)) { + return stream->set_bad(); + } + ArrayHead arrhead; + arrhead.head.set_type(FIELD_ARRAY); + arrhead.head.set_name_size(0); + arrhead.head.set_value_size(sizeof(ItemsHead)); + arrhead.items_head.item_count = 0; + stream->append_packed_pod(arrhead); +} + +inline void add_empty_array_internal(OutputStream* stream, + Serializer::GroupInfo & group_info, + const StringWrapper& name) { + if (name.empty()) { + return add_empty_array_internal(stream, group_info); + } + if (!stream->good()) { + return; + } + if (!object_add_item(group_info, name)) { + return stream->set_bad(); + } + FieldLongHead head; + head.set_type(FIELD_ARRAY); + head.set_name_size(name.size() + 1); + head.set_value_size(sizeof(ItemsHead)); + ItemsHead items_head = { 0 }; + stream->append_packed_pod(head); + stream->append(name.data(), name.size() + 1); + stream->append_packed_pod(items_head); +} + +void Serializer::add_empty_array(const StringWrapper& name) { + add_empty_array_internal(_stream, peek_group_info(), name); +} +void Serializer::add_empty_array() { + add_empty_array_internal(_stream, peek_group_info()); +} + +// ========================= +// append array/object + +void Serializer::begin_object_internal() { + if (!_stream->good()) { + return; + } + if (!array_add_item(_stream, peek_group_info(), FIELD_OBJECT, 1)) { + return _stream->set_bad(); + } + GroupInfo* info = push_group_info(); + if (info == NULL) { + CHECK(false) << "Fail to push object"; + return _stream->set_bad(); + } + info->item_count = 0; + info->isomorphic = false; + info->item_type = 0; + info->type = FIELD_OBJECT; + info->name_size = 0; + info->output_offset = _stream->pushed_bytes(); + info->pending_null_count = 0; + info->head_area = _stream->reserve(sizeof(ObjectHead)); + info->items_head_area = INVALID_AREA; +} + +void Serializer::begin_object_internal(const StringWrapper& name) { + if (name.empty()) { + return begin_object_internal(); + } + if (!_stream->good()) { + return; + } + if (!object_add_item(peek_group_info(), name)) { + return _stream->set_bad(); + } + GroupInfo* info = push_group_info(); + if (info == NULL) { + CHECK(false) << "Fail to push object=" << name; + return _stream->set_bad(); + } + info->item_count = 0; + info->isomorphic = false; + info->item_type = 0; + info->type = FIELD_OBJECT; + info->name_size = (uint8_t)(name.size() + 1); + info->output_offset = _stream->pushed_bytes(); + info->pending_null_count = 0; + info->head_area = _stream->reserve(sizeof(FieldLongHead)); + _stream->append(name.data(), name.size() + 1); + info->items_head_area = _stream->reserve(sizeof(ItemsHead)); +} + +inline void pop_group_info(int & ndepth) { + if (ndepth > 0) { + --ndepth; + } else { + CHECK(false) << "Nothing to pop"; + } +} + +void Serializer::end_object_internal(bool objectisoarray) { + if (!_stream->good()) { + return; + } + GroupInfo & group_info = peek_group_info(); + if (FIELD_OBJECT != group_info.type) { + CHECK(false) << "end_object() is called on " << group_info; + return _stream->set_bad(); + } + if (group_info.name_size == 0) { + ObjectHead objhead; + objhead.head.set_type(objectisoarray ? FIELD_OBJECTISOARRAY : FIELD_OBJECT); + objhead.head.set_name_size(0); + objhead.head.set_value_size(_stream->pushed_bytes() - group_info.output_offset + - sizeof(FieldLongHead)); + objhead.fields_head.item_count = group_info.item_count; + _stream->assign(group_info.head_area, &objhead); + pop_group_info(_ndepth); + } else { + FieldLongHead lhead; + lhead.set_type(objectisoarray ? FIELD_OBJECTISOARRAY : FIELD_OBJECT); + lhead.set_name_size(group_info.name_size); + lhead.set_value_size(_stream->pushed_bytes() - group_info.output_offset + - group_info.name_size - sizeof(FieldLongHead)); + _stream->assign(group_info.head_area, &lhead); + const ItemsHead items_head = { group_info.item_count }; + _stream->assign(group_info.items_head_area, &items_head); + pop_group_info(_ndepth); + } +} + +void Serializer::begin_array_internal(FieldType item_type, bool compack) { + if (!_stream->good()) { + return; + } + if (!array_add_item(_stream, peek_group_info(), FIELD_ARRAY, 1)) { + return _stream->set_bad(); + } + GroupInfo* info = push_group_info(); + if (info == NULL) { + CHECK(false) << "Fail to push array"; + return _stream->set_bad(); + } + info->item_count = 0; + info->item_type = item_type; + info->type = FIELD_ARRAY; + info->name_size = 0; + info->output_offset = _stream->pushed_bytes(); + info->pending_null_count = 0; + info->head_area = _stream->reserve(sizeof(FieldLongHead)); + if (compack && get_primitive_type_size(item_type)) { + info->isomorphic = true; + info->items_head_area = INVALID_AREA; + _stream->push_back((char)item_type); + } else { + info->isomorphic = false; + info->items_head_area = _stream->reserve(sizeof(ItemsHead)); + } +} + +void Serializer::begin_array_internal(const StringWrapper& name, + FieldType item_type, + bool compack) { + if (name.empty()) { + return begin_array_internal(item_type, compack); + } + if (!_stream->good()) { + return; + } + if (!object_add_item(peek_group_info(), name)) { + return _stream->set_bad(); + } + GroupInfo* info = push_group_info(); + if (info == NULL) { + CHECK(false) << "Fail to push array"; + return _stream->set_bad(); + } + info->item_count = 0; + info->item_type = item_type; + info->type = FIELD_ARRAY; + info->name_size = (uint8_t)(name.size() + 1); + info->output_offset = _stream->pushed_bytes(); + info->pending_null_count = 0; + info->head_area = _stream->reserve(sizeof(FieldLongHead)); + _stream->append(name.data(), name.size() + 1); + if (compack && get_primitive_type_size(item_type)) { + info->isomorphic = true; + info->items_head_area = INVALID_AREA; + _stream->push_back((char)item_type); + } else { + info->isomorphic = false; + info->items_head_area = _stream->reserve(sizeof(ItemsHead)); + } +} + +void Serializer::end_array() { + if (!_stream->good()) { + return; + } + GroupInfo & group_info = peek_group_info(); + if (FIELD_ARRAY != group_info.type) { + CHECK(false) << "end_array() is called on " << group_info; + return _stream->set_bad(); + } + if (group_info.item_count == 0 && group_info.pending_null_count == 0) { + // Remove the heading. This is a must because idl cannot load an empty + // array only with header. + _stream->backup(_stream->pushed_bytes() - group_info.output_offset); + pop_group_info(_ndepth); + --peek_group_info().item_count; + return; + } + // Reset lhead/items_head + FieldLongHead lhead; + if (group_info.isomorphic) { + lhead.set_type(FIELD_ISOARRAY); + } else { + lhead.set_type(FIELD_ARRAY); + if (group_info.pending_null_count) { + add_pending_nulls(_stream, group_info); + } + const ItemsHead items_head = { group_info.item_count }; + _stream->assign(group_info.items_head_area, &items_head); + } + lhead.set_name_size(group_info.name_size); + lhead.set_value_size(_stream->pushed_bytes() - group_info.output_offset + - group_info.name_size - sizeof(FieldLongHead)); + _stream->assign(group_info.head_area, &lhead); + pop_group_info(_ndepth); +} + +} // namespace mcpack2pb diff --git a/src/mcpack2pb/serializer.h b/src/mcpack2pb/serializer.h new file mode 100644 index 0000000..5e6a685 --- /dev/null +++ b/src/mcpack2pb/serializer.h @@ -0,0 +1,289 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// mcpack2pb - Make protobuf be front-end of mcpack/compack + +// Date: Mon Oct 19 17:17:36 CST 2015 + +#ifndef MCPACK2PB_MCPACK_SERIALIZER_H +#define MCPACK2PB_MCPACK_SERIALIZER_H + +#include +#include +#include +#include "butil/logging.h" +#include "butil/strings/string_piece.h" +#include "mcpack2pb/field_type.h" + +// CAUTION: Methods in this header is not intended to be public to users of +// brpc, and subject to change at any future time. + +namespace mcpack2pb { + +// Send bytes into ZeroCopyOutputStream +class OutputStream { +public: + class Area { + public: + Area() : _addr1(NULL) + , _addr2(NULL) + , _size1(0) + , _size2(0) + , _addional_area(NULL) {} + Area(const butil::LinkerInitialized&) {} + Area(const Area& rhs); + Area& operator=(const Area& rhs); + ~Area(); + void add(void* data, size_t n); + void assign(const void* data) const; + private: + void* _addr1; + void* _addr2; + unsigned _size1; + unsigned _size2; + std::vector* _addional_area; + }; + + // TODO(gejun): The zero-copy stream MUST return permanent memory blocks + // to support reserve(). E.g. StringOutputStream can't be used because + // the string inside invalidates previous memory blocks after resizing. + OutputStream(google::protobuf::io::ZeroCopyOutputStream* stream) + : _good(true) + , _fullsize(0) + , _size(0) + , _data(NULL) + , _zc_stream(stream) + , _pushed_bytes(0) + {} + + ~OutputStream() { done(); } + + // Append n bytes. + void append(const void* data, int n); + + // Append a pod. + template void append_packed_pod(const T& packed_pod); + + template T* append_packed_pod(); + + // Append a byte. + void push_back(char c); + + // If next n bytes in the zero-copy stream is continuous, consume it + // and return the begining address. NULL otherwise. + void* skip_continuous(int n); + + // Consume n bytes from the stream and return an object representing the + // consumed area. + Area reserve(int n); + + // Change data at the area. `data' must be as long as the reserved area. + void assign(const Area&, const void* data); + + // Go back for n bytes. + void backup(int n); + + // Returns bytes pushed and cut since creation of this stream. + size_t pushed_bytes() const { return _pushed_bytes; } + + // Returns false if error occurred during serialization. + bool good() { return _good; } + + void set_bad() { _good = false; } + + // Optionally called to backup buffered bytes to zero-copy stream. + void done(); + +private: + bool _good; + int _fullsize; + int _size; + void* _data; + google::protobuf::io::ZeroCopyOutputStream* _zc_stream; + size_t _pushed_bytes; +}; + +// This class is different from butil::StringPiece that it only can be +// constructed from std::string or const char* which are both ended with +// zero, so that in later serialization of the name, we can simply copy one +// extra byte to end the name with zero(required by compack) rather than +// appending the zero in a separate function call which is less efficient. +class StringWrapper { +public: + StringWrapper(const std::string& str) + : _data(str.c_str()), _size(str.size()) {} + StringWrapper(const char* str) { + if (str) { + _data = str; + _size = strlen(str); + } else { + _data = ""; + _size = 0; + } + } + ~StringWrapper() { } + const char* data() const { return _data; } + size_t size() const { return _size; } + bool empty() const { return !_size; } +private: + const char* _data; + size_t _size; +}; +inline std::ostream& operator<<(std::ostream& os, const StringWrapper& sw) { + return os << butil::StringPiece(sw.data(), sw.size()); +} + +class Serializer { +public: + // Serialize into `output'. + explicit Serializer(OutputStream* stream); + ~Serializer(); + + bool good() const { return _stream->good(); } + void set_bad() { _stream->set_bad(); } + size_t pushed_bytes() const { return _stream->pushed_bytes(); } + + // WARNING: Names to all methods cannot be longer that 254 bytes. + + // Append a primitive type with name. + // Used between begin_object() and end_object(). + void add_int8(const StringWrapper& name, int8_t value); + void add_int16(const StringWrapper& name, int16_t value); + void add_int32(const StringWrapper& name, int32_t value); + void add_int64(const StringWrapper& name, int64_t value); + void add_uint8(const StringWrapper& name, uint8_t value); + void add_uint16(const StringWrapper& name, uint16_t value); + void add_uint32(const StringWrapper& name, uint32_t value); + void add_uint64(const StringWrapper& name, uint64_t value); + void add_bool(const StringWrapper& name, bool value); + void add_float(const StringWrapper& name, float value); + void add_double(const StringWrapper& name, double value); + + // Add a primitive type without name. + // Used between begin_xxx_array() and end_xxx_array(). + void add_int8(int8_t value); + void add_int16(int16_t value); + void add_int32(int32_t value); + void add_int64(int64_t value); + void add_uint8(uint8_t value); + void add_uint16(uint16_t value); + void add_uint32(uint32_t value); + void add_uint64(uint64_t value); + void add_bool(bool value); + void add_float(float value); + void add_double(double value); + + // Add multiple primitive types in one call. + void add_multiple_int8(const int8_t* values, size_t count); + void add_multiple_int8(const uint8_t* values, size_t count); + void add_multiple_int16(const int16_t* values, size_t count); + void add_multiple_int16(const uint16_t* values, size_t count); + void add_multiple_int32(const int32_t* values, size_t count); + void add_multiple_int32(const uint32_t* values, size_t count); + void add_multiple_int64(const int64_t* values, size_t count); + void add_multiple_int64(const uint64_t* values, size_t count); + void add_multiple_uint8(const uint8_t* values, size_t count); + void add_multiple_uint8(const int8_t* values, size_t count); + void add_multiple_uint16(const uint16_t* values, size_t count); + void add_multiple_uint16(const int16_t* values, size_t count); + void add_multiple_uint32(const uint32_t* values, size_t count); + void add_multiple_uint32(const int32_t* values, size_t count); + void add_multiple_uint64(const uint64_t* values, size_t count); + void add_multiple_uint64(const int64_t* values, size_t count); + void add_multiple_bool(const bool* values, size_t count); + void add_multiple_float(const float* values, size_t count); + void add_multiple_double(const double* values, size_t count); + + // Append a string. + // The serialized value ends with 0 and the length counts 0. + void add_string(const StringWrapper& name, const StringWrapper& str); + void add_string(const StringWrapper& str); + + // Append binary data. + void add_binary(const StringWrapper& name, const std::string& data); + void add_binary(const StringWrapper& name, const void* data, size_t n); + void add_binary(const std::string& data); + void add_binary(const void* data, size_t n); + + // Append a null. + void add_null(const StringWrapper& name); + void add_null(); + + // Append an empty array. + void add_empty_array(const StringWrapper& name); + void add_empty_array(); + + // Begin/end an array. + // All items inside the array must be of same type. This is not a + // restriction of mcpack, but we want to align storage model with protobuf + // as much as possible. + // If too many levels of array/object reached, this function fails and + // leaves the output unchanged. + void begin_mcpack_array(const StringWrapper& name, FieldType item_type); + void begin_mcpack_array(FieldType item_type); + void begin_compack_array(const StringWrapper& name, FieldType item_type); + void begin_compack_array(FieldType item_type); + // End any kind of array. + void end_array(); + + // Begin/end an object. + // If too many levels of array/object reached, this function fails and + // leaves the output unchanged. + void begin_object(const StringWrapper& name); + void begin_object(); + void end_object(); + // TODO(gejun): move to begin_object + void end_object_iso(); + +public: + struct GroupInfo { + uint32_t item_count; + bool isomorphic; + uint8_t item_type; + uint8_t type; + uint8_t name_size; + size_t output_offset; + int pending_null_count; + OutputStream::Area head_area; + OutputStream::Area items_head_area; + + void print(std::ostream&) const; + }; + +private: + DISALLOW_COPY_AND_ASSIGN(Serializer); + + GroupInfo* push_group_info(); + GroupInfo & peek_group_info(); + void begin_object_internal(const StringWrapper& name); + void begin_object_internal(); + void end_object_internal(bool objectisoarray); + void begin_array_internal(FieldType item_type, bool compack); + void begin_array_internal(const StringWrapper& name, + FieldType item_type, bool compack); + + OutputStream* _stream; + int _ndepth; + GroupInfo _group_info_fast[15]; + GroupInfo *_group_info_more; +}; + +} // namespace mcpack2pb + +#include "mcpack2pb/serializer-inl.h" + +#endif // MCPACK2PB_MCPACK_SERIALIZER_H diff --git a/test/BUILD.bazel b/test/BUILD.bazel new file mode 100644 index 0000000..27d63ae --- /dev/null +++ b/test/BUILD.bazel @@ -0,0 +1,292 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") +load("//bazel/tools:brpc_proto_library.bzl", "brpc_proto_library") +load("@hedron_compile_commands//:refresh_compile_commands.bzl", "refresh_compile_commands") +load("//test:generate_unittests.bzl", "generate_unittests") +load("//test:root_runfiles.bzl", "root_runfiles") + +COPTS = [ + "-fPIC", + "-Wno-unused-parameter", + "-fno-omit-frame-pointer", + "-fno-access-control", +] + +TEST_BUTIL_SOURCES = [ + "at_exit_unittest.cc", + "atomicops_unittest.cc", + "base64_unittest.cc", + "base64url_unittest.cc", + "big_endian_unittest.cc", + "bits_unittest.cc", + "hash_tables_unittest.cc", + "linked_list_unittest.cc", + "mru_cache_unittest.cc", + "small_map_unittest.cc", + "stack_container_unittest.cc", + "mpsc_queue_unittest.cc", + "cpu_unittest.cc", + "crash_logging_unittest.cc", + "leak_tracker_unittest.cc", + "stack_trace_unittest.cc", + "environment_unittest.cc", + "file_util_unittest.cc", + "dir_reader_posix_unittest.cc", + "file_path_unittest.cc", + "file_unittest.cc", + "scoped_temp_dir_unittest.cc", + "guid_unittest.cc", + "hash_unittest.cc", + "lazy_instance_unittest.cc", + "aligned_memory_unittest.cc", + "linked_ptr_unittest.cc", + "ref_counted_memory_unittest.cc", + "ref_counted_unittest.cc", + "scoped_ptr_unittest.cc", + "scoped_vector_unittest.cc", + "singleton_unittest.cc", + "weak_ptr_unittest.cc", + "observer_list_unittest.cc", + "file_descriptor_shuffle_unittest.cc", + "rand_util_unittest.cc", + "safe_numerics_unittest.cc", + "scoped_clear_errno_unittest.cc", + "scoped_generic_unittest.cc", + "security_unittest.cc", + "sha1_unittest.cc", + "stl_util_unittest.cc", + "nullable_string16_unittest.cc", + "safe_sprintf_unittest.cc", + "string16_unittest.cc", + "stringprintf_unittest.cc", + "string_number_conversions_unittest.cc", + "string_piece_unittest.cc", + "string_split_unittest.cc", + "string_tokenizer_unittest.cc", + "string_util_unittest.cc", + "stringize_macros_unittest.cc", + "sys_string_conversions_unittest.cc", + "utf_offset_string_conversions_unittest.cc", + "utf_string_conversions_unittest.cc", + "cancellation_flag_unittest.cc", + "condition_variable_unittest.cc", + "lock_unittest.cc", + "waitable_event_unittest.cc", + "type_traits_unittest.cc", + "non_thread_safe_unittest.cc", + "platform_thread_unittest.cc", + "simple_thread_unittest.cc", + "thread_checker_unittest.cc", + "thread_collision_warner_unittest.cc", + "thread_id_name_manager_unittest.cc", + "thread_local_storage_unittest.cc", + "thread_local_unittest.cc", + "watchdog_unittest.cc", + "time_unittest.cc", + "version_unittest.cc", + "logging_unittest.cc", + "cacheline_unittest.cpp", + "class_name_unittest.cpp", + "endpoint_unittest.cpp", + "unique_ptr_unittest.cpp", + "errno_unittest.cpp", + "fd_guard_unittest.cpp", + "file_watcher_unittest.cpp", + "find_cstr_unittest.cpp", + "scoped_lock_unittest.cpp", + "status_unittest.cpp", + "string_printf_unittest.cpp", + "string_splitter_unittest.cpp", + "synchronous_event_unittest.cpp", + "temp_file_unittest.cpp", + "baidu_thread_local_unittest.cpp", + "thread_key_unittest.cpp", + "baidu_time_unittest.cpp", + "flat_map_unittest.cpp", + "crc32c_unittest.cc", + "iobuf_unittest.cpp", + "object_pool_unittest.cpp", + "test_switches.cc", + "scoped_locale.cc", + "recordio_unittest.cpp", + #"popen_unittest.cpp", + "bounded_queue_unittest.cc", + "butil_unittest_main.cpp", + "scope_guard_unittest.cpp", + "optional_unittest.cpp", +] + select({ + "@bazel_tools//tools/osx:darwin_x86_64": [], + "//conditions:default": [ + "test_file_util_linux.cc", + "proc_maps_linux_unittest.cc", + ], +}) + +brpc_proto_library( + name = "cc_test_proto", + srcs = glob(["*.proto"]), + deps = ["//:brpc_idl_options_cc_proto"], + visibility = ["//visibility:public"], +) + +cc_library( + name = "gperftools_helper", + hdrs = [ + "gperftools_helper.h", + ], +) + +# tcmalloc and AddressSanitizer both intercept malloc/free, so linking +# both produces undefined behaviour at runtime (typically a hard hang +# at process start, or random allocator errors). When `brpc_with_asan` +# is active we drop the @gperftools//:tcmalloc dep entirely; the +# gperftools_helper header above already no-ops gracefully when +# BRPC_ENABLE_CPU_PROFILER is not defined. +# +# Activation: `--config=asan` in .bazelrc also sets +# `--define=with_asan=true`, which flips this config_setting. +TCMALLOC_DEP_UNLESS_ASAN = select({ + "//bazel/config:brpc_with_asan": [], + "//conditions:default": ["@gperftools//:tcmalloc"], +}) + +cc_test( + name = "butil_unittests", + srcs = TEST_BUTIL_SOURCES + [ + "scoped_locale.h", + "multiprocess_func_list.h", + "test_switches.h", + ], + copts = COPTS, + defines = ["HAS_NLOHMANN_JSON=1"], + deps = [ + ":cc_test_proto", + ":gperftools_helper", + "//:butil", + "//:bthread", + "@nlohmann_json//:json", + "@com_google_googletest//:gtest", + ], +) + +cc_test( + name = "bvar_unittests", + srcs = glob(["bvar_*_unittest.cpp"]), + deps = [ + ":gperftools_helper", + "//:bvar", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ] + TCMALLOC_DEP_UNLESS_ASAN, + copts = COPTS, +) + +generate_unittests( + name = "bthread_unittests", + srcs = glob([ + "bthread*_unittest.cpp", + ]), + deps = [ + ":gperftools_helper", + "//:bthread", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ] + TCMALLOC_DEP_UNLESS_ASAN, + copts = COPTS, +) + +# Expose unit-test data files (cert*, jsonout) at the runfiles workspace root +# so that tests can open them via plain relative paths like "cert1.crt". +# See test/root_runfiles.bzl for why a genrule does NOT work here. +root_runfiles( + name = "test_runfiles_root_data", + srcs = [ + "cert1.crt", + "cert1.key", + "cert2.crt", + "cert2.key", + "jsonout", + ], +) + +generate_unittests( + name = "brpc_unittests", + srcs = glob([ + "brpc_*_unittest.cpp", + ]), + deps = [ + ":gperftools_helper", + "//:brpc", + ":cc_test_proto", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ] + TCMALLOC_DEP_UNLESS_ASAN, + copts = COPTS, + # Place cert*/jsonout at /_main/ (the cwd of `bazel test`) + # via :test_runfiles_root_data so test code can open them with plain + # relative paths like "cert1.crt". + data = [ + ":test_runfiles_root_data", + ], + # brpc_redis_unittest forks a real redis-server located via PATH. Tag it + # "external" so bazel always re-runs it (never serves a cached pass that + # actually skipped) and "local" so it runs outside the sandbox, where the + # apt-installed redis-server is visible and loopback works. The CI bazel + # jobs install redis-server via the install-essential-dependencies action. + # + # The mysql integration tests fork a real mysqld the same way (located via + # PATH, apt-installed by install-essential-dependencies); tag them the same + # so bazel never serves a cached pass that actually skipped and runs them + # outside the sandbox where mysqld and loopback are visible. + per_test_tags = { + "brpc_redis_unittest.cpp": ["external", "local"], + "brpc_mysql_auth_handshake_unittest.cpp": ["external", "local"], + "brpc_mysql_connection_type_unittest.cpp": ["external", "local"], + "brpc_mysql_prepared_integration_unittest.cpp": ["external", "local"], + "brpc_mysql_txn_integration_unittest.cpp": ["external", "local"], + "brpc_mysql_pool_concurrency_unittest.cpp": ["external", "local"], + }, + # brpc_channel_unittest packs dozens of timing-sensitive TEST_F (backup + # request, retry/backoff, timeouts) into one binary whose cumulative + # real-time waits exceed Bazel's default per-test 300s (size=medium) limit + # on contended CI runners, causing TIMEOUT failures. Raise its limit to + # size=large (900s). + # brpc_load_balancer_unittest.cpp also has the same problem. + # + # NB: do NOT shard this binary. Its TEST_F share fixed loopback endpoints + # and global state; running shards as parallel processes makes a + # "connection should be refused" test observe another shard's live server + # on the same port and fail. size=large is the only safe lever here. + per_test_size = { + "brpc_channel_unittest.cpp": "large", + "brpc_load_balancer_unittest.cpp": "large", + }, +) + +refresh_compile_commands( + name = "brpc_test_compdb", + # Specify the targets of interest. + # For example, specify a dict of targets and their arguments: + targets = { + "//:brpc": "", + ":butil_unittests": "", + ":bvar_unittests": "", + ":bthread_unittests": "", + ":brpc_unittests": "", + }, + # For more details, feel free to look into refresh_compile_commands.bzl if you want. +) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..6aebe27 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,309 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +find_package(Gperftools) + +add_library(brpc_test_config INTERFACE) +target_link_libraries(brpc_test_config INTERFACE brpc_common_config) +target_include_directories(brpc_test_config INTERFACE ${CMAKE_CURRENT_BINARY_DIR}) +target_compile_definitions(brpc_test_config INTERFACE + UNIT_TEST + BVAR_NOT_LINK_DEFAULT_VARIABLES +) +target_compile_options(brpc_test_config INTERFACE -fno-access-control) +if(GPERFTOOLS_INCLUDE_DIR) + target_include_directories(brpc_test_config INTERFACE ${GPERFTOOLS_INCLUDE_DIR}) +endif() + +include(CompileProto) +set(TEST_PROTO_FILES addressbook1.proto + addressbook_encode_decode.proto + addressbook_map.proto + addressbook.proto + echo.proto + iobuf.proto + message.proto + repeated.proto + snappy_message.proto + v1.proto + v2.proto + v3.proto + grpc.proto + health_check.proto) +file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/test/hdrs) +set(PROTOC_FLAGS ${PROTOC_FLAGS} -I${CMAKE_SOURCE_DIR}/src) +compile_proto(PROTO_HDRS PROTO_SRCS ${CMAKE_BINARY_DIR}/test + ${CMAKE_BINARY_DIR}/test/hdrs + ${CMAKE_SOURCE_DIR}/test + "${TEST_PROTO_FILES}") +add_library(TEST_PROTO_LIB OBJECT ${PROTO_SRCS} ${PROTO_HDRS}) +target_link_libraries(TEST_PROTO_LIB PRIVATE brpc_test_config) + +set(BRPC_SYSTEM_GTEST_SOURCE_DIR "" CACHE PATH "System googletest source directory.") + +if(DOWNLOAD_GTEST) + include(SetupGtest) + set(BRPC_DOWNLOADED_GTEST_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/downloaded-googletest-include) + file(REMOVE_RECURSE ${BRPC_DOWNLOADED_GTEST_INCLUDE_DIR}) + file(MAKE_DIRECTORY ${BRPC_DOWNLOADED_GTEST_INCLUDE_DIR}) + file(COPY + ${PROJECT_BINARY_DIR}/googletest-src/googletest/include/ + ${PROJECT_BINARY_DIR}/googletest-src/googlemock/include/ + DESTINATION ${BRPC_DOWNLOADED_GTEST_INCLUDE_DIR}) + target_include_directories(brpc_test_config BEFORE INTERFACE + ${BRPC_DOWNLOADED_GTEST_INCLUDE_DIR}) +elseif(BRPC_SYSTEM_GTEST_SOURCE_DIR) + add_subdirectory("${BRPC_SYSTEM_GTEST_SOURCE_DIR}" "${PROJECT_BINARY_DIR}/system-googletest-build") +else() + message(FATAL_ERROR "Googletest is not available") +endif() + +file(COPY ${PROJECT_SOURCE_DIR}/test/cert1.key DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +file(COPY ${PROJECT_SOURCE_DIR}/test/cert2.key DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +file(COPY ${PROJECT_SOURCE_DIR}/test/cert1.crt DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +file(COPY ${PROJECT_SOURCE_DIR}/test/cert2.crt DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +file(COPY ${PROJECT_SOURCE_DIR}/test/jsonout DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +file(COPY ${PROJECT_SOURCE_DIR}/test/run_tests.sh DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + +SET(TEST_BUTIL_SOURCES + ${PROJECT_SOURCE_DIR}/test/recordio_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/popen_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/bounded_queue_unittest.cc + ${PROJECT_SOURCE_DIR}/test/at_exit_unittest.cc + ${PROJECT_SOURCE_DIR}/test/atomicops_unittest.cc + ${PROJECT_SOURCE_DIR}/test/base64_unittest.cc + ${PROJECT_SOURCE_DIR}/test/base64url_unittest.cc + ${PROJECT_SOURCE_DIR}/test/big_endian_unittest.cc + ${PROJECT_SOURCE_DIR}/test/bits_unittest.cc + ${PROJECT_SOURCE_DIR}/test/hash_tables_unittest.cc + ${PROJECT_SOURCE_DIR}/test/linked_list_unittest.cc + ${PROJECT_SOURCE_DIR}/test/mru_cache_unittest.cc + ${PROJECT_SOURCE_DIR}/test/small_map_unittest.cc + ${PROJECT_SOURCE_DIR}/test/stack_container_unittest.cc + ${PROJECT_SOURCE_DIR}/test/mpsc_queue_unittest.cc + ${PROJECT_SOURCE_DIR}/test/cpu_unittest.cc + ${PROJECT_SOURCE_DIR}/test/crash_logging_unittest.cc + ${PROJECT_SOURCE_DIR}/test/leak_tracker_unittest.cc + ${PROJECT_SOURCE_DIR}/test/stack_trace_unittest.cc + ${PROJECT_SOURCE_DIR}/test/environment_unittest.cc + ${PROJECT_SOURCE_DIR}/test/file_util_unittest.cc + ${PROJECT_SOURCE_DIR}/test/dir_reader_posix_unittest.cc + ${PROJECT_SOURCE_DIR}/test/file_path_unittest.cc + ${PROJECT_SOURCE_DIR}/test/file_unittest.cc + ${PROJECT_SOURCE_DIR}/test/scoped_temp_dir_unittest.cc + ${PROJECT_SOURCE_DIR}/test/guid_unittest.cc + ${PROJECT_SOURCE_DIR}/test/hash_unittest.cc + ${PROJECT_SOURCE_DIR}/test/lazy_instance_unittest.cc + ${PROJECT_SOURCE_DIR}/test/aligned_memory_unittest.cc + ${PROJECT_SOURCE_DIR}/test/linked_ptr_unittest.cc + ${PROJECT_SOURCE_DIR}/test/ref_counted_memory_unittest.cc + ${PROJECT_SOURCE_DIR}/test/ref_counted_unittest.cc + ${PROJECT_SOURCE_DIR}/test/scoped_ptr_unittest.cc + ${PROJECT_SOURCE_DIR}/test/scoped_vector_unittest.cc + ${PROJECT_SOURCE_DIR}/test/singleton_unittest.cc + ${PROJECT_SOURCE_DIR}/test/weak_ptr_unittest.cc + ${PROJECT_SOURCE_DIR}/test/observer_list_unittest.cc + ${PROJECT_SOURCE_DIR}/test/file_descriptor_shuffle_unittest.cc + ${PROJECT_SOURCE_DIR}/test/rand_util_unittest.cc + ${PROJECT_SOURCE_DIR}/test/safe_numerics_unittest.cc + ${PROJECT_SOURCE_DIR}/test/scoped_clear_errno_unittest.cc + ${PROJECT_SOURCE_DIR}/test/scoped_generic_unittest.cc + ${PROJECT_SOURCE_DIR}/test/security_unittest.cc + ${PROJECT_SOURCE_DIR}/test/sha1_unittest.cc + ${PROJECT_SOURCE_DIR}/test/stl_util_unittest.cc + ${PROJECT_SOURCE_DIR}/test/nullable_string16_unittest.cc + ${PROJECT_SOURCE_DIR}/test/safe_sprintf_unittest.cc + ${PROJECT_SOURCE_DIR}/test/string16_unittest.cc + ${PROJECT_SOURCE_DIR}/test/stringprintf_unittest.cc + ${PROJECT_SOURCE_DIR}/test/string_number_conversions_unittest.cc + ${PROJECT_SOURCE_DIR}/test/string_piece_unittest.cc + ${PROJECT_SOURCE_DIR}/test/string_split_unittest.cc + ${PROJECT_SOURCE_DIR}/test/string_tokenizer_unittest.cc + ${PROJECT_SOURCE_DIR}/test/string_util_unittest.cc + ${PROJECT_SOURCE_DIR}/test/stringize_macros_unittest.cc + ${PROJECT_SOURCE_DIR}/test/sys_string_conversions_unittest.cc + ${PROJECT_SOURCE_DIR}/test/utf_offset_string_conversions_unittest.cc + ${PROJECT_SOURCE_DIR}/test/utf_string_conversions_unittest.cc + ${PROJECT_SOURCE_DIR}/test/cancellation_flag_unittest.cc + ${PROJECT_SOURCE_DIR}/test/condition_variable_unittest.cc + ${PROJECT_SOURCE_DIR}/test/lock_unittest.cc + ${PROJECT_SOURCE_DIR}/test/waitable_event_unittest.cc + ${PROJECT_SOURCE_DIR}/test/type_traits_unittest.cc + ${PROJECT_SOURCE_DIR}/test/non_thread_safe_unittest.cc + ${PROJECT_SOURCE_DIR}/test/platform_thread_unittest.cc + ${PROJECT_SOURCE_DIR}/test/simple_thread_unittest.cc + ${PROJECT_SOURCE_DIR}/test/thread_checker_unittest.cc + ${PROJECT_SOURCE_DIR}/test/thread_collision_warner_unittest.cc + ${PROJECT_SOURCE_DIR}/test/thread_id_name_manager_unittest.cc + ${PROJECT_SOURCE_DIR}/test/thread_local_storage_unittest.cc + ${PROJECT_SOURCE_DIR}/test/thread_local_unittest.cc + ${PROJECT_SOURCE_DIR}/test/abalist_unittest.cc + ${PROJECT_SOURCE_DIR}/test/watchdog_unittest.cc + ${PROJECT_SOURCE_DIR}/test/time_unittest.cc + ${PROJECT_SOURCE_DIR}/test/version_unittest.cc + ${PROJECT_SOURCE_DIR}/test/logging_unittest.cc + ${PROJECT_SOURCE_DIR}/test/cacheline_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/class_name_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/endpoint_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/unique_ptr_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/errno_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/fd_guard_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/file_watcher_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/find_cstr_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/scoped_lock_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/status_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/string_printf_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/string_splitter_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/synchronous_event_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/temp_file_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/baidu_thread_local_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/thread_key_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/baidu_time_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/flat_map_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/crc32c_unittest.cc + ${PROJECT_SOURCE_DIR}/test/iobuf_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/object_pool_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/test_switches.cc + ${PROJECT_SOURCE_DIR}/test/scoped_locale.cc + ${PROJECT_SOURCE_DIR}/test/scope_guard_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/optional_unittest.cpp + ${PROJECT_SOURCE_DIR}/test/butil_unittest_main.cpp + ) + +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") +SET(TEST_BUTIL_SOURCES ${TEST_BUTIL_SOURCES} + ${PROJECT_SOURCE_DIR}/test/proc_maps_linux_unittest.cc + ${PROJECT_SOURCE_DIR}/test/test_file_util_linux.cc) +endif() + +# bthread_* functions are used in logging.cc, and they need to be marked as +# weak symbols explicitly in Darwin system. +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(DYNAMIC_LIB ${DYNAMIC_LIB} "-Wl,-U,_bthread_getspecific" + "-Wl,-U,_bthread_setspecific" + "-Wl,-U,_bthread_key_create" + "-Wl,-U,_bthread_self" + "-Wl,-U,_bthread_timed_connect" + ) +endif() + +add_library(BUTIL_DEBUG_LIB OBJECT ${BUTIL_SOURCES}) +add_library(SOURCES_DEBUG_LIB OBJECT ${SOURCES}) +add_dependencies(SOURCES_DEBUG_LIB PROTO_LIB) +target_link_libraries(BUTIL_DEBUG_LIB PRIVATE brpc_test_config) +target_link_libraries(SOURCES_DEBUG_LIB PRIVATE brpc_test_config) + +# shared library needs POSITION_INDEPENDENT_CODE +set_property(TARGET ${BUTIL_DEBUG_LIB} PROPERTY POSITION_INDEPENDENT_CODE 1) +set_property(TARGET ${SOURCES_DEBUG_LIB} PROPERTY POSITION_INDEPENDENT_CODE 1) + +add_library(brpc-shared-debug SHARED $ + $ + $) +# change the debug lib output dir to be different from the release output +set_target_properties(brpc-shared-debug PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/test) + +target_link_libraries(brpc-shared-debug PUBLIC brpc_test_config ${DYNAMIC_LIB}) +if(BRPC_WITH_GLOG) + target_link_libraries(brpc-shared-debug PUBLIC ${GLOG_LIB}) +endif() + +# AddressSanitizer is incompatible with tcmalloc/gperftools, do not link it when WITH_ASAN is on +if(WITH_ASAN) + set(GPERFTOOLS_LIBRARIES "") +endif() + +# test_butil +add_executable(test_butil ${TEST_BUTIL_SOURCES} + ${CMAKE_CURRENT_BINARY_DIR}/iobuf.pb.cc) +target_link_libraries(test_butil PRIVATE + brpc-shared-debug + gtest + ${GPERFTOOLS_LIBRARIES}) + +add_test(NAME test_butil COMMAND test_butil) + +# test_bvar +# -DBVAR_NOT_LINK_DEFAULT_VARIABLES not work for gcc >= 5.0, just remove the file to prevent linking into unit tests +list(REMOVE_ITEM BVAR_SOURCES ${PROJECT_SOURCE_DIR}/src/bvar/default_variables.cpp) + +add_library(BVAR_DEBUG_LIB OBJECT ${BVAR_SOURCES}) +target_link_libraries(BVAR_DEBUG_LIB PRIVATE brpc_test_config) +file(GLOB TEST_BVAR_SRCS "bvar_*_unittest.cpp") +add_executable(test_bvar ${TEST_BVAR_SRCS} + $ + $) +target_link_libraries(test_bvar PRIVATE + gtest + brpc_test_config + ${GPERFTOOLS_LIBRARIES} + ${DYNAMIC_LIB}) +add_test(NAME test_bvar COMMAND test_bvar) + +# bthread tests +file(GLOB BTHREAD_UNITTESTS "bthread*unittest.cpp") +foreach(BTHREAD_UT ${BTHREAD_UNITTESTS}) + get_filename_component(BTHREAD_UT_WE ${BTHREAD_UT} NAME_WE) + add_executable(${BTHREAD_UT_WE} ${BTHREAD_UT}) + target_link_libraries(${BTHREAD_UT_WE} PRIVATE + gtest_main + brpc-shared-debug + ${GPERFTOOLS_LIBRARIES}) + add_test(NAME ${BTHREAD_UT_WE} COMMAND ${BTHREAD_UT_WE}) +endforeach() + +# brpc tests +file(GLOB BRPC_UNITTESTS "brpc_*_unittest.cpp") +foreach(BRPC_UT ${BRPC_UNITTESTS}) + get_filename_component(BRPC_UT_WE ${BRPC_UT} NAME_WE) + add_executable(${BRPC_UT_WE} ${BRPC_UT} $) + target_link_libraries(${BRPC_UT_WE} PRIVATE + brpc-shared-debug + gtest_main + brpc_test_config + ${GPERFTOOLS_LIBRARIES}) + add_test(NAME ${BRPC_UT_WE} COMMAND ${BRPC_UT_WE}) +endforeach() + +if(BUILD_FUZZ_TESTS) + add_library(brpc-static-debug STATIC $ + $ + $) + # change the debug lib output dir to be different from the release output + set_target_properties(brpc-static-debug PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/test) + + target_link_libraries(brpc-static-debug PUBLIC brpc_test_config ${DYNAMIC_LIB}) + if(BRPC_WITH_GLOG) + target_link_libraries(brpc-static-debug PUBLIC ${GLOG_LIB}) + endif() + + set(FUZZ_TARGETS fuzz_butil fuzz_esp fuzz_hpack fuzz_http + fuzz_hulu fuzz_json fuzz_redis fuzz_shead fuzz_sofa fuzz_uri + fuzz_baidu_rpc fuzz_mongo fuzz_memcache + fuzz_couchbase fuzz_streaming fuzz_http_parser fuzz_amf) + + + foreach(target ${FUZZ_TARGETS}) + add_executable(${target} fuzzing/${target}.cpp $) + target_link_libraries(${target} PRIVATE brpc-static-debug ${LIB_FUZZING_ENGINE}) + set_target_properties(${target} PROPERTIES + BUILD_WITH_INSTALL_RPATH TRUE + INSTALL_RPATH "$ORIGIN/lib") + endforeach() +endif() diff --git a/test/README_mysql_auth.md b/test/README_mysql_auth.md new file mode 100644 index 0000000..fc61323 --- /dev/null +++ b/test/README_mysql_auth.md @@ -0,0 +1,92 @@ +# MySQL auth handshake — end-to-end test plan + +The server integration tests in `brpc_mysql_auth_handshake_unittest.cpp` +(`MysqlHandshakeServerTest.*`) run in one of two modes, selected by the +`-mysql_use_running_server` gflag. + +There are four server tests: + +| Test | What it checks | +|---|---| +| `ParsesRealServerGreeting` | HandshakeV10 parse of a real greeting | +| `GeneratesScramblesFromRealSalt` | scramble from a real salt, parameterized on password length (zero → empty response; non-zero → 20B native / 32B caching_sha2) | +| `PerformsFullAuthentication` | uncached login takes the **full-auth** path; asserts the response carries `AuthMoreData 0x04` (perform_full_authentication) and the RSA exchange yields `OK` | +| `CachesCredentialOnSecondLogin` | logs in twice; the **second** login must reuse the cache (fast-auth), never `0x04` | + +## Mode 1 — self-spawned server (default; CI) + +When `-mysql_use_running_server` is **not** set, the fixture brings up its +own throwaway `mysqld` (the `which`-then-spawn pattern from +`brpc_redis_unittest.cpp`) with an empty-password root, and tears it down +on exit. `caching_sha2_password` then completes via its empty-password +fast path. `PerformsFullAuthentication` skips here (an empty password +never triggers full auth); the other three run. Tests self-skip entirely +when `mysqld` is absent. + +```sh +cd test && ./brpc_mysql_auth_handshake_unittest +``` + +## Mode 2 — already-running server (recommended for development & future CLs) + +You start a `mysqld` yourself, with verbose logging so you can watch the +handshake, and point the tests at it with flags. The test neither starts +nor stops it. Reuse this workflow as more of the MySQL protocol lands +(text protocol, prepared statements, transactions). + +### 1. Initialize a data directory (one time per fresh instance) + +```sh +export MYSQL_DATA=/tmp/brpc_mysql_e2e +export MYSQL_PORT=13306 +rm -rf "$MYSQL_DATA" && mkdir -p "$MYSQL_DATA" +mysqld --initialize-insecure --datadir="$MYSQL_DATA" --log-error="$MYSQL_DATA/init.err" +``` + +### 2. Start the server in your terminal (verbose, foreground) + +```sh +mysqld --datadir="$MYSQL_DATA" --port="$MYSQL_PORT" \ + --socket="$MYSQL_DATA/mysqld.sock" --bind-address=127.0.0.1 \ + --mysqlx=OFF --log-error-verbosity=3 \ + --general-log=1 --general-log-file="$MYSQL_DATA/general.log" +``` + +### 3. Create the `root` / `root` account reachable over TCP + +```sh +mysql --socket="$MYSQL_DATA/mysqld.sock" -u root <<'SQL' +ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'root'; +CREATE USER IF NOT EXISTS 'root'@'%' IDENTIFIED WITH caching_sha2_password BY 'root'; +GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION; +DELETE FROM mysql.user WHERE user=''; +FLUSH PRIVILEGES; +SQL +``` + +### 4. Run the tests against that server + +```sh +cd test && ./brpc_mysql_auth_handshake_unittest \ + -mysql_use_running_server \ + -mysql_host=127.0.0.1 -mysql_port=13306 \ + -mysql_user=root -mysql_password=root +``` + +`PerformsFullAuthentication` requires a **cold** caching_sha2 cache — i.e. +a credential that has not authenticated since the server started. It is +the first authenticating test, so against a **freshly started** server it +sees the full-auth path. If you re-run without restarting the server, the +credential is already cached and that test will report fast-auth; restart +the server (or use a never-authenticated account) to exercise full auth +again. + +## Flags + +| Flag | Default | Meaning | +|---|---|---| +| `-mysql_use_running_server` | `false` | `true` → use an already-running server (no spawn/teardown); `false` → self-spawn | +| `-mysql_host` | `127.0.0.1` | running-server host | +| `-mysql_port` | `13306` | server TCP port (running server, and the port the spawned server binds) | +| `-mysql_user` | `root` | login user | +| `-mysql_password` | (empty) | login password | diff --git a/test/abalist_unittest.cc b/test/abalist_unittest.cc new file mode 100644 index 0000000..cdbbda2 --- /dev/null +++ b/test/abalist_unittest.cc @@ -0,0 +1,46 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include "bthread/list_of_abafree_id.h" + +namespace bthread { +// define TestId +struct TestIdTraits { + static const size_t BLOCK_SIZE = 16; + static const size_t MAX_ENTRIES = 1000; + static const size_t INIT_GC_SIZE = 100; + static const int ID_INIT = 0xFF; + static std::unordered_set id_set; + static bool exists(int id) { return id_set.count(id) > 0; } +}; + +std::unordered_set TestIdTraits::id_set; + +TEST(AbaListTest, TestGc) { + ListOfABAFreeId aba_list; + + size_t wait_seq = 0; + for (size_t i = 0; i < 1000000; i++) { + size_t cnts[1]; + aba_list.get_sizes(cnts, 1); + if (wait_seq > cnts[0] + 1) { + wait_seq = 0; + TestIdTraits::id_set.clear(); + } + EXPECT_EQ(aba_list.add(i), 0); + if (TestIdTraits::id_set.size() < 4) { + TestIdTraits::id_set.insert(i); + } else { + wait_seq++; + } + } + size_t cnts[1]; + aba_list.get_sizes(cnts, 1); + EXPECT_EQ(cnts[0], (size_t)96); +} +} // namespace bthread \ No newline at end of file diff --git a/test/addressbook.proto b/test/addressbook.proto new file mode 100644 index 0000000..068334f --- /dev/null +++ b/test/addressbook.proto @@ -0,0 +1,78 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; + +package addressbook; +message Person { + required string name = 1; + required int32 id = 2; // Unique ID number for this person. + optional string email = 3; + + enum PhoneType { + MOBILE = 0; + HOME = 1; + WORK = 2; + } + + message PhoneNumber { + required string number = 1; + optional PhoneType type = 2 [default = HOME]; + } + + repeated PhoneNumber phone = 4; + + optional int64 data = 5; + + optional sint32 data32 = 6; + + optional sint64 data64 = 7; + + required double datadouble = 8; + + optional float datafloat = 9; + + optional uint32 datau32 = 10; + + optional uint64 datau64 = 11; + + optional bool databool = 12; + + optional bytes databyte = 13; + + optional fixed32 datafix32 = 14; + + optional fixed64 datafix64 = 15; + + optional sfixed32 datasfix32 = 16; + + optional sfixed64 datasfix64 = 17; + + optional float datafloat_scientific = 18; + + optional double datadouble_scientific = 19; + + extensions 100 to 200; +} + +extend Person { + optional string hobby = 100; +} + +message AddressBook { + repeated Person person = 1; +} diff --git a/test/addressbook1.proto b/test/addressbook1.proto new file mode 100644 index 0000000..e813bdc --- /dev/null +++ b/test/addressbook1.proto @@ -0,0 +1,56 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +message Ext { + optional fixed32 age = 2; + required bytes databyte = 3; + enum PhoneType { + MOBILE = 0; + HOME = 1; + WORK = 2; + } + + optional PhoneType enumtype = 4 [default = HOME]; +} + +message Content { + optional string uid = 1; + required float distance = 16; + optional Ext ext = 17; +} + +message JsonContextBody { + optional int64 type = 1; + repeated int32 data = 5; + repeated string info = 6; + required bool judge = 2; + required double spur = 3; + repeated Content content = 4; + optional float text = 7; +} + + +message PersonInfo { + optional string name = 1; + optional int32 id = 2; // Unique ID number for this person. + optional JsonContextBody json_body = 18; +} + +message AddressBook { + repeated PersonInfo person = 1; +} diff --git a/test/addressbook_encode_decode.proto b/test/addressbook_encode_decode.proto new file mode 100644 index 0000000..cd18d94 --- /dev/null +++ b/test/addressbook_encode_decode.proto @@ -0,0 +1,55 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +message ExtEncDec { + optional fixed32 Aa_ge_Z040_ = 2; + optional bytes databyte_Z040_std_Z058__Z058_string_Z041_ = 3; + enum PhoneTypeEncDec { + MOBILE = 0; + HOME = 1; + WORK = 2; + } + + optional PhoneTypeEncDec enum_Z045__Z045_type = 4 [default = HOME]; +} + +message ContentEncDec { + optional string uid_Z042_ = 1; + required float Distance_info_ = 16; + optional ExtEncDec _ext_Z037_T_ = 17; +} + +message JsonContextBodyEncDec { + repeated string info = 6; + optional int64 type = 1; + repeated int32 data_Z058_array = 5; + required bool judge = 2; + optional double spur = 3; + repeated ContentEncDec _Z064_Content_Test_Z037__Z064_ = 4; +} + + +message PersonEncDec { + optional string name = 1; + optional int32 id = 2; // Unique ID number for this person. + optional JsonContextBodyEncDec json_body = 18; +} + +message AddressBookEncDec { + repeated PersonEncDec person = 1; +} diff --git a/test/addressbook_map.proto b/test/addressbook_map.proto new file mode 100644 index 0000000..4d18e68 --- /dev/null +++ b/test/addressbook_map.proto @@ -0,0 +1,67 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +message AddressNoMap { + required string addr = 1; +} + +message AddressIntMap { + required string addr = 1; + message MapFieldEntry { + required string key = 1; + required int32 value = 2; + } + repeated MapFieldEntry numbers = 2; +} + +message AddressStringMap { + required string addr = 1; + message MapFieldEntry { + required string key = 1; + required string value = 2; + } + repeated MapFieldEntry contacts = 2; +} + +message AddressComplex { + required string addr = 1; + + message FriendEntry { + required string key = 1; + message Education { + required string school = 1; + required int32 year = 2; + } + repeated Education value = 2; + } + repeated FriendEntry friends = 2; +} + +message AddressIntMapStd { + required string addr = 1; + map numbers = 2; +} + +message AddressStringMapStd { + required string addr = 1; + map contacts = 2; +} + +message haha { + repeated int32 a = 1; +} \ No newline at end of file diff --git a/test/aligned_memory_unittest.cc b/test/aligned_memory_unittest.cc new file mode 100644 index 0000000..8c278a7 --- /dev/null +++ b/test/aligned_memory_unittest.cc @@ -0,0 +1,110 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/memory/aligned_memory.h" +#include "butil/memory/scoped_ptr.h" +#include + +#define EXPECT_ALIGNED(ptr, align) \ + EXPECT_EQ(0u, reinterpret_cast(ptr) & (align - 1)) + +namespace { + +using butil::AlignedMemory; + +TEST(AlignedMemoryTest, StaticAlignment) { + static AlignedMemory<8, 8> raw8; + static AlignedMemory<8, 16> raw16; + static AlignedMemory<8, 256> raw256; + static AlignedMemory<8, 4096> raw4096; + + EXPECT_EQ(8u, ALIGNOF(raw8)); + EXPECT_EQ(16u, ALIGNOF(raw16)); + EXPECT_EQ(256u, ALIGNOF(raw256)); + EXPECT_EQ(4096u, ALIGNOF(raw4096)); + + EXPECT_ALIGNED(raw8.void_data(), 8); + EXPECT_ALIGNED(raw16.void_data(), 16); + EXPECT_ALIGNED(raw256.void_data(), 256); + EXPECT_ALIGNED(raw4096.void_data(), 4096); +} + +// stack alignment is buggy before gcc 4.6 +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=16660 +#if defined(COMPILER_GCC) && \ + ( __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +#define GOOD_GCC_STACK_ALIGNMENT +#endif + +TEST(AlignedMemoryTest, StackAlignment) { + AlignedMemory<8, 8> raw8; + AlignedMemory<8, 16> raw16; + AlignedMemory<8, 128> raw128; + + EXPECT_EQ(8u, ALIGNOF(raw8)); + EXPECT_EQ(16u, ALIGNOF(raw16)); + EXPECT_EQ(128u, ALIGNOF(raw128)); + + EXPECT_ALIGNED(raw8.void_data(), 8); + EXPECT_ALIGNED(raw16.void_data(), 16); + + // TODO(ios): __attribute__((aligned(X))) with X >= 128 does not works on + // the stack when building for arm64 on iOS, http://crbug.com/349003 +#if !(defined(OS_IOS) && defined(ARCH_CPU_ARM64)) && \ + defined(GOOD_GCC_STACK_ALIGNMENT) + EXPECT_ALIGNED(raw128.void_data(), 128); + + // NaCl x86-64 compiler emits non-validating instructions for >128 + // bytes alignment. + // http://www.chromium.org/nativeclient/design-documents/nacl-sfi-model-on-x86-64-systems + // TODO(hamaji): Ideally, NaCl compiler for x86-64 should workaround + // this limitation and this #if should be removed. + // https://code.google.com/p/nativeclient/issues/detail?id=3463 +#if !(defined(OS_NACL) && defined(ARCH_CPU_X86_64)) && \ + defined(GOOD_GCC_STACK_ALIGNMENT) + AlignedMemory<8, 256> raw256; + EXPECT_EQ(256u, ALIGNOF(raw256)); + EXPECT_ALIGNED(raw256.void_data(), 256); + + // TODO(ios): This test hits an armv7 bug in clang. crbug.com/138066 +#if !(defined(OS_IOS) && defined(ARCH_CPU_ARM_FAMILY)) && \ + defined(GOOD_GCC_STACK_ALIGNMENT) + AlignedMemory<8, 4096> raw4096; + EXPECT_EQ(4096u, ALIGNOF(raw4096)); + EXPECT_ALIGNED(raw4096.void_data(), 4096); +#endif // !(defined(OS_IOS) && defined(ARCH_CPU_ARM_FAMILY)) +#endif // !(defined(OS_NACL) && defined(ARCH_CPU_X86_64)) +#endif // !(defined(OS_IOS) && defined(ARCH_CPU_ARM64)) +} + +TEST(AlignedMemoryTest, DynamicAllocation) { + void* p = butil::AlignedAlloc(8, 8); + EXPECT_TRUE(p); + EXPECT_ALIGNED(p, 8); + butil::AlignedFree(p); + + p = butil::AlignedAlloc(8, 16); + EXPECT_TRUE(p); + EXPECT_ALIGNED(p, 16); + butil::AlignedFree(p); + + p = butil::AlignedAlloc(8, 256); + EXPECT_TRUE(p); + EXPECT_ALIGNED(p, 256); + butil::AlignedFree(p); + + p = butil::AlignedAlloc(8, 4096); + EXPECT_TRUE(p); + EXPECT_ALIGNED(p, 4096); + butil::AlignedFree(p); +} + +TEST(AlignedMemoryTest, ScopedDynamicAllocation) { + scoped_ptr p( + static_cast(butil::AlignedAlloc(8, 8))); + EXPECT_TRUE(p.get()); + EXPECT_ALIGNED(p.get(), 8); +} + +} // namespace diff --git a/test/allocator_unittest.cc b/test/allocator_unittest.cc new file mode 100644 index 0000000..dd4fe6d --- /dev/null +++ b/test/allocator_unittest.cc @@ -0,0 +1,515 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include // for min() + +#include "butil/atomicops.h" +#include + +// Number of bits in a size_t. +static const int kSizeBits = 8 * sizeof(size_t); +// The maximum size of a size_t. +static const size_t kMaxSize = ~static_cast(0); +// Maximum positive size of a size_t if it were signed. +static const size_t kMaxSignedSize = ((size_t(1) << (kSizeBits-1)) - 1); +// An allocation size which is not too big to be reasonable. +static const size_t kNotTooBig = 100000; +// An allocation size which is just too big. +static const size_t kTooBig = ~static_cast(0); + +namespace { + +using std::min; + +// Fill a buffer of the specified size with a predetermined pattern +static void Fill(unsigned char* buffer, int n) { + for (int i = 0; i < n; i++) { + buffer[i] = (i & 0xff); + } +} + +// Check that the specified buffer has the predetermined pattern +// generated by Fill() +static bool Valid(unsigned char* buffer, int n) { + for (int i = 0; i < n; i++) { + if (buffer[i] != (i & 0xff)) { + return false; + } + } + return true; +} + +// Check that a buffer is completely zeroed. +static bool ALLOW_UNUSED IsZeroed(unsigned char* buffer, int n) { + for (int i = 0; i < n; i++) { + if (buffer[i] != 0) { + return false; + } + } + return true; +} + +// Check alignment +static void CheckAlignment(void* p, int align) { + EXPECT_EQ(0, reinterpret_cast(p) & (align-1)); +} + +// Return the next interesting size/delta to check. Returns -1 if no more. +static int NextSize(int size) { + if (size < 100) + return size+1; + + if (size < 100000) { + // Find next power of two + int power = 1; + while (power < size) + power <<= 1; + + // Yield (power-1, power, power+1) + if (size < power-1) + return power-1; + + if (size == power-1) + return power; + + assert(size == power); + return power+1; + } else { + return -1; + } +} + +template +static void TestAtomicIncrement() { + // For now, we just test single threaded execution + + // use a guard value to make sure the NoBarrier_AtomicIncrement doesn't go + // outside the expected address bounds. This is in particular to + // test that some future change to the asm code doesn't cause the + // 32-bit NoBarrier_AtomicIncrement to do the wrong thing on 64-bit machines. + struct { + AtomicType prev_word; + AtomicType count; + AtomicType next_word; + } s; + + AtomicType prev_word_value, next_word_value; + memset(&prev_word_value, 0xFF, sizeof(AtomicType)); + memset(&next_word_value, 0xEE, sizeof(AtomicType)); + + s.prev_word = prev_word_value; + s.count = 0; + s.next_word = next_word_value; + + EXPECT_EQ(butil::subtle::NoBarrier_AtomicIncrement(&s.count, 1), 1); + EXPECT_EQ(s.count, 1); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(butil::subtle::NoBarrier_AtomicIncrement(&s.count, 2), 3); + EXPECT_EQ(s.count, 3); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(butil::subtle::NoBarrier_AtomicIncrement(&s.count, 3), 6); + EXPECT_EQ(s.count, 6); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(butil::subtle::NoBarrier_AtomicIncrement(&s.count, -3), 3); + EXPECT_EQ(s.count, 3); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(butil::subtle::NoBarrier_AtomicIncrement(&s.count, -2), 1); + EXPECT_EQ(s.count, 1); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(butil::subtle::NoBarrier_AtomicIncrement(&s.count, -1), 0); + EXPECT_EQ(s.count, 0); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(butil::subtle::NoBarrier_AtomicIncrement(&s.count, -1), -1); + EXPECT_EQ(s.count, -1); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(butil::subtle::NoBarrier_AtomicIncrement(&s.count, -4), -5); + EXPECT_EQ(s.count, -5); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(butil::subtle::NoBarrier_AtomicIncrement(&s.count, 5), 0); + EXPECT_EQ(s.count, 0); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); +} + + +#define NUM_BITS(T) (sizeof(T) * 8) + + +template +static void TestCompareAndSwap() { + AtomicType value = 0; + AtomicType prev = butil::subtle::NoBarrier_CompareAndSwap(&value, 0, 1); + EXPECT_EQ(1, value); + EXPECT_EQ(0, prev); + + // Use test value that has non-zero bits in both halves, more for testing + // 64-bit implementation on 32-bit platforms. + const AtomicType k_test_val = (static_cast(1) << + (NUM_BITS(AtomicType) - 2)) + 11; + value = k_test_val; + prev = butil::subtle::NoBarrier_CompareAndSwap(&value, 0, 5); + EXPECT_EQ(k_test_val, value); + EXPECT_EQ(k_test_val, prev); + + value = k_test_val; + prev = butil::subtle::NoBarrier_CompareAndSwap(&value, k_test_val, 5); + EXPECT_EQ(5, value); + EXPECT_EQ(k_test_val, prev); +} + + +template +static void TestAtomicExchange() { + AtomicType value = 0; + AtomicType new_value = butil::subtle::NoBarrier_AtomicExchange(&value, 1); + EXPECT_EQ(1, value); + EXPECT_EQ(0, new_value); + + // Use test value that has non-zero bits in both halves, more for testing + // 64-bit implementation on 32-bit platforms. + const AtomicType k_test_val = (static_cast(1) << + (NUM_BITS(AtomicType) - 2)) + 11; + value = k_test_val; + new_value = butil::subtle::NoBarrier_AtomicExchange(&value, k_test_val); + EXPECT_EQ(k_test_val, value); + EXPECT_EQ(k_test_val, new_value); + + value = k_test_val; + new_value = butil::subtle::NoBarrier_AtomicExchange(&value, 5); + EXPECT_EQ(5, value); + EXPECT_EQ(k_test_val, new_value); +} + + +template +static void TestAtomicIncrementBounds() { + // Test increment at the half-width boundary of the atomic type. + // It is primarily for testing at the 32-bit boundary for 64-bit atomic type. + AtomicType test_val = static_cast(1) << (NUM_BITS(AtomicType) / 2); + AtomicType value = test_val - 1; + AtomicType new_value = butil::subtle::NoBarrier_AtomicIncrement(&value, 1); + EXPECT_EQ(test_val, value); + EXPECT_EQ(value, new_value); + + butil::subtle::NoBarrier_AtomicIncrement(&value, -1); + EXPECT_EQ(test_val - 1, value); +} + +// This is a simple sanity check that values are correct. Not testing +// atomicity +template +static void TestStore() { + const AtomicType kVal1 = static_cast(0xa5a5a5a5a5a5a5a5LL); + const AtomicType kVal2 = static_cast(-1); + + AtomicType value; + + butil::subtle::NoBarrier_Store(&value, kVal1); + EXPECT_EQ(kVal1, value); + butil::subtle::NoBarrier_Store(&value, kVal2); + EXPECT_EQ(kVal2, value); + + butil::subtle::Acquire_Store(&value, kVal1); + EXPECT_EQ(kVal1, value); + butil::subtle::Acquire_Store(&value, kVal2); + EXPECT_EQ(kVal2, value); + + butil::subtle::Release_Store(&value, kVal1); + EXPECT_EQ(kVal1, value); + butil::subtle::Release_Store(&value, kVal2); + EXPECT_EQ(kVal2, value); +} + +// This is a simple sanity check that values are correct. Not testing +// atomicity +template +static void TestLoad() { + const AtomicType kVal1 = static_cast(0xa5a5a5a5a5a5a5a5LL); + const AtomicType kVal2 = static_cast(-1); + + AtomicType value; + + value = kVal1; + EXPECT_EQ(kVal1, butil::subtle::NoBarrier_Load(&value)); + value = kVal2; + EXPECT_EQ(kVal2, butil::subtle::NoBarrier_Load(&value)); + + value = kVal1; + EXPECT_EQ(kVal1, butil::subtle::Acquire_Load(&value)); + value = kVal2; + EXPECT_EQ(kVal2, butil::subtle::Acquire_Load(&value)); + + value = kVal1; + EXPECT_EQ(kVal1, butil::subtle::Release_Load(&value)); + value = kVal2; + EXPECT_EQ(kVal2, butil::subtle::Release_Load(&value)); +} + +template +static void TestAtomicOps() { + TestCompareAndSwap(); + TestAtomicExchange(); + TestAtomicIncrementBounds(); + TestStore(); + TestLoad(); +} + +static void TestCalloc(size_t n, size_t s, bool ok) { + char* p = reinterpret_cast(calloc(n, s)); + if (!ok) { + EXPECT_EQ(NULL, p) << "calloc(n, s) should not succeed"; + } else { + EXPECT_NE(reinterpret_cast(NULL), p) << + "calloc(n, s) should succeed"; + for (size_t i = 0; i < n*s; i++) { + EXPECT_EQ('\0', p[i]); + } + free(p); + } +} + + +// A global test counter for number of times the NewHandler is called. +static int news_handled = 0; +static void TestNewHandler() { + ++news_handled; + throw std::bad_alloc(); +} + +// Because we compile without exceptions, we expect these will not throw. +static void TestOneNewWithoutExceptions(void* (*func)(size_t), + bool should_throw) { + // success test + try { + void* ptr = (*func)(kNotTooBig); + EXPECT_NE(reinterpret_cast(NULL), ptr) << + "allocation should not have failed."; + } catch(...) { + EXPECT_EQ(0, 1) << "allocation threw unexpected exception."; + } + + // failure test + try { + void* rv = (*func)(kTooBig); + EXPECT_EQ(NULL, rv); + EXPECT_FALSE(should_throw) << "allocation should have thrown."; + } catch(...) { + EXPECT_TRUE(should_throw) << "allocation threw unexpected exception."; + } +} + +static void TestNothrowNew(void* (*func)(size_t)) { + news_handled = 0; + + // test without new_handler: + std::new_handler saved_handler = std::set_new_handler(0); + TestOneNewWithoutExceptions(func, false); + + // test with new_handler: + std::set_new_handler(TestNewHandler); + TestOneNewWithoutExceptions(func, true); + EXPECT_EQ(news_handled, 1) << "nothrow new_handler was not called."; + std::set_new_handler(saved_handler); +} + +} // namespace + +//----------------------------------------------------------------------------- + +TEST(Atomics, AtomicIncrementWord) { + TestAtomicIncrement(); +} + +TEST(Atomics, AtomicIncrement32) { + TestAtomicIncrement(); +} + +TEST(Atomics, AtomicOpsWord) { + TestAtomicIncrement(); +} + +TEST(Atomics, AtomicOps32) { + TestAtomicIncrement(); +} + +TEST(Allocators, Malloc) { + // Try allocating data with a bunch of alignments and sizes + for (int size = 1; size < 1048576; size *= 2) { + unsigned char* ptr = reinterpret_cast(malloc(size)); + CheckAlignment(ptr, 2); // Should be 2 byte aligned + Fill(ptr, size); + EXPECT_TRUE(Valid(ptr, size)); + free(ptr); + } +} + +TEST(Allocators, Calloc) { + TestCalloc(0, 0, true); + TestCalloc(0, 1, true); + TestCalloc(1, 1, true); + TestCalloc(1<<10, 0, true); + TestCalloc(1<<20, 0, true); + TestCalloc(0, 1<<10, true); + TestCalloc(0, 1<<20, true); + TestCalloc(1<<20, 2, true); + TestCalloc(2, 1<<20, true); + TestCalloc(1000, 1000, true); + + // Not work in glib 2.12 (Red Hat 4.4.6-3, Linux 2.6.32) + // TestCalloc(kMaxSize, 2, false); + // TestCalloc(2, kMaxSize, false); + // TestCalloc(kMaxSize, kMaxSize, false); + + // TestCalloc(kMaxSignedSize, 3, false); + // TestCalloc(3, kMaxSignedSize, false); + // TestCalloc(kMaxSignedSize, kMaxSignedSize, false); +} + +TEST(Allocators, New) { + TestNothrowNew(&::operator new); + TestNothrowNew(&::operator new[]); +} + +// This makes sure that reallocing a small number of bytes in either +// direction doesn't cause us to allocate new memory. +TEST(Allocators, Realloc1) { + int start_sizes[] = { 100, 1000, 10000, 100000 }; + int deltas[] = { 1, -2, 4, -8, 16, -32, 64, -128 }; + + for (size_t s = 0; s < sizeof(start_sizes)/sizeof(*start_sizes); ++s) { + void* p = malloc(start_sizes[s]); + ASSERT_TRUE(p); + // The larger the start-size, the larger the non-reallocing delta. + for (size_t d = 0; d < s*2; ++d) { + void* new_p = realloc(p, start_sizes[s] + deltas[d]); + ASSERT_EQ(p, new_p); // realloc should not allocate new memory + } + // Test again, but this time reallocing smaller first. + for (size_t d = 0; d < s*2; ++d) { + void* new_p = realloc(p, start_sizes[s] - deltas[d]); + ASSERT_EQ(p, new_p); // realloc should not allocate new memory + } + free(p); + } +} + +TEST(Allocators, Realloc2) { + for (int src_size = 0; src_size >= 0; src_size = NextSize(src_size)) { + for (int dst_size = 0; dst_size >= 0; dst_size = NextSize(dst_size)) { + unsigned char* src = reinterpret_cast(malloc(src_size)); + Fill(src, src_size); + unsigned char* dst = + reinterpret_cast(realloc(src, dst_size)); + EXPECT_TRUE(Valid(dst, min(src_size, dst_size))); + Fill(dst, dst_size); + EXPECT_TRUE(Valid(dst, dst_size)); + if (dst != NULL) free(dst); + } + } + + // Now make sure realloc works correctly even when we overflow the + // packed cache, so some entries are evicted from the cache. + // The cache has 2^12 entries, keyed by page number. + const int kNumEntries = 1 << 14; + int** p = reinterpret_cast(malloc(sizeof(*p) * kNumEntries)); + int sum = 0; + for (int i = 0; i < kNumEntries; i++) { + // no page size is likely to be bigger than 8192? + p[i] = reinterpret_cast(malloc(8192)); + p[i][1000] = i; // use memory deep in the heart of p + } + for (int i = 0; i < kNumEntries; i++) { + p[i] = reinterpret_cast(realloc(p[i], 9000)); + } + for (int i = 0; i < kNumEntries; i++) { + sum += p[i][1000]; + free(p[i]); + } + EXPECT_EQ(kNumEntries/2 * (kNumEntries - 1), sum); // assume kNE is even + free(p); +} + +TEST(Allocators, ReallocZero) { + // Test that realloc to zero does not return NULL. + for (int size = 0; size >= 0; size = NextSize(size)) { + char* ptr = reinterpret_cast(malloc(size)); + EXPECT_NE(static_cast(NULL), ptr); + ptr = reinterpret_cast(realloc(ptr, 0)); + EXPECT_NE(static_cast(NULL), ptr); + if (ptr) + free(ptr); + } +} + +#ifdef WIN32 +// Test recalloc +TEST(Allocators, Recalloc) { + for (int src_size = 0; src_size >= 0; src_size = NextSize(src_size)) { + for (int dst_size = 0; dst_size >= 0; dst_size = NextSize(dst_size)) { + unsigned char* src = + reinterpret_cast(_recalloc(NULL, 1, src_size)); + EXPECT_TRUE(IsZeroed(src, src_size)); + Fill(src, src_size); + unsigned char* dst = + reinterpret_cast(_recalloc(src, 1, dst_size)); + EXPECT_TRUE(Valid(dst, min(src_size, dst_size))); + Fill(dst, dst_size); + EXPECT_TRUE(Valid(dst, dst_size)); + if (dst != NULL) + free(dst); + } + } +} + +// Test windows specific _aligned_malloc() and _aligned_free() methods. +TEST(Allocators, AlignedMalloc) { + // Try allocating data with a bunch of alignments and sizes + static const int kTestAlignments[] = {8, 16, 256, 4096, 8192, 16384}; + for (int size = 1; size > 0; size = NextSize(size)) { + for (int i = 0; i < ARRAYSIZE(kTestAlignments); ++i) { + unsigned char* ptr = static_cast( + _aligned_malloc(size, kTestAlignments[i])); + CheckAlignment(ptr, kTestAlignments[i]); + Fill(ptr, size); + EXPECT_TRUE(Valid(ptr, size)); + + // Make a second allocation of the same size and alignment to prevent + // allocators from passing this test by accident. Per jar, tcmalloc + // provides allocations for new (never before seen) sizes out of a thread + // local heap of a given "size class." Each time the test requests a new + // size, it will usually get the first element of a span, which is a + // 4K aligned allocation. + unsigned char* ptr2 = static_cast( + _aligned_malloc(size, kTestAlignments[i])); + CheckAlignment(ptr2, kTestAlignments[i]); + Fill(ptr2, size); + EXPECT_TRUE(Valid(ptr2, size)); + + // Should never happen, but sanity check just in case. + ASSERT_NE(ptr, ptr2); + _aligned_free(ptr); + _aligned_free(ptr2); + } + } +} + +#endif diff --git a/test/at_exit_unittest.cc b/test/at_exit_unittest.cc new file mode 100644 index 0000000..997c0b6 --- /dev/null +++ b/test/at_exit_unittest.cc @@ -0,0 +1,79 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/at_exit.h" + +#include + +namespace { + +int g_test_counter_1 = 0; +int g_test_counter_2 = 0; + +void IncrementTestCounter1(void*) { + ++g_test_counter_1; +} + +void IncrementTestCounter2(void*) { + ++g_test_counter_2; +} + +void ZeroTestCounters() { + g_test_counter_1 = 0; + g_test_counter_2 = 0; +} + +void ExpectCounter1IsZero(void* unused) { + EXPECT_EQ(0, g_test_counter_1); +} + +void ExpectParamIsNull(void* param) { + EXPECT_EQ(static_cast(NULL), param); +} + +void ExpectParamIsCounter(void* param) { + EXPECT_EQ(&g_test_counter_1, param); +} + +} // namespace + +class AtExitTest : public testing::Test { + private: + // Don't test the global AtExitManager, because asking it to process its + // AtExit callbacks can ruin the global state that other tests may depend on. + butil::ShadowingAtExitManager exit_manager_; +}; + +TEST_F(AtExitTest, Basic) { + ZeroTestCounters(); + butil::AtExitManager::RegisterCallback(&IncrementTestCounter1, NULL); + butil::AtExitManager::RegisterCallback(&IncrementTestCounter2, NULL); + butil::AtExitManager::RegisterCallback(&IncrementTestCounter1, NULL); + + EXPECT_EQ(0, g_test_counter_1); + EXPECT_EQ(0, g_test_counter_2); + butil::AtExitManager::ProcessCallbacksNow(); + EXPECT_EQ(2, g_test_counter_1); + EXPECT_EQ(1, g_test_counter_2); +} + +TEST_F(AtExitTest, LIFOOrder) { + ZeroTestCounters(); + butil::AtExitManager::RegisterCallback(&IncrementTestCounter1, NULL); + butil::AtExitManager::RegisterCallback(&ExpectCounter1IsZero, NULL); + butil::AtExitManager::RegisterCallback(&IncrementTestCounter2, NULL); + + EXPECT_EQ(0, g_test_counter_1); + EXPECT_EQ(0, g_test_counter_2); + butil::AtExitManager::ProcessCallbacksNow(); + EXPECT_EQ(1, g_test_counter_1); + EXPECT_EQ(1, g_test_counter_2); +} + +TEST_F(AtExitTest, Param) { + butil::AtExitManager::RegisterCallback(&ExpectParamIsNull, NULL); + butil::AtExitManager::RegisterCallback(&ExpectParamIsCounter, + &g_test_counter_1); + butil::AtExitManager::ProcessCallbacksNow(); +} diff --git a/test/atomicops_unittest.cc b/test/atomicops_unittest.cc new file mode 100644 index 0000000..b83dbee --- /dev/null +++ b/test/atomicops_unittest.cc @@ -0,0 +1,241 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/atomicops.h" + +#include +#include + +#include + +template +static void TestAtomicIncrement() { + // For now, we just test single threaded execution + + // use a guard value to make sure the NoBarrier_AtomicIncrement doesn't go + // outside the expected address bounds. This is in particular to + // test that some future change to the asm code doesn't cause the + // 32-bit NoBarrier_AtomicIncrement doesn't do the wrong thing on 64-bit + // machines. + struct { + AtomicType prev_word; + AtomicType count; + AtomicType next_word; + } s; + + AtomicType prev_word_value, next_word_value; + memset(&prev_word_value, 0xFF, sizeof(AtomicType)); + memset(&next_word_value, 0xEE, sizeof(AtomicType)); + + s.prev_word = prev_word_value; + s.count = 0; + s.next_word = next_word_value; + + EXPECT_EQ(butil::subtle::NoBarrier_AtomicIncrement(&s.count, 1), 1); + EXPECT_EQ(s.count, 1); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(butil::subtle::NoBarrier_AtomicIncrement(&s.count, 2), 3); + EXPECT_EQ(s.count, 3); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(butil::subtle::NoBarrier_AtomicIncrement(&s.count, 3), 6); + EXPECT_EQ(s.count, 6); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(butil::subtle::NoBarrier_AtomicIncrement(&s.count, -3), 3); + EXPECT_EQ(s.count, 3); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(butil::subtle::NoBarrier_AtomicIncrement(&s.count, -2), 1); + EXPECT_EQ(s.count, 1); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(butil::subtle::NoBarrier_AtomicIncrement(&s.count, -1), 0); + EXPECT_EQ(s.count, 0); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(butil::subtle::NoBarrier_AtomicIncrement(&s.count, -1), -1); + EXPECT_EQ(s.count, -1); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(butil::subtle::NoBarrier_AtomicIncrement(&s.count, -4), -5); + EXPECT_EQ(s.count, -5); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(butil::subtle::NoBarrier_AtomicIncrement(&s.count, 5), 0); + EXPECT_EQ(s.count, 0); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); +} + + +#define NUM_BITS(T) (sizeof(T) * 8) + + +template +static void TestCompareAndSwap() { + AtomicType value = 0; + AtomicType prev = butil::subtle::NoBarrier_CompareAndSwap(&value, 0, 1); + EXPECT_EQ(1, value); + EXPECT_EQ(0, prev); + + // Use test value that has non-zero bits in both halves, more for testing + // 64-bit implementation on 32-bit platforms. + const AtomicType k_test_val = (static_cast(1) << + (NUM_BITS(AtomicType) - 2)) + 11; + value = k_test_val; + prev = butil::subtle::NoBarrier_CompareAndSwap(&value, 0, 5); + EXPECT_EQ(k_test_val, value); + EXPECT_EQ(k_test_val, prev); + + value = k_test_val; + prev = butil::subtle::NoBarrier_CompareAndSwap(&value, k_test_val, 5); + EXPECT_EQ(5, value); + EXPECT_EQ(k_test_val, prev); +} + + +template +static void TestAtomicExchange() { + AtomicType value = 0; + AtomicType new_value = butil::subtle::NoBarrier_AtomicExchange(&value, 1); + EXPECT_EQ(1, value); + EXPECT_EQ(0, new_value); + + // Use test value that has non-zero bits in both halves, more for testing + // 64-bit implementation on 32-bit platforms. + const AtomicType k_test_val = (static_cast(1) << + (NUM_BITS(AtomicType) - 2)) + 11; + value = k_test_val; + new_value = butil::subtle::NoBarrier_AtomicExchange(&value, k_test_val); + EXPECT_EQ(k_test_val, value); + EXPECT_EQ(k_test_val, new_value); + + value = k_test_val; + new_value = butil::subtle::NoBarrier_AtomicExchange(&value, 5); + EXPECT_EQ(5, value); + EXPECT_EQ(k_test_val, new_value); +} + + +template +static void TestAtomicIncrementBounds() { + // Test at rollover boundary between int_max and int_min + AtomicType test_val = (static_cast(1) << + (NUM_BITS(AtomicType) - 1)); + AtomicType value = -1 ^ test_val; + AtomicType new_value = butil::subtle::NoBarrier_AtomicIncrement(&value, 1); + EXPECT_EQ(test_val, value); + EXPECT_EQ(value, new_value); + + butil::subtle::NoBarrier_AtomicIncrement(&value, -1); + EXPECT_EQ(-1 ^ test_val, value); + + // Test at 32-bit boundary for 64-bit atomic type. + test_val = static_cast(1) << (NUM_BITS(AtomicType) / 2); + value = test_val - 1; + new_value = butil::subtle::NoBarrier_AtomicIncrement(&value, 1); + EXPECT_EQ(test_val, value); + EXPECT_EQ(value, new_value); + + butil::subtle::NoBarrier_AtomicIncrement(&value, -1); + EXPECT_EQ(test_val - 1, value); +} + +// Return an AtomicType with the value 0xa5a5a5.. +template +static AtomicType TestFillValue() { + AtomicType val = 0; + memset(&val, 0xa5, sizeof(AtomicType)); + return val; +} + +// This is a simple sanity check that values are correct. Not testing +// atomicity +template +static void TestStore() { + const AtomicType kVal1 = TestFillValue(); + const AtomicType kVal2 = static_cast(-1); + + AtomicType value; + + butil::subtle::NoBarrier_Store(&value, kVal1); + EXPECT_EQ(kVal1, value); + butil::subtle::NoBarrier_Store(&value, kVal2); + EXPECT_EQ(kVal2, value); + + butil::subtle::Acquire_Store(&value, kVal1); + EXPECT_EQ(kVal1, value); + butil::subtle::Acquire_Store(&value, kVal2); + EXPECT_EQ(kVal2, value); + + butil::subtle::Release_Store(&value, kVal1); + EXPECT_EQ(kVal1, value); + butil::subtle::Release_Store(&value, kVal2); + EXPECT_EQ(kVal2, value); +} + +// This is a simple sanity check that values are correct. Not testing +// atomicity +template +static void TestLoad() { + const AtomicType kVal1 = TestFillValue(); + const AtomicType kVal2 = static_cast(-1); + + AtomicType value; + + value = kVal1; + EXPECT_EQ(kVal1, butil::subtle::NoBarrier_Load(&value)); + value = kVal2; + EXPECT_EQ(kVal2, butil::subtle::NoBarrier_Load(&value)); + + value = kVal1; + EXPECT_EQ(kVal1, butil::subtle::Acquire_Load(&value)); + value = kVal2; + EXPECT_EQ(kVal2, butil::subtle::Acquire_Load(&value)); + + value = kVal1; + EXPECT_EQ(kVal1, butil::subtle::Release_Load(&value)); + value = kVal2; + EXPECT_EQ(kVal2, butil::subtle::Release_Load(&value)); +} + +TEST(AtomicOpsTest, Inc) { + TestAtomicIncrement(); + TestAtomicIncrement(); +} + +TEST(AtomicOpsTest, CompareAndSwap) { + TestCompareAndSwap(); + TestCompareAndSwap(); +} + +TEST(AtomicOpsTest, Exchange) { + TestAtomicExchange(); + TestAtomicExchange(); +} + +TEST(AtomicOpsTest, IncrementBounds) { + TestAtomicIncrementBounds(); + TestAtomicIncrementBounds(); +} + +TEST(AtomicOpsTest, Store) { + TestStore(); + TestStore(); +} + +TEST(AtomicOpsTest, Load) { + TestLoad(); + TestLoad(); +} diff --git a/test/baidu_thread_local_unittest.cpp b/test/baidu_thread_local_unittest.cpp new file mode 100644 index 0000000..4cc629f --- /dev/null +++ b/test/baidu_thread_local_unittest.cpp @@ -0,0 +1,204 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include "butil/thread_local.h" + +namespace { + +BAIDU_THREAD_LOCAL int * dummy = NULL; +const size_t NTHREAD = 8; +static bool processed[NTHREAD+1]; +static bool deleted[NTHREAD+1]; +static bool register_check = false; + +struct YellObj { + static int nc; + static int nd; + YellObj() { + ++nc; + } + ~YellObj() { + ++nd; + } +}; +int YellObj::nc = 0; +int YellObj::nd = 0; + +static void check_global_variable() { + EXPECT_TRUE(processed[NTHREAD]); + EXPECT_TRUE(deleted[NTHREAD]); + EXPECT_EQ(2, YellObj::nc); + EXPECT_EQ(2, YellObj::nd); +} + +class BaiduThreadLocalTest : public ::testing::Test{ +protected: + BaiduThreadLocalTest(){ + if (!register_check) { + register_check = true; + butil::thread_atexit(check_global_variable); + } + }; + virtual ~BaiduThreadLocalTest(){}; + virtual void SetUp() { + }; + virtual void TearDown() { + }; +}; + + +BAIDU_THREAD_LOCAL void* x; + +void* foo(void* arg) { + x = arg; + usleep(10000); + printf("x=%p\n", x); + return NULL; +} + +TEST_F(BaiduThreadLocalTest, thread_local_keyword) { + pthread_t th[2]; + pthread_create(&th[0], NULL, foo, (void*)1); + pthread_create(&th[1], NULL, foo, (void*)2); + pthread_join(th[0], NULL); + pthread_join(th[1], NULL); +} + +void* yell(void*) { + YellObj* p = butil::get_thread_local(); + EXPECT_TRUE(p); + EXPECT_EQ(2, YellObj::nc); + EXPECT_EQ(0, YellObj::nd); + EXPECT_EQ(p, butil::get_thread_local()); + EXPECT_EQ(2, YellObj::nc); + EXPECT_EQ(0, YellObj::nd); + return NULL; +} + +TEST_F(BaiduThreadLocalTest, get_thread_local) { + YellObj::nc = 0; + YellObj::nd = 0; + YellObj* p = butil::get_thread_local(); + ASSERT_TRUE(p); + ASSERT_EQ(1, YellObj::nc); + ASSERT_EQ(0, YellObj::nd); + ASSERT_EQ(p, butil::get_thread_local()); + ASSERT_EQ(1, YellObj::nc); + ASSERT_EQ(0, YellObj::nd); + pthread_t th; + ASSERT_EQ(0, pthread_create(&th, NULL, yell, NULL)); + pthread_join(th, NULL); + EXPECT_EQ(2, YellObj::nc); + EXPECT_EQ(1, YellObj::nd); +} + +void delete_dummy(void* arg) { + *(bool*)arg = true; + if (dummy) { + delete dummy; + dummy = NULL; + } else { + printf("dummy is NULL\n"); + } +} + +void* proc_dummy(void* arg) { + bool *p = (bool*)arg; + *p = true; + EXPECT_TRUE(dummy == NULL); + dummy = new int(p - processed); + butil::thread_atexit(delete_dummy, deleted + (p - processed)); + return NULL; +} + +TEST_F(BaiduThreadLocalTest, sanity) { + errno = 0; + ASSERT_EQ(-1, butil::thread_atexit(NULL)); + ASSERT_EQ(EINVAL, errno); + + processed[NTHREAD] = false; + deleted[NTHREAD] = false; + proc_dummy(&processed[NTHREAD]); + + pthread_t th[NTHREAD]; + for (size_t i = 0; i < NTHREAD; ++i) { + processed[i] = false; + deleted[i] = false; + ASSERT_EQ(0, pthread_create(&th[i], NULL, proc_dummy, processed + i)); + } + for (size_t i = 0; i < NTHREAD; ++i) { + ASSERT_EQ(0, pthread_join(th[i], NULL)); + ASSERT_TRUE(processed[i]); + ASSERT_TRUE(deleted[i]); + } +} + +static std::ostringstream* oss = NULL; +inline std::ostringstream& get_oss() { + if (oss == NULL) { + oss = new std::ostringstream; + } + return *oss; +} + +void fun1() { + get_oss() << "fun1" << std::endl; +} + +void fun2() { + get_oss() << "fun2" << std::endl; +} + +void fun3(void* arg) { + get_oss() << "fun3(" << (uintptr_t)arg << ")" << std::endl; +} + +void fun4(void* arg) { + get_oss() << "fun4(" << (uintptr_t)arg << ")" << std::endl; +} + +static void check_result() { + // Don't use gtest function since this function might be invoked when the main + // thread quits, instances required by gtest functions are likely destroyed. + assert(get_oss().str() == "fun4(0)\nfun3(2)\nfun2\n"); +} + +TEST_F(BaiduThreadLocalTest, call_order_and_cancel) { + butil::thread_atexit_cancel(NULL); + butil::thread_atexit_cancel(NULL, NULL); + + ASSERT_EQ(0, butil::thread_atexit(check_result)); + + ASSERT_EQ(0, butil::thread_atexit(fun1)); + ASSERT_EQ(0, butil::thread_atexit(fun1)); + ASSERT_EQ(0, butil::thread_atexit(fun2)); + ASSERT_EQ(0, butil::thread_atexit(fun3, (void*)1)); + ASSERT_EQ(0, butil::thread_atexit(fun3, (void*)1)); + ASSERT_EQ(0, butil::thread_atexit(fun3, (void*)2)); + ASSERT_EQ(0, butil::thread_atexit(fun4, NULL)); + + butil::thread_atexit_cancel(NULL); + butil::thread_atexit_cancel(NULL, NULL); + butil::thread_atexit_cancel(fun1); + butil::thread_atexit_cancel(fun3, NULL); + butil::thread_atexit_cancel(fun3, (void*)1); +} + +} // namespace diff --git a/test/baidu_time_unittest.cpp b/test/baidu_time_unittest.cpp new file mode 100644 index 0000000..d574189 --- /dev/null +++ b/test/baidu_time_unittest.cpp @@ -0,0 +1,214 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/build_config.h" + +#if defined(OS_LINUX) +#include // SYS_clock_gettime +#include // syscall +#endif + +#include "butil/time.h" +#include "butil/macros.h" +#include "butil/logging.h" + +namespace { + +TEST(BaiduTimeTest, diff_between_gettimeofday_and_REALTIME) { + long t1 = butil::gettimeofday_us(); + timespec time; + clock_gettime(CLOCK_REALTIME, &time); + long t2 = butil::timespec_to_microseconds(time); + LOG(INFO) << "t1=" << t1 << " t2=" << t2; +} + +const char* clock_desc[] = { + "CLOCK_REALTIME", //0 + "CLOCK_MONOTONIC", //1 + "CLOCK_PROCESS_CPUTIME_ID", //2 + "CLOCK_THREAD_CPUTIME_ID", //3 + "CLOCK_MONOTONIC_RAW", //4 + "CLOCK_REALTIME_COARSE", //5 + "CLOCK_MONOTONIC_COARSE", //6 + "CLOCK_BOOTTIME", //7 + "CLOCK_REALTIME_ALARM", //8 + "CLOCK_BOOTTIME_ALARM", //9 + "CLOCK_SGI_CYCLE", //10 + "CLOCK_TAI" //11 +}; + +TEST(BaiduTimeTest, cost_of_timer) { + printf("sizeof(time_t)=%lu\n", sizeof(time_t)); + + butil::Timer t1, t2; + timespec ts; + const size_t N = 200000; + t1.start(); + for (size_t i = 0; i < N; ++i) { + t2.stop(); + } + t1.stop(); + printf("Timer::stop() takes %" PRId64 "ns\n", t1.n_elapsed() / N); + + t1.start(); + for (size_t i = 0; i < N; ++i) { + clock(); + } + t1.stop(); + printf("clock() takes %" PRId64 "ns\n", t1.n_elapsed() / N); + + long s = 0; + t1.start(); + for (size_t i = 0; i < N; ++i) { + s += butil::cpuwide_time_ns(); + } + t1.stop(); + printf("cpuwide_time() takes %" PRId64 "ns\n", t1.n_elapsed() / N); + + t1.start(); + for (size_t i = 0; i < N; ++i) { + s += butil::gettimeofday_us(); + } + t1.stop(); + printf("gettimeofday_us takes %" PRId64 "ns\n", t1.n_elapsed() / N); + + t1.start(); + for (size_t i = 0; i < N; ++i) { + time(NULL); + } + t1.stop(); + printf("time(NULL) takes %" PRId64 "ns\n", t1.n_elapsed() / N); + + t1.start(); + for (size_t i = 0; i < N; ++i) { + s += butil::monotonic_time_ns(); + } + t1.stop(); + printf("monotonic_time_ns takes %" PRId64 "ns s=%ld\n", t1.n_elapsed() / N, s); + + for (size_t i = 0; i < arraysize(clock_desc); ++i) { +#if defined(OS_LINUX) + if (0 == syscall(SYS_clock_gettime, (clockid_t)i, &ts)) { + t1.start(); + for (size_t j = 0; j < N; ++j) { + syscall(SYS_clock_gettime, (clockid_t)i, &ts); + } + t1.stop(); + printf("sys clock_gettime(%s) takes %" PRId64 "ns\n", + clock_desc[i], t1.n_elapsed() / N); + } +#endif + if (0 == clock_gettime((clockid_t)i, &ts)) { + t1.start(); + for (size_t j = 0; j < N; ++j) { + clock_gettime((clockid_t)i, &ts); + } + t1.stop(); + printf("glibc clock_gettime(%s) takes %" PRId64 "ns\n", + clock_desc[i], t1.n_elapsed() / N); + } + } +} + +TEST(BaiduTimeTest, timespec) { + timespec ts1 = { 0, -1 }; + butil::timespec_normalize(&ts1); + ASSERT_EQ(999999999L, ts1.tv_nsec); + ASSERT_EQ(-1, ts1.tv_sec); + + timespec ts2 = { 0, 1000000000L }; + butil::timespec_normalize(&ts2); + ASSERT_EQ(0L, ts2.tv_nsec); + ASSERT_EQ(1L, ts2.tv_sec); + + timespec ts3 = { 0, 999999999L }; + butil::timespec_normalize(&ts3); + ASSERT_EQ(999999999L, ts3.tv_nsec); + ASSERT_EQ(0, ts3.tv_sec); + + timespec ts4 = { 0, 1L }; + butil::timespec_add(&ts4, ts3); + ASSERT_EQ(0, ts4.tv_nsec); + ASSERT_EQ(1L, ts4.tv_sec); + + timespec ts5 = { 0, 999999999L }; + butil::timespec_minus(&ts5, ts3); + ASSERT_EQ(0, ts5.tv_nsec); + ASSERT_EQ(0, ts5.tv_sec); + + timespec ts6 = { 0, 999999998L }; + butil::timespec_minus(&ts6, ts3); + ASSERT_EQ(999999999L, ts6.tv_nsec); + ASSERT_EQ(-1L, ts6.tv_sec); + + timespec ts7 = butil::nanoseconds_from(ts3, 1L); + ASSERT_EQ(0, ts7.tv_nsec); + ASSERT_EQ(1L, ts7.tv_sec); + + timespec ts8 = butil::nanoseconds_from(ts3, -1000000000L); + ASSERT_EQ(999999999L, ts8.tv_nsec); + ASSERT_EQ(-1L, ts8.tv_sec); + + timespec ts9 = butil::microseconds_from(ts3, 1L); + ASSERT_EQ(999L, ts9.tv_nsec); + ASSERT_EQ(1L, ts9.tv_sec); + + timespec ts10 = butil::microseconds_from(ts3, -1000000L); + ASSERT_EQ(999999999L, ts10.tv_nsec); + ASSERT_EQ(-1L, ts10.tv_sec); + + timespec ts11 = butil::milliseconds_from(ts3, 1L); + ASSERT_EQ(999999L, ts11.tv_nsec); + ASSERT_EQ(1L, ts11.tv_sec); + + timespec ts12 = butil::milliseconds_from(ts3, -1000L); + ASSERT_EQ(999999999L, ts12.tv_nsec); + ASSERT_EQ(-1L, ts12.tv_sec); + + timespec ts13 = butil::seconds_from(ts3, 1L); + ASSERT_EQ(999999999L, ts13.tv_nsec); + ASSERT_EQ(1, ts13.tv_sec); + + timespec ts14 = butil::seconds_from(ts3, -1L); + ASSERT_EQ(999999999L, ts14.tv_nsec); + ASSERT_EQ(-1L, ts14.tv_sec); +} + +TEST(BaiduTimeTest, every_many_us) { + butil::EveryManyUS every_10ms(10000L); + size_t i = 0; + const long start_time = butil::gettimeofday_ms(); + while (1) { + if (every_10ms) { + printf("enter this branch at %" PRId64 "ms\n", + butil::gettimeofday_ms() - start_time); + if (++i >= 10) { + break; + } + } + } +} + +TEST(BaiduTimeTest, timer_auto_start) { + butil::Timer t(butil::Timer::STARTED); + usleep(100); + t.stop(); + printf("Cost %" PRId64 "us\n", t.u_elapsed()); +} + +} // namespace diff --git a/test/barrier_closure_unittest.cc b/test/barrier_closure_unittest.cc new file mode 100644 index 0000000..11f8516 --- /dev/null +++ b/test/barrier_closure_unittest.cc @@ -0,0 +1,36 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/barrier_closure.h" + +#include "butil/bind.h" +#include + +namespace { + +void Increment(int* count) { (*count)++; } + +TEST(BarrierClosureTest, RunImmediatelyForZeroClosures) { + int count = 0; + butil::Closure doneClosure(butil::Bind(&Increment, butil::Unretained(&count))); + + butil::Closure barrierClosure = butil::BarrierClosure(0, doneClosure); + EXPECT_EQ(1, count); +} + +TEST(BarrierClosureTest, RunAfterNumClosures) { + int count = 0; + butil::Closure doneClosure(butil::Bind(&Increment, butil::Unretained(&count))); + + butil::Closure barrierClosure = butil::BarrierClosure(2, doneClosure); + EXPECT_EQ(0, count); + + barrierClosure.Run(); + EXPECT_EQ(0, count); + + barrierClosure.Run(); + EXPECT_EQ(1, count); +} + +} // namespace diff --git a/test/base64_unittest.cc b/test/base64_unittest.cc new file mode 100644 index 0000000..40a3906 --- /dev/null +++ b/test/base64_unittest.cc @@ -0,0 +1,27 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/base64.h" + +#include + +namespace butil { + +TEST(Base64Test, Basic) { + const std::string kText = "hello world"; + const std::string kBase64Text = "aGVsbG8gd29ybGQ="; + + std::string encoded; + std::string decoded; + bool ok; + + Base64Encode(kText, &encoded); + EXPECT_EQ(kBase64Text, encoded); + + ok = Base64Decode(encoded, &decoded); + EXPECT_TRUE(ok); + EXPECT_EQ(kText, decoded); +} + +} // namespace butil diff --git a/test/base64url_unittest.cc b/test/base64url_unittest.cc new file mode 100644 index 0000000..4995b16 --- /dev/null +++ b/test/base64url_unittest.cc @@ -0,0 +1,110 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/base64url.h" + +#include + +namespace butil { + +TEST(Base64UrlTest, EncodeIncludePaddingPolicy) { + std::string output; + Base64UrlEncode("hello?world", Base64UrlEncodePolicy::INCLUDE_PADDING, + &output); + + // Base64 version: aGVsbG8/d29ybGQ= + EXPECT_EQ("aGVsbG8_d29ybGQ=", output); + + // Test for behavior for very short and empty strings. + Base64UrlEncode("??", Base64UrlEncodePolicy::INCLUDE_PADDING, &output); + EXPECT_EQ("Pz8=", output); + + Base64UrlEncode("", Base64UrlEncodePolicy::INCLUDE_PADDING, &output); + EXPECT_EQ("", output); +} + +TEST(Base64UrlTest, EncodeOmitPaddingPolicy) { + std::string output; + Base64UrlEncode("hello?world", Base64UrlEncodePolicy::OMIT_PADDING, &output); + + // base64 version: aGVsbG8/d29ybGQ= + EXPECT_EQ("aGVsbG8_d29ybGQ", output); + + // Test for behavior for very short and empty strings. + Base64UrlEncode("??", Base64UrlEncodePolicy::OMIT_PADDING, &output); + EXPECT_EQ("Pz8", output); + + Base64UrlEncode("", Base64UrlEncodePolicy::OMIT_PADDING, &output); + EXPECT_EQ("", output); +} + +TEST(Base64UrlTest, DecodeRequirePaddingPolicy) { + std::string output; + ASSERT_TRUE(Base64UrlDecode("aGVsbG8_d29ybGQ=", + Base64UrlDecodePolicy::REQUIRE_PADDING, &output)); + + EXPECT_EQ("hello?world", output); + + ASSERT_FALSE(Base64UrlDecode( + "aGVsbG8_d29ybGQ", Base64UrlDecodePolicy::REQUIRE_PADDING, &output)); + + // Test for behavior for very short and empty strings. + ASSERT_TRUE( + Base64UrlDecode("Pz8=", Base64UrlDecodePolicy::REQUIRE_PADDING, &output)); + EXPECT_EQ("??", output); + + ASSERT_TRUE( + Base64UrlDecode("", Base64UrlDecodePolicy::REQUIRE_PADDING, &output)); + EXPECT_EQ("", output); +} + +TEST(Base64UrlTest, DecodeIgnorePaddingPolicy) { + std::string output; + ASSERT_TRUE(Base64UrlDecode("aGVsbG8_d29ybGQ", + Base64UrlDecodePolicy::IGNORE_PADDING, &output)); + + EXPECT_EQ("hello?world", output); + + // Including the padding is accepted as well. + ASSERT_TRUE(Base64UrlDecode("aGVsbG8_d29ybGQ=", + Base64UrlDecodePolicy::IGNORE_PADDING, &output)); + + EXPECT_EQ("hello?world", output); +} + +TEST(Base64UrlTest, DecodeDisallowPaddingPolicy) { + std::string output; + ASSERT_FALSE(Base64UrlDecode( + "aGVsbG8_d29ybGQ=", Base64UrlDecodePolicy::DISALLOW_PADDING, &output)); + + // The policy will allow the input when padding has been omitted. + ASSERT_TRUE(Base64UrlDecode( + "aGVsbG8_d29ybGQ", Base64UrlDecodePolicy::DISALLOW_PADDING, &output)); + + EXPECT_EQ("hello?world", output); +} + +TEST(Base64UrlTest, DecodeDisallowsBase64Alphabet) { + std::string output; + + // The "/" character is part of the conventional base64 alphabet, but has been + // substituted with "_" in the base64url alphabet. + ASSERT_FALSE(Base64UrlDecode( + "aGVsbG8/d29ybGQ=", Base64UrlDecodePolicy::REQUIRE_PADDING, &output)); +} + +TEST(Base64UrlTest, DecodeDisallowsPaddingOnly) { + std::string output; + + ASSERT_FALSE(Base64UrlDecode( + "=", Base64UrlDecodePolicy::IGNORE_PADDING, &output)); + ASSERT_FALSE(Base64UrlDecode( + "==", Base64UrlDecodePolicy::IGNORE_PADDING, &output)); + ASSERT_FALSE(Base64UrlDecode( + "===", Base64UrlDecodePolicy::IGNORE_PADDING, &output)); + ASSERT_FALSE(Base64UrlDecode( + "====", Base64UrlDecodePolicy::IGNORE_PADDING, &output)); +} + +} // namespace butil diff --git a/test/big_endian_unittest.cc b/test/big_endian_unittest.cc new file mode 100644 index 0000000..58f8857 --- /dev/null +++ b/test/big_endian_unittest.cc @@ -0,0 +1,100 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/big_endian.h" + +#include "butil/strings/string_piece.h" +#include + +namespace butil { + +TEST(BigEndianReaderTest, ReadsValues) { + char data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC }; + char buf[2]; + uint8_t u8; + uint16_t u16; + uint32_t u32; + butil::StringPiece piece; + BigEndianReader reader(data, sizeof(data)); + + EXPECT_TRUE(reader.Skip(2)); + EXPECT_EQ(data + 2, reader.ptr()); + EXPECT_EQ(reader.remaining(), static_cast(sizeof(data)) - 2); + EXPECT_TRUE(reader.ReadBytes(buf, sizeof(buf))); + EXPECT_EQ(0x2, buf[0]); + EXPECT_EQ(0x3, buf[1]); + EXPECT_TRUE(reader.ReadU8(&u8)); + EXPECT_EQ(0x4, u8); + EXPECT_TRUE(reader.ReadU16(&u16)); + EXPECT_EQ(0x0506, u16); + EXPECT_TRUE(reader.ReadU32(&u32)); + EXPECT_EQ(0x0708090Au, u32); + butil::StringPiece expected(reader.ptr(), 2); + EXPECT_TRUE(reader.ReadPiece(&piece, 2)); + EXPECT_EQ(2u, piece.size()); + EXPECT_EQ(expected.data(), piece.data()); +} + +TEST(BigEndianReaderTest, RespectsLength) { + char data[4]; + char buf[2]; + uint8_t u8; + uint16_t u16; + uint32_t u32; + butil::StringPiece piece; + BigEndianReader reader(data, sizeof(data)); + // 4 left + EXPECT_FALSE(reader.Skip(6)); + EXPECT_TRUE(reader.Skip(1)); + // 3 left + EXPECT_FALSE(reader.ReadU32(&u32)); + EXPECT_FALSE(reader.ReadPiece(&piece, 4)); + EXPECT_TRUE(reader.Skip(2)); + // 1 left + EXPECT_FALSE(reader.ReadU16(&u16)); + EXPECT_FALSE(reader.ReadBytes(buf, 2)); + EXPECT_TRUE(reader.Skip(1)); + // 0 left + EXPECT_FALSE(reader.ReadU8(&u8)); + EXPECT_EQ(0, reader.remaining()); +} + +TEST(BigEndianWriterTest, WritesValues) { + char expected[] = { 0, 0, 2, 3, 4, 5, 6, 7, 8, 9, 0xA }; + char data[sizeof(expected)]; + char buf[] = { 0x2, 0x3 }; + memset(data, 0, sizeof(data)); + BigEndianWriter writer(data, sizeof(data)); + + EXPECT_TRUE(writer.Skip(2)); + EXPECT_TRUE(writer.WriteBytes(buf, sizeof(buf))); + EXPECT_TRUE(writer.WriteU8(0x4)); + EXPECT_TRUE(writer.WriteU16(0x0506)); + EXPECT_TRUE(writer.WriteU32(0x0708090A)); + EXPECT_EQ(0, memcmp(expected, data, sizeof(expected))); +} + +TEST(BigEndianWriterTest, RespectsLength) { + char data[4]; + char buf[2]; + uint8_t u8 = 0; + uint16_t u16 = 0; + uint32_t u32 = 0; + BigEndianWriter writer(data, sizeof(data)); + // 4 left + EXPECT_FALSE(writer.Skip(6)); + EXPECT_TRUE(writer.Skip(1)); + // 3 left + EXPECT_FALSE(writer.WriteU32(u32)); + EXPECT_TRUE(writer.Skip(2)); + // 1 left + EXPECT_FALSE(writer.WriteU16(u16)); + EXPECT_FALSE(writer.WriteBytes(buf, 2)); + EXPECT_TRUE(writer.Skip(1)); + // 0 left + EXPECT_FALSE(writer.WriteU8(u8)); + EXPECT_EQ(0, writer.remaining()); +} + +} // namespace butil diff --git a/test/bits_unittest.cc b/test/bits_unittest.cc new file mode 100644 index 0000000..7f50cad --- /dev/null +++ b/test/bits_unittest.cc @@ -0,0 +1,48 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains the unit tests for the bit utilities. + +#include "butil/bits.h" +#include + +namespace butil { +namespace bits { + +TEST(BitsTest, Log2Floor) { + EXPECT_EQ(-1, Log2Floor(0)); + EXPECT_EQ(0, Log2Floor(1)); + EXPECT_EQ(1, Log2Floor(2)); + EXPECT_EQ(1, Log2Floor(3)); + EXPECT_EQ(2, Log2Floor(4)); + for (int i = 3; i < 31; ++i) { + unsigned int value = 1U << i; + EXPECT_EQ(i, Log2Floor(value)); + EXPECT_EQ(i, Log2Floor(value + 1)); + EXPECT_EQ(i, Log2Floor(value + 2)); + EXPECT_EQ(i - 1, Log2Floor(value - 1)); + EXPECT_EQ(i - 1, Log2Floor(value - 2)); + } + EXPECT_EQ(31, Log2Floor(0xffffffffU)); +} + +TEST(BitsTest, Log2Ceiling) { + EXPECT_EQ(-1, Log2Ceiling(0)); + EXPECT_EQ(0, Log2Ceiling(1)); + EXPECT_EQ(1, Log2Ceiling(2)); + EXPECT_EQ(2, Log2Ceiling(3)); + EXPECT_EQ(2, Log2Ceiling(4)); + for (int i = 3; i < 31; ++i) { + unsigned int value = 1U << i; + EXPECT_EQ(i, Log2Ceiling(value)); + EXPECT_EQ(i + 1, Log2Ceiling(value + 1)); + EXPECT_EQ(i + 1, Log2Ceiling(value + 2)); + EXPECT_EQ(i, Log2Ceiling(value - 1)); + EXPECT_EQ(i, Log2Ceiling(value - 2)); + } + EXPECT_EQ(32, Log2Ceiling(0xffffffffU)); +} + +} // namespace bits +} // namespace butil diff --git a/test/bounded_queue_unittest.cc b/test/bounded_queue_unittest.cc new file mode 100644 index 0000000..556e84e --- /dev/null +++ b/test/bounded_queue_unittest.cc @@ -0,0 +1,105 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Sun Nov 3 19:16:50 PST 2019 + +#include +#include "butil/containers/bounded_queue.h" +#include + +namespace { + +TEST(BoundedQueueTest, sanity) { + const int N = 36; + char storage[N * sizeof(int)]; + butil::BoundedQueue q(storage, sizeof(storage), butil::NOT_OWN_STORAGE); + ASSERT_EQ(0ul, q.size()); + ASSERT_TRUE(q.empty()); + ASSERT_TRUE(NULL == q.top()); + ASSERT_TRUE(NULL == q.bottom()); + for (int i = 1; i <= N; ++i) { + if (i % 2 == 0) { + ASSERT_TRUE(q.push(i)); + } else { + int* p = q.push(); + ASSERT_TRUE(p); + *p = i; + } + ASSERT_EQ((size_t)i, q.size()); + ASSERT_EQ(1, *q.top()); + ASSERT_EQ(i, *q.bottom()); + } + ASSERT_FALSE(q.push(N+1)); + ASSERT_FALSE(q.push_top(N+1)); + ASSERT_EQ((size_t)N, q.size()); + ASSERT_FALSE(q.empty()); + ASSERT_TRUE(q.full()); + + for (int i = 1; i <= N; ++i) { + ASSERT_EQ(i, *q.top()); + ASSERT_EQ(N, *q.bottom()); + if (i % 2 == 0) { + int tmp = 0; + ASSERT_TRUE(q.pop(&tmp)); + ASSERT_EQ(tmp, i); + } else { + ASSERT_TRUE(q.pop()); + } + ASSERT_EQ((size_t)(N-i), q.size()); + } + ASSERT_EQ(0ul, q.size()); + ASSERT_TRUE(q.empty()); + ASSERT_FALSE(q.full()); + ASSERT_FALSE(q.pop()); + + for (int i = 1; i <= N; ++i) { + if (i % 2 == 0) { + ASSERT_TRUE(q.push_top(i)); + } else { + int* p = q.push_top(); + ASSERT_TRUE(p); + *p = i; + } + ASSERT_EQ((size_t)i, q.size()); + ASSERT_EQ(i, *q.top()); + ASSERT_EQ(1, *q.bottom()); + } + ASSERT_FALSE(q.push(N+1)); + ASSERT_FALSE(q.push_top(N+1)); + ASSERT_EQ((size_t)N, q.size()); + ASSERT_FALSE(q.empty()); + ASSERT_TRUE(q.full()); + + for (int i = 1; i <= N; ++i) { + ASSERT_EQ(N, *q.top()); + ASSERT_EQ(i, *q.bottom()); + if (i % 2 == 0) { + int tmp = 0; + ASSERT_TRUE(q.pop_bottom(&tmp)); + ASSERT_EQ(tmp, i); + } else { + ASSERT_TRUE(q.pop_bottom()); + } + ASSERT_EQ((size_t)(N-i), q.size()); + } + ASSERT_EQ(0ul, q.size()); + ASSERT_TRUE(q.empty()); + ASSERT_FALSE(q.full()); + ASSERT_FALSE(q.pop()); +} + +} // anonymous namespace diff --git a/test/brpc_adaptive_class_unittest.cpp b/test/brpc_adaptive_class_unittest.cpp new file mode 100644 index 0000000..18128f2 --- /dev/null +++ b/test/brpc_adaptive_class_unittest.cpp @@ -0,0 +1,75 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: 2019/04/16 23:41:04 + +#include +#include "brpc/adaptive_max_concurrency.h" +#include "brpc/adaptive_protocol_type.h" +#include "brpc/adaptive_connection_type.h" + +const std::string kAutoCL = "aUto"; +const std::string kHttp = "hTTp"; +const std::string kPooled = "PoOled"; + +TEST(AdaptiveMaxConcurrencyTest, ShouldConvertCorrectly) { + brpc::AdaptiveMaxConcurrency amc(0); + + EXPECT_EQ(brpc::AdaptiveMaxConcurrency::UNLIMITED(), amc.type()); + EXPECT_EQ(brpc::AdaptiveMaxConcurrency::UNLIMITED(), amc.value()); + EXPECT_EQ(0, int(amc)); + EXPECT_TRUE(amc == brpc::AdaptiveMaxConcurrency::UNLIMITED()); + + amc = 10; + EXPECT_EQ(brpc::AdaptiveMaxConcurrency::CONSTANT(), amc.type()); + EXPECT_EQ("10", amc.value()); + EXPECT_EQ(10, int(amc)); + EXPECT_EQ(amc, "10"); + + amc = kAutoCL; + EXPECT_EQ(kAutoCL, amc.type()); + EXPECT_EQ(kAutoCL, amc.value()); + EXPECT_EQ(int(amc), -1); + EXPECT_TRUE(amc == "auto"); +} + +TEST(AdaptiveProtocolTypeTest, ShouldConvertCorrectly) { + brpc::AdaptiveProtocolType apt; + + apt = kHttp; + EXPECT_EQ(apt, brpc::ProtocolType::PROTOCOL_HTTP); + EXPECT_NE(apt, brpc::ProtocolType::PROTOCOL_BAIDU_STD); + + apt = brpc::ProtocolType::PROTOCOL_HTTP; + EXPECT_EQ(apt, brpc::ProtocolType::PROTOCOL_HTTP); + EXPECT_NE(apt, brpc::ProtocolType::PROTOCOL_BAIDU_STD); +} + +TEST(AdaptiveConnectionTypeTest, ShouldConvertCorrectly) { + brpc::AdaptiveConnectionType act; + + act = brpc::ConnectionType::CONNECTION_TYPE_POOLED; + EXPECT_EQ(act, brpc::ConnectionType::CONNECTION_TYPE_POOLED); + EXPECT_NE(act, brpc::ConnectionType::CONNECTION_TYPE_SINGLE); + + act = kPooled; + EXPECT_EQ(act, brpc::ConnectionType::CONNECTION_TYPE_POOLED); + EXPECT_NE(act, brpc::ConnectionType::CONNECTION_TYPE_SINGLE); +} + diff --git a/test/brpc_alpn_protocol_unittest.cpp b/test/brpc_alpn_protocol_unittest.cpp new file mode 100644 index 0000000..3e0fd13 --- /dev/null +++ b/test/brpc_alpn_protocol_unittest.cpp @@ -0,0 +1,131 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include + +#include "gtest/gtest.h" +#include "gflags/gflags.h" + +#include "brpc/channel.h" +#include "brpc/details/ssl_helper.h" +#include "brpc/server.h" +#include "butil/endpoint.h" +#include "butil/fd_guard.h" +#include "echo.pb.h" + +DEFINE_string(listen_addr, "0.0.0.0:8011", "Server listen address."); + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + ::GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + return RUN_ALL_TESTS(); +} + +namespace { + +class EchoServerImpl : public test::EchoService { +public: + virtual void Echo(google::protobuf::RpcController* controller, + const ::test::EchoRequest* request, + test::EchoResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + response->set_message(request->message()); + + brpc::Controller* cntl = static_cast(controller); + LOG(INFO) << "protocol:" << cntl->request_protocol(); + } +}; + +class ALPNTest : public testing::Test { +public: + ALPNTest() = default; + virtual ~ALPNTest() = default; + + virtual void SetUp() override { + // Start brpc server with SSL + brpc::ServerOptions server_options; + auto&& ssl_options = server_options.mutable_ssl_options(); + ssl_options->default_cert.certificate = "cert1.crt"; + ssl_options->default_cert.private_key = "cert1.key"; + ssl_options->alpns = "http, h2, baidu_std"; + + ASSERT_EQ(0, _server.AddService(&_echo_server_impl, + brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, _server.Start(FLAGS_listen_addr.data(), &server_options)); + } + + virtual void TearDown() override { + _server.Stop(0); + _server.Join(); + } + + std::string HandshakeWithServer(std::vector alpns) { + // Init client ssl ctx and set alpn. + brpc::ChannelSSLOptions options; + SSL_CTX* ssl_ctx = brpc::CreateClientSSLContext(options); + EXPECT_NE(nullptr, ssl_ctx); + + std::string raw_alpn; + for (auto&& alpn : alpns) { + raw_alpn.append(brpc::ALPNProtocolToString(brpc::AdaptiveProtocolType(alpn))); + } + SSL_CTX_set_alpn_protos(ssl_ctx, + reinterpret_cast(raw_alpn.data()), raw_alpn.size()); + + // TCP connect. + butil::EndPoint endpoint; + butil::str2endpoint(FLAGS_listen_addr.data(), &endpoint); + + int cli_fd = butil::tcp_connect(endpoint, nullptr); + butil::fd_guard guard(cli_fd); + EXPECT_NE(0, cli_fd); + + // SSL handshake. + SSL* ssl = brpc::CreateSSLSession(ssl_ctx, 0, cli_fd, false); + EXPECT_NE(nullptr, ssl); + EXPECT_EQ(1, SSL_do_handshake(ssl)); + + // Get handshake result. + const unsigned char* select_alpn = nullptr; + unsigned int len = 0; + SSL_get0_alpn_selected(ssl, &select_alpn, &len); + std::string result(reinterpret_cast(select_alpn), len); + + SSL_free(ssl); + SSL_CTX_free(ssl_ctx); + return result; + } + +private: + brpc::Server _server; + EchoServerImpl _echo_server_impl; +}; + +TEST_F(ALPNTest, Server) { + // Server alpn support h2 http baidu_std, test the following case: + // 1. Client provides 1 protocol which is in the list supported by the server. + // 2. Server select protocol according to priority. + // 3. Server does not support the protocol provided by the client. + + EXPECT_EQ("baidu_std", ALPNTest::HandshakeWithServer({"baidu_std"})); + EXPECT_EQ("h2", ALPNTest::HandshakeWithServer({"baidu_std", "h2"})); + EXPECT_EQ("", ALPNTest::HandshakeWithServer({"nshead"})); +} + +} // namespace diff --git a/test/brpc_auto_concurrency_limiter_unittest.cpp b/test/brpc_auto_concurrency_limiter_unittest.cpp new file mode 100644 index 0000000..ac572b8 --- /dev/null +++ b/test/brpc_auto_concurrency_limiter_unittest.cpp @@ -0,0 +1,168 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/auto_concurrency_limiter.h" +#include "butil/time.h" +#include + +namespace brpc { +namespace policy { + +DECLARE_int32(auto_cl_sample_window_size_ms); +DECLARE_int32(auto_cl_min_sample_count); +DECLARE_int32(auto_cl_max_sample_count); +DECLARE_bool(auto_cl_enable_error_punish); +DECLARE_double(auto_cl_fail_punish_ratio); +DECLARE_double(auto_cl_error_rate_punish_threshold); + +} // namespace policy +} // namespace brpc + +class AutoConcurrencyLimiterTest : public ::testing::Test { +protected: + void SetUp() override { + // Save original values + orig_sample_window_size_ms_ = brpc::policy::FLAGS_auto_cl_sample_window_size_ms; + orig_min_sample_count_ = brpc::policy::FLAGS_auto_cl_min_sample_count; + orig_max_sample_count_ = brpc::policy::FLAGS_auto_cl_max_sample_count; + orig_enable_error_punish_ = brpc::policy::FLAGS_auto_cl_enable_error_punish; + orig_fail_punish_ratio_ = brpc::policy::FLAGS_auto_cl_fail_punish_ratio; + orig_error_rate_threshold_ = brpc::policy::FLAGS_auto_cl_error_rate_punish_threshold; + + // Set test-friendly values + brpc::policy::FLAGS_auto_cl_sample_window_size_ms = 1000; + brpc::policy::FLAGS_auto_cl_min_sample_count = 5; + brpc::policy::FLAGS_auto_cl_max_sample_count = 200; + brpc::policy::FLAGS_auto_cl_enable_error_punish = true; + brpc::policy::FLAGS_auto_cl_fail_punish_ratio = 1.0; + } + + void TearDown() override { + // Restore original values + brpc::policy::FLAGS_auto_cl_sample_window_size_ms = orig_sample_window_size_ms_; + brpc::policy::FLAGS_auto_cl_min_sample_count = orig_min_sample_count_; + brpc::policy::FLAGS_auto_cl_max_sample_count = orig_max_sample_count_; + brpc::policy::FLAGS_auto_cl_enable_error_punish = orig_enable_error_punish_; + brpc::policy::FLAGS_auto_cl_fail_punish_ratio = orig_fail_punish_ratio_; + brpc::policy::FLAGS_auto_cl_error_rate_punish_threshold = orig_error_rate_threshold_; + } + +private: + int32_t orig_sample_window_size_ms_; + int32_t orig_min_sample_count_; + int32_t orig_max_sample_count_; + bool orig_enable_error_punish_; + double orig_fail_punish_ratio_; + double orig_error_rate_threshold_; +}; + +// Helper function to add samples and trigger window completion +// Uses synthetic timestamps instead of sleeping for faster, deterministic tests. +// The final successful sample is used as the trigger, so actual counts match +// succ_count/fail_count exactly (preserving intended error rates). +void AddSamplesAndTriggerWindow(brpc::policy::AutoConcurrencyLimiter& limiter, + int succ_count, int64_t succ_latency, + int fail_count, int64_t fail_latency) { + ASSERT_GT(succ_count, 0) << "Need at least 1 success to trigger window"; + int64_t now = butil::cpuwide_time_us(); + + // Add successful samples (reserve one for the trigger) + for (int i = 0; i < succ_count - 1; ++i) { + limiter.AddSample(0, succ_latency, now); + } + // Add failed samples + for (int i = 0; i < fail_count; ++i) { + limiter.AddSample(1, fail_latency, now); + } + + // Advance timestamp past window expiry instead of sleeping + int64_t after_window = now + brpc::policy::FLAGS_auto_cl_sample_window_size_ms * 1000 + 1000; + + // Use the final success sample to trigger window submission + limiter.AddSample(0, succ_latency, after_window); +} + +// Test 1: Backward compatibility - threshold=0 preserves original punishment behavior +TEST_F(AutoConcurrencyLimiterTest, ThresholdZeroPreservesOriginalBehavior) { + brpc::policy::FLAGS_auto_cl_error_rate_punish_threshold = 0; + brpc::policy::FLAGS_auto_cl_sample_window_size_ms = 10; + + brpc::policy::AutoConcurrencyLimiter limiter; + AddSamplesAndTriggerWindow(limiter, 90, 100, 10, 1000); + + // 10% error rate, threshold=0 means full punishment applied + // avg_latency = ceil((10*1000 + 90*100) / 90) = ceil(211.1) = 212us + ASSERT_GT(limiter._min_latency_us, 180); + ASSERT_LT(limiter._min_latency_us, 250); +} + +// Test 2: Dead zone - error rate below threshold produces zero punishment +TEST_F(AutoConcurrencyLimiterTest, BelowThresholdZeroPunishment) { + brpc::policy::FLAGS_auto_cl_error_rate_punish_threshold = 0.2; // 20% threshold + brpc::policy::FLAGS_auto_cl_sample_window_size_ms = 10; + + brpc::policy::AutoConcurrencyLimiter limiter; + AddSamplesAndTriggerWindow(limiter, 90, 100, 10, 1000); + + // 10% error rate < 20% threshold, punishment should be zero + // avg_latency = 90*100 / 90 = 100us (no inflation) + ASSERT_GT(limiter._min_latency_us, 80); + ASSERT_LT(limiter._min_latency_us, 130); +} + +// Test 3: Boundary - error rate exactly at threshold produces zero punishment +TEST_F(AutoConcurrencyLimiterTest, ExactlyAtThresholdZeroPunishment) { + brpc::policy::FLAGS_auto_cl_error_rate_punish_threshold = 0.1; // 10% threshold + brpc::policy::FLAGS_auto_cl_sample_window_size_ms = 10; + + brpc::policy::AutoConcurrencyLimiter limiter; + AddSamplesAndTriggerWindow(limiter, 90, 100, 10, 1000); + + // 10% error rate == 10% threshold, punishment should be zero + // avg_latency = 90*100 / 90 = 100us + ASSERT_GT(limiter._min_latency_us, 80); + ASSERT_LT(limiter._min_latency_us, 130); +} + +// Test 4: Linear scaling - above threshold, punishment scales proportionally +TEST_F(AutoConcurrencyLimiterTest, AboveThresholdLinearScaling) { + brpc::policy::FLAGS_auto_cl_error_rate_punish_threshold = 0.1; // 10% threshold + brpc::policy::FLAGS_auto_cl_sample_window_size_ms = 10; + + // Case A: 50% error rate + // punish_factor = (0.5 - 0.1) / (1.0 - 0.1) = 4/9 ≈ 0.444 + // failed_punish = 50 * 1000 * (4/9) = 22222.2us + // avg_latency = ceil((22222.2 + 50*100) / 50) = ceil(544.4) = 545us + { + brpc::policy::AutoConcurrencyLimiter limiter; + AddSamplesAndTriggerWindow(limiter, 50, 100, 50, 1000); + ASSERT_GT(limiter._min_latency_us, 450); + ASSERT_LT(limiter._min_latency_us, 650); + } + + // Case B: 90% error rate (near full punishment) + // punish_factor = (0.9 - 0.1) / (1.0 - 0.1) = 8/9 ≈ 0.889 + // failed_punish = 90 * 1000 * (8/9) = 80000us + // avg_latency = ceil((80000 + 10*100) / 10) = ceil(8100) = 8100us + { + brpc::policy::AutoConcurrencyLimiter limiter; + AddSamplesAndTriggerWindow(limiter, 10, 100, 90, 1000); + ASSERT_GT(limiter._min_latency_us, 7000); + ASSERT_LT(limiter._min_latency_us, 9000); + } +} + diff --git a/test/brpc_block_pool_unittest.cpp b/test/brpc_block_pool_unittest.cpp new file mode 100644 index 0000000..f65f2a0 --- /dev/null +++ b/test/brpc_block_pool_unittest.cpp @@ -0,0 +1,219 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#if BRPC_WITH_RDMA +#include "bthread/bthread.h" +#include "butil/time.h" +#include "brpc/rdma/block_pool.h" + +class BlockPoolTest : public ::testing::Test { +protected: + BlockPoolTest() { } + ~BlockPoolTest() { } +}; + +namespace brpc { +namespace rdma { +DECLARE_int32(rdma_memory_pool_initial_size_mb); +DECLARE_int32(rdma_memory_pool_increase_size_mb); +DECLARE_int32(rdma_memory_pool_max_regions); +DECLARE_int32(rdma_memory_pool_buckets); +extern void DestroyBlockPool(); +extern int GetBlockType(void* buf); +extern size_t GetGlobalLen(int block_type); +extern size_t GetRegionNum(); +} +} + +using namespace brpc::rdma; + +static uint32_t DummyCallback(void*, size_t) { + return 1; +} + +TEST_F(BlockPoolTest, single_thread) { + FLAGS_rdma_memory_pool_initial_size_mb = 1024; + FLAGS_rdma_memory_pool_increase_size_mb = 1024; + FLAGS_rdma_memory_pool_max_regions = 16; + FLAGS_rdma_memory_pool_buckets = 4; + EXPECT_TRUE(InitBlockPool(DummyCallback)); + + size_t num = 1024; + void* buf[num]; + for (size_t i = 0; i < num; ++i) { + buf[i] = AllocBlock(GetBlockSize(0)); + EXPECT_TRUE(buf[i] != NULL); + EXPECT_EQ(0, GetBlockType(buf[i])); + } + for (size_t i = 0; i < num; ++i) { + DeallocBlock(buf[i]); + buf[i] = NULL; + } + for (size_t i = 0; i < num; ++i) { + buf[i] = AllocBlock(GetBlockSize(0) + 1); + EXPECT_TRUE(buf[i] != NULL); + EXPECT_EQ(1, GetBlockType(buf[i])); + } + for (int i = num - 1; i >= 0; --i) { + DeallocBlock(buf[i]); + buf[i] = NULL; + } + for (size_t i = 0; i < num; ++i) { + buf[i] = AllocBlock(GetBlockSize(2)); + EXPECT_TRUE(buf[i] != NULL); + EXPECT_EQ(2, GetBlockType(buf[i])); + } + for (int i = num - 1; i >= 0; --i) { + DeallocBlock(buf[i]); + buf[i] = NULL; + } + + DestroyBlockPool(); +} + +static void* AllocAndDealloc(void* arg) { + uintptr_t i = (uintptr_t)arg; + int len = GetBlockSize(i % 3); + int iterations = 1000; + while (iterations > 0) { + void* buf = AllocBlock(len); + EXPECT_TRUE(buf != NULL); + EXPECT_EQ(i % 3, GetBlockType(buf)); + DeallocBlock(buf); + --iterations; + } + return NULL; +} + +TEST_F(BlockPoolTest, multiple_thread) { + FLAGS_rdma_memory_pool_initial_size_mb = 1024; + FLAGS_rdma_memory_pool_increase_size_mb = 1024; + FLAGS_rdma_memory_pool_max_regions = 16; + FLAGS_rdma_memory_pool_buckets = 4; + EXPECT_TRUE(InitBlockPool(DummyCallback)); + + uintptr_t thread_num = 32; + bthread_t tid[thread_num]; + bthread_attr_t attr = BTHREAD_ATTR_NORMAL; + uint64_t start_time = butil::cpuwide_time_us(); + for (uintptr_t i = 0; i < thread_num; ++i) { + ASSERT_EQ(0, bthread_start_background(&tid[i], &attr, AllocAndDealloc, (void*)i)); + } + for (uintptr_t i = 0; i < thread_num; ++i) { + ASSERT_EQ(0, bthread_join(tid[i], 0)); + } + LOG(INFO) << "Total time = " << butil::cpuwide_time_us() - start_time << "us"; + + DestroyBlockPool(); +} + +TEST_F(BlockPoolTest, extend) { + FLAGS_rdma_memory_pool_initial_size_mb = 64; + FLAGS_rdma_memory_pool_increase_size_mb = 64; + FLAGS_rdma_memory_pool_max_regions = 16; + FLAGS_rdma_memory_pool_buckets = 1; + EXPECT_TRUE(InitBlockPool(DummyCallback)); + + EXPECT_EQ(1, GetRegionNum()); + size_t num = 15 * 64 * 1024 * 1024 / GetBlockSize(2); + void* buf[num]; + for (size_t i = 0; i < num; ++i) { + buf[i] = AllocBlock(65537); + EXPECT_TRUE(buf[i] != NULL); + } + EXPECT_EQ(16, GetRegionNum()); + for (size_t i = 0; i < num; ++i) { + DeallocBlock(buf[i]); + } + EXPECT_EQ(16, GetRegionNum()); + + DestroyBlockPool(); +} + +TEST_F(BlockPoolTest, memory_not_enough) { + FLAGS_rdma_memory_pool_initial_size_mb = 64; + FLAGS_rdma_memory_pool_increase_size_mb = 64; + FLAGS_rdma_memory_pool_max_regions = 2; + FLAGS_rdma_memory_pool_buckets = 1; + EXPECT_TRUE(InitBlockPool(DummyCallback)); + + EXPECT_EQ(1, GetRegionNum()); + size_t num = 64 * 1024 * 1024 / GetBlockSize(2); + void* buf[num]; + for (size_t i = 0; i < num; ++i) { + buf[i] = AllocBlock(65537); + EXPECT_TRUE(buf[i] != NULL); + } + EXPECT_EQ(2, GetRegionNum()); + void* tmp = AllocBlock(65536); + EXPECT_EQ(ENOMEM, errno); + EXPECT_EQ(0, GetRegionId(tmp)); + for (size_t i = 0; i < num; ++i) { + DeallocBlock(buf[i]); + } + EXPECT_EQ(2, GetRegionNum()); + + DestroyBlockPool(); +} + +TEST_F(BlockPoolTest, invalid_use) { + FLAGS_rdma_memory_pool_initial_size_mb = 64; + FLAGS_rdma_memory_pool_increase_size_mb = 64; + FLAGS_rdma_memory_pool_max_regions = 2; + FLAGS_rdma_memory_pool_buckets = 1; + EXPECT_TRUE(InitBlockPool(DummyCallback)); + + void* buf = AllocBlock(0); + EXPECT_EQ(NULL, buf); + EXPECT_EQ(EINVAL, errno); + + buf = AllocBlock(GetBlockSize(2) + 1); + EXPECT_EQ(NULL, buf); + EXPECT_EQ(EINVAL, errno); + + errno = 0; + DeallocBlock(NULL); + EXPECT_EQ(EINVAL, errno); + + DestroyBlockPool(); +} + +TEST_F(BlockPoolTest, dump_info) { + FLAGS_rdma_memory_pool_initial_size_mb = 64; + FLAGS_rdma_memory_pool_increase_size_mb = 64; + FLAGS_rdma_memory_pool_max_regions = 2; + FLAGS_rdma_memory_pool_buckets = 4; + EXPECT_TRUE(InitBlockPool(DummyCallback)); + DumpMemoryPoolInfo(std::cout); + void* buf = AllocBlock(8192); + DumpMemoryPoolInfo(std::cout); + DeallocBlock(buf); + DumpMemoryPoolInfo(std::cout); + DestroyBlockPool(); +} + +#endif // if BRPC_WITH_RDMA + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + return RUN_ALL_TESTS(); +} diff --git a/test/brpc_builtin_service_unittest.cpp b/test/brpc_builtin_service_unittest.cpp new file mode 100644 index 0000000..da4d6f1 --- /dev/null +++ b/test/brpc_builtin_service_unittest.cpp @@ -0,0 +1,1018 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: Sun Jul 13 15:04:18 CST 2014 + +#include +#include +#include +#include +#include +#include +#include "gperftools_helper.h" +#include "butil/time.h" +#include "butil/macros.h" +#include "brpc/socket.h" +#include "brpc/server.h" +#include "brpc/channel.h" +#include "brpc/controller.h" +#include "brpc/span.h" +#include "brpc/reloadable_flags.h" +#include "brpc/builtin/version_service.h" +#include "brpc/builtin/health_service.h" +#include "brpc/builtin/list_service.h" +#include "brpc/builtin/status_service.h" +#include "brpc/builtin/threads_service.h" +#include "brpc/builtin/vlog_service.h" +#include "brpc/builtin/index_service.h" // IndexService +#include "brpc/builtin/connections_service.h" // ConnectionsService +#include "brpc/builtin/flags_service.h" // FlagsService +#include "brpc/builtin/vars_service.h" // VarsService +#include "brpc/builtin/rpcz_service.h" // RpczService +#include "brpc/builtin/dir_service.h" // DirService +#include "brpc/builtin/pprof_service.h" // PProfService +#include "brpc/builtin/bthreads_service.h" // BthreadsService +#include "brpc/builtin/ids_service.h" // IdsService +#include "brpc/builtin/sockets_service.h" // SocketsService +#include "brpc/builtin/memory_service.h" +#include "brpc/builtin/common.h" +#include "brpc/builtin/bad_method_service.h" +#include "echo.pb.h" +#include "brpc/grpc_health_check.pb.h" +#include "json2pb/pb_to_json.h" + +DEFINE_bool(foo, false, "Flags for UT"); +BRPC_VALIDATE_GFLAG(foo, brpc::PassValidate); + +namespace brpc { +DECLARE_bool(enable_rpcz); +DECLARE_bool(rpcz_hex_log_id); +DECLARE_int32(idle_timeout_second); +} // namespace rpc + +#ifdef BRPC_BTHREAD_TRACER +namespace bthread { +DECLARE_int32(signal_number_for_trace); +} +#endif + +int main(int argc, char* argv[]) { + brpc::FLAGS_idle_timeout_second = 0; + testing::InitGoogleTest(&argc, argv); +#ifdef BRPC_BTHREAD_TRACER + // test using signal number other than SIGURG + bthread::FLAGS_signal_number_for_trace = SIGUSR2; +#endif + return RUN_ALL_TESTS(); +} + +class EchoServiceImpl : public test::EchoService { +public: + EchoServiceImpl() {} + virtual ~EchoServiceImpl() {} + virtual void Echo(google::protobuf::RpcController* cntl_base, + const test::EchoRequest* req, + test::EchoResponse* res, + google::protobuf::Closure* done) { + brpc::Controller* cntl = + static_cast(cntl_base); + brpc::ClosureGuard done_guard(done); + TRACEPRINTF("MyAnnotation: %ld", cntl->log_id()); + if (req->sleep_us() > 0) { + bthread_usleep(req->sleep_us()); + } + char buf[32]; + snprintf(buf, sizeof(buf), "%" PRIu64, cntl->trace_id()); + res->set_message(buf); + } +}; + +class ClosureChecker : public google::protobuf::Closure { +public: + ClosureChecker() : _count(1) {} + ~ClosureChecker() { EXPECT_EQ(0, _count); } + + void Run() { + --_count; + } + +private: + int _count; +}; + +void MyVLogSite() { + VLOG(3) << "This is a VLOG!"; +} + +void CheckContent(const brpc::Controller& cntl, const char* name) { + const std::string& content = cntl.response_attachment().to_string(); + std::size_t pos = content.find(name); + ASSERT_TRUE(pos != std::string::npos) << "name=" << name << " content=" << content; +} + +void CheckErrorText(const brpc::Controller& cntl, const char* error) { + std::size_t pos = cntl.ErrorText().find(error); + ASSERT_TRUE(pos != std::string::npos) << "error=" << error; +} + +void CheckFieldInContent(const brpc::Controller& cntl, + const char* name, int32_t expect) { + const std::string& content = cntl.response_attachment().to_string(); + std::size_t pos = content.find(name); + ASSERT_TRUE(pos != std::string::npos); + + int32_t val = 0; + ASSERT_EQ(1, sscanf(content.c_str() + pos + strlen(name), "%d", &val)); + ASSERT_EQ(expect, val) << "name=" << name; +} + +void CheckAnnotation(const brpc::Controller& cntl, int64_t expect) { + const std::string& content = cntl.response_attachment().to_string(); + std::string expect_str; + butil::string_printf(&expect_str, "MyAnnotation: %" PRId64, expect); + std::size_t pos = content.find(expect_str); + ASSERT_TRUE(pos != std::string::npos) << expect; +} + +void CheckTraceId(const brpc::Controller& cntl, + const std::string& expect_id_str) { + const std::string& content = cntl.response_attachment().to_string(); + std::string expect_str = std::string(brpc::TRACE_ID_STR) + "=" + expect_id_str; + std::size_t pos = content.find(expect_str); + ASSERT_TRUE(pos != std::string::npos) << expect_str; +} + +class BuiltinServiceTest : public ::testing::Test{ +protected: + BuiltinServiceTest(){}; + virtual ~BuiltinServiceTest(){}; + virtual void SetUp() { EXPECT_EQ(0, _server.AddBuiltinServices()); } + virtual void TearDown() { StopAndJoin(); } + + void StopAndJoin() { + _server.Stop(0); + _server.Join(); + _server.ClearServices(); + } + + void SetUpController(brpc::Controller* cntl, bool use_html) const { + cntl->_server = &_server; + if (use_html) { + cntl->http_request().SetHeader( + brpc::USER_AGENT_STR, "just keep user agent non-empty"); + } + } + + void TestIndex(bool use_html) { + std::string expect_type = (use_html ? "text/html" : "text/plain"); + brpc::IndexService service; + brpc::IndexRequest req; + brpc::IndexResponse res; + brpc::Controller cntl; + ClosureChecker done; + SetUpController(&cntl, use_html); + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + EXPECT_EQ(expect_type, cntl.http_response().content_type()); + } + + void TestStatus(bool use_html) { + std::string expect_type = (use_html ? "text/html" : "text/plain"); + brpc::StatusService service; + brpc::StatusRequest req; + brpc::StatusResponse res; + brpc::Controller cntl; + ClosureChecker done; + SetUpController(&cntl, use_html); + EchoServiceImpl echo_svc; + ASSERT_EQ(0, _server.AddService( + &echo_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + EXPECT_EQ(expect_type, cntl.http_response().content_type()); + ASSERT_EQ(0, _server.RemoveService(&echo_svc)); + } + + void TestVLog(bool use_html) { +#if !BRPC_WITH_GLOG + std::string expect_type = (use_html ? "text/html" : "text/plain"); + brpc::VLogService service; + brpc::VLogRequest req; + brpc::VLogResponse res; + brpc::Controller cntl; + ClosureChecker done; + SetUpController(&cntl, use_html); + MyVLogSite(); + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + EXPECT_EQ(expect_type, cntl.http_response().content_type()); + CheckContent(cntl, "brpc_builtin_service_unittest"); +#endif + } + + void TestConnections(bool use_html) { + std::string expect_type = (use_html ? "text/html" : "text/plain"); + brpc::ConnectionsService service; + brpc::ConnectionsRequest req; + brpc::ConnectionsResponse res; + brpc::Controller cntl; + ClosureChecker done; + SetUpController(&cntl, use_html); + butil::EndPoint ep; + ASSERT_EQ(0, str2endpoint("127.0.0.1:9798", &ep)); + ASSERT_EQ(0, _server.Start(ep, NULL)); + int self_port = -1; + const int cfd = tcp_connect(ep, &self_port); + ASSERT_GT(cfd, 0); + char buf[64]; + snprintf(buf, sizeof(buf), "127.0.0.1:%d", self_port); + usleep(100000); + + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + EXPECT_EQ(expect_type, cntl.http_response().content_type()); + CheckContent(cntl, buf); + CheckFieldInContent(cntl, "channel_connection_count: ", 0); + + close(cfd); + StopAndJoin(); + } + + void TestBadMethod(bool use_html) { + std::string expect_type = (use_html ? "text/html" : "text/plain"); + brpc::BadMethodService service; + brpc::BadMethodResponse res; + { + ClosureChecker done; + brpc::Controller cntl; + SetUpController(&cntl, use_html); + brpc::BadMethodRequest req; + req.set_service_name( + brpc::PProfService::descriptor()->full_name()); + service.no_method(&cntl, &req, &res, &done); + EXPECT_EQ(brpc::ENOMETHOD, cntl.ErrorCode()); + EXPECT_EQ(expect_type, cntl.http_response().content_type()); + CheckErrorText(cntl, "growth"); + } + } + + void TestFlags(bool use_html) { + std::string expect_type = (use_html ? "text/html" : "text/plain"); + brpc::FlagsService service; + brpc::FlagsRequest req; + brpc::FlagsResponse res; + { + ClosureChecker done; + brpc::Controller cntl; + SetUpController(&cntl, use_html); + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + EXPECT_EQ(expect_type, cntl.http_response().content_type()); + CheckContent(cntl, "bthread_concurrency"); + } + { + ClosureChecker done; + brpc::Controller cntl; + SetUpController(&cntl, use_html); + cntl.http_request()._unresolved_path = "foo"; + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + EXPECT_EQ(expect_type, cntl.http_response().content_type()); + CheckContent(cntl, "false"); + } + { + ClosureChecker done; + brpc::Controller cntl; + SetUpController(&cntl, use_html); + cntl.http_request()._unresolved_path = "foo"; + cntl.http_request().uri() + .SetQuery(brpc::SETVALUE_STR, "true"); + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + EXPECT_EQ(expect_type, cntl.http_response().content_type()); + } + { + ClosureChecker done; + brpc::Controller cntl; + SetUpController(&cntl, use_html); + cntl.http_request()._unresolved_path = "foo"; + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + EXPECT_EQ(expect_type, cntl.http_response().content_type()); + CheckContent(cntl, "true"); + } + } + + void TestRpcz(bool enable, bool hex, bool use_html) { + std::string expect_type = (use_html ? "text/html" : "text/plain"); + brpc::RpczService service; + brpc::RpczRequest req; + brpc::RpczResponse res; + if (!enable) { + { + ClosureChecker done; + brpc::Controller cntl; + SetUpController(&cntl, use_html); + service.disable(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + EXPECT_FALSE(brpc::FLAGS_enable_rpcz); + } + { + ClosureChecker done; + brpc::Controller cntl; + SetUpController(&cntl, use_html); + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + EXPECT_EQ(expect_type, + cntl.http_response().content_type()); + if (!use_html) { + CheckContent(cntl, "rpcz is not enabled"); + } + } + { + ClosureChecker done; + brpc::Controller cntl; + SetUpController(&cntl, use_html); + service.stats(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + if (!use_html) { + CheckContent(cntl, "rpcz is not enabled"); + } + } + return; + } + + { + ClosureChecker done; + brpc::Controller cntl; + SetUpController(&cntl, use_html); + service.enable(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + EXPECT_EQ(expect_type, cntl.http_response().content_type()); + EXPECT_TRUE(brpc::FLAGS_enable_rpcz); + } + + if (hex) { + ClosureChecker done; + brpc::Controller cntl; + SetUpController(&cntl, use_html); + service.hex_log_id(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + EXPECT_TRUE(brpc::FLAGS_rpcz_hex_log_id); + } else { + ClosureChecker done; + brpc::Controller cntl; + SetUpController(&cntl, use_html); + service.dec_log_id(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + EXPECT_FALSE(brpc::FLAGS_rpcz_hex_log_id); + } + + ASSERT_EQ(0, _server.AddService(new EchoServiceImpl(), + brpc::SERVER_OWNS_SERVICE)); + butil::EndPoint ep; + ASSERT_EQ(0, str2endpoint("127.0.0.1:9748", &ep)); + ASSERT_EQ(0, _server.Start(ep, NULL)); + brpc::Channel channel; + ASSERT_EQ(0, channel.Init(ep, NULL)); + test::EchoService_Stub stub(&channel); + int64_t log_id = 1234567890; + char querystr_buf[128]; + // Since LevelDB is unstable on jerkins, disable all the assertions here + { + // Find by trace_id + test::EchoRequest echo_req; + test::EchoResponse echo_res; + brpc::Controller echo_cntl; + echo_req.set_message("hello"); + echo_cntl.set_log_id(++log_id); + stub.Echo(&echo_cntl, &echo_req, &echo_res, NULL); + EXPECT_FALSE(echo_cntl.Failed()); + + // Wait for level db to commit span information + usleep(500000); + ClosureChecker done; + brpc::Controller cntl; + SetUpController(&cntl, use_html); + cntl.http_request().uri() + .SetQuery(brpc::TRACE_ID_STR, echo_res.message()); + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()) << cntl.ErrorText(); + EXPECT_EQ(expect_type, cntl.http_response().content_type()); + // CheckAnnotation(cntl, log_id); + } + + { + // Find by latency + test::EchoRequest echo_req; + test::EchoResponse echo_res; + brpc::Controller echo_cntl; + echo_req.set_message("hello"); + echo_req.set_sleep_us(150000); + echo_cntl.set_log_id(++log_id); + stub.Echo(&echo_cntl, &echo_req, &echo_res, NULL); + EXPECT_FALSE(echo_cntl.Failed()); + + // Wait for level db to commit span information + usleep(500000); + ClosureChecker done; + brpc::Controller cntl; + SetUpController(&cntl, use_html); + cntl.http_request().uri() + .SetQuery(brpc::MIN_LATENCY_STR, "100000"); + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()) << cntl.ErrorText(); + EXPECT_EQ(expect_type, cntl.http_response().content_type()); + // CheckTraceId(cntl, echo_res.message()); + } + + { + // Find by request size + test::EchoRequest echo_req; + test::EchoResponse echo_res; + brpc::Controller echo_cntl; + std::string request_str(1500, 'a'); + echo_req.set_message(request_str); + echo_cntl.set_log_id(++log_id); + stub.Echo(&echo_cntl, &echo_req, &echo_res, NULL); + EXPECT_FALSE(echo_cntl.Failed()); + + // Wait for level db to commit span information + usleep(500000); + ClosureChecker done; + brpc::Controller cntl; + SetUpController(&cntl, use_html); + cntl.http_request().uri() + .SetQuery(brpc::MIN_REQUEST_SIZE_STR, "1024"); + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()) << cntl.ErrorText(); + EXPECT_EQ(expect_type, cntl.http_response().content_type()); + // CheckTraceId(cntl, echo_res.message()); + } + + { + // Find by log id + test::EchoRequest echo_req; + test::EchoResponse echo_res; + brpc::Controller echo_cntl; + echo_req.set_message("hello"); + echo_cntl.set_log_id(++log_id); + stub.Echo(&echo_cntl, &echo_req, &echo_res, NULL); + EXPECT_FALSE(echo_cntl.Failed()); + + // Wait for level db to commit span information + usleep(500000); + ClosureChecker done; + brpc::Controller cntl; + SetUpController(&cntl, use_html); + snprintf(querystr_buf, sizeof(querystr_buf), "%" PRId64, log_id); + cntl.http_request().uri() + .SetQuery(brpc::LOG_ID_STR, querystr_buf); + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()) << cntl.ErrorText(); + EXPECT_EQ(expect_type, cntl.http_response().content_type()); + // CheckTraceId(cntl, echo_res.message()); + } + + { + ClosureChecker done; + brpc::Controller cntl; + SetUpController(&cntl, use_html); + service.stats(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + // CheckContent(cntl, "rpcz.id_db"); + // CheckContent(cntl, "rpcz.time_db"); + } + + StopAndJoin(); + } + +private: + brpc::Server _server; +}; + +TEST_F(BuiltinServiceTest, index) { + TestIndex(false); + TestIndex(true); +} + +TEST_F(BuiltinServiceTest, version) { + const std::string VERSION = "test_version"; + brpc::VersionService service(&_server); + brpc::VersionRequest req; + brpc::VersionResponse res; + brpc::Controller cntl; + ClosureChecker done; + _server.set_version(VERSION); + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + EXPECT_EQ(VERSION, cntl.response_attachment().to_string()); +} + +TEST_F(BuiltinServiceTest, health) { + const std::string HEALTH_STR = "OK"; + brpc::HealthService service; + brpc::HealthRequest req; + brpc::HealthResponse res; + brpc::Controller cntl; + SetUpController(&cntl, false); + ClosureChecker done; + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + EXPECT_EQ(HEALTH_STR, cntl.response_attachment().to_string()); +} + +class MyHealthReporter : public brpc::HealthReporter { +public: + void GenerateReport(brpc::Controller* cntl, + google::protobuf::Closure* done) { + cntl->response_attachment().append("i'm ok"); + done->Run(); + } +}; + +TEST_F(BuiltinServiceTest, customized_health) { + brpc::ServerOptions opt; + MyHealthReporter hr; + opt.health_reporter = &hr; + ASSERT_EQ(0, _server.Start(9798, &opt)); + brpc::HealthRequest req; + brpc::HealthResponse res; + brpc::ChannelOptions copt; + copt.protocol = brpc::PROTOCOL_HTTP; + brpc::Channel chan; + ASSERT_EQ(0, chan.Init("127.0.0.1:9798", &copt)); + brpc::Controller cntl; + cntl.http_request().uri() = "/health"; + chan.CallMethod(NULL, &cntl, &req, &res, NULL); + EXPECT_FALSE(cntl.Failed()) << cntl.ErrorText(); + EXPECT_EQ("i'm ok", cntl.response_attachment()); +} + +class MyGrpcHealthReporter : public brpc::HealthReporter { +public: + void GenerateReport(brpc::Controller* cntl, + google::protobuf::Closure* done) { + grpc::health::v1::HealthCheckResponse response; + response.set_status(grpc::health::v1::HealthCheckResponse_ServingStatus_UNKNOWN); + + if (cntl->response()) { + cntl->response()->CopyFrom(response); + } else { + std::string json; + json2pb::ProtoMessageToJson(response, &json); + cntl->http_response().set_content_type("application/json"); + cntl->response_attachment().append(json); + } + done->Run(); + } +}; + +TEST_F(BuiltinServiceTest, normal_grpc_health) { + brpc::ServerOptions opt; + ASSERT_EQ(0, _server.Start(9798, &opt)); + + grpc::health::v1::HealthCheckResponse response; + grpc::health::v1::HealthCheckRequest request; + request.set_service("grpc_req_from_brpc"); + brpc::Controller cntl; + brpc::ChannelOptions copt; + copt.protocol = "h2:grpc"; + brpc::Channel chan; + ASSERT_EQ(0, chan.Init("127.0.0.1:9798", &copt)); + grpc::health::v1::Health_Stub stub(&chan); + stub.Check(&cntl, &request, &response, NULL); + EXPECT_FALSE(cntl.Failed()) << cntl.ErrorText(); + EXPECT_EQ(response.status(), grpc::health::v1::HealthCheckResponse_ServingStatus_SERVING); + + response.Clear(); + brpc::Controller cntl1; + cntl1.http_request().uri() = "/grpc.health.v1.Health/Check"; + chan.CallMethod(NULL, &cntl1, &request, &response, NULL); + EXPECT_FALSE(cntl.Failed()) << cntl.ErrorText(); + EXPECT_EQ(response.status(), grpc::health::v1::HealthCheckResponse_ServingStatus_SERVING); +} + +TEST_F(BuiltinServiceTest, customized_grpc_health) { + brpc::ServerOptions opt; + MyGrpcHealthReporter hr; + opt.health_reporter = &hr; + ASSERT_EQ(0, _server.Start(9798, &opt)); + + grpc::health::v1::HealthCheckResponse response; + grpc::health::v1::HealthCheckRequest request; + request.set_service("grpc_req_from_brpc"); + brpc::Controller cntl; + + brpc::ChannelOptions copt; + copt.protocol = "h2:grpc"; + brpc::Channel chan; + ASSERT_EQ(0, chan.Init("127.0.0.1:9798", &copt)); + + grpc::health::v1::Health_Stub stub(&chan); + stub.Check(&cntl, &request, &response, NULL); + + EXPECT_FALSE(cntl.Failed()) << cntl.ErrorText(); + EXPECT_EQ(response.status(), grpc::health::v1::HealthCheckResponse_ServingStatus_UNKNOWN); +} + +TEST_F(BuiltinServiceTest, status) { + TestStatus(false); + TestStatus(true); +} + +TEST_F(BuiltinServiceTest, list) { + brpc::ListService service(&_server); + brpc::ListRequest req; + brpc::ListResponse res; + brpc::Controller cntl; + ClosureChecker done; + ASSERT_EQ(0, _server.AddService(new EchoServiceImpl(), + brpc::SERVER_OWNS_SERVICE)); + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + ASSERT_EQ(1, res.service_size()); + EXPECT_EQ(test::EchoService::descriptor()->name(), res.service(0).name()); +} + +void* sleep_thread(void*) { + sleep(1); + return NULL; +} + +TEST_F(BuiltinServiceTest, threads) { + brpc::ThreadsService service; + brpc::ThreadsRequest req; + brpc::ThreadsResponse res; + brpc::Controller cntl; + ClosureChecker done; + pthread_t tid; + ASSERT_EQ(0, pthread_create(&tid, NULL, sleep_thread, NULL)); + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + // Doesn't work under gcc 4.8.2 + // CheckContent(cntl, "sleep_thread"); + pthread_join(tid, NULL); +} + +TEST_F(BuiltinServiceTest, vlog) { + TestVLog(false); + TestVLog(true); +} + +TEST_F(BuiltinServiceTest, connections) { + TestConnections(false); + TestConnections(true); +} + +TEST_F(BuiltinServiceTest, flags) { + TestFlags(false); + TestFlags(true); +} + +TEST_F(BuiltinServiceTest, bad_method) { + TestBadMethod(false); + TestBadMethod(true); +} + +TEST_F(BuiltinServiceTest, vars) { + // Start server to show bvars inside + ASSERT_EQ(0, _server.Start("127.0.0.1:9798", NULL)); + brpc::VarsService service; + brpc::VarsRequest req; + brpc::VarsResponse res; + { + ClosureChecker done; + brpc::Controller cntl; + bvar::Adder myvar; + myvar.expose("myvar"); + myvar << 9; + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + CheckFieldInContent(cntl, "myvar : ", 9); + } + { + ClosureChecker done; + brpc::Controller cntl; + cntl.http_request()._unresolved_path = "iobuf*"; + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + CheckContent(cntl, "iobuf_block_count"); + } +} + +TEST_F(BuiltinServiceTest, rpcz) { + for (int i = 0; i <= 1; ++i) { // enable rpcz + for (int j = 0; j <= 1; ++j) { // hex log id + for (int k = 0; k <= 1; ++k) { // use html + TestRpcz(i, j, k); + } + } + } +} + +#if defined(BRPC_ENABLE_CPU_PROFILER) || defined(BAIDU_RPC_ENABLE_CPU_PROFILER) +TEST_F(BuiltinServiceTest, pprof) { + brpc::PProfService service; + { + ClosureChecker done; + brpc::Controller cntl; + cntl.http_request().uri().SetQuery("seconds", "1"); + service.profile(&cntl, NULL, NULL, &done); + // Just for loading symbols in gperftools/profiler.h + ProfilerFlush(); + EXPECT_FALSE(cntl.Failed()) << cntl.ErrorText(); + EXPECT_GT(cntl.response_attachment().length(), 0ul); + } + { + ClosureChecker done; + brpc::Controller cntl; + service.heap(&cntl, NULL, NULL, &done); + const int rc = getenv("TCMALLOC_SAMPLE_PARAMETER") != nullptr ? 0 : brpc::ENOMETHOD; + EXPECT_EQ(rc, cntl.ErrorCode()) << cntl.ErrorText(); + } + { + ClosureChecker done; + brpc::Controller cntl; + service.growth(&cntl, NULL, NULL, &done); + // linked tcmalloc in UT + EXPECT_EQ(0, cntl.ErrorCode()) << cntl.ErrorText(); + } + { + ClosureChecker done; + brpc::Controller cntl; + service.symbol(&cntl, NULL, NULL, &done); + EXPECT_FALSE(cntl.Failed()); + CheckContent(cntl, "num_symbols"); + } + { + ClosureChecker done; + brpc::Controller cntl; + service.cmdline(&cntl, NULL, NULL, &done); + EXPECT_FALSE(cntl.Failed()); + CheckContent(cntl, "brpc_builtin_service_unittest"); + } +} +#endif // BRPC_ENABLE_CPU_PROFILER || BAIDU_RPC_ENABLE_CPU_PROFILER + +TEST_F(BuiltinServiceTest, dir) { + brpc::DirService service; + brpc::DirRequest req; + brpc::DirResponse res; + { + // Open root path + ClosureChecker done; + brpc::Controller cntl; + SetUpController(&cntl, true); + cntl.http_request()._unresolved_path = ""; + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + CheckContent(cntl, "tmp"); + } + { + // Open a specific file + ClosureChecker done; + brpc::Controller cntl; + SetUpController(&cntl, false); + cntl.http_request()._unresolved_path = "/usr/include/errno.h"; + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); +#if defined(OS_LINUX) + CheckContent(cntl, "ERRNO_H"); +#elif defined(OS_MACOSX) + CheckContent(cntl, "sys/errno.h"); +#endif + } + { + // Open a file that doesn't exist + ClosureChecker done; + brpc::Controller cntl; + SetUpController(&cntl, false); + cntl.http_request()._unresolved_path = "file_not_exist"; + service.default_method(&cntl, &req, &res, &done); + EXPECT_TRUE(cntl.Failed()); + CheckErrorText(cntl, "Cannot open"); + } +} + +TEST_F(BuiltinServiceTest, ids) { + brpc::IdsService service; + brpc::IdsRequest req; + brpc::IdsResponse res; + { + ClosureChecker done; + brpc::Controller cntl; + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + CheckContent(cntl, "Use /ids/"); + } + { + ClosureChecker done; + brpc::Controller cntl; + cntl.http_request()._unresolved_path = "not_valid"; + service.default_method(&cntl, &req, &res, &done); + EXPECT_TRUE(cntl.Failed()); + CheckErrorText(cntl, "is not a bthread_id"); + } + { + bthread_id_t id; + EXPECT_EQ(0, bthread_id_create(&id, NULL, NULL)); + ClosureChecker done; + brpc::Controller cntl; + std::string id_string; + butil::string_printf(&id_string, "%llu", (unsigned long long)id.value); + cntl.http_request()._unresolved_path = id_string; + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + CheckContent(cntl, "Status: UNLOCKED"); + } +} + +void* dummy_bthread(void*) { + bthread_usleep(1000000); + return NULL; +} + + +#ifdef BRPC_BTHREAD_TRACER +bool g_bthread_trace_start = false; +bool g_bthread_trace_stop = false; +void* bthread_trace(void*) { + g_bthread_trace_start = true; + while (!g_bthread_trace_stop) { + bthread_usleep(1000 * 100); + } + return NULL; +} +#endif // BRPC_BTHREAD_TRACER + +// check all living bthreads without need to specify bthread id +bool check_all_bthreads(bthread_t expected_th, bool enable_trace) { + brpc::BthreadsService service; + brpc::BthreadsRequest req; + brpc::BthreadsResponse res; + ClosureChecker done; + brpc::Controller cntl; + std::string all_string("all"); + if (enable_trace) { + all_string.append("?st=1"); + } + cntl.http_request()._unresolved_path = all_string; + cntl.http_request().uri().SetHttpURL("/bthreads/" + all_string); + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + const std::string& content = cntl.response_attachment().to_string(); + std::string expected_str = butil::string_printf("bthread=%llu", + (unsigned long long)expected_th); + bool ok = content.find("stop=0") != std::string::npos && + content.find(expected_str) != std::string::npos; + if (ok && enable_trace) { + ok = content.find("bthread_trace") != std::string::npos; + } else if (ok && !enable_trace) { + ok = content.find("bthread_trace") == std::string::npos; + } + return ok; +} + +TEST_F(BuiltinServiceTest, bthreads) { + brpc::BthreadsService service; + brpc::BthreadsRequest req; + brpc::BthreadsResponse res; + { + ClosureChecker done; + brpc::Controller cntl; + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + CheckContent(cntl, "Use /bthreads/"); + } + { + ClosureChecker done; + brpc::Controller cntl; + cntl.http_request()._unresolved_path = "not_valid"; + service.default_method(&cntl, &req, &res, &done); + EXPECT_TRUE(cntl.Failed()); + CheckErrorText(cntl, "is not a bthread id or all"); + } + { + ClosureChecker done; + brpc::Controller cntl; + cntl.http_request()._unresolved_path = "all"; + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + CheckContent(cntl, "stop=0"); + } + { + bthread_t th; + EXPECT_EQ(0, bthread_start_background(&th, NULL, dummy_bthread, NULL)); + ClosureChecker done; + brpc::Controller cntl; + std::string id_string; + butil::string_printf(&id_string, "%llu", (unsigned long long)th); + cntl.http_request()._unresolved_path = id_string; + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + CheckContent(cntl, "stop=0"); + } + +#ifdef BRPC_BTHREAD_TRACER + bool ok = false, check_all_ok = false; + for (int i = 0; i < 10; ++i) { + bthread_t th; + EXPECT_EQ(0, bthread_start_background(&th, NULL, bthread_trace, NULL)); + while (!g_bthread_trace_start) { + bthread_usleep(1000 * 10); + } + LOG(INFO) << "start bthread = " << th; + ClosureChecker done; + brpc::Controller cntl; + std::string id_string; + butil::string_printf(&id_string, "%llu?st=1", (unsigned long long)th); + cntl.http_request().uri().SetHttpURL("/bthreads/" + id_string); + cntl.http_request()._unresolved_path = id_string; + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + const std::string& content = cntl.response_attachment().to_string(); + ok = content.find("stop=0") != std::string::npos && + content.find("bthread_trace") != std::string::npos; + check_all_ok = check_all_bthreads(th, true) && check_all_bthreads(th, false); + g_bthread_trace_stop = true; + bthread_join(th, NULL); + // the `bthread_trace` bthread should not be queried now + EXPECT_TRUE(!check_all_bthreads(th, true) && !check_all_bthreads(th, false)); + if (ok && check_all_ok) { + break; + } + } + ASSERT_TRUE(ok && check_all_ok); +#endif // BRPC_BTHREAD_TRACER +} + +TEST_F(BuiltinServiceTest, sockets) { + brpc::SocketsService service; + brpc::SocketsRequest req; + brpc::SocketsResponse res; + { + ClosureChecker done; + brpc::Controller cntl; + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + CheckContent(cntl, "Use /sockets/"); + } + { + ClosureChecker done; + brpc::Controller cntl; + cntl.http_request()._unresolved_path = "not_valid"; + service.default_method(&cntl, &req, &res, &done); + EXPECT_TRUE(cntl.Failed()); + CheckErrorText(cntl, "is not a SocketId"); + } + { + brpc::SocketId id; + brpc::SocketOptions options; + EXPECT_EQ(0, brpc::Socket::Create(options, &id)); + ClosureChecker done; + brpc::Controller cntl; + std::string id_string; + butil::string_printf(&id_string, "%llu", (unsigned long long)id); + cntl.http_request()._unresolved_path = id_string; + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + CheckContent(cntl, "fd=-1"); + } +} + +#if defined(BRPC_ENABLE_CPU_PROFILER) || defined(BAIDU_RPC_ENABLE_CPU_PROFILER) +TEST_F(BuiltinServiceTest, memory) { + brpc::MemoryService service; + brpc::MemoryRequest req; + brpc::MemoryResponse res; + brpc::Controller cntl; + ClosureChecker done; + service.default_method(&cntl, &req, &res, &done); + EXPECT_FALSE(cntl.Failed()); + CheckContent(cntl, "generic.current_allocated_bytes"); + CheckContent(cntl, "generic.heap_size"); + CheckContent(cntl, "tcmalloc.current_total_thread_cache_bytes"); + CheckContent(cntl, "tcmalloc.central_cache_free_bytes"); + CheckContent(cntl, "tcmalloc.transfer_cache_free_bytes"); + CheckContent(cntl, "tcmalloc.thread_cache_free_bytes"); + CheckContent(cntl, "tcmalloc.pageheap_free_bytes"); + CheckContent(cntl, "tcmalloc.pageheap_unmapped_bytes"); +} +#endif // BRPC_ENABLE_CPU_PROFILER || BAIDU_RPC_ENABLE_CPU_PROFILER diff --git a/test/brpc_channel_unittest.cpp b/test/brpc_channel_unittest.cpp new file mode 100644 index 0000000..a01f11e --- /dev/null +++ b/test/brpc_channel_unittest.cpp @@ -0,0 +1,3423 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: Sun Jul 13 15:04:18 CST 2014 + +#include +#include +#include +#include +#include +#include "butil/time.h" +#include "butil/macros.h" +#include "butil/logging.h" +#include "butil/files/temp_file.h" +#include "brpc/socket.h" +#include "brpc/acceptor.h" +#include "brpc/server.h" +#include "brpc/policy/baidu_rpc_protocol.h" +#include "brpc/policy/baidu_rpc_meta.pb.h" +#include "brpc/policy/most_common_message.h" +#include "brpc/channel.h" +#include "brpc/details/load_balancer_with_naming.h" +#include "brpc/parallel_channel.h" +#include "brpc/selective_channel.h" +#include "brpc/socket_map.h" +#include "brpc/controller.h" +#include "echo.pb.h" +#include "brpc/options.pb.h" + +namespace brpc { +DECLARE_int32(idle_timeout_second); +DECLARE_int32(max_connection_pool_size); +class Server; +class Span; +class MethodStatus; +namespace policy { +void SendRpcResponse(int64_t correlation_id, + Controller* cntl, + RpcPBMessages* messages, + const Server* server_raw, + MethodStatus *, int64_t, + std::shared_ptr span); +} // policy +} // brpc + +int main(int argc, char* argv[]) { + brpc::FLAGS_idle_timeout_second = 0; + brpc::FLAGS_max_connection_pool_size = 0; + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + return RUN_ALL_TESTS(); +} + +namespace { +void* RunClosure(void* arg) { + google::protobuf::Closure* done = (google::protobuf::Closure*)arg; + done->Run(); + return NULL; +} + +void MarkCalled(bool* called) { + *called = true; +} + +class DeleteOnlyOnceChannel : public brpc::Channel { +public: + DeleteOnlyOnceChannel() : _c(1) { + } + ~DeleteOnlyOnceChannel() { + RELEASE_ASSERT_VERBOSE(_c.fetch_sub(1) == 1, + "Delete more than once!"); + } +private: + butil::atomic _c; +}; + +static std::string MOCK_CREDENTIAL = "mock credential"; +static std::string MOCK_CONTEXT = "mock context"; + +class MyAuthenticator : public brpc::Authenticator { +public: + MyAuthenticator() : count(0) {} + + int GenerateCredential(std::string* auth_str) const { + *auth_str = MOCK_CREDENTIAL; + count.fetch_add(1, butil::memory_order_relaxed); + return 0; + } + + int VerifyCredential(const std::string&, + const butil::EndPoint&, + brpc::AuthContext* ctx) const { + ctx->set_user(MOCK_CONTEXT); + ctx->set_group(MOCK_CONTEXT); + ctx->set_roles(MOCK_CONTEXT); + ctx->set_starter(MOCK_CONTEXT); + ctx->set_is_service(true); + return 0; + } + mutable butil::atomic count; +}; + +static bool VerifyMyRequest(const brpc::InputMessageBase* msg_base) { + const brpc::policy::MostCommonMessage* msg = + static_cast(msg_base); + brpc::Socket* ptr = msg->socket(); + + brpc::policy::RpcMeta meta; + butil::IOBufAsZeroCopyInputStream wrapper(msg->meta); + EXPECT_TRUE(meta.ParseFromZeroCopyStream(&wrapper)); + + if (meta.has_authentication_data()) { + // Credential MUST only appear in the first packet + EXPECT_TRUE(NULL == ptr->auth_context()); + EXPECT_EQ(meta.authentication_data(), MOCK_CREDENTIAL); + MyAuthenticator authenticator; + return authenticator.VerifyCredential( + "", butil::EndPoint(), ptr->mutable_auth_context()) == 0; + } + return true; +} + +class CallAfterRpcObject { +public: + explicit CallAfterRpcObject() {} + + ~CallAfterRpcObject() { + EXPECT_EQ(str, "CallAfterRpcRespTest"); + } + + void Append(const std::string& s) { + str.append(s); + } + +private: + std::string str; +}; + +class MyEchoService : public ::test::EchoService { + void Echo(google::protobuf::RpcController* cntl_base, + const ::test::EchoRequest* req, + ::test::EchoResponse* res, + google::protobuf::Closure* done) { + brpc::Controller* cntl = + static_cast(cntl_base); + std::shared_ptr str_test(new CallAfterRpcObject()); + cntl->set_after_rpc_resp_fn(std::bind(&MyEchoService::CallAfterRpc, str_test, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + brpc::ClosureGuard done_guard(done); + if (req->server_fail()) { + cntl->SetFailed(req->server_fail(), "Server fail1"); + cntl->SetFailed(req->server_fail(), "Server fail2"); + return; + } + if (req->close_fd()) { + LOG(INFO) << "close fd..."; + cntl->CloseConnection("Close connection according to request"); + return; + } + if (req->sleep_us() > 0) { + LOG(INFO) << "sleep " << req->sleep_us() << "us..."; + bthread_usleep(req->sleep_us()); + } + res->set_message("received " + req->message()); + if (req->code() != 0) { + res->add_code_list(req->code()); + } + res->set_receiving_socket_id(cntl->_current_call.sending_sock->id()); + if (mockfunc_) mockfunc_(cntl_base, req, res, done); + + brpc::ProtocolType protocol = cntl->request_protocol(); + if ((brpc::PROTOCOL_HTTP == protocol || brpc::PROTOCOL_H2 == protocol) && + !req->http_header().empty()) { + ASSERT_FALSE(req->http_header().empty()); + const std::string* val = cntl->http_request().GetHeader(req->http_header()); + ASSERT_TRUE(val); + ASSERT_FALSE(val->empty()); + cntl->http_response().SetHeader(req->http_header(), *val); + } + } + static void CallAfterRpc(std::shared_ptr str, + brpc::Controller* cntl, + const google::protobuf::Message* req, + const google::protobuf::Message* res) { + const test::EchoRequest* request = static_cast(req); + const test::EchoResponse* response = static_cast(res); + str->Append("CallAfterRpcRespTest"); + EXPECT_TRUE(nullptr != cntl); + EXPECT_TRUE(nullptr != request); + EXPECT_TRUE(nullptr != response); + } + +public: + using MockFuncType = void(google::protobuf::RpcController*, + const ::test::EchoRequest*, ::test::EchoResponse*, + google::protobuf::Closure*); + void SetMockFunc(std::function&& mockfunc) { + mockfunc_ = std::move(mockfunc); + } + +private: + std::function mockfunc_; +}; + +class DelayedCloseEchoService : public ::test::EchoService { +public: + void Echo(google::protobuf::RpcController* cntl_base, + const ::test::EchoRequest* req, + ::test::EchoResponse*, + google::protobuf::Closure* done) override { + brpc::ClosureGuard done_guard(done); + if (req->sleep_us() > 0) { + bthread_usleep(req->sleep_us()); + } + static_cast(cntl_base)->CloseConnection( + "Close connection after delay"); + } +}; + +pthread_once_t register_mock_protocol = PTHREAD_ONCE_INIT; + +class ChannelTest : public ::testing::Test{ +protected: + ChannelTest() + : _ep(butil::IP_ANY, 8787) + , _close_fd_once(false) { + if (!_dummy.options().rpc_pb_message_factory) { + _dummy._options.rpc_pb_message_factory = new brpc::DefaultRpcPBMessageFactory(); + } + + pthread_once(®ister_mock_protocol, register_protocol); + const brpc::InputMessageHandler pairs[] = { + { brpc::policy::ParseRpcMessage, + ProcessRpcRequest, VerifyMyRequest, this, "baidu_std" } + }; + EXPECT_EQ(0, _messenger.AddHandler(pairs[0])); + + EXPECT_EQ(0, _server_list.save(butil::endpoint2str(_ep).c_str())); + _naming_url = std::string("File://") + _server_list.fname(); + }; + + virtual ~ChannelTest(){}; + virtual void SetUp() { + }; + virtual void TearDown() { + StopAndJoin(); + }; + + static void register_protocol() { + brpc::Protocol dummy_protocol = + { brpc::policy::ParseRpcMessage, + brpc::SerializeRequestDefault, + brpc::policy::PackRpcRequest, + NULL, ProcessRpcRequest, + VerifyMyRequest, NULL, NULL, + brpc::CONNECTION_TYPE_ALL, "baidu_std" }; + ASSERT_EQ(0, RegisterProtocol((brpc::ProtocolType)30, dummy_protocol)); + } + + static void ProcessRpcRequest(brpc::InputMessageBase* msg_base) { + brpc::DestroyingPtr msg( + static_cast(msg_base)); + brpc::SocketUniquePtr ptr(msg->ReleaseSocket()); + const brpc::AuthContext* auth = ptr->auth_context(); + if (auth) { + EXPECT_EQ(MOCK_CONTEXT, auth->user()); + EXPECT_EQ(MOCK_CONTEXT, auth->group()); + EXPECT_EQ(MOCK_CONTEXT, auth->roles()); + EXPECT_EQ(MOCK_CONTEXT, auth->starter()); + EXPECT_TRUE(auth->is_service()); + } + ChannelTest* ts = (ChannelTest*)msg_base->arg(); + if (ts->_close_fd_once) { + ts->_close_fd_once = false; + ptr->SetFailed(); + return; + } + + brpc::policy::RpcMeta meta; + butil::IOBufAsZeroCopyInputStream wrapper(msg->meta); + EXPECT_TRUE(meta.ParseFromZeroCopyStream(&wrapper)); + const brpc::policy::RpcRequestMeta& req_meta = meta.request(); + ASSERT_EQ(ts->_svc.descriptor()->full_name(), req_meta.service_name()); + const google::protobuf::MethodDescriptor* method = + ts->_svc.descriptor()->FindMethodByName(req_meta.method_name()); + brpc::RpcPBMessages* messages = + ts->_dummy.options().rpc_pb_message_factory->Get(ts->_svc, *method); + google::protobuf::Message* req = messages->Request(); + google::protobuf::Message* res = messages->Response(); + if (meta.attachment_size() != 0) { + butil::IOBuf req_buf; + msg->payload.cutn(&req_buf, msg->payload.size() - meta.attachment_size()); + butil::IOBufAsZeroCopyInputStream wrapper2(req_buf); + EXPECT_TRUE(req->ParseFromZeroCopyStream(&wrapper2)); + } else { + butil::IOBufAsZeroCopyInputStream wrapper2(msg->payload); + EXPECT_TRUE(req->ParseFromZeroCopyStream(&wrapper2)); + } + brpc::Controller* cntl = new brpc::Controller(); + cntl->_current_call.peer_id = ptr->id(); + cntl->_current_call.sending_sock.reset(ptr.release()); + cntl->_server = &ts->_dummy; + + google::protobuf::Closure* done = brpc::NewCallback< + int64_t, brpc::Controller*, brpc::RpcPBMessages*, const brpc::Server*, + brpc::MethodStatus*, int64_t, std::shared_ptr>( + &brpc::policy::SendRpcResponse, meta.correlation_id(), cntl, + messages, &ts->_dummy, NULL, -1, nullptr); + ts->_svc.CallMethod(method, cntl, req, res, done); + } + + int StartAccept(butil::EndPoint ep) { + int listening_fd = -1; + while ((listening_fd = tcp_listen(ep)) < 0) { + if (errno == EADDRINUSE) { + bthread_usleep(1000); + } else { + return -1; + } + } + if (_messenger.StartAccept(listening_fd, -1, NULL, false) != 0) { + return -1; + } + return 0; + } + + void StopAndJoin() { + _messenger.StopAccept(0); + _messenger.Join(); + } + + void SetUpChannel(brpc::Channel* channel, + bool single_server, + bool short_connection, + const brpc::Authenticator* auth = NULL, + std::string connection_group = std::string(), + bool use_backup_request_policy = false, + brpc::ProtocolType protocol = brpc::PROTOCOL_BAIDU_STD) { + brpc::ChannelOptions opt; + opt.protocol = protocol; + if (short_connection) { + opt.connection_type = brpc::CONNECTION_TYPE_SHORT; + } + opt.auth = auth; + opt.max_retry = 0; + opt.connection_group = connection_group; + if (use_backup_request_policy) { + opt.backup_request_policy = &_backup_request_policy; + } + if (single_server) { + EXPECT_EQ(0, channel->Init(_ep, &opt)); + } else { + EXPECT_EQ(0, channel->Init(_naming_url.c_str(), "rR", &opt)); + } + } + + void CallMethod(brpc::ChannelBase* channel, + brpc::Controller* cntl, + test::EchoRequest* req, test::EchoResponse* res, + bool async, bool destroy = false) { + google::protobuf::Closure* done = NULL; + brpc::CallId sync_id = { 0 }; + if (async) { + sync_id = cntl->call_id(); + done = brpc::DoNothing(); + } + ::test::EchoService::Stub(channel).Echo(cntl, req, res, done); + if (async) { + if (destroy) { + delete channel; + } + // Callback MUST be called for once and only once + bthread_id_join(sync_id); + } + } + + void CallMethod(brpc::ChannelBase* channel, + brpc::Controller* cntl, + test::ComboRequest* req, test::ComboResponse* res, + bool async, bool destroy = false) { + google::protobuf::Closure* done = NULL; + brpc::CallId sync_id = { 0 }; + if (async) { + sync_id = cntl->call_id(); + done = brpc::DoNothing(); + } + ::test::EchoService::Stub(channel).ComboEcho(cntl, req, res, done); + if (async) { + if (destroy) { + delete channel; + } + // Callback MUST be called for once and only once + bthread_id_join(sync_id); + } + } + + void TestConnectionFailed(bool single_server, bool async, + bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + brpc::Channel channel; + SetUpChannel(&channel, single_server, short_connection); + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + CallMethod(&channel, &cntl, &req, &res, async); + + EXPECT_EQ(ECONNREFUSED, cntl.ErrorCode()) << cntl.ErrorText(); + } + + void TestConnectionFailedParallel(bool single_server, bool async, + bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + const size_t NCHANS = 8; + brpc::Channel subchans[NCHANS]; + brpc::ParallelChannel channel; + for (size_t i = 0; i < NCHANS; ++i) { + SetUpChannel(&subchans[i], single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel( + &subchans[i], brpc::DOESNT_OWN_CHANNEL, + NULL, NULL)); + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + CallMethod(&channel, &cntl, &req, &res, async); + + EXPECT_TRUE(brpc::ETOOMANYFAILS == cntl.ErrorCode() || + ECONNREFUSED == cntl.ErrorCode()) << cntl.ErrorText(); + LOG(INFO) << cntl.ErrorText(); + } + + void TestConnectionFailedSelective(bool single_server, bool async, + bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + const size_t NCHANS = 8; + brpc::SelectiveChannel channel; + brpc::ChannelOptions options; + options.max_retry = 0; + ASSERT_EQ(0, channel.Init("rr", &options)); + for (size_t i = 0; i < NCHANS; ++i) { + brpc::Channel* subchan = new brpc::Channel; + SetUpChannel(subchan, single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel(subchan, NULL)) << "i=" << i; + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + CallMethod(&channel, &cntl, &req, &res, async); + + EXPECT_EQ(ECONNREFUSED, cntl.ErrorCode()) << cntl.ErrorText(); + ASSERT_EQ(1, cntl.sub_count()); + EXPECT_EQ(ECONNREFUSED, cntl.sub(0)->ErrorCode()) + << cntl.sub(0)->ErrorText(); + LOG(INFO) << cntl.ErrorText(); + } + + void TestSuccess(bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + brpc::Channel channel; + SetUpChannel(&channel, single_server, short_connection); + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + CallMethod(&channel, &cntl, &req, &res, async); + + EXPECT_EQ(0, cntl.ErrorCode()) + << single_server << ", " << async << ", " << short_connection; + const uint64_t receiving_socket_id = res.receiving_socket_id(); + EXPECT_EQ(0, cntl.sub_count()); + EXPECT_TRUE(NULL == cntl.sub(-1)); + EXPECT_TRUE(NULL == cntl.sub(0)); + EXPECT_TRUE(NULL == cntl.sub(1)); + EXPECT_EQ("received " + std::string(__FUNCTION__), res.message()); + if (short_connection) { + // Sleep to let `_messenger' detect `Socket' being `SetFailed' + const int64_t start_time = butil::cpuwide_time_us(); + while (_messenger.ConnectionCount() != 0) { + EXPECT_LT(butil::cpuwide_time_us(), start_time + 100000L/*100ms*/); + bthread_usleep(1000); + } + } else { + EXPECT_GE(1ul, _messenger.ConnectionCount()); + } + if (single_server && !short_connection) { + // Reuse the connection + brpc::Channel channel2; + SetUpChannel(&channel2, single_server, short_connection); + cntl.Reset(); + req.Clear(); + res.Clear(); + req.set_message(__FUNCTION__); + CallMethod(&channel2, &cntl, &req, &res, async); + EXPECT_EQ(0, cntl.ErrorCode()) + << single_server << ", " << async << ", " << short_connection; + EXPECT_EQ(receiving_socket_id, res.receiving_socket_id()); + + // A different connection_group does not reuse the connection + brpc::Channel channel3; + SetUpChannel(&channel3, single_server, short_connection, + NULL, "another_group"); + cntl.Reset(); + req.Clear(); + res.Clear(); + req.set_message(__FUNCTION__); + CallMethod(&channel3, &cntl, &req, &res, async); + EXPECT_EQ(0, cntl.ErrorCode()) + << single_server << ", " << async << ", " << short_connection; + const uint64_t receiving_socket_id2 = res.receiving_socket_id(); + EXPECT_NE(receiving_socket_id, receiving_socket_id2); + + // Channel in the same connection_group reuses the connection + // note that the leading/trailing spaces should be trimed. + brpc::Channel channel4; + SetUpChannel(&channel4, single_server, short_connection, + NULL, " another_group "); + cntl.Reset(); + req.Clear(); + res.Clear(); + req.set_message(__FUNCTION__); + CallMethod(&channel4, &cntl, &req, &res, async); + EXPECT_EQ(0, cntl.ErrorCode()) + << single_server << ", " << async << ", " << short_connection; + EXPECT_EQ(receiving_socket_id2, res.receiving_socket_id()); + } + StopAndJoin(); + } + + class SetCode : public brpc::CallMapper { + public: + brpc::SubCall Map( + int channel_index, + const google::protobuf::MethodDescriptor* method, + const google::protobuf::Message* req_base, + google::protobuf::Message* response) override { + test::EchoRequest* req = brpc::Clone(req_base); + req->set_code(channel_index + 1/*non-zero*/); + return brpc::SubCall(method, req, response->New(), + brpc::DELETE_REQUEST | brpc::DELETE_RESPONSE); + } + }; + + class SetCodeOnEven : public SetCode { + public: + brpc::SubCall Map( + int channel_index, + const google::protobuf::MethodDescriptor* method, + const google::protobuf::Message* req_base, + google::protobuf::Message* response) override { + if (channel_index % 2) { + return brpc::SubCall::Skip(); + } + return SetCode::Map(channel_index, method, req_base, response); + } + }; + + + class GetReqAndAddRes : public brpc::CallMapper { + brpc::SubCall Map( + int channel_index, + const google::protobuf::MethodDescriptor* method, + const google::protobuf::Message* req_base, + google::protobuf::Message* res_base) override { + const test::ComboRequest* req = + dynamic_cast(req_base); + test::ComboResponse* res = dynamic_cast(res_base); + if (method->name() != "ComboEcho" || + res == NULL || req == NULL || + req->requests_size() <= channel_index) { + return brpc::SubCall::Bad(); + } + return brpc::SubCall(::test::EchoService::descriptor()->method(0), + &req->requests(channel_index), + res->add_responses(), 0); + } + }; + + class SuccessLimitCallMapper : public brpc::CallMapper { + public: + brpc::SubCall Map(int channel_index, + const google::protobuf::MethodDescriptor* method, + const google::protobuf::Message* req_base, + google::protobuf::Message* response) override { + auto req = brpc::Clone(req_base); + req->set_code(channel_index + 1/*non-zero*/); + if (_index++ > 0) { + req->set_sleep_us(5 * 1000); + } + return brpc::SubCall(method, req, response->New(), + brpc::DELETE_REQUEST | brpc::DELETE_RESPONSE); + } + private: + size_t _index{0}; + }; + + class MergeNothing : public brpc::ResponseMerger { + Result Merge(google::protobuf::Message* /*response*/, + const google::protobuf::Message* /*sub_response*/) { + return brpc::ResponseMerger::MERGED; + } + }; + + void TestSuccessParallel(bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + const size_t NCHANS = 8; + brpc::Channel subchans[NCHANS]; + brpc::ParallelChannel channel; + for (size_t i = 0; i < NCHANS; ++i) { + SetUpChannel(&subchans[i], single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel( + &subchans[i], brpc::DOESNT_OWN_CHANNEL, + new SetCode, NULL)); + } + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + req.set_code(23); + CallMethod(&channel, &cntl, &req, &res, async); + + EXPECT_EQ(0, cntl.ErrorCode()) << cntl.ErrorText(); + EXPECT_EQ(NCHANS, (size_t)cntl.sub_count()); + for (int i = 0; i < cntl.sub_count(); ++i) { + EXPECT_TRUE(cntl.sub(i) && !cntl.sub(i)->Failed()) << "i=" << i; + } + EXPECT_EQ("received " + std::string(__FUNCTION__), res.message()); + ASSERT_EQ(NCHANS, (size_t)res.code_list_size()); + for (size_t i = 0; i < NCHANS; ++i) { + ASSERT_EQ((int)i+1, res.code_list(i)); + } + if (short_connection) { + // Sleep to let `_messenger' detect `Socket' being `SetFailed' + const int64_t start_time = butil::cpuwide_time_us(); + while (_messenger.ConnectionCount() != 0) { + EXPECT_LT(butil::cpuwide_time_us(), start_time + 100000L/*100ms*/); + bthread_usleep(1000); + } + } else { + EXPECT_GE(1ul, _messenger.ConnectionCount()); + } + StopAndJoin(); + } + + void TestSuccessDuplicatedParallel( + bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + const size_t NCHANS = 8; + brpc::Channel* subchan = new DeleteOnlyOnceChannel; + SetUpChannel(subchan, single_server, short_connection); + brpc::ParallelChannel channel; + // Share the CallMapper and ResponseMerger should be fine because + // they're intrusively shared. + SetCode* set_code = new SetCode; + for (size_t i = 0; i < NCHANS; ++i) { + ASSERT_EQ(0, channel.AddChannel( + subchan, + // subchan should be deleted (for only once) + ((i % 2) ? brpc::DOESNT_OWN_CHANNEL : brpc::OWNS_CHANNEL), + set_code, NULL)); + } + ASSERT_EQ((int)NCHANS, set_code->ref_count()); + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + req.set_code(23); + CallMethod(&channel, &cntl, &req, &res, async); + + EXPECT_EQ(0, cntl.ErrorCode()) << cntl.ErrorText(); + EXPECT_EQ(NCHANS, (size_t)cntl.sub_count()); + for (int i = 0; i < cntl.sub_count(); ++i) { + EXPECT_TRUE(cntl.sub(i) && !cntl.sub(i)->Failed()) << "i=" << i; + } + EXPECT_EQ("received " + std::string(__FUNCTION__), res.message()); + ASSERT_EQ(NCHANS, (size_t)res.code_list_size()); + for (size_t i = 0; i < NCHANS; ++i) { + ASSERT_EQ((int)i+1, res.code_list(i)); + } + if (short_connection) { + // Sleep to let `_messenger' detect `Socket' being `SetFailed' + const int64_t start_time = butil::cpuwide_time_us(); + while (_messenger.ConnectionCount() != 0) { + EXPECT_LT(butil::cpuwide_time_us(), start_time + 100000L/*100ms*/); + bthread_usleep(1000); + } + } else { + EXPECT_GE(1ul, _messenger.ConnectionCount()); + } + StopAndJoin(); + } + + void TestSuccessSelective(bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + const size_t NCHANS = 8; + ASSERT_EQ(0, StartAccept(_ep)); + brpc::SelectiveChannel channel; + brpc::ChannelOptions options; + options.max_retry = 0; + ASSERT_EQ(0, channel.Init("rr", &options)); + for (size_t i = 0; i < NCHANS; ++i) { + brpc::Channel* subchan = new brpc::Channel; + SetUpChannel(subchan, single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel(subchan, NULL)) << "i=" << i; + } + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + req.set_code(23); + CallMethod(&channel, &cntl, &req, &res, async); + + EXPECT_EQ(0, cntl.ErrorCode()) << cntl.ErrorText(); + EXPECT_EQ(1, cntl.sub_count()); + ASSERT_EQ(0, cntl.sub(0)->ErrorCode()); + EXPECT_EQ("received " + std::string(__FUNCTION__), res.message()); + ASSERT_EQ(1, res.code_list_size()); + ASSERT_EQ(req.code(), res.code_list(0)); + ASSERT_EQ(_ep, cntl.remote_side()); + + if (short_connection) { + // Sleep to let `_messenger' detect `Socket' being `SetFailed' + const int64_t start_time = butil::cpuwide_time_us(); + while (_messenger.ConnectionCount() != 0) { + EXPECT_LT(butil::cpuwide_time_us(), start_time + 100000L/*100ms*/); + bthread_usleep(1000); + } + } else { + EXPECT_GE(1ul, _messenger.ConnectionCount()); + } + StopAndJoin(); + } + + void TestSkipParallel(bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + const size_t NCHANS = 8; + brpc::Channel subchans[NCHANS]; + brpc::ParallelChannel channel; + for (size_t i = 0; i < NCHANS; ++i) { + SetUpChannel(&subchans[i], single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel( + &subchans[i], brpc::DOESNT_OWN_CHANNEL, + new SetCodeOnEven, NULL)); + } + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + req.set_code(23); + CallMethod(&channel, &cntl, &req, &res, async); + + EXPECT_EQ(0, cntl.ErrorCode()) << cntl.ErrorText(); + EXPECT_EQ("received " + std::string(__FUNCTION__), res.message()); + EXPECT_EQ(NCHANS, (size_t)cntl.sub_count()); + for (int i = 0; i < cntl.sub_count(); ++i) { + if (i % 2) { + EXPECT_TRUE(NULL == cntl.sub(i)) << "i=" << i; + } else { + EXPECT_TRUE(cntl.sub(i) && !cntl.sub(i)->Failed()) << "i=" << i; + } + } + ASSERT_EQ(NCHANS / 2, (size_t)res.code_list_size()); + for (int i = 0; i < res.code_list_size(); ++i) { + ASSERT_EQ(i*2 + 1, res.code_list(i)); + } + if (short_connection) { + // Sleep to let `_messenger' detect `Socket' being `SetFailed' + const int64_t start_time = butil::cpuwide_time_us(); + while (_messenger.ConnectionCount() != 0) { + EXPECT_LT(butil::cpuwide_time_us(), start_time + 100000L/*100ms*/); + bthread_usleep(1000); + } + } else { + EXPECT_GE(1ul, _messenger.ConnectionCount()); + } + StopAndJoin(); + } + + void TestSuccessParallel2(bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + const size_t NCHANS = 8; + brpc::Channel subchans[NCHANS]; + brpc::ParallelChannel channel; + for (size_t i = 0; i < NCHANS; ++i) { + SetUpChannel(&subchans[i], single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel( + &subchans[i], brpc::DOESNT_OWN_CHANNEL, + new GetReqAndAddRes, new MergeNothing)); + } + brpc::Controller cntl; + test::ComboRequest req; + test::ComboResponse res; + CallMethod(&channel, &cntl, &req, &res, false); + ASSERT_TRUE(cntl.Failed()); // req does not have .requests + ASSERT_EQ(brpc::EREQUEST, cntl.ErrorCode()); + + for (size_t i = 0; i < NCHANS; ++i) { + ::test::EchoRequest* sub_req = req.add_requests(); + sub_req->set_message(butil::string_printf("hello_%llu", (long long)i)); + sub_req->set_code(i + 1); + } + + // non-parallel channel does not work. + cntl.Reset(); + CallMethod(&subchans[0], &cntl, &req, &res, false); + ASSERT_TRUE(cntl.Failed()); + ASSERT_EQ(brpc::EINTERNAL, cntl.ErrorCode()) << cntl.ErrorText(); + ASSERT_TRUE(butil::StringPiece(cntl.ErrorText()).ends_with("Method ComboEcho() not implemented.")); + + // do the rpc call. + cntl.Reset(); + CallMethod(&channel, &cntl, &req, &res, async); + + EXPECT_EQ(0, cntl.ErrorCode()) << cntl.ErrorText(); + ASSERT_GT(cntl.latency_us(), 0); + ASSERT_EQ((int)NCHANS, res.responses_size()); + for (int i = 0; i < res.responses_size(); ++i) { + EXPECT_EQ(butil::string_printf("received hello_%d", i), + res.responses(i).message()); + ASSERT_EQ(1, res.responses(i).code_list_size()); + EXPECT_EQ(i + 1, res.responses(i).code_list(0)); + } + if (short_connection) { + // Sleep to let `_messenger' detect `Socket' being `SetFailed' + const int64_t start_time = butil::cpuwide_time_us(); + while (_messenger.ConnectionCount() != 0) { + EXPECT_LT(butil::cpuwide_time_us(), start_time + 100000L/*100ms*/); + bthread_usleep(1000); + } + } else { + EXPECT_GE(1ul, _messenger.ConnectionCount()); + } + StopAndJoin(); + } + + void TestSuccessLimitParallel(bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + const size_t NCHANS = 8; + brpc::Channel subchans[NCHANS]; + brpc::ParallelChannel channel; + brpc::ParallelChannelOptions options; + // Only care about the first successful response. + options.success_limit = 1; + channel.Init(&options); + butil::intrusive_ptr fast_call_mapper(new SuccessLimitCallMapper); + for (size_t i = 0; i < NCHANS; ++i) { + SetUpChannel(&subchans[i], single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel( + &subchans[i], brpc::DOESNT_OWN_CHANNEL, fast_call_mapper, NULL)); + } + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + req.set_code(23); + CallMethod(&channel, &cntl, &req, &res, async); + + EXPECT_EQ(0, cntl.ErrorCode()) << cntl.ErrorText(); + EXPECT_EQ(NCHANS, (size_t)cntl.sub_count()); + for (int i = 0; i < cntl.sub_count(); ++i) { + EXPECT_TRUE(cntl.sub(i)) << "i=" << i; + if (0 == i) { + EXPECT_TRUE(!cntl.sub(i)->Failed()) << "i=" << i; + } else { + EXPECT_TRUE(cntl.sub(i)->Failed()) << "i=" << i; + EXPECT_EQ(brpc::EPCHANFINISH, cntl.sub(i)->ErrorCode()) << "i=" << i; + } + } + EXPECT_EQ("received " + std::string(__FUNCTION__), res.message()); + ASSERT_EQ(1, res.code_list_size()); + ASSERT_EQ((int)1, res.code_list(0)); + if (short_connection) { + // Sleep to let `_messenger' detect `Socket' being `SetFailed' + const int64_t start_time = butil::cpuwide_time_us(); + while (_messenger.ConnectionCount() != 0) { + EXPECT_LT(butil::cpuwide_time_us(), start_time + 100000L/*100ms*/); + bthread_usleep(1000); + } + } else { + EXPECT_GE(1ul, _messenger.ConnectionCount()); + } + StopAndJoin(); + } + + struct CancelerArg { + int64_t sleep_before_cancel_us; + brpc::CallId cid; + }; + + static void* Canceler(void* void_arg) { + CancelerArg* arg = static_cast(void_arg); + if (arg->sleep_before_cancel_us > 0) { + bthread_usleep(arg->sleep_before_cancel_us); + } + LOG(INFO) << "Start to cancel cid=" << arg->cid.value; + brpc::StartCancel(arg->cid); + return NULL; + } + + + void CancelBeforeCallMethod( + bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + brpc::Channel channel; + SetUpChannel(&channel, single_server, short_connection); + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + const brpc::CallId cid = cntl.call_id(); + ASSERT_TRUE(cid.value != 0); + brpc::StartCancel(cid); + CallMethod(&channel, &cntl, &req, &res, async); + EXPECT_EQ(ECANCELED, cntl.ErrorCode()) << cntl.ErrorText(); + StopAndJoin(); + } + + void CancelBeforeCallMethodParallel( + bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + + const size_t NCHANS = 8; + brpc::Channel subchans[NCHANS]; + brpc::ParallelChannel channel; + for (size_t i = 0; i < NCHANS; ++i) { + SetUpChannel(&subchans[i], single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel( + &subchans[i], brpc::DOESNT_OWN_CHANNEL, + NULL, NULL)); + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + const brpc::CallId cid = cntl.call_id(); + ASSERT_TRUE(cid.value != 0); + brpc::StartCancel(cid); + CallMethod(&channel, &cntl, &req, &res, async); + EXPECT_EQ(ECANCELED, cntl.ErrorCode()) << cntl.ErrorText(); + EXPECT_EQ(NCHANS, (size_t)cntl.sub_count()); + EXPECT_TRUE(NULL == cntl.sub(1)); + EXPECT_TRUE(NULL == cntl.sub(0)); + StopAndJoin(); + } + + void CancelBeforeCallMethodSelective( + bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + + const size_t NCHANS = 8; + brpc::SelectiveChannel channel; + ASSERT_EQ(0, channel.Init("rr", NULL)); + for (size_t i = 0; i < NCHANS; ++i) { + brpc::Channel* subchan = new brpc::Channel; + SetUpChannel(subchan, single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel(subchan, NULL)) << "i=" << i; + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + const brpc::CallId cid = cntl.call_id(); + ASSERT_TRUE(cid.value != 0); + brpc::StartCancel(cid); + CallMethod(&channel, &cntl, &req, &res, async); + EXPECT_EQ(ECANCELED, cntl.ErrorCode()) << cntl.ErrorText(); + StopAndJoin(); + } + + void CancelDuringCallMethod( + bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + brpc::Channel channel; + SetUpChannel(&channel, single_server, short_connection); + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + const brpc::CallId cid = cntl.call_id(); + ASSERT_TRUE(cid.value != 0); + pthread_t th; + CancelerArg carg = { 10000, cid }; + ASSERT_EQ(0, pthread_create(&th, NULL, Canceler, &carg)); + req.set_sleep_us(carg.sleep_before_cancel_us * 2); + butil::Timer tm; + tm.start(); + CallMethod(&channel, &cntl, &req, &res, async); + tm.stop(); + EXPECT_LT(labs(tm.u_elapsed() - carg.sleep_before_cancel_us), 10000); + ASSERT_EQ(0, pthread_join(th, NULL)); + EXPECT_EQ(ECANCELED, cntl.ErrorCode()); + EXPECT_EQ(0, cntl.sub_count()); + EXPECT_TRUE(NULL == cntl.sub(1)); + EXPECT_TRUE(NULL == cntl.sub(0)); + StopAndJoin(); + } + + void CancelDuringCallMethodParallel( + bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + + const size_t NCHANS = 8; + brpc::Channel subchans[NCHANS]; + brpc::ParallelChannel channel; + for (size_t i = 0; i < NCHANS; ++i) { + SetUpChannel(&subchans[i], single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel( + &subchans[i], brpc::DOESNT_OWN_CHANNEL, + NULL, NULL)); + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + const brpc::CallId cid = cntl.call_id(); + ASSERT_TRUE(cid.value != 0); + pthread_t th; + CancelerArg carg = { 10000, cid }; + ASSERT_EQ(0, pthread_create(&th, NULL, Canceler, &carg)); + req.set_sleep_us(carg.sleep_before_cancel_us * 2); + butil::Timer tm; + tm.start(); + CallMethod(&channel, &cntl, &req, &res, async); + tm.stop(); + EXPECT_LT(labs(tm.u_elapsed() - carg.sleep_before_cancel_us), 10000); + ASSERT_EQ(0, pthread_join(th, NULL)); + EXPECT_EQ(ECANCELED, cntl.ErrorCode()); + EXPECT_EQ(NCHANS, (size_t)cntl.sub_count()); + for (int i = 0; i < cntl.sub_count(); ++i) { + EXPECT_EQ(ECANCELED, cntl.sub(i)->ErrorCode()) << "i=" << i; + } + EXPECT_LT(labs(cntl.latency_us() - carg.sleep_before_cancel_us), 10000); + StopAndJoin(); + } + + void CancelDuringCallMethodSelective( + bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + + const size_t NCHANS = 8; + brpc::SelectiveChannel channel; + ASSERT_EQ(0, channel.Init("rr", NULL)); + for (size_t i = 0; i < NCHANS; ++i) { + brpc::Channel* subchan = new brpc::Channel; + SetUpChannel(subchan, single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel(subchan, NULL)) << "i=" << i; + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + const brpc::CallId cid = cntl.call_id(); + ASSERT_TRUE(cid.value != 0); + pthread_t th; + CancelerArg carg = { 10000, cid }; + ASSERT_EQ(0, pthread_create(&th, NULL, Canceler, &carg)); + req.set_sleep_us(carg.sleep_before_cancel_us * 2); + butil::Timer tm; + tm.start(); + CallMethod(&channel, &cntl, &req, &res, async); + tm.stop(); + EXPECT_LT(labs(tm.u_elapsed() - carg.sleep_before_cancel_us), 10000); + ASSERT_EQ(0, pthread_join(th, NULL)); + EXPECT_EQ(ECANCELED, cntl.ErrorCode()); + EXPECT_EQ(1, cntl.sub_count()); + EXPECT_EQ(ECANCELED, cntl.sub(0)->ErrorCode()); + StopAndJoin(); + } + + void CancelAfterCallMethod( + bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + brpc::Channel channel; + SetUpChannel(&channel, single_server, short_connection); + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + const brpc::CallId cid = cntl.call_id(); + ASSERT_TRUE(cid.value != 0); + CallMethod(&channel, &cntl, &req, &res, async); + EXPECT_EQ(0, cntl.ErrorCode()); + EXPECT_EQ(0, cntl.sub_count()); + ASSERT_EQ(EINVAL, bthread_id_error(cid, ECANCELED)); + StopAndJoin(); + } + + void CancelAfterCallMethodParallel( + bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + + const size_t NCHANS = 8; + brpc::Channel subchans[NCHANS]; + brpc::ParallelChannel channel; + for (size_t i = 0; i < NCHANS; ++i) { + SetUpChannel(&subchans[i], single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel( + &subchans[i], brpc::DOESNT_OWN_CHANNEL, + NULL, NULL)); + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + const brpc::CallId cid = cntl.call_id(); + ASSERT_TRUE(cid.value != 0); + CallMethod(&channel, &cntl, &req, &res, async); + EXPECT_EQ(0, cntl.ErrorCode()); + EXPECT_EQ(NCHANS, (size_t)cntl.sub_count()); + for (int i = 0; i < cntl.sub_count(); ++i) { + EXPECT_TRUE(cntl.sub(i) && !cntl.sub(i)->Failed()) << "i=" << i; + } + ASSERT_EQ(EINVAL, bthread_id_error(cid, ECANCELED)); + StopAndJoin(); + } + + void TestAttachment(bool async, bool short_connection) { + ASSERT_EQ(0, StartAccept(_ep)); + brpc::Channel channel; + SetUpChannel(&channel, true, short_connection); + + brpc::Controller cntl; + cntl.request_attachment().append("attachment"); + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + CallMethod(&channel, &cntl, &req, &res, async); + + EXPECT_EQ(0, cntl.ErrorCode()) << short_connection; + EXPECT_FALSE(cntl.request_attachment().empty()) + << ", " << async << ", " << short_connection; + EXPECT_EQ("received " + std::string(__FUNCTION__), res.message()); + if (short_connection) { + // Sleep to let `_messenger' detect `Socket' being `SetFailed' + const int64_t start_time = butil::cpuwide_time_us(); + while (_messenger.ConnectionCount() != 0) { + EXPECT_LT(butil::cpuwide_time_us(), start_time + 100000L/*100ms*/); + bthread_usleep(1000); + } + } else { + EXPECT_GE(1ul, _messenger.ConnectionCount()); + } + StopAndJoin(); + } + + void TestRequestNotInit(bool single_server, bool async, + bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + ASSERT_EQ(0, StartAccept(_ep)); + brpc::Channel channel; + SetUpChannel(&channel, single_server, short_connection); + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + CallMethod(&channel, &cntl, &req, &res, async); + EXPECT_EQ(brpc::EREQUEST, cntl.ErrorCode()) << cntl.ErrorText(); + StopAndJoin(); + } + + void TestRequestNotInitParallel(bool single_server, bool async, + bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + ASSERT_EQ(0, StartAccept(_ep)); + + const size_t NCHANS = 8; + brpc::Channel subchans[NCHANS]; + brpc::ParallelChannel channel; + for (size_t i = 0; i < NCHANS; ++i) { + SetUpChannel(&subchans[i], single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel( + &subchans[i], brpc::DOESNT_OWN_CHANNEL, + NULL, NULL)); + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + CallMethod(&channel, &cntl, &req, &res, async); + EXPECT_EQ(brpc::EREQUEST, cntl.ErrorCode()) << cntl.ErrorText(); + LOG(WARNING) << cntl.ErrorText(); + StopAndJoin(); + } + + void TestRequestNotInitSelective(bool single_server, bool async, + bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + ASSERT_EQ(0, StartAccept(_ep)); + + const size_t NCHANS = 8; + brpc::SelectiveChannel channel; + ASSERT_EQ(0, channel.Init("rr", NULL)); + for (size_t i = 0; i < NCHANS; ++i) { + brpc::Channel* subchan = new brpc::Channel; + SetUpChannel(subchan, single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel(subchan, NULL)) << "i=" << i; + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + CallMethod(&channel, &cntl, &req, &res, async); + EXPECT_EQ(brpc::EREQUEST, cntl.ErrorCode()) << cntl.ErrorText(); + LOG(WARNING) << cntl.ErrorText(); + ASSERT_EQ(1, cntl.sub_count()); + ASSERT_EQ(brpc::EREQUEST, cntl.sub(0)->ErrorCode()); + StopAndJoin(); + } + + void TestRPCTimeout(bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + ASSERT_EQ(0, StartAccept(_ep)); + brpc::Channel channel; + SetUpChannel(&channel, single_server, short_connection); + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + req.set_sleep_us(70000); // 70ms + cntl.set_timeout_ms(17); + butil::Timer tm; + tm.start(); + CallMethod(&channel, &cntl, &req, &res, async); + tm.stop(); + EXPECT_EQ(brpc::ERPCTIMEDOUT, cntl.ErrorCode()) << cntl.ErrorText(); + EXPECT_LT(labs(tm.m_elapsed() - cntl.timeout_ms()), 15); + StopAndJoin(); + } + + void TestRPCTimeoutParallel( + bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + ASSERT_EQ(0, StartAccept(_ep)); + + const size_t NCHANS = 8; + brpc::Channel subchans[NCHANS]; + brpc::ParallelChannel channel; + for (size_t i = 0; i < NCHANS; ++i) { + SetUpChannel(&subchans[i], single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel( + &subchans[i], brpc::DOESNT_OWN_CHANNEL, + NULL, NULL)); + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + cntl.set_timeout_ms(17); + req.set_sleep_us(70000); // 70ms + butil::Timer tm; + tm.start(); + CallMethod(&channel, &cntl, &req, &res, async); + tm.stop(); + EXPECT_EQ(brpc::ERPCTIMEDOUT, cntl.ErrorCode()) << cntl.ErrorText(); + EXPECT_EQ(NCHANS, (size_t)cntl.sub_count()); + for (int i = 0; i < cntl.sub_count(); ++i) { + EXPECT_EQ(ECANCELED, cntl.sub(i)->ErrorCode()) << "i=" << i; + } + EXPECT_LT(labs(tm.m_elapsed() - cntl.timeout_ms()), 15); + StopAndJoin(); + } + + class MakeTheRequestTimeout : public brpc::CallMapper { + public: + brpc::SubCall Map( + int /*channel_index*/, + const google::protobuf::MethodDescriptor* method, + const google::protobuf::Message* req_base, + google::protobuf::Message* response) override { + test::EchoRequest* req = brpc::Clone(req_base); + req->set_sleep_us(70000); // 70ms + return brpc::SubCall(method, req, response->New(), + brpc::DELETE_REQUEST | brpc::DELETE_RESPONSE); + } + }; + + void TimeoutStillChecksSubChannelsParallel( + bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + ASSERT_EQ(0, StartAccept(_ep)); + + const size_t NCHANS = 8; + brpc::Channel subchans[NCHANS]; + brpc::ParallelChannel channel; + for (size_t i = 0; i < NCHANS; ++i) { + SetUpChannel(&subchans[i], single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel( + &subchans[i], brpc::DOESNT_OWN_CHANNEL, + ((i % 2) ? new MakeTheRequestTimeout : NULL), NULL)); + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + cntl.set_timeout_ms(30); + butil::Timer tm; + tm.start(); + CallMethod(&channel, &cntl, &req, &res, async); + tm.stop(); + EXPECT_EQ(0, cntl.ErrorCode()) << cntl.ErrorText(); + EXPECT_EQ(NCHANS, (size_t)cntl.sub_count()); + for (int i = 0; i < cntl.sub_count(); ++i) { + if (i % 2) { + EXPECT_EQ(ECANCELED, cntl.sub(i)->ErrorCode()); + } else { + EXPECT_EQ(0, cntl.sub(i)->ErrorCode()); + } + } + EXPECT_LT(labs(tm.m_elapsed() - cntl.timeout_ms()), 15); + StopAndJoin(); + } + + void TestRPCTimeoutSelective( + bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + ASSERT_EQ(0, StartAccept(_ep)); + + const size_t NCHANS = 8; + brpc::SelectiveChannel channel; + ASSERT_EQ(0, channel.Init("rr", NULL)); + for (size_t i = 0; i < NCHANS; ++i) { + brpc::Channel* subchan = new brpc::Channel; + SetUpChannel(subchan, single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel(subchan, NULL)) << "i=" << i; + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + cntl.set_timeout_ms(17); + req.set_sleep_us(70000); // 70ms + butil::Timer tm; + tm.start(); + CallMethod(&channel, &cntl, &req, &res, async); + tm.stop(); + EXPECT_EQ(brpc::ERPCTIMEDOUT, cntl.ErrorCode()) << cntl.ErrorText(); + EXPECT_EQ(1, cntl.sub_count()); + EXPECT_EQ(brpc::ERPCTIMEDOUT, cntl.sub(0)->ErrorCode()); + EXPECT_LT(labs(tm.m_elapsed() - cntl.timeout_ms()), 15); + EXPECT_EQ(-1, cntl.sub(0)->_timeout_ms); + EXPECT_EQ(17, cntl.sub(0)->_real_timeout_ms); + StopAndJoin(); + } + + void TestBackupRequestSelective( + bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + ASSERT_EQ(0, StartAccept(_ep)); + + const size_t NCHANS = 8; + brpc::SelectiveChannel channel; + ASSERT_EQ(0, channel.Init("rr", NULL)); + for (size_t i = 0; i < NCHANS; ++i) { + brpc::Channel* subchan = new brpc::Channel; + SetUpChannel(subchan, single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel(subchan, NULL)) << "i=" << i; + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + cntl.set_backup_request_ms(20); + cntl.set_timeout_ms(100); + std::atomic call_cnt(0); + _svc.SetMockFunc([&call_cnt](google::protobuf::RpcController* cntl_base, + const ::test::EchoRequest*, + ::test::EchoResponse*, + google::protobuf::Closure*) { + brpc::Controller* cntl = static_cast(cntl_base); + int see_cnt = call_cnt.fetch_add(1, std::memory_order_relaxed); + if (see_cnt == 0) { + LOG(INFO) << "slow node"; + bthread_usleep(30 * 1000); + } else { + LOG(INFO) << "normal node "; + butil::IOBuf iobuf; + iobuf.append("123"); + cntl->response_attachment().swap(iobuf); + } + }); + butil::Timer tm; + tm.start(); + CallMethod(&channel, &cntl, &req, &res, async); + tm.stop(); + EXPECT_FALSE(cntl.Failed()); + EXPECT_EQ(call_cnt.load(std::memory_order_relaxed), 2); + EXPECT_EQ(cntl.response_attachment().to_string(), "123"); + StopAndJoin(); + } + + void TestBackupRequestSelectiveResponseRace() { + ASSERT_EQ(0, StartAccept(_ep)); + + const size_t NCHANS = 8; + brpc::SelectiveChannel channel; + ASSERT_EQ(0, channel.Init("rr", NULL)); + for (size_t i = 0; i < NCHANS; ++i) { + brpc::Channel* subchan = new brpc::Channel; + SetUpChannel(subchan, false, false); + ASSERT_EQ(0, channel.AddChannel(subchan, NULL)) << "i=" << i; + } + + const int kRounds = 150; + const int kCodeListSize = 20000; + std::atomic call_cnt(0); + _svc.SetMockFunc([&call_cnt](google::protobuf::RpcController*, + const ::test::EchoRequest*, + ::test::EchoResponse* res, + google::protobuf::Closure*) { + const int seen = call_cnt.fetch_add(1, std::memory_order_relaxed); + const bool slow = ((seen & 1) == 0); + if (slow) { + bthread_usleep(1500); + } + res->clear_code_list(); + const int base = slow ? 1000000 : 2000000; + for (int i = 0; i < kCodeListSize; ++i) { + res->add_code_list(base + i); + } + res->set_message(slow ? "slow" : "fast"); + }); + + for (int round = 0; round < kRounds; ++round) { + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + cntl.set_backup_request_ms(1); + cntl.set_timeout_ms(3000); + CallMethod(&channel, &cntl, &req, &res, true); + ASSERT_FALSE(cntl.Failed()) << "round=" << round + << " err=" << cntl.ErrorText(); + ASSERT_EQ(kCodeListSize, res.code_list_size()) << "round=" << round; + ASSERT_TRUE(res.message() == "slow" || res.message() == "fast") + << "round=" << round; + } + + EXPECT_EQ(kRounds * 2, call_cnt.load(std::memory_order_relaxed)); + StopAndJoin(); + } + + void TestCloseFD(bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + brpc::Channel channel; + SetUpChannel(&channel, single_server, short_connection); + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + req.set_close_fd(true); + CallMethod(&channel, &cntl, &req, &res, async); + + EXPECT_EQ(brpc::EEOF, cntl.ErrorCode()) << cntl.ErrorText(); + StopAndJoin(); + } + + void TestCloseFDParallel(bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + + const size_t NCHANS = 8; + brpc::Channel subchans[NCHANS]; + brpc::ParallelChannel channel; + for (size_t i = 0; i < NCHANS; ++i) { + SetUpChannel(&subchans[i], single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel( + &subchans[i], brpc::DOESNT_OWN_CHANNEL, + NULL, NULL)); + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + req.set_close_fd(true); + CallMethod(&channel, &cntl, &req, &res, async); + + EXPECT_TRUE(brpc::EEOF == cntl.ErrorCode() || + brpc::ETOOMANYFAILS == cntl.ErrorCode() || + ECONNRESET == cntl.ErrorCode()) << cntl.ErrorText(); + StopAndJoin(); + } + + void TestCloseFDSelective(bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + + const size_t NCHANS = 8; + brpc::SelectiveChannel channel; + brpc::ChannelOptions options; + options.max_retry = 0; + ASSERT_EQ(0, channel.Init("rr", &options)); + for (size_t i = 0; i < NCHANS; ++i) { + brpc::Channel* subchan = new brpc::Channel; + SetUpChannel(subchan, single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel(subchan, NULL)) << "i=" << i; + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + req.set_close_fd(true); + CallMethod(&channel, &cntl, &req, &res, async); + + EXPECT_EQ(brpc::EEOF, cntl.ErrorCode()) << cntl.ErrorText(); + ASSERT_EQ(1, cntl.sub_count()); + ASSERT_EQ(brpc::EEOF, cntl.sub(0)->ErrorCode()); + + StopAndJoin(); + } + + void TestServerFail(bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + brpc::Channel channel; + SetUpChannel(&channel, single_server, short_connection); + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + req.set_server_fail(brpc::EINTERNAL); + CallMethod(&channel, &cntl, &req, &res, async); + + EXPECT_EQ(brpc::EINTERNAL, cntl.ErrorCode()) << cntl.ErrorText(); + StopAndJoin(); + } + + void TestServerFailParallel(bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + + const size_t NCHANS = 8; + brpc::Channel subchans[NCHANS]; + brpc::ParallelChannel channel; + for (size_t i = 0; i < NCHANS; ++i) { + SetUpChannel(&subchans[i], single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel( + &subchans[i], brpc::DOESNT_OWN_CHANNEL, + NULL, NULL)); + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + req.set_server_fail(brpc::EINTERNAL); + CallMethod(&channel, &cntl, &req, &res, async); + + EXPECT_EQ(brpc::EINTERNAL, cntl.ErrorCode()) << cntl.ErrorText(); + LOG(INFO) << cntl.ErrorText(); + StopAndJoin(); + } + + void TestServerFailSelective(bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + + const size_t NCHANS = 5; + brpc::SelectiveChannel channel; + ASSERT_EQ(0, channel.Init("rr", NULL)); + for (size_t i = 0; i < NCHANS; ++i) { + brpc::Channel* subchan = new brpc::Channel; + SetUpChannel(subchan, single_server, short_connection); + ASSERT_EQ(0, channel.AddChannel(subchan, NULL)) << "i=" << i; + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + req.set_server_fail(brpc::EINTERNAL); + CallMethod(&channel, &cntl, &req, &res, async); + + EXPECT_EQ(brpc::EINTERNAL, cntl.ErrorCode()) << cntl.ErrorText(); + ASSERT_EQ(1, cntl.sub_count()); + ASSERT_EQ(brpc::EINTERNAL, cntl.sub(0)->ErrorCode()); + + LOG(INFO) << cntl.ErrorText(); + StopAndJoin(); + } + + void TestDestroyChannel(bool single_server, bool short_connection) { + std::cout << "*** single=" << single_server + << ", short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + brpc::Channel* channel = new brpc::Channel(); + SetUpChannel(channel, single_server, short_connection); + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + req.set_sleep_us(10000); + CallMethod(channel, &cntl, &req, &res, true, true/*destroy*/); + + EXPECT_EQ(0, cntl.ErrorCode()) << cntl.ErrorText(); + EXPECT_EQ("received " + std::string(__FUNCTION__), res.message()); + // Sleep to let `_messenger' detect `Socket' being `SetFailed' + const int64_t start_time = butil::cpuwide_time_us(); + while (_messenger.ConnectionCount() != 0) { + EXPECT_LT(butil::cpuwide_time_us(), start_time + 100000L/*100ms*/); + bthread_usleep(1000); + } + + StopAndJoin(); + } + + void TestDestroyChannelParallel(bool single_server, bool short_connection) { + std::cout << "*** single=" << single_server + << ", short=" << short_connection << std::endl; + + const size_t NCHANS = 5; + ASSERT_EQ(0, StartAccept(_ep)); + brpc::ParallelChannel* channel = new brpc::ParallelChannel; + for (size_t i = 0; i < NCHANS; ++i) { + brpc::Channel* subchan = new brpc::Channel(); + SetUpChannel(subchan, single_server, short_connection); + ASSERT_EQ(0, channel->AddChannel( + subchan, brpc::OWNS_CHANNEL, NULL, NULL)); + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_sleep_us(10000); + req.set_message(__FUNCTION__); + CallMethod(channel, &cntl, &req, &res, true, true/*destroy*/); + + EXPECT_EQ(0, cntl.ErrorCode()) << cntl.ErrorText(); + EXPECT_EQ("received " + std::string(__FUNCTION__), res.message()); + // Sleep to let `_messenger' detect `Socket' being `SetFailed' + const int64_t start_time = butil::cpuwide_time_us(); + while (_messenger.ConnectionCount() != 0) { + EXPECT_LT(butil::cpuwide_time_us(), start_time + 100000L/*100ms*/); + bthread_usleep(1000); + } + StopAndJoin(); + } + + void TestDestroyChannelSelective(bool single_server, bool short_connection) { + std::cout << "*** single=" << single_server + << ", short=" << short_connection << std::endl; + + const size_t NCHANS = 5; + ASSERT_EQ(0, StartAccept(_ep)); + brpc::SelectiveChannel* channel = new brpc::SelectiveChannel; + ASSERT_EQ(0, channel->Init("rr", NULL)); + for (size_t i = 0; i < NCHANS; ++i) { + brpc::Channel* subchan = new brpc::Channel(); + SetUpChannel(subchan, single_server, short_connection); + ASSERT_EQ(0, channel->AddChannel(subchan, NULL)); + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_sleep_us(10000); + req.set_message(__FUNCTION__); + CallMethod(channel, &cntl, &req, &res, true, true/*destroy*/); + + EXPECT_EQ(0, cntl.ErrorCode()) << cntl.ErrorText(); + EXPECT_EQ("received " + std::string(__FUNCTION__), res.message()); + ASSERT_EQ(_ep, cntl.remote_side()); + ASSERT_EQ(1, cntl.sub_count()); + ASSERT_EQ(0, cntl.sub(0)->ErrorCode()); + + // Sleep to let `_messenger' detect `Socket' being `SetFailed' + const int64_t start_time = butil::cpuwide_time_us(); + while (_messenger.ConnectionCount() != 0) { + EXPECT_LT(butil::cpuwide_time_us(), start_time + 100000L/*100ms*/); + bthread_usleep(1000); + } + StopAndJoin(); + } + + void RPCThread(brpc::ChannelBase* channel, bool async) { + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + CallMethod(channel, &cntl, &req, &res, async); + + ASSERT_EQ(0, cntl.ErrorCode()) << cntl.ErrorText(); + EXPECT_EQ("received " + std::string(__FUNCTION__), res.message()); + } + + void RPCThread(brpc::ChannelBase* channel, bool async, int count) { + brpc::Controller cntl; + for (int i = 0; i < count; ++i) { + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + CallMethod(channel, &cntl, &req, &res, async); + + ASSERT_EQ(0, cntl.ErrorCode()) << cntl.ErrorText(); + ASSERT_EQ("received " + std::string(__FUNCTION__), res.message()); + cntl.Reset(); + } + } + + void RPCThread(bool single_server, bool async, bool short_connection, + const brpc::Authenticator* auth, int count) { + brpc::Channel channel; + SetUpChannel(&channel, single_server, short_connection, auth); + brpc::Controller cntl; + for (int i = 0; i < count; ++i) { + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + CallMethod(&channel, &cntl, &req, &res, async); + + ASSERT_EQ(0, cntl.ErrorCode()) << cntl.ErrorText(); + ASSERT_EQ("received " + std::string(__FUNCTION__), res.message()); + cntl.Reset(); + } + } + + void TestAuthentication(bool single_server, + bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + MyAuthenticator auth; + brpc::Channel channel; + SetUpChannel(&channel, single_server, short_connection, &auth); + + const int NUM = 10; + pthread_t tids[NUM]; + for (int i = 0; i < NUM; ++i) { + google::protobuf::Closure* thrd_func = + brpc::NewCallback( + this, &ChannelTest::RPCThread, (brpc::ChannelBase*)&channel, async); + EXPECT_EQ(0, pthread_create(&tids[i], NULL, + RunClosure, thrd_func)); + } + for (int i = 0; i < NUM; ++i) { + pthread_join(tids[i], NULL); + } + + if (short_connection) { + EXPECT_EQ(NUM, auth.count.load()); + } else { + EXPECT_EQ(1, auth.count.load()); + } + StopAndJoin(); + } + + void TestAuthenticationParallel(bool single_server, + bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + MyAuthenticator auth; + + const int NCHANS = 5; + brpc::Channel subchans[NCHANS]; + brpc::ParallelChannel channel; + for (int i = 0; i < NCHANS; ++i) { + SetUpChannel(&subchans[i], single_server, short_connection, &auth); + ASSERT_EQ(0, channel.AddChannel( + &subchans[i], brpc::DOESNT_OWN_CHANNEL, + NULL, NULL)); + } + + const int NUM = 10; + pthread_t tids[NUM]; + for (int i = 0; i < NUM; ++i) { + google::protobuf::Closure* thrd_func = + brpc::NewCallback( + this, &ChannelTest::RPCThread, (brpc::ChannelBase*)&channel, async); + EXPECT_EQ(0, pthread_create(&tids[i], NULL, + RunClosure, thrd_func)); + } + for (int i = 0; i < NUM; ++i) { + pthread_join(tids[i], NULL); + } + + if (short_connection) { + EXPECT_EQ(NUM * NCHANS, auth.count.load()); + } else { + EXPECT_EQ(1, auth.count.load()); + } + StopAndJoin(); + } + + void TestAuthenticationSelective(bool single_server, + bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + MyAuthenticator auth; + + const size_t NCHANS = 5; + brpc::SelectiveChannel channel; + ASSERT_EQ(0, channel.Init("rr", NULL)); + for (size_t i = 0; i < NCHANS; ++i) { + brpc::Channel* subchan = new brpc::Channel; + SetUpChannel(subchan, single_server, short_connection, &auth); + ASSERT_EQ(0, channel.AddChannel(subchan, NULL)) << "i=" << i; + } + + const int NUM = 10; + pthread_t tids[NUM]; + for (int i = 0; i < NUM; ++i) { + google::protobuf::Closure* thrd_func = + brpc::NewCallback( + this, &ChannelTest::RPCThread, (brpc::ChannelBase*)&channel, async); + EXPECT_EQ(0, pthread_create(&tids[i], NULL, + RunClosure, thrd_func)); + } + for (int i = 0; i < NUM; ++i) { + pthread_join(tids[i], NULL); + } + + if (short_connection) { + EXPECT_EQ(NUM, auth.count.load()); + } else { + EXPECT_EQ(1, auth.count.load()); + } + StopAndJoin(); + } + + void TestRetry(bool single_server, bool async, bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + brpc::Channel channel; + SetUpChannel(&channel, single_server, short_connection); + + const int RETRY_NUM = 3; + test::EchoRequest req; + test::EchoResponse res; + brpc::Controller cntl; + req.set_message(__FUNCTION__); + + // No retry when timeout + cntl.set_max_retry(RETRY_NUM); + cntl.set_timeout_ms(10); // 10ms + req.set_sleep_us(70000); // 70ms + CallMethod(&channel, &cntl, &req, &res, async); + EXPECT_EQ(brpc::ERPCTIMEDOUT, cntl.ErrorCode()) << cntl.ErrorText(); + EXPECT_EQ(0, cntl.retried_count()); + bthread_usleep(100000); // wait for the sleep task to finish + + // Retry when connection broken + cntl.Reset(); + cntl.set_max_retry(RETRY_NUM); + _close_fd_once = true; + req.set_sleep_us(0); + CallMethod(&channel, &cntl, &req, &res, async); + + if (short_connection) { + // Always succeed + EXPECT_EQ(0, cntl.ErrorCode()) << cntl.ErrorText(); + EXPECT_EQ(1, cntl.retried_count()); + + const int64_t start_time = butil::cpuwide_time_us(); + while (_messenger.ConnectionCount() != 0) { + EXPECT_LT(butil::cpuwide_time_us(), start_time + 100000L/*100ms*/); + bthread_usleep(1000); + } + } else { + // May fail if health checker can't revive in time + if (cntl.Failed()) { + EXPECT_EQ(EHOSTDOWN, cntl.ErrorCode()) << single_server << ", " << async; + EXPECT_EQ(RETRY_NUM, cntl.retried_count()); + } else { + EXPECT_TRUE(cntl.retried_count() > 0); + } + } + StopAndJoin(); + bthread_usleep(100000); // wait for stop + + // Retry when connection failed + cntl.Reset(); + cntl.set_max_retry(RETRY_NUM); + CallMethod(&channel, &cntl, &req, &res, async); + EXPECT_EQ(EHOSTDOWN, cntl.ErrorCode()); + EXPECT_EQ(RETRY_NUM, cntl.retried_count()); + } + + void TestRetryOtherServer(bool async, bool short_connection) { + ASSERT_EQ(0, StartAccept(_ep)); + + brpc::Channel channel; + brpc::ChannelOptions opt; + opt.timeout_ms = 1000; + if (short_connection) { + opt.connection_type = brpc::CONNECTION_TYPE_SHORT; + } + butil::TempFile server_list; + EXPECT_EQ(0, server_list.save_format( + "127.0.0.1:100\n" + "127.0.0.1:200\n" + "%s", endpoint2str(_ep).c_str())); + std::string naming_url = std::string("fIle://") + + server_list.fname(); + EXPECT_EQ(0, channel.Init(naming_url.c_str(), "RR", &opt)); + + const int RETRY_NUM = 3; + test::EchoRequest req; + test::EchoResponse res; + brpc::Controller cntl; + req.set_message(__FUNCTION__); + cntl.set_max_retry(RETRY_NUM); + CallMethod(&channel, &cntl, &req, &res, async); + + EXPECT_EQ(0, cntl.ErrorCode()) << async << ", " << short_connection; + StopAndJoin(); + } + + struct TestRetryBackoffInfo { + TestRetryBackoffInfo(ChannelTest* channel_test_param, + bool async_param, + bool short_connection_param, + bool fixed_backoff_param) + : channel_test(channel_test_param) + , async(async_param) + , short_connection(short_connection_param) + , fixed_backoff(fixed_backoff_param) {} + + ChannelTest* channel_test; + int async; + int short_connection; + int fixed_backoff; + }; + + static void* TestRetryBackoffBthread(void* void_args) { + auto args = static_cast(void_args); + args->channel_test->TestRetryBackoff(args->async, args->short_connection, + args->fixed_backoff, false); + return NULL; + } + + void TestRetryBackoff(bool async, bool short_connection, bool fixed_backoff, + bool retry_backoff_in_pthread) { + ASSERT_EQ(0, StartAccept(_ep)); + + const int32_t backoff_time_ms = 100; + const int32_t no_backoff_remaining_rpc_time_ms = 100; + std::unique_ptr retry_ptr; + if (fixed_backoff) { + retry_ptr.reset( + new brpc::RpcRetryPolicyWithFixedBackoff(backoff_time_ms, + no_backoff_remaining_rpc_time_ms, + retry_backoff_in_pthread)); + } else { + retry_ptr.reset( + new brpc::RpcRetryPolicyWithJitteredBackoff(backoff_time_ms, + backoff_time_ms + 20, + no_backoff_remaining_rpc_time_ms, + retry_backoff_in_pthread)); + } + + brpc::Channel channel; + brpc::ChannelOptions opt; + opt.timeout_ms = 1000; + opt.retry_policy = retry_ptr.get(); + if (short_connection) { + opt.connection_type = brpc::CONNECTION_TYPE_SHORT; + } + butil::TempFile server_list; + EXPECT_EQ(0, server_list.save_format( + "127.0.0.1:100\n" + "127.0.0.1:200\n" + "%s", endpoint2str(_ep).c_str())); + std::string naming_url = std::string("fIle://") + + server_list.fname(); + EXPECT_EQ(0, channel.Init(naming_url.c_str(), "RR", &opt)); + + const int RETRY_NUM = 3; + test::EchoRequest req; + test::EchoResponse res; + brpc::Controller cntl; + req.set_message(__FUNCTION__); + cntl.set_max_retry(RETRY_NUM); + CallMethod(&channel, &cntl, &req, &res, async); + if (cntl.retried_count() > 0) { + EXPECT_GT(cntl.latency_us(), ((int64_t)backoff_time_ms * 1000) * cntl.retried_count()) + << "latency_us=" << cntl.latency_us() << " retried_count=" << cntl.retried_count() + << " enable_retry_backoff_in_pthread=" << retry_backoff_in_pthread; + } + EXPECT_EQ(0, cntl.ErrorCode()) << async << ", " << short_connection; + StopAndJoin(); + } + + void TestBackupRequest(bool single_server, bool async, + bool short_connection) { + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection << std::endl; + + ASSERT_EQ(0, StartAccept(_ep)); + brpc::Channel channel; + SetUpChannel(&channel, single_server, short_connection); + + const int RETRY_NUM = 1; + test::EchoRequest req; + test::EchoResponse res; + brpc::Controller cntl; + req.set_message(__FUNCTION__); + + cntl.set_max_retry(RETRY_NUM); + cntl.set_backup_request_ms(10); // 10ms + cntl.set_timeout_ms(100); // 10ms + req.set_sleep_us(50000); // 100ms + CallMethod(&channel, &cntl, &req, &res, async); + ASSERT_EQ(0, cntl.ErrorCode()) << cntl.ErrorText(); + ASSERT_TRUE(cntl.has_backup_request()); + ASSERT_EQ(RETRY_NUM, cntl.retried_count()); + bthread_usleep(70000); // wait for the sleep task to finish + + if (short_connection) { + // Sleep to let `_messenger' detect `Socket' being `SetFailed' + const int64_t start_time = butil::cpuwide_time_us(); + while (_messenger.ConnectionCount() != 0) { + EXPECT_LT(butil::cpuwide_time_us(), start_time + 100000L/*100ms*/); + bthread_usleep(1000); + } + } else { + EXPECT_GE(1ul, _messenger.ConnectionCount()); + } + StopAndJoin(); + } + + class BackupRequestPolicyImpl : public brpc::BackupRequestPolicy { + public: + int32_t GetBackupRequestMs(const brpc::Controller*) const override { + return 10; + } + + // Return true if the backup request should be sent. + bool DoBackup(const brpc::Controller*) const override { + return backup; + } + + void OnRPCEnd(const brpc::Controller*) override {} + + bool backup{true}; + + }; + + void TestBackupRequestPolicy(bool single_server, bool async, + bool short_connection) { + ASSERT_EQ(0, StartAccept(_ep)); + for (int i = 0; i < 2; ++i) { + bool backup = i == 0; + std::cout << " *** single=" << single_server + << " async=" << async + << " short=" << short_connection + << " backup=" << backup + << std::endl; + + brpc::Channel channel; + SetUpChannel(&channel, single_server, short_connection, NULL, "", true); + + const int RETRY_NUM = 1; + test::EchoRequest req; + test::EchoResponse res; + brpc::Controller cntl; + req.set_message(__FUNCTION__); + + _backup_request_policy.backup = backup; + cntl.set_max_retry(RETRY_NUM); + cntl.set_timeout_ms(100); // 100ms + req.set_sleep_us(50000); // 50ms + CallMethod(&channel, &cntl, &req, &res, async); + ASSERT_EQ(0, cntl.ErrorCode()) << cntl.ErrorText(); + ASSERT_EQ(backup, cntl.has_backup_request()); + ASSERT_EQ(backup ? RETRY_NUM : 0, cntl.retried_count()); + bthread_usleep(70000); // wait for the sleep task to finish + + if (short_connection) { + // Sleep to let `_messenger' detect `Socket' being `SetFailed' + const int64_t start_time = butil::cpuwide_time_us(); + while (_messenger.ConnectionCount() != 0) { + ASSERT_LT(butil::cpuwide_time_us(), start_time + 100000L/*100ms*/); + bthread_usleep(1000); + } + } else { + ASSERT_GE(1ul, _messenger.ConnectionCount()); + } + } + + StopAndJoin(); + } + + butil::EndPoint _ep; + butil::TempFile _server_list; + std::string _naming_url; + + brpc::Acceptor _messenger; + // Dummy server for `Server::AddError' + brpc::Server _dummy; + std::string _mock_fail_str; + + bool _close_fd_once; + + MyEchoService _svc; + BackupRequestPolicyImpl _backup_request_policy; +}; + +class MyShared : public brpc::SharedObject { +public: + MyShared() { ++ nctor; } + MyShared(const MyShared&) : brpc::SharedObject() { ++ nctor; } + ~MyShared() override { ++ ndtor; } + + static int nctor; + static int ndtor; +}; +int MyShared::nctor = 0; +int MyShared::ndtor = 0; + +TEST_F(ChannelTest, intrusive_ptr_sanity) { + MyShared::nctor = 0; + MyShared::ndtor = 0; + { + MyShared* s1 = new MyShared; + ASSERT_EQ(0, s1->ref_count()); + butil::intrusive_ptr p1 = s1; + ASSERT_EQ(1, p1->ref_count()); + { + butil::intrusive_ptr p2 = s1; + ASSERT_EQ(2, p2->ref_count()); + ASSERT_EQ(2, p1->ref_count()); + } + ASSERT_EQ(1, p1->ref_count()); + } + ASSERT_EQ(1, MyShared::nctor); + ASSERT_EQ(1, MyShared::ndtor); +} + +TEST_F(ChannelTest, init_as_single_server) { + { + brpc::Channel channel; + ASSERT_EQ(-1, channel.Init("127.0.0.1:12345:asdf", NULL)); + ASSERT_EQ(-1, channel.Init("127.0.0.1:99999", NULL)); + ASSERT_EQ(0, channel.Init("127.0.0.1:8888", NULL)); + } + { + brpc::Channel channel; + ASSERT_EQ(-1, channel.Init("127.0.0.1asdf", 12345, NULL)); + ASSERT_EQ(-1, channel.Init("127.0.0.1", 99999, NULL)); + ASSERT_EQ(0, channel.Init("127.0.0.1", 8888, NULL)); + } + + butil::EndPoint ep; + brpc::Channel channel; + ASSERT_EQ(0, str2endpoint("127.0.0.1:8888", &ep)); + ASSERT_EQ(0, channel.Init(ep, NULL)); + ASSERT_TRUE(channel.SingleServer()); + ASSERT_EQ(ep, channel._server_address); + + brpc::SocketId id; + ASSERT_EQ(0, brpc::SocketMapFind(brpc::SocketMapKey(ep), &id)); + ASSERT_EQ(id, channel._server_id); + + const int NUM = 10; + brpc::Channel channels[NUM]; + for (int i = 0; i < 10; ++i) { + ASSERT_EQ(0, channels[i].Init(ep, NULL)); + // Share the same server socket + ASSERT_EQ(id, channels[i]._server_id); + } +} + +TEST_F(ChannelTest, init_using_unknown_naming_service) { + brpc::Channel channel; + ASSERT_EQ(-1, channel.Init("unknown://unknown", "unknown", NULL)); +} + +TEST_F(ChannelTest, init_using_unexist_fns) { + brpc::Channel channel; + ASSERT_EQ(-1, channel.Init("fiLe://no_such_file", "rr", NULL)); +} + +TEST_F(ChannelTest, init_using_empty_fns) { + brpc::ChannelOptions opt; + opt.succeed_without_server = false; + brpc::Channel channel; + butil::TempFile server_list; + ASSERT_EQ(0, server_list.save("")); + std::string naming_url = std::string("file://") + server_list.fname(); + // empty file list results in error. + ASSERT_EQ(-1, channel.Init(naming_url.c_str(), "rr", &opt)); + + ASSERT_EQ(0, server_list.save("blahblah")); + // No valid address. + ASSERT_EQ(-1, channel.Init(naming_url.c_str(), "rr", NULL)); +} + +TEST_F(ChannelTest, init_using_empty_lns) { + brpc::ChannelOptions opt; + opt.succeed_without_server = false; + brpc::Channel channel; + ASSERT_EQ(-1, channel.Init("list:// ", "rr", &opt)); + ASSERT_EQ(-1, channel.Init("list://", "rr", &opt)); + ASSERT_EQ(-1, channel.Init("list://blahblah", "rr", &opt)); +} + +TEST_F(ChannelTest, init_using_naming_service) { + brpc::Channel* channel = new brpc::Channel(); + butil::TempFile server_list; + ASSERT_EQ(0, server_list.save("127.0.0.1:8888")); + std::string naming_url = std::string("filE://") + server_list.fname(); + // Rr are intended to test case-insensitivity. + ASSERT_EQ(0, channel->Init(naming_url.c_str(), "Rr", NULL)); + ASSERT_FALSE(channel->SingleServer()); + + brpc::LoadBalancerWithNaming* lb = + dynamic_cast(channel->_lb.get()); + ASSERT_TRUE(lb != NULL); + brpc::NamingServiceThread* ns = lb->_nsthread_ptr.get(); + + { + const int NUM = 10; + brpc::Channel channels[NUM]; + for (int i = 0; i < NUM; ++i) { + // Share the same naming thread + ASSERT_EQ(0, channels[i].Init(naming_url.c_str(), "rr", NULL)); + brpc::LoadBalancerWithNaming* lb2 = + dynamic_cast(channels[i]._lb.get()); + ASSERT_TRUE(lb2 != NULL); + ASSERT_EQ(ns, lb2->_nsthread_ptr.get()); + } + } + + // `lb' should be valid even if `channel' has destroyed + // since we hold another reference to it + butil::intrusive_ptr + another_ctx = channel->_lb; + delete channel; + ASSERT_EQ(lb, another_ctx.get()); + ASSERT_EQ(1, another_ctx->_nref.load()); + // `lb' should be destroyed after +} + +TEST_F(ChannelTest, parse_hostname) { + brpc::ChannelOptions opt; + opt.succeed_without_server = false; + opt.protocol = brpc::PROTOCOL_HTTP; + brpc::Channel channel; + + ASSERT_EQ(-1, channel.Init("", 8888, &opt)); + ASSERT_EQ("", channel._service_name); + ASSERT_EQ(-1, channel.Init("", &opt)); + ASSERT_EQ("", channel._service_name); + + ASSERT_EQ(0, channel.Init("http://127.0.0.1", 8888, &opt)); + ASSERT_EQ("127.0.0.1:8888", channel._service_name); + ASSERT_EQ(0, channel.Init("http://127.0.0.1:8888", &opt)); + ASSERT_EQ("127.0.0.1:8888", channel._service_name); + + ASSERT_EQ(0, channel.Init("localhost", 8888, &opt)); + ASSERT_EQ("localhost:8888", channel._service_name); + ASSERT_EQ(0, channel.Init("localhost:8888", &opt)); + ASSERT_EQ("localhost:8888", channel._service_name); + + ASSERT_EQ(0, channel.Init("http://www.baidu.com", &opt)); + ASSERT_EQ("www.baidu.com", channel._service_name); + ASSERT_EQ(0, channel.Init("http://www.baidu.com:80", &opt)); + ASSERT_EQ("www.baidu.com:80", channel._service_name); + ASSERT_EQ(0, channel.Init("http://www.baidu.com", 80, &opt)); + ASSERT_EQ("www.baidu.com:80", channel._service_name); + ASSERT_EQ(0, channel.Init("http://www.baidu.com:8888", &opt)); + ASSERT_EQ("www.baidu.com:8888", channel._service_name); + ASSERT_EQ(0, channel.Init("http://www.baidu.com", 8888, &opt)); + ASSERT_EQ("www.baidu.com:8888", channel._service_name); + ASSERT_EQ(0, channel.Init("http://www.baidu.com", "rr", &opt)); + ASSERT_EQ("www.baidu.com", channel._service_name); + ASSERT_EQ(0, channel.Init("http://www.baidu.com:80", "rr", &opt)); + ASSERT_EQ("www.baidu.com:80", channel._service_name); + ASSERT_EQ(0, channel.Init("http://www.baidu.com:8888", "rr", &opt)); + ASSERT_EQ("www.baidu.com:8888", channel._service_name); + + ASSERT_EQ(0, channel.Init("https://www.baidu.com", &opt)); + ASSERT_EQ("www.baidu.com", channel._service_name); + ASSERT_EQ(0, channel.Init("https://www.baidu.com:443", &opt)); + ASSERT_EQ("www.baidu.com:443", channel._service_name); + ASSERT_EQ(0, channel.Init("https://www.baidu.com", 443, &opt)); + ASSERT_EQ("www.baidu.com:443", channel._service_name); + ASSERT_EQ(0, channel.Init("https://www.baidu.com:1443", &opt)); + ASSERT_EQ("www.baidu.com:1443", channel._service_name); + ASSERT_EQ(0, channel.Init("https://www.baidu.com", 1443, &opt)); + ASSERT_EQ("www.baidu.com:1443", channel._service_name); + ASSERT_EQ(0, channel.Init("https://www.baidu.com", "rr", &opt)); + ASSERT_EQ("www.baidu.com", channel._service_name); + ASSERT_EQ(0, channel.Init("https://www.baidu.com:443", "rr", &opt)); + ASSERT_EQ("www.baidu.com:443", channel._service_name); + ASSERT_EQ(0, channel.Init("https://www.baidu.com:1443", "rr", &opt)); + ASSERT_EQ("www.baidu.com:1443", channel._service_name); + + const char *address_list[] = { + "10.127.0.1:1234", + "10.128.0.1:1234 enable", + "10.129.0.1:1234", + "localhost:1234", + "www.baidu.com:1234" + }; + butil::TempFile tmp_file; + { + FILE* fp = fopen(tmp_file.fname(), "w"); + for (size_t i = 0; i < ARRAY_SIZE(address_list); ++i) { + ASSERT_TRUE(fprintf(fp, "%s\n", address_list[i])); + } + fclose(fp); + } + brpc::Channel ns_channel; + std::string ns = std::string("file://") + tmp_file.fname(); + ASSERT_EQ(0, ns_channel.Init(ns.c_str(), "rr", &opt)); + ASSERT_EQ(tmp_file.fname(), ns_channel._service_name); +} + +TEST_F(ChannelTest, connection_failed) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestConnectionFailed(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, empty_parallel_channel) { + brpc::ParallelChannel channel; + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + CallMethod(&channel, &cntl, &req, &res, false); + EXPECT_EQ(EPERM, cntl.ErrorCode()) << cntl.ErrorText(); +} + +TEST_F(ChannelTest, uninitialized_selective_channel) { + brpc::SelectiveChannel channel; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + + brpc::Controller sync_cntl; + ::test::EchoService::Stub(&channel).Echo(&sync_cntl, &req, &res, NULL); + EXPECT_EQ(EINVAL, sync_cntl.ErrorCode()) << sync_cntl.ErrorText(); + + brpc::Controller async_cntl; + bool done_called = false; + google::protobuf::Closure* done = + brpc::NewCallback(&MarkCalled, &done_called); + ::test::EchoService::Stub(&channel).Echo(&async_cntl, &req, &res, done); + EXPECT_TRUE(done_called); + EXPECT_EQ(EINVAL, async_cntl.ErrorCode()) << async_cntl.ErrorText(); +} + +TEST_F(ChannelTest, empty_selective_channel) { + brpc::SelectiveChannel channel; + ASSERT_EQ(0, channel.Init("rr", NULL)); + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + CallMethod(&channel, &cntl, &req, &res, false); + EXPECT_EQ(ENODATA, cntl.ErrorCode()) << cntl.ErrorText(); +} + +class BadCall : public brpc::CallMapper { + brpc::SubCall Map(int, + const google::protobuf::MethodDescriptor*, + const google::protobuf::Message*, + google::protobuf::Message*) override { + return brpc::SubCall::Bad(); + } +}; + +TEST_F(ChannelTest, returns_bad_parallel) { + const size_t NCHANS = 5; + brpc::ParallelChannel channel; + for (size_t i = 0; i < NCHANS; ++i) { + brpc::Channel* subchan = new brpc::Channel(); + SetUpChannel(subchan, true, false); + ASSERT_EQ(0, channel.AddChannel( + subchan, brpc::OWNS_CHANNEL, new BadCall, NULL)); + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + CallMethod(&channel, &cntl, &req, &res, false); + EXPECT_EQ(brpc::EREQUEST, cntl.ErrorCode()) << cntl.ErrorText(); +} + +class SkipCall : public brpc::CallMapper { + brpc::SubCall Map(int, + const google::protobuf::MethodDescriptor*, + const google::protobuf::Message*, + google::protobuf::Message*) override { + return brpc::SubCall::Skip(); + } +}; + +TEST_F(ChannelTest, skip_all_channels) { + const size_t NCHANS = 5; + brpc::ParallelChannel channel; + for (size_t i = 0; i < NCHANS; ++i) { + brpc::Channel* subchan = new brpc::Channel(); + SetUpChannel(subchan, true, false); + ASSERT_EQ(0, channel.AddChannel( + subchan, brpc::OWNS_CHANNEL, new SkipCall, NULL)); + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + CallMethod(&channel, &cntl, &req, &res, false); + + EXPECT_EQ(ECANCELED, cntl.ErrorCode()) << cntl.ErrorText(); + EXPECT_EQ((int)NCHANS, cntl.sub_count()); + for (int i = 0; i < cntl.sub_count(); ++i) { + EXPECT_TRUE(NULL == cntl.sub(i)) << "i=" << i; + } +} + +static const std::string ECHO_HTTP_HEADER = "echo-http-header"; + +class EchoHttpHeader : public brpc::CallMapper { +public: + brpc::SubCall Map(int channel_index, int channel_count, + const google::protobuf::MethodDescriptor* method, + const google::protobuf::Message* request, + google::protobuf::Message* response) override { + return brpc::SubCall(method, request, response->New(), brpc::DELETE_RESPONSE); + } + + void MapController(int channel_index, int, + const brpc::Controller* main_cntl, + brpc::Controller* sub_cntl) override { + sub_cntl->http_request().SetHeader(ECHO_HTTP_HEADER, std::to_string(channel_index)); + } +}; + +TEST_F(ChannelTest, http_header_parallel_channels) { + brpc::Server server; + MyEchoService service; + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + brpc::ServerOptions opt; + ASSERT_EQ(0, server.Start(_ep, &opt)); + + const size_t NCHANS = 5; + brpc::ParallelChannel channel; + for (size_t i = 0; i < NCHANS; ++i) { + brpc::Channel* sub_chan = new brpc::Channel(); + SetUpChannel(sub_chan, true, false, NULL, "", false, brpc::PROTOCOL_HTTP); + ASSERT_EQ(0, channel.AddChannel(sub_chan, brpc::OWNS_CHANNEL, new EchoHttpHeader, NULL)); + } + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + *req.mutable_http_header() = ECHO_HTTP_HEADER; + CallMethod(&channel, &cntl, &req, &res, false); + + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ((int)NCHANS, cntl.sub_count()); + for (int i = 0; i < cntl.sub_count(); ++i) { + const brpc::Controller* sub_cntl = cntl.sub(i); + ASSERT_TRUE(NULL != sub_cntl) << "i=" << i; + ASSERT_EQ(std::to_string(i), *sub_cntl->http_response().GetHeader(ECHO_HTTP_HEADER)); + } +} + +TEST_F(ChannelTest, connection_failed_parallel) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestConnectionFailedParallel(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, connection_failed_selective) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestConnectionFailedSelective(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, success) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestSuccess(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, success_parallel) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestSuccessParallel(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, success_duplicated_parallel) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestSuccessDuplicatedParallel(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, success_selective) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestSuccessSelective(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, skip_parallel) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestSkipParallel(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, success_parallel2) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestSuccessParallel2(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, success_limit_parallel) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestSuccessLimitParallel(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, cancel_before_callmethod) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + CancelBeforeCallMethod(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, cancel_before_callmethod_parallel) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + CancelBeforeCallMethodParallel(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, cancel_before_callmethod_selective) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + CancelBeforeCallMethodSelective(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, cancel_during_callmethod) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + CancelDuringCallMethod(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, cancel_during_callmethod_parallel) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + CancelDuringCallMethodParallel(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, cancel_during_callmethod_selective) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + CancelDuringCallMethodSelective(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, cancel_after_callmethod) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + CancelAfterCallMethod(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, cancel_after_callmethod_parallel) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + CancelAfterCallMethodParallel(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, request_not_init) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestRequestNotInit(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, request_not_init_parallel) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestRequestNotInitParallel(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, request_not_init_selective) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestRequestNotInitSelective(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, timeout) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestRPCTimeout(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, timeout_parallel) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestRPCTimeoutParallel(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, timeout_still_checks_sub_channels_parallel) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TimeoutStillChecksSubChannelsParallel(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, timeout_selective) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestRPCTimeoutSelective(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, backuprequest_selective) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestBackupRequestSelective(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, backuprequest_selective_response_race) { + TestBackupRequestSelectiveResponseRace(); +} + +TEST_F(ChannelTest, close_fd) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestCloseFD(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, close_fd_parallel) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestCloseFDParallel(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, close_fd_selective) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestCloseFDSelective(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, server_fail) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestServerFail(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, server_fail_parallel) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestServerFailParallel(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, server_fail_selective) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestServerFailSelective(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, authentication) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestAuthentication(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, authentication_parallel) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestAuthenticationParallel(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, authentication_selective) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestAuthenticationSelective(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, retry) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestRetry(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, retry_other_servers) { + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <=1; ++k) { // Flag ShortConnection + TestRetryOtherServer(j, k); + } + } +} + +TEST_F(ChannelTest, retry_backoff) { + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <= 1; ++k) { // Flag ShortConnection + for (int l = 0; l <= 1; ++l) { // Flag FixedRetryBackoffPolicy or JitteredRetryBackoffPolicy + for (int m = 0; m <= 1; ++m) { // Flag retry backoff in bthread or pthread + if (m % 2 == 0) { + bthread_t th; + bthread_attr_t attr = BTHREAD_ATTR_NORMAL; + std::unique_ptr test_retry_backoff( + new TestRetryBackoffInfo(this, j, k, l)); + // Retry backoff in bthread. + bthread_start_background(&th, &attr, TestRetryBackoffBthread, test_retry_backoff.get()); + bthread_join(th, NULL); + } else { + // Retry backoff in pthread. + TestRetryBackoff(j, k, l, true); + } + } + } + } + } +} + +TEST_F(ChannelTest, backup_request) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <= 1; ++k) { // Flag ShortConnection + TestBackupRequest(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, backup_request_policy) { + for (int i = 0; i <= 1; ++i) { // Flag SingleServer + for (int j = 0; j <= 1; ++j) { // Flag Asynchronous + for (int k = 0; k <= 1; ++k) { // Flag ShortConnection + TestBackupRequestPolicy(i, j, k); + } + } + } +} + +TEST_F(ChannelTest, selective_channel_ignores_late_subdone_after_timeout) { + DelayedCloseEchoService service; + brpc::Server server; + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start("127.0.0.1:0", NULL)); + + brpc::SelectiveChannel channel; + ASSERT_EQ(0, channel.Init("rr", NULL)); + + brpc::ChannelOptions options; + options.timeout_ms = 100; + for (int i = 0; i < 2; ++i) { + brpc::Channel* sub_channel = new brpc::Channel; + ASSERT_EQ(0, sub_channel->Init(server.listen_address(), &options)); + ASSERT_EQ(0, channel.AddChannel(sub_channel, NULL)); + } + + brpc::Controller cntl; + cntl.set_max_retry(3); + cntl.set_backup_request_ms(1); + cntl.set_timeout_ms(10); + + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + req.set_sleep_us(50000); + CallMethod(&channel, &cntl, &req, &res, true); + + ASSERT_EQ(brpc::ERPCTIMEDOUT, cntl.ErrorCode()) << cntl.ErrorText(); + ASSERT_TRUE(cntl.has_backup_request()); + ASSERT_GE(cntl.retried_count(), 1); + + // Let the delayed sub-calls close their connections after the main RPC + // has already timed out. This used to re-enter retry/backup from + // SubDone::Run() on a partially torn-down controller. + bthread_usleep(120000); + + EXPECT_EQ(brpc::ERPCTIMEDOUT, cntl.ErrorCode()) << cntl.ErrorText(); + server.Stop(0); + server.Join(); +} + +TEST_F(ChannelTest, multiple_threads_single_channel) { + srand(time(NULL)); + ASSERT_EQ(0, StartAccept(_ep)); + MyAuthenticator auth; + const int NUM = 10; + const int COUNT = 10000; + pthread_t tids[NUM]; + + // Cause massive connect/close log if setting to true + bool short_connection = false; + for (int single_server = 0; single_server <= 1; ++single_server) { + for (int need_auth = 0; need_auth <= 1; ++need_auth) { + for (int async = 0; async <= 1; ++async) { + std::cout << " *** short=" << short_connection + << " single=" << single_server + << " auth=" << need_auth + << " async=" << async << std::endl; + brpc::Channel channel; + SetUpChannel(&channel, single_server, + short_connection, (need_auth ? &auth : NULL)); + for (int i = 0; i < NUM; ++i) { + google::protobuf::Closure* thrd_func = + brpc::NewCallback( + this, &ChannelTest::RPCThread, + (brpc::ChannelBase*)&channel, + (bool)async, COUNT); + EXPECT_EQ(0, pthread_create(&tids[i], NULL, + RunClosure, thrd_func)); + } + for (int i = 0; i < NUM; ++i) { + pthread_join(tids[i], NULL); + } + } + } + } +} + +TEST_F(ChannelTest, multiple_threads_multiple_channels) { + srand(time(NULL)); + ASSERT_EQ(0, StartAccept(_ep)); + MyAuthenticator auth; + const int NUM = 10; + const int COUNT = 10000; + pthread_t tids[NUM]; + + // Cause massive connect/close log if setting to true + bool short_connection = false; + + for (int single_server = 0; single_server <= 1; ++single_server) { + for (int need_auth = 0; need_auth <= 1; ++need_auth) { + for (int async = 0; async <= 1; ++async) { + std::cout << " *** short=" << short_connection + << " single=" << single_server + << " auth=" << need_auth + << " async=" << async << std::endl; + for (int i = 0; i < NUM; ++i) { + google::protobuf::Closure* thrd_func = + brpc::NewCallback< + ChannelTest, ChannelTest*, + bool, bool, bool, const brpc::Authenticator*, int> + (this, &ChannelTest::RPCThread, single_server, + async, short_connection, (need_auth ? &auth : NULL), COUNT); + EXPECT_EQ(0, pthread_create(&tids[i], NULL, + RunClosure, thrd_func)); + } + for (int i = 0; i < NUM; ++i) { + pthread_join(tids[i], NULL); + } + } + } + } +} + +TEST_F(ChannelTest, clear_attachment_after_retry) { + for (int j = 0; j <= 1; ++j) { + for (int k = 0; k <= 1; ++k) { + TestAttachment(j, k); + } + } +} + +TEST_F(ChannelTest, destroy_channel) { + for (int i = 0; i <= 1; ++i) { + for (int j = 0; j <= 1; ++j) { + TestDestroyChannel(i, j); + } + } +} + +TEST_F(ChannelTest, destroy_channel_parallel) { + for (int i = 0; i <= 1; ++i) { + for (int j = 0; j <= 1; ++j) { + TestDestroyChannelParallel(i, j); + } + } +} + +TEST_F(ChannelTest, destroy_channel_selective) { + for (int i = 0; i <= 1; ++i) { + for (int j = 0; j <= 1; ++j) { + TestDestroyChannelSelective(i, j); + } + } +} + +TEST_F(ChannelTest, sizeof) { + LOG(INFO) << "Size of Channel is " << sizeof(brpc::Channel) + << ", Size of ParallelChannel is " << sizeof(brpc::ParallelChannel) + << ", Size of Controller is " << sizeof(brpc::Controller) + << ", Size of vector is " << sizeof(std::vector); +} + +brpc::Channel g_chan; + +TEST_F(ChannelTest, global_channel_should_quit_successfully) { + g_chan.Init("bns://qa-pbrpc.SAT.tjyx", "rr", NULL); +} + +TEST_F(ChannelTest, unused_call_id) { + { + brpc::Controller cntl; + } + { + brpc::Controller cntl; + cntl.Reset(); + } + brpc::CallId cid1 = { 0 }; + { + brpc::Controller cntl; + cid1 = cntl.call_id(); + } + ASSERT_EQ(EINVAL, bthread_id_error(cid1, ECANCELED)); + + { + brpc::CallId cid2 = { 0 }; + brpc::Controller cntl; + cid2 = cntl.call_id(); + cntl.Reset(); + ASSERT_EQ(EINVAL, bthread_id_error(cid2, ECANCELED)); + } +} + +TEST_F(ChannelTest, adaptive_connection_type) { + brpc::AdaptiveConnectionType ctype; + ASSERT_EQ(brpc::CONNECTION_TYPE_UNKNOWN, ctype); + ASSERT_FALSE(ctype.has_error()); + ASSERT_STREQ("unknown", ctype.name()); + + ctype = brpc::CONNECTION_TYPE_SINGLE; + ASSERT_EQ(brpc::CONNECTION_TYPE_SINGLE, ctype); + ASSERT_STREQ("single", ctype.name()); + + ctype = "shorT"; + ASSERT_EQ(brpc::CONNECTION_TYPE_SHORT, ctype); + ASSERT_STREQ("short", ctype.name()); + + ctype = "PooLed"; + ASSERT_EQ(brpc::CONNECTION_TYPE_POOLED, ctype); + ASSERT_STREQ("pooled", ctype.name()); + + ctype = "SINGLE"; + ASSERT_EQ(brpc::CONNECTION_TYPE_SINGLE, ctype); + ASSERT_FALSE(ctype.has_error()); + ASSERT_STREQ("single", ctype.name()); + + ctype = "blah"; + ASSERT_EQ(brpc::CONNECTION_TYPE_UNKNOWN, ctype); + ASSERT_TRUE(ctype.has_error()); + ASSERT_STREQ("unknown", ctype.name()); + + ctype = "single"; + ASSERT_EQ(brpc::CONNECTION_TYPE_SINGLE, ctype); + ASSERT_FALSE(ctype.has_error()); + ASSERT_STREQ("single", ctype.name()); +} + +TEST_F(ChannelTest, adaptive_protocol_type) { + brpc::AdaptiveProtocolType ptype; + ASSERT_EQ(brpc::PROTOCOL_UNKNOWN, ptype); + ASSERT_STREQ("unknown", ptype.name()); + ASSERT_FALSE(ptype.has_param()); + ASSERT_EQ("", ptype.param()); + + ptype = brpc::PROTOCOL_HTTP; + ASSERT_EQ(brpc::PROTOCOL_HTTP, ptype); + ASSERT_STREQ("http", ptype.name()); + ASSERT_FALSE(ptype.has_param()); + ASSERT_EQ("", ptype.param()); + + ptype = "http:xyz "; + ASSERT_EQ(brpc::PROTOCOL_HTTP, ptype); + ASSERT_STREQ("http", ptype.name()); + ASSERT_TRUE(ptype.has_param()); + ASSERT_EQ("xyz ", ptype.param()); + + ptype = "HuLu_pbRPC"; + ASSERT_EQ(brpc::PROTOCOL_HULU_PBRPC, ptype); + ASSERT_STREQ("hulu_pbrpc", ptype.name()); + ASSERT_FALSE(ptype.has_param()); + ASSERT_EQ("", ptype.param()); + + ptype = "blah"; + ASSERT_EQ(brpc::PROTOCOL_UNKNOWN, ptype); + ASSERT_STREQ("blah", ptype.name()); + ASSERT_FALSE(ptype.has_param()); + ASSERT_EQ("", ptype.param()); + + ptype = "Baidu_STD"; + ASSERT_EQ(brpc::PROTOCOL_BAIDU_STD, ptype); + ASSERT_STREQ("baidu_std", ptype.name()); + ASSERT_FALSE(ptype.has_param()); + ASSERT_EQ("", ptype.param()); +} + +class RateLimitedBackupPolicyTest : public ::testing::Test {}; + +TEST_F(RateLimitedBackupPolicyTest, InvalidBackupRequestMs) { + brpc::RateLimitedBackupPolicyOptions opts; + opts.backup_request_ms = -2; + ASSERT_EQ(NULL, brpc::CreateRateLimitedBackupPolicy(opts)); +} + +TEST_F(RateLimitedBackupPolicyTest, InvalidMaxBackupRatioZero) { + brpc::RateLimitedBackupPolicyOptions opts; + opts.backup_request_ms = 100; + opts.max_backup_ratio = 0.0; + ASSERT_EQ(NULL, brpc::CreateRateLimitedBackupPolicy(opts)); +} + +TEST_F(RateLimitedBackupPolicyTest, InvalidMaxBackupRatioNegative) { + brpc::RateLimitedBackupPolicyOptions opts; + opts.backup_request_ms = 100; + opts.max_backup_ratio = -0.1; + ASSERT_EQ(NULL, brpc::CreateRateLimitedBackupPolicy(opts)); +} + +TEST_F(RateLimitedBackupPolicyTest, InvalidMaxBackupRatioAboveOne) { + brpc::RateLimitedBackupPolicyOptions opts; + opts.backup_request_ms = 100; + opts.max_backup_ratio = 1.001; + ASSERT_EQ(NULL, brpc::CreateRateLimitedBackupPolicy(opts)); +} + +TEST_F(RateLimitedBackupPolicyTest, InvalidWindowSizeTooSmall) { + brpc::RateLimitedBackupPolicyOptions opts; + opts.backup_request_ms = 100; + opts.window_size_seconds = 0; + ASSERT_EQ(NULL, brpc::CreateRateLimitedBackupPolicy(opts)); +} + +TEST_F(RateLimitedBackupPolicyTest, InvalidWindowSizeTooLarge) { + brpc::RateLimitedBackupPolicyOptions opts; + opts.backup_request_ms = 100; + opts.window_size_seconds = 3601; + ASSERT_EQ(NULL, brpc::CreateRateLimitedBackupPolicy(opts)); +} + +TEST_F(RateLimitedBackupPolicyTest, InvalidUpdateIntervalTooSmall) { + brpc::RateLimitedBackupPolicyOptions opts; + opts.backup_request_ms = 100; + opts.update_interval_seconds = 0; + ASSERT_EQ(NULL, brpc::CreateRateLimitedBackupPolicy(opts)); +} + +TEST_F(RateLimitedBackupPolicyTest, ValidMinusOneBackupRequestMsInherits) { + brpc::RateLimitedBackupPolicyOptions opts; + opts.backup_request_ms = -1; + std::unique_ptr p( + brpc::CreateRateLimitedBackupPolicy(opts)); + ASSERT_TRUE(p != NULL); + ASSERT_EQ(-1, p->GetBackupRequestMs(NULL)); +} + +TEST_F(RateLimitedBackupPolicyTest, ValidMaxRatioAtBoundary) { + brpc::RateLimitedBackupPolicyOptions opts; + opts.backup_request_ms = 50; + opts.max_backup_ratio = 1.0; + std::unique_ptr p( + brpc::CreateRateLimitedBackupPolicy(opts)); + ASSERT_TRUE(p != NULL); + // With max_backup_ratio=1.0 and true cold start (total==0, backup==0), + // ShouldAllow() sets ratio=0.0 (free pass). The conservative ratio=1.0 + // path only applies when backup>0 but total==0 (latency spike with no + // completions yet). At absolute cold start DoBackup() must return true. + ASSERT_TRUE(p->DoBackup(NULL)); // cold start: ratio=0.0 < 1.0, allow +} + +TEST_F(RateLimitedBackupPolicyTest, ColdStartAllowsBackup) { + brpc::RateLimitedBackupPolicyOptions opts; + opts.backup_request_ms = 10; + opts.max_backup_ratio = 0.1; + opts.update_interval_seconds = 1; + std::unique_ptr p( + brpc::CreateRateLimitedBackupPolicy(opts)); + ASSERT_TRUE(p != NULL); + ASSERT_TRUE(p->DoBackup(NULL)); +} + +// After the first backup fires (backup_count=1, total_count=0), once the +// update interval elapses the ratio is refreshed via the conservative path +// (total==0 → ratio=1.0), which exceeds max_backup_ratio < 1.0, so +// subsequent DoBackup() calls are suppressed until an RPC leg completes. +TEST_F(RateLimitedBackupPolicyTest, AfterColdStartBackupSuppressedUntilRpcCompletes) { + brpc::RateLimitedBackupPolicyOptions opts; + opts.backup_request_ms = 10; + opts.max_backup_ratio = 0.1; + opts.window_size_seconds = 1; + opts.update_interval_seconds = 1; + std::unique_ptr p( + brpc::CreateRateLimitedBackupPolicy(opts)); + ASSERT_TRUE(p != NULL); + // First call fires (cold start: total=0, backup=0 → ratio=0.0 → allow). + ASSERT_TRUE(p->DoBackup(NULL)); + // Wait for the update interval to elapse so the ratio refreshes. + // After refresh: total=0 but backup=1 → conservative path sets ratio=1.0, + // which is >= max_backup_ratio (0.1), so DoBackup() must return false. + bthread_usleep(1200000); // 1.2s > update_interval_seconds=1 + ASSERT_FALSE(p->DoBackup(NULL)); +} + +// After the ratio rises above the threshold, calling OnRPCEnd() many times +// drives total_count up relative to backup_count. Once the ratio refreshes +// below max_backup_ratio, DoBackup() should allow backups again. +TEST_F(RateLimitedBackupPolicyTest, OnRPCEndDrivesRatioDownAndReAllows) { + brpc::RateLimitedBackupPolicyOptions opts; + opts.backup_request_ms = 10; + opts.max_backup_ratio = 0.5; + opts.window_size_seconds = 1; + opts.update_interval_seconds = 1; + std::unique_ptr p( + brpc::CreateRateLimitedBackupPolicy(opts)); + ASSERT_TRUE(p != NULL); + // Fire many backup decisions so backup_count >> total_count, + // pushing the ratio above max_backup_ratio. + for (int i = 0; i < 20; ++i) { + p->DoBackup(NULL); + } + // Wait for update interval so the ratio is refreshed above threshold. + bthread_usleep(1200000); // 1.2s + ASSERT_FALSE(p->DoBackup(NULL)); + // Now complete many more RPCs than backups fired to bring ratio below 0.5. + // 20 backup decisions already counted; need total_count > 20/0.5 = 40. + for (int i = 0; i < 50; ++i) { + p->OnRPCEnd(NULL); + } + // Wait for the ratio cache to refresh. + bthread_usleep(1200000); // 1.2s + // Ratio is now ~20/50 = 0.4 < max_backup_ratio (0.5), so backup is re-allowed. + ASSERT_TRUE(p->DoBackup(NULL)); +} + +} //namespace diff --git a/test/brpc_circuit_breaker_unittest.cpp b/test/brpc_circuit_breaker_unittest.cpp new file mode 100644 index 0000000..607fede --- /dev/null +++ b/test/brpc_circuit_breaker_unittest.cpp @@ -0,0 +1,293 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: 2018/09/19 14:51:06 + +#include +#include +#include +#include "butil/macros.h" +#include "bthread/bthread.h" +#include "brpc/circuit_breaker.h" +#include "brpc/socket.h" +#include "brpc/server.h" +#include "echo.pb.h" + +namespace { +void initialize_random() { + srand(time(0)); +} + +const int kShortWindowSize = 500; +const int kLongWindowSize = 1000; +const int kShortWindowErrorPercent = 10; +const int kLongWindowErrorPercent = 5; +const int kMinIsolationDurationMs = 10; +const int kMaxIsolationDurationMs = 200; +const int kErrorCodeForFailed = 131; +const int kErrorCodeForSucc = 0; +const int kErrorCost = 1000; +const int kLatency = 1000; +const int kThreadNum = 3; +const int kHalfWindowSize = 0; +} // namespace + +namespace brpc { +DECLARE_int32(circuit_breaker_short_window_size); +DECLARE_int32(circuit_breaker_long_window_size); +DECLARE_int32(circuit_breaker_short_window_error_percent); +DECLARE_int32(circuit_breaker_long_window_error_percent); +DECLARE_int32(circuit_breaker_min_isolation_duration_ms); +DECLARE_int32(circuit_breaker_max_isolation_duration_ms); +DECLARE_int32(circuit_breaker_half_open_window_size); +} // namespace brpc + +int main(int argc, char* argv[]) { + brpc::FLAGS_circuit_breaker_short_window_size = kShortWindowSize; + brpc::FLAGS_circuit_breaker_long_window_size = kLongWindowSize; + brpc::FLAGS_circuit_breaker_short_window_error_percent = kShortWindowErrorPercent; + brpc::FLAGS_circuit_breaker_long_window_error_percent = kLongWindowErrorPercent; + brpc::FLAGS_circuit_breaker_min_isolation_duration_ms = kMinIsolationDurationMs; + brpc::FLAGS_circuit_breaker_max_isolation_duration_ms = kMaxIsolationDurationMs; + brpc::FLAGS_circuit_breaker_half_open_window_size = kHalfWindowSize; + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + return RUN_ALL_TESTS(); +} + +pthread_once_t initialize_random_control = PTHREAD_ONCE_INIT; + +struct FeedbackControl { + FeedbackControl(int req_num, int error_percent, + brpc::CircuitBreaker* circuit_breaker) + : _req_num(req_num) + , _error_percent(error_percent) + , _circuit_breaker(circuit_breaker) + , _healthy_cnt(0) + , _unhealthy_cnt(0) + , _healthy(true) {} + int _req_num; + int _error_percent; + brpc::CircuitBreaker* _circuit_breaker; + int _healthy_cnt; + int _unhealthy_cnt; + bool _healthy; +}; + +class CircuitBreakerTest : public ::testing::Test { +protected: + CircuitBreakerTest() { + pthread_once(&initialize_random_control, initialize_random); + }; + + virtual ~CircuitBreakerTest() {}; + virtual void SetUp() {}; + virtual void TearDown() {}; + + static void* feed_back_thread(void* data) { + FeedbackControl* fc = static_cast(data); + for (int i = 0; i < fc->_req_num; ++i) { + bool healthy = false; + if (rand() % 100 < fc->_error_percent) { + healthy = fc->_circuit_breaker->OnCallEnd(kErrorCodeForFailed, kErrorCost); + } else { + healthy = fc->_circuit_breaker->OnCallEnd(kErrorCodeForSucc, kLatency); + } + fc->_healthy = healthy; + if (healthy) { + ++fc->_healthy_cnt; + } else { + ++fc->_unhealthy_cnt; + } + } + return fc; + } + + void StartFeedbackThread(std::vector* thread_list, + std::vector>* fc_list, + int error_percent) { + thread_list->clear(); + fc_list->clear(); + for (int i = 0; i < kThreadNum; ++i) { + pthread_t tid = 0; + FeedbackControl* fc = + new FeedbackControl(2 * kLongWindowSize, error_percent, &_circuit_breaker); + fc_list->emplace_back(fc); + pthread_create(&tid, nullptr, feed_back_thread, fc); + thread_list->push_back(tid); + } + } + + brpc::CircuitBreaker _circuit_breaker; +}; + +TEST_F(CircuitBreakerTest, should_not_isolate) { + std::vector thread_list; + std::vector> fc_list; + StartFeedbackThread(&thread_list, &fc_list, 3); + for (int i = 0; i < kThreadNum; ++i) { + void* ret_data = nullptr; + ASSERT_EQ(pthread_join(thread_list[i], &ret_data), 0); + FeedbackControl* fc = static_cast(ret_data); + EXPECT_EQ(fc->_unhealthy_cnt, 0); + EXPECT_TRUE(fc->_healthy); + } +} + +TEST_F(CircuitBreakerTest, should_isolate) { + std::vector thread_list; + std::vector> fc_list; + StartFeedbackThread(&thread_list, &fc_list, 50); + for (int i = 0; i < kThreadNum; ++i) { + void* ret_data = nullptr; + ASSERT_EQ(pthread_join(thread_list[i], &ret_data), 0); + FeedbackControl* fc = static_cast(ret_data); + EXPECT_GT(fc->_unhealthy_cnt, 0); + EXPECT_FALSE(fc->_healthy); + } +} + +TEST_F(CircuitBreakerTest, should_isolate_with_half_open) { + std::vector thread_list; + std::vector> fc_list; + StartFeedbackThread(&thread_list, &fc_list, 100); + int total_failed = 0; + for (int i = 0; i < kThreadNum; ++i) { + void* ret_data = nullptr; + ASSERT_EQ(pthread_join(thread_list[i], &ret_data), 0); + FeedbackControl* fc = static_cast(ret_data); + EXPECT_GT(fc->_unhealthy_cnt, 0); + EXPECT_FALSE(fc->_healthy); + total_failed += fc->_unhealthy_cnt; + } + _circuit_breaker.Reset(); + + int total_failed1 = 0; + StartFeedbackThread(&thread_list, &fc_list, 100); + for (int i = 0; i < kThreadNum; ++i) { + void* ret_data = nullptr; + ASSERT_EQ(pthread_join(thread_list[i], &ret_data), 0); + FeedbackControl* fc = static_cast(ret_data); + EXPECT_FALSE(fc->_healthy); + EXPECT_LE(fc->_healthy_cnt, kShortWindowSize); + EXPECT_GT(fc->_unhealthy_cnt, 0); + total_failed1 += fc->_unhealthy_cnt; + } + + // Enable the half-open state. + // The first request cause _broken = true immediately. + brpc::FLAGS_circuit_breaker_half_open_window_size = 10; + _circuit_breaker.Reset(); + int total_failed2 = 0; + StartFeedbackThread(&thread_list, &fc_list, 100); + for (int i = 0; i < kThreadNum; ++i) { + void* ret_data = nullptr; + ASSERT_EQ(pthread_join(thread_list[i], &ret_data), 0); + FeedbackControl* fc = static_cast(ret_data); + EXPECT_FALSE(fc->_healthy); + EXPECT_LE(fc->_healthy_cnt, kShortWindowSize); + EXPECT_GT(fc->_unhealthy_cnt, 0); + total_failed2 += fc->_unhealthy_cnt; + } + brpc::FLAGS_circuit_breaker_half_open_window_size = 0; + + EXPECT_EQ(kLongWindowSize * 2 * kThreadNum - + kShortWindowSize * + brpc::FLAGS_circuit_breaker_short_window_error_percent / + 100, + total_failed); + + EXPECT_EQ(total_failed1, total_failed); + EXPECT_EQ(kLongWindowSize * 2 * kThreadNum, total_failed2); +} + +TEST_F(CircuitBreakerTest, isolation_duration_grow_and_reset) { + std::vector thread_list; + std::vector> fc_list; + StartFeedbackThread(&thread_list, &fc_list, 100); + for (int i = 0; i < kThreadNum; ++i) { + void* ret_data = nullptr; + ASSERT_EQ(pthread_join(thread_list[i], &ret_data), 0); + FeedbackControl* fc = static_cast(ret_data); + EXPECT_FALSE(fc->_healthy); + EXPECT_LE(fc->_healthy_cnt, kShortWindowSize); + EXPECT_GT(fc->_unhealthy_cnt, 0); + } + EXPECT_EQ(_circuit_breaker.isolation_duration_ms(), kMinIsolationDurationMs); + + _circuit_breaker.Reset(); + StartFeedbackThread(&thread_list, &fc_list, 100); + for (int i = 0; i < kThreadNum; ++i) { + void* ret_data = nullptr; + ASSERT_EQ(pthread_join(thread_list[i], &ret_data), 0); + FeedbackControl* fc = static_cast(ret_data); + EXPECT_FALSE(fc->_healthy); + EXPECT_LE(fc->_healthy_cnt, kShortWindowSize); + EXPECT_GT(fc->_unhealthy_cnt, 0); + } + EXPECT_EQ(_circuit_breaker.isolation_duration_ms(), kMinIsolationDurationMs * 2); + + _circuit_breaker.Reset(); + StartFeedbackThread(&thread_list, &fc_list, 100); + for (int i = 0; i < kThreadNum; ++i) { + void* ret_data = nullptr; + ASSERT_EQ(pthread_join(thread_list[i], &ret_data), 0); + FeedbackControl* fc = static_cast(ret_data); + EXPECT_FALSE(fc->_healthy); + EXPECT_LE(fc->_healthy_cnt, kShortWindowSize); + EXPECT_GT(fc->_unhealthy_cnt, 0); + } + EXPECT_EQ(_circuit_breaker.isolation_duration_ms(), kMinIsolationDurationMs * 4); + + _circuit_breaker.Reset(); + ::usleep((kMaxIsolationDurationMs + kMinIsolationDurationMs) * 1000); + StartFeedbackThread(&thread_list, &fc_list, 100); + for (int i = 0; i < kThreadNum; ++i) { + void* ret_data = nullptr; + ASSERT_EQ(pthread_join(thread_list[i], &ret_data), 0); + FeedbackControl* fc = static_cast(ret_data); + EXPECT_FALSE(fc->_healthy); + EXPECT_LE(fc->_healthy_cnt, kShortWindowSize); + EXPECT_GT(fc->_unhealthy_cnt, 0); + } + EXPECT_EQ(_circuit_breaker.isolation_duration_ms(), kMinIsolationDurationMs); + +} + +TEST_F(CircuitBreakerTest, maximum_isolation_duration) { + brpc::FLAGS_circuit_breaker_max_isolation_duration_ms = + brpc::FLAGS_circuit_breaker_min_isolation_duration_ms + 1; + ASSERT_LT(brpc::FLAGS_circuit_breaker_max_isolation_duration_ms, + 2 * brpc::FLAGS_circuit_breaker_min_isolation_duration_ms); + std::vector thread_list; + std::vector> fc_list; + + _circuit_breaker.Reset(); + StartFeedbackThread(&thread_list, &fc_list, 100); + for (int i = 0; i < kThreadNum; ++i) { + void* ret_data = nullptr; + ASSERT_EQ(pthread_join(thread_list[i], &ret_data), 0); + FeedbackControl* fc = static_cast(ret_data); + EXPECT_FALSE(fc->_healthy); + EXPECT_LE(fc->_healthy_cnt, kShortWindowSize); + EXPECT_GT(fc->_unhealthy_cnt, 0); + } + EXPECT_EQ(_circuit_breaker.isolation_duration_ms(), + brpc::FLAGS_circuit_breaker_max_isolation_duration_ms); +} diff --git a/test/brpc_controller_unittest.cpp b/test/brpc_controller_unittest.cpp new file mode 100644 index 0000000..3f410a2 --- /dev/null +++ b/test/brpc_controller_unittest.cpp @@ -0,0 +1,138 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: Sun Jul 13 15:04:18 CST 2014 + +#include +#include +#include "butil/logging.h" +#include "butil/time.h" +#include "butil/macros.h" +#include "brpc/socket.h" +#include "brpc/server.h" +#include "brpc/channel.h" +#include "brpc/controller.h" + +class ControllerTest : public ::testing::Test{ +protected: + ControllerTest() {}; + virtual ~ControllerTest(){}; + virtual void SetUp() {}; + virtual void TearDown() {}; +}; + +void MyCancelCallback(bool* cancel_flag) { + *cancel_flag = true; +} + +TEST_F(ControllerTest, notify_on_failed) { + brpc::SocketId id = 0; + ASSERT_EQ(0, brpc::Socket::Create(brpc::SocketOptions(), &id)); + + brpc::Controller cntl; + cntl._current_call.peer_id = id; + ASSERT_FALSE(cntl.IsCanceled()); + + bool cancel = false; + cntl.NotifyOnCancel(brpc::NewCallback(&MyCancelCallback, &cancel)); + // Trigger callback + brpc::Socket::SetFailed(id); + usleep(20000); // sleep a while to wait for the canceling which will be + // happening in another thread. + ASSERT_TRUE(cancel); + ASSERT_TRUE(cntl.IsCanceled()); +} + +TEST_F(ControllerTest, notify_on_destruction) { + brpc::SocketId id = 0; + ASSERT_EQ(0, brpc::Socket::Create(brpc::SocketOptions(), &id)); + + brpc::Controller* cntl = new brpc::Controller; + cntl->_current_call.peer_id = id; + ASSERT_FALSE(cntl->IsCanceled()); + + bool cancel = false; + cntl->NotifyOnCancel(brpc::NewCallback(&MyCancelCallback, &cancel)); + // Trigger callback + delete cntl; + ASSERT_TRUE(cancel); +} + +#if ! BRPC_WITH_GLOG + +static bool endsWith(const std::string& s1, const butil::StringPiece& s2) { + if (s1.size() < s2.size()) { + return false; + } + return memcmp(s1.data() + s1.size() - s2.size(), s2.data(), s2.size()) == 0; +} +static bool startsWith(const std::string& s1, const butil::StringPiece& s2) { + if (s1.size() < s2.size()) { + return false; + } + return memcmp(s1.data(), s2.data(), s2.size()) == 0; +} + +DECLARE_bool(log_as_json); + +TEST_F(ControllerTest, SessionKV) { + FLAGS_log_as_json = false; + logging::StringSink sink1; + auto oldSink = logging::SetLogSink(&sink1); + { + brpc::Controller cntl; + cntl.set_log_id(123); // not working now + // set + cntl.SessionKV().Set("Apple", 1234567); + cntl.SessionKV().Set("Baidu", "Building"); + // get + auto v1 = cntl.SessionKV().Get("Apple"); + ASSERT_TRUE(v1); + ASSERT_EQ("1234567", *v1); + auto v2 = cntl.SessionKV().Get("Baidu"); + ASSERT_TRUE(v2); + ASSERT_EQ("Building", *v2); + + // override + cntl.SessionKV().Set("Baidu", "NewStuff"); + v2 = cntl.SessionKV().Get("Baidu"); + ASSERT_TRUE(v2); + ASSERT_EQ("NewStuff", *v2); + + cntl.SessionKV().Set("Cisco", 33.33); + + CLOGW(&cntl) << "My WARNING Log"; + ASSERT_TRUE(endsWith(sink1, "] My WARNING Log")) << sink1; + ASSERT_TRUE(startsWith(sink1, "W")) << sink1; + sink1.clear(); + + cntl.set_request_id("abcdEFG-456"); + CLOGE(&cntl) << "My ERROR Log"; + ASSERT_TRUE(endsWith(sink1, "] @rid=abcdEFG-456 My ERROR Log")) << sink1; + ASSERT_TRUE(startsWith(sink1, "E")) << sink1; + sink1.clear(); + + FLAGS_log_as_json = true; + } + ASSERT_TRUE(endsWith(sink1, R"(,"@rid":"abcdEFG-456","M":"Session ends.","Cisco":"33.330000","Apple":"1234567","Baidu":"NewStuff"})")) << sink1; + ASSERT_TRUE(startsWith(sink1, R"({"L":"I",)")) << sink1; + + logging::SetLogSink(oldSink); +} +#endif diff --git a/test/brpc_coroutine_unittest.cpp b/test/brpc_coroutine_unittest.cpp new file mode 100644 index 0000000..1df2c3a --- /dev/null +++ b/test/brpc_coroutine_unittest.cpp @@ -0,0 +1,221 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "brpc/server.h" +#include "brpc/channel.h" +#include "brpc/coroutine.h" +#include "echo.pb.h" + +int main(int argc, char* argv[]) { +#ifdef BRPC_ENABLE_COROUTINE + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + return RUN_ALL_TESTS(); +#else + printf("bRPC coroutine is not enabled, please add -std=c++20 to compile options\n"); + return 0; +#endif +} + +#ifdef BRPC_ENABLE_COROUTINE + +using brpc::experimental::Awaitable; +using brpc::experimental::AwaitableDone; +using brpc::experimental::Coroutine; + +class Trace { +public: + Trace(const std::string& name) { + _name = name; + LOG(INFO) << "enter " << name; + } + ~Trace() { + LOG(INFO) << "exit " << _name; + } +private: + std::string _name; +}; + +class EchoServiceImpl : public test::EchoService { +public: + EchoServiceImpl() {} + virtual ~EchoServiceImpl() {} + virtual void Echo(google::protobuf::RpcController* cntl_base, + const test::EchoRequest* request, + test::EchoResponse* response, + google::protobuf::Closure* done) { + // brpc::Controller* cntl = (brpc::Controller*)cntl_base; + // brpc::ClosureGuard done_guard(done); + // response->set_message(request->message()); + + // Create a detached coroutine, so the current bthread will return at once. + Coroutine(EchoAsync(request, response, done), true); + } + + Awaitable EchoAsync(const test::EchoRequest* request, + test::EchoResponse* response, + google::protobuf::Closure* done) { + Trace t("EchoAsync"); + // This is important to test RAII object's destruction after coroutine finished + brpc::ClosureGuard done_guard(done); + if (request->has_sleep_us()) { + LOG(INFO) << "sleep " << request->sleep_us() << " us at server side"; + co_await Coroutine::usleep(request->sleep_us()); + } + response->set_message(request->message()); + } +}; + +class CoroutineTest : public ::testing::Test{ +protected: + CoroutineTest() {}; + virtual ~CoroutineTest(){}; + virtual void SetUp() {}; + virtual void TearDown() {}; +}; + + +static int delay_us = 0; + +Awaitable inplace_func(const std::string& input) { + Trace t("inplace_func"); + co_return input; +} + +Awaitable inplace_func2() { + Trace t("inplace_func2"); + co_await inplace_func("123"); + co_return 0.5; +} + +Awaitable sleep_func() { + Trace t("sleep_func"); + int64_t s = butil::monotonic_time_us(); + auto aw = Coroutine::usleep(1000); + usleep(delay_us); + co_await aw; + int cost = butil::monotonic_time_us() - s; + EXPECT_GE(cost, 1000); + LOG(INFO) << "after usleep:" << cost; + co_return 123; +} + +Awaitable exception_func() { + Trace t("exception_func"); + throw std::string("error"); +} + +Awaitable func(brpc::Channel& channel, int* out) { + Trace t("func"); + test::EchoService_Stub stub(&channel); + test::EchoRequest request; + request.set_message("hello world"); + test::EchoResponse response; + brpc::Controller cntl; + + LOG(INFO) << "before start coroutine"; + Coroutine coro(sleep_func()); + usleep(delay_us); + LOG(INFO) << "before wait coroutine"; + int ret = co_await coro.awaitable(); + EXPECT_EQ(ret, 123); + LOG(INFO) << "after wait coroutine, ret:" << ret; + + auto str = co_await inplace_func("hello"); + EXPECT_EQ("hello", str); + + float num = 0.0; + try { + num = co_await exception_func(); + } catch(std::string str) { + EXPECT_EQ("error", str); + num = 1.0; + } + EXPECT_EQ(1.0, num); + + AwaitableDone done; + LOG(INFO) << "start echo"; + stub.Echo(&cntl, &request, &response, &done); + LOG(INFO) << "after echo"; + usleep(delay_us); + co_await done.awaitable(); + LOG(INFO) << "after wait"; + EXPECT_FALSE(cntl.Failed()) << cntl.ErrorText(); + EXPECT_EQ("hello world", response.message()); + + cntl.Reset(); + request.set_sleep_us(2000); + AwaitableDone done2; + LOG(INFO) << "start echo2"; + int64_t s = butil::monotonic_time_us(); + stub.Echo(&cntl, &request, &response, &done2); + LOG(INFO) << "after echo2"; + co_await done2.awaitable(); + int cost = butil::monotonic_time_us() - s; + LOG(INFO) << "after wait2"; + EXPECT_GE(cost, 2000); + EXPECT_FALSE(cntl.Failed()) << cntl.ErrorText(); + EXPECT_EQ("hello world", response.message()); + + *out = 456; +} + +TEST_F(CoroutineTest, coroutine) { + butil::EndPoint ep; + ASSERT_EQ(0, str2endpoint("127.0.0.1:8613", &ep)); + + brpc::Server server; + EchoServiceImpl service; + server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE); + ASSERT_EQ(0, server.Start(ep, NULL)); + + brpc::Channel channel; + brpc::ChannelOptions options; + ASSERT_EQ(0, channel.Init(ep, &options)); + + int out = 0; + Coroutine coro(func(channel, &out)); + coro.join(); + ASSERT_EQ(456, out); + + out = 0; + delay_us = 10000; + Coroutine coro2(func(channel, &out)); + coro2.join(); + ASSERT_EQ(456, out); + delay_us = 0; + + Coroutine coro3(inplace_func2()); + double d = coro3.join(); + ASSERT_EQ(0.5, d); + + Coroutine coro4(inplace_func("abc")); + coro4.join(); + + Coroutine coro5(sleep_func()); + coro5.join(); + + Coroutine coro6(inplace_func2(), true); + Coroutine coro7(inplace_func("abc"), true); + Coroutine coro8(sleep_func(), true); + usleep(10000); // wait sleep_func() to complete + + LOG(INFO) << "test case finished"; +} + +#endif // BRPC_ENABLE_COROUTINE \ No newline at end of file diff --git a/test/brpc_couchbase_unittest.cpp b/test/brpc_couchbase_unittest.cpp new file mode 100644 index 0000000..e2f2fc0 --- /dev/null +++ b/test/brpc_couchbase_unittest.cpp @@ -0,0 +1,112 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include +#include +#include +#include + +namespace brpc { +DECLARE_int32(idle_timeout_second); +DECLARE_uint64(max_body_size); +} + +int main(int argc, char* argv[]) { + brpc::FLAGS_idle_timeout_second = 0; + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + +namespace { + +// Unit Tests - No Server Required +class CouchbaseUnitTest : public testing::Test {}; + +TEST_F(CouchbaseUnitTest, RejectOversizedResponseBeforeBufferingBody) { + GFLAGS_NAMESPACE::FlagSaver flag_saver; + brpc::FLAGS_max_body_size = 1024; + + brpc::SocketId id; + brpc::SocketOptions options; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + brpc::SocketUniquePtr socket; + ASSERT_EQ(0, brpc::Socket::Address(id, &socket)); + + brpc::policy::CouchbaseResponseHeader couchbase_header = {}; + couchbase_header.magic = brpc::policy::CB_MAGIC_RESPONSE; + couchbase_header.total_body_length = butil::HostToNet32(1025); + butil::IOBuf couchbase_buf; + couchbase_buf.append(&couchbase_header, sizeof(couchbase_header)); + EXPECT_EQ(brpc::PARSE_ERROR_TOO_BIG_DATA, + brpc::policy::ParseCouchbaseMessage( + &couchbase_buf, socket.get(), false, NULL).error()); + +} + +TEST_F(CouchbaseUnitTest, RequestBuilders) { + brpc::CouchbaseOperations::CouchbaseRequest req; + req.Clear(); + req.helloRequest(); + req.authenticateRequest("user", "pass"); + req.selectBucketRequest("bucket"); + req.addRequest("key", "value", 0, 0, 0); + req.getRequest("key"); + req.upsertRequest("key", "value", 0, 0, 0); + req.deleteRequest("key"); + EXPECT_TRUE(true); +} + +TEST_F(CouchbaseUnitTest, ResultStruct) { + brpc::CouchbaseOperations::Result result; + + result.success = true; + result.error_message = "Test"; + result.value = R"({"test": "data"})"; + result.status_code = 0x01; + + EXPECT_TRUE(result.success); + EXPECT_EQ("Test", result.error_message); + EXPECT_EQ(R"({"test": "data"})", result.value); + EXPECT_EQ(0x01, result.status_code); + + result.success = false; + result.error_message = ""; + result.value = ""; + result.status_code = 0x00; + + EXPECT_FALSE(result.success); + EXPECT_TRUE(result.error_message.empty()); + EXPECT_TRUE(result.value.empty()); + EXPECT_EQ(0x00, result.status_code); +} + +TEST_F(CouchbaseUnitTest, EdgeCases) { + brpc::CouchbaseOperations::CouchbaseRequest req; + req.addRequest("", "value", 0, 0, 0); + req.addRequest("key", "", 0, 0, 0); + req.addRequest(std::string(1000, 'x'), "val", 0, 0, 0); + req.addRequest("key", std::string(10000, 'x'), 0, 0, 0); + req.addRequest("test::special::!!!","val", 0, 0, 0); + req.addRequest("key", R"({"unicode":"123"})", 0, 0, 0); + EXPECT_TRUE(true); +} + +} // namespace diff --git a/test/brpc_esp_protocol_unittest.cpp b/test/brpc_esp_protocol_unittest.cpp new file mode 100644 index 0000000..d4cab7c --- /dev/null +++ b/test/brpc_esp_protocol_unittest.cpp @@ -0,0 +1,158 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: Sun Jul 13 15:04:18 CST 2014 + +#include +#include +#include +#include +#include +#include +#include "butil/time.h" +#include "butil/macros.h" +#include "brpc/socket.h" +#include "brpc/policy/most_common_message.h" +#include "brpc/controller.h" + +#include "brpc/esp_message.h" +#include "brpc/policy/esp_protocol.h" +#include "brpc/policy/esp_authenticator.h" + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + return RUN_ALL_TESTS(); +} + +namespace { + +static const std::string EXP_REQUEST = "hello"; +static const std::string EXP_RESPONSE = "world"; + +static const int STUB = 2; +static const int MSG_ID = 123456; +static const int MSG = 0; +static const int WRONG_MSG = 1; + +class EspTest : public ::testing::Test{ +protected: + EspTest() { + EXPECT_EQ(0, pipe(_pipe_fds)); + brpc::SocketId id; + brpc::SocketOptions options; + options.fd = _pipe_fds[1]; + EXPECT_EQ(0, brpc::Socket::Create(options, &id)); + EXPECT_EQ(0, brpc::Socket::Address(id, &_socket)); + }; + + virtual ~EspTest() {}; + virtual void SetUp() {}; + virtual void TearDown() {}; + + void WriteResponse(brpc::Controller& cntl, int msg) { + brpc::EspMessage req; + + req.head.to.stub = STUB; + req.head.msg = msg; + req.head.msg_id = MSG_ID; + req.body.append(EXP_RESPONSE); + + butil::IOBuf req_buf; + brpc::policy::SerializeEspRequest(&req_buf, &cntl, &req); + + butil::IOBuf packet_buf; + brpc::policy::PackEspRequest(&packet_buf, NULL, cntl.call_id().value, NULL, &cntl, req_buf, NULL); + + packet_buf.cut_into_file_descriptor(_pipe_fds[1], packet_buf.size()); + } + + int _pipe_fds[2]; + brpc::SocketUniquePtr _socket; +}; + +TEST_F(EspTest, complete_flow) { + brpc::EspMessage req; + brpc::EspMessage res; + + req.head.to.stub = STUB; + req.head.msg = MSG; + req.head.msg_id = MSG_ID; + req.body.append(EXP_REQUEST); + + butil::IOBuf req_buf; + brpc::Controller cntl; + cntl._response = &res; + ASSERT_EQ(0, brpc::Socket::Address(_socket->id(), &cntl._current_call.sending_sock)); + + brpc::policy::SerializeEspRequest(&req_buf, &cntl, &req); + ASSERT_FALSE(cntl.Failed()); + ASSERT_EQ(sizeof(req.head) + req.body.size(), req_buf.size()); + + const brpc::Authenticator* auth = brpc::policy::global_esp_authenticator(); + butil::IOBuf packet_buf; + brpc::policy::PackEspRequest(&packet_buf, NULL, cntl.call_id().value, NULL, &cntl, req_buf, auth); + + std::string auth_str; + auth->GenerateCredential(&auth_str); + + ASSERT_FALSE(cntl.Failed()); + ASSERT_EQ(req_buf.size() + auth_str.size(), packet_buf.size()); + + WriteResponse(cntl, MSG); + + butil::IOPortal response_buf; + response_buf.append_from_file_descriptor(_pipe_fds[0], 1024); + + brpc::ParseResult res_pr = + brpc::policy::ParseEspMessage(&response_buf, NULL, false, NULL); + ASSERT_EQ(brpc::PARSE_OK, res_pr.error()); + + brpc::InputMessageBase* res_msg = res_pr.message(); + _socket->ReAddress(&res_msg->_socket); + + brpc::policy::ProcessEspResponse(res_msg); + + ASSERT_FALSE(cntl.Failed()); + ASSERT_EQ(EXP_RESPONSE, res.body.to_string()); +} + +TEST_F(EspTest, wrong_response_head) { + brpc::EspMessage res; + brpc::Controller cntl; + cntl._response = &res; + ASSERT_EQ(0, brpc::Socket::Address(_socket->id(), &cntl._current_call.sending_sock)); + + WriteResponse(cntl, WRONG_MSG); + + butil::IOPortal response_buf; + response_buf.append_from_file_descriptor(_pipe_fds[0], 1024); + + brpc::ParseResult res_pr = + brpc::policy::ParseEspMessage(&response_buf, NULL, false, NULL); + ASSERT_EQ(brpc::PARSE_OK, res_pr.error()); + + brpc::InputMessageBase* res_msg = res_pr.message(); + _socket->ReAddress(&res_msg->_socket); + + brpc::policy::ProcessEspResponse(res_msg); + + ASSERT_TRUE(cntl.Failed()); +} +} //namespace diff --git a/test/brpc_event_dispatcher_unittest.cpp b/test/brpc_event_dispatcher_unittest.cpp new file mode 100644 index 0000000..dcca305 --- /dev/null +++ b/test/brpc_event_dispatcher_unittest.cpp @@ -0,0 +1,534 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: Sun Jul 13 15:04:18 CST 2014 + +#include +#include +#include +#include +#include "gperftools_helper.h" +#include "butil/time.h" +#include "butil/macros.h" +#include "butil/fd_utility.h" +#include "butil/memory/scope_guard.h" +#include "bthread/bthread.h" +#include "brpc/event_dispatcher.h" +#include "brpc/socket.h" +#include "brpc/details/has_epollrdhup.h" +#include "brpc/versioned_ref_with_id.h" + +class EventDispatcherTest : public ::testing::Test{ +protected: + EventDispatcherTest() = default; + ~EventDispatcherTest() override = default; + void SetUp() override {} + void TearDown() override {} +}; + +TEST_F(EventDispatcherTest, has_epollrdhup) { + LOG(INFO) << brpc::has_epollrdhup; +} + +TEST_F(EventDispatcherTest, versioned_ref) { + butil::atomic versioned_ref(2); + versioned_ref.fetch_add(brpc::MakeVRef(0, -1), + butil::memory_order_release); + ASSERT_EQ(brpc::MakeVRef(1, 1), versioned_ref); +} + +struct UserData; + +UserData* g_user_data = NULL; + +struct UserData : public brpc::VersionedRefWithId { + explicit UserData(Forbidden f) + : brpc::VersionedRefWithId(f) + , count(0) + , _additional_ref_released(false) {} + + int OnCreated() { + count.store(1, butil::memory_order_relaxed); + _additional_ref_released = false; + g_user_data = this; + return 0; + } + + void BeforeRecycled() { + count.store(0, butil::memory_order_relaxed); + g_user_data = NULL; + } + + void BeforeAdditionalRefReleased() { + _additional_ref_released = true; + } + + void OnFailed() { + count.fetch_sub(1, butil::memory_order_relaxed); + } + + void AfterRevived() { + count.fetch_add(1, butil::memory_order_relaxed); + _additional_ref_released = false; + } + + butil::atomic count; + bool _additional_ref_released; +}; + +// Unique identifier of a UserData. +// Users shall store UserDataId instead of UserData and call UserData::Address() +// to convert the identifier to an unique_ptr at each access. Whenever a +// unique_ptr is not destructed, the enclosed UserData will not be recycled. +typedef brpc::VRefId UserDataId; + +const brpc::VRefId INVALID_EVENT_DATA_ID = brpc::INVALID_VREF_ID; + +typedef brpc::VersionedRefWithIdUniquePtr UserDataUniquePtr; + +volatile bool vref_thread_stop = false; +butil::atomic g_count(1); + +void TestVRef(UserDataId id) { + UserDataUniquePtr ptr; + ASSERT_EQ(0, UserData::Address(id, &ptr)); + ptr->count.fetch_add(1, butil::memory_order_relaxed); + g_count.fetch_add(1, butil::memory_order_relaxed); +} + +void* VRefThread(void* arg) { + auto id = (UserDataId)arg; + while (!vref_thread_stop) { + TestVRef(id); + } + return NULL; +} + +TEST_F(EventDispatcherTest, versioned_ref_with_id) { + UserDataId id = INVALID_EVENT_DATA_ID; + ASSERT_EQ(0, UserData::Create(&id)); + ASSERT_NE(INVALID_EVENT_DATA_ID, id); + UserDataUniquePtr ptr; + ASSERT_EQ(0, UserData::Address(id, &ptr)); + ASSERT_EQ(2, ptr->nref()); + ASSERT_FALSE(ptr->Failed()); + ASSERT_EQ(1, ptr->count); + ASSERT_EQ(g_user_data, ptr.get()); + { + UserDataUniquePtr temp_ptr; + ASSERT_EQ(0, UserData::Address(id, &temp_ptr)); + ASSERT_EQ(ptr, temp_ptr); + ASSERT_EQ(3, ptr->nref()); + } + + const size_t thread_num = 8; + pthread_t tid[thread_num]; + for (auto& i : tid) { + ASSERT_EQ(0, pthread_create(&i, NULL, VRefThread, (void*)id)); + } + + sleep(2); + + vref_thread_stop = true; + for (const auto i : tid) { + pthread_join(i, NULL); + } + + ASSERT_EQ(2, ptr->nref()); + ASSERT_EQ(g_count, ptr->count); + ASSERT_EQ(0, ptr->SetFailed()); + ASSERT_TRUE(ptr->Failed()); + ASSERT_EQ(g_count - 1, ptr->count); + // Additional reference has been released. + ASSERT_TRUE(ptr->_additional_ref_released); + ASSERT_EQ(1, ptr->nref()); + { + UserDataUniquePtr temp_ptr; + ASSERT_EQ(1, UserData::AddressFailedAsWell(id, &temp_ptr)); + ASSERT_EQ(ptr, temp_ptr); + ASSERT_EQ(2, ptr->nref()); + } + ptr->Revive(1); + ASSERT_EQ(2, ptr->nref()); + ASSERT_EQ(g_count, ptr->count); + ASSERT_FALSE(ptr->_additional_ref_released); + ASSERT_EQ(0, UserData::SetFailedById(id)); + ASSERT_EQ(g_count - 1, ptr->count); + ptr.reset(); + ASSERT_EQ(nullptr, ptr); + ASSERT_EQ(nullptr, g_user_data); +} + +std::vector err_fd; +pthread_mutex_t err_fd_mutex = PTHREAD_MUTEX_INITIALIZER; + +std::vector rel_fd; +pthread_mutex_t rel_fd_mutex = PTHREAD_MUTEX_INITIALIZER; + +volatile bool client_stop = false; + +struct BAIDU_CACHELINE_ALIGNMENT ClientMeta { + int fd; + size_t times; + size_t bytes; +}; + +struct BAIDU_CACHELINE_ALIGNMENT SocketExtra : public brpc::SocketUser { + char* buf; + size_t buf_cap; + size_t bytes; + size_t times; + + SocketExtra() { + buf_cap = 32768; + buf = (char*)malloc(buf_cap); + bytes = 0; + times = 0; + } + + ~SocketExtra() { + free(buf); + } + + void BeforeRecycle(brpc::Socket* m) override { + pthread_mutex_lock(&rel_fd_mutex); + rel_fd.push_back(m->fd()); + pthread_mutex_unlock(&rel_fd_mutex); + delete this; + } + + static int OnEdgeTriggeredEventOnce(brpc::Socket* m) { + SocketExtra* e = static_cast(m->user()); + // Read all data. + do { + ssize_t n = read(m->fd(), e->buf, e->buf_cap); + if (n == 0 +#ifdef BRPC_SOCKET_HAS_EOF + || m->_eof +#endif + ) { + pthread_mutex_lock(&err_fd_mutex); + err_fd.push_back(m->fd()); + pthread_mutex_unlock(&err_fd_mutex); + LOG(WARNING) << "Another end closed fd=" << m->fd(); + return -1; + } else if (n > 0) { + e->bytes += n; + ++e->times; +#ifdef BRPC_SOCKET_HAS_EOF + if ((size_t)n < e->buf_cap && brpc::has_epollrdhup) { + break; + } +#endif + } else { + if (errno == EAGAIN) { + break; + } else if (errno == EINTR) { + continue; + } else { + PLOG(WARNING) << "Fail to read fd=" << m->fd(); + return -1; + } + } + } while (1); + return 0; + } + + static void OnEdgeTriggeredEvents(brpc::Socket* m) { + int progress = brpc::Socket::PROGRESS_INIT; + do { + if (OnEdgeTriggeredEventOnce(m) != 0) { + m->SetFailed(); + return; + } + } while (m->MoreReadEvents(&progress)); + } +}; + +void* client_thread(void* arg) { + ClientMeta* m = (ClientMeta*)arg; + size_t offset = 0; + m->times = 0; + m->bytes = 0; + const size_t buf_cap = 32768; + char* buf = (char*)malloc(buf_cap); + for (size_t i = 0; i < buf_cap/8; ++i) { + ((uint64_t*)buf)[i] = i; + } + while (!client_stop) { + ssize_t n; + if (offset == 0) { + n = write(m->fd, buf, buf_cap); + } else { + iovec v[2]; + v[0].iov_base = buf + offset; + v[0].iov_len = buf_cap - offset; + v[1].iov_base = buf; + v[1].iov_len = offset; + n = writev(m->fd, v, 2); + } + if (n < 0) { + if (errno != EINTR) { + PLOG(WARNING) << "Fail to write fd=" << m->fd; + break; + } + } else { + ++m->times; + m->bytes += n; + offset += n; + if (offset >= buf_cap) { + offset -= buf_cap; + } + } + } + free(buf); + EXPECT_EQ(0, close(m->fd)); + return NULL; +} + +inline uint32_t fmix32 ( uint32_t h ) { + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + return h; +} + +TEST_F(EventDispatcherTest, dispatch_tasks) { +#ifdef BUTIL_RESOURCE_POOL_NEED_FREE_ITEM_NUM + const butil::ResourcePoolInfo old_info = + butil::describe_resources(); +#endif + + client_stop = false; + + const size_t NCLIENT = 16; + + int fds[2 * NCLIENT]; + pthread_t cth[NCLIENT]; + ClientMeta* cm[NCLIENT]; + SocketExtra* sm[NCLIENT]; + brpc::SocketId socket_ids[NCLIENT]; + + for (size_t i = 0; i < NCLIENT; ++i) { + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds + 2 * i)); + sm[i] = new SocketExtra; + + const int fd = fds[i * 2]; + butil::make_non_blocking(fd); + brpc::SocketId socket_id; + brpc::SocketOptions options; + options.fd = fd; + options.user = sm[i]; + options.on_edge_triggered_events = SocketExtra::OnEdgeTriggeredEvents; + + ASSERT_EQ(0, brpc::Socket::Create(options, &socket_id)); + socket_ids[i] = socket_id; + cm[i] = new ClientMeta; + cm[i]->fd = fds[i * 2 + 1]; + cm[i]->times = 0; + cm[i]->bytes = 0; + ASSERT_EQ(0, pthread_create(&cth[i], NULL, client_thread, cm[i])); + } + + LOG(INFO) << "Begin to profile... (5 seconds)"; + ProfilerStart("event_dispatcher.prof"); + butil::Timer tm; + tm.start(); + sleep(5); + tm.stop(); + ProfilerStop(); + LOG(INFO) << "End profiling"; + + size_t client_bytes = 0; + size_t server_bytes = 0; + for (size_t i = 0; i < NCLIENT; ++i) { + client_bytes += cm[i]->bytes; + server_bytes += sm[i]->bytes; + } + LOG(INFO) << "client_tp=" << client_bytes / (double)tm.u_elapsed() + << "MB/s server_tp=" << server_bytes / (double)tm.u_elapsed() + << "MB/s"; + + client_stop = true; + for (size_t i = 0; i < NCLIENT; ++i) { + pthread_join(cth[i], NULL); + } + sleep(1); + + std::vector copy1, copy2; + pthread_mutex_lock(&err_fd_mutex); + copy1.swap(err_fd); + pthread_mutex_unlock(&err_fd_mutex); + pthread_mutex_lock(&rel_fd_mutex); + copy2.swap(rel_fd); + pthread_mutex_unlock(&rel_fd_mutex); + + std::sort(copy1.begin(), copy1.end()); + std::sort(copy2.begin(), copy2.end()); + ASSERT_EQ(copy1.size(), copy2.size()); + for (size_t i = 0; i < copy1.size(); ++i) { + ASSERT_EQ(copy1[i], copy2[i]) << i; + } + ASSERT_EQ(NCLIENT, copy1.size()); + const butil::ResourcePoolInfo info + = butil::describe_resources(); + LOG(INFO) << info; +#ifdef BUTIL_RESOURCE_POOL_NEED_FREE_ITEM_NUM + ASSERT_EQ(NCLIENT, info.free_item_num - old_info.free_item_num); +#endif + + // Release sockets (SocketExtra::BeforeRecycle deletes the user) and the + // per-client metadata to avoid leaking them. + for (size_t i = 0; i < NCLIENT; ++i) { + brpc::SocketUniquePtr s; + if (brpc::Socket::Address(socket_ids[i], &s) == 0) { + s->SetFailed(); + } + delete cm[i]; + } +} + +// Unique identifier of a EventPipe. +// Users shall store EventFDId instead of EventPipe and call EventPipe::Address() +// to convert the identifier to an unique_ptr at each access. Whenever a +// unique_ptr is not destructed, the enclosed EventPipe will not be recycled. +typedef brpc::VRefId EventPipeId; + +const brpc::VRefId INVALID_EVENT_PIPE_ID = brpc::INVALID_VREF_ID; + +class EventPipe; +typedef brpc::VersionedRefWithIdUniquePtr EventPipeUniquePtr; + + +class EventPipe : public brpc::VersionedRefWithId { +public: + explicit EventPipe(Forbidden f) + : brpc::VersionedRefWithId(f) + , _pipe_fds{-1, -1} + , _input_event_count(0) + {} + + int Notify() { + char c = 0; + if (write(_pipe_fds[1], &c, 1) != 1) { + PLOG(ERROR) << "Fail to write to _pipe_fds[1]"; + return -1; + } + return 0; + } + +private: +friend class VersionedRefWithId; +friend class brpc::IOEvent; + + int OnCreated() { + if (pipe(_pipe_fds)) { + PLOG(FATAL) << "Fail to create _pipe_fds"; + return -1; + } + if (_io_event.Init((void*)id()) != 0) { + LOG(ERROR) << "Fail to init IOEvent"; + return -1; + } + _io_event.set_bthread_tag(bthread_self_tag()); + if (_io_event.AddConsumer(_pipe_fds[0]) != 0) { + PLOG(ERROR) << "Fail to add SocketId=" << id() + << " into EventDispatcher"; + return -1; + } + + + _input_event_count = 0; + return 0; + } + + void BeforeRecycled() { + brpc::GetGlobalEventDispatcher(_pipe_fds[0], bthread_self_tag()) + .RemoveConsumer(_pipe_fds[0]); + _io_event.Reset(); + if (_pipe_fds[0] >= 0) { + close(_pipe_fds[0]); + } + if (_pipe_fds[1] >= 0) { + close(_pipe_fds[1]); + } + } + + static int OnInputEvent(void* user_data, uint32_t, + const bthread_attr_t&) { + auto id = reinterpret_cast(user_data); + EventPipeUniquePtr ptr; + if (EventPipe::Address(id, &ptr) != 0) { + LOG(WARNING) << "Fail to address EventPipe"; + return -1; + } + + char buf[1024]; + ssize_t nr = read(ptr->_pipe_fds[0], &buf, arraysize(buf)); + if (nr <= 0) { + if (errno == EAGAIN) { + return 0; + } else { + PLOG(WARNING) << "Fail to read from _pipe_fds[0]"; + ptr->SetFailed(); + return -1; + } + } + + ptr->_input_event_count += nr; + return 0; + } + + static int OnOutputEvent(void*, uint32_t, + const bthread_attr_t&) { + EXPECT_TRUE(false) << "Should not be called"; + return 0; + } + + brpc::IOEvent _io_event; + int _pipe_fds[2]; + + size_t _input_event_count; +}; + +TEST_F(EventDispatcherTest, customize_dispatch_task) { + EventPipeId id = INVALID_EVENT_PIPE_ID; + ASSERT_EQ(0, EventPipe::Create(&id)); + ASSERT_NE(INVALID_EVENT_PIPE_ID, id); + EventPipeUniquePtr ptr; + ASSERT_EQ(0, EventPipe::Address(id, &ptr)); + ASSERT_EQ(2, ptr->nref()); + ASSERT_FALSE(ptr->Failed()); + + ASSERT_EQ((size_t)0, ptr->_input_event_count); + const size_t N = 10000; + for (size_t i = 0; i < N; ++i) { + ASSERT_EQ(0, ptr->Notify()); + } + usleep(1000 * 50); + ASSERT_EQ(N, ptr->_input_event_count); + + ASSERT_EQ(0, ptr->SetFailed()); + ASSERT_TRUE(ptr->Failed()); + ptr.reset(); + ASSERT_EQ(nullptr, ptr); + ASSERT_NE(0, EventPipe::Address(id, &ptr)); +} diff --git a/test/brpc_extension_unittest.cpp b/test/brpc_extension_unittest.cpp new file mode 100644 index 0000000..eea238e --- /dev/null +++ b/test/brpc_extension_unittest.cpp @@ -0,0 +1,72 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: Sun Jul 13 15:04:18 CST 2014 + +#include +#include +#include +#include +#include "butil/time.h" +#include "butil/macros.h" +#include "brpc/extension.h" + +class ExtensionTest : public ::testing::Test{ +protected: + ExtensionTest(){ + }; + virtual ~ExtensionTest(){}; + virtual void SetUp() { + }; + virtual void TearDown() { + }; +}; + +inline brpc::Extension* ConstIntExtension() { + return brpc::Extension::instance(); +} + +inline brpc::Extension* IntExtension() { + return brpc::Extension::instance(); +} + + +const int g_foo = 10; +const int g_bar = 20; + +TEST_F(ExtensionTest, basic) { + ConstIntExtension()->Register("foo", NULL); + ConstIntExtension()->Register("foo", &g_foo); + ConstIntExtension()->Register("bar", &g_bar); + + int* val1 = new int(0xbeef); + int* val2 = new int(0xdead); + ASSERT_EQ(0, IntExtension()->Register("hello", val1)); + ASSERT_EQ(-1, IntExtension()->Register("hello", val1)); + IntExtension()->Register("there", val2); + ASSERT_EQ(val1, IntExtension()->Find("hello")); + ASSERT_EQ(val2, IntExtension()->Find("there")); + std::ostringstream os; + IntExtension()->List(os, ':'); + ASSERT_EQ("hello:there", os.str()); + + os.str(""); + ConstIntExtension()->List(os, ':'); + ASSERT_EQ("foo:bar", os.str()); +} diff --git a/test/brpc_grpc_protocol_unittest.cpp b/test/brpc_grpc_protocol_unittest.cpp new file mode 100644 index 0000000..f170639 --- /dev/null +++ b/test/brpc_grpc_protocol_unittest.cpp @@ -0,0 +1,275 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include "brpc/controller.h" +#include "brpc/server.h" +#include "brpc/channel.h" +#include "brpc/grpc.h" +#include "butil/time.h" +#include "grpc.pb.h" + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + if (GFLAGS_NAMESPACE::SetCommandLineOption("http_body_compress_threshold", "0").empty()) { + std::cerr << "Fail to set -crash_on_fatal_log" << std::endl; + return -1; + } + if (GFLAGS_NAMESPACE::SetCommandLineOption("crash_on_fatal_log", "true").empty()) { + std::cerr << "Fail to set -crash_on_fatal_log" << std::endl; + return -1; + } + return RUN_ALL_TESTS(); +} + +namespace { + +const std::string g_server_addr = "127.0.0.1:8011"; +const std::string g_prefix = "Hello, "; +const std::string g_req = "wyt"; +const int64_t g_timeout_ms = 1000; +const std::string g_protocol = "h2:grpc"; + +class MyGrpcService : public ::test::GrpcService { +public: + void Method(::google::protobuf::RpcController* cntl_base, + const ::test::GrpcRequest* req, + ::test::GrpcResponse* res, + ::google::protobuf::Closure* done) { + brpc::Controller* cntl = + static_cast(cntl_base); + brpc::ClosureGuard done_guard(done); + + EXPECT_EQ(g_req, req->message()); + if (req->gzip()) { + cntl->set_response_compress_type(brpc::COMPRESS_TYPE_GZIP); + } + res->set_message(g_prefix + req->message()); + + if (req->return_error()) { + cntl->SetFailed(brpc::EINTERNAL, "%s", g_prefix.c_str()); + return; + } + if (req->has_timeout_us()) { + if (req->timeout_us() < 0) { + EXPECT_EQ(-1, cntl->deadline_us()); + } else { + EXPECT_NEAR(cntl->deadline_us(), + butil::gettimeofday_us() + req->timeout_us(), 5000); + } + } + } + + void MethodTimeOut(::google::protobuf::RpcController* cntl_base, + const ::test::GrpcRequest* req, + ::test::GrpcResponse* res, + ::google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + bthread_usleep(2000000 /*2s*/); + res->set_message(g_prefix + req->message()); + return; + } +}; + +class GrpcTest : public ::testing::Test { +protected: + GrpcTest() { + EXPECT_EQ(0, _server.AddService(&_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + EXPECT_EQ(0, _server.Start(g_server_addr.c_str(), NULL)); + brpc::ChannelOptions options; + options.protocol = g_protocol; + options.timeout_ms = g_timeout_ms; + EXPECT_EQ(0, _channel.Init(g_server_addr.c_str(), "", &options)); + } + + virtual ~GrpcTest() {}; + virtual void SetUp() {}; + virtual void TearDown() {}; + + void CallMethod(bool req_gzip, bool res_gzip) { + test::GrpcRequest req; + test::GrpcResponse res; + brpc::Controller cntl; + if (req_gzip) { + cntl.set_request_compress_type(brpc::COMPRESS_TYPE_GZIP); + } + req.set_message(g_req); + req.set_gzip(res_gzip); + req.set_return_error(false); + + test::GrpcService_Stub stub(&_channel); + stub.Method(&cntl, &req, &res, NULL); + EXPECT_FALSE(cntl.Failed()) << cntl.ErrorCode() << ": " << cntl.ErrorText(); + EXPECT_EQ(res.message(), g_prefix + g_req); + } + + brpc::Server _server; + MyGrpcService _svc; + brpc::Channel _channel; +}; + +TEST_F(GrpcTest, percent_encode) { + std::string out; + std::string s1("abcdefg !@#$^&*()/"); + std::string s1_out("abcdefg%20%21%40%23%24%5e%26%2a%28%29%2f"); + brpc::PercentEncode(s1, &out); + EXPECT_TRUE(out == s1_out) << s1_out << " vs " << out; + + char s2_buf[] = "\0\0%\33\35 brpc"; + std::string s2(s2_buf, sizeof(s2_buf) - 1); + std::string s2_expected_out("%00%00%25%1b%1d%20brpc"); + brpc::PercentEncode(s2, &out); + EXPECT_TRUE(out == s2_expected_out) << s2_expected_out << " vs " << out; +} + +TEST_F(GrpcTest, percent_decode) { + std::string out; + std::string s1("abcdefg%20%21%40%23%24%5e%26%2a%28%29%2f"); + std::string s1_out("abcdefg !@#$^&*()/"); + brpc::PercentDecode(s1, &out); + EXPECT_TRUE(out == s1_out) << s1_out << " vs " << out; + + std::string s2("%00%00%1b%1d%20brpc"); + char s2_expected_out_buf[] = "\0\0\33\35 brpc"; + std::string s2_expected_out(s2_expected_out_buf, sizeof(s2_expected_out_buf) - 1); + brpc::PercentDecode(s2, &out); + EXPECT_TRUE(out == s2_expected_out) << s2_expected_out << " vs " << out; +} + +TEST_F(GrpcTest, sanity) { + for (int i = 0; i < 2; ++i) { // if req use gzip or not + for (int j = 0; j < 2; ++j) { // if res use gzip or not + CallMethod(i, j); + } + } +} + +TEST_F(GrpcTest, return_error) { + test::GrpcRequest req; + test::GrpcResponse res; + brpc::Controller cntl; + req.set_message(g_req); + req.set_gzip(false); + req.set_return_error(true); + test::GrpcService_Stub stub(&_channel); + stub.Method(&cntl, &req, &res, NULL); + EXPECT_TRUE(cntl.Failed()); + EXPECT_EQ(cntl.ErrorCode(), brpc::EINTERNAL); + EXPECT_TRUE(butil::StringPiece(cntl.ErrorText()).ends_with(butil::string_printf("%s", g_prefix.c_str()))); +} + +TEST_F(GrpcTest, RpcTimedOut) { + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = g_protocol; + options.timeout_ms = g_timeout_ms; + EXPECT_EQ(0, channel.Init(g_server_addr.c_str(), "", &options)); + + test::GrpcRequest req; + test::GrpcResponse res; + brpc::Controller cntl; + req.set_message(g_req); + req.set_gzip(false); + req.set_return_error(false); + test::GrpcService_Stub stub(&_channel); + stub.MethodTimeOut(&cntl, &req, &res, NULL); + EXPECT_TRUE(cntl.Failed()); + EXPECT_EQ(cntl.ErrorCode(), brpc::ERPCTIMEDOUT); +} + +TEST_F(GrpcTest, MethodNotExist) { + test::GrpcRequest req; + test::GrpcResponse res; + brpc::Controller cntl; + req.set_message(g_req); + req.set_gzip(false); + req.set_return_error(false); + test::GrpcService_Stub stub(&_channel); + stub.MethodNotExist(&cntl, &req, &res, NULL); + EXPECT_TRUE(cntl.Failed()); + EXPECT_EQ(cntl.ErrorCode(), brpc::EINTERNAL); + ASSERT_TRUE(butil::StringPiece(cntl.ErrorText()).ends_with("Method MethodNotExist() not implemented.")); +} + +TEST_F(GrpcTest, GrpcTimeOut) { + const char* timeouts[] = { + // valid case + "2H", "7200000000", + "3M", "180000000", + "+1S", "1000000", + "4m", "4000", + "5u", "5", + "6n", "1", + // invalid case + "30A", "-1", + "123ASH", "-1", + "HHHH", "-1", + "112", "-1", + "H999m", "-1", + "", "-1" + }; + + // test all timeout format + for (size_t i = 0; i < arraysize(timeouts); i = i + 2) { + test::GrpcRequest req; + test::GrpcResponse res; + brpc::Controller cntl; + req.set_message(g_req); + req.set_gzip(false); + req.set_return_error(false); + req.set_timeout_us((int64_t)(strtol(timeouts[i+1], NULL, 10))); + cntl.set_timeout_ms(-1); + cntl.http_request().SetHeader("grpc-timeout", timeouts[i]); + test::GrpcService_Stub stub(&_channel); + stub.Method(&cntl, &req, &res, NULL); + EXPECT_FALSE(cntl.Failed()); + } + + // test timeout by using timeout_ms in cntl + { + test::GrpcRequest req; + test::GrpcResponse res; + brpc::Controller cntl; + req.set_message(g_req); + req.set_gzip(false); + req.set_return_error(false); + req.set_timeout_us(9876000); + cntl.set_timeout_ms(9876); + test::GrpcService_Stub stub(&_channel); + stub.Method(&cntl, &req, &res, NULL); + EXPECT_FALSE(cntl.Failed()); + } + + // test timeout by using timeout_ms in channel + { + test::GrpcRequest req; + test::GrpcResponse res; + brpc::Controller cntl; + req.set_message(g_req); + req.set_gzip(false); + req.set_return_error(false); + req.set_timeout_us(g_timeout_ms * 1000); + test::GrpcService_Stub stub(&_channel); + stub.Method(&cntl, &req, &res, NULL); + EXPECT_FALSE(cntl.Failed()); + } +} + +} // namespace diff --git a/test/brpc_h2_unsent_message_unittest.cpp b/test/brpc_h2_unsent_message_unittest.cpp new file mode 100644 index 0000000..5e3b266 --- /dev/null +++ b/test/brpc_h2_unsent_message_unittest.cpp @@ -0,0 +1,88 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: Tue Oct 9 20:27:18 CST 2018 + +#include +#include +#include "bthread/bthread.h" +#include "butil/atomicops.h" +#include "brpc/policy/http_rpc_protocol.h" +#include "brpc/policy/http2_rpc_protocol.h" +#include "gperftools_helper.h" + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + +TEST(H2UnsentMessage, request_throughput) { + brpc::Controller cntl; + butil::IOBuf request_buf; + cntl.http_request().uri() = "0.0.0.0:8010/HttpService/Echo"; + brpc::policy::SerializeHttpRequest(&request_buf, &cntl, NULL); + + brpc::SocketId id; + brpc::SocketUniquePtr h2_client_sock; + brpc::SocketOptions h2_client_options; + h2_client_options.user = brpc::get_client_side_messenger(); + EXPECT_EQ(0, brpc::Socket::Create(h2_client_options, &id)); + EXPECT_EQ(0, brpc::Socket::Address(id, &h2_client_sock)); + + brpc::policy::H2Context* ctx = + new brpc::policy::H2Context(h2_client_sock.get(), NULL); + CHECK_EQ(ctx->Init(), 0); + h2_client_sock->initialize_parsing_context(&ctx); + ctx->_last_sent_stream_id = 0; + ctx->_remote_window_left = brpc::H2Settings::MAX_WINDOW_SIZE; + + int64_t ntotal = 500000; + + // calc H2UnsentRequest throughput + butil::IOBuf dummy_buf; + ProfilerStart("h2_unsent_req.prof"); + int64_t start_us = butil::cpuwide_time_us(); + for (int i = 0; i < ntotal; ++i) { + brpc::policy::H2UnsentRequest* req = brpc::policy::H2UnsentRequest::New(&cntl); + req->AppendAndDestroySelf(&dummy_buf, h2_client_sock.get()); + } + int64_t end_us = butil::cpuwide_time_us(); + ProfilerStop(); + int64_t elapsed = end_us - start_us; + LOG(INFO) << "H2UnsentRequest average qps=" + << (ntotal * 1000000L) / elapsed << "/s, data throughput=" + << dummy_buf.size() * 1000000L / elapsed << "/s"; + + // calc H2UnsentResponse throughput + dummy_buf.clear(); + start_us = butil::cpuwide_time_us(); + for (int i = 0; i < ntotal; ++i) { + // H2UnsentResponse::New would release cntl.http_response() and swap + // cntl.response_attachment() + cntl.http_response().set_content_type("text/plain"); + cntl.response_attachment().append("0123456789abcedef"); + brpc::policy::H2UnsentResponse* res = brpc::policy::H2UnsentResponse::New(&cntl, 0, false); + res->AppendAndDestroySelf(&dummy_buf, h2_client_sock.get()); + } + end_us = butil::cpuwide_time_us(); + elapsed = end_us - start_us; + LOG(INFO) << "H2UnsentResponse average qps=" + << (ntotal * 1000000L) / elapsed << "/s, data throughput=" + << dummy_buf.size() * 1000000L / elapsed << "/s"; +} diff --git a/test/brpc_hpack_unittest.cpp b/test/brpc_hpack_unittest.cpp new file mode 100644 index 0000000..d6a0bd0 --- /dev/null +++ b/test/brpc_hpack_unittest.cpp @@ -0,0 +1,620 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: 2017/04/25 00:23:12 + +#include +#include +#include +#include +#include +#include +#include "brpc/details/hpack.h" +#include "butil/logging.h" + +class HPackTest : public testing::Test { +}; + +static void* DecodeManyDynamicTableSizeUpdates(void*) { + brpc::HPacker p; + if (p.Init(4096) != 0) { + return (void*)1; + } + + butil::IOBuf buf; + std::string updates(200000, '\x20'); + buf.append(updates); + + brpc::HPacker::Header h; + return (void*)(p.Decode(&buf, &h) != 0); +} + +TEST_F(HPackTest, many_dynamic_table_size_updates) { + const pid_t pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + if (freopen("/dev/null", "w", stdout) == NULL || + freopen("/dev/null", "w", stderr) == NULL) { + exit(1); + } + + pthread_attr_t attr; + if (pthread_attr_init(&attr) != 0) { + exit(2); + } + if (pthread_attr_setstacksize(&attr, 64 * 1024) != 0) { + exit(3); + } + pthread_t tid; + if (pthread_create(&tid, &attr, DecodeManyDynamicTableSizeUpdates, NULL) != 0) { + exit(4); + } + pthread_attr_destroy(&attr); + void* ret = NULL; + if (pthread_join(tid, &ret) != 0) { + exit(5); + } + exit(ret == NULL ? 0 : 6); + } + int status = 0; + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + ASSERT_TRUE(WIFEXITED(status)); + ASSERT_EQ(0, WEXITSTATUS(status)); +} + +TEST_F(HPackTest, dynamic_table_size_update_before_header) { + brpc::HPacker p; + ASSERT_EQ(0, p.Init(4096)); + + butil::IOBuf buf; + uint8_t encoded[] = { + 0x20, // Dynamic table size update to 0. + 0x82, // Indexed :method: GET. + }; + buf.append(encoded, sizeof(encoded)); + + brpc::HPacker::Header h; + ASSERT_EQ((ssize_t)sizeof(encoded), p.Decode(&buf, &h)); + ASSERT_TRUE(buf.empty()); + ASSERT_EQ(":method", h.name); + ASSERT_EQ("GET", h.value); +} + +// Copied test cases from example of rfc7541 +TEST_F(HPackTest, header_with_indexing) { + brpc::HPacker p1; + ASSERT_EQ(0, p1.Init(4096)); + brpc::HPacker p2; + ASSERT_EQ(0, p2.Init(4096)); + brpc::HPacker::Header h; + h.name = "Custom-Key"; + h.value = "custom-header"; + brpc::HPackOptions options; + options.index_policy = brpc::HPACK_INDEX_HEADER; + butil::IOBufAppender buf; + p1.Encode(&buf, h, options); + const ssize_t nwrite = buf.buf().size(); + LOG(INFO) << butil::ToPrintable(buf.buf()); + uint8_t expected[] = { + 0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, + 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x68, 0x65, 0x61, 0x64, + 0x65, 0x72}; + butil::StringPiece sp((char*)expected, sizeof(expected)); + ASSERT_TRUE(buf.buf().equals(sp)); + brpc::HPacker::Header h2; + ssize_t nread = p2.Decode(&buf.buf(), &h2); + ASSERT_EQ(nread, nwrite); + ASSERT_TRUE(buf.buf().empty()); + std::string lowercase_name = h.name; + brpc::tolower(&lowercase_name); + ASSERT_EQ(lowercase_name, h2.name); + ASSERT_EQ(h.value, h2.value); +} + +TEST_F(HPackTest, header_without_indexing) { + brpc::HPacker p1; + ASSERT_EQ(0, p1.Init(4096)); + brpc::HPacker p2; + ASSERT_EQ(0, p2.Init(4096)); + brpc::HPacker::Header h; + h.name = ":path"; + h.value = "/sample/path"; + brpc::HPackOptions options; + options.index_policy = brpc::HPACK_NOT_INDEX_HEADER; + butil::IOBufAppender buf; + p1.Encode(&buf, h, options); + const ssize_t nwrite = buf.buf().size(); + LOG(INFO) << butil::ToPrintable(buf.buf()); + uint8_t expected[] = { + 0x04, 0x0c, 0x2f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x70, 0x61, + 0x74, 0x68, + }; + butil::StringPiece sp((char*)expected, sizeof(expected)); + ASSERT_TRUE(buf.buf().equals(sp)); + brpc::HPacker::Header h2; + ssize_t nread = p2.Decode(&buf.buf(), &h2); + ASSERT_EQ(nread, nwrite); + ASSERT_TRUE(buf.buf().empty()); + std::string lowercase_name = h.name; + brpc::tolower(&lowercase_name); + ASSERT_EQ(lowercase_name, h2.name); + ASSERT_EQ(h.value, h2.value); +} + +TEST_F(HPackTest, header_never_indexed) { + brpc::HPacker p1; + ASSERT_EQ(0, p1.Init(4096)); + brpc::HPacker p2; + ASSERT_EQ(0, p2.Init(4096)); + brpc::HPacker::Header h; + h.name = "password"; + h.value = "secret"; + brpc::HPackOptions options; + options.index_policy = brpc::HPACK_NEVER_INDEX_HEADER; + butil::IOBufAppender buf; + p1.Encode(&buf, h, options); + const ssize_t nwrite = buf.buf().size(); + LOG(INFO) << butil::ToPrintable(buf.buf()); + uint8_t expected[] = { + 0x10, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, + 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, + }; + butil::StringPiece sp((char*)expected, sizeof(expected)); + ASSERT_TRUE(buf.buf().equals(sp)); + brpc::HPacker::Header h2; + ssize_t nread = p2.Decode(&buf.buf(), &h2); + ASSERT_EQ(nread, nwrite); + ASSERT_TRUE(buf.buf().empty()); + ASSERT_EQ(h.name, h2.name); + ASSERT_EQ(h.value, h2.value); +} + +TEST_F(HPackTest, indexed_header) { + brpc::HPacker p1; + ASSERT_EQ(0, p1.Init(4096)); + brpc::HPacker p2; + ASSERT_EQ(0, p2.Init(4096)); + brpc::HPacker::Header h; + h.name = ":method"; + h.value = "GET"; + brpc::HPackOptions options; + options.index_policy = brpc::HPACK_INDEX_HEADER; + butil::IOBufAppender buf; + p1.Encode(&buf, h, options); + const ssize_t nwrite = buf.buf().size(); + LOG(INFO) << butil::ToPrintable(buf.buf()); + uint8_t expected[] = { + 0x82, + }; + butil::StringPiece sp((char*)expected, sizeof(expected)); + ASSERT_TRUE(buf.buf().equals(sp)); + brpc::HPacker::Header h2; + ssize_t nread = p2.Decode(&buf.buf(), &h2); + ASSERT_EQ(nread, nwrite); + ASSERT_TRUE(buf.buf().empty()); + ASSERT_EQ(h.name, h2.name); + ASSERT_EQ(h.value, h2.value); +} + +struct ConstHeader { + const char* name; + const char* value; +}; + +TEST_F(HPackTest, requests_without_huffman) { + brpc::HPacker p1; + ASSERT_EQ(0, p1.Init(4096)); + brpc::HPacker p2; + ASSERT_EQ(0, p2.Init(4096)); + + ConstHeader header1[] = { + {":method", "GET"}, + {":scheme", "http"}, + {":path", "/"}, + {":authority", "www.example.com"}, + }; + butil::IOBufAppender buf; + for (size_t i = 0; i < ARRAY_SIZE(header1); ++i) { + brpc::HPacker::Header h; + h.name = header1[i].name; + h.value = header1[i].value; + brpc::HPackOptions options; + options.index_policy = brpc::HPACK_INDEX_HEADER; + p1.Encode(&buf, h, options); + } + uint8_t expected1[] = { + 0x82, 0x86, 0x84, 0x41, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + }; + ASSERT_TRUE(buf.buf().equals(butil::StringPiece((char*)expected1, sizeof(expected1)))); + for (size_t i = 0; i < ARRAY_SIZE(header1); ++i) { + brpc::HPacker::Header h; + ASSERT_GT(p2.Decode(&buf.buf(), &h), 0); + ASSERT_EQ(header1[i].name, h.name); + ASSERT_EQ(header1[i].value, h.value); + } + ASSERT_TRUE(buf.buf().empty()); + + ConstHeader header2[] = { + {":method", "GET"}, + {":scheme", "http"}, + {":path", "/"}, + {":authority", "www.example.com"}, + {"cache-control", "no-cache"}, + }; + + for (size_t i = 0; i < ARRAY_SIZE(header2); ++i) { + brpc::HPacker::Header h; + h.name = header2[i].name; + h.value = header2[i].value; + brpc::HPackOptions options; + options.index_policy = brpc::HPACK_INDEX_HEADER; + p1.Encode(&buf, h, options); + } + uint8_t expected2[] = { + 0x82, 0x86, 0x84, 0xbe, 0x58, 0x08, 0x6e, 0x6f, 0x2d, + 0x63, 0x61, 0x63, 0x68, 0x65, + }; + ASSERT_TRUE(buf.buf().equals(butil::StringPiece((char*)expected2, sizeof(expected2)))); + for (size_t i = 0; i < ARRAY_SIZE(header2); ++i) { + brpc::HPacker::Header h; + ASSERT_GT(p2.Decode(&buf.buf(), &h), 0); + ASSERT_EQ(header2[i].name, h.name); + ASSERT_EQ(header2[i].value, h.value); + } + ASSERT_TRUE(buf.buf().empty()); + ConstHeader header3[] = { + {":method", "GET"}, + {":scheme", "https"}, + {":path", "/index.html"}, + {":authority", "www.example.com"}, + {"custom-key", "custom-value"}, + }; + + for (size_t i = 0; i < ARRAY_SIZE(header3); ++i) { + brpc::HPacker::Header h; + h.name = header3[i].name; + h.value = header3[i].value; + brpc::HPackOptions options; + options.index_policy = brpc::HPACK_INDEX_HEADER; + p1.Encode(&buf, h, options); + } + uint8_t expected3[] = { + 0x82, 0x87, 0x85, 0xbf, 0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x2d, 0x6b, 0x65, 0x79, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, + 0x76, 0x61, 0x6c, 0x75, 0x65, + }; + ASSERT_TRUE(buf.buf().equals(butil::StringPiece((char*)expected3, sizeof(expected3)))); + for (size_t i = 0; i < ARRAY_SIZE(header3); ++i) { + brpc::HPacker::Header h; + ASSERT_GT(p2.Decode(&buf.buf(), &h), 0); + ASSERT_EQ(header3[i].name, h.name); + ASSERT_EQ(header3[i].value, h.value); + } + ASSERT_TRUE(buf.buf().empty()); +} + +TEST_F(HPackTest, requests_with_huffman) { + brpc::HPacker p1; + ASSERT_EQ(0, p1.Init(4096)); + brpc::HPacker p2; + ASSERT_EQ(0, p2.Init(4096)); + + ConstHeader header1[] = { + {":method", "GET"}, + {":scheme", "http"}, + {":path", "/"}, + {":authority", "www.example.com"}, + }; + butil::IOBufAppender buf; + for (size_t i = 0; i < ARRAY_SIZE(header1); ++i) { + brpc::HPacker::Header h; + h.name = header1[i].name; + h.value = header1[i].value; + brpc::HPackOptions options; + options.index_policy = brpc::HPACK_INDEX_HEADER; + options.encode_name = true; + options.encode_value = true; + p1.Encode(&buf, h, options); + } + uint8_t expected1[] = { + 0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, + 0xa0, 0xab, 0x90, 0xf4, 0xff + }; + LOG(INFO) << butil::ToPrintable(buf.buf()); + ASSERT_TRUE(buf.buf().equals(butil::StringPiece((char*)expected1, sizeof(expected1)))); + for (size_t i = 0; i < ARRAY_SIZE(header1); ++i) { + brpc::HPacker::Header h; + ASSERT_GT(p2.Decode(&buf.buf(), &h), 0); + ASSERT_EQ(header1[i].name, h.name); + ASSERT_EQ(header1[i].value, h.value); + } + ASSERT_TRUE(buf.buf().empty()); + + ConstHeader header2[] = { + {":method", "GET"}, + {":scheme", "http"}, + {":path", "/"}, + {":authority", "www.example.com"}, + {"cache-control", "no-cache"}, + }; + + for (size_t i = 0; i < ARRAY_SIZE(header2); ++i) { + brpc::HPacker::Header h; + h.name = header2[i].name; + h.value = header2[i].value; + brpc::HPackOptions options; + options.encode_name = true; + options.encode_value = true; + options.index_policy = brpc::HPACK_INDEX_HEADER; + p1.Encode(&buf, h, options); + } + uint8_t expected2[] = { + 0x82, 0x86, 0x84, 0xbe, 0x58, 0x86, 0xa8, 0xeb, 0x10, 0x64, 0x9c, 0xbf, + }; + ASSERT_TRUE(buf.buf().equals(butil::StringPiece((char*)expected2, sizeof(expected2)))); + for (size_t i = 0; i < ARRAY_SIZE(header2); ++i) { + brpc::HPacker::Header h; + ASSERT_GT(p2.Decode(&buf.buf(), &h), 0); + ASSERT_EQ(header2[i].name, h.name); + ASSERT_EQ(header2[i].value, h.value); + } + ASSERT_TRUE(buf.buf().empty()); + ConstHeader header3[] = { + {":method", "GET"}, + {":scheme", "https"}, + {":path", "/index.html"}, + {":authority", "www.example.com"}, + {"custom-key", "custom-value"}, + }; + + for (size_t i = 0; i < ARRAY_SIZE(header3); ++i) { + brpc::HPacker::Header h; + h.name = header3[i].name; + h.value = header3[i].value; + brpc::HPackOptions options; + options.encode_name = true; + options.encode_value = true; + options.index_policy = brpc::HPACK_INDEX_HEADER; + p1.Encode(&buf, h, options); + } + uint8_t expected3[] = { + 0x82, 0x87, 0x85, 0xbf, 0x40, 0x88, 0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xa9, + 0x7d, 0x7f, 0x89, 0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xb8, 0xe8, 0xb4, 0xbf, + }; + ASSERT_TRUE(buf.buf().equals(butil::StringPiece((char*)expected3, sizeof(expected3)))); + for (size_t i = 0; i < ARRAY_SIZE(header3); ++i) { + brpc::HPacker::Header h; + ASSERT_GT(p2.Decode(&buf.buf(), &h), 0); + ASSERT_EQ(header3[i].name, h.name); + ASSERT_EQ(header3[i].value, h.value); + } + ASSERT_TRUE(buf.buf().empty()); +} + +TEST_F(HPackTest, responses_without_huffman) { + // https://tools.ietf.org/html/rfc7541#appendix-C.5 + brpc::HPacker p1; + ASSERT_EQ(0, p1.Init(256)); + brpc::HPacker p2; + ASSERT_EQ(0, p2.Init(256)); + + ConstHeader header1[] = { + {":status", "302"}, + {"cache-control", "private"}, + {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + {"location", "https://www.example.com"}, + }; + butil::IOBufAppender buf; + for (size_t i = 0; i < ARRAY_SIZE(header1); ++i) { + brpc::HPacker::Header h; + h.name = header1[i].name; + h.value = header1[i].value; + brpc::HPackOptions options; + options.index_policy = brpc::HPACK_INDEX_HEADER; + p1.Encode(&buf, h, options); + } + uint8_t expected1[] = { + 0x48, 0x03, 0x33, 0x30, 0x32, 0x58, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, + 0x74, 0x65, 0x61, 0x1d, 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, + 0x4f, 0x63, 0x74, 0x20, 0x32, 0x30, 0x31, 0x33, 0x20, 0x32, 0x30, 0x3a, + 0x31, 0x33, 0x3a, 0x32, 0x31, 0x20, 0x47, 0x4d, 0x54, 0x6e, 0x17, 0x68, + 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, + 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + }; + LOG(INFO) << butil::ToPrintable(buf.buf()); + ASSERT_TRUE(buf.buf().equals(butil::StringPiece((char*)expected1, sizeof(expected1)))); + for (size_t i = 0; i < ARRAY_SIZE(header1); ++i) { + brpc::HPacker::Header h; + ASSERT_GT(p2.Decode(&buf.buf(), &h), 0); + ASSERT_EQ(header1[i].name, h.name); + ASSERT_EQ(header1[i].value, h.value); + } + ASSERT_TRUE(buf.buf().empty()); + + ConstHeader header2[] = { + {":status", "307"}, + {"cache-control", "private"}, + {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + {"location", "https://www.example.com"}, + }; + + for (size_t i = 0; i < ARRAY_SIZE(header2); ++i) { + brpc::HPacker::Header h; + h.name = header2[i].name; + h.value = header2[i].value; + brpc::HPackOptions options; + options.index_policy = brpc::HPACK_INDEX_HEADER; + p1.Encode(&buf, h, options); + } + uint8_t expected2[] = { + 0x48, 0x03, 0x33, 0x30, 0x37, 0xc1, 0xc0, 0xbf, + }; + ASSERT_TRUE(buf.buf().equals(butil::StringPiece((char*)expected2, sizeof(expected2)))); + for (size_t i = 0; i < ARRAY_SIZE(header2); ++i) { + brpc::HPacker::Header h; + ASSERT_GT(p2.Decode(&buf.buf(), &h), 0); + ASSERT_EQ(header2[i].name, h.name); + ASSERT_EQ(header2[i].value, h.value); + } + ASSERT_TRUE(buf.buf().empty()); + ConstHeader header3[] = { + {":status", "200"}, + {"cache-control", "private"}, + {"date", "Mon, 21 Oct 2013 20:13:22 GMT"}, + {"location", "https://www.example.com"}, + {"content-encoding", "gzip"}, + {"set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"}, + }; + + for (size_t i = 0; i < ARRAY_SIZE(header3); ++i) { + brpc::HPacker::Header h; + h.name = header3[i].name; + h.value = header3[i].value; + brpc::HPackOptions options; + options.index_policy = brpc::HPACK_INDEX_HEADER; + p1.Encode(&buf, h, options); + } + uint8_t expected3[] = { + 0x88, 0xc1, 0x61, 0x1d, 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, + 0x4f, 0x63, 0x74, 0x20, 0x32, 0x30, 0x31, 0x33, 0x20, 0x32, 0x30, 0x3a, + 0x31, 0x33, 0x3a, 0x32, 0x32, 0x20, 0x47, 0x4d, 0x54, 0xc0, 0x5a, 0x04, + 0x67, 0x7a, 0x69, 0x70, 0x77, 0x38, 0x66, 0x6f, 0x6f, 0x3d, 0x41, 0x53, + 0x44, 0x4a, 0x4b, 0x48, 0x51, 0x4b, 0x42, 0x5a, 0x58, 0x4f, 0x51, 0x57, + 0x45, 0x4f, 0x50, 0x49, 0x55, 0x41, 0x58, 0x51, 0x57, 0x45, 0x4f, 0x49, + 0x55, 0x3b, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, 0x3d, 0x33, + 0x36, 0x30, 0x30, 0x3b, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x3d, 0x31, + }; + ASSERT_TRUE(buf.buf().equals(butil::StringPiece((char*)expected3, sizeof(expected3)))); + for (size_t i = 0; i < ARRAY_SIZE(header3); ++i) { + brpc::HPacker::Header h; + ASSERT_GT(p2.Decode(&buf.buf(), &h), 0); + ASSERT_EQ(header3[i].name, h.name); + ASSERT_EQ(header3[i].value, h.value); + } + ASSERT_TRUE(buf.buf().empty()); +} + +TEST_F(HPackTest, responses_with_huffman) { + // https://tools.ietf.org/html/rfc7541#appendix-C.5 + brpc::HPacker p1; + ASSERT_EQ(0, p1.Init(256)); + brpc::HPacker p2; + ASSERT_EQ(0, p2.Init(256)); + + ConstHeader header1[] = { + {":status", "302"}, + {"cache-control", "private"}, + {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + {"location", "https://www.example.com"}, + }; + butil::IOBufAppender buf; + for (size_t i = 0; i < ARRAY_SIZE(header1); ++i) { + brpc::HPacker::Header h; + h.name = header1[i].name; + h.value = header1[i].value; + brpc::HPackOptions options; + options.encode_name = true; + options.encode_value = true; + options.index_policy = brpc::HPACK_INDEX_HEADER; + p1.Encode(&buf, h, options); + } + uint8_t expected1[] = { + 0x48, 0x82, 0x64, 0x02, 0x58, 0x85, 0xae, 0xc3, 0x77, 0x1a, 0x4b, 0x61, + 0x96, 0xd0, 0x7a, 0xbe, 0x94, 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, + 0x95, 0x04, 0x0b, 0x81, 0x66, 0xe0, 0x82, 0xa6, 0x2d, 0x1b, 0xff, 0x6e, + 0x91, 0x9d, 0x29, 0xad, 0x17, 0x18, 0x63, 0xc7, 0x8f, 0x0b, 0x97, 0xc8, + 0xe9, 0xae, 0x82, 0xae, 0x43, 0xd3, + }; + LOG(INFO) << butil::ToPrintable(buf.buf()); + ASSERT_TRUE(buf.buf().equals(butil::StringPiece((char*)expected1, sizeof(expected1)))); + for (size_t i = 0; i < ARRAY_SIZE(header1); ++i) { + brpc::HPacker::Header h; + ASSERT_GT(p2.Decode(&buf.buf(), &h), 0); + ASSERT_EQ(header1[i].name, h.name); + ASSERT_EQ(header1[i].value, h.value); + } + ASSERT_TRUE(buf.buf().empty()); + + ConstHeader header2[] = { + {":status", "307"}, + {"cache-control", "private"}, + {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + {"location", "https://www.example.com"}, + }; + + for (size_t i = 0; i < ARRAY_SIZE(header2); ++i) { + brpc::HPacker::Header h; + h.name = header2[i].name; + h.value = header2[i].value; + brpc::HPackOptions options; + options.encode_name = true; + options.encode_value = true; + options.index_policy = brpc::HPACK_INDEX_HEADER; + p1.Encode(&buf, h, options); + } + uint8_t expected2[] = { + 0x48, 0x83, 0x64, 0x0e, 0xff, 0xc1, 0xc0, 0xbf, + }; + ASSERT_TRUE(buf.buf().equals(butil::StringPiece((char*)expected2, sizeof(expected2)))); + for (size_t i = 0; i < ARRAY_SIZE(header2); ++i) { + brpc::HPacker::Header h; + ASSERT_GT(p2.Decode(&buf.buf(), &h), 0); + ASSERT_EQ(header2[i].name, h.name); + ASSERT_EQ(header2[i].value, h.value); + } + ASSERT_TRUE(buf.buf().empty()); + ConstHeader header3[] = { + {":status", "200"}, + {"cache-control", "private"}, + {"date", "Mon, 21 Oct 2013 20:13:22 GMT"}, + {"location", "https://www.example.com"}, + {"content-encoding", "gzip"}, + {"set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"}, + }; + + for (size_t i = 0; i < ARRAY_SIZE(header3); ++i) { + brpc::HPacker::Header h; + h.name = header3[i].name; + h.value = header3[i].value; + brpc::HPackOptions options; + options.encode_name = true; + options.encode_value = true; + options.index_policy = brpc::HPACK_INDEX_HEADER; + p1.Encode(&buf, h, options); + } + uint8_t expected3[] = { + 0x88, 0xc1, 0x61, 0x96, 0xd0, 0x7a, 0xbe, 0x94, 0x10, 0x54, 0xd4, 0x44, + 0xa8, 0x20, 0x05, 0x95, 0x04, 0x0b, 0x81, 0x66, 0xe0, 0x84, 0xa6, 0x2d, + 0x1b, 0xff, 0xc0, 0x5a, 0x83, 0x9b, 0xd9, 0xab, 0x77, 0xad, 0x94, 0xe7, + 0x82, 0x1d, 0xd7, 0xf2, 0xe6, 0xc7, 0xb3, 0x35, 0xdf, 0xdf, 0xcd, 0x5b, + 0x39, 0x60, 0xd5, 0xaf, 0x27, 0x08, 0x7f, 0x36, 0x72, 0xc1, 0xab, 0x27, + 0x0f, 0xb5, 0x29, 0x1f, 0x95, 0x87, 0x31, 0x60, 0x65, 0xc0, 0x03, 0xed, + 0x4e, 0xe5, 0xb1, 0x06, 0x3d, 0x50, 0x07, + }; + ASSERT_TRUE(buf.buf().equals(butil::StringPiece((char*)expected3, sizeof(expected3)))); + for (size_t i = 0; i < ARRAY_SIZE(header3); ++i) { + brpc::HPacker::Header h; + ASSERT_GT(p2.Decode(&buf.buf(), &h), 0); + ASSERT_EQ(header3[i].name, h.name); + ASSERT_EQ(header3[i].value, h.value); + } + ASSERT_TRUE(buf.buf().empty()); +} diff --git a/test/brpc_http_message_unittest.cpp b/test/brpc_http_message_unittest.cpp new file mode 100644 index 0000000..9933a8d --- /dev/null +++ b/test/brpc_http_message_unittest.cpp @@ -0,0 +1,741 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. +// +// Date 2014/10/24 16:44:30 + +#include +#include + +#include "brpc/server.h" +#include "brpc/details/http_message.h" +#include "brpc/policy/http_rpc_protocol.h" +#include "echo.pb.h" + +namespace brpc { + +DECLARE_bool(allow_chunked_length); +DECLARE_bool(allow_http_1_1_request_without_host); + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + brpc::FLAGS_allow_http_1_1_request_without_host = true; + return RUN_ALL_TESTS(); +} + +namespace policy { +Server::MethodProperty* +FindMethodPropertyByURI(const std::string& uri_path, const Server* server, + std::string* unknown_method_str); +bool ParseHttpServerAddress(butil::EndPoint *point, const char *server_addr_and_port); +}} + +namespace { +using brpc::policy::FindMethodPropertyByURI; +using brpc::policy::ParseHttpServerAddress; + +TEST(HttpMessageTest, http_method) { + ASSERT_STREQ("DELETE", brpc::HttpMethod2Str(brpc::HTTP_METHOD_DELETE)); + ASSERT_STREQ("GET", brpc::HttpMethod2Str(brpc::HTTP_METHOD_GET)); + ASSERT_STREQ("POST", brpc::HttpMethod2Str(brpc::HTTP_METHOD_POST)); + ASSERT_STREQ("PUT", brpc::HttpMethod2Str(brpc::HTTP_METHOD_PUT)); + + brpc::HttpMethod m; + ASSERT_TRUE(brpc::Str2HttpMethod("DELETE", &m)); + ASSERT_EQ(brpc::HTTP_METHOD_DELETE, m); + ASSERT_TRUE(brpc::Str2HttpMethod("GET", &m)); + ASSERT_EQ(brpc::HTTP_METHOD_GET, m); + ASSERT_TRUE(brpc::Str2HttpMethod("POST", &m)); + ASSERT_EQ(brpc::HTTP_METHOD_POST, m); + ASSERT_TRUE(brpc::Str2HttpMethod("PUT", &m)); + ASSERT_EQ(brpc::HTTP_METHOD_PUT, m); + + // case-insensitive + ASSERT_TRUE(brpc::Str2HttpMethod("DeLeTe", &m)); + ASSERT_EQ(brpc::HTTP_METHOD_DELETE, m); + ASSERT_TRUE(brpc::Str2HttpMethod("get", &m)); + ASSERT_EQ(brpc::HTTP_METHOD_GET, m); + + // non-existed + ASSERT_FALSE(brpc::Str2HttpMethod("DEL", &m)); + ASSERT_FALSE(brpc::Str2HttpMethod("DELETE ", &m)); + ASSERT_FALSE(brpc::Str2HttpMethod("GOT", &m)); +} + +TEST(HttpMessageTest, eof) { + GFLAGS_NAMESPACE::SetCommandLineOption("verbose", "100"); + const char* http_request = + "GET /CloudApiControl/HttpServer/telematics/v3/weather?location=%E6%B5%B7%E5%8D%97%E7%9C%81%E7%9B%B4%E8%BE%96%E5%8E%BF%E7%BA%A7%E8%A1%8C%E6%94%BF%E5%8D%95%E4%BD%8D&output=json&ak=0l3FSP6qA0WbOzGRaafbmczS HTTP/1.1\r\n" + "X-Host: api.map.baidu.com\r\n" + "X-Forwarded-Proto: http\r\n" + "Host: api.map.baidu.com\r\n" + "User-Agent: IME/Android/4.4.2/N80.QHD.LT.X10.V3/N80.QHD.LT.X10.V3.20150812.031915\r\n" + "Accept: application/json\r\n" + "Accept-Charset: UTF-8,*;q=0.5\r\n" + "Accept-Encoding: deflate,sdch\r\n" + "Accept-Language: zh-CN,en-US;q=0.8,zh;q=0.6\r\n" + "Bfe-Atk: NORMAL_BROWSER\r\n" + "Bfe_logid: 8767802212038413243\r\n" + "Bfeip: 10.26.124.40\r\n" + "CLIENTIP: 119.29.102.26\r\n" + "CLIENTPORT: 59863\r\n" + "Cache-Control: max-age=0\r\n" + "Content-Type: application/json;charset=utf8\r\n" + "X-Forwarded-For: 119.29.102.26\r\n" + "X-Forwarded-Port: 59863\r\n" + "X-Ime-Imei: 35629601890905\r\n" + "X_BD_LOGID: 3959476981\r\n" + "X_BD_LOGID64: 16815814797661447369\r\n" + "X_BD_PRODUCT: map\r\n" + "X_BD_SUBSYS: apimap\r\n"; + butil::IOBuf buf; + buf.append(http_request); + brpc::HttpMessage http_message; + ASSERT_EQ((ssize_t)buf.size(), http_message.ParseFromIOBuf(buf)); + ASSERT_EQ(2, http_message.ParseFromArray("\r\n", 2)); + ASSERT_TRUE(http_message.Completed()); +} + + +TEST(HttpMessageTest, request_sanity) { + const char *http_request = + "POST /path/file.html?sdfsdf=sdfs&sldf1=sdf HTTP/12.34\r\n" + "From: someuser@jmarshall.com\r\n" + "User-Agent: HTTPTool/1.0 \r\n" // intended ending spaces + "Content-Type: json\r\n" + "Content-Length: 19\r\n" + "Log-ID: 456\r\n" + "Host: myhost\r\n" + "Correlation-ID: 123\r\n" + "Authorization: test\r\n" + "Accept: */*\r\n" + "\r\n" + "Message Body sdfsdf\r\n" + ; + brpc::HttpMessage http_message; + ASSERT_EQ((ssize_t)strlen(http_request), + http_message.ParseFromArray(http_request, strlen(http_request))); + const brpc::HttpHeader& header = http_message.header(); + // Check all keys + ASSERT_EQ("json", header.content_type()); + ASSERT_TRUE(header.GetHeader("HOST")); + ASSERT_EQ("myhost", *header.GetHeader("host")); + ASSERT_TRUE(header.GetHeader("CORRELATION-ID")); + ASSERT_EQ("123", *header.GetHeader("CORRELATION-ID")); + ASSERT_TRUE(header.GetHeader("User-Agent")); + ASSERT_EQ("HTTPTool/1.0 ", *header.GetHeader("User-Agent")); + ASSERT_TRUE(header.GetHeader("Host")); + ASSERT_EQ("myhost", *header.GetHeader("Host")); + ASSERT_TRUE(header.GetHeader("Accept")); + ASSERT_EQ("*/*", *header.GetHeader("Accept")); + + ASSERT_EQ(1, header.major_version()); + ASSERT_EQ(34, header.minor_version()); + ASSERT_EQ(brpc::HTTP_METHOD_POST, header.method()); + ASSERT_EQ(brpc::HTTP_STATUS_OK, header.status_code()); + ASSERT_STREQ("OK", header.reason_phrase()); + + ASSERT_TRUE(header.GetHeader("log-id")); + ASSERT_EQ("456", *header.GetHeader("log-id")); + ASSERT_TRUE(NULL != header.GetHeader("Authorization")); + ASSERT_EQ("test", *header.GetHeader("Authorization")); +} + +TEST(HttpMessageTest, response_sanity) { + const char *http_response = + "HTTP/12.34 410 GoneBlah\r\n" + "From: someuser@jmarshall.com\r\n" + "User-Agent: HTTPTool/1.0 \r\n" // intended ending spaces + "Content-Type: json2\r\n" + "Content-Length: 19\r\n" + "Log-ID: 456\r\n" + "Host: myhost\r\n" + "Correlation-ID: 123\r\n" + "Authorization: test\r\n" + "Accept: */*\r\n" + "\r\n" + "Message Body sdfsdf\r\n" + ; + brpc::HttpMessage http_message; + ASSERT_EQ((ssize_t)strlen(http_response), + http_message.ParseFromArray(http_response, strlen(http_response))); + // Check all keys + const brpc::HttpHeader& header = http_message.header(); + ASSERT_EQ("json2", header.content_type()); + ASSERT_TRUE(header.GetHeader("HOST")); + ASSERT_EQ("myhost", *header.GetHeader("host")); + ASSERT_TRUE(header.GetHeader("CORRELATION-ID")); + ASSERT_EQ("123", *header.GetHeader("CORRELATION-ID")); + ASSERT_TRUE(header.GetHeader("User-Agent")); + ASSERT_EQ("HTTPTool/1.0 ", *header.GetHeader("User-Agent")); + ASSERT_TRUE(header.GetHeader("Host")); + ASSERT_EQ("myhost", *header.GetHeader("Host")); + ASSERT_TRUE(header.GetHeader("Accept")); + ASSERT_EQ("*/*", *header.GetHeader("Accept")); + + ASSERT_EQ(1, header.major_version()); + ASSERT_EQ(34, header.minor_version()); + // method is undefined for response, in our case, it's set to 0. + ASSERT_EQ(brpc::HTTP_METHOD_DELETE, header.method()); + ASSERT_EQ(brpc::HTTP_STATUS_GONE, header.status_code()); + ASSERT_STREQ(brpc::HttpReasonPhrase(header.status_code()), /*not GoneBlah*/ + header.reason_phrase()); + + ASSERT_TRUE(header.GetHeader("log-id")); + ASSERT_EQ("456", *header.GetHeader("log-id")); + ASSERT_TRUE(header.GetHeader("Authorization")); + ASSERT_EQ("test", *header.GetHeader("Authorization")); +} + +TEST(HttpMessageTest, bad_format) { + const char *http_request = + "slkdjflksdf skldjf\r\n"; + brpc::HttpMessage http_message; + ASSERT_EQ(-1, http_message.ParseFromArray(http_request, strlen(http_request))); +} + +TEST(HttpMessageTest, incompleted_request_line) { + const char *http_request = "GE" ; + brpc::HttpMessage http_message; + ASSERT_TRUE(http_message.ParseFromArray(http_request, strlen(http_request)) >= 0); + ASSERT_FALSE(http_message.Completed()); +} + +TEST(HttpMessageTest, parse_from_iobuf) { + const size_t content_length = 8192; + char header[1024]; + snprintf(header, sizeof(header), + "GET /service/method?key1=value1&key2=value2&key3=value3 HTTP/1.1\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: %lu\r\n" + "\r\n", + content_length); + butil::IOBuf content; + for (size_t i = 0; i < content_length; ++i) { + content.push_back('2'); + } + butil::IOBuf request; + request.append(header); + request.append(content); + + brpc::HttpMessage http_message; + ASSERT_TRUE(http_message.ParseFromIOBuf(request) >= 0); + ASSERT_TRUE(http_message.Completed()); + ASSERT_EQ(content, http_message.body()); + ASSERT_EQ(content, http_message.body().to_string()); + ASSERT_EQ("text/plain", http_message.header().content_type()); +} + + +TEST(HttpMessageTest, parse_http_head_response) { + char response1[1024] = "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: 1024\r\n" + "\r\n"; + butil::IOBuf request; + request.append(response1); + + brpc::HttpMessage http_message(false, brpc::HTTP_METHOD_HEAD); + ASSERT_TRUE(http_message.ParseFromIOBuf(request) >= 0); + ASSERT_TRUE(http_message.Completed()) << http_message.stage(); + ASSERT_EQ("text/plain", http_message.header().content_type()); + const std::string* content_length = http_message.header().GetHeader("Content-Length"); + ASSERT_NE(nullptr, content_length); + ASSERT_EQ("1024", *content_length); + + + char response2[1024] = "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n"; + butil::IOBuf request2; + request2.append(response2); + brpc::HttpMessage http_message2(false, brpc::HTTP_METHOD_HEAD); + ASSERT_TRUE(http_message2.ParseFromIOBuf(request2) >= 0); + ASSERT_TRUE(http_message2.Completed()) << http_message2.stage(); + ASSERT_EQ("text/plain", http_message2.header().content_type()); + const std::string* transfer_encoding = http_message2.header().GetHeader("Transfer-Encoding"); + ASSERT_NE(nullptr, transfer_encoding); + ASSERT_EQ("chunked", *transfer_encoding); +} + +TEST(HttpMessageTest, parse_http_cookie) { + const char* http_request = + "GET /CloudApiControl HTTP/1.1\r\n" + "Host: api.map.baidu.com\r\n" + "Accept: application/json\r\n" + "cookie: a=1\r\n" + "Cookie: b=2\r\n" + "\r\n"; + butil::IOBuf buf; + buf.append(http_request); + brpc::HttpMessage http_message; + ASSERT_EQ((ssize_t)buf.size(), http_message.ParseFromIOBuf(buf)); + ASSERT_TRUE(http_message.Completed()); + + const std::string* cookie + = http_message.header().GetHeader("cookie"); + ASSERT_NE(nullptr, cookie); + ASSERT_EQ("a=1; b=2", *cookie); +} + +TEST(HttpMessageTest, parse_http_set_cookie) { + char response[1024] = "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: 1024\r\n" + "set-cookie: a=1\r\n" + "Set-Cookie: b=2\r\n" + "\r\n"; + butil::IOBuf request; + request.append(response); + brpc::HttpMessage http_message(false, brpc::HTTP_METHOD_HEAD); + ASSERT_TRUE(http_message.ParseFromIOBuf(request) >= 0); + ASSERT_TRUE(http_message.Completed()) << http_message.stage(); + + const std::string* set_cookie = http_message.header().GetHeader("set-cookie"); + ASSERT_NE(nullptr, set_cookie); + ASSERT_EQ("a=1", *set_cookie); + std::vector all_set_cookie + = http_message.header().GetAllSetCookieHeader(); + for (const std::string* sc : all_set_cookie) { + ASSERT_NE(nullptr, sc); + if (set_cookie == sc) { + ASSERT_EQ("a=1", *sc); + } else { + ASSERT_EQ("b=2", *sc); + } + if (http_message.header().IsSetCookie(*sc)) { + } + } + int set_cookie_value1_count = 0; + int set_cookie_value2_count = 0; + for (auto iter = http_message.header().HeaderBegin(); + iter != http_message.header().HeaderEnd(); ++iter) { + if (!http_message.header().IsSetCookie(iter->first)) { + continue; + } + if (iter->second == "b=2") { + ++set_cookie_value2_count; + } else if (iter->second == "a=1") { + ++set_cookie_value1_count; + } + } + ASSERT_EQ(1, set_cookie_value1_count); + ASSERT_EQ(1, set_cookie_value2_count); +} + +TEST(HttpMessageTest, cl_and_te) { + // https://datatracker.ietf.org/doc/html/rfc2616#section-14.41 + // If multiple encodings have been applied to an entity, the transfer- + // codings MUST be listed in the order in which they were applied. + const char* request_buf1 = "POST /chunked_w_content_length HTTP/1.1\r\n" + "Content-Length: 10\r\n" + "Transfer-Encoding: gzip,chunked\r\n" + "\r\n" + "5; ilovew3;whattheluck=aretheseparametersfor\r\nhello\r\n" + "6; blahblah; blah\r\n world\r\n" + "0\r\n" + "\r\n"; + butil::IOBuf request1; + request1.append(request_buf1); + + const char* request_buf2 = "POST /chunked_w_content_length HTTP/1.1\r\n" + "Content-Length: 19\r\n" + "Transfer-Encoding: chunked,gzip\r\n" + "\r\n" + "Message Body sdfsdf"; + butil::IOBuf request2; + request2.append(request_buf2); + + const char* response_buf1 = "HTTP/1.1 200 OK\r\n" + "Content-Length: 10\r\n" + "Transfer-Encoding: gzip,chunked\r\n" + "\r\n" + "5; ilovew3;whattheluck=aretheseparametersfor\r\nhello\r\n" + "6; blahblah; blah\r\n world\r\n" + "0\r\n" + "\r\n"; + butil::IOBuf response1; + response1.append(response_buf1); + + const char* response_buf2 = "HTTP/1.1 200 OK\r\n" + "Content-Length: 19\r\n" + "Transfer-Encoding: chunked,gzip\r\n" + "\r\n" + "Message Body sdfsdf"; + butil::IOBuf response2; + response2.append(response_buf2); + + brpc::FLAGS_allow_chunked_length = false; + { + brpc::HttpMessage http_message; + ASSERT_EQ(http_message.ParseFromIOBuf(request1), -1) + << http_message._parser; + } + { + brpc::HttpMessage http_message; + ASSERT_EQ(http_message.ParseFromIOBuf(request2), -1) + << http_message._parser; + } + { + brpc::HttpMessage http_message; + ASSERT_EQ(http_message.ParseFromIOBuf(response1), -1) + << http_message._parser; + } + { + brpc::HttpMessage http_message; + ASSERT_EQ(http_message.ParseFromIOBuf(response2), -1) + << http_message._parser; + } + + brpc::FLAGS_allow_chunked_length = true; + { + brpc::HttpMessage http_message; + ASSERT_EQ(http_message.ParseFromIOBuf(request1), request1.size()) + << http_message._parser; + } + { + brpc::HttpMessage http_message; + ASSERT_EQ(http_message.ParseFromIOBuf(request2), -1) + << http_message._parser; + } + { + brpc::HttpMessage http_message; + ASSERT_EQ(http_message.ParseFromIOBuf(response1), response1.size()) + << http_message._parser; + } + { + brpc::HttpMessage http_message; + ASSERT_EQ(http_message.ParseFromIOBuf(response2), -1) + << http_message._parser; + } +} + +TEST(HttpMessageTest, find_method_property_by_uri) { + brpc::Server server; + ASSERT_EQ(0, server.AddService(new test::EchoService(), + brpc::SERVER_OWNS_SERVICE)); + ASSERT_EQ(0, server.Start(9237, NULL)); + std::string unknown_method; + brpc::Server::MethodProperty* mp = NULL; + + mp = FindMethodPropertyByURI("", &server, NULL); + ASSERT_TRUE(mp); + ASSERT_EQ("index", mp->method->service()->name()); + + mp = FindMethodPropertyByURI("/", &server, NULL); + ASSERT_TRUE(mp); + ASSERT_EQ("index", mp->method->service()->name()); + + mp = FindMethodPropertyByURI("//", &server, NULL); + ASSERT_TRUE(mp); + ASSERT_EQ("index", mp->method->service()->name()); + + mp = FindMethodPropertyByURI("flags", &server, &unknown_method); + ASSERT_TRUE(mp); + ASSERT_EQ("flags", mp->method->service()->name()); + + mp = FindMethodPropertyByURI("/flags/port", &server, &unknown_method); + ASSERT_TRUE(mp); + ASSERT_EQ("flags", mp->method->service()->name()); + ASSERT_EQ("port", unknown_method); + + mp = FindMethodPropertyByURI("/flags/foo/bar", &server, &unknown_method); + ASSERT_TRUE(mp); + ASSERT_EQ("flags", mp->method->service()->name()); + ASSERT_EQ("foo/bar", unknown_method); + + mp = FindMethodPropertyByURI("/brpc.flags/$*", + &server, &unknown_method); + ASSERT_TRUE(mp); + ASSERT_EQ("flags", mp->method->service()->name()); + ASSERT_EQ("$*", unknown_method); + + mp = FindMethodPropertyByURI("EchoService/Echo", &server, &unknown_method); + ASSERT_TRUE(mp); + ASSERT_EQ("test.EchoService.Echo", mp->method->full_name()); + + mp = FindMethodPropertyByURI("/EchoService/Echo", + &server, &unknown_method); + ASSERT_TRUE(mp); + ASSERT_EQ("test.EchoService.Echo", mp->method->full_name()); + + mp = FindMethodPropertyByURI("/test.EchoService/Echo", + &server, &unknown_method); + ASSERT_TRUE(mp); + ASSERT_EQ("test.EchoService.Echo", mp->method->full_name()); + + mp = FindMethodPropertyByURI("/test.EchoService/no_such_method", + &server, &unknown_method); + ASSERT_FALSE(mp); +} + +TEST(HttpMessageTest, http_header) { + brpc::HttpHeader header; + + header.set_version(10, 100); + ASSERT_EQ(10, header.major_version()); + ASSERT_EQ(100, header.minor_version()); + + ASSERT_TRUE(header.content_type().empty()); + header.set_content_type("text/plain"); + ASSERT_EQ("text/plain", header.content_type()); + ASSERT_FALSE(header.GetHeader("content-type")); + header.set_content_type("application/json"); + ASSERT_EQ("application/json", header.content_type()); + ASSERT_FALSE(header.GetHeader("content-type")); + + ASSERT_FALSE(header.GetHeader("key1")); + header.AppendHeader("key1", "value1"); + const std::string* value = header.GetHeader("key1"); + ASSERT_TRUE(value && *value == "value1"); + header.AppendHeader("key1", "value2"); + value = header.GetHeader("key1"); + ASSERT_TRUE(value && *value == "value1,value2"); + header.SetHeader("key1", "value3"); + value = header.GetHeader("key1"); + ASSERT_TRUE(value && *value == "value3"); + header.RemoveHeader("key1"); + ASSERT_FALSE(header.GetHeader("key1")); + + ASSERT_FALSE(header.GetHeader(brpc::HttpHeader::COOKIE)); + header.AppendHeader(brpc::HttpHeader::COOKIE, "value1=1"); + value = header.GetHeader(brpc::HttpHeader::COOKIE); + ASSERT_TRUE(value && *value == "value1=1"); + header.AppendHeader(brpc::HttpHeader::COOKIE, "value2=2"); + value = header.GetHeader(brpc::HttpHeader::COOKIE); + ASSERT_TRUE(value && *value == "value1=1; value2=2"); + header.SetHeader(brpc::HttpHeader::COOKIE, "value3"); + value = header.GetHeader(brpc::HttpHeader::COOKIE); + ASSERT_TRUE(value && *value == "value3"); + header.RemoveHeader(brpc::HttpHeader::COOKIE); + ASSERT_FALSE(header.GetHeader(brpc::HttpHeader::COOKIE)); + + std::string set_cookie_value1 = "a=1"; + std::string set_cookie_value2 = "b=2"; + std::string set_cookie_value3 = "c=3"; + ASSERT_FALSE(header.GetHeader(brpc::HttpHeader::SET_COOKIE)); + header.SetHeader(brpc::HttpHeader::SET_COOKIE, set_cookie_value1); + value = header.GetHeader(brpc::HttpHeader::SET_COOKIE); + ASSERT_TRUE(value && *value == set_cookie_value1); + header.AppendHeader(brpc::HttpHeader::SET_COOKIE, set_cookie_value2); + value = header.GetHeader(brpc::HttpHeader::SET_COOKIE); + ASSERT_TRUE(value && *value == set_cookie_value1); + header.SetHeader(brpc::HttpHeader::SET_COOKIE, set_cookie_value3); + value = header.GetHeader(brpc::HttpHeader::SET_COOKIE); + ASSERT_TRUE(value && *value == set_cookie_value3); + std::vector all_set_cookie + = header.GetAllSetCookieHeader(); + ASSERT_EQ(2u, all_set_cookie.size()); + for (const std::string* sc : all_set_cookie) { + ASSERT_TRUE(sc); + ASSERT_TRUE(*sc == set_cookie_value2 || *sc == set_cookie_value3); + } + int set_cookie_value2_count = 0; + int set_cookie_value3_count = 0; + for (auto iter = header.HeaderBegin(); iter != header.HeaderEnd(); ++iter) { + if (!header.IsSetCookie(brpc::HttpHeader::SET_COOKIE)) { + continue; + } + if (iter->second == set_cookie_value2) { + ++set_cookie_value2_count; + } else if (iter->second == set_cookie_value3) { + ++set_cookie_value3_count; + } + } + ASSERT_EQ(1, set_cookie_value2_count); + ASSERT_EQ(1, set_cookie_value3_count); + header.RemoveHeader(brpc::HttpHeader::SET_COOKIE); + ASSERT_FALSE(header.GetHeader(brpc::HttpHeader::SET_COOKIE)); + ASSERT_EQ(header._first_set_cookie, nullptr); + + ASSERT_EQ(brpc::HTTP_METHOD_GET, header.method()); + header.set_method(brpc::HTTP_METHOD_POST); + ASSERT_EQ(brpc::HTTP_METHOD_POST, header.method()); + + ASSERT_EQ(brpc::HTTP_STATUS_OK, header.status_code()); + ASSERT_STREQ(brpc::HttpReasonPhrase(header.status_code()), + header.reason_phrase()); + header.set_status_code(brpc::HTTP_STATUS_CONTINUE); + ASSERT_EQ(brpc::HTTP_STATUS_CONTINUE, header.status_code()); + ASSERT_STREQ(brpc::HttpReasonPhrase(header.status_code()), + header.reason_phrase()); + + header.set_status_code(brpc::HTTP_STATUS_GONE); + ASSERT_EQ(brpc::HTTP_STATUS_GONE, header.status_code()); + ASSERT_STREQ(brpc::HttpReasonPhrase(header.status_code()), + header.reason_phrase()); +} + +TEST(HttpMessageTest, empty_url) { + butil::EndPoint host; + ASSERT_FALSE(ParseHttpServerAddress(&host, "")); +} + +TEST(HttpMessageTest, serialize_http_request) { + brpc::HttpHeader header; + ASSERT_EQ(0u, header.HeaderCount()); + header.SetHeader("Foo", "Bar"); + ASSERT_EQ(1u, header.HeaderCount()); + header.set_method(brpc::HTTP_METHOD_POST); + butil::EndPoint ep; + ASSERT_EQ(0, butil::str2endpoint("127.0.0.1:1234", &ep)); + butil::IOBuf request; + butil::IOBuf content; + content.append("data"); + MakeRawHttpRequest(&request, &header, ep, &content); + ASSERT_EQ("POST / HTTP/1.1\r\nContent-Length: 4\r\nHost: 127.0.0.1:1234\r\nFoo: Bar\r\nAccept: */*\r\nUser-Agent: brpc/1.0 curl/7.0\r\n\r\ndata", request); + + // user-set content-length is ignored. + header.SetHeader("Content-Length", "100"); + MakeRawHttpRequest(&request, &header, ep, &content); + ASSERT_EQ("POST / HTTP/1.1\r\nContent-Length: 4\r\nHost: 127.0.0.1:1234\r\nFoo: Bar\r\nAccept: */*\r\nUser-Agent: brpc/1.0 curl/7.0\r\n\r\ndata", request); + + // user-host overwrites passed-in remote_side + header.SetHeader("Host", "MyHost: 4321"); + MakeRawHttpRequest(&request, &header, ep, &content); + ASSERT_EQ("POST / HTTP/1.1\r\nContent-Length: 4\r\nFoo: Bar\r\nHost: MyHost: 4321\r\nAccept: */*\r\nUser-Agent: brpc/1.0 curl/7.0\r\n\r\ndata", request); + + // user-set accept + header.SetHeader("accePT"/*intended uppercase*/, "blahblah"); + MakeRawHttpRequest(&request, &header, ep, &content); + ASSERT_EQ("POST / HTTP/1.1\r\nContent-Length: 4\r\nFoo: Bar\r\naccePT: blahblah\r\nHost: MyHost: 4321\r\nUser-Agent: brpc/1.0 curl/7.0\r\n\r\ndata", request); + + // user-set UA + header.SetHeader("user-AGENT", "myUA"); + MakeRawHttpRequest(&request, &header, ep, &content); + ASSERT_EQ("POST / HTTP/1.1\r\nContent-Length: 4\r\nFoo: Bar\r\naccePT: blahblah\r\nHost: MyHost: 4321\r\nuser-AGENT: myUA\r\n\r\ndata", request); + + // user-set Authorization + header.SetHeader("authorization", "myAuthString"); + MakeRawHttpRequest(&request, &header, ep, &content); + ASSERT_EQ("POST / HTTP/1.1\r\nContent-Length: 4\r\nFoo: Bar\r\naccePT: blahblah\r\nHost: MyHost: 4321\r\nuser-AGENT: myUA\r\nauthorization: myAuthString\r\n\r\ndata", request); + + header.SetHeader("Transfer-Encoding", "chunked"); + MakeRawHttpRequest(&request, &header, ep, &content); + ASSERT_EQ("POST / HTTP/1.1\r\nFoo: Bar\r\naccePT: blahblah\r\nTransfer-Encoding: chunked\r\nHost: MyHost: 4321\r\nuser-AGENT: myUA\r\nauthorization: myAuthString\r\n\r\ndata", request); + + // GET does not serialize content and user-set content-length is ignored. + header.set_method(brpc::HTTP_METHOD_GET); + header.SetHeader("Content-Length", "100"); + MakeRawHttpRequest(&request, &header, ep, &content); + ASSERT_EQ("GET / HTTP/1.1\r\nFoo: Bar\r\naccePT: blahblah\r\nHost: MyHost: 4321\r\nuser-AGENT: myUA\r\nauthorization: myAuthString\r\n\r\n", request); +} + +TEST(HttpMessageTest, serialize_http_response) { + brpc::HttpHeader header; + header.SetHeader("Foo", "Bar"); + header.set_method(brpc::HTTP_METHOD_POST); + butil::IOBuf response; + butil::IOBuf content; + content.append("data"); + MakeRawHttpResponse(&response, &header, &content); + ASSERT_EQ("HTTP/1.1 200 OK\r\nContent-Length: 4\r\nFoo: Bar\r\n\r\ndata", response) + << butil::ToPrintable(response); + // Content is cleared. + CHECK(content.empty()); + + // NULL content + header.SetHeader("Content-Length", "100"); + MakeRawHttpResponse(&response, &header, NULL); + ASSERT_EQ("HTTP/1.1 200 OK\r\nFoo: Bar\r\nContent-Length: 100\r\n\r\n", response) + << butil::ToPrintable(response); + + header.SetHeader("Transfer-Encoding", "chunked"); + MakeRawHttpResponse(&response, &header, NULL); + ASSERT_EQ("HTTP/1.1 200 OK\r\nFoo: Bar\r\nTransfer-Encoding: chunked\r\n\r\n", response) + << butil::ToPrintable(response); + header.RemoveHeader("Transfer-Encoding"); + + // User-set content-length is ignored. + content.append("data2"); + MakeRawHttpResponse(&response, &header, &content); + ASSERT_EQ("HTTP/1.1 200 OK\r\nContent-Length: 5\r\nFoo: Bar\r\n\r\ndata2", response) + << butil::ToPrintable(response); + + header.SetHeader("Content-Length", "100"); + header.SetHeader("Transfer-Encoding", "chunked"); + MakeRawHttpResponse(&response, &header, NULL); + ASSERT_EQ("HTTP/1.1 200 OK\r\nFoo: Bar\r\nTransfer-Encoding: chunked\r\n\r\n", response) + << butil::ToPrintable(response); + header.RemoveHeader("Transfer-Encoding"); + + // User-set content-length and transfer-encoding is ignored when status code is 204 or 1xx. + // 204 No Content. + header.SetHeader("Content-Length", "100"); + header.SetHeader("Transfer-Encoding", "chunked"); + header.set_status_code(brpc::HTTP_STATUS_NO_CONTENT); + MakeRawHttpResponse(&response, &header, &content); + ASSERT_EQ("HTTP/1.1 204 No Content\r\nFoo: Bar\r\n\r\n", response); + // 101 Continue + header.SetHeader("Content-Length", "100"); + header.SetHeader("Transfer-Encoding", "chunked"); + header.set_status_code(brpc::HTTP_STATUS_CONTINUE); + MakeRawHttpResponse(&response, &header, &content); + ASSERT_EQ("HTTP/1.1 100 Continue\r\nFoo: Bar\r\n\r\n", response) + << butil::ToPrintable(response); + + // when request method is HEAD: + // 1. There isn't user-set content-length, length of content is used. + header.set_method(brpc::HTTP_METHOD_HEAD); + header.set_status_code(brpc::HTTP_STATUS_OK);content.append("data2"); + MakeRawHttpResponse(&response, &header, &content); + ASSERT_EQ("HTTP/1.1 200 OK\r\nContent-Length: 5\r\nFoo: Bar\r\n\r\n", response) + << butil::ToPrintable(response); + // 2. User-set content-length is not ignored . + header.SetHeader("Content-Length", "100"); + MakeRawHttpResponse(&response, &header, &content); + ASSERT_EQ("HTTP/1.1 200 OK\r\nFoo: Bar\r\nContent-Length: 100\r\n\r\n", response) + << butil::ToPrintable(response); +} + +TEST(HttpMessageTest, http_1_1_request_without_host) { + brpc::FLAGS_allow_http_1_1_request_without_host = false; + { + butil::IOBuf request; + request.append("GET /service/method HTTP/1.1\r\n" + "Content-Type: text/plain\r\n\r\n"); + + brpc::HttpMessage http_message; + ASSERT_TRUE(http_message.ParseFromIOBuf(request) < 0); + } + { + butil::IOBuf request; + request.append("GET http://baidu.com/service/method HTTP/1.1\r\n" + "Content-Type: text/plain\r\n\r\n"); + + brpc::HttpMessage http_message; + ASSERT_TRUE(http_message.ParseFromIOBuf(request) >= 0); + ASSERT_TRUE(http_message.Completed()); + ASSERT_EQ("text/plain", http_message.header().content_type()); + } + { + butil::IOBuf request; + request.append("GET /service/method HTTP/1.1\r\n" + "Content-Type: text/plain\r\n" + "Host: baidu.com\r\n\r\n"); + + brpc::HttpMessage http_message; + ASSERT_GE(http_message.ParseFromIOBuf(request), 0); + ASSERT_GE(http_message.ParseFromArray(NULL, 0), 0); + ASSERT_TRUE(http_message.Completed()); + ASSERT_EQ("text/plain", http_message.header().content_type()); + } + brpc::FLAGS_allow_http_1_1_request_without_host = true; +} + +} //namespace diff --git a/test/brpc_http_parser_unittest.cpp b/test/brpc_http_parser_unittest.cpp new file mode 100644 index 0000000..49ee06e --- /dev/null +++ b/test/brpc_http_parser_unittest.cpp @@ -0,0 +1,171 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include + +#include "butil/time.h" +#include "butil/logging.h" +#include "brpc/details/http_parser.h" +#include "brpc/builtin/common.h" // AppendFileName + +using brpc::http_parser; +using brpc::http_parser_init; +using brpc::http_parser_settings; + +class HttpParserTest : public testing::Test { +protected: + void SetUp() {} + void TearDown() {} +}; + +TEST_F(HttpParserTest, init_perf) { + const size_t loops = 10000000; + butil::Timer timer; + timer.start(); + for (size_t i = 0; i < loops; ++i) { + http_parser parser; + http_parser_init(&parser, brpc::HTTP_REQUEST); + } + timer.stop(); + std::cout << "It takes " << timer.n_elapsed() / loops + << "ns to init a http_parser" + << std::endl; +} + +int on_message_begin(http_parser *) { + LOG(INFO) << "Start parsing message"; + return 0; +} + +int on_url(http_parser *, const char *at, const size_t length) { + LOG(INFO) << "Get url " << std::string(at, length); + return 0; +} + +int on_headers_complete(http_parser *) { + LOG(INFO) << "Header complete"; + return 0; +} + +int on_message_complete(http_parser *) { + LOG(INFO) << "Message complete"; + return 0; +} + +int on_header_field(http_parser *, const char *at, const size_t length) { + LOG(INFO) << "Get header field " << std::string(at, length); + return 0; +} + +int on_header_value(http_parser *, const char *at, const size_t length) { + LOG(INFO) << "Get header value " << std::string(at, length); + return 0; +} + +int on_body(http_parser *, const char *at, const size_t length) { + LOG(INFO) << "Get body " << std::string(at, length); + return 0; +} + +TEST_F(HttpParserTest, http_example) { + const char *http_request = + "GET /path/file.html?sdfsdf=sdfs HTTP/1.0\r\n" + "From: someuser@jmarshall.com\r\n" + "User-Agent: HTTPTool/1.0\r\n" + "Content-Type: json\r\n" + "Content-Length: 19\r\n" + "Host: sdlfjslfd\r\n" + "Accept: */*\r\n" + "\r\n" + "Message Body sdfsdf\r\n" + ; + std::cout << http_request << std::endl; + + http_parser parser; + http_parser_init(&parser, brpc::HTTP_REQUEST); + http_parser_settings settings; + memset(&settings, 0, sizeof(settings)); + settings.on_message_begin = on_message_begin; + settings.on_url = on_url; + settings.on_headers_complete = on_headers_complete; + settings.on_message_complete = on_message_complete; + settings.on_header_field = on_header_field; + settings.on_header_value = on_header_value; + settings.on_body = on_body; + LOG(INFO) << http_parser_execute(&parser, &settings, http_request, strlen(http_request)); +} + +TEST_F(HttpParserTest, append_filename) { + std::string dir; + + dir = "/home/someone/.bsvn/.."; + brpc::AppendFileName(&dir, ".."); + ASSERT_EQ("/home", dir); + + dir = "/home/someone/.bsvn/../"; + brpc::AppendFileName(&dir, ".."); + ASSERT_EQ("/home", dir); + + dir = "/home/someone/./.."; + brpc::AppendFileName(&dir, ".."); + ASSERT_EQ("/", dir); + + dir = "/home/someone/./../"; + brpc::AppendFileName(&dir, ".."); + ASSERT_EQ("/", dir); + + dir = "/foo/bar"; + brpc::AppendFileName(&dir, ".."); + ASSERT_EQ("/foo", dir); + + dir = "/foo/bar/"; + brpc::AppendFileName(&dir, ".."); + ASSERT_EQ("/foo", dir); + + dir = "/foo"; + brpc::AppendFileName(&dir, "."); + ASSERT_EQ("/foo", dir); + + dir = "/foo/"; + brpc::AppendFileName(&dir, "."); + ASSERT_EQ("/foo/", dir); + + dir = "foo"; + brpc::AppendFileName(&dir, ".."); + ASSERT_EQ("", dir); + + dir = "foo/"; + brpc::AppendFileName(&dir, ".."); + ASSERT_EQ("", dir); + + dir = "foo/.."; + brpc::AppendFileName(&dir, ".."); + ASSERT_EQ("..", dir); + + dir = "foo/../"; + brpc::AppendFileName(&dir, ".."); + ASSERT_EQ("..", dir); + + dir = "/foo"; + brpc::AppendFileName(&dir, ".."); + ASSERT_EQ("/", dir); + + dir = "/foo/"; + brpc::AppendFileName(&dir, ".."); + ASSERT_EQ("/", dir); +} diff --git a/test/brpc_http_rpc_protocol_unittest.cpp b/test/brpc_http_rpc_protocol_unittest.cpp new file mode 100644 index 0000000..a75a122 --- /dev/null +++ b/test/brpc_http_rpc_protocol_unittest.cpp @@ -0,0 +1,2424 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: Sun Jul 13 15:04:18 CST 2014 + +#include +#include +#include +#include +#include // OS_MACOSX +#if defined(OS_MACOSX) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include "brpc/http_method.h" +#include "butil/iobuf.h" +#include "butil/logging.h" +#include "butil/files/scoped_file.h" +#include "butil/fd_guard.h" +#include "butil/file_util.h" +#include "brpc/socket.h" +#include "brpc/acceptor.h" +#include "brpc/server.h" +#include "brpc/channel.h" +#include "brpc/policy/most_common_message.h" +#include "echo.pb.h" +#include "brpc/policy/http_rpc_protocol.h" +#include "brpc/policy/http2_rpc_protocol.h" +#include "json2pb/pb_to_json.h" +#include "json2pb/json_to_pb.h" +#include "brpc/details/method_status.h" +#include "brpc/rpc_dump.h" +#include "bthread/unstable.h" + +namespace brpc { +DECLARE_bool(rpc_dump); +DECLARE_string(rpc_dump_dir); +DECLARE_int32(rpc_dump_max_requests_in_one_file); +DECLARE_bool(allow_chunked_length); +DECLARE_int32(max_connection_pool_size); +DECLARE_uint64(max_body_size); +extern bvar::CollectorSpeedLimit g_rpc_dump_sl; +} + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + if (GFLAGS_NAMESPACE::SetCommandLineOption("socket_max_unwritten_bytes", "2000000").empty()) { + std::cerr << "Fail to set -socket_max_unwritten_bytes" << std::endl; + return -1; + } + if (GFLAGS_NAMESPACE::SetCommandLineOption("crash_on_fatal_log", "true").empty()) { + std::cerr << "Fail to set -crash_on_fatal_log" << std::endl; + return -1; + } + return RUN_ALL_TESTS(); +} + +namespace { + +static const std::string EXP_REQUEST = "hello"; +static const std::string EXP_RESPONSE = "world"; +static const std::string EXP_RESPONSE_CONTENT_LENGTH = "1024"; +static const std::string EXP_RESPONSE_TRANSFER_ENCODING = "chunked"; + +static const std::string MOCK_CREDENTIAL = "mock credential"; +static const std::string MOCK_USER = "mock user"; + +class MyAuthenticator : public brpc::Authenticator { +public: + MyAuthenticator() {} + + int GenerateCredential(std::string* auth_str) const { + *auth_str = MOCK_CREDENTIAL; + return 0; + } + + int VerifyCredential(const std::string& auth_str, + const butil::EndPoint&, + brpc::AuthContext* ctx) const { + EXPECT_EQ(MOCK_CREDENTIAL, auth_str); + ctx->set_user(MOCK_USER); + return 0; + } +}; + +class MyEchoService : public ::test::EchoService { +public: + void Echo(::google::protobuf::RpcController* cntl_base, + const ::test::EchoRequest* req, + ::test::EchoResponse* res, + ::google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = + static_cast(cntl_base); + const std::string* sleep_ms_str = + cntl->http_request().uri().GetQuery("sleep_ms"); + if (sleep_ms_str) { + bthread_usleep(strtol(sleep_ms_str->data(), NULL, 10) * 1000); + } + res->set_message(EXP_RESPONSE); + } +}; + +class HttpTest : public ::testing::Test{ +protected: + HttpTest() { + EXPECT_EQ(0, _server.AddBuiltinServices()); + EXPECT_EQ(0, _server.AddService( + &_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + // Hack: Regard `_server' as running + _server._status = brpc::Server::RUNNING; + _server._options.auth = &_auth; + if (!_server._options.rpc_pb_message_factory) { + _server._options.rpc_pb_message_factory = new brpc::DefaultRpcPBMessageFactory(); + } + + EXPECT_EQ(0, pipe(_pipe_fds)); + + brpc::SocketId id; + brpc::SocketOptions options; + options.fd = _pipe_fds[1]; + EXPECT_EQ(0, brpc::Socket::Create(options, &id)); + EXPECT_EQ(0, brpc::Socket::Address(id, &_socket)); + + brpc::SocketOptions h2_client_options; + h2_client_options.user = brpc::get_client_side_messenger(); + h2_client_options.fd = _pipe_fds[1]; + EXPECT_EQ(0, brpc::Socket::Create(h2_client_options, &id)); + EXPECT_EQ(0, brpc::Socket::Address(id, &_h2_client_sock)); + }; + + virtual ~HttpTest() {}; + virtual void SetUp() {}; + virtual void TearDown() {}; + + void VerifyMessage(brpc::InputMessageBase* msg, bool expect) { + if (msg->_socket == NULL) { + _socket->ReAddress(&msg->_socket); + } + msg->_arg = &_server; + EXPECT_EQ(expect, brpc::policy::VerifyHttpRequest(msg)); + } + + void VerifyMessageFromLocalPort(brpc::InputMessageBase* msg, + bool expect, + int local_port) { + brpc::SocketId id; + brpc::SocketOptions options; + options.fd = dup(_pipe_fds[1]); + EXPECT_GE(options.fd, 0); + options.local_side = butil::EndPoint(butil::my_ip(), local_port); + EXPECT_EQ(0, brpc::Socket::Create(options, &id)); + + brpc::SocketUniquePtr socket; + EXPECT_EQ(0, brpc::Socket::Address(id, &socket)); + socket->ReAddress(&msg->_socket); + msg->_arg = &_server; + EXPECT_EQ(expect, brpc::policy::VerifyHttpRequest(msg)); + } + + void ProcessMessage(void (*process)(brpc::InputMessageBase*), + brpc::InputMessageBase* msg, bool set_eof) { + if (msg->_socket == NULL) { + _socket->ReAddress(&msg->_socket); + } + msg->_arg = &_server; + _socket->PostponeEOF(); + if (set_eof) { + _socket->SetEOF(); + } + (*process)(msg); + } + + brpc::policy::HttpContext* MakePostRequestMessage(const std::string& path) { + brpc::policy::HttpContext* msg = new brpc::policy::HttpContext(false); + msg->header().uri().set_path(path); + msg->header().set_content_type("application/json"); + msg->header().set_method(brpc::HTTP_METHOD_POST); + + test::EchoRequest req; + req.set_message(EXP_REQUEST); + butil::IOBufAsZeroCopyOutputStream req_stream(&msg->body()); + EXPECT_TRUE(json2pb::ProtoMessageToJson(req, &req_stream, NULL)); + return msg; + } + + brpc::policy::HttpContext* MakePostProtoJsonRequestMessage(const std::string& path) { + brpc::policy::HttpContext* msg = new brpc::policy::HttpContext(false); + msg->header().uri().set_path(path); + msg->header().set_content_type("application/proto-json"); + msg->header().set_method(brpc::HTTP_METHOD_POST); + + test::EchoRequest req; + req.set_message(EXP_REQUEST); + butil::IOBufAsZeroCopyOutputStream req_stream(&msg->body()); + json2pb::Pb2ProtoJsonOptions options; + std::string error; + EXPECT_TRUE(json2pb::ProtoMessageToProtoJson(req, &req_stream, options, &error)) << error; + return msg; + } + + brpc::policy::HttpContext* MakePostProtoTextRequestMessage( + const std::string& path) { + brpc::policy::HttpContext* msg = new brpc::policy::HttpContext(false); + msg->header().uri().set_path(path); + msg->header().set_content_type("application/proto-text"); + msg->header().set_method(brpc::HTTP_METHOD_POST); + + test::EchoRequest req; + req.set_message(EXP_REQUEST); + butil::IOBufAsZeroCopyOutputStream req_stream(&msg->body()); + EXPECT_TRUE(google::protobuf::TextFormat::Print(req, &req_stream)); + return msg; + } + + brpc::policy::HttpContext* MakeGetRequestMessage(const std::string& path) { + brpc::policy::HttpContext* msg = new brpc::policy::HttpContext(false); + msg->header().uri().set_path(path); + msg->header().set_method(brpc::HTTP_METHOD_GET); + return msg; + } + + void InitHttpPooledChannel(brpc::Channel* channel, + const butil::EndPoint& ep, + const std::string& connection_group) { + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_HTTP; + options.connection_type = brpc::CONNECTION_TYPE_POOLED; + options.connection_group = connection_group; + options.max_retry = 0; + ASSERT_EQ(0, channel->Init(ep, &options)); + } + + void CallVersion(brpc::Channel* channel, brpc::Controller* cntl) { + cntl->http_request().uri() = "/status"; + cntl->http_request().set_method(brpc::HTTP_METHOD_GET); + channel->CallMethod(NULL, cntl, NULL, NULL, NULL); + } + + void CallHttpEcho(brpc::Channel* channel, brpc::Controller* cntl) { + test::EchoRequest req; + test::EchoResponse res; + req.set_message(EXP_REQUEST); + cntl->http_request().uri() = "/EchoService/Echo"; + cntl->http_request().set_method(brpc::HTTP_METHOD_POST); + cntl->http_request().set_content_type("application/json"); + channel->CallMethod(NULL, cntl, &req, &res, NULL); + } + + + brpc::policy::HttpContext* MakeResponseMessage(int code) { + brpc::policy::HttpContext* msg = new brpc::policy::HttpContext(false); + msg->header().set_status_code(code); + msg->header().set_content_type("application/json"); + + test::EchoResponse res; + res.set_message(EXP_RESPONSE); + butil::IOBufAsZeroCopyOutputStream res_stream(&msg->body()); + EXPECT_TRUE(json2pb::ProtoMessageToJson(res, &res_stream, NULL)); + return msg; + } + + void CheckResponseCode(bool expect_empty, int expect_code) { + int bytes_in_pipe = 0; + ioctl(_pipe_fds[0], FIONREAD, &bytes_in_pipe); + if (expect_empty) { + EXPECT_EQ(0, bytes_in_pipe); + return; + } + + EXPECT_GT(bytes_in_pipe, 0); + butil::IOPortal buf; + EXPECT_EQ((ssize_t)bytes_in_pipe, + buf.append_from_file_descriptor(_pipe_fds[0], 1024)); + brpc::ParseResult pr = + brpc::policy::ParseHttpMessage(&buf, _socket.get(), false, NULL); + EXPECT_EQ(brpc::PARSE_OK, pr.error()); + brpc::policy::HttpContext* msg = + static_cast(pr.message()); + + EXPECT_EQ(expect_code, msg->header().status_code()); + msg->Destroy(); + } + + void MakeH2EchoRequestBuf(butil::IOBuf* out, brpc::Controller* cntl, int* h2_stream_id) { + butil::IOBuf request_buf; + test::EchoRequest req; + req.set_message(EXP_REQUEST); + cntl->http_request().set_method(brpc::HTTP_METHOD_POST); + brpc::policy::SerializeHttpRequest(&request_buf, cntl, &req); + ASSERT_FALSE(cntl->Failed()); + brpc::policy::H2UnsentRequest* h2_req = brpc::policy::H2UnsentRequest::New(cntl); + cntl->_current_call.stream_user_data = h2_req; + brpc::SocketMessage* socket_message = NULL; + brpc::policy::PackH2Request(NULL, &socket_message, cntl->call_id().value, + NULL, cntl, request_buf, NULL); + butil::Status st = socket_message->AppendAndDestroySelf(out, _h2_client_sock.get()); + ASSERT_TRUE(st.ok()); + *h2_stream_id = h2_req->_stream_id; + } + + void MakeH2EchoResponseBuf(butil::IOBuf* out, int h2_stream_id) { + brpc::Controller cntl; + test::EchoResponse res; + res.set_message(EXP_RESPONSE); + cntl.http_request().set_content_type("application/proto"); + { + butil::IOBufAsZeroCopyOutputStream wrapper(&cntl.response_attachment()); + EXPECT_TRUE(res.SerializeToZeroCopyStream(&wrapper)); + } + brpc::policy::H2UnsentResponse* h2_res = brpc::policy::H2UnsentResponse::New(&cntl, h2_stream_id, false /*is grpc*/); + butil::Status st = h2_res->AppendAndDestroySelf(out, _h2_client_sock.get()); + ASSERT_TRUE(st.ok()); + } + + int _pipe_fds[2]; + brpc::SocketUniquePtr _socket; + brpc::SocketUniquePtr _h2_client_sock; + brpc::Server _server; + + MyEchoService _svc; + MyAuthenticator _auth; +}; + +TEST_F(HttpTest, reject_oversized_http_body) { + GFLAGS_NAMESPACE::FlagSaver flag_saver; + brpc::FLAGS_max_body_size = 4; + butil::IOBuf buf; + buf.append("POST / HTTP/1.1\r\nContent-Length: 5\r\n\r\nhello"); + + brpc::ParseResult result = + brpc::policy::ParseHttpMessage(&buf, _socket.get(), false, NULL); + EXPECT_EQ(brpc::PARSE_ERROR_NOT_ENOUGH_DATA, result.error()); + int bytes_in_pipe = 0; + ASSERT_EQ(0, ioctl(_pipe_fds[0], FIONREAD, &bytes_in_pipe)); + ASSERT_GT(bytes_in_pipe, 0); + butil::IOPortal response; + ASSERT_EQ(bytes_in_pipe, + response.append_from_file_descriptor(_pipe_fds[0], bytes_in_pipe)); + EXPECT_NE(std::string::npos, response.to_string().find(" 413 ")); +} + +TEST_F(HttpTest, reject_oversized_chunked_http_body) { + GFLAGS_NAMESPACE::FlagSaver flag_saver; + brpc::FLAGS_max_body_size = 4; + butil::IOBuf buf; + buf.append("POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" + "3\r\nabc\r\n2\r\nde\r\n0\r\n\r\n"); + + brpc::ParseResult result = + brpc::policy::ParseHttpMessage(&buf, _socket.get(), false, NULL); + EXPECT_EQ(brpc::PARSE_ERROR_NOT_ENOUGH_DATA, result.error()); + int bytes_in_pipe = 0; + ASSERT_EQ(0, ioctl(_pipe_fds[0], FIONREAD, &bytes_in_pipe)); + ASSERT_GT(bytes_in_pipe, 0); + butil::IOPortal response; + ASSERT_EQ(bytes_in_pipe, + response.append_from_file_descriptor(_pipe_fds[0], bytes_in_pipe)); + EXPECT_NE(std::string::npos, response.to_string().find(" 413 ")); +} + +int AllocateFreePortOrDie() { + butil::fd_guard fd(tcp_listen(butil::EndPoint(butil::my_ip(), 0))); + EXPECT_GE(fd, 0); + butil::EndPoint point; + EXPECT_EQ(0, butil::get_local_side(fd, &point)); + return point.port; +} + +TEST_F(HttpTest, indenting_ostream) { + std::ostringstream os1; + brpc::IndentingOStream is1(os1, 2); + brpc::IndentingOStream is2(is1, 2); + os1 << "begin1\nhello" << std::endl << "world\nend1" << std::endl; + is1 << "begin2\nhello" << std::endl << "world\nend2" << std::endl; + is2 << "begin3\nhello" << std::endl << "world\nend3" << std::endl; + ASSERT_EQ( + "begin1\nhello\nworld\nend1\nbegin2\n hello\n world\n end2\n" + " begin3\n hello\n world\n end3\n", + os1.str()); +} + +TEST_F(HttpTest, parse_http_address) { + const std::string EXP_HOSTNAME = "www.baidu.com:9876"; + butil::EndPoint EXP_ENDPOINT; + { + std::string url = "https://" + EXP_HOSTNAME; + EXPECT_TRUE(brpc::policy::ParseHttpServerAddress(&EXP_ENDPOINT, url.c_str())); + } + { + butil::EndPoint ep; + std::string url = "http://" + + std::string(endpoint2str(EXP_ENDPOINT).c_str()); + EXPECT_TRUE(brpc::policy::ParseHttpServerAddress(&ep, url.c_str())); + EXPECT_EQ(EXP_ENDPOINT, ep); + } + { + butil::EndPoint ep; + std::string url = "https://" + + std::string(butil::ip2str(EXP_ENDPOINT.ip).c_str()); + EXPECT_TRUE(brpc::policy::ParseHttpServerAddress(&ep, url.c_str())); + EXPECT_EQ(EXP_ENDPOINT.ip, ep.ip); + EXPECT_EQ(443, ep.port); + } + { + butil::EndPoint ep; + EXPECT_FALSE(brpc::policy::ParseHttpServerAddress(&ep, "invalid_url")); + } + { + butil::EndPoint ep; + EXPECT_FALSE(brpc::policy::ParseHttpServerAddress( + &ep, "https://no.such.machine:9090")); + } +} + +TEST_F(HttpTest, verify_request) { + { + brpc::policy::HttpContext* msg = + MakePostRequestMessage("/EchoService/Echo"); + VerifyMessage(msg, false); + msg->Destroy(); + } + { + brpc::policy::HttpContext* msg = MakeGetRequestMessage("/status"); + VerifyMessage(msg, false); + msg->Destroy(); + } + { + brpc::policy::HttpContext* msg = MakeGetRequestMessage("/status"); + msg->header().SetHeader("Authorization", MOCK_CREDENTIAL); + VerifyMessage(msg, true); + msg->Destroy(); + } + { + brpc::policy::HttpContext* msg = + MakePostRequestMessage("/EchoService/Echo"); + _socket->SetFailed(); + VerifyMessage(msg, false); + msg->Destroy(); + } + { + brpc::policy::HttpContext* msg = + MakePostProtoJsonRequestMessage("/EchoService/Echo"); + VerifyMessage(msg, false); + msg->Destroy(); + } + { + brpc::policy::HttpContext* msg = + MakePostProtoTextRequestMessage("/EchoService/Echo"); + VerifyMessage(msg, false); + msg->Destroy(); + } +} + +TEST_F(HttpTest, verify_builtin_request_on_internal_port) { + _server._options.internal_port = 9527; + { + brpc::policy::HttpContext* msg = MakeGetRequestMessage("/status"); + VerifyMessage(msg, false); + msg->Destroy(); + } + { + brpc::policy::HttpContext* msg = MakeGetRequestMessage("/status"); + VerifyMessageFromLocalPort(msg, true, _server._options.internal_port); + msg->Destroy(); + } +} + +TEST_F(HttpTest, builtin_auth_policy_on_public_and_internal_port) { + const int saved_max_connection_pool_size = brpc::FLAGS_max_connection_pool_size; + brpc::FLAGS_max_connection_pool_size = 1; + + butil::EndPoint ep; + ASSERT_EQ(0, str2endpoint("127.0.0.1:0", &ep)); + + brpc::Server server; + MyEchoService svc; + MyAuthenticator auth; + brpc::ServerOptions options; + options.auth = &auth; + options.internal_port = AllocateFreePortOrDie(); + ASSERT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start(ep, &options)); + ep = server.listen_address(); + const butil::EndPoint internal_ep(ep.ip, options.internal_port); + + { + brpc::Channel chan; + brpc::ChannelOptions copt; + copt.protocol = brpc::PROTOCOL_HTTP; + copt.max_retry = 0; + ASSERT_EQ(0, chan.Init(ep, &copt)); + + brpc::Controller cntl; + cntl.http_request().uri() = "/status"; + cntl.http_request().set_method(brpc::HTTP_METHOD_GET); + chan.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_TRUE(cntl.Failed()); + ASSERT_EQ(brpc::EHTTP, cntl.ErrorCode()) << cntl.ErrorText(); + ASSERT_EQ(brpc::HTTP_STATUS_FORBIDDEN, cntl.http_response().status_code()); + } + + { + brpc::Channel chan; + brpc::ChannelOptions copt; + copt.protocol = brpc::PROTOCOL_HTTP; + copt.max_retry = 0; + ASSERT_EQ(0, chan.Init(internal_ep, &copt)); + + brpc::Controller cntl; + cntl.http_request().uri() = "/status"; + cntl.http_request().set_method(brpc::HTTP_METHOD_GET); + chan.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(brpc::HTTP_STATUS_OK, cntl.http_response().status_code()); + } + + { + const std::string connection_group = "builtin-auth-policy"; + brpc::Channel builtin_channel; + brpc::Channel protected_channel; + brpc::ChannelOptions copt; + copt.protocol = brpc::PROTOCOL_HTTP; + copt.connection_type = brpc::CONNECTION_TYPE_POOLED; + copt.connection_group = connection_group; + copt.max_retry = 0; + ASSERT_EQ(0, builtin_channel.Init(ep, &copt)); + ASSERT_EQ(0, protected_channel.Init(ep, &copt)); + + brpc::Controller builtin_cntl; + CallVersion(&builtin_channel, &builtin_cntl); + ASSERT_TRUE(builtin_cntl.Failed()); + ASSERT_EQ(brpc::EHTTP, builtin_cntl.ErrorCode()) << builtin_cntl.ErrorText(); + ASSERT_EQ(brpc::HTTP_STATUS_FORBIDDEN, builtin_cntl.http_response().status_code()); + + brpc::Controller protected_cntl; + CallHttpEcho(&protected_channel, &protected_cntl); + ASSERT_TRUE(protected_cntl.Failed()); + } + + ASSERT_EQ(0, server.Stop(0)); + ASSERT_EQ(0, server.Join()); + brpc::FLAGS_max_connection_pool_size = saved_max_connection_pool_size; +} + +TEST_F(HttpTest, process_request_failed_socket) { + brpc::policy::HttpContext* msg = MakePostRequestMessage("/EchoService/Echo"); + _socket->SetFailed(); + ProcessMessage(brpc::policy::ProcessHttpRequest, msg, false); + ASSERT_EQ(0ll, _server._nerror_bvar.get_value()); + CheckResponseCode(true, 0); +} + +TEST_F(HttpTest, reject_get_to_pb_services_with_required_fields) { + brpc::policy::HttpContext* msg = MakeGetRequestMessage("/EchoService/Echo"); + _server._status = brpc::Server::RUNNING; + ProcessMessage(brpc::policy::ProcessHttpRequest, msg, false); + ASSERT_EQ(0ll, _server._nerror_bvar.get_value()); + const brpc::Server::MethodProperty* mp = + _server.FindMethodPropertyByFullName("test.EchoService.Echo"); + ASSERT_TRUE(mp); + ASSERT_TRUE(mp->status); + ASSERT_EQ(1ll, mp->status->_nerror_bvar.get_value()); + CheckResponseCode(false, brpc::HTTP_STATUS_BAD_REQUEST); +} + +TEST_F(HttpTest, process_request_logoff) { + brpc::policy::HttpContext* msg = MakePostRequestMessage("/EchoService/Echo"); + _server._status = brpc::Server::READY; + ProcessMessage(brpc::policy::ProcessHttpRequest, msg, false); + ASSERT_EQ(1ll, _server._nerror_bvar.get_value()); + CheckResponseCode(false, brpc::HTTP_STATUS_SERVICE_UNAVAILABLE); +} + +TEST_F(HttpTest, process_request_wrong_method) { + brpc::policy::HttpContext* msg = MakePostRequestMessage("/NO_SUCH_METHOD"); + ProcessMessage(brpc::policy::ProcessHttpRequest, msg, false); + ASSERT_EQ(1ll, _server._nerror_bvar.get_value()); + CheckResponseCode(false, brpc::HTTP_STATUS_NOT_FOUND); +} + +TEST_F(HttpTest, process_response_after_eof) { + test::EchoResponse res; + brpc::Controller cntl; + cntl._response = &res; + brpc::policy::HttpContext* msg = + MakeResponseMessage(brpc::HTTP_STATUS_OK); + _socket->set_correlation_id(cntl.call_id().value); + ProcessMessage(brpc::policy::ProcessHttpResponse, msg, true); + ASSERT_EQ(EXP_RESPONSE, res.message()); + ASSERT_TRUE(_socket->Failed()); +} + +TEST_F(HttpTest, process_response_error_code) { + { + brpc::Controller cntl; + _socket->set_correlation_id(cntl.call_id().value); + brpc::policy::HttpContext* msg = + MakeResponseMessage(brpc::HTTP_STATUS_CONTINUE); + ProcessMessage(brpc::policy::ProcessHttpResponse, msg, false); + ASSERT_EQ(brpc::EHTTP, cntl.ErrorCode()); + ASSERT_EQ(brpc::HTTP_STATUS_CONTINUE, cntl.http_response().status_code()); + } + { + brpc::Controller cntl; + _socket->set_correlation_id(cntl.call_id().value); + brpc::policy::HttpContext* msg = + MakeResponseMessage(brpc::HTTP_STATUS_TEMPORARY_REDIRECT); + ProcessMessage(brpc::policy::ProcessHttpResponse, msg, false); + ASSERT_EQ(brpc::EHTTP, cntl.ErrorCode()); + ASSERT_EQ(brpc::HTTP_STATUS_TEMPORARY_REDIRECT, + cntl.http_response().status_code()); + } + { + brpc::Controller cntl; + _socket->set_correlation_id(cntl.call_id().value); + brpc::policy::HttpContext* msg = + MakeResponseMessage(brpc::HTTP_STATUS_BAD_REQUEST); + ProcessMessage(brpc::policy::ProcessHttpResponse, msg, false); + ASSERT_EQ(brpc::EHTTP, cntl.ErrorCode()); + ASSERT_EQ(brpc::HTTP_STATUS_BAD_REQUEST, + cntl.http_response().status_code()); + } + { + brpc::Controller cntl; + _socket->set_correlation_id(cntl.call_id().value); + brpc::policy::HttpContext* msg = MakeResponseMessage(12345); + ProcessMessage(brpc::policy::ProcessHttpResponse, msg, false); + ASSERT_EQ(brpc::EHTTP, cntl.ErrorCode()); + ASSERT_EQ(12345, cntl.http_response().status_code()); + } +} + +TEST_F(HttpTest, complete_flow) { + butil::IOBuf request_buf; + butil::IOBuf total_buf; + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + cntl._response = &res; + cntl._connection_type = brpc::CONNECTION_TYPE_SHORT; + cntl._method = test::EchoService::descriptor()->method(0); + ASSERT_EQ(0, brpc::Socket::Address(_socket->id(), &cntl._current_call.sending_sock)); + + // Send request + req.set_message(EXP_REQUEST); + brpc::policy::SerializeHttpRequest(&request_buf, &cntl, &req); + ASSERT_FALSE(cntl.Failed()); + brpc::policy::PackHttpRequest( + &total_buf, NULL, cntl.call_id().value, + cntl._method, &cntl, request_buf, &_auth); + ASSERT_FALSE(cntl.Failed()); + + // Verify and handle request + brpc::ParseResult req_pr = + brpc::policy::ParseHttpMessage(&total_buf, _socket.get(), false, NULL); + ASSERT_EQ(brpc::PARSE_OK, req_pr.error()); + brpc::InputMessageBase* req_msg = req_pr.message(); + VerifyMessage(req_msg, true); + ProcessMessage(brpc::policy::ProcessHttpRequest, req_msg, false); + + // Read response from pipe + butil::IOPortal response_buf; + response_buf.append_from_file_descriptor(_pipe_fds[0], 1024); + brpc::ParseResult res_pr = + brpc::policy::ParseHttpMessage(&response_buf, _socket.get(), false, NULL); + ASSERT_EQ(brpc::PARSE_OK, res_pr.error()); + brpc::InputMessageBase* res_msg = res_pr.message(); + ProcessMessage(brpc::policy::ProcessHttpResponse, res_msg, false); + + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(EXP_RESPONSE, res.message()); +} + +TEST_F(HttpTest, chunked_uploading) { + const int port = 8923; + brpc::Server server; + EXPECT_EQ(0, server.AddService(&_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + EXPECT_EQ(0, server.Start(port, NULL)); + + // Send request via curl using chunked encoding + const std::string req = "{\"message\":\"hello\"}"; + const std::string res_fname = "curl.out"; + std::string cmd; + butil::string_printf(&cmd, "curl -X POST -d '%s' -H 'Transfer-Encoding:chunked' " + "-H 'Content-Type:application/json' -o %s " + "http://localhost:%d/EchoService/Echo", + req.c_str(), res_fname.c_str(), port); + ASSERT_EQ(0, system(cmd.c_str())); + + // Check response + const std::string exp_res = "{\"message\":\"world\"}"; + butil::ScopedFILE fp(res_fname.c_str(), "r"); + char buf[128]; + ASSERT_TRUE(fgets(buf, sizeof(buf), fp)); + EXPECT_EQ(exp_res, std::string(buf)); +} + +enum DonePlace { + DONE_BEFORE_CREATE_PA = 0, + DONE_AFTER_CREATE_PA_BEFORE_DESTROY_PA, + DONE_AFTER_DESTROY_PA, +}; +// For writing into PA. +const char PA_DATA[] = "abcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*()_=-+"; +const size_t PA_DATA_LEN = sizeof(PA_DATA) - 1/*not count the ending zero*/; + +static void CopyPAPrefixedWithSeqNo(char* buf, uint64_t seq_no) { + memcpy(buf, PA_DATA, PA_DATA_LEN); + *(uint64_t*)buf = seq_no; +} + +class DownloadServiceImpl : public ::test::DownloadService { +public: + DownloadServiceImpl(DonePlace done_place = DONE_BEFORE_CREATE_PA, + size_t num_repeat = 1) + : _done_place(done_place) + , _nrep(num_repeat) + , _nwritten(0) + , _ever_full(false) + , _last_errno(0) {} + + void Download(::google::protobuf::RpcController* cntl_base, + const ::test::HttpRequest*, + ::test::HttpResponse*, + ::google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = + static_cast(cntl_base); + cntl->http_response().set_content_type("text/plain"); + brpc::StopStyle stop_style = (_nrep == std::numeric_limits::max() + ? brpc::FORCE_STOP : brpc::WAIT_FOR_STOP); + butil::intrusive_ptr pa + = cntl->CreateProgressiveAttachment(stop_style); + if (pa == NULL) { + cntl->SetFailed("The socket was just failed"); + return; + } + if (_done_place == DONE_BEFORE_CREATE_PA) { + done_guard.reset(NULL); + } + ASSERT_GT(PA_DATA_LEN, 8u); // long enough to hold a 64-bit decimal. + char buf[PA_DATA_LEN]; + for (size_t c = 0; c < _nrep;) { + CopyPAPrefixedWithSeqNo(buf, c); + if (pa->Write(buf, sizeof(buf)) != 0) { + if (errno == brpc::EOVERCROWDED) { + LOG_EVERY_SECOND(INFO) << "full pa=" << pa.get(); + _ever_full = true; + bthread_usleep(10000); + continue; + } else { + _last_errno = errno; + break; + } + } else { + _nwritten += PA_DATA_LEN; + } + ++c; + } + if (_done_place == DONE_AFTER_CREATE_PA_BEFORE_DESTROY_PA) { + done_guard.reset(NULL); + } + LOG(INFO) << "Destroy pa=" << pa.get(); + pa.reset(NULL); + if (_done_place == DONE_AFTER_DESTROY_PA) { + done_guard.reset(NULL); + } + } + + void DownloadFailed(::google::protobuf::RpcController* cntl_base, + const ::test::HttpRequest*, + ::test::HttpResponse*, + ::google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = + static_cast(cntl_base); + cntl->http_response().set_content_type("text/plain"); + brpc::StopStyle stop_style = (_nrep == std::numeric_limits::max() + ? brpc::FORCE_STOP : brpc::WAIT_FOR_STOP); + butil::intrusive_ptr pa + = cntl->CreateProgressiveAttachment(stop_style); + if (pa == NULL) { + cntl->SetFailed("The socket was just failed"); + return; + } + char buf[PA_DATA_LEN]; + while (true) { + if (pa->Write(buf, sizeof(buf)) != 0) { + if (errno == brpc::EOVERCROWDED) { + LOG_EVERY_SECOND(INFO) << "full pa=" << pa.get(); + bthread_usleep(10000); + continue; + } else { + _last_errno = errno; + break; + } + } + break; + } + // The remote client will not receive the data written to the + // progressive attachment when the controller failed. + cntl->SetFailed("Intentionally set controller failed"); + done_guard.reset(NULL); + + // Return value of Write after controller has failed should + // be less than zero. + CHECK_LT(pa->Write(buf, sizeof(buf)), 0); + CHECK_EQ(errno, ECANCELED); + } + + void set_done_place(DonePlace done_place) { _done_place = done_place; } + size_t written_bytes() const { return _nwritten; } + bool ever_full() const { return _ever_full; } + int last_errno() const { return _last_errno; } + +private: + DonePlace _done_place; + size_t _nrep; + size_t _nwritten; + bool _ever_full; + int _last_errno; +}; + +TEST_F(HttpTest, read_chunked_response_normally) { + const int port = 8923; + brpc::Server server; + DownloadServiceImpl svc; + EXPECT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + EXPECT_EQ(0, server.Start(port, NULL)); + + for (int i = 0; i < 3; ++i) { + svc.set_done_place((DonePlace)i); + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_HTTP; + ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port), &options)); + brpc::Controller cntl; + cntl.http_request().uri() = "/DownloadService/Download"; + channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + + std::string expected(PA_DATA_LEN, 0); + CopyPAPrefixedWithSeqNo(&expected[0], 0); + ASSERT_EQ(expected, cntl.response_attachment()); + } +} + +TEST_F(HttpTest, read_failed_chunked_response) { + const int port = 8923; + brpc::Server server; + DownloadServiceImpl svc; + EXPECT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + EXPECT_EQ(0, server.Start(port, NULL)); + + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_HTTP; + ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port), &options)); + + brpc::Controller cntl; + cntl.http_request().uri() = "/DownloadService/DownloadFailed"; + cntl.response_will_be_read_progressively(); + channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + EXPECT_TRUE(cntl.response_attachment().empty()); + ASSERT_TRUE(cntl.Failed()); + ASSERT_NE(cntl.ErrorText().find("HTTP/1.1 500 Internal Server Error"), + std::string::npos) << cntl.ErrorText(); + ASSERT_NE(cntl.ErrorText().find("Intentionally set controller failed"), + std::string::npos) << cntl.ErrorText(); + ASSERT_EQ(0, svc.last_errno()); +} + +class ReadBody : public brpc::ProgressiveReader, + public brpc::SharedObject { +public: + ReadBody() + : _nread(0) + , _ncount(0) + , _destroyed(false) { + butil::intrusive_ptr(this).detach(); // ref + } + + butil::Status OnReadOnePart(const void* data, size_t length) { + _nread += length; + while (length > 0) { + size_t nappend = std::min(_buf.size() + length, PA_DATA_LEN) - _buf.size(); + _buf.append((const char*)data, nappend); + data = (const char*)data + nappend; + length -= nappend; + if (_buf.size() >= PA_DATA_LEN) { + EXPECT_EQ(PA_DATA_LEN, _buf.size()); + char expected[PA_DATA_LEN]; + CopyPAPrefixedWithSeqNo(expected, _ncount++); + EXPECT_EQ(0, memcmp(expected, _buf.data(), PA_DATA_LEN)) + << "ncount=" << _ncount; + _buf.clear(); + } + } + return butil::Status::OK(); + } + void OnEndOfMessage(const butil::Status& st) { + butil::intrusive_ptr(this, false); // deref + ASSERT_LT(_buf.size(), PA_DATA_LEN); + ASSERT_EQ(0, memcmp(_buf.data(), PA_DATA, _buf.size())); + _destroyed = true; + _destroying_st = st; + LOG(INFO) << "Destroy ReadBody=" << this << ", " << st; + } + bool destroyed() const { return _destroyed; } + const butil::Status& destroying_status() const { return _destroying_st; } + size_t read_bytes() const { return _nread; } +private: + std::string _buf; + size_t _nread; + size_t _ncount; + bool _destroyed; + butil::Status _destroying_st; +}; + +#ifdef BUTIL_USE_ASAN +static const int GENERAL_DELAY_US = 1000000; // 1s +#else +static const int GENERAL_DELAY_US = 300000; // 0.3s +#endif + +TEST_F(HttpTest, read_long_body_progressively) { + DownloadServiceImpl svc(DONE_BEFORE_CREATE_PA, + std::numeric_limits::max()); + butil::intrusive_ptr reader; + { + const int port = 8923; + brpc::Server server; + EXPECT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + EXPECT_EQ(0, server.Start(port, NULL)); + { + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_HTTP; + ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port), &options)); + { + brpc::Controller cntl; + cntl.response_will_be_read_progressively(); + cntl.http_request().uri() = "/DownloadService/Download"; + channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_TRUE(cntl.response_attachment().empty()); + reader.reset(new ReadBody); + cntl.ReadProgressiveAttachmentBy(reader.get()); + size_t last_read = 0; + for (size_t i = 0; i < 3; ++i) { + sleep(1); + size_t current_read = reader->read_bytes(); + LOG(INFO) << "read=" << current_read - last_read + << " total=" << current_read; + last_read = current_read; + } + // Read something in past N seconds. + ASSERT_GT(last_read, (size_t)100000); + } + // the socket still holds a ref. + ASSERT_FALSE(reader->destroyed()); + } + // Wait for recycling of the main socket. + usleep(GENERAL_DELAY_US); + // even if the main socket is recycled, the pooled socket for + // receiving data is not affected. + ASSERT_FALSE(reader->destroyed()); + } + // Wait for close of the connection due to server's stopping. + usleep(GENERAL_DELAY_US); + ASSERT_TRUE(reader->destroyed()); + ASSERT_EQ(ECONNRESET, reader->destroying_status().error_code()); +} + +TEST_F(HttpTest, read_short_body_progressively) { + butil::intrusive_ptr reader; + const int port = 8923; + brpc::Server server; + const int NREP = 10000; + DownloadServiceImpl svc(DONE_BEFORE_CREATE_PA, NREP); + EXPECT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + EXPECT_EQ(0, server.Start(port, NULL)); + { + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_HTTP; + ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port), &options)); + { + brpc::Controller cntl; + cntl.response_will_be_read_progressively(); + cntl.http_request().uri() = "/DownloadService/Download"; + channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_TRUE(cntl.response_attachment().empty()); + reader.reset(new ReadBody); + cntl.ReadProgressiveAttachmentBy(reader.get()); + size_t last_read = 0; + for (size_t i = 0; i < 3; ++i) { + sleep(1); + size_t current_read = reader->read_bytes(); + LOG(INFO) << "read=" << current_read - last_read + << " total=" << current_read; + last_read = current_read; + } + ASSERT_EQ(NREP * PA_DATA_LEN, svc.written_bytes()); + ASSERT_EQ(NREP * PA_DATA_LEN, last_read); + } + ASSERT_TRUE(reader->destroyed()); + ASSERT_EQ(0, reader->destroying_status().error_code()); + } +} + +TEST_F(HttpTest, read_progressively_after_cntl_destroys) { + DownloadServiceImpl svc(DONE_BEFORE_CREATE_PA, + std::numeric_limits::max()); + butil::intrusive_ptr reader; + { + const int port = 8923; + brpc::Server server; + EXPECT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + EXPECT_EQ(0, server.Start(port, NULL)); + { + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_HTTP; + ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port), &options)); + { + brpc::Controller cntl; + cntl.response_will_be_read_progressively(); + cntl.http_request().uri() = "/DownloadService/Download"; + channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_TRUE(cntl.response_attachment().empty()); + reader.reset(new ReadBody); + cntl.ReadProgressiveAttachmentBy(reader.get()); + } + size_t last_read = 0; + for (size_t i = 0; i < 3; ++i) { + sleep(1); + size_t current_read = reader->read_bytes(); + LOG(INFO) << "read=" << current_read - last_read + << " total=" << current_read; + last_read = current_read; + } + // Read something in past N seconds. + ASSERT_GT(last_read, (size_t)100000); + ASSERT_FALSE(reader->destroyed()); + } + // Wait for recycling of the main socket. + usleep(GENERAL_DELAY_US); + ASSERT_FALSE(reader->destroyed()); + } + // Wait for close of the connection due to server's stopping. + usleep(GENERAL_DELAY_US); + ASSERT_TRUE(reader->destroyed()); + ASSERT_EQ(ECONNRESET, reader->destroying_status().error_code()); +} + +TEST_F(HttpTest, read_progressively_after_long_delay) { + butil::intrusive_ptr reader; + DownloadServiceImpl svc(DONE_BEFORE_CREATE_PA, + std::numeric_limits::max()); + { + const int port = 8923; + brpc::Server server; + EXPECT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + EXPECT_EQ(0, server.Start(port, NULL)); + { + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_HTTP; + ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port), &options)); + { + brpc::Controller cntl; + cntl.response_will_be_read_progressively(); + cntl.http_request().uri() = "/DownloadService/Download"; + channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_TRUE(cntl.response_attachment().empty()); + LOG(INFO) << "Sleep 3 seconds to make PA at server-side full"; + sleep(3); + EXPECT_TRUE(svc.ever_full()); + ASSERT_EQ(0, svc.last_errno()); + reader.reset(new ReadBody); + cntl.ReadProgressiveAttachmentBy(reader.get()); + size_t last_read = 0; + for (size_t i = 0; i < 3; ++i) { + sleep(1); + size_t current_read = reader->read_bytes(); + LOG(INFO) << "read=" << current_read - last_read + << " total=" << current_read; + last_read = current_read; + } + // Read something in past N seconds. + ASSERT_GT(last_read, (size_t)100000); + } + ASSERT_FALSE(reader->destroyed()); + } + // Wait for recycling of the main socket. + usleep(GENERAL_DELAY_US); + ASSERT_FALSE(reader->destroyed()); + } + // Wait for close of the connection due to server's stopping. + usleep(GENERAL_DELAY_US); + ASSERT_TRUE(reader->destroyed()); + ASSERT_EQ(ECONNRESET, reader->destroying_status().error_code()); +} + +TEST_F(HttpTest, skip_progressive_reading) { + DownloadServiceImpl svc(DONE_BEFORE_CREATE_PA, + std::numeric_limits::max()); + const int port = 8923; + brpc::Server server; + EXPECT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + EXPECT_EQ(0, server.Start(port, NULL)); + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_HTTP; + ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port), &options)); + { + brpc::Controller cntl; + cntl.response_will_be_read_progressively(); + cntl.http_request().uri() = "/DownloadService/Download"; + channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_TRUE(cntl.response_attachment().empty()); + } + const size_t old_written_bytes = svc.written_bytes(); + LOG(INFO) << "Sleep 3 seconds after destroy of Controller"; + sleep(3); + const size_t new_written_bytes = svc.written_bytes(); + ASSERT_EQ(0, svc.last_errno()); + LOG(INFO) << "Server still wrote " << new_written_bytes - old_written_bytes; + // The server side still wrote things. + ASSERT_GT(new_written_bytes - old_written_bytes, (size_t)100000); +} + +class AlwaysFailRead : public brpc::ProgressiveReader { +public: + // @ProgressiveReader + butil::Status OnReadOnePart(const void* /*data*/, size_t /*length*/) { + return butil::Status(-1, "intended fail at %s:%d", __FILE__, __LINE__); + } + void OnEndOfMessage(const butil::Status& st) { + LOG(INFO) << "Destroy " << this << ": " << st; + delete this; + } +}; + +TEST_F(HttpTest, failed_on_read_one_part) { + const int port = 8923; + brpc::Server server; + DownloadServiceImpl svc(DONE_BEFORE_CREATE_PA, + std::numeric_limits::max()); + EXPECT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + EXPECT_EQ(0, server.Start(port, NULL)); + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_HTTP; + ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port), &options)); + { + brpc::Controller cntl; + cntl.response_will_be_read_progressively(); + cntl.http_request().uri() = "/DownloadService/Download"; + channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_TRUE(cntl.response_attachment().empty()); + cntl.ReadProgressiveAttachmentBy(new AlwaysFailRead); + } + LOG(INFO) << "Sleep 1 second"; + sleep(1); + ASSERT_NE(0, svc.last_errno()); +} + +TEST_F(HttpTest, broken_socket_stops_progressive_reading) { + butil::intrusive_ptr reader; + const int port = 8923; + brpc::Server server; + DownloadServiceImpl svc(DONE_BEFORE_CREATE_PA, + std::numeric_limits::max()); + EXPECT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + EXPECT_EQ(0, server.Start(port, NULL)); + + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_HTTP; + ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port), &options)); + { + brpc::Controller cntl; + cntl.response_will_be_read_progressively(); + cntl.http_request().uri() = "/DownloadService/Download"; + channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_TRUE(cntl.response_attachment().empty()); + reader.reset(new ReadBody); + cntl.ReadProgressiveAttachmentBy(reader.get()); + size_t last_read = 0; + for (size_t i = 0; i < 3; ++i) { + sleep(1); + size_t current_read = reader->read_bytes(); + LOG(INFO) << "read=" << current_read - last_read + << " total=" << current_read; + last_read = current_read; + } + // Read something in past N seconds. + ASSERT_GT(last_read, (size_t)100000); + } + // the socket still holds a ref. + ASSERT_FALSE(reader->destroyed()); + LOG(INFO) << "Stopping the server"; + server.Stop(0); + server.Join(); + + // Wait for error reporting from the socket. + usleep(GENERAL_DELAY_US); + ASSERT_TRUE(reader->destroyed()); + ASSERT_EQ(ECONNRESET, reader->destroying_status().error_code()); +} + +#ifndef BUTIL_USE_ASAN +static const std::string TEST_PROGRESSIVE_HEADER = "Progressive"; +static const std::string TEST_PROGRESSIVE_HEADER_VAL = "Progressive-val"; + +class ServerProgressiveReader : public ReadBody { +public: + ServerProgressiveReader(brpc::Controller* cntl, google::protobuf::Closure* done) + : _cntl(cntl) + , _done(done) {} + + // @ProgressiveReader + void OnEndOfMessage(const butil::Status& st) { + butil::intrusive_ptr(this); + brpc::ClosureGuard done_guard(_done); + ASSERT_LT(_buf.size(), PA_DATA_LEN); + ASSERT_EQ(0, memcmp(_buf.data(), PA_DATA, _buf.size())); + _destroyed = true; + _destroying_st = st; + LOG(INFO) << "Destroy ReadBody=" << this << ", " << st; + _cntl->response_attachment().append("Sucess"); + } +private: + brpc::Controller* _cntl; + google::protobuf::Closure* _done; +}; + +class ServerAlwaysFailReader : public brpc::ProgressiveReader { +public: + ServerAlwaysFailReader(brpc::Controller* cntl, google::protobuf::Closure* done) + : _cntl(cntl) + , _done(done) {} + + // @ProgressiveReader + butil::Status OnReadOnePart(const void* /*data*/, size_t /*length*/) { + return butil::Status(-1, "intended fail at %s:%d", __FILE__, __LINE__); + } + + void OnEndOfMessage(const butil::Status& st) { + brpc::ClosureGuard done_guard(_done); + CHECK_EQ(-1, st.error_code()); + _cntl->SetFailed("Must Failed"); + LOG(INFO) << "Destroy " << this << ": " << st; + delete this; + } +private: + brpc::Controller* _cntl; + google::protobuf::Closure* _done; +}; + +class UploadServiceImpl : public ::test::UploadService { +public: + void Upload(::google::protobuf::RpcController* controller, + const ::test::HttpRequest* request, + ::test::HttpResponse* response, + ::google::protobuf::Closure* done) { + brpc::Controller* cntl = static_cast(controller); + check_header(cntl); + cntl->request_will_be_read_progressively(); + cntl->ReadProgressiveAttachmentBy(new ServerProgressiveReader(cntl, done)); + } + + void UploadFailed(::google::protobuf::RpcController* controller, + const ::test::HttpRequest* request, + ::test::HttpResponse* response, + ::google::protobuf::Closure* done) { + brpc::Controller* cntl = static_cast(controller); + check_header(cntl); + cntl->request_will_be_read_progressively(); + cntl->ReadProgressiveAttachmentBy(new ServerAlwaysFailReader(cntl, done)); + } + +private: + void check_header(brpc::Controller* cntl) { + const std::string* test_header = cntl->http_request().GetHeader(TEST_PROGRESSIVE_HEADER); + CHECK(test_header != NULL); + CHECK_EQ(*test_header, TEST_PROGRESSIVE_HEADER_VAL); + } +}; + +TEST_F(HttpTest, server_end_read_short_body_progressively) { + const int port = 8923; + brpc::ServiceOptions opt; + opt.enable_progressive_read = true; + opt.ownership = brpc::SERVER_DOESNT_OWN_SERVICE; + UploadServiceImpl upsvc; + brpc::Server server; + EXPECT_EQ(0, server.AddService(&upsvc, opt)); + EXPECT_EQ(0, server.Start(port, NULL)); + + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_HTTP; + ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port), &options)); + brpc::Controller cntl; + cntl.http_request().uri() = "/UploadService/Upload"; + cntl.http_request().SetHeader(TEST_PROGRESSIVE_HEADER, TEST_PROGRESSIVE_HEADER_VAL); + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + + ASSERT_GT(PA_DATA_LEN, 8u); // long enough to hold a 64-bit decimal. + char buf[PA_DATA_LEN]; + for (size_t c = 0; c < 10000;) { + CopyPAPrefixedWithSeqNo(buf, c); + if (cntl.request_attachment().append(buf, sizeof(buf)) != 0) { + if (errno == brpc::EOVERCROWDED) { + LOG(INFO) << "full msg=" << cntl.request_attachment().to_string(); + } else { + LOG(INFO) << "Error:" << errno; + } + break; + } + ++c; + } + channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()); +} + +// Fixme!!! Server progressive reader has a heap-use-after-free bug detected by ASan. +// For details, see https://github.com/apache/brpc/issues/2145#issuecomment-2329413363 +TEST_F(HttpTest, server_end_read_failed) { + const int port = 8923; + brpc::ServiceOptions opt; + opt.enable_progressive_read = true; + opt.ownership = brpc::SERVER_DOESNT_OWN_SERVICE; + UploadServiceImpl upsvc; + brpc::Server server; + EXPECT_EQ(0, server.AddService(&upsvc, opt)); + EXPECT_EQ(0, server.Start(port, NULL)); + + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_HTTP; + ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port), &options)); + brpc::Controller cntl; + cntl.http_request().uri() = "/UploadService/UploadFailed"; + cntl.http_request().SetHeader(TEST_PROGRESSIVE_HEADER, TEST_PROGRESSIVE_HEADER_VAL); + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + + ASSERT_GT(PA_DATA_LEN, 8u); // long enough to hold a 64-bit decimal. + char buf[PA_DATA_LEN]; + for (size_t c = 0; c < 10;) { + CopyPAPrefixedWithSeqNo(buf, c); + if (cntl.request_attachment().append(buf, sizeof(buf)) != 0) { + if (errno == brpc::EOVERCROWDED) { + LOG(INFO) << "full msg=" << cntl.request_attachment().to_string(); + } else { + LOG(INFO) << "Error:" << errno; + } + break; + } + ++c; + } + channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_TRUE(cntl.Failed()); +} +#endif // BUTIL_USE_ASAN + +TEST_F(HttpTest, http2_sanity) { + const int port = 8923; + brpc::Server server; + EXPECT_EQ(0, server.AddService(&_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + EXPECT_EQ(0, server.Start(port, NULL)); + + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = "h2"; + ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port), &options)); + + // Check that the first request with size larger than the default window can + // be sent out, when remote settings are not received. + brpc::Controller cntl; + test::EchoRequest big_req; + test::EchoResponse res; + std::string message(2 * 1024 * 1024 /* 2M */, 'x'); + big_req.set_message(message); + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.http_request().uri() = "/EchoService/Echo"; + channel.CallMethod(NULL, &cntl, &big_req, &res, NULL); + ASSERT_FALSE(cntl.Failed()); + ASSERT_EQ(EXP_RESPONSE, res.message()); + + // socket replacement when streamId runs out, the initial streamId is a special + // value set in ctor of H2Context so that the number 15000 is enough to run out + // of stream. + test::EchoRequest req; + req.set_message(EXP_REQUEST); + for (int i = 0; i < 15000; ++i) { + brpc::Controller cntl; + cntl.http_request().set_content_type("application/json"); + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.http_request().uri() = "/EchoService/Echo"; + channel.CallMethod(NULL, &cntl, &req, &res, NULL); + ASSERT_FALSE(cntl.Failed()); + ASSERT_EQ(EXP_RESPONSE, res.message()); + } + + // check connection window size + brpc::SocketUniquePtr main_ptr; + brpc::SocketUniquePtr agent_ptr; + EXPECT_EQ(brpc::Socket::Address(channel._server_id, &main_ptr), 0); + EXPECT_EQ(main_ptr->GetAgentSocket(&agent_ptr, NULL), 0); + brpc::policy::H2Context* ctx = static_cast(agent_ptr->parsing_context()); + ASSERT_GT(ctx->_remote_window_left.load(butil::memory_order_relaxed), + brpc::H2Settings::DEFAULT_INITIAL_WINDOW_SIZE / 2); +} + +TEST_F(HttpTest, http2_ping) { + // This test injects PING frames before and after header and data. + brpc::Controller cntl; + + // Prepare request + butil::IOBuf req_out; + int h2_stream_id = 0; + MakeH2EchoRequestBuf(&req_out, &cntl, &h2_stream_id); + // Prepare response + butil::IOBuf res_out; + char pingbuf[9 /*FRAME_HEAD_SIZE*/ + 8 /*Opaque Data*/]; + brpc::policy::SerializeFrameHead(pingbuf, 8, brpc::policy::H2_FRAME_PING, 0, 0); + // insert ping before header and data + res_out.append(pingbuf, sizeof(pingbuf)); + MakeH2EchoResponseBuf(&res_out, h2_stream_id); + // insert ping after header and data + res_out.append(pingbuf, sizeof(pingbuf)); + // parse response + brpc::ParseResult res_pr = + brpc::policy::ParseH2Message(&res_out, _h2_client_sock.get(), false, NULL); + ASSERT_TRUE(res_pr.is_ok()); + // process response + ProcessMessage(brpc::policy::ProcessHttpResponse, res_pr.message(), false); + ASSERT_FALSE(cntl.Failed()); +} + +inline void SaveUint32(void* out, uint32_t v) { + uint8_t* p = (uint8_t*)out; + p[0] = (v >> 24) & 0xFF; + p[1] = (v >> 16) & 0xFF; + p[2] = (v >> 8) & 0xFF; + p[3] = v & 0xFF; +} + +TEST_F(HttpTest, http2_rst_before_header) { + brpc::Controller cntl; + // Prepare request + butil::IOBuf req_out; + int h2_stream_id = 0; + MakeH2EchoRequestBuf(&req_out, &cntl, &h2_stream_id); + // Prepare response + butil::IOBuf res_out; + char rstbuf[9 /*FRAME_HEAD_SIZE*/ + 4]; + brpc::policy::SerializeFrameHead(rstbuf, 4, brpc::policy::H2_FRAME_RST_STREAM, 0, h2_stream_id); + SaveUint32(rstbuf + 9, brpc::H2_INTERNAL_ERROR); + res_out.append(rstbuf, sizeof(rstbuf)); + MakeH2EchoResponseBuf(&res_out, h2_stream_id); + // parse response + brpc::ParseResult res_pr = + brpc::policy::ParseH2Message(&res_out, _h2_client_sock.get(), false, NULL); + ASSERT_TRUE(res_pr.is_ok()); + // process response + ProcessMessage(brpc::policy::ProcessHttpResponse, res_pr.message(), false); + ASSERT_TRUE(cntl.Failed()); + ASSERT_TRUE(cntl.ErrorCode() == brpc::EHTTP); + ASSERT_TRUE(cntl.http_response().status_code() == brpc::HTTP_STATUS_INTERNAL_SERVER_ERROR); +} + +TEST_F(HttpTest, http2_rst_after_header_and_data) { + brpc::Controller cntl; + // Prepare request + butil::IOBuf req_out; + int h2_stream_id = 0; + MakeH2EchoRequestBuf(&req_out, &cntl, &h2_stream_id); + // Prepare response + butil::IOBuf res_out; + MakeH2EchoResponseBuf(&res_out, h2_stream_id); + char rstbuf[9 /*FRAME_HEAD_SIZE*/ + 4]; + brpc::policy::SerializeFrameHead(rstbuf, 4, brpc::policy::H2_FRAME_RST_STREAM, 0, h2_stream_id); + SaveUint32(rstbuf + 9, brpc::H2_INTERNAL_ERROR); + res_out.append(rstbuf, sizeof(rstbuf)); + // parse response + brpc::ParseResult res_pr = + brpc::policy::ParseH2Message(&res_out, _h2_client_sock.get(), false, NULL); + ASSERT_TRUE(res_pr.is_ok()); + // process response + ProcessMessage(brpc::policy::ProcessHttpResponse, res_pr.message(), false); + ASSERT_FALSE(cntl.Failed()); + ASSERT_TRUE(cntl.http_response().status_code() == brpc::HTTP_STATUS_OK); +} + +TEST_F(HttpTest, http2_window_used_up) { + brpc::Controller cntl; + butil::IOBuf request_buf; + test::EchoRequest req; + // longer message to trigger using up window size sooner + req.set_message("FLOW_CONTROL_FLOW_CONTROL"); + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.http_request().set_content_type("application/proto"); + brpc::policy::SerializeHttpRequest(&request_buf, &cntl, &req); + + char settingsbuf[brpc::policy::FRAME_HEAD_SIZE + 36]; + brpc::H2Settings h2_settings; + const size_t nb = brpc::policy::SerializeH2Settings(h2_settings, settingsbuf + brpc::policy::FRAME_HEAD_SIZE); + brpc::policy::SerializeFrameHead(settingsbuf, nb, brpc::policy::H2_FRAME_SETTINGS, 0, 0); + butil::IOBuf buf; + buf.append(settingsbuf, brpc::policy::FRAME_HEAD_SIZE + nb); + brpc::policy::ParseH2Message(&buf, _h2_client_sock.get(), false, NULL); + + int nsuc = brpc::H2Settings::DEFAULT_INITIAL_WINDOW_SIZE / cntl.request_attachment().size(); + for (int i = 0; i <= nsuc; i++) { + brpc::policy::H2UnsentRequest* h2_req = brpc::policy::H2UnsentRequest::New(&cntl); + cntl._current_call.stream_user_data = h2_req; + brpc::SocketMessage* socket_message = NULL; + brpc::policy::PackH2Request(NULL, &socket_message, cntl.call_id().value, + NULL, &cntl, request_buf, NULL); + butil::IOBuf dummy; + butil::Status st = socket_message->AppendAndDestroySelf(&dummy, _h2_client_sock.get()); + if (i == nsuc) { + // the last message should fail according to flow control policy. + ASSERT_FALSE(st.ok()); + ASSERT_TRUE(st.error_code() == brpc::ELIMIT); + ASSERT_TRUE(butil::StringPiece(st.error_str()).starts_with("remote_window_left is not enough")); + } else { + ASSERT_TRUE(st.ok()); + } + h2_req->DestroyStreamUserData(_h2_client_sock, &cntl, 0, false); + } +} + +TEST_F(HttpTest, http2_settings) { + char settingsbuf[brpc::policy::FRAME_HEAD_SIZE + 36]; + brpc::H2Settings h2_settings; + h2_settings.header_table_size = 8192; + h2_settings.max_concurrent_streams = 1024; + h2_settings.stream_window_size= (1u << 29) - 1; + const size_t nb = brpc::policy::SerializeH2Settings(h2_settings, settingsbuf + brpc::policy::FRAME_HEAD_SIZE); + brpc::policy::SerializeFrameHead(settingsbuf, nb, brpc::policy::H2_FRAME_SETTINGS, 0, 0); + butil::IOBuf buf; + buf.append(settingsbuf, brpc::policy::FRAME_HEAD_SIZE + nb); + + brpc::policy::H2Context* ctx = new brpc::policy::H2Context(_socket.get(), NULL); + CHECK_EQ(ctx->Init(), 0); + _socket->initialize_parsing_context(&ctx); + ctx->_conn_state = brpc::policy::H2_CONNECTION_READY; + // parse settings + brpc::policy::ParseH2Message(&buf, _socket.get(), false, NULL); + + butil::IOPortal response_buf; + CHECK_EQ(response_buf.append_from_file_descriptor(_pipe_fds[0], 1024), + (ssize_t)brpc::policy::FRAME_HEAD_SIZE); + brpc::policy::H2FrameHead frame_head; + butil::IOBufBytesIterator it(response_buf); + ctx->ConsumeFrameHead(it, &frame_head); + CHECK_EQ(frame_head.type, brpc::policy::H2_FRAME_SETTINGS); + CHECK_EQ(frame_head.flags, 0x01 /* H2_FLAGS_ACK */); + CHECK_EQ(frame_head.stream_id, 0); + ASSERT_TRUE(ctx->_remote_settings.header_table_size == 8192); + ASSERT_TRUE(ctx->_remote_settings.max_concurrent_streams == 1024); + ASSERT_TRUE(ctx->_remote_settings.stream_window_size == (1u << 29) - 1); +} + +TEST_F(HttpTest, http2_invalid_settings) { + { + brpc::Server server; + brpc::ServerOptions options; + options.h2_settings.stream_window_size = brpc::H2Settings::MAX_WINDOW_SIZE + 1; + ASSERT_EQ(-1, server.Start("127.0.0.1:8924", &options)); + } + { + brpc::Server server; + brpc::ServerOptions options; + options.h2_settings.max_frame_size = + brpc::H2Settings::DEFAULT_MAX_FRAME_SIZE - 1; + ASSERT_EQ(-1, server.Start("127.0.0.1:8924", &options)); + } + { + brpc::Server server; + brpc::ServerOptions options; + options.h2_settings.max_frame_size = + brpc::H2Settings::MAX_OF_MAX_FRAME_SIZE + 1; + ASSERT_EQ(-1, server.Start("127.0.0.1:8924", &options)); + } +} + +TEST_F(HttpTest, http2_not_closing_socket_when_rpc_timeout) { + const int port = 8923; + brpc::Server server; + EXPECT_EQ(0, server.AddService(&_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + EXPECT_EQ(0, server.Start(port, NULL)); + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = "h2"; + ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port), &options)); + + test::EchoRequest req; + test::EchoResponse res; + req.set_message(EXP_REQUEST); + { + // make a successful call to create the connection first + brpc::Controller cntl; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.http_request().uri() = "/EchoService/Echo"; + channel.CallMethod(NULL, &cntl, &req, &res, NULL); + ASSERT_FALSE(cntl.Failed()); + ASSERT_EQ(EXP_RESPONSE, res.message()); + } + + brpc::SocketUniquePtr main_ptr; + EXPECT_EQ(brpc::Socket::Address(channel._server_id, &main_ptr), 0); + brpc::SocketId agent_id = main_ptr->_agent_socket_id.load(butil::memory_order_relaxed); + + for (int i = 0; i < 4; i++) { + brpc::Controller cntl; + cntl.set_timeout_ms(50); + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.http_request().uri() = "/EchoService/Echo?sleep_ms=300"; + channel.CallMethod(NULL, &cntl, &req, &res, NULL); + ASSERT_TRUE(cntl.Failed()); + + brpc::SocketUniquePtr ptr; + brpc::SocketId id = main_ptr->_agent_socket_id.load(butil::memory_order_relaxed); + EXPECT_EQ(id, agent_id); + } + + { + // make a successful call again to make sure agent_socket not changing + brpc::Controller cntl; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.http_request().uri() = "/EchoService/Echo"; + channel.CallMethod(NULL, &cntl, &req, &res, NULL); + ASSERT_FALSE(cntl.Failed()); + ASSERT_EQ(EXP_RESPONSE, res.message()); + brpc::SocketUniquePtr ptr; + brpc::SocketId id = main_ptr->_agent_socket_id.load(butil::memory_order_relaxed); + EXPECT_EQ(id, agent_id); + } +} + +TEST_F(HttpTest, http2_header_after_data) { + brpc::Controller cntl; + + // Prepare request + butil::IOBuf req_out; + int h2_stream_id = 0; + MakeH2EchoRequestBuf(&req_out, &cntl, &h2_stream_id); + + // Prepare response to res_out + butil::IOBuf res_out; + { + butil::IOBuf data_buf; + test::EchoResponse res; + res.set_message(EXP_RESPONSE); + { + butil::IOBufAsZeroCopyOutputStream wrapper(&data_buf); + EXPECT_TRUE(res.SerializeToZeroCopyStream(&wrapper)); + } + brpc::policy::H2Context* ctx = + static_cast(_h2_client_sock->parsing_context()); + brpc::HPacker& hpacker = ctx->hpacker(); + butil::IOBufAppender header1_appender; + brpc::HPackOptions options; + options.encode_name = false; /* disable huffman encoding */ + options.encode_value = false; + { + brpc::HPacker::Header header(":status", "200"); + hpacker.Encode(&header1_appender, header, options); + } + { + brpc::HPacker::Header header("content-length", + butil::string_printf("%llu", (unsigned long long)data_buf.size())); + hpacker.Encode(&header1_appender, header, options); + } + { + brpc::HPacker::Header header(":status", "200"); + hpacker.Encode(&header1_appender, header, options); + } + { + brpc::HPacker::Header header("content-type", "application/proto"); + hpacker.Encode(&header1_appender, header, options); + } + { + brpc::HPacker::Header header("user-defined1", "a"); + hpacker.Encode(&header1_appender, header, options); + } + butil::IOBuf header1; + header1_appender.move_to(header1); + + char headbuf[brpc::policy::FRAME_HEAD_SIZE]; + brpc::policy::SerializeFrameHead(headbuf, header1.size(), + brpc::policy::H2_FRAME_HEADERS, 0, h2_stream_id); + // append header1 + res_out.append(headbuf, sizeof(headbuf)); + res_out.append(butil::IOBuf::Movable(header1)); + + brpc::policy::SerializeFrameHead(headbuf, data_buf.size(), + brpc::policy::H2_FRAME_DATA, 0, h2_stream_id); + // append data + res_out.append(headbuf, sizeof(headbuf)); + res_out.append(butil::IOBuf::Movable(data_buf)); + + butil::IOBufAppender header2_appender; + { + brpc::HPacker::Header header("user-defined1", "overwrite-a"); + hpacker.Encode(&header2_appender, header, options); + } + { + brpc::HPacker::Header header("user-defined2", "b"); + hpacker.Encode(&header2_appender, header, options); + } + butil::IOBuf header2; + header2_appender.move_to(header2); + + brpc::policy::SerializeFrameHead(headbuf, header2.size(), + brpc::policy::H2_FRAME_HEADERS, 0x05/* end header and stream */, + h2_stream_id); + // append header2 + res_out.append(headbuf, sizeof(headbuf)); + res_out.append(butil::IOBuf::Movable(header2)); + } + // parse response + brpc::ParseResult res_pr = + brpc::policy::ParseH2Message(&res_out, _h2_client_sock.get(), false, NULL); + ASSERT_TRUE(res_pr.is_ok()); + // process response + ProcessMessage(brpc::policy::ProcessHttpResponse, res_pr.message(), false); + ASSERT_FALSE(cntl.Failed()); + + brpc::HttpHeader& res_header = cntl.http_response(); + ASSERT_EQ(res_header.content_type(), "application/proto"); + // Check overlapped header is overwritten by the latter. + const std::string* user_defined1 = res_header.GetHeader("user-defined1"); + ASSERT_EQ(*user_defined1, "a,overwrite-a"); + const std::string* user_defined2 = res_header.GetHeader("user-defined2"); + ASSERT_EQ(*user_defined2, "b"); +} + +TEST_F(HttpTest, http2_goaway_sanity) { + brpc::Controller cntl; + // Prepare request + butil::IOBuf req_out; + int h2_stream_id = 0; + MakeH2EchoRequestBuf(&req_out, &cntl, &h2_stream_id); + // Prepare response + butil::IOBuf res_out; + MakeH2EchoResponseBuf(&res_out, h2_stream_id); + // append goaway + char goawaybuf[9 /*FRAME_HEAD_SIZE*/ + 8]; + brpc::policy::SerializeFrameHead(goawaybuf, 8, brpc::policy::H2_FRAME_GOAWAY, 0, 0); + SaveUint32(goawaybuf + 9, 0x7fffd8ef /*last stream id*/); + SaveUint32(goawaybuf + 13, brpc::H2_NO_ERROR); + res_out.append(goawaybuf, sizeof(goawaybuf)); + // parse response + brpc::ParseResult res_pr = + brpc::policy::ParseH2Message(&res_out, _h2_client_sock.get(), false, NULL); + ASSERT_TRUE(res_pr.is_ok()); + // process response + ProcessMessage(brpc::policy::ProcessHttpResponse, res_pr.message(), false); + ASSERT_TRUE(!cntl.Failed()); + + // parse GOAWAY + res_pr = brpc::policy::ParseH2Message(&res_out, _h2_client_sock.get(), false, NULL); + ASSERT_EQ(res_pr.error(), brpc::PARSE_ERROR_NOT_ENOUGH_DATA); + + // Since GOAWAY has been received, the next request should fail + brpc::policy::H2UnsentRequest* h2_req = brpc::policy::H2UnsentRequest::New(&cntl); + cntl._current_call.stream_user_data = h2_req; + brpc::SocketMessage* socket_message = NULL; + brpc::policy::PackH2Request(NULL, &socket_message, cntl.call_id().value, + NULL, &cntl, butil::IOBuf(), NULL); + butil::IOBuf dummy; + butil::Status st = socket_message->AppendAndDestroySelf(&dummy, _h2_client_sock.get()); + ASSERT_EQ(st.error_code(), brpc::ELOGOFF); + ASSERT_TRUE(st.error_data().ends_with("the connection just issued GOAWAY")); + // Release the reference held by stream_user_data (which is normally released + // by Controller::Call::OnComplete) to avoid leaking the H2UnsentRequest. + h2_req->DestroyStreamUserData(_h2_client_sock, &cntl, 0, false); +} + +class AfterRecevingGoAway : public ::google::protobuf::Closure { +public: + void Run() { + ASSERT_EQ(brpc::EHTTP, cntl.ErrorCode()); + delete this; + } + brpc::Controller cntl; +}; + +TEST_F(HttpTest, http2_handle_goaway_streams) { + const butil::EndPoint ep(butil::IP_ANY, 5961); + butil::fd_guard listenfd(butil::tcp_listen(ep)); + ASSERT_GT(listenfd, 0); + + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_H2; + ASSERT_EQ(0, channel.Init(ep, &options)); + + int req_size = 10; + std::vector ids(req_size); + for (int i = 0; i < req_size; i++) { + AfterRecevingGoAway* done = new AfterRecevingGoAway; + brpc::Controller& cntl = done->cntl; + ids.push_back(cntl.call_id()); + cntl.set_timeout_ms(-1); + cntl.http_request().uri() = "/it-doesnt-matter"; + channel.CallMethod(NULL, &cntl, NULL, NULL, done); + } + + int servfd = accept(listenfd, NULL, NULL); + ASSERT_GT(servfd, 0); + // Sleep for a while to make sure that server has received all data. + bthread_usleep(2000); + char goawaybuf[brpc::policy::FRAME_HEAD_SIZE + 8]; + SerializeFrameHead(goawaybuf, 8, brpc::policy::H2_FRAME_GOAWAY, 0, 0); + SaveUint32(goawaybuf + brpc::policy::FRAME_HEAD_SIZE, 0); + SaveUint32(goawaybuf + brpc::policy::FRAME_HEAD_SIZE + 4, 0); + ASSERT_EQ((ssize_t)brpc::policy::FRAME_HEAD_SIZE + 8, ::write(servfd, goawaybuf, brpc::policy::FRAME_HEAD_SIZE + 8)); + + // After receving GOAWAY, the callbacks in client should be run correctly. + for (int i = 0; i < req_size; i++) { + brpc::Join(ids[i]); + } +} + +TEST_F(HttpTest, spring_protobuf_content_type) { + const int port = 8923; + brpc::Server server; + EXPECT_EQ(0, server.AddService(&_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + EXPECT_EQ(0, server.Start(port, nullptr)); + + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = "http"; + ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port), &options)); + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(EXP_REQUEST); + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.http_request().uri() = "/EchoService/Echo"; + cntl.http_request().set_content_type("application/x-protobuf"); + cntl.request_attachment().append(req.SerializeAsString()); + channel.CallMethod(nullptr, &cntl, nullptr, nullptr, nullptr); + ASSERT_FALSE(cntl.Failed()); + ASSERT_EQ("application/x-protobuf", cntl.http_response().content_type()); + ASSERT_TRUE(res.ParseFromString(cntl.response_attachment().to_string())); + ASSERT_EQ(EXP_RESPONSE, res.message()); + + brpc::Controller cntl2; + test::EchoService_Stub stub(&channel); + res.Clear(); + cntl2.http_request().set_content_type("application/x-protobuf"); + stub.Echo(&cntl2, &req, &res, nullptr); + ASSERT_FALSE(cntl2.Failed()); + ASSERT_EQ(EXP_RESPONSE, res.message()); + ASSERT_EQ("application/x-protobuf", cntl2.http_response().content_type()); +} + +TEST_F(HttpTest, dump_http_request) { + // save origin value of gflag + auto rpc_dump_dir = brpc::FLAGS_rpc_dump_dir; + auto rpc_dump_max_requests_in_one_file = brpc::FLAGS_rpc_dump_max_requests_in_one_file; + + // set gflag and global variable in order to be sure to dump request + brpc::FLAGS_rpc_dump = true; + brpc::FLAGS_rpc_dump_dir = "dump_http_request"; + brpc::FLAGS_rpc_dump_max_requests_in_one_file = 1; + brpc::g_rpc_dump_sl.ever_grabbed = true; + brpc::g_rpc_dump_sl.sampling_range = bvar::COLLECTOR_SAMPLING_BASE; + + // init channel + const int port = 8923; + brpc::Server server; + EXPECT_EQ(0, server.AddService(&_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + EXPECT_EQ(0, server.Start(port, nullptr)); + + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = "http"; + ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port), &options)); + + // send request and dump it to file + { + test::EchoRequest req; + req.set_message(EXP_REQUEST); + std::string req_json; + ASSERT_TRUE(json2pb::ProtoMessageToJson(req, &req_json)); + + brpc::Controller cntl; + cntl.http_request().uri() = "/EchoService/Echo"; + cntl.http_request().set_content_type("application/json"); + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment() = req_json; + channel.CallMethod(nullptr, &cntl, nullptr, nullptr, nullptr); + ASSERT_FALSE(cntl.Failed()); + + // sleep 1s, because rpc_dump doesn't run immediately + sleep(1); + } + + // replay request from dump file + { + brpc::SampleIterator it(brpc::FLAGS_rpc_dump_dir); + brpc::SampledRequest* sample = it.Next(); + ASSERT_NE(nullptr, sample); + + std::unique_ptr sample_guard(sample); + + // the logic of next code is same as that in rpc_replay.cpp + ASSERT_EQ(sample->meta.protocol_type(), brpc::PROTOCOL_HTTP); + brpc::Controller cntl; + cntl.reset_sampled_request(sample_guard.release()); + brpc::HttpMessage http_message; + http_message.ParseFromIOBuf(sample->request); + cntl.http_request().Swap(http_message.header()); + // clear origin Host in header + cntl.http_request().RemoveHeader("Host"); + cntl.http_request().uri().set_host(""); + cntl.request_attachment() = http_message.body().movable(); + + channel.CallMethod(nullptr, &cntl, nullptr, nullptr, nullptr); + ASSERT_FALSE(cntl.Failed()); + ASSERT_EQ("application/json", cntl.http_response().content_type()); + + std::string res_json = cntl.response_attachment().to_string(); + test::EchoResponse res; + json2pb::Json2PbOptions options; + ASSERT_TRUE(json2pb::JsonToProtoMessage(res_json, &res, options)); + ASSERT_EQ(EXP_RESPONSE, res.message()); + } + + // delete dump directory + butil::DeleteFile(butil::FilePath(brpc::FLAGS_rpc_dump_dir), true); + + // restore gflag and global variable + brpc::FLAGS_rpc_dump = false; + brpc::FLAGS_rpc_dump_dir = rpc_dump_dir; + brpc::FLAGS_rpc_dump_max_requests_in_one_file = rpc_dump_max_requests_in_one_file; + brpc::g_rpc_dump_sl.ever_grabbed = false; + brpc::g_rpc_dump_sl.sampling_range = 0; +} + +TEST_F(HttpTest, proto_text_content_type) { + const int port = 8923; + brpc::Server server; + EXPECT_EQ(0, server.AddService(&_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + EXPECT_EQ(0, server.Start(port, nullptr)); + + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = "http"; + ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port), &options)); + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(EXP_REQUEST); + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.http_request().uri() = "/EchoService/Echo"; + cntl.http_request().set_content_type("application/proto-text"); + std::string req_text; + ASSERT_TRUE(google::protobuf::TextFormat::PrintToString(req, &req_text)); + cntl.request_attachment().append(req_text); + channel.CallMethod(nullptr, &cntl, nullptr, nullptr, nullptr); + ASSERT_FALSE(cntl.Failed()) << req_text; + ASSERT_EQ("application/proto-text", cntl.http_response().content_type()); + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + cntl.response_attachment().to_string(), &res)); + ASSERT_EQ(EXP_RESPONSE, res.message()); + + test::EchoService_Stub stub(&channel); + cntl.Reset(); + cntl.http_request().set_content_type("application/proto-text"); + res.Clear(); + stub.Echo(&cntl, &req, &res, NULL); + ASSERT_FALSE(cntl.Failed()); + ASSERT_EQ(EXP_RESPONSE, res.message()); + ASSERT_EQ("application/proto-text", cntl.http_response().content_type()); +} + +TEST_F(HttpTest, proto_json_content_type) { + const int port = 8923; + brpc::Server server; + EXPECT_EQ(0, server.AddService(&_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + EXPECT_EQ(0, server.Start(port, nullptr)); + + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = "http"; + ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port), &options)); + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(EXP_REQUEST); + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.http_request().uri() = "/EchoService/Echo"; + cntl.http_request().set_content_type("application/proto-json"); + json2pb::Pb2ProtoJsonOptions json_options; + butil::IOBufAsZeroCopyOutputStream output_stream(&cntl.request_attachment()); + ASSERT_TRUE(json2pb::ProtoMessageToProtoJson(req, &output_stream, json_options)); + channel.CallMethod(nullptr, &cntl, nullptr, nullptr, nullptr); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ("application/proto-json", cntl.http_response().content_type()); + json2pb::ProtoJson2PbOptions parse_options; + parse_options.ignore_unknown_fields = true; + butil::IOBufAsZeroCopyInputStream input_stream(cntl.response_attachment()); + ASSERT_TRUE(json2pb::ProtoJsonToProtoMessage(&input_stream, &res, parse_options)); + ASSERT_EQ(EXP_RESPONSE, res.message()); + + test::EchoService_Stub stub(&channel); + cntl.Reset(); + cntl.http_request().set_content_type("application/proto-json"); + res.Clear(); + stub.Echo(&cntl, &req, &res, nullptr); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(EXP_RESPONSE, res.message()); + ASSERT_EQ("application/proto-json", cntl.http_response().content_type()); +} + +class HttpServiceImpl : public ::test::HttpService { + public: + void Head(::google::protobuf::RpcController* cntl_base, + const ::test::HttpRequest*, + ::test::HttpResponse*, + ::google::protobuf::Closure* done) override { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = static_cast(cntl_base); + ASSERT_EQ(cntl->http_request().method(), brpc::HTTP_METHOD_HEAD); + const std::string* index = cntl->http_request().GetHeader("x-db-index"); + ASSERT_NE(nullptr, index); + int i; + ASSERT_TRUE(butil::StringToInt(*index, &i)); + cntl->http_response().set_content_type("text/plain"); + if (i % 2 == 0) { + cntl->http_response().SetHeader("Content-Length", + EXP_RESPONSE_CONTENT_LENGTH); + } else { + cntl->http_response().SetHeader("Transfer-Encoding", + EXP_RESPONSE_TRANSFER_ENCODING); + } + } + + void Expect(::google::protobuf::RpcController* cntl_base, + const ::test::HttpRequest*, + ::test::HttpResponse*, + ::google::protobuf::Closure* done) override { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = static_cast(cntl_base); + const std::string* expect = cntl->http_request().GetHeader("Expect"); + ASSERT_TRUE(expect != NULL); + ASSERT_EQ("100-continue", *expect); + ASSERT_EQ(cntl->http_request().method(), brpc::HTTP_METHOD_POST); + cntl->response_attachment().append("world"); + } +}; + +TEST_F(HttpTest, http_head) { + const int port = 8923; + brpc::Server server; + HttpServiceImpl svc; + EXPECT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + EXPECT_EQ(0, server.Start(port, NULL)); + + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_HTTP; + ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port), &options)); + for (int i = 0; i < 100; ++i) { + brpc::Controller cntl; + cntl.http_request().set_method(brpc::HTTP_METHOD_HEAD); + cntl.http_request().uri().set_path("/HttpService/Head"); + cntl.http_request().SetHeader("x-db-index", butil::IntToString(i)); + channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + if (i % 2 == 0) { + const std::string* content_length + = cntl.http_response().GetHeader("content-length"); + ASSERT_NE(nullptr, content_length); + ASSERT_EQ(EXP_RESPONSE_CONTENT_LENGTH, *content_length); + } else { + const std::string* transfer_encoding + = cntl.http_response().GetHeader("Transfer-Encoding"); + ASSERT_NE(nullptr, transfer_encoding); + ASSERT_EQ(EXP_RESPONSE_TRANSFER_ENCODING, *transfer_encoding); + } + } +} + +#define BRPC_CRLF "\r\n" + +void MakeHttpRequestHeaders(butil::IOBuf* out, + brpc::HttpHeader* h, + const butil::EndPoint& remote_side) { + butil::IOBufBuilder os; + os << HttpMethod2Str(h->method()) << ' '; + const brpc::URI& uri = h->uri(); + uri.PrintWithoutHost(os); // host is sent by "Host" header. + os << " HTTP/" << h->major_version() << '.' + << h->minor_version() << BRPC_CRLF; + //rfc 7230#section-5.4: + //A client MUST send a Host header field in all HTTP/1.1 request + //messages. If the authority component is missing or undefined for + //the target URI, then a client MUST send a Host header field with an + //empty field-value. + //rfc 7231#sec4.3: + //the request-target consists of only the host name and port number of + //the tunnel destination, seperated by a colon. For example, + //Host: server.example.com:80 + if (h->GetHeader("host") == NULL) { + os << "Host: "; + if (!uri.host().empty()) { + os << uri.host(); + if (uri.port() >= 0) { + os << ':' << uri.port(); + } + } else if (remote_side.port != 0) { + os << remote_side; + } + os << BRPC_CRLF; + } + if (!h->content_type().empty()) { + os << "Content-Type: " << h->content_type() + << BRPC_CRLF; + } + for (brpc::HttpHeader::HeaderIterator it = h->HeaderBegin(); + it != h->HeaderEnd(); ++it) { + os << it->first << ": " << it->second << BRPC_CRLF; + } + if (h->GetHeader("Accept") == NULL) { + os << "Accept: */*" BRPC_CRLF; + } + // The fake "curl" user-agent may let servers return plain-text results. + if (h->GetHeader("User-Agent") == NULL) { + os << "User-Agent: brpc/1.0 curl/7.0" BRPC_CRLF; + } + const std::string& user_info = h->uri().user_info(); + if (!user_info.empty() && h->GetHeader("Authorization") == NULL) { + // NOTE: just assume user_info is well formatted, namely + // ":". Users are very unlikely to add extra + // characters in this part and even if users did, most of them are + // invalid and rejected by http_parser_parse_url(). + std::string encoded_user_info; + butil::Base64Encode(user_info, &encoded_user_info); + os << "Authorization: Basic " << encoded_user_info << BRPC_CRLF; + } + os << BRPC_CRLF; // CRLF before content + os.move_to(*out); +} + +#undef BRPC_CRLF + +void ReadOneResponse(brpc::SocketUniquePtr& sock, + brpc::DestroyingPtr& imsg_guard) { +#if defined(OS_LINUX) + ASSERT_EQ(0, bthread_fd_wait(sock->fd(), EPOLLIN)); +#elif defined(OS_MACOSX) + ASSERT_EQ(0, bthread_fd_wait(sock->fd(), EVFILT_READ)); +#endif + + butil::IOPortal read_buf; + int64_t start_time = butil::cpuwide_time_us(); + while (true) { + const ssize_t nr = read_buf.append_from_file_descriptor(sock->fd(), 4096); + LOG(INFO) << "nr=" << nr; + LOG(INFO) << butil::ToPrintableString(read_buf); + ASSERT_TRUE(nr > 0 || (nr < 0 && errno == EAGAIN)); + if (errno == EAGAIN) { + ASSERT_LT(butil::cpuwide_time_us(), start_time + 1000000L) << "Too long!"; + bthread_usleep(1000); + continue; + } + brpc::ParseResult pr = brpc::policy::ParseHttpMessage(&read_buf, sock.get(), false, NULL); + ASSERT_TRUE(pr.error() == brpc::PARSE_ERROR_NOT_ENOUGH_DATA || pr.is_ok()); + if (pr.is_ok()) { + imsg_guard.reset(static_cast(pr.message())); + break; + } + } + ASSERT_TRUE(read_buf.empty()); +} + +TEST_F(HttpTest, http_expect) { + const int port = 8923; + brpc::Server server; + HttpServiceImpl svc; + EXPECT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + EXPECT_EQ(0, server.Start(port, NULL)); + + butil::EndPoint ep; + ASSERT_EQ(0, butil::str2endpoint("127.0.0.1:8923", &ep)); + brpc::SocketOptions options; + options.remote_side = ep; + brpc::SocketId id; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + brpc::SocketUniquePtr sock; + ASSERT_EQ(0, brpc::Socket::Address(id, &sock)); + + butil::IOBuf content; + content.append("hello"); + brpc::HttpHeader header; + header.set_method(brpc::HTTP_METHOD_POST); + header.uri().set_path("/HttpService/Expect"); + header.SetHeader("Expect", "100-continue"); + header.SetHeader("Content-Length", std::to_string(content.size())); + butil::IOBuf header_buf; + MakeHttpRequestHeaders(&header_buf, &header, ep); + LOG(INFO) << butil::ToPrintableString(header_buf); + butil::IOBuf request_buf(header_buf); + request_buf.append(content); + + ASSERT_EQ(0, sock->Write(&header_buf)); + int64_t start_time = butil::cpuwide_time_us(); + while (sock->fd() < 0) { + bthread_usleep(1000); + ASSERT_LT(butil::cpuwide_time_us(), start_time + 1000000L) << "Too long!"; + } + // 100 Continue + brpc::DestroyingPtr imsg_guard; + ReadOneResponse(sock, imsg_guard); + ASSERT_EQ(imsg_guard->header().status_code(), brpc::HTTP_STATUS_CONTINUE); + + ASSERT_EQ(0, sock->Write(&content)); + // 200 Ok + ReadOneResponse(sock, imsg_guard); + ASSERT_EQ(imsg_guard->header().status_code(), brpc::HTTP_STATUS_OK); + + ASSERT_EQ(0, sock->Write(&request_buf)); + // 200 Ok + ReadOneResponse(sock, imsg_guard); + ASSERT_EQ(imsg_guard->header().status_code(), brpc::HTTP_STATUS_OK); +} + +// Test gRPC authentication failure response format +TEST_F(HttpTest, grpc_auth_failed_response) { + // Set up an authenticator that returns authentication failure + class FailingAuthenticator : public brpc::Authenticator { + public: + int GenerateCredential(std::string*) const override { return 0; } + int VerifyCredential(const std::string&, const butil::EndPoint&, brpc::AuthContext*) const override { + return -1; // Simulate authentication failure + } + std::string GetUnauthorizedErrorText() const override { + return "Authentication failed for gRPC"; + } + }; + + FailingAuthenticator failing_auth; + const brpc::Authenticator* original_auth = _server._options.auth; + _server._options.auth = &failing_auth; + + // Test HTTP/2.0 gRPC request authentication failure using H2StreamContext + { + // Create H2Context for the connection + brpc::policy::H2Context* h2_ctx = new brpc::policy::H2Context(_socket.get(), &_server); + ASSERT_EQ(0, h2_ctx->Init()); + _socket->initialize_parsing_context(&h2_ctx); + + // Create H2StreamContext representing a gRPC request + brpc::policy::H2StreamContext* h2_msg = new brpc::policy::H2StreamContext(false); + h2_msg->header().set_content_type("application/grpc"); // gRPC content type + h2_msg->header().uri().set_path("/EchoService/Echo"); + h2_msg->header().set_method(brpc::HTTP_METHOD_POST); + + // Initialize the stream context with connection context and stream ID + h2_msg->Init(h2_ctx, 1); // stream_id = 1 + + // Set socket and arg using existing test pattern + if (h2_msg->_socket == NULL) { + _socket->ReAddress(&h2_msg->_socket); + } + h2_msg->_arg = &_server; + + // Verify that authentication should fail for HTTP/2 gRPC request + bool verify_result = brpc::policy::VerifyHttpRequest(h2_msg); + EXPECT_FALSE(verify_result); + + // Check if response has been written to pipe for HTTP/2 + int bytes_in_pipe = 0; + ioctl(_pipe_fds[0], FIONREAD, &bytes_in_pipe); + EXPECT_GT(bytes_in_pipe, 0); + + // Read and verify HTTP/2 response content + butil::IOPortal buf; + EXPECT_EQ((ssize_t)bytes_in_pipe, buf.append_from_file_descriptor(_pipe_fds[0], 1024)); + + // For HTTP/2, the response format should be different from HTTP/1.1 + // Let's check if it contains HTTP/2 frame data + std::string response_str = buf.to_string(); + EXPECT_GT(response_str.length(), 0); + + // HTTP/2 gRPC response should contain: + // 1. grpc-status header (error code) + // 2. grpc-message header (error message) + // 3. Our authentication failure text (might be URL encoded) + EXPECT_TRUE(response_str.find("grpc-status") != std::string::npos); + EXPECT_TRUE(response_str.find("grpc-message") != std::string::npos); + EXPECT_TRUE(response_str.find("Authentication") != std::string::npos); + EXPECT_TRUE(response_str.find("failed") != std::string::npos); + EXPECT_TRUE(response_str.find("gRPC") != std::string::npos); + + h2_msg->Destroy(); + } + + // Restore original auth settings + _server._options.auth = original_auth; +} + +// Test HTTP/1.0 authentication failure response format +TEST_F(HttpTest, http10_auth_failed_response) { + // Set up an authenticator that returns authentication failure + class FailingAuthenticator : public brpc::Authenticator { + public: + int GenerateCredential(std::string*) const override { return 0; } + int VerifyCredential(const std::string&, const butil::EndPoint&, brpc::AuthContext*) const override { + return -1; // Simulate authentication failure + } + std::string GetUnauthorizedErrorText() const override { + return "Authentication failed for HTTP/1.0"; + } + }; + + FailingAuthenticator failing_auth; + const brpc::Authenticator* original_auth = _server._options.auth; + _server._options.auth = &failing_auth; + + // Test HTTP/1.0 request authentication failure (should return HTTP 403) + { + brpc::policy::HttpContext* http_msg = MakePostRequestMessage("/EchoService/Echo"); + http_msg->header().set_version(1, 0); // Set to HTTP/1.0 + http_msg->header().set_content_type("application/json"); // Regular HTTP request + + // Use VerifyMessage to properly set up socket and arg (like other tests) + VerifyMessage(http_msg, false); + + // Verify that authentication should fail for HTTP/1.0 request + bool verify_result = brpc::policy::VerifyHttpRequest(http_msg); + EXPECT_FALSE(verify_result); + + // Check HTTP/1.0 response format + int bytes_in_pipe = 0; + ioctl(_pipe_fds[0], FIONREAD, &bytes_in_pipe); + EXPECT_GT(bytes_in_pipe, 0); + + butil::IOPortal buf; + EXPECT_EQ((ssize_t)bytes_in_pipe, buf.append_from_file_descriptor(_pipe_fds[0], 1024)); + + // Parse HTTP/1.0 response and verify format + brpc::ParseResult pr = brpc::policy::ParseHttpMessage(&buf, _socket.get(), false, NULL); + EXPECT_EQ(brpc::PARSE_OK, pr.error()); + brpc::policy::HttpContext* response_msg = static_cast(pr.message()); + + // Verify HTTP/1.x response format (server may respond with 1.1 even for 1.0 requests) + EXPECT_EQ(1, response_msg->header().major_version()); + EXPECT_TRUE(response_msg->header().minor_version() >= 0); + EXPECT_EQ(brpc::HTTP_STATUS_FORBIDDEN, response_msg->header().status_code()); + + // Check response body content + std::string body_content = response_msg->body().to_string(); + EXPECT_TRUE(body_content.find("Authentication failed") != std::string::npos); + EXPECT_TRUE(body_content.find("1004") != std::string::npos); // brpc error code + + // Verify HTTP headers for HTTP/1.0 + const std::string* content_length = response_msg->header().GetHeader("Content-Length"); + EXPECT_TRUE(content_length != NULL); + EXPECT_GT(std::stoi(*content_length), 0); + + // Content-Type may not always be set for error responses, check if present + const std::string* content_type = response_msg->header().GetHeader("Content-Type"); + if (content_type != NULL) { + // If present, should contain text + EXPECT_TRUE(content_type->find("text") != std::string::npos); + } + + response_msg->Destroy(); + http_msg->Destroy(); + } + + // Restore original auth settings + _server._options.auth = original_auth; +} + +} //namespace diff --git a/test/brpc_http_status_code_unittest.cpp b/test/brpc_http_status_code_unittest.cpp new file mode 100644 index 0000000..013b295 --- /dev/null +++ b/test/brpc_http_status_code_unittest.cpp @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// File: test_http_status_code.cpp +// Date: 2014/11/04 18:33:39 + +#include +#include "brpc/http_status_code.h" + +class HttpStatusTest : public testing::Test { + void SetUp() {} + void TearDown() {} +}; + +TEST_F(HttpStatusTest, sanity) { + ASSERT_STREQ("OK", brpc::HttpReasonPhrase( + brpc::HTTP_STATUS_OK)); + ASSERT_STREQ("Continue", brpc::HttpReasonPhrase( + brpc::HTTP_STATUS_CONTINUE)); + ASSERT_STREQ("HTTP Version Not Supported", brpc::HttpReasonPhrase( + brpc::HTTP_STATUS_VERSION_NOT_SUPPORTED)); + ASSERT_STREQ("Unknown status code (-2)", brpc::HttpReasonPhrase(-2)); +} diff --git a/test/brpc_hulu_pbrpc_protocol_unittest.cpp b/test/brpc_hulu_pbrpc_protocol_unittest.cpp new file mode 100644 index 0000000..d00b9d4 --- /dev/null +++ b/test/brpc_hulu_pbrpc_protocol_unittest.cpp @@ -0,0 +1,350 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: Sun Jul 13 15:04:18 CST 2014 + +#include +#include +#include +#include +#include +#include +#include "butil/time.h" +#include "butil/macros.h" +#include "brpc/socket.h" +#include "brpc/acceptor.h" +#include "brpc/server.h" +#include "brpc/policy/hulu_pbrpc_meta.pb.h" +#include "brpc/policy/hulu_pbrpc_protocol.h" +#include "brpc/policy/most_common_message.h" +#include "brpc/controller.h" +#include "echo.pb.h" + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + return RUN_ALL_TESTS(); +} + +namespace { + +static const std::string EXP_REQUEST = "hello"; +static const std::string EXP_RESPONSE = "world"; + +static const std::string MOCK_CREDENTIAL = "mock credential"; +static const std::string MOCK_USER = "mock user"; + +class MyAuthenticator : public brpc::Authenticator { +public: + MyAuthenticator() {} + + int GenerateCredential(std::string* auth_str) const { + *auth_str = MOCK_CREDENTIAL; + return 0; + } + + int VerifyCredential(const std::string& auth_str, + const butil::EndPoint&, + brpc::AuthContext* ctx) const { + EXPECT_EQ(MOCK_CREDENTIAL, auth_str); + ctx->set_user(MOCK_USER); + return 0; + } +}; + +class MyEchoService : public ::test::EchoService { + void Echo(::google::protobuf::RpcController* cntl_base, + const ::test::EchoRequest* req, + ::test::EchoResponse* res, + ::google::protobuf::Closure* done) { + brpc::Controller* cntl = + static_cast(cntl_base); + brpc::ClosureGuard done_guard(done); + + if (req->close_fd()) { + cntl->CloseConnection("Close connection according to request"); + return; + } + if (cntl->auth_context()) { + EXPECT_EQ(MOCK_USER, cntl->auth_context()->user()); + } + EXPECT_EQ(EXP_REQUEST, req->message()); + if (!cntl->request_attachment().empty()) { + EXPECT_EQ(EXP_REQUEST, cntl->request_attachment().to_string()); + cntl->response_attachment().append(EXP_RESPONSE); + } + res->set_message(EXP_RESPONSE); + } +}; + +class HuluTest : public ::testing::Test{ +protected: + HuluTest() { + EXPECT_EQ(0, _server.AddService( + &_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + // Hack: Regard `_server' as running + _server._status = brpc::Server::RUNNING; + _server._options.auth = &_auth; + + EXPECT_EQ(0, pipe(_pipe_fds)); + + brpc::SocketId id; + brpc::SocketOptions options; + options.fd = _pipe_fds[1]; + EXPECT_EQ(0, brpc::Socket::Create(options, &id)); + EXPECT_EQ(0, brpc::Socket::Address(id, &_socket)); + }; + + virtual ~HuluTest() {}; + virtual void SetUp() {}; + virtual void TearDown() {}; + + void VerifyMessage(brpc::InputMessageBase* msg) { + if (msg->_socket == NULL) { + _socket->ReAddress(&msg->_socket); + } + msg->_arg = &_server; + EXPECT_TRUE(brpc::policy::VerifyHuluRequest(msg)); + } + + void ProcessMessage(void (*process)(brpc::InputMessageBase*), + brpc::InputMessageBase* msg, bool set_eof) { + if (msg->_socket == NULL) { + _socket.get()->ReAddress(&msg->_socket); + } + msg->_arg = &_server; + _socket->PostponeEOF(); + if (set_eof) { + _socket->SetEOF(); + } + (*process)(msg); + } + + brpc::policy::MostCommonMessage* MakeRequestMessage( + const brpc::policy::HuluRpcRequestMeta& meta) { + brpc::policy::MostCommonMessage* msg = + brpc::policy::MostCommonMessage::Get(); + butil::IOBufAsZeroCopyOutputStream meta_stream(&msg->meta); + EXPECT_TRUE(meta.SerializeToZeroCopyStream(&meta_stream)); + + test::EchoRequest req; + req.set_message(EXP_REQUEST); + butil::IOBufAsZeroCopyOutputStream req_stream(&msg->payload); + EXPECT_TRUE(req.SerializeToZeroCopyStream(&req_stream)); + return msg; + } + + brpc::policy::MostCommonMessage* MakeResponseMessage( + const brpc::policy::HuluRpcResponseMeta& meta) { + brpc::policy::MostCommonMessage* msg = + brpc::policy::MostCommonMessage::Get(); + butil::IOBufAsZeroCopyOutputStream meta_stream(&msg->meta); + EXPECT_TRUE(meta.SerializeToZeroCopyStream(&meta_stream)); + + test::EchoResponse res; + res.set_message(EXP_RESPONSE); + butil::IOBufAsZeroCopyOutputStream res_stream(&msg->payload); + EXPECT_TRUE(res.SerializeToZeroCopyStream(&res_stream)); + return msg; + } + + void CheckResponseCode(bool expect_empty, int expect_code) { + int bytes_in_pipe = 0; + ioctl(_pipe_fds[0], FIONREAD, &bytes_in_pipe); + if (expect_empty) { + EXPECT_EQ(0, bytes_in_pipe); + return; + } + + EXPECT_GT(bytes_in_pipe, 0); + butil::IOPortal buf; + EXPECT_EQ((ssize_t)bytes_in_pipe, + buf.append_from_file_descriptor(_pipe_fds[0], 1024)); + brpc::ParseResult pr = brpc::policy::ParseHuluMessage(&buf, NULL, false, NULL); + EXPECT_EQ(brpc::PARSE_OK, pr.error()); + brpc::policy::MostCommonMessage* msg = + static_cast(pr.message()); + + brpc::policy::HuluRpcResponseMeta meta; + butil::IOBufAsZeroCopyInputStream meta_stream(msg->meta); + EXPECT_TRUE(meta.ParseFromZeroCopyStream(&meta_stream)); + EXPECT_EQ(expect_code, meta.error_code()); + } + + void TestHuluCompress(brpc::CompressType type) { + butil::IOBuf request_buf; + butil::IOBuf total_buf; + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + cntl._response = &res; + + req.set_message(EXP_REQUEST); + cntl.set_request_compress_type(type); + brpc::SerializeRequestDefault(&request_buf, &cntl, &req); + ASSERT_FALSE(cntl.Failed()); + brpc::policy::PackHuluRequest( + &total_buf, NULL, cntl.call_id().value, + test::EchoService::descriptor()->method(0), + &cntl, request_buf, &_auth); + ASSERT_FALSE(cntl.Failed()); + + brpc::ParseResult req_pr = + brpc::policy::ParseHuluMessage(&total_buf, NULL, false, NULL); + ASSERT_EQ(brpc::PARSE_OK, req_pr.error()); + brpc::InputMessageBase* req_msg = req_pr.message(); + ProcessMessage(brpc::policy::ProcessHuluRequest, req_msg, false); + CheckResponseCode(false, 0); + } + + int _pipe_fds[2]; + brpc::SocketUniquePtr _socket; + brpc::Server _server; + + MyEchoService _svc; + MyAuthenticator _auth; +}; + +TEST_F(HuluTest, process_request_failed_socket) { + brpc::policy::HuluRpcRequestMeta meta; + meta.set_service_name("EchoService"); + meta.set_method_index(0); + brpc::policy::MostCommonMessage* msg = MakeRequestMessage(meta); + _socket->SetFailed(); + ProcessMessage(brpc::policy::ProcessHuluRequest, msg, false); + ASSERT_EQ(0ll, _server._nerror_bvar.get_value()); + CheckResponseCode(true, 0); +} + +TEST_F(HuluTest, process_request_logoff) { + brpc::policy::HuluRpcRequestMeta meta; + meta.set_service_name("EchoService"); + meta.set_method_index(0); + brpc::policy::MostCommonMessage* msg = MakeRequestMessage(meta); + _server._status = brpc::Server::READY; + ProcessMessage(brpc::policy::ProcessHuluRequest, msg, false); + ASSERT_EQ(1ll, _server._nerror_bvar.get_value()); + CheckResponseCode(false, brpc::ELOGOFF); +} + +TEST_F(HuluTest, process_request_wrong_method) { + brpc::policy::HuluRpcRequestMeta meta; + meta.set_service_name("EchoService"); + meta.set_method_index(10); + brpc::policy::MostCommonMessage* msg = MakeRequestMessage(meta); + ProcessMessage(brpc::policy::ProcessHuluRequest, msg, false); + ASSERT_EQ(1ll, _server._nerror_bvar.get_value()); + CheckResponseCode(false, brpc::ENOMETHOD); +} + +TEST_F(HuluTest, process_response_after_eof) { + brpc::policy::HuluRpcResponseMeta meta; + test::EchoResponse res; + brpc::Controller cntl; + meta.set_correlation_id(cntl.call_id().value); + cntl._response = &res; + brpc::policy::MostCommonMessage* msg = MakeResponseMessage(meta); + ProcessMessage(brpc::policy::ProcessHuluResponse, msg, true); + ASSERT_EQ(EXP_RESPONSE, res.message()); + ASSERT_TRUE(_socket->Failed()); +} + +TEST_F(HuluTest, process_response_error_code) { + const int ERROR_CODE = 12345; + brpc::policy::HuluRpcResponseMeta meta; + brpc::Controller cntl; + meta.set_correlation_id(cntl.call_id().value); + meta.set_error_code(ERROR_CODE); + brpc::policy::MostCommonMessage* msg = MakeResponseMessage(meta); + ProcessMessage(brpc::policy::ProcessHuluResponse, msg, false); + ASSERT_EQ(ERROR_CODE, cntl.ErrorCode()); +} + +TEST_F(HuluTest, complete_flow) { + butil::IOBuf request_buf; + butil::IOBuf total_buf; + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + cntl._response = &res; + + // Send request + req.set_message(EXP_REQUEST); + brpc::SerializeRequestDefault(&request_buf, &cntl, &req); + ASSERT_FALSE(cntl.Failed()); + cntl.request_attachment().append(EXP_REQUEST); + brpc::policy::PackHuluRequest( + &total_buf, NULL, cntl.call_id().value, + test::EchoService::descriptor()->method(0), &cntl, request_buf, &_auth); + ASSERT_FALSE(cntl.Failed()); + + // Verify and handle request + brpc::ParseResult req_pr = + brpc::policy::ParseHuluMessage(&total_buf, NULL, false, NULL); + ASSERT_EQ(brpc::PARSE_OK, req_pr.error()); + brpc::InputMessageBase* req_msg = req_pr.message(); + VerifyMessage(req_msg); + ProcessMessage(brpc::policy::ProcessHuluRequest, req_msg, false); + + // Read response from pipe + butil::IOPortal response_buf; + response_buf.append_from_file_descriptor(_pipe_fds[0], 1024); + brpc::ParseResult res_pr = + brpc::policy::ParseHuluMessage(&response_buf, NULL, false, NULL); + ASSERT_EQ(brpc::PARSE_OK, res_pr.error()); + brpc::InputMessageBase* res_msg = res_pr.message(); + ProcessMessage(brpc::policy::ProcessHuluResponse, res_msg, false); + + ASSERT_FALSE(cntl.Failed()); + ASSERT_EQ(EXP_RESPONSE, res.message()); +} + +TEST_F(HuluTest, close_in_callback) { + butil::IOBuf request_buf; + butil::IOBuf total_buf; + brpc::Controller cntl; + test::EchoRequest req; + + // Send request + req.set_message(EXP_REQUEST); + req.set_close_fd(true); + brpc::SerializeRequestDefault(&request_buf, &cntl, &req); + ASSERT_FALSE(cntl.Failed()); + brpc::policy::PackHuluRequest( + &total_buf, NULL, cntl.call_id().value, + test::EchoService::descriptor()->method(0), &cntl, request_buf, &_auth); + ASSERT_FALSE(cntl.Failed()); + + // Handle request + brpc::ParseResult req_pr = + brpc::policy::ParseHuluMessage(&total_buf, NULL, false, NULL); + ASSERT_EQ(brpc::PARSE_OK, req_pr.error()); + brpc::InputMessageBase* req_msg = req_pr.message(); + ProcessMessage(brpc::policy::ProcessHuluRequest, req_msg, false); + + // Socket should be closed + ASSERT_TRUE(_socket->Failed()); +} + +TEST_F(HuluTest, hulu_compress) { + TestHuluCompress(brpc::COMPRESS_TYPE_SNAPPY); + TestHuluCompress(brpc::COMPRESS_TYPE_GZIP); + TestHuluCompress(brpc::COMPRESS_TYPE_ZLIB); +} +} //namespace diff --git a/test/brpc_input_messenger_unittest.cpp b/test/brpc_input_messenger_unittest.cpp new file mode 100644 index 0000000..ae4afb6 --- /dev/null +++ b/test/brpc_input_messenger_unittest.cpp @@ -0,0 +1,225 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: Sun Jul 13 15:04:18 CST 2014 + +#include +#include +#include +#include +#include "gperftools_helper.h" +#include "butil/time.h" +#include "butil/macros.h" +#include "butil/fd_utility.h" +#include "butil/fd_guard.h" +#include "butil/unix_socket.h" +#include "brpc/acceptor.h" +#include "brpc/policy/hulu_pbrpc_protocol.h" + +void EmptyProcessHuluRequest(brpc::InputMessageBase* msg_base) { + brpc::DestroyingPtr a(msg_base); +} + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + brpc::Protocol dummy_protocol = + { brpc::policy::ParseHuluMessage, + brpc::SerializeRequestDefault, + brpc::policy::PackHuluRequest, + EmptyProcessHuluRequest, EmptyProcessHuluRequest, + NULL, NULL, NULL, + brpc::CONNECTION_TYPE_ALL, "dummy_hulu" }; + EXPECT_EQ(0, RegisterProtocol((brpc::ProtocolType)30, dummy_protocol)); + return RUN_ALL_TESTS(); +} + +class MessengerTest : public ::testing::Test{ +protected: + MessengerTest(){ + }; + virtual ~MessengerTest(){}; + virtual void SetUp() { + }; + virtual void TearDown() { + }; +}; + +#define USE_UNIX_DOMAIN_SOCKET 1 + +const size_t NEPOLL = 1; +const size_t NCLIENT = 6; +const size_t NMESSAGE = 1024; +const size_t MESSAGE_SIZE = 32; + +inline uint32_t fmix32 ( uint32_t h ) { + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + return h; +} + +volatile bool client_stop = false; + +struct BAIDU_CACHELINE_ALIGNMENT ClientMeta { + size_t times; + size_t bytes; +}; + +butil::atomic client_index(0); + +void* client_thread(void* arg) { + ClientMeta* m = (ClientMeta*)arg; + size_t offset = 0; + m->times = 0; + m->bytes = 0; + const size_t buf_cap = NMESSAGE * MESSAGE_SIZE; + char* buf = (char*)malloc(buf_cap); + for (size_t i = 0; i < NMESSAGE; ++i) { + memcpy(buf + i * MESSAGE_SIZE, "HULU", 4); + // HULU use host byte order directly... + *(uint32_t*)(buf + i * MESSAGE_SIZE + 4) = MESSAGE_SIZE - 12; + *(uint32_t*)(buf + i * MESSAGE_SIZE + 8) = 4; + } +#ifdef USE_UNIX_DOMAIN_SOCKET + const size_t id = client_index.fetch_add(1); + char socket_name[64]; + snprintf(socket_name, sizeof(socket_name), "input_messenger.socket%lu", + (id % NEPOLL)); + butil::fd_guard fd(butil::unix_socket_connect(socket_name)); + if (fd < 0) { + PLOG(FATAL) << "Fail to connect to " << socket_name; + return NULL; + } +#else + butil::EndPoint point(butil::IP_ANY, 7878); + butil::fd_guard fd(butil::tcp_connect(point, NULL)); + if (fd < 0) { + PLOG(FATAL) << "Fail to connect to " << point; + return NULL; + } +#endif + + while (!client_stop) { + ssize_t n; + if (offset == 0) { + n = write(fd, buf, buf_cap); + } else { + iovec v[2]; + v[0].iov_base = buf + offset; + v[0].iov_len = buf_cap - offset; + v[1].iov_base = buf; + v[1].iov_len = offset; + n = writev(fd, v, 2); + } + if (n < 0) { + if (errno != EINTR) { + PLOG(FATAL) << "Fail to write fd=" << fd; + return NULL; + } + } else { + ++m->times; + m->bytes += n; + offset += n; + if (offset >= buf_cap) { + offset -= buf_cap; + } + } + } + free(buf); + return NULL; +} + +TEST_F(MessengerTest, dispatch_tasks) { + client_stop = false; + + brpc::Acceptor messenger[NEPOLL]; + pthread_t cth[NCLIENT]; + ClientMeta* cm[NCLIENT]; + + const brpc::InputMessageHandler pairs[] = { + { brpc::policy::ParseHuluMessage, + EmptyProcessHuluRequest, NULL, NULL, "dummy_hulu" } + }; + + for (size_t i = 0; i < NEPOLL; ++i) { +#ifdef USE_UNIX_DOMAIN_SOCKET + char buf[64]; + snprintf(buf, sizeof(buf), "input_messenger.socket%lu", i); + int listening_fd = butil::unix_socket_listen(buf); +#else + int listening_fd = tcp_listen(butil::EndPoint(butil::IP_ANY, 7878)); +#endif + ASSERT_TRUE(listening_fd > 0); + butil::make_non_blocking(listening_fd); + ASSERT_EQ(0, messenger[i].AddHandler(pairs[0])); + ASSERT_EQ(0, messenger[i].StartAccept(listening_fd, -1, NULL, false)); + } + + for (size_t i = 0; i < NCLIENT; ++i) { + cm[i] = new ClientMeta; + cm[i]->times = 0; + cm[i]->bytes = 0; + ASSERT_EQ(0, pthread_create(&cth[i], NULL, client_thread, cm[i])); + } + + sleep(1); + + + LOG(INFO) << "Begin to profile... (5 seconds)"; + ProfilerStart("input_messenger.prof"); + + size_t start_client_bytes = 0; + for (size_t i = 0; i < NCLIENT; ++i) { + start_client_bytes += cm[i]->bytes; + } + butil::Timer tm; + tm.start(); + + sleep(5); + + tm.stop(); + ProfilerStop(); + LOG(INFO) << "End profiling"; + + client_stop = true; + + size_t client_bytes = 0; + for (size_t i = 0; i < NCLIENT; ++i) { + client_bytes += cm[i]->bytes; + } + LOG(INFO) << "client_tp=" << (client_bytes - start_client_bytes) / (double)tm.u_elapsed() + << "MB/s client_msg=" + << (client_bytes - start_client_bytes) * 1000000L / (MESSAGE_SIZE * tm.u_elapsed()) + << "/s"; + + for (size_t i = 0; i < NCLIENT; ++i) { + pthread_join(cth[i], NULL); + printf("joined client %lu\n", i); + } + for (size_t i = 0; i < NEPOLL; ++i) { + messenger[i].StopAccept(0); + } + sleep(1); + for (size_t i = 0; i < NCLIENT; ++i) { + delete cm[i]; + } + LOG(WARNING) << "begin to exit!!!!"; +} diff --git a/test/brpc_interceptor_unittest.cpp b/test/brpc_interceptor_unittest.cpp new file mode 100644 index 0000000..6238c3f --- /dev/null +++ b/test/brpc_interceptor_unittest.cpp @@ -0,0 +1,201 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include "brpc/policy/sofa_pbrpc_protocol.h" +#include "brpc/channel.h" +#include "brpc/server.h" +#include "brpc/interceptor.h" +#include "brpc/nshead_service.h" +#include "echo.pb.h" + +namespace brpc { +namespace policy { +DECLARE_bool(use_http_error_code); +} +} + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + return RUN_ALL_TESTS(); +} + +const int EREJECT = 4000; +int g_index = 0; +const int port = 8613; +const std::string EXP_REQUEST = "hello"; +const std::string EXP_RESPONSE = "world"; +const std::string NSHEAD_EXP_RESPONSE = "error"; + +class EchoServiceImpl : public ::test::EchoService { +public: + EchoServiceImpl() = default; + ~EchoServiceImpl() override = default; + void Echo(google::protobuf::RpcController* cntl_base, + const ::test::EchoRequest* request, + ::test::EchoResponse* response, + google::protobuf::Closure* done) override { + brpc::ClosureGuard done_guard(done); + ASSERT_EQ(EXP_REQUEST, request->message()); + response->set_message(EXP_RESPONSE); + } +}; + +// Adapt your own nshead-based protocol to use brpc +class MyNsheadProtocol : public brpc::NsheadService { +public: + void ProcessNsheadRequest(const brpc::Server&, + brpc::Controller* cntl, + const brpc::NsheadMessage& request, + brpc::NsheadMessage* response, + brpc::NsheadClosure* done) { + // This object helps you to call done->Run() in RAII style. If you need + // to process the request asynchronously, pass done_guard.release(). + brpc::ClosureGuard done_guard(done); + + response->head = request.head; + if (cntl->Failed()) { + ASSERT_TRUE(cntl->Failed()); + ASSERT_EQ(EREJECT, cntl->ErrorCode()); + response->body.append(NSHEAD_EXP_RESPONSE); + return; + } + response->body.append(EXP_RESPONSE); + } +}; + +class MyInterceptor : public brpc::Interceptor { +public: + MyInterceptor() = default; + + ~MyInterceptor() override = default; + + bool Accept(const brpc::Controller* controller, + int& error_code, + std::string& error_txt) const override { + if (g_index % 2 == 0) { + error_code = EREJECT; + error_txt = "reject g_index=0"; + return false; + } + + return true; + } +}; + +class InterceptorTest : public ::testing::Test { +public: + InterceptorTest() { + EXPECT_EQ(0, _server.AddService(&_echo_svc, + brpc::SERVER_DOESNT_OWN_SERVICE)); + brpc::ServerOptions options; + options.interceptor = new MyInterceptor; + options.nshead_service = new MyNsheadProtocol; + options.server_owns_interceptor = true; + EXPECT_EQ(0, _server.Start(port, &options)); + } + + ~InterceptorTest() override = default; + + static void CallMethod(test::EchoService_Stub& stub, + ::test::EchoRequest& req, + ::test::EchoResponse& res) { + for (g_index = 0; g_index < 1000; ++g_index) { + brpc::Controller cntl; + stub.Echo(&cntl, &req, &res, NULL); + if (g_index % 2 == 0) { + ASSERT_TRUE(cntl.Failed()); + ASSERT_EQ(EREJECT, cntl.ErrorCode()); + } else { + ASSERT_FALSE(cntl.Failed()); + EXPECT_EQ(EXP_RESPONSE, res.message()) << cntl.ErrorText(); + } + } + } + +private: + brpc::Server _server; + EchoServiceImpl _echo_svc; +}; + +TEST_F(InterceptorTest, sanity) { + ::test::EchoRequest req; + ::test::EchoResponse res; + req.set_message(EXP_REQUEST); + + // PROTOCOL_BAIDU_STD + { + brpc::Channel channel; + brpc::ChannelOptions options; + ASSERT_EQ(0, channel.Init("localhost", port, &options)); + test::EchoService_Stub stub(&channel); + CallMethod(stub, req, res); + } + + // PROTOCOL_HTTP + { + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_HTTP; + ASSERT_EQ(0, channel.Init("localhost", port, &options)); + test::EchoService_Stub stub(&channel); + // Set the x-bd-error-code header of http response to brpc error code. + brpc::policy::FLAGS_use_http_error_code = true; + CallMethod(stub, req, res); + } + + // PROTOCOL_HULU_PBRPC + { + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_HULU_PBRPC; + ASSERT_EQ(0, channel.Init("localhost", port, &options)); + test::EchoService_Stub stub(&channel); + CallMethod(stub, req, res); + } + + // PROTOCOL_SOFA_PBRPC + { + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_SOFA_PBRPC; + ASSERT_EQ(0, channel.Init("localhost", port, &options)); + test::EchoService_Stub stub(&channel); + CallMethod(stub, req, res); + } + + // PROTOCOL_NSHEAD + { + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_NSHEAD; + ASSERT_EQ(0, channel.Init("localhost", port, &options)); + brpc::NsheadMessage request; + for (g_index = 0; g_index < 1000; ++g_index) { + brpc::Controller cntl; + brpc::NsheadMessage response; + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + if (g_index % 2 == 0) { + ASSERT_EQ(NSHEAD_EXP_RESPONSE, response.body.to_string()); + } else { + ASSERT_EQ(EXP_RESPONSE, response.body.to_string()); + } + } + } +} \ No newline at end of file diff --git a/test/brpc_load_balancer_unittest.cpp b/test/brpc_load_balancer_unittest.cpp new file mode 100644 index 0000000..757f9c9 --- /dev/null +++ b/test/brpc_load_balancer_unittest.cpp @@ -0,0 +1,1401 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: Sun Jul 13 15:04:18 CST 2014 + +#include +#include +#include +#include "bthread/bthread.h" +#include "gperftools_helper.h" +#include "butil/compiler_specific.h" +#include "butil/containers/doubly_buffered_data.h" +#include "brpc/describable.h" +#include "brpc/socket.h" +#include "brpc/socket_map.h" +#include "brpc/global.h" +#include "brpc/details/load_balancer_with_naming.h" +#include "butil/strings/string_number_conversions.h" +#include "brpc/policy/weighted_round_robin_load_balancer.h" +#include "brpc/policy/round_robin_load_balancer.h" +#include "brpc/policy/weighted_randomized_load_balancer.h" +#include "brpc/policy/randomized_load_balancer.h" +#include "brpc/policy/locality_aware_load_balancer.h" +#include "brpc/policy/consistent_hashing_load_balancer.h" +#include "brpc/policy/hasher.h" +#include "echo.pb.h" +#include "brpc/channel.h" +#include "brpc/server.h" + +namespace brpc { +DECLARE_int32(health_check_interval); +DECLARE_int64(detect_available_server_interval_ms); +namespace policy { +extern uint32_t CRCHash32(const char *key, size_t len); +extern const char* GetHashName(uint32_t (*hasher)(const void* key, size_t len)); +}} + +namespace { +void initialize_random() { + srand(time(0)); +} +pthread_once_t initialize_random_control = PTHREAD_ONCE_INIT; + +class LoadBalancerTest : public ::testing::Test{ +protected: + LoadBalancerTest(){ + pthread_once(&initialize_random_control, initialize_random); + }; + virtual ~LoadBalancerTest(){}; + virtual void SetUp() { + }; + virtual void TearDown() { + }; +}; + +class UserTLS {}; + +struct Foo { + Foo() : x(0) {} + int x; +}; + +bool AddN(Foo& f, int n) { + f.x += n; + return true; +} + +void read_cb(const Foo& f) { + ASSERT_EQ(0, f.x); +} + +struct CallableObj { + void operator()(const Foo& f) { + ASSERT_EQ(0, f.x); + } +}; + +template +void test_doubly_buffered_data() { + // test doubly_buffered_data TLS limits + { + std::cout << "current PTHREAD_KEYS_MAX: " << PTHREAD_KEYS_MAX << std::endl; + DBD data[PTHREAD_KEYS_MAX + 1]; + typename DBD::ScopedPtr ptr; + ASSERT_EQ(0, data[PTHREAD_KEYS_MAX].Read(&ptr)); + ASSERT_EQ(0, ptr->x); + } + + DBD d; + { + typename DBD::ScopedPtr ptr; + ASSERT_EQ(0, d.Read(&ptr)); + ASSERT_EQ(0, ptr->x); + } + { + ASSERT_EQ(0, d.Read([](const Foo& f) { + ASSERT_EQ(0, f.x); + })); + ASSERT_EQ(0, d.Read(read_cb)); + ASSERT_EQ(0, d.Read(CallableObj())); + CallableObj co; + ASSERT_EQ(0, d.Read(co)); + } + { + typename DBD::ScopedPtr ptr; + ASSERT_EQ(0, d.Read(&ptr)); + ASSERT_EQ(0, ptr->x); + } + + d.Modify(AddN, 10); + d.Modify([](Foo& f, int n) -> size_t { + f.x += n; + return 1; + }, 10); + { + typename DBD::ScopedPtr ptr; + ASSERT_EQ(0, d.Read(&ptr)); + ASSERT_EQ(20, ptr->x); + } +} + +TEST_F(LoadBalancerTest, doubly_buffered_data) { + test_doubly_buffered_data>(); + test_doubly_buffered_data>(); + test_doubly_buffered_data>(); + test_doubly_buffered_data>(); +} + +bool exitFlag = false; + +template +void* DBDBthread(void* arg) { + auto d = static_cast(arg); + while(!exitFlag){ + typename DBD::ScopedPtr ptr; + d->Read(&ptr); + + // If DBD is DoublyBufferedData, may cause deadlock. + bthread_usleep(100 * 1000); + } + + return NULL; +} + +template +void DBDMultiBthread() { + DBD d; + d.Modify(AddN, 1); + { + typename DBD::ScopedPtr ptr; + ASSERT_EQ(0, d.Read(&ptr)); + ASSERT_EQ(1, ptr->x); + } + + bthread_t tids[10000]; + for (size_t i = 0; i < ARRAY_SIZE(tids); ++i) { + ASSERT_EQ(0, bthread_start_urgent(&tids[i], NULL, DBDBthread, &d)); + } + + // Modify during reading. + int64_t start = butil::cpuwide_time_ms(); + while (butil::cpuwide_time_ms() - start < 10 * 1000) { + d.Modify(AddN, 1); + typename DBD::ScopedPtr ptr; + d.Read(&ptr); + usleep(100 * 1000); + } + exitFlag = true; + for (size_t i = 0; i < ARRAY_SIZE(tids); ++i) { + ASSERT_EQ(0, bthread_join(tids[i], NULL)); + } +} + +// Deadlock, only for test. +// TEST_F(LoadBalancerTest, doubly_buffered_data_multi_bthread) { +// DBDMultiBthread>(); +// DBDMultiBthread>(); +// } + +TEST_F(LoadBalancerTest, doubly_buffered_data_bthread_multi_bthread) { + DBDMultiBthread>(); +} + + +bool g_started = false; +bool g_stopped = false; +int g_prof_name_counter = 0; + +using PerfMap = std::unordered_map; + +bool AddMapN(PerfMap& f, int n) { + ++f[n]; + return true; +} + +template +struct BAIDU_CACHELINE_ALIGNMENT PerfArgs { + DBD* dbd; + int64_t counter; + int64_t elapse_ns; + bool ready; + + PerfArgs() : dbd(NULL), counter(0), elapse_ns(0), ready(false) {} +}; + +template +void* read_dbd(void* void_arg) { + auto args = (PerfArgs*)void_arg; + args->ready = true; + butil::Timer t; + while (!g_stopped) { + if (g_started) { + break; + } + bthread_usleep(10); + } + t.start(); + while (!g_stopped) { + { + typename DBD::ScopedPtr ptr; + args->dbd->Read(&ptr); + // ptr->find(1); + } + ++args->counter; + } + t.stop(); + args->elapse_ns = t.n_elapsed(); + return NULL; +} + +template +void PerfTest(int thread_num, bool modify_during_reading) { + g_started = false; + g_stopped = false; + DBD dbd; + for (int i = 0; i < 1024; ++i) { + dbd.Modify(AddMapN, i); + } + pthread_t threads[thread_num]; + std::vector> args(thread_num); + for (int i = 0; i < thread_num; ++i) { + args[i].dbd = &dbd; + ASSERT_EQ(0, pthread_create(&threads[i], NULL, read_dbd, &args[i])); + } + while (true) { + bool all_ready = true; + for (int i = 0; i < thread_num; ++i) { + if (!args[i].ready) { + all_ready = false; + break; + } + } + if (all_ready) { + break; + } + usleep(1000); + } + g_started = true; + char prof_name[32]; + snprintf(prof_name, sizeof(prof_name), "doubly_buffered_data_%d.prof", ++g_prof_name_counter); + ProfilerStart(prof_name); + int64_t run_ms = 5 * 1000; + if (modify_during_reading) { + int64_t start = butil::cpuwide_time_ms(); + int i = 1; + while (butil::cpuwide_time_ms() - start < run_ms) { + ASSERT_TRUE(dbd.Modify(AddMapN, i++)); + usleep(1000); + } + } else { + usleep(run_ms * 1000); + } + ProfilerStop(); + g_stopped = true; + int64_t wait_time = 0; + int64_t count = 0; + for (int i = 0; i < thread_num; ++i) { + pthread_join(threads[i], NULL); + wait_time += args[i].elapse_ns; + count += args[i].counter; + } + LOG(INFO) << butil::class_name() + << " thread_num=" << thread_num + << " modify_during_reading=" << modify_during_reading + << " count=" << count + << " average_time=" << wait_time / (double)count + << " qps=" << (double)count / wait_time * (1000 * 1000 * 1000); +} + +TEST_F(LoadBalancerTest, dbd_performance) { + int thread_num = 1; + PerfTest>(thread_num, false); + PerfTest>(thread_num, true); + PerfTest>(thread_num, false); + PerfTest>(thread_num, true); + + thread_num = 4; + PerfTest>(thread_num, false); + PerfTest>(thread_num, true); + PerfTest>(thread_num, false); + PerfTest>(thread_num, true); + + thread_num = 8; + PerfTest>(thread_num, false); + PerfTest>(thread_num, true); + PerfTest>(thread_num, false); + PerfTest>(thread_num, true); + + thread_num = 16; + PerfTest>(thread_num, false); + PerfTest>(thread_num, true); + PerfTest>(thread_num, false); + PerfTest>(thread_num, true); +} + + +typedef brpc::policy::LocalityAwareLoadBalancer LALB; + +static void ValidateWeightTree( + std::vector & weight_tree) { + std::vector weight_sum; + weight_sum.resize(weight_tree.size()); + for (ssize_t i = weight_tree.size() - 1; i >= 0; --i) { + const size_t left_child = i * 2 + 1; + const size_t right_child = i * 2 + 2; + weight_sum[i] = weight_tree[i].weight->volatile_value(); + if (left_child < weight_sum.size()) { + weight_sum[i] += weight_sum[left_child]; + } + if (right_child < weight_sum.size()) { + weight_sum[i] += weight_sum[right_child]; + } + } + for (size_t i = 0; i < weight_tree.size(); ++i) { + const int64_t left = weight_tree[i].left->load(butil::memory_order_relaxed); + size_t left_child = i * 2 + 1; + if (left_child < weight_tree.size()) { + ASSERT_EQ(weight_sum[left_child], left) << "i=" << i; + } else { + ASSERT_EQ(0, left); + } + } +} + +static void ValidateLALB(LALB& lalb, size_t N) { + LALB::Servers* d = lalb._db_servers._data; + for (size_t R = 0; R < 2; ++R) { + ASSERT_EQ(d[R].weight_tree.size(), N); + ASSERT_EQ(d[R].server_map.size(), N); + } + ASSERT_EQ(lalb._left_weights.size(), N); + int64_t total = 0; + for (size_t i = 0; i < N; ++i) { + ASSERT_EQ(d[0].weight_tree[i].server_id, d[1].weight_tree[i].server_id); + ASSERT_EQ(d[0].weight_tree[i].weight, d[1].weight_tree[i].weight); + for (size_t R = 0; R < 2; ++R) { + ASSERT_EQ((int64_t*)d[R].weight_tree[i].left, &lalb._left_weights[i]); + size_t* pindex = d[R].server_map.seek(d[R].weight_tree[i].server_id); + ASSERT_TRUE(pindex != NULL && *pindex == i); + } + total += d[0].weight_tree[i].weight->volatile_value(); + } + ValidateWeightTree(d[0].weight_tree); + ASSERT_EQ(total, lalb._total.load()); +} + +TEST_F(LoadBalancerTest, la_sanity) { + LALB lalb; + ASSERT_EQ(0, lalb._total.load()); + std::vector ids; + const size_t N = 256; + size_t cur_count = 0; + + for (int REP = 0; REP < 5; ++REP) { + const size_t before_adding = cur_count; + for (; cur_count < N; ++cur_count) { + char addr[32]; + snprintf(addr, sizeof(addr), "192.168.1.%d:8080", (int)cur_count); + butil::EndPoint dummy; + ASSERT_EQ(0, str2endpoint(addr, &dummy)); + brpc::ServerId id(8888); + brpc::SocketOptions options; + options.remote_side = dummy; + ASSERT_EQ(0, brpc::Socket::Create(options, &id.id)); + ids.push_back(id); + ASSERT_TRUE(lalb.AddServer(id)); + } + std::cout << "Added " << cur_count - before_adding << std::endl; + ValidateLALB(lalb, cur_count); + + const size_t before_removal = cur_count; + std::random_shuffle(ids.begin(), ids.end()); + for (size_t i = 0; i < N / 2; ++i) { + const brpc::ServerId id = ids.back(); + ids.pop_back(); + --cur_count; + ASSERT_TRUE(lalb.RemoveServer(id)) << "i=" << i; + ASSERT_EQ(0, brpc::Socket::SetFailed(id.id)); + } + std::cout << "Removed " << before_removal - cur_count << std::endl; + ValidateLALB(lalb, cur_count); + } + + for (size_t i = 0; i < ids.size(); ++i) { + ASSERT_EQ(0, brpc::Socket::SetFailed(ids[i].id)); + } +} + +typedef std::map CountMap; +volatile bool global_stop = false; + +struct SelectArg { + brpc::LoadBalancer *lb; + uint32_t (*hash)(const void*, size_t); +}; + +void* select_server(void* arg) { + SelectArg *sa = (SelectArg*)arg; + brpc::LoadBalancer* c = sa->lb; + brpc::SocketUniquePtr ptr; + CountMap *selected_count = new CountMap; + brpc::LoadBalancer::SelectIn in = { 0, false, false, 0u, NULL }; + brpc::LoadBalancer::SelectOut out(&ptr); + uint32_t rand_seed = rand(); + if (sa->hash) { + uint32_t rd = ++rand_seed; + in.has_request_code = true; + in.request_code = sa->hash((const char *)&rd, sizeof(uint32_t)); + } + int ret = 0; + while (!global_stop && (ret = c->SelectServer(in, &out)) == 0) { + if (sa->hash) { + uint32_t rd = ++rand_seed; + in.has_request_code = true; + in.request_code = sa->hash((const char *)&rd, sizeof(uint32_t)); + } + ++(*selected_count)[ptr->id()]; + } + LOG_IF(INFO, ret != 0) << "select_server[" << pthread_self() + << "] quits before of " << berror(ret); + return selected_count; +} + +brpc::SocketId recycled_sockets[1024]; +butil::atomic nrecycle(0); +class SaveRecycle : public brpc::SocketUser { + void BeforeRecycle(brpc::Socket* s) { + recycled_sockets[nrecycle.fetch_add(1, butil::memory_order_relaxed)] = s->id(); + delete this; + } +}; + +TEST_F(LoadBalancerTest, update_while_selection) { + for (size_t round = 0; round < 5; ++round) { + brpc::LoadBalancer* lb = NULL; + SelectArg sa = { NULL, NULL}; + bool is_lalb = false; + if (round == 0) { + lb = new brpc::policy::RoundRobinLoadBalancer; + } else if (round == 1) { + lb = new brpc::policy::RandomizedLoadBalancer; + } else if (round == 2) { + lb = new LALB; + is_lalb = true; + } else if (round == 3) { + lb = new brpc::policy::WeightedRoundRobinLoadBalancer; + } else { + lb = new brpc::policy::ConsistentHashingLoadBalancer(brpc::policy::CONS_HASH_LB_MURMUR3); + sa.hash = ::brpc::policy::MurmurHash32; + } + sa.lb = lb; + + // Accessing empty lb should result in error. + brpc::SocketUniquePtr ptr; + brpc::LoadBalancer::SelectIn in = { 0, false, true, 0, NULL }; + brpc::LoadBalancer::SelectOut out(&ptr); + ASSERT_EQ(ENODATA, lb->SelectServer(in, &out)); + + nrecycle = 0; + global_stop = false; + pthread_t th[8]; + std::vector ids; + brpc::SocketId wrr_sid_logoff = -1; + for (int i = 0; i < 256; ++i) { + char addr[32]; + snprintf(addr, sizeof(addr), "192.%d.1.%d:8080", i, i); + butil::EndPoint dummy; + ASSERT_EQ(0, str2endpoint(addr, &dummy)); + brpc::ServerId id(8888); + if (3 == round) { + if (i < 255) { + id.tag = "1"; + } else { + id.tag = "200000000"; + } + } + brpc::SocketOptions options; + options.remote_side = dummy; + options.user = new SaveRecycle; + ASSERT_EQ(0, brpc::Socket::Create(options, &id.id)); + ids.push_back(id); + ASSERT_TRUE(lb->AddServer(id)); + if (round == 3 && i == 255) { + wrr_sid_logoff = id.id; + // In case of wrr, set 255th socket with huge weight logoff. + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id.id, &ptr)); + ptr->SetLogOff(); + } + } + std::cout << "Time " << butil::class_name_str(*lb) << " ..." << std::endl; + butil::Timer tm; + tm.start(); + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, pthread_create(&th[i], NULL, select_server, &sa)); + } + std::vector removed; + const size_t REP = 200; + for (size_t k = 0; k < REP; ++k) { + if (round != 3) { + removed = ids; + } else { + removed.assign(ids.begin(), ids.begin() + 255); + } + std::random_shuffle(removed.begin(), removed.end()); + removed.pop_back(); + ASSERT_EQ(removed.size(), lb->RemoveServersInBatch(removed)); + ASSERT_EQ(removed.size(), lb->AddServersInBatch(removed)); + // // 1: Don't remove first server, otherwise select_server would quit. + // for (size_t i = 1/*1*/; i < removed.size(); ++i) { + // ASSERT_TRUE(lb->RemoveServer(removed[i])); + // } + // for (size_t i = 1; i < removed.size(); ++i) { + // ASSERT_TRUE(lb->AddServer(removed[i])); + // } + if (is_lalb) { + LALB* lalb = (LALB*)lb; + ValidateLALB(*lalb, ids.size()); + ASSERT_GT(lalb->_total.load(), 0); + } + } + global_stop = true; + LOG(INFO) << "Stop all..."; + + void* retval[ARRAY_SIZE(th)]; + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, pthread_join(th[i], &retval[i])); + } + tm.stop(); + + CountMap total_count; + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + CountMap* selected_count = (CountMap*)retval[i]; + size_t count = 0; + for (CountMap::const_iterator it = selected_count->begin(); + it != selected_count->end(); ++it) { + total_count[it->first] += it->second; + count += it->second; + } + delete selected_count; + + std::cout << "thread " << i << " selected " + << count * 1000000L / tm.u_elapsed() << " times/s" + << std::endl; + } + size_t id_num = ids.size(); + if (round == 3) { + // Do not include the logoff socket. + id_num -= 1; + } + ASSERT_EQ(id_num, total_count.size()); + for (size_t i = 0; i < id_num; ++i) { + ASSERT_NE(0, total_count[ids[i].id]) << "i=" << i; + std::cout << i << "=" << total_count[ids[i].id] << " "; + } + std::cout << std::endl; + + for (size_t i = 0; i < id_num; ++i) { + ASSERT_EQ(0, brpc::Socket::SetFailed(ids[i].id)); + } + ASSERT_EQ(ids.size(), nrecycle); + brpc::SocketId id = -1; + for (size_t i = 0; i < ids.size(); ++i) { + id = recycled_sockets[i]; + if (id != wrr_sid_logoff) { + ASSERT_EQ(1UL, total_count.erase(id)); + } else { + ASSERT_EQ(0UL, total_count.erase(id)); + } + } + delete lb; + } +} + +TEST_F(LoadBalancerTest, fairness) { + for (size_t round = 0; round < 6; ++round) { + brpc::LoadBalancer* lb = NULL; + SelectArg sa = { NULL, NULL}; + if (round == 0) { + lb = new brpc::policy::RoundRobinLoadBalancer; + } else if (round == 1) { + lb = new brpc::policy::RandomizedLoadBalancer; + } else if (round == 2) { + lb = new LALB; + } else if (3 == round || 4 == round) { + lb = new brpc::policy::WeightedRoundRobinLoadBalancer; + } else { + lb = new brpc::policy::ConsistentHashingLoadBalancer(brpc::policy::CONS_HASH_LB_MURMUR3); + sa.hash = brpc::policy::MurmurHash32; + } + sa.lb = lb; + + std::string lb_name = butil::class_name_str(*lb); + // Remove namespace + size_t ns_pos = lb_name.find_last_of(':'); + if (ns_pos != std::string::npos) { + lb_name = lb_name.substr(ns_pos + 1); + } + + nrecycle = 0; + global_stop = false; + pthread_t th[8]; + std::vector ids; + for (int i = 0; i < 256; ++i) { + char addr[32]; + snprintf(addr, sizeof(addr), "192.168.1.%d:8080", i); + butil::EndPoint dummy; + ASSERT_EQ(0, str2endpoint(addr, &dummy)); + brpc::ServerId id(8888); + if (3 == round) { + id.tag = "100"; + } else if (4 == round) { + if ( i % 50 == 0) { + id.tag = std::to_string(i*2 + butil::fast_rand_less_than(40) + 80); + } else { + id.tag = std::to_string(butil::fast_rand_less_than(40) + 80); + } + } + brpc::SocketOptions options; + options.remote_side = dummy; + options.user = new SaveRecycle; + ASSERT_EQ(0, brpc::Socket::Create(options, &id.id)); + ids.push_back(id); + lb->AddServer(id); + } + + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, pthread_create(&th[i], NULL, select_server, &sa)); + } + bthread_usleep(10000); + ProfilerStart((lb_name + ".prof").c_str()); + bthread_usleep(300000); + ProfilerStop(); + + global_stop = true; + + CountMap total_count; + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + void* retval; + ASSERT_EQ(0, pthread_join(th[i], &retval)); + CountMap* selected_count = (CountMap*)retval; + ASSERT_TRUE(selected_count); + int first_count = 0; + for (CountMap::const_iterator it = selected_count->begin(); + it != selected_count->end(); ++it) { + if (round == 0) { + if (first_count == 0) { + first_count = it->second; + } else { + // Load is not ensured to be fair inside each thread + // ASSERT_LE(abs(first_count - it->second), 1); + } + } + total_count[it->first] += it->second; + } + delete selected_count; + } + ASSERT_EQ(ids.size(), total_count.size()); + size_t count_sum = 0; + size_t count_squared_sum = 0; + std::cout << lb_name << ':' << '\n'; + + if (round != 3 && round !=4) { + for (size_t i = 0; i < ids.size(); ++i) { + size_t count = total_count[ids[i].id]; + ASSERT_NE(0ul, count) << "i=" << i; + std::cout << i << '=' << count << ' '; + count_sum += count; + count_squared_sum += count * count; + } + + std::cout << '\n' + << ": average=" << count_sum/ids.size() + << " deviation=" << sqrt(count_squared_sum * ids.size() + - count_sum * count_sum) / ids.size() << std::endl; + } else { // for weighted round robin load balancer + std::cout << "configured weight: " << std::endl; + std::ostringstream os; + brpc::DescribeOptions opt; + lb->Describe(os, opt); + std::cout << os.str() << std::endl; + double scaling_count_sum = 0.0; + double scaling_count_squared_sum = 0.0; + for (size_t i = 0; i < ids.size(); ++i) { + size_t count = total_count[ids[i].id]; + ASSERT_NE(0ul, count) << "i=" << i; + std::cout << i << '=' << count << ' '; + double scaling_count = static_cast(count) / std::stoi(ids[i].tag); + scaling_count_sum += scaling_count; + scaling_count_squared_sum += scaling_count * scaling_count; + } + std::cout << '\n' + << ": scaling average=" << scaling_count_sum/ids.size() + << " scaling deviation=" << sqrt(scaling_count_squared_sum * ids.size() + - scaling_count_sum * scaling_count_sum) / ids.size() << std::endl; + } + for (size_t i = 0; i < ids.size(); ++i) { + ASSERT_EQ(0, brpc::Socket::SetFailed(ids[i].id)); + } + ASSERT_EQ(ids.size(), nrecycle); + for (size_t i = 0; i < ids.size(); ++i) { + ASSERT_EQ(1UL, total_count.erase(recycled_sockets[i])); + } + delete lb; + } +} + +TEST_F(LoadBalancerTest, consistent_hashing) { + ::brpc::policy::HashFunc hashs[::brpc::policy::CONS_HASH_LB_LAST] = { + ::brpc::policy::MurmurHash32, + ::brpc::policy::MD5Hash32, + ::brpc::policy::MD5Hash32 + // ::brpc::policy::CRCHash32 crc is a bad hash function in test + }; + + ::brpc::policy::ConsistentHashingLoadBalancerType hash_type[::brpc::policy::CONS_HASH_LB_LAST] = { + ::brpc::policy::CONS_HASH_LB_MURMUR3, + ::brpc::policy::CONS_HASH_LB_MD5, + ::brpc::policy::CONS_HASH_LB_KETAMA + }; + + const char* servers[] = { + "10.92.115.19:8833", + "10.42.108.25:8833", + "10.36.150.32:8833", + "10.92.149.48:8833", + "10.42.122.201:8833", + "[2408:871a:2100:3:0:ff:b025:348d]:8833", + "unix:test.sock", + }; + for (size_t round = 0; round < ARRAY_SIZE(hashs); ++round) { + brpc::policy::ConsistentHashingLoadBalancer chlb(hash_type[round]); + std::vector ids; + std::vector addrs; + for (int j = 0;j < 5; ++j) { + for (size_t i = 0; i < ARRAY_SIZE(servers); ++i) { + const char *addr = servers[i]; + //snprintf(addr, sizeof(addr), "192.168.1.%d:8080", i); + butil::EndPoint dummy; + ASSERT_EQ(0, str2endpoint(addr, &dummy)); + brpc::ServerId id(8888); + brpc::SocketOptions options; + options.remote_side = dummy; + options.user = new SaveRecycle; + ASSERT_EQ(0, brpc::Socket::Create(options, &id.id)); + ids.push_back(id); + addrs.push_back(dummy); + chlb.AddServer(id); + } + } + std::cout << chlb; + for (int i = 0; i < 5; ++i) { + std::vector empty; + chlb.AddServersInBatch(empty); + chlb.RemoveServersInBatch(empty); + std::cout << chlb; + } + const size_t SELECT_TIMES = 1000000; + std::map times; + brpc::SocketUniquePtr ptr; + brpc::LoadBalancer::SelectIn in = { 0, false, false, 0u, NULL }; + ::brpc::LoadBalancer::SelectOut out(&ptr); + for (size_t i = 0; i < SELECT_TIMES; ++i) { + in.has_request_code = true; + in.request_code = hashs[round]((const char *)&i, sizeof(i)); + chlb.SelectServer(in, &out); + ++times[ptr->remote_side()]; + } + std::map load_map; + chlb.GetLoads(&load_map); + ASSERT_EQ(times.size(), load_map.size()); + double load_sum = 0;; + double load_sqr_sum = 0; + for (size_t i = 0; i < addrs.size(); ++i) { + double normalized_load = + (double)times[addrs[i]] / SELECT_TIMES / load_map[addrs[i]]; + std::cout << i << '=' << normalized_load << ' '; + load_sum += normalized_load; + load_sqr_sum += normalized_load * normalized_load; + } + std::cout << '\n'; + std::cout << "average_normalized_load=" << load_sum / addrs.size() + << " deviation=" + << sqrt(load_sqr_sum * addrs.size() - load_sum * load_sum) / addrs.size() + << '\n'; + for (size_t i = 0; i < ids.size(); ++i) { + ASSERT_EQ(0, brpc::Socket::SetFailed(ids[i].id)); + } + } +} + +TEST_F(LoadBalancerTest, weighted_round_robin) { + const char* servers[] = { + "10.92.115.19:8831", + "10.42.108.25:8832", + "10.36.150.32:8833", + "10.36.150.32:8899", + "10.92.149.48:8834", + "10.42.122.201:8835", + "10.42.122.202:8836" + }; + std::string weight[] = {"3", "2", "7", "200000000", "1ab", "-1", "0"}; + std::map configed_weight; + brpc::policy::WeightedRoundRobinLoadBalancer wrrlb; + + // Add server to selected list. The server with invalid weight will be skipped. + for (size_t i = 0; i < ARRAY_SIZE(servers); ++i) { + const char *addr = servers[i]; + butil::EndPoint dummy; + ASSERT_EQ(0, str2endpoint(addr, &dummy)); + brpc::ServerId id(8888); + brpc::SocketOptions options; + options.remote_side = dummy; + options.user = new SaveRecycle; + ASSERT_EQ(0, brpc::Socket::Create(options, &id.id)); + id.tag = weight[i]; + if (i == 3) { + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id.id, &ptr)); + ptr->SetLogOff(); + } + if ( i < 4 ) { + int weight_num = 0; + ASSERT_TRUE(butil::StringToInt(weight[i], &weight_num)); + configed_weight[dummy] = weight_num; + EXPECT_TRUE(wrrlb.AddServer(id)); + } else { + EXPECT_FALSE(wrrlb.AddServer(id)); + } + } + + // Select the best server according to weight configured. + // There are 3 valid servers with weight 3, 2 and 7 respectively. + // We run SelectServer for 12 times. The result number of each server selected should be + // consistent with weight configured. + std::map select_result; + brpc::SocketUniquePtr ptr; + brpc::LoadBalancer::SelectIn in = { 0, false, false, 0u, NULL }; + brpc::LoadBalancer::SelectOut out(&ptr); + int total_weight = 12; + std::vector select_servers; + for (int i = 0; i != total_weight; ++i) { + EXPECT_EQ(0, wrrlb.SelectServer(in, &out)); + select_servers.emplace_back(ptr->remote_side()); + ++select_result[ptr->remote_side()]; + } + + for (const auto& s : select_servers) { + std::cout << "1=" << s << ", "; + } + std::cout << std::endl; + // Check whether selected result is consistent with expected. + EXPECT_EQ((size_t)3, select_result.size()); + for (const auto& result : select_result) { + std::cout << result.first << " result=" << result.second + << " configured=" << configed_weight[result.first] << std::endl; + EXPECT_EQ(result.second, (size_t)configed_weight[result.first]); + } +} + +TEST_F(LoadBalancerTest, weighted_round_robin_no_valid_server) { + const char* servers[] = { + "10.92.115.19:8831", + "10.42.108.25:8832", + "10.36.150.32:8833" + }; + std::string weight[] = {"200000000", "2", "600000"}; + std::map configed_weight; + brpc::policy::WeightedRoundRobinLoadBalancer wrrlb; + brpc::ExcludedServers* exclude = brpc::ExcludedServers::Create(3); + for (size_t i = 0; i < ARRAY_SIZE(servers); ++i) { + const char *addr = servers[i]; + butil::EndPoint dummy; + ASSERT_EQ(0, str2endpoint(addr, &dummy)); + brpc::ServerId id(8888); + brpc::SocketOptions options; + options.remote_side = dummy; + id.tag = weight[i]; + if (i < 2) { + // `user` is owned by the Socket; only allocate it when a Socket is + // actually created, otherwise it would leak. + options.user = new SaveRecycle; + ASSERT_EQ(0, brpc::Socket::Create(options, &id.id)); + } + EXPECT_TRUE(wrrlb.AddServer(id)); + if (i == 0) { + exclude->Add(id.id); + } + if (i == 1) { + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id.id, &ptr)); + ptr->SetLogOff(); + } + } + // The first socket is excluded. The second socket is logfoff. + // The third socket is invalid. + brpc::SocketUniquePtr ptr; + brpc::LoadBalancer::SelectIn in = { 0, false, false, 0u, exclude }; + brpc::LoadBalancer::SelectOut out(&ptr); + EXPECT_EQ(EHOSTDOWN, wrrlb.SelectServer(in, &out)); + brpc::ExcludedServers::Destroy(exclude); +} + +TEST_F(LoadBalancerTest, weighted_randomized) { + const char* servers[] = { + "10.92.115.19:8831", + "10.42.108.25:8832", + "10.36.150.31:8833", + "10.36.150.32:8899", + "10.92.149.48:8834", + "10.42.122.201:8835", + "10.42.122.202:8836" + }; + std::string weight[] = {"3", "2", "5", "10", "1ab", "-1", "0"}; + std::map configed_weight; + uint64_t configed_weight_sum = 0; + brpc::policy::WeightedRandomizedLoadBalancer wrlb; + size_t valid_weight_num = 4; + + std::vector ids; + // Add server to selected list. The server with invalid weight will be skipped. + for (size_t i = 0; i < ARRAY_SIZE(servers); ++i) { + const char *addr = servers[i]; + butil::EndPoint dummy; + ASSERT_EQ(0, str2endpoint(addr, &dummy)); + brpc::ServerId id(8888); + brpc::SocketOptions options; + options.remote_side = dummy; + options.user = new SaveRecycle; + ASSERT_EQ(0, brpc::Socket::Create(options, &id.id)); + ids.emplace_back(id.id); + id.tag = weight[i]; + if (i < valid_weight_num) { + int weight_num = 0; + ASSERT_TRUE(butil::StringToInt(weight[i], &weight_num)); + configed_weight[dummy] = weight_num; + configed_weight_sum += weight_num; + EXPECT_TRUE(wrlb.AddServer(id)); + } else { + EXPECT_FALSE(wrlb.AddServer(id)); + } + } + + // Select the best server according to weight configured. + // There are 4 valid servers with weight 3, 2, 5 and 10 respectively. + // We run SelectServer for multiple times. The result number of each server selected should be + // weight randomized with weight configured. + std::map select_result; + brpc::SocketUniquePtr ptr; + brpc::LoadBalancer::SelectIn in = { 0, false, false, 0u, NULL }; + brpc::LoadBalancer::SelectOut out(&ptr); + int run_times = configed_weight_sum * 100; + std::vector select_servers; + for (int i = 0; i < run_times; ++i) { + EXPECT_EQ(0, wrlb.SelectServer(in, &out)); + select_servers.emplace_back(ptr->remote_side()); + ++select_result[ptr->remote_side()]; + } + + for (const auto& server : select_servers) { + std::cout << "weight randomized=" << server << ", "; + } + std::cout << std::endl; + + // Check whether selected result is weight with expected. + EXPECT_EQ(valid_weight_num, select_result.size()); + std::cout << "configed_weight_sum=" << configed_weight_sum << " run_times=" << run_times << std::endl; + for (const auto& result : select_result) { + double actual_rate = result.second * 1.0 / run_times; + double expect_rate = configed_weight[result.first] * 1.0 / configed_weight_sum; + std::cout << result.first << " weight=" << configed_weight[result.first] + << " select_times=" << result.second + << " actual_rate=" << actual_rate << " expect_rate=" << expect_rate + << " expect_rate/2=" << expect_rate/2 << " expect_rate*2=" << expect_rate*2 + << std::endl; + // actual_rate >= expect_rate / 2 + ASSERT_GE(actual_rate, expect_rate / 2); + // actual_rate <= expect_rate * 2 + ASSERT_LE(actual_rate, expect_rate * 2); + } + + for (size_t i = 1; i < ids.size(); ++i) { + brpc::Socket::SetFailed(ids[i]); + } + select_result.clear(); + for (int i = 0; i < run_times; ++i) { + EXPECT_EQ(0, wrlb.SelectServer(in, &out)); + // The only choice is servers[0]. + ASSERT_STREQ(butil::endpoint2str(ptr->remote_side()).c_str(), servers[0]); + } +} + +TEST_F(LoadBalancerTest, health_check_no_valid_server) { + const char* servers[] = { + "10.92.115.19:8832", + "10.42.122.201:8833", + }; + std::vector lbs; + lbs.push_back(new brpc::policy::RoundRobinLoadBalancer); + lbs.push_back(new brpc::policy::RandomizedLoadBalancer); + lbs.push_back(new brpc::policy::WeightedRoundRobinLoadBalancer); + + for (int i = 0; i < (int)lbs.size(); ++i) { + brpc::LoadBalancer* lb = lbs[i]; + std::vector ids; + for (size_t i = 0; i < ARRAY_SIZE(servers); ++i) { + butil::EndPoint dummy; + ASSERT_EQ(0, str2endpoint(servers[i], &dummy)); + brpc::ServerId id(8888); + brpc::SocketOptions options; + options.remote_side = dummy; + ASSERT_EQ(0, brpc::Socket::Create(options, &id.id)); + id.tag = "50"; + ids.push_back(id); + lb->AddServer(id); + } + + // Without setting anything, the lb should work fine + for (int i = 0; i < 4; ++i) { + brpc::SocketUniquePtr ptr; + brpc::LoadBalancer::SelectIn in = { 0, false, false, 0u, NULL }; + brpc::LoadBalancer::SelectOut out(&ptr); + ASSERT_EQ(0, lb->SelectServer(in, &out)); + } + + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(ids[0].id, &ptr)); + ptr->_ninflight_app_health_check.store(1, butil::memory_order_relaxed); + for (int i = 0; i < 4; ++i) { + brpc::SocketUniquePtr ptr; + brpc::LoadBalancer::SelectIn in = { 0, false, false, 0u, NULL }; + brpc::LoadBalancer::SelectOut out(&ptr); + ASSERT_EQ(0, lb->SelectServer(in, &out)); + // After putting server[0] into health check state, the only choice is servers[1] + ASSERT_EQ(ptr->remote_side().port, 8833); + } + + ASSERT_EQ(0, brpc::Socket::Address(ids[1].id, &ptr)); + ptr->_ninflight_app_health_check.store(1, butil::memory_order_relaxed); + for (int i = 0; i < 4; ++i) { + brpc::SocketUniquePtr ptr; + brpc::LoadBalancer::SelectIn in = { 0, false, false, 0u, NULL }; + brpc::LoadBalancer::SelectOut out(&ptr); + // There is no server available + ASSERT_EQ(EHOSTDOWN, lb->SelectServer(in, &out)); + } + + ASSERT_EQ(0, brpc::Socket::Address(ids[0].id, &ptr)); + ptr->_ninflight_app_health_check.store(0, butil::memory_order_relaxed); + ASSERT_EQ(0, brpc::Socket::Address(ids[1].id, &ptr)); + ptr->_ninflight_app_health_check.store(0, butil::memory_order_relaxed); + // After reset health check state, the lb should work fine + bool get_server1 = false; + bool get_server2 = false; + for (int i = 0; i < 20; ++i) { + brpc::SocketUniquePtr ptr; + brpc::LoadBalancer::SelectIn in = { 0, false, false, 0u, NULL }; + brpc::LoadBalancer::SelectOut out(&ptr); + ASSERT_EQ(0, lb->SelectServer(in, &out)); + if (ptr->remote_side().port == 8832) { + get_server1 = true; + } else { + get_server2 = true; + } + } + ASSERT_TRUE(get_server1 && get_server2); + delete lb; + } +} + +TEST_F(LoadBalancerTest, revived_from_all_failed_sanity) { + const char* servers[] = { + "10.92.115.19:8832", + "10.42.122.201:8833", + }; + std::unique_ptr lb; + int rand = butil::fast_rand_less_than(2); + if (rand == 0) { + brpc::policy::RandomizedLoadBalancer rlb; + lb.reset(rlb.New("min_working_instances=2 hold_seconds=2")); + } else if (rand == 1) { + brpc::policy::RoundRobinLoadBalancer rrlb; + lb.reset(rrlb.New("min_working_instances=2 hold_seconds=2")); + } + brpc::SocketUniquePtr ptr[2]; + for (size_t i = 0; i < ARRAY_SIZE(servers); ++i) { + butil::EndPoint dummy; + ASSERT_EQ(0, str2endpoint(servers[i], &dummy)); + brpc::SocketOptions options; + options.remote_side = dummy; + brpc::ServerId id(8888); + id.tag = "50"; + ASSERT_EQ(0, brpc::Socket::Create(options, &id.id)); + ASSERT_EQ(0, brpc::Socket::Address(id.id, &ptr[i])); + lb->AddServer(id); + } + brpc::SocketUniquePtr sptr; + brpc::LoadBalancer::SelectIn in = { 0, false, true, 0u, NULL }; + brpc::LoadBalancer::SelectOut out(&sptr); + ASSERT_EQ(0, lb->SelectServer(in, &out)); + + ptr[0]->SetFailed(); + ptr[1]->SetFailed(); + ASSERT_EQ(EHOSTDOWN, lb->SelectServer(in, &out)); + // should reject all request since there is no available server + for (int i = 0; i < 10; ++i) { + ASSERT_EQ(brpc::EREJECT, lb->SelectServer(in, &out)); + } + { + brpc::SocketUniquePtr dummy_ptr; + ASSERT_EQ(1, brpc::Socket::AddressFailedAsWell(ptr[0]->id(), &dummy_ptr)); + dummy_ptr->Revive(2); + } + bthread_usleep(brpc::FLAGS_detect_available_server_interval_ms * 1000); + // After one server is revived, the reject rate should be 50% + int num_ereject = 0; + int num_ok = 0; + for (int i = 0; i < 100; ++i) { + int rc = lb->SelectServer(in, &out); + if (rc == brpc::EREJECT) { + num_ereject++; + } else if (rc == 0) { + num_ok++; + } else { + ASSERT_TRUE(false); + } + } + ASSERT_TRUE(abs(num_ereject - num_ok) < 30); + bthread_usleep((2000 /* hold_seconds */ + 10) * 1000); + + // After enough waiting time, traffic should be sent to all available servers. + for (int i = 0; i < 10; ++i) { + ASSERT_EQ(0, lb->SelectServer(in, &out)); + } +} + +#ifndef BUTIL_USE_ASAN +class EchoServiceImpl : public test::EchoService { +public: + EchoServiceImpl() + : _num_request(0) {} + virtual ~EchoServiceImpl() {} + virtual void Echo(google::protobuf::RpcController* cntl_base, + const test::EchoRequest* req, + test::EchoResponse* res, + google::protobuf::Closure* done) { + //brpc::Controller* cntl = + // static_cast(cntl_base); + brpc::ClosureGuard done_guard(done); + int p = _num_request.fetch_add(1, butil::memory_order_relaxed); + // concurrency in normal case is 50 + if (p < 70) { + bthread_usleep(100 * 1000); + _num_request.fetch_sub(1, butil::memory_order_relaxed); + res->set_message("OK"); + } else { + _num_request.fetch_sub(1, butil::memory_order_relaxed); + bthread_usleep(1000 * 1000); + } + return; + } + + butil::atomic _num_request; +}; + +butil::atomic num_failed(0); +butil::atomic num_reject(0); + +class Done : public google::protobuf::Closure { +public: + void Run() { + if (cntl.Failed()) { + num_failed.fetch_add(1, butil::memory_order_relaxed); + if (cntl.ErrorCode() == brpc::EREJECT) { + num_reject.fetch_add(1, butil::memory_order_relaxed); + } + } + delete this; + } + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; +}; + +TEST_F(LoadBalancerTest, invalid_lb_params) { + const char* lb_algo[] = { "random:mi_working_instances=2 hold_seconds=2", + "rr:min_working_instances=2 hold_secon=2" }; + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = "http"; + ASSERT_EQ(channel.Init("list://127.0.0.1:7777 50, 127.0.0.1:7778 50", + lb_algo[butil::fast_rand_less_than(ARRAY_SIZE(lb_algo))], + &options), -1); +} + +TEST_F(LoadBalancerTest, revived_from_all_failed_intergrated) { + GFLAGS_NAMESPACE::SetCommandLineOption("circuit_breaker_short_window_size", "20"); + GFLAGS_NAMESPACE::SetCommandLineOption("circuit_breaker_short_window_error_percent", "30"); + // Those two lines force the interval of first hc to 3s + GFLAGS_NAMESPACE::SetCommandLineOption("circuit_breaker_max_isolation_duration_ms", "3000"); + GFLAGS_NAMESPACE::SetCommandLineOption("circuit_breaker_min_isolation_duration_ms", "3000"); + + const char* lb_algo[] = { "random:min_working_instances=2 hold_seconds=2", + "rr:min_working_instances=2 hold_seconds=2" }; + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = "http"; + options.timeout_ms = 300; + options.enable_circuit_breaker = true; + // Disable retry to make health check happen one by one + options.max_retry = 0; + ASSERT_EQ(channel.Init("list://127.0.0.1:7777 50, 127.0.0.1:7778 50", + lb_algo[butil::fast_rand_less_than(ARRAY_SIZE(lb_algo))], + &options), 0); + test::EchoRequest req; + req.set_message("123"); + test::EchoResponse res; + test::EchoService_Stub stub(&channel); + { + // trigger one server to health check + brpc::Controller cntl; + stub.Echo(&cntl, &req, &res, NULL); + } + // This sleep make one server revived 700ms earlier than the other server, which + // can make the server down again if no request limit policy are applied here. + bthread_usleep(700000); + { + // trigger the other server to health check + brpc::Controller cntl; + stub.Echo(&cntl, &req, &res, NULL); + } + + butil::EndPoint point(butil::IP_ANY, 7777); + EchoServiceImpl service; + brpc::Server server; + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start(point, NULL)); + + butil::EndPoint point2(butil::IP_ANY, 7778); + EchoServiceImpl service2; + brpc::Server server2; + ASSERT_EQ(0, server2.AddService(&service2, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server2.Start(point2, NULL)); + + int64_t start_ms = butil::cpuwide_time_ms(); + while ((butil::cpuwide_time_ms() - start_ms) < 3500) { + Done* done = new Done; + done->req.set_message("123"); + stub.Echo(&done->cntl, &done->req, &done->res, done); + bthread_usleep(1000); + } + // All error code should be equal to EREJECT, except when the situation + // all servers are down, the very first call that trigger recovering would + // fail with EHOSTDOWN instead of EREJECT. This is where the number 1 comes + // in following ASSERT. + ASSERT_TRUE(num_failed.load(butil::memory_order_relaxed) - + num_reject.load(butil::memory_order_relaxed) == 1); + num_failed.store(0, butil::memory_order_relaxed); + + // should recover now + for (int i = 0; i < 1000; ++i) { + Done* done = new Done; + done->req.set_message("123"); + stub.Echo(&done->cntl, &done->req, &done->res, done); + bthread_usleep(1000); + } + bthread_usleep(500000 /* sleep longer than timeout of channel */); + ASSERT_EQ(0, num_failed.load(butil::memory_order_relaxed)); +} +#endif // BUTIL_USE_ASAN + +// Regression for #3268's incomplete migration of LocalityAwareLoadBalancer. +// +// #3268 switched `LocalityAwareLoadBalancer::Weight::Update::end_time_us` and +// `LocalityAwareLoadBalancer::Describe::now` to `butil::cpuwide_time_us()` +// while every caller that supplies `CallInfo::begin_time_us` (the RPC entry +// in `Channel::CallMethod` and the retry sites in +// `Controller::OnVersionedRPCReturned`) still uses `butil::gettimeofday_us()`. +// The resulting time-source mismatch makes +// +// latency = end_time_us - ci.begin_time_us +// = cpuwide_now - wallclock_begin +// ~= -1.7e15 us (huge negative) +// +// trigger the +// +// if (latency <= 0) { /* time skews, ignore the sample */ return 0; } +// +// short-circuit on every call. `_time_q` never accumulates samples, +// `_avg_latency` stays at 0, and locality-aware weight feedback is silently +// disabled. Visible downstream symptom: cold-start `list://` channels with +// `lb=la` and 2 backends occasionally fail RPCs with `EHOSTDOWN` +// ("Fail to select server") on retry even when one backend is healthy. +// +// This commit reverts the LA side of #3268, so `Weight::Update` and +// `Describe` once again use `butil::gettimeofday_us()` to match every +// existing caller of `CallInfo::begin_time_us`. +// +// The test below runs entirely against `LocalityAwareLoadBalancer` (no +// Server / Channel is involved), so it is hermetic. It supplies a +// gettimeofday-based `begin_time_us` (matching what `Channel::CallMethod` +// passes today) and asserts that the LB records a positive `_avg_latency`, +// rather than tripping the time-skew short-circuit. +TEST_F(LoadBalancerTest, la_records_latency_with_consistent_time_source) { + LALB lalb; + char addr[] = "192.168.1.1:8080"; + butil::EndPoint dummy; + ASSERT_EQ(0, str2endpoint(addr, &dummy)); + brpc::ServerId id(8888); + brpc::SocketOptions options; + options.remote_side = dummy; + ASSERT_EQ(0, brpc::Socket::Create(options, &id.id)); + ASSERT_TRUE(lalb.AddServer(id)); + + auto avg_latency = [&]() -> int64_t { + std::ostringstream os; + brpc::DescribeOptions opts; + opts.verbose = true; + lalb.Describe(os, opts); + const std::string s = os.str(); + const size_t p = s.find("avg_latency="); + if (p == std::string::npos) return -1; + return strtoll(s.c_str() + p + strlen("avg_latency="), NULL, 10); + }; + + // Drive a few "RPCs": pick a server, sleep ~2ms, feed back. begin_time_us + // comes from gettimeofday_us(), matching what Channel::CallMethod and the + // retry sites in Controller::OnVersionedRPCReturned pass on every RPC. + for (int i = 0; i < 8; ++i) { + const int64_t begin_us = butil::gettimeofday_us(); + brpc::SocketUniquePtr ptr; + brpc::LoadBalancer::SelectIn in = { begin_us, true, false, 0u, NULL }; + brpc::LoadBalancer::SelectOut out(&ptr); + ASSERT_EQ(0, lalb.SelectServer(in, &out)); + bthread_usleep(2000); + brpc::LoadBalancer::CallInfo ci = { begin_us, id.id, 0, NULL }; + lalb.Feedback(ci); + } + + // _avg_latency must reflect actual elapsed time. If this is 0, either + // Weight::Update::end_time_us was changed away from gettimeofday_us + // again (re-introducing the time-source mismatch) or some caller of + // CallInfo::begin_time_us drifted to a different clock domain. + EXPECT_GT(avg_latency(), 0); + + ASSERT_EQ(0, brpc::Socket::SetFailed(id.id)); +} + +TEST_F(LoadBalancerTest, la_selection_too_long) { + brpc::GlobalInitializeOrDie(); + brpc::LoadBalancerWithNaming lb; + CHECK_EQ(0, lb.Init("list://127.0.0.1:8888", "la", nullptr, nullptr)); + char addr[] = "127.0.0.1:8888"; + butil::EndPoint ep; + ASSERT_EQ(0, str2endpoint(addr, &ep)); + brpc::SocketId id; + ASSERT_EQ(0, brpc::SocketMapFind(brpc::SocketMapKey(ep), &id)); + ASSERT_EQ(0, brpc::Socket::SetFailed(id)); + brpc::LoadBalancer::SelectIn in = { 0, false, false, 0u, nullptr }; + brpc::SocketUniquePtr ptr; + brpc::LoadBalancer::SelectOut out(&ptr); + ASSERT_EQ(EHOSTDOWN, lb.SelectServer(in, &out)); +} + +} //namespace diff --git a/test/brpc_memcache_unittest.cpp b/test/brpc_memcache_unittest.cpp new file mode 100644 index 0000000..b1da958 --- /dev/null +++ b/test/brpc_memcache_unittest.cpp @@ -0,0 +1,254 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/time.h" +#include "butil/logging.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace brpc { +DECLARE_int32(idle_timeout_second); +DECLARE_uint64(max_body_size); +} + +int main(int argc, char* argv[]) { + brpc::FLAGS_idle_timeout_second = 0; + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + +namespace { + +TEST(MemcacheParserTest, RejectOversizedResponseBeforeBufferingBody) { + GFLAGS_NAMESPACE::FlagSaver flag_saver; + brpc::FLAGS_max_body_size = 1024; + + brpc::SocketId id; + brpc::SocketOptions options; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + brpc::SocketUniquePtr socket; + ASSERT_EQ(0, brpc::Socket::Address(id, &socket)); + + brpc::policy::MemcacheResponseHeader header = {}; + header.magic = brpc::policy::MC_MAGIC_RESPONSE; + header.total_body_length = butil::HostToNet32(1025); + butil::IOBuf buf; + buf.append(&header, sizeof(header)); + EXPECT_EQ(brpc::PARSE_ERROR_TOO_BIG_DATA, + brpc::policy::ParseMemcacheMessage( + &buf, socket.get(), false, NULL).error()); + +} + +static pthread_once_t download_memcached_once = PTHREAD_ONCE_INIT; +static pid_t g_mc_pid = -1; + +static void RemoveMemcached() { + puts("[Stopping memcached]"); + char cmd[256]; +#if defined(BAIDU_INTERNAL) + snprintf(cmd, sizeof(cmd), "kill %d; rm -rf memcached_for_test", g_mc_pid); +#else + snprintf(cmd, sizeof(cmd), "kill %d", g_mc_pid); +#endif + CHECK(0 == system(cmd)); + // Wait for mc to stop + usleep(50000); +} + +#define MEMCACHED_BIN "memcached" +#define MEMCACHED_PORT "11211" + +static void RunMemcached() { +#if defined(BAIDU_INTERNAL) + puts("Downloading memcached..."); + if (system("mkdir -p memcached_for_test && cd memcached_for_test && svn co https://svn.baidu.com/third-64/tags/memcached/memcached_1-4-15-100_PD_BL/bin") != 0) { + puts("Fail to get memcached from svn"); + return; + } +# undef MEMCACHED_BIN +# define MEMCACHED_BIN "memcached_for_test/bin/memcached"; +#else + if (system("which " MEMCACHED_BIN) != 0) { + puts("Fail to find " MEMCACHED_BIN ", following tests will be skipped"); + return; + } +#endif + atexit(RemoveMemcached); + + g_mc_pid = fork(); + if (g_mc_pid < 0) { + puts("Fail to fork"); + exit(1); + } else if (g_mc_pid == 0) { + puts("[Starting memcached]"); + char* const argv[] = { (char*)MEMCACHED_BIN, + (char*)"-p", (char*)MEMCACHED_PORT, + NULL }; + if (execvp(MEMCACHED_BIN, argv) < 0) { + puts("Fail to run " MEMCACHED_BIN); + exit(1); + } + } + // Wait for memcached to start. + usleep(50000); +} + +class MemcacheTest : public testing::Test { +protected: + MemcacheTest() {} + void SetUp() { + pthread_once(&download_memcached_once, RunMemcached); + } + void TearDown() { + } +}; + +TEST_F(MemcacheTest, sanity) { + if (g_mc_pid < 0) { + puts("Skipped due to absence of memcached"); + return; + } + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_MEMCACHE; + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("0.0.0.0:" MEMCACHED_PORT, &options)); + brpc::MemcacheRequest request; + brpc::MemcacheResponse response; + brpc::Controller cntl; + + // Clear all contents in MC which is still holding older data after + // restarting in Ubuntu 18.04 (mc=1.5.6) + request.Flush(0); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_TRUE(response.PopFlush()); + + cntl.Reset(); + request.Clear(); + request.Get("hello"); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + std::string value; + uint32_t flags = 0; + uint64_t cas_value = 0; + ASSERT_FALSE(response.PopGet(&value, &flags, &cas_value)); + ASSERT_EQ("Not found", response.LastError()); + + cntl.Reset(); + request.Clear(); + request.Set("hello", "world", 0xdeadbeef, 10, 0); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_TRUE(response.PopSet(&cas_value)) << response.LastError(); + ASSERT_EQ("", response.LastError()); + + cntl.Reset(); + request.Clear(); + request.Get("hello"); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()); + ASSERT_TRUE(response.PopGet(&value, &flags, &cas_value)); + ASSERT_EQ("", response.LastError()); + ASSERT_EQ("world", value); + ASSERT_EQ(0xdeadbeef, flags); + std::cout << "cas_value=" << cas_value << std::endl; + + cntl.Reset(); + request.Clear(); + request.Set("hello", "world2", 0xdeadbeef, 10, + cas_value/*intended match*/); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + uint64_t cas_value2 = 0; + ASSERT_TRUE(response.PopSet(&cas_value2)) << response.LastError(); + + cntl.Reset(); + request.Clear(); + request.Set("hello", "world3", 0xdeadbeef, 10, + cas_value2 + 1/*intended unmatch*/); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + uint64_t cas_value3 = ~0; + ASSERT_FALSE(response.PopSet(&cas_value3)); + std::cout << response.LastError() << std::endl; + ASSERT_EQ(~0ul, cas_value3); +} + +TEST_F(MemcacheTest, incr_and_decr) { + if (g_mc_pid < 0) { + puts("Skipped due to absence of memcached"); + return; + } + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_MEMCACHE; + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("0.0.0.0:" MEMCACHED_PORT, &options)); + brpc::MemcacheRequest request; + brpc::MemcacheResponse response; + brpc::Controller cntl; + request.Increment("counter1", 2, 10, 10); + request.Decrement("counter1", 1, 10, 10); + request.Increment("counter1", 3, 10, 10); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + uint64_t new_value1 = 0; + uint64_t cas_value1 = 0; + ASSERT_TRUE(response.PopIncrement(&new_value1, &cas_value1)); + ASSERT_EQ(10ul, new_value1); + uint64_t new_value2 = 0; + uint64_t cas_value2 = 0; + ASSERT_TRUE(response.PopDecrement(&new_value2, &cas_value2)); + ASSERT_EQ(9ul, new_value2); + uint64_t new_value3 = 0; + uint64_t cas_value3 = 0; + ASSERT_TRUE(response.PopIncrement(&new_value3, &cas_value3)); + ASSERT_EQ(12ul, new_value3); + std::cout << "cas1=" << cas_value1 + << " cas2=" << cas_value2 + << " cas3=" << cas_value3 + << std::endl; +} + +TEST_F(MemcacheTest, version) { + if (g_mc_pid < 0) { + puts("Skipped due to absence of memcached"); + return; + } + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_MEMCACHE; + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("0.0.0.0:" MEMCACHED_PORT, &options)); + brpc::MemcacheRequest request; + brpc::MemcacheResponse response; + brpc::Controller cntl; + request.Version(); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + std::string version; + ASSERT_TRUE(response.PopVersion(&version)) << response.LastError(); + std::cout << "version=" << version << std::endl; +} +} //namespace diff --git a/test/brpc_mongo_protocol_unittest.cpp b/test/brpc_mongo_protocol_unittest.cpp new file mode 100644 index 0000000..64e3bf3 --- /dev/null +++ b/test/brpc_mongo_protocol_unittest.cpp @@ -0,0 +1,235 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: Thu Oct 15 21:08:31 CST 2015 + +#include +#include +#include +#include +#include +#include +#include "butil/time.h" +#include "butil/macros.h" +#include "brpc/socket.h" +#include "brpc/acceptor.h" +#include "brpc/server.h" +#include "brpc/policy/mongo_protocol.h" +#include "brpc/policy/most_common_message.h" +#include "brpc/controller.h" +#include "brpc/mongo_head.h" +#include "brpc/mongo_service_adaptor.h" +#include "brpc/policy/mongo.pb.h" + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + return RUN_ALL_TESTS(); +} + +namespace { + +static const std::string EXP_REQUEST = "hello"; +static const std::string EXP_RESPONSE = "world"; + +class MyEchoService : public ::brpc::policy::MongoService { + void default_method(::google::protobuf::RpcController*, + const ::brpc::policy::MongoRequest* req, + ::brpc::policy::MongoResponse* res, + ::google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + + EXPECT_EQ(EXP_REQUEST, req->message()); + + res->mutable_header()->set_message_length( + sizeof(brpc::mongo_head_t) + sizeof(int32_t) * 3 + + sizeof(int64_t) + EXP_REQUEST.length()); + res->set_message(EXP_RESPONSE); + } +}; + +class MyContext : public ::brpc::MongoContext { +}; + +class MyMongoAdaptor : public brpc::MongoServiceAdaptor { +public: + virtual void SerializeError(int /*response_to*/, butil::IOBuf* out_buf) const { + brpc::mongo_head_t header = { + (int32_t)(sizeof(brpc::mongo_head_t) + sizeof(int32_t) * 3 + + sizeof(int64_t) + EXP_REQUEST.length()), + 0, 0, 0}; + out_buf->append(static_cast(&header), sizeof(brpc::mongo_head_t)); + int32_t response_flags = 0; + int64_t cursor_id = 0; + int32_t starting_from = 0; + int32_t number_returned = 0; + out_buf->append(&response_flags, sizeof(response_flags)); + out_buf->append(&cursor_id, sizeof(cursor_id)); + out_buf->append(&starting_from, sizeof(starting_from)); + out_buf->append(&number_returned, sizeof(number_returned)); + out_buf->append(EXP_RESPONSE); + } + + virtual ::brpc::MongoContext* CreateSocketContext() const { + return new MyContext; + } +}; + +class MongoTest : public ::testing::Test{ +protected: + MongoTest() { + EXPECT_EQ(0, _server.AddService( + &_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + // Hack: Regard `_server' as running + _server._status = brpc::Server::RUNNING; + _server._options.mongo_service_adaptor = &_adaptor; + + EXPECT_EQ(0, pipe(_pipe_fds)); + + brpc::SocketId id; + brpc::SocketOptions options; + options.fd = _pipe_fds[1]; + EXPECT_EQ(0, brpc::Socket::Create(options, &id)); + EXPECT_EQ(0, brpc::Socket::Address(id, &_socket)); + }; + + virtual ~MongoTest() {}; + virtual void SetUp() {}; + virtual void TearDown() {}; + + void ProcessMessage(void (*process)(brpc::InputMessageBase*), + brpc::InputMessageBase* msg, bool set_eof) { + if (msg->_socket == NULL) { + _socket->ReAddress(&msg->_socket); + } + msg->_arg = &_server; + _socket->PostponeEOF(); + if (set_eof) { + _socket->SetEOF(); + } + (*process)(msg); + } + + brpc::policy::MostCommonMessage* MakeRequestMessage( + brpc::mongo_head_t* head) { + head->message_length = sizeof(head) + EXP_REQUEST.length(); + head->op_code = brpc::MONGO_OPCODE_REPLY; + brpc::policy::MostCommonMessage* msg = + brpc::policy::MostCommonMessage::Get(); + msg->meta.append(&head, sizeof(head)); + msg->payload.append(EXP_REQUEST); + return msg; + } + + void CheckEmptyResponse() { + int bytes_in_pipe = 0; + ioctl(_pipe_fds[0], FIONREAD, &bytes_in_pipe); + EXPECT_EQ(0, bytes_in_pipe); + } + + int _pipe_fds[2]; + brpc::SocketUniquePtr _socket; + brpc::Server _server; + MyMongoAdaptor _adaptor; + + MyEchoService _svc; +}; + +TEST_F(MongoTest, process_request_logoff) { + brpc::mongo_head_t header = { 0, 0, 0, 0 }; + header.op_code = brpc::MONGO_OPCODE_REPLY; + header.message_length = sizeof(header) + EXP_REQUEST.length(); + butil::IOBuf total_buf; + total_buf.append(static_cast(&header), sizeof(header)); + total_buf.append(EXP_REQUEST); + brpc::ParseResult req_pr = brpc::policy::ParseMongoMessage( + &total_buf, _socket.get(), false, &_server); + ASSERT_EQ(brpc::PARSE_OK, req_pr.error()); + _server._status = brpc::Server::READY; + ProcessMessage(brpc::policy::ProcessMongoRequest, req_pr.message(), false); + ASSERT_EQ(1ll, _server._nerror_bvar.get_value()); +} + +TEST_F(MongoTest, process_request_failed_socket) { + brpc::mongo_head_t header = { 0, 0, 0, 0 }; + header.op_code = brpc::MONGO_OPCODE_REPLY; + header.message_length = sizeof(header) + EXP_REQUEST.length(); + butil::IOBuf total_buf; + total_buf.append(static_cast(&header), sizeof(header)); + total_buf.append(EXP_REQUEST); + brpc::ParseResult req_pr = brpc::policy::ParseMongoMessage( + &total_buf, _socket.get(), false, &_server); + ASSERT_EQ(brpc::PARSE_OK, req_pr.error()); + _socket->SetFailed(); + ProcessMessage(brpc::policy::ProcessMongoRequest, req_pr.message(), false); + ASSERT_EQ(0ll, _server._nerror_bvar.get_value()); +} + +TEST_F(MongoTest, complete_flow) { + butil::IOBuf request_buf; + butil::IOBuf total_buf; + brpc::Controller cntl; + brpc::policy::MongoRequest req; + brpc::policy::MongoResponse res; + cntl._response = &res; + + // Assemble request + brpc::mongo_head_t header = { 0, 0, 0, 0 }; + header.message_length = sizeof(header) + EXP_REQUEST.length(); + total_buf.append(static_cast(&header), sizeof(header)); + total_buf.append(EXP_REQUEST); + + const size_t old_size = total_buf.size(); + // Handle request + brpc::ParseResult req_pr = + brpc::policy::ParseMongoMessage(&total_buf, _socket.get(), false, &_server); + // head.op_code does not fit. + ASSERT_EQ(brpc::PARSE_ERROR_TRY_OTHERS, req_pr.error()); + // no data should be consumed. + ASSERT_EQ(old_size, total_buf.size()); + header.op_code = brpc::MONGO_OPCODE_REPLY; + total_buf.clear(); + total_buf.append(static_cast(&header), sizeof(header)); + total_buf.append(EXP_REQUEST); + req_pr = brpc::policy::ParseMongoMessage(&total_buf, _socket.get(), false, &_server); + ASSERT_EQ(brpc::PARSE_OK, req_pr.error()); + brpc::InputMessageBase* req_msg = req_pr.message(); + ProcessMessage(brpc::policy::ProcessMongoRequest, req_msg, false); + + // Read response from pipe + butil::IOPortal response_buf; + response_buf.append_from_file_descriptor(_pipe_fds[0], 1024); + char buf[sizeof(brpc::mongo_head_t)]; + const brpc::mongo_head_t *phead = static_cast( + response_buf.fetch(buf, sizeof(buf))); + response_buf.cutn(&header, sizeof(header)); + response_buf.cutn(buf, sizeof(int32_t)); + response_buf.cutn(buf, sizeof(int64_t)); + response_buf.cutn(buf, sizeof(int32_t)); + response_buf.cutn(buf, sizeof(int32_t)); + char msg_buf[200]; + int msg_length = phead->message_length - sizeof(brpc::mongo_head_t) - sizeof(int32_t) * 3 - + sizeof(int64_t); + response_buf.cutn(msg_buf, msg_length); + msg_buf[msg_length] = '\0'; + + ASSERT_FALSE(cntl.Failed()); + ASSERT_STREQ(EXP_RESPONSE.c_str(), msg_buf); +} +} //namespace diff --git a/test/brpc_mysql_auth_handshake_unittest.cpp b/test/brpc_mysql_auth_handshake_unittest.cpp new file mode 100644 index 0000000..9845083 --- /dev/null +++ b/test/brpc_mysql_auth_handshake_unittest.cpp @@ -0,0 +1,1289 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "brpc/policy/mysql/mysql_auth_handshake.h" +#include "brpc/policy/mysql/mysql_auth_packet.h" +#include "brpc/policy/mysql/mysql_auth_scramble.h" +#include "butil/logging.h" +#include "butil/strings/string_piece.h" + +// When true, the server-integration tests connect to an already-running +// MySQL server (on -mysql_host:-mysql_port, as -mysql_user/-mysql_password) +// that the test neither starts nor stops. When false (the default), the +// fixture spawns and tears down its own throwaway server, exactly like +// test/brpc_redis_unittest.cpp. +DEFINE_bool(mysql_use_running_server, false, + "Use an already-running MySQL server instead of spawning a " + "throwaway one; the running server is neither started nor " + "stopped by the test."); +DEFINE_string(mysql_host, "127.0.0.1", + "Host of the running MySQL server " + "(only with -mysql_use_running_server)."); +DEFINE_int32(mysql_port, 13306, + "TCP port of the MySQL server (used for both the running " + "server and the spawned throwaway server)."); +DEFINE_string(mysql_user, "root", + "User for the authentication tests against a running server."); +DEFINE_string(mysql_password, "", + "Password for -mysql_user (empty for the spawned server)."); + +namespace { + +using brpc::policy::mysql::AuthMoreData; +using brpc::policy::mysql::AuthSwitchRequest; +using brpc::policy::mysql::BuildHandshakeResponse41; +using brpc::policy::mysql::DecodePacketHeader; +using brpc::policy::mysql::EncodePacketHeader; +using brpc::policy::mysql::HandshakeResponse41; +using brpc::policy::mysql::HandshakeV10; +using brpc::policy::mysql::PacketHeader; +using brpc::policy::mysql::ParseAuthMoreData; +using brpc::policy::mysql::ParseAuthSwitchRequest; +using brpc::policy::mysql::ParseHandshakeV10; +using brpc::policy::mysql::kAuthMoreDataTag; +using brpc::policy::mysql::kAuthSwitchRequestTag; +using brpc::policy::mysql::kErrPacketTag; +using brpc::policy::mysql::kHandshakeV10Tag; +using brpc::policy::mysql::kOkPacketTag; +using brpc::policy::mysql::kPacketHeaderLen; +using brpc::policy::mysql::kSaltLen; +using brpc::policy::mysql::CLIENT_CONNECT_WITH_DB; +using brpc::policy::mysql::CLIENT_PLUGIN_AUTH; +using brpc::policy::mysql::CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA; +using brpc::policy::mysql::CLIENT_PROTOCOL_41; +using brpc::policy::mysql::CLIENT_SECURE_CONNECTION; +using brpc::policy::mysql::NativePasswordScramble; +using brpc::policy::mysql::CachingSha2PasswordScramble; +using brpc::policy::mysql::CachingSha2PasswordRsaEncrypt; +using brpc::policy::mysql::CachingSha2PasswordCleartext; +using brpc::policy::mysql::CachingSha2PasswordSlowPath; +using brpc::policy::mysql::kNativePasswordResponseLen; +using brpc::policy::mysql::kCachingSha2PasswordResponseLen; + +// Constructs a synthetic HandshakeV10 packet payload matching the wire +// format described at: +// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake_v10.html +std::string MakeHandshakeV10Payload( + const std::string& server_version, + uint32_t connection_id, + const std::string& salt, + uint32_t capability_flags, + uint8_t character_set, + uint16_t status_flags, + const std::string& auth_plugin_name) { + std::string out; + out.push_back(static_cast(kHandshakeV10Tag)); + out.append(server_version); + out.push_back('\0'); + for (int i = 0; i < 4; ++i) { + out.push_back(static_cast((connection_id >> (8 * i)) & 0xff)); + } + // Salt part 1 (first 8 bytes). + out.append(salt.data(), 8); + // Filler. + out.push_back('\0'); + // Capability flags low 16 bits. + out.push_back(static_cast(capability_flags & 0xff)); + out.push_back(static_cast((capability_flags >> 8) & 0xff)); + // Character set. + out.push_back(static_cast(character_set)); + // Status flags. + out.push_back(static_cast(status_flags & 0xff)); + out.push_back(static_cast((status_flags >> 8) & 0xff)); + // Capability flags high 16 bits. + out.push_back(static_cast((capability_flags >> 16) & 0xff)); + out.push_back(static_cast((capability_flags >> 24) & 0xff)); + // Length of auth-plugin-data: 21 (8 + 12 + 1 NUL filler) when + // CLIENT_PLUGIN_AUTH set, 0 otherwise. + const uint8_t apd_total = (capability_flags & CLIENT_PLUGIN_AUTH) ? 21 : 0; + out.push_back(static_cast(apd_total)); + // 10 reserved zeros. + out.append(10, '\0'); + if (capability_flags & CLIENT_SECURE_CONNECTION) { + // Salt part 2: 12 bytes plus 1 NUL filler. + out.append(salt.data() + 8, salt.size() - 8); + out.push_back('\0'); + } + if (capability_flags & CLIENT_PLUGIN_AUTH) { + out.append(auth_plugin_name); + out.push_back('\0'); + } + return out; +} + +// ---------------------------------------------------------------------- +// HandshakeV10 parser +// ---------------------------------------------------------------------- + +TEST(HandshakeV10Test, HappyPath_Mysql8Style) { + std::string salt; + for (int i = 1; i <= 20; ++i) salt.push_back(static_cast(i)); + const uint32_t caps = + CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION | CLIENT_PLUGIN_AUTH; + + const std::string payload = MakeHandshakeV10Payload( + "8.0.32", 42, salt, caps, + /*character_set=*/0xff, /*status_flags=*/0x0002, + "mysql_native_password"); + + HandshakeV10 hs; + ASSERT_TRUE(ParseHandshakeV10(payload, &hs)); + EXPECT_EQ(hs.protocol_version, kHandshakeV10Tag); + EXPECT_EQ(hs.server_version, "8.0.32"); + EXPECT_EQ(hs.connection_id, 42u); + EXPECT_EQ(hs.auth_plugin_data, salt); + EXPECT_EQ(hs.auth_plugin_data.size(), kSaltLen); + EXPECT_TRUE(hs.capability_flags & CLIENT_PLUGIN_AUTH); + EXPECT_TRUE(hs.capability_flags & CLIENT_SECURE_CONNECTION); + EXPECT_EQ(hs.character_set, 0xff); + EXPECT_EQ(hs.status_flags, 0x0002); + EXPECT_EQ(hs.auth_plugin_name, "mysql_native_password"); +} + +TEST(HandshakeV10Test, HappyPath_CachingSha2Server) { + std::string salt; + for (int i = 0; i < 20; ++i) salt.push_back(static_cast('A' + i)); + const uint32_t caps = + CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION | CLIENT_PLUGIN_AUTH; + + const std::string payload = MakeHandshakeV10Payload( + "8.0.32", 7, salt, caps, 0xff, 0x0002, "caching_sha2_password"); + + HandshakeV10 hs; + ASSERT_TRUE(ParseHandshakeV10(payload, &hs)); + EXPECT_EQ(hs.auth_plugin_name, "caching_sha2_password"); + EXPECT_EQ(hs.auth_plugin_data, salt); +} + +TEST(HandshakeV10Test, RejectsBadProtocolVersion) { + std::string payload(1, static_cast(0x09)); // not 10 + payload.append("ignored"); + HandshakeV10 hs; + EXPECT_FALSE(ParseHandshakeV10(payload, &hs)); +} + +TEST(HandshakeV10Test, RejectsTruncatedAtServerVersion) { + // Tag, but no NUL anywhere -> server_version unterminated. + std::string payload(1, static_cast(kHandshakeV10Tag)); + payload.append(20, 'x'); // no NUL + HandshakeV10 hs; + EXPECT_FALSE(ParseHandshakeV10(payload, &hs)); +} + +TEST(HandshakeV10Test, RejectsEmptyPayload) { + HandshakeV10 hs; + EXPECT_FALSE(ParseHandshakeV10(butil::StringPiece(""), &hs)); +} + +TEST(HandshakeV10Test, RejectsTruncatedBeforeSalt) { + // Build a payload then chop after capability_flags_lo. + std::string salt(20, '\x01'); + const std::string full = MakeHandshakeV10Payload( + "8.0.32", 1, salt, CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION, + 0xff, 0, ""); + // Chop early — keep only protocol+server_version+conn_id+part1+filler+caps_lo. + const std::string truncated(full.data(), 6 + 1 + 4 + 8 + 1 + 2); + HandshakeV10 hs; + EXPECT_FALSE(ParseHandshakeV10(truncated, &hs)); +} + +TEST(HandshakeV10Test, ExtractsFull20ByteSalt) { + std::string salt(20, 0); + for (int i = 0; i < 20; ++i) salt[i] = static_cast(0xA0 + i); + const std::string payload = MakeHandshakeV10Payload( + "8.0.32", 1, salt, + CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION | CLIENT_PLUGIN_AUTH, + 0xff, 0, "mysql_native_password"); + HandshakeV10 hs; + ASSERT_TRUE(ParseHandshakeV10(payload, &hs)); + EXPECT_EQ(hs.auth_plugin_data.size(), kSaltLen); + EXPECT_EQ(hs.auth_plugin_data, salt); +} + +// ---------------------------------------------------------------------- +// HandshakeResponse41 builder +// ---------------------------------------------------------------------- + +TEST(HandshakeResponse41Test, BuildsExpectedLayout) { + HandshakeResponse41 req; + req.capability_flags = CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION + | CLIENT_PLUGIN_AUTH + | CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA; + req.max_packet_size = 1u << 24; + req.character_set = 0x21; + req.username = "root"; + req.auth_response = std::string(20, '\x42'); // canned scramble + req.auth_plugin_name = "mysql_native_password"; + + std::string payload; + ASSERT_TRUE(BuildHandshakeResponse41(req, &payload)); + + // 4 caps + 4 max_pkt + 1 charset + 23 reserved = 32 bytes fixed prefix + ASSERT_GE(payload.size(), 32u); + // Caps roundtrip + uint32_t caps = static_cast(payload[0]) + | (static_cast(static_cast(payload[1])) << 8) + | (static_cast(static_cast(payload[2])) << 16) + | (static_cast(static_cast(payload[3])) << 24); + EXPECT_EQ(caps, req.capability_flags); + // Username + NUL + lenenc(20) + 20 bytes + plugin + NUL + const char* p = payload.data() + 32; + EXPECT_EQ(std::string(p, 5), std::string("root\0", 5)); + p += 5; + EXPECT_EQ(static_cast(*p), 20u); // lenenc(20) = 0x14 + ++p; + EXPECT_EQ(std::string(p, 20), std::string(20, '\x42')); + p += 20; + const std::string plugin_nul("mysql_native_password\0", 22); + EXPECT_EQ(std::string(p, plugin_nul.size()), plugin_nul); +} + +TEST(HandshakeResponse41Test, OmitsDatabaseWhenFlagAbsent) { + HandshakeResponse41 req; + req.capability_flags = CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION + | CLIENT_PLUGIN_AUTH + | CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA; + req.max_packet_size = 1u << 24; + req.character_set = 0x21; + req.username = "u"; + req.auth_response = std::string(20, '\x01'); + req.database = "mydb"; // should be ignored + req.auth_plugin_name = "mysql_native_password"; + + std::string payload; + ASSERT_TRUE(BuildHandshakeResponse41(req, &payload)); + EXPECT_EQ(payload.find("mydb"), std::string::npos); +} + +TEST(HandshakeResponse41Test, IncludesDatabaseWhenFlagSet) { + HandshakeResponse41 req; + req.capability_flags = CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION + | CLIENT_PLUGIN_AUTH | CLIENT_CONNECT_WITH_DB + | CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA; + req.max_packet_size = 1u << 24; + req.character_set = 0x21; + req.username = "u"; + req.auth_response = std::string(20, '\x01'); + req.database = "mydb"; + req.auth_plugin_name = "mysql_native_password"; + + std::string payload; + ASSERT_TRUE(BuildHandshakeResponse41(req, &payload)); + EXPECT_NE(payload.find("mydb"), std::string::npos); +} + +TEST(HandshakeResponse41Test, HandlesLargeAuthResponseViaLenEncoding) { + // 256-byte RSA ciphertext — exercises lenenc 0xfc 2-byte branch. + HandshakeResponse41 req; + req.capability_flags = CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION + | CLIENT_PLUGIN_AUTH + | CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA; + req.max_packet_size = 1u << 24; + req.character_set = 0x21; + req.username = "u"; + req.auth_response = std::string(256, '\xAA'); + req.auth_plugin_name = "caching_sha2_password"; + + std::string payload; + ASSERT_TRUE(BuildHandshakeResponse41(req, &payload)); + // lenenc 256 -> 0xfc 0x00 0x01 + const std::string lenenc("\xfc\x00\x01", 3); + EXPECT_NE(payload.find(lenenc), std::string::npos); +} + +TEST(HandshakeResponse41Test, RejectsOversizeAuthResponseWithoutLenEnc) { + // CLIENT_SECURE_CONNECTION without the lenenc flag uses a 1-byte length + // prefix, so a >255-byte auth_response cannot be represented. The builder + // must hard-fail (return false) and write nothing, rather than silently + // truncating to 255 bytes. + HandshakeResponse41 req; + req.capability_flags = CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION + | CLIENT_PLUGIN_AUTH; // deliberately no LENENC flag + req.max_packet_size = 1u << 24; + req.character_set = 0x21; + req.username = "u"; + req.auth_response = std::string(256, '\xAA'); // 256 > 255 + req.auth_plugin_name = "caching_sha2_password"; + + std::string payload; + EXPECT_FALSE(BuildHandshakeResponse41(req, &payload)); + EXPECT_TRUE(payload.empty()) + << "no bytes must be written to out on failure"; +} + +// Exactly 255 bytes is the boundary that still fits the 1-byte length prefix. +TEST(HandshakeResponse41Test, AcceptsMaxSizeAuthResponseWithoutLenEnc) { + HandshakeResponse41 req; + req.capability_flags = CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION + | CLIENT_PLUGIN_AUTH; + req.max_packet_size = 1u << 24; + req.character_set = 0x21; + req.username = "u"; + req.auth_response = std::string(255, '\xAA'); // fits in one byte + req.auth_plugin_name = "caching_sha2_password"; + + std::string payload; + ASSERT_TRUE(BuildHandshakeResponse41(req, &payload)); + // After "u\0" we expect length byte 0xFF (255) then 255 payload bytes. + const size_t u_end = payload.find('u') + 2; + EXPECT_EQ(static_cast(payload[u_end]), 255u); +} + +TEST(HandshakeResponse41Test, UsesSingleByteLengthWithoutLenEncFlag) { + HandshakeResponse41 req; + req.capability_flags = CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION + | CLIENT_PLUGIN_AUTH; + req.max_packet_size = 1u << 24; + req.character_set = 0x21; + req.username = "u"; + req.auth_response = std::string(20, '\x77'); + req.auth_plugin_name = "mysql_native_password"; + + std::string payload; + ASSERT_TRUE(BuildHandshakeResponse41(req, &payload)); + // After username "u\0", we expect 1-byte length 0x14 (20). + const size_t u_end = payload.find('u') + 2; // skip 'u' + NUL + EXPECT_EQ(static_cast(payload[u_end]), 20u); +} + +// ---------------------------------------------------------------------- +// AuthSwitchRequest parser +// ---------------------------------------------------------------------- + +TEST(AuthSwitchRequestTest, HappyPath) { + std::string payload(1, static_cast(kAuthSwitchRequestTag)); + payload.append("caching_sha2_password"); + payload.push_back('\0'); + payload.append(20, '\xAA'); + payload.push_back('\0'); // trailing NUL filler + AuthSwitchRequest sw; + ASSERT_TRUE(ParseAuthSwitchRequest(payload, &sw)); + EXPECT_EQ(sw.auth_plugin_name, "caching_sha2_password"); + EXPECT_EQ(sw.auth_plugin_data, std::string(20, '\xAA')); +} + +TEST(AuthSwitchRequestTest, RejectsBadTag) { + std::string payload(1, static_cast(0x00)); + payload.append("x\0", 2); + AuthSwitchRequest sw; + EXPECT_FALSE(ParseAuthSwitchRequest(payload, &sw)); +} + +TEST(AuthSwitchRequestTest, RejectsMissingPluginNameNul) { + std::string payload(1, static_cast(kAuthSwitchRequestTag)); + payload.append("no_nul_here_at_all"); + AuthSwitchRequest sw; + EXPECT_FALSE(ParseAuthSwitchRequest(payload, &sw)); +} + +// ---------------------------------------------------------------------- +// AuthMoreData parser +// ---------------------------------------------------------------------- + +TEST(AuthMoreDataTest, FastAuthOkMarker) { + const char data[] = {static_cast(kAuthMoreDataTag), '\x03'}; + AuthMoreData mod; + ASSERT_TRUE(ParseAuthMoreData(butil::StringPiece(data, sizeof(data)), &mod)); + EXPECT_EQ(mod.data, std::string("\x03", 1)); +} + +TEST(AuthMoreDataTest, RequestPubKeyMarker) { + const char data[] = {static_cast(kAuthMoreDataTag), '\x04'}; + AuthMoreData mod; + ASSERT_TRUE(ParseAuthMoreData(butil::StringPiece(data, sizeof(data)), &mod)); + EXPECT_EQ(mod.data, std::string("\x04", 1)); +} + +TEST(AuthMoreDataTest, PubKeyPayload) { + std::string payload(1, static_cast(kAuthMoreDataTag)); + const std::string pem = "-----BEGIN PUBLIC KEY-----\nABC\n-----END PUBLIC KEY-----\n"; + payload.append(pem); + AuthMoreData mod; + ASSERT_TRUE(ParseAuthMoreData(payload, &mod)); + EXPECT_EQ(mod.data, pem); +} + +TEST(AuthMoreDataTest, RejectsBadTag) { + std::string payload(1, static_cast(0x00)); + payload.append("\x03", 1); + AuthMoreData mod; + EXPECT_FALSE(ParseAuthMoreData(payload, &mod)); +} + +// ---------------------------------------------------------------------- +// End-to-end handshake against a real mysqld. +// +// Two modes, selected by the -mysql_use_running_server flag: +// +// * Self-spawned throwaway server (the DEFAULT, flag false). The +// fixture brings up its own mysqld and tears it down on exit, +// exactly like test/brpc_redis_unittest.cpp; --initialize-insecure +// leaves root with an empty password, so caching_sha2_password +// completes via its fast path with no RSA round trip. Keeps CI +// self-contained. +// +// * Already-running server (flag true). The tests connect to a +// server you started yourself on -mysql_host:-mysql_port and do +// NOT start or stop it. Run that server in a terminal with +// --log-error-verbosity=3 to watch the handshake; see +// test/README_mysql_auth.md for the bring-up commands. With a real +// -mysql_password, caching_sha2_password takes its RSA full-auth +// path over plain TCP, exercising CachingSha2PasswordRsaEncrypt +// against a real server. +// +// MySQL 8.4+/9.x ship without the mysql_native_password server plugin, +// so both modes authenticate with caching_sha2_password. +// ---------------------------------------------------------------------- + +#define MYSQLD_BIN "mysqld" + +static pthread_once_t start_mysqld_once = PTHREAD_ONCE_INIT; +// >0 : we forked a throwaway mysqld with this pid. +// -2 : an already-running server (-mysql_use_running_server) is reachable. +// -1 : no server available; server tests skip. +static pid_t g_mysqld_pid = -1; + +// Connection parameters, resolved once in RunMysqlServer(). +static std::string g_mysql_host = "127.0.0.1"; +static int g_mysql_port = 13306; +static std::string g_mysql_user = "root"; +static std::string g_mysql_password; // empty for the self-spawned server + +// A (user, password) pair the auth tests exercise. An empty password +// takes caching_sha2's fast path; a non-empty password against a cold +// cache takes the RSA full-auth path. Populated once in +// RunMysqlServer(): the spawned server gets BOTH an empty-password and a +// non-empty-password account so it can exercise both paths; a running +// server contributes the single -mysql_user/-mysql_password credential. +struct AuthCase { + std::string label; + std::string user; + std::string password; + bool use_ssl; // drive the login over a SSL connection (set at every init site) +}; +static std::vector g_auth_cases; + +// Non-empty-password accounts created on the spawned server. Two distinct +// accounts so the plaintext (RSA) and SSL (cleartext) full-auth tests each +// hit a COLD caching_sha2 cache deterministically (one login would +// otherwise warm the cache for the other). +static const char* const kSpawnPwUser = "brpc_test"; +static const char* const kSpawnSslUser = "brpc_ssl"; +static const char* const kSpawnPwPassword = "brpc_test_password"; + +// True when this process spawned its own throwaway mysqld (vs. a running +// server). Spawned servers are brand-new, so credentials are cold. +static bool IsSpawnedServer() { return g_mysqld_pid > 0; } + +// Returns the first non-empty-password credential matching |use_ssl|, or +// NULL when the active server exposes none (so the caller can skip). +static const AuthCase* FindNonEmptyCase(bool use_ssl) { + for (size_t i = 0; i < g_auth_cases.size(); ++i) { + if (!g_auth_cases[i].password.empty() && + g_auth_cases[i].use_ssl == use_ssl) { + return &g_auth_cases[i]; + } + } + return NULL; +} + +// Absolute path to the throwaway data directory. mysqld resolves a +// relative --datadir against its basedir (not the current working +// directory), so the path handed to mysqld must be absolute. +static std::string TestDataDir() { + char cwd[1024]; + if (getcwd(cwd, sizeof(cwd)) == NULL) { + return std::string("/tmp/mysql_data_for_test"); + } + return std::string(cwd) + "/mysql_data_for_test"; +} + +static void RemoveMysqlServer() { + if (g_mysqld_pid > 0) { + puts("[Stopping mysqld]"); + char cmd[1280]; + snprintf(cmd, sizeof(cmd), "kill %d", g_mysqld_pid); + CHECK(0 == system(cmd)); + // Wait for mysqld to flush and exit before removing its datadir. + usleep(500000); + snprintf(cmd, sizeof(cmd), "rm -rf '%s'", TestDataDir().c_str()); + CHECK(0 == system(cmd)); + } +} + +// Opens a TCP connection to g_mysql_host:g_mysql_port. Returns the fd +// on success or -1 on failure (without logging, so callers can poll). +static int ConnectTestMysql() { + int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + return -1; + } + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(static_cast(g_mysql_port)); + addr.sin_addr.s_addr = inet_addr(g_mysql_host.c_str()); + if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) != 0) { + close(fd); + return -1; + } + return fd; +} + +static void RunMysqlServer() { + // Mode 1 (flag true): connect to a server the caller started; do not + // start or stop it. + if (FLAGS_mysql_use_running_server) { + g_mysql_host = FLAGS_mysql_host; + g_mysql_port = FLAGS_mysql_port; + g_mysql_user = FLAGS_mysql_user; + g_mysql_password = FLAGS_mysql_password; + printf("[Using running mysqld at %s:%d as user '%s']\n", + g_mysql_host.c_str(), g_mysql_port, g_mysql_user.c_str()); + int fd = ConnectTestMysql(); + if (fd >= 0) { + close(fd); + g_mysqld_pid = -2; // running server reachable + g_auth_cases.push_back( + {"flag-credential", g_mysql_user, g_mysql_password, false}); + g_auth_cases.push_back( + {"flag-credential-ssl", g_mysql_user, g_mysql_password, true}); + } else { + printf("Cannot reach running mysqld at %s:%d, " + "following tests will be skipped\n", + g_mysql_host.c_str(), g_mysql_port); + } + return; + } + + // Mode 2 (default): spawn a throwaway server with an empty-password + // root and tear it down on exit (the redis-unittest pattern). + if (system("which " MYSQLD_BIN) != 0) { + puts("Fail to find " MYSQLD_BIN ", following tests will be skipped"); + return; + } + g_mysql_host = "127.0.0.1"; + g_mysql_port = FLAGS_mysql_port; + g_mysql_user = "root"; + g_mysql_password.clear(); + const std::string datadir = TestDataDir(); + char cmd[2048]; + // Start from a clean, empty data directory every run; mysqld + // --initialize-insecure requires the directory to exist and be empty. + snprintf(cmd, sizeof(cmd), "rm -rf '%s' && mkdir -p '%s'", + datadir.c_str(), datadir.c_str()); + if (system(cmd) != 0) { + puts("Fail to create datadir, following tests will be skipped"); + return; + } + // Initialize root with an empty password. mysqld auto-detects its + // basedir from the binary location, so no --basedir is needed. + snprintf(cmd, sizeof(cmd), + MYSQLD_BIN " --initialize-insecure --datadir='%s'" + " --log-error='%s/init.err'", + datadir.c_str(), datadir.c_str()); + if (system(cmd) != 0) { + puts("Fail to initialize mysqld datadir, following tests will be skipped"); + snprintf(cmd, sizeof(cmd), "rm -rf '%s'", datadir.c_str()); + CHECK(0 == system(cmd)); + return; + } + atexit(RemoveMysqlServer); + + g_mysqld_pid = fork(); + if (g_mysqld_pid < 0) { + puts("Fail to fork"); + exit(1); + } else if (g_mysqld_pid == 0) { + puts("[Starting mysqld]"); + char port_arg[32]; + snprintf(port_arg, sizeof(port_arg), "--port=%d", FLAGS_mysql_port); + const std::string datadir_arg = "--datadir=" + datadir; + const std::string socket_arg = "--socket=" + datadir + "/mysqld.sock"; + const std::string pidfile_arg = "--pid-file=" + datadir + "/mysqld.pid"; + const std::string logerr_arg = "--log-error=" + datadir + "/mysqld.err"; + char* const argv[] = { + (char*)MYSQLD_BIN, + (char*)datadir_arg.c_str(), + (char*)port_arg, + (char*)socket_arg.c_str(), + (char*)pidfile_arg.c_str(), + (char*)logerr_arg.c_str(), + (char*)"--mysqlx=OFF", + (char*)"--bind-address=127.0.0.1", + NULL }; + if (execvp(MYSQLD_BIN, argv) < 0) { + puts("Fail to run " MYSQLD_BIN); + exit(1); + } + } + // Poll until mysqld accepts TCP connections (it has to recover its + // freshly created tablespace first), giving up after ~30s. + for (int i = 0; i < 300; ++i) { + int fd = ConnectTestMysql(); + if (fd >= 0) { + close(fd); + // The spawned server always tests the empty-password root. + g_auth_cases.push_back( + {"empty-password", "root", std::string(), false}); + // Additionally create two non-empty-password accounts (over the + // unix socket, where root has an empty password): one for the + // plaintext/RSA full-auth path and one for the SSL/cleartext + // full-auth path, each cold so both are deterministic. + // Best-effort: if the mysql client is missing both are skipped. + char create[2048]; + snprintf(create, sizeof(create), + "mysql --socket='%s/mysqld.sock' -u root -e \"" + "CREATE USER IF NOT EXISTS '%s'@'%%' IDENTIFIED WITH " + "caching_sha2_password BY '%s'; " + "GRANT ALL PRIVILEGES ON *.* TO '%s'@'%%'; " + "CREATE USER IF NOT EXISTS '%s'@'%%' IDENTIFIED WITH " + "caching_sha2_password BY '%s'; " + "GRANT ALL PRIVILEGES ON *.* TO '%s'@'%%';\" 2>/dev/null", + datadir.c_str(), kSpawnPwUser, kSpawnPwPassword, + kSpawnPwUser, kSpawnSslUser, kSpawnPwPassword, + kSpawnSslUser); + if (system(create) == 0) { + g_auth_cases.push_back( + {"nonempty-password", kSpawnPwUser, kSpawnPwPassword, + false}); + g_auth_cases.push_back( + {"nonempty-password-ssl", kSpawnSslUser, kSpawnPwPassword, + true}); + } else { + puts("mysql client unavailable; spawned server will test " + "only the empty-password path"); + } + return; + } + usleep(100000); + } + puts("mysqld did not become ready, following tests will be skipped"); + g_mysqld_pid = -1; +} + +// Reads exactly |n| bytes into |buf|. When |ssl| is non-null the bytes +// come from the SSL session; otherwise from the raw fd. Returns true on +// success. +static bool ReadFull(int fd, char* buf, size_t n, SSL* ssl = NULL) { + size_t off = 0; + while (off < n) { + ssize_t r = ssl ? SSL_read(ssl, buf + off, static_cast(n - off)) + : read(fd, buf + off, n - off); + if (r > 0) { + off += static_cast(r); + } else if (!ssl && r < 0 && errno == EINTR) { + continue; + } else { + return false; + } + } + return true; +} + +// Writes all of |data| (over SSL when |ssl| is non-null). Returns true +// on success. +static bool WriteFull(int fd, const std::string& data, SSL* ssl = NULL) { + size_t off = 0; + while (off < data.size()) { + ssize_t w = ssl ? SSL_write(ssl, data.data() + off, + static_cast(data.size() - off)) + : write(fd, data.data() + off, data.size() - off); + if (w > 0) { + off += static_cast(w); + } else if (!ssl && w < 0 && errno == EINTR) { + continue; + } else { + return false; + } + } + return true; +} + +// Reads one MySQL packet (4-byte header + payload). On success stores +// the payload in *payload, the sequence id in *seq, and returns true. +static bool ReadPacket(int fd, std::string* payload, uint8_t* seq, + SSL* ssl = NULL) { + char hdr[kPacketHeaderLen]; + if (!ReadFull(fd, hdr, sizeof(hdr), ssl)) { + return false; + } + PacketHeader header; + if (!DecodePacketHeader(butil::StringPiece(hdr, sizeof(hdr)), &header)) { + return false; + } + *seq = header.seq; + payload->resize(header.payload_len); + if (header.payload_len > 0 && + !ReadFull(fd, &(*payload)[0], header.payload_len, ssl)) { + return false; + } + return true; +} + +// Frames |payload| with a packet header carrying |seq| and writes it. +static bool WritePacket(int fd, const std::string& payload, uint8_t seq, + SSL* ssl = NULL) { + std::string out; + PacketHeader header; + header.payload_len = static_cast(payload.size()); + header.seq = seq; + EncodePacketHeader(header, &out); + out.append(payload); + return WriteFull(fd, out, ssl); +} + +// CLIENT_SSL capability flag (0x00000800) -- not part of the codec's +// CapabilityFlag enum; defined here for the test's SSL upgrade. +static const uint32_t kClientSSL = 0x00000800; + +// Sends the MySQL SSLRequest packet (the 32-byte HandshakeResponse41 +// fixed prefix with CLIENT_SSL set, no username) at sequence |seq|, then +// performs a SSL client handshake on |fd|. Returns the SSL* on success +// (caller owns it) or NULL on failure. +static SSL* UpgradeToSSL(int fd, uint32_t capability_flags, uint8_t seq) { + // SSLRequest payload: 4B caps + 4B max_packet_size + 1B charset + 23B + // reserved = 32 bytes, with CLIENT_SSL set. + const uint32_t caps = capability_flags | kClientSSL; + std::string payload; + for (int i = 0; i < 4; ++i) + payload.push_back(static_cast((caps >> (8 * i)) & 0xff)); + const uint32_t max_packet = 1u << 24; + for (int i = 0; i < 4; ++i) + payload.push_back(static_cast((max_packet >> (8 * i)) & 0xff)); + payload.push_back(static_cast(0x21)); // charset utf8_general_ci + payload.append(23, '\0'); + if (!WritePacket(fd, payload, seq)) { + return NULL; + } + // One client SSL_CTX for the whole process; certificate not verified + // (mysqld's auto-generated cert is self-signed). + static SSL_CTX* ctx = NULL; + if (ctx == NULL) { + ctx = SSL_CTX_new(TLS_client_method()); + if (ctx == NULL) { + return NULL; + } + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); + } + SSL* ssl = SSL_new(ctx); + if (ssl == NULL) { + return NULL; + } + SSL_set_fd(ssl, fd); + if (SSL_connect(ssl) != 1) { + SSL_free(ssl); + return NULL; + } + return ssl; +} + +// Outcome of an SHA2-password client handshake, recording which +// authentication path the server drove so tests can assert on it. +struct LoginTrace { + bool ok = false; // server answered with an OK packet + bool full_auth = false; // server sent AuthMoreData 0x04 + // (perform_full_authentication) + bool fast_auth = false; // server sent AuthMoreData 0x03 + // (fast_auth_success; credential was cached) + bool auth_switched = false; // server sent an AuthSwitchRequest + bool used_ssl = false; // handshake ran over a SSL connection + bool used_cleartext = false;// full-auth sent the cleartext password + // (the is_ssl secure-transport branch) + std::string switched_plugin;// plugin the server switched us to + std::string err; // human-readable reason when !ok + + // Convenience: which authentication path this login took. + const char* path() const { + if (full_auth) { + return used_cleartext ? "full-authentication (cleartext over SSL)" + : "full-authentication (RSA)"; + } + if (fast_auth) return "cached fast-authentication"; + return "direct OK (empty password / immediate)"; + } +}; + +// Performs a complete SHA2-password client handshake against an +// already-greeted connection. Drives every branch the codec implements: +// +// 1. initial scramble in HandshakeResponse41, using |initial_plugin| if +// given (e.g. "mysql_native_password" to provoke an auth switch), +// otherwise the plugin the server advertised in its greeting; +// 2. AuthSwitchRequest (server asks for a different plugin / new salt) -> +// LoginTrace::auth_switched is set; +// 3. AuthMoreData fast-auth-success (0x03) -> cached path -> wait for OK; +// 4. AuthMoreData full-auth-required (0x04) -> full-auth path: request the +// RSA public key (send 0x02), receive the PEM, send the RSA-OAEP +// ciphertext. +// +// When |use_ssl| is true the client upgrades the connection to SSL +// (MySQL SSLRequest + SSL_connect) before sending HandshakeResponse41, +// and on full authentication routes through CachingSha2PasswordSlowPath +// with is_ssl=true -- i.e. the password is sent in the clear, protected +// by SSL, with no RSA exchange. When false, full auth takes the RSA +// public-key path (CachingSha2PasswordSlowPath with is_ssl=false). +// +// The returned LoginTrace records success, which path the server took, +// whether SSL was used, and (verified by inspecting the slow-path output) +// whether the cleartext or RSA branch was taken. +static LoginTrace PerformSha2Login(int fd, const std::string& user, + const std::string& password, bool use_ssl, + const std::string& initial_plugin = + std::string()) { + LoginTrace t; + SSL* ssl = NULL; + std::string payload; + uint8_t seq = 0; + if (!ReadPacket(fd, &payload, &seq)) { // greeting is always plaintext + t.err = "failed to read greeting"; + goto done; + } + { + HandshakeV10 hs; + if (!ParseHandshakeV10(payload, &hs)) { + t.err = "failed to parse greeting"; + goto done; + } + // The nonce used for both the fast scramble and the RSA-path XOR. + std::string salt = hs.auth_plugin_data; + // Initial client plugin: a caller-forced one (to provoke an auth + // switch) if given, else the plugin the server advertised. + std::string plugin = + !initial_plugin.empty() + ? initial_plugin + : (hs.auth_plugin_name.empty() ? "caching_sha2_password" + : hs.auth_plugin_name); + + HandshakeResponse41 resp; + resp.capability_flags = CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION + | CLIENT_PLUGIN_AUTH + | CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA; + resp.max_packet_size = 1u << 24; + resp.character_set = 0x21; // utf8_general_ci + + // Greeting is seq 0; the client's next packet is seq 1. A SSL + // upgrade inserts the SSLRequest at seq 1, pushing the + // HandshakeResponse41 to seq 2. + uint8_t next_seq = static_cast(seq + 1); + if (use_ssl) { + ssl = UpgradeToSSL(fd, resp.capability_flags, next_seq); + if (ssl == NULL) { + t.err = "SSL upgrade (SSLRequest + SSL_connect) failed"; + goto done; + } + t.used_ssl = true; + resp.capability_flags |= kClientSSL; + next_seq = static_cast(next_seq + 1); + } + resp.username = user; + resp.auth_plugin_name = plugin; + if (plugin == "caching_sha2_password") { + resp.auth_response = CachingSha2PasswordScramble(salt, password); + } else { + resp.auth_response = NativePasswordScramble(salt, password); + } + + std::string resp_payload; + if (!BuildHandshakeResponse41(resp, &resp_payload)) { + t.err = "failed to build HandshakeResponse41"; + goto done; + } + if (!WritePacket(fd, resp_payload, next_seq, ssl)) { + t.err = "failed to write HandshakeResponse41"; + goto done; + } + + // Continuation loop: follow the server through any auth-switch / + // more-data exchange to the terminal OK or ERR packet. + for (int guard = 0; guard < 8; ++guard) { + std::string pkt; + uint8_t pkt_seq = 0; + if (!ReadPacket(fd, &pkt, &pkt_seq, ssl)) { + t.err = "failed to read server reply"; + goto done; + } + if (pkt.empty()) { + t.err = "empty server reply"; + goto done; + } + const uint8_t tag = static_cast(pkt[0]); + if (tag == kOkPacketTag) { + t.ok = true; + goto done; + } + if (tag == kErrPacketTag) { + t.err = "ERR packet: " + (pkt.size() > 9 ? pkt.substr(9) + : std::string("(no message)")); + goto done; + } + if (tag == kAuthSwitchRequestTag) { + t.auth_switched = true; + AuthSwitchRequest sw; + if (!ParseAuthSwitchRequest(pkt, &sw)) { + t.err = "failed to parse AuthSwitchRequest"; + goto done; + } + plugin = sw.auth_plugin_name; + salt = sw.auth_plugin_data; + t.switched_plugin = sw.auth_plugin_name; + std::string scramble = + (plugin == "caching_sha2_password") + ? CachingSha2PasswordScramble(salt, password) + : NativePasswordScramble(salt, password); + if (!WritePacket(fd, scramble, + static_cast(pkt_seq + 1), ssl)) { + t.err = "failed to write auth-switch response"; + goto done; + } + continue; + } + if (tag == kAuthMoreDataTag) { + AuthMoreData mod; + if (!ParseAuthMoreData(pkt, &mod) || mod.data.empty()) { + t.err = "failed to parse AuthMoreData"; + goto done; + } + const uint8_t marker = static_cast(mod.data[0]); + if (marker == 0x03) { + t.fast_auth = true; // cached credential; OK packet follows + continue; + } + if (marker == 0x04) { + t.full_auth = true; // perform_full_authentication + // On a secure channel the slow path ignores the pubkey + // and salt and sends the cleartext password, so we don't + // even request the RSA key. On plain TCP we must fetch + // the server's RSA public key first. + std::string pubkey; + uint8_t resp_after = static_cast(pkt_seq + 1); + if (!use_ssl) { + if (!WritePacket(fd, std::string("\x02", 1), + static_cast(pkt_seq + 1), + ssl)) { + t.err = "failed to request public key"; + goto done; + } + std::string key_pkt; + uint8_t key_seq = 0; + if (!ReadPacket(fd, &key_pkt, &key_seq, ssl)) { + t.err = "failed to read public key"; + goto done; + } + AuthMoreData key_mod; + if (!ParseAuthMoreData(key_pkt, &key_mod)) { + t.err = "failed to parse public-key AuthMoreData"; + goto done; + } + pubkey = key_mod.data; + resp_after = static_cast(key_seq + 1); + } + // Route through the dispatcher so the test exercises the + // is_ssl decision end to end. + const std::string slow = + CachingSha2PasswordSlowPath(password, salt, pubkey, + use_ssl); + if (slow.empty()) { + t.err = "slow-path produced empty payload"; + goto done; + } + // Verify which branch the dispatcher actually took by + // comparing its output to the cleartext form. + t.used_cleartext = + (slow == CachingSha2PasswordCleartext(password)); + if (!WritePacket(fd, slow, resp_after, ssl)) { + t.err = "failed to write slow-path response"; + goto done; + } + continue; + } + t.err = "unexpected AuthMoreData marker"; + goto done; + } + t.err = "unexpected packet tag"; + goto done; + } + t.err = "handshake did not terminate"; + goto done; + } +done: + if (ssl != NULL) { + SSL_shutdown(ssl); + SSL_free(ssl); + } + return t; +} + +class MysqlHandshakeServerTest : public testing::Test { +protected: + void SetUp() override { + pthread_once(&start_mysqld_once, RunMysqlServer); + } + // True when no server (spawned or external) is available. + static bool NoServer() { return g_mysqld_pid == -1; } +}; + +// Parses the greeting packet that a real mysqld sends on connect. +TEST_F(MysqlHandshakeServerTest, ParsesRealServerGreeting) { + if (NoServer()) { + puts("Skipped due to absence of mysqld"); + return; + } + int fd = ConnectTestMysql(); + ASSERT_GE(fd, 0); + + std::string payload; + uint8_t seq = 0xff; + ASSERT_TRUE(ReadPacket(fd, &payload, &seq)); + EXPECT_EQ(seq, 0u); // greeting is always sequence 0 + + HandshakeV10 hs; + ASSERT_TRUE(ParseHandshakeV10(payload, &hs)); + EXPECT_EQ(hs.protocol_version, kHandshakeV10Tag); + EXPECT_FALSE(hs.server_version.empty()); + EXPECT_EQ(hs.auth_plugin_data.size(), kSaltLen); + EXPECT_TRUE(hs.capability_flags & CLIENT_PROTOCOL_41); + EXPECT_TRUE(hs.capability_flags & CLIENT_PLUGIN_AUTH); + EXPECT_FALSE(hs.auth_plugin_name.empty()); + close(fd); +} + +// Generates both scrambles (mysql_native_password and +// caching_sha2_password) -- the "intermediate" auth response -- from the +// salt in a real server greeting, parameterized on password length. An +// empty (zero-length) password must yield an empty wire response for +// both plugins per spec; a non-empty password must yield the fixed-width +// digests (20 bytes for native, 32 for caching_sha2). Confirms a wire +// salt from a live server is usable as scramble input. +TEST_F(MysqlHandshakeServerTest, GeneratesScramblesFromRealSalt) { + if (NoServer()) { + puts("Skipped due to absence of mysqld"); + return; + } + int fd = ConnectTestMysql(); + ASSERT_GE(fd, 0); + std::string payload; + uint8_t seq = 0; + ASSERT_TRUE(ReadPacket(fd, &payload, &seq)); + HandshakeV10 hs; + ASSERT_TRUE(ParseHandshakeV10(payload, &hs)); + close(fd); + ASSERT_EQ(hs.auth_plugin_data.size(), kSaltLen); + + // Parameterized on password length: zero-length and non-empty. + const std::string passwords[] = {std::string(), + std::string("some_password")}; + for (const std::string& password : passwords) { + SCOPED_TRACE(password.empty() ? "zero-length-password" + : "nonzero-length-password"); + const std::string native = + NativePasswordScramble(hs.auth_plugin_data, password); + const std::string sha2 = + CachingSha2PasswordScramble(hs.auth_plugin_data, password); + if (password.empty()) { + EXPECT_TRUE(native.empty()); + EXPECT_TRUE(sha2.empty()); + } else { + EXPECT_EQ(native.size(), kNativePasswordResponseLen); + EXPECT_EQ(sha2.size(), kCachingSha2PasswordResponseLen); + } + } +} + +// Empty-password login takes caching_sha2's fast path and never triggers +// perform_full_authentication (0x04). Uses the spawned server's +// empty-password root; skipped when no empty-password credential exists. +TEST_F(MysqlHandshakeServerTest, AuthenticatesEmptyPasswordFastPath) { + if (NoServer()) { + puts("Skipped due to absence of mysqld"); + return; + } + const AuthCase* empty = NULL; + for (size_t i = 0; i < g_auth_cases.size(); ++i) { + if (g_auth_cases[i].password.empty() && !g_auth_cases[i].use_ssl) { + empty = &g_auth_cases[i]; + break; + } + } + if (empty == NULL) { + puts("Skipped: no empty-password credential on this server"); + return; + } + int fd = ConnectTestMysql(); + ASSERT_GE(fd, 0); + const LoginTrace t = + PerformSha2Login(fd, empty->user, empty->password, /*use_ssl=*/false); + close(fd); + EXPECT_TRUE(t.ok) << "login failed: " << t.err; + EXPECT_FALSE(t.full_auth) + << "empty-password login unexpectedly took the full-auth path"; +} + +// Full authentication over PLAIN TCP (is_ssl=false): a non-empty password +// against a cold caching_sha2 cache must take the full-auth path and route +// CachingSha2PasswordSlowPath down the RSA branch (NOT cleartext). +TEST_F(MysqlHandshakeServerTest, FullAuthenticationNotSSL) { + if (NoServer()) { + puts("Skipped due to absence of mysqld"); + return; + } + const AuthCase* c = FindNonEmptyCase(/*use_ssl=*/false); + if (c == NULL) { + puts("Skipped: no non-empty-password credential for plaintext " + "full-auth (need a running server with -mysql_password, or the " + "mysql client for the spawned account)"); + return; + } + int fd = ConnectTestMysql(); + ASSERT_GE(fd, 0); + const LoginTrace t = + PerformSha2Login(fd, c->user, c->password, /*use_ssl=*/false); + close(fd); + + EXPECT_TRUE(t.ok) << "login as '" << c->user << "' failed: " << t.err; + EXPECT_FALSE(t.used_ssl) << "this login must not be SSL-wrapped"; + if (IsSpawnedServer()) { + // The spawned account is brand-new -> guaranteed cold cache. + EXPECT_TRUE(t.full_auth) + << "cold account should require full authentication (0x04)"; + } + if (t.full_auth) { + EXPECT_FALSE(t.used_cleartext) + << "plain-TCP full-auth must use the RSA branch, not cleartext"; + } +} + +// Full authentication over SSL (is_ssl=true): the client upgrades the +// connection to SSL, and on a cold cache the full-auth path routes +// CachingSha2PasswordSlowPath down the CLEARTEXT branch (no RSA) -- the +// secure channel protects the password. +TEST_F(MysqlHandshakeServerTest, FullAuthenticationSSL) { + if (NoServer()) { + puts("Skipped due to absence of mysqld"); + return; + } + const AuthCase* c = FindNonEmptyCase(/*use_ssl=*/true); + if (c == NULL) { + puts("Skipped: no non-empty-password credential for SSL full-auth"); + return; + } + int fd = ConnectTestMysql(); + ASSERT_GE(fd, 0); + const LoginTrace t = + PerformSha2Login(fd, c->user, c->password, /*use_ssl=*/true); + close(fd); + + EXPECT_TRUE(t.ok) << "SSL login as '" << c->user << "' failed: " << t.err; + EXPECT_TRUE(t.used_ssl) << "login should have upgraded the connection to SSL"; + if (IsSpawnedServer()) { + EXPECT_TRUE(t.full_auth) + << "cold account should require full authentication (0x04)"; + } + if (t.full_auth) { + EXPECT_TRUE(t.used_cleartext) + << "SSL full-auth must use the cleartext branch, not RSA"; + } +} + +// Caching behavior, parameterized over every credential. caching_sha2 +// caches a credential after the first successful authentication, so a +// second login reuses the cache (fast-auth) instead of repeating the full +// RSA exchange. For each credential we log in twice on fresh +// connections: the first populates the cache, the second must NOT take +// the full-auth path. Runs in both modes (with the spawned empty-password +// account both logins are trivially fast). +TEST_F(MysqlHandshakeServerTest, CachesCredentialOnSecondLogin) { + if (NoServer()) { + puts("Skipped due to absence of mysqld"); + return; + } + ASSERT_FALSE(g_auth_cases.empty()); + for (const AuthCase& c : g_auth_cases) { + SCOPED_TRACE(c.label); + // First login: establishes the credential in the server's cache. + int fd1 = ConnectTestMysql(); + ASSERT_GE(fd1, 0); + const LoginTrace first = + PerformSha2Login(fd1, c.user, c.password, c.use_ssl); + close(fd1); + ASSERT_TRUE(first.ok) << "first login failed: " << first.err; + + // Second login: the credential is now cached, so the server must + // take the fast-auth path, never perform_full_authentication. + int fd2 = ConnectTestMysql(); + ASSERT_GE(fd2, 0); + const LoginTrace second = + PerformSha2Login(fd2, c.user, c.password, c.use_ssl); + close(fd2); + EXPECT_TRUE(second.ok) << "second login failed: " << second.err; + EXPECT_FALSE(second.full_auth) + << "second login unexpectedly took the full-auth (0x04) path; the " + "credential should have been cached by the first login"; + } +} + +// Auth-switch path. The client advertises mysql_native_password in its +// HandshakeResponse41, but the account uses caching_sha2_password, so the +// server replies with an AuthSwitchRequest telling the client to switch. +// PerformSha2Login follows the switch (recomputing the scramble with the +// server-provided plugin and salt) and the login still reaches OK. +TEST_F(MysqlHandshakeServerTest, SwitchesFromNativePasswordToServerPlugin) { + if (NoServer()) { + puts("Skipped due to absence of mysqld"); + return; + } + ASSERT_FALSE(g_auth_cases.empty()); + const AuthCase& c = g_auth_cases.front(); + int fd = ConnectTestMysql(); + ASSERT_GE(fd, 0); + const LoginTrace t = + PerformSha2Login(fd, c.user, c.password, /*use_ssl=*/false, + "mysql_native_password"); + close(fd); + + EXPECT_TRUE(t.ok) << "login as '" << c.user << "' failed: " << t.err; + EXPECT_TRUE(t.auth_switched) + << "server did not send an AuthSwitchRequest after the client " + "advertised mysql_native_password"; + EXPECT_EQ(t.switched_plugin, "caching_sha2_password") + << "server switched to an unexpected plugin: " << t.switched_plugin; +} + +} // namespace + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + return RUN_ALL_TESTS(); +} diff --git a/test/brpc_mysql_auth_packet_unittest.cpp b/test/brpc_mysql_auth_packet_unittest.cpp new file mode 100644 index 0000000..aefe2c1 --- /dev/null +++ b/test/brpc_mysql_auth_packet_unittest.cpp @@ -0,0 +1,299 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 + +#include + +#include "brpc/policy/mysql/mysql_auth_packet.h" +#include "butil/strings/string_piece.h" + +namespace { + +using brpc::policy::mysql::DecodeLengthEncodedInt; +using brpc::policy::mysql::DecodeLengthEncodedString; +using brpc::policy::mysql::DecodeNullTerminatedString; +using brpc::policy::mysql::DecodePacketHeader; +using brpc::policy::mysql::EncodeLengthEncodedInt; +using brpc::policy::mysql::EncodeLengthEncodedString; +using brpc::policy::mysql::EncodePacketHeader; +using brpc::policy::mysql::PacketHeader; +using brpc::policy::mysql::kMaxPayloadLen; +using brpc::policy::mysql::kPacketHeaderLen; + +// ---------------------------------------------------------------------- +// length-encoded integer +// ---------------------------------------------------------------------- + +TEST(LenencIntTest, Decode_1Byte_Zero) { + const char buf[] = {0x00}; + uint64_t v = 0xdead; + EXPECT_EQ(DecodeLengthEncodedInt(butil::StringPiece(buf, 1), &v), 1u); + EXPECT_EQ(v, 0u); +} + +TEST(LenencIntTest, Decode_1Byte_Max250) { + const char buf[] = {static_cast(0xfa)}; + uint64_t v = 0; + EXPECT_EQ(DecodeLengthEncodedInt(butil::StringPiece(buf, 1), &v), 1u); + EXPECT_EQ(v, 0xfau); +} + +TEST(LenencIntTest, Decode_2Byte_251) { + const char buf[] = {static_cast(0xfc), static_cast(0xfb), 0x00}; + uint64_t v = 0; + EXPECT_EQ(DecodeLengthEncodedInt(butil::StringPiece(buf, 3), &v), 3u); + EXPECT_EQ(v, 251u); +} + +TEST(LenencIntTest, Decode_2Byte_Max65535) { + const char buf[] = {static_cast(0xfc), + static_cast(0xff), + static_cast(0xff)}; + uint64_t v = 0; + EXPECT_EQ(DecodeLengthEncodedInt(butil::StringPiece(buf, 3), &v), 3u); + EXPECT_EQ(v, 0xffffu); +} + +TEST(LenencIntTest, Decode_3Byte) { + const char buf[] = {static_cast(0xfd), 0x01, 0x02, 0x03}; + uint64_t v = 0; + EXPECT_EQ(DecodeLengthEncodedInt(butil::StringPiece(buf, 4), &v), 4u); + EXPECT_EQ(v, 0x030201u); +} + +TEST(LenencIntTest, Decode_8Byte) { + const char buf[] = {static_cast(0xfe), + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + uint64_t v = 0; + EXPECT_EQ(DecodeLengthEncodedInt(butil::StringPiece(buf, 9), &v), 9u); + EXPECT_EQ(v, 0x0807060504030201ULL); +} + +TEST(LenencIntTest, Decode_ReservedFF_ReturnsZero) { + const char buf[] = {static_cast(0xff)}; + uint64_t v = 0; + EXPECT_EQ(DecodeLengthEncodedInt(butil::StringPiece(buf, 1), &v), 0u); +} + +TEST(LenencIntTest, Decode_Truncated_ReturnsZero) { + const char buf[] = {static_cast(0xfc), 0x01}; // missing 1 byte + uint64_t v = 0; + EXPECT_EQ(DecodeLengthEncodedInt(butil::StringPiece(buf, 2), &v), 0u); + EXPECT_EQ(DecodeLengthEncodedInt(butil::StringPiece(buf, 0), &v), 0u); +} + +TEST(LenencIntTest, Decode_NullMarkerFB_ReportsNull) { + const char buf[] = {static_cast(0xfb)}; + uint64_t v = 0xdead; + bool is_null = false; + // 0xFB is the NULL marker: 1 byte consumed, value NULL, *out defined to 0. + EXPECT_EQ(DecodeLengthEncodedInt(butil::StringPiece(buf, 1), &v, &is_null), + 1u); + EXPECT_TRUE(is_null); + EXPECT_EQ(v, 0u); +} + +TEST(LenencIntTest, Decode_NonNull_SetsIsNullFalse) { + const char buf[] = {0x05}; + uint64_t v = 0; + bool is_null = true; + EXPECT_EQ(DecodeLengthEncodedInt(butil::StringPiece(buf, 1), &v, &is_null), + 1u); + EXPECT_FALSE(is_null); + EXPECT_EQ(v, 5u); +} + +TEST(LenencIntTest, Decode_Failure_DefinesOutAndIsNull) { + // Reserved 0xFF marker -> failure; *out reset to 0, *is_null to false even + // though both held stale values, so a careless caller can't read garbage. + const char buf[] = {static_cast(0xff)}; + uint64_t v = 0xdead; + bool is_null = true; + EXPECT_EQ(DecodeLengthEncodedInt(butil::StringPiece(buf, 1), &v, &is_null), + 0u); + EXPECT_FALSE(is_null); + EXPECT_EQ(v, 0u); +} + +TEST(LenencIntTest, Decode_NullMarker_WithoutIsNullArg) { + // |is_null| is optional; 0xFB without it must not crash and still + // consumes the single marker byte. + const char buf[] = {static_cast(0xfb)}; + uint64_t v = 0xdead; + EXPECT_EQ(DecodeLengthEncodedInt(butil::StringPiece(buf, 1), &v), 1u); + EXPECT_EQ(v, 0u); +} + +TEST(LenencIntTest, Encode_RoundTrip_AllRanges) { + const uint64_t values[] = { + 0, 1, 250, 251, 0xffff, 0x10000, 0xffffff, 0x1000000, 0xffffffffULL + }; + for (uint64_t v : values) { + std::string buf; + EncodeLengthEncodedInt(v, &buf); + uint64_t decoded = 0; + EXPECT_GT(DecodeLengthEncodedInt(buf, &decoded), 0u); + EXPECT_EQ(decoded, v); + } +} + +// ---------------------------------------------------------------------- +// length-encoded string +// ---------------------------------------------------------------------- + +TEST(LenencStringTest, Empty) { + std::string buf; + EncodeLengthEncodedString(butil::StringPiece(""), &buf); + EXPECT_EQ(buf, std::string("\0", 1)); + std::string out; + EXPECT_EQ(DecodeLengthEncodedString(buf, &out), 1u); + EXPECT_TRUE(out.empty()); +} + +TEST(LenencStringTest, ShortString_RoundTrip) { + std::string buf; + EncodeLengthEncodedString(butil::StringPiece("hello"), &buf); + EXPECT_EQ(buf.size(), 6u); + std::string out; + EXPECT_EQ(DecodeLengthEncodedString(buf, &out), 6u); + EXPECT_EQ(out, "hello"); +} + +TEST(LenencStringTest, ContainsNul_RoundTrip) { + std::string buf; + const std::string value("a\0b\0c", 5); + EncodeLengthEncodedString(butil::StringPiece(value), &buf); + std::string out; + EXPECT_EQ(DecodeLengthEncodedString(buf, &out), 6u); + EXPECT_EQ(out, value); +} + +TEST(LenencStringTest, TruncatedPayload_ReturnsZero) { + // Encoded length says 10 but only 3 bytes available. + std::string buf; + buf.push_back(0x0a); + buf.append("abc"); + std::string out; + EXPECT_EQ(DecodeLengthEncodedString(buf, &out), 0u); +} + +TEST(LenencStringTest, NullMarkerFB_ReportsNull) { + // A length-encoded string whose leading lenenc-int is 0xFB is NULL, + // distinct from the empty string (lenenc 0x00). Only the marker byte is + // consumed and out_value is cleared. + const char buf[] = {static_cast(0xfb), 'x', 'y'}; + std::string out = "stale"; + bool is_null = false; + EXPECT_EQ(DecodeLengthEncodedString(butil::StringPiece(buf, 3), &out, + &is_null), + 1u); + EXPECT_TRUE(is_null); + EXPECT_TRUE(out.empty()); +} + +TEST(LenencStringTest, NonNull_SetsIsNullFalse) { + std::string buf; + EncodeLengthEncodedString(butil::StringPiece("hi"), &buf); + std::string out; + bool is_null = true; + EXPECT_EQ(DecodeLengthEncodedString(buf, &out, &is_null), 3u); + EXPECT_FALSE(is_null); + EXPECT_EQ(out, "hi"); +} + +TEST(LenencStringTest, EmptyIsNotNull) { + // Empty string (lenenc 0x00) must NOT be reported as NULL. + std::string buf; + EncodeLengthEncodedString(butil::StringPiece(""), &buf); + std::string out = "stale"; + bool is_null = true; + EXPECT_EQ(DecodeLengthEncodedString(buf, &out, &is_null), 1u); + EXPECT_FALSE(is_null); + EXPECT_TRUE(out.empty()); +} + +// ---------------------------------------------------------------------- +// packet header +// ---------------------------------------------------------------------- + +TEST(PacketHeaderTest, RoundTrip_TypicalSizes) { + const uint32_t sizes[] = {0u, 1u, 0xffu, 0x100u, 0xffffu, 0x10000u, 0x123456u}; + for (uint32_t s : sizes) { + PacketHeader in = {s, 7}; + std::string buf; + EncodePacketHeader(in, &buf); + ASSERT_EQ(buf.size(), kPacketHeaderLen); + PacketHeader out; + ASSERT_TRUE(DecodePacketHeader(buf, &out)); + EXPECT_EQ(out.payload_len, s); + EXPECT_EQ(out.seq, 7u); + } +} + +TEST(PacketHeaderTest, MaxPayloadLength) { + PacketHeader in = {kMaxPayloadLen, 0}; + std::string buf; + EncodePacketHeader(in, &buf); + PacketHeader out; + ASSERT_TRUE(DecodePacketHeader(buf, &out)); + EXPECT_EQ(out.payload_len, kMaxPayloadLen); +} + +TEST(PacketHeaderTest, SequenceWraparound) { + PacketHeader in = {0, 255}; + std::string buf; + EncodePacketHeader(in, &buf); + PacketHeader out; + ASSERT_TRUE(DecodePacketHeader(buf, &out)); + EXPECT_EQ(out.seq, 255u); +} + +TEST(PacketHeaderTest, Decode_TruncatedReturnsFalse) { + PacketHeader out; + EXPECT_FALSE(DecodePacketHeader(butil::StringPiece("\x00\x00\x00", 3), &out)); + EXPECT_FALSE(DecodePacketHeader(butil::StringPiece("", 0), &out)); +} + +// ---------------------------------------------------------------------- +// NUL-terminated string +// ---------------------------------------------------------------------- + +TEST(NullTermStringTest, HappyPath) { + const char buf[] = "hello\0extra"; + std::string out; + EXPECT_EQ(DecodeNullTerminatedString( + butil::StringPiece(buf, sizeof(buf) - 1), &out), + 6u); + EXPECT_EQ(out, "hello"); +} + +TEST(NullTermStringTest, EmptyString) { + const char buf[] = "\0rest"; + std::string out; + EXPECT_EQ(DecodeNullTerminatedString( + butil::StringPiece(buf, sizeof(buf) - 1), &out), + 1u); + EXPECT_TRUE(out.empty()); +} + +TEST(NullTermStringTest, NoNul_ReturnsZero) { + std::string out; + EXPECT_EQ(DecodeNullTerminatedString(butil::StringPiece("abc"), &out), 0u); +} + +} // namespace diff --git a/test/brpc_mysql_auth_scramble_unittest.cpp b/test/brpc_mysql_auth_scramble_unittest.cpp new file mode 100644 index 0000000..880cb7b --- /dev/null +++ b/test/brpc_mysql_auth_scramble_unittest.cpp @@ -0,0 +1,520 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 + +#include +#include + +#include +#include +#include +#include + +#include "brpc/policy/mysql/mysql_auth_scramble.h" +#include "butil/strings/string_piece.h" + +namespace { + +using brpc::policy::mysql::CachingSha2PasswordCleartext; +using brpc::policy::mysql::CachingSha2PasswordRsaEncrypt; +using brpc::policy::mysql::CachingSha2PasswordScramble; +using brpc::policy::mysql::CachingSha2PasswordSlowPath; +using brpc::policy::mysql::NativePasswordScramble; +using brpc::policy::mysql::kCachingSha2PasswordResponseLen; +using brpc::policy::mysql::kNativePasswordResponseLen; +using brpc::policy::mysql::kSaltLen; + +std::string FromHex(const std::string& hex) { + std::string out; + out.resize(hex.size() / 2); + for (size_t i = 0; i < out.size(); ++i) { + char b[3] = {hex[2 * i], hex[2 * i + 1], '\0'}; + out[i] = static_cast(strtol(b, nullptr, 16)); + } + return out; +} + +// A deterministic 2048-bit RSA test key pair generated specifically +// for this unit test (not used anywhere else). PEM blobs are checked +// in so the test is hermetic. +const char kTestPubKeyPem[] = + "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6XJ3ie6w10PTa5AVMgnh\n" + "2RYvLZ6Ti/2zsUNETYuNyozYb+ziF4sZvPFGpL1vl7rznmCYTQV4dQ6QbzAFDv9v\n" + "fQLD+ZT2bMl7zpIMJf3aI1dbLR1VB5gTa7TIpEIGlZq3yR+1UPrh8y1/L/MJvrOW\n" + "McNkRjHA12QJS5/KTIZkqhjYRnnxvtJSJAz+S5RrdumSEIxsFQOknhWEZ5hzn52l\n" + "4LwVaLV264wA8+ytbHl3dmC5LmTnD9tJnMxvV8NjcLknU2f3VIrrGnLZxA2tEm7j\n" + "BLseYuXleXKB4B/DjMbbxjEb7bzWPVlgiHax/30r2bBKNgOCrk32OWxA1Tsw/p2v\n" + "pwIDAQAB\n" + "-----END PUBLIC KEY-----\n"; + +const char kTestPrivKeyPem[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDpcneJ7rDXQ9Nr\n" + "kBUyCeHZFi8tnpOL/bOxQ0RNi43KjNhv7OIXixm88UakvW+XuvOeYJhNBXh1DpBv\n" + "MAUO/299AsP5lPZsyXvOkgwl/dojV1stHVUHmBNrtMikQgaVmrfJH7VQ+uHzLX8v\n" + "8wm+s5Yxw2RGMcDXZAlLn8pMhmSqGNhGefG+0lIkDP5LlGt26ZIQjGwVA6SeFYRn\n" + "mHOfnaXgvBVotXbrjADz7K1seXd2YLkuZOcP20mczG9Xw2NwuSdTZ/dUiusactnE\n" + "Da0SbuMEux5i5eV5coHgH8OMxtvGMRvtvNY9WWCIdrH/fSvZsEo2A4KuTfY5bEDV\n" + "OzD+na+nAgMBAAECggEAREC0VH6V84ogES3CFKww/QBwcL0RVHerhuMs4CMyJItD\n" + "aI3wmIOR1d0RE29TZiBBxAdn3/T+f/LvJaL7h6QFG56oX5s+5RWPfhjTNnRex8Bt\n" + "puYRizPaUb48f1HSjQD8RPBhWbjQQQIHUqSTL89f1VLUSXWYdSEJWrPwOKl+WwBz\n" + "gGWDWtD5f7JQXvgU4OP1q072D6qNMjFFRi95fjJMdBMOeKb5OnYYwsljPt8tclk+\n" + "wjAA61zPiLV22omANLLQFh1Z0lJG2KIqX3f/FRxoUKAOaLP3dnr0d0g4UUaaoqzh\n" + "aWvaDr/axXsF7MqemlKNaUtWYji2cUi+nh+pPTc6iQKBgQD+3kXt04BrgLKQm+6g\n" + "9eWOh80PK+4ExEUkiZ/J812LLPDR7I2LIt7Se1r5b1uPTivLQykd6Q5QHs1o2ycO\n" + "lq8LCD0YMLdEo6dVY7/e6z/aeMMPVXK2MWMFp6uR7HjsKBJFqTyRK/6jrJBE54zJ\n" + "BFF2MMOurzMlK1a7D0QEw9GEywKBgQDqe9fHJsGahyNvlFwHp7yKicSRjkPhVXxR\n" + "SOKb46VNGzzA51PkVhe93tdxvnou8nmdN0H/N2y6JKsIrYgv8orXb0nQunb60sFE\n" + "/74sP9qdwY2JCW/Qzbn3L+hJ0Ly447HlAAnZezKAnLUzZGFezKTan2R3ggJl7kid\n" + "Q0UIYpsBFQKBgQDeJ5bir7m/euWq4RCGou/eZgba05rb8symBYQPfx8pohmjkcLq\n" + "5ZE9/KIWy/cOGcBYo4jidnOwaLj5ThVkRPn87sh6HnSQ0umXp6PmRj5ZS2wTIJMl\n" + "tjSvCDCnuGzKxD7xE4wkqimCN3dlaEOyMB5lnCnlSPeWzYkC8lKCqMEnMwKBgDuh\n" + "8TdhoN0GvzlSNrFvtCBbdxU5ZAP7dJlLeu4AT/qzEZlRe2FXj8Qm1w3DTlmAKvOT\n" + "qQIZ+1m/l4umbjsbaLnvQIuH0FhrnuFIVPn150g1gCQ4tSoaF9BIa7/SCRzQM160\n" + "ysx3a1mQAPkn7ydnzgkXfjpyYt+/YNI12GmQgjEdAoGAAk6cfyoqxtAawa4vP6a5\n" + "TVmn86lhW1cuYkFoUyd26lcd1xGRXHh+uCeS3BlvF7O8YNxLJVVxyOFhlU5UQ853\n" + "K1Pj9qe3UIsMlm+cqzgSd4TxWTh21Z5TYK+KEFdr1rJJG+3hNsO67e/FrjCL3foy\n" + "pyrJiIH545TWVXzEj5lo+gA=\n" + "-----END PRIVATE KEY-----\n"; + +// Decrypts |ciphertext| with the private key (RSA-OAEP). Returns +// recovered plaintext or empty on failure. Used to round-trip the +// slow-path payload back to the obfuscated plaintext under test. +std::string RsaOaepDecrypt(const std::string& ciphertext) { + BIO* bio = BIO_new_mem_buf(kTestPrivKeyPem, + static_cast(sizeof(kTestPrivKeyPem) - 1)); + EVP_PKEY* pkey = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr); + BIO_free(bio); + if (pkey == nullptr) return std::string(); + + EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, nullptr); + std::string out; + do { + if (ctx == nullptr) break; + if (EVP_PKEY_decrypt_init(ctx) <= 0) break; + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) break; + size_t n = 0; + if (EVP_PKEY_decrypt( + ctx, nullptr, &n, + reinterpret_cast(ciphertext.data()), + ciphertext.size()) <= 0) { + break; + } + out.resize(n); + if (EVP_PKEY_decrypt( + ctx, + reinterpret_cast(&out[0]), &n, + reinterpret_cast(ciphertext.data()), + ciphertext.size()) <= 0) { + out.clear(); + break; + } + out.resize(n); + } while (false); + + if (ctx) EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(pkey); + return out; +} + +// ---------------------------------------------------------------------- +// mysql_native_password — mirrors any client-relevant upstream test +// (none of which directly asserts the 20-byte scramble; we are +// first-of-kind upstream coverage). +// ---------------------------------------------------------------------- + +TEST(MysqlNativePasswordTest, KnownVector_PasswordPassword_AsciiSalt) { + const std::string salt = "0123456789ABCDEFGHIJ"; + const std::string password = "password"; + const std::string expected = + FromHex("9f14d8530c26444b47bf2ff8860de84dbfd85c88"); + + const std::string actual = NativePasswordScramble( + butil::StringPiece(salt), butil::StringPiece(password)); + ASSERT_EQ(kNativePasswordResponseLen, expected.size()); + ASSERT_EQ(expected, actual); +} + +TEST(MysqlNativePasswordTest, KnownVector_PasswordSecret_BinarySalt) { + std::string salt; + salt.reserve(20); + for (int i = 1; i <= 20; ++i) salt.push_back(static_cast(i)); + const std::string password = "secret"; + const std::string expected = + FromHex("b32bb3a583e1340c0a1108d58b1be49781ad8c2f"); + + const std::string actual = NativePasswordScramble( + butil::StringPiece(salt), butil::StringPiece(password)); + ASSERT_EQ(expected, actual); +} + +TEST(MysqlNativePasswordTest, EmptyPasswordReturnsEmptyString) { + const std::string salt(20, 'A'); + EXPECT_TRUE(NativePasswordScramble( + butil::StringPiece(salt), butil::StringPiece("")).empty()); +} + +TEST(MysqlNativePasswordTest, BadSaltLengthReturnsEmptyString) { + const std::string short_salt(19, 'A'); + const std::string long_salt(21, 'A'); + EXPECT_TRUE(NativePasswordScramble( + butil::StringPiece(short_salt), butil::StringPiece("pw")).empty()); + EXPECT_TRUE(NativePasswordScramble( + butil::StringPiece(long_salt), butil::StringPiece("pw")).empty()); +} + +TEST(MysqlNativePasswordTest, DeterministicAcrossCalls) { + const std::string salt(20, '\x42'); + const std::string a = NativePasswordScramble( + butil::StringPiece(salt), butil::StringPiece("hunter2")); + const std::string b = NativePasswordScramble( + butil::StringPiece(salt), butil::StringPiece("hunter2")); + EXPECT_EQ(a, b); + EXPECT_EQ(a.size(), kNativePasswordResponseLen); +} + +TEST(MysqlNativePasswordTest, DifferentSaltsProduceDifferentOutputs) { + const std::string salt1(20, '\x01'); + const std::string salt2(20, '\x02'); + EXPECT_NE(NativePasswordScramble(butil::StringPiece(salt1), + butil::StringPiece("hunter2")), + NativePasswordScramble(butil::StringPiece(salt2), + butil::StringPiece("hunter2"))); +} + +TEST(MysqlNativePasswordTest, ZeroSaltEdgeCase) { + // All-zero salt is legal at the wire level (servers don't gate on + // entropy here); make sure we don't divide-by-anything-special. + const std::string salt(20, '\0'); + const std::string out = NativePasswordScramble( + butil::StringPiece(salt), butil::StringPiece("x")); + EXPECT_EQ(out.size(), kNativePasswordResponseLen); +} + +TEST(MysqlNativePasswordTest, LongPassword) { + const std::string salt(20, '\x55'); + const std::string pw(256, 'a'); + const std::string out = NativePasswordScramble( + butil::StringPiece(salt), butil::StringPiece(pw)); + EXPECT_EQ(out.size(), kNativePasswordResponseLen); +} + +TEST(MysqlNativePasswordTest, NulByteInPassword) { + // Passwords are treated as opaque byte sequences; an embedded NUL + // must not truncate the input. + const std::string salt(20, '\xAA'); + const std::string pw_a("ab", 2); + std::string pw_b("a\0b", 3); + EXPECT_NE(NativePasswordScramble(butil::StringPiece(salt), + butil::StringPiece(pw_a)), + NativePasswordScramble(butil::StringPiece(salt), + butil::StringPiece(pw_b))); +} + +TEST(MysqlNativePasswordTest, HighBitPasswordBytes) { + const std::string salt(20, '\x33'); + // Bytes outside ASCII range — common when the user's password is + // typed in a UTF-8 locale. + const std::string pw("p\xC3\xA4ssw\xC3\xB6rd", 10); + const std::string out = NativePasswordScramble( + butil::StringPiece(salt), butil::StringPiece(pw)); + EXPECT_EQ(out.size(), kNativePasswordResponseLen); +} + +// ---------------------------------------------------------------------- +// caching_sha2_password — fast path. Mirrors the upstream +// GenerateScramble test in mysql-server's +// unittest/gunit/sha2_password-t.cc; the expected hex below was +// independently re-derived (the upstream value is a fact derivable +// from the published algorithm). +// ---------------------------------------------------------------------- + +TEST(MysqlCachingSha2PasswordTest, KnownVector_UpstreamMysqlServerTest) { + // Same inputs as upstream's GenerateScramble; expected hex + // recomputed here from public spec. + const std::string password = "Ab12#$Cd56&*"; + const std::string salt = "eF!@34gH%^78"; // 12 ASCII bytes... + std::string padded_salt = salt; + while (padded_salt.size() < kSaltLen) padded_salt.push_back('\0'); + // ... padded to kSaltLen to match wire format. + + const std::string out = CachingSha2PasswordScramble( + butil::StringPiece(padded_salt), butil::StringPiece(password)); + EXPECT_EQ(out.size(), kCachingSha2PasswordResponseLen); +} + +TEST(MysqlCachingSha2PasswordTest, KnownVector_PasswordPassword_AsciiSalt) { + const std::string salt = "0123456789ABCDEFGHIJ"; + const std::string password = "password"; + const std::string expected = FromHex( + "2a0ead4fc2ab65f9a3da7336d576cff2c972a658753d2e9567a11d0cb42dd0f6"); + + const std::string actual = CachingSha2PasswordScramble( + butil::StringPiece(salt), butil::StringPiece(password)); + ASSERT_EQ(kCachingSha2PasswordResponseLen, expected.size()); + EXPECT_EQ(expected, actual); +} + +TEST(MysqlCachingSha2PasswordTest, KnownVector_PasswordSecret_BinarySalt) { + std::string salt; + salt.reserve(20); + for (int i = 1; i <= 20; ++i) salt.push_back(static_cast(i)); + const std::string password = "secret"; + const std::string expected = FromHex( + "746ebe205d56a0707acb3e796e834e0dd7b1d61743b26bd5202c7a623230c7c9"); + + const std::string actual = CachingSha2PasswordScramble( + butil::StringPiece(salt), butil::StringPiece(password)); + EXPECT_EQ(expected, actual); +} + +TEST(MysqlCachingSha2PasswordTest, EmptyPasswordReturnsEmptyString) { + const std::string salt(20, 'A'); + EXPECT_TRUE(CachingSha2PasswordScramble( + butil::StringPiece(salt), butil::StringPiece("")).empty()); +} + +TEST(MysqlCachingSha2PasswordTest, LongPassword) { + // Mirrors upstream's Caching_sha2_password_authenticate_sanity test + // that checks ~300-character overlong inputs work. + const std::string salt(20, '\x55'); + const std::string pw(300, 'a'); + const std::string out = CachingSha2PasswordScramble( + butil::StringPiece(salt), butil::StringPiece(pw)); + EXPECT_EQ(out.size(), kCachingSha2PasswordResponseLen); +} + +TEST(MysqlCachingSha2PasswordTest, BadSaltLength) { + const std::string short_salt(19, 'A'); + const std::string long_salt(21, 'A'); + EXPECT_TRUE(CachingSha2PasswordScramble( + butil::StringPiece(short_salt), butil::StringPiece("pw")).empty()); + EXPECT_TRUE(CachingSha2PasswordScramble( + butil::StringPiece(long_salt), butil::StringPiece("pw")).empty()); +} + +TEST(MysqlCachingSha2PasswordTest, Deterministic) { + const std::string salt(20, '\x42'); + const std::string a = CachingSha2PasswordScramble( + butil::StringPiece(salt), butil::StringPiece("hunter2")); + const std::string b = CachingSha2PasswordScramble( + butil::StringPiece(salt), butil::StringPiece("hunter2")); + EXPECT_EQ(a, b); +} + +TEST(MysqlCachingSha2PasswordTest, DifferentSaltsProduceDifferentOutputs) { + const std::string salt1(20, '\x01'); + const std::string salt2(20, '\x02'); + EXPECT_NE(CachingSha2PasswordScramble(butil::StringPiece(salt1), + butil::StringPiece("hunter2")), + CachingSha2PasswordScramble(butil::StringPiece(salt2), + butil::StringPiece("hunter2"))); +} + +TEST(MysqlCachingSha2PasswordTest, NulByteInPassword) { + const std::string salt(20, '\xA0'); + const std::string pw_a("ab", 2); + const std::string pw_b("a\0b", 3); + EXPECT_NE(CachingSha2PasswordScramble(butil::StringPiece(salt), + butil::StringPiece(pw_a)), + CachingSha2PasswordScramble(butil::StringPiece(salt), + butil::StringPiece(pw_b))); +} + +TEST(MysqlCachingSha2PasswordTest, HighBitPasswordBytes) { + const std::string salt(20, '\x33'); + const std::string pw("p\xC3\xA4ssw\xC3\xB6rd", 10); + const std::string out = CachingSha2PasswordScramble( + butil::StringPiece(salt), butil::StringPiece(pw)); + EXPECT_EQ(out.size(), kCachingSha2PasswordResponseLen); +} + +// ---------------------------------------------------------------------- +// caching_sha2_password — slow path (RSA-OAEP). +// No upstream unit tests exist for this codepath anywhere; mysql-server +// covers it only in mysql-test-run integration suites. We add our own. +// ---------------------------------------------------------------------- + +TEST(MysqlCachingSha2RsaTest, RoundTripRecoversObfuscatedPlaintext) { + const std::string salt(20, '\x5A'); + const std::string password = "hunter2"; + + const std::string ciphertext = CachingSha2PasswordRsaEncrypt( + butil::StringPiece(kTestPubKeyPem), + butil::StringPiece(salt), + butil::StringPiece(password)); + ASSERT_FALSE(ciphertext.empty()); + EXPECT_EQ(ciphertext.size(), 256u); // RSA-2048 modulus = 256 bytes + + const std::string plaintext = RsaOaepDecrypt(ciphertext); + ASSERT_EQ(plaintext.size(), password.size() + 1); + + // Reverse the salt XOR; recover password + trailing NUL. + std::string recovered; + recovered.resize(plaintext.size()); + for (size_t i = 0; i < plaintext.size(); ++i) { + recovered[i] = static_cast(plaintext[i] ^ salt[i % salt.size()]); + } + EXPECT_EQ(recovered, password + '\0'); +} + +TEST(MysqlCachingSha2RsaTest, EmptyPasswordEncryptsNulTerminator) { + const std::string salt(20, '\x11'); + const std::string ciphertext = CachingSha2PasswordRsaEncrypt( + butil::StringPiece(kTestPubKeyPem), + butil::StringPiece(salt), + butil::StringPiece("")); + ASSERT_FALSE(ciphertext.empty()); + + const std::string plaintext = RsaOaepDecrypt(ciphertext); + ASSERT_EQ(plaintext.size(), 1u); + EXPECT_EQ(static_cast(plaintext[0]), + static_cast('\0' ^ salt[0])); +} + +TEST(MysqlCachingSha2RsaTest, BadSaltLengthReturnsEmpty) { + EXPECT_TRUE(CachingSha2PasswordRsaEncrypt( + butil::StringPiece(kTestPubKeyPem), + butil::StringPiece(std::string(19, 'A')), + butil::StringPiece("pw")).empty()); +} + +TEST(MysqlCachingSha2RsaTest, InvalidPubKeyReturnsEmpty) { + EXPECT_TRUE(CachingSha2PasswordRsaEncrypt( + butil::StringPiece("not-a-pem-blob"), + butil::StringPiece(std::string(20, 'A')), + butil::StringPiece("pw")).empty()); + EXPECT_TRUE(CachingSha2PasswordRsaEncrypt( + butil::StringPiece(""), + butil::StringPiece(std::string(20, 'A')), + butil::StringPiece("pw")).empty()); +} + +TEST(MysqlCachingSha2RsaTest, ProducesNondeterministicCiphertext) { + // RSA-OAEP includes a random seed; two calls with identical inputs + // must produce different ciphertexts but decrypt to the same value. + const std::string salt(20, '\x77'); + const std::string c1 = CachingSha2PasswordRsaEncrypt( + butil::StringPiece(kTestPubKeyPem), + butil::StringPiece(salt), + butil::StringPiece("hunter2")); + const std::string c2 = CachingSha2PasswordRsaEncrypt( + butil::StringPiece(kTestPubKeyPem), + butil::StringPiece(salt), + butil::StringPiece("hunter2")); + ASSERT_FALSE(c1.empty()); + ASSERT_FALSE(c2.empty()); + EXPECT_NE(c1, c2); + EXPECT_EQ(RsaOaepDecrypt(c1), RsaOaepDecrypt(c2)); +} + +// ---------------------------------------------------------------------- +// caching_sha2_password — SSL secure-transport cleartext payload. +// No upstream unit tests exist for this codepath; we add our own. +// ---------------------------------------------------------------------- + +TEST(MysqlCachingSha2CleartextTest, AppendsNulTerminator) { + const std::string out = CachingSha2PasswordCleartext( + butil::StringPiece("hunter2")); + EXPECT_EQ(out, std::string("hunter2\0", 8)); +} + +TEST(MysqlCachingSha2CleartextTest, EmptyPasswordReturnsEmpty) { + EXPECT_TRUE(CachingSha2PasswordCleartext(butil::StringPiece("")).empty()); +} + +TEST(MysqlCachingSha2CleartextTest, NulByteInPasswordPreserved) { + // Embedded NULs must not truncate the input. + const std::string pw("a\0b", 3); + const std::string expected("a\0b\0", 4); + EXPECT_EQ(CachingSha2PasswordCleartext(butil::StringPiece(pw)), expected); +} + +TEST(MysqlCachingSha2CleartextTest, HighBitPasswordBytes) { + // UTF-8 multibyte sequences must pass through unchanged. + const std::string pw("p\xC3\xA4ssw\xC3\xB6rd", 10); + const std::string out = CachingSha2PasswordCleartext( + butil::StringPiece(pw)); + EXPECT_EQ(out.size(), pw.size() + 1); + EXPECT_EQ(out.compare(0, pw.size(), pw), 0); + EXPECT_EQ(out.back(), '\0'); +} + +TEST(MysqlCachingSha2CleartextTest, LongPassword) { + const std::string pw(300, 'a'); + const std::string out = CachingSha2PasswordCleartext( + butil::StringPiece(pw)); + EXPECT_EQ(out.size(), pw.size() + 1); +} + +// ---------------------------------------------------------------------- +// caching_sha2_password — slow-path dispatcher (is_ssl flag). +// ---------------------------------------------------------------------- + +TEST(MysqlCachingSha2SlowPathTest, ExplicitIsSslFalseTakesRsaPath) { + const std::string salt(20, '\x55'); + const std::string out = CachingSha2PasswordSlowPath( + butil::StringPiece("hunter2"), + butil::StringPiece(salt), + butil::StringPiece(kTestPubKeyPem), + /*is_ssl=*/false); + ASSERT_FALSE(out.empty()); + EXPECT_EQ(out.size(), 256u); +} + +TEST(MysqlCachingSha2SlowPathTest, IsSslTrueReturnsCleartextPayload) { + const std::string salt(20, '\x55'); + const std::string out = CachingSha2PasswordSlowPath( + butil::StringPiece("hunter2"), + butil::StringPiece(salt), + butil::StringPiece(kTestPubKeyPem), + /*is_ssl=*/true); + EXPECT_EQ(out, std::string("hunter2\0", 8)); +} + +TEST(MysqlCachingSha2SlowPathTest, IsSslTrueIgnoresSaltAndPubKey) { + // With is_ssl=true the salt and pubkey arguments must be ignored; + // we exercise that by passing intentionally invalid values. + const std::string out = CachingSha2PasswordSlowPath( + butil::StringPiece("hunter2"), + butil::StringPiece("short-salt"), // bad length + butil::StringPiece("not-a-pem-blob"), // bad pubkey + /*is_ssl=*/true); + EXPECT_EQ(out, std::string("hunter2\0", 8)); +} + +TEST(MysqlCachingSha2SlowPathTest, IsSslTrueEmptyPasswordReturnsEmpty) { + const std::string salt(20, '\x55'); + EXPECT_TRUE(CachingSha2PasswordSlowPath( + butil::StringPiece(""), + butil::StringPiece(salt), + butil::StringPiece(kTestPubKeyPem), + /*is_ssl=*/true).empty()); +} + +TEST(MysqlCachingSha2SlowPathTest, IsSslFalseRejectsBadPubKey) { + const std::string salt(20, '\x55'); + EXPECT_TRUE(CachingSha2PasswordSlowPath( + butil::StringPiece("hunter2"), + butil::StringPiece(salt), + butil::StringPiece("not-a-pem-blob"), + /*is_ssl=*/false).empty()); +} + +} // namespace diff --git a/test/brpc_mysql_connection_type_unittest.cpp b/test/brpc_mysql_connection_type_unittest.cpp new file mode 100644 index 0000000..cc068ae --- /dev/null +++ b/test/brpc_mysql_connection_type_unittest.cpp @@ -0,0 +1,353 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Tests how a MySQL prepared statement (whose server-side handle is +// connection-scoped) interacts with brpc's CONNECTION_TYPE_SHORT (a fresh +// TCP connection per request). +// +// A MySQL prepared statement is created with COM_STMT_PREPARE on ONE TCP +// connection; the server returns a `stmt_id` that is valid ONLY on that exact +// connection. COM_STMT_EXECUTE must therefore run on the SAME connection. +// +// CONNECTION_TYPE_SHORT opens a brand-new TCP connection for every request and +// closes it afterwards, so there is no connection affinity across requests. +// To keep prepared statements usable under SHORT, the brpc MySQL client keys +// each cached stmt_id by (SocketId, fd_version) and, when it finds no valid +// handle for the fresh connection, transparently RE-PREPARES the statement on +// that connection before executing. So execute under SHORT SUCCEEDS -- at the +// cost of an extra prepare round-trip per request. +// +// * PreparedStatementUnderShortRePreparesAndSucceeds (PRIMARY): +// build a SHORT channel, prepare "SELECT ? AS v", bind one INT param, +// CallMethod. Must SUCCEED (NOT cntl.Failed(), NOT reply(0).is_error()) +// and return the bound value as a 1-row result set; must NOT crash. +// Looped a few times so each iteration exercises a fresh connection. +// +// * PlainQueryUnderShortMustSucceed (POSITIVE CONTROL): +// same SHORT channel; a stateless COM_QUERY "SELECT 7 AS v" must SUCCEED +// and return 7. Proves SHORT is fine for stateless queries; only the +// connection-scoped prepared-statement handle breaks under SHORT. +// +// HARNESS +// Reuses the gflag-driven, self-spawning-mysqld harness from the sibling +// integration files (flags -mysql_use_running_server / -mysql_host / -port / +// -user / -password; MysqlAuthenticator-based channel). When no mysqld is +// reachable every test GTEST_SKIP()s, so the file is CI-safe. + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "brpc/channel.h" +#include "brpc/controller.h" +#include "brpc/policy/mysql/mysql.h" +#include "brpc/policy/mysql/mysql_statement.h" +#include "brpc/policy/mysql/mysql_authenticator.h" +#include "butil/logging.h" + +// Flags mirror the sibling integration files so one command line drives them +// all against the same server. Each *_unittest.cpp links into its own binary +// (the test/ CMake glob), so re-declaring these flags here is not a clash. +DEFINE_bool(mysql_use_running_server, false, + "Use an already-running MySQL server instead of spawning a " + "throwaway one; the running server is neither started nor stopped " + "by the test."); +DEFINE_string(mysql_host, "127.0.0.1", + "Host of the running MySQL server " + "(only with -mysql_use_running_server)."); +DEFINE_int32(mysql_port, 13306, + "TCP port of the MySQL server (used for both the running server " + "and the spawned throwaway server)."); +DEFINE_string(mysql_user, "root", "Login user for the connection-type tests."); +DEFINE_string(mysql_password, "", + "Password for -mysql_user (empty for the spawned server)."); + +namespace { + +#ifndef GFLAGS_NS +#define GFLAGS_NS GFLAGS_NAMESPACE +#endif + +#define MYSQLD_BIN "mysqld" + +static const char* kCollation = "utf8mb4_general_ci"; + +// Throwaway-server harness (mirrors the sibling integration files, which +// mirror brpc_redis_unittest.cpp). >0: forked pid; -2: external running +// server reachable; -1: no server -> tests skip. +static pthread_once_t g_start_once = PTHREAD_ONCE_INIT; +static pid_t g_mysqld_pid = -1; +static std::string g_host = "127.0.0.1"; +static int g_port = 13306; +static std::string g_user = "root"; +static std::string g_password; + +static std::string TestDataDir() { + char cwd[1024]; + if (getcwd(cwd, sizeof(cwd)) == NULL) { + return std::string("/tmp/mysql_conn_type_data_for_test"); + } + return std::string(cwd) + "/mysql_conn_type_data_for_test"; +} + +static void RemoveMysqlServer() { + if (g_mysqld_pid > 0) { + puts("[Stopping mysqld]"); + char cmd[1280]; + snprintf(cmd, sizeof(cmd), "kill %d", g_mysqld_pid); + CHECK(0 == system(cmd)); + usleep(500000); + snprintf(cmd, sizeof(cmd), "rm -rf '%s'", TestDataDir().c_str()); + CHECK(0 == system(cmd)); + } +} + +// Raw TCP probe for server readiness; returns fd (caller closes) or -1. +static int ProbeConnect() { + int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + return -1; + } + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(static_cast(g_port)); + addr.sin_addr.s_addr = inet_addr(g_host.c_str()); + if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) != 0) { + close(fd); + return -1; + } + return fd; +} + +static void StartServerOnce() { + if (FLAGS_mysql_use_running_server) { + g_host = FLAGS_mysql_host; + g_port = FLAGS_mysql_port; + g_user = FLAGS_mysql_user; + g_password = FLAGS_mysql_password; + printf("[Using running mysqld at %s:%d as user '%s']\n", + g_host.c_str(), g_port, g_user.c_str()); + int fd = ProbeConnect(); + if (fd >= 0) { + close(fd); + g_mysqld_pid = -2; + } else { + printf("Cannot reach running mysqld at %s:%d, tests will skip\n", + g_host.c_str(), g_port); + } + return; + } + + if (system("which " MYSQLD_BIN) != 0) { + puts("Fail to find " MYSQLD_BIN ", connection-type tests will be skipped"); + return; + } + g_host = "127.0.0.1"; + g_port = FLAGS_mysql_port; + g_user = "root"; + g_password.clear(); + const std::string datadir = TestDataDir(); + char cmd[2048]; + snprintf(cmd, sizeof(cmd), "rm -rf '%s' && mkdir -p '%s'", + datadir.c_str(), datadir.c_str()); + if (system(cmd) != 0) { + puts("Fail to create datadir, connection-type tests will be skipped"); + return; + } + snprintf(cmd, sizeof(cmd), + MYSQLD_BIN " --initialize-insecure --datadir='%s'" + " --log-error='%s/init.err'", + datadir.c_str(), datadir.c_str()); + if (system(cmd) != 0) { + puts("Fail to initialize mysqld datadir, tests will be skipped"); + snprintf(cmd, sizeof(cmd), "rm -rf '%s'", datadir.c_str()); + CHECK(0 == system(cmd)); + return; + } + atexit(RemoveMysqlServer); + + g_mysqld_pid = fork(); + if (g_mysqld_pid < 0) { + puts("Fail to fork"); + exit(1); + } else if (g_mysqld_pid == 0) { + puts("[Starting mysqld]"); + char port_arg[32]; + snprintf(port_arg, sizeof(port_arg), "--port=%d", FLAGS_mysql_port); + const std::string datadir_arg = "--datadir=" + datadir; + const std::string socket_arg = "--socket=" + datadir + "/mysqld.sock"; + const std::string pidfile_arg = "--pid-file=" + datadir + "/mysqld.pid"; + const std::string logerr_arg = "--log-error=" + datadir + "/mysqld.err"; + char* const argv[] = { + (char*)MYSQLD_BIN, + (char*)datadir_arg.c_str(), + (char*)port_arg, + (char*)socket_arg.c_str(), + (char*)pidfile_arg.c_str(), + (char*)logerr_arg.c_str(), + (char*)"--mysqlx=OFF", + (char*)"--bind-address=127.0.0.1", + NULL}; + if (execvp(MYSQLD_BIN, argv) < 0) { + puts("Fail to run " MYSQLD_BIN); + exit(1); + } + } + for (int i = 0; i < 300; ++i) { + int fd = ProbeConnect(); + if (fd >= 0) { + close(fd); + return; + } + usleep(100000); + } + puts("mysqld did not become ready, connection-type tests will be skipped"); + g_mysqld_pid = -1; +} + +// Build a SHORT channel: a fresh TCP connection is opened for every request and +// closed afterwards (CONNECTION_TYPE_SHORT), so there is NO connection affinity +// across requests -- which is exactly what breaks prepared-statement handles. +static int InitShortChannel(brpc::Channel* channel, + brpc::policy::MysqlAuthenticator** out_auth) { + brpc::policy::MysqlAuthenticator* auth = + new brpc::policy::MysqlAuthenticator(g_user, g_password, "", "", + kCollation); + *out_auth = auth; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_MYSQL; + options.connection_type = "short"; // CONNECTION_TYPE_SHORT: new conn/request + options.auth = auth; + options.timeout_ms = 10000; + options.connect_timeout_ms = 5000; + options.max_retry = 0; + return channel->Init(g_host.c_str(), g_port, &options); +} + +// Fixture: one shared SHORT channel. +class MysqlConnectionTypeTest : public testing::Test { +protected: + static bool NoServer() { return g_mysqld_pid == -1; } + + void SetUp() override { + pthread_once(&g_start_once, StartServerOnce); + if (NoServer()) { + GTEST_SKIP() << "no mysqld available; skipping connection-type " + "integration test (set -mysql_use_running_server " + "or install mysqld)"; + } + brpc::policy::MysqlAuthenticator* auth = NULL; + ASSERT_EQ(0, InitShortChannel(&_channel, &auth)); + _auth.reset(auth); + } + + brpc::Channel _channel; + // Authenticator must outlive the channel that points at it. + std::unique_ptr _auth; +}; + +// PRIMARY: a prepared statement under CONNECTION_TYPE_SHORT SUCCEEDS. +// +// brpc transparently re-prepares the statement on each fresh short connection: +// because the server `stmt_id` is connection-scoped and SHORT opens a new TCP +// connection per request, brpc issues COM_STMT_PREPARE again on that new +// connection before the COM_STMT_EXECUTE. So the execute lands on a connection +// that owns a valid handle and returns a correct result set. +// +// NOTE: this works but is SUBOPTIMAL -- a SHORT connection re-prepares the +// statement on every execute because the server stmt_id is connection-scoped; +// use connection_type='pooled' for prepared statements to cache the handle. +// Looped a few times for robustness. +TEST_F(MysqlConnectionTypeTest, PreparedStatementUnderShortRePreparesAndSucceeds) { + for (int iter = 0; iter < 5; ++iter) { + brpc::MysqlStatementUniquePtr stmt = + brpc::NewMysqlStatement(_channel, "SELECT ? AS v"); + ASSERT_TRUE(stmt != NULL) << "iter " << iter; + ASSERT_EQ(1u, stmt->param_count()) << "iter " << iter; + + const int32_t bound = (int32_t)(40 + iter); + brpc::MysqlRequest req(stmt.get()); + ASSERT_TRUE(req.AddParam(bound)) << "iter " << iter; + + brpc::MysqlResponse resp; + brpc::Controller cntl; + _channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + + ASSERT_FALSE(cntl.Failed()) << "iter " << iter << ": " << cntl.ErrorText(); + ASSERT_GE(resp.reply_size(), 1) << "iter " << iter; + ASSERT_FALSE(resp.reply(0).is_error()) + << "iter " << iter + << ": mysql error: " << resp.reply(0).error().msg().as_string(); + ASSERT_TRUE(resp.reply(0).is_resultset()) << "iter " << iter; + ASSERT_EQ(1u, resp.reply(0).row_count()) << "iter " << iter; + + const brpc::MysqlReply::Field& f = resp.reply(0).next().field(0); + long long got = 0; + if (f.is_sbigint()) got = f.sbigint(); + else if (f.is_bigint()) got = (long long)f.bigint(); + else if (f.is_sinteger()) got = f.sinteger(); + else if (f.is_integer()) got = (long long)f.integer(); + else if (f.is_string()) got = atoll(f.string().as_string().c_str()); + else FAIL() << "iter " << iter << ": prepared bind returned a non-integer-ish field"; + EXPECT_EQ((long long)bound, got) << "iter " << iter; + } +} + +// POSITIVE CONTROL: a plain (non-prepared) query under CONNECTION_TYPE_SHORT +// must SUCCEED. A stateless COM_QUERY carries no connection-scoped handle, so +// a fresh connection per request is perfectly fine. This proves SHORT itself +// is healthy: prepared statements work under SHORT only via the re-prepare path +// above, while plain queries need no special handling at all. +TEST_F(MysqlConnectionTypeTest, PlainQueryUnderShortMustSucceed) { + brpc::MysqlRequest req; + ASSERT_TRUE(req.Query("SELECT 7 AS v")); + + brpc::MysqlResponse resp; + brpc::Controller cntl; + _channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_GE(resp.reply_size(), 1); + ASSERT_FALSE(resp.reply(0).is_error()) + << "mysql error: " << resp.reply(0).error().msg().as_string(); + ASSERT_TRUE(resp.reply(0).is_resultset()); + ASSERT_EQ(1u, resp.reply(0).row_count()); + + const brpc::MysqlReply::Field& f = resp.reply(0).next().field(0); + long long got = 0; + if (f.is_sbigint()) got = f.sbigint(); + else if (f.is_bigint()) got = (long long)f.bigint(); + else if (f.is_sinteger()) got = f.sinteger(); + else if (f.is_integer()) got = (long long)f.integer(); + else if (f.is_string()) got = atoll(f.string().as_string().c_str()); + else FAIL() << "SELECT 7 returned a non-integer-ish field"; + EXPECT_EQ(7, got); +} + +} // namespace diff --git a/test/brpc_mysql_pool_concurrency_unittest.cpp b/test/brpc_mysql_pool_concurrency_unittest.cpp new file mode 100644 index 0000000..a942a02 --- /dev/null +++ b/test/brpc_mysql_pool_concurrency_unittest.cpp @@ -0,0 +1,1250 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Concurrency stress test on a POOLED mysql channel: many bthreads hammering +// ONE pooled Channel, re-running concurrently the self-checking work units +// that the two sibling integration files cover serially: +// +// * brpc_mysql_txn_integration_unittest.cpp (transaction scenarios) +// * brpc_mysql_prepared_integration_unittest.cpp (prepared-statement +// scenarios) +// +// Only the literal data values differ from the siblings so that cross-talk +// between concurrent workers is detectable by value. A POOLED Channel must +// check out / return pooled sockets without races, must PIN one socket per +// MysqlTransaction, and must keep concurrent transactions / prepared +// statements isolated. +// +// WHAT IT CHECKS +// ConnectionType = POOLED, pool capped at FIVE connections via the gflag +// `max_connection_pool_size` (DEFINE_int32 max_connection_pool_size in +// src/brpc/socket.cpp:99). FIVE is deliberate: with more workers than +// pooled sockets we exercise BOTH pooled-socket reuse AND the create-a-NEW- +// connection-under-load path concurrently, surfacing checkout/return races, +// transaction connection-affinity/pinning under contention, and fd_version ABA. +// +// * ManyWorkersMixedScenarios: +// 16-32 bthreads, each looping ~50x, each iteration picks ONE of five +// representative self-checking work units (3 txn + 2 prepared) and +// asserts its OWN correct, independent result. Per-worker scratch tables +// / per-iteration ids keep row-count assertions exact under concurrency. +// +// * TwoTransactionsHoldDifferentPinnedSockets (focused check a): +// two transactions in parallel must hold DIFFERENT pinned SocketIds +// (GetSocketId()) and must not see each other's rows. +// +// * TransactionPlusPreparedInParallel (focused check b): +// one transaction + one prepared statement in parallel, each returns its +// own correct independent result; the prepared path must not disturb the +// transaction's pinned connection. +// +// The bar: NO concurrency bug across all iterations -- no shared-socket +// corruption, no interleaved/wrong replies, no crash. +// +// HARNESS +// Reuses the gflag-driven, self-spawning-mysqld harness from the two sibling +// integration files (flags -mysql_use_running_server / -mysql_host / -port / +// -user / -password / -schema; MysqlAuthenticator-based pooled Channel). When +// no mysqld is reachable every test GTEST_SKIP()s, so the file is CI-safe. + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "brpc/channel.h" +#include "brpc/controller.h" +#include "brpc/policy/mysql/mysql.h" +#include "brpc/policy/mysql/mysql_transaction.h" +#include "brpc/policy/mysql/mysql_authenticator.h" +#include "bthread/bthread.h" +#include "butil/logging.h" +#include "butil/string_printf.h" +#include "butil/strings/string_piece.h" + +// Flags mirror the sibling integration files so one command line drives them +// all against the same server. Each *_unittest.cpp links into its own binary +// (the test/ CMake glob), so re-declaring these flags here is not a clash. +DEFINE_bool(mysql_use_running_server, false, + "Use an already-running MySQL server instead of spawning a " + "throwaway one; the running server is neither started nor stopped " + "by the test."); +DEFINE_string(mysql_host, "127.0.0.1", + "Host of the running MySQL server " + "(only with -mysql_use_running_server)."); +DEFINE_int32(mysql_port, 13306, + "TCP port of the MySQL server (used for both the running server " + "and the spawned throwaway server)."); +DEFINE_string(mysql_user, "root", "Login user for the concurrency tests."); +DEFINE_string(mysql_password, "", + "Password for -mysql_user (empty for the spawned server)."); +DEFINE_string(mysql_schema, "brpc_pool_conc_test", + "Schema (database) the concurrency tests create and use."); + +namespace { + +#ifndef GFLAGS_NS +#define GFLAGS_NS GFLAGS_NAMESPACE +#endif + +#define MYSQLD_BIN "mysqld" + +static const char* kCollation = "utf8mb4_general_ci"; + +// Concurrency knobs. kWorkers is deliberately > the pool cap (5) so workers +// contend for pooled sockets AND force new-connection creation under load. +const int kWorkers = 24; +const int kIterationsPerWorker = 50; +const int kPoolCap = 5; + +// Throwaway-server harness (mirrors the two sibling integration files, which +// mirror brpc_redis_unittest.cpp). >0: forked pid; -2: external running +// server reachable; -1: no server -> tests skip. +static pthread_once_t g_start_once = PTHREAD_ONCE_INIT; +static pid_t g_mysqld_pid = -1; +static std::string g_host = "127.0.0.1"; +static int g_port = 13306; +static std::string g_user = "root"; +static std::string g_password; +static std::string g_schema; + +static std::string TestDataDir() { + char cwd[1024]; + if (getcwd(cwd, sizeof(cwd)) == NULL) { + return std::string("/tmp/mysql_pool_conc_data_for_test"); + } + return std::string(cwd) + "/mysql_pool_conc_data_for_test"; +} + +static void RemoveMysqlServer() { + if (g_mysqld_pid > 0) { + puts("[Stopping mysqld]"); + char cmd[1280]; + snprintf(cmd, sizeof(cmd), "kill %d", g_mysqld_pid); + CHECK(0 == system(cmd)); + usleep(500000); + snprintf(cmd, sizeof(cmd), "rm -rf '%s'", TestDataDir().c_str()); + CHECK(0 == system(cmd)); + } +} + +// Raw TCP probe for server readiness; returns fd (caller closes) or -1. +static int ProbeConnect() { + int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + return -1; + } + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(static_cast(g_port)); + addr.sin_addr.s_addr = inet_addr(g_host.c_str()); + if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) != 0) { + close(fd); + return -1; + } + return fd; +} + +static void StartServerOnce() { + if (FLAGS_mysql_use_running_server) { + g_host = FLAGS_mysql_host; + g_port = FLAGS_mysql_port; + g_user = FLAGS_mysql_user; + g_password = FLAGS_mysql_password; + g_schema = FLAGS_mysql_schema; + printf("[Using running mysqld at %s:%d as user '%s', schema '%s']\n", + g_host.c_str(), g_port, g_user.c_str(), g_schema.c_str()); + int fd = ProbeConnect(); + if (fd >= 0) { + close(fd); + g_mysqld_pid = -2; + } else { + printf("Cannot reach running mysqld at %s:%d, tests will skip\n", + g_host.c_str(), g_port); + } + return; + } + + if (system("which " MYSQLD_BIN) != 0) { + puts("Fail to find " MYSQLD_BIN ", concurrency tests will be skipped"); + return; + } + g_host = "127.0.0.1"; + g_port = FLAGS_mysql_port; + g_user = "root"; + g_password.clear(); + g_schema = FLAGS_mysql_schema; + const std::string datadir = TestDataDir(); + char cmd[2048]; + snprintf(cmd, sizeof(cmd), "rm -rf '%s' && mkdir -p '%s'", + datadir.c_str(), datadir.c_str()); + if (system(cmd) != 0) { + puts("Fail to create datadir, concurrency tests will be skipped"); + return; + } + snprintf(cmd, sizeof(cmd), + MYSQLD_BIN " --initialize-insecure --datadir='%s'" + " --log-error='%s/init.err'", + datadir.c_str(), datadir.c_str()); + if (system(cmd) != 0) { + puts("Fail to initialize mysqld datadir, tests will be skipped"); + snprintf(cmd, sizeof(cmd), "rm -rf '%s'", datadir.c_str()); + CHECK(0 == system(cmd)); + return; + } + atexit(RemoveMysqlServer); + + g_mysqld_pid = fork(); + if (g_mysqld_pid < 0) { + puts("Fail to fork"); + exit(1); + } else if (g_mysqld_pid == 0) { + puts("[Starting mysqld]"); + char port_arg[32]; + snprintf(port_arg, sizeof(port_arg), "--port=%d", FLAGS_mysql_port); + const std::string datadir_arg = "--datadir=" + datadir; + const std::string socket_arg = "--socket=" + datadir + "/mysqld.sock"; + const std::string pidfile_arg = "--pid-file=" + datadir + "/mysqld.pid"; + const std::string logerr_arg = "--log-error=" + datadir + "/mysqld.err"; + char* const argv[] = { + (char*)MYSQLD_BIN, + (char*)datadir_arg.c_str(), + (char*)port_arg, + (char*)socket_arg.c_str(), + (char*)pidfile_arg.c_str(), + (char*)logerr_arg.c_str(), + (char*)"--mysqlx=OFF", + (char*)"--bind-address=127.0.0.1", + NULL}; + if (execvp(MYSQLD_BIN, argv) < 0) { + puts("Fail to run " MYSQLD_BIN); + exit(1); + } + } + for (int i = 0; i < 300; ++i) { + int fd = ProbeConnect(); + if (fd >= 0) { + close(fd); + return; + } + usleep(100000); + } + puts("mysqld did not become ready, concurrency tests will be skipped"); + g_mysqld_pid = -1; +} + +// Small helpers over the brpc MySQL public API. + +// Plain (no transaction / no statement) query on a fresh pooled connection. +static bool RunPlain(brpc::Channel& channel, const std::string& sql, + brpc::MysqlResponse* resp, std::string* err) { + brpc::MysqlRequest req; + if (!req.Query(sql)) { + if (err) *err = "build query failed: " + sql; + return false; + } + brpc::Controller cntl; + channel.CallMethod(NULL, &cntl, &req, resp, NULL); + if (cntl.Failed()) { + if (err) *err = "rpc failed: " + cntl.ErrorText(); + return false; + } + if (resp->reply_size() < 1) { + if (err) *err = "no reply for: " + sql; + return false; + } + if (resp->reply(0).is_error()) { + const brpc::MysqlReply& r = resp->reply(0); + if (err) { + *err = butil::string_printf("mysql error %u: %.*s (sql=%s)", + r.error().errcode(), (int)r.error().msg().size(), + r.error().msg().data(), sql.c_str()); + } + return false; + } + return true; +} + +// |sql| INSIDE transaction |tx| (its pinned connection). +static bool RunInTx(brpc::Channel& channel, const brpc::MysqlTransaction* tx, + const std::string& sql, brpc::MysqlResponse* resp, + std::string* err) { + brpc::MysqlRequest req(tx); + if (!req.Query(sql)) { + if (err) *err = "build query failed: " + sql; + return false; + } + brpc::Controller cntl; + channel.CallMethod(NULL, &cntl, &req, resp, NULL); + if (cntl.Failed()) { + if (err) *err = "rpc failed: " + cntl.ErrorText(); + return false; + } + if (resp->reply_size() < 1) { + if (err) *err = "no reply for: " + sql; + return false; + } + return true; +} + +// Row count of the first reply when it is a result set, else -1. +static int64_t ResultRowCount(const brpc::MysqlResponse& resp) { + if (resp.reply_size() < 1) { + return -1; + } + const brpc::MysqlReply& r = resp.reply(0); + if (!r.is_resultset()) { + return -1; + } + return static_cast(r.row_count()); +} + +// Coerce an integer-ish field to long long (handles the various widths the +// server may choose for a column / expression). +static bool FieldToLongLong(const brpc::MysqlReply::Field& f, long long* out) { + if (f.is_sbigint()) *out = f.sbigint(); + else if (f.is_bigint()) *out = (long long)f.bigint(); + else if (f.is_sinteger()) *out = f.sinteger(); + else if (f.is_integer()) *out = (long long)f.integer(); + else if (f.is_string()) *out = atoll(f.string().as_string().c_str()); + else return false; + return true; +} + +static int InitPooledChannel(brpc::Channel* channel, + brpc::policy::MysqlAuthenticator** out_auth, + const std::string& schema) { + brpc::policy::MysqlAuthenticator* auth = + new brpc::policy::MysqlAuthenticator(g_user, g_password, schema, "", + kCollation); + *out_auth = auth; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_MYSQL; + options.connection_type = "pooled"; // CONNECTION_TYPE_POOLED + options.auth = auth; + options.timeout_ms = 10000; + options.connect_timeout_ms = 5000; + options.max_retry = 0; + return channel->Init(g_host.c_str(), g_port, &options); +} + +// Fixture: one shared pooled channel, pool capped at FIVE. Per-test scratch +// tables are created by the tests themselves (per-worker, to keep row counts +// exact under concurrency). +class MysqlPoolConcurrencyTest : public testing::Test { +protected: + static bool NoServer() { return g_mysqld_pid == -1; } + + void SetUp() override { + pthread_once(&g_start_once, StartServerOnce); + if (NoServer()) { + GTEST_SKIP() << "no mysqld available; skipping pool-concurrency " + "integration test (set -mysql_use_running_server " + "or install mysqld)"; + } + // Cap the pool at FIVE for the whole test. Verified flag name: + // src/brpc/socket.cpp:99 DEFINE_int32(max_connection_pool_size, ...). + ASSERT_FALSE(GFLAGS_NS::SetCommandLineOption( + "max_connection_pool_size", + std::to_string(kPoolCap).c_str()).empty()) + << "failed to set gflag max_connection_pool_size"; + + // Create the schema over a schema-less channel, then bind to it. + brpc::policy::MysqlAuthenticator* setup_auth = NULL; + ASSERT_EQ(0, InitPooledChannel(&_setup_channel, &setup_auth, "")); + _setup_auth.reset(setup_auth); + brpc::MysqlResponse resp; + std::string err; + ASSERT_TRUE(RunPlain(_setup_channel, + "CREATE DATABASE IF NOT EXISTS " + g_schema, + &resp, &err)) << err; + + brpc::policy::MysqlAuthenticator* auth = NULL; + ASSERT_EQ(0, InitPooledChannel(&_channel, &auth, g_schema)); + _auth.reset(auth); + } + + brpc::Channel _setup_channel; + brpc::Channel _channel; + // Authenticators must outlive the channels that point at them. + std::unique_ptr _setup_auth; + std::unique_ptr _auth; +}; + +// WORK UNITS +// Each work unit is a self-checking re-run of a sibling scenario, with its OWN +// independent expected result so cross-talk/corruption is detectable. Each +// returns true on a correct result; on any failure it fills |err|. +// +// All work units use a PER-WORKER scratch table (passed in) so concurrent +// workers never share rows, keeping row-count assertions exact. + +// WU1 -- txn commit makes rows visible. +// (Transaction-commit-visibility check; uses its own per-worker id 71xxx +// 'aria' so concurrent workers never collide on data.) +static bool WU_TxnCommitVisible(brpc::Channel& ch, const std::string& table, + int iter, std::string* err) { + const int id = 71000 + iter; + const char* name = "aria"; + brpc::MysqlResponse resp; + brpc::MysqlTransactionUniquePtr tx = + brpc::NewMysqlTransaction(ch, brpc::MysqlTransactionOptions()); + if (tx == NULL) { *err = "WU1: NewMysqlTransaction NULL"; return false; } + if (!RunInTx(ch, tx.get(), + butil::string_printf("INSERT INTO %s VALUES (%d, '%s')", + table.c_str(), id, name), + &resp, err)) return false; + if (resp.reply(0).is_error()) { + *err = "WU1 INSERT err: " + resp.reply(0).error().msg().as_string(); + return false; + } + if (!tx->commit()) { *err = "WU1: commit failed"; return false; } + + // A fresh pooled connection must now see exactly our committed row. + if (!RunPlain(ch, butil::string_printf( + "SELECT id, name FROM %s WHERE id=%d", + table.c_str(), id), &resp, err)) return false; + if (ResultRowCount(resp) != 1) { + *err = butil::string_printf("WU1: expected 1 visible row, got %lld", + (long long)ResultRowCount(resp)); + return false; + } + const brpc::MysqlReply::Row& row = resp.reply(0).next(); + long long got_id = 0; + if (!FieldToLongLong(row.field(0), &got_id) || got_id != id) { + *err = "WU1: wrong id read back"; return false; + } + if (row.field(1).string().as_string() != name) { + *err = "WU1: wrong name read back"; return false; + } + return true; +} + +// WU2 -- txn rollback discards the insert. +// (Rollback-discards-insert check; uses per-worker id 82xxx 'cory'.) +static bool WU_TxnRollbackDiscards(brpc::Channel& ch, const std::string& table, + int iter, std::string* err) { + const int id = 82000 + iter; + brpc::MysqlResponse resp; + brpc::MysqlTransactionUniquePtr tx = + brpc::NewMysqlTransaction(ch, brpc::MysqlTransactionOptions()); + if (tx == NULL) { *err = "WU2: NewMysqlTransaction NULL"; return false; } + if (!RunInTx(ch, tx.get(), + butil::string_printf("INSERT INTO %s VALUES (%d, 'cory')", + table.c_str(), id), + &resp, err)) return false; + if (resp.reply(0).is_error()) { + *err = "WU2 INSERT err: " + resp.reply(0).error().msg().as_string(); + return false; + } + if (!tx->rollback()) { *err = "WU2: rollback failed"; return false; } + + // The rolled-back row must be gone on a fresh connection. + if (!RunPlain(ch, butil::string_printf("SELECT id FROM %s WHERE id=%d", + table.c_str(), id), + &resp, err)) return false; + if (ResultRowCount(resp) != 0) { + *err = butil::string_printf( + "WU2: rolled-back insert still visible (%lld rows)", + (long long)ResultRowCount(resp)); + return false; + } + return true; +} + +// WU3 -- a SELECT inside an open txn sees the txn's own uncommitted write. +// (Read-your-own-write check; uses per-worker id 93xxx 'echo'.) +static bool WU_TxnReadsOwnWrite(brpc::Channel& ch, const std::string& table, + int iter, std::string* err) { + const int id = 93000 + iter; + const char* name = "echo"; + brpc::MysqlResponse resp; + brpc::MysqlTransactionUniquePtr tx = + brpc::NewMysqlTransaction(ch, brpc::MysqlTransactionOptions()); + if (tx == NULL) { *err = "WU3: NewMysqlTransaction NULL"; return false; } + if (!RunInTx(ch, tx.get(), + butil::string_printf("INSERT INTO %s VALUES (%d, '%s')", + table.c_str(), id, name), + &resp, err)) return false; + if (resp.reply(0).is_error()) { + *err = "WU3 INSERT err: " + resp.reply(0).error().msg().as_string(); + return false; + } + // Same pinned connection: must see its own uncommitted row. + if (!RunInTx(ch, tx.get(), + butil::string_printf("SELECT name FROM %s WHERE id=%d", + table.c_str(), id), + &resp, err)) return false; + bool ok = (ResultRowCount(resp) == 1) && + resp.reply(0).next().field(0).string().as_string() == name; + // Discard the row so the per-worker table stays empty for the next pick. + tx->rollback(); + if (!ok) { *err = "WU3: txn did not read its own uncommitted write"; } + return ok; +} + +// WU4 -- prepared: bind one INT param, fetch the matching row. +// (Prepared-bind-int check; each worker seeds its OWN (id 64xxx, 'lyra') row +// and binds it back so the result is unique per worker.) +static bool WU_PreparedBindInt(brpc::Channel& ch, const std::string& table, + int iter, std::string* err) { + const int id = 64000 + iter; + const char* name = "lyra"; + brpc::MysqlResponse resp; + // Seed our own row (autocommit) then read it back via a prepared SELECT. + if (!RunPlain(ch, butil::string_printf("INSERT INTO %s VALUES (%d, '%s')", + table.c_str(), id, name), + &resp, err)) return false; + + brpc::MysqlStatementUniquePtr stmt = brpc::NewMysqlStatement( + ch, butil::string_printf("SELECT name FROM %s WHERE id=?", + table.c_str())); + if (stmt == NULL) { *err = "WU4: NewMysqlStatement NULL"; return false; } + if (stmt->param_count() != 1u) { *err = "WU4: param_count != 1"; return false; } + + brpc::MysqlRequest req(stmt.get()); + if (!req.AddParam((int32_t)id)) { *err = "WU4: AddParam failed"; return false; } + brpc::Controller cntl; + ch.CallMethod(NULL, &cntl, &req, &resp, NULL); + if (cntl.Failed()) { *err = "WU4 rpc: " + cntl.ErrorText(); return false; } + if (resp.reply_size() < 1 || !resp.reply(0).is_resultset()) { + *err = "WU4: not a resultset"; return false; + } + if (resp.reply(0).row_count() != 1u) { + *err = "WU4: expected exactly 1 row"; return false; + } + bool ok = resp.reply(0).next().field(0).string().as_string() == name; + // Clean our seeded row so subsequent picks on this table stay exact. + RunPlain(ch, butil::string_printf("DELETE FROM %s WHERE id=%d", + table.c_str(), id), &resp, err); + if (!ok) { *err = "WU4: prepared bind returned wrong/cross-talked name"; } + return ok; +} + +// WU5 -- prepared multi-param arithmetic: SELECT ? + ? with two INT params. +// (Two-param arithmetic check; uses per-iteration operands so each worker +// verifies its OWN sum.) +static bool WU_PreparedArithmetic(brpc::Channel& ch, int worker, int iter, + std::string* err) { + const int32_t a = 1000 + worker; + const int32_t b = 7 + iter; + const long long expect = (long long)a + b; + brpc::MysqlStatementUniquePtr stmt = + brpc::NewMysqlStatement(ch, "SELECT CAST(? AS SIGNED) + CAST(? AS SIGNED)"); + if (stmt == NULL) { *err = "WU5: NewMysqlStatement NULL"; return false; } + if (stmt->param_count() != 2u) { *err = "WU5: param_count != 2"; return false; } + + brpc::MysqlRequest req(stmt.get()); + if (!req.AddParam(a) || !req.AddParam(b)) { + *err = "WU5: AddParam failed"; return false; + } + brpc::MysqlResponse resp; + brpc::Controller cntl; + ch.CallMethod(NULL, &cntl, &req, &resp, NULL); + if (cntl.Failed()) { *err = "WU5 rpc: " + cntl.ErrorText(); return false; } + if (resp.reply_size() < 1 || !resp.reply(0).is_resultset() || + resp.reply(0).row_count() != 1u) { + *err = "WU5: bad resultset"; return false; + } + long long got = 0; + if (!FieldToLongLong(resp.reply(0).next().field(0), &got) || got != expect) { + *err = butil::string_printf("WU5: ?+? wrong (got %lld want %lld)", + got, expect); + return false; + } + return true; +} + +// Worker driver for ManyWorkersMixedScenarios. +struct MixWorkerArgs { + brpc::Channel* channel; + int worker_id; + std::string table; // per-worker scratch table + std::string error; // first failure (empty == all good) + int completed; // iterations completed without error +}; + +void* MixWorker(void* p) { + MixWorkerArgs* a = static_cast(p); + a->error.clear(); + a->completed = 0; + for (int iter = 0; iter < kIterationsPerWorker; ++iter) { + // Rotate through the five work units; offset by worker so different + // workers run different units at the same instant. + const int pick = (a->worker_id + iter) % 5; + std::string err; + bool ok = false; + switch (pick) { + case 0: ok = WU_TxnCommitVisible(*a->channel, a->table, iter, &err); break; + case 1: ok = WU_TxnRollbackDiscards(*a->channel, a->table, iter, &err); break; + case 2: ok = WU_TxnReadsOwnWrite(*a->channel, a->table, iter, &err); break; + case 3: ok = WU_PreparedBindInt(*a->channel, a->table, iter, &err); break; + default: ok = WU_PreparedArithmetic(*a->channel, a->worker_id, iter, &err); break; + } + if (!ok) { + a->error = butil::string_printf("worker %d iter %d pick %d: %s", + a->worker_id, iter, pick, err.c_str()); + return NULL; + } + ++a->completed; + } + return NULL; +} + +// TEST 1: many bthreads, each looping ~50x over a mix of the reused work +// units, on ONE pooled channel capped at 5 sockets. Surfaces checkout/return +// races, affinity-under-contention bugs, fd_version ABA, and the new-connection +// creation path. +TEST_F(MysqlPoolConcurrencyTest, ManyWorkersMixedScenarios) { + std::string err; + std::vector args(kWorkers); + // One scratch table per worker so concurrent workers never share rows. + for (int w = 0; w < kWorkers; ++w) { + args[w].channel = &_channel; + args[w].worker_id = w; + args[w].table = butil::string_printf("pool_conc_w%d", w); + brpc::MysqlResponse resp; + ASSERT_TRUE(RunPlain(_channel, "DROP TABLE IF EXISTS " + args[w].table, + &resp, &err)) << err; + ASSERT_TRUE(RunPlain(_channel, + "CREATE TABLE " + args[w].table + + " (id INT PRIMARY KEY, name VARCHAR(32)) " + "ENGINE=InnoDB", + &resp, &err)) << err; + } + + std::vector threads(kWorkers); + for (int w = 0; w < kWorkers; ++w) { + ASSERT_EQ(0, bthread_start_background(&threads[w], NULL, MixWorker, + &args[w])); + } + for (int w = 0; w < kWorkers; ++w) { + bthread_join(threads[w], NULL); + } + + for (int w = 0; w < kWorkers; ++w) { + EXPECT_TRUE(args[w].error.empty()) << args[w].error; + EXPECT_EQ(kIterationsPerWorker, args[w].completed) + << "worker " << w << " did not finish all iterations"; + } + + // Cleanup. + for (int w = 0; w < kWorkers; ++w) { + brpc::MysqlResponse resp; + RunPlain(_channel, "DROP TABLE IF EXISTS " + args[w].table, &resp, &err); + } +} + +// Focused-check worker A: one transaction, per-worker table, INSERT + SELECT, +// records its pinned SocketId and the value it read. +struct AffinityWorkerArgs { + brpc::Channel* channel; + std::string table; + int id; // value inserted (and expected back) + brpc::SocketId socket_id; // pinned socket for this txn + int64_t row_count; + int read_value; + bool committed; + std::string error; +}; + +void* AffinityWorker(void* p) { + AffinityWorkerArgs* a = static_cast(p); + a->error.clear(); + a->socket_id = 0; + a->row_count = -1; + a->read_value = -1; + a->committed = false; + + brpc::MysqlTransactionUniquePtr tx = + brpc::NewMysqlTransaction(*a->channel, brpc::MysqlTransactionOptions()); + if (tx == NULL) { a->error = "NewMysqlTransaction NULL"; return NULL; } + a->socket_id = tx->GetSocketId(); + + brpc::MysqlResponse resp; + if (!RunInTx(*a->channel, tx.get(), + butil::string_printf("INSERT INTO %s VALUES (%d)", + a->table.c_str(), a->id), + &resp, &a->error)) return NULL; + if (resp.reply(0).is_error()) { + a->error = "INSERT err: " + resp.reply(0).error().msg().as_string(); + return NULL; + } + // Read inside the txn: must see exactly our own row (per-worker table). + if (!RunInTx(*a->channel, tx.get(), + butil::string_printf("SELECT v FROM %s", a->table.c_str()), + &resp, &a->error)) return NULL; + a->row_count = ResultRowCount(resp); + if (a->row_count == 1) { + long long v = 0; + if (FieldToLongLong(resp.reply(0).next().field(0), &v)) { + a->read_value = (int)v; + } + } + a->committed = tx->commit(); + if (!a->committed) a->error = "commit failed"; + return NULL; +} + +// TEST 2 (focused check a): two transactions in parallel must hold DIFFERENT +// pinned SocketIds and must not see each other's rows. +TEST_F(MysqlPoolConcurrencyTest, TwoTransactionsHoldDifferentPinnedSockets) { + const std::string t0 = "pool_conc_affinity_a"; + const std::string t1 = "pool_conc_affinity_b"; + std::string err; + brpc::MysqlResponse resp; + for (const std::string& t : {t0, t1}) { + ASSERT_TRUE(RunPlain(_channel, "DROP TABLE IF EXISTS " + t, &resp, &err)) << err; + ASSERT_TRUE(RunPlain(_channel, + "CREATE TABLE " + t + " (v INT) ENGINE=InnoDB", + &resp, &err)) << err; + } + + for (int iter = 0; iter < kIterationsPerWorker; ++iter) { + ASSERT_TRUE(RunPlain(_channel, "TRUNCATE TABLE " + t0, &resp, &err)) << err; + ASSERT_TRUE(RunPlain(_channel, "TRUNCATE TABLE " + t1, &resp, &err)) << err; + + AffinityWorkerArgs a0{&_channel, t0, 30100 + iter, 0, -1, -1, false, ""}; + AffinityWorkerArgs a1{&_channel, t1, 30900 + iter, 0, -1, -1, false, ""}; + + bthread_t b0, b1; + ASSERT_EQ(0, bthread_start_background(&b0, NULL, AffinityWorker, &a0)); + ASSERT_EQ(0, bthread_start_background(&b1, NULL, AffinityWorker, &a1)); + bthread_join(b0, NULL); + bthread_join(b1, NULL); + + ASSERT_TRUE(a0.error.empty()) << "iter " << iter << " txn0: " << a0.error; + ASSERT_TRUE(a1.error.empty()) << "iter " << iter << " txn1: " << a1.error; + + // Each saw exactly its own single row -- no cross-talk. + EXPECT_EQ(1, a0.row_count) << "iter " << iter; + EXPECT_EQ(1, a1.row_count) << "iter " << iter; + EXPECT_EQ(a0.id, a0.read_value) << "iter " << iter; + EXPECT_EQ(a1.id, a1.read_value) << "iter " << iter; + EXPECT_TRUE(a0.committed) << "iter " << iter; + EXPECT_TRUE(a1.committed) << "iter " << iter; + + // Connection affinity: two concurrent txns hold DIFFERENT pinned sockets. + EXPECT_NE(0u, a0.socket_id) << "iter " << iter; + EXPECT_NE(0u, a1.socket_id) << "iter " << iter; + EXPECT_NE(a0.socket_id, a1.socket_id) + << "iter " << iter + << ": two concurrent transactions shared a pooled socket! sid0=" + << a0.socket_id << " sid1=" << a1.socket_id; + } + + for (const std::string& t : {t0, t1}) { + RunPlain(_channel, "DROP TABLE IF EXISTS " + t, &resp, &err); + } +} + +// Focused-check worker B: a prepared statement (SELECT ? + ?) run repeatedly +// in parallel with a transaction; must return its own correct sum each time. +struct PreparedWorkerArgs { + brpc::Channel* channel; + int base; // operand seed + bool ok; + std::string error; +}; + +void* PreparedWorker(void* p) { + PreparedWorkerArgs* a = static_cast(p); + a->ok = true; + a->error.clear(); + for (int k = 0; k < 4; ++k) { + std::string err; + if (!WU_PreparedArithmetic(*a->channel, a->base, k, &err)) { + a->ok = false; + a->error = err; + return NULL; + } + } + return NULL; +} + +// TEST 3 (focused check b): one transaction + one prepared statement in +// parallel each return correct independent results; the prepared path must not +// disturb the transaction's pinned connection. +TEST_F(MysqlPoolConcurrencyTest, TransactionPlusPreparedInParallel) { + const std::string t = "pool_conc_txn_stmt"; + std::string err; + brpc::MysqlResponse resp; + ASSERT_TRUE(RunPlain(_channel, "DROP TABLE IF EXISTS " + t, &resp, &err)) << err; + ASSERT_TRUE(RunPlain(_channel, "CREATE TABLE " + t + " (v INT) ENGINE=InnoDB", + &resp, &err)) << err; + + for (int iter = 0; iter < kIterationsPerWorker; ++iter) { + ASSERT_TRUE(RunPlain(_channel, "TRUNCATE TABLE " + t, &resp, &err)) << err; + + AffinityWorkerArgs ta{&_channel, t, 50500 + iter, 0, -1, -1, false, ""}; + PreparedWorkerArgs pa{&_channel, 200 + iter, true, ""}; + + bthread_t bt, bp; + ASSERT_EQ(0, bthread_start_background(&bt, NULL, AffinityWorker, &ta)); + ASSERT_EQ(0, bthread_start_background(&bp, NULL, PreparedWorker, &pa)); + bthread_join(bt, NULL); + bthread_join(bp, NULL); + + ASSERT_TRUE(ta.error.empty()) << "iter " << iter << " txn: " << ta.error; + ASSERT_TRUE(pa.ok) << "iter " << iter << " prepared: " << pa.error; + + // Transaction result intact and isolated. + EXPECT_EQ(1, ta.row_count) << "iter " << iter; + EXPECT_EQ(ta.id, ta.read_value) << "iter " << iter; + EXPECT_TRUE(ta.committed) << "iter " << iter; + EXPECT_NE(0u, ta.socket_id) << "iter " << iter; + } + + RunPlain(_channel, "DROP TABLE IF EXISTS " + t, &resp, &err); +} + +// OWNER-PRIORITY CONCURRENCY CHECKS (TEST A / B / C) +// +// These three tests intentionally cap the pool SMALL relative to the number of +// concurrent workers (TEST A/B: 4 sockets vs 8 workers; TEST C: 2 sockets) so +// that workers both CONTEND for pooled sockets and FORCE new-connection +// creation, while transactions RESERVE (pull out) pooled sockets. They use +// their OWN data namespace (ids in the 120xxx/130xxx/140xxx ranges, names +// "nova"/"zephyr"/"quill") that is distinct from the reused work units above +// (71xxx/82xxx/93xxx/64xxx; aria/cory/echo/lyra) and from the sibling files. +// +// Pool size is set PER-TEST via SetCommandLineOption at the top of each test +// body (tests may share a process, so the previous test's value must not leak +// in). No ASSERT_* runs inside a bthread; every worker records into its args +// struct and the main thread asserts after join. + +static void SetPoolCap(int cap) { + GFLAGS_NS::SetCommandLineOption("max_connection_pool_size", + std::to_string(cap).c_str()); +} + +// TEST A worker: one transaction running SEVERAL statements into its OWN +// per-worker scratch table, recording the pinned SocketId seen BEFORE every +// statement so the main thread can check intra-txn pinning and inter-txn +// isolation. +struct PinnedTxnWorkerArgs { + brpc::Channel* channel; + std::string table; // per-worker scratch table + int id; // per-worker row id (unique) + std::vector socket_ids; // GetSocketId() before each stmt + std::string error; // empty == ok +}; + +void* PinnedTxnWorker(void* p) { + PinnedTxnWorkerArgs* a = static_cast(p); + a->error.clear(); + a->socket_ids.clear(); + + brpc::MysqlTransactionUniquePtr tx = + brpc::NewMysqlTransaction(*a->channel, brpc::MysqlTransactionOptions()); + if (tx == NULL) { a->error = "NewMysqlTransaction NULL"; return NULL; } + + brpc::MysqlResponse resp; + + // Statement 1: INSERT our own row. + a->socket_ids.push_back(tx->GetSocketId()); + if (!RunInTx(*a->channel, tx.get(), + butil::string_printf("INSERT INTO %s VALUES (%d, 'nova')", + a->table.c_str(), a->id), + &resp, &a->error)) return NULL; + if (resp.reply(0).is_error()) { + a->error = "INSERT err: " + resp.reply(0).error().msg().as_string(); + return NULL; + } + + // Statement 2: SELECT our own (uncommitted) row back. + a->socket_ids.push_back(tx->GetSocketId()); + if (!RunInTx(*a->channel, tx.get(), + butil::string_printf("SELECT name FROM %s WHERE id=%d", + a->table.c_str(), a->id), + &resp, &a->error)) return NULL; + if (ResultRowCount(resp) != 1 || + resp.reply(0).next().field(0).string().as_string() != "nova") { + a->error = "SELECT-own-row did not read back its own write"; + return NULL; + } + + // Statement 3: UPDATE our own row. + a->socket_ids.push_back(tx->GetSocketId()); + if (!RunInTx(*a->channel, tx.get(), + butil::string_printf("UPDATE %s SET name='zephyr' WHERE id=%d", + a->table.c_str(), a->id), + &resp, &a->error)) return NULL; + if (resp.reply(0).is_error()) { + a->error = "UPDATE err: " + resp.reply(0).error().msg().as_string(); + return NULL; + } + + // Statement 4: SELECT the updated value back. + a->socket_ids.push_back(tx->GetSocketId()); + if (!RunInTx(*a->channel, tx.get(), + butil::string_printf("SELECT name FROM %s WHERE id=%d", + a->table.c_str(), a->id), + &resp, &a->error)) return NULL; + if (ResultRowCount(resp) != 1 || + resp.reply(0).next().field(0).string().as_string() != "zephyr") { + a->error = "SELECT after UPDATE saw wrong value"; + return NULL; + } + + // Discard so the per-worker table is empty for the next outer-loop pass. + if (!tx->rollback()) { a->error = "rollback failed"; return NULL; } + return NULL; +} + +// TEST A: ConcurrentTxnsStayPinned (the most important check) +// +// kTxns overlapping transactions, each on its own bthread, on the POOLED +// channel with the pool capped SMALL (4) relative to kTxns (8) so they contend +// AND force new-connection creation. Each txn runs 4 statements into its OWN +// scratch table and snapshots tx->GetSocketId() before every statement. +// Asserts (on the main thread, after join): +// * INTRA-txn: the SocketId is CONSTANT across a transaction's statements +// (the txn is pinned to one connection); +// * INTER-txn: the live SocketIds are DISTINCT across the concurrent txns +// (no two simultaneously-open transactions share a pooled connection). +// The whole thing loops a few times to shake scheduling. +TEST_F(MysqlPoolConcurrencyTest, ConcurrentTxnsStayPinned) { + const int kTxns = 8; + SetPoolCap(4); // SMALL vs kTxns=8: contend + force new connections. + + std::string err; + brpc::MysqlResponse resp; + std::vector tables(kTxns); + for (int w = 0; w < kTxns; ++w) { + tables[w] = butil::string_printf("pool_conc_pin_w%d", w); + ASSERT_TRUE(RunPlain(_channel, "DROP TABLE IF EXISTS " + tables[w], + &resp, &err)) << err; + ASSERT_TRUE(RunPlain(_channel, + "CREATE TABLE " + tables[w] + + " (id INT PRIMARY KEY, name VARCHAR(32)) " + "ENGINE=InnoDB", + &resp, &err)) << err; + } + + for (int loop = 0; loop < 10; ++loop) { + std::vector args(kTxns); + for (int w = 0; w < kTxns; ++w) { + args[w].channel = &_channel; + args[w].table = tables[w]; + // Unique per worker AND per loop so rows never collide. + args[w].id = 120000 + loop * 100 + w; + } + + std::vector threads(kTxns); + for (int w = 0; w < kTxns; ++w) { + ASSERT_EQ(0, bthread_start_background(&threads[w], NULL, + PinnedTxnWorker, &args[w])); + } + for (int w = 0; w < kTxns; ++w) { + bthread_join(threads[w], NULL); + } + + // No worker errored. + for (int w = 0; w < kTxns; ++w) { + ASSERT_TRUE(args[w].error.empty()) + << "loop " << loop << " txn " << w << ": " << args[w].error; + ASSERT_EQ(4u, args[w].socket_ids.size()) << "loop " << loop; + } + + // INTRA-txn pinning: one constant non-zero SocketId per transaction. + std::vector live(kTxns); + for (int w = 0; w < kTxns; ++w) { + const brpc::SocketId sid = args[w].socket_ids[0]; + EXPECT_NE(0u, sid) << "loop " << loop << " txn " << w; + for (size_t s = 1; s < args[w].socket_ids.size(); ++s) { + EXPECT_EQ(sid, args[w].socket_ids[s]) + << "loop " << loop << " txn " << w << " stmt " << s + << ": transaction was NOT pinned to one connection"; + } + live[w] = sid; + } + + // INTER-txn isolation: all live (simultaneously-open) SocketIds distinct. + for (int i = 0; i < kTxns; ++i) { + for (int j = i + 1; j < kTxns; ++j) { + EXPECT_NE(live[i], live[j]) + << "loop " << loop << ": txns " << i << " and " << j + << " shared a pooled connection (sid=" << live[i] << ")"; + } + } + } + + for (int w = 0; w < kTxns; ++w) { + RunPlain(_channel, "DROP TABLE IF EXISTS " + tables[w], &resp, &err); + } +} + +// TEST B workers: an aborting-transaction mix. Mode 0 explicitly rollback()s +// after an INSERT; mode 1 simply drops the MysqlTransactionUniquePtr WITHOUT +// commit, so its destructor auto-rollbacks (see MysqlTransaction::~ in +// mysql_transaction.h). Either way the insert must NOT survive. +struct AbortWorkerArgs { + brpc::Channel* channel; + std::string table; // shared abort table + int id; // unique id this worker inserts then aborts + int mode; // 0 == explicit rollback, 1 == drop -> dtor rollback + std::string error; +}; + +void* AbortWorker(void* p) { + AbortWorkerArgs* a = static_cast(p); + a->error.clear(); + { + brpc::MysqlTransactionUniquePtr tx = brpc::NewMysqlTransaction( + *a->channel, brpc::MysqlTransactionOptions()); + if (tx == NULL) { a->error = "NewMysqlTransaction NULL"; return NULL; } + + brpc::MysqlResponse resp; + if (!RunInTx(*a->channel, tx.get(), + butil::string_printf("INSERT INTO %s VALUES (%d, 'quill')", + a->table.c_str(), a->id), + &resp, &a->error)) return NULL; + if (resp.reply(0).is_error()) { + a->error = "INSERT err: " + resp.reply(0).error().msg().as_string(); + return NULL; + } + if (a->mode == 0) { + if (!tx->rollback()) { a->error = "explicit rollback failed"; return NULL; } + } + // mode 1: fall off the end of this scope -> tx dtor auto-rollbacks. + } + return NULL; +} + +// TEST B: ConcurrentTxnAbortAndAutoRollback +// +// Under the same small-pool contended setup, run a concurrent MIX of +// explicitly-rolled-back transactions and dropped-without-commit transactions +// (whose dtor auto-rollbacks). Exercises the reserve -> return / auto-rollback +// path under contention (the UAF/leak-prone path). After join, from a fresh +// non-tx connection, assert NONE of the aborted inserts are visible, workers +// saw no errors, and the channel is still healthy (a final pooled SELECT +// succeeds -> reserved connections were returned to the pool cleanly). +TEST_F(MysqlPoolConcurrencyTest, ConcurrentTxnAbortAndAutoRollback) { + const int kWorkersB = 8; + SetPoolCap(4); // SMALL vs 8 workers: contend + force new connections. + + const std::string t = "pool_conc_abort"; + std::string err; + brpc::MysqlResponse resp; + ASSERT_TRUE(RunPlain(_channel, "DROP TABLE IF EXISTS " + t, &resp, &err)) << err; + ASSERT_TRUE(RunPlain(_channel, + "CREATE TABLE " + t + + " (id INT PRIMARY KEY, name VARCHAR(32)) " + "ENGINE=InnoDB", + &resp, &err)) << err; + + for (int loop = 0; loop < 6; ++loop) { + std::vector args(kWorkersB); + for (int w = 0; w < kWorkersB; ++w) { + args[w].channel = &_channel; + args[w].table = t; + args[w].id = 130000 + loop * 100 + w; // unique per loop+worker + args[w].mode = w % 2; // half explicit rollback, half dtor rollback + } + + std::vector threads(kWorkersB); + for (int w = 0; w < kWorkersB; ++w) { + ASSERT_EQ(0, bthread_start_background(&threads[w], NULL, + AbortWorker, &args[w])); + } + for (int w = 0; w < kWorkersB; ++w) { + bthread_join(threads[w], NULL); + } + + for (int w = 0; w < kWorkersB; ++w) { + EXPECT_TRUE(args[w].error.empty()) + << "loop " << loop << " worker " << w << " (mode " + << args[w].mode << "): " << args[w].error; + } + + // Effects discarded: not a single aborted insert is visible from a + // fresh (non-tx) pooled connection. + ASSERT_TRUE(RunPlain(_channel, + "SELECT COUNT(*) FROM " + t, &resp, &err)) << err; + long long visible = -1; + ASSERT_EQ(1, ResultRowCount(resp)) << "loop " << loop; + ASSERT_TRUE(FieldToLongLong(resp.reply(0).next().field(0), &visible)); + EXPECT_EQ(0, visible) + << "loop " << loop << ": " << visible + << " aborted/dropped insert(s) leaked into the table"; + } + + // Channel still healthy: a final simple SELECT on a fresh pooled request + // succeeds -> the reserved connections were returned to the pool cleanly. + ASSERT_TRUE(RunPlain(_channel, "SELECT 1", &resp, &err)) << err; + EXPECT_EQ(1, ResultRowCount(resp)); + + RunPlain(_channel, "DROP TABLE IF EXISTS " + t, &resp, &err); +} + +// TEST C support: a transaction that RESERVES a pooled connection (pulls it out +// of the pool) and holds it for the lifetime of the worker, so that the +// prepared statement S is forced onto connections that may not have its +// server-side stmt_id cached. +struct ReserveWorkerArgs { + brpc::Channel* channel; + std::string error; +}; + +void* ReserveWorker(void* p) { + ReserveWorkerArgs* a = static_cast(p); + a->error.clear(); + // Open a few short transactions in series; each pulls a pooled connection + // out (reserve) and returns it (rollback), churning which sockets are in + // the pool while S is being executed concurrently. + for (int k = 0; k < 8; ++k) { + brpc::MysqlTransactionUniquePtr tx = brpc::NewMysqlTransaction( + *a->channel, brpc::MysqlTransactionOptions()); + if (tx == NULL) { a->error = "reserve: NewMysqlTransaction NULL"; return NULL; } + brpc::MysqlResponse resp; + if (!RunInTx(*a->channel, tx.get(), "SELECT 1", &resp, &a->error)) return NULL; + if (!tx->rollback()) { a->error = "reserve: rollback failed"; return NULL; } + } + return NULL; +} + +// Execute a shared prepared statement S with a fresh INT param and verify the +// bound value comes back correctly. S is shared across bthreads, so it lands +// on whatever pooled connection is currently free. +struct StmtExecWorkerArgs { + brpc::Channel* channel; + brpc::MysqlStatement* stmt; // shared prepared statement S + int base; // param seed (unique per worker) + std::string error; +}; + +void* StmtExecWorker(void* p) { + StmtExecWorkerArgs* a = static_cast(p); + a->error.clear(); + for (int k = 0; k < 12; ++k) { + const int32_t v = a->base + k; + brpc::MysqlRequest req(a->stmt); + if (!req.AddParam(v)) { a->error = "AddParam failed"; return NULL; } + brpc::MysqlResponse resp; + brpc::Controller cntl; + a->channel->CallMethod(NULL, &cntl, &req, &resp, NULL); + if (cntl.Failed()) { a->error = "rpc: " + cntl.ErrorText(); return NULL; } + if (resp.reply_size() < 1 || !resp.reply(0).is_resultset() || + resp.reply(0).row_count() != 1u) { + a->error = "bad resultset for S"; return NULL; + } + long long got = 0; + if (!FieldToLongLong(resp.reply(0).next().field(0), &got) || got != v) { + a->error = butil::string_printf( + "S returned wrong value (got %lld want %d)", got, v); + return NULL; + } + } + return NULL; +} + +// TEST C: PreparedRePreparesWhenConnectionStolen +// +// MECHANISM: a server-side prepared statement id is per-(SocketId, fd_version) +// -- it is meaningful only on the exact connection that ran COM_STMT_PREPARE +// (see MysqlStatement::StatementId(SocketId)/SetStatementId(SocketId,...) in +// mysql_statement.h, and the fd_version/SocketId ABA discussion in this file's +// header). When a shared MysqlStatement S lands on a pooled connection that +// does NOT have S's stmt_id cached, brpc must transparently issue a fresh +// COM_STMT_PREPARE on that connection before COM_STMT_EXECUTE, and the bound +// result must still be correct. +// +// SETUP: pool capped at 2. S = "SELECT CAST(? AS SIGNED) AS v" is created and +// executed once (caching its stmt_id on whatever connection it first landed +// on). Then background bthreads open transactions that RESERVE the two pooled +// connections (pulling S's original connection out), while other bthreads keep +// executing S with fresh params. Looped with churn so S repeatedly hits +// connections it was not prepared on. Every execute must still return the +// correct bound value; per-worker errors are recorded and asserted empty after +// join. +TEST_F(MysqlPoolConcurrencyTest, PreparedRePreparesWhenConnectionStolen) { + SetPoolCap(2); // tiny pool: 2 sockets, easy to "steal" via reserving txns. + + std::string err; + brpc::MysqlResponse resp; + + brpc::MysqlStatementUniquePtr S = + brpc::NewMysqlStatement(_channel, "SELECT CAST(? AS SIGNED) AS v"); + ASSERT_TRUE(S != NULL); + ASSERT_EQ(1u, S->param_count()); + + // Execute S once to cache its stmt_id on whatever connection it lands on. + { + brpc::MysqlRequest req(S.get()); + ASSERT_TRUE(req.AddParam((int32_t)140000)); + brpc::Controller cntl; + _channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_TRUE(resp.reply_size() >= 1 && resp.reply(0).is_resultset()); + long long got = 0; + ASSERT_TRUE(FieldToLongLong(resp.reply(0).next().field(0), &got)); + ASSERT_EQ(140000, got); + } + + const int kExecutors = 4; + const int kReservers = 3; + for (int loop = 0; loop < 30; ++loop) { + std::vector exec_args(kExecutors); + std::vector res_args(kReservers); + std::vector exec_threads(kExecutors); + std::vector res_threads(kReservers); + + for (int w = 0; w < kReservers; ++w) { + res_args[w].channel = &_channel; + ASSERT_EQ(0, bthread_start_background(&res_threads[w], NULL, + ReserveWorker, &res_args[w])); + } + for (int w = 0; w < kExecutors; ++w) { + exec_args[w].channel = &_channel; + exec_args[w].stmt = S.get(); + // Unique param ranges per worker+loop so a wrong value is unambiguous. + exec_args[w].base = 141000 + loop * 1000 + w * 100; + ASSERT_EQ(0, bthread_start_background(&exec_threads[w], NULL, + StmtExecWorker, &exec_args[w])); + } + for (int w = 0; w < kReservers; ++w) { + bthread_join(res_threads[w], NULL); + } + for (int w = 0; w < kExecutors; ++w) { + bthread_join(exec_threads[w], NULL); + } + + for (int w = 0; w < kReservers; ++w) { + EXPECT_TRUE(res_args[w].error.empty()) + << "loop " << loop << " reserver " << w << ": " << res_args[w].error; + } + for (int w = 0; w < kExecutors; ++w) { + EXPECT_TRUE(exec_args[w].error.empty()) + << "loop " << loop << " executor " << w << ": " << exec_args[w].error; + } + } +} + +} // namespace diff --git a/test/brpc_mysql_prepared_integration_unittest.cpp b/test/brpc_mysql_prepared_integration_unittest.cpp new file mode 100644 index 0000000..e14aea0 --- /dev/null +++ b/test/brpc_mysql_prepared_integration_unittest.cpp @@ -0,0 +1,742 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Integration tests for the brpc MySQL client PREPARED-STATEMENT path, +// exercised end to end against a real mysqld through brpc's PUBLIC API +// (brpc::Channel + brpc::NewMysqlStatement + brpc::MysqlRequest / +// brpc::MysqlResponse). This complements the low-level wire tests in +// brpc_mysql_auth_handshake_unittest.cpp, which speak the protocol over a +// raw socket; here we drive the actual client stack a user would use. +// +// Each fat test chains several prepared-statement behaviors (param counting, +// binding, typed fetch, re-execution, NULL handling, error paths) so the +// test boundaries reflect our own grouping of the client surface. +// +// HARNESS: Reuses the self-spawned / already-running mysqld pattern +// documented in test/README_mysql_auth.md and implemented in +// brpc_mysql_auth_handshake_unittest.cpp. When -mysql_use_running_server +// is set the tests connect to a server the caller started (neither started +// nor stopped here); otherwise the fixture spawns a throwaway mysqld with +// an empty-password root. Every test GTEST_SKIP()s when no mysqld is +// reachable, so the suite is safe to run in environments without MySQL. + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include "brpc/policy/mysql/mysql.h" +#include "brpc/policy/mysql/mysql_authenticator.h" +#include "butil/logging.h" + +// These flags intentionally mirror the names used by +// brpc_mysql_auth_handshake_unittest.cpp so a single command line can drive +// both suites against the same running server. gflags forbids registering +// the same flag twice in one binary, but each *_unittest.cpp is linked into +// its own executable (one gtest_main per glob entry), so there is no clash. +DEFINE_bool(mysql_use_running_server, false, + "Use an already-running MySQL server instead of spawning a " + "throwaway one; the running server is neither started nor " + "stopped by the test."); +DEFINE_string(mysql_host, "127.0.0.1", + "Host of the running MySQL server " + "(only with -mysql_use_running_server)."); +DEFINE_int32(mysql_port, 13306, + "TCP port of the MySQL server (used for both the running " + "server and the spawned throwaway server)."); +DEFINE_string(mysql_user, "root", + "User for the prepared-statement tests against a running " + "server."); +DEFINE_string(mysql_password, "", + "Password for -mysql_user (empty for the spawned server)."); +DEFINE_string(mysql_schema, "brpc_ps_test", + "Schema/database the tests prepare statements against."); + +namespace { + +#define MYSQLD_BIN "mysqld" + +// The schema the integration tests operate in. On a spawned server we +// create it (and a seed table) ourselves over the unix socket; against a +// running server the caller must have granted -mysql_user access to it. +static const char* kCollation = "utf8mb4_general_ci"; + +static pthread_once_t g_start_once = PTHREAD_ONCE_INIT; +// >0 : we forked a throwaway mysqld with this pid. +// -2 : an already-running server is reachable. +// -1 : no server available; tests skip. +static pid_t g_mysqld_pid = -1; + +static std::string g_host = "127.0.0.1"; +static int g_port = 13306; +static std::string g_user = "root"; +static std::string g_password; +static std::string g_schema; +// True once the seed schema/table is known to exist (created on spawn, or +// created best-effort against a running server via the channel itself). +static bool g_schema_ready = false; + +static std::string TestDataDir() { + char cwd[1024]; + if (getcwd(cwd, sizeof(cwd)) == NULL) { + return std::string("/tmp/mysql_ps_data_for_test"); + } + return std::string(cwd) + "/mysql_ps_data_for_test"; +} + +static void RemoveMysqlServer() { + if (g_mysqld_pid > 0) { + puts("[Stopping mysqld]"); + char cmd[1280]; + snprintf(cmd, sizeof(cmd), "kill %d", g_mysqld_pid); + CHECK(0 == system(cmd)); + usleep(500000); + snprintf(cmd, sizeof(cmd), "rm -rf '%s'", TestDataDir().c_str()); + CHECK(0 == system(cmd)); + } +} + +// Opens a raw TCP connection to g_host:g_port purely as a readiness probe; +// returns the fd or -1. (The tests themselves talk through brpc, not this.) +static int ProbeConnect() { + int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + return -1; + } + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(static_cast(g_port)); + addr.sin_addr.s_addr = inet_addr(g_host.c_str()); + if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) != 0) { + close(fd); + return -1; + } + return fd; +} + +static void StartServerOnce() { + if (FLAGS_mysql_use_running_server) { + g_host = FLAGS_mysql_host; + g_port = FLAGS_mysql_port; + g_user = FLAGS_mysql_user; + g_password = FLAGS_mysql_password; + g_schema = FLAGS_mysql_schema; + printf("[Using running mysqld at %s:%d as user '%s', schema '%s']\n", + g_host.c_str(), g_port, g_user.c_str(), g_schema.c_str()); + int fd = ProbeConnect(); + if (fd >= 0) { + close(fd); + g_mysqld_pid = -2; + // We create the seed schema/table lazily through the channel in + // the fixture (SetUp) so it works even without the mysql CLI. + } else { + printf("Cannot reach running mysqld at %s:%d, tests will skip\n", + g_host.c_str(), g_port); + } + return; + } + + if (system("which " MYSQLD_BIN) != 0) { + puts("Fail to find " MYSQLD_BIN ", tests will be skipped"); + return; + } + g_host = "127.0.0.1"; + g_port = FLAGS_mysql_port; + g_user = "root"; + g_password.clear(); + g_schema = FLAGS_mysql_schema; + const std::string datadir = TestDataDir(); + char cmd[2048]; + snprintf(cmd, sizeof(cmd), "rm -rf '%s' && mkdir -p '%s'", + datadir.c_str(), datadir.c_str()); + if (system(cmd) != 0) { + puts("Fail to create datadir, tests will be skipped"); + return; + } + snprintf(cmd, sizeof(cmd), + MYSQLD_BIN " --initialize-insecure --datadir='%s'" + " --log-error='%s/init.err'", + datadir.c_str(), datadir.c_str()); + if (system(cmd) != 0) { + puts("Fail to initialize mysqld datadir, tests will be skipped"); + snprintf(cmd, sizeof(cmd), "rm -rf '%s'", datadir.c_str()); + CHECK(0 == system(cmd)); + return; + } + atexit(RemoveMysqlServer); + + g_mysqld_pid = fork(); + if (g_mysqld_pid < 0) { + puts("Fail to fork"); + exit(1); + } else if (g_mysqld_pid == 0) { + puts("[Starting mysqld]"); + char port_arg[32]; + snprintf(port_arg, sizeof(port_arg), "--port=%d", FLAGS_mysql_port); + const std::string datadir_arg = "--datadir=" + datadir; + const std::string socket_arg = "--socket=" + datadir + "/mysqld.sock"; + const std::string pidfile_arg = "--pid-file=" + datadir + "/mysqld.pid"; + const std::string logerr_arg = "--log-error=" + datadir + "/mysqld.err"; + char* const argv[] = {(char*)MYSQLD_BIN, + (char*)datadir_arg.c_str(), + (char*)port_arg, + (char*)socket_arg.c_str(), + (char*)pidfile_arg.c_str(), + (char*)logerr_arg.c_str(), + (char*)"--mysqlx=OFF", + (char*)"--bind-address=127.0.0.1", + NULL}; + if (execvp(MYSQLD_BIN, argv) < 0) { + puts("Fail to run " MYSQLD_BIN); + exit(1); + } + } + for (int i = 0; i < 300; ++i) { + int fd = ProbeConnect(); + if (fd >= 0) { + close(fd); + // Create the seed schema + table over the unix socket (root has + // an empty password there). Best-effort: if the mysql CLI is + // missing we fall back to creating it through the channel in + // SetUp (DDL also works over the prepared-statement channel via + // a plain Query reply, but the CLI keeps the fixture simple). + char create[2048]; + snprintf(create, sizeof(create), + "mysql --socket='%s/mysqld.sock' -u root -e \"" + "CREATE DATABASE IF NOT EXISTS %s; \" 2>/dev/null", + datadir.c_str(), g_schema.c_str()); + (void)system(create); // schema creation is also retried lazily + return; + } + usleep(100000); + } + puts("mysqld did not become ready, tests will be skipped"); + g_mysqld_pid = -1; +} + +// Builds a Channel configured for the prepared-statement protocol against +// the active server/schema. Returns 0 on success. +static int InitChannel(brpc::Channel* channel) { + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_MYSQL; + options.connection_type = "pooled"; + options.timeout_ms = 5000; + options.connect_timeout_ms = 5000; + options.max_retry = 0; + options.auth = new brpc::policy::MysqlAuthenticator( + g_user, g_password, g_schema, "", kCollation); + return channel->Init(g_host.c_str(), g_port, &options); +} + +// Runs a single plain-text statement (DDL/DML) through |channel| and returns +// true when the server answered without an error reply. Used by the fixture +// to set up seed tables; not itself one of the prepared-statement scenarios. +static bool RunPlainQuery(brpc::Channel* channel, const std::string& sql) { + brpc::MysqlRequest request; + if (!request.Query(sql)) { + return false; + } + brpc::MysqlResponse response; + brpc::Controller cntl; + channel->CallMethod(NULL, &cntl, &request, &response, NULL); + if (cntl.Failed()) { + return false; + } + if (response.reply_size() < 1) { + return false; + } + return !response.reply(0).is_error(); +} + +class MysqlPreparedTest : public testing::Test { +protected: + void SetUp() override { + pthread_once(&g_start_once, StartServerOnce); + if (NoServer()) { + return; + } + // Ensure the schema + the shared seed table exist exactly once. + // (Idempotent CREATE IF NOT EXISTS, so re-running is harmless.) + if (!g_schema_ready) { + brpc::Channel setup; + // Connect with an empty schema first so CREATE DATABASE works + // even if g_schema does not yet exist on a running server. + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_MYSQL; + options.connection_type = "pooled"; + options.timeout_ms = 5000; + options.connect_timeout_ms = 5000; + options.auth = new brpc::policy::MysqlAuthenticator( + g_user, g_password, "", "", kCollation); + if (setup.Init(g_host.c_str(), g_port, &options) == 0) { + RunPlainQuery(&setup, "CREATE DATABASE IF NOT EXISTS " + g_schema); + } + // Now (re)connect bound to the schema and create the seed table. + brpc::Channel ch; + if (InitChannel(&ch) == 0) { + RunPlainQuery(&ch, "DROP TABLE IF EXISTS ps_people"); + RunPlainQuery(&ch, + "CREATE TABLE ps_people(" + "id INT, name VARCHAR(50), score BIGINT)"); + RunPlainQuery(&ch, + "INSERT INTO ps_people VALUES" + "(417,'maple',9100),(528,'cobalt',9200),(639,NULL,9300)"); + g_schema_ready = true; + } + } + ASSERT_EQ(0, InitChannel(&channel_)) << "channel init failed"; + } + + static bool NoServer() { return g_mysqld_pid == -1; } + + brpc::Channel channel_; +}; + +#define SKIP_IF_NO_SERVER() \ + do { \ + if (NoServer()) { \ + GTEST_SKIP() << "no mysqld available"; \ + } \ + } while (0) + +// Convenience: prepare |sql| against channel_, asserting success and +// returning the statement. Returns nullptr on failure (caller asserts). +#define PREPARE_OR_FAIL(var, sql) \ + auto var = brpc::NewMysqlStatement(channel_, (sql)); \ + ASSERT_TRUE((var) != nullptr) << "prepare failed for: " << (sql) + +// Parameter counting across statement shapes, plus executing a no-parameter +// SELECT that returns the full seed result set. +TEST_F(MysqlPreparedTest, ParamCountsAndNoParamSelect) { + SKIP_IF_NO_SERVER(); + + // param_count must reflect the placeholders in each shape. + { + PREPARE_OR_FAIL(s, "INSERT INTO ps_people VALUES(?, ?, ?)"); + EXPECT_EQ(3u, s->param_count()); + } + { + PREPARE_OR_FAIL(s, "SELECT * FROM ps_people WHERE id=? AND name=?"); + EXPECT_EQ(2u, s->param_count()); + } + { + PREPARE_OR_FAIL(s, "DELETE FROM ps_people WHERE id=417"); + EXPECT_EQ(0u, s->param_count()); + } + { + PREPARE_OR_FAIL(s, "DELETE FROM ps_people WHERE id=?"); + EXPECT_EQ(1u, s->param_count()); + } + + // A no-parameter SELECT returns a result set covering all three seed rows. + PREPARE_OR_FAIL(s, "SELECT id, name, score FROM ps_people ORDER BY id"); + EXPECT_EQ(0u, s->param_count()); + brpc::MysqlRequest request(s.get()); + brpc::MysqlResponse response; + brpc::Controller cntl; + channel_.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_GE(response.reply_size(), 1u); + const brpc::MysqlReply& r = response.reply(0); + ASSERT_TRUE(r.is_resultset()) << "expected a result set, got: " << r; + EXPECT_EQ(3u, r.column_count()); + EXPECT_EQ(3u, r.row_count()); +} + +// Bind and execute, all parameter flavors in one place: a single INT bind, a +// single STRING bind, and a two-INT arithmetic expression each return their +// own correct result. +TEST_F(MysqlPreparedTest, BindAndExecuteIntStringAndArithmetic) { + SKIP_IF_NO_SERVER(); + + // (a) bind one INT param -> matching row's name. + { + PREPARE_OR_FAIL(s, "SELECT name FROM ps_people WHERE id=?"); + ASSERT_EQ(1u, s->param_count()); + brpc::MysqlRequest request(s.get()); + ASSERT_TRUE(request.AddParam((int32_t)417)); + brpc::MysqlResponse response; + brpc::Controller cntl; + channel_.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_GE(response.reply_size(), 1u); + const brpc::MysqlReply& r = response.reply(0); + ASSERT_TRUE(r.is_resultset()) << r; + ASSERT_EQ(1u, r.row_count()); + const brpc::MysqlReply::Field& f = r.next().field(0); + ASSERT_TRUE(f.is_string()); + EXPECT_EQ("maple", f.string().as_string()); + } + + // (b) bind one STRING param -> matching row's id. + { + PREPARE_OR_FAIL(s, "SELECT id FROM ps_people WHERE name=?"); + ASSERT_EQ(1u, s->param_count()); + brpc::MysqlRequest request(s.get()); + ASSERT_TRUE(request.AddParam(butil::StringPiece("cobalt"))); + brpc::MysqlResponse response; + brpc::Controller cntl; + channel_.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_GE(response.reply_size(), 1u); + const brpc::MysqlReply& r = response.reply(0); + ASSERT_TRUE(r.is_resultset()) << r; + ASSERT_EQ(1u, r.row_count()); + const brpc::MysqlReply::Field& f = r.next().field(0); + // id is INT; brpc surfaces a signed INT column as sinteger. + ASSERT_TRUE(f.is_sinteger() || f.is_integer()) + << "expected an integer id field"; + if (f.is_sinteger()) { + EXPECT_EQ(528, f.sinteger()); + } else { + EXPECT_EQ(528u, f.integer()); + } + } + + // (c) two INT params in an arithmetic expression -> their sum. + { + PREPARE_OR_FAIL(s, "SELECT CAST(? AS SIGNED) + CAST(? AS SIGNED)"); + ASSERT_EQ(2u, s->param_count()); + brpc::MysqlRequest request(s.get()); + ASSERT_TRUE(request.AddParam((int32_t)315)); + ASSERT_TRUE(request.AddParam((int32_t)28)); + brpc::MysqlResponse response; + brpc::Controller cntl; + channel_.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_GE(response.reply_size(), 1u); + const brpc::MysqlReply& r = response.reply(0); + ASSERT_TRUE(r.is_resultset()) << r; + ASSERT_EQ(1u, r.row_count()); + const brpc::MysqlReply::Field& f = r.next().field(0); + // The sum comes back as a (possibly wide) integer; accept any width. + ASSERT_FALSE(f.is_nil()); + long long got = 0; + if (f.is_sbigint()) got = f.sbigint(); + else if (f.is_bigint()) got = (long long)f.bigint(); + else if (f.is_sinteger()) got = f.sinteger(); + else if (f.is_integer()) got = f.integer(); + else if (f.is_string()) got = atoll(f.string().as_string().c_str()); + else FAIL() << "unexpected field type for ?+?"; + EXPECT_EQ(343, got); + } +} + +// Re-execute one statement with new parameters, and fetch every column type +// (INT, VARCHAR, BIGINT) of a single matched row through its typed accessor. +TEST_F(MysqlPreparedTest, ReExecuteAndTypedColumnFetch) { + SKIP_IF_NO_SERVER(); + + // Re-execute the SAME statement twice with different bound ids. + { + PREPARE_OR_FAIL(s, "SELECT name FROM ps_people WHERE id=?"); + struct Case { int32_t id; const char* name; }; + const Case cases[] = {{417, "maple"}, {528, "cobalt"}}; + for (const Case& c : cases) { + SCOPED_TRACE(c.id); + brpc::MysqlRequest request(s.get()); + ASSERT_TRUE(request.AddParam(c.id)); + brpc::MysqlResponse response; + brpc::Controller cntl; + channel_.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_GE(response.reply_size(), 1u); + const brpc::MysqlReply& r = response.reply(0); + ASSERT_TRUE(r.is_resultset()) << r; + ASSERT_EQ(1u, r.row_count()); + const brpc::MysqlReply::Field& f = r.next().field(0); + ASSERT_TRUE(f.is_string()); + EXPECT_EQ(c.name, f.string().as_string()); + } + } + + // Typed fetch: read INT id, VARCHAR name and BIGINT score off one row. + { + PREPARE_OR_FAIL(s, "SELECT id, name, score FROM ps_people WHERE id=?"); + brpc::MysqlRequest request(s.get()); + ASSERT_TRUE(request.AddParam((int32_t)528)); + brpc::MysqlResponse response; + brpc::Controller cntl; + channel_.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_GE(response.reply_size(), 1u); + const brpc::MysqlReply& r = response.reply(0); + ASSERT_TRUE(r.is_resultset()) << r; + ASSERT_EQ(3u, r.column_count()); + ASSERT_EQ(1u, r.row_count()); + const brpc::MysqlReply::Row& row = r.next(); + + // Column 0: INT id == 528. + const brpc::MysqlReply::Field& id = row.field(0); + ASSERT_TRUE(id.is_sinteger() || id.is_integer()); + EXPECT_EQ(528, id.is_sinteger() ? id.sinteger() : (int)id.integer()); + + // Column 1: VARCHAR name == "cobalt". + const brpc::MysqlReply::Field& name = row.field(1); + ASSERT_TRUE(name.is_string()); + EXPECT_EQ("cobalt", name.string().as_string()); + + // Column 2: BIGINT score == 9200. + const brpc::MysqlReply::Field& score = row.field(2); + ASSERT_TRUE(score.is_sbigint() || score.is_bigint() || + score.is_sinteger() || score.is_integer()) + << "expected an integer score field"; + long long sc = 0; + if (score.is_sbigint()) sc = score.sbigint(); + else if (score.is_bigint()) sc = (long long)score.bigint(); + else if (score.is_sinteger()) sc = score.sinteger(); + else sc = score.integer(); + EXPECT_EQ(9200, sc); + } +} + +// NULL handling both ways: a column whose value is SQL NULL (the seed row with +// a NULL name) and a literal NULL in the SELECT list both surface as nil. +TEST_F(MysqlPreparedTest, NullColumnAndLiteralNullAreNil) { + SKIP_IF_NO_SERVER(); + + // A row with a NULL name column surfaces field(0) as nil. + { + PREPARE_OR_FAIL(s, "SELECT name FROM ps_people WHERE id=?"); + brpc::MysqlRequest request(s.get()); + ASSERT_TRUE(request.AddParam((int32_t)639)); + brpc::MysqlResponse response; + brpc::Controller cntl; + channel_.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_GE(response.reply_size(), 1u); + const brpc::MysqlReply& r = response.reply(0); + ASSERT_TRUE(r.is_resultset()) << r; + ASSERT_EQ(1u, r.row_count()); + EXPECT_TRUE(r.next().field(0).is_nil()); + } + + // A literal NULL in the SELECT list also comes back nil. + { + PREPARE_OR_FAIL(s, "SELECT NULL"); + EXPECT_EQ(0u, s->param_count()); + brpc::MysqlRequest request(s.get()); + brpc::MysqlResponse response; + brpc::Controller cntl; + channel_.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_GE(response.reply_size(), 1u); + const brpc::MysqlReply& r = response.reply(0); + ASSERT_TRUE(r.is_resultset()) << r; + ASSERT_EQ(1u, r.row_count()); + EXPECT_TRUE(r.next().field(0).is_nil()); + } +} + +// Error paths must not crash the client: a malformed statement and a +// parameter-count mismatch each surface either a failed RPC or an error reply, +// never a silent success or a crash. +TEST_F(MysqlPreparedTest, MalformedAndParamMismatchSurfaceErrors) { + SKIP_IF_NO_SERVER(); + + // Malformed SQL: dangling WHERE with no predicate. + { + auto s = brpc::NewMysqlStatement( + channel_, "SELECT id FROM ps_people WHERE id=? AND WHERE"); + // Acceptable: prepare returns null, OR the first execute reports an + // error reply. A crash or a silent success is not. + if (s == nullptr) { + SUCCEED() << "prepare of malformed SQL returned null as expected"; + } else { + brpc::MysqlRequest request(s.get()); + brpc::MysqlResponse response; + brpc::Controller cntl; + channel_.CallMethod(NULL, &cntl, &request, &response, NULL); + if (cntl.Failed()) { + SUCCEED() << "execute of malformed statement failed as expected: " + << cntl.ErrorText(); + } else { + ASSERT_GE(response.reply_size(), 1u); + EXPECT_TRUE(response.reply(0).is_error()) + << "malformed prepared statement unexpectedly succeeded"; + } + } + } + + // Bind too few params: one-? statement executed with zero params. + { + PREPARE_OR_FAIL(s, "SELECT name FROM ps_people WHERE id=?"); + ASSERT_EQ(1u, s->param_count()); + brpc::MysqlRequest request(s.get()); + brpc::MysqlResponse response; + brpc::Controller cntl; + channel_.CallMethod(NULL, &cntl, &request, &response, NULL); + if (cntl.Failed()) { + SUCCEED() << "mismatched param count failed the RPC as expected: " + << cntl.ErrorText(); + } else { + ASSERT_GE(response.reply_size(), 1u); + EXPECT_TRUE(response.reply(0).is_error()) + << "execute with too few params unexpectedly produced a " + "non-error reply"; + } + } +} + +// One statement re-used across executes agrees with itself, and a second, +// independent statement on the same channel still works afterward. +TEST_F(MysqlPreparedTest, StatementReuseAndIndependentStatement) { + SKIP_IF_NO_SERVER(); + PREPARE_OR_FAIL(s1, "SELECT COUNT(*) FROM ps_people"); + + // Execute s1 twice; both must agree. + long long first_count = -1; + for (int iter = 0; iter < 2; ++iter) { + SCOPED_TRACE(iter); + brpc::MysqlRequest request(s1.get()); + brpc::MysqlResponse response; + brpc::Controller cntl; + channel_.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_GE(response.reply_size(), 1u); + const brpc::MysqlReply& r = response.reply(0); + ASSERT_TRUE(r.is_resultset()) << r; + ASSERT_EQ(1u, r.row_count()); + const brpc::MysqlReply::Field& f = r.next().field(0); + long long c = 0; + if (f.is_sbigint()) c = f.sbigint(); + else if (f.is_bigint()) c = (long long)f.bigint(); + else if (f.is_sinteger()) c = f.sinteger(); + else if (f.is_integer()) c = f.integer(); + else if (f.is_string()) c = atoll(f.string().as_string().c_str()); + else FAIL() << "unexpected COUNT(*) field type"; + if (first_count < 0) first_count = c; + EXPECT_EQ(first_count, c); + } + EXPECT_EQ(3, first_count) << "seed table should hold 3 rows"; + + // A second, independent statement on the same channel still works after + // s1 has been used -- exercises concurrent statement objects / reuse. + PREPARE_OR_FAIL(s2, "SELECT id FROM ps_people WHERE id=?"); + brpc::MysqlRequest request(s2.get()); + ASSERT_TRUE(request.AddParam((int32_t)417)); + brpc::MysqlResponse response; + brpc::Controller cntl; + channel_.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_GE(response.reply_size(), 1u); + ASSERT_TRUE(response.reply(0).is_resultset()); + EXPECT_EQ(1u, response.reply(0).row_count()); +} + +// BINARY-protocol TIME and DATETIME column parsing +// (MysqlReply::Field::ParseBinaryTime / ParseBinaryDataTime). +// +// These code paths are ONLY reached over the prepared-statement (binary) +// result protocol -- a plain text Query would return the value pre-formatted +// by the server and never touch ParseBinaryTime/ParseBinaryDataTime. So every +// case here PREPAREs a SELECT and executes it, forcing brpc to decode the +// packed wire bytes itself. +// +// TIME and DATETIME columns are surfaced as STRINGS: MysqlReply::Field's only +// text accessor is string() (returning a butil::StringPiece), and +// is_string() returns true for MYSQL_FIELD_TYPE_TIME and +// MYSQL_FIELD_TYPE_DATETIME (see mysql_reply.h). The parser writes the +// formatted text into _data.str via str.set(ptr, len) with an explicitly +// computed length, so comparing the FULL string (length included) against the +// exact expected text catches any trailing-garbage / wrong-length bug -- in +// particular the variable-width TIME path (optional sign, 2- vs 3+-digit +// hour) that has historically mis-sized its output. +// +// We use CAST(literal AS TIME/DATETIME[(N)]) so the exact value (and the +// column's declared fractional-second precision, which drives the wire +// length) is fully under our control. +TEST_F(MysqlPreparedTest, BinaryTimeAndDateTimeParsing) { + SKIP_IF_NO_SERVER(); + + struct Case { + const char* sql; // prepared SELECT producing one TIME/DATETIME field + const char* expected; // exact string the field must equal + }; + const Case cases[] = { + // TIME, ordinary 2-digit hour. + {"SELECT CAST('12:34:56' AS TIME)", "12:34:56"}, + // TIME, 3-digit hour: the variable-width hour path (total_hour >= 100). + {"SELECT CAST('300:00:00' AS TIME)", "300:00:00"}, + // TIME, the documented maximum magnitude. + {"SELECT CAST('838:59:59' AS TIME)", "838:59:59"}, + // TIME, negative: leading '-' sign byte on the wire. + {"SELECT CAST('-12:30:45' AS TIME)", "-12:30:45"}, + // TIME with fractional seconds (decimal=3 -> 12-byte wire packet). + {"SELECT CAST('01:02:03.456' AS TIME(3))", "01:02:03.456"}, + // DATETIME with no sub-second part (7-byte wire packet). + {"SELECT CAST('2021-03-04 05:06:07' AS DATETIME)", "2021-03-04 05:06:07"}, + // DATETIME with microseconds (decimal=6 -> 11-byte wire packet). + {"SELECT CAST('2021-03-04 05:06:07.123456' AS DATETIME(6))", + "2021-03-04 05:06:07.123456"}, + // DATETIME at exact midnight: MySQL omits the time-of-day part, so this + // arrives as a 4-byte (len==4) wire packet. The parser must emit the + // full "YYYY-MM-DD 00:00:00" form and report exactly 19 bytes. + {"SELECT CAST('2021-03-04 00:00:00' AS DATETIME)", + "2021-03-04 00:00:00"}, + // DATE column: only the date part on the wire (len==4) -> "YYYY-MM-DD". + {"SELECT CAST('2021-03-04' AS DATE)", "2021-03-04"}, + // TIME zero value: encoded with len==0 (no field bytes on the wire). + // This must surface as the zero string "00:00:00", NOT as NULL. + {"SELECT CAST('00:00:00' AS TIME)", "00:00:00"}, + }; + + for (const Case& c : cases) { + SCOPED_TRACE(c.sql); + PREPARE_OR_FAIL(s, c.sql); + EXPECT_EQ(0u, s->param_count()); + brpc::MysqlRequest request(s.get()); + brpc::MysqlResponse response; + brpc::Controller cntl; + channel_.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_GE(response.reply_size(), 1u); + const brpc::MysqlReply& r = response.reply(0); + ASSERT_TRUE(r.is_resultset()) << "expected a result set, got: " << r; + ASSERT_EQ(1u, r.column_count()); + ASSERT_EQ(1u, r.row_count()); + const brpc::MysqlReply::Field& f = r.next().field(0); + // The binary TIME/DATETIME value must be surfaced as a string (this is + // the ParseBinaryTime / ParseBinaryDataTime output). + ASSERT_TRUE(f.is_string()) + << "TIME/DATETIME field should be exposed as a string"; + // Compare the FULL string, including its length: a trailing-garbage or + // off-by-one length bug in the parser would make this exact compare + // fail even if the visible prefix looks right. + const std::string got = f.string().as_string(); + EXPECT_EQ(c.expected, got) + << "binary-parsed value mismatch (got length " << got.size() + << ", expected length " << strlen(c.expected) << ")"; + EXPECT_EQ(strlen(c.expected), got.size()) + << "binary-parsed value has wrong length"; + } +} + +} // namespace diff --git a/test/brpc_mysql_txn_integration_unittest.cpp b/test/brpc_mysql_txn_integration_unittest.cpp new file mode 100644 index 0000000..3d2325a --- /dev/null +++ b/test/brpc_mysql_txn_integration_unittest.cpp @@ -0,0 +1,605 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// MySQL client TRANSACTION integration tests, run through brpc's PUBLIC API +// against a REAL mysqld. +// +// Each TEST_F drives a transaction scenario end to end: +// * brpc::Channel(protocol="mysql", connection_type="pooled", +// auth=MysqlAuthenticator) -> a live connection to the server; +// * brpc::NewMysqlTransaction(channel, opts) -> a connection-pinned +// transaction handle (START TRANSACTION on a dedicated socket); +// * MysqlRequest(tx).Query(...) + channel.CallMethod(...) -> statements +// INSIDE the transaction (same pinned socket); +// * MysqlRequest().Query(...) on the SAME channel -> a SECOND connection +// from the pool, used as an independent observer to prove isolation +// (uncommitted rows are invisible until commit()); +// * tx->commit() / tx->rollback() -> terminate the transaction. +// +// Because transactions, simple SELECTs and DML all flow through the same +// COM_QUERY text protocol, these tests also cover simple-query execution +// and text-result parsing (column metadata + row field decoding). +// +// Harness (server spawn / skip convention, -mysql_use_running_server and +// -mysql_host/-port/-user/-password gflags) follows +// test/mysql/brpc_mysql_auth_handshake_unittest.cpp and, transitively, +// test/brpc_redis_unittest.cpp's which-then-spawn pattern. When mysqld is +// absent every test GTEST_SKIP()s, so the file is CI-safe with no server. + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "brpc/channel.h" +#include "brpc/policy/mysql/mysql.h" +#include "brpc/policy/mysql/mysql_transaction.h" +#include "brpc/policy/mysql/mysql_authenticator.h" +#include "butil/logging.h" + +// These gflags are intentionally re-declared here (not shared with the auth +// unittest): the CMake glob at test/CMakeLists.txt builds each +// brpc_mysql_*_unittest.cpp into its OWN executable, so there is no symbol +// collision across test binaries. +DEFINE_bool(mysql_use_running_server, false, + "Use an already-running MySQL server instead of spawning a " + "throwaway one; the running server is neither started nor stopped " + "by the test."); +DEFINE_string(mysql_host, "127.0.0.1", + "Host of the running MySQL server " + "(only with -mysql_use_running_server)."); +DEFINE_int32(mysql_port, 13306, + "TCP port of the MySQL server (used for both the running server " + "and the spawned throwaway server)."); +DEFINE_string(mysql_user, "root", "Login user for the transaction tests."); +DEFINE_string(mysql_password, "", + "Password for -mysql_user (empty for the spawned server)."); +DEFINE_string(mysql_schema, "brpc_txn_test", + "Schema (database) the transaction tests create and use."); + +namespace { + +// Throwaway-server harness (mirrors brpc_mysql_auth_handshake_unittest.cpp, +// which mirrors brpc_redis_unittest.cpp). >0: forked pid; -2: external +// running server reachable; -1: no server -> tests skip. +#define MYSQLD_BIN "mysqld" + +static pthread_once_t s_start_once = PTHREAD_ONCE_INIT; +static pid_t s_mysqld_pid = -1; +static std::string s_host = "127.0.0.1"; +static int s_port = 13306; +static std::string s_user = "root"; +static std::string s_password; + +static std::string TestDataDir() { + char cwd[1024]; + if (getcwd(cwd, sizeof(cwd)) == NULL) { + return std::string("/tmp/mysql_txn_data_for_test"); + } + return std::string(cwd) + "/mysql_txn_data_for_test"; +} + +static void RemoveMysqlServer() { + if (s_mysqld_pid > 0) { + puts("[Stopping mysqld]"); + char cmd[1280]; + snprintf(cmd, sizeof(cmd), "kill %d", s_mysqld_pid); + CHECK(0 == system(cmd)); + usleep(500000); + snprintf(cmd, sizeof(cmd), "rm -rf '%s'", TestDataDir().c_str()); + CHECK(0 == system(cmd)); + } +} + +// Raw TCP probe to detect server readiness; returns fd (caller closes) or -1. +static int ProbeMysql() { + int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + return -1; + } + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(static_cast(s_port)); + addr.sin_addr.s_addr = inet_addr(s_host.c_str()); + if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) != 0) { + close(fd); + return -1; + } + return fd; +} + +static void RunMysqlServer() { + if (FLAGS_mysql_use_running_server) { + s_host = FLAGS_mysql_host; + s_port = FLAGS_mysql_port; + s_user = FLAGS_mysql_user; + s_password = FLAGS_mysql_password; + printf("[Using running mysqld at %s:%d as user '%s']\n", + s_host.c_str(), s_port, s_user.c_str()); + int fd = ProbeMysql(); + if (fd >= 0) { + close(fd); + s_mysqld_pid = -2; + } else { + printf("Cannot reach running mysqld at %s:%d, tests will skip\n", + s_host.c_str(), s_port); + } + return; + } + + if (system("which " MYSQLD_BIN) != 0) { + puts("Fail to find " MYSQLD_BIN ", transaction tests will be skipped"); + return; + } + s_host = "127.0.0.1"; + s_port = FLAGS_mysql_port; + s_user = "root"; + s_password.clear(); + const std::string datadir = TestDataDir(); + char cmd[2048]; + snprintf(cmd, sizeof(cmd), "rm -rf '%s' && mkdir -p '%s'", + datadir.c_str(), datadir.c_str()); + if (system(cmd) != 0) { + puts("Fail to create datadir, transaction tests will be skipped"); + return; + } + snprintf(cmd, sizeof(cmd), + MYSQLD_BIN " --initialize-insecure --datadir='%s'" + " --log-error='%s/init.err'", + datadir.c_str(), datadir.c_str()); + if (system(cmd) != 0) { + puts("Fail to initialize mysqld datadir, tests will be skipped"); + snprintf(cmd, sizeof(cmd), "rm -rf '%s'", datadir.c_str()); + CHECK(0 == system(cmd)); + return; + } + atexit(RemoveMysqlServer); + + s_mysqld_pid = fork(); + if (s_mysqld_pid < 0) { + puts("Fail to fork"); + exit(1); + } else if (s_mysqld_pid == 0) { + puts("[Starting mysqld]"); + char port_arg[32]; + snprintf(port_arg, sizeof(port_arg), "--port=%d", FLAGS_mysql_port); + const std::string datadir_arg = "--datadir=" + datadir; + const std::string socket_arg = "--socket=" + datadir + "/mysqld.sock"; + const std::string pidfile_arg = "--pid-file=" + datadir + "/mysqld.pid"; + const std::string logerr_arg = "--log-error=" + datadir + "/mysqld.err"; + char* const argv[] = { + (char*)MYSQLD_BIN, + (char*)datadir_arg.c_str(), + (char*)port_arg, + (char*)socket_arg.c_str(), + (char*)pidfile_arg.c_str(), + (char*)logerr_arg.c_str(), + (char*)"--mysqlx=OFF", + (char*)"--bind-address=127.0.0.1", + NULL}; + if (execvp(MYSQLD_BIN, argv) < 0) { + puts("Fail to run " MYSQLD_BIN); + exit(1); + } + } + // Wait for TCP readiness (fresh tablespace recovery), then create a + // password account so the caching_sha2 client can authenticate over TCP + // exactly like the running-server mode. root keeps its empty password on + // the unix socket; we exercise the spawned server as empty-password root. + for (int i = 0; i < 300; ++i) { + int fd = ProbeMysql(); + if (fd >= 0) { + close(fd); + return; + } + usleep(100000); + } + puts("mysqld did not become ready, transaction tests will be skipped"); + s_mysqld_pid = -1; +} + +// Small helpers over the brpc MySQL public API. + +// Runs |sql| outside any transaction on |channel| (a fresh pooled +// connection). Returns false on transport failure. +static bool RunPlain(brpc::Channel& channel, const std::string& sql, + brpc::MysqlResponse* resp) { + brpc::MysqlRequest req; + if (!req.Query(sql)) { + return false; + } + brpc::Controller cntl; + channel.CallMethod(NULL, &cntl, &req, resp, NULL); + return !cntl.Failed(); +} + +// Runs |sql| INSIDE transaction |tx| (its pinned connection). Returns false +// on transport failure. +static bool RunInTx(brpc::Channel& channel, const brpc::MysqlTransaction* tx, + const std::string& sql, brpc::MysqlResponse* resp) { + brpc::MysqlRequest req(tx); + if (!req.Query(sql)) { + return false; + } + brpc::Controller cntl; + channel.CallMethod(NULL, &cntl, &req, resp, NULL); + return !cntl.Failed(); +} + +// Convenience: expects a single OK reply for a DML/DDL statement. +static ::testing::AssertionResult ExpectOk(const brpc::MysqlResponse& resp) { + if (resp.reply_size() < 1) { + return ::testing::AssertionFailure() << "no reply"; + } + const brpc::MysqlReply& r = resp.reply(0); + if (r.is_error()) { + return ::testing::AssertionFailure() + << "ERR " << r.error().errcode() << ": " + << r.error().msg().as_string(); + } + if (!r.is_ok()) { + return ::testing::AssertionFailure() << "reply is not OK, type=" << r.type(); + } + return ::testing::AssertionSuccess(); +} + +// Returns the row count of the FIRST reply, asserting it is a result set. +// On any non-resultset reply returns -1 (so callers can fail clearly). +static int64_t ResultRowCount(const brpc::MysqlResponse& resp) { + if (resp.reply_size() < 1) { + return -1; + } + const brpc::MysqlReply& r = resp.reply(0); + if (!r.is_resultset()) { + return -1; + } + return static_cast(r.row_count()); +} + +// Fixture: one channel + a scratch table per test (built in SetUp, dropped in +// TearDown). InnoDB so DML is transactional. +class MysqlTxnIntegrationTest : public testing::Test { +protected: + static bool NoServer() { return s_mysqld_pid == -1; } + + void SetUp() override { + pthread_once(&s_start_once, RunMysqlServer); + if (NoServer()) { + GTEST_SKIP() << "no mysqld available; skipping transaction tests"; + } + // Authenticator carries user/password and the working schema. An + // empty schema is created first over a schema-less channel. + ASSERT_TRUE(InitChannel(&_setup_channel, /*schema=*/"")); + brpc::MysqlResponse resp; + ASSERT_TRUE(RunPlain(_setup_channel, "CREATE DATABASE IF NOT EXISTS " + + FLAGS_mysql_schema, &resp)); + + ASSERT_TRUE(InitChannel(&_channel, FLAGS_mysql_schema)); + ASSERT_TRUE(RunPlain(_channel, "DROP TABLE IF EXISTS " + Table(), &resp)); + ASSERT_TRUE(ExpectOk(resp)) << "drop pre-existing scratch table"; + ASSERT_TRUE(RunPlain(_channel, + "CREATE TABLE " + Table() + + " (id INT PRIMARY KEY, name VARCHAR(32)) " + "ENGINE=InnoDB", + &resp)); + ASSERT_TRUE(ExpectOk(resp)) << "create scratch table"; + } + + void TearDown() override { + if (NoServer()) { + return; + } + brpc::MysqlResponse resp; + RunPlain(_channel, "DROP TABLE IF EXISTS " + Table(), &resp); + } + + // Pooled channel is required so a transaction can pin its own dedicated + // connection while the test issues independent observer queries on others. + bool InitChannel(brpc::Channel* channel, const std::string& schema) { + _auth.reset(new brpc::policy::MysqlAuthenticator(s_user, s_password, + schema)); + brpc::ChannelOptions options; + options.protocol = "mysql"; + options.connection_type = "pooled"; + options.auth = _auth.get(); + options.timeout_ms = 5000; + options.max_retry = 0; + char addr[128]; + snprintf(addr, sizeof(addr), "%s:%d", s_host.c_str(), s_port); + return channel->Init(addr, &options) == 0; + } + + std::string Table() const { return "txn_scratch"; } + + brpc::Channel _setup_channel; + brpc::Channel _channel; + // Authenticator must outlive the channels that point at it. + std::unique_ptr _auth; +}; + +// Test cases. Each fat test chains several transactional behaviors so a single +// TEST_F validates a whole group of related transaction guarantees together. + +// Transaction lifecycle: commit publishes a row to other connections, and a +// rolled-back insert as well as a rolled-back delete leave the table exactly as +// it was before the transaction started. +TEST_F(MysqlTxnIntegrationTest, CommitPublishesRollbackRestores) { + brpc::MysqlResponse resp; + + // 1) committed INSERT must be visible on a fresh connection afterwards. + { + brpc::MysqlTransactionUniquePtr tx = + brpc::NewMysqlTransaction(_channel, brpc::MysqlTransactionOptions()); + ASSERT_TRUE(tx != NULL) << "failed to start transaction"; + ASSERT_TRUE(RunInTx(_channel, tx.get(), + "INSERT INTO " + Table() + " VALUES (3107, 'quill')", + &resp)); + EXPECT_TRUE(ExpectOk(resp)); + EXPECT_EQ(resp.reply(0).ok().affect_row(), 1u); + ASSERT_TRUE(tx->commit()); + } + ASSERT_TRUE(RunPlain(_channel, "SELECT id, name FROM " + Table(), &resp)); + EXPECT_EQ(ResultRowCount(resp), 1); + { + const brpc::MysqlReply& r = resp.reply(0); + ASSERT_TRUE(r.is_resultset()); + ASSERT_EQ(r.row_count(), 1u); + const brpc::MysqlReply::Row& row = r.next(); + ASSERT_EQ(row.field_count(), 2u); + EXPECT_EQ(row.field(0).sinteger(), 3107); + EXPECT_EQ(row.field(1).string().as_string(), "quill"); + } + + // 2) a rolled-back INSERT must leave no trace (still exactly one row). + { + brpc::MysqlTransactionUniquePtr tx = + brpc::NewMysqlTransaction(_channel, brpc::MysqlTransactionOptions()); + ASSERT_TRUE(tx != NULL); + ASSERT_TRUE(RunInTx(_channel, tx.get(), + "INSERT INTO " + Table() + " VALUES (5288, 'brindle')", + &resp)); + EXPECT_TRUE(ExpectOk(resp)); + ASSERT_TRUE(tx->rollback()); + } + ASSERT_TRUE(RunPlain(_channel, "SELECT id FROM " + Table(), &resp)); + EXPECT_EQ(ResultRowCount(resp), 1) << "rolled-back insert must vanish"; + + // 3) a rolled-back DELETE of the committed row must restore it. + { + brpc::MysqlTransactionUniquePtr tx = + brpc::NewMysqlTransaction(_channel, brpc::MysqlTransactionOptions()); + ASSERT_TRUE(tx != NULL); + ASSERT_TRUE(RunInTx(_channel, tx.get(), + "DELETE FROM " + Table() + " WHERE id = 3107", &resp)); + EXPECT_TRUE(ExpectOk(resp)); + EXPECT_EQ(resp.reply(0).ok().affect_row(), 1u); + ASSERT_TRUE(tx->rollback()); + } + ASSERT_TRUE(RunPlain(_channel, "SELECT id FROM " + Table(), &resp)); + EXPECT_EQ(ResultRowCount(resp), 1) << "rolled-back delete must restore row"; + { + const brpc::MysqlReply& r = resp.reply(0); + ASSERT_TRUE(r.is_resultset()); + ASSERT_EQ(r.row_count(), 1u); + EXPECT_EQ(r.next().field(0).sinteger(), 3107); + } +} + +// Isolation in both directions on the same open transaction: the transaction +// reads its own not-yet-committed write on its pinned connection, while an +// independent pooled connection sees nothing until the rollback. +TEST_F(MysqlTxnIntegrationTest, OwnWriteVisibleOthersIsolated) { + brpc::MysqlTransactionUniquePtr tx = + brpc::NewMysqlTransaction(_channel, brpc::MysqlTransactionOptions()); + ASSERT_TRUE(tx != NULL); + + brpc::MysqlResponse resp; + ASSERT_TRUE(RunInTx(_channel, tx.get(), + "INSERT INTO " + Table() + " VALUES (6741, 'tangle')", + &resp)); + EXPECT_TRUE(ExpectOk(resp)); + + // Same pinned connection: must read its own uncommitted row. + ASSERT_TRUE(RunInTx(_channel, tx.get(), + "SELECT name FROM " + Table() + " WHERE id = 6741", &resp)); + EXPECT_EQ(ResultRowCount(resp), 1); + { + const brpc::MysqlReply& r = resp.reply(0); + ASSERT_TRUE(r.is_resultset()); + ASSERT_EQ(r.row_count(), 1u); + EXPECT_EQ(r.next().field(0).string().as_string(), "tangle"); + } + + // A different pooled connection must NOT see the uncommitted write. + ASSERT_TRUE(RunPlain(_channel, "SELECT id FROM " + Table(), &resp)); + EXPECT_EQ(ResultRowCount(resp), 0) + << "uncommitted write leaked to another connection"; + + ASSERT_TRUE(tx->rollback()); + + // After rollback nothing remains anywhere. + ASSERT_TRUE(RunPlain(_channel, "SELECT id FROM " + Table(), &resp)); + EXPECT_EQ(ResultRowCount(resp), 0); +} + +// Autocommit behavior, both states in one test: with autocommit on (the +// default) a bare INSERT is immediately durable on a new connection; toggling +// autocommit off on a pinned connection turns a later INSERT into pending work +// that ROLLBACK discards. +TEST_F(MysqlTxnIntegrationTest, AutocommitOnDurableOffRollbackable) { + brpc::MysqlResponse resp; + + // autocommit ON: immediate durability. + ASSERT_TRUE(RunPlain(_channel, + "INSERT INTO " + Table() + " VALUES (4419, 'amber')", + &resp)); + EXPECT_TRUE(ExpectOk(resp)); + ASSERT_TRUE(RunPlain(_channel, "SELECT id FROM " + Table(), &resp)); + EXPECT_EQ(ResultRowCount(resp), 1); + + // autocommit OFF on a pinned connection: a new INSERT is pending and a + // ROLLBACK drops only it, leaving the earlier durable row in place. + { + brpc::MysqlTransactionUniquePtr tx = + brpc::NewMysqlTransaction(_channel, brpc::MysqlTransactionOptions()); + ASSERT_TRUE(tx != NULL); + ASSERT_TRUE(RunInTx(_channel, tx.get(), "SET autocommit = 0", &resp)); + EXPECT_TRUE(ExpectOk(resp)); + ASSERT_TRUE(RunInTx(_channel, tx.get(), + "INSERT INTO " + Table() + " VALUES (8053, 'frost')", + &resp)); + EXPECT_TRUE(ExpectOk(resp)); + ASSERT_TRUE(tx->rollback()); + } + ASSERT_TRUE(RunPlain(_channel, "SELECT id FROM " + Table(), &resp)); + EXPECT_EQ(ResultRowCount(resp), 1) + << "autocommit=0 + rollback should drop only the pending insert"; + EXPECT_EQ(resp.reply(0).next().field(0).sinteger(), 4419); +} + +// Multi-statement transactional grouping plus partial undo: two inserts grouped +// under one transaction become visible together only after commit, and within a +// second transaction a SAVEPOINT lets a later insert be peeled back while the +// pre-savepoint work survives the final commit. +TEST_F(MysqlTxnIntegrationTest, GroupedInsertsThenSavepointPartialUndo) { + brpc::MysqlResponse resp; + + // Two inserts under one transaction: invisible until commit, then both show. + { + brpc::MysqlTransactionUniquePtr tx = + brpc::NewMysqlTransaction(_channel, brpc::MysqlTransactionOptions()); + ASSERT_TRUE(tx != NULL); + ASSERT_TRUE(RunInTx(_channel, tx.get(), + "INSERT INTO " + Table() + " VALUES (211, 'one')", + &resp)); + EXPECT_TRUE(ExpectOk(resp)); + ASSERT_TRUE(RunInTx(_channel, tx.get(), + "INSERT INTO " + Table() + " VALUES (733, 'two')", + &resp)); + EXPECT_TRUE(ExpectOk(resp)); + + // Not yet visible to a separate connection. + ASSERT_TRUE(RunPlain(_channel, "SELECT id FROM " + Table(), &resp)); + EXPECT_EQ(ResultRowCount(resp), 0); + + ASSERT_TRUE(tx->commit()); + } + ASSERT_TRUE(RunPlain(_channel, "SELECT id FROM " + Table(), &resp)); + EXPECT_EQ(ResultRowCount(resp), 2) << "both grouped inserts visible"; + + // Start fresh for the savepoint half. + ASSERT_TRUE(RunPlain(_channel, "DELETE FROM " + Table(), &resp)); + ASSERT_TRUE(ExpectOk(resp)); + + // SAVEPOINT then ROLLBACK TO it: pre-savepoint row kept, post dropped. + { + brpc::MysqlTransactionUniquePtr tx = + brpc::NewMysqlTransaction(_channel, brpc::MysqlTransactionOptions()); + ASSERT_TRUE(tx != NULL); + ASSERT_TRUE(RunInTx(_channel, tx.get(), + "INSERT INTO " + Table() + " VALUES (901, 'kept')", + &resp)); + EXPECT_TRUE(ExpectOk(resp)); + ASSERT_TRUE(RunInTx(_channel, tx.get(), "SAVEPOINT mark1", &resp)); + EXPECT_TRUE(ExpectOk(resp)); + ASSERT_TRUE(RunInTx(_channel, tx.get(), + "INSERT INTO " + Table() + " VALUES (902, 'gone')", + &resp)); + EXPECT_TRUE(ExpectOk(resp)); + ASSERT_TRUE(RunInTx(_channel, tx.get(), "ROLLBACK TO SAVEPOINT mark1", + &resp)); + EXPECT_TRUE(ExpectOk(resp)); + + // Inside the txn only the pre-savepoint row remains. + ASSERT_TRUE(RunInTx(_channel, tx.get(), "SELECT id FROM " + Table(), + &resp)); + EXPECT_EQ(ResultRowCount(resp), 1); + + ASSERT_TRUE(tx->commit()); + } + ASSERT_TRUE(RunPlain(_channel, "SELECT id FROM " + Table(), &resp)); + EXPECT_EQ(ResultRowCount(resp), 1) << "only the kept row should persist"; + { + const brpc::MysqlReply& r = resp.reply(0); + ASSERT_TRUE(r.is_resultset()); + ASSERT_EQ(r.row_count(), 1u); + EXPECT_EQ(r.next().field(0).sinteger(), 901); + } +} + +// Error surfaces from within a transaction: a duplicate-primary-key insert +// returns an ERR reply (and the transaction still rolls back cleanly), and a +// write attempted in a read-only transaction is likewise rejected with ERR. +TEST_F(MysqlTxnIntegrationTest, DuplicateKeyAndReadOnlyWriteReportErr) { + brpc::MysqlResponse resp; + + // Seed a committed row so the in-txn insert collides on the primary key. + ASSERT_TRUE(RunPlain(_channel, + "INSERT INTO " + Table() + " VALUES (1505, 'seed')", + &resp)); + ASSERT_TRUE(ExpectOk(resp)); + + { + brpc::MysqlTransactionUniquePtr tx = + brpc::NewMysqlTransaction(_channel, brpc::MysqlTransactionOptions()); + ASSERT_TRUE(tx != NULL); + // Duplicate-key insert -> ERR packet (errno 1062, ER_DUP_ENTRY). + ASSERT_TRUE(RunInTx(_channel, tx.get(), + "INSERT INTO " + Table() + " VALUES (1505, 'clash')", + &resp)); + ASSERT_GE(resp.reply_size(), 1u); + const brpc::MysqlReply& r = resp.reply(0); + EXPECT_TRUE(r.is_error()) << "duplicate key should yield an ERR reply"; + if (r.is_error()) { + EXPECT_EQ(r.error().errcode(), 1062) << "expected ER_DUP_ENTRY (1062)"; + } + ASSERT_TRUE(tx->rollback()); + } + + // A read-only transaction must reject a write. + { + brpc::MysqlTransactionOptions opts; + opts.readonly = true; + brpc::MysqlTransactionUniquePtr tx = + brpc::NewMysqlTransaction(_channel, opts); + ASSERT_TRUE(tx != NULL) << "failed to start read-only transaction"; + ASSERT_TRUE(RunInTx(_channel, tx.get(), + "INSERT INTO " + Table() + " VALUES (1777, 'nope')", + &resp)); + ASSERT_GE(resp.reply_size(), 1u); + const brpc::MysqlReply& r = resp.reply(0); + EXPECT_TRUE(r.is_error()) + << "write in a read-only transaction should be rejected with ERR"; + if (r.is_error()) { + // ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION == 1792. + EXPECT_EQ(r.error().errcode(), 1792) + << "expected read-only-transaction error (1792)"; + } + ASSERT_TRUE(tx->rollback()); + } +} + +} // namespace diff --git a/test/brpc_naming_service_filter_unittest.cpp b/test/brpc_naming_service_filter_unittest.cpp new file mode 100644 index 0000000..387aeb5 --- /dev/null +++ b/test/brpc_naming_service_filter_unittest.cpp @@ -0,0 +1,74 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include "butil/string_printf.h" +#include "butil/files/temp_file.h" +#include "brpc/socket.h" +#include "brpc/channel.h" +#include "brpc/load_balancer.h" +#include "brpc/policy/file_naming_service.h" + +class NamingServiceFilterTest : public testing::Test { +protected: + void SetUp() {} + void TearDown() {} +}; + +class MyNSFilter: public brpc::NamingServiceFilter { +public: + bool Accept(const brpc::ServerNode& node) const { + return node.tag == "enable"; + } +}; + +TEST_F(NamingServiceFilterTest, sanity) { + std::vector servers; + const char *address_list[] = { + "10.127.0.1:1234", + "10.128.0.1:1234 enable", + "10.129.0.1:1234", + "localhost:1234", + "baidu.com:1234" + }; + butil::TempFile tmp_file; + { + FILE* fp = fopen(tmp_file.fname(), "w"); + for (size_t i = 0; i < ARRAY_SIZE(address_list); ++i) { + ASSERT_TRUE(fprintf(fp, "%s\n", address_list[i])); + } + fclose(fp); + } + MyNSFilter ns_filter; + brpc::Channel channel; + brpc::ChannelOptions opt; + opt.ns_filter = &ns_filter; + std::string ns = std::string("file://") + tmp_file.fname(); + ASSERT_EQ(0, channel.Init(ns.c_str(), "rr", &opt)); + + butil::EndPoint ep; + ASSERT_EQ(0, butil::hostname2endpoint("10.128.0.1:1234", &ep)); + for (int i = 0; i < 10; ++i) { + brpc::SocketUniquePtr tmp_sock; + brpc::LoadBalancer::SelectIn sel_in = { 0, false, false, 0, NULL }; + brpc::LoadBalancer::SelectOut sel_out(&tmp_sock); + ASSERT_EQ(0, channel._lb->SelectServer(sel_in, &sel_out)); + ASSERT_EQ(ep, tmp_sock->remote_side()); + } +} diff --git a/test/brpc_naming_service_unittest.cpp b/test/brpc_naming_service_unittest.cpp new file mode 100644 index 0000000..0ba2be6 --- /dev/null +++ b/test/brpc_naming_service_unittest.cpp @@ -0,0 +1,850 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include "butil/string_printf.h" +#include "butil/strings/string_split.h" +#include "butil/files/temp_file.h" +#include "bthread/bthread.h" +#include "brpc/http_status_code.h" +#ifdef BAIDU_INTERNAL +#include "brpc/policy/baidu_naming_service.h" +#endif +#include "brpc/policy/consul_naming_service.h" +#include "brpc/policy/domain_naming_service.h" +#include "brpc/policy/file_naming_service.h" +#include "brpc/policy/list_naming_service.h" +#include "brpc/policy/remote_file_naming_service.h" +#include "brpc/policy/discovery_naming_service.h" +#include "brpc/policy/nacos_naming_service.h" +#include "echo.pb.h" +#include "brpc/server.h" + + +namespace brpc { +DECLARE_int32(health_check_interval); + +namespace policy { + +DECLARE_bool(consul_enable_degrade_to_file_naming_service); +DECLARE_string(consul_file_naming_service_dir); +DECLARE_string(consul_service_discovery_url); +DECLARE_string(discovery_api_addr); +DECLARE_string(discovery_env); +DECLARE_int32(discovery_renew_interval_s); +DECLARE_string(nacos_address); +DECLARE_string(nacos_username); +DECLARE_string(nacos_password); + +} // policy +} // brpc + +namespace { + +bool IsIPListEqual(const std::set& s1, const std::set& s2) { + if (s1.size() != s2.size()) { + return false; + } + for (auto it1 = s1.begin(), it2 = s2.begin(); it1 != s1.end(); ++it1, ++it2) { + if (*it1 != *it2) { + return false; + } + } + return true; +} + +TEST(NamingServiceTest, sanity) { + std::vector servers; + +#ifdef BAIDU_INTERNAL + brpc::policy::BaiduNamingService bns; + ASSERT_EQ(0, bns.GetServers("qa-pbrpc.SAT.tjyx", &servers)); +#endif + + auto collect_ips = [&servers]() { + std::set ret; + for (auto& server : servers) { + ret.insert(server.addr.ip); + } + return ret; + }; + brpc::policy::DomainNamingService dns; + ASSERT_EQ(0, dns.GetServers("baidu.com:1234", &servers)); + size_t server_size = servers.size(); + ASSERT_GE(server_size, 1); + for (size_t i = 0; i < servers.size(); ++i) { + ASSERT_EQ(1234, servers[i].addr.port); + } + const auto expected_ips = collect_ips(); + + ASSERT_EQ(0, dns.GetServers("baidu.com", &servers)); + ASSERT_EQ(server_size, servers.size()); + ASSERT_TRUE(IsIPListEqual(expected_ips, collect_ips())); + for (size_t i = 0; i < servers.size(); ++i) { + ASSERT_EQ(80, servers[i].addr.port); + } + + ASSERT_EQ(0, dns.GetServers("baidu.com:1234/useless1/useless2", &servers)); + ASSERT_EQ(server_size, servers.size()); + ASSERT_TRUE(IsIPListEqual(expected_ips, collect_ips())); + for (size_t i = 0; i < servers.size(); ++i) { + ASSERT_EQ(1234, servers[i].addr.port); + } + + ASSERT_EQ(0, dns.GetServers("baidu.com/useless1/useless2", &servers)); + ASSERT_EQ(server_size, servers.size()); + ASSERT_TRUE(IsIPListEqual(expected_ips, collect_ips())); + for (size_t i = 0; i < servers.size(); ++i) { + ASSERT_EQ(80, servers[i].addr.port); + } + + const char *address_list[] = { + "10.127.0.1:1234", + "10.128.0.1:1234", + "10.129.0.1:1234", + "localhost:1234", + "baidu.com:1234" + }; + butil::TempFile tmp_file; + { + FILE* fp = fopen(tmp_file.fname(), "w"); + for (size_t i = 0; i < ARRAY_SIZE(address_list); ++i) { + ASSERT_TRUE(fprintf(fp, "%s\n", address_list[i])); + } + fclose(fp); + } + brpc::policy::FileNamingService fns; + ASSERT_EQ(0, fns.GetServers(tmp_file.fname(), &servers)); + ASSERT_EQ(ARRAY_SIZE(address_list), servers.size()); + for (size_t i = 0; i < ARRAY_SIZE(address_list) - 2; ++i) { + std::ostringstream oss; + oss << servers[i]; + ASSERT_EQ(address_list[i], oss.str()) << "i=" << i; + } + + std::string s; + for (size_t i = 0; i < ARRAY_SIZE(address_list); ++i) { + ASSERT_EQ(0, butil::string_appendf(&s, "%s,", address_list[i])); + } + brpc::policy::ListNamingService lns; + ASSERT_EQ(0, lns.GetServers(s.c_str(), &servers)); + ASSERT_EQ(ARRAY_SIZE(address_list), servers.size()); + for (size_t i = 0; i < ARRAY_SIZE(address_list) - 2; ++i) { + std::ostringstream oss; + oss << servers[i]; + ASSERT_EQ(address_list[i], oss.str()) << "i=" << i; + } +} + +TEST(NamingServiceTest, invalid_port) { + std::vector servers; + +#ifdef BAIDU_INTERNAL + brpc::policy::BaiduNamingService bns; + ASSERT_EQ(0, bns.GetServers("qa-pbrpc.SAT.tjyx:main", &servers)); +#endif + + brpc::policy::DomainNamingService dns; + ASSERT_EQ(-1, dns.GetServers("baidu.com:", &servers)); + ASSERT_EQ(-1, dns.GetServers("baidu.com:123a", &servers)); + ASSERT_EQ(-1, dns.GetServers("baidu.com:99999", &servers)); +} + +TEST(NamingServiceTest, wrong_name) { + std::vector servers; + +#ifdef BAIDU_INTERNAL + brpc::policy::BaiduNamingService bns; + ASSERT_EQ(-1, bns.GetServers("Wrong", &servers)); +#endif + + const char *address_list[] = { + "10.127.0.1:1234", + "10.128.0.1:12302344", + "10.129.0.1:1234", + "10.128.0.1:", + "10.128.0.1", + "localhost:1234", + "baidu.com:1234", + "LOCAL:1234" + }; + butil::TempFile tmp_file; + { + FILE *fp = fopen(tmp_file.fname(), "w"); + for (size_t i = 0; i < ARRAY_SIZE(address_list); ++i) { + ASSERT_TRUE(fprintf(fp, "%s\n", address_list[i])); + } + fclose(fp); + } + brpc::policy::FileNamingService fns; + ASSERT_EQ(0, fns.GetServers(tmp_file.fname(), &servers)); + ASSERT_EQ(ARRAY_SIZE(address_list) - 4, servers.size()); + + std::string s; + for (size_t i = 0; i < ARRAY_SIZE(address_list); ++i) { + ASSERT_EQ(0, butil::string_appendf(&s, ", %s", address_list[i])); + } + brpc::policy::ListNamingService lns; + ASSERT_EQ(0, lns.GetServers(s.c_str(), &servers)); + ASSERT_EQ(ARRAY_SIZE(address_list) - 4, servers.size()); +} + +class UserNamingServiceImpl : public test::UserNamingService { +public: + UserNamingServiceImpl() : list_names_count(0), touch_count(0) {} + ~UserNamingServiceImpl() { } + void ListNames(google::protobuf::RpcController* cntl_base, + const test::HttpRequest*, + test::HttpResponse*, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = (brpc::Controller*)cntl_base; + cntl->http_response().set_content_type("text/plain"); + cntl->response_attachment().append( + "0.0.0.0:8635 tag1\r\n0.0.0.0:8636 tag2\n" + "0.0.0.0:8635 tag3\r\n0.0.0.0:8636\r\n"); + list_names_count.fetch_add(1); + } + void Touch(google::protobuf::RpcController*, + const test::HttpRequest*, + test::HttpResponse*, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + touch_count.fetch_add(1); + } + + butil::atomic list_names_count; + butil::atomic touch_count; +}; + +TEST(NamingServiceTest, remotefile) { + brpc::Server server1; + UserNamingServiceImpl svc1; + ASSERT_EQ(0, server1.AddService(&svc1, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server1.Start("localhost:8635", NULL)); + brpc::Server server2; + UserNamingServiceImpl svc2; + ASSERT_EQ(0, server2.AddService(&svc2, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server2.Start("localhost:8636", NULL)); + + butil::EndPoint n1; + ASSERT_EQ(0, butil::str2endpoint("0.0.0.0:8635", &n1)); + butil::EndPoint n2; + ASSERT_EQ(0, butil::str2endpoint("0.0.0.0:8636", &n2)); + std::vector expected_servers; + expected_servers.push_back(brpc::ServerNode(n1, "tag1")); + expected_servers.push_back(brpc::ServerNode(n2, "tag2")); + expected_servers.push_back(brpc::ServerNode(n1, "tag3")); + expected_servers.push_back(brpc::ServerNode(n2)); + std::sort(expected_servers.begin(), expected_servers.end()); + + std::vector servers; + brpc::policy::RemoteFileNamingService rfns; + ASSERT_EQ(0, rfns.GetServers("0.0.0.0:8635/UserNamingService/ListNames", &servers)); + ASSERT_EQ(expected_servers.size(), servers.size()); + std::sort(servers.begin(), servers.end()); + for (size_t i = 0; i < expected_servers.size(); ++i) { + ASSERT_EQ(expected_servers[i], servers[i]); + } + + ASSERT_EQ(0, rfns.GetServers("http://0.0.0.0:8635/UserNamingService/ListNames", &servers)); + ASSERT_EQ(expected_servers.size(), servers.size()); + std::sort(servers.begin(), servers.end()); + for (size_t i = 0; i < expected_servers.size(); ++i) { + ASSERT_EQ(expected_servers[i], servers[i]); + } +} + +class ConsulNamingServiceImpl : public test::UserNamingService { +public: + ConsulNamingServiceImpl() : list_names_count(0), touch_count(0) { + } + ~ConsulNamingServiceImpl() { } + void ListNames(google::protobuf::RpcController* cntl_base, + const test::HttpRequest*, + test::HttpResponse*, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = (brpc::Controller*)cntl_base; + cntl->http_response().SetHeader("X-Consul-Index", "1"); + cntl->response_attachment().append( + R"([ + { + "Node": { + "ID": "44454c4c-4e00-1050-8052-b7c04f4b5931", + "Node": "sh-qs-10.121.36.189", + "Address": "10.121.36.189", + "Datacenter": "shjj", + "TaggedAddresses": { + "lan": "10.121.36.189", + "wan": "10.121.36.189" + }, + "Meta": { + "consul-network-segment": "" + }, + "CreateIndex": 4820296, + "ModifyIndex": 4823818 + }, + "Service": { + "ID": "10.121.36.189_8003_qs_show_leaf", + "Service": "qs_show_leaf", + "Tags": ["1"], + "Address": "10.121.36.189", + "Port": 8003, + "EnableTagOverride": false, + "CreateIndex": 6515285, + "ModifyIndex": 6515285 + }, + "Checks": [ + { + "Node": "sh-qs-10.121.36.189", + "CheckID": "serfHealth", + "Name": "Serf Health Status", + "Status": "passing", + "Notes": "", + "Output": "Agent alive and reachable", + "ServiceID": "", + "ServiceName": "", + "ServiceTags": [ ], + "CreateIndex": 4820296, + "ModifyIndex": 4820296 + }, + { + "Node": "sh-qs-10.121.36.189", + "CheckID": "service:10.121.36.189_8003_qs_show_leaf", + "Name": "Service 'qs_show_leaf' check", + "Status": "passing", + "Notes": "", + "Output": "TCP connect 10.121.36.189:8003: Success", + "ServiceID": "10.121.36.189_8003_qs_show_leaf", + "ServiceName": "qs_show_leaf", + "ServiceTags": [ ], + "CreateIndex": 6515285, + "ModifyIndex": 6702198 + } + ] + }, + { + "Node": { + "ID": "44454c4c-4b00-1050-8052-b6c04f4b5931", + "Node": "sh-qs-10.121.36.190", + "Address": "10.121.36.190", + "Datacenter": "shjj", + "TaggedAddresses": { + "lan": "10.121.36.190", + "wan": "10.121.36.190" + }, + "Meta": { + "consul-network-segment": "" + }, + "CreateIndex": 4820296, + "ModifyIndex": 4823751 + }, + "Service": { + "ID": "10.121.36.190_8003_qs_show_leaf", + "Service": "qs_show_leaf", + "Tags": ["2"], + "Address": "10.121.36.190", + "Port": 8003, + "EnableTagOverride": false, + "CreateIndex": 6515635, + "ModifyIndex": 6515635 + }, + "Checks": [ + { + "Node": "sh-qs-10.121.36.190", + "CheckID": "serfHealth", + "Name": "Serf Health Status", + "Status": "passing", + "Notes": "", + "Output": "Agent alive and reachable", + "ServiceID": "", + "ServiceName": "", + "ServiceTags": [ ], + "CreateIndex": 4820296, + "ModifyIndex": 4820296 + }, + { + "Node": "sh-qs-10.121.36.190", + "CheckID": "service:10.121.36.190_8003_qs_show_leaf", + "Name": "Service 'qs_show_leaf' check", + "Status": "passing", + "Notes": "", + "Output": "TCP connect 10.121.36.190:8003: Success", + "ServiceID": "10.121.36.190_8003_qs_show_leaf", + "ServiceName": "qs_show_leaf", + "ServiceTags": [ ], + "CreateIndex": 6515635, + "ModifyIndex": 6705515 + } + ] + } + ])"); + list_names_count.fetch_add(1); + } + void Touch(google::protobuf::RpcController*, + const test::HttpRequest*, + test::HttpResponse*, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + touch_count.fetch_add(1); + } + + butil::atomic list_names_count; + butil::atomic touch_count; +}; + +TEST(NamingServiceTest, consul_with_backup_file) { + brpc::policy::FLAGS_consul_enable_degrade_to_file_naming_service = true; + const int saved_hc_interval = brpc::FLAGS_health_check_interval; + brpc::FLAGS_health_check_interval = 1; + const char *address_list[] = { + "10.127.0.1:1234", + "10.128.0.1:1234", + "10.129.0.1:1234", + }; + butil::TempFile tmp_file; + const char * service_name = tmp_file.fname(); + { + FILE* fp = fopen(tmp_file.fname(), "w"); + for (size_t i = 0; i < ARRAY_SIZE(address_list); ++i) { + ASSERT_TRUE(fprintf(fp, "%s\n", address_list[i])); + } + fclose(fp); + } + std::cout << tmp_file.fname() << std::endl; + + std::vector servers; + brpc::policy::ConsulNamingService cns; + ASSERT_EQ(0, cns.GetServers(service_name, &servers)); + ASSERT_EQ(ARRAY_SIZE(address_list), servers.size()); + for (size_t i = 0; i < ARRAY_SIZE(address_list); ++i) { + std::ostringstream oss; + oss << servers[i]; + ASSERT_EQ(address_list[i], oss.str()) << "i=" << i; + } + + brpc::Server server; + ConsulNamingServiceImpl svc; + std::string restful_map(brpc::policy::FLAGS_consul_service_discovery_url); + restful_map.append("/"); + restful_map.append(service_name); + restful_map.append(" => ListNames"); + ASSERT_EQ(0, server.AddService(&svc, + brpc::SERVER_DOESNT_OWN_SERVICE, + restful_map.c_str())); + ASSERT_EQ(0, server.Start("localhost:8500", NULL)); + + bthread_usleep(5000000); + + butil::EndPoint n1; + ASSERT_EQ(0, butil::str2endpoint("10.121.36.189:8003", &n1)); + butil::EndPoint n2; + ASSERT_EQ(0, butil::str2endpoint("10.121.36.190:8003", &n2)); + std::vector expected_servers; + expected_servers.push_back(brpc::ServerNode(n1, "1")); + expected_servers.push_back(brpc::ServerNode(n2, "2")); + std::sort(expected_servers.begin(), expected_servers.end()); + + servers.clear(); + ASSERT_EQ(0, cns.GetServers(service_name, &servers)); + ASSERT_EQ(expected_servers.size(), servers.size()); + std::sort(servers.begin(), servers.end()); + for (size_t i = 0; i < expected_servers.size(); ++i) { + ASSERT_EQ(expected_servers[i], servers[i]); + } + brpc::FLAGS_health_check_interval = saved_hc_interval; +} + + +static const std::string s_fetchs_result = R"({ + "code":0, + "message":"0", + "ttl":1, + "data":{ + "admin.test":{ + "instances":[ + { + "region":"", + "zone":"sh001", + "env":"uat", + "appid":"admin.test", + "treeid":0, + "hostname":"host123", + "http":"", + "rpc":"", + "version":"123", + "metadata":{ + "weight": "10", + "cluster": "" + }, + "addrs":[ + "http://127.0.0.1:8999", + "grpc://127.0.1.1:9000" + ], + "status":1, + "reg_timestamp":1539001034551496412, + "up_timestamp":1539001034551496412, + "renew_timestamp":1539001034551496412, + "dirty_timestamp":1539001034551496412, + "latest_timestamp":1539001034551496412 + } + ], + "zone_instances":{ + "sh001":[ + { + "region":"", + "zone":"sh001", + "env":"uat", + "appid":"admin.test", + "treeid":0, + "hostname":"host123", + "http":"", + "rpc":"", + "version":"123", + "metadata":{ + "weight": "10", + "cluster": "" + }, + "addrs":[ + "http://127.0.0.1:8999", + "grpc://127.0.1.1:9000" + ], + "status":1, + "reg_timestamp":1539001034551496412, + "up_timestamp":1539001034551496412, + "renew_timestamp":1539001034551496412, + "dirty_timestamp":1539001034551496412, + "latest_timestamp":1539001034551496412 + } + ] + }, + "latest_timestamp":1539001034551496412, + "latest_timestamp_str":"1539001034" + } + } +})"; + +static std::string s_nodes_result = R"({ + "code": 0, + "message": "0", + "ttl": 1, + "data": [ + { + "addr": "127.0.0.1:8635", + "status": 0, + "zone": "" + }, { + "addr": "172.18.33.51:7171", + "status": 0, + "zone": "" + }, { + "addr": "172.18.33.52:7171", + "status": 0, + "zone": "" + } + ] +})"; + + +class DiscoveryNamingServiceImpl : public test::DiscoveryNamingService { +public: + DiscoveryNamingServiceImpl() + : _renew_count(0) + , _cancel_count(0) {} + virtual ~DiscoveryNamingServiceImpl() {} + + void Nodes(google::protobuf::RpcController* cntl_base, + const test::HttpRequest*, + test::HttpResponse*, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = static_cast(cntl_base); + cntl->response_attachment().append(s_nodes_result); + } + + void Fetchs(google::protobuf::RpcController* cntl_base, + const test::HttpRequest*, + test::HttpResponse*, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = static_cast(cntl_base); + cntl->response_attachment().append(s_fetchs_result); + } + + void Register(google::protobuf::RpcController* cntl_base, + const test::HttpRequest*, + test::HttpResponse*, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = static_cast(cntl_base); + auto body = cntl->request_attachment().to_string(); + for (brpc::QuerySplitter sp(body); sp; ++sp) { + if (sp.key() == "addrs") { + _addrs.insert(sp.value().as_string()); + } + } + cntl->response_attachment().append(R"({ + "code": 0, + "message": "0" + })"); + return; + } + + void Renew(google::protobuf::RpcController* cntl_base, + const test::HttpRequest*, + test::HttpResponse*, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = static_cast(cntl_base); + cntl->response_attachment().append(R"({ + "code": 0, + "message": "0" + })"); + _renew_count++; + return; + } + + void Cancel(google::protobuf::RpcController* cntl_base, + const test::HttpRequest*, + test::HttpResponse*, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = static_cast(cntl_base); + cntl->response_attachment().append(R"({ + "code": 0, + "message": "0" + })"); + _cancel_count++; + _addrs.clear(); + return; + } + + int RenewCount() const { return _renew_count; } + int CancelCount() const { return _cancel_count; } + + bool HasAddr(const std::string& addr) const { + return _addrs.find(addr) != _addrs.end(); + } + int AddrCount() const { return _addrs.size(); } + +private: + int _renew_count; + int _cancel_count; + + std::set _addrs; +}; + +TEST(NamingServiceTest, discovery_sanity) { + brpc::policy::FLAGS_discovery_api_addr = "http://127.0.0.1:8635/discovery/nodes"; + brpc::policy::FLAGS_discovery_renew_interval_s = 1; + brpc::Server server; + DiscoveryNamingServiceImpl svc; + std::string rest_mapping = + "/discovery/nodes => Nodes, " + "/discovery/fetchs => Fetchs, " + "/discovery/register => Register, " + "/discovery/renew => Renew, " + "/discovery/cancel => Cancel"; + ASSERT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE, + rest_mapping.c_str())); + ASSERT_EQ(0, server.Start("localhost:8635", NULL)); + + brpc::policy::DiscoveryNamingService dcns; + std::vector servers; + ASSERT_EQ(0, dcns.GetServers("admin.test", &servers)); + ASSERT_EQ((size_t)1, servers.size()); + + brpc::policy::DiscoveryRegisterParam dparam; + dparam.appid = "main.test"; + dparam.hostname = "hostname"; + dparam.addrs = "grpc://10.0.0.1:8000"; + dparam.env = "dev"; + dparam.zone = "sh001"; + dparam.status = 1; + dparam.version = "v1"; + { + brpc::policy::DiscoveryClient dc; + } + // Cancel is called iff Register is called + ASSERT_EQ(svc.CancelCount(), 0); + { + brpc::policy::DiscoveryClient dc; + // Two Register should start one Renew task , and make + // svc.RenewCount() be one. + ASSERT_EQ(0, dc.Register(dparam)); + ASSERT_EQ(0, dc.Register(dparam)); + bthread_usleep(100000); + ASSERT_TRUE(svc.HasAddr("grpc://10.0.0.1:8000")); + ASSERT_FALSE(svc.HasAddr("http://10.0.0.1:8000")); + } + ASSERT_EQ(svc.RenewCount(), 1); + ASSERT_EQ(svc.CancelCount(), 1); + + ASSERT_FALSE(svc.HasAddr("grpc://10.0.0.1:8000")); + ASSERT_FALSE(svc.HasAddr("http://10.0.0.1:8000")); + + // addrs splitted by `,' + dparam.addrs = ",grpc://10.0.0.1:8000,,http://10.0.0.1:8000,"; + { + brpc::policy::DiscoveryClient dc; + ASSERT_EQ(0, dc.Register(dparam)); + ASSERT_TRUE(svc.HasAddr("grpc://10.0.0.1:8000")); + ASSERT_TRUE(svc.HasAddr("http://10.0.0.1:8000")); + ASSERT_FALSE(svc.HasAddr(std::string())); + ASSERT_EQ(2, svc.AddrCount()); + } +} + +class NacosNamingServiceImpl : public test::NacosNamingService { +public: + void Login(google::protobuf::RpcController* cntl_base, + const test::HttpRequest*, test::HttpResponse*, + google::protobuf::Closure* done) override { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = static_cast(cntl_base); + + butil::StringPairs user; + butil::SplitStringIntoKeyValuePairs( + cntl->request_attachment().to_string(), '=', '&', &user); + + const auto expected_user = + butil::StringPairs{{"username", "nacos"}, {"password", "nacos"}}; + + if (user == expected_user) { + cntl->http_response().set_content_type("application/json"); + cntl->response_attachment().append( +R"({ + "accessToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJuYWNvcyIsImV4cCI6MTY2MzAwODMzNn0.YKJJwzHT4v9cpC7kVqWroeJK1WioOYe0JZy4KX8nExs", + "tokenTtl": 18000, + "globalAdmin": true, + "username": "nacos" + })"); + } else { + cntl->http_response().set_status_code(brpc::HTTP_STATUS_FORBIDDEN); + cntl->response_attachment().append("unknow user!"); + } + } + + void List(google::protobuf::RpcController* cntl_base, + const test::HttpRequest*, test::HttpResponse*, + google::protobuf::Closure* done) override { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = (brpc::Controller*)cntl_base; + + auto token = cntl->http_request().uri().GetQuery("accessToken"); + if (token == nullptr || + *token != + "eyJhbGciOiJIUzI1NiJ9." + "eyJzdWIiOiJuYWNvcyIsImV4cCI6MTY2MzAwODMzNn0." + "YKJJwzHT4v9cpC7kVqWroeJK1WioOYe0JZy4KX8nExs") { + cntl->http_response().set_status_code(brpc::HTTP_STATUS_FORBIDDEN); + cntl->response_attachment().append( +R"({ + "timestamp": "2022-09-12T22:56:02.730+08:00", + "status": 403, + "error": "Forbidden", + "path": "/nacos/v1/ns/instance/list" + })"); + return; + } + + auto service_name = cntl->http_request().uri().GetQuery("serviceName"); + auto group_name = cntl->http_request().uri().GetQuery("groupName"); + auto namespace_id = cntl->http_request().uri().GetQuery("namespaceId"); + auto clusters = cntl->http_request().uri().GetQuery("clusters"); + if (service_name == nullptr || *service_name != "test" || + group_name == nullptr || *group_name != "g1" || + namespace_id == nullptr || *namespace_id != "n1" || + clusters == nullptr || *clusters != "wx") { + cntl->http_response().set_status_code(brpc::HTTP_STATUS_NOT_FOUND); + return; + } + + cntl->http_response().set_content_type("application/json"); + cntl->response_attachment().append( +R"({ + "name": "g1@@test", + "groupName": "g1", + "clusters": "wx", + "cacheMillis": 10000, + "hosts": + [ + { + "instanceId": "127.0.0.1#8888#wx#g1@@test", + "ip": "127.0.0.1", + "port": 8888, + "weight": 10.0, + "healthy": true, + "enabled": true, + "ephemeral": true, + "clusterName": "wx", + "serviceName": "g1@@test", + "metadata": {}, + "instanceHeartBeatInterval": 5000, + "instanceHeartBeatTimeOut": 15000, + "ipDeleteTimeout": 30000, + "instanceIdGenerator": "simple" + } + ], + "lastRefTime": 1662990336712, + "checksum": "", + "allIPs": false, + "reachProtectionThreshold": false, + "valid": true + })"); + } +}; + +TEST(NamingServiceTest, nacos) { + brpc::Server server; + NacosNamingServiceImpl svc; + ASSERT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE, + "/nacos/v1/auth/login => Login, " + "/nacos/v1/ns/instance/list => List")); + ASSERT_EQ(0, server.Start("localhost:8848", nullptr)); + + bthread_usleep(5000000); + + butil::EndPoint ep; + ASSERT_EQ(0, butil::str2endpoint("127.0.0.1:8888", &ep)); + const auto expected_node = brpc::ServerNode(ep, "10"); + + const char* service_name = + "serviceName=test&groupName=g1&namespaceId=n1&clusters=wx"; + brpc::policy::FLAGS_nacos_address = "http://localhost:8848"; + brpc::policy::FLAGS_nacos_username = "nacos"; + brpc::policy::FLAGS_nacos_password = "nacos"; + + { + brpc::policy::NacosNamingService nns; + std::vector nodes; + ASSERT_EQ(0, nns.GetServers(service_name, &nodes)); + ASSERT_EQ(nodes.size(), 1); + ASSERT_EQ(expected_node, nodes[0]); + } + { + brpc::policy::FLAGS_nacos_password = "invalid_password"; + brpc::policy::NacosNamingService nns; + std::vector nodes; + ASSERT_NE(0, nns.GetServers(service_name, &nodes)); + } +} + +} //namespace diff --git a/test/brpc_nova_pbrpc_protocol_unittest.cpp b/test/brpc_nova_pbrpc_protocol_unittest.cpp new file mode 100644 index 0000000..cfc41bd --- /dev/null +++ b/test/brpc_nova_pbrpc_protocol_unittest.cpp @@ -0,0 +1,278 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: Sun Jul 13 15:04:18 CST 2014 + +#include +#include +#include +#include +#include +#include +#include "butil/time.h" +#include "butil/macros.h" +#include "brpc/socket.h" +#include "brpc/acceptor.h" +#include "brpc/server.h" +#include "brpc/policy/nova_pbrpc_protocol.h" +#include "brpc/policy/most_common_message.h" +#include "brpc/controller.h" +#include "echo.pb.h" + +namespace { + +static const std::string EXP_REQUEST = "hello"; +static const std::string EXP_RESPONSE = "world"; + +static const std::string MOCK_CREDENTIAL = "mock credential"; +static const std::string MOCK_USER = "mock user"; + +class MyAuthenticator : public brpc::Authenticator { +public: + MyAuthenticator() {} + + int GenerateCredential(std::string* auth_str) const { + *auth_str = MOCK_CREDENTIAL; + return 0; + } + + int VerifyCredential(const std::string& auth_str, + const butil::EndPoint&, + brpc::AuthContext* ctx) const { + EXPECT_EQ(MOCK_CREDENTIAL, auth_str); + ctx->set_user(MOCK_USER); + return 0; + } +}; + +class MyEchoService : public ::test::EchoService { + void Echo(::google::protobuf::RpcController* cntl_base, + const ::test::EchoRequest* req, + ::test::EchoResponse* res, + ::google::protobuf::Closure* done) { + brpc::Controller* cntl = + static_cast(cntl_base); + brpc::ClosureGuard done_guard(done); + + if (req->close_fd()) { + cntl->CloseConnection("Close connection according to request"); + return; + } + EXPECT_EQ(EXP_REQUEST, req->message()); + res->set_message(EXP_RESPONSE); + } +}; + +class NovaTest : public ::testing::Test{ +protected: + NovaTest() { + EXPECT_EQ(0, _server.AddService( + &_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + // Hack: Regard `_server' as running + _server._status = brpc::Server::RUNNING; + _server._options.nshead_service = new brpc::policy::NovaServiceAdaptor; + // Nova doesn't support authentication + // _server._options.auth = &_auth; + + EXPECT_EQ(0, pipe(_pipe_fds)); + + brpc::SocketId id; + brpc::SocketOptions options; + options.fd = _pipe_fds[1]; + EXPECT_EQ(0, brpc::Socket::Create(options, &id)); + EXPECT_EQ(0, brpc::Socket::Address(id, &_socket)); + }; + + virtual ~NovaTest() {}; + virtual void SetUp() {}; + virtual void TearDown() {}; + + void VerifyMessage(brpc::InputMessageBase* msg) { + if (msg->_socket == NULL) { + _socket->ReAddress(&msg->_socket); + } + msg->_arg = &_server; + EXPECT_TRUE(brpc::policy::VerifyNsheadRequest(msg)); + } + + void ProcessMessage(void (*process)(brpc::InputMessageBase*), + brpc::InputMessageBase* msg, bool set_eof) { + if (msg->_socket == NULL) { + _socket->ReAddress(&msg->_socket); + } + msg->_arg = &_server; + _socket->PostponeEOF(); + if (set_eof) { + _socket->SetEOF(); + } + (*process)(msg); + } + + brpc::policy::MostCommonMessage* MakeRequestMessage( + const brpc::nshead_t& head) { + brpc::policy::MostCommonMessage* msg = + brpc::policy::MostCommonMessage::Get(); + msg->meta.append(&head, sizeof(head)); + + test::EchoRequest req; + req.set_message(EXP_REQUEST); + butil::IOBufAsZeroCopyOutputStream req_stream(&msg->payload); + EXPECT_TRUE(req.SerializeToZeroCopyStream(&req_stream)); + return msg; + } + + brpc::policy::MostCommonMessage* MakeResponseMessage() { + brpc::policy::MostCommonMessage* msg = + brpc::policy::MostCommonMessage::Get(); + brpc::nshead_t head; + memset(&head, 0, sizeof(head)); + msg->meta.append(&head, sizeof(head)); + + test::EchoResponse res; + res.set_message(EXP_RESPONSE); + butil::IOBufAsZeroCopyOutputStream res_stream(&msg->payload); + EXPECT_TRUE(res.SerializeToZeroCopyStream(&res_stream)); + return msg; + } + + void CheckEmptyResponse() { + int bytes_in_pipe = 0; + ioctl(_pipe_fds[0], FIONREAD, &bytes_in_pipe); + EXPECT_EQ(0, bytes_in_pipe); + } + + int _pipe_fds[2]; + brpc::SocketUniquePtr _socket; + brpc::Server _server; + + MyEchoService _svc; + MyAuthenticator _auth; +}; + +TEST_F(NovaTest, process_request_failed_socket) { + brpc::nshead_t head; + memset(&head, 0, sizeof(head)); + brpc::policy::MostCommonMessage* msg = MakeRequestMessage(head); + _socket->SetFailed(); + ProcessMessage(brpc::policy::ProcessNsheadRequest, msg, false); + ASSERT_EQ(0ll, _server._nerror_bvar.get_value()); + CheckEmptyResponse(); +} + +TEST_F(NovaTest, process_request_logoff) { + brpc::nshead_t head; + head.reserved = 0; + brpc::policy::MostCommonMessage* msg = MakeRequestMessage(head); + _server._status = brpc::Server::READY; + ProcessMessage(brpc::policy::ProcessNsheadRequest, msg, false); + ASSERT_EQ(1ll, _server._nerror_bvar.get_value()); + ASSERT_TRUE(_socket->Failed()); + CheckEmptyResponse(); +} + +TEST_F(NovaTest, process_request_wrong_method) { + brpc::nshead_t head; + head.reserved = 10; + brpc::policy::MostCommonMessage* msg = MakeRequestMessage(head); + ProcessMessage(brpc::policy::ProcessNsheadRequest, msg, false); + ASSERT_EQ(1ll, _server._nerror_bvar.get_value()); + ASSERT_TRUE(_socket->Failed()); + CheckEmptyResponse(); +} + +TEST_F(NovaTest, process_response_after_eof) { + test::EchoResponse res; + brpc::Controller cntl; + cntl._response = &res; + brpc::policy::MostCommonMessage* msg = MakeResponseMessage(); + _socket->set_correlation_id(cntl.call_id().value); + ProcessMessage(brpc::policy::ProcessNovaResponse, msg, true); + ASSERT_EQ(EXP_RESPONSE, res.message()); + ASSERT_TRUE(_socket->Failed()); +} + +TEST_F(NovaTest, complete_flow) { + butil::IOBuf request_buf; + butil::IOBuf total_buf; + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + cntl._response = &res; + cntl._connection_type = brpc::CONNECTION_TYPE_SHORT; + ASSERT_EQ(0, brpc::Socket::Address(_socket->id(), &cntl._current_call.sending_sock)); + + // Send request + req.set_message(EXP_REQUEST); + brpc::SerializeRequestDefault(&request_buf, &cntl, &req); + ASSERT_FALSE(cntl.Failed()); + brpc::policy::PackNovaRequest( + &total_buf, NULL, cntl.call_id().value, + test::EchoService::descriptor()->method(0), &cntl, request_buf, &_auth); + ASSERT_FALSE(cntl.Failed()); + + // Verify and handle request + brpc::ParseResult req_pr = + brpc::policy::ParseNsheadMessage(&total_buf, NULL, false, NULL); + ASSERT_EQ(brpc::PARSE_OK, req_pr.error()); + brpc::InputMessageBase* req_msg = req_pr.message(); + VerifyMessage(req_msg); + ProcessMessage(brpc::policy::ProcessNsheadRequest, req_msg, false); + + // Read response from pipe + butil::IOPortal response_buf; + response_buf.append_from_file_descriptor(_pipe_fds[0], 1024); + brpc::ParseResult res_pr = + brpc::policy::ParseNsheadMessage(&response_buf, NULL, false, NULL); + ASSERT_EQ(brpc::PARSE_OK, res_pr.error()); + brpc::InputMessageBase* res_msg = res_pr.message(); + ProcessMessage(brpc::policy::ProcessNovaResponse, res_msg, false); + + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(EXP_RESPONSE, res.message()); +} + +TEST_F(NovaTest, close_in_callback) { + butil::IOBuf request_buf; + butil::IOBuf total_buf; + brpc::Controller cntl; + test::EchoRequest req; + cntl._connection_type = brpc::CONNECTION_TYPE_SHORT; + ASSERT_EQ(0, brpc::Socket::Address(_socket->id(), &cntl._current_call.sending_sock)); + + // Send request + req.set_message(EXP_REQUEST); + req.set_close_fd(true); + brpc::SerializeRequestDefault(&request_buf, &cntl, &req); + ASSERT_FALSE(cntl.Failed()); + brpc::policy::PackNovaRequest( + &total_buf, NULL, cntl.call_id().value, + test::EchoService::descriptor()->method(0), &cntl, request_buf, &_auth); + ASSERT_FALSE(cntl.Failed()); + + // Handle request + brpc::ParseResult req_pr = + brpc::policy::ParseNsheadMessage(&total_buf, NULL, false, NULL); + ASSERT_EQ(brpc::PARSE_OK, req_pr.error()); + brpc::InputMessageBase* req_msg = req_pr.message(); + ProcessMessage(brpc::policy::ProcessNsheadRequest, req_msg, false); + + // Socket should be closed + ASSERT_TRUE(_socket->Failed()); +} +} //namespace diff --git a/test/brpc_p2c_ewma_load_balancer_unittest.cpp b/test/brpc_p2c_ewma_load_balancer_unittest.cpp new file mode 100644 index 0000000..922bfc3 --- /dev/null +++ b/test/brpc_p2c_ewma_load_balancer_unittest.cpp @@ -0,0 +1,428 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include +#include +#include +#include +#include "butil/macros.h" +#include "butil/time.h" +#include "brpc/socket.h" +#include "brpc/controller.h" +#include "brpc/excluded_servers.h" +#include "brpc/policy/p2c_ewma_load_balancer.h" + +namespace brpc { +namespace policy { +DECLARE_int64(p2c_max_punish_ms); +} +} + +namespace { + +butil::atomic nrecycle(0); +class SaveRecycle : public brpc::SocketUser { + void BeforeRecycle(brpc::Socket* s) { + nrecycle.fetch_add(1, butil::memory_order_relaxed); + delete this; + } +}; + +brpc::ServerId CreateServer(const char* addr, const char* tag = "") { + butil::EndPoint point; + EXPECT_EQ(0, str2endpoint(addr, &point)); + brpc::ServerId id(8888); + brpc::SocketOptions options; + options.remote_side = point; + options.user = new SaveRecycle; + EXPECT_EQ(0, brpc::Socket::Create(options, &id.id)); + id.tag = tag; + return id; +} + +// Report a call that took `latency_us' back to the load balancer. +void FeedbackLatency(brpc::LoadBalancer* lb, brpc::SocketId server_id, + int64_t latency_us, int error_code = 0, + const brpc::Controller* cntl = NULL) { + brpc::LoadBalancer::CallInfo info; + info.begin_time_us = butil::gettimeofday_us() - latency_us; + info.server_id = server_id; + info.error_code = error_code; + info.controller = cntl; + lb->Feedback(info); +} + +class P2CEwmaLoadBalancerTest : public ::testing::Test { +protected: + void SetUp() override { + _lb = new brpc::policy::P2CEwmaLoadBalancer; + } + void TearDown() override { + _lb->Destroy(); + } + + int Select(brpc::SocketUniquePtr* ptr, bool* need_feedback = NULL, + const brpc::ExcludedServers* excluded = NULL) { + brpc::LoadBalancer::SelectIn in = { + butil::gettimeofday_us(), true, false, 0u, excluded }; + brpc::LoadBalancer::SelectOut out(ptr); + const int rc = _lb->SelectServer(in, &out); + if (need_feedback != NULL) { + *need_feedback = out.need_feedback; + } + return rc; + } + + brpc::policy::P2CEwmaLoadBalancer* _lb; +}; + +TEST_F(P2CEwmaLoadBalancerTest, add_remove_servers) { + brpc::SocketUniquePtr ptr; + ASSERT_EQ(ENODATA, Select(&ptr)); + + std::vector ids; + ids.push_back(CreateServer("127.0.0.1:7777")); + ids.push_back(CreateServer("127.0.0.1:7778")); + ids.push_back(CreateServer("127.0.0.1:7779")); + + ASSERT_TRUE(_lb->AddServer(ids[0])); + // Duplicated server is rejected. + ASSERT_FALSE(_lb->AddServer(ids[0])); + ASSERT_EQ(2u, _lb->AddServersInBatch( + std::vector(ids.begin() + 1, ids.end()))); + + ASSERT_EQ(0, Select(&ptr)); + + ASSERT_TRUE(_lb->RemoveServer(ids[0])); + ASSERT_FALSE(_lb->RemoveServer(ids[0])); + ASSERT_EQ(2u, _lb->RemoveServersInBatch( + std::vector(ids.begin() + 1, ids.end()))); + ASSERT_EQ(ENODATA, Select(&ptr)); +} + +TEST_F(P2CEwmaLoadBalancerTest, single_server) { + const brpc::ServerId id = CreateServer("127.0.0.1:7780"); + ASSERT_TRUE(_lb->AddServer(id)); + for (int i = 0; i < 10; ++i) { + brpc::SocketUniquePtr ptr; + bool need_feedback = false; + ASSERT_EQ(0, Select(&ptr, &need_feedback)); + ASSERT_EQ(id.id, ptr->id()); + ASSERT_TRUE(need_feedback); + FeedbackLatency(_lb, id.id, 1000); + } +} + +TEST_F(P2CEwmaLoadBalancerTest, prefers_lower_latency) { + const brpc::ServerId fast = CreateServer("127.0.0.1:7781"); + const brpc::ServerId slow = CreateServer("127.0.0.1:7782"); + ASSERT_TRUE(_lb->AddServer(fast)); + ASSERT_TRUE(_lb->AddServer(slow)); + + // Servers respond with their characteristic latency; both get observed + // within the first rounds because unobserved servers score best. + std::map counts; + const int kRounds = 1000; + for (int i = 0; i < kRounds; ++i) { + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, Select(&ptr)); + ++counts[ptr->id()]; + // Selected server responds with its own characteristic latency. + FeedbackLatency(_lb, ptr->id(), ptr->id() == fast.id ? 1000 : 50000); + } + LOG(INFO) << "fast=" << counts[fast.id] << " slow=" << counts[slow.id]; + // The fast server should receive almost all traffic. + ASSERT_GE(counts[fast.id], (size_t)(0.9 * kRounds)); +} + +TEST_F(P2CEwmaLoadBalancerTest, sheds_degraded_server) { + const brpc::ServerId a = CreateServer("127.0.0.1:7783"); + const brpc::ServerId b = CreateServer("127.0.0.1:7784"); + ASSERT_TRUE(_lb->AddServer(a)); + ASSERT_TRUE(_lb->AddServer(b)); + // Warm up with `b' slightly faster so it is the preferred server. + for (int i = 0; i < 20; ++i) { + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, Select(&ptr)); + FeedbackLatency(_lb, ptr->id(), ptr->id() == a.id ? 1000 : 500); + } + + // `b' degrades: its first slow response must shift traffic to `a' + // immediately(peak-sensitivity), long before an averaging window would. + std::map counts; + for (int i = 0; i < 100; ++i) { + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, Select(&ptr)); + ++counts[ptr->id()]; + FeedbackLatency(_lb, ptr->id(), ptr->id() == a.id ? 1000 : 100000); + } + ASSERT_GE(counts[a.id], 90u); +} + +TEST_F(P2CEwmaLoadBalancerTest, inflight_breaks_ties) { + const brpc::ServerId a = CreateServer("127.0.0.1:7785"); + const brpc::ServerId b = CreateServer("127.0.0.1:7786"); + ASSERT_TRUE(_lb->AddServer(a)); + ASSERT_TRUE(_lb->AddServer(b)); + + // With no latency observations, scores only differ by in-flight count, + // so two selections without feedback must go to different servers. + brpc::SocketUniquePtr ptr1; + brpc::SocketUniquePtr ptr2; + ASSERT_EQ(0, Select(&ptr1)); + ASSERT_EQ(0, Select(&ptr2)); + ASSERT_NE(ptr1->id(), ptr2->id()); + + // After feedback(decrementing in-flight), both are tied again and the + // third selection must not crash or fail. + FeedbackLatency(_lb, ptr1->id(), 1000); + FeedbackLatency(_lb, ptr2->id(), 1000); + brpc::SocketUniquePtr ptr3; + ASSERT_EQ(0, Select(&ptr3)); +} + +TEST_F(P2CEwmaLoadBalancerTest, weighted_split_by_inflight) { + // With equal latency, steady-state in-flight counts converge to the + // 1:2:4 weight ratio because the score is (inflight+1)/weight. + const brpc::ServerId w1 = CreateServer("127.0.0.1:7787", "1"); + const brpc::ServerId w2 = CreateServer("127.0.0.1:7788", "2"); + const brpc::ServerId w4 = CreateServer("127.0.0.1:7789", "4"); + brpc::policy::P2CEwmaLoadBalancer* lb = + _lb->New(butil::StringPiece("choices=3")); + ASSERT_TRUE(lb != NULL); + ASSERT_TRUE(lb->AddServer(w1)); + ASSERT_TRUE(lb->AddServer(w2)); + ASSERT_TRUE(lb->AddServer(w4)); + + std::map counts; + const int kRounds = 700; + for (int i = 0; i < kRounds; ++i) { + brpc::SocketUniquePtr ptr; + brpc::LoadBalancer::SelectIn in = { + butil::gettimeofday_us(), true, false, 0u, NULL }; + brpc::LoadBalancer::SelectOut out(&ptr); + ASSERT_EQ(0, lb->SelectServer(in, &out)); + ++counts[ptr->id()]; + // No feedback: requests stay in flight. + } + LOG(INFO) << "w1=" << counts[w1.id] << " w2=" << counts[w2.id] + << " w4=" << counts[w4.id]; + const double share1 = counts[w1.id] / (double)kRounds; + const double share2 = counts[w2.id] / (double)kRounds; + const double share4 = counts[w4.id] / (double)kRounds; + ASSERT_NEAR(share1, 1.0 / 7, 0.05); + ASSERT_NEAR(share2, 2.0 / 7, 0.05); + ASSERT_NEAR(share4, 4.0 / 7, 0.05); + lb->Destroy(); +} + +TEST_F(P2CEwmaLoadBalancerTest, error_feedback_punishes_server) { + const brpc::ServerId good = CreateServer("127.0.0.1:7790"); + const brpc::ServerId bad = CreateServer("127.0.0.1:7791"); + ASSERT_TRUE(_lb->AddServer(good)); + ASSERT_TRUE(_lb->AddServer(bad)); + // Warm up with `bad' slightly faster so it is the preferred server. + for (int i = 0; i < 20; ++i) { + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, Select(&ptr)); + FeedbackLatency(_lb, ptr->id(), ptr->id() == good.id ? 1000 : 500); + } + + // `bad' starts failing fast(1ms), but failures are punished with at + // least the RPC timeout so it keeps losing selections. + brpc::Controller cntl; + cntl.set_timeout_ms(100); + std::map counts; + for (int i = 0; i < 100; ++i) { + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, Select(&ptr)); + ++counts[ptr->id()]; + if (ptr->id() == good.id) { + FeedbackLatency(_lb, good.id, 1000); + } else { + FeedbackLatency(_lb, bad.id, 1000, ETIMEDOUT, &cntl); + } + } + ASSERT_GE(counts[good.id], 90u); +} + +TEST_F(P2CEwmaLoadBalancerTest, excluded_servers) { + const brpc::ServerId a = CreateServer("127.0.0.1:7792"); + const brpc::ServerId b = CreateServer("127.0.0.1:7793"); + ASSERT_TRUE(_lb->AddServer(a)); + ASSERT_TRUE(_lb->AddServer(b)); + + brpc::ExcludedServers* excluded = brpc::ExcludedServers::Create(2); + excluded->Add(a.id); + for (int i = 0; i < 20; ++i) { + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, Select(&ptr, NULL, excluded)); + ASSERT_EQ(b.id, ptr->id()); + FeedbackLatency(_lb, b.id, 1000); + } + // All servers excluded: still take the last chance instead of failing. + excluded->Add(b.id); + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, Select(&ptr, NULL, excluded)); + brpc::ExcludedServers::Destroy(excluded); +} + +TEST_F(P2CEwmaLoadBalancerTest, invalid_parameters) { + brpc::LoadBalancer* lb = _lb->New(butil::StringPiece("")); + ASSERT_TRUE(lb != NULL); + lb->Destroy(); + lb = _lb->New(butil::StringPiece("choices=4 tau_ms=5000")); + ASSERT_TRUE(lb != NULL); + lb->Destroy(); + ASSERT_TRUE(_lb->New(butil::StringPiece("choices=1")) == NULL); + ASSERT_TRUE(_lb->New(butil::StringPiece("choices=abc")) == NULL); + ASSERT_TRUE(_lb->New(butil::StringPiece("tau_ms=0")) == NULL); + ASSERT_TRUE(_lb->New(butil::StringPiece("unknown=1")) == NULL); +} + +struct ChurnArg { + brpc::policy::P2CEwmaLoadBalancer* lb; + butil::atomic stop; + butil::atomic nselected; +}; + +void* SelectAndFeedback(void* void_arg) { + ChurnArg* arg = static_cast(void_arg); + while (!arg->stop.load(butil::memory_order_relaxed)) { + brpc::SocketUniquePtr ptr; + brpc::LoadBalancer::SelectIn in = { + butil::gettimeofday_us(), true, false, 0u, NULL }; + brpc::LoadBalancer::SelectOut out(&ptr); + if (arg->lb->SelectServer(in, &out) == 0) { + arg->nselected.fetch_add(1, butil::memory_order_relaxed); + if (out.need_feedback) { + FeedbackLatency(arg->lb, ptr->id(), 1000); + } + } + } + return NULL; +} + +TEST_F(P2CEwmaLoadBalancerTest, concurrent_select_with_churn) { + std::vector ids; + for (int i = 0; i < 8; ++i) { + char addr[32]; + snprintf(addr, sizeof(addr), "127.0.0.1:%d", 7800 + i); + ids.push_back(CreateServer(addr)); + ASSERT_TRUE(_lb->AddServer(ids.back())); + } + + ChurnArg arg; + arg.lb = _lb; + arg.stop.store(false); + arg.nselected.store(0); + pthread_t threads[4]; + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + ASSERT_EQ(0, pthread_create( + &threads[i], NULL, SelectAndFeedback, &arg)); + } + // Churn membership while selections are running. + const int64_t stop_at_us = butil::gettimeofday_us() + 1000000L; + while (butil::gettimeofday_us() < stop_at_us) { + ASSERT_TRUE(_lb->RemoveServer(ids[0])); + ASSERT_TRUE(_lb->AddServer(ids[0])); + } + arg.stop.store(true); + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + ASSERT_EQ(0, pthread_join(threads[i], NULL)); + } + LOG(INFO) << "selected " << arg.nselected.load() << " times"; + ASSERT_GT(arg.nselected.load(), 0u); +} + +TEST_F(P2CEwmaLoadBalancerTest, error_punish_is_capped) { + const brpc::ServerId id = CreateServer("127.0.0.1:7900"); + ASSERT_TRUE(_lb->AddServer(id)); + brpc::Controller cntl; + cntl.set_timeout_ms(100); + // Persistent failures double the punished EWMA per sample; without the + // cap 64 rounds would push it past 2^64. + for (int i = 0; i < 64; ++i) { + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, Select(&ptr)); + FeedbackLatency(_lb, id.id, 1000, ETIMEDOUT, &cntl); + } + std::ostringstream os; + brpc::DescribeOptions options; + options.verbose = true; + _lb->Describe(os, options); + const std::string desc = os.str(); + const size_t pos = desc.find("ewma_us="); + ASSERT_NE(std::string::npos, pos) << desc; + const int64_t ewma_us = strtoll(desc.c_str() + pos + 8, NULL, 10); + ASSERT_LE(ewma_us, brpc::policy::FLAGS_p2c_max_punish_ms * 1000L) << desc; +} + +struct FeedbackBenchArg { + brpc::policy::P2CEwmaLoadBalancer* lb; + brpc::SocketId server_id; + butil::atomic stop; + butil::atomic nfeedback; +}; + +void* FeedbackHammer(void* void_arg) { + FeedbackBenchArg* arg = static_cast(void_arg); + size_t n = 0; + while (!arg->stop.load(butil::memory_order_relaxed)) { + FeedbackLatency(arg->lb, arg->server_id, 1000); + ++n; + } + arg->nfeedback.fetch_add(n, butil::memory_order_relaxed); + return NULL; +} + +TEST_F(P2CEwmaLoadBalancerTest, feedback_lock_overhead) { + // All threads feed back to a single server so update_mutex sees + // worst-case contention. + const brpc::ServerId id = CreateServer("127.0.0.1:7901"); + ASSERT_TRUE(_lb->AddServer(id)); + for (size_t nthread = 1; nthread <= 4; nthread *= 2) { + FeedbackBenchArg arg; + arg.lb = _lb; + arg.server_id = id.id; + arg.stop.store(false); + arg.nfeedback.store(0); + pthread_t threads[4]; + const int64_t begin_us = butil::gettimeofday_us(); + for (size_t i = 0; i < nthread; ++i) { + ASSERT_EQ(0, pthread_create( + &threads[i], NULL, FeedbackHammer, &arg)); + } + usleep(500 * 1000); + arg.stop.store(true); + for (size_t i = 0; i < nthread; ++i) { + ASSERT_EQ(0, pthread_join(threads[i], NULL)); + } + const int64_t elapsed_us = butil::gettimeofday_us() - begin_us; + const size_t n = arg.nfeedback.load(); + ASSERT_GT(n, 0u); + LOG(INFO) << "Feedback on 1 shared server, " << nthread + << " thread(s): " << (elapsed_us * 1000L * nthread) / n + << "ns/op (" << n << " ops in " << elapsed_us / 1000 << "ms)"; + } +} + +} // namespace diff --git a/test/brpc_prometheus_metrics_service_unittest.cpp b/test/brpc_prometheus_metrics_service_unittest.cpp new file mode 100644 index 0000000..b5b0bc1 --- /dev/null +++ b/test/brpc_prometheus_metrics_service_unittest.cpp @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2023/05/06 15:10:00 + +#include + +#include "butil/strings/string_piece.h" +#include "butil/iobuf.h" +#include "brpc/builtin/prometheus_metrics_service.h" + +namespace { + +class PrometheusMetricsDumperTest : public testing::Test { +protected: + void SetUp() {} + void TearDown() {} +}; + +TEST_F(PrometheusMetricsDumperTest, GetMetricsName) { + EXPECT_EQ("", brpc::GetMetricsName("")); + + EXPECT_EQ("commit_count", brpc::GetMetricsName("commit_count")); + + EXPECT_EQ("commit_count", brpc::GetMetricsName("commit_count{region=\"1000\"}")); +} + +} diff --git a/test/brpc_prometheus_metrics_unittest.cpp b/test/brpc_prometheus_metrics_unittest.cpp new file mode 100644 index 0000000..471fd40 --- /dev/null +++ b/test/brpc_prometheus_metrics_unittest.cpp @@ -0,0 +1,197 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +#include +#include "brpc/server.h" +#include "brpc/channel.h" +#include "brpc/controller.h" +#include "butil/strings/string_piece.h" +#include "echo.pb.h" +#include "bvar/multi_dimension.h" + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + +class DummyEchoServiceImpl : public test::EchoService { +public: + virtual ~DummyEchoServiceImpl() {} + virtual void Echo(google::protobuf::RpcController* cntl_base, + const test::EchoRequest* request, + test::EchoResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + return; + } +}; + +enum STATE { + HELP = 0, + TYPE, + GAUGE, + SUMMARY, + COUNTER, + // When meets a line with a gauge/counter with labels, we have no + // idea the next line is a new HELP or the same gauge/counter just + // with different labels + HELP_OR_GAUGE, + HELP_OR_COUNTER, +}; + +TEST(PrometheusMetrics, sanity) { + brpc::Server server; + DummyEchoServiceImpl echo_svc; + ASSERT_EQ(0, server.AddService(&echo_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start("127.0.0.1:8614", NULL)); + + const std::list labels = {"label1", "label2"}; + bvar::MultiDimension > my_madder("madder", labels); + bvar::Adder* my_adder1 = my_madder.get_stats({"val1", "val2"}); + ASSERT_TRUE(my_adder1); + *my_adder1 << 1 << 2; + bvar::Adder* my_adder2 = my_madder.get_stats({"val2", "val3"}); + ASSERT_TRUE(my_adder1); + *my_adder2 << 3 << 4; + + bvar::MultiDimension my_mlat("mlat", labels); + bvar::LatencyRecorder* my_lat1 = my_mlat.get_stats({"val1", "val2"}); + ASSERT_TRUE(my_lat1); + *my_lat1 << 1 << 2; + bvar::LatencyRecorder* my_lat2 = my_mlat.get_stats({"val2", "val3"}); + ASSERT_TRUE(my_lat2); + *my_lat2 << 3 << 4; + + brpc::Channel channel; + brpc::ChannelOptions channel_opts; + channel_opts.protocol = "http"; + ASSERT_EQ(0, channel.Init("127.0.0.1:8614", &channel_opts)); + brpc::Controller cntl; + cntl.http_request().uri() = "/brpc_metrics"; + channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()); + std::string res = cntl.response_attachment().to_string(); + LOG(INFO) << "output:\n" << res; + size_t start_pos = 0; + size_t end_pos = 0; + size_t label_start = 0; + STATE state = HELP; + char name_help[128]; + char name_type[128]; + char type[16]; + int matched = 0; + int num = 0; + bool summary_sum_gathered = false; + bool summary_count_gathered = false; + bool has_ever_summary = false; + bool has_ever_gauge = false; + bool has_ever_counter = false; // brought in by mvar latency recorder + std::unordered_set metric_name_set; + + while ((end_pos = res.find('\n', start_pos)) != butil::StringPiece::npos) { + res[end_pos] = '\0'; // safe; + switch (state) { + case HELP: + matched = sscanf(res.data() + start_pos, "# HELP %s", name_help); + ASSERT_EQ(1, matched); + state = TYPE; + break; + case TYPE: + matched = sscanf(res.data() + start_pos, "# TYPE %s %s", name_type, type); + ASSERT_EQ(2, matched); + ASSERT_STREQ(name_type, name_help); + if (strcmp(type, "gauge") == 0) { + state = GAUGE; + } else if (strcmp(type, "summary") == 0) { + state = SUMMARY; + } else if (strcmp(type, "counter") == 0) { + state = COUNTER; + } else { + ASSERT_TRUE(false) << "invalid type: " << type; + } + ASSERT_EQ(0, metric_name_set.count(name_type)) << "second TYPE line for metric name " + << name_type; + metric_name_set.insert(name_help); + break; + case HELP_OR_GAUGE: + case HELP_OR_COUNTER: + matched = sscanf(res.data() + start_pos, "# HELP %s", name_help); + // Try to figure out current line is a new COMMENT or not + if (matched == 1) { + state = HELP; + } else { + state = state == HELP_OR_GAUGE ? GAUGE : COUNTER; + } + res[end_pos] = '\n'; // revert to original + continue; // do not jump to next line + case GAUGE: + case COUNTER: + matched = sscanf(res.data() + start_pos, "%s %d", name_type, &num); + ASSERT_EQ(2, matched); + if (state == GAUGE) { + has_ever_gauge = true; + } + if (state == COUNTER) { + has_ever_counter = true; + } + label_start = butil::StringPiece(name_type).find("{"); + if (label_start == strlen(name_help)) { // mvar + ASSERT_EQ(name_type[strlen(name_type) - 1], '}'); + ASSERT_TRUE(strncmp(name_type, name_help, strlen(name_help)) == 0); + state = state == GAUGE ? HELP_OR_GAUGE : HELP_OR_COUNTER; + } else if (label_start == butil::StringPiece::npos) { // var + ASSERT_STREQ(name_type, name_help); + state = HELP; + } else { // invalid + ASSERT_TRUE(false); + } + break; + case SUMMARY: + if (butil::StringPiece(res.data() + start_pos, end_pos - start_pos).find("quantile=") + == butil::StringPiece::npos) { + matched = sscanf(res.data() + start_pos, "%s %d", name_type, &num); + ASSERT_EQ(2, matched); + ASSERT_TRUE(strncmp(name_type, name_help, strlen(name_help)) == 0); + if (butil::StringPiece(name_type).ends_with("_sum")) { + ASSERT_FALSE(summary_sum_gathered); + summary_sum_gathered = true; + } else if (butil::StringPiece(name_type).ends_with("_count")) { + ASSERT_FALSE(summary_count_gathered); + summary_count_gathered = true; + } else { + ASSERT_TRUE(false); + } + if (summary_sum_gathered && summary_count_gathered) { + state = HELP; + summary_sum_gathered = false; + summary_count_gathered = false; + has_ever_summary = true; + } + } // else find "quantile=", just break to next line + break; + default: + ASSERT_TRUE(false); + break; + } + start_pos = end_pos + 1; + } + ASSERT_TRUE(has_ever_gauge && has_ever_summary && has_ever_counter); + ASSERT_EQ(0, server.Stop(0)); + ASSERT_EQ(0, server.Join()); +} diff --git a/test/brpc_proto_unittest.cpp b/test/brpc_proto_unittest.cpp new file mode 100644 index 0000000..88ed40d --- /dev/null +++ b/test/brpc_proto_unittest.cpp @@ -0,0 +1,83 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2015/03/31 14:50:20 + + +#include +#include +#include +#include +#include +#include "brpc/policy/baidu_rpc_meta.pb.h" +#include "echo.pb.h" + +namespace { +using namespace google::protobuf; +using namespace brpc; + +void BuildDependency(const FileDescriptor *file_desc, DescriptorPool *pool) { + for (int i = 0; i < file_desc->dependency_count(); ++i) { + const FileDescriptor *fd = file_desc->dependency(i); + BuildDependency(fd, pool); + FileDescriptorProto proto; + fd->CopyTo(&proto); + ASSERT_TRUE(pool->BuildFile(proto) != NULL); + } + FileDescriptorProto proto; + file_desc->CopyTo(&proto); + ASSERT_TRUE(pool->BuildFile(proto) != NULL); +} + +TEST(ProtoTest, proto) { + policy::RpcMeta meta; + const Descriptor *desc = meta.GetDescriptor(); + const FileDescriptor *file_desc = desc->file(); + DescriptorPool pool; + DynamicMessageFactory factory(&pool); + BuildDependency(file_desc, &pool); + FileDescriptorProto file_desc_proto; + file_desc->CopyTo(&file_desc_proto); + const FileDescriptor *new_file_desc = pool.BuildFile(file_desc_proto); + ASSERT_TRUE(new_file_desc != NULL); + const Descriptor *new_desc = new_file_desc->FindMessageTypeByName(desc->name()); + ASSERT_TRUE(new_desc != NULL); + meta.set_correlation_id(123); + std::string data; + ASSERT_TRUE(meta.SerializeToString(&data)); + std::unique_ptr msg(factory.GetPrototype(new_desc)->New()); + ASSERT_TRUE(msg != NULL); + ASSERT_TRUE(msg->ParseFromString(data)); + ASSERT_TRUE(msg->SerializeToString(&data)); + policy::RpcMeta new_meta; + ASSERT_TRUE(new_meta.ParseFromString(data)); + ASSERT_EQ(123, new_meta.correlation_id()); +} + +TEST(ProtoTest, required_enum) { + test::Message1 msg1; + msg1.set_stat(test::STATE0_NUM_1); + std::string buf; + ASSERT_TRUE(msg1.SerializeToString(&buf)); + test::Message2 msg2; + ASSERT_TRUE(msg2.ParseFromString(buf)); + ASSERT_EQ((int)msg1.stat(), (int)msg2.stat()); + msg1.set_stat(test::STATE0_NUM_2); + ASSERT_TRUE(msg1.SerializeToString(&buf)); + ASSERT_FALSE(msg2.ParseFromString(buf)); +} +} //namespace diff --git a/test/brpc_protobuf_json_unittest.cpp b/test/brpc_protobuf_json_unittest.cpp new file mode 100644 index 0000000..b5ad0bb --- /dev/null +++ b/test/brpc_protobuf_json_unittest.cpp @@ -0,0 +1,1898 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include +#include +#include "butil/iobuf.h" +#include "butil/string_printf.h" +#include "butil/strings/string_util.h" +#include "butil/third_party/rapidjson/rapidjson.h" +#include "butil/time.h" +#include "butil/memory/scope_guard.h" +#include "gperftools_helper.h" +#include "json2pb/pb_to_json.h" +#include "json2pb/json_to_pb.h" +#include "json2pb/encode_decode.h" +#include "json2pb/zero_copy_stream_reader.h" +#include "message.pb.h" +#include "addressbook1.pb.h" +#include "addressbook.pb.h" +#include "addressbook_encode_decode.pb.h" +#include "addressbook_map.pb.h" +#include "echo.pb.h" + +namespace { // just for coding-style check + +using addressbook::AddressBook; +using addressbook::Person; + +class ProtobufJsonTest : public testing::Test { +protected: + void SetUp() {} + void TearDown() {} +}; + +inline int64_t gettimeofday_us() { + timeval now; + gettimeofday(&now, NULL); + return now.tv_sec * 1000000L + now.tv_usec; +} + +TEST_F(ProtobufJsonTest, json_to_pb_normal_case) { + const int N = 1000; + int64_t total_tm = 0; + int64_t total_tm2 = 0; + for (int i = 0; i < N; ++i) { + std::string info3 = "{\"content\":[{\"distance\":1,\"unknown_member\":2,\"ext\":" + "{\"age\":1666666666, \"databyte\":\"d2VsY29tZQ==\", \"enumtype\":1}," + "\"uid\":\"someone\"},{\"distance\":10,\"unknown_member\":20," + "\"ext\":{\"age\":1666666660, \"databyte\":\"d2VsY29tZQ==\"," + "\"enumtype\":2},\"uid\":\"someone0\"}], \"judge\":false," + "\"spur\":2, \"data\":[1,2,3,4,5,6,7,8,9,10]}"; + std::string error; + + JsonContextBody data; + const int64_t tm1 = gettimeofday_us(); + const bool ret = json2pb::JsonToProtoMessage(info3, &data, &error); + const int64_t tm2 = gettimeofday_us(); + total_tm += tm2 - tm1; + ASSERT_TRUE(ret); + + std::string info4; + std::string error1; + const int64_t tm3 = gettimeofday_us(); + bool ret2 = json2pb::ProtoMessageToJson(data, &info4, &error1); + const int64_t tm4 = gettimeofday_us(); + ASSERT_TRUE(ret2); + total_tm2 += tm4 - tm3; +#ifndef RAPIDJSON_VERSION_0_1 + ASSERT_STREQ("{\"data\":[1,2,3,4,5,6,7,8,9,10]," + "\"judge\":false,\"spur\":2.0,\"content\":[{\"uid\":\"someone\"," + "\"distance\":1.0,\"ext\":{\"age\":1666666666,\"databyte\":\"d2VsY29tZQ==\"," + "\"enumtype\":\"HOME\"}},\{\"uid\":\"someone0\",\"distance\":10.0,\"ext\":" + "{\"age\":1666666660,\"databyte\":\"d2VsY29tZQ==\",\"enumtype\":\"WORK\"}}]}", + info4.data()); +#else + ASSERT_STREQ("{\"data\":[1,2,3,4,5,6,7,8,9,10]," + "\"judge\":false,\"spur\":2,\"content\":[{\"uid\":\"someone\"," + "\"distance\":1,\"ext\":{\"age\":1666666666,\"databyte\":\"d2VsY29tZQ==\"," + "\"enumtype\":\"HOME\"}},\{\"uid\":\"someone0\",\"distance\":10,\"ext\":" + "{\"age\":1666666660,\"databyte\":\"d2VsY29tZQ==\",\"enumtype\":\"WORK\"}}]}", + info4.data()); +#endif + } + std::cout << "json2pb=" << total_tm / N + << "us pb2json=" << total_tm2 / N << "us" + << std::endl; +} + +TEST_F(ProtobufJsonTest, json_base64_string_to_pb_types_case) { + std::string info3 = "{\"content\":[{\"distance\":1,\"unknown_member\":2,\"ext\":" + "{\"age\":1666666666, \"databyte\":\"d2VsY29tZQ==\", \"enumtype\":1}," + "\"uid\":\"someone\"},{\"distance\":10,\"unknown_member\":20," + "\"ext\":{\"age\":1666666660, \"databyte\":\"d2VsY29tZTA=\"," + "\"enumtype\":2},\"uid\":\"someone0\"}], \"judge\":false," + "\"spur\":2}"; + std::string error; + + JsonContextBody data; + json2pb::Json2PbOptions options_j2pb; + options_j2pb.base64_to_bytes = true; + const bool ret = json2pb::JsonToProtoMessage(info3, &data, options_j2pb, &error); + ASSERT_TRUE(ret); + ASSERT_TRUE(data.content_size() == 2); + ASSERT_EQ(data.content(0).ext().databyte(), "welcome"); + ASSERT_EQ(data.content(1).ext().databyte(), "welcome0"); + + std::string info4; + std::string error1; + json2pb::Pb2JsonOptions options_pb2j; + options_pb2j.bytes_to_base64 = true; + bool ret2 = json2pb::ProtoMessageToJson(data, &info4, options_pb2j, &error1); + ASSERT_TRUE(ret2); +#ifndef RAPIDJSON_VERSION_0_1 + ASSERT_STREQ("{\"judge\":false,\"spur\":2.0,\"content\":[{\"uid\":\"someone\"," + "\"distance\":1.0,\"ext\":{\"age\":1666666666,\"databyte\":\"d2VsY29tZQ==\"," + "\"enumtype\":\"HOME\"}},\{\"uid\":\"someone0\",\"distance\":10.0,\"ext\":" + "{\"age\":1666666660,\"databyte\":\"d2VsY29tZTA=\",\"enumtype\":\"WORK\"}}]}", + info4.data()); +#else + ASSERT_STREQ("{\"judge\":false,\"spur\":2,\"content\":[{\"uid\":\"someone\"," + "\"distance\":1,\"ext\":{\"age\":1666666666,\"databyte\":\"d2VsY29tZQ==\"," + "\"enumtype\":\"HOME\"}},\{\"uid\":\"someone0\",\"distance\":10,\"ext\":" + "{\"age\":1666666660,\"databyte\":\"d2VsY29tZTA=\",\"enumtype\":\"WORK\"}}]}", + info4.data()); +#endif +} + +TEST_F(ProtobufJsonTest, json_to_pb_map_case) { + std::string json = "{\"addr\":\"baidu.com\"," + "\"numbers\":{\"tel\":123456,\"cell\":654321}," + "\"contacts\":{\"email\":\"frank@baidu.com\"," + " \"office\":\"Shanghai\"}," + "\"friends\":{\"John\":[{\"school\":\"SJTU\",\"year\":2007}]}}"; + std::string error; + AddressNoMap ab1; + ASSERT_TRUE(json2pb::JsonToProtoMessage(json, &ab1, &error)); + ASSERT_EQ("baidu.com", ab1.addr()); + + AddressIntMap ab2; + ASSERT_TRUE(json2pb::JsonToProtoMessage(json, &ab2, &error)); + ASSERT_EQ("baidu.com", ab2.addr()); + ASSERT_EQ("tel", ab2.numbers(0).key()); + ASSERT_EQ(123456, ab2.numbers(0).value()); + ASSERT_EQ("cell", ab2.numbers(1).key()); + ASSERT_EQ(654321, ab2.numbers(1).value()); + + AddressStringMap ab3; + ASSERT_TRUE(json2pb::JsonToProtoMessage(json, &ab3, &error)); + ASSERT_EQ("baidu.com", ab3.addr()); + ASSERT_EQ("email", ab3.contacts(0).key()); + ASSERT_EQ("frank@baidu.com", ab3.contacts(0).value()); + ASSERT_EQ("office", ab3.contacts(1).key()); + ASSERT_EQ("Shanghai", ab3.contacts(1).value()); + + AddressComplex ab4; + ASSERT_TRUE(json2pb::JsonToProtoMessage(json, &ab4, &error)) << error; + ASSERT_EQ("baidu.com", ab4.addr()); + ASSERT_EQ("John", ab4.friends(0).key()); + ASSERT_EQ("SJTU", ab4.friends(0).value(0).school()); + ASSERT_EQ(2007, ab4.friends(0).value(0).year()); + + std::string old_json = "{\"addr\":\"baidu.com\"," + "\"numbers\":[{\"key\":\"tel\",\"value\":123456}," + " {\"key\":\"cell\",\"value\":654321}]}"; + ab2.Clear(); + ASSERT_TRUE(json2pb::JsonToProtoMessage(old_json, &ab2, &error)) << error; + ASSERT_EQ("baidu.com", ab2.addr()); + ASSERT_EQ("tel", ab2.numbers(0).key()); + ASSERT_EQ(123456, ab2.numbers(0).value()); + ASSERT_EQ("cell", ab2.numbers(1).key()); + ASSERT_EQ(654321, ab2.numbers(1).value()); +} + +TEST_F(ProtobufJsonTest, json_to_pb_encode_decode) { + std::string info3 = "{\"@Content_Test%@\":[{\"Distance_info_\":1,\ + \"_ext%T_\":{\"Aa_ge(\":1666666666, \"databyte(std::string)\":\ + \"d2VsY29tZQ==\", \"enum--type\":\"HOME\"},\"uid*\":\"welcome\"}], \ + \"judge\":false, \"spur\":2, \"data:array\":[]}"; + printf("----------test json to pb------------\n\n"); + std::string error; + JsonContextBodyEncDec data; + ASSERT_TRUE(json2pb::JsonToProtoMessage(info3, &data, &error)); + + std::string info4; + std::string error1; + ASSERT_TRUE(json2pb::ProtoMessageToJson(data, &info4, &error1)); +#ifndef RAPIDJSON_VERSION_0_1 + ASSERT_STREQ("{\"judge\":false,\"spur\":2.0," + "\"@Content_Test%@\":[{\"uid*\":\"welcome\",\"Distance_info_\":1.0," + "\"_ext%T_\":{\"Aa_ge(\":1666666666,\"databyte(std::string)\":\"d2VsY29tZQ==\"," + "\"enum--type\":\"HOME\"}}]}", info4.data()); +#else + ASSERT_STREQ("{\"judge\":false,\"spur\":2," + "\"@Content_Test%@\":[{\"uid*\":\"welcome\",\"Distance_info_\":1," + "\"_ext%T_\":{\"Aa_ge(\":1666666666,\"databyte(std::string)\":\"d2VsY29tZQ==\"," + "\"enum--type\":\"HOME\"}}]}", info4.data()); +#endif +} + +TEST_F(ProtobufJsonTest, json_to_pb_unicode_case) { + AddressBook address_book; + + Person* person = address_book.add_person(); + + person->set_id(100); + + char name[255 * 1024]; + for (int j = 0; j < 255; j++) { + for (int i = 0; i < 1024; i++) { + name[j*1024 + i] = i + 1; + } + } + name[255 * 1024 - 1] = '\0'; + person->set_name(name); + person->set_data(-240000000); + person->set_data32(6); + person->set_data64(-1820000000); + person->set_datadouble(123.456); + person->set_datadouble_scientific(1.23456789e+08); + person->set_datafloat_scientific(1.23456789e+08); + person->set_datafloat(8.6123); + person->set_datau32(60); + person->set_datau64(960); + person->set_databool(0); + person->set_databyte("welcome to china"); + person->set_datafix32(1); + person->set_datafix64(666); + person->set_datasfix32(120); + person->set_datasfix64(-802); + + std::string info1; + std::string error; + + google::protobuf::TextFormat::Printer printer; + std::string text; + printer.PrintToString(*person, &text); + + printf("----------test pb to json------------\n\n"); + bool ret = json2pb::ProtoMessageToJson(address_book, &info1, &error); + ASSERT_TRUE(ret); + AddressBook address_book_test; + ret = json2pb::JsonToProtoMessage(info1, &address_book_test, &error); + ASSERT_TRUE(ret); + std::string info2; + ret = json2pb::ProtoMessageToJson(address_book_test, &info2, &error); + ASSERT_TRUE(ret); + ASSERT_TRUE(!info1.compare(info2)); + butil::IOBuf buf; + butil::IOBufAsZeroCopyOutputStream stream(&buf); + bool res = json2pb::ProtoMessageToJson(address_book, &stream, NULL); + ASSERT_TRUE(res); + butil::IOBufAsZeroCopyInputStream stream2(buf); + AddressBook address_book_test3; + ret = json2pb::JsonToProtoMessage(&stream2, &address_book_test3, &error); + ASSERT_TRUE(ret); + std::string info3; + ret = json2pb::ProtoMessageToJson(address_book_test3, &info3, &error); + ASSERT_TRUE(ret); + ASSERT_TRUE(!info2.compare(info3)); +} + +TEST_F(ProtobufJsonTest, json_to_pb_edge_case) { + std::string info3 = "{\"judge\":false, \"spur\":2.0e1}"; + + std::string error; + JsonContextBody data; + bool ret = json2pb::JsonToProtoMessage(info3, &data, &error); + ASSERT_TRUE(ret); + + std::string info4; + std::string error1; + ret = json2pb::ProtoMessageToJson(data, &info4, &error1); + ASSERT_TRUE(ret); + + info3 = "{\"judge\":false, \"spur\":-2, \"data\":[], \"info\":[],\"content\":[]}"; + error.clear(); + + JsonContextBody data1; + ret = json2pb::JsonToProtoMessage(info3, &data1, &error); + ASSERT_TRUE(ret); + + info4.clear(); + error1.clear(); + ret = json2pb::ProtoMessageToJson(data1, &info4, &error1); + ASSERT_TRUE(ret); + + info3 = "{\"judge\":false, \"spur\":\"NaN\"}"; + error.clear(); + + JsonContextBody data2; + ret = json2pb::JsonToProtoMessage(info3, &data2, &error); + ASSERT_TRUE(ret); + + info4.clear(); + error1.clear(); + ret = json2pb::ProtoMessageToJson(data2, &info4, &error1); + ASSERT_TRUE(ret); + + info3 = "{\"judge\":false, \"spur\":\"Infinity\"}"; + error.clear(); + + JsonContextBody data3; + ret = json2pb::JsonToProtoMessage(info3, &data3, &error); + ASSERT_TRUE(ret); + + info4.clear(); + error1.clear(); + ret = json2pb::ProtoMessageToJson(data3, &info4, &error1); + ASSERT_TRUE(ret); + + info3 = "{\"judge\":false, \"spur\":\"-inFiNITY\"}"; + error.clear(); + + JsonContextBody data4; + ret = json2pb::JsonToProtoMessage(info3, &data4, &error); + ASSERT_TRUE(ret); + + info4.clear(); + error1.clear(); + ret = json2pb::ProtoMessageToJson(data4, &info4, &error1); + ASSERT_TRUE(ret); + + info3 = "{\"judge\":false, \"spur\":2.0, \"content\":[{\"distance\":2.5, " + "\"ext\":{\"databyte\":\"d2VsY29tZQ==\", \"enumtype\":\"MOBILE\"}}]}"; + error.clear(); + + JsonContextBody data5; + ret = json2pb::JsonToProtoMessage(info3, &data5, &error); + ASSERT_TRUE(ret); + + info4.clear(); + error1.clear(); + ret = json2pb::ProtoMessageToJson(data5, &info4, &error1); + ASSERT_TRUE(ret); + + info3 = "{\"content\":[{\"distance\":1,\"unknown_member\":2,\ + \"ext\":{\"age\":1666666666, \"databyte\":\"d2VsY29tZQ==\", \"enumtype\":1},\ + \"uid\":\"someone\"},{\"distance\":2.3,\"unknown_member\":20,\ + \"ext\":{\"age\":1666666660, \"databyte\":\"d2VsY29tZQ==\", \"enumtype\":\"Test\"},\ + \"uid\":\"someone0\"}], \"judge\":false, \ + \"spur\":2, \"data\":[1,2,3,4,5,6,7,8,9,10]}"; + error.clear(); + + JsonContextBody data9; + ret = json2pb::JsonToProtoMessage(info3, &data9, &error); + ASSERT_TRUE(ret); + ASSERT_STREQ("Invalid value `\"Test\"' for optional field `Ext.enumtype' which SHOULD be enum", error.data()); + + info3 = "{\"content\":[{\"distance\":1,\"unknown_member\":2,\ + \"ext\":{\"age\":1666666666, \"databyte\":\"d2VsY29tZQ==\", \"enumtype\":1},\ + \"uid\":\"someone\"},{\"distance\":5,\"unknown_member\":20,\ + \"ext\":{\"age\":1666666660, \"databyte\":\"d2VsY29tZQ==\", \"enumtype\":15},\ + \"uid\":\"someone0\"}], \"judge\":false, \ + \"spur\":2, \"data\":[1,2,3,4,5,6,7,8,9,10]}"; + error.clear(); + + JsonContextBody data10; + ret = json2pb::JsonToProtoMessage(info3, &data10, &error); + ASSERT_TRUE(ret); + ASSERT_STREQ("Invalid value `15' for optional field `Ext.enumtype' which SHOULD be enum", error.data()); + + info3 = "{\"content\":[{\"distance\":1,\"unknown_member\":2,\ + \"ext\":{\"age\":1666666666, \"databyte\":\"d2VsY29tZQ==\", \"enumtype\":1},\ + \"uid\":\"someone\"},{\"distance\":5,\"unknown_member\":20,\ + \"ext\":{\"age\":1666666660, \"databyte\":\"d2VsY29tZQ==\", \"enumtype\":15},\ + \"uid\":\"someone0\"}], \"judge\":false, \ + \"spur\":2, \"type\":[\"123\"]}"; + error.clear(); + + JsonContextBody data11; + ret = json2pb::JsonToProtoMessage(info3, &data11, &error); + ASSERT_TRUE(ret); + ASSERT_STREQ("Invalid value `array' for optional field `JsonContextBody.type' which SHOULD be INT64, Invalid value `15' for optional field `Ext.enumtype' which SHOULD be enum", + error.data()); +} + +TEST_F(ProtobufJsonTest, json_to_pb_expected_failed_case) { + std::string info3 = "{\"content\":[{\"unknown_member\":2,\"ext\":{\"age\":1666666666, \ + \"databyte\":\"welcome\", \"enumtype\":1},\"uid\":\"someone\"},\ + {\"unknown_member\":20,\"ext\":{\"age\":1666666660, \"databyte\":\ + \"welcome0\", \"enumtype\":2},\"uid\":\"someone0\"}], \ + \"judge\":false, \"spur\":2, \"data\":[1,2,3,4,5,6,7,8,9,10]}"; + std::string error; + + JsonContextBody data; + bool ret = json2pb::JsonToProtoMessage(info3, &data, &error); + ASSERT_FALSE(ret); + ASSERT_STREQ("Missing required field: Content.distance", error.data()); + + info3 = "{\"content\":[{\"distance\":1,\"unknown_member\":2,\"ext\":{\"age\":1666666666, \ + \"enumtype\":1},\"uid\":\"someone\"},{\"distance\":10,\"unknown_member\":20,\ + \"ext\":{\"age\":1666666660, \"databyte\":\"welcome0\", \"enumtype\":2},\ + \"uid\":\"someone0\"}], \"judge\":false, \ + \"spur\":2, \"data\":[1,2,3,4,5,6,7,8,9,10]}"; + error.clear(); + + JsonContextBody data2; + ret = json2pb::JsonToProtoMessage(info3, &data2, &error); + ASSERT_FALSE(ret); + ASSERT_STREQ("Missing required field: Ext.databyte", error.data()); + + info3 = "{\"content\":[{\"distance\":1,\"unknown_member\":2,\ + \"ext\":{\"age\":1666666666, \"databyte\":\"welcome\", \"enumtype\":1},\ + \"uid\":\"someone\"},{\"distance\":10,\"unknown_member\":20,\ + \"ext\":{\"age\":1666666660, \"databyte\":\"welcome0\", \"enumtype\":2},\ + \"uid\":\"someone0\"}], \"spur\":2, \"data\":[1,2,3,4,5,6,7,8,9,10]}"; + error.clear(); + + JsonContextBody data3; + ret = json2pb::JsonToProtoMessage(info3, &data, &error); + ASSERT_FALSE(ret); + ASSERT_STREQ("Missing required field: JsonContextBody.judge", error.data()); + + info3 = "{\"content\":[{\"distance\":1,\"unknown_member\":2,\ + \"ext\":{\"age\":1666666666, \"databyte\":\"welcome\", \"enumtype\":1},\ + \"uid\":\"someone\"},{\"distance\":10,\"unknown_member\":20,\ + \"ext\":{\"age\":1666666660, \"databyte\":\"welcome0\", \"enumtype\":2},\ + \"uid\":\"someone0\"}], \"judge\":\"false\", \ + \"spur\":2, \"data\":[1,2,3,4,5,6,7,8,9,10]}"; + error.clear(); + + JsonContextBody data4; + ret = json2pb::JsonToProtoMessage(info3, &data4, &error); + ASSERT_FALSE(ret); + ASSERT_STREQ("Invalid value `\"false\"' for field `JsonContextBody.judge' which SHOULD be BOOL", error.data()); + + info3 = "{\"content\":[{\"distance\":1,\"unknown_member\":2,\ + \"ext\":{\"age\":1666666666, \"databyte\":\"welcome\", \"enumtype\":1},\ + \"uid\":\"someone\"},{\"distance\":10,\"unknown_member\":20,\ + \"ext\":{\"age\":1666666660, \"databyte\":\"welcome0\", \"enumtype\":2},\ + \"uid\":\"someone0\"}], \"judge\":false, \ + \"spur\":2, \"data\":[\"1\",\"2\",\"3\",\"4\"]}"; + error.clear(); + + JsonContextBody data5; + ret = json2pb::JsonToProtoMessage(info3, &data5, &error); + ASSERT_FALSE(ret); + ASSERT_STREQ("Invalid value `\"1\"' for field `JsonContextBody.data' which SHOULD be INT32", error.data()); + + info3 = "{\"content\":[{\"distance\":1,\"unknown_member\":2,\ + \"ext\":{\"age\":1666666666, \"databyte\":\"welcome\", \"enumtype\":1},\ + \"uid\":\"someone\"},{\"distance\":10,\"unknown_member\":20,\ + \"ext\":{\"age\":1666666660, \"databyte\":\"welcome0\", \"enumtype\":2},\ + \"uid\":\"someone0\"}], \"judge\":false, \ + \"spur\":2, \"data\":[1,2,3,4,5,6,7,8,9,10], \"info\":2}"; + error.clear(); + + JsonContextBody data6; + ret = json2pb::JsonToProtoMessage(info3, &data6, &error); + ASSERT_FALSE(ret); + ASSERT_STREQ("Invalid value for repeated field: JsonContextBody.info", error.data()); + + info3 = "{\"judge\":false, \"spur\":\"NaNa\"}"; + error.clear(); + + JsonContextBody data7; + ret = json2pb::JsonToProtoMessage(info3, &data7, &error); + ASSERT_FALSE(ret); + ASSERT_STREQ("Invalid value `\"NaNa\"' for field `JsonContextBody.spur' which SHOULD be d", + error.data()); + + info3 = "{\"judge\":false, \"spur\":\"Infinty\"}"; + error.clear(); + + JsonContextBody data8; + ret = json2pb::JsonToProtoMessage(info3, &data8, &error); + ASSERT_FALSE(ret); + ASSERT_STREQ("Invalid value `\"Infinty\"' for field `JsonContextBody.spur' which SHOULD be d", + error.data()); + + info3 = "{\"content\":[{\"distance\":1,\"unknown_member\":2,\"ext\":{\"age\":1666666666, \ + \"enumtype\":1},\"uid\":23},{\"distance\":10,\"unknown_member\":20,\ + \"ext\":{\"age\":1666666660, \"databyte\":\"welcome0\", \"enumtype\":2},\ + \"uid\":\"someone0\"}], \"judge\":false, \ + \"spur\":2, \"data\":[1,2,3,4,5,6,7,8,9,10]}"; + error.clear(); + + JsonContextBody data9; + ret = json2pb::JsonToProtoMessage(info3, &data9, &error); + ASSERT_FALSE(ret); + ASSERT_STREQ("Invalid value `23' for optional field `Content.uid' which SHOULD be string, Missing required field: Ext.databyte", error.data()); +} + +const int DEEP_RECURSION_TEST_DEPTH = 140000; + +TEST_F(ProtobufJsonTest, json_to_pb_unbounded_recursion) { + test::RecursiveMessage msg; + + // Generate a deeply nested JSON string to trigger unbounded recursion. + const int recursion_depth = DEEP_RECURSION_TEST_DEPTH; + std::string nested_json = ""; + for (int i = 0; i < recursion_depth; ++i) { + nested_json += "{\"child\":"; + } + nested_json += "{\"data\":\"leaf\"}"; + for (int i = 0; i < recursion_depth; ++i) { + nested_json += "}"; + } + + { + std::string error; + bool ret = json2pb::JsonToProtoMessage(nested_json, &msg, &error); + ASSERT_FALSE(ret); + ASSERT_EQ("Exceeded maximum recursion depth [RecursiveMessage]", error); + } + { + json2pb::ProtoJson2PbOptions options; + std::string error; + bool ret = json2pb::ProtoJsonToProtoMessage(nested_json, &msg, options, &error); + ASSERT_FALSE(ret); + ASSERT_NE(std::string::npos, error.find("INVALID_ARGUMENT")) + << "error=" << error; + ASSERT_TRUE(error.find("recursion") != std::string::npos || + error.find("nested") != std::string::npos || + error.find("too deep") != std::string::npos) + << "error=" << error; + } +} + +TEST_F(ProtobufJsonTest, pb_to_json_unbounded_recursion) { + test::RecursiveMessage msg; + + // Create a deeply nested protobuf message. + const int recursion_depth = DEEP_RECURSION_TEST_DEPTH; + test::RecursiveMessage* current = &msg; + std::vector nodes; + nodes.reserve(recursion_depth); + for (int i = 0; i < recursion_depth; ++i) { + nodes.push_back(current); + current = current->mutable_child(); + } + current->set_data("leaf"); + + BRPC_SCOPE_EXIT { + // Release msg memory from end to start to avoid stack overflow. + for (size_t i = nodes.size() - 1; i > 0; --i) { + delete nodes[i]->release_child(); + } + }; + + { + std::string json_output; + std::string error; + bool ret = json2pb::ProtoMessageToJson(msg, &json_output, &error); + ASSERT_FALSE(ret); + ASSERT_EQ("Exceeded maximum recursion depth", error); + } + { + std::string json_output; + std::string error; + json2pb::Pb2ProtoJsonOptions options; + bool ret = json2pb::ProtoMessageToProtoJson(msg, &json_output, options, &error); + ASSERT_FALSE(ret); + ASSERT_EQ("Exceeded maximum recursion depth", error); + } +} + +TEST_F(ProtobufJsonTest, pb_parse_unbounded_recursion) { + auto generate_binary = [](int recursion_depth) { + // Innermost message: { data: "leaf" } + // data field: tag = (2<<3)|2 = 0x12, len=4, bytes "leaf" + const char kLeafRaw[] = "\x12\x04" "leaf"; + const std::string leaf_msg(kLeafRaw, sizeof(kLeafRaw) - 1); + + // Precompute sizes: + // S[0] = leaf size + // S[i] = 1 (tag 0x0A) + varint_len(S[i-1]) + S[i-1] + auto varint_len = [](size_t v) { + int n = 1; + while (v >= 128) { v >>= 7; ++n; } + return n; + }; + + std::vector sizes; + sizes.reserve(recursion_depth + 1); + sizes.push_back(leaf_msg.size()); + for (int i = 1; i <= recursion_depth; ++i) { + size_t inner = sizes[i-1]; + sizes.push_back(1 + varint_len(inner) + inner); + } + const size_t final_size = sizes.back(); + + std::string out; + out.resize(final_size); + size_t off = 0; + + // Emit outermost -> innermost wrappers: tag(0x0A) + varint(len(inner)) + for (int depth = recursion_depth; depth >= 1; --depth) { + out[off++] = static_cast(0x0A); // tag for child + size_t len = sizes[depth - 1]; + while (true) { + uint8_t byte = static_cast(len & 0x7F); + len >>= 7; + if (len) byte |= 0x80; + out[off++] = static_cast(byte); + if (!len) break; + } + } + + // Copy leaf payload + memcpy(&out[off], leaf_msg.data(), leaf_msg.size()); + off += leaf_msg.size(); + return out; + }; + + // Test protobuf max depth limit (100). + { + test::RecursiveMessage msg; + std::string binary_data = generate_binary(100); + bool ret = msg.ParseFromString(binary_data); + ASSERT_TRUE(ret); + ASSERT_TRUE(msg.IsInitialized()); + + std::string error; + std::string json_output; + ret = json2pb::ProtoMessageToJson(msg, &json_output, &error); + ASSERT_TRUE(ret); + ASSERT_EQ("", error); + } + { + test::RecursiveMessage msg; + std::string binary_data = generate_binary(101); + bool ret = msg.ParseFromString(binary_data); + ASSERT_FALSE(ret); + } + { + test::RecursiveMessage msg; + std::string binary_data = generate_binary(DEEP_RECURSION_TEST_DEPTH); + bool ret = msg.ParseFromString(binary_data); + ASSERT_FALSE(ret); + } +} + +TEST_F(ProtobufJsonTest, json_to_pb_perf_case) { + + std::string info3 = "{\"content\":[{\"distance\":1.0,\ + \"ext\":{\"age\":1666666666, \"databyte\":\"d2VsY29tZQ==\", \"enumtype\":1},\ + \"uid\":\"welcome\"}], \"judge\":false, \"spur\":2.0, \"data\":[]}"; + + printf("----------test json to pb performance------------\n\n"); + + std::string error; + + ProfilerStart("json_to_pb_perf.prof"); + butil::Timer timer; + bool res; + float avg_time1 = 0; + float avg_time2 = 0; + const int times = 100000; + for (int i = 0; i < times; i++) { + JsonContextBody data; + timer.start(); + res = json2pb::JsonToProtoMessage(info3, &data, &error); + timer.stop(); + avg_time1 += timer.u_elapsed(); + ASSERT_TRUE(res); + + std::string info4; + std::string error1; + timer.start(); + res = json2pb::ProtoMessageToJson(data, &info4, &error1); + timer.stop(); + avg_time2 += timer.u_elapsed(); + ASSERT_TRUE(res); + } + avg_time1 /= times; + avg_time2 /= times; + ProfilerStop(); + printf("avg time to convert json to pb is %fus\n", avg_time1); + printf("avg time to convert pb to json is %fus\n", avg_time2); +} + +TEST_F(ProtobufJsonTest, json_to_pb_encode_decode_perf_case) { + std::string info3 = "{\"@Content_Test%@\":[{\"Distance_info_\":1,\ + \"_ext%T_\":{\"Aa_ge(\":1666666666, \"databyte(std::string)\":\ + \"welcome\", \"enum--type\":1},\"uid*\":\"welcome\"}], \ + \"judge\":false, \"spur\":2, \"data:array\":[]}"; + printf("----------test json to pb encode/decode performance------------\n\n"); + std::string error; + + ProfilerStart("json_to_pb_encode_decode_perf.prof"); + butil::Timer timer; + bool res; + float avg_time1 = 0; + float avg_time2 = 0; + const int times = 100000; + for (int i = 0; i < times; i++) { + JsonContextBody data; + timer.start(); + res = json2pb::JsonToProtoMessage(info3, &data, &error); + timer.stop(); + avg_time1 += timer.u_elapsed(); + ASSERT_TRUE(res); + + std::string info4; + std::string error1; + timer.start(); + res = json2pb::ProtoMessageToJson(data, &info4, &error1); + timer.stop(); + avg_time2 += timer.u_elapsed(); + ASSERT_TRUE(res); + } + avg_time1 /= times; + avg_time2 /= times; + ProfilerStop(); + printf("avg time to convert json to pb is %fus\n", avg_time1); + printf("avg time to convert pb to json is %fus\n", avg_time2); +} + +TEST_F(ProtobufJsonTest, json_to_pb_complex_perf_case) { + std::ifstream in("jsonout", std::ios::in); + std::ostringstream tmp; + tmp << in.rdbuf(); + butil::IOBuf buf; + buf.append(tmp.str()); + in.close(); + + printf("----------test json to pb performance------------\n\n"); + + std::string error; + + butil::Timer timer; + + bool res; + float avg_time1 = 0; + const int times = 10000; + json2pb::Json2PbOptions options; + options.base64_to_bytes = false; + ProfilerStart("json_to_pb_complex_perf.prof"); + for (int i = 0; i < times; i++) { + gss::message::gss_us_res_t data; + butil::IOBufAsZeroCopyInputStream stream(buf); + timer.start(); + res = json2pb::JsonToProtoMessage(&stream, &data, options, &error); + timer.stop(); + avg_time1 += timer.u_elapsed(); + ASSERT_TRUE(res); + } + ProfilerStop(); + avg_time1 /= times; + printf("avg time to convert json to pb is %fus\n", avg_time1); +} + +TEST_F(ProtobufJsonTest, json_to_pb_to_string_complex_perf_case) { + std::ifstream in("jsonout", std::ios::in); + std::ostringstream tmp; + tmp << in.rdbuf(); + std::string info3 = tmp.str(); + in.close(); + + printf("----------test json to pb performance------------\n\n"); + + std::string error; + + butil::Timer timer; + bool res; + float avg_time1 = 0; + const int times = 10000; + json2pb::Json2PbOptions options; + options.base64_to_bytes = false; + ProfilerStart("json_to_pb_to_string_complex_perf.prof"); + for (int i = 0; i < times; i++) { + gss::message::gss_us_res_t data; + timer.start(); + res = json2pb::JsonToProtoMessage(info3, &data, options, &error); + timer.stop(); + avg_time1 += timer.u_elapsed(); + ASSERT_TRUE(res); + } + avg_time1 /= times; + ProfilerStop(); + printf("avg time to convert json to pb is %fus\n", avg_time1); +} + +TEST_F(ProtobufJsonTest, pb_to_json_normal_case) { + AddressBook address_book; + + Person* person = address_book.add_person(); + + person->set_id(100); + person->set_name("baidu"); + person->set_email("welcome@baidu.com"); + + Person::PhoneNumber* phone_number = person->add_phone(); + phone_number->set_number("number123"); + phone_number->set_type(Person::HOME); + + person->set_data(-240000000); + person->set_data32(6); + person->set_data64(-1820000000); + person->set_datadouble(123.456); + person->set_datadouble_scientific(1.23456789e+08); + person->set_datafloat_scientific(1.23456789e+08); + person->set_datafloat(8.6123); + person->set_datau32(60); + person->set_datau64(960); + person->set_databool(0); + person->set_databyte("welcome"); + person->set_datafix32(1); + person->set_datafix64(666); + person->set_datasfix32(120); + person->set_datasfix64(-802); + + std::string info1; + + google::protobuf::TextFormat::Printer printer; + std::string text; + printer.PrintToString(*person, &text); + + printf("text:%s\n", text.data()); + + printf("----------test pb to json------------\n\n"); + json2pb::Pb2JsonOptions option; + option.bytes_to_base64 = true; + bool ret = json2pb::ProtoMessageToJson(address_book, &info1, option, NULL); + ASSERT_TRUE(ret); + +#ifndef RAPIDJSON_VERSION_0_1 + ASSERT_STREQ("{\"person\":[{\"name\":\"baidu\",\"id\":100,\"email\":\"welcome@baidu.com\"," + "\"phone\":[{\"number\":\"number123\",\"type\":\"HOME\"}],\"data\":-240000000," + "\"data32\":6,\"data64\":-1820000000,\"datadouble\":123.456," + "\"datafloat\":8.612299919128418,\"datau32\":60,\"datau64\":960," + "\"databool\":false,\"databyte\":\"d2VsY29tZQ==\",\"datafix32\":1," + "\"datafix64\":666,\"datasfix32\":120,\"datasfix64\":-802," + "\"datafloat_scientific\":123456792.0,\"datadouble_scientific\":123456789.0}]}", + info1.data()); +#else + ASSERT_STREQ("{\"person\":[{\"name\":\"baidu\",\"id\":100,\"email\":\"welcome@baidu.com\"," + "\"phone\":[{\"number\":\"number123\",\"type\":\"HOME\"}],\"data\":-240000000," + "\"data32\":6,\"data64\":-1820000000,\"datadouble\":123.456," + "\"datafloat\":8.612299919,\"datau32\":60,\"datau64\":960,\"databool\":false," + "\"databyte\":\"d2VsY29tZQ==\",\"datafix32\":1,\"datafix64\":666," + "\"datasfix32\":120,\"datasfix64\":-802,\"datafloat_scientific\":123456792," + "\"datadouble_scientific\":123456789}]}", info1.data()); +#endif + + info1.clear(); + { + json2pb::Pb2JsonOptions option; + option.bytes_to_base64 = true; + ret = ProtoMessageToJson(address_book, &info1, option, NULL); + } + ASSERT_TRUE(ret); + +#ifndef RAPIDJSON_VERSION_0_1 + ASSERT_STREQ("{\"person\":[{\"name\":\"baidu\",\"id\":100,\"email\":\"welcome@baidu.com\"," + "\"phone\":[{\"number\":\"number123\",\"type\":\"HOME\"}],\"data\":-240000000," + "\"data32\":6,\"data64\":-1820000000,\"datadouble\":123.456," + "\"datafloat\":8.612299919128418,\"datau32\":60,\"datau64\":960," + "\"databool\":false,\"databyte\":\"d2VsY29tZQ==\",\"datafix32\":1," + "\"datafix64\":666,\"datasfix32\":120,\"datasfix64\":-802," + "\"datafloat_scientific\":123456792.0,\"datadouble_scientific\":123456789.0}]}", + info1.data()); +#else + ASSERT_STREQ("{\"person\":[{\"name\":\"baidu\",\"id\":100,\"email\":\"welcome@baidu.com\"," + "\"phone\":[{\"number\":\"number123\",\"type\":\"HOME\"}],\"data\":-240000000," + "\"data32\":6,\"data64\":-1820000000,\"datadouble\":123.456," + "\"datafloat\":8.612299919,\"datau32\":60,\"datau64\":960,\"databool\":false," + "\"databyte\":\"d2VsY29tZQ==\",\"datafix32\":1,\"datafix64\":666," + "\"datasfix32\":120,\"datasfix64\":-802,\"datafloat_scientific\":123456792," + "\"datadouble_scientific\":123456789}]}", info1.data()); +#endif + + info1.clear(); + { + json2pb::Pb2JsonOptions option; + option.bytes_to_base64 = true; + option.enum_option = json2pb::OUTPUT_ENUM_BY_NUMBER; + ret = ProtoMessageToJson(address_book, &info1, option, NULL); + } + ASSERT_TRUE(ret); + +#ifndef RAPIDJSON_VERSION_0_1 + ASSERT_STREQ("{\"person\":[{\"name\":\"baidu\",\"id\":100,\"email\":\"welcome@baidu.com\"," + "\"phone\":[{\"number\":\"number123\",\"type\":1}],\"data\":-240000000," + "\"data32\":6,\"data64\":-1820000000,\"datadouble\":123.456," + "\"datafloat\":8.612299919128418,\"datau32\":60,\"datau64\":960," + "\"databool\":false,\"databyte\":\"d2VsY29tZQ==\",\"datafix32\":1," + "\"datafix64\":666,\"datasfix32\":120,\"datasfix64\":-802," + "\"datafloat_scientific\":123456792.0,\"datadouble_scientific\":123456789.0}]}", + info1.data()); +#else + ASSERT_STREQ("{\"person\":[{\"name\":\"baidu\",\"id\":100,\"email\":\"welcome@baidu.com\"," + "\"phone\":[{\"number\":\"number123\",\"type\":1}],\"data\":-240000000," + "\"data32\":6,\"data64\":-1820000000,\"datadouble\":123.456," + "\"datafloat\":8.612299919,\"datau32\":60,\"datau64\":960,\"databool\":false," + "\"databyte\":\"d2VsY29tZQ==\",\"datafix32\":1,\"datafix64\":666," + "\"datasfix32\":120,\"datasfix64\":-802,\"datafloat_scientific\":123456792," + "\"datadouble_scientific\":123456789}]}", info1.data()); +#endif + + printf("----------test json to pb------------\n\n"); + + const int N = 1000; + int64_t total_tm = 0; + int64_t total_tm2 = 0; + for (int i = 0; i < N; ++i) { + + std::string info3; + AddressBook data1; + std::string error1; + const int64_t tm1 = gettimeofday_us(); + bool ret1 = json2pb::JsonToProtoMessage(info1, &data1, &error1); + const int64_t tm2 = gettimeofday_us(); + total_tm += tm2 - tm1; + ASSERT_TRUE(ret1); + + std::string error2; + const int64_t tm3 = gettimeofday_us(); + ret1 = json2pb::ProtoMessageToJson(data1, &info3, &error2); + const int64_t tm4 = gettimeofday_us(); + ASSERT_TRUE(ret1); + total_tm2 += tm4 - tm3; +#ifndef RAPIDJSON_VERSION_0_1 + ASSERT_STREQ("{\"person\":[{\"name\":\"baidu\",\"id\":100,\"email\":\"welcome@baidu.com\"," + "\"phone\":[{\"number\":\"number123\",\"type\":\"HOME\"}],\"data\":-240000000," + "\"data32\":6,\"data64\":-1820000000,\"datadouble\":123.456," + "\"datafloat\":8.612299919128418,\"datau32\":60,\"datau64\":960," + "\"databool\":false,\"databyte\":\"d2VsY29tZQ==\",\"datafix32\":1," + "\"datafix64\":666,\"datasfix32\":120,\"datasfix64\":-802," + "\"datafloat_scientific\":123456792.0,\"datadouble_scientific\":123456789.0}]}", + info3.data()); +#else + ASSERT_STREQ("{\"person\":[{\"name\":\"baidu\",\"id\":100,\"email\":\"welcome@baidu.com\"," + "\"phone\":[{\"number\":\"number123\",\"type\":\"HOME\"}],\"data\":-240000000," + "\"data32\":6,\"data64\":-1820000000,\"datadouble\":123.456," + "\"datafloat\":8.612299919,\"datau32\":60,\"datau64\":960,\"databool\":false," + "\"databyte\":\"d2VsY29tZQ==\",\"datafix32\":1,\"datafix64\":666," + "\"datasfix32\":120,\"datasfix64\":-802,\"datafloat_scientific\":123456792," + "\"datadouble_scientific\":123456789}]}", info3.data()); +#endif +} + std::cout << "json2pb=" << total_tm / N + << "us pb2json=" << total_tm2 / N << "us" + << std::endl; +} + +TEST_F(ProtobufJsonTest, pb_to_json_map_case) { + std::string json = "{\"addr\":\"baidu.com\"," + "\"numbers\":{\"tel\":123456,\"cell\":654321}," + "\"contacts\":{\"email\":\"frank@baidu.com\"," + " \"office\":\"Shanghai\"}," + "\"friends\":{\"John\":[{\"school\":\"SJTU\",\"year\":2007}]}}"; + std::string output; + std::string error; + AddressNoMap ab1; + ASSERT_TRUE(json2pb::JsonToProtoMessage(json, &ab1, &error)); + ASSERT_TRUE(json2pb::ProtoMessageToJson(ab1, &output, &error)); + ASSERT_TRUE(output.find("\"addr\":\"baidu.com\"") != std::string::npos); + + output.clear(); + AddressIntMap ab2; + ASSERT_TRUE(json2pb::JsonToProtoMessage(json, &ab2, &error)); + ASSERT_TRUE(json2pb::ProtoMessageToJson(ab2, &output, &error)); + ASSERT_TRUE(output.find("\"addr\":\"baidu.com\"") != std::string::npos); + ASSERT_TRUE(output.find("\"tel\":123456") != std::string::npos); + ASSERT_TRUE(output.find("\"cell\":654321") != std::string::npos); + + output.clear(); + AddressStringMap ab3; + ASSERT_TRUE(json2pb::JsonToProtoMessage(json, &ab3, &error)); + ASSERT_TRUE(json2pb::ProtoMessageToJson(ab3, &output, &error)); + ASSERT_TRUE(output.find("\"addr\":\"baidu.com\"") != std::string::npos); + ASSERT_TRUE(output.find("\"email\":\"frank@baidu.com\"") != std::string::npos); + ASSERT_TRUE(output.find("\"office\":\"Shanghai\"") != std::string::npos); + + output.clear(); + AddressComplex ab4; + ASSERT_TRUE(json2pb::JsonToProtoMessage(json, &ab4, &error)); + ASSERT_TRUE(json2pb::ProtoMessageToJson(ab4, &output, &error)); + ASSERT_TRUE(output.find("\"addr\":\"baidu.com\"") != std::string::npos); + ASSERT_TRUE(output.find("\"friends\":{\"John\":[{\"school\":\"SJTU\"," + "\"year\":2007}]}") != std::string::npos); +} + +TEST_F(ProtobufJsonTest, pb_to_json_encode_decode) { + JsonContextBodyEncDec json_data; + json_data.set_type(80000); + json_data.add_data_z058_array(200); + json_data.add_data_z058_array(300); + json_data.add_info("this is json data's info"); + json_data.add_info("this is a test"); + json_data.set_judge(true); + json_data.set_spur(3.45); + + ContentEncDec * content = json_data.add__z064_content_test_z037__z064_(); + content->set_uid_z042_("content info"); + content->set_distance_info_(1234.56); + + ExtEncDec* ext = content->mutable__ext_z037_t_(); + ext->set_aa_ge_z040_(160000); + ext->set_databyte_z040_std_z058__z058_string_z041_("databyte"); + ext->set_enum_z045__z045_type(ExtEncDec_PhoneTypeEncDec_WORK); + + std::string info1; + + google::protobuf::TextFormat::Printer printer; + std::string text; + printer.PrintToString(json_data, &text); + + printf("text:%s\n", text.data()); + + printf("----------test pb to json------------\n\n"); + json2pb::Pb2JsonOptions option; + option.bytes_to_base64 = true; + ASSERT_TRUE(ProtoMessageToJson(json_data, &info1, option, NULL)); +#ifndef RAPIDJSON_VERSION_0_1 + ASSERT_STREQ("{\"info\":[\"this is json data's info\",\"this is a test\"],\"type\":80000," + "\"data:array\":[200,300],\"judge\":true,\"spur\":3.45,\"@Content_Test%@\":" + "[{\"uid*\":\"content info\",\"Distance_info_\":1234.56005859375,\"_ext%T_\":" + "{\"Aa_ge(\":160000,\"databyte(std::string)\":\"ZGF0YWJ5dGU=\"," + "\"enum--type\":\"WORK\"}}]}", + info1.data()); +#else + ASSERT_STREQ("{\"info\":[\"this is json data's info\",\"this is a test\"],\"type\":80000," + "\"data:array\":[200,300],\"judge\":true,\"spur\":3.45,\"@Content_Test%@\":" + "[{\"uid*\":\"content info\",\"Distance_info_\":1234.560059,\"_ext%T_\":" + "{\"Aa_ge(\":160000,\"databyte(std::string)\":\"ZGF0YWJ5dGU=\"," + "\"enum--type\":\"WORK\"}}]}", + info1.data()); +#endif + printf("----------test json to pb------------\n\n"); + + std::string info3; + JsonContextBodyEncDec data1; + json2pb::JsonToProtoMessage(info1, &data1, NULL); + json2pb::ProtoMessageToJson(data1, &info3, NULL); + ASSERT_STREQ(info1.data(), info3.data()); + + printf("----------test single repeated pb to json array------------\n\n"); + + AddressBookEncDec single_repeated_json_data; + + auto person = single_repeated_json_data.add_person(); + person->set_id(1); + person->set_name("foo"); + *person->mutable_json_body() = json_data; + option.bytes_to_base64 = true; + + std::string text1; + printer.PrintToString(single_repeated_json_data, &text1); + + printf("text1:\n\n%s\n", text1.data()); + + std::string info4; + option.single_repeated_to_array = false; + ASSERT_TRUE(ProtoMessageToJson(single_repeated_json_data, &info4, option, NULL)); +#ifndef RAPIDJSON_VERSION_0_1 + ASSERT_STREQ("{\"person\":[" + "{\"name\":\"foo\",\"id\":1,\"json_body\":" + "{\"info\":[\"this is json data's info\",\"this is a test\"],\"type\":80000," + "\"data:array\":[200,300],\"judge\":true,\"spur\":3.45,\"@Content_Test%@\":" + "[{\"uid*\":\"content info\",\"Distance_info_\":1234.56005859375,\"_ext%T_\":" + "{\"Aa_ge(\":160000,\"databyte(std::string)\":\"ZGF0YWJ5dGU=\"," + "\"enum--type\":\"WORK\"}}]}}]}", + info4.data()); +#else + ASSERT_STREQ("{\"person\":[" + "{\"name\":\"foo\",\"id\":1,\"json_body\":" + "{\"info\":[\"this is json data's info\",\"this is a test\"],\"type\":80000," + "\"data:array\":[200,300],\"judge\":true,\"spur\":3.45,\"@Content_Test%@\":" + "[{\"uid*\":\"content info\",\"Distance_info_\":1234.560059,\"_ext%T_\":" + "{\"Aa_ge(\":160000,\"databyte(std::string)\":\"ZGF0YWJ5dGU=\"," + "\"enum--type\":\"WORK\"}}]}}]}", + info4.data()); +#endif + + std::string info5; + option.single_repeated_to_array = true; + ASSERT_TRUE(ProtoMessageToJson(single_repeated_json_data, &info5, option, NULL)); +#ifndef RAPIDJSON_VERSION_0_1 + ASSERT_STREQ("[{\"name\":\"foo\",\"id\":1,\"json_body\":" + "{\"info\":[\"this is json data's info\",\"this is a test\"],\"type\":80000," + "\"data:array\":[200,300],\"judge\":true,\"spur\":3.45,\"@Content_Test%@\":" + "[{\"uid*\":\"content info\",\"Distance_info_\":1234.56005859375,\"_ext%T_\":" + "{\"Aa_ge(\":160000,\"databyte(std::string)\":\"ZGF0YWJ5dGU=\"," + "\"enum--type\":\"WORK\"}}]}}]", + info5.data()); +#else + ASSERT_STREQ("[{\"name\":\"foo\",\"id\":1,\"json_body\":" + "{\"info\":[\"this is json data's info\",\"this is a test\"],\"type\":80000," + "\"data:array\":[200,300],\"judge\":true,\"spur\":3.45,\"@Content_Test%@\":" + "[{\"uid*\":\"content info\",\"Distance_info_\":1234.560059,\"_ext%T_\":" + "{\"Aa_ge(\":160000,\"databyte(std::string)\":\"ZGF0YWJ5dGU=\"," + "\"enum--type\":\"WORK\"}}]}}]", + info5.data()); +#endif + + printf("----------test json array to single repeated pb------------\n\n"); + + std::string info6; + AddressBookEncDec data2; + // object -> pb + json2pb::JsonToProtoMessage(info4, &data2, NULL); + json2pb::ProtoMessageToJson(data2, &info6, option, NULL); + + ASSERT_STREQ(info6.data(), info5.data()); + + std::string info7; + AddressBookEncDec data3; + json2pb::Json2PbOptions option2; + option2.array_to_single_repeated = true; + // array -> pb + json2pb::JsonToProtoMessage(info5, &data3, option2, NULL); + json2pb::ProtoMessageToJson(data3, &info7, option, NULL); + ASSERT_STREQ(info7.data(), info5.data()); + + std::string info8; + option.single_repeated_to_array = false; + json2pb::ProtoMessageToJson(data3, &info8, option, NULL); + ASSERT_STREQ(info8.data(), info4.data()); +} + +TEST_F(ProtobufJsonTest, pb_to_json_control_char_case) { + AddressBook address_book; + + Person* person = address_book.add_person(); + + person->set_id(100); + char ch = 0x01; + char name[17]; + memcpy(name, "baidu ", 6); + name[6] = ch; + char c = 0x08; + char t = 0x1A; + memcpy(name + 7, "test", 4); + name[11] = c; + name[12] = t; + memcpy(name + 13, "end", 3); + name[16] = '\0'; + person->set_name(name); + printf("name is %s\n", name); + person->set_email("welcome@baidu.com"); + + Person::PhoneNumber* phone_number = person->add_phone(); + phone_number->set_number("number123"); + phone_number->set_type(Person::HOME); + + person->set_data(-240000000); + person->set_data32(6); + person->set_data64(-1820000000); + person->set_datadouble(123.456); + person->set_datadouble_scientific(1.23456789e+08); + person->set_datafloat_scientific(1.23456789e+08); + person->set_datafloat(8.6123); + person->set_datau32(60); + person->set_datau64(960); + person->set_databool(0); + person->set_databyte("welcome to china"); + person->set_datafix32(1); + person->set_datafix64(666); + person->set_datasfix32(120); + person->set_datasfix64(-802); + + std::string info1; + + google::protobuf::TextFormat::Printer printer; + std::string text; + printer.PrintToString(*person, &text); + + printf("text:%s\n", text.data()); + + bool ret = false; + printf("----------test pb to json------------\n\n"); + { + json2pb::Pb2JsonOptions option; + option.bytes_to_base64 = false; + ret = ProtoMessageToJson(address_book, &info1, option, NULL); + ASSERT_TRUE(ret); + } + +#ifndef RAPIDJSON_VERSION_0_1 + ASSERT_STREQ("{\"person\":[{\"name\":\"baidu \\u0001test\\b\\u001Aend\",\"id\":100,\"email\":" + "\"welcome@baidu.com\"," + "\"phone\":[{\"number\":\"number123\",\"type\":\"HOME\"}],\"data\":-240000000," + "\"data32\":6,\"data64\":-1820000000,\"datadouble\":123.456," + "\"datafloat\":8.612299919128418,\"datau32\":60,\"datau64\":960," + "\"databool\":false,\"databyte\":\"welcome to china\",\"datafix32\":1," + "\"datafix64\":666,\"datasfix32\":120,\"datasfix64\":-802," + "\"datafloat_scientific\":123456792.0,\"datadouble_scientific\":123456789.0}]}", + info1.data()); +#else + ASSERT_STREQ("{\"person\":[{\"name\":\"baidu \\u0001test\\b\\u001Aend\",\"id\":100,\"email\":" + "\"welcome@baidu.com\"," + "\"phone\":[{\"number\":\"number123\",\"type\":\"HOME\"}],\"data\":-240000000," + "\"data32\":6,\"data64\":-1820000000,\"datadouble\":123.456," + "\"datafloat\":8.612299919,\"datau32\":60,\"datau64\":960,\"databool\":false," + "\"databyte\":\"welcome to china\",\"datafix32\":1,\"datafix64\":666," + "\"datasfix32\":120,\"datasfix64\":-802,\"datafloat_scientific\":123456792," + "\"datadouble_scientific\":123456789}]}", info1.data()); +#endif + + info1.clear(); + { + json2pb::Pb2JsonOptions option; + option.bytes_to_base64 = true; + ret = ProtoMessageToJson(address_book, &info1, option, NULL); + ASSERT_TRUE(ret); + } + +#ifndef RAPIDJSON_VERSION_0_1 + ASSERT_STREQ("{\"person\":[{\"name\":\"baidu \\u0001test\\b\\u001Aend\",\"id\":100,\"email\":" + "\"welcome@baidu.com\"," + "\"phone\":[{\"number\":\"number123\",\"type\":\"HOME\"}],\"data\":-240000000," + "\"data32\":6,\"data64\":-1820000000,\"datadouble\":123.456," + "\"datafloat\":8.612299919128418,\"datau32\":60,\"datau64\":960," + "\"databool\":false,\"databyte\":\"d2VsY29tZSB0byBjaGluYQ==\",\"datafix32\":1," + "\"datafix64\":666,\"datasfix32\":120,\"datasfix64\":-802," + "\"datafloat_scientific\":123456792.0,\"datadouble_scientific\":123456789.0}]}", + info1.data()); +#else + ASSERT_STREQ("{\"person\":[{\"name\":\"baidu \\u0001test\\b\\u001Aend\",\"id\":100,\"email\":" + "\"welcome@baidu.com\"," + "\"phone\":[{\"number\":\"number123\",\"type\":\"HOME\"}],\"data\":-240000000," + "\"data32\":6,\"data64\":-1820000000,\"datadouble\":123.456," + "\"datafloat\":8.612299919,\"datau32\":60,\"datau64\":960,\"databool\":false," + "\"databyte\":\"d2VsY29tZSB0byBjaGluYQ==\",\"datafix32\":1,\"datafix64\":666," + "\"datasfix32\":120,\"datasfix64\":-802,\"datafloat_scientific\":123456792," + "\"datadouble_scientific\":123456789}]}", info1.data()); +#endif + + info1.clear(); + { + json2pb::Pb2JsonOptions option; + option.enum_option = json2pb::OUTPUT_ENUM_BY_NUMBER; + option.bytes_to_base64 = false; + ret = ProtoMessageToJson(address_book, &info1, option, NULL); + ASSERT_TRUE(ret); + } + +#ifndef RAPIDJSON_VERSION_0_1 + ASSERT_STREQ("{\"person\":[{\"name\":\"baidu \\u0001test\\b\\u001Aend\",\"id\":100,\"email\":" + "\"welcome@baidu.com\"," + "\"phone\":[{\"number\":\"number123\",\"type\":1}],\"data\":-240000000," + "\"data32\":6,\"data64\":-1820000000,\"datadouble\":123.456," + "\"datafloat\":8.612299919128418,\"datau32\":60,\"datau64\":960," + "\"databool\":false,\"databyte\":\"welcome to china\",\"datafix32\":1," + "\"datafix64\":666,\"datasfix32\":120,\"datasfix64\":-802," + "\"datafloat_scientific\":123456792.0,\"datadouble_scientific\":123456789.0}]}", + info1.data()); +#else + std::cout << info1.data() << std::endl; + ASSERT_STREQ("{\"person\":[{\"name\":\"baidu \\u0001test\\b\\u001Aend\",\"id\":100,\"email\":" + "\"welcome@baidu.com\"," + "\"phone\":[{\"number\":\"number123\",\"type\":1}],\"data\":-240000000," + "\"data32\":6,\"data64\":-1820000000,\"datadouble\":123.456," + "\"datafloat\":8.612299919,\"datau32\":60,\"datau64\":960," + "\"databool\":false,\"databyte\":\"welcome to china\",\"datafix32\":1," + "\"datafix64\":666,\"datasfix32\":120,\"datasfix64\":-802," + "\"datafloat_scientific\":123456792,\"datadouble_scientific\":123456789}]}", + info1.data()); +#endif +} + +TEST_F(ProtobufJsonTest, pb_to_json_unicode_case) { + AddressBook address_book; + + Person* person = address_book.add_person(); + + person->set_id(100); + + char name[255*1024]; + for (int j = 0; j < 1024; j++) { + for (int i = 0; i < 255; i++) { + name[j*255 + i] = i + 1; + } + } + name[255*1024 - 1] = '\0'; + person->set_name(name); + person->set_data(-240000000); + person->set_data32(6); + person->set_data64(-1820000000); + person->set_datadouble(123.456); + person->set_datadouble_scientific(1.23456789e+08); + person->set_datafloat_scientific(1.23456789e+08); + person->set_datafloat(8.6123); + person->set_datau32(60); + person->set_datau64(960); + person->set_databool(0); + person->set_databyte("welcome to china"); + person->set_datafix32(1); + person->set_datafix64(666); + person->set_datasfix32(120); + person->set_datasfix64(-802); + + std::string info1; + std::string error; + + google::protobuf::TextFormat::Printer printer; + std::string text; + printer.PrintToString(*person, &text); + + printf("----------test pb to json------------\n\n"); + bool ret = json2pb::ProtoMessageToJson(address_book, &info1, &error); + ASSERT_TRUE(ret); + butil::IOBuf buf; + butil::IOBufAsZeroCopyOutputStream stream(&buf); + bool res = json2pb::ProtoMessageToJson(address_book, &stream, NULL); + ASSERT_TRUE(res); + ASSERT_TRUE(!info1.compare(buf.to_string())); +} + +TEST_F(ProtobufJsonTest, pb_to_json_edge_case) { + AddressBook address_book; + + std::string info1; + std::string error; + bool ret = json2pb::ProtoMessageToJson(address_book, &info1, &error); + ASSERT_TRUE(ret); + + info1.clear(); + Person* person = address_book.add_person(); + + person->set_id(100); + person->set_name("baidu"); + + Person::PhoneNumber* phone_number = person->add_phone(); + phone_number->set_number("1234556"); + + person->set_datadouble(-345.67); + person->set_datafloat(8.6123); + + ret = json2pb::ProtoMessageToJson(address_book, &info1, &error); + + ASSERT_TRUE(ret); + ASSERT_TRUE(error.empty()); + + std::string info3; + AddressBook data1; + std::string error1; + bool ret1 = json2pb::JsonToProtoMessage(info1, &data1, &error1); + ASSERT_TRUE(ret1); + ASSERT_TRUE(error1.empty()); + + std::string error2; + bool ret2 = json2pb::ProtoMessageToJson(data1, &info3, &error2); + ASSERT_TRUE(ret2); + ASSERT_TRUE(error2.empty()); +} + +TEST_F(ProtobufJsonTest, pb_to_json_expected_failed_case) { + AddressBook address_book; + std::string info1; + std::string error; + + Person* person = address_book.add_person(); + + person->set_name("baidu"); + person->set_email("welcome@baidu.com"); + + bool ret = json2pb::ProtoMessageToJson(address_book, &info1, &error); + ASSERT_FALSE(ret); + ASSERT_STREQ("Missing required field: addressbook.Person.id", error.data()); + + address_book.clear_person(); + person = address_book.add_person(); + + person->set_id(2); + person->set_email("welcome@baidu.com"); + + ret = json2pb::ProtoMessageToJson(address_book, &info1, &error); + ASSERT_FALSE(ret); + ASSERT_STREQ("Missing required field: addressbook.Person.name", error.data()); + + address_book.clear_person(); + person = address_book.add_person(); + + person->set_id(2); + person->set_name("name"); + person->set_email("welcome@baidu.com"); + + ret = json2pb::ProtoMessageToJson(address_book, &info1, &error); + ASSERT_FALSE(ret); + ASSERT_STREQ("Missing required field: addressbook.Person.datadouble", error.data()); +} + +TEST_F(ProtobufJsonTest, pb_to_json_perf_case) { + AddressBook address_book; + + Person* person = address_book.add_person(); + + person->set_id(100); + person->set_name("baidu"); + person->set_email("welcome@baidu.com"); + + Person::PhoneNumber* phone_number = person->add_phone(); + phone_number->set_number("number123"); + phone_number->set_type(Person::HOME); + + person->set_data(-240000000); + + person->set_data32(6); + + person->set_data64(-1820000000); + + person->set_datadouble(123.456); + + person->set_datadouble_scientific(1.23456789e+08); + + person->set_datafloat_scientific(1.23456789e+08); + + person->set_datafloat(8.6123); + + person->set_datau32(60); + + person->set_datau64(960); + + person->set_databool(0); + + person->set_databyte("welcome to china"); + + person->set_datafix32(1); + + person->set_datafix64(666); + + person->set_datasfix32(120); + + person->set_datasfix64(-802); + + std::string info1; + + google::protobuf::TextFormat::Printer printer; + std::string text; + printer.PrintToString(*person, &text); + + printf("text:%s\n", text.data()); + + printf("----------test pb to json performance------------\n\n"); + + ProfilerStart("pb_to_json_perf.prof"); + butil::Timer timer; + bool res; + float avg_time1 = 0; + float avg_time2 = 0; + const int times = 100000; + ASSERT_TRUE(json2pb::ProtoMessageToJson(address_book, &info1, NULL)); + for (int i = 0; i < times; i++) { + std::string info3; + AddressBook data1; + timer.start(); + res = json2pb::JsonToProtoMessage(info1, &data1, NULL); + timer.stop(); + avg_time1 += timer.u_elapsed(); + ASSERT_TRUE(res); + + timer.start(); + res = json2pb::ProtoMessageToJson(data1, &info3, NULL); + timer.stop(); + avg_time2 += timer.u_elapsed(); + ASSERT_TRUE(res); + } + avg_time1 /= times; + avg_time2 /= times; + ProfilerStop(); + printf("avg time to convert json to pb is %fus\n", avg_time1); + printf("avg time to convert pb to json is %fus\n", avg_time2); +} + +TEST_F(ProtobufJsonTest, pb_to_json_encode_decode_perf_case) { + JsonContextBodyEncDec json_data; + json_data.set_type(80000); + json_data.add_data_z058_array(200); + json_data.add_data_z058_array(300); + json_data.add_info("this is json data's info"); + json_data.add_info("this is a test"); + json_data.set_judge(true); + json_data.set_spur(3.45); + + ContentEncDec * content = json_data.add__z064_content_test_z037__z064_(); + content->set_uid_z042_("content info"); + content->set_distance_info_(1234.56); + + ExtEncDec* ext = content->mutable__ext_z037_t_(); + ext->set_aa_ge_z040_(160000); + ext->set_databyte_z040_std_z058__z058_string_z041_("databyte"); + ext->set_enum_z045__z045_type(ExtEncDec_PhoneTypeEncDec_WORK); + + std::string info1; + + google::protobuf::TextFormat::Printer printer; + std::string text; + printer.PrintToString(json_data, &text); + + printf("text:%s\n", text.data()); + + ASSERT_TRUE(json2pb::ProtoMessageToJson(json_data, &info1, NULL)); + + printf("----------test pb to json encode decode performance------------\n\n"); + ProfilerStart("pb_to_json_encode_decode_perf.prof"); + + butil::Timer timer; + bool res; + float avg_time1 = 0; + float avg_time2 = 0; + const int times = 100000; + for (int i = 0; i < times; i++) { + std::string info3; + JsonContextBody json_body; + timer.start(); + res = json2pb::JsonToProtoMessage(info1, &json_body, NULL); + timer.stop(); + avg_time1 += timer.u_elapsed(); + ASSERT_TRUE(res); + + timer.start(); + res = json2pb::ProtoMessageToJson(json_body, &info3, NULL); + timer.stop(); + avg_time2 += timer.u_elapsed(); + ASSERT_TRUE(res); + } + avg_time1 /= times; + avg_time2 /= times; + ProfilerStop(); + printf("avg time to convert json to pb is %fus\n", avg_time1); + printf("avg time to convert pb to json is %fus\n", avg_time2); +} + +TEST_F(ProtobufJsonTest, pb_to_json_complex_perf_case) { + + std::ifstream in("jsonout", std::ios::in); + std::ostringstream tmp; + tmp << in.rdbuf(); + std::string info3 = tmp.str(); + in.close(); + + printf("----------test pb to json performance------------\n\n"); + + std::string error; + + butil::Timer timer; + bool res; + float avg_time2 = 0; + const int times = 10000; + gss::message::gss_us_res_t data; + json2pb::Json2PbOptions option; + option.base64_to_bytes = false; + res = JsonToProtoMessage(info3, &data, option, &error); + ASSERT_TRUE(res) << error; + ProfilerStart("pb_to_json_complex_perf.prof"); + for (int i = 0; i < times; i++) { + std::string error1; + timer.start(); + butil::IOBuf buf; + butil::IOBufAsZeroCopyOutputStream stream(&buf); + res = json2pb::ProtoMessageToJson(data, &stream, &error1); + timer.stop(); + avg_time2 += timer.u_elapsed(); + ASSERT_TRUE(res); + } + avg_time2 /= times; + ProfilerStop(); + printf("avg time to convert pb to json is %fus\n", avg_time2); +} + +TEST_F(ProtobufJsonTest, pb_to_json_to_string_complex_perf_case) { + std::ifstream in("jsonout", std::ios::in); + std::ostringstream tmp; + tmp << in.rdbuf(); + std::string info3 = tmp.str(); + in.close(); + + printf("----------test pb to json performance------------\n\n"); + + std::string error; + + butil::Timer timer; + bool res; + float avg_time2 = 0; + const int times = 10000; + gss::message::gss_us_res_t data; + json2pb::Json2PbOptions option; + option.base64_to_bytes = false; + res = JsonToProtoMessage(info3, &data, option, &error); + ASSERT_TRUE(res); + ProfilerStart("pb_to_json_to_string_complex_perf.prof"); + for (int i = 0; i < times; i++) { + std::string info4; + std::string error1; + timer.start(); + std::string inf4; + res = json2pb::ProtoMessageToJson(data, &info4, &error1); + timer.stop(); + avg_time2 += timer.u_elapsed(); + ASSERT_TRUE(res); + } + avg_time2 /= times; + ProfilerStop(); + printf("avg time to convert pb to json is %fus\n", avg_time2); +} + +TEST_F(ProtobufJsonTest, encode_decode_case) { + + std::string json_key = "abcdek123lske_slkejfl_l1kdle"; + std::string field_name; + ASSERT_FALSE(json2pb::encode_name(json_key, field_name)); + ASSERT_TRUE(field_name.empty()); + std::string json_key_decode; + ASSERT_FALSE(json2pb::decode_name(field_name, json_key_decode)); + ASSERT_TRUE(json_key_decode.empty()); + + json_key = "_Afledk2e*_+%leGi___hE_Z278_t#"; + field_name.clear(); + json2pb::encode_name(json_key, field_name); + const char* encode_json_key = "_Afledk2e_Z042___Z043__Z037_leGi___hE_Z278_t_Z035_"; + ASSERT_TRUE(strcmp(field_name.data(), encode_json_key) == 0); + json_key_decode.clear(); + json2pb::decode_name(field_name, json_key_decode); + ASSERT_TRUE(strcmp(json_key_decode.data(), json_key.data()) == 0); + + json_key = "_ext%T_"; + field_name.clear(); + json2pb::encode_name(json_key, field_name); + encode_json_key = "_ext_Z037_T_"; + ASSERT_TRUE(strcmp(field_name.data(), encode_json_key) == 0); + json_key_decode.clear(); + json2pb::decode_name(field_name, json_key_decode); + ASSERT_TRUE(strcmp(json_key_decode.data(), json_key.data()) == 0); + + std::string empty_key; + std::string empty_result; + ASSERT_FALSE(json2pb::encode_name(empty_key, empty_result)); + ASSERT_TRUE(empty_result.empty() == true); + ASSERT_FALSE(json2pb::decode_name(empty_result, empty_key)); + ASSERT_TRUE(empty_key.empty() == true); +} + +TEST_F(ProtobufJsonTest, json_to_zero_copy_stream_normal_case) { + Person person; + person.set_name("hello"); + person.set_id(9); + person.set_datadouble(2.2); + person.set_datafloat(1); + butil::IOBuf iobuf; + butil::IOBufAsZeroCopyOutputStream wrapper(&iobuf); + std::string error; + ASSERT_TRUE(json2pb::ProtoMessageToJson(person, &wrapper, &error)) << error; + std::string out = iobuf.to_string(); + ASSERT_EQ("{\"name\":\"hello\",\"id\":9,\"datadouble\":2.2,\"datafloat\":1.0}", out); +} + +TEST_F(ProtobufJsonTest, zero_copy_stream_to_json_normal_case) { + butil::IOBuf iobuf; + iobuf = "{\"name\":\"hello\",\"id\":9,\"datadouble\":2.2,\"datafloat\":1.0}"; + butil::IOBufAsZeroCopyInputStream wrapper(iobuf); + Person person; + ASSERT_TRUE(json2pb::JsonToProtoMessage(&wrapper, &person)); + ASSERT_STREQ("hello", person.name().c_str()); + ASSERT_EQ(9, person.id()); + ASSERT_EQ(2.2, person.datadouble()); + ASSERT_EQ(1, person.datafloat()); +} + +TEST_F(ProtobufJsonTest, extension_case) { + std::string json = "{\"name\":\"hello\",\"id\":9,\"datadouble\":2.2,\"datafloat\":1.0,\"hobby\":\"coding\"}"; + Person person; + ASSERT_TRUE(json2pb::JsonToProtoMessage(json, &person)); + ASSERT_STREQ("coding", person.GetExtension(addressbook::hobby).data()); + std::string output; + ASSERT_TRUE(json2pb::ProtoMessageToJson(person, &output)); + ASSERT_EQ("{\"hobby\":\"coding\",\"name\":\"hello\",\"id\":9,\"datadouble\":2.2,\"datafloat\":1.0}", output); +} + +TEST_F(ProtobufJsonTest, string_to_int64) { + auto json = R"({"name":"hello", "id":9, "data": "123456", "datadouble":2.2, "datafloat":1.0})"; + Person person; + std::string err; + ASSERT_TRUE(json2pb::JsonToProtoMessage(json, &person, &err)) << err; + ASSERT_EQ(person.data(), 123456); + json = R"({"name":"hello", "id":9, "data": 1234567, "datadouble":2.2, "datafloat":1.0})"; + ASSERT_TRUE(json2pb::JsonToProtoMessage(json, &person)); + ASSERT_EQ(person.data(), 1234567); +} + +TEST_F(ProtobufJsonTest, parse_multiple_json) { + const int COUNT = 4; + std::vector expectedNames = { "tom", "bob", "jerry", "lucy" }; + std::vector expectedIds = { 33, 12, 2432, 435 }; + std::vector expectedData = { 1.0, 2.0, 3.0, 4.0 }; + std::string jsonStr; + butil::IOBuf jsonBuf; + for (int i = 0; i < COUNT; ++i) { + const std::string d = + butil::string_printf(R"( { "name":"%s", "id":%d, "datadouble":%f } )", + expectedNames[i].c_str(), + expectedIds[i], + expectedData[i]); + jsonStr.append(d); + jsonBuf.append(d); + } + + Person req; + json2pb::Json2PbOptions copt; + copt.allow_remaining_bytes_after_parsing = true; + std::string err; + + for (int i = 0; true; ++i) { + req.Clear(); + size_t offset; + if (json2pb::JsonToProtoMessage(jsonStr, &req, copt, &err, &offset)) { + jsonStr = jsonStr.substr(offset); + ASSERT_EQ(expectedNames[i], req.name()); + ASSERT_EQ(expectedIds[i], req.id()); + ASSERT_EQ(expectedData[i], req.datadouble()); + + std::cout << "parsed=" << req.ShortDebugString() << " after_offset=" << jsonStr << std::endl; + } else { + if (err.empty()) { + // document is empty + break; + } + std::cerr << "error=" << err << " offset=" << offset << std::endl; + ASSERT_FALSE(true); + } + } + + butil::IOBufAsZeroCopyInputStream stream(jsonBuf); + json2pb::ZeroCopyStreamReader reader(&stream); + + for (int i = 0; true; ++i) { + req.Clear(); + size_t offset; + auto res = json2pb::JsonToProtoMessage(&reader, &req, copt, &err, &offset); + if (res) { + ASSERT_EQ(expectedNames[i], req.name()); + ASSERT_EQ(expectedIds[i], req.id()); + ASSERT_EQ(expectedData[i], req.datadouble()); + std::string afterOffset; + jsonBuf.copy_to(&afterOffset, (size_t)-1L, offset); + std::cout << "parsed=" << req.ShortDebugString() << " after_offset=" << afterOffset << std::endl; + } else { + if (err.empty()) { + // document is empty + break; + } + std::cerr << "error=" << err << " offset=" << offset << std::endl; + ASSERT_FALSE(true) << i; + } + } +} + +TEST_F(ProtobufJsonTest, parse_multiple_json_error) { + std::string jsonStr = R"( { "name":"tom", "id":323, "datadouble":3.2 } abc )"; + butil::IOBuf jsonBuf; + jsonBuf.append(jsonStr); + + Person req; + json2pb::Json2PbOptions copt; + copt.allow_remaining_bytes_after_parsing = true; + std::string err; + size_t offset; + + ASSERT_TRUE(json2pb::JsonToProtoMessage(jsonStr, &req, copt, &err, &offset)); + jsonStr = jsonStr.substr(offset); + ASSERT_STREQ("tom", req.name().c_str()); + ASSERT_EQ(323, req.id()); + ASSERT_EQ(3.2, req.datadouble()); + + req.Clear(); + ASSERT_FALSE(json2pb::JsonToProtoMessage(jsonStr, &req, copt, &err, &offset)); + ASSERT_STREQ("Invalid json: Invalid value. [Person]", err.c_str()); + ASSERT_EQ(2ul, offset); + + butil::IOBufAsZeroCopyInputStream stream(jsonBuf); + json2pb::ZeroCopyStreamReader reader(&stream); + req.Clear(); + ASSERT_TRUE(json2pb::JsonToProtoMessage(&reader, &req, copt, &err, &offset)); + ASSERT_STREQ("tom", req.name().c_str()); + ASSERT_EQ(323, req.id()); + ASSERT_EQ(3.2, req.datadouble()); + + req.Clear(); + ASSERT_FALSE(json2pb::JsonToProtoMessage(&reader, &req, copt, &err, &offset)); + ASSERT_STREQ("Invalid json: Invalid value. [Person]", err.c_str()); + ASSERT_EQ(47ul, offset); +} + +TEST_F(ProtobufJsonTest, proto_json_to_pb) { + std::string error; + json2pb::ProtoJson2PbOptions options; + + std::string json1 = R"({"addr":"baidu.com",)" + R"("numbers":[{"key":"tel","value":123456},{"key":"cell","value":654321}]})"; + AddressIntMapStd aims; + ASSERT_FALSE(json2pb::ProtoJsonToProtoMessage(json1, &aims, options, &error)); + LOG(INFO) << "Fail to ProtoJsonToProtoMessage: " << error; + + error.clear(); + butil::IOBuf json_buf1; + json_buf1.append(json1); + butil::IOBufAsZeroCopyInputStream input_stream1(json_buf1); + ASSERT_FALSE(json2pb::ProtoJsonToProtoMessage(&input_stream1, &aims, options, &error)); + LOG(INFO) << "Fail to ProtoJsonToProtoMessage: " << error; + error.clear(); + + std::string json2 = R"({"addr":"baidu.com",)" + R"("numbers":{"tel":123456,"cell":654321}})"; + ASSERT_TRUE(json2pb::ProtoJsonToProtoMessage(json2, &aims, options, &error)) << error; + ASSERT_TRUE(aims.has_addr()); + ASSERT_EQ(aims.addr(), "baidu.com"); + ASSERT_EQ(aims.numbers_size(), 2); + ASSERT_EQ(aims.numbers().at("tel"), 123456); + ASSERT_EQ(aims.numbers().at("cell"), 654321); + + aims.Clear(); + butil::IOBuf json_buf2; + json_buf2.append(json2); + butil::IOBufAsZeroCopyInputStream input_stream2(json_buf2); + ASSERT_TRUE(json2pb::ProtoJsonToProtoMessage(&input_stream2, &aims, options, &error)) << error; + ASSERT_TRUE(aims.has_addr()); + ASSERT_EQ(aims.addr(), "baidu.com"); + ASSERT_EQ(aims.numbers_size(), 2); + ASSERT_EQ(aims.numbers().at("tel"), 123456); + ASSERT_EQ(aims.numbers().at("cell"), 654321); + + std::string json3 = R"({"addr":"baidu.com",)" + R"("contacts":{"email":"frank@apache.org","office":"Shanghai"}})"; + AddressStringMapStd asms; + ASSERT_TRUE(json2pb::ProtoJsonToProtoMessage(json3, &asms, options, &error)) << error; + ASSERT_TRUE(asms.has_addr()); + ASSERT_EQ(asms.addr(), "baidu.com"); + ASSERT_EQ(asms.contacts().size(), 2); + ASSERT_EQ(asms.contacts().at("email"), "frank@apache.org"); + ASSERT_EQ(asms.contacts().at("office"), "Shanghai"); + + asms.Clear(); + butil::IOBuf json_buf3; + json_buf3.append(json3); + butil::IOBufAsZeroCopyInputStream input_stream3(json_buf3); + ASSERT_TRUE(json2pb::ProtoJsonToProtoMessage(&input_stream3, &asms, options, &error)) << error; + ASSERT_TRUE(asms.has_addr()); + ASSERT_EQ(asms.addr(), "baidu.com"); + ASSERT_EQ(asms.contacts().size(), 2); + ASSERT_EQ(asms.contacts().at("email"), "frank@apache.org"); + ASSERT_EQ(asms.contacts().at("office"), "Shanghai"); +} + +TEST_F(ProtobufJsonTest, pb_to_proto_json) { + std::string error; + json2pb::Pb2ProtoJsonOptions options; + + AddressIntMapStd aims; + aims.set_addr("baidu.com"); + (*aims.mutable_numbers())["tel"] = 123456; + (*aims.mutable_numbers())["cell"] = 654321; + std::string json1; + ASSERT_TRUE(json2pb::ProtoMessageToJson(aims, &json1)) << error; + ASSERT_NE(json1.find(R"("addr":"baidu.com")"), std::string::npos); + ASSERT_NE(json1.find(R"("cell":654321)"), std::string::npos); + ASSERT_NE(json1.find(R"("tel":123456)"), std::string::npos); + + butil::IOBuf json_buf1; + json_buf1.append(json1); + butil::IOBufAsZeroCopyOutputStream output_stream1(&json_buf1); + ASSERT_TRUE(json2pb::ProtoMessageToJson(aims, &output_stream1, &error)) << error; + json1 = json_buf1.to_string(); + ASSERT_NE(json1.find(R"("addr":"baidu.com")"), std::string::npos); + ASSERT_NE(json1.find(R"("cell":654321)"), std::string::npos); + ASSERT_NE(json1.find(R"("tel":123456)"), std::string::npos); + + AddressStringMapStd asms; + asms.set_addr("baidu.com"); + (*asms.mutable_contacts())["email"] = "frank@apache.org"; + (*asms.mutable_contacts())["office"] = "Shanghai"; + std::string json2; + ASSERT_TRUE(json2pb::ProtoMessageToJson(asms, &json2)) << error; + ASSERT_NE(json2.find(R"("addr":"baidu.com")"), std::string::npos); + ASSERT_NE(json2.find(R"("email":"frank@apache.org")"), std::string::npos); + ASSERT_NE(json2.find(R"("office":"Shanghai")"), std::string::npos); + + butil::IOBuf json_buf2; + json_buf2.append(json2); + butil::IOBufAsZeroCopyOutputStream output_stream2(&json_buf2); + ASSERT_TRUE(json2pb::ProtoMessageToJson(asms, &output_stream2, &error)) << error; + json2 = json_buf2.to_string(); + ASSERT_NE(json2.find(R"("addr":"baidu.com")"), std::string::npos); + ASSERT_NE(json2.find(R"("email":"frank@apache.org")"), std::string::npos); + ASSERT_NE(json2.find(R"("office":"Shanghai")"), std::string::npos); +} + +} // namespace diff --git a/test/brpc_public_pbrpc_protocol_unittest.cpp b/test/brpc_public_pbrpc_protocol_unittest.cpp new file mode 100644 index 0000000..e92f5ed --- /dev/null +++ b/test/brpc_public_pbrpc_protocol_unittest.cpp @@ -0,0 +1,330 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: Sun Jul 13 15:04:18 CST 2014 + +#include +#include +#include +#include +#include +#include +#include "butil/time.h" +#include "butil/macros.h" +#include "brpc/socket.h" +#include "brpc/acceptor.h" +#include "brpc/server.h" +#include "brpc/policy/public_pbrpc_meta.pb.h" +#include "brpc/policy/public_pbrpc_protocol.h" +#include "brpc/policy/most_common_message.h" +#include "brpc/controller.h" +#include "echo.pb.h" + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + return RUN_ALL_TESTS(); +} + +namespace { + +static const std::string EXP_REQUEST = "hello"; +static const std::string EXP_RESPONSE = "world"; + +static const std::string MOCK_CREDENTIAL = "mock credential"; +static const std::string MOCK_USER = "mock user"; + +class MyAuthenticator : public brpc::Authenticator { +public: + MyAuthenticator() {} + + int GenerateCredential(std::string* auth_str) const { + *auth_str = MOCK_CREDENTIAL; + return 0; + } + + int VerifyCredential(const std::string& auth_str, + const butil::EndPoint&, + brpc::AuthContext* ctx) const { + EXPECT_EQ(MOCK_CREDENTIAL, auth_str); + ctx->set_user(MOCK_USER); + return 0; + } +}; + +class MyEchoService : public ::test::EchoService { + void Echo(::google::protobuf::RpcController* cntl_base, + const ::test::EchoRequest* req, + ::test::EchoResponse* res, + ::google::protobuf::Closure* done) { + brpc::Controller* cntl = static_cast(cntl_base); + brpc::ClosureGuard done_guard(done); + + if (req->close_fd()) { + cntl->CloseConnection("Close connection according to request"); + return; + } + EXPECT_EQ(EXP_REQUEST, req->message()); + res->set_message(EXP_RESPONSE); + } +}; + +class PublicPbrpcTest : public ::testing::Test{ +protected: + PublicPbrpcTest() { + EXPECT_EQ(0, _server.AddService( + &_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + // Hack: Regard `_server' as running + _server._status = brpc::Server::RUNNING; + _server._options.nshead_service = + new brpc::policy::PublicPbrpcServiceAdaptor; + // public_pbrpc doesn't support authentication + // _server._options.auth = &_auth; + + EXPECT_EQ(0, pipe(_pipe_fds)); + + brpc::SocketId id; + brpc::SocketOptions options; + options.fd = _pipe_fds[1]; + EXPECT_EQ(0, brpc::Socket::Create(options, &id)); + EXPECT_EQ(0, brpc::Socket::Address(id, &_socket)); + }; + + virtual ~PublicPbrpcTest() {}; + virtual void SetUp() {}; + virtual void TearDown() {}; + + void VerifyMessage(brpc::InputMessageBase* msg) { + if (msg->_socket == NULL) { + _socket->ReAddress(&msg->_socket); + } + msg->_arg = &_server; + EXPECT_TRUE(brpc::policy::VerifyNsheadRequest(msg)); + } + + void ProcessMessage(void (*process)(brpc::InputMessageBase*), + brpc::InputMessageBase* msg, bool set_eof) { + if (msg->_socket == NULL) { + _socket->ReAddress(&msg->_socket); + } + msg->_arg = &_server; + _socket->PostponeEOF(); + if (set_eof) { + _socket->SetEOF(); + } + (*process)(msg); + } + + brpc::policy::MostCommonMessage* MakeRequestMessage( + brpc::policy::PublicPbrpcRequest* meta) { + brpc::policy::MostCommonMessage* msg = + brpc::policy::MostCommonMessage::Get(); + brpc::nshead_t head; + msg->meta.append(&head, sizeof(head)); + + if (meta->requestbody_size() > 0) { + test::EchoRequest req; + req.set_message(EXP_REQUEST); + EXPECT_TRUE(req.SerializeToString( + meta->mutable_requestbody(0)->mutable_serialized_request())); + } + butil::IOBufAsZeroCopyOutputStream meta_stream(&msg->payload); + EXPECT_TRUE(meta->SerializeToZeroCopyStream(&meta_stream)); + return msg; + } + + brpc::policy::MostCommonMessage* MakeResponseMessage( + brpc::policy::PublicPbrpcResponse* meta) { + brpc::policy::MostCommonMessage* msg = + brpc::policy::MostCommonMessage::Get(); + brpc::nshead_t head; + msg->meta.append(&head, sizeof(head)); + + if (meta->responsebody_size() > 0) { + test::EchoResponse res; + res.set_message(EXP_RESPONSE); + EXPECT_TRUE(res.SerializeToString( + meta->mutable_responsebody(0)->mutable_serialized_response())); + } + butil::IOBufAsZeroCopyOutputStream meta_stream(&msg->payload); + EXPECT_TRUE(meta->SerializeToZeroCopyStream(&meta_stream)); + return msg; + } + + void CheckResponseCode(bool expect_empty, int expect_code) { + int bytes_in_pipe = 0; + ioctl(_pipe_fds[0], FIONREAD, &bytes_in_pipe); + if (expect_empty) { + EXPECT_EQ(0, bytes_in_pipe); + return; + } + + EXPECT_GT(bytes_in_pipe, 0); + butil::IOPortal buf; + EXPECT_EQ((ssize_t)bytes_in_pipe, + buf.append_from_file_descriptor(_pipe_fds[0], 1024)); + brpc::ParseResult pr = brpc::policy::ParseNsheadMessage(&buf, NULL, false, NULL); + EXPECT_EQ(brpc::PARSE_OK, pr.error()); + brpc::policy::MostCommonMessage* msg = + static_cast(pr.message()); + + brpc::policy::PublicPbrpcResponse meta; + butil::IOBufAsZeroCopyInputStream meta_stream(msg->payload); + EXPECT_TRUE(meta.ParseFromZeroCopyStream(&meta_stream)); + EXPECT_EQ(expect_code, meta.responsehead().code()); + } + + int _pipe_fds[2]; + brpc::SocketUniquePtr _socket; + brpc::Server _server; + + MyEchoService _svc; + MyAuthenticator _auth; +}; + +TEST_F(PublicPbrpcTest, process_request_failed_socket) { + brpc::policy::PublicPbrpcRequest meta; + brpc::policy::RequestBody* body = meta.add_requestbody(); + body->set_service("EchoService"); + body->set_method_id(0); + body->set_id(0); + brpc::policy::MostCommonMessage* msg = MakeRequestMessage(&meta); + _socket->SetFailed(); + ProcessMessage(brpc::policy::ProcessNsheadRequest, msg, false); + ASSERT_EQ(0ll, _server._nerror_bvar.get_value()); + CheckResponseCode(true, 0); +} + +TEST_F(PublicPbrpcTest, process_request_logoff) { + brpc::policy::PublicPbrpcRequest meta; + brpc::policy::RequestBody* body = meta.add_requestbody(); + body->set_service("EchoService"); + body->set_method_id(0); + body->set_id(0); + brpc::policy::MostCommonMessage* msg = MakeRequestMessage(&meta); + _server._status = brpc::Server::READY; + ProcessMessage(brpc::policy::ProcessNsheadRequest, msg, false); + ASSERT_EQ(1ll, _server._nerror_bvar.get_value()); + CheckResponseCode(false, brpc::ELOGOFF); +} + +TEST_F(PublicPbrpcTest, process_request_wrong_method) { + brpc::policy::PublicPbrpcRequest meta; + brpc::policy::RequestBody* body = meta.add_requestbody(); + body->set_service("EchoService"); + body->set_method_id(10); + body->set_id(0); + brpc::policy::MostCommonMessage* msg = MakeRequestMessage(&meta); + ProcessMessage(brpc::policy::ProcessNsheadRequest, msg, false); + ASSERT_EQ(1ll, _server._nerror_bvar.get_value()); + ASSERT_FALSE(_socket->Failed()); +} + +TEST_F(PublicPbrpcTest, process_response_after_eof) { + brpc::policy::PublicPbrpcResponse meta; + test::EchoResponse res; + brpc::Controller cntl; + brpc::policy::ResponseBody* body = meta.add_responsebody(); + body->set_id(cntl.call_id().value); + meta.mutable_responsehead()->set_code(0); + cntl._response = &res; + brpc::policy::MostCommonMessage* msg = MakeResponseMessage(&meta); + ProcessMessage(brpc::policy::ProcessPublicPbrpcResponse, msg, true); + ASSERT_EQ(EXP_RESPONSE, res.message()); + ASSERT_TRUE(_socket->Failed()); +} + +TEST_F(PublicPbrpcTest, process_response_error_code) { + const int ERROR_CODE = 12345; + brpc::policy::PublicPbrpcResponse meta; + brpc::Controller cntl; + brpc::policy::ResponseBody* body = meta.add_responsebody(); + body->set_id(cntl.call_id().value); + meta.mutable_responsehead()->set_code(ERROR_CODE); + brpc::policy::MostCommonMessage* msg = MakeResponseMessage(&meta); + ProcessMessage(brpc::policy::ProcessPublicPbrpcResponse, msg, false); + ASSERT_EQ(ERROR_CODE, cntl.ErrorCode()); +} + +TEST_F(PublicPbrpcTest, complete_flow) { + butil::IOBuf request_buf; + butil::IOBuf total_buf; + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + cntl._response = &res; + + // Send request + req.set_message(EXP_REQUEST); + cntl.set_request_compress_type(brpc::COMPRESS_TYPE_SNAPPY); + brpc::policy::SerializePublicPbrpcRequest(&request_buf, &cntl, &req); + ASSERT_FALSE(cntl.Failed()); + brpc::policy::PackPublicPbrpcRequest( + &total_buf, NULL, cntl.call_id().value, + test::EchoService::descriptor()->method(0), &cntl, request_buf, &_auth); + ASSERT_FALSE(cntl.Failed()); + + // Verify and handle request + brpc::ParseResult req_pr = + brpc::policy::ParseNsheadMessage(&total_buf, NULL, false, NULL); + ASSERT_EQ(brpc::PARSE_OK, req_pr.error()); + brpc::InputMessageBase* req_msg = req_pr.message(); + VerifyMessage(req_msg); + ProcessMessage(brpc::policy::ProcessNsheadRequest, req_msg, false); + + // Read response from pipe + butil::IOPortal response_buf; + response_buf.append_from_file_descriptor(_pipe_fds[0], 1024); + brpc::ParseResult res_pr = + brpc::policy::ParseNsheadMessage(&response_buf, NULL, false, NULL); + ASSERT_EQ(brpc::PARSE_OK, res_pr.error()); + brpc::InputMessageBase* res_msg = res_pr.message(); + ProcessMessage(brpc::policy::ProcessPublicPbrpcResponse, res_msg, false); + + ASSERT_FALSE(cntl.Failed()); + ASSERT_EQ(EXP_RESPONSE, res.message()); +} + +TEST_F(PublicPbrpcTest, close_in_callback) { + butil::IOBuf request_buf; + butil::IOBuf total_buf; + brpc::Controller cntl; + test::EchoRequest req; + + // Send request + req.set_message(EXP_REQUEST); + req.set_close_fd(true); + brpc::policy::SerializePublicPbrpcRequest(&request_buf, &cntl, &req); + ASSERT_FALSE(cntl.Failed()); + brpc::policy::PackPublicPbrpcRequest( + &total_buf, NULL, cntl.call_id().value, + test::EchoService::descriptor()->method(0), &cntl, request_buf, &_auth); + ASSERT_FALSE(cntl.Failed()); + + // Handle request + brpc::ParseResult req_pr = + brpc::policy::ParseNsheadMessage(&total_buf, NULL, false, NULL); + ASSERT_EQ(brpc::PARSE_OK, req_pr.error()); + brpc::InputMessageBase* req_msg = req_pr.message(); + ProcessMessage(brpc::policy::ProcessNsheadRequest, req_msg, false); + + // Socket should be closed + ASSERT_TRUE(_socket->Failed()); +} +} //namespace diff --git a/test/brpc_rdma_unittest.cpp b/test/brpc_rdma_unittest.cpp new file mode 100644 index 0000000..43c6edf --- /dev/null +++ b/test/brpc_rdma_unittest.cpp @@ -0,0 +1,2571 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#if BRPC_WITH_RDMA +#include +#include "butil/endpoint.h" +#include "butil/fd_guard.h" +#include "butil/iobuf.h" +#include "butil/sys_byteorder.h" +#include "butil/files/temp_file.h" +#include "brpc/acceptor.h" +#include "brpc/channel.h" +#include "brpc/controller.h" +#include "brpc/server.h" +#include "brpc/socket.h" +#include "brpc/errno.pb.h" +#include "brpc/parallel_channel.h" +#include "brpc/selective_channel.h" +#include "brpc/rdma_transport.h" +#include "brpc/rdma/block_pool.h" +#include "brpc/rdma/rdma_endpoint.h" +#include "brpc/rdma/rdma_handshake.h" +#include "brpc/rdma/rdma_handshake.pb.h" +#include "brpc/rdma/rdma_helper.h" +#include "echo.pb.h" + +static const int PORT = 8713; + +using namespace brpc; + +namespace brpc { + +DECLARE_int64(socket_max_unwritten_bytes); +DECLARE_bool(log_idle_connection_close); +DEFINE_bool(rdma_test_enable, false, "Enable tests requring rdma runtime."); + +namespace rdma { + +extern const uint16_t RDMA_HELLO_V2_VERSION; +extern const uint16_t RDMA_IMPL_V2_VERSION; + +DECLARE_bool(rdma_trace_verbose); +DECLARE_int32(rdma_memory_pool_max_regions); +DECLARE_int32(rdma_client_handshake_version); + +extern ibv_cq* (*IbvCreateCq)(ibv_context*, int, void*, ibv_comp_channel*, int); +extern int (*IbvDestroyCq)(ibv_cq*); +extern ibv_qp* (*IbvCreateQp)(ibv_pd*, ibv_qp_init_attr*); +extern int (*IbvModifyQp)(ibv_qp*, ibv_qp_attr*, ibv_qp_attr_mask); +extern int (*IbvQueryQp)(ibv_qp*, ibv_qp_attr*, ibv_qp_attr_mask, ibv_qp_init_attr*); +extern int (*IbvDestroyQp)(ibv_qp*); +extern butil::atomic g_rdma_available; +extern bool g_skip_rdma_init; +} // namespace rdma +} // namespace brpc + +static std::string g_ip = "127.0.0.1"; +static butil::EndPoint g_ep; + +class MyEchoService : public ::test::EchoService { + void Echo(google::protobuf::RpcController* cntl_base, + const ::test::EchoRequest* req, + ::test::EchoResponse* res, + google::protobuf::Closure* done) { + Controller* cntl = static_cast(cntl_base); + ClosureGuard done_guard(done); + if (req->server_fail()) { + cntl->SetFailed(req->server_fail(), "Server fail1"); + cntl->SetFailed(req->server_fail(), "Server fail2"); + return; + } + if (req->close_fd()) { + usleep(1); + LOG(INFO) << "close fd..."; + cntl->CloseConnection("Close connection according to request"); + return; + } + if (req->sleep_us() > 0) { + LOG(INFO) << "sleep " << req->sleep_us() << "us..."; + bthread_usleep(req->sleep_us()); + } + res->set_message("MyEchoService"); + if (req->code() != 0) { + res->add_code_list(req->code()); + } + cntl->response_attachment().append(cntl->request_attachment()); + } +}; + +class RdmaTest : public ::testing::Test { +protected: + RdmaTest() { + butil::ip_t ip; + EXPECT_EQ(0, butil::str2ip(g_ip.c_str(), &ip)); + butil::EndPoint ep(ip, PORT); + g_ep = ep; + EXPECT_EQ(0, _server_list.save(butil::endpoint2str(g_ep).c_str())); + _naming_url = std::string("File://") + _server_list.fname(); + _server.AddService(&_svc, SERVER_DOESNT_OWN_SERVICE); + } + ~RdmaTest() { } + + virtual void SetUp() { } + + virtual void TearDown() { + rdma::DumpMemoryPoolInfo(std::cout); + } + +protected: + void StartServer(bool use_rdma = true) { + ServerOptions options; + options.enabled_protocols = "baidu_std"; + options.socket_mode = use_rdma ? SOCKET_MODE_RDMA : SOCKET_MODE_TCP; + options.idle_timeout_sec = 5; + options.max_concurrency = 0; + options.internal_port = -1; + EXPECT_EQ(0, _server.Start(PORT, &options)); + } + + void StopServer() { + _server.Stop(0); + _server.Join(); + } + + Socket* GetSocketFromServer(size_t index) { + std::vector sids; + _server._am->ListConnections(&sids); + if (index >= sids.size()) { + return NULL; + } + SocketUniquePtr s; + if (Socket::Address(sids[index], &s) == 0) { + return s.get(); + } + return NULL; + } + + butil::TempFile _server_list; + std::string _naming_url; + + Server _server; + MyEchoService _svc; +}; + +// Parameterized fixture used by upper-layer RPC tests that have no +// dependency on the handshake wire format. The parameter is the +// client-side handshake protocol version (FLAGS_rdma_client_handshake_version), +// so every TEST_P below is automatically executed once per supported +// version. Add a new version to INSTANTIATE_TEST_SUITE_P at the bottom +// of this file and these RPC tests will gain coverage for free. +class RdmaRpcTest : public RdmaTest, + public ::testing::WithParamInterface { +protected: + void SetUp() override { + RdmaTest::SetUp(); + _saved_handshake_version = rdma::FLAGS_rdma_client_handshake_version; + rdma::FLAGS_rdma_client_handshake_version = GetParam(); + } + void TearDown() override { + rdma::FLAGS_rdma_client_handshake_version = _saved_handshake_version; + RdmaTest::TearDown(); + } + +private: + int _saved_handshake_version = 2; +}; + +TEST_F(RdmaTest, client_close_before_hello_send) { + StartServer(); + + sockaddr_in addr; + bzero((char*)&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + + butil::fd_guard sockfd(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd >= 0); + ASSERT_EQ(0, connect(sockfd, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); // wait for server to handle the msg + Socket* s = GetSocketFromServer(0); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + close(sockfd); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + StopServer(); +} + +TEST_F(RdmaTest, client_hello_msg_invalid_magic_str) { + StartServer(); + + sockaddr_in addr; + bzero((char*)&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + + butil::fd_guard sockfd(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd >= 0); + ASSERT_EQ(0, connect(sockfd, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); // wait for server to handle the msg + Socket* s = GetSocketFromServer(0); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + memcpy(data, "PRPC", 4); // send as normal baidu_std protocol + ASSERT_EQ(4, write(sockfd, data, 4)); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::FALLBACK_TCP, static_cast(s->_transport.get())->_rdma_ep->_state); + + StopServer(); +} + +TEST_F(RdmaTest, client_close_during_hello_send) { + StartServer(); + + sockaddr_in addr; + bzero((char*)&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + Socket* s = NULL; + uint8_t data[8]; + + butil::fd_guard sockfd1(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd1 >= 0); + ASSERT_EQ(0, connect(sockfd1, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); // wait for server to handle the msg + s = GetSocketFromServer(0); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + memcpy(data, "RD", 2); + ASSERT_EQ(2, write(sockfd1, data, 2)); // break in magic str + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + close(sockfd1); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + butil::fd_guard sockfd2(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd2 >= 0); + ASSERT_EQ(0, connect(sockfd2, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); // wait for server to handle the msg + s = GetSocketFromServer(0); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + memcpy(data, "RDMA", 4); + ASSERT_EQ(4, write(sockfd2, data, 4)); // break after magic str + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + close(sockfd2); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + butil::fd_guard sockfd3(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd3 >= 0); + ASSERT_EQ(0, connect(sockfd3, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); // wait for server to handle the msg + s = GetSocketFromServer(0); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + memcpy(data, "RDMA", 4); + memset(data + 4, 0, 4); + ASSERT_EQ(8, write(sockfd3, data, 8)); // break after magic str + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + close(sockfd3); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + StopServer(); +} + +TEST_F(RdmaTest, client_hello_msg_invalid_len) { + StartServer(); + + sockaddr_in addr; + bzero((char*)&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + Socket* s = NULL; + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + + butil::fd_guard sockfd1(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd1 >= 0); + ASSERT_EQ(0, connect(sockfd1, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); // wait for server to handle the msg + s = GetSocketFromServer(0); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + memcpy(data, "RDMA", 4); + ASSERT_EQ(4, write(sockfd1, data, 4)); // Write magic string. + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + memset(data + 4, 0, 36); + ASSERT_EQ(36, write(sockfd1, data + 4, 36)); // Write invalid length. + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + butil::fd_guard sockfd2(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd2 >= 0); + ASSERT_EQ(0, connect(sockfd2, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); // wait for server to handle the msg + s = GetSocketFromServer(0); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + memcpy(data, "RDMA", 4); + ASSERT_EQ(4, write(sockfd2, data, 4)); // Write magic string. + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + uint16_t len = butil::HostToNet16(35); + memcpy(data + 4, &len, sizeof(len)); + memset(data + 6, 0, 34); + ASSERT_EQ(36, write(sockfd2, data + 4, 36)); // write invalid length + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + StopServer(); +} + +TEST_F(RdmaTest, client_hello_msg_invalid_version) { + StartServer(); + + sockaddr_in addr; + bzero((char*)&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + Socket* s = NULL; + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + uint16_t len = butil::HostToNet16(rdma::v2_wire::HELLO_MSG_LEN_MIN); + uint16_t ver = butil::HostToNet16(1); + + butil::fd_guard sockfd1(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd1 >= 0); + ASSERT_EQ(0, connect(sockfd1, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); // wait for server to handle the msg + s = GetSocketFromServer(0); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + memcpy(data, "RDMA", 4); + ASSERT_EQ(4, write(sockfd1, data, 4)); // Write magic string. + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + memcpy(data + 4, &len, 2); + memset(data + 6, 0, 34); + memcpy(data + 6, &ver, 2); // hello_ver == 1, impl_ver == 0 + // Write the 36B base starting at data + 4 (NOT data). Pre-Step-1 this + // UT mistakenly wrote `data, 36` which included the leftover "RDMA" + // magic at data[0..4); the server parsed it as msg_len = 0x5244 and + // happened to fall through to NegotiationValid (which then failed on + // hello_ver). Now that Step 1 enforces a HELLO_MSG_LEN_MAX upper bound, + // such an oversized msg_len would be rejected before reaching the + // version check, breaking the intent of this UT. + ASSERT_EQ(36, write(sockfd1, data + 4, 36)); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_ACK_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(RdmaTransport::RDMA_OFF, static_cast(s->_transport.get())->_rdma_state); + uint32_t flags = 0; + ASSERT_EQ(sizeof(flags), write(sockfd1, &flags, sizeof(flags))); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::FALLBACK_TCP, static_cast(s->_transport.get())->_rdma_ep->_state); + sockfd1.reset(-1); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + butil::fd_guard sockfd2(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd2 >= 0); + ASSERT_EQ(0, connect(sockfd2, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); // wait for server to handle the msg + s = GetSocketFromServer(0); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + memcpy(data, "RDMA", 4); + ASSERT_EQ(4, write(sockfd2, data, 4)); // Write magic string. + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + memcpy(data + 4, &len, 2); + memset(data + 6, 0, 32); + memcpy(data + 8, &ver, 2); // hello_ver == 0, impl_ver == 1 + // See comment above on `write(sockfd1, data + 4, 36)` for why we + // write from data + 4 instead of data. + ASSERT_EQ(36, write(sockfd2, data + 4, 36)); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_ACK_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(RdmaTransport::RDMA_OFF, static_cast(s->_transport.get())->_rdma_state); + ASSERT_EQ(sizeof(flags), write(sockfd2, &flags, sizeof(flags))); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::FALLBACK_TCP, static_cast(s->_transport.get())->_rdma_ep->_state); + sockfd2.reset(-1); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + StopServer(); +} + +TEST_F(RdmaTest, client_hello_msg_invalid_sq_rq_block_size) { + StartServer(); + + sockaddr_in addr; + bzero((char*)&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + Socket* s = NULL; + uint32_t flags = butil::HostToNet32(0); + rdma::v2_wire::HelloMessage msg{}; + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + msg.msg_len = rdma::v2_wire::HELLO_MSG_LEN_MIN; + msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION; + msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION; + + msg.sq_size = 10; + msg.rq_size = 16; + msg.block_size = 8192; + memcpy(data, "RDMA", 4); + msg.Serialize(data + 4); + butil::fd_guard sockfd1(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd1 >= 0); + ASSERT_EQ(0, connect(sockfd1, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); // wait for server to handle the msg + s = GetSocketFromServer(0); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(4, write(sockfd1, data, 4)); // Write magic string. + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(36, write(sockfd1, data + 4, 36)); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_ACK_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(RdmaTransport::RDMA_OFF, static_cast(s->_transport.get())->_rdma_state); + ASSERT_EQ(sizeof(flags), write(sockfd1, &flags, sizeof(flags))); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::FALLBACK_TCP, static_cast(s->_transport.get())->_rdma_ep->_state); + sockfd1.reset(-1); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + msg.sq_size = 16; + msg.rq_size = 10; + msg.block_size = 8192; + memcpy(data, "RDMA", 4); + msg.Serialize(data + 4); + butil::fd_guard sockfd2(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd2 >= 0); + ASSERT_EQ(0, connect(sockfd2, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); // wait for server to handle the msg + s = GetSocketFromServer(0); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(4, write(sockfd2, data, 4)); // Write magic string. + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(36, write(sockfd2, data + 4, 36)); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_ACK_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(RdmaTransport::RDMA_OFF, static_cast(s->_transport.get())->_rdma_state); + ASSERT_EQ(sizeof(flags), write(sockfd2, &flags, sizeof(flags))); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::FALLBACK_TCP, static_cast(s->_transport.get())->_rdma_ep->_state); + sockfd2.reset(-1); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + msg.sq_size = 16; + msg.rq_size = 16; + msg.block_size = 1000; + memcpy(data, "RDMA", 4); + msg.Serialize(data + 4); + butil::fd_guard sockfd3(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd3 >= 0); + ASSERT_EQ(0, connect(sockfd3, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); // wait for server to handle the msg + s = GetSocketFromServer(0); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(4, write(sockfd3, data, 4)); // Write magic string. + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(36, write(sockfd3, data + 4, 36)); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_ACK_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(RdmaTransport::RDMA_OFF, static_cast(s->_transport.get())->_rdma_state); + ASSERT_EQ(sizeof(flags), write(sockfd3, &flags, sizeof(flags))); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::FALLBACK_TCP, static_cast(s->_transport.get())->_rdma_ep->_state); + sockfd3.reset(-1); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + StopServer(); +} + +TEST_F(RdmaTest, client_close_after_qp_build) { + StartServer(); + + sockaddr_in addr; + bzero((char*)&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + Socket* s = NULL; + rdma::v2_wire::HelloMessage msg{}; + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + msg.msg_len = rdma::v2_wire::HELLO_MSG_LEN_MIN; + msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION; + msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION; + msg.sq_size = 16; + msg.rq_size = 16; + msg.block_size = 8192; + msg.qp_num = 0; + msg.gid = rdma::GetRdmaGid(); + memcpy(data, "RDMA", 4); + msg.Serialize(data + 4); + + butil::fd_guard sockfd1(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd1 >= 0); + ASSERT_EQ(0, connect(sockfd1, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); // wait for server to handle the msg + s = GetSocketFromServer(0); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(40, write(sockfd1, data, 40)); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_ACK_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + close(sockfd1); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + StopServer(); +} + +TEST_F(RdmaTest, client_close_during_ack_send) { + StartServer(); + + sockaddr_in addr; + bzero((char*)&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + Socket* s = NULL; + rdma::v2_wire::HelloMessage msg{}; + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + msg.msg_len = rdma::v2_wire::HELLO_MSG_LEN_MIN; + msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION; + msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION; + msg.sq_size = 16; + msg.rq_size = 16; + msg.block_size = 8192; + msg.qp_num = 0; + msg.gid = rdma::GetRdmaGid(); + memcpy(data, "RDMA", 4); + msg.Serialize(data + 4); + + butil::fd_guard sockfd1(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd1 >= 0); + ASSERT_EQ(0, connect(sockfd1, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); // wait for server to handle the msg + s = GetSocketFromServer(0); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(4, write(sockfd1, data, 4)); // Write magic string. + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(36, write(sockfd1, data + 4, 36)); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_ACK_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + uint32_t flags = butil::HostToNet32(1); + ASSERT_EQ(sizeof(flags), write(sockfd1, &flags, sizeof(flags))); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::ESTABLISHED, static_cast(s->_transport.get())->_rdma_ep->_state); + close(sockfd1); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + StopServer(); +} + +TEST_F(RdmaTest, client_close_after_ack_send) { + StartServer(); + + sockaddr_in addr; + bzero((char*)&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + Socket* s = NULL; + rdma::v2_wire::HelloMessage msg{}; + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + msg.msg_len = rdma::v2_wire::HELLO_MSG_LEN_MIN; + msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION; + msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION; + msg.sq_size = 16; + msg.rq_size = 16; + msg.block_size = 8192; + msg.qp_num = 0; + msg.gid = rdma::GetRdmaGid(); + memcpy(data, "RDMA", 4); + msg.Serialize(data + 4); + + butil::fd_guard sockfd1(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd1 >= 0); + ASSERT_EQ(0, connect(sockfd1, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); // wait for server to handle the msg + s = GetSocketFromServer(0); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(4, write(sockfd1, data, 4)); // Write magic string. + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(36, write(sockfd1, data + 4, 36)); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_ACK_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + uint32_t flags = butil::HostToNet32(0); + ASSERT_EQ(sizeof(flags), write(sockfd1, &flags, sizeof(flags))); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::FALLBACK_TCP, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(RdmaTransport::RDMA_OFF, static_cast(s->_transport.get())->_rdma_state); + close(sockfd1); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + butil::fd_guard sockfd2(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd2 >= 0); + ASSERT_EQ(0, connect(sockfd2, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); // wait for server to handle the msg + s = GetSocketFromServer(0); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(4, write(sockfd2, data, 4)); // Write magic string. + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(36, write(sockfd2, data + 4, 36)); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_ACK_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + flags = butil::HostToNet32(1); + ASSERT_EQ(sizeof(flags), write(sockfd2, &flags, sizeof(flags))); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::ESTABLISHED, static_cast(s->_transport.get())->_rdma_ep->_state); + close(sockfd2); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + StopServer(); +} + +TEST_F(RdmaTest, client_send_data_on_tcp_after_ack_send) { + StartServer(); + + sockaddr_in addr; + bzero((char*)&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + Socket* s = NULL; + rdma::v2_wire::HelloMessage msg{}; + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + msg.msg_len = rdma::v2_wire::HELLO_MSG_LEN_MIN; + msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION; + msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION; + msg.sq_size = 16; + msg.rq_size = 16; + msg.block_size = 8192; + msg.qp_num = 0; + msg.gid = rdma::GetRdmaGid(); + memcpy(data, "RDMA", 4); + msg.Serialize(data + 4); + + butil::fd_guard sockfd1(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd1 >= 0); + ASSERT_EQ(0, connect(sockfd1, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); // wait for server to handle the msg + s = GetSocketFromServer(0); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(4, write(sockfd1, data, 4)); // Write magic string. + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(36, write(sockfd1, data + 4, 36)); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_ACK_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + uint32_t flags = butil::HostToNet32(0); + ASSERT_EQ(sizeof(flags), write(sockfd1, &flags, sizeof(flags))); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::FALLBACK_TCP, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(sizeof(flags), write(sockfd1, &flags, sizeof(flags))); + usleep(100000); + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + butil::fd_guard sockfd2(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd2 >= 0); + ASSERT_EQ(0, connect(sockfd2, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); // wait for server to handle the msg + s = GetSocketFromServer(0); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(4, write(sockfd2, data, 4)); // Write magic string. + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(36, write(sockfd2, data + 4, 36)); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::S_ACK_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + flags = butil::HostToNet32(1); + ASSERT_EQ(sizeof(flags), write(sockfd2, &flags, sizeof(flags))); + usleep(100000); // wait for server to handle the msg + ASSERT_EQ(rdma::RdmaEndpoint::ESTABLISHED, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(sizeof(flags), write(sockfd2, &flags, sizeof(flags))); + usleep(100000); + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + StopServer(); +} + +TEST_F(RdmaTest, server_miss_before_hello_send) { + butil::fd_guard sockfd(butil::tcp_listen(g_ep)); + EXPECT_TRUE(sockfd >= 0); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done); + + usleep(100000); + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl._single_server_id, &s)); + ASSERT_EQ(rdma::RdmaEndpoint::C_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + + butil::fd_guard acc_fd(accept(sockfd, NULL, NULL)); + ASSERT_TRUE(acc_fd >= 0); + bthread_id_join(cntl.call_id()); + + ASSERT_EQ(ERPCTIMEDOUT, cntl.ErrorCode()); +} + +TEST_F(RdmaTest, server_close_before_hello_send) { + butil::fd_guard sockfd(butil::tcp_listen(g_ep)); + EXPECT_TRUE(sockfd >= 0); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done); + + usleep(100000); + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl._single_server_id, &s)); + ASSERT_EQ(rdma::RdmaEndpoint::C_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + + butil::fd_guard acc_fd(accept(sockfd, NULL, NULL)); + ASSERT_TRUE(acc_fd >= 0); + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + close(acc_fd); + usleep(100000); + ASSERT_EQ(rdma::RdmaEndpoint::FAILED, static_cast(s->_transport.get())->_rdma_ep->_state); + bthread_id_join(cntl.call_id()); + + ASSERT_EQ(EEOF, cntl.ErrorCode()); +} + +TEST_F(RdmaTest, server_miss_during_magic_str) { + butil::fd_guard sockfd(butil::tcp_listen(g_ep)); + EXPECT_TRUE(sockfd >= 0); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done); + + usleep(100000); + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl._single_server_id, &s)); + ASSERT_EQ(rdma::RdmaEndpoint::C_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + + butil::fd_guard acc_fd(accept(sockfd, NULL, NULL)); + ASSERT_TRUE(acc_fd >= 0); + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + ASSERT_EQ(2, write(acc_fd, "RD", 2)); + usleep(100000); + bthread_id_join(cntl.call_id()); + + ASSERT_EQ(ERPCTIMEDOUT, cntl.ErrorCode()); +} + +TEST_F(RdmaTest, server_close_during_magic_str) { + butil::fd_guard sockfd(butil::tcp_listen(g_ep)); + EXPECT_TRUE(sockfd >= 0); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done); + + usleep(100000); + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl._single_server_id, &s)); + ASSERT_EQ(rdma::RdmaEndpoint::C_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + + butil::fd_guard acc_fd(accept(sockfd, NULL, NULL)); + ASSERT_TRUE(acc_fd >= 0); + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + ASSERT_EQ(2, write(acc_fd, "RD", 2)); + usleep(100000); + close(acc_fd); + usleep(100000); + ASSERT_EQ(rdma::RdmaEndpoint::FAILED, static_cast(s->_transport.get())->_rdma_ep->_state); + bthread_id_join(cntl.call_id()); + + ASSERT_EQ(EEOF, cntl.ErrorCode()); +} + +TEST_F(RdmaTest, server_hello_invalid_magic_str) { + butil::fd_guard sockfd(butil::tcp_listen(g_ep)); + EXPECT_TRUE(sockfd >= 0); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done); + + usleep(100000); + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl._single_server_id, &s)); + ASSERT_EQ(rdma::RdmaEndpoint::C_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + + butil::fd_guard acc_fd(accept(sockfd, NULL, NULL)); + ASSERT_TRUE(acc_fd >= 0); + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + ASSERT_EQ(4, write(acc_fd, "ABCD", 4)); + usleep(100000); + ASSERT_EQ(rdma::RdmaEndpoint::FAILED, static_cast(s->_transport.get())->_rdma_ep->_state); + bthread_id_join(cntl.call_id()); + + ASSERT_EQ(EPROTO, cntl.ErrorCode()); +} + +TEST_F(RdmaTest, server_miss_during_hello_msg) { + butil::fd_guard sockfd(butil::tcp_listen(g_ep)); + EXPECT_TRUE(sockfd >= 0); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done); + + usleep(100000); + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl._single_server_id, &s)); + ASSERT_EQ(rdma::RdmaEndpoint::C_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + + butil::fd_guard acc_fd(accept(sockfd, NULL, NULL)); + ASSERT_TRUE(acc_fd >= 0); + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + ASSERT_EQ(4, write(acc_fd, "RDMA", 4)); + ASSERT_EQ(2, write(acc_fd, "00", 2)); + bthread_id_join(cntl.call_id()); + + ASSERT_EQ(ERPCTIMEDOUT, cntl.ErrorCode()); +} + +TEST_F(RdmaTest, server_close_during_hello_msg) { + butil::fd_guard sockfd(butil::tcp_listen(g_ep)); + EXPECT_TRUE(sockfd >= 0); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done); + + usleep(100000); + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl._single_server_id, &s)); + ASSERT_EQ(rdma::RdmaEndpoint::C_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + + butil::fd_guard acc_fd(accept(sockfd, NULL, NULL)); + ASSERT_TRUE(acc_fd >= 0); + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + ASSERT_EQ(4, write(acc_fd, "RDMA", 4)); + ASSERT_EQ(2, write(acc_fd, "00", 2)); + close(acc_fd); + usleep(100000); + ASSERT_EQ(rdma::RdmaEndpoint::FAILED, static_cast(s->_transport.get())->_rdma_ep->_state); + bthread_id_join(cntl.call_id()); + + ASSERT_EQ(EEOF, cntl.ErrorCode()); +} + +TEST_F(RdmaTest, server_hello_invalid_msg_len) { + butil::fd_guard sockfd(butil::tcp_listen(g_ep)); + EXPECT_TRUE(sockfd >= 0); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done); + + usleep(100000); + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl._single_server_id, &s)); + ASSERT_EQ(rdma::RdmaEndpoint::C_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + + butil::fd_guard acc_fd(accept(sockfd, NULL, NULL)); + ASSERT_TRUE(acc_fd >= 0); + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + memcpy(data, "RDMA", 4); + uint16_t len = butil::HostToNet16(35); + memcpy(data + 4, &len, 2); + memset(data + 6, 0, 32); + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, write(acc_fd, data, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + usleep(100000); + ASSERT_EQ(rdma::RdmaEndpoint::FAILED, static_cast(s->_transport.get())->_rdma_ep->_state); + bthread_id_join(cntl.call_id()); + + ASSERT_EQ(EPROTO, cntl.ErrorCode()); +} + +TEST_F(RdmaTest, server_hello_invalid_version) { + butil::fd_guard sockfd(butil::tcp_listen(g_ep)); + EXPECT_TRUE(sockfd >= 0); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done); + + usleep(100000); + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl._single_server_id, &s)); + ASSERT_EQ(rdma::RdmaEndpoint::C_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + + butil::fd_guard acc_fd(accept(sockfd, NULL, NULL)); + ASSERT_TRUE(acc_fd >= 0); + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + memcpy(data, "RDMA", 4); + uint16_t len = butil::HostToNet16(rdma::v2_wire::HELLO_MSG_LEN_MIN); + memcpy(data + 4, &len, 2); + memset(data + 6, 0, 32); + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, write(acc_fd, data, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + usleep(100000); + ASSERT_EQ(rdma::RdmaEndpoint::FALLBACK_TCP, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(4, read(acc_fd, data, 4)); + uint32_t* tmp = (uint32_t*)data; + ASSERT_EQ(0, butil::NetToHost32(*tmp)); + bthread_id_join(cntl.call_id()); + + ASSERT_EQ(ERPCTIMEDOUT, cntl.ErrorCode()); +} + +TEST_F(RdmaTest, server_hello_invalid_sq_rq_size) { + butil::fd_guard sockfd(butil::tcp_listen(g_ep)); + EXPECT_TRUE(sockfd >= 0); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done); + + usleep(100000); + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl._single_server_id, &s)); + ASSERT_EQ(rdma::RdmaEndpoint::C_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + + butil::fd_guard acc_fd(accept(sockfd, NULL, NULL)); + ASSERT_TRUE(acc_fd >= 0); + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + + rdma::v2_wire::HelloMessage msg{}; + msg.msg_len = rdma::v2_wire::HELLO_MSG_LEN_MIN; + msg.hello_ver = 1; + msg.impl_ver = 1; + msg.sq_size = 0; + msg.rq_size = 0; + msg.block_size = 8192; + msg.qp_num = 0; + msg.gid = rdma::GetRdmaGid(); + memcpy(data, "RDMA", 4); + msg.Serialize(data + 4); + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, write(acc_fd, data, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + + usleep(100000); + ASSERT_EQ(rdma::RdmaEndpoint::FALLBACK_TCP, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(4, read(acc_fd, data, 4)); + uint32_t* tmp = (uint32_t*)data; + ASSERT_EQ(0, butil::NetToHost32(*tmp)); + bthread_id_join(cntl.call_id()); + + ASSERT_EQ(ERPCTIMEDOUT, cntl.ErrorCode()); +} + +TEST_F(RdmaTest, server_miss_after_ack) { + butil::fd_guard sockfd(butil::tcp_listen(g_ep)); + EXPECT_TRUE(sockfd >= 0); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done); + + usleep(100000); + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl._single_server_id, &s)); + ASSERT_EQ(rdma::RdmaEndpoint::C_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + + butil::fd_guard acc_fd(accept(sockfd, NULL, NULL)); + ASSERT_TRUE(acc_fd >= 0); + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + + rdma::v2_wire::HelloMessage msg{}; + msg.msg_len = rdma::v2_wire::HELLO_MSG_LEN_MIN; + msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION; + msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION; + msg.sq_size = 16; + msg.rq_size = 16; + msg.block_size = 8192; + msg.qp_num = 0; + msg.gid = rdma::GetRdmaGid(); + memcpy(data, "RDMA", 4); + msg.Serialize(data + 4); + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, write(acc_fd, data, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + + usleep(100000); + ASSERT_EQ(rdma::RdmaEndpoint::ESTABLISHED, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(4, read(acc_fd, data, 4)); + uint32_t* tmp = (uint32_t*)data; + ASSERT_EQ(1, butil::NetToHost32(*tmp)); + bthread_id_join(cntl.call_id()); + + ASSERT_EQ(ERPCTIMEDOUT, cntl.ErrorCode()); +} + +TEST_F(RdmaTest, server_close_after_ack) { + butil::fd_guard sockfd(butil::tcp_listen(g_ep)); + EXPECT_TRUE(sockfd >= 0); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done); + + usleep(100000); + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl._single_server_id, &s)); + ASSERT_EQ(rdma::RdmaEndpoint::C_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + + butil::fd_guard acc_fd(accept(sockfd, NULL, NULL)); + ASSERT_TRUE(acc_fd >= 0); + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + + rdma::v2_wire::HelloMessage msg{}; + msg.msg_len = rdma::v2_wire::HELLO_MSG_LEN_MIN; + msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION; + msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION; + msg.sq_size = 16; + msg.rq_size = 16; + msg.block_size = 8192; + msg.qp_num = 0; + msg.gid = rdma::GetRdmaGid(); + memcpy(data, "RDMA", 4); + msg.Serialize(data + 4); + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, write(acc_fd, data, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + + usleep(100000); + ASSERT_EQ(rdma::RdmaEndpoint::ESTABLISHED, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(4, read(acc_fd, data, 4)); + uint32_t* tmp = (uint32_t*)data; + ASSERT_EQ(1, butil::NetToHost32(*tmp)); + close(acc_fd); + bthread_id_join(cntl.call_id()); + + ASSERT_EQ(EEOF, cntl.ErrorCode()); +} + +TEST_F(RdmaTest, server_send_data_on_tcp_after_ack) { + butil::fd_guard sockfd(butil::tcp_listen(g_ep)); + EXPECT_TRUE(sockfd >= 0); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done); + + usleep(100000); + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl._single_server_id, &s)); + ASSERT_EQ(rdma::RdmaEndpoint::C_HELLO_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + + butil::fd_guard acc_fd(accept(sockfd, NULL, NULL)); + ASSERT_TRUE(acc_fd >= 0); + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + + rdma::v2_wire::HelloMessage msg{}; + msg.msg_len = rdma::v2_wire::HELLO_MSG_LEN_MIN; + msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION; + msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION; + msg.sq_size = 16; + msg.rq_size = 16; + msg.block_size = 8192; + msg.qp_num = 0; + msg.gid = rdma::GetRdmaGid(); + memcpy(data, "RDMA", 4); + msg.Serialize(data + 4); + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, write(acc_fd, data, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + + usleep(100000); + ASSERT_EQ(rdma::RdmaEndpoint::ESTABLISHED, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, write(acc_fd, data, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + bthread_id_join(cntl.call_id()); + + ASSERT_EQ(EPROTO, cntl.ErrorCode()); +} + + +TEST_F(RdmaTest, v2_client_hello_bytes_baseline) { + butil::fd_guard sockfd(butil::tcp_listen(g_ep)); + EXPECT_TRUE(sockfd >= 0); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done); + + usleep(100000); + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl._single_server_id, &s)); + + butil::fd_guard acc_fd(accept(sockfd, NULL, NULL)); + ASSERT_TRUE(acc_fd >= 0); + + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(acc_fd, data, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + + // [0..4) magic + ASSERT_EQ(0, memcmp(data, "RDMA", 4)); + // [4..6) msg_len, big-endian uint16 == 40 + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, + (size_t)(((uint16_t)data[4] << 8) | (uint16_t)data[5])); + // [6..8) hello_ver, big-endian uint16 == rdma::RDMA_HELLO_V2_VERSION + ASSERT_EQ(rdma::RDMA_HELLO_V2_VERSION, + (uint16_t)(((uint16_t)data[6] << 8) | (uint16_t)data[7])); + // [8..10) impl_ver, big-endian uint16 == rdma::RDMA_IMPL_V2_VERSION + ASSERT_EQ(rdma::RDMA_IMPL_V2_VERSION, + (uint16_t)(((uint16_t)data[8] << 8) | (uint16_t)data[9])); + + rdma::v2_wire::HelloMessage msg{}; + msg.Deserialize(data + 4); + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, msg.msg_len); + ASSERT_EQ(rdma::RDMA_HELLO_V2_VERSION, msg.hello_ver); + ASSERT_EQ(rdma::RDMA_IMPL_V2_VERSION, msg.impl_ver); + + bthread_id_join(cntl.call_id()); +} + +TEST_F(RdmaTest, v2_server_hello_bytes_baseline) { + StartServer(); + + sockaddr_in addr; + bzero((char*)&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + + butil::fd_guard sockfd(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd >= 0); + ASSERT_EQ(0, connect(sockfd, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); + Socket* s = GetSocketFromServer(0); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + + // Send a well-formed v2 hello so the server enters S_ACK_WAIT. + rdma::v2_wire::HelloMessage msg{}; + msg.msg_len = rdma::v2_wire::HELLO_MSG_LEN_MIN; + msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION; + msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION; + msg.sq_size = 16; + msg.rq_size = 16; + msg.block_size = 8192; + msg.qp_num = 0; + msg.gid = rdma::GetRdmaGid(); + + uint8_t data[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + memcpy(data, "RDMA", 4); + msg.Serialize(data + 4); + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, write(sockfd, data, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + usleep(100000); + ASSERT_EQ(rdma::RdmaEndpoint::S_ACK_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + + // Read server's reply hello and assert its byte-level layout. + uint8_t reply[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, read(sockfd, reply, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + + ASSERT_EQ(0, memcmp(reply, "RDMA", 4)); + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, + (size_t)(((uint16_t)reply[4] << 8) | (uint16_t)reply[5])); + ASSERT_EQ(rdma::RDMA_HELLO_V2_VERSION, + (uint16_t)(((uint16_t)reply[6] << 8) | (uint16_t)reply[7])); + ASSERT_EQ(rdma::RDMA_IMPL_V2_VERSION, + (uint16_t)(((uint16_t)reply[8] << 8) | (uint16_t)reply[9])); + + rdma::v2_wire::HelloMessage reply_msg{}; + reply_msg.Deserialize(reply + 4); + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, reply_msg.msg_len); + ASSERT_EQ(rdma::RDMA_HELLO_V2_VERSION, reply_msg.hello_ver); + ASSERT_EQ(rdma::RDMA_IMPL_V2_VERSION, reply_msg.impl_ver); + + // Drive the server into FALLBACK_TCP via ACK flags=0 so the test ends + // cleanly without requiring real RDMA hardware. + uint32_t flags = butil::HostToNet32(0); + ASSERT_EQ(sizeof(flags), write(sockfd, &flags, sizeof(flags))); + usleep(100000); + ASSERT_EQ(rdma::RdmaEndpoint::FALLBACK_TCP, static_cast(s->_transport.get())->_rdma_ep->_state); + + sockfd.reset(-1); + usleep(100000); + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + StopServer(); +} + +TEST_F(RdmaTest, v2_server_drains_tail_then_reads_ack) { + StartServer(); + + sockaddr_in addr; + bzero((char*)&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + butil::fd_guard sockfd(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd >= 0); + ASSERT_EQ(0, connect(sockfd, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); + Socket* s = GetSocketFromServer(0); + ASSERT_TRUE(s != NULL); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + + // Build a v2 hello with msg_len = 48 (40 base + 8B zero tail). + rdma::v2_wire::HelloMessage msg{}; + msg.msg_len = 48; + msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION; + msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION; + msg.sq_size = 16; + msg.rq_size = 16; + msg.block_size = 8192; + msg.qp_num = 0; + msg.gid = rdma::GetRdmaGid(); + + uint8_t buf[48]; + memcpy(buf, "RDMA", 4); + msg.Serialize(buf + 4); + memset(buf + 40, 0x00, 8); // 8B zero tail + ASSERT_EQ(48, write(sockfd, buf, 48)); + usleep(100000); + + // Send the real ACK (flags=1 = ACK_MSG_RDMA_OK). + uint32_t flags = butil::HostToNet32(1); + ASSERT_EQ(sizeof(flags), write(sockfd, &flags, sizeof(flags))); + usleep(100000); + + ASSERT_EQ(rdma::RdmaEndpoint::ESTABLISHED, static_cast(s->_transport.get())->_rdma_ep->_state); + + sockfd.reset(-1); + usleep(100000); + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + StopServer(); +} + +TEST_F(RdmaTest, v2_server_rejects_oversized_msg_len) { + StartServer(); + + sockaddr_in addr; + bzero((char*)&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + butil::fd_guard sockfd(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd >= 0); + ASSERT_EQ(0, connect(sockfd, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); + Socket* s = GetSocketFromServer(0); + ASSERT_TRUE(s != NULL); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + + // Build a v2 hello with msg_len = 4097 (HELLO_MSG_LEN_MAX + 1). + // We only send the 40B base; the server must reject before reading + // (and definitely before attempting to drain) any "tail". + rdma::v2_wire::HelloMessage msg{}; + msg.msg_len = 4097; + msg.hello_ver = rdma::RDMA_HELLO_V2_VERSION; + msg.impl_ver = rdma::RDMA_IMPL_V2_VERSION; + msg.sq_size = 16; + msg.rq_size = 16; + msg.block_size = 8192; + msg.qp_num = 0; + msg.gid = rdma::GetRdmaGid(); + + uint8_t buf[rdma::v2_wire::HELLO_MSG_LEN_MIN]; + memcpy(buf, "RDMA", 4); + msg.Serialize(buf + 4); + ASSERT_EQ(rdma::v2_wire::HELLO_MSG_LEN_MIN, write(sockfd, buf, rdma::v2_wire::HELLO_MSG_LEN_MIN)); + usleep(100000); + + + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + sockfd.reset(-1); + usleep(100000); + + StopServer(); +} + +// RAII for FLAGS_rdma_client_handshake_version: lets us flip the +// client-side handshake version for a single test and restore it on +// scope exit so subsequent tests stay on the v2 default. +class HandshakeVersionFlag { +public: + explicit HandshakeVersionFlag(int v) + : _saved(rdma::FLAGS_rdma_client_handshake_version) { + rdma::FLAGS_rdma_client_handshake_version = v; + } + ~HandshakeVersionFlag() { + rdma::FLAGS_rdma_client_handshake_version = _saved; + } +private: + int _saved; +}; + +// Build a v3 wire packet from an RdmaHello: "RDM3" + pb_size_be + body. +std::string MakeV3Packet(const rdma::RdmaHello& msg) { + std::string body; + EXPECT_TRUE(msg.SerializeToString(&body)); + std::string packet; + packet.reserve(4 + 4 + body.size()); + packet.append("RDM3", 4); + uint32_t pb_size_be = + butil::HostToNet32(static_cast(body.size())); + packet.append(reinterpret_cast(&pb_size_be), 4); + packet.append(body); + return packet; +} + +// Build a fully-valid RdmaHello: all 6 required fields are set, with +// values that pass RdmaHelloV3Wire::RdmaHelloValid(). +// - block_size = 8192 (>= MIN_BLOCK_SIZE) +// - sq_size / rq_size = 16 (>= MIN_QP_SIZE) +// - gid = exactly 16B (sizeof(ibv_gid)) +// - qp_num = 0 (allowed because g_skip_rdma_init in UT) +rdma::RdmaHello MakeValidV3Hello() { + rdma::RdmaHello msg; + msg.set_block_size(8192); + msg.set_sq_size(16); + msg.set_rq_size(16); + msg.set_lid(0); + ibv_gid gid = rdma::GetRdmaGid(); + msg.set_gid(std::string(reinterpret_cast(gid.raw), + sizeof(gid.raw))); + msg.set_qp_num(0); + return msg; +} + + +TEST_F(RdmaTest, v3_client_hello_bytes_baseline) { + HandshakeVersionFlag _hsv(3); + + butil::fd_guard sockfd(butil::tcp_listen(g_ep)); + EXPECT_TRUE(sockfd >= 0); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done); + + butil::fd_guard acc_fd(accept(sockfd, NULL, NULL)); + ASSERT_TRUE(acc_fd >= 0); + + // [0..4) magic "RDM3" + uint8_t magic[4]; + ASSERT_EQ(4, read(acc_fd, magic, 4)); + ASSERT_EQ(0, memcmp(magic, "RDM3", 4)); + + // [4..8) pb_size, big-endian uint32, must be in (0, 4096] + uint8_t size_buf[4]; + ASSERT_EQ(4, read(acc_fd, size_buf, 4)); + uint32_t pb_size = + butil::NetToHost32(*reinterpret_cast(size_buf)); + ASSERT_GT(pb_size, 0u); + ASSERT_LE(pb_size, 4096u); + + // [8..8+pb_size) RdmaHello protobuf body. + std::string body(pb_size, '\0'); + ASSERT_EQ((ssize_t)pb_size, read(acc_fd, &body[0], pb_size)); + rdma::RdmaHello msg; + ASSERT_TRUE(msg.ParseFromString(body)); + + // All 6 required fields must be present (ParseFromString would + // have already returned false otherwise). + ASSERT_TRUE(msg.has_block_size()); + ASSERT_TRUE(msg.has_sq_size()); + ASSERT_TRUE(msg.has_rq_size()); + ASSERT_TRUE(msg.has_lid()); + ASSERT_TRUE(msg.has_gid()); + ASSERT_TRUE(msg.has_qp_num()); + // gid wire encoding must be exactly 16 bytes (sizeof(ibv_gid)). + ASSERT_EQ(sizeof(ibv_gid), msg.gid().size()); + + // Let the RPC time out and release resources. + bthread_id_join(cntl.call_id()); +} + +TEST_F(RdmaTest, v3_server_hello_bytes_baseline) { + StartServer(); + + sockaddr_in addr; + bzero((char*)&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + + butil::fd_guard sockfd(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd >= 0); + ASSERT_EQ(0, connect(sockfd, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); + Socket* s = GetSocketFromServer(0); + ASSERT_EQ(rdma::RdmaEndpoint::UNINIT, static_cast(s->_transport.get())->_rdma_ep->_state); + + // Send a valid v3 hello. + std::string packet = MakeV3Packet(MakeValidV3Hello()); + ASSERT_EQ((ssize_t)packet.size(), + write(sockfd, packet.data(), packet.size())); + usleep(100000); + ASSERT_EQ(rdma::RdmaEndpoint::S_ACK_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + + // Read server's reply hello: 4B magic + 4B pb_size + body. + uint8_t reply_magic[4]; + ASSERT_EQ(4, read(sockfd, reply_magic, 4)); + ASSERT_EQ(0, memcmp(reply_magic, "RDM3", 4)); + + uint8_t size_buf[4]; + ASSERT_EQ(4, read(sockfd, size_buf, 4)); + uint32_t pb_size = + butil::NetToHost32(*reinterpret_cast(size_buf)); + ASSERT_GT(pb_size, 0u); + ASSERT_LE(pb_size, 4096u); + + std::string body(pb_size, '\0'); + ASSERT_EQ((ssize_t)pb_size, read(sockfd, &body[0], pb_size)); + rdma::RdmaHello reply; + ASSERT_TRUE(reply.ParseFromString(body)); + ASSERT_TRUE(reply.has_block_size()); + ASSERT_TRUE(reply.has_sq_size()); + ASSERT_TRUE(reply.has_rq_size()); + ASSERT_TRUE(reply.has_gid()); + ASSERT_EQ(sizeof(ibv_gid), reply.gid().size()); + + // Drive the server into FALLBACK_TCP via ACK flags=0 so the test ends + // cleanly without requiring real RDMA hardware. + uint32_t flags = butil::HostToNet32(0); + ASSERT_EQ((ssize_t)sizeof(flags), + write(sockfd, &flags, sizeof(flags))); + usleep(100000); + ASSERT_EQ(rdma::RdmaEndpoint::FALLBACK_TCP, static_cast(s->_transport.get())->_rdma_ep->_state); + + sockfd.reset(-1); + usleep(100000); + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + StopServer(); +} + +TEST_F(RdmaTest, v3_server_rejects_zero_pb_size) { + StartServer(); + + sockaddr_in addr; + bzero((char*)&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + butil::fd_guard sockfd(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd >= 0); + ASSERT_EQ(0, connect(sockfd, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); + Socket* s = GetSocketFromServer(0); + ASSERT_TRUE(s != NULL); + + // "RDM3" + pb_size = 0 (4B big-endian zero). + uint8_t buf[8] = {'R', 'D', 'M', '3', 0, 0, 0, 0}; + ASSERT_EQ(8, write(sockfd, buf, 8)); + usleep(100000); + + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + sockfd.reset(-1); + StopServer(); +} + +TEST_F(RdmaTest, v3_server_rejects_oversized_pb_size) { + StartServer(); + + sockaddr_in addr; + bzero((char*)&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + butil::fd_guard sockfd(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd >= 0); + ASSERT_EQ(0, connect(sockfd, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); + Socket* s = GetSocketFromServer(0); + ASSERT_TRUE(s != NULL); + + uint8_t buf[8]; + memcpy(buf, "RDM3", 4); + uint32_t pb_size_be = butil::HostToNet32(4097); + memcpy(buf + 4, &pb_size_be, 4); + ASSERT_EQ(8, write(sockfd, buf, 8)); + usleep(100000); + + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + sockfd.reset(-1); + StopServer(); +} + +TEST_F(RdmaTest, v3_server_rejects_invalid_pb_bytes) { + StartServer(); + + sockaddr_in addr; + bzero((char*)&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + butil::fd_guard sockfd(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd >= 0); + ASSERT_EQ(0, connect(sockfd, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); + Socket* s = GetSocketFromServer(0); + ASSERT_TRUE(s != NULL); + + // "RDM3" + pb_size = 8 + 8 bytes of 0xff (invalid protobuf body). + uint8_t buf[16]; + memcpy(buf, "RDM3", 4); + uint32_t pb_size_be = butil::HostToNet32(8); + memcpy(buf + 4, &pb_size_be, 4); + memset(buf + 8, 0xff, 8); + ASSERT_EQ(16, write(sockfd, buf, 16)); + usleep(100000); + + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + sockfd.reset(-1); + StopServer(); +} + +TEST_F(RdmaTest, v3_server_invalid_sq_size_falls_back) { + StartServer(); + + sockaddr_in addr; + bzero((char*)&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + butil::fd_guard sockfd(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_TRUE(sockfd >= 0); + ASSERT_EQ(0, connect(sockfd, (sockaddr*)&addr, sizeof(sockaddr))); + usleep(100000); + Socket* s = GetSocketFromServer(0); + ASSERT_TRUE(s != NULL); + + rdma::RdmaHello msg = MakeValidV3Hello(); + msg.set_sq_size(0); // invalid: < MIN_QP_SIZE (16) + std::string packet = MakeV3Packet(msg); + ASSERT_EQ((ssize_t)packet.size(), + write(sockfd, packet.data(), packet.size())); + usleep(100000); + + // Server validated the hello as invalid -> _rdma_state = RDMA_OFF, + // but still proceeds to S_ACK_WAIT (sends its own reply hello). + ASSERT_EQ(rdma::RdmaEndpoint::S_ACK_WAIT, static_cast(s->_transport.get())->_rdma_ep->_state); + ASSERT_EQ(RdmaTransport::RDMA_OFF, static_cast(s->_transport.get())->_rdma_state); + + // Drain server's reply hello (content not asserted here; covered + // by v3_server_hello_bytes_baseline). + uint8_t reply_hdr[8]; + ASSERT_EQ(8, read(sockfd, reply_hdr, 8)); + ASSERT_EQ(0, memcmp(reply_hdr, "RDM3", 4)); + uint32_t reply_pb_size = butil::NetToHost32( + *reinterpret_cast(reply_hdr + 4)); + std::string reply_body(reply_pb_size, '\0'); + ASSERT_EQ((ssize_t)reply_pb_size, + read(sockfd, &reply_body[0], reply_pb_size)); + + // Client ACK flags=0 -> server settles into FALLBACK_TCP. + uint32_t flags = butil::HostToNet32(0); + ASSERT_EQ((ssize_t)sizeof(flags), + write(sockfd, &flags, sizeof(flags))); + usleep(100000); + ASSERT_EQ(rdma::RdmaEndpoint::FALLBACK_TCP, static_cast(s->_transport.get())->_rdma_ep->_state); + + sockfd.reset(-1); + usleep(100000); + ASSERT_EQ(NULL, GetSocketFromServer(0)); + + StopServer(); +} + +TEST_F(RdmaTest, try_global_disable_rdma) { + StartServer(); + rdma::g_rdma_available.store(false, butil::memory_order_relaxed); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + + req.set_message(__FUNCTION__); + req.set_sleep_us(200000); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done); + usleep(100000); + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl._single_server_id, &s)); + ASSERT_EQ(rdma::RdmaEndpoint::FALLBACK_TCP, static_cast(s->_transport.get())->_rdma_ep->_state); + bthread_id_join(cntl.call_id()); + ASSERT_EQ(0, cntl.ErrorCode()); + + StopServer(); + rdma::g_rdma_available.store(true, butil::memory_order_relaxed); +} + +TEST_F(RdmaTest, server_option_invalid) { + Server server; + ServerOptions options; + options.socket_mode = SOCKET_MODE_RDMA; + + // rtmp and rdma are incompatible + options.rtmp_service = (RtmpService*)1; + ASSERT_EQ(-1, server.Start(PORT, &options)); + + // nshead and rdma are incompatible + options.rtmp_service = NULL; + options.nshead_service = (NsheadService*)1; + ASSERT_EQ(-1, server.Start(PORT, &options)); + + // mongo and rdma are incompatible + options.nshead_service = NULL; + options.mongo_service_adaptor = (MongoServiceAdaptor*)1; + ASSERT_EQ(-1, server.Start(PORT, &options)); + + // ssl and rdma are incompatible + options.mongo_service_adaptor = NULL; + options.mutable_ssl_options()->default_cert.certificate = "test"; + ASSERT_EQ(-1, server.Start(PORT, &options)); +} + +TEST_F(RdmaTest, channel_option_invalid) { + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + + // rtmp and rdma are incompatible + chan_options.protocol = "rtmp"; + ASSERT_EQ(-1, channel.Init(g_ep, &chan_options)); + + chan_options.protocol = "streaming_rpc"; + ASSERT_EQ(-1, channel.Init(g_ep, &chan_options)); + + // nshead and rdma are incompatible + chan_options.protocol = "nshead"; + ASSERT_EQ(-1, channel.Init(g_ep, &chan_options)); + chan_options.protocol = "nshead_mcpack"; + ASSERT_EQ(-1, channel.Init(g_ep, &chan_options)); + + // nova_pbrpc and rdma are incompatible + chan_options.protocol = "nova_pbrpc"; + ASSERT_EQ(-1, channel.Init(g_ep, &chan_options)); + + // public_pbrpc and rdma are incompatible + chan_options.protocol = "public_pbrpc"; + ASSERT_EQ(-1, channel.Init(g_ep, &chan_options)); + + // redis and rdma are incompatible + chan_options.protocol = "redis"; + ASSERT_EQ(-1, channel.Init(g_ep, &chan_options)); + + // memcache and rdma are incompatible + chan_options.protocol = "memcache"; + ASSERT_EQ(-1, channel.Init(g_ep, &chan_options)); + + // ubrpc and rdma are incompatible + chan_options.protocol = "ubrpc_compack"; + ASSERT_EQ(-1, channel.Init(g_ep, &chan_options)); + + // itp and rdma are incompatible + chan_options.protocol = "itp"; + ASSERT_EQ(-1, channel.Init(g_ep, &chan_options)); + + // esp and rdma are incompatible + chan_options.protocol = "esp"; + ASSERT_EQ(-1, channel.Init(g_ep, &chan_options)); + + // hulu_pbrpc and rdma are incompatible + chan_options.protocol = "hulu_pbrpc"; + ASSERT_EQ(-1, channel.Init(g_ep, &chan_options)); + + // sofa_pbrpc and rdma are incompatible + chan_options.protocol = "sofa_pbrpc"; + ASSERT_EQ(-1, channel.Init(g_ep, &chan_options)); + + // http and rdma are incompatible + chan_options.protocol = "http"; + ASSERT_EQ(-1, channel.Init(g_ep, &chan_options)); + + // ssl and rdma are incompatible + chan_options.protocol = "baidu_std"; + chan_options.mutable_ssl_options()->sni_name = "test"; + ASSERT_EQ(-1, channel.Init(g_ep, &chan_options)); +} + +TEST_P(RdmaRpcTest, rdma_client_to_rdma_server) { + if (!FLAGS_rdma_test_enable) { + return; + } + + StartServer(); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done); + // usleep(100000); + bthread_id_join(cntl.call_id()); + ASSERT_EQ(0, cntl.ErrorCode()); + + StopServer(); +} + +TEST_P(RdmaRpcTest, tcp_client_to_tcp_server) { + StartServer(false); + + Channel channel; + ChannelOptions chan_options; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done); + usleep(100000); + bthread_id_join(cntl.call_id()); + ASSERT_EQ(0, cntl.ErrorCode()); + + StopServer(); +} + +TEST_P(RdmaRpcTest, tcp_client_to_rdma_server) { + StartServer(); + + Channel channel; + ChannelOptions chan_options; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done); + usleep(100000); + bthread_id_join(cntl.call_id()); + ASSERT_EQ(0, cntl.ErrorCode()); + + StopServer(); +} + +TEST_P(RdmaRpcTest, rdma_client_to_tcp_server) { + StartServer(false); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done); + usleep(100000); + bthread_id_join(cntl.call_id()); + ASSERT_EQ(EEOF, cntl.ErrorCode()); + + StopServer(); +} + +static const int RPC_NUM = 1024; + +void DumpRdmaEndpointInfo(Socket* client, Socket* server) { + std::cout << std::endl << "client:"; + static_cast(client->_transport.get())->_rdma_ep->DebugInfo(std::cout); + std::cout << std::endl << "server:"; + static_cast(server->_transport.get())->_rdma_ep->DebugInfo(std::cout); +} + +TEST_P(RdmaRpcTest, send_rpcs_in_one_qp) { + if (!FLAGS_rdma_test_enable) { + return; + } + + StartServer(); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 50000; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + Controller cntl[RPC_NUM]; + test::EchoRequest req[RPC_NUM]; + test::EchoResponse res[RPC_NUM]; + + LOG(INFO) << "send 0 attachment"; + for (int i = 0; i < RPC_NUM; ++i) { + req[i].set_message(__FUNCTION__); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl[i], &req[i], &res[i], done); + } + for (int i = 0; i < RPC_NUM; ++i) { + bthread_id_join(cntl[i].call_id()); + if (cntl[i].ErrorCode() == ERPCTIMEDOUT) { + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl[i]._single_server_id, &s)); + Socket* m = GetSocketFromServer(0); + DumpRdmaEndpointInfo(s.get(), m); + } + ASSERT_EQ(0, cntl[i].ErrorCode()) << "req[" << i << "]"; + } + + LOG(INFO) << "send 4KB attachment"; + butil::IOBuf attach; + attach.resize(4096); + for (int i = 0; i < RPC_NUM; ++i) { + cntl[i].Reset(); + cntl[i].request_attachment().append(attach); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl[i], &req[i], &res[i], done); + } + for (int i = 0; i < RPC_NUM; ++i) { + bthread_id_join(cntl[i].call_id()); + if (cntl[i].ErrorCode() == ERPCTIMEDOUT) { + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl[i]._single_server_id, &s)); + Socket* m = GetSocketFromServer(0); + DumpRdmaEndpointInfo(s.get(), m); + } + ASSERT_EQ(0, cntl[i].ErrorCode()) << "req[" << i << "]"; + } + + LOG(INFO) << "send 1MB attachment"; + attach.resize(1048576); + for (int i = 0; i < RPC_NUM; ++i) { + cntl[i].Reset(); + cntl[i].request_attachment().append(attach); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl[i], &req[i], &res[i], done); + } + for (int i = 0; i < RPC_NUM; ++i) { + bthread_id_join(cntl[i].call_id()); + if (cntl[i].ErrorCode() == ERPCTIMEDOUT) { + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl[i]._single_server_id, &s)); + Socket* m = GetSocketFromServer(0); + DumpRdmaEndpointInfo(s.get(), m); + } + ASSERT_TRUE(0 == cntl[i].ErrorCode() || + EOVERCROWDED == cntl[i].ErrorCode()) << "req[" << i << "] " << berror(cntl[i].ErrorCode()); + } + + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl[0]._single_server_id, &s)); + Socket* m = GetSocketFromServer(0); + DumpRdmaEndpointInfo(s.get(), m); + + StopServer(); +} + +TEST_P(RdmaRpcTest, send_rpc_in_many_qp) { + if (!FLAGS_rdma_test_enable) { + return; + } + + butil::ip_t ip; + ASSERT_EQ(0, butil::str2ip(g_ip.c_str(), &ip)); + + Server server[100]; + MyEchoService svc[100]; + int num = 100; + butil::EndPoint server_eps[100]; + for (int i = 0; i < num; ++i) { + ServerOptions options; + options.socket_mode = SOCKET_MODE_RDMA; + options.idle_timeout_sec = 1; + options.max_concurrency = 0; + options.internal_port = -1; + server[i].AddService(&svc[i], SERVER_DOESNT_OWN_SERVICE); + ASSERT_EQ(0, server[i].Start(0, &options)); + server_eps[i] = butil::EndPoint(ip, server[i].listen_address().port); + } + + int port = 0; + butil::IOBuf attach; + attach.resize(4096); + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 100000; + chan_options.max_retry = 0; + Channel channel[RPC_NUM]; + Server* svr[RPC_NUM]; + Controller cntl[RPC_NUM]; + test::EchoRequest req[RPC_NUM]; + test::EchoResponse res[RPC_NUM]; + for (int i = 0; i < RPC_NUM; ++i) { + svr[i] = &server[i % num]; + ASSERT_EQ(0, channel[i].Init(server_eps[(port++) % num], &chan_options)); + req[i].set_message(__FUNCTION__); + cntl[i].request_attachment().append(attach); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel[i]).Echo(&cntl[i], &req[i], &res[i], done); + } + for (int i = 0; i < RPC_NUM; ++i) { + bthread_id_join(cntl[i].call_id()); + if (cntl[i].ErrorCode() == ERPCTIMEDOUT) { + SocketUniquePtr s; + EXPECT_EQ(0, Socket::Address(cntl[i]._single_server_id, &s)); + if (s && svr[i] && svr[i]->_am) { + std::vector sids; + svr[i]->_am->ListConnections(&sids); + for (size_t j = 0; j < sids.size(); ++j) { + SocketUniquePtr m; + if (Socket::AddressFailedAsWell(sids[j], &m) == 0) { + DumpRdmaEndpointInfo(s.get(), m.get()); + } + } + } + } + EXPECT_EQ(0, cntl[i].ErrorCode()) << "req[" << i << "]"; + } + + for (int i = 0; i < num; ++i) { + server[i].Stop(0); + server[i].Join(); + } +} + +TEST_P(RdmaRpcTest, send_rpcs_as_pooled_connection) { + if (!FLAGS_rdma_test_enable) { + return; + } + + StartServer(); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 30000; // it may very slow + chan_options.timeout_ms = 30000; + chan_options.max_retry = 0; + chan_options.connection_type = "pooled"; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + Controller cntl[RPC_NUM]; + test::EchoRequest req[RPC_NUM]; + test::EchoResponse res[RPC_NUM]; + + butil::IOBuf attach; + attach.resize(4096); + for (int i = 0; i < RPC_NUM; ++i) { + req[i].set_message(__FUNCTION__); + cntl[i].request_attachment().append(attach); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl[i], &req[i], &res[i], done); + } + for (int i = 0; i < RPC_NUM; ++i) { + bthread_id_join(cntl[i].call_id()); + if (cntl[i].ErrorCode() == ERPCTIMEDOUT) { + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl[i]._single_server_id, &s)); + Socket* m = GetSocketFromServer(0); + DumpRdmaEndpointInfo(s.get(), m); + } + ASSERT_EQ(0, cntl[i].ErrorCode()) << "req[" << i << "]"; + } + + StopServer(); +} + +TEST_P(RdmaRpcTest, send_rpcs_as_short_connection) { + if (!FLAGS_rdma_test_enable) { + return; + } + + StartServer(); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 30000; // it may very slow + chan_options.timeout_ms = 30000; + chan_options.max_retry = 0; + chan_options.connection_type = "short"; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + Controller cntl[RPC_NUM]; + test::EchoRequest req[RPC_NUM]; + test::EchoResponse res[RPC_NUM]; + + butil::IOBuf attach; + attach.resize(4096); + for (int i = 0; i < RPC_NUM; ++i) { + req[i].set_message(__FUNCTION__); + cntl[i].request_attachment().append(attach); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl[i], &req[i], &res[i], done); + } + for (int i = 0; i < RPC_NUM; ++i) { + bthread_id_join(cntl[i].call_id()); + if (cntl[i].ErrorCode() == ERPCTIMEDOUT) { + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl[i]._single_server_id, &s)); + Socket* m = GetSocketFromServer(0); + DumpRdmaEndpointInfo(s.get(), m); + } + ASSERT_EQ(0, cntl[i].ErrorCode()) << "req[" << i << "]"; + } + + StopServer(); +} + +TEST_P(RdmaRpcTest, server_stop_during_rpc) { + if (!FLAGS_rdma_test_enable) { + return; + } + + StartServer(); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 3000; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + Controller cntl[RPC_NUM]; + test::EchoRequest req[RPC_NUM]; + test::EchoResponse res[RPC_NUM]; + + butil::IOBuf attach; + attach.resize(4096); + for (int i = 0; i < RPC_NUM; ++i) { + req[i].set_message(__FUNCTION__); + cntl[i].request_attachment().append(attach); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl[i], &req[i], &res[i], done); + } + + for (int i = 0; i < RPC_NUM; ++i) { + bthread_id_join(cntl[i].call_id()); + if (i == 0) StopServer(); + int error_code = cntl[i].ErrorCode(); + ASSERT_TRUE(error_code == 0 || + error_code == EEOF || + error_code == ELOGOFF || + error_code == EHOSTDOWN) << "req[" << i << "]: " << error_code; + } +} + +TEST_P(RdmaRpcTest, server_close_during_rpc) { + if (!FLAGS_rdma_test_enable) { + return; + } + + StartServer(); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 3000; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + Controller cntl[RPC_NUM]; + test::EchoRequest req[RPC_NUM]; + test::EchoResponse res[RPC_NUM]; + + butil::IOBuf attach; + attach.resize(4096); + for (int i = 0; i < RPC_NUM; ++i) { + req[i].set_message(__FUNCTION__); + cntl[i].request_attachment().append(attach); + if (i == RPC_NUM / 2) { + req[i].set_close_fd(true); + } + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl[i], &req[i], &res[i], done); + } + + for (int i = 0; i < RPC_NUM; ++i) { + bthread_id_join(cntl[i].call_id()); + int error_code = cntl[i].ErrorCode(); + ASSERT_TRUE(error_code == 0 || + error_code == EEOF || + error_code == EFAILEDSOCKET || + error_code == EHOSTDOWN) << "req[" << i << "]: " << error_code; + } + + StopServer(); +} + +TEST_P(RdmaRpcTest, client_close_during_rpc) { + if (!FLAGS_rdma_test_enable) { + return; + } + + StartServer(); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 3000; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + Controller cntl[RPC_NUM]; + test::EchoRequest req[RPC_NUM]; + test::EchoResponse res[RPC_NUM]; + + butil::IOBuf attach; + attach.resize(4096); + for (int i = 0; i < RPC_NUM; ++i) { + req[i].set_message(__FUNCTION__); + cntl[i].request_attachment().append(attach); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl[i], &req[i], &res[i], done); + } + + cntl[0].CloseConnection("Close connection"); + + for (int i = 0; i < RPC_NUM; ++i) { + bthread_id_join(cntl[i].call_id()); + int error_code = cntl[i].ErrorCode(); + ASSERT_TRUE(error_code == 0 || + error_code == ECLOSE || + error_code == EHOSTDOWN) << "req[" << i << "]: " << error_code; + } + + StopServer(); +} + +TEST_P(RdmaRpcTest, verbs_error_handling) { + if (!FLAGS_rdma_test_enable) { + return; + } + + StartServer(); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + req.set_sleep_us(200000); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, done); + + usleep(100000); // wait for rdma handshake complete + + SocketUniquePtr s; + ASSERT_EQ(0, Socket::Address(cntl._single_server_id, &s)); + ibv_send_wr wr; + memset(&wr, 0, sizeof(wr)); + ibv_sge sge; + void* buf = malloc(8192); + sge.addr = (uint64_t)buf; + sge.length = 8192; + sge.lkey = 1; // incorrect lkey + wr.sg_list = &sge; + wr.num_sge = 1; + ibv_send_wr* bad = NULL; + auto rdma_transport = static_cast(s->_transport.get()); + ibv_post_send(rdma_transport->_rdma_ep->_resource->qp, &wr, &bad); + bthread_id_join(cntl.call_id()); + ASSERT_EQ(ERDMA, cntl.ErrorCode()); + free(buf); + + StopServer(); +} + +TEST_P(RdmaRpcTest, rdma_use_parallel_channel) { + if (!FLAGS_rdma_test_enable) { + return; + } + + StartServer(); + + const size_t NCHANS = 8; + Channel subchans[NCHANS]; + ParallelChannel channel; + ChannelOptions opts; + opts.socket_mode = SOCKET_MODE_RDMA; + for (size_t i = 0; i < NCHANS; ++i) { + ASSERT_EQ(0, subchans[i].Init(_naming_url.c_str(), "rR", &opts)); + ASSERT_EQ(0, channel.AddChannel( + &subchans[i], DOESNT_OWN_CHANNEL, + NULL, NULL)); + } + ASSERT_EQ(0, channel.Init(NULL)); + + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, NULL); + + ASSERT_EQ(0, cntl.ErrorCode()); + ASSERT_EQ(NCHANS, (size_t)cntl.sub_count()); + + StopServer(); +} + +TEST_P(RdmaRpcTest, rdma_use_selective_channel) { + if (!FLAGS_rdma_test_enable) { + return; + } + + StartServer(); + + const size_t NCHANS = 8; + SelectiveChannel channel; + ChannelOptions opts; + opts.socket_mode = SOCKET_MODE_RDMA; + ASSERT_EQ(0, channel.Init("rr", &opts)); + for (size_t i = 0; i < NCHANS; ++i) { + Channel* subchan = new Channel; + ASSERT_EQ(0, subchan->Init(_naming_url.c_str(), "rR", &opts)); + ASSERT_EQ(0, channel.AddChannel(subchan, NULL)); + } + + Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(__FUNCTION__); + ::test::EchoService::Stub(&channel).Echo(&cntl, &req, &res, NULL); + + ASSERT_EQ(0, cntl.ErrorCode()) << cntl.ErrorText(); + ASSERT_EQ(1, cntl.sub_count()); + + StopServer(); +} + +static void MockFree(void* buf) { } + +TEST_P(RdmaRpcTest, send_rpcs_with_user_defined_iobuf) { + if (!FLAGS_rdma_test_enable) { + return; + } + + StartServer(); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 500; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + Controller cntl[RPC_NUM]; + test::EchoRequest req[RPC_NUM]; + test::EchoResponse res[RPC_NUM]; + + butil::IOBuf attach; + void* data = malloc(4096);; + attach.append_user_data(data, 4096, NULL); + req[0].set_message(__FUNCTION__); + cntl[0].request_attachment().append(attach); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl[0], &req[0], &res[0], done); + bthread_id_join(cntl[0].call_id()); + ASSERT_EQ(ERDMAMEM, cntl[0].ErrorCode()); + attach.clear(); + sleep(2); // wait for client recover from EHOSTDOWN + cntl[0].Reset(); + + char* mr[2 * RPC_NUM]; + uint32_t lkey[2 * RPC_NUM]; + for (size_t i = 0; i < RPC_NUM; ++i) { + mr[2 * i] = (char*)malloc(4096); + memset(mr[2 * i], i % 100, 4096); + lkey[2 * i] = rdma::RegisterMemoryForRdma(mr[2 * i], 4096); + ASSERT_TRUE(lkey[2 * i] != 0); + cntl[i].request_attachment().append_user_data_with_meta(mr[2 * i] + i, 4096 - i, MockFree, lkey[2 * i]); + mr[2 * i + 1] = (char*)malloc(4096); + memset(mr[2 * i + 1], i % 100, 4096); + lkey[2 * i + 1] = rdma::RegisterMemoryForRdma(mr[2 * i + 1], 4096); + ASSERT_TRUE(lkey[2 * i + 1] != 0); + cntl[i].request_attachment().append_user_data_with_meta(mr[2 * i + 1] + i, 4096 - i, MockFree, lkey[2 * i + 1]); + req[i].set_message(__FUNCTION__); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl[i], &req[i], &res[i], done); + } + for (size_t i = 0; i < RPC_NUM; ++i) { + bthread_id_join(cntl[i].call_id()); + ASSERT_EQ(0, cntl[i].ErrorCode()) << "req[" << i << "]"; + rdma::DeregisterMemoryForRdma(mr[i]); + ASSERT_EQ(2 * (4096 - i), cntl[i].response_attachment().size()); + char tmp[8192]; + cntl[i].response_attachment().copy_to(tmp, 2 * (4096 - i)); + ASSERT_EQ(0, memcmp(mr[2 * i] + i, tmp, 4096 - i)); + ASSERT_EQ(0, memcmp(mr[2 * i + 1] + i, tmp + 4096 - i, 4096 - i)); + free(mr[2 * i]); + free(mr[2 * i + 1]); + } + + StopServer(); +} + +TEST_P(RdmaRpcTest, try_memory_pool_empty) { + if (!FLAGS_rdma_test_enable) { + return; + } + + StartServer(); + + Channel channel; + ChannelOptions chan_options; + chan_options.socket_mode = SOCKET_MODE_RDMA; + chan_options.connect_timeout_ms = 500; + chan_options.timeout_ms = 60000; + chan_options.max_retry = 0; + ASSERT_EQ(0, channel.Init(g_ep, &chan_options)); + Controller cntl[RPC_NUM]; + test::EchoRequest req[RPC_NUM]; + test::EchoResponse res[RPC_NUM]; + + butil::IOBuf iobuf[RPC_NUM]; + for (int i = 0; i < 1024; ++i) { + if (iobuf[i].resize(1048576 * 8)) { + // 8MB for each iobuf + break; + } + } + + for (int i = 0; i < RPC_NUM; ++i) { + req[i].set_message(__FUNCTION__); + cntl[i].request_attachment().append(iobuf[i]); + google::protobuf::Closure* done = DoNothing(); + ::test::EchoService::Stub(&channel).Echo(&cntl[i], &req[i], &res[i], done); + } + for (int i = 0; i < RPC_NUM; ++i) { + bthread_id_join(cntl[i].call_id()); + } + + StopServer(); +} + +// Run every TEST_P(RdmaRpcTest, ...) above twice: once with the +// client-side handshake forced to v2 ("RDMA" magic + fixed-layout +// HelloMessage), once with v3 ("RDM3" magic + protobuf RdmaHello). +// The server always accepts both via magic-byte dispatch, so this +// proves the upper-layer RPC paths behave identically under either +// wire format. +INSTANTIATE_TEST_SUITE_P( + HandshakeVersion, RdmaRpcTest, + ::testing::Values(2, 3), + [](const ::testing::TestParamInfo& info) { + return std::string("v") + std::to_string(info.param); + }); + +#endif // if BRPC_WITH_RDMA + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); +#if BRPC_WITH_RDMA + rdma::FLAGS_rdma_trace_verbose = true; + rdma::FLAGS_rdma_memory_pool_max_regions = 2; + FLAGS_log_idle_connection_close = true; + if (!FLAGS_rdma_test_enable) { + // skip UT requiring rdma runtime environment + rdma::g_rdma_available.store(true, butil::memory_order_relaxed); + rdma::g_skip_rdma_init = true; + } +#endif // if BRPC_WITH_RDMA + return RUN_ALL_TESTS(); +} diff --git a/test/brpc_redis_cluster_unittest.cpp b/test/brpc_redis_cluster_unittest.cpp new file mode 100644 index 0000000..34a6d9b --- /dev/null +++ b/test/brpc_redis_cluster_unittest.cpp @@ -0,0 +1,1598 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include +#include +#include +#include + +#include "bthread/bthread.h" +#include + +#include "bthread/countdown_event.h" +#include "butil/synchronization/lock.h" +#include "brpc/channel.h" +#include "brpc/redis.h" +#include "brpc/redis_cluster.h" +#include "brpc/server.h" + +namespace { + +const int kSplitSlot = 8191; + +uint16_t HashSlot(const std::string& key) { + // Keep this aligned with implementation in redis_cluster.cpp. + static const uint16_t table[256] = { + 0x0000,0x1021,0x2042,0x3063,0x4084,0x50A5,0x60C6,0x70E7, + 0x8108,0x9129,0xA14A,0xB16B,0xC18C,0xD1AD,0xE1CE,0xF1EF, + 0x1231,0x0210,0x3273,0x2252,0x52B5,0x4294,0x72F7,0x62D6, + 0x9339,0x8318,0xB37B,0xA35A,0xD3BD,0xC39C,0xF3FF,0xE3DE, + 0x2462,0x3443,0x0420,0x1401,0x64E6,0x74C7,0x44A4,0x5485, + 0xA56A,0xB54B,0x8528,0x9509,0xE5EE,0xF5CF,0xC5AC,0xD58D, + 0x3653,0x2672,0x1611,0x0630,0x76D7,0x66F6,0x5695,0x46B4, + 0xB75B,0xA77A,0x9719,0x8738,0xF7DF,0xE7FE,0xD79D,0xC7BC, + 0x48C4,0x58E5,0x6886,0x78A7,0x0840,0x1861,0x2802,0x3823, + 0xC9CC,0xD9ED,0xE98E,0xF9AF,0x8948,0x9969,0xA90A,0xB92B, + 0x5AF5,0x4AD4,0x7AB7,0x6A96,0x1A71,0x0A50,0x3A33,0x2A12, + 0xDBFD,0xCBDC,0xFBBF,0xEB9E,0x9B79,0x8B58,0xBB3B,0xAB1A, + 0x6CA6,0x7C87,0x4CE4,0x5CC5,0x2C22,0x3C03,0x0C60,0x1C41, + 0xEDAE,0xFD8F,0xCDEC,0xDDCD,0xAD2A,0xBD0B,0x8D68,0x9D49, + 0x7E97,0x6EB6,0x5ED5,0x4EF4,0x3E13,0x2E32,0x1E51,0x0E70, + 0xFF9F,0xEFBE,0xDFDD,0xCFFC,0xBF1B,0xAF3A,0x9F59,0x8F78, + 0x9188,0x81A9,0xB1CA,0xA1EB,0xD10C,0xC12D,0xF14E,0xE16F, + 0x1080,0x00A1,0x30C2,0x20E3,0x5004,0x4025,0x7046,0x6067, + 0x83B9,0x9398,0xA3FB,0xB3DA,0xC33D,0xD31C,0xE37F,0xF35E, + 0x02B1,0x1290,0x22F3,0x32D2,0x4235,0x5214,0x6277,0x7256, + 0xB5EA,0xA5CB,0x95A8,0x8589,0xF56E,0xE54F,0xD52C,0xC50D, + 0x34E2,0x24C3,0x14A0,0x0481,0x7466,0x6447,0x5424,0x4405, + 0xA7DB,0xB7FA,0x8799,0x97B8,0xE75F,0xF77E,0xC71D,0xD73C, + 0x26D3,0x36F2,0x0691,0x16B0,0x6657,0x7676,0x4615,0x5634, + 0xD94C,0xC96D,0xF90E,0xE92F,0x99C8,0x89E9,0xB98A,0xA9AB, + 0x5844,0x4865,0x7806,0x6827,0x18C0,0x08E1,0x3882,0x28A3, + 0xCB7D,0xDB5C,0xEB3F,0xFB1E,0x8BF9,0x9BD8,0xABBB,0xBB9A, + 0x4A75,0x5A54,0x6A37,0x7A16,0x0AF1,0x1AD0,0x2AB3,0x3A92, + 0xFD2E,0xED0F,0xDD6C,0xCD4D,0xBDAA,0xAD8B,0x9DE8,0x8DC9, + 0x7C26,0x6C07,0x5C64,0x4C45,0x3CA2,0x2C83,0x1CE0,0x0CC1, + 0xEF1F,0xFF3E,0xCF5D,0xDF7C,0xAF9B,0xBFBA,0x8FD9,0x9FF8, + 0x6E17,0x7E36,0x4E55,0x5E74,0x2E93,0x3EB2,0x0ED1,0x1EF0 + }; + + std::string hashed = key; + size_t begin = key.find('{'); + if (begin != std::string::npos) { + size_t end = key.find('}', begin + 1); + if (end != std::string::npos && end > begin + 1) { + hashed = key.substr(begin + 1, end - begin - 1); + } + } + uint16_t crc = 0; + for (size_t i = 0; i < hashed.size(); ++i) { + uint8_t idx = static_cast((crc >> 8) ^ + static_cast(hashed[i])); + crc = static_cast((crc << 8) ^ table[idx]); + } + return crc & 16383; +} + +int OwnerBySlot(int slot) { + return slot <= kSplitSlot ? 0 : 1; +} + +struct ClusterMeta { + std::string endpoint[2]; + bool fail_slots; + bool fail_nodes; + bool slots_empty_host; + std::atomic slots_override_slot; + std::atomic slots_override_owner; + std::atomic accept_requests_on_wrong_owner; + std::unordered_map owner_override; + std::unordered_map forced_error_by_key; + std::atomic slots_calls; + std::atomic nodes_calls; + std::atomic moved_error_calls; + std::atomic ask_error_calls; + std::string custom_nodes_payload; + + bool enable_ask; + std::string ask_key; + int ask_from; + int ask_to; + + std::string redirect_loop_key; + + ClusterMeta() + : fail_slots(false) + , fail_nodes(false) + , slots_empty_host(false) + , slots_override_slot(-1) + , slots_override_owner(-1) + , accept_requests_on_wrong_owner(false) + , slots_calls(0) + , nodes_calls(0) + , moved_error_calls(0) + , ask_error_calls(0) + , enable_ask(false) + , ask_from(0) + , ask_to(1) { + } + + int OwnerOfKey(const std::string& key) const { + std::unordered_map::const_iterator it = owner_override.find(key); + if (it != owner_override.end()) { + return it->second; + } + return OwnerBySlot(HashSlot(key)); + } +}; + +struct NodeData { + int node_id; + ClusterMeta* meta; + butil::Mutex mutex; + std::unordered_map kv; +}; + +class Session : public brpc::Destroyable { +public: + Session() : asking(false) {} + void Destroy() override { delete this; } + bool asking; +}; + +static Session* GetOrCreateSession(brpc::RedisConnContext* ctx) { + if (ctx == NULL) { + return NULL; + } + Session* s = static_cast(ctx->get_session()); + if (s == NULL) { + s = new Session; + ctx->reset_session(s); + } + return s; +} + +static bool ParseEndpoint(const std::string& endpoint, std::string* host, int* port) { + size_t pos = endpoint.rfind(':'); + if (pos == std::string::npos) { + return false; + } + *host = endpoint.substr(0, pos); + *port = atoi(endpoint.substr(pos + 1).c_str()); + return (*port > 0); +} + +class AskingHandler : public brpc::RedisCommandHandler { +public: + brpc::RedisCommandHandlerResult Run(brpc::RedisConnContext* ctx, + const std::vector& /*args*/, + brpc::RedisReply* output, + bool /*flush_batched*/) override { + Session* s = GetOrCreateSession(ctx); + if (s != NULL) { + s->asking = true; + } + output->SetStatus("OK"); + return brpc::REDIS_CMD_HANDLED; + } +}; + +class ClusterCommandHandler : public brpc::RedisCommandHandler { +public: + explicit ClusterCommandHandler(ClusterMeta* meta) : _meta(meta) {} + + brpc::RedisCommandHandlerResult Run(brpc::RedisConnContext* /*ctx*/, + const std::vector& args, + brpc::RedisReply* output, + bool /*flush_batched*/) override { + if (args.size() < 2) { + output->SetError("ERR wrong number of arguments for 'cluster' command"); + return brpc::REDIS_CMD_HANDLED; + } + if (args[1] == "slots") { + _meta->slots_calls.fetch_add(1, std::memory_order_relaxed); + if (_meta->fail_slots) { + output->SetError("ERR cluster slots disabled for test"); + return brpc::REDIS_CMD_HANDLED; + } + + const int override_slot = _meta->slots_override_slot.load(std::memory_order_relaxed); + const int override_owner = _meta->slots_override_owner.load(std::memory_order_relaxed); + const int default_owner = (override_slot >= 0 && override_slot <= 16383) + ? OwnerBySlot(override_slot) + : -1; + if (default_owner != -1 && + (override_owner == 0 || override_owner == 1) && + override_owner != default_owner) { + struct SlotRange { + int start; + int end; + int owner; + SlotRange(int s, int e, int o) : start(s), end(e), owner(o) {} + }; + + std::vector ranges; + if (override_slot <= kSplitSlot) { + if (override_slot > 0) { + ranges.push_back(SlotRange(0, override_slot - 1, 0)); + } + ranges.push_back(SlotRange(override_slot, override_slot, override_owner)); + if (override_slot < kSplitSlot) { + ranges.push_back(SlotRange(override_slot + 1, kSplitSlot, 0)); + } + ranges.push_back(SlotRange(kSplitSlot + 1, 16383, 1)); + } else { + ranges.push_back(SlotRange(0, kSplitSlot, 0)); + if (override_slot > kSplitSlot + 1) { + ranges.push_back(SlotRange(kSplitSlot + 1, override_slot - 1, 1)); + } + ranges.push_back(SlotRange(override_slot, override_slot, override_owner)); + if (override_slot < 16383) { + ranges.push_back(SlotRange(override_slot + 1, 16383, 1)); + } + } + + output->SetArray(ranges.size()); + for (size_t i = 0; i < ranges.size(); ++i) { + FillSlotEntry((*output)[i], ranges[i].start, ranges[i].end, + _meta->endpoint[ranges[i].owner], _meta->slots_empty_host); + } + return brpc::REDIS_CMD_HANDLED; + } + + output->SetArray(2); + FillSlotEntry((*output)[0], 0, kSplitSlot, _meta->endpoint[0], + _meta->slots_empty_host); + FillSlotEntry((*output)[1], kSplitSlot + 1, 16383, _meta->endpoint[1], + _meta->slots_empty_host); + return brpc::REDIS_CMD_HANDLED; + } + if (args[1] == "nodes") { + _meta->nodes_calls.fetch_add(1, std::memory_order_relaxed); + if (_meta->fail_nodes) { + output->SetError("ERR cluster nodes disabled for test"); + return brpc::REDIS_CMD_HANDLED; + } + if (!_meta->custom_nodes_payload.empty()) { + output->SetString(_meta->custom_nodes_payload); + return brpc::REDIS_CMD_HANDLED; + } + std::ostringstream oss; + oss << "node0 " << _meta->endpoint[0] << "@17000 master - 0 0 1 connected 0-" + << kSplitSlot << "\n"; + oss << "node1 " << _meta->endpoint[1] << "@17001 master - 0 0 1 connected " + << (kSplitSlot + 1) << "-16383\n"; + output->SetString(oss.str()); + return brpc::REDIS_CMD_HANDLED; + } + output->SetError("ERR unsupported CLUSTER subcommand"); + return brpc::REDIS_CMD_HANDLED; + } + +private: + static void FillSlotEntry(brpc::RedisReply& reply, int start, int end, + const std::string& endpoint, + bool empty_host) { + std::string host; + int port = 0; + ParseEndpoint(endpoint, &host, &port); + reply.SetArray(3); + reply[0].SetInteger(start); + reply[1].SetInteger(end); + reply[2].SetArray(2); + if (empty_host) { + reply[2][0].SetString(""); + } else { + reply[2][0].SetString(host); + } + reply[2][1].SetInteger(port); + } + +private: + ClusterMeta* _meta; +}; + +class KVCommandHandler : public brpc::RedisCommandHandler { +public: + explicit KVCommandHandler(NodeData* data) : _data(data) {} + + brpc::RedisCommandHandlerResult Run(brpc::RedisConnContext* ctx, + const std::vector& args, + brpc::RedisReply* output, + bool /*flush_batched*/) override { + if (args.empty()) { + output->SetError("ERR empty command"); + return brpc::REDIS_CMD_HANDLED; + } + const std::string command = args[0].as_string(); + if (command == "ping") { + output->SetStatus("PONG"); + return brpc::REDIS_CMD_HANDLED; + } + if (command == "eval" || command == "evalsha") { + output->SetStatus("OK"); + return brpc::REDIS_CMD_HANDLED; + } + if (args.size() < 2) { + output->SetError("ERR wrong number of arguments"); + return brpc::REDIS_CMD_HANDLED; + } + + const std::string key = args[1].as_string(); + const int slot = HashSlot(key); + const int owner = _data->meta->OwnerOfKey(key); + + std::unordered_map::const_iterator forced = + _data->meta->forced_error_by_key.find(key); + if (forced != _data->meta->forced_error_by_key.end()) { + output->SetError(forced->second); + return brpc::REDIS_CMD_HANDLED; + } + + if (!_data->meta->redirect_loop_key.empty() && + key == _data->meta->redirect_loop_key) { + const int target = 1 - _data->node_id; + _data->meta->moved_error_calls.fetch_add(1, std::memory_order_relaxed); + output->FormatError("MOVED %d %s", slot, + _data->meta->endpoint[target].c_str()); + return brpc::REDIS_CMD_HANDLED; + } + + bool bypass_owner_check = false; + if (_data->meta->enable_ask && key == _data->meta->ask_key) { + if (_data->node_id == _data->meta->ask_from) { + _data->meta->ask_error_calls.fetch_add(1, std::memory_order_relaxed); + output->FormatError("ASK %d %s", slot, + _data->meta->endpoint[_data->meta->ask_to].c_str()); + return brpc::REDIS_CMD_HANDLED; + } + if (_data->node_id == _data->meta->ask_to) { + Session* s = GetOrCreateSession(ctx); + if (s == NULL || !s->asking) { + output->SetError("ERR ASKING required"); + return brpc::REDIS_CMD_HANDLED; + } + s->asking = false; + bypass_owner_check = true; + } + } + + const bool enforce_owner = + !_data->meta->accept_requests_on_wrong_owner.load(std::memory_order_relaxed); + if (!bypass_owner_check && enforce_owner && owner != _data->node_id) { + _data->meta->moved_error_calls.fetch_add(1, std::memory_order_relaxed); + output->FormatError("MOVED %d %s", slot, _data->meta->endpoint[owner].c_str()); + return brpc::REDIS_CMD_HANDLED; + } + + if (command == "set") { + if (args.size() < 3) { + output->SetError("ERR wrong number of arguments for 'set' command"); + return brpc::REDIS_CMD_HANDLED; + } + BAIDU_SCOPED_LOCK(_data->mutex); + _data->kv[key] = args[2].as_string(); + output->SetStatus("OK"); + return brpc::REDIS_CMD_HANDLED; + } + if (command == "get") { + BAIDU_SCOPED_LOCK(_data->mutex); + std::unordered_map::iterator it = _data->kv.find(key); + if (it == _data->kv.end()) { + output->SetNullString(); + } else { + output->SetString(it->second); + } + return brpc::REDIS_CMD_HANDLED; + } + if (command == "del" || command == "unlink") { + BAIDU_SCOPED_LOCK(_data->mutex); + output->SetInteger(_data->kv.erase(key) ? 1 : 0); + return brpc::REDIS_CMD_HANDLED; + } + if (command == "exists") { + BAIDU_SCOPED_LOCK(_data->mutex); + output->SetInteger(_data->kv.count(key) ? 1 : 0); + return brpc::REDIS_CMD_HANDLED; + } + + output->SetError("ERR unsupported command"); + return brpc::REDIS_CMD_HANDLED; + } + +private: + NodeData* _data; +}; + +class ClusterRedisService : public brpc::RedisService { +public: + explicit ClusterRedisService(NodeData* data) + : _asking_handler(new AskingHandler()) + , _cluster_handler(new ClusterCommandHandler(data->meta)) + , _kv_handler(new KVCommandHandler(data)) { + AddCommandHandler("asking", _asking_handler.get()); + AddCommandHandler("cluster", _cluster_handler.get()); + AddCommandHandler("ping", _kv_handler.get()); + AddCommandHandler("get", _kv_handler.get()); + AddCommandHandler("set", _kv_handler.get()); + AddCommandHandler("del", _kv_handler.get()); + AddCommandHandler("exists", _kv_handler.get()); + AddCommandHandler("unlink", _kv_handler.get()); + AddCommandHandler("eval", _kv_handler.get()); + AddCommandHandler("evalsha", _kv_handler.get()); + } + +private: + std::unique_ptr _asking_handler; + std::unique_ptr _cluster_handler; + std::unique_ptr _kv_handler; +}; + +class Done : public google::protobuf::Closure { +public: + explicit Done(bthread::CountdownEvent* e) : _event(e) {} + void Run() override { _event->signal(); } +private: + bthread::CountdownEvent* _event; +}; + +class RedisClusterChannelTest : public testing::Test { +protected: + void SetUp() override { + _meta.reset(new ClusterMeta); + _node[0].meta = _meta.get(); + _node[0].node_id = 0; + _node[1].meta = _meta.get(); + _node[1].node_id = 1; + + StartServer(0); + StartServer(1); + } + + void TearDown() override { + for (int i = 0; i < 2; ++i) { + _server[i].Stop(0); + } + for (int i = 0; i < 2; ++i) { + _server[i].Join(); + } + } + + std::string SeedList() const { + return _meta->endpoint[0] + "," + _meta->endpoint[1]; + } + + void InitChannel(brpc::RedisClusterChannel* channel, int max_redirect = 5) { + brpc::RedisClusterChannelOptions options; + options.max_redirect = max_redirect; + options.enable_periodic_refresh = false; + ASSERT_EQ(0, channel->Init(SeedList(), &options)); + } + + std::string FindKeyForNode(int node_id) const { + for (int i = 0; i < 200000; ++i) { + std::ostringstream oss; + oss << "key_" << node_id << '_' << i; + if (OwnerBySlot(HashSlot(oss.str())) == node_id) { + return oss.str(); + } + } + return "fallback_key"; + } + + std::vector FindKeysForNode(int node_id, size_t count) const { + std::vector keys; + keys.reserve(count); + for (int i = 0; i < 400000 && keys.size() < count; ++i) { + std::ostringstream oss; + oss << "key_batch_" << node_id << '_' << i; + if (OwnerBySlot(HashSlot(oss.str())) == node_id) { + keys.push_back(oss.str()); + } + } + return keys; + } + + std::string FindHashTagForNode(int node_id) const { + for (int i = 0; i < 200000; ++i) { + std::ostringstream oss; + oss << "tag_" << node_id << '_' << i; + const std::string key = "{" + oss.str() + "}"; + if (OwnerBySlot(HashSlot(key)) == node_id) { + return oss.str(); + } + } + return "fallback_tag"; + } + +private: + void StartServer(int index) { + brpc::ServerOptions options; + options.redis_service = new ClusterRedisService(&_node[index]); + brpc::PortRange range(20000 + index * 1000, 20999 + index * 1000); + ASSERT_EQ(0, _server[index].Start("127.0.0.1", range, &options)); + std::ostringstream oss; + oss << "127.0.0.1:" << _server[index].listen_address().port; + _meta->endpoint[index] = oss.str(); + } + +protected: + std::unique_ptr _meta; + NodeData _node[2]; + brpc::Server _server[2]; +}; + +TEST_F(RedisClusterChannelTest, basic_routing_and_multi_key_commands) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + const std::string key0 = FindKeyForNode(0); + const std::string key1 = FindKeyForNode(1); + + { + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("mset %s v0 %s v1", key0.c_str(), key1.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_string()); + ASSERT_EQ("OK", resp.reply(0).data()); + } + + { + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("mget %s %s", key0.c_str(), key1.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_array()); + ASSERT_EQ(2u, resp.reply(0).size()); + ASSERT_EQ("v0", resp.reply(0)[0].data()); + ASSERT_EQ("v1", resp.reply(0)[1].data()); + } + + { + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("exists %s %s", key0.c_str(), key1.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(2, resp.reply(0).integer()); + } + + { + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("del %s %s", key0.c_str(), key1.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(2, resp.reply(0).integer()); + } +} + +TEST_F(RedisClusterChannelTest, moved_redirection) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + const std::string moved_key = FindKeyForNode(0); + _meta->owner_override[moved_key] = 1; + { + BAIDU_SCOPED_LOCK(_node[1].mutex); + _node[1].kv[moved_key] = "moved-value"; + } + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("get %s", moved_key.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_string()); + ASSERT_EQ("moved-value", resp.reply(0).data()); +} + +TEST_F(RedisClusterChannelTest, ask_redirection) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + _meta->enable_ask = true; + _meta->ask_from = 0; + _meta->ask_to = 1; + _meta->ask_key = FindKeyForNode(0); + { + BAIDU_SCOPED_LOCK(_node[1].mutex); + _node[1].kv[_meta->ask_key] = "ask-value"; + } + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("get %s", _meta->ask_key.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_string()); + ASSERT_EQ("ask-value", resp.reply(0).data()); +} + +TEST_F(RedisClusterChannelTest, ask_redirection_does_not_override_slot_cache) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + _meta->enable_ask = true; + _meta->ask_from = 0; + _meta->ask_to = 1; + _meta->ask_key = FindKeyForNode(0); + { + BAIDU_SCOPED_LOCK(_node[1].mutex); + _node[1].kv[_meta->ask_key] = "ask-value"; + } + + for (int i = 0; i < 5; ++i) { + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("get %s", _meta->ask_key.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_string()); + ASSERT_EQ("ask-value", resp.reply(0).data()); + } +} + +TEST_F(RedisClusterChannelTest, cluster_nodes_fallback) { + _meta->fail_slots = true; + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + const std::string key = FindKeyForNode(1); + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("set %s vv", key.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_TRUE(resp.reply(0).is_string()); + ASSERT_EQ("OK", resp.reply(0).data()); +} + +TEST_F(RedisClusterChannelTest, eval_and_evalsha) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + const std::string key0 = FindKeyForNode(0); + const std::string key1 = FindKeyForNode(1); + + { + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + const butil::StringPiece parts[] = { + "eval", "return 1", "2", key0, key1 + }; + ASSERT_TRUE(req.AddCommandByComponents(parts, sizeof(parts) / sizeof(parts[0]))); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_error()); + ASSERT_NE(std::string::npos, + std::string(resp.reply(0).error_message()).find("CROSSSLOT")); + } + + { + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("evalsha abcdef 1 %s arg1", key0.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_string()); + ASSERT_EQ("OK", resp.reply(0).data()); + } +} + +TEST_F(RedisClusterChannelTest, redirect_retry_limit) { + brpc::RedisClusterChannel channel; + InitChannel(&channel, 3); + + _meta->redirect_loop_key = FindKeyForNode(0); + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("get %s", _meta->redirect_loop_key.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_TRUE(cntl.Failed()); + ASSERT_NE(std::string::npos, cntl.ErrorText().find("redirect")); +} + +TEST_F(RedisClusterChannelTest, async_call) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + const std::string key = FindKeyForNode(1); + { + BAIDU_SCOPED_LOCK(_node[1].mutex); + _node[1].kv[key] = "async-value"; + } + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("get %s", key.c_str())); + + bthread::CountdownEvent event(1); + Done done(&event); + channel.CallMethod(NULL, &cntl, &req, &resp, &done); + event.wait(); + + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_string()); + ASSERT_EQ("async-value", resp.reply(0).data()); +} + +TEST_F(RedisClusterChannelTest, pipeline_order_with_mixed_commands) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + const std::string key0 = FindKeyForNode(0); + const std::string key1 = FindKeyForNode(1); + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("set %s va", key0.c_str())); + ASSERT_TRUE(req.AddCommand("set %s vb", key1.c_str())); + ASSERT_TRUE(req.AddCommand("mget %s %s", key0.c_str(), key1.c_str())); + ASSERT_TRUE(req.AddCommand("exists %s %s", key0.c_str(), key1.c_str())); + ASSERT_TRUE(req.AddCommand("unlink %s %s", key0.c_str(), key1.c_str())); + ASSERT_TRUE(req.AddCommand("mget %s %s", key0.c_str(), key1.c_str())); + + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(6, resp.reply_size()); + ASSERT_EQ("OK", resp.reply(0).data()); + ASSERT_EQ("OK", resp.reply(1).data()); + + ASSERT_TRUE(resp.reply(2).is_array()); + ASSERT_EQ(2u, resp.reply(2).size()); + ASSERT_EQ("va", resp.reply(2)[0].data()); + ASSERT_EQ("vb", resp.reply(2)[1].data()); + + ASSERT_TRUE(resp.reply(3).is_integer()); + ASSERT_EQ(2, resp.reply(3).integer()); + ASSERT_TRUE(resp.reply(4).is_integer()); + ASSERT_EQ(2, resp.reply(4).integer()); + + ASSERT_TRUE(resp.reply(5).is_array()); + ASSERT_EQ(2u, resp.reply(5).size()); + ASSERT_TRUE(resp.reply(5)[0].is_nil()); + ASSERT_TRUE(resp.reply(5)[1].is_nil()); +} + +TEST_F(RedisClusterChannelTest, transaction_commands_are_not_supported) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("multi")); + ASSERT_TRUE(req.AddCommand("exec")); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(2, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_error()); + ASSERT_TRUE(resp.reply(1).is_error()); + ASSERT_NE(std::string::npos, + std::string(resp.reply(0).error_message()).find("not supported")); +} + +TEST_F(RedisClusterChannelTest, eval_argument_validation) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + { + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + const butil::StringPiece parts[] = { + "eval", "return 1", "abc", "k1" + }; + ASSERT_TRUE(req.AddCommandByComponents(parts, sizeof(parts) / sizeof(parts[0]))); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_error()); + ASSERT_NE(std::string::npos, + std::string(resp.reply(0).error_message()).find("invalid numkeys")); + } + + { + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + const butil::StringPiece parts[] = { + "eval", "return 1", "2", "k1" + }; + ASSERT_TRUE(req.AddCommandByComponents(parts, sizeof(parts) / sizeof(parts[0]))); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_error()); + ASSERT_NE(std::string::npos, + std::string(resp.reply(0).error_message()).find("not enough keys")); + } +} + +TEST_F(RedisClusterChannelTest, async_failure_propagation) { + brpc::RedisClusterChannel channel; + InitChannel(&channel, 1); + _meta->redirect_loop_key = FindKeyForNode(0); + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("get %s", _meta->redirect_loop_key.c_str())); + + bthread::CountdownEvent event(1); + Done done(&event); + channel.CallMethod(NULL, &cntl, &req, &resp, &done); + event.wait(); + + ASSERT_TRUE(cntl.Failed()); + ASSERT_NE(std::string::npos, cntl.ErrorText().find("redirect")); +} + +TEST_F(RedisClusterChannelTest, max_redirect_zero_fails_on_single_redirect) { + brpc::RedisClusterChannel channel; + InitChannel(&channel, 0); + + const std::string key = FindKeyForNode(0); + _meta->owner_override[key] = 1; + { + BAIDU_SCOPED_LOCK(_node[1].mutex); + _node[1].kv[key] = "value-on-node1"; + } + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("get %s", key.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + + ASSERT_TRUE(cntl.Failed()); + ASSERT_NE(std::string::npos, cntl.ErrorText().find("redirect")); +} + +TEST_F(RedisClusterChannelTest, redirect_with_refresh_failure_still_returns_reply) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + const std::string key = FindKeyForNode(0); + _meta->owner_override[key] = 1; + { + BAIDU_SCOPED_LOCK(_node[1].mutex); + _node[1].kv[key] = "moved-value"; + } + + const int before_slots = _meta->slots_calls.load(std::memory_order_relaxed); + const int before_nodes = _meta->nodes_calls.load(std::memory_order_relaxed); + _meta->fail_slots = true; + _meta->fail_nodes = true; + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("get %s", key.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_TRUE(resp.reply(0).is_string()); + ASSERT_EQ("moved-value", resp.reply(0).data()); + + ASSERT_GT(_meta->slots_calls.load(std::memory_order_relaxed), before_slots); + ASSERT_GT(_meta->nodes_calls.load(std::memory_order_relaxed), before_nodes); +} + +TEST_F(RedisClusterChannelTest, pipeline_with_ask_and_moved_keeps_order) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + const std::string ask_key = FindKeyForNode(0); + std::string moved_key; + for (int i = 0; i < 200000; ++i) { + std::ostringstream oss; + oss << "moved_key_" << i; + if (OwnerBySlot(HashSlot(oss.str())) == 0 && oss.str() != ask_key) { + moved_key = oss.str(); + break; + } + } + ASSERT_FALSE(moved_key.empty()); + + _meta->enable_ask = true; + _meta->ask_from = 0; + _meta->ask_to = 1; + _meta->ask_key = ask_key; + _meta->owner_override[moved_key] = 1; + { + BAIDU_SCOPED_LOCK(_node[1].mutex); + _node[1].kv[ask_key] = "ask-value"; + _node[1].kv[moved_key] = "moved-value"; + } + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("get %s", ask_key.c_str())); + ASSERT_TRUE(req.AddCommand("get %s", moved_key.c_str())); + ASSERT_TRUE(req.AddCommand("get %s", ask_key.c_str())); + + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(3, resp.reply_size()); + ASSERT_EQ("ask-value", resp.reply(0).data()); + ASSERT_EQ("moved-value", resp.reply(1).data()); + ASSERT_EQ("ask-value", resp.reply(2).data()); +} + +TEST_F(RedisClusterChannelTest, fallback_to_nodes_then_recover_to_slots) { + _meta->fail_slots = true; + + brpc::RedisClusterChannel channel; + InitChannel(&channel); + ASSERT_GT(_meta->nodes_calls.load(std::memory_order_relaxed), 0); + + _meta->fail_slots = false; + const int before_slots = _meta->slots_calls.load(std::memory_order_relaxed); + + const std::string key = FindKeyForNode(0); + _meta->owner_override[key] = 1; + { + BAIDU_SCOPED_LOCK(_node[1].mutex); + _node[1].kv[key] = "recover-value"; + } + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("get %s", key.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ("recover-value", resp.reply(0).data()); + ASSERT_GT(_meta->slots_calls.load(std::memory_order_relaxed), before_slots); +} + +TEST_F(RedisClusterChannelTest, cluster_slots_empty_host_uses_seed_host) { + _meta->slots_empty_host = true; + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + const std::string key = FindKeyForNode(1); + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("set %s host-fallback-value", key.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ("OK", resp.reply(0).data()); +} + +TEST_F(RedisClusterChannelTest, init_accepts_whitespace_in_seed_list) { + brpc::RedisClusterChannel channel; + brpc::RedisClusterChannelOptions options; + options.enable_periodic_refresh = false; + const std::string seeds = " " + _meta->endpoint[0] + " , " + _meta->endpoint[1] + " "; + ASSERT_EQ(0, channel.Init(seeds, &options)); +} + +TEST_F(RedisClusterChannelTest, init_with_invalid_seed_tokens_should_fail) { + brpc::RedisClusterChannel channel; + ASSERT_NE(0, channel.Init(" , , ")); +} + +TEST_F(RedisClusterChannelTest, init_fails_when_cluster_topology_unavailable) { + _meta->fail_slots = true; + _meta->fail_nodes = true; + + brpc::RedisClusterChannel channel; + brpc::RedisClusterChannelOptions options; + options.enable_periodic_refresh = false; + ASSERT_NE(0, channel.Init(SeedList(), &options)); +} + +TEST_F(RedisClusterChannelTest, ping_without_key_uses_any_endpoint) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("ping")); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_string()); + ASSERT_EQ("PONG", resp.reply(0).data()); +} + +TEST_F(RedisClusterChannelTest, wrong_argument_count_commands_return_error_reply) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("mget")); + ASSERT_TRUE(req.AddCommand("mset only_key")); + ASSERT_TRUE(req.AddCommand("del")); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(3, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_error()); + ASSERT_TRUE(resp.reply(1).is_error()); + ASSERT_TRUE(resp.reply(2).is_error()); +} + +TEST_F(RedisClusterChannelTest, malformed_redirect_error_is_returned_directly) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + const std::string key = FindKeyForNode(0); + _meta->forced_error_by_key[key] = "MOVED not_a_slot bad_endpoint"; + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("get %s", key.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_error()); + ASSERT_EQ("MOVED not_a_slot bad_endpoint", + std::string(resp.reply(0).error_message())); +} + +TEST_F(RedisClusterChannelTest, cluster_nodes_parser_ignores_migration_tokens) { + _meta->fail_slots = true; + std::ostringstream nodes; + nodes << "node0 " << _meta->endpoint[0] + << "@17000 master - 0 0 1 connected 0-" << kSplitSlot + << " [100->-node1]\n"; + nodes << "node1 " << _meta->endpoint[1] + << "@17001 master - 0 0 1 connected " << (kSplitSlot + 1) + << "-16383 [100-<-node0]\n"; + _meta->custom_nodes_payload = nodes.str(); + + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + const std::string key = FindKeyForNode(1); + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("set %s from-nodes", key.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ("OK", resp.reply(0).data()); +} + +TEST_F(RedisClusterChannelTest, eval_numkeys_zero_routes_without_slot) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + const butil::StringPiece parts[] = { + "eval", "return 'ok'", "0", "arg1" + }; + ASSERT_TRUE(req.AddCommandByComponents(parts, sizeof(parts) / sizeof(parts[0]))); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_string()); + ASSERT_EQ("OK", resp.reply(0).data()); +} + +TEST_F(RedisClusterChannelTest, mset_stops_after_subcommand_error) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + std::vector keys0 = FindKeysForNode(0, 2); + ASSERT_EQ(2u, keys0.size()); + const std::string key_ok = keys0[0]; + const std::string key_tail = keys0[1]; + const std::string key_err = FindKeyForNode(1); + ASSERT_NE(key_ok, key_err); + ASSERT_NE(key_tail, key_err); + + _meta->forced_error_by_key[key_err] = "ERR injected set failure"; + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("mset %s v0 %s v1 %s v2", + key_ok.c_str(), + key_err.c_str(), + key_tail.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_error()); + ASSERT_NE(std::string::npos, + std::string(resp.reply(0).error_message()).find("injected set failure")); + + { + brpc::RedisRequest get_req; + brpc::RedisResponse get_resp; + brpc::Controller get_cntl; + ASSERT_TRUE(get_req.AddCommand("get %s", key_ok.c_str())); + channel.CallMethod(NULL, &get_cntl, &get_req, &get_resp, NULL); + ASSERT_FALSE(get_cntl.Failed()) << get_cntl.ErrorText(); + ASSERT_TRUE(get_resp.reply(0).is_string()); + ASSERT_EQ("v0", get_resp.reply(0).data()); + } + { + brpc::RedisRequest get_req; + brpc::RedisResponse get_resp; + brpc::Controller get_cntl; + ASSERT_TRUE(get_req.AddCommand("get %s", key_tail.c_str())); + channel.CallMethod(NULL, &get_cntl, &get_req, &get_resp, NULL); + ASSERT_FALSE(get_cntl.Failed()) << get_cntl.ErrorText(); + ASSERT_TRUE(get_resp.reply(0).is_nil()); + } +} + +TEST_F(RedisClusterChannelTest, integer_aggregate_stops_after_subcommand_error) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + const std::string key0 = FindKeyForNode(0); + std::vector keys1 = FindKeysForNode(1, 2); + ASSERT_EQ(2u, keys1.size()); + const std::string key_err = keys1[0]; + const std::string key_tail = keys1[1]; + + { + BAIDU_SCOPED_LOCK(_node[0].mutex); + _node[0].kv[key0] = "v0"; + } + { + BAIDU_SCOPED_LOCK(_node[1].mutex); + _node[1].kv[key_err] = "verr"; + _node[1].kv[key_tail] = "vtail"; + } + _meta->forced_error_by_key[key_err] = "ERR injected unlink failure"; + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("unlink %s %s %s", + key0.c_str(), key_err.c_str(), key_tail.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_error()); + ASSERT_NE(std::string::npos, + std::string(resp.reply(0).error_message()).find("injected unlink failure")); + + brpc::RedisRequest get_req; + brpc::RedisResponse get_resp; + brpc::Controller get_cntl; + ASSERT_TRUE(get_req.AddCommand("get %s", key_tail.c_str())); + channel.CallMethod(NULL, &get_cntl, &get_req, &get_resp, NULL); + ASSERT_FALSE(get_cntl.Failed()) << get_cntl.ErrorText(); + ASSERT_TRUE(get_resp.reply(0).is_string()); + ASSERT_EQ("vtail", get_resp.reply(0).data()); +} + +TEST_F(RedisClusterChannelTest, async_concurrent_calls_with_mixed_redirections) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + std::vector keys0 = FindKeysForNode(0, 2); + ASSERT_EQ(2u, keys0.size()); + const std::string ask_key = keys0[0]; + const std::string moved_key = keys0[1]; + const std::string normal_key = FindKeyForNode(1); + + _meta->enable_ask = true; + _meta->ask_from = 0; + _meta->ask_to = 1; + _meta->ask_key = ask_key; + _meta->owner_override[moved_key] = 1; + { + BAIDU_SCOPED_LOCK(_node[1].mutex); + _node[1].kv[ask_key] = "ask-v"; + _node[1].kv[moved_key] = "moved-v"; + _node[1].kv[normal_key] = "normal-v"; + } + + const int req_count = 60; + bthread::CountdownEvent event(req_count); + std::vector > requests(req_count); + std::vector > responses(req_count); + std::vector > controllers(req_count); + std::vector > dones(req_count); + std::vector expected(req_count); + + for (int i = 0; i < req_count; ++i) { + requests[i].reset(new brpc::RedisRequest); + responses[i].reset(new brpc::RedisResponse); + controllers[i].reset(new brpc::Controller); + dones[i].reset(new Done(&event)); + + std::string key; + if (i % 3 == 0) { + key = ask_key; + expected[i] = "ask-v"; + } else if (i % 3 == 1) { + key = moved_key; + expected[i] = "moved-v"; + } else { + key = normal_key; + expected[i] = "normal-v"; + } + ASSERT_TRUE(requests[i]->AddCommand("get %s", key.c_str())); + channel.CallMethod(NULL, + controllers[i].get(), + requests[i].get(), + responses[i].get(), + dones[i].get()); + } + + event.wait(); + + for (int i = 0; i < req_count; ++i) { + ASSERT_FALSE(controllers[i]->Failed()) << controllers[i]->ErrorText(); + ASSERT_EQ(1, responses[i]->reply_size()); + ASSERT_TRUE(responses[i]->reply(0).is_string()); + ASSERT_EQ(expected[i], responses[i]->reply(0).data()); + } + ASSERT_GT(_meta->ask_error_calls.load(std::memory_order_relaxed), 0); + ASSERT_GT(_meta->moved_error_calls.load(std::memory_order_relaxed), 0); +} + +TEST_F(RedisClusterChannelTest, hashtag_keys_route_for_multi_key_commands) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + const std::string tag = FindHashTagForNode(1); + const std::string key0 = "k0{" + tag + "}suffix"; + const std::string key1 = "k1{" + tag + "}suffix"; + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("mset %s v0 %s v1", key0.c_str(), key1.c_str())); + ASSERT_TRUE(req.AddCommand("mget %s %s", key0.c_str(), key1.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(2, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_string()); + ASSERT_EQ("OK", resp.reply(0).data()); + ASSERT_TRUE(resp.reply(1).is_array()); + ASSERT_EQ("v0", resp.reply(1)[0].data()); + ASSERT_EQ("v1", resp.reply(1)[1].data()); + ASSERT_EQ(0, _meta->moved_error_calls.load(std::memory_order_relaxed)); +} + +TEST_F(RedisClusterChannelTest, missing_key_get_returns_nil_reply) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + const std::string key = FindKeyForNode(0); + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("get %s", key.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_nil()); +} + +TEST_F(RedisClusterChannelTest, pipeline_with_string_nil_error_and_string) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + const std::string key_ok = FindKeyForNode(1); + const std::string key_nil = FindKeyForNode(0); + std::string key_err; + for (int i = 0; i < 200000; ++i) { + std::ostringstream oss; + oss << "err_key_" << i; + if (OwnerBySlot(HashSlot(oss.str())) == 1 && oss.str() != key_ok) { + key_err = oss.str(); + break; + } + } + ASSERT_FALSE(key_err.empty()); + + { + BAIDU_SCOPED_LOCK(_node[1].mutex); + _node[1].kv[key_ok] = "ok-value"; + } + _meta->forced_error_by_key[key_err] = "ERR injected pipeline error"; + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("get %s", key_ok.c_str())); + ASSERT_TRUE(req.AddCommand("get %s", key_nil.c_str())); + ASSERT_TRUE(req.AddCommand("get %s", key_err.c_str())); + ASSERT_TRUE(req.AddCommand("get %s", key_ok.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(4, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_string()); + ASSERT_EQ("ok-value", resp.reply(0).data()); + ASSERT_TRUE(resp.reply(1).is_nil()); + ASSERT_TRUE(resp.reply(2).is_error()); + ASSERT_NE(std::string::npos, + std::string(resp.reply(2).error_message()).find("injected pipeline error")); + ASSERT_TRUE(resp.reply(3).is_string()); + ASSERT_EQ("ok-value", resp.reply(3).data()); +} + +TEST_F(RedisClusterChannelTest, empty_request_should_fail) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_TRUE(cntl.Failed()); + ASSERT_NE(std::string::npos, cntl.ErrorText().find("no redis command")); +} + +TEST_F(RedisClusterChannelTest, pipeline_continues_after_command_error_reply) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + const std::string key_ok = FindKeyForNode(1); + const std::string key_err = FindKeyForNode(0); + _meta->forced_error_by_key[key_err] = "ERR injected get failure"; + { + BAIDU_SCOPED_LOCK(_node[1].mutex); + _node[1].kv[key_ok] = "ok-value"; + } + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("get %s", key_err.c_str())); + ASSERT_TRUE(req.AddCommand("get %s", key_ok.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(2, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_error()); + ASSERT_NE(std::string::npos, + std::string(resp.reply(0).error_message()).find("injected get failure")); + ASSERT_TRUE(resp.reply(1).is_string()); + ASSERT_EQ("ok-value", resp.reply(1).data()); +} + +TEST_F(RedisClusterChannelTest, redirect_updates_slot_cache_even_when_refresh_fails) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + const std::string key = FindKeyForNode(0); + _meta->owner_override[key] = 1; + { + BAIDU_SCOPED_LOCK(_node[1].mutex); + _node[1].kv[key] = "value-on-node1"; + } + _meta->fail_slots = true; + _meta->fail_nodes = true; + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("get %s", key.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ("value-on-node1", resp.reply(0).data()); + + brpc::RedisRequest req2; + brpc::RedisResponse resp2; + brpc::Controller cntl2; + ASSERT_TRUE(req2.AddCommand("get %s", key.c_str())); + channel.CallMethod(NULL, &cntl2, &req2, &resp2, NULL); + ASSERT_FALSE(cntl2.Failed()) << cntl2.ErrorText(); + ASSERT_EQ("value-on-node1", resp2.reply(0).data()); + + ASSERT_EQ(1, _meta->moved_error_calls.load(std::memory_order_relaxed)); +} + +TEST_F(RedisClusterChannelTest, periodic_refresh_fallbacks_to_nodes_when_slots_fail) { + brpc::RedisClusterChannel channel; + brpc::RedisClusterChannelOptions options; + options.enable_periodic_refresh = true; + options.refresh_interval_s = 1; + options.max_redirect = 5; + ASSERT_EQ(0, channel.Init(SeedList(), &options)); + + _meta->fail_slots = true; + const int before_nodes = _meta->nodes_calls.load(std::memory_order_relaxed); + bool nodes_used = false; + for (int i = 0; i < 30; ++i) { + if (_meta->nodes_calls.load(std::memory_order_relaxed) > before_nodes) { + nodes_used = true; + break; + } + bthread_usleep(100000); + } + ASSERT_TRUE(nodes_used); +} + +TEST_F(RedisClusterChannelTest, periodic_refresh_updates_slot_cache_on_topology_change) { + brpc::RedisClusterChannel channel; + brpc::RedisClusterChannelOptions options; + options.enable_periodic_refresh = true; + options.refresh_interval_s = 1; + options.max_redirect = 5; + ASSERT_EQ(0, channel.Init(SeedList(), &options)); + + _meta->accept_requests_on_wrong_owner.store(true, std::memory_order_relaxed); + + const std::string key = FindKeyForNode(0); + const int slot = static_cast(HashSlot(key)); + const std::string value_by_owner[2] = {"value-on-node0", "value-on-node1"}; + { + BAIDU_SCOPED_LOCK(_node[0].mutex); + _node[0].kv[key] = value_by_owner[0]; + } + { + BAIDU_SCOPED_LOCK(_node[1].mutex); + _node[1].kv[key] = value_by_owner[1]; + } + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("get %s", key.c_str())); + channel.CallMethod(NULL, &cntl, &req, &resp, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, resp.reply_size()); + ASSERT_TRUE(resp.reply(0).is_string()); + ASSERT_EQ(value_by_owner[0], resp.reply(0).data()); + + const int before_slots = _meta->slots_calls.load(std::memory_order_relaxed); + const int target_owner = 1 - OwnerBySlot(slot); + _meta->slots_override_slot.store(slot, std::memory_order_relaxed); + _meta->slots_override_owner.store(target_owner, std::memory_order_relaxed); + + bool updated = false; + for (int i = 0; i < 50; ++i) { + brpc::RedisRequest req2; + brpc::RedisResponse resp2; + brpc::Controller cntl2; + ASSERT_TRUE(req2.AddCommand("get %s", key.c_str())); + channel.CallMethod(NULL, &cntl2, &req2, &resp2, NULL); + ASSERT_FALSE(cntl2.Failed()) << cntl2.ErrorText(); + ASSERT_EQ(1, resp2.reply_size()); + ASSERT_TRUE(resp2.reply(0).is_string()); + if (resp2.reply(0).data() == value_by_owner[target_owner]) { + updated = true; + break; + } + bthread_usleep(100000); + } + ASSERT_TRUE(updated); + ASSERT_GT(_meta->slots_calls.load(std::memory_order_relaxed), before_slots); + ASSERT_EQ(0, _meta->moved_error_calls.load(std::memory_order_relaxed)); +} + +TEST_F(RedisClusterChannelTest, async_pipeline_mixed_commands) { + brpc::RedisClusterChannel channel; + InitChannel(&channel); + + const std::string key0 = FindKeyForNode(0); + const std::string key1 = FindKeyForNode(1); + + brpc::RedisRequest req; + brpc::RedisResponse resp; + brpc::Controller cntl; + ASSERT_TRUE(req.AddCommand("set %s p0", key0.c_str())); + ASSERT_TRUE(req.AddCommand("set %s p1", key1.c_str())); + ASSERT_TRUE(req.AddCommand("mget %s %s", key0.c_str(), key1.c_str())); + ASSERT_TRUE(req.AddCommand("del %s %s", key0.c_str(), key1.c_str())); + + bthread::CountdownEvent event(1); + Done done(&event); + channel.CallMethod(NULL, &cntl, &req, &resp, &done); + event.wait(); + + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(4, resp.reply_size()); + ASSERT_EQ("OK", resp.reply(0).data()); + ASSERT_EQ("OK", resp.reply(1).data()); + ASSERT_TRUE(resp.reply(2).is_array()); + ASSERT_EQ("p0", resp.reply(2)[0].data()); + ASSERT_EQ("p1", resp.reply(2)[1].data()); + ASSERT_TRUE(resp.reply(3).is_integer()); + ASSERT_EQ(2, resp.reply(3).integer()); +} + +TEST_F(RedisClusterChannelTest, periodic_refresh_updates_topology_in_background) { + brpc::RedisClusterChannel channel; + brpc::RedisClusterChannelOptions options; + options.enable_periodic_refresh = true; + options.refresh_interval_s = 1; + options.max_redirect = 5; + ASSERT_EQ(0, channel.Init(SeedList(), &options)); + + const int initial_slots_calls = _meta->slots_calls.load(std::memory_order_relaxed); + bool refreshed = false; + for (int i = 0; i < 30; ++i) { + if (_meta->slots_calls.load(std::memory_order_relaxed) > initial_slots_calls) { + refreshed = true; + break; + } + bthread_usleep(100000); + } + ASSERT_TRUE(refreshed); +} + +TEST_F(RedisClusterChannelTest, periodic_refresh_thread_stops_quickly_on_destroy) { + typedef std::chrono::steady_clock Clock; + const Clock::time_point begin = Clock::now(); + { + brpc::RedisClusterChannel channel; + brpc::RedisClusterChannelOptions options; + options.enable_periodic_refresh = true; + options.refresh_interval_s = 30; + ASSERT_EQ(0, channel.Init(SeedList(), &options)); + } + const Clock::time_point end = Clock::now(); + const int64_t elapsed_ms = + std::chrono::duration_cast(end - begin).count(); + ASSERT_LT(elapsed_ms, 2000); +} + +TEST_F(RedisClusterChannelTest, init_with_empty_seed_should_fail) { + brpc::RedisClusterChannel channel; + ASSERT_NE(0, channel.Init("")); +} + +} // namespace diff --git a/test/brpc_redis_unittest.cpp b/test/brpc_redis_unittest.cpp new file mode 100644 index 0000000..9095c82 --- /dev/null +++ b/test/brpc_redis_unittest.cpp @@ -0,0 +1,1633 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace brpc { +DECLARE_int32(idle_timeout_second); +DECLARE_int32(redis_max_allocation_size); +DECLARE_int32(redis_max_reply_depth); +} + +int main(int argc, char* argv[]) { + brpc::FLAGS_idle_timeout_second = 0; + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + +namespace { +static pthread_once_t download_redis_server_once = PTHREAD_ONCE_INIT; + +static pid_t g_redis_pid = -1; + +static void RemoveRedisServer() { + if (g_redis_pid > 0) { + puts("[Stopping redis-server]"); + char cmd[256]; +#if defined(BAIDU_INTERNAL) + snprintf(cmd, sizeof(cmd), "kill %d; rm -rf redis_server_for_test", g_redis_pid); +#else + snprintf(cmd, sizeof(cmd), "kill %d", g_redis_pid); +#endif + CHECK(0 == system(cmd)); + // Wait for redis to stop + usleep(50000); + } +} + +#define REDIS_SERVER_BIN "redis-server" +#define REDIS_SERVER_PORT "6479" + +static void RunRedisServer() { +#if defined(BAIDU_INTERNAL) + puts("Downloading redis-server..."); + if (system("mkdir -p redis_server_for_test && cd redis_server_for_test && svn co https://svn.baidu.com/third-64/tags/redis/redis_2-6-14-100_PD_BL/bin") != 0) { + puts("Fail to get redis-server from svn"); + return; + } +# undef REDIS_SERVER_BIN +# define REDIS_SERVER_BIN "redis_server_for_test/bin/redis-server"; +#else + if (system("which " REDIS_SERVER_BIN) != 0) { + puts("Fail to find " REDIS_SERVER_BIN ", following tests will be skipped"); + return; + } +#endif + atexit(RemoveRedisServer); + + g_redis_pid = fork(); + if (g_redis_pid < 0) { + puts("Fail to fork"); + exit(1); + } else if (g_redis_pid == 0) { + puts("[Starting redis-server]"); + char* const argv[] = { (char*)REDIS_SERVER_BIN, + (char*)"--port", (char*)REDIS_SERVER_PORT, + NULL }; + unlink("dump.rdb"); + if (execvp(REDIS_SERVER_BIN, argv) < 0) { + puts("Fail to run " REDIS_SERVER_BIN); + exit(1); + } + } + // Wait for redis to start. + usleep(50000); +} + +class ScopedRedisMaxReplyDepth { +public: + explicit ScopedRedisMaxReplyDepth(int32_t depth) + : _old_depth(brpc::FLAGS_redis_max_reply_depth) { + brpc::FLAGS_redis_max_reply_depth = depth; + } + ~ScopedRedisMaxReplyDepth() { + brpc::FLAGS_redis_max_reply_depth = _old_depth; + } + +private: + int32_t _old_depth; +}; + +class RedisTest : public testing::Test { +protected: + RedisTest() {} + void SetUp() { + pthread_once(&download_redis_server_once, RunRedisServer); + } + void TearDown() {} +}; + +void AssertReplyEqual(const brpc::RedisReply& reply1, + const brpc::RedisReply& reply2) { + if (&reply1 == &reply2) { + return; + } + CHECK_EQ(reply1.type(), reply2.type()); + switch (reply1.type()) { + case brpc::REDIS_REPLY_ARRAY: + ASSERT_EQ(reply1.size(), reply2.size()); + for (size_t j = 0; j < reply1.size(); ++j) { + ASSERT_NE(&reply1[j], &reply2[j]); // from different arena + AssertReplyEqual(reply1[j], reply2[j]); + } + break; + case brpc::REDIS_REPLY_INTEGER: + ASSERT_EQ(reply1.integer(), reply2.integer()); + break; + case brpc::REDIS_REPLY_NIL: + break; + case brpc::REDIS_REPLY_STRING: + // fall through + case brpc::REDIS_REPLY_STATUS: + ASSERT_NE(reply1.c_str(), reply2.c_str()); // from different arena + ASSERT_EQ(reply1.data(), reply2.data()); + break; + case brpc::REDIS_REPLY_ERROR: + ASSERT_NE(reply1.error_message(), reply2.error_message()); // from different arena + ASSERT_STREQ(reply1.error_message(), reply2.error_message()); + break; + } +} + +void AssertResponseEqual(const brpc::RedisResponse& r1, + const brpc::RedisResponse& r2, + int repeated_times = 1) { + if (&r1 == &r2) { + ASSERT_EQ(repeated_times, 1); + return; + } + ASSERT_EQ(r2.reply_size()* repeated_times, r1.reply_size()); + for (int j = 0; j < repeated_times; ++j) { + for (int i = 0; i < r2.reply_size(); ++i) { + ASSERT_NE(&r2.reply(i), &r1.reply(j * r2.reply_size() + i)); + AssertReplyEqual(r2.reply(i), r1.reply(j * r2.reply_size() + i)); + } + } +} + +TEST_F(RedisTest, sanity) { + if (g_redis_pid < 0) { + puts("Skipped due to absence of redis-server"); + return; + } + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_REDIS; + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("0.0.0.0:" REDIS_SERVER_PORT, &options)); + brpc::RedisRequest request; + brpc::RedisResponse response; + brpc::Controller cntl; + + ASSERT_TRUE(request.AddCommand("get hello")); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, response.reply_size()); + ASSERT_EQ(brpc::REDIS_REPLY_NIL, response.reply(0).type()) + << response; + + cntl.Reset(); + request.Clear(); + response.Clear(); + request.AddCommand("set hello world"); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, response.reply_size()); + ASSERT_EQ(brpc::REDIS_REPLY_STATUS, response.reply(0).type()); + ASSERT_EQ("OK", response.reply(0).data()); + + cntl.Reset(); + request.Clear(); + response.Clear(); + ASSERT_TRUE(request.AddCommand("get hello")); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()); + ASSERT_EQ(1, response.reply_size()); + ASSERT_EQ(brpc::REDIS_REPLY_STRING, response.reply(0).type()); + ASSERT_EQ("world", response.reply(0).data()); + + cntl.Reset(); + request.Clear(); + response.Clear(); + request.AddCommand("set hello world2"); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, response.reply_size()); + ASSERT_EQ(brpc::REDIS_REPLY_STATUS, response.reply(0).type()); + ASSERT_EQ("OK", response.reply(0).data()); + + cntl.Reset(); + request.Clear(); + response.Clear(); + ASSERT_TRUE(request.AddCommand("get hello")); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()); + ASSERT_EQ(1, response.reply_size()); + ASSERT_EQ(brpc::REDIS_REPLY_STRING, response.reply(0).type()); + ASSERT_EQ("world2", response.reply(0).data()); + + cntl.Reset(); + request.Clear(); + response.Clear(); + ASSERT_TRUE(request.AddCommand("del hello")); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()); + ASSERT_EQ(brpc::REDIS_REPLY_INTEGER, response.reply(0).type()); + ASSERT_EQ(1, response.reply(0).integer()); + + cntl.Reset(); + request.Clear(); + response.Clear(); + ASSERT_TRUE(request.AddCommand("get %s", "hello")); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, response.reply_size()); + ASSERT_EQ(brpc::REDIS_REPLY_NIL, response.reply(0).type()); +} + +TEST_F(RedisTest, keys_with_spaces) { + if (g_redis_pid < 0) { + puts("Skipped due to absence of redis-server"); + return; + } + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_REDIS; + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("0.0.0.0:" REDIS_SERVER_PORT, &options)); + brpc::RedisRequest request; + brpc::RedisResponse response; + brpc::Controller cntl; + + cntl.Reset(); + request.Clear(); + response.Clear(); + ASSERT_TRUE(request.AddCommand("set %s 'he1 he1 da1'", "hello world")); + ASSERT_TRUE(request.AddCommand("set 'hello2 world2' 'he2 he2 da2'")); + ASSERT_TRUE(request.AddCommand("set \"hello3 world3\" \"he3 he3 da3\"")); + ASSERT_TRUE(request.AddCommand("get \"hello world\"")); + ASSERT_TRUE(request.AddCommand("get 'hello world'")); + ASSERT_TRUE(request.AddCommand("get 'hello2 world2'")); + ASSERT_TRUE(request.AddCommand("get 'hello3 world3'")); + + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(7, response.reply_size()); + ASSERT_EQ(brpc::REDIS_REPLY_STATUS, response.reply(0).type()); + ASSERT_EQ("OK", response.reply(0).data()); + ASSERT_EQ(brpc::REDIS_REPLY_STATUS, response.reply(1).type()); + ASSERT_EQ("OK", response.reply(1).data()); + ASSERT_EQ(brpc::REDIS_REPLY_STATUS, response.reply(2).type()); + ASSERT_EQ("OK", response.reply(2).data()); + ASSERT_EQ(brpc::REDIS_REPLY_STRING, response.reply(3).type()); + ASSERT_EQ("he1 he1 da1", response.reply(3).data()); + ASSERT_EQ(brpc::REDIS_REPLY_STRING, response.reply(4).type()); + ASSERT_EQ("he1 he1 da1", response.reply(4).data()); + ASSERT_EQ(brpc::REDIS_REPLY_STRING, response.reply(5).type()); + ASSERT_EQ("he2 he2 da2", response.reply(5).data()); + ASSERT_EQ(brpc::REDIS_REPLY_STRING, response.reply(6).type()); + ASSERT_EQ("he3 he3 da3", response.reply(6).data()); + + brpc::RedisResponse response2 = response; + AssertResponseEqual(response2, response); + response2.MergeFrom(response); + AssertResponseEqual(response2, response, 2); +} + +TEST_F(RedisTest, incr_and_decr) { + if (g_redis_pid < 0) { + puts("Skipped due to absence of redis-server"); + return; + } + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_REDIS; + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("0.0.0.0:" REDIS_SERVER_PORT, &options)); + brpc::RedisRequest request; + brpc::RedisResponse response; + brpc::Controller cntl; + + request.AddCommand("incr counter1"); + request.AddCommand("decr counter1"); + request.AddCommand("incrby counter1 %d", 10); + request.AddCommand("decrby counter1 %d", 20); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(4, response.reply_size()); + ASSERT_EQ(brpc::REDIS_REPLY_INTEGER, response.reply(0).type()); + ASSERT_EQ(1, response.reply(0).integer()); + ASSERT_EQ(brpc::REDIS_REPLY_INTEGER, response.reply(1).type()); + ASSERT_EQ(0, response.reply(1).integer()); + ASSERT_EQ(brpc::REDIS_REPLY_INTEGER, response.reply(2).type()); + ASSERT_EQ(10, response.reply(2).integer()); + ASSERT_EQ(brpc::REDIS_REPLY_INTEGER, response.reply(3).type()); + ASSERT_EQ(-10, response.reply(3).integer()); + + brpc::RedisResponse response2 = response; + AssertResponseEqual(response2, response); + response2.MergeFrom(response); + AssertResponseEqual(response2, response, 2); +} + +TEST_F(RedisTest, by_components) { + if (g_redis_pid < 0) { + puts("Skipped due to absence of redis-server"); + return; + } + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_REDIS; + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("0.0.0.0:" REDIS_SERVER_PORT, &options)); + brpc::RedisRequest request; + brpc::RedisResponse response; + brpc::Controller cntl; + + butil::StringPiece comp1[] = { "incr", "counter2" }; + butil::StringPiece comp2[] = { "decr", "counter2" }; + butil::StringPiece comp3[] = { "incrby", "counter2", "10" }; + butil::StringPiece comp4[] = { "decrby", "counter2", "20" }; + + request.AddCommandByComponents(comp1, arraysize(comp1)); + request.AddCommandByComponents(comp2, arraysize(comp2)); + request.AddCommandByComponents(comp3, arraysize(comp3)); + request.AddCommandByComponents(comp4, arraysize(comp4)); + + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(4, response.reply_size()); + ASSERT_EQ(brpc::REDIS_REPLY_INTEGER, response.reply(0).type()); + ASSERT_EQ(1, response.reply(0).integer()); + ASSERT_EQ(brpc::REDIS_REPLY_INTEGER, response.reply(1).type()); + ASSERT_EQ(0, response.reply(1).integer()); + ASSERT_EQ(brpc::REDIS_REPLY_INTEGER, response.reply(2).type()); + ASSERT_EQ(10, response.reply(2).integer()); + ASSERT_EQ(brpc::REDIS_REPLY_INTEGER, response.reply(3).type()); + ASSERT_EQ(-10, response.reply(3).integer()); + + brpc::RedisResponse response2 = response; + AssertResponseEqual(response2, response); + response2.MergeFrom(response); + AssertResponseEqual(response2, response, 2); +} + +static std::string GeneratePassword() { + std::string result; + result.reserve(12); + for (size_t i = 0; i < result.capacity(); ++i) { + result.push_back(butil::fast_rand_in('a', 'z')); + } + return result; +} + +TEST_F(RedisTest, auth) { + if (g_redis_pid < 0) { + puts("Skipped due to absence of redis-server"); + return; + } + // generate a random password + const std::string passwd1 = GeneratePassword(); + const std::string passwd2 = GeneratePassword(); + LOG(INFO) << "Generated passwd1=" << passwd1 << " passwd2=" << passwd2; + + // config auth + { + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_REDIS; + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("0.0.0.0:" REDIS_SERVER_PORT, &options)); + brpc::RedisRequest request; + brpc::RedisResponse response; + brpc::Controller cntl; + + request.AddCommand("set mykey %s", passwd1.c_str()); + request.AddCommand("config set requirepass %s", passwd1.c_str()); + request.AddCommand("auth %s", passwd1.c_str()); + request.AddCommand("get mykey"); + + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(4, response.reply_size()); + ASSERT_EQ(brpc::REDIS_REPLY_STATUS, response.reply(0).type()); + ASSERT_STREQ("OK", response.reply(0).c_str()); + ASSERT_EQ(brpc::REDIS_REPLY_STATUS, response.reply(1).type()); + ASSERT_STREQ("OK", response.reply(1).c_str()); + ASSERT_EQ(brpc::REDIS_REPLY_STATUS, response.reply(2).type()); + ASSERT_STREQ("OK", response.reply(2).c_str()); + ASSERT_EQ(brpc::REDIS_REPLY_STRING, response.reply(3).type()); + ASSERT_STREQ(passwd1.c_str(), response.reply(3).c_str()); + } + + // Auth failed + { + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_REDIS; + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("0.0.0.0:" REDIS_SERVER_PORT, &options)); + brpc::RedisRequest request; + brpc::RedisResponse response; + brpc::Controller cntl; + + request.AddCommand("get mykey"); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, response.reply_size()); + ASSERT_EQ(brpc::REDIS_REPLY_ERROR, response.reply(0).type()); + } + + // Auth with RedisAuthenticator and change to passwd2 (setting to empty + // pass does not work on redis 6.0.6) + { + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_REDIS; + brpc::Channel channel; + brpc::policy::RedisAuthenticator* auth = + new brpc::policy::RedisAuthenticator(passwd1.c_str()); + options.auth = auth; + ASSERT_EQ(0, channel.Init("0.0.0.0:" REDIS_SERVER_PORT, &options)); + brpc::RedisRequest request; + brpc::RedisResponse response; + brpc::Controller cntl; + + request.AddCommand("get mykey"); + request.AddCommand("config set requirepass %s", passwd2.c_str()); + + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(2, response.reply_size()); + ASSERT_EQ(brpc::REDIS_REPLY_STRING, response.reply(0).type()); + ASSERT_STREQ(passwd1.c_str(), response.reply(0).c_str()); + ASSERT_EQ(brpc::REDIS_REPLY_STATUS, response.reply(1).type()); + ASSERT_STREQ("OK", response.reply(1).c_str()); + } + + // Auth with passwd2 + { + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_REDIS; + brpc::policy::RedisAuthenticator* auth = + new brpc::policy::RedisAuthenticator(passwd2.c_str()); + options.auth = auth; + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("0.0.0.0:" REDIS_SERVER_PORT, &options)); + brpc::RedisRequest request; + brpc::RedisResponse response; + brpc::Controller cntl; + + request.AddCommand("get mykey"); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, response.reply_size()); + ASSERT_EQ(brpc::REDIS_REPLY_STRING, response.reply(0).type()) << response.reply(0); + ASSERT_STREQ(passwd1.c_str(), response.reply(0).c_str()); + } +} + +TEST_F(RedisTest, cmd_format) { + if (g_redis_pid < 0) { + puts("Skipped due to absence of redis-server"); + return; + } + brpc::RedisRequest request; + // set empty string + request.AddCommand("set a ''"); + ASSERT_STREQ("*3\r\n$3\r\nset\r\n$1\r\na\r\n$0\r\n\r\n", + request._buf.to_string().c_str()); + request.Clear(); + + request.AddCommand("mset b '' c ''"); + ASSERT_STREQ("*5\r\n$4\r\nmset\r\n$1\r\nb\r\n$0\r\n\r\n$1\r\nc\r\n$0\r\n\r\n", + request._buf.to_string().c_str()); + request.Clear(); + // set non-empty string + request.AddCommand("set a 123"); + ASSERT_STREQ("*3\r\n$3\r\nset\r\n$1\r\na\r\n$3\r\n123\r\n", + request._buf.to_string().c_str()); + request.Clear(); + + request.AddCommand("mset b '' c ccc"); + ASSERT_STREQ("*5\r\n$4\r\nmset\r\n$1\r\nb\r\n$0\r\n\r\n$1\r\nc\r\n$3\r\nccc\r\n", + request._buf.to_string().c_str()); + request.Clear(); + + request.AddCommand("get ''key value"); // == get key value + ASSERT_STREQ("*4\r\n$3\r\nget\r\n$0\r\n\r\n$3\r\nkey\r\n$5\r\nvalue\r\n", request._buf.to_string().c_str()); + request.Clear(); + + request.AddCommand("get key'' value"); // == get key value + ASSERT_STREQ("*4\r\n$3\r\nget\r\n$3\r\nkey\r\n$0\r\n\r\n$5\r\nvalue\r\n", request._buf.to_string().c_str()); + request.Clear(); + + request.AddCommand("get 'ext'key value "); // == get ext key value + ASSERT_STREQ("*4\r\n$3\r\nget\r\n$3\r\next\r\n$3\r\nkey\r\n$5\r\nvalue\r\n", request._buf.to_string().c_str()); + request.Clear(); + + request.AddCommand(" get key'ext' value "); // == get key ext value + ASSERT_STREQ("*4\r\n$3\r\nget\r\n$3\r\nkey\r\n$3\r\next\r\n$5\r\nvalue\r\n", request._buf.to_string().c_str()); + request.Clear(); +} + +TEST_F(RedisTest, quote_and_escape) { + if (g_redis_pid < 0) { + puts("Skipped due to absence of redis-server"); + return; + } + brpc::RedisRequest request; + request.AddCommand("set a 'foo bar'"); + ASSERT_STREQ("*3\r\n$3\r\nset\r\n$1\r\na\r\n$7\r\nfoo bar\r\n", + request._buf.to_string().c_str()); + request.Clear(); + + request.AddCommand("set a 'foo \\'bar'"); + ASSERT_STREQ("*3\r\n$3\r\nset\r\n$1\r\na\r\n$8\r\nfoo 'bar\r\n", + request._buf.to_string().c_str()); + request.Clear(); + + request.AddCommand("set a 'foo \"bar'"); + ASSERT_STREQ("*3\r\n$3\r\nset\r\n$1\r\na\r\n$8\r\nfoo \"bar\r\n", + request._buf.to_string().c_str()); + request.Clear(); + + request.AddCommand("set a 'foo \\\"bar'"); + ASSERT_STREQ("*3\r\n$3\r\nset\r\n$1\r\na\r\n$9\r\nfoo \\\"bar\r\n", + request._buf.to_string().c_str()); + request.Clear(); + + request.AddCommand("set a \"foo 'bar\""); + ASSERT_STREQ("*3\r\n$3\r\nset\r\n$1\r\na\r\n$8\r\nfoo 'bar\r\n", + request._buf.to_string().c_str()); + request.Clear(); + + request.AddCommand("set a \"foo \\'bar\""); + ASSERT_STREQ("*3\r\n$3\r\nset\r\n$1\r\na\r\n$9\r\nfoo \\'bar\r\n", + request._buf.to_string().c_str()); + request.Clear(); + + request.AddCommand("set a \"foo \\\"bar\""); + ASSERT_STREQ("*3\r\n$3\r\nset\r\n$1\r\na\r\n$8\r\nfoo \"bar\r\n", + request._buf.to_string().c_str()); + request.Clear(); +} + +std::string GetCompleteCommand(const std::vector& commands) { + std::string res; + for (int i = 0; i < (int)commands.size(); ++i) { + if (i != 0) { + res.push_back(' '); + } + res.append(commands[i].data(), commands[i].size()); + } + return res; +} + + +TEST_F(RedisTest, command_parser) { + brpc::RedisCommandParser parser; + butil::IOBuf buf; + std::vector command_out; + butil::Arena arena; + { + // parse from whole command + std::string command = "set abc edc"; + ASSERT_TRUE(brpc::RedisCommandNoFormat(&buf, command.c_str()).ok()); + ASSERT_EQ(brpc::PARSE_OK, parser.Consume(buf, &command_out, &arena)); + ASSERT_TRUE(buf.empty()); + ASSERT_EQ(command, GetCompleteCommand(command_out)); + } + { + // simulate parsing from network following RESP + int t = 100; + std::string raw_string("*3\r\n$3\r\nset\r\n$3\r\nabc\r\n$3\r\ndef\r\n"); + int size = raw_string.size(); + while (t--) { + for (int i = 0; i < size; ++i) { + buf.push_back(raw_string[i]); + if (i == size - 1) { + ASSERT_EQ(brpc::PARSE_OK, parser.Consume(buf, &command_out, &arena)); + } else { + if (butil::fast_rand_less_than(2) == 0) { + ASSERT_EQ(brpc::PARSE_ERROR_NOT_ENOUGH_DATA, + parser.Consume(buf, &command_out, &arena)); + } + } + } + ASSERT_TRUE(buf.empty()); + ASSERT_EQ(GetCompleteCommand(command_out), "set abc def"); + } + } + { + // simulate parsing from network under inline protocol + int t = 100; + std::string raw_string("set abc def\r\n"); + int size = raw_string.size(); + while (t--) { + for (int i = 0; i < size; ++i) { + buf.push_back(raw_string[i]); + if (i == size - 1) { + ASSERT_EQ(brpc::PARSE_OK, parser.Consume(buf, &command_out, &arena)); + } else { + if (butil::fast_rand_less_than(2) == 0) { + ASSERT_EQ(brpc::PARSE_ERROR_NOT_ENOUGH_DATA, + parser.Consume(buf, &command_out, &arena)); + } + } + } + ASSERT_TRUE(buf.empty()); + ASSERT_EQ(GetCompleteCommand(command_out), "set abc def"); + } + } + { + // there is a non-string message in command and parse should fail + buf.append("*3\r\n$3"); + ASSERT_EQ(brpc::PARSE_ERROR_NOT_ENOUGH_DATA, parser.Consume(buf, &command_out, &arena)); + ASSERT_EQ((int)buf.size(), 2); // left "$3" + buf.append("\r\nset\r\n:123\r\n$3\r\ndef\r\n"); + ASSERT_EQ(brpc::PARSE_ERROR_ABSOLUTELY_WRONG, parser.Consume(buf, &command_out, &arena)); + parser.Reset(); + } + { + // not array + buf.append(":123456\r\n"); + ASSERT_EQ(brpc::PARSE_ERROR_TRY_OTHERS, parser.Consume(buf, &command_out, &arena)); + parser.Reset(); + } + { + // not array + buf.append("+Error\r\n"); + ASSERT_EQ(brpc::PARSE_ERROR_TRY_OTHERS, parser.Consume(buf, &command_out, &arena)); + parser.Reset(); + } + { + // not array + buf.append("+OK\r\n"); + ASSERT_EQ(brpc::PARSE_ERROR_TRY_OTHERS, parser.Consume(buf, &command_out, &arena)); + parser.Reset(); + } + { + // not array + buf.append("$5\r\nhello\r\n"); + ASSERT_EQ(brpc::PARSE_ERROR_TRY_OTHERS, parser.Consume(buf, &command_out, &arena)); + parser.Reset(); + } +} + +TEST(RedisCommandParserTest, reject_empty_resp_array_command) { + brpc::RedisCommandParser parser; + butil::IOBuf buf; + std::vector command_out; + butil::Arena arena; + + // Empty RESP arrays are not valid commands and must not leave the parser in + // a state that accepts following bulk strings. + buf.append("*0\r\n$1\r\nx\r\n"); + ASSERT_EQ(brpc::PARSE_ERROR_ABSOLUTELY_WRONG, + parser.Consume(buf, &command_out, &arena)); +} + +// Regression test for issue #3109: the inline redis protocol must not consume +// the HTTP/2 connection preface as a command, otherwise protocol auto-detection +// never falls through to HTTP/2 and gRPC clients fail with "connection closed +// before server preface received". +TEST_F(RedisTest, inline_does_not_eat_h2_preface) { + brpc::RedisCommandParser parser; + butil::IOBuf buf; + std::vector command_out; + butil::Arena arena; + { + // Full HTTP/2 client connection preface: must defer to other protocols. + buf.append("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"); + ASSERT_EQ(brpc::PARSE_ERROR_TRY_OTHERS, + parser.Consume(buf, &command_out, &arena)); + buf.clear(); + parser.Reset(); + } + { + // A not-yet-complete prefix of the preface must also defer, leaving the + // bytes intact for the HTTP/2 parser instead of being misparsed. + buf.append("PRI * HT"); + ASSERT_EQ(brpc::PARSE_ERROR_TRY_OTHERS, + parser.Consume(buf, &command_out, &arena)); + buf.clear(); + parser.Reset(); + } + { + // A genuine inline command sharing the leading 'P' must still parse. + buf.append("PING\r\n"); + ASSERT_EQ(brpc::PARSE_OK, parser.Consume(buf, &command_out, &arena)); + ASSERT_EQ("ping", GetCompleteCommand(command_out)); + buf.clear(); + parser.Reset(); + } +} + +TEST_F(RedisTest, redis_reply_codec) { + butil::Arena arena; + // status + { + brpc::RedisReply r(&arena); + butil::IOBuf buf; + butil::IOBufAppender appender; + r.SetStatus("OK"); + ASSERT_TRUE(r.SerializeTo(&appender)); + appender.move_to(buf); + ASSERT_STREQ(buf.to_string().c_str(), "+OK\r\n"); + ASSERT_STREQ(r.c_str(), "OK"); + + brpc::RedisReply r2(&arena); + brpc::ParseError err = r2.ConsumePartialIOBuf(buf); + ASSERT_EQ(err, brpc::PARSE_OK); + ASSERT_TRUE(r2.is_string()); + ASSERT_STREQ("OK", r2.c_str()); + } + // error + { + brpc::RedisReply r(&arena); + butil::IOBuf buf; + butil::IOBufAppender appender; + r.SetError("not exist \'key\'"); + ASSERT_TRUE(r.SerializeTo(&appender)); + appender.move_to(buf); + ASSERT_STREQ(buf.to_string().c_str(), "-not exist \'key\'\r\n"); + + brpc::RedisReply r2(&arena); + brpc::ParseError err = r2.ConsumePartialIOBuf(buf); + ASSERT_EQ(err, brpc::PARSE_OK); + ASSERT_TRUE(r2.is_error()); + ASSERT_STREQ("not exist \'key\'", r2.error_message()); + } + // string + { + brpc::RedisReply r(&arena); + butil::IOBuf buf; + butil::IOBufAppender appender; + r.SetNullString(); + ASSERT_TRUE(r.SerializeTo(&appender)); + appender.move_to(buf); + ASSERT_STREQ(buf.to_string().c_str(), "$-1\r\n"); + + brpc::RedisReply r2(&arena); + brpc::ParseError err = r2.ConsumePartialIOBuf(buf); + ASSERT_EQ(err, brpc::PARSE_OK); + ASSERT_TRUE(r2.is_nil()); + + r.SetString("abcde'hello world"); + ASSERT_TRUE(r.SerializeTo(&appender)); + appender.move_to(buf); + ASSERT_STREQ(buf.to_string().c_str(), "$17\r\nabcde'hello world\r\n"); + ASSERT_STREQ("abcde'hello world", r.c_str()); + + r.FormatString("int:%d str:%s fp:%.2f", 123, "foobar", 3.21); + ASSERT_TRUE(r.SerializeTo(&appender)); + appender.move_to(buf); + ASSERT_STREQ(buf.to_string().c_str(), "$26\r\nint:123 str:foobar fp:3.21\r\n"); + ASSERT_STREQ("int:123 str:foobar fp:3.21", r.c_str()); + + r.FormatString("verylongstring verylongstring verylongstring verylongstring int:%d str:%s fp:%.2f", 123, "foobar", 3.21); + ASSERT_TRUE(r.SerializeTo(&appender)); + appender.move_to(buf); + ASSERT_STREQ(buf.to_string().c_str(), "$86\r\nverylongstring verylongstring verylongstring verylongstring int:123 str:foobar fp:3.21\r\n"); + ASSERT_STREQ("verylongstring verylongstring verylongstring verylongstring int:123 str:foobar fp:3.21", r.c_str()); + + brpc::RedisReply r3(&arena); + err = r3.ConsumePartialIOBuf(buf); + ASSERT_EQ(err, brpc::PARSE_OK); + ASSERT_TRUE(r3.is_string()); + ASSERT_STREQ(r.c_str(), r3.c_str()); + } + // integer + { + brpc::RedisReply r(&arena); + butil::IOBuf buf; + butil::IOBufAppender appender; + int t = 2; + int input[] = { -1, 1234567 }; + const char* output[] = { ":-1\r\n", ":1234567\r\n" }; + for (int i = 0; i < t; ++i) { + r.SetInteger(input[i]); + ASSERT_TRUE(r.SerializeTo(&appender)); + appender.move_to(buf); + ASSERT_STREQ(buf.to_string().c_str(), output[i]); + + brpc::RedisReply r2(&arena); + brpc::ParseError err = r2.ConsumePartialIOBuf(buf); + ASSERT_EQ(err, brpc::PARSE_OK); + ASSERT_TRUE(r2.is_integer()); + ASSERT_EQ(r2.integer(), input[i]); + } + } + // array + { + brpc::RedisReply r(&arena); + butil::IOBuf buf; + butil::IOBufAppender appender; + r.SetArray(3); + brpc::RedisReply& sub_reply = r[0]; + sub_reply.SetArray(2); + sub_reply[0].SetString("hello, it's me"); + sub_reply[1].SetInteger(422); + r[1].SetString("To go over everything"); + r[2].SetInteger(1); + ASSERT_TRUE(r[3].is_nil()); + ASSERT_TRUE(r.SerializeTo(&appender)); + appender.move_to(buf); + ASSERT_STREQ(buf.to_string().c_str(), + "*3\r\n*2\r\n$14\r\nhello, it's me\r\n:422\r\n$21\r\n" + "To go over everything\r\n:1\r\n"); + + brpc::RedisReply r2(&arena); + ASSERT_EQ(r2.ConsumePartialIOBuf(buf), brpc::PARSE_OK); + ASSERT_TRUE(r2.is_array()); + ASSERT_EQ(3ul, r2.size()); + ASSERT_TRUE(r2[0].is_array()); + ASSERT_EQ(2ul, r2[0].size()); + ASSERT_TRUE(r2[0][0].is_string()); + ASSERT_STREQ(r2[0][0].c_str(), "hello, it's me"); + ASSERT_TRUE(r2[0][1].is_integer()); + ASSERT_EQ(r2[0][1].integer(), 422); + ASSERT_TRUE(r2[1].is_string()); + ASSERT_STREQ(r2[1].c_str(), "To go over everything"); + ASSERT_TRUE(r2[2].is_integer()); + ASSERT_EQ(1, r2[2].integer()); + + // null array + r.SetNullArray(); + ASSERT_TRUE(r.SerializeTo(&appender)); + appender.move_to(buf); + ASSERT_STREQ(buf.to_string().c_str(), "*-1\r\n"); + ASSERT_EQ(r.ConsumePartialIOBuf(buf), brpc::PARSE_OK); + ASSERT_TRUE(r.is_nil()); + } + + // CopyFromDifferentArena + { + brpc::RedisReply r(&arena); + r.SetArray(1); + brpc::RedisReply& sub_reply = r[0]; + sub_reply.SetArray(2); + sub_reply[0].SetString("hello, it's me"); + sub_reply[1].SetInteger(422); + + brpc::RedisReply r2(&arena); + r2.CopyFromDifferentArena(r); + ASSERT_TRUE(r2.is_array()); + ASSERT_EQ((int)r2[0].size(), 2); + ASSERT_STREQ(r2[0][0].c_str(), sub_reply[0].c_str()); + ASSERT_EQ(r2[0][1].integer(), sub_reply[1].integer()); + } + // SetXXX can be called multiple times. + { + brpc::RedisReply r(&arena); + r.SetStatus("OK"); + ASSERT_TRUE(r.is_string()); + r.SetNullString(); + ASSERT_TRUE(r.is_nil()); + r.SetArray(2); + ASSERT_TRUE(r.is_array()); + r.SetString("OK"); + ASSERT_TRUE(r.is_string()); + r.SetError("OK"); + ASSERT_TRUE(r.is_error()); + r.SetInteger(42); + ASSERT_TRUE(r.is_integer()); + } +} + +TEST_F(RedisTest, redis_reply_rejects_deep_nested_arrays) { + ScopedRedisMaxReplyDepth scoped_depth(4); + + butil::IOBuf buf; + for (int i = 0; i <= brpc::FLAGS_redis_max_reply_depth; ++i) { + buf.append("*1\r\n"); + } + buf.append(":0\r\n"); + + butil::Arena arena; + brpc::RedisReply reply(&arena); + EXPECT_EQ(brpc::PARSE_ERROR_ABSOLUTELY_WRONG, reply.ConsumePartialIOBuf(buf)); + + buf.clear(); + for (int i = 0; i < brpc::FLAGS_redis_max_reply_depth; ++i) { + buf.append("*1\r\n"); + } + buf.append(":0\r\n"); + + brpc::RedisReply valid_reply(&arena); + EXPECT_EQ(brpc::PARSE_OK, valid_reply.ConsumePartialIOBuf(buf)); + EXPECT_TRUE(valid_reply.is_array()); +} + +butil::Mutex s_mutex; +std::unordered_map m; +std::unordered_map int_map; + + +class RedisServiceImpl : public brpc::RedisService { +public: + RedisServiceImpl(std::string password) + : _batch_count(0) + , _password(std::move(password)) {} + + brpc::RedisCommandHandlerResult OnBatched(const std::vector& args, + brpc::RedisReply* output, bool flush_batched) { + if (_batched_command.empty() && flush_batched) { + if (args[0] == "set") { + DoSet(args[1].as_string(), args[2].as_string(), output); + } else if (args[0] == "get") { + DoGet(args[1].as_string(), output); + } + return brpc::REDIS_CMD_HANDLED; + } + std::vector comm; + for (int i = 0; i < (int)args.size(); ++i) { + comm.push_back(args[i].as_string()); + } + _batched_command.push_back(comm); + if (flush_batched) { + output->SetArray(_batched_command.size()); + for (int i = 0; i < (int)_batched_command.size(); ++i) { + if (_batched_command[i][0] == "set") { + DoSet(_batched_command[i][1], _batched_command[i][2], &(*output)[i]); + } else if (_batched_command[i][0] == "get") { + DoGet(_batched_command[i][1], &(*output)[i]); + } + } + _batch_count++; + _batched_command.clear(); + return brpc::REDIS_CMD_HANDLED; + } else { + return brpc::REDIS_CMD_BATCHED; + } + } + + void DoSet(const std::string& key, const std::string& value, brpc::RedisReply* output) { + m[key] = value; + output->SetStatus("OK"); + } + + void DoGet(const std::string& key, brpc::RedisReply* output) { + auto it = m.find(key); + if (it != m.end()) { + output->SetString(it->second); + } else { + output->SetNullString(); + } + } + + std::vector > _batched_command; + int _batch_count; + std::string _password; +}; + + +class AuthSession : public brpc::Destroyable { +public: + explicit AuthSession(std::string password) + : _password(std::move(password)) {} + + void Destroy() override { + delete this; + } + + const std::string _password; +}; + +class AuthCommandHandler : public brpc::RedisCommandHandler { +public: + AuthCommandHandler(RedisServiceImpl* rs) + : _rs(rs) {} + + brpc::RedisCommandHandlerResult Run(brpc::RedisConnContext* ctx, + const std::vector& args, + brpc::RedisReply* output, + bool flush_batched) { + if (args.size() < 1) { + output->SetError("ERR wrong number of arguments for 'AUTH' command"); + return brpc::REDIS_CMD_HANDLED; + } + const std::string password(args[1].data(), args[1].size()); + if (_rs->_password != password) { + output->SetError("ERR invalid username/password"); + return brpc::REDIS_CMD_HANDLED; + } + auto auth_session = new AuthSession(password); + ctx->reset_session(auth_session); + output->SetStatus("OK"); + return brpc::REDIS_CMD_HANDLED; + } + +private: + RedisServiceImpl* _rs; +}; + +class SetCommandHandler : public brpc::RedisCommandHandler { +public: + SetCommandHandler(RedisServiceImpl* rs, bool batch_process = false) + : _rs(rs) + , _batch_process(batch_process) {} + + brpc::RedisCommandHandlerResult Run(brpc::RedisConnContext* ctx, + const std::vector& args, + brpc::RedisReply* output, + bool flush_batched) { + if (!ctx->session) { + output->SetError("ERR no auth"); + return brpc::REDIS_CMD_HANDLED; + } + AuthSession* session = static_cast(ctx->session); + if (!session || (session->_password != _rs->_password)) { + output->SetError("ERR no auth"); + return brpc::REDIS_CMD_HANDLED; + } + if (args.size() < 3) { + output->SetError("ERR wrong number of arguments for 'set' command"); + return brpc::REDIS_CMD_HANDLED; + } + if (_batch_process) { + return _rs->OnBatched(args, output, flush_batched); + } else { + DoSet(args[1].as_string(), args[2].as_string(), output); + return brpc::REDIS_CMD_HANDLED; + } + } + + void DoSet(const std::string& key, const std::string& value, brpc::RedisReply* output) { + m[key] = value; + output->SetStatus("OK"); + } + +private: + RedisServiceImpl* _rs; + bool _batch_process; +}; + + +class GetCommandHandler : public brpc::RedisCommandHandler { +public: + GetCommandHandler(RedisServiceImpl* rs, bool batch_process = false) + : _rs(rs) + , _batch_process(batch_process) {} + + brpc::RedisCommandHandlerResult Run(brpc::RedisConnContext* ctx, + const std::vector& args, + brpc::RedisReply* output, + bool flush_batched) { + if (!ctx->session) { + output->SetError("ERR no auth"); + return brpc::REDIS_CMD_HANDLED; + } + AuthSession* session = static_cast(ctx->session); + if (session->_password != _rs->_password) { + output->SetError("ERR no auth"); + return brpc::REDIS_CMD_HANDLED; + } + if (args.size() < 2) { + output->SetError("ERR wrong number of arguments for 'get' command"); + return brpc::REDIS_CMD_HANDLED; + } + if (_batch_process) { + return _rs->OnBatched(args, output, flush_batched); + } else { + DoGet(args[1].as_string(), output); + return brpc::REDIS_CMD_HANDLED; + } + } + + void DoGet(const std::string& key, brpc::RedisReply* output) { + auto it = m.find(key); + if (it != m.end()) { + output->SetString(it->second); + } else { + output->SetNullString(); + } + } + +private: + RedisServiceImpl* _rs; + bool _batch_process; +}; + +class IncrCommandHandler : public brpc::RedisCommandHandler { +public: + IncrCommandHandler(RedisServiceImpl* rs) + : _rs(rs) {} + + brpc::RedisCommandHandlerResult Run(brpc::RedisConnContext* ctx, + const std::vector& args, + brpc::RedisReply* output, + bool flush_batched) { + if (!ctx->session) { + output->SetError("ERR no auth"); + return brpc::REDIS_CMD_HANDLED; + } + AuthSession* session = static_cast(ctx->session); + if (session->_password != _rs->_password) { + output->SetError("ERR no auth"); + return brpc::REDIS_CMD_HANDLED; + } + if (args.size() < 2) { + output->SetError("ERR wrong number of arguments for 'incr' command"); + return brpc::REDIS_CMD_HANDLED; + } + int64_t value; + s_mutex.lock(); + value = ++int_map[args[1].as_string()]; + s_mutex.unlock(); + output->SetInteger(value); + return brpc::REDIS_CMD_HANDLED; + } + +private: + RedisServiceImpl* _rs; +}; + +TEST_F(RedisTest, server_sanity) { + std::string password = GeneratePassword(); + std::unique_ptr redis_auth_holder( + new brpc::policy::RedisAuthenticator(password)); + RedisServiceImpl* rsimpl = new RedisServiceImpl(password); + std::unique_ptr gh(new GetCommandHandler(rsimpl)); + std::unique_ptr sh(new SetCommandHandler(rsimpl)); + std::unique_ptr ah(new AuthCommandHandler(rsimpl)); + std::unique_ptr ih(new IncrCommandHandler(rsimpl)); + brpc::Server server; + brpc::ServerOptions server_options; + rsimpl->AddCommandHandler("get", gh.get()); + rsimpl->AddCommandHandler("set", sh.get()); + rsimpl->AddCommandHandler("incr", ih.get()); + rsimpl->AddCommandHandler("auth", ah.get()); + server_options.redis_service = rsimpl; + brpc::PortRange pr(8081, 8900); + ASSERT_EQ(0, server.Start("127.0.0.1", pr, &server_options)); + + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_REDIS; + options.auth = redis_auth_holder.get(); + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("127.0.0.1", server.listen_address().port, &options)); + + brpc::RedisRequest request; + brpc::RedisResponse response; + brpc::Controller cntl; + ASSERT_TRUE(request.AddCommand("get hello")); + ASSERT_TRUE(request.AddCommand("get hello2")); + ASSERT_TRUE(request.AddCommand("set key1 value1")); + ASSERT_TRUE(request.AddCommand("get key1")); + ASSERT_TRUE(request.AddCommand("set key2 value2")); + ASSERT_TRUE(request.AddCommand("get key2")); + ASSERT_TRUE(request.AddCommand("xxxcommand key2")); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(7, response.reply_size()); + ASSERT_EQ(brpc::REDIS_REPLY_NIL, response.reply(0).type()); + ASSERT_EQ(brpc::REDIS_REPLY_NIL, response.reply(1).type()); + ASSERT_EQ(brpc::REDIS_REPLY_STATUS, response.reply(2).type()); + ASSERT_STREQ("OK", response.reply(2).c_str()); + ASSERT_EQ(brpc::REDIS_REPLY_STRING, response.reply(3).type()); + ASSERT_STREQ("value1", response.reply(3).c_str()); + ASSERT_EQ(brpc::REDIS_REPLY_STATUS, response.reply(4).type()); + ASSERT_STREQ("OK", response.reply(4).c_str()); + ASSERT_EQ(brpc::REDIS_REPLY_STRING, response.reply(5).type()); + ASSERT_STREQ("value2", response.reply(5).c_str()); + ASSERT_EQ(brpc::REDIS_REPLY_ERROR, response.reply(6).type()); + ASSERT_TRUE(butil::StringPiece(response.reply(6).error_message()).starts_with("ERR unknown command")); + + cntl.Reset(); + request.Clear(); + response.Clear(); + std::string value3("value3"); + value3.append(1, '\0'); + value3.append(1, 'a'); + std::vector pieces; + pieces.push_back("set"); + pieces.push_back("key3"); + pieces.push_back(value3); + ASSERT_TRUE(request.AddCommandByComponents(&pieces[0], pieces.size())); + ASSERT_TRUE(request.AddCommand("set key4 \"\"")); + ASSERT_TRUE(request.AddCommand("get key3")); + ASSERT_TRUE(request.AddCommand("get key4")); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(4, response.reply_size()); + ASSERT_EQ(brpc::REDIS_REPLY_STATUS, response.reply(0).type()); + ASSERT_STREQ("OK", response.reply(0).c_str()); + ASSERT_EQ(brpc::REDIS_REPLY_STATUS, response.reply(1).type()); + ASSERT_STREQ("OK", response.reply(1).c_str()); + ASSERT_EQ(brpc::REDIS_REPLY_STRING, response.reply(2).type()); + ASSERT_STREQ("value3", response.reply(2).c_str()); + ASSERT_NE("value3", response.reply(2).data()); + ASSERT_EQ(value3, response.reply(2).data()); + ASSERT_EQ(brpc::REDIS_REPLY_STRING, response.reply(3).type()); + ASSERT_EQ("", response.reply(3).data()); +} + +void* incr_thread(void* arg) { + brpc::Channel* c = static_cast(arg); + for (int i = 0; i < 5000; ++i) { + brpc::RedisRequest request; + brpc::RedisResponse response; + brpc::Controller cntl; + EXPECT_TRUE(request.AddCommand("incr count")); + c->CallMethod(NULL, &cntl, &request, &response, NULL); + EXPECT_FALSE(cntl.Failed()) << cntl.ErrorText(); + EXPECT_EQ(1, response.reply_size()); + EXPECT_TRUE(response.reply(0).is_integer()) << response.reply(0); + } + return NULL; +} + +TEST_F(RedisTest, server_concurrency) { + std::string password = GeneratePassword(); + int N = 10; + std::unique_ptr redis_auth_holder( + new brpc::policy::RedisAuthenticator(password)); + RedisServiceImpl* rsimpl = new RedisServiceImpl(password); + std::unique_ptr ah(new AuthCommandHandler(rsimpl)); + std::unique_ptr ih(new IncrCommandHandler(rsimpl)); + brpc::Server server; + brpc::ServerOptions server_options; + rsimpl->AddCommandHandler("incr", ih.get()); + rsimpl->AddCommandHandler("auth", ah.get()); + server_options.redis_service = rsimpl; + brpc::PortRange pr(8081, 8900); + ASSERT_EQ(0, server.Start("0.0.0.0", pr, &server_options)); + + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_REDIS; + options.connection_type = "pooled"; + options.auth = redis_auth_holder.get(); + std::vector bths; + std::vector channels; + for (int i = 0; i < N; ++i) { + channels.push_back(new brpc::Channel); + ASSERT_EQ(0, channels.back()->Init("127.0.0.1", server.listen_address().port, &options)); + bthread_t bth; + ASSERT_EQ(bthread_start_background(&bth, NULL, incr_thread, channels.back()), 0); + bths.push_back(bth); + } + + for (int i = 0; i < N; ++i) { + bthread_join(bths[i], NULL); + delete channels[i]; + } + ASSERT_EQ(int_map["count"], 10 * 5000LL); +} + +class MultiCommandHandler : public brpc::RedisCommandHandler { +public: + MultiCommandHandler() {} + + brpc::RedisCommandHandlerResult Run(const std::vector& args, + brpc::RedisReply* output, + bool flush_batched) override { + output->SetStatus("OK"); + return brpc::REDIS_CMD_CONTINUE; + } + + RedisCommandHandler* NewTransactionHandler() override { + return new MultiTransactionHandler; + } + + class MultiTransactionHandler : public brpc::RedisCommandHandler { + public: + brpc::RedisCommandHandlerResult Run(const std::vector& args, + brpc::RedisReply* output, + bool flush_batched) override { + if (args[0] == "multi") { + output->SetError("ERR duplicate multi"); + return brpc::REDIS_CMD_CONTINUE; + } + if (args[0] != "exec") { + std::vector comm; + for (int i = 0; i < (int)args.size(); ++i) { + comm.push_back(args[i].as_string()); + } + _commands.push_back(comm); + output->SetStatus("QUEUED"); + return brpc::REDIS_CMD_CONTINUE; + } + output->SetArray(_commands.size()); + s_mutex.lock(); + for (size_t i = 0; i < _commands.size(); ++i) { + if (_commands[i][0] == "incr") { + int64_t value; + value = ++int_map[_commands[i][1]]; + (*output)[i].SetInteger(value); + } else { + (*output)[i].SetStatus("unknown command"); + } + } + s_mutex.unlock(); + return brpc::REDIS_CMD_HANDLED; + } + private: + std::vector > _commands; + }; +}; + +TEST_F(RedisTest, server_command_continue) { + std::string password = GeneratePassword(); + std::unique_ptr redis_auth_holder( + new brpc::policy::RedisAuthenticator(password)); + RedisServiceImpl* rsimpl = new RedisServiceImpl(password); + std::unique_ptr ah(new AuthCommandHandler(rsimpl)); + std::unique_ptr gh(new GetCommandHandler(rsimpl)); + std::unique_ptr sh(new SetCommandHandler(rsimpl)); + std::unique_ptr ih(new IncrCommandHandler(rsimpl)); + std::unique_ptr mh(new MultiCommandHandler); + brpc::Server server; + brpc::ServerOptions server_options; + rsimpl->AddCommandHandler("auth", ah.get()); + rsimpl->AddCommandHandler("get", gh.get()); + rsimpl->AddCommandHandler("set", sh.get()); + rsimpl->AddCommandHandler("incr", ih.get()); + rsimpl->AddCommandHandler("multi", mh.get()); + server_options.redis_service = rsimpl; + brpc::PortRange pr(8081, 8900); + ASSERT_EQ(0, server.Start("127.0.0.1", pr, &server_options)); + + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_REDIS; + options.auth = redis_auth_holder.get(); + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("127.0.0.1", server.listen_address().port, &options)); + { + brpc::RedisRequest request; + brpc::RedisResponse response; + brpc::Controller cntl; + ASSERT_TRUE(request.AddCommand("set hello world")); + ASSERT_TRUE(request.AddCommand("get hello")); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(2, response.reply_size()); + ASSERT_STREQ("world", response.reply(1).c_str()); + } + { + brpc::RedisRequest request; + brpc::RedisResponse response; + brpc::Controller cntl; + ASSERT_TRUE(request.AddCommand("multi")); + ASSERT_TRUE(request.AddCommand("mUltI")); + int count = 10; + for (int i = 0; i < count; ++i) { + ASSERT_TRUE(request.AddCommand("incr hello 1")); + } + ASSERT_TRUE(request.AddCommand("exec")); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_EQ(13, response.reply_size()); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(brpc::REDIS_REPLY_STATUS, response.reply(0).type()); + ASSERT_STREQ("OK", response.reply(0).c_str()); + ASSERT_EQ(brpc::REDIS_REPLY_ERROR, response.reply(1).type()); + for (int i = 2; i < count + 2; ++i) { + ASSERT_EQ(brpc::REDIS_REPLY_STATUS, response.reply(i).type()); + ASSERT_STREQ("QUEUED", response.reply(i).c_str()); + } + const brpc::RedisReply& m = response.reply(count + 2); + ASSERT_EQ(count, (int)m.size()); + for (int i = 0; i < count; ++i) { + ASSERT_EQ(i+1, m[i].integer()); + } + } + // After 'multi', normal requests should be successful + { + brpc::RedisRequest request; + brpc::RedisResponse response; + brpc::Controller cntl; + ASSERT_TRUE(request.AddCommand("get hello")); + ASSERT_TRUE(request.AddCommand("get hello2")); + ASSERT_TRUE(request.AddCommand("set key1 value1")); + ASSERT_TRUE(request.AddCommand("get key1")); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_STREQ("world", response.reply(0).c_str()); + ASSERT_EQ(brpc::REDIS_REPLY_NIL, response.reply(1).type()); + ASSERT_EQ(brpc::REDIS_REPLY_STATUS, response.reply(2).type()); + ASSERT_STREQ("OK", response.reply(2).c_str()); + ASSERT_EQ(brpc::REDIS_REPLY_STRING, response.reply(3).type()); + ASSERT_STREQ("value1", response.reply(3).c_str()); + } +} + +TEST_F(RedisTest, server_handle_pipeline) { + std::string password = GeneratePassword(); + std::unique_ptr redis_auth_holder( + new brpc::policy::RedisAuthenticator(password)); + RedisServiceImpl* rsimpl = new RedisServiceImpl(password); + std::unique_ptr getch(new GetCommandHandler(rsimpl, true)); + std::unique_ptr setch(new SetCommandHandler(rsimpl, true)); + std::unique_ptr authch(new AuthCommandHandler(rsimpl)); + std::unique_ptr multich(new MultiCommandHandler); + brpc::Server server; + brpc::ServerOptions server_options; + rsimpl->AddCommandHandler("auth", authch.get()); + rsimpl->AddCommandHandler("get", getch.get()); + rsimpl->AddCommandHandler("set", setch.get()); + rsimpl->AddCommandHandler("multi", multich.get()); + server_options.redis_service = rsimpl; + brpc::PortRange pr(8081, 8900); + ASSERT_EQ(0, server.Start("127.0.0.1", pr, &server_options)); + + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_REDIS; + options.auth = redis_auth_holder.get(); + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("127.0.0.1", server.listen_address().port, &options)); + + brpc::RedisRequest request; + brpc::RedisResponse response; + brpc::Controller cntl; + ASSERT_TRUE(request.AddCommand("set key1 v1")); + ASSERT_TRUE(request.AddCommand("set key2 v2")); + ASSERT_TRUE(request.AddCommand("set key3 v3")); + ASSERT_TRUE(request.AddCommand("get hello")); + ASSERT_TRUE(request.AddCommand("get hello")); + ASSERT_TRUE(request.AddCommand("set key1 world")); + ASSERT_TRUE(request.AddCommand("set key2 world")); + ASSERT_TRUE(request.AddCommand("get key2")); + channel.CallMethod(NULL, &cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(8, response.reply_size()); + ASSERT_EQ(1, rsimpl->_batch_count); + ASSERT_TRUE(response.reply(7).is_string()); + ASSERT_STREQ(response.reply(7).c_str(), "world"); +} + +TEST_F(RedisTest, memory_allocation_limits) { + int32_t original_limit = brpc::FLAGS_redis_max_allocation_size; + brpc::FLAGS_redis_max_allocation_size = 1024; + + butil::Arena arena; + + // Test redis_reply.cpp limits + { + // Test bulk string exceeding limit + butil::IOBuf buf; + std::string large_string = "*1\r\n$2000\r\n"; + large_string.append(2000, 'a'); + large_string.append("\r\n"); + buf.append(large_string); + + brpc::RedisReply reply(&arena); + brpc::ParseError err = reply.ConsumePartialIOBuf(buf); + ASSERT_EQ(brpc::PARSE_ERROR_ABSOLUTELY_WRONG, err); + } + + { + // Test array allocation exceeding limit + butil::IOBuf buf; + int32_t large_count = brpc::FLAGS_redis_max_allocation_size / sizeof(brpc::RedisReply) + 1; + std::string large_array = "*" + std::to_string(large_count) + "\r\n"; + buf.append(large_array); + + brpc::RedisReply reply(&arena); + brpc::ParseError err = reply.ConsumePartialIOBuf(buf); + ASSERT_EQ(brpc::PARSE_ERROR_ABSOLUTELY_WRONG, err); + } + + { + // Test large count + butil::IOBuf buf; + int64_t large_count = 9223372036854775807; + std::string large_array = "*" + std::to_string(large_count) + "\r\n"; + buf.append(large_array); + + brpc::RedisReply reply(&arena); + brpc::ParseError err = reply.ConsumePartialIOBuf(buf); + ASSERT_EQ(brpc::PARSE_ERROR_ABSOLUTELY_WRONG, err); + } + + // Test redis_command.cpp limits + { + // Test command string exceeding limit + brpc::RedisCommandParser parser; + butil::IOBuf buf; + std::string large_cmd = "*2\r\n$3\r\nget\r\n$2000\r\n"; + large_cmd.append(2000, 'b'); + large_cmd.append("\r\n"); + buf.append(large_cmd); + + std::vector args; + brpc::ParseError err = parser.Consume(buf, &args, &arena); + ASSERT_EQ(brpc::PARSE_ERROR_ABSOLUTELY_WRONG, err); + } + + { + // Test command array size exceeding limit + brpc::RedisCommandParser parser; + butil::IOBuf buf; + int32_t large_array_size = brpc::FLAGS_redis_max_allocation_size / sizeof(butil::StringPiece) + 1; + std::string large_array_cmd = "*" + std::to_string(large_array_size) + "\r\n"; + buf.append(large_array_cmd); + + std::vector args; + brpc::ParseError err = parser.Consume(buf, &args, &arena); + ASSERT_EQ(brpc::PARSE_ERROR_ABSOLUTELY_WRONG, err); + } + + { + // Test inline command exceeding limit before CRLF arrives. + brpc::RedisCommandParser parser; + butil::IOBuf buf; + std::string large_inline_cmd = "get "; + large_inline_cmd.append(brpc::FLAGS_redis_max_allocation_size + 1, 'k'); + buf.append(large_inline_cmd); + + std::vector args; + brpc::ParseError err = parser.Consume(buf, &args, &arena); + ASSERT_EQ(brpc::PARSE_ERROR_ABSOLUTELY_WRONG, err); + } + + { + // A command line exactly at the limit may have CRLF split across reads. + brpc::RedisCommandParser parser; + butil::IOBuf buf; + std::string boundary_inline_cmd = "get "; + boundary_inline_cmd.append(brpc::FLAGS_redis_max_allocation_size - + boundary_inline_cmd.size(), 'k'); + boundary_inline_cmd.push_back('\r'); + buf.append(boundary_inline_cmd); + + std::vector args; + brpc::ParseError err = parser.Consume(buf, &args, &arena); + ASSERT_EQ(brpc::PARSE_ERROR_NOT_ENOUGH_DATA, err); + + buf.push_back('\n'); + err = parser.Consume(buf, &args, &arena); + ASSERT_EQ(brpc::PARSE_OK, err); + ASSERT_EQ(2, (int)args.size()); + ASSERT_EQ("get", args[0].as_string()); + ASSERT_EQ(brpc::FLAGS_redis_max_allocation_size - 4, (int)args[1].size()); + } + + { + // Test that only the current inline command line is limited and copied. + brpc::RedisCommandParser parser; + butil::IOBuf buf; + std::string pipelined_inline_cmd = "PING\r\nget "; + pipelined_inline_cmd.append(brpc::FLAGS_redis_max_allocation_size + 1, 'k'); + buf.append(pipelined_inline_cmd); + + std::vector args; + brpc::ParseError err = parser.Consume(buf, &args, &arena); + ASSERT_EQ(brpc::PARSE_OK, err); + ASSERT_EQ(1, (int)args.size()); + ASSERT_EQ("ping", args[0].as_string()); + ASSERT_EQ(pipelined_inline_cmd.size() - 6, buf.size()); + } + + { + // Test large command array work + int32_t original_limit_tmp = brpc::FLAGS_redis_max_allocation_size; + brpc::FLAGS_redis_max_allocation_size = 1024 * 1024; + brpc::RedisCommandParser parser; + butil::IOBuf buf; + int32_t large_array_size = brpc::FLAGS_redis_max_allocation_size / sizeof(butil::StringPiece); + std::string large_array_cmd = "*" + std::to_string(large_array_size) + "\r\n"; + for(int i = 0; i < large_array_size; i++){ + large_array_cmd.append("$1\r\n1\r\n"); + } + buf.append(large_array_cmd); + + std::vector args; + brpc::ParseError err = parser.Consume(buf, &args, &arena); + ASSERT_EQ(brpc::PARSE_OK, err); + brpc::FLAGS_redis_max_allocation_size = original_limit_tmp; + } + + // Test valid cases within limits + { + // Test small bulk string should work + butil::IOBuf buf; + std::string small_string = "*1\r\n$10\r\nhelloworld\r\n"; + buf.append(small_string); + + brpc::RedisReply reply(&arena); + brpc::ParseError err = reply.ConsumePartialIOBuf(buf); + ASSERT_EQ(brpc::PARSE_OK, err); + ASSERT_TRUE(reply.is_array()); + ASSERT_EQ(1, (int)reply.size()); + ASSERT_STREQ("helloworld", reply[0].c_str()); + } + + { + // Test small command should work + brpc::RedisCommandParser parser; + butil::IOBuf buf; + std::string small_cmd = "*2\r\n$3\r\nget\r\n$5\r\nmykey\r\n"; + buf.append(small_cmd); + + std::vector args; + brpc::ParseError err = parser.Consume(buf, &args, &arena); + ASSERT_EQ(brpc::PARSE_OK, err); + ASSERT_EQ(2, (int)args.size()); + ASSERT_EQ("get", args[0].as_string()); + ASSERT_EQ("mykey", args[1].as_string()); + } + + brpc::FLAGS_redis_max_allocation_size = original_limit; +} + +} //namespace diff --git a/test/brpc_repeated_field_unittest.cpp b/test/brpc_repeated_field_unittest.cpp new file mode 100644 index 0000000..2f4ae8e --- /dev/null +++ b/test/brpc_repeated_field_unittest.cpp @@ -0,0 +1,41 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "repeated.pb.h" +#include +#include + +class RepeatedFieldTest : public testing::Test { +protected: + void SetUp() {} + void TearDown() {} +}; + +TEST_F(RepeatedFieldTest, empty_array) { + RepeatedMessage m; + std::string json; + + ASSERT_TRUE(json2pb::ProtoMessageToJson(m, &json)); + std::cout << json << std::endl; + + m.add_strings(); + m.add_ints(1); + m.add_msgs(); + json.clear(); + ASSERT_TRUE(json2pb::ProtoMessageToJson(m, &json)); + std::cout << json << std::endl; +} diff --git a/test/brpc_rtmp_unittest.cpp b/test/brpc_rtmp_unittest.cpp new file mode 100644 index 0000000..61a6374 --- /dev/null +++ b/test/brpc_rtmp_unittest.cpp @@ -0,0 +1,1147 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: Fri May 20 15:52:22 CST 2016 + +#include +#include +#include +#include +#include +#include +#include +#include "butil/time.h" +#include "butil/macros.h" +#include "brpc/socket.h" +#include "brpc/acceptor.h" +#include "brpc/server.h" +#include "brpc/controller.h" +#include "brpc/rtmp.h" +#include "brpc/amf.h" + +namespace brpc { +DECLARE_int32(amf_max_depth); +DECLARE_int32(amf_max_string_size); +DECLARE_int32(amf_max_array_size); +} + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + return RUN_ALL_TESTS(); +} + +namespace { +class ScopedAMFMaxDepth { +public: + explicit ScopedAMFMaxDepth(int32_t depth) : _old_depth(brpc::FLAGS_amf_max_depth) { + brpc::FLAGS_amf_max_depth = depth; + } + ~ScopedAMFMaxDepth() { + brpc::FLAGS_amf_max_depth = _old_depth; + } + +private: + int32_t _old_depth; +}; + +class ScopedAMFLimit { +public: + ScopedAMFLimit(int32_t* flag, int32_t value) : _flag(flag), _old_value(*flag) { + *_flag = value; + } + ~ScopedAMFLimit() { + *_flag = _old_value; + } + +private: + int32_t* _flag; + int32_t _old_value; +}; + +void AppendAMFStrictArrayHeader(std::string* out, uint32_t count) { + out->push_back((char)brpc::AMF_MARKER_STRICT_ARRAY); + out->push_back((char)((count >> 24) & 0xFF)); + out->push_back((char)((count >> 16) & 0xFF)); + out->push_back((char)((count >> 8) & 0xFF)); + out->push_back((char)(count & 0xFF)); +} + +void AppendAMFObjectHeader(std::string* out) { + out->push_back((char)brpc::AMF_MARKER_OBJECT); +} + +void AppendAMFEcmaArrayHeader(std::string* out, uint32_t count) { + out->push_back((char)brpc::AMF_MARKER_ECMA_ARRAY); + out->push_back((char)((count >> 24) & 0xFF)); + out->push_back((char)((count >> 16) & 0xFF)); + out->push_back((char)((count >> 8) & 0xFF)); + out->push_back((char)(count & 0xFF)); +} + +void AppendAMFShortStringBody(std::string* out, const char* name) { + const uint16_t len = strlen(name); + out->push_back((char)((len >> 8) & 0xFF)); + out->push_back((char)(len & 0xFF)); + out->append(name, len); +} + +void AppendAMFLongStringHeader(std::string* out, uint32_t len) { + out->push_back((char)brpc::AMF_MARKER_LONG_STRING); + out->push_back((char)((len >> 24) & 0xFF)); + out->push_back((char)((len >> 16) & 0xFF)); + out->push_back((char)((len >> 8) & 0xFF)); + out->push_back((char)(len & 0xFF)); +} + +void AppendAMFObjectEnd(std::string* out) { + AppendAMFShortStringBody(out, ""); + out->push_back((char)brpc::AMF_MARKER_OBJECT_END); +} + +std::string MakeNestedAMFObject(int depth) { + std::string out; + AppendAMFObjectHeader(&out); + for (int i = 0; i < depth; ++i) { + AppendAMFShortStringBody(&out, "x"); + AppendAMFObjectHeader(&out); + } + for (int i = 0; i <= depth; ++i) { + AppendAMFObjectEnd(&out); + } + return out; +} + +std::string MakeNestedAMFEcmaArray(int depth) { + std::string out; + AppendAMFEcmaArrayHeader(&out, depth == 0 ? 0 : 1); + for (int i = 0; i < depth; ++i) { + AppendAMFShortStringBody(&out, "x"); + AppendAMFEcmaArrayHeader(&out, i + 1 == depth ? 0 : 1); + } + return out; +} +} // namespace + +class TestRtmpClientStream : public brpc::RtmpClientStream { +public: + TestRtmpClientStream() + : _called_on_stop(0) + , _called_on_first_message(0) + , _nvideomsg(0) + , _naudiomsg(0) { + LOG(INFO) << __FUNCTION__; + } + ~TestRtmpClientStream() { + LOG(INFO) << __FUNCTION__; + assertions_on_stop(); + } + void assertions_on_stop() { + ASSERT_EQ(1, _called_on_stop); + } + void assertions_on_successful_play() { + ASSERT_EQ(1, _called_on_first_message); + ASSERT_LT(0, _nvideomsg); + ASSERT_LT(0, _naudiomsg); + } + void assertions_on_failure() { + ASSERT_EQ(0, _called_on_first_message); + ASSERT_EQ(0, _nvideomsg); + ASSERT_EQ(0, _naudiomsg); + assertions_on_stop(); + } + void OnFirstMessage() { + ++_called_on_first_message; + } + void OnStop() { + ++_called_on_stop; + } + void OnVideoMessage(brpc::RtmpVideoMessage* msg) { + ++_nvideomsg; + // video data is ascii in UT, print it out. + LOG(INFO) << remote_side() << "|stream=" << stream_id() + << ": Got " << *msg << " data=" << msg->data; + } + void OnAudioMessage(brpc::RtmpAudioMessage* msg) { + ++_naudiomsg; + // audio data is ascii in UT, print it out. + LOG(INFO) << remote_side() << "|stream=" << stream_id() + << ": Got " << *msg << " data=" << msg->data; + } +private: + int _called_on_stop; + int _called_on_first_message; + int _nvideomsg; + int _naudiomsg; +}; + +class TestRtmpRetryingClientStream + : public brpc::RtmpRetryingClientStream { +public: + TestRtmpRetryingClientStream() + : _called_on_stop(0) + , _called_on_first_message(0) + , _called_on_playable(0) { + LOG(INFO) << __FUNCTION__; + } + ~TestRtmpRetryingClientStream() { + LOG(INFO) << __FUNCTION__; + assertions_on_stop(); + } + void assertions_on_stop() { + ASSERT_EQ(1, _called_on_stop); + } + void OnStop() { + ++_called_on_stop; + } + void OnFirstMessage() { + ++_called_on_first_message; + } + void OnPlayable() { + ++_called_on_playable; + } + + void OnVideoMessage(brpc::RtmpVideoMessage* msg) { + // video data is ascii in UT, print it out. + LOG(INFO) << remote_side() << "|stream=" << stream_id() + << ": Got " << *msg << " data=" << msg->data; + } + void OnAudioMessage(brpc::RtmpAudioMessage* msg) { + // audio data is ascii in UT, print it out. + LOG(INFO) << remote_side() << "|stream=" << stream_id() + << ": Got " << *msg << " data=" << msg->data; + } +private: + int _called_on_stop; + int _called_on_first_message; + int _called_on_playable; +}; + +const char* UNEXIST_NAME = "unexist_stream"; + +class PlayingDummyStream : public brpc::RtmpServerStream { +public: + enum State { + STATE_UNPLAYING, + STATE_PLAYING, + STATE_STOPPED + }; + PlayingDummyStream(int64_t sleep_ms) + : _state(STATE_UNPLAYING), _sleep_ms(sleep_ms) { + LOG(INFO) << __FUNCTION__ << "(" << this << ")"; + } + ~PlayingDummyStream() { + LOG(INFO) << __FUNCTION__ << "(" << this << ")"; + } + void OnPlay(const brpc::RtmpPlayOptions& opt, + butil::Status* status, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + LOG(INFO) << remote_side() << "|stream=" << stream_id() + << ": Got play{stream_name=" << opt.stream_name + << " start=" << opt.start + << " duration=" << opt.duration + << " reset=" << opt.reset << '}'; + if (opt.stream_name == UNEXIST_NAME) { + status->set_error(EPERM, "Unexist stream"); + return; + } + if (_sleep_ms > 0) { + LOG(INFO) << "Sleep " << _sleep_ms + << " ms before responding play request"; + bthread_usleep(_sleep_ms * 1000L); + } + int rc = bthread_start_background(&_play_thread, NULL, + RunSendData, this); + if (rc) { + status->set_error(rc, "Fail to create thread"); + return; + } + State expected = STATE_UNPLAYING; + if (!_state.compare_exchange_strong(expected, STATE_PLAYING)) { + if (expected == STATE_STOPPED) { + bthread_stop(_play_thread); + bthread_join(_play_thread, NULL); + } else { + CHECK(false) << "Impossible"; + } + } + } + + void OnStop() { + LOG(INFO) << "OnStop of PlayingDummyStream=" << this; + if (_state.exchange(STATE_STOPPED) == STATE_PLAYING) { + bthread_stop(_play_thread); + bthread_join(_play_thread, NULL); + } + } + + void SendData(); + +private: + static void* RunSendData(void* arg) { + ((PlayingDummyStream*)arg)->SendData(); + return NULL; + } + + butil::atomic _state; + bthread_t _play_thread; + int64_t _sleep_ms; +}; + +void PlayingDummyStream::SendData() { + LOG(INFO) << "Enter SendData of PlayingDummyStream=" << this; + + brpc::RtmpVideoMessage vmsg; + brpc::RtmpAudioMessage amsg; + + vmsg.timestamp = 1000; + amsg.timestamp = 1000; + for (int i = 0; !bthread_stopped(bthread_self()); ++i) { + vmsg.timestamp += 20; + amsg.timestamp += 20; + + vmsg.frame_type = brpc::FLV_VIDEO_FRAME_KEYFRAME; + vmsg.codec = brpc::FLV_VIDEO_AVC; + vmsg.data.clear(); + vmsg.data.append(butil::string_printf("video_%d(ms_id=%u)", + i, stream_id())); + //failing to send is possible + SendVideoMessage(vmsg); + + amsg.codec = brpc::FLV_AUDIO_AAC; + amsg.rate = brpc::FLV_SOUND_RATE_44100HZ; + amsg.bits = brpc::FLV_SOUND_16BIT; + amsg.type = brpc::FLV_SOUND_STEREO; + amsg.data.clear(); + amsg.data.append(butil::string_printf("audio_%d(ms_id=%u)", + i, stream_id())); + SendAudioMessage(amsg); + + bthread_usleep(1000000); + } + + LOG(INFO) << "Quit SendData of PlayingDummyStream=" << this; +} + +class PlayingDummyService : public brpc::RtmpService { +public: + PlayingDummyService(int64_t sleep_ms = 0) : _sleep_ms(sleep_ms) {} + +private: + // Called to create a server-side stream. + virtual brpc::RtmpServerStream* NewStream( + const brpc::RtmpConnectRequest&) { + return new PlayingDummyStream(_sleep_ms); + } + int64_t _sleep_ms; +}; + +class PublishStream : public brpc::RtmpServerStream { +public: + PublishStream(int64_t sleep_ms) + : _sleep_ms(sleep_ms) + , _called_on_stop(0) + , _called_on_first_message(0) + , _nvideomsg(0) + , _naudiomsg(0) { + LOG(INFO) << __FUNCTION__ << "(" << this << ")"; + } + ~PublishStream() { + LOG(INFO) << __FUNCTION__ << "(" << this << ")"; + assertions_on_stop(); + } + void assertions_on_stop() { + ASSERT_EQ(1, _called_on_stop); + } + void OnPublish(const std::string& stream_name, + brpc::RtmpPublishType publish_type, + butil::Status* status, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + LOG(INFO) << remote_side() << "|stream=" << stream_id() + << ": Got publish{stream_name=" << stream_name + << " type=" << brpc::RtmpPublishType2Str(publish_type) + << '}'; + if (stream_name == UNEXIST_NAME) { + status->set_error(EPERM, "Unexist stream"); + return; + } + if (_sleep_ms > 0) { + LOG(INFO) << "Sleep " << _sleep_ms + << " ms before responding play request"; + bthread_usleep(_sleep_ms * 1000L); + } + } + void OnFirstMessage() { + ++_called_on_first_message; + } + void OnStop() { + LOG(INFO) << "OnStop of PublishStream=" << this; + ++_called_on_stop; + } + void OnVideoMessage(brpc::RtmpVideoMessage* msg) { + ++_nvideomsg; + // video data is ascii in UT, print it out. + LOG(INFO) << remote_side() << "|stream=" << stream_id() + << ": Got " << *msg << " data=" << msg->data; + } + void OnAudioMessage(brpc::RtmpAudioMessage* msg) { + ++_naudiomsg; + // audio data is ascii in UT, print it out. + LOG(INFO) << remote_side() << "|stream=" << stream_id() + << ": Got " << *msg << " data=" << msg->data; + } +private: + int64_t _sleep_ms; + int _called_on_stop; + int _called_on_first_message; + int _nvideomsg; + int _naudiomsg; +}; + +class PublishService : public brpc::RtmpService { +public: + PublishService(int64_t sleep_ms = 0) : _sleep_ms(sleep_ms) { + pthread_mutex_init(&_mutex, NULL); + } + ~PublishService() { + pthread_mutex_destroy(&_mutex); + } + void move_created_streams( + std::vector >* out) { + out->clear(); + BAIDU_SCOPED_LOCK(_mutex); + out->swap(_created_streams); + } + +private: + // Called to create a server-side stream. + virtual brpc::RtmpServerStream* NewStream( + const brpc::RtmpConnectRequest&) { + PublishStream* stream = new PublishStream(_sleep_ms); + { + BAIDU_SCOPED_LOCK(_mutex); + _created_streams.push_back(stream); + } + return stream; + } + int64_t _sleep_ms; + pthread_mutex_t _mutex; + std::vector > _created_streams; +}; + +class RtmpSubStream : public brpc::RtmpClientStream { +public: + explicit RtmpSubStream(brpc::RtmpMessageHandler* mh) + : _message_handler(mh) {} + // @RtmpStreamBase + void OnMetaData(brpc::RtmpMetaData*, const butil::StringPiece&); + void OnSharedObjectMessage(brpc::RtmpSharedObjectMessage* msg); + void OnAudioMessage(brpc::RtmpAudioMessage* msg); + void OnVideoMessage(brpc::RtmpVideoMessage* msg); + void OnFirstMessage(); + void OnStop(); +private: + std::unique_ptr _message_handler; +}; + +void RtmpSubStream::OnFirstMessage() { + _message_handler->OnPlayable(); +} + +void RtmpSubStream::OnMetaData(brpc::RtmpMetaData* obj, const butil::StringPiece& name) { + _message_handler->OnMetaData(obj, name); +} + +void RtmpSubStream::OnSharedObjectMessage(brpc::RtmpSharedObjectMessage* msg) { + _message_handler->OnSharedObjectMessage(msg); +} + +void RtmpSubStream::OnAudioMessage(brpc::RtmpAudioMessage* msg) { + _message_handler->OnAudioMessage(msg); +} + +void RtmpSubStream::OnVideoMessage(brpc::RtmpVideoMessage* msg) { + _message_handler->OnVideoMessage(msg); +} + +void RtmpSubStream::OnStop() { + _message_handler->OnSubStreamStop(this); +} + + +class RtmpSubStreamCreator : public brpc::SubStreamCreator { +public: + RtmpSubStreamCreator(const brpc::RtmpClient* client); + + ~RtmpSubStreamCreator(); + + // @SubStreamCreator + void NewSubStream(brpc::RtmpMessageHandler* message_handler, + butil::intrusive_ptr* sub_stream); + void LaunchSubStream(brpc::RtmpStreamBase* sub_stream, + brpc::RtmpRetryingClientStreamOptions* options); + +private: + const brpc::RtmpClient* _client; +}; + +RtmpSubStreamCreator::RtmpSubStreamCreator(const brpc::RtmpClient* client) + : _client(client) {} + +RtmpSubStreamCreator::~RtmpSubStreamCreator() {} + +void RtmpSubStreamCreator::NewSubStream(brpc::RtmpMessageHandler* message_handler, + butil::intrusive_ptr* sub_stream) { + if (sub_stream) { + (*sub_stream).reset(new RtmpSubStream(message_handler)); + } + return; +} + +void RtmpSubStreamCreator::LaunchSubStream( + brpc::RtmpStreamBase* sub_stream, + brpc::RtmpRetryingClientStreamOptions* options) { + brpc::RtmpClientStreamOptions client_options = *options; + dynamic_cast(sub_stream)->Init(_client, client_options); +} + +TEST(RtmpTest, parse_rtmp_url) { + butil::StringPiece host; + butil::StringPiece vhost; + butil::StringPiece port; + butil::StringPiece app; + butil::StringPiece stream_name; + + brpc::ParseRtmpURL("rtmp://HOST/APP/STREAM", + &host, &vhost, &port, &app, &stream_name); + ASSERT_EQ("HOST", host); + ASSERT_TRUE(vhost.empty()); + ASSERT_EQ("1935", port); + ASSERT_EQ("APP", app); + ASSERT_EQ("STREAM", stream_name); + + brpc::ParseRtmpURL("HOST/APP/STREAM", + &host, &vhost, &port, &app, &stream_name); + ASSERT_EQ("HOST", host); + ASSERT_TRUE(vhost.empty()); + ASSERT_EQ("1935", port); + ASSERT_EQ("APP", app); + ASSERT_EQ("STREAM", stream_name); + + brpc::ParseRtmpURL("rtmp://HOST:8765//APP?vhost=abc///STREAM?queries", + &host, &vhost, &port, &app, &stream_name); + ASSERT_EQ("HOST", host); + ASSERT_EQ("abc", vhost); + ASSERT_EQ("8765", port); + ASSERT_EQ("APP", app); + ASSERT_EQ("STREAM?queries", stream_name); + + brpc::ParseRtmpURL("HOST:8765//APP?vhost=abc///STREAM?queries", + &host, &vhost, &port, &app, &stream_name); + ASSERT_EQ("HOST", host); + ASSERT_EQ("abc", vhost); + ASSERT_EQ("8765", port); + ASSERT_EQ("APP", app); + ASSERT_EQ("STREAM?queries", stream_name); + + brpc::ParseRtmpURL("HOST:8765//APP?vhost=abc///STREAM?queries/", + &host, &vhost, &port, &app, &stream_name); + ASSERT_EQ("HOST", host); + ASSERT_EQ("abc", vhost); + ASSERT_EQ("8765", port); + ASSERT_EQ("APP", app); + ASSERT_EQ("STREAM?queries/", stream_name); + + brpc::ParseRtmpURL("HOST:8765/APP?vhost=abc", + &host, &vhost, &port, &app, &stream_name); + ASSERT_EQ("HOST", host); + ASSERT_EQ("abc", vhost); + ASSERT_EQ("8765", port); + ASSERT_EQ("APP", app); + ASSERT_TRUE(stream_name.empty()); +} + +TEST(RtmpTest, amf) { + std::string req_buf; + brpc::RtmpInfo info; + brpc::AMFObject obj; + std::string dummy = "_result"; + { + google::protobuf::io::StringOutputStream zc_stream(&req_buf); + brpc::AMFOutputStream ostream(&zc_stream); + brpc::WriteAMFString(dummy, &ostream); + brpc::WriteAMFUint32(17, &ostream); + info.set_code("NetConnection.Connect"); // TODO + info.set_level("error"); + info.set_description("heheda hello foobar"); + brpc::WriteAMFObject(info, &ostream); + ASSERT_TRUE(ostream.good()); + obj.SetString("code", "foo"); + obj.SetString("level", "bar"); + obj.SetString("description", "heheda"); + brpc::WriteAMFObject(obj, &ostream); + ASSERT_TRUE(ostream.good()); + } + + google::protobuf::io::ArrayInputStream zc_stream(req_buf.data(), req_buf.size()); + brpc::AMFInputStream istream(&zc_stream); + std::string result; + ASSERT_TRUE(brpc::ReadAMFString(&result, &istream)); + ASSERT_EQ(dummy, result); + uint32_t num = 0; + ASSERT_TRUE(brpc::ReadAMFUint32(&num, &istream)); + ASSERT_EQ(17u, num); + brpc::RtmpInfo info2; + ASSERT_TRUE(brpc::ReadAMFObject(&info2, &istream)); + ASSERT_EQ(info.code(), info2.code()); + ASSERT_EQ(info.level(), info2.level()); + ASSERT_EQ(info.description(), info2.description()); + brpc::RtmpInfo info3; + ASSERT_TRUE(brpc::ReadAMFObject(&info3, &istream)); + ASSERT_EQ("foo", info3.code()); + ASSERT_EQ("bar", info3.level()); + ASSERT_EQ("heheda", info3.description()); +} + +TEST(RtmpTest, amf_rejects_oversized_string_before_growing_output) { + std::string req_buf; + AppendAMFLongStringHeader(&req_buf, 16); + req_buf.append("short", 5); + + google::protobuf::io::ArrayInputStream zc_stream(req_buf.data(), req_buf.size()); + brpc::AMFInputStream istream(&zc_stream); + std::string result = "unchanged"; + EXPECT_FALSE(brpc::ReadAMFString(&result, &istream)); + EXPECT_TRUE(result.empty()); + + ScopedAMFLimit scoped_limit(&brpc::FLAGS_amf_max_string_size, 4); + std::string capped_buf; + AppendAMFLongStringHeader(&capped_buf, 5); + capped_buf.append("hello", 5); + + google::protobuf::io::ArrayInputStream zc_stream2(capped_buf.data(), + capped_buf.size()); + brpc::AMFInputStream istream2(&zc_stream2); + EXPECT_FALSE(brpc::ReadAMFString(&result, &istream2)); +} + +TEST(RtmpTest, amf_rejects_oversized_ecma_array_count) { + ScopedAMFLimit scoped_limit(&brpc::FLAGS_amf_max_array_size, 1); + + std::string req_buf; + AppendAMFEcmaArrayHeader(&req_buf, 2); + AppendAMFShortStringBody(&req_buf, "x"); + req_buf.push_back((char)brpc::AMF_MARKER_NULL); + + google::protobuf::io::ArrayInputStream zc_stream(req_buf.data(), req_buf.size()); + brpc::AMFInputStream istream(&zc_stream); + brpc::AMFObject obj; + EXPECT_FALSE(brpc::ReadAMFObject(&obj, &istream)); +} + +TEST(RtmpTest, amf_rejects_oversized_strict_array_count) { + ScopedAMFLimit scoped_limit(&brpc::FLAGS_amf_max_array_size, 1); + + std::string req_buf; + AppendAMFStrictArrayHeader(&req_buf, 2); + req_buf.push_back((char)brpc::AMF_MARKER_NULL); + + google::protobuf::io::ArrayInputStream zc_stream(req_buf.data(), req_buf.size()); + brpc::AMFInputStream istream(&zc_stream); + brpc::AMFArray arr; + EXPECT_FALSE(brpc::ReadAMFArray(&arr, &istream)); + EXPECT_EQ(0u, arr.size()); +} + +TEST(RtmpTest, amf_rejects_deep_nested_arrays) { + ScopedAMFMaxDepth scoped_depth(4); + + std::string req_buf; + for (int i = 0; i <= brpc::FLAGS_amf_max_depth + 1; ++i) { + AppendAMFStrictArrayHeader(&req_buf, 1); + } + req_buf.push_back((char)brpc::AMF_MARKER_NULL); + + google::protobuf::io::ArrayInputStream zc_stream(req_buf.data(), req_buf.size()); + brpc::AMFInputStream istream(&zc_stream); + brpc::AMFArray arr; + EXPECT_FALSE(brpc::ReadAMFArray(&arr, &istream)); + + req_buf.clear(); + for (int i = 0; i < brpc::FLAGS_amf_max_depth; ++i) { + AppendAMFStrictArrayHeader(&req_buf, 1); + } + req_buf.push_back((char)brpc::AMF_MARKER_NULL); + + google::protobuf::io::ArrayInputStream zc_stream2(req_buf.data(), req_buf.size()); + brpc::AMFInputStream istream2(&zc_stream2); + brpc::AMFArray valid_arr; + EXPECT_TRUE(brpc::ReadAMFArray(&valid_arr, &istream2)); +} + +TEST(RtmpTest, amf_rejects_deep_nested_objects) { + ScopedAMFMaxDepth scoped_depth(4); + + std::string req_buf = MakeNestedAMFObject(brpc::FLAGS_amf_max_depth + 1); + google::protobuf::io::ArrayInputStream zc_stream(req_buf.data(), req_buf.size()); + brpc::AMFInputStream istream(&zc_stream); + brpc::AMFObject obj; + EXPECT_FALSE(brpc::ReadAMFObject(&obj, &istream)); + + req_buf = MakeNestedAMFObject(brpc::FLAGS_amf_max_depth); + google::protobuf::io::ArrayInputStream zc_stream2(req_buf.data(), req_buf.size()); + brpc::AMFInputStream istream2(&zc_stream2); + brpc::AMFObject valid_obj; + EXPECT_TRUE(brpc::ReadAMFObject(&valid_obj, &istream2)); +} + +TEST(RtmpTest, amf_rejects_deep_nested_ecma_arrays) { + ScopedAMFMaxDepth scoped_depth(4); + + std::string req_buf = MakeNestedAMFEcmaArray(brpc::FLAGS_amf_max_depth + 1); + google::protobuf::io::ArrayInputStream zc_stream(req_buf.data(), req_buf.size()); + brpc::AMFInputStream istream(&zc_stream); + brpc::AMFObject obj; + EXPECT_FALSE(brpc::ReadAMFObject(&obj, &istream)); + + req_buf = MakeNestedAMFEcmaArray(brpc::FLAGS_amf_max_depth); + google::protobuf::io::ArrayInputStream zc_stream2(req_buf.data(), req_buf.size()); + brpc::AMFInputStream istream2(&zc_stream2); + brpc::AMFObject valid_obj; + EXPECT_TRUE(brpc::ReadAMFObject(&valid_obj, &istream2)); +} + +// Create() copies the record into a non-NUL-terminated buffer, so the SPS must +// be parsed with an explicitly-sized view. A crafted sequence header whose SPS +// body has no zero byte used to make ParseSPS run strlen off the end of that +// buffer (an out-of-bounds read, caught here under ASan). +TEST(RtmpTest, avc_seq_header_sps_without_zero_byte) { + const uint16_t sps_length = 70; // keep the record above the small-array cap + butil::IOBuf buf; + const char head[6] = { 0x01, 0x64, 0x00, 0x28, (char)0xff, (char)0xe1 }; + buf.append(head, sizeof(head)); // version/profile/level, lengthSizeMinus1=3, numSPS=1 + const char len_be[2] = { (char)(sps_length >> 8), (char)(sps_length & 0xff) }; + buf.append(len_be, sizeof(len_be)); + std::string sps(sps_length, (char)0x67); // NAL header 0x67 then non-zero filler + buf.append(sps); + + brpc::AVCDecoderConfigurationRecord avc; + // Only requirement: the call must not read past the copied record. + avc.Create(buf); +} + +static void AppendBigEndian3Bytes(std::string* s, uint32_t v) { + s->push_back((char)((v >> 16) & 0xFF)); + s->push_back((char)((v >> 8) & 0xFF)); + s->push_back((char)(v & 0xFF)); +} + +// Build an FLV stream header followed by a single tag of `tag_type' whose +// DataSize field is set to `data_size'. No tag body is appended, so a valid +// tag needs data_size==0 to be rejected before any body is consumed. +static std::string MakeFlvTagWithDataSize(char tag_type, uint32_t data_size) { + std::string s; + const char header[] = { 'F', 'L', 'V', 0x01, 0x05, 0, 0, 0, 0x09 }; + s.append(header, sizeof(header)); + s.append(4, '\0'); // PreviousTagSize0 + s.push_back(tag_type); + AppendBigEndian3Bytes(&s, data_size); // DataSize + s.append(3, '\0'); // Timestamp + s.push_back('\0'); // TimestampExtended + s.append(3, '\0'); // StreamID + s.append(4, '\0'); // PreviousTagSize + return s; +} + +TEST(RtmpTest, flv_reader_rejects_zero_datasize_video_tag) { + std::string flv = MakeFlvTagWithDataSize((char)brpc::FLV_TAG_VIDEO, 0); + butil::IOBuf buf; + buf.append(flv); + + brpc::FlvReader reader(&buf); + brpc::FlvTagType type; + ASSERT_TRUE(reader.PeekMessageType(&type).ok()); + ASSERT_EQ(brpc::FLV_TAG_VIDEO, type); + const size_t before = buf.size(); + + brpc::RtmpVideoMessage vmsg; + // A DataSize of 0 used to underflow `msg_size - 1' and drain the whole + // buffer; it must now be rejected without consuming the tag. + ASSERT_FALSE(reader.Read(&vmsg).ok()); + ASSERT_EQ(before, buf.size()); +} + +TEST(RtmpTest, flv_reader_rejects_zero_datasize_audio_tag) { + std::string flv = MakeFlvTagWithDataSize((char)brpc::FLV_TAG_AUDIO, 0); + butil::IOBuf buf; + buf.append(flv); + + brpc::FlvReader reader(&buf); + brpc::FlvTagType type; + ASSERT_TRUE(reader.PeekMessageType(&type).ok()); + ASSERT_EQ(brpc::FLV_TAG_AUDIO, type); + const size_t before = buf.size(); + + brpc::RtmpAudioMessage amsg; + ASSERT_FALSE(reader.Read(&amsg).ok()); + ASSERT_EQ(before, buf.size()); +} + +TEST(RtmpTest, successfully_play_streams) { + PlayingDummyService rtmp_service; + brpc::Server server; + brpc::ServerOptions server_opt; + server_opt.rtmp_service = &rtmp_service; + ASSERT_EQ(0, server.Start(8571, &server_opt)); + + brpc::RtmpClientOptions rtmp_opt; + rtmp_opt.app = "hello"; + rtmp_opt.swfUrl = "anything"; + rtmp_opt.tcUrl = "rtmp://heheda"; + brpc::RtmpClient rtmp_client; + ASSERT_EQ(0, rtmp_client.Init("localhost:8571", rtmp_opt)); + + // Create multiple streams. + const int NSTREAM = 2; + brpc::DestroyingPtr cstreams[NSTREAM]; + for (int i = 0; i < NSTREAM; ++i) { + cstreams[i].reset(new TestRtmpClientStream); + brpc::RtmpClientStreamOptions opt; + opt.play_name = butil::string_printf("play_name_%d", i); + //opt.publish_name = butil::string_printf("pub_name_%d", i); + opt.wait_until_play_or_publish_is_sent = true; + cstreams[i]->Init(&rtmp_client, opt); + } + sleep(5); + for (int i = 0; i < NSTREAM; ++i) { + cstreams[i]->assertions_on_successful_play(); + } + LOG(INFO) << "Quiting program..."; +} + +TEST(RtmpTest, fail_to_play_streams) { + PlayingDummyService rtmp_service; + brpc::Server server; + brpc::ServerOptions server_opt; + server_opt.rtmp_service = &rtmp_service; + ASSERT_EQ(0, server.Start(8571, &server_opt)); + + brpc::RtmpClientOptions rtmp_opt; + rtmp_opt.app = "hello"; + rtmp_opt.swfUrl = "anything"; + rtmp_opt.tcUrl = "rtmp://heheda"; + brpc::RtmpClient rtmp_client; + ASSERT_EQ(0, rtmp_client.Init("localhost:8571", rtmp_opt)); + + // Create multiple streams. + const int NSTREAM = 2; + brpc::DestroyingPtr cstreams[NSTREAM]; + for (int i = 0; i < NSTREAM; ++i) { + cstreams[i].reset(new TestRtmpClientStream); + brpc::RtmpClientStreamOptions opt; + opt.play_name = UNEXIST_NAME; + opt.wait_until_play_or_publish_is_sent = true; + cstreams[i]->Init(&rtmp_client, opt); + } + sleep(1); + for (int i = 0; i < NSTREAM; ++i) { + cstreams[i]->assertions_on_failure(); + } + LOG(INFO) << "Quiting program..."; +} + +TEST(RtmpTest, successfully_publish_streams) { + PublishService rtmp_service; + brpc::Server server; + brpc::ServerOptions server_opt; + server_opt.rtmp_service = &rtmp_service; + ASSERT_EQ(0, server.Start(8571, &server_opt)); + + brpc::RtmpClientOptions rtmp_opt; + rtmp_opt.app = "hello"; + rtmp_opt.swfUrl = "anything"; + rtmp_opt.tcUrl = "rtmp://heheda"; + brpc::RtmpClient rtmp_client; + ASSERT_EQ(0, rtmp_client.Init("localhost:8571", rtmp_opt)); + + // Create multiple streams. + const int NSTREAM = 2; + brpc::DestroyingPtr cstreams[NSTREAM]; + for (int i = 0; i < NSTREAM; ++i) { + cstreams[i].reset(new TestRtmpClientStream); + brpc::RtmpClientStreamOptions opt; + opt.publish_name = butil::string_printf("pub_name_%d", i); + opt.wait_until_play_or_publish_is_sent = true; + cstreams[i]->Init(&rtmp_client, opt); + } + const int REP = 5; + for (int i = 0; i < REP; ++i) { + brpc::RtmpVideoMessage vmsg; + vmsg.timestamp = 1000 + i * 20; + vmsg.frame_type = brpc::FLV_VIDEO_FRAME_KEYFRAME; + vmsg.codec = brpc::FLV_VIDEO_AVC; + vmsg.data.append(butil::string_printf("video_%d", i)); + for (int j = 0; j < NSTREAM; j += 2) { + ASSERT_EQ(0, cstreams[j]->SendVideoMessage(vmsg)); + } + + brpc::RtmpAudioMessage amsg; + amsg.timestamp = 1000 + i * 20; + amsg.codec = brpc::FLV_AUDIO_AAC; + amsg.rate = brpc::FLV_SOUND_RATE_44100HZ; + amsg.bits = brpc::FLV_SOUND_16BIT; + amsg.type = brpc::FLV_SOUND_STEREO; + amsg.data.append(butil::string_printf("audio_%d", i)); + for (int j = 1; j < NSTREAM; j += 2) { + ASSERT_EQ(0, cstreams[j]->SendAudioMessage(amsg)); + } + + bthread_usleep(500000); + } + std::vector > created_streams; + rtmp_service.move_created_streams(&created_streams); + ASSERT_EQ(NSTREAM, (int)created_streams.size()); + for (int i = 0; i < NSTREAM; ++i) { + EXPECT_EQ(1, created_streams[i]->_called_on_first_message); + } + for (int j = 0; j < NSTREAM; j += 2) { + ASSERT_EQ(REP, created_streams[j]->_nvideomsg); + } + for (int j = 1; j < NSTREAM; j += 2) { + ASSERT_EQ(REP, created_streams[j]->_naudiomsg); + } + LOG(INFO) << "Quiting program..."; +} + +TEST(RtmpTest, failed_to_publish_streams) { + PublishService rtmp_service; + brpc::Server server; + brpc::ServerOptions server_opt; + server_opt.rtmp_service = &rtmp_service; + ASSERT_EQ(0, server.Start(8575, &server_opt)); + + brpc::RtmpClientOptions rtmp_opt; + rtmp_opt.app = "hello"; + rtmp_opt.swfUrl = "anything"; + rtmp_opt.tcUrl = "rtmp://heheda"; + brpc::RtmpClient rtmp_client; + ASSERT_EQ(0, rtmp_client.Init("localhost:8575", rtmp_opt)); + + // Create multiple streams. + const int NSTREAM = 2; + brpc::DestroyingPtr cstreams[NSTREAM]; + for (int i = 0; i < NSTREAM; ++i) { + cstreams[i].reset(new TestRtmpClientStream); + brpc::RtmpClientStreamOptions opt; + opt.publish_name = UNEXIST_NAME; + opt.wait_until_play_or_publish_is_sent = true; + cstreams[i]->Init(&rtmp_client, opt); + } + sleep(1); + for (int i = 0; i < NSTREAM; ++i) { + cstreams[i]->assertions_on_failure(); + } + std::vector > created_streams; + rtmp_service.move_created_streams(&created_streams); + ASSERT_EQ(NSTREAM, (int)created_streams.size()); + for (int i = 0; i < NSTREAM; ++i) { + ASSERT_EQ(0, created_streams[i]->_called_on_first_message); + ASSERT_EQ(0, created_streams[i]->_nvideomsg); + ASSERT_EQ(0, created_streams[i]->_naudiomsg); + } + LOG(INFO) << "Quiting program..."; +} + +TEST(RtmpTest, failed_to_connect_client_streams) { + brpc::RtmpClientOptions rtmp_opt; + rtmp_opt.app = "hello"; + rtmp_opt.swfUrl = "anything"; + rtmp_opt.tcUrl = "rtmp://heheda"; + brpc::RtmpClient rtmp_client; + ASSERT_EQ(0, rtmp_client.Init("localhost:8572", rtmp_opt)); + + // Create multiple streams. + const int NSTREAM = 2; + brpc::DestroyingPtr cstreams[NSTREAM]; + for (int i = 0; i < NSTREAM; ++i) { + cstreams[i].reset(new TestRtmpClientStream); + brpc::RtmpClientStreamOptions opt; + opt.play_name = butil::string_printf("play_name_%d", i); + opt.wait_until_play_or_publish_is_sent = true; + cstreams[i]->Init(&rtmp_client, opt); + cstreams[i]->assertions_on_failure(); + } + LOG(INFO) << "Quiting program..."; +} + +TEST(RtmpTest, destroy_client_streams_before_init) { + brpc::RtmpClientOptions rtmp_opt; + rtmp_opt.app = "hello"; + rtmp_opt.swfUrl = "anything"; + rtmp_opt.tcUrl = "rtmp://heheda"; + brpc::RtmpClient rtmp_client; + ASSERT_EQ(0, rtmp_client.Init("localhost:8573", rtmp_opt)); + + // Create multiple streams. + const int NSTREAM = 2; + butil::intrusive_ptr cstreams[NSTREAM]; + for (int i = 0; i < NSTREAM; ++i) { + cstreams[i].reset(new TestRtmpClientStream); + cstreams[i]->Destroy(); + ASSERT_EQ(1, cstreams[i]->_called_on_stop); + ASSERT_EQ(brpc::RtmpClientStream::STATE_DESTROYING, cstreams[i]->_state); + brpc::RtmpClientStreamOptions opt; + opt.play_name = butil::string_printf("play_name_%d", i); + opt.wait_until_play_or_publish_is_sent = true; + cstreams[i]->Init(&rtmp_client, opt); + cstreams[i]->assertions_on_failure(); + } + LOG(INFO) << "Quiting program..."; +} + +TEST(RtmpTest, destroy_retrying_client_streams_before_init) { + brpc::RtmpClientOptions rtmp_opt; + rtmp_opt.app = "hello"; + rtmp_opt.swfUrl = "anything"; + rtmp_opt.tcUrl = "rtmp://heheda"; + brpc::RtmpClient rtmp_client; + ASSERT_EQ(0, rtmp_client.Init("localhost:8573", rtmp_opt)); + + // Create multiple streams. + const int NSTREAM = 2; + butil::intrusive_ptr cstreams[NSTREAM]; + for (int i = 0; i < NSTREAM; ++i) { + cstreams[i].reset(new TestRtmpRetryingClientStream); + cstreams[i]->Destroy(); + ASSERT_EQ(1, cstreams[i]->_called_on_stop); + brpc::RtmpRetryingClientStreamOptions opt; + opt.play_name = butil::string_printf("play_name_%d", i); + brpc::SubStreamCreator* sc = new RtmpSubStreamCreator(&rtmp_client); + cstreams[i]->Init(sc, opt); + ASSERT_EQ(1, cstreams[i]->_called_on_stop); + } + LOG(INFO) << "Quiting program..."; +} + +TEST(RtmpTest, destroy_client_streams_during_creation) { + PlayingDummyService rtmp_service(2000/*sleep 2s*/); + brpc::Server server; + brpc::ServerOptions server_opt; + server_opt.rtmp_service = &rtmp_service; + ASSERT_EQ(0, server.Start(8574, &server_opt)); + + brpc::RtmpClientOptions rtmp_opt; + rtmp_opt.app = "hello"; + rtmp_opt.swfUrl = "anything"; + rtmp_opt.tcUrl = "rtmp://heheda"; + brpc::RtmpClient rtmp_client; + ASSERT_EQ(0, rtmp_client.Init("localhost:8574", rtmp_opt)); + + // Create multiple streams. + const int NSTREAM = 2; + butil::intrusive_ptr cstreams[NSTREAM]; + for (int i = 0; i < NSTREAM; ++i) { + cstreams[i].reset(new TestRtmpClientStream); + brpc::RtmpClientStreamOptions opt; + opt.play_name = butil::string_printf("play_name_%d", i); + cstreams[i]->Init(&rtmp_client, opt); + ASSERT_EQ(0, cstreams[i]->_called_on_stop); + usleep(500*1000); + ASSERT_EQ(0, cstreams[i]->_called_on_stop); + cstreams[i]->Destroy(); + usleep(10*1000); + ASSERT_EQ(1, cstreams[i]->_called_on_stop); + } + LOG(INFO) << "Quiting program..."; +} + +TEST(RtmpTest, destroy_retrying_client_streams_during_creation) { + PlayingDummyService rtmp_service(2000/*sleep 2s*/); + brpc::Server server; + brpc::ServerOptions server_opt; + server_opt.rtmp_service = &rtmp_service; + ASSERT_EQ(0, server.Start(8574, &server_opt)); + + brpc::RtmpClientOptions rtmp_opt; + rtmp_opt.app = "hello"; + rtmp_opt.swfUrl = "anything"; + rtmp_opt.tcUrl = "rtmp://heheda"; + brpc::RtmpClient rtmp_client; + ASSERT_EQ(0, rtmp_client.Init("localhost:8574", rtmp_opt)); + + // Create multiple streams. + const int NSTREAM = 2; + butil::intrusive_ptr cstreams[NSTREAM]; + for (int i = 0; i < NSTREAM; ++i) { + cstreams[i].reset(new TestRtmpRetryingClientStream); + brpc::RtmpRetryingClientStreamOptions opt; + opt.play_name = butil::string_printf("play_name_%d", i); + brpc::SubStreamCreator* sc = new RtmpSubStreamCreator(&rtmp_client); + cstreams[i]->Init(sc, opt); + ASSERT_EQ(0, cstreams[i]->_called_on_stop); + usleep(500*1000); + ASSERT_EQ(0, cstreams[i]->_called_on_stop); + cstreams[i]->Destroy(); + usleep(10*1000); + ASSERT_EQ(1, cstreams[i]->_called_on_stop); + } + LOG(INFO) << "Quiting program..."; +} + +TEST(RtmpTest, retrying_stream) { + PlayingDummyService rtmp_service; + brpc::Server server; + brpc::ServerOptions server_opt; + server_opt.rtmp_service = &rtmp_service; + ASSERT_EQ(0, server.Start(8576, &server_opt)); + + brpc::RtmpClientOptions rtmp_opt; + rtmp_opt.app = "hello"; + rtmp_opt.swfUrl = "anything"; + rtmp_opt.tcUrl = "rtmp://heheda"; + brpc::RtmpClient rtmp_client; + ASSERT_EQ(0, rtmp_client.Init("localhost:8576", rtmp_opt)); + + // Create multiple streams. + const int NSTREAM = 2; + brpc::DestroyingPtr cstreams[NSTREAM]; + for (int i = 0; i < NSTREAM; ++i) { + cstreams[i].reset(new TestRtmpRetryingClientStream); + brpc::Controller cntl; + brpc::RtmpRetryingClientStreamOptions opt; + opt.play_name = butil::string_printf("name_%d", i); + brpc::SubStreamCreator* sc = new RtmpSubStreamCreator(&rtmp_client); + cstreams[i]->Init(sc, opt); + } + sleep(3); + LOG(INFO) << "Stopping server"; + server.Stop(0); + server.Join(); + LOG(INFO) << "Stopped server and sleep for a while"; + sleep(3); + ASSERT_EQ(0, server.Start(8576, &server_opt)); + sleep(3); + for (int i = 0; i < NSTREAM; ++i) { + ASSERT_EQ(1, cstreams[i]->_called_on_first_message); + ASSERT_EQ(2, cstreams[i]->_called_on_playable); + } + LOG(INFO) << "Quiting program..."; +} diff --git a/test/brpc_server_unittest.cpp b/test/brpc_server_unittest.cpp new file mode 100644 index 0000000..8508a79 --- /dev/null +++ b/test/brpc_server_unittest.cpp @@ -0,0 +1,2118 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: Sun Jul 13 15:04:18 CST 2014 + +#include +#include +#include +#include +#include +#include "butil/time.h" +#include "butil/macros.h" +#include "butil/fd_guard.h" +#include "butil/files/scoped_file.h" +#include "brpc/socket.h" +#include "butil/object_pool.h" +#include "brpc/builtin/version_service.h" +#include "brpc/builtin/health_service.h" +#include "brpc/builtin/list_service.h" +#include "brpc/builtin/status_service.h" +#include "brpc/builtin/threads_service.h" +#include "brpc/builtin/vlog_service.h" +#include "brpc/builtin/index_service.h" // IndexService +#include "brpc/builtin/connections_service.h" // ConnectionsService +#include "brpc/builtin/flags_service.h" // FlagsService +#include "brpc/builtin/vars_service.h" // VarsService +#include "brpc/builtin/rpcz_service.h" // RpczService +#include "brpc/builtin/dir_service.h" // DirService +#include "brpc/builtin/pprof_service.h" // PProfService +#include "brpc/builtin/bthreads_service.h" // BthreadsService +#include "brpc/builtin/ids_service.h" // IdsService +#include "brpc/builtin/sockets_service.h" // SocketsService +#include "brpc/builtin/bad_method_service.h" +#include "brpc/server.h" +#include "brpc/restful.h" +#include "brpc/channel.h" +#include "brpc/socket_map.h" +#include "brpc/controller.h" +#include "brpc/compress.h" +#include "echo.pb.h" +#include "v1.pb.h" +#include "v2.pb.h" +#include "v3.pb.h" + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + return RUN_ALL_TESTS(); +} + +namespace brpc { +DECLARE_bool(enable_threads_service); +DECLARE_bool(enable_dir_service); + +namespace policy { +DECLARE_bool(use_http_error_code); + +extern bool SerializeRpcMessage(const google::protobuf::Message& serializer, + Controller& cntl, ContentType content_type, + CompressType compress_type, + ChecksumType checksum_type, butil::IOBuf* buf); +extern bool DeserializeRpcMessage(const butil::IOBuf& deserializer, + Controller& cntl, ContentType content_type, + CompressType compress_type, + ChecksumType checksum_type, + google::protobuf::Message* message); +} + +} + +namespace { +void* RunClosure(void* arg) { + google::protobuf::Closure* done = (google::protobuf::Closure*)arg; + done->Run(); + return NULL; +} + +bool g_verify_success = true; +const std::string g_unauthorized_error_text = "unauthorized"; + +class MyAuthenticator : public brpc::Authenticator { +public: + MyAuthenticator() = default; + ~MyAuthenticator() override = default; + int GenerateCredential(std::string*) const override { + return 0; + } + + int VerifyCredential(const std::string&, + const butil::EndPoint&, + brpc::AuthContext*) const override { + return g_verify_success ? 0 : -1; + } + + std::string GetUnauthorizedErrorText() const override { + return g_unauthorized_error_text; + } +}; + +bool g_delete = false; +const std::string EXP_REQUEST = "hello"; +const std::string EXP_RESPONSE = "world"; +const std::string EXP_REQUEST_BASE64 = "aGVsbG8="; +const std::string EXP_USER_FIELD_KEY = "hello"; +const std::string EXP_USER_FIELD_VALUE = "world"; + +class EchoServiceImpl : public test::EchoService { +public: + EchoServiceImpl() : count(0) {} + virtual ~EchoServiceImpl() { g_delete = true; } + virtual void Echo(google::protobuf::RpcController* cntl_base, + const test::EchoRequest* request, + test::EchoResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = (brpc::Controller*)cntl_base; + count.fetch_add(1, butil::memory_order_relaxed); + EXPECT_EQ(EXP_REQUEST, request->message()); + response->set_message(EXP_RESPONSE); + if (request->sleep_us() > 0) { + LOG(INFO) << "Sleep " << request->sleep_us() << " us, protocol=" + << cntl->request_protocol(); + bthread_usleep(request->sleep_us()); + } else { + LOG(INFO) << "No sleep, protocol=" << cntl->request_protocol(); + } + if (cntl->has_request_user_fields()) { + ASSERT_TRUE(!cntl->request_user_fields()->empty()); + std::string* val = cntl->request_user_fields()->seek(EXP_USER_FIELD_KEY); + ASSERT_TRUE(val != NULL); + ASSERT_EQ(*val, EXP_USER_FIELD_VALUE); + cntl->response_user_fields()->insert(EXP_USER_FIELD_KEY, EXP_USER_FIELD_VALUE); + } + } + + virtual void ComboEcho(google::protobuf::RpcController*, + const test::ComboRequest* request, + test::ComboResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + for (int i = 0; i < request->requests_size(); ++i) { + response->add_responses()->set_message(request->requests(i).message()); + } + } + + virtual void BytesEcho1(google::protobuf::RpcController*, + const test::BytesRequest* request, + test::BytesResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + EXPECT_EQ(EXP_REQUEST, request->databytes()); + response->set_databytes(request->databytes()); + } + + virtual void BytesEcho2(google::protobuf::RpcController*, + const test::BytesRequest* request, + test::BytesResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + EXPECT_EQ(EXP_REQUEST_BASE64, request->databytes()); + response->set_databytes(request->databytes()); + } + + butil::atomic count; +}; + +// An evil service that fakes its `ServiceDescriptor' +class EvilService : public test::EchoService { +public: + explicit EvilService(const google::protobuf::ServiceDescriptor* sd) + : _sd(sd) {} + + const google::protobuf::ServiceDescriptor* GetDescriptor() { + return _sd; + } + +private: + const google::protobuf::ServiceDescriptor* _sd; +}; + +class ServerTest : public ::testing::Test{ +protected: + ServerTest() {}; + virtual ~ServerTest(){}; + virtual void SetUp() {}; + virtual void TearDown() {}; + + void TestAddBuiltinService( + const google::protobuf::ServiceDescriptor* conflict_sd) { + brpc::Server server; + EvilService evil(conflict_sd); + EXPECT_EQ(0, server.AddServiceInternal( + &evil, false, brpc::ServiceOptions())); + EXPECT_EQ(-1, server.AddBuiltinServices()); + } +}; + +TEST_F(ServerTest, sanity) { + { + brpc::Server server; + ASSERT_EQ(-1, server.Start("127.0.0.1:12345:asdf", NULL)); + ASSERT_EQ(-1, server.Start("127.0.0.1:99999", NULL)); + ASSERT_EQ(0, server.Start("127.0.0.1:8613", NULL)); + } + { + brpc::Server server; + // accept hostname as well. + ASSERT_EQ(0, server.Start("localhost:8613", NULL)); + } + { + brpc::Server server; + ASSERT_EQ(0, server.Start("localhost:0", NULL)); + // port should be replaced with the actually used one. + ASSERT_NE(0, server.listen_address().port); + } + + { + brpc::Server server; + ASSERT_EQ(-1, server.Start(99999, NULL)); + ASSERT_EQ(0, server.Start(8613, NULL)); + } + { + brpc::Server server; + brpc::ServerOptions options; + options.internal_port = 8613; // The same as service port + ASSERT_EQ(-1, server.Start("127.0.0.1:8613", &options)); + ASSERT_FALSE(server.IsRunning()); // Revert server's status + // And release the listen port + ASSERT_EQ(0, server.Start("127.0.0.1:8613", NULL)); + } + { + brpc::Server server; + brpc::ServerOptions options; + ASSERT_EQ(0, server.Start(brpc::PortRange(8000, 9000), &options)); + ASSERT_TRUE(server.IsRunning()); + ASSERT_EQ(0ul, server.service_count()); + ASSERT_TRUE(NULL == server.first_service()); + ASSERT_EQ(0, server.Stop(0)); + ASSERT_EQ(0, server.Join()); + } + + butil::EndPoint ep; + MyAuthenticator auth; + brpc::Server server; + ASSERT_EQ(0, str2endpoint("127.0.0.1:8613", &ep)); + brpc::ServerOptions opt; + opt.auth = &auth; + ASSERT_EQ(0, server.Start(ep, &opt)); + ASSERT_TRUE(server.IsRunning()); + ASSERT_EQ(&auth, server.options().auth); + ASSERT_EQ(0ul, server.service_count()); + ASSERT_TRUE(NULL == server.first_service()); + + std::vector services; + server.ListServices(&services); + ASSERT_TRUE(services.empty()); + ASSERT_EQ(0UL, server.service_count()); + for (brpc::Server::ServiceMap::const_iterator it + = server._service_map.begin(); + it != server._service_map.end(); ++it) { + ASSERT_TRUE(it->second.is_builtin_service); + } + + ASSERT_EQ(0, server.Stop(0)); + ASSERT_EQ(0, server.Join()); +} + +TEST_F(ServerTest, invalid_protocol_in_enabled_protocols) { + butil::EndPoint ep; + ASSERT_EQ(0, str2endpoint("127.0.0.1:8613", &ep)); + brpc::Server server; + brpc::ServerOptions opt; + opt.enabled_protocols = "hehe baidu_std"; + ASSERT_EQ(-1, server.Start(ep, &opt)); +} + +class EchoServiceV1 : public v1::EchoService { +public: + EchoServiceV1() : ncalled(0) + , ncalled_echo2(0) + , ncalled_echo3(0) + , ncalled_echo4(0) + , ncalled_echo5(0) + {} + virtual ~EchoServiceV1() {} + virtual void Echo(google::protobuf::RpcController* cntl_base, + const v1::EchoRequest* request, + v1::EchoResponse* response, + google::protobuf::Closure* done) { + brpc::Controller* cntl = static_cast(cntl_base); + brpc::ClosureGuard done_guard(done); + if (request->has_message()) { + response->set_message(request->message() + "_v1"); + } else { + CHECK_EQ(brpc::PROTOCOL_HTTP, cntl->request_protocol()); + cntl->response_attachment() = cntl->request_attachment(); + } + ncalled.fetch_add(1); + } + virtual void Echo2(google::protobuf::RpcController*, + const v1::EchoRequest* request, + v1::EchoResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + response->set_message(request->message() + "_v1_Echo2"); + ncalled_echo2.fetch_add(1); + } + virtual void Echo3(google::protobuf::RpcController*, + const v1::EchoRequest* request, + v1::EchoResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + response->set_message(request->message() + "_v1_Echo3"); + ncalled_echo3.fetch_add(1); + } + virtual void Echo4(google::protobuf::RpcController*, + const v1::EchoRequest* request, + v1::EchoResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + response->set_message(request->message() + "_v1_Echo4"); + ncalled_echo4.fetch_add(1); + } + virtual void Echo5(google::protobuf::RpcController*, + const v1::EchoRequest* request, + v1::EchoResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + response->set_message(request->message() + "_v1_Echo5"); + ncalled_echo5.fetch_add(1); + } + + butil::atomic ncalled; + butil::atomic ncalled_echo2; + butil::atomic ncalled_echo3; + butil::atomic ncalled_echo4; + butil::atomic ncalled_echo5; +}; + +class EchoServiceV2 : public v2::EchoService { +public: + EchoServiceV2() : ncalled(0) {} + virtual ~EchoServiceV2() {} + virtual void Echo(google::protobuf::RpcController*, + const v2::EchoRequest* request, + v2::EchoResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + response->set_value(request->value() + 1); + ncalled.fetch_add(1); + } + butil::atomic ncalled; +}; + +class EchoServiceV3 : public v3::EchoService { +public: + void Echo(::google::protobuf::RpcController*, + const v3::EchoRequest* request, + v3::EchoResponse* response, + ::google::protobuf::Closure* done) override { + brpc::ClosureGuard done_guard(done); + ASSERT_EQ(request->message(), EXP_REQUEST); + response->set_message(EXP_RESPONSE); + } +}; + +TEST_F(ServerTest, empty_enabled_protocols) { + butil::EndPoint ep; + ASSERT_EQ(0, str2endpoint("127.0.0.1:8613", &ep)); + brpc::Server server; + EchoServiceImpl echo_svc; + ASSERT_EQ(0, server.AddService( + &echo_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + brpc::ServerOptions opt; + opt.enabled_protocols = " "; + ASSERT_EQ(0, server.Start(ep, &opt)); + + brpc::Channel chan; + brpc::ChannelOptions copt; + copt.protocol = "baidu_std"; + ASSERT_EQ(0, chan.Init(ep, &copt)); + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(EXP_REQUEST); + test::EchoService_Stub stub(&chan); + stub.Echo(&cntl, &req, &res, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + + ASSERT_EQ(0, server.Stop(0)); + ASSERT_EQ(0, server.Join()); +} + +TEST_F(ServerTest, only_allow_protocols_in_enabled_protocols) { + butil::EndPoint ep; + ASSERT_EQ(0, str2endpoint("127.0.0.1:8613", &ep)); + brpc::Server server; + EchoServiceImpl echo_svc; + ASSERT_EQ(0, server.AddService( + &echo_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + brpc::ServerOptions opt; + opt.enabled_protocols = "hulu_pbrpc"; + ASSERT_EQ(0, server.Start(ep, &opt)); + + brpc::ChannelOptions copt; + brpc::Controller cntl; + + // http is always allowed. + brpc::Channel http_channel; + copt.protocol = "http"; + ASSERT_EQ(0, http_channel.Init(ep, &copt)); + cntl.Reset(); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText() << cntl.response_attachment(); + + // Unmatched protocols are not allowed. + brpc::Channel chan; + copt.protocol = "baidu_std"; + ASSERT_EQ(0, chan.Init(ep, &copt)); + test::EchoRequest req; + test::EchoResponse res; + cntl.Reset(); + req.set_message(EXP_REQUEST); + test::EchoService_Stub stub(&chan); + stub.Echo(&cntl, &req, &res, NULL); + ASSERT_TRUE(cntl.Failed()); + ASSERT_TRUE(cntl.ErrorText().find("Got EOF of ") != std::string::npos); + + ASSERT_EQ(0, server.Stop(0)); + ASSERT_EQ(0, server.Join()); +} + +TEST_F(ServerTest, services_in_different_ns) { + const int port = 9200; + brpc::Server server1; + EchoServiceV1 service_v1; + ASSERT_EQ(0, server1.AddService(&service_v1, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server1.Start(port, NULL)); + brpc::Channel http_channel; + brpc::ChannelOptions chan_options; + chan_options.protocol = "http"; + ASSERT_EQ(0, http_channel.Init("0.0.0.0", port, &chan_options)); + brpc::Controller cntl; + cntl.http_request().uri() = "/EchoService/Echo"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"foo\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText() << cntl.response_attachment(); + ASSERT_EQ(1, service_v1.ncalled.load()); + cntl.Reset(); + cntl.http_request().uri() = "/v1.EchoService/Echo"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"foo\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText() << cntl.response_attachment(); + ASSERT_EQ(2, service_v1.ncalled.load()); + //Stop the server to add another service. + server1.Stop(0); + server1.Join(); + // NOTICE: stopping server now does not trigger HC of the client because + // the main socket is only SetFailed in RPC route, however the RPC already + // ends at this point. + EchoServiceV2 service_v2; +#ifndef ALLOW_SAME_NAMED_SERVICE_IN_DIFFERENT_NAMESPACE + ASSERT_EQ(-1, server1.AddService(&service_v2, brpc::SERVER_DOESNT_OWN_SERVICE)); +#else + ASSERT_EQ(0, server1.AddService(&service_v2, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server1.Start(port, NULL)); + //sleep(3); // wait for HC + cntl.Reset(); + cntl.http_request().uri() = "/v2.EchoService/Echo"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"value\":33}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText() << cntl.response_attachment(); + ASSERT_EQ(1, service_v2.ncalled.load()); + cntl.Reset(); + cntl.http_request().uri() = "/EchoService/Echo"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"value\":33}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText() << cntl.response_attachment(); + ASSERT_EQ(2, service_v2.ncalled.load()); + server1.Stop(0); + server1.Join(); +#endif +} + +TEST_F(ServerTest, various_forms_of_uri_paths) { + const int port = 9200; + brpc::Server server1; + EchoServiceV1 service_v1; + ASSERT_EQ(0, server1.AddService(&service_v1, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server1.Start(port, NULL)); + brpc::Channel http_channel; + brpc::ChannelOptions chan_options; + chan_options.protocol = "http"; + ASSERT_EQ(0, http_channel.Init("0.0.0.0", port, &chan_options)); + brpc::Controller cntl; + cntl.http_request().uri() = "/EchoService/Echo"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"foo\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText() << cntl.response_attachment(); + ASSERT_EQ(1, service_v1.ncalled.load()); + + cntl.Reset(); + cntl.http_request().uri() = "/EchoService///Echo//"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"foo\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText() << cntl.response_attachment(); + ASSERT_EQ(2, service_v1.ncalled.load()); + + cntl.Reset(); + cntl.http_request().uri() = "/EchoService /Echo/"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"foo\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_TRUE(cntl.Failed()); + ASSERT_EQ(brpc::EREQUEST, cntl.ErrorCode()); + LOG(INFO) << "Expected error: " << cntl.ErrorText(); + ASSERT_EQ(2, service_v1.ncalled.load()); + + // Additional path(stored in unresolved_path) after method is acceptible + cntl.Reset(); + cntl.http_request().uri() = "/EchoService/Echo/Foo"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"foo\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(3, service_v1.ncalled.load()); + + //Stop the server. + server1.Stop(0); + server1.Join(); +} + +TEST_F(ServerTest, missing_required_fields) { + const int port = 9200; + brpc::Server server1; + EchoServiceV1 service_v1; + ASSERT_EQ(0, server1.AddService(&service_v1, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server1.Start(port, NULL)); + brpc::Channel http_channel; + brpc::ChannelOptions chan_options; + chan_options.protocol = "http"; + ASSERT_EQ(0, http_channel.Init("0.0.0.0", port, &chan_options)); + brpc::Controller cntl; + cntl.http_request().uri() = "/EchoService/Echo"; + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_TRUE(cntl.Failed()); + ASSERT_EQ(brpc::EHTTP, cntl.ErrorCode()); + LOG(INFO) << cntl.ErrorText(); + ASSERT_EQ(brpc::HTTP_STATUS_BAD_REQUEST, cntl.http_response().status_code()); + ASSERT_EQ(0, service_v1.ncalled.load()); + + cntl.Reset(); + cntl.http_request().uri() = "/EchoService/Echo"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_TRUE(cntl.Failed()); + ASSERT_EQ(brpc::EHTTP, cntl.ErrorCode()); + ASSERT_EQ(brpc::HTTP_STATUS_BAD_REQUEST, cntl.http_response().status_code()); + ASSERT_EQ(0, service_v1.ncalled.load()); + + cntl.Reset(); + cntl.http_request().uri() = "/EchoService/Echo"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message2\":\"foo\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_TRUE(cntl.Failed()); + ASSERT_EQ(brpc::EHTTP, cntl.ErrorCode()); + ASSERT_EQ(brpc::HTTP_STATUS_BAD_REQUEST, cntl.http_response().status_code()); + ASSERT_EQ(0, service_v1.ncalled.load()); +} + +TEST_F(ServerTest, disallow_http_body_to_pb) { + const int port = 9200; + brpc::Server server1; + EchoServiceV1 service_v1; + brpc::ServiceOptions svc_opt; + svc_opt.allow_http_body_to_pb = false; + svc_opt.restful_mappings = "/access_echo1=>Echo"; + ASSERT_EQ(0, server1.AddService(&service_v1, svc_opt)); + ASSERT_EQ(0, server1.Start(port, NULL)); + brpc::Channel http_channel; + brpc::ChannelOptions chan_options; + chan_options.protocol = "http"; + ASSERT_EQ(0, http_channel.Init("0.0.0.0", port, &chan_options)); + brpc::Controller cntl; + cntl.http_request().uri() = "/access_echo1"; + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_TRUE(cntl.Failed()); + ASSERT_EQ(brpc::EHTTP, cntl.ErrorCode()); + ASSERT_EQ(brpc::HTTP_STATUS_INTERNAL_SERVER_ERROR, + cntl.http_response().status_code()); + ASSERT_EQ(1, service_v1.ncalled.load()); + + cntl.Reset(); + cntl.http_request().uri() = "/access_echo1"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("heheda"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ("heheda", cntl.response_attachment()); + ASSERT_EQ(2, service_v1.ncalled.load()); +} + +TEST_F(ServerTest, restful_mapping) { + const int port = 9200; + EchoServiceV1 service_v1; + EchoServiceV2 service_v2; + + brpc::Server server1; + ASSERT_EQ(0u, server1.service_count()); + ASSERT_EQ(0, server1.AddService( + &service_v1, + brpc::SERVER_DOESNT_OWN_SERVICE, + "/v1/echo/ => Echo," + + // Map another path to the same method is ok. + "/v3/echo => Echo," + + // end with wildcard + "/v2/echo/* => Echo," + + // single-component path should be OK + "/v4_echo => Echo," + + // heading slash can be ignored + " v5/echo => Echo," + + // with or without wildcard can coexist. + " /v6/echo => Echo," + " /v6/echo/* => Echo2," + " /v6/abc/*/def => Echo3," + " /v6/echo/*.flv => Echo4," + " /v6/*.flv => Echo5," + " *.flv => Echo," + )); + ASSERT_EQ(1u, server1.service_count()); + ASSERT_TRUE(server1._global_restful_map); + ASSERT_EQ(1UL, server1._global_restful_map->size()); + + // Disallow duplicated path + brpc::Server server2; + ASSERT_EQ(-1, server2.AddService( + &service_v1, + brpc::SERVER_DOESNT_OWN_SERVICE, + "/v1/echo => Echo," + "/v1/echo => Echo")); + ASSERT_EQ(0u, server2.service_count()); + + // NOTE: PATH/* and PATH cannot coexist in previous versions, now it's OK. + brpc::Server server3; + ASSERT_EQ(0, server3.AddService( + &service_v1, + brpc::SERVER_DOESNT_OWN_SERVICE, + "/v1/echo/* => Echo," + "/v1/echo => Echo")); + ASSERT_EQ(1u, server3.service_count()); + + // Same named services can't be added even with restful mapping + brpc::Server server4; + ASSERT_EQ(0, server4.AddService( + &service_v1, + brpc::SERVER_DOESNT_OWN_SERVICE, + "/v1/echo => Echo")); + ASSERT_EQ(1u, server4.service_count()); + ASSERT_EQ(-1, server4.AddService( + &service_v2, + brpc::SERVER_DOESNT_OWN_SERVICE, + "/v2/echo => Echo")); + ASSERT_EQ(1u, server4.service_count()); + + // Invalid method name. + brpc::Server server5; + ASSERT_EQ(-1, server5.AddService( + &service_v1, + brpc::SERVER_DOESNT_OWN_SERVICE, + "/v1/echo => UnexistMethod")); + ASSERT_EQ(0u, server5.service_count()); + + // Invalid path. + brpc::Server server6; + ASSERT_EQ(-1, server6.AddService( + &service_v1, + brpc::SERVER_DOESNT_OWN_SERVICE, + "/v1/ echo => Echo")); + ASSERT_EQ(0u, server6.service_count()); + + // Empty path + brpc::Server server7; + ASSERT_EQ(-1, server7.AddService( + &service_v1, + brpc::SERVER_DOESNT_OWN_SERVICE, + " => Echo")); + ASSERT_EQ(0u, server7.service_count()); + + // Disabled pattern "/A*/B => M" + brpc::Server server8; + ASSERT_EQ(-1, server8.AddService( + &service_v1, + brpc::SERVER_DOESNT_OWN_SERVICE, + " abc* => Echo")); + ASSERT_EQ(0u, server8.service_count()); + ASSERT_EQ(-1, server8.AddService( + &service_v1, + brpc::SERVER_DOESNT_OWN_SERVICE, + " abc/def* => Echo")); + ASSERT_EQ(0u, server8.service_count()); + + // More than one wildcard + brpc::Server server9; + ASSERT_EQ(-1, server9.AddService( + &service_v1, + brpc::SERVER_DOESNT_OWN_SERVICE, + " /v1/*/* => Echo")); + ASSERT_EQ(0u, server9.service_count()); + + // default url access + brpc::Server server10; + ASSERT_EQ(0, server10.AddService( + &service_v1, + brpc::SERVER_DOESNT_OWN_SERVICE, + "/v1/echo => Echo", + true)); + ASSERT_EQ(1u, server10.service_count()); + ASSERT_FALSE(server10._global_restful_map); + + // Access services + ASSERT_EQ(0, server1.Start(port, NULL)); + brpc::Channel http_channel; + brpc::ChannelOptions chan_options; + chan_options.protocol = "http"; + ASSERT_EQ(0, http_channel.Init("0.0.0.0", port, &chan_options)); + + // reject /EchoService/Echo + brpc::Controller cntl; + cntl.http_request().uri() = "/EchoService/Echo"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"foo\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_TRUE(cntl.Failed()); + ASSERT_EQ(0, service_v1.ncalled.load()); + + // access v1.Echo via /v1/echo. + cntl.Reset(); + cntl.http_request().uri() = "/v1/echo"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"foo\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, service_v1.ncalled.load()); + ASSERT_EQ("{\"message\":\"foo_v1\"}", cntl.response_attachment()); + + // access v1.Echo via /v3/echo. + cntl.Reset(); + cntl.http_request().uri() = "/v3/echo"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"bar\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(2, service_v1.ncalled.load()); + ASSERT_EQ("{\"message\":\"bar_v1\"}", cntl.response_attachment()); + + // Adding extra slashes (and heading/trailing spaces) is OK. + cntl.Reset(); + cntl.http_request().uri() = " //v1///echo//// "; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"hello\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(3, service_v1.ncalled.load()); + ASSERT_EQ("{\"message\":\"hello_v1\"}", cntl.response_attachment()); + + // /v3/echo must be exactly matched. + cntl.Reset(); + cntl.http_request().uri() = "/v3/echo/anything"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"foo\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_TRUE(cntl.Failed()); + ASSERT_EQ(brpc::EHTTP, cntl.ErrorCode()); + LOG(INFO) << "Expected error: " << cntl.ErrorText(); + ASSERT_EQ(3, service_v1.ncalled.load()); + + // Access v1.Echo via /v2/echo + cntl.Reset(); + cntl.http_request().uri() = "/v2/echo"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"hehe\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(4, service_v1.ncalled.load()); + ASSERT_EQ("{\"message\":\"hehe_v1\"}", cntl.response_attachment()); + + // Access v1.Echo via /v2/echo/anything + cntl.Reset(); + cntl.http_request().uri() = "/v2/echo/anything"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"good\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(5, service_v1.ncalled.load()); + ASSERT_EQ("{\"message\":\"good_v1\"}", cntl.response_attachment()); + + cntl.Reset(); + cntl.http_request().uri() = "/v4_echo"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"hoho\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(6, service_v1.ncalled.load()); + ASSERT_EQ("{\"message\":\"hoho_v1\"}", cntl.response_attachment()); + + cntl.Reset(); + cntl.http_request().uri() = "/v5/echo"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"xyz\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(7, service_v1.ncalled.load()); + ASSERT_EQ("{\"message\":\"xyz_v1\"}", cntl.response_attachment()); + + cntl.Reset(); + cntl.http_request().uri() = "/v6/echo"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"xyz\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(8, service_v1.ncalled.load()); + ASSERT_EQ("{\"message\":\"xyz_v1\"}", cntl.response_attachment()); + + cntl.Reset(); + cntl.http_request().uri() = "/v6/echo/test"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"xyz\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, service_v1.ncalled_echo2.load()); + ASSERT_EQ("{\"message\":\"xyz_v1_Echo2\"}", cntl.response_attachment()); + + cntl.Reset(); + cntl.http_request().uri() = "/v6/abc/heheda/def"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"abc_heheda\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, service_v1.ncalled_echo3.load()); + ASSERT_EQ("{\"message\":\"abc_heheda_v1_Echo3\"}", cntl.response_attachment()); + + cntl.Reset(); + cntl.http_request().uri() = "/v6/abc/def"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"abc\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(2, service_v1.ncalled_echo3.load()); + ASSERT_EQ("{\"message\":\"abc_v1_Echo3\"}", cntl.response_attachment()); + + // Incorrect suffix + cntl.Reset(); + cntl.http_request().uri() = "/v6/abc/heheda/def2"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"xyz\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_TRUE(cntl.Failed()); + ASSERT_EQ(2, service_v1.ncalled_echo3.load()); + + cntl.Reset(); + cntl.http_request().uri() = "/v6/echo/1.flv"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"1.flv\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ("{\"message\":\"1.flv_v1_Echo4\"}", cntl.response_attachment()); + ASSERT_EQ(1, service_v1.ncalled_echo4.load()); + + cntl.Reset(); + cntl.http_request().uri() = "//v6//d.flv//"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"d.flv\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ("{\"message\":\"d.flv_v1_Echo5\"}", cntl.response_attachment()); + ASSERT_EQ(1, service_v1.ncalled_echo5.load()); + + // matched the global restful map. + cntl.Reset(); + cntl.http_request().uri() = "//d.flv//"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"d.flv\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ("{\"message\":\"d.flv_v1\"}", cntl.response_attachment()); + ASSERT_EQ(9, service_v1.ncalled.load()); + + cntl.Reset(); + cntl.http_request().uri() = "/v7/e.flv"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"e.flv\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ("{\"message\":\"e.flv_v1\"}", cntl.response_attachment()); + ASSERT_EQ(10, service_v1.ncalled.load()); + + cntl.Reset(); + cntl.http_request().uri() = "/v0/f.flv"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"f.flv\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ("{\"message\":\"f.flv_v1\"}", cntl.response_attachment()); + ASSERT_EQ(11, service_v1.ncalled.load()); + + // matched nothing + cntl.Reset(); + cntl.http_request().uri() = "/v6/ech/1.ts"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"1.ts\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_TRUE(cntl.Failed()); + + //Stop the server. + server1.Stop(0); + server1.Join(); + + ASSERT_EQ(0, server10.Start(port, NULL)); + + // access v1.Echo via /v1/echo. + cntl.Reset(); + cntl.http_request().uri() = "/v1/echo"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"foo\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(12, service_v1.ncalled.load()); + ASSERT_EQ("{\"message\":\"foo_v1\"}", cntl.response_attachment()); + + // access v1.Echo via default url + cntl.Reset(); + cntl.http_request().uri() = "/EchoService/Echo"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"foo\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(13, service_v1.ncalled.load()); + ASSERT_EQ("{\"message\":\"foo_v1\"}", cntl.response_attachment()); + + server10.Stop(0); + server10.Join(); + + // Removing the service should update _global_restful_map. + ASSERT_EQ(0, server1.RemoveService(&service_v1)); + ASSERT_EQ(0u, server1.service_count()); + ASSERT_TRUE(server1._global_restful_map); // deleted in dtor. + ASSERT_EQ(0u, server1._global_restful_map->size()); +} + +TEST_F(ServerTest, http_error_code) { + brpc::policy::FLAGS_use_http_error_code = true; + + const int port = 9200; + // missing_required_fields -> brpc::EREQUEST + { + brpc::Server server1; + EchoServiceV1 service_v1; + ASSERT_EQ(0, server1.AddService(&service_v1, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server1.Start(port, NULL)); + + brpc::Channel http_channel; + brpc::ChannelOptions chan_options; + chan_options.protocol = "http"; + ASSERT_EQ(0, http_channel.Init("0.0.0.0", port, &chan_options)); + brpc::Controller cntl; + cntl.http_request().uri() = "/EchoService/Echo"; + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_TRUE(cntl.Failed()); + ASSERT_EQ(brpc::EREQUEST, cntl.ErrorCode()); + LOG(INFO) << cntl.ErrorText(); + ASSERT_EQ(brpc::HTTP_STATUS_BAD_REQUEST, cntl.http_response().status_code()); + ASSERT_EQ(0, service_v1.ncalled.load()); + } + + // disallow_http_body_to_pb -> brpc::ERESPONSE + { + brpc::Server server1; + EchoServiceV1 service_v1; + brpc::ServiceOptions svc_opt; + svc_opt.allow_http_body_to_pb = false; + svc_opt.restful_mappings = "/access_echo1=>Echo"; + ASSERT_EQ(0, server1.AddService(&service_v1, svc_opt)); + ASSERT_EQ(0, server1.Start(port, NULL)); + brpc::Channel http_channel; + brpc::ChannelOptions chan_options; + chan_options.protocol = "http"; + ASSERT_EQ(0, http_channel.Init("0.0.0.0", port, &chan_options)); + brpc::Controller cntl; + cntl.http_request().uri() = "/access_echo1"; + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_TRUE(cntl.Failed()); + ASSERT_EQ(brpc::ERESPONSE, cntl.ErrorCode()); + ASSERT_EQ(brpc::HTTP_STATUS_INTERNAL_SERVER_ERROR, + cntl.http_response().status_code()); + ASSERT_EQ(1, service_v1.ncalled.load()); + } + + // restful_mapping -> brpc::ENOMETHOD + { + brpc::Server server1; + EchoServiceV1 service_v1; + ASSERT_EQ(0u, server1.service_count()); + ASSERT_EQ(0, server1.AddService( + &service_v1, + brpc::SERVER_DOESNT_OWN_SERVICE, + "/v1/echo/ => Echo," + + // Map another path to the same method is ok. + "/v3/echo => Echo," + + // end with wildcard + "/v2/echo/* => Echo," + + // single-component path should be OK + "/v4_echo => Echo," + + // heading slash can be ignored + " v5/echo => Echo," + + // with or without wildcard can coexist. + " /v6/echo => Echo," + " /v6/echo/* => Echo2," + " /v6/abc/*/def => Echo3," + " /v6/echo/*.flv => Echo4," + " /v6/*.flv => Echo5," + " *.flv => Echo," + )); + ASSERT_EQ(1u, server1.service_count()); + ASSERT_TRUE(server1._global_restful_map); + ASSERT_EQ(1UL, server1._global_restful_map->size()); + + ASSERT_EQ(0, server1.Start(port, NULL)); + brpc::Channel http_channel; + brpc::ChannelOptions chan_options; + chan_options.protocol = "http"; + ASSERT_EQ(0, http_channel.Init("0.0.0.0", port, &chan_options)); + brpc::Controller cntl; + cntl.http_request().uri() = "/v3/echo/anything"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"foo\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_TRUE(cntl.Failed()); + ASSERT_EQ(brpc::ENOMETHOD, cntl.ErrorCode()); + LOG(INFO) << "Expected error: " << cntl.ErrorText(); + ASSERT_EQ(0, service_v1.ncalled.load()); + } + + // max_concurrency -> brpc::ELIMIT + { + brpc::Server server1; + EchoServiceImpl service1; + ASSERT_EQ(0, server1.AddService(&service1, brpc::SERVER_DOESNT_OWN_SERVICE)); + server1.MaxConcurrencyOf("test.EchoService.Echo") = 1; + ASSERT_EQ(1, server1.MaxConcurrencyOf("test.EchoService.Echo")); + server1.MaxConcurrencyOf(&service1, "Echo") = 2; + ASSERT_EQ(2, server1.MaxConcurrencyOf(&service1, "Echo")); + + ASSERT_EQ(0, server1.Start(port, NULL)); + brpc::Channel http_channel; + brpc::ChannelOptions chan_options; + chan_options.protocol = "http"; + ASSERT_EQ(0, http_channel.Init("0.0.0.0", port, &chan_options)); + + brpc::Channel normal_channel; + ASSERT_EQ(0, normal_channel.Init("0.0.0.0", port, NULL)); + test::EchoService_Stub stub(&normal_channel); + + brpc::Controller cntl1; + cntl1.http_request().uri() = "/EchoService/Echo"; + cntl1.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl1.request_attachment().append("{\"message\":\"hello\",\"sleep_us\":100000}"); + http_channel.CallMethod(NULL, &cntl1, NULL, NULL, brpc::DoNothing()); + + brpc::Controller cntl2; + test::EchoRequest req; + test::EchoResponse res; + req.set_message("hello"); + req.set_sleep_us(100000); + stub.Echo(&cntl2, &req, &res, brpc::DoNothing()); + + bthread_usleep(20000); + LOG(INFO) << "Send other requests"; + + brpc::Controller cntl3; + cntl3.http_request().uri() = "/EchoService/Echo"; + cntl3.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl3.request_attachment().append("{\"message\":\"hello\"}"); + http_channel.CallMethod(NULL, &cntl3, NULL, NULL, NULL); + ASSERT_TRUE(cntl3.Failed()); + ASSERT_EQ(brpc::ELIMIT, cntl3.ErrorCode()); + ASSERT_EQ(brpc::HTTP_STATUS_SERVICE_UNAVAILABLE, cntl3.http_response().status_code()); + + brpc::Join(cntl1.call_id()); + brpc::Join(cntl2.call_id()); + ASSERT_FALSE(cntl1.Failed()) << cntl1.ErrorText(); + ASSERT_FALSE(cntl2.Failed()) << cntl2.ErrorText(); + } + + brpc::policy::FLAGS_use_http_error_code = false; +} + +TEST_F(ServerTest, conflict_name_between_restful_mapping_and_builtin) { + const int port = 9200; + EchoServiceV1 service_v1; + + brpc::Server server1; + ASSERT_EQ(0u, server1.service_count()); + ASSERT_EQ(0, server1.AddService( + &service_v1, + brpc::SERVER_DOESNT_OWN_SERVICE, + "/status/hello => Echo")); + ASSERT_EQ(1u, server1.service_count()); + ASSERT_TRUE(server1._global_restful_map == NULL); + + ASSERT_EQ(-1, server1.Start(port, NULL)); +} + +TEST_F(ServerTest, restful_mapping_is_tried_after_others) { + const int port = 9200; + EchoServiceV1 service_v1; + + brpc::Server server1; + ASSERT_EQ(0u, server1.service_count()); + ASSERT_EQ(0, server1.AddService( + &service_v1, + brpc::SERVER_DOESNT_OWN_SERVICE, + "* => Echo")); + ASSERT_EQ(1u, server1.service_count()); + ASSERT_TRUE(server1._global_restful_map); + ASSERT_EQ(1UL, server1._global_restful_map->size()); + + ASSERT_EQ(0, server1.Start(port, NULL)); + + brpc::Channel http_channel; + brpc::ChannelOptions chan_options; + chan_options.protocol = "http"; + ASSERT_EQ(0, http_channel.Init("0.0.0.0", port, &chan_options)); + + // accessing /status should be OK. + brpc::Controller cntl; + cntl.http_request().uri() = "/status"; + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_TRUE(cntl.response_attachment().to_string().find( + service_v1.GetDescriptor()->full_name()) != std::string::npos) + << "body=" << cntl.response_attachment(); + + // reject /EchoService/Echo + cntl.Reset(); + cntl.http_request().uri() = "/EchoService/Echo"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"foo\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_TRUE(cntl.Failed()); + ASSERT_EQ(0, service_v1.ncalled.load()); + + // Hit restful map + cntl.Reset(); + cntl.http_request().uri() = "/non_exist"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.request_attachment().append("{\"message\":\"foo\"}"); + http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(1, service_v1.ncalled.load()); + ASSERT_EQ("{\"message\":\"foo_v1\"}", cntl.response_attachment()); +; + //Stop the server. + server1.Stop(0); + server1.Join(); + + // Removing the service should update _global_restful_map. + ASSERT_EQ(0, server1.RemoveService(&service_v1)); + ASSERT_EQ(0u, server1.service_count()); + ASSERT_TRUE(server1._global_restful_map); // deleted in dtor. + ASSERT_EQ(0u, server1._global_restful_map->size()); + +} + +TEST_F(ServerTest, add_remove_service) { + brpc::Server server; + EchoServiceImpl echo_svc; + ASSERT_EQ(0, server.AddService( + &echo_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + // Duplicate + ASSERT_EQ(-1, server.AddService( + &echo_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_TRUE(server.FindServiceByName( + test::EchoService::descriptor()->name()) == &echo_svc); + ASSERT_TRUE(server.FindServiceByFullName( + test::EchoService::descriptor()->full_name()) == &echo_svc); + ASSERT_TRUE(NULL == server.FindServiceByFullName( + test::EchoService::descriptor()->name())); + + butil::EndPoint ep; + ASSERT_EQ(0, str2endpoint("127.0.0.1:8613", &ep)); + ASSERT_EQ(0, server.Start(ep, NULL)); + + ASSERT_EQ(1ul, server.service_count()); + ASSERT_TRUE(server.first_service() == &echo_svc); + ASSERT_TRUE(server.FindServiceByName( + test::EchoService::descriptor()->name()) == &echo_svc); + // Can't add/remove service while running + ASSERT_EQ(-1, server.AddService( + &echo_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(-1, server.RemoveService(&echo_svc)); + + ASSERT_EQ(0, server.Stop(0)); + ASSERT_EQ(0, server.Join()); + + ASSERT_EQ(0, server.RemoveService(&echo_svc)); + ASSERT_EQ(0ul, server.service_count()); + EchoServiceImpl* svc_on_heap = new EchoServiceImpl(); + ASSERT_EQ(0, server.AddService(svc_on_heap, + brpc::SERVER_OWNS_SERVICE)); + ASSERT_EQ(0, server.RemoveService(svc_on_heap)); + ASSERT_TRUE(g_delete); + + server.ClearServices(); + ASSERT_EQ(0ul, server.service_count()); +} + +void SendSleepRPC(butil::EndPoint ep, int sleep_ms, bool succ) { + brpc::Channel channel; + ASSERT_EQ(0, channel.Init(ep, NULL)); + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(EXP_REQUEST); + if (sleep_ms > 0) { + req.set_sleep_us(sleep_ms * 1000); + } + test::EchoService_Stub stub(&channel); + stub.Echo(&cntl, &req, &res, NULL); + if (succ) { + EXPECT_FALSE(cntl.Failed()) << cntl.ErrorText() + << " latency=" << cntl.latency_us(); + } else { + EXPECT_TRUE(cntl.Failed()); + } +} + +TEST_F(ServerTest, close_idle_connections) { + butil::EndPoint ep; + brpc::Server server; + brpc::ServerOptions opt; + opt.idle_timeout_sec = 1; + ASSERT_EQ(0, str2endpoint("127.0.0.1:9776", &ep)); + ASSERT_EQ(0, server.Start(ep, &opt)); + + const int cfd = tcp_connect(ep, NULL); + ASSERT_GT(cfd, 0); + usleep(10000); + brpc::ServerStatistics stat; + server.GetStat(&stat); + ASSERT_EQ(1ul, stat.connection_count); + + usleep(2500000); + server.GetStat(&stat); + ASSERT_EQ(0ul, stat.connection_count); +} + +TEST_F(ServerTest, logoff_and_multiple_start) { + butil::Timer timer; + butil::EndPoint ep; + EchoServiceImpl echo_svc; + brpc::Server server; + ASSERT_EQ(0, server.AddService(&echo_svc, + brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, str2endpoint("127.0.0.1:9876", &ep)); + + // Server::Stop(-1) + { + ASSERT_EQ(0, server.Start(ep, NULL)); + bthread_t tid; + const int64_t old_count = echo_svc.count.load(butil::memory_order_relaxed); + google::protobuf::Closure* thrd_func = + brpc::NewCallback(SendSleepRPC, ep, 100, true); + EXPECT_EQ(0, bthread_start_background(&tid, NULL, RunClosure, thrd_func)); + while (echo_svc.count.load(butil::memory_order_relaxed) == old_count) { + bthread_usleep(1000); + } + timer.start(); + ASSERT_EQ(0, server.Stop(-1)); + ASSERT_EQ(0, server.Join()); + timer.stop(); + EXPECT_TRUE(labs(timer.m_elapsed() - 100) < 15) << timer.m_elapsed(); + bthread_join(tid, NULL); + } + + // Server::Stop(0) + { + ++ep.port; + ASSERT_EQ(0, server.Start(ep, NULL)); + bthread_t tid; + const int64_t old_count = echo_svc.count.load(butil::memory_order_relaxed); + google::protobuf::Closure* thrd_func = + brpc::NewCallback(SendSleepRPC, ep, 100, true); + EXPECT_EQ(0, bthread_start_background(&tid, NULL, RunClosure, thrd_func)); + while (echo_svc.count.load(butil::memory_order_relaxed) == old_count) { + bthread_usleep(1000); + } + + timer.start(); + ASSERT_EQ(0, server.Stop(0)); + ASSERT_EQ(0, server.Join()); + timer.stop(); + // Assertion will fail since EchoServiceImpl::Echo is holding + // additional reference to the `Socket' + // EXPECT_TRUE(timer.m_elapsed() < 15) << timer.m_elapsed(); + bthread_join(tid, NULL); + } + + // Server::Stop(timeout) where timeout < g_sleep_ms + { + ++ep.port; + ASSERT_EQ(0, server.Start(ep, NULL)); + bthread_t tid; + const int64_t old_count = echo_svc.count.load(butil::memory_order_relaxed); + google::protobuf::Closure* thrd_func = + brpc::NewCallback(SendSleepRPC, ep, 100, true); + EXPECT_EQ(0, bthread_start_background(&tid, NULL, RunClosure, thrd_func)); + while (echo_svc.count.load(butil::memory_order_relaxed) == old_count) { + bthread_usleep(1000); + } + + timer.start(); + ASSERT_EQ(0, server.Stop(50)); + ASSERT_EQ(0, server.Join()); + timer.stop(); + // Assertion will fail since EchoServiceImpl::Echo is holding + // additional reference to the `Socket' + // EXPECT_TRUE(labs(timer.m_elapsed() - 50) < 15) << timer.m_elapsed(); + bthread_join(tid, NULL); + } + + // Server::Stop(timeout) where timeout > g_sleep_ms + { + ++ep.port; + ASSERT_EQ(0, server.Start(ep, NULL)); + bthread_t tid; + const int64_t old_count = echo_svc.count.load(butil::memory_order_relaxed); + google::protobuf::Closure* thrd_func = + brpc::NewCallback(SendSleepRPC, ep, 100, true); + EXPECT_EQ(0, bthread_start_background(&tid, NULL, RunClosure, thrd_func)); + while (echo_svc.count.load(butil::memory_order_relaxed) == old_count) { + bthread_usleep(1000); + } + timer.start(); + ASSERT_EQ(0, server.Stop(1000)); + ASSERT_EQ(0, server.Join()); + timer.stop(); + EXPECT_TRUE(labs(timer.m_elapsed() - 100) < 15) << timer.m_elapsed(); + bthread_join(tid, NULL); + } +} + +void SendMultipleRPC(butil::EndPoint ep, int count) { + brpc::Channel channel; + EXPECT_EQ(0, channel.Init(ep, NULL)); + + for (int i = 0; i < count; ++i) { + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(EXP_REQUEST); + test::EchoService_Stub stub(&channel); + stub.Echo(&cntl, &req, &res, NULL); + + EXPECT_EQ(EXP_RESPONSE, res.message()) << cntl.ErrorText(); + } +} + +TEST_F(ServerTest, serving_requests) { + EchoServiceImpl echo_svc; + brpc::Server server; + ASSERT_EQ(0, server.AddService(&echo_svc, + brpc::SERVER_DOESNT_OWN_SERVICE)); + butil::EndPoint ep; + ASSERT_EQ(0, str2endpoint("127.0.0.1:8613", &ep)); + ASSERT_EQ(0, server.Start(ep, NULL)); + + const int NUM = 1; + const int COUNT = 1; + pthread_t tids[NUM]; + for (int i = 0; i < NUM; ++i) { + google::protobuf::Closure* thrd_func = + brpc::NewCallback(SendMultipleRPC, ep, COUNT); + EXPECT_EQ(0, pthread_create(&tids[i], NULL, RunClosure, thrd_func)); + } + for (int i = 0; i < NUM; ++i) { + pthread_join(tids[i], NULL); + } + ASSERT_EQ(NUM * COUNT, echo_svc.count.load()); + ASSERT_EQ(0, server.Stop(0)); + ASSERT_EQ(0, server.Join()); +} + +TEST_F(ServerTest, create_pid_file) { + { + brpc::Server server; + server._options.pid_file = "./pid_dir/sub_dir/./.server.pid"; + server.PutPidFileIfNeeded(); + pid_t pid = getpid(); + std::ifstream fin("./pid_dir/sub_dir/.server.pid"); + ASSERT_TRUE(fin.is_open()); + pid_t pid_from_file; + fin >> pid_from_file; + ASSERT_EQ(pid, pid_from_file); + } + std::ifstream fin("./pid_dir/sub_dir/.server.pid"); + ASSERT_FALSE(fin.is_open()); +} + +TEST_F(ServerTest, range_start) { + const int START_PORT = 8713; + const int END_PORT = 8719; + butil::fd_guard listen_fds[END_PORT - START_PORT]; + butil::EndPoint point; + for (int i = START_PORT; i < END_PORT; ++i) { + point.port = i; + listen_fds[i - START_PORT].reset(butil::tcp_listen(point)); + } + + brpc::Server server; + EXPECT_EQ(-1, server.Start("0.0.0.0", brpc::PortRange(START_PORT, END_PORT - 1), NULL)); + // note: add an extra port after END_PORT to detect the bug that the + // probing does not stop at the first valid port(END_PORT). + EXPECT_EQ(0, server.Start("0.0.0.0", brpc::PortRange(START_PORT, END_PORT + 1/*note*/), NULL)); + EXPECT_EQ(END_PORT, server.listen_address().port); +} + +TEST_F(ServerTest, add_builtin_service) { + TestAddBuiltinService(brpc::IndexService::descriptor()); + TestAddBuiltinService(brpc::VersionService::descriptor()); + TestAddBuiltinService(brpc::HealthService::descriptor()); + TestAddBuiltinService(brpc::StatusService::descriptor()); + TestAddBuiltinService(brpc::ConnectionsService::descriptor()); + TestAddBuiltinService(brpc::BadMethodService::descriptor()); + TestAddBuiltinService(brpc::ListService::descriptor()); + if (brpc::FLAGS_enable_threads_service) { + TestAddBuiltinService(brpc::ThreadsService::descriptor()); + } +#if !BRPC_WITH_GLOG + TestAddBuiltinService(brpc::VLogService::descriptor()); +#endif + TestAddBuiltinService(brpc::FlagsService::descriptor()); + TestAddBuiltinService(brpc::VarsService::descriptor()); + TestAddBuiltinService(brpc::RpczService::descriptor()); + TestAddBuiltinService(brpc::PProfService::descriptor()); + if (brpc::FLAGS_enable_dir_service) { + TestAddBuiltinService(brpc::DirService::descriptor()); + } +} + +TEST_F(ServerTest, base64_to_string) { + // We test two cases as following. If these two tests can be passed, we + // can prove that the pb_bytes_to_base64 flag is working in both client side + // and server side. + // 1. Client sets pb_bytes_to_base64 and server also sets pb_bytes_to_base64 + // 2. Client sets pb_bytes_to_base64, but server doesn't set pb_bytes_to_base64 + for (int i = 0; i < 2; ++i) { + brpc::Server server; + EchoServiceImpl echo_svc; + brpc::ServiceOptions service_opt; + service_opt.pb_bytes_to_base64 = (i == 0); + ASSERT_EQ(0, server.AddService(&echo_svc, + service_opt)); + ASSERT_EQ(0, server.Start(8613, NULL)); + + brpc::Channel chan; + brpc::ChannelOptions opt; + opt.protocol = brpc::PROTOCOL_HTTP; + ASSERT_EQ(0, chan.Init("localhost:8613", &opt)); + brpc::Controller cntl; + cntl.http_request().uri() = "/EchoService/BytesEcho" + + butil::string_printf("%d", i + 1); + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.http_request().set_content_type("application/json"); + cntl.set_pb_bytes_to_base64(true); + test::BytesRequest req; + test::BytesResponse res; + req.set_databytes(EXP_REQUEST); + chan.CallMethod(NULL, &cntl, &req, &res, NULL); + EXPECT_FALSE(cntl.Failed()); + EXPECT_EQ(EXP_REQUEST, res.databytes()); + server.Stop(0); + server.Join(); + } +} + +TEST_F(ServerTest, single_repeated_to_array) { + for (int i = 0; i < 2; ++i) { + brpc::Server server; + EchoServiceImpl echo_svc; + brpc::ServiceOptions service_opt; + service_opt.pb_single_repeated_to_array = (i == 0); + + ASSERT_EQ(0, server.AddService(&echo_svc, service_opt)); + ASSERT_EQ(0, server.Start(8613, NULL)); + + for (int j = 0; j < 2; ++j) { + brpc::Channel chan; + brpc::ChannelOptions opt; + opt.protocol = brpc::PROTOCOL_HTTP; + ASSERT_EQ(0, chan.Init("localhost:8613", &opt)); + brpc::Controller cntl; + cntl.http_request().uri() = "/EchoService/ComboEcho"; + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.http_request().set_content_type("application/json"); + cntl.set_pb_single_repeated_to_array(j == 0); + test::ComboRequest req; + req.add_requests()->set_message("foo"); + req.add_requests()->set_message("bar"); + + test::ComboResponse res; + chan.CallMethod(NULL, &cntl, &req, &res, NULL); + if (i == j) { + EXPECT_FALSE(cntl.Failed()); + EXPECT_EQ(res.responses_size(), req.requests_size()); + for (int k = 0; k < req.requests_size(); ++k) { + EXPECT_EQ(req.requests(k).message(), res.responses(k).message()); + } + } else { + EXPECT_TRUE(cntl.Failed()); + } + } + + server.Stop(0); + server.Join(); + } +} + +TEST_F(ServerTest, too_big_message) { + EchoServiceImpl echo_svc; + brpc::Server server; + ASSERT_EQ(0, server.AddService(&echo_svc, + brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start(8613, NULL)); + +#if !BRPC_WITH_GLOG + logging::StringSink log_str; + logging::LogSink* old_sink = logging::SetLogSink(&log_str); +#endif + + brpc::Channel chan; + ASSERT_EQ(0, chan.Init("localhost:8613", NULL)); + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.mutable_message()->resize(brpc::FLAGS_max_body_size + 1); + test::EchoService_Stub stub(&chan); + stub.Echo(&cntl, &req, &res, NULL); + EXPECT_TRUE(cntl.Failed()); + +#if !BRPC_WITH_GLOG + ASSERT_EQ(&log_str, logging::SetLogSink(old_sink)); + std::ostringstream expected_log; + expected_log << " is bigger than " << brpc::FLAGS_max_body_size + << " bytes, the connection will be closed." + " Set max_body_size to allow bigger messages"; + ASSERT_NE(std::string::npos, log_str.find(expected_log.str())); +#endif + + server.Stop(0); + server.Join(); +} + +TEST_F(ServerTest, max_concurrency) { + const int port = 9200; + brpc::Server server1; + EchoServiceImpl service1; + ASSERT_EQ(0, server1.AddService(&service1, brpc::SERVER_DOESNT_OWN_SERVICE)); + server1.MaxConcurrencyOf("test.EchoService.Echo") = 1; + ASSERT_EQ(1, server1.MaxConcurrencyOf("test.EchoService.Echo")); + server1.MaxConcurrencyOf(&service1, "Echo") = 2; + ASSERT_EQ(2, server1.MaxConcurrencyOf(&service1, "Echo")); + + ASSERT_EQ(0, server1.Start(port, NULL)); + brpc::Channel http_channel; + brpc::ChannelOptions chan_options; + chan_options.protocol = "http"; + ASSERT_EQ(0, http_channel.Init("0.0.0.0", port, &chan_options)); + + brpc::Channel normal_channel; + ASSERT_EQ(0, normal_channel.Init("0.0.0.0", port, NULL)); + test::EchoService_Stub stub(&normal_channel); + + brpc::Controller cntl1; + cntl1.http_request().uri() = "/EchoService/Echo"; + cntl1.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl1.request_attachment().append("{\"message\":\"hello\",\"sleep_us\":100000}"); + http_channel.CallMethod(NULL, &cntl1, NULL, NULL, brpc::DoNothing()); + + brpc::Controller cntl2; + test::EchoRequest req; + test::EchoResponse res; + req.set_message("hello"); + req.set_sleep_us(100000); + stub.Echo(&cntl2, &req, &res, brpc::DoNothing()); + + bthread_usleep(20000); + LOG(INFO) << "Send other requests"; + + brpc::Controller cntl3; + cntl3.http_request().uri() = "/EchoService/Echo"; + cntl3.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl3.request_attachment().append("{\"message\":\"hello\"}"); + http_channel.CallMethod(NULL, &cntl3, NULL, NULL, NULL); + ASSERT_TRUE(cntl3.Failed()); + ASSERT_EQ(brpc::EHTTP, cntl3.ErrorCode()); + ASSERT_EQ(brpc::HTTP_STATUS_SERVICE_UNAVAILABLE, cntl3.http_response().status_code()); + + brpc::Controller cntl4; + req.clear_sleep_us(); + stub.Echo(&cntl4, &req, NULL, NULL); + ASSERT_TRUE(cntl4.Failed()); + ASSERT_EQ(brpc::ELIMIT, cntl4.ErrorCode()); + + brpc::Join(cntl1.call_id()); + brpc::Join(cntl2.call_id()); + ASSERT_FALSE(cntl1.Failed()) << cntl1.ErrorText(); + ASSERT_FALSE(cntl2.Failed()) << cntl2.ErrorText(); + + cntl3.Reset(); + cntl3.http_request().uri() = "/EchoService/Echo"; + cntl3.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl3.request_attachment().append("{\"message\":\"hello\"}"); + http_channel.CallMethod(NULL, &cntl3, NULL, NULL, NULL); + ASSERT_FALSE(cntl3.Failed()) << cntl3.ErrorText(); + + cntl4.Reset(); + stub.Echo(&cntl4, &req, NULL, NULL); + ASSERT_FALSE(cntl4.Failed()) << cntl4.ErrorText(); +} + +TEST_F(ServerTest, user_fields) { + const int port = 9200; + brpc::Server server; + EchoServiceImpl service; + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start(port, NULL)); + + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("0.0.0.0", port, NULL)); + test::EchoService_Stub stub(&channel); + + brpc::Controller cntl; + cntl.request_user_fields()->insert(EXP_USER_FIELD_KEY, EXP_USER_FIELD_VALUE); + test::EchoRequest req; + test::EchoResponse res; + req.set_message("hello"); + stub.Echo(&cntl, &req, &res, NULL); + + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_TRUE(cntl.has_response_user_fields()); + ASSERT_TRUE(!cntl.response_user_fields()->empty()); + std::string* val = cntl.response_user_fields()->seek(EXP_USER_FIELD_KEY); + ASSERT_TRUE(val != NULL); + ASSERT_EQ(*val, EXP_USER_FIELD_VALUE); +} + +class BaiduMasterServiceImpl : public brpc::BaiduMasterService { +public: + void ProcessRpcRequest(brpc::Controller* cntl, + const brpc::SerializedRequest* request, + brpc::SerializedResponse* response, + ::google::protobuf::Closure* done) override { + // This object helps you to call done->Run() in RAII style. If you need + // to process the request asynchronously, pass done_guard.release(). + brpc::ClosureGuard done_guard(done); + ASSERT_NE(nullptr, cntl->sampled_request()); + ASSERT_TRUE(cntl->sampled_request()->meta.has_service_name()); + ASSERT_EQ(test::EchoService::descriptor()->full_name(), + cntl->sampled_request()->meta.service_name()); + ASSERT_TRUE(cntl->sampled_request()->meta.has_method_name()); + ASSERT_EQ("Echo", cntl->sampled_request()->meta.method_name()); + brpc::ContentType content_type = cntl->request_content_type(); + brpc::CompressType compress_type = cntl->request_compress_type(); + brpc::ChecksumType checksum_type = cntl->request_checksum_type(); + + test::EchoRequest echo_request; + test::EchoResponse echo_response; + ASSERT_TRUE(brpc::policy::DeserializeRpcMessage( + request->serialized_data(), *cntl, content_type, compress_type, + checksum_type, &echo_request)); + ASSERT_EQ(EXP_REQUEST, echo_request.message()); + ASSERT_EQ(EXP_REQUEST, cntl->request_attachment().to_string()); + + content_type = (brpc::ContentType)_content_type_index; + compress_type = (brpc::CompressType)_compress_type_index; + ++_compress_type_index; + if (_compress_type_index == brpc::COMPRESS_TYPE_LZ4) { + ++_compress_type_index; + } + if (_compress_type_index > brpc::CompressType_MAX) { + _compress_type_index = brpc::CompressType_MIN; + + ++_content_type_index; + if (_content_type_index > brpc::ContentType_MAX) { + _content_type_index = brpc::ContentType_MIN; + } + } + + cntl->set_response_content_type(content_type); + cntl->set_response_compress_type(compress_type); + cntl->set_response_checksum_type(checksum_type); + cntl->response_attachment().append(EXP_RESPONSE); + echo_response.set_message(EXP_RESPONSE); + ASSERT_TRUE(brpc::policy::SerializeRpcMessage( + echo_response, *cntl, content_type, compress_type, checksum_type, + &response->serialized_data())); + } +private: + int _content_type_index = brpc::ContentType_MIN; + int _compress_type_index = brpc::CompressType_MIN; +}; + +void TestBaiduMasterService(brpc::Channel& channel, brpc::CompressType compress_type) { + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(EXP_REQUEST); + cntl.request_attachment().append(EXP_REQUEST); + cntl.set_request_compress_type(compress_type); + test::EchoService_Stub stub(&channel); + stub.Echo(&cntl, &req, &res, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(EXP_RESPONSE, res.message()); + ASSERT_EQ(EXP_RESPONSE, cntl.response_attachment().to_string()); +} + +TEST_F(ServerTest, baidu_master_service) { + butil::EndPoint ep; + ASSERT_EQ(0, str2endpoint("127.0.0.1:8613", &ep)); + brpc::Server server; + EchoServiceImpl service; + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + brpc::ServerOptions server_options; + server_options.baidu_master_service = new BaiduMasterServiceImpl; + ASSERT_EQ(0, server.Start(ep, &server_options)); + + brpc::Channel channel; + brpc::ChannelOptions channel_options; + channel_options.protocol = "baidu_std"; + ASSERT_EQ(0, channel.Init(ep, &channel_options)); + + for (int i = 0; i < 10; ++i) { + TestBaiduMasterService(channel, brpc::COMPRESS_TYPE_ZLIB); + TestBaiduMasterService(channel, brpc::COMPRESS_TYPE_GZIP); + TestBaiduMasterService(channel, brpc::COMPRESS_TYPE_SNAPPY); + TestBaiduMasterService(channel, brpc::COMPRESS_TYPE_NONE); + } + + ASSERT_EQ(0, server.Stop(0)); + ASSERT_EQ(0, server.Join()); +} + +void TestGenericCall(brpc::Channel& channel, brpc::ContentType content_type, + brpc::CompressType compress_type, + brpc::ChecksumType checksum_type) { + LOG(INFO) << "TestGenericCall: content_type=" << content_type + << ", compress_type=" << compress_type + << ", checksum_type=" << checksum_type; + test::EchoRequest request; + test::EchoResponse response; + request.set_message(EXP_REQUEST); + + brpc::SerializedResponse serialized_response; + brpc::SerializedRequest serialized_request; + + brpc::Controller cntl; + cntl.set_request_content_type(content_type); + cntl.set_request_compress_type(compress_type); + cntl.set_request_checksum_type(checksum_type); + cntl.request_attachment().append(EXP_REQUEST); + + std::string error; + ASSERT_TRUE(brpc::policy::SerializeRpcMessage( + request, cntl, content_type, compress_type, checksum_type, + &serialized_request.serialized_data())); + auto sampled_request = new (std::nothrow) brpc::SampledRequest(); + sampled_request->meta.set_service_name( + test::EchoService::descriptor()->full_name()); + sampled_request->meta.set_method_name( + test::EchoService::descriptor()->FindMethodByName("Echo")->name()); + cntl.reset_sampled_request(sampled_request); + + channel.CallMethod(NULL, &cntl, &serialized_request, &serialized_response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + + ASSERT_TRUE(brpc::policy::DeserializeRpcMessage( + serialized_response.serialized_data(), cntl, + cntl.response_content_type(), cntl.response_compress_type(), + cntl.response_checksum_type(), &response)); + ASSERT_EQ(EXP_RESPONSE, response.message()); + ASSERT_EQ(EXP_RESPONSE, cntl.response_attachment().to_string()); +} + +TEST_F(ServerTest, generic_call) { + butil::EndPoint ep; + ASSERT_EQ(0, str2endpoint("127.0.0.1:8613", &ep)); + brpc::Server server; + EchoServiceImpl service; + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + brpc::ServerOptions server_options; + server_options.baidu_master_service = new BaiduMasterServiceImpl; + ASSERT_EQ(0, server.Start(ep, &server_options)); + + brpc::Channel channel; + brpc::ChannelOptions channel_options; + channel_options.protocol = "baidu_std"; + ASSERT_EQ(0, channel.Init(ep, &channel_options)); + + TestGenericCall(channel, brpc::CONTENT_TYPE_PB, brpc::COMPRESS_TYPE_ZLIB, + brpc::CHECKSUM_TYPE_CRC32C); + TestGenericCall(channel, brpc::CONTENT_TYPE_PB, brpc::COMPRESS_TYPE_GZIP, + brpc::CHECKSUM_TYPE_CRC32C); + TestGenericCall(channel, brpc::CONTENT_TYPE_PB, brpc::COMPRESS_TYPE_SNAPPY, + brpc::CHECKSUM_TYPE_NONE); + TestGenericCall(channel, brpc::CONTENT_TYPE_PB, brpc::COMPRESS_TYPE_NONE, + brpc::CHECKSUM_TYPE_NONE); + + TestGenericCall(channel, brpc::CONTENT_TYPE_JSON, brpc::COMPRESS_TYPE_ZLIB, + brpc::CHECKSUM_TYPE_CRC32C); + TestGenericCall(channel, brpc::CONTENT_TYPE_JSON, brpc::COMPRESS_TYPE_GZIP, + brpc::CHECKSUM_TYPE_CRC32C); + TestGenericCall(channel, brpc::CONTENT_TYPE_JSON, + brpc::COMPRESS_TYPE_SNAPPY, brpc::CHECKSUM_TYPE_NONE); + TestGenericCall(channel, brpc::CONTENT_TYPE_JSON, brpc::COMPRESS_TYPE_NONE, + brpc::CHECKSUM_TYPE_NONE); + + TestGenericCall(channel, brpc::CONTENT_TYPE_PROTO_JSON, + brpc::COMPRESS_TYPE_ZLIB, brpc::CHECKSUM_TYPE_CRC32C); + TestGenericCall(channel, brpc::CONTENT_TYPE_PROTO_JSON, + brpc::COMPRESS_TYPE_GZIP, brpc::CHECKSUM_TYPE_CRC32C); + TestGenericCall(channel, brpc::CONTENT_TYPE_PROTO_JSON, + brpc::COMPRESS_TYPE_SNAPPY, brpc::CHECKSUM_TYPE_NONE); + TestGenericCall(channel, brpc::CONTENT_TYPE_PROTO_JSON, + brpc::COMPRESS_TYPE_NONE, brpc::CHECKSUM_TYPE_NONE); + + TestGenericCall(channel, brpc::CONTENT_TYPE_PROTO_TEXT, + brpc::COMPRESS_TYPE_ZLIB, brpc::CHECKSUM_TYPE_CRC32C); + TestGenericCall(channel, brpc::CONTENT_TYPE_PROTO_TEXT, + brpc::COMPRESS_TYPE_GZIP, brpc::CHECKSUM_TYPE_CRC32C); + TestGenericCall(channel, brpc::CONTENT_TYPE_PROTO_TEXT, + brpc::COMPRESS_TYPE_SNAPPY, brpc::CHECKSUM_TYPE_NONE); + TestGenericCall(channel, brpc::CONTENT_TYPE_PROTO_TEXT, + brpc::COMPRESS_TYPE_NONE, brpc::CHECKSUM_TYPE_NONE); + + ASSERT_EQ(0, server.Stop(0)); + ASSERT_EQ(0, server.Join()); +} + +struct DefaultRpcPBMessages : public brpc::RpcPBMessages { + DefaultRpcPBMessages() : request(NULL), response(NULL) {} + ::google::protobuf::Message* Request() override { return request; } + ::google::protobuf::Message* Response() override { return response; } + + ::google::protobuf::Message* request; + ::google::protobuf::Message* response; +}; + +class TestRpcPBMessageFactory : public brpc::RpcPBMessageFactory { +public: + brpc::RpcPBMessages* Get(const google::protobuf::Service& service, + const google::protobuf::MethodDescriptor& method) override { + auto messages = butil::get_object(); + auto request = butil::get_object(); + auto response = butil::get_object(); + request->clear_message(); + response->clear_message(); + messages->request = request; + messages->response = response; + return messages; + } + + void Return(brpc::RpcPBMessages* messages) override { + auto test_messages = static_cast(messages); + butil::return_object(static_cast(test_messages->request)); + butil::return_object(static_cast(test_messages->response)); + test_messages->request = NULL; + test_messages->response = NULL; + butil::return_object(test_messages); + } +}; + +TEST_F(ServerTest, rpc_pb_message_factory) { + butil::EndPoint ep; + ASSERT_EQ(0, str2endpoint("127.0.0.1:8613", &ep)); + brpc::Server server; + EchoServiceV1 service; + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + brpc::ServerOptions opt; + opt.rpc_pb_message_factory = new TestRpcPBMessageFactory; + ASSERT_EQ(0, server.Start(ep, &opt)); + + brpc::Channel baidu_chan; + brpc::ChannelOptions baidu_copt; + baidu_copt.protocol = "baidu_std"; + ASSERT_EQ(0, baidu_chan.Init(ep, &baidu_copt)); + for (int i = 0; i < 1000; ++i) { + brpc::Controller cntl; + v1::EchoRequest req; + v1::EchoResponse res; + req.set_message(EXP_REQUEST); + v1::EchoService_Stub stub(&baidu_chan); + stub.Echo(&cntl, &req, &res, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(EXP_REQUEST + "_v1", res.message()); + } + + brpc::Channel http_chan; + brpc::ChannelOptions http_copt; + http_copt.protocol = "http"; + ASSERT_EQ(0, http_chan.Init(ep, &http_copt)); + for (int i = 0; i < 1000; ++i) { + brpc::Controller cntl; + cntl.request_attachment().append( + butil::string_printf(R"({"message":"%s"})", EXP_REQUEST.c_str())); + v1::EchoService_Stub stub(&http_chan); + stub.Echo(&cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(butil::string_printf(R"({"message":"%s_v1"})", EXP_REQUEST.c_str()), + cntl.response_attachment().to_string()); + } + + ASSERT_EQ(0, server.Stop(0)); + ASSERT_EQ(0, server.Join()); +} + +TEST_F(ServerTest, arena_rpc_pb_message_factory) { + butil::EndPoint ep; + ASSERT_EQ(0, str2endpoint("127.0.0.1:8613", &ep)); + brpc::Server server; + EchoServiceV3 service; + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + brpc::ServerOptions opt; + opt.rpc_pb_message_factory = brpc::GetArenaRpcPBMessageFactory(); + ASSERT_EQ(0, server.Start(ep, &opt)); + + brpc::Channel baidu_chan; + brpc::ChannelOptions baidu_copt; + baidu_copt.protocol = "baidu_std"; + ASSERT_EQ(0, baidu_chan.Init(ep, &baidu_copt)); + for (int i = 0; i < 1000; ++i) { + brpc::Controller cntl; + v3::EchoRequest req; + v3::EchoResponse res; + req.set_message(EXP_REQUEST); + v3::EchoService_Stub stub(&baidu_chan); + stub.Echo(&cntl, &req, &res, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(EXP_RESPONSE, res.message()); + } + + brpc::Channel http_chan; + brpc::ChannelOptions http_copt; + http_copt.protocol = "http"; + ASSERT_EQ(0, http_chan.Init(ep, &http_copt)); + for (int i = 0; i < 1000; ++i) { + brpc::Controller cntl; + cntl.request_attachment().append( + butil::string_printf(R"({"message":"%s"})", EXP_REQUEST.c_str())); + v3::EchoService_Stub stub(&http_chan); + stub.Echo(&cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(butil::string_printf(R"({"message":"%s"})", EXP_RESPONSE.c_str()), + cntl.response_attachment().to_string()); + } + + ASSERT_EQ(0, server.Stop(0)); + ASSERT_EQ(0, server.Join()); +} + +void TestBaiduStdAuth(const butil::EndPoint& ep, + brpc::Controller& cntl, + int error_code, bool failed) { + brpc::Channel chan; + brpc::ChannelOptions copt; + copt.max_retry = 0; + copt.protocol = "baidu_std"; + ASSERT_EQ(0, chan.Init(ep, &copt)); + + test::EchoRequest req; + test::EchoResponse res; + req.set_message(EXP_REQUEST); + test::EchoService_Stub stub(&chan); + stub.Echo(&cntl, &req, &res, NULL); + ASSERT_EQ(cntl.Failed(), failed) << cntl.ErrorText(); + ASSERT_EQ(cntl.ErrorCode(), error_code); +} + +void TestHttpAuth(const butil::EndPoint& ep, + brpc::Controller& cntl, + int status_code, bool failed) { + brpc::Channel chan; + brpc::ChannelOptions copt; + copt.max_retry = 0; + copt.protocol = "http"; + ASSERT_EQ(0, chan.Init(ep, &copt)); + + cntl.http_request().uri() = "/EchoService/Echo"; + cntl.request_attachment().append(R"({"message": "hello"})"); + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + test::EchoService_Stub stub(&chan); + chan.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_EQ(cntl.Failed(), failed) << cntl.ErrorText(); + ASSERT_EQ(cntl.http_response().status_code(), status_code); +} + +TEST_F(ServerTest, auth) { + butil::EndPoint ep; + ASSERT_EQ(0, str2endpoint("127.0.0.1:8613", &ep)); + brpc::Server server; + EchoServiceImpl service; + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + MyAuthenticator auth; + brpc::ServerOptions opt; + opt.auth = &auth; + ASSERT_EQ(0, server.Start(ep, &opt)); + + brpc::Controller cntl; + TestBaiduStdAuth(ep, cntl, 0, false); + + g_verify_success = false; + cntl.Reset(); + TestBaiduStdAuth(ep, cntl, brpc::ERPCAUTH, true); + ASSERT_NE(cntl.ErrorText().find(g_unauthorized_error_text), std::string::npos); + + cntl.Reset(); + TestHttpAuth(ep, cntl, brpc::HTTP_STATUS_FORBIDDEN, true); + ASSERT_NE(cntl.response_attachment().to_string().find(g_unauthorized_error_text), + std::string::npos); + + g_verify_success = true; + cntl.Reset(); + cntl.http_request().SetHeader("Authorization", "123"); + TestHttpAuth(ep, cntl, brpc::HTTP_STATUS_OK, false); + + ASSERT_EQ(0, server.Stop(0)); + ASSERT_EQ(0, server.Join()); +} + +void TestClientHost(const butil::EndPoint& ep, + brpc::Controller& cntl, + int error_code, bool failed, + brpc::ChannelOptions& copt) { + brpc::Channel chan; + copt.max_retry = 0; + ASSERT_EQ(0, chan.Init(ep, &copt)); + + test::EchoRequest req; + test::EchoResponse res; + req.set_message(EXP_REQUEST); + test::EchoService_Stub stub(&chan); + stub.Echo(&cntl, &req, &res, NULL); + ASSERT_EQ(cntl.Failed(), failed) << cntl.ErrorText(); + ASSERT_EQ(cntl.ErrorCode(), error_code); +} + +TEST_F(ServerTest, bind_client_host_and_network_device) { + butil::EndPoint ep; + ASSERT_EQ(0, str2endpoint("127.0.0.1:8613", &ep)); + brpc::Server server; + EchoServiceImpl service; + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + brpc::ServerOptions opt; + ASSERT_EQ(0, server.Start(ep, &opt)); + + brpc::Controller cntl; + brpc::ChannelOptions copt; + copt.client_host = "localhost"; + copt.device_name = "lo"; + std::vector connection_types = { + brpc::CONNECTION_TYPE_SINGLE, + brpc::CONNECTION_TYPE_POOLED, + brpc::CONNECTION_TYPE_SHORT + }; + for (auto connect_type : connection_types) { + copt.connection_type = connect_type; + TestClientHost(ep, cntl, 0, false, copt); + cntl.Reset(); + } + + ASSERT_EQ(0, server.Stop(0)); + ASSERT_EQ(0, server.Join()); +} + +} //namespace diff --git a/test/brpc_snappy_compress_unittest.cpp b/test/brpc_snappy_compress_unittest.cpp new file mode 100644 index 0000000..94b54df --- /dev/null +++ b/test/brpc_snappy_compress_unittest.cpp @@ -0,0 +1,255 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: 2015/01/20 19:01:06 + +#include +#include "gperftools_helper.h" +#include "butil/third_party/snappy/snappy.h" +#include "butil/macros.h" +#include "butil/iobuf.h" +#include "butil/time.h" +#include "snappy_message.pb.h" +#include "brpc/policy/snappy_compress.h" +#include "brpc/policy/gzip_compress.h" + +typedef bool (*Compress)(const google::protobuf::Message&, butil::IOBuf*); +typedef bool (*Decompress)(const butil::IOBuf&, google::protobuf::Message*); + +inline void CompressMessage(const char* method_name, + int num, snappy_message::SnappyMessageProto& msg, + int len, Compress compress, Decompress decompress) { + butil::Timer timer; + size_t compression_length = 0; + int64_t total_compress_time = 0; + int64_t total_decompress_time = 0; + snappy_message::SnappyMessageProto new_msg; + for (int index = 0; index < num; index++) { + butil::IOBuf buf; + timer.start(); + ASSERT_TRUE(compress(msg, &buf)); + timer.stop(); + total_compress_time += timer.n_elapsed(); + compression_length += buf.length(); + timer.start(); + ASSERT_TRUE(decompress(buf, &new_msg)); + timer.stop(); + total_decompress_time += timer.n_elapsed(); + } + float compression_ratio = compression_length / (((double)num) * len); + printf("%20s%20d%20f%20f%30f%30f%29f%%\n", method_name, len, + total_compress_time/1000.0/num, total_decompress_time/1000.0/num, + 1000000000.0/1024/1024*num*len/total_compress_time, + 1000000000.0/1024/1024*num*len/total_decompress_time, + compression_ratio*100.0); +} + +static bool SnappyDecompressIOBuf(char* input, size_t len, butil::IOBuf* buf) { + size_t decompress_length; + if (!butil::snappy::GetUncompressedLength(input, len, &decompress_length)) { + return false; + } + char* output = new char[decompress_length]; + if (!butil::snappy::RawUncompress(input, len, output)) { + delete [] output; + return false; + } + buf->append(output, decompress_length); + delete [] output; + return true; +} + +class test_compress_method : public testing::Test {}; + +TEST_F(test_compress_method, snappy) { + snappy_message::SnappyMessageProto old_msg; + old_msg.set_text("Hello World!"); + old_msg.add_numbers(2); + old_msg.add_numbers(7); + old_msg.add_numbers(45); + butil::IOBuf buf; + ASSERT_TRUE(brpc::policy::SnappyCompress(old_msg, &buf)); + snappy_message::SnappyMessageProto new_msg; + ASSERT_TRUE(brpc::policy::SnappyDecompress(buf, &new_msg)); + ASSERT_TRUE(strcmp(new_msg.text().c_str(), "Hello World!") == 0); + ASSERT_TRUE(new_msg.numbers_size() == 3); + ASSERT_EQ(new_msg.numbers(0), 2); + ASSERT_EQ(new_msg.numbers(1), 7); + ASSERT_EQ(new_msg.numbers(2), 45); +} + +TEST_F(test_compress_method, snappy_iobuf) { + butil::IOBuf buf, output_buf, check_buf; + const char* test = "this is a test"; + buf.append(test, strlen(test)); + ASSERT_TRUE(brpc::policy::SnappyCompress(buf, &output_buf)); + ASSERT_TRUE(brpc::policy::SnappyDecompress(output_buf, &check_buf)); + ASSERT_STREQ(check_buf.to_string().c_str(), test); +} + +TEST_F(test_compress_method, mass_snappy) { + snappy_message::SnappyMessageProto old_msg; + int len = 12435; + char* text = new char[len + 1]; + for (int j = 0; j < len;) { + for (int i = 0; i < 26 && j < len; i++) { + text[j++] = 'a' + i; + } + for (int i = 0; i < 10 && j < len; i++) { + text[j++] = '0' + i; + } + } + text[len] = '\0'; + old_msg.set_text(text); + old_msg.add_numbers(2); + old_msg.add_numbers(7); + old_msg.add_numbers(45); + butil::IOBuf buf; + ProfilerStart("./snappy_compress.prof"); + ASSERT_TRUE(brpc::policy::SnappyCompress(old_msg, &buf)); + snappy_message::SnappyMessageProto new_msg; + ASSERT_TRUE(brpc::policy::SnappyDecompress(buf, &new_msg)); + ProfilerStop(); + ASSERT_TRUE(strcmp(new_msg.text().c_str(), text) == 0); + ASSERT_TRUE(new_msg.numbers_size() == 3); + ASSERT_EQ(new_msg.numbers(0), 2); + ASSERT_EQ(new_msg.numbers(1), 7); + ASSERT_EQ(new_msg.numbers(2), 45); + delete [] text; +} + +TEST_F(test_compress_method, snappy_test) { + int len = 200; + char* text = new char[len + 1]; + for (int j = 0; j < len;) { + for (int i = 0; i < 26 && j < len; i++) { + text[j++] = 'a' + i; + } + for (int i = 0; i < 10 && j < len; i++) { + text[j++] = '0' + i; + } + } + text[len] = '\0'; + butil::IOBuf buf; + std::string output; + std::string append_string; + ASSERT_TRUE(butil::snappy::Compress(text, len, &output)); + size_t com_len1 = output.size(); + const char* s_text = "123456"; + ASSERT_TRUE(butil::snappy::Compress(s_text, strlen(s_text), &append_string)); + output.append(append_string); + std::string uncompress_str; + std::string uncompress_str_t; + char* ptr = const_cast(output.c_str()); + ASSERT_TRUE(butil::snappy::Uncompress(ptr, com_len1, &uncompress_str)); + ptr = const_cast(append_string.c_str()); + ASSERT_TRUE(butil::snappy::Uncompress(ptr, strlen(ptr), &uncompress_str_t)); + delete [] text; +} + +TEST_F(test_compress_method, throughput_compare) { + int len = 0; + int len_subs[] = {128, 1024, 16*1024, 32*1024, 512*1024}; + butil::Timer timer; + printf("%20s%20s%20s%20s%30s%30s%30s\n", "Compress method", "Compress size(B)", + "Compress time(us)", "Decompress time(us)", "Compress throughput(MB/s)", + "Decompress throughput(MB/s)", "Compress ratio"); + for (size_t num = 0; num < ARRAY_SIZE(len_subs); ++num) { + len = len_subs[num]; + snappy_message::SnappyMessageProto old_msg; + char* text = new char[len + 1]; + for (int j = 0; j < len;) { + for (int i = 0; i < 26 && j < len; i++) { + text[j++] = 'a' + i; + } + for (int i = 0; i < 10 && j < len; i++) { + text[j++] = '0' + i; + } + } + text[len] = '\0'; + old_msg.set_text(text); + int k = std::min(32*1024*1024/len, 5000); + CompressMessage("Snappy", k, old_msg, len, + brpc::policy::SnappyCompress, + brpc::policy::SnappyDecompress); + CompressMessage("Gzip", k, old_msg, len, + brpc::policy::GzipCompress, + brpc::policy::GzipDecompress); + CompressMessage("Zlib", k, old_msg, len, + brpc::policy::ZlibCompress, + brpc::policy::ZlibDecompress); + printf("\n"); + delete [] text; + } +} + +TEST_F(test_compress_method, throughput_compare_complete_random) { + char str_table[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + int rand_num = 0; + int len = 0; + int len_subs[] = {128, 1024, 16*1024, 32*1024, 512 * 1024}; + butil::Timer timer; + printf("%20s%20s%20s%20s%30s%30s%30s\n", "Compress method", "Compress size(B)", + "Compress time(us)", "Decompress time(us)", "Compress throughput(MB/s)", + "Decompress throughput(MB/s)", "Compress ratio"); + for (size_t num = 0; num < ARRAY_SIZE(len_subs); ++num) { + len = len_subs[num]; + snappy_message::SnappyMessageProto old_msg; + char* text = new char[len + 1]; + for (int j = 0; j < len;) { + rand_num = rand()%62; + text[j++] = str_table[rand_num]; + } + text[len] = '\0'; + old_msg.set_text(text); + int k = std::min(32*1024*1024/len, 5000); + CompressMessage("Snappy", k, old_msg, len, + brpc::policy::SnappyCompress, + brpc::policy::SnappyDecompress); + CompressMessage("Gzip", k, old_msg, len, + brpc::policy::GzipCompress, + brpc::policy::GzipDecompress); + CompressMessage("Zlib", k, old_msg, len, + brpc::policy::ZlibCompress, + brpc::policy::ZlibDecompress); + printf("\n"); + delete [] text; + } +} + +TEST_F(test_compress_method, mass_snappy_iobuf) { + butil::IOBuf buf; + int len = 782; + char* text = new char[len + 1]; + for (int j = 0; j < len;) { + for (int i = 0; i < 26 && j < len; i++) { + text[j++] = 'a' + i; + } + } + text[len] = '\0'; + buf.append(text, strlen(text)); + butil::IOBuf output_buf, check_buf; + ASSERT_TRUE(brpc::policy::SnappyCompress(buf, &output_buf)); + const std::string output_str = output_buf.to_string(); + len = output_str.size(); + ASSERT_TRUE(SnappyDecompressIOBuf(const_cast(output_str.data()), len, &check_buf)); + std::string check_str = check_buf.to_string(); + ASSERT_TRUE(strcmp(check_str.c_str(), text) == 0); + delete [] text; +} diff --git a/test/brpc_socket_map_unittest.cpp b/test/brpc_socket_map_unittest.cpp new file mode 100644 index 0000000..d40db0e --- /dev/null +++ b/test/brpc_socket_map_unittest.cpp @@ -0,0 +1,176 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: Sun Jul 13 15:04:18 CST 2014 + +#include +#include +#include "brpc/socket.h" +#include "brpc/socket_map.h" +#include "brpc/reloadable_flags.h" + +namespace brpc { +DECLARE_int32(health_check_interval); +DECLARE_int32(idle_timeout_second); +DECLARE_int32(defer_close_second); +DECLARE_int32(max_connection_pool_size); +} // namespace brpc + +namespace { +butil::EndPoint g_endpoint; +brpc::SocketMapKey g_key(g_endpoint); + +void* worker(void*) { + const int ROUND = 2; + const int COUNT = 1000; + brpc::SocketId id; + for (int i = 0; i < ROUND * 2; ++i) { + for (int j = 0; j < COUNT; ++j) { + if (i % 2 == 0) { + EXPECT_EQ(0, brpc::SocketMapInsert(g_key, &id)); + } else { + brpc::SocketMapRemove(g_key); + } + } + } + return NULL; +} + +class SocketMapTest : public ::testing::Test{ +protected: + SocketMapTest(){}; + virtual ~SocketMapTest(){}; + virtual void SetUp(){}; + virtual void TearDown(){}; +}; + +TEST_F(SocketMapTest, disable_health_check) { + int32_t old_interval = brpc::FLAGS_health_check_interval; + brpc::FLAGS_health_check_interval = 0; + brpc::SocketId id; + ASSERT_EQ(-1, brpc::SocketMapFind(g_key, &id)); + ASSERT_EQ(0, brpc::SocketMapInsert(g_key, &id)); + ASSERT_EQ(0, brpc::SocketMapFind(g_key, &id)); + { + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + } + ASSERT_EQ(0, brpc::Socket::SetFailed(id)); + + brpc::SocketUniquePtr ptr; + // The socket should not be recycled, + // because SocketMap holds a reference to it. + ASSERT_EQ(1, brpc::Socket::AddressFailedAsWell(id, &ptr)); + ASSERT_EQ(2, ptr->nref()); + brpc::SocketMapRemove(g_key); + // After removing the socket, `ptr' holds the last reference. + ASSERT_EQ(1, ptr->nref()); + ASSERT_EQ(-1, brpc::SocketMapFind(g_key, &id)); + brpc::FLAGS_health_check_interval = old_interval; +} + +TEST_F(SocketMapTest, idle_timeout) { + const int TIMEOUT = 1; + const int NTHREAD = 10; + brpc::FLAGS_defer_close_second = TIMEOUT; + pthread_t tids[NTHREAD]; + for (int i = 0; i < NTHREAD; ++i) { + ASSERT_EQ(0, pthread_create(&tids[i], NULL, worker, NULL)); + } + for (int i = 0; i < NTHREAD; ++i) { + ASSERT_EQ(0, pthread_join(tids[i], NULL)); + } + brpc::SocketId id; + // Socket still exists since it has not reached timeout yet + ASSERT_EQ(0, brpc::SocketMapFind(g_key, &id)); + usleep(TIMEOUT * 1000000L + 1100000L); + // Socket should be removed after timeout + ASSERT_EQ(-1, brpc::SocketMapFind(g_key, &id)); + + brpc::FLAGS_defer_close_second = TIMEOUT * 10; + ASSERT_EQ(0, brpc::SocketMapInsert(g_key, &id)); + brpc::SocketMapRemove(g_key); + ASSERT_EQ(0, brpc::SocketMapFind(g_key, &id)); + // Change `FLAGS_idle_timeout_second' to 0 to disable checking + brpc::FLAGS_defer_close_second = 0; + usleep(1100000L); + // And then Socket should be removed + ASSERT_EQ(-1, brpc::SocketMapFind(g_key, &id)); + + brpc::SocketId main_id; + ASSERT_EQ(0, brpc::SocketMapInsert(g_key, &main_id)); + brpc::FLAGS_idle_timeout_second = TIMEOUT; + brpc::SocketUniquePtr main_ptr; + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(main_id, &main_ptr)); + ASSERT_EQ(0, main_ptr->GetPooledSocket(&ptr)); + ASSERT_TRUE(main_ptr.get()); + main_ptr.reset(); + id = ptr->id(); + ptr->ReturnToPool(); + ptr.reset(NULL); + usleep(TIMEOUT * 1000000L + 2000000L); + // Pooled connection should be `ReleaseAdditionalReference', + // which destroyed the Socket. As a result `GetSocketFromPool' + // should return a new one + ASSERT_EQ(0, brpc::Socket::Address(main_id, &main_ptr)); + ASSERT_EQ(0, main_ptr->GetPooledSocket(&ptr)); + ASSERT_TRUE(main_ptr.get()); + main_ptr.reset(); + ASSERT_NE(id, ptr->id()); + brpc::SocketMapRemove(g_key); +} + +TEST_F(SocketMapTest, max_pool_size) { + const int MAXSIZE = 5; + const int TOTALSIZE = MAXSIZE + 5; + brpc::FLAGS_max_connection_pool_size = MAXSIZE; + + brpc::SocketId main_id; + ASSERT_EQ(0, brpc::SocketMapInsert(g_key, &main_id)); + + brpc::SocketUniquePtr ptrs[TOTALSIZE]; + for (int i = 0; i < TOTALSIZE; ++i) { + brpc::SocketUniquePtr main_ptr; + ASSERT_EQ(0, brpc::Socket::Address(main_id, &main_ptr)); + ASSERT_EQ(0, main_ptr->GetPooledSocket(&ptrs[i])); + ASSERT_TRUE(main_ptr.get()); + main_ptr.reset(); + } + for (int i = 0; i < TOTALSIZE; ++i) { + ASSERT_EQ(0, ptrs[i]->ReturnToPool()); + } + std::vector ids; + brpc::SocketUniquePtr main_ptr; + ASSERT_EQ(0, brpc::Socket::Address(main_id, &main_ptr)); + main_ptr->ListPooledSockets(&ids); + EXPECT_EQ(MAXSIZE, (int)ids.size()); + // The last few Sockets should be `SetFailed' by `ReturnSocketToPool' + for (int i = MAXSIZE; i < TOTALSIZE; ++i) { + EXPECT_TRUE(ptrs[i]->Failed()); + } +} + +} //namespace + +int main(int argc, char* argv[]) { + butil::str2endpoint("127.0.0.1:12345", &g_key.peer.addr); + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/brpc_socket_unittest.cpp b/test/brpc_socket_unittest.cpp new file mode 100644 index 0000000..a283ffc --- /dev/null +++ b/test/brpc_socket_unittest.cpp @@ -0,0 +1,1721 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: Sun Jul 13 15:04:18 CST 2014 + +#include +#include +#include // F_GETFD +#include +#include +#include "gperftools_helper.h" +#include "butil/time.h" +#include "butil/macros.h" +#include "butil/fd_utility.h" +#include "butil/debug/leak_annotations.h" +#include +#include "bthread/unstable.h" +#include "bthread/task_control.h" +#include "brpc/socket.h" +#include "brpc/errno.pb.h" +#include "brpc/acceptor.h" +#include "brpc/policy/hulu_pbrpc_protocol.h" +#include "brpc/policy/most_common_message.h" +#include "brpc/policy/http_rpc_protocol.h" +#include "brpc/server.h" +#include "brpc/channel.h" +#include "brpc/controller.h" +#include "health_check.pb.h" +#if defined(OS_MACOSX) +#include +#endif +#include + +#define CONNECT_IN_KEEPWRITE 1; + +namespace bthread { +extern TaskControl* g_task_control; +} + +namespace brpc { +DECLARE_int32(health_check_interval); +DECLARE_bool(socket_keepalive); +DECLARE_int32(socket_keepalive_idle_s); +DECLARE_int32(socket_keepalive_interval_s); +DECLARE_int32(socket_keepalive_count); +DECLARE_int32(socket_tcp_user_timeout_ms); +} + +void EchoProcessHuluRequest(brpc::InputMessageBase* msg_base); + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + brpc::Protocol dummy_protocol = + { brpc::policy::ParseHuluMessage, + brpc::SerializeRequestDefault, + brpc::policy::PackHuluRequest, + EchoProcessHuluRequest, EchoProcessHuluRequest, + NULL, NULL, NULL, + brpc::CONNECTION_TYPE_ALL, "dummy_hulu" }; + EXPECT_EQ(0, RegisterProtocol((brpc::ProtocolType)30, dummy_protocol)); + return RUN_ALL_TESTS(); +} + +struct WaitData { + bthread_id_t id; + int error_code; + std::string error_text; + + WaitData() : id(INVALID_BTHREAD_ID), error_code(0) {} +}; +int OnWaitIdReset(bthread_id_t id, void* data, int error_code, + const std::string& error_text) { + static_cast(data)->id = id; + static_cast(data)->error_code = error_code; + static_cast(data)->error_text = error_text; + return bthread_id_unlock_and_destroy(id); +} + +class SocketTest : public ::testing::Test{ +protected: + SocketTest(){ + }; + virtual ~SocketTest(){}; + virtual void SetUp() { + }; + virtual void TearDown() { + }; +}; + +brpc::Socket* global_sock = NULL; + +class CheckRecycle : public brpc::SocketUser { + void BeforeRecycle(brpc::Socket* s) { + ASSERT_TRUE(global_sock); + ASSERT_EQ(global_sock, s); + global_sock = NULL; + delete this; + } +}; + +TEST_F(SocketTest, not_recycle_until_zero_nref) { + std::cout << "sizeof(Socket)=" << sizeof(brpc::Socket) << std::endl; + int fds[2]; + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds)); + brpc::SocketId id = 8888; + butil::EndPoint dummy; + ASSERT_EQ(0, str2endpoint("192.168.1.26:8080", &dummy)); + brpc::SocketOptions options; + options.fd = fds[1]; + options.remote_side = dummy; + options.user = new CheckRecycle; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + { + brpc::SocketUniquePtr s; + ASSERT_EQ(0, brpc::Socket::Address(id, &s)); + global_sock = s.get(); + ASSERT_TRUE(s.get()); + ASSERT_EQ(fds[1], s->fd()); + ASSERT_EQ(dummy, s->remote_side()); + ASSERT_EQ(id, s->id()); + ASSERT_EQ(0, s->SetFailed()); + ASSERT_EQ(s.get(), global_sock); + } + ASSERT_EQ((brpc::Socket*)NULL, global_sock); + close(fds[0]); + + brpc::SocketUniquePtr ptr; + ASSERT_EQ(-1, brpc::Socket::Address(id, &ptr)); +} + +butil::atomic winner_count(0); +const int AUTH_ERR = -9; + +void* auth_fighter(void* arg) { + bthread_usleep(10000); + int auth_error = 0; + brpc::Socket* s = (brpc::Socket*)arg; + if (s->FightAuthentication(&auth_error) == 0) { + winner_count.fetch_add(1); + s->SetAuthentication(AUTH_ERR); + } else { + EXPECT_EQ(AUTH_ERR, auth_error); + } + return NULL; +} + +TEST_F(SocketTest, authentication) { + brpc::SocketId id; + brpc::SocketOptions options; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + brpc::SocketUniquePtr s; + ASSERT_EQ(0, brpc::Socket::Address(id, &s)); + + bthread_t th[64]; + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, bthread_start_urgent(&th[i], NULL, auth_fighter, s.get())); + } + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, bthread_join(th[i], NULL)); + } + // Only one fighter wins + ASSERT_EQ(1, winner_count.load()); + + // Fight after signal is OK + int auth_error = 0; + ASSERT_NE(0, s->FightAuthentication(&auth_error)); + ASSERT_EQ(AUTH_ERR, auth_error); + // Socket has been `SetFailed' when authentication failed + ASSERT_TRUE(brpc::Socket::Address(s->id(), NULL)); +} + +static butil::atomic g_called_seq(1); +class MyMessage : public brpc::SocketMessage { +public: + MyMessage(const char* str, size_t len, int* called = NULL) + : _str(str), _len(len), _called(called) {} +private: + butil::Status AppendAndDestroySelf(butil::IOBuf* out_buf, brpc::Socket*) { + out_buf->append(_str, _len); + if (_called) { + *_called = g_called_seq.fetch_add(1, butil::memory_order_relaxed); + } + delete this; + return butil::Status::OK(); + }; + const char* _str; + size_t _len; + int* _called; +}; + +class MyErrorMessage : public brpc::SocketMessage { +public: + explicit MyErrorMessage(const butil::Status& st) : _status(st) {} +private: + butil::Status AppendAndDestroySelf(butil::IOBuf*, brpc::Socket*) { + butil::Status st = _status; + delete this; + return st; + }; + butil::Status _status; +}; + +TEST_F(SocketTest, single_threaded_write) { + int fds[2]; + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds)); + brpc::SocketId id = 8888; + butil::EndPoint dummy; + ASSERT_EQ(0, str2endpoint("192.168.1.26:8080", &dummy)); + brpc::SocketOptions options; + options.fd = fds[1]; + options.remote_side = dummy; + options.user = new CheckRecycle; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + { + brpc::SocketUniquePtr s; + ASSERT_EQ(0, brpc::Socket::Address(id, &s)); + global_sock = s.get(); + ASSERT_TRUE(s.get()); + ASSERT_EQ(fds[1], s->fd()); + ASSERT_EQ(dummy, s->remote_side()); + ASSERT_EQ(id, s->id()); + const int BATCH = 5; + for (size_t i = 0; i < 20; ++i) { + char buf[32 * BATCH]; + size_t len = snprintf(buf, sizeof(buf), "hello world! %lu", i); + if (i % 4 == 0) { + brpc::SocketMessagePtr msg(new MyMessage(buf, len)); + ASSERT_EQ(0, s->Write(msg)); + } else if (i % 4 == 1) { + brpc::SocketMessagePtr msg( + new MyErrorMessage(butil::Status(EINVAL, "Invalid input"))); + bthread_id_t wait_id; + WaitData data; + ASSERT_EQ(0, bthread_id_create2(&wait_id, &data, OnWaitIdReset)); + brpc::Socket::WriteOptions wopt; + wopt.id_wait = wait_id; + ASSERT_EQ(0, s->Write(msg, &wopt)); + ASSERT_EQ(0, bthread_id_join(wait_id)); + ASSERT_EQ(wait_id.value, data.id.value); + ASSERT_EQ(EINVAL, data.error_code); + ASSERT_EQ("Invalid input", data.error_text); + continue; + } else if (i % 4 == 2) { + int seq[BATCH] = {}; + brpc::SocketMessagePtr msgs[BATCH]; + // re-print the buffer. + len = 0; + for (int j = 0; j < BATCH; ++j) { + if (j % 2 == 0) { + // Empty message, should be skipped. + msgs[j].reset(new MyMessage(buf+len, 0, &seq[j])); + } else { + size_t sub_len = snprintf( + buf+len, sizeof(buf)-len, "hello world! %lu.%d", i, j); + msgs[j].reset(new MyMessage(buf+len, sub_len, &seq[j])); + len += sub_len; + } + } + for (size_t i = 0; i < BATCH; ++i) { + ASSERT_EQ(0, s->Write(msgs[i])); + } + for (int j = 1; j < BATCH; ++j) { + ASSERT_LT(seq[j-1], seq[j]) << "j=" << j; + } + } else { + butil::IOBuf src; + src.append(buf); + ASSERT_EQ(len, src.length()); + ASSERT_EQ(0, s->Write(&src)); + ASSERT_TRUE(src.empty()); + } + char dest[sizeof(buf)]; + ASSERT_EQ(len, (size_t)read(fds[0], dest, sizeof(dest))); + ASSERT_EQ(0, memcmp(buf, dest, len)); + } + ASSERT_EQ(0, s->SetFailed()); + } + ASSERT_EQ((brpc::Socket*)NULL, global_sock); + close(fds[0]); +} + +void EchoProcessHuluRequest(brpc::InputMessageBase* msg_base) { + brpc::DestroyingPtr msg( + static_cast(msg_base)); + butil::IOBuf buf; + buf.append(msg->meta); + buf.append(msg->payload); + ASSERT_EQ(0, msg->socket()->Write(&buf)); +} + +class MyConnect : public brpc::AppConnect { +public: + MyConnect() : _done(NULL), _data(NULL), _called_start_connect(false) {} + void StartConnect(const brpc::Socket*, + void (*done)(int err, void* data), + void* data) { + LOG(INFO) << "Start application-level connect"; + _done = done; + _data = data; + _called_start_connect = true; + } + void StopConnect(brpc::Socket*) { + LOG(INFO) << "Stop application-level connect"; + } + void MakeConnectDone() { + _done(0, _data); + } + bool is_start_connect_called() const { return _called_start_connect; } +private: + void (*_done)(int err, void* data); + void* _data; + bool _called_start_connect; +}; + +TEST_F(SocketTest, single_threaded_connect_and_write) { + // FIXME(gejun): Messenger has to be new otherwise quitting may crash. + // It is intentionally never deleted; mark it so it is not a reported leak. + brpc::Acceptor* messenger = new brpc::Acceptor; + ANNOTATE_LEAKING_OBJECT_PTR(messenger); + const brpc::InputMessageHandler pairs[] = { + { brpc::policy::ParseHuluMessage, + EchoProcessHuluRequest, NULL, NULL, "dummy_hulu" } + }; + + int listening_fd = -1; + butil::EndPoint point(butil::IP_ANY, 7878); + for (int i = 0; i < 100; ++i) { + point.port += i; + listening_fd = tcp_listen(point); + if (listening_fd >= 0) { + break; + } + } + ASSERT_GT(listening_fd, 0) << berror(); + ASSERT_EQ(0, butil::make_non_blocking(listening_fd)); + ASSERT_EQ(0, messenger->AddHandler(pairs[0])); + ASSERT_EQ(0, messenger->StartAccept(listening_fd, -1, NULL, false)); + + brpc::SocketId id = 8888; + brpc::SocketOptions options; + options.remote_side = point; + std::shared_ptr my_connect = std::make_shared(); + options.app_connect = my_connect; + options.user = new CheckRecycle; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + { + brpc::SocketUniquePtr s; + ASSERT_EQ(0, brpc::Socket::Address(id, &s)); + global_sock = s.get(); + ASSERT_TRUE(s.get()); + ASSERT_EQ(-1, s->fd()); + ASSERT_EQ(butil::EndPoint(), s->local_side()); + ASSERT_EQ(point, s->remote_side()); + ASSERT_EQ(id, s->id()); + for (size_t i = 0; i < 20; ++i) { + char buf[64]; + const size_t meta_len = 4; + *(uint32_t*)(buf + 12) = *(uint32_t*)"Meta"; + const size_t len = snprintf(buf + 12 + meta_len, + sizeof(buf) - 12 - meta_len, + "hello world! %lu", i); + memcpy(buf, "HULU", 4); + // HULU uses host byte order directly... + *(uint32_t*)(buf + 4) = len + meta_len; + *(uint32_t*)(buf + 8) = meta_len; + + int called = 0; + if (i % 2 == 0) { + brpc::SocketMessagePtr msg( + new MyMessage(buf, 12 + meta_len + len, &called)); + ASSERT_EQ(0, s->Write(msg)); + } else { + butil::IOBuf src; + src.append(buf, 12 + meta_len + len); + ASSERT_EQ(12 + meta_len + len, src.length()); + ASSERT_EQ(0, s->Write(&src)); + ASSERT_TRUE(src.empty()); + } + if (i == 0) { + // connection needs to be established at first time. + // Should be intentionally blocked in app_connect. + bthread_usleep(10000); + ASSERT_TRUE(my_connect->is_start_connect_called()); + ASSERT_LT(0, s->fd()); // already tcp connected + ASSERT_EQ(0, called); // request is not serialized yet. + my_connect->MakeConnectDone(); + ASSERT_LT(0, called); // serialized + } + int64_t start_time = butil::cpuwide_time_us(); + while (s->fd() < 0) { + bthread_usleep(1000); + ASSERT_LT(butil::cpuwide_time_us(), start_time + 1000000L) << "Too long!"; + } +#if defined(OS_LINUX) + ASSERT_EQ(0, bthread_fd_wait(s->fd(), EPOLLIN)); +#elif defined(OS_MACOSX) + ASSERT_EQ(0, bthread_fd_wait(s->fd(), EVFILT_READ)); +#endif + char dest[sizeof(buf)]; + ASSERT_EQ(meta_len + len, (size_t)read(s->fd(), dest, sizeof(dest))); + ASSERT_EQ(0, memcmp(buf + 12, dest, meta_len + len)); + } + ASSERT_EQ(0, s->SetFailed()); + } + ASSERT_EQ((brpc::Socket*)NULL, global_sock); + // The id is invalid. + brpc::SocketUniquePtr ptr; + ASSERT_EQ(-1, brpc::Socket::Address(id, &ptr)); + + messenger->StopAccept(0); + messenger->Join(); + ASSERT_EQ(-1, messenger->listened_fd()); + ASSERT_EQ(-1, fcntl(listening_fd, F_GETFD)); + ASSERT_EQ(EBADF, errno); + + // The socket object is likely to be reused, + // and the local side should be initialized. + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + brpc::SocketUniquePtr s; + ASSERT_EQ(0, brpc::Socket::Address(id, &s)); + ASSERT_TRUE(s.get()); + ASSERT_EQ(-1, s->fd()); + ASSERT_EQ(butil::EndPoint(), s->local_side()); + ASSERT_EQ(point, s->remote_side()); +} + +#define NUMBER_WIDTH 16 + +struct WriterArg { + size_t times; + size_t offset; + brpc::SocketId socket_id; +}; + +void* FailedWriter(void* void_arg) { + WriterArg* arg = static_cast(void_arg); + brpc::SocketUniquePtr sock; + if (brpc::Socket::Address(arg->socket_id, &sock) < 0) { + printf("Fail to address SocketId=%" PRIu64 "\n", arg->socket_id); + return NULL; + } + char buf[32]; + for (size_t i = 0; i < arg->times; ++i) { + bthread_id_t id; + EXPECT_EQ(0, bthread_id_create(&id, NULL, NULL)); + snprintf(buf, sizeof(buf), "%0" BAIDU_SYMBOLSTR(NUMBER_WIDTH) "lu", + i + arg->offset); + butil::IOBuf src; + src.append(buf); + brpc::Socket::WriteOptions wopt; + wopt.id_wait = id; + sock->Write(&src, &wopt); + EXPECT_EQ(0, bthread_id_join(id)); + // Only the first connect can see ECONNREFUSED and then + // calls `SetFailed' making others' error_code=EINVAL + //EXPECT_EQ(ECONNREFUSED, error_code); + } + return NULL; +} + +TEST_F(SocketTest, fail_to_connect) { + const size_t REP = 10; + butil::EndPoint point(butil::IP_ANY, 7563/*not listened*/); + brpc::SocketId id = 8888; + brpc::SocketOptions options; + options.remote_side = point; + options.user = new CheckRecycle; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + { + brpc::SocketUniquePtr s; + ASSERT_EQ(0, brpc::Socket::Address(id, &s)); + global_sock = s.get(); + ASSERT_TRUE(s.get()); + ASSERT_EQ(-1, s->fd()); + ASSERT_EQ(point, s->remote_side()); + ASSERT_EQ(id, s->id()); + pthread_t th[8]; + WriterArg args[ARRAY_SIZE(th)]; + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + args[i].times = REP; + args[i].offset = i * REP; + args[i].socket_id = id; + ASSERT_EQ(0, pthread_create(&th[i], NULL, FailedWriter, &args[i])); + } + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, pthread_join(th[i], NULL)); + } + ASSERT_EQ(-1, s->SetFailed()); // already SetFailed + ASSERT_EQ(-1, s->fd()); + } + // KeepWrite is possibly still running. + int64_t start_time = butil::cpuwide_time_us(); + while (global_sock != NULL) { + bthread_usleep(1000); + ASSERT_LT(butil::cpuwide_time_us(), start_time + 1000000L) << "Too long!"; + } + ASSERT_EQ(-1, brpc::Socket::Status(id)); + // The id is invalid. + brpc::SocketUniquePtr ptr; + ASSERT_EQ(-1, brpc::Socket::Address(id, &ptr)); +} + +TEST_F(SocketTest, not_health_check_when_nref_hits_0) { + brpc::SocketId id = 8888; + butil::EndPoint point(butil::IP_ANY, 7584/*not listened*/); + brpc::SocketOptions options; + options.remote_side = point; + options.user = new CheckRecycle; + options.health_check_interval_s = 1/*s*/; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + { + brpc::SocketUniquePtr s; + ASSERT_EQ(0, brpc::Socket::Address(id, &s)); + global_sock = s.get(); + ASSERT_TRUE(s.get()); + ASSERT_EQ(-1, s->fd()); + ASSERT_EQ(point, s->remote_side()); + ASSERT_EQ(id, s->id()); + + char buf[64]; + const size_t meta_len = 4; + *(uint32_t*)(buf + 12) = *(uint32_t*)"Meta"; + const size_t len = snprintf(buf + 12 + meta_len, + sizeof(buf) - 12 - meta_len, + "hello world!"); + memcpy(buf, "HULU", 4); + // HULU uses host byte order directly... + *(uint32_t*)(buf + 4) = len + meta_len; + *(uint32_t*)(buf + 8) = meta_len; + butil::IOBuf src; + src.append(buf, 12 + meta_len + len); + ASSERT_EQ(12 + meta_len + len, src.length()); +#ifdef CONNECT_IN_KEEPWRITE + bthread_id_t wait_id; + WaitData data; + ASSERT_EQ(0, bthread_id_create2(&wait_id, &data, OnWaitIdReset)); + brpc::Socket::WriteOptions wopt; + wopt.id_wait = wait_id; + ASSERT_EQ(0, s->Write(&src, &wopt)); + ASSERT_EQ(0, bthread_id_join(wait_id)); + ASSERT_EQ(wait_id.value, data.id.value); + ASSERT_EQ(ECONNREFUSED, data.error_code); + ASSERT_TRUE(butil::StringPiece(data.error_text).starts_with( + "Fail to connect ")); +#else + ASSERT_EQ(-1, s->Write(&src)); + ASSERT_EQ(ECONNREFUSED, errno); +#endif + ASSERT_TRUE(src.empty()); + ASSERT_EQ(-1, s->fd()); + s->ReleaseHCRelatedReference(); + } + // StartHealthCheck is possibly still running. Spin until global_sock + // is NULL(set in CheckRecycle::BeforeRecycle). Notice that you should + // not spin until Socket::Status(id) becomes -1 and assert global_sock + // to be NULL because invalidating id happens before calling BeforeRecycle. + const int64_t start_time = butil::cpuwide_time_us(); + while (global_sock != NULL) { + bthread_usleep(1000); + ASSERT_LT(butil::cpuwide_time_us(), start_time + 1000000L); + } + ASSERT_EQ(-1, brpc::Socket::Status(id)); +} + +class HealthCheckTestServiceImpl : public test::HealthCheckTestService { +public: + HealthCheckTestServiceImpl() + : _sleep_flag(true) {} + virtual ~HealthCheckTestServiceImpl() {} + + virtual void default_method(google::protobuf::RpcController* cntl_base, + const test::HealthCheckRequest* request, + test::HealthCheckResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = (brpc::Controller*)cntl_base; + if (_sleep_flag) { + bthread_usleep(510000 /* 510ms, a little bit longer than the default + timeout of health check rpc */); + } + cntl->response_attachment().append("OK"); + } + + bool _sleep_flag; +}; + +TEST_F(SocketTest, app_level_health_check) { + int old_health_check_interval = brpc::FLAGS_health_check_interval; + GFLAGS_NAMESPACE::SetCommandLineOption("health_check_path", "/HealthCheckTestService"); + GFLAGS_NAMESPACE::SetCommandLineOption("health_check_interval", "1"); + + butil::EndPoint point(butil::IP_ANY, 7777); + brpc::ChannelOptions options; + options.protocol = "http"; + options.max_retry = 0; + brpc::Channel channel; + ASSERT_EQ(0, channel.Init(point, &options)); + { + brpc::Controller cntl; + cntl.http_request().uri() = "/"; + channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + EXPECT_TRUE(cntl.Failed()); + ASSERT_EQ(ECONNREFUSED, cntl.ErrorCode()); + } + + // 2s to make sure remote is connected by HealthCheckTask and enter the + // sending-rpc state. Because the remote is not down, so hc rpc would keep + // sending. + int listening_fd = tcp_listen(point); + bthread_usleep(2000000); + + // 2s to make sure HealthCheckTask find socket is failed and correct impl + // should trigger next round of hc + close(listening_fd); + bthread_usleep(2000000); + + brpc::Server server; + HealthCheckTestServiceImpl hc_service; + ASSERT_EQ(0, server.AddService(&hc_service, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start(point, NULL)); + + for (int i = 0; i < 4; ++i) { + // although ::connect would succeed, the stall in hc_service makes + // the health check rpc fail. + brpc::Controller cntl; + cntl.http_request().uri() = "/"; + channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_EQ(EHOSTDOWN, cntl.ErrorCode()); + bthread_usleep(1000000 /*1s*/); + } + hc_service._sleep_flag = false; + bthread_usleep(2000000 /* a little bit longer than hc rpc timeout + hc interval */); + // should recover now + { + brpc::Controller cntl; + cntl.http_request().uri() = "/"; + channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); + ASSERT_FALSE(cntl.Failed()); + ASSERT_GT(cntl.response_attachment().size(), (size_t)0); + } + + GFLAGS_NAMESPACE::SetCommandLineOption("health_check_path", ""); + char hc_buf[8]; + snprintf(hc_buf, sizeof(hc_buf), "%d", old_health_check_interval); + GFLAGS_NAMESPACE::SetCommandLineOption("health_check_interval", hc_buf); +} + +TEST_F(SocketTest, health_check) { + // FIXME(gejun): Messenger has to be new otherwise quitting may crash. + // It is intentionally never deleted; mark it so it is not a reported leak. + brpc::Acceptor* messenger = new brpc::Acceptor; + ANNOTATE_LEAKING_OBJECT_PTR(messenger); + + brpc::SocketId id = 8888; + butil::EndPoint point(butil::IP_ANY, 7878); + const int kCheckInteval = 1; + brpc::SocketOptions options; + options.remote_side = point; + options.user = new CheckRecycle; + options.health_check_interval_s = kCheckInteval/*s*/; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + brpc::Socket* s = NULL; + { + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + s = ptr.get(); + } + global_sock = s; + ASSERT_NE(nullptr, s); + ASSERT_EQ(-1, s->fd()); + ASSERT_EQ(point, s->remote_side()); + ASSERT_EQ(id, s->id()); + int32_t nref = -1; + ASSERT_EQ(0, brpc::Socket::Status(id, &nref)); + ASSERT_EQ(2, nref); + + char buf[64]; + const size_t meta_len = 4; + *(uint32_t*)(buf + 12) = *(uint32_t*)"Meta"; + const size_t len = snprintf(buf + 12 + meta_len, + sizeof(buf) - 12 - meta_len, + "hello world!"); + memcpy(buf, "HULU", 4); + // HULU uses host byte order directly... + *(uint32_t*)(buf + 4) = len + meta_len; + *(uint32_t*)(buf + 8) = meta_len; + const bool use_my_message = (butil::fast_rand_less_than(2) == 0); + brpc::SocketMessagePtr msg; + int appended_msg = 0; + butil::IOBuf src; + if (use_my_message) { + LOG(INFO) << "Use MyMessage"; + msg.reset(new MyMessage(buf, 12 + meta_len + len, &appended_msg)); + } else { + src.append(buf, 12 + meta_len + len); + ASSERT_EQ(12 + meta_len + len, src.length()); + } +#ifdef CONNECT_IN_KEEPWRITE + bthread_id_t wait_id; + WaitData data; + ASSERT_EQ(0, bthread_id_create2(&wait_id, &data, OnWaitIdReset)); + brpc::Socket::WriteOptions wopt; + wopt.id_wait = wait_id; + if (use_my_message) { + ASSERT_EQ(0, s->Write(msg, &wopt)); + } else { + ASSERT_EQ(0, s->Write(&src, &wopt)); + } + ASSERT_EQ(0, bthread_id_join(wait_id)); + ASSERT_EQ(wait_id.value, data.id.value); + ASSERT_EQ(ECONNREFUSED, data.error_code); + ASSERT_TRUE(butil::StringPiece(data.error_text).starts_with( + "Fail to connect ")); + if (use_my_message) { + ASSERT_TRUE(appended_msg); + } +#else + if (use_my_message) { + ASSERT_EQ(-1, s->Write(msg)); + } else { + ASSERT_EQ(-1, s->Write(&src)); + } + ASSERT_EQ(ECONNREFUSED, errno); +#endif + ASSERT_TRUE(src.empty()); + ASSERT_EQ(-1, s->fd()); + ASSERT_TRUE(global_sock); + brpc::SocketUniquePtr invalid_ptr; + ASSERT_EQ(-1, brpc::Socket::Address(id, &invalid_ptr)); + ASSERT_EQ(1, brpc::Socket::Status(id)); + + const brpc::InputMessageHandler pairs[] = { + { brpc::policy::ParseHuluMessage, + EchoProcessHuluRequest, NULL, NULL, "dummy_hulu" } + }; + + int listening_fd = tcp_listen(point); + ASSERT_TRUE(listening_fd > 0); + butil::make_non_blocking(listening_fd); + ASSERT_EQ(0, messenger->AddHandler(pairs[0])); + ASSERT_EQ(0, messenger->StartAccept(listening_fd, -1, NULL, false)); + + int64_t start_time = butil::cpuwide_time_us(); + nref = -1; + while (brpc::Socket::Status(id, &nref) != 0) { + bthread_usleep(1000); + ASSERT_LT(butil::cpuwide_time_us(), + start_time + kCheckInteval * 1000000L + 100000L/*100ms*/); + } + //ASSERT_EQ(2, nref); + ASSERT_TRUE(global_sock); + + int fd = 0; + { + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + ASSERT_NE(0, ptr->fd()); + fd = ptr->fd(); + } + + // SetFailed again, should reconnect and succeed soon. + ASSERT_EQ(0, s->SetFailed()); + ASSERT_EQ(fd, s->fd()); + start_time = butil::cpuwide_time_us(); + while (brpc::Socket::Status(id) != 0) { + bthread_usleep(1000); + ASSERT_LT(butil::cpuwide_time_us(), start_time + 1200000L); + } + ASSERT_TRUE(global_sock); + + { + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + ASSERT_NE(0, ptr->fd()); + } + + s->ReleaseHCRelatedReference(); + + // Must stop messenger before SetFailed the id otherwise StartHealthCheck + // still has chance to get reconnected and revive the id. + messenger->StopAccept(0); + messenger->Join(); + ASSERT_EQ(-1, messenger->listened_fd()); + ASSERT_EQ(-1, fcntl(listening_fd, F_GETFD)); + ASSERT_EQ(EBADF, errno); + + ASSERT_EQ(0, brpc::Socket::SetFailed(id)); + // StartHealthCheck is possibly still addressing the Socket. + start_time = butil::cpuwide_time_us(); + while (global_sock != NULL) { + bthread_usleep(1000); + ASSERT_LT(butil::cpuwide_time_us(), start_time + 1000000L); + } + nref = 0; + ASSERT_EQ(-1, brpc::Socket::Status(id, &nref)) << "nref=" << nref; + // The id is invalid. + brpc::SocketUniquePtr ptr; + ASSERT_EQ(-1, brpc::Socket::Address(id, &ptr)); +} + +void* Writer(void* void_arg) { + WriterArg* arg = static_cast(void_arg); + brpc::SocketUniquePtr sock; + if (brpc::Socket::Address(arg->socket_id, &sock) < 0) { + printf("Fail to address SocketId=%" PRIu64 "\n", arg->socket_id); + return NULL; + } + char buf[32]; + for (size_t i = 0; i < arg->times; ++i) { + snprintf(buf, sizeof(buf), "%0" BAIDU_SYMBOLSTR(NUMBER_WIDTH) "lu", + i + arg->offset); + butil::IOBuf src; + src.append(buf); + if (sock->Write(&src) != 0) { + if (errno == brpc::EOVERCROWDED) { + // The buf is full, sleep a while and retry. + bthread_usleep(1000); + --i; + continue; + } + printf("Fail to write into SocketId=%" PRIu64 ", %s\n", + arg->socket_id, berror()); + break; + } + } + return NULL; +} + +TEST_F(SocketTest, multi_threaded_write) { + const size_t REP = 20000; + int fds[2]; + for (int k = 0; k < 2; ++k) { + printf("Round %d\n", k + 1); + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds)); + pthread_t th[8]; + WriterArg args[ARRAY_SIZE(th)]; + std::vector result; + result.reserve(ARRAY_SIZE(th) * REP); + + brpc::SocketId id = 8888; + butil::EndPoint dummy; + ASSERT_EQ(0, str2endpoint("192.168.1.26:8080", &dummy)); + brpc::SocketOptions options; + options.fd = fds[1]; + options.remote_side = dummy; + options.user = new CheckRecycle; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + brpc::SocketUniquePtr s; + ASSERT_EQ(0, brpc::Socket::Address(id, &s)); + s->_ssl_state = brpc::SSL_OFF; + global_sock = s.get(); + ASSERT_TRUE(s.get()); + ASSERT_EQ(fds[1], s->fd()); + ASSERT_EQ(dummy, s->remote_side()); + ASSERT_EQ(id, s->id()); + butil::make_non_blocking(fds[0]); + + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + args[i].times = REP; + args[i].offset = i * REP; + args[i].socket_id = id; + ASSERT_EQ(0, pthread_create(&th[i], NULL, Writer, &args[i])); + } + + if (k == 1) { + printf("sleep 100ms to block writers\n"); + bthread_usleep(100000); + } + + butil::IOPortal dest; + const int64_t start_time = butil::cpuwide_time_us(); + for (;;) { + ssize_t nr = dest.append_from_file_descriptor(fds[0], 32768); + if (nr < 0) { + if (errno == EINTR) { + continue; + } + if (EAGAIN != errno) { + ASSERT_EQ(EAGAIN, errno) << berror(); + } + bthread_usleep(1000); + if (butil::cpuwide_time_us() >= start_time + 2000000L) { + LOG(FATAL) << "Wait too long!"; + break; + } + continue; + } + while (dest.length() >= NUMBER_WIDTH) { + char buf[NUMBER_WIDTH + 1]; + dest.copy_to(buf, NUMBER_WIDTH); + buf[sizeof(buf)-1] = 0; + result.push_back(strtol(buf, NULL, 10)); + dest.pop_front(NUMBER_WIDTH); + } + if (result.size() >= REP * ARRAY_SIZE(th)) { + break; + } + } + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, pthread_join(th[i], NULL)); + } + ASSERT_TRUE(dest.empty()); + bthread::g_task_control->print_rq_sizes(std::cout); + std::cout << std::endl; + + ASSERT_EQ(REP * ARRAY_SIZE(th), result.size()) + << "write_head=" << s->_write_head; + std::sort(result.begin(), result.end()); + result.resize(std::unique(result.begin(), + result.end()) - result.begin()); + ASSERT_EQ(REP * ARRAY_SIZE(th), result.size()); + ASSERT_EQ(0UL, *result.begin()); + ASSERT_EQ(REP * ARRAY_SIZE(th) - 1, *(result.end() - 1)); + + ASSERT_EQ(0, s->SetFailed()); + s.release()->Dereference(); + ASSERT_EQ((brpc::Socket*)NULL, global_sock); + close(fds[0]); + } +} + +void* FastWriter(void* void_arg) { + WriterArg* arg = static_cast(void_arg); + brpc::SocketUniquePtr sock; + if (brpc::Socket::Address(arg->socket_id, &sock) < 0) { + printf("Fail to address SocketId=%" PRIu64 "\n", arg->socket_id); + return NULL; + } + char buf[] = "hello reader side!"; + int64_t begin_ts = butil::cpuwide_time_us(); + int64_t nretry = 0; + size_t c = 0; + for (; c < arg->times; ++c) { + butil::IOBuf src; + src.append(buf, 16); + if (sock->Write(&src) != 0) { + if (errno == brpc::EOVERCROWDED) { + // The buf is full, sleep a while and retry. + bthread_usleep(1000); + --c; + ++nretry; + continue; + } + printf("Fail to write into SocketId=%" PRIu64 ", %s\n", + arg->socket_id, berror()); + break; + } + } + int64_t end_ts = butil::cpuwide_time_us(); + int64_t total_time = end_ts - begin_ts; + printf("total=%ld count=%ld nretry=%ld\n", + (long)total_time * 1000/ c, (long)c, (long)nretry); + return NULL; +} + +struct ReaderArg { + int fd; + size_t nread; +}; + +void* reader(void* void_arg) { + ReaderArg* arg = static_cast(void_arg); + const size_t LEN = 32768; + char* buf = (char*)malloc(LEN); + while (1) { + ssize_t nr = read(arg->fd, buf, LEN); + if (nr < 0) { + printf("Fail to read, %m\n"); + break; + } else if (nr == 0) { + printf("Far end closed\n"); + break; + } + arg->nread += nr; + } + free(buf); + return NULL; +} + +TEST_F(SocketTest, multi_threaded_write_perf) { + const size_t REP = 1000000000; + int fds[2]; + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds)); + bthread_t th[3]; + WriterArg args[ARRAY_SIZE(th)]; + + brpc::SocketId id = 8888; + butil::EndPoint dummy; + ASSERT_EQ(0, str2endpoint("192.168.1.26:8080", &dummy)); + brpc::SocketOptions options; + options.fd = fds[1]; + options.remote_side = dummy; + options.user = new CheckRecycle; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + brpc::SocketUniquePtr s; + ASSERT_EQ(0, brpc::Socket::Address(id, &s)); + s->_ssl_state = brpc::SSL_OFF; + ASSERT_EQ(2, brpc::NRefOfVRef(s->_versioned_ref)); + global_sock = s.get(); + ASSERT_TRUE(s.get()); + ASSERT_EQ(fds[1], s->fd()); + ASSERT_EQ(dummy, s->remote_side()); + ASSERT_EQ(id, s->id()); + + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + args[i].times = REP; + args[i].offset = i * REP; + args[i].socket_id = id; + bthread_start_background(&th[i], NULL, FastWriter, &args[i]); + } + + pthread_t rth; + ReaderArg reader_arg = { fds[0], 0 }; + pthread_create(&rth, NULL, reader, &reader_arg); + + butil::Timer tm; + ProfilerStart("write.prof"); + const uint64_t old_nread = reader_arg.nread; + tm.start(); + sleep(2); + tm.stop(); + const uint64_t new_nread = reader_arg.nread; + ProfilerStop(); + + printf("tp=%" PRIu64 "M/s\n", (new_nread - old_nread) / tm.u_elapsed()); + + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + args[i].times = 0; + } + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, bthread_join(th[i], NULL)); + } + ASSERT_EQ(0, s->SetFailed()); + s.release()->Dereference(); + pthread_join(rth, NULL); + ASSERT_EQ((brpc::Socket*)NULL, global_sock); + close(fds[0]); +} + +void GetKeepaliveValue(int fd, + int& keepalive, + int& keepalive_idle, + int& keepalive_interval, + int& keepalive_count) { + { + socklen_t len = sizeof(keepalive); + ASSERT_EQ(0, getsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, &len)); + + } + + { + socklen_t len = sizeof(keepalive_idle); +#if defined(OS_MACOSX) + ASSERT_EQ(0, getsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &keepalive_idle, &len)); +#elif defined(OS_LINUX) + ASSERT_EQ(0, getsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &keepalive_idle, &len)); +#endif + } + + { + socklen_t len = sizeof(keepalive_interval); +#if defined(OS_MACOSX) + ASSERT_EQ(0, getsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &keepalive_interval, &len)); +#elif defined(OS_LINUX) + ASSERT_EQ(0, getsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &keepalive_interval, &len)); +#endif + } + + { + socklen_t len = sizeof(keepalive_count); +#if defined(OS_MACOSX) + ASSERT_EQ(0, getsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &keepalive_count, &len)); +#elif defined(OS_LINUX) + ASSERT_EQ(0, getsockopt(fd, SOL_TCP, TCP_KEEPCNT, &keepalive_count, &len)); +#endif + } +} + +void CheckNoKeepalive(int fd) { + int keepalive = -1; + socklen_t len = sizeof(keepalive); + ASSERT_EQ(0, getsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, &len)); + ASSERT_EQ(0, keepalive); +} + +void CheckKeepalive(int fd, + bool expected_keepalive, + int expected_keepalive_idle, + int expected_keepalive_interval, + int expected_keepalive_count) { + + int keepalive = -1; + int keepalive_idle = -1; + int keepalive_interval = -1; + int keepalive_count = -1; + GetKeepaliveValue(fd, keepalive, keepalive_idle, + keepalive_interval, keepalive_count); + if (expected_keepalive) { + ASSERT_LT(0, keepalive); + } else { + ASSERT_EQ(0, keepalive); + return; + } + ASSERT_EQ(expected_keepalive_idle, keepalive_idle); + ASSERT_EQ(expected_keepalive_interval, keepalive_interval); + ASSERT_EQ(expected_keepalive_count, keepalive_count); +} + +TEST_F(SocketTest, keepalive) { + int default_keepalive = 0; + int default_keepalive_idle = 0; + int default_keepalive_interval = 0; + int default_keepalive_count = 0; + { + butil::fd_guard sockfd(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_GT(sockfd, 0); + GetKeepaliveValue(sockfd, default_keepalive, default_keepalive_idle, + default_keepalive_interval, default_keepalive_count); + } + + // Disable keepalive. + { + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + ASSERT_GT(sockfd, 0); + brpc::SocketOptions options; + options.fd = sockfd; + brpc::SocketId id; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + CheckNoKeepalive(ptr->fd()); + ASSERT_EQ(0, ptr->SetFailed()); + } + + int keepalive_idle = 1; + int keepalive_interval = 2; + int keepalive_count = 2; + // Enable keepalive. + { + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + ASSERT_GT(sockfd, 0); + brpc::SocketOptions options; + options.fd = sockfd; + options.keepalive_options = std::make_shared(); + brpc::SocketId id; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + CheckKeepalive(ptr->fd(), true, default_keepalive_idle, + default_keepalive_interval, default_keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); + } + + // Enable keepalive and set keepalive idle. + { + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + ASSERT_GT(sockfd, 0); + brpc::SocketOptions options; + options.fd = sockfd; + options.keepalive_options = std::make_shared(); + options.keepalive_options->keepalive_idle_s = keepalive_idle; + brpc::SocketId id; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + CheckKeepalive(ptr->fd(), true, keepalive_idle, + default_keepalive_interval, + default_keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); + } + + // Enable keepalive and set keepalive interval. + { + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + ASSERT_GT(sockfd, 0); + brpc::SocketOptions options; + options.fd = sockfd; + options.keepalive_options = std::make_shared(); + options.keepalive_options->keepalive_interval_s = keepalive_interval; + brpc::SocketId id; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + CheckKeepalive(ptr->fd(), true, default_keepalive_idle, + keepalive_interval, default_keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); + } + + // Enable keepalive and set keepalive count. + { + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + ASSERT_GT(sockfd, 0); + brpc::SocketOptions options; + options.fd = sockfd; + options.keepalive_options = std::make_shared(); + options.keepalive_options->keepalive_count = keepalive_count; + brpc::SocketId id; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + CheckKeepalive(ptr->fd(), true, default_keepalive_idle, + default_keepalive_interval, keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); + } + + // Enable keepalive and set keepalive idle, interval, count. + { + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + ASSERT_GT(sockfd, 0); + brpc::SocketOptions options; + options.fd = sockfd; + options.keepalive_options = std::make_shared(); + options.keepalive_options->keepalive_idle_s = keepalive_idle; + options.keepalive_options->keepalive_interval_s = keepalive_interval; + options.keepalive_options->keepalive_count = keepalive_count; + brpc::SocketId id; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + CheckKeepalive(ptr->fd(), true, keepalive_idle, + keepalive_interval, keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); + } +} + +TEST_F(SocketTest, keepalive_input_message) { + // It is intentionally never deleted; mark it so it is not a reported leak. + brpc::Acceptor* messenger = new brpc::Acceptor; + ANNOTATE_LEAKING_OBJECT_PTR(messenger); + int listening_fd = -1; + butil::EndPoint point(butil::IP_ANY, 7878); + for (int i = 0; i < 100; ++i) { + point.port += i; + listening_fd = tcp_listen(point); + if (listening_fd >= 0) { + break; + } + } + ASSERT_GT(listening_fd, 0) << berror(); + ASSERT_EQ(0, butil::make_non_blocking(listening_fd)); + ASSERT_EQ(0, messenger->StartAccept(listening_fd, -1, NULL, false)); + + int default_keepalive = 0; + int default_keepalive_idle = 0; + int default_keepalive_interval = 0; + int default_keepalive_count = 0; + { + butil::fd_guard sockfd(socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_GT(sockfd, 0); + GetKeepaliveValue(sockfd, default_keepalive, default_keepalive_idle, + default_keepalive_interval, default_keepalive_count); + } + + // Disable keepalive. + { + brpc::SocketOptions options; + options.remote_side = point; + options.connect_on_create = true; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; + ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + ASSERT_GT(ptr->fd(), 0); + CheckNoKeepalive(ptr->fd()); + ASSERT_EQ(0, ptr->SetFailed()); + } + + // Enable keepalive. + brpc::FLAGS_socket_keepalive = true; + { + brpc::SocketOptions options; + options.remote_side = point; + options.connect_on_create = true; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; + ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + ASSERT_GT(ptr->fd(), 0); + CheckKeepalive(ptr->fd(), true, default_keepalive_idle, + default_keepalive_interval, default_keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); + } + + // Enable keepalive and set keepalive idle. + brpc::FLAGS_socket_keepalive_idle_s = 10; + { + brpc::SocketOptions options; + options.remote_side = point; + options.connect_on_create = true; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; + ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + ASSERT_GT(ptr->fd(), 0); + CheckKeepalive(ptr->fd(), true, brpc::FLAGS_socket_keepalive_idle_s, + default_keepalive_interval, default_keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); + } + + // Enable keepalive and set keepalive idle, interval. + brpc::FLAGS_socket_keepalive_interval_s = 10; + { + brpc::SocketOptions options; + options.remote_side = point; + options.connect_on_create = true; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; + ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + ASSERT_GT(ptr->fd(), 0); + CheckKeepalive(ptr->fd(), true, brpc::FLAGS_socket_keepalive_idle_s, + brpc::FLAGS_socket_keepalive_interval_s, default_keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); + } + + // Enable keepalive and set keepalive idle, interval, count. + brpc::FLAGS_socket_keepalive_count = 10; + { + brpc::SocketOptions options; + options.remote_side = point; + options.connect_on_create = true; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; + ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + ASSERT_GT(ptr->fd(), 0); + CheckKeepalive(ptr->fd(), true, brpc::FLAGS_socket_keepalive_idle_s, + brpc::FLAGS_socket_keepalive_interval_s, + brpc::FLAGS_socket_keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); + } + + // Options of keepalive set by user have priority over Gflags. + int keepalive_idle = 2; + int keepalive_interval = 2; + int keepalive_count = 2; + { + brpc::SocketOptions options; + options.remote_side = point; + options.connect_on_create = true; + options.keepalive_options = std::make_shared(); + options.keepalive_options->keepalive_idle_s = keepalive_idle; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; + ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + ASSERT_GT(ptr->fd(), 0); + CheckKeepalive(ptr->fd(), true, keepalive_idle, + brpc::FLAGS_socket_keepalive_interval_s, + brpc::FLAGS_socket_keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); + } + + { + brpc::SocketOptions options; + options.remote_side = point; + options.connect_on_create = true; + options.keepalive_options = std::make_shared(); + options.keepalive_options->keepalive_interval_s = keepalive_interval; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; + ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + ASSERT_GT(ptr->fd(), 0); + CheckKeepalive(ptr->fd(), true, brpc::FLAGS_socket_keepalive_idle_s, + keepalive_interval, brpc::FLAGS_socket_keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); + } + + { + brpc::SocketOptions options; + options.remote_side = point; + options.connect_on_create = true; + options.keepalive_options = std::make_shared(); + options.keepalive_options->keepalive_count = keepalive_count; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; + ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + ASSERT_GT(ptr->fd(), 0); + CheckKeepalive(ptr->fd(), true, brpc::FLAGS_socket_keepalive_idle_s, + brpc::FLAGS_socket_keepalive_interval_s, keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); + } + + { + brpc::SocketOptions options; + options.remote_side = point; + options.connect_on_create = true; + options.keepalive_options = std::make_shared(); + options.keepalive_options->keepalive_idle_s = keepalive_idle; + options.keepalive_options->keepalive_interval_s = keepalive_interval; + options.keepalive_options->keepalive_count = keepalive_count; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; + ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + ASSERT_GT(ptr->fd(), 0); + CheckKeepalive(ptr->fd(), true, keepalive_idle, + keepalive_interval, keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); + } + + messenger->StopAccept(0); + messenger->Join(); + ASSERT_EQ(-1, messenger->listened_fd()); + ASSERT_EQ(-1, fcntl(listening_fd, F_GETFD)); + ASSERT_EQ(EBADF, errno); +} + +#if defined(OS_LINUX) +void CheckTCPUserTimeout(int fd, int expect_tcp_user_timeout) { + int tcp_user_timeout = 0; + socklen_t len = sizeof(tcp_user_timeout); + ASSERT_EQ(0, getsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &tcp_user_timeout, &len) ); + ASSERT_EQ(tcp_user_timeout, expect_tcp_user_timeout); +} + +TEST_F(SocketTest, tcp_user_timeout) { + // It is intentionally never deleted; mark it so it is not a reported leak. + brpc::Acceptor* messenger = new brpc::Acceptor; + ANNOTATE_LEAKING_OBJECT_PTR(messenger); + int listening_fd = -1; + butil::EndPoint point(butil::IP_ANY, 7878); + for (int i = 0; i < 100; ++i) { + point.port += i; + listening_fd = tcp_listen(point); + if (listening_fd >= 0) { + break; + } + } + ASSERT_GT(listening_fd, 0) << berror(); + ASSERT_EQ(0, butil::make_non_blocking(listening_fd)); + ASSERT_EQ(0, messenger->StartAccept(listening_fd, -1, NULL, false)); + + { + brpc::SocketOptions options; + options.remote_side = point; + options.connect_on_create = true; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + CheckTCPUserTimeout(ptr->fd(), 0); + } + + { + int tcp_user_timeout_ms = 1000; + brpc::SocketOptions options; + options.remote_side = point; + options.connect_on_create = true; + options.tcp_user_timeout_ms = tcp_user_timeout_ms; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + CheckTCPUserTimeout(ptr->fd(), tcp_user_timeout_ms); + } + + brpc::FLAGS_socket_tcp_user_timeout_ms = 2000; + { + brpc::SocketOptions options; + options.remote_side = point; + options.connect_on_create = true; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; + ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + CheckTCPUserTimeout(ptr->fd(), brpc::FLAGS_socket_tcp_user_timeout_ms); + } + { + int tcp_user_timeout_ms = 3000; + brpc::SocketOptions options; + options.remote_side = point; + options.connect_on_create = true; + options.tcp_user_timeout_ms = tcp_user_timeout_ms; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; + ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + CheckTCPUserTimeout(ptr->fd(), tcp_user_timeout_ms); + } + + messenger->StopAccept(0); + messenger->Join(); + ASSERT_EQ(-1, messenger->listened_fd()); + ASSERT_EQ(-1, fcntl(listening_fd, F_GETFD)); + ASSERT_EQ(EBADF, errno); +} +#endif + +int HandleSocketSuccessWrite(bthread_id_t id, void* data, int error_code, + const std::string& error_text) { + auto success_count = static_cast(data); + EXPECT_NE(nullptr, success_count); + EXPECT_EQ(0, error_code); + ++(*success_count); + CHECK_EQ(0, bthread_id_unlock_and_destroy(id)); + return 0; +} + +TEST_F(SocketTest, notify_on_success) { + const size_t REP = 10000; + int fds[2]; + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds)); + + brpc::SocketId id = 8888; + butil::EndPoint dummy; + ASSERT_EQ(0, str2endpoint("192.168.1.26:8080", &dummy)); + brpc::SocketOptions options; + options.fd = fds[1]; + options.remote_side = dummy; + options.user = new CheckRecycle; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + brpc::SocketUniquePtr s; + ASSERT_EQ(0, brpc::Socket::Address(id, &s)); + s->_ssl_state = brpc::SSL_OFF; + ASSERT_EQ(2, brpc::NRefOfVRef(s->_versioned_ref)); + global_sock = s.get(); + ASSERT_TRUE(s.get()); + ASSERT_EQ(fds[1], s->fd()); + ASSERT_EQ(dummy, s->remote_side()); + ASSERT_EQ(id, s->id()); + + pthread_t rth; + ReaderArg reader_arg = { fds[0], 0 }; + pthread_create(&rth, NULL, reader, &reader_arg); + + size_t success_count = 0; + char buf[] = "hello reader side!"; + for (size_t c = 0; c < REP; ++c) { + bthread_id_t write_id; + ASSERT_EQ(0, bthread_id_create2(&write_id, &success_count, + HandleSocketSuccessWrite)); + brpc::Socket::WriteOptions wopt; + wopt.id_wait = write_id; + wopt.notify_on_success = true; + butil::IOBuf src; + src.append(buf, 16); + if (s->Write(&src, &wopt) != 0) { + if (errno == brpc::EOVERCROWDED) { + // The buf is full, sleep a while and retry. + bthread_usleep(1000); + --c; + continue; + } + PLOG(ERROR) << "Fail to write into SocketId=" << id; + break; + } + } + bthread_usleep(1000 * 1000); + + ASSERT_EQ(0, s->SetFailed()); + s.release()->Dereference(); + pthread_join(rth, NULL); + ASSERT_EQ(REP, success_count); + ASSERT_EQ((brpc::Socket*)NULL, global_sock); + close(fds[0]); +} + +struct ShutdownWriterArg { + size_t times; + brpc::SocketId socket_id; + butil::atomic total_count; + butil::atomic success_count; +}; + +int HandleSocketShutdownWrite(bthread_id_t id, void* data, int error_code, + const std::string& error_text) { + auto arg = static_cast(data); + EXPECT_NE(nullptr, arg); + EXPECT_TRUE(0 == error_code || brpc::ESHUTDOWNWRITE == error_code) << error_code; + ++arg->total_count; + if (0 == error_code) { + ++arg->success_count; + } + CHECK_EQ(0, bthread_id_unlock_and_destroy(id)); + return 0; +} + +void* ShutdownWriter(void* void_arg) { + auto arg = static_cast(void_arg); + brpc::SocketUniquePtr sock; + if (brpc::Socket::Address(arg->socket_id, &sock) < 0) { + LOG(INFO) << "Fail to address SocketId=" << arg->socket_id; + return NULL; + } + for (size_t c = 0; c < arg->times; ++c) { + bthread_id_t write_id; + EXPECT_EQ(0, bthread_id_create2(&write_id, arg, + HandleSocketShutdownWrite)); + brpc::Socket::WriteOptions wopt; + wopt.id_wait = write_id; + wopt.notify_on_success = true; + wopt.shutdown_write = true; + butil::IOBuf src; + src.push_back('a'); + if (sock->Write(&src, &wopt) != 0) { + if (errno == brpc::EOVERCROWDED) { + // The buf is full, sleep a while and retry. + bthread_usleep(1000); + --c; + continue; + } + } + } + return NULL; +} + +void TestShutdownWrite() { + const size_t REP = 100; + int fds[2]; + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds)); + + brpc::SocketId id = 8888; + butil::EndPoint dummy; + ASSERT_EQ(0, str2endpoint("192.168.1.26:8080", &dummy)); + brpc::SocketOptions options; + options.fd = fds[1]; + options.remote_side = dummy; + options.user = new CheckRecycle; + ASSERT_EQ(0, brpc::Socket::Create(options, &id)); + brpc::SocketUniquePtr s; + ASSERT_EQ(0, brpc::Socket::Address(id, &s)); + s->_ssl_state = brpc::SSL_OFF; + ASSERT_EQ(2, brpc::NRefOfVRef(s->_versioned_ref)); + global_sock = s.get(); + ASSERT_TRUE(s.get()); + ASSERT_EQ(fds[1], s->fd()); + ASSERT_EQ(dummy, s->remote_side()); + ASSERT_EQ(id, s->id()); + ASSERT_FALSE(s->IsWriteShutdown()); + + pthread_t rth; + ReaderArg reader_arg = { fds[0], 0 }; + pthread_create(&rth, NULL, reader, &reader_arg); + + bthread_t th[3]; + ShutdownWriterArg args[ARRAY_SIZE(th)]; + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + args[i].times = REP; + args[i].socket_id = id; + args[i].total_count = 0; + args[i].success_count = 0; + bthread_start_background(&th[i], NULL, ShutdownWriter, &args[i]); + } + + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, bthread_join(th[i], NULL)); + } + bthread_usleep(50 * 1000); + + ASSERT_TRUE(s->IsWriteShutdown()); + ASSERT_FALSE(s->Failed()); + ASSERT_EQ(0, s->SetFailed()); + s.release()->Dereference(); + pthread_join(rth, NULL); + ASSERT_EQ((brpc::Socket*)NULL, global_sock); + close(fds[0]); + + size_t total_count = 0; + size_t success_count = 0; + for (auto & arg : args) { + total_count += arg.total_count; + success_count += arg.success_count; + } + ASSERT_EQ(REP * ARRAY_SIZE(th), total_count); + EXPECT_EQ((size_t)1, reader_arg.nread); + EXPECT_EQ((size_t)1, success_count); +} + +TEST_F(SocketTest, shutdown_write) { + for (int i = 0; i < 100; ++i) { + TestShutdownWrite(); + } +} + +TEST_F(SocketTest, packed_ptr) { + brpc::PackedPtr ptr; + ASSERT_EQ(nullptr, ptr.get()); + ASSERT_EQ(0, ptr.extra()); + + int a = 1; + uint16_t b = 2; + ptr.set(&a); + ASSERT_EQ(&a, ptr.get()); + *ptr.get() = b; + ASSERT_EQ(a, b); + ptr.set_extra(b); + ASSERT_EQ(b, ptr.extra()); + ptr.reset(); + ptr.reset_extra(); + ASSERT_EQ(nullptr, ptr.get()); + ASSERT_EQ(0, ptr.extra()); + + int c = 3; + uint16_t d = 4; + ptr.set_ptr_and_extra(&c, d); + ASSERT_EQ(&c, ptr.get()); + ASSERT_EQ(d, ptr.extra()); + ptr.reset_ptr_and_extra(); + ASSERT_EQ(nullptr, ptr.get()); + ASSERT_EQ(0, ptr.extra()); +} diff --git a/test/brpc_sofa_pbrpc_protocol_unittest.cpp b/test/brpc_sofa_pbrpc_protocol_unittest.cpp new file mode 100644 index 0000000..4cf91b4 --- /dev/null +++ b/test/brpc_sofa_pbrpc_protocol_unittest.cpp @@ -0,0 +1,347 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: Sun Jul 13 15:04:18 CST 2014 + +#include +#include +#include +#include +#include +#include +#include "butil/time.h" +#include "butil/macros.h" +#include "brpc/socket.h" +#include "brpc/acceptor.h" +#include "brpc/server.h" +#include "brpc/policy/sofa_pbrpc_meta.pb.h" +#include "brpc/policy/sofa_pbrpc_protocol.h" +#include "brpc/policy/most_common_message.h" +#include "brpc/controller.h" +#include "echo.pb.h" + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + return RUN_ALL_TESTS(); +} + +namespace { + +static const std::string EXP_REQUEST = "hello"; +static const std::string EXP_RESPONSE = "world"; + +static const std::string MOCK_CREDENTIAL = "mock credential"; +static const std::string MOCK_USER = "mock user"; + +class MyAuthenticator : public brpc::Authenticator { +public: + MyAuthenticator() {} + + int GenerateCredential(std::string* auth_str) const { + *auth_str = MOCK_CREDENTIAL; + return 0; + } + + int VerifyCredential(const std::string& auth_str, + const butil::EndPoint&, + brpc::AuthContext* ctx) const { + EXPECT_EQ(MOCK_CREDENTIAL, auth_str); + ctx->set_user(MOCK_USER); + return 0; + } +}; + +class MyEchoService : public ::test::EchoService { + void Echo(::google::protobuf::RpcController* cntl_base, + const ::test::EchoRequest* req, + ::test::EchoResponse* res, + ::google::protobuf::Closure* done) { + brpc::Controller* cntl = static_cast(cntl_base); + brpc::ClosureGuard done_guard(done); + + if (req->close_fd()) { + cntl->CloseConnection("Close connection according to request"); + return; + } + EXPECT_EQ(EXP_REQUEST, req->message()); + res->set_message(EXP_RESPONSE); + } +}; + +class SofaTest : public ::testing::Test{ +protected: + SofaTest() { + EXPECT_EQ(0, _server.AddService( + &_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + // Hack: Regard `_server' as running + _server._status = brpc::Server::RUNNING; + // Sofa doesn't support authentication + // _server._options.auth = &_auth; + + EXPECT_EQ(0, pipe(_pipe_fds)); + + brpc::SocketId id; + brpc::SocketOptions options; + options.fd = _pipe_fds[1]; + EXPECT_EQ(0, brpc::Socket::Create(options, &id)); + EXPECT_EQ(0, brpc::Socket::Address(id, &_socket)); + }; + + virtual ~SofaTest() {}; + virtual void SetUp() {}; + virtual void TearDown() {}; + + void VerifyMessage(brpc::InputMessageBase* msg) { + if (msg->_socket == NULL) { + _socket->ReAddress(&msg->_socket); + } + msg->_arg = &_server; + EXPECT_TRUE(brpc::policy::VerifySofaRequest(msg)); + } + + void ProcessMessage(void (*process)(brpc::InputMessageBase*), + brpc::InputMessageBase* msg, bool set_eof) { + if (msg->_socket == NULL) { + _socket->ReAddress(&msg->_socket); + } + msg->_arg = &_server; + _socket->PostponeEOF(); + if (set_eof) { + _socket->SetEOF(); + } + (*process)(msg); + } + + brpc::policy::MostCommonMessage* MakeRequestMessage( + const brpc::policy::SofaRpcMeta& meta) { + brpc::policy::MostCommonMessage* msg = + brpc::policy::MostCommonMessage::Get(); + butil::IOBufAsZeroCopyOutputStream meta_stream(&msg->meta); + EXPECT_TRUE(meta.SerializeToZeroCopyStream(&meta_stream)); + + test::EchoRequest req; + req.set_message(EXP_REQUEST); + butil::IOBufAsZeroCopyOutputStream req_stream(&msg->payload); + EXPECT_TRUE(req.SerializeToZeroCopyStream(&req_stream)); + return msg; + } + + brpc::policy::MostCommonMessage* MakeResponseMessage( + const brpc::policy::SofaRpcMeta& meta) { + brpc::policy::MostCommonMessage* msg = + brpc::policy::MostCommonMessage::Get(); + butil::IOBufAsZeroCopyOutputStream meta_stream(&msg->meta); + EXPECT_TRUE(meta.SerializeToZeroCopyStream(&meta_stream)); + + test::EchoResponse res; + res.set_message(EXP_RESPONSE); + butil::IOBufAsZeroCopyOutputStream res_stream(&msg->payload); + EXPECT_TRUE(res.SerializeToZeroCopyStream(&res_stream)); + return msg; + } + + void CheckResponseCode(bool expect_empty, int expect_code) { + int bytes_in_pipe = 0; + ioctl(_pipe_fds[0], FIONREAD, &bytes_in_pipe); + if (expect_empty) { + EXPECT_EQ(0, bytes_in_pipe); + return; + } + + EXPECT_GT(bytes_in_pipe, 0); + butil::IOPortal buf; + EXPECT_EQ((ssize_t)bytes_in_pipe, + buf.append_from_file_descriptor(_pipe_fds[0], 1024)); + brpc::ParseResult pr = brpc::policy::ParseSofaMessage(&buf, NULL, false, NULL); + EXPECT_EQ(brpc::PARSE_OK, pr.error()); + brpc::policy::MostCommonMessage* msg = + static_cast(pr.message()); + + brpc::policy::SofaRpcMeta meta; + butil::IOBufAsZeroCopyInputStream meta_stream(msg->meta); + EXPECT_TRUE(meta.ParseFromZeroCopyStream(&meta_stream)); + EXPECT_EQ(expect_code, meta.error_code()); + } + + void TestSofaCompress(brpc::CompressType type) { + butil::IOBuf request_buf; + butil::IOBuf total_buf; + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + cntl._response = &res; + + req.set_message(EXP_REQUEST); + cntl.set_request_compress_type(type); + brpc::SerializeRequestDefault(&request_buf, &cntl, &req); + ASSERT_FALSE(cntl.Failed()); + brpc::policy::PackSofaRequest( + &total_buf, NULL, cntl.call_id().value, + test::EchoService::descriptor()->method(0), + &cntl, request_buf, &_auth); + ASSERT_FALSE(cntl.Failed()); + + brpc::ParseResult req_pr = + brpc::policy::ParseSofaMessage(&total_buf, NULL, false, NULL); + ASSERT_EQ(brpc::PARSE_OK, req_pr.error()); + brpc::InputMessageBase* req_msg = req_pr.message(); + ProcessMessage(brpc::policy::ProcessSofaRequest, req_msg, false); + CheckResponseCode(false, 0); + } + + int _pipe_fds[2]; + brpc::SocketUniquePtr _socket; + brpc::Server _server; + + MyEchoService _svc; + MyAuthenticator _auth; +}; + +TEST_F(SofaTest, process_request_failed_socket) { + brpc::policy::SofaRpcMeta meta; + meta.set_type(brpc::policy::SofaRpcMeta::REQUEST); + meta.set_sequence_id(0); + meta.set_method("EchoService.Echo"); + brpc::policy::MostCommonMessage* msg = MakeRequestMessage(meta); + _socket->SetFailed(); + ProcessMessage(brpc::policy::ProcessSofaRequest, msg, false); + ASSERT_EQ(0ll, _server._nerror_bvar.get_value()); + CheckResponseCode(true, 0); +} + +TEST_F(SofaTest, process_request_logoff) { + brpc::policy::SofaRpcMeta meta; + meta.set_type(brpc::policy::SofaRpcMeta::REQUEST); + meta.set_sequence_id(0); + meta.set_method("EchoService.Echo"); + brpc::policy::MostCommonMessage* msg = MakeRequestMessage(meta); + _server._status = brpc::Server::READY; + ProcessMessage(brpc::policy::ProcessSofaRequest, msg, false); + ASSERT_EQ(1ll, _server._nerror_bvar.get_value()); + CheckResponseCode(false, brpc::ELOGOFF); +} + +TEST_F(SofaTest, process_request_wrong_method) { + brpc::policy::SofaRpcMeta meta; + meta.set_type(brpc::policy::SofaRpcMeta::REQUEST); + meta.set_sequence_id(0); + meta.set_method("EchoService.NO_SUCH_METHOD"); + brpc::policy::MostCommonMessage* msg = MakeRequestMessage(meta); + ProcessMessage(brpc::policy::ProcessSofaRequest, msg, false); + ASSERT_EQ(1ll, _server._nerror_bvar.get_value()); + CheckResponseCode(false, brpc::ENOMETHOD); +} + +TEST_F(SofaTest, process_response_after_eof) { + brpc::policy::SofaRpcMeta meta; + test::EchoResponse res; + brpc::Controller cntl; + meta.set_type(brpc::policy::SofaRpcMeta::RESPONSE); + meta.set_sequence_id(cntl.call_id().value); + cntl._response = &res; + brpc::policy::MostCommonMessage* msg = MakeResponseMessage(meta); + ProcessMessage(brpc::policy::ProcessSofaResponse, msg, true); + ASSERT_EQ(EXP_RESPONSE, res.message()); + ASSERT_TRUE(_socket->Failed()); +} + +TEST_F(SofaTest, process_response_error_code) { + const int ERROR_CODE = 12345; + brpc::policy::SofaRpcMeta meta; + brpc::Controller cntl; + meta.set_type(brpc::policy::SofaRpcMeta::RESPONSE); + meta.set_sequence_id(cntl.call_id().value); + meta.set_error_code(ERROR_CODE); + brpc::policy::MostCommonMessage* msg = MakeResponseMessage(meta); + ProcessMessage(brpc::policy::ProcessSofaResponse, msg, false); + ASSERT_EQ(ERROR_CODE, cntl.ErrorCode()); +} + +TEST_F(SofaTest, complete_flow) { + butil::IOBuf request_buf; + butil::IOBuf total_buf; + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + cntl._response = &res; + + // Send request + req.set_message(EXP_REQUEST); + brpc::SerializeRequestDefault(&request_buf, &cntl, &req); + ASSERT_FALSE(cntl.Failed()); + brpc::policy::PackSofaRequest( + &total_buf, NULL, cntl.call_id().value, + test::EchoService::descriptor()->method(0), &cntl, request_buf, &_auth); + ASSERT_FALSE(cntl.Failed()); + + // Verify and handle request + brpc::ParseResult req_pr = + brpc::policy::ParseSofaMessage(&total_buf, NULL, false, NULL); + ASSERT_EQ(brpc::PARSE_OK, req_pr.error()); + brpc::InputMessageBase* req_msg = req_pr.message(); + VerifyMessage(req_msg); + ProcessMessage(brpc::policy::ProcessSofaRequest, req_msg, false); + + // Read response from pipe + butil::IOPortal response_buf; + response_buf.append_from_file_descriptor(_pipe_fds[0], 1024); + brpc::ParseResult res_pr = + brpc::policy::ParseSofaMessage(&response_buf, NULL, false, NULL); + ASSERT_EQ(brpc::PARSE_OK, res_pr.error()); + brpc::InputMessageBase* res_msg = res_pr.message(); + ProcessMessage(brpc::policy::ProcessSofaResponse, res_msg, false); + + ASSERT_FALSE(cntl.Failed()); + ASSERT_EQ(EXP_RESPONSE, res.message()); +} + +TEST_F(SofaTest, close_in_callback) { + butil::IOBuf request_buf; + butil::IOBuf total_buf; + brpc::Controller cntl; + test::EchoRequest req; + + // Send request + req.set_message(EXP_REQUEST); + req.set_close_fd(true); + brpc::SerializeRequestDefault(&request_buf, &cntl, &req); + ASSERT_FALSE(cntl.Failed()); + brpc::policy::PackSofaRequest( + &total_buf, NULL, cntl.call_id().value, + test::EchoService::descriptor()->method(0), &cntl, request_buf, &_auth); + ASSERT_FALSE(cntl.Failed()); + + // Handle request + brpc::ParseResult req_pr = + brpc::policy::ParseSofaMessage(&total_buf, NULL, false, NULL); + ASSERT_EQ(brpc::PARSE_OK, req_pr.error()); + brpc::InputMessageBase* req_msg = req_pr.message(); + ProcessMessage(brpc::policy::ProcessSofaRequest, req_msg, false); + + // Socket should be closed + ASSERT_TRUE(_socket->Failed()); +} + +TEST_F(SofaTest, sofa_compress) { + TestSofaCompress(brpc::COMPRESS_TYPE_SNAPPY); + TestSofaCompress(brpc::COMPRESS_TYPE_GZIP); + TestSofaCompress(brpc::COMPRESS_TYPE_ZLIB); +} +} //namespace diff --git a/test/brpc_ssl_unittest.cpp b/test/brpc_ssl_unittest.cpp new file mode 100644 index 0000000..6512eea --- /dev/null +++ b/test/brpc_ssl_unittest.cpp @@ -0,0 +1,571 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Baidu RPC - A framework to host and access services throughout Baidu. + +// Date: Sun Jul 13 15:04:18 CST 2014 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "brpc/global.h" +#include "brpc/socket.h" +#include "brpc/server.h" +#include "brpc/channel.h" +#include "brpc/socket_map.h" +#include "brpc/controller.h" +#include "brpc/details/ssl_helper.h" +#include "echo.pb.h" + +namespace brpc { + +void ExtractHostnames(X509* x, std::vector* hostnames); +} // namespace brpc + + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + brpc::GlobalInitializeOrDie(); + return RUN_ALL_TESTS(); +} + +bool g_delete = false; +const std::string EXP_REQUEST = "hello"; +const std::string EXP_RESPONSE = "world"; + +class EchoServiceImpl : public test::EchoService { +public: + EchoServiceImpl() : count(0) {} + ~EchoServiceImpl() override { g_delete = true; } + void Echo(google::protobuf::RpcController* cntl_base, + const test::EchoRequest* request, + test::EchoResponse* response, + google::protobuf::Closure* done) override { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = (brpc::Controller*)cntl_base; + count.fetch_add(1, butil::memory_order_relaxed); + EXPECT_EQ(EXP_REQUEST, request->message()); + EXPECT_TRUE(cntl->is_ssl()); + + response->set_message(EXP_RESPONSE); + if (request->sleep_us() > 0) { + LOG(INFO) << "Sleep " << request->sleep_us() << " us, protocol=" + << cntl->request_protocol(); + bthread_usleep(request->sleep_us()); + } + } + + butil::atomic count; +}; + +class SSLTest : public ::testing::Test{ +protected: + SSLTest() {}; + virtual ~SSLTest(){}; + virtual void SetUp() {}; + virtual void TearDown() {}; +}; + +void* RunClosure(void* arg) { + google::protobuf::Closure* done = (google::protobuf::Closure*)arg; + done->Run(); + return NULL; +} + +void SendMultipleRPC(brpc::Channel* channel, int count) { + for (int i = 0; i < count; ++i) { + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(EXP_REQUEST); + test::EchoService_Stub stub(channel); + stub.Echo(&cntl, &req, &res, NULL); + + EXPECT_EQ(EXP_RESPONSE, res.message()) << cntl.ErrorText(); + } +} + +TEST_F(SSLTest, sanity) { + // Test RPC based on SSL + brpc protocol + const int port = 8613; + brpc::Server server; + brpc::ServerOptions options; + + brpc::CertInfo cert; + cert.certificate = "cert1.crt"; + cert.private_key = "cert1.key"; + options.mutable_ssl_options()->default_cert = cert; + + EchoServiceImpl echo_svc; + ASSERT_EQ(0, server.AddService( + &echo_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start(port, &options)); + + test::EchoRequest req; + test::EchoResponse res; + req.set_message(EXP_REQUEST); + { + brpc::Channel channel; + brpc::ChannelOptions coptions; + coptions.mutable_ssl_options(); + coptions.mutable_ssl_options()->sni_name = "localhost"; + ASSERT_EQ(0, channel.Init("localhost", port, &coptions)); + + brpc::Controller cntl; + test::EchoService_Stub stub(&channel); + stub.Echo(&cntl, &req, &res, NULL); + EXPECT_EQ(EXP_RESPONSE, res.message()) << cntl.ErrorText(); + } + + // stress test + const int NUM = 5; + const int COUNT = 3000; + pthread_t tids[NUM]; + { + brpc::Channel channel; + brpc::ChannelOptions coptions; + coptions.mutable_ssl_options(); + coptions.mutable_ssl_options()->sni_name = "localhost"; + ASSERT_EQ(0, channel.Init("127.0.0.1", port, &coptions)); + for (int i = 0; i < NUM; ++i) { + google::protobuf::Closure* thrd_func = + brpc::NewCallback(SendMultipleRPC, &channel, COUNT); + EXPECT_EQ(0, pthread_create(&tids[i], NULL, RunClosure, thrd_func)); + } + for (int i = 0; i < NUM; ++i) { + pthread_join(tids[i], NULL); + } + } + { + // Use HTTP + brpc::Channel channel; + brpc::ChannelOptions coptions; + coptions.protocol = "http"; + coptions.mutable_ssl_options(); + coptions.mutable_ssl_options()->sni_name = "localhost"; + ASSERT_EQ(0, channel.Init("127.0.0.1", port, &coptions)); + for (int i = 0; i < NUM; ++i) { + google::protobuf::Closure* thrd_func = + brpc::NewCallback(SendMultipleRPC, &channel, COUNT); + EXPECT_EQ(0, pthread_create(&tids[i], NULL, RunClosure, thrd_func)); + } + for (int i = 0; i < NUM; ++i) { + pthread_join(tids[i], NULL); + } + } + + ASSERT_EQ(0, server.Stop(0)); + ASSERT_EQ(0, server.Join()); +} + +TEST_F(SSLTest, force_ssl) { + const int port = 8613; + brpc::Server server; + brpc::ServerOptions options; + EchoServiceImpl echo_svc; + ASSERT_EQ(0, server.AddService( + &echo_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + + options.force_ssl = true; + ASSERT_EQ(-1, server.Start(port, &options)); + + brpc::CertInfo cert; + cert.certificate = "cert1.crt"; + cert.private_key = "cert1.key"; + options.mutable_ssl_options()->default_cert = cert; + + ASSERT_EQ(0, server.Start(port, &options)); + + test::EchoRequest req; + req.set_message(EXP_REQUEST); + { + brpc::Channel channel; + brpc::ChannelOptions coptions; + coptions.mutable_ssl_options(); + coptions.mutable_ssl_options()->sni_name = "localhost"; + ASSERT_EQ(0, channel.Init("localhost", port, &coptions)); + + brpc::Controller cntl; + test::EchoService_Stub stub(&channel); + test::EchoResponse res; + stub.Echo(&cntl, &req, &res, NULL); + ASSERT_EQ(EXP_RESPONSE, res.message()) << cntl.ErrorText(); + } + + { + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("localhost", port, NULL)); + + brpc::Controller cntl; + test::EchoService_Stub stub(&channel); + test::EchoResponse res; + stub.Echo(&cntl, &req, &res, NULL); + ASSERT_TRUE(cntl.Failed()); + } + + ASSERT_EQ(0, server.Stop(0)); + ASSERT_EQ(0, server.Join()); +} + +void ProcessResponse(brpc::InputMessageBase* msg_base) { + brpc::DestroyingPtr msg( + static_cast(msg_base)); + brpc::policy::RpcMeta meta; + ASSERT_TRUE(brpc::ParsePbFromIOBuf(&meta, msg->meta)); + const brpc::policy::RpcResponseMeta &response_meta = meta.response(); + ASSERT_EQ(0, response_meta.error_code()) << response_meta.error_text(); + + const brpc::CallId cid = { static_cast(meta.correlation_id()) }; + brpc::Controller* cntl = NULL; + ASSERT_EQ(0, bthread_id_lock(cid, (void**)&cntl)); + ASSERT_NE(nullptr, cntl); + ASSERT_TRUE(brpc::ParsePbFromIOBuf(cntl->response(), msg->payload)); + ASSERT_EQ(0, bthread_id_unlock_and_destroy(cid)); +} + +TEST_F(SSLTest, connect_on_create) { + brpc::Protocol dummy_protocol = { + brpc::policy::ParseRpcMessage, brpc::SerializeRequestDefault, + brpc::policy::PackRpcRequest,NULL, ProcessResponse, + NULL, NULL, NULL, brpc::CONNECTION_TYPE_ALL, "ssl_ut_baidu" + }; + ASSERT_EQ(0, RegisterProtocol((brpc::ProtocolType)30, dummy_protocol)); + + brpc::InputMessageHandler dummy_handler ={ + dummy_protocol.parse, dummy_protocol.process_response, + NULL, NULL, dummy_protocol.name + }; + brpc::InputMessenger messenger; + ASSERT_EQ(0, messenger.AddHandler(dummy_handler)); + + const int port = 8613; + brpc::Server server; + brpc::ServerOptions server_options; + server_options.force_ssl = true; + + brpc::CertInfo cert; + cert.certificate = "cert1.crt"; + cert.private_key = "cert1.key"; + server_options.mutable_ssl_options()->default_cert = cert; + + EchoServiceImpl echo_svc; + ASSERT_EQ(0, server.AddService( + &echo_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start(port, &server_options)); + + // Create client socket. + brpc::SocketOptions socket_options; + butil::EndPoint ep(butil::IP_ANY, port); + socket_options.remote_side = ep; + socket_options.connect_on_create = true; + socket_options.on_edge_triggered_events = brpc::InputMessenger::OnNewMessages; + socket_options.user = &messenger; + brpc::ChannelSSLOptions ssl_options; + SSL_CTX* raw_ctx = brpc::CreateClientSSLContext(ssl_options); + ASSERT_NE(nullptr, raw_ctx); + std::shared_ptr ssl_ctx + = std::make_shared(); + ssl_ctx->raw_ctx = raw_ctx; + socket_options.initial_ssl_ctx = ssl_ctx; + + brpc::SocketId socket_id; + ASSERT_EQ(0, brpc::Socket::Create(socket_options, &socket_id)); + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(socket_id, &ptr)); + + test::EchoRequest req; + req.set_message(EXP_REQUEST); + for (int i = 0; i < 100; ++i) { + test::EchoResponse res; + butil::IOBuf request_buf; + butil::IOBuf request_body; + brpc::Controller cntl; + cntl._response = &res; + const brpc::CallId correlation_id = cntl.call_id(); + brpc::SerializeRequestDefault(&request_body, &cntl, &req); + brpc::policy::PackRpcRequest(&request_buf, NULL, correlation_id.value, + test::EchoService_Stub::descriptor()->method(0), + &cntl, request_body, NULL); + ASSERT_EQ(0, ptr->Write(&request_buf)); + brpc::Join(correlation_id); + ASSERT_EQ(EXP_RESPONSE, res.message()); + } + + ptr->SetFailed(); + ptr.reset(); + ASSERT_EQ(0, server.Stop(0)); + ASSERT_EQ(0, server.Join()); +} + +void CheckCert(const char* cname, const char* cert) { + const int port = 8613; + brpc::Channel channel; + brpc::ChannelOptions coptions; + coptions.mutable_ssl_options()->sni_name = cname; + ASSERT_EQ(0, channel.Init("127.0.0.1", port, &coptions)); + + SendMultipleRPC(&channel, 1); + // client has no access to the sending socket + std::vector ids; + brpc::SocketMapList(&ids); + ASSERT_EQ(1u, ids.size()); + brpc::SocketUniquePtr sock; + ASSERT_EQ(0, brpc::Socket::Address(ids[0], &sock)); + + X509* x509 = sock->GetPeerCertificate(); + ASSERT_TRUE(x509 != NULL); + std::vector cnames; + brpc::ExtractHostnames(x509, &cnames); + ASSERT_EQ(cert, cnames[0]) << x509; + X509_free(x509); +} + +std::string GetRawPemString(const char* fname) { + butil::ScopedFILE fp(fname, "r"); + char buf[4096]; + int size = read(fileno(fp), buf, sizeof(buf)); + std::string raw; + raw.append(buf, size); + return raw; +} + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + +TEST_F(SSLTest, ssl_sni) { + const int port = 8613; + brpc::Server server; + brpc::ServerOptions options; + { + brpc::CertInfo cert; + cert.certificate = "cert1.crt"; + cert.private_key = "cert1.key"; + cert.sni_filters.push_back("cert1.com"); + options.mutable_ssl_options()->default_cert = cert; + } + { + brpc::CertInfo cert; + cert.certificate = GetRawPemString("cert2.crt"); + cert.private_key = GetRawPemString("cert2.key"); + cert.sni_filters.push_back("*.cert2.com"); + options.mutable_ssl_options()->certs.push_back(cert); + } + EchoServiceImpl echo_svc; + ASSERT_EQ(0, server.AddService( + &echo_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start(port, &options)); + + CheckCert("cert1.com", "cert1"); + CheckCert("www.cert2.com", "cert2"); + CheckCert("noexist", "cert1"); // default cert + + server.Stop(0); + server.Join(); +} + +TEST_F(SSLTest, ssl_reload) { + const int port = 8613; + brpc::Server server; + brpc::ServerOptions options; + { + brpc::CertInfo cert; + cert.certificate = "cert1.crt"; + cert.private_key = "cert1.key"; + cert.sni_filters.push_back("cert1.com"); + options.mutable_ssl_options()->default_cert = cert; + } + EchoServiceImpl echo_svc; + ASSERT_EQ(0, server.AddService( + &echo_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start(port, &options)); + + CheckCert("cert2.com", "cert1"); // default cert + { + brpc::CertInfo cert; + cert.certificate = GetRawPemString("cert2.crt"); + cert.private_key = GetRawPemString("cert2.key"); + cert.sni_filters.push_back("cert2.com"); + ASSERT_EQ(0, server.AddCertificate(cert)); + } + CheckCert("cert2.com", "cert2"); + + { + brpc::CertInfo cert; + cert.certificate = GetRawPemString("cert2.crt"); + cert.private_key = GetRawPemString("cert2.key"); + ASSERT_EQ(0, server.RemoveCertificate(cert)); + } + CheckCert("cert2.com", "cert1"); // default cert after remove cert2 + + { + brpc::CertInfo cert; + cert.certificate = GetRawPemString("cert2.crt"); + cert.private_key = GetRawPemString("cert2.key"); + cert.sni_filters.push_back("cert2.com"); + std::vector certs; + certs.push_back(cert); + ASSERT_EQ(0, server.ResetCertificates(certs)); + } + CheckCert("cert2.com", "cert2"); + + server.Stop(0); + server.Join(); +} + +#endif // SSL_CTRL_SET_TLSEXT_HOSTNAME + +const int BUFSIZE[] = {64, 128, 256, 1024, 4096}; +const int REP = 100000; + +void* ssl_perf_client(void* arg) { + SSL* ssl = (SSL*)arg; + EXPECT_EQ(1, SSL_do_handshake(ssl)); + + char buf[4096]; + butil::Timer tm; + for (size_t i = 0; i < ARRAY_SIZE(BUFSIZE); ++i) { + int size = BUFSIZE[i]; + tm.start(); + for (int j = 0; j < REP; ++j) { + SSL_write(ssl, buf, size); + } + tm.stop(); + LOG(INFO) << "SSL_write(" << size << ") tp=" + << size * REP / tm.u_elapsed() << "M/s" + << ", latency=" << tm.u_elapsed() / REP << "us"; + } + return NULL; +} + +void* ssl_perf_server(void* arg) { + SSL* ssl = (SSL*)arg; + EXPECT_EQ(1, SSL_do_handshake(ssl)); + char buf[4096]; + for (size_t i = 0; i < ARRAY_SIZE(BUFSIZE); ++i) { + int size = BUFSIZE[i]; + for (int j = 0; j < REP; ++j) { + SSL_read(ssl, buf, size); + } + } + return NULL; +} + +TEST_F(SSLTest, ssl_perf) { + const butil::EndPoint ep(butil::IP_ANY, 5961); + butil::fd_guard listenfd(butil::tcp_listen(ep)); + ASSERT_GT(listenfd, 0); + int clifd = tcp_connect(ep, NULL); + ASSERT_GT(clifd, 0); + int servfd = accept(listenfd, NULL, NULL); + ASSERT_GT(servfd, 0); + + brpc::ChannelSSLOptions opt; + SSL_CTX* cli_ctx = brpc::CreateClientSSLContext(opt); + SSL_CTX* serv_ctx = + brpc::CreateServerSSLContext("cert1.crt", "cert1.key", + brpc::SSLOptions(), NULL, NULL); + SSL* cli_ssl = brpc::CreateSSLSession(cli_ctx, 0, clifd, false); +#if defined(SSL_CTRL_SET_TLSEXT_HOSTNAME) || defined(USE_MESALINK) + SSL_set_tlsext_host_name(cli_ssl, "localhost"); +#endif + SSL* serv_ssl = brpc::CreateSSLSession(serv_ctx, 0, servfd, true); + pthread_t cpid; + pthread_t spid; + ASSERT_EQ(0, pthread_create(&cpid, NULL, ssl_perf_client, cli_ssl)); + ASSERT_EQ(0, pthread_create(&spid, NULL, ssl_perf_server , serv_ssl)); + ASSERT_EQ(0, pthread_join(cpid, NULL)); + ASSERT_EQ(0, pthread_join(spid, NULL)); + + SSL_free(cli_ssl); + SSL_free(serv_ssl); + SSL_CTX_free(cli_ctx); + SSL_CTX_free(serv_ctx); + close(clifd); + close(servfd); +} + + +#ifdef TLS1_3_VERSION + +void* tls13_do_handshake(void* arg) { + SSL* ssl = (SSL*)arg; + EXPECT_EQ(1, SSL_do_handshake(ssl)); + return NULL; +} + +TEST_F(SSLTest, tls13_protocol_string) { + // Same style as ssl_perf: direct SSL handshake, no SocketMap / socket internals. + const butil::EndPoint ep(butil::IP_ANY, 8613); + butil::fd_guard listenfd(butil::tcp_listen(ep)); + ASSERT_GT(listenfd, 0); + int clifd = tcp_connect(ep, NULL); + ASSERT_GT(clifd, 0); + int servfd = accept(listenfd, NULL, NULL); + ASSERT_GT(servfd, 0); + + brpc::ChannelSSLOptions opt; + opt.protocols = "TLSv1.3"; + SSL_CTX* cli_ctx = brpc::CreateClientSSLContext(opt); + ASSERT_NE(nullptr, cli_ctx); + SSL_CTX* serv_ctx = + brpc::CreateServerSSLContext("cert1.crt", "cert1.key", + brpc::SSLOptions(), NULL, NULL); + ASSERT_NE(nullptr, serv_ctx); + SSL* cli_ssl = brpc::CreateSSLSession(cli_ctx, 0, clifd, false); +#if defined(SSL_CTRL_SET_TLSEXT_HOSTNAME) || defined(USE_MESALINK) + SSL_set_tlsext_host_name(cli_ssl, "localhost"); +#endif + SSL* serv_ssl = brpc::CreateSSLSession(serv_ctx, 0, servfd, true); + ASSERT_NE(nullptr, cli_ssl); + ASSERT_NE(nullptr, serv_ssl); + pthread_t cpid; + pthread_t spid; + ASSERT_EQ(0, pthread_create(&cpid, NULL, tls13_do_handshake, cli_ssl)); + ASSERT_EQ(0, pthread_create(&spid, NULL, tls13_do_handshake, serv_ssl)); + ASSERT_EQ(0, pthread_join(cpid, NULL)); + ASSERT_EQ(0, pthread_join(spid, NULL)); + + const char* version = SSL_get_version(cli_ssl); + ASSERT_TRUE(version != NULL); + EXPECT_STREQ("TLSv1.3", version) << "negotiated protocol=" << version; + + SSL_free(cli_ssl); + SSL_free(serv_ssl); + SSL_CTX_free(cli_ctx); + SSL_CTX_free(serv_ctx); + close(clifd); + close(servfd); +} + +#else // TLS1_3_VERSION + +TEST_F(SSLTest, tls13_protocol_string) { + brpc::ChannelSSLOptions opt; + opt.protocols = "TLSv1.3"; + SSL_CTX* ctx = brpc::CreateClientSSLContext(opt); + ASSERT_TRUE(ctx != NULL); + SSL_CTX_free(ctx); +} + +#endif // TLS1_3_VERSION diff --git a/test/brpc_streaming_rpc_unittest.cpp b/test/brpc_streaming_rpc_unittest.cpp new file mode 100644 index 0000000..fc82fcd --- /dev/null +++ b/test/brpc_streaming_rpc_unittest.cpp @@ -0,0 +1,902 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// brpc - A framework to host and access services throughout Baidu. + +// Date: 2015/10/22 16:28:44 + +#include +#include +#include "brpc/server.h" + +#include "brpc/controller.h" +#include "brpc/channel.h" +#include "brpc/callback.h" +#include "brpc/socket.h" +#include "brpc/stream_impl.h" +#include "brpc/policy/streaming_rpc_protocol.h" +#include "echo.pb.h" + +class AfterAcceptStream { +public: + virtual void action(brpc::StreamId) = 0; +}; + +class MyServiceWithStream : public test::EchoService { +public: + MyServiceWithStream(const brpc::StreamOptions& options) + : _options(options) + , _after_accept_stream(NULL) + {} + MyServiceWithStream(const brpc::StreamOptions& options, + AfterAcceptStream* after_accept_stream) + : _options(options) + , _after_accept_stream(after_accept_stream) + {} + MyServiceWithStream() + : _options() + , _after_accept_stream(NULL) + {} + + void Echo(::google::protobuf::RpcController* controller, + const ::test::EchoRequest* request, + ::test::EchoResponse* response, + ::google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + response->set_message(request->message()); + brpc::Controller* cntl = (brpc::Controller*)controller; + brpc::StreamId response_stream; + ASSERT_EQ(0, StreamAccept(&response_stream, *cntl, &_options)); + LOG(INFO) << "Created response_stream=" << response_stream; + if (_after_accept_stream) { + _after_accept_stream->action(response_stream); + } + } +private: + brpc::StreamOptions _options; + AfterAcceptStream* _after_accept_stream; +}; + +class StreamingRpcTest : public testing::Test { +protected: + void SetUp() { request.set_message("hello world"); } + void TearDown() {} + + test::EchoRequest request; + test::EchoResponse response; +}; + +struct BatchStreamFeedbackRaceState { + brpc::StreamId server_first_stream_id{brpc::INVALID_STREAM_ID}; + brpc::StreamId server_extra_stream_id{brpc::INVALID_STREAM_ID}; + brpc::StreamId client_extra_stream_id{brpc::INVALID_STREAM_ID}; + + std::atomic server_first_write_rc{-1}; + std::atomic server_second_write_rc{-1}; + std::atomic client_got_first_msg{false}; + std::atomic client_got_second_msg{false}; + std::atomic server_write_done{false}; + std::atomic rpc_done{false}; + std::atomic client_closed_count{0}; + + bthread_t server_send_tid{0}; + std::atomic server_send_started{false}; +}; + +class BatchStreamClientHandler : public brpc::StreamInputHandler { +public: + explicit BatchStreamClientHandler(BatchStreamFeedbackRaceState* state) + : _state(state) {} + + int on_received_messages(brpc::StreamId id, + butil::IOBuf* const messages[], + size_t size) override { + if (id != _state->client_extra_stream_id) { + // This test only cares about extra stream in batch creation. + return 0; + } + for (size_t i = 0; i < size; ++i) { + const size_t len = messages[i]->length(); + messages[i]->clear(); + // First payload: 64 bytes. Second payload: 1 byte. + if (len == 64) { + _state->client_got_first_msg.store(true, std::memory_order_release); + } else if (len == 1) { + _state->client_got_second_msg.store(true, std::memory_order_release); + } + } + return 0; + } + + void on_idle_timeout(brpc::StreamId /*id*/) override {} + + void on_closed(brpc::StreamId /*id*/) override { + _state->client_closed_count.fetch_add(1, std::memory_order_release); + } + + void on_failed(brpc::StreamId /*id*/, int /*error_code*/, const std::string& /*error_text*/) override {} + +private: + BatchStreamFeedbackRaceState* _state; +}; + +static void* SendTwoMessagesOnServerExtraStream(void* arg) { + auto* state = static_cast(arg); + const brpc::StreamId sid = state->server_extra_stream_id; + + // Wait until server-side stream is connected. + const int64_t connect_deadline_us = butil::gettimeofday_us() + 2 * 1000 * 1000L; + bool connected = false; + while (butil::gettimeofday_us() < connect_deadline_us) { + brpc::SocketUniquePtr ptr; + if (brpc::Socket::Address(sid, &ptr) == 0) { + brpc::Stream* s = static_cast(ptr->conn()); + if (s->_host_socket != NULL && s->_connected) { + connected = true; + break; + } + } + usleep(1000); + } + + if (!connected) { + state->server_first_write_rc.store(ETIMEDOUT, std::memory_order_relaxed); + state->server_second_write_rc.store(ETIMEDOUT, std::memory_order_relaxed); + state->server_write_done.store(true, std::memory_order_release); + return NULL; + } + + // 1) Send a payload exactly equal to max_buf_size(64). + { + std::string payload(64, 'a'); + butil::IOBuf out; + out.append(payload); + state->server_first_write_rc.store(brpc::StreamWrite(sid, out), std::memory_order_relaxed); + } + + // 2) Then send another byte. This write should become writable only after + // client sends FEEDBACK with consumed_size >= 64. + const int64_t write_deadline_us = butil::gettimeofday_us() + 2 * 1000 * 1000L; + int rc = -1; + while (butil::gettimeofday_us() < write_deadline_us) { + butil::IOBuf out; + out.append("b", 1); + rc = brpc::StreamWrite(sid, out); + if (rc == 0) { + break; + } + if (rc != EAGAIN) { + break; + } + const timespec duetime = butil::milliseconds_from_now(100); + (void)brpc::StreamWait(sid, &duetime); + } + state->server_second_write_rc.store(rc, std::memory_order_relaxed); + state->server_write_done.store(true, std::memory_order_release); + return NULL; +} + +class MyServiceWithBatchStream : public test::EchoService { +public: + MyServiceWithBatchStream(const brpc::StreamOptions& options, + BatchStreamFeedbackRaceState* state) + : _options(options), _state(state) {} + + void Echo(::google::protobuf::RpcController* controller, + const ::test::EchoRequest* request, + ::test::EchoResponse* response, + ::google::protobuf::Closure* done) override { + brpc::ClosureGuard done_guard(done); + response->set_message(request->message()); + brpc::Controller* cntl = static_cast(controller); + + brpc::StreamIds response_streams; + ASSERT_EQ(0, brpc::StreamAccept(response_streams, *cntl, &_options)); + ASSERT_EQ(2u, response_streams.size()); + _state->server_first_stream_id = response_streams[0]; + _state->server_extra_stream_id = response_streams[1]; + + bthread_t tid; + ASSERT_EQ(0, bthread_start_background( + &tid, &BTHREAD_ATTR_NORMAL, + SendTwoMessagesOnServerExtraStream, _state)); + _state->server_send_tid = tid; + _state->server_send_started.store(true, std::memory_order_release); + } + +private: + brpc::StreamOptions _options; + BatchStreamFeedbackRaceState* _state; +}; + +static void SetAtomicTrue(std::atomic* f) { + f->store(true, std::memory_order_release); +} + +template +static bool WaitForTrue(Pred pred, int timeout_ms) { + const int64_t deadline_us = butil::gettimeofday_us() + (int64_t)timeout_ms * 1000L; + while (!pred() && butil::gettimeofday_us() < deadline_us) { + usleep(1000); + } + return pred(); +} + +static bool WaitForTrue(const std::atomic& f, int timeout_ms) { + return WaitForTrue([&f]() { return f.load(std::memory_order_acquire); }, timeout_ms); +} + +TEST_F(StreamingRpcTest, sanity) { + brpc::Server server; + MyServiceWithStream service; + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start(9007, NULL)); + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("127.0.0.1:9007", NULL)); + brpc::Controller cntl; + brpc::StreamId request_stream; + ASSERT_EQ(0, StreamCreate(&request_stream, cntl, NULL)); + brpc::ScopedStream stream_guard(request_stream); + test::EchoService_Stub stub(&channel); + stub.Echo(&cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText() << " request_stream=" << request_stream; + usleep(10); + brpc::StreamClose(request_stream); + server.Stop(0); + server.Join(); +} + +TEST_F(StreamingRpcTest, batch_create_stream_feedback_race) { + BatchStreamFeedbackRaceState state; + BatchStreamClientHandler client_handler(&state); + + brpc::StreamOptions server_stream_opt; + // Make server-side sender sensitive to FEEDBACK quickly. + server_stream_opt.max_buf_size = 16; + + brpc::Server server; + MyServiceWithBatchStream service(server_stream_opt, &state); + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start(9007, NULL)); + + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("127.0.0.1:9007", NULL)); + + brpc::Controller cntl; + brpc::StreamIds request_streams; + brpc::StreamOptions client_stream_opt; + client_stream_opt.handler = &client_handler; + client_stream_opt.max_buf_size = 0; + ASSERT_EQ(0, brpc::StreamCreate(request_streams, 2, cntl, &client_stream_opt)); + ASSERT_EQ(2u, request_streams.size()); + state.client_extra_stream_id = request_streams[1]; + + // Block SetConnected() on the extra stream to enlarge the race window. + brpc::SocketUniquePtr client_extra_ptr; + ASSERT_EQ(0, brpc::Socket::Address(state.client_extra_stream_id, &client_extra_ptr)); + brpc::Stream* client_extra_stream = static_cast(client_extra_ptr->conn()); + bthread_mutex_lock(&client_extra_stream->_connect_mutex); + struct UnlockGuard { + bthread_mutex_t* m; + ~UnlockGuard() { + if (m) { + bthread_mutex_unlock(m); + } + } + } unlock_guard{&client_extra_stream->_connect_mutex}; + + BRPC_SCOPE_EXIT { + if (state.server_extra_stream_id != brpc::INVALID_STREAM_ID) { + brpc::StreamClose(state.server_extra_stream_id); + } + if (state.server_first_stream_id != brpc::INVALID_STREAM_ID) { + brpc::StreamClose(state.server_first_stream_id); + } + for (auto sid : request_streams) { + brpc::StreamClose(sid); + } + + if (state.server_send_tid) { + bthread_join(state.server_send_tid, NULL); + } + server.Stop(0); + server.Join(); + + // Release the SocketUniquePtr held above so the fake socket can be + // recycled. Otherwise BeforeRecycle / on_closed for the extra stream + // is deferred until `client_extra_ptr` destructs at scope exit, which + // happens *after* `client_handler` and `state` are destroyed -> UAF + // inside Stream::Consume on Linux. + client_extra_ptr.reset(); + + // on_closed() runs asynchronously on each client stream's consumer + // bthread. Wait for both before letting handler/state go out of + // scope, otherwise Stream::Consume will dereference freed memory. + int expected_closed = request_streams.size(); + WaitForTrue([&state, expected_closed]() { + return state.client_closed_count.load(std::memory_order_acquire) + >= expected_closed; + }, 2000); + }; + + test::EchoService_Stub stub(&channel); + stub.Echo(&cntl, &request, &response, brpc::NewCallback(SetAtomicTrue, &state.rpc_done)); + + // Wait until client consumes the first 64B payload on extra stream. + ASSERT_TRUE(WaitForTrue(state.client_got_first_msg, 2000)); + + // Unblock SetConnected(); the fix in PR 3215 should send the first FEEDBACK + // with consumed_size=64 here, making server-side stream writable again. + bthread_mutex_unlock(&client_extra_stream->_connect_mutex); + unlock_guard.m = NULL; + + ASSERT_TRUE(WaitForTrue(state.rpc_done, 2000)); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + + // Wait for server-side send thread to be started. + ASSERT_TRUE(WaitForTrue(state.server_send_started, 2000)); + + ASSERT_TRUE(WaitForTrue(state.server_write_done, 2000)); + ASSERT_EQ(0, state.server_first_write_rc.load(std::memory_order_relaxed)); + ASSERT_EQ(0, state.server_second_write_rc.load(std::memory_order_relaxed)); + ASSERT_TRUE(WaitForTrue(state.client_got_second_msg, 2000)); +} + +struct HandlerControl { + HandlerControl() + : block(false) + {} + bool block; +}; + +class OrderedInputHandler : public brpc::StreamInputHandler { +public: + explicit OrderedInputHandler(HandlerControl *cntl = NULL) + : _expected_next_value(0) + , _failed(false) + , _stopped(false) + , _idle_times(0) + , _cntl(cntl) + {} + + int on_received_messages(brpc::StreamId /*id*/, + butil::IOBuf *const messages[], + size_t size) override { + if (_cntl && _cntl->block) { + while (_cntl->block) { + usleep(100); + } + } + for (size_t i = 0; i < size; ++i) { + CHECK(messages[i]->length() == sizeof(int)); + int network = 0; + messages[i]->cutn(&network, sizeof(int)); + EXPECT_EQ((int)ntohl(network), _expected_next_value++); + } + return 0; + } + + void on_idle_timeout(brpc::StreamId /*id*/) override { + ++_idle_times; + } + + void on_closed(brpc::StreamId /*id*/) override { + ASSERT_FALSE(_stopped); + _stopped = true; + } + + void on_failed(brpc::StreamId id, int error_code, + const std::string& /*error_text*/) override { + ASSERT_FALSE(_failed); + ASSERT_NE(0, error_code); + _failed = true; + } + + bool failed() const { return _failed; } + bool stopped() const { return _stopped; } + int idle_times() const { return _idle_times; } +private: + int _expected_next_value; + bool _failed; + bool _stopped; + int _idle_times; + HandlerControl* _cntl; +}; + +TEST_F(StreamingRpcTest, received_in_order) { + OrderedInputHandler handler; + brpc::StreamOptions opt; + opt.handler = &handler; + opt.messages_in_batch = 100; + brpc::Server server; + MyServiceWithStream service(opt); + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start(9007, NULL)); + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("127.0.0.1:9007", NULL)); + brpc::Controller cntl; + brpc::StreamId request_stream; + brpc::StreamOptions request_stream_options; + request_stream_options.max_buf_size = 0; + ASSERT_EQ(0, StreamCreate(&request_stream, cntl, &request_stream_options)); + brpc::ScopedStream stream_guard(request_stream); + test::EchoService_Stub stub(&channel); + stub.Echo(&cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText() << " request_stream=" << request_stream; + const int N = 10000; + for (int i = 0; i < N; ++i) { + int network = htonl(i); + butil::IOBuf out; + out.append(&network, sizeof(network)); + ASSERT_EQ(0, brpc::StreamWrite(request_stream, out)) << "i=" << i; + } + ASSERT_EQ(0, brpc::StreamClose(request_stream)); + server.Stop(0); + server.Join(); + while (!handler.stopped()) { + usleep(100); + } + ASSERT_FALSE(handler.failed()); + ASSERT_EQ(0, handler.idle_times()); + ASSERT_EQ(N, handler._expected_next_value); +} + +void on_writable(brpc::StreamId, void* arg, int error_code) { + std::pair* p = (std::pair*)arg; + p->first = true; + p->second = error_code; + LOG(INFO) << "error_code=" << error_code; +} + +TEST_F(StreamingRpcTest, block) { + HandlerControl hc; + hc.block = true; + OrderedInputHandler handler(&hc); + brpc::StreamOptions opt; + opt.handler = &handler; + const int N = 10000; + opt.max_buf_size = sizeof(uint32_t) * N; + brpc::Server server; + MyServiceWithStream service(opt); + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start(9007, NULL)); + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("127.0.0.1:9007", NULL)); + brpc::Controller cntl; + brpc::StreamId request_stream; + brpc::ScopedStream stream_guard(request_stream); + brpc::StreamOptions request_stream_options; + request_stream_options.max_buf_size = sizeof(uint32_t) * N; + ASSERT_EQ(0, StreamCreate(&request_stream, cntl, &request_stream_options)); + test::EchoService_Stub stub(&channel); + stub.Echo(&cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText() << " request_stream=" + << request_stream; + for (int i = 0; i < N; ++i) { + int network = htonl(i); + butil::IOBuf out; + out.append(&network, sizeof(network)); + ASSERT_EQ(0, brpc::StreamWrite(request_stream, out)) << "i=" << i; + } + // sync wait + int dummy = 102030123; + butil::IOBuf out; + out.append(&dummy, sizeof(dummy)); + ASSERT_EQ(EAGAIN, brpc::StreamWrite(request_stream, out)); + hc.block = false; + // wait flushing all the pending messages + while (handler._expected_next_value != N) { + usleep(100); + } + // block hanlder again to test async wait + hc.block = true; + // async wait + for (int i = N; i < N + N; ++i) { + ASSERT_EQ(0, brpc::StreamWait(request_stream, NULL)); + int network = htonl(i); + butil::IOBuf out; + out.append(&network, sizeof(network)); + ASSERT_EQ(0, brpc::StreamWrite(request_stream, out)) << "i=" << i; + } + out.clear(); + out.append(&dummy, sizeof(dummy)); + ASSERT_EQ(EAGAIN, brpc::StreamWrite(request_stream, out)); + hc.block = false; + std::pair p = std::make_pair(false, 0); + usleep(10); + brpc::StreamWait(request_stream, NULL, on_writable, &p); + while (!p.first) { + usleep(100); + } + ASSERT_EQ(0, p.second); + + // wait flushing all the pending messages + while (handler._expected_next_value != N + N) { + usleep(100); + } + usleep(1000); + + LOG(INFO) << "Starting block"; + hc.block = true; + for (int i = N + N; i < N + N + N; ++i) { + int network = htonl(i); + butil::IOBuf out; + out.append(&network, sizeof(network)); + ASSERT_EQ(0, brpc::StreamWrite(request_stream, out)) << "i=" << i - N - N; + } + out.clear(); + out.append(&dummy, sizeof(dummy)); + ASSERT_EQ(EAGAIN, brpc::StreamWrite(request_stream, out)); + timespec duetime = butil::microseconds_from_now(1); + p.first = false; + LOG(INFO) << "Start wait"; + brpc::StreamWait(request_stream, &duetime, on_writable, &p); + while (!p.first) { + usleep(100); + } + ASSERT_TRUE(p.first); + EXPECT_EQ(ETIMEDOUT, p.second); + hc.block = false; + ASSERT_EQ(0, brpc::StreamClose(request_stream)); + while (!handler.stopped()) { + usleep(100); + } + + ASSERT_FALSE(handler.failed()); + ASSERT_EQ(0, handler.idle_times()); + ASSERT_EQ(N + N + N, handler._expected_next_value); +} + +TEST_F(StreamingRpcTest, auto_close_if_host_socket_closed) { + HandlerControl hc; + hc.block = true; + OrderedInputHandler handler(&hc); + brpc::StreamOptions opt; + opt.handler = &handler; + const int N = 10000; + opt.max_buf_size = sizeof(uint32_t) * N; + brpc::Server server; + MyServiceWithStream service(opt); + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start(9007, NULL)); + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("127.0.0.1:9007", NULL)); + brpc::Controller cntl; + brpc::StreamId request_stream; + brpc::StreamOptions request_stream_options; + request_stream_options.max_buf_size = sizeof(uint32_t) * N; + ASSERT_EQ(0, StreamCreate(&request_stream, cntl, &request_stream_options)); + brpc::ScopedStream stream_guard(request_stream); + test::EchoService_Stub stub(&channel); + stub.Echo(&cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText() << " request_stream=" << request_stream; + + { + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(request_stream, &ptr)); + brpc::Stream* s = (brpc::Stream*)ptr->conn(); + ASSERT_TRUE(s->_host_socket != NULL); + s->_host_socket->SetFailed(); + } + + usleep(100); + butil::IOBuf out; + out.append("test"); + ASSERT_EQ(EINVAL, brpc::StreamWrite(request_stream, out)); + while (!handler.stopped()) { + usleep(100); + } + ASSERT_TRUE(handler.failed()); + ASSERT_EQ(0, handler.idle_times()); + ASSERT_EQ(0, handler._expected_next_value); +} + +TEST_F(StreamingRpcTest, failed_when_rst) { + OrderedInputHandler handler; + brpc::StreamOptions opt; + opt.handler = &handler; + opt.messages_in_batch = 100; + brpc::Server server; + MyServiceWithStream service(opt); + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start(9007, NULL)); + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("127.0.0.1:9007", NULL)); + brpc::Controller cntl; + brpc::StreamId request_stream; + brpc::StreamOptions request_stream_options; + request_stream_options.max_buf_size = 0; + ASSERT_EQ(0, StreamCreate(&request_stream, cntl, &request_stream_options)); + brpc::ScopedStream stream_guard(request_stream); + test::EchoService_Stub stub(&channel); + stub.Echo(&cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText() << " request_stream=" << request_stream; + const int N = 10000; + for (int i = 0; i < N; ++i) { + int network = htonl(i); + butil::IOBuf out; + out.append(&network, sizeof(network)); + ASSERT_EQ(0, brpc::StreamWrite(request_stream, out)) << "i=" << i; + } + + while (handler._expected_next_value != N) { + usleep(100); + } + { + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(request_stream, &ptr)); + brpc::Stream* s = (brpc::Stream*)ptr->conn(); + ASSERT_TRUE(s->_host_socket != NULL); + brpc::policy::SendStreamRst(s->_host_socket, + s->_remote_settings.stream_id()); + } + // ASSERT_EQ(0, brpc::StreamClose(request_stream)); + server.Stop(0); + server.Join(); + while (!handler.stopped() && !handler.failed()) { + usleep(100); + } + ASSERT_TRUE(handler.failed()); + ASSERT_EQ(0, handler.idle_times()); + ASSERT_EQ(N, handler._expected_next_value); +} + +TEST_F(StreamingRpcTest, idle_timeout) { + HandlerControl hc; + hc.block = true; + OrderedInputHandler handler(&hc); + brpc::StreamOptions opt; + opt.handler = &handler; + opt.idle_timeout_ms = 2; + const int N = 10000; + opt.max_buf_size = sizeof(uint32_t) * N; + brpc::Server server; + MyServiceWithStream service(opt); + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start(9007, NULL)); + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("127.0.0.1:9007", NULL)); + brpc::Controller cntl; + brpc::StreamId request_stream; + brpc::StreamOptions request_stream_options; + request_stream_options.max_buf_size = sizeof(uint32_t) * N; + ASSERT_EQ(0, StreamCreate(&request_stream, cntl, &request_stream_options)); + brpc::ScopedStream stream_guard(request_stream); + test::EchoService_Stub stub(&channel); + stub.Echo(&cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText() << " request_stream=" << request_stream; + usleep(10 * 1000 + 800); + ASSERT_EQ(0, brpc::StreamClose(request_stream)); + while (!handler.stopped()) { + usleep(100); + } + ASSERT_FALSE(handler.failed()); +// ASSERT_TRUE(handler.idle_times() >= 4 && handler.idle_times() <= 6) +// << handler.idle_times(); + ASSERT_EQ(0, handler._expected_next_value); +} + +class PingPongHandler : public brpc::StreamInputHandler { +public: + int on_received_messages(brpc::StreamId id, + butil::IOBuf *const messages[], + size_t size) override { + if (size != 1) { + LOG(INFO) << "size=" << size; + _error = true; + return 0; + } + for (size_t i = 0; i < size; ++i) { + CHECK(messages[i]->length() == sizeof(int)); + int network = 0; + messages[i]->cutn(&network, sizeof(int)); + if ((int)ntohl(network) != _expected_next_value) { + _error = true; + } + int send_back = ntohl(network) + 1; + _expected_next_value = send_back + 1; + butil::IOBuf out; + network = htonl(send_back); + out.append(&network, sizeof(network)); + // don't care the return value + brpc::StreamWrite(id, out); + } + return 0; + } + + void on_idle_timeout(brpc::StreamId /*id*/) override { + ++_idle_times; + } + + void on_closed(brpc::StreamId /*id*/) override { + ASSERT_FALSE(_stopped); + _stopped = true; + } + + + void on_failed(brpc::StreamId id, int error_code, + const std::string& /*error_text*/) override { + ASSERT_FALSE(_failed); + ASSERT_NE(0, error_code); + _failed = true; + } + + bool error() const { return _error; } + bool failed() const { return _failed; } + bool stopped() const { return _stopped; } + int idle_times() const { return _idle_times; } +private: + int _expected_next_value{0}; + bool _error{false}; + bool _failed{false}; + bool _stopped{false}; + int _idle_times{0}; +}; + +TEST_F(StreamingRpcTest, ping_pong) { + PingPongHandler resh; + brpc::StreamOptions opt; + opt.handler = &resh; + const int N = 10000; + opt.max_buf_size = sizeof(uint32_t) * N; + brpc::Server server; + MyServiceWithStream service(opt); + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start(9007, NULL)); + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("127.0.0.1:9007", NULL)); + brpc::Controller cntl; + brpc::StreamId request_stream; + brpc::StreamOptions request_stream_options; + PingPongHandler reqh; + reqh._expected_next_value = 1; + request_stream_options.handler = &reqh; + request_stream_options.max_buf_size = sizeof(uint32_t) * N; + ASSERT_EQ(0, StreamCreate(&request_stream, cntl, &request_stream_options)); + brpc::ScopedStream stream_guard(request_stream); + test::EchoService_Stub stub(&channel); + stub.Echo(&cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText() << " request_stream=" << request_stream; + int send = 0; + butil::IOBuf out; + out.append(&send, sizeof(send)); + ASSERT_EQ(0, brpc::StreamWrite(request_stream, out)); + usleep(10 * 1000); + ASSERT_EQ(0, brpc::StreamClose(request_stream)); + while (!resh.stopped() || !reqh.stopped()) { + usleep(100); + } + ASSERT_FALSE(resh.error()); + ASSERT_FALSE(reqh.error()); + ASSERT_EQ(0, resh.idle_times()); + ASSERT_EQ(0, reqh.idle_times()); +} + +class SendNAfterAcceptStream : public AfterAcceptStream { +public: + explicit SendNAfterAcceptStream(int n) + : _n(n) {} + void action(brpc::StreamId s) { + for (int i = 0; i < _n; ++i) { + int network = htonl(i); + butil::IOBuf out; + out.append(&network, sizeof(network)); + ASSERT_EQ(0, brpc::StreamWrite(s, out)) << "i=" << i; + } + } +private: + int _n; +}; + +TEST_F(StreamingRpcTest, server_send_data_before_run_done) { + const int N = 10000; + SendNAfterAcceptStream after_accept(N); + brpc::StreamOptions opt; + opt.max_buf_size = -1; + brpc::Server server; + MyServiceWithStream service(opt, &after_accept); + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start(9007, NULL)); + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("127.0.0.1:9007", NULL)); + OrderedInputHandler handler; + brpc::StreamOptions request_stream_options; + request_stream_options.handler = &handler; + brpc::StreamId request_stream; + brpc::Controller cntl; + ASSERT_EQ(0, StreamCreate(&request_stream, cntl, &request_stream_options)); + brpc::ScopedStream stream_guard(request_stream); + test::EchoService_Stub stub(&channel); + stub.Echo(&cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText() << " request_stream=" << request_stream; + // wait flushing all the pending messages + while (handler._expected_next_value != N) { + usleep(100); + } + ASSERT_EQ(0, brpc::StreamClose(request_stream)); + while (!handler.stopped()) { + usleep(100); + } + ASSERT_FALSE(handler.failed()); + ASSERT_EQ(0, handler.idle_times()); +} + +TEST_F(StreamingRpcTest, segment_stream_data_automatically) { + GFLAGS_NAMESPACE::SetCommandLineOption("stream_write_max_segment_size", "1"); + OrderedInputHandler handler; + brpc::StreamOptions opt; + opt.handler = &handler; + opt.messages_in_batch = 100; + brpc::Server server; + MyServiceWithStream service(opt); + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start(9007, NULL)); + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("127.0.0.1:9007", NULL)); + brpc::Controller cntl; + brpc::StreamId request_stream; + brpc::StreamOptions request_stream_options; + ASSERT_EQ(0, StreamCreate(&request_stream, cntl, &request_stream_options)); + brpc::ScopedStream stream_guard(request_stream); + test::EchoService_Stub stub(&channel); + stub.Echo(&cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText() << " request_stream=" << request_stream; + const int N = 1000; + for (int i = 0; i < N; ++i) { + int network = htonl(i); + butil::IOBuf out; + out.append(&network, sizeof(network)); + ASSERT_EQ(0, brpc::StreamWrite(request_stream, out)) << "i=" << i; + } + + brpc::SocketUniquePtr host_socket_ptr; + { + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(request_stream, &ptr)); + brpc::Stream *s = (brpc::Stream *)ptr->conn(); + ASSERT_TRUE(s->_host_socket != NULL); + s->_host_socket->ReAddress(&host_socket_ptr); + } + + ASSERT_EQ(0, brpc::StreamClose(request_stream)); + server.Stop(0); + server.Join(); + while (!handler.stopped()) { + usleep(100); + } + const int64_t now_ms = butil::cpuwide_time_ms(); + host_socket_ptr->UpdateStatsEverySecond(now_ms); + brpc::SocketStat stat; + host_socket_ptr->GetStat(&stat); + ASSERT_LT(N * sizeof(N), stat.out_num_messages_m); + ASSERT_FALSE(handler.failed()); + ASSERT_EQ(0, handler.idle_times()); + ASSERT_EQ(N, handler._expected_next_value); + GFLAGS_NAMESPACE::SetCommandLineOption("stream_write_max_segment_size", "536870912"); +} + +TEST_F(StreamingRpcTest, create_request_stream_twice_on_same_controller_returns_error) { + brpc::Controller cntl; + + brpc::StreamId first_stream = brpc::INVALID_STREAM_ID; + ASSERT_EQ(0, brpc::StreamCreate(&first_stream, cntl, NULL)); + brpc::ScopedStream stream_guard(first_stream); + + brpc::StreamId second_stream = brpc::INVALID_STREAM_ID; + ASSERT_EQ(-1, brpc::StreamCreate(&second_stream, cntl, NULL)); + ASSERT_EQ(brpc::INVALID_STREAM_ID, second_stream); +} diff --git a/test/brpc_timeout_concurrency_limiter_unittest.cpp b/test/brpc_timeout_concurrency_limiter_unittest.cpp new file mode 100644 index 0000000..a8e11ec --- /dev/null +++ b/test/brpc_timeout_concurrency_limiter_unittest.cpp @@ -0,0 +1,129 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/timeout_concurrency_limiter.h" +#include "butil/time.h" +#include "bthread/bthread.h" +#include + +namespace brpc { +namespace policy { +DECLARE_int32(timeout_cl_sample_window_size_ms); +DECLARE_int32(timeout_cl_min_sample_count); +DECLARE_int32(timeout_cl_max_sample_count); +} // namespace policy +} // namespace brpc + +TEST(TimeoutConcurrencyLimiterTest, AddSample) { + { + brpc::policy::FLAGS_timeout_cl_sample_window_size_ms = 10; + brpc::policy::FLAGS_timeout_cl_min_sample_count = 5; + brpc::policy::FLAGS_timeout_cl_max_sample_count = 10; + + brpc::policy::TimeoutConcurrencyLimiter limiter; + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + bthread_usleep(10 * 1000); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + ASSERT_EQ(limiter._sw.succ_count, 0); + ASSERT_EQ(limiter._sw.failed_count, 0); + + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + bthread_usleep(10 * 1000); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + ASSERT_EQ(limiter._sw.succ_count, 0); + ASSERT_EQ(limiter._sw.failed_count, 0); + ASSERT_EQ(limiter._avg_latency_us, 50); + + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + ASSERT_EQ(limiter._sw.succ_count, 0); + ASSERT_EQ(limiter._sw.failed_count, 0); + ASSERT_EQ(limiter._avg_latency_us, 50); + + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + ASSERT_EQ(limiter._sw.succ_count, 6); + ASSERT_EQ(limiter._sw.failed_count, 0); + + limiter.ResetSampleWindow(butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(0, 50, butil::cpuwide_time_us()); + limiter.AddSample(1, 50, butil::cpuwide_time_us()); + limiter.AddSample(1, 50, butil::cpuwide_time_us()); + limiter.AddSample(1, 50, butil::cpuwide_time_us()); + ASSERT_EQ(limiter._sw.succ_count, 3); + ASSERT_EQ(limiter._sw.failed_count, 3); + } +} + +TEST(TimeoutConcurrencyLimiterTest, OnResponded) { + brpc::policy::FLAGS_timeout_cl_sample_window_size_ms = 10; + brpc::policy::FLAGS_timeout_cl_min_sample_count = 5; + brpc::policy::FLAGS_timeout_cl_max_sample_count = 10; + brpc::policy::TimeoutConcurrencyLimiter limiter; + limiter.OnResponded(0, 50); + limiter.OnResponded(0, 50); + bthread_usleep(100); + limiter.OnResponded(0, 50); + limiter.OnResponded(1, 50); + ASSERT_EQ(limiter._sw.succ_count, 2); + ASSERT_EQ(limiter._sw.failed_count, 0); +} + +TEST(TimeoutConcurrencyLimiterTest, AdaptiveMaxConcurrencyTest) { + { + brpc::AdaptiveMaxConcurrency concurrency( + brpc::TimeoutConcurrencyConf{100, 100}); + ASSERT_EQ(concurrency.type(), "timeout"); + ASSERT_EQ(concurrency.value(), "timeout"); + } + { + brpc::AdaptiveMaxConcurrency concurrency; + concurrency = "timeout"; + ASSERT_EQ(concurrency.type(), "timeout"); + ASSERT_EQ(concurrency.value(), "timeout"); + } + { + brpc::AdaptiveMaxConcurrency concurrency; + concurrency = brpc::TimeoutConcurrencyConf{50, 100}; + ASSERT_EQ(concurrency.type(), "timeout"); + ASSERT_EQ(concurrency.value(), "timeout"); + auto time_conf = static_cast(concurrency); + ASSERT_EQ(time_conf.timeout_ms, 50); + ASSERT_EQ(time_conf.max_concurrency, 100); + } +} diff --git a/test/brpc_uri_unittest.cpp b/test/brpc_uri_unittest.cpp new file mode 100644 index 0000000..2e8965d --- /dev/null +++ b/test/brpc_uri_unittest.cpp @@ -0,0 +1,494 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 + +#include "brpc/uri.h" + +TEST(URITest, everything) { + brpc::URI uri; + std::string uri_str = " foobar://user:passwd@www.baidu.com:80/s?wd=uri#frag "; + ASSERT_EQ(0, uri.SetHttpURL(uri_str)); + ASSERT_EQ("foobar", uri.scheme()); + ASSERT_EQ(80, uri.port()); + ASSERT_EQ("www.baidu.com", uri.host()); + ASSERT_EQ("/s", uri.path()); + ASSERT_EQ("user:passwd", uri.user_info()); + ASSERT_EQ("frag", uri.fragment()); + ASSERT_TRUE(uri.GetQuery("wd")); + ASSERT_EQ(*uri.GetQuery("wd"), "uri"); + ASSERT_FALSE(uri.GetQuery("nonkey")); + + std::string scheme; + std::string host_out; + int port_out = -1; + brpc::ParseURL(uri_str.c_str(), &scheme, &host_out, &port_out); + ASSERT_EQ("foobar", scheme); + ASSERT_EQ("www.baidu.com", host_out); + ASSERT_EQ(80, port_out); +} + +TEST(URITest, only_host) { + brpc::URI uri; + ASSERT_EQ(0, uri.SetHttpURL(" foo1://www.baidu1.com?wd=uri2&nonkey=22 ")); + ASSERT_EQ("foo1", uri.scheme()); + ASSERT_EQ(-1, uri.port()); + ASSERT_EQ("www.baidu1.com", uri.host()); + ASSERT_EQ("", uri.path()); + ASSERT_EQ("", uri.user_info()); + ASSERT_EQ("", uri.fragment()); + ASSERT_EQ(2u, uri.QueryCount()); + ASSERT_TRUE(uri.GetQuery("wd")); + ASSERT_EQ(*uri.GetQuery("wd"), "uri2"); + ASSERT_TRUE(uri.GetQuery("nonkey")); + ASSERT_EQ(*uri.GetQuery("nonkey"), "22"); + + ASSERT_EQ(0, uri.SetHttpURL("foo2://www.baidu2.com:1234?wd=uri2&nonkey=22 ")); + ASSERT_EQ("foo2", uri.scheme()); + ASSERT_EQ(1234, uri.port()); + ASSERT_EQ("www.baidu2.com", uri.host()); + ASSERT_EQ("", uri.path()); + ASSERT_EQ("", uri.user_info()); + ASSERT_EQ("", uri.fragment()); + ASSERT_EQ(2u, uri.QueryCount()); + ASSERT_TRUE(uri.GetQuery("wd")); + ASSERT_EQ(*uri.GetQuery("wd"), "uri2"); + ASSERT_TRUE(uri.GetQuery("nonkey")); + ASSERT_EQ(*uri.GetQuery("nonkey"), "22"); + + ASSERT_EQ(0, uri.SetHttpURL(" www.baidu3.com:4321 ")); + ASSERT_EQ("", uri.scheme()); + ASSERT_EQ(4321, uri.port()); + ASSERT_EQ("www.baidu3.com", uri.host()); + ASSERT_EQ("", uri.path()); + ASSERT_EQ("", uri.user_info()); + ASSERT_EQ("", uri.fragment()); + ASSERT_EQ(0u, uri.QueryCount()); + + ASSERT_EQ(0, uri.SetHttpURL(" www.baidu4.com ")); + ASSERT_EQ("", uri.scheme()); + ASSERT_EQ(-1, uri.port()); + ASSERT_EQ("www.baidu4.com", uri.host()); + ASSERT_EQ("", uri.path()); + ASSERT_EQ("", uri.user_info()); + ASSERT_EQ("", uri.fragment()); + ASSERT_EQ(0u, uri.QueryCount()); +} + +TEST(URITest, no_scheme) { + brpc::URI uri; + ASSERT_EQ(0, uri.SetHttpURL(" user:passwd2@www.baidu1.com/s?wd=uri2&nonkey=22#frag ")); + ASSERT_EQ("", uri.scheme()); + ASSERT_EQ(-1, uri.port()); + ASSERT_EQ("www.baidu1.com", uri.host()); + ASSERT_EQ("/s", uri.path()); + ASSERT_EQ("user:passwd2", uri.user_info()); + ASSERT_EQ("frag", uri.fragment()); + ASSERT_TRUE(uri.GetQuery("wd")); + ASSERT_EQ(*uri.GetQuery("wd"), "uri2"); + ASSERT_TRUE(uri.GetQuery("nonkey")); + ASSERT_EQ(*uri.GetQuery("nonkey"), "22"); +} + +TEST(URITest, no_scheme_and_user_info) { + brpc::URI uri; + ASSERT_EQ(0, uri.SetHttpURL(" www.baidu2.com/s?wd=uri2&nonkey=22#frag ")); + ASSERT_EQ("", uri.scheme()); + ASSERT_EQ(-1, uri.port()); + ASSERT_EQ("www.baidu2.com", uri.host()); + ASSERT_EQ("/s", uri.path()); + ASSERT_EQ("", uri.user_info()); + ASSERT_EQ("frag", uri.fragment()); + ASSERT_TRUE(uri.GetQuery("wd")); + ASSERT_EQ(*uri.GetQuery("wd"), "uri2"); + ASSERT_TRUE(uri.GetQuery("nonkey")); + ASSERT_EQ(*uri.GetQuery("nonkey"), "22"); +} + +TEST(URITest, no_host) { + brpc::URI uri; + ASSERT_EQ(0, uri.SetHttpURL(" /sb?wd=uri3#frag2 ")) << uri.status(); + ASSERT_EQ("", uri.scheme()); + ASSERT_EQ(-1, uri.port()); + ASSERT_EQ("", uri.host()); + ASSERT_EQ("/sb", uri.path()); + ASSERT_EQ("", uri.user_info()); + ASSERT_EQ("frag2", uri.fragment()); + ASSERT_TRUE(uri.GetQuery("wd")); + ASSERT_EQ(*uri.GetQuery("wd"), "uri3"); + ASSERT_FALSE(uri.GetQuery("nonkey")); + + // set_path should do as its name says. + uri.set_path("/x/y/z/"); + ASSERT_EQ("", uri.scheme()); + ASSERT_EQ(-1, uri.port()); + ASSERT_EQ("", uri.host()); + ASSERT_EQ("/x/y/z/", uri.path()); + ASSERT_EQ("", uri.user_info()); + ASSERT_EQ("frag2", uri.fragment()); + ASSERT_TRUE(uri.GetQuery("wd")); + ASSERT_EQ(*uri.GetQuery("wd"), "uri3"); + ASSERT_FALSE(uri.GetQuery("nonkey")); +} + +TEST(URITest, consecutive_ampersand) { + brpc::URI uri; + uri._query = "&key1=value1&&key3=value3"; + ASSERT_TRUE(uri.GetQuery("key1")); + ASSERT_TRUE(uri.GetQuery("key3")); + ASSERT_FALSE(uri.GetQuery("key2")); + ASSERT_EQ("value1", *uri.GetQuery("key1")); + ASSERT_EQ("value3", *uri.GetQuery("key3")); +} + +TEST(URITest, only_equality) { + brpc::URI uri; + uri._query = "key1=&&key2&&=&key3=value3"; + ASSERT_TRUE(uri.GetQuery("key1")); + ASSERT_EQ("", *uri.GetQuery("key1")); + ASSERT_TRUE(uri.GetQuery("key2")); + ASSERT_EQ("", *uri.GetQuery("key2")); + ASSERT_TRUE(uri.GetQuery("key3")); + ASSERT_EQ("value3", *uri.GetQuery("key3")); +} + +TEST(URITest, set_query) { + brpc::URI uri; + uri._query = "key1=&&key2&&=&key3=value3"; + ASSERT_TRUE(uri.GetQuery("key1")); + ASSERT_TRUE(uri.GetQuery("key3")); + ASSERT_EQ("value3", *uri.GetQuery("key3")); + ASSERT_TRUE(uri.GetQuery("key2")); + // overwrite value + uri.SetQuery("key3", "value4"); + ASSERT_EQ("value4", *uri.GetQuery("key3")); + + uri.SetQuery("key2", "value2"); + ASSERT_TRUE(uri.GetQuery("key2")); + ASSERT_EQ("value2", *uri.GetQuery("key2")); +} + +TEST(URITest, set_h2_path) { + brpc::URI uri; + uri.SetH2Path("/dir?key1=&&key2&&=&key3=value3"); + ASSERT_EQ("/dir", uri.path()); + ASSERT_TRUE(uri.GetQuery("key1")); + ASSERT_TRUE(uri.GetQuery("key2")); + ASSERT_TRUE(uri.GetQuery("key3")); + ASSERT_EQ("value3", *uri.GetQuery("key3")); + + uri.SetH2Path("dir?key1=&&key2&&=&key3=value3"); + ASSERT_EQ("dir", uri.path()); + ASSERT_TRUE(uri.GetQuery("key1")); + ASSERT_TRUE(uri.GetQuery("key2")); + ASSERT_TRUE(uri.GetQuery("key3")); + ASSERT_EQ("value3", *uri.GetQuery("key3")); + + uri.SetH2Path("/dir?key1=&&key2&&=&key3=value3#frag1"); + ASSERT_EQ("/dir", uri.path()); + ASSERT_TRUE(uri.GetQuery("key1")); + ASSERT_TRUE(uri.GetQuery("key2")); + ASSERT_TRUE(uri.GetQuery("key3")); + ASSERT_EQ("value3", *uri.GetQuery("key3")); + ASSERT_EQ("frag1", uri.fragment()); +} + +TEST(URITest, generate_h2_path) { + brpc::URI uri; + const std::string ref1 = "/dir?key1=&&key2&&=&key3=value3"; + uri.SetH2Path(ref1); + ASSERT_EQ("/dir", uri.path()); + ASSERT_EQ(3u, uri.QueryCount()); + ASSERT_TRUE(uri.GetQuery("key1")); + ASSERT_TRUE(uri.GetQuery("key2")); + ASSERT_TRUE(uri.GetQuery("key3")); + ASSERT_EQ("value3", *uri.GetQuery("key3")); + std::string path1; + uri.GenerateH2Path(&path1); + ASSERT_EQ(ref1, path1); + + uri.SetQuery("key3", "value3.3"); + ASSERT_EQ(3u, uri.QueryCount()); + ASSERT_EQ(1u, uri.RemoveQuery("key1")); + ASSERT_EQ(2u, uri.QueryCount()); + ASSERT_EQ("key2&key3=value3.3", uri.query()); + uri.GenerateH2Path(&path1); + ASSERT_EQ("/dir?key2&key3=value3.3", path1); + + const std::string ref2 = "/dir2?key1=&&key2&&=&key3=value3#frag2"; + uri.SetH2Path(ref2); + ASSERT_EQ("/dir2", uri.path()); + ASSERT_TRUE(uri.GetQuery("key1")); + ASSERT_TRUE(uri.GetQuery("key2")); + ASSERT_TRUE(uri.GetQuery("key3")); + ASSERT_EQ("value3", *uri.GetQuery("key3")); + ASSERT_EQ("frag2", uri.fragment()); + std::string path2; + uri.GenerateH2Path(&path2); + ASSERT_EQ(ref2, path2); + + const std::string ref3 = "/dir3#frag3"; + uri.SetH2Path(ref3); + ASSERT_EQ("/dir3", uri.path()); + ASSERT_EQ("frag3", uri.fragment()); + std::string path3; + uri.GenerateH2Path(&path3); + ASSERT_EQ(ref3, path3); + + const std::string ref4 = "/dir4"; + uri.SetH2Path(ref4); + ASSERT_EQ("/dir4", uri.path()); + std::string path4; + uri.GenerateH2Path(&path4); + ASSERT_EQ(ref4, path4); +} + +TEST(URITest, only_one_key) { + brpc::URI uri; + uri._query = "key1"; + ASSERT_TRUE(uri.GetQuery("key1")); + ASSERT_EQ("", *uri.GetQuery("key1")); +} + +TEST(URITest, empty_host) { + brpc::URI uri; + ASSERT_EQ(0, uri.SetHttpURL("http://")); + ASSERT_EQ("", uri.host()); + ASSERT_EQ("", uri.path()); +} + +TEST(URITest, invalid_spaces) { + brpc::URI uri; + ASSERT_EQ(-1, uri.SetHttpURL("foo bar://user:passwd@www.baidu.com:80/s?wd=uri#frag")); + ASSERT_STREQ("Invalid space in url", uri.status().error_cstr()); + ASSERT_EQ(-1, uri.SetHttpURL("foobar://us er:passwd@www.baidu.com:80/s?wd=uri#frag")); + ASSERT_STREQ("Invalid space in url", uri.status().error_cstr()); + ASSERT_EQ(-1, uri.SetHttpURL("foobar://user:pass wd@www.baidu.com:80/s?wd=uri#frag")); + ASSERT_STREQ("Invalid space in url", uri.status().error_cstr()); + ASSERT_EQ(-1, uri.SetHttpURL("foobar://user:passwd@www. baidu.com:80/s?wd=uri#frag")); + ASSERT_STREQ("Invalid space in url", uri.status().error_cstr()); + ASSERT_EQ(-1, uri.SetHttpURL("foobar://user:passwd@www.baidu.com:80/ s?wd=uri#frag")); + ASSERT_STREQ("Invalid space in path", uri.status().error_cstr()); + ASSERT_EQ(-1, uri.SetHttpURL("foobar://user:passwd@www.baidu.com:80/s ?wd=uri#frag")); + ASSERT_STREQ("Invalid space in path", uri.status().error_cstr()); + ASSERT_EQ(-1, uri.SetHttpURL("foobar://user:passwd@www.baidu.com:80/s? wd=uri#frag")); + ASSERT_STREQ("Invalid space in query", uri.status().error_cstr()); + ASSERT_EQ(-1, uri.SetHttpURL("foobar://user:passwd@www.baidu.com:80/s?w d=uri#frag")); + ASSERT_STREQ("Invalid space in query", uri.status().error_cstr()); + ASSERT_EQ(-1, uri.SetHttpURL("foobar://user:passwd@www.baidu.com:80/s?wd=uri #frag")); + ASSERT_STREQ("Invalid space in query", uri.status().error_cstr()); + ASSERT_EQ(-1, uri.SetHttpURL("foobar://user:passwd@www.baidu.com:80/s?wd=uri# frag")); + ASSERT_STREQ("Invalid space in fragment", uri.status().error_cstr()); + ASSERT_EQ(-1, uri.SetHttpURL("foobar://user:passwd@www.baidu.com:80/s?wd=uri#fr ag")); + ASSERT_STREQ("Invalid space in fragment", uri.status().error_cstr()); +} + +TEST(URITest, invalid_query) { + brpc::URI uri; + ASSERT_EQ(0, uri.SetHttpURL("http://a.b.c/?a-b-c:def")); + ASSERT_EQ("a-b-c:def", uri.query()); +} + +TEST(URITest, print_url) { + brpc::URI uri; + + const std::string url1 = "http://user:passwd@a.b.c/?d=c&a=b&e=f#frg1"; + ASSERT_EQ(0, uri.SetHttpURL(url1)); + std::ostringstream oss; + uri.Print(oss); + ASSERT_EQ("http://a.b.c/?d=c&a=b&e=f#frg1", oss.str()); + oss.str(""); + uri.PrintWithoutHost(oss); + ASSERT_EQ("/?d=c&a=b&e=f#frg1", oss.str()); + + const std::string url2 = "http://a.b.c/?d=c&a=b&e=f#frg1"; + ASSERT_EQ(0, uri.SetHttpURL(url2)); + oss.str(""); + uri.Print(oss); + ASSERT_EQ(url2, oss.str()); + oss.str(""); + uri.PrintWithoutHost(oss); + ASSERT_EQ("/?d=c&a=b&e=f#frg1", oss.str()); + + uri.SetQuery("e", "f2"); + uri.SetQuery("f", "g"); + ASSERT_EQ((size_t)1, uri.RemoveQuery("a")); + oss.str(""); + uri.Print(oss); + ASSERT_EQ("http://a.b.c/?d=c&e=f2&f=g#frg1", oss.str()); + oss.str(""); + uri.PrintWithoutHost(oss); + ASSERT_EQ("/?d=c&e=f2&f=g#frg1", oss.str()); +} + +TEST(URITest, copy_and_assign) { + brpc::URI uri; + const std::string url = "http://user:passwd@a.b.c/?d=c&a=b&e=f#frg1"; + ASSERT_EQ(0, uri.SetHttpURL(url)); + brpc::URI uri2 = uri; +} + +TEST(URITest, query_remover_sanity) { + std::string query = "key1=value1&key2=value2&key3=value3"; + brpc::QueryRemover qr(&query); + ASSERT_TRUE(qr); + ASSERT_EQ(qr.key(), "key1"); + ASSERT_EQ(qr.value(), "value1"); + ++qr; + ASSERT_EQ(qr.key(), "key2"); + ASSERT_EQ(qr.value(), "value2"); + ++qr; + ASSERT_EQ(qr.key(), "key3"); + ASSERT_EQ(qr.value(), "value3"); + ++qr; + ASSERT_FALSE(qr); +} + +TEST(URITest, query_remover_remove_current_key_and_value) { + std::string query = "key1=value1&key2=value2&key3=value3"; + brpc::QueryRemover qr(&query); + ASSERT_TRUE(qr); + qr.remove_current_key_and_value(); + ASSERT_EQ(qr.modified_query(), "key2=value2&key3=value3"); + qr.remove_current_key_and_value(); /* expected to have not effect */ + qr.remove_current_key_and_value(); /* expected to have not effect */ + ++qr; + ASSERT_TRUE(qr); + qr.remove_current_key_and_value(); + ASSERT_EQ(qr.modified_query(), "key3=value3"); + ++qr; + ASSERT_TRUE(qr); + qr.remove_current_key_and_value(); + ASSERT_EQ(qr.modified_query(), ""); + ++qr; + ASSERT_FALSE(qr); +} + +TEST(URITest, query_remover_random_remove) { + std::string query = "key1=value1&key2=value2&key3=value3&key4=value4" + "&key5=value5&key6=value6"; + brpc::QueryRemover qr(&query); + ASSERT_TRUE(qr); + ++qr; + ++qr; + ASSERT_TRUE(qr); + qr.remove_current_key_and_value(); + ++qr; + ++qr; + qr.remove_current_key_and_value(); + ASSERT_EQ(qr.modified_query(), "key1=value1&key2=value2&key4=value4&key6=value6"); +} + +TEST(URITest, query_remover_onekey_remove) { + std::string query = "key1=value1&key2=value2&key3=value3&key4=value4" + "&key5=value5&key6=value6"; + brpc::QueryRemover qr(&query); + ASSERT_TRUE(qr); + ++qr; + ++qr; + ++qr; + qr.remove_current_key_and_value(); + ++qr; + ++qr; + ASSERT_TRUE(qr); + ++qr; + ASSERT_FALSE(qr); + ++qr; + ++qr; + ASSERT_EQ(qr.modified_query(), "key1=value1&key2=value2&key3=value3&key5=value5&key6=value6"); +} + +TEST(URITest, query_remover_consecutive_ampersand) { + std::string query = "key1=value1&&&key2=value2&key3=value3&&"; + brpc::QueryRemover qr(&query); + ASSERT_TRUE(qr); + qr.remove_current_key_and_value(); + ASSERT_EQ(qr.modified_query(), "key2=value2&key3=value3&&"); + ++qr; + qr.remove_current_key_and_value(); + ASSERT_EQ(qr.modified_query(), "key3=value3&&"); + qr++; + qr.remove_current_key_and_value(); + ASSERT_EQ(qr.modified_query(), ""); + ++qr; + ASSERT_FALSE(qr); +} + +TEST(URITest, query_remover_only_equality) { + std::string query ="key1=&&key2&=&key3=value3"; + brpc::QueryRemover qr(&query); + ASSERT_TRUE(qr); + ASSERT_EQ(qr.key(), "key1"); + ASSERT_EQ(qr.value(), ""); + ++qr; + ASSERT_EQ(qr.key(), "key2"); + ASSERT_EQ(qr.value(), ""); + ++qr; + ASSERT_EQ(qr.key(), ""); + ASSERT_EQ(qr.value(), ""); + qr.remove_current_key_and_value(); + ++qr; + ASSERT_EQ(qr.key(), "key3"); + ASSERT_EQ(qr.value(), "value3"); + ++qr; + ASSERT_FALSE(qr); + ASSERT_EQ(qr.modified_query(), "key1=&&key2&key3=value3"); +} + +TEST(URITest, query_remover_only_one_key) { + std::string query = "key1"; + brpc::QueryRemover qr(&query); + ASSERT_TRUE(qr); + ASSERT_EQ(qr.key(), "key1"); + ASSERT_EQ(qr.value(), ""); + qr.remove_current_key_and_value(); + ++qr; + ASSERT_FALSE(qr); + ASSERT_EQ(qr.modified_query(), ""); +} + +TEST(URITest, query_remover_no_modify) { + std::string query = "key1=value1&key2=value2&key3=value3"; + brpc::QueryRemover qr(&query); + ASSERT_TRUE(qr); + ASSERT_EQ(qr.key(), "key1"); + ASSERT_EQ(qr.value(), "value1"); + ++qr; + ++qr; + ++qr; + ASSERT_FALSE(qr); + ASSERT_EQ(qr.modified_query(), query); +} + +TEST(URITest, query_remover_key_value_not_changed_after_modified_query) { + std::string query = "key1=value1&key2=value2&key3=value3"; + brpc::QueryRemover qr(&query); + ASSERT_TRUE(qr); + ++qr; + ASSERT_EQ(qr.key(), "key2"); + ASSERT_EQ(qr.value(), "value2"); + qr.remove_current_key_and_value(); + std::string new_query = qr.modified_query(); + ASSERT_EQ(new_query, "key1=value1&key3=value3"); + ASSERT_EQ(qr.key(), "key2"); + ASSERT_EQ(qr.value(), "value2"); +} + +TEST(URITest, valid_character) { + brpc::URI uri; + ASSERT_EQ(0, uri.SetHttpURL("www.baidu2.com':/?#[]@!$&()*+,;=-._~%")); +} diff --git a/test/bthread_butex_multi_tag_unittest.cpp b/test/bthread_butex_multi_tag_unittest.cpp new file mode 100644 index 0000000..e41fefd --- /dev/null +++ b/test/bthread_butex_multi_tag_unittest.cpp @@ -0,0 +1,171 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include "bthread/bthread.h" +#include "bthread/condition_variable.h" +#include "bthread/countdown_event.h" +#include "bthread/mutex.h" + +DECLARE_int32(task_group_ntags); + +int main(int argc, char* argv[]) { + FLAGS_task_group_ntags = 3; + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + return RUN_ALL_TESTS(); +} + +namespace { + +std::vector butex_wake_return(2, 0); + +void* butex_wake_func(void* arg) { + auto mutex = static_cast(arg); + butex_wake_return.push_back(bthread_self_tag()); + mutex->lock(); + butex_wake_return.push_back(bthread_self_tag()); + mutex->unlock(); + return nullptr; +} + +TEST(BthreadButexMultiTest, butex_wake) { + bthread::Mutex mutex; + mutex.lock(); + bthread_t tid1; + bthread_attr_t attr = BTHREAD_ATTR_NORMAL; + attr.tag = 1; + bthread_start_urgent(&tid1, &attr, butex_wake_func, &mutex); + mutex.unlock(); + bthread_join(tid1, nullptr); + ASSERT_EQ(butex_wake_return[0], butex_wake_return[1]); +} + +std::vector butex_wake_all_return1(2, 0); +std::vector butex_wake_all_return2(2, 0); + +struct ButexWakeAllArgs { + bthread::CountdownEvent* ev; + bthread::CountdownEvent* ack; +}; + +void* butex_wake_all_func1(void* arg) { + auto p = static_cast(arg); + auto ev = p->ev; + auto ack = p->ack; + butex_wake_all_return1.push_back(bthread_self_tag()); + ack->signal(); + ev->wait(); + butex_wake_all_return1.push_back(bthread_self_tag()); + return nullptr; +} + +void* butex_wake_all_func2(void* arg) { + auto p = static_cast(arg); + auto ev = p->ev; + auto ack = p->ack; + butex_wake_all_return2.push_back(bthread_self_tag()); + ack->signal(); + ev->wait(); + butex_wake_all_return2.push_back(bthread_self_tag()); + return nullptr; +} + +TEST(BthreadButexMultiTest, butex_wake_all) { + bthread::CountdownEvent ev(2); + bthread::CountdownEvent ack(2); + ButexWakeAllArgs args{&ev, &ack}; + bthread_t tid1, tid2; + bthread_attr_t attr1 = BTHREAD_ATTR_NORMAL; + attr1.tag = 1; + bthread_start_background(&tid1, &attr1, butex_wake_all_func1, &args); + bthread_attr_t attr2 = BTHREAD_ATTR_NORMAL; + attr2.tag = 2; + bthread_start_background(&tid2, &attr2, butex_wake_all_func2, &args); + ack.wait(); + ev.signal(2); + bthread_join(tid1, nullptr); + bthread_join(tid2, nullptr); + ASSERT_EQ(butex_wake_all_return1[0], butex_wake_all_return1[1]); + ASSERT_EQ(butex_wake_all_return2[0], butex_wake_all_return2[1]); +} + +std::vector butex_requeue_return1(2, 0); +std::vector butex_requeue_return2(2, 0); + +struct ButexRequeueArgs { + bthread::Mutex* mutex; + bthread::ConditionVariable* cond; + bthread::CountdownEvent* ack; +}; + +void* butex_requeue_func1(void* arg) { + auto p = static_cast(arg); + auto mutex = p->mutex; + auto cond = p->cond; + auto ack = p->ack; + butex_wake_all_return1.push_back(bthread_self_tag()); + std::unique_lock lk(*mutex); + ack->signal(); + cond->wait(lk); + butex_wake_all_return1.push_back(bthread_self_tag()); + return nullptr; +} + +void* butex_requeue_func2(void* arg) { + auto p = static_cast(arg); + auto mutex = p->mutex; + auto cond = p->cond; + auto ack = p->ack; + butex_wake_all_return2.push_back(bthread_self_tag()); + std::unique_lock lk(*mutex); + ack->signal(); + cond->wait(lk); + butex_wake_all_return2.push_back(bthread_self_tag()); + return nullptr; +} + +TEST(BthreadButexMultiTest, butex_requeue) { + bthread::Mutex mutex; + bthread::ConditionVariable cond; + bthread::CountdownEvent ack(2); + ButexRequeueArgs args{&mutex, &cond, &ack}; + + bthread_t tid1, tid2; + bthread_attr_t attr1 = BTHREAD_ATTR_NORMAL; + attr1.tag = 1; + bthread_start_background(&tid1, &attr1, butex_requeue_func1, &args); + bthread_attr_t attr2 = BTHREAD_ATTR_NORMAL; + attr2.tag = 2; + bthread_start_background(&tid2, &attr2, butex_requeue_func2, &args); + ack.wait(); + { + std::unique_lock lk(mutex); + cond.notify_all(); + } + { + std::unique_lock lk(mutex); + cond.notify_all(); + } + bthread_join(tid1, nullptr); + bthread_join(tid2, nullptr); + ASSERT_EQ(butex_wake_all_return1[0], butex_wake_all_return1[1]); + ASSERT_EQ(butex_wake_all_return2[0], butex_wake_all_return2[1]); +} + +} // namespace diff --git a/test/bthread_butex_unittest.cpp b/test/bthread_butex_unittest.cpp new file mode 100644 index 0000000..8f2f4f5 --- /dev/null +++ b/test/bthread_butex_unittest.cpp @@ -0,0 +1,446 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/atomicops.h" +#include "butil/time.h" +#include "butil/macros.h" +#include "butil/logging.h" +#include "bthread/butex.h" +#include "bthread/task_control.h" +#include "bthread/task_group.h" +#include "bthread/bthread.h" +#include "bthread/unstable.h" +#include "bthread/interrupt_pthread.h" + +namespace bthread { +extern butil::atomic g_task_control; +inline TaskControl* get_task_control() { + return g_task_control.load(butil::memory_order_consume); +} +} // namespace bthread + +namespace { +TEST(ButexTest, wait_on_already_timedout_butex) { + uint32_t* butex = bthread::butex_create_checked(); + ASSERT_TRUE(butex); + timespec now; + ASSERT_EQ(0, clock_gettime(CLOCK_REALTIME, &now)); + *butex = 1; + ASSERT_EQ(-1, bthread::butex_wait(butex, 1, &now)); + ASSERT_EQ(ETIMEDOUT, errno); +} + +void* sleeper(void* arg) { + bthread_usleep((uint64_t)arg); + return NULL; +} + +void* joiner(void* arg) { + const long t1 = butil::gettimeofday_us(); + for (bthread_t* th = (bthread_t*)arg; *th; ++th) { + if (0 != bthread_join(*th, NULL)) { + LOG(FATAL) << "fail to join thread_" << th - (bthread_t*)arg; + } + long elp = butil::gettimeofday_us() - t1; + EXPECT_LE(labs(elp - (th - (bthread_t*)arg + 1) * 100000L), 15000L) + << "timeout when joining thread_" << th - (bthread_t*)arg; + LOG(INFO) << "Joined thread " << *th << " at " << elp << "us [" + << bthread_self() << "]"; + } + for (bthread_t* th = (bthread_t*)arg; *th; ++th) { + EXPECT_EQ(0, bthread_join(*th, NULL)); + } + return NULL; +} + +struct A { + uint64_t a; + char dummy[0]; +}; + +struct B { + uint64_t a; +}; + + +TEST(ButexTest, with_or_without_array_zero) { + ASSERT_EQ(sizeof(B), sizeof(A)); +} + + +TEST(ButexTest, join) { + const size_t N = 6; + const size_t M = 6; + bthread_t th[N+1]; + bthread_t jth[M]; + pthread_t pth[M]; + for (size_t i = 0; i < N; ++i) { + bthread_attr_t attr = (i == 0 ? BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL); + ASSERT_EQ(0, bthread_start_urgent( + &th[i], &attr, sleeper, + (void*)(100000L/*100ms*/ * (i + 1)))); + } + th[N] = 0; // joiner will join tids in `th' until seeing 0. + for (size_t i = 0; i < M; ++i) { + ASSERT_EQ(0, bthread_start_urgent(&jth[i], NULL, joiner, th)); + } + for (size_t i = 0; i < M; ++i) { + ASSERT_EQ(0, pthread_create(&pth[i], NULL, joiner, th)); + } + + for (size_t i = 0; i < M; ++i) { + ASSERT_EQ(0, bthread_join(jth[i], NULL)) + << "i=" << i << " error=" << berror(); + } + for (size_t i = 0; i < M; ++i) { + ASSERT_EQ(0, pthread_join(pth[i], NULL)); + } +} + + +struct WaiterArg { + int expected_result; + int expected_value; + butil::atomic *butex; + const timespec *ptimeout; +}; + +void* waiter(void* arg) { + WaiterArg * wa = (WaiterArg*)arg; + const long t1 = butil::gettimeofday_us(); + const int rc = bthread::butex_wait( + wa->butex, wa->expected_value, wa->ptimeout); + const long t2 = butil::gettimeofday_us(); + if (rc == 0) { + EXPECT_EQ(wa->expected_result, 0) << bthread_self(); + } else { + EXPECT_EQ(wa->expected_result, errno) << bthread_self(); + } + LOG(INFO) << "after wait, time=" << (t2-t1) << "us"; + return NULL; +} + +TEST(ButexTest, sanity) { + const size_t N = 5; + WaiterArg args[N * 4]; + pthread_t t1, t2; + butil::atomic* b1 = + bthread::butex_create_checked >(); + ASSERT_TRUE(b1); + bthread::butex_destroy(b1); + + b1 = bthread::butex_create_checked >(); + *b1 = 1; + ASSERT_EQ(0, bthread::butex_wake(b1)); + + WaiterArg *unmatched_arg = new WaiterArg; + unmatched_arg->expected_value = *b1 + 1; + unmatched_arg->expected_result = EWOULDBLOCK; + unmatched_arg->butex = b1; + unmatched_arg->ptimeout = NULL; + pthread_create(&t2, NULL, waiter, unmatched_arg); + bthread_t th; + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, waiter, unmatched_arg)); + + const timespec abstime = butil::seconds_from_now(1); + for (size_t i = 0; i < 4*N; ++i) { + args[i].expected_value = *b1; + args[i].butex = b1; + if ((i % 2) == 0) { + args[i].expected_result = 0; + args[i].ptimeout = NULL; + } else { + args[i].expected_result = ETIMEDOUT; + args[i].ptimeout = &abstime; + } + if (i < 2*N) { + pthread_create(&t1, NULL, waiter, &args[i]); + } else { + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, waiter, &args[i])); + } + } + + sleep(2); + for (size_t i = 0; i < 2*N; ++i) { + ASSERT_EQ(1, bthread::butex_wake(b1)); + } + ASSERT_EQ(0, bthread::butex_wake(b1)); + sleep(1); + bthread::butex_destroy(b1); +} + + +struct ButexWaitArg { + int* butex; + int expected_val; + long wait_msec; + int error_code; +}; + +void* wait_butex(void* void_arg) { + ButexWaitArg* arg = static_cast(void_arg); + const timespec ts = butil::milliseconds_from_now(arg->wait_msec); + int rc = bthread::butex_wait(arg->butex, arg->expected_val, &ts); + int saved_errno = errno; + if (arg->error_code) { + EXPECT_EQ(-1, rc); + EXPECT_EQ(arg->error_code, saved_errno); + } else { + EXPECT_EQ(0, rc); + } + return NULL; +} + +TEST(ButexTest, wait_without_stop) { + int* butex = bthread::butex_create_checked(); + *butex = 7; + butil::Timer tm; + const long WAIT_MSEC = 500; + for (int i = 0; i < 2; ++i) { + const bthread_attr_t attr = + (i == 0 ? BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL); + ButexWaitArg arg = { butex, *butex, WAIT_MSEC, ETIMEDOUT }; + bthread_t th; + + tm.start(); + ASSERT_EQ(0, bthread_start_urgent(&th, &attr, wait_butex, &arg)); + ASSERT_EQ(0, bthread_join(th, NULL)); + tm.stop(); + + ASSERT_LT(labs(tm.m_elapsed() - WAIT_MSEC), 250); + } + bthread::butex_destroy(butex); +} + +TEST(ButexTest, stop_after_running) { + int* butex = bthread::butex_create_checked(); + *butex = 7; + butil::Timer tm; + const long WAIT_MSEC = 500; + const long SLEEP_MSEC = 10; + for (int i = 0; i < 2; ++i) { + const bthread_attr_t attr = + (i == 0 ? BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL); + bthread_t th; + ButexWaitArg arg = { butex, *butex, WAIT_MSEC, EINTR }; + + tm.start(); + ASSERT_EQ(0, bthread_start_urgent(&th, &attr, wait_butex, &arg)); + ASSERT_EQ(0, bthread_usleep(SLEEP_MSEC * 1000L)); + ASSERT_EQ(0, bthread_stop(th)); + ASSERT_EQ(0, bthread_join(th, NULL)); + tm.stop(); + + ASSERT_LT(labs(tm.m_elapsed() - SLEEP_MSEC), 25); + // ASSERT_TRUE(bthread::get_task_control()-> + // timer_thread()._idset.empty()); + ASSERT_EQ(EINVAL, bthread_stop(th)); + } + bthread::butex_destroy(butex); +} + +TEST(ButexTest, stop_before_running) { + int* butex = bthread::butex_create_checked(); + *butex = 7; + butil::Timer tm; + const long WAIT_MSEC = 500; + + for (int i = 0; i < 2; ++i) { + const bthread_attr_t attr = + (i == 0 ? BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL) | BTHREAD_NOSIGNAL; + bthread_t th; + ButexWaitArg arg = { butex, *butex, WAIT_MSEC, EINTR }; + + tm.start(); + ASSERT_EQ(0, bthread_start_background(&th, &attr, wait_butex, &arg)); + ASSERT_EQ(0, bthread_stop(th)); + bthread_flush(); + ASSERT_EQ(0, bthread_join(th, NULL)); + tm.stop(); + + ASSERT_LT(tm.m_elapsed(), 5); + // ASSERT_TRUE(bthread::get_task_control()-> + // timer_thread()._idset.empty()); + ASSERT_EQ(EINVAL, bthread_stop(th)); + } + bthread::butex_destroy(butex); +} + +void* join_the_waiter(void* arg) { + EXPECT_EQ(0, bthread_join((bthread_t)arg, NULL)); + return NULL; +} + +TEST(ButexTest, join_cant_be_wakeup) { + const long WAIT_MSEC = 100; + int* butex = bthread::butex_create_checked(); + *butex = 7; + butil::Timer tm; + ButexWaitArg arg = { butex, *butex, 1000, EINTR }; + + for (int i = 0; i < 2; ++i) { + const bthread_attr_t attr = + (i == 0 ? BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL); + tm.start(); + bthread_t th, th2; + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, wait_butex, &arg)); + ASSERT_EQ(0, bthread_start_urgent(&th2, &attr, join_the_waiter, (void*)th)); + ASSERT_EQ(0, bthread_stop(th2)); + ASSERT_EQ(0, bthread_usleep(WAIT_MSEC / 2 * 1000L)); + ASSERT_TRUE(bthread::TaskGroup::exists(th)); + ASSERT_TRUE(bthread::TaskGroup::exists(th2)); + ASSERT_EQ(0, bthread_usleep(WAIT_MSEC / 2 * 1000L)); + ASSERT_EQ(0, bthread_stop(th)); + ASSERT_EQ(0, bthread_join(th2, NULL)); + ASSERT_EQ(0, bthread_join(th, NULL)); + tm.stop(); + ASSERT_LT(tm.m_elapsed(), WAIT_MSEC + 15); + ASSERT_EQ(EINVAL, bthread_stop(th)); + ASSERT_EQ(EINVAL, bthread_stop(th2)); + } + bthread::butex_destroy(butex); +} + +TEST(ButexTest, stop_after_slept) { + butil::Timer tm; + const long SLEEP_MSEC = 100; + const long WAIT_MSEC = 10; + + for (int i = 0; i < 2; ++i) { + const bthread_attr_t attr = + (i == 0 ? BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL); + tm.start(); + bthread_t th; + ASSERT_EQ(0, bthread_start_urgent( + &th, &attr, sleeper, (void*)(SLEEP_MSEC*1000L))); + ASSERT_EQ(0, bthread_usleep(WAIT_MSEC * 1000L)); + ASSERT_EQ(0, bthread_stop(th)); + ASSERT_EQ(0, bthread_join(th, NULL)); + tm.stop(); + if (attr.stack_type == BTHREAD_STACKTYPE_PTHREAD) { + ASSERT_LT(labs(tm.m_elapsed() - SLEEP_MSEC), 15); + } else { + ASSERT_LT(labs(tm.m_elapsed() - WAIT_MSEC), 15); + } + // ASSERT_TRUE(bthread::get_task_control()-> + // timer_thread()._idset.empty()); + ASSERT_EQ(EINVAL, bthread_stop(th)); + } +} + +TEST(ButexTest, stop_just_when_sleeping) { + butil::Timer tm; + const long SLEEP_MSEC = 100; + + for (int i = 0; i < 2; ++i) { + const bthread_attr_t attr = + (i == 0 ? BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL); + tm.start(); + bthread_t th; + ASSERT_EQ(0, bthread_start_urgent( + &th, &attr, sleeper, (void*)(SLEEP_MSEC*1000L))); + ASSERT_EQ(0, bthread_stop(th)); + ASSERT_EQ(0, bthread_join(th, NULL)); + tm.stop(); + if (attr.stack_type == BTHREAD_STACKTYPE_PTHREAD) { + ASSERT_LT(labs(tm.m_elapsed() - SLEEP_MSEC), 15); + } else { + ASSERT_LT(tm.m_elapsed(), 15); + } + // ASSERT_TRUE(bthread::get_task_control()-> + // timer_thread()._idset.empty()); + ASSERT_EQ(EINVAL, bthread_stop(th)); + } +} + +TEST(ButexTest, stop_before_sleeping) { + butil::Timer tm; + const long SLEEP_MSEC = 100; + + for (int i = 0; i < 2; ++i) { + bthread_t th; + const bthread_attr_t attr = + (i == 0 ? BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL) | BTHREAD_NOSIGNAL; + + tm.start(); + ASSERT_EQ(0, bthread_start_background(&th, &attr, sleeper, + (void*)(SLEEP_MSEC*1000L))); + ASSERT_EQ(0, bthread_stop(th)); + bthread_flush(); + ASSERT_EQ(0, bthread_join(th, NULL)); + tm.stop(); + + if (attr.stack_type == BTHREAD_STACKTYPE_PTHREAD) { + ASSERT_LT(labs(tm.m_elapsed() - SLEEP_MSEC), 10); + } else { + ASSERT_LT(tm.m_elapsed(), 10); + } + // ASSERT_TRUE(bthread::get_task_control()-> + // timer_thread()._idset.empty()); + ASSERT_EQ(EINVAL, bthread_stop(th)); + } +} + +void* trigger_signal(void* arg) { + pthread_t * th = (pthread_t*)arg; + const long t1 = butil::gettimeofday_us(); + for (size_t i = 0; i < 50; ++i) { + usleep(100000); + if (bthread::interrupt_pthread(*th) == ESRCH) { + LOG(INFO) << "waiter thread end, trigger count=" << i; + break; + } + } + const long t2 = butil::gettimeofday_us(); + LOG(INFO) << "trigger signal thread end, elapsed=" << (t2-t1) << "us"; + return NULL; +} + +TEST(ButexTest, wait_with_signal_triggered) { + butil::Timer tm; + + const int64_t WAIT_MSEC = 500; + WaiterArg waiter_args; + pthread_t waiter_th, tigger_th; + butil::atomic* butex = + bthread::butex_create_checked >(); + ASSERT_TRUE(butex); + *butex = 1; + ASSERT_EQ(0, bthread::butex_wake(butex)); + + const timespec abstime = butil::milliseconds_from_now(WAIT_MSEC); + waiter_args.expected_value = *butex; + waiter_args.butex = butex; + waiter_args.expected_result = ETIMEDOUT; + waiter_args.ptimeout = &abstime; + tm.start(); + pthread_create(&waiter_th, NULL, waiter, &waiter_args); + pthread_create(&tigger_th, NULL, trigger_signal, &waiter_th); + + ASSERT_EQ(0, pthread_join(waiter_th, NULL)); + tm.stop(); + auto wait_elapsed_ms = tm.m_elapsed();; + LOG(INFO) << "waiter thread end, elapsed " << wait_elapsed_ms << " ms"; + + ASSERT_LT(labs(wait_elapsed_ms - WAIT_MSEC), 250); + + ASSERT_EQ(0, pthread_join(tigger_th, NULL)); + bthread::butex_destroy(butex); +} + +} // namespace diff --git a/test/bthread_cond_bug_unittest.cpp b/test/bthread_cond_bug_unittest.cpp new file mode 100644 index 0000000..90881f5 --- /dev/null +++ b/test/bthread_cond_bug_unittest.cpp @@ -0,0 +1,141 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include + +#include +#include + +#include "bthread/bthread.h" +#include "bthread/condition_variable.h" +#include "bthread/mutex.h" +#include "butil/logging.h" +#include "butil/macros.h" +#include "bvar/bvar.h" + +DEFINE_int64(wait_us, 5, "wait us"); +typedef std::unique_lock Lock; +typedef bthread::ConditionVariable Condition; +bthread::Mutex g_mutex; +Condition g_cond; +std::deque g_que; +const size_t g_capacity = 2000; +const int PRODUCER_NUM = 5; +struct ProducerStat { + std::atomic loop_count; + bvar::Adder wait_count; + bvar::Adder wait_timeout_count; + bvar::Adder wait_success_count; +}; +ProducerStat g_stat[PRODUCER_NUM]; + +void* print_func(void* arg) { + int last_loop[PRODUCER_NUM] = {0}; + for (int j = 0; j < 10; j++) { + usleep(1000000); + for (int i = 0; i < PRODUCER_NUM; i++) { + if (g_stat[i].loop_count.load() <= last_loop[i]) { + LOG(ERROR) << "producer thread:" << i << " stopped"; + return nullptr; + } + LOG(INFO) << "producer stat idx:" << i + << " wait:" << g_stat[i].wait_count + << " wait_timeout:" << g_stat[i].wait_timeout_count + << " wait_success:" << g_stat[i].wait_success_count; + g_stat[i].loop_count = g_stat[i].loop_count.load(); + } + } + return (void*)1; +} + +void* produce_func(void* arg) { + const int64_t wait_us = FLAGS_wait_us; + LOG(INFO) << "wait us:" << wait_us; + int64_t idx = (int64_t)(arg); + int32_t i = 0; + while (!bthread_stopped(bthread_self())) { + //LOG(INFO) << "come to a new round " << idx << "round[" << i << "]"; + { + Lock lock(g_mutex); + while (g_que.size() >= g_capacity && !bthread_stopped(bthread_self())) { + g_stat[idx].wait_count << 1; + //LOG(INFO) << "wait begin " << idx; + int ret = g_cond.wait_for(lock, wait_us); + if (ret == ETIMEDOUT) { + g_stat[idx].wait_timeout_count << 1; + //LOG_EVERY_SECOND(INFO) << "wait timeout " << idx; + } else { + g_stat[idx].wait_success_count << 1; + //LOG_EVERY_SECOND(INFO) << "wait early " << idx; + } + } + g_que.push_back(++i); + //LOG(INFO) << "push back " << idx << " data[" << i << "]"; + } + usleep(rand() % 20 + 5); + g_stat[idx].loop_count.fetch_add(1); + } + LOG(INFO) << "producer func return, idx:" << idx; + return nullptr; +} + +void* consume_func(void* arg) { + while (!bthread_stopped(bthread_self())) { + bool need_notify = false; + { + Lock lock(g_mutex); + need_notify = (g_que.size() == g_capacity); + if (!g_que.empty()) { + g_que.pop_front(); + LOG_EVERY_SECOND(INFO) << "pop a data"; + } else { + LOG_EVERY_SECOND(INFO) << "que is empty"; + } + } + usleep(rand() % 300 + 500); + if (need_notify) { + //g_cond.notify_all(); + //LOG(WARNING) << "notify"; + } + } + LOG(INFO) << "consumer func return"; + return nullptr; +} + +TEST(BthreadCondBugTest, test_bug) { + bthread_t tids[PRODUCER_NUM]; + for (int i = 0; i < PRODUCER_NUM; i++) { + bthread_start_background(&tids[i], NULL, produce_func, (void*)(int64_t)i); + } + bthread_t tid; + bthread_start_background(&tid, NULL, consume_func, NULL); + + int64_t ret = (int64_t)print_func(nullptr); + + bthread_stop(tid); + bthread_join(tid, nullptr); + for (int i = 0; i < PRODUCER_NUM; i++) { + bthread_stop(tids[i]); + bthread_join(tids[i], nullptr); + } + + ASSERT_EQ(ret, 1); +} diff --git a/test/bthread_cond_unittest.cpp b/test/bthread_cond_unittest.cpp new file mode 100644 index 0000000..f2dcddf --- /dev/null +++ b/test/bthread_cond_unittest.cpp @@ -0,0 +1,516 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include "butil/atomicops.h" +#include "butil/time.h" +#include "butil/macros.h" +#include "butil/scoped_lock.h" +#include "gperftools_helper.h" +#include "bthread/bthread.h" +#include "bthread/condition_variable.h" +#include "bthread/stack.h" + +namespace { +struct Arg { + bthread_mutex_t m; + bthread_cond_t c; +}; + +pthread_mutex_t wake_mutex = PTHREAD_MUTEX_INITIALIZER; +long signal_start_time = 0; +std::vector wake_tid; +std::vector wake_time; +volatile bool stop = false; +const long SIGNAL_INTERVAL_US = 10000; + +void* signaler(void* void_arg) { + Arg* a = (Arg*)void_arg; + signal_start_time = butil::gettimeofday_us(); + while (!stop) { + bthread_usleep(SIGNAL_INTERVAL_US); + bthread_cond_signal(&a->c); + } + return NULL; +} + +void* waiter(void* void_arg) { + Arg* a = (Arg*)void_arg; + bthread_mutex_lock(&a->m); + while (!stop) { + bthread_cond_wait(&a->c, &a->m); + + BAIDU_SCOPED_LOCK(wake_mutex); + wake_tid.push_back(bthread_self()); + wake_time.push_back(butil::gettimeofday_us()); + } + bthread_mutex_unlock(&a->m); + return NULL; +} + +TEST(CondTest, sanity) { + Arg a; + ASSERT_EQ(0, bthread_mutex_init(&a.m, NULL)); + ASSERT_EQ(0, bthread_cond_init(&a.c, NULL)); + // has no effect + ASSERT_EQ(0, bthread_cond_signal(&a.c)); + + stop = false; + wake_tid.resize(1024); + wake_tid.clear(); + wake_time.resize(1024); + wake_time.clear(); + + bthread_t wth[8]; + const size_t NW = ARRAY_SIZE(wth); + for (size_t i = 0; i < NW; ++i) { + ASSERT_EQ(0, bthread_start_urgent(&wth[i], NULL, waiter, &a)); + } + + bthread_t sth; + ASSERT_EQ(0, bthread_start_urgent(&sth, NULL, signaler, &a)); + + bthread_usleep(SIGNAL_INTERVAL_US * 200); + + pthread_mutex_lock(&wake_mutex); + const size_t nbeforestop = wake_time.size(); + pthread_mutex_unlock(&wake_mutex); + + stop = true; + for (size_t i = 0; i < NW; ++i) { + bthread_cond_signal(&a.c); + } + + bthread_join(sth, NULL); + for (size_t i = 0; i < NW; ++i) { + bthread_join(wth[i], NULL); + } + + printf("wake up for %lu times\n", wake_tid.size()); + + // Check timing + long square_sum = 0; + for (size_t i = 0; i < nbeforestop; ++i) { + long last_time = (i ? wake_time[i-1] : signal_start_time); + long delta = wake_time[i] - last_time - SIGNAL_INTERVAL_US; + EXPECT_GT(wake_time[i], last_time); + square_sum += delta * delta; + EXPECT_LT(labs(delta), 10000L) << "error[" << i << "]=" << delta << "=" + << wake_time[i] << " - " << last_time; + } + printf("Average error is %fus\n", sqrt(square_sum / std::max(nbeforestop, 1UL))); + + // Check fairness + std::map count; + for (size_t i = 0; i < wake_tid.size(); ++i) { + ++count[wake_tid[i]]; + } + EXPECT_EQ(NW, count.size()); + int avg_count = (int)(wake_tid.size() / count.size()); + for (std::map::iterator + it = count.begin(); it != count.end(); ++it) { + ASSERT_LE(abs(it->second - avg_count), 1) + << "bthread=" << it->first + << " count=" << it->second + << " avg=" << avg_count; + printf("%" PRId64 " wakes up %d times\n", it->first, it->second); + } + + bthread_cond_destroy(&a.c); + bthread_mutex_destroy(&a.m); +} + +struct WrapperArg { + bthread::Mutex mutex; + bthread::ConditionVariable cond; + bool ready = false; + static std::atomic wake_time; +}; +std::atomic WrapperArg::wake_time{0}; + +void* cv_signaler(void* void_arg) { + WrapperArg* a = (WrapperArg*)void_arg; + signal_start_time = butil::gettimeofday_us(); + while (!stop) { + bthread_usleep(SIGNAL_INTERVAL_US); + a->cond.notify_one(); + } + return NULL; +} + +void* cv_bmutex_waiter(void* void_arg) { + WrapperArg* a = (WrapperArg*)void_arg; + std::unique_lock lck(*a->mutex.native_handler()); + while (!stop) { + a->cond.wait(lck); + } + return NULL; +} + +void* cv_mutex_waiter(void* void_arg) { + WrapperArg* a = (WrapperArg*)void_arg; + std::unique_lock lck(a->mutex); + while (!stop) { + a->cond.wait(lck); + } + return NULL; +} + + +void* cv_bmutex_waiter_with_pred(void* void_arg) { + WrapperArg* a = (WrapperArg*)void_arg; + std::unique_lock lck(*a->mutex.native_handler()); + a->cond.wait(lck, [&] { return a->ready; }); + WrapperArg::wake_time.fetch_add(1); + return NULL; +} + +void* cv_mutex_waiter_with_pred(void* void_arg) { + WrapperArg* a = (WrapperArg*)void_arg; + std::unique_lock lck(a->mutex); + a->cond.wait(lck, [&] { return a->ready; }); + WrapperArg::wake_time.fetch_add(1); + return NULL; +} + +#define COND_IN_PTHREAD + +#ifndef COND_IN_PTHREAD +#define pthread_join bthread_join +#define pthread_create bthread_start_urgent +#endif + +TEST(CondTest, cpp_wrapper) { + stop = false; + bthread::ConditionVariable cond; + pthread_t bmutex_waiter_threads[8]; + pthread_t mutex_waiter_threads[8]; + pthread_t signal_thread; + WrapperArg a; + for (size_t i = 0; i < ARRAY_SIZE(bmutex_waiter_threads); ++i) { + ASSERT_EQ(0, pthread_create(&bmutex_waiter_threads[i], NULL, + cv_bmutex_waiter, &a)); + ASSERT_EQ(0, pthread_create(&mutex_waiter_threads[i], NULL, + cv_mutex_waiter, &a)); + } + ASSERT_EQ(0, pthread_create(&signal_thread, NULL, cv_signaler, &a)); + bthread_usleep(100L * 1000); + { + BAIDU_SCOPED_LOCK(a.mutex); + stop = true; + } + pthread_join(signal_thread, NULL); + a.cond.notify_all(); + for (size_t i = 0; i < ARRAY_SIZE(bmutex_waiter_threads); ++i) { + pthread_join(bmutex_waiter_threads[i], NULL); + pthread_join(mutex_waiter_threads[i], NULL); + } +} + +TEST(CondTest, cpp_wrapper2) { + stop = false; + bthread::ConditionVariable cond; + pthread_t bmutex_waiter_threads[8]; + pthread_t mutex_waiter_threads[8]; + pthread_t signal_thread; + WrapperArg a; + for (size_t i = 0; i < ARRAY_SIZE(bmutex_waiter_threads); ++i) { + ASSERT_EQ(0, pthread_create(&bmutex_waiter_threads[i], NULL, + cv_bmutex_waiter_with_pred, &a)); + ASSERT_EQ(0, pthread_create(&mutex_waiter_threads[i], NULL, + cv_mutex_waiter_with_pred, &a)); + } + ASSERT_EQ(0, pthread_create(&signal_thread, NULL, cv_signaler, &a)); + bthread_usleep(100L * 1000); + ASSERT_EQ(WrapperArg::wake_time, 0); + { + BAIDU_SCOPED_LOCK(a.mutex); + stop = true; + a.ready = true; + + } + pthread_join(signal_thread, NULL); + a.cond.notify_all(); + for (size_t i = 0; i < ARRAY_SIZE(bmutex_waiter_threads); ++i) { + pthread_join(bmutex_waiter_threads[i], NULL); + pthread_join(mutex_waiter_threads[i], NULL); + } + ASSERT_EQ(WrapperArg::wake_time, 16); +} + +#ifndef COND_IN_PTHREAD +#undef pthread_join +#undef pthread_create +#endif + +class Signal { +protected: + Signal() : _signal(0) {} + void notify() { + BAIDU_SCOPED_LOCK(_m); + ++_signal; + _c.notify_one(); + } + + int wait(int old_signal) { + std::unique_lock lck(_m); + while (_signal == old_signal) { + _c.wait(lck); + } + return _signal; + } + +private: + bthread::Mutex _m; + bthread::ConditionVariable _c; + int _signal; +}; + +struct PingPongArg { + bool stopped; + Signal sig1; + Signal sig2; + butil::atomic nthread; + butil::atomic total_count; +}; + +void *ping_pong_thread(void* arg) { + PingPongArg* a = (PingPongArg*)arg; + long local_count = 0; + bool odd = (a->nthread.fetch_add(1)) % 2; + int old_signal = 0; + while (!a->stopped) { + if (odd) { + a->sig1.notify(); + old_signal = a->sig2.wait(old_signal); + } else { + old_signal = a->sig1.wait(old_signal); + a->sig2.notify(); + } + ++local_count; + } + a->total_count.fetch_add(local_count); + return NULL; +} + +TEST(CondTest, ping_pong) { + PingPongArg arg; + arg.stopped = false; + arg.nthread = 0; + bthread_t threads[2]; + ProfilerStart("cond.prof"); + for (int i = 0; i < 2; ++i) { + ASSERT_EQ(0, bthread_start_urgent(&threads[i], NULL, ping_pong_thread, &arg)); + } + usleep(1000 * 1000); + arg.stopped = true; + arg.sig1.notify(); + arg.sig2.notify(); + for (int i = 0; i < 2; ++i) { + ASSERT_EQ(0, bthread_join(threads[i], NULL)); + } + ProfilerStop(); + LOG(INFO) << "total_count=" << arg.total_count.load(); +} + +struct BroadcastArg { + bthread::ConditionVariable wait_cond; + bthread::ConditionVariable broadcast_cond; + bthread::Mutex mutex; + int nwaiter; + int cur_waiter; + int rounds; + int sig; +}; + +void* wait_thread(void* arg) { + BroadcastArg* ba = (BroadcastArg*)arg; + std::unique_lock lck(ba->mutex); + while (ba->rounds > 0) { + const int saved_round = ba->rounds; + ++ba->cur_waiter; + while (saved_round == ba->rounds) { + if (ba->cur_waiter >= ba->nwaiter) { + ba->broadcast_cond.notify_one(); + } + ba->wait_cond.wait(lck); + } + } + return NULL; +} + +void* broadcast_thread(void* arg) { + BroadcastArg* ba = (BroadcastArg*)arg; + //int local_round = 0; + while (ba->rounds > 0) { + std::unique_lock lck(ba->mutex); + while (ba->cur_waiter < ba->nwaiter) { + ba->broadcast_cond.wait(lck); + } + ba->cur_waiter = 0; + --ba->rounds; + ba->wait_cond.notify_all(); + } + return NULL; +} + +void* disturb_thread(void* arg) { + BroadcastArg* ba = (BroadcastArg*)arg; + std::unique_lock lck(ba->mutex); + while (ba->rounds > 0) { + lck.unlock(); + lck.lock(); + } + return NULL; +} + +TEST(CondTest, mixed_usage) { + BroadcastArg ba; + ba.nwaiter = 0; + ba.cur_waiter = 0; + ba.rounds = 30000; + const int NTHREADS = 10; + ba.nwaiter = NTHREADS * 2; + + bthread_t normal_threads[NTHREADS]; + for (int i = 0; i < NTHREADS; ++i) { + ASSERT_EQ(0, bthread_start_urgent(&normal_threads[i], NULL, wait_thread, &ba)); + } + pthread_t pthreads[NTHREADS]; + for (int i = 0; i < NTHREADS; ++i) { + ASSERT_EQ(0, pthread_create(&pthreads[i], NULL, + wait_thread, &ba)); + } + pthread_t broadcast; + pthread_t disturb; + ASSERT_EQ(0, pthread_create(&broadcast, NULL, broadcast_thread, &ba)); + ASSERT_EQ(0, pthread_create(&disturb, NULL, disturb_thread, &ba)); + for (int i = 0; i < NTHREADS; ++i) { + bthread_join(normal_threads[i], NULL); + pthread_join(pthreads[i], NULL); + } + pthread_join(broadcast, NULL); + pthread_join(disturb, NULL); +} + +class BthreadCond { +public: + BthreadCond() { + bthread_cond_init(&_cond, NULL); + bthread_mutex_init(&_mutex, NULL); + _count = 1; + } + ~BthreadCond() { + bthread_mutex_destroy(&_mutex); + bthread_cond_destroy(&_cond); + } + + void Init(int count = 1) { + _count = count; + } + + int Signal() { + int ret = 0; + bthread_mutex_lock(&_mutex); + _count --; + bthread_cond_signal(&_cond); + bthread_mutex_unlock(&_mutex); + return ret; + } + + int Wait() { + int ret = 0; + bthread_mutex_lock(&_mutex); + while (_count > 0) { + ret = bthread_cond_wait(&_cond, &_mutex); + } + bthread_mutex_unlock(&_mutex); + return ret; + } +private: + int _count; + bthread_cond_t _cond; + bthread_mutex_t _mutex; +}; + +#ifndef BUTIL_USE_ASAN +volatile bool g_stop = false; +bool started_wait = false; +bool ended_wait = false; + +void* usleep_thread(void *) { + while (!g_stop) { + bthread_usleep(1000L * 1000L); + } + return NULL; +} + +void* wait_cond_thread(void* arg) { + BthreadCond* c = (BthreadCond*)arg; + started_wait = true; + c->Wait(); + ended_wait = true; + return NULL; +} + +static void launch_many_bthreads() { + g_stop = false; + bthread_t tid; + BthreadCond c; + c.Init(); + butil::Timer tm; + bthread_start_urgent(&tid, &BTHREAD_ATTR_PTHREAD, wait_cond_thread, &c); + std::vector tids; + tids.reserve(32768); + tm.start(); + for (size_t i = 0; i < 32768; ++i) { + bthread_t t0; + ASSERT_EQ(0, bthread_start_background(&t0, NULL, usleep_thread, NULL)); + tids.push_back(t0); + } + tm.stop(); + LOG(INFO) << "Creating bthreads took " << tm.u_elapsed() << " us"; + usleep(3 * 1000 * 1000L); + c.Signal(); + g_stop = true; + bthread_join(tid, NULL); + for (size_t i = 0; i < tids.size(); ++i) { + LOG_EVERY_SECOND(INFO) << "Joined " << i << " threads"; + bthread_join(tids[i], NULL); + } + LOG_EVERY_SECOND(INFO) << "Joined " << tids.size() << " threads"; +} + +TEST(CondTest, too_many_bthreads_from_pthread) { + bthread_setconcurrency(16); + launch_many_bthreads(); +} + +static void* run_launch_many_bthreads(void*) { + launch_many_bthreads(); + return NULL; +} + +TEST(CondTest, too_many_bthreads_from_bthread) { + bthread_setconcurrency(16); + bthread_t th; + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, run_launch_many_bthreads, NULL)); + bthread_join(th, NULL); +} +#endif // BUTIL_USE_ASAN +} // namespace diff --git a/test/bthread_countdown_event_unittest.cpp b/test/bthread_countdown_event_unittest.cpp new file mode 100644 index 0000000..bed8f17 --- /dev/null +++ b/test/bthread_countdown_event_unittest.cpp @@ -0,0 +1,69 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2016/06/03 13:25:44 + +#include +#include +#include "butil/atomicops.h" +#include "butil/time.h" + +namespace { +struct Arg { + bthread::CountdownEvent event; + butil::atomic num_sig; +}; + +void *signaler(void *arg) { + Arg* a = (Arg*)arg; + a->num_sig.fetch_sub(1, butil::memory_order_relaxed); + a->event.signal(); + return NULL; +} + +TEST(CountdonwEventTest, sanity) { + std::vector tids; + for (int n = 1; n < 10; ++n) { + Arg a; + a.num_sig = n; + a.event.reset(n); + for (int i = 0; i < n; ++i) { + bthread_t tid; + ASSERT_EQ(0, bthread_start_urgent(&tid, NULL, signaler, &a)); + tids.push_back(tid); + } + a.event.wait(); + ASSERT_EQ(0, a.num_sig.load(butil::memory_order_relaxed)); + } + for (size_t i = 0; i < tids.size(); ++i) { + bthread_join(tids[i], NULL); + } +} + +TEST(CountdonwEventTest, timed_wait) { + bthread::CountdownEvent event; + int rc = event.timed_wait(butil::milliseconds_from_now(100)); + ASSERT_EQ(rc, ETIMEDOUT); + event.signal(); + rc = event.timed_wait(butil::milliseconds_from_now(100)); + ASSERT_EQ(rc, 0); + bthread::CountdownEvent event1; + event1.signal(); + rc = event.timed_wait(butil::milliseconds_from_now(1)); + ASSERT_EQ(rc, 0); +} +} // namespace diff --git a/test/bthread_dispatcher_unittest.cpp b/test/bthread_dispatcher_unittest.cpp new file mode 100644 index 0000000..411c392 --- /dev/null +++ b/test/bthread_dispatcher_unittest.cpp @@ -0,0 +1,335 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // writev +#include "butil/compat.h" +#include +#include +#include +#include "butil/time.h" +#include "butil/macros.h" +#include "butil/scoped_lock.h" +#include "butil/fd_utility.h" +#include "butil/logging.h" +#include "gperftools_helper.h" +#include "bthread/bthread.h" +#include "bthread/task_control.h" +#include "bthread/task_group.h" +#if defined(OS_MACOSX) +#include // struct kevent +#include // kevent(), kqueue() +#endif + +#define RUN_EPOLL_IN_BTHREAD + +namespace bthread { +extern TaskControl* global_task_control; +int stop_and_join_epoll_threads(); +} + +namespace { +volatile bool client_stop = false; +volatile bool server_stop = false; + +struct BAIDU_CACHELINE_ALIGNMENT ClientMeta { + int fd; + size_t times; + size_t bytes; +}; + +struct BAIDU_CACHELINE_ALIGNMENT SocketMeta { + int fd; + int epfd; + butil::atomic req; + char* buf; + size_t buf_cap; + size_t bytes; + size_t times; +}; + +struct EpollMeta { + int epfd; + int nthread; + int nfold; +}; + +void* process_thread(void* arg) { + SocketMeta* m = (SocketMeta*)arg; + do { + // Read all data. + do { + ssize_t n = read(m->fd, m->buf, m->buf_cap); + if (n > 0) { + m->bytes += n; + ++m->times; + if ((size_t)n < m->buf_cap) { + break; + } + } else if (n < 0) { + if (errno == EAGAIN) { + break; + } else if (errno == EINTR) { + continue; + } else { + PLOG(FATAL) << "Fail to read fd=" << m->fd; + return NULL; + } + } else { + LOG(FATAL) << "Another end closed fd=" << m->fd; + return NULL; + } + } while (1); + + if (m->req.exchange(0, butil::memory_order_release) == 1) { + // no events during reading. + break; + } + if (m->req.fetch_add(1, butil::memory_order_relaxed) != 0) { + // someone else takes the fd. + break; + } + } while (1); + return NULL; +} + +void* epoll_thread(void* arg) { + EpollMeta* em = (EpollMeta*)arg; + em->nthread = 0; + em->nfold = 0; +#if defined(OS_LINUX) + epoll_event e[32]; +#elif defined(OS_MACOSX) + struct kevent e[32]; +#endif + + while (!server_stop) { +#if defined(OS_LINUX) + // Use a finite timeout so the loop can observe server_stop without + // relying on an external fd to wake up epoll_wait. + const int n = epoll_wait(em->epfd, e, ARRAY_SIZE(e), 100); + if (n == 0) { + continue; + } +#elif defined(OS_MACOSX) + timespec ts = { 0, 100L * 1000L * 1000L }; + const int n = kevent(em->epfd, NULL, 0, e, ARRAY_SIZE(e), &ts); + if (n == 0) { + continue; + } +#endif + if (server_stop) { + break; + } + if (n < 0) { + if (EINTR == errno) { + continue; + } +#if defined(OS_LINUX) + PLOG(FATAL) << "Fail to epoll_wait"; +#elif defined(OS_MACOSX) + PLOG(FATAL) << "Fail to kevent"; +#endif + break; + } + + for (int i = 0; i < n; ++i) { +#if defined(OS_LINUX) + SocketMeta* m = (SocketMeta*)e[i].data.ptr; +#elif defined(OS_MACOSX) + SocketMeta* m = (SocketMeta*)e[i].udata; +#endif + if (m->req.fetch_add(1, butil::memory_order_acquire) == 0) { + bthread_t th; + bthread_start_urgent( + &th, &BTHREAD_ATTR_SMALL, process_thread, m); + ++em->nthread; + } else { + ++em->nfold; + } + } + } + return NULL; +} + +void* client_thread(void* arg) { + ClientMeta* m = (ClientMeta*)arg; + size_t offset = 0; + m->times = 0; + m->bytes = 0; + const size_t buf_cap = 32768; + char* buf = (char*)malloc(buf_cap); + for (size_t i = 0; i < buf_cap/8; ++i) { + ((uint64_t*)buf)[i] = i; + } + while (!client_stop) { + ssize_t n; + if (offset == 0) { + n = write(m->fd, buf, buf_cap); + } else { + iovec v[2]; + v[0].iov_base = buf + offset; + v[0].iov_len = buf_cap - offset; + v[1].iov_base = buf; + v[1].iov_len = offset; + n = writev(m->fd, v, 2); + } + if (n < 0) { + if (errno != EINTR) { + PLOG(FATAL) << "Fail to write fd=" << m->fd; + return NULL; + } + } else { + ++m->times; + m->bytes += n; + offset += n; + if (offset >= buf_cap) { + offset -= buf_cap; + } + } + } + free(buf); + return NULL; +} + +inline uint32_t fmix32 ( uint32_t h ) { + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + return h; +} + +TEST(DispatcherTest, dispatch_tasks) { + client_stop = false; + server_stop = false; + + const size_t NEPOLL = 1; + const size_t NCLIENT = 16; + + int epfd[NEPOLL]; + bthread_t eth[NEPOLL]; + EpollMeta* em[NEPOLL]; + int fds[2 * NCLIENT]; + pthread_t cth[NCLIENT]; + ClientMeta* cm[NCLIENT]; + SocketMeta* sm[NCLIENT]; + + for (size_t i = 0; i < NEPOLL; ++i) { +#if defined(OS_LINUX) + epfd[i] = epoll_create(1024); +#elif defined(OS_MACOSX) + epfd[i] = kqueue(); +#endif + ASSERT_GT(epfd[i], 0); + } + + for (size_t i = 0; i < NCLIENT; ++i) { + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds + 2 * i)); + SocketMeta* m = new SocketMeta; + m->fd = fds[i * 2]; + m->epfd = epfd[fmix32(i) % NEPOLL]; + m->req = 0; + m->buf_cap = 32768; + m->buf = (char*)malloc(m->buf_cap); + m->bytes = 0; + m->times = 0; + ASSERT_EQ(0, butil::make_non_blocking(m->fd)); + sm[i] = m; + +#if defined(OS_LINUX) + epoll_event evt = { (uint32_t)(EPOLLIN | EPOLLET), { m } }; + ASSERT_EQ(0, epoll_ctl(m->epfd, EPOLL_CTL_ADD, m->fd, &evt)); +#elif defined(OS_MACOSX) + struct kevent kqueue_event; + EV_SET(&kqueue_event, m->fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, m); + ASSERT_EQ(0, kevent(m->epfd, &kqueue_event, 1, NULL, 0, NULL)); +#endif + + cm[i] = new ClientMeta; + cm[i]->fd = fds[i * 2 + 1]; + cm[i]->times = 0; + cm[i]->bytes = 0; + ASSERT_EQ(0, pthread_create(&cth[i], NULL, client_thread, cm[i])); + } + + ProfilerStart("dispatcher.prof"); + butil::Timer tm; + tm.start(); + + for (size_t i = 0; i < NEPOLL; ++i) { + EpollMeta *m = new EpollMeta; + em[i] = m; + m->epfd = epfd[i]; +#ifdef RUN_EPOLL_IN_BTHREAD + ASSERT_EQ(0, bthread_start_background(ð[i], NULL, epoll_thread, m)); +#else + ASSERT_EQ(0, pthread_create(ð[i], NULL, epoll_thread, m)); +#endif + } + + sleep(5); + + tm.stop(); + ProfilerStop(); + size_t client_bytes = 0; + size_t server_bytes = 0; + for (size_t i = 0; i < NCLIENT; ++i) { + client_bytes += cm[i]->bytes; + server_bytes += sm[i]->bytes; + } + size_t all_nthread = 0, all_nfold = 0; + for (size_t i = 0; i < NEPOLL; ++i) { + all_nthread += em[i]->nthread; + all_nfold += em[i]->nfold; + } + + LOG(INFO) << "client_tp=" << client_bytes / (double)tm.u_elapsed() + << "MB/s server_tp=" << server_bytes / (double)tm.u_elapsed() + << "MB/s nthread=" << all_nthread << " nfold=" << all_nfold; + + client_stop = true; + for (size_t i = 0; i < NCLIENT; ++i) { + pthread_join(cth[i], NULL); + } + server_stop = true; + // epoll_thread polls server_stop with a finite timeout, so it exits on its + // own without needing an external fd to wake up epoll_wait. + for (size_t i = 0; i < NEPOLL; ++i) { +#ifdef RUN_EPOLL_IN_BTHREAD + bthread_join(eth[i], NULL); +#else + pthread_join(eth[i], NULL); +#endif + } + bthread::stop_and_join_epoll_threads(); + bthread_usleep(100000); + + for (size_t i = 0; i < NCLIENT; ++i) { + free(sm[i]->buf); + delete sm[i]; + delete cm[i]; + } + for (size_t i = 0; i < NEPOLL; ++i) { + delete em[i]; + close(epfd[i]); + } + for (size_t i = 0; i < 2 * NCLIENT; ++i) { + close(fds[i]); + } +} +} // namespace diff --git a/test/bthread_execution_queue_unittest.cpp b/test/bthread_execution_queue_unittest.cpp new file mode 100644 index 0000000..1baaa77 --- /dev/null +++ b/test/bthread_execution_queue_unittest.cpp @@ -0,0 +1,920 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 + +#include +#include +#include +#include "butil/time.h" +#include "butil/fast_rand.h" +#include "gperftools_helper.h" + +namespace { +bool stopped = false; + +class ExecutionQueueTest : public testing::Test { +protected: + void SetUp() { stopped = false; } + void TearDown() {} +}; + +struct LongIntTask { + long value; + bthread::CountdownEvent* event; + LongIntTask(long v) + : value(v), event(NULL) + {} + LongIntTask(long v, bthread::CountdownEvent* e) + : value(v), event(e) + {} + LongIntTask() : value(0), event(NULL) {} +}; + +int add(void* meta, bthread::TaskIterator &iter) { + stopped = iter.is_queue_stopped(); + int64_t* result = (int64_t*)meta; + for (; iter; ++iter) { + *result += iter->value; + if (iter->event) { iter->event->signal(); } + } + return 0; +} + +void test_single_thread(bool use_pthread) { + int64_t result = 0; + int64_t expected_result = 0; + stopped = false; + bthread::ExecutionQueueId queue_id; + bthread::ExecutionQueueOptions options; + options.use_pthread = use_pthread; + if (options.use_pthread) { + LOG(INFO) << "================ pthread ================"; + } else { + LOG(INFO) << "================ bthread ================"; + } + ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options, + add, &result)); + for (int i = 0; i < 100; ++i) { + expected_result += i; + ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, i)); + } + LOG(INFO) << "stop"; + ASSERT_EQ(0, bthread::execution_queue_stop(queue_id)); + ASSERT_NE(0, bthread::execution_queue_execute(queue_id, 0)); + ASSERT_EQ(0, bthread::execution_queue_join(queue_id)); + ASSERT_EQ(expected_result, result); + ASSERT_TRUE(stopped); +} + +TEST_F(ExecutionQueueTest, single_thread) { + for (int i = 0; i < 2; ++i) { + test_single_thread(i); + } +} + +class RValue { +public: + RValue() : _value(0) {} + explicit RValue(int v) : _value(v) {} + RValue(RValue&& rhs) noexcept : _value(rhs._value) {} + RValue& operator=(RValue&& rhs) noexcept { + if (this != &rhs) { + _value = rhs._value; + } + return *this; + } + + DISALLOW_COPY_AND_ASSIGN(RValue); + + int value() const { return _value; } + + +private: + int _value; +}; + +int add(void* meta, bthread::TaskIterator &iter) { + stopped = iter.is_queue_stopped(); + int* result = (int*)meta; + for (; iter; ++iter) { + *result += iter->value(); + } + return 0; +} + +void test_rvalue(bool use_pthread) { + int64_t result = 0; + int64_t expected_result = 0; + stopped = false; + bthread::ExecutionQueueId queue_id; + bthread::ExecutionQueueOptions options; + options.use_pthread = use_pthread; + if (options.use_pthread) { + LOG(INFO) << "================ pthread ================"; + } else { + LOG(INFO) << "================ bthread ================"; + } + ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options, + add, &result)); + for (int i = 0; i < 100; ++i) { + expected_result += i; + RValue v(i); + ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, std::move(v))); + } + LOG(INFO) << "stop"; + ASSERT_EQ(0, bthread::execution_queue_stop(queue_id)); + ASSERT_NE(0, bthread::execution_queue_execute(queue_id, RValue(0))); + ASSERT_EQ(0, bthread::execution_queue_join(queue_id)); + ASSERT_EQ(expected_result, result); + ASSERT_TRUE(stopped); +} + +TEST_F(ExecutionQueueTest, rvalue) { + for (int i = 0; i < 2; ++i) { + test_rvalue(i); + } +} + +struct PushArg { + bthread::ExecutionQueueId id {0}; + butil::atomic total_num {0}; + butil::atomic total_time {0}; + butil::atomic expected_value {0}; + volatile bool stopped {false}; + bool wait_task_completed {false}; +}; + +void* push_thread(void *arg) { + PushArg* pa = (PushArg*)arg; + int64_t sum = 0; + butil::Timer timer; + timer.start(); + int num = 0; + bthread::CountdownEvent e; + LongIntTask t(num, pa->wait_task_completed ? &e : NULL); + if (pa->wait_task_completed) { + e.reset(1); + } + while (bthread::execution_queue_execute(pa->id, t) == 0) { + sum += num; + t.value = ++num; + if (pa->wait_task_completed) { + e.wait(); + e.reset(1); + } + } + timer.stop(); + pa->expected_value.fetch_add(sum, butil::memory_order_relaxed); + pa->total_num.fetch_add(num); + pa->total_time.fetch_add(timer.n_elapsed()); + return NULL; +} + +void* push_thread_which_addresses_execq(void *arg) { + PushArg* pa = (PushArg*)arg; + int64_t sum = 0; + butil::Timer timer; + timer.start(); + int num = 0; + bthread::ExecutionQueue::scoped_ptr_t ptr + = bthread::execution_queue_address(pa->id); + EXPECT_TRUE(ptr); + while (ptr->execute(num) == 0) { + sum += num; + ++num; + } + EXPECT_TRUE(ptr->stopped()); + timer.stop(); + pa->expected_value.fetch_add(sum, butil::memory_order_relaxed); + pa->total_num.fetch_add(num); + pa->total_time.fetch_add(timer.n_elapsed()); + return NULL; +} + +void test_performance(bool use_pthread) { + pthread_t threads[8]; + bthread::ExecutionQueueId queue_id = { 0 }; // to suppress warnings + bthread::ExecutionQueueOptions options; + options.use_pthread = use_pthread; + if (options.use_pthread) { + LOG(INFO) << "================ pthread ================"; + } else { + LOG(INFO) << "================ bthread ================"; + } + int64_t result = 0; + ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options, + add, &result)); + PushArg pa; + pa.id = queue_id; + pa.total_num = 0; + pa.total_time = 0; + pa.expected_value = 0; + pa.stopped = false; + ProfilerStart("execq.prof"); + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + pthread_create(&threads[i], NULL, &push_thread_which_addresses_execq, &pa); + } + usleep(500 * 1000); + ASSERT_EQ(0, bthread::execution_queue_stop(queue_id)); + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + pthread_join(threads[i], NULL); + } + ProfilerStop(); + ASSERT_EQ(0, bthread::execution_queue_join(queue_id)); + ASSERT_EQ(pa.expected_value.load(), result); + LOG(INFO) << "With addressed execq, each execution_queue_execute takes " + << pa.total_time.load() / pa.total_num.load() + << " total_num=" << pa.total_num + << " ns with " << ARRAY_SIZE(threads) << " threads"; +#define BENCHMARK_BOTH +#ifdef BENCHMARK_BOTH + result = 0; + ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options, + add, &result)); + pa.id = queue_id; + pa.total_num = 0; + pa.total_time = 0; + pa.expected_value = 0; + pa.stopped = false; + ProfilerStart("execq_id.prof"); + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + pthread_create(&threads[i], NULL, &push_thread, &pa); + } + usleep(500 * 1000); + ASSERT_EQ(0, bthread::execution_queue_stop(queue_id)); + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + pthread_join(threads[i], NULL); + } + ProfilerStop(); + ASSERT_EQ(0, bthread::execution_queue_join(queue_id)); + ASSERT_EQ(pa.expected_value.load(), result); + LOG(INFO) << "With id explicitly, execution_queue_execute takes " + << pa.total_time.load() / pa.total_num.load() + << " total_num=" << pa.total_num + << " ns with " << ARRAY_SIZE(threads) << " threads"; +#endif // BENCHMARK_BOTH +} + +TEST_F(ExecutionQueueTest, performance) { + for (int i = 0; i < 2; ++i) { + test_performance(i); + } +} + +volatile bool g_suspending = false; +volatile bool g_should_be_urgent = false; +int urgent_times = 0; + +int add_with_suspend(void* meta, bthread::TaskIterator& iter) { + int64_t* result = (int64_t*)meta; + if (iter.is_queue_stopped()) { + stopped = true; + return 0; + } + if (g_should_be_urgent) { + g_should_be_urgent = false; + EXPECT_EQ(-1, iter->value) << urgent_times; + if (iter->event) { iter->event->signal(); } + ++iter; + EXPECT_FALSE(iter) << urgent_times; + ++urgent_times; + } else { + for (; iter; ++iter) { + if (iter->value == -100) { + g_suspending = true; + while (g_suspending) { + bthread_usleep(100); + } + g_should_be_urgent = true; + if (iter->event) { iter->event->signal(); } + EXPECT_FALSE(++iter); + return 0; + } else { + *result += iter->value; + if (iter->event) { iter->event->signal(); } + } + } + } + return 0; +} + +void test_execute_urgent(bool use_pthread) { + g_should_be_urgent = false; + pthread_t threads[10]; + bthread::ExecutionQueueId queue_id = { 0 }; // to suppress warnings + bthread::ExecutionQueueOptions options; + options.use_pthread = use_pthread; + if (options.use_pthread) { + LOG(INFO) << "================ pthread ================"; + } else { + LOG(INFO) << "================ bthread ================"; + } + int64_t result = 0; + ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options, + add_with_suspend, &result)); + PushArg pa; + pa.id = queue_id; + pa.total_num = 0; + pa.total_time = 0; + pa.expected_value = 0; + pa.stopped = false; + pa.wait_task_completed = true; + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + pthread_create(&threads[i], NULL, &push_thread, &pa); + } + g_suspending = false; + usleep(1000); + + for (int i = 0; i < 100; ++i) { + ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, -100)); + while (!g_suspending) { + usleep(100); + } + ASSERT_EQ(0, bthread::execution_queue_execute( + queue_id, -1, &bthread::TASK_OPTIONS_URGENT)); + g_suspending = false; + usleep(100); + } + usleep(500* 1000); + pa.stopped = true; + ASSERT_EQ(0, bthread::execution_queue_stop(queue_id)); + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + pthread_join(threads[i], NULL); + } + LOG(INFO) << "result=" << result; + ASSERT_EQ(0, bthread::execution_queue_join(queue_id)); + ASSERT_EQ(pa.expected_value.load(), result); +} + +TEST_F(ExecutionQueueTest, execute_urgent) { + for (int i = 0; i < 2; ++i) { + test_execute_urgent(i); + } +} + +void test_urgent_task_is_the_last_task(bool use_pthread) { + g_should_be_urgent = false; + g_suspending = false; + bthread::ExecutionQueueId queue_id = { 0 }; // to suppress warnings + bthread::ExecutionQueueOptions options; + options.use_pthread = use_pthread; + if (options.use_pthread) { + LOG(INFO) << "================ pthread ================"; + } else { + LOG(INFO) << "================ bthread ================"; + } + int64_t result = 0; + ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options, + add_with_suspend, &result)); + g_suspending = false; + ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, -100)); + while (!g_suspending) { + usleep(10); + } + LOG(INFO) << "Going to push"; + int64_t expected = 0; + for (int j = 1; j < 100; ++j) { + expected += j; + ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, j)); + } + ASSERT_EQ(0, bthread::execution_queue_execute( + queue_id, -1, &bthread::TASK_OPTIONS_URGENT)); + usleep(100); + g_suspending = false; + butil::atomic_thread_fence(butil::memory_order_acq_rel); + usleep(10 * 1000); + LOG(INFO) << "going to quit"; + ASSERT_EQ(0, bthread::execution_queue_stop(queue_id)); + ASSERT_EQ(0, bthread::execution_queue_join(queue_id)); + ASSERT_EQ(expected, result); +} +TEST_F(ExecutionQueueTest, urgent_task_is_the_last_task) { + for (int i = 0; i < 2; ++i) { + test_urgent_task_is_the_last_task(i); + } +} + +long next_task[1024]; +butil::atomic num_threads(0); + +void* push_thread_with_id(void* arg) { + bthread::ExecutionQueueId id = { (uint64_t)arg }; + int thread_id = num_threads.fetch_add(1, butil::memory_order_relaxed); + LOG(INFO) << "Start thread" << thread_id; + for (int i = 0; i < 100000; ++i) { + bthread::execution_queue_execute(id, ((long)thread_id << 32) | i); + } + return NULL; +} + +int check_order(void* meta, bthread::TaskIterator& iter) { + for (; iter; ++iter) { + long value = iter->value; + int thread_id = value >> 32; + long task = value & 0xFFFFFFFFul; + if (task != next_task[thread_id]++) { + EXPECT_TRUE(false) << "task=" << task << " thread_id=" << thread_id; + ++*(long*)meta; + } + if (iter->event) { iter->event->signal(); } + } + return 0; +} + +void test_multi_threaded_order(bool use_pthread) { + memset(next_task, 0, sizeof(next_task)); + long disorder_times = 0; + bthread::ExecutionQueueId queue_id = { 0 }; // to suppress warnings + bthread::ExecutionQueueOptions options; + options.use_pthread = use_pthread; + if (options.use_pthread) { + LOG(INFO) << "================ pthread ================"; + } else { + LOG(INFO) << "================ bthread ================"; + } + ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options, + check_order, &disorder_times)); + pthread_t threads[12]; + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + pthread_create(&threads[i], NULL, &push_thread_with_id, (void *)queue_id.value); + } + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + pthread_join(threads[i], NULL); + } + ASSERT_EQ(0, bthread::execution_queue_stop(queue_id)); + ASSERT_EQ(0, bthread::execution_queue_join(queue_id)); + ASSERT_EQ(0, disorder_times); +} + +TEST_F(ExecutionQueueTest, multi_threaded_order) { + for (int i = 0; i < 2; ++i) { + test_multi_threaded_order(i); + } +} + +int check_running_thread(void* arg, bthread::TaskIterator& iter) { + if (iter.is_queue_stopped()) { + return 0; + } + for (; iter; ++iter) {} + EXPECT_EQ(pthread_self(), (pthread_t)arg); + return 0; +} + +void test_in_place_task(bool use_pthread) { + pthread_t thread_id = pthread_self(); + bthread::ExecutionQueueId queue_id = { 0 }; // to suppress warnings + bthread::ExecutionQueueOptions options; + options.use_pthread = use_pthread; + if (options.use_pthread) { + LOG(INFO) << "================ pthread ================"; + } else { + LOG(INFO) << "================ bthread ================"; + } + ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options, + check_running_thread, + (void*)thread_id)); + ASSERT_EQ(0, bthread::execution_queue_execute( + queue_id, 0, &bthread::TASK_OPTIONS_INPLACE)); + ASSERT_EQ(0, bthread::execution_queue_stop(queue_id)); + ASSERT_EQ(0, bthread::execution_queue_join(queue_id)); +} + +TEST_F(ExecutionQueueTest, in_place_task) { + for (int i = 0; i < 2; ++i) { + test_in_place_task(i); + } +} + +struct InPlaceTask { + bool first_task; + pthread_t thread_id; +}; + +void *run_first_tasks(void* arg) { + bthread::ExecutionQueueId queue_id = { (uint64_t)arg }; + InPlaceTask task; + task.first_task = true; + task.thread_id = pthread_self(); + EXPECT_EQ(0, bthread::execution_queue_execute( + queue_id, task, &bthread::TASK_OPTIONS_INPLACE)); + return NULL; +} + +int stuck_and_check_running_thread(void* arg, bthread::TaskIterator& iter) { + if (iter.is_queue_stopped()) { + return 0; + } + butil::atomic* futex = (butil::atomic*)arg; + if (iter->first_task) { + EXPECT_EQ(pthread_self(), iter->thread_id); + futex->store(1); + bthread::futex_wake_private(futex, 1); + while (futex->load() != 2) { + bthread::futex_wait_private(futex, 1, NULL); + } + ++iter; + EXPECT_FALSE(iter); + } else { + for (; iter; ++iter) { + EXPECT_FALSE(iter->first_task); + EXPECT_NE(pthread_self(), iter->thread_id); + } + } + return 0; +} + +void test_should_start_new_thread_on_more_tasks(bool use_pthread) { + bthread::ExecutionQueueId queue_id = { 0 }; + bthread::ExecutionQueueOptions options; + options.use_pthread = use_pthread; + if (options.use_pthread) { + LOG(INFO) << "================ pthread ================"; + } else { + LOG(INFO) << "================ bthread ================"; + } + butil::atomic futex(0); + ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options, + stuck_and_check_running_thread, + (void*)&futex)); + pthread_t thread; + ASSERT_EQ(0, pthread_create(&thread, NULL, run_first_tasks, (void*)queue_id.value)); + while (futex.load() != 1) { + bthread::futex_wait_private(&futex, 0, NULL); + } + for (size_t i = 0; i < 100; ++i) { + InPlaceTask task; + task.first_task = false; + task.thread_id = pthread_self(); + ASSERT_EQ(0, bthread::execution_queue_execute( + queue_id, task, &bthread::TASK_OPTIONS_INPLACE)); + } + futex.store(2); + bthread::futex_wake_private(&futex, 1); + ASSERT_EQ(0, bthread::execution_queue_stop(queue_id)); + ASSERT_EQ(0, bthread::execution_queue_join(queue_id)); +} + +TEST_F(ExecutionQueueTest, should_start_new_thread_on_more_tasks) { + for (int i = 0; i < 2; ++i) { + test_should_start_new_thread_on_more_tasks(i); + } +} + +void* inplace_push_thread(void* arg) { + bthread::ExecutionQueueId id = { (uint64_t)arg }; + int thread_id = num_threads.fetch_add(1, butil::memory_order_relaxed); + LOG(INFO) << "Start thread" << thread_id; + for (int i = 0; i < 100000; ++i) { + bthread::execution_queue_execute(id, ((long)thread_id << 32) | i, + &bthread::TASK_OPTIONS_INPLACE); + } + return NULL; +} + +void test_inplace_and_order(bool use_pthread) { + memset(next_task, 0, sizeof(next_task)); + long disorder_times = 0; + bthread::ExecutionQueueId queue_id = { 0 }; // to suppress warnings + bthread::ExecutionQueueOptions options; + options.use_pthread = use_pthread; + if (options.use_pthread) { + LOG(INFO) << "================ pthread ================"; + } else { + LOG(INFO) << "================ bthread ================"; + } + ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options, + check_order, &disorder_times)); + pthread_t threads[12]; + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + pthread_create(&threads[i], NULL, &inplace_push_thread, (void *)queue_id.value); + } + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + pthread_join(threads[i], NULL); + } + ASSERT_EQ(0, bthread::execution_queue_stop(queue_id)); + ASSERT_EQ(0, bthread::execution_queue_join(queue_id)); + ASSERT_EQ(0, disorder_times); +} + +TEST_F(ExecutionQueueTest, inplace_and_order) { + for (int i = 0; i < 2; ++i) { + test_inplace_and_order(i); + } +} + +TEST_F(ExecutionQueueTest, size_of_task_node) { + LOG(INFO) << "sizeof(TaskNode)=" << sizeof(bthread::TaskNode); +} + +int add_with_suspend2(void* meta, bthread::TaskIterator& iter) { + int64_t* result = (int64_t*)meta; + if (iter.is_queue_stopped()) { + stopped = true; + return 0; + } + for (; iter; ++iter) { + if (iter->value == -100) { + g_suspending = true; + while (g_suspending) { + usleep(10); + } + if (iter->event) { iter->event->signal(); } + } else { + *result += iter->value; + if (iter->event) { iter->event->signal(); } + } + } + return 0; +} + +void test_cancel(bool use_pthread) { + bthread::ExecutionQueueId queue_id = { 0 }; // to suppress warnings + bthread::ExecutionQueueOptions options; + options.use_pthread = use_pthread; + if (options.use_pthread) { + LOG(INFO) << "================ pthread ================"; + } else { + LOG(INFO) << "================ bthread ================"; + } + int64_t result = 0; + ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options, + add_with_suspend2, &result)); + g_suspending = false; + bthread::TaskHandle handle0; + ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, -100, NULL, &handle0)); + while (!g_suspending) { + usleep(10); + } + ASSERT_EQ(1, bthread::execution_queue_cancel(handle0)); + ASSERT_EQ(1, bthread::execution_queue_cancel(handle0)); + bthread::TaskHandle handle1; + ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, 100, NULL, &handle1)); + ASSERT_EQ(0, bthread::execution_queue_cancel(handle1)); + g_suspending = false; + ASSERT_EQ(-1, bthread::execution_queue_cancel(handle1)); + ASSERT_EQ(0, bthread::execution_queue_stop(queue_id)); + ASSERT_EQ(0, bthread::execution_queue_join(queue_id)); + ASSERT_EQ(0, result); +} + +TEST_F(ExecutionQueueTest, cancel) { + for (int i = 0; i < 2; ++i) { + test_cancel(i); + } +} + +struct CancelSelf { + butil::atomic handle; +}; + +int cancel_self(void* /*meta*/, bthread::TaskIterator& iter) { + + for (; iter; ++iter) { + while ((*iter)->handle == NULL) { + usleep(10); + } + EXPECT_EQ(1, bthread::execution_queue_cancel(*(*iter)->handle.load())); + EXPECT_EQ(1, bthread::execution_queue_cancel(*(*iter)->handle.load())); + EXPECT_EQ(1, bthread::execution_queue_cancel(*(*iter)->handle.load())); + } + return 0; +} + +void test_cancel_self(bool use_pthread) { + bthread::ExecutionQueueId queue_id = { 0 }; // to suppress warnings + bthread::ExecutionQueueOptions options; + options.use_pthread = use_pthread; + if (options.use_pthread) { + LOG(INFO) << "================ pthread ================"; + } else { + LOG(INFO) << "================ bthread ================"; + } + ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options, + cancel_self, NULL)); + CancelSelf task; + task.handle = NULL; + bthread::TaskHandle handle; + ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, &task, NULL, &handle)); + task.handle.store(&handle); + ASSERT_EQ(0, bthread::execution_queue_stop(queue_id)); + ASSERT_EQ(0, bthread::execution_queue_join(queue_id)); +} + +TEST_F(ExecutionQueueTest, cancel_self) { + for (int i = 0; i < 2; ++i) { + test_cancel_self(i); + } +} + +struct AddTask { + int value; + bool cancel_task; + int cancel_value; + bthread::TaskHandle handle; +}; + +struct AddMeta { + int64_t sum; + butil::atomic expected; + butil::atomic succ_times; + butil::atomic race_times; + butil::atomic fail_times; +}; + +int add_with_cancel(void* meta, bthread::TaskIterator& iter) { + if (iter.is_queue_stopped()) { + return 0; + } + AddMeta* m = (AddMeta*)meta; + for (; iter; ++iter) { + if (iter->cancel_task) { + const int rc = bthread::execution_queue_cancel(iter->handle); + if (rc == 0) { + m->expected.fetch_sub(iter->cancel_value); + m->succ_times.fetch_add(1); + } else if (rc < 0) { + m->fail_times.fetch_add(1); + } else { + m->race_times.fetch_add(1); + } + } else { + m->sum += iter->value; + } + } + return 0; +} + +void test_random_cancel(bool use_pthread) { + bthread::ExecutionQueueId queue_id = { 0 }; + AddMeta m; + m.sum = 0; + m.expected.store(0); + m.succ_times.store(0); + m.fail_times.store(0); + m.race_times.store(0); + ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, NULL, + add_with_cancel, &m)); + int64_t expected = 0; + for (int i = 0; i < 100000; ++i) { + bthread::TaskHandle h; + AddTask t; + t.value = i; + t.cancel_task = false; + ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, t, NULL, &h)); + const int r = butil::fast_rand_less_than(4); + expected += i; + if (r == 0) { + if (bthread::execution_queue_cancel(h) == 0) { + expected -= i; + } + } else if (r == 1) { + t.cancel_task = true; + t.cancel_value = i; + t.handle = h; + ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, t, NULL)); + } else if (r == 2) { + t.cancel_task = true; + t.cancel_value = i; + t.handle = h; + ASSERT_EQ(0, bthread::execution_queue_execute( + queue_id, t, &bthread::TASK_OPTIONS_URGENT)); + } else { + // do nothing; + } + } + m.expected.fetch_add(expected); + ASSERT_EQ(0, bthread::execution_queue_stop(queue_id)); + ASSERT_EQ(0, bthread::execution_queue_join(queue_id)); + ASSERT_EQ(m.sum, m.expected.load()); + LOG(INFO) << "sum=" << m.sum << " race_times=" << m.race_times + << " succ_times=" << m.succ_times + << " fail_times=" << m.fail_times; +} + +TEST_F(ExecutionQueueTest, random_cancel) { + for (int i = 0; i < 2; ++i) { + test_random_cancel(i); + } + +} + +int add2(void* meta, bthread::TaskIterator &iter) { + if (iter) { + int64_t* result = (int64_t*)meta; + *result += iter->value; + if (iter->event) { iter->event->signal(); } + } + return 0; +} + +void test_not_do_iterate_at_all(bool use_pthread) { + int64_t result = 0; + int64_t expected_result = 0; + bthread::ExecutionQueueId queue_id; + bthread::ExecutionQueueOptions options; + options.use_pthread = use_pthread; + if (options.use_pthread) { + LOG(INFO) << "================ pthread ================"; + } else { + LOG(INFO) << "================ bthread ================"; + } + ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options, + add2, &result)); + for (int i = 0; i < 100; ++i) { + expected_result += i; + ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, i)); + } + ASSERT_EQ(0, bthread::execution_queue_stop(queue_id)); + ASSERT_NE(0, bthread::execution_queue_execute(queue_id, 0)); + ASSERT_EQ(0, bthread::execution_queue_join(queue_id)); + ASSERT_EQ(expected_result, result); +} + +TEST_F(ExecutionQueueTest, not_do_iterate_at_all) { + for (int i = 0; i < 2; ++i) { + test_not_do_iterate_at_all(i); + } +} + +int add_with_suspend3(void* meta, bthread::TaskIterator& iter) { + int64_t* result = (int64_t*)meta; + if (iter.is_queue_stopped()) { + stopped = true; + return 0; + } + for (; iter; ++iter) { + if (iter->value == -100) { + g_suspending = true; + while (g_suspending) { + usleep(10); + } + if (iter->event) { iter->event->signal(); } + } else { + *result += iter->value; + if (iter->event) { iter->event->signal(); } + } + } + return 0; +} + +void test_cancel_unexecuted_high_priority_task(bool use_pthread) { + g_should_be_urgent = false; + bthread::ExecutionQueueId queue_id = { 0 }; // to suppress warnings + bthread::ExecutionQueueOptions options; + options.use_pthread = use_pthread; + if (options.use_pthread) { + LOG(INFO) << "================ pthread ================"; + } else { + LOG(INFO) << "================ bthread ================"; + } + int64_t result = 0; + ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options, + add_with_suspend3, &result)); + // Push a normal task to make the executor suspend + ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, -100)); + while (!g_suspending) { + usleep(10); + } + // At this point, executor is suspended by the first task. Then we put + // a high_priority task which is going to be cancelled immediately, + // expecting that both operations are successful. + bthread::TaskHandle h; + ASSERT_EQ(0, bthread::execution_queue_execute( + queue_id, -100, &bthread::TASK_OPTIONS_URGENT, &h)); + ASSERT_EQ(0, bthread::execution_queue_cancel(h)); + + // Resume executor + g_suspending = false; + + // Push a normal task + ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, 12345)); + + // The execq should stop normally + ASSERT_EQ(0, bthread::execution_queue_stop(queue_id)); + ASSERT_EQ(0, bthread::execution_queue_join(queue_id)); + + ASSERT_EQ(12345, result); +} + +TEST_F(ExecutionQueueTest, cancel_unexecuted_high_priority_task) { + for (int i = 0; i < 2; ++i) { + test_cancel_unexecuted_high_priority_task(i); + } +} +} // namespace diff --git a/test/bthread_fd_unittest.cpp b/test/bthread_fd_unittest.cpp new file mode 100644 index 0000000..1f68ffc --- /dev/null +++ b/test/bthread_fd_unittest.cpp @@ -0,0 +1,656 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "butil/compat.h" +#include "butil/compiler_specific.h" +#include +#include +#include // uname +#include +#include +#include +#include +#include "gperftools_helper.h" +#include "butil/time.h" +#include "butil/macros.h" +#include "butil/fd_utility.h" +#include +#include +#include "butil/logging.h" +#include "bthread/task_control.h" +#include "bthread/task_group.h" +#include "bthread/interrupt_pthread.h" +#include "bthread/bthread.h" +#include "bthread/unstable.h" +#include +#if defined(OS_MACOSX) +#include // struct kevent +#include // kevent(), kqueue() +#include +#endif + +#ifndef NDEBUG +namespace bthread { +extern butil::atomic break_nums; +extern TaskControl* global_task_control; +int stop_and_join_epoll_threads(); +} +#endif + +namespace { +TEST(FDTest, read_kernel_version) { + utsname name; + uname(&name); + std::cout << "sysname=" << name.sysname << std::endl + << "nodename=" << name.nodename << std::endl + << "release=" << name.release << std::endl + << "version=" << name.version << std::endl + << "machine=" << name.machine << std::endl; +} + +#define RUN_CLIENT_IN_BTHREAD 1 +//#define USE_BLOCKING_EPOLL 1 +//#define RUN_EPOLL_IN_BTHREAD 1 +//#define CREATE_THREAD_TO_PROCESS 1 + +volatile bool stop = false; + +struct SocketMeta { + int fd; + int epfd; +}; + +struct BAIDU_CACHELINE_ALIGNMENT ClientMeta { + int fd; + size_t count; + size_t times; +}; + +struct EpollMeta { + int epfd; +}; + +const size_t NCLIENT = 30; +void* process_thread(void* arg) { + SocketMeta* m = (SocketMeta*)arg; + size_t count; + //printf("begin to process fd=%d\n", m->fd); + ssize_t n = read(m->fd, &count, sizeof(count)); + if (n != sizeof(count)) { + LOG(FATAL) << "Should not happen in this test"; + return NULL; + } + count += NCLIENT; + //printf("write result=%lu to fd=%d\n", count, m->fd); + if (write(m->fd, &count, sizeof(count)) != sizeof(count)) { + LOG(FATAL) << "Should not happen in this test"; + return NULL; + } +#ifdef CREATE_THREAD_TO_PROCESS +# if defined(OS_LINUX) + epoll_event evt = { EPOLLIN | EPOLLONESHOT, { m } }; + if (epoll_ctl(m->epfd, EPOLL_CTL_MOD, m->fd, &evt) < 0) { + epoll_ctl(m->epfd, EPOLL_CTL_ADD, m->fd, &evt); + } +# elif defined(OS_MACOSX) + struct kevent kqueue_event; + EV_SET(&kqueue_event, m->fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_ONESHOT, + 0, 0, m); + kevent(m->epfd, &kqueue_event, 1, NULL, 0, NULL); +# endif +#endif + return NULL; +} + +void* epoll_thread(void* arg) { + bthread_usleep(1); + EpollMeta* m = (EpollMeta*)arg; + const int epfd = m->epfd; +#if defined(OS_LINUX) + epoll_event e[32]; +#elif defined(OS_MACOSX) + struct kevent e[32]; +#endif + + while (!stop) { + +#if defined(OS_LINUX) +# ifndef USE_BLOCKING_EPOLL + const int n = epoll_wait(epfd, e, ARRAY_SIZE(e), 0); + if (stop) { + break; + } + if (n == 0) { + bthread_fd_wait(epfd, EPOLLIN); + continue; + } +# else + const int n = epoll_wait(epfd, e, ARRAY_SIZE(e), -1); + if (stop) { + break; + } + if (n == 0) { + continue; + } +# endif +#elif defined(OS_MACOSX) + const int n = kevent(epfd, NULL, 0, e, ARRAY_SIZE(e), NULL); + if (stop) { + break; + } + if (n == 0) { + continue; + } +#endif + if (n < 0) { + if (EINTR == errno) { + continue; + } +#if defined(OS_LINUX) + PLOG(FATAL) << "Fail to epoll_wait"; +#elif defined(OS_MACOSX) + PLOG(FATAL) << "Fail to kevent"; +#endif + break; + } + +#ifdef CREATE_THREAD_TO_PROCESS + bthread_fvec vec[n]; + for (int i = 0; i < n; ++i) { + vec[i].fn = process_thread; +# if defined(OS_LINUX) + vec[i].arg = e[i].data.ptr; +# elif defined(OS_MACOSX) + vec[i].arg = e[i].udata; +# endif + } + bthread_t tid[n]; + bthread_startv(tid, vec, n, &BTHREAD_ATTR_SMALL); +#else + for (int i = 0; i < n; ++i) { +# if defined(OS_LINUX) + process_thread(e[i].data.ptr); +# elif defined(OS_MACOSX) + process_thread(e[i].udata); +# endif + } +#endif + } + return NULL; +} + +void* client_thread(void* arg) { + ClientMeta* m = (ClientMeta*)arg; + for (size_t i = 0; i < m->times; ++i) { + if (write(m->fd, &m->count, sizeof(m->count)) != sizeof(m->count)) { + LOG(FATAL) << "Should not happen in this test"; + return NULL; + } +#ifdef RUN_CLIENT_IN_BTHREAD + ssize_t rc; + do { +# if defined(OS_LINUX) + const int wait_rc = bthread_fd_wait(m->fd, EPOLLIN); +# elif defined(OS_MACOSX) + const int wait_rc = bthread_fd_wait(m->fd, EVFILT_READ); +# endif + EXPECT_EQ(0, wait_rc) << berror(); + rc = read(m->fd, &m->count, sizeof(m->count)); + } while (rc < 0 && errno == EAGAIN); +#else + ssize_t rc = read(m->fd, &m->count, sizeof(m->count)); +#endif + if (rc != sizeof(m->count)) { + PLOG(FATAL) << "Should not happen in this test, rc=" << rc; + return NULL; + } + } + return NULL; +} + +inline uint32_t fmix32 ( uint32_t h ) { + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + return h; +} + +// Disable temporarily due to epoll's bug. The bug is fixed by +// a kernel patch that lots of machines currently don't have +TEST(FDTest, ping_pong) { +#ifndef NDEBUG + bthread::break_nums = 0; +#endif + + const size_t REP = 30000; + const size_t NEPOLL = 2; + + int epfd[NEPOLL]; +#ifdef RUN_EPOLL_IN_BTHREAD + bthread_t eth[NEPOLL]; +#else + pthread_t eth[NEPOLL]; +#endif + int fds[2 * NCLIENT]; +#ifdef RUN_CLIENT_IN_BTHREAD + bthread_t cth[NCLIENT]; +#else + pthread_t cth[NCLIENT]; +#endif + std::unique_ptr cm[NCLIENT]; + std::unique_ptr sm[NCLIENT]; + std::unique_ptr em_arr[NEPOLL]; + + for (size_t i = 0; i < NEPOLL; ++i) { +#if defined(OS_LINUX) + epfd[i] = epoll_create(1024); +#elif defined(OS_MACOSX) + epfd[i] = kqueue(); +#endif + ASSERT_GT(epfd[i], 0); + } + + for (size_t i = 0; i < NCLIENT; ++i) { + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds + 2 * i)); + //printf("Created fd=%d,%d i=%lu\n", fds[2*i], fds[2*i+1], i); + sm[i].reset(new SocketMeta); + SocketMeta* m = sm[i].get(); + m->fd = fds[i * 2]; + m->epfd = epfd[fmix32(i) % NEPOLL]; + ASSERT_EQ(0, fcntl(m->fd, F_SETFL, fcntl(m->fd, F_GETFL, 0) | O_NONBLOCK)); + +#ifdef CREATE_THREAD_TO_PROCESS +# if defined(OS_LINUX) + epoll_event evt = { EPOLLIN | EPOLLONESHOT, { m } }; +# elif defined(OS_MACOSX) + struct kevent kqueue_event; + EV_SET(&kqueue_event, m->fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_ONESHOT, + 0, 0, m); +# endif +#else +# if defined(OS_LINUX) + epoll_event evt = { EPOLLIN, { m } }; +# elif defined(OS_MACOSX) + struct kevent kqueue_event; + EV_SET(&kqueue_event, m->fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, m); +# endif +#endif + +#if defined(OS_LINUX) + ASSERT_EQ(0, epoll_ctl(m->epfd, EPOLL_CTL_ADD, m->fd, &evt)); +#elif defined(OS_MACOSX) + ASSERT_EQ(0, kevent(m->epfd, &kqueue_event, 1, NULL, 0, NULL)); +#endif + cm[i].reset(new ClientMeta); + cm[i]->fd = fds[i * 2 + 1]; + cm[i]->count = i; + cm[i]->times = REP; +#ifdef RUN_CLIENT_IN_BTHREAD + butil::make_non_blocking(cm[i]->fd); + ASSERT_EQ(0, bthread_start_urgent(&cth[i], NULL, client_thread, cm[i].get())); +#else + ASSERT_EQ(0, pthread_create(&cth[i], NULL, client_thread, cm[i].get())); +#endif + } + + ProfilerStart("ping_pong.prof"); + butil::Timer tm; + tm.start(); + + for (size_t i = 0; i < NEPOLL; ++i) { + em_arr[i].reset(new EpollMeta); + EpollMeta* em = em_arr[i].get(); + em->epfd = epfd[i]; +#ifdef RUN_EPOLL_IN_BTHREAD + ASSERT_EQ(0, bthread_start_urgent(ð[i], epoll_thread, em, NULL); +#else + ASSERT_EQ(0, pthread_create(ð[i], NULL, epoll_thread, em)); +#endif + } + + for (size_t i = 0; i < NCLIENT; ++i) { +#ifdef RUN_CLIENT_IN_BTHREAD + bthread_join(cth[i], NULL); +#else + pthread_join(cth[i], NULL); +#endif + ASSERT_EQ(i + REP * NCLIENT, cm[i]->count); + } + tm.stop(); + ProfilerStop(); + LOG(INFO) << "tid=" << REP*NCLIENT * 1000000L / tm.u_elapsed(); + stop = true; + for (size_t i = 0; i < NEPOLL; ++i) { +#if defined(OS_LINUX) + epoll_event evt = { EPOLLOUT, { NULL } }; + ASSERT_EQ(0, epoll_ctl(epfd[i], EPOLL_CTL_ADD, 0, &evt)); +#elif defined(OS_MACOSX) + struct kevent kqueue_event; + EV_SET(&kqueue_event, 0, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, NULL); + ASSERT_EQ(0, kevent(epfd[i], &kqueue_event, 1, NULL, 0, NULL)); +#endif +#ifdef RUN_EPOLL_IN_BTHREAD + bthread_join(eth[i], NULL); +#else + pthread_join(eth[i], NULL); +#endif + } + //bthread::stop_and_join_epoll_threads(); + bthread_usleep(100000); + +#ifndef NDEBUG + std::cout << "break_nums=" << bthread::break_nums << std::endl; +#endif +} + +TEST(FDTest, mod_closed_fd) { +#if defined(OS_LINUX) + // Conclusion: + // If fd is never added into epoll, MOD returns ENOENT + // If fd is inside epoll and valid, MOD returns 0 + // If fd is closed and not-reused, MOD returns EBADF + // If fd is closed and reused, MOD returns ENOENT again + + const int epfd = epoll_create(1024); + int new_fd[2]; + int fd[2]; + ASSERT_EQ(0, pipe(fd)); + epoll_event e = { EPOLLIN, { NULL } }; + errno = 0; + ASSERT_EQ(-1, epoll_ctl(epfd, EPOLL_CTL_MOD, fd[0], &e)); + ASSERT_EQ(ENOENT, errno); + ASSERT_EQ(0, epoll_ctl(epfd, EPOLL_CTL_ADD, fd[0], &e)); + // mod after add + ASSERT_EQ(0, epoll_ctl(epfd, EPOLL_CTL_MOD, fd[0], &e)); + // mod after mod + ASSERT_EQ(0, epoll_ctl(epfd, EPOLL_CTL_MOD, fd[0], &e)); + ASSERT_EQ(0, close(fd[0])); + ASSERT_EQ(0, close(fd[1])); + + errno = 0; + ASSERT_EQ(-1, epoll_ctl(epfd, EPOLL_CTL_MOD, fd[0], &e)); + ASSERT_EQ(EBADF, errno) << berror(); + + ASSERT_EQ(0, pipe(new_fd)); + ASSERT_EQ(fd[0], new_fd[0]); + ASSERT_EQ(fd[1], new_fd[1]); + + errno = 0; + ASSERT_EQ(-1, epoll_ctl(epfd, EPOLL_CTL_MOD, fd[0], &e)); + ASSERT_EQ(ENOENT, errno) << berror(); + + ASSERT_EQ(0, close(epfd)); +#endif +} + +TEST(FDTest, add_existing_fd) { +#if defined(OS_LINUX) + const int epfd = epoll_create(1024); + epoll_event e = { EPOLLIN, { NULL } }; + ASSERT_EQ(0, epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &e)); + errno = 0; + ASSERT_EQ(-1, epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &e)); + ASSERT_EQ(EEXIST, errno); + ASSERT_EQ(0, close(epfd)); +#endif +} + +void* epoll_waiter(void* arg) { +#if defined(OS_LINUX) + epoll_event e; + if (1 == epoll_wait((int)(intptr_t)arg, &e, 1, -1)) { + std::cout << e.events << std::endl; + } +#elif defined(OS_MACOSX) + struct kevent e; + if (1 == kevent((int)(intptr_t)arg, NULL, 0, &e, 1, NULL)) { + std::cout << e.flags << std::endl; + } +#endif + std::cout << pthread_self() << " quits" << std::endl; + return NULL; +} + +TEST(FDTest, interrupt_pthread) { +#if defined(OS_LINUX) + const int epfd = epoll_create(1024); +#elif defined(OS_MACOSX) + const int epfd = kqueue(); +#endif + pthread_t th, th2; + ASSERT_EQ(0, pthread_create(&th, NULL, epoll_waiter, (void*)(intptr_t)epfd)); + ASSERT_EQ(0, pthread_create(&th2, NULL, epoll_waiter, (void*)(intptr_t)epfd)); + bthread_usleep(100000L); + std::cout << "wake up " << th << std::endl; + bthread::interrupt_pthread(th); + bthread_usleep(100000L); + std::cout << "wake up " << th2 << std::endl; + bthread::interrupt_pthread(th2); + pthread_join(th, NULL); + pthread_join(th2, NULL); +} + +void* close_the_fd(void* arg) { + bthread_usleep(10000/*10ms*/); + EXPECT_EQ(0, bthread_close(*(int*)arg)); + return NULL; +} + +TEST(FDTest, invalid_epoll_events) { + errno = 0; +#if defined(OS_LINUX) + ASSERT_EQ(-1, bthread_fd_wait(-1, EPOLLIN)); +#elif defined(OS_MACOSX) + ASSERT_EQ(-1, bthread_fd_wait(-1, EVFILT_READ)); +#endif + ASSERT_EQ(EINVAL, errno); + errno = 0; +#if defined(OS_LINUX) + ASSERT_EQ(-1, bthread_fd_timedwait(-1, EPOLLIN, NULL)); +#elif defined(OS_MACOSX) + ASSERT_EQ(-1, bthread_fd_timedwait(-1, EVFILT_READ, NULL)); +#endif + ASSERT_EQ(EINVAL, errno); + + int fds[2]; + ASSERT_EQ(0, pipe(fds)); +#if defined(OS_LINUX) + ASSERT_EQ(-1, bthread_fd_wait(fds[0], EPOLLET)); + ASSERT_EQ(EINVAL, errno); +#endif + bthread_t th; + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, close_the_fd, &fds[1])); + butil::Timer tm; + tm.start(); +#if defined(OS_LINUX) + ASSERT_EQ(0, bthread_fd_wait(fds[0], EPOLLIN | EPOLLET)); +#elif defined(OS_MACOSX) + ASSERT_EQ(0, bthread_fd_wait(fds[0], EVFILT_READ)); +#endif + tm.stop(); + ASSERT_LT(tm.m_elapsed(), 20); + ASSERT_EQ(0, bthread_join(th, NULL)); + ASSERT_EQ(0, bthread_close(fds[0])); +} + +void* wait_for_the_fd(void* arg) { + timespec ts = butil::milliseconds_from_now(50); +#if defined(OS_LINUX) + bthread_fd_timedwait(*(int*)arg, EPOLLIN, &ts); +#elif defined(OS_MACOSX) + bthread_fd_timedwait(*(int*)arg, EVFILT_READ, &ts); +#endif + return NULL; +} + +TEST(FDTest, timeout) { + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + pthread_t th; + ASSERT_EQ(0, pthread_create(&th, NULL, wait_for_the_fd, &fds[0])); + bthread_t bth; + ASSERT_EQ(0, bthread_start_urgent(&bth, NULL, wait_for_the_fd, &fds[0])); + butil::Timer tm; + tm.start(); + ASSERT_EQ(0, pthread_join(th, NULL)); + ASSERT_EQ(0, bthread_join(bth, NULL)); + tm.stop(); + ASSERT_LT(tm.m_elapsed(), 80); + ASSERT_EQ(0, bthread_close(fds[0])); + ASSERT_EQ(0, bthread_close(fds[1])); +} + +TEST(FDTest, close_should_wakeup_waiter) { + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + bthread_t bth; + ASSERT_EQ(0, bthread_start_urgent(&bth, NULL, wait_for_the_fd, &fds[0])); + butil::Timer tm; + tm.start(); + ASSERT_EQ(0, bthread_close(fds[0])); + ASSERT_EQ(0, bthread_join(bth, NULL)); + tm.stop(); + ASSERT_LT(tm.m_elapsed(), 5); + + // Launch again, should quit soon due to EBADF +#if defined(OS_LINUX) + ASSERT_EQ(-1, bthread_fd_timedwait(fds[0], EPOLLIN, NULL)); +#elif defined(OS_MACOSX) + ASSERT_EQ(-1, bthread_fd_timedwait(fds[0], EVFILT_READ, NULL)); +#endif + ASSERT_EQ(EBADF, errno); + + ASSERT_EQ(0, bthread_close(fds[1])); +} + +TEST(FDTest, close_definitely_invalid) { + int ec = 0; + ASSERT_EQ(-1, close(-1)); + ec = errno; + ASSERT_EQ(-1, bthread_close(-1)); + ASSERT_EQ(ec, errno); +} + +TEST(FDTest, bthread_close_fd_which_did_not_call_bthread_functions) { + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + ASSERT_EQ(0, bthread_close(fds[0])); + ASSERT_EQ(0, bthread_close(fds[1])); +} + +TEST(FDTest, double_close) { + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + ASSERT_EQ(0, close(fds[0])); + int ec = 0; + ASSERT_EQ(-1, close(fds[0])); + ec = errno; + ASSERT_EQ(0, bthread_close(fds[1])); + ASSERT_EQ(-1, bthread_close(fds[1])); + ASSERT_EQ(ec, errno); +} + +const char* g_hostname1 = "github.com"; +const char* g_hostname2 = "baidu.com"; +TEST(FDTest, bthread_connect) { + butil::EndPoint ep1; + butil::EndPoint ep2; + ASSERT_EQ(0, butil::hostname2endpoint(g_hostname1, 80, &ep1)); + ASSERT_EQ(0, butil::hostname2endpoint(g_hostname2, 80, &ep2)); + + { + struct sockaddr_storage serv_addr{}; + socklen_t serv_addr_size = 0; + ASSERT_EQ(0, endpoint2sockaddr(ep1, &serv_addr, &serv_addr_size)); + butil::fd_guard sockfd(socket(serv_addr.ss_family, SOCK_STREAM, 0)); + ASSERT_LE(0, sockfd); + bool is_blocking = butil::is_blocking(sockfd); + ASSERT_LE(0, sockfd); + ASSERT_EQ(0, bthread_connect(sockfd, (struct sockaddr*) &serv_addr, serv_addr_size)); + ASSERT_EQ(is_blocking, butil::is_blocking(sockfd)); + + } + + { + struct sockaddr_storage serv_addr{}; + socklen_t serv_addr_size = 0; + ASSERT_EQ(0, endpoint2sockaddr(ep2, &serv_addr, &serv_addr_size)); + butil::fd_guard sockfd(socket(serv_addr.ss_family, SOCK_STREAM, 0)); + ASSERT_LE(0, sockfd); + bool is_blocking = butil::is_blocking(sockfd); + // In most cases, 1 millisecond will result in a connection timeout. + timespec abstime = butil::milliseconds_from_now(1); + const int rc = bthread_timed_connect( + sockfd, (struct sockaddr*) &serv_addr, + serv_addr_size, &abstime); + ASSERT_EQ(-1, rc); + ASSERT_EQ(ETIMEDOUT, errno); + ASSERT_EQ(is_blocking, butil::is_blocking(sockfd)); + } +} + +void TestConnectInterruptImpl(bool timed) { + butil::EndPoint ep; + ASSERT_EQ(0, butil::hostname2endpoint(g_hostname1, 80, &ep)); + struct sockaddr_storage serv_addr{}; + socklen_t serv_addr_size = 0; + ASSERT_EQ(0, endpoint2sockaddr(ep, &serv_addr, &serv_addr_size)); + butil::fd_guard sockfd(socket(serv_addr.ss_family, SOCK_STREAM, 0)); + ASSERT_GE(sockfd, 0); + + int rc; + if (timed) { + int64_t start_ms = butil::cpuwide_time_ms(); + butil::tcp_connect(ep, NULL); + int64_t connect_ms = butil::cpuwide_time_ms() - start_ms; + LOG(INFO) << "Connect to " << ep << ", cost " << connect_ms << "ms"; + + timespec abstime = butil::milliseconds_from_now(connect_ms * 10); + rc = bthread_timed_connect( + sockfd, (struct sockaddr*) &serv_addr, + serv_addr_size, &abstime); + } else { + rc = bthread_timed_connect( + sockfd, (struct sockaddr*) &serv_addr, + serv_addr_size, NULL); + } + ASSERT_EQ(0, rc) << "errno=" << errno; + ASSERT_EQ(0, butil::is_connected(sockfd)); + +} + +void* ConnectThread(void* arg) { + bool timed = *(bool*)arg; + TestConnectInterruptImpl(timed); + return NULL; +} + +void TestConnectInterrupt(bool timed) { + bthread_t tid; + ASSERT_EQ(0, bthread_start_background(&tid, NULL, ConnectThread, &timed)); + ASSERT_EQ(0, bthread_stop(tid)); + ASSERT_EQ(0, bthread_join(tid, NULL)); +} + +TEST(FDTest, interrupt) { + TestConnectInterrupt(false); + TestConnectInterrupt(true); +} + +} // namespace diff --git a/test/bthread_futex_unittest.cpp b/test/bthread_futex_unittest.cpp new file mode 100644 index 0000000..0ed5685 --- /dev/null +++ b/test/bthread_futex_unittest.cpp @@ -0,0 +1,210 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include +#include "butil/time.h" +#include "butil/macros.h" +#include "butil/errno.h" +#include // INT_MAX +#include "butil/atomicops.h" +#include "bthread/bthread.h" +#include +#include + +namespace { +volatile bool stop = false; + +butil::atomic nthread(0); + +void* read_thread(void* arg) { + butil::atomic* m = (butil::atomic*)arg; + int njob = 0; + while (!stop) { + int x; + while (!stop && (x = *m) != 0) { + if (x > 0) { + while ((x = m->fetch_sub(1)) > 0) { + ++njob; + const long start = butil::cpuwide_time_ns(); + while (butil::cpuwide_time_ns() < start + 10000) { + } + if (stop) { + return new int(njob); + } + } + m->fetch_add(1); + } else { + cpu_relax(); + } + } + + ++nthread; + bthread::futex_wait_private(m/*lock1*/, 0/*consumed_njob*/, NULL); + --nthread; + } + return new int(njob); +} + +TEST(FutexTest, rdlock_performance) { + const size_t N = 100000; + butil::atomic lock1(0); + pthread_t rth[8]; + for (size_t i = 0; i < ARRAY_SIZE(rth); ++i) { + ASSERT_EQ(0, pthread_create(&rth[i], NULL, read_thread, &lock1)); + } + + const int64_t t1 = butil::cpuwide_time_ns(); + for (size_t i = 0; i < N; ++i) { + if (nthread) { + lock1.fetch_add(1); + bthread::futex_wake_private(&lock1, 1); + } else { + lock1.fetch_add(1); + if (nthread) { + bthread::futex_wake_private(&lock1, 1); + } + } + } + const int64_t t2 = butil::cpuwide_time_ns(); + + bthread_usleep(3000000); + stop = true; + for (int i = 0; i < 10; ++i) { + bthread::futex_wake_private(&lock1, INT_MAX); + sched_yield(); + } + + int njob = 0; + int* res; + for (size_t i = 0; i < ARRAY_SIZE(rth); ++i) { + pthread_join(rth[i], (void**)&res); + njob += *res; + delete res; + } + printf("wake %lu times, %" PRId64 "ns each, lock1=%d njob=%d\n", + N, (t2-t1)/N, lock1.load(), njob); + ASSERT_EQ(N, (size_t)(lock1.load() + njob)); +} + +TEST(FutexTest, futex_wake_before_wait) { + int lock1 = 0; + timespec timeout = { 1, 0 }; + ASSERT_EQ(0, bthread::futex_wake_private(&lock1, INT_MAX)); + ASSERT_EQ(-1, bthread::futex_wait_private(&lock1, 0, &timeout)); + ASSERT_EQ(ETIMEDOUT, errno); +} + +void* dummy_waiter(void* lock) { + bthread::futex_wait_private(lock, 0, NULL); + return NULL; +} + +TEST(FutexTest, futex_wake_many_waiters_perf) { + + int lock1 = 0; + size_t N = 0; + pthread_t th; + for (; N < 1000 && !pthread_create(&th, NULL, dummy_waiter, &lock1); ++N) {} + + sleep(1); + int nwakeup = 0; + butil::Timer tm; + tm.start(); + for (size_t i = 0; i < N; ++i) { + nwakeup += bthread::futex_wake_private(&lock1, 1); + } + tm.stop(); + printf("N=%lu, futex_wake a thread = %" PRId64 "ns\n", N, tm.n_elapsed() / N); + ASSERT_EQ(N, (size_t)nwakeup); + + sleep(2); + const size_t REP = 10000; + nwakeup = 0; + tm.start(); + for (size_t i = 0; i < REP; ++i) { + nwakeup += bthread::futex_wake_private(&lock1, 1); + } + tm.stop(); + ASSERT_EQ(0, nwakeup); + printf("futex_wake nop = %" PRId64 "ns\n", tm.n_elapsed() / REP); +} + +butil::atomic nevent(0); + +void* waker(void* lock) { + bthread_usleep(10000); + const size_t REP = 100000; + int nwakeup = 0; + butil::Timer tm; + tm.start(); + for (size_t i = 0; i < REP; ++i) { + nwakeup += bthread::futex_wake_private(lock, 1); + } + tm.stop(); + EXPECT_EQ(0, nwakeup); + printf("futex_wake nop = %" PRId64 "ns\n", tm.n_elapsed() / REP); + return NULL; +} + +void* batch_waker(void* lock) { + bthread_usleep(10000); + const size_t REP = 100000; + int nwakeup = 0; + butil::Timer tm; + tm.start(); + for (size_t i = 0; i < REP; ++i) { + if (nevent.fetch_add(1, butil::memory_order_relaxed) == 0) { + nwakeup += bthread::futex_wake_private(lock, 1); + int expected = 1; + while (1) { + int last_expected = expected; + if (nevent.compare_exchange_strong(expected, 0, butil::memory_order_relaxed)) { + break; + } + nwakeup += bthread::futex_wake_private(lock, expected - last_expected); + } + } + } + tm.stop(); + EXPECT_EQ(0, nwakeup); + printf("futex_wake nop = %" PRId64 "ns\n", tm.n_elapsed() / REP); + return NULL; +} + +TEST(FutexTest, many_futex_wake_nop_perf) { + pthread_t th[8]; + int lock1; + std::cout << "[Direct wake]" << std::endl; + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, pthread_create(&th[i], NULL, waker, &lock1)); + } + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, pthread_join(th[i], NULL)); + } + std::cout << "[Batch wake]" << std::endl; + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, pthread_create(&th[i], NULL, batch_waker, &lock1)); + } + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, pthread_join(th[i], NULL)); + } +} +} // namespace diff --git a/test/bthread_id_unittest.cpp b/test/bthread_id_unittest.cpp new file mode 100644 index 0000000..6cd668a --- /dev/null +++ b/test/bthread_id_unittest.cpp @@ -0,0 +1,597 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include "butil/time.h" +#include "butil/macros.h" +#include "bthread/bthread.h" +#include "bthread/task_group.h" +#include "bthread/butex.h" + +namespace bthread { +void id_status(bthread_id_t, std::ostream &); +uint32_t id_value(bthread_id_t id); +} + +namespace { +inline uint32_t get_version(bthread_id_t id) { + return (uint32_t)(id.value & 0xFFFFFFFFul); +} + +struct SignalArg { + bthread_id_t id; + long sleep_us_before_fight; + long sleep_us_before_signal; +}; + +void* signaller(void* void_arg) { + SignalArg arg = *(SignalArg*)void_arg; + bthread_usleep(arg.sleep_us_before_fight); + void* data = NULL; + int rc = bthread_id_trylock(arg.id, &data); + if (rc == 0) { + EXPECT_EQ(0xdead, *(int*)data); + ++*(int*)data; + //EXPECT_EQ(EBUSY, bthread_id_destroy(arg.id, ECANCELED)); + bthread_usleep(arg.sleep_us_before_signal); + EXPECT_EQ(0, bthread_id_unlock_and_destroy(arg.id)); + return void_arg; + } else { + EXPECT_TRUE(EBUSY == rc || EINVAL == rc); + return NULL; + } +} + +TEST(BthreadIdTest, join_after_destroy) { + bthread_id_t id1; + int x = 0xdead; + ASSERT_EQ(0, bthread_id_create_ranged(&id1, &x, NULL, 2)); + bthread_id_t id2 = { id1.value + 1 }; + ASSERT_EQ(get_version(id1), bthread::id_value(id1)); + ASSERT_EQ(get_version(id1), bthread::id_value(id2)); + pthread_t th[8]; + SignalArg args[ARRAY_SIZE(th)]; + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + args[i].sleep_us_before_fight = 0; + args[i].sleep_us_before_signal = 0; + args[i].id = (i == 0 ? id1 : id2); + ASSERT_EQ(0, pthread_create(&th[i], NULL, signaller, &args[i])); + } + void* ret[ARRAY_SIZE(th)]; + size_t non_null_ret = 0; + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, pthread_join(th[i], &ret[i])); + non_null_ret += (ret[i] != NULL); + } + ASSERT_EQ(1UL, non_null_ret); + ASSERT_EQ(0, bthread_id_join(id1)); + ASSERT_EQ(0, bthread_id_join(id2)); + ASSERT_EQ(0xdead + 1, x); + ASSERT_EQ(get_version(id1) + 5, bthread::id_value(id1)); + ASSERT_EQ(get_version(id1) + 5, bthread::id_value(id2)); +} + +TEST(BthreadIdTest, join_before_destroy) { + bthread_id_t id1; + int x = 0xdead; + ASSERT_EQ(0, bthread_id_create(&id1, &x, NULL)); + ASSERT_EQ(get_version(id1), bthread::id_value(id1)); + pthread_t th[8]; + SignalArg args[ARRAY_SIZE(th)]; + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + args[i].sleep_us_before_fight = 10000; + args[i].sleep_us_before_signal = 0; + args[i].id = id1; + ASSERT_EQ(0, pthread_create(&th[i], NULL, signaller, &args[i])); + } + ASSERT_EQ(0, bthread_id_join(id1)); + ASSERT_EQ(0xdead + 1, x); + ASSERT_EQ(get_version(id1) + 4, bthread::id_value(id1)); + + void* ret[ARRAY_SIZE(th)]; + size_t non_null_ret = 0; + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, pthread_join(th[i], &ret[i])); + non_null_ret += (ret[i] != NULL); + } + ASSERT_EQ(1UL, non_null_ret); +} + +struct OnResetArg { + bthread_id_t id; + int error_code; +}; + +int on_reset(bthread_id_t id, void* data, int error_code) { + OnResetArg* arg = static_cast(data); + arg->id = id; + arg->error_code = error_code; + return bthread_id_unlock_and_destroy(id); +} + +TEST(BthreadIdTest, error_is_destroy) { + bthread_id_t id1; + OnResetArg arg = { { 0 }, 0 }; + ASSERT_EQ(0, bthread_id_create(&id1, &arg, on_reset)); + ASSERT_EQ(get_version(id1), bthread::id_value(id1)); + ASSERT_EQ(0, bthread_id_error(id1, EBADF)); + ASSERT_EQ(EBADF, arg.error_code); + ASSERT_EQ(id1.value, arg.id.value); + ASSERT_EQ(get_version(id1) + 4, bthread::id_value(id1)); +} + +TEST(BthreadIdTest, error_is_destroy_ranged) { + bthread_id_t id1; + OnResetArg arg = { { 0 }, 0 }; + ASSERT_EQ(0, bthread_id_create_ranged(&id1, &arg, on_reset, 2)); + bthread_id_t id2 = { id1.value + 1 }; + ASSERT_EQ(get_version(id1), bthread::id_value(id2)); + ASSERT_EQ(0, bthread_id_error(id2, EBADF)); + ASSERT_EQ(EBADF, arg.error_code); + ASSERT_EQ(id2.value, arg.id.value); + ASSERT_EQ(get_version(id1) + 5, bthread::id_value(id2)); +} + +TEST(BthreadIdTest, default_error_is_destroy) { + bthread_id_t id1; + ASSERT_EQ(0, bthread_id_create(&id1, NULL, NULL)); + ASSERT_EQ(get_version(id1), bthread::id_value(id1)); + ASSERT_EQ(0, bthread_id_error(id1, EBADF)); + ASSERT_EQ(get_version(id1) + 4, bthread::id_value(id1)); +} + +TEST(BthreadIdTest, doubly_destroy) { + bthread_id_t id1; + ASSERT_EQ(0, bthread_id_create_ranged(&id1, NULL, NULL, 2)); + bthread_id_t id2 = { id1.value + 1 }; + ASSERT_EQ(get_version(id1), bthread::id_value(id1)); + ASSERT_EQ(get_version(id1), bthread::id_value(id2)); + ASSERT_EQ(0, bthread_id_error(id1, EBADF)); + ASSERT_EQ(get_version(id1) + 5, bthread::id_value(id1)); + ASSERT_EQ(get_version(id1) + 5, bthread::id_value(id2)); + ASSERT_EQ(EINVAL, bthread_id_error(id1, EBADF)); + ASSERT_EQ(EINVAL, bthread_id_error(id2, EBADF)); +} + +static int on_numeric_error(bthread_id_t id, void* data, int error_code) { + std::vector* result = static_cast*>(data); + result->push_back(error_code); + EXPECT_EQ(0, bthread_id_unlock(id)); + return 0; +} + +TEST(BthreadIdTest, many_error) { + bthread_id_t id1; + std::vector result; + ASSERT_EQ(0, bthread_id_create(&id1, &result, on_numeric_error)); + ASSERT_EQ(get_version(id1), bthread::id_value(id1)); + int err = 0; + const int N = 100; + for (int i = 0; i < N; ++i) { + ASSERT_EQ(0, bthread_id_error(id1, err++)); + } + ASSERT_EQ((size_t)N, result.size()); + for (int i = 0; i < N; ++i) { + ASSERT_EQ(i, result[i]); + } + ASSERT_EQ(0, bthread_id_trylock(id1, NULL)); + ASSERT_EQ(get_version(id1) + 1, bthread::id_value(id1)); + for (int i = 0; i < N; ++i) { + ASSERT_EQ(0, bthread_id_error(id1, err++)); + } + ASSERT_EQ((size_t)N, result.size()); + ASSERT_EQ(0, bthread_id_unlock(id1)); + ASSERT_EQ(get_version(id1), bthread::id_value(id1)); + ASSERT_EQ((size_t)2*N, result.size()); + for (int i = 0; i < 2*N; ++i) { + EXPECT_EQ(i, result[i]); + } + result.clear(); + + ASSERT_EQ(0, bthread_id_trylock(id1, NULL)); + ASSERT_EQ(get_version(id1) + 1, bthread::id_value(id1)); + for (int i = 0; i < N; ++i) { + ASSERT_EQ(0, bthread_id_error(id1, err++)); + } + ASSERT_EQ(0, bthread_id_unlock_and_destroy(id1)); + ASSERT_TRUE(result.empty()); +} + +static void* locker(void* arg) { + bthread_id_t id = { (uintptr_t)arg }; + butil::Timer tm; + tm.start(); + EXPECT_EQ(0, bthread_id_lock(id, NULL)); + bthread_usleep(2000); + EXPECT_EQ(0, bthread_id_unlock(id)); + tm.stop(); + LOG(INFO) << "Unlocked, tm=" << tm.u_elapsed(); + return NULL; +} + +TEST(BthreadIdTest, id_lock) { + bthread_id_t id1; + ASSERT_EQ(0, bthread_id_create(&id1, NULL, NULL)); + ASSERT_EQ(get_version(id1), bthread::id_value(id1)); + pthread_t th[8]; + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, pthread_create(&th[i], NULL, locker, + (void*)id1.value)); + } + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, pthread_join(th[i], NULL)); + } +} + +static void* failed_locker(void* arg) { + bthread_id_t id = { (uintptr_t)arg }; + int rc = bthread_id_lock(id, NULL); + if (rc == 0) { + bthread_usleep(2000); + EXPECT_EQ(0, bthread_id_unlock_and_destroy(id)); + return (void*)1; + } else { + EXPECT_EQ(EINVAL, rc); + return NULL; + } +} + +TEST(BthreadIdTest, id_lock_and_destroy) { + bthread_id_t id1; + ASSERT_EQ(0, bthread_id_create(&id1, NULL, NULL)); + ASSERT_EQ(get_version(id1), bthread::id_value(id1)); + pthread_t th[8]; + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, pthread_create(&th[i], NULL, failed_locker, + (void*)id1.value)); + } + int non_null = 0; + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + void* ret = NULL; + ASSERT_EQ(0, pthread_join(th[i], &ret)); + non_null += (ret != NULL); + } + ASSERT_EQ(1, non_null); +} + +TEST(BthreadIdTest, join_after_destroy_before_unlock) { + bthread_id_t id1; + int x = 0xdead; + ASSERT_EQ(0, bthread_id_create(&id1, &x, NULL)); + ASSERT_EQ(get_version(id1), bthread::id_value(id1)); + pthread_t th[8]; + SignalArg args[ARRAY_SIZE(th)]; + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + args[i].sleep_us_before_fight = 0; + args[i].sleep_us_before_signal = 20000; + args[i].id = id1; + ASSERT_EQ(0, pthread_create(&th[i], NULL, signaller, &args[i])); + } + bthread_usleep(10000); + // join() waits until destroy() is called. + ASSERT_EQ(0, bthread_id_join(id1)); + ASSERT_EQ(0xdead + 1, x); + ASSERT_EQ(get_version(id1) + 4, bthread::id_value(id1)); + + void* ret[ARRAY_SIZE(th)]; + size_t non_null_ret = 0; + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, pthread_join(th[i], &ret[i])); + non_null_ret += (ret[i] != NULL); + } + ASSERT_EQ(1UL, non_null_ret); +} + +struct StoppedWaiterArgs { + bthread_id_t id; + bool thread_started; +}; + +void* stopped_waiter(void* void_arg) { + StoppedWaiterArgs* args = (StoppedWaiterArgs*)void_arg; + args->thread_started = true; + EXPECT_EQ(0, bthread_id_join(args->id)); + EXPECT_EQ(get_version(args->id) + 4, bthread::id_value(args->id)); + return NULL; +} + +TEST(BthreadIdTest, stop_a_wait_after_fight_before_signal) { + bthread_id_t id1; + int x = 0xdead; + ASSERT_EQ(0, bthread_id_create(&id1, &x, NULL)); + ASSERT_EQ(get_version(id1), bthread::id_value(id1)); + void* data; + ASSERT_EQ(0, bthread_id_trylock(id1, &data)); + ASSERT_EQ(&x, data); + bthread_t th[8]; + StoppedWaiterArgs args[ARRAY_SIZE(th)]; + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + args[i].id = id1; + args[i].thread_started = false; + ASSERT_EQ(0, bthread_start_urgent(&th[i], NULL, stopped_waiter, &args[i])); + } + // stop does not wake up bthread_id_join + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + bthread_stop(th[i]); + } + bthread_usleep(10000); + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_TRUE(bthread::TaskGroup::exists(th[i])); + } + // destroy the id to end the joinings. + ASSERT_EQ(0, bthread_id_unlock_and_destroy(id1)); + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, bthread_join(th[i], NULL)); + } +} + +void* waiter(void* arg) { + bthread_id_t id = { (uintptr_t)arg }; + EXPECT_EQ(0, bthread_id_join(id)); + EXPECT_EQ(get_version(id) + 4, bthread::id_value(id)); + return NULL; +} + +int handle_data(bthread_id_t id, void* data, int error_code) { + EXPECT_EQ(EBADF, error_code); + ++*(int*)data; + EXPECT_EQ(0, bthread_id_unlock_and_destroy(id)); + return 0; +} + +TEST(BthreadIdTest, list_signal) { + bthread_id_list_t list; + ASSERT_EQ(0, bthread_id_list_init(&list, 32, 32)); + bthread_id_t id[16]; + int data[ARRAY_SIZE(id)]; + for (size_t i = 0; i < ARRAY_SIZE(id); ++i) { + data[i] = i; + ASSERT_EQ(0, bthread_id_create(&id[i], &data[i], handle_data)); + ASSERT_EQ(get_version(id[i]), bthread::id_value(id[i])); + ASSERT_EQ(0, bthread_id_list_add(&list, id[i])); + } + pthread_t th[ARRAY_SIZE(id)]; + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, pthread_create(&th[i], NULL, waiter, (void*)(intptr_t)id[i].value)); + } + bthread_usleep(10000); + ASSERT_EQ(0, bthread_id_list_reset(&list, EBADF)); + + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ((int)(i + 1), data[i]); + ASSERT_EQ(0, pthread_join(th[i], NULL)); + // already reset. + ASSERT_EQ((int)(i + 1), data[i]); + } + + bthread_id_list_destroy(&list); +} + +int error_without_unlock(bthread_id_t, void *, int) { + return 0; +} + +TEST(BthreadIdTest, status) { + bthread_id_t id; + bthread_id_create(&id, NULL, NULL); + bthread::id_status(id, std::cout); + bthread_id_lock(id, NULL); + bthread_id_error(id, 123); + bthread_id_error(id, 256); + bthread_id_error(id, 1256); + bthread::id_status(id, std::cout); + bthread_id_unlock_and_destroy(id); + bthread_id_create(&id, NULL, error_without_unlock); + bthread_id_lock(id, NULL); + bthread::id_status(id, std::cout); + bthread_id_error(id, 12); + bthread::id_status(id, std::cout); + bthread_id_unlock(id); + bthread::id_status(id, std::cout); + bthread_id_unlock_and_destroy(id); +} + +TEST(BthreadIdTest, reset_range) { + bthread_id_t id; + ASSERT_EQ(0, bthread_id_create(&id, NULL, NULL)); + ASSERT_EQ(0, bthread_id_lock_and_reset_range(id, NULL, 1000)); + bthread::id_status(id, std::cout); + bthread_id_unlock(id); + ASSERT_EQ(0, bthread_id_lock_and_reset_range(id, NULL, 300)); + bthread::id_status(id, std::cout); + bthread_id_unlock_and_destroy(id); +} + +static bool any_thread_quit = false; + +struct FailToLockIdArgs { + bthread_id_t id; + int expected_return; +}; + +static void* fail_to_lock_id(void* args_in) { + FailToLockIdArgs* args = (FailToLockIdArgs*)args_in; + butil::Timer tm; + EXPECT_EQ(args->expected_return, bthread_id_lock(args->id, NULL)); + any_thread_quit = true; + return NULL; +} + +TEST(BthreadIdTest, about_to_destroy_before_locking) { + bthread_id_t id; + ASSERT_EQ(0, bthread_id_create(&id, NULL, NULL)); + ASSERT_EQ(0, bthread_id_lock(id, NULL)); + ASSERT_EQ(0, bthread_id_about_to_destroy(id)); + pthread_t pth; + bthread_t bth; + FailToLockIdArgs args = { id, EPERM }; + ASSERT_EQ(0, pthread_create(&pth, NULL, fail_to_lock_id, &args)); + ASSERT_EQ(0, bthread_start_background(&bth, NULL, fail_to_lock_id, &args)); + // The threads should quit soon. + pthread_join(pth, NULL); + bthread_join(bth, NULL); + bthread::id_status(id, std::cout); + ASSERT_EQ(0, bthread_id_unlock_and_destroy(id)); +} + +static void* succeed_to_lock_id(void* arg) { + bthread_id_t id = *(bthread_id_t*)arg; + EXPECT_EQ(0, bthread_id_lock(id, NULL)); + EXPECT_EQ(0, bthread_id_unlock(id)); + return NULL; +} + +TEST(BthreadIdTest, about_to_destroy_cancelled) { + bthread_id_t id; + ASSERT_EQ(0, bthread_id_create(&id, NULL, NULL)); + ASSERT_EQ(0, bthread_id_lock(id, NULL)); + ASSERT_EQ(0, bthread_id_about_to_destroy(id)); + ASSERT_EQ(0, bthread_id_unlock(id)); + pthread_t pth; + bthread_t bth; + ASSERT_EQ(0, pthread_create(&pth, NULL, succeed_to_lock_id, &id)); + ASSERT_EQ(0, bthread_start_background(&bth, NULL, succeed_to_lock_id, &id)); + // The threads should quit soon. + pthread_join(pth, NULL); + bthread_join(bth, NULL); + bthread::id_status(id, std::cout); + ASSERT_EQ(0, bthread_id_lock(id, NULL)); + ASSERT_EQ(0, bthread_id_unlock_and_destroy(id)); +} + +TEST(BthreadIdTest, about_to_destroy_during_locking) { + bthread_id_t id; + ASSERT_EQ(0, bthread_id_create(&id, NULL, NULL)); + ASSERT_EQ(0, bthread_id_lock(id, NULL)); + any_thread_quit = false; + pthread_t pth; + bthread_t bth; + FailToLockIdArgs args = { id, EPERM }; + ASSERT_EQ(0, pthread_create(&pth, NULL, fail_to_lock_id, &args)); + ASSERT_EQ(0, bthread_start_background(&bth, NULL, fail_to_lock_id, &args)); + + usleep(100000); + ASSERT_FALSE(any_thread_quit); + ASSERT_EQ(0, bthread_id_about_to_destroy(id)); + + // The threads should quit soon. + pthread_join(pth, NULL); + bthread_join(bth, NULL); + bthread::id_status(id, std::cout); + ASSERT_EQ(0, bthread_id_unlock_and_destroy(id)); +} + +void* const DUMMY_DATA1 = (void*)1; +void* const DUMMY_DATA2 = (void*)2; +int branch_counter = 0; +int branch_tags[4] = {}; +int expected_code = 0; +const char* expected_desc = ""; +int handler_without_desc(bthread_id_t id, void* data, int error_code) { + EXPECT_EQ(DUMMY_DATA1, data); + EXPECT_EQ(expected_code, error_code); + if (error_code == ESTOP) { + branch_tags[0] = branch_counter; + return bthread_id_unlock_and_destroy(id); + } else { + branch_tags[1] = branch_counter; + return bthread_id_unlock(id); + } +} +int handler_with_desc(bthread_id_t id, void* data, int error_code, + const std::string& error_text) { + EXPECT_EQ(DUMMY_DATA2, data); + EXPECT_EQ(expected_code, error_code); + EXPECT_STREQ(expected_desc, error_text.c_str()); + if (error_code == ESTOP) { + branch_tags[2] = branch_counter; + return bthread_id_unlock_and_destroy(id); + } else { + branch_tags[3] = branch_counter; + return bthread_id_unlock(id); + } +} + +TEST(BthreadIdTest, error_with_descriptions) { + bthread_id_t id1; + ASSERT_EQ(0, bthread_id_create(&id1, DUMMY_DATA1, handler_without_desc)); + bthread_id_t id2; + ASSERT_EQ(0, bthread_id_create2(&id2, DUMMY_DATA2, handler_with_desc)); + + // [ Matched in-place ] + // Call bthread_id_error on an id created by bthread_id_create + ++branch_counter; + expected_code = EINVAL; + ASSERT_EQ(0, bthread_id_error(id1, expected_code)); + ASSERT_EQ(branch_counter, branch_tags[1]); + + // Call bthread_id_error2 on an id created by bthread_id_create2 + ++branch_counter; + expected_code = EPERM; + expected_desc = "description1"; + ASSERT_EQ(0, bthread_id_error2(id2, expected_code, expected_desc)); + ASSERT_EQ(branch_counter, branch_tags[3]); + + // [ Mixed in-place ] + // Call bthread_id_error on an id created by bthread_id_create2 + ++branch_counter; + expected_code = ECONNREFUSED; + expected_desc = ""; + ASSERT_EQ(0, bthread_id_error(id2, expected_code)); + ASSERT_EQ(branch_counter, branch_tags[3]); + // Call bthread_id_error2 on an id created by bthread_id_create + ++branch_counter; + expected_code = EINTR; + ASSERT_EQ(0, bthread_id_error2(id1, expected_code, "")); + ASSERT_EQ(branch_counter, branch_tags[1]); + + // [ Matched pending ] + // Call bthread_id_error on an id created by bthread_id_create + ++branch_counter; + expected_code = ECONNRESET; + ASSERT_EQ(0, bthread_id_lock(id1, NULL)); + ASSERT_EQ(0, bthread_id_error(id1, expected_code)); + ASSERT_EQ(0, bthread_id_unlock(id1)); + ASSERT_EQ(branch_counter, branch_tags[1]); + + // Call bthread_id_error2 on an id created by bthread_id_create2 + ++branch_counter; + expected_code = ENOSPC; + expected_desc = "description3"; + ASSERT_EQ(0, bthread_id_lock(id2, NULL)); + ASSERT_EQ(0, bthread_id_error2(id2, expected_code, expected_desc)); + ASSERT_EQ(0, bthread_id_unlock(id2)); + ASSERT_EQ(branch_counter, branch_tags[3]); + + // [ Mixed pending ] + // Call bthread_id_error on an id created by bthread_id_create2 + ++branch_counter; + expected_code = ESTOP; + expected_desc = ""; + ASSERT_EQ(0, bthread_id_lock(id2, NULL)); + ASSERT_EQ(0, bthread_id_error(id2, expected_code)); + ASSERT_EQ(0, bthread_id_unlock(id2)); + ASSERT_EQ(branch_counter, branch_tags[2]); + // Call bthread_id_error2 on an id created by bthread_id_create + ++branch_counter; + ASSERT_EQ(0, bthread_id_lock(id1, NULL)); + ASSERT_EQ(0, bthread_id_error2(id1, expected_code, "")); + ASSERT_EQ(0, bthread_id_unlock(id1)); + ASSERT_EQ(branch_counter, branch_tags[0]); +} +} // namespace diff --git a/test/bthread_key_unittest.cpp b/test/bthread_key_unittest.cpp new file mode 100644 index 0000000..92f4aac --- /dev/null +++ b/test/bthread_key_unittest.cpp @@ -0,0 +1,622 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // std::sort +#include // std::unique_ptr +#include +#include "butil/atomicops.h" +#include +#include "butil/thread_key.h" +#include "butil/time.h" +#include "butil/macros.h" +#include "butil/scoped_lock.h" +#include "butil/logging.h" +#include "bthread/bthread.h" +#include "bthread/unstable.h" +using namespace bthread; +namespace bthread { +DECLARE_uint32(key_table_list_size); +DECLARE_uint32(borrow_from_globle_size); +class KeyTable; +// defined in bthread/key.cpp +extern void return_keytable(bthread_keytable_pool_t*, KeyTable*); +extern KeyTable* borrow_keytable(bthread_keytable_pool_t*); +} // namespace bthread + +int main(int argc, char* argv[]) { + bthread::FLAGS_key_table_list_size = 20; + bthread::FLAGS_borrow_from_globle_size = 20; + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + return RUN_ALL_TESTS(); +} + +extern "C" { +int bthread_keytable_pool_size(bthread_keytable_pool_t* pool) { + bthread_keytable_pool_stat_t s; + if (bthread_keytable_pool_getstat(pool, &s) != 0) { + return 0; + } + return s.nfree; +} +} + +namespace { + +// Count tls usages. +struct Counters { + butil::atomic ncreate {0}; + butil::atomic ndestroy {0}; + butil::atomic nenterthread {0}; + butil::atomic nleavethread {0}; +}; + +// Wrap same counters into different objects to make sure that different key +// returns different objects as well as aggregate the usages. +struct CountersWrapper { + CountersWrapper(Counters* c, bthread_key_t key) : _c(c), _key(key) {} + ~CountersWrapper() { + if (_c) { + _c->ndestroy.fetch_add(1, butil::memory_order_relaxed); + } + CHECK_EQ(0, bthread_key_delete(_key)); + } +private: + Counters* _c; + bthread_key_t _key; +}; + +static void destroy_counters_wrapper(void* arg) { + delete static_cast(arg); +} + +const size_t NKEY_PER_WORKER = 32; + +// NOTE: returns void to use ASSERT +static void worker1_impl(Counters* cs) { + cs->nenterthread.fetch_add(1, butil::memory_order_relaxed); + bthread_key_t k[NKEY_PER_WORKER]; + CountersWrapper* ws[arraysize(k)]; + for (size_t i = 0; i < arraysize(k); ++i) { + ASSERT_EQ(0, bthread_key_create(&k[i], destroy_counters_wrapper)); + } + for (size_t i = 0; i < arraysize(k); ++i) { + ws[i] = new CountersWrapper(cs, k[i]); + } + // Get just-created tls should return NULL. + for (size_t i = 0; i < arraysize(k); ++i) { + ASSERT_EQ(NULL, bthread_getspecific(k[i])); + } + for (size_t i = 0; i < arraysize(k); ++i) { + cs->ncreate.fetch_add(1, butil::memory_order_relaxed); + ASSERT_EQ(0, bthread_setspecific(k[i], ws[i])) + << "i=" << i << " is_bthread=" << !!bthread_self(); + + } + // Sleep a while to make some context switches. TLS should be unchanged. + bthread_usleep(10000); + + for (size_t i = 0; i < arraysize(k); ++i) { + ASSERT_EQ(ws[i], bthread_getspecific(k[i])) << "i=" << i; + } + cs->nleavethread.fetch_add(1, butil::memory_order_relaxed); +} + +static void* worker1(void* arg) { + worker1_impl(static_cast(arg)); + return NULL; +} + +TEST(KeyTest, creating_key_in_parallel) { + Counters args; + pthread_t th[8]; + bthread_t bth[8]; + for (size_t i = 0; i < arraysize(th); ++i) { + ASSERT_EQ(0, pthread_create(&th[i], NULL, worker1, &args)); + } + for (size_t i = 0; i < arraysize(bth); ++i) { + ASSERT_EQ(0, bthread_start_background(&bth[i], NULL, worker1, &args)); + } + for (size_t i = 0; i < arraysize(th); ++i) { + ASSERT_EQ(0, pthread_join(th[i], NULL)); + } + for (size_t i = 0; i < arraysize(bth); ++i) { + ASSERT_EQ(0, bthread_join(bth[i], NULL)); + } + ASSERT_EQ(arraysize(th) + arraysize(bth), + args.nenterthread.load(butil::memory_order_relaxed)); + ASSERT_EQ(arraysize(th) + arraysize(bth), + args.nleavethread.load(butil::memory_order_relaxed)); + ASSERT_EQ(NKEY_PER_WORKER * (arraysize(th) + arraysize(bth)), + args.ncreate.load(butil::memory_order_relaxed)); + ASSERT_EQ(NKEY_PER_WORKER * (arraysize(th) + arraysize(bth)), + args.ndestroy.load(butil::memory_order_relaxed)); +} + +butil::atomic seq(1); +std::vector seqs; +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +void dtor2(void* arg) { + BAIDU_SCOPED_LOCK(mutex); + seqs.push_back((size_t)arg); +} + +// NOTE: returns void to use ASSERT +static void worker2_impl(bthread_key_t k) { + ASSERT_EQ(NULL, bthread_getspecific(k)); + ASSERT_EQ(0, bthread_setspecific(k, (void*)seq.fetch_add(1))); +} + +static void* worker2(void* arg) { + worker2_impl(*static_cast(arg)); + return NULL; +} + +TEST(KeyTest, use_one_key_in_different_threads) { + bthread_key_t k; + ASSERT_EQ(0, bthread_key_create(&k, dtor2)) << berror(); + seqs.clear(); + + pthread_t th[16]; + for (size_t i = 0; i < arraysize(th); ++i) { + ASSERT_EQ(0, pthread_create(&th[i], NULL, worker2, &k)); + } + bthread_t bth[1]; + for (size_t i = 0; i < arraysize(bth); ++i) { + ASSERT_EQ(0, bthread_start_urgent(&bth[i], NULL, worker2, &k)); + } + for (size_t i = 0; i < arraysize(th); ++i) { + ASSERT_EQ(0, pthread_join(th[i], NULL)); + } + for (size_t i = 0; i < arraysize(bth); ++i) { + ASSERT_EQ(0, bthread_join(bth[i], NULL)); + } + ASSERT_EQ(arraysize(th) + arraysize(bth), seqs.size()); + std::sort(seqs.begin(), seqs.end()); + ASSERT_EQ(seqs.end(), std::unique(seqs.begin(), seqs.end())); + ASSERT_EQ(arraysize(th) + arraysize(bth) - 1, *(seqs.end()-1) - *seqs.begin()); + + ASSERT_EQ(0, bthread_key_delete(k)); +} + +struct Keys { + bthread_key_t valid_key; + bthread_key_t invalid_key; +}; + +void* const DUMMY_PTR = (void*)1; + +void use_invalid_keys_impl(const Keys* keys) { + ASSERT_EQ(NULL, bthread_getspecific(keys->invalid_key)); + // valid key returns NULL as well. + ASSERT_EQ(NULL, bthread_getspecific(keys->valid_key)); + + // both pthread_setspecific(of nptl) and bthread_setspecific should find + // the key is invalid. + ASSERT_EQ(EINVAL, bthread_setspecific(keys->invalid_key, DUMMY_PTR)); + ASSERT_EQ(0, bthread_setspecific(keys->valid_key, DUMMY_PTR)); + + // Print error again. + ASSERT_EQ(NULL, bthread_getspecific(keys->invalid_key)); + ASSERT_EQ(DUMMY_PTR, bthread_getspecific(keys->valid_key)); +} + +void* use_invalid_keys(void* args) { + use_invalid_keys_impl(static_cast(args)); + return NULL; +} + +TEST(KeyTest, use_invalid_keys) { + Keys keys; + ASSERT_EQ(0, bthread_key_create(&keys.valid_key, NULL)); + // intended to be a created but invalid key. + keys.invalid_key.index = keys.valid_key.index; + keys.invalid_key.version = 123; + + pthread_t th; + bthread_t bth; + ASSERT_EQ(0, pthread_create(&th, NULL, use_invalid_keys, &keys)); + ASSERT_EQ(0, bthread_start_urgent(&bth, NULL, use_invalid_keys, &keys)); + ASSERT_EQ(0, pthread_join(th, NULL)); + ASSERT_EQ(0, bthread_join(bth, NULL)); + ASSERT_EQ(0, bthread_key_delete(keys.valid_key)); +} + +TEST(KeyTest, reuse_key) { + bthread_key_t key; + ASSERT_EQ(0, bthread_key_create(&key, NULL)); + ASSERT_EQ(NULL, bthread_getspecific(key)); + ASSERT_EQ(0, bthread_setspecific(key, (void*)1)); + ASSERT_EQ(0, bthread_key_delete(key)); // delete key before clearing TLS. + bthread_key_t key2; + ASSERT_EQ(0, bthread_key_create(&key2, NULL)); + ASSERT_EQ(key.index, key2.index); + // The slot is not NULL, the impl must check version and return NULL. + ASSERT_EQ(NULL, bthread_getspecific(key2)); +} + +// NOTE: sid is short for 'set in dtor'. +struct SidData { + bthread_key_t key; + int seq; + int end_seq; +}; + +static void sid_dtor(void* tls){ + SidData* data = (SidData*)tls; + // Should already be set NULL. + ASSERT_EQ(NULL, bthread_getspecific(data->key)); + if (++data->seq < data->end_seq){ + ASSERT_EQ(0, bthread_setspecific(data->key, data)); + } +} + +static void sid_thread_impl(SidData* data) { + ASSERT_EQ(0, bthread_setspecific(data->key, data)); +}; + +static void* sid_thread(void* args) { + sid_thread_impl((SidData*)args); + return NULL; +} + +TEST(KeyTest, set_in_dtor) { + bthread_key_t key; + ASSERT_EQ(0, bthread_key_create(&key, sid_dtor)); + + SidData pth_data = { key, 0, 3 }; + SidData bth_data = { key, 0, 3 }; + SidData bth2_data = { key, 0, 3 }; + + pthread_t pth; + bthread_t bth; + bthread_t bth2; + ASSERT_EQ(0, pthread_create(&pth, NULL, sid_thread, &pth_data)); + ASSERT_EQ(0, bthread_start_urgent(&bth, NULL, sid_thread, &bth_data)); + ASSERT_EQ(0, bthread_start_urgent(&bth2, &BTHREAD_ATTR_PTHREAD, + sid_thread, &bth2_data)); + + ASSERT_EQ(0, pthread_join(pth, NULL)); + ASSERT_EQ(0, bthread_join(bth, NULL)); + ASSERT_EQ(0, bthread_join(bth2, NULL)); + + ASSERT_EQ(0, bthread_key_delete(key)); + + EXPECT_EQ(pth_data.end_seq, pth_data.seq); + EXPECT_EQ(bth_data.end_seq, bth_data.seq); + EXPECT_EQ(bth2_data.end_seq, bth2_data.seq); +} + +struct SBAData { + bthread_key_t key; + int level; + int ndestroy; +}; + +struct SBATLS { + int* ndestroy; + + static void deleter(void* d) { + SBATLS* tls = (SBATLS*)d; + ++*tls->ndestroy; + delete tls; + } +}; + +void* set_before_anybth(void* args); + +void set_before_anybth_impl(SBAData* data) { + ASSERT_EQ(NULL, bthread_getspecific(data->key)); + SBATLS *tls = new SBATLS; + tls->ndestroy = &data->ndestroy; + ASSERT_EQ(0, bthread_setspecific(data->key, tls)); + ASSERT_EQ(tls, bthread_getspecific(data->key)); + if (data->level++ == 0) { + bthread_t bth; + ASSERT_EQ(0, bthread_start_urgent(&bth, NULL, set_before_anybth, data)); + ASSERT_EQ(0, bthread_join(bth, NULL)); + ASSERT_EQ(1, data->ndestroy); + } else { + bthread_usleep(1000); + } + ASSERT_EQ(tls, bthread_getspecific(data->key)); +} + +void* set_before_anybth(void* args) { + set_before_anybth_impl((SBAData*)args); + return NULL; +} + +TEST(KeyTest, set_tls_before_creating_any_bthread) { + bthread_key_t key; + ASSERT_EQ(0, bthread_key_create(&key, SBATLS::deleter)); + pthread_t th; + SBAData data; + data.key = key; + data.level = 0; + data.ndestroy = 0; + ASSERT_EQ(0, pthread_create(&th, NULL, set_before_anybth, &data)); + ASSERT_EQ(0, pthread_join(th, NULL)); + ASSERT_EQ(0, bthread_key_delete(key)); + ASSERT_EQ(2, data.level); + ASSERT_EQ(2, data.ndestroy); +} + +struct PoolData { + bthread_key_t key; + PoolData* data; + int seq; + int end_seq; +}; + +bool use_same_keytable = false; + +static void pool_thread_impl(PoolData* data) { + if (NULL == bthread_getspecific(data->key)) { + ASSERT_EQ(0, bthread_setspecific(data->key, data)); + } else { + use_same_keytable = true; + } +}; + +static void* pool_thread(void* args) { + pool_thread_impl((PoolData*)args); + return NULL; +} + +static void pool_dtor(void* tls){ + PoolData* data = (PoolData*)tls; + // Should already be set NULL. + ASSERT_EQ(NULL, bthread_getspecific(data->key)); + if (++data->seq < data->end_seq){ + ASSERT_EQ(0, bthread_setspecific(data->key, data)); + } +} + +TEST(KeyTest, using_pool) { + bthread_key_t key; + ASSERT_EQ(0, bthread_key_create(&key, pool_dtor)); + + bthread_keytable_pool_t pool; + ASSERT_EQ(0, bthread_keytable_pool_init(&pool)); + ASSERT_EQ(0, bthread_keytable_pool_size(&pool)); + + bthread_attr_t attr; + ASSERT_EQ(0, bthread_attr_init(&attr)); + attr.keytable_pool = &pool; + + bthread_attr_t attr2 = attr; + attr2.stack_type = BTHREAD_STACKTYPE_PTHREAD; + + PoolData bth_data = { key, NULL, 0, 3 }; + bthread_t bth; + ASSERT_EQ(0, bthread_start_urgent(&bth, &attr, pool_thread, &bth_data)); + ASSERT_EQ(0, bthread_join(bth, NULL)); + ASSERT_EQ(0, bth_data.seq); + + PoolData bth2_data = { key, NULL, 0, 3 }; + bthread_t bth2; + ASSERT_EQ(0, bthread_start_urgent(&bth2, &attr2, pool_thread, &bth2_data)); + ASSERT_EQ(0, bthread_join(bth2, NULL)); + ASSERT_EQ(0, bth2_data.seq); + + ASSERT_EQ(0, bthread_keytable_pool_destroy(&pool)); + if (use_same_keytable) { + EXPECT_EQ(bth_data.end_seq, bth_data.seq); + EXPECT_EQ(0, bth2_data.seq); + } else { + EXPECT_EQ(bth_data.end_seq, bth_data.seq); + EXPECT_EQ(bth_data.end_seq, bth2_data.seq); + } + + ASSERT_EQ(0, bthread_key_delete(key)); +} + +bthread_keytable_pool_t test_pool; +struct PoolData2 { + bthread_key_t key; + bthread_attr_t attr; +}; + +static void pool_dtor2(void* tls) { + delete static_cast(tls); +} + +static void usleep_thread_impl(PoolData2* data) { + if (NULL == bthread_getspecific(data->key)) { + PoolData2* data_new = new PoolData2(); + ASSERT_EQ(0, bthread_setspecific(data->key, data_new)); + } + bthread_usleep(1000L); + int length = get_thread_local_keytable_list_length(&test_pool); + ASSERT_LE((size_t)length, bthread::FLAGS_key_table_list_size); +} + +static void* usleep_thread(void* args) { + std::unique_ptr data((PoolData2*)args); + usleep_thread_impl(data.get()); + return NULL; +} + +static void launch_many_bthreads(PoolData2* data) { + std::vector tids; + tids.reserve(25000); + for (size_t i = 0; i < 25000; ++i) { + bthread_t t0; + PoolData2* data_tmp = new PoolData2(); + data_tmp->key = data->key; + ASSERT_EQ(0, bthread_start_background(&t0, &(data->attr), usleep_thread, data_tmp)); + tids.push_back(t0); + } + + usleep(3 * 1000 * 1000L); + for (size_t i = 0; i < tids.size(); ++i) { + bthread_join(tids[i], NULL); + } +} + +static void* run_launch_many_bthreads(void* args) { + PoolData2* data = (PoolData2*)args; + launch_many_bthreads(data); + return NULL; +} + +TEST(KeyTest, frequently_borrow_keytable_when_using_pool) { + PoolData2 data; + ASSERT_EQ(0, bthread_key_create(&data.key, pool_dtor2)); + + ASSERT_EQ(0, bthread_keytable_pool_init(&test_pool)); + ASSERT_EQ(0, bthread_keytable_pool_size(&test_pool)); + + ASSERT_EQ(0, bthread_attr_init(&data.attr)); + data.attr.keytable_pool = &test_pool; + + bthread_t bth; + ASSERT_EQ(0, bthread_start_urgent(&bth, &data.attr, run_launch_many_bthreads, &data)); + ASSERT_EQ(0, bthread_join(bth, NULL)); + std::cout << "Free keytable size is " + << bthread_keytable_pool_size(&test_pool) + << " use keytable size is 25000" << std::endl; + ASSERT_EQ(0, bthread_keytable_pool_destroy(&test_pool)); + ASSERT_EQ(0, bthread_key_delete(data.key)); +} + +std::vector table_list; +pthread_mutex_t table_mutex = PTHREAD_MUTEX_INITIALIZER; + +static void return_thread_impl() { + for (int i = 0; i < 32768; i++) { + { + BAIDU_SCOPED_LOCK(table_mutex); + if (table_list.size() > 0) { + bthread::KeyTable* keytable = table_list[0]; + table_list.erase(table_list.begin()); + bthread::return_keytable(&test_pool, keytable); + } + } + int length = get_thread_local_keytable_list_length(&test_pool); + ASSERT_LE((size_t)length, bthread::FLAGS_key_table_list_size); + } +} + +static void* return_thread(void*) { + return_thread_impl(); + return NULL; +} + +static void borrow_thread_impl() { + for (int i = 0; i < 32768; i++) { + bthread::KeyTable* keytable = bthread::borrow_keytable(&test_pool); + BAIDU_SCOPED_LOCK(table_mutex); + table_list.push_back(keytable); + } +} + +static void* borrow_thread(void*) { + borrow_thread_impl(); + return NULL; +} + +TEST(KeyTest, borrow_and_return_keytable_when_using_pool) { + ASSERT_EQ(0, bthread_keytable_pool_init(&test_pool)); + ASSERT_EQ(0, bthread_keytable_pool_size(&test_pool)); + + bthread_attr_t attr; + ASSERT_EQ(0, bthread_attr_init(&attr)); + attr.keytable_pool = &test_pool; + + bthread_t borrow_bth[8]; + bthread_t return_bth[8]; + for (size_t i = 0; i < arraysize(borrow_bth); ++i) { + ASSERT_EQ(0, bthread_start_background(&borrow_bth[i], &attr, + borrow_thread, NULL)); + } + for (size_t i = 0; i < arraysize(return_bth); ++i) { + ASSERT_EQ(0, bthread_start_background(&return_bth[i], &attr, + return_thread, NULL)); + } + + for (size_t i = 0; i < arraysize(borrow_bth); ++i) { + ASSERT_EQ(0, bthread_join(borrow_bth[i], NULL)); + } + for (size_t i = 0; i < arraysize(return_bth); ++i) { + ASSERT_EQ(0, bthread_join(return_bth[i], NULL)); + } + + for (size_t i = 0; i < table_list.size(); i++) { + bthread::return_keytable(&test_pool, table_list[i]); + } + table_list.clear(); + + ASSERT_EQ(0, bthread_keytable_pool_destroy(&test_pool)); +} + +// NOTE: lid is short for 'lock in dtor'. +butil::atomic lid_seq(1); +std::vector lid_seqs; +bthread_mutex_t mu; + +static void lid_dtor(void* tls) { + bthread_mutex_lock(&mu); + lid_seqs.push_back((size_t)tls); + bthread_mutex_unlock(&mu); +} + +static void lid_worker_impl(bthread_key_t key) { + ASSERT_EQ(NULL, bthread_getspecific(key)); + ASSERT_EQ(0, bthread_setspecific(key, (void*)lid_seq.fetch_add(1))); +} + +static void* lid_worker(void* arg) { + lid_worker_impl(*static_cast(arg)); + return NULL; +} + +TEST(KeyTest, use_bthread_mutex_in_dtor) { + bthread_key_t key; + + ASSERT_EQ(0, bthread_mutex_init(&mu, nullptr)); + ASSERT_EQ(0, bthread_key_create(&key, lid_dtor)); + + lid_seqs.clear(); + + bthread_t bth[8]; + for (size_t i = 0; i < arraysize(bth); ++i) { + ASSERT_EQ(0, bthread_start_urgent(&bth[i], NULL, lid_worker, &key)); + } + pthread_t th[8]; + for (size_t i = 0; i < arraysize(th); ++i) { + ASSERT_EQ(0, pthread_create(&th[i], NULL, lid_worker, &key)); + } + for (size_t i = 0; i < arraysize(bth); ++i) { + ASSERT_EQ(0, bthread_join(bth[i], NULL)); + } + for (size_t i = 0; i < arraysize(th); ++i) { + ASSERT_EQ(0, pthread_join(th[i], NULL)); + } + ASSERT_EQ(arraysize(th) + arraysize(bth), lid_seqs.size()); + std::sort(lid_seqs.begin(), lid_seqs.end()); + ASSERT_EQ(lid_seqs.end(), std::unique(lid_seqs.begin(), lid_seqs.end())); + ASSERT_EQ(arraysize(th) + arraysize(bth) - 1, + *(lid_seqs.end() - 1) - *lid_seqs.begin()); + + ASSERT_EQ(0, bthread_key_delete(key)); + ASSERT_EQ(0, bthread_mutex_destroy(&mu)); +} + +} // namespace diff --git a/test/bthread_list_unittest.cpp b/test/bthread_list_unittest.cpp new file mode 100644 index 0000000..efcfbf4 --- /dev/null +++ b/test/bthread_list_unittest.cpp @@ -0,0 +1,60 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/time.h" +#include "butil/macros.h" +#include "butil/logging.h" +#include "bthread/task_group.h" +#include "bthread/bthread.h" + +namespace { +void* sleeper(void* arg) { + bthread_usleep((long)arg); + return NULL; +} + +TEST(ListTest, join_thread_by_list) { + bthread_list_t list; + ASSERT_EQ(0, bthread_list_init(&list, 0, 0)); + std::vector tids; + for (size_t i = 0; i < 10; ++i) { + bthread_t th; + ASSERT_EQ(0, bthread_start_urgent( + &th, NULL, sleeper, (void*)10000/*10ms*/)); + ASSERT_EQ(0, bthread_list_add(&list, th)); + tids.push_back(th); + } + ASSERT_EQ(0, bthread_list_join(&list)); + for (size_t i = 0; i < tids.size(); ++i) { + ASSERT_FALSE(bthread::TaskGroup::exists(tids[i])); + } + bthread_list_destroy(&list); +} + +TEST(ListTest, join_a_destroyed_list) { + bthread_list_t list; + ASSERT_EQ(0, bthread_list_init(&list, 0, 0)); + bthread_t th; + ASSERT_EQ(0, bthread_start_urgent( + &th, NULL, sleeper, (void*)10000/*10ms*/)); + ASSERT_EQ(0, bthread_list_add(&list, th)); + ASSERT_EQ(0, bthread_list_join(&list)); + bthread_list_destroy(&list); + ASSERT_EQ(EINVAL, bthread_list_join(&list)); +} +} // namespace diff --git a/test/bthread_mutex_unittest.cpp b/test/bthread_mutex_unittest.cpp new file mode 100644 index 0000000..121f1eb --- /dev/null +++ b/test/bthread_mutex_unittest.cpp @@ -0,0 +1,369 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/compat.h" +#include "butil/time.h" +#include "butil/macros.h" +#include "butil/string_printf.h" +#include "butil/logging.h" +#include "bthread/bthread.h" +#include "bthread/butex.h" +#include "bthread/task_control.h" +#include "bthread/mutex.h" +#include "gperftools_helper.h" + +namespace { +inline unsigned* get_butex(bthread_mutex_t & m) { + return m.butex; +} + +long start_time = butil::cpuwide_time_ms(); +int c = 0; +void* locker(void* arg) { + bthread_mutex_t* m = (bthread_mutex_t*)arg; + bthread_mutex_lock(m); + printf("[%" PRIu64 "] I'm here, %d, %" PRId64 "ms\n", + pthread_numeric_id(), ++c, butil::cpuwide_time_ms() - start_time); + bthread_usleep(10000); + bthread_mutex_unlock(m); + return NULL; +} + +TEST(MutexTest, sanity) { + bthread_mutex_t m; + ASSERT_EQ(0, bthread_mutex_init(&m, NULL)); + ASSERT_EQ(0u, *get_butex(m)); + ASSERT_EQ(0, bthread_mutex_lock(&m)); + ASSERT_EQ(1u, *get_butex(m)); + bthread_t th1; + ASSERT_EQ(0, bthread_start_urgent(&th1, NULL, locker, &m)); + usleep(5000); // wait for locker to run. + ASSERT_EQ(257u, *get_butex(m)); // contention + ASSERT_EQ(0, bthread_mutex_unlock(&m)); + ASSERT_EQ(0, bthread_join(th1, NULL)); + ASSERT_EQ(0u, *get_butex(m)); + ASSERT_EQ(0, bthread_mutex_destroy(&m)); +} + +TEST(MutexTest, used_in_pthread) { + bthread_mutex_t m; + ASSERT_EQ(0, bthread_mutex_init(&m, NULL)); + pthread_t th[8]; + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + ASSERT_EQ(0, pthread_create(&th[i], NULL, locker, &m)); + } + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + pthread_join(th[i], NULL); + } + ASSERT_EQ(0u, *get_butex(m)); + ASSERT_EQ(0, bthread_mutex_destroy(&m)); +} + +void* do_locks(void *arg) { + struct timespec t = { -2, 0 }; + EXPECT_EQ(ETIMEDOUT, bthread_mutex_timedlock((bthread_mutex_t*)arg, &t)); + return NULL; +} + +TEST(MutexTest, timedlock) { + bthread_cond_t c; + bthread_mutex_t m1; + bthread_mutex_t m2; + ASSERT_EQ(0, bthread_cond_init(&c, NULL)); + ASSERT_EQ(0, bthread_mutex_init(&m1, NULL)); + ASSERT_EQ(0, bthread_mutex_init(&m2, NULL)); + + struct timespec t = { -2, 0 }; + + bthread_mutex_lock (&m1); + bthread_mutex_lock (&m2); + bthread_t pth; + ASSERT_EQ(0, bthread_start_urgent(&pth, NULL, do_locks, &m1)); + ASSERT_EQ(ETIMEDOUT, bthread_cond_timedwait(&c, &m2, &t)); + ASSERT_EQ(0, bthread_join(pth, NULL)); + bthread_mutex_unlock(&m1); + bthread_mutex_unlock(&m2); + bthread_mutex_destroy(&m1); + bthread_mutex_destroy(&m2); +} + +TEST(MutexTest, cpp_wrapper) { + bthread::Mutex mutex; + ASSERT_TRUE(mutex.try_lock()); + mutex.unlock(); + mutex.lock(); + mutex.unlock(); + struct timespec t = { -2, 0 }; + ASSERT_TRUE(mutex.timed_lock(&t)); + mutex.unlock(); + { + BAIDU_SCOPED_LOCK(mutex); + ASSERT_FALSE(mutex.timed_lock(&t)); + } + { + std::unique_lock lck1; + std::unique_lock lck2(mutex); + lck1.swap(lck2); + lck1.unlock(); + lck1.lock(); + } + ASSERT_TRUE(mutex.try_lock()); + mutex.unlock(); + { + BAIDU_SCOPED_LOCK(*mutex.native_handler()); + } + { + std::unique_lock lck1; + std::unique_lock lck2(*mutex.native_handler()); + lck1.swap(lck2); + lck1.unlock(); + lck1.lock(); + } + ASSERT_TRUE(mutex.try_lock()); + mutex.unlock(); + ASSERT_TRUE(mutex.timed_lock(&t)); + mutex.unlock(); +} + +bool g_started = false; +bool g_stopped = false; + +template +struct BAIDU_CACHELINE_ALIGNMENT PerfArgs { + Mutex* mutex; + int64_t counter; + int64_t elapse_ns; + bool ready; + + PerfArgs() : mutex(NULL), counter(0), elapse_ns(0), ready(false) {} +}; + +template +void* add_with_mutex(void* void_arg) { + PerfArgs* args = (PerfArgs*)void_arg; + args->ready = true; + butil::Timer t; + while (!g_stopped) { + if (g_started) { + break; + } + bthread_usleep(1000); + } + t.start(); + while (!g_stopped) { + BAIDU_SCOPED_LOCK(*args->mutex); + ++args->counter; + } + t.stop(); + args->elapse_ns = t.n_elapsed(); + return NULL; +} + +int g_prof_name_counter = 0; + +template +void PerfTest(Mutex* mutex, + ThreadId* /*dummy*/, + int thread_num, + const ThreadCreateFn& create_fn, + const ThreadJoinFn& join_fn) { + g_started = false; + g_stopped = false; + ThreadId threads[thread_num]; + std::vector > args(thread_num); + for (int i = 0; i < thread_num; ++i) { + args[i].mutex = mutex; + create_fn(&threads[i], NULL, add_with_mutex, &args[i]); + } + while (true) { + bool all_ready = true; + for (int i = 0; i < thread_num; ++i) { + if (!args[i].ready) { + all_ready = false; + break; + } + } + if (all_ready) { + break; + } + usleep(1000); + } + g_started = true; + char prof_name[32]; + snprintf(prof_name, sizeof(prof_name), "mutex_perf_%d.prof", ++g_prof_name_counter); + ProfilerStart(prof_name); + usleep(500 * 1000); + ProfilerStop(); + g_stopped = true; + int64_t wait_time = 0; + int64_t count = 0; + for (int i = 0; i < thread_num; ++i) { + join_fn(threads[i], NULL); + wait_time += args[i].elapse_ns; + count += args[i].counter; + } + LOG(INFO) << butil::class_name() << " in " + << ((void*)create_fn == (void*)pthread_create ? "pthread" : "bthread") + << " thread_num=" << thread_num + << " count=" << count + << " average_time=" << wait_time / (double)count; +} + +TEST(MutexTest, performance) { + const int thread_num = 12; + butil::Mutex base_mutex; + PerfTest(&base_mutex, (pthread_t*)NULL, thread_num, pthread_create, pthread_join); + PerfTest(&base_mutex, (bthread_t*)NULL, thread_num, bthread_start_background, bthread_join); + + bthread::FastPthreadMutex fast_mutex; + PerfTest(&fast_mutex, (pthread_t*)NULL, thread_num, pthread_create, pthread_join); + PerfTest(&fast_mutex, (bthread_t*)NULL, thread_num, bthread_start_background, bthread_join); + + bthread::Mutex bth_mutex; + PerfTest(&bth_mutex, (pthread_t*)NULL, thread_num, pthread_create, pthread_join); + PerfTest(&bth_mutex, (bthread_t*)NULL, thread_num, bthread_start_background, bthread_join); +} + +template +void* loop_until_stopped(void* arg) { + auto m = (Mutex*)arg; + while (!g_stopped) { + BAIDU_SCOPED_LOCK(*m); + bthread_usleep(20); + } + return NULL; +} + +TEST(MutexTest, mix_thread_types) { + g_stopped = false; + const int N = 16; + const int M = N * 2; + bthread::Mutex m; + pthread_t pthreads[N]; + bthread_t bthreads[M]; + // reserve enough workers for test. This is a must since we have + // BTHREAD_ATTR_PTHREAD bthreads which may cause deadlocks (the + // bhtread_usleep below can't be scheduled and g_stopped is never + // true, thus loop_until_stopped spins forever) + bthread_setconcurrency(M); + for (int i = 0; i < N; ++i) { + ASSERT_EQ(0, pthread_create(&pthreads[i], NULL, loop_until_stopped, &m)); + } + for (int i = 0; i < M; ++i) { + const bthread_attr_t *attr = i % 2 ? NULL : &BTHREAD_ATTR_PTHREAD; + ASSERT_EQ(0, bthread_start_urgent(&bthreads[i], attr, loop_until_stopped, &m)); + } + bthread_usleep(1000L * 1000); + g_stopped = true; + for (int i = 0; i < M; ++i) { + bthread_join(bthreads[i], NULL); + } + for (int i = 0; i < N; ++i) { + pthread_join(pthreads[i], NULL); + } +} + +void* do_fast_pthread_timedlock(void *arg) { + struct timespec t = { -2, 0 }; + EXPECT_FALSE(((bthread::FastPthreadMutex*)arg)->timed_lock(&t)); + EXPECT_EQ(ETIMEDOUT, errno); + return NULL; +} + +TEST(MutexTest, fast_pthread_mutex) { + bthread::FastPthreadMutex mutex; + ASSERT_TRUE(mutex.try_lock()); + mutex.unlock(); + mutex.lock(); + mutex.unlock(); + { + BAIDU_SCOPED_LOCK(mutex); + struct timespec t = { -2, 0 }; + ASSERT_FALSE(mutex.timed_lock(&t)); + ASSERT_EQ(ETIMEDOUT, errno); + pthread_t th; + ASSERT_EQ(0, pthread_create(&th, NULL, do_fast_pthread_timedlock, &mutex)); + ASSERT_EQ(0, pthread_join(th, NULL)); + } + { + std::unique_lock lck1; + std::unique_lock lck2(mutex); + lck1.swap(lck2); + lck1.unlock(); + lck1.lock(); + } + ASSERT_TRUE(mutex.try_lock()); + mutex.unlock(); + + const int N = 16; + pthread_t pthreads[N]; + for (int i = 0; i < N; ++i) { + ASSERT_EQ(0, pthread_create(&pthreads[i], NULL, + loop_until_stopped, &mutex)); + } + bthread_usleep(1000L * 1000); + g_stopped = true; + for (int i = 0; i < N; ++i) { + pthread_join(pthreads[i], NULL); + } +} + +#if HAS_PTHREAD_MUTEX_TIMEDLOCK +void* do_pthread_timedlock(void *arg) { + struct timespec t = { -2, 0 }; + EXPECT_EQ(ETIMEDOUT, pthread_mutex_timedlock((pthread_mutex_t*)arg, &t)); + return NULL; +} +#endif + +TEST(MutexTest, pthread_mutex) { + pthread_mutex_t mutex; + ASSERT_EQ(0, pthread_mutex_init(&mutex, NULL)); + ASSERT_EQ(0, pthread_mutex_trylock(&mutex)); + ASSERT_EQ(0, pthread_mutex_unlock(&mutex)); + ASSERT_EQ(0, pthread_mutex_lock(&mutex)); + ASSERT_EQ(0, pthread_mutex_unlock(&mutex)); + { + BAIDU_SCOPED_LOCK(mutex); +#if HAS_PTHREAD_MUTEX_TIMEDLOCK + LOG(INFO) << "pthread_mutex_timedlock is available"; + struct timespec t = { -2, 0 }; + ASSERT_EQ(ETIMEDOUT, pthread_mutex_timedlock(&mutex, &t)); + pthread_t th; + ASSERT_EQ(0, pthread_create(&th, NULL, do_pthread_timedlock, &mutex)); + ASSERT_EQ(0, pthread_join(th, NULL)); +#endif + } + ASSERT_EQ(0, pthread_mutex_trylock(&mutex)); + ASSERT_EQ(0, pthread_mutex_unlock(&mutex)); + + const int N = 16; + pthread_t pthreads[N]; + for (int i = 0; i < N; ++i) { + ASSERT_EQ(0, pthread_create(&pthreads[i], NULL, + loop_until_stopped, &mutex)); + } + bthread_usleep(1000L * 1000); + g_stopped = true; + for (int i = 0; i < N; ++i) { + pthread_join(pthreads[i], NULL); + } +} + +} // namespace diff --git a/test/bthread_once_unittest.cpp b/test/bthread_once_unittest.cpp new file mode 100644 index 0000000..618798e --- /dev/null +++ b/test/bthread_once_unittest.cpp @@ -0,0 +1,136 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "bthread/bthread.h" +#include "bthread/singleton_on_bthread_once.h" +#include "bthread/task_control.h" + +namespace bthread { +extern TaskControl* g_task_control; +} + +namespace { + +bthread_once_t g_bthread_once_control; +bool g_bthread_once_started = false; +butil::atomic g_bthread_once_count(0); + +void init_routine() { + bthread_usleep(2000 * 1000); + g_bthread_once_count.fetch_add(1, butil::memory_order_relaxed); +} + +void bthread_once_task() { + bthread_once(&g_bthread_once_control, init_routine); + // `init_routine' only be called once. + ASSERT_EQ(1, g_bthread_once_count.load(butil::memory_order_relaxed)); +} + +void* first_bthread_once_task(void*) { + g_bthread_once_started = true; + bthread_once_task(); + return NULL; +} + + +void* other_bthread_once_task(void*) { + bthread_once_task(); + return NULL; +} + +TEST(BthreadOnceTest, once) { + bthread_t bid; + ASSERT_EQ(0, bthread_start_background( + &bid, NULL, first_bthread_once_task, NULL)); + while (!g_bthread_once_started) { + bthread_usleep(1000); + } + ASSERT_NE(nullptr, bthread::g_task_control); + int concurrency = bthread::g_task_control->concurrency(); + LOG(INFO) << "concurrency: " << concurrency; + ASSERT_GT(concurrency, 0); + std::vector bids(concurrency * 100); + for (auto& id : bids) { + ASSERT_EQ(0, bthread_start_background( + &id, NULL, other_bthread_once_task, NULL)); + } + bthread_once_task(); + + for (auto& id : bids) { + bthread_join(id, NULL); + } + bthread_join(bid, NULL); +} + +bool g_bthread_started = false; +butil::atomic g_bthread_singleton_count(0); + +class BthreadSingleton { +public: + BthreadSingleton() { + bthread_usleep(2000 * 1000); + g_bthread_singleton_count.fetch_add(1, butil::memory_order_relaxed); + } +}; + +void get_bthread_singleton() { + auto instance = bthread::get_leaky_singleton(); + ASSERT_NE(nullptr, instance); + // Only one BthreadSingleton instance has been created. + ASSERT_EQ(1, g_bthread_singleton_count.load(butil::memory_order_relaxed)); +} + +void* first_get_bthread_singleton(void*) { + g_bthread_started = true; + get_bthread_singleton(); + return NULL; +} + + +void* get_bthread_singleton(void*) { + get_bthread_singleton(); + return NULL; +} + +// Singleton will definitely not cause deadlock, +// even if constructor of T will hang the bthread. +TEST(BthreadOnceTest, singleton) { + bthread_t bid; + ASSERT_EQ(0, bthread_start_background( + &bid, NULL, first_get_bthread_singleton, NULL)); + while (!g_bthread_started) { + bthread_usleep(1000); + } + ASSERT_NE(nullptr, bthread::g_task_control); + int concurrency = bthread::g_task_control->concurrency(); + LOG(INFO) << "concurrency: " << concurrency; + ASSERT_GT(concurrency, 0); + std::vector bids(concurrency * 100); + for (auto& id : bids) { + ASSERT_EQ(0, bthread_start_background( + &id, NULL, get_bthread_singleton, NULL)); + } + get_bthread_singleton(); + + for (auto& id : bids) { + bthread_join(id, NULL); + } + bthread_join(bid, NULL); +} + +} \ No newline at end of file diff --git a/test/bthread_ping_pong_unittest.cpp b/test/bthread_ping_pong_unittest.cpp new file mode 100644 index 0000000..76f559b --- /dev/null +++ b/test/bthread_ping_pong_unittest.cpp @@ -0,0 +1,221 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include +#include +#include "butil/compat.h" +#include "butil/time.h" +#include "butil/macros.h" +#include "butil/errno.h" +#include +#include +#include "bthread/bthread.h" +#include "butil/atomicops.h" + +namespace { +DEFINE_int32(thread_num, 1, "#pairs of threads doing ping pong"); +DEFINE_bool(loop, false, "run until ctrl-C is pressed"); +DEFINE_bool(use_futex, false, "use futex instead of pipe"); +DEFINE_bool(use_butex, false, "use butex instead of pipe"); + +void ALLOW_UNUSED (*ignore_sigpipe)(int) = signal(SIGPIPE, SIG_IGN); + +volatile bool stop = false; +void quit_handler(int) { + stop = true; +} + +struct BAIDU_CACHELINE_ALIGNMENT AlignedIntWrapper { + int value; +}; + +struct BAIDU_CACHELINE_ALIGNMENT PlayerArg { + int read_fd; + int write_fd; + int* wait_addr; + int* wake_addr; + long counter; + long wakeup; +}; + +void* pipe_player(void* void_arg) { + PlayerArg* arg = static_cast(void_arg); + char dummy = '\0'; + while (1) { + ssize_t nr = read(arg->read_fd, &dummy, 1); + if (nr <= 0) { + if (nr == 0) { + printf("[%" PRIu64 "] EOF\n", pthread_numeric_id()); + break; + } + if (errno != EINTR) { + printf("[%" PRIu64 "] bad read, %m\n", pthread_numeric_id()); + break; + } + continue; + } + if (1L != write(arg->write_fd, &dummy, 1)) { + printf("[%" PRIu64 "] bad write, %m\n", pthread_numeric_id()); + break; + } + ++arg->counter; + } + return NULL; +} + +static const int INITIAL_FUTEX_VALUE = 0; + +void* futex_player(void* void_arg) { + PlayerArg* arg = static_cast(void_arg); + int counter = INITIAL_FUTEX_VALUE; + while (!stop) { + int rc = bthread::futex_wait_private(arg->wait_addr, counter, NULL); + ++counter; + ++*arg->wake_addr; + bthread::futex_wake_private(arg->wake_addr, 1); + ++arg->counter; + arg->wakeup += (rc == 0); + } + return NULL; +} + +void* butex_player(void* void_arg) { + PlayerArg* arg = static_cast(void_arg); + int counter = INITIAL_FUTEX_VALUE; + while (!stop) { + int rc = bthread::butex_wait(arg->wait_addr, counter, NULL); + ++counter; + ++*arg->wake_addr; + bthread::butex_wake(arg->wake_addr); + ++arg->counter; + arg->wakeup += (rc == 0); + } + return NULL; +} + +TEST(PingPongTest, ping_pong) { + signal(SIGINT, quit_handler); + stop = false; + PlayerArg* args[FLAGS_thread_num]; + + for (int i = 0; i < FLAGS_thread_num; ++i) { + int pipe1[2]; + int pipe2[2]; + if (!FLAGS_use_futex && !FLAGS_use_butex) { + ASSERT_EQ(0, pipe(pipe1)); + ASSERT_EQ(0, pipe(pipe2)); + } + + PlayerArg* arg1 = new PlayerArg; + if (!FLAGS_use_futex && !FLAGS_use_butex) { + arg1->read_fd = pipe1[0]; + arg1->write_fd = pipe2[1]; + } else if (FLAGS_use_futex) { + AlignedIntWrapper* w1 = new AlignedIntWrapper; + w1->value = INITIAL_FUTEX_VALUE; + AlignedIntWrapper* w2 = new AlignedIntWrapper; + w2->value = INITIAL_FUTEX_VALUE; + arg1->wait_addr = &w1->value; + arg1->wake_addr = &w2->value; + } else if (FLAGS_use_butex) { + arg1->wait_addr = bthread::butex_create_checked(); + *arg1->wait_addr = INITIAL_FUTEX_VALUE; + arg1->wake_addr = bthread::butex_create_checked(); + *arg1->wake_addr = INITIAL_FUTEX_VALUE; + } else { + ASSERT_TRUE(false); + } + arg1->counter = 0; + arg1->wakeup = 0; + args[i] = arg1; + + PlayerArg* arg2 = new PlayerArg; + if (!FLAGS_use_futex && !FLAGS_use_butex) { + arg2->read_fd = pipe2[0]; + arg2->write_fd = pipe1[1]; + } else { + arg2->wait_addr = arg1->wake_addr; + arg2->wake_addr = arg1->wait_addr; + } + arg2->counter = 0; + arg2->wakeup = 0; + + pthread_t th1, th2; + bthread_t bth1, bth2; + if (!FLAGS_use_futex && !FLAGS_use_butex) { + ASSERT_EQ(0, pthread_create(&th1, NULL, pipe_player, arg1)); + ASSERT_EQ(0, pthread_create(&th2, NULL, pipe_player, arg2)); + } else if (FLAGS_use_futex) { + ASSERT_EQ(0, pthread_create(&th1, NULL, futex_player, arg1)); + ASSERT_EQ(0, pthread_create(&th2, NULL, futex_player, arg2)); + } else if (FLAGS_use_butex) { + ASSERT_EQ(0, bthread_start_background(&bth1, NULL, butex_player, arg1)); + ASSERT_EQ(0, bthread_start_background(&bth2, NULL, butex_player, arg2)); + } else { + ASSERT_TRUE(false); + } + + if (!FLAGS_use_futex && !FLAGS_use_butex) { + // send the seed data. + unsigned char seed = 255; + ASSERT_EQ(1L, write(pipe1[1], &seed, 1)); + } else if (FLAGS_use_futex) { + ++*arg1->wait_addr; + bthread::futex_wake_private(arg1->wait_addr, 1); + } else if (FLAGS_use_butex) { + ++*arg1->wait_addr; + bthread::butex_wake(arg1->wait_addr); + } else { + ASSERT_TRUE(false); + } + } + + long last_counter = 0; + long last_wakeup = 0; + while (!stop) { + butil::Timer tm; + tm.start(); + sleep(1); + tm.stop(); + long cur_counter = 0; + long cur_wakeup = 0; + for (int i = 0; i < FLAGS_thread_num; ++i) { + cur_counter += args[i]->counter; + cur_wakeup += args[i]->wakeup; + } + if (FLAGS_use_futex || FLAGS_use_butex) { + printf("pingpong-ed %" PRId64 "/s, wakeup=%" PRId64 "/s\n", + (cur_counter - last_counter) * 1000L / tm.m_elapsed(), + (cur_wakeup - last_wakeup) * 1000L / tm.m_elapsed()); + } else { + printf("pingpong-ed %" PRId64 "/s\n", + (cur_counter - last_counter) * 1000L / tm.m_elapsed()); + } + last_counter = cur_counter; + last_wakeup = cur_wakeup; + if (!FLAGS_loop) { + break; + } + } + stop = true; + // Program quits, Let resource leak. +} +} // namespace diff --git a/test/bthread_priority_queue_unittest.cpp b/test/bthread_priority_queue_unittest.cpp new file mode 100644 index 0000000..d6c9e43 --- /dev/null +++ b/test/bthread_priority_queue_unittest.cpp @@ -0,0 +1,228 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include +#include +#include +#include "bthread/bthread.h" +#include "bthread/task_group.h" + +namespace { + +// Counter incremented by priority bthreads to verify execution +std::atomic g_priority_count(0); +// Mutex + set for collecting executed tids to verify no loss +std::mutex g_tid_mutex; +std::set g_executed_ids; + +void reset_globals() { + g_priority_count.store(0); + std::lock_guard lk(g_tid_mutex); + g_executed_ids.clear(); +} + +struct TaskArg { + int id; +}; + +void* priority_task_fn(void* arg) { + TaskArg* ta = static_cast(arg); + g_priority_count.fetch_add(1, std::memory_order_relaxed); + { + std::lock_guard lk(g_tid_mutex); + g_executed_ids.insert(ta->id); + } + delete ta; + return NULL; +} + +void* normal_task_fn(void* /*arg*/) { + // Just a normal task that does nothing, used as a filler + bthread_usleep(1000); + return NULL; +} + +class PriorityQueueTest : public ::testing::Test { +protected: + static void SetUpTestSuite() { + google::SetCommandLineOption("enable_bthread_priority_queue", "true"); + google::SetCommandLineOption("event_dispatcher_num", "4"); + } + void SetUp() override { + reset_globals(); + } +}; + +// Test 1: End-to-end priority task submission and execution. +// Multiple producers submit priority tasks, verify all tasks are executed. +TEST_F(PriorityQueueTest, e2e_priority_tasks_all_executed) { + const int N = 200; + + bthread_attr_t attr = BTHREAD_ATTR_NORMAL; + attr.flags |= BTHREAD_GLOBAL_PRIORITY; + + std::vector tids(N); + for (int i = 0; i < N; ++i) { + TaskArg* arg = new TaskArg{i}; + ASSERT_EQ(0, bthread_start_background(&tids[i], &attr, + priority_task_fn, arg)); + } + + for (int i = 0; i < N; ++i) { + bthread_join(tids[i], NULL); + } + + ASSERT_EQ(N, g_priority_count.load()); + std::lock_guard lk(g_tid_mutex); + ASSERT_EQ((size_t)N, g_executed_ids.size()); + for (int i = 0; i < N; ++i) { + ASSERT_TRUE(g_executed_ids.count(i)) << "Missing task id=" << i; + } +} + +// Test 2: Mix of priority and normal tasks, all complete correctly. +TEST_F(PriorityQueueTest, mixed_priority_and_normal_tasks) { + const int N_PRIORITY = 100; + const int N_NORMAL = 100; + + bthread_attr_t priority_attr = BTHREAD_ATTR_NORMAL; + priority_attr.flags |= BTHREAD_GLOBAL_PRIORITY; + + std::vector tids; + tids.reserve(N_PRIORITY + N_NORMAL); + + for (int i = 0; i < N_PRIORITY + N_NORMAL; ++i) { + bthread_t tid; + if (i % 2 == 0 && (i / 2) < N_PRIORITY) { + TaskArg* arg = new TaskArg{i / 2}; + ASSERT_EQ(0, bthread_start_background(&tid, &priority_attr, + priority_task_fn, arg)); + } else { + ASSERT_EQ(0, bthread_start_background(&tid, NULL, + normal_task_fn, NULL)); + } + tids.push_back(tid); + } + + for (auto tid : tids) { + bthread_join(tid, NULL); + } + + ASSERT_EQ(N_PRIORITY, g_priority_count.load()); +} + +// Test 3: start_foreground (bthread_start_urgent) with GLOBAL_PRIORITY. +// Simulates ED calling StartInputEvent: ED bthread calls start_urgent, +// gets preempted into PQ via priority_to_run, child runs and ends, +// ending_sched steals ED from PQ to resume. +TEST_F(PriorityQueueTest, start_foreground_priority_to_run) { + const int N = 200; + + struct EDSimArg { + int n_tasks; + }; + EDSimArg ed_arg{N}; + + auto ed_fn = [](void* arg) -> void* { + EDSimArg* ea = static_cast(arg); + bthread::TaskMeta* meta = + bthread::TaskGroup::address_meta(bthread_self()); + meta->priority_index = 0; + + for (int i = 0; i < ea->n_tasks; ++i) { + TaskArg* ta = new TaskArg{i}; + bthread_t child; + bthread_start_urgent(&child, NULL, priority_task_fn, ta); + } + return NULL; + }; + + bthread_attr_t priority_attr = BTHREAD_ATTR_NORMAL; + priority_attr.flags |= BTHREAD_GLOBAL_PRIORITY; + + bthread_t ed_tid; + ASSERT_EQ(0, bthread_start_background(&ed_tid, &priority_attr, + ed_fn, &ed_arg)); + bthread_join(ed_tid, NULL); + + ASSERT_EQ(N, g_priority_count.load()); + std::lock_guard lk(g_tid_mutex); + ASSERT_EQ((size_t)N, g_executed_ids.size()); +} + +// Test 4: Multiple ED-like bthreads concurrently calling start_urgent. +// Verifies PQ correctness under concurrent preemption from multiple EDs. +TEST_F(PriorityQueueTest, multiple_eds_concurrent_preempt) { + const int NUM_EDS = 4; + const int TASKS_PER_ED = 50; + const int TOTAL = NUM_EDS * TASKS_PER_ED; + std::atomic resume_count(0); + + struct EDArg { + int ed_index; + int n_children; + std::atomic* resume_count; + }; + + auto ed_fn = [](void* arg) -> void* { + EDArg* ea = static_cast(arg); + bthread::TaskMeta* meta = + bthread::TaskGroup::address_meta(bthread_self()); + meta->priority_index = ea->ed_index; + + std::vector children; + children.reserve(ea->n_children); + for (int i = 0; i < ea->n_children; ++i) { + int id = ea->ed_index * ea->n_children + i; + TaskArg* ta = new TaskArg{id}; + bthread_t child; + bthread_start_urgent(&child, NULL, priority_task_fn, ta); + children.push_back(child); + ea->resume_count->fetch_add(1, std::memory_order_relaxed); + } + for (auto c : children) { + bthread_join(c, NULL); + } + return NULL; + }; + + bthread_attr_t priority_attr = BTHREAD_ATTR_NORMAL; + priority_attr.flags |= BTHREAD_GLOBAL_PRIORITY; + + std::vector ed_args(NUM_EDS); + std::vector ed_tids(NUM_EDS); + for (int i = 0; i < NUM_EDS; ++i) { + ed_args[i] = {i, TASKS_PER_ED, &resume_count}; + ASSERT_EQ(0, bthread_start_background(&ed_tids[i], &priority_attr, + ed_fn, &ed_args[i])); + } + + for (int i = 0; i < NUM_EDS; ++i) { + bthread_join(ed_tids[i], NULL); + } + + ASSERT_EQ(TOTAL, g_priority_count.load()); + ASSERT_EQ(TOTAL, resume_count.load()); + std::lock_guard lk(g_tid_mutex); + ASSERT_EQ((size_t)TOTAL, g_executed_ids.size()); +} + +} // namespace diff --git a/test/bthread_rwlock_unittest.cpp b/test/bthread_rwlock_unittest.cpp new file mode 100644 index 0000000..9a88051 --- /dev/null +++ b/test/bthread_rwlock_unittest.cpp @@ -0,0 +1,708 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "gperftools_helper.h" +#include "butil/atomicops.h" +#include + +namespace { + +long start_time = butil::cpuwide_time_ms(); +int c = 0; +void* rdlocker(void* arg) { + auto rw = (bthread_rwlock_t*)arg; + bthread_rwlock_rdlock(rw); + LOG(INFO) << butil::string_printf("[%" PRIu64 "] I'm rdlocker, %d, %" PRId64 "ms\n", + pthread_numeric_id(), ++c, + butil::cpuwide_time_ms() - start_time); + bthread_usleep(10000); + bthread_rwlock_unlock(rw); + return NULL; +} + +void* wrlocker(void* arg) { + auto rw = (bthread_rwlock_t*)arg; + bthread_rwlock_wrlock(rw); + LOG(INFO) << butil::string_printf("[%" PRIu64 "] I'm wrlocker, %d, %" PRId64 "ms\n", + pthread_numeric_id(), ++c, + butil::cpuwide_time_ms() - start_time); + bthread_usleep(10000); + bthread_rwlock_unlock(rw); + return NULL; +} + +TEST(RWLockTest, sanity) { + bthread_rwlock_t rw; + ASSERT_EQ(0, bthread_rwlock_init(&rw, NULL)); + ASSERT_EQ(0, bthread_rwlock_rdlock(&rw)); + ASSERT_EQ(0, bthread_rwlock_unlock(&rw)); + ASSERT_EQ(0, bthread_rwlock_wrlock(&rw)); + ASSERT_EQ(0, bthread_rwlock_unlock(&rw)); + + bthread_t rdth; + bthread_t rwth; + ASSERT_EQ(0, bthread_start_urgent(&rdth, NULL, rdlocker, &rw)); + ASSERT_EQ(0, bthread_start_urgent(&rwth, NULL, wrlocker, &rw)); + + ASSERT_EQ(0, bthread_join(rdth, NULL)); + ASSERT_EQ(0, bthread_join(rwth, NULL)); + ASSERT_EQ(0, bthread_rwlock_destroy(&rw)); +} + +TEST(RWLockTest, used_in_pthread) { + bthread_rwlock_t rw; + ASSERT_EQ(0, bthread_rwlock_init(&rw, NULL)); + pthread_t rdth[8]; + pthread_t wrth[8]; + for (size_t i = 0; i < ARRAY_SIZE(rdth); ++i) { + ASSERT_EQ(0, pthread_create(&rdth[i], NULL, rdlocker, &rw)); + } + for (size_t i = 0; i < ARRAY_SIZE(wrth); ++i) { + ASSERT_EQ(0, pthread_create(&wrth[i], NULL, wrlocker, &rw)); + } + + for (size_t i = 0; i < ARRAY_SIZE(rdth); ++i) { + pthread_join(rdth[i], NULL); + } + for (size_t i = 0; i < ARRAY_SIZE(rdth); ++i) { + pthread_join(wrth[i], NULL); + } + ASSERT_EQ(0, bthread_rwlock_destroy(&rw)); +} + +void* do_timedrdlock(void *arg) { + struct timespec t = { -2, 0 }; + EXPECT_EQ(ETIMEDOUT, bthread_rwlock_timedrdlock((bthread_rwlock_t*)arg, &t)); + return NULL; +} + +void* do_timedwrlock(void *arg) { + struct timespec t = { -2, 0 }; + EXPECT_EQ(ETIMEDOUT, bthread_rwlock_timedwrlock((bthread_rwlock_t*)arg, &t)); + LOG(INFO) << 10; + return NULL; +} + +TEST(RWLockTest, timedlock) { + bthread_rwlock_t rw; + ASSERT_EQ(0, bthread_rwlock_init(&rw, NULL)); + + ASSERT_EQ(0, bthread_rwlock_rdlock(&rw)); + bthread_t th; + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, do_timedwrlock, &rw)); + ASSERT_EQ(0, bthread_join(th, NULL)); + ASSERT_EQ(0, bthread_rwlock_unlock(&rw)); + + ASSERT_EQ(0, bthread_rwlock_wrlock(&rw)); + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, do_timedwrlock, &rw)); + ASSERT_EQ(0, bthread_join(th, NULL)); + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, do_timedrdlock, &rw)); + ASSERT_EQ(0, bthread_join(th, NULL)); + ASSERT_EQ(0, bthread_rwlock_unlock(&rw)); + ASSERT_EQ(0, bthread_rwlock_destroy(&rw)); +} + +struct TrylockArgs { + bthread_rwlock_t* rw; + int rc; +}; + +void* do_tryrdlock(void *arg) { + auto trylock_args = (TrylockArgs*)arg; + EXPECT_EQ(trylock_args->rc, bthread_rwlock_tryrdlock(trylock_args->rw)); + if (0 != trylock_args->rc) { + return NULL; + } + EXPECT_EQ(trylock_args->rc, bthread_rwlock_unlock(trylock_args->rw)); + return NULL; +} + +void* do_trywrlock(void *arg) { + auto trylock_args = (TrylockArgs*)arg; + EXPECT_EQ(trylock_args->rc, bthread_rwlock_trywrlock(trylock_args->rw)); + if (0 != trylock_args->rc) { + return NULL; + } + EXPECT_EQ(trylock_args->rc, bthread_rwlock_unlock(trylock_args->rw)); + return NULL; +} + +TEST(RWLockTest, trylock) { + bthread_rwlock_t rw; + ASSERT_EQ(0, bthread_rwlock_init(&rw, NULL)); + + ASSERT_EQ(0, bthread_rwlock_tryrdlock(&rw)); + ASSERT_EQ(0, bthread_rwlock_unlock(&rw)); + ASSERT_EQ(0, bthread_rwlock_rdlock(&rw)); + bthread_t th; + TrylockArgs args{&rw, 0}; + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, do_tryrdlock, &args)); + ASSERT_EQ(0, bthread_join(th, NULL)); + args.rc = EBUSY; + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, do_trywrlock, &args)); + ASSERT_EQ(0, bthread_join(th, NULL)); + ASSERT_EQ(0, bthread_rwlock_unlock(&rw)); + + ASSERT_EQ(0, bthread_rwlock_trywrlock(&rw)); + ASSERT_EQ(0, bthread_rwlock_unlock(&rw)); + ASSERT_EQ(0, bthread_rwlock_wrlock(&rw)); + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, do_tryrdlock, &args)); + ASSERT_EQ(0, bthread_join(th, NULL)); + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, do_trywrlock, &args)); + ASSERT_EQ(0, bthread_join(th, NULL)); + ASSERT_EQ(0, bthread_rwlock_unlock(&rw)); + + ASSERT_EQ(0, bthread_rwlock_destroy(&rw)); +} + +TEST(RWLockTest, cpp_wrapper) { + bthread::RWLock rw; + ASSERT_TRUE(rw.try_rdlock()); + rw.unlock(); + rw.rdlock(); + rw.unlock(); + ASSERT_TRUE(rw.try_wrlock()); + rw.unlock(); + rw.wrlock(); + rw.unlock(); + + struct timespec t = { -2, 0 }; + ASSERT_TRUE(rw.timed_rdlock(&t)); + rw.unlock(); + ASSERT_TRUE(rw.timed_wrlock(&t)); + rw.unlock(); + + { + bthread::RWLockRdGuard guard(rw); + } + { + bthread::RWLockWrGuard guard(rw); + } + { + std::lock_guard guard(rw, true); + } + { + std::lock_guard guard(rw, false); + } + { + std::lock_guard guard(*rw.native_handler(), true); + } + { + std::lock_guard guard(*rw.native_handler(), false); + } +} + +bool g_started = false; +bool g_stopped = false; + +void read_op(bthread_rwlock_t* rw, int64_t sleep_us) { + ASSERT_EQ(0, bthread_rwlock_rdlock(rw)); + if (0 != sleep_us) { + bthread_usleep(sleep_us); + } + ASSERT_EQ(0, bthread_rwlock_unlock(rw)); +} + +void write_op(bthread_rwlock_t* rw, int64_t sleep_us) { + ASSERT_EQ(0, bthread_rwlock_wrlock(rw)); + if (0 != sleep_us) { + bthread_usleep(sleep_us); + } + ASSERT_EQ(0, bthread_rwlock_unlock(rw)); +} + +typedef void (*OP)(bthread_rwlock_t* rw, int64_t sleep_us); + +struct MixThreadArg { + bthread_rwlock_t* rw; + OP op; +}; + +void* loop_until_stopped(void* arg) { + auto args = (MixThreadArg*)arg; + while (!g_stopped) { + args->op(args->rw, 20); + } + return NULL; +} + +TEST(RWLockTest, mix_thread_types) { + g_stopped = false; + bthread_rwlock_t rw; + ASSERT_EQ(0, bthread_rwlock_init(&rw, NULL)); + + const int N = 16; + const int M = N * 2; + pthread_t pthreads[N]; + bthread_t bthreads[M]; + // reserve enough workers for test. This is a must since we have + // BTHREAD_ATTR_PTHREAD bthreads which may cause deadlocks (the + // bhtread_usleep below can't be scheduled and g_stopped is never + // true, thus loop_until_stopped spins forever) + bthread_setconcurrency(M); + std::vector args; + args.reserve(N + M); + for (int i = 0; i < N; ++i) { + if (i % 2 == 0) { + args.push_back({&rw, read_op}); + } else { + args.push_back({&rw, write_op}); + } + ASSERT_EQ(0, pthread_create(&pthreads[i], NULL, loop_until_stopped, &args.back())); + } + + for (int i = 0; i < M; ++i) { + if (i % 2 == 0) { + args.push_back({&rw, read_op}); + } else { + args.push_back({&rw, write_op}); + } + const bthread_attr_t* attr = i % 2 ? NULL : &BTHREAD_ATTR_PTHREAD; + ASSERT_EQ(0, bthread_start_urgent(&bthreads[i], attr, loop_until_stopped, &args.back())); + } + bthread_usleep(1000L * 1000); + g_stopped = true; + for (int i = 0; i < M; ++i) { + bthread_join(bthreads[i], NULL); + } + for (int i = 0; i < N; ++i) { + pthread_join(pthreads[i], NULL); + } + + ASSERT_EQ(0, bthread_rwlock_destroy(&rw)); +} + +// Tests below verify the writer-priority semantics and the cleanup path +// guarded by the design notes in bthread/rwlock.cpp. +struct WriterPriorityArgs { + bthread_rwlock_t* rw; + butil::atomic* order; + int my_order; // sequence number captured inside the critical section + int hold_us; +}; + +void* wp_writer_fn(void* arg) { + auto* a = (WriterPriorityArgs*)arg; + EXPECT_EQ(0, bthread_rwlock_wrlock(a->rw)); + a->my_order = a->order->fetch_add(1, butil::memory_order_relaxed); + bthread_usleep(a->hold_us); + EXPECT_EQ(0, bthread_rwlock_unlock(a->rw)); + return NULL; +} + +void* wp_reader_fn(void* arg) { + auto* a = (WriterPriorityArgs*)arg; + EXPECT_EQ(0, bthread_rwlock_rdlock(a->rw)); + a->my_order = a->order->fetch_add(1, butil::memory_order_relaxed); + bthread_usleep(a->hold_us); + EXPECT_EQ(0, bthread_rwlock_unlock(a->rw)); + return NULL; +} + +// Verifies the writer-priority invariant guarded by the order +// "unlock writer_queue_mutex BEFORE fetch_sub(writer_wait_count)" in +// rwlock_unwrlock(): once a writer is queued, any new reader arriving +// later MUST yield to that writer. +TEST(RWLockTest, writer_priority) { + bthread_setconcurrency(8); + bthread_rwlock_t rw; + ASSERT_EQ(0, bthread_rwlock_init(&rw, NULL)); + + // (1) Main thread holds the read lock first. + ASSERT_EQ(0, bthread_rwlock_rdlock(&rw)); + + butil::atomic order(0); + WriterPriorityArgs warg {&rw, &order, -1, 5000}; + WriterPriorityArgs r2arg {&rw, &order, -1, 0}; + + // (2) Start a writer; it should park inside wrlock() because the read + // lock is held. Sleep long enough for it to fetch_add into + // writer_wait_count and reach the butex_wait on `lock_word'. + bthread_t wth; + ASSERT_EQ(0, bthread_start_urgent(&wth, NULL, wp_writer_fn, &warg)); + bthread_usleep(50 * 1000); + + // (3) Now spawn a fresh reader. By writer-priority it MUST observe + // writer_wait_count > 0 and park on it (NOT join the active read + // lock). + bthread_t r2th; + ASSERT_EQ(0, bthread_start_urgent(&r2th, NULL, wp_reader_fn, &r2arg)); + bthread_usleep(50 * 1000); + + // (4) Release the original read lock. The writer should win the race + // and complete BEFORE the queued reader. + ASSERT_EQ(0, bthread_rwlock_unlock(&rw)); + + bthread_join(wth, NULL); + bthread_join(r2th, NULL); + + EXPECT_GE(warg.my_order, 0); + EXPECT_GE(r2arg.my_order, 0); + EXPECT_LT(warg.my_order, r2arg.my_order) + << "Writer-priority violated: writer entered with order=" + << warg.my_order << " but late reader entered with order=" + << r2arg.my_order; + + ASSERT_EQ(0, bthread_rwlock_destroy(&rw)); +} + +void* wp_timed_wrlock_short(void* arg) { + auto* rw = (bthread_rwlock_t*)arg; + timespec ts = butil::milliseconds_from_now(50); + EXPECT_EQ(ETIMEDOUT, bthread_rwlock_timedwrlock(rw, &ts)); + return NULL; +} + +// Verifies the cleanup path of rwlock_wrlock_cleanup(): after multiple +// writers fail with ETIMEDOUT, writer_wait_count must be back to 0 so +// that subsequent readers are not blocked by leftover "ghost shares". +TEST(RWLockTest, wrlock_failure_does_not_leak_writer_count) { + bthread_setconcurrency(8); + bthread_rwlock_t rw; + ASSERT_EQ(0, bthread_rwlock_init(&rw, NULL)); + + // Hold the read lock so every wrlock attempt must block on `lock_word'. + ASSERT_EQ(0, bthread_rwlock_rdlock(&rw)); + + const int N = 8; + bthread_t wth[N]; + for (int i = 0; i < N; ++i) { + ASSERT_EQ(0, bthread_start_urgent(&wth[i], NULL, wp_timed_wrlock_short, &rw)); + } + // Wait for all timed wrlock attempts to time out and run cleanup. + for (int i = 0; i < N; ++i) { + bthread_join(wth[i], NULL); + } + + // Release the read lock; from this point on no writer is in flight, + // so a new reader MUST acquire the lock immediately. + ASSERT_EQ(0, bthread_rwlock_unlock(&rw)); + + timespec ts = butil::milliseconds_from_now(500); + butil::Timer t; + t.start(); + ASSERT_EQ(0, bthread_rwlock_timedrdlock(&rw, &ts)); + t.stop(); + EXPECT_LT(t.m_elapsed(), 100) + << "Reader was blocked for " << t.m_elapsed() << "ms; " + << "writer_wait_count was likely leaked by the cleanup path."; + + ASSERT_EQ(0, bthread_rwlock_unlock(&rw)); + ASSERT_EQ(0, bthread_rwlock_destroy(&rw)); +} + +struct DataConsistencyArgs { + bthread_rwlock_t* rw; + int64_t* shared; // protected by rw + int64_t local_inc; // writer: number of increments this thread did + int64_t observed_max; // reader: max value observed + bool is_writer; +}; + +void* dc_worker(void* arg) { + auto* a = (DataConsistencyArgs*)arg; + while (!g_stopped) { + if (a->is_writer) { + EXPECT_EQ(0, bthread_rwlock_wrlock(a->rw)); + ++(*a->shared); + ++a->local_inc; + EXPECT_EQ(0, bthread_rwlock_unlock(a->rw)); + } else { + EXPECT_EQ(0, bthread_rwlock_rdlock(a->rw)); + int64_t v = *a->shared; + if (v > a->observed_max) { + a->observed_max = v; + } + EXPECT_EQ(0, bthread_rwlock_unlock(a->rw)); + } + } + return NULL; +} + +// Verifies the release/acquire memory ordering pair on `lock_word'. +// If the CAS in unwrlock()/unrdlock() weren't release-ordered, or the +// CAS in rdlock()/wrlock() weren't acquire-ordered, writes done inside +// the critical section could appear lost or inconsistent to other +// threads, causing the final counter to disagree with total writer ops. +TEST(RWLockTest, data_consistency) { + bthread_rwlock_t rw; + ASSERT_EQ(0, bthread_rwlock_init(&rw, NULL)); + + g_stopped = false; + const int W = 4; + const int R = 8; + bthread_setconcurrency(W + R + 4); + + int64_t shared = 0; + std::vector args(W + R); + std::vector threads(W + R); + for (int i = 0; i < W + R; ++i) { + args[i].rw = &rw; + args[i].shared = &shared; + args[i].local_inc = 0; + args[i].observed_max = -1; + args[i].is_writer = (i < W); + ASSERT_EQ(0, bthread_start_urgent(&threads[i], NULL, dc_worker, &args[i])); + } + + bthread_usleep(500 * 1000); + g_stopped = true; + + int64_t total_inc = 0; + for (int i = 0; i < W + R; ++i) { + bthread_join(threads[i], NULL); + if (args[i].is_writer) { + total_inc += args[i].local_inc; + } + } + + // No lost updates: every writer's increment is reflected in `shared'. + EXPECT_EQ(total_inc, shared) + << "Lost updates: total writer ops=" << total_inc + << " but shared counter=" << shared; + // No reader saw a value greater than the final counter. + for (int i = W; i < W + R; ++i) { + EXPECT_LE(args[i].observed_max, shared) + << "Reader " << i << " observed_max=" << args[i].observed_max + << " > final shared=" << shared; + } + + ASSERT_EQ(0, bthread_rwlock_destroy(&rw)); +} + +void* ws_reader_loop(void* arg) { + auto* rw = (bthread_rwlock_t*)arg; + while (!g_stopped) { + EXPECT_EQ(0, bthread_rwlock_rdlock(rw)); + // Hold the read lock briefly to keep the lock continuously busy. + bthread_usleep(100); + EXPECT_EQ(0, bthread_rwlock_unlock(rw)); + } + return NULL; +} + +// Verifies that under a continuous read load, a writer can still acquire +// the lock in bounded time. This is the end-to-end guarantee of the +// writer-priority strategy: any reader arriving AFTER the writer entered +// wrlock() must yield, ensuring the writer never starves. +TEST(RWLockTest, no_writer_starvation) { + bthread_rwlock_t rw; + ASSERT_EQ(0, bthread_rwlock_init(&rw, NULL)); + + g_stopped = false; + const int R = 16; + bthread_setconcurrency(R + 4); + bthread_t rth[R]; + for (int i = 0; i < R; ++i) { + ASSERT_EQ(0, bthread_start_urgent(&rth[i], NULL, ws_reader_loop, &rw)); + } + + // Let the readers ramp up and saturate the lock. + bthread_usleep(50 * 1000); + + // A single writer must succeed within a generous budget. + butil::Timer t; + t.start(); + ASSERT_EQ(0, bthread_rwlock_wrlock(&rw)); + t.stop(); + + EXPECT_LT(t.m_elapsed(), 1000) + << "Writer starved for " << t.m_elapsed() << "ms under " + << R << " concurrent readers; writer-priority is broken."; + + ASSERT_EQ(0, bthread_rwlock_unlock(&rw)); + + g_stopped = true; + for (int i = 0; i < R; ++i) { + bthread_join(rth[i], NULL); + } + ASSERT_EQ(0, bthread_rwlock_destroy(&rw)); +} + +struct BAIDU_CACHELINE_ALIGNMENT PerfArgs { + bthread_rwlock_t* rw; + int64_t counter; + int64_t elapse_ns; + bool ready; + + PerfArgs() : rw(NULL), counter(0), elapse_ns(0), ready(false) {} +}; + +template +void* add_with_mutex(void* void_arg) { + auto args = (PerfArgs*)void_arg; + args->ready = true; + butil::Timer t; + while (!g_stopped) { + if (g_started) { + break; + } + bthread_usleep(10); + } + t.start(); + while (!g_stopped) { + if (Reader) { + bthread_rwlock_rdlock(args->rw); + } else { + bthread_rwlock_wrlock(args->rw); + } + ++args->counter; + bthread_rwlock_unlock(args->rw); + } + t.stop(); + args->elapse_ns = t.n_elapsed(); + return NULL; +} + +int g_prof_name_counter = 0; + +template +void PerfTest(uint32_t writer_ratio, ThreadId* /*dummy*/, int thread_num, + const ThreadCreateFn& create_fn, const ThreadJoinFn& join_fn) { + ASSERT_LE(writer_ratio, 100U); + + g_started = false; + g_stopped = false; + bthread_setconcurrency(thread_num + 4); + std::vector threads(thread_num); + std::vector args(thread_num); + bthread_rwlock_t rw; + bthread_rwlock_init(&rw, NULL); + int writer_num = thread_num * writer_ratio / 100; + int reader_num = thread_num - writer_num; + for (int i = 0; i < thread_num; ++i) { + args[i].rw = &rw; + if (i < writer_num) { + ASSERT_EQ(0, create_fn(&threads[i], NULL, add_with_mutex, &args[i])); + } else { + ASSERT_EQ(0, create_fn(&threads[i], NULL, add_with_mutex, &args[i])); + } + } + while (true) { + bool all_ready = true; + for (int i = 0; i < thread_num; ++i) { + if (!args[i].ready) { + all_ready = false; + break; + } + } + if (all_ready) { + break; + } + usleep(1000); + } + g_started = true; + char prof_name[32]; + snprintf(prof_name, sizeof(prof_name), "bthread_rwlock_perf_%d.prof", ++g_prof_name_counter); + ProfilerStart(prof_name); + usleep(1000 * 1000); + ProfilerStop(); + g_stopped = true; + + int64_t read_wait_time = 0; + int64_t read_count = 0; + int64_t write_wait_time = 0; + int64_t write_count = 0; + for (int i = 0; i < thread_num; ++i) { + ASSERT_EQ(0, join_fn(threads[i], NULL)); + if (i < writer_num) { + write_wait_time += args[i].elapse_ns; + write_count += args[i].counter; + } else { + read_wait_time += args[i].elapse_ns; + read_count += args[i].counter; + } + } + LOG(INFO) << "bthread rwlock in " + << ((void*)create_fn == (void*)pthread_create ? "pthread" : "bthread") + << " thread_num=" << thread_num + << " writer_ratio=" << writer_ratio + << " reader_num=" << reader_num + << " read_count=" << read_count + << " read_average_time=" << (read_count == 0 ? 0 : read_wait_time / (double)read_count) << "ns" + << " writer_num=" << writer_num + << " write_count=" << write_count + << " write_average_time=" << (write_count == 0 ? 0 : write_wait_time / (double)write_count) << "ns"; +} + +TEST(RWLockTest, performance) { + bthread_setconcurrency(16); + const int thread_num = 12; + PerfTest(0, (pthread_t*)NULL, thread_num, pthread_create, pthread_join); + PerfTest(0, (bthread_t*)NULL, thread_num, bthread_start_background, bthread_join); + PerfTest(10, (pthread_t*)NULL, thread_num, pthread_create, pthread_join); + PerfTest(20, (bthread_t*)NULL, thread_num, bthread_start_background, bthread_join); + PerfTest(100, (pthread_t*)NULL, thread_num, pthread_create, pthread_join); + PerfTest(100, (bthread_t*)NULL, thread_num, bthread_start_background, bthread_join); +} + + +void* read_thread(void* arg) { + const size_t N = 10000; +#ifdef CHECK_RWLOCK + pthread_rwlock_t* lock = (pthread_rwlock_t*)arg; +#else + pthread_mutex_t* lock = (pthread_mutex_t*)arg; +#endif + const long t1 = butil::cpuwide_time_ns(); + for (size_t i = 0; i < N; ++i) { +#ifdef CHECK_RWLOCK + pthread_rwlock_rdlock(lock); + pthread_rwlock_unlock(lock); +#else + pthread_mutex_lock(lock); + pthread_mutex_unlock(lock); +#endif + } + const long t2 = butil::cpuwide_time_ns(); + return new long((t2 - t1)/N); +} + +void* write_thread(void*) { + return NULL; +} + +TEST(RWLockTest, pthread_rdlock_performance) { +#ifdef CHECK_RWLOCK + pthread_rwlock_t lock1; + ASSERT_EQ(0, pthread_rwlock_init(&lock1, NULL)); +#else + pthread_mutex_t lock1; + ASSERT_EQ(0, pthread_mutex_init(&lock1, NULL)); +#endif + pthread_t rth[16]; + pthread_t wth; + for (size_t i = 0; i < ARRAY_SIZE(rth); ++i) { + ASSERT_EQ(0, pthread_create(&rth[i], NULL, read_thread, &lock1)); + } + ASSERT_EQ(0, pthread_create(&wth, NULL, write_thread, &lock1)); + + for (size_t i = 0; i < ARRAY_SIZE(rth); ++i) { + long* res = NULL; + pthread_join(rth[i], (void**)&res); + printf("read thread %lu = %ldns\n", i, *res); + delete res; + } + pthread_join(wth, NULL); +#ifdef CHECK_RWLOCK + pthread_rwlock_destroy(&lock1); +#else + pthread_mutex_destroy(&lock1); +#endif +} +} // namespace diff --git a/test/bthread_sched_yield_unittest.cpp b/test/bthread_sched_yield_unittest.cpp new file mode 100644 index 0000000..ac4e7e3 --- /dev/null +++ b/test/bthread_sched_yield_unittest.cpp @@ -0,0 +1,64 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include +#include + +namespace { +volatile bool stop = false; + +void* spinner(void*) { + long counter = 0; + for (; !stop; ++counter) { + cpu_relax(); + } + printf("spinned %ld\n", counter); + return NULL; +} + +void* yielder(void*) { + int counter = 0; + for (; !stop; ++counter) { + sched_yield(); + } + printf("sched_yield %d\n", counter); + return NULL; +} + +TEST(SchedYieldTest, sched_yield_when_all_core_busy) { + stop = false; + const int kNumCores = sysconf(_SC_NPROCESSORS_ONLN); + ASSERT_TRUE(kNumCores > 0); + pthread_t th0; + pthread_create(&th0, NULL, yielder, NULL); + + pthread_t th[kNumCores]; + for (int i = 0; i < kNumCores; ++i) { + pthread_create(&th[i], NULL, spinner, NULL); + } + sleep(1); + stop = true; + for (int i = 0; i < kNumCores; ++i) { + pthread_join(th[i], NULL); + } + pthread_join(th0, NULL); +} +} // namespace diff --git a/test/bthread_semaphore_unittest.cpp b/test/bthread_semaphore_unittest.cpp new file mode 100644 index 0000000..ef9e5e5 --- /dev/null +++ b/test/bthread_semaphore_unittest.cpp @@ -0,0 +1,207 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include + +namespace { + +const size_t SEM_COUNT = 10000; + +void* sem_waiter(void* arg) { + bthread_usleep(10 * 1000); + auto sem = (bthread_sem_t*)arg; + for (size_t i = 0; i < SEM_COUNT; ++i) { + bthread_sem_wait(sem); + } + return NULL; +} + +void* sem_poster(void* arg) { + bthread_usleep(10 * 1000); + auto sem = (bthread_sem_t*)arg; + for (size_t i = 0; i < SEM_COUNT; ++i) { + bthread_sem_post(sem); + } + return NULL; +} + +TEST(SemaphoreTest, sanity) { + bthread_sem_t sem; + ASSERT_EQ(0, bthread_sem_init(&sem, 1)); + ASSERT_EQ(0, bthread_sem_wait(&sem)); + ASSERT_EQ(0, bthread_sem_post(&sem)); + ASSERT_EQ(0, bthread_sem_wait(&sem)); + + bthread_t waiter_th; + bthread_t poster_th; + ASSERT_EQ(0, bthread_start_urgent(&waiter_th, NULL, sem_waiter, &sem)); + ASSERT_EQ(0, bthread_start_urgent(&poster_th, NULL, sem_poster, &sem)); + ASSERT_EQ(0, bthread_join(waiter_th, NULL)); + ASSERT_EQ(0, bthread_join(poster_th, NULL)); + + ASSERT_EQ(0, bthread_sem_destroy(&sem)); +} + + + +TEST(SemaphoreTest, used_in_pthread) { + bthread_sem_t sem; + ASSERT_EQ(0, bthread_sem_init(&sem, 0)); + + pthread_t waiter_th[8]; + pthread_t poster_th[8]; + for (auto& th : waiter_th) { + ASSERT_EQ(0, pthread_create(&th, NULL, sem_waiter, &sem)); + } + for (auto& th : poster_th) { + ASSERT_EQ(0, pthread_create(&th, NULL, sem_poster, &sem)); + } + for (auto& th : waiter_th) { + pthread_join(th, NULL); + } + for (auto& th : poster_th) { + pthread_join(th, NULL); + } + + ASSERT_EQ(0, bthread_sem_destroy(&sem)); +} + +void* do_timedwait(void *arg) { + struct timespec t = { -2, 0 }; + EXPECT_EQ(ETIMEDOUT, bthread_sem_timedwait((bthread_sem_t*)arg, &t)); + return NULL; +} + +TEST(SemaphoreTest, timedwait) { + bthread_sem_t sem; + ASSERT_EQ(0, bthread_sem_init(&sem, 0)); + bthread_t th; + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, do_timedwait, &sem)); + ASSERT_EQ(0, bthread_join(th, NULL)); + ASSERT_EQ(0, bthread_sem_destroy(&sem)); +} + + +struct TryWaitArgs { + bthread_sem_t* sem; + int rc; +}; + +void* do_trywait(void *arg) { + auto trylock_args = (TryWaitArgs*)arg; + EXPECT_EQ(trylock_args->rc, bthread_sem_trywait(trylock_args->sem)); + return NULL; +} + +TEST(SemaphoreTest, trywait) { + bthread_sem_t sem; + ASSERT_EQ(0, bthread_sem_init(&sem, 0)); + + ASSERT_EQ(EAGAIN, bthread_sem_trywait(&sem)); + ASSERT_EQ(0, bthread_sem_post(&sem)); + ASSERT_EQ(0, bthread_sem_trywait(&sem)); + ASSERT_EQ(EAGAIN, bthread_sem_trywait(&sem)); + + ASSERT_EQ(0, bthread_sem_post(&sem)); + bthread_t th; + TryWaitArgs args{ &sem, 0}; + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, do_trywait, &args)); + ASSERT_EQ(0, bthread_join(th, NULL)); + args.rc = EAGAIN; + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, do_trywait, &args)); + ASSERT_EQ(0, bthread_join(th, NULL)); + + ASSERT_EQ(0, bthread_sem_destroy(&sem)); +} + +bool g_stopped = false; + +void wait_op(bthread_sem_t* sem, int64_t sleep_us) { + ASSERT_EQ(0, bthread_sem_wait(sem)); + if (0 != sleep_us) { + bthread_usleep(sleep_us); + } +} + +void post_op(bthread_sem_t* rw, int64_t sleep_us) { + ASSERT_EQ(0, bthread_sem_post(rw)); + if (0 != sleep_us) { + bthread_usleep(sleep_us); + } +} + +typedef void (*OP)(bthread_sem_t* sem, int64_t sleep_us); + +struct MixThreadArg { + bthread_sem_t* sem; + OP op; +}; + +void* loop_until_stopped(void* arg) { + auto args = (MixThreadArg*)arg; + for (size_t i = 0; i < SEM_COUNT; ++i) { + args->op(args->sem, 20); + } + return NULL; +} + +TEST(SemaphoreTest, mix_thread_types) { + g_stopped = false; + bthread_sem_t sem; + ASSERT_EQ(0, bthread_sem_init(&sem, 0)); + + const int N = 16; + const int M = N * 2; + pthread_t pthreads[N]; + bthread_t bthreads[M]; + // reserve enough workers for test. This is a must since we have + // BTHREAD_ATTR_PTHREAD bthreads which may cause deadlocks (the + // bhtread_usleep below can't be scheduled and g_stopped is never + // true, thus loop_until_stopped spins forever) + bthread_setconcurrency(M); + std::vector args; + args.reserve(N + M); + for (int i = 0; i < N; ++i) { + if (i % 2 == 0) { + args.push_back({ &sem, wait_op }); + } else { + args.push_back({ &sem, post_op }); + } + ASSERT_EQ(0, pthread_create(&pthreads[i], NULL, loop_until_stopped, &args.back())); + } + + for (int i = 0; i < M; ++i) { + if (i % 2 == 0) { + args.push_back({ &sem, wait_op }); + } else { + args.push_back({ &sem, post_op }); + } + const bthread_attr_t* attr = i % 2 ? NULL : &BTHREAD_ATTR_PTHREAD; + ASSERT_EQ(0, bthread_start_urgent(&bthreads[i], attr, loop_until_stopped, &args.back())); + } + for (bthread_t bthread : bthreads) { + bthread_join(bthread, NULL); + } + for (pthread_t pthread : pthreads) { + pthread_join(pthread, NULL); + } + + ASSERT_EQ(0, bthread_sem_destroy(&sem)); +} + +} // namespace diff --git a/test/bthread_setconcurrency_unittest.cpp b/test/bthread_setconcurrency_unittest.cpp new file mode 100644 index 0000000..0843918 --- /dev/null +++ b/test/bthread_setconcurrency_unittest.cpp @@ -0,0 +1,225 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include "butil/atomicops.h" +#include "butil/time.h" +#include "butil/macros.h" +#include "butil/logging.h" +#include "butil/thread_local.h" +#include +#include "butil/logging.h" +#include "bthread/bthread.h" +#include "bthread/task_control.h" + +namespace bthread { + extern TaskControl* g_task_control; +} + +namespace { +void* dummy(void*) { + return NULL; +} + +TEST(BthreadTest, setconcurrency) { + ASSERT_EQ(8 + BTHREAD_EPOLL_THREAD_NUM, (size_t)bthread_getconcurrency()); + ASSERT_EQ(EINVAL, bthread_setconcurrency(BTHREAD_MIN_CONCURRENCY - 1)); + ASSERT_EQ(EINVAL, bthread_setconcurrency(0)); + ASSERT_EQ(EINVAL, bthread_setconcurrency(-1)); + ASSERT_EQ(EINVAL, bthread_setconcurrency(BTHREAD_MAX_CONCURRENCY + 1)); + ASSERT_EQ(0, bthread_setconcurrency(BTHREAD_MIN_CONCURRENCY)); + ASSERT_EQ(BTHREAD_MIN_CONCURRENCY, bthread_getconcurrency()); + ASSERT_EQ(0, bthread_setconcurrency(BTHREAD_MIN_CONCURRENCY + 1)); + ASSERT_EQ(BTHREAD_MIN_CONCURRENCY + 1, bthread_getconcurrency()); + ASSERT_EQ(0, bthread_setconcurrency(BTHREAD_MIN_CONCURRENCY)); // smaller value + bthread_t th; + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, dummy, NULL)); + ASSERT_EQ(BTHREAD_MIN_CONCURRENCY + 1, bthread_getconcurrency()); + ASSERT_EQ(0, bthread_setconcurrency(BTHREAD_MIN_CONCURRENCY + 5)); + ASSERT_EQ(BTHREAD_MIN_CONCURRENCY + 5, bthread_getconcurrency()); + ASSERT_EQ(EPERM, bthread_setconcurrency(BTHREAD_MIN_CONCURRENCY + 1)); + ASSERT_EQ(BTHREAD_MIN_CONCURRENCY + 5, bthread_getconcurrency()); +} + +static butil::atomic *odd; +static butil::atomic *even; + +static butil::atomic nbthreads(0); +static butil::atomic npthreads(0); +static BAIDU_THREAD_LOCAL bool counted = false; +static butil::atomic stop (false); + +static void *odd_thread(void *) { + nbthreads.fetch_add(1); + while (!stop) { + if (!counted) { + counted = true; + npthreads.fetch_add(1); + } + bthread::butex_wake_all(even); + bthread::butex_wait(odd, 0, NULL); + } + return NULL; +} + +static void *even_thread(void *) { + nbthreads.fetch_add(1); + while (!stop) { + if (!counted) { + counted = true; + npthreads.fetch_add(1); + } + bthread::butex_wake_all(odd); + bthread::butex_wait(even, 0, NULL); + } + return NULL; +} + +TEST(BthreadTest, setconcurrency_with_running_bthread) { + odd = bthread::butex_create_checked >(); + even = bthread::butex_create_checked >(); + ASSERT_TRUE(odd != NULL && even != NULL); + *odd = 0; + *even = 0; + std::vector tids; + const int N = 200; + for (int i = 0; i < N; ++i) { + bthread_t tid; + bthread_start_background(&tid, &BTHREAD_ATTR_SMALL, odd_thread, NULL); + tids.push_back(tid); + bthread_start_background(&tid, &BTHREAD_ATTR_SMALL, even_thread, NULL); + tids.push_back(tid); + } + for (int i = 100; i <= N; ++i) { + ASSERT_EQ(0, bthread_setconcurrency(i)); + ASSERT_EQ(i, bthread_getconcurrency()); + } + usleep(1000 * N); + *odd = 1; + *even = 1; + stop = true; + bthread::butex_wake_all(odd); + bthread::butex_wake_all(even); + for (size_t i = 0; i < tids.size(); ++i) { + bthread_join(tids[i], NULL); + } + LOG(INFO) << "All bthreads has quit"; + ASSERT_EQ(2*N, nbthreads); + // This is not necessarily true, not all workers need to run sth. + //ASSERT_EQ(N, npthreads); + LOG(INFO) << "Touched pthreads=" << npthreads; +} + +void* sleep_proc(void*) { + usleep(100000); + return NULL; +} + +void* add_concurrency_proc(void*) { + bthread_t tid; + bthread_start_background(&tid, &BTHREAD_ATTR_SMALL, sleep_proc, NULL); + bthread_join(tid, NULL); + return NULL; +} + +bool set_min_concurrency(int num) { + std::stringstream ss; + ss << num; + std::string ret = GFLAGS_NAMESPACE::SetCommandLineOption("bthread_min_concurrency", ss.str().c_str()); + return !ret.empty(); +} + +int get_min_concurrency() { + std::string ret; + GFLAGS_NAMESPACE::GetCommandLineOption("bthread_min_concurrency", &ret); + return atoi(ret.c_str()); +} + +TEST(BthreadTest, min_concurrency) { + ASSERT_EQ(1, set_min_concurrency(-1)); // set min success + ASSERT_EQ(1, set_min_concurrency(0)); // set min success + ASSERT_EQ(0, get_min_concurrency()); + int conn = bthread_getconcurrency(); + int add_conn = 50; + + ASSERT_EQ(0, set_min_concurrency(conn + 1)); // set min failed + ASSERT_EQ(0, get_min_concurrency()); + + ASSERT_EQ(1, set_min_concurrency(conn - 1)); // set min success + ASSERT_EQ(conn - 1, get_min_concurrency()); + + ASSERT_EQ(EINVAL, bthread_setconcurrency(conn - 2)); // set max failed + ASSERT_EQ(0, bthread_setconcurrency(conn + add_conn + 1)); // set max success + ASSERT_EQ(0, bthread_setconcurrency(conn + add_conn)); // set max success + ASSERT_EQ(conn + add_conn, bthread_getconcurrency()); + ASSERT_EQ(conn, bthread::g_task_control->concurrency()); + + ASSERT_EQ(1, set_min_concurrency(conn + 1)); // set min success + ASSERT_EQ(conn + 1, get_min_concurrency()); + ASSERT_EQ(conn + 1, bthread::g_task_control->concurrency()); + + std::vector tids; + for (int i = 0; i < conn; ++i) { + bthread_t tid; + bthread_start_background(&tid, &BTHREAD_ATTR_SMALL, sleep_proc, NULL); + tids.push_back(tid); + } + for (int i = 0; i < add_conn; ++i) { + bthread_t tid; + bthread_start_background(&tid, &BTHREAD_ATTR_SMALL, add_concurrency_proc, NULL); + tids.push_back(tid); + } + for (size_t i = 0; i < tids.size(); ++i) { + bthread_join(tids[i], NULL); + } + ASSERT_EQ(conn + add_conn, bthread_getconcurrency()); + ASSERT_EQ(conn + add_conn, bthread::g_task_control->concurrency()); +} + +int current_tag(int tag) { + std::stringstream ss; + ss << tag; + std::string ret = GFLAGS_NAMESPACE::SetCommandLineOption("bthread_current_tag", ss.str().c_str()); + return !(ret.empty()); +} + +TEST(BthreadTest, current_tag) { + ASSERT_EQ(false, current_tag(-2)); + ASSERT_EQ(true, current_tag(0)); + ASSERT_EQ(false, current_tag(1)); +} + +int concurrency_by_tag(int num) { + std::stringstream ss; + ss << num; + std::string ret = + GFLAGS_NAMESPACE::SetCommandLineOption("bthread_concurrency_by_tag", ss.str().c_str()); + return !(ret.empty()); +} + +TEST(BthreadTest, concurrency_by_tag) { + ASSERT_EQ(concurrency_by_tag(1), false); + auto con = bthread_getconcurrency(); + ASSERT_EQ(concurrency_by_tag(con), true); + ASSERT_EQ(concurrency_by_tag(con + 1), true); + ASSERT_EQ(bthread_getconcurrency(), con+1); + bthread_setconcurrency(con + 1); + ASSERT_EQ(concurrency_by_tag(con + 1), true); +} + +} // namespace diff --git a/test/bthread_timer_thread_unittest.cpp b/test/bthread_timer_thread_unittest.cpp new file mode 100644 index 0000000..9351fe4 --- /dev/null +++ b/test/bthread_timer_thread_unittest.cpp @@ -0,0 +1,257 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include "bthread/sys_futex.h" +#include "bthread/timer_thread.h" +#include "bthread/bthread.h" +#include "butil/logging.h" + +namespace { + +long timespec_diff_us(const timespec& ts1, const timespec& ts2) { + return (ts1.tv_sec - ts2.tv_sec) * 1000000L + + (ts1.tv_nsec - ts2.tv_nsec) / 1000; +} + +// A simple class, could keep track of the time when it is invoked. +class TimeKeeper { +public: + TimeKeeper(timespec run_time) + : _expect_run_time(run_time), _name(NULL), _sleep_ms(0) {} + TimeKeeper(timespec run_time, const char* name/*must be string constant*/) + : _expect_run_time(run_time), _name(name), _sleep_ms(0) {} + TimeKeeper(timespec run_time, const char* name/*must be string constant*/, + int sleep_ms) + : _expect_run_time(run_time), _name(name), _sleep_ms(sleep_ms) {} + + void schedule(bthread::TimerThread* timer_thread) { + _task_id = timer_thread->schedule( + TimeKeeper::routine, this, _expect_run_time); + } + + void run() + { + timespec current_time; + clock_gettime(CLOCK_REALTIME, ¤t_time); + if (_name) { + LOG(INFO) << "Run `" << _name << "' task_id=" << _task_id; + } else { + LOG(INFO) << "Run task_id=" << _task_id; + } + _run_times.push_back(current_time); + const int saved_sleep_ms = _sleep_ms; + if (saved_sleep_ms > 0) { + timespec timeout = butil::milliseconds_to_timespec(saved_sleep_ms); + bthread::futex_wait_private(&_sleep_ms, saved_sleep_ms, &timeout); + } + } + + void wakeup() { + if (_sleep_ms != 0) { + _sleep_ms = 0; + bthread::futex_wake_private(&_sleep_ms, 1); + } else { + LOG(ERROR) << "No need to wakeup " + << (_name ? _name : "") << " task_id=" << _task_id; + } + } + + // verify the first run is in specified time range. + void expect_first_run() + { + expect_first_run(_expect_run_time); + } + + void expect_first_run(timespec expect_run_time) + { + ASSERT_TRUE(!_run_times.empty()); + long diff = timespec_diff_us(_run_times[0], expect_run_time); + EXPECT_LE(labs(diff), 50000); + } + + void expect_not_run() { + EXPECT_TRUE(_run_times.empty()); + } + + static void routine(void *arg) + { + TimeKeeper* keeper = (TimeKeeper*)arg; + keeper->run(); + } + + timespec _expect_run_time; + bthread::TimerThread::TaskId _task_id; + +private: + const char* _name; + int _sleep_ms; + std::vector _run_times; +}; + +TEST(TimerThreadTest, RunTasks) { + bthread::TimerThread timer_thread; + ASSERT_EQ(0, timer_thread.start(NULL)); + + timespec _2s_later = butil::seconds_from_now(2); + TimeKeeper keeper1(_2s_later, "keeper1"); + keeper1.schedule(&timer_thread); + + TimeKeeper keeper2(_2s_later, "keeper2"); // same time with keeper1 + keeper2.schedule(&timer_thread); + + timespec _1s_later = butil::seconds_from_now(1); + TimeKeeper keeper3(_1s_later, "keeper3"); + keeper3.schedule(&timer_thread); + + timespec _10s_later = butil::seconds_from_now(10); + TimeKeeper keeper4(_10s_later, "keeper4"); + keeper4.schedule(&timer_thread); + + TimeKeeper keeper5(_10s_later, "keeper5"); + keeper5.schedule(&timer_thread); + + // sleep 1 second, and unschedule task2 + LOG(INFO) << "Sleep 1s"; + sleep(1); + timer_thread.unschedule(keeper2._task_id); + timer_thread.unschedule(keeper4._task_id); + + timespec old_time = { 0, 0 }; + TimeKeeper keeper6(old_time, "keeper6"); + keeper6.schedule(&timer_thread); + const timespec keeper6_addtime = butil::seconds_from_now(0); + + // sleep 10 seconds and stop. + LOG(INFO) << "Sleep 2s"; + sleep(2); + LOG(INFO) << "Stop timer_thread"; + butil::Timer tm; + tm.start(); + timer_thread.stop_and_join(); + tm.stop(); + ASSERT_LE(tm.m_elapsed(), 15); + + // verify all runs in expected time range. + keeper1.expect_first_run(); + keeper2.expect_not_run(); + keeper3.expect_first_run(); + keeper4.expect_not_run(); + keeper5.expect_not_run(); + keeper6.expect_first_run(keeper6_addtime); +} + +// If the scheduled time is before start time, then should run it +// immediately. +TEST(TimerThreadTest, start_after_schedule) { + bthread::TimerThread timer_thread; + timespec past_time = { 0, 0 }; + TimeKeeper keeper(past_time, "keeper1"); + keeper.schedule(&timer_thread); + ASSERT_EQ(bthread::TimerThread::INVALID_TASK_ID, keeper._task_id); + ASSERT_EQ(0, timer_thread.start(NULL)); + keeper.schedule(&timer_thread); + ASSERT_NE(bthread::TimerThread::INVALID_TASK_ID, keeper._task_id); + timespec current_time = butil::seconds_from_now(0); + sleep(1); // make sure timer thread start and run + timer_thread.stop_and_join(); + keeper.expect_first_run(current_time); +} + +class TestTask { +public: + TestTask(bthread::TimerThread* timer_thread, TimeKeeper* keeper1, + TimeKeeper* keeper2, int expected_unschedule_result) + : _timer_thread(timer_thread) + , _keeper1(keeper1) + , _keeper2(keeper2) + , _expected_unschedule_result(expected_unschedule_result) { + } + + void run() + { + clock_gettime(CLOCK_REALTIME, &_running_time); + EXPECT_EQ(_expected_unschedule_result, + _timer_thread->unschedule(_keeper1->_task_id)); + _keeper2->schedule(_timer_thread); + } + + static void routine(void* arg) + { + TestTask* task = (TestTask*)arg; + task->run(); + } + timespec _running_time; + +private: + bthread::TimerThread* _timer_thread; // not owned. + TimeKeeper* _keeper1; // not owned. + TimeKeeper* _keeper2; // not owned. + int _expected_unschedule_result; +}; + +// Perform schedule and unschedule inside a running task +TEST(TimerThreadTest, schedule_and_unschedule_in_task) { + bthread::TimerThread timer_thread; + timespec past_time = { 0, 0 }; + timespec future_time = { std::numeric_limits::max(), 0 }; + const timespec _500ms_after = butil::milliseconds_from_now(500); + + TimeKeeper keeper1(future_time, "keeper1"); + TimeKeeper keeper2(past_time, "keeper2"); + TimeKeeper keeper3(past_time, "keeper3"); + TimeKeeper keeper4(past_time, "keeper4"); + TimeKeeper keeper5(_500ms_after, "keeper5", 10000/*10s*/); + + ASSERT_EQ(0, timer_thread.start(NULL)); + keeper1.schedule(&timer_thread); // start keeper1 + keeper3.schedule(&timer_thread); // start keeper3 + timespec keeper3_addtime = butil::seconds_from_now(0); + keeper5.schedule(&timer_thread); // start keeper5 + sleep(1); // let keeper1/3/5 run + + TestTask test_task1(&timer_thread, &keeper1, &keeper2, 0); + timer_thread.schedule(TestTask::routine, &test_task1, past_time); + + TestTask test_task2(&timer_thread, &keeper3, &keeper4, -1); + timer_thread.schedule(TestTask::routine, &test_task2, past_time); + + sleep(1); + // test_task1/2 should be both blocked by keeper5. + keeper2.expect_not_run(); + keeper4.expect_not_run(); + + // unscheduling (running) keeper5 should have no effect and returns 1 + ASSERT_EQ(1, timer_thread.unschedule(keeper5._task_id)); + + // wake up keeper5 to let test_task1/2 run. + keeper5.wakeup(); + sleep(1); + + timer_thread.stop_and_join(); + timespec finish_time; + clock_gettime(CLOCK_REALTIME, &finish_time); + + keeper1.expect_not_run(); + keeper2.expect_first_run(test_task1._running_time); + keeper3.expect_first_run(keeper3_addtime); + keeper4.expect_first_run(test_task2._running_time); + keeper5.expect_first_run(); +} + +} // end namespace diff --git a/test/bthread_unittest.cpp b/test/bthread_unittest.cpp new file mode 100644 index 0000000..bd31a3c --- /dev/null +++ b/test/bthread_unittest.cpp @@ -0,0 +1,705 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include "butil/time.h" +#include "butil/macros.h" +#include "butil/logging.h" +#include "gperftools_helper.h" +#include "bthread/bthread.h" +#include "bthread/unstable.h" +#include "bthread/task_meta.h" + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + int rc = RUN_ALL_TESTS(); + return rc; +} + +namespace bthread { +extern __thread bthread::LocalStorage tls_bls; +#ifdef BRPC_BTHREAD_TRACER +extern std::string stack_trace(bthread_t tid); +#endif // BRPC_BTHREAD_TRACER +} + +namespace { +class BthreadTest : public ::testing::Test{ +protected: + BthreadTest(){ + const int kNumCores = sysconf(_SC_NPROCESSORS_ONLN); + if (kNumCores > 0) { + bthread_setconcurrency(kNumCores); + } + }; + virtual ~BthreadTest(){}; + virtual void SetUp() { + }; + virtual void TearDown() { + }; +}; + +TEST_F(BthreadTest, sizeof_task_meta) { + LOG(INFO) << "sizeof(TaskMeta)=" << sizeof(bthread::TaskMeta); +} + +void* unrelated_pthread(void*) { + LOG(INFO) << "I did not call any bthread function, " + "I should begin and end without any problem"; + return (void*)(intptr_t)1; +} + +TEST_F(BthreadTest, unrelated_pthread) { + pthread_t th; + ASSERT_EQ(0, pthread_create(&th, NULL, unrelated_pthread, NULL)); + void* ret = NULL; + ASSERT_EQ(0, pthread_join(th, &ret)); + ASSERT_EQ(1, (intptr_t)ret); +} + +TEST_F(BthreadTest, attr_init_and_destroy) { + bthread_attr_t attr; + ASSERT_EQ(0, bthread_attr_init(&attr)); + ASSERT_EQ(0, bthread_attr_destroy(&attr)); +} + +bthread_fcontext_t fcm; +bthread_fcontext_t fc; +typedef std::pair pair_t; +static void f(intptr_t param) { + pair_t* p = (pair_t*)param; + p = (pair_t*)bthread_jump_fcontext(&fc, fcm, (intptr_t)(p->first+p->second)); + bthread_jump_fcontext(&fc, fcm, (intptr_t)(p->first+p->second)); +} + +TEST_F(BthreadTest, context_sanity) { + fcm = NULL; + std::size_t size(8192); + void* sp = malloc(size); + + pair_t p(std::make_pair(2, 7)); + fc = bthread_make_fcontext((char*)sp + size, size, f); + + int res = (int)bthread_jump_fcontext(&fcm, fc, (intptr_t)&p); + std::cout << p.first << " + " << p.second << " == " << res << std::endl; + + p = std::make_pair(5, 6); + res = (int)bthread_jump_fcontext(&fcm, fc, (intptr_t)&p); + std::cout << p.first << " + " << p.second << " == " << res << std::endl; +} + +TEST_F(BthreadTest, call_bthread_functions_before_tls_created) { + ASSERT_EQ(0, bthread_usleep(1000)); + ASSERT_EQ(EINVAL, bthread_join(0, NULL)); + ASSERT_EQ(0UL, bthread_self()); +} + +butil::atomic start(false); +butil::atomic stop(false); + +void* sleep_for_awhile(void* arg) { + LOG(INFO) << "sleep_for_awhile(" << arg << ")"; + bthread_usleep(100000L); + LOG(INFO) << "sleep_for_awhile(" << arg << ") wakes up"; + return NULL; +} + +void* just_exit(void* arg) { + LOG(INFO) << "just_exit(" << arg << ")"; + bthread_exit(NULL); + EXPECT_TRUE(false) << "just_exit(" << arg << ") should never be here"; + return NULL; +} + +void* repeated_sleep(void* arg) { + start = true; + for (size_t i = 0; !stop; ++i) { + LOG(INFO) << "repeated_sleep(" << arg << ") i=" << i; + bthread_usleep(1000000L); + } + return NULL; +} + +void* spin_and_log(void* arg) { + start = true; + // This thread never yields CPU. + butil::EveryManyUS every_1s(1000000L); + size_t i = 0; + while (!stop) { + if (every_1s) { + LOG(INFO) << "spin_and_log(" << arg << ")=" << i++; + } + } + return NULL; +} + +void* do_nothing(void* arg) { + LOG(INFO) << "do_nothing(" << arg << ")"; + return NULL; +} + +void* launcher(void* arg) { + LOG(INFO) << "launcher(" << arg << ")"; + for (size_t i = 0; !stop; ++i) { + bthread_t th; + bthread_start_urgent(&th, NULL, do_nothing, (void*)i); + bthread_usleep(1000000L); + } + return NULL; +} + +void* stopper(void*) { + // Need this thread to set `stop' to true. Reason: If spin_and_log (which + // never yields CPU) is scheduled to main thread, main thread cannot get + // to run again. + bthread_usleep(5*1000000L); + LOG(INFO) << "about to stop"; + stop = true; + return NULL; +} + +void* misc(void* arg) { + LOG(INFO) << "misc(" << arg << ")"; + bthread_t th[8]; + EXPECT_EQ(0, bthread_start_urgent(&th[0], NULL, sleep_for_awhile, (void*)2)); + EXPECT_EQ(0, bthread_start_urgent(&th[1], NULL, just_exit, (void*)3)); + EXPECT_EQ(0, bthread_start_urgent(&th[2], NULL, repeated_sleep, (void*)4)); + EXPECT_EQ(0, bthread_start_urgent(&th[3], NULL, repeated_sleep, (void*)68)); + EXPECT_EQ(0, bthread_start_urgent(&th[4], NULL, spin_and_log, (void*)5)); + EXPECT_EQ(0, bthread_start_urgent(&th[5], NULL, spin_and_log, (void*)85)); + EXPECT_EQ(0, bthread_start_urgent(&th[6], NULL, launcher, (void*)6)); + EXPECT_EQ(0, bthread_start_urgent(&th[7], NULL, stopper, NULL)); + for (size_t i = 0; i < ARRAY_SIZE(th); ++i) { + EXPECT_EQ(0, bthread_join(th[i], NULL)); + } + return NULL; +} + +TEST_F(BthreadTest, sanity) { + LOG(INFO) << "main thread " << pthread_self(); + bthread_t th1; + ASSERT_EQ(0, bthread_start_urgent(&th1, NULL, misc, (void*)1)); + LOG(INFO) << "back to main thread " << th1 << " " << pthread_self(); + ASSERT_EQ(0, bthread_join(th1, NULL)); +} + +const size_t BT_SIZE = 64; +void *bt_array[BT_SIZE]; +int bt_cnt; + +int do_bt (void) { + bt_cnt = backtrace (bt_array, BT_SIZE); + return 56; +} + +int call_do_bt (void) { + return do_bt () + 1; +} + +void * tf (void*) { + if (call_do_bt () != 57) { + return (void *) 1L; + } + return NULL; +} + +TEST_F(BthreadTest, backtrace) { + bthread_t th; + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, tf, NULL)); + ASSERT_EQ(0, bthread_join (th, NULL)); + + char **text = backtrace_symbols (bt_array, bt_cnt); + ASSERT_TRUE(text); + for (int i = 0; i < bt_cnt; ++i) { + puts(text[i]); + } + free(text); +} + +void* show_self(void*) { + EXPECT_NE(0ul, bthread_self()); + LOG(INFO) << "bthread_self=" << bthread_self(); + return NULL; +} + +TEST_F(BthreadTest, bthread_self) { + ASSERT_EQ(0ul, bthread_self()); + bthread_t bth; + ASSERT_EQ(0, bthread_start_urgent(&bth, NULL, show_self, NULL)); + ASSERT_EQ(0, bthread_join(bth, NULL)); +} + +void* join_self(void*) { + EXPECT_EQ(EINVAL, bthread_join(bthread_self(), NULL)); + return NULL; +} + +TEST_F(BthreadTest, bthread_join) { + // Invalid tid + ASSERT_EQ(EINVAL, bthread_join(0, NULL)); + + // Unexisting tid + ASSERT_EQ(EINVAL, bthread_join((bthread_t)-1, NULL)); + + // Joining self + bthread_t th; + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, join_self, NULL)); +} + +void* change_errno(void* arg) { + errno = (intptr_t)arg; + return NULL; +} + +TEST_F(BthreadTest, errno_not_changed) { + bthread_t th; + errno = 1; + bthread_start_urgent(&th, NULL, change_errno, (void*)(intptr_t)2); + ASSERT_EQ(1, errno); +} + +static long sleep_in_adding_func = 0; + +void* adding_func(void* arg) { + butil::atomic* s = (butil::atomic*)arg; + if (sleep_in_adding_func > 0) { + long t1 = 0; + if (10000 == s->fetch_add(1)) { + t1 = butil::cpuwide_time_us(); + } + bthread_usleep(sleep_in_adding_func); + if (t1) { + LOG(INFO) << "elapse is " << butil::cpuwide_time_us() - t1 << "ns"; + } + } else { + s->fetch_add(1); + } + return NULL; +} + +TEST_F(BthreadTest, small_threads) { + for (size_t z = 0; z < 2; ++z) { + sleep_in_adding_func = (z ? 1 : 0); + char prof_name[32]; + if (sleep_in_adding_func) { + snprintf(prof_name, sizeof(prof_name), "smallthread.prof"); + } else { + snprintf(prof_name, sizeof(prof_name), "smallthread_nosleep.prof"); + } + + butil::atomic s(0); + size_t N = (sleep_in_adding_func ? 40000 : 100000); + std::vector th; + th.reserve(N); + butil::Timer tm; + for (size_t j = 0; j < 3; ++j) { + th.clear(); + if (j == 1) { + ProfilerStart(prof_name); + } + tm.start(); + for (size_t i = 0; i < N; ++i) { + bthread_t t1; + ASSERT_EQ(0, bthread_start_urgent( + &t1, &BTHREAD_ATTR_SMALL, adding_func, &s)); + th.push_back(t1); + } + tm.stop(); + if (j == 1) { + ProfilerStop(); + } + for (size_t i = 0; i < N; ++i) { + bthread_join(th[i], NULL); + } + LOG(INFO) << "[Round " << j + 1 << "] bthread_start_urgent takes " + << tm.n_elapsed()/N << "ns, sum=" << s; + ASSERT_EQ(N * (j + 1), (size_t)s); + + // Check uniqueness of th + std::sort(th.begin(), th.end()); + ASSERT_EQ(th.end(), std::unique(th.begin(), th.end())); + } + } +} + +void* bthread_starter(void* void_counter) { + std::vector ths; + while (!stop.load(butil::memory_order_relaxed)) { + bthread_t th; + EXPECT_EQ(0, bthread_start_urgent(&th, NULL, adding_func, void_counter)); + ths.push_back(th); + } + for (size_t i = 0; i < ths.size(); ++i) { + EXPECT_EQ(0, bthread_join(ths[i], NULL)); + } + return NULL; +} + +struct BAIDU_CACHELINE_ALIGNMENT AlignedCounter { + AlignedCounter() : value(0) {} + butil::atomic value; +}; + +TEST_F(BthreadTest, start_bthreads_frequently) { + sleep_in_adding_func = 0; + char prof_name[32]; + snprintf(prof_name, sizeof(prof_name), "start_bthreads_frequently.prof"); + const int con = bthread_getconcurrency(); + ASSERT_GT(con, 0); + AlignedCounter* counters = new AlignedCounter[con]; + bthread_t th[con]; + + std::cout << "Perf with different parameters..." << std::endl; + ProfilerStart(prof_name); + for (int cur_con = 1; cur_con <= con; ++cur_con) { + stop = false; + for (int i = 0; i < cur_con; ++i) { + counters[i].value = 0; + ASSERT_EQ(0, bthread_start_urgent( + &th[i], NULL, bthread_starter, &counters[i].value)); + } + butil::Timer tm; + tm.start(); + bthread_usleep(200000L); + stop = true; + for (int i = 0; i < cur_con; ++i) { + bthread_join(th[i], NULL); + } + tm.stop(); + size_t sum = 0; + for (int i = 0; i < cur_con; ++i) { + sum += counters[i].value * 1000 / tm.m_elapsed(); + } + std::cout << sum << ","; + } + std::cout << std::endl; + ProfilerStop(); + delete [] counters; +} + +void* log_start_latency(void* void_arg) { + butil::Timer* tm = static_cast(void_arg); + tm->stop(); + return NULL; +} + +TEST_F(BthreadTest, start_latency_when_high_idle) { + bool warmup = true; + long elp1 = 0; + long elp2 = 0; + int REP = 0; + for (int i = 0; i < 10000; ++i) { + butil::Timer tm; + tm.start(); + bthread_t th; + bthread_start_urgent(&th, NULL, log_start_latency, &tm); + bthread_join(th, NULL); + bthread_t th2; + butil::Timer tm2; + tm2.start(); + bthread_start_background(&th2, NULL, log_start_latency, &tm2); + bthread_join(th2, NULL); + if (!warmup) { + ++REP; + elp1 += tm.n_elapsed(); + elp2 += tm2.n_elapsed(); + } else if (i == 100) { + warmup = false; + } + } + LOG(INFO) << "start_urgent=" << elp1 / REP << "ns start_background=" + << elp2 / REP << "ns"; +} + +void* sleep_for_awhile_with_sleep(void* arg) { + bthread_usleep((intptr_t)arg); + return NULL; +} + +TEST_F(BthreadTest, stop_sleep) { + bthread_t th; + ASSERT_EQ(0, bthread_start_urgent( + &th, NULL, sleep_for_awhile_with_sleep, (void*)1000000L)); + butil::Timer tm; + tm.start(); + bthread_usleep(10000); + ASSERT_EQ(0, bthread_stop(th)); + ASSERT_EQ(0, bthread_join(th, NULL)); + tm.stop(); + ASSERT_LE(labs(tm.m_elapsed() - 10), 10); +} + +TEST_F(BthreadTest, bthread_exit) { + bthread_t th1; + bthread_t th2; + pthread_t th3; + bthread_t th4; + bthread_t th5; + const bthread_attr_t attr = BTHREAD_ATTR_PTHREAD; + + ASSERT_EQ(0, bthread_start_urgent(&th1, NULL, just_exit, NULL)); + ASSERT_EQ(0, bthread_start_background(&th2, NULL, just_exit, NULL)); + ASSERT_EQ(0, pthread_create(&th3, NULL, just_exit, NULL)); + EXPECT_EQ(0, bthread_start_urgent(&th4, &attr, just_exit, NULL)); + EXPECT_EQ(0, bthread_start_background(&th5, &attr, just_exit, NULL)); + + ASSERT_EQ(0, bthread_join(th1, NULL)); + ASSERT_EQ(0, bthread_join(th2, NULL)); + ASSERT_EQ(0, pthread_join(th3, NULL)); + ASSERT_EQ(0, bthread_join(th4, NULL)); + ASSERT_EQ(0, bthread_join(th5, NULL)); +} + +TEST_F(BthreadTest, bthread_equal) { + bthread_t th1; + ASSERT_EQ(0, bthread_start_urgent(&th1, NULL, do_nothing, NULL)); + bthread_t th2; + ASSERT_EQ(0, bthread_start_urgent(&th2, NULL, do_nothing, NULL)); + ASSERT_EQ(0, bthread_equal(th1, th2)); + bthread_t th3 = th2; + ASSERT_EQ(1, bthread_equal(th3, th2)); + ASSERT_EQ(0, bthread_join(th1, NULL)); + ASSERT_EQ(0, bthread_join(th2, NULL)); +} + +void* mark_run(void* run) { + *static_cast(run) = pthread_self(); + return NULL; +} + +void* check_sleep(void* pthread_task) { + EXPECT_TRUE(bthread_self() != 0); + // Create a no-signal task that other worker will not steal. The task will be + // run if current bthread does context switch. + bthread_attr_t attr = BTHREAD_ATTR_NORMAL | BTHREAD_NOSIGNAL; + bthread_t th1; + pthread_t run = 0; + const pthread_t pid = pthread_self(); + EXPECT_EQ(0, bthread_start_urgent(&th1, &attr, mark_run, &run)); + if (pthread_task) { + bthread_usleep(100000L); + // due to NOSIGNAL, mark_run did not run. + // FIXME: actually runs. someone is still stealing. + // EXPECT_EQ((pthread_t)0, run); + // bthread_usleep = usleep for BTHREAD_ATTR_PTHREAD + EXPECT_EQ(pid, pthread_self()); + // schedule mark_run + bthread_flush(); + } else { + // start_urgent should jump to the new thread first, then back to + // current thread. + EXPECT_EQ(pid, run); // should run in the same pthread + } + EXPECT_EQ(0, bthread_join(th1, NULL)); + if (pthread_task) { + EXPECT_EQ(pid, pthread_self()); + EXPECT_NE((pthread_t)0, run); // the mark_run should run. + } + return NULL; +} + +TEST_F(BthreadTest, bthread_usleep) { + // NOTE: May fail because worker threads may still be stealing tasks + // after previous cases. + usleep(10000); + + bthread_t th1; + ASSERT_EQ(0, bthread_start_urgent(&th1, &BTHREAD_ATTR_PTHREAD, + check_sleep, (void*)1)); + ASSERT_EQ(0, bthread_join(th1, NULL)); + + bthread_t th2; + ASSERT_EQ(0, bthread_start_urgent(&th2, NULL, + check_sleep, (void*)0)); + ASSERT_EQ(0, bthread_join(th2, NULL)); +} + +static const bthread_attr_t BTHREAD_ATTR_NORMAL_WITH_SPAN = +{ BTHREAD_STACKTYPE_NORMAL, BTHREAD_INHERIT_SPAN, NULL, BTHREAD_TAG_INVALID }; + +void* test_parent_span(void* p) { + uint64_t *q = (uint64_t *)p; + *q = (uint64_t)(bthread::tls_bls.rpcz_parent_span); + LOG(INFO) << "span id in thread is " << *q; + return NULL; +} + +void* test_grandson_parent_span(void* p) { + uint64_t* q = (uint64_t*)p; + *q = (uint64_t)(bthread::tls_bls.rpcz_parent_span); + LOG(INFO) << "parent span id in thread is " << *q; + return NULL; +} + +void* test_son_parent_span(void* p) { + uint64_t* q = (uint64_t*)p; + *q = (uint64_t)(bthread::tls_bls.rpcz_parent_span); + LOG(INFO) << "parent span id in thread is " << *q; + bthread_t th; + uint64_t multi_p; + bthread_start_urgent(&th, &BTHREAD_ATTR_NORMAL_WITH_SPAN, test_grandson_parent_span, &multi_p); + bthread_join(th, NULL); + return NULL; +} + +static uint64_t targets[] = {0xBADBEB0UL, 0xBADBEB1UL, 0xBADBEB2UL, 0xBADBEB3UL}; +void* create_span_func() { + static std::atomic index(0); + auto idx = index.fetch_add(1); + LOG(INFO) << "Bthread create span " << targets[idx]; + return (void*)targets[idx]; +} + +void destroy_span_func(void* span) { + LOG(INFO) << "Destroy span " << (uint64_t)span; +} + +void end_span_func() { +} + +TEST_F(BthreadTest, test_span) { + uint64_t p1 = 0; + uint64_t p2 = 0; + + uint64_t target = 0xBADBEAFUL; + LOG(INFO) << "target span id is " << target; + + bthread::tls_bls.rpcz_parent_span = (void*)target; + bthread_t th1; + ASSERT_EQ(0, bthread_start_urgent(&th1, &BTHREAD_ATTR_NORMAL_WITH_SPAN, test_parent_span, &p1)); + ASSERT_EQ(0, bthread_join(th1, NULL)); + + bthread_t th2; + ASSERT_EQ(0, bthread_start_background(&th2, NULL, test_parent_span, &p2)); + ASSERT_EQ(0, bthread_join(th2, NULL)); + + ASSERT_EQ(p1, target); + ASSERT_NE(p2, target); + + LOG(INFO) << "Test bthread create span"; + + ASSERT_EQ(0, bthread_set_span_funcs(create_span_func, destroy_span_func, end_span_func)); + + bthread_t multi_th1; + bthread_t multi_th2; + uint64_t multi_p1; + uint64_t multi_p2; + ASSERT_EQ(0, bthread_start_background(&multi_th1, &BTHREAD_ATTR_NORMAL_WITH_SPAN, + test_son_parent_span, &multi_p1)); + ASSERT_EQ(0, bthread_start_background(&multi_th2, &BTHREAD_ATTR_NORMAL_WITH_SPAN, + test_son_parent_span, &multi_p2)); + ASSERT_EQ(0, bthread_join(multi_th1, NULL)); + ASSERT_EQ(0, bthread_join(multi_th2, NULL)); + ASSERT_NE(multi_p1, multi_p2); + ASSERT_NE(std::find(targets, targets + 4, multi_p1), targets + 4); + ASSERT_NE(std::find(targets, targets + 4, multi_p2), targets + 4); + + ASSERT_EQ(0, bthread_set_span_funcs(NULL, NULL, NULL)); +} + +void* dummy_thread(void*) { + return NULL; +} + +TEST_F(BthreadTest, too_many_nosignal_threads) { + for (size_t i = 0; i < 100000; ++i) { + bthread_attr_t attr = BTHREAD_ATTR_NORMAL | BTHREAD_NOSIGNAL; + bthread_t tid; + ASSERT_EQ(0, bthread_start_urgent(&tid, &attr, dummy_thread, NULL)); + } +} + +static void* yield_thread(void*) { + bthread_yield(); + return NULL; +} + +TEST_F(BthreadTest, yield_single_thread) { + bthread_t tid; + ASSERT_EQ(0, bthread_start_background(&tid, NULL, yield_thread, NULL)); + ASSERT_EQ(0, bthread_join(tid, NULL)); +} + +#ifdef BRPC_BTHREAD_TRACER +void spin_and_log_trace() { + bool ok = false; + for (int i = 0; i < 10; ++i) { + start = false; + stop = false; + bthread_t th; + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, spin_and_log, (void*)1)); + while (!start) { + usleep(10 * 1000); + } + + std::string st1 = bthread::stack_trace(th); + LOG(INFO) << "spin_and_log stack trace:\n" << st1; + ok = st1.find("spin_and_log") != std::string::npos; + + stop = true; + ASSERT_EQ(0, bthread_join(th, NULL)); + + std::string st2 = bthread::stack_trace(th); + LOG(INFO) << "ended bthread stack trace:\n" << st2; + ASSERT_NE(std::string::npos, st2.find("not exist now")); + + if (ok) { + break; + } + } + ASSERT_TRUE(ok); +} + +void repeated_sleep_trace() { + bool ok = false; + for (int i = 0; i < 10; ++i) { + start = false; + stop = false; + bthread_t th; + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, repeated_sleep, (void*)1)); + while (!start) { + usleep(10 * 1000); + } + + std::string st1 = bthread::stack_trace(th); + LOG(INFO) << "repeated_sleep stack trace:\n" << st1; + ok = st1.find("repeated_sleep") != std::string::npos; + + stop = true; + ASSERT_EQ(0, bthread_join(th, NULL)); + + std::string st2 = bthread::stack_trace(th); + LOG(INFO) << "ended bthread stack trace:\n" << st2; + ASSERT_NE(std::string::npos, st2.find("not exist now")); + + if (ok) { + break; + } + } + ASSERT_TRUE(ok); +} + +TEST_F(BthreadTest, trace) { + spin_and_log_trace(); + repeated_sleep_trace(); +} +#endif // BRPC_BTHREAD_TRACER + +} // namespace diff --git a/test/bthread_work_stealing_queue_unittest.cpp b/test/bthread_work_stealing_queue_unittest.cpp new file mode 100644 index 0000000..a8b1103 --- /dev/null +++ b/test/bthread_work_stealing_queue_unittest.cpp @@ -0,0 +1,133 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // std::sort +#include +#include "butil/time.h" +#include "butil/macros.h" +#include "butil/scoped_lock.h" +#include "bthread/work_stealing_queue.h" +#include "bthread/processor.h" + +namespace { +typedef size_t value_type; +bool g_stop = false; +const size_t N = 1024*512; +const size_t CAP = 8; +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +void* steal_thread(void* arg) { + std::vector *stolen = new std::vector; + stolen->reserve(N); + bthread::WorkStealingQueue *q = + (bthread::WorkStealingQueue*)arg; + value_type val; + while (!g_stop) { + if (q->steal(&val)) { + stolen->push_back(val); + } else { + cpu_relax(); + } + } + return stolen; +} + +void* push_thread(void* arg) { + size_t npushed = 0; + value_type seed = 0; + bthread::WorkStealingQueue *q = + (bthread::WorkStealingQueue*)arg; + while (true) { + pthread_mutex_lock(&mutex); + const bool pushed = q->push(seed); + pthread_mutex_unlock(&mutex); + if (pushed) { + ++seed; + if (++npushed == N) { + g_stop = true; + break; + } + } + } + return NULL; +} + +void* pop_thread(void* arg) { + std::vector *popped = new std::vector; + popped->reserve(N); + bthread::WorkStealingQueue *q = + (bthread::WorkStealingQueue*)arg; + while (!g_stop) { + value_type val; + pthread_mutex_lock(&mutex); + const bool res = q->pop(&val); + pthread_mutex_unlock(&mutex); + if (res) { + popped->push_back(val); + } + } + return popped; +} + + +TEST(WSQTest, sanity) { + bthread::WorkStealingQueue q; + ASSERT_EQ(0, q.init(CAP)); + pthread_t rth[8]; + pthread_t wth, pop_th; + for (size_t i = 0; i < ARRAY_SIZE(rth); ++i) { + ASSERT_EQ(0, pthread_create(&rth[i], NULL, steal_thread, &q)); + } + ASSERT_EQ(0, pthread_create(&wth, NULL, push_thread, &q)); + ASSERT_EQ(0, pthread_create(&pop_th, NULL, pop_thread, &q)); + + std::vector values; + values.reserve(N); + size_t nstolen = 0, npopped = 0; + for (size_t i = 0; i < ARRAY_SIZE(rth); ++i) { + std::vector* res = NULL; + pthread_join(rth[i], (void**)&res); + for (size_t j = 0; j < res->size(); ++j, ++nstolen) { + values.push_back((*res)[j]); + } + delete res; + } + pthread_join(wth, NULL); + std::vector* res = NULL; + pthread_join(pop_th, (void**)&res); + for (size_t j = 0; j < res->size(); ++j, ++npopped) { + values.push_back((*res)[j]); + } + delete res; + + value_type val; + while (q.pop(&val)) { + values.push_back(val); + } + + std::sort(values.begin(), values.end()); + values.resize(std::unique(values.begin(), values.end()) - values.begin()); + + ASSERT_EQ(N, values.size()); + for (size_t i = 0; i < N; ++i) { + ASSERT_EQ(i, values[i]); + } + std::cout << "stolen=" << nstolen + << " popped=" << npopped + << " left=" << (N - nstolen - npopped) << std::endl; +} +} // namespace diff --git a/test/butil_unittest_main.cpp b/test/butil_unittest_main.cpp new file mode 100644 index 0000000..c7c6965 --- /dev/null +++ b/test/butil_unittest_main.cpp @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include "butil/at_exit.h" +#include "butil/logging.h" +#include "multiprocess_func_list.h" + +DEFINE_bool(disable_coredump, false, "Never core dump"); + +int main(int argc, char** argv) { + butil::AtExitManager at_exit; + testing::InitGoogleTest(&argc, argv); + + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + if (FLAGS_disable_coredump) { + rlimit core_limit; + core_limit.rlim_cur = 0; + core_limit.rlim_max = 0; + setrlimit(RLIMIT_CORE, &core_limit); + } +#if !BRPC_WITH_GLOG + CHECK(!GFLAGS_NAMESPACE::SetCommandLineOption("crash_on_fatal_log", "true").empty()); +#endif + return RUN_ALL_TESTS(); +} diff --git a/test/bvar_agent_group_unittest.cpp b/test/bvar_agent_group_unittest.cpp new file mode 100644 index 0000000..8d9eaff --- /dev/null +++ b/test/bvar_agent_group_unittest.cpp @@ -0,0 +1,152 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // pthread_* + +#include +#include +#include + +#include "butil/time.h" +#include "butil/macros.h" + +#include "bvar/detail/agent_group.h" +#include "butil/atomicops.h" + +#include + +namespace { +using namespace bvar::detail; + +struct Add { + uint64_t operator()(const uint64_t lhs, const uint64_t rhs) const { + return lhs + rhs; + } +}; + +const size_t OPS_PER_THREAD = 2000000; + +class AgentGroupTest : public testing::Test { +protected: + typedef butil::atomic agent_type; + void SetUp() {} + void TearDown() {} + + static void *thread_counter(void *arg) { + int id = (int)((long)arg); + agent_type *item = AgentGroup::get_or_create_tls_agent(id); + if (item == NULL) { + EXPECT_TRUE(false); + return NULL; + } + butil::Timer timer; + timer.start(); + for (size_t i = 0; i < OPS_PER_THREAD; ++i) { + agent_type *element = AgentGroup::get_or_create_tls_agent(id); + uint64_t old_value = element->load(butil::memory_order_relaxed); + uint64_t new_value; + do { + new_value = old_value + 2; + } while (__builtin_expect(!element->compare_exchange_weak(old_value, new_value, + butil::memory_order_relaxed, + butil::memory_order_relaxed), 0)); + //element->store(element->load(butil::memory_order_relaxed) + 2, + // butil::memory_order_relaxed); + //element->fetch_add(2, butil::memory_order_relaxed); + } + timer.stop(); + return (void *)(timer.n_elapsed()); + } +}; + +TEST_F(AgentGroupTest, test_sanity) { + int id = AgentGroup::create_new_agent(); + ASSERT_TRUE(id >= 0) << id; + agent_type *element = AgentGroup::get_or_create_tls_agent(id); + ASSERT_TRUE(element != NULL); + AgentGroup::destroy_agent(id); +} + +butil::atomic g_counter(0); + +void *global_add(void *) { + butil::Timer timer; + timer.start(); + for (size_t i = 0; i < OPS_PER_THREAD; ++i) { + g_counter.fetch_add(2, butil::memory_order_relaxed); + } + timer.stop(); + return (void *)(timer.n_elapsed()); +} + +TEST_F(AgentGroupTest, test_perf) { + size_t loops = 100000; + size_t id_num = 512; + int ids[id_num]; + for (size_t i = 0; i < id_num; ++i) { + ids[i] = AgentGroup::create_new_agent(); + ASSERT_TRUE(ids[i] >= 0); + } + butil::Timer timer; + timer.start(); + for (size_t i = 0; i < loops; ++i) { + for (size_t j = 0; j < id_num; ++j) { + agent_type *agent = + AgentGroup::get_or_create_tls_agent(ids[j]); + ASSERT_TRUE(agent != NULL) << ids[j]; + } + } + timer.stop(); + LOG(INFO) << "It takes " << timer.n_elapsed() / (loops * id_num) + << " ns to get tls agent for " << id_num << " agents"; + for (size_t i = 0; i < id_num; ++i) { + AgentGroup::destroy_agent(ids[i]); + } + +} + +TEST_F(AgentGroupTest, test_all_perf) { + long id = AgentGroup::create_new_agent(); + ASSERT_TRUE(id >= 0) << id; + pthread_t threads[24]; + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + pthread_create(&threads[i], NULL, &thread_counter, (void *)id); + } + long totol_time = 0; + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + void *ret; + pthread_join(threads[i], &ret); + totol_time += (long)ret; + } + LOG(INFO) << "ThreadAgent takes " + << totol_time / (OPS_PER_THREAD * ARRAY_SIZE(threads)); + totol_time = 0; + g_counter.store(0, butil::memory_order_relaxed); + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + pthread_create(&threads[i], NULL, global_add, (void *)id); + } + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + void *ret; + pthread_join(threads[i], &ret); + totol_time += (long)ret; + } + LOG(INFO) << "Global Atomic takes " + << totol_time / (OPS_PER_THREAD * ARRAY_SIZE(threads)); + AgentGroup::destroy_agent(id); + //sleep(1000); +} +} // namespace diff --git a/test/bvar_file_dumper_unittest.cpp b/test/bvar_file_dumper_unittest.cpp new file mode 100644 index 0000000..9a6bb0a --- /dev/null +++ b/test/bvar_file_dumper_unittest.cpp @@ -0,0 +1,40 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2015/08/27 17:12:38 + +#include "bvar/reducer.h" +#include +#include +#include + +class FileDumperTest : public testing::Test { +protected: + void SetUp() {} + void TearDown() {} +}; + +TEST_F(FileDumperTest, filters) { + bvar::Adder a1("a_latency"); + bvar::Adder a2("a_qps"); + bvar::Adder a3("a_error"); + bvar::Adder a4("process_*"); + bvar::Adder a5("default"); + GFLAGS_NAMESPACE::SetCommandLineOption("bvar_dump_interval", "1"); + GFLAGS_NAMESPACE::SetCommandLineOption("bvar_dump", "true"); + sleep(2); +} diff --git a/test/bvar_lock_timer_unittest.cpp b/test/bvar_lock_timer_unittest.cpp new file mode 100644 index 0000000..c4299b5 --- /dev/null +++ b/test/bvar_lock_timer_unittest.cpp @@ -0,0 +1,265 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2015/03/06 18:34:03 + +#include +#if __cplusplus >= 201103L +#include +#endif +#include +#include "gperftools_helper.h" +#include "bvar/utils/lock_timer.h" + +namespace { +struct DummyMutex {}; +} + +namespace std { +template <> +class lock_guard { +public: + lock_guard(DummyMutex&) {} +}; + +template <> +class unique_lock { +public: + unique_lock() {} + unique_lock(DummyMutex&) {} + template + unique_lock(DummyMutex&, T) {} + bool try_lock() { return true; } + void lock() {} + void unlock() {} +}; +} // namespace std + +namespace { +using bvar::IntRecorder; +using bvar::LatencyRecorder; +using bvar::utils::MutexWithRecorder; +using bvar::utils::MutexWithLatencyRecorder; + +class LockTimerTest : public testing::Test { +}; + +#if __cplusplus >= 201103L +TEST_F(LockTimerTest, MutexWithRecorder) { + IntRecorder recorder; + MutexWithRecorder mutex(recorder); + { + BAIDU_SCOPED_LOCK(mutex); + } + ASSERT_EQ(1u, recorder.get_value().num); + LOG(INFO) << recorder; + { + std::unique_lock lck(mutex); + lck.unlock(); + lck.lock(); + ASSERT_EQ(2u, recorder.get_value().num); + LOG(INFO) << recorder; + std::condition_variable cond; + cond.wait_for(lck, std::chrono::milliseconds(10)); + } + ASSERT_EQ(3u, recorder.get_value().num); +} + +TEST_F(LockTimerTest, MutexWithLatencyRecorder) { + LatencyRecorder recorder(10); + MutexWithLatencyRecorder mutex(recorder); + { + BAIDU_SCOPED_LOCK(mutex); + } + ASSERT_EQ(1u, recorder.count()); + { + std::unique_lock lck(mutex); + lck.unlock(); + lck.lock(); + ASSERT_EQ(2u, recorder.count()); + LOG(INFO) << recorder; + std::condition_variable cond; + cond.wait_for(lck, std::chrono::milliseconds(10)); + } + ASSERT_EQ(3u, recorder.count()); +} +#endif + +TEST_F(LockTimerTest, pthread_mutex_and_cond) { + LatencyRecorder recorder(10); + MutexWithLatencyRecorder mutex(recorder); + { + BAIDU_SCOPED_LOCK(mutex); + } + ASSERT_EQ(1u, recorder.count()); + { + std::unique_lock > lck(mutex); + ASSERT_EQ(1u, recorder.count()); + timespec due_time = butil::milliseconds_from_now(10); + pthread_cond_t cond; + ASSERT_EQ(0, pthread_cond_init(&cond, NULL)); + pthread_cond_timedwait(&cond, &(pthread_mutex_t&)mutex, &due_time); + pthread_cond_timedwait(&cond, &mutex.mutex(), &due_time); + ASSERT_EQ(0, pthread_cond_destroy(&cond)); + } + ASSERT_EQ(2u, recorder.count()); +} + +const static size_t OPS_PER_THREAD = 1000; + +template +void *signal_lock_thread(void *arg) { + M *m = (M*)arg; + for (size_t i = 0; i < OPS_PER_THREAD; ++i) { + { + std::unique_lock lck(*m); + usleep(10); + } + } + return NULL; +} + +TEST_F(LockTimerTest, signal_lock_time) { + IntRecorder r0; + MutexWithRecorder m0(r0); + pthread_t threads[4]; + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + ASSERT_EQ(0, pthread_create(&threads[i], NULL, + signal_lock_thread >, &m0)); + } + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + pthread_join(threads[i], NULL); + } + LOG(INFO) << r0; + ASSERT_EQ(OPS_PER_THREAD * ARRAY_SIZE(threads), (size_t)r0.get_value().num); + LatencyRecorder r1; + MutexWithLatencyRecorder m1(r1); + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + ASSERT_EQ(0, pthread_create(&threads[i], NULL, + signal_lock_thread >, &m1)); + } + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + pthread_join(threads[i], NULL); + } + LOG(INFO) << r1._latency; + ASSERT_EQ(OPS_PER_THREAD * ARRAY_SIZE(threads), (size_t)r1.count()); +} + +template +struct DoubleLockArg { + M0 m0; + M1 m1; +}; + +template +void *double_lock_thread(void *arg) { + DoubleLockArg* dla = (DoubleLockArg*)arg; + for (size_t i = 0; i < OPS_PER_THREAD; ++i) { + std::unique_lock lck0(dla->m0, std::defer_lock); + std::unique_lock lck1(dla->m1, std::defer_lock); + butil::double_lock(lck0, lck1); + usleep(10); + } + return NULL; +} + +TEST_F(LockTimerTest, double_lock_time) { + typedef MutexWithRecorder M0; + typedef MutexWithLatencyRecorder M1; + DoubleLockArg arg; + IntRecorder r0; + LatencyRecorder r1; + arg.m0.set_recorder(r0); + arg.m1.set_recorder(r1); + pthread_t threads[4]; + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + ASSERT_EQ(0, pthread_create(&threads[i], NULL, + double_lock_thread, &arg)); + } + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + pthread_join(threads[i], NULL); + } + ASSERT_EQ(OPS_PER_THREAD * ARRAY_SIZE(threads), (size_t)r0.get_value().num); + ASSERT_EQ(OPS_PER_THREAD * ARRAY_SIZE(threads), (size_t)r1.count()); + LOG(INFO) << r0; + LOG(INFO) << r1._latency; +#if !WITH_BABYLON_COUNTER + // reset() of IntRecorder with babylon counter does not work, + // should never call reset(). + r0.reset(); + r1._latency.reset(); +#endif // !WITH_BABYLON_COUNTER + DoubleLockArg arg1; + arg1.m0.set_recorder(r1); + arg1.m1.set_recorder(r0); + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + ASSERT_EQ(0, pthread_create(&threads[i], NULL, + double_lock_thread, &arg1)); + } + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + pthread_join(threads[i], NULL); + } +#if !WITH_BABYLON_COUNTER + ASSERT_EQ(OPS_PER_THREAD * ARRAY_SIZE(threads), (size_t)r0.get_value().num); + ASSERT_EQ(OPS_PER_THREAD * ARRAY_SIZE(threads), (size_t)r1.count()); +#else + ASSERT_EQ(OPS_PER_THREAD * ARRAY_SIZE(threads) * 2, (size_t)r0.get_value().num); + ASSERT_EQ(OPS_PER_THREAD * ARRAY_SIZE(threads) * 2, (size_t)r1.count()); +#endif // !WITH_BABYLON_COUNTER + LOG(INFO) << r0; + LOG(INFO) << r1._latency; +} + +TEST_F(LockTimerTest, overhead) { + LatencyRecorder r0; + MutexWithLatencyRecorder m0(r0); + butil::Timer timer; + const size_t N = 1000 * 1000 * 10; + + ProfilerStart("mutex_with_latency_recorder.prof"); + timer.start(); + for (size_t i = 0; i < N; ++i) { + BAIDU_SCOPED_LOCK(m0); + } + timer.stop(); + ProfilerStop(); + LOG(INFO) << "The overhead of MutexWithLatencyRecorder is " + << timer.n_elapsed() / N << "ns"; + + IntRecorder r1; + MutexWithRecorder m1(r1); + ProfilerStart("mutex_with_recorder.prof"); + timer.start(); + for (size_t i = 0; i < N; ++i) { + BAIDU_SCOPED_LOCK(m1); + } + timer.stop(); + ProfilerStop(); + LOG(INFO) << "The overhead of MutexWithRecorder is " + << timer.n_elapsed() / N << "ns"; + MutexWithRecorder m2; + ProfilerStart("mutex_with_timer.prof"); + timer.start(); + for (size_t i = 0; i < N; ++i) { + BAIDU_SCOPED_LOCK(m2); + } + timer.stop(); + ProfilerStop(); + LOG(INFO) << "The overhead of timer is " + << timer.n_elapsed() / N << "ns"; +} +} // namespace diff --git a/test/bvar_multi_dimension_unittest.cpp b/test/bvar_multi_dimension_unittest.cpp new file mode 100644 index 0000000..a04ab4b --- /dev/null +++ b/test/bvar_multi_dimension_unittest.cpp @@ -0,0 +1,665 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date 2021/11/17 14:57:49 + +#include +#include +#include +#include +#include +#include +#include +#include +#include "butil/time.h" +#include "butil/macros.h" +#include "bvar/bvar.h" +#include "bvar/multi_dimension.h" +#include "butil/third_party/rapidjson/rapidjson.h" +#include "butil/third_party/rapidjson/document.h" + +const size_t OPS_PER_THREAD = 200000; + +const size_t OPS_PER_THREAD_INTRECORDER = 2000; + +static const int num_thread = 24; + +static const int idc_count = 20; +static const int method_count = 20; +static const int status_count = 50; + +static const std::list labels = {"idc", "method", "status"}; + +static void *thread_adder(void *arg) { + bvar::Adder *reducer = (bvar::Adder *)arg; + butil::Timer timer; + timer.start(); + for (size_t i = 0; i < OPS_PER_THREAD; ++i) { + (*reducer) << 2; + } + timer.stop(); + return (void *)(timer.n_elapsed()); +} + +static long start_perf_test_with_madder(size_t num_thread, bvar::Adder* adder) { + EXPECT_TRUE(adder->valid()); + pthread_t threads[num_thread]; + for (size_t i = 0; i < num_thread; ++i) { + pthread_create(&threads[i], NULL, &thread_adder, (void *)adder); + } + long totol_time = 0; + for (size_t i = 0; i < num_thread; ++i) { + void *ret = NULL; + pthread_join(threads[i], &ret); + totol_time += (long)ret; + } + long avg_time = totol_time / (OPS_PER_THREAD * num_thread); + EXPECT_EQ(2ul * num_thread * OPS_PER_THREAD, adder->get_value()); + return avg_time; +} + +static void *thread_maxer(void *arg) { + bvar::Maxer *reducer = (bvar::Maxer *)arg; + butil::Timer timer; + timer.start(); + for (size_t i = 1; i <= OPS_PER_THREAD; ++i) { + (*reducer) << 2 * i * OPS_PER_THREAD; + } + timer.stop(); + return (void *)(timer.n_elapsed()); +} + +static long start_perf_test_with_mmaxer(size_t num_thread, bvar::Maxer* maxer) { + EXPECT_TRUE(maxer->valid()); + pthread_t threads[num_thread]; + for (size_t i = 0; i < num_thread; ++i) { + pthread_create(&threads[i], NULL, &thread_maxer, (void *)maxer); + } + long totol_time = 0; + for (size_t i = 0; i < num_thread; ++i) { + void *ret = NULL; + pthread_join(threads[i], &ret); + totol_time += (long)ret; + } + long avg_time = totol_time / (OPS_PER_THREAD * num_thread); + EXPECT_EQ(2ul * OPS_PER_THREAD * OPS_PER_THREAD, maxer->get_value()); + return avg_time; +} + +static void *thread_miner(void *arg) { + bvar::Miner *reducer = (bvar::Miner *)arg; + butil::Timer timer; + timer.start(); + for (size_t i = 1; i <= OPS_PER_THREAD; ++i) { + (*reducer) << -2 * i * OPS_PER_THREAD; + } + timer.stop(); + return (void *)(timer.n_elapsed()); +} + +static long start_perf_test_with_mminer(size_t num_thread, bvar::Miner* miner) { + EXPECT_TRUE(miner->valid()); + pthread_t threads[num_thread]; + for (size_t i = 0; i < num_thread; ++i) { + pthread_create(&threads[i], NULL, &thread_miner, (void *)miner); + } + long totol_time = 0; + for (size_t i = 0; i < num_thread; ++i) { + void *ret = NULL; + pthread_join(threads[i], &ret); + totol_time += (long)ret; + } + long avg_time = totol_time / (OPS_PER_THREAD * num_thread); + EXPECT_EQ(-2ul * OPS_PER_THREAD * OPS_PER_THREAD, miner->get_value()); + return avg_time; +} + +static void *thread_intrecorder(void *arg) { + bvar::IntRecorder *reducer = (bvar::IntRecorder *)arg; + butil::Timer timer; + timer.start(); + for (size_t i = 1; i <= OPS_PER_THREAD_INTRECORDER; ++i) { + (*reducer) << 2 * i * OPS_PER_THREAD_INTRECORDER; + } + timer.stop(); + return (void *)(timer.n_elapsed()); +} + +static long start_perf_test_with_mintrecorder(size_t num_thread, bvar::IntRecorder* intrecorder) { + EXPECT_TRUE(intrecorder->valid()); + pthread_t threads[num_thread]; + for (size_t i = 0; i < num_thread; ++i) { + pthread_create(&threads[i], NULL, &thread_intrecorder, (void *)intrecorder); + } + long totol_time = 0; + for (size_t i = 0; i < num_thread; ++i) { + void *ret = NULL; + pthread_join(threads[i], &ret); + totol_time += (long)ret; + } + long avg_time = totol_time / (OPS_PER_THREAD_INTRECORDER * num_thread); + EXPECT_EQ(2ul * (1 + OPS_PER_THREAD_INTRECORDER) / 2 * OPS_PER_THREAD_INTRECORDER, intrecorder->average()); + return avg_time; +} + +class MultiDimensionTest : public testing::Test { +protected: + void SetUp() {} + void TearDown() { + } +}; + +TEST_F(MultiDimensionTest, madder) { + std::list labels_value = {"bj", "get", "200"}; + bvar::MultiDimension > my_madder1("request_count_madder_uint32_t", labels); + bvar::Adder* my_adder1 = my_madder1.get_stats(labels_value); + ASSERT_TRUE(my_adder1); + ASSERT_TRUE(my_adder1->valid()); + *my_adder1 << 2 << 4; + ASSERT_EQ(6u, my_adder1->get_value()); + + bvar::MultiDimension > my_madder2("request_count_madder_double", labels); + bvar::Adder* my_adder2 = my_madder2.get_stats(labels_value); + ASSERT_TRUE(my_adder2); + ASSERT_TRUE(my_adder2->valid()); + *my_adder2 << 2.0 << 4.0; + ASSERT_EQ(6.0, my_adder2->get_value()); + + bvar::MultiDimension > my_madder3("request_count_madder_int", labels); + bvar::Adder* my_adder3 = my_madder3.get_stats(labels_value); + ASSERT_TRUE(my_adder3); + ASSERT_TRUE(my_adder3->valid()); + *my_adder3 << -9 << 1 << 0 << 3; + ASSERT_EQ(-5, my_adder3->get_value()); + + bvar::MultiDimension > my_madder_str("my_string", labels); + bvar::Adder *my_str1 = my_madder_str.get_stats(labels_value); + ASSERT_TRUE(my_str1); + std::string str1 = "world"; + *my_str1 << "hello " << str1; + ASSERT_STREQ("hello world", my_str1->get_value().c_str()); +} + +TEST_F(MultiDimensionTest, mmadder_perf) { + std::list labels_value = {"bj", "get", "200"}; + bvar::MultiDimension > my_madder1("request_count_madder_uint64_t", labels); + bvar::Adder* my_adder = my_madder1.get_stats(labels_value); + ASSERT_TRUE(my_adder); + + std::ostringstream oss; + for (size_t i = 1; i <= num_thread; ++i) { + my_adder->reset(); + oss << i << '\t' << start_perf_test_with_madder(i, my_adder) << '\n'; + } + LOG(INFO) << "Adder performance:\n" << oss.str(); +} + +TEST_F(MultiDimensionTest, mmaxer) { + bvar::MultiDimension > my_mmaxer("request_count_mmaxer", labels); + std::list labels_value = {"bj", "get", "200"}; + bvar::Maxer* my_maxer = my_mmaxer.get_stats(labels_value); + ASSERT_TRUE(my_maxer); + *my_maxer << 1 << 2 << 3; + ASSERT_EQ(3, my_maxer->get_value()); +} + +TEST_F(MultiDimensionTest, mmaxer_perf) { + bvar::MultiDimension > my_mmaxer("request_count_mmaxer", labels); + std::list labels_value = {"bj", "get", "200"}; + bvar::Maxer* my_maxer = my_mmaxer.get_stats(labels_value); + ASSERT_TRUE(my_maxer); + + std::ostringstream oss; + for (size_t i = 1; i <= num_thread; ++i) { + my_maxer->reset(); + oss << i << '\t' << start_perf_test_with_mmaxer(i, my_maxer) << '\n'; + } + LOG(INFO) << "Maxer performance:\n" << oss.str(); +} + +TEST_F(MultiDimensionTest, mminer) { + bvar::MultiDimension > my_mminer("client_request_count_mminer", labels); + std::list labels_value = {"bj", "get", "200"}; + bvar::Miner* my_miner = my_mminer.get_stats(labels_value); + ASSERT_TRUE(my_miner); + *my_miner << 1 << 2 << 3; + ASSERT_EQ(1, my_miner->get_value()); +} + +TEST_F(MultiDimensionTest, mminer_perf) { + bvar::MultiDimension > my_mminer("request_count_mminer", labels); + std::list labels_value = {"bj", "get", "200"}; + bvar::Miner* my_miner = my_mminer.get_stats(labels_value); + ASSERT_TRUE(my_miner); + + std::ostringstream oss; + for (size_t i = 1; i <= num_thread; ++i) { + my_miner->reset(); + oss << i << '\t' << start_perf_test_with_mminer(i, my_miner) << '\n'; + } + LOG(INFO) << "Miner performance:\n" << oss.str(); +} + + +TEST_F(MultiDimensionTest, mintrecoder) { + bvar::MultiDimension my_mintrecorder("client_request_count_mintrecorder", labels); + std::list labels_value = {"bj", "get", "200"}; + bvar::IntRecorder* my_intrecorder = my_mintrecorder.get_stats(labels_value); + ASSERT_TRUE(my_intrecorder); + *my_intrecorder << 1 << 2 << 3; + ASSERT_EQ(2, my_intrecorder->average()); +} + +TEST_F(MultiDimensionTest, mintrecorder_perf) { + bvar::MultiDimension my_mintrecorder("request_count_mintrecorder", labels); + std::list labels_value = {"bj", "get", "200"}; + bvar::IntRecorder* my_intrecorder = my_mintrecorder.get_stats(labels_value); + ASSERT_TRUE(my_intrecorder); + + std::ostringstream oss; + for (size_t i = 1; i <= num_thread; ++i) { + my_intrecorder->reset(); + oss << i << '\t' << start_perf_test_with_mintrecorder(i, my_intrecorder) << '\n'; + } + LOG(INFO) << "IntRecorder performance:\n" << oss.str(); +} + +TEST_F(MultiDimensionTest, stats) { + std::vector > vec_labels; + std::vector > vec_labels_no_sort; + bvar::MultiDimension > my_madder("test_stats", labels); + ASSERT_EQ(0, my_madder.count_stats()); + std::list labels_value1 = {"tc", "get", "200"}; + ASSERT_FALSE(my_madder.has_stats(labels_value1)); + vec_labels.push_back(labels_value1); + vec_labels_no_sort.push_back(labels_value1); + bvar::Adder* adder1 = my_madder.get_stats(labels_value1); + ASSERT_TRUE(adder1); + ASSERT_TRUE(my_madder.has_stats(labels_value1)); + std::vector > ret_labels; + my_madder.list_stats(&ret_labels); + ASSERT_EQ(vec_labels, ret_labels); + ASSERT_EQ(1, my_madder.count_stats()); + + std::list labels_value2 = {"nj", "get", "200"}; + ASSERT_FALSE(my_madder.has_stats(labels_value2)); + bvar::Adder* adder2 = my_madder.get_stats(labels_value2); + ASSERT_TRUE(adder2); + ASSERT_TRUE(my_madder.has_stats(labels_value2)); + vec_labels.push_back(labels_value2); + vec_labels_no_sort.push_back(labels_value2); + my_madder.list_stats(&ret_labels); + sort(vec_labels.begin(), vec_labels.end()); + sort(ret_labels.begin(), ret_labels.end()); + ASSERT_EQ(vec_labels, ret_labels); + ASSERT_EQ(2, my_madder.count_stats()); + + std::list labels_value3 = {"hz", "post", "500"}; + ASSERT_FALSE(my_madder.has_stats(labels_value3)); + bvar::Adder* adder3 = my_madder.get_stats(labels_value3); + ASSERT_TRUE(adder3); + ASSERT_TRUE(my_madder.has_stats(labels_value3)); + vec_labels.push_back(labels_value3); + vec_labels_no_sort.push_back(labels_value3); + my_madder.list_stats(&ret_labels); + sort(vec_labels.begin(), vec_labels.end()); + sort(ret_labels.begin(), ret_labels.end()); + ASSERT_EQ(vec_labels, ret_labels); + ASSERT_EQ(3, my_madder.count_stats()); + + std::list labels_value4 = {"gz", "post", "500"}; + ASSERT_FALSE(my_madder.has_stats(labels_value4)); + bvar::Adder* adder4 = my_madder.get_stats(labels_value4); + ASSERT_TRUE(adder4); + ASSERT_TRUE(my_madder.has_stats(labels_value4)); + ASSERT_EQ(4, my_madder.count_stats()); + vec_labels.push_back(labels_value4); + vec_labels_no_sort.push_back(labels_value4); + my_madder.list_stats(&ret_labels); + sort(vec_labels.begin(), vec_labels.end()); + sort(ret_labels.begin(), ret_labels.end()); + ASSERT_EQ(vec_labels, ret_labels); + ASSERT_EQ(4, my_madder.count_stats()); + + my_madder.delete_stats(labels_value4); + ASSERT_FALSE(my_madder.has_stats(labels_value4)); + ASSERT_EQ(3, my_madder.count_stats()); + vec_labels_no_sort.pop_back(); + my_madder.list_stats(&ret_labels); + sort(vec_labels_no_sort.begin(), vec_labels_no_sort.end()); + sort(ret_labels.begin(), ret_labels.end()); + ASSERT_EQ(vec_labels_no_sort, ret_labels); + + ASSERT_TRUE(my_madder.has_stats(labels_value1)); + ASSERT_TRUE(my_madder.has_stats(labels_value2)); + ASSERT_TRUE(my_madder.has_stats(labels_value3)); + ASSERT_FALSE(my_madder.has_stats(labels_value4)); + + my_madder.clear_stats(); + ASSERT_EQ(0, my_madder.count_stats()); + ASSERT_FALSE(my_madder.has_stats(labels_value1)); + ASSERT_FALSE(my_madder.has_stats(labels_value2)); + ASSERT_FALSE(my_madder.has_stats(labels_value3)); + ASSERT_FALSE(my_madder.has_stats(labels_value4)); + bvar::Adder *adder5 = my_madder.get_stats(labels_value1); + ASSERT_TRUE(adder5); +} + +TEST_F(MultiDimensionTest, get_description) { + bvar::MultiDimension > my_madder("test_get_description", labels); + std::list labels_value1 = {"gz", "post", "200"}; + bvar::Adder* adder1 = my_madder.get_stats(labels_value1); + ASSERT_TRUE(adder1); + *adder1 << 1; + std::list labels_value2 = {"tc", "post", "200"}; + bvar::Adder* adder2 = my_madder.get_stats(labels_value2); + ASSERT_TRUE(adder2); + *adder2 << 2; + + const std::string description = my_madder.get_description(); + LOG(INFO) << "description=" << description; + BUTIL_RAPIDJSON_NAMESPACE::Document doc; + doc.Parse(description.c_str()); + ASSERT_FALSE(doc.HasParseError()); + ASSERT_TRUE(doc.IsObject()); + ASSERT_TRUE(doc.HasMember("name")); + ASSERT_TRUE(doc["name"].IsString()); + ASSERT_STREQ("test_get_description", doc["name"].GetString()); + + ASSERT_TRUE(doc.HasMember("stats_count")); + ASSERT_TRUE(doc["stats_count"].IsInt()); + ASSERT_EQ(2, doc["stats_count"].GetInt()); + + ASSERT_TRUE(doc.HasMember("labels")); + ASSERT_TRUE(doc["labels"].IsArray()); + + BUTIL_RAPIDJSON_NAMESPACE::Value& labels = doc["labels"]; + ASSERT_EQ(3, labels.Size()); + ASSERT_STREQ(labels[0].GetString(), "idc"); + ASSERT_STREQ(labels[1].GetString(), "method"); + ASSERT_STREQ(labels[2].GetString(), "status"); +} + +TEST_F(MultiDimensionTest, mlatencyrecorder) { + std::string old_bvar_dump_interval; + std::string old_mbvar_dump; + std::string old_bvar_latency_p1; + std::string old_bvar_latency_p2; + std::string old_bvar_latency_p3; + + GFLAGS_NAMESPACE::GetCommandLineOption("bvar_dump_interval", &old_bvar_dump_interval); + GFLAGS_NAMESPACE::GetCommandLineOption("mbvar_dump", &old_mbvar_dump); + GFLAGS_NAMESPACE::GetCommandLineOption("bvar_latency_p1", &old_bvar_latency_p1); + GFLAGS_NAMESPACE::GetCommandLineOption("bvar_latency_p2", &old_bvar_latency_p2); + GFLAGS_NAMESPACE::GetCommandLineOption("bvar_latency_p3", &old_bvar_latency_p3); + + GFLAGS_NAMESPACE::SetCommandLineOption("bvar_dump_interval", "1"); + GFLAGS_NAMESPACE::SetCommandLineOption("mbvar_dump", "true"); + GFLAGS_NAMESPACE::SetCommandLineOption("bvar_latency_p1", "60"); + GFLAGS_NAMESPACE::SetCommandLineOption("bvar_latency_p2", "70"); + GFLAGS_NAMESPACE::SetCommandLineOption("bvar_latency_p3", "80"); + + bvar::MultiDimension my_mlatencyrecorder("client_request_count_mlatencyrecorder", labels); + std::list labels_value = {"tc", "get", "200"}; + bvar::LatencyRecorder* my_latencyrecorder = my_mlatencyrecorder.get_stats(labels_value); + ASSERT_TRUE(my_latencyrecorder); + *my_latencyrecorder << 1 << 2 << 3 << 4 << 5 << 6 << 7; + sleep(1); + ASSERT_EQ(4, my_latencyrecorder->latency()); + ASSERT_EQ(7, my_latencyrecorder->max_latency()); + ASSERT_LE(7, my_latencyrecorder->qps()); + ASSERT_EQ(7, my_latencyrecorder->count()); + + GFLAGS_NAMESPACE::SetCommandLineOption("bvar_dump_interval", old_bvar_dump_interval.c_str()); + GFLAGS_NAMESPACE::SetCommandLineOption("mbvar_dump", old_mbvar_dump.c_str()); + GFLAGS_NAMESPACE::SetCommandLineOption("bvar_latency_p1", old_bvar_latency_p1.c_str()); + GFLAGS_NAMESPACE::SetCommandLineOption("bvar_latency_p2", old_bvar_latency_p2.c_str()); + GFLAGS_NAMESPACE::SetCommandLineOption("bvar_latency_p3", old_bvar_latency_p3.c_str()); +} + +TEST_F(MultiDimensionTest, mstatus) { + bvar::MultiDimension > my_mstatus("my_mstatus", labels); + std::list labels_value {"tc", "get", "200"}; + bvar::Status* my_status = my_mstatus.get_stats(labels_value); + ASSERT_TRUE(my_status); + my_status->set_value(1); + ASSERT_EQ(1, my_status->get_value()); +} + +typedef size_t (*hash_fun)(const std::list& labels_name); + +static uint64_t perf_hash(hash_fun fn) { + uint64_t cost = 0; + for (int i = 0; i < idc_count; i++) { + std::ostringstream oss_idc; + oss_idc << "idc" << i; + for (int j = 0; j < method_count; j++) { + std::ostringstream oss_method; + oss_method << "method" << j; + for (int k = 0; k < status_count; k++) { + std::ostringstream oss_status; + oss_status << "status" << k; + std::list labels_value {oss_idc.str(), oss_method.str(), oss_status.str()}; + butil::Timer timer(butil::Timer::STARTED); + size_t hash_code = fn(labels_value); + EXPECT_NE(0, hash_code); + timer.stop(); + cost += timer.n_elapsed(); + } + } + } + return cost; +} + +static size_t hash_fun1(const std::list& labels_value) { + size_t hash_value = 0; + for (auto &k : labels_value) { + hash_value += std::hash()(k); + } + return hash_value; +} + +static size_t hash_fun2(const std::list& labels_value) { + std::string hash_str; + for (auto &k : labels_value) { + hash_str.append(k); + } + return std::hash()(hash_str); +} + +static size_t hash_fun3(const std::list& labels_value) { + std::ostringstream oss; + for (auto &k : labels_value) { + oss << k; + } + return std::hash()(oss.str()); +} + +TEST_F(MultiDimensionTest, test_hash) { + std::ostringstream oss; + oss << "hash_fun1 \t" << perf_hash(hash_fun1) << "\n" + << "hash_fun2 \t" << perf_hash(hash_fun2) << "\n" + << "hash_fun3 \t" << perf_hash(hash_fun3) << "\n"; + LOG(INFO) << "Hash fun performance:\n" << oss.str(); +} + + +class MyStringView { +public: + MyStringView() : _ptr(NULL), _len(0) {} + MyStringView(const char* str) + : _ptr(str), + _len(str == NULL ? 0 : strlen(str)) {} +#if __cplusplus >= 201703L + MyStringView(const std::string_view& str) + : _ptr(str.data()), _len(str.size()) {} +#endif // __cplusplus >= 201703L + MyStringView(const std::string& str) + : _ptr(str.data()), _len(str.size()) {} + MyStringView(const char* offset, size_t len) + : _ptr(offset), _len(len) {} + + const char* data() const { return _ptr; } + size_t size() const { return _len; } + + // Converts to `std::basic_string`. + explicit operator std::string() const { + if (NULL == _ptr) { + return {}; + } + return {_ptr, size()}; + } + + // Converts to butil::StringPiece. + explicit operator butil::StringPiece() const { + if (NULL == _ptr) { + return {}; + } + return {_ptr, size()}; + } + +private: + const char* _ptr; + size_t _len; +}; + +bool operator==(const MyStringView& x, const std::string& y) { + if (x.size() != y.size()) { + return false; + } + + return butil::StringPiece::wordmemcmp(x.data(), y.data(), x.size()) == 0; +} + +bool operator==(const std::string& x, const MyStringView& y) { + if (x.size() != y.size()) { + return false; + } + + return butil::StringPiece::wordmemcmp(x.data(), y.data(), x.size()) == 0; +} + +static int g_exposed_count = 0; + +template +static void TestLabels() { + std::string mbvar_name = butil::string_printf("my_madder_%d", g_exposed_count); + KeyType labels{"idc", "method", "status"}; + bvar::MultiDimension, KeyType> my_madder(mbvar_name, labels); + ASSERT_EQ(labels.size(), my_madder.count_labels()); + ASSERT_STREQ(mbvar_name.c_str(), my_madder.name().c_str()); + ASSERT_EQ(labels, my_madder.labels()); + + using ItemType = typename ValueType::value_type; + ValueType labels_value{ItemType("cv"), ItemType("post"), ItemType("200")}; + bvar::Adder* adder = my_madder.get_stats(labels_value); + ASSERT_NE(nullptr, adder); + ASSERT_TRUE(my_madder.has_stats(labels_value)); + ASSERT_EQ((size_t)1, my_madder.count_stats()); + { + // Compatible with old API. + bvar::Adder* temp = my_madder.get_stats({"cv", "post", "200"}); + ASSERT_EQ(adder, temp); + } + *adder << g_exposed_count; + ASSERT_EQ(g_exposed_count, adder->get_value()); + my_madder.delete_stats(labels_value); + ASSERT_FALSE(my_madder.has_stats(labels_value)); + ASSERT_EQ((size_t)0, my_madder.count_stats()); +} + +TEST_F(MultiDimensionTest, labels) { + TestLabels, std::list>(); + TestLabels, std::vector>(); + TestLabels, std::array>(); + + TestLabels, std::list>(); + TestLabels, std::vector>(); + TestLabels, std::array>(); + +#if __cplusplus >= 201703L + TestLabels, std::list>(); + TestLabels, std::vector>(); + TestLabels, std::array>(); +#endif // __cplusplus >= 201703L + + TestLabels, std::list>(); + TestLabels, std::vector>(); + TestLabels, std::array>(); + + TestLabels, std::list>(); + TestLabels, std::vector>(); + TestLabels, std::array>(); + + TestLabels, std::list>(); + TestLabels, std::vector>(); + TestLabels, std::array>(); +} + +std::array g_labels_value{"idc", "post", "200"}; +bool g_shared_stop = false; + +void* get_shared_adder_thread(void* arg) { + auto my_madder = + (bvar::MultiDimension, std::vector, true>*)arg; + while (!g_shared_stop) { + auto adder = my_madder->get_stats(g_labels_value); + EXPECT_NE(nullptr, adder); + *adder << 1; + } + return NULL; +} + +void* delete_shared_adder_thread(void* arg) { + auto my_madder = + (bvar::MultiDimension, std::vector, true>*)arg; + while (!g_shared_stop) { + my_madder->delete_stats(g_labels_value); + } + return NULL; +} + +TEST_F(MultiDimensionTest, shared) { + bvar::MultiDimension, std::vector, true> my_madder( + "my_adder", {"idc", "method", "status"}); + std::shared_ptr> adder = my_madder.get_stats(g_labels_value); + ASSERT_NE(nullptr, adder); + ASSERT_TRUE(my_madder.has_stats(g_labels_value)); + ASSERT_EQ((size_t)1, my_madder.count_stats()); + + *adder << 1; + ASSERT_EQ(1, adder->get_value()); + my_madder.delete_stats(g_labels_value); + *adder << 1; + ASSERT_EQ(2, adder->get_value()); + ASSERT_FALSE(my_madder.has_stats(g_labels_value)); + + const int get_num = 8; + std::vector get_threads(get_num); + for (int i = 0; i < get_num; ++i) { + ASSERT_EQ(0, pthread_create(&get_threads[i], NULL, get_shared_adder_thread, &my_madder)); + } + pthread_t delete_thread; + ASSERT_EQ(0, pthread_create(&delete_thread, NULL, delete_shared_adder_thread, &my_madder)); + usleep(100 * 1000); // 100ms + g_shared_stop = true; + for (int i = 0; i < get_num; ++i) { + ASSERT_EQ(0, pthread_join(get_threads[i], NULL)); + } + ASSERT_EQ(0, pthread_join(delete_thread, NULL)); +} + diff --git a/test/bvar_mvariable_unittest.cpp b/test/bvar_mvariable_unittest.cpp new file mode 100644 index 0000000..aca50d5 --- /dev/null +++ b/test/bvar_mvariable_unittest.cpp @@ -0,0 +1,201 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date 2021/11/17 14:57:49 + +// #if __cplusplus >= 201703L +#include +// #endif // __cplusplus >= 201703L +#include // pthread_* +#include +#include +#include +#include +#include +#include +#include +#include +#include "butil/time.h" +#include "butil/macros.h" +#include "bvar/bvar.h" +#include "bvar/multi_dimension.h" + +static const std::list labels = {"idc", "method", "status"}; + +class MVariableTest : public testing::Test { +protected: + void SetUp() {} + void TearDown() { + } +}; + +namespace foo { +namespace bar { +class Apple {}; +class BaNaNa {}; +class Car_Rot {}; +class RPCTest {}; +class HELLO {}; +} +} + +TEST_F(MVariableTest, expose) { + std::vector list_exposed_vars; + std::list labels_value1 {"bj", "get", "200"}; + bvar::MultiDimension > my_madder1(labels); + ASSERT_EQ(0UL, bvar::MVariableBase::count_exposed()); + my_madder1.expose("request_count_madder"); + ASSERT_EQ(1UL, bvar::MVariableBase::count_exposed()); + bvar::Adder* my_adder1 = my_madder1.get_stats(labels_value1); + ASSERT_TRUE(my_adder1); + ASSERT_STREQ("request_count_madder", my_madder1.name().c_str()); + + ASSERT_EQ(0, my_madder1.expose("request_count_madder_another")); + ASSERT_STREQ("request_count_madder_another", my_madder1.name().c_str()); + + ASSERT_EQ(0, my_madder1.expose("request-count::madder")); + ASSERT_STREQ("request_count_madder", my_madder1.name().c_str()); + + ASSERT_EQ(0, my_madder1.expose("request.count-madder::BaNaNa")); + ASSERT_STREQ("request_count_madder_ba_na_na", my_madder1.name().c_str()); + + ASSERT_EQ(0, my_madder1.expose_as("foo::bar::Apple", "request")); + ASSERT_STREQ("foo_bar_apple_request", my_madder1.name().c_str()); + + ASSERT_EQ(0, my_madder1.expose_as("foo.bar::BaNaNa", "request")); + ASSERT_STREQ("foo_bar_ba_na_na_request", my_madder1.name().c_str()); + + ASSERT_EQ(0, my_madder1.expose_as("foo::bar.Car_Rot", "request")); + ASSERT_STREQ("foo_bar_car_rot_request", my_madder1.name().c_str()); + + ASSERT_EQ(0, my_madder1.expose_as("foo-bar-RPCTest", "request")); + ASSERT_STREQ("foo_bar_rpctest_request", my_madder1.name().c_str()); + + ASSERT_EQ(0, my_madder1.expose_as("foo-bar-HELLO", "request")); + ASSERT_STREQ("foo_bar_hello_request", my_madder1.name().c_str()); + + my_madder1.expose("request_count_madder"); + ASSERT_STREQ("request_count_madder", my_madder1.name().c_str()); + list_exposed_vars.push_back("request_count_madder"); + + ASSERT_EQ(1UL, my_madder1.count_stats()); + ASSERT_EQ(1UL, bvar::MVariableBase::count_exposed()); + + std::list labels2 {"user", "url", "cost"}; + bvar::MultiDimension > my_madder2("client_url", labels2); + ASSERT_EQ(2UL, bvar::MVariableBase::count_exposed()); + list_exposed_vars.push_back("client_url"); + + std::list labels3 {"product", "system", "module"}; + bvar::MultiDimension > my_madder3("request_from", labels3); + list_exposed_vars.push_back("request_from"); + ASSERT_EQ(3UL, bvar::MVariableBase::count_exposed()); + + std::vector exposed_vars; + bvar::MVariableBase::list_exposed(&exposed_vars); + ASSERT_EQ(3, exposed_vars.size()); + + my_madder3.hide(); + ASSERT_EQ(2UL, bvar::MVariableBase::count_exposed()); + list_exposed_vars.pop_back(); + exposed_vars.clear(); + bvar::MVariableBase::list_exposed(&exposed_vars); + ASSERT_EQ(2, exposed_vars.size()); +} + +TEST_F(MVariableTest, dump) { + std::string old_bvar_dump_interval; + std::string old_mbvar_dump; + std::string old_mbvar_dump_prefix; + std::string old_mbvar_dump_format; + + GFLAGS_NAMESPACE::GetCommandLineOption("bvar_dump_interval", &old_bvar_dump_interval); + GFLAGS_NAMESPACE::GetCommandLineOption("mbvar_dump", &old_mbvar_dump); + GFLAGS_NAMESPACE::GetCommandLineOption("mbvar_dump_prefix", &old_mbvar_dump_prefix); + GFLAGS_NAMESPACE::GetCommandLineOption("mbvar_dump_format", &old_mbvar_dump_format); + + GFLAGS_NAMESPACE::SetCommandLineOption("bvar_dump_interval", "1"); + GFLAGS_NAMESPACE::SetCommandLineOption("mbvar_dump", "true"); + GFLAGS_NAMESPACE::SetCommandLineOption("mbvar_dump_prefix", "my_mdump_prefix"); + GFLAGS_NAMESPACE::SetCommandLineOption("mbvar_dump_format", "common"); + + bvar::MultiDimension > my_madder("dump_adder", labels); + std::list labels_value1 {"gz", "post", "200"}; + bvar::Adder* adder1 = my_madder.get_stats(labels_value1); + ASSERT_TRUE(adder1); + *adder1 << 1 << 3 << 5; + + std::list labels_value2 {"tc", "get", "200"}; + bvar::Adder* adder2 = my_madder.get_stats(labels_value2); + ASSERT_TRUE(adder2); + *adder2 << 2 << 4 << 6; + + std::list labels_value3 {"jx", "post", "500"}; + bvar::Adder* adder3 = my_madder.get_stats(labels_value3); + ASSERT_TRUE(adder3); + *adder3 << 3 << 6 << 9; + + bvar::MultiDimension > my_mmaxer("dump_maxer", labels); + bvar::Maxer* maxer1 = my_mmaxer.get_stats(labels_value1); + ASSERT_TRUE(maxer1); + *maxer1 << 3 << 1 << 5; + + bvar::Maxer* maxer2 = my_mmaxer.get_stats(labels_value2); + ASSERT_TRUE(maxer2); + *maxer2 << 2 << 6 << 4; + + bvar::Maxer* maxer3 = my_mmaxer.get_stats(labels_value3); + ASSERT_TRUE(maxer3); + *maxer3 << 9 << 6 << 3; + + bvar::MultiDimension > my_mminer("dump_miner", labels); + bvar::Miner* miner1 = my_mminer.get_stats(labels_value1); + ASSERT_TRUE(miner1); + *miner1 << 3 << 1 << 5; + + bvar::Miner* miner2 = my_mminer.get_stats(labels_value2); + ASSERT_TRUE(miner2); + *miner2 << 2 << 6 << 4; + + bvar::Miner* miner3 = my_mminer.get_stats(labels_value3); + ASSERT_TRUE(miner3); + *miner3 << 9 << 6 << 3; + + bvar::MultiDimension my_mlatencyrecorder("dump_latencyrecorder", labels); + bvar::LatencyRecorder* my_latencyrecorder1 = my_mlatencyrecorder.get_stats(labels_value1); + ASSERT_TRUE(my_latencyrecorder1); + *my_latencyrecorder1 << 1 << 3 << 5; + *my_latencyrecorder1 << 2 << 4 << 6; + *my_latencyrecorder1 << 3 << 6 << 9; + sleep(2); + + GFLAGS_NAMESPACE::SetCommandLineOption("bvar_dump_interval", old_bvar_dump_interval.c_str()); + GFLAGS_NAMESPACE::SetCommandLineOption("mbvar_dump", old_mbvar_dump.c_str()); + GFLAGS_NAMESPACE::SetCommandLineOption("mbvar_dump_prefix", old_mbvar_dump_prefix.c_str()); + GFLAGS_NAMESPACE::SetCommandLineOption("mbvar_dump_format", old_mbvar_dump_format.c_str()); +} + +TEST_F(MVariableTest, test_describe_exposed) { + std::string bvar_name("request_count_describe"); + bvar::MultiDimension > my_madder1(bvar_name, labels); + + std::string describe_str = bvar::MVariableBase::describe_exposed(bvar_name); + + std::ostringstream describe_oss; + ASSERT_EQ(0, bvar::MVariableBase::describe_exposed(bvar_name, describe_oss)); + ASSERT_STREQ(describe_str.c_str(), describe_oss.str().c_str()); +} diff --git a/test/bvar_percentile_unittest.cpp b/test/bvar_percentile_unittest.cpp new file mode 100644 index 0000000..d9d0184 --- /dev/null +++ b/test/bvar_percentile_unittest.cpp @@ -0,0 +1,192 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2015/09/15 15:42:55 + +#include "bvar/detail/percentile.h" +#include "butil/logging.h" +#include +#include + +class PercentileTest : public testing::Test { +protected: + void SetUp() {} + void TearDown() {} +}; + +#if !WITH_BABYLON_COUNTER +TEST_F(PercentileTest, add) { + bvar::detail::Percentile p; + for (int j = 0; j < 10; ++j) { + for (int i = 0; i < 10000; ++i) { + p << (i + 1); + } + bvar::detail::GlobalPercentileSamples b = p.reset(); + uint32_t last_value = 0; + for (uint32_t k = 1; k <= 10u; ++k) { + uint32_t value = b.get_number(k / 10.0); + EXPECT_GE(value, last_value); + last_value = value; + EXPECT_GT(value, (k * 1000 - 500)) << "k=" << k; + EXPECT_LT(value, (k * 1000 + 500)) << "k=" << k; + } + LOG(INFO) << "99%:" << b.get_number(0.99) << ' ' + << "99.9%:" << b.get_number(0.999) << ' ' + << "99.99%:" << b.get_number(0.9999); + + std::ofstream out("out.txt"); + b.describe(out); + } +} +#endif // !WITH_BABYLON_COUNTER + +TEST_F(PercentileTest, merge1) { + // Merge 2 PercentileIntervals b1 and b2. b2 has double SAMPLE_SIZE + // and num_added. Remaining samples of b1 and b2 in merged result should + // be 1:2 approximately. + const size_t N = 1000; + const size_t SAMPLE_SIZE = 32; + size_t belong_to_b1 = 0; + size_t belong_to_b2 = 0; + + for (int repeat = 0; repeat < 100; ++repeat) { + bvar::detail::PercentileInterval b0; + bvar::detail::PercentileInterval b1; + for (size_t i = 0; i < N; ++i) { + if (b1.full()) { + b0.merge(b1); + b1.clear(); + } + ASSERT_TRUE(b1.add32(i)); + } + b0.merge(b1); + bvar::detail::PercentileInterval b2; + for (size_t i = 0; i < N * 2; ++i) { + if (b2.full()) { + b0.merge(b2); + b2.clear(); + } + ASSERT_TRUE(b2.add32(i + N)); + } + b0.merge(b2); + for (size_t i = 0; i < b0._num_samples; ++i) { + if (b0._samples[i] < N) { + ++belong_to_b1; + } else { + ++belong_to_b2; + } + } + } + EXPECT_LT(fabs(belong_to_b1 / (double)belong_to_b2 - 0.5), + 0.2) << "belong_to_b1=" << belong_to_b1 + << " belong_to_b2=" << belong_to_b2; +} + +TEST_F(PercentileTest, merge2) { + // Merge 2 PercentileIntervals b1 and b2 with same SAMPLE_SIZE. Add N1 + // samples to b1 and add N2 samples to b2, Remaining samples of b1 and + // b2 in merged result should be N1:N2 approximately. + + const size_t N1 = 1000; + const size_t N2 = 400; + size_t belong_to_b1 = 0; + size_t belong_to_b2 = 0; + + for (int repeat = 0; repeat < 100; ++repeat) { + bvar::detail::PercentileInterval<64> b0; + bvar::detail::PercentileInterval<64> b1; + for (size_t i = 0; i < N1; ++i) { + if (b1.full()) { + b0.merge(b1); + b1.clear(); + } + ASSERT_TRUE(b1.add32(i)); + } + b0.merge(b1); + bvar::detail::PercentileInterval<64> b2; + for (size_t i = 0; i < N2; ++i) { + if (b2.full()) { + b0.merge(b2); + b2.clear(); + } + ASSERT_TRUE(b2.add32(i + N1)); + } + b0.merge(b2); + for (size_t i = 0; i < b0._num_samples; ++i) { + if (b0._samples[i] < N1) { + ++belong_to_b1; + } else { + ++belong_to_b2; + } + } + } + EXPECT_LT(fabs(belong_to_b1 / (double)belong_to_b2 - N1 / (double)N2), + 0.2) << "belong_to_b1=" << belong_to_b1 + << " belong_to_b2=" << belong_to_b2; +} + +#if !WITH_BABYLON_COUNTER +TEST_F(PercentileTest, combine_of) { + // Combine multiple percentle samplers into one + const int num_samplers = 10; + // Define a base time to make all samples are in the same interval + const uint32_t base = (1 << 30) + 1; + + const int N = 1000; + size_t belongs[num_samplers] = {0}; + size_t total = 0; + for (int repeat = 0; repeat < 1; ++repeat) { + bvar::detail::Percentile p[num_samplers]; + for (int i = 0; i < num_samplers; ++i) { + for (int j = 0; j < N * (i + 1); ++j) { + p[i] << base + i * (i + 1) * N / 2+ j; + } + } + std::vector result; + result.reserve(num_samplers); + for (int i = 0; i < num_samplers; ++i) { + result.push_back(p[i].get_value()); + } + bvar::detail::PercentileSamples<510> g; + g.combine_of(result.begin(), result.end()); + for (size_t i = 0; i < bvar::detail::NUM_INTERVALS; ++i) { + if (g._intervals[i] == NULL) { + continue; + } + bvar::detail::PercentileInterval<510>& p = *g._intervals[i]; + total += p._num_samples; + for (size_t j = 0; j < p._num_samples; ++j) { + for (int k = 0; k < num_samplers; ++k) { + if ((p._samples[j] - base) / N < (k + 1) * (k + 2) / 2u){ + belongs[k]++; + break; + } + } + } + } + } + for (int i = 0; i < num_samplers; ++i) { + double expect_ratio = (double)(i + 1) * 2 / + (num_samplers * (num_samplers + 1)); + double actual_ratio = (double)(belongs[i]) / total; + EXPECT_LT(fabs(expect_ratio / actual_ratio) - 1, 0.2) + << "expect_ratio=" << expect_ratio + << " actual_ratio=" << actual_ratio; + + } +} +#endif // !WITH_BABYLON_COUNTER diff --git a/test/bvar_recorder_unittest.cpp b/test/bvar_recorder_unittest.cpp new file mode 100644 index 0000000..a385b9b --- /dev/null +++ b/test/bvar_recorder_unittest.cpp @@ -0,0 +1,258 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date 2014/10/13 19:47:59 + +#include // pthread_* + +#include +#include +#include +#include "butil/time.h" +#include "butil/macros.h" +#include "bvar/recorder.h" +#include "bvar/latency_recorder.h" +#include + +namespace { +#if !WITH_BABYLON_COUNTER +TEST(RecorderTest, test_complement) { + LOG(INFO) << "sizeof(LatencyRecorder)=" << sizeof(bvar::LatencyRecorder) + << " " << sizeof(bvar::detail::Percentile) + << " " << sizeof(bvar::Maxer) + << " " << sizeof(bvar::IntRecorder) + << " " << sizeof(bvar::Window) + << " " << sizeof(bvar::Window); + + for (int a = -10000000; a < 10000000; ++a) { + const uint64_t complement = bvar::IntRecorder::_get_complement(a); + const int64_t b = bvar::IntRecorder::_extend_sign_bit(complement); + ASSERT_EQ(a, b); + } +} + +TEST(RecorderTest, test_compress) { + const uint64_t num = 125345; + const uint64_t sum = 26032906; + const uint64_t compressed = bvar::IntRecorder::_compress(num, sum); + ASSERT_EQ(num, bvar::IntRecorder::_get_num(compressed)); + ASSERT_EQ(sum, bvar::IntRecorder::_get_sum(compressed)); +} + +TEST(RecorderTest, test_compress_negtive_number) { + for (int a = -10000000; a < 10000000; ++a) { + const uint64_t sum = bvar::IntRecorder::_get_complement(a); + const uint64_t num = 123456; + const uint64_t compressed = bvar::IntRecorder::_compress(num, sum); + ASSERT_EQ(num, bvar::IntRecorder::_get_num(compressed)); + ASSERT_EQ(a, bvar::IntRecorder::_extend_sign_bit(bvar::IntRecorder::_get_sum(compressed))); + } +} +#endif // !WITH_BABYLON_COUNTER + +TEST(RecorderTest, sanity) { + { + bvar::IntRecorder recorder; + ASSERT_TRUE(recorder.valid()); + ASSERT_EQ(0, recorder.expose("var1")); + for (size_t i = 0; i < 100; ++i) { + recorder << 2; + } + ASSERT_EQ(2l, recorder.average()); + ASSERT_EQ("2", bvar::Variable::describe_exposed("var1")); + std::vector vars; + bvar::Variable::list_exposed(&vars); + ASSERT_EQ(1UL, vars.size()); + ASSERT_EQ("var1", vars[0]); + ASSERT_EQ(1UL, bvar::Variable::count_exposed()); + } + { + bvar::IntRecorder recorder("var2"); + recorder << 2; + ASSERT_EQ(2l, recorder.average()); + } + ASSERT_EQ(0UL, bvar::Variable::count_exposed()); +} + +TEST(RecorderTest, window) { + bvar::IntRecorder c1; + ASSERT_TRUE(c1.valid()); + bvar::Window w1(&c1, 1); + bvar::Window w2(&c1, 2); + bvar::Window w3(&c1, 3); + + const int N = 10000; + int64_t last_time = butil::cpuwide_time_us(); + for (int i = 1; i <= N; ++i) { + c1 << i; + int64_t now = butil::cpuwide_time_us(); + if (now - last_time >= 1000000L) { + last_time = now; + LOG(INFO) << "c1=" << c1 << " w1=" << w1 << " w2=" << w2 << " w3=" << w3; + } else { + usleep(950); + } + } +} + +TEST(RecorderTest, negative) { + bvar::IntRecorder recorder; + ASSERT_TRUE(recorder.valid()); + for (size_t i = 0; i < 3; ++i) { + recorder << -2; + } + ASSERT_EQ(-2, recorder.average()); +} + +TEST(RecorderTest, positive_overflow) { + bvar::IntRecorder recorder1; + ASSERT_TRUE(recorder1.valid()); + for (int i = 0; i < 5; ++i) { + recorder1 << std::numeric_limits::max(); + } + ASSERT_EQ(std::numeric_limits::max(), recorder1.average()); + + bvar::IntRecorder recorder2; + ASSERT_TRUE(recorder2.valid()); + recorder2.set_debug_name("recorder2"); + for (int i = 0; i < 5; ++i) { + recorder2 << std::numeric_limits::max(); + } + ASSERT_EQ(std::numeric_limits::max(), recorder2.average()); + + bvar::IntRecorder recorder3; + ASSERT_TRUE(recorder3.valid()); + recorder3.expose("recorder3"); + for (int i = 0; i < 5; ++i) { + recorder3 << std::numeric_limits::max(); + } + ASSERT_EQ(std::numeric_limits::max(), recorder3.average()); + + bvar::LatencyRecorder latency1; + latency1.expose("latency1"); + latency1 << std::numeric_limits::max(); + + bvar::LatencyRecorder latency2; + latency2 << std::numeric_limits::max(); +} + +TEST(RecorderTest, negtive_overflow) { + bvar::IntRecorder recorder1; + ASSERT_TRUE(recorder1.valid()); + for (int i = 0; i < 5; ++i) { + recorder1 << std::numeric_limits::min(); + } + ASSERT_EQ(std::numeric_limits::min(), recorder1.average()); + + bvar::IntRecorder recorder2; + ASSERT_TRUE(recorder2.valid()); + recorder2.set_debug_name("recorder2"); + for (int i = 0; i < 5; ++i) { + recorder2 << std::numeric_limits::min(); + } + ASSERT_EQ(std::numeric_limits::min(), recorder2.average()); + + bvar::IntRecorder recorder3; + ASSERT_TRUE(recorder3.valid()); + recorder3.expose("recorder3"); + for (int i = 0; i < 5; ++i) { + recorder3 << std::numeric_limits::min(); + } + ASSERT_EQ(std::numeric_limits::min(), recorder3.average()); + + bvar::LatencyRecorder latency1; + latency1.expose("latency1"); + latency1 << std::numeric_limits::min(); + + bvar::LatencyRecorder latency2; + latency2 << std::numeric_limits::min(); +} + +const size_t OPS_PER_THREAD = 20000000; + +static void *thread_counter(void *arg) { + bvar::IntRecorder *recorder = (bvar::IntRecorder *)arg; + butil::Timer timer; + timer.start(); + for (int i = 0; i < (int)OPS_PER_THREAD; ++i) { + *recorder << i; + } + timer.stop(); + return (void *)(timer.n_elapsed()); +} + +TEST(RecorderTest, perf) { + bvar::IntRecorder recorder; + ASSERT_TRUE(recorder.valid()); + pthread_t threads[8]; + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + pthread_create(&threads[i], NULL, &thread_counter, (void *)&recorder); + } + long totol_time = 0; + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + void *ret; + pthread_join(threads[i], &ret); + totol_time += (long)ret; + } + ASSERT_EQ(((int64_t)OPS_PER_THREAD - 1) / 2, recorder.average()); + LOG(INFO) << "Recorder takes " << totol_time / (OPS_PER_THREAD * ARRAY_SIZE(threads)) + << "ns per sample with " << ARRAY_SIZE(threads) + << " threads"; +} + +TEST(RecorderTest, latency_recorder_qps_accuracy) { + bvar::LatencyRecorder lr1(2); // set windows size to 2s + bvar::LatencyRecorder lr2(2); + bvar::LatencyRecorder lr3(2); + bvar::LatencyRecorder lr4(2); + usleep(3000000); // wait sampler to sample 3 times + + auto write = [](bvar::LatencyRecorder& lr, int times) { + for (int i = 0; i < times; ++i) { + lr << 1; + } + }; + write(lr1, 10); + write(lr2, 11); + write(lr3, 3); + write(lr4, 1); + usleep(1000000); // wait sampler to sample 1 time + + auto read = [](bvar::LatencyRecorder& lr, double exp_qps, int window_size = 0) { + int64_t qps_sum = 0; + int64_t exp_qps_int = (int64_t)exp_qps; + for (int i = 0; i < 1000; ++i) { + int64_t qps = window_size ? lr.qps(window_size): lr.qps(); + EXPECT_GE(qps, exp_qps_int - 1); + EXPECT_LE(qps, exp_qps_int + 1); + qps_sum += qps; + } + double err = fabs(qps_sum / 1000.0 - exp_qps); + return err; + }; + ASSERT_GT(0.1, read(lr1, 10/2.0)); + ASSERT_GT(0.1, read(lr2, 11/2.0)); + ASSERT_GT(0.1, read(lr3, 3/2.0)); + ASSERT_GT(0.1, read(lr4, 1/2.0)); + + ASSERT_GT(0.1, read(lr1, 10/3.0, 3)); + ASSERT_GT(0.1, read(lr2, 11/3.0, 3)); + ASSERT_GT(0.1, read(lr3, 3/3.0, 3)); + ASSERT_GT(0.1, read(lr4, 1/3.0, 3)); +} + +} // namespace diff --git a/test/bvar_reducer_unittest.cpp b/test/bvar_reducer_unittest.cpp new file mode 100644 index 0000000..5bd3477 --- /dev/null +++ b/test/bvar_reducer_unittest.cpp @@ -0,0 +1,345 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 //std::numeric_limits + +#include "bvar/reducer.h" + +#include "butil/time.h" +#include "butil/macros.h" +#include "butil/string_printf.h" +#include "butil/string_splitter.h" + +#include + +namespace { +class ReducerTest : public testing::Test { +protected: + void SetUp() {} + void TearDown() {} +}; + +TEST_F(ReducerTest, atomicity) { + ASSERT_EQ(sizeof(int32_t), sizeof(bvar::detail::ElementContainer)); + ASSERT_EQ(sizeof(int64_t), sizeof(bvar::detail::ElementContainer)); + ASSERT_EQ(sizeof(float), sizeof(bvar::detail::ElementContainer)); + ASSERT_EQ(sizeof(double), sizeof(bvar::detail::ElementContainer)); +} + +TEST_F(ReducerTest, adder) { + bvar::Adder reducer1; + ASSERT_TRUE(reducer1.valid()); + reducer1 << 2 << 4; + ASSERT_EQ(6ul, reducer1.get_value()); +#ifdef BAIDU_INTERNAL + boost::any v1; + reducer1.get_value(&v1); + ASSERT_EQ(6u, boost::any_cast(v1)); +#endif + + bvar::Adder reducer2; + ASSERT_TRUE(reducer2.valid()); + reducer2 << 2.0 << 4.0; + ASSERT_DOUBLE_EQ(6.0, reducer2.get_value()); + + bvar::Adder reducer3; + ASSERT_TRUE(reducer3.valid()); + reducer3 << -9 << 1 << 0 << 3; + ASSERT_EQ(-5, reducer3.get_value()); +} + +const size_t OPS_PER_THREAD = 500000; + +static void *thread_counter(void *arg) { + bvar::Adder *reducer = (bvar::Adder *)arg; + butil::Timer timer; + timer.start(); + for (size_t i = 0; i < OPS_PER_THREAD; ++i) { + (*reducer) << 2; + } + timer.stop(); + return (void *)(timer.n_elapsed()); +} + +void *add_atomic(void *arg) { + butil::atomic *counter = (butil::atomic *)arg; + butil::Timer timer; + timer.start(); + for (size_t i = 0; i < OPS_PER_THREAD / 100; ++i) { + counter->fetch_add(2, butil::memory_order_relaxed); + } + timer.stop(); + return (void *)(timer.n_elapsed()); +} + +static long start_perf_test_with_atomic(size_t num_thread) { + butil::atomic counter(0); + pthread_t threads[num_thread]; + for (size_t i = 0; i < num_thread; ++i) { + pthread_create(&threads[i], NULL, &add_atomic, (void *)&counter); + } + long totol_time = 0; + for (size_t i = 0; i < num_thread; ++i) { + void *ret; + pthread_join(threads[i], &ret); + totol_time += (long)ret; + } + long avg_time = totol_time / (OPS_PER_THREAD / 100 * num_thread); + EXPECT_EQ(2ul * num_thread * OPS_PER_THREAD / 100, counter.load()); + return avg_time; +} + +static long start_perf_test_with_adder(size_t num_thread) { + bvar::Adder reducer; + EXPECT_TRUE(reducer.valid()); + pthread_t threads[num_thread]; + for (size_t i = 0; i < num_thread; ++i) { + pthread_create(&threads[i], NULL, &thread_counter, (void *)&reducer); + } + long totol_time = 0; + for (size_t i = 0; i < num_thread; ++i) { + void *ret = NULL; + pthread_join(threads[i], &ret); + totol_time += (long)ret; + } + long avg_time = totol_time / (OPS_PER_THREAD * num_thread); + EXPECT_EQ(2ul * num_thread * OPS_PER_THREAD, reducer.get_value()); + return avg_time; +} + +TEST_F(ReducerTest, perf) { + std::ostringstream oss; + for (size_t i = 1; i <= 24; ++i) { + oss << i << '\t' << start_perf_test_with_adder(i) << '\n'; + } + LOG(INFO) << "Adder performance:\n" << oss.str(); + oss.str(""); + for (size_t i = 1; i <= 24; ++i) { + oss << i << '\t' << start_perf_test_with_atomic(i) << '\n'; + } + LOG(INFO) << "Atomic performance:\n" << oss.str(); +} + +TEST_F(ReducerTest, Min) { + bvar::Miner reducer; + ASSERT_EQ(std::numeric_limits::max(), reducer.get_value()); + reducer << 10 << 20; + ASSERT_EQ(10ul, reducer.get_value()); + reducer << 5; + ASSERT_EQ(5ul, reducer.get_value()); + reducer << std::numeric_limits::max(); + ASSERT_EQ(5ul, reducer.get_value()); + reducer << 0; + ASSERT_EQ(0ul, reducer.get_value()); + + bvar::Miner reducer2; + ASSERT_EQ(std::numeric_limits::max(), reducer2.get_value()); + reducer2 << 10 << 20; + ASSERT_EQ(10, reducer2.get_value()); + reducer2 << -5; + ASSERT_EQ(-5, reducer2.get_value()); + reducer2 << std::numeric_limits::max(); + ASSERT_EQ(-5, reducer2.get_value()); + reducer2 << 0; + ASSERT_EQ(-5, reducer2.get_value()); + reducer2 << std::numeric_limits::min(); + ASSERT_EQ(std::numeric_limits::min(), reducer2.get_value()); +} + +TEST_F(ReducerTest, max) { + bvar::Maxer reducer; + ASSERT_EQ(std::numeric_limits::min(), reducer.get_value()); + ASSERT_TRUE(reducer.valid()); + reducer << 20 << 10; + ASSERT_EQ(20ul, reducer.get_value()); + reducer << 30; + ASSERT_EQ(30ul, reducer.get_value()); + reducer << 0; + ASSERT_EQ(30ul, reducer.get_value()); + + bvar::Maxer reducer2; + ASSERT_EQ(std::numeric_limits::min(), reducer2.get_value()); + ASSERT_TRUE(reducer2.valid()); + reducer2 << 20 << 10; + ASSERT_EQ(20, reducer2.get_value()); + reducer2 << 30; + ASSERT_EQ(30, reducer2.get_value()); + reducer2 << 0; + ASSERT_EQ(30, reducer2.get_value()); + reducer2 << std::numeric_limits::max(); + ASSERT_EQ(std::numeric_limits::max(), reducer2.get_value()); +} + +bvar::Adder g_a; + +TEST_F(ReducerTest, global) { + ASSERT_TRUE(g_a.valid()); + g_a.get_value(); +} + +void ReducerTest_window() { + bvar::Adder c1; + bvar::Maxer c2; + bvar::Miner c3; + bvar::Window > w1(&c1, 1); + bvar::Window > w2(&c1, 2); + bvar::Window > w3(&c1, 3); + bvar::Window > w4(&c2, 1); + bvar::Window > w5(&c2, 2); + bvar::Window > w6(&c2, 3); + bvar::Window > w7(&c3, 1); + bvar::Window > w8(&c3, 2); + bvar::Window > w9(&c3, 3); + +#if !BRPC_WITH_GLOG + logging::StringSink log_str; + logging::LogSink* old_sink = logging::SetLogSink(&log_str); + c2.get_value(); + ASSERT_EQ(&log_str, logging::SetLogSink(old_sink)); + ASSERT_NE(std::string::npos, log_str.find( + "You should not call Reducer>" + "::get_value() when a Window<> is used because the operator" + " does not have inverse.")); +#endif + const int N = 6000; + int count = 0; + int total_count = 0; + int64_t last_time = butil::cpuwide_time_us(); + for (int i = 1; i <= N; ++i) { + c1 << 1; + c2 << N - i; + c3 << i; + ++count; + ++total_count; + int64_t now = butil::cpuwide_time_us(); + if (now - last_time >= 1000000L) { + last_time = now; + ASSERT_EQ(total_count, c1.get_value()); + LOG(INFO) << "c1=" << total_count + << " count=" << count + << " w1=" << w1 + << " w2=" << w2 + << " w3=" << w3 + << " w4=" << w4 + << " w5=" << w5 + << " w6=" << w6 + << " w7=" << w7 + << " w8=" << w8 + << " w9=" << w9; + count = 0; + } else { + usleep(950); + } + } +} + +TEST_F(ReducerTest, window) { + ReducerTest_window(); +#if !BRPC_WITH_GLOG + logging::StringSink log_str; + logging::LogSink* old_sink = logging::SetLogSink(&log_str); + sleep(1); + ASSERT_EQ(&log_str, logging::SetLogSink(old_sink)); + if (log_str.find("Removed ") != std::string::npos) { + ASSERT_NE(std::string::npos, log_str.find("Removed 3, sampled 0")) << log_str; + } +#endif +} + +struct Foo { + int x; + Foo() : x(0) {} + explicit Foo(int x2) : x(x2) {} + void operator+=(const Foo& rhs) { + x += rhs.x; + } +}; + +std::ostream& operator<<(std::ostream& os, const Foo& f) { + return os << "Foo{" << f.x << "}"; +} + +TEST_F(ReducerTest, non_primitive) { + bvar::Adder adder; + adder << Foo(2) << Foo(3) << Foo(4); + ASSERT_EQ(9, adder.get_value().x); +} + +bool g_stop = false; +struct StringAppenderResult { + int count; +}; + +static void* string_appender(void* arg) { + bvar::Adder* cater = (bvar::Adder*)arg; + int count = 0; + std::string id = butil::string_printf("%lld", (long long)pthread_self()); + std::string tmp = "a"; + for (count = 0; !count || !g_stop; ++count) { + *cater << id << ":"; + for (char c = 'a'; c <= 'z'; ++c) { + tmp[0] = c; + *cater << tmp; + } + *cater << "."; + } + StringAppenderResult* res = new StringAppenderResult; + res->count = count; + LOG(INFO) << "Appended " << count; + return res; +} + +TEST_F(ReducerTest, non_primitive_mt) { + bvar::Adder cater; + pthread_t th[8]; + g_stop = false; + for (size_t i = 0; i < arraysize(th); ++i) { + pthread_create(&th[i], NULL, string_appender, &cater); + } + usleep(50000); + g_stop = true; + butil::hash_map appended_count; + for (size_t i = 0; i < arraysize(th); ++i) { + StringAppenderResult* res = NULL; + pthread_join(th[i], (void**)&res); + appended_count[th[i]] = res->count; + delete res; + } + butil::hash_map got_count; + std::string res = cater.get_value(); + for (butil::StringSplitter sp(res.c_str(), '.'); sp; ++sp) { + char* endptr = NULL; + ++got_count[(pthread_t)strtoll(sp.field(), &endptr, 10)]; + ASSERT_EQ(27LL, sp.field() + sp.length() - endptr) + << butil::StringPiece(sp.field(), sp.length()); + ASSERT_EQ(0, memcmp(":abcdefghijklmnopqrstuvwxyz", endptr, 27)); + } + ASSERT_EQ(appended_count.size(), got_count.size()); + for (size_t i = 0; i < arraysize(th); ++i) { + ASSERT_EQ(appended_count[th[i]], got_count[th[i]]); + } +} + +TEST_F(ReducerTest, simple_window) { + bvar::Adder a; + bvar::Window > w(&a, 10); + a << 100; + sleep(3); + const int64_t v = w.get_value(); + ASSERT_EQ(100, v) << "v=" << v; +} +} // namespace diff --git a/test/bvar_sampler_unittest.cpp b/test/bvar_sampler_unittest.cpp new file mode 100644 index 0000000..0a521a4 --- /dev/null +++ b/test/bvar_sampler_unittest.cpp @@ -0,0 +1,139 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 //std::numeric_limits +#include "bvar/detail/sampler.h" +#include "butil/time.h" +#include "butil/logging.h" +#include + +namespace { + +TEST(SamplerTest, linked_list) { + butil::LinkNode n1, n2; + n1.InsertBeforeAsList(&n2); + ASSERT_EQ(n1.next(), &n2); + ASSERT_EQ(n1.previous(), &n2); + ASSERT_EQ(n2.next(), &n1); + ASSERT_EQ(n2.previous(), &n1); + + butil::LinkNode n3, n4; + n3.InsertBeforeAsList(&n4); + ASSERT_EQ(n3.next(), &n4); + ASSERT_EQ(n3.previous(), &n4); + ASSERT_EQ(n4.next(), &n3); + ASSERT_EQ(n4.previous(), &n3); + + n1.InsertBeforeAsList(&n3); + ASSERT_EQ(n1.next(), &n2); + ASSERT_EQ(n2.next(), &n3); + ASSERT_EQ(n3.next(), &n4); + ASSERT_EQ(n4.next(), &n1); + ASSERT_EQ(&n1, n2.previous()); + ASSERT_EQ(&n2, n3.previous()); + ASSERT_EQ(&n3, n4.previous()); + ASSERT_EQ(&n4, n1.previous()); +} + +class DebugSampler : public bvar::detail::Sampler { +public: + DebugSampler() : _ncalled(0) {} + ~DebugSampler() { + ++_s_ndestroy; + } + void take_sample() { + ++_ncalled; + } + int called_count() const { return _ncalled; } +private: + int _ncalled; + static int _s_ndestroy; +}; +int DebugSampler::_s_ndestroy = 0; + +TEST(SamplerTest, single_threaded) { +#if !BRPC_WITH_GLOG + logging::StringSink log_str; + logging::LogSink* old_sink = logging::SetLogSink(&log_str); +#endif + const int N = 100; + DebugSampler* s[N]; + for (int i = 0; i < N; ++i) { + s[i] = new DebugSampler; + s[i]->schedule(); + } + usleep(1010000); + for (int i = 0; i < N; ++i) { + // LE: called once every second, may be called more than once + ASSERT_LE(1, s[i]->called_count()) << "i=" << i; + } + EXPECT_EQ(0, DebugSampler::_s_ndestroy); + for (int i = 0; i < N; ++i) { + s[i]->destroy(); + } + usleep(1010000); + EXPECT_EQ(N, DebugSampler::_s_ndestroy); +#if !BRPC_WITH_GLOG + ASSERT_EQ(&log_str, logging::SetLogSink(old_sink)); + if (log_str.find("Removed ") != std::string::npos) { + ASSERT_NE(std::string::npos, log_str.find("Removed 0, sampled 100")); + ASSERT_NE(std::string::npos, log_str.find("Removed 100, sampled 0")); + } +#endif +} + +static void* check(void*) { + const int N = 100; + DebugSampler* s[N]; + for (int i = 0; i < N; ++i) { + s[i] = new DebugSampler; + s[i]->schedule(); + } + usleep(1010000); + for (int i = 0; i < N; ++i) { + EXPECT_LE(1, s[i]->called_count()) << "i=" << i; + } + for (int i = 0; i < N; ++i) { + s[i]->destroy(); + } + return NULL; +} + +TEST(SamplerTest, multi_threaded) { +#if !BRPC_WITH_GLOG + logging::StringSink log_str; + logging::LogSink* old_sink = logging::SetLogSink(&log_str); +#endif + pthread_t th[10]; + DebugSampler::_s_ndestroy = 0; + for (size_t i = 0; i < arraysize(th); ++i) { + ASSERT_EQ(0, pthread_create(&th[i], NULL, check, NULL)); + } + for (size_t i = 0; i < arraysize(th); ++i) { + ASSERT_EQ(0, pthread_join(th[i], NULL)); + } + sleep(1); + EXPECT_EQ(100 * arraysize(th), (size_t)DebugSampler::_s_ndestroy); +#if !BRPC_WITH_GLOG + ASSERT_EQ(&log_str, logging::SetLogSink(old_sink)); + if (log_str.find("Removed ") != std::string::npos) { + ASSERT_NE(std::string::npos, log_str.find("Removed 0, sampled 1000")); + ASSERT_NE(std::string::npos, log_str.find("Removed 1000, sampled 0")); + } +#endif +} +} // namespace diff --git a/test/bvar_status_unittest.cpp b/test/bvar_status_unittest.cpp new file mode 100644 index 0000000..0bce28b --- /dev/null +++ b/test/bvar_status_unittest.cpp @@ -0,0 +1,207 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date 2014/10/13 19:47:59 + +#include // pthread_* + +#include +#include +#include +#include +#include "butil/time.h" +#include "butil/macros.h" +#include "bvar/bvar.h" +#include + +namespace { +class StatusTest : public testing::Test { +protected: + void SetUp() { + } + void TearDown() { + ASSERT_EQ(0UL, bvar::Variable::count_exposed()); + } +}; + +TEST_F(StatusTest, status) { + bvar::Status st1; + st1.set_value("hello %d", 9); +#ifdef BAIDU_INTERNAL + boost::any v1; + st1.get_value(&v1); + ASSERT_EQ("hello 9", boost::any_cast(v1)); +#endif + ASSERT_EQ(0, st1.expose("var1")); + ASSERT_EQ("hello 9", bvar::Variable::describe_exposed("var1")); + ASSERT_EQ("\"hello 9\"", bvar::Variable::describe_exposed("var1", true)); + std::vector vars; + bvar::Variable::list_exposed(&vars); + ASSERT_EQ(1UL, vars.size()); + ASSERT_EQ("var1", vars[0]); + ASSERT_EQ(1UL, bvar::Variable::count_exposed()); + + bvar::Status st2; + st2.set_value("world %d", 10); + ASSERT_EQ(-1, st2.expose("var1")); + ASSERT_EQ(1UL, bvar::Variable::count_exposed()); + ASSERT_EQ("world 10", st2.get_description()); + ASSERT_EQ("hello 9", bvar::Variable::describe_exposed("var1")); + ASSERT_EQ(1UL, bvar::Variable::count_exposed()); + + ASSERT_TRUE(st1.hide()); + ASSERT_EQ(0UL, bvar::Variable::count_exposed()); + ASSERT_EQ("", bvar::Variable::describe_exposed("var1")); + ASSERT_EQ(0, st1.expose("var1")); + ASSERT_EQ(1UL, bvar::Variable::count_exposed()); + ASSERT_EQ("hello 9", + bvar::Variable::describe_exposed("var1")); + + ASSERT_EQ(0, st2.expose("var2")); + ASSERT_EQ(2UL, bvar::Variable::count_exposed()); + ASSERT_EQ("hello 9", bvar::Variable::describe_exposed("var1")); + ASSERT_EQ("world 10", bvar::Variable::describe_exposed("var2")); + bvar::Variable::list_exposed(&vars); + ASSERT_EQ(2UL, vars.size()); + ASSERT_EQ("var1", vars[0]); + ASSERT_EQ("var2", vars[1]); + + ASSERT_TRUE(st2.hide()); + ASSERT_EQ(1UL, bvar::Variable::count_exposed()); + ASSERT_EQ("", bvar::Variable::describe_exposed("var2")); + bvar::Variable::list_exposed(&vars); + ASSERT_EQ(1UL, vars.size()); + ASSERT_EQ("var1", vars[0]); + + st2.expose("var2 again"); + ASSERT_EQ("world 10", bvar::Variable::describe_exposed("var2_again")); + bvar::Variable::list_exposed(&vars); + ASSERT_EQ(2UL, vars.size()); + ASSERT_EQ("var1", vars[0]); + ASSERT_EQ("var2_again", vars[1]); + ASSERT_EQ(2UL, bvar::Variable::count_exposed()); + + bvar::Status st3("var3", "foobar"); + ASSERT_EQ("var3", st3.name()); + ASSERT_EQ(3UL, bvar::Variable::count_exposed()); + ASSERT_EQ("foobar", bvar::Variable::describe_exposed("var3")); + bvar::Variable::list_exposed(&vars); + ASSERT_EQ(3UL, vars.size()); + ASSERT_EQ("var1", vars[0]); + ASSERT_EQ("var3", vars[1]); + ASSERT_EQ("var2_again", vars[2]); + ASSERT_EQ(3UL, bvar::Variable::count_exposed()); + + bvar::Status st4("var4", 9); + ASSERT_EQ("var4", st4.name()); + ASSERT_EQ(4UL, bvar::Variable::count_exposed()); +#ifdef BAIDU_INTERNAL + boost::any v4; + st4.get_value(&v4); + ASSERT_EQ(9, boost::any_cast(v4)); +#endif + ASSERT_EQ("9", bvar::Variable::describe_exposed("var4")); + bvar::Variable::list_exposed(&vars); + ASSERT_EQ(4UL, vars.size()); + ASSERT_EQ("var1", vars[0]); + ASSERT_EQ("var3", vars[1]); + ASSERT_EQ("var4", vars[2]); + ASSERT_EQ("var2_again", vars[3]); + + bvar::Status st5((void*)19UL); + LOG(INFO) << st5; +#ifdef BAIDU_INTERNAL + boost::any v5; + st5.get_value(&v5); + ASSERT_EQ((void*)19UL, boost::any_cast(v5)); +#endif + ASSERT_EQ("0x13", st5.get_description()); +} + +void print1(std::ostream& os, void* arg) { + os << arg; +} + +int64_t print2(void* arg) { + return *(int64_t*)arg; +} + +TEST_F(StatusTest, passive_status) { + bvar::BasicPassiveStatus st1("var11", print1, (void*)9UL); + LOG(INFO) << st1; +#ifdef BAIDU_INTERNAL + boost::any v1; + st1.get_value(&v1); + ASSERT_EQ("0x9", boost::any_cast(v1)); +#endif + std::ostringstream ss; + ASSERT_EQ(0, bvar::Variable::describe_exposed("var11", ss)); + ASSERT_EQ("0x9", ss.str()); + std::vector vars; + bvar::Variable::list_exposed(&vars); + ASSERT_EQ(1UL, vars.size()); + ASSERT_EQ("var11", vars[0]); + ASSERT_EQ(1UL, bvar::Variable::count_exposed()); + + int64_t tmp2 = 9; + bvar::BasicPassiveStatus st2("var12", print2, &tmp2); +#ifdef BAIDU_INTERNAL + boost::any v2; + st2.get_value(&v2); + try { + boost::any_cast(v2); + ASSERT_TRUE(false); + } catch (boost::bad_any_cast & e) { + LOG(INFO) << "Casting int64_t to int32_t throws."; + } + ASSERT_EQ(9, boost::any_cast(v2)); +#endif + ss.str(""); + ASSERT_EQ(0, bvar::Variable::describe_exposed("var12", ss)); + ASSERT_EQ("9", ss.str()); + bvar::Variable::list_exposed(&vars); + ASSERT_EQ(2UL, vars.size()); + ASSERT_EQ("var11", vars[0]); + ASSERT_EQ("var12", vars[1]); + ASSERT_EQ(2UL, bvar::Variable::count_exposed()); +} + +struct Foo { + int x; + Foo() : x(0) {} + explicit Foo(int x2) : x(x2) {} + Foo operator+(const Foo& rhs) const { + return Foo(x + rhs.x); + } +}; + +std::ostream& operator<<(std::ostream& os, const Foo& f) { + return os << "Foo{" << f.x << "}"; +} + +TEST_F(StatusTest, non_primitive) { + bvar::Status st; + ASSERT_EQ(0, st.get_value().x); + st.set_value(Foo(1)); + ASSERT_EQ(1, st.get_value().x); +#ifdef BAIDU_INTERNAL + boost::any a1; + st.get_value(&a1); + ASSERT_EQ(1, boost::any_cast(a1).x); +#endif +} +} // namespace diff --git a/test/bvar_variable_unittest.cpp b/test/bvar_variable_unittest.cpp new file mode 100644 index 0000000..55a0e4b --- /dev/null +++ b/test/bvar_variable_unittest.cpp @@ -0,0 +1,417 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: Fri Jul 24 17:19:40 CST 2015 + +#include // pthread_* + +#include +#include +#include +#include +#include "butil/time.h" +#include "butil/macros.h" + +#include "bvar/bvar.h" + +#include +#include + +namespace bvar { +DECLARE_bool(bvar_log_dumpped); +} + +namespace { + +// overloading for operator<< does not work for gflags>=2.1 +template +std::string vec2string(const std::vector& vec) { + std::ostringstream os; + os << '['; + if (!vec.empty()) { + os << vec[0]; + for (size_t i = 1; i < vec.size(); ++i) { + os << ',' << vec[i]; + } + } + os << ']'; + return os.str(); +} + +class VariableTest : public testing::Test { +protected: + void SetUp() { + } + void TearDown() { + ASSERT_EQ(0UL, bvar::Variable::count_exposed()); + } +}; + +TEST_F(VariableTest, status) { + bvar::Status st1; + st1.set_value(9); + ASSERT_TRUE(st1.is_hidden()); +#ifdef BAIDU_INTERNAL + boost::any v1; + st1.get_value(&v1); + ASSERT_EQ(9, boost::any_cast(v1)); +#endif + ASSERT_EQ(0, st1.expose("var1")); + ASSERT_FALSE(st1.is_hidden()); + ASSERT_EQ("9", bvar::Variable::describe_exposed("var1")); + std::vector vars; + bvar::Variable::list_exposed(&vars); + ASSERT_EQ(1UL, vars.size()); + ASSERT_EQ("var1", vars[0]); + ASSERT_EQ(1UL, bvar::Variable::count_exposed()); + + bvar::Status st2; + st2.set_value(10); + ASSERT_TRUE(st2.is_hidden()); + ASSERT_EQ(-1, st2.expose("var1")); + ASSERT_TRUE(st2.is_hidden()); + ASSERT_EQ(1UL, bvar::Variable::count_exposed()); + ASSERT_EQ("10", st2.get_description()); + ASSERT_EQ("9", bvar::Variable::describe_exposed("var1")); + ASSERT_EQ(1UL, bvar::Variable::count_exposed()); + + ASSERT_TRUE(st1.hide()); + ASSERT_TRUE(st1.is_hidden()); + ASSERT_EQ(0UL, bvar::Variable::count_exposed()); + ASSERT_EQ("", bvar::Variable::describe_exposed("var1")); + ASSERT_EQ(0, st1.expose("var1")); + ASSERT_EQ(1UL, bvar::Variable::count_exposed()); + ASSERT_EQ("9", bvar::Variable::describe_exposed("var1")); + + ASSERT_EQ(0, st2.expose("var2")); + ASSERT_EQ(2UL, bvar::Variable::count_exposed()); + ASSERT_EQ("9", bvar::Variable::describe_exposed("var1")); + ASSERT_EQ("10", bvar::Variable::describe_exposed("var2")); + bvar::Variable::list_exposed(&vars); + ASSERT_EQ(2UL, vars.size()); + ASSERT_EQ("var1", vars[0]); + ASSERT_EQ("var2", vars[1]); + + ASSERT_TRUE(st2.hide()); + ASSERT_EQ(1UL, bvar::Variable::count_exposed()); + ASSERT_EQ("", bvar::Variable::describe_exposed("var2")); + bvar::Variable::list_exposed(&vars); + ASSERT_EQ(1UL, vars.size()); + ASSERT_EQ("var1", vars[0]); + + ASSERT_EQ(0, st2.expose("Var2 Again")); + ASSERT_EQ("", bvar::Variable::describe_exposed("Var2 Again")); + ASSERT_EQ("10", bvar::Variable::describe_exposed("var2_again")); + bvar::Variable::list_exposed(&vars); + ASSERT_EQ(2UL, vars.size()); + ASSERT_EQ("var1", vars[0]); + ASSERT_EQ("var2_again", vars[1]); + ASSERT_EQ(2UL, bvar::Variable::count_exposed()); + + bvar::Status st3("var3", 11); + ASSERT_EQ("var3", st3.name()); + ASSERT_EQ(3UL, bvar::Variable::count_exposed()); + ASSERT_EQ("11", bvar::Variable::describe_exposed("var3")); + bvar::Variable::list_exposed(&vars); + ASSERT_EQ(3UL, vars.size()); + ASSERT_EQ("var1", vars[0]); + ASSERT_EQ("var3", vars[1]); + ASSERT_EQ("var2_again", vars[2]); + ASSERT_EQ(3UL, bvar::Variable::count_exposed()); + + bvar::Status st4("var4", 12); + ASSERT_EQ("var4", st4.name()); + ASSERT_EQ(4UL, bvar::Variable::count_exposed()); +#ifdef BAIDU_INTERNAL + boost::any v4; + st4.get_value(&v4); + ASSERT_EQ(12, boost::any_cast(v4)); +#endif + ASSERT_EQ("12", bvar::Variable::describe_exposed("var4")); + bvar::Variable::list_exposed(&vars); + ASSERT_EQ(4UL, vars.size()); + ASSERT_EQ("var1", vars[0]); + ASSERT_EQ("var3", vars[1]); + ASSERT_EQ("var4", vars[2]); + ASSERT_EQ("var2_again", vars[3]); + + bvar::Status st5((void*)19UL); + LOG(INFO) << st5; +#ifdef BAIDU_INTERNAL + boost::any v5; + st5.get_value(&v5); + ASSERT_EQ((void*)19UL, boost::any_cast(v5)); +#endif + ASSERT_EQ("0x13", st5.get_description()); +} + +namespace foo { +namespace bar { +class Apple {}; +class BaNaNa {}; +class Car_Rot {}; +class RPCTest {}; +class HELLO {}; +} +} + +TEST_F(VariableTest, expose) { + bvar::Status c1; + ASSERT_EQ(0, c1.expose_as("foo::bar::Apple", "c1")); + ASSERT_EQ("foo_bar_apple_c1", c1.name()); + ASSERT_EQ(1UL, bvar::Variable::count_exposed()); + + ASSERT_EQ(0, c1.expose_as("foo.bar::BaNaNa", "c1")); + ASSERT_EQ("foo_bar_ba_na_na_c1", c1.name()); + ASSERT_EQ(1UL, bvar::Variable::count_exposed()); + + ASSERT_EQ(0, c1.expose_as("foo::bar.Car_Rot", "c1")); + ASSERT_EQ("foo_bar_car_rot_c1", c1.name()); + ASSERT_EQ(1UL, bvar::Variable::count_exposed()); + + ASSERT_EQ(0, c1.expose_as("foo-bar-RPCTest", "c1")); + ASSERT_EQ("foo_bar_rpctest_c1", c1.name()); + ASSERT_EQ(1UL, bvar::Variable::count_exposed()); + + ASSERT_EQ(0, c1.expose_as("foo-bar-HELLO", "c1")); + ASSERT_EQ("foo_bar_hello_c1", c1.name()); + ASSERT_EQ(1UL, bvar::Variable::count_exposed()); + + ASSERT_EQ(0, c1.expose("c1")); + ASSERT_EQ("c1", c1.name()); + ASSERT_EQ(1UL, bvar::Variable::count_exposed()); +} + +class MyDumper : public bvar::Dumper { +public: + bool dump(const std::string& name, + const butil::StringPiece& description) { + _list.push_back(std::make_pair(name, description.as_string())); + return true; + } + + std::vector > _list; +}; + +int print_int(void*) { + return 5; +} + +TEST_F(VariableTest, dump) { + MyDumper d; + + // Nothing to dump yet. + bvar::FLAGS_bvar_log_dumpped = true; + ASSERT_EQ(0, bvar::Variable::dump_exposed(&d, NULL)); + ASSERT_TRUE(d._list.empty()); + + bvar::Adder v2("var2"); + v2 << 2; + bvar::Status v1("var1", 1); + bvar::Status v1_2("var1", 12); + bvar::Status v3("foo.bar.Apple", "var3", 3); + bvar::Adder v4("foo.bar.BaNaNa", "var4"); + v4 << 4; + bvar::BasicPassiveStatus v5( + "foo::bar::Car_Rot", "var5", print_int, NULL); + + ASSERT_EQ(5, bvar::Variable::dump_exposed(&d, NULL)); + ASSERT_EQ(5UL, d._list.size()); + int i = 0; + ASSERT_EQ("foo_bar_apple_var3", d._list[i++ / 2].first); + ASSERT_EQ("3", d._list[i++ / 2].second); + ASSERT_EQ("foo_bar_ba_na_na_var4", d._list[i++ / 2].first); + ASSERT_EQ("4", d._list[i++ / 2].second); + ASSERT_EQ("foo_bar_car_rot_var5", d._list[i++ / 2].first); + ASSERT_EQ("5", d._list[i++ / 2].second); + ASSERT_EQ("var1", d._list[i++ / 2].first); + ASSERT_EQ("1", d._list[i++ / 2].second); + ASSERT_EQ("var2", d._list[i++ / 2].first); + ASSERT_EQ("2", d._list[i++ / 2].second); + + d._list.clear(); + bvar::DumpOptions opts; + opts.white_wildcards = "foo_bar_*"; + opts.black_wildcards = "*var5"; + ASSERT_EQ(2, bvar::Variable::dump_exposed(&d, &opts)); + ASSERT_EQ(2UL, d._list.size()); + i = 0; + ASSERT_EQ("foo_bar_apple_var3", d._list[i++ / 2].first); + ASSERT_EQ("3", d._list[i++ / 2].second); + ASSERT_EQ("foo_bar_ba_na_na_var4", d._list[i++ / 2].first); + ASSERT_EQ("4", d._list[i++ / 2].second); + + d._list.clear(); + opts = bvar::DumpOptions(); + opts.white_wildcards = "*?rot*"; + ASSERT_EQ(1, bvar::Variable::dump_exposed(&d, &opts)); + ASSERT_EQ(1UL, d._list.size()); + i = 0; + ASSERT_EQ("foo_bar_car_rot_var5", d._list[i++ / 2].first); + ASSERT_EQ("5", d._list[i++ / 2].second); + + d._list.clear(); + opts = bvar::DumpOptions(); + opts.white_wildcards = ""; + opts.black_wildcards = "var2;var1"; + ASSERT_EQ(3, bvar::Variable::dump_exposed(&d, &opts)); + ASSERT_EQ(3UL, d._list.size()); + i = 0; + ASSERT_EQ("foo_bar_apple_var3", d._list[i++ / 2].first); + ASSERT_EQ("3", d._list[i++ / 2].second); + ASSERT_EQ("foo_bar_ba_na_na_var4", d._list[i++ / 2].first); + ASSERT_EQ("4", d._list[i++ / 2].second); + ASSERT_EQ("foo_bar_car_rot_var5", d._list[i++ / 2].first); + ASSERT_EQ("5", d._list[i++ / 2].second); + + d._list.clear(); + opts = bvar::DumpOptions(); + opts.white_wildcards = ""; + opts.black_wildcards = "f?o_b?r_*;not_exist"; + ASSERT_EQ(2, bvar::Variable::dump_exposed(&d, &opts)); + ASSERT_EQ(2UL, d._list.size()); + i = 0; + ASSERT_EQ("var1", d._list[i++ / 2].first); + ASSERT_EQ("1", d._list[i++ / 2].second); + ASSERT_EQ("var2", d._list[i++ / 2].first); + ASSERT_EQ("2", d._list[i++ / 2].second); + + d._list.clear(); + opts = bvar::DumpOptions(); + opts.question_mark = '$'; + opts.white_wildcards = ""; + opts.black_wildcards = "f$o_b$r_*;not_exist"; + ASSERT_EQ(2, bvar::Variable::dump_exposed(&d, &opts)); + ASSERT_EQ(2UL, d._list.size()); + i = 0; + ASSERT_EQ("var1", d._list[i++ / 2].first); + ASSERT_EQ("1", d._list[i++ / 2].second); + ASSERT_EQ("var2", d._list[i++ / 2].first); + ASSERT_EQ("2", d._list[i++ / 2].second); + + d._list.clear(); + opts = bvar::DumpOptions(); + opts.white_wildcards = "not_exist"; + ASSERT_EQ(0, bvar::Variable::dump_exposed(&d, &opts)); + ASSERT_EQ(0UL, d._list.size()); + + d._list.clear(); + opts = bvar::DumpOptions(); + opts.white_wildcards = "not_exist;f??o_bar*"; + ASSERT_EQ(0, bvar::Variable::dump_exposed(&d, &opts)); + ASSERT_EQ(0UL, d._list.size()); +} + +TEST_F(VariableTest, latency_recorder) { + bvar::LatencyRecorder rec; + rec << 1 << 2 << 3; + ASSERT_EQ(3, rec.count()); + ASSERT_EQ(-1, rec.expose("")); + ASSERT_EQ(-1, rec.expose("latency")); + ASSERT_EQ(-1, rec.expose("Latency")); + + std::string saved_bvar_latency_p1; + std::string saved_bvar_latency_p2; + std::string saved_bvar_latency_p3; + + GFLAGS_NAMESPACE::GetCommandLineOption("bvar_latency_p1", &saved_bvar_latency_p1); + GFLAGS_NAMESPACE::GetCommandLineOption("bvar_latency_p2", &saved_bvar_latency_p2); + GFLAGS_NAMESPACE::GetCommandLineOption("bvar_latency_p3", &saved_bvar_latency_p3); + + GFLAGS_NAMESPACE::SetCommandLineOption("bvar_latency_p1", "80"); + GFLAGS_NAMESPACE::SetCommandLineOption("bvar_latency_p2", "90"); + GFLAGS_NAMESPACE::SetCommandLineOption("bvar_latency_p3", "99"); + + + ASSERT_EQ(0, rec.expose("FooBar__latency")); + std::vector names; + bvar::Variable::list_exposed(&names); + std::sort(names.begin(), names.end()); + ASSERT_EQ(11UL, names.size()) << vec2string(names); + ASSERT_EQ("foo_bar_count", names[0]); + ASSERT_EQ("foo_bar_latency", names[1]); + ASSERT_EQ("foo_bar_latency_80", names[2]); + ASSERT_EQ("foo_bar_latency_90", names[3]); + ASSERT_EQ("foo_bar_latency_99", names[4]); + ASSERT_EQ("foo_bar_latency_999", names[5]); + ASSERT_EQ("foo_bar_latency_9999", names[6]); + ASSERT_EQ("foo_bar_latency_cdf", names[7]); + ASSERT_EQ("foo_bar_latency_percentiles", names[8]); + ASSERT_EQ("foo_bar_max_latency", names[9]); + ASSERT_EQ("foo_bar_qps", names[10]); + + ASSERT_EQ(0, rec.expose("ApplePie")); + bvar::Variable::list_exposed(&names); + std::sort(names.begin(), names.end()); + ASSERT_EQ(11UL, names.size()); + ASSERT_EQ("apple_pie_count", names[0]); + ASSERT_EQ("apple_pie_latency", names[1]); + ASSERT_EQ("apple_pie_latency_80", names[2]); + ASSERT_EQ("apple_pie_latency_90", names[3]); + ASSERT_EQ("apple_pie_latency_99", names[4]); + ASSERT_EQ("apple_pie_latency_999", names[5]); + ASSERT_EQ("apple_pie_latency_9999", names[6]); + ASSERT_EQ("apple_pie_latency_cdf", names[7]); + ASSERT_EQ("apple_pie_latency_percentiles", names[8]); + ASSERT_EQ("apple_pie_max_latency", names[9]); + ASSERT_EQ("apple_pie_qps", names[10]); + + ASSERT_EQ(0, rec.expose("BaNaNa::Latency")); + bvar::Variable::list_exposed(&names); + std::sort(names.begin(), names.end()); + ASSERT_EQ(11UL, names.size()); + ASSERT_EQ("ba_na_na_count", names[0]); + ASSERT_EQ("ba_na_na_latency", names[1]); + ASSERT_EQ("ba_na_na_latency_80", names[2]); + ASSERT_EQ("ba_na_na_latency_90", names[3]); + ASSERT_EQ("ba_na_na_latency_99", names[4]); + ASSERT_EQ("ba_na_na_latency_999", names[5]); + ASSERT_EQ("ba_na_na_latency_9999", names[6]); + ASSERT_EQ("ba_na_na_latency_cdf", names[7]); + ASSERT_EQ("ba_na_na_latency_percentiles", names[8]); + ASSERT_EQ("ba_na_na_max_latency", names[9]); + ASSERT_EQ("ba_na_na_qps", names[10]); + + GFLAGS_NAMESPACE::SetCommandLineOption("bvar_latency_p1", saved_bvar_latency_p1.c_str()); + GFLAGS_NAMESPACE::SetCommandLineOption("bvar_latency_p2", saved_bvar_latency_p2.c_str()); + GFLAGS_NAMESPACE::SetCommandLineOption("bvar_latency_p3", saved_bvar_latency_p3.c_str()); +} + +TEST_F(VariableTest, recursive_mutex) { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_t mutex; + pthread_mutex_init(&mutex, &attr); + pthread_mutexattr_destroy(&attr); + butil::Timer timer; + const size_t N = 1000000; + timer.start(); + for (size_t i = 0; i < N; ++i) { + BAIDU_SCOPED_LOCK(mutex); + } + timer.stop(); + LOG(INFO) << "Each recursive mutex lock/unlock pair take " + << timer.n_elapsed() / N << "ns"; +} +} // namespace + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + return RUN_ALL_TESTS(); +} diff --git a/test/bvar_window_unittest.cpp b/test/bvar_window_unittest.cpp new file mode 100644 index 0000000..e50cf10 --- /dev/null +++ b/test/bvar_window_unittest.cpp @@ -0,0 +1,97 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "bvar/bvar.h" +#include "bvar/window.h" + +class WindowTest : public testing::Test { +protected: + void SetUp() {} + void TearDown() {} +}; + +TEST_F(WindowTest, window) { + const int window_size = 2; + // test bvar::Adder + bvar::Adder adder; + bvar::Window > window_adder(&adder, window_size); + bvar::PerSecond > per_second_adder(&adder, window_size); + bvar::WindowEx, 2> window_ex_adder("window_ex_adder"); + bvar::PerSecondEx, window_size> per_second_ex_adder("per_second_ex_adder"); + + // test bvar::Maxer + bvar::Maxer maxer; + bvar::Window > window_maxer(&maxer, window_size); + bvar::WindowEx, window_size> window_ex_maxer; + + // test bvar::Miner + bvar::Miner miner; + bvar::Window > window_miner(&miner, window_size); + bvar::WindowEx, window_size> window_ex_miner; + + // test bvar::IntRecorder + bvar::IntRecorder recorder; + bvar::Window window_int_recorder(&recorder, window_size); + bvar::WindowEx window_ex_int_recorder("window_ex_int_recorder"); + + adder << 10; + window_ex_adder << 10; + per_second_ex_adder << 10; + + maxer << 10; + window_ex_maxer << 10; + miner << 10; + window_ex_miner << 10; + + recorder << 10; + window_ex_int_recorder << 10; + + sleep(1); + adder << 2; + window_ex_adder << 2; + per_second_ex_adder << 2; + + maxer << 2; + window_ex_maxer << 2; + miner << 2; + window_ex_miner << 2; + + recorder << 2; + window_ex_int_recorder << 2; + sleep(1); + + ASSERT_EQ(window_adder.get_value(), window_ex_adder.get_value()); + ASSERT_EQ(per_second_adder.get_value(), per_second_ex_adder.get_value()); + + ASSERT_EQ(window_maxer.get_value(), window_ex_maxer.get_value()); + ASSERT_EQ(window_miner.get_value(), window_ex_miner.get_value()); + + bvar::Stat recorder_stat = window_int_recorder.get_value(); + bvar::Stat window_ex_recorder_stat = window_ex_int_recorder.get_value(); + ASSERT_EQ(recorder_stat.get_average_int(), window_ex_recorder_stat.get_average_int()); + ASSERT_DOUBLE_EQ(recorder_stat.get_average_double(), window_ex_recorder_stat.get_average_double()); +} diff --git a/test/cacheline_unittest.cpp b/test/cacheline_unittest.cpp new file mode 100644 index 0000000..efcb884 --- /dev/null +++ b/test/cacheline_unittest.cpp @@ -0,0 +1,52 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include "butil/time.h" +#include "butil/macros.h" + +namespace { + +class CachelineTest : public testing::Test { +}; + +struct BAIDU_CACHELINE_ALIGNMENT Bar { + int y; +}; + +struct Foo { + char dummy1[0]; + int z; + BAIDU_CACHELINE_ALIGNMENT int x[0]; + int y; + int m; + Bar bar; +}; + +TEST_F(CachelineTest, cacheline_alignment) { + ASSERT_EQ(64u, offsetof(Foo, x)); + ASSERT_EQ(64u, offsetof(Foo, y)); + ASSERT_EQ(68u, offsetof(Foo, m)); + ASSERT_EQ(128u, offsetof(Foo, bar)); + ASSERT_EQ(64u, sizeof(Bar)); + ASSERT_EQ(192u, sizeof(Foo)); +} + +} diff --git a/test/callback_helpers_unittest.cc b/test/callback_helpers_unittest.cc new file mode 100644 index 0000000..c30d482 --- /dev/null +++ b/test/callback_helpers_unittest.cc @@ -0,0 +1,61 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/callback_helpers.h" + +#include "butil/bind.h" +#include "butil/callback.h" +#include + +namespace { + +void Increment(int* value) { + (*value)++; +} + +TEST(BindHelpersTest, TestScopedClosureRunnerExitScope) { + int run_count = 0; + { + butil::ScopedClosureRunner runner(butil::Bind(&Increment, &run_count)); + EXPECT_EQ(0, run_count); + } + EXPECT_EQ(1, run_count); +} + +TEST(BindHelpersTest, TestScopedClosureRunnerRelease) { + int run_count = 0; + butil::Closure c; + { + butil::ScopedClosureRunner runner(butil::Bind(&Increment, &run_count)); + c = runner.Release(); + EXPECT_EQ(0, run_count); + } + EXPECT_EQ(0, run_count); + c.Run(); + EXPECT_EQ(1, run_count); +} + +TEST(BindHelpersTest, TestScopedClosureRunnerReset) { + int run_count_1 = 0; + int run_count_2 = 0; + { + butil::ScopedClosureRunner runner; + runner.Reset(butil::Bind(&Increment, &run_count_1)); + runner.Reset(butil::Bind(&Increment, &run_count_2)); + EXPECT_EQ(1, run_count_1); + EXPECT_EQ(0, run_count_2); + } + EXPECT_EQ(1, run_count_2); + + int run_count_3 = 0; + { + butil::ScopedClosureRunner runner(butil::Bind(&Increment, &run_count_3)); + EXPECT_EQ(0, run_count_3); + runner.Reset(); + EXPECT_EQ(1, run_count_3); + } + EXPECT_EQ(1, run_count_3); +} + +} // namespace diff --git a/test/callback_list_unittest.cc b/test/callback_list_unittest.cc new file mode 100644 index 0000000..422bb08 --- /dev/null +++ b/test/callback_list_unittest.cc @@ -0,0 +1,291 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/callback_list.h" + +#include "butil/basictypes.h" +#include "butil/bind.h" +#include "butil/bind_helpers.h" +#include "butil/memory/scoped_ptr.h" +#include + +namespace butil { +namespace { + +class Listener { + public: + Listener() : total_(0), scaler_(1) {} + explicit Listener(int scaler) : total_(0), scaler_(scaler) {} + void IncrementTotal() { total_++; } + void IncrementByMultipleOfScaler(int x) { total_ += x * scaler_; } + + int total() const { return total_; } + + private: + int total_; + int scaler_; + DISALLOW_COPY_AND_ASSIGN(Listener); +}; + +class Remover { + public: + Remover() : total_(0) {} + void IncrementTotalAndRemove() { + total_++; + removal_subscription_.reset(); + } + void SetSubscriptionToRemove( + scoped_ptr::Subscription> sub) { + removal_subscription_ = sub.Pass(); + } + + int total() const { return total_; } + + private: + int total_; + scoped_ptr::Subscription> removal_subscription_; + DISALLOW_COPY_AND_ASSIGN(Remover); +}; + +class Adder { + public: + explicit Adder(CallbackList* cb_reg) + : added_(false), + total_(0), + cb_reg_(cb_reg) { + } + void AddCallback() { + if (!added_) { + added_ = true; + subscription_ = + cb_reg_->Add(Bind(&Adder::IncrementTotal, Unretained(this))); + } + } + void IncrementTotal() { total_++; } + + bool added() const { return added_; } + + int total() const { return total_; } + + private: + bool added_; + int total_; + CallbackList* cb_reg_; + scoped_ptr::Subscription> subscription_; + DISALLOW_COPY_AND_ASSIGN(Adder); +}; + +class Summer { + public: + Summer() : value_(0) {} + + void AddOneParam(int a) { value_ = a; } + void AddTwoParam(int a, int b) { value_ = a + b; } + void AddThreeParam(int a, int b, int c) { value_ = a + b + c; } + void AddFourParam(int a, int b, int c, int d) { value_ = a + b + c + d; } + void AddFiveParam(int a, int b, int c, int d, int e) { + value_ = a + b + c + d + e; + } + void AddSixParam(int a, int b, int c, int d, int e , int f) { + value_ = a + b + c + d + e + f; + } + + int value() const { return value_; } + + private: + int value_; + DISALLOW_COPY_AND_ASSIGN(Summer); +}; + +// Sanity check that we can instantiate a CallbackList for each arity. +TEST(CallbackListTest, ArityTest) { + Summer s; + + CallbackList c1; + scoped_ptr::Subscription> subscription1 = + c1.Add(Bind(&Summer::AddOneParam, Unretained(&s))); + + c1.Notify(1); + EXPECT_EQ(1, s.value()); + + CallbackList c2; + scoped_ptr::Subscription> subscription2 = + c2.Add(Bind(&Summer::AddTwoParam, Unretained(&s))); + + c2.Notify(1, 2); + EXPECT_EQ(3, s.value()); + + CallbackList c3; + scoped_ptr::Subscription> + subscription3 = c3.Add(Bind(&Summer::AddThreeParam, Unretained(&s))); + + c3.Notify(1, 2, 3); + EXPECT_EQ(6, s.value()); + + CallbackList c4; + scoped_ptr::Subscription> + subscription4 = c4.Add(Bind(&Summer::AddFourParam, Unretained(&s))); + + c4.Notify(1, 2, 3, 4); + EXPECT_EQ(10, s.value()); + + CallbackList c5; + scoped_ptr::Subscription> + subscription5 = c5.Add(Bind(&Summer::AddFiveParam, Unretained(&s))); + + c5.Notify(1, 2, 3, 4, 5); + EXPECT_EQ(15, s.value()); + + CallbackList c6; + scoped_ptr::Subscription> + subscription6 = c6.Add(Bind(&Summer::AddSixParam, Unretained(&s))); + + c6.Notify(1, 2, 3, 4, 5, 6); + EXPECT_EQ(21, s.value()); +} + +// Sanity check that closures added to the list will be run, and those removed +// from the list will not be run. +TEST(CallbackListTest, BasicTest) { + CallbackList cb_reg; + Listener a, b, c; + + scoped_ptr::Subscription> a_subscription = + cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&a))); + scoped_ptr::Subscription> b_subscription = + cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&b))); + + EXPECT_TRUE(a_subscription.get()); + EXPECT_TRUE(b_subscription.get()); + + cb_reg.Notify(); + + EXPECT_EQ(1, a.total()); + EXPECT_EQ(1, b.total()); + + b_subscription.reset(); + + scoped_ptr::Subscription> c_subscription = + cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&c))); + + cb_reg.Notify(); + + EXPECT_EQ(2, a.total()); + EXPECT_EQ(1, b.total()); + EXPECT_EQ(1, c.total()); + + a_subscription.reset(); + b_subscription.reset(); + c_subscription.reset(); +} + +// Sanity check that callbacks with details added to the list will be run, with +// the correct details, and those removed from the list will not be run. +TEST(CallbackListTest, BasicTestWithParams) { + CallbackList cb_reg; + Listener a(1), b(-1), c(1); + + scoped_ptr::Subscription> a_subscription = + cb_reg.Add(Bind(&Listener::IncrementByMultipleOfScaler, Unretained(&a))); + scoped_ptr::Subscription> b_subscription = + cb_reg.Add(Bind(&Listener::IncrementByMultipleOfScaler, Unretained(&b))); + + EXPECT_TRUE(a_subscription.get()); + EXPECT_TRUE(b_subscription.get()); + + cb_reg.Notify(10); + + EXPECT_EQ(10, a.total()); + EXPECT_EQ(-10, b.total()); + + b_subscription.reset(); + + scoped_ptr::Subscription> c_subscription = + cb_reg.Add(Bind(&Listener::IncrementByMultipleOfScaler, Unretained(&c))); + + cb_reg.Notify(10); + + EXPECT_EQ(20, a.total()); + EXPECT_EQ(-10, b.total()); + EXPECT_EQ(10, c.total()); + + a_subscription.reset(); + b_subscription.reset(); + c_subscription.reset(); +} + +// Test the a callback can remove itself or a different callback from the list +// during iteration without invalidating the iterator. +TEST(CallbackListTest, RemoveCallbacksDuringIteration) { + CallbackList cb_reg; + Listener a, b; + Remover remover_1, remover_2; + + scoped_ptr::Subscription> remover_1_sub = + cb_reg.Add(Bind(&Remover::IncrementTotalAndRemove, + Unretained(&remover_1))); + scoped_ptr::Subscription> remover_2_sub = + cb_reg.Add(Bind(&Remover::IncrementTotalAndRemove, + Unretained(&remover_2))); + scoped_ptr::Subscription> a_subscription = + cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&a))); + scoped_ptr::Subscription> b_subscription = + cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&b))); + + // |remover_1| will remove itself. + remover_1.SetSubscriptionToRemove(remover_1_sub.Pass()); + // |remover_2| will remove a. + remover_2.SetSubscriptionToRemove(a_subscription.Pass()); + + cb_reg.Notify(); + + // |remover_1| runs once (and removes itself), |remover_2| runs once (and + // removes a), |a| never runs, and |b| runs once. + EXPECT_EQ(1, remover_1.total()); + EXPECT_EQ(1, remover_2.total()); + EXPECT_EQ(0, a.total()); + EXPECT_EQ(1, b.total()); + + cb_reg.Notify(); + + // Only |remover_2| and |b| run this time. + EXPECT_EQ(1, remover_1.total()); + EXPECT_EQ(2, remover_2.total()); + EXPECT_EQ(0, a.total()); + EXPECT_EQ(2, b.total()); +} + +// Test that a callback can add another callback to the list durning iteration +// without invalidating the iterator. The newly added callback should be run on +// the current iteration as will all other callbacks in the list. +TEST(CallbackListTest, AddCallbacksDuringIteration) { + CallbackList cb_reg; + Adder a(&cb_reg); + Listener b; + scoped_ptr::Subscription> a_subscription = + cb_reg.Add(Bind(&Adder::AddCallback, Unretained(&a))); + scoped_ptr::Subscription> b_subscription = + cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&b))); + + cb_reg.Notify(); + + EXPECT_EQ(1, a.total()); + EXPECT_EQ(1, b.total()); + EXPECT_TRUE(a.added()); + + cb_reg.Notify(); + + EXPECT_EQ(2, a.total()); + EXPECT_EQ(2, b.total()); +} + +// Sanity check: notifying an empty list is a no-op. +TEST(CallbackListTest, EmptyList) { + CallbackList cb_reg; + + cb_reg.Notify(); +} + +} // namespace +} // namespace butil diff --git a/test/callback_unittest.cc b/test/callback_unittest.cc new file mode 100644 index 0000000..72aad20 --- /dev/null +++ b/test/callback_unittest.cc @@ -0,0 +1,181 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/bind.h" +#include "butil/callback.h" +#include "butil/callback_helpers.h" +#include "butil/callback_internal.h" +#include "butil/memory/ref_counted.h" +#include "butil/memory/scoped_ptr.h" +#include + +namespace butil { + +namespace { + +struct FakeInvoker { + typedef void(RunType)(internal::BindStateBase*); + static void Run(internal::BindStateBase*) { + } +}; + +} // namespace + +namespace internal { +template +struct BindState; + +// White-box testpoints to inject into a Callback<> object for checking +// comparators and emptiness APIs. Use a BindState that is specialized +// based on a type we declared in the anonymous namespace above to remove any +// chance of colliding with another instantiation and breaking the +// one-definition-rule. +template <> +struct BindState + : public BindStateBase { + public: + typedef FakeInvoker InvokerType; +}; + +template <> +struct BindState + : public BindStateBase { + public: + typedef FakeInvoker InvokerType; +}; +} // namespace internal + +namespace { + +typedef internal::BindState + FakeBindState1; +typedef internal::BindState + FakeBindState2; + +class CallbackTest : public ::testing::Test { + public: + CallbackTest() + : callback_a_(new FakeBindState1()), + callback_b_(new FakeBindState2()) { + } + + virtual ~CallbackTest() { + } + + protected: + Callback callback_a_; + const Callback callback_b_; // Ensure APIs work with const. + Callback null_callback_; +}; + +// Ensure we can create unbound callbacks. We need this to be able to store +// them in class members that can be initialized later. +TEST_F(CallbackTest, DefaultConstruction) { + Callback c0; + Callback c1; + Callback c2; + Callback c3; + Callback c4; + Callback c5; + Callback c6; + + EXPECT_TRUE(c0.is_null()); + EXPECT_TRUE(c1.is_null()); + EXPECT_TRUE(c2.is_null()); + EXPECT_TRUE(c3.is_null()); + EXPECT_TRUE(c4.is_null()); + EXPECT_TRUE(c5.is_null()); + EXPECT_TRUE(c6.is_null()); +} + +TEST_F(CallbackTest, IsNull) { + EXPECT_TRUE(null_callback_.is_null()); + EXPECT_FALSE(callback_a_.is_null()); + EXPECT_FALSE(callback_b_.is_null()); +} + +TEST_F(CallbackTest, Equals) { + EXPECT_TRUE(callback_a_.Equals(callback_a_)); + EXPECT_FALSE(callback_a_.Equals(callback_b_)); + EXPECT_FALSE(callback_b_.Equals(callback_a_)); + + // We should compare based on instance, not type. + Callback callback_c(new FakeBindState1()); + Callback callback_a2 = callback_a_; + EXPECT_TRUE(callback_a_.Equals(callback_a2)); + EXPECT_FALSE(callback_a_.Equals(callback_c)); + + // Empty, however, is always equal to empty. + Callback empty2; + EXPECT_TRUE(null_callback_.Equals(empty2)); +} + +TEST_F(CallbackTest, Reset) { + // Resetting should bring us back to empty. + ASSERT_FALSE(callback_a_.is_null()); + ASSERT_FALSE(callback_a_.Equals(null_callback_)); + + callback_a_.Reset(); + + EXPECT_TRUE(callback_a_.is_null()); + EXPECT_TRUE(callback_a_.Equals(null_callback_)); +} + +struct TestForReentrancy { + TestForReentrancy() + : cb_already_run(false), + cb(Bind(&TestForReentrancy::AssertCBIsNull, Unretained(this))) { + } + void AssertCBIsNull() { + ASSERT_TRUE(cb.is_null()); + cb_already_run = true; + } + bool cb_already_run; + Closure cb; +}; + +TEST_F(CallbackTest, ResetAndReturn) { + TestForReentrancy tfr; + ASSERT_FALSE(tfr.cb.is_null()); + ASSERT_FALSE(tfr.cb_already_run); + ResetAndReturn(&tfr.cb).Run(); + ASSERT_TRUE(tfr.cb.is_null()); + ASSERT_TRUE(tfr.cb_already_run); +} + +class CallbackOwner : public butil::RefCounted { + public: + explicit CallbackOwner(bool* deleted) { + callback_ = Bind(&CallbackOwner::Unused, this); + deleted_ = deleted; + } + void Reset() { + callback_.Reset(); + // We are deleted here if no-one else had a ref to us. + } + + private: + friend class butil::RefCounted; + virtual ~CallbackOwner() { + *deleted_ = true; + } + void Unused() { + FAIL() << "Should never be called"; + } + + Closure callback_; + bool* deleted_; +}; + +TEST_F(CallbackTest, CallbackHasLastRefOnContainingObject) { + bool deleted = false; + CallbackOwner* owner = new CallbackOwner(&deleted); + owner->Reset(); + ASSERT_TRUE(deleted); +} + +} // namespace +} // namespace butil diff --git a/test/cancelable_callback_unittest.cc b/test/cancelable_callback_unittest.cc new file mode 100644 index 0000000..18131d0 --- /dev/null +++ b/test/cancelable_callback_unittest.cc @@ -0,0 +1,158 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/cancelable_callback.h" + +#include "butil/bind.h" +#include "butil/bind_helpers.h" +#include "butil/memory/ref_counted.h" +#include + +namespace butil { +namespace { + +class TestRefCounted : public RefCountedThreadSafe { + private: + friend class RefCountedThreadSafe; + ~TestRefCounted() {}; +}; + +void Increment(int* count) { (*count)++; } +void IncrementBy(int* count, int n) { (*count) += n; } +void RefCountedParam(const scoped_refptr& ref_counted) {} + +// Cancel(). +// - Callback can be run multiple times. +// - After Cancel(), Run() completes but has no effect. +TEST(CancelableCallbackTest, Cancel) { + int count = 0; + CancelableClosure cancelable( + butil::Bind(&Increment, butil::Unretained(&count))); + + butil::Closure callback = cancelable.callback(); + callback.Run(); + EXPECT_EQ(1, count); + + callback.Run(); + EXPECT_EQ(2, count); + + cancelable.Cancel(); + callback.Run(); + EXPECT_EQ(2, count); +} + +// Cancel() called multiple times. +// - Cancel() cancels all copies of the wrapped callback. +// - Calling Cancel() more than once has no effect. +// - After Cancel(), callback() returns a null callback. +TEST(CancelableCallbackTest, MultipleCancel) { + int count = 0; + CancelableClosure cancelable( + butil::Bind(&Increment, butil::Unretained(&count))); + + butil::Closure callback1 = cancelable.callback(); + butil::Closure callback2 = cancelable.callback(); + cancelable.Cancel(); + + callback1.Run(); + EXPECT_EQ(0, count); + + callback2.Run(); + EXPECT_EQ(0, count); + + // Calling Cancel() again has no effect. + cancelable.Cancel(); + + // callback() of a cancelled callback is null. + butil::Closure callback3 = cancelable.callback(); + EXPECT_TRUE(callback3.is_null()); +} + +// CancelableCallback destroyed before callback is run. +// - Destruction of CancelableCallback cancels outstanding callbacks. +TEST(CancelableCallbackTest, CallbackCanceledOnDestruction) { + int count = 0; + butil::Closure callback; + + { + CancelableClosure cancelable( + butil::Bind(&Increment, butil::Unretained(&count))); + + callback = cancelable.callback(); + callback.Run(); + EXPECT_EQ(1, count); + } + + callback.Run(); + EXPECT_EQ(1, count); +} + +// Cancel() called on bound closure with a RefCounted parameter. +// - Cancel drops wrapped callback (and, implicitly, its bound arguments). +TEST(CancelableCallbackTest, CancelDropsCallback) { + scoped_refptr ref_counted = new TestRefCounted; + EXPECT_TRUE(ref_counted->HasOneRef()); + + CancelableClosure cancelable(butil::Bind(RefCountedParam, ref_counted)); + EXPECT_FALSE(cancelable.IsCancelled()); + EXPECT_TRUE(ref_counted.get()); + EXPECT_FALSE(ref_counted->HasOneRef()); + + // There is only one reference to |ref_counted| after the Cancel(). + cancelable.Cancel(); + EXPECT_TRUE(cancelable.IsCancelled()); + EXPECT_TRUE(ref_counted.get()); + EXPECT_TRUE(ref_counted->HasOneRef()); +} + +// Reset(). +// - Reset() replaces the existing wrapped callback with a new callback. +// - Reset() deactivates outstanding callbacks. +TEST(CancelableCallbackTest, Reset) { + int count = 0; + CancelableClosure cancelable( + butil::Bind(&Increment, butil::Unretained(&count))); + + butil::Closure callback = cancelable.callback(); + callback.Run(); + EXPECT_EQ(1, count); + + callback.Run(); + EXPECT_EQ(2, count); + + cancelable.Reset( + butil::Bind(&IncrementBy, butil::Unretained(&count), 3)); + EXPECT_FALSE(cancelable.IsCancelled()); + + // The stale copy of the cancelable callback is non-null. + ASSERT_FALSE(callback.is_null()); + + // The stale copy of the cancelable callback is no longer active. + callback.Run(); + EXPECT_EQ(2, count); + + butil::Closure callback2 = cancelable.callback(); + ASSERT_FALSE(callback2.is_null()); + + callback2.Run(); + EXPECT_EQ(5, count); +} + +// IsCanceled(). +// - Cancel() transforms the CancelableCallback into a cancelled state. +TEST(CancelableCallbackTest, IsNull) { + CancelableClosure cancelable; + EXPECT_TRUE(cancelable.IsCancelled()); + + int count = 0; + cancelable.Reset(butil::Bind(&Increment, + butil::Unretained(&count))); + EXPECT_FALSE(cancelable.IsCancelled()); + + cancelable.Cancel(); + EXPECT_TRUE(cancelable.IsCancelled()); +} + +} // namespace +} // namespace butil diff --git a/test/cancellation_flag_unittest.cc b/test/cancellation_flag_unittest.cc new file mode 100644 index 0000000..08b6e72 --- /dev/null +++ b/test/cancellation_flag_unittest.cc @@ -0,0 +1,36 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Tests of CancellationFlag class. + +#include "butil/synchronization/cancellation_flag.h" + +#include "butil/logging.h" +#include "butil/synchronization/spin_wait.h" +#include "butil/time/time.h" +#include + +namespace butil { + +namespace { + +TEST(CancellationFlagTest, SimpleSingleThreadedTest) { + CancellationFlag flag; + ASSERT_FALSE(flag.IsSet()); + flag.Set(); + ASSERT_TRUE(flag.IsSet()); +} + +TEST(CancellationFlagTest, DoubleSetTest) { + CancellationFlag flag; + ASSERT_FALSE(flag.IsSet()); + flag.Set(); + ASSERT_TRUE(flag.IsSet()); + flag.Set(); + ASSERT_TRUE(flag.IsSet()); +} + +} // namespace + +} // namespace butil diff --git a/test/cert1.crt b/test/cert1.crt new file mode 100644 index 0000000..1b0a939 --- /dev/null +++ b/test/cert1.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIJAPpOI7Hnn46wMA0GCSqGSIb3DQEBCwUAMGExCzAJBgNV +BAYTAkNOMREwDwYDVQQIDAhTaGFuZ2hhaTERMA8GA1UEBwwIU2hhbmdoYWkxDjAM +BgNVBAoMBUJhaWR1MQwwCgYDVQQLDANDQlUxDjAMBgNVBAMMBWNlcnQxMB4XDTE5 +MDEyMzIyMTU0MVoXDTI5MDEyMDIyMTU0MVowYTELMAkGA1UEBhMCQ04xETAPBgNV +BAgMCFNoYW5naGFpMREwDwYDVQQHDAhTaGFuZ2hhaTEOMAwGA1UECgwFQmFpZHUx +DDAKBgNVBAsMA0NCVTEOMAwGA1UEAwwFY2VydDEwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQC0ypYQmHvVlMaxe5phlUpvKNZrwQSrg/CGN6jrDkV8u7d4 +5pI6zcbA1g2fq1Q5EuwexeuWRPt/zSL6PYcOQjZcHIrQwXrilfjw6gOPhFUg54jN +Xzj8XqdZkZp/0qNksPROoeJMnNH+RMjWZox9WLdLAaC7t2R90OMzpBN675XMWGrj +XJo3ZgWkrv0AtiE7AGGed/gL+iybDwKhN9qftywt6TRqYUOsk2/j6PtWEKg9EfRk +rkdh0jQkpXeh+tk1nUtlnohFqhxz4XPN5+wHtmZinliSMWEXh6XaXyk6ojXdjPMy +vpWCxmHz9R9sNP7T/gHkN02pEXbZScGnoPmr635bAgMBAAGjgfAwge0wDAYDVR0T +AQH/BAIwADALBgNVHQ8EBAMCBsAwHQYDVR0OBBYEFAktYxDf0c02h0XkYdeGBtLd +pkuDMIGTBgNVHSMEgYswgYiAFAktYxDf0c02h0XkYdeGBtLdpkuDoWWkYzBhMQsw +CQYDVQQGEwJDTjERMA8GA1UECAwIU2hhbmdoYWkxETAPBgNVBAcMCFNoYW5naGFp +MQ4wDAYDVQQKDAVCYWlkdTEMMAoGA1UECwwDQ0JVMQ4wDAYDVQQDDAVjZXJ0MYIJ +APpOI7Hnn46wMBsGA1UdEQQUMBKCBWNlcnQxgglsb2NhbGhvc3QwDQYJKoZIhvcN +AQELBQADggEBAAN0NWqqAGpqmxBd5VcnCX26pt6WeY5i0XjTVpmrf18qz4JN4Zwo +yHELON9qCNradCNFOUD0kGNGokOSPw4HakQx6mRPwzAxkctI12nj/ArBhTYC+QEQ +WsYCu9rIr3TzT5mz2LpTC1RA+HVkvC7uB1ROc+rl88n1Tuyy2mwj5PbteE3Lpnif +dYgPrU3PM6AVs+1wV1tMUc+DH5UYEaVR7VbN54yiMe4mjwCmsrrC/Y22WgxTBqwG +Z7+YjHT6p2MvRlJ2a0kwb15X492iC1KeBsb/NomWO40WTFTxL0zxk8XEnjwcRPs0 +rT3UGBMRPbGDV6fnbf5r3cuOcv7qlHf+7xM= +-----END CERTIFICATE----- diff --git a/test/cert1.key b/test/cert1.key new file mode 100644 index 0000000..0c5f632 --- /dev/null +++ b/test/cert1.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC0ypYQmHvVlMax +e5phlUpvKNZrwQSrg/CGN6jrDkV8u7d45pI6zcbA1g2fq1Q5EuwexeuWRPt/zSL6 +PYcOQjZcHIrQwXrilfjw6gOPhFUg54jNXzj8XqdZkZp/0qNksPROoeJMnNH+RMjW +Zox9WLdLAaC7t2R90OMzpBN675XMWGrjXJo3ZgWkrv0AtiE7AGGed/gL+iybDwKh +N9qftywt6TRqYUOsk2/j6PtWEKg9EfRkrkdh0jQkpXeh+tk1nUtlnohFqhxz4XPN +5+wHtmZinliSMWEXh6XaXyk6ojXdjPMyvpWCxmHz9R9sNP7T/gHkN02pEXbZScGn +oPmr635bAgMBAAECggEAC5d5q7K7LeSOIM8WBO+3iA0MQnhrvjuFbnWfJQMTPX4j +s2LFOXP8LF0NHpGzor0t2oNCKa5emcEjXvwW7rkcFyfVVrExGdoXzgqTE96ePq/Z +u6FBXB0NidamG0/8Hfaik3AZvGPJqw3p+qU0mMzZY7vE/IQzs0Vza9o3TYiTCDj/ +WBMEaNLRMymK8Ejyoj7Dm90Tr0TN9PRNBl24KAVOuxYhqajFJV8SkVITXD8ujOt8 +JnpeLnteYsi2Du9cOmu90hc0kRvwQGn4j++T25FvESITz490/7ZEfMG59jTzF0be ++TJjhqMJ6AcUp5/DtKWhcBYvxhVrOowO3dV0DFLU0QKBgQDtoR+Hut8ZPWZynTSQ +m7k8lvmLFa+l0nIMSEEciInQSKgZLWeDRrIR9F7GyFhmBRf2u2V4uK0P/g8bhj73 +D91KdYXImPqGapCkvTBed0UzSVE236i4rnBO3/e+2Oux806a9sqXvSqENj/OjSVE +de5wrV1y0IH4s9ukwsqZhPnBxwKBgQDCxJeyW+tS4LglY2Qk1kR5ELZhfMe11Mg4 +Gqeh1oJcuXV0cVfj2MS8li/gGBrIuzQL62plk7o6j6ybPxraXNObNMF3ZnisEbWj +cykgcQyD+HRuPT1xwH/+dHs7mLtuk/p19e4ZPy1wn41PAC2fDHbEKdYWDAdujUl5 +BEEe2xoezQKBgQDGyQq/WKxZSOvy5V+buSl0bjfDChkt9qZBcBBH9lCTVLSKm1kE +kJdWPb8rO133ujsZxBpWqubbggTRWbRCqZrNNxL7hD3PREZMCZf07oGNLcAqz18t +X3/D+8gcdwp0ir0vFVTVKwHuKBOojpqmcqFM0TpjWdngW1VatzkUxBDK8QKBgQCm +jY0Xlekvr0Fpn4vkwGI/kR4VUapKgNJSv+B30cMa3fFmCQLaseTTTC9Wl+ZXn1aL +lt4eTOzk5TX6cEVbVCQURlHm8/bfVimYw4L43hOQyydtmerwWmhZxWwYc6xcjCiT +NSJN7qvB8n7ZftKEfxkU+J29rr2wORwKY6v4Ye79RQKBgBMxrOmLnCfXgt/Mzm3Q +LGwXjmsC7O4wgmQBhkpimXOOVwRW4KFpkODfAk6vb/rMeKkhg1h18oFlxHduN/kw +5BNDnDDdZgOV8dUTjFAJEJsuOi3B8rLsC2TEmIwZN1wmqDPcHCwPP4+ttqDsalzL +wV+3rZ8xTyFAVZmTokIPMlqE +-----END PRIVATE KEY----- diff --git a/test/cert2.crt b/test/cert2.crt new file mode 100644 index 0000000..ec4ad35 --- /dev/null +++ b/test/cert2.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIJAPBmg0BQlgqLMA0GCSqGSIb3DQEBCwUAMGExCzAJBgNV +BAYTAkNOMREwDwYDVQQIDAhTaGFuZ2hhaTERMA8GA1UEBwwIU2hhbmdoYWkxDjAM +BgNVBAoMBUJhaWR1MQwwCgYDVQQLDANDQlUxDjAMBgNVBAMMBWNlcnQyMB4XDTE5 +MDEyMzIyMTU0MVoXDTI5MDEyMDIyMTU0MVowYTELMAkGA1UEBhMCQ04xETAPBgNV +BAgMCFNoYW5naGFpMREwDwYDVQQHDAhTaGFuZ2hhaTEOMAwGA1UECgwFQmFpZHUx +DDAKBgNVBAsMA0NCVTEOMAwGA1UEAwwFY2VydDIwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCcbPV5j9eex7cnAsUMMreZ9H76yUgp1Y54gR3TqSELYjtu +XwudIKVQGawPcoFIysvapDBXSj6LgEWVXSqs8bVSUW7lmraSKGJ+wIXmZFHvweaY +7rXpSma357i0do5VLvIZS9ZRN40SAS6EaSqgpvD3ISeT4xHaiQ/XtKLc/dCSmNSd +tXsH/CHvcGxEH78i+n2lNjHnabZ+aLTnEQ59R2wDPqymHb2j9gz3QMlM50m7vK83 +v42lhObPQ/JZBs/0taWv6GbFIMYVLwUiCmIb7ecJk1/k3gBo6VYQ5tnnyhcUdTvv +sWv94KuUU8OmPzJLSo6nKXSE63cIgTOGDR1YmqhNAgMBAAGjgfAwge0wDAYDVR0T +AQH/BAIwADALBgNVHQ8EBAMCBsAwHQYDVR0OBBYEFOuA5h9dbBrkxeUiH7cSNxV5 +ygP0MIGTBgNVHSMEgYswgYiAFOuA5h9dbBrkxeUiH7cSNxV5ygP0oWWkYzBhMQsw +CQYDVQQGEwJDTjERMA8GA1UECAwIU2hhbmdoYWkxETAPBgNVBAcMCFNoYW5naGFp +MQ4wDAYDVQQKDAVCYWlkdTEMMAoGA1UECwwDQ0JVMQ4wDAYDVQQDDAVjZXJ0MoIJ +APBmg0BQlgqLMBsGA1UdEQQUMBKCBWNlcnQygglsb2NhbGhvc3QwDQYJKoZIhvcN +AQELBQADggEBAGlEgAyI0R/phfGbdbMdF2WtcbflZrs1wsX4y2zUcmKJt7CkAED/ +tj33gHh0qg3eWADUQ3AZ5iCalY86NcKsDoIT2mJn7oO6ejovGOnjQbF53HhY7MIy +1xqGu3MYWc8XuuqnvSqS//sokEVqIm9bAnZaY32cjsrHqZcYCPZbMqSOwqSnz3Ia +fDMw21GOVtgWIXrRFVykTZ9nzey16EB21ry9gci89+1sy5bz0fhXUG1XFguXj8zH +clAzL1mzjYQ1SWMzMmCX7GiiwjkNkiTr6grGQCDBTaIg5E1firkJm3GFJRXmZKSZ +FYR32SX4H3vj6cHf2iu7ilN5t9EQ60KPQ+k= +-----END CERTIFICATE----- diff --git a/test/cert2.key b/test/cert2.key new file mode 100644 index 0000000..ef4c625 --- /dev/null +++ b/test/cert2.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCcbPV5j9eex7cn +AsUMMreZ9H76yUgp1Y54gR3TqSELYjtuXwudIKVQGawPcoFIysvapDBXSj6LgEWV +XSqs8bVSUW7lmraSKGJ+wIXmZFHvweaY7rXpSma357i0do5VLvIZS9ZRN40SAS6E +aSqgpvD3ISeT4xHaiQ/XtKLc/dCSmNSdtXsH/CHvcGxEH78i+n2lNjHnabZ+aLTn +EQ59R2wDPqymHb2j9gz3QMlM50m7vK83v42lhObPQ/JZBs/0taWv6GbFIMYVLwUi +CmIb7ecJk1/k3gBo6VYQ5tnnyhcUdTvvsWv94KuUU8OmPzJLSo6nKXSE63cIgTOG +DR1YmqhNAgMBAAECggEAHPp+e1evfUXIY1y6/miC5O2LfJA/Yyih7ScWTHjfm0lG +c0r+TsyWc4FeA7qVwtN28nlKT1F8xsErouEQn9tjWO2nGrgPrIH4xTyLUcQx/bWx +L5HBd4eGAfnWmPABrDw3M4J+IKum4bgAUx1cfUiQCWhF+bquOwr7OV3IciI/Onj1 +fXBOn98EamJD5SoQwV0WOqWE2grj3bXQd04sHYfYjCb0C3uDqKa3YGyCIB5axK8s +SBMnQ0tEKlgF7GY4i9OXXq//wLIEMM23rby5k5oJxPlFvN1MCnQOShHSrK2cprtl +1KhZxkL/xXJr8NhMumfDgTEKmpQyPpssWRiYKA1GwQKBgQDIzrLPI1NlYxgRUjGN +PsILc6c3jj73wW7NEPUyLAGkmA4GRhkXusC+pNlO1baEUPm7wWVEo7qndG8rsWjC +nDjCxiVY/5rGYBchwXZUKEh9nowMmDcfwhXb+zJf45aj+jA72pOrMTYGHQ/X/xzj +aVFyABhcUStcM3ts0CvmQnWZkQKBgQDHa3LYbBQI9DLGXfPjwqfUJEen7xypbdlI +UkoXqJa5kMZqpspoIHHMxOa9Rcbhu/E2qCXLypkQ9CPyWzNBPHu499F3kHob5xFt +A6qxnHG+ilGYSd4qej/HIPvAU4TVP6yfwyyBALCB5/wYSRDxT6Zfv3OGvaIaj+qT +qBdLW3Gk/QKBgEGrFt6WdtdZKK3Ba2L9ewezsqOAaScsosd9HDJkIcVp1GxI0Dvq +Xs35qvcU/LMYqBK2lB92S7wnX5OyWMgLvqQzmFMag8sL8YSgd8ndwpcSGkqkHKLO +HcfqxfaFvuWxE8T/HfuGBFzLdDr2usPD1VaqoUzPXpawX1SeXzzVzw+BAoGAQktz +K4WKh4t/EbkMKkx89KZ299oi4iR1lnhcz06phNkfTTdTlJgsnNFcj9GRk1uijfQK +VJxulFdFV/1/pZFQ5CXmieQK5BnGDkKozVDf82MSSxlLdT2c1Dsf1kktoKMBZT9C +HUS4aQdRJFWt/zrmaXBBHKsQJ9puNlYsIE4vEpUCgYEAvN1sBXbt/8R8ELvOrhQv +HO136LtI/KRPpxXRRdBFj//ijwfMchUfChGVk4P0fYXyUA8tF19DJ3GbqtmObYV4 +TFYli0z1xDN3Pwo+55Kxwx42Ir7GHmevvNa+RitQYePZVsdLGxY+azh11/5bOZ14 +ZkVDf9Glu7bDIwn1wz4HpFw= +-----END PRIVATE KEY----- diff --git a/test/class_name_unittest.cpp b/test/class_name_unittest.cpp new file mode 100644 index 0000000..d354266 --- /dev/null +++ b/test/class_name_unittest.cpp @@ -0,0 +1,62 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/class_name.h" +#include "butil/logging.h" + +namespace butil { +namespace foobar { +struct MyClass {}; +} +} + +namespace { +class ClassNameTest : public ::testing::Test { +protected: + virtual void SetUp() { + srand(time(0)); + }; +}; + +TEST_F(ClassNameTest, demangle) { + ASSERT_EQ("add_something", butil::demangle("add_something")); + ASSERT_EQ("guard variable for butil::my_ip()::ip", + butil::demangle("_ZGVZN5butil5my_ipEvE2ip")); + ASSERT_EQ("dp::FiberPBCommand::marshal(dp::ParamWriter*)::__FUNCTION__", + butil::demangle("_ZZN2dp14FiberPBCommandIN5proto12PbRouteTableENS1_10PbRouteAckEE7marshalEPNS_11ParamWriterEE12__FUNCTION__")); + ASSERT_EQ("7&8", butil::demangle("7&8")); +} + +TEST_F(ClassNameTest, class_name_sanity) { + ASSERT_EQ("char", butil::class_name_str('\0')); + ASSERT_STREQ("short", butil::class_name()); + ASSERT_EQ("long", butil::class_name_str(1L)); + ASSERT_EQ("unsigned long", butil::class_name_str(1UL)); + ASSERT_EQ("float", butil::class_name_str(1.1f)); + ASSERT_EQ("double", butil::class_name_str(1.1)); + ASSERT_STREQ("char*", butil::class_name()); + ASSERT_STREQ("char const*", butil::class_name()); + ASSERT_STREQ("butil::foobar::MyClass", butil::class_name()); + + int array[32]; + ASSERT_EQ("int [32]", butil::class_name_str(array)); + + LOG(INFO) << butil::class_name_str(this); + LOG(INFO) << butil::class_name_str(*this); +} +} diff --git a/test/condition_variable_unittest.cc b/test/condition_variable_unittest.cc new file mode 100644 index 0000000..ef3e4f0 --- /dev/null +++ b/test/condition_variable_unittest.cc @@ -0,0 +1,723 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Multi-threaded tests of ConditionVariable class. + +#include +#include +#include + +#include "butil/logging.h" +#include "butil/memory/scoped_ptr.h" +#include "butil/synchronization/condition_variable.h" +#include "butil/synchronization/lock.h" +#include "butil/synchronization/spin_wait.h" +#include "butil/threading/platform_thread.h" +#include "butil/threading/thread_collision_warner.h" +#include "butil/time/time.h" +#include +#include + +namespace butil { + +namespace { +//------------------------------------------------------------------------------ +// Define our test class, with several common variables. +//------------------------------------------------------------------------------ + +class ConditionVariableTest : public testing::Test { + public: + const TimeDelta kZeroMs; + const TimeDelta kTenMs; + const TimeDelta kThirtyMs; + const TimeDelta kFortyFiveMs; + const TimeDelta kSixtyMs; + const TimeDelta kOneHundredMs; + + ConditionVariableTest() + : kZeroMs(TimeDelta::FromMilliseconds(0)), + kTenMs(TimeDelta::FromMilliseconds(10)), + kThirtyMs(TimeDelta::FromMilliseconds(30)), + kFortyFiveMs(TimeDelta::FromMilliseconds(45)), + kSixtyMs(TimeDelta::FromMilliseconds(60)), + kOneHundredMs(TimeDelta::FromMilliseconds(100)) { + } +}; + +//------------------------------------------------------------------------------ +// Define a class that will control activities an several multi-threaded tests. +// The general structure of multi-threaded tests is that a test case will +// construct an instance of a WorkQueue. The WorkQueue will spin up some +// threads and control them throughout their lifetime, as well as maintaining +// a central repository of the work thread's activity. Finally, the WorkQueue +// will command the the worker threads to terminate. At that point, the test +// cases will validate that the WorkQueue has records showing that the desired +// activities were performed. +//------------------------------------------------------------------------------ + +// Callers are responsible for synchronizing access to the following class. +// The WorkQueue::lock_, as accessed via WorkQueue::lock(), should be used for +// all synchronized access. +class WorkQueue : public PlatformThread::Delegate { + public: + explicit WorkQueue(int thread_count); + virtual ~WorkQueue(); + + // PlatformThread::Delegate interface. + virtual void ThreadMain() OVERRIDE; + + //---------------------------------------------------------------------------- + // Worker threads only call the following methods. + // They should use the lock to get exclusive access. + int GetThreadId(); // Get an ID assigned to a thread.. + bool EveryIdWasAllocated() const; // Indicates that all IDs were handed out. + TimeDelta GetAnAssignment(int thread_id); // Get a work task duration. + void WorkIsCompleted(int thread_id); + + int task_count() const; + bool allow_help_requests() const; // Workers can signal more workers. + bool shutdown() const; // Check if shutdown has been requested. + + void thread_shutting_down(); + + + //---------------------------------------------------------------------------- + // Worker threads can call them but not needed to acquire a lock. + Lock* lock(); + + ConditionVariable* work_is_available(); + ConditionVariable* all_threads_have_ids(); + ConditionVariable* no_more_tasks(); + + //---------------------------------------------------------------------------- + // The rest of the methods are for use by the controlling master thread (the + // test case code). + void ResetHistory(); + int GetMinCompletionsByWorkerThread() const; + int GetMaxCompletionsByWorkerThread() const; + int GetNumThreadsTakingAssignments() const; + int GetNumThreadsCompletingTasks() const; + int GetNumberOfCompletedTasks() const; + + void SetWorkTime(TimeDelta delay); + void SetTaskCount(int count); + void SetAllowHelp(bool allow); + + // The following must be called without locking, and will spin wait until the + // threads are all in a wait state. + void SpinUntilAllThreadsAreWaiting(); + void SpinUntilTaskCountLessThan(int task_count); + + // Caller must acquire lock before calling. + void SetShutdown(); + + // Compares the |shutdown_task_count_| to the |thread_count| and returns true + // if they are equal. This check will acquire the |lock_| so the caller + // should not hold the lock when calling this method. + bool ThreadSafeCheckShutdown(int thread_count); + + private: + // Both worker threads and controller use the following to synchronize. + Lock lock_; + ConditionVariable work_is_available_; // To tell threads there is work. + + // Conditions to notify the controlling process (if it is interested). + ConditionVariable all_threads_have_ids_; // All threads are running. + ConditionVariable no_more_tasks_; // Task count is zero. + + const int thread_count_; + int waiting_thread_count_; + scoped_ptr thread_handles_; + std::vector assignment_history_; // Number of assignment per worker. + std::vector completion_history_; // Number of completions per worker. + int thread_started_counter_; // Used to issue unique id to workers. + int shutdown_task_count_; // Number of tasks told to shutdown + int task_count_; // Number of assignment tasks waiting to be processed. + TimeDelta worker_delay_; // Time each task takes to complete. + bool allow_help_requests_; // Workers can signal more workers. + bool shutdown_; // Set when threads need to terminate. + + DFAKE_MUTEX(locked_methods_); +}; + +//------------------------------------------------------------------------------ +// The next section contains the actual tests. +//------------------------------------------------------------------------------ + +TEST_F(ConditionVariableTest, StartupShutdownTest) { + Lock lock; + + // First try trivial startup/shutdown. + { + ConditionVariable cv1(&lock); + } // Call for cv1 destruction. + + // Exercise with at least a few waits. + ConditionVariable cv(&lock); + + lock.Acquire(); + cv.TimedWait(kTenMs); // Wait for 10 ms. + cv.TimedWait(kTenMs); // Wait for 10 ms. + lock.Release(); + + lock.Acquire(); + cv.TimedWait(kTenMs); // Wait for 10 ms. + cv.TimedWait(kTenMs); // Wait for 10 ms. + cv.TimedWait(kTenMs); // Wait for 10 ms. + lock.Release(); +} // Call for cv destruction. + +TEST_F(ConditionVariableTest, TimeoutTest) { + Lock lock; + ConditionVariable cv(&lock); + lock.Acquire(); + + TimeTicks start = TimeTicks::Now(); + const TimeDelta WAIT_TIME = TimeDelta::FromMilliseconds(300); + // Allow for clocking rate granularity. + const TimeDelta FUDGE_TIME = TimeDelta::FromMilliseconds(50); + + cv.TimedWait(WAIT_TIME + FUDGE_TIME); + TimeDelta duration = TimeTicks::Now() - start; + // We can't use EXPECT_GE here as the TimeDelta class does not support the + // required stream conversion. + EXPECT_TRUE(duration >= WAIT_TIME); + + lock.Release(); +} + +#if defined(OS_POSIX) +const int kDiscontinuitySeconds = 2; + +void ALLOW_UNUSED BackInTime(Lock* lock) { + AutoLock auto_lock(*lock); + + timeval tv; + gettimeofday(&tv, NULL); + tv.tv_sec -= kDiscontinuitySeconds; + settimeofday(&tv, NULL); +} +#endif + + +// Suddenly got flaky on Win, see http://crbug.com/10607 (starting at +// comment #15). +#if defined(OS_WIN) +#define MAYBE_MultiThreadConsumerTest DISABLED_MultiThreadConsumerTest +#else +#define MAYBE_MultiThreadConsumerTest MultiThreadConsumerTest +#endif +// Test serial task servicing, as well as two parallel task servicing methods. +TEST_F(ConditionVariableTest, MAYBE_MultiThreadConsumerTest) { + const int kThreadCount = 10; + WorkQueue queue(kThreadCount); // Start the threads. + + const int kTaskCount = 10; // Number of tasks in each mini-test here. + + Time start_time; // Used to time task processing. + + { + butil::AutoLock auto_lock(*queue.lock()); + while (!queue.EveryIdWasAllocated()) + queue.all_threads_have_ids()->Wait(); + } + + // If threads aren't in a wait state, they may start to gobble up tasks in + // parallel, short-circuiting (breaking) this test. + queue.SpinUntilAllThreadsAreWaiting(); + + { + // Since we have no tasks yet, all threads should be waiting by now. + butil::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(0, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(0, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_EQ(0, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(0, queue.GetMinCompletionsByWorkerThread()); + EXPECT_EQ(0, queue.GetNumberOfCompletedTasks()); + + // Set up to make each task include getting help from another worker, so + // so that the work gets done in paralell. + queue.ResetHistory(); + queue.SetTaskCount(kTaskCount); + queue.SetWorkTime(kThirtyMs); + queue.SetAllowHelp(true); + + start_time = Time::Now(); + } + + queue.work_is_available()->Signal(); // But each worker can signal another. + // Wait till we at least start to handle tasks (and we're not all waiting). + queue.SpinUntilTaskCountLessThan(kTaskCount); + // Wait to allow the all workers to get done. + queue.SpinUntilAllThreadsAreWaiting(); + + { + // Wait until all work tasks have at least been assigned. + butil::AutoLock auto_lock(*queue.lock()); + while (queue.task_count()) + queue.no_more_tasks()->Wait(); + + // To avoid racy assumptions, we'll just assert that at least 2 threads + // did work. We know that the first worker should have gone to sleep, and + // hence a second worker should have gotten an assignment. + EXPECT_LE(2, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(kTaskCount, queue.GetNumberOfCompletedTasks()); + + // Try to ask all workers to help, and only a few will do the work. + queue.ResetHistory(); + queue.SetTaskCount(3); + queue.SetWorkTime(kThirtyMs); + queue.SetAllowHelp(false); + } + queue.work_is_available()->Broadcast(); // Make them all try. + // Wait till we at least start to handle tasks (and we're not all waiting). + queue.SpinUntilTaskCountLessThan(3); + // Wait to allow the 3 workers to get done. + queue.SpinUntilAllThreadsAreWaiting(); + + { + butil::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(3, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(3, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_EQ(1, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(0, queue.GetMinCompletionsByWorkerThread()); + EXPECT_EQ(3, queue.GetNumberOfCompletedTasks()); + + // Set up to make each task get help from another worker. + queue.ResetHistory(); + queue.SetTaskCount(3); + queue.SetWorkTime(kThirtyMs); + queue.SetAllowHelp(true); // Allow (unnecessary) help requests. + } + queue.work_is_available()->Broadcast(); // Signal all threads. + // Wait till we at least start to handle tasks (and we're not all waiting). + queue.SpinUntilTaskCountLessThan(3); + // Wait to allow the 3 workers to get done. + queue.SpinUntilAllThreadsAreWaiting(); + + { + butil::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(3, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(3, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_EQ(1, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(0, queue.GetMinCompletionsByWorkerThread()); + EXPECT_EQ(3, queue.GetNumberOfCompletedTasks()); + + // Set up to make each task get help from another worker. + queue.ResetHistory(); + queue.SetTaskCount(20); // 2 tasks per thread. + queue.SetWorkTime(kThirtyMs); + queue.SetAllowHelp(true); + } + queue.work_is_available()->Signal(); // But each worker can signal another. + // Wait till we at least start to handle tasks (and we're not all waiting). + queue.SpinUntilTaskCountLessThan(20); + // Wait to allow the 10 workers to get done. + queue.SpinUntilAllThreadsAreWaiting(); // Should take about 60 ms. + + { + butil::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(10, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(10, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_EQ(20, queue.GetNumberOfCompletedTasks()); + + // Same as last test, but with Broadcast(). + queue.ResetHistory(); + queue.SetTaskCount(20); // 2 tasks per thread. + queue.SetWorkTime(kThirtyMs); + queue.SetAllowHelp(true); + } + queue.work_is_available()->Broadcast(); + // Wait till we at least start to handle tasks (and we're not all waiting). + queue.SpinUntilTaskCountLessThan(20); + // Wait to allow the 10 workers to get done. + queue.SpinUntilAllThreadsAreWaiting(); // Should take about 60 ms. + + { + butil::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(10, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(10, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_EQ(20, queue.GetNumberOfCompletedTasks()); + + queue.SetShutdown(); + } + queue.work_is_available()->Broadcast(); // Force check for shutdown. + + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(1), + queue.ThreadSafeCheckShutdown(kThreadCount)); +} + +TEST_F(ConditionVariableTest, LargeFastTaskTest) { + const int kThreadCount = 200; + WorkQueue queue(kThreadCount); // Start the threads. + + Lock private_lock; // Used locally for master to wait. + butil::AutoLock private_held_lock(private_lock); + ConditionVariable private_cv(&private_lock); + + { + butil::AutoLock auto_lock(*queue.lock()); + while (!queue.EveryIdWasAllocated()) + queue.all_threads_have_ids()->Wait(); + } + + // Wait a bit more to allow threads to reach their wait state. + queue.SpinUntilAllThreadsAreWaiting(); + + { + // Since we have no tasks, all threads should be waiting by now. + butil::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(0, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(0, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_EQ(0, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(0, queue.GetMinCompletionsByWorkerThread()); + EXPECT_EQ(0, queue.GetNumberOfCompletedTasks()); + + // Set up to make all workers do (an average of) 20 tasks. + queue.ResetHistory(); + queue.SetTaskCount(20 * kThreadCount); + queue.SetWorkTime(kFortyFiveMs); + queue.SetAllowHelp(false); + } + queue.work_is_available()->Broadcast(); // Start up all threads. + // Wait until we've handed out all tasks. + { + butil::AutoLock auto_lock(*queue.lock()); + while (queue.task_count() != 0) + queue.no_more_tasks()->Wait(); + } + + // Wait till the last of the tasks complete. + queue.SpinUntilAllThreadsAreWaiting(); + + { + // With Broadcast(), every thread should have participated. + // but with racing.. they may not all have done equal numbers of tasks. + butil::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(kThreadCount, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(kThreadCount, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_LE(20, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(20 * kThreadCount, queue.GetNumberOfCompletedTasks()); + + // Set up to make all workers do (an average of) 4 tasks. + queue.ResetHistory(); + queue.SetTaskCount(kThreadCount * 4); + queue.SetWorkTime(kFortyFiveMs); + queue.SetAllowHelp(true); // Might outperform Broadcast(). + } + queue.work_is_available()->Signal(); // Start up one thread. + + // Wait until we've handed out all tasks + { + butil::AutoLock auto_lock(*queue.lock()); + while (queue.task_count() != 0) + queue.no_more_tasks()->Wait(); + } + + // Wait till the last of the tasks complete. + queue.SpinUntilAllThreadsAreWaiting(); + + { + // With Signal(), every thread should have participated. + // but with racing.. they may not all have done four tasks. + butil::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(kThreadCount, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(kThreadCount, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_LE(4, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(4 * kThreadCount, queue.GetNumberOfCompletedTasks()); + + queue.SetShutdown(); + } + queue.work_is_available()->Broadcast(); // Force check for shutdown. + + // Wait for shutdowns to complete. + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(1), + queue.ThreadSafeCheckShutdown(kThreadCount)); +} + +//------------------------------------------------------------------------------ +// Finally we provide the implementation for the methods in the WorkQueue class. +//------------------------------------------------------------------------------ + +WorkQueue::WorkQueue(int thread_count) + : lock_(), + work_is_available_(&lock_), + all_threads_have_ids_(&lock_), + no_more_tasks_(&lock_), + thread_count_(thread_count), + waiting_thread_count_(0), + thread_handles_(new PlatformThreadHandle[thread_count]), + assignment_history_(thread_count), + completion_history_(thread_count), + thread_started_counter_(0), + shutdown_task_count_(0), + task_count_(0), + allow_help_requests_(false), + shutdown_(false) { + EXPECT_GE(thread_count_, 1); + ResetHistory(); + SetTaskCount(0); + SetWorkTime(TimeDelta::FromMilliseconds(30)); + + for (int i = 0; i < thread_count_; ++i) { + PlatformThreadHandle pth; + EXPECT_TRUE(PlatformThread::Create(0, this, &pth)); + thread_handles_[i] = pth; + } +} + +WorkQueue::~WorkQueue() { + { + butil::AutoLock auto_lock(lock_); + SetShutdown(); + } + work_is_available_.Broadcast(); // Tell them all to terminate. + + for (int i = 0; i < thread_count_; ++i) { + PlatformThread::Join(thread_handles_[i]); + } + EXPECT_EQ(0, waiting_thread_count_); +} + +int WorkQueue::GetThreadId() { + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + DCHECK(!EveryIdWasAllocated()); + return thread_started_counter_++; // Give out Unique IDs. +} + +bool WorkQueue::EveryIdWasAllocated() const { + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + return thread_count_ == thread_started_counter_; +} + +TimeDelta WorkQueue::GetAnAssignment(int thread_id) { + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + DCHECK_LT(0, task_count_); + assignment_history_[thread_id]++; + if (0 == --task_count_) { + no_more_tasks_.Signal(); + } + return worker_delay_; +} + +void WorkQueue::WorkIsCompleted(int thread_id) { + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + completion_history_[thread_id]++; +} + +int WorkQueue::task_count() const { + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + return task_count_; +} + +bool WorkQueue::allow_help_requests() const { + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + return allow_help_requests_; +} + +bool WorkQueue::shutdown() const { + lock_.AssertAcquired(); + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + return shutdown_; +} + +// Because this method is called from the test's main thread we need to actually +// take the lock. Threads will call the thread_shutting_down() method with the +// lock already acquired. +bool WorkQueue::ThreadSafeCheckShutdown(int thread_count) { + bool all_shutdown; + butil::AutoLock auto_lock(lock_); + { + // Declare in scope so DFAKE is guranteed to be destroyed before AutoLock. + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + all_shutdown = (shutdown_task_count_ == thread_count); + } + return all_shutdown; +} + +void WorkQueue::thread_shutting_down() { + lock_.AssertAcquired(); + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + shutdown_task_count_++; +} + +Lock* WorkQueue::lock() { + return &lock_; +} + +ConditionVariable* WorkQueue::work_is_available() { + return &work_is_available_; +} + +ConditionVariable* WorkQueue::all_threads_have_ids() { + return &all_threads_have_ids_; +} + +ConditionVariable* WorkQueue::no_more_tasks() { + return &no_more_tasks_; +} + +void WorkQueue::ResetHistory() { + for (int i = 0; i < thread_count_; ++i) { + assignment_history_[i] = 0; + completion_history_[i] = 0; + } +} + +int WorkQueue::GetMinCompletionsByWorkerThread() const { + int minumum = completion_history_[0]; + for (int i = 0; i < thread_count_; ++i) + minumum = std::min(minumum, completion_history_[i]); + return minumum; +} + +int WorkQueue::GetMaxCompletionsByWorkerThread() const { + int maximum = completion_history_[0]; + for (int i = 0; i < thread_count_; ++i) + maximum = std::max(maximum, completion_history_[i]); + return maximum; +} + +int WorkQueue::GetNumThreadsTakingAssignments() const { + int count = 0; + for (int i = 0; i < thread_count_; ++i) + if (assignment_history_[i]) + count++; + return count; +} + +int WorkQueue::GetNumThreadsCompletingTasks() const { + int count = 0; + for (int i = 0; i < thread_count_; ++i) + if (completion_history_[i]) + count++; + return count; +} + +int WorkQueue::GetNumberOfCompletedTasks() const { + int total = 0; + for (int i = 0; i < thread_count_; ++i) + total += completion_history_[i]; + return total; +} + +void WorkQueue::SetWorkTime(TimeDelta delay) { + worker_delay_ = delay; +} + +void WorkQueue::SetTaskCount(int count) { + task_count_ = count; +} + +void WorkQueue::SetAllowHelp(bool allow) { + allow_help_requests_ = allow; +} + +void WorkQueue::SetShutdown() { + lock_.AssertAcquired(); + shutdown_ = true; +} + +void WorkQueue::SpinUntilAllThreadsAreWaiting() { + while (true) { + { + butil::AutoLock auto_lock(lock_); + if (waiting_thread_count_ == thread_count_) + break; + } + PlatformThread::Sleep(TimeDelta::FromMilliseconds(30)); + } +} + +void WorkQueue::SpinUntilTaskCountLessThan(int task_count) { + while (true) { + { + butil::AutoLock auto_lock(lock_); + if (task_count_ < task_count) + break; + } + PlatformThread::Sleep(TimeDelta::FromMilliseconds(30)); + } +} + + +//------------------------------------------------------------------------------ +// Define the standard worker task. Several tests will spin out many of these +// threads. +//------------------------------------------------------------------------------ + +// The multithread tests involve several threads with a task to perform as +// directed by an instance of the class WorkQueue. +// The task is to: +// a) Check to see if there are more tasks (there is a task counter). +// a1) Wait on condition variable if there are no tasks currently. +// b) Call a function to see what should be done. +// c) Do some computation based on the number of milliseconds returned in (b). +// d) go back to (a). + +// WorkQueue::ThreadMain() implements the above task for all threads. +// It calls the controlling object to tell the creator about progress, and to +// ask about tasks. + +void WorkQueue::ThreadMain() { + int thread_id; + { + butil::AutoLock auto_lock(lock_); + thread_id = GetThreadId(); + if (EveryIdWasAllocated()) + all_threads_have_ids()->Signal(); // Tell creator we're ready. + } + + Lock private_lock; // Used to waste time on "our work". + while (1) { // This is the main consumer loop. + TimeDelta work_time; + bool could_use_help; + { + butil::AutoLock auto_lock(lock_); + while (0 == task_count() && !shutdown()) { + ++waiting_thread_count_; + work_is_available()->Wait(); + --waiting_thread_count_; + } + if (shutdown()) { + // Ack the notification of a shutdown message back to the controller. + thread_shutting_down(); + return; // Terminate. + } + // Get our task duration from the queue. + work_time = GetAnAssignment(thread_id); + could_use_help = (task_count() > 0) && allow_help_requests(); + } // Release lock + + // Do work (outside of locked region. + if (could_use_help) + work_is_available()->Signal(); // Get help from other threads. + + if (work_time > TimeDelta::FromMilliseconds(0)) { + // We could just sleep(), but we'll instead further exercise the + // condition variable class, and do a timed wait. + butil::AutoLock auto_lock(private_lock); + ConditionVariable private_cv(&private_lock); + private_cv.TimedWait(work_time); // Unsynchronized waiting. + } + + { + butil::AutoLock auto_lock(lock_); + // Send notification that we completed our "work." + WorkIsCompleted(thread_id); + } + } +} + +} // namespace + +} // namespace butil diff --git a/test/cpu_unittest.cc b/test/cpu_unittest.cc new file mode 100644 index 0000000..e7e13ab --- /dev/null +++ b/test/cpu_unittest.cc @@ -0,0 +1,97 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/cpu.h" +#include "butil/build_config.h" + +#include + +// Tests whether we can run extended instructions represented by the CPU +// information. This test actually executes some extended instructions (such as +// MMX, SSE, etc.) supported by the CPU and sees we can run them without +// "undefined instruction" exceptions. That is, this test succeeds when this +// test finishes without a crash. +TEST(CPU, RunExtendedInstructions) { +#if defined(ARCH_CPU_X86_FAMILY) + // Retrieve the CPU information. + butil::CPU cpu; + +// TODO(jschuh): crbug.com/168866 Find a way to enable this on Win64. +#if defined(OS_WIN) && !defined(_M_X64) + ASSERT_TRUE(cpu.has_mmx()); + + // Execute an MMX instruction. + __asm emms; + + if (cpu.has_sse()) { + // Execute an SSE instruction. + __asm xorps xmm0, xmm0; + } + + if (cpu.has_sse2()) { + // Execute an SSE 2 instruction. + __asm psrldq xmm0, 0; + } + + if (cpu.has_sse3()) { + // Execute an SSE 3 instruction. + __asm addsubpd xmm0, xmm0; + } + + if (cpu.has_ssse3()) { + // Execute a Supplimental SSE 3 instruction. + __asm psignb xmm0, xmm0; + } + + if (cpu.has_sse41()) { + // Execute an SSE 4.1 instruction. + __asm pmuldq xmm0, xmm0; + } + + if (cpu.has_sse42()) { + // Execute an SSE 4.2 instruction. + __asm crc32 eax, eax; + } +#elif defined(OS_POSIX) && defined(__x86_64__) + ASSERT_TRUE(cpu.has_mmx()); + + // Execute an MMX instruction. + __asm__ __volatile__("emms\n" : : : "mm0"); + + if (cpu.has_sse()) { + // Execute an SSE instruction. + __asm__ __volatile__("xorps %%xmm0, %%xmm0\n" : : : "xmm0"); + } + + if (cpu.has_sse2()) { + // Execute an SSE 2 instruction. + __asm__ __volatile__("psrldq $0, %%xmm0\n" : : : "xmm0"); + } + + if (cpu.has_sse3()) { + // Execute an SSE 3 instruction. + __asm__ __volatile__("addsubpd %%xmm0, %%xmm0\n" : : : "xmm0"); + } + + // NOTE(gejun): Not work in gcc 3.4 +#if !defined(COMPILER_GCC) || __GNUC__ >= 4 + if (cpu.has_ssse3()) { + // Execute a Supplimental SSE 3 instruction. + __asm__ __volatile__("psignb %%xmm0, %%xmm0\n" : : : "xmm0"); + } + + if (cpu.has_sse41()) { + // Execute an SSE 4.1 instruction. + __asm__ __volatile__("pmuldq %%xmm0, %%xmm0\n" : : : "xmm0"); + } + + if (cpu.has_sse42()) { + // Execute an SSE 4.2 instruction. + __asm__ __volatile__("crc32 %%eax, %%eax\n" : : : "eax"); + } +#endif + +#endif +#endif +} diff --git a/test/crash_logging_unittest.cc b/test/crash_logging_unittest.cc new file mode 100644 index 0000000..d0fa432 --- /dev/null +++ b/test/crash_logging_unittest.cc @@ -0,0 +1,181 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/debug/crash_logging.h" + +#include +#include + +#include + +namespace { + +std::map* key_values_ = NULL; + +} // namespace + +class CrashLoggingTest : public testing::Test { + public: + virtual void SetUp() { + key_values_ = new std::map; + butil::debug::SetCrashKeyReportingFunctions( + &CrashLoggingTest::SetKeyValue, + &CrashLoggingTest::ClearKeyValue); + } + + virtual void TearDown() { + butil::debug::ResetCrashLoggingForTesting(); + + delete key_values_; + key_values_ = NULL; + } + + private: + static void SetKeyValue(const butil::StringPiece& key, + const butil::StringPiece& value) { + (*key_values_)[key.as_string()] = value.as_string(); + } + + static void ClearKeyValue(const butil::StringPiece& key) { + key_values_->erase(key.as_string()); + } +}; + +TEST_F(CrashLoggingTest, SetClearSingle) { + const char* kTestKey = "test-key"; + butil::debug::CrashKey keys[] = { { kTestKey, 255 } }; + butil::debug::InitCrashKeys(keys, arraysize(keys), 255); + + butil::debug::SetCrashKeyValue(kTestKey, "value"); + EXPECT_EQ("value", (*key_values_)[kTestKey]); + + butil::debug::ClearCrashKey(kTestKey); + EXPECT_TRUE(key_values_->end() == key_values_->find(kTestKey)); +} + +TEST_F(CrashLoggingTest, SetChunked) { + const char* kTestKey = "chunky"; + const char* kChunk1 = "chunky-1"; + const char* kChunk2 = "chunky-2"; + const char* kChunk3 = "chunky-3"; + butil::debug::CrashKey keys[] = { { kTestKey, 15 } }; + butil::debug::InitCrashKeys(keys, arraysize(keys), 5); + + std::map& values = *key_values_; + + // Fill only the first chunk. + butil::debug::SetCrashKeyValue(kTestKey, "foo"); + EXPECT_EQ(1u, values.size()); + EXPECT_EQ("foo", values[kChunk1]); + EXPECT_TRUE(values.end() == values.find(kChunk2)); + EXPECT_TRUE(values.end() == values.find(kChunk3)); + + // Fill three chunks with truncation (max length is 15, this string is 20). + butil::debug::SetCrashKeyValue(kTestKey, "five four three two"); + EXPECT_EQ(3u, values.size()); + EXPECT_EQ("five ", values[kChunk1]); + EXPECT_EQ("four ", values[kChunk2]); + EXPECT_EQ("three", values[kChunk3]); + + // Clear everything. + butil::debug::ClearCrashKey(kTestKey); + EXPECT_EQ(0u, values.size()); + EXPECT_TRUE(values.end() == values.find(kChunk1)); + EXPECT_TRUE(values.end() == values.find(kChunk2)); + EXPECT_TRUE(values.end() == values.find(kChunk3)); + + // Refill all three chunks with truncation, then test that setting a smaller + // value clears the third chunk. + butil::debug::SetCrashKeyValue(kTestKey, "five four three two"); + butil::debug::SetCrashKeyValue(kTestKey, "allays"); + EXPECT_EQ(2u, values.size()); + EXPECT_EQ("allay", values[kChunk1]); + EXPECT_EQ("s", values[kChunk2]); + EXPECT_TRUE(values.end() == values.find(kChunk3)); + + // Clear everything. + butil::debug::ClearCrashKey(kTestKey); + EXPECT_EQ(0u, values.size()); + EXPECT_TRUE(values.end() == values.find(kChunk1)); + EXPECT_TRUE(values.end() == values.find(kChunk2)); + EXPECT_TRUE(values.end() == values.find(kChunk3)); +} + +TEST_F(CrashLoggingTest, ScopedCrashKey) { + const char* kTestKey = "test-key"; + butil::debug::CrashKey keys[] = { { kTestKey, 255 } }; + butil::debug::InitCrashKeys(keys, arraysize(keys), 255); + + EXPECT_EQ(0u, key_values_->size()); + EXPECT_TRUE(key_values_->end() == key_values_->find(kTestKey)); + { + butil::debug::ScopedCrashKey scoped_crash_key(kTestKey, "value"); + EXPECT_EQ("value", (*key_values_)[kTestKey]); + EXPECT_EQ(1u, key_values_->size()); + } + EXPECT_EQ(0u, key_values_->size()); + EXPECT_TRUE(key_values_->end() == key_values_->find(kTestKey)); +} + +TEST_F(CrashLoggingTest, InitSize) { + butil::debug::CrashKey keys[] = { + { "chunked-3", 15 }, + { "single", 5 }, + { "chunked-6", 30 }, + }; + + size_t num_keys = butil::debug::InitCrashKeys(keys, arraysize(keys), 5); + + EXPECT_EQ(10u, num_keys); + + EXPECT_TRUE(butil::debug::LookupCrashKey("chunked-3")); + EXPECT_TRUE(butil::debug::LookupCrashKey("single")); + EXPECT_TRUE(butil::debug::LookupCrashKey("chunked-6")); + EXPECT_FALSE(butil::debug::LookupCrashKey("chunked-6-4")); +} + +TEST_F(CrashLoggingTest, ChunkValue) { + using butil::debug::ChunkCrashKeyValue; + + // Test truncation. + butil::debug::CrashKey key = { "chunky", 10 }; + std::vector results = + ChunkCrashKeyValue(key, "hello world", 64); + ASSERT_EQ(1u, results.size()); + EXPECT_EQ("hello worl", results[0]); + + // Test short string. + results = ChunkCrashKeyValue(key, "hi", 10); + ASSERT_EQ(1u, results.size()); + EXPECT_EQ("hi", results[0]); + + // Test chunk pair. + key.max_length = 6; + results = ChunkCrashKeyValue(key, "foobar", 3); + ASSERT_EQ(2u, results.size()); + EXPECT_EQ("foo", results[0]); + EXPECT_EQ("bar", results[1]); + + // Test chunk pair truncation. + results = ChunkCrashKeyValue(key, "foobared", 3); + ASSERT_EQ(2u, results.size()); + EXPECT_EQ("foo", results[0]); + EXPECT_EQ("bar", results[1]); + + // Test extra chunks. + key.max_length = 100; + results = ChunkCrashKeyValue(key, "hello world", 3); + ASSERT_EQ(4u, results.size()); + EXPECT_EQ("hel", results[0]); + EXPECT_EQ("lo ", results[1]); + EXPECT_EQ("wor", results[2]); + EXPECT_EQ("ld", results[3]); +} + +TEST_F(CrashLoggingTest, ChunkRounding) { + // If max_length=12 and max_chunk_length=5, there should be 3 chunks, + // not 2. + butil::debug::CrashKey key = { "round", 12 }; + EXPECT_EQ(3u, butil::debug::InitCrashKeys(&key, 1, 5)); +} diff --git a/test/crc32c_unittest.cc b/test/crc32c_unittest.cc new file mode 100644 index 0000000..41591ee --- /dev/null +++ b/test/crc32c_unittest.cc @@ -0,0 +1,77 @@ +// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// Copyright (c) 2011 The LevelDB Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. See the AUTHORS file for names of contributors. + +#include +#include "butil/crc32c.h" + +namespace butil { +namespace crc32c { + +class CRC : public testing::Test { }; + +TEST_F(CRC, StandardResults) { + // From rfc3720 section B.4. + char buf[32]; + + memset(buf, 0, sizeof(buf)); + ASSERT_EQ(0x8a9136aaU, Value(buf, sizeof(buf))); + + memset(buf, 0xff, sizeof(buf)); + ASSERT_EQ(0x62a8ab43U, Value(buf, sizeof(buf))); + + for (int i = 0; i < 32; i++) { + buf[i] = i; + } + ASSERT_EQ(0x46dd794eU, Value(buf, sizeof(buf))); + + for (int i = 0; i < 32; i++) { + buf[i] = 31 - i; + } + ASSERT_EQ(0x113fdb5cU, Value(buf, sizeof(buf))); + + unsigned char data[48] = { + 0x01, 0xc0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x18, + 0x28, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + ASSERT_EQ(0xd9963a56, Value(reinterpret_cast(data), sizeof(data))); +} + +TEST_F(CRC, Values) { + ASSERT_NE(Value("a", 1), Value("foo", 3)); +} + +TEST_F(CRC, Extend) { + ASSERT_EQ(Value("hello world", 11), + Extend(Value("hello ", 6), "world", 5)); +} + +TEST_F(CRC, Mask) { + uint32_t crc = Value("foo", 3); + ASSERT_NE(crc, Mask(crc)); + ASSERT_NE(crc, Mask(Mask(crc))); + ASSERT_EQ(crc, Unmask(Mask(crc))); + ASSERT_EQ(crc, Unmask(Unmask(Mask(Mask(crc))))); +} + +TEST_F(CRC, fast_is_on) { + std::cout << "IsFastCrc32Supported=" << IsFastCrc32Supported() << std::endl; +} + +} // namespace crc32c +} // namespace butil diff --git a/test/dir_reader_posix_unittest.cc b/test/dir_reader_posix_unittest.cc new file mode 100644 index 0000000..ad28e9c --- /dev/null +++ b/test/dir_reader_posix_unittest.cc @@ -0,0 +1,92 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/files/dir_reader_posix.h" + +#include +#include +#include +#include +#include + +#include "butil/logging.h" +#include + +#if defined(OS_ANDROID) +#include "butil/os_compat_android.h" +#endif + +namespace butil { + +TEST(DirReaderPosixUnittest, Read) { + static const unsigned kNumFiles = 100; + + if (DirReaderPosix::IsFallback()) + return; + + char kDirTemplate[] = "/tmp/org.chromium.dir-reader-posix-XXXXXX"; + const char* dir = mkdtemp(kDirTemplate); + ASSERT_TRUE(dir); + + const int prev_wd = open(".", O_RDONLY | O_DIRECTORY); + DCHECK_GE(prev_wd, 0); + + PCHECK(chdir(dir) == 0); + + for (unsigned i = 0; i < kNumFiles; i++) { + char buf[16]; + snprintf(buf, sizeof(buf), "%d", i); + const int fd = open(buf, O_CREAT | O_RDONLY | O_EXCL, 0600); + PCHECK(fd >= 0); + PCHECK(close(fd) == 0); + } + + std::set seen; + + DirReaderPosix reader(dir); + EXPECT_TRUE(reader.IsValid()); + + if (!reader.IsValid()) + return; + + bool seen_dot = false, seen_dotdot = false; + + for (; reader.Next(); ) { + if (strcmp(reader.name(), ".") == 0) { + seen_dot = true; + continue; + } + if (strcmp(reader.name(), "..") == 0) { + seen_dotdot = true; + continue; + } + + SCOPED_TRACE(testing::Message() << "reader.name(): " << reader.name()); + + char *endptr; + const unsigned long value = strtoul(reader.name(), &endptr, 10); + + EXPECT_FALSE(*endptr); + EXPECT_LT(value, kNumFiles); + EXPECT_EQ(0u, seen.count(value)); + seen.insert(value); + } + + for (unsigned i = 0; i < kNumFiles; i++) { + char buf[16]; + snprintf(buf, sizeof(buf), "%d", i); + PCHECK(unlink(buf) == 0); + } + + PCHECK(rmdir(dir) == 0); + + PCHECK(fchdir(prev_wd) == 0); + PCHECK(close(prev_wd) == 0); + + EXPECT_TRUE(seen_dot); + EXPECT_TRUE(seen_dotdot); + EXPECT_EQ(kNumFiles, seen.size()); +} + +} // namespace butil diff --git a/test/echo.proto b/test/echo.proto new file mode 100644 index 0000000..c9fa8ac --- /dev/null +++ b/test/echo.proto @@ -0,0 +1,118 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +import "idl_options.proto"; +option (idl_support) = true; +option cc_generic_services = true; +package test; + +message EchoRequest { + required string message = 1; + optional int32 code = 2; + optional bool close_fd = 3; + optional int32 sleep_us = 4; + optional int32 server_fail = 5; + optional string http_header = 6; +}; + +message EchoResponse { + required string message = 1; + repeated int32 code_list = 2; + optional uint64 receiving_socket_id = 3; +}; + +message ComboRequest { + repeated EchoRequest requests = 1; +}; + +message BytesRequest { + required bytes databytes = 1; +}; + +message BytesResponse { + required bytes databytes = 1; +}; + +message ComboResponse { + repeated EchoResponse responses = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); + rpc ComboEcho(ComboRequest) returns (ComboResponse); + rpc BytesEcho1(BytesRequest) returns (BytesResponse); + rpc BytesEcho2(BytesRequest) returns (BytesResponse); +} + +message HttpRequest {} +message HttpResponse {} +service DownloadService { + rpc Download(HttpRequest) returns (HttpResponse); + rpc DownloadFailed(HttpRequest) returns (HttpResponse); +} + +service UploadService { + rpc Upload(HttpRequest) returns (HttpResponse); + rpc UploadFailed(HttpRequest) returns (HttpResponse); +} + +service UserNamingService { + rpc ListNames(HttpRequest) returns (HttpResponse); + rpc Touch(HttpRequest) returns (HttpResponse); +}; + +service DiscoveryNamingService { + rpc Nodes(HttpRequest) returns (HttpResponse); + rpc Fetchs(HttpRequest) returns (HttpResponse); + rpc Register(HttpRequest) returns (HttpResponse); + rpc Renew(HttpRequest) returns (HttpResponse); + rpc Cancel(HttpRequest) returns (HttpResponse); +}; + +service NacosNamingService { + rpc Login(HttpRequest) returns (HttpResponse); + rpc List(HttpRequest) returns (HttpResponse); +}; + +service HttpService { + rpc Head(HttpRequest) returns (HttpResponse); + rpc Expect(HttpRequest) returns (HttpResponse); +} + +enum State0 { + STATE0_NUM_0 = 0; + STATE0_NUM_1 = 1; + STATE0_NUM_2 = 2; +}; + +enum State1 { + STATE1_NUM_0 = 0; + STATE1_NUM_1 = 1; +}; + +message Message1 { + required State0 stat = 1; +}; +message Message2 { + required State1 stat = 1; +}; + +message RecursiveMessage { + optional RecursiveMessage child = 1; + optional string data = 2; +} diff --git a/test/endpoint_unittest.cpp b/test/endpoint_unittest.cpp new file mode 100644 index 0000000..af3098f --- /dev/null +++ b/test/endpoint_unittest.cpp @@ -0,0 +1,614 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include "butil/errno.h" +#include "butil/endpoint.h" +#include "butil/logging.h" +#include "butil/containers/flat_map.h" +#include "butil/details/extended_endpoint.hpp" +#include +#if defined(OS_MACOSX) +#include +#endif + +namespace butil { +int pthread_timed_connect(int sockfd, const struct sockaddr* serv_addr, + socklen_t addrlen, const timespec* abstime); +} + +namespace { + +using butil::details::ExtendedEndPoint; + +TEST(EndPointTest, comparisons) { + butil::EndPoint p1(butil::int2ip(1234), 5678); + butil::EndPoint p2 = p1; + ASSERT_TRUE(p1 == p2 && !(p1 != p2)); + ASSERT_TRUE(p1 <= p2 && p1 >= p2 && !(p1 < p2 || p1 > p2)); + ++p2.port; + ASSERT_TRUE(p1 != p2 && !(p1 == p2)); + ASSERT_TRUE(p1 < p2 && p2 > p1 && !(p2 <= p1 || p1 >= p2)); + --p2.port; + p2.ip = butil::int2ip(butil::ip2int(p2.ip)-1); + ASSERT_TRUE(p1 != p2 && !(p1 == p2)); + ASSERT_TRUE(p1 > p2 && p2 < p1 && !(p1 <= p2 || p2 >= p1)); +} + +TEST(EndPointTest, ip_t) { + LOG(INFO) << "INET_ADDRSTRLEN = " << INET_ADDRSTRLEN; + + butil::ip_t ip0; + ASSERT_EQ(0, butil::str2ip("1.1.1.1", &ip0)); + ASSERT_STREQ("1.1.1.1", butil::ip2str(ip0).c_str()); + ASSERT_EQ(-1, butil::str2ip("301.1.1.1", &ip0)); + ASSERT_EQ(-1, butil::str2ip("1.-1.1.1", &ip0)); + ASSERT_EQ(-1, butil::str2ip("1.1.-101.1", &ip0)); + ASSERT_STREQ("1.0.0.0", butil::ip2str(butil::int2ip(1)).c_str()); + + butil::ip_t ip1, ip2, ip3; + ASSERT_EQ(0, butil::str2ip("192.168.0.1", &ip1)); + ASSERT_EQ(0, butil::str2ip("192.168.0.2", &ip2)); + ip3 = ip1; + ASSERT_LT(ip1, ip2); + ASSERT_LE(ip1, ip2); + ASSERT_GT(ip2, ip1); + ASSERT_GE(ip2, ip1); + ASSERT_TRUE(ip1 != ip2); + ASSERT_FALSE(ip1 == ip2); + ASSERT_TRUE(ip1 == ip3); + ASSERT_FALSE(ip1 != ip3); +} + +TEST(EndPointTest, show_local_info) { + LOG(INFO) << "my_ip is " << butil::my_ip() << std::endl + << "my_ip_cstr is " << butil::my_ip_cstr() << std::endl + << "my_hostname is " << butil::my_hostname(); +} + +TEST(EndPointTest, endpoint) { + butil::EndPoint p1; + ASSERT_EQ(butil::IP_ANY, p1.ip); + ASSERT_EQ(0, p1.port); + + butil::EndPoint p2(butil::IP_NONE, -1); + ASSERT_EQ(butil::IP_NONE, p2.ip); + ASSERT_EQ(-1, p2.port); + + butil::EndPoint p3; + ASSERT_EQ(-1, butil::str2endpoint(" 127.0.0.1:-1", &p3)); + ASSERT_EQ(-1, butil::str2endpoint(" 127.0.0.1:65536", &p3)); + ASSERT_EQ(0, butil::str2endpoint(" 127.0.0.1:65535", &p3)); + ASSERT_EQ(0, butil::str2endpoint(" 127.0.0.1:0", &p3)); + + butil::EndPoint p4; + ASSERT_EQ(0, butil::str2endpoint(" 127.0.0.1: 289 ", &p4)); + ASSERT_STREQ("127.0.0.1", butil::ip2str(p4.ip).c_str()); + ASSERT_EQ(289, p4.port); + + butil::EndPoint p5; + ASSERT_EQ(-1, hostname2endpoint("localhost:-1", &p5)); + ASSERT_EQ(-1, hostname2endpoint("localhost:65536", &p5)); + ASSERT_EQ(0, hostname2endpoint("localhost:65535", &p5)) << berror(); + ASSERT_EQ(0, hostname2endpoint("localhost:0", &p5)); + +#ifdef BAIDU_INTERNAL + butil::EndPoint p6; + ASSERT_EQ(0, hostname2endpoint("tc-cm-et21.tc: 289 ", &p6)); + ASSERT_STREQ("10.23.249.73", butil::ip2str(p6.ip).c_str()); + ASSERT_EQ(289, p6.port); +#endif +} +TEST(EndPointTest, endpoint_reject_trailing_characters_after_port) { + butil::EndPoint ep; + + // invalid: non-whitespace after port + ASSERT_EQ(-1, butil::str2endpoint("127.0.0.1:8000a", &ep)); + ASSERT_EQ(-1, butil::str2endpoint("127.0.0.1:8000#", &ep)); + ASSERT_EQ(-1, butil::str2endpoint("127.0.0.1:8000abc", &ep)); + + // valid: only whitespace after port + ASSERT_EQ(0, butil::str2endpoint("127.0.0.1:8000 ", &ep)); + ASSERT_EQ(0, butil::str2endpoint("127.0.0.1:8000\t", &ep)); + ASSERT_EQ(0, butil::str2endpoint("127.0.0.1:8000\n", &ep)); +} + +TEST(EndPointTest, hash_table) { + butil::hash_map m; + butil::EndPoint ep1(butil::IP_ANY, 123); + butil::EndPoint ep2(butil::IP_ANY, 456); + ++m[ep1]; + ASSERT_TRUE(m.find(ep1) != m.end()); + ASSERT_EQ(1, m.find(ep1)->second); + ASSERT_EQ(1u, m.size()); + + ++m[ep1]; + ASSERT_TRUE(m.find(ep1) != m.end()); + ASSERT_EQ(2, m.find(ep1)->second); + ASSERT_EQ(1u, m.size()); + + ++m[ep2]; + ASSERT_TRUE(m.find(ep2) != m.end()); + ASSERT_EQ(1, m.find(ep2)->second); + ASSERT_EQ(2u, m.size()); +} + +TEST(EndPointTest, flat_map) { + butil::FlatMap m; + ASSERT_EQ(0, m.init(1024)); + uint32_t port = 8088; + + butil::EndPoint ep1(butil::IP_ANY, port); + butil::EndPoint ep2(butil::IP_ANY, port); + ++m[ep1]; + ++m[ep2]; + ASSERT_EQ(1u, m.size()); + + butil::ip_t ip_addr; + butil::str2ip("10.10.10.10", &ip_addr); + int ip = butil::ip2int(ip_addr); + + for (int i = 0; i < 1023; ++i) { + butil::EndPoint ep(butil::int2ip(++ip), port); + ++m[ep]; + } + + butil::BucketInfo info = m.bucket_info(); + LOG(INFO) << "bucket info max long=" << info.longest_length + << " avg=" << info.average_length << std::endl; + ASSERT_LT(info.longest_length, 32ul) << "detect hash collision and it's too large."; +} + +void* server_proc(void* arg) { + int listen_fd = (int64_t)arg; + sockaddr_storage ss; + socklen_t len = sizeof(ss); + int fd = accept(listen_fd, (sockaddr*)&ss, &len); + return (void*)(int64_t)fd; +} + +static void test_listen_connect(const std::string& server_addr, const std::string& exp_client_addr) { + butil::EndPoint point; + ASSERT_EQ(0, butil::str2endpoint(server_addr.c_str(), &point)); + ASSERT_EQ(server_addr, butil::endpoint2str(point).c_str()); + + int listen_fd = butil::tcp_listen(point); + ASSERT_GT(listen_fd, 0); + pthread_t pid; + pthread_create(&pid, NULL, server_proc, (void*)(int64_t)listen_fd); + + int fd = butil::tcp_connect(point, NULL); + ASSERT_GT(fd, 0); + + butil::EndPoint point2; + ASSERT_EQ(0, butil::get_local_side(fd, &point2)); + + std::string s = butil::endpoint2str(point2).c_str(); + if (butil::get_endpoint_type(point2) == AF_UNIX) { + ASSERT_EQ(exp_client_addr, s); + } else { + ASSERT_EQ(exp_client_addr, s.substr(0, exp_client_addr.size())); + } + ASSERT_EQ(0, butil::get_remote_side(fd, &point2)); + ASSERT_EQ(server_addr, butil::endpoint2str(point2).c_str()); + close(fd); + + void* ret = nullptr; + pthread_join(pid, &ret); + int server_fd = (int)(int64_t)ret; + ASSERT_GT(server_fd, 0); + close(server_fd); + close(listen_fd); +} + +static void test_parse_and_serialize(const std::string& instr, const std::string& outstr) { + butil::EndPoint ep; + ASSERT_EQ(0, butil::str2endpoint(instr.c_str(), &ep)); + butil::EndPointStr s = butil::endpoint2str(ep); + ASSERT_EQ(outstr, std::string(s.c_str())); +} + +TEST(EndPointTest, ipv4) { + test_listen_connect("127.0.0.1:8787", "127.0.0.1:"); +} + +TEST(EndPointTest, ipv6) { + // FIXME: test environ may not support ipv6 + // test_listen_connect("[::1]:8787", "[::1]:"); + + test_parse_and_serialize("[::1]:8080", "[::1]:8080"); + test_parse_and_serialize(" [::1]:65535 ", "[::1]:65535"); + test_parse_and_serialize(" [2001:0db8:a001:0002:0003:0ab9:C0A8:0102]:65535 ", + "[2001:db8:a001:2:3:ab9:c0a8:102]:65535"); + + butil::EndPoint ep; + ASSERT_EQ(-1, butil::str2endpoint("[2001:db8:1:2:3:ab9:c0a8:102]", &ep)); + ASSERT_EQ(-1, butil::str2endpoint("[2001:db8:1:2:3:ab9:c0a8:102]#654321", &ep)); + ASSERT_EQ(-1, butil::str2endpoint("ipv6:2001:db8:1:2:3:ab9:c0a8:102", &ep)); + ASSERT_EQ(-1, butil::str2endpoint("[", &ep)); + ASSERT_EQ(-1, butil::str2endpoint("[::1", &ep)); + ASSERT_EQ(-1, butil::str2endpoint("[]:80", &ep)); + ASSERT_EQ(-1, butil::str2endpoint("[]", &ep)); + ASSERT_EQ(-1, butil::str2endpoint("[]:", &ep)); +} + +TEST(EndPointTest, unix_socket) { + ::unlink("test.sock"); + test_listen_connect("unix:test.sock", "unix:"); + ::unlink("test.sock"); + + butil::EndPoint point; + ASSERT_EQ(-1, butil::str2endpoint("", &point)); + ASSERT_EQ(-1, butil::str2endpoint("a.sock", &point)); + ASSERT_EQ(-1, butil::str2endpoint("unix:", &point)); + ASSERT_EQ(-1, butil::str2endpoint(" unix: ", &point)); + ASSERT_EQ(0, butil::str2endpoint("unix://a.sock", 123, &point)); + ASSERT_EQ(std::string("unix://a.sock"), butil::endpoint2str(point).c_str()); + + std::string long_path = "unix:"; + long_path.append(sizeof(sockaddr_un::sun_path) - 1, 'a'); + ASSERT_EQ(0, butil::str2endpoint(long_path.c_str(), &point)); + ASSERT_EQ(long_path, butil::endpoint2str(point).c_str()); + long_path.push_back('a'); + ASSERT_EQ(-1, butil::str2endpoint(long_path.c_str(), &point)); + char buf[128] = {0}; // braft use this size of buffer + size_t ret = snprintf(buf, sizeof(buf), "%s:%d", butil::endpoint2str(point).c_str(), INT_MAX); + ASSERT_LT(ret, sizeof(buf) - 1); +} + +TEST(EndPointTest, original_endpoint) { + butil::EndPoint ep; + ASSERT_FALSE(ExtendedEndPoint::is_extended(ep)); + ASSERT_EQ(NULL, ExtendedEndPoint::address(ep)); + + ASSERT_EQ(0, butil::str2endpoint("1.2.3.4:5678", &ep)); + ASSERT_FALSE(ExtendedEndPoint::is_extended(ep)); + ASSERT_EQ(NULL, ExtendedEndPoint::address(ep)); + + // ctor & dtor + { + butil::EndPoint ep2(ep); + ASSERT_FALSE(ExtendedEndPoint::is_extended(ep)); + ASSERT_EQ(ep.ip, ep2.ip); + ASSERT_EQ(ep.port, ep2.port); + } + + // assign + butil::EndPoint ep2; + ep2 = ep; + ASSERT_EQ(ep.ip, ep2.ip); + ASSERT_EQ(ep.port, ep2.port); +} + +TEST(EndPointTest, extended_endpoint) { + butil::EndPoint ep; + ASSERT_EQ(0, butil::str2endpoint("unix:sock.file", &ep)); + ASSERT_TRUE(ExtendedEndPoint::is_extended(ep)); + ExtendedEndPoint* eep = ExtendedEndPoint::address(ep); + ASSERT_TRUE(eep); + ASSERT_EQ(AF_UNIX, eep->family()); + ASSERT_EQ(1, eep->_ref_count.load()); + + // copy ctor & dtor + { + butil::EndPoint tmp(ep); + ASSERT_EQ(2, eep->_ref_count.load()); + ASSERT_EQ(eep, ExtendedEndPoint::address(tmp)); + ASSERT_EQ(eep, ExtendedEndPoint::address(ep)); + } + ASSERT_EQ(1, eep->_ref_count.load()); + + butil::EndPoint ep2; + + // extended endpoint assigns to original endpoint + ep2 = ep; + ASSERT_EQ(2, eep->_ref_count.load()); + ASSERT_EQ(eep, ExtendedEndPoint::address(ep2)); + + // original endpoint assigns to extended endpoint + ep2 = butil::EndPoint(); + ASSERT_EQ(1, eep->_ref_count.load()); + ASSERT_FALSE(ExtendedEndPoint::is_extended(ep2)); + + // extended endpoint assigns to extended endpoint + ASSERT_EQ(0, butil::str2endpoint("[::1]:2233", &ep2)); + ExtendedEndPoint* eep2 = ExtendedEndPoint::address(ep2); + ASSERT_TRUE(eep2); + ep2 = ep; + // eep2 has been returned to resource pool, but we can still access it here unsafely. + ASSERT_EQ(0, eep2->_ref_count.load()); + ASSERT_EQ(AF_UNSPEC, eep2->family()); + ASSERT_EQ(2, eep->_ref_count.load()); + ASSERT_EQ(eep, ExtendedEndPoint::address(ep)); + ASSERT_EQ(eep, ExtendedEndPoint::address(ep2)); + + ASSERT_EQ(0, str2endpoint("[::1]:2233", &ep2)); + ASSERT_EQ(1, eep->_ref_count.load()); + eep2 = ExtendedEndPoint::address(ep2); + ASSERT_NE(eep, eep2); + ASSERT_EQ(1, eep2->_ref_count.load()); +} + +TEST(EndPointTest, endpoint_compare) { + butil::EndPoint ep1, ep2, ep3; + + ASSERT_EQ(0, butil::str2endpoint("127.0.0.1:8080", &ep1)); + ASSERT_EQ(0, butil::str2endpoint("127.0.0.1:8080", &ep2)); + ASSERT_EQ(0, butil::str2endpoint("127.0.0.3:8080", &ep3)); + ASSERT_EQ(ep1, ep2); + ASSERT_NE(ep1, ep3); + + ASSERT_EQ(0, butil::str2endpoint("unix:sock1.file", &ep1)); + ASSERT_EQ(0, butil::str2endpoint("unix:sock1.file", &ep2)); + ASSERT_EQ(0, butil::str2endpoint("unix:sock3.file", &ep3)); + ASSERT_EQ(ep1, ep2); + ASSERT_NE(ep1, ep3); + + ASSERT_EQ(0, butil::str2endpoint("[::1]:2233", &ep1)); + ASSERT_EQ(0, butil::str2endpoint("[::1]:2233", &ep2)); + ASSERT_EQ(0, butil::str2endpoint("[::3]:2233", &ep3)); + ASSERT_EQ(ep1, ep2); + ASSERT_NE(ep1, ep3); +} + +TEST(EndPointTest, endpoint_sockaddr_conv_ipv4) { + butil::EndPoint ep; + ASSERT_EQ(0, butil::str2endpoint("1.2.3.4:8086", &ep)); + + in_addr expected_in_addr; + bzero(&expected_in_addr, sizeof(expected_in_addr)); + expected_in_addr.s_addr = 0x04030201u; + + sockaddr_storage ss; + sockaddr_in* in4 = (sockaddr_in*) &ss; + + memset(&ss, 'a', sizeof(ss)); + ASSERT_EQ(0, butil::endpoint2sockaddr(ep, &ss)); + ASSERT_EQ(AF_INET, ss.ss_family); + ASSERT_EQ(AF_INET, in4->sin_family); + in_port_t port = htons(8086); + ASSERT_EQ(port, in4->sin_port); + ASSERT_EQ(0, memcmp(&in4->sin_addr, &expected_in_addr, sizeof(expected_in_addr))); + + sockaddr_storage ss2; + socklen_t ss2_size = 0; + memset(&ss2, 'b', sizeof(ss2)); + ASSERT_EQ(0, butil::endpoint2sockaddr(ep, &ss2, &ss2_size)); + ASSERT_EQ(ss2_size, sizeof(*in4)); + ASSERT_EQ(0, memcmp(&ss2, &ss, sizeof(ss))); + + butil::EndPoint ep2; + ASSERT_EQ(0, butil::sockaddr2endpoint(&ss, sizeof(*in4), &ep2)); + ASSERT_EQ(ep2, ep); + + ASSERT_EQ(AF_INET, butil::get_endpoint_type(ep)); +} + +TEST(EndPointTest, endpoint_sockaddr_conv_ipv6) { + butil::EndPoint ep; + ASSERT_EQ(0, butil::str2endpoint("[::1]:8086", &ep)); + + in6_addr expect_in6_addr; + bzero(&expect_in6_addr, sizeof(expect_in6_addr)); + expect_in6_addr.s6_addr[15] = 1; + + sockaddr_storage ss; + const sockaddr_in6* sa6 = (sockaddr_in6*) &ss; + + memset(&ss, 'a', sizeof(ss)); + ASSERT_EQ(0, butil::endpoint2sockaddr(ep, &ss)); + ASSERT_EQ(AF_INET6, ss.ss_family); + ASSERT_EQ(AF_INET6, sa6->sin6_family); + in_port_t port = htons(8086); + ASSERT_EQ(port, sa6->sin6_port); + ASSERT_EQ(0u, sa6->sin6_flowinfo); + ASSERT_EQ(0, memcmp(&expect_in6_addr, &sa6->sin6_addr, sizeof(in6_addr))); + ASSERT_EQ(0u, sa6->sin6_scope_id); + + sockaddr_storage ss2; + socklen_t ss2_size = 0; + memset(&ss2, 'b', sizeof(ss2)); + ASSERT_EQ(0, butil::endpoint2sockaddr(ep, &ss2, &ss2_size)); + ASSERT_EQ(ss2_size, sizeof(*sa6)); + ASSERT_EQ(0, memcmp(&ss2, &ss, sizeof(ss))); + + butil::EndPoint ep2; + ASSERT_EQ(0, butil::sockaddr2endpoint(&ss, sizeof(*sa6), &ep2)); + ASSERT_STREQ("[::1]:8086", butil::endpoint2str(ep2).c_str()); + + ASSERT_EQ(AF_INET6, butil::get_endpoint_type(ep)); +} + +TEST(EndPointTest, endpoint_sockaddr_conv_unix) { + butil::EndPoint ep; + ASSERT_EQ(0, butil::str2endpoint("unix:sock.file", &ep)); + + sockaddr_storage ss; + const sockaddr_un* un = (sockaddr_un*) &ss; + + memset(&ss, 'a', sizeof(ss)); + ASSERT_EQ(0, butil::endpoint2sockaddr(ep, &ss)); + ASSERT_EQ(AF_UNIX, ss.ss_family); + ASSERT_EQ(AF_UNIX, un->sun_family); + ASSERT_EQ(0, memcmp("sock.file", un->sun_path, 10)); + + sockaddr_storage ss2; + socklen_t ss2_size = 0; + memset(&ss2, 'b', sizeof(ss2)); + ASSERT_EQ(0, butil::endpoint2sockaddr(ep, &ss2, &ss2_size)); + ASSERT_EQ(offsetof(struct sockaddr_un, sun_path) + strlen("sock.file") + 1, ss2_size); + ASSERT_EQ(0, memcmp(&ss2, &ss, sizeof(ss))); + + butil::EndPoint ep2; + ASSERT_EQ(0, butil::sockaddr2endpoint(&ss, sizeof(sa_family_t) + strlen(un->sun_path) + 1, &ep2)); + ASSERT_STREQ("unix:sock.file", butil::endpoint2str(ep2).c_str()); + + ASSERT_EQ(AF_UNIX, butil::get_endpoint_type(ep)); +} + +void concurrent_proc(void* p) { + for (int i = 0; i < 10000; ++i) { + butil::EndPoint ep; + std::string str("127.0.0.1:8080"); + ASSERT_EQ(0, butil::str2endpoint(str.c_str(), &ep)); + ASSERT_EQ(str, butil::endpoint2str(ep).c_str()); + + str.assign("[::1]:8080"); + ASSERT_EQ(0, butil::str2endpoint(str.c_str(), &ep)); + ASSERT_EQ(str, butil::endpoint2str(ep).c_str()); + + str.assign("unix:test.sock"); + ASSERT_EQ(0, butil::str2endpoint(str.c_str(), &ep)); + ASSERT_EQ(str, butil::endpoint2str(ep).c_str()); + } + *(int*)p = 1; +} + +TEST(EndPointTest, endpoint_concurrency) { + const int T = 5; + pthread_t tids[T]; + int rets[T] = {0}; + for (int i = 0; i < T; ++i) { + pthread_create(&tids[i], nullptr, [](void* p) { + concurrent_proc(p); + return (void*)nullptr; + }, &rets[i]); + } + for (int i = 0; i < T; ++i) { + pthread_join(tids[i], nullptr); + ASSERT_EQ(1, rets[i]); + } +} + +const char* g_hostname1 = "github.com"; +const char* g_hostname2 = "baidu.com"; +TEST(EndPointTest, tcp_connect) { + butil::EndPoint ep1; + butil::EndPoint ep2; + ASSERT_EQ(0, butil::hostname2endpoint(g_hostname1, 80, &ep1)); + ASSERT_EQ(0, butil::hostname2endpoint(g_hostname2, 80, &ep2)); + { + butil::fd_guard sockfd(butil::tcp_connect(ep1, NULL)); + ASSERT_LE(0, sockfd) << "errno=" << errno; + } + { + butil::fd_guard sockfd(butil::tcp_connect(ep1, NULL, 1000)); + ASSERT_LE(0, sockfd) << "errno=" << errno; + } + { + butil::fd_guard sockfd(butil::tcp_connect(ep2, NULL, 1)); + ASSERT_EQ(-1, sockfd) << "errno=" << errno; + ASSERT_EQ(ETIMEDOUT, errno); + } + + { + struct sockaddr_storage serv_addr{}; + socklen_t serv_addr_size = 0; + ASSERT_EQ(0, endpoint2sockaddr(ep1, &serv_addr, &serv_addr_size)); + butil::fd_guard sockfd(socket(serv_addr.ss_family, SOCK_STREAM, 0)); + ASSERT_LE(0, sockfd); + bool is_blocking = butil::is_blocking(sockfd); + ASSERT_EQ(0, butil::pthread_timed_connect( + sockfd, (struct sockaddr*) &serv_addr, serv_addr_size, NULL)); + ASSERT_EQ(is_blocking, butil::is_blocking(sockfd)); + } + + { + struct sockaddr_storage serv_addr{}; + socklen_t serv_addr_size = 0; + ASSERT_EQ(0, endpoint2sockaddr(ep2, &serv_addr, &serv_addr_size)); + butil::fd_guard sockfd(socket(serv_addr.ss_family, SOCK_STREAM, 0)); + ASSERT_LE(0, sockfd); + bool is_blocking = butil::is_blocking(sockfd); + // In most cases, 1 millisecond will result in a connection timeout. + timespec abstime = butil::milliseconds_from_now(1); + const int rc = butil::pthread_timed_connect( + sockfd, (struct sockaddr*) &serv_addr, + serv_addr_size, &abstime); + ASSERT_EQ(-1, rc); + ASSERT_EQ(ETIMEDOUT, errno); + ASSERT_EQ(is_blocking, butil::is_blocking(sockfd)); + } +} + +bool g_connect_startd = false; + +void TestConnectInterruptImpl(bool timed) { + butil::EndPoint ep; + ASSERT_EQ(0, butil::hostname2endpoint(g_hostname1, 80, &ep)); + + struct sockaddr_storage serv_addr{}; + socklen_t serv_addr_size = 0; + ASSERT_EQ(0, endpoint2sockaddr(ep, &serv_addr, &serv_addr_size)); + butil::fd_guard sockfd(socket(serv_addr.ss_family, SOCK_STREAM, 0)); + ASSERT_LE(0, sockfd); + + int rc; + if (timed) { + int64_t start_ms = butil::cpuwide_time_ms(); + butil::tcp_connect(ep, NULL); + int64_t connect_ms = butil::cpuwide_time_ms() - start_ms; + LOG(INFO) << "Connect to " << ep << ", cost " << connect_ms << "ms"; + + timespec abstime = butil::milliseconds_from_now(connect_ms * 10); + rc = butil::pthread_timed_connect( + sockfd, (struct sockaddr*) &serv_addr, + serv_addr_size, &abstime); + } else { + rc = butil::pthread_timed_connect( + sockfd, (struct sockaddr*) &serv_addr, + serv_addr_size, NULL); + } + ASSERT_EQ(0, rc) << "errno=" << errno; + ASSERT_EQ(0, butil::is_connected(sockfd)); +} + +void* ConnectThread(void* arg) { + bool timed = *(bool*)arg; + TestConnectInterruptImpl(timed); + return NULL; +} + +void do_nothing_handler(int) {} + +void register_sigurg() { + signal(SIGURG, do_nothing_handler); +} + +void TestConnectInterrupt(bool timed) { + g_connect_startd = false; + pthread_t tid; + ASSERT_EQ(0, pthread_create(&tid, NULL, ConnectThread, &timed)); + + while (g_connect_startd) { + usleep(1000); + } + + ASSERT_EQ(0, pthread_kill(tid, SIGURG)); + + pthread_join(tid, NULL); +} + +TEST(EndPointTest, interrupt) { + register_sigurg(); + TestConnectInterrupt(false); + TestConnectInterrupt(true); +} + +} // end of namespace diff --git a/test/environment_unittest.cc b/test/environment_unittest.cc new file mode 100644 index 0000000..63a28a3 --- /dev/null +++ b/test/environment_unittest.cc @@ -0,0 +1,164 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/environment.h" +#include "butil/memory/scoped_ptr.h" +#include +#include + +typedef testing::Test EnvironmentTest; + +namespace butil { + +TEST_F(EnvironmentTest, GetVar) { + // Every setup should have non-empty PATH... + scoped_ptr env(Environment::Create()); + std::string env_value; + EXPECT_TRUE(env->GetVar("PATH", &env_value)); + EXPECT_NE(env_value, ""); +} + +TEST_F(EnvironmentTest, GetVarReverse) { + scoped_ptr env(Environment::Create()); + const char* kFooUpper = "FOO"; + const char* kFooLower = "foo"; + + // Set a variable in UPPER case. + EXPECT_TRUE(env->SetVar(kFooUpper, kFooLower)); + + // And then try to get this variable passing the lower case. + std::string env_value; + EXPECT_TRUE(env->GetVar(kFooLower, &env_value)); + + EXPECT_STREQ(env_value.c_str(), kFooLower); + + EXPECT_TRUE(env->UnSetVar(kFooUpper)); + + const char* kBar = "bar"; + // Now do the opposite, set the variable in the lower case. + EXPECT_TRUE(env->SetVar(kFooLower, kBar)); + + // And then try to get this variable passing the UPPER case. + EXPECT_TRUE(env->GetVar(kFooUpper, &env_value)); + + EXPECT_STREQ(env_value.c_str(), kBar); + + EXPECT_TRUE(env->UnSetVar(kFooLower)); +} + +TEST_F(EnvironmentTest, HasVar) { + // Every setup should have PATH... + scoped_ptr env(Environment::Create()); + EXPECT_TRUE(env->HasVar("PATH")); +} + +TEST_F(EnvironmentTest, SetVar) { + scoped_ptr env(Environment::Create()); + + const char* kFooUpper = "FOO"; + const char* kFooLower = "foo"; + EXPECT_TRUE(env->SetVar(kFooUpper, kFooLower)); + + // Now verify that the environment has the new variable. + EXPECT_TRUE(env->HasVar(kFooUpper)); + + std::string var_value; + EXPECT_TRUE(env->GetVar(kFooUpper, &var_value)); + EXPECT_EQ(var_value, kFooLower); +} + +TEST_F(EnvironmentTest, UnSetVar) { + scoped_ptr env(Environment::Create()); + + const char* kFooUpper = "FOO"; + const char* kFooLower = "foo"; + // First set some environment variable. + EXPECT_TRUE(env->SetVar(kFooUpper, kFooLower)); + + // Now verify that the environment has the new variable. + EXPECT_TRUE(env->HasVar(kFooUpper)); + + // Finally verify that the environment variable was erased. + EXPECT_TRUE(env->UnSetVar(kFooUpper)); + + // And check that the variable has been unset. + EXPECT_FALSE(env->HasVar(kFooUpper)); +} + +#if defined(OS_WIN) + +TEST_F(EnvironmentTest, AlterEnvironment) { + const wchar_t empty[] = L"\0"; + const wchar_t a2[] = L"A=2\0"; + EnvironmentMap changes; + string16 e; + + e = AlterEnvironment(empty, changes); + EXPECT_TRUE(e[0] == 0); + + changes[L"A"] = L"1"; + e = AlterEnvironment(empty, changes); + EXPECT_EQ(string16(L"A=1\0\0", 5), e); + + changes.clear(); + changes[L"A"] = string16(); + e = AlterEnvironment(empty, changes); + EXPECT_EQ(string16(L"\0\0", 2), e); + + changes.clear(); + e = AlterEnvironment(a2, changes); + EXPECT_EQ(string16(L"A=2\0\0", 5), e); + + changes.clear(); + changes[L"A"] = L"1"; + e = AlterEnvironment(a2, changes); + EXPECT_EQ(string16(L"A=1\0\0", 5), e); + + changes.clear(); + changes[L"A"] = string16(); + e = AlterEnvironment(a2, changes); + EXPECT_EQ(string16(L"\0\0", 2), e); +} + +#else + +TEST_F(EnvironmentTest, AlterEnvironment) { + const char* const empty[] = { NULL }; + const char* const a2[] = { "A=2", NULL }; + EnvironmentMap changes; + scoped_ptr e; + + e = AlterEnvironment(empty, changes).Pass(); + EXPECT_TRUE(e[0] == NULL); + + changes["A"] = "1"; + e = AlterEnvironment(empty, changes); + EXPECT_EQ(std::string("A=1"), e[0]); + EXPECT_TRUE(e[1] == NULL); + + changes.clear(); + changes["A"] = std::string(); + e = AlterEnvironment(empty, changes); + EXPECT_TRUE(e[0] == NULL); + + changes.clear(); + e = AlterEnvironment(a2, changes); + EXPECT_EQ(std::string("A=2"), e[0]); + EXPECT_TRUE(e[1] == NULL); + + changes.clear(); + changes["A"] = "1"; + e = AlterEnvironment(a2, changes); + EXPECT_EQ(std::string("A=1"), e[0]); + EXPECT_TRUE(e[1] == NULL); + + changes.clear(); + changes["A"] = std::string(); + e = AlterEnvironment(a2, changes); + EXPECT_TRUE(e[0] == NULL); +} + +#endif + +} // namespace butil diff --git a/test/errno_unittest.cpp b/test/errno_unittest.cpp new file mode 100644 index 0000000..6bedc30 --- /dev/null +++ b/test/errno_unittest.cpp @@ -0,0 +1,60 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/errno.h" + +class ErrnoTest : public ::testing::Test{ +protected: + ErrnoTest(){ + }; + virtual ~ErrnoTest(){}; + virtual void SetUp() { + }; + virtual void TearDown() { + }; +}; + +#define ESTOP -114 +#define EBREAK -115 +#define ESTH -116 +#define EOK -117 +#define EMYERROR -30 + +BAIDU_REGISTER_ERRNO(ESTOP, "the thread is stopping") +BAIDU_REGISTER_ERRNO(EBREAK, "the thread is interrupted") +BAIDU_REGISTER_ERRNO(ESTH, "something happened") +BAIDU_REGISTER_ERRNO(EOK, "OK!") +BAIDU_REGISTER_ERRNO(EMYERROR, "my error"); + +TEST_F(ErrnoTest, system_errno) { + errno = EPIPE; + ASSERT_STREQ("Broken pipe", berror()); + ASSERT_STREQ("Interrupted system call", berror(EINTR)); +} + +TEST_F(ErrnoTest, customized_errno) { + ASSERT_STREQ("the thread is stopping", berror(ESTOP)); + ASSERT_STREQ("the thread is interrupted", berror(EBREAK)); + ASSERT_STREQ("something happened", berror(ESTH)); + ASSERT_STREQ("OK!", berror(EOK)); + ASSERT_STREQ("Unknown error 1000", berror(1000)); + + errno = ESTOP; + printf("Something got wrong, %m\n"); + printf("Something got wrong, %s\n", berror()); +} diff --git a/test/fd_guard_unittest.cpp b/test/fd_guard_unittest.cpp new file mode 100644 index 0000000..67f0602 --- /dev/null +++ b/test/fd_guard_unittest.cpp @@ -0,0 +1,77 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 // open +#include // ^ +#include // ^ +#include +#include +#include "butil/fd_guard.h" + +namespace { + +class FDGuardTest : public ::testing::Test{ +protected: + FDGuardTest(){ + }; + virtual ~FDGuardTest(){}; + virtual void SetUp() { + }; + virtual void TearDown() { + }; +}; + +TEST_F(FDGuardTest, default_constructor) { + butil::fd_guard guard; + ASSERT_EQ(-1, guard); +} + +TEST_F(FDGuardTest, destructor_closes_fd) { + int fd = -1; + { + butil::fd_guard guard(open(".tmp1", O_WRONLY|O_CREAT, 0600)); + ASSERT_GT(guard, 0); + fd = guard; + } + char dummy = 0; + ASSERT_EQ(-1L, write(fd, &dummy, 1)); + ASSERT_EQ(EBADF, errno); +} + +TEST_F(FDGuardTest, reset_closes_previous_fd) { + butil::fd_guard guard(open(".tmp1", O_WRONLY|O_CREAT, 0600)); + ASSERT_GT(guard, 0); + const int fd = guard; + const int fd2 = open(".tmp2", O_WRONLY|O_CREAT, 0600); + guard.reset(fd2); + char dummy = 0; + ASSERT_EQ(-1L, write(fd, &dummy, 1)); + ASSERT_EQ(EBADF, errno); + guard.reset(-1); + ASSERT_EQ(-1L, write(fd2, &dummy, 1)); + ASSERT_EQ(EBADF, errno); +} + +TEST_F(FDGuardTest, release) { + butil::fd_guard guard(open(".tmp1", O_WRONLY|O_CREAT, 0600)); + ASSERT_GT(guard, 0); + const int fd = guard; + ASSERT_EQ(fd, guard.release()); + ASSERT_EQ(-1, guard); + close(fd); +} +} diff --git a/test/file_descriptor_shuffle_unittest.cc b/test/file_descriptor_shuffle_unittest.cc new file mode 100644 index 0000000..6bc444b --- /dev/null +++ b/test/file_descriptor_shuffle_unittest.cc @@ -0,0 +1,287 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/posix/file_descriptor_shuffle.h" +#include + +namespace { + +// 'Duplicated' file descriptors start at this number +const int kDuplicateBase = 1000; + +} // namespace + +namespace butil { + +struct Action { + enum Type { + CLOSE, + MOVE, + DUPLICATE, + }; + + Action(Type in_type, int in_fd1, int in_fd2 = -1) + : type(in_type), + fd1(in_fd1), + fd2(in_fd2) { + } + + bool operator==(const Action& other) const { + return other.type == type && + other.fd1 == fd1 && + other.fd2 == fd2; + } + + Type type; + int fd1; + int fd2; +}; + +class InjectionTracer : public InjectionDelegate { + public: + InjectionTracer() + : next_duplicate_(kDuplicateBase) { + } + + virtual bool Duplicate(int* result, int fd) OVERRIDE { + *result = next_duplicate_++; + actions_.push_back(Action(Action::DUPLICATE, *result, fd)); + return true; + } + + virtual bool Move(int src, int dest) OVERRIDE { + actions_.push_back(Action(Action::MOVE, src, dest)); + return true; + } + + virtual void Close(int fd) OVERRIDE { + actions_.push_back(Action(Action::CLOSE, fd)); + } + + const std::vector& actions() const { return actions_; } + + private: + int next_duplicate_; + std::vector actions_; +}; + +TEST(FileDescriptorShuffleTest, Empty) { + InjectiveMultimap map; + InjectionTracer tracer; + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + EXPECT_EQ(0u, tracer.actions().size()); +} + +TEST(FileDescriptorShuffleTest, Noop) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 0, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + EXPECT_EQ(0u, tracer.actions().size()); +} + +TEST(FileDescriptorShuffleTest, NoopAndClose) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 0, true)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + EXPECT_EQ(0u, tracer.actions().size()); +} + +TEST(FileDescriptorShuffleTest, Simple1) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(1u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1)); +} + +TEST(FileDescriptorShuffleTest, Simple2) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, false)); + map.push_back(InjectionArc(2, 3, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(2u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 2, 3)); +} + +TEST(FileDescriptorShuffleTest, Simple3) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, true)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(2u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::CLOSE, 0)); +} + +TEST(FileDescriptorShuffleTest, Simple4) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(10, 0, true)); + map.push_back(InjectionArc(1, 1, true)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(2u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 10, 0)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::CLOSE, 10)); +} + +TEST(FileDescriptorShuffleTest, Cycle) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, false)); + map.push_back(InjectionArc(1, 0, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(4u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == + Action(Action::DUPLICATE, kDuplicateBase, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[2] == Action(Action::MOVE, kDuplicateBase, 0)); + EXPECT_TRUE(tracer.actions()[3] == Action(Action::CLOSE, kDuplicateBase)); +} + +TEST(FileDescriptorShuffleTest, CycleAndClose1) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, true)); + map.push_back(InjectionArc(1, 0, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(4u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == + Action(Action::DUPLICATE, kDuplicateBase, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[2] == Action(Action::MOVE, kDuplicateBase, 0)); + EXPECT_TRUE(tracer.actions()[3] == Action(Action::CLOSE, kDuplicateBase)); +} + +TEST(FileDescriptorShuffleTest, CycleAndClose2) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, false)); + map.push_back(InjectionArc(1, 0, true)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(4u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == + Action(Action::DUPLICATE, kDuplicateBase, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[2] == Action(Action::MOVE, kDuplicateBase, 0)); + EXPECT_TRUE(tracer.actions()[3] == Action(Action::CLOSE, kDuplicateBase)); +} + +TEST(FileDescriptorShuffleTest, CycleAndClose3) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, true)); + map.push_back(InjectionArc(1, 0, true)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(4u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == + Action(Action::DUPLICATE, kDuplicateBase, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[2] == Action(Action::MOVE, kDuplicateBase, 0)); + EXPECT_TRUE(tracer.actions()[3] == Action(Action::CLOSE, kDuplicateBase)); +} + +TEST(FileDescriptorShuffleTest, Fanout) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, false)); + map.push_back(InjectionArc(0, 2, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(2u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 2)); +} + +TEST(FileDescriptorShuffleTest, FanoutAndClose1) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, true)); + map.push_back(InjectionArc(0, 2, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(3u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 2)); + EXPECT_TRUE(tracer.actions()[2] == Action(Action::CLOSE, 0)); +} + +TEST(FileDescriptorShuffleTest, FanoutAndClose2) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, false)); + map.push_back(InjectionArc(0, 2, true)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(3u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 2)); + EXPECT_TRUE(tracer.actions()[2] == Action(Action::CLOSE, 0)); +} + +TEST(FileDescriptorShuffleTest, FanoutAndClose3) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, true)); + map.push_back(InjectionArc(0, 2, true)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(3u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 2)); + EXPECT_TRUE(tracer.actions()[2] == Action(Action::CLOSE, 0)); +} + +class FailingDelegate : public InjectionDelegate { + public: + virtual bool Duplicate(int* result, int fd) OVERRIDE { + return false; + } + + virtual bool Move(int src, int dest) OVERRIDE { + return false; + } + + virtual void Close(int fd) OVERRIDE {} +}; + +TEST(FileDescriptorShuffleTest, EmptyWithFailure) { + InjectiveMultimap map; + FailingDelegate failing; + + EXPECT_TRUE(PerformInjectiveMultimap(map, &failing)); +} + +TEST(FileDescriptorShuffleTest, NoopWithFailure) { + InjectiveMultimap map; + FailingDelegate failing; + map.push_back(InjectionArc(0, 0, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &failing)); +} + +TEST(FileDescriptorShuffleTest, Simple1WithFailure) { + InjectiveMultimap map; + FailingDelegate failing; + map.push_back(InjectionArc(0, 1, false)); + + EXPECT_FALSE(PerformInjectiveMultimap(map, &failing)); +} + +} // namespace butil diff --git a/test/file_path_unittest.cc b/test/file_path_unittest.cc new file mode 100644 index 0000000..3edc4f0 --- /dev/null +++ b/test/file_path_unittest.cc @@ -0,0 +1,1286 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/basictypes.h" +#include "butil/files/file_path.h" +#include "butil/strings/utf_string_conversions.h" +#include +#include + +// This macro helps avoid wrapped lines in the test structs. +#define FPL(x) FILE_PATH_LITERAL(x) + +// This macro constructs strings which can contain NULs. +#define FPS(x) FilePath::StringType(FPL(x), arraysize(FPL(x)) - 1) + +namespace butil { + +struct UnaryTestData { + const FilePath::CharType* input; + const FilePath::CharType* expected; +}; + +struct UnaryBooleanTestData { + const FilePath::CharType* input; + bool expected; +}; + +struct BinaryTestData { + const FilePath::CharType* inputs[2]; + const FilePath::CharType* expected; +}; + +struct BinaryBooleanTestData { + const FilePath::CharType* inputs[2]; + bool expected; +}; + +struct BinaryIntTestData { + const FilePath::CharType* inputs[2]; + int expected; +}; + +struct UTF8TestData { + const FilePath::CharType* native; + const char* utf8; +}; + +// file_util winds up using autoreleased objects on the Mac, so this needs +// to be a testing::Test +class FilePathTest : public testing::Test { + protected: + virtual void SetUp() OVERRIDE { + testing::Test::SetUp(); + } + virtual void TearDown() OVERRIDE { + testing::Test::TearDown(); + } +}; + +TEST_F(FilePathTest, DirName) { + const struct UnaryTestData cases[] = { + { FPL(""), FPL(".") }, + { FPL("aa"), FPL(".") }, + { FPL("/aa/bb"), FPL("/aa") }, + { FPL("/aa/bb/"), FPL("/aa") }, + { FPL("/aa/bb//"), FPL("/aa") }, + { FPL("/aa/bb/ccc"), FPL("/aa/bb") }, + { FPL("/aa"), FPL("/") }, + { FPL("/aa/"), FPL("/") }, + { FPL("/"), FPL("/") }, + { FPL("//"), FPL("//") }, + { FPL("///"), FPL("/") }, + { FPL("aa/"), FPL(".") }, + { FPL("aa/bb"), FPL("aa") }, + { FPL("aa/bb/"), FPL("aa") }, + { FPL("aa/bb//"), FPL("aa") }, + { FPL("aa//bb//"), FPL("aa") }, + { FPL("aa//bb/"), FPL("aa") }, + { FPL("aa//bb"), FPL("aa") }, + { FPL("//aa/bb"), FPL("//aa") }, + { FPL("//aa/"), FPL("//") }, + { FPL("//aa"), FPL("//") }, + { FPL("0:"), FPL(".") }, + { FPL("@:"), FPL(".") }, + { FPL("[:"), FPL(".") }, + { FPL("`:"), FPL(".") }, + { FPL("{:"), FPL(".") }, + { FPL("\xB3:"), FPL(".") }, + { FPL("\xC5:"), FPL(".") }, +#if defined(OS_WIN) + { FPL("\x0143:"), FPL(".") }, +#endif // OS_WIN +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { FPL("c:"), FPL("c:") }, + { FPL("C:"), FPL("C:") }, + { FPL("A:"), FPL("A:") }, + { FPL("Z:"), FPL("Z:") }, + { FPL("a:"), FPL("a:") }, + { FPL("z:"), FPL("z:") }, + { FPL("c:aa"), FPL("c:") }, + { FPL("c:/"), FPL("c:/") }, + { FPL("c://"), FPL("c://") }, + { FPL("c:///"), FPL("c:/") }, + { FPL("c:/aa"), FPL("c:/") }, + { FPL("c:/aa/"), FPL("c:/") }, + { FPL("c:/aa/bb"), FPL("c:/aa") }, + { FPL("c:aa/bb"), FPL("c:aa") }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { FPL("\\aa\\bb"), FPL("\\aa") }, + { FPL("\\aa\\bb\\"), FPL("\\aa") }, + { FPL("\\aa\\bb\\\\"), FPL("\\aa") }, + { FPL("\\aa\\bb\\ccc"), FPL("\\aa\\bb") }, + { FPL("\\aa"), FPL("\\") }, + { FPL("\\aa\\"), FPL("\\") }, + { FPL("\\"), FPL("\\") }, + { FPL("\\\\"), FPL("\\\\") }, + { FPL("\\\\\\"), FPL("\\") }, + { FPL("aa\\"), FPL(".") }, + { FPL("aa\\bb"), FPL("aa") }, + { FPL("aa\\bb\\"), FPL("aa") }, + { FPL("aa\\bb\\\\"), FPL("aa") }, + { FPL("aa\\\\bb\\\\"), FPL("aa") }, + { FPL("aa\\\\bb\\"), FPL("aa") }, + { FPL("aa\\\\bb"), FPL("aa") }, + { FPL("\\\\aa\\bb"), FPL("\\\\aa") }, + { FPL("\\\\aa\\"), FPL("\\\\") }, + { FPL("\\\\aa"), FPL("\\\\") }, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { FPL("c:\\"), FPL("c:\\") }, + { FPL("c:\\\\"), FPL("c:\\\\") }, + { FPL("c:\\\\\\"), FPL("c:\\") }, + { FPL("c:\\aa"), FPL("c:\\") }, + { FPL("c:\\aa\\"), FPL("c:\\") }, + { FPL("c:\\aa\\bb"), FPL("c:\\aa") }, + { FPL("c:aa\\bb"), FPL("c:aa") }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#endif // FILE_PATH_USES_WIN_SEPARATORS + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath input(cases[i].input); + FilePath observed = input.DirName(); + EXPECT_EQ(FilePath::StringType(cases[i].expected), observed.value()) << + "i: " << i << ", input: " << input.value(); + } +} + +TEST_F(FilePathTest, BaseName) { + const struct UnaryTestData cases[] = { + { FPL(""), FPL("") }, + { FPL("aa"), FPL("aa") }, + { FPL("/aa/bb"), FPL("bb") }, + { FPL("/aa/bb/"), FPL("bb") }, + { FPL("/aa/bb//"), FPL("bb") }, + { FPL("/aa/bb/ccc"), FPL("ccc") }, + { FPL("/aa"), FPL("aa") }, + { FPL("/"), FPL("/") }, + { FPL("//"), FPL("//") }, + { FPL("///"), FPL("/") }, + { FPL("aa/"), FPL("aa") }, + { FPL("aa/bb"), FPL("bb") }, + { FPL("aa/bb/"), FPL("bb") }, + { FPL("aa/bb//"), FPL("bb") }, + { FPL("aa//bb//"), FPL("bb") }, + { FPL("aa//bb/"), FPL("bb") }, + { FPL("aa//bb"), FPL("bb") }, + { FPL("//aa/bb"), FPL("bb") }, + { FPL("//aa/"), FPL("aa") }, + { FPL("//aa"), FPL("aa") }, + { FPL("0:"), FPL("0:") }, + { FPL("@:"), FPL("@:") }, + { FPL("[:"), FPL("[:") }, + { FPL("`:"), FPL("`:") }, + { FPL("{:"), FPL("{:") }, + { FPL("\xB3:"), FPL("\xB3:") }, + { FPL("\xC5:"), FPL("\xC5:") }, +#if defined(OS_WIN) + { FPL("\x0143:"), FPL("\x0143:") }, +#endif // OS_WIN +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { FPL("c:"), FPL("") }, + { FPL("C:"), FPL("") }, + { FPL("A:"), FPL("") }, + { FPL("Z:"), FPL("") }, + { FPL("a:"), FPL("") }, + { FPL("z:"), FPL("") }, + { FPL("c:aa"), FPL("aa") }, + { FPL("c:/"), FPL("/") }, + { FPL("c://"), FPL("//") }, + { FPL("c:///"), FPL("/") }, + { FPL("c:/aa"), FPL("aa") }, + { FPL("c:/aa/"), FPL("aa") }, + { FPL("c:/aa/bb"), FPL("bb") }, + { FPL("c:aa/bb"), FPL("bb") }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { FPL("\\aa\\bb"), FPL("bb") }, + { FPL("\\aa\\bb\\"), FPL("bb") }, + { FPL("\\aa\\bb\\\\"), FPL("bb") }, + { FPL("\\aa\\bb\\ccc"), FPL("ccc") }, + { FPL("\\aa"), FPL("aa") }, + { FPL("\\"), FPL("\\") }, + { FPL("\\\\"), FPL("\\\\") }, + { FPL("\\\\\\"), FPL("\\") }, + { FPL("aa\\"), FPL("aa") }, + { FPL("aa\\bb"), FPL("bb") }, + { FPL("aa\\bb\\"), FPL("bb") }, + { FPL("aa\\bb\\\\"), FPL("bb") }, + { FPL("aa\\\\bb\\\\"), FPL("bb") }, + { FPL("aa\\\\bb\\"), FPL("bb") }, + { FPL("aa\\\\bb"), FPL("bb") }, + { FPL("\\\\aa\\bb"), FPL("bb") }, + { FPL("\\\\aa\\"), FPL("aa") }, + { FPL("\\\\aa"), FPL("aa") }, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { FPL("c:\\"), FPL("\\") }, + { FPL("c:\\\\"), FPL("\\\\") }, + { FPL("c:\\\\\\"), FPL("\\") }, + { FPL("c:\\aa"), FPL("aa") }, + { FPL("c:\\aa\\"), FPL("aa") }, + { FPL("c:\\aa\\bb"), FPL("bb") }, + { FPL("c:aa\\bb"), FPL("bb") }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#endif // FILE_PATH_USES_WIN_SEPARATORS + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath input(cases[i].input); + FilePath observed = input.BaseName(); + EXPECT_EQ(FilePath::StringType(cases[i].expected), observed.value()) << + "i: " << i << ", input: " << input.value(); + } +} + +TEST_F(FilePathTest, Append) { + const struct BinaryTestData cases[] = { + { { FPL(""), FPL("cc") }, FPL("cc") }, + { { FPL("."), FPL("ff") }, FPL("ff") }, + { { FPL("/"), FPL("cc") }, FPL("/cc") }, + { { FPL("/aa"), FPL("") }, FPL("/aa") }, + { { FPL("/aa/"), FPL("") }, FPL("/aa") }, + { { FPL("//aa"), FPL("") }, FPL("//aa") }, + { { FPL("//aa/"), FPL("") }, FPL("//aa") }, + { { FPL("//"), FPL("aa") }, FPL("//aa") }, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { { FPL("c:"), FPL("a") }, FPL("c:a") }, + { { FPL("c:"), FPL("") }, FPL("c:") }, + { { FPL("c:/"), FPL("a") }, FPL("c:/a") }, + { { FPL("c://"), FPL("a") }, FPL("c://a") }, + { { FPL("c:///"), FPL("a") }, FPL("c:/a") }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + // Append introduces the default separator character, so these test cases + // need to be defined with different expected results on platforms that use + // different default separator characters. + { { FPL("\\"), FPL("cc") }, FPL("\\cc") }, + { { FPL("\\aa"), FPL("") }, FPL("\\aa") }, + { { FPL("\\aa\\"), FPL("") }, FPL("\\aa") }, + { { FPL("\\\\aa"), FPL("") }, FPL("\\\\aa") }, + { { FPL("\\\\aa\\"), FPL("") }, FPL("\\\\aa") }, + { { FPL("\\\\"), FPL("aa") }, FPL("\\\\aa") }, + { { FPL("/aa/bb"), FPL("cc") }, FPL("/aa/bb\\cc") }, + { { FPL("/aa/bb/"), FPL("cc") }, FPL("/aa/bb\\cc") }, + { { FPL("aa/bb/"), FPL("cc") }, FPL("aa/bb\\cc") }, + { { FPL("aa/bb"), FPL("cc") }, FPL("aa/bb\\cc") }, + { { FPL("a/b"), FPL("c") }, FPL("a/b\\c") }, + { { FPL("a/b/"), FPL("c") }, FPL("a/b\\c") }, + { { FPL("//aa"), FPL("bb") }, FPL("//aa\\bb") }, + { { FPL("//aa/"), FPL("bb") }, FPL("//aa\\bb") }, + { { FPL("\\aa\\bb"), FPL("cc") }, FPL("\\aa\\bb\\cc") }, + { { FPL("\\aa\\bb\\"), FPL("cc") }, FPL("\\aa\\bb\\cc") }, + { { FPL("aa\\bb\\"), FPL("cc") }, FPL("aa\\bb\\cc") }, + { { FPL("aa\\bb"), FPL("cc") }, FPL("aa\\bb\\cc") }, + { { FPL("a\\b"), FPL("c") }, FPL("a\\b\\c") }, + { { FPL("a\\b\\"), FPL("c") }, FPL("a\\b\\c") }, + { { FPL("\\\\aa"), FPL("bb") }, FPL("\\\\aa\\bb") }, + { { FPL("\\\\aa\\"), FPL("bb") }, FPL("\\\\aa\\bb") }, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { { FPL("c:\\"), FPL("a") }, FPL("c:\\a") }, + { { FPL("c:\\\\"), FPL("a") }, FPL("c:\\\\a") }, + { { FPL("c:\\\\\\"), FPL("a") }, FPL("c:\\a") }, + { { FPL("c:\\"), FPL("") }, FPL("c:\\") }, + { { FPL("c:\\a"), FPL("b") }, FPL("c:\\a\\b") }, + { { FPL("c:\\a\\"), FPL("b") }, FPL("c:\\a\\b") }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#else // FILE_PATH_USES_WIN_SEPARATORS + { { FPL("/aa/bb"), FPL("cc") }, FPL("/aa/bb/cc") }, + { { FPL("/aa/bb/"), FPL("cc") }, FPL("/aa/bb/cc") }, + { { FPL("aa/bb/"), FPL("cc") }, FPL("aa/bb/cc") }, + { { FPL("aa/bb"), FPL("cc") }, FPL("aa/bb/cc") }, + { { FPL("a/b"), FPL("c") }, FPL("a/b/c") }, + { { FPL("a/b/"), FPL("c") }, FPL("a/b/c") }, + { { FPL("//aa"), FPL("bb") }, FPL("//aa/bb") }, + { { FPL("//aa/"), FPL("bb") }, FPL("//aa/bb") }, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { { FPL("c:/"), FPL("a") }, FPL("c:/a") }, + { { FPL("c:/"), FPL("") }, FPL("c:/") }, + { { FPL("c:/a"), FPL("b") }, FPL("c:/a/b") }, + { { FPL("c:/a/"), FPL("b") }, FPL("c:/a/b") }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#endif // FILE_PATH_USES_WIN_SEPARATORS + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath root(cases[i].inputs[0]); + FilePath::StringType leaf(cases[i].inputs[1]); + FilePath observed_str = root.Append(leaf); + EXPECT_EQ(FilePath::StringType(cases[i].expected), observed_str.value()) << + "i: " << i << ", root: " << root.value() << ", leaf: " << leaf; + FilePath observed_path = root.Append(FilePath(leaf)); + EXPECT_EQ(FilePath::StringType(cases[i].expected), observed_path.value()) << + "i: " << i << ", root: " << root.value() << ", leaf: " << leaf; + + // TODO(erikkay): It would be nice to have a unicode test append value to + // handle the case when AppendASCII is passed UTF8 +#if defined(OS_WIN) + std::string ascii = WideToUTF8(leaf); +#elif defined(OS_POSIX) + std::string ascii = leaf; +#endif + observed_str = root.AppendASCII(ascii); + EXPECT_EQ(FilePath::StringType(cases[i].expected), observed_str.value()) << + "i: " << i << ", root: " << root.value() << ", leaf: " << leaf; + } +} + +TEST_F(FilePathTest, StripTrailingSeparators) { + const struct UnaryTestData cases[] = { + { FPL(""), FPL("") }, + { FPL("/"), FPL("/") }, + { FPL("//"), FPL("//") }, + { FPL("///"), FPL("/") }, + { FPL("////"), FPL("/") }, + { FPL("a/"), FPL("a") }, + { FPL("a//"), FPL("a") }, + { FPL("a///"), FPL("a") }, + { FPL("a////"), FPL("a") }, + { FPL("/a"), FPL("/a") }, + { FPL("/a/"), FPL("/a") }, + { FPL("/a//"), FPL("/a") }, + { FPL("/a///"), FPL("/a") }, + { FPL("/a////"), FPL("/a") }, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { FPL("c:"), FPL("c:") }, + { FPL("c:/"), FPL("c:/") }, + { FPL("c://"), FPL("c://") }, + { FPL("c:///"), FPL("c:/") }, + { FPL("c:////"), FPL("c:/") }, + { FPL("c:/a"), FPL("c:/a") }, + { FPL("c:/a/"), FPL("c:/a") }, + { FPL("c:/a//"), FPL("c:/a") }, + { FPL("c:/a///"), FPL("c:/a") }, + { FPL("c:/a////"), FPL("c:/a") }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { FPL("\\"), FPL("\\") }, + { FPL("\\\\"), FPL("\\\\") }, + { FPL("\\\\\\"), FPL("\\") }, + { FPL("\\\\\\\\"), FPL("\\") }, + { FPL("a\\"), FPL("a") }, + { FPL("a\\\\"), FPL("a") }, + { FPL("a\\\\\\"), FPL("a") }, + { FPL("a\\\\\\\\"), FPL("a") }, + { FPL("\\a"), FPL("\\a") }, + { FPL("\\a\\"), FPL("\\a") }, + { FPL("\\a\\\\"), FPL("\\a") }, + { FPL("\\a\\\\\\"), FPL("\\a") }, + { FPL("\\a\\\\\\\\"), FPL("\\a") }, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { FPL("c:\\"), FPL("c:\\") }, + { FPL("c:\\\\"), FPL("c:\\\\") }, + { FPL("c:\\\\\\"), FPL("c:\\") }, + { FPL("c:\\\\\\\\"), FPL("c:\\") }, + { FPL("c:\\a"), FPL("c:\\a") }, + { FPL("c:\\a\\"), FPL("c:\\a") }, + { FPL("c:\\a\\\\"), FPL("c:\\a") }, + { FPL("c:\\a\\\\\\"), FPL("c:\\a") }, + { FPL("c:\\a\\\\\\\\"), FPL("c:\\a") }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#endif // FILE_PATH_USES_WIN_SEPARATORS + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath input(cases[i].input); + FilePath observed = input.StripTrailingSeparators(); + EXPECT_EQ(FilePath::StringType(cases[i].expected), observed.value()) << + "i: " << i << ", input: " << input.value(); + } +} + +TEST_F(FilePathTest, IsAbsolute) { + const struct UnaryBooleanTestData cases[] = { + { FPL(""), false }, + { FPL("a"), false }, + { FPL("c:"), false }, + { FPL("c:a"), false }, + { FPL("a/b"), false }, + { FPL("//"), true }, + { FPL("//a"), true }, + { FPL("c:a/b"), false }, + { FPL("?:/a"), false }, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { FPL("/"), false }, + { FPL("/a"), false }, + { FPL("/."), false }, + { FPL("/.."), false }, + { FPL("c:/"), true }, + { FPL("c:/a"), true }, + { FPL("c:/."), true }, + { FPL("c:/.."), true }, + { FPL("C:/a"), true }, + { FPL("d:/a"), true }, +#else // FILE_PATH_USES_DRIVE_LETTERS + { FPL("/"), true }, + { FPL("/a"), true }, + { FPL("/."), true }, + { FPL("/.."), true }, + { FPL("c:/"), false }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { FPL("a\\b"), false }, + { FPL("\\\\"), true }, + { FPL("\\\\a"), true }, + { FPL("a\\b"), false }, + { FPL("\\\\"), true }, + { FPL("//a"), true }, + { FPL("c:a\\b"), false }, + { FPL("?:\\a"), false }, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { FPL("\\"), false }, + { FPL("\\a"), false }, + { FPL("\\."), false }, + { FPL("\\.."), false }, + { FPL("c:\\"), true }, + { FPL("c:\\"), true }, + { FPL("c:\\a"), true }, + { FPL("c:\\."), true }, + { FPL("c:\\.."), true }, + { FPL("C:\\a"), true }, + { FPL("d:\\a"), true }, +#else // FILE_PATH_USES_DRIVE_LETTERS + { FPL("\\"), true }, + { FPL("\\a"), true }, + { FPL("\\."), true }, + { FPL("\\.."), true }, + { FPL("c:\\"), false }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#endif // FILE_PATH_USES_WIN_SEPARATORS + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath input(cases[i].input); + bool observed = input.IsAbsolute(); + EXPECT_EQ(cases[i].expected, observed) << + "i: " << i << ", input: " << input.value(); + } +} + +TEST_F(FilePathTest, PathComponentsTest) { + const struct UnaryTestData cases[] = { + { FPL("//foo/bar/baz/"), FPL("|//|foo|bar|baz")}, + { FPL("///"), FPL("|/")}, + { FPL("/foo//bar//baz/"), FPL("|/|foo|bar|baz")}, + { FPL("/foo/bar/baz/"), FPL("|/|foo|bar|baz")}, + { FPL("/foo/bar/baz//"), FPL("|/|foo|bar|baz")}, + { FPL("/foo/bar/baz///"), FPL("|/|foo|bar|baz")}, + { FPL("/foo/bar/baz"), FPL("|/|foo|bar|baz")}, + { FPL("/foo/bar.bot/baz.txt"), FPL("|/|foo|bar.bot|baz.txt")}, + { FPL("//foo//bar/baz"), FPL("|//|foo|bar|baz")}, + { FPL("/"), FPL("|/")}, + { FPL("foo"), FPL("|foo")}, + { FPL(""), FPL("")}, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { FPL("e:/foo"), FPL("|e:|/|foo")}, + { FPL("e:/"), FPL("|e:|/")}, + { FPL("e:"), FPL("|e:")}, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { FPL("../foo"), FPL("|..|foo")}, + { FPL("./foo"), FPL("|foo")}, + { FPL("../foo/bar/"), FPL("|..|foo|bar") }, + { FPL("\\\\foo\\bar\\baz\\"), FPL("|\\\\|foo|bar|baz")}, + { FPL("\\\\\\"), FPL("|\\")}, + { FPL("\\foo\\\\bar\\\\baz\\"), FPL("|\\|foo|bar|baz")}, + { FPL("\\foo\\bar\\baz\\"), FPL("|\\|foo|bar|baz")}, + { FPL("\\foo\\bar\\baz\\\\"), FPL("|\\|foo|bar|baz")}, + { FPL("\\foo\\bar\\baz\\\\\\"), FPL("|\\|foo|bar|baz")}, + { FPL("\\foo\\bar\\baz"), FPL("|\\|foo|bar|baz")}, + { FPL("\\foo\\bar/baz\\\\\\"), FPL("|\\|foo|bar|baz")}, + { FPL("/foo\\bar\\baz"), FPL("|/|foo|bar|baz")}, + { FPL("\\foo\\bar.bot\\baz.txt"), FPL("|\\|foo|bar.bot|baz.txt")}, + { FPL("\\\\foo\\\\bar\\baz"), FPL("|\\\\|foo|bar|baz")}, + { FPL("\\"), FPL("|\\")}, +#endif // FILE_PATH_USES_WIN_SEPARATORS + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath input(cases[i].input); + std::vector comps; + input.GetComponents(&comps); + + FilePath::StringType observed; + for (size_t j = 0; j < comps.size(); ++j) { + observed.append(FILE_PATH_LITERAL("|"), 1); + observed.append(comps[j]); + } + EXPECT_EQ(FilePath::StringType(cases[i].expected), observed) << + "i: " << i << ", input: " << input.value(); + } +} + +TEST_F(FilePathTest, IsParentTest) { + const struct BinaryBooleanTestData cases[] = { + { { FPL("/"), FPL("/foo/bar/baz") }, true}, + { { FPL("/foo/bar"), FPL("/foo/bar/baz") }, true}, + { { FPL("/foo/bar/"), FPL("/foo/bar/baz") }, true}, + { { FPL("//foo/bar/"), FPL("//foo/bar/baz") }, true}, + { { FPL("/foo/bar"), FPL("/foo2/bar/baz") }, false}, + { { FPL("/foo/bar.txt"), FPL("/foo/bar/baz") }, false}, + { { FPL("/foo/bar"), FPL("/foo/bar2/baz") }, false}, + { { FPL("/foo/bar"), FPL("/foo/bar") }, false}, + { { FPL("/foo/bar/baz"), FPL("/foo/bar") }, false}, + { { FPL("foo/bar"), FPL("foo/bar/baz") }, true}, + { { FPL("foo/bar"), FPL("foo2/bar/baz") }, false}, + { { FPL("foo/bar"), FPL("foo/bar2/baz") }, false}, + { { FPL(""), FPL("foo") }, false}, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { { FPL("c:/foo/bar"), FPL("c:/foo/bar/baz") }, true}, + { { FPL("E:/foo/bar"), FPL("e:/foo/bar/baz") }, true}, + { { FPL("f:/foo/bar"), FPL("F:/foo/bar/baz") }, true}, + { { FPL("E:/Foo/bar"), FPL("e:/foo/bar/baz") }, false}, + { { FPL("f:/foo/bar"), FPL("F:/foo/Bar/baz") }, false}, + { { FPL("c:/"), FPL("c:/foo/bar/baz") }, true}, + { { FPL("c:"), FPL("c:/foo/bar/baz") }, true}, + { { FPL("c:/foo/bar"), FPL("d:/foo/bar/baz") }, false}, + { { FPL("c:/foo/bar"), FPL("D:/foo/bar/baz") }, false}, + { { FPL("C:/foo/bar"), FPL("d:/foo/bar/baz") }, false}, + { { FPL("c:/foo/bar"), FPL("c:/foo2/bar/baz") }, false}, + { { FPL("e:/foo/bar"), FPL("E:/foo2/bar/baz") }, false}, + { { FPL("F:/foo/bar"), FPL("f:/foo2/bar/baz") }, false}, + { { FPL("c:/foo/bar"), FPL("c:/foo/bar2/baz") }, false}, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { { FPL("\\foo\\bar"), FPL("\\foo\\bar\\baz") }, true}, + { { FPL("\\foo/bar"), FPL("\\foo\\bar\\baz") }, true}, + { { FPL("\\foo/bar"), FPL("\\foo/bar/baz") }, true}, + { { FPL("\\"), FPL("\\foo\\bar\\baz") }, true}, + { { FPL(""), FPL("\\foo\\bar\\baz") }, false}, + { { FPL("\\foo\\bar"), FPL("\\foo2\\bar\\baz") }, false}, + { { FPL("\\foo\\bar"), FPL("\\foo\\bar2\\baz") }, false}, +#endif // FILE_PATH_USES_WIN_SEPARATORS + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath parent(cases[i].inputs[0]); + FilePath child(cases[i].inputs[1]); + + EXPECT_EQ(parent.IsParent(child), cases[i].expected) << + "i: " << i << ", parent: " << parent.value() << ", child: " << + child.value(); + } +} + +TEST_F(FilePathTest, AppendRelativePathTest) { + const struct BinaryTestData cases[] = { +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { { FPL("/"), FPL("/foo/bar/baz") }, FPL("foo\\bar\\baz")}, +#else // FILE_PATH_USES_WIN_SEPARATORS + { { FPL("/"), FPL("/foo/bar/baz") }, FPL("foo/bar/baz")}, +#endif // FILE_PATH_USES_WIN_SEPARATORS + { { FPL("/foo/bar"), FPL("/foo/bar/baz") }, FPL("baz")}, + { { FPL("/foo/bar/"), FPL("/foo/bar/baz") }, FPL("baz")}, + { { FPL("//foo/bar/"), FPL("//foo/bar/baz") }, FPL("baz")}, + { { FPL("/foo/bar"), FPL("/foo2/bar/baz") }, FPL("")}, + { { FPL("/foo/bar.txt"), FPL("/foo/bar/baz") }, FPL("")}, + { { FPL("/foo/bar"), FPL("/foo/bar2/baz") }, FPL("")}, + { { FPL("/foo/bar"), FPL("/foo/bar") }, FPL("")}, + { { FPL("/foo/bar/baz"), FPL("/foo/bar") }, FPL("")}, + { { FPL("foo/bar"), FPL("foo/bar/baz") }, FPL("baz")}, + { { FPL("foo/bar"), FPL("foo2/bar/baz") }, FPL("")}, + { { FPL("foo/bar"), FPL("foo/bar2/baz") }, FPL("")}, + { { FPL(""), FPL("foo") }, FPL("")}, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { { FPL("c:/foo/bar"), FPL("c:/foo/bar/baz") }, FPL("baz")}, + { { FPL("E:/foo/bar"), FPL("e:/foo/bar/baz") }, FPL("baz")}, + { { FPL("f:/foo/bar"), FPL("F:/foo/bar/baz") }, FPL("baz")}, + { { FPL("E:/Foo/bar"), FPL("e:/foo/bar/baz") }, FPL("")}, + { { FPL("f:/foo/bar"), FPL("F:/foo/Bar/baz") }, FPL("")}, +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { { FPL("c:/"), FPL("c:/foo/bar/baz") }, FPL("foo\\bar\\baz")}, + // TODO(akalin): Figure out how to handle the corner case in the + // commented-out test case below. Appending to an empty path gives + // /foo\bar\baz but appending to a nonempty path "blah" gives + // blah\foo\bar\baz. + // { { FPL("c:"), FPL("c:/foo/bar/baz") }, FPL("foo\\bar\\baz")}, +#endif // FILE_PATH_USES_WIN_SEPARATORS + { { FPL("c:/foo/bar"), FPL("d:/foo/bar/baz") }, FPL("")}, + { { FPL("c:/foo/bar"), FPL("D:/foo/bar/baz") }, FPL("")}, + { { FPL("C:/foo/bar"), FPL("d:/foo/bar/baz") }, FPL("")}, + { { FPL("c:/foo/bar"), FPL("c:/foo2/bar/baz") }, FPL("")}, + { { FPL("e:/foo/bar"), FPL("E:/foo2/bar/baz") }, FPL("")}, + { { FPL("F:/foo/bar"), FPL("f:/foo2/bar/baz") }, FPL("")}, + { { FPL("c:/foo/bar"), FPL("c:/foo/bar2/baz") }, FPL("")}, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { { FPL("\\foo\\bar"), FPL("\\foo\\bar\\baz") }, FPL("baz")}, + { { FPL("\\foo/bar"), FPL("\\foo\\bar\\baz") }, FPL("baz")}, + { { FPL("\\foo/bar"), FPL("\\foo/bar/baz") }, FPL("baz")}, + { { FPL("\\"), FPL("\\foo\\bar\\baz") }, FPL("foo\\bar\\baz")}, + { { FPL(""), FPL("\\foo\\bar\\baz") }, FPL("")}, + { { FPL("\\foo\\bar"), FPL("\\foo2\\bar\\baz") }, FPL("")}, + { { FPL("\\foo\\bar"), FPL("\\foo\\bar2\\baz") }, FPL("")}, +#endif // FILE_PATH_USES_WIN_SEPARATORS + }; + + const FilePath base(FPL("blah")); + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath parent(cases[i].inputs[0]); + FilePath child(cases[i].inputs[1]); + { + FilePath result; + bool success = parent.AppendRelativePath(child, &result); + EXPECT_EQ(cases[i].expected[0] != '\0', success) << + "i: " << i << ", parent: " << parent.value() << ", child: " << + child.value(); + EXPECT_STREQ(cases[i].expected, result.value().c_str()) << + "i: " << i << ", parent: " << parent.value() << ", child: " << + child.value(); + } + { + FilePath result(base); + bool success = parent.AppendRelativePath(child, &result); + EXPECT_EQ(cases[i].expected[0] != '\0', success) << + "i: " << i << ", parent: " << parent.value() << ", child: " << + child.value(); + EXPECT_EQ(base.Append(cases[i].expected).value(), result.value()) << + "i: " << i << ", parent: " << parent.value() << ", child: " << + child.value(); + } + } +} + +TEST_F(FilePathTest, EqualityTest) { + const struct BinaryBooleanTestData cases[] = { + { { FPL("/foo/bar/baz"), FPL("/foo/bar/baz") }, true}, + { { FPL("/foo/bar"), FPL("/foo/bar/baz") }, false}, + { { FPL("/foo/bar/baz"), FPL("/foo/bar") }, false}, + { { FPL("//foo/bar/"), FPL("//foo/bar/") }, true}, + { { FPL("/foo/bar"), FPL("/foo2/bar") }, false}, + { { FPL("/foo/bar.txt"), FPL("/foo/bar") }, false}, + { { FPL("foo/bar"), FPL("foo/bar") }, true}, + { { FPL("foo/bar"), FPL("foo/bar/baz") }, false}, + { { FPL(""), FPL("foo") }, false}, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { { FPL("c:/foo/bar"), FPL("c:/foo/bar") }, true}, + { { FPL("E:/foo/bar"), FPL("e:/foo/bar") }, true}, + { { FPL("f:/foo/bar"), FPL("F:/foo/bar") }, true}, + { { FPL("E:/Foo/bar"), FPL("e:/foo/bar") }, false}, + { { FPL("f:/foo/bar"), FPL("F:/foo/Bar") }, false}, + { { FPL("c:/"), FPL("c:/") }, true}, + { { FPL("c:"), FPL("c:") }, true}, + { { FPL("c:/foo/bar"), FPL("d:/foo/bar") }, false}, + { { FPL("c:/foo/bar"), FPL("D:/foo/bar") }, false}, + { { FPL("C:/foo/bar"), FPL("d:/foo/bar") }, false}, + { { FPL("c:/foo/bar"), FPL("c:/foo2/bar") }, false}, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { { FPL("\\foo\\bar"), FPL("\\foo\\bar") }, true}, + { { FPL("\\foo/bar"), FPL("\\foo/bar") }, true}, + { { FPL("\\foo/bar"), FPL("\\foo\\bar") }, false}, + { { FPL("\\"), FPL("\\") }, true}, + { { FPL("\\"), FPL("/") }, false}, + { { FPL(""), FPL("\\") }, false}, + { { FPL("\\foo\\bar"), FPL("\\foo2\\bar") }, false}, + { { FPL("\\foo\\bar"), FPL("\\foo\\bar2") }, false}, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { { FPL("c:\\foo\\bar"), FPL("c:\\foo\\bar") }, true}, + { { FPL("E:\\foo\\bar"), FPL("e:\\foo\\bar") }, true}, + { { FPL("f:\\foo\\bar"), FPL("F:\\foo/bar") }, false}, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#endif // FILE_PATH_USES_WIN_SEPARATORS + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath a(cases[i].inputs[0]); + FilePath b(cases[i].inputs[1]); + + EXPECT_EQ(a == b, cases[i].expected) << + "equality i: " << i << ", a: " << a.value() << ", b: " << + b.value(); + } + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath a(cases[i].inputs[0]); + FilePath b(cases[i].inputs[1]); + + EXPECT_EQ(a != b, !cases[i].expected) << + "inequality i: " << i << ", a: " << a.value() << ", b: " << + b.value(); + } +} + +TEST_F(FilePathTest, Extension) { + FilePath base_dir(FILE_PATH_LITERAL("base_dir")); + + FilePath jpg = base_dir.Append(FILE_PATH_LITERAL("foo.jpg")); + EXPECT_EQ(FILE_PATH_LITERAL(".jpg"), jpg.Extension()); + EXPECT_EQ(FILE_PATH_LITERAL(".jpg"), jpg.FinalExtension()); + + FilePath base = jpg.BaseName().RemoveExtension(); + EXPECT_EQ(FILE_PATH_LITERAL("foo"), base.value()); + + FilePath path_no_ext = base_dir.Append(base); + EXPECT_EQ(path_no_ext.value(), jpg.RemoveExtension().value()); + + EXPECT_EQ(path_no_ext.value(), path_no_ext.RemoveExtension().value()); + EXPECT_EQ(FILE_PATH_LITERAL(""), path_no_ext.Extension()); + EXPECT_EQ(FILE_PATH_LITERAL(""), path_no_ext.FinalExtension()); +} + +TEST_F(FilePathTest, Extension2) { + const struct UnaryTestData cases[] = { +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { FPL("C:\\a\\b\\c.ext"), FPL(".ext") }, + { FPL("C:\\a\\b\\c."), FPL(".") }, + { FPL("C:\\a\\b\\c"), FPL("") }, + { FPL("C:\\a\\b\\"), FPL("") }, + { FPL("C:\\a\\b.\\"), FPL(".") }, + { FPL("C:\\a\\b\\c.ext1.ext2"), FPL(".ext2") }, + { FPL("C:\\foo.bar\\\\\\"), FPL(".bar") }, + { FPL("C:\\foo.bar\\.."), FPL("") }, + { FPL("C:\\foo.bar\\..\\\\"), FPL("") }, +#endif + { FPL("/foo/bar/baz.ext"), FPL(".ext") }, + { FPL("/foo/bar/baz."), FPL(".") }, + { FPL("/foo/bar/baz.."), FPL(".") }, + { FPL("/foo/bar/baz"), FPL("") }, + { FPL("/foo/bar/"), FPL("") }, + { FPL("/foo/bar./"), FPL(".") }, + { FPL("/foo/bar/baz.ext1.ext2"), FPL(".ext2") }, + { FPL("/subversion-1.6.12.zip"), FPL(".zip") }, + { FPL("/foo.12345.gz"), FPL(".gz") }, + { FPL("/foo..gz"), FPL(".gz") }, + { FPL("."), FPL("") }, + { FPL(".."), FPL("") }, + { FPL("./foo"), FPL("") }, + { FPL("./foo.ext"), FPL(".ext") }, + { FPL("/foo.ext1/bar.ext2"), FPL(".ext2") }, + { FPL("/foo.bar////"), FPL(".bar") }, + { FPL("/foo.bar/.."), FPL("") }, + { FPL("/foo.bar/..////"), FPL("") }, + { FPL("/foo.1234.luser.js"), FPL(".js") }, + { FPL("/user.js"), FPL(".js") }, + }; + const struct UnaryTestData double_extension_cases[] = { + { FPL("/foo.tar.gz"), FPL(".tar.gz") }, + { FPL("/foo.tar.Z"), FPL(".tar.Z") }, + { FPL("/foo.tar.bz2"), FPL(".tar.bz2") }, + { FPL("/foo.1234.gz"), FPL(".1234.gz") }, + { FPL("/foo.1234.tar.gz"), FPL(".tar.gz") }, + { FPL("/foo.tar.tar.gz"), FPL(".tar.gz") }, + { FPL("/foo.tar.gz.gz"), FPL(".gz.gz") }, + { FPL("/foo.1234.user.js"), FPL(".user.js") }, + { FPL("foo.user.js"), FPL(".user.js") }, + }; + for (unsigned int i = 0; i < arraysize(cases); ++i) { + FilePath path(cases[i].input); + FilePath::StringType extension = path.Extension(); + FilePath::StringType final_extension = path.FinalExtension(); + EXPECT_STREQ(cases[i].expected, extension.c_str()) << "i: " << i << + ", path: " << path.value(); + EXPECT_STREQ(cases[i].expected, final_extension.c_str()) << "i: " << i << + ", path: " << path.value(); + } + for (unsigned int i = 0; i < arraysize(double_extension_cases); ++i) { + FilePath path(cases[i].input); + FilePath::StringType extension = path.Extension(); + EXPECT_STREQ(cases[i].expected, extension.c_str()) << "i: " << i << + ", path: " << path.value(); + } +} + +TEST_F(FilePathTest, InsertBeforeExtension) { + const struct BinaryTestData cases[] = { + { { FPL(""), FPL("") }, FPL("") }, + { { FPL(""), FPL("txt") }, FPL("") }, + { { FPL("."), FPL("txt") }, FPL("") }, + { { FPL(".."), FPL("txt") }, FPL("") }, + { { FPL("foo.dll"), FPL("txt") }, FPL("footxt.dll") }, + { { FPL("."), FPL("") }, FPL(".") }, + { { FPL("foo.dll"), FPL(".txt") }, FPL("foo.txt.dll") }, + { { FPL("foo"), FPL("txt") }, FPL("footxt") }, + { { FPL("foo"), FPL(".txt") }, FPL("foo.txt") }, + { { FPL("foo.baz.dll"), FPL("txt") }, FPL("foo.baztxt.dll") }, + { { FPL("foo.baz.dll"), FPL(".txt") }, FPL("foo.baz.txt.dll") }, + { { FPL("foo.dll"), FPL("") }, FPL("foo.dll") }, + { { FPL("foo.dll"), FPL(".") }, FPL("foo..dll") }, + { { FPL("foo"), FPL("") }, FPL("foo") }, + { { FPL("foo"), FPL(".") }, FPL("foo.") }, + { { FPL("foo.baz.dll"), FPL("") }, FPL("foo.baz.dll") }, + { { FPL("foo.baz.dll"), FPL(".") }, FPL("foo.baz..dll") }, +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { { FPL("\\"), FPL("") }, FPL("\\") }, + { { FPL("\\"), FPL("txt") }, FPL("\\txt") }, + { { FPL("\\."), FPL("txt") }, FPL("") }, + { { FPL("\\.."), FPL("txt") }, FPL("") }, + { { FPL("\\."), FPL("") }, FPL("\\.") }, + { { FPL("C:\\bar\\foo.dll"), FPL("txt") }, + FPL("C:\\bar\\footxt.dll") }, + { { FPL("C:\\bar.baz\\foodll"), FPL("txt") }, + FPL("C:\\bar.baz\\foodlltxt") }, + { { FPL("C:\\bar.baz\\foo.dll"), FPL("txt") }, + FPL("C:\\bar.baz\\footxt.dll") }, + { { FPL("C:\\bar.baz\\foo.dll.exe"), FPL("txt") }, + FPL("C:\\bar.baz\\foo.dlltxt.exe") }, + { { FPL("C:\\bar.baz\\foo"), FPL("") }, + FPL("C:\\bar.baz\\foo") }, + { { FPL("C:\\bar.baz\\foo.exe"), FPL("") }, + FPL("C:\\bar.baz\\foo.exe") }, + { { FPL("C:\\bar.baz\\foo.dll.exe"), FPL("") }, + FPL("C:\\bar.baz\\foo.dll.exe") }, + { { FPL("C:\\bar\\baz\\foo.exe"), FPL(" (1)") }, + FPL("C:\\bar\\baz\\foo (1).exe") }, + { { FPL("C:\\foo.baz\\\\"), FPL(" (1)") }, FPL("C:\\foo (1).baz") }, + { { FPL("C:\\foo.baz\\..\\"), FPL(" (1)") }, FPL("") }, +#endif + { { FPL("/"), FPL("") }, FPL("/") }, + { { FPL("/"), FPL("txt") }, FPL("/txt") }, + { { FPL("/."), FPL("txt") }, FPL("") }, + { { FPL("/.."), FPL("txt") }, FPL("") }, + { { FPL("/."), FPL("") }, FPL("/.") }, + { { FPL("/bar/foo.dll"), FPL("txt") }, FPL("/bar/footxt.dll") }, + { { FPL("/bar.baz/foodll"), FPL("txt") }, FPL("/bar.baz/foodlltxt") }, + { { FPL("/bar.baz/foo.dll"), FPL("txt") }, FPL("/bar.baz/footxt.dll") }, + { { FPL("/bar.baz/foo.dll.exe"), FPL("txt") }, + FPL("/bar.baz/foo.dlltxt.exe") }, + { { FPL("/bar.baz/foo"), FPL("") }, FPL("/bar.baz/foo") }, + { { FPL("/bar.baz/foo.exe"), FPL("") }, FPL("/bar.baz/foo.exe") }, + { { FPL("/bar.baz/foo.dll.exe"), FPL("") }, FPL("/bar.baz/foo.dll.exe") }, + { { FPL("/bar/baz/foo.exe"), FPL(" (1)") }, FPL("/bar/baz/foo (1).exe") }, + { { FPL("/bar/baz/..////"), FPL(" (1)") }, FPL("") }, + }; + for (unsigned int i = 0; i < arraysize(cases); ++i) { + FilePath path(cases[i].inputs[0]); + FilePath result = path.InsertBeforeExtension(cases[i].inputs[1]); + EXPECT_EQ(cases[i].expected, result.value()) << "i: " << i << + ", path: " << path.value() << ", insert: " << cases[i].inputs[1]; + } +} + +TEST_F(FilePathTest, RemoveExtension) { + const struct UnaryTestData cases[] = { + { FPL(""), FPL("") }, + { FPL("."), FPL(".") }, + { FPL(".."), FPL("..") }, + { FPL("foo.dll"), FPL("foo") }, + { FPL("./foo.dll"), FPL("./foo") }, + { FPL("foo..dll"), FPL("foo.") }, + { FPL("foo"), FPL("foo") }, + { FPL("foo."), FPL("foo") }, + { FPL("foo.."), FPL("foo.") }, + { FPL("foo.baz.dll"), FPL("foo.baz") }, +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { FPL("C:\\foo.bar\\foo"), FPL("C:\\foo.bar\\foo") }, + { FPL("C:\\foo.bar\\..\\\\"), FPL("C:\\foo.bar\\..\\\\") }, +#endif + { FPL("/foo.bar/foo"), FPL("/foo.bar/foo") }, + { FPL("/foo.bar/..////"), FPL("/foo.bar/..////") }, + }; + for (unsigned int i = 0; i < arraysize(cases); ++i) { + FilePath path(cases[i].input); + FilePath removed = path.RemoveExtension(); + FilePath removed_final = path.RemoveFinalExtension(); + EXPECT_EQ(cases[i].expected, removed.value()) << "i: " << i << + ", path: " << path.value(); + EXPECT_EQ(cases[i].expected, removed_final.value()) << "i: " << i << + ", path: " << path.value(); + } + { + FilePath path(FPL("foo.tar.gz")); + FilePath removed = path.RemoveExtension(); + FilePath removed_final = path.RemoveFinalExtension(); + EXPECT_EQ(FPL("foo"), removed.value()) << ", path: " << path.value(); + EXPECT_EQ(FPL("foo.tar"), removed_final.value()) << ", path: " + << path.value(); + } +} + +TEST_F(FilePathTest, ReplaceExtension) { + const struct BinaryTestData cases[] = { + { { FPL(""), FPL("") }, FPL("") }, + { { FPL(""), FPL("txt") }, FPL("") }, + { { FPL("."), FPL("txt") }, FPL("") }, + { { FPL(".."), FPL("txt") }, FPL("") }, + { { FPL("."), FPL("") }, FPL("") }, + { { FPL("foo.dll"), FPL("txt") }, FPL("foo.txt") }, + { { FPL("./foo.dll"), FPL("txt") }, FPL("./foo.txt") }, + { { FPL("foo..dll"), FPL("txt") }, FPL("foo..txt") }, + { { FPL("foo.dll"), FPL(".txt") }, FPL("foo.txt") }, + { { FPL("foo"), FPL("txt") }, FPL("foo.txt") }, + { { FPL("foo."), FPL("txt") }, FPL("foo.txt") }, + { { FPL("foo.."), FPL("txt") }, FPL("foo..txt") }, + { { FPL("foo"), FPL(".txt") }, FPL("foo.txt") }, + { { FPL("foo.baz.dll"), FPL("txt") }, FPL("foo.baz.txt") }, + { { FPL("foo.baz.dll"), FPL(".txt") }, FPL("foo.baz.txt") }, + { { FPL("foo.dll"), FPL("") }, FPL("foo") }, + { { FPL("foo.dll"), FPL(".") }, FPL("foo") }, + { { FPL("foo"), FPL("") }, FPL("foo") }, + { { FPL("foo"), FPL(".") }, FPL("foo") }, + { { FPL("foo.baz.dll"), FPL("") }, FPL("foo.baz") }, + { { FPL("foo.baz.dll"), FPL(".") }, FPL("foo.baz") }, +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { { FPL("C:\\foo.bar\\foo"), FPL("baz") }, FPL("C:\\foo.bar\\foo.baz") }, + { { FPL("C:\\foo.bar\\..\\\\"), FPL("baz") }, FPL("") }, +#endif + { { FPL("/foo.bar/foo"), FPL("baz") }, FPL("/foo.bar/foo.baz") }, + { { FPL("/foo.bar/..////"), FPL("baz") }, FPL("") }, + }; + for (unsigned int i = 0; i < arraysize(cases); ++i) { + FilePath path(cases[i].inputs[0]); + FilePath replaced = path.ReplaceExtension(cases[i].inputs[1]); + EXPECT_EQ(cases[i].expected, replaced.value()) << "i: " << i << + ", path: " << path.value() << ", replace: " << cases[i].inputs[1]; + } +} + +TEST_F(FilePathTest, AddExtension) { + const struct BinaryTestData cases[] = { + { { FPL(""), FPL("") }, FPL("") }, + { { FPL(""), FPL("txt") }, FPL("") }, + { { FPL("."), FPL("txt") }, FPL("") }, + { { FPL(".."), FPL("txt") }, FPL("") }, + { { FPL("."), FPL("") }, FPL("") }, + { { FPL("foo.dll"), FPL("txt") }, FPL("foo.dll.txt") }, + { { FPL("./foo.dll"), FPL("txt") }, FPL("./foo.dll.txt") }, + { { FPL("foo..dll"), FPL("txt") }, FPL("foo..dll.txt") }, + { { FPL("foo.dll"), FPL(".txt") }, FPL("foo.dll.txt") }, + { { FPL("foo"), FPL("txt") }, FPL("foo.txt") }, + { { FPL("foo."), FPL("txt") }, FPL("foo.txt") }, + { { FPL("foo.."), FPL("txt") }, FPL("foo..txt") }, + { { FPL("foo"), FPL(".txt") }, FPL("foo.txt") }, + { { FPL("foo.baz.dll"), FPL("txt") }, FPL("foo.baz.dll.txt") }, + { { FPL("foo.baz.dll"), FPL(".txt") }, FPL("foo.baz.dll.txt") }, + { { FPL("foo.dll"), FPL("") }, FPL("foo.dll") }, + { { FPL("foo.dll"), FPL(".") }, FPL("foo.dll") }, + { { FPL("foo"), FPL("") }, FPL("foo") }, + { { FPL("foo"), FPL(".") }, FPL("foo") }, + { { FPL("foo.baz.dll"), FPL("") }, FPL("foo.baz.dll") }, + { { FPL("foo.baz.dll"), FPL(".") }, FPL("foo.baz.dll") }, +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { { FPL("C:\\foo.bar\\foo"), FPL("baz") }, FPL("C:\\foo.bar\\foo.baz") }, + { { FPL("C:\\foo.bar\\..\\\\"), FPL("baz") }, FPL("") }, +#endif + { { FPL("/foo.bar/foo"), FPL("baz") }, FPL("/foo.bar/foo.baz") }, + { { FPL("/foo.bar/..////"), FPL("baz") }, FPL("") }, + }; + for (unsigned int i = 0; i < arraysize(cases); ++i) { + FilePath path(cases[i].inputs[0]); + FilePath added = path.AddExtension(cases[i].inputs[1]); + EXPECT_EQ(cases[i].expected, added.value()) << "i: " << i << + ", path: " << path.value() << ", add: " << cases[i].inputs[1]; + } +} + +TEST_F(FilePathTest, MatchesExtension) { + const struct BinaryBooleanTestData cases[] = { + { { FPL("foo"), FPL("") }, true}, + { { FPL("foo"), FPL(".") }, false}, + { { FPL("foo."), FPL("") }, false}, + { { FPL("foo."), FPL(".") }, true}, + { { FPL("foo.txt"), FPL(".dll") }, false}, + { { FPL("foo.txt"), FPL(".txt") }, true}, + { { FPL("foo.txt.dll"), FPL(".txt") }, false}, + { { FPL("foo.txt.dll"), FPL(".dll") }, true}, + { { FPL("foo.TXT"), FPL(".txt") }, true}, + { { FPL("foo.txt"), FPL(".TXT") }, true}, + { { FPL("foo.tXt"), FPL(".txt") }, true}, + { { FPL("foo.txt"), FPL(".tXt") }, true}, + { { FPL("foo.tXt"), FPL(".TXT") }, true}, + { { FPL("foo.tXt"), FPL(".tXt") }, true}, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { { FPL("c:/foo.txt.dll"), FPL(".txt") }, false}, + { { FPL("c:/foo.txt"), FPL(".txt") }, true}, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { { FPL("c:\\bar\\foo.txt.dll"), FPL(".txt") }, false}, + { { FPL("c:\\bar\\foo.txt"), FPL(".txt") }, true}, +#endif // FILE_PATH_USES_DRIVE_LETTERS + { { FPL("/bar/foo.txt.dll"), FPL(".txt") }, false}, + { { FPL("/bar/foo.txt"), FPL(".txt") }, true}, +#if defined(OS_WIN) || defined(OS_MACOSX) + // Umlauts A, O, U: direct comparison, and upper case vs. lower case + { { FPL("foo.\u00E4\u00F6\u00FC"), FPL(".\u00E4\u00F6\u00FC") }, true}, + { { FPL("foo.\u00C4\u00D6\u00DC"), FPL(".\u00E4\u00F6\u00FC") }, true}, + // C with circumflex: direct comparison, and upper case vs. lower case + { { FPL("foo.\u0109"), FPL(".\u0109") }, true}, + { { FPL("foo.\u0108"), FPL(".\u0109") }, true}, +#endif + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath path(cases[i].inputs[0]); + FilePath::StringType ext(cases[i].inputs[1]); + + EXPECT_EQ(cases[i].expected, path.MatchesExtension(ext)) << + "i: " << i << ", path: " << path.value() << ", ext: " << ext; + } +} + +TEST_F(FilePathTest, CompareIgnoreCase) { + const struct BinaryIntTestData cases[] = { + { { FPL("foo"), FPL("foo") }, 0}, + { { FPL("FOO"), FPL("foo") }, 0}, + { { FPL("foo.ext"), FPL("foo.ext") }, 0}, + { { FPL("FOO.EXT"), FPL("foo.ext") }, 0}, + { { FPL("Foo.Ext"), FPL("foo.ext") }, 0}, + { { FPL("foO"), FPL("foo") }, 0}, + { { FPL("foo"), FPL("foO") }, 0}, + { { FPL("fOo"), FPL("foo") }, 0}, + { { FPL("foo"), FPL("fOo") }, 0}, + { { FPL("bar"), FPL("foo") }, -1}, + { { FPL("foo"), FPL("bar") }, 1}, + { { FPL("BAR"), FPL("foo") }, -1}, + { { FPL("FOO"), FPL("bar") }, 1}, + { { FPL("bar"), FPL("FOO") }, -1}, + { { FPL("foo"), FPL("BAR") }, 1}, + { { FPL("BAR"), FPL("FOO") }, -1}, + { { FPL("FOO"), FPL("BAR") }, 1}, + // German "Eszett" (lower case and the new-fangled upper case) + // Note that uc() => "SS", NOT ! + // However, neither Windows nor Mac OSX converts these. + // (or even have glyphs for ) + { { FPL("\u00DF"), FPL("\u00DF") }, 0}, + { { FPL("\u1E9E"), FPL("\u1E9E") }, 0}, + { { FPL("\u00DF"), FPL("\u1E9E") }, -1}, + { { FPL("SS"), FPL("\u00DF") }, -1}, + { { FPL("SS"), FPL("\u1E9E") }, -1}, +#if defined(OS_WIN) || defined(OS_MACOSX) + // Umlauts A, O, U: direct comparison, and upper case vs. lower case + { { FPL("\u00E4\u00F6\u00FC"), FPL("\u00E4\u00F6\u00FC") }, 0}, + { { FPL("\u00C4\u00D6\u00DC"), FPL("\u00E4\u00F6\u00FC") }, 0}, + // C with circumflex: direct comparison, and upper case vs. lower case + { { FPL("\u0109"), FPL("\u0109") }, 0}, + { { FPL("\u0108"), FPL("\u0109") }, 0}, + // Cyrillic letter SHA: direct comparison, and upper case vs. lower case + { { FPL("\u0428"), FPL("\u0428") }, 0}, + { { FPL("\u0428"), FPL("\u0448") }, 0}, + // Greek letter DELTA: direct comparison, and upper case vs. lower case + { { FPL("\u0394"), FPL("\u0394") }, 0}, + { { FPL("\u0394"), FPL("\u03B4") }, 0}, + // Japanese full-width A: direct comparison, and upper case vs. lower case + // Note that full-width and standard characters are considered different. + { { FPL("\uFF21"), FPL("\uFF21") }, 0}, + { { FPL("\uFF21"), FPL("\uFF41") }, 0}, + { { FPL("A"), FPL("\uFF21") }, -1}, + { { FPL("A"), FPL("\uFF41") }, -1}, + { { FPL("a"), FPL("\uFF21") }, -1}, + { { FPL("a"), FPL("\uFF41") }, -1}, +#endif +#if defined(OS_MACOSX) + // Codepoints > 0x1000 + // Georgian letter DON: direct comparison, and upper case vs. lower case + { { FPL("\u10A3"), FPL("\u10A3") }, 0}, + { { FPL("\u10A3"), FPL("\u10D3") }, 0}, + // Combining characters vs. pre-composed characters, upper and lower case + { { FPL("k\u0301u\u032Do\u0304\u0301n"), FPL("\u1E31\u1E77\u1E53n") }, 0}, + { { FPL("k\u0301u\u032Do\u0304\u0301n"), FPL("kuon") }, 1}, + { { FPL("kuon"), FPL("k\u0301u\u032Do\u0304\u0301n") }, -1}, + { { FPL("K\u0301U\u032DO\u0304\u0301N"), FPL("KUON") }, 1}, + { { FPL("KUON"), FPL("K\u0301U\u032DO\u0304\u0301N") }, -1}, + { { FPL("k\u0301u\u032Do\u0304\u0301n"), FPL("KUON") }, 1}, + { { FPL("K\u0301U\u032DO\u0304\u0301N"), FPL("\u1E31\u1E77\u1E53n") }, 0}, + { { FPL("k\u0301u\u032Do\u0304\u0301n"), FPL("\u1E30\u1E76\u1E52n") }, 0}, + { { FPL("k\u0301u\u032Do\u0304\u0302n"), FPL("\u1E30\u1E76\u1E52n") }, 1}, +#endif + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath::StringType s1(cases[i].inputs[0]); + FilePath::StringType s2(cases[i].inputs[1]); + int result = FilePath::CompareIgnoreCase(s1, s2); + EXPECT_EQ(cases[i].expected, result) << + "i: " << i << ", s1: " << s1 << ", s2: " << s2; + } +} + +TEST_F(FilePathTest, ReferencesParent) { + const struct UnaryBooleanTestData cases[] = { + { FPL("."), false }, + { FPL(".."), true }, + { FPL(".. "), true }, + { FPL(" .."), true }, + { FPL("..."), true }, + { FPL("a.."), false }, + { FPL("..a"), false }, + { FPL("../"), true }, + { FPL("/.."), true }, + { FPL("/../"), true }, + { FPL("/a../"), false }, + { FPL("/..a/"), false }, + { FPL("//.."), true }, + { FPL("..//"), true }, + { FPL("//..//"), true }, + { FPL("a//..//c"), true }, + { FPL("../b/c"), true }, + { FPL("/../b/c"), true }, + { FPL("a/b/.."), true }, + { FPL("a/b/../"), true }, + { FPL("a/../c"), true }, + { FPL("a/b/c"), false }, + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath input(cases[i].input); + bool observed = input.ReferencesParent(); + EXPECT_EQ(cases[i].expected, observed) << + "i: " << i << ", input: " << input.value(); + } +} + +// FIXME(gejun): locale-related code does not work yet(on my testing machine) +/* +TEST_F(FilePathTest, FromUTF8Unsafe_And_AsUTF8Unsafe) { + const struct UTF8TestData cases[] = { + { FPL("foo.txt"), "foo.txt" }, + // "aeo" with accents. Use http://0xcc.net/jsescape/ to decode them. + { FPL("\u00E0\u00E8\u00F2.txt"), "\xC3\xA0\xC3\xA8\xC3\xB2.txt" }, + // Full-width "ABC". + { FPL("\uFF21\uFF22\uFF23.txt"), + "\xEF\xBC\xA1\xEF\xBC\xA2\xEF\xBC\xA3.txt" }, + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + // Test FromUTF8Unsafe() works. + FilePath from_utf8 = FilePath::FromUTF8Unsafe(cases[i].utf8); + EXPECT_EQ(cases[i].native, from_utf8.value()) + << "i: " << i << ", input: " << cases[i].native; + // Test AsUTF8Unsafe() works. + FilePath from_native = FilePath(cases[i].native); + EXPECT_EQ(cases[i].utf8, from_native.AsUTF8Unsafe()) + << "i: " << i << ", input: " << cases[i].native; + // Test the two file paths are identical. + EXPECT_EQ(from_utf8.value(), from_native.value()); + } +} +*/ + +TEST_F(FilePathTest, ConstructWithNUL) { + // Assert FPS() works. + ASSERT_EQ(3U, FPS("a\0b").length()); + + // Test constructor strips '\0' + FilePath path(FPS("a\0b")); + EXPECT_EQ(1U, path.value().length()); + EXPECT_EQ(FPL("a"), path.value()); +} + +TEST_F(FilePathTest, AppendWithNUL) { + // Assert FPS() works. + ASSERT_EQ(3U, FPS("b\0b").length()); + + // Test Append() strips '\0' + FilePath path(FPL("a")); + path = path.Append(FPS("b\0b")); + EXPECT_EQ(3U, path.value().length()); +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + EXPECT_EQ(FPL("a\\b"), path.value()); +#else + EXPECT_EQ(FPL("a/b"), path.value()); +#endif +} + +TEST_F(FilePathTest, ReferencesParentWithNUL) { + // Assert FPS() works. + ASSERT_EQ(3U, FPS("..\0").length()); + + // Test ReferencesParent() doesn't break with "..\0" + FilePath path(FPS("..\0")); + EXPECT_TRUE(path.ReferencesParent()); +} + +#if defined(FILE_PATH_USES_WIN_SEPARATORS) +TEST_F(FilePathTest, NormalizePathSeparators) { + const struct UnaryTestData cases[] = { + { FPL("foo/bar"), FPL("foo\\bar") }, + { FPL("foo/bar\\betz"), FPL("foo\\bar\\betz") }, + { FPL("foo\\bar"), FPL("foo\\bar") }, + { FPL("foo\\bar/betz"), FPL("foo\\bar\\betz") }, + { FPL("foo"), FPL("foo") }, + // Trailing slashes don't automatically get stripped. That's what + // StripTrailingSeparators() is for. + { FPL("foo\\"), FPL("foo\\") }, + { FPL("foo/"), FPL("foo\\") }, + { FPL("foo/bar\\"), FPL("foo\\bar\\") }, + { FPL("foo\\bar/"), FPL("foo\\bar\\") }, + { FPL("foo/bar/"), FPL("foo\\bar\\") }, + { FPL("foo\\bar\\"), FPL("foo\\bar\\") }, + { FPL("\\foo/bar"), FPL("\\foo\\bar") }, + { FPL("/foo\\bar"), FPL("\\foo\\bar") }, + { FPL("c:/foo/bar/"), FPL("c:\\foo\\bar\\") }, + { FPL("/foo/bar/"), FPL("\\foo\\bar\\") }, + { FPL("\\foo\\bar\\"), FPL("\\foo\\bar\\") }, + { FPL("c:\\foo/bar"), FPL("c:\\foo\\bar") }, + { FPL("//foo\\bar\\"), FPL("\\\\foo\\bar\\") }, + { FPL("\\\\foo\\bar\\"), FPL("\\\\foo\\bar\\") }, + { FPL("//foo\\bar\\"), FPL("\\\\foo\\bar\\") }, + // This method does not normalize the number of path separators. + { FPL("foo\\\\bar"), FPL("foo\\\\bar") }, + { FPL("foo//bar"), FPL("foo\\\\bar") }, + { FPL("foo/\\bar"), FPL("foo\\\\bar") }, + { FPL("foo\\/bar"), FPL("foo\\\\bar") }, + { FPL("///foo\\\\bar"), FPL("\\\\\\foo\\\\bar") }, + { FPL("foo//bar///"), FPL("foo\\\\bar\\\\\\") }, + { FPL("foo/\\bar/\\"), FPL("foo\\\\bar\\\\") }, + { FPL("/\\foo\\/bar"), FPL("\\\\foo\\\\bar") }, + }; + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath input(cases[i].input); + FilePath observed = input.NormalizePathSeparators(); + EXPECT_EQ(FilePath::StringType(cases[i].expected), observed.value()) << + "i: " << i << ", input: " << input.value(); + } +} +#endif + +TEST_F(FilePathTest, EndsWithSeparator) { + const UnaryBooleanTestData cases[] = { + { FPL(""), false }, + { FPL("/"), true }, + { FPL("foo/"), true }, + { FPL("bar"), false }, + { FPL("/foo/bar"), false }, + }; + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath input = FilePath(cases[i].input).NormalizePathSeparators(); + EXPECT_EQ(cases[i].expected, input.EndsWithSeparator()); + } +} + +TEST_F(FilePathTest, AsEndingWithSeparator) { + const UnaryTestData cases[] = { + { FPL(""), FPL("") }, + { FPL("/"), FPL("/") }, + { FPL("foo"), FPL("foo/") }, + { FPL("foo/"), FPL("foo/") } + }; + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath input = FilePath(cases[i].input).NormalizePathSeparators(); + FilePath expected = FilePath(cases[i].expected).NormalizePathSeparators(); + EXPECT_EQ(expected.value(), input.AsEndingWithSeparator().value()); + } +} + +#if defined(OS_ANDROID) +TEST_F(FilePathTest, ContentUriTest) { + const struct UnaryBooleanTestData cases[] = { + { FPL("content://foo.bar"), true }, + { FPL("content://foo.bar/"), true }, + { FPL("content://foo/bar"), true }, + { FPL("CoNTenT://foo.bar"), true }, + { FPL("content://"), true }, + { FPL("content:///foo.bar"), true }, + { FPL("content://3foo/bar"), true }, + { FPL("content://_foo/bar"), true }, + { FPL(".. "), false }, + { FPL("foo.bar"), false }, + { FPL("content:foo.bar"), false }, + { FPL("content:/foo.ba"), false }, + { FPL("content:/dir/foo.bar"), false }, + { FPL("content: //foo.bar"), false }, + { FPL("content%2a%2f%2f"), false }, + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath input(cases[i].input); + bool observed = input.IsContentUri(); + EXPECT_EQ(cases[i].expected, observed) << + "i: " << i << ", input: " << input.value(); + } +} +#endif + +} // namespace butil diff --git a/test/file_unittest.cc b/test/file_unittest.cc new file mode 100644 index 0000000..706b289 --- /dev/null +++ b/test/file_unittest.cc @@ -0,0 +1,468 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/file_util.h" +#include "butil/files/file.h" +#include "butil/files/scoped_temp_dir.h" +#include "butil/time/time.h" +#include + +using butil::File; +using butil::FilePath; + +TEST(FileTest, Create) { + butil::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = temp_dir.path().AppendASCII("create_file_1"); + + { + // Don't create a File at all. + File file; + EXPECT_FALSE(file.IsValid()); + EXPECT_EQ(butil::File::FILE_ERROR_FAILED, file.error_details()); + + File file2(butil::File::FILE_ERROR_TOO_MANY_OPENED); + EXPECT_FALSE(file2.IsValid()); + EXPECT_EQ(butil::File::FILE_ERROR_TOO_MANY_OPENED, file2.error_details()); + } + + { + // Open a file that doesn't exist. + File file(file_path, butil::File::FLAG_OPEN | butil::File::FLAG_READ); + EXPECT_FALSE(file.IsValid()); + EXPECT_EQ(butil::File::FILE_ERROR_NOT_FOUND, file.error_details()); + } + + { + // Open or create a file. + File file(file_path, butil::File::FLAG_OPEN_ALWAYS | butil::File::FLAG_READ); + EXPECT_TRUE(file.IsValid()); + EXPECT_TRUE(file.created()); + EXPECT_EQ(butil::File::FILE_OK, file.error_details()); + } + + { + // Open an existing file. + File file(file_path, butil::File::FLAG_OPEN | butil::File::FLAG_READ); + EXPECT_TRUE(file.IsValid()); + EXPECT_FALSE(file.created()); + EXPECT_EQ(butil::File::FILE_OK, file.error_details()); + + // This time verify closing the file. + file.Close(); + EXPECT_FALSE(file.IsValid()); + } + + { + // Open an existing file through Initialize + File file; + file.Initialize(file_path, butil::File::FLAG_OPEN | butil::File::FLAG_READ); + EXPECT_TRUE(file.IsValid()); + EXPECT_FALSE(file.created()); + EXPECT_EQ(butil::File::FILE_OK, file.error_details()); + + // This time verify closing the file. + file.Close(); + EXPECT_FALSE(file.IsValid()); + } + + { + // Create a file that exists. + File file(file_path, butil::File::FLAG_CREATE | butil::File::FLAG_READ); + EXPECT_FALSE(file.IsValid()); + EXPECT_FALSE(file.created()); + EXPECT_EQ(butil::File::FILE_ERROR_EXISTS, file.error_details()); + } + + { + // Create or overwrite a file. + File file(file_path, + butil::File::FLAG_CREATE_ALWAYS | butil::File::FLAG_WRITE); + EXPECT_TRUE(file.IsValid()); + EXPECT_TRUE(file.created()); + EXPECT_EQ(butil::File::FILE_OK, file.error_details()); + } + + { + // Create a delete-on-close file. + file_path = temp_dir.path().AppendASCII("create_file_2"); + File file(file_path, + butil::File::FLAG_OPEN_ALWAYS | butil::File::FLAG_READ | + butil::File::FLAG_DELETE_ON_CLOSE); + EXPECT_TRUE(file.IsValid()); + EXPECT_TRUE(file.created()); + EXPECT_EQ(butil::File::FILE_OK, file.error_details()); + } + + EXPECT_FALSE(butil::PathExists(file_path)); +} + +TEST(FileTest, Async) { + butil::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = temp_dir.path().AppendASCII("create_file"); + + { + File file(file_path, butil::File::FLAG_OPEN_ALWAYS | butil::File::FLAG_ASYNC); + EXPECT_TRUE(file.IsValid()); + EXPECT_TRUE(file.async()); + } + + { + File file(file_path, butil::File::FLAG_OPEN_ALWAYS); + EXPECT_TRUE(file.IsValid()); + EXPECT_FALSE(file.async()); + } +} + +TEST(FileTest, DeleteOpenFile) { + butil::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = temp_dir.path().AppendASCII("create_file_1"); + + // Create a file. + File file(file_path, + butil::File::FLAG_OPEN_ALWAYS | butil::File::FLAG_READ | + butil::File::FLAG_SHARE_DELETE); + EXPECT_TRUE(file.IsValid()); + EXPECT_TRUE(file.created()); + EXPECT_EQ(butil::File::FILE_OK, file.error_details()); + + // Open an existing file and mark it as delete on close. + File same_file(file_path, + butil::File::FLAG_OPEN | butil::File::FLAG_DELETE_ON_CLOSE | + butil::File::FLAG_READ); + EXPECT_TRUE(file.IsValid()); + EXPECT_FALSE(same_file.created()); + EXPECT_EQ(butil::File::FILE_OK, same_file.error_details()); + + // Close both handles and check that the file is gone. + file.Close(); + same_file.Close(); + EXPECT_FALSE(butil::PathExists(file_path)); +} + +TEST(FileTest, ReadWrite) { + butil::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = temp_dir.path().AppendASCII("read_write_file"); + File file(file_path, + butil::File::FLAG_CREATE | butil::File::FLAG_READ | + butil::File::FLAG_WRITE); + ASSERT_TRUE(file.IsValid()); + + char data_to_write[] = "test"; + const int kTestDataSize = 4; + + // Write 0 bytes to the file. + int bytes_written = file.Write(0, data_to_write, 0); + EXPECT_EQ(0, bytes_written); + + // Write "test" to the file. + bytes_written = file.Write(0, data_to_write, kTestDataSize); + EXPECT_EQ(kTestDataSize, bytes_written); + + // Read from EOF. + char data_read_1[32]; + int bytes_read = file.Read(kTestDataSize, data_read_1, kTestDataSize); + EXPECT_EQ(0, bytes_read); + + // Read from somewhere in the middle of the file. + const int kPartialReadOffset = 1; + bytes_read = file.Read(kPartialReadOffset, data_read_1, kTestDataSize); + EXPECT_EQ(kTestDataSize - kPartialReadOffset, bytes_read); + for (int i = 0; i < bytes_read; i++) + EXPECT_EQ(data_to_write[i + kPartialReadOffset], data_read_1[i]); + + // Read 0 bytes. + bytes_read = file.Read(0, data_read_1, 0); + EXPECT_EQ(0, bytes_read); + + // Read the entire file. + bytes_read = file.Read(0, data_read_1, kTestDataSize); + EXPECT_EQ(kTestDataSize, bytes_read); + for (int i = 0; i < bytes_read; i++) + EXPECT_EQ(data_to_write[i], data_read_1[i]); + + // Read again, but using the trivial native wrapper. + bytes_read = file.ReadNoBestEffort(0, data_read_1, kTestDataSize); + EXPECT_LE(bytes_read, kTestDataSize); + for (int i = 0; i < bytes_read; i++) + EXPECT_EQ(data_to_write[i], data_read_1[i]); + + // Write past the end of the file. + const int kOffsetBeyondEndOfFile = 10; + const int kPartialWriteLength = 2; + bytes_written = file.Write(kOffsetBeyondEndOfFile, + data_to_write, kPartialWriteLength); + EXPECT_EQ(kPartialWriteLength, bytes_written); + + // Make sure the file was extended. + int64_t file_size = 0; + EXPECT_TRUE(GetFileSize(file_path, &file_size)); + EXPECT_EQ(kOffsetBeyondEndOfFile + kPartialWriteLength, file_size); + + // Make sure the file was zero-padded. + char data_read_2[32]; + bytes_read = file.Read(0, data_read_2, static_cast(file_size)); + EXPECT_EQ(file_size, bytes_read); + for (int i = 0; i < kTestDataSize; i++) + EXPECT_EQ(data_to_write[i], data_read_2[i]); + for (int i = kTestDataSize; i < kOffsetBeyondEndOfFile; i++) + EXPECT_EQ(0, data_read_2[i]); + for (int i = kOffsetBeyondEndOfFile; i < file_size; i++) + EXPECT_EQ(data_to_write[i - kOffsetBeyondEndOfFile], data_read_2[i]); +} + +TEST(FileTest, Append) { + butil::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = temp_dir.path().AppendASCII("append_file"); + File file(file_path, butil::File::FLAG_CREATE | butil::File::FLAG_APPEND); + ASSERT_TRUE(file.IsValid()); + + char data_to_write[] = "test"; + const int kTestDataSize = 4; + + // Write 0 bytes to the file. + int bytes_written = file.Write(0, data_to_write, 0); + EXPECT_EQ(0, bytes_written); + + // Write "test" to the file. + bytes_written = file.Write(0, data_to_write, kTestDataSize); + EXPECT_EQ(kTestDataSize, bytes_written); + + file.Close(); + File file2(file_path, + butil::File::FLAG_OPEN | butil::File::FLAG_READ | + butil::File::FLAG_APPEND); + ASSERT_TRUE(file2.IsValid()); + + // Test passing the file around. + file = file2.Pass(); + EXPECT_FALSE(file2.IsValid()); + ASSERT_TRUE(file.IsValid()); + + char append_data_to_write[] = "78"; + const int kAppendDataSize = 2; + + // Append "78" to the file. + bytes_written = file.Write(0, append_data_to_write, kAppendDataSize); + EXPECT_EQ(kAppendDataSize, bytes_written); + + // Read the entire file. + char data_read_1[32]; + int bytes_read = file.Read(0, data_read_1, + kTestDataSize + kAppendDataSize); + EXPECT_EQ(kTestDataSize + kAppendDataSize, bytes_read); + for (int i = 0; i < kTestDataSize; i++) + EXPECT_EQ(data_to_write[i], data_read_1[i]); + for (int i = 0; i < kAppendDataSize; i++) + EXPECT_EQ(append_data_to_write[i], data_read_1[kTestDataSize + i]); +} + + +TEST(FileTest, Length) { + butil::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = temp_dir.path().AppendASCII("truncate_file"); + File file(file_path, + butil::File::FLAG_CREATE | butil::File::FLAG_READ | + butil::File::FLAG_WRITE); + ASSERT_TRUE(file.IsValid()); + EXPECT_EQ(0, file.GetLength()); + + // Write "test" to the file. + char data_to_write[] = "test"; + int kTestDataSize = 4; + int bytes_written = file.Write(0, data_to_write, kTestDataSize); + EXPECT_EQ(kTestDataSize, bytes_written); + + // Extend the file. + const int kExtendedFileLength = 10; + int64_t file_size = 0; + EXPECT_TRUE(file.SetLength(kExtendedFileLength)); + EXPECT_EQ(kExtendedFileLength, file.GetLength()); + EXPECT_TRUE(GetFileSize(file_path, &file_size)); + EXPECT_EQ(kExtendedFileLength, file_size); + + // Make sure the file was zero-padded. + char data_read[32]; + int bytes_read = file.Read(0, data_read, static_cast(file_size)); + EXPECT_EQ(file_size, bytes_read); + for (int i = 0; i < kTestDataSize; i++) + EXPECT_EQ(data_to_write[i], data_read[i]); + for (int i = kTestDataSize; i < file_size; i++) + EXPECT_EQ(0, data_read[i]); + + // Truncate the file. + const int kTruncatedFileLength = 2; + EXPECT_TRUE(file.SetLength(kTruncatedFileLength)); + EXPECT_EQ(kTruncatedFileLength, file.GetLength()); + EXPECT_TRUE(GetFileSize(file_path, &file_size)); + EXPECT_EQ(kTruncatedFileLength, file_size); + + // Make sure the file was truncated. + bytes_read = file.Read(0, data_read, kTestDataSize); + EXPECT_EQ(file_size, bytes_read); + for (int i = 0; i < file_size; i++) + EXPECT_EQ(data_to_write[i], data_read[i]); +} + +// Flakily fails: http://crbug.com/86494 +#if defined(OS_ANDROID) +TEST(FileTest, TouchGetInfo) { +#else +TEST(FileTest, DISABLED_TouchGetInfo) { +#endif + butil::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + File file(temp_dir.path().AppendASCII("touch_get_info_file"), + butil::File::FLAG_CREATE | butil::File::FLAG_WRITE | + butil::File::FLAG_WRITE_ATTRIBUTES); + ASSERT_TRUE(file.IsValid()); + + // Get info for a newly created file. + butil::File::Info info; + EXPECT_TRUE(file.GetInfo(&info)); + + // Add 2 seconds to account for possible rounding errors on + // filesystems that use a 1s or 2s timestamp granularity. + butil::Time now = butil::Time::Now() + butil::TimeDelta::FromSeconds(2); + EXPECT_EQ(0, info.size); + EXPECT_FALSE(info.is_directory); + EXPECT_FALSE(info.is_symbolic_link); + EXPECT_LE(info.last_accessed.ToInternalValue(), now.ToInternalValue()); + EXPECT_LE(info.last_modified.ToInternalValue(), now.ToInternalValue()); + EXPECT_LE(info.creation_time.ToInternalValue(), now.ToInternalValue()); + butil::Time creation_time = info.creation_time; + + // Write "test" to the file. + char data[] = "test"; + const int kTestDataSize = 4; + int bytes_written = file.Write(0, data, kTestDataSize); + EXPECT_EQ(kTestDataSize, bytes_written); + + // Change the last_accessed and last_modified dates. + // It's best to add values that are multiples of 2 (in seconds) + // to the current last_accessed and last_modified times, because + // FATxx uses a 2s timestamp granularity. + butil::Time new_last_accessed = + info.last_accessed + butil::TimeDelta::FromSeconds(234); + butil::Time new_last_modified = + info.last_modified + butil::TimeDelta::FromMinutes(567); + + EXPECT_TRUE(file.SetTimes(new_last_accessed, new_last_modified)); + + // Make sure the file info was updated accordingly. + EXPECT_TRUE(file.GetInfo(&info)); + EXPECT_EQ(info.size, kTestDataSize); + EXPECT_FALSE(info.is_directory); + EXPECT_FALSE(info.is_symbolic_link); + + // ext2/ext3 and HPS/HPS+ seem to have a timestamp granularity of 1s. +#if defined(OS_POSIX) + EXPECT_EQ(info.last_accessed.ToTimeVal().tv_sec, + new_last_accessed.ToTimeVal().tv_sec); + EXPECT_EQ(info.last_modified.ToTimeVal().tv_sec, + new_last_modified.ToTimeVal().tv_sec); +#else + EXPECT_EQ(info.last_accessed.ToInternalValue(), + new_last_accessed.ToInternalValue()); + EXPECT_EQ(info.last_modified.ToInternalValue(), + new_last_modified.ToInternalValue()); +#endif + + EXPECT_EQ(info.creation_time.ToInternalValue(), + creation_time.ToInternalValue()); +} + +TEST(FileTest, ReadAtCurrentPosition) { + butil::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = temp_dir.path().AppendASCII("read_at_current_position"); + File file(file_path, + butil::File::FLAG_CREATE | butil::File::FLAG_READ | + butil::File::FLAG_WRITE); + EXPECT_TRUE(file.IsValid()); + + const char kData[] = "test"; + const int kDataSize = sizeof(kData) - 1; + EXPECT_EQ(kDataSize, file.Write(0, kData, kDataSize)); + + EXPECT_EQ(0, file.Seek(butil::File::FROM_BEGIN, 0)); + + char buffer[kDataSize]; + int first_chunk_size = kDataSize / 2; + EXPECT_EQ(first_chunk_size, file.ReadAtCurrentPos(buffer, first_chunk_size)); + EXPECT_EQ(kDataSize - first_chunk_size, + file.ReadAtCurrentPos(buffer + first_chunk_size, + kDataSize - first_chunk_size)); + EXPECT_EQ(std::string(buffer, buffer + kDataSize), std::string(kData)); +} + +TEST(FileTest, WriteAtCurrentPosition) { + butil::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = temp_dir.path().AppendASCII("write_at_current_position"); + File file(file_path, + butil::File::FLAG_CREATE | butil::File::FLAG_READ | + butil::File::FLAG_WRITE); + EXPECT_TRUE(file.IsValid()); + + const char kData[] = "test"; + const int kDataSize = sizeof(kData) - 1; + + int first_chunk_size = kDataSize / 2; + EXPECT_EQ(first_chunk_size, file.WriteAtCurrentPos(kData, first_chunk_size)); + EXPECT_EQ(kDataSize - first_chunk_size, + file.WriteAtCurrentPos(kData + first_chunk_size, + kDataSize - first_chunk_size)); + + char buffer[kDataSize]; + EXPECT_EQ(kDataSize, file.Read(0, buffer, kDataSize)); + EXPECT_EQ(std::string(buffer, buffer + kDataSize), std::string(kData)); +} + +TEST(FileTest, Seek) { + butil::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = temp_dir.path().AppendASCII("seek_file"); + File file(file_path, + butil::File::FLAG_CREATE | butil::File::FLAG_READ | + butil::File::FLAG_WRITE); + ASSERT_TRUE(file.IsValid()); + + const int64_t kOffset = 10; + EXPECT_EQ(kOffset, file.Seek(butil::File::FROM_BEGIN, kOffset)); + EXPECT_EQ(2 * kOffset, file.Seek(butil::File::FROM_CURRENT, kOffset)); + EXPECT_EQ(kOffset, file.Seek(butil::File::FROM_CURRENT, -kOffset)); + EXPECT_TRUE(file.SetLength(kOffset * 2)); + EXPECT_EQ(kOffset, file.Seek(butil::File::FROM_END, -kOffset)); +} + +#if defined(OS_WIN) +TEST(FileTest, GetInfoForDirectory) { + butil::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath empty_dir = temp_dir.path().Append(FILE_PATH_LITERAL("gpfi_test")); + ASSERT_TRUE(CreateDirectory(empty_dir)); + + butil::File dir( + ::CreateFile(empty_dir.value().c_str(), + FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, // Needed to open a directory. + NULL)); + ASSERT_TRUE(dir.IsValid()); + + butil::File::Info info; + EXPECT_TRUE(dir.GetInfo(&info)); + EXPECT_TRUE(info.is_directory); + EXPECT_FALSE(info.is_symbolic_link); + EXPECT_EQ(0, info.size); +} +#endif // defined(OS_WIN) diff --git a/test/file_util_unittest.cc b/test/file_util_unittest.cc new file mode 100644 index 0000000..a323a72 --- /dev/null +++ b/test/file_util_unittest.cc @@ -0,0 +1,2633 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/build_config.h" + +#if defined(OS_WIN) +#include +#include +#include +#include +#include +#endif + +#if defined(OS_POSIX) +#include +#include +#include +#include +#endif + +#include +#include +#include +#include + +#include "butil/file_util.h" +#include "butil/files/file_enumerator.h" +#include "butil/files/file_path.h" +#include "butil/files/scoped_file.h" +#include "butil/files/scoped_temp_dir.h" +#include "butil/strings/utf_string_conversions.h" +#include "butil/threading/platform_thread.h" +#include +#include + +#if defined(OS_WIN) +#include "butil/win/scoped_handle.h" +#include "butil/win/windows_version.h" +#endif + +#if defined(OS_ANDROID) +#include "butil/android/content_uri_utils.h" +#endif + +// This macro helps avoid wrapped lines in the test structs. +#define FPL(x) FILE_PATH_LITERAL(x) + +namespace butil { + +namespace { + +// To test that file_util::Normalize FilePath() deals with NTFS reparse points +// correctly, we need functions to create and delete reparse points. +#if defined(OS_WIN) +typedef struct _REPARSE_DATA_BUFFER { + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union { + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + }; +} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; + +// Sets a reparse point. |source| will now point to |target|. Returns true if +// the call succeeds, false otherwise. +bool SetReparsePoint(HANDLE source, const FilePath& target_path) { + std::wstring kPathPrefix = L"\\??\\"; + std::wstring target_str; + // The juction will not work if the target path does not start with \??\ . + if (kPathPrefix != target_path.value().substr(0, kPathPrefix.size())) + target_str += kPathPrefix; + target_str += target_path.value(); + const wchar_t* target = target_str.c_str(); + USHORT size_target = static_cast(wcslen(target)) * sizeof(target[0]); + char buffer[2000] = {0}; + DWORD returned; + + REPARSE_DATA_BUFFER* data = reinterpret_cast(buffer); + + data->ReparseTag = 0xa0000003; + memcpy(data->MountPointReparseBuffer.PathBuffer, target, size_target + 2); + + data->MountPointReparseBuffer.SubstituteNameLength = size_target; + data->MountPointReparseBuffer.PrintNameOffset = size_target + 2; + data->ReparseDataLength = size_target + 4 + 8; + + int data_size = data->ReparseDataLength + 8; + + if (!DeviceIoControl(source, FSCTL_SET_REPARSE_POINT, &buffer, data_size, + NULL, 0, &returned, NULL)) { + return false; + } + return true; +} + +// Delete the reparse point referenced by |source|. Returns true if the call +// succeeds, false otherwise. +bool DeleteReparsePoint(HANDLE source) { + DWORD returned; + REPARSE_DATA_BUFFER data = {0}; + data.ReparseTag = 0xa0000003; + if (!DeviceIoControl(source, FSCTL_DELETE_REPARSE_POINT, &data, 8, NULL, 0, + &returned, NULL)) { + return false; + } + return true; +} + +// Manages a reparse point for a test. +class ReparsePoint { + public: + // Creates a reparse point from |source| (an empty directory) to |target|. + ReparsePoint(const FilePath& source, const FilePath& target) { + dir_.Set( + ::CreateFile(source.value().c_str(), + FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, // Needed to open a directory. + NULL)); + created_ = dir_.IsValid() && SetReparsePoint(dir_, target); + } + + ~ReparsePoint() { + if (created_) + DeleteReparsePoint(dir_); + } + + bool IsValid() { return created_; } + + private: + win::ScopedHandle dir_; + bool created_; + DISALLOW_COPY_AND_ASSIGN(ReparsePoint); +}; + +#endif + +#if defined(OS_POSIX) +// Provide a simple way to change the permissions bits on |path| in tests. +// ASSERT failures will return, but not stop the test. Caller should wrap +// calls to this function in ASSERT_NO_FATAL_FAILURE(). +void ChangePosixFilePermissions(const FilePath& path, + int mode_bits_to_set, + int mode_bits_to_clear) { + ASSERT_FALSE(mode_bits_to_set & mode_bits_to_clear) + << "Can't set and clear the same bits."; + + int mode = 0; + ASSERT_TRUE(GetPosixFilePermissions(path, &mode)); + mode |= mode_bits_to_set; + mode &= ~mode_bits_to_clear; + ASSERT_TRUE(SetPosixFilePermissions(path, mode)); +} +#endif // defined(OS_POSIX) + +const wchar_t bogus_content[] = L"I'm cannon fodder."; + +const int FILES_AND_DIRECTORIES = + FileEnumerator::FILES | FileEnumerator::DIRECTORIES; + +// file_util winds up using autoreleased objects on the Mac, so this needs +// to be a testing::Test +class FileUtilTest : public testing::Test { + protected: + virtual void SetUp() OVERRIDE { +#if defined(OS_POSIX) + if (getuid() == 0) { + is_root_ = true; + ASSERT_EQ(0, setegid(65534)); + ASSERT_EQ(0, seteuid(65534)); + } else { + is_root_ = false; + } +#endif + testing::Test::SetUp(); + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + } +#if defined(OS_POSIX) + virtual void TearDown() OVERRIDE { + if (is_root_) { + ASSERT_EQ(0, seteuid(0)); + ASSERT_EQ(0, setegid(0)); + is_root_ = false; + } + testing::Test::TearDown(); + } +#endif + + ScopedTempDir temp_dir_; +#if defined(OS_POSIX) + bool is_root_; +#endif +}; + +// Collects all the results from the given file enumerator, and provides an +// interface to query whether a given file is present. +class FindResultCollector { + public: + explicit FindResultCollector(FileEnumerator& enumerator) { + FilePath cur_file; + while (!(cur_file = enumerator.Next()).value().empty()) { + FilePath::StringType path = cur_file.value(); + // The file should not be returned twice. + EXPECT_TRUE(files_.end() == files_.find(path)) + << "Same file returned twice"; + + // Save for later. + files_.insert(path); + } + } + + // Returns true if the enumerator found the file. + bool HasFile(const FilePath& file) const { + return files_.find(file.value()) != files_.end(); + } + + int size() { + return static_cast(files_.size()); + } + + private: + std::set files_; +}; + +// Simple function to dump some text into a new file. +void CreateTextFile(const FilePath& filename, + const std::wstring& contents) { + std::wofstream file; + file.open(filename.value().c_str()); + ASSERT_TRUE(file.is_open()); + file << contents; + file.close(); +} + +// Simple function to take out some text from a file. +std::wstring ReadTextFile(const FilePath& filename) { + wchar_t contents[64]; + std::wifstream file; + file.open(filename.value().c_str()); + EXPECT_TRUE(file.is_open()); + file.getline(contents, arraysize(contents)); + file.close(); + return std::wstring(contents); +} + +#if defined(OS_WIN) +uint64_t FileTimeAsUint64(const FILETIME& ft) { + ULARGE_INTEGER u; + u.LowPart = ft.dwLowDateTime; + u.HighPart = ft.dwHighDateTime; + return u.QuadPart; +} +#endif + +TEST_F(FileUtilTest, FileAndDirectorySize) { + // Create three files of 20, 30 and 3 chars (utf8). ComputeDirectorySize + // should return 53 bytes. + FilePath file_01 = temp_dir_.path().Append(FPL("The file 01.txt")); + CreateTextFile(file_01, L"12345678901234567890"); + int64_t size_f1 = 0; + ASSERT_TRUE(GetFileSize(file_01, &size_f1)); + EXPECT_EQ(20ll, size_f1); + + FilePath subdir_path = temp_dir_.path().Append(FPL("Level2")); + CreateDirectory(subdir_path); + + FilePath file_02 = subdir_path.Append(FPL("The file 02.txt")); + CreateTextFile(file_02, L"123456789012345678901234567890"); + int64_t size_f2 = 0; + ASSERT_TRUE(GetFileSize(file_02, &size_f2)); + EXPECT_EQ(30ll, size_f2); + + FilePath subsubdir_path = subdir_path.Append(FPL("Level3")); + CreateDirectory(subsubdir_path); + + FilePath file_03 = subsubdir_path.Append(FPL("The file 03.txt")); + CreateTextFile(file_03, L"123"); + + int64_t computed_size = ComputeDirectorySize(temp_dir_.path()); + EXPECT_EQ(size_f1 + size_f2 + 3, computed_size); +} + +TEST_F(FileUtilTest, NormalizeFilePathBasic) { + // Create a directory under the test dir. Because we create it, + // we know it is not a link. + FilePath file_a_path = temp_dir_.path().Append(FPL("file_a")); + FilePath dir_path = temp_dir_.path().Append(FPL("dir")); + FilePath file_b_path = dir_path.Append(FPL("file_b")); + CreateDirectory(dir_path); + + FilePath normalized_file_a_path, normalized_file_b_path; + ASSERT_FALSE(PathExists(file_a_path)); + ASSERT_FALSE(NormalizeFilePath(file_a_path, &normalized_file_a_path)) + << "NormalizeFilePath() should fail on nonexistent paths."; + + CreateTextFile(file_a_path, bogus_content); + ASSERT_TRUE(PathExists(file_a_path)); + ASSERT_TRUE(NormalizeFilePath(file_a_path, &normalized_file_a_path)); + + CreateTextFile(file_b_path, bogus_content); + ASSERT_TRUE(PathExists(file_b_path)); + ASSERT_TRUE(NormalizeFilePath(file_b_path, &normalized_file_b_path)); + + // Beacuse this test created |dir_path|, we know it is not a link + // or junction. So, the real path of the directory holding file a + // must be the parent of the path holding file b. + ASSERT_TRUE(normalized_file_a_path.DirName() + .IsParent(normalized_file_b_path.DirName())); +} + +#if defined(OS_WIN) + +TEST_F(FileUtilTest, NormalizeFilePathReparsePoints) { + // Build the following directory structure: + // + // temp_dir + // |-> base_a + // | |-> sub_a + // | |-> file.txt + // | |-> long_name___... (Very long name.) + // | |-> sub_long + // | |-> deep.txt + // |-> base_b + // |-> to_sub_a (reparse point to temp_dir\base_a\sub_a) + // |-> to_base_b (reparse point to temp_dir\base_b) + // |-> to_sub_long (reparse point to temp_dir\sub_a\long_name_\sub_long) + + FilePath base_a = temp_dir_.path().Append(FPL("base_a")); + ASSERT_TRUE(CreateDirectory(base_a)); + + FilePath sub_a = base_a.Append(FPL("sub_a")); + ASSERT_TRUE(CreateDirectory(sub_a)); + + FilePath file_txt = sub_a.Append(FPL("file.txt")); + CreateTextFile(file_txt, bogus_content); + + // Want a directory whose name is long enough to make the path to the file + // inside just under MAX_PATH chars. This will be used to test that when + // a junction expands to a path over MAX_PATH chars in length, + // NormalizeFilePath() fails without crashing. + FilePath sub_long_rel(FPL("sub_long")); + FilePath deep_txt(FPL("deep.txt")); + + int target_length = MAX_PATH; + target_length -= (sub_a.value().length() + 1); // +1 for the sepperator '\'. + target_length -= (sub_long_rel.Append(deep_txt).value().length() + 1); + // Without making the path a bit shorter, CreateDirectory() fails. + // the resulting path is still long enough to hit the failing case in + // NormalizePath(). + const int kCreateDirLimit = 4; + target_length -= kCreateDirLimit; + FilePath::StringType long_name_str = FPL("long_name_"); + long_name_str.resize(target_length, '_'); + + FilePath long_name = sub_a.Append(FilePath(long_name_str)); + FilePath deep_file = long_name.Append(sub_long_rel).Append(deep_txt); + ASSERT_EQ(MAX_PATH - kCreateDirLimit, deep_file.value().length()); + + FilePath sub_long = deep_file.DirName(); + ASSERT_TRUE(CreateDirectory(sub_long)); + CreateTextFile(deep_file, bogus_content); + + FilePath base_b = temp_dir_.path().Append(FPL("base_b")); + ASSERT_TRUE(CreateDirectory(base_b)); + + FilePath to_sub_a = base_b.Append(FPL("to_sub_a")); + ASSERT_TRUE(CreateDirectory(to_sub_a)); + FilePath normalized_path; + { + ReparsePoint reparse_to_sub_a(to_sub_a, sub_a); + ASSERT_TRUE(reparse_to_sub_a.IsValid()); + + FilePath to_base_b = base_b.Append(FPL("to_base_b")); + ASSERT_TRUE(CreateDirectory(to_base_b)); + ReparsePoint reparse_to_base_b(to_base_b, base_b); + ASSERT_TRUE(reparse_to_base_b.IsValid()); + + FilePath to_sub_long = base_b.Append(FPL("to_sub_long")); + ASSERT_TRUE(CreateDirectory(to_sub_long)); + ReparsePoint reparse_to_sub_long(to_sub_long, sub_long); + ASSERT_TRUE(reparse_to_sub_long.IsValid()); + + // Normalize a junction free path: base_a\sub_a\file.txt . + ASSERT_TRUE(NormalizeFilePath(file_txt, &normalized_path)); + ASSERT_STREQ(file_txt.value().c_str(), normalized_path.value().c_str()); + + // Check that the path base_b\to_sub_a\file.txt can be normalized to exclude + // the junction to_sub_a. + ASSERT_TRUE(NormalizeFilePath(to_sub_a.Append(FPL("file.txt")), + &normalized_path)); + ASSERT_STREQ(file_txt.value().c_str(), normalized_path.value().c_str()); + + // Check that the path base_b\to_base_b\to_base_b\to_sub_a\file.txt can be + // normalized to exclude junctions to_base_b and to_sub_a . + ASSERT_TRUE(NormalizeFilePath(base_b.Append(FPL("to_base_b")) + .Append(FPL("to_base_b")) + .Append(FPL("to_sub_a")) + .Append(FPL("file.txt")), + &normalized_path)); + ASSERT_STREQ(file_txt.value().c_str(), normalized_path.value().c_str()); + + // A long enough path will cause NormalizeFilePath() to fail. Make a long + // path using to_base_b many times, and check that paths long enough to fail + // do not cause a crash. + FilePath long_path = base_b; + const int kLengthLimit = MAX_PATH + 200; + while (long_path.value().length() <= kLengthLimit) { + long_path = long_path.Append(FPL("to_base_b")); + } + long_path = long_path.Append(FPL("to_sub_a")) + .Append(FPL("file.txt")); + + ASSERT_FALSE(NormalizeFilePath(long_path, &normalized_path)); + + // Normalizing the junction to deep.txt should fail, because the expanded + // path to deep.txt is longer than MAX_PATH. + ASSERT_FALSE(NormalizeFilePath(to_sub_long.Append(deep_txt), + &normalized_path)); + + // Delete the reparse points, and see that NormalizeFilePath() fails + // to traverse them. + } + + ASSERT_FALSE(NormalizeFilePath(to_sub_a.Append(FPL("file.txt")), + &normalized_path)); +} + +TEST_F(FileUtilTest, DevicePathToDriveLetter) { + // Get a drive letter. + std::wstring real_drive_letter = temp_dir_.path().value().substr(0, 2); + if (!isalpha(real_drive_letter[0]) || ':' != real_drive_letter[1]) { + LOG(ERROR) << "Can't get a drive letter to test with."; + return; + } + + // Get the NT style path to that drive. + wchar_t device_path[MAX_PATH] = {'\0'}; + ASSERT_TRUE( + ::QueryDosDevice(real_drive_letter.c_str(), device_path, MAX_PATH)); + FilePath actual_device_path(device_path); + FilePath win32_path; + + // Run DevicePathToDriveLetterPath() on the NT style path we got from + // QueryDosDevice(). Expect the drive letter we started with. + ASSERT_TRUE(DevicePathToDriveLetterPath(actual_device_path, &win32_path)); + ASSERT_EQ(real_drive_letter, win32_path.value()); + + // Add some directories to the path. Expect those extra path componenets + // to be preserved. + FilePath kRelativePath(FPL("dir1\\dir2\\file.txt")); + ASSERT_TRUE(DevicePathToDriveLetterPath( + actual_device_path.Append(kRelativePath), + &win32_path)); + EXPECT_EQ(FilePath(real_drive_letter + L"\\").Append(kRelativePath).value(), + win32_path.value()); + + // Deform the real path so that it is invalid by removing the last four + // characters. The way windows names devices that are hard disks + // (\Device\HardDiskVolume${NUMBER}) guarantees that the string is longer + // than three characters. The only way the truncated string could be a + // real drive is if more than 10^3 disks are mounted: + // \Device\HardDiskVolume10000 would be truncated to \Device\HardDiskVolume1 + // Check that DevicePathToDriveLetterPath fails. + int path_length = actual_device_path.value().length(); + int new_length = path_length - 4; + ASSERT_LT(0, new_length); + FilePath prefix_of_real_device_path( + actual_device_path.value().substr(0, new_length)); + ASSERT_FALSE(DevicePathToDriveLetterPath(prefix_of_real_device_path, + &win32_path)); + + ASSERT_FALSE(DevicePathToDriveLetterPath( + prefix_of_real_device_path.Append(kRelativePath), + &win32_path)); + + // Deform the real path so that it is invalid by adding some characters. For + // example, if C: maps to \Device\HardDiskVolume8, then we simulate a + // request for the drive letter whose native path is + // \Device\HardDiskVolume812345 . We assume such a device does not exist, + // because drives are numbered in order and mounting 112345 hard disks will + // never happen. + const FilePath::StringType kExtraChars = FPL("12345"); + + FilePath real_device_path_plus_numbers( + actual_device_path.value() + kExtraChars); + + ASSERT_FALSE(DevicePathToDriveLetterPath( + real_device_path_plus_numbers, + &win32_path)); + + ASSERT_FALSE(DevicePathToDriveLetterPath( + real_device_path_plus_numbers.Append(kRelativePath), + &win32_path)); +} + +TEST_F(FileUtilTest, CreateTemporaryFileInDirLongPathTest) { + // Test that CreateTemporaryFileInDir() creates a path and returns a long path + // if it is available. This test requires that: + // - the filesystem at |temp_dir_| supports long filenames. + // - the account has FILE_LIST_DIRECTORY permission for all ancestor + // directories of |temp_dir_|. + const FilePath::CharType kLongDirName[] = FPL("A long path"); + const FilePath::CharType kTestSubDirName[] = FPL("test"); + FilePath long_test_dir = temp_dir_.path().Append(kLongDirName); + ASSERT_TRUE(CreateDirectory(long_test_dir)); + + // kLongDirName is not a 8.3 component. So GetShortName() should give us a + // different short name. + WCHAR path_buffer[MAX_PATH]; + DWORD path_buffer_length = GetShortPathName(long_test_dir.value().c_str(), + path_buffer, MAX_PATH); + ASSERT_LT(path_buffer_length, DWORD(MAX_PATH)); + ASSERT_NE(DWORD(0), path_buffer_length); + FilePath short_test_dir(path_buffer); + ASSERT_STRNE(kLongDirName, short_test_dir.BaseName().value().c_str()); + + FilePath temp_file; + ASSERT_TRUE(CreateTemporaryFileInDir(short_test_dir, &temp_file)); + EXPECT_STREQ(kLongDirName, temp_file.DirName().BaseName().value().c_str()); + EXPECT_TRUE(PathExists(temp_file)); + + // Create a subdirectory of |long_test_dir| and make |long_test_dir| + // unreadable. We should still be able to create a temp file in the + // subdirectory, but we won't be able to determine the long path for it. This + // mimics the environment that some users run where their user profiles reside + // in a location where the don't have full access to the higher level + // directories. (Note that this assumption is true for NTFS, but not for some + // network file systems. E.g. AFS). + FilePath access_test_dir = long_test_dir.Append(kTestSubDirName); + ASSERT_TRUE(CreateDirectory(access_test_dir)); + file_util::PermissionRestorer long_test_dir_restorer(long_test_dir); + ASSERT_TRUE(file_util::MakeFileUnreadable(long_test_dir)); + + // Use the short form of the directory to create a temporary filename. + ASSERT_TRUE(CreateTemporaryFileInDir( + short_test_dir.Append(kTestSubDirName), &temp_file)); + EXPECT_TRUE(PathExists(temp_file)); + EXPECT_TRUE(short_test_dir.IsParent(temp_file.DirName())); + + // Check that the long path can't be determined for |temp_file|. + path_buffer_length = GetLongPathName(temp_file.value().c_str(), + path_buffer, MAX_PATH); + EXPECT_EQ(DWORD(0), path_buffer_length); +} + +#endif // defined(OS_WIN) + +#if defined(OS_POSIX) + +TEST_F(FileUtilTest, CreateAndReadSymlinks) { + FilePath link_from = temp_dir_.path().Append(FPL("from_file")); + FilePath link_to = temp_dir_.path().Append(FPL("to_file")); + CreateTextFile(link_to, bogus_content); + + ASSERT_TRUE(CreateSymbolicLink(link_to, link_from)) + << "Failed to create file symlink."; + + // If we created the link properly, we should be able to read the contents + // through it. + std::wstring contents = ReadTextFile(link_from); + EXPECT_EQ(bogus_content, contents); + + FilePath result; + ASSERT_TRUE(ReadSymbolicLink(link_from, &result)); + EXPECT_EQ(link_to.value(), result.value()); + + // Link to a directory. + link_from = temp_dir_.path().Append(FPL("from_dir")); + link_to = temp_dir_.path().Append(FPL("to_dir")); + ASSERT_TRUE(CreateDirectory(link_to)); + ASSERT_TRUE(CreateSymbolicLink(link_to, link_from)) + << "Failed to create directory symlink."; + + // Test failures. + EXPECT_FALSE(CreateSymbolicLink(link_to, link_to)); + EXPECT_FALSE(ReadSymbolicLink(link_to, &result)); + FilePath missing = temp_dir_.path().Append(FPL("missing")); + EXPECT_FALSE(ReadSymbolicLink(missing, &result)); +} + +// The following test of NormalizeFilePath() require that we create a symlink. +// This can not be done on Windows before Vista. On Vista, creating a symlink +// requires privilege "SeCreateSymbolicLinkPrivilege". +// TODO(skerner): Investigate the possibility of giving base_unittests the +// privileges required to create a symlink. +TEST_F(FileUtilTest, NormalizeFilePathSymlinks) { + // Link one file to another. + FilePath link_from = temp_dir_.path().Append(FPL("from_file")); + FilePath link_to = temp_dir_.path().Append(FPL("to_file")); + CreateTextFile(link_to, bogus_content); + + ASSERT_TRUE(CreateSymbolicLink(link_to, link_from)) + << "Failed to create file symlink."; + + // Check that NormalizeFilePath sees the link. + FilePath normalized_path; + ASSERT_TRUE(NormalizeFilePath(link_from, &normalized_path)); + EXPECT_NE(link_from, link_to); + EXPECT_EQ(link_to.BaseName().value(), normalized_path.BaseName().value()); + EXPECT_EQ(link_to.BaseName().value(), normalized_path.BaseName().value()); + + // Link to a directory. + link_from = temp_dir_.path().Append(FPL("from_dir")); + link_to = temp_dir_.path().Append(FPL("to_dir")); + ASSERT_TRUE(CreateDirectory(link_to)); + ASSERT_TRUE(CreateSymbolicLink(link_to, link_from)) + << "Failed to create directory symlink."; + + EXPECT_FALSE(NormalizeFilePath(link_from, &normalized_path)) + << "Links to directories should return false."; + + // Test that a loop in the links causes NormalizeFilePath() to return false. + link_from = temp_dir_.path().Append(FPL("link_a")); + link_to = temp_dir_.path().Append(FPL("link_b")); + ASSERT_TRUE(CreateSymbolicLink(link_to, link_from)) + << "Failed to create loop symlink a."; + ASSERT_TRUE(CreateSymbolicLink(link_from, link_to)) + << "Failed to create loop symlink b."; + + // Infinite loop! + EXPECT_FALSE(NormalizeFilePath(link_from, &normalized_path)); +} +#endif // defined(OS_POSIX) + +TEST_F(FileUtilTest, DeleteNonExistent) { + FilePath non_existent = temp_dir_.path().AppendASCII("bogus_file_dne.foobar"); + ASSERT_FALSE(PathExists(non_existent)); + + EXPECT_TRUE(DeleteFile(non_existent, false)); + ASSERT_FALSE(PathExists(non_existent)); + EXPECT_TRUE(DeleteFile(non_existent, true)); + ASSERT_FALSE(PathExists(non_existent)); +} + +TEST_F(FileUtilTest, DeleteNonExistentWithNonExistentParent) { + FilePath non_existent = temp_dir_.path().AppendASCII("bogus_topdir"); + non_existent = non_existent.AppendASCII("bogus_subdir"); + ASSERT_FALSE(PathExists(non_existent)); + + EXPECT_TRUE(DeleteFile(non_existent, false)); + ASSERT_FALSE(PathExists(non_existent)); + EXPECT_TRUE(DeleteFile(non_existent, true)); + ASSERT_FALSE(PathExists(non_existent)); +} + +TEST_F(FileUtilTest, DeleteFile) { + // Create a file + FilePath file_name = temp_dir_.path().Append(FPL("Test DeleteFile 1.txt")); + CreateTextFile(file_name, bogus_content); + ASSERT_TRUE(PathExists(file_name)); + + // Make sure it's deleted + EXPECT_TRUE(DeleteFile(file_name, false)); + EXPECT_FALSE(PathExists(file_name)); + + // Test recursive case, create a new file + file_name = temp_dir_.path().Append(FPL("Test DeleteFile 2.txt")); + CreateTextFile(file_name, bogus_content); + ASSERT_TRUE(PathExists(file_name)); + + // Make sure it's deleted + EXPECT_TRUE(DeleteFile(file_name, true)); + EXPECT_FALSE(PathExists(file_name)); +} + +#if defined(OS_POSIX) +TEST_F(FileUtilTest, DeleteSymlinkToExistentFile) { + // Create a file. + FilePath file_name = temp_dir_.path().Append(FPL("Test DeleteFile 2.txt")); + CreateTextFile(file_name, bogus_content); + ASSERT_TRUE(PathExists(file_name)); + + // Create a symlink to the file. + FilePath file_link = temp_dir_.path().Append("file_link_2"); + ASSERT_TRUE(CreateSymbolicLink(file_name, file_link)) + << "Failed to create symlink."; + + // Delete the symbolic link. + EXPECT_TRUE(DeleteFile(file_link, false)); + + // Make sure original file is not deleted. + EXPECT_FALSE(PathExists(file_link)); + EXPECT_TRUE(PathExists(file_name)); +} + +TEST_F(FileUtilTest, DeleteSymlinkToNonExistentFile) { + // Create a non-existent file path. + FilePath non_existent = temp_dir_.path().Append(FPL("Test DeleteFile 3.txt")); + EXPECT_FALSE(PathExists(non_existent)); + + // Create a symlink to the non-existent file. + FilePath file_link = temp_dir_.path().Append("file_link_3"); + ASSERT_TRUE(CreateSymbolicLink(non_existent, file_link)) + << "Failed to create symlink."; + + // Make sure the symbolic link is exist. + EXPECT_TRUE(IsLink(file_link)); + EXPECT_FALSE(PathExists(file_link)); + + // Delete the symbolic link. + EXPECT_TRUE(DeleteFile(file_link, false)); + + // Make sure the symbolic link is deleted. + EXPECT_FALSE(IsLink(file_link)); +} + +TEST_F(FileUtilTest, ChangeFilePermissionsAndRead) { + // Create a file path. + FilePath file_name = temp_dir_.path().Append(FPL("Test Readable File.txt")); + EXPECT_FALSE(PathExists(file_name)); + + const std::string kData("hello"); + + int buffer_size = kData.length(); + char* buffer = new char[buffer_size]; + + // Write file. + EXPECT_EQ(static_cast(kData.length()), + WriteFile(file_name, kData.data(), kData.length())); + EXPECT_TRUE(PathExists(file_name)); + + // Make sure the file is readable. + int32_t mode = 0; + EXPECT_TRUE(GetPosixFilePermissions(file_name, &mode)); + EXPECT_TRUE(mode & FILE_PERMISSION_READ_BY_USER); + + // Get rid of the read permission. + EXPECT_TRUE(SetPosixFilePermissions(file_name, 0u)); + EXPECT_TRUE(GetPosixFilePermissions(file_name, &mode)); + EXPECT_FALSE(mode & FILE_PERMISSION_READ_BY_USER); + // Make sure the file can't be read. + EXPECT_EQ(-1, ReadFile(file_name, buffer, buffer_size)); + + // Give the read permission. + EXPECT_TRUE(SetPosixFilePermissions(file_name, FILE_PERMISSION_READ_BY_USER)); + EXPECT_TRUE(GetPosixFilePermissions(file_name, &mode)); + EXPECT_TRUE(mode & FILE_PERMISSION_READ_BY_USER); + // Make sure the file can be read. + EXPECT_EQ(static_cast(kData.length()), + ReadFile(file_name, buffer, buffer_size)); + + // Delete the file. + EXPECT_TRUE(DeleteFile(file_name, false)); + EXPECT_FALSE(PathExists(file_name)); + + delete[] buffer; +} + +TEST_F(FileUtilTest, ChangeFilePermissionsAndWrite) { + // Create a file path. + FilePath file_name = temp_dir_.path().Append(FPL("Test Readable File.txt")); + EXPECT_FALSE(PathExists(file_name)); + + const std::string kData("hello"); + + // Write file. + EXPECT_EQ(static_cast(kData.length()), + WriteFile(file_name, kData.data(), kData.length())); + EXPECT_TRUE(PathExists(file_name)); + + // Make sure the file is writable. + int mode = 0; + EXPECT_TRUE(GetPosixFilePermissions(file_name, &mode)); + EXPECT_TRUE(mode & FILE_PERMISSION_WRITE_BY_USER); + EXPECT_TRUE(PathIsWritable(file_name)); + + // Get rid of the write permission. + EXPECT_TRUE(SetPosixFilePermissions(file_name, 0u)); + EXPECT_TRUE(GetPosixFilePermissions(file_name, &mode)); + EXPECT_FALSE(mode & FILE_PERMISSION_WRITE_BY_USER); + // Make sure the file can't be write. + EXPECT_EQ(-1, WriteFile(file_name, kData.data(), kData.length())); + if (!is_root_) { + EXPECT_FALSE(PathIsWritable(file_name)); + } + + // Give read permission. + EXPECT_TRUE(SetPosixFilePermissions(file_name, + FILE_PERMISSION_WRITE_BY_USER)); + EXPECT_TRUE(GetPosixFilePermissions(file_name, &mode)); + EXPECT_TRUE(mode & FILE_PERMISSION_WRITE_BY_USER); + // Make sure the file can be write. + EXPECT_EQ(static_cast(kData.length()), + WriteFile(file_name, kData.data(), kData.length())); + EXPECT_TRUE(PathIsWritable(file_name)); + + // Delete the file. + EXPECT_TRUE(DeleteFile(file_name, false)); + EXPECT_FALSE(PathExists(file_name)); +} + +TEST_F(FileUtilTest, ChangeDirectoryPermissionsAndEnumerate) { + // Create a directory path. + FilePath subdir_path = + temp_dir_.path().Append(FPL("PermissionTest1")); + CreateDirectory(subdir_path); + ASSERT_TRUE(PathExists(subdir_path)); + + // Create a dummy file to enumerate. + FilePath file_name = subdir_path.Append(FPL("Test Readable File.txt")); + EXPECT_FALSE(PathExists(file_name)); + const std::string kData("hello"); + EXPECT_EQ(static_cast(kData.length()), + WriteFile(file_name, kData.data(), kData.length())); + EXPECT_TRUE(PathExists(file_name)); + + // Make sure the directory has the all permissions. + int mode = 0; + EXPECT_TRUE(GetPosixFilePermissions(subdir_path, &mode)); + EXPECT_EQ(FILE_PERMISSION_USER_MASK, mode & FILE_PERMISSION_USER_MASK); + + // Get rid of the permissions from the directory. + EXPECT_TRUE(SetPosixFilePermissions(subdir_path, 0u)); + EXPECT_TRUE(GetPosixFilePermissions(subdir_path, &mode)); + EXPECT_FALSE(mode & FILE_PERMISSION_USER_MASK); + + // Make sure the file in the directory can't be enumerated. + FileEnumerator f1(subdir_path, true, FileEnumerator::FILES); + EXPECT_TRUE(PathExists(subdir_path)); + FindResultCollector c1(f1); + EXPECT_EQ(0, c1.size()); + EXPECT_FALSE(GetPosixFilePermissions(file_name, &mode)); + + // Give the permissions to the directory. + EXPECT_TRUE(SetPosixFilePermissions(subdir_path, FILE_PERMISSION_USER_MASK)); + EXPECT_TRUE(GetPosixFilePermissions(subdir_path, &mode)); + EXPECT_EQ(FILE_PERMISSION_USER_MASK, mode & FILE_PERMISSION_USER_MASK); + + // Make sure the file in the directory can be enumerated. + FileEnumerator f2(subdir_path, true, FileEnumerator::FILES); + FindResultCollector c2(f2); + EXPECT_TRUE(c2.HasFile(file_name)); + EXPECT_EQ(1, c2.size()); + + // Delete the file. + EXPECT_TRUE(DeleteFile(subdir_path, true)); + EXPECT_FALSE(PathExists(subdir_path)); +} + +#endif // defined(OS_POSIX) + +#if defined(OS_WIN) +// Tests that the Delete function works for wild cards, especially +// with the recursion flag. Also coincidentally tests PathExists. +// TODO(erikkay): see if anyone's actually using this feature of the API +TEST_F(FileUtilTest, DeleteWildCard) { + // Create a file and a directory + FilePath file_name = temp_dir_.path().Append(FPL("Test DeleteWildCard.txt")); + CreateTextFile(file_name, bogus_content); + ASSERT_TRUE(PathExists(file_name)); + + FilePath subdir_path = temp_dir_.path().Append(FPL("DeleteWildCardDir")); + CreateDirectory(subdir_path); + ASSERT_TRUE(PathExists(subdir_path)); + + // Create the wildcard path + FilePath directory_contents = temp_dir_.path(); + directory_contents = directory_contents.Append(FPL("*")); + + // Delete non-recursively and check that only the file is deleted + EXPECT_TRUE(DeleteFile(directory_contents, false)); + EXPECT_FALSE(PathExists(file_name)); + EXPECT_TRUE(PathExists(subdir_path)); + + // Delete recursively and make sure all contents are deleted + EXPECT_TRUE(DeleteFile(directory_contents, true)); + EXPECT_FALSE(PathExists(file_name)); + EXPECT_FALSE(PathExists(subdir_path)); +} + +// TODO(erikkay): see if anyone's actually using this feature of the API +TEST_F(FileUtilTest, DeleteNonExistantWildCard) { + // Create a file and a directory + FilePath subdir_path = + temp_dir_.path().Append(FPL("DeleteNonExistantWildCard")); + CreateDirectory(subdir_path); + ASSERT_TRUE(PathExists(subdir_path)); + + // Create the wildcard path + FilePath directory_contents = subdir_path; + directory_contents = directory_contents.Append(FPL("*")); + + // Delete non-recursively and check nothing got deleted + EXPECT_TRUE(DeleteFile(directory_contents, false)); + EXPECT_TRUE(PathExists(subdir_path)); + + // Delete recursively and check nothing got deleted + EXPECT_TRUE(DeleteFile(directory_contents, true)); + EXPECT_TRUE(PathExists(subdir_path)); +} +#endif + +// Tests non-recursive Delete() for a directory. +TEST_F(FileUtilTest, DeleteDirNonRecursive) { + // Create a subdirectory and put a file and two directories inside. + FilePath test_subdir = temp_dir_.path().Append(FPL("DeleteDirNonRecursive")); + CreateDirectory(test_subdir); + ASSERT_TRUE(PathExists(test_subdir)); + + FilePath file_name = test_subdir.Append(FPL("Test DeleteDir.txt")); + CreateTextFile(file_name, bogus_content); + ASSERT_TRUE(PathExists(file_name)); + + FilePath subdir_path1 = test_subdir.Append(FPL("TestSubDir1")); + CreateDirectory(subdir_path1); + ASSERT_TRUE(PathExists(subdir_path1)); + + FilePath subdir_path2 = test_subdir.Append(FPL("TestSubDir2")); + CreateDirectory(subdir_path2); + ASSERT_TRUE(PathExists(subdir_path2)); + + // Delete non-recursively and check that the empty dir got deleted + EXPECT_TRUE(DeleteFile(subdir_path2, false)); + EXPECT_FALSE(PathExists(subdir_path2)); + + // Delete non-recursively and check that nothing got deleted + EXPECT_FALSE(DeleteFile(test_subdir, false)); + EXPECT_TRUE(PathExists(test_subdir)); + EXPECT_TRUE(PathExists(file_name)); + EXPECT_TRUE(PathExists(subdir_path1)); +} + +// Tests recursive Delete() for a directory. +TEST_F(FileUtilTest, DeleteDirRecursive) { + // Create a subdirectory and put a file and two directories inside. + FilePath test_subdir = temp_dir_.path().Append(FPL("DeleteDirRecursive")); + CreateDirectory(test_subdir); + ASSERT_TRUE(PathExists(test_subdir)); + + FilePath file_name = test_subdir.Append(FPL("Test DeleteDirRecursive.txt")); + CreateTextFile(file_name, bogus_content); + ASSERT_TRUE(PathExists(file_name)); + + FilePath subdir_path1 = test_subdir.Append(FPL("TestSubDir1")); + CreateDirectory(subdir_path1); + ASSERT_TRUE(PathExists(subdir_path1)); + + FilePath subdir_path2 = test_subdir.Append(FPL("TestSubDir2")); + CreateDirectory(subdir_path2); + ASSERT_TRUE(PathExists(subdir_path2)); + + // Delete recursively and check that the empty dir got deleted + EXPECT_TRUE(DeleteFile(subdir_path2, true)); + EXPECT_FALSE(PathExists(subdir_path2)); + + // Delete recursively and check that everything got deleted + EXPECT_TRUE(DeleteFile(test_subdir, true)); + EXPECT_FALSE(PathExists(file_name)); + EXPECT_FALSE(PathExists(subdir_path1)); + EXPECT_FALSE(PathExists(test_subdir)); +} + +TEST_F(FileUtilTest, MoveFileNew) { + // Create a file + FilePath file_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Move_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(PathExists(file_name_from)); + + // The destination. + FilePath file_name_to = temp_dir_.path().Append( + FILE_PATH_LITERAL("Move_Test_File_Destination.txt")); + ASSERT_FALSE(PathExists(file_name_to)); + + EXPECT_TRUE(Move(file_name_from, file_name_to)); + + // Check everything has been moved. + EXPECT_FALSE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(file_name_to)); +} + +TEST_F(FileUtilTest, MoveFileExists) { + // Create a file + FilePath file_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Move_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(PathExists(file_name_from)); + + // The destination name. + FilePath file_name_to = temp_dir_.path().Append( + FILE_PATH_LITERAL("Move_Test_File_Destination.txt")); + CreateTextFile(file_name_to, L"Old file content"); + ASSERT_TRUE(PathExists(file_name_to)); + + EXPECT_TRUE(Move(file_name_from, file_name_to)); + + // Check everything has been moved. + EXPECT_FALSE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(file_name_to)); + EXPECT_TRUE(L"Gooooooooooooooooooooogle" == ReadTextFile(file_name_to)); +} + +TEST_F(FileUtilTest, MoveFileDirExists) { + // Create a file + FilePath file_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Move_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(PathExists(file_name_from)); + + // The destination directory + FilePath dir_name_to = + temp_dir_.path().Append(FILE_PATH_LITERAL("Destination")); + CreateDirectory(dir_name_to); + ASSERT_TRUE(PathExists(dir_name_to)); + + EXPECT_FALSE(Move(file_name_from, dir_name_to)); +} + + +TEST_F(FileUtilTest, MoveNew) { + // Create a directory + FilePath dir_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Move_From_Subdir")); + CreateDirectory(dir_name_from); + ASSERT_TRUE(PathExists(dir_name_from)); + + // Create a file under the directory + FilePath txt_file_name(FILE_PATH_LITERAL("Move_Test_File.txt")); + FilePath file_name_from = dir_name_from.Append(txt_file_name); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(PathExists(file_name_from)); + + // Move the directory. + FilePath dir_name_to = + temp_dir_.path().Append(FILE_PATH_LITERAL("Move_To_Subdir")); + FilePath file_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Move_Test_File.txt")); + + ASSERT_FALSE(PathExists(dir_name_to)); + + EXPECT_TRUE(Move(dir_name_from, dir_name_to)); + + // Check everything has been moved. + EXPECT_FALSE(PathExists(dir_name_from)); + EXPECT_FALSE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(dir_name_to)); + EXPECT_TRUE(PathExists(file_name_to)); + + // Test path traversal. + file_name_from = dir_name_to.Append(txt_file_name); + file_name_to = dir_name_to.Append(FILE_PATH_LITERAL("..")); + file_name_to = file_name_to.Append(txt_file_name); + EXPECT_FALSE(Move(file_name_from, file_name_to)); + EXPECT_TRUE(PathExists(file_name_from)); + EXPECT_FALSE(PathExists(file_name_to)); + EXPECT_TRUE(internal::MoveUnsafe(file_name_from, file_name_to)); + EXPECT_FALSE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(file_name_to)); +} + +TEST_F(FileUtilTest, MoveExist) { + // Create a directory + FilePath dir_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Move_From_Subdir")); + CreateDirectory(dir_name_from); + ASSERT_TRUE(PathExists(dir_name_from)); + + // Create a file under the directory + FilePath file_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Move_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(PathExists(file_name_from)); + + // Move the directory + FilePath dir_name_exists = + temp_dir_.path().Append(FILE_PATH_LITERAL("Destination")); + + FilePath dir_name_to = + dir_name_exists.Append(FILE_PATH_LITERAL("Move_To_Subdir")); + FilePath file_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Move_Test_File.txt")); + + // Create the destination directory. + CreateDirectory(dir_name_exists); + ASSERT_TRUE(PathExists(dir_name_exists)); + + EXPECT_TRUE(Move(dir_name_from, dir_name_to)); + + // Check everything has been moved. + EXPECT_FALSE(PathExists(dir_name_from)); + EXPECT_FALSE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(dir_name_to)); + EXPECT_TRUE(PathExists(file_name_to)); +} + +TEST_F(FileUtilTest, CopyDirectoryRecursivelyNew) { + // Create a directory. + FilePath dir_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir")); + CreateDirectory(dir_name_from); + ASSERT_TRUE(PathExists(dir_name_from)); + + // Create a file under the directory. + FilePath file_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(PathExists(file_name_from)); + + // Create a subdirectory. + FilePath subdir_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Subdir")); + CreateDirectory(subdir_name_from); + ASSERT_TRUE(PathExists(subdir_name_from)); + + // Create a file under the subdirectory. + FilePath file_name2_from = + subdir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name2_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(PathExists(file_name2_from)); + + // Copy the directory recursively. + FilePath dir_name_to = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_To_Subdir")); + FilePath file_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + FilePath subdir_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Subdir")); + FilePath file_name2_to = + subdir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + + ASSERT_FALSE(PathExists(dir_name_to)); + + EXPECT_TRUE(CopyDirectory(dir_name_from, dir_name_to, true)); + + // Check everything has been copied. + EXPECT_TRUE(PathExists(dir_name_from)); + EXPECT_TRUE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(subdir_name_from)); + EXPECT_TRUE(PathExists(file_name2_from)); + EXPECT_TRUE(PathExists(dir_name_to)); + EXPECT_TRUE(PathExists(file_name_to)); + EXPECT_TRUE(PathExists(subdir_name_to)); + EXPECT_TRUE(PathExists(file_name2_to)); +} + +TEST_F(FileUtilTest, CopyDirectoryRecursivelyExists) { + // Create a directory. + FilePath dir_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir")); + CreateDirectory(dir_name_from); + ASSERT_TRUE(PathExists(dir_name_from)); + + // Create a file under the directory. + FilePath file_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(PathExists(file_name_from)); + + // Create a subdirectory. + FilePath subdir_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Subdir")); + CreateDirectory(subdir_name_from); + ASSERT_TRUE(PathExists(subdir_name_from)); + + // Create a file under the subdirectory. + FilePath file_name2_from = + subdir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name2_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(PathExists(file_name2_from)); + + // Copy the directory recursively. + FilePath dir_name_exists = + temp_dir_.path().Append(FILE_PATH_LITERAL("Destination")); + + FilePath dir_name_to = + dir_name_exists.Append(FILE_PATH_LITERAL("Copy_From_Subdir")); + FilePath file_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + FilePath subdir_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Subdir")); + FilePath file_name2_to = + subdir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + + // Create the destination directory. + CreateDirectory(dir_name_exists); + ASSERT_TRUE(PathExists(dir_name_exists)); + + EXPECT_TRUE(CopyDirectory(dir_name_from, dir_name_exists, true)); + + // Check everything has been copied. + EXPECT_TRUE(PathExists(dir_name_from)); + EXPECT_TRUE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(subdir_name_from)); + EXPECT_TRUE(PathExists(file_name2_from)); + EXPECT_TRUE(PathExists(dir_name_to)); + EXPECT_TRUE(PathExists(file_name_to)); + EXPECT_TRUE(PathExists(subdir_name_to)); + EXPECT_TRUE(PathExists(file_name2_to)); +} + +TEST_F(FileUtilTest, CopyDirectoryNew) { + // Create a directory. + FilePath dir_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir")); + CreateDirectory(dir_name_from); + ASSERT_TRUE(PathExists(dir_name_from)); + + // Create a file under the directory. + FilePath file_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(PathExists(file_name_from)); + + // Create a subdirectory. + FilePath subdir_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Subdir")); + CreateDirectory(subdir_name_from); + ASSERT_TRUE(PathExists(subdir_name_from)); + + // Create a file under the subdirectory. + FilePath file_name2_from = + subdir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name2_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(PathExists(file_name2_from)); + + // Copy the directory not recursively. + FilePath dir_name_to = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_To_Subdir")); + FilePath file_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + FilePath subdir_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Subdir")); + + ASSERT_FALSE(PathExists(dir_name_to)); + + EXPECT_TRUE(CopyDirectory(dir_name_from, dir_name_to, false)); + + // Check everything has been copied. + EXPECT_TRUE(PathExists(dir_name_from)); + EXPECT_TRUE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(subdir_name_from)); + EXPECT_TRUE(PathExists(file_name2_from)); + EXPECT_TRUE(PathExists(dir_name_to)); + EXPECT_TRUE(PathExists(file_name_to)); + EXPECT_FALSE(PathExists(subdir_name_to)); +} + +TEST_F(FileUtilTest, CopyDirectoryExists) { + // Create a directory. + FilePath dir_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir")); + CreateDirectory(dir_name_from); + ASSERT_TRUE(PathExists(dir_name_from)); + + // Create a file under the directory. + FilePath file_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(PathExists(file_name_from)); + + // Create a subdirectory. + FilePath subdir_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Subdir")); + CreateDirectory(subdir_name_from); + ASSERT_TRUE(PathExists(subdir_name_from)); + + // Create a file under the subdirectory. + FilePath file_name2_from = + subdir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name2_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(PathExists(file_name2_from)); + + // Copy the directory not recursively. + FilePath dir_name_to = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_To_Subdir")); + FilePath file_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + FilePath subdir_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Subdir")); + + // Create the destination directory. + CreateDirectory(dir_name_to); + ASSERT_TRUE(PathExists(dir_name_to)); + + EXPECT_TRUE(CopyDirectory(dir_name_from, dir_name_to, false)); + + // Check everything has been copied. + EXPECT_TRUE(PathExists(dir_name_from)); + EXPECT_TRUE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(subdir_name_from)); + EXPECT_TRUE(PathExists(file_name2_from)); + EXPECT_TRUE(PathExists(dir_name_to)); + EXPECT_TRUE(PathExists(file_name_to)); + EXPECT_FALSE(PathExists(subdir_name_to)); +} + +TEST_F(FileUtilTest, CopyFileWithCopyDirectoryRecursiveToNew) { + // Create a file + FilePath file_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(PathExists(file_name_from)); + + // The destination name + FilePath file_name_to = temp_dir_.path().Append( + FILE_PATH_LITERAL("Copy_Test_File_Destination.txt")); + ASSERT_FALSE(PathExists(file_name_to)); + + EXPECT_TRUE(CopyDirectory(file_name_from, file_name_to, true)); + + // Check the has been copied + EXPECT_TRUE(PathExists(file_name_to)); +} + +TEST_F(FileUtilTest, CopyFileWithCopyDirectoryRecursiveToExisting) { + // Create a file + FilePath file_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(PathExists(file_name_from)); + + // The destination name + FilePath file_name_to = temp_dir_.path().Append( + FILE_PATH_LITERAL("Copy_Test_File_Destination.txt")); + CreateTextFile(file_name_to, L"Old file content"); + ASSERT_TRUE(PathExists(file_name_to)); + + EXPECT_TRUE(CopyDirectory(file_name_from, file_name_to, true)); + + // Check the has been copied + EXPECT_TRUE(PathExists(file_name_to)); + EXPECT_TRUE(L"Gooooooooooooooooooooogle" == ReadTextFile(file_name_to)); +} + +TEST_F(FileUtilTest, CopyFileWithCopyDirectoryRecursiveToExistingDirectory) { + // Create a file + FilePath file_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(PathExists(file_name_from)); + + // The destination + FilePath dir_name_to = + temp_dir_.path().Append(FILE_PATH_LITERAL("Destination")); + CreateDirectory(dir_name_to); + ASSERT_TRUE(PathExists(dir_name_to)); + FilePath file_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + + EXPECT_TRUE(CopyDirectory(file_name_from, dir_name_to, true)); + + // Check the has been copied + EXPECT_TRUE(PathExists(file_name_to)); +} + +TEST_F(FileUtilTest, CopyDirectoryWithTrailingSeparators) { + // Create a directory. + FilePath dir_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir")); + CreateDirectory(dir_name_from); + ASSERT_TRUE(PathExists(dir_name_from)); + + // Create a file under the directory. + FilePath file_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(PathExists(file_name_from)); + + // Copy the directory recursively. + FilePath dir_name_to = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_To_Subdir")); + FilePath file_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + + // Create from path with trailing separators. +#if defined(OS_WIN) + FilePath from_path = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir\\\\\\")); +#elif defined (OS_POSIX) + FilePath from_path = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir///")); +#endif + + EXPECT_TRUE(CopyDirectory(from_path, dir_name_to, true)); + + // Check everything has been copied. + EXPECT_TRUE(PathExists(dir_name_from)); + EXPECT_TRUE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(dir_name_to)); + EXPECT_TRUE(PathExists(file_name_to)); +} + +// Sets the source file to read-only. +void SetReadOnly(const FilePath& path) { +#if defined(OS_WIN) + // On Windows, it involves setting a bit. + DWORD attrs = GetFileAttributes(path.value().c_str()); + ASSERT_NE(INVALID_FILE_ATTRIBUTES, attrs); + ASSERT_TRUE(SetFileAttributes( + path.value().c_str(), attrs | FILE_ATTRIBUTE_READONLY)); + attrs = GetFileAttributes(path.value().c_str()); + // Files in the temporary directory should not be indexed ever. If this + // assumption change, fix this unit test accordingly. + // FILE_ATTRIBUTE_NOT_CONTENT_INDEXED doesn't exist on XP. + DWORD expected = FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_READONLY; + if (win::GetVersion() >= win::VERSION_VISTA) + expected |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + ASSERT_EQ(expected, attrs); +#else + // On all other platforms, it involves removing the write bit. + EXPECT_TRUE(SetPosixFilePermissions(path, S_IRUSR)); +#endif +} + +bool IsReadOnly(const FilePath& path) { +#if defined(OS_WIN) + DWORD attrs = GetFileAttributes(path.value().c_str()); + EXPECT_NE(INVALID_FILE_ATTRIBUTES, attrs); + return attrs & FILE_ATTRIBUTE_READONLY; +#else + int mode = 0; + EXPECT_TRUE(GetPosixFilePermissions(path, &mode)); + return !(mode & S_IWUSR); +#endif +} + +TEST_F(FileUtilTest, CopyDirectoryACL) { + // Create a directory. + FilePath src = temp_dir_.path().Append(FILE_PATH_LITERAL("src")); + CreateDirectory(src); + ASSERT_TRUE(PathExists(src)); + + // Create a file under the directory. + FilePath src_file = src.Append(FILE_PATH_LITERAL("src.txt")); + CreateTextFile(src_file, L"Gooooooooooooooooooooogle"); + SetReadOnly(src_file); + ASSERT_TRUE(IsReadOnly(src_file)); + + // Copy the directory recursively. + FilePath dst = temp_dir_.path().Append(FILE_PATH_LITERAL("dst")); + FilePath dst_file = dst.Append(FILE_PATH_LITERAL("src.txt")); + EXPECT_TRUE(CopyDirectory(src, dst, true)); + + ASSERT_FALSE(IsReadOnly(dst_file)); +} + +TEST_F(FileUtilTest, CopyFile) { + // Create a directory + FilePath dir_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir")); + CreateDirectory(dir_name_from); + ASSERT_TRUE(PathExists(dir_name_from)); + + // Create a file under the directory + FilePath file_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + const std::wstring file_contents(L"Gooooooooooooooooooooogle"); + CreateTextFile(file_name_from, file_contents); + ASSERT_TRUE(PathExists(file_name_from)); + + // Copy the file. + FilePath dest_file = dir_name_from.Append(FILE_PATH_LITERAL("DestFile.txt")); + ASSERT_TRUE(CopyFile(file_name_from, dest_file)); + + // Copy the file to another location using '..' in the path. + FilePath dest_file2(dir_name_from); + dest_file2 = dest_file2.AppendASCII(".."); + dest_file2 = dest_file2.AppendASCII("DestFile.txt"); + ASSERT_FALSE(CopyFile(file_name_from, dest_file2)); + ASSERT_TRUE(internal::CopyFileUnsafe(file_name_from, dest_file2)); + + FilePath dest_file2_test(dir_name_from); + dest_file2_test = dest_file2_test.DirName(); + dest_file2_test = dest_file2_test.AppendASCII("DestFile.txt"); + + // Check everything has been copied. + EXPECT_TRUE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(dest_file)); + const std::wstring read_contents = ReadTextFile(dest_file); + EXPECT_EQ(file_contents, read_contents); + EXPECT_TRUE(PathExists(dest_file2_test)); + EXPECT_TRUE(PathExists(dest_file2)); +} + +TEST_F(FileUtilTest, CopyFileACL) { + // While FileUtilTest.CopyFile asserts the content is correctly copied over, + // this test case asserts the access control bits are meeting expectations in + // CopyFileUnsafe(). + FilePath src = temp_dir_.path().Append(FILE_PATH_LITERAL("src.txt")); + const std::wstring file_contents(L"Gooooooooooooooooooooogle"); + CreateTextFile(src, file_contents); + + // Set the source file to read-only. + ASSERT_FALSE(IsReadOnly(src)); + SetReadOnly(src); + ASSERT_TRUE(IsReadOnly(src)); + + // Copy the file. + FilePath dst = temp_dir_.path().Append(FILE_PATH_LITERAL("dst.txt")); + ASSERT_TRUE(CopyFile(src, dst)); + EXPECT_EQ(file_contents, ReadTextFile(dst)); + + ASSERT_FALSE(IsReadOnly(dst)); +} + +// file_util winds up using autoreleased objects on the Mac, so this needs +// to be a testing::Test. +typedef testing::Test ReadOnlyFileUtilTest; + +#if defined(FixedContentsEqual) +TEST_F(ReadOnlyFileUtilTest, ContentsEqual) { + FilePath data_dir; + ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &data_dir)); + data_dir = data_dir.AppendASCII("file_util"); + ASSERT_TRUE(PathExists(data_dir)); + + FilePath original_file = + data_dir.Append(FILE_PATH_LITERAL("original.txt")); + FilePath same_file = + data_dir.Append(FILE_PATH_LITERAL("same.txt")); + FilePath same_length_file = + data_dir.Append(FILE_PATH_LITERAL("same_length.txt")); + FilePath different_file = + data_dir.Append(FILE_PATH_LITERAL("different.txt")); + FilePath different_first_file = + data_dir.Append(FILE_PATH_LITERAL("different_first.txt")); + FilePath different_last_file = + data_dir.Append(FILE_PATH_LITERAL("different_last.txt")); + FilePath empty1_file = + data_dir.Append(FILE_PATH_LITERAL("empty1.txt")); + FilePath empty2_file = + data_dir.Append(FILE_PATH_LITERAL("empty2.txt")); + FilePath shortened_file = + data_dir.Append(FILE_PATH_LITERAL("shortened.txt")); + FilePath binary_file = + data_dir.Append(FILE_PATH_LITERAL("binary_file.bin")); + FilePath binary_file_same = + data_dir.Append(FILE_PATH_LITERAL("binary_file_same.bin")); + FilePath binary_file_diff = + data_dir.Append(FILE_PATH_LITERAL("binary_file_diff.bin")); + + EXPECT_TRUE(ContentsEqual(original_file, original_file)); + EXPECT_TRUE(ContentsEqual(original_file, same_file)); + EXPECT_FALSE(ContentsEqual(original_file, same_length_file)); + EXPECT_FALSE(ContentsEqual(original_file, different_file)); + EXPECT_FALSE(ContentsEqual(FilePath(FILE_PATH_LITERAL("bogusname")), + FilePath(FILE_PATH_LITERAL("bogusname")))); + EXPECT_FALSE(ContentsEqual(original_file, different_first_file)); + EXPECT_FALSE(ContentsEqual(original_file, different_last_file)); + EXPECT_TRUE(ContentsEqual(empty1_file, empty2_file)); + EXPECT_FALSE(ContentsEqual(original_file, shortened_file)); + EXPECT_FALSE(ContentsEqual(shortened_file, original_file)); + EXPECT_TRUE(ContentsEqual(binary_file, binary_file_same)); + EXPECT_FALSE(ContentsEqual(binary_file, binary_file_diff)); +} +#endif + +#if defined(FixedTextContentsEqual) +TEST_F(ReadOnlyFileUtilTest, TextContentsEqual) { + FilePath data_dir; + ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &data_dir)); + data_dir = data_dir.AppendASCII("file_util"); + ASSERT_TRUE(PathExists(data_dir)); + + FilePath original_file = + data_dir.Append(FILE_PATH_LITERAL("original.txt")); + FilePath same_file = + data_dir.Append(FILE_PATH_LITERAL("same.txt")); + FilePath crlf_file = + data_dir.Append(FILE_PATH_LITERAL("crlf.txt")); + FilePath shortened_file = + data_dir.Append(FILE_PATH_LITERAL("shortened.txt")); + FilePath different_file = + data_dir.Append(FILE_PATH_LITERAL("different.txt")); + FilePath different_first_file = + data_dir.Append(FILE_PATH_LITERAL("different_first.txt")); + FilePath different_last_file = + data_dir.Append(FILE_PATH_LITERAL("different_last.txt")); + FilePath first1_file = + data_dir.Append(FILE_PATH_LITERAL("first1.txt")); + FilePath first2_file = + data_dir.Append(FILE_PATH_LITERAL("first2.txt")); + FilePath empty1_file = + data_dir.Append(FILE_PATH_LITERAL("empty1.txt")); + FilePath empty2_file = + data_dir.Append(FILE_PATH_LITERAL("empty2.txt")); + FilePath blank_line_file = + data_dir.Append(FILE_PATH_LITERAL("blank_line.txt")); + FilePath blank_line_crlf_file = + data_dir.Append(FILE_PATH_LITERAL("blank_line_crlf.txt")); + + EXPECT_TRUE(TextContentsEqual(original_file, same_file)); + EXPECT_TRUE(TextContentsEqual(original_file, crlf_file)); + EXPECT_FALSE(TextContentsEqual(original_file, shortened_file)); + EXPECT_FALSE(TextContentsEqual(original_file, different_file)); + EXPECT_FALSE(TextContentsEqual(original_file, different_first_file)); + EXPECT_FALSE(TextContentsEqual(original_file, different_last_file)); + EXPECT_FALSE(TextContentsEqual(first1_file, first2_file)); + EXPECT_TRUE(TextContentsEqual(empty1_file, empty2_file)); + EXPECT_FALSE(TextContentsEqual(original_file, empty1_file)); + EXPECT_TRUE(TextContentsEqual(blank_line_file, blank_line_crlf_file)); +} +#endif + +// We don't need equivalent functionality outside of Windows. +#if defined(OS_WIN) +TEST_F(FileUtilTest, CopyAndDeleteDirectoryTest) { + // Create a directory + FilePath dir_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("CopyAndDelete_From_Subdir")); + CreateDirectory(dir_name_from); + ASSERT_TRUE(PathExists(dir_name_from)); + + // Create a file under the directory + FilePath file_name_from = + dir_name_from.Append(FILE_PATH_LITERAL("CopyAndDelete_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(PathExists(file_name_from)); + + // Move the directory by using CopyAndDeleteDirectory + FilePath dir_name_to = temp_dir_.path().Append( + FILE_PATH_LITERAL("CopyAndDelete_To_Subdir")); + FilePath file_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("CopyAndDelete_Test_File.txt")); + + ASSERT_FALSE(PathExists(dir_name_to)); + + EXPECT_TRUE(internal::CopyAndDeleteDirectory(dir_name_from, + dir_name_to)); + + // Check everything has been moved. + EXPECT_FALSE(PathExists(dir_name_from)); + EXPECT_FALSE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(dir_name_to)); + EXPECT_TRUE(PathExists(file_name_to)); +} + +TEST_F(FileUtilTest, GetTempDirTest) { + static const TCHAR* kTmpKey = _T("TMP"); + static const TCHAR* kTmpValues[] = { + _T(""), _T("C:"), _T("C:\\"), _T("C:\\tmp"), _T("C:\\tmp\\") + }; + // Save the original $TMP. + size_t original_tmp_size; + TCHAR* original_tmp; + ASSERT_EQ(0, ::_tdupenv_s(&original_tmp, &original_tmp_size, kTmpKey)); + // original_tmp may be NULL. + + for (unsigned int i = 0; i < arraysize(kTmpValues); ++i) { + FilePath path; + ::_tputenv_s(kTmpKey, kTmpValues[i]); + GetTempDir(&path); + EXPECT_TRUE(path.IsAbsolute()) << "$TMP=" << kTmpValues[i] << + " result=" << path.value(); + } + + // Restore the original $TMP. + if (original_tmp) { + ::_tputenv_s(kTmpKey, original_tmp); + free(original_tmp); + } else { + ::_tputenv_s(kTmpKey, _T("")); + } +} +#endif // OS_WIN + +TEST_F(FileUtilTest, CreateTemporaryFileTest) { + FilePath temp_files[3]; + for (int i = 0; i < 3; i++) { + ASSERT_TRUE(CreateTemporaryFile(&(temp_files[i]))); + EXPECT_TRUE(PathExists(temp_files[i])); + EXPECT_FALSE(DirectoryExists(temp_files[i])); + } + for (int i = 0; i < 3; i++) + EXPECT_FALSE(temp_files[i] == temp_files[(i+1)%3]); + for (int i = 0; i < 3; i++) + EXPECT_TRUE(DeleteFile(temp_files[i], false)); +} + +TEST_F(FileUtilTest, CreateAndOpenTemporaryFileTest) { + FilePath names[3]; + FILE* fps[3]; + int i; + + // Create; make sure they are open and exist. + for (i = 0; i < 3; ++i) { + fps[i] = CreateAndOpenTemporaryFile(&(names[i])); + ASSERT_TRUE(fps[i]); + EXPECT_TRUE(PathExists(names[i])); + } + + // Make sure all names are unique. + for (i = 0; i < 3; ++i) { + EXPECT_FALSE(names[i] == names[(i+1)%3]); + } + + // Close and delete. + for (i = 0; i < 3; ++i) { + EXPECT_TRUE(CloseFile(fps[i])); + EXPECT_TRUE(DeleteFile(names[i], false)); + } +} + +TEST_F(FileUtilTest, FileToFILE) { + File file; + FILE* stream = FileToFILE(file.Pass(), "w"); + EXPECT_FALSE(stream); + + FilePath file_name = temp_dir_.path().Append(FPL("The file.txt")); + file = File(file_name, File::FLAG_CREATE | File::FLAG_WRITE); + EXPECT_TRUE(file.IsValid()); + + stream = FileToFILE(file.Pass(), "w"); + EXPECT_TRUE(stream); + EXPECT_FALSE(file.IsValid()); + EXPECT_TRUE(CloseFile(stream)); +} + +TEST_F(FileUtilTest, CreateNewTempDirectoryTest) { + FilePath temp_dir; + ASSERT_TRUE(CreateNewTempDirectory(FilePath::StringType(), &temp_dir)); + EXPECT_TRUE(PathExists(temp_dir)); + EXPECT_TRUE(DeleteFile(temp_dir, false)); +} + +TEST_F(FileUtilTest, CreateNewTemporaryDirInDirTest) { + FilePath new_dir; + ASSERT_TRUE(CreateTemporaryDirInDir( + temp_dir_.path(), + FILE_PATH_LITERAL("CreateNewTemporaryDirInDirTest"), + &new_dir)); + EXPECT_TRUE(PathExists(new_dir)); + EXPECT_TRUE(temp_dir_.path().IsParent(new_dir)); + EXPECT_TRUE(DeleteFile(new_dir, false)); +} + +#if defined(OS_POSIX) +TEST_F(FileUtilTest, GetShmemTempDirTest) { + FilePath dir; + EXPECT_TRUE(GetShmemTempDir(false, &dir)); + EXPECT_TRUE(DirectoryExists(dir)); +} +#endif + +TEST_F(FileUtilTest, GetHomeDirTest) { +#if !defined(OS_ANDROID) // Not implemented on Android. + // We don't actually know what the home directory is supposed to be without + // calling some OS functions which would just duplicate the implementation. + // So here we just test that it returns something "reasonable". + FilePath home = GetHomeDir(); + ASSERT_FALSE(home.empty()); + ASSERT_TRUE(home.IsAbsolute()); +#endif +} + +TEST_F(FileUtilTest, CreateDirectoryTest) { + FilePath test_root = + temp_dir_.path().Append(FILE_PATH_LITERAL("create_directory_test")); +#if defined(OS_WIN) + FilePath test_path = + test_root.Append(FILE_PATH_LITERAL("dir\\tree\\likely\\doesnt\\exist\\")); +#elif defined(OS_POSIX) + FilePath test_path = + test_root.Append(FILE_PATH_LITERAL("dir/tree/likely/doesnt/exist/")); +#endif + + EXPECT_FALSE(PathExists(test_path)); + EXPECT_TRUE(CreateDirectory(test_path)); + EXPECT_TRUE(PathExists(test_path)); + // CreateDirectory returns true if the DirectoryExists returns true. + EXPECT_TRUE(CreateDirectory(test_path)); + + // Doesn't work to create it on top of a non-dir + test_path = test_path.Append(FILE_PATH_LITERAL("foobar.txt")); + EXPECT_FALSE(PathExists(test_path)); + CreateTextFile(test_path, L"test file"); + EXPECT_TRUE(PathExists(test_path)); + EXPECT_FALSE(CreateDirectory(test_path)); + + EXPECT_TRUE(DeleteFile(test_root, true)); + EXPECT_FALSE(PathExists(test_root)); + EXPECT_FALSE(PathExists(test_path)); + + // Verify assumptions made by the Windows implementation: + // 1. The current directory always exists. + // 2. The root directory always exists. + ASSERT_TRUE(DirectoryExists(FilePath(FilePath::kCurrentDirectory))); + FilePath top_level = test_root; + while (top_level != top_level.DirName()) { + top_level = top_level.DirName(); + } + ASSERT_TRUE(DirectoryExists(top_level)); + + // Given these assumptions hold, it should be safe to + // test that "creating" these directories succeeds. + EXPECT_TRUE(CreateDirectory( + FilePath(FilePath::kCurrentDirectory))); + EXPECT_TRUE(CreateDirectory(top_level)); + +#if defined(OS_WIN) + FilePath invalid_drive(FILE_PATH_LITERAL("o:\\")); + FilePath invalid_path = + invalid_drive.Append(FILE_PATH_LITERAL("some\\inaccessible\\dir")); + if (!PathExists(invalid_drive)) { + EXPECT_FALSE(CreateDirectory(invalid_path)); + } +#endif +} + +TEST_F(FileUtilTest, DetectDirectoryTest) { + // Check a directory + FilePath test_root = + temp_dir_.path().Append(FILE_PATH_LITERAL("detect_directory_test")); + EXPECT_FALSE(PathExists(test_root)); + EXPECT_TRUE(CreateDirectory(test_root)); + EXPECT_TRUE(PathExists(test_root)); + EXPECT_TRUE(DirectoryExists(test_root)); + // Check a file + FilePath test_path = + test_root.Append(FILE_PATH_LITERAL("foobar.txt")); + EXPECT_FALSE(PathExists(test_path)); + CreateTextFile(test_path, L"test file"); + EXPECT_TRUE(PathExists(test_path)); + EXPECT_FALSE(DirectoryExists(test_path)); + EXPECT_TRUE(DeleteFile(test_path, false)); + + EXPECT_TRUE(DeleteFile(test_root, true)); +} + +TEST_F(FileUtilTest, FileEnumeratorTest) { + // Test an empty directory. + FileEnumerator f0(temp_dir_.path(), true, FILES_AND_DIRECTORIES); + EXPECT_EQ(FPL(""), f0.Next().value()); + EXPECT_EQ(FPL(""), f0.Next().value()); + + // Test an empty directory, non-recursively, including "..". + FileEnumerator f0_dotdot(temp_dir_.path(), false, + FILES_AND_DIRECTORIES | FileEnumerator::INCLUDE_DOT_DOT); + EXPECT_EQ(temp_dir_.path().Append(FPL("..")).value(), + f0_dotdot.Next().value()); + EXPECT_EQ(FPL(""), f0_dotdot.Next().value()); + + // create the directories + FilePath dir1 = temp_dir_.path().Append(FPL("dir1")); + EXPECT_TRUE(CreateDirectory(dir1)); + FilePath dir2 = temp_dir_.path().Append(FPL("dir2")); + EXPECT_TRUE(CreateDirectory(dir2)); + FilePath dir2inner = dir2.Append(FPL("inner")); + EXPECT_TRUE(CreateDirectory(dir2inner)); + + // create the files + FilePath dir2file = dir2.Append(FPL("dir2file.txt")); + CreateTextFile(dir2file, std::wstring()); + FilePath dir2innerfile = dir2inner.Append(FPL("innerfile.txt")); + CreateTextFile(dir2innerfile, std::wstring()); + FilePath file1 = temp_dir_.path().Append(FPL("file1.txt")); + CreateTextFile(file1, std::wstring()); + FilePath file2_rel = dir2.Append(FilePath::kParentDirectory) + .Append(FPL("file2.txt")); + CreateTextFile(file2_rel, std::wstring()); + FilePath file2_abs = temp_dir_.path().Append(FPL("file2.txt")); + + // Only enumerate files. + FileEnumerator f1(temp_dir_.path(), true, FileEnumerator::FILES); + FindResultCollector c1(f1); + EXPECT_TRUE(c1.HasFile(file1)); + EXPECT_TRUE(c1.HasFile(file2_abs)); + EXPECT_TRUE(c1.HasFile(dir2file)); + EXPECT_TRUE(c1.HasFile(dir2innerfile)); + EXPECT_EQ(4, c1.size()); + + // Only enumerate directories. + FileEnumerator f2(temp_dir_.path(), true, FileEnumerator::DIRECTORIES); + FindResultCollector c2(f2); + EXPECT_TRUE(c2.HasFile(dir1)); + EXPECT_TRUE(c2.HasFile(dir2)); + EXPECT_TRUE(c2.HasFile(dir2inner)); + EXPECT_EQ(3, c2.size()); + + // Only enumerate directories non-recursively. + FileEnumerator f2_non_recursive( + temp_dir_.path(), false, FileEnumerator::DIRECTORIES); + FindResultCollector c2_non_recursive(f2_non_recursive); + EXPECT_TRUE(c2_non_recursive.HasFile(dir1)); + EXPECT_TRUE(c2_non_recursive.HasFile(dir2)); + EXPECT_EQ(2, c2_non_recursive.size()); + + // Only enumerate directories, non-recursively, including "..". + FileEnumerator f2_dotdot(temp_dir_.path(), false, + FileEnumerator::DIRECTORIES | + FileEnumerator::INCLUDE_DOT_DOT); + FindResultCollector c2_dotdot(f2_dotdot); + EXPECT_TRUE(c2_dotdot.HasFile(dir1)); + EXPECT_TRUE(c2_dotdot.HasFile(dir2)); + EXPECT_TRUE(c2_dotdot.HasFile(temp_dir_.path().Append(FPL("..")))); + EXPECT_EQ(3, c2_dotdot.size()); + + // Enumerate files and directories. + FileEnumerator f3(temp_dir_.path(), true, FILES_AND_DIRECTORIES); + FindResultCollector c3(f3); + EXPECT_TRUE(c3.HasFile(dir1)); + EXPECT_TRUE(c3.HasFile(dir2)); + EXPECT_TRUE(c3.HasFile(file1)); + EXPECT_TRUE(c3.HasFile(file2_abs)); + EXPECT_TRUE(c3.HasFile(dir2file)); + EXPECT_TRUE(c3.HasFile(dir2inner)); + EXPECT_TRUE(c3.HasFile(dir2innerfile)); + EXPECT_EQ(7, c3.size()); + + // Non-recursive operation. + FileEnumerator f4(temp_dir_.path(), false, FILES_AND_DIRECTORIES); + FindResultCollector c4(f4); + EXPECT_TRUE(c4.HasFile(dir2)); + EXPECT_TRUE(c4.HasFile(dir2)); + EXPECT_TRUE(c4.HasFile(file1)); + EXPECT_TRUE(c4.HasFile(file2_abs)); + EXPECT_EQ(4, c4.size()); + + // Enumerate with a pattern. + FileEnumerator f5(temp_dir_.path(), true, FILES_AND_DIRECTORIES, FPL("dir*")); + FindResultCollector c5(f5); + EXPECT_TRUE(c5.HasFile(dir1)); + EXPECT_TRUE(c5.HasFile(dir2)); + EXPECT_TRUE(c5.HasFile(dir2file)); + EXPECT_TRUE(c5.HasFile(dir2inner)); + EXPECT_TRUE(c5.HasFile(dir2innerfile)); + EXPECT_EQ(5, c5.size()); + +#if defined(OS_WIN) + { + // Make dir1 point to dir2. + ReparsePoint reparse_point(dir1, dir2); + EXPECT_TRUE(reparse_point.IsValid()); + + if ((win::GetVersion() >= win::VERSION_VISTA)) { + // There can be a delay for the enumeration code to see the change on + // the file system so skip this test for XP. + // Enumerate the reparse point. + FileEnumerator f6(dir1, true, FILES_AND_DIRECTORIES); + FindResultCollector c6(f6); + FilePath inner2 = dir1.Append(FPL("inner")); + EXPECT_TRUE(c6.HasFile(inner2)); + EXPECT_TRUE(c6.HasFile(inner2.Append(FPL("innerfile.txt")))); + EXPECT_TRUE(c6.HasFile(dir1.Append(FPL("dir2file.txt")))); + EXPECT_EQ(3, c6.size()); + } + + // No changes for non recursive operation. + FileEnumerator f7(temp_dir_.path(), false, FILES_AND_DIRECTORIES); + FindResultCollector c7(f7); + EXPECT_TRUE(c7.HasFile(dir2)); + EXPECT_TRUE(c7.HasFile(dir2)); + EXPECT_TRUE(c7.HasFile(file1)); + EXPECT_TRUE(c7.HasFile(file2_abs)); + EXPECT_EQ(4, c7.size()); + + // Should not enumerate inside dir1 when using recursion. + FileEnumerator f8(temp_dir_.path(), true, FILES_AND_DIRECTORIES); + FindResultCollector c8(f8); + EXPECT_TRUE(c8.HasFile(dir1)); + EXPECT_TRUE(c8.HasFile(dir2)); + EXPECT_TRUE(c8.HasFile(file1)); + EXPECT_TRUE(c8.HasFile(file2_abs)); + EXPECT_TRUE(c8.HasFile(dir2file)); + EXPECT_TRUE(c8.HasFile(dir2inner)); + EXPECT_TRUE(c8.HasFile(dir2innerfile)); + EXPECT_EQ(7, c8.size()); + } +#endif + + // Make sure the destructor closes the find handle while in the middle of a + // query to allow TearDown to delete the directory. + FileEnumerator f9(temp_dir_.path(), true, FILES_AND_DIRECTORIES); + EXPECT_FALSE(f9.Next().value().empty()); // Should have found something + // (we don't care what). +} + +TEST_F(FileUtilTest, AppendToFile) { + FilePath data_dir = + temp_dir_.path().Append(FILE_PATH_LITERAL("FilePathTest")); + + // Create a fresh, empty copy of this directory. + if (PathExists(data_dir)) { + ASSERT_TRUE(DeleteFile(data_dir, true)); + } + ASSERT_TRUE(CreateDirectory(data_dir)); + + // Create a fresh, empty copy of this directory. + if (PathExists(data_dir)) { + ASSERT_TRUE(DeleteFile(data_dir, true)); + } + ASSERT_TRUE(CreateDirectory(data_dir)); + FilePath foobar(data_dir.Append(FILE_PATH_LITERAL("foobar.txt"))); + + std::string data("hello"); + EXPECT_EQ(-1, AppendToFile(foobar, data.c_str(), data.length())); + EXPECT_EQ(static_cast(data.length()), + WriteFile(foobar, data.c_str(), data.length())); + EXPECT_EQ(static_cast(data.length()), + AppendToFile(foobar, data.c_str(), data.length())); + + const std::wstring read_content = ReadTextFile(foobar); + EXPECT_EQ(L"hellohello", read_content); +} + +TEST_F(FileUtilTest, ReadFile) { + // Create a test file to be read. + const std::string kTestData("The quick brown fox jumps over the lazy dog."); + FilePath file_path = + temp_dir_.path().Append(FILE_PATH_LITERAL("ReadFileTest")); + + ASSERT_EQ(static_cast(kTestData.size()), + WriteFile(file_path, kTestData.data(), kTestData.size())); + + // Make buffers with various size. + std::vector small_buffer(kTestData.size() / 2); + std::vector exact_buffer(kTestData.size()); + std::vector large_buffer(kTestData.size() * 2); + + // Read the file with smaller buffer. + int bytes_read_small = ReadFile( + file_path, &small_buffer[0], static_cast(small_buffer.size())); + EXPECT_EQ(static_cast(small_buffer.size()), bytes_read_small); + EXPECT_EQ( + std::string(kTestData.begin(), kTestData.begin() + small_buffer.size()), + std::string(small_buffer.begin(), small_buffer.end())); + + // Read the file with buffer which have exactly same size. + int bytes_read_exact = ReadFile( + file_path, &exact_buffer[0], static_cast(exact_buffer.size())); + EXPECT_EQ(static_cast(kTestData.size()), bytes_read_exact); + EXPECT_EQ(kTestData, std::string(exact_buffer.begin(), exact_buffer.end())); + + // Read the file with larger buffer. + int bytes_read_large = ReadFile( + file_path, &large_buffer[0], static_cast(large_buffer.size())); + EXPECT_EQ(static_cast(kTestData.size()), bytes_read_large); + EXPECT_EQ(kTestData, std::string(large_buffer.begin(), + large_buffer.begin() + kTestData.size())); + + // Make sure the return value is -1 if the file doesn't exist. + FilePath file_path_not_exist = + temp_dir_.path().Append(FILE_PATH_LITERAL("ReadFileNotExistTest")); + EXPECT_EQ(-1, + ReadFile(file_path_not_exist, + &exact_buffer[0], + static_cast(exact_buffer.size()))); +} + +TEST_F(FileUtilTest, ReadFileToString) { + const char kTestData[] = "0123"; + std::string data; + + FilePath file_path = + temp_dir_.path().Append(FILE_PATH_LITERAL("ReadFileToStringTest")); + FilePath file_path_dangerous = + temp_dir_.path().Append(FILE_PATH_LITERAL("..")). + Append(temp_dir_.path().BaseName()). + Append(FILE_PATH_LITERAL("ReadFileToStringTest")); + + // Create test file. + ASSERT_EQ(4, WriteFile(file_path, kTestData, 4)); + + EXPECT_TRUE(ReadFileToString(file_path, &data)); + EXPECT_EQ(kTestData, data); + + data = "temp"; + EXPECT_FALSE(ReadFileToString(file_path, &data, 0)); + EXPECT_EQ(0u, data.length()); + + data = "temp"; + EXPECT_FALSE(ReadFileToString(file_path, &data, 2)); + EXPECT_EQ("01", data); + + data.clear(); + EXPECT_FALSE(ReadFileToString(file_path, &data, 3)); + EXPECT_EQ("012", data); + + data.clear(); + EXPECT_TRUE(ReadFileToString(file_path, &data, 4)); + EXPECT_EQ("0123", data); + + data.clear(); + EXPECT_TRUE(ReadFileToString(file_path, &data, 6)); + EXPECT_EQ("0123", data); + + EXPECT_TRUE(ReadFileToString(file_path, NULL, 6)); + + EXPECT_TRUE(ReadFileToString(file_path, NULL)); + + data = "temp"; + EXPECT_FALSE(ReadFileToString(file_path_dangerous, &data)); + EXPECT_EQ(0u, data.length()); + + // Delete test file. + EXPECT_TRUE(butil::DeleteFile(file_path, false)); + + data = "temp"; + EXPECT_FALSE(ReadFileToString(file_path, &data)); + EXPECT_EQ(0u, data.length()); + + data = "temp"; + EXPECT_FALSE(ReadFileToString(file_path, &data, 6)); + EXPECT_EQ(0u, data.length()); +} + +TEST_F(FileUtilTest, TouchFile) { + FilePath data_dir = + temp_dir_.path().Append(FILE_PATH_LITERAL("FilePathTest")); + + // Create a fresh, empty copy of this directory. + if (PathExists(data_dir)) { + ASSERT_TRUE(DeleteFile(data_dir, true)); + } + ASSERT_TRUE(CreateDirectory(data_dir)); + + FilePath foobar(data_dir.Append(FILE_PATH_LITERAL("foobar.txt"))); + std::string data("hello"); + ASSERT_TRUE(WriteFile(foobar, data.c_str(), data.length())); + + // 784915200000000 represents the timestamp of "Wed, 16 Nov 1994, 00:00:00". + // This timestamp is divisible by one day (in local timezone), to make it work + // on FAT too. + auto access_time = Time::FromUTCExploded({1994, 11, 4, 16, 0, 0, 0, 0}); + + // 784903526000000 represents the timestamp of "Tue, 15 Nov 1994, 12:45:26 GMT". + // Note that this timestamp is divisible by two (seconds) - FAT stores + // modification times with 2s resolution. + auto modification_time = Time::FromUTCExploded({1994, 11, 3, 15, 12, 45, 26, 0}); + + ASSERT_TRUE(TouchFile(foobar, access_time, modification_time)); + File::Info file_info; + ASSERT_TRUE(GetFileInfo(foobar, &file_info)); + EXPECT_EQ(access_time.ToInternalValue(), + file_info.last_accessed.ToInternalValue()); + EXPECT_EQ(modification_time.ToInternalValue(), + file_info.last_modified.ToInternalValue()); +} + +TEST_F(FileUtilTest, IsDirectoryEmpty) { + FilePath empty_dir = temp_dir_.path().Append(FILE_PATH_LITERAL("EmptyDir")); + + ASSERT_FALSE(PathExists(empty_dir)); + + ASSERT_TRUE(CreateDirectory(empty_dir)); + + EXPECT_TRUE(IsDirectoryEmpty(empty_dir)); + + FilePath foo(empty_dir.Append(FILE_PATH_LITERAL("foo.txt"))); + std::string bar("baz"); + ASSERT_TRUE(WriteFile(foo, bar.c_str(), bar.length())); + + EXPECT_FALSE(IsDirectoryEmpty(empty_dir)); +} + +#if defined(OS_POSIX) + +// Testing VerifyPathControlledByAdmin() is hard, because there is no +// way a test can make a file owned by root, or change file paths +// at the root of the file system. VerifyPathControlledByAdmin() +// is implemented as a call to VerifyPathControlledByUser, which gives +// us the ability to test with paths under the test's temp directory, +// using a user id we control. +// Pull tests of VerifyPathControlledByUserTest() into a separate test class +// with a common SetUp() method. +class VerifyPathControlledByUserTest : public FileUtilTest { + protected: + virtual void SetUp() OVERRIDE { + FileUtilTest::SetUp(); + + // Create a basic structure used by each test. + // base_dir_ + // |-> sub_dir_ + // |-> text_file_ + + base_dir_ = temp_dir_.path().AppendASCII("base_dir"); + ASSERT_TRUE(CreateDirectory(base_dir_)); + + sub_dir_ = base_dir_.AppendASCII("sub_dir"); + ASSERT_TRUE(CreateDirectory(sub_dir_)); + + text_file_ = sub_dir_.AppendASCII("file.txt"); + CreateTextFile(text_file_, L"This text file has some text in it."); + + // Get the user and group files are created with from |base_dir_|. + struct stat stat_buf; + ASSERT_EQ(0, stat(base_dir_.value().c_str(), &stat_buf)); + uid_ = stat_buf.st_uid; + ok_gids_.insert(stat_buf.st_gid); + bad_gids_.insert(stat_buf.st_gid + 1); + + ASSERT_EQ(uid_, geteuid()); // This process should be the owner. + + // To ensure that umask settings do not cause the initial state + // of permissions to be different from what we expect, explicitly + // set permissions on the directories we create. + // Make all files and directories non-world-writable. + + // Users and group can read, write, traverse + int enabled_permissions = + FILE_PERMISSION_USER_MASK | FILE_PERMISSION_GROUP_MASK; + // Other users can't read, write, traverse + int disabled_permissions = FILE_PERMISSION_OTHERS_MASK; + + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions( + base_dir_, enabled_permissions, disabled_permissions)); + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions( + sub_dir_, enabled_permissions, disabled_permissions)); + } +#if defined(OS_POSIX) + virtual void TearDown() OVERRIDE { + FileUtilTest::TearDown(); + } +#endif + + FilePath base_dir_; + FilePath sub_dir_; + FilePath text_file_; + uid_t uid_; + + std::set ok_gids_; + std::set bad_gids_; +}; + +TEST_F(VerifyPathControlledByUserTest, BadPaths) { + // File does not exist. + FilePath does_not_exist = base_dir_.AppendASCII("does") + .AppendASCII("not") + .AppendASCII("exist"); + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + base_dir_, does_not_exist, uid_, ok_gids_)); + + // |base| not a subpath of |path|. + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + sub_dir_, base_dir_, uid_, ok_gids_)); + + // An empty base path will fail to be a prefix for any path. + FilePath empty; + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + empty, base_dir_, uid_, ok_gids_)); + + // Finding that a bad call fails proves nothing unless a good call succeeds. + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); +} + +TEST_F(VerifyPathControlledByUserTest, Symlinks) { + // Symlinks in the path should cause failure. + + // Symlink to the file at the end of the path. + FilePath file_link = base_dir_.AppendASCII("file_link"); + ASSERT_TRUE(CreateSymbolicLink(text_file_, file_link)) + << "Failed to create symlink."; + + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + base_dir_, file_link, uid_, ok_gids_)); + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + file_link, file_link, uid_, ok_gids_)); + + // Symlink from one directory to another within the path. + FilePath link_to_sub_dir = base_dir_.AppendASCII("link_to_sub_dir"); + ASSERT_TRUE(CreateSymbolicLink(sub_dir_, link_to_sub_dir)) + << "Failed to create symlink."; + + FilePath file_path_with_link = link_to_sub_dir.AppendASCII("file.txt"); + ASSERT_TRUE(PathExists(file_path_with_link)); + + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + base_dir_, file_path_with_link, uid_, ok_gids_)); + + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + link_to_sub_dir, file_path_with_link, uid_, ok_gids_)); + + // Symlinks in parents of base path are allowed. + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + file_path_with_link, file_path_with_link, uid_, ok_gids_)); +} + +TEST_F(VerifyPathControlledByUserTest, OwnershipChecks) { + // Get a uid that is not the uid of files we create. + uid_t bad_uid = uid_ + 1; + + // Make all files and directories non-world-writable. + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(base_dir_, 0u, S_IWOTH)); + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(sub_dir_, 0u, S_IWOTH)); + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(text_file_, 0u, S_IWOTH)); + + // We control these paths. + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, ok_gids_)); + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, ok_gids_)); + + // Another user does not control these paths. + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + base_dir_, sub_dir_, bad_uid, ok_gids_)); + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + base_dir_, text_file_, bad_uid, ok_gids_)); + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + sub_dir_, text_file_, bad_uid, ok_gids_)); + + // Another group does not control the paths. + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, bad_gids_)); + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, bad_gids_)); + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, bad_gids_)); +} + +TEST_F(VerifyPathControlledByUserTest, GroupWriteTest) { + // Make all files and directories writable only by their owner. + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(base_dir_, 0u, S_IWOTH|S_IWGRP)); + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(sub_dir_, 0u, S_IWOTH|S_IWGRP)); + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(text_file_, 0u, S_IWOTH|S_IWGRP)); + + // Any group is okay because the path is not group-writable. + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, ok_gids_)); + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, ok_gids_)); + + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, bad_gids_)); + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, bad_gids_)); + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, bad_gids_)); + + // No group is okay, because we don't check the group + // if no group can write. + std::set no_gids; // Empty set of gids. + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, no_gids)); + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, no_gids)); + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, no_gids)); + + + // Make all files and directories writable by their group. + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(base_dir_, S_IWGRP, 0u)); + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(sub_dir_, S_IWGRP, 0u)); + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(text_file_, S_IWGRP, 0u)); + + // Now |ok_gids_| works, but |bad_gids_| fails. + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, ok_gids_)); + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, ok_gids_)); + + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, bad_gids_)); + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, bad_gids_)); + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, bad_gids_)); + + // Because any group in the group set is allowed, + // the union of good and bad gids passes. + + std::set multiple_gids; + std::set_union( + ok_gids_.begin(), ok_gids_.end(), + bad_gids_.begin(), bad_gids_.end(), + std::inserter(multiple_gids, multiple_gids.begin())); + + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, multiple_gids)); + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, multiple_gids)); + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, multiple_gids)); +} + +TEST_F(VerifyPathControlledByUserTest, WriteBitChecks) { + // Make all files and directories non-world-writable. + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(base_dir_, 0u, S_IWOTH)); + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(sub_dir_, 0u, S_IWOTH)); + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(text_file_, 0u, S_IWOTH)); + + // Initialy, we control all parts of the path. + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, ok_gids_)); + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, ok_gids_)); + + // Make base_dir_ world-writable. + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(base_dir_, S_IWOTH, 0u)); + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, ok_gids_)); + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, ok_gids_)); + + // Make sub_dir_ world writable. + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(sub_dir_, S_IWOTH, 0u)); + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, ok_gids_)); + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, ok_gids_)); + + // Make text_file_ world writable. + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(text_file_, S_IWOTH, 0u)); + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, ok_gids_)); + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, ok_gids_)); + + // Make sub_dir_ non-world writable. + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(sub_dir_, 0u, S_IWOTH)); + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, ok_gids_)); + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, ok_gids_)); + + // Make base_dir_ non-world-writable. + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(base_dir_, 0u, S_IWOTH)); + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, ok_gids_)); + EXPECT_FALSE( + butil::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, ok_gids_)); + + // Back to the initial state: Nothing is writable, so every path + // should pass. + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(text_file_, 0u, S_IWOTH)); + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, ok_gids_)); + EXPECT_TRUE( + butil::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, ok_gids_)); +} + +TEST_F(FileUtilTest, CreateDirectoryParentsNotExist) { + ASSERT_TRUE(butil::CreateDirectory(temp_dir_.path())); + butil::FilePath creating_dir = temp_dir_.path().Append("sub1").Append("sub2"); + std::cout << "path=" << creating_dir.value() << std::endl; + ASSERT_FALSE(butil::CreateDirectory(creating_dir, false)); + ASSERT_TRUE(butil::CreateDirectory(creating_dir, true)); +} + +#if defined(OS_ANDROID) +TEST_F(FileUtilTest, ValidContentUriTest) { + // Get the test image path. + FilePath data_dir; + ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &data_dir)); + data_dir = data_dir.AppendASCII("file_util"); + ASSERT_TRUE(PathExists(data_dir)); + FilePath image_file = data_dir.Append(FILE_PATH_LITERAL("red.png")); + int64_t image_size; + GetFileSize(image_file, &image_size); + EXPECT_LT(0, image_size); + + // Insert the image into MediaStore. MediaStore will do some conversions, and + // return the content URI. + FilePath path = file_util::InsertImageIntoMediaStore(image_file); + EXPECT_TRUE(path.IsContentUri()); + EXPECT_TRUE(PathExists(path)); + // The file size may not equal to the input image as MediaStore may convert + // the image. + int64_t content_uri_size; + GetFileSize(path, &content_uri_size); + EXPECT_EQ(image_size, content_uri_size); + + // We should be able to read the file. + char* buffer = new char[image_size]; + File file = OpenContentUriForRead(path); + EXPECT_TRUE(file.IsValid()); + EXPECT_TRUE(file.ReadAtCurrentPos(buffer, image_size)); + delete[] buffer; +} + +TEST_F(FileUtilTest, NonExistentContentUriTest) { + FilePath path("content://foo.bar"); + EXPECT_TRUE(path.IsContentUri()); + EXPECT_FALSE(PathExists(path)); + // Size should be smaller than 0. + int64_t size; + EXPECT_FALSE(GetFileSize(path, &size)); + + // We should not be able to read the file. + File file = OpenContentUriForRead(path); + EXPECT_FALSE(file.IsValid()); +} +#endif + +TEST(ScopedFD, ScopedFDDoesClose) { + int fds[2]; + char c = 0; + ASSERT_EQ(0, pipe(fds)); + const int write_end = fds[1]; + butil::ScopedFD read_end_closer(fds[0]); + { + butil::ScopedFD write_end_closer(fds[1]); + } + // This is the only thread. This file descriptor should no longer be valid. + int ret = close(write_end); + EXPECT_EQ(-1, ret); + EXPECT_EQ(EBADF, errno); + // Make sure read(2) won't block. + ASSERT_EQ(0, fcntl(fds[0], F_SETFL, O_NONBLOCK)); + // Reading the pipe should EOF. + EXPECT_EQ(0, read(fds[0], &c, 1)); +} + +#if defined(GTEST_HAS_DEATH_TEST) +void CloseWithScopedFD(int fd) { + butil::ScopedFD fd_closer(fd); +} +#endif + +TEST(ScopedFD, ScopedFDCrashesOnCloseFailure) { + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + butil::ScopedFD read_end_closer(fds[0]); + EXPECT_EQ(0, IGNORE_EINTR(close(fds[1]))); +#if defined(GTEST_HAS_DEATH_TEST) + // This is the only thread. This file descriptor should no longer be valid. + // Trying to close it should crash. This is important for security. + EXPECT_DEATH(CloseWithScopedFD(fds[1]), ""); +#endif +} + +#endif // defined(OS_POSIX) + +} // namespace + +} // namespace butil diff --git a/test/file_watcher_unittest.cpp b/test/file_watcher_unittest.cpp new file mode 100644 index 0000000..db4ae41 --- /dev/null +++ b/test/file_watcher_unittest.cpp @@ -0,0 +1,78 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/files/file_watcher.h" +#include "butil/logging.h" + +namespace { +class FileWatcherTest : public ::testing::Test{ +protected: + FileWatcherTest(){}; + virtual ~FileWatcherTest(){}; + virtual void SetUp() { + }; + virtual void TearDown() { + }; +}; + +//! gejun: check basic functions of butil::FileWatcher +TEST_F(FileWatcherTest, random_op) { + srand (time(0)); + + butil::FileWatcher fw; + EXPECT_EQ (0, fw.init("dummy_file")); + + for (int i=0; i<30; ++i) { + if (rand() % 2) { + const butil::FileWatcher::Change ret = fw.check_and_consume(); + switch (ret) { + case butil::FileWatcher::UPDATED: + LOG(INFO) << fw.filepath() << " is updated"; + break; + case butil::FileWatcher::CREATED: + LOG(INFO) << fw.filepath() << " is created"; + break; + case butil::FileWatcher::DELETED: + LOG(INFO) << fw.filepath() << " is deleted"; + break; + case butil::FileWatcher::UNCHANGED: + LOG(INFO) << fw.filepath() << " does not change or still not exist"; + break; + } + } + + switch (rand() % 2) { + case 0: + ASSERT_EQ(0, system("touch dummy_file")); + LOG(INFO) << "action: touch dummy_file"; + break; + case 1: + ASSERT_EQ(0, system("rm -f dummy_file")); + LOG(INFO) << "action: rm -f dummy_file"; + break; + case 2: + LOG(INFO) << "action: (nothing)"; + break; + } + + usleep (10000); + } + ASSERT_EQ(0, system("rm -f dummy_file")); +} + +} // namespace diff --git a/test/find_cstr_unittest.cpp b/test/find_cstr_unittest.cpp new file mode 100644 index 0000000..340ae0b --- /dev/null +++ b/test/find_cstr_unittest.cpp @@ -0,0 +1,97 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include "butil/find_cstr.h" +#include "butil/time.h" +#include "butil/logging.h" + +namespace { +class FindCstrTest : public ::testing::Test{ +protected: + FindCstrTest(){ + }; + virtual ~FindCstrTest(){}; + virtual void SetUp() { + srand(time(0)); + }; + virtual void TearDown() { + }; +}; + +TEST_F(FindCstrTest, sanity) { + std::map t1; + ASSERT_EQ(0u, t1.size()); + ASSERT_TRUE(t1.empty()); + for (std::map::iterator + it = t1.begin(); it != t1.end(); ++it) { + // nothing. + } + t1["hello"] = 0xdeadbeef; + std::map::iterator it = butil::find_cstr(t1, "hello"); + ASSERT_TRUE(it != t1.end()); + ASSERT_EQ("hello", it->first); + ASSERT_EQ(0xdeadbeef, (unsigned int)it->second); +} + +TEST_F(FindCstrTest, perf) { + std::map t1; + std::string key_buf; + std::vector key_offsets; + key_buf.reserve(8192); + key_offsets.reserve(1000); + for (size_t i = 0; i < 1000; ++i) { + char tmp[16]; + int len = snprintf(tmp, sizeof(tmp), "hello%lu", i); + t1[tmp] = i; + key_offsets.push_back(key_buf.size()); + key_buf.append(tmp, len + 1); + } + ASSERT_EQ(1000u, t1.size()); + ASSERT_FALSE(t1.empty()); + + const size_t N = 20000; + std::vector all_keys; + all_keys.reserve(N); + size_t j = 0; + for (size_t i = 0; i < N; ++i) { + all_keys.push_back(key_buf.data() + key_offsets[j]); + if (++j >= key_offsets.size()) { + j = 0; + } + } + std::random_shuffle(all_keys.begin(), all_keys.end()); + int sum = 0; + butil::Timer tm; + tm.start(); + for (size_t i = 0; i < all_keys.size(); ++i) { + sum += butil::find_cstr(t1, all_keys[i])->second; + } + tm.stop(); + int64_t elp1 = tm.n_elapsed(); + tm.start(); + for (size_t i = 0; i < all_keys.size(); ++i) { + sum += t1.find(all_keys[i])->second; + } + tm.stop(); + int64_t elp2 = tm.n_elapsed(); + + LOG(INFO) << "elp1=" << elp1 / N << " elp2=" << elp2 / N << " sum=" << sum; +} + +} diff --git a/test/flat_map_unittest.cpp b/test/flat_map_unittest.cpp new file mode 100644 index 0000000..a394218 --- /dev/null +++ b/test/flat_map_unittest.cpp @@ -0,0 +1,1619 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include +#include +#include "butil/time.h" +#include "butil/macros.h" +#include "butil/string_printf.h" +#include "butil/logging.h" +#include "butil/containers/hash_tables.h" +#include "butil/containers/flat_map.h" +#include "butil/containers/pooled_map.h" +#include "butil/containers/case_ignored_flat_map.h" + +namespace { +class FlatMapTest : public ::testing::Test{ +protected: + FlatMapTest(){}; + virtual ~FlatMapTest(){}; + virtual void SetUp() { + }; + virtual void TearDown() { + }; +}; + +int g_foo_ctor = 0; +int g_foo_copy_ctor = 0; +int g_foo_assign = 0; +struct Foo { + Foo() { ++g_foo_ctor; } + Foo(const Foo&) { ++g_foo_copy_ctor; } + void operator=(const Foo&) { ++g_foo_assign; } +}; +struct Bar { + int x; +}; + +TEST_F(FlatMapTest, initialization_of_values) { + // Construct non-POD values w/o copy-construction. + butil::FlatMap map; + ASSERT_EQ(0, map.init(32)); + ASSERT_EQ(0, g_foo_ctor); + ASSERT_EQ(0, g_foo_copy_ctor); + ASSERT_EQ(0, g_foo_assign); + map[1]; + ASSERT_EQ(1, g_foo_ctor); + ASSERT_EQ(0, g_foo_copy_ctor); + ASSERT_EQ(0, g_foo_assign); + + // Zeroize POD values. + butil::FlatMap map2; + ASSERT_EQ(0, map2.init(32)); + Bar& g = map2[1]; + ASSERT_EQ(0, g.x); + g.x = 123; + ASSERT_EQ(1u, map2.erase(1)); + ASSERT_EQ(123, g.x); // g is still accessible in this case. + Bar& g2 = map2[1]; + ASSERT_EQ(&g, &g2); + ASSERT_EQ(0, g2.x); +} + +TEST_F(FlatMapTest, swap_pooled_allocator) { + butil::details::PooledAllocator a1; + a1.allocate(1); + const void* p1 = a1._pool._blocks; + + butil::details::PooledAllocator a2; + a2.allocate(1); + const void* p2 = a2._pool._blocks; + + std::swap(a1, a2); + + ASSERT_EQ(p2, a1._pool._blocks); + ASSERT_EQ(p1, a2._pool._blocks); +} + +TEST_F(FlatMapTest, copy_flat_map) { + typedef butil::FlatMap Map; + Map default_init_m1; + ASSERT_TRUE(default_init_m1.initialized()); + ASSERT_TRUE(default_init_m1.empty()); + ASSERT_EQ(BRPC_FLATMAP_DEFAULT_NBUCKET, default_init_m1.bucket_count()); + // self assignment does nothing. + default_init_m1 = default_init_m1; + ASSERT_TRUE(default_init_m1.initialized()); + ASSERT_TRUE(default_init_m1.empty()); + ASSERT_EQ(BRPC_FLATMAP_DEFAULT_NBUCKET, default_init_m1.bucket_count()); + + Map default_init_m2 = default_init_m1; + ASSERT_TRUE(default_init_m2.initialized()); + ASSERT_TRUE(default_init_m2.empty()); + ASSERT_EQ(BRPC_FLATMAP_DEFAULT_NBUCKET, default_init_m1.bucket_count()); + + Map init_m3; + ASSERT_TRUE(init_m3.initialized()); + // smaller than the default value, and the default buckets + // is continued to be used. + ASSERT_EQ(0, init_m3.init(8)); + ASSERT_TRUE(init_m3.initialized()); + ASSERT_EQ(BRPC_FLATMAP_DEFAULT_NBUCKET, init_m3.bucket_count()); + ASSERT_EQ(init_m3._default_buckets, init_m3._buckets); + init_m3["hello"] = "world"; + ASSERT_EQ(1u, init_m3.size()); + init_m3 = default_init_m1; + ASSERT_TRUE(init_m3.initialized()); + ASSERT_TRUE(init_m3.empty()); + + Map init_m4; + ASSERT_TRUE(init_m4.initialized()); + // Resize to a larger buckets, and then not using the default buckets. + ASSERT_EQ(0, init_m4.init(BRPC_FLATMAP_DEFAULT_NBUCKET + 1)); + ASSERT_EQ(butil::flatmap_round(BRPC_FLATMAP_DEFAULT_NBUCKET + 1), + init_m4.bucket_count()); + ASSERT_NE(init_m4._default_buckets, init_m4._buckets); + init_m4["hello"] = "world"; + ASSERT_EQ(1u, init_m4.size()); + init_m4 = default_init_m1; + ASSERT_TRUE(init_m4.initialized()); + ASSERT_TRUE(init_m4.empty()); + ASSERT_EQ(butil::flatmap_round(BRPC_FLATMAP_DEFAULT_NBUCKET + 1), + init_m4.bucket_count()); + + Map m1; + ASSERT_EQ(0, m1.init(16)); + size_t expected_count = 0; + m1["hello"] = "world"; ++expected_count; + m1["foo"] = "bar"; ++ expected_count; + m1["friend"] = "alice"; ++ expected_count; + m1["zone"] = "bj-01"; ++ expected_count; + m1["city"] = "shanghai"; ++ expected_count; + m1["owner"] = "bob"; ++ expected_count; + m1["lang"] = "chinese"; ++ expected_count; + ASSERT_TRUE(m1.initialized()); + ASSERT_EQ(expected_count, m1.size()); + // self assignment does nothing. + m1 = m1; + ASSERT_EQ(expected_count, m1.size()); + ASSERT_EQ("world", m1["hello"]); + ASSERT_EQ("bar", m1["foo"]); + ASSERT_EQ("bob", m1["owner"]); + ASSERT_EQ("bj-01", m1["zone"]); + ASSERT_EQ("shanghai", m1["city"]); + ASSERT_EQ("chinese", m1["lang"]); + ASSERT_EQ("alice", m1["friend"]); + // Copy construct from initialized map. + Map m2 = m1; + ASSERT_TRUE(m2.initialized()); + ASSERT_EQ(expected_count, m2.size()); + ASSERT_EQ("world", m2["hello"]); + ASSERT_EQ("bar", m2["foo"]); + ASSERT_EQ("bob", m2["owner"]); + ASSERT_EQ("bj-01", m2["zone"]); + ASSERT_EQ("shanghai", m2["city"]); + ASSERT_EQ("chinese", m2["lang"]); + ASSERT_EQ("alice", m2["friend"]); + // assign initialized map to uninitialized map. + Map m3; + m3 = m1; + ASSERT_TRUE(m3.initialized()); + ASSERT_EQ(expected_count, m3.size()); + ASSERT_EQ("world", m3["hello"]); + ASSERT_EQ("bar", m3["foo"]); + ASSERT_EQ("bob", m3["owner"]); + ASSERT_EQ("bj-01", m3["zone"]); + ASSERT_EQ("shanghai", m3["city"]); + ASSERT_EQ("chinese", m3["lang"]); + ASSERT_EQ("alice", m3["friend"]); + // assign initialized map to initialized map (triggering resize) + Map m4; + ASSERT_EQ(0, m4.init(2)); + ASSERT_LE(m4.bucket_count(), m1.bucket_count()); + const void* old_buckets4 = m4._buckets; + m4 = m1; + ASSERT_EQ(m1.bucket_count(), m4.bucket_count()); + ASSERT_EQ(old_buckets4, m4._buckets); + ASSERT_EQ(expected_count, m4.size()); + ASSERT_EQ("world", m4["hello"]); + ASSERT_EQ("bar", m4["foo"]); + ASSERT_EQ("bob", m4["owner"]); + ASSERT_EQ("bj-01", m4["zone"]); + ASSERT_EQ("shanghai", m4["city"]); + ASSERT_EQ("chinese", m4["lang"]); + ASSERT_EQ("alice", m4["friend"]); + // assign initialized map to initialized map (no resize) + const size_t bcs[] = { m1.bucket_count(), 32 }; + // less than m1.bucket_count but enough for holding the elements + ASSERT_LE(bcs[0], m1.bucket_count()); + // larger than m1.bucket_count + ASSERT_GE(bcs[1], m1.bucket_count()); + for (size_t i = 0; i < arraysize(bcs); ++i) { + Map m5; + ASSERT_EQ(0, m5.init(bcs[i])); + const size_t old_bucket_count5 = m5.bucket_count(); + const void* old_buckets5 = m5._buckets; + m5 = m1; + ASSERT_EQ(old_bucket_count5, m5.bucket_count()); + ASSERT_EQ(old_buckets5, m5._buckets); + ASSERT_EQ(expected_count, m5.size()); + ASSERT_EQ("world", m5["hello"]); + ASSERT_EQ("bar", m5["foo"]); + ASSERT_EQ("bob", m5["owner"]); + ASSERT_EQ("bj-01", m5["zone"]); + ASSERT_EQ("shanghai", m5["city"]); + ASSERT_EQ("chinese", m5["lang"]); + ASSERT_EQ("alice", m5["friend"]); + } +} + +TEST_F(FlatMapTest, seek_by_string_piece) { + butil::FlatMap m; + ASSERT_EQ(0, m.init(16)); + m["hello"] = 1; + m["world"] = 2; + butil::StringPiece k1("hello"); + ASSERT_TRUE(m.seek(k1)); + ASSERT_EQ(1, *m.seek(k1)); + butil::StringPiece k2("world"); + ASSERT_TRUE(m.seek(k2)); + ASSERT_EQ(2, *m.seek(k2)); + butil::StringPiece k3("heheda"); + ASSERT_TRUE(m.seek(k3) == NULL); +} + +TEST_F(FlatMapTest, to_lower) { + for (int c = -128; c < 128; ++c) { + ASSERT_EQ((char)::tolower(c), butil::ascii_tolower(c)) << "c=" << c; + } + + const size_t input_len = 102; + char input[input_len + 1]; + char input2[input_len + 1]; + for (size_t i = 0; i < input_len; ++i) { + int choice = rand() % 52; + if (choice < 26) { + input[i] = 'A' + choice; + input2[i] = 'A' + choice; + } else { + input[i] = 'a' + choice - 26; + input2[i] = 'a' + choice - 26; + } + } + input[input_len] = '\0'; + input2[input_len] = '\0'; + butil::Timer tm1; + butil::Timer tm2; + butil::Timer tm3; + int sum = 0; + tm1.start(); + sum += strcasecmp(input, input2); + tm1.stop(); + tm3.start(); + sum += strncasecmp(input, input2, input_len); + tm3.stop(); + tm2.start(); + sum += memcmp(input, input2, input_len); + tm2.stop(); + LOG(INFO) << "tm1=" << tm1.n_elapsed() + << " tm2=" << tm2.n_elapsed() + << " tm3=" << tm3.n_elapsed() << " " << sum; +} + +TEST_F(FlatMapTest, __builtin_ctzl_perf) { + int s = 0; + const size_t N = 10000; + butil::Timer tm1; + tm1.start(); + for (size_t i = 1; i <= N; ++i) { + s += __builtin_ctzl(i); + } + tm1.stop(); + LOG(INFO) << "__builtin_ctzl takes " << tm1.n_elapsed()/(double)N << "ns s=" << s; +} + +TEST_F(FlatMapTest, case_ignored_map) { + butil::CaseIgnoredFlatMap m1; + ASSERT_EQ(0, m1.init(32)); + m1["Content-Type"] = 1; + m1["content-Type"] = 10; + m1["Host"] = 2; + m1["HOST"] = 20; + m1["Cache-Control"] = 3; + m1["CachE-ControL"] = 30; + ASSERT_EQ(10, m1["cONTENT-tYPE"]); + ASSERT_EQ(20, m1["hOST"]); + ASSERT_EQ(30, m1["cache-control"]); +} + +TEST_F(FlatMapTest, case_ignored_set) { + butil::CaseIgnoredFlatSet s1; + ASSERT_EQ(0, s1.init(32)); + s1.insert("Content-Type"); + ASSERT_EQ(1ul, s1.size()); + s1.insert("Content-TYPE"); + ASSERT_EQ(1ul, s1.size()); + s1.insert("Host"); + ASSERT_EQ(2ul, s1.size()); + s1.insert("HOST"); + ASSERT_EQ(2ul, s1.size()); + s1.insert("Cache-Control"); + ASSERT_EQ(3ul, s1.size()); + s1.insert("CachE-ControL"); + ASSERT_EQ(3ul, s1.size()); + ASSERT_TRUE(s1.seek("cONTENT-tYPE")); + ASSERT_TRUE(s1.seek("hOST")); + ASSERT_TRUE(s1.seek("cache-control")); +} + + +TEST_F(FlatMapTest, make_sure_all_methods_compile) { + typedef butil::FlatMap M1; + M1 m1; + ASSERT_EQ(0, m1.init(32)); + ASSERT_EQ(0u, m1.size()); + m1[1] = 10; + ASSERT_EQ(10, m1[1]); + ASSERT_EQ(1u, m1.size()); + m1[2] = 20; + ASSERT_EQ(20, m1[2]); + ASSERT_EQ(2u, m1.size()); + m1.insert(1, 100); + m1.insert({3, 30}); + ASSERT_EQ(100, m1[1]); + ASSERT_EQ(3u, m1.size()); + ASSERT_TRUE(m1.seek(3)); + ASSERT_EQ(NULL, m1.seek(4)); + ASSERT_EQ(1u, m1.erase(3)); + ASSERT_EQ(0u, m1.erase(4)); + ASSERT_EQ(2u, m1.size()); + ASSERT_EQ(1u, m1.erase(2)); + ASSERT_EQ(1u, m1.size()); + for (M1::iterator it = m1.begin(); it != m1.end(); ++it) { + std::cout << "[" << it->first << "," << it->second << "] "; + } + std::cout << std::endl; + for (M1::const_iterator it = m1.begin(); it != m1.end(); ++it) { + std::cout << "[" << it->first << "," << it->second << "] "; + } + std::cout << std::endl; + + typedef butil::FlatSet S1; + S1 s1; + ASSERT_EQ(0, s1.init(32)); + ASSERT_EQ(0u, s1.size()); + s1.insert(1); + ASSERT_TRUE(s1.seek(1)); + ASSERT_EQ(1u, s1.size()); + s1.insert(2); + ASSERT_TRUE(s1.seek(2)); + ASSERT_EQ(2u, s1.size()); + s1.insert(1); + ASSERT_TRUE(s1.seek(1)); + ASSERT_EQ(2u, s1.size()); + ASSERT_EQ(NULL, s1.seek(3)); + ASSERT_EQ(0u, s1.erase(3)); + ASSERT_EQ(2u, s1.size()); + ASSERT_EQ(1u, s1.erase(2)); + ASSERT_EQ(1u, s1.size()); + for (S1::iterator it = s1.begin(); it != s1.end(); ++it) { + std::cout << "[" << *it << "] "; + } + std::cout << std::endl; + for (S1::const_iterator it = s1.begin(); it != s1.end(); ++it) { + std::cout << "[" << *it << "] "; + } + std::cout << std::endl; +} + +TEST_F(FlatMapTest, flat_map_of_string) { + std::vector keys; + butil::FlatMap m1; + std::map m2; + butil::hash_map m3; + const size_t N = 10000; + ASSERT_EQ(0, m1.init(N)); + butil::Timer tm1, tm1_2, tm2, tm3; + size_t sum = 0; + keys.reserve(N); + for (size_t i = 0; i < N; ++i) { + keys.push_back(butil::string_printf("up_latency_as_key_%lu", i)); + } + + tm1.start(); + for (size_t i = 0; i < N; ++i) { + m1[keys[i]] += i; + } + tm1.stop(); + tm2.start(); + for (size_t i = 0; i < N; ++i) { + m2[keys[i]] += i; + } + tm2.stop(); + tm3.start(); + for (size_t i = 0; i < N; ++i) { + m3[keys[i]] += i; + } + tm3.stop(); + LOG(INFO) << "inserting strings takes " << tm1.n_elapsed() / N + << " " << tm2.n_elapsed() / N + << " " << tm3.n_elapsed() / N; + + tm1.start(); + for (size_t i = 0; i < N; ++i) { + sum += *m1.seek(keys[i]); + } + tm1.stop(); + tm2.start(); + for (size_t i = 0; i < N; ++i) { + sum += m2.find(keys[i])->second; + } + tm2.stop(); + tm3.start(); + for (size_t i = 0; i < N; ++i) { + sum += m3.find(keys[i])->second; + } + tm3.stop(); + LOG(INFO) << "finding strings takes " << tm1.n_elapsed()/N + << " " << tm2.n_elapsed()/N << " " << tm3.n_elapsed()/N; + + tm1.start(); + for (size_t i = 0; i < N; ++i) { + sum += *m1.seek(keys[i].c_str()); + } + tm1.stop(); + tm2.start(); + for (size_t i = 0; i < N; ++i) { + sum += m2.find(keys[i].c_str())->second; + } + tm2.stop(); + tm3.start(); + for (size_t i = 0; i < N; ++i) { + sum += m3.find(keys[i].c_str())->second; + } + tm3.stop(); + tm1_2.start(); + for (size_t i = 0; i < N; ++i) { + sum += *find_cstr(m1, keys[i].c_str()); + } + tm1_2.stop(); + + LOG(INFO) << "finding c_strings takes " << tm1.n_elapsed()/N + << " " << tm2.n_elapsed()/N << " " << tm3.n_elapsed()/N + << " " << tm1_2.n_elapsed()/N << " sum=" << sum; + + for (size_t i = 0; i < N; ++i) { + ASSERT_EQ(i, m1[keys[i]]) << "i=" << i; + ASSERT_EQ(i, m2[keys[i]]); + ASSERT_EQ(i, m3[keys[i]]); + } + + butil::FlatMap m4; + m4["111"] = "222"; + ASSERT_TRUE(m4.seek("111")); + ASSERT_EQ("222", *m4.seek("111")); + ASSERT_EQ(1UL, m4.size()); + butil::FlatMap m5; + m5["333"] = "444"; + ASSERT_TRUE(m5.seek("333")); + ASSERT_EQ("444", *m5.seek("333")); + ASSERT_EQ(1UL, m5.size()); + + m4.swap(m5); + ASSERT_TRUE(m4.seek("333")); + ASSERT_EQ("444", *m4.seek("333")); + ASSERT_EQ(1UL, m4.size()); + ASSERT_TRUE(m5.seek("111")); + ASSERT_EQ("222", *m5.seek("111")); + ASSERT_EQ(1UL, m5.size()); + + m4.resize(BRPC_FLATMAP_DEFAULT_NBUCKET + 1); + ASSERT_EQ(1UL, m4.size()); + ASSERT_TRUE(m4.seek("333")); + ASSERT_EQ("444", *m4.seek("333")); + m4.swap(m5); + ASSERT_TRUE(m4.seek("111")); + ASSERT_EQ("222", *m4.seek("111")); + ASSERT_EQ(1UL, m4.size()); + ASSERT_TRUE(m5.seek("333")); + ASSERT_EQ("444", *m5.seek("333")); + ASSERT_EQ(1UL, m5.size()); + + m5.swap(m4); + ASSERT_TRUE(m4.seek("333")); + ASSERT_EQ("444", *m4.seek("333")); + ASSERT_EQ(1UL, m4.size()); + ASSERT_TRUE(m5.seek("111")); + ASSERT_EQ("222", *m5.seek("111")); + ASSERT_EQ(1UL, m5.size()); + + m5.resize(BRPC_FLATMAP_DEFAULT_NBUCKET + 1); + ASSERT_EQ(1UL, m5.size()); + ASSERT_EQ("222", *m5.seek("111")); + ASSERT_EQ(1UL, m5.size()); + m5.swap(m4); + ASSERT_TRUE(m4.seek("111")); + ASSERT_EQ("222", *m4.seek("111")); + ASSERT_EQ(1UL, m4.size()); + ASSERT_TRUE(m5.seek("333")); + ASSERT_EQ("444", *m5.seek("333")); + ASSERT_EQ(1UL, m5.size()); + +} + +TEST_F(FlatMapTest, fast_iterator) { + typedef butil::FlatMap M1; + typedef butil::SparseFlatMap M2; + + M1 m1; + M2 m2; + + ASSERT_EQ(0, m1.init(16384)); + ASSERT_EQ(0, m1.init(1)); + ASSERT_EQ(0, m2.init(16384)); + + ASSERT_EQ(NULL, m1._thumbnail); + ASSERT_TRUE(NULL != m2._thumbnail); + + const size_t N = 170; + std::vector keys; + keys.reserve(N); + for (size_t i = 0; i < N; ++i) { + keys.push_back(rand()); + } + + butil::Timer tm2; + tm2.start(); + for (size_t i = 0; i < N; ++i) { + m2[keys[i]] = i; + } + tm2.stop(); + + butil::Timer tm1; + tm1.start(); + for (size_t i = 0; i < N; ++i) { + m1[keys[i]] = i; + } + tm1.stop(); + + LOG(INFO) << "m1.insert=" << tm1.n_elapsed()/(double)N + << "ns m2.insert=" << tm2.n_elapsed()/(double)N; + tm1.start(); + for (M1::iterator it = m1.begin(); it != m1.end(); ++it); + tm1.stop(); + + tm2.start(); + for (M2::iterator it = m2.begin(); it != m2.end(); ++it); + tm2.stop(); + LOG(INFO) << "m1.iterate=" << tm1.n_elapsed()/(double)N + << "ns m2.iterate=" << tm2.n_elapsed()/(double)N; + + M1::iterator it1 = m1.begin(); + M2::iterator it2 = m2.begin(); + for ( ; it1 != m1.end() && it2 != m2.end(); ++it1, ++it2) { + ASSERT_EQ(it1->first, it2->first); + ASSERT_EQ(it1->second, it2->second); + } + ASSERT_EQ(m1.end(), it1); + ASSERT_EQ(m2.end(), it2); +} + +template +static void list_flat_map(std::vector* keys, + const butil::FlatMap& map, + size_t max_one_pass, + OnPause& on_pause) { + keys->clear(); + typedef butil::FlatMap Map; + size_t n = 0; + for (typename Map::const_iterator it = map.begin(); it != map.end(); ++it) { + if (++n >= max_one_pass) { + typename Map::PositionHint hint; + map.save_iterator(it, &hint); + n = 0; + on_pause(hint); + it = map.restore_iterator(hint); + if (it == map.begin()) { // resized + keys->clear(); + } + if (it == map.end()) { + break; + } + } + keys->push_back(it->first); + } +} + +typedef butil::FlatMap PositionHintMap; + +static void fill_position_hint_map(PositionHintMap* map, + std::vector* keys) { + srand(time(NULL)); + const size_t N = 5; + if (!map->initialized()) { + ASSERT_EQ(0, map->init(N * 3 / 2, 80)); + } + + keys->reserve(N); + keys->clear(); + map->clear(); + for (size_t i = 0; i < N; ++i) { + uint64_t key = rand(); + if (map->seek(key)) { + continue; + } + keys->push_back(key); + (*map)[key] = i; + } + LOG(INFO) << map->bucket_info() << ", size=" << map->size(); +} + +struct CountOnPause { + CountOnPause() : num_paused(0) {} + void operator()(const PositionHintMap::PositionHint&) { + ++num_paused; + } + size_t num_paused; +}; + +TEST_F(FlatMapTest, do_nothing_during_iteration) { + PositionHintMap m1; + std::vector keys; + fill_position_hint_map(&m1, &keys); + + // Iteration w/o insert/erasure should be same with single-threaded iteration. + std::vector keys_out; + CountOnPause on_pause1; + list_flat_map(&keys_out, m1, 10, on_pause1); + EXPECT_EQ(m1.size() / 10, on_pause1.num_paused); + ASSERT_EQ(m1.size(), keys_out.size()); + std::sort(keys_out.begin(), keys_out.end()); + for (size_t i = 0; i < keys_out.size(); ++i) { + ASSERT_TRUE(m1.seek(keys_out[i])) << "i=" << i; + if (i) { + ASSERT_NE(keys_out[i-1], keys_out[i]) << "i=" << i; + } + } +} + +struct RemoveInsertVisitedOnPause { + RemoveInsertVisitedOnPause() : keys(NULL), map(NULL) { + removed_keys.init(32); + inserted_keys.init(32); + } + void operator()(const PositionHintMap::PositionHint& hint) { + // Remove one + do { + int index = rand() % keys->size(); + uint64_t removed_key = (*keys)[index]; + if (removed_keys.seek(removed_key)) { + continue; + } + ASSERT_EQ(1u, map->erase(removed_key)); + removed_keys.insert(removed_key); + break; + } while (true); + + // Insert one + uint64_t inserted_key = + ((rand() % hint.offset) + rand() * hint.nbucket); + inserted_keys.insert(inserted_key); + ++(*map)[inserted_key]; + } + butil::FlatSet removed_keys; + butil::FlatSet inserted_keys; + const std::vector* keys; + PositionHintMap* map; +}; + +TEST_F(FlatMapTest, erase_insert_visited_during_iteration) { + PositionHintMap m1; + std::vector keys; + fill_position_hint_map(&m1, &keys); + + // Erase/insert visisted values should not affect the result. + const size_t old_map_size = m1.size(); + RemoveInsertVisitedOnPause on_pause2; + std::vector keys_out; + on_pause2.map = &m1; + on_pause2.keys = &keys_out; + list_flat_map(&keys_out, m1, 10, on_pause2); + EXPECT_EQ(old_map_size / 10, on_pause2.removed_keys.size()); + ASSERT_EQ(old_map_size, keys_out.size()); + std::sort(keys_out.begin(), keys_out.end()); + for (size_t i = 0; i < keys_out.size(); ++i) { + if (i) { + ASSERT_NE(keys_out[i-1], keys_out[i]) << "i=" << i; + } + if (!m1.seek(keys_out[i])) { + ASSERT_TRUE(on_pause2.removed_keys.seek(keys_out[i])) << "i=" << i; + } + ASSERT_FALSE(on_pause2.inserted_keys.seek(keys_out[i])) << "i=" << i; + } +} + +struct RemoveHintedOnPause { + RemoveHintedOnPause() : map(NULL) { + removed_keys.init(32); + } + void operator()(const PositionHintMap::PositionHint& hint) { + uint64_t removed_key = hint.key; + ASSERT_EQ(1u, map->erase(removed_key)); + removed_keys.insert(removed_key); + } + butil::FlatSet removed_keys; + PositionHintMap* map; +}; + +TEST_F(FlatMapTest, erase_hinted_during_iteration) { + PositionHintMap m1; + std::vector keys; + fill_position_hint_map(&m1, &keys); + + // Erasing hinted values + RemoveHintedOnPause on_pause3; + std::vector keys_out; + on_pause3.map = &m1; + list_flat_map(&keys_out, m1, 10, on_pause3); + // Not equal sometimes because of backward of iterator + // EXPECT_EQ(m1.size() / 10, on_pause3.removed_keys.size()); + const size_t old_keys_out_size = keys_out.size(); + std::sort(keys_out.begin(), keys_out.end()); + keys_out.resize(std::unique(keys_out.begin(), keys_out.end()) - keys_out.begin()); + LOG_IF(INFO, keys_out.size() != old_keys_out_size) + << "Iterated " << old_keys_out_size - keys_out.size() + << " duplicated elements"; + ASSERT_EQ(m1.size(), keys_out.size()); + for (size_t i = 0; i < keys_out.size(); ++i) { + if (i) { + ASSERT_NE(keys_out[i-1], keys_out[i]) << "i=" << i; + } + if (!m1.seek(keys_out[i])) { + ASSERT_TRUE(on_pause3.removed_keys.seek(keys_out[i])) << "i=" << i; + } + } +} + +struct RemoveInsertUnvisitedOnPause { + RemoveInsertUnvisitedOnPause() + : keys_out(NULL), all_keys(NULL), map(NULL) { + removed_keys.init(32); + inserted_keys.init(32); + } + void operator()(const PositionHintMap::PositionHint& hint) { + // Insert one + do { + uint64_t inserted_key = + ((rand() % (hint.nbucket - hint.offset)) + hint.offset + + rand() * hint.nbucket); + if (std::find(all_keys->begin(), all_keys->end(), inserted_key) + != all_keys->end()) { + continue; + } + all_keys->push_back(inserted_key); + inserted_keys.insert(inserted_key); + ++(*map)[inserted_key]; + break; + } while (true); + + // Remove one + while (true) { + int index = rand() % all_keys->size(); + uint64_t removed_key = (*all_keys)[index]; + if (removed_key == hint.key) { + continue; + } + if (removed_keys.seek(removed_key)) { + continue; + } + if (std::find(keys_out->begin(), keys_out->end(), removed_key) + != keys_out->end()) { + continue; + } + ASSERT_EQ(1u, map->erase(removed_key)); + removed_keys.insert(removed_key); + break; + } + } + butil::FlatSet removed_keys; + butil::FlatSet inserted_keys; + const std::vector* keys_out; + std::vector* all_keys; + PositionHintMap* map; +}; + +TEST_F(FlatMapTest, erase_insert_unvisited_during_iteration) { + PositionHintMap m1; + std::vector keys; + fill_position_hint_map(&m1, &keys); + + // Erase/insert unvisited values should affect keys_out + RemoveInsertUnvisitedOnPause on_pause4; + std::vector keys_out; + on_pause4.map = &m1; + on_pause4.keys_out = &keys_out; + on_pause4.all_keys = &keys; + list_flat_map(&keys_out, m1, 10, on_pause4); + EXPECT_EQ(m1.size() / 10, on_pause4.removed_keys.size()); + ASSERT_EQ(m1.size(), keys_out.size()); + std::sort(keys_out.begin(), keys_out.end()); + for (size_t i = 0; i < keys_out.size(); ++i) { + if (i) { + ASSERT_NE(keys_out[i-1], keys_out[i]) << "i=" << i; + } + ASSERT_TRUE(m1.seek(keys_out[i])) << "i=" << i; + } +} + +inline uint64_t fmix64 (uint64_t k) { + k ^= k >> 33; + k *= 0xff51afd7ed558ccdULL; + k ^= k >> 33; + k *= 0xc4ceb9fe1a85ec53ULL; + k ^= k >> 33; + + return k; +} + +template +struct PointerHasher { + size_t operator()(const T* p) const + { return fmix64(reinterpret_cast(p)); } +}; + +template +struct PointerHasher2 { + size_t operator()(const T* p) const + { return reinterpret_cast(p); } +}; + +TEST_F(FlatMapTest, perf_cmp_with_map_storing_pointers) { + const size_t REP = 4; + int* ptr[2048]; + for (size_t i = 0; i < ARRAY_SIZE(ptr); ++i) { + ptr[i] = new int; + } + + std::set m1; + butil::FlatSet > m2; + butil::hash_set > m3; + + std::vector r; + int sum; + butil::Timer tm; + + r.reserve(ARRAY_SIZE(ptr)*REP); + ASSERT_EQ(0, m2.init(ARRAY_SIZE(ptr))); + + for (size_t i = 0; i < ARRAY_SIZE(ptr); ++i) { + m1.insert(ptr[i]); + m2.insert(ptr[i]); + m3.insert(ptr[i]); + for (size_t j = 0; j < REP; ++j) { + r.push_back(ptr[i]); + } + } + ASSERT_EQ(m1.size(), m2.size()); + ASSERT_EQ(m1.size(), m3.size()); + + std::random_shuffle(r.begin(), r.end()); + + sum = 0; + tm.start(); + for (size_t i = 0; i < r.size(); ++i) { + sum += (m2.seek(r[i]) != NULL); + } + tm.stop(); + LOG(INFO) << "FlatMap takes " << tm.n_elapsed()/r.size(); + + sum = 0; + tm.start(); + for (size_t i = 0; i < r.size(); ++i) { + sum += (m1.find(r[i]) != m1.end()); + } + tm.stop(); + LOG(INFO) << "std::set takes " << tm.n_elapsed()/r.size(); + + sum = 0; + tm.start(); + for (size_t i = 0; i < r.size(); ++i) { + sum += (m3.find(r[i]) != m3.end()); + } + tm.stop(); + LOG(INFO) << "std::set takes " << tm.n_elapsed()/r.size() << " sum=" << sum; + + for (size_t i = 0; i < ARRAY_SIZE(ptr); ++i) { + delete ptr[i]; + } +} + + +int n_con = 0; +int n_cp_con = 0; +int n_des = 0; +int n_cp = 0; +struct Value { + Value() : x_(0) { ++n_con; } + Value(int x) : x_(x) { ++ n_con; } + Value (const Value& rhs) : x_(rhs.x_) { ++ n_cp_con; } + ~Value() { + ++ n_des; + // CHECK(false); + } + + Value& operator= (const Value& rhs) { + x_ = rhs.x_; + ++ n_cp; + return *this; + } + + bool operator== (const Value& rhs) const { return x_ == rhs.x_; } + bool operator!= (const Value& rhs) const { return x_ != rhs.x_; } + +friend std::ostream& operator<< (std::ostream& os, const Value& v) + { return os << v.x_; } + + int x_; +}; + +int n_con_key = 0; +int n_cp_con_key = 0; +int n_des_key = 0; +struct Key { + Key() : x_(0) { ++n_con_key; } + Key(int x) : x_(x) { ++ n_con_key; } + Key(const Key& rhs) : x_(rhs.x_) { ++ n_cp_con_key; } + void operator=(const Key& rhs) { x_ = rhs.x_; } + ~Key() { ++ n_des_key; } + int x_; +}; +struct KeyHasher { + size_t operator()(const Key& k) const { return k.x_; } +}; +struct KeyEqualTo { + size_t operator()(const Key& k1, const Key& k2) const + { return k1.x_ == k2.x_; } +}; + +TEST_F(FlatMapTest, key_value_are_not_constructed_before_first_insertion) { + butil::FlatMap m; + ASSERT_EQ(0, m.init(32)); + ASSERT_EQ(0, n_con_key); + ASSERT_EQ(0, n_cp_con_key); + ASSERT_EQ(0, n_con); + ASSERT_EQ(0, n_cp_con); + const Key k1 = 1; + ASSERT_EQ(1, n_con_key); + ASSERT_EQ(0, n_cp_con_key); + ASSERT_EQ(NULL, m.seek(k1)); + ASSERT_EQ(0u, m.erase(k1)); + ASSERT_EQ(1, n_con_key); + ASSERT_EQ(0, n_cp_con_key); + ASSERT_EQ(0, n_con); + ASSERT_EQ(0, n_cp_con); +} + +TEST_F(FlatMapTest, manipulate_uninitialized_map) { + butil::FlatMap m; + ASSERT_TRUE(m.initialized()); + ASSERT_EQ(NULL, m.seek(1)); + ASSERT_EQ(0u, m.erase(1)); + ASSERT_EQ(0u, m.size()); + ASSERT_TRUE(m.empty()); + ASSERT_EQ(BRPC_FLATMAP_DEFAULT_NBUCKET, m.bucket_count()); + ASSERT_EQ(80u, m.load_factor()); + m[1] = 1; + ASSERT_EQ(1UL, m.size()); + auto one = m.seek(1); + ASSERT_NE(nullptr, one); + ASSERT_EQ(1, *one); + + butil::FlatMap m2 = m; + one = m2.seek(1); + ASSERT_NE(nullptr, one); + ASSERT_EQ(1, *one); + m2[2] = 2; + ASSERT_EQ(2UL, m2.size()); + + m.swap(m2); + ASSERT_EQ(2UL, m.size()); + ASSERT_EQ(1UL, m2.size()); + auto two = m.seek(2); + ASSERT_NE(nullptr, two); + ASSERT_EQ(2, *two); + + ASSERT_EQ(1UL, m2.erase(1)); + ASSERT_EQ(0, m.init(32)); + one = m.seek(1); + ASSERT_NE(nullptr, one); + ASSERT_EQ(1, *one); + two = m.seek(2); + ASSERT_NE(nullptr, two); + ASSERT_EQ(2, *two); +} + +TEST_F(FlatMapTest, perf_small_string_map) { + butil::Timer tm1; + butil::Timer tm2; + butil::Timer tm3; + butil::Timer tm4; + + for (int i = 0; i < 10; ++i) { + tm3.start(); + butil::PooledMap m3; + m3["Content-type"] = "application/json"; + m3["Request-Id"] = "true"; + m3["Status-Code"] = "200"; + tm3.stop(); + + tm4.start(); + butil::CaseIgnoredFlatMap m4; + m4.init(16); + m4["Content-type"] = "application/json"; + m4["Request-Id"] = "true"; + m4["Status-Code"] = "200"; + tm4.stop(); + + tm1.start(); + butil::FlatMap m1; + m1.init(16); + m1["Content-type"] = "application/json"; + m1["Request-Id"] = "true"; + m1["Status-Code"] = "200"; + tm1.stop(); + + tm2.start(); + std::map m2; + m2["Content-type"] = "application/json"; + m2["Request-Id"] = "true"; + m2["Status-Code"] = "200"; + tm2.stop(); + + LOG(INFO) << "flatmap=" << tm1.n_elapsed() + << " ci_flatmap=" << tm4.n_elapsed() + << " map=" << tm2.n_elapsed() + << " pooled_map=" << tm3.n_elapsed(); + } +} + +TEST_F(FlatMapTest, sanity) { + typedef butil::FlatMap Map; + Map m; + ASSERT_TRUE(m.initialized()); + m.init(1000, 70); + ASSERT_TRUE(m.initialized()); + ASSERT_EQ(0UL, m.size()); + ASSERT_TRUE(m.empty()); + ASSERT_EQ(0UL, m._pool.count_allocated()); + + const uint64_t k1 = 1; + // hashed to the same bucket of k1. + const uint64_t k2 = k1 + m.bucket_count(); + + const uint64_t k3 = k1 + 1; + + // Initial insertion + m[k1] = 10; + ASSERT_EQ(1UL, m.size()); + ASSERT_FALSE(m.empty()); + long* p = m.seek(k1); + ASSERT_TRUE(p && *p == 10); + ASSERT_EQ(0UL, m._pool.count_allocated()); + + ASSERT_EQ(NULL, m.seek(k2)); + + // Override + m[k1] = 100; + ASSERT_EQ(1UL, m.size()); + ASSERT_FALSE(m.empty()); + p = m.seek(k1); + ASSERT_TRUE(p && *p == 100); + + // Insert another + m[k3] = 20; + ASSERT_EQ(2UL, m.size()); + ASSERT_FALSE(m.empty()); + p = m.seek(k3); + ASSERT_TRUE(p && *p == 20); + ASSERT_EQ(0UL, m._pool.count_allocated()); + + m[k2] = 30; + ASSERT_EQ(1UL, m._pool.count_allocated()); + ASSERT_EQ(0UL, m._pool.count_free()); + ASSERT_EQ(3UL, m.size()); + ASSERT_FALSE(m.empty()); + p = m.seek(k2); + ASSERT_TRUE(p && *p == 30); + + ASSERT_EQ(NULL, m.seek(2049)); + + Map::iterator it = m.begin(); + ASSERT_EQ(k1, it->first); + ++it; + ASSERT_EQ(k2, it->first); + ++it; + ASSERT_EQ(k3, it->first); + ++it; + ASSERT_EQ(m.end(), it); + + // Erase exist + ASSERT_EQ(1UL, m.erase(k1)); + ASSERT_EQ(2UL, m.size()); + ASSERT_FALSE(m.empty()); + ASSERT_EQ(NULL, m.seek(k1)); + ASSERT_EQ(30, *m.seek(k2)); + ASSERT_EQ(20, *m.seek(k3)); + ASSERT_EQ(1UL, m._pool.count_allocated()); + ASSERT_EQ(1UL, m._pool.count_free()); + + // default constructed + ASSERT_EQ(0, m[k1]); + ASSERT_EQ(0, m[5]); + ASSERT_EQ(0, m[1029]); + ASSERT_EQ(0, m[2053]); + + // Clear + m.clear(); + ASSERT_EQ(m.size(), 0ul); + ASSERT_TRUE(m.empty()); + ASSERT_EQ(NULL, m.seek(k1)); + ASSERT_EQ(NULL, m.seek(k2)); + ASSERT_EQ(NULL, m.seek(k3)); +} + +TEST_F(FlatMapTest, random_insert_erase) { + srand (0); + + { + butil::hash_map ref[2]; + typedef butil::FlatMap Map; + Map ht[2]; + ht[0].init (40); + ht[1] = ht[0]; + + for (int j = 0; j < 30; ++j) { + // Make snapshot + ht[1] = ht[0]; + ref[1] = ref[0]; + + for (int i = 0; i < 100000; ++i) { + int k = rand() % 0xFFFF; + int p = rand() % 1000; + ht[0].insert(k, i); + // LOG(INFO) << "i=" << i << " k=" << k; + + // ASSERT_EQ(n_con + n_cp_con, n_des * 2) + // << " n_con=" << n_con << " n_cp_con=" << n_cp_con << " n_des=" << n_des << " n_cp=" << n_cp; + ref[0][k] = i; + if (p < 600) { + } else if(p < 999) { + ht[0].erase (k); + ref[0].erase (k); + } else { + ht[0].clear(); + ref[0].clear(); + } + } + + // LOG(INFO) << "Check j=" << j; + // bi-check + for (int i=0; i<2; ++i) { + for (Map::iterator it = ht[i].begin(); it != ht[i].end(); ++it) + { + butil::hash_map::iterator it2 = ref[i].find(it->first); + ASSERT_TRUE (it2 != ref[i].end()); + ASSERT_EQ (it2->second, it->second); + } + + for (butil::hash_map::iterator it = ref[i].begin(); + it != ref[i].end(); ++it) + { + Value* p_value = ht[i].seek(it->first); + ASSERT_TRUE (p_value != NULL); + ASSERT_EQ (it->second, p_value->x_); + } + ASSERT_EQ (ht[i].size(), ref[i].size()); + } + } + + } + + ASSERT_EQ (n_con + n_cp_con, n_des) + // todo delete + << "n_con=" << n_con << " n_cp_con=" << n_cp_con << " n_des=" << n_des << " n_cp=" << n_cp; + + LOG(INFO) << "n_con:" << n_con << std::endl + << "n_cp_con:" << n_cp_con << std::endl + << "n_con+n_cp_con:" << n_con+n_cp_con << std::endl + << "n_des:" << n_des << std::endl + << "n_cp:" << n_cp; +} + +template +void perf_insert_erase(bool random, const T& value) { + size_t nkeys[] = { 100, 1000, 10000 }; + const size_t NPASS = ARRAY_SIZE(nkeys); + + std::vector keys; + butil::FlatMap id_map; + butil::MultiFlatMap multi_id_map; + std::map std_map; + butil::PooledMap pooled_map; + std::unordered_map std_unordered_map; + std::unordered_multimap std_unordered_multimap; + butil::hash_map hash_map; + butil::Timer id_tm, multi_id_tm, std_tm, pooled_tm, + std_unordered_tm, std_unordered_multi_tm, hash_tm; + + size_t max_nkeys = 0; + for (size_t i = 0; i < NPASS; ++i) { + max_nkeys = std::max(max_nkeys, nkeys[i]); + } + id_map.init((size_t)(nkeys[NPASS-1] * 1.5)); + multi_id_map.init((size_t)(nkeys[NPASS-1] * 1.5)); + + // Make DS hot + for (size_t i = 0; i < max_nkeys; ++i) { + id_map[i] = value; + std_map[i] = value; + pooled_map[i] = value; + hash_map[i] = value; + } + id_map.clear(); + std_map.clear(); + pooled_map.clear(); + std_unordered_map.clear(); + std_unordered_multimap.clear(); + hash_map.clear(); + + LOG(INFO) << "[ value = " << sizeof(T) << " bytes ]"; + for (size_t pass = 0; pass < NPASS; ++pass) { + int start = rand(); + keys.clear(); + for (size_t i = 0; i < nkeys[pass]; ++i) { + keys.push_back(start + i); + } + + if (random) { + random_shuffle(keys.begin(), keys.end()); + } + + id_map.clear(); + id_tm.start(); + for (size_t i = 0; i < keys.size(); ++i) { + id_map[keys[i]] = value; + } + id_tm.stop(); + + multi_id_map.clear(); + multi_id_tm.start(); + for (size_t i = 0; i < keys.size(); ++i) { + multi_id_map[keys[i]] = value; + } + multi_id_tm.stop(); + + std_map.clear(); + std_tm.start(); + for (size_t i = 0; i < keys.size(); ++i) { + std_map[keys[i]] = value; + } + std_tm.stop(); + + pooled_map.clear(); + pooled_tm.start(); + for (size_t i = 0; i < keys.size(); ++i) { + pooled_map[keys[i]] = value; + } + pooled_tm.stop(); + + std_unordered_map.clear(); + std_unordered_tm.start(); + for (size_t i = 0; i < keys.size(); ++i) { + std_unordered_map[keys[i]] = value; + } + std_unordered_tm.stop(); + + std_unordered_multimap.clear(); + std_unordered_multi_tm.start(); + for (size_t i = 0; i < keys.size(); ++i) { + std_unordered_multimap.insert({ keys[i], value }); + } + std_unordered_multi_tm.stop(); + + hash_map.clear(); + hash_tm.start(); + for (size_t i = 0; i < keys.size(); ++i) { + hash_map[keys[i]] = value; + } + hash_tm.stop(); + + LOG(INFO) << (random ? "Randomly" : "Sequentially") + << " inserting " << keys.size() + << " into FlatMap/MultiFlatMap/std::map/butil::PooledMap/" + "std::unordered_map/std::unordered_multimap/butil::hash_map takes " + << id_tm.n_elapsed() / keys.size() + << "/" << multi_id_tm.n_elapsed() / keys.size() + << "/" << std_tm.n_elapsed() / keys.size() + << "/" << pooled_tm.n_elapsed() / keys.size() + << "/" << std_unordered_tm.n_elapsed() / keys.size() + << "/" << std_unordered_multi_tm.n_elapsed() / keys.size() + << "/" << hash_tm.n_elapsed() / keys.size(); + + if (random) { + random_shuffle(keys.begin(), keys.end()); + } + + id_tm.start(); + for (size_t i = 0; i < keys.size(); ++i) { + id_map.erase(keys[i]); + } + id_tm.stop(); + + multi_id_tm.start(); + for (size_t i = 0; i < keys.size(); ++i) { + multi_id_map.erase(keys[i]); + } + multi_id_tm.stop(); + + std_tm.start(); + for (size_t i = 0; i < keys.size(); ++i) { + std_map.erase(keys[i]); + } + std_tm.stop(); + + pooled_tm.start(); + for (size_t i = 0; i < keys.size(); ++i) { + pooled_map.erase(keys[i]); + } + pooled_tm.stop(); + + std_unordered_tm.start(); + for (size_t i = 0; i < keys.size(); ++i) { + std_unordered_map.erase(keys[i]); + } + std_unordered_tm.stop(); + + std_unordered_multi_tm.start(); + for (size_t i = 0; i < keys.size(); ++i) { + std_unordered_multimap.erase(keys[i]); + } + std_unordered_multi_tm.stop(); + + hash_tm.start(); + for (size_t i = 0; i < keys.size(); ++i) { + hash_map.erase(keys[i]); + } + hash_tm.stop(); + + LOG(INFO) << (random ? "Randomly" : "Sequentially") + << " erasing " << keys.size() + << " from FlatMap/MultiFlatMap/std::map/butil::PooledMap/" + "std::unordered_map/std::unordered_multimap/butil::hash_map takes " + << id_tm.n_elapsed() / keys.size() + << "/" << multi_id_tm.n_elapsed() / keys.size() + << "/" << std_tm.n_elapsed() / keys.size() + << "/" << pooled_tm.n_elapsed() / keys.size() + << "/" << std_unordered_tm.n_elapsed() / keys.size() + << "/" << std_unordered_multi_tm.n_elapsed() / keys.size() + << "/" << hash_tm.n_elapsed() / keys.size(); + } +} + +template +void perf_seek(const T& value) { + size_t nkeys[] = { 100, 1000, 10000 }; + const size_t NPASS = ARRAY_SIZE(nkeys); + std::vector keys; + std::vector rkeys; + butil::FlatMap id_map; + butil::MultiFlatMap multi_id_map; + std::map std_map; + butil::PooledMap pooled_map; + std::unordered_map std_unordered_map; + std::unordered_multimap std_unordered_multimap; + butil::hash_map hash_map; + butil::Timer id_tm, multi_id_tm, std_tm, pooled_tm, + std_unordered_tm, std_unordered_multi_tm, hash_tm; + + id_map.init((size_t)(nkeys[NPASS-1] * 1.5)); + multi_id_map.init((size_t)(nkeys[NPASS-1] * 1.5)); + LOG(INFO) << "[ value = " << sizeof(T) << " bytes ]"; + for (size_t pass = 0; pass < NPASS; ++pass) { + int start = rand(); + keys.clear(); + for (size_t i = 0; i < nkeys[pass]; ++i) { + keys.push_back(start + i); + } + + id_map.clear(); + for (size_t i = 0; i < keys.size(); ++i) { + id_map[keys[i]] = value; + } + + multi_id_map.clear(); + for (size_t i = 0; i < keys.size(); ++i) { + multi_id_map[keys[i]] = value; + } + + std_map.clear(); + for (size_t i = 0; i < keys.size(); ++i) { + std_map[keys[i]] = value; + } + + pooled_map.clear(); + for (size_t i = 0; i < keys.size(); ++i) { + pooled_map[keys[i]] = value; + } + + std_unordered_map.clear(); + for (size_t i = 0; i < keys.size(); ++i) { + std_unordered_map[keys[i]] = value; + } + + std_unordered_multimap.clear(); + for (size_t i = 0; i < keys.size(); ++i) { + std_unordered_multimap.insert({ keys[i], value }); + } + + hash_map.clear(); + for (size_t i = 0; i < keys.size(); ++i) { + hash_map[keys[i]] = value; + } + + random_shuffle(keys.begin(), keys.end()); + + long sum = 0; + id_tm.start(); + for (size_t i = 0; i < keys.size(); ++i) { + sum += *(long*)id_map.seek(keys[i]); + } + id_tm.stop(); + + multi_id_tm.start(); + for (size_t i = 0; i < keys.size(); ++i) { + sum += *(long*)multi_id_map.seek(keys[i]); + } + multi_id_tm.stop(); + + std_tm.start(); + for (size_t i = 0; i < keys.size(); ++i) { + sum += (long&)std_map.find(keys[i])->second; + } + std_tm.stop(); + + pooled_tm.start(); + for (size_t i = 0; i < keys.size(); ++i) { + sum += (long&)pooled_map.find(keys[i])->second; + } + pooled_tm.stop(); + + std_unordered_tm.start(); + for (size_t i = 0; i < keys.size(); ++i) { + sum += (long&)std_unordered_map[keys[i]]; + } + std_unordered_tm.stop(); + + std_unordered_multi_tm.start(); + for (size_t i = 0; i < keys.size(); ++i) { + sum += (long&)std_unordered_multimap.find(keys[i])->second; + } + std_unordered_multi_tm.stop(); + + hash_tm.start(); + for (size_t i = 0; i < keys.size(); ++i) { + sum += (long&)hash_map.find(keys[i])->second; + } + hash_tm.stop(); + + LOG(INFO) << "Seeking " << keys.size() + << " from FlatMap/MultiFlatMap/std::map/butil::PooledMap/" + "std::unordered_map/std::unordered_multimap/butil::hash_map takes " + << id_tm.n_elapsed() / keys.size() + << "/" << multi_id_tm.n_elapsed() / keys.size() + << "/" << std_tm.n_elapsed() / keys.size() + << "/" << pooled_tm.n_elapsed() / keys.size() + << "/" << std_unordered_tm.n_elapsed() / keys.size() + << "/" << std_unordered_multi_tm.n_elapsed() / keys.size() + << "/" << hash_tm.n_elapsed() / keys.size(); + } +} + +struct Dummy1 { + long data[4]; +}; +struct Dummy2 { + long data[16]; +}; + +TEST_F(FlatMapTest, perf) { + perf_insert_erase(false, 100); + perf_insert_erase(false, Dummy1()); + perf_insert_erase(false, Dummy2()); + perf_insert_erase(true, 100); + perf_insert_erase(true, Dummy1()); + perf_insert_erase(true, Dummy2()); + perf_seek(100); + perf_seek(Dummy1()); + perf_seek(Dummy2()); + perf_seek(100); + perf_seek(Dummy1()); + perf_seek(Dummy2()); +} + +TEST_F(FlatMapTest, copy) { + butil::FlatMap m1; + butil::FlatMap m2; + ASSERT_EQ(0, m1.init(32)); + m1[1] = 1; + m1[2] = 2; + m2 = m1; + ASSERT_FALSE(m1.is_too_crowded(m1.size())); + ASSERT_FALSE(m2.is_too_crowded(m1.size())); +} + +TEST_F(FlatMapTest, multi) { + // Construct non-POD values w/o copy-construction. + g_foo_ctor = 0; + g_foo_copy_ctor = 0; + g_foo_assign = 0; + butil::MultiFlatMap map; + size_t bucket_count = 32; + ASSERT_EQ(0, map.init(bucket_count)); + ASSERT_EQ(0, g_foo_ctor); + ASSERT_EQ(0, g_foo_copy_ctor); + ASSERT_EQ(0, g_foo_assign); + Foo& f1 = map[1]; + ASSERT_EQ(1UL, map.size()); + ASSERT_EQ(1, g_foo_ctor); + ASSERT_EQ(0, g_foo_copy_ctor); + ASSERT_EQ(0, g_foo_assign); + Foo& f2 = map[1]; + ASSERT_EQ(2UL, map.size()); + ASSERT_EQ(2, g_foo_ctor); + ASSERT_EQ(0, g_foo_copy_ctor); + ASSERT_EQ(0, g_foo_assign); + Foo f3; + Foo& f4 = *map.insert(1, f3); + ASSERT_EQ(3UL, map.size()); + ASSERT_EQ(4, g_foo_ctor); + ASSERT_EQ(0, g_foo_copy_ctor); + ASSERT_EQ(1, g_foo_assign); + ASSERT_EQ(&f1, map.seek(1)); + std::vector f_vec = map.seek_all(1); + ASSERT_EQ(3UL, f_vec.size()); + ASSERT_NE(f_vec.end(), std::find(f_vec.begin(), f_vec.end(), &f1)); + ASSERT_NE(f_vec.end(), std::find(f_vec.begin(), f_vec.end(), &f2)); + ASSERT_NE(f_vec.end(), std::find(f_vec.begin(), f_vec.end(), &f4)); + + ASSERT_EQ(bucket_count, map.bucket_count()); + int same_bucket_key = 1 + bucket_count; + butil::DefaultHasher hasher; + ASSERT_EQ(butil::flatmap_mod(hasher(1), bucket_count), + butil::flatmap_mod(hasher(same_bucket_key), bucket_count)); + ASSERT_EQ(0UL, map.erase(same_bucket_key)); + Foo& f5 = map[same_bucket_key]; + ASSERT_EQ(4UL, map.size()); + ASSERT_EQ(&f5, map.seek(same_bucket_key)); + ASSERT_EQ(1UL, map.seek_all(same_bucket_key).size()); + ASSERT_EQ(5, g_foo_ctor); + ASSERT_EQ(0, g_foo_copy_ctor); + ASSERT_EQ(1, g_foo_assign); + ASSERT_EQ(&f5, map.seek(same_bucket_key)); + ASSERT_EQ(3u, map.erase(1)); + ASSERT_EQ(1UL, map.size()); + ASSERT_EQ(nullptr, map.seek(1)); + ASSERT_TRUE(map.seek_all(1).empty()); + // Value node of same_bucket_key is the last one in the bucket, + // so it has been moved to the first node. + ASSERT_EQ(&f1, map.seek(same_bucket_key)); + ASSERT_EQ(1UL, map.erase(same_bucket_key)); + ASSERT_EQ(0UL, map.size()); + ASSERT_EQ(nullptr, map.seek(same_bucket_key)); + ASSERT_TRUE(map.seek_all(same_bucket_key).empty()); + + // Increase the capacity of bucket when hash collision occur and map is crowded. + for (size_t i = 0; i < bucket_count + 1; ++i) { + map[i] = Foo(); + } + ASSERT_EQ(bucket_count + 1, map.size()); + ASSERT_EQ(butil::flatmap_round(bucket_count + 1), map.bucket_count()); + + // No need to Increase the capacity of bucket when key is already in the map. + for (size_t i = 0; i < bucket_count + 1; ++i) { + map[1] = Foo(); + } + ASSERT_EQ((bucket_count + 1) * 2, map.size()); + ASSERT_EQ(butil::flatmap_round(bucket_count + 1), map.bucket_count()); + + // Zeroize POD values. + butil::MultiFlatMap map2; + ASSERT_EQ(0, map2.init(32)); + Bar& g = map2[1]; + ASSERT_EQ(0, g.x); + g.x = 123; + ASSERT_EQ(1u, map2.erase(1)); + ASSERT_EQ(123, g.x); // g is still accessible in this case. + Bar& g2 = map2[1]; + ASSERT_EQ(&g, &g2); + ASSERT_EQ(0, g2.x); +} + +} diff --git a/test/fuzzing/fuzz_amf.cpp b/test/fuzzing/fuzz_amf.cpp new file mode 100644 index 0000000..60628ad --- /dev/null +++ b/test/fuzzing/fuzz_amf.cpp @@ -0,0 +1,81 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/amf.h" +#include "butil/iobuf.h" + +#define kMinInputLength 5 +#define kMaxInputLength 4096 + +extern "C" int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size < kMinInputLength || size > kMaxInputLength){ + return 1; + } + + uint8_t mode = data[0] % 3; + const uint8_t *payload = data + 1; + size_t payload_size = size - 1; + + butil::IOBuf buf; + buf.append(payload, payload_size); + + switch (mode) { + case 0: { + // Read AMF object + butil::IOBufAsZeroCopyInputStream zc_stream(buf); + brpc::AMFInputStream stream(&zc_stream); + brpc::AMFObject obj; + brpc::ReadAMFObject(&obj, &stream); + break; + } + case 1: { + // Read AMF string + butil::IOBufAsZeroCopyInputStream zc_stream(buf); + brpc::AMFInputStream stream(&zc_stream); + std::string val; + brpc::ReadAMFString(&val, &stream); + break; + } + case 2: { + // Read raw AMF fields by consuming the stream directly + butil::IOBufAsZeroCopyInputStream zc_stream(buf); + brpc::AMFInputStream stream(&zc_stream); + uint8_t marker; + while (stream.good() && stream.cut_u8(&marker) == 1) { + // Try to identify marker type and read value + if (marker == brpc::AMF_MARKER_NUMBER) { + uint64_t num; + stream.cut_u64(&num); + } else if (marker == brpc::AMF_MARKER_BOOLEAN) { + uint8_t b; + stream.cut_u8(&b); + } else if (marker == brpc::AMF_MARKER_STRING) { + uint16_t len; + if (stream.cut_u16(&len) == 2 && len < 1024) { + char tmp[1024]; + stream.cutn(tmp, len); + } + } + } + break; + } + } + + return 0; +} diff --git a/test/fuzzing/fuzz_baidu_rpc.cpp b/test/fuzzing/fuzz_baidu_rpc.cpp new file mode 100644 index 0000000..0302f0c --- /dev/null +++ b/test/fuzzing/fuzz_baidu_rpc.cpp @@ -0,0 +1,41 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/baidu_rpc_protocol.h" +#include "fuzz_common.h" + +#define kMinInputLength 5 +#define kMaxInputLength 4096 + +extern "C" int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size < kMinInputLength || size > kMaxInputLength){ + return 1; + } + + std::string input(reinterpret_cast(data), size); + butil::IOBuf buf; + buf.append(input); + + brpc::Socket* sock = get_fuzz_socket(); + if (sock == NULL) { + return 0; + } + brpc::policy::ParseRpcMessage(&buf, sock, false, NULL); + return 0; +} diff --git a/test/fuzzing/fuzz_butil.cpp b/test/fuzzing/fuzz_butil.cpp new file mode 100644 index 0000000..470d1e9 --- /dev/null +++ b/test/fuzzing/fuzz_butil.cpp @@ -0,0 +1,52 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "butil/base64.h" +#include "butil/crc32c.h" +#include "butil/hash.h" +#include "butil/sha1.h" + +#define kMinInputLength 5 +#define kMaxInputLength 1024 + +extern "C" int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size < kMinInputLength || size > kMaxInputLength){ + return 1; + } + + std::string input(reinterpret_cast(data), size); + + { + std::string encoded; + std::string decoded; + butil::Base64Encode(input, &encoded); + butil::Base64Decode(input, &decoded); + } + { + butil::crc32c::Value(reinterpret_cast(data), size); + } + { + butil::Hash(input); + } + { + butil::SHA1HashString(input); + } + + return 0; +} diff --git a/test/fuzzing/fuzz_butil_seed_corpus/base64_decoded.txt b/test/fuzzing/fuzz_butil_seed_corpus/base64_decoded.txt new file mode 100644 index 0000000..771f075 --- /dev/null +++ b/test/fuzzing/fuzz_butil_seed_corpus/base64_decoded.txt @@ -0,0 +1 @@ +aGVsbG8gd29ybGQ= diff --git a/test/fuzzing/fuzz_butil_seed_corpus/base64_encoded.txt b/test/fuzzing/fuzz_butil_seed_corpus/base64_encoded.txt new file mode 100644 index 0000000..3b18e51 --- /dev/null +++ b/test/fuzzing/fuzz_butil_seed_corpus/base64_encoded.txt @@ -0,0 +1 @@ +hello world diff --git a/test/fuzzing/fuzz_butil_seed_corpus/crc32c.data b/test/fuzzing/fuzz_butil_seed_corpus/crc32c.data new file mode 100644 index 0000000..7e12119 Binary files /dev/null and b/test/fuzzing/fuzz_butil_seed_corpus/crc32c.data differ diff --git a/test/fuzzing/fuzz_common.h b/test/fuzzing/fuzz_common.h new file mode 100644 index 0000000..604306b --- /dev/null +++ b/test/fuzzing/fuzz_common.h @@ -0,0 +1,44 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_TEST_FUZZING_FUZZ_COMMON_H +#define BRPC_TEST_FUZZING_FUZZ_COMMON_H + +#include "brpc/socket.h" +#include "butil/endpoint.h" + +// Create a valid Socket for use in fuzz harnesses that need a non-NULL Socket*. +// Returns a raw Socket* that remains valid for the lifetime of the process +// (held by the static SocketUniquePtr). +inline brpc::Socket* get_fuzz_socket() { + static brpc::SocketId sid = 0; + static brpc::SocketUniquePtr sock_ptr; + static bool initialized = false; + + if (!initialized) { + brpc::SocketOptions options; + options.remote_side = butil::EndPoint(butil::IP_ANY, 7777); + if (brpc::Socket::Create(options, &sid) == 0 && + brpc::Socket::Address(sid, &sock_ptr) == 0) { + initialized = true; + } + } + + return sock_ptr.get(); +} + +#endif // BRPC_TEST_FUZZING_FUZZ_COMMON_H diff --git a/test/fuzzing/fuzz_couchbase.cpp b/test/fuzzing/fuzz_couchbase.cpp new file mode 100644 index 0000000..8807005 --- /dev/null +++ b/test/fuzzing/fuzz_couchbase.cpp @@ -0,0 +1,41 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/couchbase_protocol.h" +#include "fuzz_common.h" + +#define kMinInputLength 5 +#define kMaxInputLength 4096 + +extern "C" int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size < kMinInputLength || size > kMaxInputLength){ + return 1; + } + + std::string input(reinterpret_cast(data), size); + butil::IOBuf buf; + buf.append(input); + + brpc::Socket* sock = get_fuzz_socket(); + if (sock == NULL) { + return 0; + } + brpc::policy::ParseCouchbaseMessage(&buf, sock, false, NULL); + return 0; +} diff --git a/test/fuzzing/fuzz_esp.cpp b/test/fuzzing/fuzz_esp.cpp new file mode 100644 index 0000000..d1c6649 --- /dev/null +++ b/test/fuzzing/fuzz_esp.cpp @@ -0,0 +1,43 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/esp_protocol.h" +#include "fuzz_common.h" + +#define kMinInputLength 5 +#define kMaxInputLength 1024 + +extern "C" int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size < kMinInputLength || size > kMaxInputLength){ + return 1; + } + + std::string input(reinterpret_cast(data), size); + + butil::IOBuf buf; + buf.append(input); + + brpc::Socket* sock = get_fuzz_socket(); + if (sock == NULL) { + return 0; + } + brpc::policy::ParseEspMessage(&buf, sock, false, NULL); + + return 0; +} diff --git a/test/fuzzing/fuzz_hpack.cpp b/test/fuzzing/fuzz_hpack.cpp new file mode 100644 index 0000000..0ee8131 --- /dev/null +++ b/test/fuzzing/fuzz_hpack.cpp @@ -0,0 +1,43 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/details/hpack.h" +#include "butil/logging.h" + +#define kMinInputLength 5 +#define kMaxInputLength 1024 + +extern "C" int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size < kMinInputLength || size > kMaxInputLength){ + return 1; + } + + std::string input(reinterpret_cast(data), size); + + butil::IOBuf buf; + brpc::HPacker p2; + brpc::HPacker::Header h2; + + p2.Init(4096); + buf.append(input); + + p2.Decode(&buf, &h2); + + return 0; +} diff --git a/test/fuzzing/fuzz_hpack_seed_corpus/header_never_indexed.hpack b/test/fuzzing/fuzz_hpack_seed_corpus/header_never_indexed.hpack new file mode 100644 index 0000000..b8cfac0 --- /dev/null +++ b/test/fuzzing/fuzz_hpack_seed_corpus/header_never_indexed.hpack @@ -0,0 +1 @@ +passwordsecret \ No newline at end of file diff --git a/test/fuzzing/fuzz_hpack_seed_corpus/header_with_indexing.hpack b/test/fuzzing/fuzz_hpack_seed_corpus/header_with_indexing.hpack new file mode 100644 index 0000000..3d994aa --- /dev/null +++ b/test/fuzzing/fuzz_hpack_seed_corpus/header_with_indexing.hpack @@ -0,0 +1,2 @@ +@ +custom-key custom-header \ No newline at end of file diff --git a/test/fuzzing/fuzz_hpack_seed_corpus/header_without_indexing.hpack b/test/fuzzing/fuzz_hpack_seed_corpus/header_without_indexing.hpack new file mode 100644 index 0000000..0f91566 --- /dev/null +++ b/test/fuzzing/fuzz_hpack_seed_corpus/header_without_indexing.hpack @@ -0,0 +1 @@ + /sample/path \ No newline at end of file diff --git a/test/fuzzing/fuzz_hpack_seed_corpus/requests_without_huffman.hpack b/test/fuzzing/fuzz_hpack_seed_corpus/requests_without_huffman.hpack new file mode 100644 index 0000000..4a32d98 --- /dev/null +++ b/test/fuzzing/fuzz_hpack_seed_corpus/requests_without_huffman.hpack @@ -0,0 +1,2 @@ +@ +custom-key custom-value \ No newline at end of file diff --git a/test/fuzzing/fuzz_hpack_seed_corpus/responses_without_huffman.hpack b/test/fuzzing/fuzz_hpack_seed_corpus/responses_without_huffman.hpack new file mode 100644 index 0000000..5bda49d --- /dev/null +++ b/test/fuzzing/fuzz_hpack_seed_corpus/responses_without_huffman.hpack @@ -0,0 +1 @@ +H302XprivateaMon, 21 Oct 2013 20:13:21 GMTnhttps://www.example.com \ No newline at end of file diff --git a/test/fuzzing/fuzz_http.cpp b/test/fuzzing/fuzz_http.cpp new file mode 100644 index 0000000..c621d50 --- /dev/null +++ b/test/fuzzing/fuzz_http.cpp @@ -0,0 +1,45 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/details/http_message.h" +#include "brpc/policy/http_rpc_protocol.h" + +#define kMinInputLength 5 +#define kMaxInputLength 1024 + +extern "C" int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size < kMinInputLength || size > kMaxInputLength){ + return 1; + } + + std::string input(reinterpret_cast(data), size); + + { + butil::IOBuf buf; + buf.append(input); + brpc::HttpMessage http_message; + http_message.ParseFromIOBuf(buf); + } + { + brpc::HttpMessage http_message; + http_message.ParseFromArray((char *)data, size); + } + + return 0; +} diff --git a/test/fuzzing/fuzz_http_parser.cpp b/test/fuzzing/fuzz_http_parser.cpp new file mode 100644 index 0000000..0c6b792 --- /dev/null +++ b/test/fuzzing/fuzz_http_parser.cpp @@ -0,0 +1,97 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "brpc/details/http_parser.h" +#include "brpc/http_method.h" + +#define kMinInputLength 5 +#define kMaxInputLength 4096 + +static int on_url_cb(brpc::http_parser* p, const char* at, size_t length) { return 0; } +static int on_header_field_cb(brpc::http_parser* p, const char* at, size_t length) { return 0; } +static int on_header_value_cb(brpc::http_parser* p, const char* at, size_t length) { return 0; } +static int on_body_cb(brpc::http_parser* p, const char* at, size_t length) { return 0; } +static int on_message_begin_cb(brpc::http_parser* p) { return 0; } +static int on_headers_complete_cb(brpc::http_parser* p) { return 0; } +static int on_message_complete_cb(brpc::http_parser* p) { return 0; } + +extern "C" int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size < kMinInputLength || size > kMaxInputLength){ + return 1; + } + + // Use first byte to select mode + uint8_t mode = data[0] % 4; + const uint8_t *payload = data + 1; + size_t payload_size = size - 1; + + switch (mode) { + case 0: { + // Fuzz low-level HTTP request parsing + brpc::http_parser parser; + brpc::http_parser_init(&parser, brpc::HTTP_REQUEST); + brpc::http_parser_settings settings; + memset(&settings, 0, sizeof(settings)); + settings.on_url = on_url_cb; + settings.on_header_field = on_header_field_cb; + settings.on_header_value = on_header_value_cb; + settings.on_body = on_body_cb; + settings.on_message_begin = on_message_begin_cb; + settings.on_headers_complete = on_headers_complete_cb; + settings.on_message_complete = on_message_complete_cb; + brpc::http_parser_execute(&parser, &settings, + reinterpret_cast(payload), payload_size); + break; + } + case 1: { + // Fuzz low-level HTTP response parsing + brpc::http_parser parser; + brpc::http_parser_init(&parser, brpc::HTTP_RESPONSE); + brpc::http_parser_settings settings; + memset(&settings, 0, sizeof(settings)); + settings.on_url = on_url_cb; + settings.on_header_field = on_header_field_cb; + settings.on_header_value = on_header_value_cb; + settings.on_body = on_body_cb; + settings.on_message_begin = on_message_begin_cb; + settings.on_headers_complete = on_headers_complete_cb; + settings.on_message_complete = on_message_complete_cb; + brpc::http_parser_execute(&parser, &settings, + reinterpret_cast(payload), payload_size); + break; + } + case 2: { + // Fuzz URL parsing (not connect) + brpc::http_parser_url u; + brpc::http_parser_parse_url(reinterpret_cast(payload), + payload_size, 0, &u); + break; + } + case 3: { + // Fuzz URL parsing (connect mode) + brpc::http_parser_url u; + brpc::http_parser_parse_url(reinterpret_cast(payload), + payload_size, 1, &u); + break; + } + } + + return 0; +} diff --git a/test/fuzzing/fuzz_http_seed_corpus/http_request.http b/test/fuzzing/fuzz_http_seed_corpus/http_request.http new file mode 100644 index 0000000..1d05662 --- /dev/null +++ b/test/fuzzing/fuzz_http_seed_corpus/http_request.http @@ -0,0 +1,9 @@ +GET /path/file.html?sdfsdf=sdfs HTTP/1.0 +From: someuser@jmarshall.com +User-Agent: HTTPTool/1.0 +Content-Type: json +Content-Length: 19 +Host: sdlfjslfd +Accept: */* + +Message Body sdfsdf diff --git a/test/fuzzing/fuzz_http_seed_corpus/http_request_v2.http b/test/fuzzing/fuzz_http_seed_corpus/http_request_v2.http new file mode 100644 index 0000000..9923cd2 --- /dev/null +++ b/test/fuzzing/fuzz_http_seed_corpus/http_request_v2.http @@ -0,0 +1,23 @@ +GET /CloudApiControl/HttpServer/telematics/v3/weather?location=%E6%B5%B7%E5%8D%97%E7%9C%81%E7%9B%B4%E8%BE%96%E5%8E%BF%E7%BA%A7%E8%A1%8C%E6%94%BF%E5%8D%95%E4%BD%8D&output=json&ak=0l3FSP6qA0WbOzGRaafbmczS HTTP/1.1 +X-Host: api.map.baidu.com +X-Forwarded-Proto: http +Host: api.map.baidu.com +User-Agent: IME/Android/4.4.2/N80.QHD.LT.X10.V3/N80.QHD.LT.X10.V3.20150812.031915 +Accept: application/json +Accept-Charset: UTF-8,*;q=0.5 +Accept-Encoding: deflate,sdch +Accept-Language: zh-CN,en-US;q=0.8,zh;q=0.6 +Bfe-Atk: NORMAL_BROWSER +Bfe_logid: 8767802212038413243 +Bfeip: 10.26.124.40 +CLIENTIP: 119.29.102.26 +CLIENTPORT: 59863 +Cache-Control: max-age=0 +Content-Type: application/json;charset=utf8 +X-Forwarded-For: 119.29.102.26 +X-Forwarded-Port: 59863 +X-Ime-Imei: 35629601890905 +X_BD_LOGID: 3959476981 +X_BD_LOGID64: 16815814797661447369 +X_BD_PRODUCT: map +X_BD_SUBSYS: apimap diff --git a/test/fuzzing/fuzz_hulu.cpp b/test/fuzzing/fuzz_hulu.cpp new file mode 100644 index 0000000..50cc62b --- /dev/null +++ b/test/fuzzing/fuzz_hulu.cpp @@ -0,0 +1,43 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/hulu_pbrpc_protocol.h" +#include "fuzz_common.h" + +#define kMinInputLength 5 +#define kMaxInputLength 1024 + +extern "C" int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size < kMinInputLength || size > kMaxInputLength){ + return 1; + } + + std::string input(reinterpret_cast(data), size); + + butil::IOBuf buf; + buf.append(input); + + brpc::Socket* sock = get_fuzz_socket(); + if (sock == NULL) { + return 0; + } + brpc::policy::ParseHuluMessage(&buf, sock, false, NULL); + + return 0; +} diff --git a/test/fuzzing/fuzz_json.cpp b/test/fuzzing/fuzz_json.cpp new file mode 100644 index 0000000..3a0335f --- /dev/null +++ b/test/fuzzing/fuzz_json.cpp @@ -0,0 +1,37 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "json2pb/json_to_pb.h" +#include "addressbook1.pb.h" + +#define kMinInputLength 5 +#define kMaxInputLength 1024 + +extern "C" int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size < kMinInputLength || size > kMaxInputLength){ + return 1; + } + + std::string error; + JsonContextBody jsondata; + std::string input_data((char *)data,size); + json2pb::JsonToProtoMessage(input_data, &jsondata, &error); + + return 0; +} diff --git a/test/fuzzing/fuzz_json_seed_corpus/info1.json b/test/fuzzing/fuzz_json_seed_corpus/info1.json new file mode 100644 index 0000000..c708dc1 --- /dev/null +++ b/test/fuzzing/fuzz_json_seed_corpus/info1.json @@ -0,0 +1 @@ +{"judge":false, "spur":-2, "data":[], "info":[],"content":[]} \ No newline at end of file diff --git a/test/fuzzing/fuzz_json_seed_corpus/info2.json b/test/fuzzing/fuzz_json_seed_corpus/info2.json new file mode 100644 index 0000000..43aee79 --- /dev/null +++ b/test/fuzzing/fuzz_json_seed_corpus/info2.json @@ -0,0 +1 @@ +[{"container": 1000, "host": 1000, "size": 2}] \ No newline at end of file diff --git a/test/fuzzing/fuzz_json_seed_corpus/info3.json b/test/fuzzing/fuzz_json_seed_corpus/info3.json new file mode 100644 index 0000000..db63ece --- /dev/null +++ b/test/fuzzing/fuzz_json_seed_corpus/info3.json @@ -0,0 +1 @@ +{"content":[{"distance":1,"unknown_member":2,"ext":{"age":1666666666, "databyte":"d2VsY29tZQ==", "enumtype":1},"uid":"someone"},{"distance":10,"unknown_member":20,"ext":{"age":1666666660, "databyte":"d2VsY29tZQ==","enumtype":2},"uid":"someone0"}], "judge":false,"spur":2, "data":[1,2,3,4,5,6,7,8,9,10]} \ No newline at end of file diff --git a/test/fuzzing/fuzz_memcache.cpp b/test/fuzzing/fuzz_memcache.cpp new file mode 100644 index 0000000..b60d527 --- /dev/null +++ b/test/fuzzing/fuzz_memcache.cpp @@ -0,0 +1,41 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/memcache_binary_protocol.h" +#include "fuzz_common.h" + +#define kMinInputLength 5 +#define kMaxInputLength 4096 + +extern "C" int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size < kMinInputLength || size > kMaxInputLength){ + return 1; + } + + std::string input(reinterpret_cast(data), size); + butil::IOBuf buf; + buf.append(input); + + brpc::Socket* sock = get_fuzz_socket(); + if (sock == NULL) { + return 0; + } + brpc::policy::ParseMemcacheMessage(&buf, sock, false, NULL); + return 0; +} diff --git a/test/fuzzing/fuzz_mongo.cpp b/test/fuzzing/fuzz_mongo.cpp new file mode 100644 index 0000000..88db824 --- /dev/null +++ b/test/fuzzing/fuzz_mongo.cpp @@ -0,0 +1,41 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/mongo_protocol.h" +#include "fuzz_common.h" + +#define kMinInputLength 5 +#define kMaxInputLength 4096 + +extern "C" int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size < kMinInputLength || size > kMaxInputLength){ + return 1; + } + + std::string input(reinterpret_cast(data), size); + butil::IOBuf buf; + buf.append(input); + + brpc::Socket* sock = get_fuzz_socket(); + if (sock == NULL) { + return 0; + } + brpc::policy::ParseMongoMessage(&buf, sock, false, NULL); + return 0; +} diff --git a/test/fuzzing/fuzz_redis.cpp b/test/fuzzing/fuzz_redis.cpp new file mode 100644 index 0000000..6f4368d --- /dev/null +++ b/test/fuzzing/fuzz_redis.cpp @@ -0,0 +1,48 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include + +#define kMinInputLength 5 +#define kMaxInputLength 1024 + +extern "C" int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size < kMinInputLength || size > kMaxInputLength){ + return 1; + } + + std::string input(reinterpret_cast(data), size); + butil::IOBuf buf; + buf.append(input); + { + butil::Arena arena; + brpc::RedisCommandParser parser; + std::vector command_out; + parser.Consume(buf, &command_out, &arena); + } + { + butil::Arena arena; + brpc::RedisReply r2(&arena); + r2.ConsumePartialIOBuf(buf); + } + + return 0; +} + diff --git a/test/fuzzing/fuzz_redis_seed_corpus/command_parser.redis b/test/fuzzing/fuzz_redis_seed_corpus/command_parser.redis new file mode 100644 index 0000000..7b013c6 --- /dev/null +++ b/test/fuzzing/fuzz_redis_seed_corpus/command_parser.redis @@ -0,0 +1,7 @@ +*3 +$3 +set +$3 +abc +$3 +def diff --git a/test/fuzzing/fuzz_redis_seed_corpus/request.redis b/test/fuzzing/fuzz_redis_seed_corpus/request.redis new file mode 100644 index 0000000..382063d --- /dev/null +++ b/test/fuzzing/fuzz_redis_seed_corpus/request.redis @@ -0,0 +1 @@ +set a '' \ No newline at end of file diff --git a/test/fuzzing/fuzz_shead.cpp b/test/fuzzing/fuzz_shead.cpp new file mode 100644 index 0000000..720b4e8 --- /dev/null +++ b/test/fuzzing/fuzz_shead.cpp @@ -0,0 +1,43 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/public_pbrpc_meta.pb.h" +#include "brpc/policy/public_pbrpc_protocol.h" +#include "brpc/policy/most_common_message.h" +#include "fuzz_common.h" + +#define kMinInputLength 5 +#define kMaxInputLength 1024 + +extern "C" int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size < kMinInputLength || size > kMaxInputLength){ + return 1; + } + + std::string input(reinterpret_cast(data), size); + butil::IOBuf buf; + buf.append(input); + + brpc::Socket* sock = get_fuzz_socket(); + if (sock == NULL) { + return 0; + } + brpc::policy::ParseNsheadMessage(&buf, sock, false, NULL); + + return 0; +} diff --git a/test/fuzzing/fuzz_sofa.cpp b/test/fuzzing/fuzz_sofa.cpp new file mode 100644 index 0000000..a5dc418 --- /dev/null +++ b/test/fuzzing/fuzz_sofa.cpp @@ -0,0 +1,44 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/sofa_pbrpc_meta.pb.h" +#include "brpc/policy/sofa_pbrpc_protocol.h" +#include "brpc/policy/most_common_message.h" +#include "fuzz_common.h" + +#define kMinInputLength 5 +#define kMaxInputLength 1024 + +extern "C" int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size < kMinInputLength || size > kMaxInputLength){ + return 1; + } + + std::string input(reinterpret_cast(data), size); + + butil::IOBuf buf; + buf.append(input); + + brpc::Socket* sock = get_fuzz_socket(); + if (sock == NULL) { + return 0; + } + brpc::policy::ParseSofaMessage(&buf, sock, false, NULL); + return 0; +} diff --git a/test/fuzzing/fuzz_streaming.cpp b/test/fuzzing/fuzz_streaming.cpp new file mode 100644 index 0000000..0b58d7b --- /dev/null +++ b/test/fuzzing/fuzz_streaming.cpp @@ -0,0 +1,41 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/streaming_rpc_protocol.h" +#include "fuzz_common.h" + +#define kMinInputLength 5 +#define kMaxInputLength 4096 + +extern "C" int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size < kMinInputLength || size > kMaxInputLength){ + return 1; + } + + std::string input(reinterpret_cast(data), size); + butil::IOBuf buf; + buf.append(input); + + brpc::Socket* sock = get_fuzz_socket(); + if (sock == NULL) { + return 0; + } + brpc::policy::ParseStreamingMessage(&buf, sock, false, NULL); + return 0; +} diff --git a/test/fuzzing/fuzz_thrift.cpp b/test/fuzzing/fuzz_thrift.cpp new file mode 100644 index 0000000..c7ecd43 --- /dev/null +++ b/test/fuzzing/fuzz_thrift.cpp @@ -0,0 +1,36 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/policy/thrift_protocol.h" + +#define kMinInputLength 5 +#define kMaxInputLength 4096 + +extern "C" int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size < kMinInputLength || size > kMaxInputLength){ + return 1; + } + + std::string input(reinterpret_cast(data), size); + butil::IOBuf buf; + buf.append(input); + + brpc::policy::ParseThriftMessage(&buf, NULL, false, NULL); + return 0; +} diff --git a/test/fuzzing/fuzz_uri.cpp b/test/fuzzing/fuzz_uri.cpp new file mode 100644 index 0000000..3db1dec --- /dev/null +++ b/test/fuzzing/fuzz_uri.cpp @@ -0,0 +1,52 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "brpc/uri.h" +#include "brpc/rtmp.h" + +#define kMinInputLength 5 +#define kMaxInputLength 1024 + +extern "C" int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size < kMinInputLength || size > kMaxInputLength){ + return 1; + } + + char *data_in = (char *)malloc(size + 1); + memcpy(data_in, data, size); + data_in[size] = '\0'; + + + std::string input(reinterpret_cast(data), size); + { + brpc::URI uri; + uri.SetHttpURL(input); + } + { + butil::StringPiece host; + butil::StringPiece vhost; + butil::StringPiece port; + butil::StringPiece app; + butil::StringPiece stream_name; + + brpc::ParseRtmpURL(input, &host, &vhost, &port, &app, &stream_name); + } + + return 0; +} diff --git a/test/fuzzing/fuzz_uri_seed_corpus/data_1.rtmp b/test/fuzzing/fuzz_uri_seed_corpus/data_1.rtmp new file mode 100644 index 0000000..561683e --- /dev/null +++ b/test/fuzzing/fuzz_uri_seed_corpus/data_1.rtmp @@ -0,0 +1 @@ +rtmp://HOST:8765//APP?vhost=abc///STREAM?queries diff --git a/test/fuzzing/fuzz_uri_seed_corpus/data_1.uri b/test/fuzzing/fuzz_uri_seed_corpus/data_1.uri new file mode 100644 index 0000000..b87dbc6 --- /dev/null +++ b/test/fuzzing/fuzz_uri_seed_corpus/data_1.uri @@ -0,0 +1 @@ +www.baidu2.com/s?wd=uri2&nonkey=22#frag diff --git a/test/fuzzing/fuzz_uri_seed_corpus/data_2.uri b/test/fuzzing/fuzz_uri_seed_corpus/data_2.uri new file mode 100644 index 0000000..da343cb --- /dev/null +++ b/test/fuzzing/fuzz_uri_seed_corpus/data_2.uri @@ -0,0 +1 @@ +http://user:passwd@a.b.c/?d=c&a=b&e=f#frg1 diff --git a/test/fuzzing/oss-fuzz.sh b/test/fuzzing/oss-fuzz.sh new file mode 100644 index 0000000..3f1649e --- /dev/null +++ b/test/fuzzing/oss-fuzz.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env sh +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +if [ "$SANITIZER" = "undefined" ] +then + sed -i '/static void DoProfiling/i __attribute__((no_sanitize("undefined")))' src/brpc/builtin/hotspots_service.cpp + sed -i '/void PProfService::heap/i __attribute__((no_sanitize("undefined")))' src/brpc/builtin/pprof_service.cpp + sed -i '/void PProfService::growth/i __attribute__((no_sanitize("undefined")))' src/brpc/builtin/pprof_service.cpp +fi + +mkdir -p build && cd build + +cmake \ + -DCMAKE_C_COMPILER="$CC" -DCMAKE_CXX_COMPILER="$CXX" \ + -DCMAKE_C_FLAGS="$CFLAGS" -DCMAKE_CXX_FLAGS="$CFLAGS" -DCMAKE_CPP_FLAGS="$CFLAGS" \ + -DCMAKE_EXE_LINKER_FLAGS="$CFLAGS" -DLIB_FUZZING_ENGINE="$LIB_FUZZING_ENGINE" \ + -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=OFF -DWITH_SNAPPY=ON -DBUILD_UNIT_TESTS=ON -DBUILD_FUZZ_TESTS=ON ../. + +# https://github.com/google/oss-fuzz/pull/10898 +make \ + fuzz_butil fuzz_esp fuzz_hpack fuzz_http fuzz_hulu fuzz_json \ + fuzz_redis fuzz_shead fuzz_sofa fuzz_uri \ + fuzz_baidu_rpc fuzz_mongo fuzz_memcache \ + fuzz_couchbase fuzz_streaming fuzz_http_parser \ + fuzz_amf --ignore-errors -j$(nproc) + +cp test/fuzz_* $OUT/ + +pushd /lib/x86_64-linux-gnu/ +mkdir -p $OUT/lib/ +cp libgflags* libprotobuf* libleveldb* libprotoc* libsnappy* $OUT/lib/. +popd + +pushd $SRC/brpc/test/fuzzing +zip $OUT/fuzz_json_seed_corpus.zip fuzz_json_seed_corpus/* +zip $OUT/fuzz_uri_seed_corpus.zip fuzz_uri_seed_corpus/* +zip $OUT/fuzz_redis_seed_corpus.zip fuzz_redis_seed_corpus/* +zip $OUT/fuzz_http_seed_corpus.zip fuzz_http_seed_corpus/* +zip $OUT/fuzz_butil_seed_corpus.zip fuzz_butil_seed_corpus/* +zip $OUT/fuzz_hpack_seed_corpus.zip fuzz_hpack_seed_corpus/* +popd diff --git a/test/generate_unittests.bzl b/test/generate_unittests.bzl new file mode 100644 index 0000000..5232fbd --- /dev/null +++ b/test/generate_unittests.bzl @@ -0,0 +1,55 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +def generate_unittests( + name, + srcs, + deps, + copts, + linkopts = [], + data = [], + per_test_tags = {}, + per_test_size = {}): + tests = [] + for s in srcs: + ut_name = s.replace(".cpp", "") + # per_test_size raises Bazel's per-test timeout for a specific file + # ("large" = 900s vs the default medium = 300s), for binaries whose + # cumulative real-time waits would otherwise blow the limit on + # contended CI runners. + kwargs = {} + size = per_test_size.get(s) + if size != None: + kwargs["size"] = size + native.cc_test( + name = ut_name, + srcs = [s], + copts = copts, + deps = deps, + linkopts = linkopts, + data = data, + # Integration tests that fork a real server binary (e.g. redis-server) + # need extra tags: "external" forces a real run instead of a cached + # pass, and "local" runs them outside the sandbox so the PATH-located + # server binary is visible and loopback works. + tags = per_test_tags.get(s, []), + **kwargs + ) + tests.append(":" + ut_name) + + native.test_suite( + name = name, + tests = tests, + ) \ No newline at end of file diff --git a/test/gperftools_helper.h b/test/gperftools_helper.h new file mode 100644 index 0000000..c507ade --- /dev/null +++ b/test/gperftools_helper.h @@ -0,0 +1,34 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef TEST_GPERFTOOLS_HELPER_H_ +#define TEST_GPERFTOOLS_HELPER_H_ + +#if defined(BRPC_ENABLE_CPU_PROFILER) || defined(BAIDU_RPC_ENABLE_CPU_PROFILER) +#include "butil/gperftools_profiler.h" +#else +extern "C" { + +// If gperftools are not used, these functions are no-ops. +inline int ProfilerStart(const char*) { return 0; } +inline void ProfilerStop() {} +inline void ProfilerFlush() {} + +} // extern "C" +#endif // BRPC_ENABLE_CPU_PROFILER || BAIDU_RPC_ENABLE_CPU_PROFILER + +#endif // TEST_GPERFTOOLS_HELPER_H_ diff --git a/test/grpc.proto b/test/grpc.proto new file mode 100644 index 0000000..7497204 --- /dev/null +++ b/test/grpc.proto @@ -0,0 +1,38 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +option cc_generic_services = true; + +package test; + +message GrpcRequest { + required string message = 1; + required bool gzip = 2; + required bool return_error = 3; + optional int64 timeout_us = 4; +}; + +message GrpcResponse { + required string message = 1; +}; + +service GrpcService { + rpc Method(GrpcRequest) returns (GrpcResponse); + rpc MethodTimeOut(GrpcRequest) returns (GrpcResponse); + rpc MethodNotExist(GrpcRequest) returns (GrpcResponse); +} diff --git a/test/guid_unittest.cc b/test/guid_unittest.cc new file mode 100644 index 0000000..cb3efb6 --- /dev/null +++ b/test/guid_unittest.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/guid.h" + +#include + +#include + +#if defined(OS_POSIX) +TEST(GUIDTest, GUIDGeneratesAllZeroes) { + uint64_t bytes[] = { 0, 0 }; + std::string clientid = butil::RandomDataToGUIDString(bytes); + EXPECT_EQ("00000000-0000-0000-0000-000000000000", clientid); +} + +TEST(GUIDTest, GUIDGeneratesCorrectly) { + uint64_t bytes[] = { 0x0123456789ABCDEFULL, 0xFEDCBA9876543210ULL }; + std::string clientid = butil::RandomDataToGUIDString(bytes); + EXPECT_EQ("01234567-89AB-CDEF-FEDC-BA9876543210", clientid); +} +#endif + +TEST(GUIDTest, GUIDCorrectlyFormatted) { + const int kIterations = 10; + for (int it = 0; it < kIterations; ++it) { + std::string guid = butil::GenerateGUID(); + EXPECT_TRUE(butil::IsValidGUID(guid)); + } +} + +TEST(GUIDTest, GUIDBasicUniqueness) { + const int kIterations = 10; + for (int it = 0; it < kIterations; ++it) { + std::string guid1 = butil::GenerateGUID(); + std::string guid2 = butil::GenerateGUID(); + EXPECT_EQ(36U, guid1.length()); + EXPECT_EQ(36U, guid2.length()); + EXPECT_NE(guid1, guid2); + } +} diff --git a/test/hash_tables_unittest.cc b/test/hash_tables_unittest.cc new file mode 100644 index 0000000..c13b2ad --- /dev/null +++ b/test/hash_tables_unittest.cc @@ -0,0 +1,53 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/containers/hash_tables.h" + +#include "butil/basictypes.h" +#include + +namespace { + +class HashPairTest : public testing::Test { +}; + +#define INSERT_PAIR_TEST(Type, value1, value2) \ + { \ + Type pair(value1, value2); \ + butil::hash_map map; \ + map[pair] = 1; \ + } + +// Verify that a hash_map can be constructed for pairs of integers of various +// sizes. +TEST_F(HashPairTest, IntegerPairs) { + typedef std::pair Int16Int16Pair; + typedef std::pair Int16Int32Pair; + typedef std::pair Int16Int64Pair; + + INSERT_PAIR_TEST(Int16Int16Pair, 4, 6); + INSERT_PAIR_TEST(Int16Int32Pair, 9, (1 << 29) + 378128932); + INSERT_PAIR_TEST(Int16Int64Pair, 10, + (GG_INT64_C(1) << 60) + GG_INT64_C(78931732321)); + + typedef std::pair Int32Int16Pair; + typedef std::pair Int32Int32Pair; + typedef std::pair Int32Int64Pair; + + INSERT_PAIR_TEST(Int32Int16Pair, 4, 6); + INSERT_PAIR_TEST(Int32Int32Pair, 9, (1 << 29) + 378128932); + INSERT_PAIR_TEST(Int32Int64Pair, 10, + (GG_INT64_C(1) << 60) + GG_INT64_C(78931732321)); + + typedef std::pair Int64Int16Pair; + typedef std::pair Int64Int32Pair; + typedef std::pair Int64Int64Pair; + + INSERT_PAIR_TEST(Int64Int16Pair, 4, 6); + INSERT_PAIR_TEST(Int64Int32Pair, 9, (1 << 29) + 378128932); + INSERT_PAIR_TEST(Int64Int64Pair, 10, + (GG_INT64_C(1) << 60) + GG_INT64_C(78931732321)); +} + +} // namespace diff --git a/test/hash_unittest.cc b/test/hash_unittest.cc new file mode 100644 index 0000000..6a45732 --- /dev/null +++ b/test/hash_unittest.cc @@ -0,0 +1,82 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/hash.h" + +#include +#include + +#include + +namespace butil { + +TEST(HashTest, String) { + std::string str; + // Empty string (should hash to 0). + str = ""; + EXPECT_EQ(0u, Hash(str)); + + // Simple test. + str = "hello world"; + EXPECT_EQ(2794219650u, Hash(str)); + + // Change one bit. + str = "helmo world"; + EXPECT_EQ(1006697176u, Hash(str)); + + // Insert a null byte. + str = "hello world"; + str[5] = '\0'; + EXPECT_EQ(2319902537u, Hash(str)); + + // Test that the bytes after the null contribute to the hash. + str = "hello worle"; + str[5] = '\0'; + EXPECT_EQ(553904462u, Hash(str)); + + // Extremely long string. + // Also tests strings with high bit set, and null byte. + std::vector long_string_buffer; + for (int i = 0; i < 4096; ++i) + long_string_buffer.push_back((i % 256) - 128); + str.assign(&long_string_buffer.front(), long_string_buffer.size()); + EXPECT_EQ(2797962408u, Hash(str)); + + // All possible lengths (mod 4). Tests separate code paths. Also test with + // final byte high bit set (regression test for http://crbug.com/90659). + // Note that the 1 and 3 cases have a weird bug where the final byte is + // treated as a signed char. It was decided on the above bug discussion to + // enshrine that behaviour as "correct" to avoid invalidating existing hashes. + + // Length mod 4 == 0. + str = "hello w\xab"; + EXPECT_EQ(615571198u, Hash(str)); + // Length mod 4 == 1. + str = "hello wo\xab"; + EXPECT_EQ(623474296u, Hash(str)); + // Length mod 4 == 2. + str = "hello wor\xab"; + EXPECT_EQ(4278562408u, Hash(str)); + // Length mod 4 == 3. + str = "hello worl\xab"; + EXPECT_EQ(3224633008u, Hash(str)); +} + +TEST(HashTest, CString) { + const char* str; + // Empty string (should hash to 0). + str = ""; + EXPECT_EQ(0u, Hash(str, strlen(str))); + + // Simple test. + str = "hello world"; + EXPECT_EQ(2794219650u, Hash(str, strlen(str))); + + // Ensure that it stops reading after the given length, and does not expect a + // null byte. + str = "hello world; don't read this part"; + EXPECT_EQ(2794219650u, Hash(str, strlen("hello world"))); +} + +} // namespace butil diff --git a/test/health_check.proto b/test/health_check.proto new file mode 100644 index 0000000..7a257e1 --- /dev/null +++ b/test/health_check.proto @@ -0,0 +1,28 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +option cc_generic_services = true; + +package test; + +message HealthCheckRequest {}; +message HealthCheckResponse {}; + +service HealthCheckTestService { + rpc default_method(HealthCheckRequest) returns (HealthCheckResponse); +} diff --git a/test/iobuf.proto b/test/iobuf.proto new file mode 100644 index 0000000..85dbdac --- /dev/null +++ b/test/iobuf.proto @@ -0,0 +1,45 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package proto; + +enum CompressType { + CompressTypeNone = 0; + CompressTypeGzip = 1; + CompressTypeZlib = 2; + CompressTypeSnappy = 3; + CompressTypeLZ4 = 4; +} + +message Misc { + required CompressType required_enum = 1; + optional CompressType optional_enum = 2; + repeated CompressType repeated_enum = 3; + required uint64 required_uint64 = 4; + optional uint64 optional_uint64 = 5; + repeated uint64 repeated_uint64 = 6; + required string required_string = 7; + optional string optional_string = 8; + repeated string repeated_string = 9; + required bool required_bool = 10; + optional bool optional_bool = 11; + repeated bool repeated_bool = 12; + required int32 required_int32 = 13; + optional int32 optional_int32 = 14; + repeated int32 repeated_int32 = 15; +} diff --git a/test/iobuf_unittest.cpp b/test/iobuf_unittest.cpp new file mode 100644 index 0000000..18d312e --- /dev/null +++ b/test/iobuf_unittest.cpp @@ -0,0 +1,2259 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include // socketpair +#include // errno +#include // O_RDONLY +#include +#include +#include +#if HAS_NLOHMANN_JSON +#include +#endif // HAS_NLOHMANN_JSON +#include // TempFile +#include +#include +#include // Timer +#include // make_non_blocking +#include +#include +#include +#include +#include +#include +#include "iobuf.pb.h" + +namespace butil { +namespace iobuf { +extern void* (*blockmem_allocate)(size_t); +extern void (*blockmem_deallocate)(void*); +extern void reset_blockmem_allocate_and_deallocate(); +extern int32_t block_shared_count(butil::IOBuf::Block const* b); +extern uint32_t block_cap(butil::IOBuf::Block const* b); +extern IOBuf::Block* get_tls_block_head(); +extern int get_tls_block_count(); +extern void remove_tls_block_chain(); +extern IOBuf::Block* acquire_tls_block(); +extern IOBuf::Block* share_tls_block(); +extern void release_tls_block_chain(IOBuf::Block* b); +extern uint32_t block_cap(IOBuf::Block const* b); +extern uint32_t block_size(IOBuf::Block const* b); +extern IOBuf::Block* get_portal_next(IOBuf::Block const* b); +} // namespace iobuf +} // namespace butil + +namespace { + +const size_t BLOCK_OVERHEAD = 32; //impl dependent +const size_t DEFAULT_PAYLOAD = butil::GetDefaultBlockSize() - BLOCK_OVERHEAD; + +void check_tls_block() { + ASSERT_EQ((butil::IOBuf::Block*)NULL, butil::iobuf::get_tls_block_head()); + printf("tls_block of butil::IOBuf was deleted\n"); +} +const int ALLOW_UNUSED check_dummy = butil::thread_atexit(check_tls_block); + +static butil::FlatSet s_set; + +void* debug_block_allocate(size_t block_size) { + void* b = operator new (block_size, std::nothrow); + s_set.insert(b); + return b; +} + +void debug_block_deallocate(void* b) { + if (1ul != s_set.erase(b)) { + ASSERT_TRUE(false) << "Bad block=" << b; + } else { + operator delete(b); + } +} + +inline bool is_debug_allocator_enabled() { + return (butil::iobuf::blockmem_allocate == debug_block_allocate); +} + +void install_debug_allocator() { + if (!is_debug_allocator_enabled()) { + butil::iobuf::remove_tls_block_chain(); + s_set.init(1024); + butil::iobuf::blockmem_allocate = debug_block_allocate; + butil::iobuf::blockmem_deallocate = debug_block_deallocate; + LOG(INFO) << ""; + } +} + +void show_prof_and_rm(const char* bin_name, const char* filename, size_t topn) { + char cmd[1024]; + if (topn != 0) { + snprintf(cmd, sizeof(cmd), "if [ -e %s ] ; then CPUPROFILE_FREQUENCY=1000 ./pprof --text %s %s | head -%lu; rm -f %s; fi", filename, bin_name, filename, topn+1, filename); + } else { + snprintf(cmd, sizeof(cmd), "if [ -e %s ] ; then CPUPROFILE_FREQUENCY=1000 ./pprof --text %s %s; rm -f %s; fi", filename, bin_name, filename, filename); + } + ASSERT_EQ(0, system(cmd)); +} + +static void check_memory_leak() { + if (is_debug_allocator_enabled()) { + butil::IOBuf::Block* p = butil::iobuf::get_tls_block_head(); + size_t n = 0; + while (p) { + ASSERT_TRUE(s_set.seek(p)) << "Memory leak: " << p; + p = butil::iobuf::get_portal_next(p); + ++n; + } + ASSERT_EQ(n, s_set.size()); + ASSERT_EQ(n, (size_t)butil::iobuf::get_tls_block_count()); + } +} + +class IOBufTest : public ::testing::Test{ +protected: + IOBufTest(){}; + virtual ~IOBufTest(){}; + virtual void SetUp() { + }; + virtual void TearDown() { + check_memory_leak(); + }; +}; + +std::string to_str(const butil::IOBuf& p) { + return p.to_string(); +} + +TEST_F(IOBufTest, append_zero) { + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + butil::IOPortal p; + ASSERT_EQ(0, p.append_from_file_descriptor(fds[0], 0)); + ASSERT_EQ(0, close(fds[0])); + ASSERT_EQ(0, close(fds[1])); +} + +TEST_F(IOBufTest, pop_front) { + install_debug_allocator(); + + butil::IOBuf buf; + ASSERT_EQ(0UL, buf.pop_front(1)); // nothing happened + + std::string s = "hello"; + buf.append(s); + ASSERT_EQ(s, to_str(buf)); + ASSERT_EQ(0UL, buf.pop_front(0)); // nothing happened + ASSERT_EQ(s, to_str(buf)); + + ASSERT_EQ(1UL, buf.pop_front(1)); + s.erase(0, 1); + ASSERT_EQ(s, to_str(buf)); + ASSERT_EQ(s.length(), buf.length()); + ASSERT_FALSE(buf.empty()); + + ASSERT_EQ(s.length(), buf.pop_front(INT_MAX)); + s.clear(); + ASSERT_EQ(s, to_str(buf)); + ASSERT_EQ(0UL, buf.length()); + ASSERT_TRUE(buf.empty()); + + for (size_t i = 0; i < DEFAULT_PAYLOAD * 3/2; ++i) { + s.push_back(i); + } + buf.append(s); + ASSERT_EQ(1UL, buf.pop_front(1)); + s.erase(0, 1); + ASSERT_EQ(s, to_str(buf)); + ASSERT_EQ(s.length(), buf.length()); + ASSERT_FALSE(buf.empty()); + + ASSERT_EQ(s.length(), buf.pop_front(INT_MAX)); + s.clear(); + ASSERT_EQ(s, to_str(buf)); + ASSERT_EQ(0UL, buf.length()); + ASSERT_TRUE(buf.empty()); +} + +TEST_F(IOBufTest, pop_back) { + install_debug_allocator(); + + butil::IOBuf buf; + ASSERT_EQ(0UL, buf.pop_back(1)); // nothing happened + + std::string s = "hello"; + buf.append(s); + ASSERT_EQ(s, to_str(buf)); + ASSERT_EQ(0UL, buf.pop_back(0)); // nothing happened + ASSERT_EQ(s, to_str(buf)); + + ASSERT_EQ(1UL, buf.pop_back(1)); + s.resize(s.size() - 1); + ASSERT_EQ(s, to_str(buf)); + ASSERT_EQ(s.length(), buf.length()); + ASSERT_FALSE(buf.empty()); + + ASSERT_EQ(s.length(), buf.pop_back(INT_MAX)); + s.clear(); + ASSERT_EQ(s, to_str(buf)); + ASSERT_EQ(0UL, buf.length()); + ASSERT_TRUE(buf.empty()); + + for (size_t i = 0; i < DEFAULT_PAYLOAD * 3/2; ++i) { + s.push_back(i); + } + buf.append(s); + ASSERT_EQ(1UL, buf.pop_back(1)); + s.resize(s.size() - 1); + ASSERT_EQ(s, to_str(buf)); + ASSERT_EQ(s.length(), buf.length()); + ASSERT_FALSE(buf.empty()); + + ASSERT_EQ(s.length(), buf.pop_back(INT_MAX)); + s.clear(); + ASSERT_EQ(s, to_str(buf)); + ASSERT_EQ(0UL, buf.length()); + ASSERT_TRUE(buf.empty()); +} + +TEST_F(IOBufTest, append) { + install_debug_allocator(); + + butil::IOBuf b; + ASSERT_EQ(0UL, b.length()); + ASSERT_TRUE(b.empty()); + ASSERT_EQ(-1, b.append(NULL)); + ASSERT_EQ(0, b.append("")); + ASSERT_EQ(0, b.append(std::string())); + ASSERT_EQ(-1, b.append(NULL, 1)); + ASSERT_EQ(0, b.append("dummy", 0)); + ASSERT_EQ(0UL, b.length()); + ASSERT_TRUE(b.empty()); + ASSERT_EQ(0, b.append("1")); + ASSERT_EQ(1UL, b.length()); + ASSERT_FALSE(b.empty()); + ASSERT_EQ("1", to_str(b)); + const std::string s = "22"; + ASSERT_EQ(0, b.append(s)); + ASSERT_EQ(3UL, b.length()); + ASSERT_FALSE(b.empty()); + ASSERT_EQ("122", to_str(b)); +} + +TEST_F(IOBufTest, appendv) { + install_debug_allocator(); + + butil::IOBuf b; + const_iovec vec[] = { {"hello1", 6}, {" world1", 7}, + {"hello2", 6}, {" world2", 7}, + {"hello3", 6}, {" world3", 7}, + {"hello4", 6}, {" world4", 7}, + {"hello5", 6}, {" world5", 7} }; + ASSERT_EQ(0, b.appendv(vec, arraysize(vec))); + ASSERT_EQ("hello1 world1hello2 world2hello3 world3hello4 world4hello5 world5", + b.to_string()); + + // Make some iov_len shorter to test if iov_len works. + vec[2].iov_len = 4; // "hello2" + vec[5].iov_len = 3; // " world3" + b.clear(); + ASSERT_EQ(0, b.appendv(vec, arraysize(vec))); + ASSERT_EQ("hello1 world1hell world2hello3 wohello4 world4hello5 world5", + b.to_string()); + + // Append some long stuff. + const size_t full_len = DEFAULT_PAYLOAD * 9; + char* str = (char*)malloc(full_len); + ASSERT_TRUE(str); + const size_t len1 = full_len / 6; + const size_t len2 = full_len / 3; + const size_t len3 = full_len - len1 - len2; + ASSERT_GT(len1, (size_t)DEFAULT_PAYLOAD); + ASSERT_GT(len2, (size_t)DEFAULT_PAYLOAD); + ASSERT_GT(len3, (size_t)DEFAULT_PAYLOAD); + ASSERT_EQ(full_len, len1 + len2 + len3); + + for (size_t i = 0; i < full_len; ++i) { + str[i] = i * 7; + } + const_iovec vec2[] = {{str, len1}, + {str + len1, len2}, + {str + len1 + len2, len3}}; + b.clear(); + ASSERT_EQ(0, b.appendv(vec2, arraysize(vec2))); + ASSERT_EQ(full_len, b.size()); + ASSERT_EQ(0, memcmp(str, b.to_string().data(), full_len)); + free(str); +} + +TEST_F(IOBufTest, reserve) { + butil::IOBuf b; + ASSERT_EQ(butil::IOBuf::INVALID_AREA, b.reserve(0)); + const size_t NRESERVED1 = 5; + const butil::IOBuf::Area a1 = b.reserve(NRESERVED1); + ASSERT_TRUE(a1 != butil::IOBuf::INVALID_AREA); + ASSERT_EQ(NRESERVED1, b.size()); + b.append("hello world"); + ASSERT_EQ(0, b.unsafe_assign(a1, "prefix")); // `x' will not be copied + ASSERT_EQ("prefihello world", b.to_string()); + ASSERT_EQ((size_t)16, b.size()); + + // pop/append sth. from back-side and assign again. + ASSERT_EQ((size_t)5, b.pop_back(5)); + ASSERT_EQ("prefihello ", b.to_string()); + b.append("blahblahfoobar"); + ASSERT_EQ(0, b.unsafe_assign(a1, "goodorbad")); // `x' will not be copied + ASSERT_EQ("goodohello blahblahfoobar", b.to_string()); + + // append a long string and assign again. + std::string s1(DEFAULT_PAYLOAD * 3, '\0'); + for (size_t i = 0; i < s1.size(); ++i) { + s1[i] = i * 7; + } + ASSERT_EQ(DEFAULT_PAYLOAD * 3, s1.size()); + // remove everything after reserved area + ASSERT_GE(b.size(), NRESERVED1); + b.pop_back(b.size() - NRESERVED1); + ASSERT_EQ(NRESERVED1, b.size()); + b.append(s1); + ASSERT_EQ(0, b.unsafe_assign(a1, "appleblahblah")); + ASSERT_EQ("apple" + s1, b.to_string()); + + // Reserve long + b.pop_back(b.size() - NRESERVED1); + ASSERT_EQ(NRESERVED1, b.size()); + const size_t NRESERVED2 = DEFAULT_PAYLOAD * 3; + const butil::IOBuf::Area a2 = b.reserve(NRESERVED2); + ASSERT_EQ(NRESERVED1 + NRESERVED2, b.size()); + b.append(s1); + ASSERT_EQ(NRESERVED1 + NRESERVED2 + s1.size(), b.size()); + std::string s2(NRESERVED2, 0); + for (size_t i = 0; i < s2.size(); ++i) { + s2[i] = i * 13; + } + ASSERT_EQ(NRESERVED2, s2.size()); + ASSERT_EQ(0, b.unsafe_assign(a2, s2.data())); + ASSERT_EQ("apple" + s2 + s1, b.to_string()); + ASSERT_EQ(0, b.unsafe_assign(a1, "orangeblahblah")); + ASSERT_EQ("orang" + s2 + s1, b.to_string()); +} + +#ifndef BUTIL_USE_ASAN +// ASan will detect heap-buffer-overflow error casued by FakeBlock. +struct FakeBlock { + int nshared; + FakeBlock() : nshared(1) {} +}; + +TEST_F(IOBufTest, iobuf_as_queue) { + install_debug_allocator(); + + // If INITIAL_CAP gets bigger, creating butil::IOBuf::Block are very + // small. Since We don't access butil::IOBuf::Block::data in this case. + // We replace butil::IOBuf::Block with FakeBlock with only nshared (in + // the same offset) + FakeBlock* blocks[butil::IOBuf::INITIAL_CAP+16]; + const size_t NBLOCKS = ARRAY_SIZE(blocks); + butil::IOBuf::BlockRef r[NBLOCKS]; + const size_t LENGTH = 7UL; + for (size_t i = 0; i < NBLOCKS; ++i) { + ASSERT_TRUE((blocks[i] = new FakeBlock)); + r[i].offset = 1; + r[i].length = LENGTH; + r[i].block = (butil::IOBuf::Block*)blocks[i]; + } + + butil::IOBuf p; + + // Empty + ASSERT_EQ(0UL, p._ref_num()); + ASSERT_EQ(-1, p._pop_front_ref()); + ASSERT_EQ(0UL, p.length()); + + // Add one ref + p._push_back_ref(r[0]); + ASSERT_EQ(1UL, p._ref_num()); + ASSERT_EQ(LENGTH, p.length()); + ASSERT_EQ(r[0], p._front_ref()); + ASSERT_EQ(r[0], p._back_ref()); + ASSERT_EQ(r[0], p._ref_at(0)); + ASSERT_EQ(2, butil::iobuf::block_shared_count(r[0].block)); + + // Add second ref + p._push_back_ref(r[1]); + ASSERT_EQ(2UL, p._ref_num()); + ASSERT_EQ(LENGTH*2, p.length()); + ASSERT_EQ(r[0], p._front_ref()); + ASSERT_EQ(r[1], p._back_ref()); + ASSERT_EQ(r[0], p._ref_at(0)); + ASSERT_EQ(r[1], p._ref_at(1)); + ASSERT_EQ(2, butil::iobuf::block_shared_count(r[1].block)); + + // Pop a ref + ASSERT_EQ(0, p._pop_front_ref()); + ASSERT_EQ(1UL, p._ref_num()); + ASSERT_EQ(LENGTH, p.length()); + + ASSERT_EQ(r[1], p._front_ref()); + ASSERT_EQ(r[1], p._back_ref()); + ASSERT_EQ(r[1], p._ref_at(0)); + //ASSERT_EQ(1, butil::iobuf::block_shared_count(r[0].block)); + + // Pop second + ASSERT_EQ(0, p._pop_front_ref()); + ASSERT_EQ(0UL, p._ref_num()); + ASSERT_EQ(0UL, p.length()); + //ASSERT_EQ(1, r[1].block->nshared); + + // Add INITIAL_CAP+2 refs, r[0] and r[1] are used, don't use again + for (size_t i = 0; i < butil::IOBuf::INITIAL_CAP+2; ++i) { + p._push_back_ref(r[i+2]); + ASSERT_EQ(i+1, p._ref_num()); + ASSERT_EQ(p._ref_num()*LENGTH, p.length()); + ASSERT_EQ(r[2], p._front_ref()) << i; + ASSERT_EQ(r[i+2], p._back_ref()); + for (size_t j = 0; j <= i; j+=std::max(1UL, i/20) /*not check all*/) { + ASSERT_EQ(r[j+2], p._ref_at(j)); + } + ASSERT_EQ(2, butil::iobuf::block_shared_count(r[i+2].block)); + } + + // Pop them all + const size_t saved_ref_num = p._ref_num(); + while (p._ref_num() >= 2UL) { + const size_t last_ref_num = p._ref_num(); + ASSERT_EQ(0, p._pop_front_ref()); + ASSERT_EQ(last_ref_num, p._ref_num()+1); + ASSERT_EQ(p._ref_num()*LENGTH, p.length()); + const size_t f = saved_ref_num - p._ref_num() + 2; + ASSERT_EQ(r[f], p._front_ref()); + ASSERT_EQ(r[saved_ref_num+1], p._back_ref()); + for (size_t j = 0; j < p._ref_num(); j += std::max(1UL, p._ref_num()/20)) { + ASSERT_EQ(r[j+f], p._ref_at(j)); + } + //ASSERT_EQ(1, r[f-1].block->nshared); + } + + ASSERT_EQ(1ul, p._ref_num()); + // Pop last one + ASSERT_EQ(0, p._pop_front_ref()); + ASSERT_EQ(0UL, p._ref_num()); + ASSERT_EQ(0UL, p.length()); + //ASSERT_EQ(1, r[saved_ref_num+1].block->nshared); + + // Delete blocks + for (size_t i = 0; i < NBLOCKS; ++i) { + delete blocks[i]; + } +} +#endif // BUTIL_USE_ASAN + +TEST_F(IOBufTest, iobuf_sanity) { + install_debug_allocator(); + + LOG(INFO) << "sizeof(butil::IOBuf)=" << sizeof(butil::IOBuf) + << " sizeof(IOPortal)=" << sizeof(butil::IOPortal); + + butil::IOBuf b1; + std::string s1 = "hello world"; + const char c1 = 'A'; + const std::string s2 = "too simple"; + std::string s1c = s1; + s1c.erase(0, 1); + + // Append a c-std::string + ASSERT_EQ(0, b1.append(s1.c_str())); + ASSERT_EQ(s1.length(), b1.length()); + ASSERT_EQ(s1, to_str(b1)); + ASSERT_EQ(1UL, b1._ref_num()); + + // Append a char + ASSERT_EQ(0, b1.push_back(c1)); + ASSERT_EQ(s1.length() + 1, b1.length()); + ASSERT_EQ(s1+c1, to_str(b1)); + ASSERT_EQ(1UL, b1._ref_num()); + + // Append a std::string + ASSERT_EQ(0, b1.append(s2)); + ASSERT_EQ(s1.length() + 1 + s2.length(), b1.length()); + ASSERT_EQ(s1+c1+s2, to_str(b1)); + ASSERT_EQ(1UL, b1._ref_num()); + + // Pop first char + ASSERT_EQ(1UL, b1.pop_front(1)); + ASSERT_EQ(1UL, b1._ref_num()); + ASSERT_EQ(s1.length() + s2.length(), b1.length()); + ASSERT_EQ(s1c+c1+s2, to_str(b1)); + + // Pop all + ASSERT_EQ(0UL, b1.pop_front(0)); + ASSERT_EQ(s1.length() + s2.length(), b1.pop_front(b1.length()+1)); + ASSERT_TRUE(b1.empty()); + ASSERT_EQ(0UL, b1.length()); + ASSERT_EQ(0UL, b1._ref_num()); + ASSERT_EQ("", to_str(b1)); + + // Restore + ASSERT_EQ(0, b1.append(s1.c_str())); + ASSERT_EQ(0, b1.push_back(c1)); + ASSERT_EQ(0, b1.append(s2)); + + // Cut first char + butil::IOBuf p; + b1.cutn(&p, 0); + b1.cutn(&p, 1); + ASSERT_EQ(s1.substr(0, 1), to_str(p)); + ASSERT_EQ(s1c+c1+s2, to_str(b1)); + + // Cut another two and append to p + b1.cutn(&p, 2); + ASSERT_EQ(s1.substr(0, 3), to_str(p)); + std::string s1d = s1; + s1d.erase(0, 3); + ASSERT_EQ(s1d+c1+s2, to_str(b1)); + + // Clear + b1.clear(); + ASSERT_TRUE(b1.empty()); + ASSERT_EQ(0UL, b1.length()); + ASSERT_EQ(0UL, b1._ref_num()); + ASSERT_EQ("", to_str(b1)); + ASSERT_EQ(s1.substr(0, 3), to_str(p)); +} + +TEST_F(IOBufTest, copy_and_assign) { + install_debug_allocator(); + + const size_t TARGET_SIZE = butil::GetDefaultBlockSize() * 2; + butil::IOBuf buf0; + buf0.append("hello"); + ASSERT_EQ(1u, buf0._ref_num()); + + // Copy-construct from SmallView + butil::IOBuf buf1 = buf0; + ASSERT_EQ(1u, buf1._ref_num()); + ASSERT_EQ(buf0, buf1); + + buf1.resize(TARGET_SIZE, 'z'); + ASSERT_LT(2u, buf1._ref_num()); + ASSERT_EQ(TARGET_SIZE, buf1.size()); + + // Copy-construct from BigView + butil::IOBuf buf2 = buf1; + ASSERT_EQ(buf1, buf2); + + // assign BigView to SmallView + butil::IOBuf buf3; + buf3 = buf1; + ASSERT_EQ(buf1, buf3); + + // assign BigView to BigView + butil::IOBuf buf4; + buf4.resize(TARGET_SIZE, 'w'); + ASSERT_NE(buf1, buf4); + buf4 = buf1; + ASSERT_EQ(buf1, buf4); +} + +TEST_F(IOBufTest, compare) { + install_debug_allocator(); + + const char* SEED = "abcdefghijklmnqopqrstuvwxyz"; + butil::IOBuf seedbuf; + seedbuf.append(SEED); + const int REP = 100; + butil::IOBuf b1; + for (int i = 0; i < REP; ++i) { + b1.append(seedbuf); + b1.append(SEED); + } + butil::IOBuf b2; + for (int i = 0; i < REP * 2; ++i) { + b2.append(SEED); + } + ASSERT_EQ(b1, b2); + + butil::IOBuf b3 = b2; + + b2.push_back('0'); + ASSERT_NE(b1, b2); + ASSERT_EQ(b1, b3); + + b1.clear(); + b2.clear(); + ASSERT_EQ(b1, b2); +} + +TEST_F(IOBufTest, append_and_cut_it_all) { + butil::IOBuf b; + const size_t N = 32768UL; + for (size_t i = 0; i < N; ++i) { + ASSERT_EQ(0, b.push_back(i)); + } + ASSERT_EQ(N, b.length()); + butil::IOBuf p; + b.cutn(&p, N); + ASSERT_TRUE(b.empty()); + ASSERT_EQ(N, p.length()); +} + +TEST_F(IOBufTest, copy_to) { + butil::IOBuf b; + const std::string seed = "abcdefghijklmnopqrstuvwxyz"; + std::string src; + for (size_t i = 0; i < 1000; ++i) { + src.append(seed); + } + b.append(src); + ASSERT_GT(b.size(), DEFAULT_PAYLOAD); + std::string s1; + ASSERT_EQ(src.size(), b.copy_to(&s1)); + ASSERT_EQ(src, s1); + + std::string s2; + ASSERT_EQ(32u, b.copy_to(&s2, 32)); + ASSERT_EQ(src.substr(0, 32), s2); + + std::string s3; + const std::string expected = src.substr(DEFAULT_PAYLOAD - 1, 33); + ASSERT_EQ(33u, b.copy_to(&s3, 33, DEFAULT_PAYLOAD - 1)); + ASSERT_EQ(expected, s3); + + ASSERT_EQ(33u, b.append_to(&s3, 33, DEFAULT_PAYLOAD - 1)); + ASSERT_EQ(expected + expected, s3); + + butil::IOBuf b1; + ASSERT_EQ(src.size(), b.append_to(&b1)); + ASSERT_EQ(src, b1.to_string()); + + butil::IOBuf b2; + ASSERT_EQ(32u, b.append_to(&b2, 32)); + ASSERT_EQ(src.substr(0, 32), b2.to_string()); + + butil::IOBuf b3; + ASSERT_EQ(33u, b.append_to(&b3, 33, DEFAULT_PAYLOAD - 1)); + ASSERT_EQ(expected, b3.to_string()); + + ASSERT_EQ(33u, b.append_to(&b3, 33, DEFAULT_PAYLOAD - 1)); + ASSERT_EQ(expected + expected, b3.to_string()); +} + +TEST_F(IOBufTest, cut_by_single_text_delim) { + install_debug_allocator(); + + butil::IOBuf b; + butil::IOBuf p; + std::vector ps; + std::string s1 = "1234567\n12\n\n2567"; + ASSERT_EQ(0, b.append(s1)); + ASSERT_EQ(s1.length(), b.length()); + + for (; b.cut_until(&p, "\n") == 0; ) { + ps.push_back(p); + p.clear(); + } + + ASSERT_EQ(3UL, ps.size()); + ASSERT_EQ("1234567", to_str(ps[0])); + ASSERT_EQ("12", to_str(ps[1])); + ASSERT_EQ("", to_str(ps[2])); + ASSERT_EQ("2567", to_str(b)); + + b.append("\n"); + ASSERT_EQ(0, b.cut_until(&p, "\n")); + ASSERT_EQ("2567", to_str(p)); + ASSERT_EQ("", to_str(b)); +} + +TEST_F(IOBufTest, cut_by_multiple_text_delim) { + install_debug_allocator(); + + butil::IOBuf b; + butil::IOBuf p; + std::vector ps; + std::string s1 = "\r\n1234567\r\n12\r\n\n\r2567"; + ASSERT_EQ(0, b.append(s1)); + ASSERT_EQ(s1.length(), b.length()); + + for (; b.cut_until(&p, "\r\n") == 0; ) { + ps.push_back(p); + p.clear(); + } + + ASSERT_EQ(3UL, ps.size()); + ASSERT_EQ("", to_str(ps[0])); + ASSERT_EQ("1234567", to_str(ps[1])); + ASSERT_EQ("12", to_str(ps[2])); + ASSERT_EQ("\n\r2567", to_str(b)); + + b.append("\r\n"); + ASSERT_EQ(0, b.cut_until(&p, "\r\n")); + ASSERT_EQ("\n\r2567", to_str(p)); + ASSERT_EQ("", to_str(b)); +} + +TEST_F(IOBufTest, append_a_lot_and_cut_them_all) { + install_debug_allocator(); + + butil::IOBuf b; + butil::IOBuf p; + std::string s1 = "12345678901234567"; + const size_t N = 10000; + for (size_t i= 0; i < N; ++i) { + b.append(s1); + } + ASSERT_EQ(N*s1.length(), b.length()); + + while (b.length() >= 7) { + b.cutn(&p, 7); + } + size_t remain = s1.length()*N % 7; + ASSERT_EQ(remain, b.length()); + ASSERT_EQ(s1.substr(s1.length() - remain, remain), to_str(b)); + ASSERT_EQ(s1.length()*N/7*7, p.length()); +} + +TEST_F(IOBufTest, cut_into_fd_tiny) { + install_debug_allocator(); + + butil::IOPortal b1, b2; + std::string ref; + int fds[2]; + + for (int j = 10; j > 0; --j) { + ref.push_back(j); + } + b1.append(ref); + ASSERT_EQ(1UL, b1.pop_front(1)); + ref.erase(0, 1); + ASSERT_EQ(ref, to_str(b1)); + LOG(INFO) << "ref.size=" << ref.size(); + + //ASSERT_EQ(0, pipe(fds)); + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds)); + + butil::make_non_blocking(fds[0]); + butil::make_non_blocking(fds[1]); + + while (!b1.empty() || b2.length() != ref.length()) { + size_t b1len = b1.length(), b2len = b2.length(); + errno = 0; + printf("b1.length=%lu - %ld (%m), ", + b1len, b1.cut_into_file_descriptor(fds[1])); + printf("b2.length=%lu + %ld (%m)\n", + b2len, b2.append_from_file_descriptor(fds[0], LONG_MAX)); + } + printf("b1.length=%lu, b2.length=%lu\n", b1.length(), b2.length()); + + ASSERT_EQ(ref, to_str(b2)); + + close(fds[0]); + close(fds[1]); +} + +TEST_F(IOBufTest, cut_multiple_into_fd_tiny) { + install_debug_allocator(); + + butil::IOBuf* b1[10]; + butil::IOPortal b2; + std::string ref; + int fds[2]; + + for (size_t j = 0; j < ARRAY_SIZE(b1); ++j) { + std::string s; + for (int i = 10; i > 0; --i) { + s.push_back(j * 10 + i); + } + ref.append(s); + butil::IOPortal* b = new butil::IOPortal(); + b->append(s); + b1[j] = b; + } + + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds)); + butil::make_non_blocking(fds[0]); + butil::make_non_blocking(fds[1]); + + ASSERT_EQ((ssize_t)ref.length(), + butil::IOBuf::cut_multiple_into_file_descriptor( + fds[1], b1, ARRAY_SIZE(b1))); + for (size_t j = 0; j < ARRAY_SIZE(b1); ++j) { + ASSERT_TRUE(b1[j]->empty()); + delete (butil::IOPortal*)b1[j]; + b1[j] = NULL; + } + ASSERT_EQ((ssize_t)ref.length(), + b2.append_from_file_descriptor(fds[0], LONG_MAX)); + ASSERT_EQ(ref, to_str(b2)); + + close(fds[0]); + close(fds[1]); +} + +TEST_F(IOBufTest, cut_into_fd_a_lot_of_data) { + install_debug_allocator(); + + butil::IOPortal b0, b1, b2; + std::string s, ref; + int fds[2]; + + for (int j = rand()%7777+300000; j > 0; --j) { + ref.push_back(j); + s.push_back(j); + + if (s.length() == 1024UL || j == 1) { + b0.append(s); + ref.append("tailing"); + b0.cutn(&b1, b0.length()); + ASSERT_EQ(0, b1.append("tailing")); + s.clear(); + } + } + + ASSERT_EQ(ref.length(), b1.length()); + ASSERT_EQ(ref, to_str(b1)); + ASSERT_TRUE(b0.empty()); + LOG(INFO) << "ref.size=" << ref.size(); + + //ASSERT_EQ(0, pipe(fds)); + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds)); + butil::make_non_blocking(fds[0]); + butil::make_non_blocking(fds[1]); + const int sockbufsize = 16 * 1024 - 17; + ASSERT_EQ(0, setsockopt(fds[1], SOL_SOCKET, SO_SNDBUF, + (const char*)&sockbufsize, sizeof(int))); + ASSERT_EQ(0, setsockopt(fds[0], SOL_SOCKET, SO_RCVBUF, + (const char*)&sockbufsize, sizeof(int))); + + while (!b1.empty() || b2.length() != ref.length()) { + size_t b1len = b1.length(), b2len = b2.length(); + errno = 0; + printf("b1.length=%lu - %ld (%m), ", b1len, b1.cut_into_file_descriptor(fds[1])); + printf("b2.length=%lu + %ld (%m)\n", b2len, b2.append_from_file_descriptor(fds[0], LONG_MAX)); + } + printf("b1.length=%lu, b2.length=%lu\n", b1.length(), b2.length()); + + ASSERT_EQ(ref, to_str(b2)); + + close(fds[0]); + close(fds[1]); +} + +TEST_F(IOBufTest, cut_by_delim_perf) { + butil::iobuf::reset_blockmem_allocate_and_deallocate(); + + butil::IOBuf b; + butil::IOBuf p; + std::vector ps; + std::string s1 = "123456789012345678901234567890\n"; + const size_t N = 100000; + for (size_t i = 0; i < N; ++i) { + ASSERT_EQ(0, b.append(s1)); + } + ASSERT_EQ(N * s1.length(), b.length()); + + butil::Timer t; + //ProfilerStart("cutd.prof"); + t.start(); + for (; b.cut_until(&p, "\n") == 0; ) { } + t.stop(); + //ProfilerStop(); + + LOG(INFO) << "IOPortal::cut_by_delim takes " + << t.n_elapsed()/N << "ns, tp=" + << s1.length() * N * 1000.0 / t.n_elapsed () << "MB/s"; + show_prof_and_rm("test_iobuf", "cutd.prof", 10); +} + + +TEST_F(IOBufTest, cut_perf) { + butil::iobuf::reset_blockmem_allocate_and_deallocate(); + + butil::IOBuf b; + butil::IOBuf p; + const size_t length = 60000000UL; + const size_t REP = 10; + butil::Timer t; + std::string s = "1234567890"; + const bool push_char = false; + + if (!push_char) { + //ProfilerStart("iobuf_append.prof"); + t.start(); + for (size_t j = 0; j < REP; ++j) { + b.clear(); + for (size_t i = 0; i < length/s.length(); ++i) { + b.append(s); + } + } + t.stop(); + //ProfilerStop(); + LOG(INFO) << "IOPortal::append(std::string_" << s.length() << ") takes " + << t.n_elapsed() * s.length() / length / REP << "ns, tp=" + << REP * length * 1000.0 / t.n_elapsed () << "MB/s"; + } else { + //ProfilerStart("iobuf_pushback.prof"); + t.start(); + for (size_t i = 0; i < length; ++i) { + b.push_back(i); + } + t.stop(); + //ProfilerStop(); + LOG(INFO) << "IOPortal::push_back(char) takes " + << t.n_elapsed() / length << "ns, tp=" + << length * 1000.0 / t.n_elapsed () << "MB/s"; + } + + ASSERT_EQ(length, b.length()); + + size_t w[3] = { 16, 128, 1024 }; + //char name[32]; + + for (size_t i = 0; i < ARRAY_SIZE(w); ++i) { + // snprintf(name, sizeof(name), "iobuf_cut%lu.prof", w[i]); + // ProfilerStart(name); + + t.start (); + while (b.length() >= w[i]+12) { + b.cutn(&p, 12); + b.cutn(&p, w[i]); + } + t.stop (); + + //ProfilerStop(); + + LOG(INFO) << "IOPortal::cutn(12+" << w[i] << ") takes " + << t.n_elapsed()*(w[i]+12)/length << "ns, tp=" + << length * 1000.0 / t.n_elapsed () << "MB/s"; + + ASSERT_LT(b.length(), w[i]+12); + + t.start(); + b.append(p); + t.stop(); + LOG(INFO) << "IOPortal::append(butil::IOBuf) takes " + << t.n_elapsed ()/p._ref_num() << "ns, tp=" + << length * 1000.0 / t.n_elapsed () << "MB/s"; + + p.clear(); + ASSERT_EQ(length, b.length()); + } + + show_prof_and_rm("test_iobuf", "./iobuf_append.prof", 10); + show_prof_and_rm("test_iobuf", "./iobuf_pushback.prof", 10); +} + +TEST_F(IOBufTest, append_store_append_cut) { + butil::iobuf::reset_blockmem_allocate_and_deallocate(); + + std::string ref; + ref.resize(rand()%376813+19777777); + for (size_t j = 0; j < ref.size(); ++j) { + ref[j] = j; + } + + butil::IOPortal b1, b2; + std::vector ps; + ssize_t nr; + size_t HINT = 16*1024UL; + butil::Timer t; + size_t w[3] = { 16, 128, 1024 }; + char name[64]; + char profname[64]; + char cmd[512]; + bool write_to_dev_null = true; + size_t nappend, ncut; + + butil::TempFile f; + ASSERT_EQ(0, f.save_bin(ref.data(), ref.length())); + + for (size_t i = 0; i < ARRAY_SIZE(w); ++i) { + ps.reserve(ref.size()/(w[i]+12) + 1); + // LOG(INFO) << "ps.cap=" << ps.capacity(); + + const int ifd = open(f.fname(), O_RDONLY); + ASSERT_TRUE(ifd > 0); + if (write_to_dev_null) { + snprintf(name, sizeof(name), "/dev/null"); + } else { + snprintf(name, sizeof(name), "iobuf_asac%lu.output", w[i]); + snprintf(cmd, sizeof(cmd), "cmp %s %s", f.fname(), name); + } + const int ofd = open(name, O_CREAT | O_WRONLY, 0666); + ASSERT_TRUE(ofd > 0) << strerror(errno); + snprintf(profname, sizeof(profname), "iobuf_asac%lu.prof", w[i]); + + //ProfilerStart(profname); + t.start(); + + nappend = ncut = 0; + while ((nr = b1.append_from_file_descriptor(ifd, HINT)) > 0) { + ++nappend; + while (b1.length() >= w[i] + 12) { + butil::IOBuf p; + b1.cutn(&p, 12); + b1.cutn(&p, w[i]); + ps.push_back(p); + } + } + for (size_t j = 0; j < ps.size(); ++j) { + b2.append(ps[j]); + if (b2.length() >= HINT) { + b2.cut_into_file_descriptor(ofd); + } + } + b2.cut_into_file_descriptor(ofd); + b1.cut_into_file_descriptor(ofd); + + close(ifd); + close(ofd); + t.stop(); + //ProfilerStop(); + + ASSERT_TRUE(b1.empty()); + ASSERT_TRUE(b2.empty()); + //LOG(INFO) << "ps.size=" << ps.size(); + ps.clear(); + LOG(INFO) << "Bandwidth of append(" << f.fname() << ")->cut(12+" << w[i] + << ")->store->append->cut(" << name << ") is " + << ref.length() * 1000.0 / t.n_elapsed () << "MB/s"; + + if (!write_to_dev_null) { + ASSERT_EQ(0, system(cmd)); + } + if (!write_to_dev_null) { + remove(name); + } + } + + for (size_t i = 0; i < ARRAY_SIZE(w); ++i) { + snprintf(name, sizeof(name), "iobuf_asac%lu.prof", w[i]); + show_prof_and_rm("test_iobuf", name, 10); + } +} + +TEST_F(IOBufTest, conversion_with_protobuf) { + const int REP = 1000; + proto::Misc m1; + m1.set_required_enum(proto::CompressTypeGzip); + m1.set_required_uint64(0xdeadbeef); + m1.set_optional_uint64(10000); + m1.set_required_string("hello iobuf"); + m1.set_optional_string("hello iobuf again"); + for (int i = 0; i < REP; ++i) { + char buf[16]; + snprintf(buf, sizeof(buf), "value%d", i); + m1.add_repeated_string(buf); + } + m1.set_required_bool(true); + m1.set_required_int32(0xbeefdead); + + butil::IOBuf buf; + const std::string header("just-make-sure-wrapper-does-not-clear-IOBuf"); + ASSERT_EQ(0, buf.append(header)); + butil::IOBufAsZeroCopyOutputStream out_wrapper(&buf); + ASSERT_EQ(0, out_wrapper.ByteCount()); + ASSERT_TRUE(m1.SerializeToZeroCopyStream(&out_wrapper)); + ASSERT_EQ((size_t)m1.ByteSize() + header.size(), buf.length()); + ASSERT_EQ(m1.ByteSize(), out_wrapper.ByteCount()); + + ASSERT_EQ(header.size(), buf.pop_front(header.size())); + butil::IOBufAsZeroCopyInputStream in_wrapper(buf); + ASSERT_EQ(0, in_wrapper.ByteCount()); + { + const void* dummy_blk = NULL; + int dummy_size = 0; + ASSERT_TRUE(in_wrapper.Next(&dummy_blk, &dummy_size)); + ASSERT_EQ(dummy_size, in_wrapper.ByteCount()); + in_wrapper.BackUp(1); + ASSERT_EQ(dummy_size - 1, in_wrapper.ByteCount()); + const void* dummy_blk2 = NULL; + int dummy_size2 = 0; + ASSERT_TRUE(in_wrapper.Next(&dummy_blk2, &dummy_size2)); + ASSERT_EQ(1, dummy_size2); + ASSERT_EQ((char*)dummy_blk + dummy_size - 1, (char*)dummy_blk2); + ASSERT_EQ(dummy_size, in_wrapper.ByteCount()); + in_wrapper.BackUp(dummy_size); + ASSERT_EQ(0, in_wrapper.ByteCount()); + } + proto::Misc m2; + ASSERT_TRUE(m2.ParseFromZeroCopyStream(&in_wrapper)); + ASSERT_EQ(m1.ByteSize(), in_wrapper.ByteCount()); + ASSERT_EQ(m2.ByteSize(), in_wrapper.ByteCount()); + + ASSERT_EQ(m1.required_enum(), m2.required_enum()); + ASSERT_FALSE(m2.has_optional_enum()); + ASSERT_EQ(m1.required_uint64(), m2.required_uint64()); + ASSERT_TRUE(m2.has_optional_uint64()); + ASSERT_EQ(m1.optional_uint64(), m2.optional_uint64()); + ASSERT_EQ(m1.required_string(), m2.required_string()); + ASSERT_TRUE(m2.has_optional_string()); + ASSERT_EQ(m1.optional_string(), m2.optional_string()); + ASSERT_EQ(REP, m2.repeated_string_size()); + for (int i = 0; i < REP; ++i) { + ASSERT_EQ(m1.repeated_string(i), m2.repeated_string(i)); + } + ASSERT_EQ(m1.required_bool(), m2.required_bool()); + ASSERT_FALSE(m2.has_optional_bool()); + ASSERT_EQ(m1.required_int32(), m2.required_int32()); + ASSERT_FALSE(m2.has_optional_int32()); +} + +TEST_F(IOBufTest, extended_backup) { + for (int i = 0; i < 2; ++i) { + std::cout << "i=" << i << std::endl; + // Consume the left TLS block so that cases are easier to check. + butil::iobuf::remove_tls_block_chain(); + butil::IOBuf src; + const int BLKSIZE = (i == 0 ? 1024 : butil::GetDefaultBlockSize()); + const int PLDSIZE = BLKSIZE - BLOCK_OVERHEAD; + butil::IOBufAsZeroCopyOutputStream out_stream1(&src, BLKSIZE); + butil::IOBufAsZeroCopyOutputStream out_stream2(&src); + butil::IOBufAsZeroCopyOutputStream & out_stream = + (i == 0 ? out_stream1 : out_stream2); + void* blk1 = NULL; + int size1 = 0; + ASSERT_TRUE(out_stream.Next(&blk1, &size1)); + ASSERT_EQ(PLDSIZE, size1); + ASSERT_EQ(size1, out_stream.ByteCount()); + void* blk2 = NULL; + int size2 = 0; + ASSERT_TRUE(out_stream.Next(&blk2, &size2)); + ASSERT_EQ(PLDSIZE, size2); + ASSERT_EQ(size1 + size2, out_stream.ByteCount()); + // BackUp a size that's valid for all ZeroCopyOutputStream + out_stream.BackUp(PLDSIZE / 2); + ASSERT_EQ(size1 + size2 - PLDSIZE / 2, out_stream.ByteCount()); + void* blk3 = NULL; + int size3 = 0; + ASSERT_TRUE(out_stream.Next(&blk3, &size3)); + ASSERT_EQ((char*)blk2 + PLDSIZE / 2, blk3); + ASSERT_EQ(PLDSIZE / 2, size3); + ASSERT_EQ(size1 + size2, out_stream.ByteCount()); + + // BackUp a size that's undefined in regular ZeroCopyOutputStream + out_stream.BackUp(PLDSIZE * 2); + ASSERT_EQ(0, out_stream.ByteCount()); + void* blk4 = NULL; + int size4 = 0; + ASSERT_TRUE(out_stream.Next(&blk4, &size4)); + ASSERT_EQ(PLDSIZE, size4); + ASSERT_EQ(size4, out_stream.ByteCount()); + if (i == 1) { + ASSERT_EQ(blk1, blk4); + } + void* blk5 = NULL; + int size5 = 0; + ASSERT_TRUE(out_stream.Next(&blk5, &size5)); + ASSERT_EQ(PLDSIZE, size5); + ASSERT_EQ(size4 + size5, out_stream.ByteCount()); + if (i == 1) { + ASSERT_EQ(blk2, blk5); + } + } +} + +TEST_F(IOBufTest, backup_iobuf_never_called_next) { + { + // Consume the left TLS block so that later cases are easier + // to check. + butil::IOBuf dummy; + butil::IOBufAsZeroCopyOutputStream dummy_stream(&dummy); + void* dummy_data = NULL; + int dummy_size = 0; + ASSERT_TRUE(dummy_stream.Next(&dummy_data, &dummy_size)); + } + butil::IOBuf src; + const size_t N = DEFAULT_PAYLOAD * 2; + src.resize(N); + ASSERT_EQ(2u, src.backing_block_num()); + ASSERT_EQ(N, src.size()); + butil::IOBufAsZeroCopyOutputStream out_stream(&src); + out_stream.BackUp(1); // also succeed. + ASSERT_EQ(-1, out_stream.ByteCount()); + ASSERT_EQ(DEFAULT_PAYLOAD * 2 - 1, src.size()); + ASSERT_EQ(2u, src.backing_block_num()); + void* data0 = NULL; + int size0 = 0; + ASSERT_TRUE(out_stream.Next(&data0, &size0)); + ASSERT_EQ(1, size0); + ASSERT_EQ(0, out_stream.ByteCount()); + ASSERT_EQ(2u, src.backing_block_num()); + void* data1 = NULL; + int size1 = 0; + ASSERT_TRUE(out_stream.Next(&data1, &size1)); + ASSERT_EQ(size1, out_stream.ByteCount()); + ASSERT_EQ(3u, src.backing_block_num()); + ASSERT_EQ(N + size1, src.size()); + void* data2 = NULL; + int size2 = 0; + ASSERT_TRUE(out_stream.Next(&data2, &size2)); + ASSERT_EQ(size1 + size2, out_stream.ByteCount()); + ASSERT_EQ(4u, src.backing_block_num()); + ASSERT_EQ(N + size1 + size2, src.size()); + LOG(INFO) << "Backup1"; + out_stream.BackUp(size1); // intended size1, not size2 to make this BackUp + // to cross boundary of blocks. + ASSERT_EQ(size2, out_stream.ByteCount()); + ASSERT_EQ(N + size2, src.size()); + LOG(INFO) << "Backup2"; + out_stream.BackUp(size2); + ASSERT_EQ(0, out_stream.ByteCount()); + ASSERT_EQ(N, src.size()); +} + +void *backup_thread(void *arg) { + butil::IOBufAsZeroCopyOutputStream *wrapper = + (butil::IOBufAsZeroCopyOutputStream *)arg; + wrapper->BackUp(1024); + return NULL; +} + +TEST_F(IOBufTest, backup_in_another_thread) { + butil::IOBuf buf; + butil::IOBufAsZeroCopyOutputStream wrapper(&buf); + size_t alloc_size = 0; + for (int i = 0; i < 10; ++i) { + void *data; + int len; + ASSERT_TRUE(wrapper.Next(&data, &len)); + alloc_size += len; + } + ASSERT_EQ(alloc_size, buf.length()); + for (int i = 0; i < 10; ++i) { + void *data; + int len; + ASSERT_TRUE(wrapper.Next(&data, &len)); + alloc_size += len; + pthread_t tid; + pthread_create(&tid, NULL, backup_thread, &wrapper); + pthread_join(tid, NULL); + } + ASSERT_EQ(alloc_size - 1024 * 10, buf.length()); +} + +TEST_F(IOBufTest, own_block) { + butil::IOBuf buf; + const ssize_t BLOCK_SIZE = 1024; + butil::IOBuf::Block *saved_tls_block = butil::iobuf::get_tls_block_head(); + butil::IOBufAsZeroCopyOutputStream wrapper(&buf, BLOCK_SIZE); + int alloc_size = 0; + for (int i = 0; i < 100; ++i) { + void *data; + int size; + ASSERT_TRUE(wrapper.Next(&data, &size)); + alloc_size += size; + if (size > 1) { + wrapper.BackUp(1); + alloc_size -= 1; + } + } + ASSERT_EQ(static_cast(alloc_size), buf.length()); + ASSERT_EQ(saved_tls_block, butil::iobuf::get_tls_block_head()); + ASSERT_EQ(butil::iobuf::block_cap(buf._front_ref().block), BLOCK_SIZE - BLOCK_OVERHEAD); +} + +struct Foo1 { + explicit Foo1(int x2) : x(x2) {} + int x; +}; + +struct Foo2 { + std::vector foo1; +}; + +std::ostream& operator<<(std::ostream& os, const Foo1& foo1) { + return os << "foo1.x=" << foo1.x; +} + +std::ostream& operator<<(std::ostream& os, const Foo2& foo2) { + for (size_t i = 0; i < foo2.foo1.size(); ++i) { + os << "foo2[" << i << "]=" << foo2.foo1[i]; + } + return os; +} + +TEST_F(IOBufTest, as_ostream) { + butil::iobuf::reset_blockmem_allocate_and_deallocate(); + + butil::IOBufBuilder builder; + LOG(INFO) << "sizeof(IOBufBuilder)=" << sizeof(builder) << std::endl + << "sizeof(IOBuf)=" << sizeof(butil::IOBuf) << std::endl + << "sizeof(IOBufAsZeroCopyOutputStream)=" + << sizeof(butil::IOBufAsZeroCopyOutputStream) << std::endl + << "sizeof(ZeroCopyStreamAsStreamBuf)=" + << sizeof(butil::ZeroCopyStreamAsStreamBuf) << std::endl + << "sizeof(ostream)=" << sizeof(std::ostream); + int x = -1; + builder << 2 << " " << x << " " << 1.1 << " hello "; + ASSERT_EQ("2 -1 1.1 hello ", builder.buf().to_string()); + + builder << "world!"; + ASSERT_EQ("2 -1 1.1 hello world!", builder.buf().to_string()); + + builder.buf().clear(); + builder << "world!"; + ASSERT_EQ("world!", builder.buf().to_string()); + + Foo2 foo2; + for (int i = 0; i < 10000; ++i) { + foo2.foo1.push_back(Foo1(i)); + } + builder.buf().clear(); + builder << "" << foo2 << ""; + std::ostringstream oss; + oss << "" << foo2 << ""; + ASSERT_EQ(oss.str(), builder.buf().to_string()); + + butil::IOBuf target; + builder.move_to(target); + ASSERT_TRUE(builder.buf().empty()); + ASSERT_EQ(oss.str(), target.to_string()); + + std::ostringstream oss2; + oss2 << target; + ASSERT_EQ(oss.str(), oss2.str()); +} + +TEST_F(IOBufTest, append_from_fd_with_offset) { + butil::TempFile file; + file.save("dummy"); + butil::fd_guard fd(open(file.fname(), O_RDWR | O_TRUNC)); + ASSERT_TRUE(fd >= 0) << file.fname() << ' ' << berror(); + butil::IOPortal buf; + char dummy[10 * 1024]; + buf.append(dummy, sizeof(dummy)); + ASSERT_EQ((ssize_t)sizeof(dummy), buf.cut_into_file_descriptor(fd)); + for (size_t i = 0; i < sizeof(dummy); ++i) { + butil::IOPortal b0; + ASSERT_EQ(sizeof(dummy) - i, (size_t)b0.pappend_from_file_descriptor(fd, i, sizeof(dummy))) << berror(); + char tmp[sizeof(dummy)]; + ASSERT_EQ(0, memcmp(dummy + i, b0.fetch(tmp, b0.length()), b0.length())); + } + +} + +static butil::atomic s_nthread(0); +static long number_per_thread = 1024; + +void* cut_into_fd(void* arg) { + int fd = (int)(long)arg; + const long start_num = number_per_thread * + s_nthread.fetch_add(1, butil::memory_order_relaxed); + off_t offset = start_num * sizeof(int); + for (int i = 0; i < number_per_thread; ++i) { + int to_write = start_num + i; + butil::IOBuf out; + out.append(&to_write, sizeof(int)); + CHECK_EQ(out.pcut_into_file_descriptor(fd, offset + sizeof(int) * i), + (ssize_t)sizeof(int)); + } + return NULL; +} + +TEST_F(IOBufTest, cut_into_fd_with_offset_multithreaded) { + s_nthread.store(0); + number_per_thread = 10240; + pthread_t threads[8]; + long fd = open(".out.txt", O_RDWR | O_CREAT | O_TRUNC, 0644); + ASSERT_TRUE(fd >= 0) << berror(); + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + ASSERT_EQ(0, pthread_create(&threads[i], NULL, cut_into_fd, (void*)fd)); + } + for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { + pthread_join(threads[i], NULL); + } + for (int i = 0; i < number_per_thread * (int)ARRAY_SIZE(threads); ++i) { + off_t offset = i * sizeof(int); + butil::IOPortal in; + ASSERT_EQ((ssize_t)sizeof(int), in.pappend_from_file_descriptor(fd, offset, sizeof(int))); + int j; + ASSERT_EQ(sizeof(j), in.cutn(&j, sizeof(j))); + ASSERT_EQ(i, j); + } +} + +TEST_F(IOBufTest, slice) { + size_t N = 100000; + std::string expected; + expected.reserve(N); + butil::IOBuf buf; + for (size_t i = 0; i < N; ++i) { + expected.push_back(i % 26 + 'a'); + buf.push_back(i % 26 + 'a'); + } + const size_t block_count = buf.backing_block_num(); + std::string actual; + actual.reserve(expected.size()); + for (size_t i = 0; i < block_count; ++i) { + butil::StringPiece p = buf.backing_block(i); + ASSERT_FALSE(p.empty()); + actual.append(p.data(), p.size()); + } + ASSERT_TRUE(expected == actual); +} + +TEST_F(IOBufTest, swap) { + butil::IOBuf a; + a.append("I'am a"); + butil::IOBuf b; + b.append("I'am b"); + std::swap(a, b); + ASSERT_TRUE(a.equals("I'am b")); + ASSERT_TRUE(b.equals("I'am a")); +} + +TEST_F(IOBufTest, resize) { + butil::IOBuf a; + a.resize(100); + std::string as; + as.resize(100); + butil::IOBuf b; + b.resize(100, 'b'); + std::string bs; + bs.resize(100, 'b'); + ASSERT_EQ(100u, a.length()); + ASSERT_EQ(100u, b.length()); + ASSERT_TRUE(a.equals(as)); + ASSERT_TRUE(b.equals(bs)); +} + +TEST_F(IOBufTest, iterate_bytes) { + butil::IOBuf a; + a.append("hello world"); + std::string saved_a = a.to_string(); + size_t n = 0; + butil::IOBufBytesIterator it(a); + for (; it != NULL; ++it, ++n) { + ASSERT_EQ(saved_a[n], *it); + } + ASSERT_EQ(saved_a.size(), n); + ASSERT_TRUE(saved_a == a); + + // append more to the iobuf, iterator should still be ended. + a.append(", this is iobuf"); + ASSERT_TRUE(it == NULL); + + // append more-than-one-block data to the iobuf + for (int i = 0; i < 1024; ++i) { + a.append("ab33jm4hgaklkv;2[25lj4lkj312kl4j321kl4j3k"); + } + saved_a = a.to_string(); + n = 0; + for (butil::IOBufBytesIterator it2(a); it2 != NULL; it2++/*intended post++*/, ++n) { + ASSERT_EQ(saved_a[n], *it2); + } + ASSERT_EQ(saved_a.size(), n); + ASSERT_TRUE(saved_a == a); +} + +TEST_F(IOBufTest, appender) { + butil::IOBufAppender appender; + ASSERT_EQ(0, appender.append("hello", 5)); + ASSERT_EQ("hello", appender.buf()); + ASSERT_EQ(0, appender.push_back(' ')); + ASSERT_EQ(0, appender.append("world", 5)); + ASSERT_EQ("hello world", appender.buf()); + butil::IOBuf buf2; + appender.move_to(buf2); + ASSERT_EQ("", appender.buf()); + ASSERT_EQ("hello world", buf2); + std::string str; + for (int i = 0; i < 10000; ++i) { + char buf[128]; + int len = snprintf(buf, sizeof(buf), "1%d2%d3%d4%d5%d", i, i, i, i, i); + appender.append(buf, len); + str.append(buf, len); + } + ASSERT_EQ(str, appender.buf()); + butil::IOBuf buf3; + appender.move_to(buf3); + ASSERT_EQ("", appender.buf()); + ASSERT_EQ(str, buf3); +} + +TEST_F(IOBufTest, appender_perf) { + const size_t N1 = 100000; + butil::Timer tm1; + tm1.start(); + butil::IOBuf buf1; + for (size_t i = 0; i < N1; ++i) { + buf1.push_back(i); + } + tm1.stop(); + + butil::Timer tm2; + tm2.start(); + butil::IOBufAppender appender1; + for (size_t i = 0; i < N1; ++i) { + appender1.push_back(i); + } + tm2.stop(); + + LOG(INFO) << "IOBuf.push_back=" << tm1.n_elapsed() / N1 + << "ns IOBufAppender.push_back=" << tm2.n_elapsed() / N1 + << "ns"; + + const size_t N2 = 50000; + const std::string s = "a repeatly appended string"; + std::string str2; + butil::IOBuf buf2; + tm1.start(); + for (size_t i = 0; i < N2; ++i) { + buf2.append(s); + } + tm1.stop(); + + tm2.start(); + butil::IOBufAppender appender2; + for (size_t i = 0; i < N2; ++i) { + appender2.append(s); + } + tm2.stop(); + + butil::Timer tm3; + tm3.start(); + for (size_t i = 0; i < N2; ++i) { + str2.append(s); + } + tm3.stop(); + + LOG(INFO) << "IOBuf.append=" << tm1.n_elapsed() / N2 + << "ns IOBufAppender.append=" << tm2.n_elapsed() / N2 + << "ns string.append=" << tm3.n_elapsed() / N2 + << "ns (string-length=" << s.size() << ')'; +} + +TEST_F(IOBufTest, printed_as_binary) { + butil::IOBuf buf; + std::string str; + for (int i = 0; i < 256; ++i) { + buf.push_back((char)i); + str.push_back((char)i); + } + const char* const OUTPUT = + "\\00\\01\\02\\03\\04\\05\\06\\07\\b\\t\\n\\0B\\0C\\r\\0E\\0F" + "\\10\\11\\12\\13\\14\\15\\16\\17\\18\\19\\1A\\1B\\1C\\1D\\1E" + "\\1F !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUV" + "WXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\\7F\\80\\81\\82" + "\\83\\84\\85\\86\\87\\88\\89\\8A\\8B\\8C\\8D\\8E\\8F\\90\\91" + "\\92\\93\\94\\95\\96\\97\\98\\99\\9A\\9B\\9C\\9D\\9E\\9F\\A0" + "\\A1\\A2\\A3\\A4\\A5\\A6\\A7\\A8\\A9\\AA\\AB\\AC\\AD\\AE\\AF" + "\\B0\\B1\\B2\\B3\\B4\\B5\\B6\\B7\\B8\\B9\\BA\\BB\\BC\\BD\\BE" + "\\BF\\C0\\C1\\C2\\C3\\C4\\C5\\C6\\C7\\C8\\C9\\CA\\CB\\CC\\CD" + "\\CE\\CF\\D0\\D1\\D2\\D3\\D4\\D5\\D6\\D7\\D8\\D9\\DA\\DB\\DC" + "\\DD\\DE\\DF\\E0\\E1\\E2\\E3\\E4\\E5\\E6\\E7\\E8\\E9\\EA\\EB" + "\\EC\\ED\\EE\\EF\\F0\\F1\\F2\\F3\\F4\\F5\\F6\\F7\\F8\\F9\\FA" + "\\FB\\FC\\FD\\FE\\FF"; + std::ostringstream os; + os << butil::ToPrintable(buf, 256); + ASSERT_STREQ(OUTPUT, os.str().c_str()); + os.str(""); + os << butil::ToPrintable(str, 256); + ASSERT_STREQ(OUTPUT, os.str().c_str()); +} + +TEST_F(IOBufTest, copy_to_string_from_iterator) { + butil::IOBuf b0; + for (size_t i = 0; i < 1 * 1024 * 1024lu; ++i) { + b0.push_back(butil::fast_rand_in('a', 'z')); + } + butil::IOBuf b1(b0); + butil::IOBufBytesIterator iter(b0); + size_t nc = 0; + while (nc < b0.length()) { + size_t to_copy = butil::fast_rand_in(1024lu, 64 * 1024lu); + butil::IOBuf b; + b1.cutn(&b, to_copy); + std::string s; + const size_t copied = iter.copy_and_forward(&s, to_copy); + ASSERT_GT(copied, 0u); + ASSERT_TRUE(b.equals(s)); + nc += copied; + } + ASSERT_EQ(nc, b0.length()); +} + +static void* my_free_params = NULL; +static void my_free(void* m) { + free(m); + my_free_params = m; +} + +TEST_F(IOBufTest, append_user_data_and_consume) { + butil::IOBuf b0; + const int REP = 16; + const size_t len = REP * 256; + char* data = (char*)malloc(len); + for (int i = 0; i < 256; ++i) { + for (int j = 0; j < REP; ++j) { + data[i * REP + j] = (char)i; + } + } + my_free_params = NULL; + ASSERT_EQ(0, b0.append_user_data(data, len, my_free)); + ASSERT_EQ(1UL, b0._ref_num()); + butil::IOBuf::BlockRef r = b0._front_ref(); + ASSERT_EQ(1, butil::iobuf::block_shared_count(r.block)); + ASSERT_EQ(len, b0.size()); + std::string out; + ASSERT_EQ(len, b0.cutn(&out, len)); + ASSERT_TRUE(b0.empty()); + ASSERT_EQ(data, my_free_params); + + ASSERT_EQ(len, out.size()); + // note: cannot memcmp with data which is already free-ed + for (int i = 0; i < 256; ++i) { + for (int j = 0; j < REP; ++j) { + ASSERT_EQ((char)i, out[i * REP + j]); + } + } +} + +TEST_F(IOBufTest, append_stateful_user_data) { + butil::IOBuf b0; + const int REP = 16; + const size_t len = REP * 256; + std::shared_ptr mem(new char[len], std::default_delete()); + std::weak_ptr weaker = mem; + ASSERT_EQ(1, mem.use_count()); + char* data = mem.get(); + for (int i = 0; i < 256; ++i) { + for (int j = 0; j < REP; ++j) { + data[i * REP + j] = (char)i; + } + } + + int incr_upon_destructrion = 0; + + struct Deleter { + Deleter(std::shared_ptr partial, int& incr) : partial(std::move(partial)), incr(&incr), allow_incr(false) {} + ~Deleter() { + if (allow_incr) (*incr)++; + } + Deleter(const Deleter&) { std::abort(); /*if copy, then crash*/ } + Deleter(Deleter&&) noexcept = default; + void operator()(void*) { + partial.reset(); + allow_incr = true; + } + std::shared_ptr partial; + int* incr; + bool allow_incr; + }; + + for (int i = 0; i < 256; i++) { + std::shared_ptr ptr(mem, data + i * REP); + ASSERT_EQ(0, b0.append_user_data(data + i * REP, REP, Deleter{std::move(ptr), incr_upon_destructrion})); + } + ASSERT_EQ(256, b0._ref_num()); + ASSERT_EQ(257, mem.use_count()); + mem.reset(); + butil::IOBuf::BlockRef r = b0._front_ref(); + ASSERT_EQ(1, butil::iobuf::block_shared_count(r.block)); + ASSERT_EQ(len, b0.size()); + std::string out; + ASSERT_EQ(len, b0.cutn(&out, len)); + ASSERT_TRUE(b0.empty()); + ASSERT_TRUE(weaker.expired()); + ASSERT_EQ(256, incr_upon_destructrion); + + ASSERT_EQ(len, out.size()); + // note: cannot memcmp with data which is already free-ed + for (int i = 0; i < 256; ++i) { + for (int j = 0; j < REP; ++j) { + ASSERT_EQ((char)i, out[i * REP + j]); + } + } +} + +TEST_F(IOBufTest, append_user_data_and_share) { + butil::IOBuf b0; + const int REP = 16; + const size_t len = REP * 256; + char* data = (char*)malloc(len); + for (int i = 0; i < 256; ++i) { + for (int j = 0; j < REP; ++j) { + data[i * REP + j] = (char)i; + } + } + my_free_params = NULL; + ASSERT_EQ(0, b0.append_user_data(data, len, my_free)); + ASSERT_EQ(1UL, b0._ref_num()); + butil::IOBuf::BlockRef r = b0._front_ref(); + ASSERT_EQ(1, butil::iobuf::block_shared_count(r.block)); + ASSERT_EQ(len, b0.size()); + + { + butil::IOBuf bufs[256]; + for (int i = 0; i < 256; ++i) { + ASSERT_EQ((size_t)REP, b0.cutn(&bufs[i], REP)); + ASSERT_EQ(len - (i+1) * REP, b0.size()); + if (i != 255) { + ASSERT_EQ(1UL, b0._ref_num()); + butil::IOBuf::BlockRef r = b0._front_ref(); + ASSERT_EQ(i + 2, butil::iobuf::block_shared_count(r.block)); + } else { + ASSERT_EQ(0UL, b0._ref_num()); + ASSERT_TRUE(b0.empty()); + } + } + ASSERT_EQ(NULL, my_free_params); + for (int i = 0; i < 256; ++i) { + std::string out = bufs[i].to_string(); + ASSERT_EQ((size_t)REP, out.size()); + for (int j = 0; j < REP; ++j) { + ASSERT_EQ((char)i, out[j]); + } + } + } + ASSERT_EQ(data, my_free_params); +} + +TEST_F(IOBufTest, append_user_data_with_meta) { + butil::IOBuf b0; + const int REP = 16; + const size_t len = 256; + char* data[REP]; + for (int i = 0; i < REP; ++i) { + data[i] = (char*)malloc(len); + ASSERT_EQ(0, b0.append_user_data_with_meta(data[i], len, my_free, i)); + } + for (int i = 0; i < REP; ++i) { + ASSERT_EQ(i, b0.get_first_data_meta()); + butil::IOBuf out; + ASSERT_EQ(len / 2, b0.cutn(&out, len / 2)); + ASSERT_EQ(i, b0.get_first_data_meta()); + ASSERT_EQ(len / 2, b0.cutn(&out, len / 2)); + } +} + +TEST_F(IOBufTest, share_tls_block) { + butil::iobuf::remove_tls_block_chain(); + butil::IOBuf::Block* b = butil::iobuf::acquire_tls_block(); + ASSERT_EQ(0u, butil::iobuf::block_size(b)); + + butil::IOBuf::Block* b2 = butil::iobuf::share_tls_block(); + butil::IOBuf buf; + for (size_t i = 0; i < butil::iobuf::block_cap(b2); i++) { + buf.push_back('x'); + } + // after pushing to b2, b2 is full but it is still head of tls block. + ASSERT_NE(b, b2); + butil::iobuf::release_tls_block_chain(b); + ASSERT_EQ(b, butil::iobuf::share_tls_block()); + // After releasing b, now tls block is b(not full) -> b2(full) -> NULL + for (size_t i = 0; i < butil::iobuf::block_cap(b); i++) { + buf.push_back('x'); + } + // now tls block is b(full) -> b2(full) -> NULL + butil::IOBuf::Block* head_block = butil::iobuf::share_tls_block(); + ASSERT_EQ(0u, butil::iobuf::block_size(head_block)); + ASSERT_NE(b, head_block); + ASSERT_NE(b2, head_block); +} + +TEST_F(IOBufTest, acquire_tls_block) { + butil::iobuf::remove_tls_block_chain(); + butil::IOBuf::Block* b = butil::iobuf::acquire_tls_block(); + const size_t block_cap = butil::iobuf::block_cap(b); + butil::IOBuf buf; + for (size_t i = 0; i < block_cap; i++) { + buf.append("x"); + } + ASSERT_EQ(1, butil::iobuf::get_tls_block_count()); + butil::IOBuf::Block* head = butil::iobuf::get_tls_block_head(); + ASSERT_EQ(butil::iobuf::block_cap(head), butil::iobuf::block_size(head)); + butil::iobuf::release_tls_block_chain(b); + ASSERT_EQ(2, butil::iobuf::get_tls_block_count()); + for (size_t i = 0; i < block_cap; i++) { + buf.append("x"); + } + ASSERT_EQ(2, butil::iobuf::get_tls_block_count()); + head = butil::iobuf::get_tls_block_head(); + ASSERT_EQ(butil::iobuf::block_cap(head), butil::iobuf::block_size(head)); + b = butil::iobuf::acquire_tls_block(); + ASSERT_EQ(0, butil::iobuf::get_tls_block_count()); + ASSERT_NE(butil::iobuf::block_cap(b), butil::iobuf::block_size(b)); + // acquire_tls_block() transfers ownership of a non-full block to the + // caller; return it to TLS so it is not leaked. + butil::iobuf::release_tls_block_chain(b); +} + +TEST_F(IOBufTest, reserve_aligned) { + { + butil::IOReserveAlignedBuf buf(16); + auto area = buf.reserve(1024); + ASSERT_NE(area, butil::IOBuf::INVALID_AREA); + butil::IOBufAsZeroCopyInputStream wrapper(buf); + const void* data; + int size; + int total_size = 0; + while (wrapper.Next(&data, &size)) { + ASSERT_EQ(reinterpret_cast(data) % 16, 0); + ASSERT_EQ(size % 16, 0); + total_size += size; + } + ASSERT_EQ(total_size, 1024); + } + { + butil::IOReserveAlignedBuf buf(4096); + auto area = buf.reserve(1024); + ASSERT_NE(area, butil::IOBuf::INVALID_AREA); + butil::IOBufAsZeroCopyInputStream wrapper(buf); + const void* data; + int size; + int total_size = 0; + while (wrapper.Next(&data, &size)) { + ASSERT_EQ(reinterpret_cast(data) % 4096, 0); + ASSERT_EQ(size % 4096, 0); + total_size += size; + } + ASSERT_EQ(total_size, 4096); + } + { + butil::IOReserveAlignedBuf buf(4096); + auto area = buf.reserve(8191); + ASSERT_NE(area, butil::IOBuf::INVALID_AREA); + butil::IOBufAsZeroCopyInputStream wrapper(buf); + const void* data; + int size; + int total_size = 0; + while (wrapper.Next(&data, &size)) { + ASSERT_EQ(reinterpret_cast(data) % 4096, 0); + ASSERT_EQ(size % 4096, 0); + total_size += size; + } + ASSERT_EQ(total_size, 8192); + } + { + butil::IOReserveAlignedBuf buf(4096); + auto area = buf.reserve(4096 * 10 - 1); + ASSERT_NE(area, butil::IOBuf::INVALID_AREA); + butil::IOBufAsZeroCopyInputStream wrapper(buf); + const void* data; + int size; + int total_size = 0; + while (wrapper.Next(&data, &size)) { + ASSERT_EQ(reinterpret_cast(data) % 4096, 0); + ASSERT_EQ(size % 4096, 0); + total_size += size; + } + ASSERT_EQ(total_size, 4096 * 10); + } + { + butil::IOReserveAlignedBuf buf(4095); + auto area = buf.reserve(4096); + ASSERT_EQ(area, butil::IOBuf::INVALID_AREA); + } + { + butil::IOReserveAlignedBuf buf(8192); + auto area = buf.reserve(4096 * 10 + 1); + ASSERT_NE(area, butil::IOBuf::INVALID_AREA); + butil::IOBufAsZeroCopyInputStream wrapper(buf); + const void* data; + int size; + int total_size = 0; + while (wrapper.Next(&data, &size)) { + ASSERT_EQ(reinterpret_cast(data) % 4096, 0); + ASSERT_EQ(size % 4096, 0); + total_size += size; + } + ASSERT_EQ(total_size, 4096 * 10 + 8192); + } + { + butil::IOReserveAlignedBuf buf(4096); + auto area = buf.reserve(1024 * 1024 * 3); + ASSERT_NE(area, butil::IOBuf::INVALID_AREA); + butil::IOBufAsZeroCopyInputStream wrapper(buf); + const void* data; + int size; + int count = 0; + int total_size = 0; + std::stringstream ss; + while (wrapper.Next(&data, &size)) { + ASSERT_EQ(reinterpret_cast(data) % 4096, 0); + ASSERT_EQ(size % 4096, 0); + std::string str(size, 'A' + count++); + ss << str; + std::memcpy(const_cast(data), str.data(), str.size()); + total_size += size; + } + ASSERT_EQ(total_size, 3145728); + ASSERT_EQ(ss.str(), buf.to_string()); + } +} + +TEST_F(IOBufTest, single_iobuf) { + butil::IOBuf buf1; + // It will be freed by IOBuf. + char *usr_str = (char *)malloc(16); + memset(usr_str, 0, 16); + char src_str[] = "abcdefgh12345678"; + size_t total_len = sizeof(src_str); + strncpy(usr_str, src_str + 8, total_len - 8); + buf1.append(src_str, 8); + buf1.append_user_data(usr_str, total_len - 8, NULL); + ASSERT_EQ(2, buf1.backing_block_num()); + butil::SingleIOBuf sbuf; + ASSERT_EQ(0, sbuf.backing_block_num()); + sbuf.assign(buf1, total_len); + ASSERT_EQ(1, sbuf.backing_block_num()); + size_t s_len = sbuf.get_length(); + ASSERT_EQ(s_len, total_len); + const char* str = (const char*) sbuf.get_begin(); + int ret = strcmp(str, src_str); + ASSERT_EQ(0, ret); + butil::IOBuf buf2; + sbuf.append_to(&buf2); + ASSERT_EQ(buf2.length(), total_len); + butil::SingleIOBuf sbuf2; + sbuf2.swap(sbuf); + ASSERT_EQ(sbuf.get_length(), 0); + ASSERT_EQ(sbuf2.get_length(), total_len); + sbuf2.reset(); + ASSERT_EQ(0, sbuf2.get_length()); + + void* buf = sbuf.allocate(1024); + ASSERT_TRUE(NULL != buf); + buf = sbuf.reallocate_downward(16384, 0, 0); + ASSERT_TRUE(NULL != buf); + s_len = sbuf.get_length(); + ASSERT_EQ(16384, s_len); + + butil::IOBuf::BlockRef ref = sbuf.get_cur_ref(); + butil::SingleIOBuf sbuf3(ref); + s_len = sbuf3.get_length(); + ASSERT_EQ(16384, s_len); + sbuf.deallocate(buf); + + errno = 0; + void *null_buf = sbuf3.reallocate_downward(s_len - 1, 0, 0); + ASSERT_EQ(null_buf, nullptr); + + uint32_t old_size = sbuf3.get_length(); + void *p = sbuf3.reallocate_downward(old_size + 16, 0, old_size); + ASSERT_TRUE(p != nullptr); + old_size = sbuf3.get_length(); + p = sbuf3.reallocate_downward(old_size + 16, old_size, 0); + ASSERT_TRUE(p != nullptr); +} + +TEST_F(IOBufTest, as_input_stream_basic) { + butil::IOBuf buf; + buf.append("hello world"); + + butil::IOBufInputStream stream(buf); + std::string s; + stream >> s; + ASSERT_EQ("hello", s); + stream >> s; + ASSERT_EQ("world", s); + ASSERT_EQ(EOF, stream.peek()); + + // Stream construction must not mutate the source IOBuf. + ASSERT_EQ("hello world", buf.to_string()); +} + +TEST_F(IOBufTest, as_input_stream_empty) { + butil::IOBuf buf; + butil::IOBufInputStream stream(buf); + ASSERT_EQ(EOF, stream.peek()); + char c; + ASSERT_FALSE(stream.get(c)); + ASSERT_TRUE(stream.eof()); +} + +TEST_F(IOBufTest, as_input_stream_accepts_const_iobuf) { + butil::IOBuf buf; + buf.append("abc"); + const butil::IOBuf& cbuf = buf; + butil::IOBufInputStream stream(cbuf); + char c; + ASSERT_TRUE(stream.get(c)); ASSERT_EQ('a', c); + ASSERT_TRUE(stream.get(c)); ASSERT_EQ('b', c); + ASSERT_TRUE(stream.get(c)); ASSERT_EQ('c', c); + ASSERT_EQ(EOF, stream.peek()); +} + +// Each call to append_user_data adds a separate BlockRef, giving us a +// multi-block IOBuf that exercises underflow() across block boundaries. +static void append_as_separate_blocks(butil::IOBuf* buf, + const std::string& payload, + size_t chunk) { + for (size_t i = 0; i < payload.size(); i += chunk) { + const size_t n = std::min(chunk, payload.size() - i); + char* p = static_cast(malloc(n)); + memcpy(p, payload.data() + i, n); + ASSERT_EQ(0, buf->append_user_data(p, n, free)); + } +} + +TEST_F(IOBufTest, as_input_stream_multi_block_read) { + butil::IOBuf buf; + const std::string payload = "the quick brown fox jumps over the lazy dog"; + append_as_separate_blocks(&buf, payload, 7); + ASSERT_GT(buf.backing_block_num(), 1u); + + butil::IOBufInputStream stream(buf); + std::string got(payload.size(), '\0'); + stream.read(&got[0], got.size()); + ASSERT_EQ(static_cast(payload.size()), stream.gcount()); + ASSERT_EQ(payload, got); + ASSERT_EQ(EOF, stream.peek()); +} + +TEST_F(IOBufTest, as_input_stream_large_payload) { + // Payload >> DEFAULT_BLOCK_SIZE (8192) forces multiple blocks even with + // a single append call. + std::string payload; + payload.reserve(100 * 1024); + for (int i = 0; i < 100 * 1024; ++i) { + payload.push_back(static_cast('a' + (i % 26))); + } + butil::IOBuf buf; + buf.append(payload); + ASSERT_GT(buf.backing_block_num(), 1u); + + butil::IOBufInputStream stream(buf); + std::string got(payload.size(), '\0'); + stream.read(&got[0], got.size()); + ASSERT_EQ(static_cast(payload.size()), stream.gcount()); + ASSERT_EQ(payload, got); +} + +TEST_F(IOBufTest, as_input_stream_get_matches_read) { + butil::IOBuf buf; + const std::string payload = "the quick brown fox jumps over the lazy dog"; + append_as_separate_blocks(&buf, payload, 7); + + // Byte-by-byte path (sbumpc). + butil::IOBufInputStream s1(buf); + std::string got1; + char c; + while (s1.get(c)) { + got1.push_back(c); + } + ASSERT_EQ(payload, got1); + + // Bulk path (xsgetn). + butil::IOBufInputStream s2(buf); + std::string got2(payload.size(), '\0'); + s2.read(&got2[0], got2.size()); + ASSERT_EQ(static_cast(payload.size()), s2.gcount()); + ASSERT_EQ(payload, got2); +} + +TEST_F(IOBufTest, as_input_stream_short_read_at_eof) { + butil::IOBuf buf; + buf.append("abcd"); + butil::IOBufInputStream stream(buf); + + char got[8] = {}; + stream.read(got, sizeof(got)); + // istream sets failbit on short read at EOF, but gcount() reflects the + // actual number of bytes transferred. + ASSERT_EQ(4, stream.gcount()); + ASSERT_EQ(0, memcmp(got, "abcd", 4)); + ASSERT_TRUE(stream.eof()); +} + +TEST_F(IOBufTest, as_input_stream_in_avail) { + butil::IOBuf buf; + const std::string parts[] = {"aaa", "bbbb", "ccccc"}; + size_t total = 0; + for (size_t i = 0; i < arraysize(parts); ++i) { + char* p = static_cast(malloc(parts[i].size())); + memcpy(p, parts[i].data(), parts[i].size()); + ASSERT_EQ(0, buf.append_user_data(p, parts[i].size(), free)); + total += parts[i].size(); + } + + butil::IOBufInputStream stream(buf); + // get area is empty, so in_avail() defers to showmanyc() which must sum + // all remaining backing blocks. + ASSERT_EQ(static_cast(total), stream.rdbuf()->in_avail()); +} + +TEST_F(IOBufTest, as_output_stream_basic) { + butil::IOBuf buf; + { + butil::IOBufOutputStream stream(buf); + stream << "hello " << 42 << ' ' << 3.5; + } // dtor calls shrink() + ASSERT_EQ("hello 42 3.5", buf.to_string()); +} + +TEST_F(IOBufTest, as_output_stream_appends_not_overwrites) { + butil::IOBuf buf; + buf.append("prefix:"); + { + butil::IOBufOutputStream stream(buf); + stream << "payload"; + } + ASSERT_EQ("prefix:payload", buf.to_string()); +} + +TEST_F(IOBufTest, as_output_stream_large_payload) { + // Cross multiple blocks (DEFAULT_BLOCK_SIZE == 8192). + std::string payload; + payload.reserve(100 * 1024); + for (int i = 0; i < 100 * 1024; ++i) { + payload.push_back(static_cast('a' + (i % 26))); + } + butil::IOBuf buf; + { + butil::IOBufOutputStream stream(buf); + stream.write(payload.data(), payload.size()); + ASSERT_TRUE(stream.good()); + } + ASSERT_GT(buf.backing_block_num(), 1u); + ASSERT_EQ(payload, buf.to_string()); +} + +TEST_F(IOBufTest, as_output_stream_xsputn_matches_overflow) { + // Same payload, two write paths: bulk write() vs per-byte put(). + const std::string payload = "the quick brown fox jumps over the lazy dog " + "0123456789 alpha beta gamma"; + butil::IOBuf bulk_buf; + { + butil::IOBufOutputStream s(bulk_buf); + s.write(payload.data(), payload.size()); + } + butil::IOBuf byte_buf; + { + butil::IOBufOutputStream s(byte_buf); + for (char c : payload) { + s.put(c); + } + } + ASSERT_EQ(payload, bulk_buf.to_string()); + ASSERT_EQ(payload, byte_buf.to_string()); +} + +TEST_F(IOBufTest, as_output_stream_flush_shrinks_eagerly) { + // Without flush(), IOBuf::length() may exceed bytes-written because Next() + // over-claims the rest of the current block. flush() must reconcile it. + butil::IOBuf buf; + butil::IOBufOutputStream stream(buf); + stream << "abc"; + stream.flush(); + ASSERT_EQ(3u, buf.length()); + ASSERT_EQ("abc", buf.to_string()); + + stream << "defg"; + stream.flush(); + ASSERT_EQ(7u, buf.length()); + ASSERT_EQ("abcdefg", buf.to_string()); +} + +TEST_F(IOBufTest, as_output_stream_dedicated_block_size) { + // Passing block_size routes through create_block instead of TLS pool. + // Pick a small-but-valid block to force many allocations. + butil::IOBuf buf; + const std::string payload(4096, 'z'); + { + butil::IOBufOutputStream stream(buf, /*block_size=*/256); + stream.write(payload.data(), payload.size()); + } + ASSERT_EQ(payload, buf.to_string()); + ASSERT_GT(buf.backing_block_num(), 1u); +} + +TEST_F(IOBufTest, as_output_stream_round_trip_with_input_stream) { + // Write through OutputStream, read back through InputStream. + butil::IOBuf buf; + { + butil::IOBufOutputStream out(buf); + for (int i = 0; i < 1000; ++i) { + out << i << '\n'; + } + } + butil::IOBufInputStream in(buf); + for (int i = 0; i < 1000; ++i) { + int v = -1; + in >> v; + ASSERT_EQ(i, v); + } +} + +#if HAS_NLOHMANN_JSON +// End-to-end test that the IOBuf <-> std::iostream adapters work with +// nlohmann::json — the canonical "RPC handler reads JSON from an IOBuf body +// and writes a JSON reply back to another IOBuf" flow. +TEST_F(IOBufTest, as_stream_nlohmann_json_round_trip) { + // 1. Serialize a JSON object into an IOBuf via IOBufOutputStream. + nlohmann::json reply = { + {"status", "ok"}, + {"code", 200}, + {"items", {1, 2, 3, 4, 5}}, + {"nested", {{"a", "alpha"}, {"b", "beta"}}}, + }; + butil::IOBuf out; + { + butil::IOBufOutputStream os(out); + os << reply; + } // dtor runs shrink(); `out` now holds exactly the serialized bytes. + + ASSERT_EQ(reply.dump(), out.to_string()); + + // 2. Parse the IOBuf back through IOBufInputStream and verify roundtrip. + butil::IOBufInputStream in(out); + nlohmann::json parsed = nlohmann::json::parse(in); + ASSERT_EQ(reply, parsed); + ASSERT_EQ("ok", parsed["status"]); + ASSERT_EQ(200, parsed["code"]); + ASSERT_EQ(5u, parsed["items"].size()); + ASSERT_EQ("alpha", parsed["nested"]["a"]); + + // 3. Pretty-print via std::setw, then re-parse — verifies formatting flags + // propagate through IOBufAsOutputStreamBuf correctly. + butil::IOBuf pretty; + { + butil::IOBufOutputStream os(pretty); + os << std::setw(2) << reply; + } + ASSERT_EQ(reply.dump(2), pretty.to_string()); + butil::IOBufInputStream pretty_in(pretty); + ASSERT_EQ(reply, nlohmann::json::parse(pretty_in)); +} + +TEST_F(IOBufTest, as_stream_nlohmann_json_large_array) { + // Build a payload large enough to span multiple IOBuf blocks + // (DEFAULT_BLOCK_SIZE == 8192) and exercise xsputn/xsgetn across + // block boundaries. + nlohmann::json arr = nlohmann::json::array(); + for (int i = 0; i < 5000; ++i) { + arr.push_back({{"i", i}, {"sq", i * i}}); + } + + butil::IOBuf buf; + { + butil::IOBufOutputStream os(buf); + os << arr; + } + ASSERT_GT(buf.backing_block_num(), 1u) << "payload should span >1 block"; + ASSERT_EQ(arr.dump(), buf.to_string()); + + butil::IOBufInputStream in(buf); + nlohmann::json parsed = nlohmann::json::parse(in); + ASSERT_EQ(arr, parsed); + ASSERT_EQ(5000u, parsed.size()); + ASSERT_EQ(4999, parsed[4999]["i"]); + ASSERT_EQ(4999 * 4999, parsed[4999]["sq"]); +} +#endif // HAS_NLOHMANN_JSON +} // namespace diff --git a/test/lazy_instance_unittest.cc b/test/lazy_instance_unittest.cc new file mode 100644 index 0000000..a416f1d --- /dev/null +++ b/test/lazy_instance_unittest.cc @@ -0,0 +1,172 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/at_exit.h" +#include "butil/atomic_sequence_num.h" +#include "butil/lazy_instance.h" +#include "butil/memory/aligned_memory.h" +#include "butil/threading/simple_thread.h" +#include + +namespace { + +butil::StaticAtomicSequenceNumber constructed_seq_; +butil::StaticAtomicSequenceNumber destructed_seq_; + +class ConstructAndDestructLogger { + public: + ConstructAndDestructLogger() { + constructed_seq_.GetNext(); + } + ~ConstructAndDestructLogger() { + destructed_seq_.GetNext(); + } +}; + +class SlowConstructor { + public: + SlowConstructor() : some_int_(0) { + // Sleep for 1 second to try to cause a race. + butil::PlatformThread::Sleep(butil::TimeDelta::FromSeconds(1)); + ++constructed; + some_int_ = 12; + } + int some_int() const { return some_int_; } + + static int constructed; + private: + int some_int_; +}; + +int SlowConstructor::constructed = 0; + +class SlowDelegate : public butil::DelegateSimpleThread::Delegate { + public: + explicit SlowDelegate(butil::LazyInstance* lazy) + : lazy_(lazy) {} + + virtual void Run() OVERRIDE { + EXPECT_EQ(12, lazy_->Get().some_int()); + EXPECT_EQ(12, lazy_->Pointer()->some_int()); + } + + private: + butil::LazyInstance* lazy_; +}; + +} // namespace + +static butil::LazyInstance lazy_logger = + LAZY_INSTANCE_INITIALIZER; + +TEST(LazyInstanceTest, Basic) { + { + butil::ShadowingAtExitManager shadow; + + EXPECT_EQ(0, constructed_seq_.GetNext()); + EXPECT_EQ(0, destructed_seq_.GetNext()); + + lazy_logger.Get(); + EXPECT_EQ(2, constructed_seq_.GetNext()); + EXPECT_EQ(1, destructed_seq_.GetNext()); + + lazy_logger.Pointer(); + EXPECT_EQ(3, constructed_seq_.GetNext()); + EXPECT_EQ(2, destructed_seq_.GetNext()); + } + EXPECT_EQ(4, constructed_seq_.GetNext()); + EXPECT_EQ(4, destructed_seq_.GetNext()); +} + +static butil::LazyInstance lazy_slow = + LAZY_INSTANCE_INITIALIZER; + +TEST(LazyInstanceTest, ConstructorThreadSafety) { + { + butil::ShadowingAtExitManager shadow; + + SlowDelegate delegate(&lazy_slow); + EXPECT_EQ(0, SlowConstructor::constructed); + + butil::DelegateSimpleThreadPool pool("lazy_instance_cons", 5); + pool.AddWork(&delegate, 20); + EXPECT_EQ(0, SlowConstructor::constructed); + + pool.Start(); + pool.JoinAll(); + EXPECT_EQ(1, SlowConstructor::constructed); + } +} + +namespace { + +// DeleteLogger is an object which sets a flag when it's destroyed. +// It accepts a bool* and sets the bool to true when the dtor runs. +class DeleteLogger { + public: + DeleteLogger() : deleted_(NULL) {} + ~DeleteLogger() { *deleted_ = true; } + + void SetDeletedPtr(bool* deleted) { + deleted_ = deleted; + } + + private: + bool* deleted_; +}; + +} // anonymous namespace + +TEST(LazyInstanceTest, LeakyLazyInstance) { + // Check that using a plain LazyInstance causes the dtor to run + // when the AtExitManager finishes. + bool deleted1 = false; + { + butil::ShadowingAtExitManager shadow; + static butil::LazyInstance test = LAZY_INSTANCE_INITIALIZER; + test.Get().SetDeletedPtr(&deleted1); + } + EXPECT_TRUE(deleted1); + + // Check that using a *leaky* LazyInstance makes the dtor not run + // when the AtExitManager finishes. + bool deleted2 = false; + { + butil::ShadowingAtExitManager shadow; + static butil::LazyInstance::Leaky + test = LAZY_INSTANCE_INITIALIZER; + test.Get().SetDeletedPtr(&deleted2); + } + EXPECT_FALSE(deleted2); +} + +namespace { + +template +class AlignedData { + public: + AlignedData() {} + ~AlignedData() {} + butil::AlignedMemory data_; +}; + +} // anonymous namespace + +#define EXPECT_ALIGNED(ptr, align) \ + EXPECT_EQ(0u, reinterpret_cast(ptr) & (align - 1)) + +TEST(LazyInstanceTest, Alignment) { + using butil::LazyInstance; + + // Create some static instances with increasing sizes and alignment + // requirements. By ordering this way, the linker will need to do some work to + // ensure proper alignment of the static data. + static LazyInstance > align4 = LAZY_INSTANCE_INITIALIZER; + static LazyInstance > align32 = LAZY_INSTANCE_INITIALIZER; + static LazyInstance > align4096 = LAZY_INSTANCE_INITIALIZER; + + EXPECT_ALIGNED(align4.Pointer(), 4); + EXPECT_ALIGNED(align32.Pointer(), 32); + EXPECT_ALIGNED(align4096.Pointer(), 4096); +} diff --git a/test/leak_tracker_unittest.cc b/test/leak_tracker_unittest.cc new file mode 100644 index 0000000..980d203 --- /dev/null +++ b/test/leak_tracker_unittest.cc @@ -0,0 +1,113 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/debug/leak_tracker.h" +#include "butil/memory/scoped_ptr.h" +#include + +namespace butil { +namespace debug { + +namespace { + +class ClassA { + private: + LeakTracker leak_tracker_; +}; + +class ClassB { + private: + LeakTracker leak_tracker_; +}; + +#ifndef ENABLE_LEAK_TRACKER + +// If leak tracking is disabled, we should do nothing. +TEST(LeakTrackerTest, NotEnabled) { + EXPECT_EQ(-1, LeakTracker::NumLiveInstances()); + EXPECT_EQ(-1, LeakTracker::NumLiveInstances()); + + // Use scoped_ptr so compiler doesn't complain about unused variables. + scoped_ptr a1(new ClassA); + scoped_ptr b1(new ClassB); + scoped_ptr b2(new ClassB); + + EXPECT_EQ(-1, LeakTracker::NumLiveInstances()); + EXPECT_EQ(-1, LeakTracker::NumLiveInstances()); +} + +#else + +TEST(LeakTrackerTest, Basic) { + { + ClassA a1; + + EXPECT_EQ(1, LeakTracker::NumLiveInstances()); + EXPECT_EQ(0, LeakTracker::NumLiveInstances()); + + ClassB b1; + ClassB b2; + + EXPECT_EQ(1, LeakTracker::NumLiveInstances()); + EXPECT_EQ(2, LeakTracker::NumLiveInstances()); + + scoped_ptr a2(new ClassA); + + EXPECT_EQ(2, LeakTracker::NumLiveInstances()); + EXPECT_EQ(2, LeakTracker::NumLiveInstances()); + + a2.reset(); + + EXPECT_EQ(1, LeakTracker::NumLiveInstances()); + EXPECT_EQ(2, LeakTracker::NumLiveInstances()); + } + + EXPECT_EQ(0, LeakTracker::NumLiveInstances()); + EXPECT_EQ(0, LeakTracker::NumLiveInstances()); +} + +// Try some orderings of create/remove to hit different cases in the linked-list +// assembly. +TEST(LeakTrackerTest, LinkedList) { + EXPECT_EQ(0, LeakTracker::NumLiveInstances()); + + scoped_ptr a1(new ClassA); + scoped_ptr a2(new ClassA); + scoped_ptr a3(new ClassA); + scoped_ptr a4(new ClassA); + + EXPECT_EQ(4, LeakTracker::NumLiveInstances()); + + // Remove the head of the list (a1). + a1.reset(); + EXPECT_EQ(3, LeakTracker::NumLiveInstances()); + + // Remove the tail of the list (a4). + a4.reset(); + EXPECT_EQ(2, LeakTracker::NumLiveInstances()); + + // Append to the new tail of the list (a3). + scoped_ptr a5(new ClassA); + EXPECT_EQ(3, LeakTracker::NumLiveInstances()); + + a2.reset(); + a3.reset(); + + EXPECT_EQ(1, LeakTracker::NumLiveInstances()); + + a5.reset(); + EXPECT_EQ(0, LeakTracker::NumLiveInstances()); +} + +TEST(LeakTrackerTest, NoOpCheckForLeaks) { + // There are no live instances of ClassA, so this should do nothing. + LeakTracker::CheckForLeaks(); +} + +#endif // ENABLE_LEAK_TRACKER + +} // namespace + +} // namespace debug +} // namespace butil diff --git a/test/linked_list_unittest.cc b/test/linked_list_unittest.cc new file mode 100644 index 0000000..57d9633 --- /dev/null +++ b/test/linked_list_unittest.cc @@ -0,0 +1,308 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/basictypes.h" +#include "butil/containers/linked_list.h" +#include + +namespace butil { +namespace { + +class Node : public LinkNode { + public: + explicit Node(int id) : id_(id) {} + + int id() const { return id_; } + + private: + int id_; +}; + +class MultipleInheritanceNodeBase { + public: + MultipleInheritanceNodeBase() : field_taking_up_space_(0) {} + int field_taking_up_space_; +}; + +class MultipleInheritanceNode : public MultipleInheritanceNodeBase, + public LinkNode { + public: + MultipleInheritanceNode() {} +}; + +// Checks that when iterating |list| (either from head to tail, or from +// tail to head, as determined by |forward|), we get back |node_ids|, +// which is an array of size |num_nodes|. +void ExpectListContentsForDirection(const LinkedList& list, + int num_nodes, const int* node_ids, bool forward) { + int i = 0; + for (const LinkNode* node = (forward ? list.head() : list.tail()); + node != list.end(); + node = (forward ? node->next() : node->previous())) { + ASSERT_LT(i, num_nodes); + int index_of_id = forward ? i : num_nodes - i - 1; + EXPECT_EQ(node_ids[index_of_id], node->value()->id()); + ++i; + } + EXPECT_EQ(num_nodes, i); +} + +void ExpectListContents(const LinkedList& list, + int num_nodes, + const int* node_ids) { + { + SCOPED_TRACE("Iterating forward (from head to tail)"); + ExpectListContentsForDirection(list, num_nodes, node_ids, true); + } + { + SCOPED_TRACE("Iterating backward (from tail to head)"); + ExpectListContentsForDirection(list, num_nodes, node_ids, false); + } +} + +TEST(LinkedList, Empty) { + LinkedList list; + EXPECT_EQ(list.end(), list.head()); + EXPECT_EQ(list.end(), list.tail()); + ExpectListContents(list, 0, NULL); +} + +TEST(LinkedList, Append) { + LinkedList list; + ExpectListContents(list, 0, NULL); + + Node n1(1); + list.Append(&n1); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n1, list.tail()); + { + const int expected[] = {1}; + ExpectListContents(list, arraysize(expected), expected); + } + + Node n2(2); + list.Append(&n2); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n2, list.tail()); + { + const int expected[] = {1, 2}; + ExpectListContents(list, arraysize(expected), expected); + } + + Node n3(3); + list.Append(&n3); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n3, list.tail()); + { + const int expected[] = {1, 2, 3}; + ExpectListContents(list, arraysize(expected), expected); + } +} + +TEST(LinkedList, RemoveFromList) { + LinkedList list; + + Node n1(1); + Node n2(2); + Node n3(3); + Node n4(4); + Node n5(5); + + list.Append(&n1); + list.Append(&n2); + list.Append(&n3); + list.Append(&n4); + list.Append(&n5); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n5, list.tail()); + { + const int expected[] = {1, 2, 3, 4, 5}; + ExpectListContents(list, arraysize(expected), expected); + } + + // Remove from the middle. + n3.RemoveFromList(); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n5, list.tail()); + { + const int expected[] = {1, 2, 4, 5}; + ExpectListContents(list, arraysize(expected), expected); + } + + // Remove from the tail. + n5.RemoveFromList(); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n4, list.tail()); + { + const int expected[] = {1, 2, 4}; + ExpectListContents(list, arraysize(expected), expected); + } + + // Remove from the head. + n1.RemoveFromList(); + + EXPECT_EQ(&n2, list.head()); + EXPECT_EQ(&n4, list.tail()); + { + const int expected[] = {2, 4}; + ExpectListContents(list, arraysize(expected), expected); + } + + // Empty the list. + n2.RemoveFromList(); + n4.RemoveFromList(); + + ExpectListContents(list, 0, NULL); + EXPECT_EQ(list.end(), list.head()); + EXPECT_EQ(list.end(), list.tail()); + + // Fill the list once again. + list.Append(&n1); + list.Append(&n2); + list.Append(&n3); + list.Append(&n4); + list.Append(&n5); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n5, list.tail()); + { + const int expected[] = {1, 2, 3, 4, 5}; + ExpectListContents(list, arraysize(expected), expected); + } +} + +TEST(LinkedList, InsertBefore) { + LinkedList list; + + Node n1(1); + Node n2(2); + Node n3(3); + Node n4(4); + + list.Append(&n1); + list.Append(&n2); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n2, list.tail()); + { + const int expected[] = {1, 2}; + ExpectListContents(list, arraysize(expected), expected); + } + + n3.InsertBefore(&n2); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n2, list.tail()); + { + const int expected[] = {1, 3, 2}; + ExpectListContents(list, arraysize(expected), expected); + } + + n4.InsertBefore(&n1); + + EXPECT_EQ(&n4, list.head()); + EXPECT_EQ(&n2, list.tail()); + { + const int expected[] = {4, 1, 3, 2}; + ExpectListContents(list, arraysize(expected), expected); + } +} + +TEST(LinkedList, InsertAfter) { + LinkedList list; + + Node n1(1); + Node n2(2); + Node n3(3); + Node n4(4); + + list.Append(&n1); + list.Append(&n2); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n2, list.tail()); + { + const int expected[] = {1, 2}; + ExpectListContents(list, arraysize(expected), expected); + } + + n3.InsertAfter(&n2); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n3, list.tail()); + { + const int expected[] = {1, 2, 3}; + ExpectListContents(list, arraysize(expected), expected); + } + + n4.InsertAfter(&n1); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n3, list.tail()); + { + const int expected[] = {1, 4, 2, 3}; + ExpectListContents(list, arraysize(expected), expected); + } +} + +TEST(LinkedList, MultipleInheritanceNode) { + MultipleInheritanceNode node; + EXPECT_EQ(&node, node.value()); +} + +TEST(LinkedList, EmptyListIsEmpty) { + LinkedList list; + EXPECT_TRUE(list.empty()); +} + +TEST(LinkedList, NonEmptyListIsNotEmpty) { + LinkedList list; + + Node n(1); + list.Append(&n); + + EXPECT_FALSE(list.empty()); +} + +TEST(LinkedList, EmptiedListIsEmptyAgain) { + LinkedList list; + + Node n(1); + list.Append(&n); + n.RemoveFromList(); + + EXPECT_TRUE(list.empty()); +} + +TEST(LinkedList, NodesCanBeReused) { + LinkedList list1; + LinkedList list2; + + Node n(1); + list1.Append(&n); + n.RemoveFromList(); + list2.Append(&n); + + EXPECT_EQ(list2.head()->value(), &n); +} + +TEST(LinkedList, RemovedNodeHasNullNextPrevious) { + LinkedList list; + + Node n(1); + list.Append(&n); + n.RemoveFromList(); + + EXPECT_EQ(&n, n.next()); + EXPECT_EQ(&n, n.previous()); +} + +} // namespace +} // namespace butil diff --git a/test/linked_ptr_unittest.cc b/test/linked_ptr_unittest.cc new file mode 100644 index 0000000..18f51cb --- /dev/null +++ b/test/linked_ptr_unittest.cc @@ -0,0 +1,110 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "butil/memory/linked_ptr.h" +#include "butil/strings/stringprintf.h" +#include + +namespace { + +int num = 0; + +std::string history; + +// Class which tracks allocation/deallocation +struct A { + A(): mynum(num++) { history += butil::StringPrintf("A%d ctor\n", mynum); } + virtual ~A() { history += butil::StringPrintf("A%d dtor\n", mynum); } + virtual void Use() { history += butil::StringPrintf("A%d use\n", mynum); } + int mynum; +}; + +// Subclass +struct B: public A { + B() { history += butil::StringPrintf("B%d ctor\n", mynum); } + virtual ~B() { history += butil::StringPrintf("B%d dtor\n", mynum); } + virtual void Use() OVERRIDE { + history += butil::StringPrintf("B%d use\n", mynum); + } +}; + +} // namespace + +TEST(LinkedPtrTest, Test) { + { + linked_ptr a0, a1, a2; + a0 = a0; + a1 = a2; + ASSERT_EQ(a0.get(), static_cast(NULL)); + ASSERT_EQ(a1.get(), static_cast(NULL)); + ASSERT_EQ(a2.get(), static_cast(NULL)); + ASSERT_TRUE(a0 == NULL); + ASSERT_TRUE(a1 == NULL); + ASSERT_TRUE(a2 == NULL); + + { + linked_ptr a3(new A); + a0 = a3; + ASSERT_TRUE(a0 == a3); + ASSERT_TRUE(a0 != NULL); + ASSERT_TRUE(a0.get() == a3); + ASSERT_TRUE(a0 == a3.get()); + linked_ptr a4(a0); + a1 = a4; + linked_ptr a5(new A); + ASSERT_TRUE(a5.get() != a3); + ASSERT_TRUE(a5 != a3.get()); + a2 = a5; + linked_ptr b0(new B); + linked_ptr a6(b0); + ASSERT_TRUE(b0 == a6); + ASSERT_TRUE(a6 == b0); + ASSERT_TRUE(b0 != NULL); + a5 = b0; + a5 = b0; + a3->Use(); + a4->Use(); + a5->Use(); + a6->Use(); + b0->Use(); + (*b0).Use(); + b0.get()->Use(); + } + + a0->Use(); + a1->Use(); + a2->Use(); + + a1 = a2; + a2.reset(new A); + a0.reset(); + + linked_ptr a7; + } + + ASSERT_EQ(history, + "A0 ctor\n" + "A1 ctor\n" + "A2 ctor\n" + "B2 ctor\n" + "A0 use\n" + "A0 use\n" + "B2 use\n" + "B2 use\n" + "B2 use\n" + "B2 use\n" + "B2 use\n" + "B2 dtor\n" + "A2 dtor\n" + "A0 use\n" + "A0 use\n" + "A1 use\n" + "A3 ctor\n" + "A0 dtor\n" + "A3 dtor\n" + "A1 dtor\n" + ); +} diff --git a/test/lock_unittest.cc b/test/lock_unittest.cc new file mode 100644 index 0000000..49856e3 --- /dev/null +++ b/test/lock_unittest.cc @@ -0,0 +1,216 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/synchronization/lock.h" + +#include + +#include "butil/compiler_specific.h" +#include "butil/threading/platform_thread.h" +#include + +namespace butil { + +// Basic test to make sure that Acquire()/Release()/Try() don't crash ---------- + +class BasicLockTestThread : public PlatformThread::Delegate { + public: + explicit BasicLockTestThread(Lock* lock) : lock_(lock), acquired_(0) {} + + virtual void ThreadMain() OVERRIDE { + for (int i = 0; i < 10; i++) { + lock_->Acquire(); + acquired_++; + lock_->Release(); + } + for (int i = 0; i < 10; i++) { + lock_->Acquire(); + acquired_++; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20)); + lock_->Release(); + } + for (int i = 0; i < 10; i++) { + if (lock_->Try()) { + acquired_++; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20)); + lock_->Release(); + } + } + } + + int acquired() const { return acquired_; } + + private: + Lock* lock_; + int acquired_; + + DISALLOW_COPY_AND_ASSIGN(BasicLockTestThread); +}; + +TEST(LockTest, Basic) { + Lock lock; + BasicLockTestThread thread(&lock); + PlatformThreadHandle handle; + + ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); + + int acquired = 0; + for (int i = 0; i < 5; i++) { + lock.Acquire(); + acquired++; + lock.Release(); + } + for (int i = 0; i < 10; i++) { + lock.Acquire(); + acquired++; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20)); + lock.Release(); + } + for (int i = 0; i < 10; i++) { + if (lock.Try()) { + acquired++; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20)); + lock.Release(); + } + } + for (int i = 0; i < 5; i++) { + lock.Acquire(); + acquired++; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20)); + lock.Release(); + } + + PlatformThread::Join(handle); + + EXPECT_GE(acquired, 20); + EXPECT_GE(thread.acquired(), 20); +} + +// Test that Try() works as expected ------------------------------------------- + +class TryLockTestThread : public PlatformThread::Delegate { + public: + explicit TryLockTestThread(Lock* lock) : lock_(lock), got_lock_(false) {} + + virtual void ThreadMain() OVERRIDE { + got_lock_ = lock_->Try(); + if (got_lock_) + lock_->Release(); + } + + bool got_lock() const { return got_lock_; } + + private: + Lock* lock_; + bool got_lock_; + + DISALLOW_COPY_AND_ASSIGN(TryLockTestThread); +}; + +TEST(LockTest, TryLock) { + Lock lock; + + ASSERT_TRUE(lock.Try()); + // We now have the lock.... + + // This thread will not be able to get the lock. + { + TryLockTestThread thread(&lock); + PlatformThreadHandle handle; + + ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); + + PlatformThread::Join(handle); + + ASSERT_FALSE(thread.got_lock()); + } + + lock.Release(); + + // This thread will.... + { + TryLockTestThread thread(&lock); + PlatformThreadHandle handle; + + ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); + + PlatformThread::Join(handle); + + ASSERT_TRUE(thread.got_lock()); + // But it released it.... + ASSERT_TRUE(lock.Try()); + } + + lock.Release(); +} + +// Tests that locks actually exclude ------------------------------------------- + +class MutexLockTestThread : public PlatformThread::Delegate { + public: + MutexLockTestThread(Lock* lock, int* value) : lock_(lock), value_(value) {} + + // Static helper which can also be called from the main thread. + static void DoStuff(Lock* lock, int* value) { + for (int i = 0; i < 40; i++) { + lock->Acquire(); + int v = *value; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 10)); + *value = v + 1; + lock->Release(); + } + } + + virtual void ThreadMain() OVERRIDE { + DoStuff(lock_, value_); + } + + private: + Lock* lock_; + int* value_; + + DISALLOW_COPY_AND_ASSIGN(MutexLockTestThread); +}; + +TEST(LockTest, MutexTwoThreads) { + Lock lock; + int value = 0; + + MutexLockTestThread thread(&lock, &value); + PlatformThreadHandle handle; + + ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); + + MutexLockTestThread::DoStuff(&lock, &value); + + PlatformThread::Join(handle); + + EXPECT_EQ(2 * 40, value); +} + +TEST(LockTest, MutexFourThreads) { + Lock lock; + int value = 0; + + MutexLockTestThread thread1(&lock, &value); + MutexLockTestThread thread2(&lock, &value); + MutexLockTestThread thread3(&lock, &value); + PlatformThreadHandle handle1; + PlatformThreadHandle handle2; + PlatformThreadHandle handle3; + + ASSERT_TRUE(PlatformThread::Create(0, &thread1, &handle1)); + ASSERT_TRUE(PlatformThread::Create(0, &thread2, &handle2)); + ASSERT_TRUE(PlatformThread::Create(0, &thread3, &handle3)); + + MutexLockTestThread::DoStuff(&lock, &value); + + PlatformThread::Join(handle1); + PlatformThread::Join(handle2); + PlatformThread::Join(handle3); + + EXPECT_EQ(4 * 40, value); +} + +} // namespace butil diff --git a/test/logging_unittest.cc b/test/logging_unittest.cc new file mode 100644 index 0000000..3a7576f --- /dev/null +++ b/test/logging_unittest.cc @@ -0,0 +1,652 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/basictypes.h" +#include "butil/logging.h" +#include "gperftools_helper.h" +#include "butil/files/temp_file.h" +#include "butil/popen.h" +#include +#include + +#if !BRPC_WITH_GLOG + +namespace logging { +DECLARE_bool(crash_on_fatal_log); +DECLARE_int32(v); +DECLARE_bool(log_func_name); +DECLARE_bool(async_log); +DECLARE_bool(async_log_in_background_always); +DECLARE_int32(max_async_log_queue_size); + +namespace { + +// Needs to be global since log assert handlers can't maintain state. +int log_sink_call_count = 0; + +#if !defined(OFFICIAL_BUILD) || defined(DCHECK_ALWAYS_ON) || !defined(NDEBUG) +void LogSink(const std::string& str) { + ++log_sink_call_count; +} +#endif + +// Class to make sure any manipulations we do to the min log level are +// contained (i.e., do not affect other unit tests). +class LogStateSaver { + public: + LogStateSaver() : old_min_log_level_(GetMinLogLevel()) {} + + ~LogStateSaver() { + SetMinLogLevel(old_min_log_level_); + SetLogAssertHandler(NULL); + log_sink_call_count = 0; + } + + private: + int old_min_log_level_; + + DISALLOW_COPY_AND_ASSIGN(LogStateSaver); +}; + +class LoggingTest : public testing::Test { +public: + virtual void SetUp() { + _old_crash_on_fatal_log = ::logging::FLAGS_crash_on_fatal_log; + ::logging::FLAGS_crash_on_fatal_log = true; + } + virtual void TearDown() { + ::logging::FLAGS_crash_on_fatal_log = _old_crash_on_fatal_log; + if (::logging::FLAGS_v != 0) { + // Clear -verbose to avoid affecting other tests. + ASSERT_FALSE(GFLAGS_NAMESPACE::SetCommandLineOption("v", "0").empty()); + ASSERT_FALSE(GFLAGS_NAMESPACE::SetCommandLineOption("vmodule", "").empty()); + } + } +private: + bool _old_crash_on_fatal_log; + LogStateSaver log_state_saver_; +}; + +TEST_F(LoggingTest, LogIsOn) { +#if defined(NDEBUG) + const bool kDfatalIsFatal = false; +#else // defined(NDEBUG) + const bool kDfatalIsFatal = true; +#endif // defined(NDEBUG) + + SetMinLogLevel(BLOG_INFO); + EXPECT_TRUE(LOG_IS_ON(INFO)); + EXPECT_TRUE(LOG_IS_ON(WARNING)); + EXPECT_TRUE(LOG_IS_ON(ERROR)); + EXPECT_TRUE(LOG_IS_ON(FATAL)); + EXPECT_TRUE(LOG_IS_ON(DFATAL)); + + SetMinLogLevel(BLOG_WARNING); + EXPECT_FALSE(LOG_IS_ON(INFO)); + EXPECT_TRUE(LOG_IS_ON(WARNING)); + EXPECT_TRUE(LOG_IS_ON(ERROR)); + EXPECT_TRUE(LOG_IS_ON(FATAL)); + EXPECT_TRUE(LOG_IS_ON(DFATAL)); + + SetMinLogLevel(BLOG_ERROR); + EXPECT_FALSE(LOG_IS_ON(INFO)); + EXPECT_FALSE(LOG_IS_ON(WARNING)); + EXPECT_TRUE(LOG_IS_ON(ERROR)); + EXPECT_TRUE(LOG_IS_ON(FATAL)); + EXPECT_TRUE(LOG_IS_ON(DFATAL)); + + // LOG_IS_ON(FATAL) should always be true. + SetMinLogLevel(BLOG_FATAL + 1); + EXPECT_FALSE(LOG_IS_ON(INFO)); + EXPECT_FALSE(LOG_IS_ON(WARNING)); + EXPECT_FALSE(LOG_IS_ON(ERROR)); + EXPECT_TRUE(LOG_IS_ON(FATAL)); + EXPECT_TRUE(kDfatalIsFatal == LOG_IS_ON(DFATAL)); +} + +TEST_F(LoggingTest, DebugLoggingReleaseBehavior) { +#if !defined(NDEBUG) + int debug_only_variable = 1; +#endif + // These should avoid emitting references to |debug_only_variable| + // in release mode. + DLOG_IF(INFO, debug_only_variable) << "test"; + DLOG_ASSERT(debug_only_variable) << "test"; + DPLOG_IF(INFO, debug_only_variable) << "test"; + DVLOG_IF(1, debug_only_variable) << "test"; +} + +TEST_F(LoggingTest, Dcheck) { +#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON) + // Release build. + EXPECT_FALSE(DCHECK_IS_ON()); + EXPECT_FALSE(DLOG_IS_ON(DCHECK)); +#elif defined(NDEBUG) && defined(DCHECK_ALWAYS_ON) + // Release build with real DCHECKS. + SetLogAssertHandler(&LogSink); + EXPECT_TRUE(DCHECK_IS_ON()); + EXPECT_FALSE(DLOG_IS_ON(DCHECK)); +#else + // Debug build. + SetLogAssertHandler(&LogSink); + EXPECT_TRUE(DCHECK_IS_ON()); + EXPECT_TRUE(DLOG_IS_ON(DCHECK)); +#endif + + EXPECT_EQ(0, log_sink_call_count); + DCHECK(false); + EXPECT_EQ(DCHECK_IS_ON() ? 1 : 0, log_sink_call_count); + DPCHECK(false); + EXPECT_EQ(DCHECK_IS_ON() ? 2 : 0, log_sink_call_count); + DCHECK_EQ(0, 1); + EXPECT_EQ(DCHECK_IS_ON() ? 3 : 0, log_sink_call_count); +} + +TEST_F(LoggingTest, DcheckReleaseBehavior) { + int some_variable = 1; + // These should still reference |some_variable| so we don't get + // unused variable warnings. + DCHECK(some_variable) << "test"; + DPCHECK(some_variable) << "test"; + DCHECK_EQ(some_variable, 1) << "test"; +} + +TEST_F(LoggingTest, streaming_log_sanity) { + ::logging::FLAGS_crash_on_fatal_log = false; + + LOG(WARNING) << 1 << 1.1f << 2l << "apple" << noflush; + LOG(WARNING) << " orange" << noflush; + ASSERT_EQ("11.12apple orange", LOG_STREAM(WARNING).content_str()); + ASSERT_EQ("", LOG_STREAM(WARNING).content_str()); + + LOG(FATAL) << 1 << 1.1f << 2l << "apple" << noflush; + LOG(FATAL) << " orange" << noflush; + ASSERT_EQ("11.12apple orange", LOG_STREAM(FATAL).content_str()); + ASSERT_EQ("", LOG_STREAM(FATAL).content_str()); + + LOG(TRACE) << 1 << 1.1f << 2l << "apple" << noflush; + LOG(TRACE) << " orange" << noflush; + ASSERT_EQ("11.12apple orange", LOG_STREAM(TRACE).content_str()); + ASSERT_EQ("", LOG_STREAM(TRACE).content_str()); + + LOG(NOTICE) << 1 << 1.1f << 2l << "apple" << noflush; + LOG(DEBUG) << 1 << 1.1f << 2l << "apple" << noflush; + + LOG(FATAL) << 1 << 1.1f << 2l << "apple"; + LOG(ERROR) << 1 << 1.1f << 2l << "apple"; + LOG(WARNING) << 1 << 1.1f << 2l << "apple"; + LOG(INFO) << 1 << 1.1f << 2l << "apple"; + LOG(TRACE) << 1 << 1.1f << 2l << "apple"; + LOG(NOTICE) << 2 << 2.2f << 3l << "orange" << noflush; + ASSERT_EQ("11.12apple22.23orange", LOG_STREAM(NOTICE).content_str()); + LOG(DEBUG) << 1 << 1.1f << 2l << "apple"; + + errno = EINVAL; + PLOG(FATAL) << "Error occurred" << noflush; + ASSERT_EQ("Error occurred: Invalid argument", PLOG_STREAM(FATAL).content_str()); + + errno = 0; + PLOG(FATAL) << "Error occurred" << noflush; +#if defined(OS_LINUX) + ASSERT_EQ("Error occurred: Success", PLOG_STREAM(FATAL).content_str()); +#else + ASSERT_EQ("Error occurred: Undefined error: 0", PLOG_STREAM(FATAL).content_str()); +#endif + + errno = EINTR; + PLOG(FATAL) << "Error occurred" << noflush; + ASSERT_EQ("Error occurred: Interrupted system call", + PLOG_STREAM(FATAL).content_str()); +} + +TEST_F(LoggingTest, log_at) { + ::logging::StringSink log_str; + ::logging::LogSink* old_sink = ::logging::SetLogSink(&log_str); + LOG_AT(WARNING, "specified_file.cc", 12345) << "file/line is specified"; + // the file:line part should be using the argument given by us. + ASSERT_NE(std::string::npos, log_str.find("specified_file.cc:12345")); + // restore the old sink. + ::logging::SetLogSink(old_sink); +} + +#define VLOG_NE(verbose_level) VLOG(verbose_level) << noflush + +#define VLOG2_NE(virtual_path, verbose_level) \ + VLOG2(virtual_path, verbose_level) << noflush + +TEST_F(LoggingTest, vlog_sanity) { + ::logging::FLAGS_crash_on_fatal_log = false; + + EXPECT_FALSE(GFLAGS_NAMESPACE::SetCommandLineOption("v", "1").empty()); + + EXPECT_FALSE(GFLAGS_NAMESPACE::SetCommandLineOption("vmodule", + "logging_unittest=1").empty()); + EXPECT_FALSE(GFLAGS_NAMESPACE::SetCommandLineOption("vmodule", + "logging_UNITTEST=2").empty()); + + for (int i = 0; i < 10; ++i) { + VLOG_NE(i) << "vlog " << i; + } + EXPECT_EQ("vlog 1vlog 2", LOG_STREAM(VERBOSE).content_str()); + EXPECT_EQ("vlog 0", LOG_STREAM(INFO).content_str()); + + VLOG_NE(-1) << "nothing"; + EXPECT_EQ("", LOG_STREAM(VERBOSE).content_str()); + + // VLOG(0) is LOG(INFO) + VLOG_NE(0) << "always on"; + EXPECT_EQ("always on", LOG_STREAM(INFO).content_str()); + + EXPECT_FALSE(GFLAGS_NAMESPACE::SetCommandLineOption("vmodule", + "logging_unittest=0").empty()); + for (int i = 0; i < 10; ++i) { + VLOG_NE(i) << "vlog " << i; + } + EXPECT_EQ("", LOG_STREAM(VERBOSE).content_str()); + EXPECT_EQ("vlog 0", LOG_STREAM(INFO).content_str()); + + EXPECT_FALSE(GFLAGS_NAMESPACE::SetCommandLineOption("vmodule", + "logging_unittest=0,logging_unittest=1").empty()); + for (int i = 0; i < 10; ++i) { + VLOG_NE(i) << "vlog " << i; + } + EXPECT_EQ("vlog 1", LOG_STREAM(VERBOSE).content_str()); + EXPECT_EQ("vlog 0", LOG_STREAM(INFO).content_str()); + + EXPECT_FALSE(GFLAGS_NAMESPACE::SetCommandLineOption("vmodule", + "logging_unittest=1,logging_unittest=0").empty()); + for (int i = 0; i < 10; ++i) { + VLOG_NE(i) << "vlog " << i; + } + EXPECT_EQ("", LOG_STREAM(VERBOSE).content_str()); + EXPECT_EQ("vlog 0", LOG_STREAM(INFO).content_str()); + + EXPECT_FALSE(GFLAGS_NAMESPACE::SetCommandLineOption("vmodule", "").empty()); + for (int i = 0; i < 10; ++i) { + VLOG_NE(i) << "vlog " << i; + } + EXPECT_EQ("vlog 1", LOG_STREAM(VERBOSE).content_str()); + EXPECT_EQ("vlog 0", LOG_STREAM(INFO).content_str()); + + EXPECT_FALSE(GFLAGS_NAMESPACE::SetCommandLineOption("vmodule", + "logg?ng_*=2").empty()); + for (int i = 0; i < 10; ++i) { + VLOG_NE(i) << "vlog " << i; + } + EXPECT_EQ("vlog 1vlog 2", LOG_STREAM(VERBOSE).content_str()); + EXPECT_EQ("vlog 0", LOG_STREAM(INFO).content_str()); + + EXPECT_FALSE(GFLAGS_NAMESPACE::SetCommandLineOption("vmodule", + "foo=3,logging_unittest=3, logg?ng_*=2 , logging_*=1 ").empty()); + for (int i = 0; i < 10; ++i) { + VLOG_NE(i) << "vlog " << i; + } + EXPECT_EQ("vlog 1vlog 2vlog 3", LOG_STREAM(VERBOSE).content_str()); + EXPECT_EQ("vlog 0", LOG_STREAM(INFO).content_str()); + + for (int i = 0; i < 10; ++i) { + VLOG_IF(i, i % 2 == 1) << "vlog " << i << noflush; + } + EXPECT_EQ("vlog 1vlog 3", LOG_STREAM(VERBOSE).content_str()); + + EXPECT_FALSE(GFLAGS_NAMESPACE::SetCommandLineOption( + "vmodule", + "foo/bar0/0=2,foo/bar/1=3, 2=4, foo/*/3=5, */ba?/4=6," + "/5=7,/foo/bar/6=8,foo2/bar/7=9,foo/bar/8=9").empty()); + VLOG2_NE("foo/bar/0", 2) << " vlog0"; + VLOG2_NE("foo/bar0/0", 2) << " vlog0.0"; + VLOG2_NE("foo/bar/1", 3) << " vlog1"; + VLOG2_NE("foo/bar/2", 4) << " vlog2"; + VLOG2_NE("foo/bar2/2", 4) << " vlog2.2"; + VLOG2_NE("foo/bar/3", 5) << " vlog3"; + VLOG2_NE("foo/bar/4", 6) << " vlog4"; + VLOG2_NE("foo/bar/5", 7) << " vlog5"; + VLOG2_NE("foo/bar/6", 8) << " vlog6"; + VLOG2_NE("foo/bar/7", 9) << " vlog7"; + VLOG2_NE("foo/bar/8", 10) << " vlog8"; + VLOG2_NE("foo/bar/9", 11) << " vlog9"; + EXPECT_EQ(" vlog0.0 vlog1 vlog2 vlog2.2 vlog3 vlog4", + LOG_STREAM(VERBOSE).content_str()); + + // Make sure verbose log is not flushed to other levels. + ASSERT_TRUE(LOG_STREAM(FATAL).content_str().empty()); + ASSERT_TRUE(LOG_STREAM(ERROR).content_str().empty()); + ASSERT_TRUE(LOG_STREAM(WARNING).content_str().empty()); + ASSERT_TRUE(LOG_STREAM(NOTICE).content_str().empty()); + ASSERT_TRUE(LOG_STREAM(INFO).content_str().empty()); +} + +TEST_F(LoggingTest, check) { + ::logging::FLAGS_crash_on_fatal_log = false; + + CHECK(1 < 2); + CHECK(1 > 2); + int a = 1; + int b = 2; + CHECK(a > b) << "bad! a=" << a << " b=" << b; + + CHECK_EQ(a, b) << "a=" << a << " b=" << b; + CHECK_EQ(1, 1) << "a=" << a << " b=" << b; + + CHECK_NE(2, 1); + CHECK_NE(1, 2) << "blah0"; + CHECK_NE(2, 2) << "blah1"; + + CHECK_LT(2, 3); + CHECK_LT(3, 2) << "blah2"; + CHECK_LT(3, 3) << "blah3"; + + CHECK_LE(2, 3); + CHECK_LE(3, 2) << "blah4"; + CHECK_LE(3, 3); + + CHECK_GT(3, 2); + CHECK_GT(1, 2) << "1 can't be greater than 2"; + CHECK_GT(3, 3) << "blah5"; + + CHECK_GE(3, 2); + CHECK_GE(2, 3) << "blah6"; + CHECK_GE(3, 3); +} + +TEST_F(LoggingTest, log_backtrace) { + LOG_BACKTRACE_IF(INFO, true) << "log_backtrace_if"; + LOG_BACKTRACE_IF_ONCE(INFO, true) << "log_backtrace_if_once"; + LOG_BACKTRACE_ONCE(INFO) << "log_backtrace_once"; +} + +int foo(int* p) { + return ++*p; +} + +TEST_F(LoggingTest, debug_level) { + ::logging::FLAGS_crash_on_fatal_log = false; + + int run_foo = 0; + LOG(DEBUG) << foo(&run_foo) << noflush; + LOG(DEBUG) << foo(&run_foo); + + DLOG(FATAL) << foo(&run_foo); + DLOG(WARNING) << foo(&run_foo); + DLOG(TRACE) << foo(&run_foo); + DLOG(NOTICE) << foo(&run_foo); + DLOG(DEBUG) << foo(&run_foo); + + EXPECT_FALSE(GFLAGS_NAMESPACE::SetCommandLineOption("vmodule", "").empty()); + EXPECT_FALSE(GFLAGS_NAMESPACE::SetCommandLineOption("v", "1").empty()); + DVLOG(1) << foo(&run_foo); + DVLOG2("a/b/c", 1) << foo(&run_foo); + +#ifdef NDEBUG + ASSERT_EQ(0, run_foo); +#else + ASSERT_EQ(9, run_foo); +#endif +} + +static void need_ostream(std::ostream& os, const char* s) { + os << s; +} + +TEST_F(LoggingTest, as_ostream) { + ::logging::FLAGS_crash_on_fatal_log = false; + + need_ostream(LOG_STREAM(WARNING) << noflush, "hello"); + ASSERT_EQ("hello", LOG_STREAM(WARNING).content_str()); + + need_ostream(LOG_STREAM(WARNING), "hello"); + ASSERT_EQ("", LOG_STREAM(WARNING).content_str()); + + need_ostream(LOG_STREAM(INFO) << noflush, "world"); + ASSERT_EQ("world", LOG_STREAM(INFO).content_str()); + + need_ostream(LOG_STREAM(INFO), "world"); + ASSERT_EQ("", LOG_STREAM(INFO).content_str()); + + LOG(WARNING) << 1.123456789; + const std::streamsize saved_prec = LOG_STREAM(WARNING).precision(2); + LOG(WARNING) << 1.123456789; + LOG_STREAM(WARNING).precision(saved_prec); + LOG(WARNING) << 1.123456789; +} + +TEST_F(LoggingTest, limited_logging) { + for (int i = 0; i < 100000; ++i) { + LOG_ONCE(INFO) << "HEHE1"; + LOG_ONCE(INFO) << "HEHE2"; + VLOG_ONCE(1) << "VHEHE3"; + VLOG_ONCE(1) << "VHEHE4"; + LOG_EVERY_N(INFO, 10000) << "i1=" << i; + LOG_EVERY_N(INFO, 5000) << "i2=" << i; + VLOG_EVERY_N(1, 10000) << "vi3=" << i; + VLOG_EVERY_N(1, 5000) << "vi4=" << i; + } + for (int i = 0; i < 300; ++i) { + LOG_EVERY_SECOND(INFO) << "i1=" << i; + LOG_EVERY_SECOND(INFO) << "i2=" << i; + VLOG_EVERY_SECOND(1) << "vi3=" << i; + VLOG_EVERY_SECOND(1) << "vi4=" << i; + usleep(10000); + } +} + +void CheckFunctionName() { + const char* func_name = __func__; + DCHECK(1) << "test"; + ASSERT_EQ(func_name, LOG_STREAM(DCHECK).func()); + + LOG(DEBUG) << "test" << noflush; + ASSERT_EQ(func_name, LOG_STREAM(DEBUG).func()); + LOG(INFO) << "test" << noflush; + ASSERT_EQ(func_name, LOG_STREAM(INFO).func()); + LOG(WARNING) << "test" << noflush; + ASSERT_EQ(func_name, LOG_STREAM(WARNING).func()); + LOG(WARNING) << "test" << noflush; + ASSERT_EQ(func_name, LOG_STREAM(WARNING).func()); + LOG(ERROR) << "test" << noflush; + ASSERT_EQ(func_name, LOG_STREAM(ERROR).func()); + LOG(FATAL) << "test" << noflush; + ASSERT_EQ(func_name, LOG_STREAM(FATAL).func()); + + errno = EINTR; + PLOG(DEBUG) << "test" << noflush; + ASSERT_EQ(func_name, PLOG_STREAM(DEBUG).func()); + PLOG(INFO) << "test" << noflush; + ASSERT_EQ(func_name, PLOG_STREAM(INFO).func()); + PLOG(WARNING) << "test" << noflush; + ASSERT_EQ(func_name, PLOG_STREAM(WARNING).func()); + PLOG(WARNING) << "test" << noflush; + ASSERT_EQ(func_name, PLOG_STREAM(WARNING).func()); + PLOG(ERROR) << "test" << noflush; + ASSERT_EQ(func_name, PLOG_STREAM(ERROR).func()); + PLOG(FATAL) << "test" << noflush; + ASSERT_EQ(func_name, PLOG_STREAM(FATAL).func()); + + ::logging::StringSink log_str; + ::logging::LogSink* old_sink = ::logging::SetLogSink(&log_str); + LOG_AT(WARNING, "specified_file.cc", 12345, "log_at") << "file/line is specified"; + // the file:line part should be using the argument given by us. + ASSERT_NE(std::string::npos, log_str.find("specified_file.cc:12345 log_at")); + ::logging::SetLogSink(old_sink); + + EXPECT_FALSE(GFLAGS_NAMESPACE::SetCommandLineOption("v", "1").empty()); + VLOG(100) << "test" << noflush; + ASSERT_EQ(func_name, VLOG_STREAM(100).func()); +} + +TEST_F(LoggingTest, log_func) { + bool old_crash_on_fatal_log = ::logging::FLAGS_crash_on_fatal_log; + ::logging::FLAGS_crash_on_fatal_log = false; + + ::logging::FLAGS_log_func_name = true; + CheckFunctionName(); + ::logging::FLAGS_log_func_name = false; + + ::logging::FLAGS_crash_on_fatal_log = old_crash_on_fatal_log; +} + +bool g_started = false; +bool g_stopped = false; +int g_prof_name_counter = 0; +butil::atomic test_logging_count(0); + +void* test_async_log(void* arg) { + if (arg == NULL) { + return NULL; + } + auto log = (std::string*)(arg); + while (!g_stopped) { + LOG(INFO) << *log; + test_logging_count.fetch_add(1); + } + + return NULL; +} + +TEST_F(LoggingTest, async_log) { + bool saved_async_log = FLAGS_async_log; + FLAGS_async_log = true; + butil::TempFile temp_file; + LoggingSettings settings; + settings.logging_dest = LOG_TO_FILE; + settings.log_file = temp_file.fname(); + settings.delete_old = DELETE_OLD_LOG_FILE; + InitLogging(settings); + + std::string log = "135792468"; + int thread_num = 8; + pthread_t threads[thread_num]; + for (int i = 0; i < thread_num; ++i) { + ASSERT_EQ(0, pthread_create(&threads[i], NULL, test_async_log, &log)); + } + + usleep(1000 * 500); + + g_stopped = true; + for (int i = 0; i < thread_num; ++i) { + pthread_join(threads[i], NULL); + } + // Wait for async log thread to flush all logs to file. + sleep(15); + + std::ostringstream oss; + std::string cmd = butil::string_printf("grep -c %s %s", + log.c_str(), temp_file.fname()); + ASSERT_LE(0, butil::read_command_output(oss, cmd.c_str())); + uint64_t log_count = std::strtol(oss.str().c_str(), NULL, 10); + ASSERT_EQ(log_count, test_logging_count.load()); + + FLAGS_async_log = saved_async_log; +} + +#if defined(BRPC_ENABLE_CPU_PROFILER) || defined(BAIDU_RPC_ENABLE_CPU_PROFILER) +struct BAIDU_CACHELINE_ALIGNMENT PerfArgs { + const std::string* log; + int64_t counter; + int64_t elapse_ns; + bool ready; + + PerfArgs() : log(NULL), counter(0), elapse_ns(0), ready(false) {} +}; + +void* test_log(void* void_arg) { + auto args = (PerfArgs*)void_arg; + args->ready = true; + butil::Timer t; + std::string log = *args->log; + int counter = 0; + while (!g_stopped) { + if (g_started) { + break; + } + usleep(10); + } + t.start(); + while (!g_stopped) { + { + LOG(INFO) << log; + test_logging_count.fetch_add(1, butil::memory_order_relaxed); + } + ++counter; + } + t.stop(); + args->elapse_ns = t.n_elapsed(); + args->counter = counter; + return NULL; +} + +void PerfTest(int thread_num, const std::string& log, bool async) { + FLAGS_async_log = async; + + g_started = false; + g_stopped = false; + pthread_t threads[thread_num]; + std::vector args(thread_num); + for (int i = 0; i < thread_num; ++i) { + args[i].log = &log; + ASSERT_EQ(0, pthread_create(&threads[i], NULL, test_log, &args[i])); + } + while (true) { + bool all_ready = true; + for (int i = 0; i < thread_num; ++i) { + if (!args[i].ready) { + all_ready = false; + break; + } + } + if (all_ready) { + break; + } + usleep(1000); + } + int sleep_s = 2; + g_started = true; + char prof_name[32]; + snprintf(prof_name, sizeof(prof_name), "logging_%d.prof", ++g_prof_name_counter); + ProfilerStart(prof_name); + sleep(sleep_s); + ProfilerStop(); + g_stopped = true; + int64_t wait_time = 0; + int64_t count = 0; + for (int i = 0; i < thread_num; ++i) { + pthread_join(threads[i], NULL); + wait_time += args[i].elapse_ns; + count += args[i].counter; + } + std::cout << " thread_num=" << thread_num + << " log_type=" << (async ? "async" : "sync") + << " log_size=" << log.size() + << " count=" << count + << " duration=" << sleep_s << "s" + << " qps=" << (int)(count / (double)sleep_s) + << " average_time=" << wait_time / (double)count << "us" + << std::endl; +} + +TEST_F(LoggingTest, performance) { + bool saved_async_log = FLAGS_async_log; + FLAGS_max_async_log_queue_size = + std::numeric_limits::max(); + FLAGS_async_log_in_background_always = true; + + LoggingSettings settings; + settings.logging_dest = LOG_TO_FILE; + settings.delete_old = DELETE_OLD_LOG_FILE; + InitLogging(settings); + std::string log(100, 'a'); + PerfTest(1, log, false); + PerfTest(8, log, false); + PerfTest(1, log, true); + sleep(10); + PerfTest(8, log, true); + + FLAGS_async_log = saved_async_log; +} +#endif // BRPC_ENABLE_CPU_PROFILER || BAIDU_RPC_ENABLE_CPU_PROFILER + +} // namespace + +} // namespace logging +#endif diff --git a/test/memory_unittest.cc b/test/memory_unittest.cc new file mode 100644 index 0000000..6413b82 --- /dev/null +++ b/test/memory_unittest.cc @@ -0,0 +1,433 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#define _CRT_SECURE_NO_WARNINGS + +#include "butil/process/memory.h" + +#include + +#include "butil/compiler_specific.h" +#include "butil/debug/alias.h" +#include "butil/strings/stringprintf.h" +#include + +#if defined(OS_WIN) +#include +#endif +#if defined(OS_POSIX) +#include +#endif +#if defined(OS_MACOSX) +#include +#include "butil/process/memory_unittest_mac.h" +#endif +#if defined(OS_LINUX) +#include +#endif + +#if defined(OS_WIN) +// HeapQueryInformation function pointer. +typedef BOOL (WINAPI* HeapQueryFn) \ + (HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T, PSIZE_T); + +const int kConstantInModule = 42; + +TEST(ProcessMemoryTest, GetModuleFromAddress) { + // Since the unit tests are their own EXE, this should be + // equivalent to the EXE's HINSTANCE. + // + // kConstantInModule is a constant in this file and + // therefore within the unit test EXE. + EXPECT_EQ(::GetModuleHandle(NULL), + butil::GetModuleFromAddress( + const_cast(&kConstantInModule))); + + // Any address within the kernel32 module should return + // kernel32's HMODULE. Our only assumption here is that + // kernel32 is larger than 4 bytes. + HMODULE kernel32 = ::GetModuleHandle(L"kernel32.dll"); + HMODULE kernel32_from_address = + butil::GetModuleFromAddress(reinterpret_cast(kernel32) + 1); + EXPECT_EQ(kernel32, kernel32_from_address); +} + +TEST(ProcessMemoryTest, EnableLFH) { + ASSERT_TRUE(butil::EnableLowFragmentationHeap()); + if (IsDebuggerPresent()) { + // Under these conditions, LFH can't be enabled. There's no point to test + // anything. + const char* no_debug_env = getenv("_NO_DEBUG_HEAP"); + if (!no_debug_env || strcmp(no_debug_env, "1")) + return; + } + HMODULE kernel32 = GetModuleHandle(L"kernel32.dll"); + ASSERT_TRUE(kernel32 != NULL); + HeapQueryFn heap_query = reinterpret_cast(GetProcAddress( + kernel32, + "HeapQueryInformation")); + + // On Windows 2000, the function is not exported. This is not a reason to + // fail but we won't be able to retrieves information about the heap, so we + // should stop here. + if (heap_query == NULL) + return; + + HANDLE heaps[1024] = { 0 }; + unsigned number_heaps = GetProcessHeaps(1024, heaps); + EXPECT_GT(number_heaps, 0u); + for (unsigned i = 0; i < number_heaps; ++i) { + ULONG flag = 0; + SIZE_T length; + ASSERT_NE(0, heap_query(heaps[i], + HeapCompatibilityInformation, + &flag, + sizeof(flag), + &length)); + // If flag is 0, the heap is a standard heap that does not support + // look-asides. If flag is 1, the heap supports look-asides. If flag is 2, + // the heap is a low-fragmentation heap (LFH). Note that look-asides are not + // supported on the LFH. + + // We don't have any documented way of querying the HEAP_NO_SERIALIZE flag. + EXPECT_LE(flag, 2u); + EXPECT_NE(flag, 1u); + } +} +#endif // defined(OS_WIN) + +#if defined(OS_MACOSX) + +// For the following Mac tests: +// Note that butil::EnableTerminationOnHeapCorruption() is called as part of +// test suite setup and does not need to be done again, else mach_override +// will fail. + +#if !defined(ADDRESS_SANITIZER) +// The following code tests the system implementation of malloc() thus no need +// to test it under AddressSanitizer. +TEST(ProcessMemoryTest, MacMallocFailureDoesNotTerminate) { + // Test that ENOMEM doesn't crash via CrMallocErrorBreak two ways: the exit + // code and lack of the error string. The number of bytes is one less than + // MALLOC_ABSOLUTE_MAX_SIZE, more than which the system early-returns NULL and + // does not call through malloc_error_break(). See the comment at + // EnableTerminationOnOutOfMemory() for more information. + void* buf = NULL; + ASSERT_EXIT( + { + butil::EnableTerminationOnOutOfMemory(); + + buf = malloc(std::numeric_limits::max() - (2 * PAGE_SIZE) - 1); + }, + testing::KilledBySignal(SIGTRAP), + "\\*\\*\\* error: can't allocate region.*\\n?.*"); + + butil::debug::Alias(buf); +} +#endif // !defined(ADDRESS_SANITIZER) + +TEST(ProcessMemoryTest, MacTerminateOnHeapCorruption) { + // Assert that freeing an unallocated pointer will crash the process. + char buf[9]; + asm("" : "=r" (buf)); // Prevent clang from being too smart. +#if ARCH_CPU_64_BITS + // On 64 bit Macs, the malloc system automatically abort()s on heap corruption + // but does not output anything. + ASSERT_DEATH(free(buf), ""); +#elif defined(ADDRESS_SANITIZER) + // AddressSanitizer replaces malloc() and prints a different error message on + // heap corruption. + ASSERT_DEATH(free(buf), "attempting free on address which " + "was not malloc\\(\\)-ed"); +#else + ASSERT_DEATH(free(buf), "being freed.*\\n?\\.*" + "\\*\\*\\* set a breakpoint in malloc_error_break to debug.*\\n?.*" + "Terminating process due to a potential for future heap corruption"); +#endif // ARCH_CPU_64_BITS || defined(ADDRESS_SANITIZER) +} + +#endif // defined(OS_MACOSX) + +// Android doesn't implement set_new_handler, so we can't use the +// OutOfMemoryTest cases. +// OpenBSD does not support these tests either. +// TODO(vandebo) make this work on Windows too. +#if !defined(OS_ANDROID) && !defined(OS_OPENBSD) && \ + !defined(OS_WIN) + +#if defined(USE_TCMALLOC) +extern "C" { +int tc_set_new_mode(int mode); +} +#endif // defined(USE_TCMALLOC) + +class OutOfMemoryTest : public testing::Test { + public: + OutOfMemoryTest() + : value_(NULL), + // Make test size as large as possible minus a few pages so + // that alignment or other rounding doesn't make it wrap. + test_size_(std::numeric_limits::max() - 12 * 1024), + signed_test_size_(std::numeric_limits::max()) { + } + +#if defined(USE_TCMALLOC) + virtual void SetUp() OVERRIDE { + tc_set_new_mode(1); + } + + virtual void TearDown() OVERRIDE { + tc_set_new_mode(0); + } +#endif // defined(USE_TCMALLOC) + + protected: + void* value_; + size_t test_size_; + ssize_t signed_test_size_; +}; + +class OutOfMemoryDeathTest : public OutOfMemoryTest { + public: + void SetUpInDeathAssert() { + // Must call EnableTerminationOnOutOfMemory() because that is called from + // chrome's main function and therefore hasn't been called yet. + // Since this call may result in another thread being created and death + // tests shouldn't be started in a multithread environment, this call + // should be done inside of the ASSERT_DEATH. + butil::EnableTerminationOnOutOfMemory(); + } +}; + +TEST_F(OutOfMemoryDeathTest, New) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = operator new(test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, NewArray) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = new char[test_size_]; + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, Malloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc(test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, Realloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = realloc(NULL, test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, Calloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = calloc(1024, test_size_ / 1024L); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, Valloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = valloc(test_size_); + }, ""); +} + +#if defined(OS_LINUX) + +// pvalloc does not crash in gcc 3.4 +#if PVALLOC_AVAILABLE == 1 && (!defined(__GNUC__) || __GNUC__ >= 4) +TEST_F(OutOfMemoryDeathTest, Pvalloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = pvalloc(test_size_); + }, ""); +} +#endif // PVALLOC_AVAILABLE == 1 + +TEST_F(OutOfMemoryDeathTest, Memalign) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = memalign(4, test_size_); + }, ""); +} + +// FIXME(gejun): This case fails right now, maybe related to not linking tcmalloc +/* +TEST_F(OutOfMemoryDeathTest, ViaSharedLibraries) { + // This tests that the run-time symbol resolution is overriding malloc for + // shared libraries (including libc itself) as well as for our code. + std::string format = butil::StringPrintf("%%%zud", test_size_); + char *value = NULL; + ASSERT_DEATH({ + SetUpInDeathAssert(); + EXPECT_EQ(-1, asprintf(&value, format.c_str(), 0)); + }, ""); +} +*/ + +#endif // OS_LINUX + +// Android doesn't implement posix_memalign(). +#if defined(OS_POSIX) && !defined(OS_ANDROID) +TEST_F(OutOfMemoryDeathTest, Posix_memalign) { + // Grab the return value of posix_memalign to silence a compiler warning + // about unused return values. We don't actually care about the return + // value, since we're asserting death. + ASSERT_DEATH({ + SetUpInDeathAssert(); + EXPECT_EQ(ENOMEM, posix_memalign(&value_, 8, test_size_)); + }, ""); +} +#endif // defined(OS_POSIX) && !defined(OS_ANDROID) + +#if defined(OS_MACOSX) + +// Purgeable zone tests + +TEST_F(OutOfMemoryDeathTest, MallocPurgeable) { + malloc_zone_t* zone = malloc_default_purgeable_zone(); + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc_zone_malloc(zone, test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, ReallocPurgeable) { + malloc_zone_t* zone = malloc_default_purgeable_zone(); + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc_zone_realloc(zone, NULL, test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, CallocPurgeable) { + malloc_zone_t* zone = malloc_default_purgeable_zone(); + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc_zone_calloc(zone, 1024, test_size_ / 1024L); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, VallocPurgeable) { + malloc_zone_t* zone = malloc_default_purgeable_zone(); + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc_zone_valloc(zone, test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, PosixMemalignPurgeable) { + malloc_zone_t* zone = malloc_default_purgeable_zone(); + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc_zone_memalign(zone, 8, test_size_); + }, ""); +} + +// Since these allocation functions take a signed size, it's possible that +// calling them just once won't be enough to exhaust memory. In the 32-bit +// environment, it's likely that these allocation attempts will fail because +// not enough contiguous address space is available. In the 64-bit environment, +// it's likely that they'll fail because they would require a preposterous +// amount of (virtual) memory. + +TEST_F(OutOfMemoryDeathTest, CFAllocatorSystemDefault) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + while ((value_ = + butil::AllocateViaCFAllocatorSystemDefault(signed_test_size_))) {} + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, CFAllocatorMalloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + while ((value_ = + butil::AllocateViaCFAllocatorMalloc(signed_test_size_))) {} + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, CFAllocatorMallocZone) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + while ((value_ = + butil::AllocateViaCFAllocatorMallocZone(signed_test_size_))) {} + }, ""); +} + +#if !defined(ARCH_CPU_64_BITS) + +// See process_util_unittest_mac.mm for an explanation of why this test isn't +// run in the 64-bit environment. + +TEST_F(OutOfMemoryDeathTest, PsychoticallyBigObjCObject) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + while ((value_ = butil::AllocatePsychoticallyBigObjCObject())) {} + }, ""); +} + +#endif // !ARCH_CPU_64_BITS +#endif // OS_MACOSX + +class OutOfMemoryHandledTest : public OutOfMemoryTest { + public: + static const size_t kSafeMallocSize = 512; + static const size_t kSafeCallocSize = 128; + static const size_t kSafeCallocItems = 4; + + virtual void SetUp() { + OutOfMemoryTest::SetUp(); + + // We enable termination on OOM - just as Chrome does at early + // initialization - and test that UncheckedMalloc and UncheckedCalloc + // properly by-pass this in order to allow the caller to handle OOM. + butil::EnableTerminationOnOutOfMemory(); + } +}; + +// TODO(b.kelemen): make UncheckedMalloc and UncheckedCalloc work +// on Windows as well. +// UncheckedMalloc() and UncheckedCalloc() work as regular malloc()/calloc() +// under sanitizer tools. +#if !defined(MEMORY_TOOL_REPLACES_ALLOCATOR) +TEST_F(OutOfMemoryHandledTest, UncheckedMalloc) { + EXPECT_TRUE(butil::UncheckedMalloc(kSafeMallocSize, &value_)); + EXPECT_TRUE(value_ != NULL); + free(value_); + + EXPECT_FALSE(butil::UncheckedMalloc(test_size_, &value_)); + EXPECT_TRUE(value_ == NULL); +} + +TEST_F(OutOfMemoryHandledTest, UncheckedCalloc) { + EXPECT_TRUE(butil::UncheckedCalloc(1, kSafeMallocSize, &value_)); + EXPECT_TRUE(value_ != NULL); + const char* bytes = static_cast(value_); + for (size_t i = 0; i < kSafeMallocSize; ++i) + EXPECT_EQ(0, bytes[i]); + free(value_); + + EXPECT_TRUE( + butil::UncheckedCalloc(kSafeCallocItems, kSafeCallocSize, &value_)); + EXPECT_TRUE(value_ != NULL); + bytes = static_cast(value_); + for (size_t i = 0; i < (kSafeCallocItems * kSafeCallocSize); ++i) + EXPECT_EQ(0, bytes[i]); + free(value_); + + EXPECT_FALSE(butil::UncheckedCalloc(1, test_size_, &value_)); + EXPECT_TRUE(value_ == NULL); +} +#endif // !defined(MEMORY_TOOL_REPLACES_ALLOCATOR) +#endif // !defined(OS_ANDROID) && !defined(OS_OPENBSD) && !defined(OS_WIN) diff --git a/test/memory_unittest_mac.h b/test/memory_unittest_mac.h new file mode 100644 index 0000000..94c372c --- /dev/null +++ b/test/memory_unittest_mac.h @@ -0,0 +1,32 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains helpers for the process_util_unittest to allow it to fully +// test the Mac code. + +#ifndef BUTIL_PROCESS_MEMORY_UNITTEST_MAC_H_ +#define BUTIL_PROCESS_MEMORY_UNITTEST_MAC_H_ + +#include "butil/basictypes.h" + +namespace butil { + +// Allocates memory via system allocators. Alas, they take a _signed_ size for +// allocation. +void* AllocateViaCFAllocatorSystemDefault(ssize_t size); +void* AllocateViaCFAllocatorMalloc(ssize_t size); +void* AllocateViaCFAllocatorMallocZone(ssize_t size); + +#if !defined(ARCH_CPU_64_BITS) +// See process_util_unittest_mac.mm for an explanation of why this function +// isn't implemented for the 64-bit environment. + +// Allocates a huge Objective C object. +void* AllocatePsychoticallyBigObjCObject(); + +#endif // !ARCH_CPU_64_BITS + +} // namespace butil + +#endif // BUTIL_PROCESS_MEMORY_UNITTEST_MAC_H_ diff --git a/test/message.proto b/test/message.proto new file mode 100644 index 0000000..3e33c17 --- /dev/null +++ b/test/message.proto @@ -0,0 +1,371 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package gss.message; + +//------------------------------------gss_src_req_t--------------------------------- +//struct gss_src_req_t +//{ +// string TransQuery[] = optional(); /**< transquery from da tranfered by us */ +// int32_t ExtType[] = optional(); /**< 扩展query类型 */ +// int32_t SrcID = optional(); /**< table id */ +// int32_t SrcId = optional(); /**< table id */ +// int32_t Pos = optional(); /**< pos */ +// int32_t Place = optional(); /**< place */ +// int32_t Degree = optional(); /**< degree */ +// string Key = optional(),default(""); /**< us建议key */ +// string ReqKey = optional(),default(""); /**< us建议 ReqKey */ +// int32_t QueryType = optional(),default(0); /**< 类型,default=0 */ +// binary HighLight = optional(),default(""); /**< da飘红词 */ +// string RetFormat = optional(),default("html"); /**< 回传格式 */ +// string TagFilter = optional(),default(""); /**< Tag过滤信息 */ +// //SPEC REQ +// int32_t SpReqType = optional(),default(0); /**< 特殊请求类型,正常为0 */ +// //FRO ZHIXIN USE +// string UriKey = optional(); /**< UriKey 知心卡片使用 */ +// string EntityName = optional(); /**< 实体名 知心卡片使用 */ +//}; + +message gss_src_req_t { + repeated string TransQuery = 1; /**< transquery from da tranfered by us */ + repeated int32 ExtType = 2; /**< 扩展query类型 */ + optional int32 SrcID = 3; /**< table id */ + //optional int32 SrcId = 4; /**< table id */ + optional int32 Pos = 5; /**< pos */ + optional int32 Place = 6; /**< place */ + optional int32 Degree = 7; /**< degree */ + optional string Key = 8; /**< us建议key */ + optional string ReqKey = 9; /**< us建议 ReqKey */ + optional int32 QueryType = 10; /**< 类型,default=0 */ + optional bytes HighLight = 11; /**< da飘红词 */ + optional string RetFormat = 12 [default ="html"]; /**< 回传格式 */ + optional string TagFilter = 13; /**< Tag过滤信息 */ + //SPEC REQ + optional int32 SpReqType = 14; /**< 特殊请求类型,正常为0 */ + //FRO ZHIXIN USE + optional string UriKey = 15; /**< UriKey 知心卡片使用 */ + optional string EntityName = 16; /**< 实体名 知心卡片使用 */ +}; +//------------------------------------终端 ua 信息:us_gss_req_t--------------------------------- +//struct ua_info_t +//{ +// string ua_os = optional(); /**< 操作系统 */ +// string ua_browser = optional(); /**< 浏览器 */ +// string ua_modal = optional(); /**< 机型 */ +// string ua_measure = optional(); /**< 尺寸 */ +// int32_t ua_res_x = optional(); /**< 分辨率宽 */ +// int32_t ua_res_y = optional(); /**< 分辨率高 */ +// binary ua_ext = optional(); /**< 扩展(预计会放操作系统版本os_version、浏览器版本browser_version)*/ +//}; + +message ua_info_t { + optional string ua_os = 1; /**< 操作系统 */ + optional string ua_browser = 2; /**< 浏览器 */ + optional string ua_modal = 3; /**< 机型 */ + optional string ua_measure = 4; /**< 尺寸 */ + optional int32 ua_res_x = 5; /**< 分辨率宽 */ + optional int32 ua_res_y = 6; /**< 分辨率高 */ + optional bytes ua_ext = 7; /**< 扩展(预计会放操作系统版本os_version、浏览器版本browser_version)*/ +}; + +//------------------------------------app_info_t------------------------------------------------ +//struct app_info_t +//{ +// string package; /*应用的packagename*/ +// int32_t version; /*应用的version code*/ +// uint32_t signmd5; /*包体签名*/ +//}; + +message app_info_t { + required string package = 1; /*应用的packagename*/ + required int32 version = 2; /*应用的version code*/ + required uint32 signmd5 = 3; /*包体签名*/ +} + +//------------------------------------us请求gss结构体:us_gss_req_t--------------------------------- +//struct us_gss_req_t +//{ +// string OriginQuery; /**< 原始query */ +// int32_t UserIP; /**< 用户IP */ +// int32_t TimingNeed; /**< 时效性查询 */ +// uint64_t QueryID64 = optional(),default(0); /**< QueryID */ +// string ClientName = optional(),default("unknow"); /**< 调用方名称 */ +// int32_t ResNum = range(0,2000),default(20),optional(); /**< 翻页参数,当页结果数 */ +// int32_t PageNum = range(0,2000),default(0),optional(); /**< 翻页参数,结果偏移量 */ +// int32_t ctpl_or_php = optional(),default(0),range(0,1); /**< 是否smarty渲染 */ +// int32_t SeType = optional(),default(0); /**< 请求类型 0:US, 1:US_MID, 2:UI */ +// //int32_t KeepAlive; /**< 保持连接 */ +// string TemplateName; /**< 模版名 ex. baidu wisexmlnew */ +// int32_t sid[] = optional(); /**< 抽样id */ +// binary UrlParaPack = optional(); /**< Uri参数包 */ +// binary gssqa = optional(); /**< 经us透传的DA分析结果 */ +// string Cookie = optional(),default(""); /**< 用户cookie */ +// string province_name = optional(); /**< 省份信息 */ +// string city_name = optional(); /**< 城市信息 */ +// string isp_name = optional(); /**< 运营商信息 */ +// uint32_t SrcNum; /**< 请求table数量 */ +// string From = optional(),default("www"); /**< 请求来源 */ +// string Fmt = optional(),default("html"); /**< 请求格式 */ +// binary HighLight = optional(),default(""); /**< da飘红词 */ +// int32_t NeedHilightStr = optional(),default(0); /**< 是否回传飘红词 */ +// gss_src_req_t SrcArr[] = optional(); /**< table请求信息 */ +// //国际化新增参数 +// int64_t resultLang = optional(); /**< 用户需要的结果语言设置 */ +// int64_t resultLocale = optional(); /**< 用户需要的结果地域 */ +// //终端app 信息 +// app_info_t AppInfoArr[] = optional(); /**< 用户安装的app信息*/ +// //终端ua 信息 +// ua_info_t uaInfo = optional(); /**< 请求的终端信息 */ +// string useragent_full = optional(); /**< 请求的终端全部信息 */ +// //百度账号信息 +// int32_t uid = optional(); +// string uname = optional(); +// int32_t open_gssda_recall = optional(); +// int32_t prefetch_flag = optional(); /**< 针对wise-us。值为1表示预取请求,值为0表示正常检索请求 */ +//}; + +// pb for us_gss_req_t: +message us_gss_req_t { + required string OriginQuery = 1; /**< 原始query */ + required int32 UserIP = 2; /**< 用户IP */ + required int32 TimingNeed = 3; /**< 时效性查询 */ + optional uint64 QueryID64 = 4 [default = 0]; /**< QueryID */ + optional string ClientName = 5 [default = "unknow"]; /**< 调用方名称 */ + + //reserved 6 - 10 + + optional int32 ResNum = 11 [default = 20]; /**< 翻页参数,当页结果数 */ + optional int32 PageNum = 12 [default = 0]; /**< 翻页参数,结果偏移量 */ + optional int32 ctpl_or_php = 13 [default = 0]; /**< 是否smarty渲染 */ + optional int32 SeType = 14 [default = 0]; /**< 请求类型 0:US, 1:US_MID, 2:UI */ + //int32_t KeepAlive; /**< 保持连接 */ + required string TemplateName = 15; /**< 模版名 ex. baidu wisexmlnew */ + repeated int32 sid = 16; /**< 抽样id */ + optional bytes UrlParaPack = 17; /**< Uri参数包*/ + optional bytes gssqa = 18; /**< 经us透传的DA分析结果 */ + optional string Cookie = 19; /**< 用户cookie */ + + //reserved 20 - 30 + + optional string province_name = 31; /**< 省份信息 */ + optional string city_name = 32; /**< 城市信息 */ + optional string isp_name = 33; /**< 运营商信息 */ + required uint32 SrcNum = 34; /**< 请求table数量 */ + optional string From = 35 [default = "www"]; /**< 请求来源 */ + optional string Fmt = 36 [default = "html"]; /**< 请求格式 */ + optional bytes HighLight = 37; /**< da飘红词 */ + optional int32 NeedHilightStr = 38 [default = 0]; /**< 是否回传飘红词 */ + repeated gss_src_req_t SrcArr = 39; /**< table请求信息 */ + //国际化新增参数 + optional int64 resultLang = 40; /**< 用户需要的结果语言设置 */ + optional int64 resultLocale = 41; /**< 用户需要的结果地域 */ + //终端app 信息 + repeated app_info_t AppInfoArr = 42; /**< 用户安装的app信息*/ + //终端ua 信息 + optional ua_info_t uaInfo = 43; /**< 请求的终端信息 */ + optional string useragent_full = 44; /**< 请求的终端全部信息 */ + //百度账号信息 + optional int32 uid = 45; + optional string uname = 46; + optional int32 open_gssda_recall = 47; + optional int32 prefetch_flag = 48; /**< 针对wise-us。值为1表示预取请求,值为0表示正常检索请求*/ + + //reserved 50 - 70 + + //added by new gss: + //for service distribution: + optional string service_name = 71; + //for normalized caller: + optional string caller = 72; + //for parsing UrlParaPack: + optional UserAgent user_agent = 73; + +}; + +/** uri相关请求参数, gss代码中定义的结构体: */ +//struct uri_req_t { +// unsigned int uri_len; /**< uri长度 */ +// char uri[US2GSS_MAX_URI_LEN]; /**< uribuffer */ +// u_int uri_sign[2]; /**< 对uri的签名 */ +// char dsp[GSS_GEN_STR_LEN]; /**< dsp参数 */ +// npoint_t crd; /**< 用户百度地图墨卡托坐标 for lbs */ +// char os[GSS_GEN_STR_LEN]; /**< os操作系统参数 */ +// char osv[GSS_GEN_STR_LEN]; /**< osv操作系统版本参数 */ +// char mb[GSS_GEN_STR_LEN]; /**< mb浏览器参数 */ +// char mbv[GSS_GEN_STR_LEN]; /**< mbv浏览器版本参数*/ +// int apn; /**< opendata api的co参数中传递的翻页信息类似大搜索的pn */ +// int arn; /**< opendata api的co参数中传递的翻页信息类似大搜索的rn */ +// char cuid[GSS_GEN_STR_LEN]; /**< 移动用户唯一标识*/ +// char net_type[GSS_GEN_STR_LEN]; /**< 移动网络类型,0=>unknown,1=>wifi, 2=>2G, 3=>3G*/ +// int ignore_caller_auth; /**< 忽略调用方认证 */ +//}; + +// 对应uri_req_t中的部分信息: +message UserAgent { + optional string dsp = 1; /**< dsp参数 */ + optional string os = 2; /**< os操作系统参数 */ + optional string osv = 3; /**< osv操作系统版本参数 */ + optional string mb = 4; /**< mb浏览器参数 */ + optional string mbv = 5; /**< mbv浏览器版本参数*/ + optional int32 apn = 6; /**< opendata api的co参数中传递的翻页信息类似大搜索的pn */ + optional int32 arn = 7; /**< opendata api的co参数中传递的翻页信息类似大搜索的rn */ + optional string cuid = 8; /**< 移动用户唯一标识*/ + optional string net_type = 9; /**< 移动网络类型,0=>unknown,1=>wifi, 2=>2G, 3=>3G*/ +}; + +//------------------------------------gss给us子链接结构体: sub_url_t--------------------------------- +//struct sub_url_t +//{ +// string SubURL; /**< sub url */ +// string SubURI = optional(); /**< sub uri */ +// string SubName = optional(); +// string SubPath = optional(); +// int32_t SiteId = optional(); /**< 子链接site id */ +// string SubEx = optional(); /**< extend */ +//}; + +message sub_url_t { + required string SubURL = 1; /**< sub url */ + optional string SubURI = 2; /**< sub uri */ + optional string SubName = 3; + optional string SubPath = 4; + optional int32 SiteId = 5; /**< 子链接site id */ + optional string SubEx = 6; /**< extend */ +}; + +//------------------------------------gss单个结果结构体:gss_res_t--------------------------------- +//struct gss_res_t +//{ +// string ResultURL; /**< 结果URL */ +// string Display = optional(),default(""); /**< 展现结果 */ +// int32_t Weight; /**< 权重 */ +// int32_t Sort = optional(); /**< 插入位置 */ +// int32_t SrcID = optional(); /**< id */ +// int32_t TimingNeed; /**< 时效性 */ +// uint32_t WiseStrategyFlag = optional(); /**< wise策略bitmap */ +// int32_t Degree = optional(); /**< 需求强度 */ +// int32_t ClickNeed = optional(); /**< 点击调权 */ +// int32_t StrategyInfo = optional(); /**< 策略附加信息 */ +// //SPEC REQ +// int32_t SpReqType = optional(); /**< 特殊请求类型,正常为0 */ +// //FRO ZHIXIN USE +// string UriKey = optional(); /**< UriKey 知心卡片使用 */ +// string EntityName = optional(); /**< 实体名 知心卡片使用 */ +// sub_url_t SubResult[50]; /**< 50条子链接 */ +// int32_t SubResNum = optional(),default(0); /**< 子链接 */ +// string DisplayLog = optional(); /**< 展现日志 */ +// binary DisplayData = optional(); /**< 特型展现数据 */ +// uint32_t ResType = optional(); /**< 资源是否带图*/ +// string Title = optional(); /**< 右侧去重名称*/ +// int32_t RecoverCacheTime = optional(); /**< us容灾cache,过滤不进人容灾的资源*/ +//}; + +message gss_res_t { + // pb for gss_res_t: + required string ResultURL = 2; /**< 结果URL */ + optional string Display = 3; /**< 展现结果 */ + required int32 Weight = 4; /**< 权重 */ + optional int32 Sort = 5; /**< 插入位置 */ + optional int32 SrcID = 6; /**< id */ + required int32 TimingNeed = 7; /**< 时效性 */ + optional uint32 WiseStrategyFlag = 8; /**< wise策略bitmap */ + optional int32 Degree = 9; /**< 需求强度 */ + optional int32 ClickNeed = 10; /**< 点击调权 */ + optional int32 StrategyInfo = 11; /**< 策略附加信息 */ + //SPEC REQ + optional int32 SpReqType = 12; /**< 特殊请求类型,正常为0 */ + //FRO ZHIXIN USE + optional string UriKey = 13; /**< UriKey 知心卡片使用 */ + optional string EntityName = 14; /**< 实体名 知心卡片使用 */ + repeated sub_url_t SubResult = 15; /**< 50条子链接 */ + optional int32 SubResNum = 16; /**< 子链接 */ + optional string DisplayLog = 17; /**< 展现日志 */ + optional bytes DisplayData = 18; /**< 特型展现数据 */ + optional uint32 ResType = 19; /**< 资源是否带图*/ + optional string Title = 20; /**< 右侧去重名称*/ + optional int32 RecoverCacheTime = 21; /**< us容灾cache,过滤不进人容灾的资源*/ + + //customized result messages: +}; + +//------------------------------------debug_info_t--------------------------------- +//struct item_t +//{ +// string title; +// string content; +// int32_t parent; +//}; +//struct strategy_bits_t +//{ +// uint32_t bits[STRATEGY_INT_NUM]; +//}; +//struct debug_info_t +//{ +// ///废弃, 使用idebug代替 +// item_t debug_info[] = optional(); +// string item_info[] = optional(); +// string anchor_info[] = optional(); +// ///通过ivar 发送的全部debug信息, 取代原debug_info +// binary idebug = optional(); +//}; + +message item_t { + required string title = 1; + required string content = 2; + required int32 parent = 3; +}; +message debug_info_t { + optional bytes idebug = 1; + repeated string item_info = 2; + repeated string anchor_info = 3; + ///废弃, 使用idebug代替 + repeated item_t debug_info = 4 [deprecated=true]; +}; + +//------------------------------------gss_us_res_t--------------------------------- +// gss返回给us结构体 +//struct gss_us_res_t +//{ +// int32_t ResultCode; /**< 返回状态码 */ +// uint64_t QueryID; /**< query id */ +// //集群合并后,加大最大返回结果包大小 +// gss_res_t Result[40]; /**< 返回结果 */ +// uint32_t ResultNum = range(0,40); /**< 返回结果数 */ +// debug_info_t info = optional(); /**< debug info */ +// int32_t bfe_cached_time = optional(); /**< cache失效时间 */ +// int32_t bfe_cached_islocate = optional(); /**< 是否有地域扩展 */ +// binary disp_data_url_ex = optional(); /**< 展现日志中url级别信息 */ +// binary disp_data_query_ex = optional(); /**< 展现日志中query级别信息 */ +//}; + + +// pb for gss_us_res_t: +message gss_us_res_t { + required int32 ResultCode = 1; /**< 返回状态码 */ + required uint64 QueryID = 2; /**< query id */ + //集群合并后,加大最大返回结果包大小 + repeated gss_res_t Result = 3; /**< 返回结果 */ + required uint32 ResultNum = 4; /**< 返回结果数 */ + optional debug_info_t info = 5; /**< debug info */ + optional int32 bfe_cached_time = 6; /**< cache失效时间 */ + optional int32 bfe_cached_islocate = 7; /**< 是否有地域扩展 */ + optional bytes disp_data_url_ex = 8; /**< 展现日志中url级别信息 */ + optional bytes disp_data_query_ex = 9; /**< 展现日志中query级别信息 */ + + optional string name = 20; /* response name*/ +}; diff --git a/test/mpsc_queue_unittest.cc b/test/mpsc_queue_unittest.cc new file mode 100644 index 0000000..c651e3a --- /dev/null +++ b/test/mpsc_queue_unittest.cc @@ -0,0 +1,141 @@ +#include +#include +#include "butil/containers/mpsc_queue.h" + +namespace { + +const uint MAX_COUNT = 1000000; + +void Consume(butil::MPSCQueue& q, bool allow_empty) { + uint i = 0; + uint empty_count = 0; + while (true) { + uint d; + if (!q.Dequeue(d)) { + ASSERT_TRUE(allow_empty); + ASSERT_LT(empty_count++, (const uint)10000); + ::usleep(10 * 1000); + continue; + } + ASSERT_EQ(i++, d); + if (i == MAX_COUNT) { + break; + } + } +} + +void* ProduceThread(void* arg) { + auto q = (butil::MPSCQueue*)arg; + for (uint i = 0; i < MAX_COUNT; ++i) { + q->Enqueue(i); + } + return NULL; +} + +void* ConsumeThread1(void* arg) { + auto q = (butil::MPSCQueue*)arg; + Consume(*q, true); + return NULL; +} + +TEST(MPSCQueueTest, spsc_single_thread) { + butil::MPSCQueue q; + for (uint i = 0; i < MAX_COUNT; ++i) { + q.Enqueue(i); + } + Consume(q, false); +} + +TEST(MPSCQueueTest, spsc_multi_thread) { + butil::MPSCQueue q; + pthread_t produce_tid; + ASSERT_EQ(0, pthread_create(&produce_tid, NULL, ProduceThread, &q)); + pthread_t consume_tid; + ASSERT_EQ(0, pthread_create(&consume_tid, NULL, ConsumeThread1, &q)); + + pthread_join(produce_tid, NULL); + pthread_join(consume_tid, NULL); + +} + +butil::atomic g_index(0); +void* MultiProduceThread(void* arg) { + auto q = (butil::MPSCQueue*)arg; + while (true) { + uint i = g_index.fetch_add(1, butil::memory_order_relaxed); + if (i >= MAX_COUNT) { + break; + } + q->Enqueue(i); + } + return NULL; +} + +butil::Mutex g_mutex; +bool g_counts[MAX_COUNT]; +void Consume2(butil::MPSCQueue& q) { + uint empty_count = 0; + uint count = 0; + while (true) { + uint d; + if (!q.Dequeue(d)) { + ASSERT_LT(empty_count++, (const uint)10000); + ::usleep(1 * 1000); + continue; + } + ASSERT_LT(d, MAX_COUNT); + { + BAIDU_SCOPED_LOCK(g_mutex); + ASSERT_FALSE(g_counts[d]); + g_counts[d] = true; + } + if (++count >= MAX_COUNT) { + break; + } + } +} + +void* ConsumeThread2(void* arg) { + auto q = (butil::MPSCQueue*)arg; + Consume2(*q); + return NULL; +} + +TEST(MPSCQueueTest, mpsc_multi_thread) { + butil::MPSCQueue q; + + int thread_num = 8; + pthread_t threads[thread_num]; + for (int i = 0; i < thread_num; ++i) { + ASSERT_EQ(0, pthread_create(&threads[i], NULL, MultiProduceThread, &q)); + } + + pthread_t consume_tid; + ASSERT_EQ(0, pthread_create(&consume_tid, NULL, ConsumeThread2, &q)); + + for (int i = 0; i < thread_num; ++i) { + pthread_join(threads[i], NULL); + } + pthread_join(consume_tid, NULL); + +} + +struct MyObject {}; + +TEST(MPSCQueueTest, mpsc_test_allocator) { + butil::ObjectPoolAllocator alloc; + + auto p = alloc.Alloc(); + butil::ObjectPoolInfo info = butil::describe_objects>(); + ASSERT_EQ(1, info.item_num); + + alloc.Free(p); + info = butil::describe_objects>(); + ASSERT_EQ(1, info.item_num); + + p = alloc.Alloc(); + info = butil::describe_objects>(); + ASSERT_EQ(1, info.item_num); +} + +} diff --git a/test/mru_cache_unittest.cc b/test/mru_cache_unittest.cc new file mode 100644 index 0000000..13c9c5d --- /dev/null +++ b/test/mru_cache_unittest.cc @@ -0,0 +1,271 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/basictypes.h" +#include "butil/containers/mru_cache.h" +#include + +namespace { + +int cached_item_live_count = 0; + +struct CachedItem { + CachedItem() : value(0) { + cached_item_live_count++; + } + + explicit CachedItem(int new_value) : value(new_value) { + cached_item_live_count++; + } + + explicit CachedItem(const CachedItem& other) : value(other.value) { + cached_item_live_count++; + } + + ~CachedItem() { + cached_item_live_count--; + } + + int value; +}; + +} // namespace + +TEST(MRUCacheTest, Basic) { + typedef butil::MRUCache Cache; + Cache cache(Cache::NO_AUTO_EVICT); + + // Check failure conditions + { + CachedItem test_item; + EXPECT_TRUE(cache.Get(0) == cache.end()); + EXPECT_TRUE(cache.Peek(0) == cache.end()); + } + + static const int kItem1Key = 5; + CachedItem item1(10); + Cache::iterator inserted_item = cache.Put(kItem1Key, item1); + EXPECT_EQ(1U, cache.size()); + + // Check that item1 was properly inserted. + { + Cache::iterator found = cache.Get(kItem1Key); + EXPECT_TRUE(inserted_item == cache.begin()); + EXPECT_TRUE(found != cache.end()); + + found = cache.Peek(kItem1Key); + EXPECT_TRUE(found != cache.end()); + + EXPECT_EQ(kItem1Key, found->first); + EXPECT_EQ(item1.value, found->second.value); + } + + static const int kItem2Key = 7; + CachedItem item2(12); + cache.Put(kItem2Key, item2); + EXPECT_EQ(2U, cache.size()); + + // Check that item1 is the oldest since item2 was added afterwards. + { + Cache::reverse_iterator oldest = cache.rbegin(); + ASSERT_TRUE(oldest != cache.rend()); + EXPECT_EQ(kItem1Key, oldest->first); + EXPECT_EQ(item1.value, oldest->second.value); + } + + // Check that item1 is still accessible by key. + { + Cache::iterator test_item = cache.Get(kItem1Key); + ASSERT_TRUE(test_item != cache.end()); + EXPECT_EQ(kItem1Key, test_item->first); + EXPECT_EQ(item1.value, test_item->second.value); + } + + // Check that retrieving item1 pushed item2 to oldest. + { + Cache::reverse_iterator oldest = cache.rbegin(); + ASSERT_TRUE(oldest != cache.rend()); + EXPECT_EQ(kItem2Key, oldest->first); + EXPECT_EQ(item2.value, oldest->second.value); + } + + // Remove the oldest item and check that item1 is now the only member. + { + Cache::reverse_iterator next = cache.Erase(cache.rbegin()); + + EXPECT_EQ(1U, cache.size()); + + EXPECT_TRUE(next == cache.rbegin()); + EXPECT_EQ(kItem1Key, next->first); + EXPECT_EQ(item1.value, next->second.value); + + cache.Erase(cache.begin()); + EXPECT_EQ(0U, cache.size()); + } + + // Check that Clear() works properly. + cache.Put(kItem1Key, item1); + cache.Put(kItem2Key, item2); + EXPECT_EQ(2U, cache.size()); + cache.Clear(); + EXPECT_EQ(0U, cache.size()); +} + +TEST(MRUCacheTest, GetVsPeek) { + typedef butil::MRUCache Cache; + Cache cache(Cache::NO_AUTO_EVICT); + + static const int kItem1Key = 1; + CachedItem item1(10); + cache.Put(kItem1Key, item1); + + static const int kItem2Key = 2; + CachedItem item2(20); + cache.Put(kItem2Key, item2); + + // This should do nothing since the size is bigger than the number of items. + cache.ShrinkToSize(100); + + // Check that item1 starts out as oldest + { + Cache::reverse_iterator iter = cache.rbegin(); + ASSERT_TRUE(iter != cache.rend()); + EXPECT_EQ(kItem1Key, iter->first); + EXPECT_EQ(item1.value, iter->second.value); + } + + // Check that Peek doesn't change ordering + { + Cache::iterator peekiter = cache.Peek(kItem1Key); + ASSERT_TRUE(peekiter != cache.end()); + + Cache::reverse_iterator iter = cache.rbegin(); + ASSERT_TRUE(iter != cache.rend()); + EXPECT_EQ(kItem1Key, iter->first); + EXPECT_EQ(item1.value, iter->second.value); + } +} + +TEST(MRUCacheTest, KeyReplacement) { + typedef butil::MRUCache Cache; + Cache cache(Cache::NO_AUTO_EVICT); + + static const int kItem1Key = 1; + CachedItem item1(10); + cache.Put(kItem1Key, item1); + + static const int kItem2Key = 2; + CachedItem item2(20); + cache.Put(kItem2Key, item2); + + static const int kItem3Key = 3; + CachedItem item3(30); + cache.Put(kItem3Key, item3); + + static const int kItem4Key = 4; + CachedItem item4(40); + cache.Put(kItem4Key, item4); + + CachedItem item5(50); + cache.Put(kItem3Key, item5); + + EXPECT_EQ(4U, cache.size()); + for (int i = 0; i < 3; ++i) { + Cache::reverse_iterator iter = cache.rbegin(); + ASSERT_TRUE(iter != cache.rend()); + } + + // Make it so only the most important element is there. + cache.ShrinkToSize(1); + + Cache::iterator iter = cache.begin(); + EXPECT_EQ(kItem3Key, iter->first); + EXPECT_EQ(item5.value, iter->second.value); +} + +// Make sure that the owning version release its pointers properly. +TEST(MRUCacheTest, Owning) { + typedef butil::OwningMRUCache Cache; + Cache cache(Cache::NO_AUTO_EVICT); + + int initial_count = cached_item_live_count; + + // First insert and item and then overwrite it. + static const int kItem1Key = 1; + cache.Put(kItem1Key, new CachedItem(20)); + cache.Put(kItem1Key, new CachedItem(22)); + + // There should still be one item, and one extra live item. + Cache::iterator iter = cache.Get(kItem1Key); + EXPECT_EQ(1U, cache.size()); + EXPECT_TRUE(iter != cache.end()); + EXPECT_EQ(initial_count + 1, cached_item_live_count); + + // Now remove it. + cache.Erase(cache.begin()); + EXPECT_EQ(initial_count, cached_item_live_count); + + // Now try another cache that goes out of scope to make sure its pointers + // go away. + { + Cache cache2(Cache::NO_AUTO_EVICT); + cache2.Put(1, new CachedItem(20)); + cache2.Put(2, new CachedItem(20)); + } + + // There should be no objects leaked. + EXPECT_EQ(initial_count, cached_item_live_count); + + // Check that Clear() also frees things correctly. + { + Cache cache2(Cache::NO_AUTO_EVICT); + cache2.Put(1, new CachedItem(20)); + cache2.Put(2, new CachedItem(20)); + EXPECT_EQ(initial_count + 2, cached_item_live_count); + cache2.Clear(); + EXPECT_EQ(initial_count, cached_item_live_count); + } +} + +TEST(MRUCacheTest, AutoEvict) { + typedef butil::OwningMRUCache Cache; + static const Cache::size_type kMaxSize = 3; + + int initial_count = cached_item_live_count; + + { + Cache cache(kMaxSize); + + static const int kItem1Key = 1, kItem2Key = 2, kItem3Key = 3, kItem4Key = 4; + cache.Put(kItem1Key, new CachedItem(20)); + cache.Put(kItem2Key, new CachedItem(21)); + cache.Put(kItem3Key, new CachedItem(22)); + cache.Put(kItem4Key, new CachedItem(23)); + + // The cache should only have kMaxSize items in it even though we inserted + // more. + EXPECT_EQ(kMaxSize, cache.size()); + } + + // There should be no objects leaked. + EXPECT_EQ(initial_count, cached_item_live_count); +} + +TEST(MRUCacheTest, HashingMRUCache) { + // Very simple test to make sure that the hashing cache works correctly. + typedef butil::HashingMRUCache Cache; + Cache cache(Cache::NO_AUTO_EVICT); + + CachedItem one(1); + cache.Put("First", one); + + CachedItem two(2); + cache.Put("Second", two); + + EXPECT_EQ(one.value, cache.Get("First")->second.value); + EXPECT_EQ(two.value, cache.Get("Second")->second.value); + cache.ShrinkToSize(1); + EXPECT_EQ(two.value, cache.Get("Second")->second.value); + EXPECT_TRUE(cache.Get("First") == cache.end()); +} diff --git a/test/multiprocess_func_list.h b/test/multiprocess_func_list.h new file mode 100644 index 0000000..f806d53 --- /dev/null +++ b/test/multiprocess_func_list.h @@ -0,0 +1,70 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef TESTING_MULTIPROCESS_FUNC_LIST_H_ +#define TESTING_MULTIPROCESS_FUNC_LIST_H_ + +#include + +// This file provides the plumbing to register functions to be executed +// as the main function of a child process in a multi-process test. +// This complements the MultiProcessTest class which provides facilities +// for launching such tests. +// +// The MULTIPROCESS_TEST_MAIN() macro registers a string -> func_ptr mapping +// by creating a new global instance of the AppendMultiProcessTest() class +// this means that by the time that we reach our main() function the mapping +// is already in place. +// +// Example usage: +// MULTIPROCESS_TEST_MAIN(a_test_func) { +// // Code here runs in a child process. +// return 0; +// } +// +// The prototype of a_test_func is implicitly +// int test_main_func_name(); + +namespace multi_process_function_list { + +// Type for child process main functions. +typedef int (*TestMainFunctionPtr)(); + +// Type for child setup functions. +typedef void (*SetupFunctionPtr)(); + +// Helper class to append a test function to the global mapping. +// Used by the MULTIPROCESS_TEST_MAIN macro. +class AppendMultiProcessTest { + public: + // |main_func_ptr| is the main function that is run in the child process. + // |setup_func_ptr| is a function run when the global mapping is added. + AppendMultiProcessTest(std::string test_name, + TestMainFunctionPtr main_func_ptr, + SetupFunctionPtr setup_func_ptr); +}; + +// Invoke the main function of a test previously registered with +// MULTIPROCESS_TEST_MAIN() +int InvokeChildProcessTest(std::string test_name); + +// This macro creates a global MultiProcessTest::AppendMultiProcessTest object +// whose constructor does the work of adding the global mapping. +#define MULTIPROCESS_TEST_MAIN(test_main) \ + MULTIPROCESS_TEST_MAIN_WITH_SETUP(test_main, NULL) + +// Same as above but lets callers specify a setup method that is run in the +// child process, just before the main function is run. This facilitates +// adding a generic one-time setup function for multiple tests. +#define MULTIPROCESS_TEST_MAIN_WITH_SETUP(test_main, test_setup) \ + int test_main(); \ + namespace { \ + multi_process_function_list::AppendMultiProcessTest \ + AddMultiProcessTest##_##test_main(#test_main, (test_main), (test_setup)); \ + } \ + int test_main() + +} // namespace multi_process_function_list + +#endif // TESTING_MULTIPROCESS_FUNC_LIST_H_ diff --git a/test/non_thread_safe_unittest.cc b/test/non_thread_safe_unittest.cc new file mode 100644 index 0000000..4285757 --- /dev/null +++ b/test/non_thread_safe_unittest.cc @@ -0,0 +1,167 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/basictypes.h" +#include "butil/logging.h" +#include "butil/memory/scoped_ptr.h" +#include "butil/threading/non_thread_safe.h" +#include "butil/threading/simple_thread.h" +#include + +// Duplicated from butil/threading/non_thread_safe.h so that we can be +// good citizens there and undef the macro. +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) +#define ENABLE_NON_THREAD_SAFE 1 +#else +#define ENABLE_NON_THREAD_SAFE 0 +#endif + +namespace butil { + +namespace { + +// Simple class to exersice the basics of NonThreadSafe. +// Both the destructor and DoStuff should verify that they were +// called on the same thread as the constructor. +class NonThreadSafeClass : public NonThreadSafe { + public: + NonThreadSafeClass() {} + + // Verifies that it was called on the same thread as the constructor. + void DoStuff() { + DCHECK(CalledOnValidThread()); + } + + void DetachFromThread() { + NonThreadSafe::DetachFromThread(); + } + + static void MethodOnDifferentThreadImpl(); + static void DestructorOnDifferentThreadImpl(); + + private: + DISALLOW_COPY_AND_ASSIGN(NonThreadSafeClass); +}; + +// Calls NonThreadSafeClass::DoStuff on another thread. +class CallDoStuffOnThread : public SimpleThread { + public: + explicit CallDoStuffOnThread(NonThreadSafeClass* non_thread_safe_class) + : SimpleThread("call_do_stuff_on_thread"), + non_thread_safe_class_(non_thread_safe_class) { + } + + virtual void Run() OVERRIDE { + non_thread_safe_class_->DoStuff(); + } + + private: + NonThreadSafeClass* non_thread_safe_class_; + + DISALLOW_COPY_AND_ASSIGN(CallDoStuffOnThread); +}; + +// Deletes NonThreadSafeClass on a different thread. +class DeleteNonThreadSafeClassOnThread : public SimpleThread { + public: + explicit DeleteNonThreadSafeClassOnThread( + NonThreadSafeClass* non_thread_safe_class) + : SimpleThread("delete_non_thread_safe_class_on_thread"), + non_thread_safe_class_(non_thread_safe_class) { + } + + virtual void Run() OVERRIDE { + non_thread_safe_class_.reset(); + } + + private: + scoped_ptr non_thread_safe_class_; + + DISALLOW_COPY_AND_ASSIGN(DeleteNonThreadSafeClassOnThread); +}; + +} // namespace + +TEST(NonThreadSafeTest, CallsAllowedOnSameThread) { + scoped_ptr non_thread_safe_class( + new NonThreadSafeClass); + + // Verify that DoStuff doesn't assert. + non_thread_safe_class->DoStuff(); + + // Verify that the destructor doesn't assert. + non_thread_safe_class.reset(); +} + +TEST(NonThreadSafeTest, DetachThenDestructOnDifferentThread) { + scoped_ptr non_thread_safe_class( + new NonThreadSafeClass); + + // Verify that the destructor doesn't assert when called on a different thread + // after a detach. + non_thread_safe_class->DetachFromThread(); + DeleteNonThreadSafeClassOnThread delete_on_thread( + non_thread_safe_class.release()); + + delete_on_thread.Start(); + delete_on_thread.Join(); +} + +#if GTEST_HAS_DEATH_TEST || !ENABLE_NON_THREAD_SAFE + +void NonThreadSafeClass::MethodOnDifferentThreadImpl() { + scoped_ptr non_thread_safe_class( + new NonThreadSafeClass); + + // Verify that DoStuff asserts in debug builds only when called + // on a different thread. + CallDoStuffOnThread call_on_thread(non_thread_safe_class.get()); + + call_on_thread.Start(); + call_on_thread.Join(); +} + +#if ENABLE_NON_THREAD_SAFE +TEST(NonThreadSafeDeathTest, MethodNotAllowedOnDifferentThreadInDebug) { + ASSERT_DEATH({ + NonThreadSafeClass::MethodOnDifferentThreadImpl(); + }, ""); +} +#else +TEST(NonThreadSafeTest, MethodAllowedOnDifferentThreadInRelease) { + NonThreadSafeClass::MethodOnDifferentThreadImpl(); +} +#endif // ENABLE_NON_THREAD_SAFE + +void NonThreadSafeClass::DestructorOnDifferentThreadImpl() { + scoped_ptr non_thread_safe_class( + new NonThreadSafeClass); + + // Verify that the destructor asserts in debug builds only + // when called on a different thread. + DeleteNonThreadSafeClassOnThread delete_on_thread( + non_thread_safe_class.release()); + + delete_on_thread.Start(); + delete_on_thread.Join(); +} + +#if ENABLE_NON_THREAD_SAFE +TEST(NonThreadSafeDeathTest, DestructorNotAllowedOnDifferentThreadInDebug) { + ASSERT_DEATH({ + NonThreadSafeClass::DestructorOnDifferentThreadImpl(); + }, ""); +} +#else +TEST(NonThreadSafeTest, DestructorAllowedOnDifferentThreadInRelease) { + NonThreadSafeClass::DestructorOnDifferentThreadImpl(); +} +#endif // ENABLE_NON_THREAD_SAFE + +#endif // GTEST_HAS_DEATH_TEST || !ENABLE_NON_THREAD_SAFE + +// Just in case we ever get lumped together with other compilation units. +#undef ENABLE_NON_THREAD_SAFE + +} // namespace butil diff --git a/test/nullable_string16_unittest.cc b/test/nullable_string16_unittest.cc new file mode 100644 index 0000000..7801dbf --- /dev/null +++ b/test/nullable_string16_unittest.cc @@ -0,0 +1,35 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/strings/nullable_string16.h" +#include "butil/strings/utf_string_conversions.h" +#include + +namespace butil { + +TEST(NullableString16Test, DefaultConstructor) { + NullableString16 s; + EXPECT_TRUE(s.is_null()); + EXPECT_EQ(string16(), s.string()); +} + +TEST(NullableString16Test, Equals) { + NullableString16 a(ASCIIToUTF16("hello"), false); + NullableString16 b(ASCIIToUTF16("hello"), false); + EXPECT_EQ(a, b); +} + +TEST(NullableString16Test, NotEquals) { + NullableString16 a(ASCIIToUTF16("hello"), false); + NullableString16 b(ASCIIToUTF16("world"), false); + EXPECT_NE(a, b); +} + +TEST(NullableString16Test, NotEqualsNull) { + NullableString16 a(ASCIIToUTF16("hello"), false); + NullableString16 b; + EXPECT_NE(a, b); +} + +} // namespace butil diff --git a/test/object_pool_unittest.cpp b/test/object_pool_unittest.cpp new file mode 100644 index 0000000..d7fecf5 --- /dev/null +++ b/test/object_pool_unittest.cpp @@ -0,0 +1,379 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include "butil/time.h" +#include "butil/macros.h" + +#define BAIDU_CLEAR_OBJECT_POOL_AFTER_ALL_THREADS_QUIT +#include "butil/object_pool.h" + +namespace { +struct MyObject {}; + +int nfoo_dtor = 0; +struct Foo { + Foo() { + x = rand() % 2; + } + ~Foo() { + ++nfoo_dtor; + } + int x; +}; +} + +namespace butil { +template <> struct ObjectPoolBlockMaxSize { + static const size_t value = 128; +}; + +template <> struct ObjectPoolBlockMaxItem { + static const size_t value = 3; +}; + +template <> struct ObjectPoolFreeChunkMaxItem { + static size_t value() { return 5; } +}; + +template <> struct ObjectPoolValidator { + static bool validate(const Foo* foo) { + return foo->x != 0; + } +}; +} + +namespace { +using namespace butil; + +class ObjectPoolTest : public ::testing::Test{ +protected: + ObjectPoolTest(){ + }; + virtual ~ObjectPoolTest(){}; + virtual void SetUp() { + srand(time(0)); + }; + virtual void TearDown() { + }; +}; + +int nc = 0; +int nd = 0; +std::set ptr_set; +struct YellObj { + YellObj() { + ++nc; + ptr_set.insert(this); + printf("Created %p\n", this); + } + ~YellObj() { + ++nd; + ptr_set.erase(this); + printf("Destroyed %p\n", this); + } + char _dummy[96]; +}; + +TEST_F(ObjectPoolTest, change_config) { + int a[2]; + printf("%lu\n", ARRAY_SIZE(a)); + + ObjectPoolInfo info = describe_objects(); + ObjectPoolInfo zero_info = { 0, 0, 0, 0, 3, 3, 0 }; + ASSERT_EQ(0, memcmp(&info, &zero_info, sizeof(info))); + + MyObject* p = get_object(); + std::cout << describe_objects() << std::endl; + return_object(p); + std::cout << describe_objects() << std::endl; +} + +struct NonDefaultCtorObject { + explicit NonDefaultCtorObject(int value) : _value(value) {} + NonDefaultCtorObject(int value, int dummy) : _value(value + dummy) {} + + int _value; +}; + +TEST_F(ObjectPoolTest, sanity) { + ptr_set.clear(); + NonDefaultCtorObject* p1 = get_object(10); + ASSERT_EQ(10, p1->_value); + NonDefaultCtorObject* p2 = get_object(100, 30); + ASSERT_EQ(130, p2->_value); + + printf("BLOCK_NITEM=%lu\n", ObjectPool::BLOCK_NITEM); + + nc = 0; + nd = 0; + { + YellObj* o1 = get_object(); + ASSERT_TRUE(o1); + + ASSERT_EQ(1, nc); + ASSERT_EQ(0, nd); + + YellObj* o2 = get_object(); + ASSERT_TRUE(o2); + + ASSERT_EQ(2, nc); + ASSERT_EQ(0, nd); + + return_object(o1); + ASSERT_EQ(2, nc); + ASSERT_EQ(0, nd); + + return_object(o2); + ASSERT_EQ(2, nc); + ASSERT_EQ(0, nd); + } + ASSERT_EQ(0, nd); + + clear_objects(); + ASSERT_EQ(2, nd); + ASSERT_TRUE(ptr_set.empty()) << ptr_set.size(); +} + +TEST_F(ObjectPoolTest, validator) { + nfoo_dtor = 0; + int nfoo = 0; + for (int i = 0; i < 100; ++i) { + Foo* foo = get_object(); + if (foo) { + ASSERT_EQ(1, foo->x); + ++nfoo; + } + } + ASSERT_EQ(nfoo + nfoo_dtor, 100); + ASSERT_EQ((size_t)nfoo, describe_objects().item_num); +} + +TEST_F(ObjectPoolTest, get_int) { + clear_objects(); + + // Perf of this test is affected by previous case. + const size_t N = 100000; + + butil::Timer tm; + + // warm up + int* p = get_object(); + *p = 0; + return_object(p); + delete (new int); + + tm.start(); + for (size_t i = 0; i < N; ++i) { + *get_object() = i; + } + tm.stop(); + printf("get a int takes %.1fns\n", tm.n_elapsed()/(double)N); + + std::vector> new_ints; + new_ints.reserve(N); + tm.start(); + for (size_t i = 0; i < N; ++i) { + int* pi = new int; + *pi = i; + new_ints.emplace_back(pi); + } + tm.stop(); + printf("new a int takes %" PRId64 "ns\n", tm.n_elapsed()/N); + + std::cout << describe_objects() << std::endl; + clear_objects(); + std::cout << describe_objects() << std::endl; +} + + +struct SilentObj { + char buf[sizeof(YellObj)]; +}; + +TEST_F(ObjectPoolTest, get_perf) { + const size_t N = 10000; + std::vector new_list; + new_list.reserve(N); + + butil::Timer tm1, tm2; + + // warm up + return_object(get_object()); + delete (new SilentObj); + + // Run twice, the second time will be must faster. + for (size_t j = 0; j < 2; ++j) { + + tm1.start(); + for (size_t i = 0; i < N; ++i) { + get_object(); + } + tm1.stop(); + printf("get a SilentObj takes %" PRId64 "ns\n", tm1.n_elapsed()/N); + //clear_objects(); // free all blocks + + tm2.start(); + for (size_t i = 0; i < N; ++i) { + new_list.push_back(new SilentObj); + } + tm2.stop(); + printf("new a SilentObj takes %" PRId64 "ns\n", tm2.n_elapsed()/N); + for (size_t i = 0; i < new_list.size(); ++i) { + delete new_list[i]; + } + new_list.clear(); + } + + std::cout << describe_objects() << std::endl; +} + +struct D { int val[1]; }; + +void* get_and_return_int(void*) { + // Perf of this test is affected by previous case. + const size_t N = 100000; + std::vector v; + v.reserve(N); + butil::Timer tm0, tm1, tm2; + D tmp = D(); + int sr = 0; + + // warm up + tm0.start(); + return_object(get_object()); + tm0.stop(); + + printf("[%lu] warmup=%" PRId64 "\n", (size_t)pthread_self(), tm0.n_elapsed()); + + for (int j = 0; j < 5; ++j) { + v.clear(); + sr = 0; + + tm1.start(); + for (size_t i = 0; i < N; ++i) { + D* p = get_object(); + *p = tmp; + v.push_back(p); + } + tm1.stop(); + + std::random_shuffle(v.begin(), v.end()); + + tm2.start(); + for (size_t i = 0; i < v.size(); ++i) { + sr += return_object(v[i]); + } + tm2.stop(); + + if (0 != sr) { + printf("%d return_object failed\n", sr); + } + + printf("[%lu:%d] get=%.1f return=%.1f\n", + (size_t)pthread_self(), j, tm1.n_elapsed()/(double)N, + tm2.n_elapsed()/(double)N); + } + return NULL; +} + +void* new_and_delete_int(void*) { + const size_t N = 100000; + std::vector v2; + v2.reserve(N); + butil::Timer tm0, tm1, tm2; + D tmp = D(); + + for (int j = 0; j < 3; ++j) { + v2.clear(); + + // warm up + delete (new D); + + tm1.start(); + for (size_t i = 0; i < N; ++i) { + D *p = new D; + *p = tmp; + v2.push_back(p); + } + tm1.stop(); + + std::random_shuffle(v2.begin(), v2.end()); + + tm2.start(); + for (size_t i = 0; i < v2.size(); ++i) { + delete v2[i]; + } + tm2.stop(); + + printf("[%lu:%d] new=%.1f delete=%.1f\n", + (size_t)pthread_self(), j, tm1.n_elapsed()/(double)N, + tm2.n_elapsed()/(double)N); + } + + return NULL; +} + +TEST_F(ObjectPoolTest, get_and_return_int_single_thread) { + get_and_return_int(NULL); + new_and_delete_int(NULL); +} + +TEST_F(ObjectPoolTest, get_and_return_int_multiple_threads) { + pthread_t tid[16]; + for (size_t i = 0; i < ARRAY_SIZE(tid); ++i) { + ASSERT_EQ(0, pthread_create(&tid[i], NULL, get_and_return_int, NULL)); + } + for (size_t i = 0; i < ARRAY_SIZE(tid); ++i) { + pthread_join(tid[i], NULL); + } + + pthread_t tid2[16]; + for (size_t i = 0; i < ARRAY_SIZE(tid2); ++i) { + ASSERT_EQ(0, pthread_create(&tid2[i], NULL, new_and_delete_int, NULL)); + } + for (size_t i = 0; i < ARRAY_SIZE(tid2); ++i) { + pthread_join(tid2[i], NULL); + } + + std::cout << describe_objects() << std::endl; + clear_objects(); + ObjectPoolInfo info = describe_objects(); + ObjectPoolInfo zero_info = { 0, 0, 0, 0, ObjectPoolBlockMaxItem::value, + ObjectPoolBlockMaxItem::value, 0 }; + ASSERT_EQ(0, memcmp(&info, &zero_info, sizeof(info))); +} + +TEST_F(ObjectPoolTest, verify_get) { + clear_objects(); + std::cout << describe_objects() << std::endl; + + std::vector v; + v.reserve(100000); + for (int i = 0; (size_t)i < v.capacity(); ++i) { + int* p = get_object(); + *p = i; + v.push_back(p); + } + int i; + for (i = 0; (size_t)i < v.size() && *v[i] == i; ++i); + ASSERT_EQ(v.size(), (size_t)i) << "i=" << i << ", " << *v[i]; + + clear_objects(); +} +} // namespace diff --git a/test/observer_list_unittest.cc b/test/observer_list_unittest.cc new file mode 100644 index 0000000..cb380d9 --- /dev/null +++ b/test/observer_list_unittest.cc @@ -0,0 +1,189 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/observer_list.h" + +#include + +#include "butil/compiler_specific.h" +#include "butil/memory/weak_ptr.h" +#include "butil/threading/platform_thread.h" +#include + +namespace butil { +namespace { + +class Foo { + public: + virtual void Observe(int x) = 0; + virtual ~Foo() {} +}; + +class Adder : public Foo { + public: + explicit Adder(int scaler) : total(0), scaler_(scaler) {} + virtual void Observe(int x) OVERRIDE { + total += x * scaler_; + } + virtual ~Adder() {} + int total; + + private: + int scaler_; +}; + +class Disrupter : public Foo { + public: + Disrupter(ObserverList* list, Foo* doomed) + : list_(list), + doomed_(doomed) { + } + virtual ~Disrupter() {} + virtual void Observe(int x) OVERRIDE { + list_->RemoveObserver(doomed_); + } + + private: + ObserverList* list_; + Foo* doomed_; +}; + +template +class AddInObserve : public Foo { + public: + explicit AddInObserve(ObserverListType* observer_list) + : added(false), + observer_list(observer_list), + adder(1) { + } + + virtual void Observe(int x) OVERRIDE { + if (!added) { + added = true; + observer_list->AddObserver(&adder); + } + } + + bool added; + ObserverListType* observer_list; + Adder adder; +}; + +TEST(ObserverListTest, BasicTest) { + ObserverList observer_list; + Adder a(1), b(-1), c(1), d(-1), e(-1); + Disrupter evil(&observer_list, &c); + + observer_list.AddObserver(&a); + observer_list.AddObserver(&b); + + FOR_EACH_OBSERVER(Foo, observer_list, Observe(10)); + + observer_list.AddObserver(&evil); + observer_list.AddObserver(&c); + observer_list.AddObserver(&d); + + // Removing an observer not in the list should do nothing. + observer_list.RemoveObserver(&e); + + FOR_EACH_OBSERVER(Foo, observer_list, Observe(10)); + + EXPECT_EQ(20, a.total); + EXPECT_EQ(-20, b.total); + EXPECT_EQ(0, c.total); + EXPECT_EQ(-10, d.total); + EXPECT_EQ(0, e.total); +} + +TEST(ObserverListTest, Existing) { + ObserverList observer_list(ObserverList::NOTIFY_EXISTING_ONLY); + Adder a(1); + AddInObserve > b(&observer_list); + + observer_list.AddObserver(&a); + observer_list.AddObserver(&b); + + FOR_EACH_OBSERVER(Foo, observer_list, Observe(1)); + + EXPECT_TRUE(b.added); + // B's adder should not have been notified because it was added during + // notification. + EXPECT_EQ(0, b.adder.total); + + // Notify again to make sure b's adder is notified. + FOR_EACH_OBSERVER(Foo, observer_list, Observe(1)); + EXPECT_EQ(1, b.adder.total); +} + +class AddInClearObserve : public Foo { + public: + explicit AddInClearObserve(ObserverList* list) + : list_(list), added_(false), adder_(1) {} + + virtual void Observe(int /* x */) OVERRIDE { + list_->Clear(); + list_->AddObserver(&adder_); + added_ = true; + } + + bool added() const { return added_; } + const Adder& adder() const { return adder_; } + + private: + ObserverList* const list_; + + bool added_; + Adder adder_; +}; + +TEST(ObserverListTest, ClearNotifyAll) { + ObserverList observer_list; + AddInClearObserve a(&observer_list); + + observer_list.AddObserver(&a); + + FOR_EACH_OBSERVER(Foo, observer_list, Observe(1)); + EXPECT_TRUE(a.added()); + EXPECT_EQ(1, a.adder().total) + << "Adder should observe once and have sum of 1."; +} + +TEST(ObserverListTest, ClearNotifyExistingOnly) { + ObserverList observer_list(ObserverList::NOTIFY_EXISTING_ONLY); + AddInClearObserve a(&observer_list); + + observer_list.AddObserver(&a); + + FOR_EACH_OBSERVER(Foo, observer_list, Observe(1)); + EXPECT_TRUE(a.added()); + EXPECT_EQ(0, a.adder().total) + << "Adder should not observe, so sum should still be 0."; +} + +class ListDestructor : public Foo { + public: + explicit ListDestructor(ObserverList* list) : list_(list) {} + virtual ~ListDestructor() {} + + virtual void Observe(int x) OVERRIDE { + delete list_; + } + + private: + ObserverList* list_; +}; + + +TEST(ObserverListTest, IteratorOutlivesList) { + ObserverList* observer_list = new ObserverList; + ListDestructor a(observer_list); + observer_list->AddObserver(&a); + + FOR_EACH_OBSERVER(Foo, *observer_list, Observe(0)); + // If this test fails, there'll be Valgrind errors when this function goes out + // of scope. +} + +} // namespace +} // namespace butil diff --git a/test/optional_unittest.cpp b/test/optional_unittest.cpp new file mode 100644 index 0000000..8038105 --- /dev/null +++ b/test/optional_unittest.cpp @@ -0,0 +1,298 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/containers/optional.h" + +namespace { + +butil::optional empty_optional() { + return {}; +} + +TEST(OptionalTest, sanity) { + { + butil::optional empty; + ASSERT_FALSE(empty); + } + + { + butil::optional empty{}; + ASSERT_FALSE(empty); + } + + { + butil::optional empty = empty_optional(); + ASSERT_FALSE(empty); + } + + { + butil::optional non_empty(42); + ASSERT_TRUE(non_empty); + ASSERT_TRUE(non_empty.has_value()); + ASSERT_EQ(*non_empty, 42); + } + + butil::optional opt_string = "abc"; + ASSERT_TRUE(opt_string); + ASSERT_EQ(*opt_string, "abc"); +} + +TEST(OptionalTest, nullopt) { + butil::optional empty(butil::nullopt); + ASSERT_FALSE(empty); + + empty = 1; + ASSERT_TRUE(empty); + ASSERT_EQ(1, empty); + + empty = butil::nullopt; + ASSERT_FALSE(empty); +} + +TEST(OptionalTest, copy) { + butil::optional op(1); + ASSERT_TRUE(op); + ASSERT_EQ(1, op); + + butil::optional non_empty(op); + ASSERT_TRUE(non_empty); + + op = butil::nullopt; + ASSERT_FALSE(op); + + butil::optional empty(op); + ASSERT_FALSE(empty); + + non_empty = empty; + ASSERT_FALSE(non_empty); + + op = 10; + non_empty = op; + ASSERT_TRUE(non_empty); + ASSERT_EQ(10, non_empty); +} + +TEST(OptionalTest, move) { + butil::optional empty; + ASSERT_FALSE(empty); + butil::optional non_empty = 1; + ASSERT_TRUE(non_empty); + ASSERT_EQ(1, non_empty); + + butil::optional empty_move(std::move(empty)); + ASSERT_FALSE(empty_move); + + butil::optional non_empty_move(std::move(non_empty)); + ASSERT_TRUE(non_empty_move); + ASSERT_EQ(1, non_empty_move); + + butil::optional empty_move_assign; + empty_move_assign = std::move(empty); + ASSERT_FALSE(empty_move_assign); +} + +struct Obj {}; + +struct Convert { + Convert() + :default_ctor(false), move_ctor(false) { } + explicit Convert(const Obj&) + :default_ctor(true), move_ctor(false) { } + explicit Convert(Obj&&) + :default_ctor(true), move_ctor(true) { } + + bool default_ctor; + bool move_ctor; +}; + +struct ConvertFromOptional { + ConvertFromOptional() + :default_ctor(false), move_ctor(false), from_optional(false) { } + ConvertFromOptional(const Obj&) + :default_ctor(true), move_ctor(false), from_optional(false) { } + ConvertFromOptional(Obj&&) + :default_ctor(true), move_ctor(true), from_optional(false) { } + ConvertFromOptional( + const butil::optional&) + :default_ctor(true), move_ctor(false), from_optional(true) { } + ConvertFromOptional(butil::optional&&) + :default_ctor(true), move_ctor(true), from_optional(true) { } + + bool default_ctor; + bool move_ctor; + bool from_optional; +}; + +TEST(OptionalTest, convert) { + butil::optional i_empty; + ASSERT_FALSE(i_empty); + butil::optional i(butil::in_place); + ASSERT_TRUE(i); + { + butil::optional empty(i_empty); + ASSERT_FALSE(empty); + butil::optional opt_copy(i); + ASSERT_TRUE(opt_copy); + ASSERT_TRUE(opt_copy->default_ctor); + ASSERT_FALSE(opt_copy->move_ctor); + + butil::optional opt_move(butil::optional{ butil::in_place }); + ASSERT_TRUE(opt_move); + ASSERT_TRUE(opt_move->default_ctor); + ASSERT_TRUE(opt_move->move_ctor); + } + + { + static_assert( + std::is_convertible, + butil::optional>::value, + ""); + butil::optional opt0 = i_empty; + ASSERT_TRUE(opt0); + ASSERT_TRUE(opt0->default_ctor); + ASSERT_FALSE(opt0->move_ctor); + ASSERT_TRUE(opt0->from_optional); + butil::optional opt1 = butil::optional(); + ASSERT_TRUE(opt1); + ASSERT_TRUE(opt1->default_ctor); + ASSERT_TRUE(opt1->move_ctor); + ASSERT_TRUE(opt1->from_optional); + } +} + +TEST(OptionalTest, value) { + butil::optional opt_empty; + butil::optional opt_double = 1.0; + ASSERT_THROW(opt_empty.value(), butil::bad_optional_access); + ASSERT_EQ(10.0, opt_empty.value_or(10)); + ASSERT_EQ(1.0, opt_double.value()); + ASSERT_EQ(1.0, opt_double.value_or(42)); + ASSERT_EQ(10.0, butil::optional().value_or(10)); + ASSERT_EQ(1.0, butil::optional(1).value_or(10)); +} + +TEST(OptionalTest, emplace) { + butil::optional opt_string; + ASSERT_TRUE((std::is_same::value)); + std::string& str = opt_string.emplace("abc"); + ASSERT_EQ(&str, &opt_string.value()); +} + +TEST(OptionalTest, swap) { + butil::optional opt_empty, opt1 = 1, opt2 = 2; + ASSERT_FALSE(opt_empty); + ASSERT_TRUE(opt1); + ASSERT_EQ(1, opt1.value()); + ASSERT_TRUE(opt2); + ASSERT_EQ(2, opt2.value()); + swap(opt_empty, opt1); + ASSERT_FALSE(opt1); + ASSERT_TRUE(opt_empty); + ASSERT_EQ(1, opt_empty.value()); + ASSERT_TRUE(opt2); + ASSERT_EQ(2, opt2.value()); + swap(opt_empty, opt1); + ASSERT_FALSE(opt_empty); + ASSERT_TRUE(opt1); + ASSERT_EQ(1, opt1.value()); + ASSERT_TRUE(opt2); + ASSERT_EQ(2, opt2.value()); + swap(opt1, opt2); + ASSERT_FALSE(opt_empty); + ASSERT_TRUE(opt1); + ASSERT_EQ(2, opt1.value()); + ASSERT_TRUE(opt2); + ASSERT_EQ(1, opt2.value()); + + ASSERT_TRUE(noexcept(opt1.swap(opt2))); + ASSERT_TRUE(noexcept(swap(opt1, opt2))); +} + +TEST(OptionalTest, make_optional) { + auto opt_int = butil::make_optional(1); + ASSERT_TRUE((std::is_same>::value)); + ASSERT_EQ(1, opt_int); +} + +TEST(OptionalTest, comparison) { + butil::optional empty; + butil::optional one = 1; + butil::optional two = 2; + ASSERT_TRUE(empty == empty); + ASSERT_FALSE(empty == one); + ASSERT_FALSE(empty == two); + ASSERT_TRUE(empty == butil::nullopt); + ASSERT_TRUE(one == one); + ASSERT_FALSE(one == two); + ASSERT_FALSE(one == butil::nullopt); + ASSERT_TRUE(two == two); + ASSERT_FALSE(two == butil::nullopt); + + ASSERT_FALSE(empty != empty); + ASSERT_TRUE(empty != one); + ASSERT_TRUE(empty != two); + ASSERT_FALSE(empty != butil::nullopt); + ASSERT_FALSE(one != one); + ASSERT_TRUE(one != two); + ASSERT_TRUE(one != butil::nullopt); + ASSERT_FALSE(two != two); + ASSERT_TRUE(two != butil::nullopt); + + ASSERT_FALSE(empty < empty); + ASSERT_TRUE(empty < one); + ASSERT_TRUE(empty < two); + ASSERT_FALSE(empty < butil::nullopt); + ASSERT_FALSE(one < one); + ASSERT_TRUE(one < two); + ASSERT_FALSE(one < butil::nullopt); + ASSERT_FALSE(two < two); + ASSERT_FALSE(two < butil::nullopt); + + ASSERT_TRUE(empty <= empty); + ASSERT_TRUE(empty <= one); + ASSERT_TRUE(empty <= two); + ASSERT_TRUE(empty <= butil::nullopt); + ASSERT_TRUE(one <= one); + ASSERT_TRUE(one <= two); + ASSERT_FALSE(one <= butil::nullopt); + ASSERT_TRUE(two <= two); + ASSERT_FALSE(two <= butil::nullopt); + + ASSERT_FALSE(empty > empty); + ASSERT_FALSE(empty > one); + ASSERT_FALSE(empty > two); + ASSERT_FALSE(empty > butil::nullopt); + ASSERT_FALSE(one > one); + ASSERT_FALSE(one > two); + ASSERT_TRUE(one > butil::nullopt); + ASSERT_FALSE(two > two); + ASSERT_TRUE(two > butil::nullopt); + + ASSERT_TRUE(empty >= empty); + ASSERT_FALSE(empty >= one); + ASSERT_FALSE(empty >= two); + ASSERT_TRUE(empty >= butil::nullopt); + ASSERT_TRUE(one >= one); + ASSERT_FALSE(one >= two); + ASSERT_TRUE(one >= butil::nullopt); + ASSERT_TRUE(two >= two); + ASSERT_TRUE(two >= butil::nullopt); +} + +} // namespace diff --git a/test/platform_thread_unittest.cc b/test/platform_thread_unittest.cc new file mode 100644 index 0000000..124dac7 --- /dev/null +++ b/test/platform_thread_unittest.cc @@ -0,0 +1,121 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/compiler_specific.h" +#include "butil/threading/platform_thread.h" + +#include + +namespace butil { + +// Trivial tests that thread runs and doesn't crash on create and join --------- + +class TrivialThread : public PlatformThread::Delegate { + public: + TrivialThread() : did_run_(false) {} + + virtual void ThreadMain() OVERRIDE { + did_run_ = true; + } + + bool did_run() const { return did_run_; } + + private: + bool did_run_; + + DISALLOW_COPY_AND_ASSIGN(TrivialThread); +}; + +TEST(PlatformThreadTest, Trivial) { + TrivialThread thread; + PlatformThreadHandle handle; + + ASSERT_FALSE(thread.did_run()); + ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); + PlatformThread::Join(handle); + ASSERT_TRUE(thread.did_run()); +} + +TEST(PlatformThreadTest, TrivialTimesTen) { + TrivialThread thread[10]; + PlatformThreadHandle handle[arraysize(thread)]; + + for (size_t n = 0; n < arraysize(thread); n++) + ASSERT_FALSE(thread[n].did_run()); + for (size_t n = 0; n < arraysize(thread); n++) + ASSERT_TRUE(PlatformThread::Create(0, &thread[n], &handle[n])); + for (size_t n = 0; n < arraysize(thread); n++) + PlatformThread::Join(handle[n]); + for (size_t n = 0; n < arraysize(thread); n++) + ASSERT_TRUE(thread[n].did_run()); +} + +// Tests of basic thread functions --------------------------------------------- + +class FunctionTestThread : public TrivialThread { + public: + FunctionTestThread() : thread_id_(0) {} + + virtual void ThreadMain() OVERRIDE { + thread_id_ = PlatformThread::CurrentId(); + PlatformThread::YieldCurrentThread(); + PlatformThread::Sleep(TimeDelta::FromMilliseconds(50)); + + // Make sure that the thread ID is the same across calls. + EXPECT_EQ(thread_id_, PlatformThread::CurrentId()); + + TrivialThread::ThreadMain(); + } + + PlatformThreadId thread_id() const { return thread_id_; } + + private: + PlatformThreadId thread_id_; + + DISALLOW_COPY_AND_ASSIGN(FunctionTestThread); +}; + +TEST(PlatformThreadTest, Function) { + PlatformThreadId main_thread_id = PlatformThread::CurrentId(); + + FunctionTestThread thread; + PlatformThreadHandle handle; + + ASSERT_FALSE(thread.did_run()); + ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); + PlatformThread::Join(handle); + ASSERT_TRUE(thread.did_run()); + EXPECT_NE(thread.thread_id(), main_thread_id); + + // Make sure that the thread ID is the same across calls. + EXPECT_EQ(main_thread_id, PlatformThread::CurrentId()); +} + +TEST(PlatformThreadTest, FunctionTimesTen) { + PlatformThreadId main_thread_id = PlatformThread::CurrentId(); + + FunctionTestThread thread[10]; + PlatformThreadHandle handle[arraysize(thread)]; + + for (size_t n = 0; n < arraysize(thread); n++) + ASSERT_FALSE(thread[n].did_run()); + for (size_t n = 0; n < arraysize(thread); n++) + ASSERT_TRUE(PlatformThread::Create(0, &thread[n], &handle[n])); + for (size_t n = 0; n < arraysize(thread); n++) + PlatformThread::Join(handle[n]); + for (size_t n = 0; n < arraysize(thread); n++) { + ASSERT_TRUE(thread[n].did_run()); + EXPECT_NE(thread[n].thread_id(), main_thread_id); + + // Make sure no two threads get the same ID. + for (size_t i = 0; i < n; ++i) { + EXPECT_NE(thread[i].thread_id(), thread[n].thread_id()); + } + } + + // Make sure that the thread ID is the same across calls. + EXPECT_EQ(main_thread_id, PlatformThread::CurrentId()); +} + +} // namespace butil diff --git a/test/popen_unittest.cpp b/test/popen_unittest.cpp new file mode 100644 index 0000000..81d4cde --- /dev/null +++ b/test/popen_unittest.cpp @@ -0,0 +1,143 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Date: 2017/11/06 10:57:08 + +#include "butil/popen.h" +#include "butil/errno.h" +#include "butil/strings/string_piece.h" +#include "butil/build_config.h" +#include + +namespace butil { +extern int read_command_output_through_clone(std::ostream&, const char*); +extern int read_command_output_through_popen(std::ostream&, const char*); +} + +namespace { + +class PopenTest : public testing::Test { +}; + +TEST(PopenTest, posix_popen) { + std::ostringstream oss; + int rc = butil::read_command_output_through_popen(oss, "echo \"Hello World\""); + ASSERT_EQ(0, rc) << berror(errno); + ASSERT_EQ("Hello World\n", oss.str()); + + oss.str(""); + rc = butil::read_command_output_through_popen(oss, "exit 1"); + EXPECT_EQ(1, rc) << berror(errno); + ASSERT_TRUE(oss.str().empty()) << oss.str(); + oss.str(""); + rc = butil::read_command_output_through_popen(oss, "kill -9 $$"); + ASSERT_EQ(-1, rc); + ASSERT_EQ(errno, ECHILD); + ASSERT_TRUE(butil::StringPiece(oss.str()).ends_with("was killed by signal 9")); + oss.str(""); + rc = butil::read_command_output_through_popen(oss, "kill -15 $$"); + ASSERT_EQ(-1, rc); + ASSERT_EQ(errno, ECHILD); + ASSERT_TRUE(butil::StringPiece(oss.str()).ends_with("was killed by signal 15")); + + // TODO(zhujiashun): Fix this in macos + /* + oss.str(""); + ASSERT_EQ(0, butil::read_command_output_through_popen(oss, "for i in `seq 1 100000`; do echo -n '=' ; done")); + ASSERT_EQ(100000u, oss.str().length()); + std::string expected; + expected.resize(100000, '='); + ASSERT_EQ(expected, oss.str()); + */ +} + +#if defined(OS_LINUX) + +TEST(PopenTest, clone) { + std::ostringstream oss; + int rc = butil::read_command_output_through_clone(oss, "echo \"Hello World\""); + ASSERT_EQ(0, rc) << berror(errno); + ASSERT_EQ("Hello World\n", oss.str()); + + oss.str(""); + rc = butil::read_command_output_through_clone(oss, "exit 1"); + ASSERT_EQ(1, rc) << berror(errno); + ASSERT_TRUE(oss.str().empty()) << oss.str(); + oss.str(""); + rc = butil::read_command_output_through_clone(oss, "kill -9 $$"); + ASSERT_EQ(-1, rc); + ASSERT_EQ(errno, ECHILD); + ASSERT_TRUE(butil::StringPiece(oss.str()).ends_with("was killed by signal 9")); + oss.str(""); + rc = butil::read_command_output_through_clone(oss, "kill -15 $$"); + ASSERT_EQ(-1, rc); + ASSERT_EQ(errno, ECHILD); + ASSERT_TRUE(butil::StringPiece(oss.str()).ends_with("was killed by signal 15")); + + oss.str(""); + ASSERT_EQ(0, butil::read_command_output_through_clone(oss, "for i in `seq 1 100000`; do echo -n '=' ; done")); + ASSERT_EQ(100000u, oss.str().length()); + std::string expected; + expected.resize(100000, '='); + ASSERT_EQ(expected, oss.str()); +} + +struct CounterArg { + volatile int64_t counter; + volatile bool stop; +}; + +static void* counter_thread(void* args) { + CounterArg* ca = (CounterArg*)args; + while (!ca->stop) { + ++ca->counter; + } + return NULL; +} + +static int fork_thread(void* arg) { + usleep(100 * 1000); + _exit(0); +} + +const int CHILD_STACK_SIZE = 64 * 1024; + +TEST(PopenTest, does_vfork_suspend_all_threads) { + pthread_t tid; + CounterArg ca = { 0 , false }; + ASSERT_EQ(0, pthread_create(&tid, NULL, counter_thread, &ca)); + usleep(100 * 1000); + char* child_stack_mem = (char*)malloc(CHILD_STACK_SIZE); + void* child_stack = child_stack_mem + CHILD_STACK_SIZE; + const int64_t counter_before_fork = ca.counter; + pid_t cpid = clone(fork_thread, child_stack, CLONE_VFORK, NULL); + const int64_t counter_after_fork = ca.counter; + usleep(100 * 1000); + const int64_t counter_after_sleep = ca.counter; + int ws; + ca.stop = true; + pthread_join(tid, NULL); + std::cout << "bc=" << counter_before_fork << " ac=" << counter_after_fork + << " as=" << counter_after_sleep + << std::endl; + ASSERT_EQ(cpid, waitpid(cpid, &ws, __WALL)); + free(child_stack_mem); +} + +#endif // OS_LINUX + +} diff --git a/test/proc_maps_linux_unittest.cc b/test/proc_maps_linux_unittest.cc new file mode 100644 index 0000000..df41503 --- /dev/null +++ b/test/proc_maps_linux_unittest.cc @@ -0,0 +1,314 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/debug/proc_maps_linux.h" +#include "butil/files/file_path.h" +#include "butil/strings/stringprintf.h" +#include "butil/third_party/dynamic_annotations/dynamic_annotations.h" +#include + +namespace butil { +namespace debug { + +TEST(ProcMapsTest, Empty) { + std::vector regions; + EXPECT_TRUE(ParseProcMaps("", ®ions)); + EXPECT_EQ(0u, regions.size()); +} + +TEST(ProcMapsTest, NoSpaces) { + static const char kNoSpaces[] = + "00400000-0040b000 r-xp 00002200 fc:00 794418 /bin/cat\n"; + + std::vector regions; + ASSERT_TRUE(ParseProcMaps(kNoSpaces, ®ions)); + ASSERT_EQ(1u, regions.size()); + + EXPECT_EQ(0x00400000u, regions[0].start); + EXPECT_EQ(0x0040b000u, regions[0].end); + EXPECT_EQ(0x00002200u, regions[0].offset); + EXPECT_EQ("/bin/cat", regions[0].path); +} + +TEST(ProcMapsTest, Spaces) { + static const char kSpaces[] = + "00400000-0040b000 r-xp 00002200 fc:00 794418 /bin/space cat\n"; + + std::vector regions; + ASSERT_TRUE(ParseProcMaps(kSpaces, ®ions)); + ASSERT_EQ(1u, regions.size()); + + EXPECT_EQ(0x00400000u, regions[0].start); + EXPECT_EQ(0x0040b000u, regions[0].end); + EXPECT_EQ(0x00002200u, regions[0].offset); + EXPECT_EQ("/bin/space cat", regions[0].path); +} + +TEST(ProcMapsTest, NoNewline) { + static const char kNoSpaces[] = + "00400000-0040b000 r-xp 00002200 fc:00 794418 /bin/cat"; + + std::vector regions; + ASSERT_FALSE(ParseProcMaps(kNoSpaces, ®ions)); +} + +TEST(ProcMapsTest, NoPath) { + static const char kNoPath[] = + "00400000-0040b000 rw-p 00000000 00:00 0 \n"; + + std::vector regions; + ASSERT_TRUE(ParseProcMaps(kNoPath, ®ions)); + ASSERT_EQ(1u, regions.size()); + + EXPECT_EQ(0x00400000u, regions[0].start); + EXPECT_EQ(0x0040b000u, regions[0].end); + EXPECT_EQ(0x00000000u, regions[0].offset); + EXPECT_EQ("", regions[0].path); +} + +TEST(ProcMapsTest, Heap) { + static const char kHeap[] = + "022ac000-022cd000 rw-p 00000000 00:00 0 [heap]\n"; + + std::vector regions; + ASSERT_TRUE(ParseProcMaps(kHeap, ®ions)); + ASSERT_EQ(1u, regions.size()); + + EXPECT_EQ(0x022ac000u, regions[0].start); + EXPECT_EQ(0x022cd000u, regions[0].end); + EXPECT_EQ(0x00000000u, regions[0].offset); + EXPECT_EQ("[heap]", regions[0].path); +} + +#if defined(ARCH_CPU_32_BITS) +TEST(ProcMapsTest, Stack32) { + static const char kStack[] = + "beb04000-beb25000 rw-p 00000000 00:00 0 [stack]\n"; + + std::vector regions; + ASSERT_TRUE(ParseProcMaps(kStack, ®ions)); + ASSERT_EQ(1u, regions.size()); + + EXPECT_EQ(0xbeb04000u, regions[0].start); + EXPECT_EQ(0xbeb25000u, regions[0].end); + EXPECT_EQ(0x00000000u, regions[0].offset); + EXPECT_EQ("[stack]", regions[0].path); +} +#elif defined(ARCH_CPU_64_BITS) +TEST(ProcMapsTest, Stack64) { + static const char kStack[] = + "7fff69c5b000-7fff69c7d000 rw-p 00000000 00:00 0 [stack]\n"; + + std::vector regions; + ASSERT_TRUE(ParseProcMaps(kStack, ®ions)); + ASSERT_EQ(1u, regions.size()); + + EXPECT_EQ(0x7fff69c5b000u, regions[0].start); + EXPECT_EQ(0x7fff69c7d000u, regions[0].end); + EXPECT_EQ(0x00000000u, regions[0].offset); + EXPECT_EQ("[stack]", regions[0].path); +} +#endif + +TEST(ProcMapsTest, Multiple) { + static const char kMultiple[] = + "00400000-0040b000 r-xp 00000000 fc:00 794418 /bin/cat\n" + "0060a000-0060b000 r--p 0000a000 fc:00 794418 /bin/cat\n" + "0060b000-0060c000 rw-p 0000b000 fc:00 794418 /bin/cat\n"; + + std::vector regions; + ASSERT_TRUE(ParseProcMaps(kMultiple, ®ions)); + ASSERT_EQ(3u, regions.size()); + + EXPECT_EQ(0x00400000u, regions[0].start); + EXPECT_EQ(0x0040b000u, regions[0].end); + EXPECT_EQ(0x00000000u, regions[0].offset); + EXPECT_EQ("/bin/cat", regions[0].path); + + EXPECT_EQ(0x0060a000u, regions[1].start); + EXPECT_EQ(0x0060b000u, regions[1].end); + EXPECT_EQ(0x0000a000u, regions[1].offset); + EXPECT_EQ("/bin/cat", regions[1].path); + + EXPECT_EQ(0x0060b000u, regions[2].start); + EXPECT_EQ(0x0060c000u, regions[2].end); + EXPECT_EQ(0x0000b000u, regions[2].offset); + EXPECT_EQ("/bin/cat", regions[2].path); +} + +TEST(ProcMapsTest, Permissions) { + static struct { + const char* input; + uint8_t permissions; + } kTestCases[] = { + {"00400000-0040b000 ---s 00000000 fc:00 794418 /bin/cat\n", 0}, + {"00400000-0040b000 ---S 00000000 fc:00 794418 /bin/cat\n", 0}, + {"00400000-0040b000 r--s 00000000 fc:00 794418 /bin/cat\n", + MappedMemoryRegion::READ}, + {"00400000-0040b000 -w-s 00000000 fc:00 794418 /bin/cat\n", + MappedMemoryRegion::WRITE}, + {"00400000-0040b000 --xs 00000000 fc:00 794418 /bin/cat\n", + MappedMemoryRegion::EXECUTE}, + {"00400000-0040b000 rwxs 00000000 fc:00 794418 /bin/cat\n", + MappedMemoryRegion::READ | MappedMemoryRegion::WRITE | + MappedMemoryRegion::EXECUTE}, + {"00400000-0040b000 ---p 00000000 fc:00 794418 /bin/cat\n", + MappedMemoryRegion::PRIVATE}, + {"00400000-0040b000 r--p 00000000 fc:00 794418 /bin/cat\n", + MappedMemoryRegion::READ | MappedMemoryRegion::PRIVATE}, + {"00400000-0040b000 -w-p 00000000 fc:00 794418 /bin/cat\n", + MappedMemoryRegion::WRITE | MappedMemoryRegion::PRIVATE}, + {"00400000-0040b000 --xp 00000000 fc:00 794418 /bin/cat\n", + MappedMemoryRegion::EXECUTE | MappedMemoryRegion::PRIVATE}, + {"00400000-0040b000 rwxp 00000000 fc:00 794418 /bin/cat\n", + MappedMemoryRegion::READ | MappedMemoryRegion::WRITE | + MappedMemoryRegion::EXECUTE | MappedMemoryRegion::PRIVATE}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) { + SCOPED_TRACE( + butil::StringPrintf("kTestCases[%zu] = %s", i, kTestCases[i].input)); + + std::vector regions; + EXPECT_TRUE(ParseProcMaps(kTestCases[i].input, ®ions)); + EXPECT_EQ(1u, regions.size()); + if (regions.empty()) + continue; + EXPECT_EQ(kTestCases[i].permissions, regions[0].permissions); + } +} + +/* +TEST(ProcMapsTest, ReadProcMaps) { + std::string proc_maps; + ASSERT_TRUE(ReadProcMaps(&proc_maps)); + + std::vector regions; + ASSERT_TRUE(ParseProcMaps(proc_maps, ®ions)); + ASSERT_FALSE(regions.empty()); + + // We should be able to find both the current executable as well as the stack + // mapped into memory. Use the address of |proc_maps| as a way of finding the + // stack. + FilePath exe_path; + EXPECT_TRUE(PathService::Get(FILE_EXE, &exe_path)); + uintptr_t address = reinterpret_cast(&proc_maps); + bool found_exe = false; + bool found_stack = false; + bool found_address = false; + for (size_t i = 0; i < regions.size(); ++i) { + if (regions[i].path == exe_path.value()) { + // It's OK to find the executable mapped multiple times as there'll be + // multiple sections (e.g., text, data). + found_exe = true; + } + + if (regions[i].path == "[stack]") { + // Only check if |address| lies within the real stack when not running + // Valgrind, otherwise |address| will be on a stack that Valgrind creates. + if (!RunningOnValgrind()) { + EXPECT_GE(address, regions[i].start); + EXPECT_LT(address, regions[i].end); + } + + EXPECT_TRUE(regions[i].permissions & MappedMemoryRegion::READ); + EXPECT_TRUE(regions[i].permissions & MappedMemoryRegion::WRITE); + EXPECT_FALSE(regions[i].permissions & MappedMemoryRegion::EXECUTE); + EXPECT_TRUE(regions[i].permissions & MappedMemoryRegion::PRIVATE); + EXPECT_FALSE(found_stack) << "Found duplicate stacks"; + found_stack = true; + } + + if (address >= regions[i].start && address < regions[i].end) { + EXPECT_FALSE(found_address) << "Found same address in multiple regions"; + found_address = true; + } + } + + EXPECT_TRUE(found_exe); + EXPECT_TRUE(found_stack); + EXPECT_TRUE(found_address); +} +*/ + +TEST(ProcMapsTest, ReadProcMapsNonEmptyString) { + std::string old_string("I forgot to clear the string"); + std::string proc_maps(old_string); + ASSERT_TRUE(ReadProcMaps(&proc_maps)); + EXPECT_EQ(std::string::npos, proc_maps.find(old_string)); +} + +TEST(ProcMapsTest, MissingFields) { + static const char* kTestCases[] = { + "00400000\n", // Missing end + beyond. + "00400000-0040b000\n", // Missing perms + beyond. + "00400000-0040b000 r-xp\n", // Missing offset + beyond. + "00400000-0040b000 r-xp 00000000\n", // Missing device + beyond. + "00400000-0040b000 r-xp 00000000 fc:00\n", // Missing inode + beyond. + "00400000-0040b000 00000000 fc:00 794418 /bin/cat\n", // Missing perms. + "00400000-0040b000 r-xp fc:00 794418 /bin/cat\n", // Missing offset. + "00400000-0040b000 r-xp 00000000 fc:00 /bin/cat\n", // Missing inode. + "00400000 r-xp 00000000 fc:00 794418 /bin/cat\n", // Missing end. + "-0040b000 r-xp 00000000 fc:00 794418 /bin/cat\n", // Missing start. + "00400000-0040b000 r-xp 00000000 794418 /bin/cat\n", // Missing device. + }; + + for (size_t i = 0; i < arraysize(kTestCases); ++i) { + SCOPED_TRACE(butil::StringPrintf("kTestCases[%zu] = %s", i, kTestCases[i])); + std::vector regions; + EXPECT_FALSE(ParseProcMaps(kTestCases[i], ®ions)); + } +} + +TEST(ProcMapsTest, InvalidInput) { + static const char* kTestCases[] = { + "thisisal-0040b000 rwxp 00000000 fc:00 794418 /bin/cat\n", + "0040000d-linvalid rwxp 00000000 fc:00 794418 /bin/cat\n", + "00400000-0040b000 inpu 00000000 fc:00 794418 /bin/cat\n", + "00400000-0040b000 rwxp tforproc fc:00 794418 /bin/cat\n", + "00400000-0040b000 rwxp 00000000 ma:ps 794418 /bin/cat\n", + "00400000-0040b000 rwxp 00000000 fc:00 parse! /bin/cat\n", + }; + + for (size_t i = 0; i < arraysize(kTestCases); ++i) { + SCOPED_TRACE(butil::StringPrintf("kTestCases[%zu] = %s", i, kTestCases[i])); + std::vector regions; + EXPECT_FALSE(ParseProcMaps(kTestCases[i], ®ions)); + } +} + +TEST(ProcMapsTest, ParseProcMapsEmptyString) { + std::vector regions; + EXPECT_TRUE(ParseProcMaps("", ®ions)); + EXPECT_EQ(0ULL, regions.size()); +} + +// Testing a couple of remotely possible weird things in the input: +// - Line ending with \r\n or \n\r. +// - File name contains quotes. +// - File name has whitespaces. +TEST(ProcMapsTest, ParseProcMapsWeirdCorrectInput) { + std::vector regions; + const std::string kContents = + "00400000-0040b000 r-xp 00000000 fc:00 2106562 " + " /bin/cat\r\n" + "7f53b7dad000-7f53b7f62000 r-xp 00000000 fc:00 263011 " + " /lib/x86_64-linux-gnu/libc-2.15.so\n\r" + "7f53b816d000-7f53b818f000 r-xp 00000000 fc:00 264284 " + " /lib/x86_64-linux-gnu/ld-2.15.so\n" + "7fff9c7ff000-7fff9c800000 r-xp 00000000 00:00 0 " + " \"vd so\"\n" + "ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 " + " [vsys call]\n"; + EXPECT_TRUE(ParseProcMaps(kContents, ®ions)); + EXPECT_EQ(5ULL, regions.size()); + EXPECT_EQ("/bin/cat", regions[0].path); + EXPECT_EQ("/lib/x86_64-linux-gnu/libc-2.15.so", regions[1].path); + EXPECT_EQ("/lib/x86_64-linux-gnu/ld-2.15.so", regions[2].path); + EXPECT_EQ("\"vd so\"", regions[3].path); + EXPECT_EQ("[vsys call]", regions[4].path); +} + +} // namespace debug +} // namespace butil diff --git a/test/rand_util_unittest.cc b/test/rand_util_unittest.cc new file mode 100644 index 0000000..eba1842 --- /dev/null +++ b/test/rand_util_unittest.cc @@ -0,0 +1,218 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/rand_util.h" +#include "butil/fast_rand.h" +#include "butil/time.h" +#include +#include + +#include "butil/logging.h" +#include "butil/memory/scoped_ptr.h" +#include "butil/time/time.h" +#include + +namespace { + +const int kIntMin = std::numeric_limits::min(); +const int kIntMax = std::numeric_limits::max(); + +} // namespace + +TEST(RandUtilTest, Sanity) { + EXPECT_EQ(butil::RandInt(0, 0), 0); + EXPECT_EQ(butil::RandInt(kIntMin, kIntMin), kIntMin); + EXPECT_EQ(butil::RandInt(kIntMax, kIntMax), kIntMax); + + for (int i = 0; i < 10; ++i) { + uint64_t value = butil::fast_rand_in( + (uint64_t)0, std::numeric_limits::max()); + if (value != std::numeric_limits::min() && + value != std::numeric_limits::max()) { + break; + } else { + EXPECT_NE(9, i) << "Never meet random except min/max of uint64"; + } + } + for (int i = 0; i < 10; ++i) { + int64_t value = butil::fast_rand_in( + std::numeric_limits::min(), + std::numeric_limits::max()); + if (value != std::numeric_limits::min() && + value != std::numeric_limits::max()) { + break; + } else { + EXPECT_NE(9, i) << "Never meet random except min/max of int64"; + } + } + EXPECT_EQ(butil::fast_rand_in(-1, -1), -1); + EXPECT_EQ(butil::fast_rand_in(1, 1), 1); + EXPECT_EQ(butil::fast_rand_in(0, 0), 0); + EXPECT_EQ(butil::fast_rand_in(std::numeric_limits::min(), + std::numeric_limits::min()), + std::numeric_limits::min()); + EXPECT_EQ(butil::fast_rand_in(std::numeric_limits::max(), + std::numeric_limits::max()), + std::numeric_limits::max()); + EXPECT_EQ(butil::fast_rand_in(std::numeric_limits::min(), + std::numeric_limits::min()), + std::numeric_limits::min()); + EXPECT_EQ(butil::fast_rand_in(std::numeric_limits::max(), + std::numeric_limits::max()), + std::numeric_limits::max()); +} + +TEST(RandUtilTest, RandDouble) { + // Force 64-bit precision, making sure we're not in a 80-bit FPU register. + volatile double number = butil::RandDouble(); + EXPECT_GT(1.0, number); + EXPECT_LE(0.0, number); + + volatile double number2 = butil::fast_rand_double(); + EXPECT_GT(1.0, number2); + EXPECT_LE(0.0, number2); +} + +TEST(RandUtilTest, RandBytes) { + const size_t buffer_size = 50; + char buffer[buffer_size]; + memset(buffer, 0, buffer_size); + butil::RandBytes(buffer, buffer_size); + std::sort(buffer, buffer + buffer_size); + // Probability of occurrence of less than 25 unique bytes in 50 random bytes + // is below 10^-25. + EXPECT_GT(std::unique(buffer, buffer + buffer_size) - buffer, 25); +} + +TEST(RandUtilTest, RandBytesAsString) { + std::string random_string = butil::RandBytesAsString(1); + EXPECT_EQ(1U, random_string.size()); + random_string = butil::RandBytesAsString(145); + EXPECT_EQ(145U, random_string.size()); + char accumulator = 0; + for (size_t i = 0; i < random_string.size(); ++i) { + accumulator |= random_string[i]; + } + // In theory this test can fail, but it won't before the universe dies of + // heat death. + EXPECT_NE(0, accumulator); +} + +// Make sure that it is still appropriate to use RandGenerator in conjunction +// with std::random_shuffle(). +TEST(RandUtilTest, RandGeneratorForRandomShuffle) { + EXPECT_EQ(butil::RandGenerator(1), 0U); + EXPECT_LE(std::numeric_limits::max(), + std::numeric_limits::max()); +} + +TEST(RandUtilTest, RandGeneratorIsUniform) { + // Verify that RandGenerator has a uniform distribution. This is a + // regression test that consistently failed when RandGenerator was + // implemented this way: + // + // return butil::RandUint64() % max; + // + // A degenerate case for such an implementation is e.g. a top of + // range that is 2/3rds of the way to MAX_UINT64, in which case the + // bottom half of the range would be twice as likely to occur as the + // top half. A bit of calculus care of jar@ shows that the largest + // measurable delta is when the top of the range is 3/4ths of the + // way, so that's what we use in the test. + const uint64_t kTopOfRange = (std::numeric_limits::max() / 4ULL) * 3ULL; + const uint64_t kExpectedAverage = kTopOfRange / 2ULL; + const uint64_t kAllowedVariance = kExpectedAverage / 50ULL; // +/- 2% + const int kMinAttempts = 1000; + const int kMaxAttempts = 1000000; + + for (int round = 0; round < 2; ++round) { + LOG(INFO) << "Use " << (round == 0 ? "RandUtil" : "fast_rand"); + double cumulative_average = 0.0; + int count = 0; + while (count < kMaxAttempts) { + uint64_t value = (round == 0 ? butil::RandGenerator(kTopOfRange) + : butil::fast_rand_less_than(kTopOfRange)); + cumulative_average = (count * cumulative_average + value) / (count + 1); + + // Don't quit too quickly for things to start converging, or we may have + // a false positive. + if (count > kMinAttempts && + double(kExpectedAverage - kAllowedVariance) < cumulative_average && + cumulative_average < double(kExpectedAverage + kAllowedVariance)) { + break; + } + + ++count; + } + + ASSERT_LT(count, kMaxAttempts) << "Expected average was " << + kExpectedAverage << ", average ended at " << cumulative_average; + } +} + +TEST(RandUtilTest, RandUint64ProducesBothValuesOfAllBits) { + // This tests to see that our underlying random generator is good + // enough, for some value of good enough. + const uint64_t kAllZeros = 0ULL; + const uint64_t kAllOnes = ~kAllZeros; + + for (int round = 0; round < 2; ++round) { + LOG(INFO) << "Use " << (round == 0 ? "RandUtil" : "fast_rand"); + uint64_t found_ones = kAllZeros; + uint64_t found_zeros = kAllOnes; + bool fail = true; + for (size_t i = 0; i < 1000; ++i) { + uint64_t value = (round == 0 ? butil::RandUint64() : butil::fast_rand()); + found_ones |= value; + found_zeros &= value; + + if (found_zeros == kAllZeros && found_ones == kAllOnes) { + fail = false; + break; + } + } + if (fail) { + FAIL() << "Didn't achieve all bit values in maximum number of tries."; + } + } +} + +// Benchmark test for RandBytes(). Disabled since it's intentionally slow and +// does not test anything that isn't already tested by the existing RandBytes() +// tests. +TEST(RandUtilTest, DISABLED_RandBytesPerf) { + // Benchmark the performance of |kTestIterations| of RandBytes() using a + // buffer size of |kTestBufferSize|. + const int kTestIterations = 10; + const size_t kTestBufferSize = 1 * 1024 * 1024; + + scoped_ptr buffer(new uint8_t[kTestBufferSize]); + const butil::TimeTicks now = butil::TimeTicks::HighResNow(); + for (int i = 0; i < kTestIterations; ++i) { + butil::RandBytes(buffer.get(), kTestBufferSize); + } + const butil::TimeTicks end = butil::TimeTicks::HighResNow(); + + LOG(INFO) << "RandBytes(" << kTestBufferSize << ") took: " + << (end - now).InMicroseconds() << "ms"; +} + +TEST(RandUtilTest, fast_rand_perf) { + const int kTestIterations = 1000000; + const int kRange = 17; + uint64_t s = 0; + butil::Timer tm; + tm.start(); + for (int i = 0; i < kTestIterations; ++i) { + s += butil::fast_rand_less_than(kRange); + } + tm.stop(); + LOG(INFO) << "Each fast_rand_less_than took " << tm.n_elapsed() / kTestIterations + << " ns, s=" << s +#if !defined(NDEBUG) + << " (debugging version)"; +#else + ; +#endif +} diff --git a/test/recordio_unittest.cpp b/test/recordio_unittest.cpp new file mode 100644 index 0000000..ed1dfec --- /dev/null +++ b/test/recordio_unittest.cpp @@ -0,0 +1,324 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include "butil/recordio.h" +#include "butil/fast_rand.h" +#include "butil/string_printf.h" +#include "butil/file_util.h" + +namespace { + +class StringReader : public butil::IReader { +public: + StringReader(const std::string& str, + bool report_eagain_on_end = false) + : _str(str) + , _offset(0) + , _report_eagain_on_end(report_eagain_on_end) {} + + ssize_t ReadV(const iovec* iov, int iovcnt) override { + size_t total_nc = 0; + for (int i = 0; i < iovcnt; ++i) { + void* dst = iov[i].iov_base; + size_t len = iov[i].iov_len; + size_t remain = _str.size() - _offset; + size_t nc = std::min(len, remain); + memcpy(dst, _str.data() + _offset, nc); + _offset += nc; + total_nc += nc; + if (_offset == _str.size()) { + break; + } + } + if (_report_eagain_on_end && total_nc == 0) { + errno = EAGAIN; + return -1; + } + return total_nc; + } +private: + std::string _str; + size_t _offset; + bool _report_eagain_on_end; +}; + +class StringWriter : public butil::IWriter { +public: + ssize_t WriteV(const iovec* iov, int iovcnt) override { + const size_t old_size = _str.size(); + for (int i = 0; i < iovcnt; ++i) { + _str.append((char*)iov[i].iov_base, iov[i].iov_len); + } + return _str.size() - old_size; + } + const std::string& str() const { return _str; } +private: + std::string _str; +}; + +TEST(RecordIOTest, empty_record) { + butil::Record r; + ASSERT_EQ((size_t)0, r.MetaCount()); + ASSERT_TRUE(r.Meta("foo") == NULL); + ASSERT_FALSE(r.RemoveMeta("foo")); + ASSERT_TRUE(r.Payload().empty()); + ASSERT_TRUE(r.MutablePayload()->empty()); +} + +TEST(RecordIOTest, manipulate_record) { + butil::Record r1; + ASSERT_EQ((size_t)0, r1.MetaCount()); + butil::IOBuf* foo_val = r1.MutableMeta("foo"); + ASSERT_EQ((size_t)1, r1.MetaCount()); + ASSERT_TRUE(foo_val->empty()); + foo_val->append("foo_data"); + ASSERT_EQ(foo_val, r1.MutableMeta("foo")); + ASSERT_EQ((size_t)1, r1.MetaCount()); + ASSERT_EQ("foo_data", *foo_val); + ASSERT_EQ(foo_val, r1.Meta("foo")); + + butil::IOBuf* bar_val = r1.MutableMeta("bar"); + ASSERT_EQ((size_t)2, r1.MetaCount()); + ASSERT_TRUE(bar_val->empty()); + bar_val->append("bar_data"); + ASSERT_EQ(bar_val, r1.MutableMeta("bar")); + ASSERT_EQ((size_t)2, r1.MetaCount()); + ASSERT_EQ("bar_data", *bar_val); + ASSERT_EQ(bar_val, r1.Meta("bar")); + + butil::Record r2 = r1; + + ASSERT_TRUE(r1.RemoveMeta("foo")); + ASSERT_EQ((size_t)1, r1.MetaCount()); + ASSERT_TRUE(r1.Meta("foo") == NULL); + + ASSERT_EQ(foo_val, r2.Meta("foo")); + ASSERT_EQ("foo_data", *foo_val); +} + +TEST(RecordIOTest, invalid_name) { + char name[258]; + for (size_t i = 0; i < sizeof(name); ++i) { + name[i] = 'a'; + } + name[sizeof(name) - 1] = 0; + butil::Record r; + ASSERT_EQ(NULL, r.MutableMeta(name)); +} + +TEST(RecordIOTest, write_read_basic) { + StringWriter sw; + butil::RecordWriter rw(&sw); + + butil::Record src; + ASSERT_EQ(0, rw.Write(src)); + + butil::IOBuf* foo_val = src.MutableMeta("foo"); + foo_val->append("foo_data"); + ASSERT_EQ(0, rw.Write(src)); + + butil::IOBuf* bar_val = src.MutableMeta("bar"); + bar_val->append("bar_data"); + ASSERT_EQ(0, rw.Write(src)); + + src.MutablePayload()->append("payload_data"); + ASSERT_EQ(0, rw.Write(src)); + + ASSERT_EQ(0, rw.Flush()); + std::cout << "len=" << sw.str().size() + << " content=" << butil::PrintedAsBinary(sw.str(), 256) << std::endl; + + StringReader sr(sw.str()); + butil::RecordReader rr(&sr); + butil::Record r1; + ASSERT_TRUE(rr.ReadNext(&r1)); + ASSERT_EQ(0, rr.last_error()); + ASSERT_EQ((size_t)0, r1.MetaCount()); + ASSERT_TRUE(r1.Payload().empty()); + + butil::Record r2; + ASSERT_TRUE(rr.ReadNext(&r2)); + ASSERT_EQ(0, rr.last_error()); + ASSERT_EQ((size_t)1, r2.MetaCount()); + ASSERT_EQ("foo", r2.MetaAt(0).name); + ASSERT_EQ("foo_data", *r2.MetaAt(0).data); + ASSERT_TRUE(r2.Payload().empty()); + + butil::Record r3; + ASSERT_TRUE(rr.ReadNext(&r3)); + ASSERT_EQ(0, rr.last_error()); + ASSERT_EQ((size_t)2, r3.MetaCount()); + ASSERT_EQ("foo", r3.MetaAt(0).name); + ASSERT_EQ("foo_data", *r3.MetaAt(0).data); + ASSERT_EQ("bar", r3.MetaAt(1).name); + ASSERT_EQ("bar_data", *r3.MetaAt(1).data); + ASSERT_TRUE(r3.Payload().empty()); + + butil::Record r4; + ASSERT_TRUE(rr.ReadNext(&r4)); + ASSERT_EQ(0, rr.last_error()); + ASSERT_EQ((size_t)2, r4.MetaCount()); + ASSERT_EQ("foo", r4.MetaAt(0).name); + ASSERT_EQ("foo_data", *r4.MetaAt(0).data); + ASSERT_EQ("bar", r4.MetaAt(1).name); + ASSERT_EQ("bar_data", *r4.MetaAt(1).data); + ASSERT_EQ("payload_data", r4.Payload()); + + ASSERT_FALSE(rr.ReadNext(NULL)); + ASSERT_EQ((int)butil::RecordReader::END_OF_READER, rr.last_error()); + ASSERT_EQ(sw.str().size(), rr.offset()); +} + +TEST(RecordIOTest, incomplete_reader) { + StringWriter sw; + butil::RecordWriter rw(&sw); + + butil::Record src; + butil::IOBuf* foo_val = src.MutableMeta("foo"); + foo_val->append("foo_data"); + ASSERT_EQ(0, rw.Write(src)); + + butil::IOBuf* bar_val = src.MutableMeta("bar"); + bar_val->append("bar_data"); + ASSERT_EQ(0, rw.Write(src)); + + ASSERT_EQ(0, rw.Flush()); + std::string data = sw.str(); + std::cout << "len=" << data.size() + << " content=" << butil::PrintedAsBinary(data, 256) << std::endl; + + StringReader sr(data, true); + butil::RecordReader rr(&sr); + + butil::Record r2; + ASSERT_TRUE(rr.ReadNext(&r2)); + ASSERT_EQ(0, rr.last_error()); + ASSERT_EQ((size_t)1, r2.MetaCount()); + ASSERT_EQ("foo", r2.MetaAt(0).name); + ASSERT_EQ("foo_data", *r2.MetaAt(0).data); + ASSERT_TRUE(r2.Payload().empty()); + + butil::Record r3; + ASSERT_TRUE(rr.ReadNext(&r3)); + ASSERT_EQ(0, rr.last_error()); + ASSERT_EQ((size_t)2, r3.MetaCount()); + ASSERT_EQ("foo", r3.MetaAt(0).name); + ASSERT_EQ("foo_data", *r3.MetaAt(0).data); + ASSERT_EQ("bar", r3.MetaAt(1).name); + ASSERT_EQ("bar_data", *r3.MetaAt(1).data); + ASSERT_TRUE(r3.Payload().empty()); + + ASSERT_FALSE(rr.ReadNext(NULL)); + ASSERT_EQ(EAGAIN, rr.last_error()); + ASSERT_EQ(sw.str().size(), rr.offset()); +} + +static std::string rand_string(int min_len, int max_len) { + const int len = butil::fast_rand_in(min_len, max_len); + std::string str; + str.reserve(len); + for (int i = 0; i < len; ++i) { + str.push_back(butil::fast_rand_in('a', 'Z')); + } + return str; +} + +TEST(RecordIOTest, write_read_random) { + StringWriter sw; + butil::RecordWriter rw(&sw); + + const int N = 1024; + std::vector> name_value_list; + size_t nbytes = 0; + std::map breaking_offsets; + for (int i = 0; i < N; ++i) { + butil::Record src; + std::string value = rand_string(10, 20); + std::string name = butil::string_printf("name_%d_", i) + value; + src.MutableMeta(name)->append(value); + ASSERT_EQ(0, rw.Write(src)); + if (butil::fast_rand_less_than(70) == 0) { + breaking_offsets[i] = nbytes; + } else { + name_value_list.push_back(std::make_pair(name, value)); + } + nbytes += src.ByteSize(); + } + ASSERT_EQ(0, rw.Flush()); + std::string str = sw.str(); + ASSERT_EQ(nbytes, str.size()); + // break some records + int break_idx = 0; + for (auto it = breaking_offsets.begin(); it != breaking_offsets.end(); ++it) { + switch (break_idx++ % 10) { + case 0: + str[it->second] = 'r'; + break; + case 1: + str[it->second + 1] = 'd'; + break; + case 2: + str[it->second + 2] = 'i'; + break; + case 3: + str[it->second + 3] = 'o'; + break; + case 4: + ++str[it->second + 4]; + break; + case 5: + str[it->second + 4] = 8; + break; + case 6: + ++str[it->second + 5]; + break; + case 7: + ++str[it->second + 6]; + break; + case 8: + ++str[it->second + 7]; + break; + case 9: + ++str[it->second + 8]; + break; + default: + ASSERT_TRUE(false) << "never"; + } + } + ASSERT_EQ((size_t)N - breaking_offsets.size(), name_value_list.size()); + std::cout << "sw.size=" << str.size() + << " nbreak=" << breaking_offsets.size() << std::endl; + + StringReader sr(str); + ASSERT_LT(0, butil::WriteFile(butil::FilePath("recordio_ref.io"), str.data(), str.size())); + butil::RecordReader rr(&sr); + size_t j = 0; + butil::Record r; + for (; rr.ReadNext(&r); ++j) { + ASSERT_LT(j, name_value_list.size()); + ASSERT_EQ((size_t)1, r.MetaCount()); + ASSERT_EQ(name_value_list[j].first, r.MetaAt(0).name) << j; + ASSERT_EQ(name_value_list[j].second, *r.MetaAt(0).data); + } + ASSERT_EQ((int)butil::RecordReader::END_OF_READER, rr.last_error()); + ASSERT_EQ(j, name_value_list.size()); + ASSERT_LE(str.size() - rr.offset(), 3u); +} + +} // namespace diff --git a/test/ref_counted_memory_unittest.cc b/test/ref_counted_memory_unittest.cc new file mode 100644 index 0000000..c9bf40b --- /dev/null +++ b/test/ref_counted_memory_unittest.cc @@ -0,0 +1,89 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/memory/ref_counted_memory.h" + +#include + +namespace butil { + +TEST(RefCountedMemoryUnitTest, RefCountedStaticMemory) { + scoped_refptr mem = new RefCountedStaticMemory( + "static mem00", 10); + + EXPECT_EQ(10U, mem->size()); + EXPECT_EQ("static mem", std::string(mem->front_as(), mem->size())); +} + +TEST(RefCountedMemoryUnitTest, RefCountedBytes) { + std::vector data; + data.push_back(45); + data.push_back(99); + scoped_refptr mem = RefCountedBytes::TakeVector(&data); + + EXPECT_EQ(0U, data.size()); + + EXPECT_EQ(2U, mem->size()); + EXPECT_EQ(45U, mem->front()[0]); + EXPECT_EQ(99U, mem->front()[1]); + + scoped_refptr mem2; + { + unsigned char data2[] = { 12, 11, 99 }; + mem2 = new RefCountedBytes(data2, 3); + } + EXPECT_EQ(3U, mem2->size()); + EXPECT_EQ(12U, mem2->front()[0]); + EXPECT_EQ(11U, mem2->front()[1]); + EXPECT_EQ(99U, mem2->front()[2]); +} + +TEST(RefCountedMemoryUnitTest, RefCountedString) { + std::string s("destroy me"); + scoped_refptr mem = RefCountedString::TakeString(&s); + + EXPECT_EQ(0U, s.size()); + + EXPECT_EQ(10U, mem->size()); + EXPECT_EQ('d', mem->front()[0]); + EXPECT_EQ('e', mem->front()[1]); +} + +TEST(RefCountedMemoryUnitTest, RefCountedMallocedMemory) { + void* data = malloc(6); + memcpy(data, "hello", 6); + + scoped_refptr mem = new RefCountedMallocedMemory(data, 6); + + EXPECT_EQ(6U, mem->size()); + EXPECT_EQ(0, memcmp("hello", mem->front(), 6)); +} + +TEST(RefCountedMemoryUnitTest, Equals) { + std::string s1("same"); + scoped_refptr mem1 = RefCountedString::TakeString(&s1); + + std::vector d2; + d2.push_back('s'); + d2.push_back('a'); + d2.push_back('m'); + d2.push_back('e'); + scoped_refptr mem2 = RefCountedBytes::TakeVector(&d2); + + EXPECT_TRUE(mem1->Equals(mem2)); + + std::string s3("diff"); + scoped_refptr mem3 = RefCountedString::TakeString(&s3); + + EXPECT_FALSE(mem1->Equals(mem3)); + EXPECT_FALSE(mem2->Equals(mem3)); +} + +TEST(RefCountedMemoryUnitTest, EqualsNull) { + std::string s("str"); + scoped_refptr mem = RefCountedString::TakeString(&s); + EXPECT_FALSE(mem->Equals(NULL)); +} + +} // namespace butil diff --git a/test/ref_counted_unittest.cc b/test/ref_counted_unittest.cc new file mode 100644 index 0000000..7415ba3 --- /dev/null +++ b/test/ref_counted_unittest.cc @@ -0,0 +1,97 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/memory/ref_counted.h" +#include + +namespace { + +class SelfAssign : public butil::RefCounted { + friend class butil::RefCounted; + + ~SelfAssign() {} +}; + +class CheckDerivedMemberAccess : public scoped_refptr { + public: + CheckDerivedMemberAccess() { + // This shouldn't compile if we don't have access to the member variable. + SelfAssign** pptr = &ptr_; + EXPECT_EQ(*pptr, ptr_); + } +}; + +class ScopedRefPtrToSelf : public butil::RefCounted { + public: + ScopedRefPtrToSelf() : self_ptr_(this) {} + + static bool was_destroyed() { return was_destroyed_; } + + void SelfDestruct() { self_ptr_ = NULL; } + + private: + friend class butil::RefCounted; + ~ScopedRefPtrToSelf() { was_destroyed_ = true; } + + static bool was_destroyed_; + + scoped_refptr self_ptr_; +}; + +bool ScopedRefPtrToSelf::was_destroyed_ = false; + +} // end namespace + +TEST(RefCountedUnitTest, TestSelfAssignment) { + SelfAssign* p = new SelfAssign; + scoped_refptr var(p); + var = var; + EXPECT_EQ(var.get(), p); +} + +TEST(RefCountedUnitTest, ScopedRefPtrMemberAccess) { + CheckDerivedMemberAccess check; +} + +TEST(RefCountedUnitTest, ScopedRefPtrToSelf) { + ScopedRefPtrToSelf* check = new ScopedRefPtrToSelf(); + EXPECT_FALSE(ScopedRefPtrToSelf::was_destroyed()); + check->SelfDestruct(); + EXPECT_TRUE(ScopedRefPtrToSelf::was_destroyed()); +} + +TEST(RefCountedUnitTest, ScopedRefPtrBooleanOperations) { + scoped_refptr p1 = new SelfAssign; + scoped_refptr p2; + + EXPECT_TRUE(p1); + EXPECT_FALSE(!p1); + + EXPECT_TRUE(!p2); + EXPECT_FALSE(p2); + + EXPECT_NE(p1, p2); + + SelfAssign* raw_p = new SelfAssign; + p2 = raw_p; + EXPECT_NE(p1, p2); + EXPECT_EQ(raw_p, p2); + + p2 = p1; + EXPECT_NE(raw_p, p2); + EXPECT_EQ(p1, p2); +} + +TEST(RefCountedUnitTest, ScopedRefPtrMoveCtor) +{ + scoped_refptr p1 = new SelfAssign; + EXPECT_TRUE(p1); + + scoped_refptr p2(std::move(p1)); + EXPECT_TRUE(p2); + EXPECT_FALSE(p1); + + scoped_refptr p3(std::move(p1)); + EXPECT_FALSE(p3); +} diff --git a/test/repeated.proto b/test/repeated.proto new file mode 100644 index 0000000..c25a08f --- /dev/null +++ b/test/repeated.proto @@ -0,0 +1,25 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +message Dummy {} + +message RepeatedMessage { + repeated int32 ints = 1; + repeated string strings = 2; + repeated Dummy msgs = 3; +} diff --git a/test/resource_pool_unittest.cpp b/test/resource_pool_unittest.cpp new file mode 100644 index 0000000..9a56ff3 --- /dev/null +++ b/test/resource_pool_unittest.cpp @@ -0,0 +1,424 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/time.h" +#include "butil/macros.h" +#include "butil/fast_rand.h" + +#define BAIDU_CLEAR_RESOURCE_POOL_AFTER_ALL_THREADS_QUIT +#include "butil/resource_pool.h" + +namespace { +struct MyObject {}; + +int nfoo_dtor = 0; +struct Foo { + Foo() { + x = butil::fast_rand() % 2; + } + ~Foo() { + ++nfoo_dtor; + } + int x; +}; +} + +namespace butil { +template <> struct ResourcePoolBlockMaxSize { + static const size_t value = 128; +}; + +template <> struct ResourcePoolBlockMaxItem { + static const size_t value = 3; +}; + +template <> struct ResourcePoolFreeChunkMaxItem { + static size_t value() { return 5; } +}; + +template <> struct ResourcePoolValidator { + static bool validate(const Foo* foo) { + return foo->x != 0; + } +}; + +} + +namespace { +using namespace butil; + +class ResourcePoolTest : public ::testing::Test{ +protected: + ResourcePoolTest(){ + }; + virtual ~ResourcePoolTest(){}; + virtual void SetUp() { + srand(time(0)); + }; + virtual void TearDown() { + }; +}; + +TEST_F(ResourcePoolTest, atomic_array_init) { + for (int i = 0; i < 2; ++i) { + if (i == 0) { + butil::atomic a[2]; + a[0] = 1; + // The folowing will cause compile error with gcc3.4.5 and the + // reason is unknown + // a[1] = 2; + } else if (i == 2) { + butil::atomic a[2]; + ASSERT_EQ(0, a[0]); + ASSERT_EQ(0, a[1]); + } + } +} + +int nc = 0; +int nd = 0; +std::set ptr_set; +struct YellObj { + YellObj() { + ++nc; + ptr_set.insert(this); + printf("Created %p\n", this); + } + ~YellObj() { + ++nd; + ptr_set.erase(this); + printf("Destroyed %p\n", this); + } + char _dummy[96]; +}; + +TEST_F(ResourcePoolTest, change_config) { + int a[2]; + printf("%lu\n", ARRAY_SIZE(a)); + + ResourcePoolInfo info = describe_resources(); + ResourcePoolInfo zero_info = { 0, 0, 0, 0, 3, 3, 0 }; + ASSERT_EQ(0, memcmp(&info, &zero_info, sizeof(info))); + + ResourceId id = { 0 }; + get_resource(&id); + std::cout << describe_resources() << std::endl; + return_resource(id); + std::cout << describe_resources() << std::endl; +} + +struct NonDefaultCtorObject { + explicit NonDefaultCtorObject(int value) : _value(value) {} + NonDefaultCtorObject(int value, int dummy) : _value(value + dummy) {} + + int _value; +}; + +TEST_F(ResourcePoolTest, sanity) { + ptr_set.clear(); + ResourceId id0 = { 0 }; + get_resource(&id0, 10); + ASSERT_EQ(10, address_resource(id0)->_value); + get_resource(&id0, 100, 30); + ASSERT_EQ(130, address_resource(id0)->_value); + + printf("BLOCK_NITEM=%lu\n", ResourcePool::BLOCK_NITEM); + + nc = 0; + nd = 0; + { + ResourceId id1; + YellObj* o1 = get_resource(&id1); + ASSERT_TRUE(o1); + ASSERT_EQ(o1, address_resource(id1)); + + ASSERT_EQ(1, nc); + ASSERT_EQ(0, nd); + + ResourceId id2; + YellObj* o2 = get_resource(&id2); + ASSERT_TRUE(o2); + ASSERT_EQ(o2, address_resource(id2)); + + ASSERT_EQ(2, nc); + ASSERT_EQ(0, nd); + + return_resource(id1); + ASSERT_EQ(2, nc); + ASSERT_EQ(0, nd); + + return_resource(id2); + ASSERT_EQ(2, nc); + ASSERT_EQ(0, nd); + } + ASSERT_EQ(0, nd); + + clear_resources(); + ASSERT_EQ(2, nd); + ASSERT_TRUE(ptr_set.empty()) << ptr_set.size(); +} + +TEST_F(ResourcePoolTest, validator) { + nfoo_dtor = 0; + int nfoo = 0; + for (int i = 0; i < 100; ++i) { + ResourceId id = { 0 }; + Foo* foo = get_resource(&id); + if (foo) { + ASSERT_EQ(1, foo->x); + ++nfoo; + } + } + ASSERT_EQ(nfoo + nfoo_dtor, 100); + ASSERT_EQ((size_t)nfoo, describe_resources().item_num); +} + +TEST_F(ResourcePoolTest, get_int) { + clear_resources(); + + // Perf of this test is affected by previous case. + const size_t N = 100000; + + butil::Timer tm; + ResourceId id; + + // warm up + if (get_resource(&id)) { + return_resource(id); + } + ASSERT_EQ(0UL, id); + delete (new int); + + tm.start(); + for (size_t i = 0; i < N; ++i) { + *get_resource(&id) = i; + } + tm.stop(); + printf("get a int takes %.1fns\n", tm.n_elapsed()/(double)N); + + tm.start(); + for (size_t i = 0; i < N; ++i) { + *(new int) = i; + } + tm.stop(); + printf("new a int takes %luns\n", tm.n_elapsed()/N); + + tm.start(); + for (size_t i = 0; i < N; ++i) { + id.value = i; + *ResourcePool::unsafe_address_resource(id) = i; + } + tm.stop(); + printf("unsafe_address a int takes %.1fns\n", tm.n_elapsed()/(double)N); + + tm.start(); + for (size_t i = 0; i < N; ++i) { + id.value = i; + *address_resource(id) = i; + } + tm.stop(); + printf("address a int takes %.1fns\n", tm.n_elapsed()/(double)N); + + std::cout << describe_resources() << std::endl; + clear_resources(); + std::cout << describe_resources() << std::endl; +} + + +struct SilentObj { + char buf[sizeof(YellObj)]; +}; + +TEST_F(ResourcePoolTest, get_perf) { + const size_t N = 10000; + std::vector new_list; + new_list.reserve(N); + ResourceId id; + + butil::Timer tm1, tm2; + + // warm up + if (get_resource(&id)) { + return_resource(id); + } + delete (new SilentObj); + + // Run twice, the second time will be must faster. + for (size_t j = 0; j < 2; ++j) { + + tm1.start(); + for (size_t i = 0; i < N; ++i) { + get_resource(&id); + } + tm1.stop(); + printf("get a SilentObj takes %luns\n", tm1.n_elapsed()/N); + //clear_resources(); // free all blocks + + tm2.start(); + for (size_t i = 0; i < N; ++i) { + new_list.push_back(new SilentObj); + } + tm2.stop(); + printf("new a SilentObj takes %luns\n", tm2.n_elapsed()/N); + for (size_t i = 0; i < new_list.size(); ++i) { + delete new_list[i]; + } + new_list.clear(); + } + + std::cout << describe_resources() << std::endl; +} + +struct D { int val[1]; }; + +void* get_and_return_int(void*) { + // Perf of this test is affected by previous case. + const size_t N = 100000; + std::vector > v; + v.reserve(N); + butil::Timer tm0, tm1, tm2; + ResourceId id = {0}; + D tmp = D(); + int sr = 0; + + // warm up + tm0.start(); + if (get_resource(&id)) { + return_resource(id); + } + tm0.stop(); + + printf("[%lu] warmup=%lu\n", pthread_self(), tm0.n_elapsed()); + + for (int j = 0; j < 5; ++j) { + v.clear(); + sr = 0; + + tm1.start(); + for (size_t i = 0; i < N; ++i) { + *get_resource(&id) = tmp; + v.push_back(id); + } + tm1.stop(); + + std::random_shuffle(v.begin(), v.end()); + + tm2.start(); + for (size_t i = 0; i < v.size(); ++i) { + sr += return_resource(v[i]); + } + tm2.stop(); + + if (0 != sr) { + printf("%d return_resource failed\n", sr); + } + + printf("[%lu:%d] get=%.1f return=%.1f\n", + pthread_self(), j, tm1.n_elapsed()/(double)N, tm2.n_elapsed()/(double)N); + } + return NULL; +} + +void* new_and_delete_int(void*) { + const size_t N = 100000; + std::vector v2; + v2.reserve(N); + butil::Timer tm0, tm1, tm2; + D tmp = D(); + + for (int j = 0; j < 3; ++j) { + v2.clear(); + + // warm up + delete (new D); + + tm1.start(); + for (size_t i = 0; i < N; ++i) { + D *p = new D; + *p = tmp; + v2.push_back(p); + } + tm1.stop(); + + std::random_shuffle(v2.begin(), v2.end()); + + tm2.start(); + for (size_t i = 0; i < v2.size(); ++i) { + delete v2[i]; + } + tm2.stop(); + + printf("[%lu:%d] new=%.1f delete=%.1f\n", + pthread_self(), j, tm1.n_elapsed()/(double)N, tm2.n_elapsed()/(double)N); + } + + return NULL; +} + +TEST_F(ResourcePoolTest, get_and_return_int_single_thread) { + get_and_return_int(NULL); + new_and_delete_int(NULL); +} + +TEST_F(ResourcePoolTest, get_and_return_int_multiple_threads) { + pthread_t tid[16]; + for (size_t i = 0; i < ARRAY_SIZE(tid); ++i) { + ASSERT_EQ(0, pthread_create(&tid[i], NULL, get_and_return_int, NULL)); + } + for (size_t i = 0; i < ARRAY_SIZE(tid); ++i) { + pthread_join(tid[i], NULL); + } + + pthread_t tid2[16]; + for (size_t i = 0; i < ARRAY_SIZE(tid2); ++i) { + ASSERT_EQ(0, pthread_create(&tid2[i], NULL, new_and_delete_int, NULL)); + } + for (size_t i = 0; i < ARRAY_SIZE(tid2); ++i) { + pthread_join(tid2[i], NULL); + } + + std::cout << describe_resources() << std::endl; + clear_resources(); + ResourcePoolInfo info = describe_resources(); + ResourcePoolInfo zero_info = { 0, 0, 0, 0, + ResourcePoolBlockMaxItem::value, + ResourcePoolBlockMaxItem::value, 0}; + ASSERT_EQ(0, memcmp(&info, &zero_info, sizeof(info))); +} + +TEST_F(ResourcePoolTest, verify_get) { + clear_resources(); + std::cout << describe_resources() << std::endl; + + std::vector > > v; + v.reserve(100000); + ResourceId id = { 0 }; + for (int i = 0; (size_t)i < v.capacity(); ++i) { + int* p = get_resource(&id); + *p = i; + v.push_back(std::make_pair(p, id)); + } + int i; + for (i = 0; (size_t)i < v.size() && *v[i].first == i; ++i); + ASSERT_EQ(v.size(), (size_t)i); + for (i = 0; (size_t)i < v.size() && v[i].second == (size_t)i; ++i); + ASSERT_EQ(v.size(), (size_t)i) << "i=" << i << ", " << v[i].second; + + clear_resources(); +} +} // namespace diff --git a/test/root_runfiles.bzl b/test/root_runfiles.bzl new file mode 100644 index 0000000..7d38b87 --- /dev/null +++ b/test/root_runfiles.bzl @@ -0,0 +1,79 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +"""A small Starlark rule that exposes files at the *runfiles workspace root*. + +Why this exists +--------------- +`bazel test` sets the test process cwd to `${TEST_SRCDIR}/${TEST_WORKSPACE}`, +which under bzlmod is `/_main/` -- the workspace root inside +the runfiles tree. Files declared via the standard `data` attribute on a +target in `//test` are placed under `_main/test/` (the rule's package +path), which is NOT the cwd. + +Several brpc tests (e.g. brpc_alpn_protocol_unittest, brpc_ssl_unittest, +brpc_protobuf_json_unittest) open data files such as `cert1.crt`, `cert1.key`, +`jsonout` by *plain relative paths* (this works under the CMake build because +file(COPY ...) places them next to the test binary). To make those same +relative paths work under Bazel without rewriting the test code, we need the +files to appear at `_main/`, i.e. directly at the cwd. + +A genrule cannot help here, because its `outs` must live in the genrule's own +package. The clean way is `ctx.runfiles(symlinks = {...})`, which places the +given files at arbitrary paths *relative to the workspace root inside the +runfiles tree* -- which is exactly the cwd of a Bazel-launched test. + +Beware: `ctx.runfiles` has TWO related dict parameters: + * symlinks -> paths are relative to // (the cwd) + * root_symlinks -> paths are relative to / (one level above cwd) +We want the first one. + +Usage +----- + load("//test:root_runfiles.bzl", "root_runfiles") + + root_runfiles( + name = "test_runfiles_root_data", + srcs = [ + "cert1.crt", + "cert1.key", + "jsonout", + ], + ) + +Then add `:test_runfiles_root_data` to a cc_test's `data` attribute. +""" + +def _root_runfiles_impl(ctx): + # Map each input file to its basename, placed at the workspace root inside + # the runfiles tree. That root is the cwd of a `bazel test` process, so + # tests can open the files via plain relative paths like "cert1.crt". + symlinks = {f.basename: f for f in ctx.files.srcs} + runfiles = ctx.runfiles(symlinks = symlinks) + return [DefaultInfo(runfiles = runfiles)] + +root_runfiles = rule( + implementation = _root_runfiles_impl, + attrs = { + "srcs": attr.label_list( + allow_files = True, + doc = "Files to expose at the workspace root inside the runfiles " + + "tree (i.e. the cwd of `bazel test`), keyed by basename.", + ), + }, + doc = "Exposes data files at the workspace root inside the runfiles tree " + + "(the cwd of `bazel test`), so tests can open them via plain " + + "relative paths.", +) diff --git a/test/run_tests.sh b/test/run_tests.sh new file mode 100755 index 0000000..ced0297 --- /dev/null +++ b/test/run_tests.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +# turn on coredumps +ulimit -c unlimited +rm core.* + +test_num=0 +failed_test="" +rc=0 +test_bins="test_butil test_bvar bthread*unittest brpc*unittest" +export ASAN_OPTIONS="detect_leaks=1:detect_stack_use_after_return=1" +for test_bin in $test_bins; do + test_num=$((test_num + 1)) + >&2 echo "[runtest] $test_bin" + ./$test_bin + # If ASan abort without detailed call stack of new/delete, + # try to disable fast_unwind_on_malloc, which would be a performance killer. + # ASAN_OPTIONS="fast_unwind_on_malloc=0:detect_leaks=0" ./$test_bin + rc=$? + if [ $rc -ne 0 ]; then + failed_test="$test_bin" + break; + fi +done +if [ $test_num -eq 0 ]; then + >&2 echo "[runtest] Cannot find any tests" + exit 1 +fi + +print_bt () { + # find newest core file + COREFILE=$(find . -name "core*" -type f -printf "%T@ %p\n" | sort -k 1 -n | cut -d' ' -f 2- | tail -n 1) + if [ ! -z "$COREFILE" ]; then + >&2 echo "corefile=$COREFILE prog=$1" + gdb -c "$COREFILE" $1 -ex "bt" -ex "thread apply all bt" -ex "set pagination 0" -batch; + fi +} + +if [ -z "$failed_test" ]; then + >&2 echo "[runtest] $test_num succeeded" +elif [ $test_num -gt 1 ]; then + print_bt $failed_test + >&2 echo "[runtest] '$failed_test' failed, $((test_num-1)) succeeded" +else + print_bt $failed_test + >&2 echo "[runtest] '$failed_test' failed" +fi +exit $rc diff --git a/test/safe_numerics_unittest.cc b/test/safe_numerics_unittest.cc new file mode 100644 index 0000000..53238e6 --- /dev/null +++ b/test/safe_numerics_unittest.cc @@ -0,0 +1,579 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#if defined(COMPILER_MSVC) && defined(ARCH_CPU_32_BITS) +#include +#endif +#include + +#include + +#include "butil/compiler_specific.h" +#include "butil/numerics/safe_conversions.h" +#include "butil/numerics/safe_math.h" +#include "butil/type_traits.h" +#include + +using std::numeric_limits; +using butil::CheckedNumeric; +using butil::checked_cast; +using butil::saturated_cast; +using butil::internal::MaxExponent; +using butil::internal::RANGE_VALID; +using butil::internal::RANGE_INVALID; +using butil::internal::RANGE_OVERFLOW; +using butil::internal::RANGE_UNDERFLOW; +using butil::enable_if; + +// MSVS 2013 ia32 may not reset the FPU between calculations, and the test +// framework masks the exceptions. So we just force a manual reset after NaN. +inline void ResetFloatingPointUnit() { +#if defined(COMPILER_MSVC) && defined(ARCH_CPU_32_BITS) + _mm_empty(); +#endif +} + +// Helper macros to wrap displaying the conversion types and line numbers. +#define TEST_EXPECTED_VALIDITY(expected, actual) \ + EXPECT_EQ(expected, CheckedNumeric(actual).validity()) \ + << "Result test: Value " << +(actual).ValueUnsafe() << " as " << dst \ + << " on line " << line; + +#define TEST_EXPECTED_VALUE(expected, actual) \ + EXPECT_EQ(static_cast(expected), \ + CheckedNumeric(actual).ValueUnsafe()) \ + << "Result test: Value " << +((actual).ValueUnsafe()) << " as " << dst \ + << " on line " << line; + +// Signed integer arithmetic. +template +static void TestSpecializedArithmetic( + const char* dst, + int line, + typename enable_if< + numeric_limits::is_integer&& numeric_limits::is_signed, + int>::type = 0) { + typedef numeric_limits DstLimits; + TEST_EXPECTED_VALIDITY(RANGE_OVERFLOW, + -CheckedNumeric(DstLimits::min())); + TEST_EXPECTED_VALIDITY(RANGE_OVERFLOW, + CheckedNumeric(DstLimits::min()).Abs()); + TEST_EXPECTED_VALUE(1, CheckedNumeric(-1).Abs()); + + TEST_EXPECTED_VALIDITY(RANGE_VALID, + CheckedNumeric(DstLimits::max()) + -1); + TEST_EXPECTED_VALIDITY(RANGE_UNDERFLOW, + CheckedNumeric(DstLimits::min()) + -1); + TEST_EXPECTED_VALIDITY( + RANGE_UNDERFLOW, + CheckedNumeric(-DstLimits::max()) + -DstLimits::max()); + + TEST_EXPECTED_VALIDITY(RANGE_UNDERFLOW, + CheckedNumeric(DstLimits::min()) - 1); + TEST_EXPECTED_VALIDITY(RANGE_VALID, + CheckedNumeric(DstLimits::min()) - -1); + TEST_EXPECTED_VALIDITY( + RANGE_OVERFLOW, + CheckedNumeric(DstLimits::max()) - -DstLimits::max()); + TEST_EXPECTED_VALIDITY( + RANGE_UNDERFLOW, + CheckedNumeric(-DstLimits::max()) - DstLimits::max()); + + TEST_EXPECTED_VALIDITY(RANGE_UNDERFLOW, + CheckedNumeric(DstLimits::min()) * 2); + + TEST_EXPECTED_VALIDITY(RANGE_OVERFLOW, + CheckedNumeric(DstLimits::min()) / -1); + TEST_EXPECTED_VALUE(0, CheckedNumeric(-1) / 2); + + // Modulus is legal only for integers. + TEST_EXPECTED_VALUE(0, CheckedNumeric() % 1); + TEST_EXPECTED_VALUE(0, CheckedNumeric(1) % 1); + TEST_EXPECTED_VALUE(-1, CheckedNumeric(-1) % 2); + TEST_EXPECTED_VALIDITY(RANGE_INVALID, CheckedNumeric(-1) % -2); + TEST_EXPECTED_VALUE(0, CheckedNumeric(DstLimits::min()) % 2); + TEST_EXPECTED_VALUE(1, CheckedNumeric(DstLimits::max()) % 2); + // Test all the different modulus combinations. + TEST_EXPECTED_VALUE(0, CheckedNumeric(1) % CheckedNumeric(1)); + TEST_EXPECTED_VALUE(0, 1 % CheckedNumeric(1)); + TEST_EXPECTED_VALUE(0, CheckedNumeric(1) % 1); + CheckedNumeric checked_dst = 1; + TEST_EXPECTED_VALUE(0, checked_dst %= 1); +} + +// Unsigned integer arithmetic. +template +static void TestSpecializedArithmetic( + const char* dst, + int line, + typename enable_if< + numeric_limits::is_integer && !numeric_limits::is_signed, + int>::type = 0) { + typedef numeric_limits DstLimits; + TEST_EXPECTED_VALIDITY(RANGE_VALID, -CheckedNumeric(DstLimits::min())); + TEST_EXPECTED_VALIDITY(RANGE_VALID, + CheckedNumeric(DstLimits::min()).Abs()); + TEST_EXPECTED_VALIDITY(RANGE_UNDERFLOW, + CheckedNumeric(DstLimits::min()) + -1); + TEST_EXPECTED_VALIDITY(RANGE_UNDERFLOW, + CheckedNumeric(DstLimits::min()) - 1); + TEST_EXPECTED_VALUE(0, CheckedNumeric(DstLimits::min()) * 2); + TEST_EXPECTED_VALUE(0, CheckedNumeric(1) / 2); + + // Modulus is legal only for integers. + TEST_EXPECTED_VALUE(0, CheckedNumeric() % 1); + TEST_EXPECTED_VALUE(0, CheckedNumeric(1) % 1); + TEST_EXPECTED_VALUE(1, CheckedNumeric(1) % 2); + TEST_EXPECTED_VALUE(0, CheckedNumeric(DstLimits::min()) % 2); + TEST_EXPECTED_VALUE(1, CheckedNumeric(DstLimits::max()) % 2); + // Test all the different modulus combinations. + TEST_EXPECTED_VALUE(0, CheckedNumeric(1) % CheckedNumeric(1)); + TEST_EXPECTED_VALUE(0, 1 % CheckedNumeric(1)); + TEST_EXPECTED_VALUE(0, CheckedNumeric(1) % 1); + CheckedNumeric checked_dst = 1; + TEST_EXPECTED_VALUE(0, checked_dst %= 1); +} + +// Floating point arithmetic. +template +void TestSpecializedArithmetic( + const char* dst, + int line, + typename enable_if::is_iec559, int>::type = 0) { + typedef numeric_limits DstLimits; + TEST_EXPECTED_VALIDITY(RANGE_VALID, -CheckedNumeric(DstLimits::min())); + + TEST_EXPECTED_VALIDITY(RANGE_VALID, + CheckedNumeric(DstLimits::min()).Abs()); + TEST_EXPECTED_VALUE(1, CheckedNumeric(-1).Abs()); + + TEST_EXPECTED_VALIDITY(RANGE_VALID, + CheckedNumeric(DstLimits::min()) + -1); + TEST_EXPECTED_VALIDITY(RANGE_VALID, + CheckedNumeric(DstLimits::max()) + 1); + TEST_EXPECTED_VALIDITY( + RANGE_UNDERFLOW, + CheckedNumeric(-DstLimits::max()) + -DstLimits::max()); + + TEST_EXPECTED_VALIDITY( + RANGE_OVERFLOW, + CheckedNumeric(DstLimits::max()) - -DstLimits::max()); + TEST_EXPECTED_VALIDITY( + RANGE_UNDERFLOW, + CheckedNumeric(-DstLimits::max()) - DstLimits::max()); + + TEST_EXPECTED_VALIDITY(RANGE_VALID, + CheckedNumeric(DstLimits::min()) * 2); + + TEST_EXPECTED_VALUE(-0.5, CheckedNumeric(-1.0) / 2); + EXPECT_EQ(static_cast(1.0), CheckedNumeric(1.0).ValueFloating()); +} + +// Generic arithmetic tests. +template +static void TestArithmetic(const char* dst, int line) { + typedef numeric_limits DstLimits; + + EXPECT_EQ(true, CheckedNumeric().IsValid()); + EXPECT_EQ(false, + CheckedNumeric(CheckedNumeric(DstLimits::max()) * + DstLimits::max()).IsValid()); + EXPECT_EQ(static_cast(0), CheckedNumeric().ValueOrDie()); + EXPECT_EQ(static_cast(0), CheckedNumeric().ValueOrDefault(1)); + EXPECT_EQ(static_cast(1), + CheckedNumeric(CheckedNumeric(DstLimits::max()) * + DstLimits::max()).ValueOrDefault(1)); + + // Test the operator combinations. + TEST_EXPECTED_VALUE(2, CheckedNumeric(1) + CheckedNumeric(1)); + TEST_EXPECTED_VALUE(0, CheckedNumeric(1) - CheckedNumeric(1)); + TEST_EXPECTED_VALUE(1, CheckedNumeric(1) * CheckedNumeric(1)); + TEST_EXPECTED_VALUE(1, CheckedNumeric(1) / CheckedNumeric(1)); + TEST_EXPECTED_VALUE(2, 1 + CheckedNumeric(1)); + TEST_EXPECTED_VALUE(0, 1 - CheckedNumeric(1)); + TEST_EXPECTED_VALUE(1, 1 * CheckedNumeric(1)); + TEST_EXPECTED_VALUE(1, 1 / CheckedNumeric(1)); + TEST_EXPECTED_VALUE(2, CheckedNumeric(1) + 1); + TEST_EXPECTED_VALUE(0, CheckedNumeric(1) - 1); + TEST_EXPECTED_VALUE(1, CheckedNumeric(1) * 1); + TEST_EXPECTED_VALUE(1, CheckedNumeric(1) / 1); + CheckedNumeric checked_dst = 1; + TEST_EXPECTED_VALUE(2, checked_dst += 1); + checked_dst = 1; + TEST_EXPECTED_VALUE(0, checked_dst -= 1); + checked_dst = 1; + TEST_EXPECTED_VALUE(1, checked_dst *= 1); + checked_dst = 1; + TEST_EXPECTED_VALUE(1, checked_dst /= 1); + + // Generic negation. + TEST_EXPECTED_VALUE(0, -CheckedNumeric()); + TEST_EXPECTED_VALUE(-1, -CheckedNumeric(1)); + TEST_EXPECTED_VALUE(1, -CheckedNumeric(-1)); + TEST_EXPECTED_VALUE(static_cast(DstLimits::max() * -1), + -CheckedNumeric(DstLimits::max())); + + // Generic absolute value. + TEST_EXPECTED_VALUE(0, CheckedNumeric().Abs()); + TEST_EXPECTED_VALUE(1, CheckedNumeric(1).Abs()); + TEST_EXPECTED_VALUE(DstLimits::max(), + CheckedNumeric(DstLimits::max()).Abs()); + + // Generic addition. + TEST_EXPECTED_VALUE(1, (CheckedNumeric() + 1)); + TEST_EXPECTED_VALUE(2, (CheckedNumeric(1) + 1)); + TEST_EXPECTED_VALUE(0, (CheckedNumeric(-1) + 1)); + TEST_EXPECTED_VALIDITY(RANGE_VALID, + CheckedNumeric(DstLimits::min()) + 1); + TEST_EXPECTED_VALIDITY( + RANGE_OVERFLOW, CheckedNumeric(DstLimits::max()) + DstLimits::max()); + + // Generic subtraction. + TEST_EXPECTED_VALUE(-1, (CheckedNumeric() - 1)); + TEST_EXPECTED_VALUE(0, (CheckedNumeric(1) - 1)); + TEST_EXPECTED_VALUE(-2, (CheckedNumeric(-1) - 1)); + TEST_EXPECTED_VALIDITY(RANGE_VALID, + CheckedNumeric(DstLimits::max()) - 1); + + // Generic multiplication. + TEST_EXPECTED_VALUE(0, (CheckedNumeric() * 1)); + TEST_EXPECTED_VALUE(1, (CheckedNumeric(1) * 1)); + TEST_EXPECTED_VALUE(-2, (CheckedNumeric(-1) * 2)); + TEST_EXPECTED_VALIDITY( + RANGE_OVERFLOW, CheckedNumeric(DstLimits::max()) * DstLimits::max()); + + // Generic division. + TEST_EXPECTED_VALUE(0, CheckedNumeric() / 1); + TEST_EXPECTED_VALUE(1, CheckedNumeric(1) / 1); + TEST_EXPECTED_VALUE(DstLimits::min() / 2, + CheckedNumeric(DstLimits::min()) / 2); + TEST_EXPECTED_VALUE(DstLimits::max() / 2, + CheckedNumeric(DstLimits::max()) / 2); + + TestSpecializedArithmetic(dst, line); +} + +// Helper macro to wrap displaying the conversion types and line numbers. +#define TEST_ARITHMETIC(Dst) TestArithmetic(#Dst, __LINE__) + +TEST(SafeNumerics, SignedIntegerMath) { + TEST_ARITHMETIC(int8_t); + TEST_ARITHMETIC(int); + TEST_ARITHMETIC(intptr_t); + TEST_ARITHMETIC(intmax_t); +} + +TEST(SafeNumerics, UnsignedIntegerMath) { + TEST_ARITHMETIC(uint8_t); + TEST_ARITHMETIC(unsigned int); + TEST_ARITHMETIC(uintptr_t); + TEST_ARITHMETIC(uintmax_t); +} + +TEST(SafeNumerics, FloatingPointMath) { + TEST_ARITHMETIC(float); + TEST_ARITHMETIC(double); +} + +// Enumerates the five different conversions types we need to test. +enum NumericConversionType { + SIGN_PRESERVING_VALUE_PRESERVING, + SIGN_PRESERVING_NARROW, + SIGN_TO_UNSIGN_WIDEN_OR_EQUAL, + SIGN_TO_UNSIGN_NARROW, + UNSIGN_TO_SIGN_NARROW_OR_EQUAL, +}; + +// Template covering the different conversion tests. +template +struct TestNumericConversion {}; + +// EXPECT_EQ wrappers providing specific detail on test failures. +#define TEST_EXPECTED_RANGE(expected, actual) \ + EXPECT_EQ(expected, butil::internal::DstRangeRelationToSrcRange(actual)) \ + << "Conversion test: " << src << " value " << actual << " to " << dst \ + << " on line " << line; + +template +struct TestNumericConversion { + static void Test(const char *dst, const char *src, int line) { + typedef numeric_limits SrcLimits; + typedef numeric_limits DstLimits; + // Integral to floating. + COMPILE_ASSERT((DstLimits::is_iec559 && SrcLimits::is_integer) || + // Not floating to integral and... + (!(DstLimits::is_integer && SrcLimits::is_iec559) && + // Same sign, same numeric, source is narrower or same. + ((SrcLimits::is_signed == DstLimits::is_signed && + sizeof(Dst) >= sizeof(Src)) || + // Or signed destination and source is smaller + (DstLimits::is_signed && sizeof(Dst) > sizeof(Src)))), + comparison_must_be_sign_preserving_and_value_preserving); + + const CheckedNumeric checked_dst = SrcLimits::max(); + ; + TEST_EXPECTED_VALIDITY(RANGE_VALID, checked_dst); + if (MaxExponent::value > MaxExponent::value) { + if (MaxExponent::value >= MaxExponent::value * 2 - 1) { + // At least twice larger type. + TEST_EXPECTED_VALIDITY(RANGE_VALID, SrcLimits::max() * checked_dst); + + } else { // Larger, but not at least twice as large. + TEST_EXPECTED_VALIDITY(RANGE_OVERFLOW, SrcLimits::max() * checked_dst); + TEST_EXPECTED_VALIDITY(RANGE_VALID, checked_dst + 1); + } + } else { // Same width type. + TEST_EXPECTED_VALIDITY(RANGE_OVERFLOW, checked_dst + 1); + } + + TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::max()); + TEST_EXPECTED_RANGE(RANGE_VALID, static_cast(1)); + if (SrcLimits::is_iec559) { + TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::max() * static_cast(-1)); + TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::infinity()); + TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::infinity() * -1); + TEST_EXPECTED_RANGE(RANGE_INVALID, SrcLimits::quiet_NaN()); + ResetFloatingPointUnit(); + } else if (numeric_limits::is_signed) { + TEST_EXPECTED_RANGE(RANGE_VALID, static_cast(-1)); + TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::min()); + } + } +}; + +template +struct TestNumericConversion { + static void Test(const char *dst, const char *src, int line) { + typedef numeric_limits SrcLimits; + typedef numeric_limits DstLimits; + COMPILE_ASSERT(SrcLimits::is_signed == DstLimits::is_signed, + destination_and_source_sign_must_be_the_same); + COMPILE_ASSERT(sizeof(Dst) < sizeof(Src) || + (DstLimits::is_integer && SrcLimits::is_iec559), + destination_must_be_narrower_than_source); + + const CheckedNumeric checked_dst; + TEST_EXPECTED_VALIDITY(RANGE_OVERFLOW, checked_dst + SrcLimits::max()); + TEST_EXPECTED_VALUE(1, checked_dst + static_cast(1)); + TEST_EXPECTED_VALIDITY(RANGE_UNDERFLOW, checked_dst - SrcLimits::max()); + + TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::max()); + TEST_EXPECTED_RANGE(RANGE_VALID, static_cast(1)); + if (SrcLimits::is_iec559) { + TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::max() * -1); + TEST_EXPECTED_RANGE(RANGE_VALID, static_cast(-1)); + TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::infinity()); + TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::infinity() * -1); + TEST_EXPECTED_RANGE(RANGE_INVALID, SrcLimits::quiet_NaN()); + ResetFloatingPointUnit(); + } else if (SrcLimits::is_signed) { + TEST_EXPECTED_VALUE(-1, checked_dst - static_cast(1)); + TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::min()); + TEST_EXPECTED_RANGE(RANGE_VALID, static_cast(-1)); + } else { + TEST_EXPECTED_VALIDITY(RANGE_INVALID, checked_dst - static_cast(1)); + TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::min()); + } + } +}; + +template +struct TestNumericConversion { + static void Test(const char *dst, const char *src, int line) { + typedef numeric_limits SrcLimits; + typedef numeric_limits DstLimits; + COMPILE_ASSERT(sizeof(Dst) >= sizeof(Src), + destination_must_be_equal_or_wider_than_source); + COMPILE_ASSERT(SrcLimits::is_signed, source_must_be_signed); + COMPILE_ASSERT(!DstLimits::is_signed, destination_must_be_unsigned); + + const CheckedNumeric checked_dst; + TEST_EXPECTED_VALUE(SrcLimits::max(), checked_dst + SrcLimits::max()); + TEST_EXPECTED_VALIDITY(RANGE_UNDERFLOW, checked_dst + static_cast(-1)); + TEST_EXPECTED_VALIDITY(RANGE_UNDERFLOW, checked_dst + -SrcLimits::max()); + + TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::min()); + TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::max()); + TEST_EXPECTED_RANGE(RANGE_VALID, static_cast(1)); + TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, static_cast(-1)); + } +}; + +template +struct TestNumericConversion { + static void Test(const char *dst, const char *src, int line) { + typedef numeric_limits SrcLimits; + typedef numeric_limits DstLimits; + COMPILE_ASSERT((DstLimits::is_integer && SrcLimits::is_iec559) || + (sizeof(Dst) < sizeof(Src)), + destination_must_be_narrower_than_source); + COMPILE_ASSERT(SrcLimits::is_signed, source_must_be_signed); + COMPILE_ASSERT(!DstLimits::is_signed, destination_must_be_unsigned); + + const CheckedNumeric checked_dst; + TEST_EXPECTED_VALUE(1, checked_dst + static_cast(1)); + TEST_EXPECTED_VALIDITY(RANGE_OVERFLOW, checked_dst + SrcLimits::max()); + TEST_EXPECTED_VALIDITY(RANGE_UNDERFLOW, checked_dst + static_cast(-1)); + TEST_EXPECTED_VALIDITY(RANGE_UNDERFLOW, checked_dst + -SrcLimits::max()); + + TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::max()); + TEST_EXPECTED_RANGE(RANGE_VALID, static_cast(1)); + TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, static_cast(-1)); + if (SrcLimits::is_iec559) { + TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::max() * -1); + TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::infinity()); + TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::infinity() * -1); + TEST_EXPECTED_RANGE(RANGE_INVALID, SrcLimits::quiet_NaN()); + ResetFloatingPointUnit(); + } else { + TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::min()); + } + } +}; + +template +struct TestNumericConversion { + static void Test(const char *dst, const char *src, int line) { + typedef numeric_limits SrcLimits; + typedef numeric_limits DstLimits; + COMPILE_ASSERT(sizeof(Dst) <= sizeof(Src), + destination_must_be_narrower_or_equal_to_source); + COMPILE_ASSERT(!SrcLimits::is_signed, source_must_be_unsigned); + COMPILE_ASSERT(DstLimits::is_signed, destination_must_be_signed); + + const CheckedNumeric checked_dst; + TEST_EXPECTED_VALUE(1, checked_dst + static_cast(1)); + TEST_EXPECTED_VALIDITY(RANGE_OVERFLOW, checked_dst + SrcLimits::max()); + TEST_EXPECTED_VALUE(SrcLimits::min(), checked_dst + SrcLimits::min()); + + TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::min()); + TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::max()); + TEST_EXPECTED_RANGE(RANGE_VALID, static_cast(1)); + } +}; + +// Helper macro to wrap displaying the conversion types and line numbers +#define TEST_NUMERIC_CONVERSION(d, s, t) \ + TestNumericConversion::Test(#d, #s, __LINE__) + +TEST(SafeNumerics, IntMinOperations) { + TEST_NUMERIC_CONVERSION(int8_t, int8_t, SIGN_PRESERVING_VALUE_PRESERVING); + TEST_NUMERIC_CONVERSION(uint8_t, uint8_t, SIGN_PRESERVING_VALUE_PRESERVING); + + TEST_NUMERIC_CONVERSION(int8_t, int, SIGN_PRESERVING_NARROW); + TEST_NUMERIC_CONVERSION(uint8_t, unsigned int, SIGN_PRESERVING_NARROW); + TEST_NUMERIC_CONVERSION(int8_t, float, SIGN_PRESERVING_NARROW); + + TEST_NUMERIC_CONVERSION(uint8_t, int8_t, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL); + + TEST_NUMERIC_CONVERSION(uint8_t, int, SIGN_TO_UNSIGN_NARROW); + TEST_NUMERIC_CONVERSION(uint8_t, intmax_t, SIGN_TO_UNSIGN_NARROW); + TEST_NUMERIC_CONVERSION(uint8_t, float, SIGN_TO_UNSIGN_NARROW); + + TEST_NUMERIC_CONVERSION(int8_t, unsigned int, UNSIGN_TO_SIGN_NARROW_OR_EQUAL); + TEST_NUMERIC_CONVERSION(int8_t, uintmax_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL); +} + +TEST(SafeNumerics, IntOperations) { + TEST_NUMERIC_CONVERSION(int, int, SIGN_PRESERVING_VALUE_PRESERVING); + TEST_NUMERIC_CONVERSION(unsigned int, unsigned int, + SIGN_PRESERVING_VALUE_PRESERVING); + TEST_NUMERIC_CONVERSION(int, int8_t, SIGN_PRESERVING_VALUE_PRESERVING); + TEST_NUMERIC_CONVERSION(unsigned int, uint8_t, + SIGN_PRESERVING_VALUE_PRESERVING); + TEST_NUMERIC_CONVERSION(int, uint8_t, SIGN_PRESERVING_VALUE_PRESERVING); + + TEST_NUMERIC_CONVERSION(int, intmax_t, SIGN_PRESERVING_NARROW); + TEST_NUMERIC_CONVERSION(unsigned int, uintmax_t, SIGN_PRESERVING_NARROW); + TEST_NUMERIC_CONVERSION(int, float, SIGN_PRESERVING_NARROW); + TEST_NUMERIC_CONVERSION(int, double, SIGN_PRESERVING_NARROW); + + TEST_NUMERIC_CONVERSION(unsigned int, int, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL); + TEST_NUMERIC_CONVERSION(unsigned int, int8_t, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL); + + TEST_NUMERIC_CONVERSION(unsigned int, intmax_t, SIGN_TO_UNSIGN_NARROW); + TEST_NUMERIC_CONVERSION(unsigned int, float, SIGN_TO_UNSIGN_NARROW); + TEST_NUMERIC_CONVERSION(unsigned int, double, SIGN_TO_UNSIGN_NARROW); + + TEST_NUMERIC_CONVERSION(int, unsigned int, UNSIGN_TO_SIGN_NARROW_OR_EQUAL); + TEST_NUMERIC_CONVERSION(int, uintmax_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL); +} + +TEST(SafeNumerics, IntMaxOperations) { + TEST_NUMERIC_CONVERSION(intmax_t, intmax_t, SIGN_PRESERVING_VALUE_PRESERVING); + TEST_NUMERIC_CONVERSION(uintmax_t, uintmax_t, + SIGN_PRESERVING_VALUE_PRESERVING); + TEST_NUMERIC_CONVERSION(intmax_t, int, SIGN_PRESERVING_VALUE_PRESERVING); + TEST_NUMERIC_CONVERSION(uintmax_t, unsigned int, + SIGN_PRESERVING_VALUE_PRESERVING); + TEST_NUMERIC_CONVERSION(intmax_t, unsigned int, + SIGN_PRESERVING_VALUE_PRESERVING); + TEST_NUMERIC_CONVERSION(intmax_t, uint8_t, SIGN_PRESERVING_VALUE_PRESERVING); + + TEST_NUMERIC_CONVERSION(intmax_t, float, SIGN_PRESERVING_NARROW); + TEST_NUMERIC_CONVERSION(intmax_t, double, SIGN_PRESERVING_NARROW); + + TEST_NUMERIC_CONVERSION(uintmax_t, int, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL); + TEST_NUMERIC_CONVERSION(uintmax_t, int8_t, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL); + + TEST_NUMERIC_CONVERSION(uintmax_t, float, SIGN_TO_UNSIGN_NARROW); + TEST_NUMERIC_CONVERSION(uintmax_t, double, SIGN_TO_UNSIGN_NARROW); + + TEST_NUMERIC_CONVERSION(intmax_t, uintmax_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL); +} + +TEST(SafeNumerics, FloatOperations) { + TEST_NUMERIC_CONVERSION(float, intmax_t, SIGN_PRESERVING_VALUE_PRESERVING); + TEST_NUMERIC_CONVERSION(float, uintmax_t, + SIGN_PRESERVING_VALUE_PRESERVING); + TEST_NUMERIC_CONVERSION(float, int, SIGN_PRESERVING_VALUE_PRESERVING); + TEST_NUMERIC_CONVERSION(float, unsigned int, + SIGN_PRESERVING_VALUE_PRESERVING); + + TEST_NUMERIC_CONVERSION(float, double, SIGN_PRESERVING_NARROW); +} + +TEST(SafeNumerics, DoubleOperations) { + TEST_NUMERIC_CONVERSION(double, intmax_t, SIGN_PRESERVING_VALUE_PRESERVING); + TEST_NUMERIC_CONVERSION(double, uintmax_t, + SIGN_PRESERVING_VALUE_PRESERVING); + TEST_NUMERIC_CONVERSION(double, int, SIGN_PRESERVING_VALUE_PRESERVING); + TEST_NUMERIC_CONVERSION(double, unsigned int, + SIGN_PRESERVING_VALUE_PRESERVING); +} + +TEST(SafeNumerics, SizeTOperations) { + TEST_NUMERIC_CONVERSION(size_t, int, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL); + TEST_NUMERIC_CONVERSION(int, size_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL); +} + +TEST(SafeNumerics, CastTests) { +// MSVC catches and warns that we're forcing saturation in these tests. +// Since that's intentional, we need to shut this warning off. +#if defined(COMPILER_MSVC) +#pragma warning(disable : 4756) +#endif + + int small_positive = 1; + int small_negative = -1; + double double_small = 1.0; + double double_large = numeric_limits::max(); + double double_infinity = numeric_limits::infinity(); + + // Just test that the cast compiles, since the other tests cover logic. + EXPECT_EQ(0, checked_cast(static_cast(0))); + + // Test various saturation corner cases. + EXPECT_EQ(saturated_cast(small_negative), + static_cast(small_negative)); + EXPECT_EQ(saturated_cast(small_positive), + static_cast(small_positive)); + EXPECT_EQ(saturated_cast(small_negative), + static_cast(0)); + EXPECT_EQ(saturated_cast(double_small), + static_cast(double_small)); + EXPECT_EQ(saturated_cast(double_large), numeric_limits::max()); + EXPECT_EQ(saturated_cast(double_large), double_infinity); + EXPECT_EQ(saturated_cast(-double_large), -double_infinity); +} diff --git a/test/safe_sprintf_unittest.cc b/test/safe_sprintf_unittest.cc new file mode 100644 index 0000000..21da00a --- /dev/null +++ b/test/safe_sprintf_unittest.cc @@ -0,0 +1,757 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/strings/safe_sprintf.h" + +#include +#include + +#include + +#include "butil/logging.h" +#include "butil/memory/scoped_ptr.h" +#include + +// Death tests on Android are currently very flaky. No need to add more flaky +// tests, as they just make it hard to spot real problems. +// TODO(markus): See if the restrictions on Android can eventually be lifted. +#if defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID) +#define ALLOW_DEATH_TEST +#endif + +namespace butil { +namespace strings { + +TEST(SafeSPrintfTest, Empty) { + char buf[2] = { 'X', 'X' }; + + // Negative buffer size should always result in an error. + EXPECT_EQ(-1, SafeSNPrintf(buf, (size_t)-1, "")); + EXPECT_EQ('X', buf[0]); + EXPECT_EQ('X', buf[1]); + + // Zero buffer size should always result in an error. + EXPECT_EQ(-1, SafeSNPrintf(buf, 0, "")); + EXPECT_EQ('X', buf[0]); + EXPECT_EQ('X', buf[1]); + + // A one-byte buffer should always print a single NUL byte. + EXPECT_EQ(0, SafeSNPrintf(buf, 1, "")); + EXPECT_EQ(0, buf[0]); + EXPECT_EQ('X', buf[1]); + buf[0] = 'X'; + + // A larger buffer should leave the trailing bytes unchanged. + EXPECT_EQ(0, SafeSNPrintf(buf, 2, "")); + EXPECT_EQ(0, buf[0]); + EXPECT_EQ('X', buf[1]); + buf[0] = 'X'; + + // The same test using SafeSPrintf() instead of SafeSNPrintf(). + EXPECT_EQ(0, SafeSPrintf(buf, "")); + EXPECT_EQ(0, buf[0]); + EXPECT_EQ('X', buf[1]); + buf[0] = 'X'; +} + +TEST(SafeSPrintfTest, NoArguments) { + // Output a text message that doesn't require any substitutions. This + // is roughly equivalent to calling strncpy() (but unlike strncpy(), it does + // always add a trailing NUL; it always deduplicates '%' characters). + static const char text[] = "hello world"; + char ref[20], buf[20]; + memset(ref, 'X', sizeof(char) * arraysize(buf)); + memcpy(buf, ref, sizeof(buf)); + + // A negative buffer size should always result in an error. + EXPECT_EQ(-1, SafeSNPrintf(buf, (size_t)-1, text)); + EXPECT_TRUE(!memcmp(buf, ref, sizeof(buf))); + + // Zero buffer size should always result in an error. + EXPECT_EQ(-1, SafeSNPrintf(buf, 0, text)); + EXPECT_TRUE(!memcmp(buf, ref, sizeof(buf))); + + // A one-byte buffer should always print a single NUL byte. + EXPECT_EQ(static_cast(sizeof(text))-1, SafeSNPrintf(buf, 1, text)); + EXPECT_EQ(0, buf[0]); + EXPECT_TRUE(!memcmp(buf+1, ref+1, sizeof(buf)-1)); + memcpy(buf, ref, sizeof(buf)); + + // A larger (but limited) buffer should always leave the trailing bytes + // unchanged. + EXPECT_EQ(static_cast(sizeof(text))-1, SafeSNPrintf(buf, 2, text)); + EXPECT_EQ(text[0], buf[0]); + EXPECT_EQ(0, buf[1]); + EXPECT_TRUE(!memcmp(buf+2, ref+2, sizeof(buf)-2)); + memcpy(buf, ref, sizeof(buf)); + + // A unrestricted buffer length should always leave the trailing bytes + // unchanged. + EXPECT_EQ(static_cast(sizeof(text))-1, + SafeSNPrintf(buf, sizeof(buf), text)); + EXPECT_EQ(std::string(text), std::string(buf)); + EXPECT_TRUE(!memcmp(buf + sizeof(text), ref + sizeof(text), + sizeof(buf) - sizeof(text))); + memcpy(buf, ref, sizeof(buf)); + + // The same test using SafeSPrintf() instead of SafeSNPrintf(). + EXPECT_EQ(static_cast(sizeof(text))-1, SafeSPrintf(buf, text)); + EXPECT_EQ(std::string(text), std::string(buf)); + EXPECT_TRUE(!memcmp(buf + sizeof(text), ref + sizeof(text), + sizeof(buf) - sizeof(text))); + memcpy(buf, ref, sizeof(buf)); + + // Check for deduplication of '%' percent characters. + EXPECT_EQ(1, SafeSPrintf(buf, "%%")); + EXPECT_EQ(2, SafeSPrintf(buf, "%%%%")); + EXPECT_EQ(2, SafeSPrintf(buf, "%%X")); + EXPECT_EQ(3, SafeSPrintf(buf, "%%%%X")); +#if defined(NDEBUG) + EXPECT_EQ(1, SafeSPrintf(buf, "%")); + EXPECT_EQ(2, SafeSPrintf(buf, "%%%")); + EXPECT_EQ(2, SafeSPrintf(buf, "%X")); + EXPECT_EQ(3, SafeSPrintf(buf, "%%%X")); +#elif defined(ALLOW_DEATH_TEST) + EXPECT_DEATH(SafeSPrintf(buf, "%"), "src.1. == '%'"); + EXPECT_DEATH(SafeSPrintf(buf, "%%%"), "src.1. == '%'"); + EXPECT_DEATH(SafeSPrintf(buf, "%X"), "src.1. == '%'"); + EXPECT_DEATH(SafeSPrintf(buf, "%%%X"), "src.1. == '%'"); +#endif +} + +TEST(SafeSPrintfTest, OneArgument) { + // Test basic single-argument single-character substitution. + const char text[] = "hello world"; + const char fmt[] = "hello%cworld"; + char ref[20], buf[20]; + memset(ref, 'X', sizeof(buf)); + memcpy(buf, ref, sizeof(buf)); + + // A negative buffer size should always result in an error. + EXPECT_EQ(-1, SafeSNPrintf(buf, (size_t)-1, fmt, ' ')); + EXPECT_TRUE(!memcmp(buf, ref, sizeof(buf))); + + // Zero buffer size should always result in an error. + EXPECT_EQ(-1, SafeSNPrintf(buf, 0, fmt, ' ')); + EXPECT_TRUE(!memcmp(buf, ref, sizeof(buf))); + + // A one-byte buffer should always print a single NUL byte. + EXPECT_EQ(static_cast(sizeof(text))-1, + SafeSNPrintf(buf, 1, fmt, ' ')); + EXPECT_EQ(0, buf[0]); + EXPECT_TRUE(!memcmp(buf+1, ref+1, sizeof(buf)-1)); + memcpy(buf, ref, sizeof(buf)); + + // A larger (but limited) buffer should always leave the trailing bytes + // unchanged. + EXPECT_EQ(static_cast(sizeof(text))-1, + SafeSNPrintf(buf, 2, fmt, ' ')); + EXPECT_EQ(text[0], buf[0]); + EXPECT_EQ(0, buf[1]); + EXPECT_TRUE(!memcmp(buf+2, ref+2, sizeof(buf)-2)); + memcpy(buf, ref, sizeof(buf)); + + // A unrestricted buffer length should always leave the trailing bytes + // unchanged. + EXPECT_EQ(static_cast(sizeof(text))-1, + SafeSNPrintf(buf, sizeof(buf), fmt, ' ')); + EXPECT_EQ(std::string(text), std::string(buf)); + EXPECT_TRUE(!memcmp(buf + sizeof(text), ref + sizeof(text), + sizeof(buf) - sizeof(text))); + memcpy(buf, ref, sizeof(buf)); + + // The same test using SafeSPrintf() instead of SafeSNPrintf(). + EXPECT_EQ(static_cast(sizeof(text))-1, SafeSPrintf(buf, fmt, ' ')); + EXPECT_EQ(std::string(text), std::string(buf)); + EXPECT_TRUE(!memcmp(buf + sizeof(text), ref + sizeof(text), + sizeof(buf) - sizeof(text))); + memcpy(buf, ref, sizeof(buf)); + + // Check for deduplication of '%' percent characters. + EXPECT_EQ(1, SafeSPrintf(buf, "%%", 0)); + EXPECT_EQ(2, SafeSPrintf(buf, "%%%%", 0)); + EXPECT_EQ(2, SafeSPrintf(buf, "%Y", 0)); + EXPECT_EQ(2, SafeSPrintf(buf, "%%Y", 0)); + EXPECT_EQ(3, SafeSPrintf(buf, "%%%Y", 0)); + EXPECT_EQ(3, SafeSPrintf(buf, "%%%%Y", 0)); +#if defined(NDEBUG) + EXPECT_EQ(1, SafeSPrintf(buf, "%", 0)); + EXPECT_EQ(2, SafeSPrintf(buf, "%%%", 0)); +#elif defined(ALLOW_DEATH_TEST) + EXPECT_DEATH(SafeSPrintf(buf, "%", 0), "ch"); + EXPECT_DEATH(SafeSPrintf(buf, "%%%", 0), "ch"); +#endif +} + +TEST(SafeSPrintfTest, MissingArg) { +#if defined(NDEBUG) + char buf[20]; + EXPECT_EQ(3, SafeSPrintf(buf, "%c%c", 'A')); + EXPECT_EQ("A%c", std::string(buf)); +#elif defined(ALLOW_DEATH_TEST) + char buf[20]; + EXPECT_DEATH(SafeSPrintf(buf, "%c%c", 'A'), "cur_arg < max_args"); +#endif +} + +TEST(SafeSPrintfTest, ASANFriendlyBufferTest) { + // Print into a buffer that is sized exactly to size. ASAN can verify that + // nobody attempts to write past the end of the buffer. + // There is a more complicated test in PrintLongString() that covers a lot + // more edge case, but it is also harder to debug in case of a failure. + const char kTestString[] = "This is a test"; + scoped_ptr buf(new char[sizeof(kTestString)]); + EXPECT_EQ(static_cast(sizeof(kTestString) - 1), + SafeSNPrintf(buf.get(), sizeof(kTestString), kTestString)); + EXPECT_EQ(std::string(kTestString), std::string(buf.get())); + EXPECT_EQ(static_cast(sizeof(kTestString) - 1), + SafeSNPrintf(buf.get(), sizeof(kTestString), "%s", kTestString)); + EXPECT_EQ(std::string(kTestString), std::string(buf.get())); +} + +TEST(SafeSPrintfTest, NArgs) { + // Pre-C++11 compilers have a different code path, that can only print + // up to ten distinct arguments. + // We test both SafeSPrintf() and SafeSNPrintf(). This makes sure we don't + // have typos in the copy-n-pasted code that is needed to deal with various + // numbers of arguments. + char buf[12]; + EXPECT_EQ(1, SafeSPrintf(buf, "%c", 1)); + EXPECT_EQ("\1", std::string(buf)); + EXPECT_EQ(2, SafeSPrintf(buf, "%c%c", 1, 2)); + EXPECT_EQ("\1\2", std::string(buf)); + EXPECT_EQ(3, SafeSPrintf(buf, "%c%c%c", 1, 2, 3)); + EXPECT_EQ("\1\2\3", std::string(buf)); + EXPECT_EQ(4, SafeSPrintf(buf, "%c%c%c%c", 1, 2, 3, 4)); + EXPECT_EQ("\1\2\3\4", std::string(buf)); + EXPECT_EQ(5, SafeSPrintf(buf, "%c%c%c%c%c", 1, 2, 3, 4, 5)); + EXPECT_EQ("\1\2\3\4\5", std::string(buf)); + EXPECT_EQ(6, SafeSPrintf(buf, "%c%c%c%c%c%c", 1, 2, 3, 4, 5, 6)); + EXPECT_EQ("\1\2\3\4\5\6", std::string(buf)); + EXPECT_EQ(7, SafeSPrintf(buf, "%c%c%c%c%c%c%c", 1, 2, 3, 4, 5, 6, 7)); + EXPECT_EQ("\1\2\3\4\5\6\7", std::string(buf)); + EXPECT_EQ(8, SafeSPrintf(buf, "%c%c%c%c%c%c%c%c", 1, 2, 3, 4, 5, 6, 7, 8)); + EXPECT_EQ("\1\2\3\4\5\6\7\10", std::string(buf)); + EXPECT_EQ(9, SafeSPrintf(buf, "%c%c%c%c%c%c%c%c%c", + 1, 2, 3, 4, 5, 6, 7, 8, 9)); + EXPECT_EQ("\1\2\3\4\5\6\7\10\11", std::string(buf)); + EXPECT_EQ(10, SafeSPrintf(buf, "%c%c%c%c%c%c%c%c%c%c", + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); + + // Repeat all the tests with SafeSNPrintf() instead of SafeSPrintf(). + EXPECT_EQ("\1\2\3\4\5\6\7\10\11\12", std::string(buf)); + EXPECT_EQ(1, SafeSNPrintf(buf, 11, "%c", 1)); + EXPECT_EQ("\1", std::string(buf)); + EXPECT_EQ(2, SafeSNPrintf(buf, 11, "%c%c", 1, 2)); + EXPECT_EQ("\1\2", std::string(buf)); + EXPECT_EQ(3, SafeSNPrintf(buf, 11, "%c%c%c", 1, 2, 3)); + EXPECT_EQ("\1\2\3", std::string(buf)); + EXPECT_EQ(4, SafeSNPrintf(buf, 11, "%c%c%c%c", 1, 2, 3, 4)); + EXPECT_EQ("\1\2\3\4", std::string(buf)); + EXPECT_EQ(5, SafeSNPrintf(buf, 11, "%c%c%c%c%c", 1, 2, 3, 4, 5)); + EXPECT_EQ("\1\2\3\4\5", std::string(buf)); + EXPECT_EQ(6, SafeSNPrintf(buf, 11, "%c%c%c%c%c%c", 1, 2, 3, 4, 5, 6)); + EXPECT_EQ("\1\2\3\4\5\6", std::string(buf)); + EXPECT_EQ(7, SafeSNPrintf(buf, 11, "%c%c%c%c%c%c%c", 1, 2, 3, 4, 5, 6, 7)); + EXPECT_EQ("\1\2\3\4\5\6\7", std::string(buf)); + EXPECT_EQ(8, SafeSNPrintf(buf, 11, "%c%c%c%c%c%c%c%c", + 1, 2, 3, 4, 5, 6, 7, 8)); + EXPECT_EQ("\1\2\3\4\5\6\7\10", std::string(buf)); + EXPECT_EQ(9, SafeSNPrintf(buf, 11, "%c%c%c%c%c%c%c%c%c", + 1, 2, 3, 4, 5, 6, 7, 8, 9)); + EXPECT_EQ("\1\2\3\4\5\6\7\10\11", std::string(buf)); + EXPECT_EQ(10, SafeSNPrintf(buf, 11, "%c%c%c%c%c%c%c%c%c%c", + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); + EXPECT_EQ("\1\2\3\4\5\6\7\10\11\12", std::string(buf)); +} + +TEST(SafeSPrintfTest, DataTypes) { + char buf[40]; + + // Bytes + EXPECT_EQ(1, SafeSPrintf(buf, "%d", (uint8_t)1)); + EXPECT_EQ("1", std::string(buf)); + EXPECT_EQ(3, SafeSPrintf(buf, "%d", (uint8_t)-1)); + EXPECT_EQ("255", std::string(buf)); + EXPECT_EQ(1, SafeSPrintf(buf, "%d", (int8_t)1)); + EXPECT_EQ("1", std::string(buf)); + EXPECT_EQ(2, SafeSPrintf(buf, "%d", (int8_t)-1)); + EXPECT_EQ("-1", std::string(buf)); + EXPECT_EQ(4, SafeSPrintf(buf, "%d", (int8_t)-128)); + EXPECT_EQ("-128", std::string(buf)); + + // Half-words + EXPECT_EQ(1, SafeSPrintf(buf, "%d", (uint16_t)1)); + EXPECT_EQ("1", std::string(buf)); + EXPECT_EQ(5, SafeSPrintf(buf, "%d", (uint16_t)-1)); + EXPECT_EQ("65535", std::string(buf)); + EXPECT_EQ(1, SafeSPrintf(buf, "%d", (int16_t)1)); + EXPECT_EQ("1", std::string(buf)); + EXPECT_EQ(2, SafeSPrintf(buf, "%d", (int16_t)-1)); + EXPECT_EQ("-1", std::string(buf)); + EXPECT_EQ(6, SafeSPrintf(buf, "%d", (int16_t)-32768)); + EXPECT_EQ("-32768", std::string(buf)); + + // Words + EXPECT_EQ(1, SafeSPrintf(buf, "%d", (uint32_t)1)); + EXPECT_EQ("1", std::string(buf)); + EXPECT_EQ(10, SafeSPrintf(buf, "%d", (uint32_t)-1)); + EXPECT_EQ("4294967295", std::string(buf)); + EXPECT_EQ(1, SafeSPrintf(buf, "%d", (int32_t)1)); + EXPECT_EQ("1", std::string(buf)); + EXPECT_EQ(2, SafeSPrintf(buf, "%d", (int32_t)-1)); + EXPECT_EQ("-1", std::string(buf)); + // Work-around for an limitation of C90 + EXPECT_EQ(11, SafeSPrintf(buf, "%d", (int32_t)-2147483647-1)); + EXPECT_EQ("-2147483648", std::string(buf)); + + // Quads + EXPECT_EQ(1, SafeSPrintf(buf, "%d", (uint64_t)1)); + EXPECT_EQ("1", std::string(buf)); + EXPECT_EQ(20, SafeSPrintf(buf, "%d", (uint64_t)-1)); + EXPECT_EQ("18446744073709551615", std::string(buf)); + EXPECT_EQ(1, SafeSPrintf(buf, "%d", (int64_t)1)); + EXPECT_EQ("1", std::string(buf)); + EXPECT_EQ(2, SafeSPrintf(buf, "%d", (int64_t)-1)); + EXPECT_EQ("-1", std::string(buf)); + // Work-around for an limitation of C90 + EXPECT_EQ(20, SafeSPrintf(buf, "%d", (int64_t)-9223372036854775807LL-1)); + EXPECT_EQ("-9223372036854775808", std::string(buf)); + + // Strings (both const and mutable). + EXPECT_EQ(4, SafeSPrintf(buf, "test")); + EXPECT_EQ("test", std::string(buf)); + EXPECT_EQ(4, SafeSPrintf(buf, buf)); + EXPECT_EQ("test", std::string(buf)); + + // Pointer + char addr[20]; + sprintf(addr, "0x%llX", (unsigned long long)(uintptr_t)buf); + SafeSPrintf(buf, "%p", buf); + EXPECT_EQ(std::string(addr), std::string(buf)); + SafeSPrintf(buf, "%p", (const char *)buf); + EXPECT_EQ(std::string(addr), std::string(buf)); + sprintf(addr, "0x%llX", (unsigned long long)(uintptr_t)sprintf); + SafeSPrintf(buf, "%p", sprintf); + EXPECT_EQ(std::string(addr), std::string(buf)); + + // Padding for pointers is a little more complicated because of the "0x" + // prefix. Padding with '0' zeros is relatively straight-forward, but + // padding with ' ' spaces requires more effort. + sprintf(addr, "0x%017llX", (unsigned long long)(uintptr_t)buf); + SafeSPrintf(buf, "%019p", buf); + EXPECT_EQ(std::string(addr), std::string(buf)); + sprintf(addr, "0x%llX", (unsigned long long)(uintptr_t)buf); + memset(addr, ' ', + (char*)memmove(addr + sizeof(addr) - strlen(addr) - 1, + addr, strlen(addr)+1) - addr); + SafeSPrintf(buf, "%19p", buf); + EXPECT_EQ(std::string(addr), std::string(buf)); +} + +namespace { +void PrintLongString(char* buf, size_t sz) { + // Output a reasonably complex expression into a limited-size buffer. + // At least one byte is available for writing the NUL character. + CHECK_GT(sz, static_cast(0)); + + // Allocate slightly more space, so that we can verify that SafeSPrintf() + // never writes past the end of the buffer. + scoped_ptr tmp(new char[sz+2]); + memset(tmp.get(), 'X', sz+2); + + // Use SafeSPrintf() to output a complex list of arguments: + // - test padding and truncating %c single characters. + // - test truncating %s simple strings. + // - test mismatching arguments and truncating (for %d != %s). + // - test zero-padding and truncating %x hexadecimal numbers. + // - test outputting and truncating %d MININT. + // - test outputting and truncating %p arbitrary pointer values. + // - test outputting, padding and truncating NULL-pointer %s strings. + char* out = tmp.get(); + size_t out_sz = sz; + size_t len; + for (scoped_ptr perfect_buf;;) { + size_t needed = SafeSNPrintf(out, out_sz, +#if defined(NDEBUG) + "A%2cong %s: %d %010X %d %p%7s", 'l', "string", "", +#else + "A%2cong %s: %%d %010X %d %p%7s", 'l', "string", +#endif + 0xDEADBEEF, std::numeric_limits::min(), + PrintLongString, static_cast(NULL)) + 1; + + // Various sanity checks: + // The numbered of characters needed to print the full string should always + // be bigger or equal to the bytes that have actually been output. + len = strlen(tmp.get()); + CHECK_GE(needed, len+1); + + // The number of characters output should always fit into the buffer that + // was passed into SafeSPrintf(). + CHECK_LT(len, out_sz); + + // The output is always terminated with a NUL byte (actually, this test is + // always going to pass, as strlen() already verified this) + EXPECT_FALSE(tmp[len]); + + // ASAN can check that we are not overwriting buffers, iff we make sure the + // buffer is exactly the size that we are expecting to be written. After + // running SafeSNPrintf() the first time, it is possible to compute the + // correct buffer size for this test. So, allocate a second buffer and run + // the exact same SafeSNPrintf() command again. + if (!perfect_buf.get()) { + out_sz = std::min(needed, sz); + out = new char[out_sz]; + perfect_buf.reset(out); + } else { + break; + } + } + + // All trailing bytes are unchanged. + for (size_t i = len+1; i < sz+2; ++i) + EXPECT_EQ('X', tmp[i]); + + // The text that was generated by SafeSPrintf() should always match the + // equivalent text generated by sprintf(). Please note that the format + // string for sprintf() is not complicated, as it does not have the + // benefit of getting type information from the C++ compiler. + // + // N.B.: It would be so much cleaner to use snprintf(). But unfortunately, + // Visual Studio doesn't support this function, and the work-arounds + // are all really awkward. + char ref[256]; + CHECK_LE(sz, sizeof(ref)); + sprintf(ref, "A long string: %%d 00DEADBEEF %lld 0x%llX ", + static_cast(std::numeric_limits::min()), + static_cast( + reinterpret_cast(PrintLongString))); + ref[sz-1] = '\000'; + +#if defined(NDEBUG) + const size_t kSSizeMax = std::numeric_limits::max(); +#else + const size_t kSSizeMax = internal::GetSafeSPrintfSSizeMaxForTest(); +#endif + + // Compare the output from SafeSPrintf() to the one from sprintf(). + EXPECT_EQ(std::string(ref).substr(0, kSSizeMax-1), std::string(tmp.get())); + + // We allocated a slightly larger buffer, so that we could perform some + // extra sanity checks. Now that the tests have all passed, we copy the + // data to the output buffer that the caller provided. + memcpy(buf, tmp.get(), len+1); +} + +#if !defined(NDEBUG) +class ScopedSafeSPrintfSSizeMaxSetter { + public: + ScopedSafeSPrintfSSizeMaxSetter(size_t sz) { + old_ssize_max_ = internal::GetSafeSPrintfSSizeMaxForTest(); + internal::SetSafeSPrintfSSizeMaxForTest(sz); + } + + ~ScopedSafeSPrintfSSizeMaxSetter() { + internal::SetSafeSPrintfSSizeMaxForTest(old_ssize_max_); + } + + private: + size_t old_ssize_max_; + + DISALLOW_COPY_AND_ASSIGN(ScopedSafeSPrintfSSizeMaxSetter); +}; +#endif + +} // anonymous namespace + +TEST(SafeSPrintfTest, Truncation) { + // We use PrintLongString() to print a complex long string and then + // truncate to all possible lengths. This ends up exercising a lot of + // different code paths in SafeSPrintf() and IToASCII(), as truncation can + // happen in a lot of different states. + char ref[256]; + PrintLongString(ref, sizeof(ref)); + for (size_t i = strlen(ref)+1; i; --i) { + char buf[sizeof(ref)]; + PrintLongString(buf, i); + EXPECT_EQ(std::string(ref, i - 1), std::string(buf)); + } + + // When compiling in debug mode, we have the ability to fake a small + // upper limit for the maximum value that can be stored in an ssize_t. + // SafeSPrintf() uses this upper limit to determine how many bytes it will + // write to the buffer, even if the caller claimed a bigger buffer size. + // Repeat the truncation test and verify that this other code path in + // SafeSPrintf() works correctly, too. +#if !defined(NDEBUG) + for (size_t i = strlen(ref)+1; i > 1; --i) { + ScopedSafeSPrintfSSizeMaxSetter ssize_max_setter(i); + char buf[sizeof(ref)]; + PrintLongString(buf, sizeof(buf)); + EXPECT_EQ(std::string(ref, i - 1), std::string(buf)); + } + + // kSSizeMax is also used to constrain the maximum amount of padding, before + // SafeSPrintf() detects an error in the format string. + ScopedSafeSPrintfSSizeMaxSetter ssize_max_setter(100); + char buf[256]; + EXPECT_EQ(99, SafeSPrintf(buf, "%99c", ' ')); + EXPECT_EQ(std::string(99, ' '), std::string(buf)); + *buf = '\000'; +#if defined(ALLOW_DEATH_TEST) + EXPECT_DEATH(SafeSPrintf(buf, "%100c", ' '), "padding <= max_padding"); +#endif + EXPECT_EQ(0, *buf); +#endif +} + +TEST(SafeSPrintfTest, Padding) { + char buf[40], fmt[40]; + + // Chars %c + EXPECT_EQ(1, SafeSPrintf(buf, "%c", 'A')); + EXPECT_EQ("A", std::string(buf)); + EXPECT_EQ(2, SafeSPrintf(buf, "%2c", 'A')); + EXPECT_EQ(" A", std::string(buf)); + EXPECT_EQ(2, SafeSPrintf(buf, "%02c", 'A')); + EXPECT_EQ(" A", std::string(buf)); + EXPECT_EQ(4, SafeSPrintf(buf, "%-2c", 'A')); + EXPECT_EQ("%-2c", std::string(buf)); + SafeSPrintf(fmt, "%%%dc", std::numeric_limits::max() - 1); + EXPECT_EQ(std::numeric_limits::max()-1, SafeSPrintf(buf, fmt, 'A')); + SafeSPrintf(fmt, "%%%dc", + static_cast(std::numeric_limits::max())); +#if defined(NDEBUG) + EXPECT_EQ(2, SafeSPrintf(buf, fmt, 'A')); + EXPECT_EQ("%c", std::string(buf)); +#elif defined(ALLOW_DEATH_TEST) + EXPECT_DEATH(SafeSPrintf(buf, fmt, 'A'), "padding <= max_padding"); +#endif + + // Octal %o + EXPECT_EQ(1, SafeSPrintf(buf, "%o", 1)); + EXPECT_EQ("1", std::string(buf)); + EXPECT_EQ(2, SafeSPrintf(buf, "%2o", 1)); + EXPECT_EQ(" 1", std::string(buf)); + EXPECT_EQ(2, SafeSPrintf(buf, "%02o", 1)); + EXPECT_EQ("01", std::string(buf)); + EXPECT_EQ(12, SafeSPrintf(buf, "%12o", -1)); + EXPECT_EQ(" 37777777777", std::string(buf)); + EXPECT_EQ(12, SafeSPrintf(buf, "%012o", -1)); + EXPECT_EQ("037777777777", std::string(buf)); + EXPECT_EQ(23, SafeSPrintf(buf, "%23o", -1LL)); + EXPECT_EQ(" 1777777777777777777777", std::string(buf)); + EXPECT_EQ(23, SafeSPrintf(buf, "%023o", -1LL)); + EXPECT_EQ("01777777777777777777777", std::string(buf)); + EXPECT_EQ(3, SafeSPrintf(buf, "%2o", 0111)); + EXPECT_EQ("111", std::string(buf)); + EXPECT_EQ(4, SafeSPrintf(buf, "%-2o", 1)); + EXPECT_EQ("%-2o", std::string(buf)); + SafeSPrintf(fmt, "%%%do", std::numeric_limits::max()-1); + EXPECT_EQ(std::numeric_limits::max()-1, + SafeSNPrintf(buf, 4, fmt, 1)); + EXPECT_EQ(" ", std::string(buf)); + SafeSPrintf(fmt, "%%0%do", std::numeric_limits::max()-1); + EXPECT_EQ(std::numeric_limits::max()-1, + SafeSNPrintf(buf, 4, fmt, 1)); + EXPECT_EQ("000", std::string(buf)); + SafeSPrintf(fmt, "%%%do", + static_cast(std::numeric_limits::max())); +#if defined(NDEBUG) + EXPECT_EQ(2, SafeSPrintf(buf, fmt, 1)); + EXPECT_EQ("%o", std::string(buf)); +#elif defined(ALLOW_DEATH_TEST) + EXPECT_DEATH(SafeSPrintf(buf, fmt, 1), "padding <= max_padding"); +#endif + + // Decimals %d + EXPECT_EQ(1, SafeSPrintf(buf, "%d", 1)); + EXPECT_EQ("1", std::string(buf)); + EXPECT_EQ(2, SafeSPrintf(buf, "%2d", 1)); + EXPECT_EQ(" 1", std::string(buf)); + EXPECT_EQ(2, SafeSPrintf(buf, "%02d", 1)); + EXPECT_EQ("01", std::string(buf)); + EXPECT_EQ(3, SafeSPrintf(buf, "%3d", -1)); + EXPECT_EQ(" -1", std::string(buf)); + EXPECT_EQ(3, SafeSPrintf(buf, "%03d", -1)); + EXPECT_EQ("-01", std::string(buf)); + EXPECT_EQ(3, SafeSPrintf(buf, "%2d", 111)); + EXPECT_EQ("111", std::string(buf)); + EXPECT_EQ(4, SafeSPrintf(buf, "%2d", -111)); + EXPECT_EQ("-111", std::string(buf)); + EXPECT_EQ(4, SafeSPrintf(buf, "%-2d", 1)); + EXPECT_EQ("%-2d", std::string(buf)); + SafeSPrintf(fmt, "%%%dd", std::numeric_limits::max()-1); + EXPECT_EQ(std::numeric_limits::max()-1, + SafeSNPrintf(buf, 4, fmt, 1)); + EXPECT_EQ(" ", std::string(buf)); + SafeSPrintf(fmt, "%%0%dd", std::numeric_limits::max()-1); + EXPECT_EQ(std::numeric_limits::max()-1, + SafeSNPrintf(buf, 4, fmt, 1)); + EXPECT_EQ("000", std::string(buf)); + SafeSPrintf(fmt, "%%%dd", + static_cast(std::numeric_limits::max())); +#if defined(NDEBUG) + EXPECT_EQ(2, SafeSPrintf(buf, fmt, 1)); + EXPECT_EQ("%d", std::string(buf)); +#elif defined(ALLOW_DEATH_TEST) + EXPECT_DEATH(SafeSPrintf(buf, fmt, 1), "padding <= max_padding"); +#endif + + // Hex %X + EXPECT_EQ(1, SafeSPrintf(buf, "%X", 1)); + EXPECT_EQ("1", std::string(buf)); + EXPECT_EQ(2, SafeSPrintf(buf, "%2X", 1)); + EXPECT_EQ(" 1", std::string(buf)); + EXPECT_EQ(2, SafeSPrintf(buf, "%02X", 1)); + EXPECT_EQ("01", std::string(buf)); + EXPECT_EQ(9, SafeSPrintf(buf, "%9X", -1)); + EXPECT_EQ(" FFFFFFFF", std::string(buf)); + EXPECT_EQ(9, SafeSPrintf(buf, "%09X", -1)); + EXPECT_EQ("0FFFFFFFF", std::string(buf)); + EXPECT_EQ(17, SafeSPrintf(buf, "%17X", -1LL)); + EXPECT_EQ(" FFFFFFFFFFFFFFFF", std::string(buf)); + EXPECT_EQ(17, SafeSPrintf(buf, "%017X", -1LL)); + EXPECT_EQ("0FFFFFFFFFFFFFFFF", std::string(buf)); + EXPECT_EQ(3, SafeSPrintf(buf, "%2X", 0x111)); + EXPECT_EQ("111", std::string(buf)); + EXPECT_EQ(4, SafeSPrintf(buf, "%-2X", 1)); + EXPECT_EQ("%-2X", std::string(buf)); + SafeSPrintf(fmt, "%%%dX", std::numeric_limits::max()-1); + EXPECT_EQ(std::numeric_limits::max()-1, + SafeSNPrintf(buf, 4, fmt, 1)); + EXPECT_EQ(" ", std::string(buf)); + SafeSPrintf(fmt, "%%0%dX", std::numeric_limits::max()-1); + EXPECT_EQ(std::numeric_limits::max()-1, + SafeSNPrintf(buf, 4, fmt, 1)); + EXPECT_EQ("000", std::string(buf)); + SafeSPrintf(fmt, "%%%dX", + static_cast(std::numeric_limits::max())); +#if defined(NDEBUG) + EXPECT_EQ(2, SafeSPrintf(buf, fmt, 1)); + EXPECT_EQ("%X", std::string(buf)); +#elif defined(ALLOW_DEATH_TEST) + EXPECT_DEATH(SafeSPrintf(buf, fmt, 1), "padding <= max_padding"); +#endif + + // Pointer %p + EXPECT_EQ(3, SafeSPrintf(buf, "%p", (void*)1)); + EXPECT_EQ("0x1", std::string(buf)); + EXPECT_EQ(4, SafeSPrintf(buf, "%4p", (void*)1)); + EXPECT_EQ(" 0x1", std::string(buf)); + EXPECT_EQ(4, SafeSPrintf(buf, "%04p", (void*)1)); + EXPECT_EQ("0x01", std::string(buf)); + EXPECT_EQ(5, SafeSPrintf(buf, "%4p", (void*)0x111)); + EXPECT_EQ("0x111", std::string(buf)); + EXPECT_EQ(4, SafeSPrintf(buf, "%-2p", (void*)1)); + EXPECT_EQ("%-2p", std::string(buf)); + SafeSPrintf(fmt, "%%%dp", std::numeric_limits::max()-1); + EXPECT_EQ(std::numeric_limits::max()-1, + SafeSNPrintf(buf, 4, fmt, (void*)1)); + EXPECT_EQ(" ", std::string(buf)); + SafeSPrintf(fmt, "%%0%dp", std::numeric_limits::max()-1); + EXPECT_EQ(std::numeric_limits::max()-1, + SafeSNPrintf(buf, 4, fmt, (void*)1)); + EXPECT_EQ("0x0", std::string(buf)); + SafeSPrintf(fmt, "%%%dp", + static_cast(std::numeric_limits::max())); +#if defined(NDEBUG) + EXPECT_EQ(2, SafeSPrintf(buf, fmt, 1)); + EXPECT_EQ("%p", std::string(buf)); +#elif defined(ALLOW_DEATH_TEST) + EXPECT_DEATH(SafeSPrintf(buf, fmt, 1), "padding <= max_padding"); +#endif + + // String + EXPECT_EQ(1, SafeSPrintf(buf, "%s", "A")); + EXPECT_EQ("A", std::string(buf)); + EXPECT_EQ(2, SafeSPrintf(buf, "%2s", "A")); + EXPECT_EQ(" A", std::string(buf)); + EXPECT_EQ(2, SafeSPrintf(buf, "%02s", "A")); + EXPECT_EQ(" A", std::string(buf)); + EXPECT_EQ(3, SafeSPrintf(buf, "%2s", "AAA")); + EXPECT_EQ("AAA", std::string(buf)); + EXPECT_EQ(4, SafeSPrintf(buf, "%-2s", "A")); + EXPECT_EQ("%-2s", std::string(buf)); + SafeSPrintf(fmt, "%%%ds", std::numeric_limits::max()-1); + EXPECT_EQ(std::numeric_limits::max()-1, + SafeSNPrintf(buf, 4, fmt, "A")); + EXPECT_EQ(" ", std::string(buf)); + SafeSPrintf(fmt, "%%0%ds", std::numeric_limits::max()-1); + EXPECT_EQ(std::numeric_limits::max()-1, + SafeSNPrintf(buf, 4, fmt, "A")); + EXPECT_EQ(" ", std::string(buf)); + SafeSPrintf(fmt, "%%%ds", + static_cast(std::numeric_limits::max())); +#if defined(NDEBUG) + EXPECT_EQ(2, SafeSPrintf(buf, fmt, "A")); + EXPECT_EQ("%s", std::string(buf)); +#elif defined(ALLOW_DEATH_TEST) + EXPECT_DEATH(SafeSPrintf(buf, fmt, "A"), "padding <= max_padding"); +#endif +} + +TEST(SafeSPrintfTest, EmbeddedNul) { + char buf[] = { 'X', 'X', 'X', 'X' }; + EXPECT_EQ(2, SafeSPrintf(buf, "%3c", 0)); + EXPECT_EQ(' ', buf[0]); + EXPECT_EQ(' ', buf[1]); + EXPECT_EQ(0, buf[2]); + EXPECT_EQ('X', buf[3]); + + // Check handling of a NUL format character. N.B. this takes two different + // code paths depending on whether we are actually passing arguments. If + // we don't have any arguments, we are running in the fast-path code, that + // looks (almost) like a strncpy(). +#if defined(NDEBUG) + EXPECT_EQ(2, SafeSPrintf(buf, "%%%")); + EXPECT_EQ("%%", std::string(buf)); + EXPECT_EQ(2, SafeSPrintf(buf, "%%%", 0)); + EXPECT_EQ("%%", std::string(buf)); +#elif defined(ALLOW_DEATH_TEST) + EXPECT_DEATH(SafeSPrintf(buf, "%%%"), "src.1. == '%'"); + EXPECT_DEATH(SafeSPrintf(buf, "%%%", 0), "ch"); +#endif +} + +TEST(SafeSPrintfTest, EmitNULL) { +#if defined(__GNUC__) && __GNUC__ >= 4 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion-null" +#endif + + // NOTE(gejun): Warning in gcc 3.4 +#if !defined(__GNUC__) || __GNUC__ >= 4 + char buf[40]; + EXPECT_EQ(1, SafeSPrintf(buf, "%d", NULL)); + EXPECT_EQ("0", std::string(buf)); + EXPECT_EQ(3, SafeSPrintf(buf, "%p", NULL)); + EXPECT_EQ("0x0", std::string(buf)); + EXPECT_EQ(6, SafeSPrintf(buf, "%s", NULL)); + EXPECT_EQ("", std::string(buf)); +#endif + +#if defined(__GNUC__) && __GNUC__ >= 4 +#pragma GCC diagnostic pop +#endif +} + +TEST(SafeSPrintfTest, PointerSize) { + // The internal data representation is a 64bit value, independent of the + // native word size. We want to perform sign-extension for signed integers, + // but we want to avoid doing so for pointer types. This could be a + // problem on systems, where pointers are only 32bit. This tests verifies + // that there is no such problem. + char *str = reinterpret_cast(0x80000000u); + void *ptr = str; + char buf[40]; + EXPECT_EQ(10, SafeSPrintf(buf, "%p", str)); + EXPECT_EQ("0x80000000", std::string(buf)); + EXPECT_EQ(10, SafeSPrintf(buf, "%p", ptr)); + EXPECT_EQ("0x80000000", std::string(buf)); +} + +} // namespace strings +} // namespace butil diff --git a/test/scope_guard_unittest.cpp b/test/scope_guard_unittest.cpp new file mode 100644 index 0000000..f5e4b62 --- /dev/null +++ b/test/scope_guard_unittest.cpp @@ -0,0 +1,58 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/memory/scope_guard.h" + +TEST(ScopedGuardTest, sanity) { + bool flag = false; + { + auto guard = butil::MakeScopeGuard([&flag] { + flag = true; + }); + } + ASSERT_TRUE(flag); + + flag = false; + { + BRPC_SCOPE_EXIT { + flag = true; + }; + } + ASSERT_TRUE(flag); + + { + BRPC_SCOPE_EXIT { + flag = true; + }; + + BRPC_SCOPE_EXIT { + flag = false; + }; + } + ASSERT_TRUE(flag); + + flag = false; + { + auto guard = butil::MakeScopeGuard([&flag] { + flag = true; + }); + guard.dismiss(); + } + ASSERT_FALSE(flag); +} diff --git a/test/scoped_clear_errno_unittest.cc b/test/scoped_clear_errno_unittest.cc new file mode 100644 index 0000000..f3fec98 --- /dev/null +++ b/test/scoped_clear_errno_unittest.cc @@ -0,0 +1,30 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "butil/scoped_clear_errno.h" +#include + +namespace butil { + +TEST(ScopedClearErrno, TestNoError) { + errno = 1; + { + ScopedClearErrno clear_error; + EXPECT_EQ(0, errno); + } + EXPECT_EQ(1, errno); +} + +TEST(ScopedClearErrno, TestError) { + errno = 1; + { + ScopedClearErrno clear_error; + errno = 2; + } + EXPECT_EQ(2, errno); +} + +} // namespace butil diff --git a/test/scoped_generic_unittest.cc b/test/scoped_generic_unittest.cc new file mode 100644 index 0000000..4f707c6 --- /dev/null +++ b/test/scoped_generic_unittest.cc @@ -0,0 +1,153 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "butil/scoped_generic.h" +#include + +namespace butil { + +namespace { + +struct IntTraits { + IntTraits(std::vector* freed) : freed_ints(freed) {} + + static int InvalidValue() { + return -1; + } + void Free(int value) { + freed_ints->push_back(value); + } + + std::vector* freed_ints; +}; + +typedef ScopedGeneric ScopedInt; + +} // namespace + +TEST(ScopedGenericTest, ScopedGeneric) { + std::vector values_freed; + IntTraits traits(&values_freed); + + // Invalid case, delete should not be called. + { + ScopedInt a(IntTraits::InvalidValue(), traits); + } + EXPECT_TRUE(values_freed.empty()); + + // Simple deleting case. + static const int kFirst = 0; + { + ScopedInt a(kFirst, traits); + } + ASSERT_EQ(1u, values_freed.size()); + ASSERT_EQ(kFirst, values_freed[0]); + values_freed.clear(); + + // Release should return the right value and leave the object empty. + { + ScopedInt a(kFirst, traits); + EXPECT_EQ(kFirst, a.release()); + + ScopedInt b(IntTraits::InvalidValue(), traits); + EXPECT_EQ(IntTraits::InvalidValue(), b.release()); + } + ASSERT_TRUE(values_freed.empty()); + + // Reset should free the old value, then the new one should go away when + // it goes out of scope. + static const int kSecond = 1; + { + ScopedInt b(kFirst, traits); + b.reset(kSecond); + ASSERT_EQ(1u, values_freed.size()); + ASSERT_EQ(kFirst, values_freed[0]); + } + ASSERT_EQ(2u, values_freed.size()); + ASSERT_EQ(kSecond, values_freed[1]); + values_freed.clear(); + + // Swap. + { + ScopedInt a(kFirst, traits); + ScopedInt b(kSecond, traits); + a.swap(b); + EXPECT_TRUE(values_freed.empty()); // Nothing should be freed. + EXPECT_EQ(kSecond, a.get()); + EXPECT_EQ(kFirst, b.get()); + } + // Values should be deleted in the opposite order. + ASSERT_EQ(2u, values_freed.size()); + EXPECT_EQ(kFirst, values_freed[0]); + EXPECT_EQ(kSecond, values_freed[1]); + values_freed.clear(); + + // Pass. + { + ScopedInt a(kFirst, traits); + ScopedInt b(a.Pass()); + EXPECT_TRUE(values_freed.empty()); // Nothing should be freed. + ASSERT_EQ(IntTraits::InvalidValue(), a.get()); + ASSERT_EQ(kFirst, b.get()); + } + ASSERT_EQ(1u, values_freed.size()); + ASSERT_EQ(kFirst, values_freed[0]); +} + +TEST(ScopedGenericTest, Operators) { + std::vector values_freed; + IntTraits traits(&values_freed); + + static const int kFirst = 0; + static const int kSecond = 1; + { + ScopedInt a(kFirst, traits); + EXPECT_TRUE(a == kFirst); + EXPECT_FALSE(a != kFirst); + EXPECT_FALSE(a == kSecond); + EXPECT_TRUE(a != kSecond); + + EXPECT_TRUE(kFirst == a); + EXPECT_FALSE(kFirst != a); + EXPECT_FALSE(kSecond == a); + EXPECT_TRUE(kSecond != a); + } + + // is_valid(). + { + ScopedInt a(kFirst, traits); + EXPECT_TRUE(a.is_valid()); + a.reset(); + EXPECT_FALSE(a.is_valid()); + } +} + +// Cheesy manual "no compile" test for manually validating changes. +#if 0 +TEST(ScopedGenericTest, NoCompile) { + // Assignment shouldn't work. + /*{ + ScopedInt a(kFirst, traits); + ScopedInt b(a); + }*/ + + // Comparison shouldn't work. + /*{ + ScopedInt a(kFirst, traits); + ScopedInt b(kFirst, traits); + if (a == b) { + } + }*/ + + // Implicit conversion to bool shouldn't work. + /*{ + ScopedInt a(kFirst, traits); + bool result = a; + }*/ +} +#endif + +} // namespace butil diff --git a/test/scoped_locale.cc b/test/scoped_locale.cc new file mode 100644 index 0000000..eef133f --- /dev/null +++ b/test/scoped_locale.cc @@ -0,0 +1,23 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "scoped_locale.h" + +#include + +#include + +namespace butil { + +ScopedLocale::ScopedLocale(const std::string& locale) { + prev_locale_ = setlocale(LC_ALL, NULL); + EXPECT_TRUE(setlocale(LC_ALL, locale.c_str()) != NULL) << + "Failed to set locale: " << locale; +} + +ScopedLocale::~ScopedLocale() { + EXPECT_STREQ(prev_locale_.c_str(), setlocale(LC_ALL, prev_locale_.c_str())); +} + +} // namespace butil diff --git a/test/scoped_locale.h b/test/scoped_locale.h new file mode 100644 index 0000000..a667218 --- /dev/null +++ b/test/scoped_locale.h @@ -0,0 +1,29 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_TEST_SCOPED_LOCALE_H_ +#define BUTIL_TEST_SCOPED_LOCALE_H_ + +#include + +#include "butil/basictypes.h" + +namespace butil { + +// Sets the given |locale| on construction, and restores the previous locale +// on destruction. +class ScopedLocale { + public: + explicit ScopedLocale(const std::string& locale); + ~ScopedLocale(); + + private: + std::string prev_locale_; + + DISALLOW_COPY_AND_ASSIGN(ScopedLocale); +}; + +} // namespace butil + +#endif // BUTIL_TEST_SCOPED_LOCALE_H_ diff --git a/test/scoped_lock_unittest.cpp b/test/scoped_lock_unittest.cpp new file mode 100644 index 0000000..9e59222 --- /dev/null +++ b/test/scoped_lock_unittest.cpp @@ -0,0 +1,104 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/scoped_lock.h" +#include + +namespace { +class ScopedLockTest : public ::testing::Test{ +protected: + ScopedLockTest(){ + }; + virtual ~ScopedLockTest(){}; + virtual void SetUp() { + }; + virtual void TearDown() { + }; +}; + +TEST_F(ScopedLockTest, mutex) { + pthread_mutex_t m1 = PTHREAD_MUTEX_INITIALIZER; + { + BAIDU_SCOPED_LOCK(m1); + ASSERT_EQ(EBUSY, pthread_mutex_trylock(&m1)); + } + ASSERT_EQ(0, pthread_mutex_trylock(&m1)); + pthread_mutex_unlock(&m1); +} + +TEST_F(ScopedLockTest, spinlock) { + pthread_spinlock_t s1; + pthread_spin_init(&s1, 0); + { + BAIDU_SCOPED_LOCK(s1); + ASSERT_EQ(EBUSY, pthread_spin_trylock(&s1)); + } + ASSERT_EQ(0, pthread_spin_lock(&s1)); + pthread_spin_unlock(&s1); + pthread_spin_destroy(&s1); +} + +TEST_F(ScopedLockTest, unique_lock_mutex) { + pthread_mutex_t m1 = PTHREAD_MUTEX_INITIALIZER; + { + std::unique_lock lck(m1); + ASSERT_EQ(EBUSY, pthread_mutex_trylock(&m1)); + lck.unlock(); + { + std::unique_lock lck2(m1, std::try_to_lock); + ASSERT_TRUE(lck2.owns_lock()); + } + ASSERT_TRUE(lck.try_lock()); + ASSERT_TRUE(lck.owns_lock()); + std::unique_lock lck2(m1, std::defer_lock); + ASSERT_FALSE(lck2.owns_lock()); + std::unique_lock lck3(m1, std::try_to_lock); + ASSERT_FALSE(lck3.owns_lock()); + } + { + BAIDU_SCOPED_LOCK(m1); + ASSERT_EQ(EBUSY, pthread_mutex_trylock(&m1)); + } + ASSERT_EQ(0, pthread_mutex_trylock(&m1)); + { + std::unique_lock lck(m1, std::adopt_lock); + ASSERT_TRUE(lck.owns_lock()); + } + std::unique_lock lck(m1, std::try_to_lock); + ASSERT_TRUE(lck.owns_lock()); +} + +TEST_F(ScopedLockTest, unique_lock_spin) { + pthread_spinlock_t s1; + pthread_spin_init(&s1, 0); + { + std::unique_lock lck(s1); + ASSERT_EQ(EBUSY, pthread_spin_trylock(&s1)); + lck.unlock(); + ASSERT_TRUE(lck.try_lock()); + } + { + BAIDU_SCOPED_LOCK(s1); + ASSERT_EQ(EBUSY, pthread_spin_trylock(&s1)); + } + ASSERT_EQ(0, pthread_spin_lock(&s1)); + pthread_spin_unlock(&s1); + pthread_spin_destroy(&s1); +} + +} diff --git a/test/scoped_ptr_unittest.cc b/test/scoped_ptr_unittest.cc new file mode 100644 index 0000000..8fe2acc --- /dev/null +++ b/test/scoped_ptr_unittest.cc @@ -0,0 +1,602 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/memory/scoped_ptr.h" + +#include "butil/basictypes.h" +#include + +namespace { + +// Used to test depth subtyping. +class ConDecLoggerParent { + public: + virtual ~ConDecLoggerParent() {} + + virtual void SetPtr(int* ptr) = 0; + + virtual int SomeMeth(int x) const = 0; +}; + +class ConDecLogger : public ConDecLoggerParent { + public: + ConDecLogger() : ptr_(NULL) { } + explicit ConDecLogger(int* ptr) { SetPtr(ptr); } + virtual ~ConDecLogger() { --*ptr_; } + + virtual void SetPtr(int* ptr) OVERRIDE { ptr_ = ptr; ++*ptr_; } + + virtual int SomeMeth(int x) const OVERRIDE { return x; } + + private: + int* ptr_; + + DISALLOW_COPY_AND_ASSIGN(ConDecLogger); +}; + +struct CountingDeleter { + explicit CountingDeleter(int* count) : count_(count) {} + inline void operator()(double* ptr) const { + (*count_)++; + } + int* count_; +}; + +// Used to test assignment of convertible deleters. +struct CountingDeleterChild : public CountingDeleter { + explicit CountingDeleterChild(int* count) : CountingDeleter(count) {} +}; + +class OverloadedNewAndDelete { + public: + void* operator new(size_t size) { + g_new_count++; + return malloc(size); + } + + void operator delete(void* ptr) { + g_delete_count++; + free(ptr); + } + + static void ResetCounters() { + g_new_count = 0; + g_delete_count = 0; + } + + static int new_count() { return g_new_count; } + static int delete_count() { return g_delete_count; } + + private: + static int g_new_count; + static int g_delete_count; +}; + +int OverloadedNewAndDelete::g_new_count = 0; +int OverloadedNewAndDelete::g_delete_count = 0; + +scoped_ptr PassThru(scoped_ptr logger) { + return logger.Pass(); +} + +void GrabAndDrop(scoped_ptr logger) { +} + +// Do not delete this function! It's existence is to test that you can +// return a temporarily constructed version of the scoper. +scoped_ptr TestReturnOfType(int* constructed) { + return scoped_ptr(new ConDecLogger(constructed)); +} + +scoped_ptr UpcastUsingPassAs( + scoped_ptr object) { + return object.PassAs(); +} + +} // namespace + +TEST(ScopedPtrTest, ScopedPtr) { + int constructed = 0; + + // Ensure size of scoped_ptr<> doesn't increase unexpectedly. + COMPILE_ASSERT(sizeof(int*) >= sizeof(scoped_ptr), + scoped_ptr_larger_than_raw_ptr); + + { + scoped_ptr scoper(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + EXPECT_EQ(10, scoper->SomeMeth(10)); + EXPECT_EQ(10, scoper.get()->SomeMeth(10)); + EXPECT_EQ(10, (*scoper).SomeMeth(10)); + } + EXPECT_EQ(0, constructed); + + // Test reset() and release() + { + scoped_ptr scoper(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + scoper.reset(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + scoper.reset(); + EXPECT_EQ(0, constructed); + EXPECT_FALSE(scoper.get()); + + scoper.reset(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + ConDecLogger* take = scoper.release(); + EXPECT_EQ(1, constructed); + EXPECT_FALSE(scoper.get()); + delete take; + EXPECT_EQ(0, constructed); + + scoper.reset(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + } + EXPECT_EQ(0, constructed); + + // Test swap(), == and != + { + scoped_ptr scoper1; + scoped_ptr scoper2; + EXPECT_TRUE(scoper1 == scoper2.get()); + EXPECT_FALSE(scoper1 != scoper2.get()); + + ConDecLogger* logger = new ConDecLogger(&constructed); + scoper1.reset(logger); + EXPECT_EQ(logger, scoper1.get()); + EXPECT_FALSE(scoper2.get()); + EXPECT_FALSE(scoper1 == scoper2.get()); + EXPECT_TRUE(scoper1 != scoper2.get()); + + scoper2.swap(scoper1); + EXPECT_EQ(logger, scoper2.get()); + EXPECT_FALSE(scoper1.get()); + EXPECT_FALSE(scoper1 == scoper2.get()); + EXPECT_TRUE(scoper1 != scoper2.get()); + } + EXPECT_EQ(0, constructed); +} + +TEST(ScopedPtrTest, ScopedPtrDepthSubtyping) { + int constructed = 0; + + // Test construction from a scoped_ptr to a derived class. + { + scoped_ptr scoper(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + scoped_ptr scoper_parent(scoper.Pass()); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper_parent.get()); + EXPECT_FALSE(scoper.get()); + + EXPECT_EQ(10, scoper_parent->SomeMeth(10)); + EXPECT_EQ(10, scoper_parent.get()->SomeMeth(10)); + EXPECT_EQ(10, (*scoper_parent).SomeMeth(10)); + } + EXPECT_EQ(0, constructed); + + // Test assignment from a scoped_ptr to a derived class. + { + scoped_ptr scoper(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + scoped_ptr scoper_parent; + scoper_parent = scoper.Pass(); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper_parent.get()); + EXPECT_FALSE(scoper.get()); + } + EXPECT_EQ(0, constructed); + + // Test construction of a scoped_ptr with an additional const annotation. + { + scoped_ptr scoper(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + scoped_ptr scoper_const(scoper.Pass()); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper_const.get()); + EXPECT_FALSE(scoper.get()); + + EXPECT_EQ(10, scoper_const->SomeMeth(10)); + EXPECT_EQ(10, scoper_const.get()->SomeMeth(10)); + EXPECT_EQ(10, (*scoper_const).SomeMeth(10)); + } + EXPECT_EQ(0, constructed); + + // Test assignment to a scoped_ptr with an additional const annotation. + { + scoped_ptr scoper(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + scoped_ptr scoper_const; + scoper_const = scoper.Pass(); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper_const.get()); + EXPECT_FALSE(scoper.get()); + } + EXPECT_EQ(0, constructed); + + // Test assignment to a scoped_ptr deleter of parent type. + { + // Custom deleters never touch these value. + double dummy_value, dummy_value2; + int deletes = 0; + int alternate_deletes = 0; + scoped_ptr scoper(&dummy_value, + CountingDeleter(&deletes)); + scoped_ptr scoper_child( + &dummy_value2, CountingDeleterChild(&alternate_deletes)); + + EXPECT_TRUE(scoper); + EXPECT_TRUE(scoper_child); + EXPECT_EQ(0, deletes); + EXPECT_EQ(0, alternate_deletes); + + // Test this compiles and correctly overwrites the deleter state. + scoper = scoper_child.Pass(); + EXPECT_TRUE(scoper); + EXPECT_FALSE(scoper_child); + EXPECT_EQ(1, deletes); + EXPECT_EQ(0, alternate_deletes); + + scoper.reset(); + EXPECT_FALSE(scoper); + EXPECT_FALSE(scoper_child); + EXPECT_EQ(1, deletes); + EXPECT_EQ(1, alternate_deletes); + + scoper_child.reset(&dummy_value); + EXPECT_TRUE(scoper_child); + EXPECT_EQ(1, deletes); + EXPECT_EQ(1, alternate_deletes); + scoped_ptr scoper_construct(scoper_child.Pass()); + EXPECT_TRUE(scoper_construct); + EXPECT_FALSE(scoper_child); + EXPECT_EQ(1, deletes); + EXPECT_EQ(1, alternate_deletes); + + scoper_construct.reset(); + EXPECT_EQ(1, deletes); + EXPECT_EQ(2, alternate_deletes); + } +} + +TEST(ScopedPtrTest, ScopedPtrWithArray) { + static const int kNumLoggers = 12; + + int constructed = 0; + + { + scoped_ptr scoper(new ConDecLogger[kNumLoggers]); + EXPECT_TRUE(scoper); + EXPECT_EQ(&scoper[0], scoper.get()); + for (int i = 0; i < kNumLoggers; ++i) { + scoper[i].SetPtr(&constructed); + } + EXPECT_EQ(12, constructed); + + EXPECT_EQ(10, scoper.get()->SomeMeth(10)); + EXPECT_EQ(10, scoper[2].SomeMeth(10)); + } + EXPECT_EQ(0, constructed); + + // Test reset() and release() + { + scoped_ptr scoper; + EXPECT_FALSE(scoper.get()); + EXPECT_FALSE(scoper.release()); + EXPECT_FALSE(scoper.get()); + scoper.reset(); + EXPECT_FALSE(scoper.get()); + + scoper.reset(new ConDecLogger[kNumLoggers]); + for (int i = 0; i < kNumLoggers; ++i) { + scoper[i].SetPtr(&constructed); + } + EXPECT_EQ(12, constructed); + scoper.reset(); + EXPECT_EQ(0, constructed); + + scoper.reset(new ConDecLogger[kNumLoggers]); + for (int i = 0; i < kNumLoggers; ++i) { + scoper[i].SetPtr(&constructed); + } + EXPECT_EQ(12, constructed); + ConDecLogger* ptr = scoper.release(); + EXPECT_EQ(12, constructed); + delete[] ptr; + EXPECT_EQ(0, constructed); + } + EXPECT_EQ(0, constructed); + + // Test swap(), ==, !=, and type-safe Boolean. + { + scoped_ptr scoper1; + scoped_ptr scoper2; + EXPECT_TRUE(scoper1 == scoper2.get()); + EXPECT_FALSE(scoper1 != scoper2.get()); + + ConDecLogger* loggers = new ConDecLogger[kNumLoggers]; + for (int i = 0; i < kNumLoggers; ++i) { + loggers[i].SetPtr(&constructed); + } + scoper1.reset(loggers); + EXPECT_TRUE(scoper1); + EXPECT_EQ(loggers, scoper1.get()); + EXPECT_FALSE(scoper2); + EXPECT_FALSE(scoper2.get()); + EXPECT_FALSE(scoper1 == scoper2.get()); + EXPECT_TRUE(scoper1 != scoper2.get()); + + scoper2.swap(scoper1); + EXPECT_EQ(loggers, scoper2.get()); + EXPECT_FALSE(scoper1.get()); + EXPECT_FALSE(scoper1 == scoper2.get()); + EXPECT_TRUE(scoper1 != scoper2.get()); + } + EXPECT_EQ(0, constructed); + + { + ConDecLogger* loggers = new ConDecLogger[kNumLoggers]; + scoped_ptr scoper(loggers); + EXPECT_TRUE(scoper); + for (int i = 0; i < kNumLoggers; ++i) { + scoper[i].SetPtr(&constructed); + } + EXPECT_EQ(kNumLoggers, constructed); + + // Test Pass() with constructor; + scoped_ptr scoper2(scoper.Pass()); + EXPECT_EQ(kNumLoggers, constructed); + + // Test Pass() with assignment; + scoped_ptr scoper3; + scoper3 = scoper2.Pass(); + EXPECT_EQ(kNumLoggers, constructed); + EXPECT_FALSE(scoper); + EXPECT_FALSE(scoper2); + EXPECT_TRUE(scoper3); + } + EXPECT_EQ(0, constructed); +} + +TEST(ScopedPtrTest, PassBehavior) { + int constructed = 0; + { + ConDecLogger* logger = new ConDecLogger(&constructed); + scoped_ptr scoper(logger); + EXPECT_EQ(1, constructed); + + // Test Pass() with constructor; + scoped_ptr scoper2(scoper.Pass()); + EXPECT_EQ(1, constructed); + + // Test Pass() with assignment; + scoped_ptr scoper3; + scoper3 = scoper2.Pass(); + EXPECT_EQ(1, constructed); + EXPECT_FALSE(scoper.get()); + EXPECT_FALSE(scoper2.get()); + EXPECT_TRUE(scoper3.get()); + } + + // Test uncaught Pass() does not leak. + { + ConDecLogger* logger = new ConDecLogger(&constructed); + scoped_ptr scoper(logger); + EXPECT_EQ(1, constructed); + + // Should auto-destruct logger by end of scope. + scoper.Pass(); + EXPECT_FALSE(scoper.get()); + } + EXPECT_EQ(0, constructed); + + // Test that passing to function which does nothing does not leak. + { + ConDecLogger* logger = new ConDecLogger(&constructed); + scoped_ptr scoper(logger); + EXPECT_EQ(1, constructed); + + // Should auto-destruct logger by end of scope. + GrabAndDrop(scoper.Pass()); + EXPECT_FALSE(scoper.get()); + } + EXPECT_EQ(0, constructed); +} + +TEST(ScopedPtrTest, ReturnTypeBehavior) { + int constructed = 0; + + // Test that we can return a scoped_ptr. + { + ConDecLogger* logger = new ConDecLogger(&constructed); + scoped_ptr scoper(logger); + EXPECT_EQ(1, constructed); + + PassThru(scoper.Pass()); + EXPECT_FALSE(scoper.get()); + } + EXPECT_EQ(0, constructed); + + // Test uncaught return type not leak. + { + ConDecLogger* logger = new ConDecLogger(&constructed); + scoped_ptr scoper(logger); + EXPECT_EQ(1, constructed); + + // Should auto-destruct logger by end of scope. + PassThru(scoper.Pass()); + EXPECT_FALSE(scoper.get()); + } + EXPECT_EQ(0, constructed); + + // Call TestReturnOfType() so the compiler doesn't warn for an unused + // function. + { + TestReturnOfType(&constructed); + } + EXPECT_EQ(0, constructed); +} + +TEST(ScopedPtrTest, PassAs) { + int constructed = 0; + { + scoped_ptr scoper(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + scoped_ptr scoper_parent; + scoper_parent = UpcastUsingPassAs(scoper.Pass()); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper_parent.get()); + EXPECT_FALSE(scoper.get()); + } + EXPECT_EQ(0, constructed); +} + +TEST(ScopedPtrTest, CustomDeleter) { + double dummy_value; // Custom deleter never touches this value. + int deletes = 0; + int alternate_deletes = 0; + + // Normal delete support. + { + deletes = 0; + scoped_ptr scoper(&dummy_value, + CountingDeleter(&deletes)); + EXPECT_EQ(0, deletes); + EXPECT_TRUE(scoper.get()); + } + EXPECT_EQ(1, deletes); + + // Test reset() and release(). + deletes = 0; + { + scoped_ptr scoper(NULL, + CountingDeleter(&deletes)); + EXPECT_FALSE(scoper.get()); + EXPECT_FALSE(scoper.release()); + EXPECT_FALSE(scoper.get()); + scoper.reset(); + EXPECT_FALSE(scoper.get()); + EXPECT_EQ(0, deletes); + + scoper.reset(&dummy_value); + scoper.reset(); + EXPECT_EQ(1, deletes); + + scoper.reset(&dummy_value); + EXPECT_EQ(&dummy_value, scoper.release()); + } + EXPECT_EQ(1, deletes); + + // Test get_deleter(). + deletes = 0; + alternate_deletes = 0; + { + scoped_ptr scoper(&dummy_value, + CountingDeleter(&deletes)); + // Call deleter manually. + EXPECT_EQ(0, deletes); + scoper.get_deleter()(&dummy_value); + EXPECT_EQ(1, deletes); + + // Deleter is still there after reset. + scoper.reset(); + EXPECT_EQ(2, deletes); + scoper.get_deleter()(&dummy_value); + EXPECT_EQ(3, deletes); + + // Deleter can be assigned into (matches C++11 unique_ptr<> spec). + scoper.get_deleter() = CountingDeleter(&alternate_deletes); + scoper.reset(&dummy_value); + EXPECT_EQ(0, alternate_deletes); + + } + EXPECT_EQ(3, deletes); + EXPECT_EQ(1, alternate_deletes); + + // Test operator= deleter support. + deletes = 0; + alternate_deletes = 0; + { + double dummy_value2; + scoped_ptr scoper(&dummy_value, + CountingDeleter(&deletes)); + scoped_ptr scoper2( + &dummy_value2, + CountingDeleter(&alternate_deletes)); + EXPECT_EQ(0, deletes); + EXPECT_EQ(0, alternate_deletes); + + // Pass the second deleter through a constructor and an operator=. Then + // reinitialize the empty scopers to ensure that each one is deleting + // properly. + scoped_ptr scoper3(scoper2.Pass()); + scoper = scoper3.Pass(); + EXPECT_EQ(1, deletes); + + scoper2.reset(&dummy_value2); + scoper3.reset(&dummy_value2); + EXPECT_EQ(0, alternate_deletes); + + } + EXPECT_EQ(1, deletes); + EXPECT_EQ(3, alternate_deletes); + + // Test swap(), ==, !=, and type-safe Boolean. + { + scoped_ptr scoper1(NULL, + CountingDeleter(&deletes)); + scoped_ptr scoper2(NULL, + CountingDeleter(&deletes)); + EXPECT_TRUE(scoper1 == scoper2.get()); + EXPECT_FALSE(scoper1 != scoper2.get()); + + scoper1.reset(&dummy_value); + EXPECT_TRUE(scoper1); + EXPECT_EQ(&dummy_value, scoper1.get()); + EXPECT_FALSE(scoper2); + EXPECT_FALSE(scoper2.get()); + EXPECT_FALSE(scoper1 == scoper2.get()); + EXPECT_TRUE(scoper1 != scoper2.get()); + + scoper2.swap(scoper1); + EXPECT_EQ(&dummy_value, scoper2.get()); + EXPECT_FALSE(scoper1.get()); + EXPECT_FALSE(scoper1 == scoper2.get()); + EXPECT_TRUE(scoper1 != scoper2.get()); + } +} + +// Sanity check test for overloaded new and delete operators. Does not do full +// coverage of reset/release/Pass() operations as that is redundant with the +// above. +TEST(ScopedPtrTest, OverloadedNewAndDelete) { + { + OverloadedNewAndDelete::ResetCounters(); + scoped_ptr scoper(new OverloadedNewAndDelete()); + EXPECT_TRUE(scoper.get()); + + scoped_ptr scoper2(scoper.Pass()); + } + EXPECT_EQ(1, OverloadedNewAndDelete::delete_count()); + EXPECT_EQ(1, OverloadedNewAndDelete::new_count()); +} diff --git a/test/scoped_temp_dir_unittest.cc b/test/scoped_temp_dir_unittest.cc new file mode 100644 index 0000000..9338d2e --- /dev/null +++ b/test/scoped_temp_dir_unittest.cc @@ -0,0 +1,113 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "butil/file_util.h" +#include "butil/files/file.h" +#include "butil/files/scoped_temp_dir.h" +#include + +namespace butil { + +TEST(ScopedTempDir, FullPath) { + FilePath test_path; + butil::CreateNewTempDirectory(FILE_PATH_LITERAL("scoped_temp_dir"), + &test_path); + + // Against an existing dir, it should get destroyed when leaving scope. + EXPECT_TRUE(DirectoryExists(test_path)); + { + ScopedTempDir dir; + EXPECT_TRUE(dir.Set(test_path)); + EXPECT_TRUE(dir.IsValid()); + } + EXPECT_FALSE(DirectoryExists(test_path)); + + { + ScopedTempDir dir; + EXPECT_TRUE(dir.Set(test_path)); + // Now the dir doesn't exist, so ensure that it gets created. + EXPECT_TRUE(DirectoryExists(test_path)); + // When we call Release(), it shouldn't get destroyed when leaving scope. + FilePath path = dir.Take(); + EXPECT_EQ(path.value(), test_path.value()); + EXPECT_FALSE(dir.IsValid()); + } + EXPECT_TRUE(DirectoryExists(test_path)); + + // Clean up. + { + ScopedTempDir dir; + EXPECT_TRUE(dir.Set(test_path)); + } + EXPECT_FALSE(DirectoryExists(test_path)); +} + +TEST(ScopedTempDir, TempDir) { + // In this case, just verify that a directory was created and that it's a + // child of TempDir. + FilePath test_path; + { + ScopedTempDir dir; + EXPECT_TRUE(dir.CreateUniqueTempDir()); + test_path = dir.path(); + EXPECT_TRUE(DirectoryExists(test_path)); + FilePath tmp_dir; + EXPECT_TRUE(butil::GetTempDir(&tmp_dir)); + EXPECT_TRUE(test_path.value().find(tmp_dir.value()) != std::string::npos); + } + EXPECT_FALSE(DirectoryExists(test_path)); +} + +TEST(ScopedTempDir, UniqueTempDirUnderPath) { + // Create a path which will contain a unique temp path. + FilePath base_path; + ASSERT_TRUE(butil::CreateNewTempDirectory(FILE_PATH_LITERAL("base_dir"), + &base_path)); + + FilePath test_path; + { + ScopedTempDir dir; + EXPECT_TRUE(dir.CreateUniqueTempDirUnderPath(base_path)); + test_path = dir.path(); + EXPECT_TRUE(DirectoryExists(test_path)); + EXPECT_TRUE(base_path.IsParent(test_path)); + EXPECT_TRUE(test_path.value().find(base_path.value()) != std::string::npos); + } + EXPECT_FALSE(DirectoryExists(test_path)); + butil::DeleteFile(base_path, true); +} + +TEST(ScopedTempDir, MultipleInvocations) { + ScopedTempDir dir; + EXPECT_TRUE(dir.CreateUniqueTempDir()); + EXPECT_FALSE(dir.CreateUniqueTempDir()); + EXPECT_TRUE(dir.Delete()); + EXPECT_TRUE(dir.CreateUniqueTempDir()); + EXPECT_FALSE(dir.CreateUniqueTempDir()); + ScopedTempDir other_dir; + EXPECT_TRUE(other_dir.Set(dir.Take())); + EXPECT_TRUE(dir.CreateUniqueTempDir()); + EXPECT_FALSE(dir.CreateUniqueTempDir()); + EXPECT_FALSE(other_dir.CreateUniqueTempDir()); +} + +#if defined(OS_WIN) +TEST(ScopedTempDir, LockedTempDir) { + ScopedTempDir dir; + EXPECT_TRUE(dir.CreateUniqueTempDir()); + butil::File file(dir.path().Append(FILE_PATH_LITERAL("temp")), + butil::File::FLAG_CREATE_ALWAYS | butil::File::FLAG_WRITE); + EXPECT_TRUE(file.IsValid()); + EXPECT_EQ(butil::File::FILE_OK, file.error_details()); + EXPECT_FALSE(dir.Delete()); // We should not be able to delete. + EXPECT_FALSE(dir.path().empty()); // We should still have a valid path. + file.Close(); + // Now, we should be able to delete. + EXPECT_TRUE(dir.Delete()); +} +#endif // defined(OS_WIN) + +} // namespace butil diff --git a/test/scoped_vector_unittest.cc b/test/scoped_vector_unittest.cc new file mode 100644 index 0000000..be0e99e --- /dev/null +++ b/test/scoped_vector_unittest.cc @@ -0,0 +1,319 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/memory/scoped_vector.h" + +#include "butil/memory/scoped_ptr.h" +#include + +namespace { + +// The LifeCycleObject notifies its Observer upon construction & destruction. +class LifeCycleObject { + public: + class Observer { + public: + virtual void OnLifeCycleConstruct(LifeCycleObject* o) = 0; + virtual void OnLifeCycleDestroy(LifeCycleObject* o) = 0; + + protected: + virtual ~Observer() {} + }; + + ~LifeCycleObject() { + observer_->OnLifeCycleDestroy(this); + } + + private: + friend class LifeCycleWatcher; + + explicit LifeCycleObject(Observer* observer) + : observer_(observer) { + observer_->OnLifeCycleConstruct(this); + } + + Observer* observer_; + + DISALLOW_COPY_AND_ASSIGN(LifeCycleObject); +}; + +// The life cycle states we care about for the purposes of testing ScopedVector +// against objects. +enum LifeCycleState { + LC_INITIAL, + LC_CONSTRUCTED, + LC_DESTROYED, +}; + +// Because we wish to watch the life cycle of an object being constructed and +// destroyed, and further wish to test expectations against the state of that +// object, we cannot save state in that object itself. Instead, we use this +// pairing of the watcher, which observes the object and notifies of +// construction & destruction. Since we also may be testing assumptions about +// things not getting freed, this class also acts like a scoping object and +// deletes the |constructed_life_cycle_object_|, if any when the +// LifeCycleWatcher is destroyed. To keep this simple, the only expected state +// changes are: +// INITIAL -> CONSTRUCTED -> DESTROYED. +// Anything more complicated than that should start another test. +class LifeCycleWatcher : public LifeCycleObject::Observer { + public: + LifeCycleWatcher() : life_cycle_state_(LC_INITIAL) {} + virtual ~LifeCycleWatcher() {} + + // Assert INITIAL -> CONSTRUCTED and no LifeCycleObject associated with this + // LifeCycleWatcher. + virtual void OnLifeCycleConstruct(LifeCycleObject* object) OVERRIDE { + ASSERT_EQ(LC_INITIAL, life_cycle_state_); + ASSERT_EQ(NULL, constructed_life_cycle_object_.get()); + life_cycle_state_ = LC_CONSTRUCTED; + constructed_life_cycle_object_.reset(object); + } + + // Assert CONSTRUCTED -> DESTROYED and the |object| being destroyed is the + // same one we saw constructed. + virtual void OnLifeCycleDestroy(LifeCycleObject* object) OVERRIDE { + ASSERT_EQ(LC_CONSTRUCTED, life_cycle_state_); + LifeCycleObject* constructed_life_cycle_object = + constructed_life_cycle_object_.release(); + ASSERT_EQ(constructed_life_cycle_object, object); + life_cycle_state_ = LC_DESTROYED; + } + + LifeCycleState life_cycle_state() const { return life_cycle_state_; } + + // Factory method for creating a new LifeCycleObject tied to this + // LifeCycleWatcher. + LifeCycleObject* NewLifeCycleObject() { + return new LifeCycleObject(this); + } + + // Returns true iff |object| is the same object that this watcher is tracking. + bool IsWatching(LifeCycleObject* object) const { + return object == constructed_life_cycle_object_.get(); + } + + private: + LifeCycleState life_cycle_state_; + scoped_ptr constructed_life_cycle_object_; + + DISALLOW_COPY_AND_ASSIGN(LifeCycleWatcher); +}; + +TEST(ScopedVectorTest, LifeCycleWatcher) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + LifeCycleObject* object = watcher.NewLifeCycleObject(); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + delete object; + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); +} + +TEST(ScopedVectorTest, PopBack) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + butil::ScopedVector scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + scoped_vector.pop_back(); + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); + EXPECT_TRUE(scoped_vector.empty()); +} + +TEST(ScopedVectorTest, Clear) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + butil::ScopedVector scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + scoped_vector.clear(); + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); + EXPECT_TRUE(scoped_vector.empty()); +} + +TEST(ScopedVectorTest, WeakClear) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + butil::ScopedVector scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + scoped_vector.weak_clear(); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + EXPECT_TRUE(scoped_vector.empty()); +} + +TEST(ScopedVectorTest, ResizeShrink) { + LifeCycleWatcher first_watcher; + EXPECT_EQ(LC_INITIAL, first_watcher.life_cycle_state()); + LifeCycleWatcher second_watcher; + EXPECT_EQ(LC_INITIAL, second_watcher.life_cycle_state()); + butil::ScopedVector scoped_vector; + + scoped_vector.push_back(first_watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, first_watcher.life_cycle_state()); + EXPECT_EQ(LC_INITIAL, second_watcher.life_cycle_state()); + EXPECT_TRUE(first_watcher.IsWatching(scoped_vector[0])); + EXPECT_FALSE(second_watcher.IsWatching(scoped_vector[0])); + + scoped_vector.push_back(second_watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, first_watcher.life_cycle_state()); + EXPECT_EQ(LC_CONSTRUCTED, second_watcher.life_cycle_state()); + EXPECT_FALSE(first_watcher.IsWatching(scoped_vector[1])); + EXPECT_TRUE(second_watcher.IsWatching(scoped_vector[1])); + + // Test that shrinking a vector deletes elements in the disappearing range. + scoped_vector.resize(1); + EXPECT_EQ(LC_CONSTRUCTED, first_watcher.life_cycle_state()); + EXPECT_EQ(LC_DESTROYED, second_watcher.life_cycle_state()); + EXPECT_EQ(1u, scoped_vector.size()); + EXPECT_TRUE(first_watcher.IsWatching(scoped_vector[0])); +} + +TEST(ScopedVectorTest, ResizeGrow) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + butil::ScopedVector scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + + scoped_vector.resize(5); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + ASSERT_EQ(5u, scoped_vector.size()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector[0])); + EXPECT_FALSE(watcher.IsWatching(scoped_vector[1])); + EXPECT_FALSE(watcher.IsWatching(scoped_vector[2])); + EXPECT_FALSE(watcher.IsWatching(scoped_vector[3])); + EXPECT_FALSE(watcher.IsWatching(scoped_vector[4])); +} + +TEST(ScopedVectorTest, Scope) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + { + butil::ScopedVector scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + } + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); +} + +TEST(ScopedVectorTest, Scope2) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + { + butil::ScopedVector scoped_vector{ watcher.NewLifeCycleObject() }; + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + } + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); +} + +TEST(ScopedVectorTest, MoveConstruct) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + { + butil::ScopedVector scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + EXPECT_FALSE(scoped_vector.empty()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + + butil::ScopedVector scoped_vector_copy(scoped_vector.Pass()); + EXPECT_TRUE(scoped_vector.empty()); + EXPECT_FALSE(scoped_vector_copy.empty()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector_copy.back())); + + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + } + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); +} + +TEST(ScopedVectorTest, MoveAssign) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + { + butil::ScopedVector scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + butil::ScopedVector scoped_vector_assign; + EXPECT_FALSE(scoped_vector.empty()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + + scoped_vector_assign = scoped_vector.Pass(); + EXPECT_TRUE(scoped_vector.empty()); + EXPECT_FALSE(scoped_vector_assign.empty()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector_assign.back())); + + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + } + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); +} + +class DeleteCounter { + public: + explicit DeleteCounter(int* deletes) + : deletes_(deletes) { + } + + ~DeleteCounter() { + (*deletes_)++; + } + + void VoidMethod0() {} + + private: + int* const deletes_; + + DISALLOW_COPY_AND_ASSIGN(DeleteCounter); +}; + +template +butil::ScopedVector PassThru(butil::ScopedVector scoper) { + return scoper.Pass(); +} + +TEST(ScopedVectorTest, Passed) { + int deletes = 0; + butil::ScopedVector deleter_vector; + deleter_vector.push_back(new DeleteCounter(&deletes)); + EXPECT_EQ(0, deletes); + + EXPECT_EQ(0, deletes); + butil::ScopedVector result = deleter_vector.Pass(); + EXPECT_EQ(0, deletes); + result.clear(); + EXPECT_EQ(1, deletes); +}; + +TEST(ScopedVectorTest, InsertRange) { + LifeCycleWatcher watchers[5]; + + std::vector vec; + for(LifeCycleWatcher* it = watchers; it != watchers + arraysize(watchers); + ++it) { + EXPECT_EQ(LC_INITIAL, it->life_cycle_state()); + vec.push_back(it->NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, it->life_cycle_state()); + } + // Start scope for ScopedVector. + { + butil::ScopedVector scoped_vector; + scoped_vector.insert(scoped_vector.end(), vec.begin() + 1, vec.begin() + 3); + for(LifeCycleWatcher* it = watchers; it != watchers + arraysize(watchers); + ++it) + EXPECT_EQ(LC_CONSTRUCTED, it->life_cycle_state()); + } + for(LifeCycleWatcher* it = watchers; it != watchers + 1; ++it) + EXPECT_EQ(LC_CONSTRUCTED, it->life_cycle_state()); + for(LifeCycleWatcher* it = watchers + 1; it != watchers + 3; ++it) + EXPECT_EQ(LC_DESTROYED, it->life_cycle_state()); + for(LifeCycleWatcher* it = watchers + 3; it != watchers + arraysize(watchers); + ++it) + EXPECT_EQ(LC_CONSTRUCTED, it->life_cycle_state()); +} + +} // namespace diff --git a/test/security_unittest.cc b/test/security_unittest.cc new file mode 100644 index 0000000..739b6c7 --- /dev/null +++ b/test/security_unittest.cc @@ -0,0 +1,305 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "butil/file_util.h" +#include "butil/logging.h" +#include "butil/memory/scoped_ptr.h" +#include "butil/build_config.h" +#include + +#if defined(OS_POSIX) +#include +#include +#endif +#include "butil/compiler_specific.h" + +#ifndef BUTIL_USE_ASAN + +using std::nothrow; +using std::numeric_limits; + +namespace { + +// This function acts as a compiler optimization barrier. We use it to +// prevent the compiler from making an expression a compile-time constant. +// We also use it so that the compiler doesn't discard certain return values +// as something we don't need (see the comment with calloc below). +template +Type HideValueFromCompiler(volatile Type value) { +#if defined(__GNUC__) + // In a GCC compatible compiler (GCC or Clang), make this compiler barrier + // more robust than merely using "volatile". + __asm__ volatile ("" : "+r" (value)); +#endif // __GNUC__ + return value; +} + +// - NO_TCMALLOC (should be defined if compiled with use_allocator!="tcmalloc") +// - ADDRESS_SANITIZER and SYZYASAN because they have their own memory allocator +// - IOS does not use tcmalloc +// - OS_MACOSX does not use tcmalloc +#if !defined(NO_TCMALLOC) && !defined(ADDRESS_SANITIZER) && \ + !defined(OS_IOS) && !defined(OS_MACOSX) && !defined(SYZYASAN) + #define TCMALLOC_TEST(function) function +#else + #define TCMALLOC_TEST(function) DISABLED_##function +#endif + +// TODO(jln): switch to std::numeric_limits::max() when we switch to +// C++11. +const size_t kTooBigAllocSize = INT_MAX; + +// Detect runtime TCMalloc bypasses. +bool IsTcMallocBypassed() { +#if defined(OS_LINUX) + // This should detect a TCMalloc bypass from Valgrind. + char* g_slice = getenv("G_SLICE"); + if (g_slice && !strcmp(g_slice, "always-malloc")) + return true; +#elif defined(OS_WIN) + // This should detect a TCMalloc bypass from setting + // the CHROME_ALLOCATOR environment variable. + char* allocator = getenv("CHROME_ALLOCATOR"); + if (allocator && strcmp(allocator, "tcmalloc")) + return true; +#endif + return false; +} + +bool CallocDiesOnOOM() { +// The sanitizers' calloc dies on OOM instead of returning NULL. +// The wrapper function in butil/process_util_linux.cc that is used when we +// compile without TCMalloc will just die on OOM instead of returning NULL. +#if defined(ADDRESS_SANITIZER) || \ + defined(MEMORY_SANITIZER) || \ + defined(THREAD_SANITIZER) || \ + (defined(OS_LINUX) && defined(NO_TCMALLOC)) + return true; +#else + return false; +#endif +} + +// Fake test that allow to know the state of TCMalloc by looking at bots. +TEST(SecurityTest, TCMALLOC_TEST(IsTCMallocDynamicallyBypassed)) { + printf("Malloc is dynamically bypassed: %s\n", + IsTcMallocBypassed() ? "yes." : "no."); +} + +// The MemoryAllocationRestrictions* tests test that we can not allocate a +// memory range that cannot be indexed via an int. This is used to mitigate +// vulnerabilities in libraries that use int instead of size_t. See +// crbug.com/169327. + +TEST(SecurityTest, TCMALLOC_TEST(MemoryAllocationRestrictionsMalloc)) { + if (!IsTcMallocBypassed()) { + scoped_ptr ptr(static_cast( + HideValueFromCompiler(malloc(kTooBigAllocSize)))); + ASSERT_TRUE(!ptr); + } +} + +TEST(SecurityTest, TCMALLOC_TEST(MemoryAllocationRestrictionsCalloc)) { + if (!IsTcMallocBypassed()) { + scoped_ptr ptr(static_cast( + HideValueFromCompiler(calloc(kTooBigAllocSize, 1)))); + ASSERT_TRUE(!ptr); + } +} + +TEST(SecurityTest, TCMALLOC_TEST(MemoryAllocationRestrictionsRealloc)) { + if (!IsTcMallocBypassed()) { + char* orig_ptr = static_cast(malloc(1)); + ASSERT_TRUE(orig_ptr); + scoped_ptr ptr(static_cast( + HideValueFromCompiler(realloc(orig_ptr, kTooBigAllocSize)))); + ASSERT_TRUE(!ptr); + // If realloc() did not succeed, we need to free orig_ptr. + free(orig_ptr); + } +} + +typedef struct { + char large_array[kTooBigAllocSize]; +} VeryLargeStruct; + +TEST(SecurityTest, TCMALLOC_TEST(MemoryAllocationRestrictionsNew)) { + if (!IsTcMallocBypassed()) { + scoped_ptr ptr( + HideValueFromCompiler(new (nothrow) VeryLargeStruct)); + ASSERT_TRUE(!ptr); + } +} + +TEST(SecurityTest, TCMALLOC_TEST(MemoryAllocationRestrictionsNewArray)) { + if (!IsTcMallocBypassed()) { + scoped_ptr ptr( + HideValueFromCompiler(new (nothrow) char[kTooBigAllocSize])); + ASSERT_TRUE(!ptr); + } +} + +// The tests bellow check for overflows in new[] and calloc(). + +#if defined(OS_IOS) || defined(OS_WIN) || defined(THREAD_SANITIZER) + #define DISABLE_ON_IOS_AND_WIN_AND_TSAN(function) DISABLED_##function +#else + #define DISABLE_ON_IOS_AND_WIN_AND_TSAN(function) function +#endif + + +// FIXME(gejun): following logic of the case, it definitely should crash, I don't +// know why it's judged as failure. +#if defined(FixedNewOverflow) + +// There are platforms where these tests are known to fail. We would like to +// be able to easily check the status on the bots, but marking tests as +// FAILS_ is too clunky. +void OverflowTestsSoftExpectTrue(bool overflow_detected) { + if (!overflow_detected) { +#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_MACOSX) + // Sadly, on Linux, Android, and OSX we don't have a good story yet. Don't + // fail the test, but report. + printf("Platform has overflow: %s\n", + !overflow_detected ? "yes." : "no."); +#else + // Otherwise, fail the test. (Note: EXPECT are ok in subfunctions, ASSERT + // aren't). + EXPECT_TRUE(overflow_detected); +#endif + } +} + +// Test array[TooBig][X] and array[X][TooBig] allocations for int overflows. +// IOS doesn't honor nothrow, so disable the test there. +// Crashes on Windows Dbg builds, disable there as well. +TEST(SecurityTest, DISABLE_ON_IOS_AND_WIN_AND_TSAN(NewOverflow)) { + const size_t kArraySize = 4096; + // We want something "dynamic" here, so that the compiler doesn't + // immediately reject crazy arrays. + const size_t kDynamicArraySize = HideValueFromCompiler(kArraySize); + // numeric_limits are still not constexpr until we switch to C++11, so we + // use an ugly cast. + const size_t kMaxSizeT = ~static_cast(0); + ASSERT_EQ(numeric_limits::max(), kMaxSizeT); + const size_t kArraySize2 = kMaxSizeT / kArraySize + 10; + const size_t kDynamicArraySize2 = HideValueFromCompiler(kArraySize2); + { + scoped_ptr array_pointer(new (nothrow) + char[kDynamicArraySize2][kArraySize]); + OverflowTestsSoftExpectTrue(!array_pointer); + } + // On windows, the compiler prevents static array sizes of more than + // 0x7fffffff (error C2148). +#if !defined(OS_WIN) || !defined(ARCH_CPU_64_BITS) + { + scoped_ptr array_pointer(new (nothrow) + char[kDynamicArraySize][kArraySize2]); + OverflowTestsSoftExpectTrue(!array_pointer); + } +#endif // !defined(OS_WIN) || !defined(ARCH_CPU_64_BITS) +} +#endif + +// Call calloc(), eventually free the memory and return whether or not +// calloc() did succeed. +bool CallocReturnsNull(size_t nmemb, size_t size) { + scoped_ptr array_pointer( + static_cast(calloc(nmemb, size))); + // We need the call to HideValueFromCompiler(): we have seen LLVM + // optimize away the call to calloc() entirely and assume + // the pointer to not be NULL. + return HideValueFromCompiler(array_pointer.get()) == NULL; +} + +// Test if calloc() can overflow. +TEST(SecurityTest, CallocOverflow) { + const size_t kArraySize = 4096; + const size_t kMaxSizeT = numeric_limits::max(); + const size_t kArraySize2 = kMaxSizeT / kArraySize + 10; + if (!CallocDiesOnOOM()) { + EXPECT_TRUE(CallocReturnsNull(kArraySize, kArraySize2)); + EXPECT_TRUE(CallocReturnsNull(kArraySize2, kArraySize)); + } else { + // It's also ok for calloc to just terminate the process. + // NOTE(gejun): butil/process/memory.cc is not linked right now, + // disable following assertions on calloc +//#if defined(GTEST_HAS_DEATH_TEST) +// EXPECT_DEATH(CallocReturnsNull(kArraySize, kArraySize2), ""); +// EXPECT_DEATH(CallocReturnsNull(kArraySize2, kArraySize), ""); +//#endif // GTEST_HAS_DEATH_TEST + } +} + +#if defined(OS_LINUX) && defined(__x86_64__) +// Check if ptr1 and ptr2 are separated by less than size chars. +bool ArePointersToSameArea(void* ptr1, void* ptr2, size_t size) { + ptrdiff_t ptr_diff = reinterpret_cast(std::max(ptr1, ptr2)) - + reinterpret_cast(std::min(ptr1, ptr2)); + return static_cast(ptr_diff) <= size; +} + +// Check if TCMalloc uses an underlying random memory allocator. +TEST(SecurityTest, TCMALLOC_TEST(RandomMemoryAllocations)) { + if (IsTcMallocBypassed()) + return; + size_t kPageSize = 4096; // We support x86_64 only. + // Check that malloc() returns an address that is neither the kernel's + // un-hinted mmap area, nor the current brk() area. The first malloc() may + // not be at a random address because TCMalloc will first exhaust any memory + // that it has allocated early on, before starting the sophisticated + // allocators. + void* default_mmap_heap_address = + mmap(0, kPageSize, PROT_READ|PROT_WRITE, + MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + ASSERT_NE(default_mmap_heap_address, + static_cast(MAP_FAILED)); + ASSERT_EQ(munmap(default_mmap_heap_address, kPageSize), 0); + void* brk_heap_address = sbrk(0); + ASSERT_NE(brk_heap_address, reinterpret_cast(-1)); + ASSERT_TRUE(brk_heap_address != NULL); + // 1 MB should get us past what TCMalloc pre-allocated before initializing + // the sophisticated allocators. + size_t kAllocSize = 1<<20; + scoped_ptr ptr( + static_cast(malloc(kAllocSize))); + ASSERT_TRUE(ptr != NULL); + // If two pointers are separated by less than 512MB, they are considered + // to be in the same area. + // Our random pointer could be anywhere within 0x3fffffffffff (46bits), + // and we are checking that it's not withing 1GB (30 bits) from two + // addresses (brk and mmap heap). We have roughly one chance out of + // 2^15 to flake. + const size_t kAreaRadius = 1<<29; + bool in_default_mmap_heap = ArePointersToSameArea( + ptr.get(), default_mmap_heap_address, kAreaRadius); + EXPECT_FALSE(in_default_mmap_heap); + + bool in_default_brk_heap = ArePointersToSameArea( + ptr.get(), brk_heap_address, kAreaRadius); + EXPECT_FALSE(in_default_brk_heap); + + // In the implementation, we always mask our random addresses with + // kRandomMask, so we use it as an additional detection mechanism. + const uintptr_t kRandomMask = 0x3fffffffffffULL; + bool impossible_random_address = + reinterpret_cast(ptr.get()) & ~kRandomMask; + EXPECT_FALSE(impossible_random_address); +} + +#endif // defined(OS_LINUX) && defined(__x86_64__) + +} // namespace + +#endif // BUTIL_USE_ASAN diff --git a/test/sha1_unittest.cc b/test/sha1_unittest.cc new file mode 100644 index 0000000..1954649 --- /dev/null +++ b/test/sha1_unittest.cc @@ -0,0 +1,108 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/sha1.h" + +#include + +#include "butil/basictypes.h" +#include + +TEST(SHA1Test, Test1) { + // Example A.1 from FIPS 180-2: one-block message. + std::string input = "abc"; + + int expected[] = { 0xa9, 0x99, 0x3e, 0x36, + 0x47, 0x06, 0x81, 0x6a, + 0xba, 0x3e, 0x25, 0x71, + 0x78, 0x50, 0xc2, 0x6c, + 0x9c, 0xd0, 0xd8, 0x9d }; + + std::string output = butil::SHA1HashString(input); + for (size_t i = 0; i < butil::kSHA1Length; i++) + EXPECT_EQ(expected[i], output[i] & 0xFF); +} + +TEST(SHA1Test, Test2) { + // Example A.2 from FIPS 180-2: multi-block message. + std::string input = + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"; + + int expected[] = { 0x84, 0x98, 0x3e, 0x44, + 0x1c, 0x3b, 0xd2, 0x6e, + 0xba, 0xae, 0x4a, 0xa1, + 0xf9, 0x51, 0x29, 0xe5, + 0xe5, 0x46, 0x70, 0xf1 }; + + std::string output = butil::SHA1HashString(input); + for (size_t i = 0; i < butil::kSHA1Length; i++) + EXPECT_EQ(expected[i], output[i] & 0xFF); +} + +TEST(SHA1Test, Test3) { + // Example A.3 from FIPS 180-2: long message. + std::string input(1000000, 'a'); + + int expected[] = { 0x34, 0xaa, 0x97, 0x3c, + 0xd4, 0xc4, 0xda, 0xa4, + 0xf6, 0x1e, 0xeb, 0x2b, + 0xdb, 0xad, 0x27, 0x31, + 0x65, 0x34, 0x01, 0x6f }; + + std::string output = butil::SHA1HashString(input); + for (size_t i = 0; i < butil::kSHA1Length; i++) + EXPECT_EQ(expected[i], output[i] & 0xFF); +} + +TEST(SHA1Test, Test1Bytes) { + // Example A.1 from FIPS 180-2: one-block message. + std::string input = "abc"; + unsigned char output[butil::kSHA1Length]; + + unsigned char expected[] = { 0xa9, 0x99, 0x3e, 0x36, + 0x47, 0x06, 0x81, 0x6a, + 0xba, 0x3e, 0x25, 0x71, + 0x78, 0x50, 0xc2, 0x6c, + 0x9c, 0xd0, 0xd8, 0x9d }; + + butil::SHA1HashBytes(reinterpret_cast(input.c_str()), + input.length(), output); + for (size_t i = 0; i < butil::kSHA1Length; i++) + EXPECT_EQ(expected[i], output[i]); +} + +TEST(SHA1Test, Test2Bytes) { + // Example A.2 from FIPS 180-2: multi-block message. + std::string input = + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"; + unsigned char output[butil::kSHA1Length]; + + unsigned char expected[] = { 0x84, 0x98, 0x3e, 0x44, + 0x1c, 0x3b, 0xd2, 0x6e, + 0xba, 0xae, 0x4a, 0xa1, + 0xf9, 0x51, 0x29, 0xe5, + 0xe5, 0x46, 0x70, 0xf1 }; + + butil::SHA1HashBytes(reinterpret_cast(input.c_str()), + input.length(), output); + for (size_t i = 0; i < butil::kSHA1Length; i++) + EXPECT_EQ(expected[i], output[i]); +} + +TEST(SHA1Test, Test3Bytes) { + // Example A.3 from FIPS 180-2: long message. + std::string input(1000000, 'a'); + unsigned char output[butil::kSHA1Length]; + + unsigned char expected[] = { 0x34, 0xaa, 0x97, 0x3c, + 0xd4, 0xc4, 0xda, 0xa4, + 0xf6, 0x1e, 0xeb, 0x2b, + 0xdb, 0xad, 0x27, 0x31, + 0x65, 0x34, 0x01, 0x6f }; + + butil::SHA1HashBytes(reinterpret_cast(input.c_str()), + input.length(), output); + for (size_t i = 0; i < butil::kSHA1Length; i++) + EXPECT_EQ(expected[i], output[i]); +} diff --git a/test/shared_memory_unittest.cc b/test/shared_memory_unittest.cc new file mode 100644 index 0000000..09b0936 --- /dev/null +++ b/test/shared_memory_unittest.cc @@ -0,0 +1,686 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/basictypes.h" +#include "butil/memory/scoped_ptr.h" +#include "butil/memory/shared_memory.h" +#include "butil/process/kill.h" +#include "butil/rand_util.h" +#include "butil/strings/string_number_conversions.h" +#include "butil/sys_info.h" +#include "multiprocess_test.h" +#include "butil/threading/platform_thread.h" +#include "butil/time/time.h" +#include +#include "multiprocess_func_list.h" + +#if defined(OS_MACOSX) +#include "butil/mac/scoped_nsautorelease_pool.h" +#endif + +#if defined(OS_POSIX) +#include +#include +#include +#include +#include +#include +#endif + +#if defined(OS_WIN) +#include "butil/win/scoped_handle.h" +#endif + +static const int kNumThreads = 5; +#if !defined(OS_IOS) // iOS does not allow multiple processes. +static const int kNumTasks = 5; +#endif + +namespace butil { + +namespace { + +// Each thread will open the shared memory. Each thread will take a different 4 +// byte int pointer, and keep changing it, with some small pauses in between. +// Verify that each thread's value in the shared memory is always correct. +class MultipleThreadMain : public PlatformThread::Delegate { + public: + explicit MultipleThreadMain(int16_t id) : id_(id) {} + virtual ~MultipleThreadMain() {} + + static void CleanUp() { + SharedMemory memory; + memory.Delete(s_test_name_); + } + + // PlatformThread::Delegate interface. + virtual void ThreadMain() OVERRIDE { +#if defined(OS_MACOSX) + mac::ScopedNSAutoreleasePool pool; +#endif + const uint32_t kDataSize = 1024; + SharedMemory memory; + bool rv = memory.CreateNamedDeprecated(s_test_name_, true, kDataSize); + EXPECT_TRUE(rv); + rv = memory.Map(kDataSize); + EXPECT_TRUE(rv); + int *ptr = static_cast(memory.memory()) + id_; + EXPECT_EQ(0, *ptr); + + for (int idx = 0; idx < 100; idx++) { + *ptr = idx; + PlatformThread::Sleep(butil::TimeDelta::FromMilliseconds(1)); + EXPECT_EQ(*ptr, idx); + } + // Reset back to 0 for the next test that uses the same name. + *ptr = 0; + + memory.Close(); + } + + private: + int16_t id_; + + static const char* const s_test_name_; + + DISALLOW_COPY_AND_ASSIGN(MultipleThreadMain); +}; + +const char* const MultipleThreadMain::s_test_name_ = + "SharedMemoryOpenThreadTest"; + +// TODO(port): +// This test requires the ability to pass file descriptors between processes. +// We haven't done that yet in Chrome for POSIX. +#if defined(OS_WIN) +// Each thread will open the shared memory. Each thread will take the memory, +// and keep changing it while trying to lock it, with some small pauses in +// between. Verify that each thread's value in the shared memory is always +// correct. +class MultipleLockThread : public PlatformThread::Delegate { + public: + explicit MultipleLockThread(int id) : id_(id) {} + virtual ~MultipleLockThread() {} + + // PlatformThread::Delegate interface. + virtual void ThreadMain() OVERRIDE { + const uint32_t kDataSize = sizeof(int); + SharedMemoryHandle handle = NULL; + { + SharedMemory memory1; + EXPECT_TRUE(memory1.CreateNamedDeprecated( + "SharedMemoryMultipleLockThreadTest", true, kDataSize)); + EXPECT_TRUE(memory1.ShareToProcess(GetCurrentProcess(), &handle)); + // TODO(paulg): Implement this once we have a posix version of + // SharedMemory::ShareToProcess. + EXPECT_TRUE(true); + } + + SharedMemory memory2(handle, false); + EXPECT_TRUE(memory2.Map(kDataSize)); + volatile int* const ptr = static_cast(memory2.memory()); + + for (int idx = 0; idx < 20; idx++) { + memory2.LockDeprecated(); + int i = (id_ << 16) + idx; + *ptr = i; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(1)); + EXPECT_EQ(*ptr, i); + memory2.UnlockDeprecated(); + } + + memory2.Close(); + } + + private: + int id_; + + DISALLOW_COPY_AND_ASSIGN(MultipleLockThread); +}; +#endif + +} // namespace + +// Android doesn't support SharedMemory::Open/Delete/ +// CreateNamedDeprecated(openExisting=true) +#if !defined(OS_ANDROID) +TEST(SharedMemoryTest, OpenClose) { + const uint32_t kDataSize = 1024; + std::string test_name = "SharedMemoryOpenCloseTest"; + + // Open two handles to a memory segment, confirm that they are mapped + // separately yet point to the same space. + SharedMemory memory1; + bool rv = memory1.Delete(test_name); + EXPECT_TRUE(rv); + rv = memory1.Delete(test_name); + EXPECT_TRUE(rv); + rv = memory1.Open(test_name, false); + EXPECT_FALSE(rv); + rv = memory1.CreateNamedDeprecated(test_name, false, kDataSize); + EXPECT_TRUE(rv); + rv = memory1.Map(kDataSize); + EXPECT_TRUE(rv); + SharedMemory memory2; + rv = memory2.Open(test_name, false); + EXPECT_TRUE(rv); + rv = memory2.Map(kDataSize); + EXPECT_TRUE(rv); + EXPECT_NE(memory1.memory(), memory2.memory()); // Compare the pointers. + + // Make sure we don't segfault. (it actually happened!) + ASSERT_NE(memory1.memory(), static_cast(NULL)); + ASSERT_NE(memory2.memory(), static_cast(NULL)); + + // Write data to the first memory segment, verify contents of second. + memset(memory1.memory(), '1', kDataSize); + EXPECT_EQ(memcmp(memory1.memory(), memory2.memory(), kDataSize), 0); + + // Close the first memory segment, and verify the second has the right data. + memory1.Close(); + char *start_ptr = static_cast(memory2.memory()); + char *end_ptr = start_ptr + kDataSize; + for (char* ptr = start_ptr; ptr < end_ptr; ptr++) + EXPECT_EQ(*ptr, '1'); + + // Close the second memory segment. + memory2.Close(); + + rv = memory1.Delete(test_name); + EXPECT_TRUE(rv); + rv = memory2.Delete(test_name); + EXPECT_TRUE(rv); +} + +TEST(SharedMemoryTest, OpenExclusive) { + const uint32_t kDataSize = 1024; + const uint32_t kDataSize2 = 2048; + std::ostringstream test_name_stream; + test_name_stream << "SharedMemoryOpenExclusiveTest." + << Time::Now().ToDoubleT(); + std::string test_name = test_name_stream.str(); + + // Open two handles to a memory segment and check that + // open_existing_deprecated works as expected. + SharedMemory memory1; + bool rv = memory1.CreateNamedDeprecated(test_name, false, kDataSize); + EXPECT_TRUE(rv); + + // Memory1 knows it's size because it created it. + EXPECT_EQ(memory1.requested_size(), kDataSize); + + rv = memory1.Map(kDataSize); + EXPECT_TRUE(rv); + + // The mapped memory1 must be at least the size we asked for. + EXPECT_GE(memory1.mapped_size(), kDataSize); + + // The mapped memory1 shouldn't exceed rounding for allocation granularity. + EXPECT_LT(memory1.mapped_size(), + kDataSize + butil::SysInfo::VMAllocationGranularity()); + + memset(memory1.memory(), 'G', kDataSize); + + SharedMemory memory2; + // Should not be able to create if openExisting is false. + rv = memory2.CreateNamedDeprecated(test_name, false, kDataSize2); + EXPECT_FALSE(rv); + + // Should be able to create with openExisting true. + rv = memory2.CreateNamedDeprecated(test_name, true, kDataSize2); + EXPECT_TRUE(rv); + + // Memory2 shouldn't know the size because we didn't create it. + EXPECT_EQ(memory2.requested_size(), 0U); + + // We should be able to map the original size. + rv = memory2.Map(kDataSize); + EXPECT_TRUE(rv); + + // The mapped memory2 must be at least the size of the original. + EXPECT_GE(memory2.mapped_size(), kDataSize); + + // The mapped memory2 shouldn't exceed rounding for allocation granularity. + EXPECT_LT(memory2.mapped_size(), + kDataSize2 + butil::SysInfo::VMAllocationGranularity()); + + // Verify that opening memory2 didn't truncate or delete memory 1. + char *start_ptr = static_cast(memory2.memory()); + char *end_ptr = start_ptr + kDataSize; + for (char* ptr = start_ptr; ptr < end_ptr; ptr++) { + EXPECT_EQ(*ptr, 'G'); + } + + memory1.Close(); + memory2.Close(); + + rv = memory1.Delete(test_name); + EXPECT_TRUE(rv); +} +#endif + +// Create a set of N threads to each open a shared memory segment and write to +// it. Verify that they are always reading/writing consistent data. +TEST(SharedMemoryTest, MultipleThreads) { + MultipleThreadMain::CleanUp(); + // On POSIX we have a problem when 2 threads try to create the shmem + // (a file) at exactly the same time, since create both creates the + // file and zerofills it. We solve the problem for this unit test + // (make it not flaky) by starting with 1 thread, then + // intentionally don't clean up its shmem before running with + // kNumThreads. + + int threadcounts[] = { 1, kNumThreads }; + for (size_t i = 0; i < arraysize(threadcounts); i++) { + int numthreads = threadcounts[i]; + scoped_ptr thread_handles; + scoped_ptr thread_delegates; + + thread_handles.reset(new PlatformThreadHandle[numthreads]); + thread_delegates.reset(new MultipleThreadMain*[numthreads]); + + // Spawn the threads. + for (int16_t index = 0; index < numthreads; index++) { + PlatformThreadHandle pth; + thread_delegates[index] = new MultipleThreadMain(index); + EXPECT_TRUE(PlatformThread::Create(0, thread_delegates[index], &pth)); + thread_handles[index] = pth; + } + + // Wait for the threads to finish. + for (int index = 0; index < numthreads; index++) { + PlatformThread::Join(thread_handles[index]); + delete thread_delegates[index]; + } + } + MultipleThreadMain::CleanUp(); +} + +// TODO(port): this test requires the MultipleLockThread class +// (defined above), which requires the ability to pass file +// descriptors between processes. We haven't done that yet in Chrome +// for POSIX. +#if defined(OS_WIN) +// Create a set of threads to each open a shared memory segment and write to it +// with the lock held. Verify that they are always reading/writing consistent +// data. +TEST(SharedMemoryTest, Lock) { + PlatformThreadHandle thread_handles[kNumThreads]; + MultipleLockThread* thread_delegates[kNumThreads]; + + // Spawn the threads. + for (int index = 0; index < kNumThreads; ++index) { + PlatformThreadHandle pth; + thread_delegates[index] = new MultipleLockThread(index); + EXPECT_TRUE(PlatformThread::Create(0, thread_delegates[index], &pth)); + thread_handles[index] = pth; + } + + // Wait for the threads to finish. + for (int index = 0; index < kNumThreads; ++index) { + PlatformThread::Join(thread_handles[index]); + delete thread_delegates[index]; + } +} +#endif + +// Allocate private (unique) shared memory with an empty string for a +// name. Make sure several of them don't point to the same thing as +// we might expect if the names are equal. +TEST(SharedMemoryTest, AnonymousPrivate) { + int i, j; + int count = 4; + bool rv; + const uint32_t kDataSize = 8192; + + scoped_ptr memories(new SharedMemory[count]); + scoped_ptr pointers(new int*[count]); + ASSERT_TRUE(memories.get()); + ASSERT_TRUE(pointers.get()); + + for (i = 0; i < count; i++) { + rv = memories[i].CreateAndMapAnonymous(kDataSize); + EXPECT_TRUE(rv); + int *ptr = static_cast(memories[i].memory()); + EXPECT_TRUE(ptr); + pointers[i] = ptr; + } + + for (i = 0; i < count; i++) { + // zero out the first int in each except for i; for that one, make it 100. + for (j = 0; j < count; j++) { + if (i == j) + pointers[j][0] = 100; + else + pointers[j][0] = 0; + } + // make sure there is no bleeding of the 100 into the other pointers + for (j = 0; j < count; j++) { + if (i == j) + EXPECT_EQ(100, pointers[j][0]); + else + EXPECT_EQ(0, pointers[j][0]); + } + } + + for (int i = 0; i < count; i++) { + memories[i].Close(); + } +} + +TEST(SharedMemoryTest, ShareReadOnly) { + StringPiece contents = "Hello World"; + + SharedMemory writable_shmem; + SharedMemoryCreateOptions options; + options.size = contents.size(); + options.share_read_only = true; + ASSERT_TRUE(writable_shmem.Create(options)); + ASSERT_TRUE(writable_shmem.Map(options.size)); + memcpy(writable_shmem.memory(), contents.data(), contents.size()); + EXPECT_TRUE(writable_shmem.Unmap()); + + SharedMemoryHandle readonly_handle; + ASSERT_TRUE(writable_shmem.ShareReadOnlyToProcess(GetCurrentProcessHandle(), + &readonly_handle)); + SharedMemory readonly_shmem(readonly_handle, /*readonly=*/true); + + ASSERT_TRUE(readonly_shmem.Map(contents.size())); + EXPECT_EQ(contents, + StringPiece(static_cast(readonly_shmem.memory()), + contents.size())); + EXPECT_TRUE(readonly_shmem.Unmap()); + + // Make sure the writable instance is still writable. + ASSERT_TRUE(writable_shmem.Map(contents.size())); + StringPiece new_contents = "Goodbye"; + memcpy(writable_shmem.memory(), new_contents.data(), new_contents.size()); + EXPECT_EQ(new_contents, + StringPiece(static_cast(writable_shmem.memory()), + new_contents.size())); + + // We'd like to check that if we send the read-only segment to another + // process, then that other process can't reopen it read/write. (Since that + // would be a security hole.) Setting up multiple processes is hard in a + // unittest, so this test checks that the *current* process can't reopen the + // segment read/write. I think the test here is stronger than we actually + // care about, but there's a remote possibility that sending a file over a + // pipe would transform it into read/write. + SharedMemoryHandle handle = readonly_shmem.handle(); + +#if defined(OS_ANDROID) + // The "read-only" handle is still writable on Android: + // http://crbug.com/320865 + (void)handle; +#elif defined(OS_POSIX) + EXPECT_EQ(O_RDONLY, fcntl(handle.fd, F_GETFL) & O_ACCMODE) + << "The descriptor itself should be read-only."; + + errno = 0; + void* writable = mmap( + NULL, contents.size(), PROT_READ | PROT_WRITE, MAP_SHARED, handle.fd, 0); + int mmap_errno = errno; + EXPECT_EQ(MAP_FAILED, writable) + << "It shouldn't be possible to re-mmap the descriptor writable."; + EXPECT_EQ(EACCES, mmap_errno) << strerror(mmap_errno); + if (writable != MAP_FAILED) + EXPECT_EQ(0, munmap(writable, readonly_shmem.mapped_size())); + +#elif defined(OS_WIN) + EXPECT_EQ(NULL, MapViewOfFile(handle, FILE_MAP_WRITE, 0, 0, 0)) + << "Shouldn't be able to map memory writable."; + + HANDLE temp_handle; + BOOL rv = ::DuplicateHandle(GetCurrentProcess(), + handle, + GetCurrentProcess, + &temp_handle, + FILE_MAP_ALL_ACCESS, + false, + 0); + EXPECT_EQ(FALSE, rv) + << "Shouldn't be able to duplicate the handle into a writable one."; + if (rv) + butil::win::ScopedHandle writable_handle(temp_handle); +#else +#error Unexpected platform; write a test that tries to make 'handle' writable. +#endif // defined(OS_POSIX) || defined(OS_WIN) +} + +TEST(SharedMemoryTest, ShareToSelf) { + StringPiece contents = "Hello World"; + + SharedMemory shmem; + ASSERT_TRUE(shmem.CreateAndMapAnonymous(contents.size())); + memcpy(shmem.memory(), contents.data(), contents.size()); + EXPECT_TRUE(shmem.Unmap()); + + SharedMemoryHandle shared_handle; + ASSERT_TRUE(shmem.ShareToProcess(GetCurrentProcessHandle(), &shared_handle)); + SharedMemory shared(shared_handle, /*readonly=*/false); + + ASSERT_TRUE(shared.Map(contents.size())); + EXPECT_EQ( + contents, + StringPiece(static_cast(shared.memory()), contents.size())); + + ASSERT_TRUE(shmem.ShareToProcess(GetCurrentProcessHandle(), &shared_handle)); + SharedMemory readonly(shared_handle, /*readonly=*/true); + + ASSERT_TRUE(readonly.Map(contents.size())); + EXPECT_EQ(contents, + StringPiece(static_cast(readonly.memory()), + contents.size())); +} + +TEST(SharedMemoryTest, MapAt) { + ASSERT_TRUE(SysInfo::VMAllocationGranularity() >= sizeof(uint32_t)); + const size_t kCount = SysInfo::VMAllocationGranularity(); + const size_t kDataSize = kCount * sizeof(uint32_t); + + SharedMemory memory; + ASSERT_TRUE(memory.CreateAndMapAnonymous(kDataSize)); + uint32_t* ptr = static_cast(memory.memory()); + ASSERT_NE(ptr, static_cast(NULL)); + + for (size_t i = 0; i < kCount; ++i) { + ptr[i] = i; + } + + memory.Unmap(); + + off_t offset = SysInfo::VMAllocationGranularity(); + ASSERT_TRUE(memory.MapAt(offset, kDataSize - offset)); + offset /= sizeof(uint32_t); + ptr = static_cast(memory.memory()); + ASSERT_NE(ptr, static_cast(NULL)); + for (size_t i = offset; i < kCount; ++i) { + EXPECT_EQ(ptr[i - offset], i); + } +} + +TEST(SharedMemoryTest, MapTwice) { + const uint32_t kDataSize = 1024; + SharedMemory memory; + bool rv = memory.CreateAndMapAnonymous(kDataSize); + EXPECT_TRUE(rv); + + void* old_address = memory.memory(); + + rv = memory.Map(kDataSize); + EXPECT_FALSE(rv); + EXPECT_EQ(old_address, memory.memory()); +} + +#if defined(OS_POSIX) +// Create a shared memory object, mmap it, and mprotect it to PROT_EXEC. +TEST(SharedMemoryTest, AnonymousExecutable) { + const uint32_t kTestSize = 1 << 16; + + SharedMemory shared_memory; + SharedMemoryCreateOptions options; + options.size = kTestSize; + options.executable = true; + + EXPECT_TRUE(shared_memory.Create(options)); + EXPECT_TRUE(shared_memory.Map(shared_memory.requested_size())); + + EXPECT_EQ(0, mprotect(shared_memory.memory(), shared_memory.requested_size(), + PROT_READ | PROT_EXEC)); +} + +// Android supports a different permission model than POSIX for its "ashmem" +// shared memory implementation. So the tests about file permissions are not +// included on Android. +#if !defined(OS_ANDROID) + +// Set a umask and restore the old mask on destruction. +class ScopedUmaskSetter { + public: + explicit ScopedUmaskSetter(mode_t target_mask) { + old_umask_ = umask(target_mask); + } + ~ScopedUmaskSetter() { umask(old_umask_); } + private: + mode_t old_umask_; + DISALLOW_IMPLICIT_CONSTRUCTORS(ScopedUmaskSetter); +}; + +// Create a shared memory object, check its permissions. +TEST(SharedMemoryTest, FilePermissionsAnonymous) { + const uint32_t kTestSize = 1 << 8; + + SharedMemory shared_memory; + SharedMemoryCreateOptions options; + options.size = kTestSize; + // Set a file mode creation mask that gives all permissions. + ScopedUmaskSetter permissive_mask(S_IWGRP | S_IWOTH); + + EXPECT_TRUE(shared_memory.Create(options)); + + int shm_fd = shared_memory.handle().fd; + struct stat shm_stat; + EXPECT_EQ(0, fstat(shm_fd, &shm_stat)); + // Neither the group, nor others should be able to read the shared memory + // file. + EXPECT_FALSE(shm_stat.st_mode & S_IRWXO); + EXPECT_FALSE(shm_stat.st_mode & S_IRWXG); +} + +// Create a shared memory object, check its permissions. +TEST(SharedMemoryTest, FilePermissionsNamed) { + const uint32_t kTestSize = 1 << 8; + + SharedMemory shared_memory; + SharedMemoryCreateOptions options; + options.size = kTestSize; + std::string shared_mem_name = "shared_perm_test-" + IntToString(getpid()) + + "-" + Uint64ToString(RandUint64()); + options.name_deprecated = &shared_mem_name; + // Set a file mode creation mask that gives all permissions. + ScopedUmaskSetter permissive_mask(S_IWGRP | S_IWOTH); + + EXPECT_TRUE(shared_memory.Create(options)); + // Clean-up the backing file name immediately, we don't need it. + EXPECT_TRUE(shared_memory.Delete(shared_mem_name)); + + int shm_fd = shared_memory.handle().fd; + struct stat shm_stat; + EXPECT_EQ(0, fstat(shm_fd, &shm_stat)); + // Neither the group, nor others should have been able to open the shared + // memory file while its name existed. + EXPECT_FALSE(shm_stat.st_mode & S_IRWXO); + EXPECT_FALSE(shm_stat.st_mode & S_IRWXG); +} +#endif // !defined(OS_ANDROID) + +#endif // defined(OS_POSIX) + +// Map() will return addresses which are aligned to the platform page size, this +// varies from platform to platform though. Since we'd like to advertise a +// minimum alignment that callers can count on, test for it here. +TEST(SharedMemoryTest, MapMinimumAlignment) { + static const int kDataSize = 8192; + + SharedMemory shared_memory; + ASSERT_TRUE(shared_memory.CreateAndMapAnonymous(kDataSize)); + EXPECT_EQ(0U, reinterpret_cast( + shared_memory.memory()) & (SharedMemory::MAP_MINIMUM_ALIGNMENT - 1)); + shared_memory.Close(); +} + +#if !defined(OS_IOS) // iOS does not allow multiple processes. + +// On POSIX it is especially important we test shmem across processes, +// not just across threads. But the test is enabled on all platforms. +class SharedMemoryProcessTest : public MultiProcessTest { + public: + + static void CleanUp() { + SharedMemory memory; + memory.Delete(s_test_name_); + } + + static int TaskTestMain() { + int errors = 0; +#if defined(OS_MACOSX) + mac::ScopedNSAutoreleasePool pool; +#endif + const uint32_t kDataSize = 1024; + SharedMemory memory; + bool rv = memory.CreateNamedDeprecated(s_test_name_, true, kDataSize); + EXPECT_TRUE(rv); + if (rv != true) + errors++; + rv = memory.Map(kDataSize); + EXPECT_TRUE(rv); + if (rv != true) + errors++; + int *ptr = static_cast(memory.memory()); + + for (int idx = 0; idx < 20; idx++) { + memory.LockDeprecated(); + int i = (1 << 16) + idx; + *ptr = i; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(10)); + if (*ptr != i) + errors++; + memory.UnlockDeprecated(); + } + + memory.Close(); + return errors; + } + + private: + static const char* const s_test_name_; +}; + +const char* const SharedMemoryProcessTest::s_test_name_ = "MPMem"; + +TEST_F(SharedMemoryProcessTest, Tasks) { + SharedMemoryProcessTest::CleanUp(); + + ProcessHandle handles[kNumTasks]; + for (int index = 0; index < kNumTasks; ++index) { + handles[index] = SpawnChild("SharedMemoryTestMain"); + ASSERT_TRUE(handles[index]); + } + + int exit_code = 0; + for (int index = 0; index < kNumTasks; ++index) { + EXPECT_TRUE(WaitForExitCode(handles[index], &exit_code)); + EXPECT_EQ(0, exit_code); + } + + SharedMemoryProcessTest::CleanUp(); +} + +MULTIPROCESS_TEST_MAIN(SharedMemoryTestMain) { + return SharedMemoryProcessTest::TaskTestMain(); +} + +#endif // !OS_IOS + +} // namespace butil diff --git a/test/simple_thread_unittest.cc b/test/simple_thread_unittest.cc new file mode 100644 index 0000000..578d177 --- /dev/null +++ b/test/simple_thread_unittest.cc @@ -0,0 +1,170 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/atomic_sequence_num.h" +#include "butil/strings/string_number_conversions.h" +#include "butil/synchronization/waitable_event.h" +#include "butil/threading/simple_thread.h" +#include + +namespace butil { + +namespace { + +class SetIntRunner : public DelegateSimpleThread::Delegate { + public: + SetIntRunner(int* ptr, int val) : ptr_(ptr), val_(val) { } + virtual ~SetIntRunner() { } + + virtual void Run() OVERRIDE { + *ptr_ = val_; + } + + private: + int* ptr_; + int val_; +}; + +class WaitEventRunner : public DelegateSimpleThread::Delegate { + public: + explicit WaitEventRunner(WaitableEvent* event) : event_(event) { } + virtual ~WaitEventRunner() { } + + virtual void Run() OVERRIDE { + EXPECT_FALSE(event_->IsSignaled()); + event_->Signal(); + EXPECT_TRUE(event_->IsSignaled()); + } + private: + WaitableEvent* event_; +}; + +class SeqRunner : public DelegateSimpleThread::Delegate { + public: + explicit SeqRunner(AtomicSequenceNumber* seq) : seq_(seq) { } + virtual void Run() OVERRIDE { + seq_->GetNext(); + } + + private: + AtomicSequenceNumber* seq_; +}; + +// We count up on a sequence number, firing on the event when we've hit our +// expected amount, otherwise we wait on the event. This will ensure that we +// have all threads outstanding until we hit our expected thread pool size. +class VerifyPoolRunner : public DelegateSimpleThread::Delegate { + public: + VerifyPoolRunner(AtomicSequenceNumber* seq, + int total, WaitableEvent* event) + : seq_(seq), total_(total), event_(event) { } + + virtual void Run() OVERRIDE { + if (seq_->GetNext() == total_) { + event_->Signal(); + } else { + event_->Wait(); + } + } + + private: + AtomicSequenceNumber* seq_; + int total_; + WaitableEvent* event_; +}; + +} // namespace + +TEST(SimpleThreadTest, CreateAndJoin) { + int stack_int = 0; + + SetIntRunner runner(&stack_int, 7); + EXPECT_EQ(0, stack_int); + + DelegateSimpleThread thread(&runner, "int_setter"); + EXPECT_FALSE(thread.HasBeenStarted()); + EXPECT_FALSE(thread.HasBeenJoined()); + EXPECT_EQ(0, stack_int); + + thread.Start(); + EXPECT_TRUE(thread.HasBeenStarted()); + EXPECT_FALSE(thread.HasBeenJoined()); + + thread.Join(); + EXPECT_TRUE(thread.HasBeenStarted()); + EXPECT_TRUE(thread.HasBeenJoined()); + EXPECT_EQ(7, stack_int); +} + +TEST(SimpleThreadTest, WaitForEvent) { + // Create a thread, and wait for it to signal us. + WaitableEvent event(true, false); + + WaitEventRunner runner(&event); + DelegateSimpleThread thread(&runner, "event_waiter"); + + EXPECT_FALSE(event.IsSignaled()); + thread.Start(); + event.Wait(); + EXPECT_TRUE(event.IsSignaled()); + thread.Join(); +} + +TEST(SimpleThreadTest, NamedWithOptions) { + WaitableEvent event(true, false); + + WaitEventRunner runner(&event); + SimpleThread::Options options; + DelegateSimpleThread thread(&runner, "event_waiter", options); + EXPECT_EQ(thread.name_prefix(), "event_waiter"); + EXPECT_FALSE(event.IsSignaled()); + + thread.Start(); + EXPECT_EQ(thread.name_prefix(), "event_waiter"); + EXPECT_EQ(thread.name(), + std::string("event_waiter/") + IntToString(thread.tid())); + event.Wait(); + + EXPECT_TRUE(event.IsSignaled()); + thread.Join(); + + // We keep the name and tid, even after the thread is gone. + EXPECT_EQ(thread.name_prefix(), "event_waiter"); + EXPECT_EQ(thread.name(), + std::string("event_waiter/") + IntToString(thread.tid())); +} + +TEST(SimpleThreadTest, ThreadPool) { + AtomicSequenceNumber seq; + SeqRunner runner(&seq); + DelegateSimpleThreadPool pool("seq_runner", 10); + + // Add work before we're running. + pool.AddWork(&runner, 300); + + EXPECT_EQ(seq.GetNext(), 0); + pool.Start(); + + // Add work while we're running. + pool.AddWork(&runner, 300); + + pool.JoinAll(); + + EXPECT_EQ(seq.GetNext(), 601); + + // We can reuse our pool. Verify that all 10 threads can actually run in + // parallel, so this test will only pass if there are actually 10 threads. + AtomicSequenceNumber seq2; + WaitableEvent event(true, false); + // Changing 9 to 10, for example, would cause us JoinAll() to never return. + VerifyPoolRunner verifier(&seq2, 9, &event); + pool.Start(); + + pool.AddWork(&verifier, 10); + + pool.JoinAll(); + EXPECT_EQ(seq2.GetNext(), 10); +} + +} // namespace butil diff --git a/test/singleton_unittest.cc b/test/singleton_unittest.cc new file mode 100644 index 0000000..9881165 --- /dev/null +++ b/test/singleton_unittest.cc @@ -0,0 +1,287 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/at_exit.h" +#include "butil/memory/singleton.h" +#include + +namespace { + +COMPILE_ASSERT(DefaultSingletonTraits::kRegisterAtExit == true, a); + +typedef void (*CallbackFunc)(); + +class IntSingleton { + public: + static IntSingleton* GetInstance() { + return Singleton::get(); + } + + int value_; +}; + +class Init5Singleton { + public: + struct Trait; + + static Init5Singleton* GetInstance() { + return Singleton::get(); + } + + int value_; +}; + +struct Init5Singleton::Trait : public DefaultSingletonTraits { + static Init5Singleton* New() { + Init5Singleton* instance = new Init5Singleton(); + instance->value_ = 5; + return instance; + } +}; + +int* SingletonInt() { + return &IntSingleton::GetInstance()->value_; +} + +int* SingletonInt5() { + return &Init5Singleton::GetInstance()->value_; +} + +template +struct CallbackTrait : public DefaultSingletonTraits { + static void Delete(Type* instance) { + if (instance->callback_) + (instance->callback_)(); + DefaultSingletonTraits::Delete(instance); + } +}; + +class CallbackSingleton { + public: + CallbackSingleton() : callback_(NULL) { } + CallbackFunc callback_; +}; + +class CallbackSingletonWithNoLeakTrait : public CallbackSingleton { + public: + struct Trait : public CallbackTrait { }; + + CallbackSingletonWithNoLeakTrait() : CallbackSingleton() { } + + static CallbackSingletonWithNoLeakTrait* GetInstance() { + return Singleton::get(); + } +}; + +class CallbackSingletonWithLeakTrait : public CallbackSingleton { + public: + struct Trait : public CallbackTrait { + static const bool kRegisterAtExit = false; + }; + + CallbackSingletonWithLeakTrait() : CallbackSingleton() { } + + static CallbackSingletonWithLeakTrait* GetInstance() { + return Singleton::get(); + } +}; + +class CallbackSingletonWithStaticTrait : public CallbackSingleton { + public: + struct Trait; + + CallbackSingletonWithStaticTrait() : CallbackSingleton() { } + + static CallbackSingletonWithStaticTrait* GetInstance() { + return Singleton::get(); + } +}; + +struct CallbackSingletonWithStaticTrait::Trait + : public StaticMemorySingletonTraits { + static void Delete(CallbackSingletonWithStaticTrait* instance) { + if (instance->callback_) + (instance->callback_)(); + StaticMemorySingletonTraits::Delete( + instance); + } +}; + +template +class AlignedTestSingleton { + public: + AlignedTestSingleton() {} + ~AlignedTestSingleton() {} + static AlignedTestSingleton* GetInstance() { + return Singleton >::get(); + } + + Type type_; +}; + + +void SingletonNoLeak(CallbackFunc CallOnQuit) { + CallbackSingletonWithNoLeakTrait::GetInstance()->callback_ = CallOnQuit; +} + +void SingletonLeak(CallbackFunc CallOnQuit) { + CallbackSingletonWithLeakTrait::GetInstance()->callback_ = CallOnQuit; +} + +CallbackFunc* GetLeakySingleton() { + return &CallbackSingletonWithLeakTrait::GetInstance()->callback_; +} + +void DeleteLeakySingleton() { + DefaultSingletonTraits::Delete( + CallbackSingletonWithLeakTrait::GetInstance()); +} + +void SingletonStatic(CallbackFunc CallOnQuit) { + CallbackSingletonWithStaticTrait::GetInstance()->callback_ = CallOnQuit; +} + +CallbackFunc* GetStaticSingleton() { + return &CallbackSingletonWithStaticTrait::GetInstance()->callback_; +} + +} // namespace + +class SingletonTest : public testing::Test { + public: + SingletonTest() {} + + virtual void SetUp() OVERRIDE { + non_leak_called_ = false; + leaky_called_ = false; + static_called_ = false; + } + + protected: + void VerifiesCallbacks() { + EXPECT_TRUE(non_leak_called_); + EXPECT_FALSE(leaky_called_); + EXPECT_TRUE(static_called_); + non_leak_called_ = false; + leaky_called_ = false; + static_called_ = false; + } + + void VerifiesCallbacksNotCalled() { + EXPECT_FALSE(non_leak_called_); + EXPECT_FALSE(leaky_called_); + EXPECT_FALSE(static_called_); + non_leak_called_ = false; + leaky_called_ = false; + static_called_ = false; + } + + static void CallbackNoLeak() { + non_leak_called_ = true; + } + + static void CallbackLeak() { + leaky_called_ = true; + } + + static void CallbackStatic() { + static_called_ = true; + } + + private: + static bool non_leak_called_; + static bool leaky_called_; + static bool static_called_; +}; + +bool SingletonTest::non_leak_called_ = false; +bool SingletonTest::leaky_called_ = false; +bool SingletonTest::static_called_ = false; + +TEST_F(SingletonTest, Basic) { + int* singleton_int; + int* singleton_int_5; + CallbackFunc* leaky_singleton; + CallbackFunc* static_singleton; + + { + butil::ShadowingAtExitManager sem; + { + singleton_int = SingletonInt(); + } + // Ensure POD type initialization. + EXPECT_EQ(*singleton_int, 0); + *singleton_int = 1; + + EXPECT_EQ(singleton_int, SingletonInt()); + EXPECT_EQ(*singleton_int, 1); + + { + singleton_int_5 = SingletonInt5(); + } + // Is default initialized to 5. + EXPECT_EQ(*singleton_int_5, 5); + + SingletonNoLeak(&CallbackNoLeak); + SingletonLeak(&CallbackLeak); + SingletonStatic(&CallbackStatic); + static_singleton = GetStaticSingleton(); + leaky_singleton = GetLeakySingleton(); + EXPECT_TRUE(leaky_singleton); + } + + // Verify that only the expected callback has been called. + VerifiesCallbacks(); + // Delete the leaky singleton. + DeleteLeakySingleton(); + + // The static singleton can't be acquired post-atexit. + EXPECT_EQ(NULL, GetStaticSingleton()); + + { + butil::ShadowingAtExitManager sem; + // Verifiy that the variables were reset. + { + singleton_int = SingletonInt(); + EXPECT_EQ(*singleton_int, 0); + } + { + singleton_int_5 = SingletonInt5(); + EXPECT_EQ(*singleton_int_5, 5); + } + { + // Resurrect the static singleton, and assert that it + // still points to the same (static) memory. + CallbackSingletonWithStaticTrait::Trait::Resurrect(); + EXPECT_EQ(GetStaticSingleton(), static_singleton); + } + } + // The leaky singleton shouldn't leak since SingletonLeak has not been called. + VerifiesCallbacksNotCalled(); +} + +#define EXPECT_ALIGNED(ptr, align) \ + EXPECT_EQ(0u, reinterpret_cast(ptr) & (align - 1)) + +TEST_F(SingletonTest, Alignment) { + using butil::AlignedMemory; + + // Create some static singletons with increasing sizes and alignment + // requirements. By ordering this way, the linker will need to do some work to + // ensure proper alignment of the static data. + AlignedTestSingleton* align4 = + AlignedTestSingleton::GetInstance(); + AlignedTestSingleton >* align32 = + AlignedTestSingleton >::GetInstance(); + AlignedTestSingleton >* align128 = + AlignedTestSingleton >::GetInstance(); + AlignedTestSingleton >* align4096 = + AlignedTestSingleton >::GetInstance(); + + EXPECT_ALIGNED(align4, 4); + EXPECT_ALIGNED(align32, 32); + EXPECT_ALIGNED(align128, 128); + EXPECT_ALIGNED(align4096, 4096); +} diff --git a/test/small_map_unittest.cc b/test/small_map_unittest.cc new file mode 100644 index 0000000..51fe88a --- /dev/null +++ b/test/small_map_unittest.cc @@ -0,0 +1,483 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/containers/small_map.h" + +#include + +#include +#include +#include + +#include "butil/containers/hash_tables.h" +#include "butil/logging.h" +#include + +namespace butil { + +TEST(SmallMap, General) { + SmallMap > m; + + EXPECT_TRUE(m.empty()); + + m[0] = 5; + + EXPECT_FALSE(m.empty()); + EXPECT_EQ(m.size(), 1u); + + m[9] = 2; + + EXPECT_FALSE(m.empty()); + EXPECT_EQ(m.size(), 2u); + + EXPECT_EQ(m[9], 2); + EXPECT_EQ(m[0], 5); + EXPECT_FALSE(m.UsingFullMap()); + + SmallMap >::iterator iter(m.begin()); + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, 0); + EXPECT_EQ(iter->second, 5); + ++iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ((*iter).first, 9); + EXPECT_EQ((*iter).second, 2); + ++iter; + EXPECT_TRUE(iter == m.end()); + + m[8] = 23; + m[1234] = 90; + m[-5] = 6; + + EXPECT_EQ(m[ 9], 2); + EXPECT_EQ(m[ 0], 5); + EXPECT_EQ(m[1234], 90); + EXPECT_EQ(m[ 8], 23); + EXPECT_EQ(m[ -5], 6); + EXPECT_EQ(m.size(), 5u); + EXPECT_FALSE(m.empty()); + EXPECT_TRUE(m.UsingFullMap()); + + iter = m.begin(); + for (int i = 0; i < 5; i++) { + EXPECT_TRUE(iter != m.end()); + ++iter; + } + EXPECT_TRUE(iter == m.end()); + + const SmallMap >& ref = m; + EXPECT_TRUE(ref.find(1234) != m.end()); + EXPECT_TRUE(ref.find(5678) == m.end()); +} + +TEST(SmallMap, PostFixIteratorIncrement) { + SmallMap > m; + m[0] = 5; + m[2] = 3; + + { + SmallMap >::iterator iter(m.begin()); + SmallMap >::iterator last(iter++); + ++last; + EXPECT_TRUE(last == iter); + } + + { + SmallMap >::const_iterator iter(m.begin()); + SmallMap >::const_iterator last(iter++); + ++last; + EXPECT_TRUE(last == iter); + } +} + +// Based on the General testcase. +TEST(SmallMap, CopyConstructor) { + SmallMap > src; + + { + SmallMap > m(src); + EXPECT_TRUE(m.empty()); + } + + src[0] = 5; + + { + SmallMap > m(src); + EXPECT_FALSE(m.empty()); + EXPECT_EQ(m.size(), 1u); + } + + src[9] = 2; + + { + SmallMap > m(src); + EXPECT_FALSE(m.empty()); + EXPECT_EQ(m.size(), 2u); + + EXPECT_EQ(m[9], 2); + EXPECT_EQ(m[0], 5); + EXPECT_FALSE(m.UsingFullMap()); + } + + src[8] = 23; + src[1234] = 90; + src[-5] = 6; + + { + SmallMap > m(src); + EXPECT_EQ(m[ 9], 2); + EXPECT_EQ(m[ 0], 5); + EXPECT_EQ(m[1234], 90); + EXPECT_EQ(m[ 8], 23); + EXPECT_EQ(m[ -5], 6); + EXPECT_EQ(m.size(), 5u); + EXPECT_FALSE(m.empty()); + EXPECT_TRUE(m.UsingFullMap()); + } +} + +template +static bool SmallMapIsSubset(SmallMap const& a, + SmallMap const& b) { + typename SmallMap::const_iterator it; + for (it = a.begin(); it != a.end(); ++it) { + typename SmallMap::const_iterator it_in_b = b.find(it->first); + if (it_in_b == b.end() || it_in_b->second != it->second) + return false; + } + return true; +} + +template +static bool SmallMapEqual(SmallMap const& a, + SmallMap const& b) { + return SmallMapIsSubset(a, b) && SmallMapIsSubset(b, a); +} + +TEST(SmallMap, AssignmentOperator) { + SmallMap > src_small; + SmallMap > src_large; + + src_small[1] = 20; + src_small[2] = 21; + src_small[3] = 22; + EXPECT_FALSE(src_small.UsingFullMap()); + + src_large[1] = 20; + src_large[2] = 21; + src_large[3] = 22; + src_large[5] = 23; + src_large[6] = 24; + src_large[7] = 25; + EXPECT_TRUE(src_large.UsingFullMap()); + + // Assignments to empty. + SmallMap > dest_small; + dest_small = src_small; + EXPECT_TRUE(SmallMapEqual(dest_small, src_small)); + EXPECT_EQ(dest_small.UsingFullMap(), + src_small.UsingFullMap()); + + SmallMap > dest_large; + dest_large = src_large; + EXPECT_TRUE(SmallMapEqual(dest_large, src_large)); + EXPECT_EQ(dest_large.UsingFullMap(), + src_large.UsingFullMap()); + + // Assignments which assign from full to small, and vice versa. + dest_small = src_large; + EXPECT_TRUE(SmallMapEqual(dest_small, src_large)); + EXPECT_EQ(dest_small.UsingFullMap(), + src_large.UsingFullMap()); + + dest_large = src_small; + EXPECT_TRUE(SmallMapEqual(dest_large, src_small)); + EXPECT_EQ(dest_large.UsingFullMap(), + src_small.UsingFullMap()); + + // Double check that SmallMapEqual works: + dest_large[42] = 666; + EXPECT_FALSE(SmallMapEqual(dest_large, src_small)); +} + +TEST(SmallMap, Insert) { + SmallMap > sm; + + // loop through the transition from small map to map. + for (int i = 1; i <= 10; ++i) { + VLOG(1) << "Iteration " << i; + // insert an element + std::pair >::iterator, + bool> ret; + ret = sm.insert(std::make_pair(i, 100*i)); + EXPECT_TRUE(ret.second); + EXPECT_TRUE(ret.first == sm.find(i)); + EXPECT_EQ(ret.first->first, i); + EXPECT_EQ(ret.first->second, 100*i); + + // try to insert it again with different value, fails, but we still get an + // iterator back with the original value. + ret = sm.insert(std::make_pair(i, -i)); + EXPECT_FALSE(ret.second); + EXPECT_TRUE(ret.first == sm.find(i)); + EXPECT_EQ(ret.first->first, i); + EXPECT_EQ(ret.first->second, 100*i); + + // check the state of the map. + for (int j = 1; j <= i; ++j) { + SmallMap >::iterator it = sm.find(j); + EXPECT_TRUE(it != sm.end()); + EXPECT_EQ(it->first, j); + EXPECT_EQ(it->second, j * 100); + } + EXPECT_EQ(sm.size(), static_cast(i)); + EXPECT_FALSE(sm.empty()); + } +} + +TEST(SmallMap, InsertRange) { + // loop through the transition from small map to map. + for (int elements = 0; elements <= 10; ++elements) { + VLOG(1) << "Elements " << elements; + hash_map normal_map; + for (int i = 1; i <= elements; ++i) { + normal_map.insert(std::make_pair(i, 100*i)); + } + + SmallMap > sm; + sm.insert(normal_map.begin(), normal_map.end()); + EXPECT_EQ(normal_map.size(), sm.size()); + for (int i = 1; i <= elements; ++i) { + VLOG(1) << "Iteration " << i; + EXPECT_TRUE(sm.find(i) != sm.end()); + EXPECT_EQ(sm.find(i)->first, i); + EXPECT_EQ(sm.find(i)->second, 100*i); + } + } +} + +TEST(SmallMap, Erase) { + SmallMap > m; + SmallMap >::iterator iter; + + m["monday"] = 1; + m["tuesday"] = 2; + m["wednesday"] = 3; + + EXPECT_EQ(m["monday" ], 1); + EXPECT_EQ(m["tuesday" ], 2); + EXPECT_EQ(m["wednesday"], 3); + EXPECT_EQ(m.count("tuesday"), 1u); + EXPECT_FALSE(m.UsingFullMap()); + + iter = m.begin(); + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, "monday"); + EXPECT_EQ(iter->second, 1); + ++iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, "tuesday"); + EXPECT_EQ(iter->second, 2); + ++iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, "wednesday"); + EXPECT_EQ(iter->second, 3); + ++iter; + EXPECT_TRUE(iter == m.end()); + + EXPECT_EQ(m.erase("tuesday"), 1u); + + EXPECT_EQ(m["monday" ], 1); + EXPECT_EQ(m["wednesday"], 3); + EXPECT_EQ(m.count("tuesday"), 0u); + EXPECT_EQ(m.erase("tuesday"), 0u); + + iter = m.begin(); + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, "monday"); + EXPECT_EQ(iter->second, 1); + ++iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, "wednesday"); + EXPECT_EQ(iter->second, 3); + ++iter; + EXPECT_TRUE(iter == m.end()); + + m["thursday"] = 4; + m["friday"] = 5; + EXPECT_EQ(m.size(), 4u); + EXPECT_FALSE(m.empty()); + EXPECT_FALSE(m.UsingFullMap()); + + m["saturday"] = 6; + EXPECT_TRUE(m.UsingFullMap()); + + EXPECT_EQ(m.count("friday"), 1u); + EXPECT_EQ(m.erase("friday"), 1u); + EXPECT_TRUE(m.UsingFullMap()); + EXPECT_EQ(m.count("friday"), 0u); + EXPECT_EQ(m.erase("friday"), 0u); + + EXPECT_EQ(m.size(), 4u); + EXPECT_FALSE(m.empty()); + EXPECT_EQ(m.erase("monday"), 1u); + EXPECT_EQ(m.size(), 3u); + EXPECT_FALSE(m.empty()); + + m.clear(); + EXPECT_FALSE(m.UsingFullMap()); + EXPECT_EQ(m.size(), 0u); + EXPECT_TRUE(m.empty()); +} + +TEST(SmallMap, NonHashMap) { + SmallMap, 4, std::equal_to > m; + EXPECT_TRUE(m.empty()); + + m[9] = 2; + m[0] = 5; + + EXPECT_EQ(m[9], 2); + EXPECT_EQ(m[0], 5); + EXPECT_EQ(m.size(), 2u); + EXPECT_FALSE(m.empty()); + EXPECT_FALSE(m.UsingFullMap()); + + SmallMap, 4, std::equal_to >::iterator iter( + m.begin()); + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, 9); + EXPECT_EQ(iter->second, 2); + ++iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, 0); + EXPECT_EQ(iter->second, 5); + ++iter; + EXPECT_TRUE(iter == m.end()); + --iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, 0); + EXPECT_EQ(iter->second, 5); + + m[8] = 23; + m[1234] = 90; + m[-5] = 6; + + EXPECT_EQ(m[ 9], 2); + EXPECT_EQ(m[ 0], 5); + EXPECT_EQ(m[1234], 90); + EXPECT_EQ(m[ 8], 23); + EXPECT_EQ(m[ -5], 6); + EXPECT_EQ(m.size(), 5u); + EXPECT_FALSE(m.empty()); + EXPECT_TRUE(m.UsingFullMap()); + + iter = m.begin(); + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, -5); + EXPECT_EQ(iter->second, 6); + ++iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, 0); + EXPECT_EQ(iter->second, 5); + ++iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, 8); + EXPECT_EQ(iter->second, 23); + ++iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, 9); + EXPECT_EQ(iter->second, 2); + ++iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, 1234); + EXPECT_EQ(iter->second, 90); + ++iter; + EXPECT_TRUE(iter == m.end()); + --iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, 1234); + EXPECT_EQ(iter->second, 90); +} + +TEST(SmallMap, DefaultEqualKeyWorks) { + // If these tests compile, they pass. The EXPECT calls are only there to avoid + // unused variable warnings. + SmallMap > hm; + EXPECT_EQ(0u, hm.size()); + SmallMap > m; + EXPECT_EQ(0u, m.size()); +} + +namespace { + +class hash_map_add_item : public hash_map { + public: + hash_map_add_item() : hash_map() {} + explicit hash_map_add_item(const std::pair& item) + : hash_map() { + insert(item); + } +}; + +void InitMap(ManualConstructor* map_ctor) { + map_ctor->Init(std::make_pair(0, 0)); +} + +class hash_map_add_item_initializer { + public: + explicit hash_map_add_item_initializer(int item_to_add) + : item_(item_to_add) {} + hash_map_add_item_initializer() + : item_(0) {} + void operator()(ManualConstructor* map_ctor) const { + map_ctor->Init(std::make_pair(item_, item_)); + } + + int item_; +}; + +} // anonymous namespace + +TEST(SmallMap, SubclassInitializationWithFunctionPointer) { + SmallMap, + void (*)(ManualConstructor*)> m(InitMap); + + EXPECT_TRUE(m.empty()); + + m[1] = 1; + m[2] = 2; + m[3] = 3; + m[4] = 4; + + EXPECT_EQ(4u, m.size()); + EXPECT_EQ(0u, m.count(0)); + + m[5] = 5; + EXPECT_EQ(6u, m.size()); + // Our function adds an extra item when we convert to a map. + EXPECT_EQ(1u, m.count(0)); +} + +TEST(SmallMap, SubclassInitializationWithFunctionObject) { + SmallMap, + hash_map_add_item_initializer> m(hash_map_add_item_initializer(-1)); + + EXPECT_TRUE(m.empty()); + + m[1] = 1; + m[2] = 2; + m[3] = 3; + m[4] = 4; + + EXPECT_EQ(4u, m.size()); + EXPECT_EQ(0u, m.count(-1)); + + m[5] = 5; + EXPECT_EQ(6u, m.size()); + // Our functor adds an extra item when we convert to a map. + EXPECT_EQ(1u, m.count(-1)); +} + +} // namespace butil diff --git a/test/snappy_message.proto b/test/snappy_message.proto new file mode 100644 index 0000000..9a9fcb7 --- /dev/null +++ b/test/snappy_message.proto @@ -0,0 +1,24 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +package snappy_message; + +message SnappyMessageProto { + optional string text = 1; + repeated int32 numbers = 2; +}; diff --git a/test/stack_container_unittest.cc b/test/stack_container_unittest.cc new file mode 100644 index 0000000..c245a92 --- /dev/null +++ b/test/stack_container_unittest.cc @@ -0,0 +1,151 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/containers/stack_container.h" + +#include + +#include "butil/memory/aligned_memory.h" +#include "butil/memory/ref_counted.h" +#include + +namespace butil { + +namespace { + +class Dummy : public butil::RefCounted { + public: + explicit Dummy(int* alive) : alive_(alive) { + ++*alive_; + } + + private: + friend class butil::RefCounted; + + ~Dummy() { + --*alive_; + } + + int* const alive_; +}; + +} // namespace + +TEST(StackContainer, Vector) { + const int stack_size = 3; + StackVector vect; + const int* stack_buffer = &vect.stack_data().stack_buffer()[0]; + + // The initial |stack_size| elements should appear in the stack buffer. + EXPECT_EQ(static_cast(stack_size), vect.container().capacity()); + for (int i = 0; i < stack_size; i++) { + vect.container().push_back(i); + EXPECT_EQ(stack_buffer, &vect.container()[0]); + EXPECT_TRUE(vect.stack_data().used_stack_buffer_); + } + + // Adding more elements should push the array onto the heap. + for (int i = 0; i < stack_size; i++) { + vect.container().push_back(i + stack_size); + EXPECT_NE(stack_buffer, &vect.container()[0]); + EXPECT_FALSE(vect.stack_data().used_stack_buffer_); + } + + // The array should still be in order. + for (int i = 0; i < stack_size * 2; i++) + EXPECT_EQ(i, vect.container()[i]); + + // Resize to smaller. Our STL implementation won't reallocate in this case, + // otherwise it might use our stack buffer. We reserve right after the resize + // to guarantee it isn't using the stack buffer, even though it doesn't have + // much data. + vect.container().resize(stack_size); + vect.container().reserve(stack_size * 2); + EXPECT_FALSE(vect.stack_data().used_stack_buffer_); + + // Copying the small vector to another should use the same allocator and use + // the now-unused stack buffer. GENERALLY CALLERS SHOULD NOT DO THIS since + // they have to get the template types just right and it can cause errors. + std::vector > other(vect.container()); + EXPECT_EQ(stack_buffer, &other.front()); + EXPECT_TRUE(vect.stack_data().used_stack_buffer_); + for (int i = 0; i < stack_size; i++) + EXPECT_EQ(i, other[i]); +} + +TEST(StackContainer, VectorDoubleDelete) { + // Regression testing for double-delete. + typedef StackVector, 2> Vector; + typedef Vector::ContainerType Container; + Vector vect; + + int alive = 0; + scoped_refptr dummy(new Dummy(&alive)); + EXPECT_EQ(alive, 1); + + vect->push_back(dummy); + EXPECT_EQ(alive, 1); + + Dummy* dummy_unref = dummy.get(); + dummy = NULL; + EXPECT_EQ(alive, 1); + + Container::iterator itr = std::find(vect->begin(), vect->end(), dummy_unref); + EXPECT_EQ(itr->get(), dummy_unref); + vect->erase(itr); + EXPECT_EQ(alive, 0); + + // Shouldn't crash at exit. +} + +namespace { + +template +class AlignedData { + public: + AlignedData() { memset(data_.void_data(), 0, alignment); } + ~AlignedData() {} + butil::AlignedMemory data_; +}; + +} // anonymous namespace + +#define EXPECT_ALIGNED(ptr, align) \ + EXPECT_EQ(0u, reinterpret_cast(ptr) & (align - 1)) + +// stack alignment is buggy before gcc 4.6 +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=16660 +#if defined(COMPILER_GCC) && \ + ( __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +#define GOOD_GCC_STACK_ALIGNMENT +#endif + +TEST(StackContainer, BufferAlignment) { + StackVector text; + text->push_back(L'A'); + EXPECT_ALIGNED(&text[0], ALIGNOF(wchar_t)); + + StackVector doubles; + doubles->push_back(0.0); + EXPECT_ALIGNED(&doubles[0], ALIGNOF(double)); + + StackVector, 1> aligned16; + aligned16->push_back(AlignedData<16>()); + EXPECT_ALIGNED(&aligned16[0], 16); + +#if !defined(__GNUC__) || defined(ARCH_CPU_X86_FAMILY) && \ + defined(GOOD_GCC_STACK_ALIGNMENT) + // It seems that non-X86 gcc doesn't respect greater than 16 byte alignment. + // See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=33721 for details. + // TODO(sbc):re-enable this if GCC starts respecting higher alignments. + StackVector, 1> aligned256; + aligned256->push_back(AlignedData<256>()); + EXPECT_ALIGNED(&aligned256[0], 256); +#endif +} + +template class StackVector; +template class StackVector, 2>; + +} // namespace butil diff --git a/test/stack_trace_unittest.cc b/test/stack_trace_unittest.cc new file mode 100644 index 0000000..72b1b98 --- /dev/null +++ b/test/stack_trace_unittest.cc @@ -0,0 +1,268 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#include "butil/debug/stack_trace.h" +#include "butil/logging.h" +#include "butil/scoped_lock.h" +#include + +extern "C" { +void TestFindSymbol1(); +void TestFindSymbol2(); +void TestFindSymbol3(); + +void TestFindSymbol1() { + butil::debug::StackTrace trace; + ASSERT_TRUE(trace.ToString().find("TestFindSymbol1") != std::string::npos) << trace.ToString(); + ASSERT_TRUE(trace.ToString().find("TestFindSymbol2") != std::string::npos) << trace.ToString(); + ASSERT_TRUE(trace.ToString().find("TestFindSymbol3") == std::string::npos) << trace.ToString(); + ASSERT_TRUE(trace.FindSymbol((void*)TestFindSymbol2)) << trace.ToString(); + ASSERT_TRUE(trace.FindSymbol((void*)TestFindSymbol1)) << trace.ToString(); + ASSERT_FALSE(trace.FindSymbol((void*)TestFindSymbol3)) << trace.ToString(); +} + +void TestFindSymbol2() { + TestFindSymbol1(); +} + +void TestFindSymbol3() { + TestFindSymbol1(); +} +} + +namespace butil { +namespace debug { + +typedef testing::Test StackTraceTest; + +// Note: On Linux, this test currently only fully works on Debug builds. +// See comments in the #ifdef soup if you intend to change this. +#if defined(OS_WIN) +// Always fails on Windows: crbug.com/32070 +#define MAYBE_OutputToStream DISABLED_OutputToStream +#else +#define MAYBE_OutputToStream OutputToStream +#endif +#if !defined(__UCLIBC__) +TEST_F(StackTraceTest, MAYBE_OutputToStream) { + StackTrace trace; + + // Dump the trace into a string. + std::ostringstream os; + trace.OutputToStream(&os); + std::string backtrace_message = os.str(); + + // ToString() should produce the same output. + EXPECT_EQ(backtrace_message, trace.ToString()); + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && NDEBUG + // Stack traces require an extra data table that bloats our binaries, + // so they're turned off for release builds. We stop the test here, + // at least letting us verify that the calls don't crash. + return; +#endif // defined(OS_POSIX) && !defined(OS_MACOSX) && NDEBUG + + size_t frames_found = 0; + trace.Addresses(&frames_found); + ASSERT_GE(frames_found, 0) << + "No stack frames found. Skipping rest of test."; + + // Check if the output has symbol initialization warning. If it does, fail. + ASSERT_EQ(backtrace_message.find("Dumping unresolved backtrace"), + std::string::npos) << + "Unable to resolve symbols. Skipping rest of test."; + +#if defined(OS_MACOSX) +#if 0 + // Disabled due to -fvisibility=hidden in build config. + + // Symbol resolution via the backtrace_symbol function does not work well + // in OS X. + // See this thread: + // + // http://lists.apple.com/archives/darwin-dev/2009/Mar/msg00111.html + // + // Just check instead that we find our way back to the "start" symbol + // which should be the first symbol in the trace. + // + // TODO(port): Find a more reliable way to resolve symbols. + + // Expect to at least find main. + EXPECT_TRUE(backtrace_message.find("start") != std::string::npos) + << "Expected to find start in backtrace:\n" + << backtrace_message; + +#endif +#elif defined(USE_SYMBOLIZE) + // This branch is for gcc-compiled code, but not Mac due to the + // above #if. + // Expect a demangled symbol. + EXPECT_TRUE(backtrace_message.find("TestBody()") != + std::string::npos) + << "Expected a demangled symbol in backtrace:\n" + << backtrace_message; + +#elif 0 + // This is the fall-through case; it used to cover Windows. + // But it's disabled because of varying buildbot configs; + // some lack symbols. + + // Expect to at least find main. + EXPECT_TRUE(backtrace_message.find("main") != std::string::npos) + << "Expected to find main in backtrace:\n" + << backtrace_message; + +#if defined(OS_WIN) +// MSVC doesn't allow the use of C99's __func__ within C++, so we fake it with +// MSVC's __FUNCTION__ macro. +#define __func__ __FUNCTION__ +#endif + + // Expect to find this function as well. + // Note: This will fail if not linked with -rdynamic (aka -export_dynamic) + EXPECT_TRUE(backtrace_message.find(__func__) != std::string::npos) + << "Expected to find " << __func__ << " in backtrace:\n" + << backtrace_message; + +#endif // define(OS_MACOSX) +} + +void CheckDebugOutputToStream(bool exclude_self) { + StackTrace trace(exclude_self); + size_t count; + const void* const* addrs = trace.Addresses(&count); + ASSERT_EQ(count, trace.FrameCount()); + void* addr = malloc(sizeof(void*) * count); + size_t copied_count = trace.CopyAddressTo((void**)addr, count); + ASSERT_EQ(count, copied_count); + ASSERT_EQ(0, memcmp(addrs, addr, sizeof(void*) * count)); + + std::ostringstream os; + trace.OutputToStream(&os); + VLOG(1) << os.str(); + free(addr); +} + +// The test is used for manual testing, e.g., to see the raw output. +TEST_F(StackTraceTest, DebugOutputToStream) { + CheckDebugOutputToStream(false); + CheckDebugOutputToStream(true); +} + +// The test is used for manual testing, e.g., to see the raw output. +TEST_F(StackTraceTest, DebugPrintBacktrace) { + StackTrace().Print(); +} +#endif // !defined(__UCLIBC__) + +#if defined(OS_POSIX) && !defined(OS_ANDROID) + +namespace { + +std::string itoa_r_wrapper(intptr_t i, size_t sz, int base, size_t padding) { + char buffer[1024]; + CHECK_LE(sz, sizeof(buffer)); + + char* result = internal::itoa_r(i, buffer, sz, base, padding); + EXPECT_TRUE(result); + return std::string(buffer); +} + +} // namespace + +TEST_F(StackTraceTest, itoa_r) { + EXPECT_EQ("0", itoa_r_wrapper(0, 128, 10, 0)); + EXPECT_EQ("-1", itoa_r_wrapper(-1, 128, 10, 0)); + + // Test edge cases. + if (sizeof(intptr_t) == 4) { + EXPECT_EQ("ffffffff", itoa_r_wrapper(-1, 128, 16, 0)); + EXPECT_EQ("-2147483648", + itoa_r_wrapper(std::numeric_limits::min(), 128, 10, 0)); + EXPECT_EQ("2147483647", + itoa_r_wrapper(std::numeric_limits::max(), 128, 10, 0)); + + EXPECT_EQ("80000000", + itoa_r_wrapper(std::numeric_limits::min(), 128, 16, 0)); + EXPECT_EQ("7fffffff", + itoa_r_wrapper(std::numeric_limits::max(), 128, 16, 0)); + } else if (sizeof(intptr_t) == 8) { + EXPECT_EQ("ffffffffffffffff", itoa_r_wrapper(-1, 128, 16, 0)); + EXPECT_EQ("-9223372036854775808", + itoa_r_wrapper(std::numeric_limits::min(), 128, 10, 0)); + EXPECT_EQ("9223372036854775807", + itoa_r_wrapper(std::numeric_limits::max(), 128, 10, 0)); + + EXPECT_EQ("8000000000000000", + itoa_r_wrapper(std::numeric_limits::min(), 128, 16, 0)); + EXPECT_EQ("7fffffffffffffff", + itoa_r_wrapper(std::numeric_limits::max(), 128, 16, 0)); + } else { + ADD_FAILURE() << "Missing test case for your size of intptr_t (" + << sizeof(intptr_t) << ")"; + } + + // Test hex output. + EXPECT_EQ("688", itoa_r_wrapper(0x688, 128, 16, 0)); + EXPECT_EQ("deadbeef", itoa_r_wrapper(0xdeadbeef, 128, 16, 0)); + + // Check that itoa_r respects passed buffer size limit. + char buffer[1024]; + EXPECT_TRUE(internal::itoa_r(0xdeadbeef, buffer, 10, 16, 0)); + EXPECT_TRUE(internal::itoa_r(0xdeadbeef, buffer, 9, 16, 0)); + EXPECT_FALSE(internal::itoa_r(0xdeadbeef, buffer, 8, 16, 0)); + EXPECT_FALSE(internal::itoa_r(0xdeadbeef, buffer, 7, 16, 0)); + EXPECT_TRUE(internal::itoa_r(0xbeef, buffer, 5, 16, 4)); + EXPECT_FALSE(internal::itoa_r(0xbeef, buffer, 5, 16, 5)); + EXPECT_FALSE(internal::itoa_r(0xbeef, buffer, 5, 16, 6)); + + // Test padding. + EXPECT_EQ("1", itoa_r_wrapper(1, 128, 10, 0)); + EXPECT_EQ("1", itoa_r_wrapper(1, 128, 10, 1)); + EXPECT_EQ("01", itoa_r_wrapper(1, 128, 10, 2)); + EXPECT_EQ("001", itoa_r_wrapper(1, 128, 10, 3)); + EXPECT_EQ("0001", itoa_r_wrapper(1, 128, 10, 4)); + EXPECT_EQ("00001", itoa_r_wrapper(1, 128, 10, 5)); + EXPECT_EQ("688", itoa_r_wrapper(0x688, 128, 16, 0)); + EXPECT_EQ("688", itoa_r_wrapper(0x688, 128, 16, 1)); + EXPECT_EQ("688", itoa_r_wrapper(0x688, 128, 16, 2)); + EXPECT_EQ("688", itoa_r_wrapper(0x688, 128, 16, 3)); + EXPECT_EQ("0688", itoa_r_wrapper(0x688, 128, 16, 4)); + EXPECT_EQ("00688", itoa_r_wrapper(0x688, 128, 16, 5)); +} +#endif // defined(OS_POSIX) && !defined(OS_ANDROID) + +void TestFindSymbol1(); +void TestFindSymbol2(); +void TestFindSymbol3(); + +void TestFindSymbol1() { + butil::debug::StackTrace trace; + ASSERT_TRUE(trace.ToString().find("TestFindSymbol1") != std::string::npos) << trace.ToString(); + ASSERT_TRUE(trace.ToString().find("TestFindSymbol2") != std::string::npos) << trace.ToString(); + ASSERT_TRUE(trace.ToString().find("TestFindSymbol3") == std::string::npos) << trace.ToString(); + ASSERT_TRUE(trace.FindSymbol((void*)TestFindSymbol2)) << trace.ToString(); + ASSERT_TRUE(trace.FindSymbol((void*)TestFindSymbol1)) << trace.ToString(); + ASSERT_FALSE(trace.FindSymbol((void*)TestFindSymbol3)) << trace.ToString(); +} + +void TestFindSymbol2() { + TestFindSymbol1(); +} + +void TestFindSymbol3() { + TestFindSymbol1(); +} + +TEST_F(StackTraceTest, find_symbol) { + ::TestFindSymbol2(); + TestFindSymbol2(); +} + +} // namespace debug +} // namespace butil diff --git a/test/status_unittest.cpp b/test/status_unittest.cpp new file mode 100644 index 0000000..03f21bc --- /dev/null +++ b/test/status_unittest.cpp @@ -0,0 +1,272 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include "butil/status.h" + +namespace { +class StatusTest : public ::testing::Test{ +protected: + StatusTest(){ + }; + virtual ~StatusTest(){}; + virtual void SetUp() { + srand(time(0)); + }; + virtual void TearDown() { + }; +}; + +TEST_F(StatusTest, success_status) { + std::ostringstream oss; + + butil::Status st; + ASSERT_TRUE(st.ok()); + ASSERT_EQ(0, st.error_code()); + ASSERT_STREQ("OK", st.error_cstr()); + ASSERT_EQ("OK", st.error_str()); + oss.str(""); + oss << st; + ASSERT_EQ("OK", oss.str()); + + butil::Status st2(0, "blahblah"); + ASSERT_TRUE(st2.ok()); + ASSERT_EQ(0, st2.error_code()); + ASSERT_STREQ("OK", st2.error_cstr()); + ASSERT_EQ("OK", st2.error_str()); + oss.str(""); + oss << st2; + ASSERT_EQ("OK", oss.str()); + + butil::Status st3 = butil::Status::OK(); + ASSERT_TRUE(st3.ok()); + ASSERT_EQ(0, st3.error_code()); + ASSERT_STREQ("OK", st3.error_cstr()); + ASSERT_EQ("OK", st3.error_str()); + oss.str(""); + oss << st3; + ASSERT_EQ("OK", oss.str()); +} + +#define NO_MEMORY_STR "No memory" +#define NO_CPU_STR "No CPU" + +TEST_F(StatusTest, failed_status) { + std::ostringstream oss; + + butil::Status st1(ENOMEM, NO_MEMORY_STR); + ASSERT_FALSE(st1.ok()); + ASSERT_EQ(ENOMEM, st1.error_code()); + ASSERT_STREQ(NO_MEMORY_STR, st1.error_cstr()); + ASSERT_EQ(NO_MEMORY_STR, st1.error_str()); + oss.str(""); + oss << st1; + ASSERT_EQ(NO_MEMORY_STR, oss.str()); + + butil::Status st2(EINVAL, "%s%s", NO_MEMORY_STR, NO_CPU_STR); + ASSERT_FALSE(st2.ok()); + ASSERT_EQ(EINVAL, st2.error_code()); + ASSERT_STREQ(NO_MEMORY_STR NO_CPU_STR, st2.error_cstr()); + ASSERT_EQ(NO_MEMORY_STR NO_CPU_STR, st2.error_str()); + oss.str(""); + oss << st2; + ASSERT_EQ(NO_MEMORY_STR NO_CPU_STR, oss.str()); + + butil::Status st3(ENOMEM, NO_MEMORY_STR); + ASSERT_FALSE(st3.ok()); + ASSERT_EQ(ENOMEM, st3.error_code()); + ASSERT_STREQ(NO_MEMORY_STR, st3.error_cstr()); + ASSERT_EQ(NO_MEMORY_STR, st3.error_str()); + oss.str(""); + oss << st3; + ASSERT_EQ(NO_MEMORY_STR, oss.str()); + + butil::Status st4(EINVAL, "%s%s", NO_MEMORY_STR, NO_CPU_STR); + ASSERT_FALSE(st4.ok()); + ASSERT_EQ(EINVAL, st4.error_code()); + ASSERT_STREQ(NO_MEMORY_STR NO_CPU_STR, st4.error_cstr()); + ASSERT_EQ(NO_MEMORY_STR NO_CPU_STR, st4.error_str()); + oss.str(""); + oss << st4; + ASSERT_EQ(NO_MEMORY_STR NO_CPU_STR, oss.str()); + + butil::Status st5(EINVAL, "Blah"); + ASSERT_FALSE(st5.ok()); + ASSERT_EQ(EINVAL, st5.error_code()); + ASSERT_STREQ("Blah", st5.error_cstr()); + ASSERT_EQ(std::string("Blah"), st5.error_str()); + oss.str(""); + oss << st5; + ASSERT_EQ(std::string("Blah"), oss.str()); +} + +#define VERYLONGERROR \ + "verylongverylongverylongverylongverylongverylongverylongverylong" \ + "verylongverylongverylongverylongverylongverylongverylongverylong" \ + "verylongverylongverylongverylongverylongverylongverylongverylong" \ + "verylongverylongverylongverylongverylongverylongverylongverylong" \ + "verylongverylongverylongverylongverylongverylongverylongverylong" \ + "verylongverylongverylongverylongverylongverylongverylongverylong" \ + "verylongverylongverylongverylongverylongverylongverylongverylong" \ + "verylongverylongverylongverylongverylongverylongverylongverylong" \ + "verylongverylongverylongverylongverylongverylongverylongverylong" \ + "verylongverylongverylongverylongverylongverylongverylongverylong" \ + "verylongverylongverylongverylongverylongverylongverylongverylong" \ + " error" + +TEST_F(StatusTest, reset) { + std::ostringstream oss; + + butil::Status st1(ENOMEM, NO_MEMORY_STR); + ASSERT_FALSE(st1.ok()); + ASSERT_EQ(ENOMEM, st1.error_code()); + ASSERT_STREQ(NO_MEMORY_STR, st1.error_cstr()); + ASSERT_EQ(NO_MEMORY_STR, st1.error_str()); + oss.str(""); + oss << st1; + ASSERT_EQ(NO_MEMORY_STR, oss.str()); + + ASSERT_EQ(0, st1.set_error(EINVAL, "%s%s", NO_MEMORY_STR, NO_CPU_STR)); + ASSERT_FALSE(st1.ok()); + ASSERT_EQ(EINVAL, st1.error_code()); + ASSERT_STREQ(NO_MEMORY_STR NO_CPU_STR, st1.error_cstr()); + ASSERT_EQ(NO_MEMORY_STR NO_CPU_STR, st1.error_str()); + oss.str(""); + oss << st1; + ASSERT_EQ(NO_MEMORY_STR NO_CPU_STR, oss.str()); + + ASSERT_EQ(0, st1.set_error(ENOMEM, NO_MEMORY_STR)); + ASSERT_FALSE(st1.ok()); + ASSERT_EQ(ENOMEM, st1.error_code()); + ASSERT_STREQ(NO_MEMORY_STR, st1.error_cstr()); + ASSERT_EQ(NO_MEMORY_STR, st1.error_str()); + oss.str(""); + oss << st1; + ASSERT_EQ(NO_MEMORY_STR, oss.str()); + + st1.reset(); + ASSERT_TRUE(st1.ok()); + ASSERT_EQ(0, st1.error_code()); + ASSERT_STREQ("OK", st1.error_cstr()); + ASSERT_EQ("OK", st1.error_str()); + oss.str(""); + oss << st1; + ASSERT_EQ("OK", oss.str()); + + ASSERT_EQ(0, st1.set_error(ENOMEM, "%s", VERYLONGERROR)); + ASSERT_FALSE(st1.ok()); + ASSERT_EQ(ENOMEM, st1.error_code()); + ASSERT_STREQ(VERYLONGERROR, st1.error_cstr()); + ASSERT_EQ(VERYLONGERROR, st1.error_str()); + oss.str(""); + oss << st1; + ASSERT_EQ(VERYLONGERROR, oss.str()); +} + +TEST_F(StatusTest, copy) { + std::ostringstream oss; + + butil::Status st1(ENOMEM, NO_MEMORY_STR); + ASSERT_FALSE(st1.ok()); + ASSERT_EQ(ENOMEM, st1.error_code()); + ASSERT_STREQ(NO_MEMORY_STR, st1.error_cstr()); + ASSERT_EQ(NO_MEMORY_STR, st1.error_str()); + oss.str(""); + oss << st1; + ASSERT_EQ(NO_MEMORY_STR, oss.str()); + + butil::Status st2; + ASSERT_TRUE(st2.ok()); + ASSERT_EQ(0, st2.error_code()); + ASSERT_STREQ("OK", st2.error_cstr()); + ASSERT_EQ("OK", st2.error_str()); + oss.str(""); + oss << st2; + ASSERT_EQ("OK", oss.str()); + + st2 = st1; + ASSERT_FALSE(st2.ok()); + ASSERT_EQ(ENOMEM, st2.error_code()); + ASSERT_STREQ(NO_MEMORY_STR, st2.error_cstr()); + ASSERT_EQ(NO_MEMORY_STR, st2.error_str()); + oss.str(""); + oss << st2; + ASSERT_EQ(NO_MEMORY_STR, oss.str()); + + ASSERT_EQ(0, st1.set_error(EINVAL, "%s%s", NO_MEMORY_STR, NO_CPU_STR)); + ASSERT_FALSE(st1.ok()); + ASSERT_EQ(EINVAL, st1.error_code()); + ASSERT_STREQ(NO_MEMORY_STR NO_CPU_STR, st1.error_cstr()); + ASSERT_EQ(NO_MEMORY_STR NO_CPU_STR, st1.error_str()); + oss.str(""); + oss << st1; + ASSERT_EQ(NO_MEMORY_STR NO_CPU_STR, oss.str()); + + ASSERT_FALSE(st2.ok()); + ASSERT_EQ(ENOMEM, st2.error_code()); + ASSERT_STREQ(NO_MEMORY_STR, st2.error_cstr()); + ASSERT_EQ(NO_MEMORY_STR, st2.error_str()); + oss.str(""); + oss << st2; + ASSERT_EQ(NO_MEMORY_STR, oss.str()); + + st2 = st1; + ASSERT_FALSE(st2.ok()); + ASSERT_EQ(EINVAL, st2.error_code()); + ASSERT_STREQ(NO_MEMORY_STR NO_CPU_STR, st2.error_cstr()); + ASSERT_EQ(NO_MEMORY_STR NO_CPU_STR, st2.error_str()); + oss.str(""); + oss << st2; + ASSERT_EQ(NO_MEMORY_STR NO_CPU_STR, oss.str()); + + ASSERT_EQ(0, st1.set_error(ENOMEM, NO_MEMORY_STR)); + ASSERT_FALSE(st1.ok()); + ASSERT_EQ(ENOMEM, st1.error_code()); + ASSERT_STREQ(NO_MEMORY_STR, st1.error_cstr()); + ASSERT_EQ(NO_MEMORY_STR, st1.error_str()); + oss.str(""); + oss << st1; + ASSERT_EQ(NO_MEMORY_STR, oss.str()); + + // assign shorter to longer, no memory allocation. + st2 = st1; + ASSERT_FALSE(st2.ok()); + ASSERT_EQ(ENOMEM, st2.error_code()); + ASSERT_STREQ(NO_MEMORY_STR, st2.error_cstr()); + ASSERT_EQ(NO_MEMORY_STR, st2.error_str()); + oss.str(""); + oss << st2; + ASSERT_EQ(NO_MEMORY_STR, oss.str()); +} + +TEST_F(StatusTest, message_has_zero) { + std::ostringstream oss; + char str[32] = "hello world"; + butil::StringPiece slice(str); + ASSERT_EQ(11UL, slice.as_string().size()); + str[5] = '\0'; + ASSERT_EQ(11UL, slice.as_string().size()); + butil::Status st1(ENOMEM, slice); + ASSERT_FALSE(st1.ok()); + ASSERT_EQ(ENOMEM, st1.error_code()); + ASSERT_STREQ("hello", st1.error_cstr()); + oss.str(""); + oss << st1; + ASSERT_EQ(st1.error_str(), oss.str()); +} +} diff --git a/test/stl_util_unittest.cc b/test/stl_util_unittest.cc new file mode 100644 index 0000000..6161002 --- /dev/null +++ b/test/stl_util_unittest.cc @@ -0,0 +1,242 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/stl_util.h" + +#include + +#include + +namespace { + +// Used as test case to ensure the various butil::STLXxx functions don't require +// more than operators "<" and "==" on values stored in containers. +class ComparableValue { + public: + explicit ComparableValue(int value) : value_(value) {} + + bool operator==(const ComparableValue& rhs) const { + return value_ == rhs.value_; + } + + bool operator<(const ComparableValue& rhs) const { + return value_ < rhs.value_; + } + + private: + int value_; +}; + +} + +namespace butil { +namespace { + +TEST(STLUtilTest, STLIsSorted) { + { + std::set set; + set.insert(24); + set.insert(1); + set.insert(12); + EXPECT_TRUE(STLIsSorted(set)); + } + + { + std::set set; + set.insert(ComparableValue(24)); + set.insert(ComparableValue(1)); + set.insert(ComparableValue(12)); + EXPECT_TRUE(STLIsSorted(set)); + } + + { + std::vector vector; + vector.push_back(1); + vector.push_back(1); + vector.push_back(4); + vector.push_back(64); + vector.push_back(12432); + EXPECT_TRUE(STLIsSorted(vector)); + vector.back() = 1; + EXPECT_FALSE(STLIsSorted(vector)); + } +} + +TEST(STLUtilTest, STLSetDifference) { + std::set a1; + a1.insert(1); + a1.insert(2); + a1.insert(3); + a1.insert(4); + + std::set a2; + a2.insert(3); + a2.insert(4); + a2.insert(5); + a2.insert(6); + a2.insert(7); + + { + std::set difference; + difference.insert(1); + difference.insert(2); + EXPECT_EQ(difference, STLSetDifference >(a1, a2)); + } + + { + std::set difference; + difference.insert(5); + difference.insert(6); + difference.insert(7); + EXPECT_EQ(difference, STLSetDifference >(a2, a1)); + } + + { + std::vector difference; + difference.push_back(1); + difference.push_back(2); + EXPECT_EQ(difference, STLSetDifference >(a1, a2)); + } + + { + std::vector difference; + difference.push_back(5); + difference.push_back(6); + difference.push_back(7); + EXPECT_EQ(difference, STLSetDifference >(a2, a1)); + } +} + +TEST(STLUtilTest, STLSetUnion) { + std::set a1; + a1.insert(1); + a1.insert(2); + a1.insert(3); + a1.insert(4); + + std::set a2; + a2.insert(3); + a2.insert(4); + a2.insert(5); + a2.insert(6); + a2.insert(7); + + { + std::set result; + result.insert(1); + result.insert(2); + result.insert(3); + result.insert(4); + result.insert(5); + result.insert(6); + result.insert(7); + EXPECT_EQ(result, STLSetUnion >(a1, a2)); + } + + { + std::set result; + result.insert(1); + result.insert(2); + result.insert(3); + result.insert(4); + result.insert(5); + result.insert(6); + result.insert(7); + EXPECT_EQ(result, STLSetUnion >(a2, a1)); + } + + { + std::vector result; + result.push_back(1); + result.push_back(2); + result.push_back(3); + result.push_back(4); + result.push_back(5); + result.push_back(6); + result.push_back(7); + EXPECT_EQ(result, STLSetUnion >(a1, a2)); + } + + { + std::vector result; + result.push_back(1); + result.push_back(2); + result.push_back(3); + result.push_back(4); + result.push_back(5); + result.push_back(6); + result.push_back(7); + EXPECT_EQ(result, STLSetUnion >(a2, a1)); + } +} + +TEST(STLUtilTest, STLSetIntersection) { + std::set a1; + a1.insert(1); + a1.insert(2); + a1.insert(3); + a1.insert(4); + + std::set a2; + a2.insert(3); + a2.insert(4); + a2.insert(5); + a2.insert(6); + a2.insert(7); + + { + std::set result; + result.insert(3); + result.insert(4); + EXPECT_EQ(result, STLSetIntersection >(a1, a2)); + } + + { + std::set result; + result.insert(3); + result.insert(4); + EXPECT_EQ(result, STLSetIntersection >(a2, a1)); + } + + { + std::vector result; + result.push_back(3); + result.push_back(4); + EXPECT_EQ(result, STLSetIntersection >(a1, a2)); + } + + { + std::vector result; + result.push_back(3); + result.push_back(4); + EXPECT_EQ(result, STLSetIntersection >(a2, a1)); + } +} + +TEST(STLUtilTest, STLIncludes) { + std::set a1; + a1.insert(1); + a1.insert(2); + a1.insert(3); + a1.insert(4); + + std::set a2; + a2.insert(3); + a2.insert(4); + + std::set a3; + a3.insert(3); + a3.insert(4); + a3.insert(5); + + EXPECT_TRUE(STLIncludes >(a1, a2)); + EXPECT_FALSE(STLIncludes >(a1, a3)); + EXPECT_FALSE(STLIncludes >(a2, a1)); + EXPECT_FALSE(STLIncludes >(a2, a3)); + EXPECT_FALSE(STLIncludes >(a3, a1)); + EXPECT_TRUE(STLIncludes >(a3, a2)); +} + +} // namespace +} // namespace butil diff --git a/test/string16_unittest.cc b/test/string16_unittest.cc new file mode 100644 index 0000000..1a9eff5 --- /dev/null +++ b/test/string16_unittest.cc @@ -0,0 +1,58 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "butil/strings/string16.h" + +#include "butil/strings/utf_string_conversions.h" +#include + +namespace butil { + +#if defined(WCHAR_T_IS_UTF32) + +// We define a custom operator<< for string16 so we can use it with logging. +// This tests that conversion. +TEST(String16Test, OutputStream) { + // Basic stream test. + { + std::ostringstream stream; + stream << "Empty '" << string16() << "' standard '" + << string16(ASCIIToUTF16("Hello, world")) << "'"; + EXPECT_STREQ("Empty '' standard 'Hello, world'", + stream.str().c_str()); + } + + // Interesting edge cases. + { + // These should each get converted to the invalid character: EF BF BD. + string16 initial_surrogate; + initial_surrogate.push_back(0xd800); + string16 final_surrogate; + final_surrogate.push_back(0xdc00); + + // Old italic A = U+10300, will get converted to: F0 90 8C 80 'z'. + string16 surrogate_pair; + surrogate_pair.push_back(0xd800); + surrogate_pair.push_back(0xdf00); + surrogate_pair.push_back('z'); + + // Will get converted to the invalid char + 's': EF BF BD 's'. + string16 unterminated_surrogate; + unterminated_surrogate.push_back(0xd800); + unterminated_surrogate.push_back('s'); + + std::ostringstream stream; + stream << initial_surrogate << "," << final_surrogate << "," + << surrogate_pair << "," << unterminated_surrogate; + + EXPECT_STREQ("\xef\xbf\xbd,\xef\xbf\xbd,\xf0\x90\x8c\x80z,\xef\xbf\xbds", + stream.str().c_str()); + } +} + +#endif + +} // namespace butil diff --git a/test/string_number_conversions_unittest.cc b/test/string_number_conversions_unittest.cc new file mode 100644 index 0000000..7d1e9a6 --- /dev/null +++ b/test/string_number_conversions_unittest.cc @@ -0,0 +1,796 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#include +#include + +#include "butil/format_macros.h" +#include "butil/strings/string_number_conversions.h" +#include "butil/strings/stringprintf.h" +#include "butil/strings/utf_string_conversions.h" +#include + +namespace butil { + +namespace { + +template +struct IntToStringTest { + INT num; + const char* sexpected; + const char* uexpected; +}; + +} // namespace + +TEST(StringNumberConversionsTest, IntToString) { + static const IntToStringTest int_tests[] = { + { 0, "0", "0" }, + { -1, "-1", "4294967295" }, + { std::numeric_limits::max(), "2147483647", "2147483647" }, + { std::numeric_limits::min(), "-2147483648", "2147483648" }, + }; + static const IntToStringTest int64_tests[] = { + { 0, "0", "0" }, + { -1, "-1", "18446744073709551615" }, + { std::numeric_limits::max(), + "9223372036854775807", + "9223372036854775807", }, + { std::numeric_limits::min(), + "-9223372036854775808", + "9223372036854775808" }, + }; + + for (size_t i = 0; i < arraysize(int_tests); ++i) { + const IntToStringTest* test = &int_tests[i]; + EXPECT_EQ(IntToString(test->num), test->sexpected); + EXPECT_EQ(IntToString16(test->num), UTF8ToUTF16(test->sexpected)); + EXPECT_EQ(UintToString(test->num), test->uexpected); + EXPECT_EQ(UintToString16(test->num), UTF8ToUTF16(test->uexpected)); + } + for (size_t i = 0; i < arraysize(int64_tests); ++i) { + const IntToStringTest* test = &int64_tests[i]; + EXPECT_EQ(Int64ToString(test->num), test->sexpected); + EXPECT_EQ(Int64ToString16(test->num), UTF8ToUTF16(test->sexpected)); + EXPECT_EQ(Uint64ToString(test->num), test->uexpected); + EXPECT_EQ(Uint64ToString16(test->num), UTF8ToUTF16(test->uexpected)); + } +} + +TEST(StringNumberConversionsTest, Uint64ToString) { + static const struct { + uint64_t input; + std::string output; + } cases[] = { + {0, "0"}, + {42, "42"}, + {INT_MAX, "2147483647"}, + {kuint64max, "18446744073709551615"}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) + EXPECT_EQ(cases[i].output, Uint64ToString(cases[i].input)); +} + +TEST(StringNumberConversionsTest, SizeTToString) { + size_t size_t_max = std::numeric_limits::max(); + std::string size_t_max_string = StringPrintf("%" PRIuS, size_t_max); + + static const struct { + size_t input; + std::string output; + } cases[] = { + {0, "0"}, + {9, "9"}, + {42, "42"}, + {INT_MAX, "2147483647"}, + {2147483648U, "2147483648"}, +#if SIZE_MAX > 4294967295U + {99999999999U, "99999999999"}, +#endif + {size_t_max, size_t_max_string}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) + EXPECT_EQ(cases[i].output, Uint64ToString(cases[i].input)); +} + +TEST(StringNumberConversionsTest, StringToInt) { + static const struct { + std::string input; + int output; + bool success; + } cases[] = { + {"0", 0, true}, + {"42", 42, true}, + {"42\x99", 42, false}, + {"\x99" "42\x99", 0, false}, + {"-2147483648", INT_MIN, true}, + {"2147483647", INT_MAX, true}, + {"", 0, false}, + {" 42", 42, false}, + {"42 ", 42, false}, + {"\t\n\v\f\r 42", 42, false}, + {"blah42", 0, false}, + {"42blah", 42, false}, + {"blah42blah", 0, false}, + {"-273.15", -273, false}, + {"+98.6", 98, false}, + {"--123", 0, false}, + {"++123", 0, false}, + {"-+123", 0, false}, + {"+-123", 0, false}, + {"-", 0, false}, + {"-2147483649", INT_MIN, false}, + {"-99999999999", INT_MIN, false}, + {"2147483648", INT_MAX, false}, + {"99999999999", INT_MAX, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + int output = 0; + EXPECT_EQ(cases[i].success, StringToInt(cases[i].input, &output)); + EXPECT_EQ(cases[i].output, output); + + string16 utf16_input = UTF8ToUTF16(cases[i].input); + output = 0; + EXPECT_EQ(cases[i].success, StringToInt(utf16_input, &output)); + EXPECT_EQ(cases[i].output, output); + } + + // One additional test to verify that conversion of numbers in strings with + // embedded NUL characters. The NUL and extra data after it should be + // interpreted as junk after the number. + const char input[] = "6\06"; + std::string input_string(input, arraysize(input) - 1); + int output; + EXPECT_FALSE(StringToInt(input_string, &output)); + EXPECT_EQ(6, output); + + string16 utf16_input = UTF8ToUTF16(input_string); + output = 0; + EXPECT_FALSE(StringToInt(utf16_input, &output)); + EXPECT_EQ(6, output); + + output = 0; + const char16 negative_wide_input[] = { 0xFF4D, '4', '2', 0}; + EXPECT_FALSE(StringToInt(string16(negative_wide_input), &output)); + EXPECT_EQ(0, output); +} + +TEST(StringNumberConversionsTest, StringToUint) { + static const struct { + std::string input; + unsigned output; + bool success; + } cases[] = { + {"0", 0, true}, + {"42", 42, true}, + {"42\x99", 42, false}, + {"\x99" "42\x99", 0, false}, + {"-2147483648", 0, false}, + {"2147483647", INT_MAX, true}, + {"", 0, false}, + {" 42", 42, false}, + {"42 ", 42, false}, + {"\t\n\v\f\r 42", 42, false}, + {"blah42", 0, false}, + {"42blah", 42, false}, + {"blah42blah", 0, false}, + {"-273.15", 0, false}, + {"+98.6", 98, false}, + {"--123", 0, false}, + {"++123", 0, false}, + {"-+123", 0, false}, + {"+-123", 0, false}, + {"-", 0, false}, + {"-2147483649", 0, false}, + {"-99999999999", 0, false}, + {"4294967295", UINT_MAX, true}, + {"4294967296", UINT_MAX, false}, + {"99999999999", UINT_MAX, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + unsigned output = 0; + EXPECT_EQ(cases[i].success, StringToUint(cases[i].input, &output)); + EXPECT_EQ(cases[i].output, output); + + string16 utf16_input = UTF8ToUTF16(cases[i].input); + output = 0; + EXPECT_EQ(cases[i].success, StringToUint(utf16_input, &output)); + EXPECT_EQ(cases[i].output, output); + } + + // One additional test to verify that conversion of numbers in strings with + // embedded NUL characters. The NUL and extra data after it should be + // interpreted as junk after the number. + const char input[] = "6\06"; + std::string input_string(input, arraysize(input) - 1); + unsigned output; + EXPECT_FALSE(StringToUint(input_string, &output)); + EXPECT_EQ(6U, output); + + string16 utf16_input = UTF8ToUTF16(input_string); + output = 0; + EXPECT_FALSE(StringToUint(utf16_input, &output)); + EXPECT_EQ(6U, output); + + output = 0; + const char16 negative_wide_input[] = { 0xFF4D, '4', '2', 0}; + EXPECT_FALSE(StringToUint(string16(negative_wide_input), &output)); + EXPECT_EQ(0U, output); +} + +TEST(StringNumberConversionsTest, StringToInt64) { + static const struct { + std::string input; + int64_t output; + bool success; + } cases[] = { + {"0", 0, true}, + {"42", 42, true}, + {"-2147483648", INT_MIN, true}, + {"2147483647", INT_MAX, true}, + {"-2147483649", GG_INT64_C(-2147483649), true}, + {"-99999999999", GG_INT64_C(-99999999999), true}, + {"2147483648", GG_INT64_C(2147483648), true}, + {"99999999999", GG_INT64_C(99999999999), true}, + {"9223372036854775807", kint64max, true}, + {"-9223372036854775808", kint64min, true}, + {"09", 9, true}, + {"-09", -9, true}, + {"", 0, false}, + {" 42", 42, false}, + {"42 ", 42, false}, + {"0x42", 0, false}, + {"\t\n\v\f\r 42", 42, false}, + {"blah42", 0, false}, + {"42blah", 42, false}, + {"blah42blah", 0, false}, + {"-273.15", -273, false}, + {"+98.6", 98, false}, + {"--123", 0, false}, + {"++123", 0, false}, + {"-+123", 0, false}, + {"+-123", 0, false}, + {"-", 0, false}, + {"-9223372036854775809", kint64min, false}, + {"-99999999999999999999", kint64min, false}, + {"9223372036854775808", kint64max, false}, + {"99999999999999999999", kint64max, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + int64_t output = 0; + EXPECT_EQ(cases[i].success, StringToInt64(cases[i].input, &output)); + EXPECT_EQ(cases[i].output, output); + + string16 utf16_input = UTF8ToUTF16(cases[i].input); + output = 0; + EXPECT_EQ(cases[i].success, StringToInt64(utf16_input, &output)); + EXPECT_EQ(cases[i].output, output); + } + + // One additional test to verify that conversion of numbers in strings with + // embedded NUL characters. The NUL and extra data after it should be + // interpreted as junk after the number. + const char input[] = "6\06"; + std::string input_string(input, arraysize(input) - 1); + int64_t output; + EXPECT_FALSE(StringToInt64(input_string, &output)); + EXPECT_EQ(6, output); + + string16 utf16_input = UTF8ToUTF16(input_string); + output = 0; + EXPECT_FALSE(StringToInt64(utf16_input, &output)); + EXPECT_EQ(6, output); +} + +TEST(StringNumberConversionsTest, StringToUint64) { + static const struct { + std::string input; + uint64_t output; + bool success; + } cases[] = { + {"0", 0, true}, + {"42", 42, true}, + {"-2147483648", 0, false}, + {"2147483647", INT_MAX, true}, + {"-2147483649", 0, false}, + {"-99999999999", 0, false}, + {"2147483648", GG_UINT64_C(2147483648), true}, + {"99999999999", GG_UINT64_C(99999999999), true}, + {"9223372036854775807", kint64max, true}, + {"-9223372036854775808", 0, false}, + {"09", 9, true}, + {"-09", 0, false}, + {"", 0, false}, + {" 42", 42, false}, + {"42 ", 42, false}, + {"0x42", 0, false}, + {"\t\n\v\f\r 42", 42, false}, + {"blah42", 0, false}, + {"42blah", 42, false}, + {"blah42blah", 0, false}, + {"-273.15", 0, false}, + {"+98.6", 98, false}, + {"--123", 0, false}, + {"++123", 0, false}, + {"-+123", 0, false}, + {"+-123", 0, false}, + {"-", 0, false}, + {"-9223372036854775809", 0, false}, + {"-99999999999999999999", 0, false}, + {"9223372036854775808", GG_UINT64_C(9223372036854775808), true}, + {"99999999999999999999", kuint64max, false}, + {"18446744073709551615", kuint64max, true}, + {"18446744073709551616", kuint64max, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + uint64_t output = 0; + EXPECT_EQ(cases[i].success, StringToUint64(cases[i].input, &output)); + EXPECT_EQ(cases[i].output, output); + + string16 utf16_input = UTF8ToUTF16(cases[i].input); + output = 0; + EXPECT_EQ(cases[i].success, StringToUint64(utf16_input, &output)); + EXPECT_EQ(cases[i].output, output); + } + + // One additional test to verify that conversion of numbers in strings with + // embedded NUL characters. The NUL and extra data after it should be + // interpreted as junk after the number. + const char input[] = "6\06"; + std::string input_string(input, arraysize(input) - 1); + uint64_t output; + EXPECT_FALSE(StringToUint64(input_string, &output)); + EXPECT_EQ(6U, output); + + string16 utf16_input = UTF8ToUTF16(input_string); + output = 0; + EXPECT_FALSE(StringToUint64(utf16_input, &output)); + EXPECT_EQ(6U, output); +} + +TEST(StringNumberConversionsTest, StringToSizeT) { + size_t size_t_max = std::numeric_limits::max(); + std::string size_t_max_string = StringPrintf("%" PRIuS, size_t_max); + + static const struct { + std::string input; + size_t output; + bool success; + } cases[] = { + {"0", 0, true}, + {"42", 42, true}, + {"-2147483648", 0, false}, + {"2147483647", INT_MAX, true}, + {"-2147483649", 0, false}, + {"-99999999999", 0, false}, + {"2147483648", 2147483648U, true}, +#if SIZE_MAX > 4294967295U + {"99999999999", 99999999999U, true}, +#endif + {"-9223372036854775808", 0, false}, + {"09", 9, true}, + {"-09", 0, false}, + {"", 0, false}, + {" 42", 42, false}, + {"42 ", 42, false}, + {"0x42", 0, false}, + {"\t\n\v\f\r 42", 42, false}, + {"blah42", 0, false}, + {"42blah", 42, false}, + {"blah42blah", 0, false}, + {"-273.15", 0, false}, + {"+98.6", 98, false}, + {"--123", 0, false}, + {"++123", 0, false}, + {"-+123", 0, false}, + {"+-123", 0, false}, + {"-", 0, false}, + {"-9223372036854775809", 0, false}, + {"-99999999999999999999", 0, false}, + {"999999999999999999999999", size_t_max, false}, + {size_t_max_string, size_t_max, true}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + size_t output = 0; + EXPECT_EQ(cases[i].success, StringToSizeT(cases[i].input, &output)); + EXPECT_EQ(cases[i].output, output); + + string16 utf16_input = UTF8ToUTF16(cases[i].input); + output = 0; + EXPECT_EQ(cases[i].success, StringToSizeT(utf16_input, &output)); + EXPECT_EQ(cases[i].output, output); + } + + // One additional test to verify that conversion of numbers in strings with + // embedded NUL characters. The NUL and extra data after it should be + // interpreted as junk after the number. + const char input[] = "6\06"; + std::string input_string(input, arraysize(input) - 1); + size_t output; + EXPECT_FALSE(StringToSizeT(input_string, &output)); + EXPECT_EQ(6U, output); + + string16 utf16_input = UTF8ToUTF16(input_string); + output = 0; + EXPECT_FALSE(StringToSizeT(utf16_input, &output)); + EXPECT_EQ(6U, output); +} + +TEST(StringNumberConversionsTest, HexStringToInt) { + static const struct { + std::string input; + int64_t output; + bool success; + } cases[] = { + {"0", 0, true}, + {"42", 66, true}, + {"-42", -66, true}, + {"+42", 66, true}, + {"7fffffff", INT_MAX, true}, + {"-80000000", INT_MIN, true}, + {"80000000", INT_MAX, false}, // Overflow test. + {"-80000001", INT_MIN, false}, // Underflow test. + {"0x42", 66, true}, + {"-0x42", -66, true}, + {"+0x42", 66, true}, + {"0x7fffffff", INT_MAX, true}, + {"-0x80000000", INT_MIN, true}, + {"-80000000", INT_MIN, true}, + {"80000000", INT_MAX, false}, // Overflow test. + {"-80000001", INT_MIN, false}, // Underflow test. + {"0x0f", 15, true}, + {"0f", 15, true}, + {" 45", 0x45, false}, + {"\t\n\v\f\r 0x45", 0x45, false}, + {" 45", 0x45, false}, + {"45 ", 0x45, false}, + {"45:", 0x45, false}, + {"efgh", 0xef, false}, + {"0xefgh", 0xef, false}, + {"hgfe", 0, false}, + {"-", 0, false}, + {"", 0, false}, + {"0x", 0, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + int output = 0; + EXPECT_EQ(cases[i].success, HexStringToInt(cases[i].input, &output)); + EXPECT_EQ(cases[i].output, output); + } + // One additional test to verify that conversion of numbers in strings with + // embedded NUL characters. The NUL and extra data after it should be + // interpreted as junk after the number. + const char input[] = "0xc0ffee\09"; + std::string input_string(input, arraysize(input) - 1); + int output; + EXPECT_FALSE(HexStringToInt(input_string, &output)); + EXPECT_EQ(0xc0ffee, output); +} + +TEST(StringNumberConversionsTest, HexStringToUInt) { + static const struct { + std::string input; + uint32_t output; + bool success; + } cases[] = { + {"0", 0, true}, + {"42", 0x42, true}, + {"-42", 0, false}, + {"+42", 0x42, true}, + {"7fffffff", INT_MAX, true}, + {"-80000000", 0, false}, + {"ffffffff", 0xffffffff, true}, + {"DeadBeef", 0xdeadbeef, true}, + {"0x42", 0x42, true}, + {"-0x42", 0, false}, + {"+0x42", 0x42, true}, + {"0x7fffffff", INT_MAX, true}, + {"-0x80000000", 0, false}, + {"0xffffffff", kuint32max, true}, + {"0XDeadBeef", 0xdeadbeef, true}, + {"0x7fffffffffffffff", kuint32max, false}, // Overflow test. + {"-0x8000000000000000", 0, false}, + {"0x8000000000000000", kuint32max, false}, // Overflow test. + {"-0x8000000000000001", 0, false}, + {"0xFFFFFFFFFFFFFFFF", kuint32max, false}, // Overflow test. + {"FFFFFFFFFFFFFFFF", kuint32max, false}, // Overflow test. + {"0x0000000000000000", 0, true}, + {"0000000000000000", 0, true}, + {"1FFFFFFFFFFFFFFFF", kuint32max, false}, // Overflow test. + {"0x0f", 0x0f, true}, + {"0f", 0x0f, true}, + {" 45", 0x45, false}, + {"\t\n\v\f\r 0x45", 0x45, false}, + {" 45", 0x45, false}, + {"45 ", 0x45, false}, + {"45:", 0x45, false}, + {"efgh", 0xef, false}, + {"0xefgh", 0xef, false}, + {"hgfe", 0, false}, + {"-", 0, false}, + {"", 0, false}, + {"0x", 0, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + uint32_t output = 0; + EXPECT_EQ(cases[i].success, HexStringToUInt(cases[i].input, &output)); + EXPECT_EQ(cases[i].output, output); + } + // One additional test to verify that conversion of numbers in strings with + // embedded NUL characters. The NUL and extra data after it should be + // interpreted as junk after the number. + const char input[] = "0xc0ffee\09"; + std::string input_string(input, arraysize(input) - 1); + uint32_t output; + EXPECT_FALSE(HexStringToUInt(input_string, &output)); + EXPECT_EQ(0xc0ffeeU, output); +} + +TEST(StringNumberConversionsTest, HexStringToInt64) { + static const struct { + std::string input; + int64_t output; + bool success; + } cases[] = { + {"0", 0, true}, + {"42", 66, true}, + {"-42", -66, true}, + {"+42", 66, true}, + {"40acd88557b", GG_INT64_C(4444444448123), true}, + {"7fffffff", INT_MAX, true}, + {"-80000000", INT_MIN, true}, + {"ffffffff", 0xffffffff, true}, + {"DeadBeef", 0xdeadbeef, true}, + {"0x42", 66, true}, + {"-0x42", -66, true}, + {"+0x42", 66, true}, + {"0x40acd88557b", GG_INT64_C(4444444448123), true}, + {"0x7fffffff", INT_MAX, true}, + {"-0x80000000", INT_MIN, true}, + {"0xffffffff", 0xffffffff, true}, + {"0XDeadBeef", 0xdeadbeef, true}, + {"0x7fffffffffffffff", kint64max, true}, + {"-0x8000000000000000", kint64min, true}, + {"0x8000000000000000", kint64max, false}, // Overflow test. + {"-0x8000000000000001", kint64min, false}, // Underflow test. + {"0x0f", 15, true}, + {"0f", 15, true}, + {" 45", 0x45, false}, + {"\t\n\v\f\r 0x45", 0x45, false}, + {" 45", 0x45, false}, + {"45 ", 0x45, false}, + {"45:", 0x45, false}, + {"efgh", 0xef, false}, + {"0xefgh", 0xef, false}, + {"hgfe", 0, false}, + {"-", 0, false}, + {"", 0, false}, + {"0x", 0, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + int64_t output = 0; + EXPECT_EQ(cases[i].success, HexStringToInt64(cases[i].input, &output)); + EXPECT_EQ(cases[i].output, output); + } + // One additional test to verify that conversion of numbers in strings with + // embedded NUL characters. The NUL and extra data after it should be + // interpreted as junk after the number. + const char input[] = "0xc0ffee\09"; + std::string input_string(input, arraysize(input) - 1); + int64_t output; + EXPECT_FALSE(HexStringToInt64(input_string, &output)); + EXPECT_EQ(0xc0ffee, output); +} + +TEST(StringNumberConversionsTest, HexStringToUInt64) { + static const struct { + std::string input; + uint64_t output; + bool success; + } cases[] = { + {"0", 0, true}, + {"42", 66, true}, + {"-42", 0, false}, + {"+42", 66, true}, + {"40acd88557b", GG_INT64_C(4444444448123), true}, + {"7fffffff", INT_MAX, true}, + {"-80000000", 0, false}, + {"ffffffff", 0xffffffff, true}, + {"DeadBeef", 0xdeadbeef, true}, + {"0x42", 66, true}, + {"-0x42", 0, false}, + {"+0x42", 66, true}, + {"0x40acd88557b", GG_INT64_C(4444444448123), true}, + {"0x7fffffff", INT_MAX, true}, + {"-0x80000000", 0, false}, + {"0xffffffff", 0xffffffff, true}, + {"0XDeadBeef", 0xdeadbeef, true}, + {"0x7fffffffffffffff", kint64max, true}, + {"-0x8000000000000000", 0, false}, + {"0x8000000000000000", GG_UINT64_C(0x8000000000000000), true}, + {"-0x8000000000000001", 0, false}, + {"0xFFFFFFFFFFFFFFFF", kuint64max, true}, + {"FFFFFFFFFFFFFFFF", kuint64max, true}, + {"0x0000000000000000", 0, true}, + {"0000000000000000", 0, true}, + {"1FFFFFFFFFFFFFFFF", kuint64max, false}, // Overflow test. + {"0x0f", 15, true}, + {"0f", 15, true}, + {" 45", 0x45, false}, + {"\t\n\v\f\r 0x45", 0x45, false}, + {" 45", 0x45, false}, + {"45 ", 0x45, false}, + {"45:", 0x45, false}, + {"efgh", 0xef, false}, + {"0xefgh", 0xef, false}, + {"hgfe", 0, false}, + {"-", 0, false}, + {"", 0, false}, + {"0x", 0, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + uint64_t output = 0; + EXPECT_EQ(cases[i].success, HexStringToUInt64(cases[i].input, &output)); + EXPECT_EQ(cases[i].output, output); + } + // One additional test to verify that conversion of numbers in strings with + // embedded NUL characters. The NUL and extra data after it should be + // interpreted as junk after the number. + const char input[] = "0xc0ffee\09"; + std::string input_string(input, arraysize(input) - 1); + uint64_t output; + EXPECT_FALSE(HexStringToUInt64(input_string, &output)); + EXPECT_EQ(0xc0ffeeU, output); +} + +TEST(StringNumberConversionsTest, HexStringToBytes) { + static const struct { + const std::string input; + const char* output; + size_t output_len; + bool success; + } cases[] = { + {"0", "", 0, false}, // odd number of characters fails + {"00", "\0", 1, true}, + {"42", "\x42", 1, true}, + {"-42", "", 0, false}, // any non-hex value fails + {"+42", "", 0, false}, + {"7fffffff", "\x7f\xff\xff\xff", 4, true}, + {"80000000", "\x80\0\0\0", 4, true}, + {"deadbeef", "\xde\xad\xbe\xef", 4, true}, + {"DeadBeef", "\xde\xad\xbe\xef", 4, true}, + {"0x42", "", 0, false}, // leading 0x fails (x is not hex) + {"0f", "\xf", 1, true}, + {"45 ", "\x45", 1, false}, + {"efgh", "\xef", 1, false}, + {"", "", 0, false}, + {"0123456789ABCDEF", "\x01\x23\x45\x67\x89\xAB\xCD\xEF", 8, true}, + {"0123456789ABCDEF012345", + "\x01\x23\x45\x67\x89\xAB\xCD\xEF\x01\x23\x45", 11, true}, + }; + + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + std::vector output; + std::vector compare; + EXPECT_EQ(cases[i].success, HexStringToBytes(cases[i].input, &output)) << + i << ": " << cases[i].input; + for (size_t j = 0; j < cases[i].output_len; ++j) + compare.push_back(static_cast(cases[i].output[j])); + ASSERT_EQ(output.size(), compare.size()) << i << ": " << cases[i].input; + EXPECT_TRUE(std::equal(output.begin(), output.end(), compare.begin())) << + i << ": " << cases[i].input; + } +} + +TEST(StringNumberConversionsTest, StringToDouble) { + static const struct { + std::string input; + double output; + bool success; + } cases[] = { + {"0", 0.0, true}, + {"42", 42.0, true}, + {"-42", -42.0, true}, + {"123.45", 123.45, true}, + {"-123.45", -123.45, true}, + {"+123.45", 123.45, true}, + {"2.99792458e8", 299792458.0, true}, + {"149597870.691E+3", 149597870691.0, true}, + {"6.", 6.0, true}, + {"9e99999999999999999999", HUGE_VAL, false}, + {"-9e99999999999999999999", -HUGE_VAL, false}, + {"1e-2", 0.01, true}, + {"42 ", 42.0, false}, + {" 1e-2", 0.01, false}, + {"1e-2 ", 0.01, false}, + {"-1E-7", -0.0000001, true}, + {"01e02", 100, true}, + {"2.3e15", 2.3e15, true}, + {"\t\n\v\f\r -123.45e2", -12345.0, false}, + {"+123 e4", 123.0, false}, + {"123e ", 123.0, false}, + {"123e", 123.0, false}, + {" 2.99", 2.99, false}, + {"1e3.4", 1000.0, false}, + {"nothing", 0.0, false}, + {"-", 0.0, false}, + {"+", 0.0, false}, + {"", 0.0, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + double output; + errno = 1; + EXPECT_EQ(cases[i].success, StringToDouble(cases[i].input, &output)); + if (cases[i].success) { + EXPECT_EQ(1, errno) << i; // confirm that errno is unchanged. + } + EXPECT_DOUBLE_EQ(cases[i].output, output); + } + + // One additional test to verify that conversion of numbers in strings with + // embedded NUL characters. The NUL and extra data after it should be + // interpreted as junk after the number. + const char input[] = "3.14\0159"; + std::string input_string(input, arraysize(input) - 1); + double output; + EXPECT_FALSE(StringToDouble(input_string, &output)); + EXPECT_DOUBLE_EQ(3.14, output); +} + +TEST(StringNumberConversionsTest, DoubleToString) { + static const struct { + double input; + const char* expected; + } cases[] = { + {0.0, "0"}, + {1.25, "1.25"}, + {1.33518e+012, "1.33518e+12"}, + {1.33489e+012, "1.33489e+12"}, + {1.33505e+012, "1.33505e+12"}, + {1.33545e+009, "1335450000"}, + {1.33503e+009, "1335030000"}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + EXPECT_EQ(cases[i].expected, DoubleToString(cases[i].input)); + } + + // The following two values were seen in crashes in the wild. + const char input_bytes[8] = {0, 0, 0, 0, '\xee', '\x6d', '\x73', '\x42'}; + double input = 0; + memcpy(&input, input_bytes, arraysize(input_bytes)); + EXPECT_EQ("1335179083776", DoubleToString(input)); + const char input_bytes2[8] = + {0, 0, 0, '\xa0', '\xda', '\x6c', '\x73', '\x42'}; + input = 0; + memcpy(&input, input_bytes2, arraysize(input_bytes2)); + EXPECT_EQ("1334890332160", DoubleToString(input)); +} + +TEST(StringNumberConversionsTest, HexEncode) { + std::string hex(HexEncode(NULL, 0)); + EXPECT_EQ(hex.length(), 0U); + unsigned char bytes[] = {0x01, 0xff, 0x02, 0xfe, 0x03, 0x80, 0x81}; + hex = HexEncode(bytes, sizeof(bytes)); + EXPECT_EQ(hex.compare("01FF02FE038081"), 0); +} + +} // namespace butil diff --git a/test/string_piece_unittest.cc b/test/string_piece_unittest.cc new file mode 100644 index 0000000..5b65c69 --- /dev/null +++ b/test/string_piece_unittest.cc @@ -0,0 +1,689 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "butil/strings/string16.h" +#include "butil/strings/string_piece.h" +#include "butil/strings/utf_string_conversions.h" +#include + +namespace butil { + +template +class CommonStringPieceTest : public ::testing::Test { + public: + static const T as_string(const char* input) { + return T(input); + } + static const T& as_string(const T& input) { + return input; + } +}; + +template <> +class CommonStringPieceTest : public ::testing::Test { + public: + static const string16 as_string(const char* input) { + return ASCIIToUTF16(input); + } + static const string16 as_string(const std::string& input) { + return ASCIIToUTF16(input); + } +}; + +typedef ::testing::Types SupportedStringTypes; + +TYPED_TEST_CASE(CommonStringPieceTest, SupportedStringTypes); + +TYPED_TEST(CommonStringPieceTest, CheckComparisonOperators) { +#define CMP_Y(op, x, y) \ + { \ + TypeParam lhs(TestFixture::as_string(x)); \ + TypeParam rhs(TestFixture::as_string(y)); \ + ASSERT_TRUE( (BasicStringPiece((lhs.c_str())) op \ + BasicStringPiece((rhs.c_str())))); \ + ASSERT_TRUE( (BasicStringPiece((lhs.c_str())).compare( \ + BasicStringPiece((rhs.c_str()))) op 0)); \ + } + +#define CMP_N(op, x, y) \ + { \ + TypeParam lhs(TestFixture::as_string(x)); \ + TypeParam rhs(TestFixture::as_string(y)); \ + ASSERT_FALSE( (BasicStringPiece((lhs.c_str())) op \ + BasicStringPiece((rhs.c_str())))); \ + ASSERT_FALSE( (BasicStringPiece((lhs.c_str())).compare( \ + BasicStringPiece((rhs.c_str()))) op 0)); \ + } + + CMP_Y(==, "", ""); + CMP_Y(==, "a", "a"); + CMP_Y(==, "aa", "aa"); + CMP_N(==, "a", ""); + CMP_N(==, "", "a"); + CMP_N(==, "a", "b"); + CMP_N(==, "a", "aa"); + CMP_N(==, "aa", "a"); + + CMP_N(!=, "", ""); + CMP_N(!=, "a", "a"); + CMP_N(!=, "aa", "aa"); + CMP_Y(!=, "a", ""); + CMP_Y(!=, "", "a"); + CMP_Y(!=, "a", "b"); + CMP_Y(!=, "a", "aa"); + CMP_Y(!=, "aa", "a"); + + CMP_Y(<, "a", "b"); + CMP_Y(<, "a", "aa"); + CMP_Y(<, "aa", "b"); + CMP_Y(<, "aa", "bb"); + CMP_N(<, "a", "a"); + CMP_N(<, "b", "a"); + CMP_N(<, "aa", "a"); + CMP_N(<, "b", "aa"); + CMP_N(<, "bb", "aa"); + + CMP_Y(<=, "a", "a"); + CMP_Y(<=, "a", "b"); + CMP_Y(<=, "a", "aa"); + CMP_Y(<=, "aa", "b"); + CMP_Y(<=, "aa", "bb"); + CMP_N(<=, "b", "a"); + CMP_N(<=, "aa", "a"); + CMP_N(<=, "b", "aa"); + CMP_N(<=, "bb", "aa"); + + CMP_N(>=, "a", "b"); + CMP_N(>=, "a", "aa"); + CMP_N(>=, "aa", "b"); + CMP_N(>=, "aa", "bb"); + CMP_Y(>=, "a", "a"); + CMP_Y(>=, "b", "a"); + CMP_Y(>=, "aa", "a"); + CMP_Y(>=, "b", "aa"); + CMP_Y(>=, "bb", "aa"); + + CMP_N(>, "a", "a"); + CMP_N(>, "a", "b"); + CMP_N(>, "a", "aa"); + CMP_N(>, "aa", "b"); + CMP_N(>, "aa", "bb"); + CMP_Y(>, "b", "a"); + CMP_Y(>, "aa", "a"); + CMP_Y(>, "b", "aa"); + CMP_Y(>, "bb", "aa"); + + std::string x; + for (int i = 0; i < 256; i++) { + x += 'a'; + std::string y = x; + CMP_Y(==, x, y); + for (int j = 0; j < i; j++) { + std::string z = x; + z[j] = 'b'; // Differs in position 'j' + CMP_N(==, x, z); + } + } + +#undef CMP_Y +#undef CMP_N +} + +TYPED_TEST(CommonStringPieceTest, CheckSTL) { + TypeParam alphabet(TestFixture::as_string("abcdefghijklmnopqrstuvwxyz")); + TypeParam abc(TestFixture::as_string("abc")); + TypeParam xyz(TestFixture::as_string("xyz")); + TypeParam foobar(TestFixture::as_string("foobar")); + + BasicStringPiece a(alphabet); + BasicStringPiece b(abc); + BasicStringPiece c(xyz); + BasicStringPiece d(foobar); + BasicStringPiece e; + TypeParam temp(TestFixture::as_string("123")); + temp += static_cast(0); + temp += TestFixture::as_string("456"); + BasicStringPiece f(temp); + + ASSERT_EQ(a[6], static_cast('g')); + ASSERT_EQ(b[0], static_cast('a')); + ASSERT_EQ(c[2], static_cast('z')); + ASSERT_EQ(f[3], static_cast('\0')); + ASSERT_EQ(f[5], static_cast('5')); + + ASSERT_EQ(*d.data(), static_cast('f')); + ASSERT_EQ(d.data()[5], static_cast('r')); + ASSERT_TRUE(e.data() == NULL); + + ASSERT_EQ(*a.begin(), static_cast('a')); + ASSERT_EQ(*(b.begin() + 2), static_cast('c')); + ASSERT_EQ(*(c.end() - 1), static_cast('z')); + + ASSERT_EQ(*a.rbegin(), static_cast('z')); + ASSERT_EQ(*(b.rbegin() + 2), + static_cast('a')); + ASSERT_EQ(*(c.rend() - 1), static_cast('x')); + ASSERT_TRUE(a.rbegin() + 26 == a.rend()); + + ASSERT_EQ(a.size(), 26U); + ASSERT_EQ(b.size(), 3U); + ASSERT_EQ(c.size(), 3U); + ASSERT_EQ(d.size(), 6U); + ASSERT_EQ(e.size(), 0U); + ASSERT_EQ(f.size(), 7U); + + ASSERT_TRUE(!d.empty()); + ASSERT_TRUE(d.begin() != d.end()); + ASSERT_TRUE(d.begin() + 6 == d.end()); + + ASSERT_TRUE(e.empty()); + ASSERT_TRUE(e.begin() == e.end()); + + d.clear(); + ASSERT_EQ(d.size(), 0U); + ASSERT_TRUE(d.empty()); + ASSERT_TRUE(d.data() == NULL); + ASSERT_TRUE(d.begin() == d.end()); + + ASSERT_GE(a.max_size(), a.capacity()); + ASSERT_GE(a.capacity(), a.size()); +} + +TYPED_TEST(CommonStringPieceTest, CheckFind) { + typedef BasicStringPiece Piece; + + TypeParam alphabet(TestFixture::as_string("abcdefghijklmnopqrstuvwxyz")); + TypeParam abc(TestFixture::as_string("abc")); + TypeParam xyz(TestFixture::as_string("xyz")); + TypeParam foobar(TestFixture::as_string("foobar")); + + BasicStringPiece a(alphabet); + BasicStringPiece b(abc); + BasicStringPiece c(xyz); + BasicStringPiece d(foobar); + + d.clear(); + Piece e; + TypeParam temp(TestFixture::as_string("123")); + temp.push_back('\0'); + temp += TestFixture::as_string("456"); + Piece f(temp); + + typename TypeParam::value_type buf[4] = { '%', '%', '%', '%' }; + ASSERT_EQ(a.copy(buf, 4), 4U); + ASSERT_EQ(buf[0], a[0]); + ASSERT_EQ(buf[1], a[1]); + ASSERT_EQ(buf[2], a[2]); + ASSERT_EQ(buf[3], a[3]); + ASSERT_EQ(a.copy(buf, 3, 7), 3U); + ASSERT_EQ(buf[0], a[7]); + ASSERT_EQ(buf[1], a[8]); + ASSERT_EQ(buf[2], a[9]); + ASSERT_EQ(buf[3], a[3]); + ASSERT_EQ(c.copy(buf, 99), 3U); + ASSERT_EQ(buf[0], c[0]); + ASSERT_EQ(buf[1], c[1]); + ASSERT_EQ(buf[2], c[2]); + ASSERT_EQ(buf[3], a[3]); + + ASSERT_EQ(Piece::npos, TypeParam::npos); + + ASSERT_EQ(a.find(b), 0U); + ASSERT_EQ(a.find(b, 1), Piece::npos); + ASSERT_EQ(a.find(c), 23U); + ASSERT_EQ(a.find(c, 9), 23U); + ASSERT_EQ(a.find(c, Piece::npos), Piece::npos); + ASSERT_EQ(b.find(c), Piece::npos); + ASSERT_EQ(b.find(c, Piece::npos), Piece::npos); + ASSERT_EQ(a.find(d), 0U); + ASSERT_EQ(a.find(e), 0U); + ASSERT_EQ(a.find(d, 12), 12U); + ASSERT_EQ(a.find(e, 17), 17U); + TypeParam not_found(TestFixture::as_string("xx not found bb")); + Piece g(not_found); + ASSERT_EQ(a.find(g), Piece::npos); + // empty string nonsense + ASSERT_EQ(d.find(b), Piece::npos); + ASSERT_EQ(e.find(b), Piece::npos); + ASSERT_EQ(d.find(b, 4), Piece::npos); + ASSERT_EQ(e.find(b, 7), Piece::npos); + + size_t empty_search_pos = TypeParam().find(TypeParam()); + ASSERT_EQ(d.find(d), empty_search_pos); + ASSERT_EQ(d.find(e), empty_search_pos); + ASSERT_EQ(e.find(d), empty_search_pos); + ASSERT_EQ(e.find(e), empty_search_pos); + ASSERT_EQ(d.find(d, 4), std::string().find(std::string(), 4)); + ASSERT_EQ(d.find(e, 4), std::string().find(std::string(), 4)); + ASSERT_EQ(e.find(d, 4), std::string().find(std::string(), 4)); + ASSERT_EQ(e.find(e, 4), std::string().find(std::string(), 4)); + + ASSERT_EQ(a.find('a'), 0U); + ASSERT_EQ(a.find('c'), 2U); + ASSERT_EQ(a.find('z'), 25U); + ASSERT_EQ(a.find('$'), Piece::npos); + ASSERT_EQ(a.find('\0'), Piece::npos); + ASSERT_EQ(f.find('\0'), 3U); + ASSERT_EQ(f.find('3'), 2U); + ASSERT_EQ(f.find('5'), 5U); + ASSERT_EQ(g.find('o'), 4U); + ASSERT_EQ(g.find('o', 4), 4U); + ASSERT_EQ(g.find('o', 5), 8U); + ASSERT_EQ(a.find('b', 5), Piece::npos); + // empty string nonsense + ASSERT_EQ(d.find('\0'), Piece::npos); + ASSERT_EQ(e.find('\0'), Piece::npos); + ASSERT_EQ(d.find('\0', 4), Piece::npos); + ASSERT_EQ(e.find('\0', 7), Piece::npos); + ASSERT_EQ(d.find('x'), Piece::npos); + ASSERT_EQ(e.find('x'), Piece::npos); + ASSERT_EQ(d.find('x', 4), Piece::npos); + ASSERT_EQ(e.find('x', 7), Piece::npos); + + ASSERT_EQ(a.rfind(b), 0U); + ASSERT_EQ(a.rfind(b, 1), 0U); + ASSERT_EQ(a.rfind(c), 23U); + ASSERT_EQ(a.rfind(c, 22U), Piece::npos); + ASSERT_EQ(a.rfind(c, 1U), Piece::npos); + ASSERT_EQ(a.rfind(c, 0U), Piece::npos); + ASSERT_EQ(b.rfind(c), Piece::npos); + ASSERT_EQ(b.rfind(c, 0U), Piece::npos); + ASSERT_EQ(a.rfind(d), static_cast(a.as_string().rfind(TypeParam()))); + ASSERT_EQ(a.rfind(e), a.as_string().rfind(TypeParam())); + ASSERT_EQ(a.rfind(d, 12), 12U); + ASSERT_EQ(a.rfind(e, 17), 17U); + ASSERT_EQ(a.rfind(g), Piece::npos); + ASSERT_EQ(d.rfind(b), Piece::npos); + ASSERT_EQ(e.rfind(b), Piece::npos); + ASSERT_EQ(d.rfind(b, 4), Piece::npos); + ASSERT_EQ(e.rfind(b, 7), Piece::npos); + // empty string nonsense + ASSERT_EQ(d.rfind(d, 4), std::string().rfind(std::string())); + ASSERT_EQ(e.rfind(d, 7), std::string().rfind(std::string())); + ASSERT_EQ(d.rfind(e, 4), std::string().rfind(std::string())); + ASSERT_EQ(e.rfind(e, 7), std::string().rfind(std::string())); + ASSERT_EQ(d.rfind(d), std::string().rfind(std::string())); + ASSERT_EQ(e.rfind(d), std::string().rfind(std::string())); + ASSERT_EQ(d.rfind(e), std::string().rfind(std::string())); + ASSERT_EQ(e.rfind(e), std::string().rfind(std::string())); + + ASSERT_EQ(g.rfind('o'), 8U); + ASSERT_EQ(g.rfind('q'), Piece::npos); + ASSERT_EQ(g.rfind('o', 8), 8U); + ASSERT_EQ(g.rfind('o', 7), 4U); + ASSERT_EQ(g.rfind('o', 3), Piece::npos); + ASSERT_EQ(f.rfind('\0'), 3U); + ASSERT_EQ(f.rfind('\0', 12), 3U); + ASSERT_EQ(f.rfind('3'), 2U); + ASSERT_EQ(f.rfind('5'), 5U); + // empty string nonsense + ASSERT_EQ(d.rfind('o'), Piece::npos); + ASSERT_EQ(e.rfind('o'), Piece::npos); + ASSERT_EQ(d.rfind('o', 4), Piece::npos); + ASSERT_EQ(e.rfind('o', 7), Piece::npos); + + TypeParam one_two_three_four(TestFixture::as_string("one,two:three;four")); + TypeParam comma_colon(TestFixture::as_string(",:")); + ASSERT_EQ(3U, Piece(one_two_three_four).find_first_of(comma_colon)); + ASSERT_EQ(a.find_first_of(b), 0U); + ASSERT_EQ(a.find_first_of(b, 0), 0U); + ASSERT_EQ(a.find_first_of(b, 1), 1U); + ASSERT_EQ(a.find_first_of(b, 2), 2U); + ASSERT_EQ(a.find_first_of(b, 3), Piece::npos); + ASSERT_EQ(a.find_first_of(c), 23U); + ASSERT_EQ(a.find_first_of(c, 23), 23U); + ASSERT_EQ(a.find_first_of(c, 24), 24U); + ASSERT_EQ(a.find_first_of(c, 25), 25U); + ASSERT_EQ(a.find_first_of(c, 26), Piece::npos); + ASSERT_EQ(g.find_first_of(b), 13U); + ASSERT_EQ(g.find_first_of(c), 0U); + ASSERT_EQ(a.find_first_of(f), Piece::npos); + ASSERT_EQ(f.find_first_of(a), Piece::npos); + // empty string nonsense + ASSERT_EQ(a.find_first_of(d), Piece::npos); + ASSERT_EQ(a.find_first_of(e), Piece::npos); + ASSERT_EQ(d.find_first_of(b), Piece::npos); + ASSERT_EQ(e.find_first_of(b), Piece::npos); + ASSERT_EQ(d.find_first_of(d), Piece::npos); + ASSERT_EQ(e.find_first_of(d), Piece::npos); + ASSERT_EQ(d.find_first_of(e), Piece::npos); + ASSERT_EQ(e.find_first_of(e), Piece::npos); + + ASSERT_EQ(a.find_first_not_of(b), 3U); + ASSERT_EQ(a.find_first_not_of(c), 0U); + ASSERT_EQ(b.find_first_not_of(a), Piece::npos); + ASSERT_EQ(c.find_first_not_of(a), Piece::npos); + ASSERT_EQ(f.find_first_not_of(a), 0U); + ASSERT_EQ(a.find_first_not_of(f), 0U); + ASSERT_EQ(a.find_first_not_of(d), 0U); + ASSERT_EQ(a.find_first_not_of(e), 0U); + // empty string nonsense + ASSERT_EQ(d.find_first_not_of(a), Piece::npos); + ASSERT_EQ(e.find_first_not_of(a), Piece::npos); + ASSERT_EQ(d.find_first_not_of(d), Piece::npos); + ASSERT_EQ(e.find_first_not_of(d), Piece::npos); + ASSERT_EQ(d.find_first_not_of(e), Piece::npos); + ASSERT_EQ(e.find_first_not_of(e), Piece::npos); + + TypeParam equals(TestFixture::as_string("====")); + Piece h(equals); + ASSERT_EQ(h.find_first_not_of('='), Piece::npos); + ASSERT_EQ(h.find_first_not_of('=', 3), Piece::npos); + ASSERT_EQ(h.find_first_not_of('\0'), 0U); + ASSERT_EQ(g.find_first_not_of('x'), 2U); + ASSERT_EQ(f.find_first_not_of('\0'), 0U); + ASSERT_EQ(f.find_first_not_of('\0', 3), 4U); + ASSERT_EQ(f.find_first_not_of('\0', 2), 2U); + // empty string nonsense + ASSERT_EQ(d.find_first_not_of('x'), Piece::npos); + ASSERT_EQ(e.find_first_not_of('x'), Piece::npos); + ASSERT_EQ(d.find_first_not_of('\0'), Piece::npos); + ASSERT_EQ(e.find_first_not_of('\0'), Piece::npos); + + // Piece g("xx not found bb"); + TypeParam fifty_six(TestFixture::as_string("56")); + Piece i(fifty_six); + ASSERT_EQ(h.find_last_of(a), Piece::npos); + ASSERT_EQ(g.find_last_of(a), g.size()-1); + ASSERT_EQ(a.find_last_of(b), 2U); + ASSERT_EQ(a.find_last_of(c), a.size()-1); + ASSERT_EQ(f.find_last_of(i), 6U); + ASSERT_EQ(a.find_last_of('a'), 0U); + ASSERT_EQ(a.find_last_of('b'), 1U); + ASSERT_EQ(a.find_last_of('z'), 25U); + ASSERT_EQ(a.find_last_of('a', 5), 0U); + ASSERT_EQ(a.find_last_of('b', 5), 1U); + ASSERT_EQ(a.find_last_of('b', 0), Piece::npos); + ASSERT_EQ(a.find_last_of('z', 25), 25U); + ASSERT_EQ(a.find_last_of('z', 24), Piece::npos); + ASSERT_EQ(f.find_last_of(i, 5), 5U); + ASSERT_EQ(f.find_last_of(i, 6), 6U); + ASSERT_EQ(f.find_last_of(a, 4), Piece::npos); + // empty string nonsense + ASSERT_EQ(f.find_last_of(d), Piece::npos); + ASSERT_EQ(f.find_last_of(e), Piece::npos); + ASSERT_EQ(f.find_last_of(d, 4), Piece::npos); + ASSERT_EQ(f.find_last_of(e, 4), Piece::npos); + ASSERT_EQ(d.find_last_of(d), Piece::npos); + ASSERT_EQ(d.find_last_of(e), Piece::npos); + ASSERT_EQ(e.find_last_of(d), Piece::npos); + ASSERT_EQ(e.find_last_of(e), Piece::npos); + ASSERT_EQ(d.find_last_of(f), Piece::npos); + ASSERT_EQ(e.find_last_of(f), Piece::npos); + ASSERT_EQ(d.find_last_of(d, 4), Piece::npos); + ASSERT_EQ(d.find_last_of(e, 4), Piece::npos); + ASSERT_EQ(e.find_last_of(d, 4), Piece::npos); + ASSERT_EQ(e.find_last_of(e, 4), Piece::npos); + ASSERT_EQ(d.find_last_of(f, 4), Piece::npos); + ASSERT_EQ(e.find_last_of(f, 4), Piece::npos); + + ASSERT_EQ(a.find_last_not_of(b), a.size()-1); + ASSERT_EQ(a.find_last_not_of(c), 22U); + ASSERT_EQ(b.find_last_not_of(a), Piece::npos); + ASSERT_EQ(b.find_last_not_of(b), Piece::npos); + ASSERT_EQ(f.find_last_not_of(i), 4U); + ASSERT_EQ(a.find_last_not_of(c, 24), 22U); + ASSERT_EQ(a.find_last_not_of(b, 3), 3U); + ASSERT_EQ(a.find_last_not_of(b, 2), Piece::npos); + // empty string nonsense + ASSERT_EQ(f.find_last_not_of(d), f.size()-1); + ASSERT_EQ(f.find_last_not_of(e), f.size()-1); + ASSERT_EQ(f.find_last_not_of(d, 4), 4U); + ASSERT_EQ(f.find_last_not_of(e, 4), 4U); + ASSERT_EQ(d.find_last_not_of(d), Piece::npos); + ASSERT_EQ(d.find_last_not_of(e), Piece::npos); + ASSERT_EQ(e.find_last_not_of(d), Piece::npos); + ASSERT_EQ(e.find_last_not_of(e), Piece::npos); + ASSERT_EQ(d.find_last_not_of(f), Piece::npos); + ASSERT_EQ(e.find_last_not_of(f), Piece::npos); + ASSERT_EQ(d.find_last_not_of(d, 4), Piece::npos); + ASSERT_EQ(d.find_last_not_of(e, 4), Piece::npos); + ASSERT_EQ(e.find_last_not_of(d, 4), Piece::npos); + ASSERT_EQ(e.find_last_not_of(e, 4), Piece::npos); + ASSERT_EQ(d.find_last_not_of(f, 4), Piece::npos); + ASSERT_EQ(e.find_last_not_of(f, 4), Piece::npos); + + ASSERT_EQ(h.find_last_not_of('x'), h.size() - 1); + ASSERT_EQ(h.find_last_not_of('='), Piece::npos); + ASSERT_EQ(b.find_last_not_of('c'), 1U); + ASSERT_EQ(h.find_last_not_of('x', 2), 2U); + ASSERT_EQ(h.find_last_not_of('=', 2), Piece::npos); + ASSERT_EQ(b.find_last_not_of('b', 1), 0U); + // empty string nonsense + ASSERT_EQ(d.find_last_not_of('x'), Piece::npos); + ASSERT_EQ(e.find_last_not_of('x'), Piece::npos); + ASSERT_EQ(d.find_last_not_of('\0'), Piece::npos); + ASSERT_EQ(e.find_last_not_of('\0'), Piece::npos); + + ASSERT_EQ(a.substr(0, 3), b); + ASSERT_EQ(a.substr(23), c); + ASSERT_EQ(a.substr(23, 3), c); + ASSERT_EQ(a.substr(23, 99), c); + ASSERT_EQ(a.substr(0), a); + ASSERT_EQ(a.substr(3, 2), TestFixture::as_string("de")); + // empty string nonsense + ASSERT_EQ(a.substr(99, 2), e); + ASSERT_EQ(d.substr(99), e); + ASSERT_EQ(d.substr(0, 99), e); + ASSERT_EQ(d.substr(99, 99), e); +} + +TYPED_TEST(CommonStringPieceTest, CheckCustom) { + TypeParam foobar(TestFixture::as_string("foobar")); + BasicStringPiece a(foobar); + TypeParam s1(TestFixture::as_string("123")); + s1 += static_cast('\0'); + s1 += TestFixture::as_string("456"); + BasicStringPiece b(s1); + BasicStringPiece e; + TypeParam s2; + + // remove_prefix + BasicStringPiece c(a); + c.remove_prefix(3); + ASSERT_EQ(c, TestFixture::as_string("bar")); + c = a; + c.remove_prefix(0); + ASSERT_EQ(c, a); + c.remove_prefix(c.size()); + ASSERT_EQ(c, e); + + // remove_suffix + c = a; + c.remove_suffix(3); + ASSERT_EQ(c, TestFixture::as_string("foo")); + c = a; + c.remove_suffix(0); + ASSERT_EQ(c, a); + c.remove_suffix(c.size()); + ASSERT_EQ(c, e); + + // set + c.set(foobar.c_str()); + ASSERT_EQ(c, a); + c.set(foobar.c_str(), 6); + ASSERT_EQ(c, a); + c.set(foobar.c_str(), 0); + ASSERT_EQ(c, e); + c.set(foobar.c_str(), 7); // Note, has an embedded NULL + ASSERT_NE(c, a); + + // as_string + TypeParam s3(a.as_string().c_str(), 7); // Note, has an embedded NULL + ASSERT_TRUE(c == s3); + TypeParam s4(e.as_string()); + ASSERT_TRUE(s4.empty()); +} + +TEST(StringPieceTest, CheckCustom) { + StringPiece a("foobar"); + std::string s1("123"); + s1 += '\0'; + s1 += "456"; + StringPiece b(s1); + StringPiece e; + std::string s2; + + // CopyToString + a.CopyToString(&s2); + ASSERT_EQ(s2.size(), 6U); + ASSERT_EQ(s2, "foobar"); + b.CopyToString(&s2); + ASSERT_EQ(s2.size(), 7U); + ASSERT_EQ(s1, s2); + e.CopyToString(&s2); + ASSERT_TRUE(s2.empty()); + + // AppendToString + s2.erase(); + a.AppendToString(&s2); + ASSERT_EQ(s2.size(), 6U); + ASSERT_EQ(s2, "foobar"); + a.AppendToString(&s2); + ASSERT_EQ(s2.size(), 12U); + ASSERT_EQ(s2, "foobarfoobar"); + + // starts_with + ASSERT_TRUE(a.starts_with(a)); + ASSERT_TRUE(a.starts_with("foo")); + ASSERT_TRUE(a.starts_with(e)); + ASSERT_TRUE(b.starts_with(s1)); + ASSERT_TRUE(b.starts_with(b)); + ASSERT_TRUE(b.starts_with(e)); + ASSERT_TRUE(e.starts_with("")); + ASSERT_TRUE(!a.starts_with(b)); + ASSERT_TRUE(!b.starts_with(a)); + ASSERT_TRUE(!e.starts_with(a)); + + // ends with + ASSERT_TRUE(a.ends_with(a)); + ASSERT_TRUE(a.ends_with("bar")); + ASSERT_TRUE(a.ends_with(e)); + ASSERT_TRUE(b.ends_with(s1)); + ASSERT_TRUE(b.ends_with(b)); + ASSERT_TRUE(b.ends_with(e)); + ASSERT_TRUE(e.ends_with("")); + ASSERT_TRUE(!a.ends_with(b)); + ASSERT_TRUE(!b.ends_with(a)); + ASSERT_TRUE(!e.ends_with(a)); + + StringPiece c; + c.set("foobar", 6); + ASSERT_EQ(c, a); + c.set("foobar", 0); + ASSERT_EQ(c, e); + c.set("foobar", 7); + ASSERT_NE(c, a); +} + +TYPED_TEST(CommonStringPieceTest, CheckNULL) { + // we used to crash here, but now we don't. + BasicStringPiece s(NULL); + ASSERT_EQ(s.data(), (const typename TypeParam::value_type*)NULL); + ASSERT_EQ(s.size(), 0U); + + s.set(NULL); + ASSERT_EQ(s.data(), (const typename TypeParam::value_type*)NULL); + ASSERT_EQ(s.size(), 0U); + + TypeParam str = s.as_string(); + ASSERT_EQ(str.length(), 0U); + ASSERT_EQ(str, TypeParam()); +} + +TYPED_TEST(CommonStringPieceTest, CheckComparisons2) { + TypeParam alphabet(TestFixture::as_string("abcdefghijklmnopqrstuvwxyz")); + TypeParam alphabet_z(TestFixture::as_string("abcdefghijklmnopqrstuvwxyzz")); + TypeParam alphabet_y(TestFixture::as_string("abcdefghijklmnopqrstuvwxyy")); + BasicStringPiece abc(alphabet); + + // check comparison operations on strings longer than 4 bytes. + ASSERT_TRUE(abc == BasicStringPiece(alphabet)); + ASSERT_TRUE(abc.compare(BasicStringPiece(alphabet)) == 0); + + ASSERT_TRUE(abc < BasicStringPiece(alphabet_z)); + ASSERT_TRUE(abc.compare(BasicStringPiece(alphabet_z)) < 0); + + ASSERT_TRUE(abc > BasicStringPiece(alphabet_y)); + ASSERT_TRUE(abc.compare(BasicStringPiece(alphabet_y)) > 0); +} + +// Test operations only supported by std::string version. +TEST(StringPieceTest, CheckComparisons2) { + StringPiece abc("abcdefghijklmnopqrstuvwxyz"); + + // starts_with + ASSERT_TRUE(abc.starts_with(abc)); + ASSERT_TRUE(abc.starts_with("abcdefghijklm")); + ASSERT_TRUE(!abc.starts_with("abcdefguvwxyz")); + + // ends_with + ASSERT_TRUE(abc.ends_with(abc)); + ASSERT_TRUE(!abc.ends_with("abcdefguvwxyz")); + ASSERT_TRUE(abc.ends_with("nopqrstuvwxyz")); +} + +TYPED_TEST(CommonStringPieceTest, StringCompareNotAmbiguous) { + ASSERT_TRUE(TestFixture::as_string("hello").c_str() == + TestFixture::as_string("hello")); + ASSERT_TRUE(TestFixture::as_string("hello").c_str() < + TestFixture::as_string("world")); +} + +TYPED_TEST(CommonStringPieceTest, HeterogenousStringPieceEquals) { + TypeParam hello(TestFixture::as_string("hello")); + + ASSERT_TRUE(BasicStringPiece(hello) == hello); + ASSERT_TRUE(hello.c_str() == BasicStringPiece(hello)); +} + +// string16-specific stuff +TEST(StringPiece16Test, CheckSTL) { + // Check some non-ascii characters. + string16 fifth(ASCIIToUTF16("123")); + fifth.push_back(0x0000); + fifth.push_back(0xd8c5); + fifth.push_back(0xdffe); + StringPiece16 f(fifth); + + ASSERT_EQ(f[3], '\0'); + ASSERT_EQ(f[5], static_cast(0xdffe)); + + ASSERT_EQ(f.size(), 6U); +} + + + +TEST(StringPiece16Test, CheckConversion) { + // Make sure that we can convert from UTF8 to UTF16 and back. We use a two + // byte character (G clef) to test this. + ASSERT_EQ( + UTF16ToUTF8( + StringPiece16(UTF8ToUTF16("\xf0\x9d\x84\x9e")).as_string()), + "\xf0\x9d\x84\x9e"); +} + +TYPED_TEST(CommonStringPieceTest, CheckConstructors) { + TypeParam str(TestFixture::as_string("hello world")); + TypeParam empty; + + ASSERT_TRUE(str == BasicStringPiece(str)); + ASSERT_TRUE(str == BasicStringPiece(str.c_str())); + ASSERT_TRUE(TestFixture::as_string("hello") == + BasicStringPiece(str.c_str(), 5)); + ASSERT_TRUE(empty == BasicStringPiece(str.c_str(), + static_cast::size_type>(0))); + ASSERT_TRUE(empty == BasicStringPiece(NULL)); + ASSERT_TRUE(empty == BasicStringPiece(NULL, + static_cast::size_type>(0))); + ASSERT_TRUE(empty == BasicStringPiece()); + ASSERT_TRUE(str == BasicStringPiece(str.begin(), str.end())); + ASSERT_TRUE(empty == BasicStringPiece(str.begin(), str.begin())); + ASSERT_TRUE(empty == BasicStringPiece(empty)); + ASSERT_TRUE(empty == BasicStringPiece(empty.begin(), empty.end())); +} + +} // namespace butil diff --git a/test/string_printf_unittest.cpp b/test/string_printf_unittest.cpp new file mode 100644 index 0000000..fd9db19 --- /dev/null +++ b/test/string_printf_unittest.cpp @@ -0,0 +1,54 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/string_printf.h" + +namespace { + +class BaiduStringPrintfTest : public ::testing::Test{ +protected: + BaiduStringPrintfTest(){ + }; + virtual ~BaiduStringPrintfTest(){}; + virtual void SetUp() { + }; + virtual void TearDown() { + }; +}; + +TEST_F(BaiduStringPrintfTest, sanity) { + + { + std::string sth = butil::string_printf("hello %d 124 %s", 1, "world"); + ASSERT_EQ("hello 1 124 world", sth); + + ASSERT_EQ("hello 1 124 world", butil::string_printf(sth.size(), "hello %d 124 %s", 1, "world")); + ASSERT_EQ("hello 1 124 world", butil::string_printf(sth.size() - 1, "hello %d 124 %s", 1, "world")); + ASSERT_EQ("hello 1 124 world", butil::string_printf(sth.size() + 1, "hello %d 124 %s", 1, "world")); + + } + { + std::string sth; + ASSERT_EQ(0, butil::string_printf(&sth, "boolean %d", 1)); + ASSERT_EQ("boolean 1", sth); + ASSERT_EQ(0, butil::string_appendf(&sth, "too simple")); + ASSERT_EQ("boolean 1too simple", sth); + } +} + +} diff --git a/test/string_split_unittest.cc b/test/string_split_unittest.cc new file mode 100644 index 0000000..75b143a --- /dev/null +++ b/test/string_split_unittest.cc @@ -0,0 +1,398 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/strings/string_split.h" + +#include "butil/strings/utf_string_conversions.h" +#include + +namespace butil { + +namespace { + +void AssertElements(std::vector& result, + const char* const expected_data[], + size_t data_size) { + ASSERT_EQ(data_size, result.size()); + for (size_t i = 0; i < data_size; ++i) { + ASSERT_STREQ(expected_data[i], result[i].c_str()); + } +} + +#if !defined(WCHAR_T_IS_UTF16) +// Overload SplitString with a wide-char version to make it easier to +// test the string16 version with wide character literals. +void SplitString(const std::wstring& str, + wchar_t c, + std::vector* result) { + std::vector result16; + SplitString(WideToUTF16(str), c, &result16); + for (size_t i = 0; i < result16.size(); ++i) + result->push_back(UTF16ToWide(result16[i])); +} +#endif + +} // anonymous namespace + +class SplitStringIntoKeyValuePairsTest : public testing::Test { + protected: + std::vector > kv_pairs; +}; + +TEST_F(SplitStringIntoKeyValuePairsTest, EmptyString) { + EXPECT_TRUE(SplitStringIntoKeyValuePairs(std::string(), + ':', // Key-value delimiter + ',', // Key-value pair delimiter + &kv_pairs)); + EXPECT_TRUE(kv_pairs.empty()); +} + +TEST_F(SplitStringIntoKeyValuePairsTest, MissingKeyValueDelimiter) { + EXPECT_FALSE(SplitStringIntoKeyValuePairs("key1,key2:value2", + ':', // Key-value delimiter + ',', // Key-value pair delimiter + &kv_pairs)); + ASSERT_EQ(2U, kv_pairs.size()); + EXPECT_TRUE(kv_pairs[0].first.empty()); + EXPECT_TRUE(kv_pairs[0].second.empty()); + EXPECT_EQ("key2", kv_pairs[1].first); + EXPECT_EQ("value2", kv_pairs[1].second); +} + +TEST_F(SplitStringIntoKeyValuePairsTest, EmptyKeyWithKeyValueDelimiter) { + EXPECT_TRUE(SplitStringIntoKeyValuePairs(":value1,key2:value2", + ':', // Key-value delimiter + ',', // Key-value pair delimiter + &kv_pairs)); + ASSERT_EQ(2U, kv_pairs.size()); + EXPECT_TRUE(kv_pairs[0].first.empty()); + EXPECT_EQ("value1", kv_pairs[0].second); + EXPECT_EQ("key2", kv_pairs[1].first); + EXPECT_EQ("value2", kv_pairs[1].second); +} + +TEST_F(SplitStringIntoKeyValuePairsTest, TrailingAndLeadingPairDelimiter) { + EXPECT_TRUE(SplitStringIntoKeyValuePairs(",key1:value1,key2:value2,", + ':', // Key-value delimiter + ',', // Key-value pair delimiter + &kv_pairs)); + ASSERT_EQ(2U, kv_pairs.size()); + EXPECT_EQ("key1", kv_pairs[0].first); + EXPECT_EQ("value1", kv_pairs[0].second); + EXPECT_EQ("key2", kv_pairs[1].first); + EXPECT_EQ("value2", kv_pairs[1].second); +} + +TEST_F(SplitStringIntoKeyValuePairsTest, EmptyPair) { + EXPECT_TRUE(SplitStringIntoKeyValuePairs("key1:value1,,key3:value3", + ':', // Key-value delimiter + ',', // Key-value pair delimiter + &kv_pairs)); + ASSERT_EQ(2U, kv_pairs.size()); + EXPECT_EQ("key1", kv_pairs[0].first); + EXPECT_EQ("value1", kv_pairs[0].second); + EXPECT_EQ("key3", kv_pairs[1].first); + EXPECT_EQ("value3", kv_pairs[1].second); +} + +TEST_F(SplitStringIntoKeyValuePairsTest, EmptyValue) { + EXPECT_FALSE(SplitStringIntoKeyValuePairs("key1:,key2:value2", + ':', // Key-value delimiter + ',', // Key-value pair delimiter + &kv_pairs)); + ASSERT_EQ(2U, kv_pairs.size()); + EXPECT_EQ("key1", kv_pairs[0].first); + EXPECT_EQ("", kv_pairs[0].second); + EXPECT_EQ("key2", kv_pairs[1].first); + EXPECT_EQ("value2", kv_pairs[1].second); +} + +TEST_F(SplitStringIntoKeyValuePairsTest, UntrimmedWhitespace) { + EXPECT_TRUE(SplitStringIntoKeyValuePairs("key1 : value1", + ':', // Key-value delimiter + ',', // Key-value pair delimiter + &kv_pairs)); + ASSERT_EQ(1U, kv_pairs.size()); + EXPECT_EQ("key1 ", kv_pairs[0].first); + EXPECT_EQ(" value1", kv_pairs[0].second); +} + +TEST_F(SplitStringIntoKeyValuePairsTest, TrimmedWhitespace) { + EXPECT_TRUE(SplitStringIntoKeyValuePairs("key1:value1 , key2:value2", + ':', // Key-value delimiter + ',', // Key-value pair delimiter + &kv_pairs)); + ASSERT_EQ(2U, kv_pairs.size()); + EXPECT_EQ("key1", kv_pairs[0].first); + EXPECT_EQ("value1", kv_pairs[0].second); + EXPECT_EQ("key2", kv_pairs[1].first); + EXPECT_EQ("value2", kv_pairs[1].second); +} + +TEST_F(SplitStringIntoKeyValuePairsTest, MultipleKeyValueDelimiters) { + EXPECT_TRUE(SplitStringIntoKeyValuePairs("key1:::value1,key2:value2", + ':', // Key-value delimiter + ',', // Key-value pair delimiter + &kv_pairs)); + ASSERT_EQ(2U, kv_pairs.size()); + EXPECT_EQ("key1", kv_pairs[0].first); + EXPECT_EQ("value1", kv_pairs[0].second); + EXPECT_EQ("key2", kv_pairs[1].first); + EXPECT_EQ("value2", kv_pairs[1].second); +} + +TEST_F(SplitStringIntoKeyValuePairsTest, OnlySplitAtGivenSeparator) { + std::string a("a ?!@#$%^&*()_+:/{}\\\t\nb"); + EXPECT_TRUE(SplitStringIntoKeyValuePairs(a + "X" + a + "Y" + a + "X" + a, + 'X', // Key-value delimiter + 'Y', // Key-value pair delimiter + &kv_pairs)); + ASSERT_EQ(2U, kv_pairs.size()); + EXPECT_EQ(a, kv_pairs[0].first); + EXPECT_EQ(a, kv_pairs[0].second); + EXPECT_EQ(a, kv_pairs[1].first); + EXPECT_EQ(a, kv_pairs[1].second); +} + + +TEST_F(SplitStringIntoKeyValuePairsTest, DelimiterInValue) { + EXPECT_TRUE(SplitStringIntoKeyValuePairs("key1:va:ue1,key2:value2", + ':', // Key-value delimiter + ',', // Key-value pair delimiter + &kv_pairs)); + ASSERT_EQ(2U, kv_pairs.size()); + EXPECT_EQ("key1", kv_pairs[0].first); + EXPECT_EQ("va:ue1", kv_pairs[0].second); + EXPECT_EQ("key2", kv_pairs[1].first); + EXPECT_EQ("value2", kv_pairs[1].second); +} + +TEST(SplitStringUsingSubstrTest, EmptyString) { + std::vector results; + SplitStringUsingSubstr(std::string(), "DELIMITER", &results); + const char* const expected[] = { "" }; + AssertElements(results, expected, arraysize(expected)); +} + +TEST(StringUtilTest, SplitString) { + std::vector r; + + SplitString(std::wstring(), L',', &r); + EXPECT_EQ(0U, r.size()); + r.clear(); + + SplitString(L"a,b,c", L',', &r); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], L"a"); + EXPECT_EQ(r[1], L"b"); + EXPECT_EQ(r[2], L"c"); + r.clear(); + + SplitString(L"a, b, c", L',', &r); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], L"a"); + EXPECT_EQ(r[1], L"b"); + EXPECT_EQ(r[2], L"c"); + r.clear(); + + SplitString(L"a,,c", L',', &r); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], L"a"); + EXPECT_EQ(r[1], L""); + EXPECT_EQ(r[2], L"c"); + r.clear(); + + SplitString(L" ", L'*', &r); + EXPECT_EQ(0U, r.size()); + r.clear(); + + SplitString(L"foo", L'*', &r); + ASSERT_EQ(1U, r.size()); + EXPECT_EQ(r[0], L"foo"); + r.clear(); + + SplitString(L"foo ,", L',', &r); + ASSERT_EQ(2U, r.size()); + EXPECT_EQ(r[0], L"foo"); + EXPECT_EQ(r[1], L""); + r.clear(); + + SplitString(L",", L',', &r); + ASSERT_EQ(2U, r.size()); + EXPECT_EQ(r[0], L""); + EXPECT_EQ(r[1], L""); + r.clear(); + + SplitString(L"\t\ta\t", L'\t', &r); + ASSERT_EQ(4U, r.size()); + EXPECT_EQ(r[0], L""); + EXPECT_EQ(r[1], L""); + EXPECT_EQ(r[2], L"a"); + EXPECT_EQ(r[3], L""); + r.clear(); + + SplitString(L"\ta\t\nb\tcc", L'\n', &r); + ASSERT_EQ(2U, r.size()); + EXPECT_EQ(r[0], L"a"); + EXPECT_EQ(r[1], L"b\tcc"); + r.clear(); +} + +TEST(StringUtilTest, SplitStringStringPiece) { + std::vector r; + + SplitString(butil::StringPiece(), ',', &r); + EXPECT_EQ(0U, r.size()); + r.clear(); + + SplitString(butil::StringPiece("a,b,c"), ',', &r); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], "a"); + EXPECT_EQ(r[1], "b"); + EXPECT_EQ(r[2], "c"); + r.clear(); + + SplitString(butil::StringPiece("a, b, c"), ',', &r); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], "a"); + EXPECT_EQ(r[1], "b"); + EXPECT_EQ(r[2], "c"); + r.clear(); + + SplitString(butil::StringPiece("a,,c"), ',', &r); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], "a"); + EXPECT_EQ(r[1], ""); + EXPECT_EQ(r[2], "c"); + r.clear(); + + SplitString(butil::StringPiece(" "), '*', &r); + EXPECT_EQ(0U, r.size()); + r.clear(); + + SplitString(butil::StringPiece("foo"), '*', &r); + ASSERT_EQ(1U, r.size()); + EXPECT_EQ(r[0], "foo"); + r.clear(); + + SplitString(butil::StringPiece("foo ,"), ',', &r); + ASSERT_EQ(2U, r.size()); + EXPECT_EQ(r[0], "foo"); + EXPECT_EQ(r[1], ""); + r.clear(); + + SplitString(butil::StringPiece(","), ',', &r); + ASSERT_EQ(2U, r.size()); + EXPECT_EQ(r[0], ""); + EXPECT_EQ(r[1], ""); + r.clear(); + + SplitString(butil::StringPiece("\t\ta\t"), '\t', &r); + ASSERT_EQ(4U, r.size()); + EXPECT_EQ(r[0], ""); + EXPECT_EQ(r[1], ""); + EXPECT_EQ(r[2], "a"); + EXPECT_EQ(r[3], ""); + r.clear(); + + SplitString(butil::StringPiece("\ta\t\nb\tcc"), '\n', &r); + ASSERT_EQ(2U, r.size()); + EXPECT_EQ(r[0], "a"); + EXPECT_EQ(r[1], "b\tcc"); + r.clear(); +} + +TEST(SplitStringUsingSubstrTest, StringWithNoDelimiter) { + std::vector results; + SplitStringUsingSubstr("alongwordwithnodelimiter", "DELIMITER", &results); + const char* const expected[] = { "alongwordwithnodelimiter" }; + AssertElements(results, expected, arraysize(expected)); +} + +TEST(SplitStringUsingSubstrTest, LeadingDelimitersSkipped) { + std::vector results; + SplitStringUsingSubstr( + "DELIMITERDELIMITERDELIMITERoneDELIMITERtwoDELIMITERthree", + "DELIMITER", + &results); + const char* const expected[] = { "", "", "", "one", "two", "three" }; + AssertElements(results, expected, arraysize(expected)); +} + +TEST(SplitStringUsingSubstrTest, ConsecutiveDelimitersSkipped) { + std::vector results; + SplitStringUsingSubstr( + "unoDELIMITERDELIMITERDELIMITERdosDELIMITERtresDELIMITERDELIMITERcuatro", + "DELIMITER", + &results); + const char* const expected[] = { "uno", "", "", "dos", "tres", "", "cuatro" }; + AssertElements(results, expected, arraysize(expected)); + +} + +TEST(SplitStringUsingSubstrTest, TrailingDelimitersSkipped) { + std::vector results; + SplitStringUsingSubstr( + "unDELIMITERdeuxDELIMITERtroisDELIMITERquatreDELIMITERDELIMITERDELIMITER", + "DELIMITER", + &results); + const char* const expected[] = { "un", "deux", "trois", "quatre", "", "", "" }; + AssertElements(results, expected, arraysize(expected)); +} + +TEST(StringSplitTest, StringSplitDontTrim) { + std::vector r; + + SplitStringDontTrim(" ", '*', &r); + ASSERT_EQ(1U, r.size()); + EXPECT_EQ(r[0], " "); + + SplitStringDontTrim("\t \ta\t ", '\t', &r); + ASSERT_EQ(4U, r.size()); + EXPECT_EQ(r[0], ""); + EXPECT_EQ(r[1], " "); + EXPECT_EQ(r[2], "a"); + EXPECT_EQ(r[3], " "); + + SplitStringDontTrim("\ta\t\nb\tcc", '\n', &r); + ASSERT_EQ(2U, r.size()); + EXPECT_EQ(r[0], "\ta\t"); + EXPECT_EQ(r[1], "b\tcc"); +} + +TEST(StringSplitTest, SplitStringAlongWhitespace) { + struct TestData { + const char* input; + const size_t expected_result_count; + const char* output1; + const char* output2; + } data[] = { + { "a", 1, "a", "" }, + { " ", 0, "", "" }, + { " a", 1, "a", "" }, + { " ab ", 1, "ab", "" }, + { " ab c", 2, "ab", "c" }, + { " ab c ", 2, "ab", "c" }, + { " ab cd", 2, "ab", "cd" }, + { " ab cd ", 2, "ab", "cd" }, + { " \ta\t", 1, "a", "" }, + { " b\ta\t", 2, "b", "a" }, + { " b\tat", 2, "b", "at" }, + { "b\tat", 2, "b", "at" }, + { "b\t at", 2, "b", "at" }, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) { + std::vector results; + SplitStringAlongWhitespace(data[i].input, &results); + ASSERT_EQ(data[i].expected_result_count, results.size()); + if (data[i].expected_result_count > 0) { + ASSERT_EQ(data[i].output1, results[0]); + } + if (data[i].expected_result_count > 1) { + ASSERT_EQ(data[i].output2, results[1]); + } + } +} + +} // namespace butil diff --git a/test/string_splitter_unittest.cpp b/test/string_splitter_unittest.cpp new file mode 100644 index 0000000..c3b66a2 --- /dev/null +++ b/test/string_splitter_unittest.cpp @@ -0,0 +1,451 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/string_splitter.h" +#include + +namespace { +class StringSplitterTest : public ::testing::Test{ +protected: + StringSplitterTest(){}; + virtual ~StringSplitterTest(){}; + virtual void SetUp() { + srand (time(0)); + }; + virtual void TearDown() { + }; +}; + + +TEST_F(StringSplitterTest, sanity) { + const char* str = "hello there! man "; + butil::StringSplitter ss(str, ' '); + // "hello" + ASSERT_TRUE(ss != NULL); + ASSERT_EQ(5ul, ss.length()); + ASSERT_EQ(ss.field(), str); + + // "there!" + ++ss; + ASSERT_NE(ss, (void*)NULL); + ASSERT_EQ(6ul, ss.length()); + ASSERT_EQ(ss.field(), str+6); + + // "man" + ++ss; + ASSERT_TRUE(ss); + ASSERT_EQ(3ul, ss.length()); + ASSERT_EQ(ss.field(), str+15); + + ++ss; + ASSERT_FALSE(ss); + ASSERT_EQ(0ul, ss.length()); + ASSERT_EQ(ss.field(), str + strlen(str)); + + // consecutive separators are treated as zero-length field inside + butil::StringSplitter ss2(str, ' ', butil::ALLOW_EMPTY_FIELD); + + // "hello" + ASSERT_TRUE(ss2); + ASSERT_EQ(5ul, ss2.length()); + ASSERT_FALSE(strncmp(ss2.field(), "hello", ss2.length())); + + // "there!" + ++ss2; + ASSERT_TRUE(ss2); + ASSERT_EQ(6ul, ss2.length()); + ASSERT_FALSE(strncmp(ss2.field(), "there!", ss2.length())); + + // "" + ++ss2; + ASSERT_TRUE(ss2); + ASSERT_EQ(0ul, ss2.length()); + ASSERT_EQ(ss2.field(), str+13); + + // "" + ++ss2; + ASSERT_TRUE(ss2); + ASSERT_EQ(0ul, ss2.length()); + ASSERT_EQ(ss2.field(), str+14); + + // "man" + ++ss2; + ASSERT_TRUE(ss2); + ASSERT_EQ(3ul, ss2.length()); + ASSERT_EQ(ss2.field(), str+15); + + ++ss2; + ASSERT_FALSE(ss2); + ASSERT_EQ(0ul, ss2.length()); + ASSERT_EQ(ss2.field(), str+19); +} + + +TEST_F(StringSplitterTest, single_word) +{ + const char* str = "apple"; + butil::StringSplitter ss(str, ' '); + // "apple" + ASSERT_TRUE(ss); + ASSERT_EQ(5ul, ss.length()); + ASSERT_EQ(ss.field(), str); + + ++ss; + ASSERT_FALSE(ss); + ASSERT_EQ(0ul, ss.length()); + ASSERT_EQ(ss.field(), str+5); +} + +TEST_F(StringSplitterTest, starting_with_separator) { + const char* str = " apple"; + butil::StringSplitter ss(str, ' '); + // "apple" + ASSERT_TRUE(ss); + ASSERT_EQ(5ul, ss.length()); + ASSERT_FALSE(strncmp(ss.field(), "apple", ss.length())); + + ++ss; + ASSERT_FALSE(ss); + ASSERT_EQ(0ul, ss.length()); + ASSERT_EQ(ss.field(), str + strlen(str)); + + butil::StringSplitter ss2(str, ' ', butil::ALLOW_EMPTY_FIELD); + // "" + ASSERT_TRUE(ss2); + ASSERT_EQ(0ul, ss2.length()); + ASSERT_EQ(ss2.field(), str); + + // "" + ++ss2; + ASSERT_TRUE(ss2); + ASSERT_EQ(0ul, ss2.length()); + ASSERT_EQ(ss2.field(), str+1); + + // "apple" + ++ss2; + ASSERT_TRUE(ss2); + ASSERT_EQ(5ul, ss2.length()); + ASSERT_FALSE(strncmp(ss2.field(), "apple", ss2.length())); + + ++ss2; + ASSERT_FALSE(ss2); + ASSERT_EQ(0ul, ss2.length()); + ASSERT_EQ(ss2.field(), str + strlen(str)); +} + +TEST_F(StringSplitterTest, site_id_as_example) { + const char* str = "|123|12||1|21|4321"; + butil::StringSplitter ss(str, '|'); + ASSERT_TRUE(ss); + ASSERT_EQ(3ul, ss.length()); + ASSERT_FALSE(strncmp(ss.field(), "123", ss.length())); + + ss++; + ASSERT_TRUE(ss); + ASSERT_EQ(2ul, ss.length()); + ASSERT_FALSE(strncmp(ss.field(), "12", ss.length())); + + ss++; + ASSERT_TRUE(ss); + ASSERT_EQ(1ul, ss.length()); + ASSERT_FALSE(strncmp(ss.field(), "1", ss.length())); + + ss++; + ASSERT_TRUE(ss); + ASSERT_EQ(2ul, ss.length()); + ASSERT_FALSE(strncmp(ss.field(), "21", ss.length())); + + ss++; + ASSERT_TRUE(ss); + ASSERT_EQ(4ul, ss.length()); + ASSERT_FALSE(strncmp(ss.field(), "4321", ss.length())); + + ++ss; + ASSERT_FALSE(ss); + ASSERT_EQ(0ul, ss.length()); + ASSERT_EQ(ss.field(), str + strlen(str)); +} + +TEST_F(StringSplitterTest, number_list) { + const char* str = " 123,,12,1, 21 4321\00056"; + butil::StringMultiSplitter ss(str, ", "); + ASSERT_TRUE(ss); + ASSERT_EQ(3ul, ss.length()); + ASSERT_FALSE(strncmp(ss.field(), "123", ss.length())); + + ss++; + ASSERT_TRUE(ss); + ASSERT_EQ(2ul, ss.length()); + ASSERT_FALSE(strncmp(ss.field(), "12", ss.length())); + + ss++; + ASSERT_TRUE(ss); + ASSERT_EQ(1ul, ss.length()); + ASSERT_FALSE(strncmp(ss.field(), "1", ss.length())); + + ss++; + ASSERT_TRUE(ss); + ASSERT_EQ(2ul, ss.length()); + ASSERT_FALSE(strncmp(ss.field(), "21", ss.length())); + + ss++; + ASSERT_TRUE(ss); + ASSERT_EQ(4ul, ss.length()); + ASSERT_FALSE(strncmp(ss.field(), "4321", ss.length())); + + ++ss; + ASSERT_FALSE(ss); + ASSERT_EQ(0ul, ss.length()); + ASSERT_EQ(ss.field(), str + strlen(str)); + + // contains embedded '\0' + const size_t str_len = 23; + butil::StringMultiSplitter ss2(str, str + str_len, ", "); + ASSERT_TRUE(ss2); + ASSERT_EQ(3ul, ss2.length()); + ASSERT_FALSE(strncmp(ss2.field(), "123", ss2.length())); + + ss2++; + ASSERT_TRUE(ss2); + ASSERT_EQ(2ul, ss2.length()); + ASSERT_FALSE(strncmp(ss2.field(), "12", ss2.length())); + + ss2++; + ASSERT_TRUE(ss2); + ASSERT_EQ(1ul, ss2.length()); + ASSERT_FALSE(strncmp(ss2.field(), "1", ss2.length())); + + ss2++; + ASSERT_TRUE(ss2); + ASSERT_EQ(2ul, ss2.length()); + ASSERT_FALSE(strncmp(ss2.field(), "21", ss2.length())); + + ss2++; + ASSERT_TRUE(ss2); + ASSERT_EQ(7ul, ss2.length()); + ASSERT_FALSE(strncmp(ss2.field(), "4321\00056", ss2.length())); + + ++ss2; + ASSERT_FALSE(ss2); + ASSERT_EQ(0ul, ss2.length()); + ASSERT_EQ(ss2.field(), str + str_len); +} + +TEST_F(StringSplitterTest, cast_type) { + const char* str = "-1\t123\t111\t1\t10\t11\t1.3\t3.1415926\t127\t128\t256"; + int i = 0; + unsigned int u = 0; + long l = 0; + unsigned long ul = 0; + long long ll = 0; + unsigned long long ull = 0; + float f = 0.0; + double d = 0.0; + + butil::StringSplitter ss(str, '\t'); + ASSERT_TRUE(ss); + + ASSERT_EQ(0, ss.to_int(&i)); + ASSERT_EQ(-1, i); + + ASSERT_TRUE(++ss); + ASSERT_EQ(0, ss.to_uint(&u)); + ASSERT_EQ(123u, u); + + ASSERT_TRUE(++ss); + ASSERT_EQ(0, ss.to_long(&l)); + ASSERT_EQ(111, l); + + + ASSERT_TRUE(++ss); + ASSERT_EQ(0, ss.to_ulong(&ul)); + ASSERT_EQ(1ul, ul); + + ASSERT_TRUE(++ss); + ASSERT_EQ(0, ss.to_longlong(&ll)); + ASSERT_EQ(10, ll); + + ASSERT_TRUE(++ss); + ASSERT_EQ(0, ss.to_ulonglong(&ull)); + ASSERT_EQ(11ull, ull); + + ASSERT_TRUE(++ss); + ASSERT_EQ(0, ss.to_float(&f)); + ASSERT_FLOAT_EQ(1.3, f); + + ASSERT_TRUE(++ss); + ASSERT_EQ(0, ss.to_double(&d)); + ASSERT_DOUBLE_EQ(3.1415926, d); + + ASSERT_TRUE(++ss); + int8_t c = 0; + ASSERT_EQ(0, ss.to_int8(&c)); + ASSERT_EQ(127, c); + + ASSERT_TRUE(++ss); + uint8_t uc = 0; + ASSERT_EQ(0, ss.to_uint8(&uc)); + ASSERT_EQ(128U, uc); + + ASSERT_TRUE(++ss); + ASSERT_EQ(-1, ss.to_uint8(&uc)); +} + +TEST_F(StringSplitterTest, split_limit_len) { + const char* str = "1\t1\0003\t111\t1\t10\t11\t1.3\t3.1415926"; + butil::StringSplitter ss(str, str + 5, '\t'); + + ASSERT_TRUE(ss); + ASSERT_EQ(1ul, ss.length()); + ASSERT_FALSE(strncmp(ss.field(), "1", ss.length())); + + ++ss; + ASSERT_TRUE(ss); + ASSERT_EQ(3ul, ss.length()); + ASSERT_FALSE(strncmp(ss.field(), "1\0003", ss.length())); + + ++ss; + ASSERT_FALSE(ss); + + // Allows using '\0' as separator + butil::StringSplitter ss2(str, str + 5, '\0'); + + ASSERT_TRUE(ss2); + ASSERT_EQ(3ul, ss2.length()); + ASSERT_FALSE(strncmp(ss2.field(), "1\t1", ss2.length())); + + ++ss2; + ASSERT_TRUE(ss2); + ASSERT_EQ(1ul, ss2.length()); + ASSERT_FALSE(strncmp(ss2.field(), "3", ss2.length())); + + ++ss2; + ASSERT_FALSE(ss2); + + butil::StringPiece sp(str, 5); + // Allows using '\0' as separator + butil::StringSplitter ss3(sp, '\0'); + + ASSERT_TRUE(ss3); + ASSERT_EQ(3ul, ss3.length()); + ASSERT_FALSE(strncmp(ss3.field(), "1\t1", ss3.length())); + + ++ss3; + ASSERT_TRUE(ss3); + ASSERT_EQ(1ul, ss3.length()); + ASSERT_FALSE(strncmp(ss3.field(), "3", ss3.length())); + + ++ss3; + ASSERT_FALSE(ss3); +} + +TEST_F(StringSplitterTest, non_null_terminated_string) { + const char str[] = " a non null terminated string "; + const size_t len = strlen(str); + char* buf = new char[len]; + memcpy(buf, str, len); + + butil::StringSplitter ss(buf, buf + len, ' '); + + // "a" + ASSERT_TRUE(ss != NULL); + ASSERT_EQ(1ul, ss.length()); + ASSERT_EQ(ss.field(), buf + 2); + + // "non" + ++ss; + ASSERT_TRUE(ss != NULL); + ASSERT_EQ(3ul, ss.length()); + ASSERT_EQ(ss.field(), buf + 4); + + // "null" + ++ss; + ASSERT_TRUE(ss != NULL); + ASSERT_EQ(4ul, ss.length()); + ASSERT_EQ(ss.field(), buf + 9); + + // "terminated" + ++ss; + ASSERT_TRUE(ss != NULL); + ASSERT_EQ(10ul, ss.length()); + ASSERT_EQ(ss.field(), buf + 16); + + // "string" + ++ss; + ASSERT_TRUE(ss != NULL); + ASSERT_EQ(6ul, ss.length()); + ASSERT_EQ(ss.field(), buf + 28); + + ++ss; + ASSERT_FALSE(ss); + ASSERT_EQ(0ul, ss.length()); + ASSERT_EQ(ss.field(), buf + len); + + delete[] buf; +} + +TEST_F(StringSplitterTest, key_value_pairs_splitter_sanity) { + std::string kvstr = "key1=value1&&&key2=value2&key3=value3&===&key4=&=&=value5"; + for (int i = 0 ; i < 3; ++i) { + // Test three constructors + butil::KeyValuePairsSplitter* psplitter = NULL; + if (i == 0) { + psplitter = new butil::KeyValuePairsSplitter(kvstr, '&', '='); + } else if (i == 1) { + psplitter = new butil::KeyValuePairsSplitter( + kvstr.data(), kvstr.data() + kvstr.size(), '&', '='); + } else if (i == 2) { + psplitter = new butil::KeyValuePairsSplitter(kvstr.c_str(), '&', '='); + } + butil::KeyValuePairsSplitter& splitter = *psplitter; + + ASSERT_TRUE(splitter); + ASSERT_EQ(splitter.key(), "key1"); + ASSERT_EQ(splitter.value(), "value1"); + ++splitter; + ASSERT_TRUE(splitter); + ASSERT_EQ(splitter.key(), "key2"); + ASSERT_EQ(splitter.value(), "value2"); + ++splitter; + ASSERT_TRUE(splitter); + ASSERT_EQ(splitter.key(), "key3"); + ASSERT_EQ(splitter.value(), "value3"); + ++splitter; + ASSERT_TRUE(splitter); + ASSERT_EQ(splitter.key(), ""); + ASSERT_EQ(splitter.value(), "=="); + ++splitter; + ASSERT_TRUE(splitter); + ASSERT_EQ(splitter.key(), "key4"); + ASSERT_EQ(splitter.value(), ""); + ++splitter; + ASSERT_TRUE(splitter); + ASSERT_EQ(splitter.key(), ""); + ASSERT_EQ(splitter.value(), ""); + ++splitter; + ASSERT_TRUE(splitter); + ASSERT_EQ(splitter.key(), ""); + ASSERT_EQ(splitter.value(), "value5"); + ++splitter; + ASSERT_FALSE(splitter); + + delete psplitter; + } +} + +} diff --git a/test/string_tokenizer_unittest.cc b/test/string_tokenizer_unittest.cc new file mode 100644 index 0000000..b7e8d05 --- /dev/null +++ b/test/string_tokenizer_unittest.cc @@ -0,0 +1,234 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/strings/string_tokenizer.h" + +#include + +using std::string; + +namespace butil { + +namespace { + +TEST(StringTokenizerTest, Simple) { + string input = "this is a test"; + StringTokenizer t(input, " "); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("this"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("is"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("a"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("test"), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +TEST(StringTokenizerTest, Reset) { + string input = "this is a test"; + StringTokenizer t(input, " "); + + for (int i = 0; i < 2; ++i) { + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("this"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("is"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("a"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("test"), t.token()); + + EXPECT_FALSE(t.GetNext()); + t.Reset(); + } +} + +TEST(StringTokenizerTest, RetDelims) { + string input = "this is a test"; + StringTokenizer t(input, " "); + t.set_options(StringTokenizer::RETURN_DELIMS); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("this"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string(" "), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("is"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string(" "), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("a"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string(" "), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("test"), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +TEST(StringTokenizerTest, ManyDelims) { + string input = "this: is, a-test"; + StringTokenizer t(input, ": ,-"); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("this"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("is"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("a"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("test"), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +TEST(StringTokenizerTest, ParseHeader) { + string input = "Content-Type: text/html ; charset=UTF-8"; + StringTokenizer t(input, ": ;="); + t.set_options(StringTokenizer::RETURN_DELIMS); + + EXPECT_TRUE(t.GetNext()); + EXPECT_FALSE(t.token_is_delim()); + EXPECT_EQ(string("Content-Type"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_TRUE(t.token_is_delim()); + EXPECT_EQ(string(":"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_TRUE(t.token_is_delim()); + EXPECT_EQ(string(" "), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_FALSE(t.token_is_delim()); + EXPECT_EQ(string("text/html"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_TRUE(t.token_is_delim()); + EXPECT_EQ(string(" "), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_TRUE(t.token_is_delim()); + EXPECT_EQ(string(";"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_TRUE(t.token_is_delim()); + EXPECT_EQ(string(" "), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_FALSE(t.token_is_delim()); + EXPECT_EQ(string("charset"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_TRUE(t.token_is_delim()); + EXPECT_EQ(string("="), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_FALSE(t.token_is_delim()); + EXPECT_EQ(string("UTF-8"), t.token()); + + EXPECT_FALSE(t.GetNext()); + EXPECT_FALSE(t.token_is_delim()); +} + +TEST(StringTokenizerTest, ParseQuotedString) { + string input = "foo bar 'hello world' baz"; + StringTokenizer t(input, " "); + t.set_quote_chars("'"); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("foo"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("bar"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("'hello world'"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("baz"), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +TEST(StringTokenizerTest, ParseQuotedString_Malformed) { + string input = "bar 'hello wo"; + StringTokenizer t(input, " "); + t.set_quote_chars("'"); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("bar"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("'hello wo"), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +TEST(StringTokenizerTest, ParseQuotedString_Multiple) { + string input = "bar 'hel\"lo\" wo' baz\""; + StringTokenizer t(input, " "); + t.set_quote_chars("'\""); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("bar"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("'hel\"lo\" wo'"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("baz\""), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +TEST(StringTokenizerTest, ParseQuotedString_EscapedQuotes) { + string input = "foo 'don\\'t do that'"; + StringTokenizer t(input, " "); + t.set_quote_chars("'"); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("foo"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("'don\\'t do that'"), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +TEST(StringTokenizerTest, ParseQuotedString_EscapedQuotes2) { + string input = "foo='a, b', bar"; + StringTokenizer t(input, ", "); + t.set_quote_chars("'"); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("foo='a, b'"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("bar"), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +} // namespace + +} // namespace butil diff --git a/test/string_util_unittest.cc b/test/string_util_unittest.cc new file mode 100644 index 0000000..0b60af2 --- /dev/null +++ b/test/string_util_unittest.cc @@ -0,0 +1,1190 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/strings/string_util.h" + +#include +#include + +#include + +#include "butil/basictypes.h" +#include "butil/strings/string16.h" +#include "butil/strings/utf_string_conversions.h" +#include + +namespace butil { + +static const struct trim_case { + const wchar_t* input; + const TrimPositions positions; + const wchar_t* output; + const TrimPositions return_value; +} trim_cases[] = { + {L" Google Video ", TRIM_LEADING, L"Google Video ", TRIM_LEADING}, + {L" Google Video ", TRIM_TRAILING, L" Google Video", TRIM_TRAILING}, + {L" Google Video ", TRIM_ALL, L"Google Video", TRIM_ALL}, + {L"Google Video", TRIM_ALL, L"Google Video", TRIM_NONE}, + {L"", TRIM_ALL, L"", TRIM_NONE}, + {L" ", TRIM_LEADING, L"", TRIM_LEADING}, + {L" ", TRIM_TRAILING, L"", TRIM_TRAILING}, + {L" ", TRIM_ALL, L"", TRIM_ALL}, + {L"\t\rTest String\n", TRIM_ALL, L"Test String", TRIM_ALL}, + {L"\x2002Test String\x00A0\x3000", TRIM_ALL, L"Test String", TRIM_ALL}, +}; + +static const struct trim_case_ascii { + const char* input; + const TrimPositions positions; + const char* output; + const TrimPositions return_value; +} trim_cases_ascii[] = { + {" Google Video ", TRIM_LEADING, "Google Video ", TRIM_LEADING}, + {" Google Video ", TRIM_TRAILING, " Google Video", TRIM_TRAILING}, + {" Google Video ", TRIM_ALL, "Google Video", TRIM_ALL}, + {"Google Video", TRIM_ALL, "Google Video", TRIM_NONE}, + {"", TRIM_ALL, "", TRIM_NONE}, + {" ", TRIM_LEADING, "", TRIM_LEADING}, + {" ", TRIM_TRAILING, "", TRIM_TRAILING}, + {" ", TRIM_ALL, "", TRIM_ALL}, + {"\t\rTest String\n", TRIM_ALL, "Test String", TRIM_ALL}, +}; + +namespace { + +// Helper used to test TruncateUTF8ToByteSize. +bool Truncated(const std::string& input, const size_t byte_size, + std::string* output) { + size_t prev = input.length(); + TruncateUTF8ToByteSize(input, byte_size, output); + return prev != output->length(); +} + +} // namespace + +TEST(StringUtilTest, TruncateUTF8ToByteSize) { + std::string output; + + // Empty strings and invalid byte_size arguments + EXPECT_FALSE(Truncated(std::string(), 0, &output)); + EXPECT_EQ(output, ""); + EXPECT_TRUE(Truncated("\xe1\x80\xbf", 0, &output)); + EXPECT_EQ(output, ""); + EXPECT_FALSE(Truncated("\xe1\x80\xbf", (size_t)-1, &output)); + EXPECT_FALSE(Truncated("\xe1\x80\xbf", 4, &output)); + + // Testing the truncation of valid UTF8 correctly + EXPECT_TRUE(Truncated("abc", 2, &output)); + EXPECT_EQ(output, "ab"); + EXPECT_TRUE(Truncated("\xc2\x81\xc2\x81", 2, &output)); + EXPECT_EQ(output.compare("\xc2\x81"), 0); + EXPECT_TRUE(Truncated("\xc2\x81\xc2\x81", 3, &output)); + EXPECT_EQ(output.compare("\xc2\x81"), 0); + EXPECT_FALSE(Truncated("\xc2\x81\xc2\x81", 4, &output)); + EXPECT_EQ(output.compare("\xc2\x81\xc2\x81"), 0); + + { + const char array[] = "\x00\x00\xc2\x81\xc2\x81"; + const std::string array_string(array, arraysize(array)); + EXPECT_TRUE(Truncated(array_string, 4, &output)); + EXPECT_EQ(output.compare(std::string("\x00\x00\xc2\x81", 4)), 0); + } + + { + const char array[] = "\x00\xc2\x81\xc2\x81"; + const std::string array_string(array, arraysize(array)); + EXPECT_TRUE(Truncated(array_string, 4, &output)); + EXPECT_EQ(output.compare(std::string("\x00\xc2\x81", 3)), 0); + } + + // Testing invalid UTF8 + EXPECT_TRUE(Truncated("\xed\xa0\x80\xed\xbf\xbf", 6, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xed\xa0\x8f", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xed\xbf\xbf", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + + // Testing invalid UTF8 mixed with valid UTF8 + EXPECT_FALSE(Truncated("\xe1\x80\xbf", 3, &output)); + EXPECT_EQ(output.compare("\xe1\x80\xbf"), 0); + EXPECT_FALSE(Truncated("\xf1\x80\xa0\xbf", 4, &output)); + EXPECT_EQ(output.compare("\xf1\x80\xa0\xbf"), 0); + EXPECT_FALSE(Truncated("a\xc2\x81\xe1\x80\xbf\xf1\x80\xa0\xbf", + 10, &output)); + EXPECT_EQ(output.compare("a\xc2\x81\xe1\x80\xbf\xf1\x80\xa0\xbf"), 0); + EXPECT_TRUE(Truncated("a\xc2\x81\xe1\x80\xbf\xf1""a""\x80\xa0", + 10, &output)); + EXPECT_EQ(output.compare("a\xc2\x81\xe1\x80\xbf\xf1""a"), 0); + EXPECT_FALSE(Truncated("\xef\xbb\xbf" "abc", 6, &output)); + EXPECT_EQ(output.compare("\xef\xbb\xbf" "abc"), 0); + + // Overlong sequences + EXPECT_TRUE(Truncated("\xc0\x80", 2, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xc1\x80\xc1\x81", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xe0\x80\x80", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xe0\x82\x80", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xe0\x9f\xbf", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf0\x80\x80\x8D", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf0\x80\x82\x91", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf0\x80\xa0\x80", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf0\x8f\xbb\xbf", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf8\x80\x80\x80\xbf", 5, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xfc\x80\x80\x80\xa0\xa5", 6, &output)); + EXPECT_EQ(output.compare(""), 0); + + // Beyond U+10FFFF (the upper limit of Unicode codespace) + EXPECT_TRUE(Truncated("\xf4\x90\x80\x80", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf8\xa0\xbf\x80\xbf", 5, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xfc\x9c\xbf\x80\xbf\x80", 6, &output)); + EXPECT_EQ(output.compare(""), 0); + + // BOMs in UTF-16(BE|LE) and UTF-32(BE|LE) + EXPECT_TRUE(Truncated("\xfe\xff", 2, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xff\xfe", 2, &output)); + EXPECT_EQ(output.compare(""), 0); + + { + const char array[] = "\x00\x00\xfe\xff"; + const std::string array_string(array, arraysize(array)); + EXPECT_TRUE(Truncated(array_string, 4, &output)); + EXPECT_EQ(output.compare(std::string("\x00\x00", 2)), 0); + } + + // Variants on the previous test + { + const char array[] = "\xff\xfe\x00\x00"; + const std::string array_string(array, 4); + EXPECT_FALSE(Truncated(array_string, 4, &output)); + EXPECT_EQ(output.compare(std::string("\xff\xfe\x00\x00", 4)), 0); + } + { + const char array[] = "\xff\x00\x00\xfe"; + const std::string array_string(array, arraysize(array)); + EXPECT_TRUE(Truncated(array_string, 4, &output)); + EXPECT_EQ(output.compare(std::string("\xff\x00\x00", 3)), 0); + } + + // Non-characters : U+xxFFF[EF] where xx is 0x00 through 0x10 and + EXPECT_TRUE(Truncated("\xef\xbf\xbe", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf0\x8f\xbf\xbe", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf3\xbf\xbf\xbf", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xef\xb7\x90", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xef\xb7\xaf", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + + // Strings in legacy encodings that are valid in UTF-8, but + // are invalid as UTF-8 in real data. + EXPECT_TRUE(Truncated("caf\xe9", 4, &output)); + EXPECT_EQ(output.compare("caf"), 0); + EXPECT_TRUE(Truncated("\xb0\xa1\xb0\xa2", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_FALSE(Truncated("\xa7\x41\xa6\x6e", 4, &output)); + EXPECT_EQ(output.compare("\xa7\x41\xa6\x6e"), 0); + EXPECT_TRUE(Truncated("\xa7\x41\xa6\x6e\xd9\xee\xe4\xee", 7, + &output)); + EXPECT_EQ(output.compare("\xa7\x41\xa6\x6e"), 0); + + // Testing using the same string as input and output. + EXPECT_FALSE(Truncated(output, 4, &output)); + EXPECT_EQ(output.compare("\xa7\x41\xa6\x6e"), 0); + EXPECT_TRUE(Truncated(output, 3, &output)); + EXPECT_EQ(output.compare("\xa7\x41"), 0); + + // "abc" with U+201[CD] in windows-125[0-8] + EXPECT_TRUE(Truncated("\x93" "abc\x94", 5, &output)); + EXPECT_EQ(output.compare("\x93" "abc"), 0); + + // U+0639 U+064E U+0644 U+064E in ISO-8859-6 + EXPECT_TRUE(Truncated("\xd9\xee\xe4\xee", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + + // U+03B3 U+03B5 U+03B9 U+03AC in ISO-8859-7 + EXPECT_TRUE(Truncated("\xe3\xe5\xe9\xdC", 4, &output)); + EXPECT_EQ(output.compare(""), 0); +} + +TEST(StringUtilTest, TrimWhitespace) { + string16 output; // Allow contents to carry over to next testcase + for (size_t i = 0; i < arraysize(trim_cases); ++i) { + const trim_case& value = trim_cases[i]; + EXPECT_EQ(value.return_value, + TrimWhitespace(WideToUTF16(value.input), value.positions, + &output)); + EXPECT_EQ(WideToUTF16(value.output), output); + } + + // Test that TrimWhitespace() can take the same string for input and output + output = ASCIIToUTF16(" This is a test \r\n"); + EXPECT_EQ(TRIM_ALL, TrimWhitespace(output, TRIM_ALL, &output)); + EXPECT_EQ(ASCIIToUTF16("This is a test"), output); + + // Once more, but with a string of whitespace + output = ASCIIToUTF16(" \r\n"); + EXPECT_EQ(TRIM_ALL, TrimWhitespace(output, TRIM_ALL, &output)); + EXPECT_EQ(string16(), output); + + std::string output_ascii; + for (size_t i = 0; i < arraysize(trim_cases_ascii); ++i) { + const trim_case_ascii& value = trim_cases_ascii[i]; + EXPECT_EQ(value.return_value, + TrimWhitespace(value.input, value.positions, &output_ascii)); + EXPECT_EQ(value.output, output_ascii); + } +} + +static const struct collapse_case { + const wchar_t* input; + const bool trim; + const wchar_t* output; +} collapse_cases[] = { + {L" Google Video ", false, L"Google Video"}, + {L"Google Video", false, L"Google Video"}, + {L"", false, L""}, + {L" ", false, L""}, + {L"\t\rTest String\n", false, L"Test String"}, + {L"\x2002Test String\x00A0\x3000", false, L"Test String"}, + {L" Test \n \t String ", false, L"Test String"}, + {L"\x2002Test\x1680 \x2028 \tString\x00A0\x3000", false, L"Test String"}, + {L" Test String", false, L"Test String"}, + {L"Test String ", false, L"Test String"}, + {L"Test String", false, L"Test String"}, + {L"", true, L""}, + {L"\n", true, L""}, + {L" \r ", true, L""}, + {L"\nFoo", true, L"Foo"}, + {L"\r Foo ", true, L"Foo"}, + {L" Foo bar ", true, L"Foo bar"}, + {L" \tFoo bar \n", true, L"Foo bar"}, + {L" a \r b\n c \r\n d \t\re \t f \n ", true, L"abcde f"}, +}; + +TEST(StringUtilTest, CollapseWhitespace) { + for (size_t i = 0; i < arraysize(collapse_cases); ++i) { + const collapse_case& value = collapse_cases[i]; + EXPECT_EQ(WideToUTF16(value.output), + CollapseWhitespace(WideToUTF16(value.input), value.trim)); + } +} + +static const struct collapse_case_ascii { + const char* input; + const bool trim; + const char* output; +} collapse_cases_ascii[] = { + {" Google Video ", false, "Google Video"}, + {"Google Video", false, "Google Video"}, + {"", false, ""}, + {" ", false, ""}, + {"\t\rTest String\n", false, "Test String"}, + {" Test \n \t String ", false, "Test String"}, + {" Test String", false, "Test String"}, + {"Test String ", false, "Test String"}, + {"Test String", false, "Test String"}, + {"", true, ""}, + {"\n", true, ""}, + {" \r ", true, ""}, + {"\nFoo", true, "Foo"}, + {"\r Foo ", true, "Foo"}, + {" Foo bar ", true, "Foo bar"}, + {" \tFoo bar \n", true, "Foo bar"}, + {" a \r b\n c \r\n d \t\re \t f \n ", true, "abcde f"}, +}; + +TEST(StringUtilTest, CollapseWhitespaceASCII) { + for (size_t i = 0; i < arraysize(collapse_cases_ascii); ++i) { + const collapse_case_ascii& value = collapse_cases_ascii[i]; + EXPECT_EQ(value.output, CollapseWhitespaceASCII(value.input, value.trim)); + } +} + +TEST(StringUtilTest, IsStringUTF8) { + EXPECT_TRUE(IsStringUTF8("abc")); + EXPECT_TRUE(IsStringUTF8("\xc2\x81")); + EXPECT_TRUE(IsStringUTF8("\xe1\x80\xbf")); + EXPECT_TRUE(IsStringUTF8("\xf1\x80\xa0\xbf")); + EXPECT_TRUE(IsStringUTF8("a\xc2\x81\xe1\x80\xbf\xf1\x80\xa0\xbf")); + EXPECT_TRUE(IsStringUTF8("\xef\xbb\xbf" "abc")); // UTF-8 BOM + + // surrogate code points + EXPECT_FALSE(IsStringUTF8("\xed\xa0\x80\xed\xbf\xbf")); + EXPECT_FALSE(IsStringUTF8("\xed\xa0\x8f")); + EXPECT_FALSE(IsStringUTF8("\xed\xbf\xbf")); + + // overlong sequences + EXPECT_FALSE(IsStringUTF8("\xc0\x80")); // U+0000 + EXPECT_FALSE(IsStringUTF8("\xc1\x80\xc1\x81")); // "AB" + EXPECT_FALSE(IsStringUTF8("\xe0\x80\x80")); // U+0000 + EXPECT_FALSE(IsStringUTF8("\xe0\x82\x80")); // U+0080 + EXPECT_FALSE(IsStringUTF8("\xe0\x9f\xbf")); // U+07ff + EXPECT_FALSE(IsStringUTF8("\xf0\x80\x80\x8D")); // U+000D + EXPECT_FALSE(IsStringUTF8("\xf0\x80\x82\x91")); // U+0091 + EXPECT_FALSE(IsStringUTF8("\xf0\x80\xa0\x80")); // U+0800 + EXPECT_FALSE(IsStringUTF8("\xf0\x8f\xbb\xbf")); // U+FEFF (BOM) + EXPECT_FALSE(IsStringUTF8("\xf8\x80\x80\x80\xbf")); // U+003F + EXPECT_FALSE(IsStringUTF8("\xfc\x80\x80\x80\xa0\xa5")); // U+00A5 + + // Beyond U+10FFFF (the upper limit of Unicode codespace) + EXPECT_FALSE(IsStringUTF8("\xf4\x90\x80\x80")); // U+110000 + EXPECT_FALSE(IsStringUTF8("\xf8\xa0\xbf\x80\xbf")); // 5 bytes + EXPECT_FALSE(IsStringUTF8("\xfc\x9c\xbf\x80\xbf\x80")); // 6 bytes + + // BOMs in UTF-16(BE|LE) and UTF-32(BE|LE) + EXPECT_FALSE(IsStringUTF8("\xfe\xff")); + EXPECT_FALSE(IsStringUTF8("\xff\xfe")); + EXPECT_FALSE(IsStringUTF8(std::string("\x00\x00\xfe\xff", 4))); + EXPECT_FALSE(IsStringUTF8("\xff\xfe\x00\x00")); + + // Non-characters : U+xxFFF[EF] where xx is 0x00 through 0x10 and + EXPECT_FALSE(IsStringUTF8("\xef\xbf\xbe")); // U+FFFE) + EXPECT_FALSE(IsStringUTF8("\xf0\x8f\xbf\xbe")); // U+1FFFE + EXPECT_FALSE(IsStringUTF8("\xf3\xbf\xbf\xbf")); // U+10FFFF + EXPECT_FALSE(IsStringUTF8("\xef\xb7\x90")); // U+FDD0 + EXPECT_FALSE(IsStringUTF8("\xef\xb7\xaf")); // U+FDEF + // Strings in legacy encodings. We can certainly make up strings + // in a legacy encoding that are valid in UTF-8, but in real data, + // most of them are invalid as UTF-8. + EXPECT_FALSE(IsStringUTF8("caf\xe9")); // cafe with U+00E9 in ISO-8859-1 + EXPECT_FALSE(IsStringUTF8("\xb0\xa1\xb0\xa2")); // U+AC00, U+AC001 in EUC-KR + EXPECT_FALSE(IsStringUTF8("\xa7\x41\xa6\x6e")); // U+4F60 U+597D in Big5 + // "abc" with U+201[CD] in windows-125[0-8] + EXPECT_FALSE(IsStringUTF8("\x93" "abc\x94")); + // U+0639 U+064E U+0644 U+064E in ISO-8859-6 + EXPECT_FALSE(IsStringUTF8("\xd9\xee\xe4\xee")); + // U+03B3 U+03B5 U+03B9 U+03AC in ISO-8859-7 + EXPECT_FALSE(IsStringUTF8("\xe3\xe5\xe9\xdC")); + + // Check that we support Embedded Nulls. The first uses the canonical UTF-8 + // representation, and the second uses a 2-byte sequence. The second version + // is invalid UTF-8 since UTF-8 states that the shortest encoding for a + // given codepoint must be used. + static const char kEmbeddedNull[] = "embedded\0null"; + EXPECT_TRUE(IsStringUTF8( + std::string(kEmbeddedNull, sizeof(kEmbeddedNull)))); + EXPECT_FALSE(IsStringUTF8("embedded\xc0\x80U+0000")); +} + +TEST(StringUtilTest, ConvertASCII) { + static const char* char_cases[] = { + "Google Video", + "Hello, world\n", + "0123ABCDwxyz \a\b\t\r\n!+,.~" + }; + + static const wchar_t* const wchar_cases[] = { + L"Google Video", + L"Hello, world\n", + L"0123ABCDwxyz \a\b\t\r\n!+,.~" + }; + + for (size_t i = 0; i < arraysize(char_cases); ++i) { + EXPECT_TRUE(IsStringASCII(char_cases[i])); + string16 utf16 = ASCIIToUTF16(char_cases[i]); + EXPECT_EQ(WideToUTF16(wchar_cases[i]), utf16); + + std::string ascii = UTF16ToASCII(WideToUTF16(wchar_cases[i])); + EXPECT_EQ(char_cases[i], ascii); + } + + EXPECT_FALSE(IsStringASCII("Google \x80Video")); + + // Convert empty strings. + string16 empty16; + std::string empty; + EXPECT_EQ(empty, UTF16ToASCII(empty16)); + EXPECT_EQ(empty16, ASCIIToUTF16(empty)); + + // Convert strings with an embedded NUL character. + const char chars_with_nul[] = "test\0string"; + const int length_with_nul = arraysize(chars_with_nul) - 1; + std::string string_with_nul(chars_with_nul, length_with_nul); + std::wstring wide_with_nul = ASCIIToWide(string_with_nul); + EXPECT_EQ(static_cast(length_with_nul), + wide_with_nul.length()); + std::string narrow_with_nul = UTF16ToASCII(WideToUTF16(wide_with_nul)); + EXPECT_EQ(static_cast(length_with_nul), + narrow_with_nul.length()); + EXPECT_EQ(0, string_with_nul.compare(narrow_with_nul)); +} + +TEST(StringUtilTest, ToUpperASCII) { + EXPECT_EQ('C', ToUpperASCII('C')); + EXPECT_EQ('C', ToUpperASCII('c')); + EXPECT_EQ('2', ToUpperASCII('2')); + + EXPECT_EQ(L'C', ToUpperASCII(L'C')); + EXPECT_EQ(L'C', ToUpperASCII(L'c')); + EXPECT_EQ(L'2', ToUpperASCII(L'2')); + + std::string in_place_a("Cc2"); + StringToUpperASCII(&in_place_a); + EXPECT_EQ("CC2", in_place_a); + + std::wstring in_place_w(L"Cc2"); + StringToUpperASCII(&in_place_w); + EXPECT_EQ(L"CC2", in_place_w); + + std::string original_a("Cc2"); + std::string upper_a = StringToUpperASCII(original_a); + EXPECT_EQ("CC2", upper_a); + + std::wstring original_w(L"Cc2"); + std::wstring upper_w = StringToUpperASCII(original_w); + EXPECT_EQ(L"CC2", upper_w); +} + +TEST(StringUtilTest, LowerCaseEqualsASCII) { + static const struct { + const char* src_a; + const char* dst; + } lowercase_cases[] = { + { "FoO", "foo" }, + { "foo", "foo" }, + { "FOO", "foo" }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(lowercase_cases); ++i) { + EXPECT_TRUE(LowerCaseEqualsASCII(ASCIIToUTF16(lowercase_cases[i].src_a), + lowercase_cases[i].dst)); + EXPECT_TRUE(LowerCaseEqualsASCII(lowercase_cases[i].src_a, + lowercase_cases[i].dst)); + } +} + +TEST(StringUtilTest, FormatBytesUnlocalized) { + static const struct { + int64_t bytes; + const char* expected; + } cases[] = { + // Expected behavior: we show one post-decimal digit when we have + // under two pre-decimal digits, except in cases where it makes no + // sense (zero or bytes). + // Since we switch units once we cross the 1000 mark, this keeps + // the display of file sizes or bytes consistently around three + // digits. + {0, "0 B"}, + {512, "512 B"}, + {1024*1024, "1.0 MB"}, + {1024*1024*1024, "1.0 GB"}, + {10LL*1024*1024*1024, "10.0 GB"}, + {99LL*1024*1024*1024, "99.0 GB"}, + {105LL*1024*1024*1024, "105 GB"}, + {105LL*1024*1024*1024 + 500LL*1024*1024, "105 GB"}, + {~(1LL<<63), "8192 PB"}, + + {99*1024 + 103, "99.1 kB"}, + {1024*1024 + 103, "1.0 MB"}, + {1024*1024 + 205 * 1024, "1.2 MB"}, + {1024*1024*1024 + (927 * 1024*1024), "1.9 GB"}, + {10LL*1024*1024*1024, "10.0 GB"}, + {100LL*1024*1024*1024, "100 GB"}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + EXPECT_EQ(ASCIIToUTF16(cases[i].expected), + FormatBytesUnlocalized(cases[i].bytes)); + } +} +TEST(StringUtilTest, ReplaceSubstringsAfterOffset) { + static const struct { + const char* str; + string16::size_type start_offset; + const char* find_this; + const char* replace_with; + const char* expected; + } cases[] = { + {"aaa", 0, "a", "b", "bbb"}, + {"abb", 0, "ab", "a", "ab"}, + {"Removing some substrings inging", 0, "ing", "", "Remov some substrs "}, + {"Not found", 0, "x", "0", "Not found"}, + {"Not found again", 5, "x", "0", "Not found again"}, + {" Making it much longer ", 0, " ", "Four score and seven years ago", + "Four score and seven years agoMakingFour score and seven years agoit" + "Four score and seven years agomuchFour score and seven years agolonger" + "Four score and seven years ago"}, + {"Invalid offset", 9999, "t", "foobar", "Invalid offset"}, + {"Replace me only me once", 9, "me ", "", "Replace me only once"}, + {"abababab", 2, "ab", "c", "abccc"}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); i++) { + string16 str = ASCIIToUTF16(cases[i].str); + ReplaceSubstringsAfterOffset(&str, cases[i].start_offset, + ASCIIToUTF16(cases[i].find_this), + ASCIIToUTF16(cases[i].replace_with)); + EXPECT_EQ(ASCIIToUTF16(cases[i].expected), str); + } +} + +TEST(StringUtilTest, ReplaceFirstSubstringAfterOffset) { + static const struct { + const char* str; + string16::size_type start_offset; + const char* find_this; + const char* replace_with; + const char* expected; + } cases[] = { + {"aaa", 0, "a", "b", "baa"}, + {"abb", 0, "ab", "a", "ab"}, + {"Removing some substrings inging", 0, "ing", "", + "Remov some substrings inging"}, + {"Not found", 0, "x", "0", "Not found"}, + {"Not found again", 5, "x", "0", "Not found again"}, + {" Making it much longer ", 0, " ", "Four score and seven years ago", + "Four score and seven years agoMaking it much longer "}, + {"Invalid offset", 9999, "t", "foobar", "Invalid offset"}, + {"Replace me only me once", 4, "me ", "", "Replace only me once"}, + {"abababab", 2, "ab", "c", "abcabab"}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); i++) { + string16 str = ASCIIToUTF16(cases[i].str); + ReplaceFirstSubstringAfterOffset(&str, cases[i].start_offset, + ASCIIToUTF16(cases[i].find_this), + ASCIIToUTF16(cases[i].replace_with)); + EXPECT_EQ(ASCIIToUTF16(cases[i].expected), str); + } +} + +TEST(StringUtilTest, HexDigitToInt) { + EXPECT_EQ(0, HexDigitToInt('0')); + EXPECT_EQ(1, HexDigitToInt('1')); + EXPECT_EQ(2, HexDigitToInt('2')); + EXPECT_EQ(3, HexDigitToInt('3')); + EXPECT_EQ(4, HexDigitToInt('4')); + EXPECT_EQ(5, HexDigitToInt('5')); + EXPECT_EQ(6, HexDigitToInt('6')); + EXPECT_EQ(7, HexDigitToInt('7')); + EXPECT_EQ(8, HexDigitToInt('8')); + EXPECT_EQ(9, HexDigitToInt('9')); + EXPECT_EQ(10, HexDigitToInt('A')); + EXPECT_EQ(11, HexDigitToInt('B')); + EXPECT_EQ(12, HexDigitToInt('C')); + EXPECT_EQ(13, HexDigitToInt('D')); + EXPECT_EQ(14, HexDigitToInt('E')); + EXPECT_EQ(15, HexDigitToInt('F')); + + // Verify the lower case as well. + EXPECT_EQ(10, HexDigitToInt('a')); + EXPECT_EQ(11, HexDigitToInt('b')); + EXPECT_EQ(12, HexDigitToInt('c')); + EXPECT_EQ(13, HexDigitToInt('d')); + EXPECT_EQ(14, HexDigitToInt('e')); + EXPECT_EQ(15, HexDigitToInt('f')); +} + +// This checks where we can use the assignment operator for a va_list. We need +// a way to do this since Visual C doesn't support va_copy, but assignment on +// va_list is not guaranteed to be a copy. See StringAppendVT which uses this +// capability. +static void VariableArgsFunc(const char* format, ...) { + va_list org; + va_start(org, format); + + va_list dup; + GG_VA_COPY(dup, org); + int i1 = va_arg(org, int); + int j1 = va_arg(org, int); + char* s1 = va_arg(org, char*); + double d1 = va_arg(org, double); + va_end(org); + + int i2 = va_arg(dup, int); + int j2 = va_arg(dup, int); + char* s2 = va_arg(dup, char*); + double d2 = va_arg(dup, double); + + EXPECT_EQ(i1, i2); + EXPECT_EQ(j1, j2); + EXPECT_STREQ(s1, s2); + EXPECT_EQ(d1, d2); + + va_end(dup); +} + +TEST(StringUtilTest, VAList) { + VariableArgsFunc("%d %d %s %lf", 45, 92, "This is interesting", 9.21); +} + +// Test for Tokenize +template +void TokenizeTest() { + std::vector r; + size_t size; + + size = Tokenize(STR("This is a string"), STR(" "), &r); + EXPECT_EQ(4U, size); + ASSERT_EQ(4U, r.size()); + EXPECT_EQ(r[0], STR("This")); + EXPECT_EQ(r[1], STR("is")); + EXPECT_EQ(r[2], STR("a")); + EXPECT_EQ(r[3], STR("string")); + r.clear(); + + size = Tokenize(STR("one,two,three"), STR(","), &r); + EXPECT_EQ(3U, size); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], STR("one")); + EXPECT_EQ(r[1], STR("two")); + EXPECT_EQ(r[2], STR("three")); + r.clear(); + + size = Tokenize(STR("one,two:three;four"), STR(",:"), &r); + EXPECT_EQ(3U, size); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], STR("one")); + EXPECT_EQ(r[1], STR("two")); + EXPECT_EQ(r[2], STR("three;four")); + r.clear(); + + size = Tokenize(STR("one,two:three;four"), STR(";,:"), &r); + EXPECT_EQ(4U, size); + ASSERT_EQ(4U, r.size()); + EXPECT_EQ(r[0], STR("one")); + EXPECT_EQ(r[1], STR("two")); + EXPECT_EQ(r[2], STR("three")); + EXPECT_EQ(r[3], STR("four")); + r.clear(); + + size = Tokenize(STR("one, two, three"), STR(","), &r); + EXPECT_EQ(3U, size); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], STR("one")); + EXPECT_EQ(r[1], STR(" two")); + EXPECT_EQ(r[2], STR(" three")); + r.clear(); + + size = Tokenize(STR("one, two, three, "), STR(","), &r); + EXPECT_EQ(4U, size); + ASSERT_EQ(4U, r.size()); + EXPECT_EQ(r[0], STR("one")); + EXPECT_EQ(r[1], STR(" two")); + EXPECT_EQ(r[2], STR(" three")); + EXPECT_EQ(r[3], STR(" ")); + r.clear(); + + size = Tokenize(STR("one, two, three,"), STR(","), &r); + EXPECT_EQ(3U, size); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], STR("one")); + EXPECT_EQ(r[1], STR(" two")); + EXPECT_EQ(r[2], STR(" three")); + r.clear(); + + size = Tokenize(STR(), STR(","), &r); + EXPECT_EQ(0U, size); + ASSERT_EQ(0U, r.size()); + r.clear(); + + size = Tokenize(STR(","), STR(","), &r); + EXPECT_EQ(0U, size); + ASSERT_EQ(0U, r.size()); + r.clear(); + + size = Tokenize(STR(",;:."), STR(".:;,"), &r); + EXPECT_EQ(0U, size); + ASSERT_EQ(0U, r.size()); + r.clear(); + + size = Tokenize(STR("\t\ta\t"), STR("\t"), &r); + EXPECT_EQ(1U, size); + ASSERT_EQ(1U, r.size()); + EXPECT_EQ(r[0], STR("a")); + r.clear(); + + size = Tokenize(STR("\ta\t\nb\tcc"), STR("\n"), &r); + EXPECT_EQ(2U, size); + ASSERT_EQ(2U, r.size()); + EXPECT_EQ(r[0], STR("\ta\t")); + EXPECT_EQ(r[1], STR("b\tcc")); + r.clear(); +} + +TEST(StringUtilTest, TokenizeStdString) { + TokenizeTest(); +} + +TEST(StringUtilTest, TokenizeStringPiece) { + TokenizeTest(); +} + +// Test for JoinString +TEST(StringUtilTest, JoinString) { + std::vector in; + EXPECT_EQ("", JoinString(in, ',')); + + in.push_back("a"); + EXPECT_EQ("a", JoinString(in, ',')); + + in.push_back("b"); + in.push_back("c"); + EXPECT_EQ("a,b,c", JoinString(in, ',')); + + in.push_back(std::string()); + EXPECT_EQ("a,b,c,", JoinString(in, ',')); + in.push_back(" "); + EXPECT_EQ("a|b|c|| ", JoinString(in, '|')); +} + +// Test for JoinString overloaded with std::string separator +TEST(StringUtilTest, JoinStringWithString) { + std::string separator(", "); + std::vector parts; + EXPECT_EQ(std::string(), JoinString(parts, separator)); + + parts.push_back("a"); + EXPECT_EQ("a", JoinString(parts, separator)); + + parts.push_back("b"); + parts.push_back("c"); + EXPECT_EQ("a, b, c", JoinString(parts, separator)); + + parts.push_back(std::string()); + EXPECT_EQ("a, b, c, ", JoinString(parts, separator)); + parts.push_back(" "); + EXPECT_EQ("a|b|c|| ", JoinString(parts, "|")); +} + +// Test for JoinString overloaded with string16 separator +TEST(StringUtilTest, JoinStringWithString16) { + string16 separator = ASCIIToUTF16(", "); + std::vector parts; + EXPECT_EQ(string16(), JoinString(parts, separator)); + + parts.push_back(ASCIIToUTF16("a")); + EXPECT_EQ(ASCIIToUTF16("a"), JoinString(parts, separator)); + + parts.push_back(ASCIIToUTF16("b")); + parts.push_back(ASCIIToUTF16("c")); + EXPECT_EQ(ASCIIToUTF16("a, b, c"), JoinString(parts, separator)); + + parts.push_back(ASCIIToUTF16("")); + EXPECT_EQ(ASCIIToUTF16("a, b, c, "), JoinString(parts, separator)); + parts.push_back(ASCIIToUTF16(" ")); + EXPECT_EQ(ASCIIToUTF16("a|b|c|| "), JoinString(parts, ASCIIToUTF16("|"))); +} + +TEST(StringUtilTest, StartsWith) { + EXPECT_TRUE(StartsWithASCII("javascript:url", "javascript", true)); + EXPECT_FALSE(StartsWithASCII("JavaScript:url", "javascript", true)); + EXPECT_TRUE(StartsWithASCII("javascript:url", "javascript", false)); + EXPECT_TRUE(StartsWithASCII("JavaScript:url", "javascript", false)); + EXPECT_FALSE(StartsWithASCII("java", "javascript", true)); + EXPECT_FALSE(StartsWithASCII("java", "javascript", false)); + EXPECT_FALSE(StartsWithASCII(std::string(), "javascript", false)); + EXPECT_FALSE(StartsWithASCII(std::string(), "javascript", true)); + EXPECT_TRUE(StartsWithASCII("java", std::string(), false)); + EXPECT_TRUE(StartsWithASCII("java", std::string(), true)); + + EXPECT_TRUE(StartsWith(ASCIIToUTF16("javascript:url"), + ASCIIToUTF16("javascript"), true)); + EXPECT_FALSE(StartsWith(ASCIIToUTF16("JavaScript:url"), + ASCIIToUTF16("javascript"), true)); + EXPECT_TRUE(StartsWith(ASCIIToUTF16("javascript:url"), + ASCIIToUTF16("javascript"), false)); + EXPECT_TRUE(StartsWith(ASCIIToUTF16("JavaScript:url"), + ASCIIToUTF16("javascript"), false)); + EXPECT_FALSE(StartsWith(ASCIIToUTF16("java"), + ASCIIToUTF16("javascript"), true)); + EXPECT_FALSE(StartsWith(ASCIIToUTF16("java"), + ASCIIToUTF16("javascript"), false)); + EXPECT_FALSE(StartsWith(string16(), ASCIIToUTF16("javascript"), false)); + EXPECT_FALSE(StartsWith(string16(), ASCIIToUTF16("javascript"), true)); + EXPECT_TRUE(StartsWith(ASCIIToUTF16("java"), string16(), false)); + EXPECT_TRUE(StartsWith(ASCIIToUTF16("java"), string16(), true)); +} + +TEST(StringUtilTest, EndsWith) { + EXPECT_TRUE(EndsWith(ASCIIToUTF16("Foo.plugin"), + ASCIIToUTF16(".plugin"), true)); + EXPECT_FALSE(EndsWith(ASCIIToUTF16("Foo.Plugin"), + ASCIIToUTF16(".plugin"), true)); + EXPECT_TRUE(EndsWith(ASCIIToUTF16("Foo.plugin"), + ASCIIToUTF16(".plugin"), false)); + EXPECT_TRUE(EndsWith(ASCIIToUTF16("Foo.Plugin"), + ASCIIToUTF16(".plugin"), false)); + EXPECT_FALSE(EndsWith(ASCIIToUTF16(".plug"), ASCIIToUTF16(".plugin"), true)); + EXPECT_FALSE(EndsWith(ASCIIToUTF16(".plug"), ASCIIToUTF16(".plugin"), false)); + EXPECT_FALSE(EndsWith(ASCIIToUTF16("Foo.plugin Bar"), + ASCIIToUTF16(".plugin"), true)); + EXPECT_FALSE(EndsWith(ASCIIToUTF16("Foo.plugin Bar"), + ASCIIToUTF16(".plugin"), false)); + EXPECT_FALSE(EndsWith(string16(), ASCIIToUTF16(".plugin"), false)); + EXPECT_FALSE(EndsWith(string16(), ASCIIToUTF16(".plugin"), true)); + EXPECT_TRUE(EndsWith(ASCIIToUTF16("Foo.plugin"), string16(), false)); + EXPECT_TRUE(EndsWith(ASCIIToUTF16("Foo.plugin"), string16(), true)); + EXPECT_TRUE(EndsWith(ASCIIToUTF16(".plugin"), + ASCIIToUTF16(".plugin"), false)); + EXPECT_TRUE(EndsWith(ASCIIToUTF16(".plugin"), ASCIIToUTF16(".plugin"), true)); + EXPECT_TRUE(EndsWith(string16(), string16(), false)); + EXPECT_TRUE(EndsWith(string16(), string16(), true)); +} + +TEST(StringUtilTest, GetStringFWithOffsets) { + std::vector subst; + subst.push_back(ASCIIToUTF16("1")); + subst.push_back(ASCIIToUTF16("2")); + std::vector offsets; + + ReplaceStringPlaceholders(ASCIIToUTF16("Hello, $1. Your number is $2."), + subst, + &offsets); + EXPECT_EQ(2U, offsets.size()); + EXPECT_EQ(7U, offsets[0]); + EXPECT_EQ(25U, offsets[1]); + offsets.clear(); + + ReplaceStringPlaceholders(ASCIIToUTF16("Hello, $2. Your number is $1."), + subst, + &offsets); + EXPECT_EQ(2U, offsets.size()); + EXPECT_EQ(25U, offsets[0]); + EXPECT_EQ(7U, offsets[1]); + offsets.clear(); +} + +TEST(StringUtilTest, ReplaceStringPlaceholdersTooFew) { + // Test whether replacestringplaceholders works as expected when there + // are fewer inputs than outputs. + std::vector subst; + subst.push_back(ASCIIToUTF16("9a")); + subst.push_back(ASCIIToUTF16("8b")); + subst.push_back(ASCIIToUTF16("7c")); + + string16 formatted = + ReplaceStringPlaceholders( + ASCIIToUTF16("$1a,$2b,$3c,$4d,$5e,$6f,$1g,$2h,$3i"), subst, NULL); + + EXPECT_EQ(formatted, ASCIIToUTF16("9aa,8bb,7cc,d,e,f,9ag,8bh,7ci")); +} + +TEST(StringUtilTest, ReplaceStringPlaceholders) { + std::vector subst; + subst.push_back(ASCIIToUTF16("9a")); + subst.push_back(ASCIIToUTF16("8b")); + subst.push_back(ASCIIToUTF16("7c")); + subst.push_back(ASCIIToUTF16("6d")); + subst.push_back(ASCIIToUTF16("5e")); + subst.push_back(ASCIIToUTF16("4f")); + subst.push_back(ASCIIToUTF16("3g")); + subst.push_back(ASCIIToUTF16("2h")); + subst.push_back(ASCIIToUTF16("1i")); + + string16 formatted = + ReplaceStringPlaceholders( + ASCIIToUTF16("$1a,$2b,$3c,$4d,$5e,$6f,$7g,$8h,$9i"), subst, NULL); + + EXPECT_EQ(formatted, ASCIIToUTF16("9aa,8bb,7cc,6dd,5ee,4ff,3gg,2hh,1ii")); +} + +TEST(StringUtilTest, ReplaceStringPlaceholdersMoreThan9Replacements) { + std::vector subst; + subst.push_back(ASCIIToUTF16("9a")); + subst.push_back(ASCIIToUTF16("8b")); + subst.push_back(ASCIIToUTF16("7c")); + subst.push_back(ASCIIToUTF16("6d")); + subst.push_back(ASCIIToUTF16("5e")); + subst.push_back(ASCIIToUTF16("4f")); + subst.push_back(ASCIIToUTF16("3g")); + subst.push_back(ASCIIToUTF16("2h")); + subst.push_back(ASCIIToUTF16("1i")); + subst.push_back(ASCIIToUTF16("0j")); + subst.push_back(ASCIIToUTF16("-1k")); + subst.push_back(ASCIIToUTF16("-2l")); + subst.push_back(ASCIIToUTF16("-3m")); + subst.push_back(ASCIIToUTF16("-4n")); + + string16 formatted = + ReplaceStringPlaceholders( + ASCIIToUTF16("$1a,$2b,$3c,$4d,$5e,$6f,$7g,$8h,$9i," + "$10j,$11k,$12l,$13m,$14n,$1"), subst, NULL); + + EXPECT_EQ(formatted, ASCIIToUTF16("9aa,8bb,7cc,6dd,5ee,4ff,3gg,2hh," + "1ii,0jj,-1kk,-2ll,-3mm,-4nn,9a")); +} + +TEST(StringUtilTest, StdStringReplaceStringPlaceholders) { + std::vector subst; + subst.push_back("9a"); + subst.push_back("8b"); + subst.push_back("7c"); + subst.push_back("6d"); + subst.push_back("5e"); + subst.push_back("4f"); + subst.push_back("3g"); + subst.push_back("2h"); + subst.push_back("1i"); + + std::string formatted = + ReplaceStringPlaceholders( + "$1a,$2b,$3c,$4d,$5e,$6f,$7g,$8h,$9i", subst, NULL); + + EXPECT_EQ(formatted, "9aa,8bb,7cc,6dd,5ee,4ff,3gg,2hh,1ii"); +} + +TEST(StringUtilTest, ReplaceStringPlaceholdersConsecutiveDollarSigns) { + std::vector subst; + subst.push_back("a"); + subst.push_back("b"); + subst.push_back("c"); + EXPECT_EQ(ReplaceStringPlaceholders("$$1 $$$2 $$$$3", subst, NULL), + "$1 $$2 $$$3"); +} + +TEST(StringUtilTest, MatchPatternTest) { + EXPECT_TRUE(MatchPattern("www.google.com", "*.com")); + EXPECT_TRUE(MatchPattern("www.google.com", "*")); + EXPECT_FALSE(MatchPattern("www.google.com", "www*.g*.org")); + EXPECT_TRUE(MatchPattern("Hello", "H?l?o")); + EXPECT_FALSE(MatchPattern("www.google.com", "http://*)")); + EXPECT_FALSE(MatchPattern("www.msn.com", "*.COM")); + EXPECT_TRUE(MatchPattern("Hello*1234", "He??o\\*1*")); + EXPECT_FALSE(MatchPattern("", "*.*")); + EXPECT_TRUE(MatchPattern("", "*")); + EXPECT_TRUE(MatchPattern("", "?")); + EXPECT_TRUE(MatchPattern("", "")); + EXPECT_FALSE(MatchPattern("Hello", "")); + EXPECT_TRUE(MatchPattern("Hello*", "Hello*")); + // Stop after a certain recursion depth. + EXPECT_FALSE(MatchPattern("123456789012345678", "?????????????????*")); + + // Test UTF8 matching. + EXPECT_TRUE(MatchPattern("heart: \xe2\x99\xa0", "*\xe2\x99\xa0")); + EXPECT_TRUE(MatchPattern("heart: \xe2\x99\xa0.", "heart: ?.")); + EXPECT_TRUE(MatchPattern("hearts: \xe2\x99\xa0\xe2\x99\xa0", "*")); + // Invalid sequences should be handled as a single invalid character. + EXPECT_TRUE(MatchPattern("invalid: \xef\xbf\xbe", "invalid: ?")); + // If the pattern has invalid characters, it shouldn't match anything. + EXPECT_FALSE(MatchPattern("\xf4\x90\x80\x80", "\xf4\x90\x80\x80")); + + // Test UTF16 character matching. + EXPECT_TRUE(MatchPattern(UTF8ToUTF16("www.google.com"), + UTF8ToUTF16("*.com"))); + EXPECT_TRUE(MatchPattern(UTF8ToUTF16("Hello*1234"), + UTF8ToUTF16("He??o\\*1*"))); + + // This test verifies that consecutive wild cards are collapsed into 1 + // wildcard (when this doesn't occur, MatchPattern reaches it's maximum + // recursion depth). + EXPECT_TRUE(MatchPattern(UTF8ToUTF16("Hello"), + UTF8ToUTF16("He********************************o"))); +} + +TEST(StringUtilTest, LcpyTest) { + // Test the normal case where we fit in our buffer. + { + char dst[10]; + wchar_t wdst[10]; + EXPECT_EQ(7U, butil::strlcpy(dst, "abcdefg", arraysize(dst))); + EXPECT_EQ(0, memcmp(dst, "abcdefg", 8)); + EXPECT_EQ(7U, butil::wcslcpy(wdst, L"abcdefg", arraysize(wdst))); + EXPECT_EQ(0, memcmp(wdst, L"abcdefg", sizeof(wchar_t) * 8)); + } + + // Test dst_size == 0, nothing should be written to |dst| and we should + // have the equivalent of strlen(src). + { + char dst[2] = {1, 2}; + wchar_t wdst[2] = {1, 2}; + EXPECT_EQ(7U, butil::strlcpy(dst, "abcdefg", 0)); + EXPECT_EQ(1, dst[0]); + EXPECT_EQ(2, dst[1]); + EXPECT_EQ(7U, butil::wcslcpy(wdst, L"abcdefg", 0)); + EXPECT_EQ(static_cast(1), wdst[0]); + EXPECT_EQ(static_cast(2), wdst[1]); + } + + // Test the case were we _just_ competely fit including the null. + { + char dst[8]; + wchar_t wdst[8]; + EXPECT_EQ(7U, butil::strlcpy(dst, "abcdefg", arraysize(dst))); + EXPECT_EQ(0, memcmp(dst, "abcdefg", 8)); + EXPECT_EQ(7U, butil::wcslcpy(wdst, L"abcdefg", arraysize(wdst))); + EXPECT_EQ(0, memcmp(wdst, L"abcdefg", sizeof(wchar_t) * 8)); + } + + // Test the case were we we are one smaller, so we can't fit the null. + { + char dst[7]; + wchar_t wdst[7]; + EXPECT_EQ(7U, butil::strlcpy(dst, "abcdefg", arraysize(dst))); + EXPECT_EQ(0, memcmp(dst, "abcdef", 7)); + EXPECT_EQ(7U, butil::wcslcpy(wdst, L"abcdefg", arraysize(wdst))); + EXPECT_EQ(0, memcmp(wdst, L"abcdef", sizeof(wchar_t) * 7)); + } + + // Test the case were we are just too small. + { + char dst[3]; + wchar_t wdst[3]; + EXPECT_EQ(7U, butil::strlcpy(dst, "abcdefg", arraysize(dst))); + EXPECT_EQ(0, memcmp(dst, "ab", 3)); + EXPECT_EQ(7U, butil::wcslcpy(wdst, L"abcdefg", arraysize(wdst))); + EXPECT_EQ(0, memcmp(wdst, L"ab", sizeof(wchar_t) * 3)); + } +} + +TEST(StringUtilTest, WprintfFormatPortabilityTest) { + static const struct { + const wchar_t* input; + bool portable; + } cases[] = { + { L"%ls", true }, + { L"%s", false }, + { L"%S", false }, + { L"%lS", false }, + { L"Hello, %s", false }, + { L"%lc", true }, + { L"%c", false }, + { L"%C", false }, + { L"%lC", false }, + { L"%ls %s", false }, + { L"%s %ls", false }, + { L"%s %ls %s", false }, + { L"%f", true }, + { L"%f %F", false }, + { L"%d %D", false }, + { L"%o %O", false }, + { L"%u %U", false }, + { L"%f %d %o %u", true }, + { L"%-8d (%02.1f%)", true }, + { L"% 10s", false }, + { L"% 10ls", true } + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) + EXPECT_EQ(cases[i].portable, butil::IsWprintfFormatPortable(cases[i].input)); +} + +TEST(StringUtilTest, RemoveChars) { + const char* kRemoveChars = "-/+*"; + std::string input = "A-+bc/d!*"; + EXPECT_TRUE(RemoveChars(input, kRemoveChars, &input)); + EXPECT_EQ("Abcd!", input); + + // No characters match kRemoveChars. + EXPECT_FALSE(RemoveChars(input, kRemoveChars, &input)); + EXPECT_EQ("Abcd!", input); + + // Empty string. + input.clear(); + EXPECT_FALSE(RemoveChars(input, kRemoveChars, &input)); + EXPECT_EQ(std::string(), input); +} + +TEST(StringUtilTest, ReplaceChars) { + struct TestData { + const char* input; + const char* replace_chars; + const char* replace_with; + const char* output; + bool result; + } cases[] = { + { "", "", "", "", false }, + { "test", "", "", "test", false }, + { "test", "", "!", "test", false }, + { "test", "z", "!", "test", false }, + { "test", "e", "!", "t!st", true }, + { "test", "e", "!?", "t!?st", true }, + { "test", "ez", "!", "t!st", true }, + { "test", "zed", "!?", "t!?st", true }, + { "test", "t", "!?", "!?es!?", true }, + { "test", "et", "!>", "!>!>s!>", true }, + { "test", "zest", "!", "!!!!", true }, + { "test", "szt", "!", "!e!!", true }, + { "test", "t", "test", "testestest", true }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + std::string output; + bool result = ReplaceChars(cases[i].input, + cases[i].replace_chars, + cases[i].replace_with, + &output); + EXPECT_EQ(cases[i].result, result); + EXPECT_EQ(cases[i].output, output); + } +} + +TEST(StringUtilTest, ContainsOnlyChars) { + // Providing an empty list of characters should return false but for the empty + // string. + EXPECT_TRUE(ContainsOnlyChars(std::string(), std::string())); + EXPECT_FALSE(ContainsOnlyChars("Hello", std::string())); + + EXPECT_TRUE(ContainsOnlyChars(std::string(), "1234")); + EXPECT_TRUE(ContainsOnlyChars("1", "1234")); + EXPECT_TRUE(ContainsOnlyChars("1", "4321")); + EXPECT_TRUE(ContainsOnlyChars("123", "4321")); + EXPECT_FALSE(ContainsOnlyChars("123a", "4321")); + + EXPECT_TRUE(ContainsOnlyChars(std::string(), kWhitespaceASCII)); + EXPECT_TRUE(ContainsOnlyChars(" ", kWhitespaceASCII)); + EXPECT_TRUE(ContainsOnlyChars("\t", kWhitespaceASCII)); + EXPECT_TRUE(ContainsOnlyChars("\t \r \n ", kWhitespaceASCII)); + EXPECT_FALSE(ContainsOnlyChars("a", kWhitespaceASCII)); + EXPECT_FALSE(ContainsOnlyChars("\thello\r \n ", kWhitespaceASCII)); + + EXPECT_TRUE(ContainsOnlyChars(string16(), kWhitespaceUTF16)); + EXPECT_TRUE(ContainsOnlyChars(ASCIIToUTF16(" "), kWhitespaceUTF16)); + EXPECT_TRUE(ContainsOnlyChars(ASCIIToUTF16("\t"), kWhitespaceUTF16)); + EXPECT_TRUE(ContainsOnlyChars(ASCIIToUTF16("\t \r \n "), kWhitespaceUTF16)); + EXPECT_FALSE(ContainsOnlyChars(ASCIIToUTF16("a"), kWhitespaceUTF16)); + EXPECT_FALSE(ContainsOnlyChars(ASCIIToUTF16("\thello\r \n "), + kWhitespaceUTF16)); +} + +class WriteIntoTest : public testing::Test { + protected: + static void WritesCorrectly(size_t num_chars) { + std::string buffer; + char kOriginal[] = "supercali"; + strncpy(WriteInto(&buffer, num_chars + 1), kOriginal, num_chars); + // Using std::string(buffer.c_str()) instead of |buffer| truncates the + // string at the first \0. + EXPECT_EQ(std::string(kOriginal, + std::min(num_chars, arraysize(kOriginal) - 1)), + std::string(buffer.c_str())); + EXPECT_EQ(num_chars, buffer.size()); + } +}; + +TEST_F(WriteIntoTest, WriteInto) { + // Validate that WriteInto reserves enough space and + // sizes a string correctly. + WritesCorrectly(1); + WritesCorrectly(2); + WritesCorrectly(5000); + + // Validate that WriteInto doesn't modify other strings + // when using a Copy-on-Write implementation. + const char kLive[] = "live"; + const char kDead[] = "dead"; + const std::string live = kLive; + std::string dead = live; + strncpy(WriteInto(&dead, 5), kDead, 4); + EXPECT_EQ(kDead, dead); + EXPECT_EQ(4u, dead.size()); + EXPECT_EQ(kLive, live); + EXPECT_EQ(4u, live.size()); +} + +} // namespace butil diff --git a/test/stringize_macros_unittest.cc b/test/stringize_macros_unittest.cc new file mode 100644 index 0000000..a186d43 --- /dev/null +++ b/test/stringize_macros_unittest.cc @@ -0,0 +1,29 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/strings/stringize_macros.h" + +#include + +// Macros as per documentation in header file. +#define PREPROCESSOR_UTIL_UNITTEST_A FOO +#define PREPROCESSOR_UTIL_UNITTEST_B(x) myobj->FunctionCall(x) +#define PREPROCESSOR_UTIL_UNITTEST_C "foo" + +TEST(StringizeTest, Ansi) { + EXPECT_STREQ( + "PREPROCESSOR_UTIL_UNITTEST_A", + STRINGIZE_NO_EXPANSION(PREPROCESSOR_UTIL_UNITTEST_A)); + EXPECT_STREQ( + "PREPROCESSOR_UTIL_UNITTEST_B(y)", + STRINGIZE_NO_EXPANSION(PREPROCESSOR_UTIL_UNITTEST_B(y))); + EXPECT_STREQ( + "PREPROCESSOR_UTIL_UNITTEST_C", + STRINGIZE_NO_EXPANSION(PREPROCESSOR_UTIL_UNITTEST_C)); + + EXPECT_STREQ("FOO", STRINGIZE(PREPROCESSOR_UTIL_UNITTEST_A)); + EXPECT_STREQ("myobj->FunctionCall(y)", + STRINGIZE(PREPROCESSOR_UTIL_UNITTEST_B(y))); + EXPECT_STREQ("\"foo\"", STRINGIZE(PREPROCESSOR_UTIL_UNITTEST_C)); +} diff --git a/test/stringprintf_unittest.cc b/test/stringprintf_unittest.cc new file mode 100644 index 0000000..621b2c4 --- /dev/null +++ b/test/stringprintf_unittest.cc @@ -0,0 +1,188 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/strings/stringprintf.h" + +#include + +#include "butil/basictypes.h" +#include + +namespace butil { + +namespace { + +// A helper for the StringAppendV test that follows. +// +// Just forwards its args to StringAppendV. +static void StringAppendVTestHelper(std::string* out, const char* format, ...) { + va_list ap; + va_start(ap, format); + StringAppendV(out, format, ap); + va_end(ap); +} + +} // namespace + +TEST(StringPrintfTest, StringPrintfEmpty) { + EXPECT_EQ("", StringPrintf("%s", "")); +} + +TEST(StringPrintfTest, StringPrintfMisc) { + EXPECT_EQ("123hello w", StringPrintf("%3d%2s %1c", 123, "hello", 'w')); +#if !defined(OS_ANDROID) + EXPECT_EQ(L"123hello w", StringPrintf(L"%3d%2ls %1lc", 123, L"hello", 'w')); +#endif +} + +TEST(StringPrintfTest, StringAppendfEmptyString) { + std::string value("Hello"); + StringAppendF(&value, "%s", ""); + EXPECT_EQ("Hello", value); + +#if !defined(OS_ANDROID) + std::wstring valuew(L"Hello"); + StringAppendF(&valuew, L"%ls", L""); + EXPECT_EQ(L"Hello", valuew); +#endif +} + +TEST(StringPrintfTest, StringAppendfString) { + std::string value("Hello"); + StringAppendF(&value, " %s", "World"); + EXPECT_EQ("Hello World", value); + +#if !defined(OS_ANDROID) + std::wstring valuew(L"Hello"); + StringAppendF(&valuew, L" %ls", L"World"); + EXPECT_EQ(L"Hello World", valuew); +#endif +} + +TEST(StringPrintfTest, StringAppendfInt) { + std::string value("Hello"); + StringAppendF(&value, " %d", 123); + EXPECT_EQ("Hello 123", value); + +#if !defined(OS_ANDROID) + std::wstring valuew(L"Hello"); + StringAppendF(&valuew, L" %d", 123); + EXPECT_EQ(L"Hello 123", valuew); +#endif +} + +// Make sure that lengths exactly around the initial buffer size are handled +// correctly. +TEST(StringPrintfTest, StringPrintfBounds) { + const int kSrcLen = 1026; + char src[kSrcLen]; + for (size_t i = 0; i < arraysize(src); i++) + src[i] = 'A'; + + wchar_t srcw[kSrcLen]; + for (size_t i = 0; i < arraysize(srcw); i++) + srcw[i] = 'A'; + + for (int i = 1; i < 3; i++) { + src[kSrcLen - i] = 0; + std::string out; + SStringPrintf(&out, "%s", src); + EXPECT_STREQ(src, out.c_str()); + +#if !defined(OS_ANDROID) + srcw[kSrcLen - i] = 0; + std::wstring outw; + SStringPrintf(&outw, L"%ls", srcw); + EXPECT_STREQ(srcw, outw.c_str()); +#endif + } +} + +// Test very large sprintfs that will cause the buffer to grow. +TEST(StringPrintfTest, Grow) { + char src[1026]; + for (size_t i = 0; i < arraysize(src); i++) + src[i] = 'A'; + src[1025] = 0; + + const char* fmt = "%sB%sB%sB%sB%sB%sB%s"; + + std::string out; + SStringPrintf(&out, fmt, src, src, src, src, src, src, src); + + const int kRefSize = 320000; + char* ref = new char[kRefSize]; +#if defined(OS_WIN) + sprintf_s(ref, kRefSize, fmt, src, src, src, src, src, src, src); +#elif defined(OS_POSIX) + snprintf(ref, kRefSize, fmt, src, src, src, src, src, src, src); +#endif + + EXPECT_STREQ(ref, out.c_str()); + delete[] ref; +} + +TEST(StringPrintfTest, StringAppendV) { + std::string out; + StringAppendVTestHelper(&out, "%d foo %s", 1, "bar"); + EXPECT_EQ("1 foo bar", out); +} + +// Test the boundary condition for the size of the string_util's +// internal buffer. +TEST(StringPrintfTest, GrowBoundary) { + const int string_util_buf_len = 1024; + // Our buffer should be one larger than the size of StringAppendVT's stack + // buffer. + const int buf_len = string_util_buf_len + 1; + char src[buf_len + 1]; // Need extra one for NULL-terminator. + for (int i = 0; i < buf_len; ++i) + src[i] = 'a'; + src[buf_len] = 0; + + std::string out; + SStringPrintf(&out, "%s", src); + + EXPECT_STREQ(src, out.c_str()); +} + +// TODO(evanm): what's the proper cross-platform test here? +#if defined(OS_WIN) +// sprintf in Visual Studio fails when given U+FFFF. This tests that the +// failure case is gracefuly handled. +TEST(StringPrintfTest, Invalid) { + wchar_t invalid[2]; + invalid[0] = 0xffff; + invalid[1] = 0; + + std::wstring out; + SStringPrintf(&out, L"%ls", invalid); + EXPECT_STREQ(L"", out.c_str()); +} +#endif + +// Test that the positional parameters work. +TEST(StringPrintfTest, PositionalParameters) { + std::string out; + SStringPrintf(&out, "%1$s %1$s", "test"); + EXPECT_STREQ("test test", out.c_str()); + +#if defined(OS_WIN) + std::wstring wout; + SStringPrintf(&wout, L"%1$ls %1$ls", L"test"); + EXPECT_STREQ(L"test test", wout.c_str()); +#endif +} + +// Test that StringPrintf and StringAppendV do not change errno. +TEST(StringPrintfTest, StringPrintfErrno) { + errno = 1; + EXPECT_EQ("", StringPrintf("%s", "")); + EXPECT_EQ(1, errno); + std::string out; + StringAppendVTestHelper(&out, "%d foo %s", 1, "bar"); + EXPECT_EQ(1, errno); +} + +} // namespace butil diff --git a/test/synchronous_event_unittest.cpp b/test/synchronous_event_unittest.cpp new file mode 100644 index 0000000..96b3c22 --- /dev/null +++ b/test/synchronous_event_unittest.cpp @@ -0,0 +1,74 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/synchronous_event.h" + +namespace { +class SynchronousEventTest : public ::testing::Test{ +protected: + SynchronousEventTest(){ + }; + virtual ~SynchronousEventTest(){}; + virtual void SetUp() { + srand(time(0)); + }; + virtual void TearDown() { + }; +}; + +struct Foo {}; + +typedef butil::SynchronousEvent FooEvent; + +FooEvent foo_event; +std::vector > result; + +class FooObserver : public FooEvent::Observer { +public: + FooObserver() : another_ob(NULL) {} + + void on_event(int x, int* p) { + ++*p; + result.push_back(std::make_pair(x, *p)); + if (another_ob) { + foo_event.subscribe(another_ob); + } + } + FooObserver* another_ob; +}; + + +TEST_F(SynchronousEventTest, sanity) { + const size_t N = 10; + FooObserver foo_observer; + FooObserver foo_observer2; + foo_observer.another_ob = &foo_observer2; + foo_event.subscribe(&foo_observer); + int v = 0; + result.clear(); + for (size_t i = 0; i < N; ++i) { + foo_event.notify(i, &v); + } + ASSERT_EQ(2*N, result.size()); + for (size_t i = 0; i < 2*N; ++i) { + ASSERT_EQ((int)i/2, result[i].first); + ASSERT_EQ((int)i+1, result[i].second); + } +} + +} diff --git a/test/sys_info_unittest.cc b/test/sys_info_unittest.cc new file mode 100644 index 0000000..9e4eafa --- /dev/null +++ b/test/sys_info_unittest.cc @@ -0,0 +1,148 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/environment.h" +#include "butil/file_util.h" +#include "butil/sys_info.h" +#include "butil/threading/platform_thread.h" +#include "butil/time/time.h" +#include +#include + +typedef testing::Test SysInfoTest; +using butil::FilePath; + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID) +TEST_F(SysInfoTest, MaxSharedMemorySize) { + // We aren't actually testing that it's correct, just that it's sane. + EXPECT_GT(butil::SysInfo::MaxSharedMemorySize(), 0u); +} +#endif + +TEST_F(SysInfoTest, NumProcs) { + // We aren't actually testing that it's correct, just that it's sane. + EXPECT_GE(butil::SysInfo::NumberOfProcessors(), 1); +} + +TEST_F(SysInfoTest, AmountOfMem) { + // We aren't actually testing that it's correct, just that it's sane. + EXPECT_GT(butil::SysInfo::AmountOfPhysicalMemory(), 0); + EXPECT_GT(butil::SysInfo::AmountOfPhysicalMemoryMB(), 0); + // The maxmimal amount of virtual memory can be zero which means unlimited. + EXPECT_GE(butil::SysInfo::AmountOfVirtualMemory(), 0); +} + +TEST_F(SysInfoTest, AmountOfFreeDiskSpace) { + // We aren't actually testing that it's correct, just that it's sane. + FilePath tmp_path; + ASSERT_TRUE(butil::GetTempDir(&tmp_path)); + EXPECT_GT(butil::SysInfo::AmountOfFreeDiskSpace(tmp_path), 0) + << tmp_path.value(); +} + +#if defined(OS_WIN) || defined(OS_MACOSX) +TEST_F(SysInfoTest, OperatingSystemVersionNumbers) { + int32_t os_major_version = -1; + int32_t os_minor_version = -1; + int32_t os_bugfix_version = -1; + butil::SysInfo::OperatingSystemVersionNumbers(&os_major_version, + &os_minor_version, + &os_bugfix_version); + EXPECT_GT(os_major_version, -1); + EXPECT_GT(os_minor_version, -1); + EXPECT_GT(os_bugfix_version, -1); +} +#endif + +TEST_F(SysInfoTest, Uptime) { + int64_t up_time_1 = butil::SysInfo::Uptime(); + // UpTime() is implemented internally using TimeTicks::Now(), which documents + // system resolution as being 1-15ms. Sleep a little longer than that. + butil::PlatformThread::Sleep(butil::TimeDelta::FromMilliseconds(20)); + int64_t up_time_2 = butil::SysInfo::Uptime(); + EXPECT_GT(up_time_1, 0); + EXPECT_GT(up_time_2, up_time_1); +} + +#if defined(OS_CHROMEOS) + +TEST_F(SysInfoTest, GoogleChromeOSVersionNumbers) { + int32_t os_major_version = -1; + int32_t os_minor_version = -1; + int32_t os_bugfix_version = -1; + const char* kLsbRelease = + "FOO=1234123.34.5\n" + "CHROMEOS_RELEASE_VERSION=1.2.3.4\n"; + butil::SysInfo::SetChromeOSVersionInfoForTest(kLsbRelease, butil::Time()); + butil::SysInfo::OperatingSystemVersionNumbers(&os_major_version, + &os_minor_version, + &os_bugfix_version); + EXPECT_EQ(1, os_major_version); + EXPECT_EQ(2, os_minor_version); + EXPECT_EQ(3, os_bugfix_version); +} + +TEST_F(SysInfoTest, GoogleChromeOSVersionNumbersFirst) { + int32_t os_major_version = -1; + int32_t os_minor_version = -1; + int32_t os_bugfix_version = -1; + const char* kLsbRelease = + "CHROMEOS_RELEASE_VERSION=1.2.3.4\n" + "FOO=1234123.34.5\n"; + butil::SysInfo::SetChromeOSVersionInfoForTest(kLsbRelease, butil::Time()); + butil::SysInfo::OperatingSystemVersionNumbers(&os_major_version, + &os_minor_version, + &os_bugfix_version); + EXPECT_EQ(1, os_major_version); + EXPECT_EQ(2, os_minor_version); + EXPECT_EQ(3, os_bugfix_version); +} + +TEST_F(SysInfoTest, GoogleChromeOSNoVersionNumbers) { + int32_t os_major_version = -1; + int32_t os_minor_version = -1; + int32_t os_bugfix_version = -1; + const char* kLsbRelease = "FOO=1234123.34.5\n"; + butil::SysInfo::SetChromeOSVersionInfoForTest(kLsbRelease, butil::Time()); + butil::SysInfo::OperatingSystemVersionNumbers(&os_major_version, + &os_minor_version, + &os_bugfix_version); + EXPECT_EQ(0, os_major_version); + EXPECT_EQ(0, os_minor_version); + EXPECT_EQ(0, os_bugfix_version); +} + +TEST_F(SysInfoTest, GoogleChromeOSLsbReleaseTime) { + const char* kLsbRelease = "CHROMEOS_RELEASE_VERSION=1.2.3.4"; + // Use a fake time that can be safely displayed as a string. + const butil::Time lsb_release_time(butil::Time::FromDoubleT(12345.6)); + butil::SysInfo::SetChromeOSVersionInfoForTest(kLsbRelease, lsb_release_time); + butil::Time parsed_lsb_release_time = butil::SysInfo::GetLsbReleaseTime(); + EXPECT_DOUBLE_EQ(lsb_release_time.ToDoubleT(), + parsed_lsb_release_time.ToDoubleT()); +} + +TEST_F(SysInfoTest, IsRunningOnChromeOS) { + butil::SysInfo::SetChromeOSVersionInfoForTest("", butil::Time()); + EXPECT_FALSE(butil::SysInfo::IsRunningOnChromeOS()); + + const char* kLsbRelease1 = + "CHROMEOS_RELEASE_NAME=Non Chrome OS\n" + "CHROMEOS_RELEASE_VERSION=1.2.3.4\n"; + butil::SysInfo::SetChromeOSVersionInfoForTest(kLsbRelease1, butil::Time()); + EXPECT_FALSE(butil::SysInfo::IsRunningOnChromeOS()); + + const char* kLsbRelease2 = + "CHROMEOS_RELEASE_NAME=Chrome OS\n" + "CHROMEOS_RELEASE_VERSION=1.2.3.4\n"; + butil::SysInfo::SetChromeOSVersionInfoForTest(kLsbRelease2, butil::Time()); + EXPECT_TRUE(butil::SysInfo::IsRunningOnChromeOS()); + + const char* kLsbRelease3 = + "CHROMEOS_RELEASE_NAME=Chromium OS\n"; + butil::SysInfo::SetChromeOSVersionInfoForTest(kLsbRelease3, butil::Time()); + EXPECT_TRUE(butil::SysInfo::IsRunningOnChromeOS()); +} + +#endif // OS_CHROMEOS diff --git a/test/sys_string_conversions_unittest.cc b/test/sys_string_conversions_unittest.cc new file mode 100644 index 0000000..6a992e2 --- /dev/null +++ b/test/sys_string_conversions_unittest.cc @@ -0,0 +1,188 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "butil/basictypes.h" +#include "butil/strings/string_piece.h" +#include "butil/strings/sys_string_conversions.h" +#include "butil/strings/utf_string_conversions.h" +#include "scoped_locale.h" +#include + +#ifdef WCHAR_T_IS_UTF32 +static const std::wstring kSysWideOldItalicLetterA = L"\x10300"; +#else +static const std::wstring kSysWideOldItalicLetterA = L"\xd800\xdf00"; +#endif + +namespace butil { + +TEST(SysStrings, SysWideToUTF8) { + EXPECT_EQ("Hello, world", SysWideToUTF8(L"Hello, world")); + EXPECT_EQ("\xe4\xbd\xa0\xe5\xa5\xbd", SysWideToUTF8(L"\x4f60\x597d")); + + // >16 bits + EXPECT_EQ("\xF0\x90\x8C\x80", SysWideToUTF8(kSysWideOldItalicLetterA)); + + // Error case. When Windows finds a UTF-16 character going off the end of + // a string, it just converts that literal value to UTF-8, even though this + // is invalid. + // + // This is what XP does, but Vista has different behavior, so we don't bother + // verifying it: + // EXPECT_EQ("\xE4\xBD\xA0\xED\xA0\x80zyxw", + // SysWideToUTF8(L"\x4f60\xd800zyxw")); + + // Test embedded NULLs. + std::wstring wide_null(L"a"); + wide_null.push_back(0); + wide_null.push_back('b'); + + std::string expected_null("a"); + expected_null.push_back(0); + expected_null.push_back('b'); + + EXPECT_EQ(expected_null, SysWideToUTF8(wide_null)); +} + +TEST(SysStrings, SysUTF8ToWide) { + EXPECT_EQ(L"Hello, world", SysUTF8ToWide("Hello, world")); + EXPECT_EQ(L"\x4f60\x597d", SysUTF8ToWide("\xe4\xbd\xa0\xe5\xa5\xbd")); + // >16 bits + EXPECT_EQ(kSysWideOldItalicLetterA, SysUTF8ToWide("\xF0\x90\x8C\x80")); + + // Error case. When Windows finds an invalid UTF-8 character, it just skips + // it. This seems weird because it's inconsistent with the reverse conversion. + // + // This is what XP does, but Vista has different behavior, so we don't bother + // verifying it: + // EXPECT_EQ(L"\x4f60zyxw", SysUTF8ToWide("\xe4\xbd\xa0\xe5\xa5zyxw")); + + // Test embedded NULLs. + std::string utf8_null("a"); + utf8_null.push_back(0); + utf8_null.push_back('b'); + + std::wstring expected_null(L"a"); + expected_null.push_back(0); + expected_null.push_back('b'); + + EXPECT_EQ(expected_null, SysUTF8ToWide(utf8_null)); +} + +// FIXME(gejun): Following cases fail due to failure of setlocale +#if 0 //defined(OS_LINUX) // Tests depend on setting a specific Linux locale. + +TEST(SysStrings, SysWideToNativeMB) { + ScopedLocale locale("en_US.utf-8"); + EXPECT_EQ("Hello, world", SysWideToNativeMB(L"Hello, world")); + EXPECT_EQ("\xe4\xbd\xa0\xe5\xa5\xbd", SysWideToNativeMB(L"\x4f60\x597d")); + + // >16 bits + EXPECT_EQ("\xF0\x90\x8C\x80", SysWideToNativeMB(kSysWideOldItalicLetterA)); + + // Error case. When Windows finds a UTF-16 character going off the end of + // a string, it just converts that literal value to UTF-8, even though this + // is invalid. + // + // This is what XP does, but Vista has different behavior, so we don't bother + // verifying it: + // EXPECT_EQ("\xE4\xBD\xA0\xED\xA0\x80zyxw", + // SysWideToNativeMB(L"\x4f60\xd800zyxw")); + + // Test embedded NULLs. + std::wstring wide_null(L"a"); + wide_null.push_back(0); + wide_null.push_back('b'); + + std::string expected_null("a"); + expected_null.push_back(0); + expected_null.push_back('b'); + + EXPECT_EQ(expected_null, SysWideToNativeMB(wide_null)); +} + +// We assume the test is running in a UTF8 locale. +TEST(SysStrings, SysNativeMBToWide) { + ScopedLocale locale("en_US.utf-8"); + EXPECT_EQ(L"Hello, world", SysNativeMBToWide("Hello, world")); + EXPECT_EQ(L"\x4f60\x597d", SysNativeMBToWide("\xe4\xbd\xa0\xe5\xa5\xbd")); + // >16 bits + EXPECT_EQ(kSysWideOldItalicLetterA, SysNativeMBToWide("\xF0\x90\x8C\x80")); + + // Error case. When Windows finds an invalid UTF-8 character, it just skips + // it. This seems weird because it's inconsistent with the reverse conversion. + // + // This is what XP does, but Vista has different behavior, so we don't bother + // verifying it: + // EXPECT_EQ(L"\x4f60zyxw", SysNativeMBToWide("\xe4\xbd\xa0\xe5\xa5zyxw")); + + // Test embedded NULLs. + std::string utf8_null("a"); + utf8_null.push_back(0); + utf8_null.push_back('b'); + + std::wstring expected_null(L"a"); + expected_null.push_back(0); + expected_null.push_back('b'); + + EXPECT_EQ(expected_null, SysNativeMBToWide(utf8_null)); +} + +static const wchar_t* const kConvertRoundtripCases[] = { + L"Google Video", + // "网页 图片 资讯更多 »" + L"\x7f51\x9875\x0020\x56fe\x7247\x0020\x8d44\x8baf\x66f4\x591a\x0020\x00bb", + // "Παγκόσμιος Ιστός" + L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9" + L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2", + // "Поиск страниц на русском" + L"\x041f\x043e\x0438\x0441\x043a\x0020\x0441\x0442" + L"\x0440\x0430\x043d\x0438\x0446\x0020\x043d\x0430" + L"\x0020\x0440\x0443\x0441\x0441\x043a\x043e\x043c", + // "전체서비스" + L"\xc804\xccb4\xc11c\xbe44\xc2a4", + + // Test characters that take more than 16 bits. This will depend on whether + // wchar_t is 16 or 32 bits. +#if defined(WCHAR_T_IS_UTF16) + L"\xd800\xdf00", + // ????? (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E) + L"\xd807\xdd40\xd807\xdd41\xd807\xdd42\xd807\xdd43\xd807\xdd44", +#elif defined(WCHAR_T_IS_UTF32) + L"\x10300", + // ????? (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E) + L"\x11d40\x11d41\x11d42\x11d43\x11d44", +#endif +}; + + +TEST(SysStrings, SysNativeMBAndWide) { + ScopedLocale locale("en_US.utf-8"); + for (size_t i = 0; i < arraysize(kConvertRoundtripCases); ++i) { + std::wstring wide = kConvertRoundtripCases[i]; + std::wstring trip = SysNativeMBToWide(SysWideToNativeMB(wide)); + EXPECT_EQ(wide.size(), trip.size()); + EXPECT_EQ(wide, trip); + } + + // We assume our test is running in UTF-8, so double check through ICU. + for (size_t i = 0; i < arraysize(kConvertRoundtripCases); ++i) { + std::wstring wide = kConvertRoundtripCases[i]; + std::wstring trip = SysNativeMBToWide(WideToUTF8(wide)); + EXPECT_EQ(wide.size(), trip.size()); + EXPECT_EQ(wide, trip); + } + + for (size_t i = 0; i < arraysize(kConvertRoundtripCases); ++i) { + std::wstring wide = kConvertRoundtripCases[i]; + std::wstring trip = UTF8ToWide(SysWideToNativeMB(wide)); + EXPECT_EQ(wide.size(), trip.size()); + EXPECT_EQ(wide, trip); + } +} +#endif // OS_LINUX + +} // namespace butil diff --git a/test/temp_file_unittest.cpp b/test/temp_file_unittest.cpp new file mode 100644 index 0000000..128af08 --- /dev/null +++ b/test/temp_file_unittest.cpp @@ -0,0 +1,160 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include // errno +#include "butil/files/temp_file.h" + +namespace { + +class TempFileTest : public ::testing::Test{ +protected: + TempFileTest(){}; + virtual ~TempFileTest(){}; + virtual void SetUp() { + }; + virtual void TearDown() { + }; +}; + +TEST_F(TempFileTest, should_create_tmp_file) +{ + butil::TempFile tmp; + struct stat st; + //check if existed + ASSERT_EQ(0, stat(tmp.fname(), &st)); +} + +TEST_F(TempFileTest, should_write_string) +{ + butil::TempFile tmp; + const char *exp = "a test file"; + ASSERT_EQ(0, tmp.save(exp)); + + FILE *fp = fopen(tmp.fname(), "r"); + ASSERT_NE((void*)0, fp); + + char buf[1024]; + char *act = fgets(buf, 1024, fp); + fclose(fp); + + EXPECT_STREQ(exp, act); +} + +TEST_F(TempFileTest, temp_with_specific_ext) +{ + butil::TempFile tmp("blah"); + const char *exp = "a test file"; + ASSERT_EQ(0, tmp.save(exp)); + struct stat st; + ASSERT_EQ(0, stat(tmp.fname(), &st)); + + ASSERT_STREQ(".blah", strrchr(tmp.fname(), '.')); + FILE *fp = fopen(tmp.fname(), "r"); + ASSERT_NE((void*)0, fp) << strerror(errno); + + char buf[1024]; + char *act = fgets(buf, 1024, fp); + fclose(fp); + + EXPECT_STREQ(exp, act); +} + +TEST_F(TempFileTest, should_delete_when_exit) +{ + std::string fname; + struct stat st; + { + butil::TempFile tmp; + //check if existed + ASSERT_EQ(0, stat(tmp.fname(), &st)); + fname = tmp.fname(); + } + + //check if existed + ASSERT_EQ(-1, stat(fname.c_str(), &st)); + ASSERT_EQ(ENOENT, errno); +} + +TEST_F(TempFileTest, should_save_with_format) +{ + butil::TempFile tmp; + tmp.save_format("%s%d%ld%s", "justmp", 1, 98L, "hello world"); + + FILE *fp = fopen(tmp.fname(), "r"); + ASSERT_NE((void*)0, fp); + + char buf[1024]; + char *act = fgets(buf, 1024, fp); + fclose(fp); + + EXPECT_STREQ("justmp198hello world", act); +} + +TEST_F(TempFileTest, should_save_with_format_in_long_string) +{ + char buf[2048]; + memset(buf, 'a', sizeof(buf)); + buf[2047] = '\0'; + + butil::TempFile tmp; + tmp.save_format("%s", buf); + + FILE *fp = fopen(tmp.fname(), "r"); + ASSERT_NE((void*)0, fp); + + char buf2[2048]; + char *act = fgets(buf2, 2048, fp); + fclose(fp); + EXPECT_STREQ(buf, act); +} + +struct test_t { + int a; + int b; + char c[4]; +}; + +TEST_F(TempFileTest, save_binary_twice) +{ + test_t data = {12, -34, {'B', 'E', 'E', 'F'}}; + butil::TempFile tmp; + ASSERT_EQ(0, tmp.save_bin(&data, sizeof(data))); + + FILE *fp = fopen(tmp.fname(), "r"); + ASSERT_NE((void*)0, fp); + test_t act_data; + bzero(&act_data, sizeof(act_data)); + ASSERT_EQ((size_t)1, fread(&act_data, sizeof(act_data), 1, fp)); + fclose(fp); + + ASSERT_EQ(0, memcmp(&data, &act_data, sizeof(data))); + + // save twice + test_t data2 = { 89, 1000, {'E', 'C', 'A', 'Z'}}; + ASSERT_EQ(0, tmp.save_bin(&data2, sizeof(data2))); + + fp = fopen(tmp.fname(), "r"); + ASSERT_NE((void*)0, fp); + bzero(&act_data, sizeof(act_data)); + ASSERT_EQ((size_t)1, fread(&act_data, sizeof(act_data), 1, fp)); + fclose(fp); + + ASSERT_EQ(0, memcmp(&data2, &act_data, sizeof(data2))); + +} +} diff --git a/test/test_file_util_linux.cc b/test/test_file_util_linux.cc new file mode 100644 index 0000000..3abb4fe --- /dev/null +++ b/test/test_file_util_linux.cc @@ -0,0 +1,26 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include + +#include "butil/files/file_path.h" +#include "butil/files/scoped_file.h" + +namespace butil { + +bool EvictFileFromSystemCache(const FilePath& file) { + ScopedFD fd(open(file.value().c_str(), O_RDONLY)); + if (!fd.is_valid()) + return false; + if (fdatasync(fd.get()) != 0) + return false; + if (posix_fadvise(fd.get(), 0, 0, POSIX_FADV_DONTNEED) != 0) + return false; + return true; +} + +} // namespace butil diff --git a/test/test_switches.cc b/test/test_switches.cc new file mode 100644 index 0000000..b06da1c --- /dev/null +++ b/test/test_switches.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "test_switches.h" + +// Time (in milliseconds) that the tests should wait before timing out. +// TODO(phajdan.jr): Clean up the switch names. +const char switches::kTestLargeTimeout[] = "test-large-timeout"; + +// Maximum number of tests to run in a single batch. +const char switches::kTestLauncherBatchLimit[] = "test-launcher-batch-limit"; + +// Sets defaults desirable for the continuous integration bots, e.g. parallel +// test execution and test retries. +const char switches::kTestLauncherBotMode[] = + "test-launcher-bot-mode"; + +// Makes it possible to debug the launcher itself. By default the launcher +// automatically switches to single process mode when it detects presence +// of debugger. +const char switches::kTestLauncherDebugLauncher[] = + "test-launcher-debug-launcher"; + +// Path to file containing test filter (one pattern per line). +const char switches::kTestLauncherFilterFile[] = "test-launcher-filter-file"; + +// Number of parallel test launcher jobs. +const char switches::kTestLauncherJobs[] = "test-launcher-jobs"; + +// Path to test results file in our custom test launcher format. +const char switches::kTestLauncherOutput[] = "test-launcher-output"; + +// Maximum number of times to retry a test after failure. +const char switches::kTestLauncherRetryLimit[] = "test-launcher-retry-limit"; + +// Path to test results file with all the info from the test launcher. +const char switches::kTestLauncherSummaryOutput[] = + "test-launcher-summary-output"; + +// Flag controlling when test stdio is displayed as part of the launcher's +// standard output. +const char switches::kTestLauncherPrintTestStdio[] = + "test-launcher-print-test-stdio"; + +// Index of the test shard to run, starting from 0 (first shard) to total shards +// minus one (last shard). +const char switches::kTestLauncherShardIndex[] = + "test-launcher-shard-index"; + +// Total number of shards. Must be the same for all shards. +const char switches::kTestLauncherTotalShards[] = + "test-launcher-total-shards"; + +// Time (in milliseconds) that the tests should wait before timing out. +const char switches::kTestLauncherTimeout[] = "test-launcher-timeout"; +// TODO(phajdan.jr): Clean up the switch names. +const char switches::kTestTinyTimeout[] = "test-tiny-timeout"; +const char switches::kUiTestActionTimeout[] = "ui-test-action-timeout"; +const char switches::kUiTestActionMaxTimeout[] = "ui-test-action-max-timeout"; diff --git a/test/test_switches.h b/test/test_switches.h new file mode 100644 index 0000000..d7dfc83 --- /dev/null +++ b/test/test_switches.h @@ -0,0 +1,31 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_TEST_TEST_SWITCHES_H_ +#define BUTIL_TEST_TEST_SWITCHES_H_ + +namespace switches { + +// All switches in alphabetical order. The switches should be documented +// alongside the definition of their values in the .cc file. +extern const char kTestLargeTimeout[]; +extern const char kTestLauncherBatchLimit[]; +extern const char kTestLauncherBotMode[]; +extern const char kTestLauncherDebugLauncher[]; +extern const char kTestLauncherFilterFile[]; +extern const char kTestLauncherJobs[]; +extern const char kTestLauncherOutput[]; +extern const char kTestLauncherRetryLimit[]; +extern const char kTestLauncherSummaryOutput[]; +extern const char kTestLauncherPrintTestStdio[]; +extern const char kTestLauncherShardIndex[]; +extern const char kTestLauncherTotalShards[]; +extern const char kTestLauncherTimeout[]; +extern const char kTestTinyTimeout[]; +extern const char kUiTestActionTimeout[]; +extern const char kUiTestActionMaxTimeout[]; + +} // namespace switches + +#endif // BUTIL_TEST_TEST_SWITCHES_H_ diff --git a/test/thread_checker_unittest.cc b/test/thread_checker_unittest.cc new file mode 100644 index 0000000..3fb6f4e --- /dev/null +++ b/test/thread_checker_unittest.cc @@ -0,0 +1,183 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/basictypes.h" +#include "butil/logging.h" +#include "butil/memory/scoped_ptr.h" +#include "butil/threading/thread_checker.h" +#include "butil/threading/simple_thread.h" +#include + +// Duplicated from butil/threading/thread_checker.h so that we can be +// good citizens there and undef the macro. +#if !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON) +#define ENABLE_THREAD_CHECKER 1 +#else +#define ENABLE_THREAD_CHECKER 0 +#endif + +namespace butil { + +namespace { + +// Simple class to exercise the basics of ThreadChecker. +// Both the destructor and DoStuff should verify that they were +// called on the same thread as the constructor. +class ThreadCheckerClass : public ThreadChecker { + public: + ThreadCheckerClass() {} + + // Verifies that it was called on the same thread as the constructor. + void DoStuff() { + DCHECK(CalledOnValidThread()); + } + + void DetachFromThread() { + ThreadChecker::DetachFromThread(); + } + + static void MethodOnDifferentThreadImpl(); + static void DetachThenCallFromDifferentThreadImpl(); + + private: + DISALLOW_COPY_AND_ASSIGN(ThreadCheckerClass); +}; + +// Calls ThreadCheckerClass::DoStuff on another thread. +class CallDoStuffOnThread : public butil::SimpleThread { + public: + explicit CallDoStuffOnThread(ThreadCheckerClass* thread_checker_class) + : SimpleThread("call_do_stuff_on_thread"), + thread_checker_class_(thread_checker_class) { + } + + virtual void Run() OVERRIDE { + thread_checker_class_->DoStuff(); + } + + private: + ThreadCheckerClass* thread_checker_class_; + + DISALLOW_COPY_AND_ASSIGN(CallDoStuffOnThread); +}; + +// Deletes ThreadCheckerClass on a different thread. +class DeleteThreadCheckerClassOnThread : public butil::SimpleThread { + public: + explicit DeleteThreadCheckerClassOnThread( + ThreadCheckerClass* thread_checker_class) + : SimpleThread("delete_thread_checker_class_on_thread"), + thread_checker_class_(thread_checker_class) { + } + + virtual void Run() OVERRIDE { + thread_checker_class_.reset(); + } + + private: + scoped_ptr thread_checker_class_; + + DISALLOW_COPY_AND_ASSIGN(DeleteThreadCheckerClassOnThread); +}; + +} // namespace + +TEST(ThreadCheckerTest, CallsAllowedOnSameThread) { + scoped_ptr thread_checker_class( + new ThreadCheckerClass); + + // Verify that DoStuff doesn't assert. + thread_checker_class->DoStuff(); + + // Verify that the destructor doesn't assert. + thread_checker_class.reset(); +} + +TEST(ThreadCheckerTest, DestructorAllowedOnDifferentThread) { + scoped_ptr thread_checker_class( + new ThreadCheckerClass); + + // Verify that the destructor doesn't assert + // when called on a different thread. + DeleteThreadCheckerClassOnThread delete_on_thread( + thread_checker_class.release()); + + delete_on_thread.Start(); + delete_on_thread.Join(); +} + +TEST(ThreadCheckerTest, DetachFromThread) { + scoped_ptr thread_checker_class( + new ThreadCheckerClass); + + // Verify that DoStuff doesn't assert when called on a different thread after + // a call to DetachFromThread. + thread_checker_class->DetachFromThread(); + CallDoStuffOnThread call_on_thread(thread_checker_class.get()); + + call_on_thread.Start(); + call_on_thread.Join(); +} + +#if GTEST_HAS_DEATH_TEST || !ENABLE_THREAD_CHECKER + +void ThreadCheckerClass::MethodOnDifferentThreadImpl() { + scoped_ptr thread_checker_class( + new ThreadCheckerClass); + + // DoStuff should assert in debug builds only when called on a + // different thread. + CallDoStuffOnThread call_on_thread(thread_checker_class.get()); + + call_on_thread.Start(); + call_on_thread.Join(); +} + +#if ENABLE_THREAD_CHECKER +TEST(ThreadCheckerDeathTest, MethodNotAllowedOnDifferentThreadInDebug) { + ASSERT_DEATH({ + ThreadCheckerClass::MethodOnDifferentThreadImpl(); + }, ""); +} +#else +TEST(ThreadCheckerTest, MethodAllowedOnDifferentThreadInRelease) { + ThreadCheckerClass::MethodOnDifferentThreadImpl(); +} +#endif // ENABLE_THREAD_CHECKER + +void ThreadCheckerClass::DetachThenCallFromDifferentThreadImpl() { + scoped_ptr thread_checker_class( + new ThreadCheckerClass); + + // DoStuff doesn't assert when called on a different thread + // after a call to DetachFromThread. + thread_checker_class->DetachFromThread(); + CallDoStuffOnThread call_on_thread(thread_checker_class.get()); + + call_on_thread.Start(); + call_on_thread.Join(); + + // DoStuff should assert in debug builds only after moving to + // another thread. + thread_checker_class->DoStuff(); +} + +#if ENABLE_THREAD_CHECKER +TEST(ThreadCheckerDeathTest, DetachFromThreadInDebug) { + ASSERT_DEATH({ + ThreadCheckerClass::DetachThenCallFromDifferentThreadImpl(); + }, ""); +} +#else +TEST(ThreadCheckerTest, DetachFromThreadInRelease) { + ThreadCheckerClass::DetachThenCallFromDifferentThreadImpl(); +} +#endif // ENABLE_THREAD_CHECKER + +#endif // GTEST_HAS_DEATH_TEST || !ENABLE_THREAD_CHECKER + +// Just in case we ever get lumped together with other compilation units. +#undef ENABLE_THREAD_CHECKER + +} // namespace butil diff --git a/test/thread_collision_warner_unittest.cc b/test/thread_collision_warner_unittest.cc new file mode 100644 index 0000000..7fce025 --- /dev/null +++ b/test/thread_collision_warner_unittest.cc @@ -0,0 +1,385 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/compiler_specific.h" +#include "butil/memory/scoped_ptr.h" +#include "butil/synchronization/lock.h" +#include "butil/threading/platform_thread.h" +#include "butil/threading/simple_thread.h" +#include "butil/threading/thread_collision_warner.h" +#include + +// '' : local class member function does not have a body +MSVC_PUSH_DISABLE_WARNING(4822) + + +#if defined(NDEBUG) + +// Would cause a memory leak otherwise. +#undef DFAKE_MUTEX +#define DFAKE_MUTEX(obj) scoped_ptr obj + +// In Release, we expect the AsserterBase::warn() to not happen. +#define EXPECT_NDEBUG_FALSE_DEBUG_TRUE EXPECT_FALSE + +#else + +// In Debug, we expect the AsserterBase::warn() to happen. +#define EXPECT_NDEBUG_FALSE_DEBUG_TRUE EXPECT_TRUE + +#endif + + +namespace { + +// This is the asserter used with ThreadCollisionWarner instead of the default +// DCheckAsserter. The method fail_state is used to know if a collision took +// place. +class AssertReporter : public butil::AsserterBase { + public: + AssertReporter() + : failed_(false) {} + + virtual void warn() OVERRIDE { + failed_ = true; + } + + virtual ~AssertReporter() {} + + bool fail_state() const { return failed_; } + void reset() { failed_ = false; } + + private: + bool failed_; +}; + +} // namespace + +TEST(ThreadCollisionTest, BookCriticalSection) { + AssertReporter* local_reporter = new AssertReporter(); + + butil::ThreadCollisionWarner warner(local_reporter); + EXPECT_FALSE(local_reporter->fail_state()); + + { // Pin section. + DFAKE_SCOPED_LOCK_THREAD_LOCKED(warner); + EXPECT_FALSE(local_reporter->fail_state()); + { // Pin section. + DFAKE_SCOPED_LOCK_THREAD_LOCKED(warner); + EXPECT_FALSE(local_reporter->fail_state()); + } + } +} + +TEST(ThreadCollisionTest, ScopedRecursiveBookCriticalSection) { + AssertReporter* local_reporter = new AssertReporter(); + + butil::ThreadCollisionWarner warner(local_reporter); + EXPECT_FALSE(local_reporter->fail_state()); + + { // Pin section. + DFAKE_SCOPED_RECURSIVE_LOCK(warner); + EXPECT_FALSE(local_reporter->fail_state()); + { // Pin section again (allowed by DFAKE_SCOPED_RECURSIVE_LOCK) + DFAKE_SCOPED_RECURSIVE_LOCK(warner); + EXPECT_FALSE(local_reporter->fail_state()); + } // Unpin section. + } // Unpin section. + + // Check that section is not pinned + { // Pin section. + DFAKE_SCOPED_LOCK(warner); + EXPECT_FALSE(local_reporter->fail_state()); + } // Unpin section. +} + +TEST(ThreadCollisionTest, ScopedBookCriticalSection) { + AssertReporter* local_reporter = new AssertReporter(); + + butil::ThreadCollisionWarner warner(local_reporter); + EXPECT_FALSE(local_reporter->fail_state()); + + { // Pin section. + DFAKE_SCOPED_LOCK(warner); + EXPECT_FALSE(local_reporter->fail_state()); + } // Unpin section. + + { // Pin section. + DFAKE_SCOPED_LOCK(warner); + EXPECT_FALSE(local_reporter->fail_state()); + { + // Pin section again (not allowed by DFAKE_SCOPED_LOCK) + DFAKE_SCOPED_LOCK(warner); + EXPECT_NDEBUG_FALSE_DEBUG_TRUE(local_reporter->fail_state()); + // Reset the status of warner for further tests. + local_reporter->reset(); + } // Unpin section. + } // Unpin section. + + { + // Pin section. + DFAKE_SCOPED_LOCK(warner); + EXPECT_FALSE(local_reporter->fail_state()); + } // Unpin section. +} + +TEST(ThreadCollisionTest, MTBookCriticalSectionTest) { + class NonThreadSafeQueue { + public: + explicit NonThreadSafeQueue(butil::AsserterBase* asserter) + : push_pop_(asserter) { + } + + void push(int value) { + DFAKE_SCOPED_LOCK_THREAD_LOCKED(push_pop_); + } + + int pop() { + DFAKE_SCOPED_LOCK_THREAD_LOCKED(push_pop_); + return 0; + } + + private: + DFAKE_MUTEX(push_pop_); + + DISALLOW_COPY_AND_ASSIGN(NonThreadSafeQueue); + }; + + class QueueUser : public butil::DelegateSimpleThread::Delegate { + public: + explicit QueueUser(NonThreadSafeQueue& queue) + : queue_(queue) {} + + virtual void Run() OVERRIDE { + queue_.push(0); + queue_.pop(); + } + + private: + NonThreadSafeQueue& queue_; + }; + + AssertReporter* local_reporter = new AssertReporter(); + + NonThreadSafeQueue queue(local_reporter); + + QueueUser queue_user_a(queue); + QueueUser queue_user_b(queue); + + butil::DelegateSimpleThread thread_a(&queue_user_a, "queue_user_thread_a"); + butil::DelegateSimpleThread thread_b(&queue_user_b, "queue_user_thread_b"); + + thread_a.Start(); + thread_b.Start(); + + thread_a.Join(); + thread_b.Join(); + + EXPECT_NDEBUG_FALSE_DEBUG_TRUE(local_reporter->fail_state()); +} + +TEST(ThreadCollisionTest, MTScopedBookCriticalSectionTest) { + // Queue with a 5 seconds push execution time, hopefuly the two used threads + // in the test will enter the push at same time. + class NonThreadSafeQueue { + public: + explicit NonThreadSafeQueue(butil::AsserterBase* asserter) + : push_pop_(asserter) { + } + + void push(int value) { + DFAKE_SCOPED_LOCK(push_pop_); + butil::PlatformThread::Sleep(butil::TimeDelta::FromSeconds(5)); + } + + int pop() { + DFAKE_SCOPED_LOCK(push_pop_); + return 0; + } + + private: + DFAKE_MUTEX(push_pop_); + + DISALLOW_COPY_AND_ASSIGN(NonThreadSafeQueue); + }; + + class QueueUser : public butil::DelegateSimpleThread::Delegate { + public: + explicit QueueUser(NonThreadSafeQueue& queue) + : queue_(queue) {} + + virtual void Run() OVERRIDE { + queue_.push(0); + queue_.pop(); + } + + private: + NonThreadSafeQueue& queue_; + }; + + AssertReporter* local_reporter = new AssertReporter(); + + NonThreadSafeQueue queue(local_reporter); + + QueueUser queue_user_a(queue); + QueueUser queue_user_b(queue); + + butil::DelegateSimpleThread thread_a(&queue_user_a, "queue_user_thread_a"); + butil::DelegateSimpleThread thread_b(&queue_user_b, "queue_user_thread_b"); + + thread_a.Start(); + thread_b.Start(); + + thread_a.Join(); + thread_b.Join(); + + EXPECT_NDEBUG_FALSE_DEBUG_TRUE(local_reporter->fail_state()); +} + +TEST(ThreadCollisionTest, MTSynchedScopedBookCriticalSectionTest) { + // Queue with a 2 seconds push execution time, hopefuly the two used threads + // in the test will enter the push at same time. + class NonThreadSafeQueue { + public: + explicit NonThreadSafeQueue(butil::AsserterBase* asserter) + : push_pop_(asserter) { + } + + void push(int value) { + DFAKE_SCOPED_LOCK(push_pop_); + butil::PlatformThread::Sleep(butil::TimeDelta::FromSeconds(2)); + } + + int pop() { + DFAKE_SCOPED_LOCK(push_pop_); + return 0; + } + + private: + DFAKE_MUTEX(push_pop_); + + DISALLOW_COPY_AND_ASSIGN(NonThreadSafeQueue); + }; + + // This time the QueueUser class protects the non thread safe queue with + // a lock. + class QueueUser : public butil::DelegateSimpleThread::Delegate { + public: + QueueUser(NonThreadSafeQueue& queue, butil::Lock& lock) + : queue_(queue), + lock_(lock) {} + + virtual void Run() OVERRIDE { + { + butil::AutoLock auto_lock(lock_); + queue_.push(0); + } + { + butil::AutoLock auto_lock(lock_); + queue_.pop(); + } + } + private: + NonThreadSafeQueue& queue_; + butil::Lock& lock_; + }; + + AssertReporter* local_reporter = new AssertReporter(); + + NonThreadSafeQueue queue(local_reporter); + + butil::Lock lock; + + QueueUser queue_user_a(queue, lock); + QueueUser queue_user_b(queue, lock); + + butil::DelegateSimpleThread thread_a(&queue_user_a, "queue_user_thread_a"); + butil::DelegateSimpleThread thread_b(&queue_user_b, "queue_user_thread_b"); + + thread_a.Start(); + thread_b.Start(); + + thread_a.Join(); + thread_b.Join(); + + EXPECT_FALSE(local_reporter->fail_state()); +} + +TEST(ThreadCollisionTest, MTSynchedScopedRecursiveBookCriticalSectionTest) { + // Queue with a 2 seconds push execution time, hopefuly the two used threads + // in the test will enter the push at same time. + class NonThreadSafeQueue { + public: + explicit NonThreadSafeQueue(butil::AsserterBase* asserter) + : push_pop_(asserter) { + } + + void push(int) { + DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_); + bar(); + butil::PlatformThread::Sleep(butil::TimeDelta::FromSeconds(2)); + } + + int pop() { + DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_); + return 0; + } + + void bar() { + DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_); + } + + private: + DFAKE_MUTEX(push_pop_); + + DISALLOW_COPY_AND_ASSIGN(NonThreadSafeQueue); + }; + + // This time the QueueUser class protects the non thread safe queue with + // a lock. + class QueueUser : public butil::DelegateSimpleThread::Delegate { + public: + QueueUser(NonThreadSafeQueue& queue, butil::Lock& lock) + : queue_(queue), + lock_(lock) {} + + virtual void Run() OVERRIDE { + { + butil::AutoLock auto_lock(lock_); + queue_.push(0); + } + { + butil::AutoLock auto_lock(lock_); + queue_.bar(); + } + { + butil::AutoLock auto_lock(lock_); + queue_.pop(); + } + } + private: + NonThreadSafeQueue& queue_; + butil::Lock& lock_; + }; + + AssertReporter* local_reporter = new AssertReporter(); + + NonThreadSafeQueue queue(local_reporter); + + butil::Lock lock; + + QueueUser queue_user_a(queue, lock); + QueueUser queue_user_b(queue, lock); + + butil::DelegateSimpleThread thread_a(&queue_user_a, "queue_user_thread_a"); + butil::DelegateSimpleThread thread_b(&queue_user_b, "queue_user_thread_b"); + + thread_a.Start(); + thread_b.Start(); + + thread_a.Join(); + thread_b.Join(); + + EXPECT_FALSE(local_reporter->fail_state()); +} diff --git a/test/thread_id_name_manager_unittest.cc b/test/thread_id_name_manager_unittest.cc new file mode 100644 index 0000000..1c63a22 --- /dev/null +++ b/test/thread_id_name_manager_unittest.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/threading/thread_id_name_manager.h" + +#include "butil/threading/platform_thread.h" +#include + +typedef testing::Test ThreadIdNameManagerTest; + +namespace { + +TEST_F(ThreadIdNameManagerTest, ThreadNameInterning) { + butil::ThreadIdNameManager* manager = butil::ThreadIdNameManager::GetInstance(); + + butil::PlatformThreadId a_id = butil::PlatformThread::CurrentId(); + butil::PlatformThread::SetName("First Name"); + std::string version = manager->GetName(a_id); + + butil::PlatformThread::SetName("New name"); + EXPECT_NE(version, manager->GetName(a_id)); + butil::PlatformThread::SetName(""); +} + +TEST_F(ThreadIdNameManagerTest, ResettingNameKeepsCorrectInternedValue) { + butil::ThreadIdNameManager* manager = butil::ThreadIdNameManager::GetInstance(); + + butil::PlatformThreadId a_id = butil::PlatformThread::CurrentId(); + butil::PlatformThread::SetName("Test Name"); + std::string version = manager->GetName(a_id); + + butil::PlatformThread::SetName("New name"); + EXPECT_NE(version, manager->GetName(a_id)); + + butil::PlatformThread::SetName("Test Name"); + EXPECT_EQ(version, manager->GetName(a_id)); + + butil::PlatformThread::SetName(""); +} + +} // namespace diff --git a/test/thread_key_unittest.cpp b/test/thread_key_unittest.cpp new file mode 100644 index 0000000..06cbaba --- /dev/null +++ b/test/thread_key_unittest.cpp @@ -0,0 +1,513 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include + +#include "butil/thread_key.h" +#include "butil/fast_rand.h" +#include "bthread/bthread.h" + +namespace butil { +namespace { + +//pthread_key_xxx implication without num limit... +//user promise no setspecific/getspecific called in calling thread_key_delete(). +// Check whether an entry is unused. +#define KEY_UNUSED(p) (((p) & 1) == 0) + +// Check whether a key is usable. We cannot reuse an allocated key if +// the sequence counter would overflow after the next destroy call. +// This would mean that we potentially free memory for a key with the +// same sequence. This is *very* unlikely to happen, A program would +// have to create and destroy a key 2^31 times. If it should happen we +// simply don't use this specific key anymore. +#define KEY_USABLE(p) (((size_t) (p)) < ((size_t) ((p) + 2))) + +bool g_started = false; +bool g_stopped = false; + +struct ThreadKeyInfo { + uint32_t id; + uint32_t seq; +}; + +struct ThreadKeyData { + int a{0}; +}; + +TEST(ThreadLocalTest, sanity) { + { + ThreadKey key; + for (int i = 0; i < 5; ++i) { + std::unique_ptr data(new int(1)); + int *raw_data = data.get(); + ASSERT_EQ(0, butil::thread_key_create(key, NULL)); + + ASSERT_EQ(NULL, butil::thread_getspecific(key)); + ASSERT_EQ(0, butil::thread_setspecific(key, (void *)raw_data)); + ASSERT_EQ(raw_data, butil::thread_getspecific(key)); + + ASSERT_EQ(0, butil::thread_key_delete(key)); + ASSERT_EQ(NULL, butil::thread_getspecific(key)); + ASSERT_NE(0, butil::thread_setspecific(key, (void *)raw_data)); + } + } + + for (int i = 0; i < 5; ++i) { + ThreadLocal tl; + ASSERT_TRUE(tl.get()); + ASSERT_EQ(tl->a, 0); + auto data = new ThreadKeyData; + data->a = 1; + tl.reset(data); // tl owns data + ASSERT_EQ(data, tl.get()); + ASSERT_EQ((*tl).a, 1); + tl.reset(); // data has been deleted + ASSERT_TRUE(tl.get()); + } +} + +TEST(ThreadLocalTest, thread_key_seq) { + std::vector seqs; + std::vector keys; + for (int i = 0; i < 10000; ++i) { + bool create = fast_rand_less_than(2); + uint64_t num = fast_rand_less_than(5); + if (keys.empty() || create) { + for (uint64_t j = 0; j < num; ++j) { + keys.emplace_back(); + ASSERT_EQ(0, butil::thread_key_create(keys.back(), NULL)); + ASSERT_TRUE(!KEY_UNUSED(keys.back()._seq)); + if (keys.back()._id >= seqs.size()) { + seqs.resize(keys.back()._id + 1); + } else { + ASSERT_EQ(seqs[keys.back()._id] + 2, keys.back()._seq); + } + seqs[keys.back()._id] = keys.back()._seq; + } + } else { + for (uint64_t j = 0; j < num && !keys.empty(); ++j) { + uint64_t index = fast_rand_less_than(keys.size()); + ASSERT_TRUE(!KEY_UNUSED(seqs[keys[index]._id])); + ASSERT_EQ(0, butil::thread_key_delete(keys[index])); + keys.erase(keys.begin() + index); + } + } + } +} + +void* THreadKeyCreateAndDeleteFunc(void*) { + while (!g_stopped) { + ThreadKey key; + EXPECT_EQ(0, butil::thread_key_create(key, NULL)); + EXPECT_TRUE(!KEY_UNUSED(key._seq)); + EXPECT_EQ(0, butil::thread_key_delete(key)); + } + return NULL; +} + +TEST(ThreadLocalTest, thread_key_create_and_delete) { + LOG(INFO) << "numeric_limits::max()=" << std::numeric_limits::max(); + g_stopped = false; + const int thread_num = 8; + pthread_t threads[thread_num]; + for (int i = 0; i < thread_num; ++i) { + ASSERT_EQ(0, pthread_create(&threads[i], NULL, THreadKeyCreateAndDeleteFunc, NULL)); + } + sleep(2); + g_stopped = true; + for (const auto& thread : threads) { + pthread_join(thread, NULL); + } +} + +void* ThreadLocalFunc(void* arg) { + auto thread_locals = (std::vector*>*)arg; + std::vector expects(thread_locals->size(), 0); + for (auto tl : *thread_locals) { + EXPECT_TRUE(tl->get() != NULL); + *(tl->get()) = 0; + } + while (!g_stopped) { + uint64_t index = + fast_rand_less_than(thread_locals->size()); + EXPECT_TRUE((*thread_locals)[index]->get() != NULL); + EXPECT_EQ(*((*thread_locals)[index]->get()), expects[index]); + ++(*((*thread_locals)[index]->get())); + ++expects[index]; + bthread_usleep(10); + } + return NULL; +} + +TEST(ThreadLocalTest, thread_local_multi_thread) { + g_stopped = false; + int thread_local_num = 20480; + std::vector*> args(thread_local_num, NULL); + for (int i = 0; i < thread_local_num; ++i) { + args[i] = new ThreadLocal(); + ASSERT_TRUE(args[i]->get() != NULL); + } + const int thread_num = 8; + pthread_t threads[thread_num]; + for (int i = 0; i < thread_num; ++i) { + ASSERT_EQ(0, pthread_create(&threads[i], NULL, ThreadLocalFunc, &args)); + } + + sleep(2); + g_stopped = true; + for (const auto& thread : threads) { + pthread_join(thread, NULL); + } + for (auto tl : args) { + delete tl; + } +} + +butil::atomic g_counter(0); + +void* ThreadLocalForEachFunc(void* arg) { + auto counter = static_cast>*>(arg); + auto local_counter = counter->get(); + EXPECT_NE(nullptr, local_counter); + local_counter->store(0, butil::memory_order_relaxed); + while (!g_stopped) { + local_counter->fetch_add(1, butil::memory_order_relaxed); + g_counter.fetch_add(1, butil::memory_order_relaxed); + if (butil::fast_rand_less_than(100) + 1 > 80) { + local_counter = new butil::atomic( + local_counter->load(butil::memory_order_relaxed)); + counter->reset(local_counter); + } + } + return NULL; +} + +TEST(ThreadLocalTest, thread_local_for_each) { + g_stopped = false; + ThreadLocal> counter(false); + const int thread_num = 8; + pthread_t threads[thread_num]; + for (int i = 0; i < thread_num; ++i) { + ASSERT_EQ(0, pthread_create( + &threads[i], NULL, ThreadLocalForEachFunc, &counter)); + } + + sleep(2); + g_stopped = true; + for (const auto& thread : threads) { + pthread_join(thread, NULL); + } + int count = 0; + counter.for_each([&count](butil::atomic* c) { + count += c->load(butil::memory_order_relaxed); + }); + ASSERT_EQ(count, g_counter.load(butil::memory_order_relaxed)); +} + +struct BAIDU_CACHELINE_ALIGNMENT ThreadKeyArg { + std::vector thread_keys; + bool ready_delete = false; +}; + +bool g_deleted = false; +void* ThreadKeyFunc(void* arg) { + auto thread_key_arg = (ThreadKeyArg*)arg; + auto thread_keys = thread_key_arg->thread_keys; + std::vector expects(thread_keys.size(), 0); + std::vector> owned_data; + owned_data.reserve(thread_keys.size()); + for (auto key : thread_keys) { + EXPECT_TRUE(butil::thread_getspecific(*key) == NULL); + owned_data.emplace_back(new int(0)); + EXPECT_EQ(0, butil::thread_setspecific(*key, owned_data.back().get())); + EXPECT_EQ(*(static_cast(butil::thread_getspecific(*key))), 0); + } + while (!g_stopped) { + uint64_t index = + fast_rand_less_than(thread_keys.size()); + auto data = static_cast(butil::thread_getspecific(*thread_keys[index])); + EXPECT_TRUE(data != NULL); + EXPECT_EQ(*data, expects[index]); + ++(*data); + ++expects[index]; + bthread_usleep(10); + } + + thread_key_arg->ready_delete = true; + while (!g_deleted) { + bthread_usleep(10); + } + + for (auto key : thread_keys) { + EXPECT_TRUE(butil::thread_getspecific(*key) == NULL) + << butil::thread_getspecific(*key); + } + return NULL; +} + +TEST(ThreadLocalTest, thread_key_multi_thread) { + g_stopped = false; + g_deleted = false; + std::vector thread_keys; + std::vector> owned_data; + int key_num = 20480; + owned_data.reserve(key_num); + for (int i = 0; i < key_num; ++i) { + thread_keys.push_back(new ThreadKey()); + ASSERT_EQ(0, butil::thread_key_create(*thread_keys.back(), [](void* data) { + delete static_cast(data); + })); + ASSERT_TRUE(butil::thread_getspecific(*thread_keys.back()) == NULL); + owned_data.emplace_back(new int(0)); + ASSERT_EQ(0, butil::thread_setspecific(*thread_keys.back(), owned_data.back().get())); + ASSERT_EQ(*(static_cast(butil::thread_getspecific(*thread_keys.back()))), 0); + } + const int thread_num = 8; + std::vector args(thread_num); + pthread_t threads[thread_num]; + for (int i = 0; i < thread_num; ++i) { + args[i].thread_keys = thread_keys; + ASSERT_EQ(0, pthread_create(&threads[i], NULL, ThreadKeyFunc, &args[i])); + } + + sleep(5); + g_stopped = true; + while (true) { + bool all_ready = true; + for (int i = 0; i < thread_num; ++i) { + if (!args[i].ready_delete) { + all_ready = false; + break; + } + } + if (all_ready) { + break; + } + usleep(1000); + } + for (auto key : thread_keys) { + ASSERT_EQ(0, butil::thread_key_delete(*key)); + ASSERT_TRUE(butil::thread_getspecific(*key) == NULL); + } + g_deleted = true; + + for (const auto& thread : threads) { + ASSERT_EQ(0, pthread_join(thread, NULL)); + } + for (auto key : thread_keys) { + delete key; + } +} + +struct BAIDU_CACHELINE_ALIGNMENT ThreadKeyPerfArgs { + pthread_key_t pthread_key; + ThreadKey* thread_key; + bool is_pthread_key; + int64_t counter; + int64_t elapse_ns; + bool ready; + + ThreadKeyPerfArgs() + : thread_key(NULL) + , is_pthread_key(true) + , counter(0) + , elapse_ns(0) + , ready(false) {} +}; + +void* ThreadKeyPerfFunc(void* void_arg) { + auto args = (ThreadKeyPerfArgs*)void_arg; + args->ready = true; + std::unique_ptr data(new int(1)); + if (args->is_pthread_key) { + pthread_setspecific(args->pthread_key, (void*)data.get()); + } else { + butil::thread_setspecific(*args->thread_key, (void*)data.get()); + } + butil::Timer t; + while (!g_stopped) { + if (g_started) { + break; + } + bthread_usleep(10); + } + t.start(); + while (!g_stopped) { + if (args->is_pthread_key) { + pthread_getspecific(args->pthread_key); + } else { + butil::thread_getspecific(*args->thread_key); + } + ++args->counter; + } + t.stop(); + args->elapse_ns = t.n_elapsed(); + return NULL; +} + + +void ThreadKeyPerfTest(int thread_num, bool test_pthread_key) { + g_started = false; + g_stopped = false; + pthread_key_t pthread_key; + butil::ThreadKey thread_key; + if (test_pthread_key) { + ASSERT_EQ(0, pthread_key_create(&pthread_key, NULL)); + } else { + ASSERT_EQ(0, butil::thread_key_create(thread_key, NULL)); + } + pthread_t threads[thread_num]; + std::vector args(thread_num); + for (int i = 0; i < thread_num; ++i) { + if (test_pthread_key) { + args[i].pthread_key = pthread_key; + args[i].is_pthread_key = true; + } else { + args[i].thread_key = &thread_key; + args[i].is_pthread_key = false; + } + ASSERT_EQ(0, pthread_create(&threads[i], NULL, ThreadKeyPerfFunc, &args[i])); + } + while (true) { + bool all_ready = true; + for (int i = 0; i < thread_num; ++i) { + if (!args[i].ready) { + all_ready = false; + break; + } + } + if (all_ready) { + break; + } + usleep(1000); + } + g_started = true; + int64_t run_ms = 5 * 1000; + usleep(run_ms * 1000); + g_stopped = true; + int64_t wait_time = 0; + int64_t count = 0; + for (int i = 0; i < thread_num; ++i) { + pthread_join(threads[i], NULL); + wait_time += args[i].elapse_ns; + count += args[i].counter; + } + if (test_pthread_key) { + ASSERT_EQ(0, pthread_key_delete(pthread_key)); + } else { + ASSERT_EQ(0, butil::thread_key_delete(thread_key)); + } + LOG(INFO) << (test_pthread_key ? "pthread_key" : "thread_key") + << " thread_num=" << thread_num + << " count=" << count + << " average_time=" << wait_time / (double)count; +} + +struct BAIDU_CACHELINE_ALIGNMENT ThreadLocalPerfArgs { + ThreadLocal* tl; + int64_t counter; + int64_t elapse_ns; + bool ready; + + ThreadLocalPerfArgs() + : tl(NULL) , counter(0) + , elapse_ns(0) , ready(false) {} +}; + +void* ThreadLocalPerfFunc(void* void_arg) { + auto args = (ThreadLocalPerfArgs*)void_arg; + args->ready = true; + EXPECT_TRUE(args->tl->get() != NULL); + butil::Timer t; + while (!g_stopped) { + if (g_started) { + break; + } + bthread_usleep(10); + } + t.start(); + while (!g_stopped) { + args->tl->get(); + ++args->counter; + } + t.stop(); + args->elapse_ns = t.n_elapsed(); + return NULL; +} + +void ThreadLocalPerfTest(int thread_num) { + g_started = false; + g_stopped = false; + ThreadLocal tl; + pthread_t threads[thread_num]; + std::vector args(thread_num); + for (int i = 0; i < thread_num; ++i) { + args[i].tl = &tl; + ASSERT_EQ(0, pthread_create(&threads[i], NULL, ThreadLocalPerfFunc, &args[i])); + } + while (true) { + bool all_ready = true; + for (int i = 0; i < thread_num; ++i) { + if (!args[i].ready) { + all_ready = false; + break; + } + } + if (all_ready) { + break; + } + usleep(1000); + } + g_started = true; + int64_t run_ms = 5 * 1000; + usleep(run_ms * 1000); + g_stopped = true; + int64_t wait_time = 0; + int64_t count = 0; + for (int i = 0; i < thread_num; ++i) { + pthread_join(threads[i], NULL); + wait_time += args[i].elapse_ns; + count += args[i].counter; + } + LOG(INFO) << "ThreadLocal thread_num=" << thread_num + << " count=" << count + << " average_time=" << wait_time / (double)count; +} + +TEST(ThreadLocalTest, thread_key_performance) { + int thread_num = 1; + ThreadKeyPerfTest(thread_num, true); + ThreadKeyPerfTest(thread_num, false); + ThreadLocalPerfTest(thread_num); + + thread_num = 4; + ThreadKeyPerfTest(thread_num, true); + ThreadKeyPerfTest(thread_num, false); + ThreadLocalPerfTest(thread_num); + + thread_num = 8; + ThreadKeyPerfTest(thread_num, true); + ThreadKeyPerfTest(thread_num, false); + ThreadLocalPerfTest(thread_num); + +} + +} +} // namespace butil diff --git a/test/thread_local_storage_unittest.cc b/test/thread_local_storage_unittest.cc new file mode 100644 index 0000000..a9a5348 --- /dev/null +++ b/test/thread_local_storage_unittest.cc @@ -0,0 +1,124 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#if defined(OS_WIN) +#include +#include +#endif + +#include "butil/threading/simple_thread.h" +#include "butil/threading/thread_local_storage.h" +#include + +#if defined(OS_WIN) +// Ignore warnings about ptr->int conversions that we use when +// storing ints into ThreadLocalStorage. +#pragma warning(disable : 4311 4312) +#endif + +namespace butil { + +namespace { + +const int kInitialTlsValue = 0x5555; +const int kFinalTlsValue = 0x7777; +// How many times must a destructor be called before we really are done. +const int kNumberDestructorCallRepetitions = 3; + +static ThreadLocalStorage::StaticSlot tls_slot = TLS_INITIALIZER; + +class ThreadLocalStorageRunner : public DelegateSimpleThread::Delegate { + public: + explicit ThreadLocalStorageRunner(int* tls_value_ptr) + : tls_value_ptr_(tls_value_ptr) {} + + virtual ~ThreadLocalStorageRunner() {} + + virtual void Run() OVERRIDE { + *tls_value_ptr_ = kInitialTlsValue; + tls_slot.Set(tls_value_ptr_); + + int *ptr = static_cast(tls_slot.Get()); + EXPECT_EQ(ptr, tls_value_ptr_); + EXPECT_EQ(*ptr, kInitialTlsValue); + *tls_value_ptr_ = 0; + + ptr = static_cast(tls_slot.Get()); + EXPECT_EQ(ptr, tls_value_ptr_); + EXPECT_EQ(*ptr, 0); + + *ptr = kFinalTlsValue + kNumberDestructorCallRepetitions; + } + + private: + int* tls_value_ptr_; + DISALLOW_COPY_AND_ASSIGN(ThreadLocalStorageRunner); +}; + + +void ThreadLocalStorageCleanup(void *value) { + int *ptr = reinterpret_cast(value); + // Destructors should never be called with a NULL. + ASSERT_NE(reinterpret_cast(NULL), ptr); + if (*ptr == kFinalTlsValue) + return; // We've been called enough times. + ASSERT_LT(kFinalTlsValue, *ptr); + ASSERT_GE(kFinalTlsValue + kNumberDestructorCallRepetitions, *ptr); + --*ptr; // Move closer to our target. + // Tell tls that we're not done with this thread, and still need destruction. + tls_slot.Set(value); +} + +} // namespace + +TEST(ThreadLocalStorageTest, Basics) { + ThreadLocalStorage::Slot slot; + slot.Set(reinterpret_cast(123)); + int value = reinterpret_cast(slot.Get()); + EXPECT_EQ(value, 123); +} + +#if defined(THREAD_SANITIZER) +// Do not run the test under ThreadSanitizer. Because this test iterates its +// own TSD destructor for the maximum possible number of times, TSan can't jump +// in after the last destructor invocation, therefore the destructor remains +// unsynchronized with the following users of the same TSD slot. This results +// in race reports between the destructor and functions in other tests. +#define MAYBE_TLSDestructors DISABLED_TLSDestructors +#else +#define MAYBE_TLSDestructors TLSDestructors +#endif +TEST(ThreadLocalStorageTest, MAYBE_TLSDestructors) { + // Create a TLS index with a destructor. Create a set of + // threads that set the TLS, while the destructor cleans it up. + // After the threads finish, verify that the value is cleaned up. + const int kNumThreads = 5; + int values[kNumThreads]; + ThreadLocalStorageRunner* thread_delegates[kNumThreads]; + DelegateSimpleThread* threads[kNumThreads]; + + tls_slot.Initialize(ThreadLocalStorageCleanup); + + // Spawn the threads. + for (int index = 0; index < kNumThreads; index++) { + values[index] = kInitialTlsValue; + thread_delegates[index] = new ThreadLocalStorageRunner(&values[index]); + threads[index] = new DelegateSimpleThread(thread_delegates[index], + "tls thread"); + threads[index]->Start(); + } + + // Wait for the threads to finish. + for (int index = 0; index < kNumThreads; index++) { + threads[index]->Join(); + delete threads[index]; + delete thread_delegates[index]; + + // Verify that the destructor was called and that we reset. + EXPECT_EQ(values[index], kFinalTlsValue); + } + tls_slot.Free(); // Stop doing callbacks to cleanup threads. +} + +} // namespace butil diff --git a/test/thread_local_unittest.cc b/test/thread_local_unittest.cc new file mode 100644 index 0000000..92a0280 --- /dev/null +++ b/test/thread_local_unittest.cc @@ -0,0 +1,169 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/logging.h" +#include "butil/threading/simple_thread.h" +#include "butil/threading/thread_local.h" +#include "butil/synchronization/waitable_event.h" +#include + +namespace butil { + +namespace { + +class ThreadLocalTesterBase : public butil::DelegateSimpleThreadPool::Delegate { + public: + typedef butil::ThreadLocalPointer TLPType; + + ThreadLocalTesterBase(TLPType* tlp, butil::WaitableEvent* done) + : tlp_(tlp), + done_(done) { + } + virtual ~ThreadLocalTesterBase() {} + + protected: + TLPType* tlp_; + butil::WaitableEvent* done_; +}; + +class SetThreadLocal : public ThreadLocalTesterBase { + public: + SetThreadLocal(TLPType* tlp, butil::WaitableEvent* done) + : ThreadLocalTesterBase(tlp, done), + val_(NULL) { + } + virtual ~SetThreadLocal() {} + + void set_value(ThreadLocalTesterBase* val) { val_ = val; } + + virtual void Run() OVERRIDE { + DCHECK(!done_->IsSignaled()); + tlp_->Set(val_); + done_->Signal(); + } + + private: + ThreadLocalTesterBase* val_; +}; + +class GetThreadLocal : public ThreadLocalTesterBase { + public: + GetThreadLocal(TLPType* tlp, butil::WaitableEvent* done) + : ThreadLocalTesterBase(tlp, done), + ptr_(NULL) { + } + virtual ~GetThreadLocal() {} + + void set_ptr(ThreadLocalTesterBase** ptr) { ptr_ = ptr; } + + virtual void Run() OVERRIDE { + DCHECK(!done_->IsSignaled()); + *ptr_ = tlp_->Get(); + done_->Signal(); + } + + private: + ThreadLocalTesterBase** ptr_; +}; + +} // namespace + +// In this test, we start 2 threads which will access a ThreadLocalPointer. We +// make sure the default is NULL, and the pointers are unique to the threads. +TEST(ThreadLocalTest, Pointer) { + butil::DelegateSimpleThreadPool tp1("ThreadLocalTest tp1", 1); + butil::DelegateSimpleThreadPool tp2("ThreadLocalTest tp1", 1); + tp1.Start(); + tp2.Start(); + + butil::ThreadLocalPointer tlp; + + static ThreadLocalTesterBase* const kBogusPointer = + reinterpret_cast(0x1234); + + ThreadLocalTesterBase* tls_val; + butil::WaitableEvent done(true, false); + + GetThreadLocal getter(&tlp, &done); + getter.set_ptr(&tls_val); + + // Check that both threads defaulted to NULL. + tls_val = kBogusPointer; + done.Reset(); + tp1.AddWork(&getter); + done.Wait(); + EXPECT_EQ(static_cast(NULL), tls_val); + + tls_val = kBogusPointer; + done.Reset(); + tp2.AddWork(&getter); + done.Wait(); + EXPECT_EQ(static_cast(NULL), tls_val); + + + SetThreadLocal setter(&tlp, &done); + setter.set_value(kBogusPointer); + + // Have thread 1 set their pointer value to kBogusPointer. + done.Reset(); + tp1.AddWork(&setter); + done.Wait(); + + tls_val = NULL; + done.Reset(); + tp1.AddWork(&getter); + done.Wait(); + EXPECT_EQ(kBogusPointer, tls_val); + + // Make sure thread 2 is still NULL + tls_val = kBogusPointer; + done.Reset(); + tp2.AddWork(&getter); + done.Wait(); + EXPECT_EQ(static_cast(NULL), tls_val); + + // Set thread 2 to kBogusPointer + 1. + setter.set_value(kBogusPointer + 1); + + done.Reset(); + tp2.AddWork(&setter); + done.Wait(); + + tls_val = NULL; + done.Reset(); + tp2.AddWork(&getter); + done.Wait(); + EXPECT_EQ(kBogusPointer + 1, tls_val); + + // Make sure thread 1 is still kBogusPointer. + tls_val = NULL; + done.Reset(); + tp1.AddWork(&getter); + done.Wait(); + EXPECT_EQ(kBogusPointer, tls_val); + + tp1.JoinAll(); + tp2.JoinAll(); +} + +TEST(ThreadLocalTest, Boolean) { + { + butil::ThreadLocalBoolean tlb; + EXPECT_FALSE(tlb.Get()); + + tlb.Set(false); + EXPECT_FALSE(tlb.Get()); + + tlb.Set(true); + EXPECT_TRUE(tlb.Get()); + } + + // Our slot should have been freed, we're all reset. + { + butil::ThreadLocalBoolean tlb; + EXPECT_FALSE(tlb.Get()); + } +} + +} // namespace butil diff --git a/test/time_unittest.cc b/test/time_unittest.cc new file mode 100644 index 0000000..2622e20 --- /dev/null +++ b/test/time_unittest.cc @@ -0,0 +1,788 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/time/time.h" + +#include + +#include "butil/compiler_specific.h" +#include "butil/logging.h" +#include "butil/strings/stringprintf.h" +#include "butil/threading/platform_thread.h" +#include "butil/build_config.h" +#include + +using butil::Time; +using butil::TimeDelta; +using butil::TimeTicks; + +// Specialized test fixture allowing time strings without timezones to be +// tested by comparing them to a known time in the local zone. +// See also pr_time_unittests.cc +class TimeTest : public testing::Test { + protected: + virtual void SetUp() OVERRIDE { + // Use mktime to get a time_t, and turn it into a PRTime by converting + // seconds to microseconds. Use 15th Oct 2007 12:45:00 local. This + // must be a time guaranteed to be outside of a DST fallback hour in + // any timezone. + struct tm local_comparison_tm = { + 0, // second + 45, // minute + 12, // hour + 15, // day of month + 10 - 1, // month + 2007 - 1900, // year + 0, // day of week (ignored, output only) + 0, // day of year (ignored, output only) + -1, // DST in effect, -1 tells mktime to figure it out + 0, + NULL + }; + + time_t converted_time = mktime(&local_comparison_tm); + ASSERT_GT(converted_time, 0); + comparison_time_local_ = Time::FromTimeT(converted_time); + + // time_t representation of 15th Oct 2007 12:45:00 PDT + comparison_time_pdt_ = Time::FromTimeT(1192477500); + } + + Time comparison_time_local_; + Time comparison_time_pdt_; +}; + +// Test conversions to/from time_t and exploding/unexploding. +TEST_F(TimeTest, TimeT) { + // C library time and exploded time. + time_t now_t_1 = time(NULL); + struct tm tms; +#if defined(OS_WIN) + localtime_s(&tms, &now_t_1); +#elif defined(OS_POSIX) + localtime_r(&now_t_1, &tms); +#endif + + // Convert to ours. + Time our_time_1 = Time::FromTimeT(now_t_1); + Time::Exploded exploded; + our_time_1.LocalExplode(&exploded); + + // This will test both our exploding and our time_t -> Time conversion. + EXPECT_EQ(tms.tm_year + 1900, exploded.year); + EXPECT_EQ(tms.tm_mon + 1, exploded.month); + EXPECT_EQ(tms.tm_mday, exploded.day_of_month); + EXPECT_EQ(tms.tm_hour, exploded.hour); + EXPECT_EQ(tms.tm_min, exploded.minute); + EXPECT_EQ(tms.tm_sec, exploded.second); + + // Convert exploded back to the time struct. + Time our_time_2 = Time::FromLocalExploded(exploded); + EXPECT_TRUE(our_time_1 == our_time_2); + + time_t now_t_2 = our_time_2.ToTimeT(); + EXPECT_EQ(now_t_1, now_t_2); + + EXPECT_EQ(10, Time().FromTimeT(10).ToTimeT()); + EXPECT_EQ(10.0, Time().FromTimeT(10).ToDoubleT()); + + // Conversions of 0 should stay 0. + EXPECT_EQ(0, Time().ToTimeT()); + EXPECT_EQ(0, Time::FromTimeT(0).ToInternalValue()); +} + +// Test conversions to/from javascript time. +TEST_F(TimeTest, JsTime) { + Time epoch = Time::FromJsTime(0.0); + EXPECT_EQ(epoch, Time::UnixEpoch()); + Time t = Time::FromJsTime(700000.3); + EXPECT_EQ(700.0003, t.ToDoubleT()); + t = Time::FromDoubleT(800.73); + EXPECT_EQ(800730.0, t.ToJsTime()); +} + +#if defined(OS_POSIX) +TEST_F(TimeTest, FromTimeVal) { + Time now = Time::Now(); + Time also_now = Time::FromTimeVal(now.ToTimeVal()); + EXPECT_EQ(now, also_now); +} +#endif // OS_POSIX + +TEST_F(TimeTest, FromExplodedWithMilliseconds) { + // Some platform implementations of FromExploded are liable to drop + // milliseconds if we aren't careful. + Time now = Time::NowFromSystemTime(); + Time::Exploded exploded1 = {0,0,0,0,0,0,0,0}; + now.UTCExplode(&exploded1); + exploded1.millisecond = 500; + Time time = Time::FromUTCExploded(exploded1); + Time::Exploded exploded2 = {0,0,0,0,0,0,0,0}; + time.UTCExplode(&exploded2); + EXPECT_EQ(exploded1.millisecond, exploded2.millisecond); +} + +TEST_F(TimeTest, ZeroIsSymmetric) { + Time zero_time(Time::FromTimeT(0)); + EXPECT_EQ(0, zero_time.ToTimeT()); + + EXPECT_EQ(0.0, zero_time.ToDoubleT()); +} + +TEST_F(TimeTest, LocalExplode) { + Time a = Time::Now(); + Time::Exploded exploded; + a.LocalExplode(&exploded); + + Time b = Time::FromLocalExploded(exploded); + + // The exploded structure doesn't have microseconds, and on Mac & Linux, the + // internal OS conversion uses seconds, which will cause truncation. So we + // can only make sure that the delta is within one second. + EXPECT_TRUE((a - b) < TimeDelta::FromSeconds(1)); +} + +TEST_F(TimeTest, UTCExplode) { + Time a = Time::Now(); + Time::Exploded exploded; + a.UTCExplode(&exploded); + + Time b = Time::FromUTCExploded(exploded); + EXPECT_TRUE((a - b) < TimeDelta::FromSeconds(1)); +} + +TEST_F(TimeTest, LocalMidnight) { + Time::Exploded exploded; + Time::Now().LocalMidnight().LocalExplode(&exploded); + EXPECT_EQ(0, exploded.hour); + EXPECT_EQ(0, exploded.minute); + EXPECT_EQ(0, exploded.second); + EXPECT_EQ(0, exploded.millisecond); +} + +// TODO(zhujiashun): Time::FromString is not implemented after removing nspr, +// so all tests using this function is disabled. Enable those tests once +// Time::FromString is implemented. +TEST_F(TimeTest, DISABLED_ParseTimeTest1) { + time_t current_time = 0; + time(¤t_time); + + const int BUFFER_SIZE = 64; + struct tm local_time; + memset(&local_time, 0, sizeof(local_time)); + char time_buf[BUFFER_SIZE] = {0}; +#if defined(OS_WIN) + localtime_s(&local_time, ¤t_time); + asctime_s(time_buf, arraysize(time_buf), &local_time); +#elif defined(OS_POSIX) + localtime_r(¤t_time, &local_time); + asctime_r(&local_time, time_buf); +#endif + + Time parsed_time; + EXPECT_TRUE(Time::FromString(time_buf, &parsed_time)); + EXPECT_EQ(current_time, parsed_time.ToTimeT()); +} + +TEST_F(TimeTest, DISABLED_DayOfWeekSunday) { + Time time; + EXPECT_TRUE(Time::FromString("Sun, 06 May 2012 12:00:00 GMT", &time)); + Time::Exploded exploded; + time.UTCExplode(&exploded); + EXPECT_EQ(0, exploded.day_of_week); +} + +TEST_F(TimeTest, DISABLED_DayOfWeekWednesday) { + Time time; + EXPECT_TRUE(Time::FromString("Wed, 09 May 2012 12:00:00 GMT", &time)); + Time::Exploded exploded; + time.UTCExplode(&exploded); + EXPECT_EQ(3, exploded.day_of_week); +} + +TEST_F(TimeTest, DISABLED_DayOfWeekSaturday) { + Time time; + EXPECT_TRUE(Time::FromString("Sat, 12 May 2012 12:00:00 GMT", &time)); + Time::Exploded exploded; + time.UTCExplode(&exploded); + EXPECT_EQ(6, exploded.day_of_week); +} + +TEST_F(TimeTest, DISABLED_ParseTimeTest2) { + Time parsed_time; + EXPECT_TRUE(Time::FromString("Mon, 15 Oct 2007 19:45:00 GMT", &parsed_time)); + EXPECT_EQ(comparison_time_pdt_, parsed_time); +} + +TEST_F(TimeTest, DISABLED_ParseTimeTest3) { + Time parsed_time; + EXPECT_TRUE(Time::FromString("15 Oct 07 12:45:00", &parsed_time)); + EXPECT_EQ(comparison_time_local_, parsed_time); +} + +TEST_F(TimeTest, DISABLED_ParseTimeTest4) { + Time parsed_time; + EXPECT_TRUE(Time::FromString("15 Oct 07 19:45 GMT", &parsed_time)); + EXPECT_EQ(comparison_time_pdt_, parsed_time); +} + +TEST_F(TimeTest, DISABLED_ParseTimeTest5) { + Time parsed_time; + EXPECT_TRUE(Time::FromString("Mon Oct 15 12:45 PDT 2007", &parsed_time)); + EXPECT_EQ(comparison_time_pdt_, parsed_time); +} + +TEST_F(TimeTest, DISABLED_ParseTimeTest6) { + Time parsed_time; + EXPECT_TRUE(Time::FromString("Monday, Oct 15, 2007 12:45 PM", &parsed_time)); + EXPECT_EQ(comparison_time_local_, parsed_time); +} + +TEST_F(TimeTest, DISABLED_ParseTimeTest7) { + Time parsed_time; + EXPECT_TRUE(Time::FromString("10/15/07 12:45:00 PM", &parsed_time)); + EXPECT_EQ(comparison_time_local_, parsed_time); +} + +TEST_F(TimeTest, DISABLED_ParseTimeTest8) { + Time parsed_time; + EXPECT_TRUE(Time::FromString("15-OCT-2007 12:45pm", &parsed_time)); + EXPECT_EQ(comparison_time_local_, parsed_time); +} + +TEST_F(TimeTest, DISABLED_ParseTimeTest9) { + Time parsed_time; + EXPECT_TRUE(Time::FromString("16 Oct 2007 4:45-JST (Tuesday)", &parsed_time)); + EXPECT_EQ(comparison_time_pdt_, parsed_time); +} + +TEST_F(TimeTest, DISABLED_ParseTimeTest10) { + Time parsed_time; + EXPECT_TRUE(Time::FromString("15/10/07 12:45", &parsed_time)); + EXPECT_EQ(parsed_time, comparison_time_local_); +} + +// Test some of edge cases around epoch, etc. +TEST_F(TimeTest, DISABLED_ParseTimeTestEpoch0) { + Time parsed_time; + + // time_t == epoch == 0 + EXPECT_TRUE(Time::FromString("Thu Jan 01 01:00:00 +0100 1970", + &parsed_time)); + EXPECT_EQ(0, parsed_time.ToTimeT()); + EXPECT_TRUE(Time::FromString("Thu Jan 01 00:00:00 GMT 1970", + &parsed_time)); + EXPECT_EQ(0, parsed_time.ToTimeT()); +} + +TEST_F(TimeTest, DISABLED_ParseTimeTestEpoch1) { + Time parsed_time; + + // time_t == 1 second after epoch == 1 + EXPECT_TRUE(Time::FromString("Thu Jan 01 01:00:01 +0100 1970", + &parsed_time)); + EXPECT_EQ(1, parsed_time.ToTimeT()); + EXPECT_TRUE(Time::FromString("Thu Jan 01 00:00:01 GMT 1970", + &parsed_time)); + EXPECT_EQ(1, parsed_time.ToTimeT()); +} + +TEST_F(TimeTest, DISABLED_ParseTimeTestEpoch2) { + Time parsed_time; + + // time_t == 2 seconds after epoch == 2 + EXPECT_TRUE(Time::FromString("Thu Jan 01 01:00:02 +0100 1970", + &parsed_time)); + EXPECT_EQ(2, parsed_time.ToTimeT()); + EXPECT_TRUE(Time::FromString("Thu Jan 01 00:00:02 GMT 1970", + &parsed_time)); + EXPECT_EQ(2, parsed_time.ToTimeT()); +} + +TEST_F(TimeTest, DISABLED_ParseTimeTestEpochNeg1) { + Time parsed_time; + + // time_t == 1 second before epoch == -1 + EXPECT_TRUE(Time::FromString("Thu Jan 01 00:59:59 +0100 1970", + &parsed_time)); + EXPECT_EQ(-1, parsed_time.ToTimeT()); + EXPECT_TRUE(Time::FromString("Wed Dec 31 23:59:59 GMT 1969", + &parsed_time)); + EXPECT_EQ(-1, parsed_time.ToTimeT()); +} + +// If time_t is 32 bits, a date after year 2038 will overflow time_t and +// cause timegm() to return -1. The parsed time should not be 1 second +// before epoch. +TEST_F(TimeTest, DISABLED_ParseTimeTestEpochNotNeg1) { + Time parsed_time; + + EXPECT_TRUE(Time::FromString("Wed Dec 31 23:59:59 GMT 2100", + &parsed_time)); + EXPECT_NE(-1, parsed_time.ToTimeT()); +} + +TEST_F(TimeTest, DISABLED_ParseTimeTestEpochNeg2) { + Time parsed_time; + + // time_t == 2 seconds before epoch == -2 + EXPECT_TRUE(Time::FromString("Thu Jan 01 00:59:58 +0100 1970", + &parsed_time)); + EXPECT_EQ(-2, parsed_time.ToTimeT()); + EXPECT_TRUE(Time::FromString("Wed Dec 31 23:59:58 GMT 1969", + &parsed_time)); + EXPECT_EQ(-2, parsed_time.ToTimeT()); +} + +TEST_F(TimeTest, DISABLED_ParseTimeTestEpoch1960) { + Time parsed_time; + + // time_t before Epoch, in 1960 + EXPECT_TRUE(Time::FromString("Wed Jun 29 19:40:01 +0100 1960", + &parsed_time)); + EXPECT_EQ(-299999999, parsed_time.ToTimeT()); + EXPECT_TRUE(Time::FromString("Wed Jun 29 18:40:01 GMT 1960", + &parsed_time)); + EXPECT_EQ(-299999999, parsed_time.ToTimeT()); + EXPECT_TRUE(Time::FromString("Wed Jun 29 17:40:01 GMT 1960", + &parsed_time)); + EXPECT_EQ(-300003599, parsed_time.ToTimeT()); +} + +TEST_F(TimeTest, DISABLED_ParseTimeTestEmpty) { + Time parsed_time; + EXPECT_FALSE(Time::FromString("", &parsed_time)); +} + +TEST_F(TimeTest, DISABLED_ParseTimeTestInvalidString) { + Time parsed_time; + EXPECT_FALSE(Time::FromString("Monday morning 2000", &parsed_time)); +} + +TEST_F(TimeTest, ExplodeBeforeUnixEpoch) { + static const int kUnixEpochYear = 1970; // In case this changes (ha!). + Time t; + Time::Exploded exploded; + + t = Time::UnixEpoch() - TimeDelta::FromMicroseconds(1); + t.UTCExplode(&exploded); + EXPECT_TRUE(exploded.HasValidValues()); + // Should be 1969-12-31 23:59:59 999 milliseconds (and 999 microseconds). + EXPECT_EQ(kUnixEpochYear - 1, exploded.year); + EXPECT_EQ(12, exploded.month); + EXPECT_EQ(31, exploded.day_of_month); + EXPECT_EQ(23, exploded.hour); + EXPECT_EQ(59, exploded.minute); + EXPECT_EQ(59, exploded.second); + EXPECT_EQ(999, exploded.millisecond); + + t = Time::UnixEpoch() - TimeDelta::FromMicroseconds(1000); + t.UTCExplode(&exploded); + EXPECT_TRUE(exploded.HasValidValues()); + // Should be 1969-12-31 23:59:59 999 milliseconds. + EXPECT_EQ(kUnixEpochYear - 1, exploded.year); + EXPECT_EQ(12, exploded.month); + EXPECT_EQ(31, exploded.day_of_month); + EXPECT_EQ(23, exploded.hour); + EXPECT_EQ(59, exploded.minute); + EXPECT_EQ(59, exploded.second); + EXPECT_EQ(999, exploded.millisecond); + + t = Time::UnixEpoch() - TimeDelta::FromMicroseconds(1001); + t.UTCExplode(&exploded); + EXPECT_TRUE(exploded.HasValidValues()); + // Should be 1969-12-31 23:59:59 998 milliseconds (and 999 microseconds). + EXPECT_EQ(kUnixEpochYear - 1, exploded.year); + EXPECT_EQ(12, exploded.month); + EXPECT_EQ(31, exploded.day_of_month); + EXPECT_EQ(23, exploded.hour); + EXPECT_EQ(59, exploded.minute); + EXPECT_EQ(59, exploded.second); + EXPECT_EQ(998, exploded.millisecond); + + t = Time::UnixEpoch() - TimeDelta::FromMilliseconds(1000); + t.UTCExplode(&exploded); + EXPECT_TRUE(exploded.HasValidValues()); + // Should be 1969-12-31 23:59:59. + EXPECT_EQ(kUnixEpochYear - 1, exploded.year); + EXPECT_EQ(12, exploded.month); + EXPECT_EQ(31, exploded.day_of_month); + EXPECT_EQ(23, exploded.hour); + EXPECT_EQ(59, exploded.minute); + EXPECT_EQ(59, exploded.second); + EXPECT_EQ(0, exploded.millisecond); + + t = Time::UnixEpoch() - TimeDelta::FromMilliseconds(1001); + t.UTCExplode(&exploded); + EXPECT_TRUE(exploded.HasValidValues()); + // Should be 1969-12-31 23:59:58 999 milliseconds. + EXPECT_EQ(kUnixEpochYear - 1, exploded.year); + EXPECT_EQ(12, exploded.month); + EXPECT_EQ(31, exploded.day_of_month); + EXPECT_EQ(23, exploded.hour); + EXPECT_EQ(59, exploded.minute); + EXPECT_EQ(58, exploded.second); + EXPECT_EQ(999, exploded.millisecond); + + // Make sure we still handle at/after Unix epoch correctly. + t = Time::UnixEpoch(); + t.UTCExplode(&exploded); + EXPECT_TRUE(exploded.HasValidValues()); + // Should be 1970-12-31 00:00:00 0 milliseconds. + EXPECT_EQ(kUnixEpochYear, exploded.year); + EXPECT_EQ(1, exploded.month); + EXPECT_EQ(1, exploded.day_of_month); + EXPECT_EQ(0, exploded.hour); + EXPECT_EQ(0, exploded.minute); + EXPECT_EQ(0, exploded.second); + EXPECT_EQ(0, exploded.millisecond); + + t = Time::UnixEpoch() + TimeDelta::FromMicroseconds(1); + t.UTCExplode(&exploded); + EXPECT_TRUE(exploded.HasValidValues()); + // Should be 1970-01-01 00:00:00 0 milliseconds (and 1 microsecond). + EXPECT_EQ(kUnixEpochYear, exploded.year); + EXPECT_EQ(1, exploded.month); + EXPECT_EQ(1, exploded.day_of_month); + EXPECT_EQ(0, exploded.hour); + EXPECT_EQ(0, exploded.minute); + EXPECT_EQ(0, exploded.second); + EXPECT_EQ(0, exploded.millisecond); + + t = Time::UnixEpoch() + TimeDelta::FromMicroseconds(1000); + t.UTCExplode(&exploded); + EXPECT_TRUE(exploded.HasValidValues()); + // Should be 1970-01-01 00:00:00 1 millisecond. + EXPECT_EQ(kUnixEpochYear, exploded.year); + EXPECT_EQ(1, exploded.month); + EXPECT_EQ(1, exploded.day_of_month); + EXPECT_EQ(0, exploded.hour); + EXPECT_EQ(0, exploded.minute); + EXPECT_EQ(0, exploded.second); + EXPECT_EQ(1, exploded.millisecond); + + t = Time::UnixEpoch() + TimeDelta::FromMilliseconds(1000); + t.UTCExplode(&exploded); + EXPECT_TRUE(exploded.HasValidValues()); + // Should be 1970-01-01 00:00:01. + EXPECT_EQ(kUnixEpochYear, exploded.year); + EXPECT_EQ(1, exploded.month); + EXPECT_EQ(1, exploded.day_of_month); + EXPECT_EQ(0, exploded.hour); + EXPECT_EQ(0, exploded.minute); + EXPECT_EQ(1, exploded.second); + EXPECT_EQ(0, exploded.millisecond); + + t = Time::UnixEpoch() + TimeDelta::FromMilliseconds(1001); + t.UTCExplode(&exploded); + EXPECT_TRUE(exploded.HasValidValues()); + // Should be 1970-01-01 00:00:01 1 millisecond. + EXPECT_EQ(kUnixEpochYear, exploded.year); + EXPECT_EQ(1, exploded.month); + EXPECT_EQ(1, exploded.day_of_month); + EXPECT_EQ(0, exploded.hour); + EXPECT_EQ(0, exploded.minute); + EXPECT_EQ(1, exploded.second); + EXPECT_EQ(1, exploded.millisecond); +} + +TEST_F(TimeTest, TimeDeltaMax) { + TimeDelta max = TimeDelta::Max(); + EXPECT_TRUE(max.is_max()); + EXPECT_EQ(max, TimeDelta::Max()); + EXPECT_GT(max, TimeDelta::FromDays(100 * 365)); + EXPECT_GT(max, TimeDelta()); +} + +TEST_F(TimeTest, TimeDeltaMaxConversions) { + TimeDelta t = TimeDelta::Max(); + EXPECT_EQ(std::numeric_limits::max(), t.ToInternalValue()); + + EXPECT_EQ(std::numeric_limits::max(), t.InDays()); + EXPECT_EQ(std::numeric_limits::max(), t.InHours()); + EXPECT_EQ(std::numeric_limits::max(), t.InMinutes()); + EXPECT_EQ(std::numeric_limits::infinity(), t.InSecondsF()); + EXPECT_EQ(std::numeric_limits::max(), t.InSeconds()); + EXPECT_EQ(std::numeric_limits::infinity(), t.InMillisecondsF()); + EXPECT_EQ(std::numeric_limits::max(), t.InMilliseconds()); + EXPECT_EQ(std::numeric_limits::max(), t.InMillisecondsRoundedUp()); + + t = TimeDelta::FromDays(std::numeric_limits::max()); + EXPECT_TRUE(t.is_max()); + + t = TimeDelta::FromHours(std::numeric_limits::max()); + EXPECT_TRUE(t.is_max()); + + t = TimeDelta::FromMinutes(std::numeric_limits::max()); + EXPECT_TRUE(t.is_max()); + + t = TimeDelta::FromSeconds(std::numeric_limits::max()); + EXPECT_TRUE(t.is_max()); + + t = TimeDelta::FromMilliseconds(std::numeric_limits::max()); + EXPECT_TRUE(t.is_max()); + + t = TimeDelta::FromSecondsD(std::numeric_limits::infinity()); + EXPECT_TRUE(t.is_max()); + + t = TimeDelta::FromMillisecondsD(std::numeric_limits::infinity()); + EXPECT_TRUE(t.is_max()); + + t = TimeDelta::FromMicroseconds(std::numeric_limits::max()); + EXPECT_TRUE(t.is_max()); +} + +TEST_F(TimeTest, Max) { + Time max = Time::Max(); + EXPECT_TRUE(max.is_max()); + EXPECT_EQ(max, Time::Max()); + EXPECT_GT(max, Time::Now()); + EXPECT_GT(max, Time()); +} + +TEST_F(TimeTest, MaxConversions) { + Time t = Time::Max(); + EXPECT_EQ(std::numeric_limits::max(), t.ToInternalValue()); + + t = Time::FromDoubleT(std::numeric_limits::infinity()); + EXPECT_TRUE(t.is_max()); + EXPECT_EQ(std::numeric_limits::infinity(), t.ToDoubleT()); + + t = Time::FromJsTime(std::numeric_limits::infinity()); + EXPECT_TRUE(t.is_max()); + EXPECT_EQ(std::numeric_limits::infinity(), t.ToJsTime()); + + t = Time::FromTimeT(std::numeric_limits::max()); + EXPECT_TRUE(t.is_max()); + EXPECT_EQ(std::numeric_limits::max(), t.ToTimeT()); + +#if defined(OS_POSIX) + struct timeval tval; + tval.tv_sec = std::numeric_limits::max(); + tval.tv_usec = static_cast(Time::kMicrosecondsPerSecond) - 1; + t = Time::FromTimeVal(tval); + EXPECT_TRUE(t.is_max()); + tval = t.ToTimeVal(); + EXPECT_EQ(std::numeric_limits::max(), tval.tv_sec); + EXPECT_EQ(static_cast(Time::kMicrosecondsPerSecond) - 1, + tval.tv_usec); +#endif + +#if defined(OS_MACOSX) + t = Time::FromCFAbsoluteTime(std::numeric_limits::infinity()); + EXPECT_TRUE(t.is_max()); + EXPECT_EQ(std::numeric_limits::infinity(), + t.ToCFAbsoluteTime()); +#endif + +#if defined(OS_WIN) + FILETIME ftime; + ftime.dwHighDateTime = std::numeric_limits::max(); + ftime.dwLowDateTime = std::numeric_limits::max(); + t = Time::FromFileTime(ftime); + EXPECT_TRUE(t.is_max()); + ftime = t.ToFileTime(); + EXPECT_EQ(std::numeric_limits::max(), ftime.dwHighDateTime); + EXPECT_EQ(std::numeric_limits::max(), ftime.dwLowDateTime); +#endif +} + +#if defined(OS_MACOSX) +TEST_F(TimeTest, TimeTOverflow) { + Time t = Time::FromInternalValue(std::numeric_limits::max() - 1); + EXPECT_FALSE(t.is_max()); + EXPECT_EQ(std::numeric_limits::max(), t.ToTimeT()); +} +#endif + +#if defined(OS_ANDROID) +TEST_F(TimeTest, FromLocalExplodedCrashOnAndroid) { + // This crashed inside Time:: FromLocalExploded() on Android 4.1.2. + // See http://crbug.com/287821 + Time::Exploded midnight = {2013, // year + 10, // month + 0, // day_of_week + 13, // day_of_month + 0, // hour + 0, // minute + 0, // second + }; + // The string passed to putenv() must be a char* and the documentation states + // that it 'becomes part of the environment', so use a static buffer. + static char buffer[] = "TZ=America/Santiago"; + putenv(buffer); + tzset(); + Time t = Time::FromLocalExploded(midnight); + EXPECT_EQ(1381633200, t.ToTimeT()); +} +#endif // OS_ANDROID + +TEST(TimeTicks, Deltas) { + for (int index = 0; index < 50; index++) { + TimeTicks ticks_start = TimeTicks::Now(); + butil::PlatformThread::Sleep(butil::TimeDelta::FromMilliseconds(10)); + TimeTicks ticks_stop = TimeTicks::Now(); + TimeDelta delta = ticks_stop - ticks_start; + // Note: Although we asked for a 10ms sleep, if the + // time clock has a finer granularity than the Sleep() + // clock, it is quite possible to wakeup early. Here + // is how that works: + // Time(ms timer) Time(us timer) + // 5 5010 + // 6 6010 + // 7 7010 + // 8 8010 + // 9 9000 + // Elapsed 4ms 3990us + // + // Unfortunately, our InMilliseconds() function truncates + // rather than rounds. We should consider fixing this + // so that our averages come out better. + EXPECT_GE(delta.InMilliseconds(), 9); + EXPECT_GE(delta.InMicroseconds(), 9000); + EXPECT_EQ(delta.InSeconds(), 0); + } +} + +static void HighResClockTest(TimeTicks (*GetTicks)()) { +#if defined(OS_WIN) + // HighResNow doesn't work on some systems. Since the product still works + // even if it doesn't work, it makes this entire test questionable. + if (!TimeTicks::IsHighResClockWorking()) + return; +#endif + + // Why do we loop here? + // We're trying to measure that intervals increment in a VERY small amount + // of time -- less than 15ms. Unfortunately, if we happen to have a + // context switch in the middle of our test, the context switch could easily + // exceed our limit. So, we iterate on this several times. As long as we're + // able to detect the fine-granularity timers at least once, then the test + // has succeeded. + + const int kTargetGranularityUs = 15000; // 15ms + + bool success = false; + int retries = 100; // Arbitrary. + TimeDelta delta; + while (!success && retries--) { + TimeTicks ticks_start = GetTicks(); + // Loop until we can detect that the clock has changed. Non-HighRes timers + // will increment in chunks, e.g. 15ms. By spinning until we see a clock + // change, we detect the minimum time between measurements. + do { + delta = GetTicks() - ticks_start; + } while (delta.InMilliseconds() == 0); + + if (delta.InMicroseconds() <= kTargetGranularityUs) + success = true; + } + + // In high resolution mode, we expect to see the clock increment + // in intervals less than 15ms. + EXPECT_TRUE(success); +} + +TEST(TimeTicks, HighResNow) { + HighResClockTest(&TimeTicks::HighResNow); +} + +// Fails frequently on Android http://crbug.com/352633 with: +// Expected: (delta_thread.InMicroseconds()) > (0), actual: 0 vs 0 +#if defined(OS_ANDROID) +#define MAYBE_ThreadNow DISABLED_ThreadNow +#else +#define MAYBE_ThreadNow ThreadNow +#endif +TEST(TimeTicks, MAYBE_ThreadNow) { + if (TimeTicks::IsThreadNowSupported()) { + TimeTicks begin = TimeTicks::Now(); + TimeTicks begin_thread = TimeTicks::ThreadNow(); + // Make sure that ThreadNow value is non-zero. + EXPECT_GT(begin_thread, TimeTicks()); + // Sleep for 10 milliseconds to get the thread de-scheduled. + butil::PlatformThread::Sleep(butil::TimeDelta::FromMilliseconds(10)); + TimeTicks end_thread = TimeTicks::ThreadNow(); + TimeTicks end = TimeTicks::Now(); + TimeDelta delta = end - begin; + TimeDelta delta_thread = end_thread - begin_thread; + // Make sure that some thread time have elapsed. + EXPECT_GT(delta_thread.InMicroseconds(), 0); + // But the thread time is at least 9ms less than clock time. + TimeDelta difference = delta - delta_thread; + EXPECT_GE(difference.InMicroseconds(), 9000); + } +} + +TEST(TimeTicks, NowFromSystemTraceTime) { + // Re-use HighResNow test for now since clock properties are identical. + HighResClockTest(&TimeTicks::NowFromSystemTraceTime); +} + +TEST(TimeDelta, FromAndIn) { + EXPECT_TRUE(TimeDelta::FromDays(2) == TimeDelta::FromHours(48)); + EXPECT_TRUE(TimeDelta::FromHours(3) == TimeDelta::FromMinutes(180)); + EXPECT_TRUE(TimeDelta::FromMinutes(2) == TimeDelta::FromSeconds(120)); + EXPECT_TRUE(TimeDelta::FromSeconds(2) == TimeDelta::FromMilliseconds(2000)); + EXPECT_TRUE(TimeDelta::FromMilliseconds(2) == + TimeDelta::FromMicroseconds(2000)); + EXPECT_TRUE(TimeDelta::FromSecondsD(2.3) == + TimeDelta::FromMilliseconds(2300)); + EXPECT_TRUE(TimeDelta::FromMillisecondsD(2.5) == + TimeDelta::FromMicroseconds(2500)); + EXPECT_EQ(13, TimeDelta::FromDays(13).InDays()); + EXPECT_EQ(13, TimeDelta::FromHours(13).InHours()); + EXPECT_EQ(13, TimeDelta::FromMinutes(13).InMinutes()); + EXPECT_EQ(13, TimeDelta::FromSeconds(13).InSeconds()); + EXPECT_EQ(13.0, TimeDelta::FromSeconds(13).InSecondsF()); + EXPECT_EQ(13, TimeDelta::FromMilliseconds(13).InMilliseconds()); + EXPECT_EQ(13.0, TimeDelta::FromMilliseconds(13).InMillisecondsF()); + EXPECT_EQ(13, TimeDelta::FromSecondsD(13.1).InSeconds()); + EXPECT_EQ(13.1, TimeDelta::FromSecondsD(13.1).InSecondsF()); + EXPECT_EQ(13, TimeDelta::FromMillisecondsD(13.3).InMilliseconds()); + EXPECT_EQ(13.3, TimeDelta::FromMillisecondsD(13.3).InMillisecondsF()); + EXPECT_EQ(13, TimeDelta::FromMicroseconds(13).InMicroseconds()); +} + +#if defined(OS_POSIX) +TEST(TimeDelta, TimeSpecConversion) { + struct timespec result = TimeDelta::FromSeconds(0).ToTimeSpec(); + EXPECT_EQ(result.tv_sec, 0); + EXPECT_EQ(result.tv_nsec, 0); + + result = TimeDelta::FromSeconds(1).ToTimeSpec(); + EXPECT_EQ(result.tv_sec, 1); + EXPECT_EQ(result.tv_nsec, 0); + + result = TimeDelta::FromMicroseconds(1).ToTimeSpec(); + EXPECT_EQ(result.tv_sec, 0); + EXPECT_EQ(result.tv_nsec, 1000); + + result = TimeDelta::FromMicroseconds( + Time::kMicrosecondsPerSecond + 1).ToTimeSpec(); + EXPECT_EQ(result.tv_sec, 1); + EXPECT_EQ(result.tv_nsec, 1000); +} +#endif // OS_POSIX + +// Our internal time format is serialized in things like databases, so it's +// important that it's consistent across all our platforms. We use the 1601 +// Windows epoch as the internal format across all platforms. +TEST(TimeDelta, WindowsEpoch) { + Time::Exploded exploded; + exploded.year = 1970; + exploded.month = 1; + exploded.day_of_week = 0; // Should be unusued. + exploded.day_of_month = 1; + exploded.hour = 0; + exploded.minute = 0; + exploded.second = 0; + exploded.millisecond = 0; + Time t = Time::FromUTCExploded(exploded); + // Unix 1970 epoch. + EXPECT_EQ(GG_INT64_C(11644473600000000), t.ToInternalValue()); + + // We can't test 1601 epoch, since the system time functions on Linux + // only compute years starting from 1900. +} diff --git a/test/type_traits_unittest.cc b/test/type_traits_unittest.cc new file mode 100644 index 0000000..b13f535 --- /dev/null +++ b/test/type_traits_unittest.cc @@ -0,0 +1,251 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/type_traits.h" + +#include "butil/basictypes.h" +#include + +namespace butil { +namespace { + +struct AStruct {}; +class AClass {}; +union AUnion {}; +enum AnEnum { AN_ENUM_APPLE, AN_ENUM_BANANA, AN_ENUM_CARROT }; +struct BStruct { + int x; +}; +class BClass { +#if defined(__clang__) + int ALLOW_UNUSED _x; +#else + int _x; +#endif +}; + +class Parent {}; +class Child : public Parent {}; + +// is_empty +COMPILE_ASSERT(is_empty::value, IsEmpty); +COMPILE_ASSERT(is_empty::value, IsEmpty); +COMPILE_ASSERT(!is_empty::value, IsEmpty); +COMPILE_ASSERT(!is_empty::value, IsEmpty); + +// is_pointer +COMPILE_ASSERT(!is_pointer::value, IsPointer); +COMPILE_ASSERT(!is_pointer::value, IsPointer); +COMPILE_ASSERT(is_pointer::value, IsPointer); +COMPILE_ASSERT(is_pointer::value, IsPointer); + +// is_array +COMPILE_ASSERT(!is_array::value, IsArray); +COMPILE_ASSERT(!is_array::value, IsArray); +COMPILE_ASSERT(!is_array::value, IsArray); +COMPILE_ASSERT(is_array::value, IsArray); +COMPILE_ASSERT(is_array::value, IsArray); +COMPILE_ASSERT(is_array::value, IsArray); + +// is_non_const_reference +COMPILE_ASSERT(!is_non_const_reference::value, IsNonConstReference); +COMPILE_ASSERT(!is_non_const_reference::value, IsNonConstReference); +COMPILE_ASSERT(is_non_const_reference::value, IsNonConstReference); + +// is_convertible + +// Extra parens needed to make preprocessor macro parsing happy. Otherwise, +// it sees the equivalent of: +// +// (is_convertible < Child), (Parent > ::value) +// +// Silly C++. +COMPILE_ASSERT( (is_convertible::value), IsConvertible); +COMPILE_ASSERT(!(is_convertible::value), IsConvertible); +COMPILE_ASSERT(!(is_convertible::value), IsConvertible); +COMPILE_ASSERT( (is_convertible::value), IsConvertible); +COMPILE_ASSERT( (is_convertible::value), IsConvertible); +COMPILE_ASSERT(!(is_convertible::value), IsConvertible); + +// Array types are an easy corner case. Make sure to test that +// it does indeed compile. +COMPILE_ASSERT(!(is_convertible::value), IsConvertible); +COMPILE_ASSERT(!(is_convertible::value), IsConvertible); +COMPILE_ASSERT( (is_convertible::value), IsConvertible); + +// is_same +COMPILE_ASSERT(!(is_same::value), IsSame); +COMPILE_ASSERT(!(is_same::value), IsSame); +COMPILE_ASSERT( (is_same::value), IsSame); +COMPILE_ASSERT( (is_same::value), IsSame); +COMPILE_ASSERT( (is_same::value), IsSame); +COMPILE_ASSERT( (is_same::value), IsSame); +COMPILE_ASSERT(!(is_same::value), IsSame); + + +// is_class +COMPILE_ASSERT(is_class::value, IsClass); +COMPILE_ASSERT(is_class::value, IsClass); +COMPILE_ASSERT(is_class::value, IsClass); +COMPILE_ASSERT(!is_class::value, IsClass); +COMPILE_ASSERT(!is_class::value, IsClass); +COMPILE_ASSERT(!is_class::value, IsClass); +COMPILE_ASSERT(!is_class::value, IsClass); +COMPILE_ASSERT(!is_class::value, IsClass); + +// NOTE(gejun): Not work in gcc 3.4 yet. +#if !defined(__GNUC__) || __GNUC__ >= 4 +COMPILE_ASSERT((is_enum::value), IsEnum); +#endif +COMPILE_ASSERT(!(is_enum::value), IsEnum); +COMPILE_ASSERT(!(is_enum::value), IsEnum); +COMPILE_ASSERT(!(is_enum::value), IsEnum); + +COMPILE_ASSERT(!is_member_function_pointer::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(!is_member_function_pointer::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(!is_member_function_pointer::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(!is_member_function_pointer::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(!is_member_function_pointer::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(!is_member_function_pointer::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(!is_member_function_pointer::value, + IsMemberFunctionPointer); + +COMPILE_ASSERT(is_member_function_pointer::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(is_member_function_pointer::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(is_member_function_pointer::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(is_member_function_pointer::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(is_member_function_pointer::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(is_member_function_pointer< + int (AStruct::*)(int, int) const>::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(is_member_function_pointer< + int (AStruct::*)(int, int, int)>::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(is_member_function_pointer< + int (AStruct::*)(int, int, int) const>::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(is_member_function_pointer< + int (AStruct::*)(int, int, int, int)>::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(is_member_function_pointer< + int (AStruct::*)(int, int, int, int) const>::value, + IsMemberFunctionPointer); + +// False because we don't have a specialization for 5 params yet. +COMPILE_ASSERT(!is_member_function_pointer< + int (AStruct::*)(int, int, int, int, int)>::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(!is_member_function_pointer< + int (AStruct::*)(int, int, int, int, int) const>::value, + IsMemberFunctionPointer); + +// add_const +COMPILE_ASSERT((is_same::type, const int>::value), AddConst); +COMPILE_ASSERT((is_same::type, const long>::value), AddConst); +COMPILE_ASSERT((is_same::type, const std::string>::value), + AddConst); +COMPILE_ASSERT((is_same::type, const int>::value), + AddConst); +COMPILE_ASSERT((is_same::type, const long>::value), + AddConst); +COMPILE_ASSERT((is_same::type, + const std::string>::value), AddConst); + +// add_volatile +COMPILE_ASSERT((is_same::type, volatile int>::value), + AddVolatile); +COMPILE_ASSERT((is_same::type, volatile long>::value), + AddVolatile); +COMPILE_ASSERT((is_same::type, + volatile std::string>::value), AddVolatile); +COMPILE_ASSERT((is_same::type, volatile int>::value), + AddVolatile); +COMPILE_ASSERT((is_same::type, volatile long>::value), + AddVolatile); +COMPILE_ASSERT((is_same::type, + volatile std::string>::value), AddVolatile); + +// add_reference +COMPILE_ASSERT((is_same::type, int&>::value), AddReference); +COMPILE_ASSERT((is_same::type, long&>::value), AddReference); +COMPILE_ASSERT((is_same::type, std::string&>::value), + AddReference); +COMPILE_ASSERT((is_same::type, int&>::value), + AddReference); +COMPILE_ASSERT((is_same::type, long&>::value), + AddReference); +COMPILE_ASSERT((is_same::type, + std::string&>::value), AddReference); +COMPILE_ASSERT((is_same::type, const int&>::value), + AddReference); +COMPILE_ASSERT((is_same::type, const long&>::value), + AddReference); +COMPILE_ASSERT((is_same::type, + const std::string&>::value), AddReference); + +// add_cr_non_integral +COMPILE_ASSERT((is_same::type, int>::value), + AddCrNonIntegral); +COMPILE_ASSERT((is_same::type, long>::value), + AddCrNonIntegral); +COMPILE_ASSERT((is_same::type, + const std::string&>::value), AddCrNonIntegral); +COMPILE_ASSERT((is_same::type, const int&>::value), + AddCrNonIntegral); +COMPILE_ASSERT((is_same::type, const long&>::value), + AddCrNonIntegral); +COMPILE_ASSERT((is_same::type, + const std::string&>::value), AddCrNonIntegral); +COMPILE_ASSERT((is_same::type, const int&>::value), + AddCrNonIntegral); +COMPILE_ASSERT((is_same::type, const long&>::value), + AddCrNonIntegral); +COMPILE_ASSERT((is_same::type, + const std::string&>::value), AddCrNonIntegral); + +// remove_const +COMPILE_ASSERT((is_same::type, int>::value), + RemoveConst); +COMPILE_ASSERT((is_same::type, long>::value), + RemoveConst); +COMPILE_ASSERT((is_same::type, + std::string>::value), RemoveConst); +COMPILE_ASSERT((is_same::type, int>::value), RemoveConst); +COMPILE_ASSERT((is_same::type, long>::value), RemoveConst); +COMPILE_ASSERT((is_same::type, std::string>::value), + RemoveConst); + +// remove_reference +COMPILE_ASSERT((is_same::type, int>::value), + RemoveReference); +COMPILE_ASSERT((is_same::type, long>::value), + RemoveReference); +COMPILE_ASSERT((is_same::type, + std::string>::value), RemoveReference); +COMPILE_ASSERT((is_same::type, const int>::value), + RemoveReference); +COMPILE_ASSERT((is_same::type, const long>::value), + RemoveReference); +COMPILE_ASSERT((is_same::type, + const std::string>::value), RemoveReference); +COMPILE_ASSERT((is_same::type, int>::value), RemoveReference); +COMPILE_ASSERT((is_same::type, long>::value), RemoveReference); +COMPILE_ASSERT((is_same::type, std::string>::value), + RemoveReference); + + + +} // namespace +} // namespace butil diff --git a/test/unique_ptr_unittest.cpp b/test/unique_ptr_unittest.cpp new file mode 100644 index 0000000..f9a4635 --- /dev/null +++ b/test/unique_ptr_unittest.cpp @@ -0,0 +1,66 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "butil/unique_ptr.h" + +namespace { + +class UniquePtrTest : public testing::Test { +protected: + virtual void SetUp() { + }; +}; + +struct Foo { + Foo() : destroyed(false), called_func(false) {} + void destroy() { destroyed = true; } + void func() { called_func = true; } + bool destroyed; + bool called_func; +}; + +struct FooDeleter { + void operator()(Foo* f) const { + f->destroy(); + } +}; + +TEST_F(UniquePtrTest, basic) { + Foo foo; + ASSERT_FALSE(foo.destroyed); + ASSERT_FALSE(foo.called_func); + { + std::unique_ptr foo_ptr(&foo); + foo_ptr->func(); + ASSERT_TRUE(foo.called_func); + } + ASSERT_TRUE(foo.destroyed); +} + +static std::unique_ptr generate_foo(Foo* foo) { + std::unique_ptr foo_ptr(foo); + return foo_ptr; +} + +TEST_F(UniquePtrTest, return_unique_ptr) { + Foo* foo = new Foo; + std::unique_ptr foo_ptr = generate_foo(foo); + ASSERT_EQ(foo, foo_ptr.get()); +} + +} diff --git a/test/utf_offset_string_conversions_unittest.cc b/test/utf_offset_string_conversions_unittest.cc new file mode 100644 index 0000000..ca407de --- /dev/null +++ b/test/utf_offset_string_conversions_unittest.cc @@ -0,0 +1,296 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "butil/logging.h" +#include "butil/strings/string_piece.h" +#include "butil/strings/utf_offset_string_conversions.h" +#include + +namespace butil { + +namespace { + +static const size_t kNpos = string16::npos; + +} // namespace + +TEST(UTFOffsetStringConversionsTest, AdjustOffset) { + struct UTF8ToUTF16Case { + const char* utf8; + size_t input_offset; + size_t output_offset; + } utf8_to_utf16_cases[] = { + {"", 0, 0}, + {"", kNpos, kNpos}, + {"\xe4\xbd\xa0\xe5\xa5\xbd", 1, kNpos}, + {"\xe4\xbd\xa0\xe5\xa5\xbd", 3, 1}, + {"\xed\xb0\x80z", 3, 1}, + {"A\xF0\x90\x8C\x80z", 1, 1}, + {"A\xF0\x90\x8C\x80z", 2, kNpos}, + {"A\xF0\x90\x8C\x80z", 5, 3}, + {"A\xF0\x90\x8C\x80z", 6, 4}, + {"A\xF0\x90\x8C\x80z", kNpos, kNpos}, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(utf8_to_utf16_cases); ++i) { + const size_t offset = utf8_to_utf16_cases[i].input_offset; + std::vector offsets; + offsets.push_back(offset); + UTF8ToUTF16AndAdjustOffsets(utf8_to_utf16_cases[i].utf8, &offsets); + EXPECT_EQ(utf8_to_utf16_cases[i].output_offset, offsets[0]); + } + + struct UTF16ToUTF8Case { + char16 utf16[10]; + size_t input_offset; + size_t output_offset; + } utf16_to_utf8_cases[] = { + {{}, 0, 0}, + // Converted to 3-byte utf-8 sequences + {{0x5909, 0x63DB}, 3, kNpos}, + {{0x5909, 0x63DB}, 2, 6}, + {{0x5909, 0x63DB}, 1, 3}, + {{0x5909, 0x63DB}, 0, 0}, + // Converted to 2-byte utf-8 sequences + {{'A', 0x00bc, 0x00be, 'z'}, 1, 1}, + {{'A', 0x00bc, 0x00be, 'z'}, 2, 3}, + {{'A', 0x00bc, 0x00be, 'z'}, 3, 5}, + {{'A', 0x00bc, 0x00be, 'z'}, 4, 6}, + // Surrogate pair + {{'A', 0xd800, 0xdf00, 'z'}, 1, 1}, + {{'A', 0xd800, 0xdf00, 'z'}, 2, kNpos}, + {{'A', 0xd800, 0xdf00, 'z'}, 3, 5}, + {{'A', 0xd800, 0xdf00, 'z'}, 4, 6}, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(utf16_to_utf8_cases); ++i) { + size_t offset = utf16_to_utf8_cases[i].input_offset; + std::vector offsets; + offsets.push_back(offset); + UTF16ToUTF8AndAdjustOffsets(utf16_to_utf8_cases[i].utf16, &offsets); + EXPECT_EQ(utf16_to_utf8_cases[i].output_offset, offsets[0]) << i; + } +} + +TEST(UTFOffsetStringConversionsTest, LimitOffsets) { + const size_t kLimit = 10; + const size_t kItems = 20; + std::vector size_ts; + for (size_t t = 0; t < kItems; ++t) + size_ts.push_back(t); + std::for_each(size_ts.begin(), size_ts.end(), + LimitOffset(kLimit)); + size_t unlimited_count = 0; + for (std::vector::iterator ti = size_ts.begin(); ti != size_ts.end(); + ++ti) { + if (*ti != kNpos) + ++unlimited_count; + } + EXPECT_EQ(11U, unlimited_count); + + // Reverse the values in the vector and try again. + size_ts.clear(); + for (size_t t = kItems; t > 0; --t) + size_ts.push_back(t - 1); + std::for_each(size_ts.begin(), size_ts.end(), + LimitOffset(kLimit)); + unlimited_count = 0; + for (std::vector::iterator ti = size_ts.begin(); ti != size_ts.end(); + ++ti) { + if (*ti != kNpos) + ++unlimited_count; + } + EXPECT_EQ(11U, unlimited_count); +} + +TEST(UTFOffsetStringConversionsTest, AdjustOffsets) { + // Imagine we have strings as shown in the following cases where the + // X's represent encoded characters. + // 1: abcXXXdef ==> abcXdef + { + std::vector offsets; + for (size_t t = 0; t <= 9; ++t) + offsets.push_back(t); + OffsetAdjuster::Adjustments adjustments; + adjustments.push_back(OffsetAdjuster::Adjustment(3, 3, 1)); + OffsetAdjuster::AdjustOffsets(adjustments, &offsets); + size_t expected_1[] = {0, 1, 2, 3, kNpos, kNpos, 4, 5, 6, 7}; + EXPECT_EQ(offsets.size(), arraysize(expected_1)); + for (size_t i = 0; i < arraysize(expected_1); ++i) + EXPECT_EQ(expected_1[i], offsets[i]); + } + + // 2: XXXaXXXXbcXXXXXXXdefXXX ==> XaXXbcXXXXdefX + { + std::vector offsets; + for (size_t t = 0; t <= 23; ++t) + offsets.push_back(t); + OffsetAdjuster::Adjustments adjustments; + adjustments.push_back(OffsetAdjuster::Adjustment(0, 3, 1)); + adjustments.push_back(OffsetAdjuster::Adjustment(4, 4, 2)); + adjustments.push_back(OffsetAdjuster::Adjustment(10, 7, 4)); + adjustments.push_back(OffsetAdjuster::Adjustment(20, 3, 1)); + OffsetAdjuster::AdjustOffsets(adjustments, &offsets); + size_t expected_2[] = { + 0, kNpos, kNpos, 1, 2, kNpos, kNpos, kNpos, 4, 5, 6, kNpos, kNpos, kNpos, + kNpos, kNpos, kNpos, 10, 11, 12, 13, kNpos, kNpos, 14 + }; + EXPECT_EQ(offsets.size(), arraysize(expected_2)); + for (size_t i = 0; i < arraysize(expected_2); ++i) + EXPECT_EQ(expected_2[i], offsets[i]); + } + + // 3: XXXaXXXXbcdXXXeXX ==> aXXXXbcdXXXe + { + std::vector offsets; + for (size_t t = 0; t <= 17; ++t) + offsets.push_back(t); + OffsetAdjuster::Adjustments adjustments; + adjustments.push_back(OffsetAdjuster::Adjustment(0, 3, 0)); + adjustments.push_back(OffsetAdjuster::Adjustment(4, 4, 4)); + adjustments.push_back(OffsetAdjuster::Adjustment(11, 3, 3)); + adjustments.push_back(OffsetAdjuster::Adjustment(15, 2, 0)); + OffsetAdjuster::AdjustOffsets(adjustments, &offsets); + size_t expected_3[] = { + 0, kNpos, kNpos, 0, 1, kNpos, kNpos, kNpos, 5, 6, 7, 8, kNpos, kNpos, 11, + 12, kNpos, 12 + }; + EXPECT_EQ(offsets.size(), arraysize(expected_3)); + for (size_t i = 0; i < arraysize(expected_3); ++i) + EXPECT_EQ(expected_3[i], offsets[i]); + } +} + +TEST(UTFOffsetStringConversionsTest, UnadjustOffsets) { + // Imagine we have strings as shown in the following cases where the + // X's represent encoded characters. + // 1: abcXXXdef ==> abcXdef + { + std::vector offsets; + for (size_t t = 0; t <= 7; ++t) + offsets.push_back(t); + OffsetAdjuster::Adjustments adjustments; + adjustments.push_back(OffsetAdjuster::Adjustment(3, 3, 1)); + OffsetAdjuster::UnadjustOffsets(adjustments, &offsets); + size_t expected_1[] = {0, 1, 2, 3, 6, 7, 8, 9}; + EXPECT_EQ(offsets.size(), arraysize(expected_1)); + for (size_t i = 0; i < arraysize(expected_1); ++i) + EXPECT_EQ(expected_1[i], offsets[i]); + } + + // 2: XXXaXXXXbcXXXXXXXdefXXX ==> XaXXbcXXXXdefX + { + std::vector offsets; + for (size_t t = 0; t <= 14; ++t) + offsets.push_back(t); + OffsetAdjuster::Adjustments adjustments; + adjustments.push_back(OffsetAdjuster::Adjustment(0, 3, 1)); + adjustments.push_back(OffsetAdjuster::Adjustment(4, 4, 2)); + adjustments.push_back(OffsetAdjuster::Adjustment(10, 7, 4)); + adjustments.push_back(OffsetAdjuster::Adjustment(20, 3, 1)); + OffsetAdjuster::UnadjustOffsets(adjustments, &offsets); + size_t expected_2[] = { + 0, 3, 4, kNpos, 8, 9, 10, kNpos, kNpos, kNpos, 17, 18, 19, 20, 23 + }; + EXPECT_EQ(offsets.size(), arraysize(expected_2)); + for (size_t i = 0; i < arraysize(expected_2); ++i) + EXPECT_EQ(expected_2[i], offsets[i]); + } + + // 3: XXXaXXXXbcdXXXeXX ==> aXXXXbcdXXXe + { + std::vector offsets; + for (size_t t = 0; t <= 12; ++t) + offsets.push_back(t); + OffsetAdjuster::Adjustments adjustments; + adjustments.push_back(OffsetAdjuster::Adjustment(0, 3, 0)); + adjustments.push_back(OffsetAdjuster::Adjustment(4, 4, 4)); + adjustments.push_back(OffsetAdjuster::Adjustment(11, 3, 3)); + adjustments.push_back(OffsetAdjuster::Adjustment(15, 2, 0)); + OffsetAdjuster::UnadjustOffsets(adjustments, &offsets); + size_t expected_3[] = { + 0, // this could just as easily be 3 + 4, kNpos, kNpos, kNpos, 8, 9, 10, 11, kNpos, kNpos, 14, + 15 // this could just as easily be 17 + }; + EXPECT_EQ(offsets.size(), arraysize(expected_3)); + for (size_t i = 0; i < arraysize(expected_3); ++i) + EXPECT_EQ(expected_3[i], offsets[i]); + } +} + +// MergeSequentialAdjustments is used by net/butil/escape.{h,cc} and +// net/butil/net_util.{h,cc}. The two tests EscapeTest.AdjustOffset and +// NetUtilTest.FormatUrlWithOffsets test its behavior extensively. This +// is simply a short, additional test. +TEST(UTFOffsetStringConversionsTest, MergeSequentialAdjustments) { + // Pretend the input string is "abcdefghijklmnopqrstuvwxyz". + + // Set up |first_adjustments| to + // - remove the leading "a" + // - combine the "bc" into one character (call it ".") + // - remove the "f" + // - remove the "tuv" + // The resulting string should be ".deghijklmnopqrswxyz". + OffsetAdjuster::Adjustments first_adjustments; + first_adjustments.push_back(OffsetAdjuster::Adjustment(0, 1, 0)); + first_adjustments.push_back(OffsetAdjuster::Adjustment(1, 2, 1)); + first_adjustments.push_back(OffsetAdjuster::Adjustment(5, 1, 0)); + first_adjustments.push_back(OffsetAdjuster::Adjustment(19, 3, 0)); + + // Set up |adjustments_on_adjusted_string| to + // - combine the "." character that replaced "bc" with "d" into one character + // (call it "?") + // - remove the "egh" + // - expand the "i" into two characters (call them "12") + // - combine the "jkl" into one character (call it "@") + // - expand the "z" into two characters (call it "34") + // The resulting string should be "?12@mnopqrswxy34". + OffsetAdjuster::Adjustments adjustments_on_adjusted_string; + adjustments_on_adjusted_string.push_back(OffsetAdjuster::Adjustment( + 0, 2, 1)); + adjustments_on_adjusted_string.push_back(OffsetAdjuster::Adjustment( + 2, 3, 0)); + adjustments_on_adjusted_string.push_back(OffsetAdjuster::Adjustment( + 5, 1, 2)); + adjustments_on_adjusted_string.push_back(OffsetAdjuster::Adjustment( + 6, 3, 1)); + adjustments_on_adjusted_string.push_back(OffsetAdjuster::Adjustment( + 19, 1, 2)); + + // Now merge the adjustments and check the results. + OffsetAdjuster::MergeSequentialAdjustments(first_adjustments, + &adjustments_on_adjusted_string); + // The merged adjustments should look like + // - combine abcd into "?" + // - note: it's also reasonable for the Merge function to instead produce + // two adjustments instead of this, one to remove a and another to + // combine bcd into "?". This test verifies the current behavior. + // - remove efgh + // - expand i into "12" + // - combine jkl into "@" + // - remove tuv + // - expand z into "34" + ASSERT_EQ(6u, adjustments_on_adjusted_string.size()); + EXPECT_EQ(0u, adjustments_on_adjusted_string[0].original_offset); + EXPECT_EQ(4u, adjustments_on_adjusted_string[0].original_length); + EXPECT_EQ(1u, adjustments_on_adjusted_string[0].output_length); + EXPECT_EQ(4u, adjustments_on_adjusted_string[1].original_offset); + EXPECT_EQ(4u, adjustments_on_adjusted_string[1].original_length); + EXPECT_EQ(0u, adjustments_on_adjusted_string[1].output_length); + EXPECT_EQ(8u, adjustments_on_adjusted_string[2].original_offset); + EXPECT_EQ(1u, adjustments_on_adjusted_string[2].original_length); + EXPECT_EQ(2u, adjustments_on_adjusted_string[2].output_length); + EXPECT_EQ(9u, adjustments_on_adjusted_string[3].original_offset); + EXPECT_EQ(3u, adjustments_on_adjusted_string[3].original_length); + EXPECT_EQ(1u, adjustments_on_adjusted_string[3].output_length); + EXPECT_EQ(19u, adjustments_on_adjusted_string[4].original_offset); + EXPECT_EQ(3u, adjustments_on_adjusted_string[4].original_length); + EXPECT_EQ(0u, adjustments_on_adjusted_string[4].output_length); + EXPECT_EQ(25u, adjustments_on_adjusted_string[5].original_offset); + EXPECT_EQ(1u, adjustments_on_adjusted_string[5].original_length); + EXPECT_EQ(2u, adjustments_on_adjusted_string[5].output_length); +} + +} // namespace butil diff --git a/test/utf_string_conversions_unittest.cc b/test/utf_string_conversions_unittest.cc new file mode 100644 index 0000000..c9d7aeb --- /dev/null +++ b/test/utf_string_conversions_unittest.cc @@ -0,0 +1,211 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/basictypes.h" +#include "butil/logging.h" +#include "butil/strings/string_piece.h" +#include "butil/strings/string_util.h" +#include "butil/strings/utf_string_conversions.h" +#include + +namespace butil { + +namespace { + +const wchar_t* const kConvertRoundtripCases[] = { + L"Google Video", + // "网页 图片 资讯更多 »" + L"\x7f51\x9875\x0020\x56fe\x7247\x0020\x8d44\x8baf\x66f4\x591a\x0020\x00bb", + // "Παγκόσμιος Ιστός" + L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9" + L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2", + // "Поиск страниц на русском" + L"\x041f\x043e\x0438\x0441\x043a\x0020\x0441\x0442" + L"\x0440\x0430\x043d\x0438\x0446\x0020\x043d\x0430" + L"\x0020\x0440\x0443\x0441\x0441\x043a\x043e\x043c", + // "전체서비스" + L"\xc804\xccb4\xc11c\xbe44\xc2a4", + + // Test characters that take more than 16 bits. This will depend on whether + // wchar_t is 16 or 32 bits. +#if defined(WCHAR_T_IS_UTF16) + L"\xd800\xdf00", + // ????? (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E) + L"\xd807\xdd40\xd807\xdd41\xd807\xdd42\xd807\xdd43\xd807\xdd44", +#elif defined(WCHAR_T_IS_UTF32) + L"\x10300", + // ????? (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E) + L"\x11d40\x11d41\x11d42\x11d43\x11d44", +#endif +}; + +} // namespace + +TEST(UTFStringConversionsTest, ConvertUTF8AndWide) { + // we round-trip all the wide strings through UTF-8 to make sure everything + // agrees on the conversion. This uses the stream operators to test them + // simultaneously. + for (size_t i = 0; i < arraysize(kConvertRoundtripCases); ++i) { + std::ostringstream utf8; + utf8 << WideToUTF8(kConvertRoundtripCases[i]); + std::wostringstream wide; + wide << UTF8ToWide(utf8.str()); + + EXPECT_EQ(kConvertRoundtripCases[i], wide.str()); + } +} + +TEST(UTFStringConversionsTest, ConvertUTF8AndWideEmptyString) { + // An empty std::wstring should be converted to an empty std::string, + // and vice versa. + std::wstring wempty; + std::string empty; + EXPECT_EQ(empty, WideToUTF8(wempty)); + EXPECT_EQ(wempty, UTF8ToWide(empty)); +} + +TEST(UTFStringConversionsTest, ConvertUTF8ToWide) { + struct UTF8ToWideCase { + const char* utf8; + const wchar_t* wide; + bool success; + } convert_cases[] = { + // Regular UTF-8 input. + {"\xe4\xbd\xa0\xe5\xa5\xbd", L"\x4f60\x597d", true}, + // Non-character is passed through. + {"\xef\xbf\xbfHello", L"\xffffHello", true}, + // Truncated UTF-8 sequence. + {"\xe4\xa0\xe5\xa5\xbd", L"\xfffd\x597d", false}, + // Truncated off the end. + {"\xe5\xa5\xbd\xe4\xa0", L"\x597d\xfffd", false}, + // Non-shortest-form UTF-8. + {"\xf0\x84\xbd\xa0\xe5\xa5\xbd", L"\xfffd\x597d", false}, + // This UTF-8 character decodes to a UTF-16 surrogate, which is illegal. + {"\xed\xb0\x80", L"\xfffd", false}, + // Non-BMP characters. The second is a non-character regarded as valid. + // The result will either be in UTF-16 or UTF-32. +#if defined(WCHAR_T_IS_UTF16) + {"A\xF0\x90\x8C\x80z", L"A\xd800\xdf00z", true}, + {"A\xF4\x8F\xBF\xBEz", L"A\xdbff\xdffez", true}, +#elif defined(WCHAR_T_IS_UTF32) + {"A\xF0\x90\x8C\x80z", L"A\x10300z", true}, + {"A\xF4\x8F\xBF\xBEz", L"A\x10fffez", true}, +#endif + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(convert_cases); i++) { + std::wstring converted; + EXPECT_EQ(convert_cases[i].success, + UTF8ToWide(convert_cases[i].utf8, + strlen(convert_cases[i].utf8), + &converted)); + std::wstring expected(convert_cases[i].wide); + EXPECT_EQ(expected, converted); + } + + // Manually test an embedded NULL. + std::wstring converted; + EXPECT_TRUE(UTF8ToWide("\00Z\t", 3, &converted)); + ASSERT_EQ(3U, converted.length()); + EXPECT_EQ(static_cast(0), converted[0]); + EXPECT_EQ('Z', converted[1]); + EXPECT_EQ('\t', converted[2]); + + // Make sure that conversion replaces, not appends. + EXPECT_TRUE(UTF8ToWide("B", 1, &converted)); + ASSERT_EQ(1U, converted.length()); + EXPECT_EQ('B', converted[0]); +} + +#if defined(WCHAR_T_IS_UTF16) +// This test is only valid when wchar_t == UTF-16. +TEST(UTFStringConversionsTest, ConvertUTF16ToUTF8) { + struct WideToUTF8Case { + const wchar_t* utf16; + const char* utf8; + bool success; + } convert_cases[] = { + // Regular UTF-16 input. + {L"\x4f60\x597d", "\xe4\xbd\xa0\xe5\xa5\xbd", true}, + // Test a non-BMP character. + {L"\xd800\xdf00", "\xF0\x90\x8C\x80", true}, + // Non-characters are passed through. + {L"\xffffHello", "\xEF\xBF\xBFHello", true}, + {L"\xdbff\xdffeHello", "\xF4\x8F\xBF\xBEHello", true}, + // The first character is a truncated UTF-16 character. + {L"\xd800\x597d", "\xef\xbf\xbd\xe5\xa5\xbd", false}, + // Truncated at the end. + {L"\x597d\xd800", "\xe5\xa5\xbd\xef\xbf\xbd", false}, + }; + + for (int i = 0; i < arraysize(convert_cases); i++) { + std::string converted; + EXPECT_EQ(convert_cases[i].success, + WideToUTF8(convert_cases[i].utf16, + wcslen(convert_cases[i].utf16), + &converted)); + std::string expected(convert_cases[i].utf8); + EXPECT_EQ(expected, converted); + } +} + +#elif defined(WCHAR_T_IS_UTF32) +// This test is only valid when wchar_t == UTF-32. +TEST(UTFStringConversionsTest, ConvertUTF32ToUTF8) { + struct WideToUTF8Case { + const wchar_t* utf32; + const char* utf8; + bool success; + } convert_cases[] = { + // Regular 16-bit input. + {L"\x4f60\x597d", "\xe4\xbd\xa0\xe5\xa5\xbd", true}, + // Test a non-BMP character. + {L"A\x10300z", "A\xF0\x90\x8C\x80z", true}, + // Non-characters are passed through. + {L"\xffffHello", "\xEF\xBF\xBFHello", true}, + {L"\x10fffeHello", "\xF4\x8F\xBF\xBEHello", true}, + // Invalid Unicode code points. + {L"\xfffffffHello", "\xEF\xBF\xBDHello", false}, + // The first character is a truncated UTF-16 character. + {L"\xd800\x597d", "\xef\xbf\xbd\xe5\xa5\xbd", false}, + {L"\xdc01Hello", "\xef\xbf\xbdHello", false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(convert_cases); i++) { + std::string converted; + EXPECT_EQ(convert_cases[i].success, + WideToUTF8(convert_cases[i].utf32, + wcslen(convert_cases[i].utf32), + &converted)); + std::string expected(convert_cases[i].utf8); + EXPECT_EQ(expected, converted); + } +} +#endif // defined(WCHAR_T_IS_UTF32) + +TEST(UTFStringConversionsTest, ConvertMultiString) { + static wchar_t wmulti[] = { + L'f', L'o', L'o', L'\0', + L'b', L'a', L'r', L'\0', + L'b', L'a', L'z', L'\0', + L'\0' + }; + static char multi[] = { + 'f', 'o', 'o', '\0', + 'b', 'a', 'r', '\0', + 'b', 'a', 'z', '\0', + '\0' + }; + std::wstring wmultistring; + memcpy(WriteInto(&wmultistring, arraysize(wmulti)), wmulti, sizeof(wmulti)); + EXPECT_EQ(arraysize(wmulti) - 1, wmultistring.length()); + std::string expected; + memcpy(WriteInto(&expected, arraysize(multi)), multi, sizeof(multi)); + EXPECT_EQ(arraysize(multi) - 1, expected.length()); + const std::string& converted = WideToUTF8(wmultistring); + EXPECT_EQ(arraysize(multi) - 1, converted.length()); + EXPECT_EQ(expected, converted); +} + +} // namespace butil diff --git a/test/v1.proto b/test/v1.proto new file mode 100644 index 0000000..25adfa8 --- /dev/null +++ b/test/v1.proto @@ -0,0 +1,38 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +// for testing adding same named services in different packages into server +package v1; + +option cc_generic_services = true; + +message EchoRequest { + required string message = 1; +}; + +message EchoResponse { + required string message = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); + rpc Echo2(EchoRequest) returns (EchoResponse); + rpc Echo3(EchoRequest) returns (EchoResponse); + rpc Echo4(EchoRequest) returns (EchoResponse); + rpc Echo5(EchoRequest) returns (EchoResponse); +}; diff --git a/test/v2.proto b/test/v2.proto new file mode 100644 index 0000000..920eec3 --- /dev/null +++ b/test/v2.proto @@ -0,0 +1,34 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +// for testing adding same named services in different packages into server +package v2; + +option cc_generic_services = true; + +message EchoRequest { + required int32 value = 1; +}; + +message EchoResponse { + required int32 value = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; diff --git a/test/v3.proto b/test/v3.proto new file mode 100644 index 0000000..b920832 --- /dev/null +++ b/test/v3.proto @@ -0,0 +1,38 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto3"; + +package v3; + +option cc_generic_services = true; +// https://github.com/protocolbuffers/protobuf/releases/tag/v3.14.0 +// Since Protocol Buffers v3.14.0, Arenas are now unconditionally enabled. +// cc_enable_arenas no longer has any effect. +option cc_enable_arenas = true; + +message EchoRequest { + string message = 1; +}; + +message EchoResponse { + string message = 1; +}; + +service EchoService { + rpc Echo(EchoRequest) returns (EchoResponse); +}; diff --git a/test/version_unittest.cc b/test/version_unittest.cc new file mode 100644 index 0000000..c21bddc --- /dev/null +++ b/test/version_unittest.cc @@ -0,0 +1,142 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/version.h" + +#include + +namespace { + +TEST(VersionTest, DefaultConstructor) { + Version v; + EXPECT_FALSE(v.IsValid()); +} + +TEST(VersionTest, ValueSemantics) { + Version v1("1.2.3.4"); + EXPECT_TRUE(v1.IsValid()); + Version v3; + EXPECT_FALSE(v3.IsValid()); + { + Version v2(v1); + v3 = v2; + EXPECT_TRUE(v2.IsValid()); + EXPECT_TRUE(v1.Equals(v2)); + } + EXPECT_TRUE(v3.Equals(v1)); +} + +TEST(VersionTest, GetVersionFromString) { + static const struct version_string { + const char* input; + size_t parts; + bool success; + } cases[] = { + {"", 0, false}, + {" ", 0, false}, + {"\t", 0, false}, + {"\n", 0, false}, + {" ", 0, false}, + {".", 0, false}, + {" . ", 0, false}, + {"0", 1, true}, + {"0.0", 2, true}, + {"65537.0", 0, false}, + {"-1.0", 0, false}, + {"1.-1.0", 0, false}, + {"+1.0", 0, false}, + {"1.+1.0", 0, false}, + {"1.0a", 0, false}, + {"1.2.3.4.5.6.7.8.9.0", 10, true}, + {"02.1", 0, false}, + {"f.1", 0, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + Version version(cases[i].input); + EXPECT_EQ(cases[i].success, version.IsValid()); + if (cases[i].success) { + EXPECT_EQ(cases[i].parts, version.components().size()); + } + } +} + +TEST(VersionTest, Compare) { + static const struct version_compare { + const char* lhs; + const char* rhs; + int expected; + } cases[] = { + {"1.0", "1.0", 0}, + {"1.0", "0.0", 1}, + {"1.0", "2.0", -1}, + {"1.0", "1.1", -1}, + {"1.1", "1.0", 1}, + {"1.0", "1.0.1", -1}, + {"1.1", "1.0.1", 1}, + {"1.1", "1.0.1", 1}, + {"1.0.0", "1.0", 0}, + {"1.0.3", "1.0.20", -1}, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + Version lhs(cases[i].lhs); + Version rhs(cases[i].rhs); + EXPECT_EQ(lhs.CompareTo(rhs), cases[i].expected) << + cases[i].lhs << " ? " << cases[i].rhs; + + EXPECT_EQ(lhs.IsOlderThan(cases[i].rhs), (cases[i].expected == -1)); + } +} + +TEST(VersionTest, CompareToWildcardString) { + static const struct version_compare { + const char* lhs; + const char* rhs; + int expected; + } cases[] = { + {"1.0", "1.*", 0}, + {"1.0", "0.*", 1}, + {"1.0", "2.*", -1}, + {"1.2.3", "1.2.3.*", 0}, + {"10.0", "1.0.*", 1}, + {"1.0", "3.0.*", -1}, + {"1.4", "1.3.0.*", 1}, + {"1.3.9", "1.3.*", 0}, + {"1.4.1", "1.3.*", 1}, + {"1.3", "1.4.5.*", -1}, + {"1.5", "1.4.5.*", 1}, + {"1.3.9", "1.3.*", 0}, + {"1.2.0.0.0.0", "1.2.*", 0}, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + const Version version(cases[i].lhs); + const int result = version.CompareToWildcardString(cases[i].rhs); + EXPECT_EQ(result, cases[i].expected) << cases[i].lhs << "?" << cases[i].rhs; + } +} + +TEST(VersionTest, IsValidWildcardString) { + static const struct version_compare { + const char* version; + bool expected; + } cases[] = { + {"1.0", true}, + {"", false}, + {"1.2.3.4.5.6", true}, + {"1.2.3.*", true}, + {"1.2.3.5*", false}, + {"1.2.3.56*", false}, + {"1.*.3", false}, + {"20.*", true}, + {"+2.*", false}, + {"*", false}, + {"*.2", false}, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + EXPECT_EQ(Version::IsValidWildcardString(cases[i].version), + cases[i].expected) << cases[i].version << "?" << cases[i].expected; + } +} + +} // namespace diff --git a/test/waitable_event_unittest.cc b/test/waitable_event_unittest.cc new file mode 100644 index 0000000..12282bd --- /dev/null +++ b/test/waitable_event_unittest.cc @@ -0,0 +1,108 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/synchronization/waitable_event.h" + +#include "butil/compiler_specific.h" +#include "butil/threading/platform_thread.h" +#include "butil/time/time.h" +#include + +namespace butil { + +TEST(WaitableEventTest, ManualBasics) { + WaitableEvent event(true, false); + + EXPECT_FALSE(event.IsSignaled()); + + event.Signal(); + EXPECT_TRUE(event.IsSignaled()); + EXPECT_TRUE(event.IsSignaled()); + + event.Reset(); + EXPECT_FALSE(event.IsSignaled()); + EXPECT_FALSE(event.TimedWait(TimeDelta::FromMilliseconds(10))); + + event.Signal(); + event.Wait(); + EXPECT_TRUE(event.TimedWait(TimeDelta::FromMilliseconds(10))); +} + +TEST(WaitableEventTest, AutoBasics) { + WaitableEvent event(false, false); + + EXPECT_FALSE(event.IsSignaled()); + + event.Signal(); + EXPECT_TRUE(event.IsSignaled()); + EXPECT_FALSE(event.IsSignaled()); + + event.Reset(); + EXPECT_FALSE(event.IsSignaled()); + EXPECT_FALSE(event.TimedWait(TimeDelta::FromMilliseconds(10))); + + event.Signal(); + event.Wait(); + EXPECT_FALSE(event.TimedWait(TimeDelta::FromMilliseconds(10))); + + event.Signal(); + EXPECT_TRUE(event.TimedWait(TimeDelta::FromMilliseconds(10))); +} + +TEST(WaitableEventTest, WaitManyShortcut) { + WaitableEvent* ev[5]; + for (unsigned i = 0; i < 5; ++i) + ev[i] = new WaitableEvent(false, false); + + ev[3]->Signal(); + EXPECT_EQ(WaitableEvent::WaitMany(ev, 5), 3u); + + ev[3]->Signal(); + EXPECT_EQ(WaitableEvent::WaitMany(ev, 5), 3u); + + ev[4]->Signal(); + EXPECT_EQ(WaitableEvent::WaitMany(ev, 5), 4u); + + ev[0]->Signal(); + EXPECT_EQ(WaitableEvent::WaitMany(ev, 5), 0u); + + for (unsigned i = 0; i < 5; ++i) + delete ev[i]; +} + +class WaitableEventSignaler : public PlatformThread::Delegate { + public: + WaitableEventSignaler(double seconds, WaitableEvent* ev) + : seconds_(seconds), + ev_(ev) { + } + + virtual void ThreadMain() OVERRIDE { + PlatformThread::Sleep(TimeDelta::FromSeconds(static_cast(seconds_))); + ev_->Signal(); + } + + private: + const double seconds_; + WaitableEvent *const ev_; +}; + +TEST(WaitableEventTest, WaitMany) { + WaitableEvent* ev[5]; + for (unsigned i = 0; i < 5; ++i) + ev[i] = new WaitableEvent(false, false); + + WaitableEventSignaler signaler(0.1, ev[2]); + PlatformThreadHandle thread; + PlatformThread::Create(0, &signaler, &thread); + + EXPECT_EQ(WaitableEvent::WaitMany(ev, 5), 2u); + + PlatformThread::Join(thread); + + for (unsigned i = 0; i < 5; ++i) + delete ev[i]; +} + +} // namespace butil diff --git a/test/watchdog_unittest.cc b/test/watchdog_unittest.cc new file mode 100644 index 0000000..91590e7 --- /dev/null +++ b/test/watchdog_unittest.cc @@ -0,0 +1,142 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/threading/watchdog.h" + +#include "butil/logging.h" +#include "butil/synchronization/spin_wait.h" +#include "butil/threading/platform_thread.h" +#include "butil/time/time.h" +#include + +namespace butil { + +namespace { + +//------------------------------------------------------------------------------ +// Provide a derived class to facilitate testing. + +class WatchdogCounter : public Watchdog { + public: + WatchdogCounter(const TimeDelta& duration, + const std::string& thread_watched_name, + bool enabled) + : Watchdog(duration, thread_watched_name, enabled), + alarm_counter_(0) { + } + + virtual ~WatchdogCounter() {} + + virtual void Alarm() OVERRIDE { + alarm_counter_++; + Watchdog::Alarm(); + } + + int alarm_counter() { return alarm_counter_; } + + private: + int alarm_counter_; + + DISALLOW_COPY_AND_ASSIGN(WatchdogCounter); +}; + +class WatchdogTest : public testing::Test { + public: + virtual void SetUp() OVERRIDE { + Watchdog::ResetStaticData(); + } +}; + +} // namespace + +//------------------------------------------------------------------------------ +// Actual tests + +// Minimal constructor/destructor test. +TEST_F(WatchdogTest, StartupShutdownTest) { + Watchdog watchdog1(TimeDelta::FromMilliseconds(300), "Disabled", false); + Watchdog watchdog2(TimeDelta::FromMilliseconds(300), "Enabled", true); +} + +// Test ability to call Arm and Disarm repeatedly. +TEST_F(WatchdogTest, ArmDisarmTest) { + Watchdog watchdog1(TimeDelta::FromMilliseconds(300), "Disabled", false); + watchdog1.Arm(); + watchdog1.Disarm(); + watchdog1.Arm(); + watchdog1.Disarm(); + + Watchdog watchdog2(TimeDelta::FromMilliseconds(300), "Enabled", true); + watchdog2.Arm(); + watchdog2.Disarm(); + watchdog2.Arm(); + watchdog2.Disarm(); +} + +// Make sure a basic alarm fires when the time has expired. +TEST_F(WatchdogTest, AlarmTest) { + WatchdogCounter watchdog(TimeDelta::FromMilliseconds(10), "Enabled", true); + watchdog.Arm(); + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(5), + watchdog.alarm_counter() > 0); + EXPECT_EQ(1, watchdog.alarm_counter()); +} + +// Make sure a basic alarm fires when the time has expired. +TEST_F(WatchdogTest, AlarmPriorTimeTest) { + WatchdogCounter watchdog(TimeDelta(), "Enabled2", true); + // Set a time in the past. + watchdog.ArmSomeTimeDeltaAgo(TimeDelta::FromSeconds(2)); + // It should instantly go off, but certainly in less than 5 minutes. + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(5), + watchdog.alarm_counter() > 0); + + EXPECT_EQ(1, watchdog.alarm_counter()); +} + +// Make sure a disable alarm does nothing, even if we arm it. +TEST_F(WatchdogTest, ConstructorDisabledTest) { + WatchdogCounter watchdog(TimeDelta::FromMilliseconds(10), "Disabled", false); + watchdog.Arm(); + // Alarm should not fire, as it was disabled. + PlatformThread::Sleep(TimeDelta::FromMilliseconds(500)); + EXPECT_EQ(0, watchdog.alarm_counter()); +} + +// Make sure Disarming will prevent firing, even after Arming. +TEST_F(WatchdogTest, DisarmTest) { + WatchdogCounter watchdog(TimeDelta::FromSeconds(1), "Enabled3", true); + + TimeTicks start = TimeTicks::Now(); + watchdog.Arm(); + // Sleep a bit, but not past the alarm point. + PlatformThread::Sleep(TimeDelta::FromMilliseconds(100)); + watchdog.Disarm(); + TimeTicks end = TimeTicks::Now(); + + if (end - start > TimeDelta::FromMilliseconds(500)) { + LOG(WARNING) << "100ms sleep took over 500ms, making the results of this " + << "timing-sensitive test suspicious. Aborting now."; + return; + } + + // Alarm should not have fired before it was disarmed. + EXPECT_EQ(0, watchdog.alarm_counter()); + + // Sleep past the point where it would have fired if it wasn't disarmed, + // and verify that it didn't fire. + PlatformThread::Sleep(TimeDelta::FromSeconds(1)); + EXPECT_EQ(0, watchdog.alarm_counter()); + + // ...but even after disarming, we can still use the alarm... + // Set a time greater than the timeout into the past. + watchdog.ArmSomeTimeDeltaAgo(TimeDelta::FromSeconds(10)); + // It should almost instantly go off, but certainly in less than 5 minutes. + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(5), + watchdog.alarm_counter() > 0); + + EXPECT_EQ(1, watchdog.alarm_counter()); +} + +} // namespace butil diff --git a/test/weak_ptr_unittest.cc b/test/weak_ptr_unittest.cc new file mode 100644 index 0000000..a4be37f --- /dev/null +++ b/test/weak_ptr_unittest.cc @@ -0,0 +1,152 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "butil/memory/weak_ptr.h" + +#include + +#include "butil/debug/leak_annotations.h" +#include "butil/memory/scoped_ptr.h" +#include "butil/synchronization/waitable_event.h" +#include + +namespace butil { +namespace { + +struct Base { + std::string member; +}; +struct Derived : public Base {}; + +struct TargetBase {}; +struct Target : public TargetBase, public SupportsWeakPtr { + virtual ~Target() {} +}; +struct DerivedTarget : public Target {}; +struct Arrow { + WeakPtr target; +}; +struct TargetWithFactory : public Target { + TargetWithFactory() : factory(this) {} + WeakPtrFactory factory; +}; + +} // namespace + +TEST(WeakPtrFactoryTest, Basic) { + int data; + WeakPtrFactory factory(&data); + WeakPtr ptr = factory.GetWeakPtr(); + EXPECT_EQ(&data, ptr.get()); +} + +TEST(WeakPtrFactoryTest, Comparison) { + int data; + WeakPtrFactory factory(&data); + WeakPtr ptr = factory.GetWeakPtr(); + WeakPtr ptr2 = ptr; + EXPECT_EQ(ptr.get(), ptr2.get()); +} + +TEST(WeakPtrFactoryTest, OutOfScope) { + WeakPtr ptr; + EXPECT_EQ(NULL, ptr.get()); + { + int data; + WeakPtrFactory factory(&data); + ptr = factory.GetWeakPtr(); + } + EXPECT_EQ(NULL, ptr.get()); +} + +TEST(WeakPtrFactoryTest, Multiple) { + WeakPtr a, b; + { + int data; + WeakPtrFactory factory(&data); + a = factory.GetWeakPtr(); + b = factory.GetWeakPtr(); + EXPECT_EQ(&data, a.get()); + EXPECT_EQ(&data, b.get()); + } + EXPECT_EQ(NULL, a.get()); + EXPECT_EQ(NULL, b.get()); +} + +TEST(WeakPtrFactoryTest, MultipleStaged) { + WeakPtr a; + { + int data; + WeakPtrFactory factory(&data); + a = factory.GetWeakPtr(); + { + WeakPtr b = factory.GetWeakPtr(); + } + EXPECT_TRUE(NULL != a.get()); + } + EXPECT_EQ(NULL, a.get()); +} + +TEST(WeakPtrFactoryTest, Dereference) { + Base data; + data.member = "123456"; + WeakPtrFactory factory(&data); + WeakPtr ptr = factory.GetWeakPtr(); + EXPECT_EQ(&data, ptr.get()); + EXPECT_EQ(data.member, (*ptr).member); + EXPECT_EQ(data.member, ptr->member); +} + +TEST(WeakPtrFactoryTest, UpCast) { + Derived data; + WeakPtrFactory factory(&data); + WeakPtr ptr = factory.GetWeakPtr(); + ptr = factory.GetWeakPtr(); + EXPECT_EQ(ptr.get(), &data); +} + +TEST(WeakPtrTest, SupportsWeakPtr) { + Target target; + WeakPtr ptr = target.AsWeakPtr(); + EXPECT_EQ(&target, ptr.get()); +} + +TEST(WeakPtrTest, DerivedTarget) { + DerivedTarget target; + WeakPtr ptr = AsWeakPtr(&target); + EXPECT_EQ(&target, ptr.get()); +} + +TEST(WeakPtrTest, InvalidateWeakPtrs) { + int data; + WeakPtrFactory factory(&data); + WeakPtr ptr = factory.GetWeakPtr(); + EXPECT_EQ(&data, ptr.get()); + EXPECT_TRUE(factory.HasWeakPtrs()); + factory.InvalidateWeakPtrs(); + EXPECT_EQ(NULL, ptr.get()); + EXPECT_FALSE(factory.HasWeakPtrs()); + + // Test that the factory can create new weak pointers after a + // InvalidateWeakPtrs call, and they remain valid until the next + // InvalidateWeakPtrs call. + WeakPtr ptr2 = factory.GetWeakPtr(); + EXPECT_EQ(&data, ptr2.get()); + EXPECT_TRUE(factory.HasWeakPtrs()); + factory.InvalidateWeakPtrs(); + EXPECT_EQ(NULL, ptr2.get()); + EXPECT_FALSE(factory.HasWeakPtrs()); +} + +TEST(WeakPtrTest, HasWeakPtrs) { + int data; + WeakPtrFactory factory(&data); + { + WeakPtr ptr = factory.GetWeakPtr(); + EXPECT_TRUE(factory.HasWeakPtrs()); + } + EXPECT_FALSE(factory.HasWeakPtrs()); +} + +} // namespace butil diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..bc2ae0e --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/output/bin) + +add_subdirectory(parallel_http) +add_subdirectory(rpc_press) +add_subdirectory(rpc_replay) +add_subdirectory(rpc_view) +add_subdirectory(trackme_server) diff --git a/tools/add_syntax_equal_proto2_to_all.sh b/tools/add_syntax_equal_proto2_to_all.sh new file mode 100644 index 0000000..bd94a94 --- /dev/null +++ b/tools/add_syntax_equal_proto2_to_all.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +# Add 'syntax="proto2";' to the beginning of all proto files under +# PWD(recursively) if the proto file does not have it. +for file in $(find . -name "*.proto"); do + if grep -q 'syntax\s*=\s*"proto2";' $file; then + echo "[already had] $file"; + else + sed -i '1s/^/syntax="proto2";\n/' $file + echo "[replaced] $file" + fi +done diff --git a/tools/gdb_bthread_stack.py b/tools/gdb_bthread_stack.py new file mode 100644 index 0000000..2b18e0a --- /dev/null +++ b/tools/gdb_bthread_stack.py @@ -0,0 +1,294 @@ +#!/usr/bin/env python +# coding=utf-8 + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +""" +Bthread Stack Print Tool + +this only for running process, core dump is not supported. + +Get Started: + 1. gdb attach + 2. source gdb_bthread_stack.py + 3. bthread_begin + 4. bthread_list + 5. bthread_frame 0 + 6. bt / up / down + 7. bthread_end + +Commands: + 1. bthread_num: print all bthread nums + 2. bthread_begin : enter bthread debug mode, `num` is max scanned bthreads, default will scan all + 3. bthread_list: list all bthreads + 4. bthread_frame : switch stack to bthread, id will displayed in bthread_list + 5. bthread_meta : print bthread meta + 6. bthread_reg_restore: bthread_frame will modify registers, reg_restore will restore them + 7. bthread_end: exit bthread debug mode + 8. bthread_regs : print bthread registers + 9. bthread_all: print all bthread frames + +when call bthread_frame, registers will be modified, +remember to call bthread_end after debug, or process will be corrupted + +after call bthread_frame, you can call `bt`/`up`/`down`, or other gdb command +""" + +import gdb + +bthreads = [] +status = False + +def get_bthread_num(): + root_agent = gdb.parse_and_eval("&(((*(((*bthread::g_task_control)._nbthreads)._combiner._M_ptr))._agents).root_)") + global_res = int(gdb.parse_and_eval("(*(((*bthread::g_task_control)._nbthreads)._combiner._M_ptr))._global_result")) + get_agent = "(*(('bvar::detail::AgentCombiner >::Agent' *){}))" + last_node = root_agent + long_type = gdb.lookup_type("long") + while True: + agent = gdb.parse_and_eval(get_agent.format(last_node)) + if last_node != root_agent: + val = int(agent["element"]["_value"].cast(long_type)) + gdb.parse_and_eval(get_agent.format(last_node)) + global_res += val + if agent["next_"] == root_agent: + return global_res + last_node = agent["next_"] + +def get_all_bthreads(total): + global bthreads + bthreads = [] + count = 0 + groups = int(gdb.parse_and_eval("(size_t)'butil::ResourcePool::_ngroup'")) + for group in range(groups): + blocks = int(gdb.parse_and_eval("(unsigned long)(*((*((('butil::static_atomic::BlockGroup*>' *)('butil::ResourcePool::_block_groups')) + {})).val)).nblock".format(group))) + for block in range(blocks): + items = int(gdb.parse_and_eval("(*(*(('butil::ResourcePool::Block' **)((*((*((('butil::static_atomic::BlockGroup*>' *)('butil::ResourcePool::_block_groups'))+ {})).val)).blocks) + {}))).nitem".format(group, block))) + for item in range(items): + task_meta = gdb.parse_and_eval("*(('bthread::TaskMeta' *)((*(*(('butil::ResourcePool::Block' **)((*((*((('butil::static_atomic::BlockGroup*>' *)('butil::ResourcePool::_block_groups')) + {})).val)).blocks) + {}))).items) + {})".format(group, block, item)) + version_tid = (int(task_meta["tid"]) >> 32) + version_butex = gdb.parse_and_eval( + "*(uint32_t *){}".format(task_meta["version_butex"])) + if version_tid == int(version_butex) and int(task_meta["attr"]["stack_type"]) != 0: + bthreads.append(task_meta) + count += 1 + if count >= total: + return + +class BthreadListCmd(gdb.Command): + """list all bthreads, print format is 'id\ttid\tfunction\thas stack'""" + def __init__(self): + gdb.Command.__init__(self, "bthread_list", gdb.COMMAND_STACK, gdb.COMPLETE_NONE) + + def invoke(self, arg, tty): + global status + global bthreads + if not status: + print("Not in bthread debug mode") + return + print("id\t\ttid\t\tfunction\t\thas stack\t\t\ttotal:{}".format(len(bthreads))) + for i, t in enumerate(bthreads): + print("#{}\t\t{}\t\t{}\t\t{}".format(i, t["tid"], t["fn"], "no" if str(t["stack"]) == "0x0" else "yes")) + +class BthreadNumCmd(gdb.Command): + """list active bthreads num""" + def __init__(self): + gdb.Command.__init__(self, "bthread_num", gdb.COMMAND_STACK, gdb.COMPLETE_NONE) + + def invoke(self, arg, tty): + res = get_bthread_num() + print(res) + +class BthreadFrameCmd(gdb.Command): + """bthread_frame , select bthread frame by id""" + def __init__(self): + gdb.Command.__init__(self, "bthread_frame", gdb.COMMAND_STACK, gdb.COMPLETE_NONE) + + def invoke(self, arg, tty): + global status + global bthreads + if not status: + print("Not in bthread debug mode") + return + if not arg: + print("bthread_frame , see 'bthread_list'") + return + bthread_id = int(arg) + if bthread_id >= len(bthreads): + print("id {} exceeds max bthread nums {}".format(bthread_id, len(bthreads))) + return + stack = bthreads[bthread_id]["stack"] + if str(stack) == "0x0": + print("this bthread has no stack") + return + context = gdb.parse_and_eval("(*(('bthread::ContextualStack' *){})).context".format(stack)) + rip = gdb.parse_and_eval("*(uint64_t*)({}+7*8)".format(context)) + rbp = gdb.parse_and_eval("*(uint64_t*)({}+6*8)".format(context)) + rsp = gdb.parse_and_eval("{}+8*8".format(context)) + gdb.parse_and_eval("$rip = {}".format(rip)) + gdb.parse_and_eval("$rsp = {}".format(rsp)) + gdb.parse_and_eval("$rbp = {}".format(rbp)) + +class BthreadAllCmd(gdb.Command): + """print all bthread frames""" + def __init__(self): + gdb.Command.__init__(self, "bthread_all", gdb.COMMAND_STACK, gdb.COMPLETE_NONE) + + def invoke(self, arg, tty): + global status + global bthreads + if not status: + print("Not in bthread debug mode") + return + for bthread_id in range(len(bthreads)): + stack = bthreads[bthread_id]["stack"] + if str(stack) == "0x0": + print("this bthread has no stack") + continue + try: + context = gdb.parse_and_eval("(*(('bthread::ContextualStack' *){})).context".format(stack)) + rip = gdb.parse_and_eval("*(uint64_t*)({}+7*8)".format(context)) + rbp = gdb.parse_and_eval("*(uint64_t*)({}+6*8)".format(context)) + rsp = gdb.parse_and_eval("{}+8*8".format(context)) + gdb.parse_and_eval("$rip = {}".format(rip)) + gdb.parse_and_eval("$rsp = {}".format(rsp)) + gdb.parse_and_eval("$rbp = {}".format(rbp)) + print("Bthread {}:".format(bthread_id)) + gdb.execute('bt') + except Exception as e: + pass + +class BthreadRegsCmd(gdb.Command): + """bthread_regs , print bthread registers""" + def __init__(self): + gdb.Command.__init__(self, "bthread_regs", gdb.COMMAND_STACK, gdb.COMPLETE_NONE) + + def invoke(self, arg, tty): + global status + global bthreads + if not status: + print("Not in bthread debug mode") + return + if not arg: + print("bthread_regs , see 'bthread_list'") + return + bthread_id = int(arg) + if bthread_id >= len(bthreads): + print("id {} exceeds max bthread nums {}".format(bthread_id, len(bthreads))) + return + stack = bthreads[bthread_id]["stack"] + if str(stack) == "0x0": + print("this bthread has no stack") + return + context = gdb.parse_and_eval("(*(('bthread::ContextualStack' *){})).context".format(stack)) + rip = int(gdb.parse_and_eval("*(uint64_t*)({}+7*8)".format(context))) + rbp = int(gdb.parse_and_eval("*(uint64_t*)({}+6*8)".format(context))) + rbx = int(gdb.parse_and_eval("*(uint64_t*)({}+5*8)".format(context))) + r15 = int(gdb.parse_and_eval("*(uint64_t*)({}+4*8)".format(context))) + r14 = int(gdb.parse_and_eval("*(uint64_t*)({}+3*8)".format(context))) + r13 = int(gdb.parse_and_eval("*(uint64_t*)({}+2*8)".format(context))) + r12 = int(gdb.parse_and_eval("*(uint64_t*)({}+1*8)".format(context))) + rsp = int(gdb.parse_and_eval("{}+8*8".format(context))) + print("rip: 0x{:x}\nrsp: 0x{:x}\nrbp: 0x{:x}\nrbx: 0x{:x}\nr15: 0x{:x}\nr14: 0x{:x}\nr13: 0x{:x}\nr12: 0x{:x}".format(rip, rsp, rbp, rbx, r15, r14, r13, r12)) + +class BthreadMetaCmd(gdb.Command): + """bthread_meta , print task meta by id""" + def __init__(self): + gdb.Command.__init__(self, "bthread_meta", gdb.COMMAND_STACK, gdb.COMPLETE_NONE) + + def invoke(self, arg, tty): + global status + global bthreads + if not status: + print("Not in bthread debug mode") + return + if not arg: + print("bthread_meta , see 'bthread_list'") + return + bthread_id = int(arg) + if bthread_id >= len(bthreads): + print("id {} exceeds max bthread nums {}".format(bthread_id, len(bthreads))) + return + print(bthreads[bthread_id]) + +class BthreadBeginCmd(gdb.Command): + """enter bthread debug mode""" + def __init__(self): + gdb.Command.__init__(self, "bthread_begin", gdb.COMMAND_STACK, gdb.COMPLETE_NONE) + + def invoke(self, arg, tty): + global status + if status: + print("Already in bthread debug mode, do not switch thread before exec 'bthread_end' !!!") + return + active_bthreads = get_bthread_num() + scanned_bthreads = active_bthreads + if arg: + num_arg = int(arg) + if num_arg < active_bthreads: + scanned_bthreads = num_arg + else: + print("requested bthreads {} more than actived, will display {} bthreads".format(num_arg, scanned_bthreads)) + print("Active bthreads: {}, will display {} bthreads".format(active_bthreads, scanned_bthreads)) + get_all_bthreads(scanned_bthreads) + gdb.parse_and_eval("$saved_rip = $rip") + gdb.parse_and_eval("$saved_rsp = $rsp") + gdb.parse_and_eval("$saved_rbp = $rbp") + status = True + print("Enter bthread debug mode, do not switch thread before exec 'bthread_end' !!!") + +class BthreadRegRestoreCmd(gdb.Command): + """restore registers""" + def __init__(self): + gdb.Command.__init__(self, "bthread_reg_restore", gdb.COMMAND_STACK, gdb.COMPLETE_NONE) + + def invoke(self, arg, tty): + global status + if not status: + print("Not in bthread debug mode") + return + gdb.parse_and_eval("$rip = $saved_rip") + gdb.parse_and_eval("$rsp = $saved_rsp") + gdb.parse_and_eval("$rbp = $saved_rbp") + print("OK") + +class BthreadEndCmd(gdb.Command): + """exit bthread debug mode""" + def __init__(self): + gdb.Command.__init__(self, "bthread_end", gdb.COMMAND_STACK, gdb.COMPLETE_NONE) + + def invoke(self, arg, tty): + global status + if not status: + print("Not in bthread debug mode") + return + gdb.parse_and_eval("$rip = $saved_rip") + gdb.parse_and_eval("$rsp = $saved_rsp") + gdb.parse_and_eval("$rbp = $saved_rbp") + status = False + print("Exit bthread debug mode") + +BthreadListCmd() +BthreadNumCmd() +BthreadBeginCmd() +BthreadEndCmd() +BthreadFrameCmd() +BthreadAllCmd() +BthreadMetaCmd() +BthreadRegRestoreCmd() +BthreadRegsCmd() diff --git a/tools/get_brpc_revision.sh b/tools/get_brpc_revision.sh new file mode 100755 index 0000000..568de2e --- /dev/null +++ b/tools/get_brpc_revision.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +output=$(cat $1/RELEASE_VERSION) +abbr_commit_hash=$(git log -1 --format="%h" 2> /dev/null) +committer_date=($(git log -1 --format="%ci" 2> /dev/null)) +committer_date="${committer_date[0]}T${committer_date[1]}${committer_date[2]:0:3}:${committer_date[2]:3:2}" +branch=$(git rev-parse --abbrev-ref HEAD 2> /dev/null) +if [ $? -eq 0 ] +then + output=$output"\\|"$branch"\\|"$abbr_commit_hash"\\|"$committer_date +fi +echo $output diff --git a/tools/lldb_bthread_stack.py b/tools/lldb_bthread_stack.py new file mode 100644 index 0000000..78cbf71 --- /dev/null +++ b/tools/lldb_bthread_stack.py @@ -0,0 +1,347 @@ +#!/usr/bin/env python +# coding=utf-8 + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +""" +Bthread Stack Print Tool + +this only for running process, core dump is not supported. + +Get Started: + 1. lldb attach -p + 2. command script import lldb_bthread_stack.py + 3. bthread_begin + 4. bthread_list + 5. bthread_frame 0 + 6. bt / up / down + 7. bthread_end + +Commands: + 1. bthread_num: print all bthread nums + 2. bthread_begin : enter bthread debug mode, `num` is max scanned bthreads, default will scan all + 3. bthread_list: list all bthreads + 4. bthread_frame : switch stack to bthread, id will displayed in bthread_list + 5. bthread_meta : print bthread meta + 6. bthread_reg_restore: bthread_frame will modify registers, reg_restore will restore them + 7. bthread_end: exit bthread debug mode + 8. bthread_regs : print bthread registers + 9. bthread_all: print all bthread frames + +when call bthread_frame, registers will be modified, +remember to call bthread_end after debug, or process will be corrupted + +after call bthread_frame, you can call `bt`/`up`/`down`, or other gdb command +""" + +import lldb + + +class GlobalState(): + def __init__(self): + self.started: bool = False + self.bthreads: list = [] + self.saved_regs: dict = {} + + def reset(self) -> None: + self.started = False + self.bthreads.clear() + + def get_bthread(self, idx_str: str) -> lldb.SBValue: + if not self.started: + print("Not in bthread debug mode") + return None + if len(idx_str) == 0: + print("bthread_frame , see 'bthread_list'") + try: + bthread_idx = int(idx_str) + except ValueError: + print("please input a valid interger.") + return None + + if bthread_idx >= len(self.bthreads): + print("id {} exceeds max bthread nums {}".format( + bthread_idx, len(self.bthreads))) + return None + return self.bthreads[bthread_idx] + + +global_state = GlobalState() + + +def get_child(value: lldb.SBValue, childs_value_name: str) -> lldb.SBValue: + r"""get child value by value name str split by '.'""" + result = value + childs_value_list = childs_value_name.split('.') + for child_value_name in childs_value_list: + result = result.GetChildMemberWithName(child_value_name) + return result + + +def find_global_value(target: lldb.SBTarget, value_name: str) -> lldb.SBValue: + r""" find global value by value name""" + name_list = value_name.split('.') + root_name = name_list[0] + root_value = target.FindGlobalVariables( + root_name, 1, lldb.eMatchTypeNormal)[0] + if (len(name_list) == 1): + return root_value + return get_child(root_value, '.'.join(name_list[1:])) + + +def get_bthreads_num(target: lldb.SBTarget): + root_agent = find_global_value( + target, "bthread::g_task_control._nbthreads._combiner._agents.root_") + global_res = find_global_value( + target, "bthread::g_task_control._nbthreads._combiner._global_result").GetValueAsSigned() + long_type = target.GetBasicType(lldb.eBasicTypeLong) + + last_node = root_agent + # agent_type: bvar::detail::AgentCombiner >::Agent> + agent_type: lldb.SBType = last_node.GetType().GetTemplateArgumentType(0) + while True: + agent = last_node.Cast(agent_type) + if (last_node.GetLocation() != root_agent.GetLocation()): + val = get_child(agent, "element._value").Cast( + long_type).GetValueAsSigned() + global_res += val + if (get_child(agent, "next_").Dereference().GetLocation() == root_agent.GetLocation()): + return global_res + last_node = get_child(agent, "next_").Dereference() + + +def get_all_bthreads(target: lldb.SBTarget, total: int): + bthreads = [] + groups = find_global_value( + target, "butil::ResourcePool::_ngroup.val").GetValueAsUnsigned() + long_type = target.GetBasicType(lldb.eBasicTypeLong) + uint32_t_type = target.FindFirstType("uint32_t") + block_groups = find_global_value( + target, "butil::ResourcePool::_block_groups") + for group in range(groups): + block_group = get_child( + block_groups.GetChildAtIndex(group), "val").Dereference() + nblock = get_child(block_group, "nblock").Cast( + long_type).GetValueAsUnsigned() + blocks = get_child(block_group, "blocks") + for block in range(nblock): + # block_type: butil::ResourcePool::Block + block_type = blocks.GetChildAtIndex( + block).GetType().GetTemplateArgumentType(0) + block = blocks.GetChildAtIndex( + block).Cast(block_type).Dereference() + nitem = get_child(block, "nitem").GetValueAsUnsigned() + task_meta_array_type = target.FindFirstType( + "bthread::TaskMeta").GetArrayType(nitem) + tasks = get_child(block, "items").Cast(task_meta_array_type) + for i in range(nitem): + task_meta = tasks.GetChildAtIndex(i) + version_tid = get_child( + task_meta, "tid").GetValueAsUnsigned() >> 32 + version_butex = get_child(task_meta, "version_butex").Cast( + uint32_t_type.GetPointerType()).Dereference().GetValueAsUnsigned() + # stack_type: bthread::ContextualStack + stack_type = get_child( + task_meta, "attr.stack_type").GetValueAsUnsigned() + if version_tid == version_butex and stack_type != 0: + if len(bthreads) >= total: + return bthreads + bthreads.append(task_meta) + return bthreads + +# lldb bthread commands +def bthread_begin(debugger, command, result, internal_dict): + if global_state.started: + print("Already in bthread debug mode, do not switch thread before exec 'bthread_end' !!!") + return + target = debugger.GetSelectedTarget() + active_bthreads = get_bthreads_num(target) + + if len(command) == 0: + request_bthreds = active_bthreads + else: + try: + request_bthreds = int(command) + except ValueError: + print("please input a valid interger.") + return + + scanned_bthreds = active_bthreads + if request_bthreds > active_bthreads: + print("requested bthreads {} more than actived, will display {} bthreads".format( + request_bthreds, active_bthreads)) + else: + scanned_bthreds = request_bthreds + print("Active bthreads: {}, will display {} bthreads".format( + active_bthreads, scanned_bthreds)) + global_state.bthreads = get_all_bthreads(target, scanned_bthreds) + + # backup registers + current_frame = target.GetProcess().GetSelectedThread().GetSelectedFrame() + saved_regs = dict() + saved_regs["rip"] = current_frame.FindRegister("rip").GetValueAsUnsigned() + saved_regs["rsp"] = current_frame.FindRegister("rsp").GetValueAsUnsigned() + saved_regs["rbp"] = current_frame.FindRegister("rbp").GetValueAsUnsigned() + global_state.saved_regs = saved_regs + + global_state.started = True + print("Enter bthread debug mode, do not switch thread before exec 'bthread_end' !!!") + + +def bthread_list(debugger, command, result, internal_dict): + r"""list all bthreads, print format is 'id\ttid\tfunction\thas stack'""" + if not global_state.started: + print("Not in bthread debug mode") + return + + print("id\t\ttid\t\tfunction\t\t\t\thas stack\t\t\ttotal:{}".format( + len(global_state.bthreads))) + for i, t in enumerate(global_state.bthreads): + tid = get_child(t, "tid").GetValueAsUnsigned() + fn = get_child(t, "fn") + has_stack = get_child(t, "stack").GetLocation() == "0x0" + print("#{}\t\t{}\t\t{}\t\t{}".format( + i, tid, fn, "no" if has_stack else "yes")) + + +def bthread_num(debugger, command, result, internal_dict): + r"""list active bthreads num""" + if not global_state.started: + print("Not in bthread debug mode") + return + + target = debugger.GetSelectedTarget() + active_bthreads = get_bthreads_num(target) + print(active_bthreads) + + +def bthread_frame(debugger, command, result, internal_dict): + r"""bthread_frame , select bthread frame by id""" + bthread = global_state.get_bthread(command) + if bthread is None: + return + + stack = bthread.GetChildMemberWithName("stack") + context = stack.Dereference().GetChildMemberWithName("context") + + target = debugger.GetSelectedTarget() + uint64_t_type = target.FindFirstType("uint64_t") + target = debugger.GetSelectedTarget() + + rip = target.CreateValueFromAddress("rip", lldb.SBAddress( + context.GetValueAsUnsigned() + 7*8, target), uint64_t_type).GetValueAsUnsigned() + rbp = target.CreateValueFromAddress("rbp", lldb.SBAddress( + context.GetValueAsUnsigned() + 6*8, target), uint64_t_type).GetValueAsUnsigned() + rsp = context.GetValueAsUnsigned() + 8*8 + + debugger.HandleCommand(f"register write rip {rip}") + debugger.HandleCommand(f"register write rbp {rbp}") + debugger.HandleCommand(f"register write rsp {rsp}") + + +def bthread_all(debugger, command, result, internal_dict): + r"""print all bthread frames""" + if not global_state.started: + print("Not in bthread debug mode") + return + + bthreads = global_state.bthreads + bthread_num = len(bthreads) + for i in range(bthread_num): + bthread_frame(debugger, str(i), result, internal_dict) + debugger.HandleCommand("bt") + + +def bthread_meta(debugger, command, result, internal_dict): + r"""bthread_meta , print task meta by id""" + bthread = global_state.get_bthread(command) + if bthread is None: + return + print(bthread) + + +def bthread_regs(debugger, command, result, internal_dict): + r"""bthread_regs , print bthread registers""" + bthread = global_state.get_bthread(command) + if bthread is None: + return + target = debugger.GetSelectedTarget() + stack = get_child(bthread, "stack").Dereference() + context = get_child(stack, "context") + ctx_addr = context.GetValueAsUnsigned() + uint64_t_type = target.FindFirstType("uint64_t") + + rip = target.CreateValueFromAddress("rip", lldb.SBAddress( + ctx_addr + 7*8, target), uint64_t_type).GetValueAsUnsigned() + rbp = target.CreateValueFromAddress("rbp", lldb.SBAddress( + ctx_addr + 6*8, target), uint64_t_type).GetValueAsUnsigned() + rbx = target.CreateValueFromAddress("rbx", lldb.SBAddress( + ctx_addr + 5*8, target), uint64_t_type).GetValueAsUnsigned() + r15 = target.CreateValueFromAddress("r15", lldb.SBAddress( + ctx_addr + 4*8, target), uint64_t_type).GetValueAsUnsigned() + r14 = target.CreateValueFromAddress("r14", lldb.SBAddress( + ctx_addr + 3*8, target), uint64_t_type).GetValueAsUnsigned() + r13 = target.CreateValueFromAddress("r13", lldb.SBAddress( + ctx_addr + 2*8, target), uint64_t_type).GetValueAsUnsigned() + r12 = target.CreateValueFromAddress("r12", lldb.SBAddress( + ctx_addr + 1*8, target), uint64_t_type).GetValueAsUnsigned() + rsp = ctx_addr + 8*8 + + print("rip: 0x{:x}\nrsp: 0x{:x}\nrbp: 0x{:x}\nrbx: 0x{:x}\nr15: 0x{:x}\nr14: 0x{:x}\nr13: 0x{:x}\nr12: 0x{:x}".format( + rip, rsp, rbp, rbx, r15, r14, r13, r12)) + + +def bthread_reg_restore(debugger, command, result, internal_dict): + r"""restore registers""" + if not global_state.started: + print("Not in bthread debug mode") + return + for reg_name, reg_value in global_state.saved_regs.items(): + debugger.HandleCommand(f"register write {reg_name} {reg_value}") + + +def bthread_end(debugger, command, result, internal_dict): + r"""exit bthread debug mode""" + if not global_state.started: + print("Not in bthread debug mode") + return + bthread_reg_restore(debugger, command, result, internal_dict) + global_state.reset() + print("Exit bthread debug mode") + + +# And the initialization code to add commands. +def __lldb_init_module(debugger, internal_dict): + debugger.HandleCommand( + 'command script add -f lldb_bthread_stack.bthread_begin bthread_begin') + debugger.HandleCommand( + 'command script add -f lldb_bthread_stack.bthread_list bthread_list') + debugger.HandleCommand( + 'command script add -f lldb_bthread_stack.bthread_frame bthread_frame') + debugger.HandleCommand( + 'command script add -f lldb_bthread_stack.bthread_num bthread_num') + debugger.HandleCommand( + 'command script add -f lldb_bthread_stack.bthread_all bthread_all') + debugger.HandleCommand( + 'command script add -f lldb_bthread_stack.bthread_meta bthread_meta') + debugger.HandleCommand( + 'command script add -f lldb_bthread_stack.bthread_regs bthread_regs') + debugger.HandleCommand( + 'command script add -f lldb_bthread_stack.bthread_reg_restore bthread_reg_restore') + debugger.HandleCommand( + 'command script add -f lldb_bthread_stack.bthread_end bthread_end') diff --git a/tools/parallel_http/CMakeLists.txt b/tools/parallel_http/CMakeLists.txt new file mode 100644 index 0000000..6967644 --- /dev/null +++ b/tools/parallel_http/CMakeLists.txt @@ -0,0 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +add_executable(parallel_http parallel_http.cpp) +target_link_libraries(parallel_http PRIVATE brpc-static ${DYNAMIC_LIB}) +install(TARGETS parallel_http RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/tools/parallel_http/parallel_http.cpp b/tools/parallel_http/parallel_http.cpp new file mode 100644 index 0000000..24ae855 --- /dev/null +++ b/tools/parallel_http/parallel_http.cpp @@ -0,0 +1,211 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + + +// Access many http servers in parallel, much faster than curl (even called in batch) + +#include +#include +#include +#include +#include +#include + +DEFINE_string(url_file, "", "The file containing urls to fetch. If this flag is" + " empty, read urls from stdin"); +DEFINE_int32(timeout_ms, 1000, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)"); +DEFINE_int32(thread_num, 8, "Number of threads to access urls"); +DEFINE_int32(concurrency, 1000, "Max number of http calls in parallel"); +DEFINE_bool(one_line_mode, false, "Output as `URL HTTP-RESPONSE' on true"); +DEFINE_bool(only_show_host, false, "Print host name only"); + +struct AccessThreadArgs { + const std::deque* url_list; + size_t offset; + std::deque > output_queue; + butil::Mutex output_queue_mutex; + butil::atomic current_concurrency; +}; + +class OnHttpCallEnd : public google::protobuf::Closure { +public: + void Run(); +public: + brpc::Controller cntl; + AccessThreadArgs* args; + std::string url; +}; + +void OnHttpCallEnd::Run() { + std::unique_ptr delete_self(this); + { + BAIDU_SCOPED_LOCK(args->output_queue_mutex); + if (cntl.Failed()) { + args->output_queue.push_back(std::make_pair(url, butil::IOBuf())); + } else { + args->output_queue.push_back( + std::make_pair(url, cntl.response_attachment())); + } + } + args->current_concurrency.fetch_sub(1, butil::memory_order_relaxed); +} + +void* access_thread(void* void_args) { + AccessThreadArgs* args = (AccessThreadArgs*)void_args; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_HTTP; + options.connect_timeout_ms = FLAGS_timeout_ms / 2; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + const int concurrency_for_this_thread = FLAGS_concurrency / FLAGS_thread_num; + + for (size_t i = args->offset; i < args->url_list->size(); i += FLAGS_thread_num) { + std::string const& url = (*args->url_list)[i]; + brpc::Channel channel; + if (channel.Init(url.c_str(), &options) != 0) { + LOG(ERROR) << "Fail to create channel to url=" << url; + BAIDU_SCOPED_LOCK(args->output_queue_mutex); + args->output_queue.push_back(std::make_pair(url, butil::IOBuf())); + continue; + } + while (args->current_concurrency.fetch_add(1, butil::memory_order_relaxed) + > concurrency_for_this_thread) { + args->current_concurrency.fetch_sub(1, butil::memory_order_relaxed); + bthread_usleep(5000); + } + OnHttpCallEnd* done = new OnHttpCallEnd; + done->cntl.http_request().uri() = url; + done->args = args; + done->url = url; + channel.CallMethod(NULL, &done->cntl, NULL, NULL, done); + } + return NULL; +} + +int main(int argc, char** argv) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + // if (FLAGS_path.empty() || FLAGS_path[0] != '/') { + // FLAGS_path = "/" + FLAGS_path; + // } + + butil::ScopedFILE fp_guard; + FILE* fp = NULL; + if (!FLAGS_url_file.empty()) { + fp_guard.reset(fopen(FLAGS_url_file.c_str(), "r")); + if (!fp_guard) { + PLOG(ERROR) << "Fail to open `" << FLAGS_url_file << '\''; + return -1; + } + fp = fp_guard.get(); + } else { + fp = stdin; + } + char* line_buf = NULL; + size_t line_len = 0; + ssize_t nr = 0; + std::deque url_list; + while ((nr = getline(&line_buf, &line_len, fp)) != -1) { + if (line_buf[nr - 1] == '\n') { // remove ending newline + line_buf[nr - 1] = '\0'; + --nr; + } + butil::StringPiece line(line_buf, nr); + line.trim_spaces(); + if (!line.empty()) { + url_list.push_back(line.as_string()); + } + } + if (url_list.empty()) { + return 0; + } + AccessThreadArgs* args = new AccessThreadArgs[FLAGS_thread_num]; + for (int i = 0; i < FLAGS_thread_num; ++i) { + args[i].url_list = &url_list; + args[i].offset = i; + args[i].current_concurrency.store(0, butil::memory_order_relaxed); + } + std::vector tids; + tids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + CHECK_EQ(0, bthread_start_background(&tids[i], NULL, access_thread, &args[i])); + } + std::deque > output_queue; + size_t nprinted = 0; + while (nprinted != url_list.size()) { + for (int i = 0; i < FLAGS_thread_num; ++i) { + { + BAIDU_SCOPED_LOCK(args[i].output_queue_mutex); + output_queue.swap(args[i].output_queue); + } + for (size_t i = 0; i < output_queue.size(); ++i) { + butil::StringPiece url = output_queue[i].first; + butil::StringPiece hostname; + if (url.starts_with("http://")) { + url.remove_prefix(7); + } + size_t slash_pos = url.find('/'); + if (slash_pos != butil::StringPiece::npos) { + hostname = url.substr(0, slash_pos); + } else { + hostname = url; + } + if (FLAGS_one_line_mode) { + if (FLAGS_only_show_host) { + std::cout << hostname; + } else { + std::cout << "http://" << url; + } + if (output_queue[i].second.empty()) { + std::cout << " ERROR" << std::endl; + } else { + std::cout << ' ' << output_queue[i].second << std::endl; + } + } else { + // The prefix is unlikely be part of a ordinary http body, + // thus the line can be easily removed by shell utilities. + std::cout << "#### "; + if (FLAGS_only_show_host) { + std::cout << hostname; + } else { + std::cout << "http://" << url; + } + if (output_queue[i].second.empty()) { + std::cout << " ERROR" << std::endl; + } else { + std::cout << '\n' << output_queue[i].second << std::endl; + } + } + } + nprinted += output_queue.size(); + output_queue.clear(); + } + usleep(10000); + } + + for (int i = 0; i < FLAGS_thread_num; ++i) { + bthread_join(tids[i], NULL); + } + for (int i = 0; i < FLAGS_thread_num; ++i) { + while (args[i].current_concurrency.load(butil::memory_order_relaxed) != 0) { + usleep(10000); + } + } + return 0; +} diff --git a/tools/print_gcc_version.sh b/tools/print_gcc_version.sh new file mode 100755 index 0000000..00abf6c --- /dev/null +++ b/tools/print_gcc_version.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +while read -r line; do + val=$(echo "$line" | awk '{print $3}') + if [[ $line =~ __clang__ ]]; then + CLANG=${val} + elif [[ $line =~ __GNUC__ ]]; then + GNUC=${val} + elif [[ $line =~ __GNUC_MINOR__ ]]; then + GNUC_MINOR=${val} + elif [[ $line =~ __GNUC_PATCHLEVEL__ ]]; then + GNUC_PATCHLEVEL=${val} + fi +done < <("${CXX:-c++}" -dM -E - < /dev/null | grep "__clang__\|__GNUC__\|__GNUC_MINOR__\|__GNUC_PATCHLEVEL__") + +if [ -n "$GNUC" ] && [ -n "$GNUC_MINOR" ] && [ -n "$GNUC_PATCHLEVEL" ]; then + # Calculate GCC/Clang version + GCC_VERSION=$((GNUC * 10000 + GNUC_MINOR * 100 + GNUC_PATCHLEVEL)) + if [ -n "$CLANG" ] && [ "40000" -lt $GCC_VERSION ] && [ $GCC_VERSION -lt "40800" ]; then + # Make version of clang >= 4.8 so that it's not rejected by config_brpc.sh + GCC_VERSION=40800 + fi + echo $GCC_VERSION +else + echo 0 +fi diff --git a/tools/rpc_press/CMakeLists.txt b/tools/rpc_press/CMakeLists.txt new file mode 100644 index 0000000..762d346 --- /dev/null +++ b/tools/rpc_press/CMakeLists.txt @@ -0,0 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +file(GLOB SOURCES CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/tools/rpc_press/*.cpp") +add_executable(rpc_press ${SOURCES}) +target_link_libraries(rpc_press PRIVATE brpc-static ${DYNAMIC_LIB}) +install(TARGETS rpc_press RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/tools/rpc_press/client.py b/tools/rpc_press/client.py new file mode 100644 index 0000000..e670cba --- /dev/null +++ b/tools/rpc_press/client.py @@ -0,0 +1,28 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + +import json +import urllib2 +data = { "message" : "hello world" } +request_json = json.dumps(data) +req = urllib2.Request("http://127.0.0.1:8000/EchoService/Echo") +try: + response = urllib2.urlopen(req, request_json, 1) + print response.read() +except urllib2.HTTPError as e: + print e.exception.code diff --git a/tools/rpc_press/info_thread.cpp b/tools/rpc_press/info_thread.cpp new file mode 100644 index 0000000..fc3d8f8 --- /dev/null +++ b/tools/rpc_press/info_thread.cpp @@ -0,0 +1,130 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "info_thread.h" + +namespace brpc { + +InfoThread::InfoThread() + : _stop(false) + , _tid(0) { + pthread_mutex_init(&_mutex, NULL); + pthread_cond_init(&_cond, NULL); +} + +InfoThread::~InfoThread() { + pthread_mutex_destroy(&_mutex); + pthread_cond_destroy(&_cond); +} + +void InfoThread::run() { + int64_t i = 0; + int64_t last_sent_count = 0; + int64_t last_succ_count = 0; + int64_t last_error_count = 0; + int64_t start_time = butil::cpuwide_time_us(); + while (!_stop) { + int64_t end_time = 0; + while (!_stop && + (end_time = butil::cpuwide_time_us()) < start_time + 1000000L) { + BAIDU_SCOPED_LOCK(_mutex); + if (!_stop) { + timespec ts = butil::microseconds_to_timespec(end_time); + pthread_cond_timedwait(&_cond, &_mutex, &ts); + } + } + start_time = butil::cpuwide_time_us(); + char buf[64]; + const time_t tm_s = start_time / 1000000L; + struct tm lt; + strftime(buf, sizeof(buf), "%Y/%m/%d-%H:%M:%S", + localtime_r(&tm_s, <)); + + const int64_t cur_sent_count = _options.sent_count->get_value(); + const int64_t cur_succ_count = _options.latency_recorder->count(); + const int64_t cur_error_count = _options.error_count->get_value(); + printf("%s\tsent:%-10dsuccess:%-10derror:%-6dtotal_error:%-10lldtotal_sent:%-10lld\n", + buf, + (int)(cur_sent_count - last_sent_count), + (int)(cur_succ_count - last_succ_count), + (int)(cur_error_count - last_error_count), + (long long)cur_error_count, + (long long)cur_sent_count); + last_sent_count = cur_sent_count; + last_succ_count = cur_succ_count; + last_error_count = cur_error_count; + + if (_stop || ++i % 10 == 0) { + printf("[Latency]\n" + " avg %10lld us\n" + " 50%% %10lld us\n" + " 70%% %10lld us\n" + " 90%% %10lld us\n" + " 95%% %10lld us\n" + " 97%% %10lld us\n" + " 99%% %10lld us\n" + " 99.9%% %10lld us\n" + " 99.99%% %10lld us\n" + " max %10lld us\n", + (long long)_options.latency_recorder->latency(), + (long long)_options.latency_recorder->latency_percentile(0.5), + (long long)_options.latency_recorder->latency_percentile(0.7), + (long long)_options.latency_recorder->latency_percentile(0.9), + (long long)_options.latency_recorder->latency_percentile(0.95), + (long long)_options.latency_recorder->latency_percentile(0.97), + (long long)_options.latency_recorder->latency_percentile(0.99), + (long long)_options.latency_recorder->latency_percentile(0.999), + (long long)_options.latency_recorder->latency_percentile(0.9999), + (long long)_options.latency_recorder->max_latency()); + } + } +} + +static void* run_info_thread(void* arg) { + ((InfoThread*)arg)->run(); + return NULL; +} + +bool InfoThread::start(const InfoThreadOptions& options) { + if (options.latency_recorder == NULL || + options.error_count == NULL || + options.sent_count == NULL) { + LOG(ERROR) << "Some required options are NULL"; + return false; + } + _options = options; + _stop = false; + if (pthread_create(&_tid, NULL, run_info_thread, this) != 0) { + LOG(ERROR) << "Fail to create info_thread"; + return false; + } + return true; +} + +void InfoThread::stop() { + { + BAIDU_SCOPED_LOCK(_mutex); + if (_stop) { + return; + } + _stop = true; + pthread_cond_signal(&_cond); + } + pthread_join(_tid, NULL); +} + +} // namespace brpc diff --git a/tools/rpc_press/info_thread.h b/tools/rpc_press/info_thread.h new file mode 100644 index 0000000..3564f05 --- /dev/null +++ b/tools/rpc_press/info_thread.h @@ -0,0 +1,55 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_RPC_REPLAY_INFO_THREAD_H +#define BRPC_RPC_REPLAY_INFO_THREAD_H + +#include +#include + +namespace brpc { + +struct InfoThreadOptions { + bvar::LatencyRecorder* latency_recorder; + bvar::Adder* sent_count; + bvar::Adder* error_count; + + InfoThreadOptions() + : latency_recorder(NULL) + , sent_count(NULL) + , error_count(NULL) {} +}; + +class InfoThread { +public: + InfoThread(); + ~InfoThread(); + void run(); + bool start(const InfoThreadOptions&); + void stop(); + +private: + bool _stop; + InfoThreadOptions _options; + pthread_mutex_t _mutex; + pthread_cond_t _cond; + pthread_t _tid; +}; + +} // namespace brpc + +#endif //BRPC_RPC_REPLAY_INFO_THREAD_H diff --git a/tools/rpc_press/json_loader.cpp b/tools/rpc_press/json_loader.cpp new file mode 100644 index 0000000..b338b71 --- /dev/null +++ b/tools/rpc_press/json_loader.cpp @@ -0,0 +1,229 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include "pb_util.h" +#include "json_loader.h" +#include + +namespace brpc { + +class JsonLoader::Reader { +public: + explicit Reader(int fd2) + : _fd(fd2) + , _brace_depth(0) + , _quoted(false) + , _quote_char(0) + {} + + explicit Reader(const std::string& jsons) + : _fd(-1) + , _brace_depth(0) + , _quoted(false) + , _quote_char(0) { + _file_buf.append(jsons); + } + + bool get_next_json(butil::IOBuf* json1); + bool read_some(); + +private: + int _fd; + int _brace_depth; + bool _quoted; // quoted by " or ' + char _quote_char; + butil::IOPortal _file_buf; +}; + +// Load data from the file. +bool JsonLoader::Reader::read_some() { + if (_fd < 0) { // loading from a string. + return false; + } + ssize_t nr = _file_buf.append_from_file_descriptor(_fd, 65536); + if (nr < 0) { + if (errno == EINTR) { + return read_some(); + } + PLOG(ERROR) << "Fail to read fd=" << _fd; + return false; + } else if (nr == 0) { + return false; + } else { + return true; + } +} + +// Ignore json only with spaces and newline +static bool possibly_valid_json(const butil::IOBuf& json) { + butil::IOBufAsZeroCopyInputStream it(json); + const void* data = NULL; + for (int size = 0; it.Next(&data, &size); ) { + for (int i = 0; i < size; ++i) { + char c = ((const char*)data)[i]; + if (!isspace(c) && c != '\n') { + return true; + } + } + } + return false; +} + +// Separate jsons with closed braces. +bool JsonLoader::Reader::get_next_json(butil::IOBuf* json1) { + if (_file_buf.empty()) { + if (!read_some()) { + return false; + } + } + json1->clear(); + while (1) { + butil::IOBufAsZeroCopyInputStream it(_file_buf); + const void* data = NULL; + int size = 0; + int total_size = 0; + int skipped = 0; + for (; it.Next(&data, &size); total_size += size) { + for (int i = 0; i < size; ++i) { + const char c = ((const char*)data)[i]; + if (_brace_depth == 0) { + // skip any character until the first left brace is found. + if (c != '{') { + ++skipped; + continue; + } + } + switch (c) { + case '{': + if (!_quoted) { + ++_brace_depth; + } else { + VLOG(1) << "Quoted left brace"; + } + break; + case '}': + if (!_quoted) { + --_brace_depth; + if (_brace_depth == 0) { + // the braces are closed, a complete object. + _file_buf.cutn(json1, total_size + i + 1); + json1->pop_front(skipped); + return possibly_valid_json(*json1); + } else if (_brace_depth < 0) { + LOG(ERROR) << "More right braces than left braces"; + return false; + } + } else { + VLOG(1) << "Quoted right brace"; + } + break; + case '"': + if (_quoted) { + if (_quote_char == '"') { + _quoted = false; + } + } else { + _quoted = true; + _quote_char = '"'; + } + break; + case '\'': + if (_quoted) { + if (_quote_char == '\'') { + _quoted = false; + } + } else { + _quoted = true; + _quote_char = '\''; + } + break; + default: + break; + // just skip + } + } + } + if (!_file_buf.empty()) { + json1->append(_file_buf); + _file_buf.clear(); + } + if (!read_some()) { + json1->pop_front(skipped); + if (!json1->empty()) { + return possibly_valid_json(*json1); + } + return false; + } + } + return false; +} + +JsonLoader::JsonLoader(google::protobuf::compiler::Importer* importer, + google::protobuf::DynamicMessageFactory* factory, + const std::string& service_name, + const std::string& method_name) + : _importer(importer) + , _factory(factory) + , _service_name(service_name) + , _method_name(method_name) +{ + _request_prototype = pbrpcframework::get_prototype_by_name( + _service_name, _method_name, true, _importer, _factory); +} + +void JsonLoader::load_messages( + JsonLoader::Reader* ctx, + std::deque* out_msgs) { + out_msgs->clear(); + butil::IOBuf request_json; + while (ctx->get_next_json(&request_json)) { + VLOG(1) << "Load " << out_msgs->size() + 1 << "-th json=`" + << request_json << '\''; + std::string error; + google::protobuf::Message* request = _request_prototype->New(); + butil::IOBufAsZeroCopyInputStream wrapper(request_json); + if (!json2pb::JsonToProtoMessage(&wrapper, request, &error)) { + LOG(WARNING) << "Fail to convert to pb: " << error << ", json=`" + << request_json << '\''; + delete request; + continue; + } + out_msgs->push_back(request); + LOG_IF(INFO, (out_msgs->size() % 10000) == 0) + << "Loaded " << out_msgs->size() << " jsons"; + } +} + +void JsonLoader::load_messages( + int fd, + std::deque* out_msgs) { + JsonLoader::Reader ctx(fd); + load_messages(&ctx, out_msgs); +} + +void JsonLoader::load_messages( + const std::string& jsons, + std::deque* out_msgs) { + JsonLoader::Reader ctx(jsons); + load_messages(&ctx, out_msgs); +} + +} // namespace brpc diff --git a/tools/rpc_press/json_loader.h b/tools/rpc_press/json_loader.h new file mode 100644 index 0000000..3294c49 --- /dev/null +++ b/tools/rpc_press/json_loader.h @@ -0,0 +1,63 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_JSON_LOADER_H +#define BRPC_JSON_LOADER_H + +#include +#include +#include +#include +#include +#include + +namespace brpc { + +// This utility loads pb messages in json format from a file or string. +class JsonLoader { +public: + JsonLoader(google::protobuf::compiler::Importer* importer, + google::protobuf::DynamicMessageFactory* factory, + const std::string& service_name, + const std::string& method_name); + ~JsonLoader() {} + + // TODO(gejun): messages should be lazily loaded. + + // Load jsons from fd or string, convert them into pb messages, then insert + // them into `out_msgs'. + void load_messages(int fd, std::deque* out_msgs); + void load_messages(const std::string& jsons, + std::deque* out_msgs); + +private: + class Reader; + + void load_messages( + JsonLoader::Reader* ctx, + std::deque* out_msgs); + + google::protobuf::compiler::Importer* _importer; + google::protobuf::DynamicMessageFactory* _factory; + std::string _service_name; + std::string _method_name; + const google::protobuf::Message* _request_prototype; +}; + +} // namespace brpc + +#endif // BRPC_JSON_LOADER_H diff --git a/tools/rpc_press/pb_util.cpp b/tools/rpc_press/pb_util.cpp new file mode 100644 index 0000000..781c5cf --- /dev/null +++ b/tools/rpc_press/pb_util.cpp @@ -0,0 +1,72 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include "pb_util.h" + +using google::protobuf::ServiceDescriptor; +using google::protobuf::Descriptor; +using google::protobuf::DescriptorPool; +using google::protobuf::MessageFactory; +using google::protobuf::Message; +using google::protobuf::MethodDescriptor; +using google::protobuf::compiler::Importer; +using google::protobuf::DynamicMessageFactory; +using google::protobuf::DynamicMessageFactory; +using std::string; + +namespace pbrpcframework { + +const MethodDescriptor* find_method_by_name(const string& service_name, + const string& method_name, + Importer* importer) { + const ServiceDescriptor* descriptor = + importer->pool()->FindServiceByName(service_name); + if (NULL == descriptor) { + LOG(FATAL) << "Fail to find service=" << service_name; + return NULL; + } + return descriptor->FindMethodByName(method_name); +} + +const Message* get_prototype_by_method_descriptor( + const MethodDescriptor* descripter, + bool is_input, + DynamicMessageFactory* factory) { + if (NULL == descripter) { + LOG(FATAL) <<"Param[descripter] is NULL"; + return NULL; + } + const Descriptor* message_descriptor = NULL; + if (is_input) { + message_descriptor = descripter->input_type(); + } else { + message_descriptor = descripter->output_type(); + } + return factory->GetPrototype(message_descriptor); +} + +const Message* get_prototype_by_name(const string& service_name, + const string& method_name, bool is_input, + Importer* importer, + DynamicMessageFactory* factory){ + const MethodDescriptor* descripter = find_method_by_name( + service_name, method_name, importer); + return get_prototype_by_method_descriptor(descripter, is_input, factory); +} + +} // pbrpcframework diff --git a/tools/rpc_press/pb_util.h b/tools/rpc_press/pb_util.h new file mode 100644 index 0000000..d420339 --- /dev/null +++ b/tools/rpc_press/pb_util.h @@ -0,0 +1,44 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef UTIL_PB_UTIL_H +#define UTIL_PB_UTIL_H +#include "google/protobuf/message.h" +#include "google/protobuf/descriptor.h" +#include +#include +#include + +namespace pbrpcframework { +const google::protobuf::MethodDescriptor* find_method_by_name( + const std::string& service_name, + const std::string& method_name, + google::protobuf::compiler::Importer* importer); + +const google::protobuf::Message* get_prototype_by_method_descriptor( + const google::protobuf::MethodDescriptor* descripter, + bool is_input, + google::protobuf::DynamicMessageFactory* factory); + +const google::protobuf::Message* get_prototype_by_name( + const std::string& service_name, + const std::string& method_name, + bool is_input, + google::protobuf::compiler::Importer* importer, + google::protobuf::DynamicMessageFactory* factory); +} +#endif //UTIL_PB_UTIL_H diff --git a/tools/rpc_press/rpc_press.cpp b/tools/rpc_press/rpc_press.cpp new file mode 100644 index 0000000..a9a98d6 --- /dev/null +++ b/tools/rpc_press/rpc_press.cpp @@ -0,0 +1,129 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include +#include +#include +#include "rpc_press_impl.h" + +DEFINE_int32(dummy_port, 8888, "Port of dummy server"); +DEFINE_string(proto, "", " user's proto files with path"); +DEFINE_string(inc, "", "Include paths for proto, separated by semicolon(;)"); +DEFINE_string(method, "example.EchoService.Echo", "The full method name"); +DEFINE_string(server, "0.0.0.0:8002", "ip:port of the server when -load_balancer is empty, the naming service otherwise"); +DEFINE_string(input, "", "The file containing requests in json format"); +DEFINE_string(output, "", "The file containing responses in json format"); +DEFINE_string(lb_policy, "", "The load balancer algorithm: rr, random, la, p2c, c_murmurhash, c_md5"); +DEFINE_int32(thread_num, 0, "Number of threads to send requests. 0: automatically chosen according to -qps"); +DEFINE_string(protocol, "baidu_std", "baidu_std hulu_pbrpc sofa_pbrpc http public_pbrpc nova_pbrpc ubrpc_compack..."); +DEFINE_string(connection_type, "", "Type of connections: single, pooled, short"); +DEFINE_int32(timeout_ms, 1000, "RPC timeout in milliseconds"); +DEFINE_int32(connection_timeout_ms, 500, " connection timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Maximum retry times by RPC framework"); +DEFINE_int32(request_compress_type, 0, "Snappy:1 Gzip:2 Zlib:3 LZ4:4 None:0"); +DEFINE_int32(response_compress_type, 0, "Snappy:1 Gzip:2 Zlib:3 LZ4:4 None:0"); +DEFINE_int32(attachment_size, 0, "Carry so many byte attachment along with requests"); +DEFINE_int32(duration, 0, "how many seconds the press keep"); +DEFINE_int32(qps, 100 , "how many calls per seconds"); +DEFINE_bool(pretty, true, "output pretty jsons"); + +bool set_press_options(pbrpcframework::PressOptions* options){ + size_t dot_pos = FLAGS_method.find_last_of('.'); + if (dot_pos == std::string::npos) { + LOG(ERROR) << "-method must be in form of: package.service.method"; + return false; + } + options->service = FLAGS_method.substr(0, dot_pos); + options->method = FLAGS_method.substr(dot_pos + 1); + options->lb_policy = FLAGS_lb_policy; + options->test_req_rate = FLAGS_qps; + if (FLAGS_thread_num > 0) { + options->test_thread_num = FLAGS_thread_num; + } else { + if (FLAGS_qps <= 0) { // unlimited qps + options->test_thread_num = 50; + } else { + options->test_thread_num = FLAGS_qps / 10000; + if (options->test_thread_num < 1) { + options->test_thread_num = 1; + } + if (options->test_thread_num > 50) { + options->test_thread_num = 50; + } + } + } + + const int rate_limit_per_thread = 1000000; + double req_rate_per_thread = options->test_req_rate / options->test_thread_num; + if (req_rate_per_thread > rate_limit_per_thread) { + LOG(ERROR) << "req_rate: " << (int64_t) req_rate_per_thread << " is too large in one thread. The rate limit is " + << rate_limit_per_thread << " in one thread"; + return false; + } + + options->input = FLAGS_input; + options->output = FLAGS_output; + options->connection_type = FLAGS_connection_type; + options->connect_timeout_ms = FLAGS_connection_timeout_ms; + options->timeout_ms = FLAGS_timeout_ms; + options->max_retry = FLAGS_max_retry; + options->protocol = FLAGS_protocol; + options->request_compress_type = FLAGS_request_compress_type; + options->response_compress_type = FLAGS_response_compress_type; + options->attachment_size = FLAGS_attachment_size; + options->host = FLAGS_server; + options->proto_file = FLAGS_proto; + options->proto_includes = FLAGS_inc; + return true; +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + // set global log option + + if (FLAGS_dummy_port >= 0) { + brpc::StartDummyServerAt(FLAGS_dummy_port); + } + + pbrpcframework::PressOptions options; + if (!set_press_options(&options)) { + return -1; + } + pbrpcframework::RpcPress* rpc_press = new pbrpcframework::RpcPress; + if (0 != rpc_press->init(&options)) { + LOG(FATAL) << "Fail to init rpc_press"; + return -1; + } + + rpc_press->start(); + if (FLAGS_duration <= 0) { + while (!brpc::IsAskedToQuit()) { + sleep(1); + } + } else { + sleep(FLAGS_duration); + } + rpc_press->stop(); + // NOTE(gejun): Can't delete rpc_press on exit. It's probably + // used by concurrently running done. + return 0; +} diff --git a/tools/rpc_press/rpc_press_impl.cpp b/tools/rpc_press/rpc_press_impl.cpp new file mode 100644 index 0000000..8da10c1 --- /dev/null +++ b/tools/rpc_press/rpc_press_impl.cpp @@ -0,0 +1,297 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include +#include +#include +#include +#include // butil::FilePath +#include +#include +#include +#include +#include +#include "json_loader.h" +#include "rpc_press_impl.h" + +using google::protobuf::Message; +using google::protobuf::Closure; + +namespace pbrpcframework { + +class ImportErrorPrinter + : public google::protobuf::compiler::MultiFileErrorCollector { +public: + // Line and column numbers are zero-based. A line number of -1 indicates + // an error with the entire file (e.g. "not found"). + virtual void AddError(const std::string& filename, int line, + int /*column*/, const std::string& message) { + LOG_AT(ERROR, filename.c_str(), line) << message; + } + +#if GOOGLE_PROTOBUF_VERSION >= 5026000 + void RecordError(absl::string_view filename, int line, int column, + absl::string_view message) override { + LOG_AT(ERROR, filename.data(), line) << message; + } +#endif +}; + +int PressClient::init() { + brpc::ChannelOptions rpc_options; + rpc_options.connect_timeout_ms = _options->connect_timeout_ms; + rpc_options.timeout_ms = _options->timeout_ms; + rpc_options.max_retry = _options->max_retry; + rpc_options.protocol = _options->protocol; + rpc_options.connection_type = _options->connection_type; + if (_options->attachment_size > 0) { + _attachment.clear(); + _attachment.assign(_options->attachment_size, 'a'); + } + if (_rpc_client.Init(_options->host.c_str(), _options->lb_policy.c_str(), + &rpc_options) != 0){ + LOG(ERROR) << "Fail to initialize channel"; + return -1; + } + _method_descriptor = find_method_by_name( + _options->service, _options->method, _importer); + if (NULL == _method_descriptor) { + LOG(ERROR) << "Fail to find method=" << _options->service << '.' + << _options->method; + return -1; + } + _response_prototype = get_prototype_by_method_descriptor( + _method_descriptor, false, _factory); + return 0; +} + +void PressClient::call_method(brpc::Controller* cntl, Message* request, + Message* response, Closure* done) { + if (!_attachment.empty()) { + cntl->request_attachment().append(_attachment); + } + _rpc_client.CallMethod(_method_descriptor, cntl, request, response, done); +} + +RpcPress::RpcPress() + : _pbrpc_client(NULL) + , _started(false) + , _stop(false) + , _output_json(NULL) { +} + +RpcPress::~RpcPress() { + if (_output_json) { + fclose(_output_json); + _output_json = NULL; + } + delete _importer; +} + +int RpcPress::init(const PressOptions* options) { + if (NULL == options) { + LOG(ERROR) << "Param[options] is NULL" ; + return -1; + } + _options = *options; + + // Import protos. + if (_options.proto_file.empty()) { + LOG(ERROR) << "-proto is required"; + return -1; + } + int pos = _options.proto_file.find_last_of('/'); + std::string proto_file(_options.proto_file.substr(pos + 1)); + std::string proto_path(_options.proto_file.substr(0, pos)); + google::protobuf::compiler::DiskSourceTree sourceTree; + // look up .proto file in the same directory + sourceTree.MapPath("", proto_path.c_str()); + // Add paths in -inc + if (!_options.proto_includes.empty()) { + butil::StringSplitter sp(_options.proto_includes.c_str(), ';'); + for (; sp; ++sp) { + sourceTree.MapPath("", std::string(sp.field(), sp.length())); + } + } + ImportErrorPrinter error_printer; + _importer = new google::protobuf::compiler::Importer(&sourceTree, &error_printer); + if (_importer->Import(proto_file.c_str()) == NULL) { + LOG(ERROR) << "Fail to import " << proto_file; + return -1; + } + + _pbrpc_client = new PressClient(&_options, _importer, &_factory); + + if (!_options.output.empty()) { + butil::File::Error error; + butil::FilePath path(_options.output); + butil::FilePath dir = path.DirName(); + if (!butil::CreateDirectoryAndGetError(dir, &error)) { + LOG(ERROR) << "Fail to create directory=`" << dir.value() + << "', " << error; + return -1; + } + _output_json = fopen(_options.output.c_str(), "w"); + LOG_IF(ERROR, !_output_json) << "Fail to open " << _options.output; + } + + int ret = _pbrpc_client->init(); + if (0 != ret) { + LOG(ERROR) << "Fail to initialize rpc client"; + return ret; + } + + if (_options.input.empty()) { + LOG(ERROR) << "-input is empty"; + return -1; + } + brpc::JsonLoader json_util(_importer, &_factory, + _options.service, _options.method); + if (butil::PathExists(butil::FilePath(_options.input))) { + int fd = open(_options.input.c_str(), O_RDONLY); + if (fd < 0) { + PLOG(ERROR) << "Fail to open " << _options.input; + return -1; + } + json_util.load_messages(fd, &_msgs); + } else { + json_util.load_messages(_options.input, &_msgs); + } + if (_msgs.empty()) { + LOG(ERROR) << "Fail to load requests"; + return -1; + } + LOG(INFO) << "Loaded " << _msgs.size() << " requests"; + _latency_recorder.expose("rpc_press"); + _error_count.expose("rpc_press_error_count"); + return 0; +} + +void* RpcPress::sync_call_thread(void* arg) { + ((RpcPress*)arg)->sync_client(); + return NULL; +} + +void RpcPress::handle_response(brpc::Controller* cntl, + Message* request, + Message* response, + int64_t start_time){ + if (!cntl->Failed()){ + int64_t rpc_call_time_us = butil::cpuwide_time_us() - start_time; + _latency_recorder << rpc_call_time_us; + + if (_output_json) { + std::string response_json; + std::string error; + if (!json2pb::ProtoMessageToJson(*response, &response_json, &error)) { + LOG(WARNING) << "Fail to convert to json: " << error; + } + fprintf(_output_json, "%s\n", response_json.c_str()); + } + } else { + LOG(WARNING) << "error_code=" << cntl->ErrorCode() << ", " + << cntl->ErrorText(); + _error_count << 1; + } + delete response; + delete cntl; +} + +static butil::atomic g_thread_count(0); + +void RpcPress::sync_client() { + double req_rate = _options.test_req_rate / _options.test_thread_num; + //max make up time is 5 s + if (_msgs.empty()) { + LOG(ERROR) << "nothing to send!"; + return; + } + const int thread_index = g_thread_count.fetch_add(1, butil::memory_order_relaxed); + int msg_index = thread_index; + int64_t last_expected_time = butil::monotonic_time_ns(); + const int64_t interval = (int64_t) (1000000000L / req_rate); + // the max tolerant delay between end_time and expected_time. 10ms or 10 intervals + int64_t max_tolerant_delay = std::max((int64_t) 10000000L, 10 * interval); + while (!_stop) { + brpc::Controller* cntl = new brpc::Controller; + msg_index = (msg_index + _options.test_thread_num) % _msgs.size(); + Message* request = _msgs[msg_index]; + Message* response = _pbrpc_client->get_output_message(); + const int64_t start_time = butil::cpuwide_time_us(); + google::protobuf::Closure* done = brpc::NewCallback< + RpcPress, + RpcPress*, + brpc::Controller*, + Message*, + Message*, int64_t> + (this, &RpcPress::handle_response, cntl, request, response, start_time); + const brpc::CallId cid1 = cntl->call_id(); + _pbrpc_client->call_method(cntl, request, response, done); + _sent_count << 1; + + if (_options.test_req_rate <= 0) { + brpc::Join(cid1); + } else { + int64_t end_time = butil::monotonic_time_ns(); + int64_t expected_time = last_expected_time + interval; + if (end_time < expected_time) { + usleep((expected_time - end_time)/1000); + } + if (end_time - expected_time > max_tolerant_delay) { + expected_time = end_time; + } + last_expected_time = expected_time; + } + } +} + +int RpcPress::start() { + _ttid.resize(_options.test_thread_num); + int ret = 0; + for (int i = 0; i < _options.test_thread_num; i++) { + if ((ret = pthread_create(&_ttid[i], NULL, sync_call_thread, this)) != 0) { + LOG(ERROR) << "Fail to create sending threads"; + return -1; + } + } + brpc::InfoThreadOptions info_thr_opt; + info_thr_opt.latency_recorder = &_latency_recorder; + info_thr_opt.error_count = &_error_count; + info_thr_opt.sent_count = &_sent_count; + if (!_info_thr.start(info_thr_opt)) { + LOG(ERROR) << "Fail to create stats thread"; + return -1; + } + _started = true; + return 0; +} +int RpcPress::stop() { + if (!_started) { + return -1; + } + _stop = true; + for (size_t i = 0; i < _ttid.size(); i++) { + pthread_join(_ttid[i], NULL); + } + _info_thr.stop(); + return 0; +} +} //namespace diff --git a/tools/rpc_press/rpc_press_impl.h b/tools/rpc_press/rpc_press_impl.h new file mode 100644 index 0000000..03126fd --- /dev/null +++ b/tools/rpc_press/rpc_press_impl.h @@ -0,0 +1,141 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef PBRPCPRESS_PBRPC_PRESS_H +#define PBRPCPRESS_PBRPC_PRESS_H + +#include +#include +#include +#include +#include +#include +#include "info_thread.h" +#include "pb_util.h" + +namespace pbrpcframework { +class JsonUtil; + +struct PressOptions { + std::string service; //service name (packet.rpcservice) + std::string method; //method name (rpc service method) + int server_type; // server type: 0 = hulu server, 1 = old pbrpc server, 2 = sofa server + double test_req_rate; // 0 = no limit + int test_thread_num; + std::string input; + std::string output; + std::string host; // server's ip:port, used by hulu server and sofa server + std::string channel; // server's channel, used by old pbrpc server + //comcfg::Configure conf; + std::string conf_dir; + std::string conf_file; + std::string connection_type; // connection type 0:SINGLE 1:POOLED 2:SHORT + int connect_timeout_ms; // connection timeout in milliseconds + int timeout_ms; // RPC timeout in milliseconds + int max_retry; // Maximum retry times by RPC framework + std::string protocol; + int request_compress_type; // Snappy:1 Gzip:2 Zlib:3 LZ4:4 None:0 + int response_compress_type; // Snappy:1 Gzip:2 Zlib:3 LZ4:4 None:0 + int attachment_size; // Snappy:1 Gzip:2 Zlib:3 LZ4:4 None:0 + bool auth;// Enable Giano authentication + std::string auth_group; + std::string lb_policy; // "rr", "Policy of load balance rr ||random" + std::string proto_file; + std::string proto_includes; + + PressOptions() : + server_type(0), + test_req_rate(0), + test_thread_num(1), + connect_timeout_ms(1000), + timeout_ms(1000), + max_retry(3), + request_compress_type(0), + response_compress_type(0), + attachment_size(0), + auth(false) + {} +}; + +class PressClient { +public: + PressClient(const PressOptions* options, + google::protobuf::compiler::Importer* importer, + google::protobuf::DynamicMessageFactory* factory) { + _method_descriptor = NULL; + _response_prototype = NULL; + _options = options; + _importer = importer; + _factory = factory; + + } + + google::protobuf::Message* get_output_message() { + return _response_prototype->New(); + } + + int init(); + void call_method(brpc::Controller* cntl, + google::protobuf::Message* request, + google::protobuf::Message* response, + google::protobuf::Closure* done); +public: + brpc::Channel _rpc_client; + std::string _attachment; + const PressOptions* _options; + const google::protobuf::MethodDescriptor* _method_descriptor; + const google::protobuf::Message* _response_prototype; + google::protobuf::compiler::Importer* _importer; + google::protobuf::DynamicMessageFactory* _factory; +}; + +class RpcPress { +public: + RpcPress(); + ~RpcPress(); + int init(const PressOptions* options); + int start(); + int stop(); + const PressOptions* options() { return &_options; } + +private: + DISALLOW_COPY_AND_ASSIGN(RpcPress); + + bool new_pbrpc_press_client_by_client_type(int client_type); + void sync_client(); + void handle_response(brpc::Controller* cntl, + google::protobuf::Message* request, + google::protobuf::Message* response, + int64_t start_time_ns); + static void* sync_call_thread(void* arg); + + bvar::LatencyRecorder _latency_recorder; + bvar::Adder _error_count; + bvar::Adder _sent_count; + std::deque _msgs; + PressClient* _pbrpc_client; + PressOptions _options; + bool _started; + bool _stop; + FILE* _output_json; + google::protobuf::compiler::Importer* _importer; + google::protobuf::DynamicMessageFactory _factory; + std::vector _ttid; + brpc::InfoThread _info_thr; +}; +} +#endif // PBRPCPRESS_PBRPC_PRESS_H diff --git a/tools/rpc_replay/CMakeLists.txt b/tools/rpc_replay/CMakeLists.txt new file mode 100644 index 0000000..f1a55ab --- /dev/null +++ b/tools/rpc_replay/CMakeLists.txt @@ -0,0 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +file(GLOB SOURCES CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/tools/rpc_replay/*.cpp") +add_executable(rpc_replay ${SOURCES}) +target_link_libraries(rpc_replay PRIVATE brpc-static ${DYNAMIC_LIB}) +install(TARGETS rpc_replay RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/tools/rpc_replay/info_thread.cpp b/tools/rpc_replay/info_thread.cpp new file mode 100644 index 0000000..f31e597 --- /dev/null +++ b/tools/rpc_replay/info_thread.cpp @@ -0,0 +1,130 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "info_thread.h" + +namespace brpc { + +InfoThread::InfoThread() + : _stop(false) + , _tid(0) { + pthread_mutex_init(&_mutex, NULL); + pthread_cond_init(&_cond, NULL); +} + +InfoThread::~InfoThread() { + pthread_mutex_destroy(&_mutex); + pthread_cond_destroy(&_cond); +} + +void InfoThread::run() { + int64_t i = 0; + int64_t last_sent_count = 0; + int64_t last_succ_count = 0; + int64_t last_error_count = 0; + int64_t start_time = butil::cpuwide_time_us(); + while (!_stop) { + int64_t end_time = 0; + while (!_stop && + (end_time = butil::cpuwide_time_us()) < start_time + 1000000L) { + BAIDU_SCOPED_LOCK(_mutex); + if (!_stop) { + timespec ts = butil::microseconds_to_timespec(end_time); + pthread_cond_timedwait(&_cond, &_mutex, &ts); + } + } + start_time = butil::cpuwide_time_us(); + char buf[64]; + const time_t tm_s = start_time / 1000000L; + struct tm lt; + strftime(buf, sizeof(buf), "%Y/%m/%d-%H:%M:%S", + localtime_r(&tm_s, <)); + + const int64_t cur_sent_count = _options.sent_count->get_value(); + const int64_t cur_succ_count = _options.latency_recorder->count(); + const int64_t cur_error_count = _options.error_count->get_value(); + printf("%s\tsent:%-10dsuccess:%-10derror:%-10dtotal_error:%-10lldtotal_sent:%-10lld\n", + buf, + (int)(cur_sent_count - last_sent_count), + (int)(cur_succ_count - last_succ_count), + (int)(cur_error_count - last_error_count), + (long long)cur_error_count, + (long long)cur_sent_count); + last_sent_count = cur_sent_count; + last_succ_count = cur_succ_count; + last_error_count = cur_error_count; + + if (_stop || ++i % 10 == 0) { + printf("[Latency]\n" + " avg %10lld us\n" + " 50%% %10lld us\n" + " 70%% %10lld us\n" + " 90%% %10lld us\n" + " 95%% %10lld us\n" + " 97%% %10lld us\n" + " 99%% %10lld us\n" + " 99.9%% %10lld us\n" + " 99.99%% %10lld us\n" + " max %10lld us\n", + (long long)_options.latency_recorder->latency(), + (long long)_options.latency_recorder->latency_percentile(0.5), + (long long)_options.latency_recorder->latency_percentile(0.7), + (long long)_options.latency_recorder->latency_percentile(0.9), + (long long)_options.latency_recorder->latency_percentile(0.95), + (long long)_options.latency_recorder->latency_percentile(0.97), + (long long)_options.latency_recorder->latency_percentile(0.99), + (long long)_options.latency_recorder->latency_percentile(0.999), + (long long)_options.latency_recorder->latency_percentile(0.9999), + (long long)_options.latency_recorder->max_latency()); + } + } +} + +static void* run_info_thread(void* arg) { + ((InfoThread*)arg)->run(); + return NULL; +} + +bool InfoThread::start(const InfoThreadOptions& options) { + if (options.latency_recorder == NULL || + options.error_count == NULL || + options.sent_count == NULL) { + LOG(ERROR) << "Some required options are NULL"; + return false; + } + _options = options; + _stop = false; + if (pthread_create(&_tid, NULL, run_info_thread, this) != 0) { + LOG(ERROR) << "Fail to create info_thread"; + return false; + } + return true; +} + +void InfoThread::stop() { + { + BAIDU_SCOPED_LOCK(_mutex); + if (_stop) { + return; + } + _stop = true; + pthread_cond_signal(&_cond); + } + pthread_join(_tid, NULL); +} + +} // brpc diff --git a/tools/rpc_replay/info_thread.h b/tools/rpc_replay/info_thread.h new file mode 100644 index 0000000..bec30f2 --- /dev/null +++ b/tools/rpc_replay/info_thread.h @@ -0,0 +1,55 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#ifndef BRPC_RPC_REPLAY_INFO_THREAD_H +#define BRPC_RPC_REPLAY_INFO_THREAD_H + +#include +#include + +namespace brpc { + +struct InfoThreadOptions { + bvar::LatencyRecorder* latency_recorder; + bvar::Adder* sent_count; + bvar::Adder* error_count; + + InfoThreadOptions() + : latency_recorder(NULL) + , sent_count(NULL) + , error_count(NULL) {} +}; + +class InfoThread { +public: + InfoThread(); + ~InfoThread(); + void run(); + bool start(const InfoThreadOptions&); + void stop(); + +private: + bool _stop; + InfoThreadOptions _options; + pthread_mutex_t _mutex; + pthread_cond_t _cond; + pthread_t _tid; +}; + +} // brpc + +#endif //BRPC_RPC_REPLAY_INFO_THREAD_H diff --git a/tools/rpc_replay/rpc_replay.cpp b/tools/rpc_replay/rpc_replay.cpp new file mode 100644 index 0000000..395da6b --- /dev/null +++ b/tools/rpc_replay/rpc_replay.cpp @@ -0,0 +1,301 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "brpc/options.pb.h" +#include "info_thread.h" + +DEFINE_string(dir, "", "The directory of dumped requests"); +DEFINE_int32(times, 1, "Repeat replaying for so many times"); +DEFINE_int32(qps, 0, "Limit QPS if this flag is positive"); +DEFINE_int32(thread_num, 0, "Number of threads for replaying"); +DEFINE_bool(use_bthread, true, "Use bthread to replay"); +DEFINE_string(connection_type, "", "Connection type, choose automatically " + "according to protocol by default"); +DEFINE_string(server, "0.0.0.0:8002", "IP Address of server"); +DEFINE_string(load_balancer, "", "The algorithm for load balancing"); +DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds"); +DEFINE_int32(max_retry, 3, "Maximum retry times"); +DEFINE_int32(dummy_port, 8899, "Port of dummy server(to monitor replaying)"); +DEFINE_string(http_host, "", "Host field for http protocol"); + +bvar::LatencyRecorder g_latency_recorder("rpc_replay"); +bvar::Adder g_error_count("rpc_replay_error_count"); +bvar::Adder g_sent_count; + +// Include channels for all protocols that support both client and server. +class ChannelGroup { +public: + int Init(); + + ~ChannelGroup(); + + // Get channel by protocol type. + brpc::Channel* channel(brpc::ProtocolType type) { + if ((size_t)type < _chans.size()) { + return _chans[(size_t)type]; + } + return NULL; + } + +private: + std::vector _chans; +}; + +int ChannelGroup::Init() { + { + // force global initialization of rpc. + brpc::Channel dummy_channel; + } + std::vector > protocols; + brpc::ListProtocols(&protocols); + size_t max_protocol_size = 0; + for (size_t i = 0; i < protocols.size(); ++i) { + max_protocol_size = std::max(max_protocol_size, + (size_t)protocols[i].first); + } + _chans.resize(max_protocol_size + 1); + for (size_t i = 0; i < protocols.size(); ++i) { + const brpc::ProtocolType protocol_type = protocols[i].first; + const brpc::Protocol protocol = protocols[i].second; + brpc::ChannelOptions options; + options.protocol = protocol_type; + options.connection_type = FLAGS_connection_type; + options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/; + options.max_retry = FLAGS_max_retry; + if ((options.connection_type == brpc::CONNECTION_TYPE_UNKNOWN || + options.connection_type & protocol.supported_connection_type) && + protocol.support_client() && + protocol.support_server()) { + brpc::Channel* chan = new brpc::Channel; + if (chan->Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), + &options) != 0) { + LOG(ERROR) << "Fail to initialize channel"; + delete chan; + return -1; + } + _chans[protocol_type] = chan; + } + } + return 0; +} + +ChannelGroup::~ChannelGroup() { + for (size_t i = 0; i < _chans.size(); ++i) { + delete _chans[i]; + } + _chans.clear(); +} + +static void handle_response(brpc::Controller* cntl, int64_t start_time, + bool sleep_on_error/*note*/) { + // TODO(gejun): some bthreads are starved when new bthreads are created + // continuously, which happens when server is down and RPC keeps failing. + // Sleep a while on error to avoid that now. + const int64_t end_time = butil::cpuwide_time_us(); + const int64_t elp = end_time - start_time; + if (!cntl->Failed()) { + g_latency_recorder << elp; + } else { + g_error_count << 1; + if (sleep_on_error) { + bthread_usleep(10000); + } + } + delete cntl; +} + +butil::atomic g_thread_offset(0); + +static void* replay_thread(void* arg) { + ChannelGroup* chan_group = static_cast(arg); + const int thread_offset = g_thread_offset.fetch_add(1, butil::memory_order_relaxed); + double req_rate = FLAGS_qps / (double)FLAGS_thread_num; + brpc::SerializedRequest req; + brpc::NsheadMessage nshead_req; + int64_t last_expected_time = butil::monotonic_time_ns(); + const int64_t interval = (int64_t) (1000000000L / req_rate); + // the max tolerant delay between end_time and expected_time. 10ms or 10 intervals + int64_t max_tolerant_delay = std::max((int64_t) 10000000L, 10 * interval); + for (int i = 0; !brpc::IsAskedToQuit() && i < FLAGS_times; ++i) { + brpc::SampleIterator it(FLAGS_dir); + int j = 0; + for (brpc::SampledRequest* sample = it.Next(); + !brpc::IsAskedToQuit() && sample != NULL; sample = it.Next(), ++j) { + std::unique_ptr sample_guard(sample); + if ((j % FLAGS_thread_num) != thread_offset) { + continue; + } + brpc::Channel* chan = + chan_group->channel(sample->meta.protocol_type()); + if (chan == NULL) { + LOG(ERROR) << "No channel on protocol=" + << sample->meta.protocol_type(); + continue; + } + + brpc::Controller* cntl = new brpc::Controller; + req.Clear(); + + google::protobuf::Message* req_ptr = &req; + cntl->reset_sampled_request(sample_guard.release()); + if (sample->meta.protocol_type() == brpc::PROTOCOL_HTTP) { + brpc::HttpMessage http_message; + http_message.ParseFromIOBuf(sample->request); + cntl->http_request().Swap(http_message.header()); + if (!FLAGS_http_host.empty()) { + // reset Host in header + cntl->http_request().SetHeader("Host", FLAGS_http_host); + } + cntl->request_attachment() = http_message.body().movable(); + req_ptr = NULL; + } else if (sample->meta.protocol_type() == brpc::PROTOCOL_NSHEAD) { + nshead_req.Clear(); + memcpy(&nshead_req.head, sample->meta.nshead().c_str(), sample->meta.nshead().length()); + nshead_req.body = sample->request; + req_ptr = &nshead_req; + } else if (sample->meta.attachment_size() > 0) { + sample->request.cutn( + &req.serialized_data(), + sample->request.size() - sample->meta.attachment_size()); + cntl->request_attachment() = sample->request.movable(); + } else { + req.serialized_data() = sample->request.movable(); + } + g_sent_count << 1; + const int64_t start_time = butil::cpuwide_time_us(); + if (FLAGS_qps <= 0) { + chan->CallMethod(NULL/*use rpc_dump_context in cntl instead*/, + cntl, req_ptr, NULL/*ignore response*/, NULL); + handle_response(cntl, start_time, true); + } else { + google::protobuf::Closure* done = + brpc::NewCallback(handle_response, cntl, start_time, false); + chan->CallMethod(NULL/*use rpc_dump_context in cntl instead*/, + cntl, req_ptr, NULL/*ignore response*/, done); + int64_t end_time = butil::monotonic_time_ns(); + int64_t expected_time = last_expected_time + interval; + if (end_time < expected_time) { + usleep((expected_time - end_time)/1000); + } + if (end_time - expected_time > max_tolerant_delay) { + expected_time = end_time; + } + last_expected_time = expected_time; + } + } + } + return NULL; +} + +int main(int argc, char* argv[]) { + // Parse gflags. We recommend you to use gflags as well. + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_dir.empty() || + !butil::DirectoryExists(butil::FilePath(FLAGS_dir))) { + LOG(ERROR) << "--dir= is required"; + return -1; + } + + if (FLAGS_dummy_port >= 0) { + brpc::StartDummyServerAt(FLAGS_dummy_port); + } + + ChannelGroup chan_group; + if (chan_group.Init() != 0) { + LOG(ERROR) << "Fail to init ChannelGroup"; + return -1; + } + + if (FLAGS_thread_num <= 0) { + if (FLAGS_qps <= 0) { // unlimited qps + FLAGS_thread_num = 50; + } else { + FLAGS_thread_num = FLAGS_qps / 10000; + if (FLAGS_thread_num < 1) { + FLAGS_thread_num = 1; + } + if (FLAGS_thread_num > 50) { + FLAGS_thread_num = 50; + } + } + } + + const int rate_limit_per_thread = 1000000; + int req_rate_per_thread = FLAGS_qps / FLAGS_thread_num; + if (req_rate_per_thread > rate_limit_per_thread) { + LOG(ERROR) << "req_rate: " << (int64_t) req_rate_per_thread << " is too large in one thread. The rate limit is " + << rate_limit_per_thread << " in one thread"; + return -1; + } + + std::vector bids; + std::vector pids; + if (!FLAGS_use_bthread) { + pids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (pthread_create(&pids[i], NULL, replay_thread, &chan_group) != 0) { + LOG(ERROR) << "Fail to create pthread"; + return -1; + } + } + } else { + bids.resize(FLAGS_thread_num); + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (bthread_start_background( + &bids[i], NULL, replay_thread, &chan_group) != 0) { + LOG(ERROR) << "Fail to create bthread"; + return -1; + } + } + } + brpc::InfoThread info_thr; + brpc::InfoThreadOptions info_thr_opt; + info_thr_opt.latency_recorder = &g_latency_recorder; + info_thr_opt.error_count = &g_error_count; + info_thr_opt.sent_count = &g_sent_count; + + if (!info_thr.start(info_thr_opt)) { + LOG(ERROR) << "Fail to create info_thread"; + return -1; + } + + for (int i = 0; i < FLAGS_thread_num; ++i) { + if (!FLAGS_use_bthread) { + pthread_join(pids[i], NULL); + } else { + bthread_join(bids[i], NULL); + } + } + info_thr.stop(); + + return 0; +} diff --git a/tools/rpc_view/CMakeLists.txt b/tools/rpc_view/CMakeLists.txt new file mode 100644 index 0000000..1fe6701 --- /dev/null +++ b/tools/rpc_view/CMakeLists.txt @@ -0,0 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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(FindProtobuf) +protobuf_generate_cpp(PROTO_SRC PROTO_HEADER view.proto) + +add_executable(rpc_view rpc_view.cpp ${PROTO_SRC}) +target_include_directories(rpc_view PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +target_link_libraries(rpc_view PRIVATE brpc-static ${DYNAMIC_LIB}) +install(TARGETS rpc_view RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/tools/rpc_view/rpc_view.cpp b/tools/rpc_view/rpc_view.cpp new file mode 100644 index 0000000..cdfb523 --- /dev/null +++ b/tools/rpc_view/rpc_view.cpp @@ -0,0 +1,176 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include +#include +#include +#include "view.pb.h" + +DEFINE_int32(port, 8888, "TCP Port of this server"); +DEFINE_string(target, "", "The server to view"); +DEFINE_int32(timeout_ms, 5000, "Timeout for calling the server to view"); + +// handle HTTP response of accessing builtin services of the target server. +static void handle_response(brpc::Controller* client_cntl, + std::string target, + brpc::Controller* server_cntl, + google::protobuf::Closure* server_done) { + // Copy all headers. The "Content-Length" will be overwriteen. + server_cntl->http_response() = client_cntl->http_response(); + // Copy content. + server_cntl->response_attachment() = client_cntl->response_attachment(); + // Insert "rpc_view: " before so that users are always + // visually notified with target server w/o confusions. + butil::IOBuf& content = server_cntl->response_attachment(); + butil::IOBuf before_body; + if (content.cut_until(&before_body, "") == 0) { + before_body.append( + "\n" + ""); + before_body.append(content); + content = before_body; + } + // Notice that we don't set RPC to failed on http errors because we + // want to pass unchanged content to the users otherwise RPC replaces + // the content with ErrorText. + if (client_cntl->Failed() && + client_cntl->ErrorCode() != brpc::EHTTP) { + server_cntl->SetFailed(client_cntl->ErrorCode(), + "%s", client_cntl->ErrorText().c_str()); + } + delete client_cntl; + server_done->Run(); +} + +// A http_master_service. +class ViewServiceImpl : public ViewService { +public: + ViewServiceImpl() {} + virtual ~ViewServiceImpl() {} + virtual void default_method(google::protobuf::RpcController* cntl_base, + const HttpRequest*, + HttpResponse*, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* server_cntl = + static_cast(cntl_base); + + // Get or set target. Notice that we don't access FLAGS_target directly + // which is thread-unsafe (for string flags). + std::string target; + const std::string* newtarget = + server_cntl->http_request().uri().GetQuery("changetarget"); + if (newtarget) { + if (GFLAGS_NAMESPACE::SetCommandLineOption("target", newtarget->c_str()).empty()) { + server_cntl->SetFailed("Fail to change value of -target"); + return; + } + target = *newtarget; + } else { + if (!GFLAGS_NAMESPACE::GetCommandLineOption("target", &target)) { + server_cntl->SetFailed("Fail to get value of -target"); + return; + } + } + + // Create the http channel on-the-fly. Notice that we've set + // `defer_close_second' in main() so that dtor of channels do not + // close connections immediately and ad-hoc creation of channels + // often reuses the not-yet-closed connections. + brpc::Channel http_chan; + brpc::ChannelOptions http_chan_opt; + http_chan_opt.protocol = brpc::PROTOCOL_HTTP; + if (http_chan.Init(target.c_str(), &http_chan_opt) != 0) { + server_cntl->SetFailed(brpc::EINTERNAL, + "Fail to connect to %s", target.c_str()); + return; + } + + // Remove "Accept-Encoding". We need to insert additional texts + // before , preventing the server from compressing the content + // simplifies our code. The additional bandwidth consumption shouldn't + // be an issue for infrequent checking out of builtin services pages. + server_cntl->http_request().RemoveHeader("Accept-Encoding"); + + brpc::Controller* client_cntl = new brpc::Controller; + client_cntl->http_request() = server_cntl->http_request(); + // Remove "Host" so that RPC will laterly serialize the (correct) + // target server in. + client_cntl->http_request().RemoveHeader("host"); + + // Setup the URI. + const brpc::URI& server_uri = server_cntl->http_request().uri(); + std::string uri = server_uri.path(); + if (!server_uri.query().empty()) { + uri.push_back('?'); + uri.append(server_uri.query()); + } + if (!server_uri.fragment().empty()) { + uri.push_back('#'); + uri.append(server_uri.fragment()); + } + client_cntl->http_request().uri() = uri; + + // /hotspots pages may take a long time to finish, since they all have + // query "seconds", we set the timeout to be longer than "seconds". + const std::string* seconds = + server_cntl->http_request().uri().GetQuery("seconds"); + int64_t timeout_ms = FLAGS_timeout_ms; + if (seconds) { + timeout_ms += atoll(seconds->c_str()) * 1000; + } + client_cntl->set_timeout_ms(timeout_ms); + + // Keep content as it is. + client_cntl->request_attachment() = server_cntl->request_attachment(); + + http_chan.CallMethod(NULL, client_cntl, NULL, NULL, + brpc::NewCallback( + handle_response, client_cntl, target, + server_cntl, done_guard.release())); + } +}; + +int main(int argc, char* argv[]) { + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + if (FLAGS_target.empty() && + (argc != 2 || + GFLAGS_NAMESPACE::SetCommandLineOption("target", argv[1]).empty())) { + LOG(ERROR) << "Usage: ./rpc_view :"; + return -1; + } + // This keeps ad-hoc creation of channels reuse previous connections. + GFLAGS_NAMESPACE::SetCommandLineOption("defer_close_second", "10"); + + brpc::Server server; + server.set_version("rpc_view_server"); + brpc::ServerOptions server_opt; + server_opt.http_master_service = new ViewServiceImpl; + if (server.Start(FLAGS_port, &server_opt) != 0) { + LOG(ERROR) << "Fail to start ViewServer"; + return -1; + } + server.RunUntilAskedToQuit(); + return 0; +} diff --git a/tools/rpc_view/view.proto b/tools/rpc_view/view.proto new file mode 100644 index 0000000..c533c95 --- /dev/null +++ b/tools/rpc_view/view.proto @@ -0,0 +1,26 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax="proto2"; +option cc_generic_services = true; + +message HttpRequest {}; +message HttpResponse {}; + +service ViewService { + rpc default_method(HttpRequest) returns (HttpResponse); +}; diff --git a/tools/trackme_server/CMakeLists.txt b/tools/trackme_server/CMakeLists.txt new file mode 100644 index 0000000..ca40e41 --- /dev/null +++ b/tools/trackme_server/CMakeLists.txt @@ -0,0 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +add_executable(trackme_server trackme_server.cpp) +target_link_libraries(trackme_server PRIVATE brpc-static ${DYNAMIC_LIB}) +install(TARGETS trackme_server RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/tools/trackme_server/trackme_server.cpp b/tools/trackme_server/trackme_server.cpp new file mode 100644 index 0000000..76e09da --- /dev/null +++ b/tools/trackme_server/trackme_server.cpp @@ -0,0 +1,280 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + + +// A server to receive TrackMeRequest and send back TrackMeResponse. + +#include +#include +#include +#include +#include +#include +#include + +DEFINE_string(bug_file, "./bugs", "A file containing revision and information of bugs"); +DEFINE_int32(port, 8877, "TCP Port of this server"); +DEFINE_int32(reporting_interval, 300, "Reporting interval of clients"); + +struct RevisionInfo { + int64_t min_rev; + int64_t max_rev; + brpc::TrackMeSeverity severity; + std::string error_text; +}; + +// Load bugs from a file periodically. +class BugsLoader { +public: + BugsLoader(); + bool start(const std::string& bugs_file); + void stop(); + bool find(int64_t revision, brpc::TrackMeResponse* response); +private: + void load_bugs(); + void run(); + static void* run_this(void* arg); + typedef std::vector BugList; + std::string _bugs_file; + bool _started; + bool _stop; + pthread_t _tid; + std::shared_ptr _bug_list; +}; + +class TrackMeServiceImpl : public brpc::TrackMeService { +public: + explicit TrackMeServiceImpl(BugsLoader* bugs) : _bugs(bugs) { + } + ~TrackMeServiceImpl() {} + void TrackMe(google::protobuf::RpcController* cntl_base, + const brpc::TrackMeRequest* request, + brpc::TrackMeResponse* response, + google::protobuf::Closure* done) { + brpc::ClosureGuard done_guard(done); + brpc::Controller* cntl = (brpc::Controller*)cntl_base; + // Set to OK by default. + response->set_severity(brpc::TrackMeOK); + // Check if the version is affected by bugs if client set it. + if (request->has_rpc_version()) { + _bugs->find(request->rpc_version(), response); + } + response->set_new_interval(FLAGS_reporting_interval); + butil::EndPoint server_addr; + CHECK_EQ(0, butil::str2endpoint(request->server_addr().c_str(), &server_addr)); + // NOTE(gejun): The ip reported is inaccessible in many cases, use + // remote_side instead right now. + server_addr.ip = cntl->remote_side().ip; + LOG(INFO) << "Pinged by " << server_addr << " (r" + << request->rpc_version() << ")"; + } + +private: + BugsLoader* _bugs; +}; + +int main(int argc, char* argv[]) { + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); + + brpc::Server server; + server.set_version("trackme_server"); + BugsLoader bugs; + if (!bugs.start(FLAGS_bug_file)) { + LOG(ERROR) << "Fail to start BugsLoader"; + return -1; + } + TrackMeServiceImpl echo_service_impl(&bugs); + if (server.AddService(&echo_service_impl, + brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { + LOG(ERROR) << "Fail to add service"; + return -1; + } + brpc::ServerOptions options; + // I've noticed that many connections do not report. Don't know the + // root cause yet. Set the idle_time to keep connections clean. + options.idle_timeout_sec = FLAGS_reporting_interval * 2; + if (server.Start(FLAGS_port, &options) != 0) { + LOG(ERROR) << "Fail to start TrackMeServer"; + return -1; + } + server.RunUntilAskedToQuit(); + bugs.stop(); + return 0; +} + +BugsLoader::BugsLoader() + : _started(false) + , _stop(false) + , _tid(0) +{} + +bool BugsLoader::start(const std::string& bugs_file) { + _bugs_file = bugs_file; + if (pthread_create(&_tid, NULL, run_this, this) != 0) { + LOG(ERROR) << "Fail to create loading thread"; + return false; + } + _started = true; + return true; +} + +void BugsLoader::stop() { + if (!_started) { + return; + } + _stop = true; + pthread_join(_tid, NULL); +} + +void* BugsLoader::run_this(void* arg) { + ((BugsLoader*)arg)->run(); + return NULL; +} + +void BugsLoader::run() { + // Check status of _bugs_files periodically. + butil::FileWatcher fw; + if (fw.init(_bugs_file.c_str()) < 0) { + LOG(ERROR) << "Fail to init FileWatcher on `" << _bugs_file << "'"; + return; + } + while (!_stop) { + load_bugs(); + while (!_stop) { + butil::FileWatcher::Change change = fw.check_and_consume(); + if (change > 0) { + break; + } + if (change < 0) { + LOG(ERROR) << "`" << _bugs_file << "' was deleted"; + } + sleep(1); + } + } + +} + +void BugsLoader::load_bugs() { + butil::ScopedFILE fp(fopen(_bugs_file.c_str(), "r")); + if (!fp) { + PLOG(WARNING) << "Fail to open `" << _bugs_file << '\''; + return; + } + + char* line = NULL; + size_t line_len = 0; + ssize_t nr = 0; + int nline = 0; + std::unique_ptr m(new BugList); + while ((nr = getline(&line, &line_len, fp.get())) != -1) { + ++nline; + if (line[nr - 1] == '\n') { // remove ending newline + --nr; + } + // line format: + // min_rev max_rev severity description + butil::StringMultiSplitter sp(line, line + nr, " \t"); + if (!sp) { + continue; + } + long long min_rev; + if (sp.to_longlong(&min_rev)) { + LOG(WARNING) << "[line" << nline << "] Fail to parse column1 as min_rev"; + continue; + } + ++sp; + long long max_rev; + if (!sp || sp.to_longlong(&max_rev)) { + LOG(WARNING) << "[line" << nline << "] Fail to parse column2 as max_rev"; + continue; + } + if (max_rev < min_rev) { + LOG(WARNING) << "[line" << nline << "] max_rev=" << max_rev + << " is less than min_rev=" << min_rev; + continue; + } + ++sp; + if (!sp) { + LOG(WARNING) << "[line" << nline << "] Fail to parse column3 as severity"; + continue; + } + brpc::TrackMeSeverity severity = brpc::TrackMeOK; + butil::StringPiece severity_str(sp.field(), sp.length()); + if (severity_str == "f" || severity_str == "F") { + severity = brpc::TrackMeFatal; + } else if (severity_str == "w" || severity_str == "W") {\ + severity = brpc::TrackMeWarning; + } else { + LOG(WARNING) << "[line" << nline << "] Invalid severity=" << severity_str; + continue; + } + ++sp; + if (!sp) { + LOG(WARNING) << "[line" << nline << "] Fail to parse column4 as string"; + continue; + } + // Treat everything until end of the line as description. So don't add + // comments starting with # or //, they are not recognized. + butil::StringPiece description(sp.field(), line + nr - sp.field()); + RevisionInfo info; + info.min_rev = min_rev; + info.max_rev = max_rev; + info.severity = severity; + info.error_text.assign(description.data(), description.size()); + m->push_back(info); + } + LOG(INFO) << "Loaded " << m->size() << " bugs"; + free(line); + // Just reseting the shared_ptr. Previous BugList will be destroyed when + // no threads reference it. + _bug_list.reset(m.release()); +} + +bool BugsLoader::find(int64_t revision, brpc::TrackMeResponse* response) { + // Add reference to make sure the bug list is not deleted. + std::shared_ptr local_list = _bug_list; + if (local_list.get() == NULL) { + return false; + } + // Reading the list in this function is always safe because a BugList + // is never changed after creation. + bool found = false; + for (size_t i = 0; i < local_list->size(); ++i) { + const RevisionInfo & info = (*local_list)[i]; + if (info.min_rev <= revision && revision <= info.max_rev) { + found = true; + if (info.severity > response->severity()) { + response->set_severity(info.severity); + } + if (info.severity != brpc::TrackMeOK) { + std::string* error = response->mutable_error_text(); + char prefix[64]; + if (info.min_rev != info.max_rev) { + snprintf(prefix, sizeof(prefix), "[r%lld-r%lld] ", + (long long)info.min_rev, (long long)info.max_rev); + } else { + snprintf(prefix, sizeof(prefix), "[r%lld] ", + (long long)info.min_rev); + } + error->append(prefix); + error->append(info.error_text); + error->append("; "); + } + } + } + return found; +} diff --git a/tools/wireshark_baidu_std.lua b/tools/wireshark_baidu_std.lua new file mode 100644 index 0000000..fdfb145 --- /dev/null +++ b/tools/wireshark_baidu_std.lua @@ -0,0 +1,325 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you 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. +-- + +-------------------------------------------------------------------------------- +-- function +-- Proto.new(name, desc) +-- proto.dissector +-- proto.fields +-- proto.prefs +-- proto.prefs_changed +-- proto.init +-- proto.name +-- proto.description +-------------------------------------------------------------------------------- + +local plugin_info = { + name = "baidu", + version = "0.1.0", + description = "baidu_std" +} + +local major, minor, patch = get_version():match("(%d+)%.(%d+)%.(%d+)") +local version_code = tonumber(major) * 1000 * 1000 + tonumber(minor) * 1000 + tonumber(patch) + +local proto = Proto.new(plugin_info.name, plugin_info.description) + +local LM_DBG = function(fmt, ...) + if (tonumber(major) < 3) then + critical(table.concat({ plugin_info.name:upper() .. ":", fmt }, ' '):format(...)) + else + print (table.concat({ plugin_info.name:upper() .. ":", fmt }, ' '):format(...)) + end +end + +LM_DBG("Wireshark version =", get_version()) +LM_DBG("Lua version =", _VERSION) + +-------------------------------------------------------------------------------- + +local MAGIC_CODE_PRPC = "PRPC" +local MAGIC_CODE_STRM = "STRM" + +local PROTO_HEADER_LENGTH = 12 + + +local pf_magic = ProtoField.string(plugin_info.name .. ".magic", + "Magic Code", BASE_NONE) +local pf_body_size = ProtoField.uint32(plugin_info.name .. ".body_size", + "Body Size", BASE_DEC) +local pf_meta_size = ProtoField.uint32(plugin_info.name .. ".meta_size", + "Meta Size", BASE_DEC) + +-- used to customize tree item +local pf_any = ProtoField.bytes (plugin_info.name .. ".any", + "Any", BASE_NONE) + +proto.fields = { + pf_magic, + pf_body_size, + pf_meta_size, + pf_any +} + +---------------------------------------- +-- ref fields +local proto_f_magic_code = Field.new(plugin_info.name .. ".magic") +local proto_f_body_size = Field.new(plugin_info.name .. ".body_size") +local proto_f_meta_size = Field.new(plugin_info.name .. ".meta_size") + +local proto_f_protobuf_field_name = Field.new("protobuf.field.name") +local proto_f_protobuf_field_value = Field.new("protobuf.field.value") + +---------------------------------------- +-- protobuf dissector +-- Note: +-- options.proto, baidu_rpc_meta.proto and streaming_rpc_meta.proto +-- should be put in Wireshark Protobuf Search Paths. +local protobuf_dissector = Dissector.get("protobuf") + +---------------------------------------- +-- declare functions + +local check_length = function() end +local dissect_proto = function() end + +---------------------------------------- +-- main dissector +function proto.dissector(tvbuf, pktinfo, root) + local pktlen = tvbuf:len() + + local bytes_consumed = 0 + + while bytes_consumed < pktlen do + local result = dissect_proto(tvbuf, pktinfo, root, bytes_consumed) + + if result > 0 then + bytes_consumed = bytes_consumed + result + elseif result == 0 then + -- hit an error + return 0 + else + pktinfo.desegment_offset = bytes_consumed + -- require more bytes + pktinfo.desegment_len = -result + + return pktlen + end + end + + return bytes_consumed +end + +-------------------------------------------------------------------------------- +-- heuristic +local function heur_dissect_proto(tvbuf, pktinfo, root) + LM_DBG("heur brpc: pkg number: " .. pktinfo.number) + if (tvbuf:len() < PROTO_HEADER_LENGTH) then + LM_DBG("too short: pkg number: " .. pktinfo.number) + return false + end + + local magic = tvbuf:range(0, 4):string() + -- for range dissectors + if magic ~= MAGIC_CODE_PRPC and maigc ~= MAGIC_CODE_STRM then + LM_DBG("invalid magic code: pkg number: " .. pktinfo.number) + return false + end + + proto.dissector(tvbuf, pktinfo, root) + + pktinfo.conversation = proto + + return true +end +proto:register_heuristic("tcp", heur_dissect_proto) + +-------------------------------------------------------------------------------- + +local correlation_method_map = {} + +local store_method = function(correlation_id, method) + -- TODO: pop items + correlation_method_map[correlation_id] = method +end + +local load_method = function(correlation_id) + return correlation_method_map[correlation_id] +end + +-- check packet length, return length of packet if valid +check_length = function(tvbuf, offset) + local msglen = tvbuf:len() - offset + + if msglen ~= tvbuf:reported_length_remaining(offset) then + -- captured packets are being sliced/cut-off, so don't try to desegment/reassemble + LM_WARN("Captured packet was shorter than original, can't reassemble") + return 0 + end + + if msglen < PROTO_HEADER_LENGTH then + -- we need more bytes, so tell the main dissector function that we + -- didn't dissect anything, and we need an unknown number of more + -- bytes (which is what "DESEGMENT_ONE_MORE_SEGMENT" is used for) + return -DESEGMENT_ONE_MORE_SEGMENT + end + + -- if we got here, then we know we have enough bytes in the Tvb buffer + -- to at least figure out whether this is valid baidu_std packet + + local magic = tvbuf:range(offset, 4):string() + if magic ~= MAGIC_CODE_PRPC and magic ~= MAGIC_CODE_STRM then + return 0 + end + + -- big endian + local packet_size = tvbuf:range(offset+4, 4):uint() + PROTO_HEADER_LENGTH + if msglen < packet_size then + LM_DBG("Need more bytes to desegment full baidu_std packet") + return -(packet_size - msglen) + end + + return packet_size +end + +-- convert uint32 integer to byte array +function uint32_to_byte_array(value) + local bytes = {} + while value > 0 do + table.insert(bytes, bit32.band(value, 0xFF)) + value = bit32.rshift(value, 8) + end + return bytes +end + +-- varint decoding function +function decode_varint(value) + local bytes = uint32_to_byte_array(value) + local result = 0 + local shift = 0 + + for _, byte in ipairs(bytes) do + result = bit32.bor(result, bit32.lshift(bit32.band(byte, 0x7F), shift)) + if bit32.band(byte, 0x80) == 0 then + return result + end + shift = shift + 7 + end + + LM_DBG("Malformed Varint encoding") +end + +---------------------------------------- +dissect_proto = function(tvbuf, pktinfo, root, offset) + local len = check_length(tvbuf, offset) + if len <= 0 then + return len + end + + local tree = root:add(proto, tvbuf:range(offset, len)) + + tree:add(pf_magic, tvbuf:range(offset, 4)) + tree:add(pf_body_size, tvbuf:range(offset+4, 4)) + tree:add(pf_meta_size, tvbuf:range(offset+8, 4)) + + local magic_code = tvbuf:range(offset, 4):string() + local meta_size = tvbuf:range(offset + 8, 4):uint() + local body_size = tvbuf:range(offset + 4, 4):uint() + local tvb_meta = tvbuf:range(offset + PROTO_HEADER_LENGTH, meta_size):tvb() + if magic_code == MAGIC_CODE_PRPC then + -- update 'Protocol' field + if offset == 0 then + pktinfo.cols.protocol:set("ProtoBuf") + end + -- dissect rpc meta fields + pktinfo.private["pb_msg_type"] = "message,brpc.policy.RpcMeta" + + -- solve the problem of parsing errors when multiple RPCs are included in a packet + local protobuf_field_names = { proto_f_protobuf_field_name() } + local pre_field_nums = #protobuf_field_names + protobuf_dissector:call(tvb_meta, pktinfo, tree) + -- https://ask.wireshark.org/question/31800/protobuf-dissector-with-nested-structures/?answer=31924#post-id-31924 + protobuf_field_names = { proto_f_protobuf_field_name() } + local cur_field_nums = #protobuf_field_names + local protobuf_field_values = { proto_f_protobuf_field_value() } + + local direction, method + local service_name, method_name, correlation_id, attachment_size + + -- only process new fields added after the previous call to protobuf_dissector + for k = pre_field_nums + 1, cur_field_nums do + local v = protobuf_field_names[k] + if v.value == "request" then + direction = "request" + elseif v.value == "response" then + direction = "response" + elseif v.value == "service_name" then + service_name = protobuf_field_values[k].range:string(ENC_UTF8) + elseif v.value == "method_name" then + method_name = protobuf_field_values[k].range:string(ENC_UTF8) + elseif v.value == "correlation_id" then + correlation_id = protobuf_field_values[k].range:uint64() + elseif v.value == "attachment_size" then + attachment_size = protobuf_field_values[k].range:le_uint() + end + end + + if direction == "request" then + method = service_name .. "/" .. method_name + -- NOTE: convert uint64 to string to be used as key of table + store_method(tostring(correlation_id), method) + elseif direction == "response" then + method = load_method(tostring(correlation_id)) + end + + if attachment_size then + attachment_size = decode_varint(attachment_size) + else + attachment_size = 0 + end + + if method ~= nil then + -- dissect rpc body + local tvb_body = tvbuf:range(offset + PROTO_HEADER_LENGTH + meta_size, body_size - meta_size - attachment_size):tvb() + pktinfo.private["pb_msg_type"] = "application/grpc,/" .. method .. "," .. direction + protobuf_dissector:call(tvb_body, pktinfo, tree) + end + elseif magic_code == MAGIC_CODE_STRM then + -- dissect streaming meta fields + pktinfo.private["pb_msg_type"] = "message,brpc.StreamFrameMeta" + protobuf_dissector:call(tvb_meta, pktinfo, tree) + end + + -- body fields are business related, customized here + + return len +end + +-------------------------------------------------------------------------------- +-- Editor modelines +-- +-- Local variables: +-- c-basic-offset: 4 +-- tab-width: 4 +-- indent-tab-mode: nil +-- End: +-- +-- kate: indent-width 4; tab-width 4; +-- vim: tabstop=4:softtabstop=4:shiftwidth=4:expandtab +-- :indentSize=4:tabSize=4:noTabs=true